mirror of
https://github.com/Termix-SSH/Termix.git
synced 2026-10-28 14:15:24 +00:00
feat: finish the fleets plugin, add ctx.hosts and host-commands SDK (B4)
This commit is contained in:
1 parent
d2ff5fc78f
commit
3b2c849514
59 files changed
+31807
-2977
No files matched your search
@@ -2664,87 +2664,6 @@ const migrateSchema = () => {
|
||||
}
|
||||
// --- homepage end ---
|
||||
|
||||
// --- fleets begin ---
|
||||
try {
|
||||
sqlite.prepare("SELECT id FROM fleets LIMIT 1").get();
|
||||
} catch {
|
||||
try {
|
||||
sqlite.exec(`
|
||||
CREATE TABLE IF NOT EXISTS fleets (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
user_id TEXT NOT NULL REFERENCES users(id) ON DELETE CASCADE,
|
||||
name TEXT NOT NULL,
|
||||
description TEXT,
|
||||
color TEXT,
|
||||
icon TEXT,
|
||||
tag_rules TEXT,
|
||||
sync_id TEXT UNIQUE,
|
||||
created_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
`);
|
||||
} catch (createError) {
|
||||
databaseLogger.warn("Failed to create fleets table", {
|
||||
operation: "schema_migration",
|
||||
error: createError,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
sqlite.prepare("SELECT id FROM fleet_members LIMIT 1").get();
|
||||
} catch {
|
||||
try {
|
||||
sqlite.exec(`
|
||||
CREATE TABLE IF NOT EXISTS fleet_members (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
fleet_id INTEGER NOT NULL REFERENCES fleets(id) ON DELETE CASCADE,
|
||||
host_id INTEGER NOT NULL REFERENCES ssh_data(id) ON DELETE CASCADE,
|
||||
added_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
`);
|
||||
sqlite.exec(
|
||||
"CREATE UNIQUE INDEX IF NOT EXISTS idx_fleet_members_fleet_host ON fleet_members(fleet_id, host_id)",
|
||||
);
|
||||
} catch (createError) {
|
||||
databaseLogger.warn("Failed to create fleet_members table", {
|
||||
operation: "schema_migration",
|
||||
error: createError,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
sqlite.prepare("SELECT id FROM fleet_inventory LIMIT 1").get();
|
||||
} catch {
|
||||
try {
|
||||
sqlite.exec(`
|
||||
CREATE TABLE IF NOT EXISTS fleet_inventory (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
host_id INTEGER NOT NULL REFERENCES ssh_data(id) ON DELETE CASCADE,
|
||||
user_id TEXT NOT NULL REFERENCES users(id) ON DELETE CASCADE,
|
||||
os_pretty_name TEXT,
|
||||
kernel TEXT,
|
||||
architecture TEXT,
|
||||
hostname TEXT,
|
||||
uptime_seconds INTEGER,
|
||||
ip TEXT,
|
||||
package_manager TEXT,
|
||||
collected_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
`);
|
||||
sqlite.exec(
|
||||
"CREATE UNIQUE INDEX IF NOT EXISTS idx_fleet_inventory_host ON fleet_inventory(host_id, user_id)",
|
||||
);
|
||||
} catch (createError) {
|
||||
databaseLogger.warn("Failed to create fleet_inventory table", {
|
||||
operation: "schema_migration",
|
||||
error: createError,
|
||||
});
|
||||
}
|
||||
}
|
||||
// --- fleets end ---
|
||||
|
||||
// --- sync begin ---
|
||||
// Stable per-row identity used to match rows across two independently-
|
||||
// seeded databases (the embedded desktop backend and a connected remote
|
||||
|
||||
@@ -168,23 +168,6 @@ export const PERFORMANCE_INDEXES: PerformanceIndex[] = [
|
||||
columns: "host_id",
|
||||
},
|
||||
|
||||
// Fleets.
|
||||
{
|
||||
name: "idx_fleet_members_fleet",
|
||||
table: "fleet_members",
|
||||
columns: "fleet_id",
|
||||
},
|
||||
{
|
||||
name: "idx_fleet_members_host",
|
||||
table: "fleet_members",
|
||||
columns: "host_id",
|
||||
},
|
||||
{
|
||||
name: "idx_fleet_inventory_user",
|
||||
table: "fleet_inventory",
|
||||
columns: "user_id",
|
||||
},
|
||||
|
||||
// Automations.
|
||||
{
|
||||
name: "idx_automations_user",
|
||||
|
||||
@@ -1616,81 +1616,6 @@ export const homepageLayouts = mysqlTable("homepage_layouts", {
|
||||
});
|
||||
// --- homepage end ---
|
||||
|
||||
// --- fleets begin ---
|
||||
export const fleets = mysqlTable("fleets", {
|
||||
id: int("id").autoincrement().primaryKey(),
|
||||
userId: varchar("user_id", { length: 255 })
|
||||
.notNull()
|
||||
.references(() => users.id, { onDelete: "cascade" }),
|
||||
name: varchar("name", { length: 255 }).notNull(),
|
||||
description: text("description"),
|
||||
color: text("color"),
|
||||
icon: text("icon"),
|
||||
// JSON array of { tag: string } rules, unioned with static fleetMembers at
|
||||
// resolution time. Kept to tag-equality matching for v1.
|
||||
tagRules: text("tag_rules"),
|
||||
syncId: varchar("sync_id", { length: 255 }).unique(),
|
||||
createdAt: varchar("created_at", { length: 255 })
|
||||
.notNull()
|
||||
.default(sql`(CURRENT_TIMESTAMP)`),
|
||||
updatedAt: varchar("updated_at", { length: 255 })
|
||||
.notNull()
|
||||
.default(sql`(CURRENT_TIMESTAMP)`),
|
||||
});
|
||||
|
||||
export const fleetMembers = mysqlTable(
|
||||
"fleet_members",
|
||||
{
|
||||
id: int("id").autoincrement().primaryKey(),
|
||||
fleetId: int("fleet_id")
|
||||
.notNull()
|
||||
.references(() => fleets.id, { onDelete: "cascade" }),
|
||||
hostId: int("host_id")
|
||||
.notNull()
|
||||
.references(() => hosts.id, { onDelete: "cascade" }),
|
||||
addedAt: text("added_at")
|
||||
.notNull()
|
||||
.default(sql`(CURRENT_TIMESTAMP)`),
|
||||
},
|
||||
// fleet_id leads the unique pair, so listing a fleet's hosts is already
|
||||
// served. Finding the fleets a host belongs to starts from host_id.
|
||||
(table) => [
|
||||
uniqueIndex("idx_fleet_members_fleet_host").on(table.fleetId, table.hostId),
|
||||
index("idx_fleet_members_host").on(table.hostId),
|
||||
],
|
||||
);
|
||||
|
||||
// Latest-only inventory snapshot per host, overwritten on each refresh - no
|
||||
// historical log, matching the "latest snapshot only" scope decision.
|
||||
export const fleetInventory = mysqlTable(
|
||||
"fleet_inventory",
|
||||
{
|
||||
id: int("id").autoincrement().primaryKey(),
|
||||
hostId: int("host_id")
|
||||
.notNull()
|
||||
.references(() => hosts.id, { onDelete: "cascade" }),
|
||||
userId: varchar("user_id", { length: 255 })
|
||||
.notNull()
|
||||
.references(() => users.id, { onDelete: "cascade" }),
|
||||
osPrettyName: text("os_pretty_name"),
|
||||
kernel: text("kernel"),
|
||||
architecture: text("architecture"),
|
||||
hostname: text("hostname"),
|
||||
uptimeSeconds: int("uptime_seconds"),
|
||||
ip: text("ip"),
|
||||
packageManager: text("package_manager"),
|
||||
collectedAt: text("collected_at")
|
||||
.notNull()
|
||||
.default(sql`(CURRENT_TIMESTAMP)`),
|
||||
},
|
||||
// host_id leads the unique pair; a user's whole inventory is read by user_id.
|
||||
(table) => [
|
||||
uniqueIndex("idx_fleet_inventory_host").on(table.hostId, table.userId),
|
||||
index("idx_fleet_inventory_user").on(table.userId),
|
||||
],
|
||||
);
|
||||
// --- fleets end ---
|
||||
|
||||
|
||||
|
||||
// --- sync begin ---
|
||||
|
||||
@@ -1617,81 +1617,6 @@ export const homepageLayouts = pgTable("homepage_layouts", {
|
||||
});
|
||||
// --- homepage end ---
|
||||
|
||||
// --- fleets begin ---
|
||||
export const fleets = pgTable("fleets", {
|
||||
id: serial("id").primaryKey(),
|
||||
userId: varchar("user_id", { length: 255 })
|
||||
.notNull()
|
||||
.references(() => users.id, { onDelete: "cascade" }),
|
||||
name: varchar("name", { length: 255 }).notNull(),
|
||||
description: text("description"),
|
||||
color: text("color"),
|
||||
icon: text("icon"),
|
||||
// JSON array of { tag: string } rules, unioned with static fleetMembers at
|
||||
// resolution time. Kept to tag-equality matching for v1.
|
||||
tagRules: text("tag_rules"),
|
||||
syncId: varchar("sync_id", { length: 255 }).unique(),
|
||||
createdAt: varchar("created_at", { length: 255 })
|
||||
.notNull()
|
||||
.default(sql`CURRENT_TIMESTAMP`),
|
||||
updatedAt: varchar("updated_at", { length: 255 })
|
||||
.notNull()
|
||||
.default(sql`CURRENT_TIMESTAMP`),
|
||||
});
|
||||
|
||||
export const fleetMembers = pgTable(
|
||||
"fleet_members",
|
||||
{
|
||||
id: serial("id").primaryKey(),
|
||||
fleetId: integer("fleet_id")
|
||||
.notNull()
|
||||
.references(() => fleets.id, { onDelete: "cascade" }),
|
||||
hostId: integer("host_id")
|
||||
.notNull()
|
||||
.references(() => hosts.id, { onDelete: "cascade" }),
|
||||
addedAt: text("added_at")
|
||||
.notNull()
|
||||
.default(sql`CURRENT_TIMESTAMP`),
|
||||
},
|
||||
// fleet_id leads the unique pair, so listing a fleet's hosts is already
|
||||
// served. Finding the fleets a host belongs to starts from host_id.
|
||||
(table) => [
|
||||
uniqueIndex("idx_fleet_members_fleet_host").on(table.fleetId, table.hostId),
|
||||
index("idx_fleet_members_host").on(table.hostId),
|
||||
],
|
||||
);
|
||||
|
||||
// Latest-only inventory snapshot per host, overwritten on each refresh - no
|
||||
// historical log, matching the "latest snapshot only" scope decision.
|
||||
export const fleetInventory = pgTable(
|
||||
"fleet_inventory",
|
||||
{
|
||||
id: serial("id").primaryKey(),
|
||||
hostId: integer("host_id")
|
||||
.notNull()
|
||||
.references(() => hosts.id, { onDelete: "cascade" }),
|
||||
userId: varchar("user_id", { length: 255 })
|
||||
.notNull()
|
||||
.references(() => users.id, { onDelete: "cascade" }),
|
||||
osPrettyName: text("os_pretty_name"),
|
||||
kernel: text("kernel"),
|
||||
architecture: text("architecture"),
|
||||
hostname: text("hostname"),
|
||||
uptimeSeconds: integer("uptime_seconds"),
|
||||
ip: text("ip"),
|
||||
packageManager: text("package_manager"),
|
||||
collectedAt: text("collected_at")
|
||||
.notNull()
|
||||
.default(sql`CURRENT_TIMESTAMP`),
|
||||
},
|
||||
// host_id leads the unique pair; a user's whole inventory is read by user_id.
|
||||
(table) => [
|
||||
uniqueIndex("idx_fleet_inventory_host").on(table.hostId, table.userId),
|
||||
index("idx_fleet_inventory_user").on(table.userId),
|
||||
],
|
||||
);
|
||||
// --- fleets end ---
|
||||
|
||||
|
||||
|
||||
// --- sync begin ---
|
||||
|
||||
@@ -1615,81 +1615,6 @@ export const homepageLayouts = sqliteTable("homepage_layouts", {
|
||||
});
|
||||
// --- homepage end ---
|
||||
|
||||
// --- fleets begin ---
|
||||
export const fleets = sqliteTable("fleets", {
|
||||
id: integer("id").primaryKey({ autoIncrement: true }),
|
||||
userId: text("user_id")
|
||||
.notNull()
|
||||
.references(() => users.id, { onDelete: "cascade" }),
|
||||
name: text("name").notNull(),
|
||||
description: text("description"),
|
||||
color: text("color"),
|
||||
icon: text("icon"),
|
||||
// JSON array of { tag: string } rules, unioned with static fleetMembers at
|
||||
// resolution time. Kept to tag-equality matching for v1.
|
||||
tagRules: text("tag_rules"),
|
||||
syncId: text("sync_id").unique(),
|
||||
createdAt: text("created_at")
|
||||
.notNull()
|
||||
.default(sql`CURRENT_TIMESTAMP`),
|
||||
updatedAt: text("updated_at")
|
||||
.notNull()
|
||||
.default(sql`CURRENT_TIMESTAMP`),
|
||||
});
|
||||
|
||||
export const fleetMembers = sqliteTable(
|
||||
"fleet_members",
|
||||
{
|
||||
id: integer("id").primaryKey({ autoIncrement: true }),
|
||||
fleetId: integer("fleet_id")
|
||||
.notNull()
|
||||
.references(() => fleets.id, { onDelete: "cascade" }),
|
||||
hostId: integer("host_id")
|
||||
.notNull()
|
||||
.references(() => hosts.id, { onDelete: "cascade" }),
|
||||
addedAt: text("added_at")
|
||||
.notNull()
|
||||
.default(sql`CURRENT_TIMESTAMP`),
|
||||
},
|
||||
// fleet_id leads the unique pair, so listing a fleet's hosts is already
|
||||
// served. Finding the fleets a host belongs to starts from host_id.
|
||||
(table) => [
|
||||
uniqueIndex("idx_fleet_members_fleet_host").on(table.fleetId, table.hostId),
|
||||
index("idx_fleet_members_host").on(table.hostId),
|
||||
],
|
||||
);
|
||||
|
||||
// Latest-only inventory snapshot per host, overwritten on each refresh - no
|
||||
// historical log, matching the "latest snapshot only" scope decision.
|
||||
export const fleetInventory = sqliteTable(
|
||||
"fleet_inventory",
|
||||
{
|
||||
id: integer("id").primaryKey({ autoIncrement: true }),
|
||||
hostId: integer("host_id")
|
||||
.notNull()
|
||||
.references(() => hosts.id, { onDelete: "cascade" }),
|
||||
userId: text("user_id")
|
||||
.notNull()
|
||||
.references(() => users.id, { onDelete: "cascade" }),
|
||||
osPrettyName: text("os_pretty_name"),
|
||||
kernel: text("kernel"),
|
||||
architecture: text("architecture"),
|
||||
hostname: text("hostname"),
|
||||
uptimeSeconds: integer("uptime_seconds"),
|
||||
ip: text("ip"),
|
||||
packageManager: text("package_manager"),
|
||||
collectedAt: text("collected_at")
|
||||
.notNull()
|
||||
.default(sql`CURRENT_TIMESTAMP`),
|
||||
},
|
||||
// host_id leads the unique pair; a user's whole inventory is read by user_id.
|
||||
(table) => [
|
||||
uniqueIndex("idx_fleet_inventory_host").on(table.hostId, table.userId),
|
||||
index("idx_fleet_inventory_user").on(table.userId),
|
||||
],
|
||||
);
|
||||
// --- fleets end ---
|
||||
|
||||
|
||||
|
||||
// --- sync begin ---
|
||||
|
||||
Reference in new issue
Block a user