Compare commits
8 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 59a3f7978e | |||
| 7dc976b59e | |||
| 345effee13 | |||
| dee6897931 | |||
| 56f41d70b3 | |||
| 8f570ae8a0 | |||
| e58e24a92d | |||
| 12070bc7b5 |
33
changelog.md
33
changelog.md
@@ -1,5 +1,38 @@
|
|||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
|
## 2026-02-27 - 10.1.1 - fix(ops-view-apitokens)
|
||||||
|
replace lucide:refresh-cw with lucide:rotate-cw for Roll action icon
|
||||||
|
|
||||||
|
- Updated ts_web/elements/ops-view-apitokens.ts: changed iconName in two locations to 'lucide:rotate-cw' for the Roll/Roll Token actions.
|
||||||
|
- UI-only change — no functional or API behavior modified.
|
||||||
|
- Current package version is 10.1.0; recommended patch bump to 10.1.1.
|
||||||
|
|
||||||
|
## 2026-02-27 - 10.1.0 - feat(api-tokens)
|
||||||
|
add ability to roll (regenerate) API token secrets and UI to display the newly generated token once
|
||||||
|
|
||||||
|
- Server: added ApiTokenManager.rollToken(id) to regenerate a token secret, update its hash, persist it and log the action.
|
||||||
|
- Server: added opsserver handler 'rollApiToken' which requires admin identity and returns the new raw token value (shown once) or error messages.
|
||||||
|
- API: added typed request interface IReq_RollApiToken for the rollApiToken RPC.
|
||||||
|
- Web: added appstate.rollApiToken wrapper to call the new typed request.
|
||||||
|
- UI: ops-view-apitokens updated with a 'Roll' action and a modal flow to confirm rolling, call the API, refresh token list, and present the new token value to copy (token value is shown only once).
|
||||||
|
- Security: operation is admin-only and the raw token is returned only once after rolling.
|
||||||
|
|
||||||
|
## 2026-02-27 - 10.0.0 - BREAKING CHANGE(remote-ingress)
|
||||||
|
replace tlsConfigured boolean with tlsMode ('custom' | 'acme' | 'self-signed') and compute TLS mode server-side
|
||||||
|
|
||||||
|
- Server: compute remoteIngress.tlsMode = 'custom' when custom certPath/keyPath provided; else attempt to detect ACME by checking stored certs for hubDomain; default to 'self-signed' as fallback.
|
||||||
|
- API: replaced remoteIngress.tlsConfigured:boolean with tlsMode:'custom'|'acme'|'self-signed' — this is a breaking change for consumers of the config API.
|
||||||
|
- UI: ops view updated to display TLS Mode as a badge instead of a boolean "TLS Configured" field.
|
||||||
|
- Action required: update clients and integrations to read remoteIngress.tlsMode instead of tlsConfigured.
|
||||||
|
|
||||||
|
## 2026-02-26 - 9.3.0 - feat(remoteingress)
|
||||||
|
add TLS certificate resolution and passthrough for RemoteIngress tunnel
|
||||||
|
|
||||||
|
- Resolve TLS certs for the RemoteIngress tunnel with priority: explicit certPath/keyPath files → stored ACME cert for hubDomain → fallback to self-signed
|
||||||
|
- Expose tls option on ITunnelManagerConfig and forward certPem/keyPem into hub.start so the hub can use the provided TLS materials
|
||||||
|
- Add logging for cert selection and file read failures
|
||||||
|
- Bump dependency @serve.zone/remoteingress from ^4.2.0 to ^4.3.0
|
||||||
|
|
||||||
## 2026-02-26 - 9.2.0 - feat(remoteingress)
|
## 2026-02-26 - 9.2.0 - feat(remoteingress)
|
||||||
expose connected edge IPs and detected public IP; resolve proxy IPs from SmartProxy and improve ops UI
|
expose connected edge IPs and detected public IP; resolve proxy IPs from SmartProxy and improve ops UI
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "@serve.zone/dcrouter",
|
"name": "@serve.zone/dcrouter",
|
||||||
"private": false,
|
"private": false,
|
||||||
"version": "9.2.0",
|
"version": "10.1.1",
|
||||||
"description": "A multifaceted routing service handling mail and SMS delivery functions.",
|
"description": "A multifaceted routing service handling mail and SMS delivery functions.",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"exports": {
|
"exports": {
|
||||||
@@ -57,7 +57,7 @@
|
|||||||
"@push.rocks/smartunique": "^3.0.9",
|
"@push.rocks/smartunique": "^3.0.9",
|
||||||
"@serve.zone/catalog": "^2.5.0",
|
"@serve.zone/catalog": "^2.5.0",
|
||||||
"@serve.zone/interfaces": "^5.3.0",
|
"@serve.zone/interfaces": "^5.3.0",
|
||||||
"@serve.zone/remoteingress": "^4.2.0",
|
"@serve.zone/remoteingress": "^4.3.0",
|
||||||
"@tsclass/tsclass": "^9.3.0",
|
"@tsclass/tsclass": "^9.3.0",
|
||||||
"lru-cache": "^11.2.6",
|
"lru-cache": "^11.2.6",
|
||||||
"uuid": "^13.0.0"
|
"uuid": "^13.0.0"
|
||||||
|
|||||||
10
pnpm-lock.yaml
generated
10
pnpm-lock.yaml
generated
@@ -99,8 +99,8 @@ importers:
|
|||||||
specifier: ^5.3.0
|
specifier: ^5.3.0
|
||||||
version: 5.3.0
|
version: 5.3.0
|
||||||
'@serve.zone/remoteingress':
|
'@serve.zone/remoteingress':
|
||||||
specifier: ^4.2.0
|
specifier: ^4.3.0
|
||||||
version: 4.2.0
|
version: 4.3.0
|
||||||
'@tsclass/tsclass':
|
'@tsclass/tsclass':
|
||||||
specifier: ^9.3.0
|
specifier: ^9.3.0
|
||||||
version: 9.3.0
|
version: 9.3.0
|
||||||
@@ -1344,8 +1344,8 @@ packages:
|
|||||||
'@serve.zone/interfaces@5.3.0':
|
'@serve.zone/interfaces@5.3.0':
|
||||||
resolution: {integrity: sha512-venO7wtDR9ixzD9NhdERBGjNKbFA5LL0yHw4eqGh0UpmvtXVc3SFG0uuHDilOKMZqZ8bttV88qVsFy1aSTJrtA==}
|
resolution: {integrity: sha512-venO7wtDR9ixzD9NhdERBGjNKbFA5LL0yHw4eqGh0UpmvtXVc3SFG0uuHDilOKMZqZ8bttV88qVsFy1aSTJrtA==}
|
||||||
|
|
||||||
'@serve.zone/remoteingress@4.2.0':
|
'@serve.zone/remoteingress@4.3.0':
|
||||||
resolution: {integrity: sha512-VJsWAaTuNGtschbvnDP3PciZ5CGE8KLwrTXC8GN3tcDa2wGj7mJQ+JFlTaQRVoRLtCEMkxMYnLIrhJwal9yEYg==}
|
resolution: {integrity: sha512-yk14uS6oWIP83Zpem4hGf8zi3W9pefnxijtSWp45WvZ+u9XTXIADQNaUZBSTCId8CYkfPkfRGaaaARunVdjFXg==}
|
||||||
|
|
||||||
'@sindresorhus/is@5.6.0':
|
'@sindresorhus/is@5.6.0':
|
||||||
resolution: {integrity: sha512-TV7t8GKYaJWsn00tFDqBw8+Uqmr8A0fRU1tvTQhyZzGv0sJCGRQL3JGMI3ucuKo3XIZdUP+Lx7/gh2t3lewy7g==}
|
resolution: {integrity: sha512-TV7t8GKYaJWsn00tFDqBw8+Uqmr8A0fRU1tvTQhyZzGv0sJCGRQL3JGMI3ucuKo3XIZdUP+Lx7/gh2t3lewy7g==}
|
||||||
@@ -6827,7 +6827,7 @@ snapshots:
|
|||||||
'@push.rocks/smartlog-interfaces': 3.0.2
|
'@push.rocks/smartlog-interfaces': 3.0.2
|
||||||
'@tsclass/tsclass': 9.3.0
|
'@tsclass/tsclass': 9.3.0
|
||||||
|
|
||||||
'@serve.zone/remoteingress@4.2.0':
|
'@serve.zone/remoteingress@4.3.0':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@push.rocks/qenv': 6.1.3
|
'@push.rocks/qenv': 6.1.3
|
||||||
'@push.rocks/smartrust': 1.3.1
|
'@push.rocks/smartrust': 1.3.1
|
||||||
|
|||||||
@@ -3,6 +3,6 @@
|
|||||||
*/
|
*/
|
||||||
export const commitinfo = {
|
export const commitinfo = {
|
||||||
name: '@serve.zone/dcrouter',
|
name: '@serve.zone/dcrouter',
|
||||||
version: '9.2.0',
|
version: '10.1.1',
|
||||||
description: 'A multifaceted routing service handling mail and SMS delivery functions.'
|
description: 'A multifaceted routing service handling mail and SMS delivery functions.'
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1714,10 +1714,42 @@ export class DcRouter {
|
|||||||
const currentRoutes = this.options.smartProxyConfig?.routes || [];
|
const currentRoutes = this.options.smartProxyConfig?.routes || [];
|
||||||
this.remoteIngressManager.setRoutes(currentRoutes as any[]);
|
this.remoteIngressManager.setRoutes(currentRoutes as any[]);
|
||||||
|
|
||||||
|
// Resolve TLS certs for tunnel: explicit paths > ACME for hubDomain > self-signed (Rust default)
|
||||||
|
const riCfg = this.options.remoteIngressConfig;
|
||||||
|
let tlsConfig: { certPem: string; keyPem: string } | undefined;
|
||||||
|
|
||||||
|
// Priority 1: Explicit cert/key file paths
|
||||||
|
if (riCfg.tls?.certPath && riCfg.tls?.keyPath) {
|
||||||
|
try {
|
||||||
|
const certPem = plugins.fs.readFileSync(riCfg.tls.certPath, 'utf8');
|
||||||
|
const keyPem = plugins.fs.readFileSync(riCfg.tls.keyPath, 'utf8');
|
||||||
|
tlsConfig = { certPem, keyPem };
|
||||||
|
logger.log('info', 'Using explicit TLS cert/key for RemoteIngress tunnel');
|
||||||
|
} catch (err) {
|
||||||
|
logger.log('warn', `Failed to read RemoteIngress TLS cert/key files: ${err.message}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Priority 2: Existing cert from SmartProxy cert store for hubDomain
|
||||||
|
if (!tlsConfig && riCfg.hubDomain) {
|
||||||
|
try {
|
||||||
|
const stored = await this.storageManager.getJSON(`/proxy-certs/${riCfg.hubDomain}`);
|
||||||
|
if (stored?.publicKey && stored?.privateKey) {
|
||||||
|
tlsConfig = { certPem: stored.publicKey, keyPem: stored.privateKey };
|
||||||
|
logger.log('info', `Using stored ACME cert for RemoteIngress tunnel TLS: ${riCfg.hubDomain}`);
|
||||||
|
}
|
||||||
|
} catch { /* no stored cert, fall through */ }
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!tlsConfig) {
|
||||||
|
logger.log('info', 'No TLS cert configured for RemoteIngress tunnel — using auto-generated self-signed');
|
||||||
|
}
|
||||||
|
|
||||||
// Create and start the tunnel manager
|
// Create and start the tunnel manager
|
||||||
this.tunnelManager = new TunnelManager(this.remoteIngressManager, {
|
this.tunnelManager = new TunnelManager(this.remoteIngressManager, {
|
||||||
tunnelPort: this.options.remoteIngressConfig.tunnelPort ?? 8443,
|
tunnelPort: riCfg.tunnelPort ?? 8443,
|
||||||
targetHost: '127.0.0.1',
|
targetHost: '127.0.0.1',
|
||||||
|
tls: tlsConfig,
|
||||||
});
|
});
|
||||||
await this.tunnelManager.start();
|
await this.tunnelManager.start();
|
||||||
|
|
||||||
|
|||||||
@@ -122,6 +122,24 @@ export class ApiTokenManager {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Roll (regenerate) a token's secret while keeping its identity.
|
||||||
|
* Returns the new raw token value (shown once).
|
||||||
|
*/
|
||||||
|
public async rollToken(id: string): Promise<{ id: string; rawToken: string } | null> {
|
||||||
|
const stored = this.tokens.get(id);
|
||||||
|
if (!stored) return null;
|
||||||
|
|
||||||
|
const randomBytes = plugins.crypto.randomBytes(32);
|
||||||
|
const rawPayload = `${id}:${randomBytes.toString('base64url')}`;
|
||||||
|
const rawToken = `${TOKEN_PREFIX_STR}${rawPayload}`;
|
||||||
|
|
||||||
|
stored.tokenHash = plugins.crypto.createHash('sha256').update(rawToken).digest('hex');
|
||||||
|
await this.persistToken(stored);
|
||||||
|
logger.log('info', `API token '${stored.name}' rolled (id: ${id})`);
|
||||||
|
return { id, rawToken };
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Enable or disable a token.
|
* Enable or disable a token.
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -77,6 +77,25 @@ export class ApiTokenHandler {
|
|||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// Roll API token
|
||||||
|
this.typedrouter.addTypedHandler(
|
||||||
|
new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_RollApiToken>(
|
||||||
|
'rollApiToken',
|
||||||
|
async (dataArg) => {
|
||||||
|
await this.requireAdmin(dataArg.identity);
|
||||||
|
const manager = this.opsServerRef.dcRouterRef.apiTokenManager;
|
||||||
|
if (!manager) {
|
||||||
|
return { success: false, message: 'Token management not initialized' };
|
||||||
|
}
|
||||||
|
const result = await manager.rollToken(dataArg.id);
|
||||||
|
if (!result) {
|
||||||
|
return { success: false, message: 'Token not found' };
|
||||||
|
}
|
||||||
|
return { success: true, tokenValue: result.rawToken };
|
||||||
|
},
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
// Toggle API token
|
// Toggle API token
|
||||||
this.typedrouter.addTypedHandler(
|
this.typedrouter.addTypedHandler(
|
||||||
new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_ToggleApiToken>(
|
new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_ToggleApiToken>(
|
||||||
|
|||||||
@@ -179,11 +179,25 @@ export class ConfigHandler {
|
|||||||
// --- Remote Ingress ---
|
// --- Remote Ingress ---
|
||||||
const riCfg = opts.remoteIngressConfig;
|
const riCfg = opts.remoteIngressConfig;
|
||||||
const connectedEdgeIps = dcRouter.tunnelManager?.getConnectedEdgeIps() || [];
|
const connectedEdgeIps = dcRouter.tunnelManager?.getConnectedEdgeIps() || [];
|
||||||
|
|
||||||
|
// Determine TLS mode: custom certs > ACME from cert store > self-signed fallback
|
||||||
|
let tlsMode: 'custom' | 'acme' | 'self-signed' = 'self-signed';
|
||||||
|
if (riCfg?.tls?.certPath && riCfg?.tls?.keyPath) {
|
||||||
|
tlsMode = 'custom';
|
||||||
|
} else if (riCfg?.hubDomain) {
|
||||||
|
try {
|
||||||
|
const stored = await dcRouter.storageManager.getJSON(`/proxy-certs/${riCfg.hubDomain}`);
|
||||||
|
if (stored?.publicKey && stored?.privateKey) {
|
||||||
|
tlsMode = 'acme';
|
||||||
|
}
|
||||||
|
} catch { /* no stored cert */ }
|
||||||
|
}
|
||||||
|
|
||||||
const remoteIngress: interfaces.requests.IConfigData['remoteIngress'] = {
|
const remoteIngress: interfaces.requests.IConfigData['remoteIngress'] = {
|
||||||
enabled: !!dcRouter.remoteIngressManager,
|
enabled: !!dcRouter.remoteIngressManager,
|
||||||
tunnelPort: riCfg?.tunnelPort || null,
|
tunnelPort: riCfg?.tunnelPort || null,
|
||||||
hubDomain: riCfg?.hubDomain || null,
|
hubDomain: riCfg?.hubDomain || null,
|
||||||
tlsConfigured: !!(riCfg?.tls?.certPath && riCfg?.tls?.keyPath),
|
tlsMode,
|
||||||
connectedEdgeIps,
|
connectedEdgeIps,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -5,6 +5,10 @@ import type { RemoteIngressManager } from './classes.remoteingress-manager.js';
|
|||||||
export interface ITunnelManagerConfig {
|
export interface ITunnelManagerConfig {
|
||||||
tunnelPort?: number;
|
tunnelPort?: number;
|
||||||
targetHost?: string;
|
targetHost?: string;
|
||||||
|
tls?: {
|
||||||
|
certPem?: string;
|
||||||
|
keyPem?: string;
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -61,6 +65,7 @@ export class TunnelManager {
|
|||||||
await this.hub.start({
|
await this.hub.start({
|
||||||
tunnelPort: this.config.tunnelPort ?? 8443,
|
tunnelPort: this.config.tunnelPort ?? 8443,
|
||||||
targetHost: this.config.targetHost ?? '127.0.0.1',
|
targetHost: this.config.targetHost ?? '127.0.0.1',
|
||||||
|
tls: this.config.tls,
|
||||||
});
|
});
|
||||||
|
|
||||||
// Send allowed edges to the hub
|
// Send allowed edges to the hub
|
||||||
|
|||||||
@@ -63,6 +63,26 @@ export interface IReq_RevokeApiToken extends plugins.typedrequestInterfaces.impl
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Roll (regenerate) an API token's secret. Returns the new raw token value once.
|
||||||
|
* Admin JWT only.
|
||||||
|
*/
|
||||||
|
export interface IReq_RollApiToken extends plugins.typedrequestInterfaces.implementsTR<
|
||||||
|
plugins.typedrequestInterfaces.ITypedRequest,
|
||||||
|
IReq_RollApiToken
|
||||||
|
> {
|
||||||
|
method: 'rollApiToken';
|
||||||
|
request: {
|
||||||
|
identity?: authInterfaces.IIdentity;
|
||||||
|
id: string;
|
||||||
|
};
|
||||||
|
response: {
|
||||||
|
success: boolean;
|
||||||
|
tokenValue?: string;
|
||||||
|
message?: string;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Enable or disable an API token.
|
* Enable or disable an API token.
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -69,7 +69,7 @@ export interface IConfigData {
|
|||||||
enabled: boolean;
|
enabled: boolean;
|
||||||
tunnelPort: number | null;
|
tunnelPort: number | null;
|
||||||
hubDomain: string | null;
|
hubDomain: string | null;
|
||||||
tlsConfigured: boolean;
|
tlsMode: 'custom' | 'acme' | 'self-signed';
|
||||||
connectedEdgeIps: string[];
|
connectedEdgeIps: string[];
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,6 @@
|
|||||||
*/
|
*/
|
||||||
export const commitinfo = {
|
export const commitinfo = {
|
||||||
name: '@serve.zone/dcrouter',
|
name: '@serve.zone/dcrouter',
|
||||||
version: '9.2.0',
|
version: '10.1.1',
|
||||||
description: 'A multifaceted routing service handling mail and SMS delivery functions.'
|
description: 'A multifaceted routing service handling mail and SMS delivery functions.'
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1115,6 +1115,18 @@ export async function createApiToken(name: string, scopes: interfaces.data.TApiT
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function rollApiToken(id: string) {
|
||||||
|
const context = getActionContext();
|
||||||
|
const request = new plugins.domtools.plugins.typedrequest.TypedRequest<
|
||||||
|
interfaces.requests.IReq_RollApiToken
|
||||||
|
>('/typedrequest', 'rollApiToken');
|
||||||
|
|
||||||
|
return request.fire({
|
||||||
|
identity: context.identity,
|
||||||
|
id,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
export const revokeApiTokenAction = routeManagementStatePart.createAction<string>(
|
export const revokeApiTokenAction = routeManagementStatePart.createAction<string>(
|
||||||
async (statePartArg, tokenId) => {
|
async (statePartArg, tokenId) => {
|
||||||
const context = getActionContext();
|
const context = getActionContext();
|
||||||
|
|||||||
@@ -152,6 +152,15 @@ export class OpsViewApiTokens extends DeesElement {
|
|||||||
);
|
);
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: 'Roll',
|
||||||
|
iconName: 'lucide:rotate-cw',
|
||||||
|
type: ['inRow', 'contextmenu'] as any,
|
||||||
|
actionFunc: async (actionData: any) => {
|
||||||
|
const token = actionData.item as interfaces.data.IApiTokenInfo;
|
||||||
|
await this.showRollTokenDialog(token);
|
||||||
|
},
|
||||||
|
},
|
||||||
{
|
{
|
||||||
name: 'Revoke',
|
name: 'Revoke',
|
||||||
iconName: 'lucide:trash2',
|
iconName: 'lucide:trash2',
|
||||||
@@ -279,6 +288,60 @@ export class OpsViewApiTokens extends DeesElement {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async showRollTokenDialog(token: interfaces.data.IApiTokenInfo) {
|
||||||
|
const { DeesModal } = await import('@design.estate/dees-catalog');
|
||||||
|
|
||||||
|
await DeesModal.createAndShow({
|
||||||
|
heading: 'Roll Token Secret',
|
||||||
|
content: html`
|
||||||
|
<div style="color: #ccc; padding: 8px 0;">
|
||||||
|
<p>This will regenerate the secret for <strong>${token.name}</strong>. The old token value will stop working immediately.</p>
|
||||||
|
</div>
|
||||||
|
`,
|
||||||
|
menuOptions: [
|
||||||
|
{
|
||||||
|
name: 'Cancel',
|
||||||
|
iconName: 'lucide:x',
|
||||||
|
action: async (modalArg: any) => await modalArg.destroy(),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Roll Token',
|
||||||
|
iconName: 'lucide:rotate-cw',
|
||||||
|
action: async (modalArg: any) => {
|
||||||
|
await modalArg.destroy();
|
||||||
|
try {
|
||||||
|
const response = await appstate.rollApiToken(token.id);
|
||||||
|
if (response.success && response.tokenValue) {
|
||||||
|
await appstate.routeManagementStatePart.dispatchAction(appstate.fetchApiTokensAction, null);
|
||||||
|
|
||||||
|
await DeesModal.createAndShow({
|
||||||
|
heading: 'Token Rolled',
|
||||||
|
content: html`
|
||||||
|
<div style="color: #ccc; padding: 8px 0;">
|
||||||
|
<p>Copy this token now. It will not be shown again.</p>
|
||||||
|
<div style="background: #111; padding: 12px; border-radius: 6px; margin-top: 8px;">
|
||||||
|
<code style="color: #0f8; word-break: break-all; font-size: 13px;">${response.tokenValue}</code>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`,
|
||||||
|
menuOptions: [
|
||||||
|
{
|
||||||
|
name: 'Done',
|
||||||
|
iconName: 'lucide:check',
|
||||||
|
action: async (m: any) => await m.destroy(),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Failed to roll token:', error);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
async firstUpdated() {
|
async firstUpdated() {
|
||||||
await appstate.routeManagementStatePart.dispatchAction(appstate.fetchApiTokensAction, null);
|
await appstate.routeManagementStatePart.dispatchAction(appstate.fetchApiTokensAction, null);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -300,7 +300,7 @@ export class OpsViewConfig extends DeesElement {
|
|||||||
const fields: IConfigField[] = [
|
const fields: IConfigField[] = [
|
||||||
{ key: 'Tunnel Port', value: ri.tunnelPort },
|
{ key: 'Tunnel Port', value: ri.tunnelPort },
|
||||||
{ key: 'Hub Domain', value: ri.hubDomain },
|
{ key: 'Hub Domain', value: ri.hubDomain },
|
||||||
{ key: 'TLS Configured', value: ri.tlsConfigured, type: 'boolean' },
|
{ key: 'TLS Mode', value: ri.tlsMode, type: 'badge' },
|
||||||
{ key: 'Connected Edge IPs', value: ri.connectedEdgeIps?.length > 0 ? ri.connectedEdgeIps : null, type: 'pills' },
|
{ key: 'Connected Edge IPs', value: ri.connectedEdgeIps?.length > 0 ? ri.connectedEdgeIps : null, type: 'pills' },
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user