@w6w/ui 0.1.1 → 0.2.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/CHANGELOG.md +20 -0
- package/README.md +7 -5
- package/dist/AppPicker.d.ts +1 -0
- package/dist/ConfirmModal-ChoiGy2K.cjs +11 -0
- package/dist/ConfirmModal-ChoiGy2K.cjs.map +1 -0
- package/dist/{ConfirmModal-CD-P8mhN.js → ConfirmModal-Wx3i8F1n.js} +1588 -1564
- package/dist/ConfirmModal-Wx3i8F1n.js.map +1 -0
- package/dist/ConnectionHeader.d.ts +19 -0
- package/dist/EditConnectionModal.d.ts +21 -0
- package/dist/app-pages.d.ts +3 -0
- package/dist/components/AuthFieldsForm.d.ts +8 -1
- package/dist/components/CallableList.d.ts +57 -0
- package/dist/components/ConnectionList.d.ts +44 -0
- package/dist/components/ListItem.d.ts +16 -6
- package/dist/components/ReliabilityChart.d.ts +10 -0
- package/dist/components/ReliabilityList.d.ts +47 -0
- package/dist/connection-app-meta.d.ts +32 -0
- package/dist/flow.cjs +1 -1
- package/dist/flow.mjs +2 -2
- package/dist/index.cjs +1 -1
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +16 -0
- package/dist/index.mjs +1700 -891
- package/dist/index.mjs.map +1 -1
- package/dist/provider.d.ts +19 -0
- package/dist/reliability-board-layout.d.ts +121 -0
- package/dist/types.d.ts +8 -0
- package/package.json +2 -2
- package/src/styles/_callable-list.scss +60 -0
- package/src/styles/_connections.scss +100 -0
- package/src/styles/_lists.scss +4 -0
- package/src/styles/_reliability.scss +339 -0
- package/src/styles.css +592 -0
- package/src/styles.scss +3 -0
- package/dist/ConfirmModal-8avl9Nv8.cjs +0 -11
- package/dist/ConfirmModal-8avl9Nv8.cjs.map +0 -1
- package/dist/ConfirmModal-CD-P8mhN.js.map +0 -1
package/dist/provider.d.ts
CHANGED
|
@@ -119,12 +119,21 @@ export interface ListAppsPageOptions {
|
|
|
119
119
|
compact?: boolean;
|
|
120
120
|
/** Abort this request. Local request control only; never a wire field. */
|
|
121
121
|
signal?: AbortSignal;
|
|
122
|
+
/** Snapshot id from a prior {@link AppsPageResult.snapshot}. Navigation normally uses `cursor` alone. */
|
|
123
|
+
snapshot?: string;
|
|
124
|
+
/** Absolute offset into the snapshot. */
|
|
125
|
+
offset?: number;
|
|
122
126
|
}
|
|
123
127
|
/** One page of apps, as returned by {@link W6WApi.listAppsPage}. */
|
|
124
128
|
export interface AppsPageResult {
|
|
125
129
|
apps: AppSummary[];
|
|
126
130
|
/** Present unless this is the last page. */
|
|
127
131
|
nextCursor?: string;
|
|
132
|
+
snapshot?: string;
|
|
133
|
+
total?: number;
|
|
134
|
+
offset?: number;
|
|
135
|
+
limit?: number;
|
|
136
|
+
expiresAt?: string;
|
|
128
137
|
}
|
|
129
138
|
export interface W6WApi {
|
|
130
139
|
/**
|
|
@@ -174,6 +183,16 @@ export interface W6WApi {
|
|
|
174
183
|
displayName?: string;
|
|
175
184
|
profile?: Record<string, unknown>;
|
|
176
185
|
}): Promise<ConnectionSummary>;
|
|
186
|
+
/**
|
|
187
|
+
* Rename a connection and/or replace some of its credential fields (`PATCH /connections/:id`).
|
|
188
|
+
* Send only the fields being changed: the server merges a partial `credential` over the stored
|
|
189
|
+
* one. Optional: when absent, `EditConnectionModal` shows no Save button (a one-line notice
|
|
190
|
+
* instead) and hosts should not offer an Edit action. It never throws for the absence.
|
|
191
|
+
*/
|
|
192
|
+
updateConnection?(id: string, body: {
|
|
193
|
+
displayName?: string;
|
|
194
|
+
credential?: Record<string, unknown>;
|
|
195
|
+
}): Promise<ConnectionSummary>;
|
|
177
196
|
/**
|
|
178
197
|
* Start an OAuth 2.0 flow. Server builds the provider's authorize URL and
|
|
179
198
|
* returns it; the caller opens it in a popup and awaits the server's
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* How the reliability board arranges itself — the decisions that are arithmetic
|
|
3
|
+
* rather than markup, kept pure (no React, no DOM) so they test without a DOM
|
|
4
|
+
* and so `@w6w/embed` can import them beside the components.
|
|
5
|
+
*
|
|
6
|
+
* **None of these decide a STATE.** Every state read below arrived already
|
|
7
|
+
* decided by the server; nothing here compares a ratio, applies a threshold to
|
|
8
|
+
* call data, or invents a fifth word. What these decide is where a row is PUT,
|
|
9
|
+
* which is presentation.
|
|
10
|
+
*/
|
|
11
|
+
/** The four words the server uses for a health verdict. */
|
|
12
|
+
export type ReliabilityViewState = "ok" | "degraded" | "down" | "unknown";
|
|
13
|
+
/**
|
|
14
|
+
* The row shape the reliability components render — owned by `@w6w/ui` so it
|
|
15
|
+
* imports nothing from the SDK. The SDK's `ReliabilityService` assigns to it
|
|
16
|
+
* structurally; the generic functions below keep the caller's richer row type.
|
|
17
|
+
*/
|
|
18
|
+
export interface ReliabilityServiceView {
|
|
19
|
+
appId: string;
|
|
20
|
+
displayName: string;
|
|
21
|
+
/** Inline icon fields — the board never asks a catalog for them. */
|
|
22
|
+
iconSrc?: string;
|
|
23
|
+
iconSrcDark?: string;
|
|
24
|
+
brandColor?: string;
|
|
25
|
+
/** "Did YOUR calls succeed". */
|
|
26
|
+
state: ReliabilityViewState;
|
|
27
|
+
calls: number;
|
|
28
|
+
errors: {
|
|
29
|
+
e4xx: number;
|
|
30
|
+
e429: number;
|
|
31
|
+
eAuth: number;
|
|
32
|
+
e5xx: number;
|
|
33
|
+
};
|
|
34
|
+
p95Ms: number | null;
|
|
35
|
+
ongoingOutageId: string | null;
|
|
36
|
+
/** One entry per calendar day, oldest first, exactly as the server sent it. */
|
|
37
|
+
uptime: readonly {
|
|
38
|
+
day: string;
|
|
39
|
+
state: ReliabilityViewState;
|
|
40
|
+
}[];
|
|
41
|
+
/** "What does the vendor say about itself"; `null` when no public check exists. */
|
|
42
|
+
vendorStatus: {
|
|
43
|
+
state: ReliabilityViewState;
|
|
44
|
+
message: string | null;
|
|
45
|
+
} | null;
|
|
46
|
+
/** The server's tier for the row — read, never re-derived. */
|
|
47
|
+
attention: "now" | "recent" | "none";
|
|
48
|
+
}
|
|
49
|
+
/** One point of the performance scatter — a service with both a call volume and a measured p95. */
|
|
50
|
+
export interface PerformancePoint {
|
|
51
|
+
appId: string;
|
|
52
|
+
displayName: string;
|
|
53
|
+
calls: number;
|
|
54
|
+
p95Ms: number;
|
|
55
|
+
state: ReliabilityViewState;
|
|
56
|
+
/** Total errors across all four kinds — drives the point's size, not its position. */
|
|
57
|
+
errorCount: number;
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Every service with at least one timed call, as a (calls, p95) point — what
|
|
61
|
+
* the performance chart plots. Excludes `p95Ms: null`: there is no Y position
|
|
62
|
+
* for a service with nothing timed.
|
|
63
|
+
*/
|
|
64
|
+
export declare function performancePoints(services: readonly ReliabilityServiceView[]): PerformancePoint[];
|
|
65
|
+
/**
|
|
66
|
+
* Does this service want the reader's attention? READS the tier the server
|
|
67
|
+
* sent (`attention`), never re-derives it from `state` or `vendorStatus`: the
|
|
68
|
+
* server's `boardRank` is the one implementation, and a second copy here would
|
|
69
|
+
* drift. `now` and `recent` both count; they differ in tense only.
|
|
70
|
+
*/
|
|
71
|
+
export declare function needsAttention(service: Pick<ReliabilityServiceView, "attention">): boolean;
|
|
72
|
+
/** The board in its three groups, each keeping the server's own ordering. */
|
|
73
|
+
export interface BoardPartition<T extends ReliabilityServiceView = ReliabilityServiceView> {
|
|
74
|
+
/** Broken right now, on either signal. */
|
|
75
|
+
now: T[];
|
|
76
|
+
/** Clear now, but this window holds a failed stretch or an open advisory. */
|
|
77
|
+
recent: T[];
|
|
78
|
+
/** Everything else, in the server's recency order. */
|
|
79
|
+
clear: T[];
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* Group the board without reordering within any group. Bucketing is not
|
|
83
|
+
* sorting: each group comes out in the order it arrived. An unrecognised
|
|
84
|
+
* `attention` value falls to `clear` rather than throwing — a client can be
|
|
85
|
+
* older than the server that answered it.
|
|
86
|
+
*/
|
|
87
|
+
export declare function partitionBoard<T extends ReliabilityServiceView>(services: readonly T[]): BoardPartition<T>;
|
|
88
|
+
/**
|
|
89
|
+
* The uptime strip's cells for one service — the server's own array, in the
|
|
90
|
+
* order it arrived (oldest first). A passthrough on purpose: no sort, no
|
|
91
|
+
* reverse, no gap filling. The strip has no date axis, so a reversed array is
|
|
92
|
+
* visually plausible and would silently move an outage to the wrong end.
|
|
93
|
+
*/
|
|
94
|
+
export declare function uptimeDays(service: Pick<ReliabilityServiceView, "uptime">): {
|
|
95
|
+
day: string;
|
|
96
|
+
state: ReliabilityViewState;
|
|
97
|
+
}[];
|
|
98
|
+
/** Compact mode shows at most this many needs-attention cards, `now` filling first. */
|
|
99
|
+
export declare const ATTENTION_DISPLAY_CAP = 3;
|
|
100
|
+
/** Compact mode shows this many table rows unless the caller passes `limit`. */
|
|
101
|
+
export declare const RECENT_DISPLAY_CAP = 5;
|
|
102
|
+
/** What the board shows and what a display cap hid. */
|
|
103
|
+
export interface BoardDisplay<T extends ReliabilityServiceView = ReliabilityServiceView> {
|
|
104
|
+
/** TRUE total of attention rows (the whole input, not the capped slice). */
|
|
105
|
+
attentionCount: number;
|
|
106
|
+
shownNow: T[];
|
|
107
|
+
shownRecent: T[];
|
|
108
|
+
hiddenAttentionCount: number;
|
|
109
|
+
/** Table rows in the server's order, every attention tier included. */
|
|
110
|
+
shownTable: T[];
|
|
111
|
+
hiddenTableCount: number;
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* Apply the display caps to the complete board. Compact: ≤ 3 attention cards
|
|
115
|
+
* (`now` first, `recent` tops up) and `limit ?? 5` table rows; otherwise
|
|
116
|
+
* uncapped. Hidden counts are computed from the full input, so they are real.
|
|
117
|
+
*/
|
|
118
|
+
export declare function displayBoard<T extends ReliabilityServiceView>(services: readonly T[], opts?: {
|
|
119
|
+
compact?: boolean;
|
|
120
|
+
limit?: number;
|
|
121
|
+
}): BoardDisplay<T>;
|
package/dist/types.d.ts
CHANGED
|
@@ -328,6 +328,14 @@ export interface ConnectionSummary {
|
|
|
328
328
|
profile?: Record<string, unknown>;
|
|
329
329
|
createdAt?: string;
|
|
330
330
|
updatedAt?: string;
|
|
331
|
+
/**
|
|
332
|
+
* True for the server's synthetic "no credential needed" rows. They have no stored credential,
|
|
333
|
+
* so the server 404s get/test/delete on their ids; the connection UI offers nothing on them.
|
|
334
|
+
* Detect with this flag only, never an id prefix.
|
|
335
|
+
*/
|
|
336
|
+
builtIn?: boolean;
|
|
337
|
+
/** The last test result: `false` = failed, `null`/absent = never tested. */
|
|
338
|
+
lastTestOk?: boolean | null;
|
|
331
339
|
}
|
|
332
340
|
/** A saved set of action-test input values, scoped to a connection + action. */
|
|
333
341
|
export interface SavedTest {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@w6w/ui",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "React components for w6w — central API management. Build plugins, apps and integrations on them.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"homepage": "https://w6w.io",
|
|
@@ -98,7 +98,7 @@
|
|
|
98
98
|
"build:css": "node scripts/build-css.mjs",
|
|
99
99
|
"check:css": "node scripts/build-css.mjs --check",
|
|
100
100
|
"typecheck": "tsc -b --noEmit",
|
|
101
|
-
"test": "node --conditions=w6w-source --import ./src/test-jsx-loader.mjs --test src/__tests__/*.test.ts src/components/__tests__/*.test.ts",
|
|
101
|
+
"test": "node --max-old-space-size=2048 --conditions=w6w-source --import ./src/test-jsx-loader.mjs --test --test-concurrency=4 --test-timeout=120000 src/__tests__/*.test.ts src/components/__tests__/*.test.ts",
|
|
102
102
|
"test:picker-layout": "bash test/picker-layout/run.sh",
|
|
103
103
|
"test:expr-template": "bash test/expr-template/run.sh",
|
|
104
104
|
"test:action-test-form": "bash test/action-test-form/run.sh",
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
.w6w-callable-list {
|
|
2
|
+
display: flex;
|
|
3
|
+
flex-direction: column;
|
|
4
|
+
gap: var(--w6w-sp-4);
|
|
5
|
+
}
|
|
6
|
+
.w6w-callable-list-head {
|
|
7
|
+
display: flex;
|
|
8
|
+
justify-content: space-between;
|
|
9
|
+
align-items: flex-start;
|
|
10
|
+
gap: var(--w6w-sp-4);
|
|
11
|
+
}
|
|
12
|
+
.w6w-callable-list-heading {
|
|
13
|
+
display: flex;
|
|
14
|
+
flex-direction: column;
|
|
15
|
+
gap: var(--w6w-sp-1);
|
|
16
|
+
min-width: 0;
|
|
17
|
+
}
|
|
18
|
+
.w6w-callable-list-title {
|
|
19
|
+
margin: 0;
|
|
20
|
+
}
|
|
21
|
+
.w6w-callable-list-description {
|
|
22
|
+
margin: 0;
|
|
23
|
+
}
|
|
24
|
+
.w6w-callable-list-toggle {
|
|
25
|
+
display: flex;
|
|
26
|
+
align-items: center;
|
|
27
|
+
gap: var(--w6w-sp-2);
|
|
28
|
+
cursor: pointer;
|
|
29
|
+
}
|
|
30
|
+
.w6w-callable-list-actions {
|
|
31
|
+
display: flex;
|
|
32
|
+
gap: var(--w6w-sp-2);
|
|
33
|
+
flex-shrink: 0;
|
|
34
|
+
white-space: nowrap;
|
|
35
|
+
}
|
|
36
|
+
.w6w-callable-list-body {
|
|
37
|
+
border: 1px solid var(--w6w-border);
|
|
38
|
+
border-radius: 8px;
|
|
39
|
+
padding: var(--w6w-sp-2);
|
|
40
|
+
}
|
|
41
|
+
.w6w-callable-list-rows {
|
|
42
|
+
list-style: none;
|
|
43
|
+
margin: 0;
|
|
44
|
+
padding: 0;
|
|
45
|
+
display: flex;
|
|
46
|
+
flex-direction: column;
|
|
47
|
+
gap: var(--w6w-sp-1);
|
|
48
|
+
}
|
|
49
|
+
.w6w-callable-list-trail {
|
|
50
|
+
display: inline-flex;
|
|
51
|
+
align-items: center;
|
|
52
|
+
gap: var(--w6w-sp-2);
|
|
53
|
+
}
|
|
54
|
+
.w6w-callable-list-badge {
|
|
55
|
+
padding: var(--w6w-sp-0-5) var(--w6w-sp-2);
|
|
56
|
+
border-radius: 999px;
|
|
57
|
+
border: 1px solid var(--w6w-border);
|
|
58
|
+
color: var(--w6w-muted);
|
|
59
|
+
font-size: var(--w6w-fs-xs);
|
|
60
|
+
}
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
/* Connections (D-12u): ConnectionList, ConnectionHeader and the state tags they share. */
|
|
2
|
+
.w6w-connection-list {
|
|
3
|
+
display: flex;
|
|
4
|
+
flex-direction: column;
|
|
5
|
+
gap: var(--w6w-sp-3);
|
|
6
|
+
}
|
|
7
|
+
.w6w-connection-list-head {
|
|
8
|
+
display: flex;
|
|
9
|
+
justify-content: space-between;
|
|
10
|
+
align-items: flex-start;
|
|
11
|
+
gap: var(--w6w-sp-4);
|
|
12
|
+
}
|
|
13
|
+
.w6w-connection-list-heading {
|
|
14
|
+
min-width: 0;
|
|
15
|
+
}
|
|
16
|
+
.w6w-connection-list-actions {
|
|
17
|
+
display: flex;
|
|
18
|
+
gap: var(--w6w-sp-2);
|
|
19
|
+
flex-shrink: 0;
|
|
20
|
+
}
|
|
21
|
+
.w6w-connection-rows {
|
|
22
|
+
list-style: none;
|
|
23
|
+
margin: 0;
|
|
24
|
+
padding: 0;
|
|
25
|
+
display: flex;
|
|
26
|
+
flex-direction: column;
|
|
27
|
+
gap: var(--w6w-sp-1);
|
|
28
|
+
}
|
|
29
|
+
.w6w-connection-row {
|
|
30
|
+
display: flex;
|
|
31
|
+
align-items: center;
|
|
32
|
+
gap: var(--w6w-sp-2);
|
|
33
|
+
}
|
|
34
|
+
.w6w-connection-row-main {
|
|
35
|
+
flex: 1;
|
|
36
|
+
min-width: 0;
|
|
37
|
+
}
|
|
38
|
+
.w6w-connection-row-actions {
|
|
39
|
+
display: flex;
|
|
40
|
+
gap: var(--w6w-sp-1-5);
|
|
41
|
+
flex-shrink: 0;
|
|
42
|
+
}
|
|
43
|
+
.w6w-connection-name {
|
|
44
|
+
margin-right: var(--w6w-sp-2);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/* State tags (connected / needs attention / invalid). */
|
|
48
|
+
.w6w-connection-tag {
|
|
49
|
+
display: inline-block;
|
|
50
|
+
background: var(--w6w-panel-2);
|
|
51
|
+
border: 1px solid var(--w6w-border);
|
|
52
|
+
border-radius: 999px;
|
|
53
|
+
padding: var(--w6w-sp-0-5) var(--w6w-sp-2);
|
|
54
|
+
font-size: var(--w6w-fs-xs);
|
|
55
|
+
margin-right: var(--w6w-sp-1);
|
|
56
|
+
}
|
|
57
|
+
.w6w-connection-tag-warn {
|
|
58
|
+
color: #ffb86b;
|
|
59
|
+
border-color: #ffb86b;
|
|
60
|
+
}
|
|
61
|
+
.w6w-connection-tag-danger {
|
|
62
|
+
color: var(--w6w-danger);
|
|
63
|
+
border-color: var(--w6w-danger);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/* ConnectionHeader */
|
|
67
|
+
.w6w-connection-header {
|
|
68
|
+
display: flex;
|
|
69
|
+
align-items: center;
|
|
70
|
+
gap: var(--w6w-sp-3);
|
|
71
|
+
}
|
|
72
|
+
.w6w-connection-header-main {
|
|
73
|
+
flex: 1;
|
|
74
|
+
min-width: 0;
|
|
75
|
+
}
|
|
76
|
+
.w6w-connection-header-title {
|
|
77
|
+
margin: 0;
|
|
78
|
+
}
|
|
79
|
+
.w6w-connection-header-meta {
|
|
80
|
+
margin: 0;
|
|
81
|
+
}
|
|
82
|
+
.w6w-connection-header-actions {
|
|
83
|
+
display: flex;
|
|
84
|
+
gap: var(--w6w-sp-1-5);
|
|
85
|
+
align-items: center;
|
|
86
|
+
}
|
|
87
|
+
/* The id's copy button appears on hover/focus only. */
|
|
88
|
+
.w6w-connection-id-copy.w6w-copyable {
|
|
89
|
+
display: inline-flex;
|
|
90
|
+
width: auto;
|
|
91
|
+
min-width: 0;
|
|
92
|
+
vertical-align: middle;
|
|
93
|
+
}
|
|
94
|
+
.w6w-connection-id-copy .w6w-copyable-btn {
|
|
95
|
+
opacity: 0;
|
|
96
|
+
}
|
|
97
|
+
.w6w-connection-id-copy:hover .w6w-copyable-btn,
|
|
98
|
+
.w6w-connection-id-copy:focus-within .w6w-copyable-btn {
|
|
99
|
+
opacity: 1;
|
|
100
|
+
}
|
package/src/styles/_lists.scss
CHANGED
|
@@ -0,0 +1,339 @@
|
|
|
1
|
+
/* --- ReliabilityList / ReliabilityChart ---------------------------------- */
|
|
2
|
+
/* The service-reliability board: "needs attention" cards, a calls x p95
|
|
3
|
+
scatter and the services table. State colour throughout comes from the
|
|
4
|
+
shared `--w6w-health-*` tokens via HealthStatusPill and UptimeStrip (and,
|
|
5
|
+
for the scatter, read directly) — never from a colour written here.
|
|
6
|
+
Every navigation site (card, service cell, scatter point) may be an `<a>`,
|
|
7
|
+
a `<button>` or a plain element, so the shared rules reset all three. */
|
|
8
|
+
|
|
9
|
+
.w6w-rel-section {
|
|
10
|
+
margin-bottom: var(--w6w-sp-6);
|
|
11
|
+
}
|
|
12
|
+
.w6w-rel-heading {
|
|
13
|
+
margin: 0 0 var(--w6w-sp-2);
|
|
14
|
+
display: flex;
|
|
15
|
+
align-items: baseline;
|
|
16
|
+
justify-content: space-between;
|
|
17
|
+
gap: var(--w6w-sp-3);
|
|
18
|
+
}
|
|
19
|
+
.w6w-rel-intro {
|
|
20
|
+
margin: 0 0 var(--w6w-sp-2);
|
|
21
|
+
}
|
|
22
|
+
.w6w-rel-card {
|
|
23
|
+
background: var(--w6w-panel);
|
|
24
|
+
border: 1px solid var(--w6w-border);
|
|
25
|
+
border-radius: var(--w6w-radius);
|
|
26
|
+
padding: var(--w6w-sp-4);
|
|
27
|
+
}
|
|
28
|
+
.w6w-rel-card h4 {
|
|
29
|
+
margin: 0 0 var(--w6w-sp-2);
|
|
30
|
+
}
|
|
31
|
+
.w6w-rel-tag {
|
|
32
|
+
display: inline-block;
|
|
33
|
+
background: var(--w6w-panel-2);
|
|
34
|
+
border: 1px solid var(--w6w-border);
|
|
35
|
+
border-radius: 999px;
|
|
36
|
+
padding: var(--w6w-sp-0-5) var(--w6w-sp-2);
|
|
37
|
+
font-size: var(--w6w-fs-xs);
|
|
38
|
+
color: var(--w6w-muted);
|
|
39
|
+
margin-right: var(--w6w-sp-1);
|
|
40
|
+
}
|
|
41
|
+
.w6w-rel-group + .w6w-rel-group {
|
|
42
|
+
margin-top: var(--w6w-sp-5);
|
|
43
|
+
}
|
|
44
|
+
.w6w-rel-group-head {
|
|
45
|
+
margin: 0 0 var(--w6w-sp-2);
|
|
46
|
+
font-size: var(--w6w-fs-sm);
|
|
47
|
+
}
|
|
48
|
+
/* The "N more — view all" line under a capped section. */
|
|
49
|
+
.w6w-rel-more-note {
|
|
50
|
+
margin: var(--w6w-sp-2) 0 0;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/* --- "Needs attention" callout — a few dense cards, not table rows -------- */
|
|
54
|
+
.w6w-rel-issues {
|
|
55
|
+
display: grid;
|
|
56
|
+
/* `auto-fit`, not `auto-fill`: with fewer cards than fit the row, `auto-fill`
|
|
57
|
+
keeps empty width-reserving tracks and the cards stop short of the edge. */
|
|
58
|
+
grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
|
|
59
|
+
gap: var(--w6w-sp-2);
|
|
60
|
+
}
|
|
61
|
+
.w6w-rel-issue-card {
|
|
62
|
+
display: flex;
|
|
63
|
+
flex-direction: column;
|
|
64
|
+
gap: var(--w6w-sp-2);
|
|
65
|
+
padding: var(--w6w-sp-3);
|
|
66
|
+
background: var(--w6w-panel);
|
|
67
|
+
border: 1px solid var(--w6w-border);
|
|
68
|
+
/* Same `--w6w-health-*` tokens as the pills inside, so the stripe cannot
|
|
69
|
+
drift from the pill beside it. `down` on either signal escalates it. */
|
|
70
|
+
border-left: 3px solid var(--w6w-health-degraded);
|
|
71
|
+
border-radius: var(--w6w-radius);
|
|
72
|
+
color: inherit;
|
|
73
|
+
text-decoration: none;
|
|
74
|
+
/* button reset (the card is a <button> when only `onOpen` is given) */
|
|
75
|
+
font: inherit;
|
|
76
|
+
text-align: left;
|
|
77
|
+
width: 100%;
|
|
78
|
+
cursor: pointer;
|
|
79
|
+
}
|
|
80
|
+
div.w6w-rel-issue-card {
|
|
81
|
+
cursor: default;
|
|
82
|
+
}
|
|
83
|
+
.w6w-rel-issue-card:has(.w6w-health-pill-down) {
|
|
84
|
+
border-left-color: var(--w6w-health-down);
|
|
85
|
+
}
|
|
86
|
+
a.w6w-rel-issue-card:hover,
|
|
87
|
+
a.w6w-rel-issue-card:focus-visible,
|
|
88
|
+
button.w6w-rel-issue-card:hover,
|
|
89
|
+
button.w6w-rel-issue-card:focus-visible {
|
|
90
|
+
border-color: var(--w6w-accent);
|
|
91
|
+
}
|
|
92
|
+
.w6w-rel-issue-head {
|
|
93
|
+
display: flex;
|
|
94
|
+
align-items: center;
|
|
95
|
+
gap: var(--w6w-sp-2);
|
|
96
|
+
}
|
|
97
|
+
.w6w-rel-issue-head-text {
|
|
98
|
+
display: flex;
|
|
99
|
+
flex-direction: column;
|
|
100
|
+
gap: var(--w6w-sp-0-5);
|
|
101
|
+
min-width: 0;
|
|
102
|
+
}
|
|
103
|
+
.w6w-rel-issue-signals {
|
|
104
|
+
display: flex;
|
|
105
|
+
gap: var(--w6w-sp-4);
|
|
106
|
+
}
|
|
107
|
+
.w6w-rel-issue-signal {
|
|
108
|
+
display: flex;
|
|
109
|
+
flex-direction: column;
|
|
110
|
+
gap: var(--w6w-sp-0-5);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/* --- Performance scatter — calls x p95, one point per API ----------------- */
|
|
114
|
+
.w6w-rel-scatter svg {
|
|
115
|
+
display: block;
|
|
116
|
+
width: 100%;
|
|
117
|
+
height: auto;
|
|
118
|
+
}
|
|
119
|
+
/* SVG shapes have no fill unless one is set here; without it the plot
|
|
120
|
+
renders as SVG's default opaque black regardless of theme. */
|
|
121
|
+
.w6w-rel-scatter-plot-bg {
|
|
122
|
+
fill: var(--w6w-panel-2);
|
|
123
|
+
}
|
|
124
|
+
.w6w-rel-scatter-grid {
|
|
125
|
+
stroke: var(--w6w-border);
|
|
126
|
+
stroke-width: 1;
|
|
127
|
+
}
|
|
128
|
+
.w6w-rel-scatter-axis-label {
|
|
129
|
+
fill: var(--w6w-muted);
|
|
130
|
+
font-size: var(--w6w-fs-xs);
|
|
131
|
+
}
|
|
132
|
+
.w6w-rel-scatter-axis-title {
|
|
133
|
+
fill: var(--w6w-muted);
|
|
134
|
+
font-size: var(--w6w-fs-xs);
|
|
135
|
+
}
|
|
136
|
+
/* The point's coloured ring — carries exec status. */
|
|
137
|
+
.w6w-rel-scatter-ring {
|
|
138
|
+
fill: var(--w6w-panel);
|
|
139
|
+
stroke-width: 2;
|
|
140
|
+
}
|
|
141
|
+
.w6w-rel-scatter-ring-ok {
|
|
142
|
+
stroke: var(--w6w-health-ok);
|
|
143
|
+
}
|
|
144
|
+
.w6w-rel-scatter-ring-degraded {
|
|
145
|
+
stroke: var(--w6w-health-degraded);
|
|
146
|
+
}
|
|
147
|
+
.w6w-rel-scatter-ring-down {
|
|
148
|
+
stroke: var(--w6w-health-down);
|
|
149
|
+
}
|
|
150
|
+
.w6w-rel-scatter-ring-unknown {
|
|
151
|
+
stroke: var(--w6w-health-unknown);
|
|
152
|
+
}
|
|
153
|
+
.w6w-rel-scatter-point-link {
|
|
154
|
+
cursor: pointer;
|
|
155
|
+
}
|
|
156
|
+
.w6w-rel-scatter-point-link:hover .w6w-rel-scatter-ring,
|
|
157
|
+
.w6w-rel-scatter-point-link:focus-visible .w6w-rel-scatter-ring {
|
|
158
|
+
stroke-width: 3;
|
|
159
|
+
}
|
|
160
|
+
.w6w-rel-scatter-point {
|
|
161
|
+
stroke: var(--w6w-panel);
|
|
162
|
+
stroke-width: 1.5;
|
|
163
|
+
fill-opacity: 0.85;
|
|
164
|
+
}
|
|
165
|
+
.w6w-rel-scatter-point-ok {
|
|
166
|
+
fill: var(--w6w-health-ok);
|
|
167
|
+
}
|
|
168
|
+
.w6w-rel-scatter-point-degraded {
|
|
169
|
+
fill: var(--w6w-health-degraded);
|
|
170
|
+
}
|
|
171
|
+
.w6w-rel-scatter-point-down {
|
|
172
|
+
fill: var(--w6w-health-down);
|
|
173
|
+
}
|
|
174
|
+
.w6w-rel-scatter-point-unknown {
|
|
175
|
+
fill: var(--w6w-health-unknown);
|
|
176
|
+
}
|
|
177
|
+
.w6w-rel-scatter-note {
|
|
178
|
+
margin: var(--w6w-sp-1) 0 0;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
/* --- The full roster — one real <table> ------------------------------------ */
|
|
182
|
+
.w6w-rel-table-wrap {
|
|
183
|
+
overflow-x: auto;
|
|
184
|
+
}
|
|
185
|
+
.w6w-rel-table {
|
|
186
|
+
width: 100%;
|
|
187
|
+
border-collapse: collapse;
|
|
188
|
+
}
|
|
189
|
+
.w6w-rel-table th {
|
|
190
|
+
padding: var(--w6w-sp-2) var(--w6w-sp-3);
|
|
191
|
+
text-align: left;
|
|
192
|
+
font-size: var(--w6w-fs-xs);
|
|
193
|
+
font-weight: var(--w6w-fw-semibold);
|
|
194
|
+
color: var(--w6w-muted);
|
|
195
|
+
border-bottom: 1px solid var(--w6w-border);
|
|
196
|
+
white-space: nowrap;
|
|
197
|
+
}
|
|
198
|
+
.w6w-rel-table td {
|
|
199
|
+
padding: var(--w6w-sp-2-5) var(--w6w-sp-3);
|
|
200
|
+
border-bottom: 1px solid var(--w6w-border);
|
|
201
|
+
vertical-align: top;
|
|
202
|
+
}
|
|
203
|
+
/* Inside a compact card there is no rule under the last row. */
|
|
204
|
+
.w6w-rel-card .w6w-rel-table tbody tr:last-child td {
|
|
205
|
+
border-bottom: none;
|
|
206
|
+
}
|
|
207
|
+
.w6w-rel-table tbody tr:hover {
|
|
208
|
+
background: var(--w6w-panel-2);
|
|
209
|
+
}
|
|
210
|
+
/* A row's own stripe — same tokens and escalation as the issue card; both the
|
|
211
|
+
tier and the state that select the token are decided server-side. */
|
|
212
|
+
.w6w-rel-tr.w6w-rel-attention {
|
|
213
|
+
box-shadow: inset 3px 0 0 var(--w6w-health-degraded);
|
|
214
|
+
}
|
|
215
|
+
.w6w-rel-tr.w6w-rel-attention:has(.w6w-health-pill-down) {
|
|
216
|
+
box-shadow: inset 3px 0 0 var(--w6w-health-down);
|
|
217
|
+
}
|
|
218
|
+
.w6w-rel-tr[data-attention="recent"] {
|
|
219
|
+
box-shadow: inset 3px 0 0 var(--w6w-health-unknown);
|
|
220
|
+
}
|
|
221
|
+
.w6w-rel-open {
|
|
222
|
+
display: flex;
|
|
223
|
+
align-items: center;
|
|
224
|
+
gap: var(--w6w-sp-2);
|
|
225
|
+
color: inherit;
|
|
226
|
+
text-decoration: none;
|
|
227
|
+
/* button reset (the cell is a <button> when only `onOpen` is given) */
|
|
228
|
+
font: inherit;
|
|
229
|
+
text-align: left;
|
|
230
|
+
background: none;
|
|
231
|
+
border: 0;
|
|
232
|
+
padding: 0;
|
|
233
|
+
}
|
|
234
|
+
a.w6w-rel-open,
|
|
235
|
+
button.w6w-rel-open {
|
|
236
|
+
cursor: pointer;
|
|
237
|
+
}
|
|
238
|
+
.w6w-rel-td-service-text {
|
|
239
|
+
display: flex;
|
|
240
|
+
flex-direction: column;
|
|
241
|
+
gap: var(--w6w-sp-0-5);
|
|
242
|
+
min-width: 0;
|
|
243
|
+
}
|
|
244
|
+
.w6w-rel-td-service strong {
|
|
245
|
+
overflow-wrap: anywhere;
|
|
246
|
+
}
|
|
247
|
+
a.w6w-rel-open:hover strong,
|
|
248
|
+
a.w6w-rel-open:focus-visible strong,
|
|
249
|
+
button.w6w-rel-open:hover strong,
|
|
250
|
+
button.w6w-rel-open:focus-visible strong {
|
|
251
|
+
color: var(--w6w-accent);
|
|
252
|
+
}
|
|
253
|
+
.w6w-rel-td-uptime {
|
|
254
|
+
min-width: 220px;
|
|
255
|
+
}
|
|
256
|
+
.w6w-rel-td-uptime .w6w-uptime-strip {
|
|
257
|
+
max-width: 260px;
|
|
258
|
+
}
|
|
259
|
+
/* A single short value ("397 ms" / "—"); no caption, whose varying length
|
|
260
|
+
destabilised this column's `auto` layout. */
|
|
261
|
+
.w6w-rel-td-p95 {
|
|
262
|
+
white-space: nowrap;
|
|
263
|
+
}
|
|
264
|
+
/* Both signals stacked as one line each ("Ops: <pill>" / "API: <pill>") rather
|
|
265
|
+
than two columns, so the table fits a smaller screen without scrolling. */
|
|
266
|
+
.w6w-rel-td-status {
|
|
267
|
+
display: flex;
|
|
268
|
+
flex-direction: column;
|
|
269
|
+
gap: var(--w6w-sp-1);
|
|
270
|
+
min-width: 140px;
|
|
271
|
+
}
|
|
272
|
+
.w6w-rel-status-row {
|
|
273
|
+
display: flex;
|
|
274
|
+
align-items: center;
|
|
275
|
+
flex-wrap: wrap;
|
|
276
|
+
gap: var(--w6w-sp-1-5);
|
|
277
|
+
}
|
|
278
|
+
.w6w-rel-status-label {
|
|
279
|
+
min-width: 28px;
|
|
280
|
+
}
|
|
281
|
+
/* Within a status ROW the signal reads inline, beside its label — the issue
|
|
282
|
+
card's own use of `.w6w-rel-signal` (label above pill) is unaffected. */
|
|
283
|
+
.w6w-rel-status-row .w6w-rel-signal {
|
|
284
|
+
flex-direction: row;
|
|
285
|
+
align-items: center;
|
|
286
|
+
flex-wrap: wrap;
|
|
287
|
+
}
|
|
288
|
+
/* One labelled signal inside a state cell/card — label ABOVE its pill, so two
|
|
289
|
+
labelled pills in a narrow column do not wrap unpredictably. */
|
|
290
|
+
.w6w-rel-signal {
|
|
291
|
+
display: flex;
|
|
292
|
+
flex-direction: column;
|
|
293
|
+
align-items: flex-start;
|
|
294
|
+
gap: var(--w6w-sp-0-5);
|
|
295
|
+
min-width: 0;
|
|
296
|
+
}
|
|
297
|
+
.w6w-rel-vendor-msg {
|
|
298
|
+
color: var(--w6w-muted);
|
|
299
|
+
/* The API's own incident title — clamped, full text in the row's `title`, so
|
|
300
|
+
one verbose status page cannot stretch a board row. */
|
|
301
|
+
display: -webkit-box;
|
|
302
|
+
-webkit-line-clamp: 2;
|
|
303
|
+
-webkit-box-orient: vertical;
|
|
304
|
+
overflow: hidden;
|
|
305
|
+
overflow-wrap: anywhere;
|
|
306
|
+
}
|
|
307
|
+
/* A service the account never called: no strip, just a note. */
|
|
308
|
+
.w6w-rel-idle-note {
|
|
309
|
+
font-size: var(--w6w-fs-xs);
|
|
310
|
+
font-style: italic;
|
|
311
|
+
color: var(--w6w-text);
|
|
312
|
+
}
|
|
313
|
+
.w6w-rel-allclear {
|
|
314
|
+
margin-bottom: var(--w6w-sp-4);
|
|
315
|
+
}
|
|
316
|
+
.w6w-rel-allclear p {
|
|
317
|
+
margin: 0 0 var(--w6w-sp-1-5);
|
|
318
|
+
}
|
|
319
|
+
@media (max-width: 900px) {
|
|
320
|
+
/* The table scrolls inside `.w6w-rel-table-wrap` rather than reflowing —
|
|
321
|
+
4 columns of pills and a strip cannot usefully stack. */
|
|
322
|
+
.w6w-rel-table {
|
|
323
|
+
min-width: 600px;
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
/* "Show all services →" when only `onShowAll` is given: a <button> that reads as the link. */
|
|
328
|
+
.w6w-rel-show-all {
|
|
329
|
+
font: inherit;
|
|
330
|
+
background: none;
|
|
331
|
+
border: 0;
|
|
332
|
+
padding: 0;
|
|
333
|
+
color: inherit;
|
|
334
|
+
cursor: pointer;
|
|
335
|
+
}
|
|
336
|
+
.w6w-rel-show-all:hover,
|
|
337
|
+
.w6w-rel-show-all:focus-visible {
|
|
338
|
+
color: var(--w6w-accent);
|
|
339
|
+
}
|