2025-09-14 17:28:21 +00:00
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 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 } > < / d e e s - i n p u t - d r o p d o w n >
< dees - input - text .key = $ { 'name' } .label = $ { 'Registry Name' } .value = $ { registry.data.name } .required = $ { true } > < / d e e s - i n p u t - t e x t >
< dees - input - text .key = $ { 'url' } .label = $ { 'Registry URL' } .value = $ { registry.data.url } .required = $ { true } > < / d e e s - i n p u t - t e x t >
< dees - input - text .key = $ { 'username' } .label = $ { 'Username (only needed for basic auth)' } .value = $ { registry.data.username | | '' } .placeholder = $ { 'Leave empty for token auth' } > < / d e e s - i n p u t - t e x t >
< dees - input - text .key = $ { 'password' } .label = $ { 'Password / Token (leave empty to keep current)' } .placeholder = $ { 'New token or password' } .isPasswordBool = $ { true } > < / d e e s - i n p u t - t e x t >
< dees - input - text .key = $ { 'namespace' } .label = $ { 'Namespace/Organization (optional)' } .value = $ { registry.data.namespace | | '' } > < / d e e s - i n p u t - t e x t >
< dees - input - text .key = $ { 'description' } .label = $ { 'Description (optional)' } .value = $ { registry.data.description | | '' } > < / d e e s - i n p u t - t e x t >
< 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' } > < / d e e s - i n p u t - d r o p d o w n >
< dees - input - checkbox .key = $ { 'isDefault' } .label = $ { 'Set as default registry for this type' } .value = $ { registry.data.isDefault | | false } > < / d e e s - i n p u t - c h e c k b o x >
< dees - input - checkbox .key = $ { 'insecure' } .label = $ { 'Allow insecure connections (HTTP/self-signed certs)' } .value = $ { registry.data.insecure | | false } > < / d e e s - i n p u t - c h e c k b o x >
< / d e e s - f o r m >
2026-04-28 14:35:19 +00:00
` , 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(); } } ] });
2025-09-14 17:28:21 +00:00
} },
{ 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 > < / d e e s - s p i n n e r > < p s t y l e = " m a r g i n - t o p : 2 0 p x ; " > T e s t i n g c o n n e c t i o n t o $ { r e g i s t r y . d a t a . n a m e } . . . < / p > < / d i v > ` , m e n u O p t i o n s : [ ] } ) ;
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 [ ] }
> < / d e e s - t a b l e >
` ;
}
}
declare global { interface HTMLElementTagNameMap { 'cloudly-view-externalregistries': CloudlyViewExternalRegistries; } }