Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
49 commits
Select commit Hold shift + click to select a range
cb79f54
feat: add applications management from system inventory
edospadoni Jan 19, 2026
830f74b
feat: added custom-entity filters
edospadoni Jan 19, 2026
3a351ec
feat: added suspension for organizations
edospadoni Jan 20, 2026
ee6ebf0
fix: migration application schema
edospadoni Jan 20, 2026
2de98ea
feat: add node_label field to applications
edospadoni Jan 20, 2026
97f7074
fix: lower_case roles
edospadoni Jan 20, 2026
3b91241
docs: add detailed comments to all database tables in schema.sql
edospadoni Jan 21, 2026
dca6be8
fix: update README for db-migrate step
edospadoni Jan 21, 2026
a072209
fix: use same pagination pattern
edospadoni Jan 21, 2026
333f3b6
fix: order fix if null
edospadoni Jan 21, 2026
d5b5f00
fix: added trim in search
edospadoni Jan 21, 2026
503672e
fix: sort_by correct fields
edospadoni Jan 21, 2026
5201582
feat(collect): parse modules from inventory to applications
edospadoni Jan 22, 2026
4073621
fix: return org info with correct ids
edospadoni Jan 22, 2026
e3444ca
fix: return database id for organizations
edospadoni Jan 23, 2026
45518b0
fix: return both local id and logto id
edospadoni Jan 23, 2026
a88d620
chore: set NETH- as new system key
edospadoni Jan 23, 2026
995b552
fix: better openapi description
edospadoni Jan 24, 2026
5c658f6
chore: remove useless schema fields
edospadoni Jan 26, 2026
311939b
fix: update collect field to match new inventory
edospadoni Jan 26, 2026
5a35e64
fix: inventory_diffs schema migration
edospadoni Jan 26, 2026
6085309
fix: merge config.yml in a single file
edospadoni Jan 27, 2026
829e3b0
chore: remove useless config defaults
edospadoni Jan 27, 2026
b563df9
update pinia-colada and pinia-colada devtools
andre8244 Jan 8, 2026
5e5d27e
show pinia-colada devtools on production environment
andre8244 Jan 8, 2026
775dff9
fix style of system secret
andre8244 Jan 8, 2026
e7c597c
fix system notes
andre8244 Jan 8, 2026
e593e92
show pinia-colada devtools on production environment
andre8244 Jan 8, 2026
c293e62
add documentation link to top bar
andre8244 Jan 8, 2026
f98f663
add kebab menu in system detail
andre8244 Jan 8, 2026
e639f06
add restore system action
andre8244 Jan 8, 2026
360a355
improve go to system button
andre8244 Jan 8, 2026
f571cb1
add suspend/reactivate user action
andre8244 Jan 9, 2026
6862142
improve users and roles
andre8244 Jan 15, 2026
d553fe8
systems: add organization filter
andre8244 Jan 15, 2026
043c767
show sort dropdown also on larger screens
andre8244 Jan 15, 2026
c7cd829
fix empty states and create buttons
andre8244 Jan 19, 2026
4edc1c5
fix
andre8244 Jan 20, 2026
3e85285
fix
andre8244 Jan 20, 2026
a209e59
fix
andre8244 Jan 20, 2026
ea33185
fix api names
andre8244 Jan 20, 2026
7cef51d
add application page (wip)
andre8244 Jan 21, 2026
5754665
add application page (wip)
andre8244 Jan 23, 2026
d99efb2
add application page (wip)
andre8244 Jan 27, 2026
6ba18fb
add application page (wip)
andre8244 Jan 27, 2026
1dedc2f
add application page (wip)
andre8244 Jan 28, 2026
852b066
add application page (wip)
andre8244 Jan 28, 2026
141dcfd
add application page (wip)
andre8244 Jan 28, 2026
f40a148
add application page (wip)
andre8244 Jan 28, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions backend/Makefile
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
# Use bash for shell commands (required for 'source' command on Linux)
SHELL := /bin/bash

# Variables
BINARY_NAME=backend
BUILD_DIR=build
Expand Down
8 changes: 4 additions & 4 deletions backend/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,12 +20,12 @@ make dev-setup
# Start PostgreSQL and Redis containers
make dev-up

# Run database migrations
make db-migrate

# Start the application
# Start the application (initializes database with schema.sql on first run)
make run

# Run database migrations (applies incremental changes on top of base schema)
make db-migrate

# Stop PostgreSQL and Redis when done
make dev-down
```
Expand Down
129 changes: 129 additions & 0 deletions backend/database/migrations/001_add_applications.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
-- Migration: 001_add_applications
-- Description: Add applications table for tracking NS8 cluster applications

-- Applications table - extracted from inventory, with organization assignment
CREATE TABLE IF NOT EXISTS applications (
id VARCHAR(255) PRIMARY KEY,

-- Relationship to system (source of the application)
system_id VARCHAR(255) NOT NULL,

-- Identity from inventory (facts.modules[])
module_id VARCHAR(255) NOT NULL, -- Module ID from inventory (e.g., "nethvoice1", "webtop3", "mail1")
instance_of VARCHAR(100) NOT NULL, -- Module type/name from inventory (e.g., "nethvoice", "webtop", "mail")

-- Display name (for UI customization)
display_name VARCHAR(255), -- Custom name like "Milan Office PBX" (nullable, falls back to module_id)

-- From inventory (facts.modules[] and facts.nodes[])
node_id INTEGER, -- Cluster node ID where the app runs (from modules[].node)
node_label VARCHAR(255), -- Node label from nodes[].ui_name
version VARCHAR(100), -- Application version (from modules[].version)

-- Organization assignment (core business requirement)
organization_id VARCHAR(255), -- FK to org (NULL = unassigned)
organization_type VARCHAR(50), -- owner, distributor, reseller, customer (denormalized for queries)

-- Status tracking
status VARCHAR(50) NOT NULL DEFAULT 'unassigned', -- unassigned, assigned, error

-- Flexible JSONB for type-specific data from inventory
inventory_data JSONB, -- Module data from facts.modules[] (excludes id, name, version, node, ui_name)
backup_data JSONB, -- Backup status from inventory (when available)
services_data JSONB, -- Services health status (when available)

-- App URL (extracted from traefik name_module_map or configured manually)
url VARCHAR(500),

-- Notes/description
notes TEXT,

-- Flags
is_user_facing BOOLEAN NOT NULL DEFAULT TRUE, -- FALSE for system components like traefik, loki

-- Timestamps
created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(),
updated_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(),
first_seen_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(),
last_inventory_at TIMESTAMP WITH TIME ZONE,
deleted_at TIMESTAMP WITH TIME ZONE -- Soft delete
);

-- Comment for applications table
COMMENT ON TABLE applications IS 'Applications extracted from NS8 cluster inventory with organization assignment';

-- Comments for key columns
COMMENT ON COLUMN applications.module_id IS 'Unique module identifier from inventory (e.g., nethvoice1, webtop3)';
COMMENT ON COLUMN applications.instance_of IS 'Application type from inventory (e.g., nethvoice, webtop, mail, nextcloud)';
COMMENT ON COLUMN applications.display_name IS 'Custom display name for UI. Falls back to module_id if NULL';
COMMENT ON COLUMN applications.node_id IS 'Cluster node ID where the application runs (from modules[].node)';
COMMENT ON COLUMN applications.node_label IS 'Human-readable node label from nodes[].ui_name';
COMMENT ON COLUMN applications.organization_id IS 'Assigned organization ID. NULL means unassigned';
COMMENT ON COLUMN applications.organization_type IS 'Denormalized organization type for efficient filtering';
COMMENT ON COLUMN applications.status IS 'Application status: unassigned (no org), assigned (has org), error (has issues)';
COMMENT ON COLUMN applications.inventory_data IS 'Module-specific data from facts.modules[] with enriched user_domains';
COMMENT ON COLUMN applications.backup_data IS 'Backup status information from inventory';
COMMENT ON COLUMN applications.services_data IS 'Services health status from inventory';
COMMENT ON COLUMN applications.is_user_facing IS 'FALSE for system components (traefik, loki) that should be hidden in UI';
COMMENT ON COLUMN applications.deleted_at IS 'Soft delete timestamp. NULL means active';

-- Unique constraint: one application per module_id per system
CREATE UNIQUE INDEX IF NOT EXISTS idx_applications_system_module
ON applications(system_id, module_id) WHERE deleted_at IS NULL;

-- Performance indexes
CREATE INDEX IF NOT EXISTS idx_applications_system_id ON applications(system_id);
CREATE INDEX IF NOT EXISTS idx_applications_organization_id ON applications(organization_id);
CREATE INDEX IF NOT EXISTS idx_applications_instance_of ON applications(instance_of);
CREATE INDEX IF NOT EXISTS idx_applications_status ON applications(status);
CREATE INDEX IF NOT EXISTS idx_applications_version ON applications(version);
CREATE INDEX IF NOT EXISTS idx_applications_is_user_facing ON applications(is_user_facing);
CREATE INDEX IF NOT EXISTS idx_applications_deleted_at ON applications(deleted_at);
CREATE INDEX IF NOT EXISTS idx_applications_created_at ON applications(created_at DESC);
CREATE INDEX IF NOT EXISTS idx_applications_node_id ON applications(node_id);

-- Composite indexes for common queries
CREATE INDEX IF NOT EXISTS idx_applications_org_type_status
ON applications(organization_id, instance_of, status) WHERE deleted_at IS NULL;
CREATE INDEX IF NOT EXISTS idx_applications_system_user_facing
ON applications(system_id, is_user_facing) WHERE deleted_at IS NULL;

-- Foreign key to systems (idempotent)
DO $$
BEGIN
IF NOT EXISTS (
SELECT 1 FROM information_schema.table_constraints
WHERE constraint_name = 'applications_system_id_fkey'
AND table_name = 'applications'
) THEN
ALTER TABLE applications
ADD CONSTRAINT applications_system_id_fkey
FOREIGN KEY (system_id) REFERENCES systems(id) ON DELETE CASCADE;
END IF;
END $$;

-- Status validation (idempotent)
DO $$
BEGIN
IF NOT EXISTS (
SELECT 1 FROM information_schema.table_constraints
WHERE constraint_name = 'chk_applications_status'
AND table_name = 'applications'
) THEN
ALTER TABLE applications ADD CONSTRAINT chk_applications_status
CHECK (status IN ('unassigned', 'assigned', 'error'));
END IF;
END $$;

-- Organization type validation (idempotent)
DO $$
BEGIN
IF NOT EXISTS (
SELECT 1 FROM information_schema.table_constraints
WHERE constraint_name = 'chk_applications_org_type'
AND table_name = 'applications'
) THEN
ALTER TABLE applications ADD CONSTRAINT chk_applications_org_type
CHECK (organization_type IS NULL OR organization_type IN ('owner', 'distributor', 'reseller', 'customer'));
END IF;
END $$;
22 changes: 22 additions & 0 deletions backend/database/migrations/001_add_applications_rollback.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
-- Rollback Migration: 001_add_applications
-- Description: Remove applications table

-- Drop indexes first
DROP INDEX IF EXISTS idx_applications_system_module;
DROP INDEX IF EXISTS idx_applications_system_id;
DROP INDEX IF EXISTS idx_applications_organization_id;
DROP INDEX IF EXISTS idx_applications_instance_of;
DROP INDEX IF EXISTS idx_applications_status;
DROP INDEX IF EXISTS idx_applications_version;
DROP INDEX IF EXISTS idx_applications_is_user_facing;
DROP INDEX IF EXISTS idx_applications_deleted_at;
DROP INDEX IF EXISTS idx_applications_created_at;
DROP INDEX IF EXISTS idx_applications_node_id;
DROP INDEX IF EXISTS idx_applications_org_type_status;
DROP INDEX IF EXISTS idx_applications_system_user_facing;

-- Drop table
DROP TABLE IF EXISTS applications;

-- Remove migration record
DELETE FROM schema_migrations WHERE migration_number = '001';
17 changes: 17 additions & 0 deletions backend/database/migrations/002_add_suspended_at_organizations.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
-- Migration: Add suspended_at column to organization tables
-- This enables status filtering (Enabled/Blocked) for distributors, resellers, and customers

-- Add suspended_at to distributors
ALTER TABLE distributors ADD COLUMN IF NOT EXISTS suspended_at TIMESTAMP WITH TIME ZONE;
COMMENT ON COLUMN distributors.suspended_at IS 'Suspension timestamp. NULL means enabled, non-NULL means blocked/suspended at that time.';
CREATE INDEX IF NOT EXISTS idx_distributors_suspended_at ON distributors(suspended_at);

-- Add suspended_at to resellers
ALTER TABLE resellers ADD COLUMN IF NOT EXISTS suspended_at TIMESTAMP WITH TIME ZONE;
COMMENT ON COLUMN resellers.suspended_at IS 'Suspension timestamp. NULL means enabled, non-NULL means blocked/suspended at that time.';
CREATE INDEX IF NOT EXISTS idx_resellers_suspended_at ON resellers(suspended_at);

-- Add suspended_at to customers
ALTER TABLE customers ADD COLUMN IF NOT EXISTS suspended_at TIMESTAMP WITH TIME ZONE;
COMMENT ON COLUMN customers.suspended_at IS 'Suspension timestamp. NULL means enabled, non-NULL means blocked/suspended at that time.';
CREATE INDEX IF NOT EXISTS idx_customers_suspended_at ON customers(suspended_at);
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
-- Rollback: Remove suspended_at column from organization tables

-- Remove from distributors
DROP INDEX IF EXISTS idx_distributors_suspended_at;
ALTER TABLE distributors DROP COLUMN IF EXISTS suspended_at;

-- Remove from resellers
DROP INDEX IF EXISTS idx_resellers_suspended_at;
ALTER TABLE resellers DROP COLUMN IF EXISTS suspended_at;

-- Remove from customers
DROP INDEX IF EXISTS idx_customers_suspended_at;
ALTER TABLE customers DROP COLUMN IF EXISTS suspended_at;
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
-- Migration: Add suspended_by_org_id to users table
-- Tracks cascade suspensions when an organization is suspended

ALTER TABLE users ADD COLUMN IF NOT EXISTS suspended_by_org_id VARCHAR(255);

-- Index for fast lookups when reactivating an organization
CREATE INDEX IF NOT EXISTS idx_users_suspended_by_org_id ON users(suspended_by_org_id) WHERE suspended_by_org_id IS NOT NULL;

COMMENT ON COLUMN users.suspended_by_org_id IS 'Organization ID that caused this user to be suspended (for cascade reactivation)';
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
-- Rollback migration: Remove suspended_by_org_id from users table

DROP INDEX IF EXISTS idx_users_suspended_by_org_id;
ALTER TABLE users DROP COLUMN IF EXISTS suspended_by_org_id;
Loading