feat: settings modal with database export and import
All checks were successful
CI / test (pull_request) Successful in 12s
CI / build-dev (pull_request) Has been skipped

Adds a gear button to the nav that opens a settings modal with:
- Export: GET /api/export returns all instances as a JSON backup file
  with a Content-Disposition attachment header
- Import: POST /api/import validates and bulk-replaces all instances;
  client uses FileReader to POST the parsed JSON, with a confirm dialog
  before destructive replace

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-28 14:10:59 -04:00
parent 20d8a13375
commit af207339a4
7 changed files with 230 additions and 4 deletions

View File

@@ -2,7 +2,7 @@ import { describe, it, expect, beforeEach } from 'vitest'
import {
_resetForTest,
getInstances, getInstance, getDistinctStacks,
createInstance, updateInstance, deleteInstance,
createInstance, updateInstance, deleteInstance, importInstances,
} from '../server/db.js'
beforeEach(() => _resetForTest());
@@ -166,6 +166,25 @@ describe('deleteInstance', () => {
});
});
// ── importInstances ───────────────────────────────────────────────────────────
describe('importInstances', () => {
const base = { state: 'deployed', stack: 'production', atlas: 0, argus: 0, semaphore: 0, patchmon: 0, tailscale: 0, andromeda: 0, tailscale_ip: '', hardware_acceleration: 0 };
it('replaces all existing instances with the imported set', () => {
createInstance({ ...base, name: 'old', vmid: 1 });
importInstances([{ ...base, name: 'new', vmid: 2 }]);
expect(getInstance(1)).toBeNull();
expect(getInstance(2)).not.toBeNull();
});
it('clears all instances when passed an empty array', () => {
createInstance({ ...base, name: 'a', vmid: 1 });
importInstances([]);
expect(getInstances()).toEqual([]);
});
});
// ── Test environment boot isolation ───────────────────────────────────────────
describe('test environment boot isolation', () => {