akv-manager 0.0.1 → 0.0.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/package.json +1 -1
- package/public/index.html +35 -258
- package/public/styles.css +249 -0
- package/server.js +16 -7
package/README.md
CHANGED
|
@@ -52,7 +52,7 @@ By default environments are inferred from common keywords (`dev`, `test`, `qa`,
|
|
|
52
52
|
convention, map them yourself, e.g.:
|
|
53
53
|
|
|
54
54
|
```bash
|
|
55
|
-
AZBO_ENV_RULES='[["
|
|
55
|
+
AZBO_ENV_RULES='[["-prd-","Prod"],["-dev-","Dev"],["-tst-","Test"]]' npx akv-manager
|
|
56
56
|
```
|
|
57
57
|
|
|
58
58
|
## Notes on access
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "akv-manager",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.3",
|
|
4
4
|
"description": "Local web console to browse and manage Azure Key Vaults — secrets, keys, certificates, expiry — using your own az login. No credentials are stored.",
|
|
5
5
|
"type": "commonjs",
|
|
6
6
|
"main": "server.js",
|
package/public/index.html
CHANGED
|
@@ -7,262 +7,34 @@
|
|
|
7
7
|
<link rel="preconnect" href="https://fonts.googleapis.com">
|
|
8
8
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
|
9
9
|
<link href="https://fonts.googleapis.com/css2?family=IBM+Plex+Mono:wght@400;500;600&family=IBM+Plex+Sans:wght@400;500;600;700&display=swap" rel="stylesheet">
|
|
10
|
-
<
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
.muted{color:var(--muted)} .faint{color:var(--faint)} .right{text-align:right}
|
|
36
|
-
.hide{display:none!important}
|
|
37
|
-
.eyebrow{font-family:var(--mono);font-size:10.5px;font-weight:500;letter-spacing:.18em;
|
|
38
|
-
text-transform:uppercase;color:var(--faint)}
|
|
39
|
-
|
|
40
|
-
.app{display:grid;grid-template-columns:236px 1fr;min-height:100vh}
|
|
41
|
-
|
|
42
|
-
/* ---------- sidebar ---------- */
|
|
43
|
-
aside{background:linear-gradient(180deg,rgba(255,255,255,.015),transparent),var(--panel2);
|
|
44
|
-
border-right:1px solid var(--line);padding:22px 14px;display:flex;flex-direction:column;
|
|
45
|
-
position:sticky;top:0;height:100vh}
|
|
46
|
-
.brand{display:flex;align-items:center;gap:11px;padding:0 6px 20px}
|
|
47
|
-
.brand .mark{width:34px;height:34px;flex:none}
|
|
48
|
-
.brand .wm{font-family:var(--mono);font-weight:600;font-size:13px;letter-spacing:.14em;color:var(--text);line-height:1.15}
|
|
49
|
-
.brand .wm small{display:block;font-size:9.5px;letter-spacing:.22em;color:var(--faint);font-weight:400;margin-top:2px}
|
|
50
|
-
.navlabel{font-family:var(--mono);font-size:9.5px;letter-spacing:.2em;color:var(--faint);
|
|
51
|
-
text-transform:uppercase;padding:14px 10px 7px}
|
|
52
|
-
nav button{display:flex;align-items:center;gap:11px;width:100%;text-align:left;background:transparent;
|
|
53
|
-
border:0;color:var(--muted);padding:9px 11px;border-radius:var(--r-sm);cursor:pointer;
|
|
54
|
-
font-family:var(--sans);font-size:13.5px;font-weight:500;position:relative;transition:.15s}
|
|
55
|
-
nav button svg{width:17px;height:17px;flex:none;opacity:.8}
|
|
56
|
-
nav button:hover{background:rgba(255,255,255,.03);color:var(--text)}
|
|
57
|
-
nav button.active{background:var(--azuredim);color:#fff}
|
|
58
|
-
nav button.active svg{opacity:1;color:var(--azure)}
|
|
59
|
-
nav button.active::before{content:"";position:absolute;left:-14px;top:7px;bottom:7px;width:3px;
|
|
60
|
-
background:var(--azure);border-radius:0 3px 3px 0}
|
|
61
|
-
.who{margin-top:auto;font-size:12px;color:var(--muted);border-top:1px solid var(--line2);
|
|
62
|
-
padding:14px 8px 4px;line-height:1.6}
|
|
63
|
-
.who b{color:var(--text);font-weight:600}
|
|
64
|
-
.who .mono{font-size:11px}
|
|
65
|
-
.dot{width:7px;height:7px;border-radius:50%;display:inline-block;vertical-align:middle}
|
|
66
|
-
|
|
67
|
-
/* ---------- main ---------- */
|
|
68
|
-
main{padding:26px 32px 60px;max-width:1560px}
|
|
69
|
-
.topbar{display:flex;align-items:flex-end;justify-content:space-between;gap:18px;margin-bottom:26px}
|
|
70
|
-
h1{font-size:23px;font-weight:600;margin:4px 0 0;letter-spacing:-.01em}
|
|
71
|
-
.sub{color:var(--muted);font-size:13px;margin-top:3px;max-width:62ch}
|
|
72
|
-
.row{display:flex;gap:10px;align-items:center;flex-wrap:wrap}
|
|
73
|
-
|
|
74
|
-
.btn{font-family:var(--sans);font-weight:500;font-size:13px;border:0;border-radius:var(--r-sm);
|
|
75
|
-
padding:9px 15px;cursor:pointer;color:#06101f;background:var(--azure);transition:.15s}
|
|
76
|
-
.btn:hover{background:#75AEFF}
|
|
77
|
-
.btn:disabled{opacity:.55;cursor:default}
|
|
78
|
-
.btn.ghost{background:var(--raise);color:var(--text);border:1px solid var(--line)}
|
|
79
|
-
.btn.ghost:hover{border-color:var(--azure);background:var(--raise)}
|
|
80
|
-
.btn.sm{padding:5px 11px;font-size:12px}
|
|
81
|
-
input.search{font-family:var(--mono);background:var(--panel2);border:1px solid var(--line);color:var(--text);
|
|
82
|
-
padding:9px 13px;border-radius:var(--r-sm);min-width:320px;font-size:12.5px}
|
|
83
|
-
input.search::placeholder{color:var(--faint)}
|
|
84
|
-
input.search:focus{outline:none;border-color:var(--azure);background:var(--panel)}
|
|
85
|
-
|
|
86
|
-
/* ---------- KPI cards ---------- */
|
|
87
|
-
.cards{display:grid;grid-template-columns:repeat(auto-fill,minmax(190px,1fr));gap:13px;margin-bottom:24px}
|
|
88
|
-
.card{background:linear-gradient(180deg,var(--panel),var(--panel2));border:1px solid var(--line);
|
|
89
|
-
border-radius:var(--r);padding:15px 17px;position:relative;overflow:hidden}
|
|
90
|
-
.card::after{content:"";position:absolute;inset:0 0 auto;height:2px;background:var(--line)}
|
|
91
|
-
.card .k{font-family:var(--mono);color:var(--muted);font-size:10.5px;text-transform:uppercase;letter-spacing:.13em}
|
|
92
|
-
.card .v{font-family:var(--mono);font-size:30px;font-weight:600;margin-top:9px;line-height:1;letter-spacing:-.02em}
|
|
93
|
-
.card .v small{font-family:var(--sans);font-size:12px;font-weight:400;color:var(--muted);letter-spacing:0}
|
|
94
|
-
.card--alert::after{background:var(--alert)} .card--alert .v{color:#FF8893}
|
|
95
|
-
.card--warn::after{background:var(--warn)} .card--warn .v{color:#FBC56B}
|
|
96
|
-
.card--azure::after{background:var(--azure)} .card--azure .v{color:#8FBEFF}
|
|
97
|
-
|
|
98
|
-
/* ---------- chips ---------- */
|
|
99
|
-
.chips{display:flex;gap:8px;flex-wrap:wrap;margin-bottom:16px}
|
|
100
|
-
.chip{font-family:var(--mono);background:var(--panel2);border:1px solid var(--line);color:var(--muted);
|
|
101
|
-
padding:6px 12px;border-radius:7px;cursor:pointer;font-size:11.5px;display:flex;gap:8px;align-items:center;transition:.15s}
|
|
102
|
-
.chip:hover{color:var(--text);border-color:var(--line)}
|
|
103
|
-
.chip.active{border-color:var(--azure);color:#fff;background:var(--azuredim)}
|
|
104
|
-
.chip .n{background:var(--ink);border-radius:5px;padding:1px 7px;font-size:10.5px;color:var(--muted)}
|
|
105
|
-
.chip.active .n{color:var(--azure)}
|
|
106
|
-
|
|
107
|
-
/* ---------- panel + table ---------- */
|
|
108
|
-
.panel{background:var(--panel);border:1px solid var(--line);border-radius:var(--r);overflow:hidden;box-shadow:var(--shadow)}
|
|
109
|
-
table{width:100%;border-collapse:collapse;font-size:13px}
|
|
110
|
-
th,td{padding:11px 16px;text-align:left;border-bottom:1px solid var(--line2);white-space:nowrap}
|
|
111
|
-
thead th{position:sticky;top:0;z-index:2;background:var(--panel2);color:var(--muted);font-weight:500;
|
|
112
|
-
font-family:var(--mono);font-size:10.5px;text-transform:uppercase;letter-spacing:.1em;
|
|
113
|
-
cursor:pointer;user-select:none}
|
|
114
|
-
thead th:hover{color:var(--text)}
|
|
115
|
-
tbody tr{transition:background .12s}
|
|
116
|
-
tbody tr:hover{background:rgba(91,157,255,.05)}
|
|
117
|
-
tbody tr:last-child td{border-bottom:0}
|
|
118
|
-
td.mono,.mono{font-family:var(--mono);font-size:12.5px}
|
|
119
|
-
td b{font-weight:600}
|
|
120
|
-
|
|
121
|
-
.tag{display:inline-flex;align-items:center;font-family:var(--mono);font-size:10px;font-weight:500;
|
|
122
|
-
letter-spacing:.08em;text-transform:uppercase;padding:2px 7px;border-radius:5px;border:1px solid transparent}
|
|
123
|
-
.tag.e-dev{color:#7FE0C8;background:rgba(70,214,180,.10);border-color:rgba(70,214,180,.22)}
|
|
124
|
-
.tag.e-test{color:#7FD0E0;background:rgba(70,180,214,.10);border-color:rgba(70,180,214,.22)}
|
|
125
|
-
.tag.e-qa{color:#93C0FF;background:rgba(91,157,255,.12);border-color:rgba(91,157,255,.24)}
|
|
126
|
-
.tag.e-stage{color:#F4C977;background:rgba(242,181,61,.10);border-color:rgba(242,181,61,.22)}
|
|
127
|
-
.tag.e-prod{color:#FF9BA4;background:rgba(255,92,108,.10);border-color:rgba(255,92,108,.26)}
|
|
128
|
-
.tag.e-sandbox{color:#BCA8FF;background:rgba(169,139,255,.10);border-color:rgba(169,139,255,.22)}
|
|
129
|
-
.tag.e-other{color:var(--muted);background:rgba(255,255,255,.04);border-color:var(--line)}
|
|
130
|
-
|
|
131
|
-
.pill{font-family:var(--mono);font-size:10px;letter-spacing:.06em;color:var(--muted);
|
|
132
|
-
border:1px solid var(--line);border-radius:5px;padding:1px 7px;white-space:nowrap}
|
|
133
|
-
.pill.rbac{color:#7FE0C8;border-color:rgba(70,214,180,.3)}
|
|
134
|
-
.pill.policy{color:#F4C977;border-color:rgba(242,181,61,.3)}
|
|
135
|
-
.sev{font-family:var(--mono);font-size:10px;letter-spacing:.1em;padding:2px 8px;border-radius:5px;border:1px solid}
|
|
136
|
-
.sev.expired{color:#FF8893;background:rgba(255,92,108,.12);border-color:rgba(255,92,108,.32)}
|
|
137
|
-
.sev.urgent{color:#FBC56B;background:rgba(242,181,61,.12);border-color:rgba(242,181,61,.32)}
|
|
138
|
-
.sev.ok{color:var(--muted);border-color:var(--line);background:transparent}
|
|
139
|
-
|
|
140
|
-
.iconbtn{font-family:var(--mono);background:var(--raise);border:1px solid var(--line);color:var(--muted);
|
|
141
|
-
border-radius:6px;padding:4px 9px;cursor:pointer;font-size:11.5px;transition:.15s}
|
|
142
|
-
.iconbtn:hover{color:#fff;border-color:var(--azure)}
|
|
143
|
-
.empty{padding:48px 24px;text-align:center;color:var(--muted)}
|
|
144
|
-
.scroll{max-height:calc(100vh - 380px);overflow:auto}
|
|
145
|
-
.scroll::-webkit-scrollbar{width:10px;height:10px}
|
|
146
|
-
.scroll::-webkit-scrollbar-thumb{background:var(--line);border-radius:8px;border:2px solid var(--panel)}
|
|
147
|
-
.tablenote{font-family:var(--mono);font-size:11px;color:var(--faint);margin-top:10px}
|
|
148
|
-
|
|
149
|
-
/* ---------- signature: expiry horizon ---------- */
|
|
150
|
-
.horizon{padding:18px 20px;margin-bottom:22px}
|
|
151
|
-
.hz-head{display:flex;justify-content:space-between;align-items:baseline;margin-bottom:14px}
|
|
152
|
-
.hz-body{display:flex;gap:12px;height:104px}
|
|
153
|
-
.hz-expired{position:relative;width:118px;flex:none;border:1px solid rgba(255,92,108,.28);
|
|
154
|
-
background:linear-gradient(180deg,rgba(255,92,108,.12),rgba(255,92,108,.02));
|
|
155
|
-
border-radius:10px;display:flex;flex-direction:column;align-items:center;justify-content:center;overflow:hidden}
|
|
156
|
-
.hz-expired-n{font-family:var(--mono);font-size:30px;font-weight:600;color:#FF8893;line-height:1}
|
|
157
|
-
.hz-expired-l{font-family:var(--mono);font-size:9.5px;letter-spacing:.18em;color:#FF8893;margin-top:3px}
|
|
158
|
-
.hz-axis{position:relative;flex:1;border:1px solid var(--line);border-radius:10px;
|
|
159
|
-
background:linear-gradient(180deg,rgba(255,255,255,.015),transparent);overflow:hidden}
|
|
160
|
-
.hz-grid{position:absolute;top:0;bottom:0;border-left:1px dashed var(--line);}
|
|
161
|
-
.hz-grid:first-of-type{border-left-color:rgba(91,157,255,.35)}
|
|
162
|
-
.hz-grid span{position:absolute;bottom:7px;left:6px;font-family:var(--mono);font-size:9.5px;color:var(--faint);white-space:nowrap}
|
|
163
|
-
.hz-tick{position:absolute;top:12px;bottom:26px;width:2px;border-radius:2px;background:var(--ok);
|
|
164
|
-
transform:translateX(-1px);opacity:.9}
|
|
165
|
-
.hz-tick.warn{background:var(--warn)} .hz-tick.alert{background:var(--alert)}
|
|
166
|
-
.hz-tick:hover{opacity:1;width:3px;box-shadow:0 0 8px currentColor}
|
|
167
|
-
.hz-expired .hz-tick{top:auto;bottom:9px;height:16px;opacity:.7}
|
|
168
|
-
.hz-legend{display:flex;gap:16px;align-items:center}
|
|
169
|
-
.hz-legend span{font-family:var(--mono);font-size:10px;color:var(--muted);display:flex;align-items:center;gap:5px}
|
|
170
|
-
.hz-legend i{width:8px;height:8px;border-radius:2px;display:inline-block}
|
|
171
|
-
|
|
172
|
-
/* ---------- segmented + window ---------- */
|
|
173
|
-
.seg{display:inline-flex;gap:4px;background:var(--panel2);border:1px solid var(--line);
|
|
174
|
-
border-radius:var(--r-sm);padding:3px}
|
|
175
|
-
.seg button{font-family:var(--mono);background:transparent;border:0;color:var(--muted);padding:6px 12px;
|
|
176
|
-
border-radius:6px;cursor:pointer;font-size:12px;transition:.15s}
|
|
177
|
-
.seg button:hover{color:var(--text)}
|
|
178
|
-
.seg button.active{background:var(--raise);color:#fff;box-shadow:0 1px 0 rgba(0,0,0,.4)}
|
|
179
|
-
|
|
180
|
-
/* ---------- drawer ---------- */
|
|
181
|
-
.overlay{position:fixed;inset:0;background:rgba(5,8,14,.6);backdrop-filter:blur(2px);opacity:0;
|
|
182
|
-
pointer-events:none;transition:.25s;z-index:30}
|
|
183
|
-
.overlay.show{opacity:1;pointer-events:auto}
|
|
184
|
-
.drawer{position:fixed;top:0;right:0;height:100vh;width:100vw;max-width:100vw;background:var(--panel);
|
|
185
|
-
border-left:1px solid var(--line);box-shadow:-30px 0 80px -30px rgba(0,0,0,.9);
|
|
186
|
-
transform:translateX(100%);transition:transform .28s cubic-bezier(.2,.7,.2,1);z-index:40;
|
|
187
|
-
display:flex;flex-direction:column}
|
|
188
|
-
.drawer.open{transform:translateX(0)}
|
|
189
|
-
.drawer header{padding:20px 22px;border-bottom:1px solid var(--line);display:flex;
|
|
190
|
-
justify-content:space-between;align-items:flex-start;gap:14px}
|
|
191
|
-
.drawer header .dt{font-family:var(--mono);font-size:16px;font-weight:600;word-break:break-all}
|
|
192
|
-
.drawer .body{padding:18px 22px;overflow:auto}
|
|
193
|
-
.kvitem{border-bottom:1px solid var(--line2)}
|
|
194
|
-
.kvitem:last-child{border-bottom:0}
|
|
195
|
-
.kvitem.sel{background:rgba(91,157,255,.06);margin:0 -22px;padding:0 22px}
|
|
196
|
-
.kvline{display:flex;align-items:center;gap:10px;padding:10px 0}
|
|
197
|
-
.kvline .nm{flex:1 1 auto;min-width:60px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font-family:var(--mono);font-size:12.5px}
|
|
198
|
-
.kvline .meta{display:flex;align-items:center;gap:8px;flex:0 0 auto}
|
|
199
|
-
.kvline input[type=checkbox]{flex:0 0 auto}
|
|
200
|
-
.revealed{padding:0 0 12px;display:flex;align-items:flex-start;gap:8px}
|
|
201
|
-
.revealed code.val{display:block;flex:1;min-width:0;background:var(--ink);border:1px solid var(--line);
|
|
202
|
-
border-radius:6px;padding:8px 10px;max-height:120px;overflow:auto}
|
|
203
|
-
.cmd{background:var(--ink);border:1px solid var(--line);border-radius:var(--r-sm);padding:10px 12px;
|
|
204
|
-
margin:8px 0;font-family:var(--mono);font-size:11.5px;color:#A9D2FF;display:flex;justify-content:space-between;gap:10px;align-items:center}
|
|
205
|
-
.secthead{font-family:var(--mono);margin:24px 0 6px;color:var(--faint);font-size:10px;text-transform:uppercase;letter-spacing:.16em}
|
|
206
|
-
.loadval{font-family:var(--mono);font-size:11px;color:var(--azure);cursor:pointer;background:none;border:0;text-decoration:underline}
|
|
207
|
-
code.val{font-family:var(--mono);user-select:all;color:#A9D2FF;font-size:11.5px;word-break:break-all}
|
|
208
|
-
/* drawer edit + batch */
|
|
209
|
-
.dtools{display:flex;align-items:center;gap:12px;margin-top:14px}
|
|
210
|
-
.dtools .search{min-width:0;flex:1;padding:8px 11px}
|
|
211
|
-
.selall{font-family:var(--mono);font-size:11px;color:var(--muted);display:flex;align-items:center;gap:6px;cursor:pointer;white-space:nowrap}
|
|
212
|
-
input[type=checkbox]{accent-color:var(--azure);width:14px;height:14px;cursor:pointer}
|
|
213
|
-
.batchbar{display:flex;align-items:center;gap:8px;flex-wrap:wrap;margin-top:12px;padding:10px 12px;
|
|
214
|
-
background:var(--azuredim);border:1px solid rgba(91,157,255,.4);border-radius:var(--r-sm)}
|
|
215
|
-
.batchbar .bn{font-family:var(--mono);font-size:12px;color:#cfe2ff;margin-right:2px}
|
|
216
|
-
.batchbar input[type=date]{font-family:var(--mono);font-size:12px;background:var(--ink);border:1px solid var(--line);
|
|
217
|
-
color:var(--text);border-radius:6px;padding:5px 8px;color-scheme:dark}
|
|
218
|
-
.editor{margin:0 -22px;padding:14px 22px;background:var(--panel2);border-top:1px solid var(--line2)}
|
|
219
|
-
/* create panel */
|
|
220
|
-
.createp{margin-top:12px;padding:16px;background:var(--panel2);border:1px solid var(--azure);border-radius:var(--r-sm)}
|
|
221
|
-
.createp h4{margin:0 0 12px;font-family:var(--mono);font-size:12px;color:#cfe2ff;font-weight:500;letter-spacing:.04em}
|
|
222
|
-
.createp .fld{display:flex;align-items:center;gap:10px;margin-bottom:10px}
|
|
223
|
-
.createp label{font-family:var(--mono);font-size:11px;color:var(--muted);width:72px;flex:none}
|
|
224
|
-
.createp input[type=text],.createp input[type=date],.createp textarea{font-family:var(--mono);font-size:12px;
|
|
225
|
-
background:var(--ink);border:1px solid var(--line);color:var(--text);border-radius:6px;padding:8px 10px;flex:1;width:100%;color-scheme:dark}
|
|
226
|
-
.createp textarea{min-height:120px;resize:vertical;line-height:1.6}
|
|
227
|
-
.createp .actions{display:flex;gap:8px;align-items:center;margin-top:6px}
|
|
228
|
-
.newbtns{display:flex;gap:6px;margin-left:auto}
|
|
229
|
-
.editor .fld{display:flex;align-items:center;gap:10px;margin-bottom:10px}
|
|
230
|
-
.editor label{font-family:var(--mono);font-size:11px;color:var(--muted);width:78px;flex:none}
|
|
231
|
-
.editor input[type=text],.editor input[type=date]{font-family:var(--mono);font-size:12px;background:var(--ink);
|
|
232
|
-
border:1px solid var(--line);color:var(--text);border-radius:6px;padding:7px 9px;flex:1;color-scheme:dark}
|
|
233
|
-
.editor input[type=date],.createp input[type=date]{flex:0 0 auto;width:200px}
|
|
234
|
-
.editor .hint{font-family:var(--mono);font-size:10.5px;color:var(--faint)}
|
|
235
|
-
.toggle{display:flex;align-items:center;gap:7px;font-family:var(--mono);font-size:12px;color:var(--text);cursor:pointer}
|
|
236
|
-
|
|
237
|
-
/* ---------- misc ---------- */
|
|
238
|
-
.toast{position:fixed;bottom:24px;left:50%;transform:translateX(-50%) translateY(20px);
|
|
239
|
-
background:var(--raise);border:1px solid var(--azure);color:#fff;padding:10px 18px;
|
|
240
|
-
border-radius:var(--r-sm);opacity:0;transition:.25s;z-index:60;font-size:13px}
|
|
241
|
-
.toast.show{opacity:1;transform:translateX(-50%) translateY(0)}
|
|
242
|
-
.banner{background:linear-gradient(180deg,rgba(255,92,108,.14),rgba(255,92,108,.04));
|
|
243
|
-
border:1px solid rgba(255,92,108,.4);color:#FFC4C9;padding:13px 16px;border-radius:var(--r);
|
|
244
|
-
margin-bottom:18px;display:none;font-size:13px}
|
|
245
|
-
.banner.show{display:block}
|
|
246
|
-
.banner code{font-family:var(--mono);background:rgba(0,0,0,.3);padding:1px 6px;border-radius:4px;color:#fff}
|
|
247
|
-
.spin{width:18px;height:18px;border:2px solid var(--line);border-top-color:var(--azure);border-radius:50%;
|
|
248
|
-
display:inline-block;animation:spin .7s linear infinite;vertical-align:-4px}
|
|
249
|
-
@keyframes spin{to{transform:rotate(360deg)}}
|
|
250
|
-
.progress{width:64%;max-width:380px;height:6px;background:var(--ink);border:1px solid var(--line);
|
|
251
|
-
border-radius:999px;margin:16px auto 0;overflow:hidden}
|
|
252
|
-
.progress .bar{height:100%;background:linear-gradient(90deg,var(--azure),var(--ok));border-radius:999px;transition:width .4s}
|
|
253
|
-
|
|
254
|
-
@media (max-width:900px){
|
|
255
|
-
.app{grid-template-columns:1fr}
|
|
256
|
-
aside{position:static;height:auto;flex-direction:row;flex-wrap:wrap;align-items:center}
|
|
257
|
-
aside nav{display:flex;gap:4px;flex-wrap:wrap} .navlabel{display:none} .who{display:none}
|
|
258
|
-
main{padding:20px}
|
|
259
|
-
input.search{min-width:180px}
|
|
260
|
-
}
|
|
261
|
-
@media (prefers-reduced-motion:reduce){*{animation-duration:.01ms!important;transition:none!important}}
|
|
262
|
-
</style>
|
|
10
|
+
<script src="https://cdn.tailwindcss.com"></script>
|
|
11
|
+
<script>
|
|
12
|
+
// Tailwind Play CDN, themed to the design tokens. Preflight is OFF so the
|
|
13
|
+
// hand-authored component layer in styles.css stays authoritative.
|
|
14
|
+
tailwind.config = {
|
|
15
|
+
corePlugins: { preflight: false },
|
|
16
|
+
theme: {
|
|
17
|
+
extend: {
|
|
18
|
+
colors: {
|
|
19
|
+
ink: '#0A0D14', 'ink-2': '#0C1019',
|
|
20
|
+
panel: '#121826', 'panel-2': '#0E141F', raise: '#171F2E',
|
|
21
|
+
line: '#222C3E', 'line-2': '#1A2233',
|
|
22
|
+
text: '#E6EBF2', muted: '#79859B', faint: '#4C586E',
|
|
23
|
+
azure: '#5B9DFF', azure2: '#3F86F0',
|
|
24
|
+
ok: '#46D6B4', warn: '#F2B53D', alert: '#FF5C6C', violet: '#A98BFF',
|
|
25
|
+
},
|
|
26
|
+
fontFamily: {
|
|
27
|
+
sans: ['"IBM Plex Sans"', 'system-ui', 'sans-serif'],
|
|
28
|
+
mono: ['"IBM Plex Mono"', 'ui-monospace', 'monospace'],
|
|
29
|
+
},
|
|
30
|
+
},
|
|
31
|
+
},
|
|
32
|
+
};
|
|
33
|
+
</script>
|
|
34
|
+
<link rel="stylesheet" href="/styles.css">
|
|
263
35
|
</head>
|
|
264
36
|
<body>
|
|
265
|
-
<div class="
|
|
37
|
+
<div class="grid min-h-screen grid-cols-[236px_1fr] max-[900px]:grid-cols-1">
|
|
266
38
|
<aside>
|
|
267
39
|
<div class="brand">
|
|
268
40
|
<svg class="mark" viewBox="0 0 34 34" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
@@ -292,12 +64,13 @@
|
|
|
292
64
|
Expiry</button>
|
|
293
65
|
</nav>
|
|
294
66
|
<div class="who" id="who"><span class="spin"></span> connecting…</div>
|
|
67
|
+
<div class="ver mono faint" id="ver"></div>
|
|
295
68
|
</aside>
|
|
296
69
|
|
|
297
|
-
<main>
|
|
70
|
+
<main class="max-w-[1560px] px-8 pt-[26px] pb-[60px] max-[900px]:p-5">
|
|
298
71
|
<div class="banner" id="banner"></div>
|
|
299
72
|
|
|
300
|
-
<div class="
|
|
73
|
+
<div class="flex items-end justify-between gap-[18px] mb-[26px]">
|
|
301
74
|
<div>
|
|
302
75
|
<div class="eyebrow" id="eyebrow">Overview</div>
|
|
303
76
|
<h1 id="title">Overview</h1>
|
|
@@ -442,10 +215,14 @@ function subName(id){ return (SUBMETA[id]&&SUBMETA[id].name)||id; }
|
|
|
442
215
|
async function boot(){
|
|
443
216
|
const cfg = await api('/api/config'); if(cfg.ok){ CFG = {...CFG, ...cfg.data};
|
|
444
217
|
if(CFG.title){ document.title = CFG.title; const wm=document.querySelector('.brand .wm'); if(wm) wm.firstChild.textContent = CFG.title; } }
|
|
218
|
+
$('#ver').textContent = 'akv-manager v' + (CFG.version || '?');
|
|
445
219
|
const acc = await api('/api/account');
|
|
446
220
|
if(!acc.ok){
|
|
447
|
-
$('#who').innerHTML = '<span class="dot" style="background:var(--alert)"></span> not
|
|
448
|
-
|
|
221
|
+
$('#who').innerHTML = '<span class="dot" style="background:var(--alert)"></span> not connected';
|
|
222
|
+
const detail = acc.error ? `<div class="hint" style="margin-top:6px">${acc.error}</div>` : '';
|
|
223
|
+
banner((acc.needLogin
|
|
224
|
+
? 'Azure CLI is signed out. Run <code>az login</code>, then press Refresh.'
|
|
225
|
+
: 'Could not reach the Azure CLI. Make sure <code>az</code> is installed and on your PATH (on Windows it is <code>az.cmd</code>), then press Refresh.') + detail);
|
|
449
226
|
return;
|
|
450
227
|
}
|
|
451
228
|
STATE.account = acc.data;
|
|
@@ -0,0 +1,249 @@
|
|
|
1
|
+
:root{
|
|
2
|
+
--ink:#0A0D14; --ink2:#0C1019;
|
|
3
|
+
--panel:#121826; --panel2:#0E141F; --raise:#171F2E;
|
|
4
|
+
--line:#222C3E; --line2:#1A2233;
|
|
5
|
+
--text:#E6EBF2; --muted:#79859B; --faint:#4C586E;
|
|
6
|
+
--azure:#5B9DFF; --azure2:#3F86F0; --azuredim:rgba(91,157,255,.12);
|
|
7
|
+
--ok:#46D6B4; --warn:#F2B53D; --alert:#FF5C6C; --bad:#FF5C6C; --good:#46D6B4;
|
|
8
|
+
--violet:#A98BFF;
|
|
9
|
+
--r:12px; --r-sm:8px;
|
|
10
|
+
--shadow:0 18px 40px -22px rgba(0,0,0,.8);
|
|
11
|
+
--mono:'IBM Plex Mono',ui-monospace,SFMono-Regular,Menlo,monospace;
|
|
12
|
+
--sans:'IBM Plex Sans',-apple-system,BlinkMacSystemFont,sans-serif;
|
|
13
|
+
}
|
|
14
|
+
*{box-sizing:border-box}
|
|
15
|
+
html,body{margin:0}
|
|
16
|
+
body{font-family:var(--sans);font-size:14px;line-height:1.5;color:var(--text);min-height:100vh;
|
|
17
|
+
background:
|
|
18
|
+
radial-gradient(900px 500px at 88% -8%,rgba(91,157,255,.07),transparent 60%),
|
|
19
|
+
linear-gradient(180deg,var(--ink2),var(--ink));
|
|
20
|
+
-webkit-font-smoothing:antialiased}
|
|
21
|
+
::selection{background:rgba(91,157,255,.32)}
|
|
22
|
+
a{color:var(--azure);text-decoration:none}
|
|
23
|
+
:focus-visible{outline:2px solid var(--azure);outline-offset:2px;border-radius:4px}
|
|
24
|
+
.mono{font-family:var(--mono);font-variant-numeric:tabular-nums}
|
|
25
|
+
.muted{color:var(--muted)} .faint{color:var(--faint)} .right{text-align:right}
|
|
26
|
+
.hide{display:none!important}
|
|
27
|
+
.eyebrow{font-family:var(--mono);font-size:10.5px;font-weight:500;letter-spacing:.18em;
|
|
28
|
+
text-transform:uppercase;color:var(--faint)}
|
|
29
|
+
|
|
30
|
+
/* page skeleton (.app grid, main, .topbar) now uses Tailwind utilities in index.html */
|
|
31
|
+
|
|
32
|
+
/* ---------- sidebar ---------- */
|
|
33
|
+
aside{background:linear-gradient(180deg,rgba(255,255,255,.015),transparent),var(--panel2);
|
|
34
|
+
border-right:1px solid var(--line);padding:22px 14px;display:flex;flex-direction:column;
|
|
35
|
+
position:sticky;top:0;height:100vh}
|
|
36
|
+
.brand{display:flex;align-items:center;gap:11px;padding:0 6px 20px}
|
|
37
|
+
.brand .mark{width:34px;height:34px;flex:none}
|
|
38
|
+
.brand .wm{font-family:var(--mono);font-weight:600;font-size:13px;letter-spacing:.14em;color:var(--text);line-height:1.15}
|
|
39
|
+
.brand .wm small{display:block;font-size:9.5px;letter-spacing:.22em;color:var(--faint);font-weight:400;margin-top:2px}
|
|
40
|
+
.navlabel{font-family:var(--mono);font-size:9.5px;letter-spacing:.2em;color:var(--faint);
|
|
41
|
+
text-transform:uppercase;padding:14px 10px 7px}
|
|
42
|
+
nav button{display:flex;align-items:center;gap:11px;width:100%;text-align:left;background:transparent;
|
|
43
|
+
border:0;color:var(--muted);padding:9px 11px;border-radius:var(--r-sm);cursor:pointer;
|
|
44
|
+
font-family:var(--sans);font-size:13.5px;font-weight:500;position:relative;transition:.15s}
|
|
45
|
+
nav button svg{width:17px;height:17px;flex:none;opacity:.8}
|
|
46
|
+
nav button:hover{background:rgba(255,255,255,.03);color:var(--text)}
|
|
47
|
+
nav button.active{background:var(--azuredim);color:#fff}
|
|
48
|
+
nav button.active svg{opacity:1;color:var(--azure)}
|
|
49
|
+
nav button.active::before{content:"";position:absolute;left:-14px;top:7px;bottom:7px;width:3px;
|
|
50
|
+
background:var(--azure);border-radius:0 3px 3px 0}
|
|
51
|
+
.who{margin-top:auto;font-size:12px;color:var(--muted);border-top:1px solid var(--line2);
|
|
52
|
+
padding:14px 8px 4px;line-height:1.6}
|
|
53
|
+
.who b{color:var(--text);font-weight:600}
|
|
54
|
+
.who .mono{font-size:11px}
|
|
55
|
+
.dot{width:7px;height:7px;border-radius:50%;display:inline-block;vertical-align:middle}
|
|
56
|
+
.ver{font-size:10px;letter-spacing:.04em;padding:8px 8px 2px}
|
|
57
|
+
|
|
58
|
+
/* ---------- main ---------- */
|
|
59
|
+
h1{font-size:23px;font-weight:600;margin:4px 0 0;letter-spacing:-.01em}
|
|
60
|
+
.sub{color:var(--muted);font-size:13px;margin-top:3px;max-width:62ch}
|
|
61
|
+
.row{display:flex;gap:10px;align-items:center;flex-wrap:wrap}
|
|
62
|
+
|
|
63
|
+
.btn{font-family:var(--sans);font-weight:500;font-size:13px;border:0;border-radius:var(--r-sm);
|
|
64
|
+
padding:9px 15px;cursor:pointer;color:#06101f;background:var(--azure);transition:.15s}
|
|
65
|
+
.btn:hover{background:#75AEFF}
|
|
66
|
+
.btn:disabled{opacity:.55;cursor:default}
|
|
67
|
+
.btn.ghost{background:var(--raise);color:var(--text);border:1px solid var(--line)}
|
|
68
|
+
.btn.ghost:hover{border-color:var(--azure);background:var(--raise)}
|
|
69
|
+
.btn.sm{padding:5px 11px;font-size:12px}
|
|
70
|
+
input.search{font-family:var(--mono);background:var(--panel2);border:1px solid var(--line);color:var(--text);
|
|
71
|
+
padding:9px 13px;border-radius:var(--r-sm);min-width:320px;font-size:12.5px}
|
|
72
|
+
input.search::placeholder{color:var(--faint)}
|
|
73
|
+
input.search:focus{outline:none;border-color:var(--azure);background:var(--panel)}
|
|
74
|
+
|
|
75
|
+
/* ---------- KPI cards ---------- */
|
|
76
|
+
.cards{display:grid;grid-template-columns:repeat(auto-fill,minmax(190px,1fr));gap:13px;margin-bottom:24px}
|
|
77
|
+
.card{background:linear-gradient(180deg,var(--panel),var(--panel2));border:1px solid var(--line);
|
|
78
|
+
border-radius:var(--r);padding:15px 17px;position:relative;overflow:hidden}
|
|
79
|
+
.card::after{content:"";position:absolute;inset:0 0 auto;height:2px;background:var(--line)}
|
|
80
|
+
.card .k{font-family:var(--mono);color:var(--muted);font-size:10.5px;text-transform:uppercase;letter-spacing:.13em}
|
|
81
|
+
.card .v{font-family:var(--mono);font-size:30px;font-weight:600;margin-top:9px;line-height:1;letter-spacing:-.02em}
|
|
82
|
+
.card .v small{font-family:var(--sans);font-size:12px;font-weight:400;color:var(--muted);letter-spacing:0}
|
|
83
|
+
.card--alert::after{background:var(--alert)} .card--alert .v{color:#FF8893}
|
|
84
|
+
.card--warn::after{background:var(--warn)} .card--warn .v{color:#FBC56B}
|
|
85
|
+
.card--azure::after{background:var(--azure)} .card--azure .v{color:#8FBEFF}
|
|
86
|
+
|
|
87
|
+
/* ---------- chips ---------- */
|
|
88
|
+
.chips{display:flex;gap:8px;flex-wrap:wrap;margin-bottom:16px}
|
|
89
|
+
.chip{font-family:var(--mono);background:var(--panel2);border:1px solid var(--line);color:var(--muted);
|
|
90
|
+
padding:6px 12px;border-radius:7px;cursor:pointer;font-size:11.5px;display:flex;gap:8px;align-items:center;transition:.15s}
|
|
91
|
+
.chip:hover{color:var(--text);border-color:var(--line)}
|
|
92
|
+
.chip.active{border-color:var(--azure);color:#fff;background:var(--azuredim)}
|
|
93
|
+
.chip .n{background:var(--ink);border-radius:5px;padding:1px 7px;font-size:10.5px;color:var(--muted)}
|
|
94
|
+
.chip.active .n{color:var(--azure)}
|
|
95
|
+
|
|
96
|
+
/* ---------- panel + table ---------- */
|
|
97
|
+
.panel{background:var(--panel);border:1px solid var(--line);border-radius:var(--r);overflow:hidden;box-shadow:var(--shadow)}
|
|
98
|
+
table{width:100%;border-collapse:collapse;font-size:13px}
|
|
99
|
+
th,td{padding:11px 16px;text-align:left;border-bottom:1px solid var(--line2);white-space:nowrap}
|
|
100
|
+
thead th{position:sticky;top:0;z-index:2;background:var(--panel2);color:var(--muted);font-weight:500;
|
|
101
|
+
font-family:var(--mono);font-size:10.5px;text-transform:uppercase;letter-spacing:.1em;
|
|
102
|
+
cursor:pointer;user-select:none}
|
|
103
|
+
thead th:hover{color:var(--text)}
|
|
104
|
+
tbody tr{transition:background .12s}
|
|
105
|
+
tbody tr:hover{background:rgba(91,157,255,.05)}
|
|
106
|
+
tbody tr:last-child td{border-bottom:0}
|
|
107
|
+
td.mono,.mono{font-family:var(--mono);font-size:12.5px}
|
|
108
|
+
td b{font-weight:600}
|
|
109
|
+
|
|
110
|
+
.tag{display:inline-flex;align-items:center;font-family:var(--mono);font-size:10px;font-weight:500;
|
|
111
|
+
letter-spacing:.08em;text-transform:uppercase;padding:2px 7px;border-radius:5px;border:1px solid transparent}
|
|
112
|
+
.tag.e-dev{color:#7FE0C8;background:rgba(70,214,180,.10);border-color:rgba(70,214,180,.22)}
|
|
113
|
+
.tag.e-test{color:#7FD0E0;background:rgba(70,180,214,.10);border-color:rgba(70,180,214,.22)}
|
|
114
|
+
.tag.e-qa{color:#93C0FF;background:rgba(91,157,255,.12);border-color:rgba(91,157,255,.24)}
|
|
115
|
+
.tag.e-stage{color:#F4C977;background:rgba(242,181,61,.10);border-color:rgba(242,181,61,.22)}
|
|
116
|
+
.tag.e-prod{color:#FF9BA4;background:rgba(255,92,108,.10);border-color:rgba(255,92,108,.26)}
|
|
117
|
+
.tag.e-sandbox{color:#BCA8FF;background:rgba(169,139,255,.10);border-color:rgba(169,139,255,.22)}
|
|
118
|
+
.tag.e-other{color:var(--muted);background:rgba(255,255,255,.04);border-color:var(--line)}
|
|
119
|
+
|
|
120
|
+
.pill{font-family:var(--mono);font-size:10px;letter-spacing:.06em;color:var(--muted);
|
|
121
|
+
border:1px solid var(--line);border-radius:5px;padding:1px 7px;white-space:nowrap}
|
|
122
|
+
.pill.rbac{color:#7FE0C8;border-color:rgba(70,214,180,.3)}
|
|
123
|
+
.pill.policy{color:#F4C977;border-color:rgba(242,181,61,.3)}
|
|
124
|
+
.sev{font-family:var(--mono);font-size:10px;letter-spacing:.1em;padding:2px 8px;border-radius:5px;border:1px solid}
|
|
125
|
+
.sev.expired{color:#FF8893;background:rgba(255,92,108,.12);border-color:rgba(255,92,108,.32)}
|
|
126
|
+
.sev.urgent{color:#FBC56B;background:rgba(242,181,61,.12);border-color:rgba(242,181,61,.32)}
|
|
127
|
+
.sev.ok{color:var(--muted);border-color:var(--line);background:transparent}
|
|
128
|
+
|
|
129
|
+
.iconbtn{font-family:var(--mono);background:var(--raise);border:1px solid var(--line);color:var(--muted);
|
|
130
|
+
border-radius:6px;padding:4px 9px;cursor:pointer;font-size:11.5px;transition:.15s}
|
|
131
|
+
.iconbtn:hover{color:#fff;border-color:var(--azure)}
|
|
132
|
+
.empty{padding:48px 24px;text-align:center;color:var(--muted)}
|
|
133
|
+
.scroll{max-height:calc(100vh - 380px);overflow:auto}
|
|
134
|
+
.scroll::-webkit-scrollbar{width:10px;height:10px}
|
|
135
|
+
.scroll::-webkit-scrollbar-thumb{background:var(--line);border-radius:8px;border:2px solid var(--panel)}
|
|
136
|
+
.tablenote{font-family:var(--mono);font-size:11px;color:var(--faint);margin-top:10px}
|
|
137
|
+
|
|
138
|
+
/* ---------- signature: expiry horizon ---------- */
|
|
139
|
+
.horizon{padding:18px 20px;margin-bottom:22px}
|
|
140
|
+
.hz-head{display:flex;justify-content:space-between;align-items:baseline;margin-bottom:14px}
|
|
141
|
+
.hz-body{display:flex;gap:12px;height:104px}
|
|
142
|
+
.hz-expired{position:relative;width:118px;flex:none;border:1px solid rgba(255,92,108,.28);
|
|
143
|
+
background:linear-gradient(180deg,rgba(255,92,108,.12),rgba(255,92,108,.02));
|
|
144
|
+
border-radius:10px;display:flex;flex-direction:column;align-items:center;justify-content:center;overflow:hidden}
|
|
145
|
+
.hz-expired-n{font-family:var(--mono);font-size:30px;font-weight:600;color:#FF8893;line-height:1}
|
|
146
|
+
.hz-expired-l{font-family:var(--mono);font-size:9.5px;letter-spacing:.18em;color:#FF8893;margin-top:3px}
|
|
147
|
+
.hz-axis{position:relative;flex:1;border:1px solid var(--line);border-radius:10px;
|
|
148
|
+
background:linear-gradient(180deg,rgba(255,255,255,.015),transparent);overflow:hidden}
|
|
149
|
+
.hz-grid{position:absolute;top:0;bottom:0;border-left:1px dashed var(--line);}
|
|
150
|
+
.hz-grid:first-of-type{border-left-color:rgba(91,157,255,.35)}
|
|
151
|
+
.hz-grid span{position:absolute;bottom:7px;left:6px;font-family:var(--mono);font-size:9.5px;color:var(--faint);white-space:nowrap}
|
|
152
|
+
.hz-tick{position:absolute;top:12px;bottom:26px;width:2px;border-radius:2px;background:var(--ok);
|
|
153
|
+
transform:translateX(-1px);opacity:.9}
|
|
154
|
+
.hz-tick.warn{background:var(--warn)} .hz-tick.alert{background:var(--alert)}
|
|
155
|
+
.hz-tick:hover{opacity:1;width:3px;box-shadow:0 0 8px currentColor}
|
|
156
|
+
.hz-expired .hz-tick{top:auto;bottom:9px;height:16px;opacity:.7}
|
|
157
|
+
.hz-legend{display:flex;gap:16px;align-items:center}
|
|
158
|
+
.hz-legend span{font-family:var(--mono);font-size:10px;color:var(--muted);display:flex;align-items:center;gap:5px}
|
|
159
|
+
.hz-legend i{width:8px;height:8px;border-radius:2px;display:inline-block}
|
|
160
|
+
|
|
161
|
+
/* ---------- segmented + window ---------- */
|
|
162
|
+
.seg{display:inline-flex;gap:4px;background:var(--panel2);border:1px solid var(--line);
|
|
163
|
+
border-radius:var(--r-sm);padding:3px}
|
|
164
|
+
.seg button{font-family:var(--mono);background:transparent;border:0;color:var(--muted);padding:6px 12px;
|
|
165
|
+
border-radius:6px;cursor:pointer;font-size:12px;transition:.15s}
|
|
166
|
+
.seg button:hover{color:var(--text)}
|
|
167
|
+
.seg button.active{background:var(--raise);color:#fff;box-shadow:0 1px 0 rgba(0,0,0,.4)}
|
|
168
|
+
|
|
169
|
+
/* ---------- drawer ---------- */
|
|
170
|
+
.overlay{position:fixed;inset:0;background:rgba(5,8,14,.6);backdrop-filter:blur(2px);opacity:0;
|
|
171
|
+
pointer-events:none;transition:.25s;z-index:30}
|
|
172
|
+
.overlay.show{opacity:1;pointer-events:auto}
|
|
173
|
+
.drawer{position:fixed;top:0;right:0;height:100vh;width:100vw;max-width:100vw;background:var(--panel);
|
|
174
|
+
border-left:1px solid var(--line);box-shadow:-30px 0 80px -30px rgba(0,0,0,.9);
|
|
175
|
+
transform:translateX(100%);transition:transform .28s cubic-bezier(.2,.7,.2,1);z-index:40;
|
|
176
|
+
display:flex;flex-direction:column}
|
|
177
|
+
.drawer.open{transform:translateX(0)}
|
|
178
|
+
.drawer header{padding:20px 22px;border-bottom:1px solid var(--line);display:flex;
|
|
179
|
+
justify-content:space-between;align-items:flex-start;gap:14px}
|
|
180
|
+
.drawer header .dt{font-family:var(--mono);font-size:16px;font-weight:600;word-break:break-all}
|
|
181
|
+
.drawer .body{padding:18px 22px;overflow:auto}
|
|
182
|
+
.kvitem{border-bottom:1px solid var(--line2)}
|
|
183
|
+
.kvitem:last-child{border-bottom:0}
|
|
184
|
+
.kvitem.sel{background:rgba(91,157,255,.06);margin:0 -22px;padding:0 22px}
|
|
185
|
+
.kvline{display:flex;align-items:center;gap:10px;padding:10px 0}
|
|
186
|
+
.kvline .nm{flex:1 1 auto;min-width:60px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font-family:var(--mono);font-size:12.5px}
|
|
187
|
+
.kvline .meta{display:flex;align-items:center;gap:8px;flex:0 0 auto}
|
|
188
|
+
.kvline input[type=checkbox]{flex:0 0 auto}
|
|
189
|
+
.revealed{padding:0 0 12px;display:flex;align-items:flex-start;gap:8px}
|
|
190
|
+
.revealed code.val{display:block;flex:1;min-width:0;background:var(--ink);border:1px solid var(--line);
|
|
191
|
+
border-radius:6px;padding:8px 10px;max-height:120px;overflow:auto}
|
|
192
|
+
.cmd{background:var(--ink);border:1px solid var(--line);border-radius:var(--r-sm);padding:10px 12px;
|
|
193
|
+
margin:8px 0;font-family:var(--mono);font-size:11.5px;color:#A9D2FF;display:flex;justify-content:space-between;gap:10px;align-items:center}
|
|
194
|
+
.secthead{font-family:var(--mono);margin:24px 0 6px;color:var(--faint);font-size:10px;text-transform:uppercase;letter-spacing:.16em}
|
|
195
|
+
.loadval{font-family:var(--mono);font-size:11px;color:var(--azure);cursor:pointer;background:none;border:0;text-decoration:underline}
|
|
196
|
+
code.val{font-family:var(--mono);user-select:all;color:#A9D2FF;font-size:11.5px;word-break:break-all}
|
|
197
|
+
/* drawer edit + batch */
|
|
198
|
+
.dtools{display:flex;align-items:center;gap:12px;margin-top:14px}
|
|
199
|
+
.dtools .search{min-width:0;flex:1;padding:8px 11px}
|
|
200
|
+
.selall{font-family:var(--mono);font-size:11px;color:var(--muted);display:flex;align-items:center;gap:6px;cursor:pointer;white-space:nowrap}
|
|
201
|
+
input[type=checkbox]{accent-color:var(--azure);width:14px;height:14px;cursor:pointer}
|
|
202
|
+
.batchbar{display:flex;align-items:center;gap:8px;flex-wrap:wrap;margin-top:12px;padding:10px 12px;
|
|
203
|
+
background:var(--azuredim);border:1px solid rgba(91,157,255,.4);border-radius:var(--r-sm)}
|
|
204
|
+
.batchbar .bn{font-family:var(--mono);font-size:12px;color:#cfe2ff;margin-right:2px}
|
|
205
|
+
.batchbar input[type=date]{font-family:var(--mono);font-size:12px;background:var(--ink);border:1px solid var(--line);
|
|
206
|
+
color:var(--text);border-radius:6px;padding:5px 8px;color-scheme:dark}
|
|
207
|
+
.editor{margin:0 -22px;padding:14px 22px;background:var(--panel2);border-top:1px solid var(--line2)}
|
|
208
|
+
/* create panel */
|
|
209
|
+
.createp{margin-top:12px;padding:16px;background:var(--panel2);border:1px solid var(--azure);border-radius:var(--r-sm)}
|
|
210
|
+
.createp h4{margin:0 0 12px;font-family:var(--mono);font-size:12px;color:#cfe2ff;font-weight:500;letter-spacing:.04em}
|
|
211
|
+
.createp .fld{display:flex;align-items:center;gap:10px;margin-bottom:10px}
|
|
212
|
+
.createp label{font-family:var(--mono);font-size:11px;color:var(--muted);width:72px;flex:none}
|
|
213
|
+
.createp input[type=text],.createp input[type=date],.createp textarea{font-family:var(--mono);font-size:12px;
|
|
214
|
+
background:var(--ink);border:1px solid var(--line);color:var(--text);border-radius:6px;padding:8px 10px;flex:1;width:100%;color-scheme:dark}
|
|
215
|
+
.createp textarea{min-height:120px;resize:vertical;line-height:1.6}
|
|
216
|
+
.createp .actions{display:flex;gap:8px;align-items:center;margin-top:6px}
|
|
217
|
+
.newbtns{display:flex;gap:6px;margin-left:auto}
|
|
218
|
+
.editor .fld{display:flex;align-items:center;gap:10px;margin-bottom:10px}
|
|
219
|
+
.editor label{font-family:var(--mono);font-size:11px;color:var(--muted);width:78px;flex:none}
|
|
220
|
+
.editor input[type=text],.editor input[type=date]{font-family:var(--mono);font-size:12px;background:var(--ink);
|
|
221
|
+
border:1px solid var(--line);color:var(--text);border-radius:6px;padding:7px 9px;flex:1;color-scheme:dark}
|
|
222
|
+
.editor input[type=date],.createp input[type=date]{flex:0 0 auto;width:200px}
|
|
223
|
+
.editor .hint{font-family:var(--mono);font-size:10.5px;color:var(--faint)}
|
|
224
|
+
.toggle{display:flex;align-items:center;gap:7px;font-family:var(--mono);font-size:12px;color:var(--text);cursor:pointer}
|
|
225
|
+
|
|
226
|
+
/* ---------- misc ---------- */
|
|
227
|
+
.toast{position:fixed;bottom:24px;left:50%;transform:translateX(-50%) translateY(20px);
|
|
228
|
+
background:var(--raise);border:1px solid var(--azure);color:#fff;padding:10px 18px;
|
|
229
|
+
border-radius:var(--r-sm);opacity:0;transition:.25s;z-index:60;font-size:13px}
|
|
230
|
+
.toast.show{opacity:1;transform:translateX(-50%) translateY(0)}
|
|
231
|
+
.banner{background:linear-gradient(180deg,rgba(255,92,108,.14),rgba(255,92,108,.04));
|
|
232
|
+
border:1px solid rgba(255,92,108,.4);color:#FFC4C9;padding:13px 16px;border-radius:var(--r);
|
|
233
|
+
margin-bottom:18px;display:none;font-size:13px}
|
|
234
|
+
.banner.show{display:block}
|
|
235
|
+
.banner code{font-family:var(--mono);background:rgba(0,0,0,.3);padding:1px 6px;border-radius:4px;color:#fff}
|
|
236
|
+
.spin{width:18px;height:18px;border:2px solid var(--line);border-top-color:var(--azure);border-radius:50%;
|
|
237
|
+
display:inline-block;animation:spin .7s linear infinite;vertical-align:-4px}
|
|
238
|
+
@keyframes spin{to{transform:rotate(360deg)}}
|
|
239
|
+
.progress{width:64%;max-width:380px;height:6px;background:var(--ink);border:1px solid var(--line);
|
|
240
|
+
border-radius:999px;margin:16px auto 0;overflow:hidden}
|
|
241
|
+
.progress .bar{height:100%;background:linear-gradient(90deg,var(--azure),var(--ok));border-radius:999px;transition:width .4s}
|
|
242
|
+
|
|
243
|
+
@media (max-width:900px){
|
|
244
|
+
/* .app grid + main padding handled by Tailwind max-[900px]: utilities */
|
|
245
|
+
aside{position:static;height:auto;flex-direction:row;flex-wrap:wrap;align-items:center}
|
|
246
|
+
aside nav{display:flex;gap:4px;flex-wrap:wrap} .navlabel{display:none} .who{display:none}
|
|
247
|
+
input.search{min-width:180px}
|
|
248
|
+
}
|
|
249
|
+
@media (prefers-reduced-motion:reduce){*{animation-duration:.01ms!important;transition:none!important}}
|
package/server.js
CHANGED
|
@@ -31,17 +31,25 @@ const SUB_RE = /^[0-9a-fA-F-]{1,40}$|^[\w .()-]{1,80}$/; // sub id or display na
|
|
|
31
31
|
|
|
32
32
|
function isValidName(v) { return typeof v === 'string' && NAME_RE.test(v); }
|
|
33
33
|
|
|
34
|
-
/** Run az with an args array
|
|
34
|
+
/** Run az with an args array. Returns parsed JSON.
|
|
35
|
+
* az() only runs trusted, fixed commands (get-access-token / logout) — never user
|
|
36
|
+
* input — so enabling the shell on Windows (required to launch az.cmd) is safe here. */
|
|
35
37
|
function az(args, { json = true } = {}) {
|
|
36
38
|
const finalArgs = json ? [...args, '--output', 'json'] : args;
|
|
39
|
+
const isWin = process.platform === 'win32';
|
|
37
40
|
return new Promise((resolve, reject) => {
|
|
38
|
-
execFile(AZ, finalArgs, {
|
|
39
|
-
|
|
41
|
+
execFile(AZ, finalArgs, {
|
|
42
|
+
timeout: AZ_TIMEOUT, maxBuffer: 64 * 1024 * 1024,
|
|
43
|
+
shell: isWin, // on Windows the CLI is `az.cmd`; execFile can't run it without a shell
|
|
44
|
+
windowsHide: true,
|
|
45
|
+
}, (err, stdout, stderr) => {
|
|
40
46
|
if (err) {
|
|
41
47
|
const msg = (stderr || err.message || '').trim();
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
48
|
+
const notFound = err.code === 'ENOENT' || /not recognized|cannot find the (path|file)|ENOENT/i.test(msg);
|
|
49
|
+
return reject(Object.assign(new Error(
|
|
50
|
+
notFound ? `Azure CLI not found (tried "${AZ}"). Install the Azure CLI, or set AZ_PATH to its full path.` : (msg || 'az command failed')), {
|
|
51
|
+
azError: true, notFound,
|
|
52
|
+
needLogin: /AADSTS70043|refresh token|please run ['"]?az login|InvalidAuthenticationToken|expired|no subscription|not logged in/i.test(msg),
|
|
45
53
|
forbidden: /Forbidden|does not have secrets|AccessDenied|not authorized|403/i.test(msg),
|
|
46
54
|
stderr: msg, args: finalArgs,
|
|
47
55
|
}));
|
|
@@ -524,8 +532,9 @@ const ENV_RULES = (() => {
|
|
|
524
532
|
try { return process.env.AZBO_ENV_RULES ? JSON.parse(process.env.AZBO_ENV_RULES) : null; }
|
|
525
533
|
catch { console.warn(' AZBO_ENV_RULES is not valid JSON — ignoring.'); return null; }
|
|
526
534
|
})();
|
|
535
|
+
const PKG_VERSION = (() => { try { return require('./package.json').version; } catch { return null; } })();
|
|
527
536
|
app.get('/api/config', (req, res) =>
|
|
528
|
-
res.json({ ok: true, data: { envRules: ENV_RULES, title: process.env.AZBO_TITLE || null } }));
|
|
537
|
+
res.json({ ok: true, data: { envRules: ENV_RULES, title: process.env.AZBO_TITLE || null, version: PKG_VERSION } }));
|
|
529
538
|
|
|
530
539
|
function openBrowser(url) {
|
|
531
540
|
if (process.env.AZBO_NO_OPEN) return;
|