mirror of
https://github.com/Termix-SSH/Termix.git
synced 2026-10-29 14:45:24 +00:00
feat: add ctx.hosts create/update to the SDK, host-metrics drops proxmox
This commit is contained in:
1 parent
f9283ec83f
commit
218c5088e7
20 files changed
+194
-1329
No files matched your search
@@ -409,6 +409,56 @@ export interface PluginHostSummary {
|
||||
authType: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* A host record wide enough for a plugin that creates or updates hosts on the
|
||||
* user's behalf (import, discovery, sync), decrypted for that user. Unlike
|
||||
* PluginHostSummary this carries the fields a plugin needs to set up a
|
||||
* connectable host, not just what a list view shows. Still no secret auth
|
||||
* material (password, key, vault tokens): a plugin that creates a host picks
|
||||
* an authType and, for "credential", a credentialId it does not need to see
|
||||
* the contents of.
|
||||
*/
|
||||
export interface PluginHostRecord {
|
||||
id: number;
|
||||
userId: string;
|
||||
name: string | null;
|
||||
ip: string;
|
||||
port: number;
|
||||
username: string;
|
||||
authType: string;
|
||||
credentialId?: number | null;
|
||||
overrideCredentialUsername?: boolean | null;
|
||||
connectionType?: string | null;
|
||||
tags: string | null;
|
||||
folder: string | null;
|
||||
jumpHosts?: unknown;
|
||||
enableSsh?: boolean | null;
|
||||
enableRdp?: boolean | null;
|
||||
enableTerminal?: boolean | null;
|
||||
enableFileManager?: boolean | null;
|
||||
enableTunnel?: boolean | null;
|
||||
enableDocker?: boolean | null;
|
||||
createdAt?: string | null;
|
||||
updatedAt?: string | null;
|
||||
[key: string]: unknown;
|
||||
}
|
||||
|
||||
/** Fields a plugin may set when creating a host it will own or manage. */
|
||||
export type PluginHostCreateInput = Partial<
|
||||
Omit<PluginHostRecord, "id" | "userId">
|
||||
> & {
|
||||
name: string;
|
||||
ip: string;
|
||||
port: number;
|
||||
username: string;
|
||||
authType: string;
|
||||
};
|
||||
|
||||
/** Fields a plugin may change on a host it already created or was granted access to. */
|
||||
export type PluginHostUpdateInput = Partial<
|
||||
Omit<PluginHostRecord, "id" | "userId">
|
||||
>;
|
||||
|
||||
/** The same shape canAccessHost returns internally, without secrets. */
|
||||
export interface PluginHostAccess {
|
||||
hasAccess: boolean;
|
||||
@@ -462,6 +512,30 @@ export interface PluginHosts {
|
||||
hostId: number,
|
||||
level: PluginHostShareLevel,
|
||||
) => Promise<PluginHostAccess>;
|
||||
/**
|
||||
* Creates a host owned by the acting user, encrypted the same way the host
|
||||
* editor's own create route does it. Needs hosts:write. For a plugin that
|
||||
* imports or discovers hosts on the user's behalf (B5's proxmox discovery).
|
||||
*/
|
||||
create: (host: PluginHostCreateInput) => Promise<PluginHostRecord>;
|
||||
/**
|
||||
* Updates a host the acting user owns. Refuses a host it does not own
|
||||
* (sharing a write onto someone else's host goes through share(), not
|
||||
* update()). Needs hosts:write.
|
||||
*/
|
||||
update: (
|
||||
hostId: number,
|
||||
patch: PluginHostUpdateInput,
|
||||
) => Promise<PluginHostRecord | null>;
|
||||
/**
|
||||
* Every host the acting user owns, decrypted and wide (PluginHostRecord,
|
||||
* not the narrow list() summary). For a plugin that scans its own hosts in
|
||||
* the background (auto-sync) and needs fields list() does not carry, such
|
||||
* as credentialId or jumpHosts. Needs hosts:write, matching create/update:
|
||||
* this is the same "full host detail" surface a create/update caller needs
|
||||
* to read back, not a wider read grant than hosts:read gives via list/get.
|
||||
*/
|
||||
listOwned: () => Promise<PluginHostRecord[]>;
|
||||
/**
|
||||
* Grants access to a host the caller manages, snapshotting shared secrets
|
||||
* for each target the same way the host editor's own share action does.
|
||||
|
||||
@@ -20,6 +20,7 @@ export type PluginColumnType =
|
||||
| "varchar"
|
||||
| "integer"
|
||||
| "bigint"
|
||||
| "real"
|
||||
| "boolean"
|
||||
| "timestamp"
|
||||
| "json"
|
||||
@@ -77,6 +78,8 @@ export const varchar = (length: number = KEY_LENGTH) =>
|
||||
|
||||
export const integer = () => builder({ type: "integer" });
|
||||
export const bigint = () => builder({ type: "bigint" });
|
||||
/** Floating point. A plain number on every engine. Never indexable. */
|
||||
export const real = () => builder({ type: "real" });
|
||||
export const boolean = () => builder({ type: "boolean" });
|
||||
|
||||
/** Stored as text on every engine. See src/backend/database/repositories/sql-timestamp.ts. */
|
||||
|
||||
@@ -62,6 +62,9 @@ function columnType(
|
||||
return dialect === "mysql" ? "int" : "integer";
|
||||
case "bigint":
|
||||
return dialect === "sqlite" ? "integer" : "bigint";
|
||||
case "real":
|
||||
if (dialect === "sqlite") return "real";
|
||||
return dialect === "mysql" ? "double" : "double precision";
|
||||
case "boolean":
|
||||
return dialect === "sqlite" ? "integer" : "boolean";
|
||||
case "varchar":
|
||||
|
||||
@@ -12,6 +12,7 @@ import {
|
||||
sqliteTable,
|
||||
text as sqliteText,
|
||||
integer as sqliteInteger,
|
||||
real as sqliteReal,
|
||||
index as sqliteIndex,
|
||||
uniqueIndex as sqliteUniqueIndex,
|
||||
type AnySQLiteColumn,
|
||||
@@ -55,6 +56,9 @@ export function buildTable(
|
||||
case "refHost":
|
||||
built = sqliteInteger(name);
|
||||
break;
|
||||
case "real":
|
||||
built = sqliteReal(name);
|
||||
break;
|
||||
case "boolean":
|
||||
built = sqliteInteger(name, { mode: "boolean" });
|
||||
break;
|
||||
|
||||
@@ -24,6 +24,9 @@ import type {
|
||||
PluginSshAuthProvider,
|
||||
PluginSshHost,
|
||||
PluginHostSummary,
|
||||
PluginHostRecord,
|
||||
PluginHostCreateInput,
|
||||
PluginHostUpdateInput,
|
||||
PluginHostAccess,
|
||||
PluginHostShareResult,
|
||||
PluginShareableUser,
|
||||
@@ -174,6 +177,8 @@ export function createFakeContext(
|
||||
const hostsById = new Map<number, PluginHostSummary>(
|
||||
(options.hosts ?? []).map((h) => [h.id, h]),
|
||||
);
|
||||
const hostRecordsById = new Map<number, PluginHostRecord>();
|
||||
let nextHostId = Math.max(0, ...(options.hosts ?? []).map((h) => h.id)) + 1;
|
||||
const services = new Map<string, object>();
|
||||
const auth: FakeAuthRegistrations = {
|
||||
sshAuthProviders: [],
|
||||
@@ -421,6 +426,33 @@ export function createFakeContext(
|
||||
permissionLevel: "manage",
|
||||
};
|
||||
},
|
||||
create: async (
|
||||
host: PluginHostCreateInput,
|
||||
): Promise<PluginHostRecord> => {
|
||||
const id = nextHostId++;
|
||||
const record: PluginHostRecord = {
|
||||
tags: null,
|
||||
folder: null,
|
||||
...host,
|
||||
id,
|
||||
userId: actor ?? "unknown",
|
||||
};
|
||||
hostRecordsById.set(id, record);
|
||||
return record;
|
||||
},
|
||||
update: async (
|
||||
hostId: number,
|
||||
patch: PluginHostUpdateInput,
|
||||
): Promise<PluginHostRecord | null> => {
|
||||
const existing = hostRecordsById.get(hostId);
|
||||
if (!existing) return null;
|
||||
const updated = { ...existing, ...patch };
|
||||
hostRecordsById.set(hostId, updated);
|
||||
return updated;
|
||||
},
|
||||
listOwned: async (): Promise<PluginHostRecord[]> => [
|
||||
...hostRecordsById.values(),
|
||||
],
|
||||
share: async (
|
||||
hostId,
|
||||
targets,
|
||||
@@ -706,6 +738,18 @@ export function createMockCtx(
|
||||
require("hosts:read");
|
||||
return ctx.hosts.checkAccess(hostId, level);
|
||||
},
|
||||
create: async (host) => {
|
||||
require("hosts:write");
|
||||
return ctx.hosts.create(host);
|
||||
},
|
||||
update: async (hostId, patch) => {
|
||||
require("hosts:write");
|
||||
return ctx.hosts.update(hostId, patch);
|
||||
},
|
||||
listOwned: async () => {
|
||||
require("hosts:write");
|
||||
return ctx.hosts.listOwned();
|
||||
},
|
||||
share: async (hostId, targets, permissionLevel, durationHours) => {
|
||||
require("hosts:write");
|
||||
return ctx.hosts.share(hostId, targets, permissionLevel, durationHours);
|
||||
|
||||
Reference in new issue
Block a user