f40ef6b7c0
Align Cloudly with the current typedserver, smartconfig, smartstate, and Docker tooling releases so builds and Docker output stay compatible with the upgraded stack.
118 lines
12 KiB
TypeScript
118 lines
12 KiB
TypeScript
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-externalregistries')
|
|
export class CloudlyViewExternalRegistries extends DeesElement {
|
|
@state()
|
|
private accessor data: appstate.IDataState = { secretGroups: [], secretBundles: [], externalRegistries: [] } as any;
|
|
|
|
constructor() {
|
|
super();
|
|
const subscription = appstate.dataState.select((stateArg) => stateArg).subscribe((dataArg) => { this.data = dataArg; });
|
|
this.rxSubscriptions.push(subscription);
|
|
}
|
|
|
|
async connectedCallback() {
|
|
super.connectedCallback();
|
|
await appstate.dataState.dispatchAction(appstate.getAllDataAction, {});
|
|
}
|
|
|
|
public static styles = [
|
|
cssManager.defaultStyles,
|
|
shared.viewHostCss,
|
|
css`
|
|
.status-badge { display: inline-block; padding: 2px 8px; border-radius: 4px; font-size: 0.85em; font-weight: 500; color: white; }
|
|
.status-active { background: #4CAF50; }
|
|
.status-inactive { background: #9E9E9E; }
|
|
.status-error { background: #f44336; }
|
|
.status-unverified { background: #FF9800; }
|
|
.type-badge { display: inline-block; padding: 2px 8px; border-radius: 4px; font-size: 0.85em; font-weight: 500; color: white; }
|
|
.type-docker { background: #2196F3; }
|
|
.type-npm { background: #CB3837; }
|
|
.default-badge { display: inline-block; padding: 2px 8px; border-radius: 4px; font-size: 0.85em; font-weight: 500; background: #673AB7; color: white; margin-left: 8px; }
|
|
`,
|
|
];
|
|
|
|
public render() {
|
|
return html`
|
|
<cloudly-sectionheading>External Registries</cloudly-sectionheading>
|
|
<dees-table
|
|
.heading1=${'External Registries'}
|
|
.heading2=${'Configure external Docker and NPM registries'}
|
|
.data=${this.data.externalRegistries || []}
|
|
.displayFunction=${(registry: plugins.interfaces.data.IExternalRegistry) => {
|
|
return {
|
|
Name: html`${registry.data.name}${registry.data.isDefault ? html`<span class="default-badge">DEFAULT</span>` : ''}`,
|
|
Type: html`<span class="type-badge type-${registry.data.type}">${registry.data.type.toUpperCase()}</span>`,
|
|
URL: registry.data.url,
|
|
Auth: registry.data.authType === 'none' ? 'Public' : (registry.data.username || 'Token Auth'),
|
|
Namespace: registry.data.namespace || '-',
|
|
Status: html`<span class="status-badge status-${registry.data.status || 'unverified'}">${(registry.data.status || 'unverified').toUpperCase()}</span>`,
|
|
'Last Verified': registry.data.lastVerified ? new Date(registry.data.lastVerified).toLocaleString() : 'Never',
|
|
};
|
|
}}
|
|
.dataActions=${[
|
|
{ name: 'Add Registry', iconName: 'plus', type: ['header', 'footer'], actionFunc: async () => {
|
|
await plugins.deesCatalog.DeesModal.createAndShow({ heading: 'Add External Registry', content: html`
|
|
<dees-form>
|
|
<dees-input-dropdown .key=${'type'} .label=${'Registry Type'} .options=${[{key: 'docker', option: 'Docker'}, {key: 'npm', option: 'NPM'}]} .value=${'docker'} .required=${true}></dees-input-dropdown>
|
|
<dees-input-text .key=${'name'} .label=${'Registry Name'} .placeholder=${'My Docker Hub'} .required=${true}></dees-input-text>
|
|
<dees-input-text .key=${'url'} .label=${'Registry URL'} .placeholder=${'https://index.docker.io/v2/ or registry.gitlab.com'} .required=${true}></dees-input-text>
|
|
<dees-input-text .key=${'username'} .label=${'Username (only needed for basic auth)'} .placeholder=${'username or leave empty for token auth'}></dees-input-text>
|
|
<dees-input-text .key=${'password'} .label=${'Password / Token (NPM _authToken, Docker access token, etc.)'} .placeholder=${'Token or password'} .isPasswordBool=${true}></dees-input-text>
|
|
<dees-input-text .key=${'namespace'} .label=${'Namespace/Organization (optional)'} .placeholder=${'myorg'}></dees-input-text>
|
|
<dees-input-text .key=${'description'} .label=${'Description (optional)'} .placeholder=${'Production Docker registry'}></dees-input-text>
|
|
<dees-input-dropdown .key=${'authType'} .label=${'Authentication Type'} .options=${[{key: 'none', option: 'No Authentication (Public Registry)'}, {key: 'basic', option: 'Basic Auth (Username + Password)'}, {key: 'token', option: 'Token Only (NPM, GitHub, GitLab tokens)'}, {key: 'oauth2', option: 'OAuth2 (Advanced)'}]} .value=${'none'}></dees-input-dropdown>
|
|
<dees-input-checkbox .key=${'isDefault'} .label=${'Set as default registry for this type'} .value=${false}></dees-input-checkbox>
|
|
<dees-input-checkbox .key=${'insecure'} .label=${'Allow insecure connections (HTTP/self-signed certs)'} .value=${false}></dees-input-checkbox>
|
|
</dees-form>
|
|
`, menuOptions: [ { name: 'Create Registry', action: async (modalArg: any) => { const form = modalArg.shadowRoot.querySelector('dees-form') as any; const formData = await form.gatherData(); await appstate.dataState.dispatchAction(appstate.createExternalRegistryAction, { registryData: { type: formData.type, name: formData.name, url: formData.url, username: formData.username, password: formData.password, namespace: formData.namespace || undefined, description: formData.description || undefined, authType: formData.authType, isDefault: formData.isDefault, insecure: formData.insecure, }, }); await modalArg.destroy(); } }, { name: 'Cancel', action: async (modalArg: any) => { await modalArg.destroy(); } } ] });
|
|
} },
|
|
{ name: 'Edit', iconName: 'edit', type: ['contextmenu', 'inRow'], actionFunc: async (actionDataArg: any) => {
|
|
const registry = actionDataArg.item as plugins.interfaces.data.IExternalRegistry;
|
|
await plugins.deesCatalog.DeesModal.createAndShow({ heading: `Edit Registry: ${registry.data.name}`, content: html`
|
|
<dees-form>
|
|
<dees-input-dropdown .key=${'type'} .label=${'Registry Type'} .options=${[{key: 'docker', option: 'Docker'}, {key: 'npm', option: 'NPM'}]} .value=${registry.data.type} .required=${true}></dees-input-dropdown>
|
|
<dees-input-text .key=${'name'} .label=${'Registry Name'} .value=${registry.data.name} .required=${true}></dees-input-text>
|
|
<dees-input-text .key=${'url'} .label=${'Registry URL'} .value=${registry.data.url} .required=${true}></dees-input-text>
|
|
<dees-input-text .key=${'username'} .label=${'Username (only needed for basic auth)'} .value=${registry.data.username || ''} .placeholder=${'Leave empty for token auth'}></dees-input-text>
|
|
<dees-input-text .key=${'password'} .label=${'Password / Token (leave empty to keep current)'} .placeholder=${'New token or password'} .isPasswordBool=${true}></dees-input-text>
|
|
<dees-input-text .key=${'namespace'} .label=${'Namespace/Organization (optional)'} .value=${registry.data.namespace || ''}></dees-input-text>
|
|
<dees-input-text .key=${'description'} .label=${'Description (optional)'} .value=${registry.data.description || ''}></dees-input-text>
|
|
<dees-input-dropdown .key=${'authType'} .label=${'Authentication Type'} .options=${[{key: 'none', option: 'No Authentication (Public Registry)'}, {key: 'basic', option: 'Basic Auth (Username + Password)'}, {key: 'token', option: 'Token Only (NPM, GitHub, GitLab tokens)'}, {key: 'oauth2', option: 'OAuth2 (Advanced)'}]} .value=${registry.data.authType || 'none'}></dees-input-dropdown>
|
|
<dees-input-checkbox .key=${'isDefault'} .label=${'Set as default registry for this type'} .value=${registry.data.isDefault || false}></dees-input-checkbox>
|
|
<dees-input-checkbox .key=${'insecure'} .label=${'Allow insecure connections (HTTP/self-signed certs)'} .value=${registry.data.insecure || false}></dees-input-checkbox>
|
|
</dees-form>
|
|
`, menuOptions: [ { name: 'Update Registry', action: async (modalArg: any) => { const form = modalArg.shadowRoot.querySelector('dees-form') as any; const formData = await form.gatherData(); const updateData: any = { type: formData.type, name: formData.name, url: formData.url, username: formData.username, namespace: formData.namespace || undefined, description: formData.description || undefined, authType: formData.authType, isDefault: formData.isDefault, insecure: formData.insecure, }; if (formData.password) { updateData.password = formData.password; } await appstate.dataState.dispatchAction(appstate.updateExternalRegistryAction, { registryId: registry.id, registryData: updateData, }); await modalArg.destroy(); } }, { name: 'Cancel', action: async (modalArg: any) => { await modalArg.destroy(); } } ] });
|
|
} },
|
|
{ name: 'Test Connection', iconName: 'check-circle', type: ['contextmenu'], actionFunc: async (actionDataArg: any) => {
|
|
const registry = actionDataArg.item as plugins.interfaces.data.IExternalRegistry;
|
|
const loadingModal = await plugins.deesCatalog.DeesModal.createAndShow({ heading: 'Testing Registry Connection', content: html`<div style="text-align: center; padding: 20px;"><dees-spinner></dees-spinner><p style="margin-top: 20px;">Testing connection to ${registry.data.name}...</p></div>`, menuOptions: [] });
|
|
await appstate.dataState.dispatchAction(appstate.verifyExternalRegistryAction, { registryId: registry.id, });
|
|
await loadingModal.destroy();
|
|
const updatedRegistry = this.data.externalRegistries?.find(r => r.id === registry.id);
|
|
await plugins.deesCatalog.DeesModal.createAndShow({ heading: 'Connection Test Result', content: html`<div style="text-align: center; padding: 20px;">${updatedRegistry?.data.status === 'active' ? html`<div style="color: #4CAF50; font-size: 48px;">✓</div><p style="margin-top: 20px; color: #4CAF50;">Connection successful!</p>` : html`<div style="color: #f44336; font-size: 48px;">✗</div><p style="margin-top: 20px; color: #f44336;">Connection failed!</p>${updatedRegistry?.data.lastError ? html`<p style="margin-top: 10px; font-size: 0.9em; color: #999;">Error: ${updatedRegistry.data.lastError}</p>` : ''}`}</div>`, menuOptions: [ { name: 'OK', action: async (modalArg: any) => { await modalArg.destroy(); } } ] });
|
|
} },
|
|
{ name: 'Delete', iconName: 'trash', type: ['contextmenu'], actionFunc: async (actionDataArg: any) => {
|
|
const registry = actionDataArg.item as plugins.interfaces.data.IExternalRegistry;
|
|
plugins.deesCatalog.DeesModal.createAndShow({ heading: `Delete Registry: ${registry.data.name}`, content: html`<div style="text-align:center"><p>Do you really want to delete this external registry?</p><p style="color: #999; font-size: 0.9em; margin-top: 10px;">This will remove all stored credentials and configuration.</p></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;">${registry.data.name} (${registry.data.url})</div>`, menuOptions: [ { name: 'Cancel', action: async (modalArg: any) => { await modalArg.destroy(); } }, { name: 'Delete', action: async (modalArg: any) => { await appstate.dataState.dispatchAction(appstate.deleteExternalRegistryAction, { registryId: registry.id, }); await modalArg.destroy(); } } ] });
|
|
} },
|
|
] as plugins.deesCatalog.ITableAction[]}
|
|
></dees-table>
|
|
`;
|
|
}
|
|
}
|
|
|
|
declare global { interface HTMLElementTagNameMap { 'cloudly-view-externalregistries': CloudlyViewExternalRegistries; } }
|