Files
ArchiTools/docs/guides/CONFIGURATION.md
Marius Tarau 4c46e8bcdd Initial commit: ArchiTools modular dashboard platform
Complete Next.js 16 application with 13 fully implemented modules:
Email Signature, Word XML Generator, Registratura, Dashboard,
Tag Manager, IT Inventory, Address Book, Password Vault,
Mini Utilities, Prompt Generator, Digital Signatures,
Word Templates, and AI Chat.

Includes core platform systems (module registry, feature flags,
storage abstraction, i18n, theming, auth stub, tagging),
16 technical documentation files, Docker deployment config,
and legacy HTML tool reference.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-17 12:50:25 +02:00

22 KiB

Configuration Guide

How ArchiTools is configured at build time, runtime, and per-module.


Environment Variables

All environment variables are defined in .env.local for development and injected via Docker for production. The .env.example file in the repository root documents every variable:

# =============================================================================
# ArchiTools Environment Configuration
# =============================================================================
# Copy this file to .env.local for development.
# For Docker, pass variables via docker-compose.yml or Portainer.
# =============================================================================

# -----------------------------------------------------------------------------
# Application
# -----------------------------------------------------------------------------
NEXT_PUBLIC_APP_NAME=ArchiTools
NEXT_PUBLIC_APP_URL=http://localhost:3000

# Version displayed in footer/about. Set by CI or manually.
NEXT_PUBLIC_APP_VERSION=0.1.0

# -----------------------------------------------------------------------------
# Storage
# -----------------------------------------------------------------------------
# Active storage adapter: 'localStorage' | 'api'
NEXT_PUBLIC_STORAGE_ADAPTER=localStorage

# REST API storage backend (required when STORAGE_ADAPTER=api)
# STORAGE_API_URL=http://api.internal/storage

# MinIO object storage (server-side only, for file/binary storage)
# MINIO_ENDPOINT=minio.internal:9000
# MINIO_ACCESS_KEY=
# MINIO_SECRET_KEY=
# MINIO_BUCKET=architools
# MINIO_USE_SSL=false

# -----------------------------------------------------------------------------
# Authentication (future — Authentik SSO)
# -----------------------------------------------------------------------------
# AUTHENTIK_URL=https://auth.internal
# AUTHENTIK_CLIENT_ID=
# AUTHENTIK_CLIENT_SECRET=
# NEXTAUTH_URL=http://localhost:3000
# NEXTAUTH_SECRET=

# -----------------------------------------------------------------------------
# Feature Flags
# -----------------------------------------------------------------------------
# Comma-separated list of flag overrides. Format: flag_name=true/false
# Example: NEXT_PUBLIC_FLAGS_OVERRIDE=module_ai_chat=true,module_password_vault=false
NEXT_PUBLIC_FLAGS_OVERRIDE=

# -----------------------------------------------------------------------------
# External Services
# -----------------------------------------------------------------------------
# N8N webhook endpoint for automation triggers
# N8N_WEBHOOK_URL=https://n8n.internal/webhook

# Gitea API (for potential repo integration)
# GITEA_URL=https://git.internal
# GITEA_TOKEN=

# -----------------------------------------------------------------------------
# External Tool Links (displayed on dashboard)
# -----------------------------------------------------------------------------
# NEXT_PUBLIC_PORTAINER_URL=https://portainer.internal
# NEXT_PUBLIC_DOZZLE_URL=https://dozzle.internal
# NEXT_PUBLIC_NETDATA_URL=https://netdata.internal
# NEXT_PUBLIC_UPTIME_KUMA_URL=https://uptime.internal
# NEXT_PUBLIC_IT_TOOLS_URL=https://it-tools.internal
# NEXT_PUBLIC_STIRLING_PDF_URL=https://pdf.internal
# NEXT_PUBLIC_FILEBROWSER_URL=https://files.internal
# NEXT_PUBLIC_N8N_URL=https://n8n.internal
# NEXT_PUBLIC_GITEA_URL=https://git.internal
# NEXT_PUBLIC_MINIO_CONSOLE_URL=https://minio-console.internal

Naming Rules

Prefix Scope Access
NEXT_PUBLIC_ Client + Server Bundled into client JS. Never put secrets here.
No prefix Server only Available in API routes, server components, middleware.

Never put credentials, API keys, or secrets in NEXT_PUBLIC_ variables. They are embedded in the JavaScript bundle and visible to anyone with browser DevTools.


Module Configuration

File: src/config/modules.ts

Defines the module registry. Every module in the system is declared here. Modules not in this registry do not exist to the platform.

// src/config/modules.ts

import type { LucideIcon } from 'lucide-react';

interface ModuleDefinition {
  /** Unique module identifier. Matches the directory name under src/modules/. */
  id: string;

  /** Romanian display name shown in navigation and headers. */
  label: string;

  /** Short Romanian description for tooltips and dashboard cards. */
  description: string;

  /** Lucide icon component reference. */
  icon: LucideIcon;

  /** URL path segment. Module is accessible at /modules/{path}. */
  path: string;

  /** Storage namespace. Must match STORAGE-LAYER.md namespace table. */
  namespace: string;

  /** Feature flag that controls activation. References flags.ts. */
  featureFlag: string;

  /** Default enabled state when no flag override exists. */
  defaultEnabled: boolean;

  /** Minimum role required to see this module. */
  minRole: Role;

  /** Sort order in navigation sidebar. Lower = higher. */
  order: number;

  /** If true, module is loaded only when navigated to. */
  lazy: boolean;

  /** Which companies this module is relevant to. Empty = all. */
  companies: CompanyId[];
}

export const MODULE_REGISTRY: ModuleDefinition[] = [
  {
    id: 'dashboard',
    label: 'Panou Principal',
    description: 'Tablou de bord cu widget-uri și acces rapid',
    icon: LayoutDashboard,
    path: 'dashboard',
    namespace: 'dashboard',
    featureFlag: 'module_dashboard',
    defaultEnabled: true,
    minRole: 'viewer',
    order: 0,
    lazy: false,
    companies: [],
  },
  {
    id: 'registratura',
    label: 'Registratură',
    description: 'Registru de intrări și ieșiri documente',
    icon: BookOpen,
    path: 'registratura',
    namespace: 'registratura',
    featureFlag: 'module_registratura',
    defaultEnabled: true,
    minRole: 'user',
    order: 10,
    lazy: true,
    companies: [],
  },
  // ... remaining modules follow same pattern
];

Adding a New Module

  1. Create the module directory: src/modules/{module-id}/.
  2. Add an entry to MODULE_REGISTRY in src/config/modules.ts.
  3. Add a feature flag in src/config/flags.ts.
  4. Add the namespace to the namespace table in the storage layer docs.
  5. Create the App Router page: src/app/modules/{path}/page.tsx.
  6. The navigation system and feature flag engine pick it up automatically.

Feature Flag Configuration

File: src/config/flags.ts

// src/config/flags.ts

interface FeatureFlag {
  /** Flag identifier. Convention: module_{id} for module toggles, feature_{name} for features. */
  id: string;

  /** Romanian description. */
  description: string;

  /** Default value when no override exists. */
  defaultValue: boolean;

  /** If true, flag is only visible in admin settings. */
  adminOnly: boolean;

  /** If true, flag is experimental and shown with a warning badge. */
  experimental: boolean;
}

export const FEATURE_FLAGS: FeatureFlag[] = [
  // Module flags
  { id: 'module_dashboard',           description: 'Panou principal',                defaultValue: true,  adminOnly: false, experimental: false },
  { id: 'module_registratura',        description: 'Registratură',                   defaultValue: true,  adminOnly: false, experimental: false },
  { id: 'module_email_signature',     description: 'Generator semnătură email',      defaultValue: true,  adminOnly: false, experimental: false },
  { id: 'module_word_xml',            description: 'Generatoare Word XML',           defaultValue: true,  adminOnly: false, experimental: false },
  { id: 'module_digital_signatures',  description: 'Semnături și ștampile digitale', defaultValue: true,  adminOnly: false, experimental: false },
  { id: 'module_password_vault',      description: 'Seif parole partajat',           defaultValue: true,  adminOnly: true,  experimental: false },
  { id: 'module_it_inventory',        description: 'Inventar IT',                    defaultValue: true,  adminOnly: false, experimental: false },
  { id: 'module_address_book',        description: 'Agendă de contacte',             defaultValue: true,  adminOnly: false, experimental: false },
  { id: 'module_prompt_generator',    description: 'Generator de prompturi AI',      defaultValue: true,  adminOnly: false, experimental: false },
  { id: 'module_word_templates',      description: 'Șabloane Word',                  defaultValue: true,  adminOnly: false, experimental: false },
  { id: 'module_tag_manager',         description: 'Manager etichete',               defaultValue: true,  adminOnly: false, experimental: false },
  { id: 'module_mini_utilities',      description: 'Mini utilitare',                 defaultValue: true,  adminOnly: false, experimental: false },
  { id: 'module_ai_chat',            description: 'Chat AI',                        defaultValue: false, adminOnly: false, experimental: true  },

  // Feature flags (non-module)
  { id: 'feature_dark_mode',          description: 'Mod întunecat',                  defaultValue: true,  adminOnly: false, experimental: false },
  { id: 'feature_export_import',      description: 'Export/import date',             defaultValue: true,  adminOnly: true,  experimental: false },
  { id: 'feature_cross_tab_sync',     description: 'Sincronizare între tab-uri',     defaultValue: true,  adminOnly: false, experimental: false },
  { id: 'feature_infra_links',        description: 'Linkuri infrastructură pe panou',defaultValue: true,  adminOnly: false, experimental: false },
];

Flag Resolution Order

  1. Environment override (NEXT_PUBLIC_FLAGS_OVERRIDE): Highest priority. Parsed as comma-separated key=value pairs.
  2. Runtime override (stored in system namespace under key flag-overrides): Set through admin UI. Persists across sessions.
  3. Default value (defaultValue in flags.ts): Lowest priority. Used when no override exists.
// src/lib/flags/resolve.ts

function resolveFlag(flagId: string): boolean {
  // 1. Environment override
  const envOverrides = parseEnvOverrides(process.env.NEXT_PUBLIC_FLAGS_OVERRIDE);
  if (flagId in envOverrides) return envOverrides[flagId];

  // 2. Runtime override (from storage)
  const runtimeOverrides = getRuntimeOverrides(); // from system namespace
  if (flagId in runtimeOverrides) return runtimeOverrides[flagId];

  // 3. Default
  const flag = FEATURE_FLAGS.find(f => f.id === flagId);
  return flag?.defaultValue ?? false;
}

Usage in Components

import { useFeatureFlag } from '@/lib/flags/hooks';

function SomeComponent() {
  const aiChatEnabled = useFeatureFlag('module_ai_chat');

  if (!aiChatEnabled) return null;
  return <AIChatModule />;
}

Navigation Configuration

File: src/config/navigation.ts

Navigation is derived from the module registry. No separate navigation config is maintained. The navigation system reads MODULE_REGISTRY, filters by feature flags and role, sorts by order, and renders the sidebar.

// src/config/navigation.ts

import { MODULE_REGISTRY } from './modules';

interface NavGroup {
  label: string;
  modules: string[];  // Module IDs belonging to this group
}

/**
 * Groups organize modules in the sidebar.
 * Modules not listed in any group appear under "Altele" (Others).
 */
export const NAV_GROUPS: NavGroup[] = [
  {
    label: 'Principal',
    modules: ['dashboard'],
  },
  {
    label: 'Documente',
    modules: ['registratura', 'word-templates', 'word-xml', 'email-signature'],
  },
  {
    label: 'Resurse',
    modules: ['address-book', 'digital-signatures', 'password-vault', 'it-inventory'],
  },
  {
    label: 'Instrumente',
    modules: ['prompt-generator', 'ai-chat', 'mini-utilities'],
  },
  {
    label: 'Administrare',
    modules: ['tag-manager'],
  },
];

Infrastructure tool links are configured via environment variables and displayed in a separate sidebar section or dashboard widget:

// src/config/navigation.ts

interface ExternalLink {
  label: string;
  envVar: string;       // Environment variable holding the URL
  icon: LucideIcon;
  category: 'infra' | 'dev' | 'tools';
}

export const EXTERNAL_LINKS: ExternalLink[] = [
  { label: 'Portainer',      envVar: 'NEXT_PUBLIC_PORTAINER_URL',      icon: Container,    category: 'infra' },
  { label: 'Dozzle',         envVar: 'NEXT_PUBLIC_DOZZLE_URL',         icon: ScrollText,   category: 'infra' },
  { label: 'Netdata',        envVar: 'NEXT_PUBLIC_NETDATA_URL',        icon: Activity,     category: 'infra' },
  { label: 'Uptime Kuma',    envVar: 'NEXT_PUBLIC_UPTIME_KUMA_URL',    icon: HeartPulse,   category: 'infra' },
  { label: 'Gitea',          envVar: 'NEXT_PUBLIC_GITEA_URL',          icon: GitBranch,    category: 'dev'   },
  { label: 'MinIO',          envVar: 'NEXT_PUBLIC_MINIO_CONSOLE_URL',  icon: Database,     category: 'dev'   },
  { label: 'IT-Tools',       envVar: 'NEXT_PUBLIC_IT_TOOLS_URL',       icon: Wrench,       category: 'tools' },
  { label: 'Stirling PDF',   envVar: 'NEXT_PUBLIC_STIRLING_PDF_URL',   icon: FileText,     category: 'tools' },
  { label: 'N8N',            envVar: 'NEXT_PUBLIC_N8N_URL',            icon: Workflow,      category: 'tools' },
  { label: 'Filebrowser',    envVar: 'NEXT_PUBLIC_FILEBROWSER_URL',    icon: FolderOpen,   category: 'tools' },
];

Links with no URL configured (empty env var) are hidden automatically.


Company Configuration

File: src/config/companies.ts

Static company data. Updated rarely and only by developers.

// src/config/companies.ts

import type { CompanyId, Company } from '@/types/company';

export const COMPANIES: Record<CompanyId, Company> = {
  beletage: {
    id: 'beletage',
    name: 'Beletage SRL',
    shortName: 'Beletage',
    cui: 'RO12345678',      // replace with real CUI
    address: '...',
    email: 'office@beletage.ro',
    phone: '...',
  },
  'urban-switch': {
    id: 'urban-switch',
    name: 'Urban Switch SRL',
    shortName: 'Urban Switch',
    cui: 'RO23456789',
    address: '...',
    email: 'office@urbanswitch.ro',
    phone: '...',
  },
  'studii-de-teren': {
    id: 'studii-de-teren',
    name: 'Studii de Teren SRL',
    shortName: 'Studii de Teren',
    cui: 'RO34567890',
    address: '...',
    email: 'office@studiideteren.ro',
    phone: '...',
  },
  group: {
    id: 'group',
    name: 'Grup Beletage',
    shortName: 'Grup',
    cui: '',
  },
};

export const COMPANY_IDS: CompanyId[] = ['beletage', 'urban-switch', 'studii-de-teren'];
export const ALL_COMPANY_IDS: CompanyId[] = [...COMPANY_IDS, 'group'];

COMPANY_IDS excludes 'group' for UI dropdowns where a real company selection is required. ALL_COMPANY_IDS includes it for contexts where "all companies" is valid.


Theme Configuration

File: src/config/theme.ts

Theme tokens extend the shadcn/ui and Tailwind defaults.

// src/config/theme.ts

export const THEME_CONFIG = {
  /** Default theme on first visit. User preference is stored in system namespace. */
  defaultTheme: 'light' as 'light' | 'dark' | 'system',

  /** Company brand colors for badges, indicators, and charts. */
  companyColors: {
    beletage:          { primary: '#1E3A5F', accent: '#4A90D9' },
    'urban-switch':    { primary: '#2D5F3E', accent: '#6BBF8A' },
    'studii-de-teren': { primary: '#5F4B1E', accent: '#D9A44A' },
    group:             { primary: '#374151', accent: '#6B7280' },
  },

  /** Tag category default colors. */
  tagCategoryColors: {
    project:  '#3B82F6',
    client:   '#8B5CF6',
    phase:    '#F59E0B',
    type:     '#10B981',
    priority: '#EF4444',
    domain:   '#6366F1',
    custom:   '#6B7280',
  },
} as const;

Theme switching is handled by next-themes (shadcn/ui standard). The user's preference is stored in the system storage namespace under the key theme-preference.

Tailwind Integration

Company colors and tag colors are registered in tailwind.config.ts as extended colors:

// tailwind.config.ts (relevant excerpt)

theme: {
  extend: {
    colors: {
      beletage:       { DEFAULT: '#1E3A5F', accent: '#4A90D9' },
      urbanswitch:    { DEFAULT: '#2D5F3E', accent: '#6BBF8A' },
      studiideteren:  { DEFAULT: '#5F4B1E', accent: '#D9A44A' },
    },
  },
},

Build-Time vs Runtime Configuration

Configuration Type When Resolved How Set Changeable Without Rebuild
NEXT_PUBLIC_* env vars Build time (bundled into JS) .env.local, Docker build args No (requires rebuild)
Server-only env vars Runtime (read on each request) Docker env vars, .env.local Yes
Feature flag defaults Build time (in flags.ts) Source code No
Feature flag overrides (env) Build time (via NEXT_PUBLIC_FLAGS_OVERRIDE) Env var No
Feature flag overrides (runtime) Runtime (from storage) Admin UI Yes
Module registry Build time (in modules.ts) Source code No
Company data Build time (in companies.ts) Source code No
Theme preference Runtime (from storage) User toggle Yes
External tool URLs Build time (via NEXT_PUBLIC_*) Env vars No

Making NEXT_PUBLIC_ Variables Runtime-Configurable in Docker

Next.js inlines NEXT_PUBLIC_ variables at build time, which is problematic for Docker images that should be configurable at deploy time. Solution:

1. Build with placeholder values:

# Dockerfile
ARG NEXT_PUBLIC_APP_URL=__NEXT_PUBLIC_APP_URL__
ARG NEXT_PUBLIC_STORAGE_ADAPTER=__NEXT_PUBLIC_STORAGE_ADAPTER__

2. Replace at container start:

#!/bin/sh
# docker/entrypoint.sh

# Replace build-time placeholders with runtime environment values
find /app/.next -type f -name '*.js' | while read file; do
  sed -i "s|__NEXT_PUBLIC_APP_URL__|${NEXT_PUBLIC_APP_URL:-http://localhost:3000}|g" "$file"
  sed -i "s|__NEXT_PUBLIC_STORAGE_ADAPTER__|${NEXT_PUBLIC_STORAGE_ADAPTER:-localStorage}|g" "$file"
  # ... repeat for each NEXT_PUBLIC_ variable
done

exec node server.js

3. Use in docker-compose:

# docker-compose.yml
services:
  architools:
    image: architools:latest
    environment:
      - NEXT_PUBLIC_APP_URL=https://tools.internal
      - NEXT_PUBLIC_STORAGE_ADAPTER=api
      - STORAGE_API_URL=http://api:8080/storage
      - MINIO_ENDPOINT=minio:9000
      - MINIO_ACCESS_KEY=${MINIO_ACCESS_KEY}
      - MINIO_SECRET_KEY=${MINIO_SECRET_KEY}
    ports:
      - "3000:3000"

Docker Environment Injection

Development

# .env.local (gitignored)
NEXT_PUBLIC_APP_NAME=ArchiTools
NEXT_PUBLIC_APP_URL=http://localhost:3000
NEXT_PUBLIC_STORAGE_ADAPTER=localStorage

Production (Docker Compose)

# docker-compose.yml
version: '3.8'

services:
  architools:
    build:
      context: .
      dockerfile: Dockerfile
    container_name: architools
    restart: unless-stopped
    ports:
      - "3000:3000"
    environment:
      - NODE_ENV=production
      - NEXT_PUBLIC_APP_NAME=ArchiTools
      - NEXT_PUBLIC_APP_URL=https://tools.internal
      - NEXT_PUBLIC_STORAGE_ADAPTER=api
      - STORAGE_API_URL=http://api:8080/storage
      - MINIO_ENDPOINT=minio:9000
      - MINIO_ACCESS_KEY=${MINIO_ACCESS_KEY}
      - MINIO_SECRET_KEY=${MINIO_SECRET_KEY}
      - MINIO_BUCKET=architools
      # External links
      - NEXT_PUBLIC_PORTAINER_URL=https://portainer.internal
      - NEXT_PUBLIC_GITEA_URL=https://git.internal
      - NEXT_PUBLIC_N8N_URL=https://n8n.internal
    networks:
      - internal

networks:
  internal:
    external: true

Production (Portainer)

When deploying via Portainer, set environment variables in the container's Environment section. Sensitive values (MinIO keys, auth secrets) should use Portainer's secrets management rather than plain environment variables.


Configuration Precedence

Resolution order from lowest to highest priority:

1. Source code defaults (flags.ts, modules.ts, companies.ts, theme.ts)
       ↑ overridden by
2. Environment variables (.env.local / Docker env)
       ↑ overridden by
3. Runtime overrides (admin UI → stored in system namespace)

If a conflict exists, the higher-priority source wins. Runtime overrides are only available for feature flags and user preferences (theme, sidebar state). Structural configuration (module registry, company data, navigation groups) is not runtime-overridable; it requires a code change and rebuild.


Configuration File Index

File Purpose Changeable at Runtime
src/config/modules.ts Module registry and metadata No
src/config/flags.ts Feature flag definitions and defaults Overridable via env/storage
src/config/navigation.ts Sidebar groups and external links No
src/config/companies.ts Company master data No
src/config/theme.ts Theme tokens and brand colors Theme preference only
.env.local Development environment variables N/A (dev only)
.env.example Documented variable template (committed) N/A (reference)
docker-compose.yml Production environment variables At deploy time
docker/entrypoint.sh Runtime placeholder replacement At container start

Validation

On application startup, the config system validates:

  1. All required NEXT_PUBLIC_ variables are set (not empty or placeholder).
  2. NEXT_PUBLIC_STORAGE_ADAPTER is a known adapter type.
  3. If adapter is api, STORAGE_API_URL is set.
  4. If MinIO is configured, all three of MINIO_ENDPOINT, MINIO_ACCESS_KEY, and MINIO_SECRET_KEY are present.
  5. Feature flag overrides parse correctly (no malformed entries).
  6. Module IDs in NAV_GROUPS reference existing modules in MODULE_REGISTRY.

Validation errors are logged to the console with [ArchiTools Config] prefix and do not crash the application. Missing optional config results in graceful degradation (e.g., external links not shown, MinIO features unavailable).