feat(remote-ingress): add Remote Ingress hub integration, OpsServer UI, APIs, and docs
This commit is contained in:
@@ -3,6 +3,6 @@
|
||||
*/
|
||||
export const commitinfo = {
|
||||
name: '@serve.zone/dcrouter',
|
||||
version: '6.11.0',
|
||||
version: '6.12.0',
|
||||
description: 'A multifaceted routing service handling mail and SMS delivery functions.'
|
||||
}
|
||||
|
||||
@@ -200,7 +200,7 @@ export interface IRemoteIngressState {
|
||||
edges: interfaces.data.IRemoteIngress[];
|
||||
statuses: interfaces.data.IRemoteIngressStatus[];
|
||||
selectedEdgeId: string | null;
|
||||
newEdgeSecret: string | null;
|
||||
newEdgeId: string | null;
|
||||
isLoading: boolean;
|
||||
error: string | null;
|
||||
lastUpdated: number;
|
||||
@@ -212,7 +212,7 @@ export const remoteIngressStatePart = await appState.getStatePart<IRemoteIngress
|
||||
edges: [],
|
||||
statuses: [],
|
||||
selectedEdgeId: null,
|
||||
newEdgeSecret: null,
|
||||
newEdgeId: null,
|
||||
isLoading: false,
|
||||
error: null,
|
||||
lastUpdated: 0,
|
||||
@@ -928,11 +928,12 @@ export const createRemoteIngressAction = remoteIngressStatePart.createAction<{
|
||||
});
|
||||
|
||||
if (response.success) {
|
||||
// Refresh the list and store the new secret for display
|
||||
// Refresh the list
|
||||
await remoteIngressStatePart.dispatchAction(fetchRemoteIngressAction, null);
|
||||
|
||||
return {
|
||||
...statePartArg.getState(),
|
||||
newEdgeSecret: response.edge.secret,
|
||||
newEdgeId: response.edge.id,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1023,7 +1024,7 @@ export const regenerateRemoteIngressSecretAction = remoteIngressStatePart.create
|
||||
if (response.success) {
|
||||
return {
|
||||
...currentState,
|
||||
newEdgeSecret: response.secret,
|
||||
newEdgeId: edgeId,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1037,11 +1038,11 @@ export const regenerateRemoteIngressSecretAction = remoteIngressStatePart.create
|
||||
}
|
||||
);
|
||||
|
||||
export const clearNewEdgeSecretAction = remoteIngressStatePart.createAction(
|
||||
export const clearNewEdgeIdAction = remoteIngressStatePart.createAction(
|
||||
async (statePartArg) => {
|
||||
return {
|
||||
...statePartArg.getState(),
|
||||
newEdgeSecret: null,
|
||||
newEdgeId: null,
|
||||
};
|
||||
}
|
||||
);
|
||||
|
||||
@@ -382,7 +382,7 @@ export class OpsViewCertificates extends DeesElement {
|
||||
},
|
||||
{
|
||||
name: 'View Details',
|
||||
iconName: 'lucide:Search',
|
||||
iconName: 'fa:magnifyingGlass',
|
||||
type: ['doubleClick', 'contextmenu'],
|
||||
actionFunc: async (actionData: { item: interfaces.requests.ICertificateInfo }) => {
|
||||
const cert = actionData.item;
|
||||
|
||||
@@ -287,7 +287,7 @@ export class OpsViewNetwork extends DeesElement {
|
||||
.dataActions=${[
|
||||
{
|
||||
name: 'View Details',
|
||||
iconName: 'lucide:Search',
|
||||
iconName: 'fa:magnifyingGlass',
|
||||
type: ['inRow', 'doubleClick', 'contextmenu'],
|
||||
actionFunc: async (actionData) => {
|
||||
await this.showRequestDetails(actionData.item);
|
||||
@@ -435,7 +435,7 @@ export class OpsViewNetwork extends DeesElement {
|
||||
actions: [
|
||||
{
|
||||
name: 'View Details',
|
||||
iconName: 'lucide:Search',
|
||||
iconName: 'fa:magnifyingGlass',
|
||||
action: async () => {
|
||||
},
|
||||
},
|
||||
|
||||
@@ -176,13 +176,39 @@ export class OpsViewRemoteIngress extends DeesElement {
|
||||
return html`
|
||||
<ops-sectionheading>Remote Ingress</ops-sectionheading>
|
||||
|
||||
${this.riState.newEdgeSecret ? html`
|
||||
${this.riState.newEdgeId ? html`
|
||||
<div class="secretDialog">
|
||||
<strong>Edge Secret (copy now - shown only once):</strong>
|
||||
<code>${this.riState.newEdgeSecret}</code>
|
||||
<div class="warning">This secret will not be shown again. Save it securely.</div>
|
||||
<strong>Edge created successfully!</strong>
|
||||
<div class="warning">Copy the connection token now. Use it with edge.start({ token: '...' }).</div>
|
||||
<dees-button
|
||||
@click=${() => appstate.remoteIngressStatePart.dispatchAction(appstate.clearNewEdgeSecretAction, null)}
|
||||
@click=${async () => {
|
||||
const { DeesToast } = await import('@design.estate/dees-catalog');
|
||||
try {
|
||||
const response = await appstate.fetchConnectionToken(this.riState.newEdgeId);
|
||||
if (response.success && response.token) {
|
||||
if (navigator.clipboard && typeof navigator.clipboard.writeText === 'function') {
|
||||
await navigator.clipboard.writeText(response.token);
|
||||
} else {
|
||||
const textarea = document.createElement('textarea');
|
||||
textarea.value = response.token;
|
||||
textarea.style.position = 'fixed';
|
||||
textarea.style.opacity = '0';
|
||||
document.body.appendChild(textarea);
|
||||
textarea.select();
|
||||
document.execCommand('copy');
|
||||
document.body.removeChild(textarea);
|
||||
}
|
||||
DeesToast.show({ message: 'Connection token copied!', type: 'success', duration: 3000 });
|
||||
} else {
|
||||
DeesToast.show({ message: response.message || 'Failed to get token', type: 'error', duration: 4000 });
|
||||
}
|
||||
} catch (err) {
|
||||
DeesToast.show({ message: `Failed: ${err.message}`, type: 'error', duration: 4000 });
|
||||
}
|
||||
}}
|
||||
>Copy Connection Token</dees-button>
|
||||
<dees-button
|
||||
@click=${() => appstate.remoteIngressStatePart.dispatchAction(appstate.clearNewEdgeIdAction, null)}
|
||||
>Dismiss</dees-button>
|
||||
</div>
|
||||
` : ''}
|
||||
@@ -348,7 +374,7 @@ export class OpsViewRemoteIngress extends DeesElement {
|
||||
},
|
||||
{
|
||||
name: 'Copy Token',
|
||||
iconName: 'lucide:clipboard-copy',
|
||||
iconName: 'lucide:ClipboardCopy',
|
||||
type: ['inRow', 'contextmenu'] as any,
|
||||
actionFunc: async (actionData: any) => {
|
||||
const edge = actionData.item as interfaces.data.IRemoteIngress;
|
||||
@@ -356,7 +382,19 @@ export class OpsViewRemoteIngress extends DeesElement {
|
||||
try {
|
||||
const response = await appstate.fetchConnectionToken(edge.id);
|
||||
if (response.success && response.token) {
|
||||
await navigator.clipboard.writeText(response.token);
|
||||
// Use clipboard API with fallback for non-HTTPS contexts
|
||||
if (navigator.clipboard && typeof navigator.clipboard.writeText === 'function') {
|
||||
await navigator.clipboard.writeText(response.token);
|
||||
} else {
|
||||
const textarea = document.createElement('textarea');
|
||||
textarea.value = response.token;
|
||||
textarea.style.position = 'fixed';
|
||||
textarea.style.opacity = '0';
|
||||
document.body.appendChild(textarea);
|
||||
textarea.select();
|
||||
document.execCommand('copy');
|
||||
document.body.removeChild(textarea);
|
||||
}
|
||||
DeesToast.show({ message: `Connection token copied for ${edge.name}`, type: 'success', duration: 3000 });
|
||||
} else {
|
||||
DeesToast.show({ message: response.message || 'Failed to get token', type: 'error', duration: 4000 });
|
||||
|
||||
@@ -40,6 +40,15 @@ For reporting bugs, issues, or security vulnerabilities, please visit [community
|
||||
- Expiry date monitoring and alerts
|
||||
- Per-domain backoff status for failed provisions
|
||||
- One-click reprovisioning per domain
|
||||
- Certificate import, export, and deletion
|
||||
|
||||
### 🌍 Remote Ingress Management
|
||||
- Edge node registration with name, ports, and tags
|
||||
- Real-time connection status (connected/disconnected/disabled)
|
||||
- Public IP and active tunnel count per edge
|
||||
- Auto-derived port display with manual/derived breakdown
|
||||
- **Connection token generation** — one-click "Copy Token" for easy edge provisioning
|
||||
- Enable/disable, edit, secret regeneration, and delete actions
|
||||
|
||||
### 📜 Log Viewer
|
||||
- Real-time log streaming
|
||||
@@ -85,6 +94,7 @@ ts_web/
|
||||
├── ops-view-network.ts # Network monitoring
|
||||
├── ops-view-emails.ts # Email queue management
|
||||
├── ops-view-certificates.ts # Certificate overview & reprovisioning
|
||||
├── ops-view-remoteingress.ts # Remote ingress edge management
|
||||
├── ops-view-logs.ts # Log viewer
|
||||
├── ops-view-config.ts # Configuration display
|
||||
├── ops-view-security.ts # Security dashboard
|
||||
@@ -106,6 +116,8 @@ The app uses `@push.rocks/smartstate` with multiple state parts:
|
||||
| `logStatePart` | Soft | Recent logs, streaming status, filters |
|
||||
| `networkStatePart` | Soft | Connections, IPs, throughput rates |
|
||||
| `emailOpsStatePart` | Soft | Email queues, bounces, suppression list |
|
||||
| `certificateStatePart` | Soft | Certificate list, summary, loading state |
|
||||
| `remoteIngressStatePart` | Soft | Edge list, statuses, new edge secret |
|
||||
|
||||
### Actions
|
||||
|
||||
@@ -128,6 +140,23 @@ fetchSecurityIncidentsAction() // Security events
|
||||
fetchBounceRecordsAction() // Bounce records
|
||||
resendEmailAction(emailId) // Re-queue failed email
|
||||
removeFromSuppressionAction(email) // Remove from suppression list
|
||||
|
||||
// Certificates
|
||||
fetchCertificateOverviewAction() // All certificates with summary
|
||||
reprovisionCertificateAction(domain) // Reprovision a certificate
|
||||
deleteCertificateAction(domain) // Delete a certificate
|
||||
importCertificateAction(cert) // Import a certificate
|
||||
fetchCertificateExport(domain) // Export (standalone function)
|
||||
|
||||
// Remote Ingress
|
||||
fetchRemoteIngressAction() // Edges + statuses
|
||||
createRemoteIngressAction(data) // Create new edge
|
||||
updateRemoteIngressAction(data) // Update edge settings
|
||||
deleteRemoteIngressAction(id) // Remove edge
|
||||
regenerateRemoteIngressSecretAction(id) // New secret
|
||||
toggleRemoteIngressAction(id, enabled) // Enable/disable
|
||||
clearNewEdgeSecretAction() // Dismiss secret banner
|
||||
fetchConnectionToken(edgeId) // Get connection token (standalone function)
|
||||
```
|
||||
|
||||
### Client-Side Routing
|
||||
@@ -141,6 +170,7 @@ removeFromSuppressionAction(email) // Remove from suppression list
|
||||
/emails/failed → Failed emails
|
||||
/emails/security → Security incidents
|
||||
/certificates → Certificate management
|
||||
/remoteingress → Remote ingress edge management
|
||||
/logs → Log viewer
|
||||
/configuration → System configuration
|
||||
/security → Security dashboard
|
||||
|
||||
Reference in New Issue
Block a user