207 lines
7.0 KiB
TypeScript
207 lines
7.0 KiB
TypeScript
import * as plugins from '../../../plugins.js';
|
|
import * as appstate from '../../../appstate.js';
|
|
import * as shared from '../../shared/index.js';
|
|
|
|
import {
|
|
DeesElement,
|
|
css,
|
|
cssManager,
|
|
customElement,
|
|
html,
|
|
state,
|
|
} from '@design.estate/dees-element';
|
|
|
|
type TBaseOsImageBuild = any;
|
|
|
|
@customElement('cloudly-view-baseos')
|
|
export class CloudlyViewBaseOs extends DeesElement {
|
|
@state() private builds: TBaseOsImageBuild[] = [];
|
|
@state() private isLoading = false;
|
|
|
|
private refreshTimer?: number;
|
|
|
|
public static styles = [
|
|
cssManager.defaultStyles,
|
|
shared.viewHostCss,
|
|
css`
|
|
.layout {
|
|
display: grid;
|
|
grid-template-columns: 420px 1fr;
|
|
gap: 16px;
|
|
padding: 24px 0;
|
|
}
|
|
|
|
.builds {
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 12px;
|
|
}
|
|
|
|
.build {
|
|
border: 1px solid #2a2f3a;
|
|
border-radius: 12px;
|
|
padding: 16px;
|
|
background: #10151f;
|
|
}
|
|
|
|
.build-head {
|
|
display: flex;
|
|
justify-content: space-between;
|
|
align-items: center;
|
|
gap: 12px;
|
|
}
|
|
|
|
.meta {
|
|
color: #9aa4b2;
|
|
font-size: 13px;
|
|
margin-top: 8px;
|
|
}
|
|
|
|
.logs {
|
|
margin-top: 12px;
|
|
max-height: 120px;
|
|
overflow: auto;
|
|
font-family: monospace;
|
|
font-size: 12px;
|
|
color: #bac4d1;
|
|
white-space: pre-wrap;
|
|
}
|
|
|
|
@media (max-width: 900px) {
|
|
.layout {
|
|
grid-template-columns: 1fr;
|
|
}
|
|
}
|
|
`,
|
|
];
|
|
|
|
public async connectedCallback() {
|
|
await super.connectedCallback();
|
|
await this.loadBuilds();
|
|
this.refreshTimer = window.setInterval(() => this.loadBuilds(), 5000);
|
|
}
|
|
|
|
public async disconnectedCallback() {
|
|
await super.disconnectedCallback();
|
|
if (this.refreshTimer) {
|
|
window.clearInterval(this.refreshTimer);
|
|
}
|
|
}
|
|
|
|
public render() {
|
|
return html`
|
|
<cloudly-sectionheading>BaseOS Images</cloudly-sectionheading>
|
|
<div class="layout">
|
|
<dees-panel .title=${'Create Image'} .subtitle=${'Build a Cloudly-bound BaseOS ISO'} .variant=${'outline'}>
|
|
<dees-form @formData=${(eventArg: CustomEvent) => this.createBuild((eventArg.detail as any).data)}>
|
|
<dees-input-dropdown
|
|
.key=${'architecture'}
|
|
.label=${'Architecture'}
|
|
.selectedOption=${'amd64'}
|
|
.options=${[
|
|
{ key: 'amd64', option: 'amd64 ISO', payload: null },
|
|
{ key: 'arm64', option: 'arm64 ISO', payload: null },
|
|
]}
|
|
></dees-input-dropdown>
|
|
<dees-input-text .key=${'cloudlyUrl'} .label=${'Cloudly URL'} .value=${window.location.origin} .required=${true}></dees-input-text>
|
|
<dees-input-text .key=${'hostname'} .label=${'Hostname'} .value=${'baseos-node'} .required=${false}></dees-input-text>
|
|
<dees-input-text .key=${'wifiSsid'} .label=${'WiFi SSID'} .required=${false}></dees-input-text>
|
|
<dees-input-text .key=${'wifiPassword'} .label=${'WiFi Password'} .isPasswordBool=${true} .required=${false}></dees-input-text>
|
|
<dees-input-textarea .key=${'sshPublicKey'} .label=${'SSH Public Key'} .required=${false}></dees-input-textarea>
|
|
<dees-input-text .key=${'sourceImageUrl'} .label=${'Source ISO URL'} .description=${'Optional. Defaults to Ubuntu 24.04 through isocreator.'} .required=${false}></dees-input-text>
|
|
<dees-form-submit .text=${this.isLoading ? 'Creating...' : 'Create BaseOS Image'} .disabled=${this.isLoading}></dees-form-submit>
|
|
</dees-form>
|
|
</dees-panel>
|
|
<div class="builds">
|
|
${this.builds.length === 0
|
|
? html`<dees-panel .title=${'No image builds yet'} .subtitle=${'Create an image to start a corebuild job.'}></dees-panel>`
|
|
: this.builds.map((buildArg) => this.renderBuild(buildArg))}
|
|
</div>
|
|
</div>
|
|
`;
|
|
}
|
|
|
|
private renderBuild(buildArg: TBaseOsImageBuild) {
|
|
const data = buildArg.data;
|
|
return html`
|
|
<div class="build">
|
|
<div class="build-head">
|
|
<div>
|
|
<strong>${data.hostname || buildArg.id}</strong>
|
|
<div class="meta">${data.architecture} · ${data.cloudlyUrl}</div>
|
|
</div>
|
|
<dees-badge .text=${data.status} .type=${data.status === 'ready' ? 'success' : data.status === 'failed' ? 'error' : 'info'}></dees-badge>
|
|
</div>
|
|
<div class="meta">
|
|
${data.artifact ? `${data.artifact.filename} · ${Math.round(data.artifact.size / 1024 / 1024)} MB · ${data.artifact.sha256.slice(0, 12)}...` : data.errorText || 'Waiting for artifact'}
|
|
</div>
|
|
${data.status === 'ready'
|
|
? html`<dees-button .text=${'Download'} .type=${'primary'} @click=${() => this.downloadBuild(buildArg.id)}></dees-button>`
|
|
: ''}
|
|
${data.logs?.length ? html`<div class="logs">${data.logs.slice(-8).join('\n')}</div>` : ''}
|
|
</div>
|
|
`;
|
|
}
|
|
|
|
private async loadBuilds() {
|
|
try {
|
|
const response = await this.fireBaseOsRequest('getBaseOsImageBuilds', {});
|
|
this.builds = response.builds || [];
|
|
} catch (error) {
|
|
console.error('Failed to load BaseOS image builds:', error);
|
|
}
|
|
}
|
|
|
|
private async createBuild(formDataArg: any) {
|
|
this.isLoading = true;
|
|
try {
|
|
const response = await this.fireBaseOsRequest('createBaseOsImageBuild', {
|
|
build: {
|
|
architecture: formDataArg.architecture || 'amd64',
|
|
cloudlyUrl: formDataArg.cloudlyUrl || window.location.origin,
|
|
hostname: formDataArg.hostname || undefined,
|
|
sourceImageUrl: formDataArg.sourceImageUrl || undefined,
|
|
wifi: formDataArg.wifiSsid
|
|
? {
|
|
ssid: formDataArg.wifiSsid,
|
|
password: formDataArg.wifiPassword || undefined,
|
|
}
|
|
: undefined,
|
|
sshPublicKey: formDataArg.sshPublicKey || undefined,
|
|
},
|
|
});
|
|
this.builds = [response.build, ...this.builds];
|
|
plugins.deesCatalog.DeesToast.createAndShow({ message: 'BaseOS image build queued', type: 'success' });
|
|
} catch (error: any) {
|
|
plugins.deesCatalog.DeesToast.createAndShow({ message: `Failed to create image build: ${error.message}`, type: 'error' });
|
|
} finally {
|
|
this.isLoading = false;
|
|
}
|
|
}
|
|
|
|
private async downloadBuild(buildIdArg: string) {
|
|
const response = await this.fireBaseOsRequest('createBaseOsImageDownloadUrl', {
|
|
buildId: buildIdArg,
|
|
});
|
|
window.location.href = response.url;
|
|
}
|
|
|
|
private async fireBaseOsRequest(methodArg: string, payloadArg: Record<string, unknown>) {
|
|
appstate.apiClient.identity = appstate.loginStatePart.getState()?.identity || null as any;
|
|
if (!appstate.apiClient.typedsocketClient) {
|
|
await appstate.apiClient.start();
|
|
}
|
|
const request = appstate.apiClient.typedsocketClient.createTypedRequest<any>(methodArg);
|
|
return await request.fire({
|
|
identity: appstate.apiClient.identity,
|
|
...payloadArg,
|
|
});
|
|
}
|
|
}
|
|
|
|
declare global {
|
|
interface HTMLElementTagNameMap {
|
|
'cloudly-view-baseos': CloudlyViewBaseOs;
|
|
}
|
|
}
|