Skip to content

State Management

Overview

AMP Manager uses two storage systems:

SystemPurposePersists?
JSON Files (users/ folder)User data, encrypted contentYes
ZustandUI state, real-time dataIn-memory

JSON File Storage - Persistent User Data

What It Stores (v1.0+)

IMPORTANT: All user data is stored under users/user_{username}/ folder. The user_ prefix avoids conflicts with MariaDB databases.

CategoryFileEncrypted?
User (auth)users/user_{username}/user.jsonNo
Credentialsusers/user_{username}/credentials.jsonYes
Notesusers/user_{username}/notes.jsonYes
Settingsusers/user_{username}/settings.jsonYes
Sites/Domainsusers/user_{username}/sites.jsonNo
Tagsusers/user_{username}/tags.jsonNo
Tunnelsusers/user_{username}/tunnels.jsonNo
Activity Logsusers/user_{username}/activity_logs.jsonNo
Workflowsusers/user_{username}/workflows.jsonYes
Site Configsusers/user_{username}/site_configs.jsonYes
Domain Statususers/user_{username}/domain_status.jsonNo
Databasesusers/user_{username}/databases.jsonNo
Databases Cacheusers/user_{username}/databases_cache.jsonNo
App Configconfig.jsonNo (lastUser, watchdog)

Folder Structure (v1.0+)

text
users/
|-- user_{username}/        # ALL user-specific data
|   |-- user.json           # UNENCRYPTED: salt + validation token
|   |-- credentials.json    # ENCRYPTED
|   |-- notes.json          # ENCRYPTED
|   |-- settings.json       # ENCRYPTED
|   |-- sites.json          # Plain text
|   |-- tags.json           # Plain text
|   |-- tunnels.json        # Plain text
|   |-- activity_logs.json  # Plain text
|   |-- workflows.json      # ENCRYPTED
|   |-- site_configs.json   # ENCRYPTED
|   |-- domain_status.json  # Plain text
|   |-- databases.json      # Plain text
|   |-- databases_cache.json
|
|-- user_{another}/         # Another user

config.json                 # Global app config (lastUser only)

mysql/    # MariaDB (unaffected)
ampdb/    # MariaDB database (unaffected)
{dbname}/ # User's MariaDB databases

Why user_ Prefix?

The user_ prefix (not user-) avoids conflicts with MariaDB databases:

  • User registers as: myproject
  • MariaDB creates: /data/myproject/
  • AMP Manager creates: /users/user_myproject/ (no conflict)

Also uses forward slashes for cross-platform path compatibility (Neutralino normalizes paths).

Security Model

The password is never stored. Instead, a validation token proves password knowledge:

  1. Registration:

    • Generate random salt
    • Derive encryption key from password + salt
    • Encrypt "VALIDATION_CHECK" -> { iv, ciphertext }
    • Save to user.json: { salt, validation_iv, validation_ciphertext }
  2. Login:

    • Read user.json (unencrypted)
    • Re-derive key: deriveKey(password, salt)
    • Decrypt validation token with key + iv
    • If result === "VALIDATION_CHECK" -> password correct
  3. After Login:

    • Encryption key stored in memory (cleared on logout)
    • All encrypted files use this key

Storage Location

  • Path: NL_PATH/data/ (app data folder)
  • Helper: src/lib/storage.ts (low-level file operations)
  • High-level API: src/lib/db.ts (JSON storage functions)

JSON Storage Functions

Use functions from src/lib/db.ts:

FunctionPurpose
loadSitesJSON() / saveSitesJSON()Sites/Domains
loadTagsJSON() / saveTagsJSON()Tags
loadNotesJSON() / saveNotesJSON()Notes (encrypted)
loadCredentialsJSON() / saveCredentialsJSON()Credentials (encrypted)
loadSettingsJSON() / saveSettingsJSON()User settings
loadTunnelsJSON() / saveTunnelsJSON()Active tunnels
loadActivityLogsJSON() / saveActivityLogsJSON()Activity history
loadWorkflowsJSON() / saveWorkflowsJSON()Workflows
loadSiteConfigsJSON() / saveSiteConfigsJSON()Config backups
loadDomainStatusJSON() / saveDomainStatusJSON()Domain health
loadDatabasesJSON() / saveDatabasesJSON()Database metadata
logActivityJSON()Activity logging
deleteUserData()Complete user data deletion

Accessing Data

typescript
// Use high-level functions from db.ts
import { loadSitesJSON, saveSitesJSON, loadTagsJSON } from '@/lib/db';

// Load sites and tags
const [sites, tags] = await Promise.all([
  loadSitesJSON(),
  loadTagsJSON()
]);

// Save sites
sites.push(newSite);
await saveSitesJSON(sites);

Encrypted data requires user and key:

typescript
import { loadNotesJSON, saveNotesJSON } from '@/lib/db';

const { user, encryptionKey } = useAuth();

// Load notes (key is optional - decrypts if provided)
const notes = await loadNotesJSON(user, encryptionKey || undefined);

// Save notes - only encrypt if toggle is enabled
await saveNotesJSON(user, notes, formData.is_encrypted ? encryptionKey : undefined);

File: src/lib/db.ts - JSON storage functions with encryption support

Zustand - UI State

What It Manages

StorePurposeFile
Docker MetricsReal-time container statssrc/stores/dockerMetricsStore.ts
Docker SettingsPolling preferencessrc/stores/dockerSettings.ts
Dashboard SettingsDashboard preferencessrc/stores/dashboardSettings.ts

Accessing a Store

typescript
// Import the hook
import { useDockerMetricsStore } from '@/stores/dockerMetricsStore';

// In component
const { stats, loading, fetchMetrics } = useDockerMetricsStore();

When to Use Which

text
Is the data...
- Created by the user?          -> JSON files
- Needs encryption?             -> JSON files (encrypted)
- Complex queries?              -> In-memory after load
- Real-time/changes frequently? -> Zustand
- UI-only state (e.g. loading)? -> Zustand
- Simple preferences?           -> Zustand or JSON
Use CaseStorageWhy
Notes, credentialsJSON (encrypted)User data, encrypted
Docker metricsZustandReal-time, transient
Tag listJSON filesPersistent
Modal open/closedZustandUI state
Polling intervalZustandSimple preference

Encryption Overview

Sensitive data (credentials, notes) is encrypted before storage:

Password + Salt -> AES Key (in memory) -> Encrypt/Decrypt -> JSON Files

Key files:

  • src/lib/crypto.ts - Encryption utilities
  • src/context/AuthContext.tsx - Key derivation

Key Files Reference

FilePurpose
src/lib/db.tsJSON storage functions
src/lib/storage.tsLow-level file storage helper
src/context/AuthContext.tsxUser authentication & encryption
src/stores/*.tsZustand state stores

Common Patterns

Saving Encrypted Data

typescript
import { loadCredentialsJSON, saveCredentialsJSON } from '@/lib/db';

const { user, encryptionKey } = useAuth();

// Save encrypted credentials
const creds = await loadCredentialsJSON(user, encryptionKey);
creds.push(newCredential);
await saveCredentialsJSON(user, creds, encryptionKey);

Explicit User Parameter for Async Operations

In async operations (like sync), always pass user explicitly to ensure data saves to the correct user's folder:

typescript
import { saveDomainStatusJSON, saveSitesJSON, loadSettingsJSON, saveSettingsJSON } from '@/lib/db';

// In useProjectSync.ts - pass user explicitly
const existingStatuses = await loadDomainStatusJSON();
const allStatuses = [...existingStatuses, ...newStatuses];
await saveDomainStatusJSON(user, allStatuses);  // Explicit user

const settings = await loadSettingsJSON(user);
settings.lastSyncTimestamp = Date.now();
await saveSettingsJSON(user, settings);  // Explicit user

// Plain file saves also benefit from explicit user
await saveSitesJSON(user, filteredSites);  // Explicit user

Key rule: When in doubt, pass the user explicitly - it's safer than relying on global state.

Reading Data

typescript
import { loadSitesJSON, loadTagsJSON } from '@/lib/db';

// Load sites and tags
const [sites, tags] = await Promise.all([
  loadSitesJSON(),
  loadTagsJSON()
]);

Deleting All User Data

typescript
import { deleteUserData } from '@/lib/db';

const { user } = useAuth();

// Complete data wipe (use with caution!)
await deleteUserData(user);

See Also

AMP Manager
Released under the MIT License.