Files
komodo/ui/public/client/lib.js
T
Maxwell Becker 96c4ae9fc5 2.0.0 UI (#1220)
* new ui using mantine

* resources page

* prog on resource page

* resources and resource layouts

* confirm button and modal

* tweaks

* update details

* topbar updates

* add skeletons for resource implementations

* add resource tables

* add tags to recents cards

* resource page table scrolling

* table component + tags filter

* export toml

* New Resource button

* Fix update details capture closing

* tweaks

* omni search

* refine config

* config tweaks

* implement more configs / resource selector

* add profile page

* provider / account selectors

* container table page

* build config

* deployment config

* fix deployment build version selector

* fix secrets selector

* resource sync config

* mobile topbar and updates

* update details fz sm

* stack config

* terminals page

* create terminal in prog

* create terminal menu

* finish create terminal menu

* terminal pages working

* stack tabs / info

* add executions

* add server header info

* confirm pubkey modal

* improve resource header styling

* FileSource component

* stack service table, move icons.ts

* basic procedure config

* tweak procedure config

* container / image pages

* network / volume pages

* clean up docker resource pages

*  basic log / terminal ui

* reusable log section

* styling

* clean up resource components

* delete in resource header

* log auto select stderr

* fix some bgs

* stack logs with service selector

* stack terminals

* add deployment executions

* use correct icon

* useResource hooks

* build info

* build info

* tweaks

* server tabs

* fix terminal section target

* prog on server tabs

* server stats

* light theme

* start on historical stats

* stack service page

* resource sync tabs

* sync tabs

* more topbar icons

* add settings basic

* add topbar alerts

* tweak stream selector behavior

* tweak alert icon topbar

* improve styling smaller screen

* schedules page and other progress

* onboarding keys

* improve schedule page descriptions

* improve update notifications

* schedule timezone selector

* tag color selector

* finish settings / providers

* use shared-text-update component so settings tables aren't janky

* updates page

* refine updates page

* alert page

* standardize borders

* theme and swarm

* swarm tabs

* swarm node page

* swarm config page

* swarm pages

* swarm task and secret pages

* swarm stack page

* fix stack log service selector in swarm mode

* standard inspect section

* swarm inspect tab

* server and swarm resources tab

* add disable confirm dialog (modal) option for executions

* stack update available indicator

* deployment update available

* add template switch to resource headers

* ResourceHeader + rename

* set editing name onclick

* repo tabs

* server stats table

* refine a bit

* refine deployment / stack header info

* show server stats dashboard. dashboard tables

* action last run in config

* SettingsUsers page

* user page etc

* manage api key

* user base permissions

* color the table multi select

* user group page

* UserAddUserGroup

* active includes deployments / stacks

* improve small screen view

* fix docker pages execution showing

* clean up

* rename frontend to UI

* align profile page styling

* config maintenance windows

* finish maintenance windows

* builder config

* add batch execute dropdown / confirm menu

* batch execute styling

* deploy 2.0.0-dev-117

* improve stats card light theme

* add update page

* improve mobile

* terminal group nowrap

* mobile improvements

* allow unused again

* improve mobile font sizing

* improve mobile updates / alerts

* mobile tabs

* alert page

* add server version mismatch color

* new resource, clearable selector

* Fix build show info tab

* copy resources

* keyboard shortcuts

* server resource header version mismatch

* fix type errors

* container page server multi select

* confirm button clear timeout

* hash compare force uses first 8 for short hash

* fix log height

* copy webhooks

* responsive tweaks

* add icons to server stat sections

* add historical server stats charts

* server stat current card shows usage numbers

* refine current stats more

* fix shortcuts interfering with monaco brave

* clean up unused

* remove v1 frontend
2026-02-25 15:28:23 -08:00

506 lines
17 KiB
JavaScript

import { MoghAuthClient } from "npm:mogh_auth_client";
import { terminal_methods } from "./terminal.js";
import { UpdateStatus, } from "./types.js";
export * as MoghAuth from "npm:mogh_auth_client";
export * as Types from "./types.js";
export class CancelToken {
cancelled;
constructor() {
this.cancelled = false;
}
cancel() {
this.cancelled = true;
}
}
/** Initialize a new client for Komodo */
export function KomodoClient(url, options) {
const state = {
jwt: options.type === "jwt" ? options.params.jwt : undefined,
key: options.type === "api-key" ? options.params.key : undefined,
secret: options.type === "api-key" ? options.params.secret : undefined,
};
const auth = MoghAuthClient(url + "/auth", state.jwt);
const request = (path, type, params, method = "POST") => new Promise(async (res, rej) => {
try {
let response = await fetch(`${url}${path}${type ? "/" + type : ""}`, {
method,
body: JSON.stringify(params),
headers: {
...(state.jwt
? {
authorization: state.jwt,
}
: state.key && state.secret
? {
"x-api-key": state.key,
"x-api-secret": state.secret,
}
: {}),
"content-type": "application/json",
},
credentials: "include",
});
if (response.status === 200) {
const body = await response.json();
res(body);
}
else {
try {
const result = await response.json();
rej({ status: response.status, result });
}
catch (error) {
rej({
status: response.status,
result: {
error: "Failed to get response body",
trace: [JSON.stringify(error)],
},
error,
});
}
}
}
catch (error) {
rej({
status: 1,
result: {
error: "Request failed with error",
trace: [JSON.stringify(error)],
},
error,
});
}
});
const getUser = async () => await request("/user", "", undefined, "GET");
const read = async (type, params) => await request("/read", type, params);
const write = async (type, params) => await request("/write", type, params);
const execute = async (type, params) => await request("/execute", type, params);
const execute_and_poll = async (type, params) => {
const res = await execute(type, params);
// Check if its a batch of updates or a single update;
if (Array.isArray(res)) {
const batch = res;
return await Promise.all(batch.map(async (item) => {
if (item.status === "Err") {
return item;
}
return await poll_update_until_complete(item.data._id?.$oid);
}));
}
else {
// it is a single update
const update = res;
if (update.status === UpdateStatus.Complete || !update._id?.$oid) {
return update;
}
return await poll_update_until_complete(update._id?.$oid);
}
};
const poll_update_until_complete = async (update_id) => {
while (true) {
await new Promise((resolve) => setTimeout(resolve, 1000));
const update = await read("GetUpdate", { id: update_id });
if (update.status === UpdateStatus.Complete) {
return update;
}
}
};
const core_version = () => read("GetVersion", {}).then((res) => res.version);
const get_update_websocket = ({ on_update, on_login, on_open, on_close, }) => {
const ws = new WebSocket(url.replace("http", "ws") + "/ws/update");
// Handle login on websocket open
ws.addEventListener("open", () => {
on_open?.();
const login_msg = options.type === "jwt"
? {
type: "Jwt",
params: {
jwt: options.params.jwt,
},
}
: {
type: "ApiKeys",
params: {
key: options.params.key,
secret: options.params.secret,
},
};
ws.send(JSON.stringify(login_msg));
});
ws.addEventListener("message", ({ data }) => {
if (data == "LOGGED_IN")
return on_login?.();
on_update(JSON.parse(data));
});
if (on_close) {
ws.addEventListener("close", on_close);
}
return ws;
};
const subscribe_to_update_websocket = async ({ on_update, on_open, on_login, on_close, retry = true, retry_timeout_ms = 5_000, cancel = new CancelToken(), on_cancel, }) => {
while (true) {
if (cancel.cancelled) {
on_cancel?.();
return;
}
try {
const ws = get_update_websocket({
on_open,
on_login,
on_update,
on_close,
});
// This while loop will end when the socket is closed
while (ws.readyState !== WebSocket.CLOSING &&
ws.readyState !== WebSocket.CLOSED) {
if (cancel.cancelled)
ws.close();
// Sleep for a bit before checking for websocket closed
await new Promise((resolve) => setTimeout(resolve, 500));
}
if (retry) {
// Sleep for a bit before retrying connection to avoid spam.
await new Promise((resolve) => setTimeout(resolve, retry_timeout_ms));
}
else {
return;
}
}
catch (error) {
console.error(error);
if (retry) {
// Sleep for a bit before retrying, maybe Komodo Core is down temporarily.
await new Promise((resolve) => setTimeout(resolve, retry_timeout_ms));
}
else {
return;
}
}
}
};
const { connect_terminal, execute_terminal, execute_terminal_stream, execute_server_terminal, execute_container_terminal, execute_stack_service_terminal, execute_deployment_terminal, execute_container_exec, execute_deployment_exec, execute_stack_exec, } = terminal_methods(url, state);
return {
/**
* Call the `/auth` api.
*
* ```
* const { jwt } = await komodo.auth.login("LoginLocalUser", {
* username: "test-user",
* password: "test-pass"
* });
* ```
*
* https://docs.rs/mogh_auth_client/latest/mogh_auth_client/api/index.html
*/
auth,
/**
* Get the current (calling) user.
*
* ```
* const user = await komodo.getUser();
* ```
*
* https://docs.rs/komodo_client/latest/komodo_client/api/user/index.html
*/
getUser,
/**
* Call the `/read` api.
*
* ```
* const stack = await komodo.read("GetStack", {
* stack: "my-stack"
* });
* ```
*
* https://docs.rs/komodo_client/latest/komodo_client/api/read/index.html
*/
read,
/**
* Call the `/write` api.
*
* ```
* const build = await komodo.write("UpdateBuild", {
* id: "my-build",
* config: {
* version: "1.0.4"
* }
* });
* ```
*
* https://docs.rs/komodo_client/latest/komodo_client/api/write/index.html
*/
write,
/**
* Call the `/execute` api.
*
* ```
* const update = await komodo.execute("DeployStack", {
* stack: "my-stack"
* });
* ```
*
* NOTE. These calls return immediately when the update is created, NOT when the execution task finishes.
* To have the call only return when the task finishes, use [execute_and_poll_until_complete].
*
* https://docs.rs/komodo_client/latest/komodo_client/api/execute/index.html
*/
execute,
/**
* Call the `/execute` api, and poll the update until the task has completed.
*
* ```
* const update = await komodo.execute_and_poll("DeployStack", {
* stack: "my-stack"
* });
* ```
*
* https://docs.rs/komodo_client/latest/komodo_client/api/execute/index.html
*/
execute_and_poll,
/**
* Poll an Update (returned by the `execute` calls) until the `status` is `Complete`.
* https://docs.rs/komodo_client/latest/komodo_client/entities/update/struct.Update.html#structfield.status.
*/
poll_update_until_complete,
/** Returns the version of Komodo Core the client is calling to. */
core_version,
/**
* Connects to update websocket, performs login and attaches handlers,
* and returns the WebSocket handle.
*/
get_update_websocket,
/**
* Subscribes to the update websocket with automatic reconnect loop.
*
* Note. Awaiting this method will never finish.
*/
subscribe_to_update_websocket,
/**
* Subscribes to terminal io over websocket message,
* for use with xtermjs.
*/
connect_terminal,
/**
* Executes a command on a given target / terminal,
* and gives callbacks to handle the output as it comes in.
*
* ```ts
* await komodo.execute_terminal(
* {
* target: {
* type: "Server",
* params: {
* server: "my-server"
* }
* },
* terminal: "name",
* command: 'for i in {1..3}; do echo "$i"; sleep 1; done',
* init: {
* command: "bash",
* recreate: Types.TerminalRecreateMode.Always
* }
* },
* {
* onLine: (line) => console.log(line),
* onFinish: (code) => console.log("Finished:", code),
* }
* );
* ```
*/
execute_terminal,
/**
* Executes a command on a given target / terminal,
* and returns a stream to process the output as it comes in.
*
* Note. The final line of the stream will usually be
* `__KOMODO_EXIT_CODE__:0`. The number
* is the exit code of the command.
*
* If this line is NOT present, it means the stream
* was terminated early, ie like running `exit`.
*
* ```ts
* const stream = await komodo.execute_terminal_stream({
* target: {
* type: "Server",
* params: {
* server: "my-server"
* }
* },
* terminal: "name",
* command: 'for i in {1..3}; do echo "$i"; sleep 1; done',
* init: {
* command: "bash",
* recreate: Types.TerminalRecreateMode.Always
* }
* });
*
* for await (const line of stream) {
* console.log(line);
* }
* ```
*/
execute_terminal_stream,
/**
* Executes a command on a given Server / terminal,
* and gives callbacks to handle the output as it comes in.
*
* ```ts
* await komodo.execute_server_terminal(
* {
* server: "my-server",
* terminal: "name",
* command: 'for i in {1..3}; do echo "$i"; sleep 1; done',
* init: {
* command: "bash",
* recreate: Types.TerminalRecreateMode.Always
* }
* },
* {
* onLine: (line) => console.log(line),
* onFinish: (code) => console.log("Finished:", code),
* }
* );
* ```
*/
execute_server_terminal,
/**
* Executes a command on a given Server / Container / terminal,
* and gives callbacks to handle the output as it comes in.
*
* ```ts
* await komodo.execute_container_terminal(
* {
* server: "my-server",
* container: "my-container",
* terminal: "name",
* command: 'for i in {1..3}; do echo "$i"; sleep 1; done',
* init: {
* command: "bash",
* recreate: Types.TerminalRecreateMode.Always
* }
* },
* {
* onLine: (line) => console.log(line),
* onFinish: (code) => console.log("Finished:", code),
* }
* );
* ```
*/
execute_container_terminal,
/**
* Executes a command on a given Stack / service / terminal,
* and gives callbacks to handle the output as it comes in.
*
* ```ts
* await komodo.execute_stack_service_terminal(
* {
* stack: "my-stack",
* service: "my-service",
* terminal: "name",
* command: 'for i in {1..3}; do echo "$i"; sleep 1; done',
* init: {
* command: "bash",
* recreate: Types.TerminalRecreateMode.Always
* }
* },
* {
* onLine: (line) => console.log(line),
* onFinish: (code) => console.log("Finished:", code),
* }
* );
* ```
*/
execute_stack_service_terminal,
/**
* Executes a command on a given Deployment / terminal,
* and gives callbacks to handle the output as it comes in.
*
* ```ts
* await komodo.execute_deployment_terminal(
* {
* deployment: "my-deployemnt",
* terminal: "name",
* command: 'for i in {1..3}; do echo "$i"; sleep 1; done',
* init: {
* command: "bash",
* recreate: Types.TerminalRecreateMode.Always
* }
* },
* {
* onLine: (line) => console.log(line),
* onFinish: (code) => console.log("Finished:", code),
* }
* );
* ```
*/
execute_deployment_terminal,
/**
* Executes a command on a given Server / Container / terminal,
* and gives callbacks to handle the output as it comes in.
*
* ```ts
* await komodo.execute_container_exec(
* {
* server: "my-server",
* container: "my-container",
* command: 'for i in {1..3}; do echo "$i"; sleep 1; done',
* shell: "bash",
* terminal: "name",
* recreate: Types.TerminalRecreateMode.Always,
* },
* {
* onLine: (line) => console.log(line),
* onFinish: (code) => console.log("Finished:", code),
* }
* );
* ```
*/
execute_container_exec,
/**
* Executes a command on a given Deployment / terminal,
* and gives callbacks to handle the output as it comes in.
*
* ```ts
* await komodo.execute_deployment_exec(
* {
* deployment: "my-deployemnt",
* command: 'for i in {1..3}; do echo "$i"; sleep 1; done',
* shell: "bash",
* terminal: "name",
* recreate: Types.TerminalRecreateMode.Always,
* },
* {
* onLine: (line) => console.log(line),
* onFinish: (code) => console.log("Finished:", code),
* }
* );
* ```
*/
execute_deployment_exec,
/**
* Executes a command on a given Stack / service / terminal,
* and gives callbacks to handle the output as it comes in.
*
* ```ts
* await komodo.execute_stack_exec(
* {
* stack: "my-stack",
* service: "my-service",
* command: 'for i in {1..3}; do echo "$i"; sleep 1; done',
* shell: "bash",
* terminal: "name",
* recreate: Types.TerminalRecreateMode.Always
* },
* {
* onLine: (line) => console.log(line),
* onFinish: (code) => console.log("Finished:", code),
* }
* );
* ```
*/
execute_stack_exec,
};
}