@syndew/oluso-admin-ui 0.0.0-stage → 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +6 -2
- package/fesm2022/syndew-oluso-admin-ui-entitlements-page.component-BeZtIUnk.mjs +149 -0
- package/fesm2022/syndew-oluso-admin-ui-entitlements-page.component-BeZtIUnk.mjs.map +1 -0
- package/fesm2022/syndew-oluso-admin-ui-license-status-page.component-JQmj_qhz.mjs +205 -0
- package/fesm2022/syndew-oluso-admin-ui-license-status-page.component-JQmj_qhz.mjs.map +1 -0
- package/fesm2022/syndew-oluso-admin-ui-platform-clients-page.component-ab3WDRsI.mjs +80 -0
- package/fesm2022/syndew-oluso-admin-ui-platform-clients-page.component-ab3WDRsI.mjs.map +1 -0
- package/fesm2022/syndew-oluso-admin-ui-search-results-page.component-EH82yuW3.mjs +543 -0
- package/fesm2022/syndew-oluso-admin-ui-search-results-page.component-EH82yuW3.mjs.map +1 -0
- package/fesm2022/syndew-oluso-admin-ui-syndew-oluso-admin-ui-BVs_bAxm.mjs +22987 -0
- package/fesm2022/syndew-oluso-admin-ui-syndew-oluso-admin-ui-BVs_bAxm.mjs.map +1 -0
- package/fesm2022/syndew-oluso-admin-ui-team-detail-page.component-CjrQ89qU.mjs +566 -0
- package/fesm2022/syndew-oluso-admin-ui-team-detail-page.component-CjrQ89qU.mjs.map +1 -0
- package/fesm2022/syndew-oluso-admin-ui-unauthorized-page.component-CWYFN6mp.mjs +73 -0
- package/fesm2022/syndew-oluso-admin-ui-unauthorized-page.component-CWYFN6mp.mjs.map +1 -0
- package/fesm2022/syndew-oluso-admin-ui-webhook-detail-page.component-Bh5dZwt-.mjs +728 -0
- package/fesm2022/syndew-oluso-admin-ui-webhook-detail-page.component-Bh5dZwt-.mjs.map +1 -0
- package/fesm2022/syndew-oluso-admin-ui.mjs +3 -0
- package/fesm2022/syndew-oluso-admin-ui.mjs.map +1 -0
- package/help/en/admin.api-scopes.detail.md +43 -0
- package/help/en/admin.api-scopes.md +37 -0
- package/help/en/admin.audit-logs.md +44 -0
- package/help/en/admin.clients.create.md +60 -0
- package/help/en/admin.clients.detail.md +61 -0
- package/help/en/admin.clients.md +48 -0
- package/help/en/admin.dashboard.md +28 -0
- package/help/en/admin.experience.journeys.builder.md +115 -0
- package/help/en/admin.experience.journeys.md +54 -0
- package/help/en/admin.experience.plugins.md +83 -0
- package/help/en/admin.experience.submissions.md +71 -0
- package/help/en/admin.feedback.detail.md +36 -0
- package/help/en/admin.feedback.md +42 -0
- package/help/en/admin.grants.md +42 -0
- package/help/en/admin.identity-providers.detail.md +46 -0
- package/help/en/admin.identity-providers.md +39 -0
- package/help/en/admin.identity-resources.md +47 -0
- package/help/en/admin.module-seats.md +45 -0
- package/help/en/admin.organizations.detail.md +51 -0
- package/help/en/admin.organizations.md +38 -0
- package/help/en/admin.platform.clients.create.md +34 -0
- package/help/en/admin.platform.clients.detail.md +28 -0
- package/help/en/admin.platform.clients.md +38 -0
- package/help/en/admin.resources.detail.md +44 -0
- package/help/en/admin.resources.md +43 -0
- package/help/en/admin.roles.md +33 -0
- package/help/en/admin.settings.license.md +39 -0
- package/help/en/admin.settings.md +70 -0
- package/help/en/admin.signing-keys.md +47 -0
- package/help/en/admin.tenants.detail.md +58 -0
- package/help/en/admin.tenants.md +46 -0
- package/help/en/admin.users.detail.md +41 -0
- package/help/en/admin.users.md +56 -0
- package/help/en/admin.webhooks.detail.md +41 -0
- package/help/en/admin.webhooks.md +37 -0
- package/package.json +32 -3
- package/types/syndew-oluso-admin-ui.d.ts +5786 -0
package/README.md
CHANGED
|
@@ -1,3 +1,7 @@
|
|
|
1
|
-
#
|
|
1
|
+
# admin-ui
|
|
2
2
|
|
|
3
|
-
This
|
|
3
|
+
This library was generated with [Nx](https://nx.dev).
|
|
4
|
+
|
|
5
|
+
## Running unit tests
|
|
6
|
+
|
|
7
|
+
Run `nx test admin-ui` to execute the unit tests.
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
import * as i0 from '@angular/core';
|
|
2
|
+
import { inject, signal, Component } from '@angular/core';
|
|
3
|
+
import { CommonModule } from '@angular/common';
|
|
4
|
+
import * as i1 from '@angular/forms';
|
|
5
|
+
import { FormsModule } from '@angular/forms';
|
|
6
|
+
import { CardComponent, ButtonComponent, BadgeComponent, ModalComponent, IconComponent } from '@syndew/oluso-ui-core';
|
|
7
|
+
import { E as EntitlementService } from './syndew-oluso-admin-ui-syndew-oluso-admin-ui-BVs_bAxm.mjs';
|
|
8
|
+
|
|
9
|
+
class EntitlementsPageComponent {
|
|
10
|
+
entitlementService = inject(EntitlementService);
|
|
11
|
+
loading = signal(true, ...(ngDevMode ? [{ debugName: "loading" }] : /* istanbul ignore next */ []));
|
|
12
|
+
saving = signal(false, ...(ngDevMode ? [{ debugName: "saving" }] : /* istanbul ignore next */ []));
|
|
13
|
+
deleting = signal(false, ...(ngDevMode ? [{ debugName: "deleting" }] : /* istanbul ignore next */ []));
|
|
14
|
+
entitlements = signal([], ...(ngDevMode ? [{ debugName: "entitlements" }] : /* istanbul ignore next */ []));
|
|
15
|
+
error = signal(null, ...(ngDevMode ? [{ debugName: "error" }] : /* istanbul ignore next */ []));
|
|
16
|
+
showCreateEditModal = signal(false, ...(ngDevMode ? [{ debugName: "showCreateEditModal" }] : /* istanbul ignore next */ []));
|
|
17
|
+
showDeleteModal = signal(false, ...(ngDevMode ? [{ debugName: "showDeleteModal" }] : /* istanbul ignore next */ []));
|
|
18
|
+
selected = signal(null, ...(ngDevMode ? [{ debugName: "selected" }] : /* istanbul ignore next */ []));
|
|
19
|
+
formData = this.emptyForm();
|
|
20
|
+
constructor() {
|
|
21
|
+
this.load();
|
|
22
|
+
}
|
|
23
|
+
emptyForm() {
|
|
24
|
+
return { name: '', displayName: '', description: '', group: '', enabled: true };
|
|
25
|
+
}
|
|
26
|
+
load() {
|
|
27
|
+
this.loading.set(true);
|
|
28
|
+
this.entitlementService.getAll().subscribe({
|
|
29
|
+
next: (entitlements) => {
|
|
30
|
+
this.entitlements.set(entitlements);
|
|
31
|
+
this.loading.set(false);
|
|
32
|
+
},
|
|
33
|
+
error: (err) => {
|
|
34
|
+
console.error('Failed to load entitlements:', err);
|
|
35
|
+
this.loading.set(false);
|
|
36
|
+
},
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
/** Grouped for display; ungrouped entries fall under "General". */
|
|
40
|
+
groups() {
|
|
41
|
+
const groups = [];
|
|
42
|
+
for (const entitlement of this.entitlements()) {
|
|
43
|
+
const groupName = entitlement.group || 'General';
|
|
44
|
+
const group = groups.find((g) => g.name === groupName);
|
|
45
|
+
if (group) {
|
|
46
|
+
group.entitlements.push(entitlement);
|
|
47
|
+
}
|
|
48
|
+
else {
|
|
49
|
+
groups.push({ name: groupName, entitlements: [entitlement] });
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
return groups;
|
|
53
|
+
}
|
|
54
|
+
openCreateModal() {
|
|
55
|
+
this.selected.set(null);
|
|
56
|
+
this.formData = this.emptyForm();
|
|
57
|
+
this.error.set(null);
|
|
58
|
+
this.showCreateEditModal.set(true);
|
|
59
|
+
}
|
|
60
|
+
openEditModal(entitlement) {
|
|
61
|
+
this.selected.set(entitlement);
|
|
62
|
+
this.formData = {
|
|
63
|
+
name: entitlement.name,
|
|
64
|
+
displayName: entitlement.displayName,
|
|
65
|
+
description: entitlement.description || '',
|
|
66
|
+
group: entitlement.group || '',
|
|
67
|
+
enabled: entitlement.enabled,
|
|
68
|
+
};
|
|
69
|
+
this.error.set(null);
|
|
70
|
+
this.showCreateEditModal.set(true);
|
|
71
|
+
}
|
|
72
|
+
closeCreateEditModal() {
|
|
73
|
+
this.showCreateEditModal.set(false);
|
|
74
|
+
this.selected.set(null);
|
|
75
|
+
this.error.set(null);
|
|
76
|
+
}
|
|
77
|
+
onSave() {
|
|
78
|
+
const existing = this.selected();
|
|
79
|
+
this.saving.set(true);
|
|
80
|
+
this.error.set(null);
|
|
81
|
+
const done = {
|
|
82
|
+
next: () => {
|
|
83
|
+
this.saving.set(false);
|
|
84
|
+
this.closeCreateEditModal();
|
|
85
|
+
this.load();
|
|
86
|
+
},
|
|
87
|
+
error: (err) => {
|
|
88
|
+
this.error.set(err?.error?.error || 'Save failed');
|
|
89
|
+
this.saving.set(false);
|
|
90
|
+
},
|
|
91
|
+
};
|
|
92
|
+
if (existing) {
|
|
93
|
+
// Name is immutable — it's already in issued tokens and on roles.
|
|
94
|
+
this.entitlementService
|
|
95
|
+
.update(existing.id, {
|
|
96
|
+
displayName: this.formData.displayName || this.formData.name,
|
|
97
|
+
description: this.formData.description,
|
|
98
|
+
group: this.formData.group,
|
|
99
|
+
enabled: this.formData.enabled,
|
|
100
|
+
})
|
|
101
|
+
.subscribe(done);
|
|
102
|
+
}
|
|
103
|
+
else {
|
|
104
|
+
this.entitlementService
|
|
105
|
+
.create({
|
|
106
|
+
name: this.formData.name.trim(),
|
|
107
|
+
displayName: this.formData.displayName || undefined,
|
|
108
|
+
description: this.formData.description || undefined,
|
|
109
|
+
group: this.formData.group || undefined,
|
|
110
|
+
enabled: this.formData.enabled,
|
|
111
|
+
})
|
|
112
|
+
.subscribe(done);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
openDeleteModal(entitlement) {
|
|
116
|
+
this.selected.set(entitlement);
|
|
117
|
+
this.showDeleteModal.set(true);
|
|
118
|
+
}
|
|
119
|
+
closeDeleteModal() {
|
|
120
|
+
this.showDeleteModal.set(false);
|
|
121
|
+
this.selected.set(null);
|
|
122
|
+
}
|
|
123
|
+
confirmDelete() {
|
|
124
|
+
const entitlement = this.selected();
|
|
125
|
+
if (!entitlement)
|
|
126
|
+
return;
|
|
127
|
+
this.deleting.set(true);
|
|
128
|
+
this.entitlementService.delete(entitlement.id).subscribe({
|
|
129
|
+
next: () => {
|
|
130
|
+
this.deleting.set(false);
|
|
131
|
+
this.closeDeleteModal();
|
|
132
|
+
this.load();
|
|
133
|
+
},
|
|
134
|
+
error: (err) => {
|
|
135
|
+
console.error('Failed to delete entitlement:', err);
|
|
136
|
+
this.deleting.set(false);
|
|
137
|
+
},
|
|
138
|
+
});
|
|
139
|
+
}
|
|
140
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.25", ngImport: i0, type: EntitlementsPageComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
141
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.25", type: EntitlementsPageComponent, isStandalone: true, selector: "admin-entitlements-page", ngImport: i0, template: "<div class=\"space-y-6\">\n <div class=\"flex items-start justify-between\">\n <div>\n <h1 class=\"text-2xl font-semibold text-gray-900\">Entitlements</h1>\n <p class=\"mt-1 text-sm text-gray-500\">\n Permissions your own applications enforce. Assign them to roles, and they arrive in the\n <code class=\"font-mono\">entitlements</code> claim for clients granted the\n <code class=\"font-mono\">entitlements</code> scope. They grant nothing inside Oluso.\n </p>\n </div>\n <ui-button (clicked)=\"openCreateModal()\">\n <ui-icon name=\"plus\" class=\"h-4 w-4 mr-1\" />\n New Entitlement\n </ui-button>\n </div>\n\n @if (loading()) {\n <div class=\"text-sm text-gray-500 p-4\">Loading entitlements...</div>\n } @else if (entitlements().length === 0) {\n <ui-card>\n <div class=\"text-center py-10\">\n <ui-icon name=\"key\" class=\"mx-auto h-8 w-8 text-gray-300\" />\n <p class=\"mt-2 text-sm text-gray-500\">\n No entitlements yet. Define one to make it selectable when editing a role.\n </p>\n </div>\n </ui-card>\n } @else {\n @for (group of groups(); track group.name) {\n <ui-card [title]=\"group.name\">\n <div class=\"divide-y\">\n @for (entitlement of group.entitlements; track entitlement.id) {\n <div class=\"flex items-start justify-between py-3\">\n <div>\n <div class=\"flex items-center gap-2\">\n <span class=\"text-sm font-medium text-gray-900\">{{ entitlement.displayName }}</span>\n <span class=\"font-mono text-xs text-gray-500\">{{ entitlement.name }}</span>\n @if (!entitlement.enabled) {\n <ui-badge variant=\"warning\">Disabled</ui-badge>\n }\n </div>\n @if (entitlement.description) {\n <p class=\"mt-1 text-sm text-gray-500\">{{ entitlement.description }}</p>\n }\n </div>\n <div class=\"flex items-center gap-2\">\n <button\n type=\"button\"\n class=\"text-gray-500 hover:text-gray-700\"\n title=\"Edit\"\n (click)=\"openEditModal(entitlement)\"\n >\n <ui-icon name=\"pencil\" class=\"h-4 w-4\" />\n </button>\n <button\n type=\"button\"\n class=\"text-red-500 hover:text-red-700\"\n title=\"Delete\"\n (click)=\"openDeleteModal(entitlement)\"\n >\n <ui-icon name=\"trash\" class=\"h-4 w-4\" />\n </button>\n </div>\n </div>\n }\n </div>\n </ui-card>\n }\n }\n</div>\n\n<ui-modal\n [open]=\"showCreateEditModal()\"\n [title]=\"selected() ? 'Edit Entitlement' : 'New Entitlement'\"\n (closed)=\"closeCreateEditModal()\"\n>\n <div class=\"space-y-4\">\n @if (error()) {\n <div class=\"rounded-md bg-red-50 p-3 text-sm text-red-700\">{{ error() }}</div>\n }\n\n <div>\n <label class=\"block text-sm font-medium text-gray-700 mb-1\">Name</label>\n <input\n type=\"text\"\n [(ngModel)]=\"formData.name\"\n [disabled]=\"!!selected()\"\n placeholder=\"invoices.approve\"\n class=\"w-full rounded-md border-gray-300 shadow-sm focus:border-primary-500 focus:ring-primary-500 sm:text-sm disabled:bg-gray-100\"\n />\n <p class=\"mt-1 text-xs text-gray-500\">\n @if (selected()) {\n The name is what your app checks and is already in issued tokens, so it can't be changed.\n Delete and recreate to rename.\n } @else {\n The exact value your application will check. No spaces.\n }\n </p>\n </div>\n\n <div>\n <label class=\"block text-sm font-medium text-gray-700 mb-1\">Display name</label>\n <input\n type=\"text\"\n [(ngModel)]=\"formData.displayName\"\n placeholder=\"Approve invoices\"\n class=\"w-full rounded-md border-gray-300 shadow-sm focus:border-primary-500 focus:ring-primary-500 sm:text-sm\"\n />\n </div>\n\n <div>\n <label class=\"block text-sm font-medium text-gray-700 mb-1\">Description</label>\n <textarea\n [(ngModel)]=\"formData.description\"\n rows=\"2\"\n class=\"w-full rounded-md border-gray-300 shadow-sm focus:border-primary-500 focus:ring-primary-500 sm:text-sm\"\n ></textarea>\n </div>\n\n <div>\n <label class=\"block text-sm font-medium text-gray-700 mb-1\">Group</label>\n <input\n type=\"text\"\n [(ngModel)]=\"formData.group\"\n placeholder=\"Invoices\"\n class=\"w-full rounded-md border-gray-300 shadow-sm focus:border-primary-500 focus:ring-primary-500 sm:text-sm\"\n />\n <p class=\"mt-1 text-xs text-gray-500\">Groups the entitlement in the role editor.</p>\n </div>\n\n <label class=\"flex items-start gap-2 text-sm\">\n <input\n type=\"checkbox\"\n class=\"h-4 w-4 mt-0.5 text-primary-600 focus:ring-primary-500 border-gray-300 rounded\"\n [(ngModel)]=\"formData.enabled\"\n />\n <span>\n Enabled\n <span class=\"block text-gray-500\">\n Turning this off withholds it from new tokens without unassigning it from roles.\n </span>\n </span>\n </label>\n </div>\n\n <div modal-footer class=\"px-6 py-4 border-t border-gray-200 flex justify-end gap-2\">\n <ui-button variant=\"secondary\" (clicked)=\"closeCreateEditModal()\">Cancel</ui-button>\n <ui-button [loading]=\"saving()\" [disabled]=\"!formData.name.trim()\" (clicked)=\"onSave()\">\n {{ selected() ? 'Save' : 'Create' }}\n </ui-button>\n </div>\n</ui-modal>\n\n<ui-modal [open]=\"showDeleteModal()\" title=\"Delete Entitlement\" (closed)=\"closeDeleteModal()\">\n <p class=\"text-sm text-gray-500\">\n Delete <span class=\"font-mono\">{{ selected()?.name }}</span>? It will also be removed from every\n role that grants it, and will stop appearing in tokens.\n </p>\n <div modal-footer class=\"px-6 py-4 border-t border-gray-200 flex justify-end gap-2\">\n <ui-button variant=\"secondary\" (clicked)=\"closeDeleteModal()\">Cancel</ui-button>\n <ui-button variant=\"danger\" [loading]=\"deleting()\" (clicked)=\"confirmDelete()\">Delete</ui-button>\n </div>\n</ui-modal>\n", dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1.CheckboxControlValueAccessor, selector: "input[type=checkbox][formControlName],input[type=checkbox][formControl],input[type=checkbox][ngModel]" }, { kind: "directive", type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "component", type: CardComponent, selector: "ui-card", inputs: ["title", "subtitle", "padding", "shadow"] }, { kind: "component", type: ButtonComponent, selector: "ui-button", inputs: ["variant", "size", "disabled", "loading", "type", "fullWidth"], outputs: ["clicked"] }, { kind: "component", type: BadgeComponent, selector: "ui-badge", inputs: ["variant", "size"] }, { kind: "component", type: ModalComponent, selector: "ui-modal", inputs: ["open", "title", "size", "showCloseButton", "closeOnBackdrop", "closeOnEscape", "scrollable"], outputs: ["closed"] }, { kind: "component", type: IconComponent, selector: "ui-icon", inputs: ["name", "strokeWidth"] }] });
|
|
142
|
+
}
|
|
143
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.25", ngImport: i0, type: EntitlementsPageComponent, decorators: [{
|
|
144
|
+
type: Component,
|
|
145
|
+
args: [{ selector: 'admin-entitlements-page', standalone: true, imports: [CommonModule, FormsModule, CardComponent, ButtonComponent, BadgeComponent, ModalComponent, IconComponent], template: "<div class=\"space-y-6\">\n <div class=\"flex items-start justify-between\">\n <div>\n <h1 class=\"text-2xl font-semibold text-gray-900\">Entitlements</h1>\n <p class=\"mt-1 text-sm text-gray-500\">\n Permissions your own applications enforce. Assign them to roles, and they arrive in the\n <code class=\"font-mono\">entitlements</code> claim for clients granted the\n <code class=\"font-mono\">entitlements</code> scope. They grant nothing inside Oluso.\n </p>\n </div>\n <ui-button (clicked)=\"openCreateModal()\">\n <ui-icon name=\"plus\" class=\"h-4 w-4 mr-1\" />\n New Entitlement\n </ui-button>\n </div>\n\n @if (loading()) {\n <div class=\"text-sm text-gray-500 p-4\">Loading entitlements...</div>\n } @else if (entitlements().length === 0) {\n <ui-card>\n <div class=\"text-center py-10\">\n <ui-icon name=\"key\" class=\"mx-auto h-8 w-8 text-gray-300\" />\n <p class=\"mt-2 text-sm text-gray-500\">\n No entitlements yet. Define one to make it selectable when editing a role.\n </p>\n </div>\n </ui-card>\n } @else {\n @for (group of groups(); track group.name) {\n <ui-card [title]=\"group.name\">\n <div class=\"divide-y\">\n @for (entitlement of group.entitlements; track entitlement.id) {\n <div class=\"flex items-start justify-between py-3\">\n <div>\n <div class=\"flex items-center gap-2\">\n <span class=\"text-sm font-medium text-gray-900\">{{ entitlement.displayName }}</span>\n <span class=\"font-mono text-xs text-gray-500\">{{ entitlement.name }}</span>\n @if (!entitlement.enabled) {\n <ui-badge variant=\"warning\">Disabled</ui-badge>\n }\n </div>\n @if (entitlement.description) {\n <p class=\"mt-1 text-sm text-gray-500\">{{ entitlement.description }}</p>\n }\n </div>\n <div class=\"flex items-center gap-2\">\n <button\n type=\"button\"\n class=\"text-gray-500 hover:text-gray-700\"\n title=\"Edit\"\n (click)=\"openEditModal(entitlement)\"\n >\n <ui-icon name=\"pencil\" class=\"h-4 w-4\" />\n </button>\n <button\n type=\"button\"\n class=\"text-red-500 hover:text-red-700\"\n title=\"Delete\"\n (click)=\"openDeleteModal(entitlement)\"\n >\n <ui-icon name=\"trash\" class=\"h-4 w-4\" />\n </button>\n </div>\n </div>\n }\n </div>\n </ui-card>\n }\n }\n</div>\n\n<ui-modal\n [open]=\"showCreateEditModal()\"\n [title]=\"selected() ? 'Edit Entitlement' : 'New Entitlement'\"\n (closed)=\"closeCreateEditModal()\"\n>\n <div class=\"space-y-4\">\n @if (error()) {\n <div class=\"rounded-md bg-red-50 p-3 text-sm text-red-700\">{{ error() }}</div>\n }\n\n <div>\n <label class=\"block text-sm font-medium text-gray-700 mb-1\">Name</label>\n <input\n type=\"text\"\n [(ngModel)]=\"formData.name\"\n [disabled]=\"!!selected()\"\n placeholder=\"invoices.approve\"\n class=\"w-full rounded-md border-gray-300 shadow-sm focus:border-primary-500 focus:ring-primary-500 sm:text-sm disabled:bg-gray-100\"\n />\n <p class=\"mt-1 text-xs text-gray-500\">\n @if (selected()) {\n The name is what your app checks and is already in issued tokens, so it can't be changed.\n Delete and recreate to rename.\n } @else {\n The exact value your application will check. No spaces.\n }\n </p>\n </div>\n\n <div>\n <label class=\"block text-sm font-medium text-gray-700 mb-1\">Display name</label>\n <input\n type=\"text\"\n [(ngModel)]=\"formData.displayName\"\n placeholder=\"Approve invoices\"\n class=\"w-full rounded-md border-gray-300 shadow-sm focus:border-primary-500 focus:ring-primary-500 sm:text-sm\"\n />\n </div>\n\n <div>\n <label class=\"block text-sm font-medium text-gray-700 mb-1\">Description</label>\n <textarea\n [(ngModel)]=\"formData.description\"\n rows=\"2\"\n class=\"w-full rounded-md border-gray-300 shadow-sm focus:border-primary-500 focus:ring-primary-500 sm:text-sm\"\n ></textarea>\n </div>\n\n <div>\n <label class=\"block text-sm font-medium text-gray-700 mb-1\">Group</label>\n <input\n type=\"text\"\n [(ngModel)]=\"formData.group\"\n placeholder=\"Invoices\"\n class=\"w-full rounded-md border-gray-300 shadow-sm focus:border-primary-500 focus:ring-primary-500 sm:text-sm\"\n />\n <p class=\"mt-1 text-xs text-gray-500\">Groups the entitlement in the role editor.</p>\n </div>\n\n <label class=\"flex items-start gap-2 text-sm\">\n <input\n type=\"checkbox\"\n class=\"h-4 w-4 mt-0.5 text-primary-600 focus:ring-primary-500 border-gray-300 rounded\"\n [(ngModel)]=\"formData.enabled\"\n />\n <span>\n Enabled\n <span class=\"block text-gray-500\">\n Turning this off withholds it from new tokens without unassigning it from roles.\n </span>\n </span>\n </label>\n </div>\n\n <div modal-footer class=\"px-6 py-4 border-t border-gray-200 flex justify-end gap-2\">\n <ui-button variant=\"secondary\" (clicked)=\"closeCreateEditModal()\">Cancel</ui-button>\n <ui-button [loading]=\"saving()\" [disabled]=\"!formData.name.trim()\" (clicked)=\"onSave()\">\n {{ selected() ? 'Save' : 'Create' }}\n </ui-button>\n </div>\n</ui-modal>\n\n<ui-modal [open]=\"showDeleteModal()\" title=\"Delete Entitlement\" (closed)=\"closeDeleteModal()\">\n <p class=\"text-sm text-gray-500\">\n Delete <span class=\"font-mono\">{{ selected()?.name }}</span>? It will also be removed from every\n role that grants it, and will stop appearing in tokens.\n </p>\n <div modal-footer class=\"px-6 py-4 border-t border-gray-200 flex justify-end gap-2\">\n <ui-button variant=\"secondary\" (clicked)=\"closeDeleteModal()\">Cancel</ui-button>\n <ui-button variant=\"danger\" [loading]=\"deleting()\" (clicked)=\"confirmDelete()\">Delete</ui-button>\n </div>\n</ui-modal>\n" }]
|
|
146
|
+
}], ctorParameters: () => [] });
|
|
147
|
+
|
|
148
|
+
export { EntitlementsPageComponent };
|
|
149
|
+
//# sourceMappingURL=syndew-oluso-admin-ui-entitlements-page.component-BeZtIUnk.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"syndew-oluso-admin-ui-entitlements-page.component-BeZtIUnk.mjs","sources":["../../../../libs/admin-ui/src/lib/pages/entitlements/entitlements-page.component.ts","../../../../libs/admin-ui/src/lib/pages/entitlements/entitlements-page.component.html"],"sourcesContent":["import { Component, inject, signal } from '@angular/core';\nimport { CommonModule } from '@angular/common';\nimport { FormsModule } from '@angular/forms';\nimport {\n CardComponent,\n ButtonComponent,\n BadgeComponent,\n ModalComponent,\n IconComponent,\n} from '@syndew/oluso-ui-core';\nimport { EntitlementService } from '../../services/entitlement.service';\nimport { EntitlementDto } from '../../models/entitlement.model';\n\ninterface EntitlementFormData {\n name: string;\n displayName: string;\n description: string;\n group: string;\n enabled: boolean;\n}\n\n@Component({\n selector: 'admin-entitlements-page',\n standalone: true,\n imports: [CommonModule, FormsModule, CardComponent, ButtonComponent, BadgeComponent, ModalComponent, IconComponent],\n templateUrl: './entitlements-page.component.html',\n})\nexport class EntitlementsPageComponent {\n private readonly entitlementService = inject(EntitlementService);\n\n readonly loading = signal(true);\n readonly saving = signal(false);\n readonly deleting = signal(false);\n readonly entitlements = signal<EntitlementDto[]>([]);\n readonly error = signal<string | null>(null);\n\n readonly showCreateEditModal = signal(false);\n readonly showDeleteModal = signal(false);\n readonly selected = signal<EntitlementDto | null>(null);\n\n formData: EntitlementFormData = this.emptyForm();\n\n constructor() {\n this.load();\n }\n\n private emptyForm(): EntitlementFormData {\n return { name: '', displayName: '', description: '', group: '', enabled: true };\n }\n\n load(): void {\n this.loading.set(true);\n this.entitlementService.getAll().subscribe({\n next: (entitlements) => {\n this.entitlements.set(entitlements);\n this.loading.set(false);\n },\n error: (err) => {\n console.error('Failed to load entitlements:', err);\n this.loading.set(false);\n },\n });\n }\n\n /** Grouped for display; ungrouped entries fall under \"General\". */\n groups(): { name: string; entitlements: EntitlementDto[] }[] {\n const groups: { name: string; entitlements: EntitlementDto[] }[] = [];\n for (const entitlement of this.entitlements()) {\n const groupName = entitlement.group || 'General';\n const group = groups.find((g) => g.name === groupName);\n if (group) {\n group.entitlements.push(entitlement);\n } else {\n groups.push({ name: groupName, entitlements: [entitlement] });\n }\n }\n return groups;\n }\n\n openCreateModal(): void {\n this.selected.set(null);\n this.formData = this.emptyForm();\n this.error.set(null);\n this.showCreateEditModal.set(true);\n }\n\n openEditModal(entitlement: EntitlementDto): void {\n this.selected.set(entitlement);\n this.formData = {\n name: entitlement.name,\n displayName: entitlement.displayName,\n description: entitlement.description || '',\n group: entitlement.group || '',\n enabled: entitlement.enabled,\n };\n this.error.set(null);\n this.showCreateEditModal.set(true);\n }\n\n closeCreateEditModal(): void {\n this.showCreateEditModal.set(false);\n this.selected.set(null);\n this.error.set(null);\n }\n\n onSave(): void {\n const existing = this.selected();\n this.saving.set(true);\n this.error.set(null);\n\n const done = {\n next: () => {\n this.saving.set(false);\n this.closeCreateEditModal();\n this.load();\n },\n error: (err: { error?: { error?: string } }) => {\n this.error.set(err?.error?.error || 'Save failed');\n this.saving.set(false);\n },\n };\n\n if (existing) {\n // Name is immutable — it's already in issued tokens and on roles.\n this.entitlementService\n .update(existing.id, {\n displayName: this.formData.displayName || this.formData.name,\n description: this.formData.description,\n group: this.formData.group,\n enabled: this.formData.enabled,\n })\n .subscribe(done);\n } else {\n this.entitlementService\n .create({\n name: this.formData.name.trim(),\n displayName: this.formData.displayName || undefined,\n description: this.formData.description || undefined,\n group: this.formData.group || undefined,\n enabled: this.formData.enabled,\n })\n .subscribe(done);\n }\n }\n\n openDeleteModal(entitlement: EntitlementDto): void {\n this.selected.set(entitlement);\n this.showDeleteModal.set(true);\n }\n\n closeDeleteModal(): void {\n this.showDeleteModal.set(false);\n this.selected.set(null);\n }\n\n confirmDelete(): void {\n const entitlement = this.selected();\n if (!entitlement) return;\n\n this.deleting.set(true);\n this.entitlementService.delete(entitlement.id).subscribe({\n next: () => {\n this.deleting.set(false);\n this.closeDeleteModal();\n this.load();\n },\n error: (err) => {\n console.error('Failed to delete entitlement:', err);\n this.deleting.set(false);\n },\n });\n }\n}\n","<div class=\"space-y-6\">\n <div class=\"flex items-start justify-between\">\n <div>\n <h1 class=\"text-2xl font-semibold text-gray-900\">Entitlements</h1>\n <p class=\"mt-1 text-sm text-gray-500\">\n Permissions your own applications enforce. Assign them to roles, and they arrive in the\n <code class=\"font-mono\">entitlements</code> claim for clients granted the\n <code class=\"font-mono\">entitlements</code> scope. They grant nothing inside Oluso.\n </p>\n </div>\n <ui-button (clicked)=\"openCreateModal()\">\n <ui-icon name=\"plus\" class=\"h-4 w-4 mr-1\" />\n New Entitlement\n </ui-button>\n </div>\n\n @if (loading()) {\n <div class=\"text-sm text-gray-500 p-4\">Loading entitlements...</div>\n } @else if (entitlements().length === 0) {\n <ui-card>\n <div class=\"text-center py-10\">\n <ui-icon name=\"key\" class=\"mx-auto h-8 w-8 text-gray-300\" />\n <p class=\"mt-2 text-sm text-gray-500\">\n No entitlements yet. Define one to make it selectable when editing a role.\n </p>\n </div>\n </ui-card>\n } @else {\n @for (group of groups(); track group.name) {\n <ui-card [title]=\"group.name\">\n <div class=\"divide-y\">\n @for (entitlement of group.entitlements; track entitlement.id) {\n <div class=\"flex items-start justify-between py-3\">\n <div>\n <div class=\"flex items-center gap-2\">\n <span class=\"text-sm font-medium text-gray-900\">{{ entitlement.displayName }}</span>\n <span class=\"font-mono text-xs text-gray-500\">{{ entitlement.name }}</span>\n @if (!entitlement.enabled) {\n <ui-badge variant=\"warning\">Disabled</ui-badge>\n }\n </div>\n @if (entitlement.description) {\n <p class=\"mt-1 text-sm text-gray-500\">{{ entitlement.description }}</p>\n }\n </div>\n <div class=\"flex items-center gap-2\">\n <button\n type=\"button\"\n class=\"text-gray-500 hover:text-gray-700\"\n title=\"Edit\"\n (click)=\"openEditModal(entitlement)\"\n >\n <ui-icon name=\"pencil\" class=\"h-4 w-4\" />\n </button>\n <button\n type=\"button\"\n class=\"text-red-500 hover:text-red-700\"\n title=\"Delete\"\n (click)=\"openDeleteModal(entitlement)\"\n >\n <ui-icon name=\"trash\" class=\"h-4 w-4\" />\n </button>\n </div>\n </div>\n }\n </div>\n </ui-card>\n }\n }\n</div>\n\n<ui-modal\n [open]=\"showCreateEditModal()\"\n [title]=\"selected() ? 'Edit Entitlement' : 'New Entitlement'\"\n (closed)=\"closeCreateEditModal()\"\n>\n <div class=\"space-y-4\">\n @if (error()) {\n <div class=\"rounded-md bg-red-50 p-3 text-sm text-red-700\">{{ error() }}</div>\n }\n\n <div>\n <label class=\"block text-sm font-medium text-gray-700 mb-1\">Name</label>\n <input\n type=\"text\"\n [(ngModel)]=\"formData.name\"\n [disabled]=\"!!selected()\"\n placeholder=\"invoices.approve\"\n class=\"w-full rounded-md border-gray-300 shadow-sm focus:border-primary-500 focus:ring-primary-500 sm:text-sm disabled:bg-gray-100\"\n />\n <p class=\"mt-1 text-xs text-gray-500\">\n @if (selected()) {\n The name is what your app checks and is already in issued tokens, so it can't be changed.\n Delete and recreate to rename.\n } @else {\n The exact value your application will check. No spaces.\n }\n </p>\n </div>\n\n <div>\n <label class=\"block text-sm font-medium text-gray-700 mb-1\">Display name</label>\n <input\n type=\"text\"\n [(ngModel)]=\"formData.displayName\"\n placeholder=\"Approve invoices\"\n class=\"w-full rounded-md border-gray-300 shadow-sm focus:border-primary-500 focus:ring-primary-500 sm:text-sm\"\n />\n </div>\n\n <div>\n <label class=\"block text-sm font-medium text-gray-700 mb-1\">Description</label>\n <textarea\n [(ngModel)]=\"formData.description\"\n rows=\"2\"\n class=\"w-full rounded-md border-gray-300 shadow-sm focus:border-primary-500 focus:ring-primary-500 sm:text-sm\"\n ></textarea>\n </div>\n\n <div>\n <label class=\"block text-sm font-medium text-gray-700 mb-1\">Group</label>\n <input\n type=\"text\"\n [(ngModel)]=\"formData.group\"\n placeholder=\"Invoices\"\n class=\"w-full rounded-md border-gray-300 shadow-sm focus:border-primary-500 focus:ring-primary-500 sm:text-sm\"\n />\n <p class=\"mt-1 text-xs text-gray-500\">Groups the entitlement in the role editor.</p>\n </div>\n\n <label class=\"flex items-start gap-2 text-sm\">\n <input\n type=\"checkbox\"\n class=\"h-4 w-4 mt-0.5 text-primary-600 focus:ring-primary-500 border-gray-300 rounded\"\n [(ngModel)]=\"formData.enabled\"\n />\n <span>\n Enabled\n <span class=\"block text-gray-500\">\n Turning this off withholds it from new tokens without unassigning it from roles.\n </span>\n </span>\n </label>\n </div>\n\n <div modal-footer class=\"px-6 py-4 border-t border-gray-200 flex justify-end gap-2\">\n <ui-button variant=\"secondary\" (clicked)=\"closeCreateEditModal()\">Cancel</ui-button>\n <ui-button [loading]=\"saving()\" [disabled]=\"!formData.name.trim()\" (clicked)=\"onSave()\">\n {{ selected() ? 'Save' : 'Create' }}\n </ui-button>\n </div>\n</ui-modal>\n\n<ui-modal [open]=\"showDeleteModal()\" title=\"Delete Entitlement\" (closed)=\"closeDeleteModal()\">\n <p class=\"text-sm text-gray-500\">\n Delete <span class=\"font-mono\">{{ selected()?.name }}</span>? It will also be removed from every\n role that grants it, and will stop appearing in tokens.\n </p>\n <div modal-footer class=\"px-6 py-4 border-t border-gray-200 flex justify-end gap-2\">\n <ui-button variant=\"secondary\" (clicked)=\"closeDeleteModal()\">Cancel</ui-button>\n <ui-button variant=\"danger\" [loading]=\"deleting()\" (clicked)=\"confirmDelete()\">Delete</ui-button>\n </div>\n</ui-modal>\n"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
import * as i0 from '@angular/core';
|
|
2
|
+
import { inject, signal, Component } from '@angular/core';
|
|
3
|
+
import * as i1 from '@angular/common';
|
|
4
|
+
import { CommonModule } from '@angular/common';
|
|
5
|
+
import { CardComponent, BadgeComponent, IconComponent, LocaleDatePipe } from '@syndew/oluso-ui-core';
|
|
6
|
+
import { f as ApiService } from './syndew-oluso-admin-ui-syndew-oluso-admin-ui-BVs_bAxm.mjs';
|
|
7
|
+
|
|
8
|
+
class LicenseStatusPageComponent {
|
|
9
|
+
api = inject(ApiService);
|
|
10
|
+
loading = signal(true, ...(ngDevMode ? [{ debugName: "loading" }] : /* istanbul ignore next */ []));
|
|
11
|
+
license = signal(null, ...(ngDevMode ? [{ debugName: "license" }] : /* istanbul ignore next */ []));
|
|
12
|
+
ngOnInit() {
|
|
13
|
+
this.api.get('/settings/license').subscribe({
|
|
14
|
+
next: (status) => {
|
|
15
|
+
this.license.set(status);
|
|
16
|
+
this.loading.set(false);
|
|
17
|
+
},
|
|
18
|
+
error: (err) => {
|
|
19
|
+
console.error('Failed to load license status:', err);
|
|
20
|
+
this.loading.set(false);
|
|
21
|
+
},
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.25", ngImport: i0, type: LicenseStatusPageComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
25
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.25", type: LicenseStatusPageComponent, isStandalone: true, selector: "admin-license-status-page", ngImport: i0, template: `
|
|
26
|
+
<div class="page-header">
|
|
27
|
+
<h1>License Status</h1>
|
|
28
|
+
</div>
|
|
29
|
+
|
|
30
|
+
@if (loading()) {
|
|
31
|
+
<ui-card>
|
|
32
|
+
<div class="loading-placeholder">Loading license information...</div>
|
|
33
|
+
</ui-card>
|
|
34
|
+
} @else if (license(); as lic) {
|
|
35
|
+
<!-- License Overview -->
|
|
36
|
+
<ui-card>
|
|
37
|
+
<div class="license-overview">
|
|
38
|
+
<div class="license-tier">
|
|
39
|
+
<span class="tier-label">License Tier</span>
|
|
40
|
+
<span class="tier-value">{{ lic.tier | titlecase }}</span>
|
|
41
|
+
@if (lic.isTrial) {
|
|
42
|
+
<ui-badge variant="warning">Trial</ui-badge>
|
|
43
|
+
}
|
|
44
|
+
@if (lic.isExpired) {
|
|
45
|
+
<ui-badge variant="danger">Expired</ui-badge>
|
|
46
|
+
} @else if (lic.isValid) {
|
|
47
|
+
<ui-badge variant="success">Active</ui-badge>
|
|
48
|
+
}
|
|
49
|
+
</div>
|
|
50
|
+
|
|
51
|
+
<div class="license-details">
|
|
52
|
+
@if (lic.expiresAt) {
|
|
53
|
+
<div class="detail-item">
|
|
54
|
+
<span class="detail-label">Expires</span>
|
|
55
|
+
<span class="detail-value">
|
|
56
|
+
{{ lic.expiresAt | localeDate:'mediumDate' }}
|
|
57
|
+
@if (!lic.isExpired && lic.daysUntilExpiration <= 30) {
|
|
58
|
+
<span class="expiry-warning">({{ lic.daysUntilExpiration }} days remaining)</span>
|
|
59
|
+
}
|
|
60
|
+
</span>
|
|
61
|
+
</div>
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
<div class="detail-item">
|
|
65
|
+
<span class="detail-label">Max Tenants</span>
|
|
66
|
+
<span class="detail-value">{{ lic.maxTenants === -1 ? 'Unlimited' : lic.maxTenants }}</span>
|
|
67
|
+
</div>
|
|
68
|
+
</div>
|
|
69
|
+
</div>
|
|
70
|
+
</ui-card>
|
|
71
|
+
|
|
72
|
+
<!-- Licensed Features -->
|
|
73
|
+
<ui-card>
|
|
74
|
+
<h2 class="section-title">Licensed Features</h2>
|
|
75
|
+
@if (lic.features.length === 0) {
|
|
76
|
+
<p class="empty-state">No features licensed</p>
|
|
77
|
+
} @else {
|
|
78
|
+
<div class="feature-grid">
|
|
79
|
+
@for (feature of lic.features; track feature) {
|
|
80
|
+
<div class="feature-item">
|
|
81
|
+
<ui-icon name="check-circle" class="feature-icon licensed"></ui-icon>
|
|
82
|
+
<span>{{ feature }}</span>
|
|
83
|
+
</div>
|
|
84
|
+
}
|
|
85
|
+
</div>
|
|
86
|
+
}
|
|
87
|
+
</ui-card>
|
|
88
|
+
|
|
89
|
+
<!-- Add-Ons -->
|
|
90
|
+
@if (lic.addOns.length > 0) {
|
|
91
|
+
<ui-card>
|
|
92
|
+
<h2 class="section-title">Add-Ons</h2>
|
|
93
|
+
<div class="feature-grid">
|
|
94
|
+
@for (addon of lic.addOns; track addon) {
|
|
95
|
+
<div class="feature-item">
|
|
96
|
+
<ui-icon name="puzzle-piece" class="feature-icon addon"></ui-icon>
|
|
97
|
+
<span>{{ addon }}</span>
|
|
98
|
+
</div>
|
|
99
|
+
}
|
|
100
|
+
</div>
|
|
101
|
+
</ui-card>
|
|
102
|
+
}
|
|
103
|
+
} @else {
|
|
104
|
+
<ui-card>
|
|
105
|
+
<div class="empty-state">
|
|
106
|
+
<p>No license information available.</p>
|
|
107
|
+
<p class="empty-hint">Contact support to obtain a license key.</p>
|
|
108
|
+
</div>
|
|
109
|
+
</ui-card>
|
|
110
|
+
}
|
|
111
|
+
`, isInline: true, styles: [".page-header{margin-bottom:1.5rem}.page-header h1{font-size:1.5rem;font-weight:600}.license-overview{display:flex;flex-direction:column;gap:1.5rem}.license-tier{display:flex;align-items:center;gap:.75rem}.tier-label{font-size:.875rem;color:var(--text-secondary)}.tier-value{font-size:1.5rem;font-weight:700}.license-details{display:grid;grid-template-columns:repeat(auto-fill,minmax(200px,1fr));gap:1rem}.detail-item{display:flex;flex-direction:column;gap:.25rem}.detail-label{font-size:.75rem;color:var(--text-secondary);text-transform:uppercase;letter-spacing:.05em}.detail-value{font-size:.875rem;font-weight:500}.expiry-warning{color:var(--color-warning);font-size:.75rem}.section-title{font-size:1rem;font-weight:600;margin-bottom:1rem}.feature-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(180px,1fr));gap:.5rem}.feature-item{display:flex;align-items:center;gap:.5rem;padding:.5rem;border-radius:.375rem;background:var(--bg-secondary);font-size:.875rem}.feature-icon{width:1rem;height:1rem;flex-shrink:0}.feature-icon.licensed{color:var(--color-success)}.feature-icon.addon{color:var(--color-primary)}.loading-placeholder{padding:2rem;text-align:center;color:var(--text-secondary)}.empty-state{text-align:center;padding:2rem;color:var(--text-secondary)}.empty-hint{font-size:.875rem;margin-top:.5rem}ui-card{display:block;margin-bottom:1rem}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "component", type: CardComponent, selector: "ui-card", inputs: ["title", "subtitle", "padding", "shadow"] }, { kind: "component", type: BadgeComponent, selector: "ui-badge", inputs: ["variant", "size"] }, { kind: "component", type: IconComponent, selector: "ui-icon", inputs: ["name", "strokeWidth"] }, { kind: "pipe", type: i1.TitleCasePipe, name: "titlecase" }, { kind: "pipe", type: LocaleDatePipe, name: "localeDate" }] });
|
|
112
|
+
}
|
|
113
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.25", ngImport: i0, type: LicenseStatusPageComponent, decorators: [{
|
|
114
|
+
type: Component,
|
|
115
|
+
args: [{ selector: 'admin-license-status-page', standalone: true, imports: [CommonModule, CardComponent, BadgeComponent, IconComponent, LocaleDatePipe], template: `
|
|
116
|
+
<div class="page-header">
|
|
117
|
+
<h1>License Status</h1>
|
|
118
|
+
</div>
|
|
119
|
+
|
|
120
|
+
@if (loading()) {
|
|
121
|
+
<ui-card>
|
|
122
|
+
<div class="loading-placeholder">Loading license information...</div>
|
|
123
|
+
</ui-card>
|
|
124
|
+
} @else if (license(); as lic) {
|
|
125
|
+
<!-- License Overview -->
|
|
126
|
+
<ui-card>
|
|
127
|
+
<div class="license-overview">
|
|
128
|
+
<div class="license-tier">
|
|
129
|
+
<span class="tier-label">License Tier</span>
|
|
130
|
+
<span class="tier-value">{{ lic.tier | titlecase }}</span>
|
|
131
|
+
@if (lic.isTrial) {
|
|
132
|
+
<ui-badge variant="warning">Trial</ui-badge>
|
|
133
|
+
}
|
|
134
|
+
@if (lic.isExpired) {
|
|
135
|
+
<ui-badge variant="danger">Expired</ui-badge>
|
|
136
|
+
} @else if (lic.isValid) {
|
|
137
|
+
<ui-badge variant="success">Active</ui-badge>
|
|
138
|
+
}
|
|
139
|
+
</div>
|
|
140
|
+
|
|
141
|
+
<div class="license-details">
|
|
142
|
+
@if (lic.expiresAt) {
|
|
143
|
+
<div class="detail-item">
|
|
144
|
+
<span class="detail-label">Expires</span>
|
|
145
|
+
<span class="detail-value">
|
|
146
|
+
{{ lic.expiresAt | localeDate:'mediumDate' }}
|
|
147
|
+
@if (!lic.isExpired && lic.daysUntilExpiration <= 30) {
|
|
148
|
+
<span class="expiry-warning">({{ lic.daysUntilExpiration }} days remaining)</span>
|
|
149
|
+
}
|
|
150
|
+
</span>
|
|
151
|
+
</div>
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
<div class="detail-item">
|
|
155
|
+
<span class="detail-label">Max Tenants</span>
|
|
156
|
+
<span class="detail-value">{{ lic.maxTenants === -1 ? 'Unlimited' : lic.maxTenants }}</span>
|
|
157
|
+
</div>
|
|
158
|
+
</div>
|
|
159
|
+
</div>
|
|
160
|
+
</ui-card>
|
|
161
|
+
|
|
162
|
+
<!-- Licensed Features -->
|
|
163
|
+
<ui-card>
|
|
164
|
+
<h2 class="section-title">Licensed Features</h2>
|
|
165
|
+
@if (lic.features.length === 0) {
|
|
166
|
+
<p class="empty-state">No features licensed</p>
|
|
167
|
+
} @else {
|
|
168
|
+
<div class="feature-grid">
|
|
169
|
+
@for (feature of lic.features; track feature) {
|
|
170
|
+
<div class="feature-item">
|
|
171
|
+
<ui-icon name="check-circle" class="feature-icon licensed"></ui-icon>
|
|
172
|
+
<span>{{ feature }}</span>
|
|
173
|
+
</div>
|
|
174
|
+
}
|
|
175
|
+
</div>
|
|
176
|
+
}
|
|
177
|
+
</ui-card>
|
|
178
|
+
|
|
179
|
+
<!-- Add-Ons -->
|
|
180
|
+
@if (lic.addOns.length > 0) {
|
|
181
|
+
<ui-card>
|
|
182
|
+
<h2 class="section-title">Add-Ons</h2>
|
|
183
|
+
<div class="feature-grid">
|
|
184
|
+
@for (addon of lic.addOns; track addon) {
|
|
185
|
+
<div class="feature-item">
|
|
186
|
+
<ui-icon name="puzzle-piece" class="feature-icon addon"></ui-icon>
|
|
187
|
+
<span>{{ addon }}</span>
|
|
188
|
+
</div>
|
|
189
|
+
}
|
|
190
|
+
</div>
|
|
191
|
+
</ui-card>
|
|
192
|
+
}
|
|
193
|
+
} @else {
|
|
194
|
+
<ui-card>
|
|
195
|
+
<div class="empty-state">
|
|
196
|
+
<p>No license information available.</p>
|
|
197
|
+
<p class="empty-hint">Contact support to obtain a license key.</p>
|
|
198
|
+
</div>
|
|
199
|
+
</ui-card>
|
|
200
|
+
}
|
|
201
|
+
`, styles: [".page-header{margin-bottom:1.5rem}.page-header h1{font-size:1.5rem;font-weight:600}.license-overview{display:flex;flex-direction:column;gap:1.5rem}.license-tier{display:flex;align-items:center;gap:.75rem}.tier-label{font-size:.875rem;color:var(--text-secondary)}.tier-value{font-size:1.5rem;font-weight:700}.license-details{display:grid;grid-template-columns:repeat(auto-fill,minmax(200px,1fr));gap:1rem}.detail-item{display:flex;flex-direction:column;gap:.25rem}.detail-label{font-size:.75rem;color:var(--text-secondary);text-transform:uppercase;letter-spacing:.05em}.detail-value{font-size:.875rem;font-weight:500}.expiry-warning{color:var(--color-warning);font-size:.75rem}.section-title{font-size:1rem;font-weight:600;margin-bottom:1rem}.feature-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(180px,1fr));gap:.5rem}.feature-item{display:flex;align-items:center;gap:.5rem;padding:.5rem;border-radius:.375rem;background:var(--bg-secondary);font-size:.875rem}.feature-icon{width:1rem;height:1rem;flex-shrink:0}.feature-icon.licensed{color:var(--color-success)}.feature-icon.addon{color:var(--color-primary)}.loading-placeholder{padding:2rem;text-align:center;color:var(--text-secondary)}.empty-state{text-align:center;padding:2rem;color:var(--text-secondary)}.empty-hint{font-size:.875rem;margin-top:.5rem}ui-card{display:block;margin-bottom:1rem}\n"] }]
|
|
202
|
+
}] });
|
|
203
|
+
|
|
204
|
+
export { LicenseStatusPageComponent };
|
|
205
|
+
//# sourceMappingURL=syndew-oluso-admin-ui-license-status-page.component-JQmj_qhz.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"syndew-oluso-admin-ui-license-status-page.component-JQmj_qhz.mjs","sources":["../../../../libs/admin-ui/src/lib/pages/settings/license-status-page.component.ts"],"sourcesContent":["import { Component, inject, signal, OnInit } from '@angular/core';\nimport { CommonModule } from '@angular/common';\nimport { CardComponent, BadgeComponent, IconComponent, LocaleDatePipe } from '@syndew/oluso-ui-core';\nimport { ApiService } from '../../services/api.service';\n\ninterface LicenseStatus {\n tier: string;\n isValid: boolean;\n expiresAt: string | null;\n isExpired: boolean;\n isTrial: boolean;\n daysUntilExpiration: number;\n maxTenants: number;\n features: string[];\n addOns: string[];\n message: string | null;\n}\n\n@Component({\n selector: 'admin-license-status-page',\n standalone: true,\n imports: [CommonModule, CardComponent, BadgeComponent, IconComponent, LocaleDatePipe],\n template: `\n <div class=\"page-header\">\n <h1>License Status</h1>\n </div>\n\n @if (loading()) {\n <ui-card>\n <div class=\"loading-placeholder\">Loading license information...</div>\n </ui-card>\n } @else if (license(); as lic) {\n <!-- License Overview -->\n <ui-card>\n <div class=\"license-overview\">\n <div class=\"license-tier\">\n <span class=\"tier-label\">License Tier</span>\n <span class=\"tier-value\">{{ lic.tier | titlecase }}</span>\n @if (lic.isTrial) {\n <ui-badge variant=\"warning\">Trial</ui-badge>\n }\n @if (lic.isExpired) {\n <ui-badge variant=\"danger\">Expired</ui-badge>\n } @else if (lic.isValid) {\n <ui-badge variant=\"success\">Active</ui-badge>\n }\n </div>\n\n <div class=\"license-details\">\n @if (lic.expiresAt) {\n <div class=\"detail-item\">\n <span class=\"detail-label\">Expires</span>\n <span class=\"detail-value\">\n {{ lic.expiresAt | localeDate:'mediumDate' }}\n @if (!lic.isExpired && lic.daysUntilExpiration <= 30) {\n <span class=\"expiry-warning\">({{ lic.daysUntilExpiration }} days remaining)</span>\n }\n </span>\n </div>\n }\n\n <div class=\"detail-item\">\n <span class=\"detail-label\">Max Tenants</span>\n <span class=\"detail-value\">{{ lic.maxTenants === -1 ? 'Unlimited' : lic.maxTenants }}</span>\n </div>\n </div>\n </div>\n </ui-card>\n\n <!-- Licensed Features -->\n <ui-card>\n <h2 class=\"section-title\">Licensed Features</h2>\n @if (lic.features.length === 0) {\n <p class=\"empty-state\">No features licensed</p>\n } @else {\n <div class=\"feature-grid\">\n @for (feature of lic.features; track feature) {\n <div class=\"feature-item\">\n <ui-icon name=\"check-circle\" class=\"feature-icon licensed\"></ui-icon>\n <span>{{ feature }}</span>\n </div>\n }\n </div>\n }\n </ui-card>\n\n <!-- Add-Ons -->\n @if (lic.addOns.length > 0) {\n <ui-card>\n <h2 class=\"section-title\">Add-Ons</h2>\n <div class=\"feature-grid\">\n @for (addon of lic.addOns; track addon) {\n <div class=\"feature-item\">\n <ui-icon name=\"puzzle-piece\" class=\"feature-icon addon\"></ui-icon>\n <span>{{ addon }}</span>\n </div>\n }\n </div>\n </ui-card>\n }\n } @else {\n <ui-card>\n <div class=\"empty-state\">\n <p>No license information available.</p>\n <p class=\"empty-hint\">Contact support to obtain a license key.</p>\n </div>\n </ui-card>\n }\n `,\n styles: [`\n .page-header { margin-bottom: 1.5rem; }\n .page-header h1 { font-size: 1.5rem; font-weight: 600; }\n\n .license-overview { display: flex; flex-direction: column; gap: 1.5rem; }\n .license-tier { display: flex; align-items: center; gap: 0.75rem; }\n .tier-label { font-size: 0.875rem; color: var(--text-secondary); }\n .tier-value { font-size: 1.5rem; font-weight: 700; }\n\n .license-details { display: grid; grid-template-columns: repeat(auto-fill, minmax(200px, 1fr)); gap: 1rem; }\n .detail-item { display: flex; flex-direction: column; gap: 0.25rem; }\n .detail-label { font-size: 0.75rem; color: var(--text-secondary); text-transform: uppercase; letter-spacing: 0.05em; }\n .detail-value { font-size: 0.875rem; font-weight: 500; }\n .expiry-warning { color: var(--color-warning); font-size: 0.75rem; }\n\n .section-title { font-size: 1rem; font-weight: 600; margin-bottom: 1rem; }\n .feature-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(180px, 1fr)); gap: 0.5rem; }\n .feature-item { display: flex; align-items: center; gap: 0.5rem; padding: 0.5rem; border-radius: 0.375rem; background: var(--bg-secondary); font-size: 0.875rem; }\n .feature-icon { width: 1rem; height: 1rem; flex-shrink: 0; }\n .feature-icon.licensed { color: var(--color-success); }\n .feature-icon.addon { color: var(--color-primary); }\n\n .loading-placeholder { padding: 2rem; text-align: center; color: var(--text-secondary); }\n .empty-state { text-align: center; padding: 2rem; color: var(--text-secondary); }\n .empty-hint { font-size: 0.875rem; margin-top: 0.5rem; }\n\n ui-card { display: block; margin-bottom: 1rem; }\n `],\n})\nexport class LicenseStatusPageComponent implements OnInit {\n private readonly api = inject(ApiService);\n\n readonly loading = signal(true);\n readonly license = signal<LicenseStatus | null>(null);\n\n ngOnInit(): void {\n this.api.get<LicenseStatus>('/settings/license').subscribe({\n next: (status) => {\n this.license.set(status);\n this.loading.set(false);\n },\n error: (err) => {\n console.error('Failed to load license status:', err);\n this.loading.set(false);\n },\n });\n }\n}\n"],"names":[],"mappings":";;;;;;;MA0Ia,0BAA0B,CAAA;AACpB,IAAA,GAAG,GAAG,MAAM,CAAC,UAAU,CAAC;AAEhC,IAAA,OAAO,GAAG,MAAM,CAAC,IAAI,8EAAC;AACtB,IAAA,OAAO,GAAG,MAAM,CAAuB,IAAI,8EAAC;IAErD,QAAQ,GAAA;QACN,IAAI,CAAC,GAAG,CAAC,GAAG,CAAgB,mBAAmB,CAAC,CAAC,SAAS,CAAC;AACzD,YAAA,IAAI,EAAE,CAAC,MAAM,KAAI;AACf,gBAAA,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,MAAM,CAAC;AACxB,gBAAA,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC;YACzB,CAAC;AACD,YAAA,KAAK,EAAE,CAAC,GAAG,KAAI;AACb,gBAAA,OAAO,CAAC,KAAK,CAAC,gCAAgC,EAAE,GAAG,CAAC;AACpD,gBAAA,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC;YACzB,CAAC;AACF,SAAA,CAAC;IACJ;wGAjBW,0BAA0B,EAAA,IAAA,EAAA,EAAA,EAAA,MAAA,EAAA,EAAA,CAAA,eAAA,CAAA,SAAA,EAAA,CAAA;AAA1B,IAAA,OAAA,IAAA,GAAA,EAAA,CAAA,oBAAA,CAAA,EAAA,UAAA,EAAA,QAAA,EAAA,OAAA,EAAA,SAAA,EAAA,IAAA,EAAA,0BAA0B,EAAA,YAAA,EAAA,IAAA,EAAA,QAAA,EAAA,2BAAA,EAAA,QAAA,EAAA,EAAA,EAAA,QAAA,EApH3B;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAsFT,EAAA,QAAA,EAAA,IAAA,EAAA,MAAA,EAAA,CAAA,y1CAAA,CAAA,EAAA,YAAA,EAAA,CAAA,EAAA,IAAA,EAAA,UAAA,EAAA,IAAA,EAvFS,YAAY,+BAAE,aAAa,EAAA,QAAA,EAAA,SAAA,EAAA,MAAA,EAAA,CAAA,OAAA,EAAA,UAAA,EAAA,SAAA,EAAA,QAAA,CAAA,EAAA,EAAA,EAAA,IAAA,EAAA,WAAA,EAAA,IAAA,EAAE,cAAc,EAAA,QAAA,EAAA,UAAA,EAAA,MAAA,EAAA,CAAA,SAAA,EAAA,MAAA,CAAA,EAAA,EAAA,EAAA,IAAA,EAAA,WAAA,EAAA,IAAA,EAAE,aAAa,6IAAE,cAAc,EAAA,IAAA,EAAA,YAAA,EAAA,CAAA,EAAA,CAAA;;4FAqHzE,0BAA0B,EAAA,UAAA,EAAA,CAAA;kBAxHtC,SAAS;AACE,YAAA,IAAA,EAAA,CAAA,EAAA,QAAA,EAAA,2BAA2B,EAAA,UAAA,EACzB,IAAI,EAAA,OAAA,EACP,CAAC,YAAY,EAAE,aAAa,EAAE,cAAc,EAAE,aAAa,EAAE,cAAc,CAAC,EAAA,QAAA,EAC3E;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAsFT,EAAA,CAAA,EAAA,MAAA,EAAA,CAAA,y1CAAA,CAAA,EAAA;;;;;"}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import * as i0 from '@angular/core';
|
|
2
|
+
import { inject, signal, Component } from '@angular/core';
|
|
3
|
+
import { CommonModule } from '@angular/common';
|
|
4
|
+
import { Router } from '@angular/router';
|
|
5
|
+
import { CardComponent, ButtonComponent, BadgeComponent, IconComponent, ConfirmDialogComponent } from '@syndew/oluso-ui-core';
|
|
6
|
+
import { e as PlatformClientService } from './syndew-oluso-admin-ui-syndew-oluso-admin-ui-BVs_bAxm.mjs';
|
|
7
|
+
|
|
8
|
+
class PlatformClientsPageComponent {
|
|
9
|
+
router = inject(Router);
|
|
10
|
+
platformClientService = inject(PlatformClientService);
|
|
11
|
+
loading = signal(true, ...(ngDevMode ? [{ debugName: "loading" }] : /* istanbul ignore next */ []));
|
|
12
|
+
clients = signal([], ...(ngDevMode ? [{ debugName: "clients" }] : /* istanbul ignore next */ []));
|
|
13
|
+
// Delete confirmation
|
|
14
|
+
showDeleteConfirm = signal(false, ...(ngDevMode ? [{ debugName: "showDeleteConfirm" }] : /* istanbul ignore next */ []));
|
|
15
|
+
deletingClient = signal(null, ...(ngDevMode ? [{ debugName: "deletingClient" }] : /* istanbul ignore next */ []));
|
|
16
|
+
saving = signal(false, ...(ngDevMode ? [{ debugName: "saving" }] : /* istanbul ignore next */ []));
|
|
17
|
+
constructor() {
|
|
18
|
+
this.loadClients();
|
|
19
|
+
}
|
|
20
|
+
loadClients() {
|
|
21
|
+
this.loading.set(true);
|
|
22
|
+
this.platformClientService.getAll().subscribe({
|
|
23
|
+
next: (clients) => {
|
|
24
|
+
this.clients.set(clients);
|
|
25
|
+
this.loading.set(false);
|
|
26
|
+
},
|
|
27
|
+
error: (error) => {
|
|
28
|
+
console.error('Failed to load platform clients:', error);
|
|
29
|
+
this.loading.set(false);
|
|
30
|
+
},
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
navigateToCreate() {
|
|
34
|
+
this.router.navigate(['/platform/clients/new']);
|
|
35
|
+
}
|
|
36
|
+
navigateToEdit(client) {
|
|
37
|
+
this.router.navigate(['/platform/clients', client.id]);
|
|
38
|
+
}
|
|
39
|
+
confirmDelete(client) {
|
|
40
|
+
this.deletingClient.set(client);
|
|
41
|
+
this.showDeleteConfirm.set(true);
|
|
42
|
+
}
|
|
43
|
+
cancelDelete() {
|
|
44
|
+
this.showDeleteConfirm.set(false);
|
|
45
|
+
this.deletingClient.set(null);
|
|
46
|
+
}
|
|
47
|
+
deleteClient() {
|
|
48
|
+
const client = this.deletingClient();
|
|
49
|
+
if (!client)
|
|
50
|
+
return;
|
|
51
|
+
this.saving.set(true);
|
|
52
|
+
this.platformClientService.delete(client.id).subscribe({
|
|
53
|
+
next: () => {
|
|
54
|
+
this.showDeleteConfirm.set(false);
|
|
55
|
+
this.deletingClient.set(null);
|
|
56
|
+
this.saving.set(false);
|
|
57
|
+
this.loadClients();
|
|
58
|
+
},
|
|
59
|
+
error: (error) => {
|
|
60
|
+
console.error('Failed to delete client:', error);
|
|
61
|
+
this.saving.set(false);
|
|
62
|
+
},
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
formatDate(dateString) {
|
|
66
|
+
return new Date(dateString).toLocaleDateString();
|
|
67
|
+
}
|
|
68
|
+
formatGrantTypes(grantTypes) {
|
|
69
|
+
return grantTypes?.join(', ') || '-';
|
|
70
|
+
}
|
|
71
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.25", ngImport: i0, type: PlatformClientsPageComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
72
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.25", type: PlatformClientsPageComponent, isStandalone: true, selector: "admin-platform-clients-page", ngImport: i0, template: "<div class=\"space-y-6\">\n <!-- Page header -->\n <div class=\"flex items-center justify-between\">\n <div>\n <h1 class=\"text-2xl font-bold text-gray-900\">Platform Clients</h1>\n <p class=\"mt-1 text-sm text-gray-500\">\n System-level OAuth clients available across all tenants\n </p>\n </div>\n <ui-button (clicked)=\"navigateToCreate()\">\n <ui-icon name=\"plus\" class=\"h-4 w-4 mr-2\" />\n Add Platform Client\n </ui-button>\n </div>\n\n <!-- Platform clients table -->\n <ui-card [padding]=\"false\">\n <div class=\"overflow-hidden\">\n <table class=\"min-w-full divide-y divide-gray-200\">\n <thead class=\"bg-gray-50\">\n <tr>\n <th scope=\"col\" class=\"px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider\">\n Client ID\n </th>\n <th scope=\"col\" class=\"px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider\">\n Name\n </th>\n <th scope=\"col\" class=\"px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider\">\n Status\n </th>\n <th scope=\"col\" class=\"px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider\">\n Grant Types\n </th>\n <th scope=\"col\" class=\"px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider\">\n Created\n </th>\n <th scope=\"col\" class=\"px-6 py-3 text-right text-xs font-medium text-gray-500 uppercase tracking-wider\">\n Actions\n </th>\n </tr>\n </thead>\n <tbody class=\"bg-white divide-y divide-gray-200\">\n @if (loading()) {\n <tr>\n <td colspan=\"6\" class=\"px-6 py-12 text-center\">\n <div class=\"flex justify-center\">\n <svg\n class=\"animate-spin h-8 w-8 text-primary-600\"\n xmlns=\"http://www.w3.org/2000/svg\"\n fill=\"none\"\n viewBox=\"0 0 24 24\"\n >\n <circle\n class=\"opacity-25\"\n cx=\"12\"\n cy=\"12\"\n r=\"10\"\n stroke=\"currentColor\"\n stroke-width=\"4\"\n ></circle>\n <path\n class=\"opacity-75\"\n fill=\"currentColor\"\n d=\"M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z\"\n ></path>\n </svg>\n </div>\n </td>\n </tr>\n } @else if (clients().length === 0) {\n <tr>\n <td colspan=\"6\" class=\"px-6 py-12 text-center text-sm text-gray-500\">\n No platform clients configured yet\n </td>\n </tr>\n } @else {\n @for (client of clients(); track client.clientId) {\n <tr class=\"hover:bg-gray-50 cursor-pointer\" (click)=\"navigateToEdit(client)\">\n <td class=\"px-6 py-4 whitespace-nowrap text-sm font-medium text-gray-900\">\n {{ client.clientId }}\n </td>\n <td class=\"px-6 py-4 whitespace-nowrap text-sm text-gray-500\">\n {{ client.clientName || '-' }}\n </td>\n <td class=\"px-6 py-4 whitespace-nowrap\">\n <ui-badge [variant]=\"client.enabled ? 'success' : 'danger'\">\n {{ client.enabled ? 'Enabled' : 'Disabled' }}\n </ui-badge>\n </td>\n <td class=\"px-6 py-4 whitespace-nowrap text-xs text-gray-500\">\n {{ formatGrantTypes(client.allowedGrantTypes) }}\n </td>\n <td class=\"px-6 py-4 whitespace-nowrap text-sm text-gray-500\">\n {{ formatDate(client.created) }}\n </td>\n <td class=\"px-6 py-4 whitespace-nowrap text-right text-sm font-medium space-x-2\" (click)=\"$event.stopPropagation()\">\n <ui-button variant=\"secondary\" size=\"sm\" (clicked)=\"navigateToEdit(client)\">\n Edit\n </ui-button>\n <ui-button variant=\"danger\" size=\"sm\" (clicked)=\"confirmDelete(client)\">\n Delete\n </ui-button>\n </td>\n </tr>\n }\n }\n </tbody>\n </table>\n </div>\n </ui-card>\n</div>\n\n<!-- Delete Confirmation -->\n<ui-confirm-dialog\n [open]=\"showDeleteConfirm()\"\n title=\"Delete Platform Client\"\n [message]=\"'Are you sure you want to delete the platform client \\'' + (deletingClient()?.clientId ?? '') + '\\'? This action cannot be undone.'\"\n confirmText=\"Delete\"\n confirmVariant=\"danger\"\n [loading]=\"saving()\"\n (confirmed)=\"deleteClient()\"\n (cancelled)=\"cancelDelete()\"\n/>\n", dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "component", type: CardComponent, selector: "ui-card", inputs: ["title", "subtitle", "padding", "shadow"] }, { kind: "component", type: ButtonComponent, selector: "ui-button", inputs: ["variant", "size", "disabled", "loading", "type", "fullWidth"], outputs: ["clicked"] }, { kind: "component", type: BadgeComponent, selector: "ui-badge", inputs: ["variant", "size"] }, { kind: "component", type: IconComponent, selector: "ui-icon", inputs: ["name", "strokeWidth"] }, { kind: "component", type: ConfirmDialogComponent, selector: "ui-confirm-dialog", inputs: ["open", "title", "message", "confirmText", "cancelText", "variant", "loading", "closeOnBackdrop", "closeOnEscape"], outputs: ["confirmed", "cancelled"] }] });
|
|
73
|
+
}
|
|
74
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.25", ngImport: i0, type: PlatformClientsPageComponent, decorators: [{
|
|
75
|
+
type: Component,
|
|
76
|
+
args: [{ selector: 'admin-platform-clients-page', standalone: true, imports: [CommonModule, CardComponent, ButtonComponent, BadgeComponent, IconComponent, ConfirmDialogComponent], template: "<div class=\"space-y-6\">\n <!-- Page header -->\n <div class=\"flex items-center justify-between\">\n <div>\n <h1 class=\"text-2xl font-bold text-gray-900\">Platform Clients</h1>\n <p class=\"mt-1 text-sm text-gray-500\">\n System-level OAuth clients available across all tenants\n </p>\n </div>\n <ui-button (clicked)=\"navigateToCreate()\">\n <ui-icon name=\"plus\" class=\"h-4 w-4 mr-2\" />\n Add Platform Client\n </ui-button>\n </div>\n\n <!-- Platform clients table -->\n <ui-card [padding]=\"false\">\n <div class=\"overflow-hidden\">\n <table class=\"min-w-full divide-y divide-gray-200\">\n <thead class=\"bg-gray-50\">\n <tr>\n <th scope=\"col\" class=\"px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider\">\n Client ID\n </th>\n <th scope=\"col\" class=\"px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider\">\n Name\n </th>\n <th scope=\"col\" class=\"px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider\">\n Status\n </th>\n <th scope=\"col\" class=\"px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider\">\n Grant Types\n </th>\n <th scope=\"col\" class=\"px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider\">\n Created\n </th>\n <th scope=\"col\" class=\"px-6 py-3 text-right text-xs font-medium text-gray-500 uppercase tracking-wider\">\n Actions\n </th>\n </tr>\n </thead>\n <tbody class=\"bg-white divide-y divide-gray-200\">\n @if (loading()) {\n <tr>\n <td colspan=\"6\" class=\"px-6 py-12 text-center\">\n <div class=\"flex justify-center\">\n <svg\n class=\"animate-spin h-8 w-8 text-primary-600\"\n xmlns=\"http://www.w3.org/2000/svg\"\n fill=\"none\"\n viewBox=\"0 0 24 24\"\n >\n <circle\n class=\"opacity-25\"\n cx=\"12\"\n cy=\"12\"\n r=\"10\"\n stroke=\"currentColor\"\n stroke-width=\"4\"\n ></circle>\n <path\n class=\"opacity-75\"\n fill=\"currentColor\"\n d=\"M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z\"\n ></path>\n </svg>\n </div>\n </td>\n </tr>\n } @else if (clients().length === 0) {\n <tr>\n <td colspan=\"6\" class=\"px-6 py-12 text-center text-sm text-gray-500\">\n No platform clients configured yet\n </td>\n </tr>\n } @else {\n @for (client of clients(); track client.clientId) {\n <tr class=\"hover:bg-gray-50 cursor-pointer\" (click)=\"navigateToEdit(client)\">\n <td class=\"px-6 py-4 whitespace-nowrap text-sm font-medium text-gray-900\">\n {{ client.clientId }}\n </td>\n <td class=\"px-6 py-4 whitespace-nowrap text-sm text-gray-500\">\n {{ client.clientName || '-' }}\n </td>\n <td class=\"px-6 py-4 whitespace-nowrap\">\n <ui-badge [variant]=\"client.enabled ? 'success' : 'danger'\">\n {{ client.enabled ? 'Enabled' : 'Disabled' }}\n </ui-badge>\n </td>\n <td class=\"px-6 py-4 whitespace-nowrap text-xs text-gray-500\">\n {{ formatGrantTypes(client.allowedGrantTypes) }}\n </td>\n <td class=\"px-6 py-4 whitespace-nowrap text-sm text-gray-500\">\n {{ formatDate(client.created) }}\n </td>\n <td class=\"px-6 py-4 whitespace-nowrap text-right text-sm font-medium space-x-2\" (click)=\"$event.stopPropagation()\">\n <ui-button variant=\"secondary\" size=\"sm\" (clicked)=\"navigateToEdit(client)\">\n Edit\n </ui-button>\n <ui-button variant=\"danger\" size=\"sm\" (clicked)=\"confirmDelete(client)\">\n Delete\n </ui-button>\n </td>\n </tr>\n }\n }\n </tbody>\n </table>\n </div>\n </ui-card>\n</div>\n\n<!-- Delete Confirmation -->\n<ui-confirm-dialog\n [open]=\"showDeleteConfirm()\"\n title=\"Delete Platform Client\"\n [message]=\"'Are you sure you want to delete the platform client \\'' + (deletingClient()?.clientId ?? '') + '\\'? This action cannot be undone.'\"\n confirmText=\"Delete\"\n confirmVariant=\"danger\"\n [loading]=\"saving()\"\n (confirmed)=\"deleteClient()\"\n (cancelled)=\"cancelDelete()\"\n/>\n" }]
|
|
77
|
+
}], ctorParameters: () => [] });
|
|
78
|
+
|
|
79
|
+
export { PlatformClientsPageComponent };
|
|
80
|
+
//# sourceMappingURL=syndew-oluso-admin-ui-platform-clients-page.component-ab3WDRsI.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"syndew-oluso-admin-ui-platform-clients-page.component-ab3WDRsI.mjs","sources":["../../../../libs/admin-ui/src/lib/pages/platform-clients/platform-clients-page.component.ts","../../../../libs/admin-ui/src/lib/pages/platform-clients/platform-clients-page.component.html"],"sourcesContent":["import { Component, inject, signal } from '@angular/core';\nimport { CommonModule } from '@angular/common';\nimport { Router } from '@angular/router';\nimport { CardComponent, ButtonComponent, BadgeComponent, IconComponent, ConfirmDialogComponent } from '@syndew/oluso-ui-core';\nimport { PlatformClientService } from '../../services/platform-client.service';\nimport { Client } from '../../models/client.model';\n\n@Component({\n selector: 'admin-platform-clients-page',\n standalone: true,\n imports: [CommonModule, CardComponent, ButtonComponent, BadgeComponent, IconComponent, ConfirmDialogComponent],\n templateUrl: './platform-clients-page.component.html',\n})\nexport class PlatformClientsPageComponent {\n private readonly router = inject(Router);\n private readonly platformClientService = inject(PlatformClientService);\n\n readonly loading = signal(true);\n readonly clients = signal<Client[]>([]);\n\n // Delete confirmation\n readonly showDeleteConfirm = signal(false);\n readonly deletingClient = signal<Client | null>(null);\n readonly saving = signal(false);\n\n constructor() {\n this.loadClients();\n }\n\n loadClients(): void {\n this.loading.set(true);\n this.platformClientService.getAll().subscribe({\n next: (clients) => {\n this.clients.set(clients);\n this.loading.set(false);\n },\n error: (error) => {\n console.error('Failed to load platform clients:', error);\n this.loading.set(false);\n },\n });\n }\n\n navigateToCreate(): void {\n this.router.navigate(['/platform/clients/new']);\n }\n\n navigateToEdit(client: Client): void {\n this.router.navigate(['/platform/clients', client.id]);\n }\n\n confirmDelete(client: Client): void {\n this.deletingClient.set(client);\n this.showDeleteConfirm.set(true);\n }\n\n cancelDelete(): void {\n this.showDeleteConfirm.set(false);\n this.deletingClient.set(null);\n }\n\n deleteClient(): void {\n const client = this.deletingClient();\n if (!client) return;\n\n this.saving.set(true);\n this.platformClientService.delete(client.id).subscribe({\n next: () => {\n this.showDeleteConfirm.set(false);\n this.deletingClient.set(null);\n this.saving.set(false);\n this.loadClients();\n },\n error: (error) => {\n console.error('Failed to delete client:', error);\n this.saving.set(false);\n },\n });\n }\n\n formatDate(dateString: string): string {\n return new Date(dateString).toLocaleDateString();\n }\n\n formatGrantTypes(grantTypes: string[]): string {\n return grantTypes?.join(', ') || '-';\n }\n}\n","<div class=\"space-y-6\">\n <!-- Page header -->\n <div class=\"flex items-center justify-between\">\n <div>\n <h1 class=\"text-2xl font-bold text-gray-900\">Platform Clients</h1>\n <p class=\"mt-1 text-sm text-gray-500\">\n System-level OAuth clients available across all tenants\n </p>\n </div>\n <ui-button (clicked)=\"navigateToCreate()\">\n <ui-icon name=\"plus\" class=\"h-4 w-4 mr-2\" />\n Add Platform Client\n </ui-button>\n </div>\n\n <!-- Platform clients table -->\n <ui-card [padding]=\"false\">\n <div class=\"overflow-hidden\">\n <table class=\"min-w-full divide-y divide-gray-200\">\n <thead class=\"bg-gray-50\">\n <tr>\n <th scope=\"col\" class=\"px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider\">\n Client ID\n </th>\n <th scope=\"col\" class=\"px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider\">\n Name\n </th>\n <th scope=\"col\" class=\"px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider\">\n Status\n </th>\n <th scope=\"col\" class=\"px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider\">\n Grant Types\n </th>\n <th scope=\"col\" class=\"px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider\">\n Created\n </th>\n <th scope=\"col\" class=\"px-6 py-3 text-right text-xs font-medium text-gray-500 uppercase tracking-wider\">\n Actions\n </th>\n </tr>\n </thead>\n <tbody class=\"bg-white divide-y divide-gray-200\">\n @if (loading()) {\n <tr>\n <td colspan=\"6\" class=\"px-6 py-12 text-center\">\n <div class=\"flex justify-center\">\n <svg\n class=\"animate-spin h-8 w-8 text-primary-600\"\n xmlns=\"http://www.w3.org/2000/svg\"\n fill=\"none\"\n viewBox=\"0 0 24 24\"\n >\n <circle\n class=\"opacity-25\"\n cx=\"12\"\n cy=\"12\"\n r=\"10\"\n stroke=\"currentColor\"\n stroke-width=\"4\"\n ></circle>\n <path\n class=\"opacity-75\"\n fill=\"currentColor\"\n d=\"M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z\"\n ></path>\n </svg>\n </div>\n </td>\n </tr>\n } @else if (clients().length === 0) {\n <tr>\n <td colspan=\"6\" class=\"px-6 py-12 text-center text-sm text-gray-500\">\n No platform clients configured yet\n </td>\n </tr>\n } @else {\n @for (client of clients(); track client.clientId) {\n <tr class=\"hover:bg-gray-50 cursor-pointer\" (click)=\"navigateToEdit(client)\">\n <td class=\"px-6 py-4 whitespace-nowrap text-sm font-medium text-gray-900\">\n {{ client.clientId }}\n </td>\n <td class=\"px-6 py-4 whitespace-nowrap text-sm text-gray-500\">\n {{ client.clientName || '-' }}\n </td>\n <td class=\"px-6 py-4 whitespace-nowrap\">\n <ui-badge [variant]=\"client.enabled ? 'success' : 'danger'\">\n {{ client.enabled ? 'Enabled' : 'Disabled' }}\n </ui-badge>\n </td>\n <td class=\"px-6 py-4 whitespace-nowrap text-xs text-gray-500\">\n {{ formatGrantTypes(client.allowedGrantTypes) }}\n </td>\n <td class=\"px-6 py-4 whitespace-nowrap text-sm text-gray-500\">\n {{ formatDate(client.created) }}\n </td>\n <td class=\"px-6 py-4 whitespace-nowrap text-right text-sm font-medium space-x-2\" (click)=\"$event.stopPropagation()\">\n <ui-button variant=\"secondary\" size=\"sm\" (clicked)=\"navigateToEdit(client)\">\n Edit\n </ui-button>\n <ui-button variant=\"danger\" size=\"sm\" (clicked)=\"confirmDelete(client)\">\n Delete\n </ui-button>\n </td>\n </tr>\n }\n }\n </tbody>\n </table>\n </div>\n </ui-card>\n</div>\n\n<!-- Delete Confirmation -->\n<ui-confirm-dialog\n [open]=\"showDeleteConfirm()\"\n title=\"Delete Platform Client\"\n [message]=\"'Are you sure you want to delete the platform client \\'' + (deletingClient()?.clientId ?? '') + '\\'? This action cannot be undone.'\"\n confirmText=\"Delete\"\n confirmVariant=\"danger\"\n [loading]=\"saving()\"\n (confirmed)=\"deleteClient()\"\n (cancelled)=\"cancelDelete()\"\n/>\n"],"names":[],"mappings":";;;;;;;MAaa,4BAA4B,CAAA;AACtB,IAAA,MAAM,GAAG,MAAM,CAAC,MAAM,CAAC;AACvB,IAAA,qBAAqB,GAAG,MAAM,CAAC,qBAAqB,CAAC;AAE7D,IAAA,OAAO,GAAG,MAAM,CAAC,IAAI,8EAAC;AACtB,IAAA,OAAO,GAAG,MAAM,CAAW,EAAE,8EAAC;;AAG9B,IAAA,iBAAiB,GAAG,MAAM,CAAC,KAAK,wFAAC;AACjC,IAAA,cAAc,GAAG,MAAM,CAAgB,IAAI,qFAAC;AAC5C,IAAA,MAAM,GAAG,MAAM,CAAC,KAAK,6EAAC;AAE/B,IAAA,WAAA,GAAA;QACE,IAAI,CAAC,WAAW,EAAE;IACpB;IAEA,WAAW,GAAA;AACT,QAAA,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC;AACtB,QAAA,IAAI,CAAC,qBAAqB,CAAC,MAAM,EAAE,CAAC,SAAS,CAAC;AAC5C,YAAA,IAAI,EAAE,CAAC,OAAO,KAAI;AAChB,gBAAA,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,OAAO,CAAC;AACzB,gBAAA,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC;YACzB,CAAC;AACD,YAAA,KAAK,EAAE,CAAC,KAAK,KAAI;AACf,gBAAA,OAAO,CAAC,KAAK,CAAC,kCAAkC,EAAE,KAAK,CAAC;AACxD,gBAAA,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC;YACzB,CAAC;AACF,SAAA,CAAC;IACJ;IAEA,gBAAgB,GAAA;QACd,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC,uBAAuB,CAAC,CAAC;IACjD;AAEA,IAAA,cAAc,CAAC,MAAc,EAAA;AAC3B,QAAA,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC,mBAAmB,EAAE,MAAM,CAAC,EAAE,CAAC,CAAC;IACxD;AAEA,IAAA,aAAa,CAAC,MAAc,EAAA;AAC1B,QAAA,IAAI,CAAC,cAAc,CAAC,GAAG,CAAC,MAAM,CAAC;AAC/B,QAAA,IAAI,CAAC,iBAAiB,CAAC,GAAG,CAAC,IAAI,CAAC;IAClC;IAEA,YAAY,GAAA;AACV,QAAA,IAAI,CAAC,iBAAiB,CAAC,GAAG,CAAC,KAAK,CAAC;AACjC,QAAA,IAAI,CAAC,cAAc,CAAC,GAAG,CAAC,IAAI,CAAC;IAC/B;IAEA,YAAY,GAAA;AACV,QAAA,MAAM,MAAM,GAAG,IAAI,CAAC,cAAc,EAAE;AACpC,QAAA,IAAI,CAAC,MAAM;YAAE;AAEb,QAAA,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC;QACrB,IAAI,CAAC,qBAAqB,CAAC,MAAM,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC,SAAS,CAAC;YACrD,IAAI,EAAE,MAAK;AACT,gBAAA,IAAI,CAAC,iBAAiB,CAAC,GAAG,CAAC,KAAK,CAAC;AACjC,gBAAA,IAAI,CAAC,cAAc,CAAC,GAAG,CAAC,IAAI,CAAC;AAC7B,gBAAA,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,KAAK,CAAC;gBACtB,IAAI,CAAC,WAAW,EAAE;YACpB,CAAC;AACD,YAAA,KAAK,EAAE,CAAC,KAAK,KAAI;AACf,gBAAA,OAAO,CAAC,KAAK,CAAC,0BAA0B,EAAE,KAAK,CAAC;AAChD,gBAAA,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,KAAK,CAAC;YACxB,CAAC;AACF,SAAA,CAAC;IACJ;AAEA,IAAA,UAAU,CAAC,UAAkB,EAAA;QAC3B,OAAO,IAAI,IAAI,CAAC,UAAU,CAAC,CAAC,kBAAkB,EAAE;IAClD;AAEA,IAAA,gBAAgB,CAAC,UAAoB,EAAA;QACnC,OAAO,UAAU,EAAE,IAAI,CAAC,IAAI,CAAC,IAAI,GAAG;IACtC;wGAzEW,4BAA4B,EAAA,IAAA,EAAA,EAAA,EAAA,MAAA,EAAA,EAAA,CAAA,eAAA,CAAA,SAAA,EAAA,CAAA;AAA5B,IAAA,OAAA,IAAA,GAAA,EAAA,CAAA,oBAAA,CAAA,EAAA,UAAA,EAAA,QAAA,EAAA,OAAA,EAAA,SAAA,EAAA,IAAA,EAAA,4BAA4B,EAAA,YAAA,EAAA,IAAA,EAAA,QAAA,EAAA,6BAAA,EAAA,QAAA,EAAA,EAAA,EAAA,QAAA,ECbzC,mnKA2HA,EAAA,YAAA,EAAA,CAAA,EAAA,IAAA,EAAA,UAAA,EAAA,IAAA,EDjHY,YAAY,EAAA,EAAA,EAAA,IAAA,EAAA,WAAA,EAAA,IAAA,EAAE,aAAa,EAAA,QAAA,EAAA,SAAA,EAAA,MAAA,EAAA,CAAA,OAAA,EAAA,UAAA,EAAA,SAAA,EAAA,QAAA,CAAA,EAAA,EAAA,EAAA,IAAA,EAAA,WAAA,EAAA,IAAA,EAAE,eAAe,EAAA,QAAA,EAAA,WAAA,EAAA,MAAA,EAAA,CAAA,SAAA,EAAA,MAAA,EAAA,UAAA,EAAA,SAAA,EAAA,MAAA,EAAA,WAAA,CAAA,EAAA,OAAA,EAAA,CAAA,SAAA,CAAA,EAAA,EAAA,EAAA,IAAA,EAAA,WAAA,EAAA,IAAA,EAAE,cAAc,EAAA,QAAA,EAAA,UAAA,EAAA,MAAA,EAAA,CAAA,SAAA,EAAA,MAAA,CAAA,EAAA,EAAA,EAAA,IAAA,EAAA,WAAA,EAAA,IAAA,EAAE,aAAa,qFAAE,sBAAsB,EAAA,QAAA,EAAA,mBAAA,EAAA,MAAA,EAAA,CAAA,MAAA,EAAA,OAAA,EAAA,SAAA,EAAA,aAAA,EAAA,YAAA,EAAA,SAAA,EAAA,SAAA,EAAA,iBAAA,EAAA,eAAA,CAAA,EAAA,OAAA,EAAA,CAAA,WAAA,EAAA,WAAA,CAAA,EAAA,CAAA,EAAA,CAAA;;4FAGlG,4BAA4B,EAAA,UAAA,EAAA,CAAA;kBANxC,SAAS;AACE,YAAA,IAAA,EAAA,CAAA,EAAA,QAAA,EAAA,6BAA6B,EAAA,UAAA,EAC3B,IAAI,EAAA,OAAA,EACP,CAAC,YAAY,EAAE,aAAa,EAAE,eAAe,EAAE,cAAc,EAAE,aAAa,EAAE,sBAAsB,CAAC,EAAA,QAAA,EAAA,mnKAAA,EAAA;;;;;"}
|