Compare commits
6 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 590e4bb039 | |||
| c4bef0d443 | |||
| 7f05b3b52d | |||
| 1fc1de6691 | |||
| 70c43ceae3 | |||
| cf247117d4 |
19
changelog.md
19
changelog.md
@@ -1,5 +1,24 @@
|
||||
# Changelog
|
||||
|
||||
## 2026-04-05 - 2.11.2 - fix(route-card)
|
||||
align route card with source profile metadata and vpnOnly route configuration
|
||||
|
||||
- rename linked route metadata fields from security profile to source profile in rendering and feature detection
|
||||
- simplify VPN display logic to use the boolean vpnOnly flag instead of the previous nested VPN configuration object
|
||||
|
||||
## 2026-04-04 - 2.11.1 - fix(route-card)
|
||||
clarify VPN mode badge labels in route cards
|
||||
|
||||
- Renames VPN badge text from "VPN Only"/"VPN + Public" to "VPN Mandatory"/"VPN Voluntary" for clearer route mode descriptions.
|
||||
|
||||
## 2026-04-02 - 2.11.0 - feat(route-ui)
|
||||
add VPN details and conditional card actions to route cards
|
||||
|
||||
- Extend route card data and rendering to display VPN access mode and allowed client tags.
|
||||
- Add optional Edit and Delete action buttons that emit route-edit and route-delete events.
|
||||
- Allow the route list view to control action visibility per route via a showActionsFilter callback.
|
||||
- Include VPN as a visible route feature indicator in the card summary.
|
||||
|
||||
## 2026-04-02 - 2.10.0 - feat(docs)
|
||||
document newly available catalog components and updated build configuration details
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@serve.zone/catalog",
|
||||
"version": "2.10.0",
|
||||
"version": "2.11.2",
|
||||
"private": false,
|
||||
"description": "UI component catalog for serve.zone",
|
||||
"main": "dist_ts_web/index.js",
|
||||
|
||||
@@ -3,6 +3,6 @@
|
||||
*/
|
||||
export const commitinfo = {
|
||||
name: '@serve.zone/catalog',
|
||||
version: '2.10.0',
|
||||
version: '2.11.2',
|
||||
description: 'UI component catalog for serve.zone'
|
||||
}
|
||||
|
||||
@@ -56,9 +56,9 @@ export interface IRouteSecurity {
|
||||
}
|
||||
|
||||
export interface IRouteMetadata {
|
||||
securityProfileRef?: string;
|
||||
sourceProfileRef?: string;
|
||||
networkTargetRef?: string;
|
||||
securityProfileName?: string;
|
||||
sourceProfileName?: string;
|
||||
networkTargetName?: string;
|
||||
lastResolvedAt?: number;
|
||||
}
|
||||
@@ -70,6 +70,8 @@ export interface IRouteConfig {
|
||||
security?: IRouteSecurity;
|
||||
headers?: { request?: Record<string, string>; response?: Record<string, string> };
|
||||
metadata?: IRouteMetadata;
|
||||
/** When true, only VPN clients whose TargetProfile matches this route get access */
|
||||
vpnOnly?: boolean;
|
||||
name?: string;
|
||||
description?: string;
|
||||
priority?: number;
|
||||
@@ -136,8 +138,9 @@ export class SzRouteCard extends DeesElement {
|
||||
rateLimit: { enabled: true, maxRequests: 100, window: 60 },
|
||||
maxConnections: 1000,
|
||||
},
|
||||
vpnOnly: true,
|
||||
metadata: {
|
||||
securityProfileName: 'STANDARD',
|
||||
sourceProfileName: 'STANDARD',
|
||||
networkTargetName: 'LOSSLESS_INFRA',
|
||||
},
|
||||
} satisfies IRouteConfig}
|
||||
@@ -150,6 +153,9 @@ export class SzRouteCard extends DeesElement {
|
||||
@property({ type: Object })
|
||||
public accessor route: IRouteConfig | null = null;
|
||||
|
||||
@property({ type: Boolean })
|
||||
public accessor showActions: boolean = false;
|
||||
|
||||
public static styles = [
|
||||
cssManager.defaultStyles,
|
||||
css`
|
||||
@@ -459,6 +465,83 @@ export class SzRouteCard extends DeesElement {
|
||||
color: ${cssManager.bdTheme('#a1a1aa', '#52525b')};
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.section.vpn {
|
||||
border-left-color: ${cssManager.bdTheme('#0891b2', '#06b6d4')};
|
||||
}
|
||||
|
||||
.vpn-badge {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
padding: 2px 8px;
|
||||
border-radius: 9999px;
|
||||
font-size: 11px;
|
||||
font-weight: 600;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.vpn-badge.mandatory {
|
||||
background: ${cssManager.bdTheme('#fff7ed', 'rgba(249, 115, 22, 0.2)')};
|
||||
color: ${cssManager.bdTheme('#c2410c', '#fb923c')};
|
||||
}
|
||||
|
||||
.vpn-badge.optional {
|
||||
background: ${cssManager.bdTheme('#ecfdf5', 'rgba(16, 185, 129, 0.2)')};
|
||||
color: ${cssManager.bdTheme('#047857', '#34d399')};
|
||||
}
|
||||
|
||||
.vpn-tag {
|
||||
display: inline-flex;
|
||||
padding: 1px 6px;
|
||||
border-radius: 3px;
|
||||
font-size: 12px;
|
||||
font-weight: 600;
|
||||
font-family: monospace;
|
||||
margin-right: 4px;
|
||||
margin-bottom: 2px;
|
||||
background: ${cssManager.bdTheme('#ecfeff', 'rgba(6, 182, 212, 0.15)')};
|
||||
color: ${cssManager.bdTheme('#0e7490', '#22d3ee')};
|
||||
}
|
||||
|
||||
.card-actions {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
margin-top: 14px;
|
||||
padding-top: 12px;
|
||||
border-top: 1px solid ${cssManager.bdTheme('#f4f4f5', '#1a1a1a')};
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
.action-btn {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
padding: 5px 12px;
|
||||
border-radius: 6px;
|
||||
font-size: 12px;
|
||||
font-weight: 500;
|
||||
cursor: pointer;
|
||||
border: 1px solid ${cssManager.bdTheme('#e4e4e7', '#27272a')};
|
||||
background: ${cssManager.bdTheme('#ffffff', '#09090b')};
|
||||
color: ${cssManager.bdTheme('#52525b', '#a1a1aa')};
|
||||
transition: all 150ms ease;
|
||||
}
|
||||
|
||||
.action-btn:hover {
|
||||
border-color: ${cssManager.bdTheme('#d4d4d8', '#3f3f46')};
|
||||
background: ${cssManager.bdTheme('#f4f4f5', '#18181b')};
|
||||
color: ${cssManager.bdTheme('#18181b', '#fafafa')};
|
||||
}
|
||||
|
||||
.action-btn.edit:hover {
|
||||
border-color: ${cssManager.bdTheme('#93c5fd', 'rgba(59, 130, 246, 0.5)')};
|
||||
color: ${cssManager.bdTheme('#2563eb', '#60a5fa')};
|
||||
}
|
||||
|
||||
.action-btn.delete:hover {
|
||||
border-color: ${cssManager.bdTheme('#fca5a5', 'rgba(239, 68, 68, 0.5)')};
|
||||
color: ${cssManager.bdTheme('#dc2626', '#f87171')};
|
||||
}
|
||||
`,
|
||||
];
|
||||
|
||||
@@ -652,11 +735,36 @@ export class SzRouteCard extends DeesElement {
|
||||
`
|
||||
: ''}
|
||||
|
||||
<!-- VPN Section -->
|
||||
${this.renderVpn()}
|
||||
|
||||
<!-- Linked References Section -->
|
||||
${this.renderLinked()}
|
||||
|
||||
<!-- Feature Icons Row -->
|
||||
${this.renderFeatures()}
|
||||
|
||||
<!-- Action Buttons -->
|
||||
${this.showActions ? html`
|
||||
<div class="card-actions">
|
||||
<button class="action-btn edit" @click=${(e: Event) => {
|
||||
e.stopPropagation();
|
||||
this.dispatchEvent(new CustomEvent('route-edit', {
|
||||
detail: this.route,
|
||||
bubbles: true,
|
||||
composed: true,
|
||||
}));
|
||||
}}>Edit</button>
|
||||
<button class="action-btn delete" @click=${(e: Event) => {
|
||||
e.stopPropagation();
|
||||
this.dispatchEvent(new CustomEvent('route-delete', {
|
||||
detail: this.route,
|
||||
bubbles: true,
|
||||
composed: true,
|
||||
}));
|
||||
}}>Delete</button>
|
||||
</div>
|
||||
` : ''}
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
@@ -669,10 +777,26 @@ export class SzRouteCard extends DeesElement {
|
||||
)}`;
|
||||
}
|
||||
|
||||
private renderVpn(): TemplateResult {
|
||||
if (!this.route?.vpnOnly) return html``;
|
||||
|
||||
return html`
|
||||
<div class="section vpn">
|
||||
<div class="section-label">VPN Access</div>
|
||||
<div class="field-row">
|
||||
<span class="field-key">Mode</span>
|
||||
<span class="field-value">
|
||||
<span class="vpn-badge mandatory">VPN Only</span>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
private renderLinked(): TemplateResult {
|
||||
const meta = this.route?.metadata;
|
||||
if (!meta) return html``;
|
||||
const hasProfile = !!meta.securityProfileName;
|
||||
const hasProfile = !!meta.sourceProfileName;
|
||||
const hasTarget = !!meta.networkTargetName;
|
||||
if (!hasProfile && !hasTarget) return html``;
|
||||
|
||||
@@ -683,7 +807,7 @@ export class SzRouteCard extends DeesElement {
|
||||
? html`
|
||||
<div class="field-row">
|
||||
<span class="field-key">Profile</span>
|
||||
<span class="field-value"><span class="linked-name">${meta.securityProfileName}</span></span>
|
||||
<span class="field-value"><span class="linked-name">${meta.sourceProfileName}</span></span>
|
||||
</div>
|
||||
`
|
||||
: ''}
|
||||
@@ -722,7 +846,10 @@ export class SzRouteCard extends DeesElement {
|
||||
if (headers) {
|
||||
features.push(html`<span class="feature"><span class="feature-icon">⚙</span>Headers</span>`);
|
||||
}
|
||||
if (meta?.securityProfileName || meta?.networkTargetName) {
|
||||
if (this.route?.vpnOnly) {
|
||||
features.push(html`<span class="feature"><span class="feature-icon">🔐</span>VPN</span>`);
|
||||
}
|
||||
if (meta?.sourceProfileName || meta?.networkTargetName) {
|
||||
features.push(html`<span class="feature"><span class="feature-icon">🔗</span>Linked</span>`);
|
||||
}
|
||||
|
||||
|
||||
@@ -76,6 +76,9 @@ export class SzRouteListView extends DeesElement {
|
||||
@property({ type: Array })
|
||||
public accessor routes: IRouteConfig[] = [];
|
||||
|
||||
@property({ attribute: false })
|
||||
public accessor showActionsFilter: ((route: IRouteConfig) => boolean) | null = null;
|
||||
|
||||
@state()
|
||||
private accessor searchQuery: string = '';
|
||||
|
||||
@@ -299,6 +302,7 @@ export class SzRouteListView extends DeesElement {
|
||||
(route) => html`
|
||||
<sz-route-card
|
||||
.route=${route}
|
||||
.showActions=${this.showActionsFilter?.(route) ?? false}
|
||||
@click=${() => this.handleRouteClick(route)}
|
||||
></sz-route-card>
|
||||
`
|
||||
|
||||
Reference in New Issue
Block a user