feat: customers management UI with paginated table and full delete
All checks were successful
Build & Release / build (push) Successful in 12s

- Fix SSH key missing trailing newline (error in libcrypto)
- Pass env with SSH command through all git operations
- Add customers table (modules, start/expiration dates, created/updated timestamps)
- Idempotent ALTER TABLE for existing deployments
- GET /customers with pagination, search, and sort
- POST /customers persists slug with modules and dates to DB
- DELETE /customers/:slug removes ArgoCD chart, DO DB, pgbouncer pool, and manager record
- Redesigned frontend: dark slate theme, customers table page with search/sort/pagination, delete confirm dialog, module checkboxes, slate buttons
This commit is contained in:
Ryan Moon
2026-04-03 17:46:16 -05:00
parent cb3e027ed2
commit d1c4aa7d6f
4 changed files with 567 additions and 106 deletions

View File

@@ -4,11 +4,12 @@ import { tmpdir } from "os";
import { join } from "path";
import { config } from "../lib/config";
function withRepo<T>(fn: (dir: string) => T): T {
function withRepo<T>(fn: (dir: string, env: NodeJS.ProcessEnv) => T): T {
const keyPath = join(tmpdir(), `manager-ssh-key-${Date.now()}`);
const dir = join(tmpdir(), `lunarfront-charts-${Date.now()}`);
writeFileSync(keyPath, config.gitSshKey, { mode: 0o600 });
const keyContent = config.gitSshKey.endsWith("\n") ? config.gitSshKey : config.gitSshKey + "\n";
writeFileSync(keyPath, keyContent, { mode: 0o600 });
const env = {
...process.env,
@@ -19,7 +20,7 @@ function withRepo<T>(fn: (dir: string) => T): T {
execSync(`git clone ${config.gitRepoUrl} ${dir}`, { env, stdio: "pipe" });
execSync(`git -C ${dir} config user.email "manager@lunarfront.tech"`, { env });
execSync(`git -C ${dir} config user.name "lunarfront-manager"`, { env });
const result = fn(dir);
const result = fn(dir, env);
execSync(`git -C ${dir} push origin main`, { env, stdio: "pipe" });
return result;
} finally {
@@ -29,19 +30,19 @@ function withRepo<T>(fn: (dir: string) => T): T {
}
export function addCustomerChart(slug: string, appVersion: string) {
withRepo((dir) => {
withRepo((dir, env) => {
const manifest = buildArgoCDApp(slug, appVersion);
writeFileSync(join(dir, "customers", `${slug}.yaml`), manifest);
execSync(`git -C ${dir} add customers/${slug}.yaml`);
execSync(`git -C ${dir} commit -m "feat: provision customer ${slug}"`, { env: process.env });
execSync(`git -C ${dir} add customers/${slug}.yaml`, { env });
execSync(`git -C ${dir} commit -m "feat: provision customer ${slug}"`, { env });
});
}
export function removeCustomerChart(slug: string) {
withRepo((dir) => {
withRepo((dir, env) => {
rmSync(join(dir, "customers", `${slug}.yaml`), { force: true });
execSync(`git -C ${dir} add customers/${slug}.yaml`);
execSync(`git -C ${dir} commit -m "chore: deprovision customer ${slug}"`, { env: process.env });
execSync(`git -C ${dir} add customers/${slug}.yaml`, { env });
execSync(`git -C ${dir} commit -m "chore: deprovision customer ${slug}"`, { env });
});
}