Files
cloudly/ts_web/elements/views/clusters/index.ts
T

164 lines
6.7 KiB
TypeScript
Raw Normal View History

import * as plugins from '../../../plugins.js';
import * as shared from '../../shared/index.js';
import {
DeesElement,
customElement,
html,
state,
css,
cssManager,
} from '@design.estate/dees-element';
import * as appstate from '../../../appstate.js';
@customElement('cloudly-view-clusters')
export class CloudlyViewClusters extends DeesElement {
@state()
2026-05-08 13:56:20 +00:00
private accessor data: appstate.IDataState = {} as any;
constructor() {
super();
const subecription = appstate.dataState
.select((stateArg) => stateArg)
.subscribe((dataArg) => {
this.data = dataArg;
});
this.rxSubscriptions.push(subecription);
}
private async createJumpCommand(clusterArg: plugins.interfaces.data.ICluster) {
const identity = appstate.loginStatePart.getState()?.identity;
if (!identity) {
plugins.deesCatalog.DeesToast.createAndShow({ message: 'Login required to create a jump code', type: 'error' });
return;
}
try {
appstate.apiClient.identity = identity;
const apiClient = appstate.apiClient as any;
const response = apiClient.node?.createNodeJumpCommand
? await apiClient.node.createNodeJumpCommand({ clusterId: clusterArg.id })
: await apiClient.typedsocketClient
.createTypedRequest('createNodeJumpCommand')
.fire({ identity, clusterId: clusterArg.id });
await plugins.deesCatalog.DeesModal.createAndShow({
heading: 'Connect System',
content: html`
<div style="display: grid; gap: 16px; min-width: min(680px, 80vw);">
<div>
Connect a Linux system to <strong>${clusterArg.data.name}</strong> by running this command as an administrator.
</div>
<pre style="white-space: pre-wrap; word-break: break-all; background: #0d1117; border: 1px solid #30363d; border-radius: 12px; padding: 16px; color: #7ee787;">${response.command}</pre>
<div style="color: #9aa4b2; font-size: 13px;">
Jump URL: ${response.jumpUrl}<br>
Expires: ${new Date(response.expiresAt).toLocaleString()}
</div>
</div>
`,
menuOptions: [
{
name: 'copy command',
action: async () => {
await navigator.clipboard.writeText(response.command);
plugins.deesCatalog.DeesToast.createAndShow({ message: 'Jump command copied', type: 'success' });
},
},
{ name: 'close', action: async (modalArg: any) => modalArg.destroy() },
],
});
} catch (error: any) {
plugins.deesCatalog.DeesToast.createAndShow({ message: `Failed to create jump code: ${error.message}`, type: 'error' });
}
}
public static styles = [
cssManager.defaultStyles,
shared.viewHostCss,
css``,
];
public render() {
return html`
<cloudly-sectionheading>Clusters</cloudly-sectionheading>
<dees-table
.heading1=${'Clusters'}
.heading2=${'decoded in client'}
.data=${this.data.clusters}
.displayFunction=${(itemArg: plugins.interfaces.data.ICluster) => {
return {
id: itemArg.id,
serverAmount: itemArg.data.nodes.length,
};
}}
.dataActions=${[
{
name: 'add cluster',
iconName: 'plus',
type: ['header', 'footer'],
actionFunc: async () => {
await plugins.deesCatalog.DeesModal.createAndShow({
heading: 'Add Cluster',
content: html`
<dees-form>
<dees-input-text .key=${'clusterName'} .label=${'cluster name'} .description=${'a descriptive name for the cluster'} .value=${''}></dees-input-text>
<dees-input-dropdown .key=${'setupMode'} .label=${'Setup Mode'} .description=${'How the cluster infrastructure should be managed'}
.options=${[
{option: 'manual', key: 'manual', description: 'Manual Setup - Add your own servers manually'},
{option: 'hetzner', key: 'hetzner', description: 'Hetzner Cloud - Auto-provision servers on Hetzner'},
{option: 'aws', key: 'aws', description: 'AWS - Auto-provision on Amazon Web Services (coming soon)', disabled: true},
{option: 'digitalocean', key: 'digitalocean', description: 'DigitalOcean - Auto-provision on DigitalOcean (coming soon)', disabled: true}
]}
.selectedOption=${'manual'}>
</dees-input-dropdown>
</dees-form>
`,
menuOptions: [
{ name: 'create', action: async (modalArg: any) => {
const data = (await modalArg.shadowRoot.querySelector('dees-form').collectFormData()) as any;
await appstate.dataState.dispatchAction(appstate.addClusterAction, data);
await modalArg.destroy();
}},
{ name: 'cancel', action: async (modalArg: any) => modalArg.destroy() },
],
});
},
},
{
name: 'delete',
iconName: 'trash',
type: ['contextmenu', 'inRow'],
actionFunc: async (actionDataArg: any) => {
plugins.deesCatalog.DeesModal.createAndShow({
heading: `Delete ConfigBundle ${actionDataArg.item.id}`,
content: html`
<div style="text-align:center">Do you really want to delete the ConfigBundle?</div>
<div style="font-size: 0.8em; color: red; text-align:center; padding: 16px; margin-top: 24px; border: 1px solid #444; font-family: Intel One Mono; font-size: 16px;">${actionDataArg.item.id}</div>
`,
menuOptions: [
{ name: 'cancel', action: async (modalArg: any) => { await modalArg.destroy(); } },
{ name: 'delete', action: async (modalArg: any) => { appstate.dataState.dispatchAction(appstate.deleteSecretBundleAction, { configBundleId: actionDataArg.item.id, }); await modalArg.destroy(); } },
],
});
},
},
{
name: 'connect system',
iconName: 'terminal',
type: ['contextmenu', 'inRow'],
actionFunc: async (actionDataArg: any) => {
await this.createJumpCommand(actionDataArg.item as plugins.interfaces.data.ICluster);
},
},
] as plugins.deesCatalog.ITableAction[]}
></dees-table>
`;
}
}
declare global {
interface HTMLElementTagNameMap {
'cloudly-view-clusters': CloudlyViewClusters;
}
}