@memberjunction/ng-dashboards 3.2.0 → 3.3.0
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/dist/APIKeys/api-applications-panel.component.d.ts +167 -0
- package/dist/APIKeys/api-applications-panel.component.d.ts.map +1 -0
- package/dist/APIKeys/api-applications-panel.component.js +1059 -0
- package/dist/APIKeys/api-applications-panel.component.js.map +1 -0
- package/dist/APIKeys/api-key-create-dialog.component.d.ts +124 -0
- package/dist/APIKeys/api-key-create-dialog.component.d.ts.map +1 -0
- package/dist/APIKeys/api-key-create-dialog.component.js +743 -0
- package/dist/APIKeys/api-key-create-dialog.component.js.map +1 -0
- package/dist/APIKeys/api-key-edit-panel.component.d.ts +149 -0
- package/dist/APIKeys/api-key-edit-panel.component.d.ts.map +1 -0
- package/dist/APIKeys/api-key-edit-panel.component.js +1061 -0
- package/dist/APIKeys/api-key-edit-panel.component.js.map +1 -0
- package/dist/APIKeys/api-key-list.component.d.ts +130 -0
- package/dist/APIKeys/api-key-list.component.d.ts.map +1 -0
- package/dist/APIKeys/api-key-list.component.js +814 -0
- package/dist/APIKeys/api-key-list.component.js.map +1 -0
- package/dist/APIKeys/api-keys-resource.component.d.ts +209 -0
- package/dist/APIKeys/api-keys-resource.component.d.ts.map +1 -0
- package/dist/APIKeys/api-keys-resource.component.js +1165 -0
- package/dist/APIKeys/api-keys-resource.component.js.map +1 -0
- package/dist/APIKeys/api-scopes-panel.component.d.ts +98 -0
- package/dist/APIKeys/api-scopes-panel.component.d.ts.map +1 -0
- package/dist/APIKeys/api-scopes-panel.component.js +652 -0
- package/dist/APIKeys/api-scopes-panel.component.js.map +1 -0
- package/dist/APIKeys/api-usage-panel.component.d.ts +174 -0
- package/dist/APIKeys/api-usage-panel.component.d.ts.map +1 -0
- package/dist/APIKeys/api-usage-panel.component.js +1013 -0
- package/dist/APIKeys/api-usage-panel.component.js.map +1 -0
- package/dist/APIKeys/index.d.ts +7 -0
- package/dist/APIKeys/index.d.ts.map +1 -0
- package/dist/APIKeys/index.js +8 -0
- package/dist/APIKeys/index.js.map +1 -0
- package/dist/DashboardBrowser/dashboard-browser-resource.component.d.ts.map +1 -1
- package/dist/DashboardBrowser/dashboard-browser-resource.component.js +4 -2
- package/dist/DashboardBrowser/dashboard-browser-resource.component.js.map +1 -1
- package/dist/DataExplorer/data-explorer-dashboard.component.d.ts +3 -3
- package/dist/DataExplorer/data-explorer-dashboard.component.d.ts.map +1 -1
- package/dist/DataExplorer/data-explorer-dashboard.component.js +22 -6
- package/dist/DataExplorer/data-explorer-dashboard.component.js.map +1 -1
- package/dist/EntityAdmin/entity-admin-dashboard.component.js +2 -2
- package/dist/module.d.ts +39 -32
- package/dist/module.d.ts.map +1 -1
- package/dist/module.js +44 -4
- package/dist/module.js.map +1 -1
- package/dist/public-api.d.ts +7 -0
- package/dist/public-api.d.ts.map +1 -1
- package/dist/public-api.js +24 -0
- package/dist/public-api.js.map +1 -1
- package/package.json +32 -32
|
@@ -0,0 +1,1165 @@
|
|
|
1
|
+
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
2
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
3
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
4
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
5
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
6
|
+
};
|
|
7
|
+
import { Component, ViewChild } from '@angular/core';
|
|
8
|
+
import { BaseResourceComponent } from '@memberjunction/ng-shared';
|
|
9
|
+
import { RegisterClass } from '@memberjunction/global';
|
|
10
|
+
import { Metadata, RunView } from '@memberjunction/core';
|
|
11
|
+
import { Subject } from 'rxjs';
|
|
12
|
+
import * as i0 from "@angular/core";
|
|
13
|
+
import * as i1 from "@angular/common";
|
|
14
|
+
import * as i2 from "@memberjunction/ng-shared-generic";
|
|
15
|
+
import * as i3 from "./api-key-create-dialog.component";
|
|
16
|
+
import * as i4 from "./api-key-edit-panel.component";
|
|
17
|
+
import * as i5 from "./api-key-list.component";
|
|
18
|
+
import * as i6 from "./api-applications-panel.component";
|
|
19
|
+
import * as i7 from "./api-scopes-panel.component";
|
|
20
|
+
import * as i8 from "./api-usage-panel.component";
|
|
21
|
+
const _c0 = ["keyList"];
|
|
22
|
+
function APIKeysResourceComponent_mj_loading_1_Template(rf, ctx) { if (rf & 1) {
|
|
23
|
+
i0.ɵɵelement(0, "mj-loading", 6);
|
|
24
|
+
} }
|
|
25
|
+
function APIKeysResourceComponent_ng_container_2_ng_container_32_ng_container_1_button_10_Template(rf, ctx) { if (rf & 1) {
|
|
26
|
+
const _r4 = i0.ɵɵgetCurrentView();
|
|
27
|
+
i0.ɵɵelementStart(0, "button", 78);
|
|
28
|
+
i0.ɵɵlistener("click", function APIKeysResourceComponent_ng_container_2_ng_container_32_ng_container_1_button_10_Template_button_click_0_listener() { i0.ɵɵrestoreView(_r4); const ctx_r1 = i0.ɵɵnextContext(4); return i0.ɵɵresetView(ctx_r1.openCreateDialog()); });
|
|
29
|
+
i0.ɵɵelement(1, "i", 79);
|
|
30
|
+
i0.ɵɵelementStart(2, "span");
|
|
31
|
+
i0.ɵɵtext(3, "Generate New Key");
|
|
32
|
+
i0.ɵɵelementEnd()();
|
|
33
|
+
} }
|
|
34
|
+
function APIKeysResourceComponent_ng_container_2_ng_container_32_ng_container_1_span_23_Template(rf, ctx) { if (rf & 1) {
|
|
35
|
+
const _r5 = i0.ɵɵgetCurrentView();
|
|
36
|
+
i0.ɵɵelementStart(0, "span", 80);
|
|
37
|
+
i0.ɵɵlistener("click", function APIKeysResourceComponent_ng_container_2_ng_container_32_ng_container_1_span_23_Template_span_click_0_listener() { i0.ɵɵrestoreView(_r5); const ctx_r1 = i0.ɵɵnextContext(4); return i0.ɵɵresetView(ctx_r1.showListView("expired")); });
|
|
38
|
+
i0.ɵɵelement(1, "i", 81);
|
|
39
|
+
i0.ɵɵtext(2);
|
|
40
|
+
i0.ɵɵelementEnd();
|
|
41
|
+
} if (rf & 2) {
|
|
42
|
+
const ctx_r1 = i0.ɵɵnextContext(4);
|
|
43
|
+
i0.ɵɵadvance(2);
|
|
44
|
+
i0.ɵɵtextInterpolate1(" ", ctx_r1.ExpiredKeys, " expired ");
|
|
45
|
+
} }
|
|
46
|
+
function APIKeysResourceComponent_ng_container_2_ng_container_32_ng_container_1_span_24_Template(rf, ctx) { if (rf & 1) {
|
|
47
|
+
const _r6 = i0.ɵɵgetCurrentView();
|
|
48
|
+
i0.ɵɵelementStart(0, "span", 82);
|
|
49
|
+
i0.ɵɵlistener("click", function APIKeysResourceComponent_ng_container_2_ng_container_32_ng_container_1_span_24_Template_span_click_0_listener() { i0.ɵɵrestoreView(_r6); const ctx_r1 = i0.ɵɵnextContext(4); return i0.ɵɵresetView(ctx_r1.showListView("expiring")); });
|
|
50
|
+
i0.ɵɵelement(1, "i", 57);
|
|
51
|
+
i0.ɵɵtext(2);
|
|
52
|
+
i0.ɵɵelementEnd();
|
|
53
|
+
} if (rf & 2) {
|
|
54
|
+
const ctx_r1 = i0.ɵɵnextContext(4);
|
|
55
|
+
i0.ɵɵadvance(2);
|
|
56
|
+
i0.ɵɵtextInterpolate1(" ", ctx_r1.ExpiringSoonCount, " expiring soon ");
|
|
57
|
+
} }
|
|
58
|
+
function APIKeysResourceComponent_ng_container_2_ng_container_32_ng_container_1_span_25_Template(rf, ctx) { if (rf & 1) {
|
|
59
|
+
const _r7 = i0.ɵɵgetCurrentView();
|
|
60
|
+
i0.ɵɵelementStart(0, "span", 83);
|
|
61
|
+
i0.ɵɵlistener("click", function APIKeysResourceComponent_ng_container_2_ng_container_32_ng_container_1_span_25_Template_span_click_0_listener() { i0.ɵɵrestoreView(_r7); const ctx_r1 = i0.ɵɵnextContext(4); return i0.ɵɵresetView(ctx_r1.showListView("never-used")); });
|
|
62
|
+
i0.ɵɵelement(1, "i", 84);
|
|
63
|
+
i0.ɵɵtext(2);
|
|
64
|
+
i0.ɵɵelementEnd();
|
|
65
|
+
} if (rf & 2) {
|
|
66
|
+
const ctx_r1 = i0.ɵɵnextContext(4);
|
|
67
|
+
i0.ɵɵadvance(2);
|
|
68
|
+
i0.ɵɵtextInterpolate1(" ", ctx_r1.NeverUsedKeys, " never used ");
|
|
69
|
+
} }
|
|
70
|
+
function APIKeysResourceComponent_ng_container_2_ng_container_32_ng_container_1_span_26_Template(rf, ctx) { if (rf & 1) {
|
|
71
|
+
i0.ɵɵelementStart(0, "span", 85);
|
|
72
|
+
i0.ɵɵelement(1, "i", 54);
|
|
73
|
+
i0.ɵɵtext(2, " All keys healthy ");
|
|
74
|
+
i0.ɵɵelementEnd();
|
|
75
|
+
} }
|
|
76
|
+
function APIKeysResourceComponent_ng_container_2_ng_container_32_ng_container_1_div_57_Template(rf, ctx) { if (rf & 1) {
|
|
77
|
+
i0.ɵɵelementStart(0, "div", 86);
|
|
78
|
+
i0.ɵɵelement(1, "i", 52);
|
|
79
|
+
i0.ɵɵelementEnd();
|
|
80
|
+
} }
|
|
81
|
+
function APIKeysResourceComponent_ng_container_2_ng_container_32_ng_container_1_div_85_div_1_Template(rf, ctx) { if (rf & 1) {
|
|
82
|
+
const _r8 = i0.ɵɵgetCurrentView();
|
|
83
|
+
i0.ɵɵelementStart(0, "div", 89);
|
|
84
|
+
i0.ɵɵlistener("click", function APIKeysResourceComponent_ng_container_2_ng_container_32_ng_container_1_div_85_div_1_Template_div_click_0_listener() { const key_r9 = i0.ɵɵrestoreView(_r8).$implicit; const ctx_r1 = i0.ɵɵnextContext(5); return i0.ɵɵresetView(ctx_r1.openEditPanel(key_r9)); });
|
|
85
|
+
i0.ɵɵelementStart(1, "div", 90);
|
|
86
|
+
i0.ɵɵelement(2, "i", 10);
|
|
87
|
+
i0.ɵɵelementEnd();
|
|
88
|
+
i0.ɵɵelementStart(3, "div", 91)(4, "div", 92);
|
|
89
|
+
i0.ɵɵtext(5);
|
|
90
|
+
i0.ɵɵelementEnd();
|
|
91
|
+
i0.ɵɵelementStart(6, "div", 93)(7, "span", 94);
|
|
92
|
+
i0.ɵɵtext(8);
|
|
93
|
+
i0.ɵɵelementEnd();
|
|
94
|
+
i0.ɵɵelementStart(9, "span", 95);
|
|
95
|
+
i0.ɵɵtext(10);
|
|
96
|
+
i0.ɵɵelementEnd()()();
|
|
97
|
+
i0.ɵɵelementStart(11, "div", 96)(12, "div", 97);
|
|
98
|
+
i0.ɵɵtext(13);
|
|
99
|
+
i0.ɵɵelementEnd();
|
|
100
|
+
i0.ɵɵelementStart(14, "div", 98);
|
|
101
|
+
i0.ɵɵtext(15);
|
|
102
|
+
i0.ɵɵelementEnd()()();
|
|
103
|
+
} if (rf & 2) {
|
|
104
|
+
const key_r9 = ctx.$implicit;
|
|
105
|
+
const ctx_r1 = i0.ɵɵnextContext(5);
|
|
106
|
+
i0.ɵɵadvance();
|
|
107
|
+
i0.ɵɵclassProp("active", key_r9.Status === "Active");
|
|
108
|
+
i0.ɵɵadvance(4);
|
|
109
|
+
i0.ɵɵtextInterpolate(key_r9.Label);
|
|
110
|
+
i0.ɵɵadvance(3);
|
|
111
|
+
i0.ɵɵtextInterpolate(key_r9.User);
|
|
112
|
+
i0.ɵɵadvance(2);
|
|
113
|
+
i0.ɵɵtextInterpolate1("...", key_r9.Hash.slice(-8), "");
|
|
114
|
+
i0.ɵɵadvance(3);
|
|
115
|
+
i0.ɵɵtextInterpolate(ctx_r1.formatDate(key_r9.LastUsedAt));
|
|
116
|
+
i0.ɵɵadvance();
|
|
117
|
+
i0.ɵɵproperty("ngClass", ctx_r1.getExpirationClass(key_r9));
|
|
118
|
+
i0.ɵɵadvance();
|
|
119
|
+
i0.ɵɵtextInterpolate1(" ", ctx_r1.formatExpiration(key_r9.ExpiresAt), " ");
|
|
120
|
+
} }
|
|
121
|
+
function APIKeysResourceComponent_ng_container_2_ng_container_32_ng_container_1_div_85_Template(rf, ctx) { if (rf & 1) {
|
|
122
|
+
i0.ɵɵelementStart(0, "div", 87);
|
|
123
|
+
i0.ɵɵtemplate(1, APIKeysResourceComponent_ng_container_2_ng_container_32_ng_container_1_div_85_div_1_Template, 16, 8, "div", 88);
|
|
124
|
+
i0.ɵɵelementEnd();
|
|
125
|
+
} if (rf & 2) {
|
|
126
|
+
const ctx_r1 = i0.ɵɵnextContext(4);
|
|
127
|
+
i0.ɵɵadvance();
|
|
128
|
+
i0.ɵɵproperty("ngForOf", ctx_r1.TopUsedKeys);
|
|
129
|
+
} }
|
|
130
|
+
function APIKeysResourceComponent_ng_container_2_ng_container_32_ng_container_1_div_86_Template(rf, ctx) { if (rf & 1) {
|
|
131
|
+
i0.ɵɵelementStart(0, "div", 99);
|
|
132
|
+
i0.ɵɵelement(1, "i", 10);
|
|
133
|
+
i0.ɵɵelementStart(2, "span");
|
|
134
|
+
i0.ɵɵtext(3, "No active keys with recent usage");
|
|
135
|
+
i0.ɵɵelementEnd()();
|
|
136
|
+
} }
|
|
137
|
+
function APIKeysResourceComponent_ng_container_2_ng_container_32_ng_container_1_div_96__svg_ng_container_4_Template(rf, ctx) { if (rf & 1) {
|
|
138
|
+
const _r10 = i0.ɵɵgetCurrentView();
|
|
139
|
+
i0.ɵɵnamespaceSVG();
|
|
140
|
+
i0.ɵɵelementContainerStart(0);
|
|
141
|
+
i0.ɵɵelementStart(1, "circle", 110);
|
|
142
|
+
i0.ɵɵlistener("click", function APIKeysResourceComponent_ng_container_2_ng_container_32_ng_container_1_div_96__svg_ng_container_4_Template_circle_click_1_listener() { const stat_r11 = i0.ɵɵrestoreView(_r10).$implicit; const ctx_r1 = i0.ɵɵnextContext(5); return i0.ɵɵresetView(ctx_r1.onScopeClick(stat_r11)); });
|
|
143
|
+
i0.ɵɵelementEnd();
|
|
144
|
+
i0.ɵɵelementContainerEnd();
|
|
145
|
+
} if (rf & 2) {
|
|
146
|
+
const stat_r11 = ctx.$implicit;
|
|
147
|
+
const i_r12 = ctx.index;
|
|
148
|
+
const ctx_r1 = i0.ɵɵnextContext(5);
|
|
149
|
+
i0.ɵɵadvance();
|
|
150
|
+
i0.ɵɵattribute("stroke", stat_r11.color)("stroke-dasharray", stat_r11.percentage * 2.51 + " " + (251 - stat_r11.percentage * 2.51))("stroke-dashoffset", ctx_r1.getDonutOffset(i_r12));
|
|
151
|
+
} }
|
|
152
|
+
function APIKeysResourceComponent_ng_container_2_ng_container_32_ng_container_1_div_96_div_11_Template(rf, ctx) { if (rf & 1) {
|
|
153
|
+
const _r13 = i0.ɵɵgetCurrentView();
|
|
154
|
+
i0.ɵɵelementStart(0, "div", 111);
|
|
155
|
+
i0.ɵɵlistener("click", function APIKeysResourceComponent_ng_container_2_ng_container_32_ng_container_1_div_96_div_11_Template_div_click_0_listener() { const stat_r14 = i0.ɵɵrestoreView(_r13).$implicit; const ctx_r1 = i0.ɵɵnextContext(5); return i0.ɵɵresetView(ctx_r1.onScopeClick(stat_r14)); });
|
|
156
|
+
i0.ɵɵelement(1, "div", 112);
|
|
157
|
+
i0.ɵɵelementStart(2, "div", 113)(3, "div", 114);
|
|
158
|
+
i0.ɵɵelement(4, "i");
|
|
159
|
+
i0.ɵɵtext(5);
|
|
160
|
+
i0.ɵɵelementEnd();
|
|
161
|
+
i0.ɵɵelementStart(6, "div", 115);
|
|
162
|
+
i0.ɵɵtext(7);
|
|
163
|
+
i0.ɵɵelementEnd()();
|
|
164
|
+
i0.ɵɵelement(8, "i", 116);
|
|
165
|
+
i0.ɵɵelementEnd();
|
|
166
|
+
} if (rf & 2) {
|
|
167
|
+
const stat_r14 = ctx.$implicit;
|
|
168
|
+
i0.ɵɵadvance();
|
|
169
|
+
i0.ɵɵstyleProp("background-color", stat_r14.color);
|
|
170
|
+
i0.ɵɵadvance(3);
|
|
171
|
+
i0.ɵɵclassMap(stat_r14.iconClass);
|
|
172
|
+
i0.ɵɵadvance();
|
|
173
|
+
i0.ɵɵtextInterpolate1(" ", stat_r14.category, " ");
|
|
174
|
+
i0.ɵɵadvance(2);
|
|
175
|
+
i0.ɵɵtextInterpolate1("", stat_r14.count, " scopes");
|
|
176
|
+
} }
|
|
177
|
+
function APIKeysResourceComponent_ng_container_2_ng_container_32_ng_container_1_div_96_Template(rf, ctx) { if (rf & 1) {
|
|
178
|
+
i0.ɵɵelementStart(0, "div", 100)(1, "div", 101);
|
|
179
|
+
i0.ɵɵnamespaceSVG();
|
|
180
|
+
i0.ɵɵelementStart(2, "svg", 102);
|
|
181
|
+
i0.ɵɵelement(3, "circle", 103);
|
|
182
|
+
i0.ɵɵtemplate(4, APIKeysResourceComponent_ng_container_2_ng_container_32_ng_container_1_div_96__svg_ng_container_4_Template, 2, 3, "ng-container", 104);
|
|
183
|
+
i0.ɵɵelementEnd();
|
|
184
|
+
i0.ɵɵnamespaceHTML();
|
|
185
|
+
i0.ɵɵelementStart(5, "div", 105)(6, "div", 106);
|
|
186
|
+
i0.ɵɵtext(7);
|
|
187
|
+
i0.ɵɵelementEnd();
|
|
188
|
+
i0.ɵɵelementStart(8, "div", 107);
|
|
189
|
+
i0.ɵɵtext(9, "Categories");
|
|
190
|
+
i0.ɵɵelementEnd()()();
|
|
191
|
+
i0.ɵɵelementStart(10, "div", 108);
|
|
192
|
+
i0.ɵɵtemplate(11, APIKeysResourceComponent_ng_container_2_ng_container_32_ng_container_1_div_96_div_11_Template, 9, 6, "div", 109);
|
|
193
|
+
i0.ɵɵelementEnd()();
|
|
194
|
+
} if (rf & 2) {
|
|
195
|
+
const ctx_r1 = i0.ɵɵnextContext(4);
|
|
196
|
+
i0.ɵɵadvance(4);
|
|
197
|
+
i0.ɵɵproperty("ngForOf", ctx_r1.ScopeStats);
|
|
198
|
+
i0.ɵɵadvance(3);
|
|
199
|
+
i0.ɵɵtextInterpolate(ctx_r1.ScopeStats.length);
|
|
200
|
+
i0.ɵɵadvance(4);
|
|
201
|
+
i0.ɵɵproperty("ngForOf", ctx_r1.ScopeStats);
|
|
202
|
+
} }
|
|
203
|
+
function APIKeysResourceComponent_ng_container_2_ng_container_32_ng_container_1_div_97_Template(rf, ctx) { if (rf & 1) {
|
|
204
|
+
i0.ɵɵelementStart(0, "div", 99);
|
|
205
|
+
i0.ɵɵelement(1, "i", 16);
|
|
206
|
+
i0.ɵɵelementStart(2, "span");
|
|
207
|
+
i0.ɵɵtext(3, "No scopes configured");
|
|
208
|
+
i0.ɵɵelementEnd()();
|
|
209
|
+
} }
|
|
210
|
+
function APIKeysResourceComponent_ng_container_2_ng_container_32_ng_container_1_div_105_div_1_span_9_Template(rf, ctx) { if (rf & 1) {
|
|
211
|
+
i0.ɵɵelementStart(0, "span", 127);
|
|
212
|
+
i0.ɵɵtext(1);
|
|
213
|
+
i0.ɵɵelementEnd();
|
|
214
|
+
} if (rf & 2) {
|
|
215
|
+
const activity_r16 = i0.ɵɵnextContext().$implicit;
|
|
216
|
+
i0.ɵɵadvance();
|
|
217
|
+
i0.ɵɵtextInterpolate1("by ", activity_r16.user, "");
|
|
218
|
+
} }
|
|
219
|
+
function APIKeysResourceComponent_ng_container_2_ng_container_32_ng_container_1_div_105_div_1_Template(rf, ctx) { if (rf & 1) {
|
|
220
|
+
const _r15 = i0.ɵɵgetCurrentView();
|
|
221
|
+
i0.ɵɵelementStart(0, "div", 119);
|
|
222
|
+
i0.ɵɵlistener("click", function APIKeysResourceComponent_ng_container_2_ng_container_32_ng_container_1_div_105_div_1_Template_div_click_0_listener() { const activity_r16 = i0.ɵɵrestoreView(_r15).$implicit; const ctx_r1 = i0.ɵɵnextContext(5); return i0.ɵɵresetView(ctx_r1.onActivityClick(activity_r16)); });
|
|
223
|
+
i0.ɵɵelementStart(1, "div", 120);
|
|
224
|
+
i0.ɵɵelement(2, "i");
|
|
225
|
+
i0.ɵɵelementEnd();
|
|
226
|
+
i0.ɵɵelementStart(3, "div", 121)(4, "div", 122);
|
|
227
|
+
i0.ɵɵtext(5);
|
|
228
|
+
i0.ɵɵelementEnd();
|
|
229
|
+
i0.ɵɵelementStart(6, "div", 123)(7, "span", 124);
|
|
230
|
+
i0.ɵɵtext(8);
|
|
231
|
+
i0.ɵɵelementEnd();
|
|
232
|
+
i0.ɵɵtemplate(9, APIKeysResourceComponent_ng_container_2_ng_container_32_ng_container_1_div_105_div_1_span_9_Template, 2, 1, "span", 125);
|
|
233
|
+
i0.ɵɵelementEnd()();
|
|
234
|
+
i0.ɵɵelementStart(10, "div", 126);
|
|
235
|
+
i0.ɵɵtext(11);
|
|
236
|
+
i0.ɵɵelementEnd()();
|
|
237
|
+
} if (rf & 2) {
|
|
238
|
+
const activity_r16 = ctx.$implicit;
|
|
239
|
+
const ctx_r1 = i0.ɵɵnextContext(5);
|
|
240
|
+
i0.ɵɵadvance();
|
|
241
|
+
i0.ɵɵproperty("ngClass", ctx_r1.getActionClass(activity_r16.action));
|
|
242
|
+
i0.ɵɵadvance();
|
|
243
|
+
i0.ɵɵclassMap(ctx_r1.getActionIcon(activity_r16.action));
|
|
244
|
+
i0.ɵɵadvance(3);
|
|
245
|
+
i0.ɵɵtextInterpolate(activity_r16.keyLabel);
|
|
246
|
+
i0.ɵɵadvance(3);
|
|
247
|
+
i0.ɵɵtextInterpolate(activity_r16.action);
|
|
248
|
+
i0.ɵɵadvance();
|
|
249
|
+
i0.ɵɵproperty("ngIf", activity_r16.user);
|
|
250
|
+
i0.ɵɵadvance(2);
|
|
251
|
+
i0.ɵɵtextInterpolate1(" ", ctx_r1.formatDate(activity_r16.date), " ");
|
|
252
|
+
} }
|
|
253
|
+
function APIKeysResourceComponent_ng_container_2_ng_container_32_ng_container_1_div_105_Template(rf, ctx) { if (rf & 1) {
|
|
254
|
+
i0.ɵɵelementStart(0, "div", 117);
|
|
255
|
+
i0.ɵɵtemplate(1, APIKeysResourceComponent_ng_container_2_ng_container_32_ng_container_1_div_105_div_1_Template, 12, 7, "div", 118);
|
|
256
|
+
i0.ɵɵelementEnd();
|
|
257
|
+
} if (rf & 2) {
|
|
258
|
+
const ctx_r1 = i0.ɵɵnextContext(4);
|
|
259
|
+
i0.ɵɵadvance();
|
|
260
|
+
i0.ɵɵproperty("ngForOf", ctx_r1.RecentActivity);
|
|
261
|
+
} }
|
|
262
|
+
function APIKeysResourceComponent_ng_container_2_ng_container_32_ng_container_1_div_106_Template(rf, ctx) { if (rf & 1) {
|
|
263
|
+
i0.ɵɵelementStart(0, "div", 99);
|
|
264
|
+
i0.ɵɵelement(1, "i", 128);
|
|
265
|
+
i0.ɵɵelementStart(2, "span");
|
|
266
|
+
i0.ɵɵtext(3, "No recent activity");
|
|
267
|
+
i0.ɵɵelementEnd()();
|
|
268
|
+
} }
|
|
269
|
+
function APIKeysResourceComponent_ng_container_2_ng_container_32_ng_container_1_div_107_Template(rf, ctx) { if (rf & 1) {
|
|
270
|
+
const _r17 = i0.ɵɵgetCurrentView();
|
|
271
|
+
i0.ɵɵelementStart(0, "div", 129)(1, "div", 130);
|
|
272
|
+
i0.ɵɵtext(2, "Quick Actions");
|
|
273
|
+
i0.ɵɵelementEnd();
|
|
274
|
+
i0.ɵɵelementStart(3, "div", 131)(4, "button", 132);
|
|
275
|
+
i0.ɵɵlistener("click", function APIKeysResourceComponent_ng_container_2_ng_container_32_ng_container_1_div_107_Template_button_click_4_listener() { i0.ɵɵrestoreView(_r17); const ctx_r1 = i0.ɵɵnextContext(4); return i0.ɵɵresetView(ctx_r1.openCreateDialog()); });
|
|
276
|
+
i0.ɵɵelement(5, "i", 79);
|
|
277
|
+
i0.ɵɵelementStart(6, "span");
|
|
278
|
+
i0.ɵɵtext(7, "Generate Key");
|
|
279
|
+
i0.ɵɵelementEnd()();
|
|
280
|
+
i0.ɵɵelementStart(8, "button", 132);
|
|
281
|
+
i0.ɵɵlistener("click", function APIKeysResourceComponent_ng_container_2_ng_container_32_ng_container_1_div_107_Template_button_click_8_listener() { i0.ɵɵrestoreView(_r17); const ctx_r1 = i0.ɵɵnextContext(4); return i0.ɵɵresetView(ctx_r1.showListView("all")); });
|
|
282
|
+
i0.ɵɵelement(9, "i", 133);
|
|
283
|
+
i0.ɵɵelementStart(10, "span");
|
|
284
|
+
i0.ɵɵtext(11, "View All Keys");
|
|
285
|
+
i0.ɵɵelementEnd()();
|
|
286
|
+
i0.ɵɵelementStart(12, "button", 132);
|
|
287
|
+
i0.ɵɵlistener("click", function APIKeysResourceComponent_ng_container_2_ng_container_32_ng_container_1_div_107_Template_button_click_12_listener() { i0.ɵɵrestoreView(_r17); const ctx_r1 = i0.ɵɵnextContext(4); return i0.ɵɵresetView(ctx_r1.showListView("expiring")); });
|
|
288
|
+
i0.ɵɵelement(13, "i", 57);
|
|
289
|
+
i0.ɵɵelementStart(14, "span");
|
|
290
|
+
i0.ɵɵtext(15, "Expiring Keys");
|
|
291
|
+
i0.ɵɵelementEnd()();
|
|
292
|
+
i0.ɵɵelementStart(16, "button", 132);
|
|
293
|
+
i0.ɵɵlistener("click", function APIKeysResourceComponent_ng_container_2_ng_container_32_ng_container_1_div_107_Template_button_click_16_listener() { i0.ɵɵrestoreView(_r17); const ctx_r1 = i0.ɵɵnextContext(4); return i0.ɵɵresetView(ctx_r1.showListView("never-used")); });
|
|
294
|
+
i0.ɵɵelement(17, "i", 84);
|
|
295
|
+
i0.ɵɵelementStart(18, "span");
|
|
296
|
+
i0.ɵɵtext(19, "Never Used");
|
|
297
|
+
i0.ɵɵelementEnd()()()();
|
|
298
|
+
} }
|
|
299
|
+
function APIKeysResourceComponent_ng_container_2_ng_container_32_ng_container_1_Template(rf, ctx) { if (rf & 1) {
|
|
300
|
+
const _r3 = i0.ɵɵgetCurrentView();
|
|
301
|
+
i0.ɵɵelementContainerStart(0);
|
|
302
|
+
i0.ɵɵelementStart(1, "div", 23)(2, "div", 24)(3, "h2", 25);
|
|
303
|
+
i0.ɵɵtext(4, "API Keys Management");
|
|
304
|
+
i0.ɵɵelementEnd();
|
|
305
|
+
i0.ɵɵelementStart(5, "p", 26);
|
|
306
|
+
i0.ɵɵtext(6, "Manage API keys for external integrations and services");
|
|
307
|
+
i0.ɵɵelementEnd()();
|
|
308
|
+
i0.ɵɵelementStart(7, "div", 27)(8, "button", 28);
|
|
309
|
+
i0.ɵɵlistener("click", function APIKeysResourceComponent_ng_container_2_ng_container_32_ng_container_1_Template_button_click_8_listener() { i0.ɵɵrestoreView(_r3); const ctx_r1 = i0.ɵɵnextContext(3); return i0.ɵɵresetView(ctx_r1.refresh()); });
|
|
310
|
+
i0.ɵɵelement(9, "i", 29);
|
|
311
|
+
i0.ɵɵelementEnd();
|
|
312
|
+
i0.ɵɵtemplate(10, APIKeysResourceComponent_ng_container_2_ng_container_32_ng_container_1_button_10_Template, 4, 0, "button", 30);
|
|
313
|
+
i0.ɵɵelementEnd()();
|
|
314
|
+
i0.ɵɵelementStart(11, "div", 31)(12, "div", 32)(13, "div", 33);
|
|
315
|
+
i0.ɵɵnamespaceSVG();
|
|
316
|
+
i0.ɵɵelementStart(14, "svg", 34);
|
|
317
|
+
i0.ɵɵelement(15, "path", 35)(16, "path", 36);
|
|
318
|
+
i0.ɵɵelementEnd();
|
|
319
|
+
i0.ɵɵnamespaceHTML();
|
|
320
|
+
i0.ɵɵelementStart(17, "div", 37);
|
|
321
|
+
i0.ɵɵtext(18);
|
|
322
|
+
i0.ɵɵelementEnd()()();
|
|
323
|
+
i0.ɵɵelementStart(19, "div", 38)(20, "div", 39);
|
|
324
|
+
i0.ɵɵtext(21);
|
|
325
|
+
i0.ɵɵelementEnd();
|
|
326
|
+
i0.ɵɵelementStart(22, "div", 40);
|
|
327
|
+
i0.ɵɵtemplate(23, APIKeysResourceComponent_ng_container_2_ng_container_32_ng_container_1_span_23_Template, 3, 1, "span", 41)(24, APIKeysResourceComponent_ng_container_2_ng_container_32_ng_container_1_span_24_Template, 3, 1, "span", 42)(25, APIKeysResourceComponent_ng_container_2_ng_container_32_ng_container_1_span_25_Template, 3, 1, "span", 43)(26, APIKeysResourceComponent_ng_container_2_ng_container_32_ng_container_1_span_26_Template, 3, 0, "span", 44);
|
|
328
|
+
i0.ɵɵelementEnd()()();
|
|
329
|
+
i0.ɵɵelementStart(27, "div", 45)(28, "div", 46);
|
|
330
|
+
i0.ɵɵlistener("click", function APIKeysResourceComponent_ng_container_2_ng_container_32_ng_container_1_Template_div_click_28_listener() { i0.ɵɵrestoreView(_r3); const ctx_r1 = i0.ɵɵnextContext(3); return i0.ɵɵresetView(ctx_r1.showListView("all")); });
|
|
331
|
+
i0.ɵɵelementStart(29, "div", 47);
|
|
332
|
+
i0.ɵɵelement(30, "i", 10);
|
|
333
|
+
i0.ɵɵelementEnd();
|
|
334
|
+
i0.ɵɵelementStart(31, "div", 48)(32, "div", 49);
|
|
335
|
+
i0.ɵɵtext(33);
|
|
336
|
+
i0.ɵɵelementEnd();
|
|
337
|
+
i0.ɵɵelementStart(34, "div", 50);
|
|
338
|
+
i0.ɵɵtext(35, "Total Keys");
|
|
339
|
+
i0.ɵɵelementEnd()();
|
|
340
|
+
i0.ɵɵelementStart(36, "div", 51);
|
|
341
|
+
i0.ɵɵelement(37, "i", 52);
|
|
342
|
+
i0.ɵɵelementEnd()();
|
|
343
|
+
i0.ɵɵelementStart(38, "div", 53);
|
|
344
|
+
i0.ɵɵlistener("click", function APIKeysResourceComponent_ng_container_2_ng_container_32_ng_container_1_Template_div_click_38_listener() { i0.ɵɵrestoreView(_r3); const ctx_r1 = i0.ɵɵnextContext(3); return i0.ɵɵresetView(ctx_r1.showListView("active")); });
|
|
345
|
+
i0.ɵɵelementStart(39, "div", 47);
|
|
346
|
+
i0.ɵɵelement(40, "i", 54);
|
|
347
|
+
i0.ɵɵelementEnd();
|
|
348
|
+
i0.ɵɵelementStart(41, "div", 48)(42, "div", 49);
|
|
349
|
+
i0.ɵɵtext(43);
|
|
350
|
+
i0.ɵɵelementEnd();
|
|
351
|
+
i0.ɵɵelementStart(44, "div", 50);
|
|
352
|
+
i0.ɵɵtext(45, "Active");
|
|
353
|
+
i0.ɵɵelementEnd()();
|
|
354
|
+
i0.ɵɵelementStart(46, "div", 55)(47, "span", 56);
|
|
355
|
+
i0.ɵɵtext(48);
|
|
356
|
+
i0.ɵɵelementEnd()()();
|
|
357
|
+
i0.ɵɵelementStart(49, "div", 46);
|
|
358
|
+
i0.ɵɵlistener("click", function APIKeysResourceComponent_ng_container_2_ng_container_32_ng_container_1_Template_div_click_49_listener() { i0.ɵɵrestoreView(_r3); const ctx_r1 = i0.ɵɵnextContext(3); return i0.ɵɵresetView(ctx_r1.showListView("expiring")); });
|
|
359
|
+
i0.ɵɵelementStart(50, "div", 47);
|
|
360
|
+
i0.ɵɵelement(51, "i", 57);
|
|
361
|
+
i0.ɵɵelementEnd();
|
|
362
|
+
i0.ɵɵelementStart(52, "div", 48)(53, "div", 49);
|
|
363
|
+
i0.ɵɵtext(54);
|
|
364
|
+
i0.ɵɵelementEnd();
|
|
365
|
+
i0.ɵɵelementStart(55, "div", 50);
|
|
366
|
+
i0.ɵɵtext(56, "Expiring Soon");
|
|
367
|
+
i0.ɵɵelementEnd()();
|
|
368
|
+
i0.ɵɵtemplate(57, APIKeysResourceComponent_ng_container_2_ng_container_32_ng_container_1_div_57_Template, 2, 0, "div", 58);
|
|
369
|
+
i0.ɵɵelementEnd();
|
|
370
|
+
i0.ɵɵelementStart(58, "div", 46);
|
|
371
|
+
i0.ɵɵlistener("click", function APIKeysResourceComponent_ng_container_2_ng_container_32_ng_container_1_Template_div_click_58_listener() { i0.ɵɵrestoreView(_r3); const ctx_r1 = i0.ɵɵnextContext(3); return i0.ɵɵresetView(ctx_r1.showListView("revoked")); });
|
|
372
|
+
i0.ɵɵelementStart(59, "div", 47);
|
|
373
|
+
i0.ɵɵelement(60, "i", 59);
|
|
374
|
+
i0.ɵɵelementEnd();
|
|
375
|
+
i0.ɵɵelementStart(61, "div", 48)(62, "div", 49);
|
|
376
|
+
i0.ɵɵtext(63);
|
|
377
|
+
i0.ɵɵelementEnd();
|
|
378
|
+
i0.ɵɵelementStart(64, "div", 50);
|
|
379
|
+
i0.ɵɵtext(65, "Revoked");
|
|
380
|
+
i0.ɵɵelementEnd()()();
|
|
381
|
+
i0.ɵɵelementStart(66, "div", 46);
|
|
382
|
+
i0.ɵɵlistener("click", function APIKeysResourceComponent_ng_container_2_ng_container_32_ng_container_1_Template_div_click_66_listener() { i0.ɵɵrestoreView(_r3); const ctx_r1 = i0.ɵɵnextContext(3); return i0.ɵɵresetView(ctx_r1.switchTab("scopes")); });
|
|
383
|
+
i0.ɵɵelementStart(67, "div", 47);
|
|
384
|
+
i0.ɵɵelement(68, "i", 16);
|
|
385
|
+
i0.ɵɵelementEnd();
|
|
386
|
+
i0.ɵɵelementStart(69, "div", 48)(70, "div", 49);
|
|
387
|
+
i0.ɵɵtext(71);
|
|
388
|
+
i0.ɵɵelementEnd();
|
|
389
|
+
i0.ɵɵelementStart(72, "div", 50);
|
|
390
|
+
i0.ɵɵtext(73, "Scope Categories");
|
|
391
|
+
i0.ɵɵelementEnd()()()();
|
|
392
|
+
i0.ɵɵelementStart(74, "div", 60)(75, "div", 61)(76, "div", 62)(77, "div", 63);
|
|
393
|
+
i0.ɵɵelement(78, "i", 10);
|
|
394
|
+
i0.ɵɵelementStart(79, "span");
|
|
395
|
+
i0.ɵɵtext(80, "Recently Used Keys");
|
|
396
|
+
i0.ɵɵelementEnd()();
|
|
397
|
+
i0.ɵɵelementStart(81, "button", 64);
|
|
398
|
+
i0.ɵɵlistener("click", function APIKeysResourceComponent_ng_container_2_ng_container_32_ng_container_1_Template_button_click_81_listener() { i0.ɵɵrestoreView(_r3); const ctx_r1 = i0.ɵɵnextContext(3); return i0.ɵɵresetView(ctx_r1.showListView("all")); });
|
|
399
|
+
i0.ɵɵtext(82, " View All ");
|
|
400
|
+
i0.ɵɵelement(83, "i", 52);
|
|
401
|
+
i0.ɵɵelementEnd()();
|
|
402
|
+
i0.ɵɵelementStart(84, "div", 65);
|
|
403
|
+
i0.ɵɵtemplate(85, APIKeysResourceComponent_ng_container_2_ng_container_32_ng_container_1_div_85_Template, 2, 1, "div", 66)(86, APIKeysResourceComponent_ng_container_2_ng_container_32_ng_container_1_div_86_Template, 4, 0, "div", 67);
|
|
404
|
+
i0.ɵɵelementEnd()();
|
|
405
|
+
i0.ɵɵelementStart(87, "div", 68)(88, "div", 62)(89, "div", 63);
|
|
406
|
+
i0.ɵɵelement(90, "i", 16);
|
|
407
|
+
i0.ɵɵelementStart(91, "span");
|
|
408
|
+
i0.ɵɵtext(92, "Permission Scopes");
|
|
409
|
+
i0.ɵɵelementEnd()();
|
|
410
|
+
i0.ɵɵelementStart(93, "span", 69);
|
|
411
|
+
i0.ɵɵtext(94, "Available scope categories");
|
|
412
|
+
i0.ɵɵelementEnd()();
|
|
413
|
+
i0.ɵɵelementStart(95, "div", 65);
|
|
414
|
+
i0.ɵɵtemplate(96, APIKeysResourceComponent_ng_container_2_ng_container_32_ng_container_1_div_96_Template, 12, 3, "div", 70)(97, APIKeysResourceComponent_ng_container_2_ng_container_32_ng_container_1_div_97_Template, 4, 0, "div", 67);
|
|
415
|
+
i0.ɵɵelementEnd()();
|
|
416
|
+
i0.ɵɵelementStart(98, "div", 71)(99, "div", 62)(100, "div", 63);
|
|
417
|
+
i0.ɵɵelement(101, "i", 72);
|
|
418
|
+
i0.ɵɵelementStart(102, "span");
|
|
419
|
+
i0.ɵɵtext(103, "Recent Activity");
|
|
420
|
+
i0.ɵɵelementEnd()()();
|
|
421
|
+
i0.ɵɵelementStart(104, "div", 65);
|
|
422
|
+
i0.ɵɵtemplate(105, APIKeysResourceComponent_ng_container_2_ng_container_32_ng_container_1_div_105_Template, 2, 1, "div", 73)(106, APIKeysResourceComponent_ng_container_2_ng_container_32_ng_container_1_div_106_Template, 4, 0, "div", 67);
|
|
423
|
+
i0.ɵɵelementEnd()()();
|
|
424
|
+
i0.ɵɵtemplate(107, APIKeysResourceComponent_ng_container_2_ng_container_32_ng_container_1_div_107_Template, 20, 0, "div", 74);
|
|
425
|
+
i0.ɵɵelementStart(108, "div", 75);
|
|
426
|
+
i0.ɵɵelement(109, "i", 76);
|
|
427
|
+
i0.ɵɵelementStart(110, "div", 77)(111, "strong");
|
|
428
|
+
i0.ɵɵtext(112, "Security Best Practices:");
|
|
429
|
+
i0.ɵɵelementEnd();
|
|
430
|
+
i0.ɵɵtext(113, " API keys should be rotated regularly and revoked when no longer needed. Keys are hashed and stored securely - the raw key is only shown once at creation time. All API key usage is logged for audit purposes. ");
|
|
431
|
+
i0.ɵɵelementEnd()();
|
|
432
|
+
i0.ɵɵelementContainerEnd();
|
|
433
|
+
} if (rf & 2) {
|
|
434
|
+
const ctx_r1 = i0.ɵɵnextContext(3);
|
|
435
|
+
i0.ɵɵadvance(10);
|
|
436
|
+
i0.ɵɵproperty("ngIf", ctx_r1.UserCanCreateKeys);
|
|
437
|
+
i0.ɵɵadvance();
|
|
438
|
+
i0.ɵɵproperty("ngClass", ctx_r1.getHealthClass());
|
|
439
|
+
i0.ɵɵadvance(5);
|
|
440
|
+
i0.ɵɵattribute("stroke-dasharray", ctx_r1.getHealthScore() + ", 100");
|
|
441
|
+
i0.ɵɵadvance(2);
|
|
442
|
+
i0.ɵɵtextInterpolate(ctx_r1.getHealthScore());
|
|
443
|
+
i0.ɵɵadvance(3);
|
|
444
|
+
i0.ɵɵtextInterpolate(ctx_r1.getHealthLabel());
|
|
445
|
+
i0.ɵɵadvance(2);
|
|
446
|
+
i0.ɵɵproperty("ngIf", ctx_r1.ExpiredKeys > 0);
|
|
447
|
+
i0.ɵɵadvance();
|
|
448
|
+
i0.ɵɵproperty("ngIf", ctx_r1.ExpiringSoonCount > 0);
|
|
449
|
+
i0.ɵɵadvance();
|
|
450
|
+
i0.ɵɵproperty("ngIf", ctx_r1.NeverUsedKeys > 0);
|
|
451
|
+
i0.ɵɵadvance();
|
|
452
|
+
i0.ɵɵproperty("ngIf", ctx_r1.ExpiredKeys === 0 && ctx_r1.ExpiringSoonCount === 0 && ctx_r1.NeverUsedKeys === 0);
|
|
453
|
+
i0.ɵɵadvance(7);
|
|
454
|
+
i0.ɵɵtextInterpolate(ctx_r1.TotalKeys);
|
|
455
|
+
i0.ɵɵadvance(10);
|
|
456
|
+
i0.ɵɵtextInterpolate(ctx_r1.ActiveKeys);
|
|
457
|
+
i0.ɵɵadvance(5);
|
|
458
|
+
i0.ɵɵtextInterpolate1("", ctx_r1.TotalKeys > 0 ? (ctx_r1.ActiveKeys / ctx_r1.TotalKeys * 100).toFixed(0) : 100, "%");
|
|
459
|
+
i0.ɵɵadvance();
|
|
460
|
+
i0.ɵɵclassProp("warning", ctx_r1.ExpiringSoonCount > 0);
|
|
461
|
+
i0.ɵɵadvance(5);
|
|
462
|
+
i0.ɵɵtextInterpolate(ctx_r1.ExpiringSoonCount);
|
|
463
|
+
i0.ɵɵadvance(3);
|
|
464
|
+
i0.ɵɵproperty("ngIf", ctx_r1.ExpiringSoonCount > 0);
|
|
465
|
+
i0.ɵɵadvance();
|
|
466
|
+
i0.ɵɵclassProp("danger", ctx_r1.RevokedKeys > 0);
|
|
467
|
+
i0.ɵɵadvance(5);
|
|
468
|
+
i0.ɵɵtextInterpolate(ctx_r1.RevokedKeys);
|
|
469
|
+
i0.ɵɵadvance(8);
|
|
470
|
+
i0.ɵɵtextInterpolate(ctx_r1.ScopeStats.length);
|
|
471
|
+
i0.ɵɵadvance(14);
|
|
472
|
+
i0.ɵɵproperty("ngIf", ctx_r1.TopUsedKeys.length > 0);
|
|
473
|
+
i0.ɵɵadvance();
|
|
474
|
+
i0.ɵɵproperty("ngIf", ctx_r1.TopUsedKeys.length === 0);
|
|
475
|
+
i0.ɵɵadvance(10);
|
|
476
|
+
i0.ɵɵproperty("ngIf", ctx_r1.ScopeStats.length > 0);
|
|
477
|
+
i0.ɵɵadvance();
|
|
478
|
+
i0.ɵɵproperty("ngIf", ctx_r1.ScopeStats.length === 0);
|
|
479
|
+
i0.ɵɵadvance(8);
|
|
480
|
+
i0.ɵɵproperty("ngIf", ctx_r1.RecentActivity.length > 0);
|
|
481
|
+
i0.ɵɵadvance();
|
|
482
|
+
i0.ɵɵproperty("ngIf", ctx_r1.RecentActivity.length === 0);
|
|
483
|
+
i0.ɵɵadvance();
|
|
484
|
+
i0.ɵɵproperty("ngIf", ctx_r1.UserCanCreateKeys);
|
|
485
|
+
} }
|
|
486
|
+
function APIKeysResourceComponent_ng_container_2_ng_container_32_ng_container_2_Template(rf, ctx) { if (rf & 1) {
|
|
487
|
+
const _r18 = i0.ɵɵgetCurrentView();
|
|
488
|
+
i0.ɵɵelementContainerStart(0);
|
|
489
|
+
i0.ɵɵelementStart(1, "div", 134)(2, "button", 135);
|
|
490
|
+
i0.ɵɵlistener("click", function APIKeysResourceComponent_ng_container_2_ng_container_32_ng_container_2_Template_button_click_2_listener() { i0.ɵɵrestoreView(_r18); const ctx_r1 = i0.ɵɵnextContext(3); return i0.ɵɵresetView(ctx_r1.showOverview()); });
|
|
491
|
+
i0.ɵɵelement(3, "i", 136);
|
|
492
|
+
i0.ɵɵtext(4, " Back to Overview ");
|
|
493
|
+
i0.ɵɵelementEnd()();
|
|
494
|
+
i0.ɵɵelementStart(5, "mj-api-key-list", 137, 0);
|
|
495
|
+
i0.ɵɵlistener("KeySelected", function APIKeysResourceComponent_ng_container_2_ng_container_32_ng_container_2_Template_mj_api_key_list_KeySelected_5_listener($event) { i0.ɵɵrestoreView(_r18); const ctx_r1 = i0.ɵɵnextContext(3); return i0.ɵɵresetView(ctx_r1.onKeySelected($event)); })("CreateRequested", function APIKeysResourceComponent_ng_container_2_ng_container_32_ng_container_2_Template_mj_api_key_list_CreateRequested_5_listener() { i0.ɵɵrestoreView(_r18); const ctx_r1 = i0.ɵɵnextContext(3); return i0.ɵɵresetView(ctx_r1.openCreateDialog()); });
|
|
496
|
+
i0.ɵɵelementEnd();
|
|
497
|
+
i0.ɵɵelementContainerEnd();
|
|
498
|
+
} if (rf & 2) {
|
|
499
|
+
const ctx_r1 = i0.ɵɵnextContext(3);
|
|
500
|
+
i0.ɵɵadvance(5);
|
|
501
|
+
i0.ɵɵproperty("Filter", ctx_r1.ListFilter);
|
|
502
|
+
} }
|
|
503
|
+
function APIKeysResourceComponent_ng_container_2_ng_container_32_Template(rf, ctx) { if (rf & 1) {
|
|
504
|
+
i0.ɵɵelementContainerStart(0);
|
|
505
|
+
i0.ɵɵtemplate(1, APIKeysResourceComponent_ng_container_2_ng_container_32_ng_container_1_Template, 114, 27, "ng-container", 3)(2, APIKeysResourceComponent_ng_container_2_ng_container_32_ng_container_2_Template, 7, 1, "ng-container", 3);
|
|
506
|
+
i0.ɵɵelementContainerEnd();
|
|
507
|
+
} if (rf & 2) {
|
|
508
|
+
const ctx_r1 = i0.ɵɵnextContext(2);
|
|
509
|
+
i0.ɵɵadvance();
|
|
510
|
+
i0.ɵɵproperty("ngIf", ctx_r1.CurrentView === "overview");
|
|
511
|
+
i0.ɵɵadvance();
|
|
512
|
+
i0.ɵɵproperty("ngIf", ctx_r1.CurrentView === "list");
|
|
513
|
+
} }
|
|
514
|
+
function APIKeysResourceComponent_ng_container_2_ng_container_33_Template(rf, ctx) { if (rf & 1) {
|
|
515
|
+
const _r19 = i0.ɵɵgetCurrentView();
|
|
516
|
+
i0.ɵɵelementContainerStart(0);
|
|
517
|
+
i0.ɵɵelementStart(1, "mj-api-applications-panel", 138);
|
|
518
|
+
i0.ɵɵlistener("ApplicationUpdated", function APIKeysResourceComponent_ng_container_2_ng_container_33_Template_mj_api_applications_panel_ApplicationUpdated_1_listener() { i0.ɵɵrestoreView(_r19); const ctx_r1 = i0.ɵɵnextContext(2); return i0.ɵɵresetView(ctx_r1.onDataUpdated()); });
|
|
519
|
+
i0.ɵɵelementEnd();
|
|
520
|
+
i0.ɵɵelementContainerEnd();
|
|
521
|
+
} }
|
|
522
|
+
function APIKeysResourceComponent_ng_container_2_ng_container_34_Template(rf, ctx) { if (rf & 1) {
|
|
523
|
+
const _r20 = i0.ɵɵgetCurrentView();
|
|
524
|
+
i0.ɵɵelementContainerStart(0);
|
|
525
|
+
i0.ɵɵelementStart(1, "mj-api-scopes-panel", 139);
|
|
526
|
+
i0.ɵɵlistener("ScopeUpdated", function APIKeysResourceComponent_ng_container_2_ng_container_34_Template_mj_api_scopes_panel_ScopeUpdated_1_listener() { i0.ɵɵrestoreView(_r20); const ctx_r1 = i0.ɵɵnextContext(2); return i0.ɵɵresetView(ctx_r1.onDataUpdated()); });
|
|
527
|
+
i0.ɵɵelementEnd();
|
|
528
|
+
i0.ɵɵelementContainerEnd();
|
|
529
|
+
} }
|
|
530
|
+
function APIKeysResourceComponent_ng_container_2_ng_container_35_Template(rf, ctx) { if (rf & 1) {
|
|
531
|
+
i0.ɵɵelementContainerStart(0);
|
|
532
|
+
i0.ɵɵelement(1, "mj-api-usage-panel");
|
|
533
|
+
i0.ɵɵelementContainerEnd();
|
|
534
|
+
} }
|
|
535
|
+
function APIKeysResourceComponent_ng_container_2_Template(rf, ctx) { if (rf & 1) {
|
|
536
|
+
const _r1 = i0.ɵɵgetCurrentView();
|
|
537
|
+
i0.ɵɵelementContainerStart(0);
|
|
538
|
+
i0.ɵɵelementStart(1, "div", 7)(2, "div", 8)(3, "div", 9);
|
|
539
|
+
i0.ɵɵelement(4, "i", 10);
|
|
540
|
+
i0.ɵɵelementStart(5, "span");
|
|
541
|
+
i0.ɵɵtext(6, "API Keys");
|
|
542
|
+
i0.ɵɵelementEnd()()();
|
|
543
|
+
i0.ɵɵelementStart(7, "div", 11)(8, "button", 12);
|
|
544
|
+
i0.ɵɵlistener("click", function APIKeysResourceComponent_ng_container_2_Template_button_click_8_listener() { i0.ɵɵrestoreView(_r1); const ctx_r1 = i0.ɵɵnextContext(); ctx_r1.switchTab("keys"); return i0.ɵɵresetView(ctx_r1.closeNav()); });
|
|
545
|
+
i0.ɵɵelement(9, "i", 10);
|
|
546
|
+
i0.ɵɵelementStart(10, "span", 13);
|
|
547
|
+
i0.ɵɵtext(11, "API Keys");
|
|
548
|
+
i0.ɵɵelementEnd();
|
|
549
|
+
i0.ɵɵelementStart(12, "span", 14);
|
|
550
|
+
i0.ɵɵtext(13);
|
|
551
|
+
i0.ɵɵelementEnd()();
|
|
552
|
+
i0.ɵɵelementStart(14, "button", 12);
|
|
553
|
+
i0.ɵɵlistener("click", function APIKeysResourceComponent_ng_container_2_Template_button_click_14_listener() { i0.ɵɵrestoreView(_r1); const ctx_r1 = i0.ɵɵnextContext(); ctx_r1.switchTab("applications"); return i0.ɵɵresetView(ctx_r1.closeNav()); });
|
|
554
|
+
i0.ɵɵelement(15, "i", 15);
|
|
555
|
+
i0.ɵɵelementStart(16, "span", 13);
|
|
556
|
+
i0.ɵɵtext(17, "Applications");
|
|
557
|
+
i0.ɵɵelementEnd();
|
|
558
|
+
i0.ɵɵelementStart(18, "span", 14);
|
|
559
|
+
i0.ɵɵtext(19);
|
|
560
|
+
i0.ɵɵelementEnd()();
|
|
561
|
+
i0.ɵɵelementStart(20, "button", 12);
|
|
562
|
+
i0.ɵɵlistener("click", function APIKeysResourceComponent_ng_container_2_Template_button_click_20_listener() { i0.ɵɵrestoreView(_r1); const ctx_r1 = i0.ɵɵnextContext(); ctx_r1.switchTab("scopes"); return i0.ɵɵresetView(ctx_r1.closeNav()); });
|
|
563
|
+
i0.ɵɵelement(21, "i", 16);
|
|
564
|
+
i0.ɵɵelementStart(22, "span", 13);
|
|
565
|
+
i0.ɵɵtext(23, "Scopes");
|
|
566
|
+
i0.ɵɵelementEnd();
|
|
567
|
+
i0.ɵɵelementStart(24, "span", 14);
|
|
568
|
+
i0.ɵɵtext(25);
|
|
569
|
+
i0.ɵɵelementEnd()();
|
|
570
|
+
i0.ɵɵelementStart(26, "button", 12);
|
|
571
|
+
i0.ɵɵlistener("click", function APIKeysResourceComponent_ng_container_2_Template_button_click_26_listener() { i0.ɵɵrestoreView(_r1); const ctx_r1 = i0.ɵɵnextContext(); ctx_r1.switchTab("usage"); return i0.ɵɵresetView(ctx_r1.closeNav()); });
|
|
572
|
+
i0.ɵɵelement(27, "i", 17);
|
|
573
|
+
i0.ɵɵelementStart(28, "span", 13);
|
|
574
|
+
i0.ɵɵtext(29, "Usage Analytics");
|
|
575
|
+
i0.ɵɵelementEnd()()()();
|
|
576
|
+
i0.ɵɵelementStart(30, "div", 18)(31, "div", 19);
|
|
577
|
+
i0.ɵɵtemplate(32, APIKeysResourceComponent_ng_container_2_ng_container_32_Template, 3, 2, "ng-container", 3)(33, APIKeysResourceComponent_ng_container_2_ng_container_33_Template, 2, 0, "ng-container", 3)(34, APIKeysResourceComponent_ng_container_2_ng_container_34_Template, 2, 0, "ng-container", 3)(35, APIKeysResourceComponent_ng_container_2_ng_container_35_Template, 2, 0, "ng-container", 3);
|
|
578
|
+
i0.ɵɵelementEnd()();
|
|
579
|
+
i0.ɵɵelementStart(36, "button", 20);
|
|
580
|
+
i0.ɵɵlistener("click", function APIKeysResourceComponent_ng_container_2_Template_button_click_36_listener() { i0.ɵɵrestoreView(_r1); const ctx_r1 = i0.ɵɵnextContext(); return i0.ɵɵresetView(ctx_r1.toggleNav()); });
|
|
581
|
+
i0.ɵɵelement(37, "i", 21);
|
|
582
|
+
i0.ɵɵelementEnd();
|
|
583
|
+
i0.ɵɵelementStart(38, "div", 22);
|
|
584
|
+
i0.ɵɵlistener("click", function APIKeysResourceComponent_ng_container_2_Template_div_click_38_listener() { i0.ɵɵrestoreView(_r1); const ctx_r1 = i0.ɵɵnextContext(); return i0.ɵɵresetView(ctx_r1.closeNav()); });
|
|
585
|
+
i0.ɵɵelementEnd();
|
|
586
|
+
i0.ɵɵelementContainerEnd();
|
|
587
|
+
} if (rf & 2) {
|
|
588
|
+
const ctx_r1 = i0.ɵɵnextContext();
|
|
589
|
+
i0.ɵɵadvance(8);
|
|
590
|
+
i0.ɵɵclassProp("active", ctx_r1.MainTab === "keys");
|
|
591
|
+
i0.ɵɵadvance(5);
|
|
592
|
+
i0.ɵɵtextInterpolate(ctx_r1.TotalKeys);
|
|
593
|
+
i0.ɵɵadvance();
|
|
594
|
+
i0.ɵɵclassProp("active", ctx_r1.MainTab === "applications");
|
|
595
|
+
i0.ɵɵadvance(5);
|
|
596
|
+
i0.ɵɵtextInterpolate(ctx_r1.ApplicationCount);
|
|
597
|
+
i0.ɵɵadvance();
|
|
598
|
+
i0.ɵɵclassProp("active", ctx_r1.MainTab === "scopes");
|
|
599
|
+
i0.ɵɵadvance(5);
|
|
600
|
+
i0.ɵɵtextInterpolate(ctx_r1.ScopeCount);
|
|
601
|
+
i0.ɵɵadvance();
|
|
602
|
+
i0.ɵɵclassProp("active", ctx_r1.MainTab === "usage");
|
|
603
|
+
i0.ɵɵadvance(6);
|
|
604
|
+
i0.ɵɵproperty("ngIf", ctx_r1.MainTab === "keys");
|
|
605
|
+
i0.ɵɵadvance();
|
|
606
|
+
i0.ɵɵproperty("ngIf", ctx_r1.MainTab === "applications");
|
|
607
|
+
i0.ɵɵadvance();
|
|
608
|
+
i0.ɵɵproperty("ngIf", ctx_r1.MainTab === "scopes");
|
|
609
|
+
i0.ɵɵadvance();
|
|
610
|
+
i0.ɵɵproperty("ngIf", ctx_r1.MainTab === "usage");
|
|
611
|
+
i0.ɵɵadvance(2);
|
|
612
|
+
i0.ɵɵclassProp("fa-bars", !ctx_r1.NavOpen)("fa-times", ctx_r1.NavOpen);
|
|
613
|
+
} }
|
|
614
|
+
/** Tree shaking prevention function */
|
|
615
|
+
export function LoadAPIKeysResource() {
|
|
616
|
+
// This function prevents tree shaking
|
|
617
|
+
}
|
|
618
|
+
/**
|
|
619
|
+
* API Keys Resource Component
|
|
620
|
+
* Provides management interface for MJ API Keys including:
|
|
621
|
+
* - Overview statistics and health monitoring
|
|
622
|
+
* - Key listing with status filters
|
|
623
|
+
* - Key creation and revocation
|
|
624
|
+
* - Usage tracking and analytics
|
|
625
|
+
*/
|
|
626
|
+
let APIKeysResourceComponent = class APIKeysResourceComponent extends BaseResourceComponent {
|
|
627
|
+
keyListComponent;
|
|
628
|
+
destroy$ = new Subject();
|
|
629
|
+
md = new Metadata();
|
|
630
|
+
cdr;
|
|
631
|
+
// View state
|
|
632
|
+
CurrentView = 'overview';
|
|
633
|
+
ListFilter = 'all';
|
|
634
|
+
MainTab = 'keys';
|
|
635
|
+
NavOpen = false;
|
|
636
|
+
// Application and scope counts for tab badges
|
|
637
|
+
ApplicationCount = 0;
|
|
638
|
+
ScopeCount = 0;
|
|
639
|
+
// Loading states
|
|
640
|
+
IsLoading = true;
|
|
641
|
+
// Statistics
|
|
642
|
+
TotalKeys = 0;
|
|
643
|
+
ActiveKeys = 0;
|
|
644
|
+
RevokedKeys = 0;
|
|
645
|
+
ExpiringSoonCount = 0;
|
|
646
|
+
ExpiredKeys = 0;
|
|
647
|
+
NeverUsedKeys = 0;
|
|
648
|
+
// Data collections
|
|
649
|
+
APIKeys = [];
|
|
650
|
+
RecentActivity = [];
|
|
651
|
+
ScopeStats = [];
|
|
652
|
+
TopUsedKeys = [];
|
|
653
|
+
// Dialog states
|
|
654
|
+
ShowCreateDialog = false;
|
|
655
|
+
ShowEditPanel = false;
|
|
656
|
+
SelectedKeyId = null;
|
|
657
|
+
// Scope category colors
|
|
658
|
+
categoryColors = {
|
|
659
|
+
'Entities': '#6366f1',
|
|
660
|
+
'Agents': '#10b981',
|
|
661
|
+
'Admin': '#f59e0b',
|
|
662
|
+
'Actions': '#8b5cf6',
|
|
663
|
+
'Queries': '#3b82f6',
|
|
664
|
+
'Reports': '#ef4444',
|
|
665
|
+
'Communication': '#ec4899',
|
|
666
|
+
'Other': '#6b7280'
|
|
667
|
+
};
|
|
668
|
+
categoryIcons = {
|
|
669
|
+
'Entities': 'fa-solid fa-database',
|
|
670
|
+
'Agents': 'fa-solid fa-robot',
|
|
671
|
+
'Admin': 'fa-solid fa-shield-halved',
|
|
672
|
+
'Actions': 'fa-solid fa-bolt',
|
|
673
|
+
'Queries': 'fa-solid fa-magnifying-glass',
|
|
674
|
+
'Reports': 'fa-solid fa-chart-bar',
|
|
675
|
+
'Communication': 'fa-solid fa-envelope',
|
|
676
|
+
'Other': 'fa-solid fa-ellipsis'
|
|
677
|
+
};
|
|
678
|
+
// User permissions
|
|
679
|
+
UserCanCreateKeys = false;
|
|
680
|
+
UserCanRevokeKeys = false;
|
|
681
|
+
constructor(cdr) {
|
|
682
|
+
super();
|
|
683
|
+
this.cdr = cdr;
|
|
684
|
+
}
|
|
685
|
+
async ngOnInit() {
|
|
686
|
+
await this.loadData();
|
|
687
|
+
}
|
|
688
|
+
ngOnDestroy() {
|
|
689
|
+
this.destroy$.next();
|
|
690
|
+
this.destroy$.complete();
|
|
691
|
+
}
|
|
692
|
+
async GetResourceDisplayName(_data) {
|
|
693
|
+
return 'API Keys';
|
|
694
|
+
}
|
|
695
|
+
async GetResourceIconClass(_data) {
|
|
696
|
+
return 'fa-solid fa-key';
|
|
697
|
+
}
|
|
698
|
+
/**
|
|
699
|
+
* Load all dashboard data
|
|
700
|
+
*/
|
|
701
|
+
async loadData() {
|
|
702
|
+
this.IsLoading = true;
|
|
703
|
+
this.cdr.markForCheck();
|
|
704
|
+
try {
|
|
705
|
+
await Promise.all([
|
|
706
|
+
this.loadAPIKeys(),
|
|
707
|
+
this.loadScopeStats(),
|
|
708
|
+
this.loadRecentActivity(),
|
|
709
|
+
this.loadCounts(),
|
|
710
|
+
this.checkPermissions()
|
|
711
|
+
]);
|
|
712
|
+
this.calculateStatistics();
|
|
713
|
+
}
|
|
714
|
+
catch (error) {
|
|
715
|
+
console.error('Error loading API Keys dashboard data:', error);
|
|
716
|
+
}
|
|
717
|
+
finally {
|
|
718
|
+
this.IsLoading = false;
|
|
719
|
+
this.NotifyLoadComplete();
|
|
720
|
+
this.cdr.markForCheck();
|
|
721
|
+
}
|
|
722
|
+
}
|
|
723
|
+
/**
|
|
724
|
+
* Load application and scope counts for tab badges
|
|
725
|
+
*/
|
|
726
|
+
async loadCounts() {
|
|
727
|
+
const rv = new RunView();
|
|
728
|
+
const [appsResult, scopesResult] = await rv.RunViews([
|
|
729
|
+
{
|
|
730
|
+
EntityName: 'MJ: API Applications',
|
|
731
|
+
ResultType: 'simple',
|
|
732
|
+
Fields: ['ID']
|
|
733
|
+
},
|
|
734
|
+
{
|
|
735
|
+
EntityName: 'MJ: API Scopes',
|
|
736
|
+
ResultType: 'simple',
|
|
737
|
+
Fields: ['ID']
|
|
738
|
+
}
|
|
739
|
+
]);
|
|
740
|
+
this.ApplicationCount = appsResult.Success ? appsResult.Results.length : 0;
|
|
741
|
+
this.ScopeCount = scopesResult.Success ? scopesResult.Results.length : 0;
|
|
742
|
+
}
|
|
743
|
+
/**
|
|
744
|
+
* Load all API keys
|
|
745
|
+
*/
|
|
746
|
+
async loadAPIKeys() {
|
|
747
|
+
const rv = new RunView();
|
|
748
|
+
const result = await rv.RunView({
|
|
749
|
+
EntityName: 'MJ: API Keys',
|
|
750
|
+
OrderBy: '__mj_CreatedAt DESC',
|
|
751
|
+
ResultType: 'entity_object'
|
|
752
|
+
});
|
|
753
|
+
if (result.Success) {
|
|
754
|
+
this.APIKeys = result.Results;
|
|
755
|
+
}
|
|
756
|
+
}
|
|
757
|
+
/**
|
|
758
|
+
* Load scope statistics by category
|
|
759
|
+
*/
|
|
760
|
+
async loadScopeStats() {
|
|
761
|
+
const rv = new RunView();
|
|
762
|
+
const result = await rv.RunView({
|
|
763
|
+
EntityName: 'MJ: API Scopes',
|
|
764
|
+
OrderBy: 'Category, Name',
|
|
765
|
+
ResultType: 'entity_object'
|
|
766
|
+
});
|
|
767
|
+
if (result.Success) {
|
|
768
|
+
const categoryMap = new Map();
|
|
769
|
+
for (const scope of result.Results) {
|
|
770
|
+
const category = scope.Category || 'Other';
|
|
771
|
+
categoryMap.set(category, (categoryMap.get(category) || 0) + 1);
|
|
772
|
+
}
|
|
773
|
+
const total = result.Results.length;
|
|
774
|
+
this.ScopeStats = Array.from(categoryMap.entries()).map(([category, count]) => ({
|
|
775
|
+
category,
|
|
776
|
+
count,
|
|
777
|
+
percentage: total > 0 ? Math.round((count / total) * 100) : 0,
|
|
778
|
+
color: this.categoryColors[category] || this.categoryColors['Other'],
|
|
779
|
+
iconClass: this.categoryIcons[category] || this.categoryIcons['Other']
|
|
780
|
+
}));
|
|
781
|
+
}
|
|
782
|
+
}
|
|
783
|
+
/**
|
|
784
|
+
* Load recent activity from usage logs and key changes
|
|
785
|
+
*/
|
|
786
|
+
async loadRecentActivity() {
|
|
787
|
+
const rv = new RunView();
|
|
788
|
+
// Load usage logs
|
|
789
|
+
const usageResult = await rv.RunView({
|
|
790
|
+
EntityName: 'MJ: API Key Usage Logs',
|
|
791
|
+
OrderBy: '__mj_CreatedAt DESC',
|
|
792
|
+
MaxRows: 20,
|
|
793
|
+
ResultType: 'entity_object'
|
|
794
|
+
});
|
|
795
|
+
const activities = [];
|
|
796
|
+
if (usageResult.Success) {
|
|
797
|
+
for (const log of usageResult.Results.slice(0, 10)) {
|
|
798
|
+
activities.push({
|
|
799
|
+
keyLabel: log.APIKey || 'Unknown Key',
|
|
800
|
+
action: 'Used',
|
|
801
|
+
user: log.APIKey || 'System',
|
|
802
|
+
date: log.__mj_CreatedAt,
|
|
803
|
+
keyId: log.APIKeyID
|
|
804
|
+
});
|
|
805
|
+
}
|
|
806
|
+
}
|
|
807
|
+
// Add key creation/update activities from keys
|
|
808
|
+
for (const key of this.APIKeys.slice(0, 10)) {
|
|
809
|
+
if (key.Status === 'Revoked') {
|
|
810
|
+
activities.push({
|
|
811
|
+
keyLabel: key.Label,
|
|
812
|
+
action: 'Revoked',
|
|
813
|
+
user: key.User,
|
|
814
|
+
date: key.__mj_UpdatedAt,
|
|
815
|
+
keyId: key.ID
|
|
816
|
+
});
|
|
817
|
+
}
|
|
818
|
+
else {
|
|
819
|
+
activities.push({
|
|
820
|
+
keyLabel: key.Label,
|
|
821
|
+
action: 'Created',
|
|
822
|
+
user: key.CreatedByUser,
|
|
823
|
+
date: key.__mj_CreatedAt,
|
|
824
|
+
keyId: key.ID
|
|
825
|
+
});
|
|
826
|
+
}
|
|
827
|
+
}
|
|
828
|
+
// Sort by date and take top 10
|
|
829
|
+
this.RecentActivity = activities
|
|
830
|
+
.sort((a, b) => b.date.getTime() - a.date.getTime())
|
|
831
|
+
.slice(0, 10);
|
|
832
|
+
}
|
|
833
|
+
/**
|
|
834
|
+
* Check user permissions
|
|
835
|
+
*/
|
|
836
|
+
async checkPermissions() {
|
|
837
|
+
// Check if user can create/manage API keys
|
|
838
|
+
const entityInfo = this.md.Entities.find(e => e.Name === 'MJ: API Keys');
|
|
839
|
+
if (entityInfo) {
|
|
840
|
+
const permissions = entityInfo.GetUserPermisions(this.md.CurrentUser);
|
|
841
|
+
this.UserCanCreateKeys = permissions.CanCreate;
|
|
842
|
+
this.UserCanRevokeKeys = permissions.CanUpdate;
|
|
843
|
+
}
|
|
844
|
+
}
|
|
845
|
+
/**
|
|
846
|
+
* Calculate dashboard statistics from loaded data
|
|
847
|
+
*/
|
|
848
|
+
calculateStatistics() {
|
|
849
|
+
const now = new Date();
|
|
850
|
+
const thirtyDaysFromNow = new Date(now.getTime() + 30 * 24 * 60 * 60 * 1000);
|
|
851
|
+
this.TotalKeys = this.APIKeys.length;
|
|
852
|
+
this.ActiveKeys = this.APIKeys.filter(k => k.Status === 'Active').length;
|
|
853
|
+
this.RevokedKeys = this.APIKeys.filter(k => k.Status === 'Revoked').length;
|
|
854
|
+
this.ExpiredKeys = this.APIKeys.filter(k => k.ExpiresAt && new Date(k.ExpiresAt) < now).length;
|
|
855
|
+
this.ExpiringSoonCount = this.APIKeys.filter(k => k.Status === 'Active' &&
|
|
856
|
+
k.ExpiresAt &&
|
|
857
|
+
new Date(k.ExpiresAt) > now &&
|
|
858
|
+
new Date(k.ExpiresAt) < thirtyDaysFromNow).length;
|
|
859
|
+
this.NeverUsedKeys = this.APIKeys.filter(k => k.Status === 'Active' && !k.LastUsedAt).length;
|
|
860
|
+
// Get top used keys (most recently used active keys)
|
|
861
|
+
this.TopUsedKeys = this.APIKeys
|
|
862
|
+
.filter(k => k.Status === 'Active' && k.LastUsedAt)
|
|
863
|
+
.sort((a, b) => {
|
|
864
|
+
const aDate = a.LastUsedAt ? new Date(a.LastUsedAt).getTime() : 0;
|
|
865
|
+
const bDate = b.LastUsedAt ? new Date(b.LastUsedAt).getTime() : 0;
|
|
866
|
+
return bDate - aDate;
|
|
867
|
+
})
|
|
868
|
+
.slice(0, 5);
|
|
869
|
+
}
|
|
870
|
+
/**
|
|
871
|
+
* Refresh all data
|
|
872
|
+
*/
|
|
873
|
+
async refresh() {
|
|
874
|
+
await this.loadData();
|
|
875
|
+
if (this.keyListComponent) {
|
|
876
|
+
await this.keyListComponent.loadKeys();
|
|
877
|
+
}
|
|
878
|
+
}
|
|
879
|
+
/**
|
|
880
|
+
* Switch to list view
|
|
881
|
+
*/
|
|
882
|
+
showListView(filter = 'all') {
|
|
883
|
+
this.ListFilter = filter;
|
|
884
|
+
this.CurrentView = 'list';
|
|
885
|
+
}
|
|
886
|
+
/**
|
|
887
|
+
* Switch to overview
|
|
888
|
+
*/
|
|
889
|
+
showOverview() {
|
|
890
|
+
this.CurrentView = 'overview';
|
|
891
|
+
}
|
|
892
|
+
/**
|
|
893
|
+
* Open create dialog
|
|
894
|
+
*/
|
|
895
|
+
openCreateDialog() {
|
|
896
|
+
this.ShowCreateDialog = true;
|
|
897
|
+
}
|
|
898
|
+
/**
|
|
899
|
+
* Handle key created
|
|
900
|
+
*/
|
|
901
|
+
async onKeyCreated(result) {
|
|
902
|
+
if (result.success) {
|
|
903
|
+
await this.refresh();
|
|
904
|
+
}
|
|
905
|
+
}
|
|
906
|
+
/**
|
|
907
|
+
* Handle create dialog closed
|
|
908
|
+
*/
|
|
909
|
+
onCreateDialogClosed() {
|
|
910
|
+
this.ShowCreateDialog = false;
|
|
911
|
+
}
|
|
912
|
+
/**
|
|
913
|
+
* Open edit panel for a key
|
|
914
|
+
*/
|
|
915
|
+
openEditPanel(key) {
|
|
916
|
+
this.SelectedKeyId = key.ID;
|
|
917
|
+
this.ShowEditPanel = true;
|
|
918
|
+
}
|
|
919
|
+
/**
|
|
920
|
+
* Handle key from list selected
|
|
921
|
+
*/
|
|
922
|
+
onKeySelected(key) {
|
|
923
|
+
this.openEditPanel(key);
|
|
924
|
+
}
|
|
925
|
+
/**
|
|
926
|
+
* Handle key updated
|
|
927
|
+
*/
|
|
928
|
+
async onKeyUpdated() {
|
|
929
|
+
await this.refresh();
|
|
930
|
+
}
|
|
931
|
+
/**
|
|
932
|
+
* Handle key revoked
|
|
933
|
+
*/
|
|
934
|
+
async onKeyRevoked() {
|
|
935
|
+
await this.refresh();
|
|
936
|
+
}
|
|
937
|
+
/**
|
|
938
|
+
* Handle edit panel closed
|
|
939
|
+
*/
|
|
940
|
+
onEditPanelClosed() {
|
|
941
|
+
this.ShowEditPanel = false;
|
|
942
|
+
this.SelectedKeyId = null;
|
|
943
|
+
}
|
|
944
|
+
/**
|
|
945
|
+
* Get health score (0-100) based on key status
|
|
946
|
+
*/
|
|
947
|
+
getHealthScore() {
|
|
948
|
+
if (this.TotalKeys === 0)
|
|
949
|
+
return 100;
|
|
950
|
+
let score = 100;
|
|
951
|
+
// Deduct for expired keys
|
|
952
|
+
score -= (this.ExpiredKeys / this.TotalKeys) * 40;
|
|
953
|
+
// Deduct for expiring soon
|
|
954
|
+
score -= (this.ExpiringSoonCount / this.TotalKeys) * 20;
|
|
955
|
+
// Deduct for never used active keys (might be leaked)
|
|
956
|
+
score -= (this.NeverUsedKeys / this.TotalKeys) * 10;
|
|
957
|
+
// Deduct if too many active keys
|
|
958
|
+
if (this.ActiveKeys > 20) {
|
|
959
|
+
score -= Math.min(15, (this.ActiveKeys - 20) * 0.5);
|
|
960
|
+
}
|
|
961
|
+
return Math.max(0, Math.round(score));
|
|
962
|
+
}
|
|
963
|
+
/**
|
|
964
|
+
* Get health label based on score
|
|
965
|
+
*/
|
|
966
|
+
getHealthLabel() {
|
|
967
|
+
const score = this.getHealthScore();
|
|
968
|
+
if (score >= 90)
|
|
969
|
+
return 'Excellent Security';
|
|
970
|
+
if (score >= 75)
|
|
971
|
+
return 'Good Security';
|
|
972
|
+
if (score >= 50)
|
|
973
|
+
return 'Needs Attention';
|
|
974
|
+
return 'Critical Issues';
|
|
975
|
+
}
|
|
976
|
+
/**
|
|
977
|
+
* Get CSS class for health banner
|
|
978
|
+
*/
|
|
979
|
+
getHealthClass() {
|
|
980
|
+
const score = this.getHealthScore();
|
|
981
|
+
if (score >= 75)
|
|
982
|
+
return '';
|
|
983
|
+
if (score >= 50)
|
|
984
|
+
return 'health-warning';
|
|
985
|
+
return 'health-critical';
|
|
986
|
+
}
|
|
987
|
+
/**
|
|
988
|
+
* Get donut chart offset for segment
|
|
989
|
+
*/
|
|
990
|
+
getDonutOffset(index) {
|
|
991
|
+
let offset = 0;
|
|
992
|
+
for (let i = 0; i < index; i++) {
|
|
993
|
+
offset -= this.ScopeStats[i].percentage * 2.51;
|
|
994
|
+
}
|
|
995
|
+
return offset;
|
|
996
|
+
}
|
|
997
|
+
/**
|
|
998
|
+
* Get activity icon based on action
|
|
999
|
+
*/
|
|
1000
|
+
getActionIcon(action) {
|
|
1001
|
+
switch (action) {
|
|
1002
|
+
case 'Created': return 'fa-solid fa-plus';
|
|
1003
|
+
case 'Updated': return 'fa-solid fa-pencil';
|
|
1004
|
+
case 'Revoked': return 'fa-solid fa-ban';
|
|
1005
|
+
case 'Used': return 'fa-solid fa-arrow-right-to-bracket';
|
|
1006
|
+
case 'Extended': return 'fa-solid fa-clock-rotate-left';
|
|
1007
|
+
default: return 'fa-solid fa-circle';
|
|
1008
|
+
}
|
|
1009
|
+
}
|
|
1010
|
+
/**
|
|
1011
|
+
* Get CSS class for activity action
|
|
1012
|
+
*/
|
|
1013
|
+
getActionClass(action) {
|
|
1014
|
+
switch (action) {
|
|
1015
|
+
case 'Created': return 'action-created';
|
|
1016
|
+
case 'Updated': return 'action-updated';
|
|
1017
|
+
case 'Revoked': return 'action-revoked';
|
|
1018
|
+
case 'Used': return 'action-used';
|
|
1019
|
+
case 'Extended': return 'action-extended';
|
|
1020
|
+
default: return '';
|
|
1021
|
+
}
|
|
1022
|
+
}
|
|
1023
|
+
/**
|
|
1024
|
+
* Format date for display
|
|
1025
|
+
*/
|
|
1026
|
+
formatDate(date) {
|
|
1027
|
+
if (!date)
|
|
1028
|
+
return 'Never';
|
|
1029
|
+
const now = new Date();
|
|
1030
|
+
const diff = now.getTime() - new Date(date).getTime();
|
|
1031
|
+
const minutes = Math.floor(diff / 60000);
|
|
1032
|
+
const hours = Math.floor(diff / 3600000);
|
|
1033
|
+
const days = Math.floor(diff / 86400000);
|
|
1034
|
+
if (minutes < 1)
|
|
1035
|
+
return 'Just now';
|
|
1036
|
+
if (minutes < 60)
|
|
1037
|
+
return `${minutes}m ago`;
|
|
1038
|
+
if (hours < 24)
|
|
1039
|
+
return `${hours}h ago`;
|
|
1040
|
+
if (days < 7)
|
|
1041
|
+
return `${days}d ago`;
|
|
1042
|
+
return new Date(date).toLocaleDateString();
|
|
1043
|
+
}
|
|
1044
|
+
/**
|
|
1045
|
+
* Format expiration for display
|
|
1046
|
+
*/
|
|
1047
|
+
formatExpiration(date) {
|
|
1048
|
+
if (!date)
|
|
1049
|
+
return 'Never expires';
|
|
1050
|
+
const now = new Date();
|
|
1051
|
+
const expiresAt = new Date(date);
|
|
1052
|
+
const diff = expiresAt.getTime() - now.getTime();
|
|
1053
|
+
if (diff < 0)
|
|
1054
|
+
return 'Expired';
|
|
1055
|
+
const days = Math.floor(diff / 86400000);
|
|
1056
|
+
if (days === 0)
|
|
1057
|
+
return 'Expires today';
|
|
1058
|
+
if (days === 1)
|
|
1059
|
+
return 'Expires tomorrow';
|
|
1060
|
+
if (days < 30)
|
|
1061
|
+
return `Expires in ${days} days`;
|
|
1062
|
+
return `Expires ${expiresAt.toLocaleDateString()}`;
|
|
1063
|
+
}
|
|
1064
|
+
/**
|
|
1065
|
+
* Get expiration status class
|
|
1066
|
+
*/
|
|
1067
|
+
getExpirationClass(key) {
|
|
1068
|
+
if (!key.ExpiresAt)
|
|
1069
|
+
return '';
|
|
1070
|
+
const now = new Date();
|
|
1071
|
+
const expiresAt = new Date(key.ExpiresAt);
|
|
1072
|
+
const diff = expiresAt.getTime() - now.getTime();
|
|
1073
|
+
const days = Math.floor(diff / 86400000);
|
|
1074
|
+
if (diff < 0)
|
|
1075
|
+
return 'expired';
|
|
1076
|
+
if (days < 7)
|
|
1077
|
+
return 'expiring-critical';
|
|
1078
|
+
if (days < 30)
|
|
1079
|
+
return 'expiring-soon';
|
|
1080
|
+
return '';
|
|
1081
|
+
}
|
|
1082
|
+
/**
|
|
1083
|
+
* View activity for a key
|
|
1084
|
+
*/
|
|
1085
|
+
onActivityClick(activity) {
|
|
1086
|
+
const key = this.APIKeys.find(k => k.ID === activity.keyId);
|
|
1087
|
+
if (key) {
|
|
1088
|
+
this.openEditPanel(key);
|
|
1089
|
+
}
|
|
1090
|
+
}
|
|
1091
|
+
/**
|
|
1092
|
+
* View scope details - now navigates to scopes tab
|
|
1093
|
+
*/
|
|
1094
|
+
onScopeClick(_stat) {
|
|
1095
|
+
this.MainTab = 'scopes';
|
|
1096
|
+
}
|
|
1097
|
+
/**
|
|
1098
|
+
* Switch to a main tab
|
|
1099
|
+
*/
|
|
1100
|
+
switchTab(tab) {
|
|
1101
|
+
this.MainTab = tab;
|
|
1102
|
+
// Reset to overview when switching back to keys
|
|
1103
|
+
if (tab === 'keys') {
|
|
1104
|
+
this.CurrentView = 'overview';
|
|
1105
|
+
}
|
|
1106
|
+
}
|
|
1107
|
+
/**
|
|
1108
|
+
* Toggle mobile navigation
|
|
1109
|
+
*/
|
|
1110
|
+
toggleNav() {
|
|
1111
|
+
this.NavOpen = !this.NavOpen;
|
|
1112
|
+
}
|
|
1113
|
+
/**
|
|
1114
|
+
* Close mobile navigation
|
|
1115
|
+
*/
|
|
1116
|
+
closeNav() {
|
|
1117
|
+
this.NavOpen = false;
|
|
1118
|
+
}
|
|
1119
|
+
/**
|
|
1120
|
+
* Handle updates from child panels
|
|
1121
|
+
*/
|
|
1122
|
+
async onDataUpdated() {
|
|
1123
|
+
await this.loadCounts();
|
|
1124
|
+
this.cdr.markForCheck();
|
|
1125
|
+
}
|
|
1126
|
+
static ɵfac = function APIKeysResourceComponent_Factory(__ngFactoryType__) { return new (__ngFactoryType__ || APIKeysResourceComponent)(i0.ɵɵdirectiveInject(i0.ChangeDetectorRef)); };
|
|
1127
|
+
static ɵcmp = /*@__PURE__*/ i0.ɵɵdefineComponent({ type: APIKeysResourceComponent, selectors: [["mj-api-keys-resource"]], viewQuery: function APIKeysResourceComponent_Query(rf, ctx) { if (rf & 1) {
|
|
1128
|
+
i0.ɵɵviewQuery(_c0, 5);
|
|
1129
|
+
} if (rf & 2) {
|
|
1130
|
+
let _t;
|
|
1131
|
+
i0.ɵɵqueryRefresh(_t = i0.ɵɵloadQuery()) && (ctx.keyListComponent = _t.first);
|
|
1132
|
+
} }, features: [i0.ɵɵInheritDefinitionFeature], decls: 5, vars: 9, consts: [["keyList", ""], [1, "api-keys-dashboard"], ["text", "Loading API Keys...", 4, "ngIf"], [4, "ngIf"], [3, "Created", "Closed", "Visible"], [3, "Updated", "Revoked", "Closed", "Visible", "KeyId"], ["text", "Loading API Keys..."], [1, "dashboard-nav"], [1, "nav-header"], [1, "nav-title"], [1, "fa-solid", "fa-key"], [1, "nav-items"], [1, "nav-item", 3, "click"], [1, "nav-label"], [1, "nav-badge"], [1, "fa-solid", "fa-cube"], [1, "fa-solid", "fa-shield-halved"], [1, "fa-solid", "fa-chart-line"], [1, "dashboard-content"], [1, "content-wrapper"], [1, "mobile-nav-toggle", 3, "click"], [1, "fa-solid"], [1, "mobile-nav-overlay", 3, "click"], [1, "overview-header"], [1, "header-left"], [1, "overview-title"], [1, "overview-subtitle"], [1, "header-actions"], ["title", "Refresh", 1, "btn-refresh", 3, "click"], [1, "fa-solid", "fa-refresh"], ["class", "btn-primary", 3, "click", 4, "ngIf"], [1, "health-banner", 3, "ngClass"], [1, "health-score-container"], [1, "health-score-ring"], ["viewBox", "0 0 36 36", 1, "circular-chart"], ["d", "M18 2.0845\n a 15.9155 15.9155 0 0 1 0 31.831\n a 15.9155 15.9155 0 0 1 0 -31.831", 1, "circle-bg"], ["d", "M18 2.0845\n a 15.9155 15.9155 0 0 1 0 31.831\n a 15.9155 15.9155 0 0 1 0 -31.831", 1, "circle"], [1, "health-score-value"], [1, "health-info"], [1, "health-label"], [1, "health-details"], ["class", "health-alert", 3, "click", 4, "ngIf"], ["class", "health-warning-text", 3, "click", 4, "ngIf"], ["class", "health-info-text", 3, "click", 4, "ngIf"], ["class", "health-ok", 4, "ngIf"], [1, "kpi-row"], [1, "kpi-card", "clickable", 3, "click"], [1, "kpi-icon"], [1, "kpi-content"], [1, "kpi-value"], [1, "kpi-label"], [1, "kpi-trend"], [1, "fa-solid", "fa-arrow-right"], [1, "kpi-card", "active", "clickable", 3, "click"], [1, "fa-solid", "fa-check-circle"], [1, "kpi-trend", "success"], [1, "percentage"], [1, "fa-solid", "fa-clock"], ["class", "kpi-trend warning", 4, "ngIf"], [1, "fa-solid", "fa-ban"], [1, "content-grid"], [1, "panel", "keys-panel"], [1, "panel-header"], [1, "panel-title"], [1, "panel-action", 3, "click"], [1, "panel-body"], ["class", "key-list", 4, "ngIf"], ["class", "empty-state", 4, "ngIf"], [1, "panel", "scope-panel"], [1, "panel-subtitle"], ["class", "scope-chart", 4, "ngIf"], [1, "panel", "activity-panel"], [1, "fa-solid", "fa-clock-rotate-left"], ["class", "activity-list", 4, "ngIf"], ["class", "quick-actions", 4, "ngIf"], [1, "security-notice"], [1, "fa-solid", "fa-shield-check"], [1, "notice-content"], [1, "btn-primary", 3, "click"], [1, "fa-solid", "fa-plus"], [1, "health-alert", 3, "click"], [1, "fa-solid", "fa-circle-exclamation"], [1, "health-warning-text", 3, "click"], [1, "health-info-text", 3, "click"], [1, "fa-solid", "fa-question-circle"], [1, "health-ok"], [1, "kpi-trend", "warning"], [1, "key-list"], ["class", "key-item", 3, "click", 4, "ngFor", "ngForOf"], [1, "key-item", 3, "click"], [1, "key-icon"], [1, "key-info"], [1, "key-label"], [1, "key-meta"], [1, "key-user"], [1, "key-hash"], [1, "key-status"], [1, "last-used"], [1, "expiration", 3, "ngClass"], [1, "empty-state"], [1, "scope-chart"], [1, "donut-chart-container"], ["viewBox", "0 0 100 100", 1, "donut-chart"], ["cx", "50", "cy", "50", "r", "40", "fill", "none", "stroke", "#e5e7eb", "stroke-width", "12"], [4, "ngFor", "ngForOf"], [1, "donut-center"], [1, "donut-total"], [1, "donut-label"], [1, "scope-legend"], ["class", "legend-item", 3, "click", 4, "ngFor", "ngForOf"], ["cx", "50", "cy", "50", "r", "40", "fill", "none", "stroke-width", "12", 1, "donut-segment", 3, "click"], [1, "legend-item", 3, "click"], [1, "legend-color"], [1, "legend-info"], [1, "legend-name"], [1, "legend-value"], [1, "fa-solid", "fa-chevron-right", "legend-arrow"], [1, "activity-list"], ["class", "activity-item", 3, "click", 4, "ngFor", "ngForOf"], [1, "activity-item", 3, "click"], [1, "activity-icon", 3, "ngClass"], [1, "activity-info"], [1, "activity-name"], [1, "activity-meta"], [1, "activity-action"], ["class", "activity-user", 4, "ngIf"], [1, "activity-time"], [1, "activity-user"], [1, "fa-solid", "fa-inbox"], [1, "quick-actions"], [1, "quick-actions-title"], [1, "quick-actions-grid"], [1, "quick-action", 3, "click"], [1, "fa-solid", "fa-list"], [1, "list-view-header"], [1, "back-btn", 3, "click"], [1, "fa-solid", "fa-arrow-left"], [3, "KeySelected", "CreateRequested", "Filter"], [3, "ApplicationUpdated"], [3, "ScopeUpdated"]], template: function APIKeysResourceComponent_Template(rf, ctx) { if (rf & 1) {
|
|
1133
|
+
i0.ɵɵelementStart(0, "div", 1);
|
|
1134
|
+
i0.ɵɵtemplate(1, APIKeysResourceComponent_mj_loading_1_Template, 1, 0, "mj-loading", 2)(2, APIKeysResourceComponent_ng_container_2_Template, 39, 19, "ng-container", 3);
|
|
1135
|
+
i0.ɵɵelementStart(3, "mj-api-key-create-dialog", 4);
|
|
1136
|
+
i0.ɵɵlistener("Created", function APIKeysResourceComponent_Template_mj_api_key_create_dialog_Created_3_listener($event) { return ctx.onKeyCreated($event); })("Closed", function APIKeysResourceComponent_Template_mj_api_key_create_dialog_Closed_3_listener() { return ctx.onCreateDialogClosed(); });
|
|
1137
|
+
i0.ɵɵelementEnd();
|
|
1138
|
+
i0.ɵɵelementStart(4, "mj-api-key-edit-panel", 5);
|
|
1139
|
+
i0.ɵɵlistener("Updated", function APIKeysResourceComponent_Template_mj_api_key_edit_panel_Updated_4_listener() { return ctx.onKeyUpdated(); })("Revoked", function APIKeysResourceComponent_Template_mj_api_key_edit_panel_Revoked_4_listener() { return ctx.onKeyRevoked(); })("Closed", function APIKeysResourceComponent_Template_mj_api_key_edit_panel_Closed_4_listener() { return ctx.onEditPanelClosed(); });
|
|
1140
|
+
i0.ɵɵelementEnd()();
|
|
1141
|
+
} if (rf & 2) {
|
|
1142
|
+
i0.ɵɵclassProp("panel-open", ctx.ShowCreateDialog || ctx.ShowEditPanel)("nav-open", ctx.NavOpen);
|
|
1143
|
+
i0.ɵɵadvance();
|
|
1144
|
+
i0.ɵɵproperty("ngIf", ctx.IsLoading);
|
|
1145
|
+
i0.ɵɵadvance();
|
|
1146
|
+
i0.ɵɵproperty("ngIf", !ctx.IsLoading);
|
|
1147
|
+
i0.ɵɵadvance();
|
|
1148
|
+
i0.ɵɵproperty("Visible", ctx.ShowCreateDialog);
|
|
1149
|
+
i0.ɵɵadvance();
|
|
1150
|
+
i0.ɵɵproperty("Visible", ctx.ShowEditPanel)("KeyId", ctx.SelectedKeyId);
|
|
1151
|
+
} }, dependencies: [i1.NgClass, i1.NgForOf, i1.NgIf, i2.LoadingComponent, i3.APIKeyCreateDialogComponent, i4.APIKeyEditPanelComponent, i5.APIKeyListComponent, i6.APIApplicationsPanelComponent, i7.APIScopesPanelComponent, i8.APIUsagePanelComponent], styles: ["\n\n.api-keys-dashboard[_ngcontent-%COMP%] {\n display: flex;\n height: 100%;\n position: relative;\n overflow: hidden;\n}\n\n.api-keys-dashboard.panel-open[_ngcontent-%COMP%] .dashboard-content[_ngcontent-%COMP%] {\n overflow: hidden;\n}\n\n\n\n.dashboard-nav[_ngcontent-%COMP%] {\n width: 220px;\n background: var(--card-background, #ffffff);\n border-right: 1px solid var(--border-color, #e5e7eb);\n display: flex;\n flex-direction: column;\n flex-shrink: 0;\n}\n\n.nav-header[_ngcontent-%COMP%] {\n padding: 20px;\n border-bottom: 1px solid var(--border-color, #e5e7eb);\n}\n\n.nav-title[_ngcontent-%COMP%] {\n display: flex;\n align-items: center;\n gap: 10px;\n font-weight: 700;\n font-size: 16px;\n color: var(--text-primary, #1f2937);\n}\n\n.nav-title[_ngcontent-%COMP%] i[_ngcontent-%COMP%] {\n color: #f59e0b;\n font-size: 18px;\n}\n\n.nav-items[_ngcontent-%COMP%] {\n flex: 1;\n padding: 12px;\n overflow-y: auto;\n}\n\n.nav-item[_ngcontent-%COMP%] {\n display: flex;\n align-items: center;\n gap: 12px;\n padding: 12px 16px;\n margin-bottom: 4px;\n background: transparent;\n border: none;\n border-radius: 8px;\n font-size: 14px;\n font-weight: 500;\n color: var(--text-secondary, #6b7280);\n cursor: pointer;\n transition: all 0.2s ease;\n width: 100%;\n text-align: left;\n}\n\n.nav-item[_ngcontent-%COMP%]:hover {\n background: var(--item-hover, #f3f4f6);\n color: var(--text-primary, #1f2937);\n}\n\n.nav-item.active[_ngcontent-%COMP%] {\n background: linear-gradient(135deg, #f59e0b 0%, #d97706 100%);\n color: white;\n box-shadow: 0 2px 8px rgba(245, 158, 11, 0.3);\n}\n\n.nav-item[_ngcontent-%COMP%] i[_ngcontent-%COMP%] {\n font-size: 16px;\n width: 20px;\n text-align: center;\n}\n\n.nav-item[_ngcontent-%COMP%] .nav-label[_ngcontent-%COMP%] {\n flex: 1;\n}\n\n.nav-badge[_ngcontent-%COMP%] {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n min-width: 22px;\n height: 20px;\n padding: 0 6px;\n background: rgba(0, 0, 0, 0.1);\n border-radius: 10px;\n font-size: 11px;\n font-weight: 600;\n}\n\n.nav-item.active[_ngcontent-%COMP%] .nav-badge[_ngcontent-%COMP%] {\n background: rgba(255, 255, 255, 0.25);\n}\n\n\n\n.dashboard-content[_ngcontent-%COMP%] {\n flex: 1;\n display: flex;\n flex-direction: column;\n overflow-y: auto;\n min-width: 0;\n}\n\n.content-wrapper[_ngcontent-%COMP%] {\n flex: 1;\n padding: 24px;\n overflow-y: auto;\n}\n\n\n\n.list-view-header[_ngcontent-%COMP%] {\n margin-bottom: 16px;\n}\n\n.back-btn[_ngcontent-%COMP%] {\n display: inline-flex;\n align-items: center;\n gap: 8px;\n padding: 8px 16px;\n background: var(--card-background, #ffffff);\n border: 1px solid var(--border-color, #e5e7eb);\n border-radius: 8px;\n font-size: 14px;\n font-weight: 500;\n color: var(--text-secondary, #6b7280);\n cursor: pointer;\n transition: all 0.2s ease;\n}\n\n.back-btn[_ngcontent-%COMP%]:hover {\n background: var(--item-hover, #f3f4f6);\n color: var(--text-primary, #1f2937);\n}\n\n\n\n.overview-header[_ngcontent-%COMP%] {\n display: flex;\n justify-content: space-between;\n align-items: flex-start;\n margin-bottom: 24px;\n}\n\n.header-left[_ngcontent-%COMP%] {\n flex: 1;\n}\n\n.overview-title[_ngcontent-%COMP%] {\n font-size: 24px;\n font-weight: 700;\n color: var(--text-primary, #1f2937);\n margin: 0 0 4px 0;\n}\n\n.overview-subtitle[_ngcontent-%COMP%] {\n font-size: 14px;\n color: var(--text-secondary, #6b7280);\n margin: 0;\n}\n\n.header-actions[_ngcontent-%COMP%] {\n display: flex;\n gap: 12px;\n}\n\n.btn-refresh[_ngcontent-%COMP%] {\n display: flex;\n align-items: center;\n justify-content: center;\n width: 40px;\n height: 40px;\n background: var(--card-background, #ffffff);\n border: 1px solid var(--border-color, #e5e7eb);\n border-radius: 8px;\n cursor: pointer;\n transition: all 0.2s ease;\n}\n\n.btn-refresh[_ngcontent-%COMP%]:hover {\n background: var(--item-hover, #f3f4f6);\n}\n\n.btn-refresh[_ngcontent-%COMP%] i[_ngcontent-%COMP%] {\n color: var(--text-secondary, #6b7280);\n}\n\n.btn-primary[_ngcontent-%COMP%] {\n display: flex;\n align-items: center;\n gap: 8px;\n padding: 10px 20px;\n background: linear-gradient(135deg, #f59e0b 0%, #d97706 100%);\n color: white;\n border: none;\n border-radius: 8px;\n font-weight: 600;\n font-size: 14px;\n cursor: pointer;\n transition: all 0.2s ease;\n}\n\n.btn-primary[_ngcontent-%COMP%]:hover {\n transform: translateY(-1px);\n box-shadow: 0 4px 12px rgba(245, 158, 11, 0.4);\n}\n\n\n\n.health-banner[_ngcontent-%COMP%] {\n display: flex;\n align-items: center;\n padding: 20px 24px;\n background: linear-gradient(135deg, #ecfdf5 0%, #d1fae5 100%);\n border: 1px solid #a7f3d0;\n border-radius: 16px;\n margin-bottom: 24px;\n}\n\n.health-banner.health-warning[_ngcontent-%COMP%] {\n background: linear-gradient(135deg, #fffbeb 0%, #fef3c7 100%);\n border-color: #fcd34d;\n}\n\n.health-banner.health-critical[_ngcontent-%COMP%] {\n background: linear-gradient(135deg, #fef2f2 0%, #fee2e2 100%);\n border-color: #fca5a5;\n}\n\n.health-score-container[_ngcontent-%COMP%] {\n margin-right: 24px;\n}\n\n.health-score-ring[_ngcontent-%COMP%] {\n position: relative;\n width: 80px;\n height: 80px;\n}\n\n.circular-chart[_ngcontent-%COMP%] {\n display: block;\n margin: 0 auto;\n max-height: 80px;\n transform: rotate(-90deg);\n}\n\n.circle-bg[_ngcontent-%COMP%] {\n fill: none;\n stroke: #e5e7eb;\n stroke-width: 3.8;\n}\n\n.circle[_ngcontent-%COMP%] {\n fill: none;\n stroke-width: 3.8;\n stroke-linecap: round;\n stroke: #10b981;\n animation: _ngcontent-%COMP%_progress 1s ease-out forwards;\n}\n\n.health-banner.health-warning[_ngcontent-%COMP%] .circle[_ngcontent-%COMP%] {\n stroke: #f59e0b;\n}\n\n.health-banner.health-critical[_ngcontent-%COMP%] .circle[_ngcontent-%COMP%] {\n stroke: #ef4444;\n}\n\n@keyframes _ngcontent-%COMP%_progress {\n 0% {\n stroke-dasharray: 0 100;\n }\n}\n\n.health-score-value[_ngcontent-%COMP%] {\n position: absolute;\n top: 50%;\n left: 50%;\n transform: translate(-50%, -50%);\n font-size: 20px;\n font-weight: 700;\n color: var(--text-primary, #1f2937);\n}\n\n.health-info[_ngcontent-%COMP%] {\n flex: 1;\n}\n\n.health-label[_ngcontent-%COMP%] {\n font-size: 18px;\n font-weight: 700;\n color: #059669;\n margin-bottom: 4px;\n}\n\n.health-banner.health-warning[_ngcontent-%COMP%] .health-label[_ngcontent-%COMP%] {\n color: #d97706;\n}\n\n.health-banner.health-critical[_ngcontent-%COMP%] .health-label[_ngcontent-%COMP%] {\n color: #dc2626;\n}\n\n.health-details[_ngcontent-%COMP%] {\n display: flex;\n gap: 16px;\n flex-wrap: wrap;\n}\n\n.health-alert[_ngcontent-%COMP%] {\n display: flex;\n align-items: center;\n gap: 6px;\n color: #dc2626;\n font-weight: 500;\n font-size: 14px;\n cursor: pointer;\n}\n\n.health-alert[_ngcontent-%COMP%]:hover {\n text-decoration: underline;\n}\n\n.health-warning-text[_ngcontent-%COMP%] {\n display: flex;\n align-items: center;\n gap: 6px;\n color: #d97706;\n font-weight: 500;\n font-size: 14px;\n cursor: pointer;\n}\n\n.health-warning-text[_ngcontent-%COMP%]:hover {\n text-decoration: underline;\n}\n\n.health-info-text[_ngcontent-%COMP%] {\n display: flex;\n align-items: center;\n gap: 6px;\n color: #6b7280;\n font-weight: 500;\n font-size: 14px;\n cursor: pointer;\n}\n\n.health-info-text[_ngcontent-%COMP%]:hover {\n text-decoration: underline;\n}\n\n.health-ok[_ngcontent-%COMP%] {\n display: flex;\n align-items: center;\n gap: 6px;\n color: #059669;\n font-weight: 500;\n font-size: 14px;\n}\n\n\n\n.kpi-row[_ngcontent-%COMP%] {\n display: grid;\n grid-template-columns: repeat(auto-fit, minmax(160px, 1fr));\n gap: 16px;\n margin-bottom: 24px;\n}\n\n.kpi-card[_ngcontent-%COMP%] {\n display: flex;\n align-items: center;\n padding: 16px 20px;\n background: var(--card-background, #ffffff);\n border-radius: 12px;\n box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);\n transition: transform 0.2s ease, box-shadow 0.2s ease;\n}\n\n.kpi-card.clickable[_ngcontent-%COMP%] {\n cursor: pointer;\n}\n\n.kpi-card.clickable[_ngcontent-%COMP%]:hover {\n transform: translateY(-2px);\n box-shadow: 0 4px 16px rgba(0, 0, 0, 0.12);\n}\n\n.kpi-card.active[_ngcontent-%COMP%] .kpi-icon[_ngcontent-%COMP%] {\n background: linear-gradient(135deg, #10b981 0%, #059669 100%);\n}\n\n.kpi-card.warning[_ngcontent-%COMP%] .kpi-icon[_ngcontent-%COMP%] {\n background: linear-gradient(135deg, #f59e0b 0%, #d97706 100%);\n}\n\n.kpi-card.danger[_ngcontent-%COMP%] .kpi-icon[_ngcontent-%COMP%] {\n background: linear-gradient(135deg, #ef4444 0%, #dc2626 100%);\n}\n\n.kpi-icon[_ngcontent-%COMP%] {\n width: 48px;\n height: 48px;\n display: flex;\n align-items: center;\n justify-content: center;\n background: linear-gradient(135deg, #f59e0b 0%, #d97706 100%);\n border-radius: 10px;\n margin-right: 14px;\n flex-shrink: 0;\n}\n\n.kpi-icon[_ngcontent-%COMP%] i[_ngcontent-%COMP%] {\n font-size: 20px;\n color: white;\n}\n\n.kpi-content[_ngcontent-%COMP%] {\n flex: 1;\n min-width: 0;\n}\n\n.kpi-value[_ngcontent-%COMP%] {\n font-size: 24px;\n font-weight: 700;\n color: var(--text-primary, #1f2937);\n line-height: 1.2;\n}\n\n.kpi-label[_ngcontent-%COMP%] {\n font-size: 12px;\n color: var(--text-secondary, #6b7280);\n margin-top: 2px;\n white-space: nowrap;\n}\n\n.kpi-trend[_ngcontent-%COMP%] {\n display: flex;\n align-items: center;\n margin-left: 8px;\n color: var(--text-tertiary, #9ca3af);\n}\n\n.kpi-trend.success[_ngcontent-%COMP%] {\n color: #10b981;\n}\n\n.kpi-trend.warning[_ngcontent-%COMP%] {\n color: #f59e0b;\n}\n\n.kpi-trend[_ngcontent-%COMP%] .percentage[_ngcontent-%COMP%] {\n font-size: 12px;\n font-weight: 600;\n}\n\n\n\n.content-grid[_ngcontent-%COMP%] {\n display: grid;\n grid-template-columns: 1fr 1fr;\n grid-template-rows: auto auto;\n gap: 20px;\n margin-bottom: 24px;\n}\n\n.keys-panel[_ngcontent-%COMP%] {\n grid-row: span 1;\n}\n\n.scope-panel[_ngcontent-%COMP%] {\n grid-row: span 1;\n}\n\n.activity-panel[_ngcontent-%COMP%] {\n grid-column: span 2;\n}\n\n.panel[_ngcontent-%COMP%] {\n background: var(--card-background, #ffffff);\n border-radius: 12px;\n box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);\n overflow: hidden;\n display: flex;\n flex-direction: column;\n}\n\n.panel-header[_ngcontent-%COMP%] {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 16px 20px;\n background: var(--header-background, #f9fafb);\n border-bottom: 1px solid var(--border-color, #e5e7eb);\n}\n\n.panel-title[_ngcontent-%COMP%] {\n display: flex;\n align-items: center;\n gap: 10px;\n font-weight: 600;\n font-size: 14px;\n color: var(--text-primary, #1f2937);\n}\n\n.panel-title[_ngcontent-%COMP%] i[_ngcontent-%COMP%] {\n color: #f59e0b;\n}\n\n.panel-subtitle[_ngcontent-%COMP%] {\n font-size: 12px;\n color: var(--text-tertiary, #9ca3af);\n}\n\n.panel-action[_ngcontent-%COMP%] {\n display: flex;\n align-items: center;\n gap: 6px;\n padding: 6px 12px;\n background: transparent;\n border: 1px solid var(--border-color, #e5e7eb);\n border-radius: 6px;\n font-size: 12px;\n font-weight: 500;\n color: var(--text-secondary, #6b7280);\n cursor: pointer;\n transition: all 0.2s ease;\n}\n\n.panel-action[_ngcontent-%COMP%]:hover {\n background: #f59e0b;\n border-color: #f59e0b;\n color: white;\n}\n\n.panel-body[_ngcontent-%COMP%] {\n padding: 20px;\n flex: 1;\n overflow-y: auto;\n}\n\n\n\n.key-list[_ngcontent-%COMP%] {\n display: flex;\n flex-direction: column;\n gap: 12px;\n}\n\n.key-item[_ngcontent-%COMP%] {\n display: flex;\n align-items: center;\n padding: 14px 16px;\n background: var(--item-background, #f9fafb);\n border-radius: 10px;\n cursor: pointer;\n transition: all 0.2s ease;\n}\n\n.key-item[_ngcontent-%COMP%]:hover {\n background: var(--item-hover, #f3f4f6);\n transform: translateX(4px);\n}\n\n.key-icon[_ngcontent-%COMP%] {\n width: 40px;\n height: 40px;\n display: flex;\n align-items: center;\n justify-content: center;\n background: linear-gradient(135deg, #6b7280 0%, #4b5563 100%);\n border-radius: 8px;\n margin-right: 14px;\n flex-shrink: 0;\n}\n\n.key-icon.active[_ngcontent-%COMP%] {\n background: linear-gradient(135deg, #f59e0b 0%, #d97706 100%);\n}\n\n.key-icon[_ngcontent-%COMP%] i[_ngcontent-%COMP%] {\n font-size: 16px;\n color: white;\n}\n\n.key-info[_ngcontent-%COMP%] {\n flex: 1;\n min-width: 0;\n}\n\n.key-label[_ngcontent-%COMP%] {\n font-size: 14px;\n font-weight: 600;\n color: var(--text-primary, #1f2937);\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n}\n\n.key-meta[_ngcontent-%COMP%] {\n display: flex;\n align-items: center;\n gap: 8px;\n margin-top: 2px;\n font-size: 12px;\n color: var(--text-secondary, #6b7280);\n}\n\n.key-hash[_ngcontent-%COMP%] {\n font-family: monospace;\n background: var(--tag-background, #e5e7eb);\n padding: 2px 6px;\n border-radius: 4px;\n font-size: 11px;\n}\n\n.key-status[_ngcontent-%COMP%] {\n display: flex;\n flex-direction: column;\n align-items: flex-end;\n gap: 4px;\n margin-left: 16px;\n}\n\n.last-used[_ngcontent-%COMP%] {\n font-size: 12px;\n color: var(--text-secondary, #6b7280);\n}\n\n.expiration[_ngcontent-%COMP%] {\n font-size: 11px;\n color: var(--text-tertiary, #9ca3af);\n}\n\n.expiration.expiring-soon[_ngcontent-%COMP%] {\n color: #f59e0b;\n font-weight: 500;\n}\n\n.expiration.expiring-critical[_ngcontent-%COMP%] {\n color: #ef4444;\n font-weight: 500;\n}\n\n.expiration.expired[_ngcontent-%COMP%] {\n color: #ef4444;\n font-weight: 600;\n}\n\n\n\n.scope-chart[_ngcontent-%COMP%] {\n display: flex;\n flex-direction: column;\n gap: 24px;\n height: 100%;\n}\n\n.donut-chart-container[_ngcontent-%COMP%] {\n position: relative;\n width: 160px;\n height: 160px;\n margin: 0 auto;\n}\n\n.donut-chart[_ngcontent-%COMP%] {\n width: 100%;\n height: 100%;\n transform: rotate(-90deg);\n}\n\n.donut-segment[_ngcontent-%COMP%] {\n cursor: pointer;\n transition: opacity 0.2s ease;\n}\n\n.donut-segment[_ngcontent-%COMP%]:hover {\n opacity: 0.8;\n}\n\n.donut-center[_ngcontent-%COMP%] {\n position: absolute;\n top: 50%;\n left: 50%;\n transform: translate(-50%, -50%);\n text-align: center;\n}\n\n.donut-total[_ngcontent-%COMP%] {\n font-size: 28px;\n font-weight: 700;\n color: var(--text-primary, #1f2937);\n}\n\n.donut-label[_ngcontent-%COMP%] {\n font-size: 11px;\n color: var(--text-secondary, #6b7280);\n}\n\n\n\n.scope-legend[_ngcontent-%COMP%] {\n display: flex;\n flex-direction: column;\n gap: 8px;\n}\n\n.legend-item[_ngcontent-%COMP%] {\n display: flex;\n align-items: center;\n padding: 10px 12px;\n background: var(--item-background, #f9fafb);\n border-radius: 8px;\n cursor: pointer;\n transition: all 0.2s ease;\n}\n\n.legend-item[_ngcontent-%COMP%]:hover {\n background: var(--item-hover, #f3f4f6);\n transform: translateX(4px);\n}\n\n.legend-color[_ngcontent-%COMP%] {\n width: 12px;\n height: 12px;\n border-radius: 4px;\n margin-right: 12px;\n flex-shrink: 0;\n}\n\n.legend-info[_ngcontent-%COMP%] {\n flex: 1;\n}\n\n.legend-name[_ngcontent-%COMP%] {\n font-size: 14px;\n font-weight: 500;\n color: var(--text-primary, #1f2937);\n display: flex;\n align-items: center;\n gap: 8px;\n}\n\n.legend-name[_ngcontent-%COMP%] i[_ngcontent-%COMP%] {\n font-size: 12px;\n color: var(--text-secondary, #6b7280);\n}\n\n.legend-value[_ngcontent-%COMP%] {\n font-size: 12px;\n color: var(--text-secondary, #6b7280);\n}\n\n.legend-arrow[_ngcontent-%COMP%] {\n color: var(--text-tertiary, #9ca3af);\n font-size: 10px;\n}\n\n\n\n.activity-list[_ngcontent-%COMP%] {\n display: grid;\n grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));\n gap: 12px;\n}\n\n.activity-item[_ngcontent-%COMP%] {\n display: flex;\n align-items: center;\n padding: 12px 14px;\n background: var(--item-background, #f9fafb);\n border-radius: 8px;\n cursor: pointer;\n transition: all 0.2s ease;\n}\n\n.activity-item[_ngcontent-%COMP%]:hover {\n background: var(--item-hover, #f3f4f6);\n}\n\n.activity-icon[_ngcontent-%COMP%] {\n width: 32px;\n height: 32px;\n display: flex;\n align-items: center;\n justify-content: center;\n background: var(--icon-background, #fef3c7);\n border-radius: 6px;\n margin-right: 12px;\n flex-shrink: 0;\n}\n\n.activity-icon[_ngcontent-%COMP%] i[_ngcontent-%COMP%] {\n font-size: 12px;\n color: #f59e0b;\n}\n\n.activity-icon.action-created[_ngcontent-%COMP%] {\n background: #d1fae5;\n}\n\n.activity-icon.action-created[_ngcontent-%COMP%] i[_ngcontent-%COMP%] {\n color: #059669;\n}\n\n.activity-icon.action-updated[_ngcontent-%COMP%] {\n background: #e0e7ff;\n}\n\n.activity-icon.action-updated[_ngcontent-%COMP%] i[_ngcontent-%COMP%] {\n color: #6366f1;\n}\n\n.activity-icon.action-revoked[_ngcontent-%COMP%] {\n background: #fee2e2;\n}\n\n.activity-icon.action-revoked[_ngcontent-%COMP%] i[_ngcontent-%COMP%] {\n color: #dc2626;\n}\n\n.activity-icon.action-used[_ngcontent-%COMP%] {\n background: #fef3c7;\n}\n\n.activity-icon.action-used[_ngcontent-%COMP%] i[_ngcontent-%COMP%] {\n color: #d97706;\n}\n\n.activity-icon.action-extended[_ngcontent-%COMP%] {\n background: #dbeafe;\n}\n\n.activity-icon.action-extended[_ngcontent-%COMP%] i[_ngcontent-%COMP%] {\n color: #3b82f6;\n}\n\n.activity-info[_ngcontent-%COMP%] {\n flex: 1;\n min-width: 0;\n}\n\n.activity-name[_ngcontent-%COMP%] {\n font-weight: 500;\n font-size: 13px;\n color: var(--text-primary, #1f2937);\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n}\n\n.activity-meta[_ngcontent-%COMP%] {\n display: flex;\n align-items: center;\n gap: 6px;\n margin-top: 2px;\n font-size: 11px;\n color: var(--text-secondary, #6b7280);\n}\n\n.activity-action[_ngcontent-%COMP%] {\n font-weight: 500;\n text-transform: capitalize;\n}\n\n.activity-time[_ngcontent-%COMP%] {\n font-size: 11px;\n color: var(--text-tertiary, #9ca3af);\n white-space: nowrap;\n}\n\n\n\n.quick-actions[_ngcontent-%COMP%] {\n margin-bottom: 24px;\n}\n\n.quick-actions-title[_ngcontent-%COMP%] {\n font-size: 14px;\n font-weight: 600;\n color: var(--text-primary, #1f2937);\n margin-bottom: 12px;\n}\n\n.quick-actions-grid[_ngcontent-%COMP%] {\n display: grid;\n grid-template-columns: repeat(auto-fit, minmax(140px, 1fr));\n gap: 12px;\n}\n\n.quick-action[_ngcontent-%COMP%] {\n display: flex;\n flex-direction: column;\n align-items: center;\n gap: 8px;\n padding: 16px;\n background: var(--card-background, #ffffff);\n border: 1px solid var(--border-color, #e5e7eb);\n border-radius: 10px;\n cursor: pointer;\n transition: all 0.2s ease;\n}\n\n.quick-action[_ngcontent-%COMP%]:hover {\n border-color: #f59e0b;\n background: #fffbeb;\n transform: translateY(-2px);\n}\n\n.quick-action[_ngcontent-%COMP%] i[_ngcontent-%COMP%] {\n font-size: 20px;\n color: #f59e0b;\n}\n\n.quick-action[_ngcontent-%COMP%] span[_ngcontent-%COMP%] {\n font-size: 12px;\n font-weight: 500;\n color: var(--text-secondary, #6b7280);\n}\n\n\n\n.empty-state[_ngcontent-%COMP%] {\n display: flex;\n flex-direction: column;\n align-items: center;\n justify-content: center;\n padding: 32px;\n color: var(--text-secondary, #6b7280);\n text-align: center;\n}\n\n.empty-state[_ngcontent-%COMP%] i[_ngcontent-%COMP%] {\n font-size: 32px;\n margin-bottom: 12px;\n opacity: 0.5;\n}\n\n\n\n.security-notice[_ngcontent-%COMP%] {\n display: flex;\n align-items: flex-start;\n padding: 16px 20px;\n background: linear-gradient(135deg, #fffbeb 0%, #fef3c7 100%);\n border-radius: 12px;\n border: 1px solid #fcd34d;\n}\n\n.security-notice[_ngcontent-%COMP%] i[_ngcontent-%COMP%] {\n font-size: 20px;\n color: #f59e0b;\n margin-right: 12px;\n margin-top: 2px;\n}\n\n.notice-content[_ngcontent-%COMP%] {\n font-size: 13px;\n color: #92400e;\n line-height: 1.5;\n}\n\n.notice-content[_ngcontent-%COMP%] strong[_ngcontent-%COMP%] {\n font-weight: 600;\n}\n\n\n\n.mobile-nav-toggle[_ngcontent-%COMP%] {\n display: none;\n position: fixed;\n bottom: 20px;\n right: 20px;\n width: 56px;\n height: 56px;\n background: linear-gradient(135deg, #f59e0b 0%, #d97706 100%);\n border: none;\n border-radius: 50%;\n color: white;\n font-size: 24px;\n box-shadow: 0 4px 16px rgba(245, 158, 11, 0.4);\n cursor: pointer;\n z-index: 50;\n transition: transform 0.2s ease;\n}\n\n.mobile-nav-toggle[_ngcontent-%COMP%]:hover {\n transform: scale(1.05);\n}\n\n\n\n.mobile-nav-overlay[_ngcontent-%COMP%] {\n display: none;\n position: fixed;\n top: 0;\n left: 0;\n right: 0;\n bottom: 0;\n background: rgba(0, 0, 0, 0.5);\n z-index: 90;\n}\n\n\n\n\n\n\n\n\n@media (max-width: 1024px) {\n .content-grid[_ngcontent-%COMP%] {\n grid-template-columns: 1fr;\n }\n\n .activity-panel[_ngcontent-%COMP%] {\n grid-column: span 1;\n }\n\n .scope-chart[_ngcontent-%COMP%] {\n flex-direction: row;\n align-items: center;\n }\n\n .donut-chart-container[_ngcontent-%COMP%] {\n width: 140px;\n height: 140px;\n }\n\n .scope-legend[_ngcontent-%COMP%] {\n flex: 1;\n }\n\n .dashboard-nav[_ngcontent-%COMP%] {\n width: 200px;\n }\n}\n\n\n\n@media (max-width: 768px) {\n .api-keys-dashboard[_ngcontent-%COMP%] {\n flex-direction: column;\n }\n\n .dashboard-nav[_ngcontent-%COMP%] {\n position: fixed;\n top: 0;\n left: -280px;\n width: 280px;\n height: 100%;\n z-index: 100;\n transition: left 0.3s ease;\n box-shadow: 4px 0 20px rgba(0, 0, 0, 0.15);\n }\n\n .api-keys-dashboard.nav-open[_ngcontent-%COMP%] .dashboard-nav[_ngcontent-%COMP%] {\n left: 0;\n }\n\n .mobile-nav-toggle[_ngcontent-%COMP%] {\n display: flex;\n align-items: center;\n justify-content: center;\n }\n\n .mobile-nav-overlay[_ngcontent-%COMP%] {\n display: block;\n opacity: 0;\n pointer-events: none;\n transition: opacity 0.3s ease;\n }\n\n .api-keys-dashboard.nav-open[_ngcontent-%COMP%] .mobile-nav-overlay[_ngcontent-%COMP%] {\n opacity: 1;\n pointer-events: auto;\n }\n\n .dashboard-content[_ngcontent-%COMP%] {\n width: 100%;\n }\n\n .content-wrapper[_ngcontent-%COMP%] {\n padding: 16px;\n }\n\n .overview-header[_ngcontent-%COMP%] {\n flex-direction: column;\n gap: 16px;\n }\n\n .header-actions[_ngcontent-%COMP%] {\n width: 100%;\n justify-content: flex-end;\n }\n\n .health-banner[_ngcontent-%COMP%] {\n flex-direction: column;\n text-align: center;\n }\n\n .health-score-container[_ngcontent-%COMP%] {\n margin-right: 0;\n margin-bottom: 16px;\n }\n\n .kpi-row[_ngcontent-%COMP%] {\n grid-template-columns: repeat(2, 1fr);\n }\n\n .scope-chart[_ngcontent-%COMP%] {\n flex-direction: column;\n }\n\n .quick-actions-grid[_ngcontent-%COMP%] {\n grid-template-columns: repeat(2, 1fr);\n }\n\n .activity-list[_ngcontent-%COMP%] {\n grid-template-columns: 1fr;\n }\n\n .key-item[_ngcontent-%COMP%] {\n flex-wrap: wrap;\n }\n\n .key-status[_ngcontent-%COMP%] {\n width: 100%;\n flex-direction: row;\n justify-content: flex-start;\n margin-left: 54px;\n margin-top: 8px;\n gap: 12px;\n }\n}\n\n\n\n@media (max-width: 480px) {\n .kpi-row[_ngcontent-%COMP%] {\n grid-template-columns: 1fr;\n }\n\n .quick-actions-grid[_ngcontent-%COMP%] {\n grid-template-columns: 1fr;\n }\n\n .kpi-card[_ngcontent-%COMP%] {\n padding: 12px 16px;\n }\n\n .kpi-icon[_ngcontent-%COMP%] {\n width: 40px;\n height: 40px;\n }\n\n .kpi-value[_ngcontent-%COMP%] {\n font-size: 20px;\n }\n\n .overview-title[_ngcontent-%COMP%] {\n font-size: 20px;\n }\n\n .btn-primary[_ngcontent-%COMP%] {\n padding: 8px 16px;\n font-size: 13px;\n }\n}"] });
|
|
1152
|
+
};
|
|
1153
|
+
APIKeysResourceComponent = __decorate([
|
|
1154
|
+
RegisterClass(BaseResourceComponent, 'APIKeysResource')
|
|
1155
|
+
], APIKeysResourceComponent);
|
|
1156
|
+
export { APIKeysResourceComponent };
|
|
1157
|
+
(() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassMetadata(APIKeysResourceComponent, [{
|
|
1158
|
+
type: Component,
|
|
1159
|
+
args: [{ selector: 'mj-api-keys-resource', template: "<div class=\"api-keys-dashboard\" [class.panel-open]=\"ShowCreateDialog || ShowEditPanel\" [class.nav-open]=\"NavOpen\">\n <mj-loading *ngIf=\"IsLoading\" text=\"Loading API Keys...\"></mj-loading>\n\n <ng-container *ngIf=\"!IsLoading\">\n <!-- Left Navigation Sidebar -->\n <div class=\"dashboard-nav\">\n <div class=\"nav-header\">\n <div class=\"nav-title\">\n <i class=\"fa-solid fa-key\"></i>\n <span>API Keys</span>\n </div>\n </div>\n <div class=\"nav-items\">\n <button class=\"nav-item\" [class.active]=\"MainTab === 'keys'\" (click)=\"switchTab('keys'); closeNav()\">\n <i class=\"fa-solid fa-key\"></i>\n <span class=\"nav-label\">API Keys</span>\n <span class=\"nav-badge\">{{TotalKeys}}</span>\n </button>\n <button class=\"nav-item\" [class.active]=\"MainTab === 'applications'\" (click)=\"switchTab('applications'); closeNav()\">\n <i class=\"fa-solid fa-cube\"></i>\n <span class=\"nav-label\">Applications</span>\n <span class=\"nav-badge\">{{ApplicationCount}}</span>\n </button>\n <button class=\"nav-item\" [class.active]=\"MainTab === 'scopes'\" (click)=\"switchTab('scopes'); closeNav()\">\n <i class=\"fa-solid fa-shield-halved\"></i>\n <span class=\"nav-label\">Scopes</span>\n <span class=\"nav-badge\">{{ScopeCount}}</span>\n </button>\n <button class=\"nav-item\" [class.active]=\"MainTab === 'usage'\" (click)=\"switchTab('usage'); closeNav()\">\n <i class=\"fa-solid fa-chart-line\"></i>\n <span class=\"nav-label\">Usage Analytics</span>\n </button>\n </div>\n </div>\n\n <!-- Main Content Area -->\n <div class=\"dashboard-content\">\n <div class=\"content-wrapper\">\n <!-- Keys Tab Content -->\n <ng-container *ngIf=\"MainTab === 'keys'\">\n <!-- Overview View -->\n <ng-container *ngIf=\"CurrentView === 'overview'\">\n <!-- Header with Actions -->\n <div class=\"overview-header\">\n <div class=\"header-left\">\n <h2 class=\"overview-title\">API Keys Management</h2>\n <p class=\"overview-subtitle\">Manage API keys for external integrations and services</p>\n </div>\n <div class=\"header-actions\">\n <button class=\"btn-refresh\" (click)=\"refresh()\" title=\"Refresh\">\n <i class=\"fa-solid fa-refresh\"></i>\n </button>\n <button class=\"btn-primary\" *ngIf=\"UserCanCreateKeys\" (click)=\"openCreateDialog()\">\n <i class=\"fa-solid fa-plus\"></i>\n <span>Generate New Key</span>\n </button>\n </div>\n </div>\n\n <!-- Health Score Banner -->\n <div class=\"health-banner\" [ngClass]=\"getHealthClass()\">\n <div class=\"health-score-container\">\n <div class=\"health-score-ring\">\n <svg viewBox=\"0 0 36 36\" class=\"circular-chart\">\n <path class=\"circle-bg\"\n d=\"M18 2.0845\n a 15.9155 15.9155 0 0 1 0 31.831\n a 15.9155 15.9155 0 0 1 0 -31.831\"\n />\n <path class=\"circle\"\n [attr.stroke-dasharray]=\"getHealthScore() + ', 100'\"\n d=\"M18 2.0845\n a 15.9155 15.9155 0 0 1 0 31.831\n a 15.9155 15.9155 0 0 1 0 -31.831\"\n />\n </svg>\n <div class=\"health-score-value\">{{getHealthScore()}}</div>\n </div>\n </div>\n <div class=\"health-info\">\n <div class=\"health-label\">{{getHealthLabel()}}</div>\n <div class=\"health-details\">\n <span *ngIf=\"ExpiredKeys > 0\" class=\"health-alert\" (click)=\"showListView('expired')\">\n <i class=\"fa-solid fa-circle-exclamation\"></i>\n {{ExpiredKeys}} expired\n </span>\n <span *ngIf=\"ExpiringSoonCount > 0\" class=\"health-warning-text\" (click)=\"showListView('expiring')\">\n <i class=\"fa-solid fa-clock\"></i>\n {{ExpiringSoonCount}} expiring soon\n </span>\n <span *ngIf=\"NeverUsedKeys > 0\" class=\"health-info-text\" (click)=\"showListView('never-used')\">\n <i class=\"fa-solid fa-question-circle\"></i>\n {{NeverUsedKeys}} never used\n </span>\n <span *ngIf=\"ExpiredKeys === 0 && ExpiringSoonCount === 0 && NeverUsedKeys === 0\" class=\"health-ok\">\n <i class=\"fa-solid fa-check-circle\"></i>\n All keys healthy\n </span>\n </div>\n </div>\n </div>\n\n <!-- KPI Cards Row -->\n <div class=\"kpi-row\">\n <div class=\"kpi-card clickable\" (click)=\"showListView('all')\">\n <div class=\"kpi-icon\">\n <i class=\"fa-solid fa-key\"></i>\n </div>\n <div class=\"kpi-content\">\n <div class=\"kpi-value\">{{TotalKeys}}</div>\n <div class=\"kpi-label\">Total Keys</div>\n </div>\n <div class=\"kpi-trend\">\n <i class=\"fa-solid fa-arrow-right\"></i>\n </div>\n </div>\n\n <div class=\"kpi-card active clickable\" (click)=\"showListView('active')\">\n <div class=\"kpi-icon\">\n <i class=\"fa-solid fa-check-circle\"></i>\n </div>\n <div class=\"kpi-content\">\n <div class=\"kpi-value\">{{ActiveKeys}}</div>\n <div class=\"kpi-label\">Active</div>\n </div>\n <div class=\"kpi-trend success\">\n <span class=\"percentage\">{{TotalKeys > 0 ? (ActiveKeys / TotalKeys * 100).toFixed(0) : 100}}%</span>\n </div>\n </div>\n\n <div class=\"kpi-card clickable\" [class.warning]=\"ExpiringSoonCount > 0\" (click)=\"showListView('expiring')\">\n <div class=\"kpi-icon\">\n <i class=\"fa-solid fa-clock\"></i>\n </div>\n <div class=\"kpi-content\">\n <div class=\"kpi-value\">{{ExpiringSoonCount}}</div>\n <div class=\"kpi-label\">Expiring Soon</div>\n </div>\n <div class=\"kpi-trend warning\" *ngIf=\"ExpiringSoonCount > 0\">\n <i class=\"fa-solid fa-arrow-right\"></i>\n </div>\n </div>\n\n <div class=\"kpi-card clickable\" [class.danger]=\"RevokedKeys > 0\" (click)=\"showListView('revoked')\">\n <div class=\"kpi-icon\">\n <i class=\"fa-solid fa-ban\"></i>\n </div>\n <div class=\"kpi-content\">\n <div class=\"kpi-value\">{{RevokedKeys}}</div>\n <div class=\"kpi-label\">Revoked</div>\n </div>\n </div>\n\n <div class=\"kpi-card clickable\" (click)=\"switchTab('scopes')\">\n <div class=\"kpi-icon\">\n <i class=\"fa-solid fa-shield-halved\"></i>\n </div>\n <div class=\"kpi-content\">\n <div class=\"kpi-value\">{{ScopeStats.length}}</div>\n <div class=\"kpi-label\">Scope Categories</div>\n </div>\n </div>\n </div>\n\n <!-- Content Grid -->\n <div class=\"content-grid\">\n <!-- Recently Used Keys -->\n <div class=\"panel keys-panel\">\n <div class=\"panel-header\">\n <div class=\"panel-title\">\n <i class=\"fa-solid fa-key\"></i>\n <span>Recently Used Keys</span>\n </div>\n <button class=\"panel-action\" (click)=\"showListView('all')\">\n View All <i class=\"fa-solid fa-arrow-right\"></i>\n </button>\n </div>\n <div class=\"panel-body\">\n <div class=\"key-list\" *ngIf=\"TopUsedKeys.length > 0\">\n <div class=\"key-item\" *ngFor=\"let key of TopUsedKeys\" (click)=\"openEditPanel(key)\">\n <div class=\"key-icon\" [class.active]=\"key.Status === 'Active'\">\n <i class=\"fa-solid fa-key\"></i>\n </div>\n <div class=\"key-info\">\n <div class=\"key-label\">{{key.Label}}</div>\n <div class=\"key-meta\">\n <span class=\"key-user\">{{key.User}}</span>\n <span class=\"key-hash\">...{{key.Hash.slice(-8)}}</span>\n </div>\n </div>\n <div class=\"key-status\">\n <div class=\"last-used\">{{formatDate(key.LastUsedAt)}}</div>\n <div class=\"expiration\" [ngClass]=\"getExpirationClass(key)\">\n {{formatExpiration(key.ExpiresAt)}}\n </div>\n </div>\n </div>\n </div>\n <div class=\"empty-state\" *ngIf=\"TopUsedKeys.length === 0\">\n <i class=\"fa-solid fa-key\"></i>\n <span>No active keys with recent usage</span>\n </div>\n </div>\n </div>\n\n <!-- Scope Categories -->\n <div class=\"panel scope-panel\">\n <div class=\"panel-header\">\n <div class=\"panel-title\">\n <i class=\"fa-solid fa-shield-halved\"></i>\n <span>Permission Scopes</span>\n </div>\n <span class=\"panel-subtitle\">Available scope categories</span>\n </div>\n <div class=\"panel-body\">\n <div class=\"scope-chart\" *ngIf=\"ScopeStats.length > 0\">\n <!-- Visual Donut Chart -->\n <div class=\"donut-chart-container\">\n <svg viewBox=\"0 0 100 100\" class=\"donut-chart\">\n <circle cx=\"50\" cy=\"50\" r=\"40\" fill=\"none\" stroke=\"#e5e7eb\" stroke-width=\"12\"/>\n <ng-container *ngFor=\"let stat of ScopeStats; let i = index\">\n <circle\n cx=\"50\" cy=\"50\" r=\"40\"\n fill=\"none\"\n [attr.stroke]=\"stat.color\"\n stroke-width=\"12\"\n [attr.stroke-dasharray]=\"stat.percentage * 2.51 + ' ' + (251 - stat.percentage * 2.51)\"\n [attr.stroke-dashoffset]=\"getDonutOffset(i)\"\n class=\"donut-segment\"\n (click)=\"onScopeClick(stat)\"\n />\n </ng-container>\n </svg>\n <div class=\"donut-center\">\n <div class=\"donut-total\">{{ScopeStats.length}}</div>\n <div class=\"donut-label\">Categories</div>\n </div>\n </div>\n <!-- Legend -->\n <div class=\"scope-legend\">\n <div class=\"legend-item\" *ngFor=\"let stat of ScopeStats\" (click)=\"onScopeClick(stat)\">\n <div class=\"legend-color\" [style.backgroundColor]=\"stat.color\"></div>\n <div class=\"legend-info\">\n <div class=\"legend-name\">\n <i [class]=\"stat.iconClass\"></i>\n {{stat.category}}\n </div>\n <div class=\"legend-value\">{{stat.count}} scopes</div>\n </div>\n <i class=\"fa-solid fa-chevron-right legend-arrow\"></i>\n </div>\n </div>\n </div>\n <div class=\"empty-state\" *ngIf=\"ScopeStats.length === 0\">\n <i class=\"fa-solid fa-shield-halved\"></i>\n <span>No scopes configured</span>\n </div>\n </div>\n </div>\n\n <!-- Recent Activity -->\n <div class=\"panel activity-panel\">\n <div class=\"panel-header\">\n <div class=\"panel-title\">\n <i class=\"fa-solid fa-clock-rotate-left\"></i>\n <span>Recent Activity</span>\n </div>\n </div>\n <div class=\"panel-body\">\n <div class=\"activity-list\" *ngIf=\"RecentActivity.length > 0\">\n <div class=\"activity-item\" *ngFor=\"let activity of RecentActivity\" (click)=\"onActivityClick(activity)\">\n <div class=\"activity-icon\" [ngClass]=\"getActionClass(activity.action)\">\n <i [class]=\"getActionIcon(activity.action)\"></i>\n </div>\n <div class=\"activity-info\">\n <div class=\"activity-name\">{{activity.keyLabel}}</div>\n <div class=\"activity-meta\">\n <span class=\"activity-action\">{{activity.action}}</span>\n <span class=\"activity-user\" *ngIf=\"activity.user\">by {{activity.user}}</span>\n </div>\n </div>\n <div class=\"activity-time\">\n {{formatDate(activity.date)}}\n </div>\n </div>\n </div>\n <div class=\"empty-state\" *ngIf=\"RecentActivity.length === 0\">\n <i class=\"fa-solid fa-inbox\"></i>\n <span>No recent activity</span>\n </div>\n </div>\n </div>\n </div>\n\n <!-- Quick Actions -->\n <div class=\"quick-actions\" *ngIf=\"UserCanCreateKeys\">\n <div class=\"quick-actions-title\">Quick Actions</div>\n <div class=\"quick-actions-grid\">\n <button class=\"quick-action\" (click)=\"openCreateDialog()\">\n <i class=\"fa-solid fa-plus\"></i>\n <span>Generate Key</span>\n </button>\n <button class=\"quick-action\" (click)=\"showListView('all')\">\n <i class=\"fa-solid fa-list\"></i>\n <span>View All Keys</span>\n </button>\n <button class=\"quick-action\" (click)=\"showListView('expiring')\">\n <i class=\"fa-solid fa-clock\"></i>\n <span>Expiring Keys</span>\n </button>\n <button class=\"quick-action\" (click)=\"showListView('never-used')\">\n <i class=\"fa-solid fa-question-circle\"></i>\n <span>Never Used</span>\n </button>\n </div>\n </div>\n\n <!-- Security Notice -->\n <div class=\"security-notice\">\n <i class=\"fa-solid fa-shield-check\"></i>\n <div class=\"notice-content\">\n <strong>Security Best Practices:</strong>\n API keys should be rotated regularly and revoked when no longer needed.\n Keys are hashed and stored securely - the raw key is only shown once at creation time.\n All API key usage is logged for audit purposes.\n </div>\n </div>\n </ng-container>\n\n <!-- List View -->\n <ng-container *ngIf=\"CurrentView === 'list'\">\n <div class=\"list-view-header\">\n <button class=\"back-btn\" (click)=\"showOverview()\">\n <i class=\"fa-solid fa-arrow-left\"></i>\n Back to Overview\n </button>\n </div>\n <mj-api-key-list #keyList\n [Filter]=\"ListFilter\"\n (KeySelected)=\"onKeySelected($event)\"\n (CreateRequested)=\"openCreateDialog()\">\n </mj-api-key-list>\n </ng-container>\n </ng-container>\n\n <!-- Applications Tab Content -->\n <ng-container *ngIf=\"MainTab === 'applications'\">\n <mj-api-applications-panel\n (ApplicationUpdated)=\"onDataUpdated()\">\n </mj-api-applications-panel>\n </ng-container>\n\n <!-- Scopes Tab Content -->\n <ng-container *ngIf=\"MainTab === 'scopes'\">\n <mj-api-scopes-panel\n (ScopeUpdated)=\"onDataUpdated()\">\n </mj-api-scopes-panel>\n </ng-container>\n\n <!-- Usage Tab Content -->\n <ng-container *ngIf=\"MainTab === 'usage'\">\n <mj-api-usage-panel></mj-api-usage-panel>\n </ng-container>\n </div><!-- /.content-wrapper -->\n </div><!-- /.dashboard-content -->\n\n <!-- Mobile Navigation Toggle -->\n <button class=\"mobile-nav-toggle\" (click)=\"toggleNav()\">\n <i class=\"fa-solid\" [class.fa-bars]=\"!NavOpen\" [class.fa-times]=\"NavOpen\"></i>\n </button>\n\n <!-- Mobile Navigation Overlay -->\n <div class=\"mobile-nav-overlay\" (click)=\"closeNav()\"></div>\n </ng-container>\n\n <!-- Create Dialog -->\n <mj-api-key-create-dialog\n [Visible]=\"ShowCreateDialog\"\n (Created)=\"onKeyCreated($event)\"\n (Closed)=\"onCreateDialogClosed()\">\n </mj-api-key-create-dialog>\n\n <!-- Edit Panel -->\n <mj-api-key-edit-panel\n [Visible]=\"ShowEditPanel\"\n [KeyId]=\"SelectedKeyId\"\n (Updated)=\"onKeyUpdated()\"\n (Revoked)=\"onKeyRevoked()\"\n (Closed)=\"onEditPanelClosed()\">\n </mj-api-key-edit-panel>\n</div>\n", styles: ["/* Main Dashboard Container */\n.api-keys-dashboard {\n display: flex;\n height: 100%;\n position: relative;\n overflow: hidden;\n}\n\n.api-keys-dashboard.panel-open .dashboard-content {\n overflow: hidden;\n}\n\n/* Left Navigation Sidebar */\n.dashboard-nav {\n width: 220px;\n background: var(--card-background, #ffffff);\n border-right: 1px solid var(--border-color, #e5e7eb);\n display: flex;\n flex-direction: column;\n flex-shrink: 0;\n}\n\n.nav-header {\n padding: 20px;\n border-bottom: 1px solid var(--border-color, #e5e7eb);\n}\n\n.nav-title {\n display: flex;\n align-items: center;\n gap: 10px;\n font-weight: 700;\n font-size: 16px;\n color: var(--text-primary, #1f2937);\n}\n\n.nav-title i {\n color: #f59e0b;\n font-size: 18px;\n}\n\n.nav-items {\n flex: 1;\n padding: 12px;\n overflow-y: auto;\n}\n\n.nav-item {\n display: flex;\n align-items: center;\n gap: 12px;\n padding: 12px 16px;\n margin-bottom: 4px;\n background: transparent;\n border: none;\n border-radius: 8px;\n font-size: 14px;\n font-weight: 500;\n color: var(--text-secondary, #6b7280);\n cursor: pointer;\n transition: all 0.2s ease;\n width: 100%;\n text-align: left;\n}\n\n.nav-item:hover {\n background: var(--item-hover, #f3f4f6);\n color: var(--text-primary, #1f2937);\n}\n\n.nav-item.active {\n background: linear-gradient(135deg, #f59e0b 0%, #d97706 100%);\n color: white;\n box-shadow: 0 2px 8px rgba(245, 158, 11, 0.3);\n}\n\n.nav-item i {\n font-size: 16px;\n width: 20px;\n text-align: center;\n}\n\n.nav-item .nav-label {\n flex: 1;\n}\n\n.nav-badge {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n min-width: 22px;\n height: 20px;\n padding: 0 6px;\n background: rgba(0, 0, 0, 0.1);\n border-radius: 10px;\n font-size: 11px;\n font-weight: 600;\n}\n\n.nav-item.active .nav-badge {\n background: rgba(255, 255, 255, 0.25);\n}\n\n/* Dashboard Content Area */\n.dashboard-content {\n flex: 1;\n display: flex;\n flex-direction: column;\n overflow-y: auto;\n min-width: 0;\n}\n\n.content-wrapper {\n flex: 1;\n padding: 24px;\n overflow-y: auto;\n}\n\n/* List View Header */\n.list-view-header {\n margin-bottom: 16px;\n}\n\n.back-btn {\n display: inline-flex;\n align-items: center;\n gap: 8px;\n padding: 8px 16px;\n background: var(--card-background, #ffffff);\n border: 1px solid var(--border-color, #e5e7eb);\n border-radius: 8px;\n font-size: 14px;\n font-weight: 500;\n color: var(--text-secondary, #6b7280);\n cursor: pointer;\n transition: all 0.2s ease;\n}\n\n.back-btn:hover {\n background: var(--item-hover, #f3f4f6);\n color: var(--text-primary, #1f2937);\n}\n\n/* Header */\n.overview-header {\n display: flex;\n justify-content: space-between;\n align-items: flex-start;\n margin-bottom: 24px;\n}\n\n.header-left {\n flex: 1;\n}\n\n.overview-title {\n font-size: 24px;\n font-weight: 700;\n color: var(--text-primary, #1f2937);\n margin: 0 0 4px 0;\n}\n\n.overview-subtitle {\n font-size: 14px;\n color: var(--text-secondary, #6b7280);\n margin: 0;\n}\n\n.header-actions {\n display: flex;\n gap: 12px;\n}\n\n.btn-refresh {\n display: flex;\n align-items: center;\n justify-content: center;\n width: 40px;\n height: 40px;\n background: var(--card-background, #ffffff);\n border: 1px solid var(--border-color, #e5e7eb);\n border-radius: 8px;\n cursor: pointer;\n transition: all 0.2s ease;\n}\n\n.btn-refresh:hover {\n background: var(--item-hover, #f3f4f6);\n}\n\n.btn-refresh i {\n color: var(--text-secondary, #6b7280);\n}\n\n.btn-primary {\n display: flex;\n align-items: center;\n gap: 8px;\n padding: 10px 20px;\n background: linear-gradient(135deg, #f59e0b 0%, #d97706 100%);\n color: white;\n border: none;\n border-radius: 8px;\n font-weight: 600;\n font-size: 14px;\n cursor: pointer;\n transition: all 0.2s ease;\n}\n\n.btn-primary:hover {\n transform: translateY(-1px);\n box-shadow: 0 4px 12px rgba(245, 158, 11, 0.4);\n}\n\n/* Health Banner */\n.health-banner {\n display: flex;\n align-items: center;\n padding: 20px 24px;\n background: linear-gradient(135deg, #ecfdf5 0%, #d1fae5 100%);\n border: 1px solid #a7f3d0;\n border-radius: 16px;\n margin-bottom: 24px;\n}\n\n.health-banner.health-warning {\n background: linear-gradient(135deg, #fffbeb 0%, #fef3c7 100%);\n border-color: #fcd34d;\n}\n\n.health-banner.health-critical {\n background: linear-gradient(135deg, #fef2f2 0%, #fee2e2 100%);\n border-color: #fca5a5;\n}\n\n.health-score-container {\n margin-right: 24px;\n}\n\n.health-score-ring {\n position: relative;\n width: 80px;\n height: 80px;\n}\n\n.circular-chart {\n display: block;\n margin: 0 auto;\n max-height: 80px;\n transform: rotate(-90deg);\n}\n\n.circle-bg {\n fill: none;\n stroke: #e5e7eb;\n stroke-width: 3.8;\n}\n\n.circle {\n fill: none;\n stroke-width: 3.8;\n stroke-linecap: round;\n stroke: #10b981;\n animation: progress 1s ease-out forwards;\n}\n\n.health-banner.health-warning .circle {\n stroke: #f59e0b;\n}\n\n.health-banner.health-critical .circle {\n stroke: #ef4444;\n}\n\n@keyframes progress {\n 0% {\n stroke-dasharray: 0 100;\n }\n}\n\n.health-score-value {\n position: absolute;\n top: 50%;\n left: 50%;\n transform: translate(-50%, -50%);\n font-size: 20px;\n font-weight: 700;\n color: var(--text-primary, #1f2937);\n}\n\n.health-info {\n flex: 1;\n}\n\n.health-label {\n font-size: 18px;\n font-weight: 700;\n color: #059669;\n margin-bottom: 4px;\n}\n\n.health-banner.health-warning .health-label {\n color: #d97706;\n}\n\n.health-banner.health-critical .health-label {\n color: #dc2626;\n}\n\n.health-details {\n display: flex;\n gap: 16px;\n flex-wrap: wrap;\n}\n\n.health-alert {\n display: flex;\n align-items: center;\n gap: 6px;\n color: #dc2626;\n font-weight: 500;\n font-size: 14px;\n cursor: pointer;\n}\n\n.health-alert:hover {\n text-decoration: underline;\n}\n\n.health-warning-text {\n display: flex;\n align-items: center;\n gap: 6px;\n color: #d97706;\n font-weight: 500;\n font-size: 14px;\n cursor: pointer;\n}\n\n.health-warning-text:hover {\n text-decoration: underline;\n}\n\n.health-info-text {\n display: flex;\n align-items: center;\n gap: 6px;\n color: #6b7280;\n font-weight: 500;\n font-size: 14px;\n cursor: pointer;\n}\n\n.health-info-text:hover {\n text-decoration: underline;\n}\n\n.health-ok {\n display: flex;\n align-items: center;\n gap: 6px;\n color: #059669;\n font-weight: 500;\n font-size: 14px;\n}\n\n/* KPI Cards */\n.kpi-row {\n display: grid;\n grid-template-columns: repeat(auto-fit, minmax(160px, 1fr));\n gap: 16px;\n margin-bottom: 24px;\n}\n\n.kpi-card {\n display: flex;\n align-items: center;\n padding: 16px 20px;\n background: var(--card-background, #ffffff);\n border-radius: 12px;\n box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);\n transition: transform 0.2s ease, box-shadow 0.2s ease;\n}\n\n.kpi-card.clickable {\n cursor: pointer;\n}\n\n.kpi-card.clickable:hover {\n transform: translateY(-2px);\n box-shadow: 0 4px 16px rgba(0, 0, 0, 0.12);\n}\n\n.kpi-card.active .kpi-icon {\n background: linear-gradient(135deg, #10b981 0%, #059669 100%);\n}\n\n.kpi-card.warning .kpi-icon {\n background: linear-gradient(135deg, #f59e0b 0%, #d97706 100%);\n}\n\n.kpi-card.danger .kpi-icon {\n background: linear-gradient(135deg, #ef4444 0%, #dc2626 100%);\n}\n\n.kpi-icon {\n width: 48px;\n height: 48px;\n display: flex;\n align-items: center;\n justify-content: center;\n background: linear-gradient(135deg, #f59e0b 0%, #d97706 100%);\n border-radius: 10px;\n margin-right: 14px;\n flex-shrink: 0;\n}\n\n.kpi-icon i {\n font-size: 20px;\n color: white;\n}\n\n.kpi-content {\n flex: 1;\n min-width: 0;\n}\n\n.kpi-value {\n font-size: 24px;\n font-weight: 700;\n color: var(--text-primary, #1f2937);\n line-height: 1.2;\n}\n\n.kpi-label {\n font-size: 12px;\n color: var(--text-secondary, #6b7280);\n margin-top: 2px;\n white-space: nowrap;\n}\n\n.kpi-trend {\n display: flex;\n align-items: center;\n margin-left: 8px;\n color: var(--text-tertiary, #9ca3af);\n}\n\n.kpi-trend.success {\n color: #10b981;\n}\n\n.kpi-trend.warning {\n color: #f59e0b;\n}\n\n.kpi-trend .percentage {\n font-size: 12px;\n font-weight: 600;\n}\n\n/* Content Grid */\n.content-grid {\n display: grid;\n grid-template-columns: 1fr 1fr;\n grid-template-rows: auto auto;\n gap: 20px;\n margin-bottom: 24px;\n}\n\n.keys-panel {\n grid-row: span 1;\n}\n\n.scope-panel {\n grid-row: span 1;\n}\n\n.activity-panel {\n grid-column: span 2;\n}\n\n.panel {\n background: var(--card-background, #ffffff);\n border-radius: 12px;\n box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);\n overflow: hidden;\n display: flex;\n flex-direction: column;\n}\n\n.panel-header {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 16px 20px;\n background: var(--header-background, #f9fafb);\n border-bottom: 1px solid var(--border-color, #e5e7eb);\n}\n\n.panel-title {\n display: flex;\n align-items: center;\n gap: 10px;\n font-weight: 600;\n font-size: 14px;\n color: var(--text-primary, #1f2937);\n}\n\n.panel-title i {\n color: #f59e0b;\n}\n\n.panel-subtitle {\n font-size: 12px;\n color: var(--text-tertiary, #9ca3af);\n}\n\n.panel-action {\n display: flex;\n align-items: center;\n gap: 6px;\n padding: 6px 12px;\n background: transparent;\n border: 1px solid var(--border-color, #e5e7eb);\n border-radius: 6px;\n font-size: 12px;\n font-weight: 500;\n color: var(--text-secondary, #6b7280);\n cursor: pointer;\n transition: all 0.2s ease;\n}\n\n.panel-action:hover {\n background: #f59e0b;\n border-color: #f59e0b;\n color: white;\n}\n\n.panel-body {\n padding: 20px;\n flex: 1;\n overflow-y: auto;\n}\n\n/* Key List */\n.key-list {\n display: flex;\n flex-direction: column;\n gap: 12px;\n}\n\n.key-item {\n display: flex;\n align-items: center;\n padding: 14px 16px;\n background: var(--item-background, #f9fafb);\n border-radius: 10px;\n cursor: pointer;\n transition: all 0.2s ease;\n}\n\n.key-item:hover {\n background: var(--item-hover, #f3f4f6);\n transform: translateX(4px);\n}\n\n.key-icon {\n width: 40px;\n height: 40px;\n display: flex;\n align-items: center;\n justify-content: center;\n background: linear-gradient(135deg, #6b7280 0%, #4b5563 100%);\n border-radius: 8px;\n margin-right: 14px;\n flex-shrink: 0;\n}\n\n.key-icon.active {\n background: linear-gradient(135deg, #f59e0b 0%, #d97706 100%);\n}\n\n.key-icon i {\n font-size: 16px;\n color: white;\n}\n\n.key-info {\n flex: 1;\n min-width: 0;\n}\n\n.key-label {\n font-size: 14px;\n font-weight: 600;\n color: var(--text-primary, #1f2937);\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n}\n\n.key-meta {\n display: flex;\n align-items: center;\n gap: 8px;\n margin-top: 2px;\n font-size: 12px;\n color: var(--text-secondary, #6b7280);\n}\n\n.key-hash {\n font-family: monospace;\n background: var(--tag-background, #e5e7eb);\n padding: 2px 6px;\n border-radius: 4px;\n font-size: 11px;\n}\n\n.key-status {\n display: flex;\n flex-direction: column;\n align-items: flex-end;\n gap: 4px;\n margin-left: 16px;\n}\n\n.last-used {\n font-size: 12px;\n color: var(--text-secondary, #6b7280);\n}\n\n.expiration {\n font-size: 11px;\n color: var(--text-tertiary, #9ca3af);\n}\n\n.expiration.expiring-soon {\n color: #f59e0b;\n font-weight: 500;\n}\n\n.expiration.expiring-critical {\n color: #ef4444;\n font-weight: 500;\n}\n\n.expiration.expired {\n color: #ef4444;\n font-weight: 600;\n}\n\n/* Scope Chart */\n.scope-chart {\n display: flex;\n flex-direction: column;\n gap: 24px;\n height: 100%;\n}\n\n.donut-chart-container {\n position: relative;\n width: 160px;\n height: 160px;\n margin: 0 auto;\n}\n\n.donut-chart {\n width: 100%;\n height: 100%;\n transform: rotate(-90deg);\n}\n\n.donut-segment {\n cursor: pointer;\n transition: opacity 0.2s ease;\n}\n\n.donut-segment:hover {\n opacity: 0.8;\n}\n\n.donut-center {\n position: absolute;\n top: 50%;\n left: 50%;\n transform: translate(-50%, -50%);\n text-align: center;\n}\n\n.donut-total {\n font-size: 28px;\n font-weight: 700;\n color: var(--text-primary, #1f2937);\n}\n\n.donut-label {\n font-size: 11px;\n color: var(--text-secondary, #6b7280);\n}\n\n/* Scope Legend */\n.scope-legend {\n display: flex;\n flex-direction: column;\n gap: 8px;\n}\n\n.legend-item {\n display: flex;\n align-items: center;\n padding: 10px 12px;\n background: var(--item-background, #f9fafb);\n border-radius: 8px;\n cursor: pointer;\n transition: all 0.2s ease;\n}\n\n.legend-item:hover {\n background: var(--item-hover, #f3f4f6);\n transform: translateX(4px);\n}\n\n.legend-color {\n width: 12px;\n height: 12px;\n border-radius: 4px;\n margin-right: 12px;\n flex-shrink: 0;\n}\n\n.legend-info {\n flex: 1;\n}\n\n.legend-name {\n font-size: 14px;\n font-weight: 500;\n color: var(--text-primary, #1f2937);\n display: flex;\n align-items: center;\n gap: 8px;\n}\n\n.legend-name i {\n font-size: 12px;\n color: var(--text-secondary, #6b7280);\n}\n\n.legend-value {\n font-size: 12px;\n color: var(--text-secondary, #6b7280);\n}\n\n.legend-arrow {\n color: var(--text-tertiary, #9ca3af);\n font-size: 10px;\n}\n\n/* Activity List */\n.activity-list {\n display: grid;\n grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));\n gap: 12px;\n}\n\n.activity-item {\n display: flex;\n align-items: center;\n padding: 12px 14px;\n background: var(--item-background, #f9fafb);\n border-radius: 8px;\n cursor: pointer;\n transition: all 0.2s ease;\n}\n\n.activity-item:hover {\n background: var(--item-hover, #f3f4f6);\n}\n\n.activity-icon {\n width: 32px;\n height: 32px;\n display: flex;\n align-items: center;\n justify-content: center;\n background: var(--icon-background, #fef3c7);\n border-radius: 6px;\n margin-right: 12px;\n flex-shrink: 0;\n}\n\n.activity-icon i {\n font-size: 12px;\n color: #f59e0b;\n}\n\n.activity-icon.action-created {\n background: #d1fae5;\n}\n\n.activity-icon.action-created i {\n color: #059669;\n}\n\n.activity-icon.action-updated {\n background: #e0e7ff;\n}\n\n.activity-icon.action-updated i {\n color: #6366f1;\n}\n\n.activity-icon.action-revoked {\n background: #fee2e2;\n}\n\n.activity-icon.action-revoked i {\n color: #dc2626;\n}\n\n.activity-icon.action-used {\n background: #fef3c7;\n}\n\n.activity-icon.action-used i {\n color: #d97706;\n}\n\n.activity-icon.action-extended {\n background: #dbeafe;\n}\n\n.activity-icon.action-extended i {\n color: #3b82f6;\n}\n\n.activity-info {\n flex: 1;\n min-width: 0;\n}\n\n.activity-name {\n font-weight: 500;\n font-size: 13px;\n color: var(--text-primary, #1f2937);\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n}\n\n.activity-meta {\n display: flex;\n align-items: center;\n gap: 6px;\n margin-top: 2px;\n font-size: 11px;\n color: var(--text-secondary, #6b7280);\n}\n\n.activity-action {\n font-weight: 500;\n text-transform: capitalize;\n}\n\n.activity-time {\n font-size: 11px;\n color: var(--text-tertiary, #9ca3af);\n white-space: nowrap;\n}\n\n/* Quick Actions */\n.quick-actions {\n margin-bottom: 24px;\n}\n\n.quick-actions-title {\n font-size: 14px;\n font-weight: 600;\n color: var(--text-primary, #1f2937);\n margin-bottom: 12px;\n}\n\n.quick-actions-grid {\n display: grid;\n grid-template-columns: repeat(auto-fit, minmax(140px, 1fr));\n gap: 12px;\n}\n\n.quick-action {\n display: flex;\n flex-direction: column;\n align-items: center;\n gap: 8px;\n padding: 16px;\n background: var(--card-background, #ffffff);\n border: 1px solid var(--border-color, #e5e7eb);\n border-radius: 10px;\n cursor: pointer;\n transition: all 0.2s ease;\n}\n\n.quick-action:hover {\n border-color: #f59e0b;\n background: #fffbeb;\n transform: translateY(-2px);\n}\n\n.quick-action i {\n font-size: 20px;\n color: #f59e0b;\n}\n\n.quick-action span {\n font-size: 12px;\n font-weight: 500;\n color: var(--text-secondary, #6b7280);\n}\n\n/* Empty State */\n.empty-state {\n display: flex;\n flex-direction: column;\n align-items: center;\n justify-content: center;\n padding: 32px;\n color: var(--text-secondary, #6b7280);\n text-align: center;\n}\n\n.empty-state i {\n font-size: 32px;\n margin-bottom: 12px;\n opacity: 0.5;\n}\n\n/* Security Notice */\n.security-notice {\n display: flex;\n align-items: flex-start;\n padding: 16px 20px;\n background: linear-gradient(135deg, #fffbeb 0%, #fef3c7 100%);\n border-radius: 12px;\n border: 1px solid #fcd34d;\n}\n\n.security-notice i {\n font-size: 20px;\n color: #f59e0b;\n margin-right: 12px;\n margin-top: 2px;\n}\n\n.notice-content {\n font-size: 13px;\n color: #92400e;\n line-height: 1.5;\n}\n\n.notice-content strong {\n font-weight: 600;\n}\n\n/* Mobile Navigation Toggle */\n.mobile-nav-toggle {\n display: none;\n position: fixed;\n bottom: 20px;\n right: 20px;\n width: 56px;\n height: 56px;\n background: linear-gradient(135deg, #f59e0b 0%, #d97706 100%);\n border: none;\n border-radius: 50%;\n color: white;\n font-size: 24px;\n box-shadow: 0 4px 16px rgba(245, 158, 11, 0.4);\n cursor: pointer;\n z-index: 50;\n transition: transform 0.2s ease;\n}\n\n.mobile-nav-toggle:hover {\n transform: scale(1.05);\n}\n\n/* Mobile Nav Overlay */\n.mobile-nav-overlay {\n display: none;\n position: fixed;\n top: 0;\n left: 0;\n right: 0;\n bottom: 0;\n background: rgba(0, 0, 0, 0.5);\n z-index: 90;\n}\n\n/* ========================================\n Responsive Styles\n ======================================== */\n\n/* Tablet */\n@media (max-width: 1024px) {\n .content-grid {\n grid-template-columns: 1fr;\n }\n\n .activity-panel {\n grid-column: span 1;\n }\n\n .scope-chart {\n flex-direction: row;\n align-items: center;\n }\n\n .donut-chart-container {\n width: 140px;\n height: 140px;\n }\n\n .scope-legend {\n flex: 1;\n }\n\n .dashboard-nav {\n width: 200px;\n }\n}\n\n/* Mobile */\n@media (max-width: 768px) {\n .api-keys-dashboard {\n flex-direction: column;\n }\n\n .dashboard-nav {\n position: fixed;\n top: 0;\n left: -280px;\n width: 280px;\n height: 100%;\n z-index: 100;\n transition: left 0.3s ease;\n box-shadow: 4px 0 20px rgba(0, 0, 0, 0.15);\n }\n\n .api-keys-dashboard.nav-open .dashboard-nav {\n left: 0;\n }\n\n .mobile-nav-toggle {\n display: flex;\n align-items: center;\n justify-content: center;\n }\n\n .mobile-nav-overlay {\n display: block;\n opacity: 0;\n pointer-events: none;\n transition: opacity 0.3s ease;\n }\n\n .api-keys-dashboard.nav-open .mobile-nav-overlay {\n opacity: 1;\n pointer-events: auto;\n }\n\n .dashboard-content {\n width: 100%;\n }\n\n .content-wrapper {\n padding: 16px;\n }\n\n .overview-header {\n flex-direction: column;\n gap: 16px;\n }\n\n .header-actions {\n width: 100%;\n justify-content: flex-end;\n }\n\n .health-banner {\n flex-direction: column;\n text-align: center;\n }\n\n .health-score-container {\n margin-right: 0;\n margin-bottom: 16px;\n }\n\n .kpi-row {\n grid-template-columns: repeat(2, 1fr);\n }\n\n .scope-chart {\n flex-direction: column;\n }\n\n .quick-actions-grid {\n grid-template-columns: repeat(2, 1fr);\n }\n\n .activity-list {\n grid-template-columns: 1fr;\n }\n\n .key-item {\n flex-wrap: wrap;\n }\n\n .key-status {\n width: 100%;\n flex-direction: row;\n justify-content: flex-start;\n margin-left: 54px;\n margin-top: 8px;\n gap: 12px;\n }\n}\n\n/* Small Mobile */\n@media (max-width: 480px) {\n .kpi-row {\n grid-template-columns: 1fr;\n }\n\n .quick-actions-grid {\n grid-template-columns: 1fr;\n }\n\n .kpi-card {\n padding: 12px 16px;\n }\n\n .kpi-icon {\n width: 40px;\n height: 40px;\n }\n\n .kpi-value {\n font-size: 20px;\n }\n\n .overview-title {\n font-size: 20px;\n }\n\n .btn-primary {\n padding: 8px 16px;\n font-size: 13px;\n }\n}\n"] }]
|
|
1160
|
+
}], () => [{ type: i0.ChangeDetectorRef }], { keyListComponent: [{
|
|
1161
|
+
type: ViewChild,
|
|
1162
|
+
args: ['keyList']
|
|
1163
|
+
}] }); })();
|
|
1164
|
+
(() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassDebugInfo(APIKeysResourceComponent, { className: "APIKeysResourceComponent", filePath: "src/APIKeys/api-keys-resource.component.ts", lineNumber: 56 }); })();
|
|
1165
|
+
//# sourceMappingURL=api-keys-resource.component.js.map
|