Initial
This commit is contained in:
532
server/db.js
Normal file
532
server/db.js
Normal file
@@ -0,0 +1,532 @@
|
||||
import fs from 'node:fs';
|
||||
import path from 'node:path';
|
||||
import { DatabaseSync } from 'node:sqlite';
|
||||
import bcrypt from 'bcryptjs';
|
||||
import { nanoid } from 'nanoid';
|
||||
import { config } from './config.js';
|
||||
import { settingDefinitions } from './settingsRegistry.js';
|
||||
|
||||
fs.mkdirSync(path.dirname(config.databasePath), { recursive: true });
|
||||
fs.mkdirSync(config.fileLockerDir, { recursive: true });
|
||||
fs.mkdirSync(config.toolsDir, { recursive: true });
|
||||
|
||||
export const db = new DatabaseSync(config.databasePath);
|
||||
db.exec('PRAGMA foreign_keys = ON; PRAGMA journal_mode = WAL;');
|
||||
|
||||
export function id(prefix) {
|
||||
return `${prefix}_${nanoid(12)}`;
|
||||
}
|
||||
|
||||
export function now() {
|
||||
return new Date().toISOString();
|
||||
}
|
||||
|
||||
export function json(value, fallback = []) {
|
||||
if (value == null || value === '') return JSON.stringify(fallback);
|
||||
return JSON.stringify(value);
|
||||
}
|
||||
|
||||
export function parseJson(value, fallback = []) {
|
||||
try {
|
||||
return value ? JSON.parse(value) : fallback;
|
||||
} catch {
|
||||
return fallback;
|
||||
}
|
||||
}
|
||||
|
||||
function ensureColumn(table, column, definition) {
|
||||
const columns = db.prepare(`PRAGMA table_info(${table})`).all();
|
||||
if (!columns.some((row) => row.name === column)) {
|
||||
db.exec(`ALTER TABLE ${table} ADD COLUMN ${column} ${definition}`);
|
||||
}
|
||||
}
|
||||
|
||||
export function migrate() {
|
||||
db.exec(`
|
||||
CREATE TABLE IF NOT EXISTS users (
|
||||
id TEXT PRIMARY KEY,
|
||||
email TEXT NOT NULL UNIQUE,
|
||||
display_name TEXT NOT NULL,
|
||||
password_hash TEXT,
|
||||
auth_provider TEXT NOT NULL DEFAULT 'local',
|
||||
role TEXT NOT NULL DEFAULT 'user',
|
||||
created_at TEXT NOT NULL
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS groups (
|
||||
id TEXT PRIMARY KEY,
|
||||
name TEXT NOT NULL UNIQUE,
|
||||
description TEXT,
|
||||
created_at TEXT NOT NULL
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS user_groups (
|
||||
user_id TEXT NOT NULL REFERENCES users(id) ON DELETE CASCADE,
|
||||
group_id TEXT NOT NULL REFERENCES groups(id) ON DELETE CASCADE,
|
||||
PRIMARY KEY (user_id, group_id)
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS credentials (
|
||||
id TEXT PRIMARY KEY,
|
||||
name TEXT NOT NULL,
|
||||
kind TEXT NOT NULL,
|
||||
username TEXT,
|
||||
secret_cipher TEXT NOT NULL,
|
||||
secret_iv TEXT NOT NULL,
|
||||
secret_tag TEXT NOT NULL,
|
||||
visibility TEXT NOT NULL DEFAULT 'personal',
|
||||
owner_user_id TEXT REFERENCES users(id) ON DELETE SET NULL,
|
||||
group_id TEXT REFERENCES groups(id) ON DELETE SET NULL,
|
||||
created_by TEXT REFERENCES users(id) ON DELETE SET NULL,
|
||||
created_at TEXT NOT NULL,
|
||||
updated_at TEXT NOT NULL
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS hosts (
|
||||
id TEXT PRIMARY KEY,
|
||||
name TEXT NOT NULL,
|
||||
fqdn TEXT,
|
||||
address TEXT NOT NULL,
|
||||
os_family TEXT NOT NULL DEFAULT 'windows',
|
||||
transport TEXT NOT NULL DEFAULT 'winrm',
|
||||
port INTEGER,
|
||||
credential_id TEXT REFERENCES credentials(id) ON DELETE SET NULL,
|
||||
tags TEXT NOT NULL DEFAULT '[]',
|
||||
notes TEXT,
|
||||
created_at TEXT NOT NULL,
|
||||
updated_at TEXT NOT NULL
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS folders (
|
||||
id TEXT PRIMARY KEY,
|
||||
parent_id TEXT REFERENCES folders(id) ON DELETE CASCADE,
|
||||
name TEXT NOT NULL,
|
||||
visibility TEXT NOT NULL DEFAULT 'personal',
|
||||
owner_user_id TEXT REFERENCES users(id) ON DELETE SET NULL,
|
||||
group_id TEXT REFERENCES groups(id) ON DELETE SET NULL,
|
||||
created_at TEXT NOT NULL,
|
||||
updated_at TEXT NOT NULL
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS scripts (
|
||||
id TEXT PRIMARY KEY,
|
||||
folder_id TEXT REFERENCES folders(id) ON DELETE SET NULL,
|
||||
name TEXT NOT NULL,
|
||||
description TEXT,
|
||||
content TEXT NOT NULL,
|
||||
visibility TEXT NOT NULL DEFAULT 'personal',
|
||||
owner_user_id TEXT REFERENCES users(id) ON DELETE SET NULL,
|
||||
group_id TEXT REFERENCES groups(id) ON DELETE SET NULL,
|
||||
version INTEGER NOT NULL DEFAULT 1,
|
||||
created_at TEXT NOT NULL,
|
||||
updated_at TEXT NOT NULL
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS asset_folders (
|
||||
id TEXT PRIMARY KEY,
|
||||
parent_id TEXT REFERENCES asset_folders(id) ON DELETE CASCADE,
|
||||
name TEXT NOT NULL,
|
||||
description TEXT,
|
||||
visibility TEXT NOT NULL DEFAULT 'personal',
|
||||
owner_user_id TEXT REFERENCES users(id) ON DELETE SET NULL,
|
||||
group_id TEXT REFERENCES groups(id) ON DELETE SET NULL,
|
||||
created_by TEXT REFERENCES users(id) ON DELETE SET NULL,
|
||||
created_at TEXT NOT NULL,
|
||||
updated_at TEXT NOT NULL
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS assets (
|
||||
id TEXT PRIMARY KEY,
|
||||
folder_id TEXT REFERENCES asset_folders(id) ON DELETE SET NULL,
|
||||
name TEXT NOT NULL,
|
||||
original_name TEXT NOT NULL,
|
||||
description TEXT,
|
||||
kind TEXT NOT NULL DEFAULT 'generic',
|
||||
mime_type TEXT,
|
||||
size_bytes INTEGER NOT NULL DEFAULT 0,
|
||||
checksum TEXT NOT NULL,
|
||||
storage_path TEXT NOT NULL,
|
||||
package_path TEXT,
|
||||
visibility TEXT NOT NULL DEFAULT 'personal',
|
||||
owner_user_id TEXT REFERENCES users(id) ON DELETE SET NULL,
|
||||
group_id TEXT REFERENCES groups(id) ON DELETE SET NULL,
|
||||
created_by TEXT REFERENCES users(id) ON DELETE SET NULL,
|
||||
created_at TEXT NOT NULL,
|
||||
updated_at TEXT NOT NULL
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS asset_links (
|
||||
id TEXT PRIMARY KEY,
|
||||
asset_id TEXT NOT NULL REFERENCES assets(id) ON DELETE CASCADE,
|
||||
target_type TEXT NOT NULL,
|
||||
target_id TEXT NOT NULL,
|
||||
link_role TEXT NOT NULL DEFAULT 'reference',
|
||||
package_path TEXT,
|
||||
created_by TEXT REFERENCES users(id) ON DELETE SET NULL,
|
||||
created_at TEXT NOT NULL
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS custom_variables (
|
||||
id TEXT PRIMARY KEY,
|
||||
name TEXT NOT NULL,
|
||||
description TEXT,
|
||||
category TEXT NOT NULL DEFAULT 'Custom',
|
||||
value_type TEXT NOT NULL DEFAULT 'string',
|
||||
secret_cipher TEXT NOT NULL,
|
||||
secret_iv TEXT NOT NULL,
|
||||
secret_tag TEXT NOT NULL,
|
||||
sensitive INTEGER NOT NULL DEFAULT 0,
|
||||
visibility TEXT NOT NULL DEFAULT 'personal',
|
||||
owner_user_id TEXT REFERENCES users(id) ON DELETE SET NULL,
|
||||
group_id TEXT REFERENCES groups(id) ON DELETE SET NULL,
|
||||
created_by TEXT REFERENCES users(id) ON DELETE SET NULL,
|
||||
created_at TEXT NOT NULL,
|
||||
updated_at TEXT NOT NULL
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS psadt_profiles (
|
||||
id TEXT PRIMARY KEY,
|
||||
name TEXT NOT NULL,
|
||||
app_vendor TEXT,
|
||||
app_name TEXT NOT NULL,
|
||||
app_version TEXT,
|
||||
app_arch TEXT,
|
||||
app_lang TEXT,
|
||||
app_revision TEXT,
|
||||
toolkit_version TEXT NOT NULL DEFAULT 'v4',
|
||||
template_version TEXT NOT NULL DEFAULT 'v4',
|
||||
deployment_type TEXT NOT NULL DEFAULT 'Install',
|
||||
deploy_mode TEXT NOT NULL DEFAULT 'Interactive',
|
||||
require_admin INTEGER NOT NULL DEFAULT 1,
|
||||
zero_config INTEGER NOT NULL DEFAULT 0,
|
||||
suppress_reboot INTEGER NOT NULL DEFAULT 0,
|
||||
close_processes TEXT NOT NULL DEFAULT '[]',
|
||||
install_tasks TEXT NOT NULL DEFAULT '[]',
|
||||
ui_plan TEXT NOT NULL DEFAULT '{}',
|
||||
config_plan TEXT NOT NULL DEFAULT '{}',
|
||||
admx_plan TEXT NOT NULL DEFAULT '{}',
|
||||
visibility TEXT NOT NULL DEFAULT 'personal',
|
||||
owner_user_id TEXT REFERENCES users(id) ON DELETE SET NULL,
|
||||
group_id TEXT REFERENCES groups(id) ON DELETE SET NULL,
|
||||
created_by TEXT REFERENCES users(id) ON DELETE SET NULL,
|
||||
created_at TEXT NOT NULL,
|
||||
updated_at TEXT NOT NULL
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS intune_deployments (
|
||||
id TEXT PRIMARY KEY,
|
||||
name TEXT NOT NULL,
|
||||
profile_id TEXT REFERENCES psadt_profiles(id) ON DELETE SET NULL,
|
||||
script_id TEXT REFERENCES scripts(id) ON DELETE SET NULL,
|
||||
source_folder TEXT,
|
||||
intunewin_file TEXT,
|
||||
app_type TEXT NOT NULL DEFAULT 'Windows app (Win32)',
|
||||
command_style TEXT NOT NULL DEFAULT 'v4',
|
||||
install_behavior TEXT NOT NULL DEFAULT 'system',
|
||||
restart_behavior TEXT NOT NULL DEFAULT 'return-code',
|
||||
ui_mode TEXT NOT NULL DEFAULT 'native-v4',
|
||||
requirements TEXT NOT NULL DEFAULT '{}',
|
||||
install_command TEXT NOT NULL,
|
||||
uninstall_command TEXT NOT NULL,
|
||||
detection_type TEXT NOT NULL DEFAULT 'custom-script',
|
||||
detection_rule TEXT NOT NULL DEFAULT '',
|
||||
return_codes TEXT NOT NULL DEFAULT '[]',
|
||||
assignments TEXT NOT NULL DEFAULT '[]',
|
||||
status TEXT NOT NULL DEFAULT 'draft',
|
||||
graph_app_id TEXT,
|
||||
notes TEXT,
|
||||
visibility TEXT NOT NULL DEFAULT 'personal',
|
||||
owner_user_id TEXT REFERENCES users(id) ON DELETE SET NULL,
|
||||
group_id TEXT REFERENCES groups(id) ON DELETE SET NULL,
|
||||
created_by TEXT REFERENCES users(id) ON DELETE SET NULL,
|
||||
created_at TEXT NOT NULL,
|
||||
updated_at TEXT NOT NULL
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS graph_connections (
|
||||
id TEXT PRIMARY KEY,
|
||||
name TEXT NOT NULL,
|
||||
tenant_id TEXT NOT NULL,
|
||||
client_id TEXT NOT NULL,
|
||||
secret_cipher TEXT NOT NULL,
|
||||
secret_iv TEXT NOT NULL,
|
||||
secret_tag TEXT NOT NULL,
|
||||
cloud TEXT NOT NULL DEFAULT 'global',
|
||||
graph_base_url TEXT NOT NULL DEFAULT 'https://graph.microsoft.com',
|
||||
authority_host TEXT NOT NULL DEFAULT 'https://login.microsoftonline.com',
|
||||
default_api_version TEXT NOT NULL DEFAULT 'v1.0',
|
||||
enabled INTEGER NOT NULL DEFAULT 1,
|
||||
last_test_status TEXT,
|
||||
last_test_message TEXT,
|
||||
last_test_at TEXT,
|
||||
visibility TEXT NOT NULL DEFAULT 'personal',
|
||||
owner_user_id TEXT REFERENCES users(id) ON DELETE SET NULL,
|
||||
group_id TEXT REFERENCES groups(id) ON DELETE SET NULL,
|
||||
created_by TEXT REFERENCES users(id) ON DELETE SET NULL,
|
||||
created_at TEXT NOT NULL,
|
||||
updated_at TEXT NOT NULL
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS intune_deployment_statuses (
|
||||
id TEXT PRIMARY KEY,
|
||||
deployment_id TEXT NOT NULL REFERENCES intune_deployments(id) ON DELETE CASCADE,
|
||||
connection_id TEXT REFERENCES graph_connections(id) ON DELETE SET NULL,
|
||||
graph_app_id TEXT NOT NULL,
|
||||
scope TEXT NOT NULL DEFAULT 'device',
|
||||
principal_id TEXT,
|
||||
principal_name TEXT,
|
||||
device_id TEXT,
|
||||
device_name TEXT,
|
||||
user_id TEXT,
|
||||
user_principal_name TEXT,
|
||||
install_state TEXT,
|
||||
install_state_detail TEXT,
|
||||
error_code TEXT,
|
||||
error_description TEXT,
|
||||
last_sync_datetime TEXT,
|
||||
raw_json TEXT NOT NULL DEFAULT '{}',
|
||||
created_at TEXT NOT NULL,
|
||||
updated_at TEXT NOT NULL
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS intune_deployment_sync_runs (
|
||||
id TEXT PRIMARY KEY,
|
||||
deployment_id TEXT NOT NULL REFERENCES intune_deployments(id) ON DELETE CASCADE,
|
||||
connection_id TEXT REFERENCES graph_connections(id) ON DELETE SET NULL,
|
||||
graph_app_id TEXT NOT NULL,
|
||||
status TEXT NOT NULL,
|
||||
message TEXT,
|
||||
summary TEXT NOT NULL DEFAULT '{}',
|
||||
started_at TEXT NOT NULL,
|
||||
ended_at TEXT
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS applications (
|
||||
id TEXT PRIMARY KEY,
|
||||
name TEXT NOT NULL,
|
||||
vendor TEXT,
|
||||
publisher TEXT,
|
||||
current_version TEXT,
|
||||
latest_known_version TEXT,
|
||||
version_source TEXT NOT NULL DEFAULT 'manual',
|
||||
version_source_ref TEXT,
|
||||
graph_connection_id TEXT REFERENCES graph_connections(id) ON DELETE SET NULL,
|
||||
current_graph_app_id TEXT,
|
||||
auto_check INTEGER NOT NULL DEFAULT 0,
|
||||
last_checked_at TEXT,
|
||||
last_check_message TEXT,
|
||||
notes TEXT,
|
||||
visibility TEXT NOT NULL DEFAULT 'personal',
|
||||
owner_user_id TEXT REFERENCES users(id) ON DELETE SET NULL,
|
||||
group_id TEXT REFERENCES groups(id) ON DELETE SET NULL,
|
||||
created_by TEXT REFERENCES users(id) ON DELETE SET NULL,
|
||||
created_at TEXT NOT NULL,
|
||||
updated_at TEXT NOT NULL
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS change_requests (
|
||||
id TEXT PRIMARY KEY,
|
||||
deployment_id TEXT REFERENCES intune_deployments(id) ON DELETE CASCADE,
|
||||
connection_id TEXT REFERENCES graph_connections(id) ON DELETE SET NULL,
|
||||
action TEXT NOT NULL,
|
||||
status TEXT NOT NULL DEFAULT 'pending',
|
||||
requested_by TEXT REFERENCES users(id) ON DELETE SET NULL,
|
||||
requester_email TEXT,
|
||||
approver_user_id TEXT REFERENCES users(id) ON DELETE SET NULL,
|
||||
approver_email TEXT,
|
||||
reason TEXT,
|
||||
decided_at TEXT,
|
||||
executed_at TEXT,
|
||||
created_at TEXT NOT NULL,
|
||||
updated_at TEXT NOT NULL
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS graph_audit_log (
|
||||
id TEXT PRIMARY KEY,
|
||||
connection_id TEXT REFERENCES graph_connections(id) ON DELETE SET NULL,
|
||||
deployment_id TEXT REFERENCES intune_deployments(id) ON DELETE SET NULL,
|
||||
actor_user_id TEXT REFERENCES users(id) ON DELETE SET NULL,
|
||||
actor_email TEXT,
|
||||
action TEXT NOT NULL,
|
||||
target_graph_id TEXT,
|
||||
status TEXT NOT NULL,
|
||||
message TEXT,
|
||||
request_json TEXT NOT NULL DEFAULT '{}',
|
||||
response_json TEXT NOT NULL DEFAULT '{}',
|
||||
created_at TEXT NOT NULL
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS script_versions (
|
||||
id TEXT PRIMARY KEY,
|
||||
script_id TEXT NOT NULL REFERENCES scripts(id) ON DELETE CASCADE,
|
||||
version INTEGER NOT NULL,
|
||||
content TEXT NOT NULL,
|
||||
created_by TEXT REFERENCES users(id) ON DELETE SET NULL,
|
||||
created_at TEXT NOT NULL
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS runplans (
|
||||
id TEXT PRIMARY KEY,
|
||||
name TEXT NOT NULL,
|
||||
description TEXT,
|
||||
script_id TEXT NOT NULL REFERENCES scripts(id) ON DELETE CASCADE,
|
||||
visibility TEXT NOT NULL DEFAULT 'personal',
|
||||
owner_user_id TEXT REFERENCES users(id) ON DELETE SET NULL,
|
||||
group_id TEXT REFERENCES groups(id) ON DELETE SET NULL,
|
||||
parallel INTEGER NOT NULL DEFAULT 1,
|
||||
created_at TEXT NOT NULL,
|
||||
updated_at TEXT NOT NULL
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS runplan_hosts (
|
||||
runplan_id TEXT NOT NULL REFERENCES runplans(id) ON DELETE CASCADE,
|
||||
host_id TEXT NOT NULL REFERENCES hosts(id) ON DELETE CASCADE,
|
||||
PRIMARY KEY (runplan_id, host_id)
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS jobs (
|
||||
id TEXT PRIMARY KEY,
|
||||
runplan_id TEXT REFERENCES runplans(id) ON DELETE SET NULL,
|
||||
script_id TEXT REFERENCES scripts(id) ON DELETE SET NULL,
|
||||
status TEXT NOT NULL,
|
||||
triggered_by TEXT REFERENCES users(id) ON DELETE SET NULL,
|
||||
started_at TEXT,
|
||||
ended_at TEXT,
|
||||
created_at TEXT NOT NULL
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS job_hosts (
|
||||
id TEXT PRIMARY KEY,
|
||||
job_id TEXT NOT NULL REFERENCES jobs(id) ON DELETE CASCADE,
|
||||
host_id TEXT REFERENCES hosts(id) ON DELETE SET NULL,
|
||||
status TEXT NOT NULL,
|
||||
exit_code INTEGER,
|
||||
started_at TEXT,
|
||||
ended_at TEXT
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS job_logs (
|
||||
id TEXT PRIMARY KEY,
|
||||
job_id TEXT NOT NULL REFERENCES jobs(id) ON DELETE CASCADE,
|
||||
host_id TEXT REFERENCES hosts(id) ON DELETE SET NULL,
|
||||
stream TEXT NOT NULL,
|
||||
line TEXT NOT NULL,
|
||||
created_at TEXT NOT NULL
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS settings (
|
||||
key TEXT PRIMARY KEY,
|
||||
value TEXT NOT NULL,
|
||||
source TEXT NOT NULL DEFAULT 'db',
|
||||
updated_at TEXT NOT NULL
|
||||
);
|
||||
`);
|
||||
|
||||
ensureColumn('users', 'theme', "TEXT NOT NULL DEFAULT 'dashtreme'");
|
||||
ensureColumn('users', 'job_title', 'TEXT');
|
||||
ensureColumn('users', 'phone', 'TEXT');
|
||||
ensureColumn('users', 'timezone', 'TEXT');
|
||||
ensureColumn('users', 'avatar_url', 'TEXT');
|
||||
ensureColumn('users', 'updated_at', 'TEXT');
|
||||
ensureColumn('intune_deployments', 'source_folder', 'TEXT');
|
||||
ensureColumn('intune_deployments', 'intunewin_file', 'TEXT');
|
||||
ensureColumn('intune_deployments', 'command_style', "TEXT NOT NULL DEFAULT 'v4'");
|
||||
ensureColumn('intune_deployments', 'install_behavior', "TEXT NOT NULL DEFAULT 'system'");
|
||||
ensureColumn('intune_deployments', 'restart_behavior', "TEXT NOT NULL DEFAULT 'return-code'");
|
||||
ensureColumn('intune_deployments', 'ui_mode', "TEXT NOT NULL DEFAULT 'native-v4'");
|
||||
ensureColumn('intune_deployments', 'requirements', "TEXT NOT NULL DEFAULT '{}'");
|
||||
ensureColumn('intune_deployments', 'graph_app_id', 'TEXT');
|
||||
ensureColumn('intune_deployments', 'graph_connection_id', 'TEXT');
|
||||
ensureColumn('intune_deployments', 'relationships', "TEXT NOT NULL DEFAULT '[]'");
|
||||
ensureColumn('intune_deployments', 'application_id', 'TEXT REFERENCES applications(id) ON DELETE SET NULL');
|
||||
// Auto-promote: after the soak window elapses, advance a rollout ring's intent.
|
||||
ensureColumn('intune_deployments', 'auto_promote_after_hours', 'INTEGER NOT NULL DEFAULT 0');
|
||||
ensureColumn('intune_deployments', 'auto_promote_ring', 'TEXT');
|
||||
ensureColumn('intune_deployments', 'auto_promote_intent', "TEXT NOT NULL DEFAULT 'required'");
|
||||
ensureColumn('intune_deployments', 'soak_started_at', 'TEXT');
|
||||
ensureColumn('intune_deployments', 'promoted_at', 'TEXT');
|
||||
// Host visibility scoping. Existing rows default to 'shared' so prior global
|
||||
// hosts remain visible to everyone; new hosts can be personal/group-scoped.
|
||||
ensureColumn('hosts', 'visibility', "TEXT NOT NULL DEFAULT 'shared'");
|
||||
ensureColumn('hosts', 'owner_user_id', 'TEXT REFERENCES users(id) ON DELETE SET NULL');
|
||||
ensureColumn('hosts', 'group_id', 'TEXT REFERENCES groups(id) ON DELETE SET NULL');
|
||||
// Tenant write-back is opt-in per Graph connection. Default 0 so existing
|
||||
// read-only connections can never be used to change a tenant by accident.
|
||||
ensureColumn('graph_connections', 'allow_write', 'INTEGER NOT NULL DEFAULT 0');
|
||||
// When set, tenant-changing actions through this connection require an
|
||||
// approved change request before they run.
|
||||
ensureColumn('graph_connections', 'require_approval', 'INTEGER NOT NULL DEFAULT 0');
|
||||
// Optional named-RBAC lists (JSON arrays of user ids). Empty = no restriction
|
||||
// beyond the allow_write/admin gates.
|
||||
ensureColumn('graph_connections', 'publisher_ids', "TEXT NOT NULL DEFAULT '[]'");
|
||||
ensureColumn('graph_connections', 'approver_ids', "TEXT NOT NULL DEFAULT '[]'");
|
||||
}
|
||||
|
||||
export function seed() {
|
||||
const userCount = db.prepare('SELECT COUNT(*) AS count FROM users').get().count;
|
||||
if (userCount === 0) {
|
||||
const adminId = id('usr');
|
||||
db.prepare(`
|
||||
INSERT INTO users (id, email, display_name, password_hash, auth_provider, role, created_at)
|
||||
VALUES (?, ?, ?, ?, 'local', 'admin', ?)
|
||||
`).run(
|
||||
adminId,
|
||||
config.defaultAdminEmail.toLowerCase(),
|
||||
config.defaultAdminName,
|
||||
bcrypt.hashSync(config.defaultAdminPassword, 12),
|
||||
now()
|
||||
);
|
||||
const groupId = id('grp');
|
||||
db.prepare('INSERT INTO groups (id, name, description, created_at) VALUES (?, ?, ?, ?)').run(
|
||||
groupId,
|
||||
'Operations',
|
||||
'Default shared operations group',
|
||||
now()
|
||||
);
|
||||
db.prepare('INSERT INTO user_groups (user_id, group_id) VALUES (?, ?)').run(adminId, groupId);
|
||||
|
||||
const folderId = id('fld');
|
||||
db.prepare(`
|
||||
INSERT INTO folders (id, parent_id, name, visibility, owner_user_id, group_id, created_at, updated_at)
|
||||
VALUES (?, NULL, 'Examples', 'shared', ?, NULL, ?, ?)
|
||||
`).run(folderId, adminId, now(), now());
|
||||
|
||||
const scriptId = id('scr');
|
||||
const sample = [
|
||||
'Write-Output "POSHManager sample run"',
|
||||
'Write-Output "Target host: $env:POSHM_HOST_NAME"',
|
||||
'Get-Date'
|
||||
].join('\n');
|
||||
db.prepare(`
|
||||
INSERT INTO scripts (id, folder_id, name, description, content, visibility, owner_user_id, group_id, version, created_at, updated_at)
|
||||
VALUES (?, ?, ?, ?, ?, 'shared', ?, NULL, 1, ?, ?)
|
||||
`).run(scriptId, folderId, 'Sample Health Check.ps1', 'Simple smoke-test script for local execution.', sample, adminId, now(), now());
|
||||
db.prepare(`
|
||||
INSERT INTO script_versions (id, script_id, version, content, created_by, created_at)
|
||||
VALUES (?, ?, 1, ?, ?, ?)
|
||||
`).run(id('ver'), scriptId, sample, adminId, now());
|
||||
}
|
||||
|
||||
// Env-pinned settings are refreshed from the environment on every boot and
|
||||
// marked source='env' (locked in the UI). Unpinned settings are seeded once
|
||||
// as editable defaults so admin edits in the Config screen survive restarts.
|
||||
const pinned = db.prepare(`
|
||||
INSERT INTO settings (key, value, source, updated_at)
|
||||
VALUES (?, ?, 'env', ?)
|
||||
ON CONFLICT(key) DO UPDATE SET value = excluded.value, source = 'env', updated_at = excluded.updated_at
|
||||
`);
|
||||
const defaulted = db.prepare(`
|
||||
INSERT INTO settings (key, value, source, updated_at)
|
||||
VALUES (?, ?, 'default', ?)
|
||||
ON CONFLICT(key) DO NOTHING
|
||||
`);
|
||||
for (const def of settingDefinitions()) {
|
||||
if (def.pinned) pinned.run(def.key, def.value, now());
|
||||
else defaulted.run(def.key, def.value, now());
|
||||
}
|
||||
}
|
||||
|
||||
export function visibleClause(alias = '') {
|
||||
const p = alias ? `${alias}.` : '';
|
||||
return `(${p}visibility = 'shared' OR ${p}owner_user_id = ? OR ${p}group_id IN (SELECT group_id FROM user_groups WHERE user_id = ?))`;
|
||||
}
|
||||
Reference in New Issue
Block a user