apiblaze 0.17.20 → 0.17.22
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/index.js +79 -3
- package/dist/react/index.d.mts +59 -0
- package/dist/react/index.d.ts +59 -0
- package/dist/react/index.js +336 -0
- package/dist/react/index.mjs +302 -0
- package/dist/server/index.d.mts +100 -0
- package/dist/server/index.d.ts +100 -0
- package/dist/server/index.js +158 -0
- package/dist/server/index.mjs +133 -0
- package/package.json +23 -7
package/dist/index.js
CHANGED
|
@@ -703,7 +703,7 @@ var import_commander = require("commander");
|
|
|
703
703
|
var import_chalk38 = __toESM(require("chalk"));
|
|
704
704
|
|
|
705
705
|
// package.json
|
|
706
|
-
var version = "0.17.
|
|
706
|
+
var version = "0.17.22";
|
|
707
707
|
|
|
708
708
|
// src/index.ts
|
|
709
709
|
init_types();
|
|
@@ -4021,9 +4021,85 @@ var SETTINGS = [
|
|
|
4021
4021
|
type: "string",
|
|
4022
4022
|
desc: "Team-scoped tag grouping projects in the consumer portal",
|
|
4023
4023
|
toPatch: (v) => ({ product_slug: v })
|
|
4024
|
+
},
|
|
4025
|
+
// ── Advanced (specs/missing.md): all optional, default-off; null clears ────
|
|
4026
|
+
{
|
|
4027
|
+
key: "ip_access.mode",
|
|
4028
|
+
label: "IP filter mode",
|
|
4029
|
+
group: "Advanced",
|
|
4030
|
+
type: "enum",
|
|
4031
|
+
enum: ["off", "allow", "deny"],
|
|
4032
|
+
desc: "allow = only listed CIDRs may call; deny = listed CIDRs are blocked",
|
|
4033
|
+
read: (cfg) => dig(cfg, "ip_access.mode") ?? "off",
|
|
4034
|
+
toPatch: (v, cfg) => ({ ip_access: { cidrs: [], ...cfg.ip_access ?? {}, mode: v } })
|
|
4035
|
+
},
|
|
4036
|
+
{
|
|
4037
|
+
key: "ip_access.cidrs",
|
|
4038
|
+
label: "IP filter CIDR list",
|
|
4039
|
+
group: "Advanced",
|
|
4040
|
+
type: "json",
|
|
4041
|
+
desc: 'JSON array of CIDRs (max 100), e.g. ["203.0.113.0/24","2001:db8::/32"]',
|
|
4042
|
+
toPatch: (v, cfg) => ({ ip_access: { mode: "off", ...cfg.ip_access ?? {}, cidrs: v } })
|
|
4043
|
+
},
|
|
4044
|
+
{
|
|
4045
|
+
key: "ip_access.reject_status",
|
|
4046
|
+
label: "IP filter reject status",
|
|
4047
|
+
group: "Advanced",
|
|
4048
|
+
type: "enum",
|
|
4049
|
+
enum: ["403", "404"],
|
|
4050
|
+
desc: "403 = explicit denial; 404 = stealth (looks like no such API)",
|
|
4051
|
+
read: (cfg) => dig(cfg, "ip_access.reject_status") ?? 403,
|
|
4052
|
+
toPatch: (v, cfg) => ({ ip_access: { mode: "off", cidrs: [], ...cfg.ip_access ?? {}, reject_status: Number(v) } })
|
|
4053
|
+
},
|
|
4054
|
+
{
|
|
4055
|
+
key: "upstream_policy.timeout_ms",
|
|
4056
|
+
label: "Upstream timeout (ms)",
|
|
4057
|
+
group: "Advanced",
|
|
4058
|
+
type: "number",
|
|
4059
|
+
desc: "Per-attempt wall clock before the proxy answers 504 (1000\u201390000; default 30000)",
|
|
4060
|
+
read: (cfg) => dig(cfg, "upstream_policy.timeout_ms") ?? 3e4,
|
|
4061
|
+
toPatch: (v, cfg) => ({ upstream_policy: { ...cfg.upstream_policy ?? {}, timeout_ms: v } })
|
|
4062
|
+
},
|
|
4063
|
+
{
|
|
4064
|
+
key: "upstream_policy.retries.max",
|
|
4065
|
+
label: "Upstream retries",
|
|
4066
|
+
group: "Advanced",
|
|
4067
|
+
type: "number",
|
|
4068
|
+
desc: "Extra attempts on 502/503/504/timeouts (0\u20133, idempotent methods only; billing counts the request once)",
|
|
4069
|
+
read: (cfg) => dig(cfg, "upstream_policy.retries.max") ?? 0,
|
|
4070
|
+
toPatch: (v, cfg) => ({ upstream_policy: { ...cfg.upstream_policy ?? {}, retries: { ...cfg.upstream_policy?.retries ?? {}, max: v } } })
|
|
4071
|
+
},
|
|
4072
|
+
{
|
|
4073
|
+
key: "upstream_policy.breaker.enabled",
|
|
4074
|
+
label: "Circuit breaker",
|
|
4075
|
+
group: "Advanced",
|
|
4076
|
+
type: "boolean",
|
|
4077
|
+
desc: "Trip to fast 503s when the upstream is failing; auto-probes to recover",
|
|
4078
|
+
read: (cfg) => dig(cfg, "upstream_policy.breaker.enabled") === true,
|
|
4079
|
+
toPatch: (v, cfg) => ({ upstream_policy: { ...cfg.upstream_policy ?? {}, breaker: { ...cfg.upstream_policy?.breaker ?? {}, enabled: v } } })
|
|
4080
|
+
},
|
|
4081
|
+
{
|
|
4082
|
+
key: "request_validation.mode",
|
|
4083
|
+
label: "Request validation",
|
|
4084
|
+
group: "Advanced",
|
|
4085
|
+
type: "enum",
|
|
4086
|
+
enum: ["off", "report", "enforce"],
|
|
4087
|
+
desc: "Validate requests against your OpenAPI spec: report = log only, enforce = reject 422",
|
|
4088
|
+
read: (cfg) => dig(cfg, "request_validation.mode") ?? "off",
|
|
4089
|
+
toPatch: (v, cfg) => ({ request_validation: { ...cfg.request_validation ?? {}, mode: v } })
|
|
4090
|
+
},
|
|
4091
|
+
{
|
|
4092
|
+
key: "request_validation.unknown_routes",
|
|
4093
|
+
label: "Unknown routes",
|
|
4094
|
+
group: "Advanced",
|
|
4095
|
+
type: "enum",
|
|
4096
|
+
enum: ["forward", "reject"],
|
|
4097
|
+
desc: "What to do when a request matches no spec operation (default forward \u2014 traffic-built specs trail reality)",
|
|
4098
|
+
read: (cfg) => dig(cfg, "request_validation.unknown_routes") ?? "forward",
|
|
4099
|
+
toPatch: (v, cfg) => ({ request_validation: { mode: "off", ...cfg.request_validation ?? {}, unknown_routes: v } })
|
|
4024
4100
|
}
|
|
4025
4101
|
];
|
|
4026
|
-
var SETTING_GROUPS = ["Basics", "Traffic & limits", "Access & auth", "Portal & MCP"];
|
|
4102
|
+
var SETTING_GROUPS = ["Basics", "Traffic & limits", "Access & auth", "Portal & MCP", "Advanced"];
|
|
4027
4103
|
var FEATURES = [
|
|
4028
4104
|
{ go: "transforms", label: "Transforms", desc: "Rewrite requests/responses (headers, body fields) without touching your upstream" },
|
|
4029
4105
|
{ go: "mappings", label: "Mapping tables", desc: "Reusable value-mapping tables used by map transforms (values can be hidden/encrypted)" },
|
|
@@ -5398,7 +5474,7 @@ async function mintDurableProxyKey(teamId, tenant2) {
|
|
|
5398
5474
|
const out = await admin({
|
|
5399
5475
|
method: "POST",
|
|
5400
5476
|
path: `/teams/${encodeURIComponent(teamId)}/tenants/${encodeURIComponent(tenant2)}/admin/keys`,
|
|
5401
|
-
body: { permissions: ["call", "
|
|
5477
|
+
body: { permissions: ["call", "use-premium"], environment: "prod" },
|
|
5402
5478
|
// key_model §2: call the API + spend the team wallet; non-expiring service key
|
|
5403
5479
|
summary: "Mint a durable data-plane key for apichat"
|
|
5404
5480
|
});
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* apiblaze/react — <ApiKeyWidget/>. Drop it into your site (e.g. resiresi.com);
|
|
5
|
+
* it talks ONLY to your own backend route (which holds the CP key). Shows the
|
|
6
|
+
* logged-in user their API keys as a simple list with one always-visible
|
|
7
|
+
* "Create key" action, plus per-key show / rotate / revoke.
|
|
8
|
+
*
|
|
9
|
+
* import { ApiKeyWidget } from 'apiblaze/react';
|
|
10
|
+
* <ApiKeyWidget theme={{ accent: '#e11d48' }} />
|
|
11
|
+
*
|
|
12
|
+
* Fully white-labelable through `theme` alone — every font and color is a token,
|
|
13
|
+
* so you can match your brand (including a dark "codebox" look) without forking
|
|
14
|
+
* the component. See ApiKeyWidgetTheme.
|
|
15
|
+
*
|
|
16
|
+
* `endpoint` defaults to /api/apiblaze/keys (where you mounted createApiblazeKeys().handler).
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
interface ApiKeyWidgetTheme {
|
|
20
|
+
/** Accent / primary action color. */
|
|
21
|
+
accent?: string;
|
|
22
|
+
/** Text/icon color ON accent buttons. Default #fff. */
|
|
23
|
+
accentText?: string;
|
|
24
|
+
/** Page color behind the card. Default transparent. */
|
|
25
|
+
background?: string;
|
|
26
|
+
/** Card body color. */
|
|
27
|
+
surface?: string;
|
|
28
|
+
/** Header strip color. Defaults to `surface` when unset. */
|
|
29
|
+
headerBackground?: string;
|
|
30
|
+
/** Primary text color. */
|
|
31
|
+
text?: string;
|
|
32
|
+
/** Secondary / muted text color. */
|
|
33
|
+
muted?: string;
|
|
34
|
+
/** Border + divider color. */
|
|
35
|
+
border?: string;
|
|
36
|
+
/** Destructive (revoke) color. */
|
|
37
|
+
danger?: string;
|
|
38
|
+
/** Positive (copied) color. */
|
|
39
|
+
success?: string;
|
|
40
|
+
/** Corner radius of the card, e.g. "12px". */
|
|
41
|
+
radius?: string;
|
|
42
|
+
/** Body font stack. */
|
|
43
|
+
fontFamily?: string;
|
|
44
|
+
/** Monospace font stack for keys and secrets. */
|
|
45
|
+
monoFontFamily?: string;
|
|
46
|
+
}
|
|
47
|
+
interface ApiKeyWidgetProps {
|
|
48
|
+
endpoint?: string;
|
|
49
|
+
theme?: ApiKeyWidgetTheme;
|
|
50
|
+
/** Heading text. Default "Your API keys". */
|
|
51
|
+
title?: string;
|
|
52
|
+
className?: string;
|
|
53
|
+
}
|
|
54
|
+
interface ApiKeyWidgetHandle {
|
|
55
|
+
refresh: () => void;
|
|
56
|
+
}
|
|
57
|
+
declare const ApiKeyWidget: React.ForwardRefExoticComponent<ApiKeyWidgetProps & React.RefAttributes<ApiKeyWidgetHandle>>;
|
|
58
|
+
|
|
59
|
+
export { ApiKeyWidget, type ApiKeyWidgetHandle, type ApiKeyWidgetProps, type ApiKeyWidgetTheme, ApiKeyWidget as default };
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* apiblaze/react — <ApiKeyWidget/>. Drop it into your site (e.g. resiresi.com);
|
|
5
|
+
* it talks ONLY to your own backend route (which holds the CP key). Shows the
|
|
6
|
+
* logged-in user their API keys as a simple list with one always-visible
|
|
7
|
+
* "Create key" action, plus per-key show / rotate / revoke.
|
|
8
|
+
*
|
|
9
|
+
* import { ApiKeyWidget } from 'apiblaze/react';
|
|
10
|
+
* <ApiKeyWidget theme={{ accent: '#e11d48' }} />
|
|
11
|
+
*
|
|
12
|
+
* Fully white-labelable through `theme` alone — every font and color is a token,
|
|
13
|
+
* so you can match your brand (including a dark "codebox" look) without forking
|
|
14
|
+
* the component. See ApiKeyWidgetTheme.
|
|
15
|
+
*
|
|
16
|
+
* `endpoint` defaults to /api/apiblaze/keys (where you mounted createApiblazeKeys().handler).
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
interface ApiKeyWidgetTheme {
|
|
20
|
+
/** Accent / primary action color. */
|
|
21
|
+
accent?: string;
|
|
22
|
+
/** Text/icon color ON accent buttons. Default #fff. */
|
|
23
|
+
accentText?: string;
|
|
24
|
+
/** Page color behind the card. Default transparent. */
|
|
25
|
+
background?: string;
|
|
26
|
+
/** Card body color. */
|
|
27
|
+
surface?: string;
|
|
28
|
+
/** Header strip color. Defaults to `surface` when unset. */
|
|
29
|
+
headerBackground?: string;
|
|
30
|
+
/** Primary text color. */
|
|
31
|
+
text?: string;
|
|
32
|
+
/** Secondary / muted text color. */
|
|
33
|
+
muted?: string;
|
|
34
|
+
/** Border + divider color. */
|
|
35
|
+
border?: string;
|
|
36
|
+
/** Destructive (revoke) color. */
|
|
37
|
+
danger?: string;
|
|
38
|
+
/** Positive (copied) color. */
|
|
39
|
+
success?: string;
|
|
40
|
+
/** Corner radius of the card, e.g. "12px". */
|
|
41
|
+
radius?: string;
|
|
42
|
+
/** Body font stack. */
|
|
43
|
+
fontFamily?: string;
|
|
44
|
+
/** Monospace font stack for keys and secrets. */
|
|
45
|
+
monoFontFamily?: string;
|
|
46
|
+
}
|
|
47
|
+
interface ApiKeyWidgetProps {
|
|
48
|
+
endpoint?: string;
|
|
49
|
+
theme?: ApiKeyWidgetTheme;
|
|
50
|
+
/** Heading text. Default "Your API keys". */
|
|
51
|
+
title?: string;
|
|
52
|
+
className?: string;
|
|
53
|
+
}
|
|
54
|
+
interface ApiKeyWidgetHandle {
|
|
55
|
+
refresh: () => void;
|
|
56
|
+
}
|
|
57
|
+
declare const ApiKeyWidget: React.ForwardRefExoticComponent<ApiKeyWidgetProps & React.RefAttributes<ApiKeyWidgetHandle>>;
|
|
58
|
+
|
|
59
|
+
export { ApiKeyWidget, type ApiKeyWidgetHandle, type ApiKeyWidgetProps, type ApiKeyWidgetTheme, ApiKeyWidget as default };
|
|
@@ -0,0 +1,336 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
"use strict";
|
|
3
|
+
var __create = Object.create;
|
|
4
|
+
var __defProp = Object.defineProperty;
|
|
5
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
6
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
7
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
8
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
9
|
+
var __export = (target, all) => {
|
|
10
|
+
for (var name in all)
|
|
11
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
12
|
+
};
|
|
13
|
+
var __copyProps = (to, from, except, desc) => {
|
|
14
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
15
|
+
for (let key of __getOwnPropNames(from))
|
|
16
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
17
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
18
|
+
}
|
|
19
|
+
return to;
|
|
20
|
+
};
|
|
21
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
22
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
23
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
24
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
25
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
26
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
27
|
+
mod
|
|
28
|
+
));
|
|
29
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
30
|
+
|
|
31
|
+
// src/react/index.tsx
|
|
32
|
+
var react_exports = {};
|
|
33
|
+
__export(react_exports, {
|
|
34
|
+
ApiKeyWidget: () => ApiKeyWidget,
|
|
35
|
+
default: () => react_default
|
|
36
|
+
});
|
|
37
|
+
module.exports = __toCommonJS(react_exports);
|
|
38
|
+
var React = __toESM(require("react"));
|
|
39
|
+
var import_jsx_runtime = require("react/jsx-runtime");
|
|
40
|
+
var DEFAULTS = {
|
|
41
|
+
accent: "#4f46e5",
|
|
42
|
+
accentText: "#ffffff",
|
|
43
|
+
background: "transparent",
|
|
44
|
+
surface: "#ffffff",
|
|
45
|
+
headerBackground: "",
|
|
46
|
+
text: "#111827",
|
|
47
|
+
muted: "#6b7280",
|
|
48
|
+
border: "#e5e7eb",
|
|
49
|
+
danger: "#dc2626",
|
|
50
|
+
success: "#16a34a",
|
|
51
|
+
radius: "12px",
|
|
52
|
+
fontFamily: "ui-sans-serif, system-ui, -apple-system, sans-serif",
|
|
53
|
+
monoFontFamily: "ui-monospace, SFMono-Regular, Menlo, Consolas, monospace"
|
|
54
|
+
};
|
|
55
|
+
function relTime(iso) {
|
|
56
|
+
if (!iso) return null;
|
|
57
|
+
const t = new Date(iso).getTime();
|
|
58
|
+
if (Number.isNaN(t)) return null;
|
|
59
|
+
const s = Math.floor((Date.now() - t) / 1e3);
|
|
60
|
+
if (s < 60) return "just now";
|
|
61
|
+
const m = Math.floor(s / 60);
|
|
62
|
+
if (m < 60) return `${m}m ago`;
|
|
63
|
+
const h = Math.floor(m / 60);
|
|
64
|
+
if (h < 24) return `${h}h ago`;
|
|
65
|
+
const d = Math.floor(h / 24);
|
|
66
|
+
if (d < 30) return `${d}d ago`;
|
|
67
|
+
return new Date(iso).toLocaleDateString();
|
|
68
|
+
}
|
|
69
|
+
var ApiKeyWidget = React.forwardRef(function ApiKeyWidget2({ endpoint = "/api/apiblaze/keys", theme, title = "Your API keys", className }, ref) {
|
|
70
|
+
const t = { ...DEFAULTS, ...theme ?? {} };
|
|
71
|
+
const headerBg = t.headerBackground || t.surface;
|
|
72
|
+
const [keys, setKeys] = React.useState(null);
|
|
73
|
+
const [eligible, setEligible] = React.useState([]);
|
|
74
|
+
const [access, setAccess] = React.useState("ok");
|
|
75
|
+
const [chosenType, setChosenType] = React.useState("");
|
|
76
|
+
const [loadError, setLoadError] = React.useState(null);
|
|
77
|
+
const [secret, setSecret] = React.useState(null);
|
|
78
|
+
const [creating, setCreating] = React.useState(false);
|
|
79
|
+
const [pending, setPending] = React.useState(null);
|
|
80
|
+
const [confirming, setConfirming] = React.useState(null);
|
|
81
|
+
const [rowError, setRowError] = React.useState(null);
|
|
82
|
+
const [copied, setCopied] = React.useState(false);
|
|
83
|
+
const call = React.useCallback(async (body) => {
|
|
84
|
+
const res = await fetch(endpoint, body ? { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(body) } : { cache: "no-store" });
|
|
85
|
+
const data = await res.json().catch(() => ({}));
|
|
86
|
+
if (!res.ok) throw new Error(data.error || `Error ${res.status}`);
|
|
87
|
+
return data;
|
|
88
|
+
}, [endpoint]);
|
|
89
|
+
const load = React.useCallback(async () => {
|
|
90
|
+
setLoadError(null);
|
|
91
|
+
try {
|
|
92
|
+
const d = await call();
|
|
93
|
+
setKeys((d.keys ?? []).slice().sort(byNewest));
|
|
94
|
+
setEligible(d.keyTypes ?? []);
|
|
95
|
+
setAccess(d.access === "denied" ? "denied" : "ok");
|
|
96
|
+
} catch (e) {
|
|
97
|
+
setLoadError(e instanceof Error ? e.message : "Could not load your keys.");
|
|
98
|
+
setKeys(null);
|
|
99
|
+
}
|
|
100
|
+
}, [call]);
|
|
101
|
+
React.useEffect(() => {
|
|
102
|
+
load();
|
|
103
|
+
}, [load]);
|
|
104
|
+
React.useImperativeHandle(ref, () => ({ refresh: load }), [load]);
|
|
105
|
+
React.useEffect(() => {
|
|
106
|
+
if (eligible.length && !eligible.includes(chosenType)) {
|
|
107
|
+
setChosenType(eligible.includes("call-only") ? "call-only" : eligible[0]);
|
|
108
|
+
}
|
|
109
|
+
}, [eligible, chosenType]);
|
|
110
|
+
async function create() {
|
|
111
|
+
setCreating(true);
|
|
112
|
+
setRowError(null);
|
|
113
|
+
try {
|
|
114
|
+
const d = await call({ action: "create", keyType: chosenType || void 0 });
|
|
115
|
+
if (d.key) setSecret({ key: d.key, keyId: d.key_id });
|
|
116
|
+
await load();
|
|
117
|
+
} catch (e) {
|
|
118
|
+
setRowError({ keyId: "", msg: e instanceof Error ? e.message : "Could not create a key." });
|
|
119
|
+
} finally {
|
|
120
|
+
setCreating(false);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
async function reveal(keyId) {
|
|
124
|
+
setPending(`reveal:${keyId}`);
|
|
125
|
+
setRowError(null);
|
|
126
|
+
try {
|
|
127
|
+
const d = await call({ action: "reveal", keyId });
|
|
128
|
+
if (d.key) setSecret({ key: d.key, keyId });
|
|
129
|
+
else setRowError({ keyId, msg: "For security, this key can only be shown once \u2014 right after it was created." });
|
|
130
|
+
} catch (e) {
|
|
131
|
+
setRowError({ keyId, msg: e instanceof Error ? e.message : "Could not show this key." });
|
|
132
|
+
} finally {
|
|
133
|
+
setPending(null);
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
async function rotate(keyId) {
|
|
137
|
+
setConfirming(null);
|
|
138
|
+
setPending(`rotate:${keyId}`);
|
|
139
|
+
setRowError(null);
|
|
140
|
+
try {
|
|
141
|
+
const d = await call({ action: "rotate", keyId });
|
|
142
|
+
if (d.key) setSecret({ key: d.key, keyId: d.key_id });
|
|
143
|
+
await load();
|
|
144
|
+
} catch (e) {
|
|
145
|
+
setRowError({ keyId, msg: e instanceof Error ? e.message : "Could not rotate this key." });
|
|
146
|
+
} finally {
|
|
147
|
+
setPending(null);
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
async function revoke(keyId) {
|
|
151
|
+
setConfirming(null);
|
|
152
|
+
setPending(`revoke:${keyId}`);
|
|
153
|
+
setRowError(null);
|
|
154
|
+
try {
|
|
155
|
+
await call({ action: "revoke", keyId });
|
|
156
|
+
await load();
|
|
157
|
+
} catch (e) {
|
|
158
|
+
setRowError({ keyId, msg: e instanceof Error ? e.message : "Could not revoke this key." });
|
|
159
|
+
} finally {
|
|
160
|
+
setPending(null);
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
async function copy(v) {
|
|
164
|
+
try {
|
|
165
|
+
await navigator.clipboard.writeText(v);
|
|
166
|
+
setCopied(true);
|
|
167
|
+
setTimeout(() => setCopied(false), 1600);
|
|
168
|
+
} catch {
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
const vars = {
|
|
172
|
+
// @ts-expect-error CSS custom properties
|
|
173
|
+
"--abz-accent": t.accent,
|
|
174
|
+
"--abz-surface": t.surface,
|
|
175
|
+
"--abz-text": t.text,
|
|
176
|
+
"--abz-muted": t.muted,
|
|
177
|
+
"--abz-border": t.border,
|
|
178
|
+
"--abz-danger": t.danger,
|
|
179
|
+
"--abz-radius": t.radius,
|
|
180
|
+
background: t.background,
|
|
181
|
+
color: t.text,
|
|
182
|
+
fontFamily: t.fontFamily,
|
|
183
|
+
fontSize: 14
|
|
184
|
+
};
|
|
185
|
+
const card = { background: t.surface, border: `1px solid ${t.border}`, borderRadius: t.radius, overflow: "hidden" };
|
|
186
|
+
const btn = (kind = "ghost", small = false) => ({
|
|
187
|
+
cursor: "pointer",
|
|
188
|
+
borderRadius: 8,
|
|
189
|
+
fontWeight: 600,
|
|
190
|
+
fontSize: small ? 12 : 13,
|
|
191
|
+
padding: small ? "5px 10px" : "8px 14px",
|
|
192
|
+
fontFamily: "inherit",
|
|
193
|
+
border: `1px solid ${kind === "primary" ? t.accent : kind === "danger" ? t.danger : t.border}`,
|
|
194
|
+
background: kind === "primary" ? t.accent : "transparent",
|
|
195
|
+
color: kind === "primary" ? t.accentText : kind === "danger" ? t.danger : t.text,
|
|
196
|
+
whiteSpace: "nowrap"
|
|
197
|
+
});
|
|
198
|
+
const selectStyle = {
|
|
199
|
+
fontFamily: "inherit",
|
|
200
|
+
fontSize: 12,
|
|
201
|
+
fontWeight: 600,
|
|
202
|
+
padding: "5px 8px",
|
|
203
|
+
borderRadius: 8,
|
|
204
|
+
border: `1px solid ${t.border}`,
|
|
205
|
+
background: t.surface,
|
|
206
|
+
color: t.text,
|
|
207
|
+
cursor: "pointer"
|
|
208
|
+
};
|
|
209
|
+
const disabledStyle = (on) => on ? { opacity: 0.5, cursor: "not-allowed" } : {};
|
|
210
|
+
const secretBg = `color-mix(in srgb, ${t.accent} 8%, transparent)`;
|
|
211
|
+
const busyAny = creating || pending !== null;
|
|
212
|
+
const denied = access === "denied";
|
|
213
|
+
const showPicker = eligible.length > 1;
|
|
214
|
+
const Picker = showPicker ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("select", { value: chosenType, onChange: (e) => setChosenType(e.target.value), style: selectStyle, "aria-label": "Key type", disabled: busyAny, children: eligible.map((k) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)("option", { value: k, children: k }, k)) }) : null;
|
|
215
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: `abz-widget ${className ?? ""}`, style: vars, children: [
|
|
216
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("style", { children: `.abz-widget button:focus-visible,.abz-widget select:focus-visible{outline:2px solid var(--abz-accent);outline-offset:2px}` }),
|
|
217
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: card, children: [
|
|
218
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: { display: "flex", justifyContent: "space-between", alignItems: "center", gap: 12, padding: "14px 16px", borderBottom: `1px solid ${t.border}`, background: headerBg }, children: [
|
|
219
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("strong", { style: { fontSize: 15 }, children: title }),
|
|
220
|
+
!denied && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: { display: "flex", gap: 8, alignItems: "center" }, children: [
|
|
221
|
+
Picker,
|
|
222
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
223
|
+
"button",
|
|
224
|
+
{
|
|
225
|
+
style: { ...btn("primary", true), ...disabledStyle(busyAny) },
|
|
226
|
+
disabled: busyAny,
|
|
227
|
+
onClick: create,
|
|
228
|
+
"aria-label": "Create a new API key",
|
|
229
|
+
children: creating ? "Creating\u2026" : "+ Create key"
|
|
230
|
+
}
|
|
231
|
+
)
|
|
232
|
+
] })
|
|
233
|
+
] }),
|
|
234
|
+
denied && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: { padding: "28px 20px", textAlign: "center", color: t.muted }, children: "Your account doesn\u2019t have API access." }),
|
|
235
|
+
!denied && secret && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { role: "status", "aria-live": "polite", style: { margin: 16, padding: 14, borderRadius: 10, border: `1px solid ${t.accent}`, background: secretBg }, children: [
|
|
236
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: { fontWeight: 600, marginBottom: 4 }, children: "New API key" }),
|
|
237
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: { fontSize: 12, color: t.muted, marginBottom: 8 }, children: "This is the only time you\u2019ll see this key. Copy it and store it somewhere safe." }),
|
|
238
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: { display: "flex", gap: 8, alignItems: "center" }, children: [
|
|
239
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
240
|
+
"input",
|
|
241
|
+
{
|
|
242
|
+
readOnly: true,
|
|
243
|
+
value: secret.key,
|
|
244
|
+
onFocus: (e) => e.currentTarget.select(),
|
|
245
|
+
"aria-label": "New API key value",
|
|
246
|
+
style: { flex: 1, fontFamily: t.monoFontFamily, fontSize: 13, padding: "8px 10px", borderRadius: 8, border: `1px solid ${t.border}`, background: t.surface, color: t.text, minWidth: 0 }
|
|
247
|
+
}
|
|
248
|
+
),
|
|
249
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { style: { ...btn("primary"), ...copied ? { background: t.success, borderColor: t.success } : {} }, onClick: () => copy(secret.key), children: copied ? "Copied" : "Copy" })
|
|
250
|
+
] }),
|
|
251
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: { marginTop: 10, textAlign: "right" }, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { style: btn("ghost", true), onClick: () => setSecret(null), children: "I\u2019ve saved it" }) })
|
|
252
|
+
] }),
|
|
253
|
+
!denied && keys === null && !loadError && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { "aria-busy": "true", style: { padding: 24, color: t.muted }, children: "Loading your keys\u2026" }),
|
|
254
|
+
!denied && loadError && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { role: "alert", style: { padding: 20, color: t.danger, fontSize: 13 }, children: [
|
|
255
|
+
loadError,
|
|
256
|
+
" ",
|
|
257
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { style: { ...btn("ghost", true), marginLeft: 6 }, onClick: load, children: "Retry" })
|
|
258
|
+
] }),
|
|
259
|
+
!denied && keys !== null && keys.length === 0 && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: { padding: "28px 20px", textAlign: "center" }, children: [
|
|
260
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: { color: t.muted, marginBottom: 14 }, children: "You don\u2019t have an API key yet. Create one to start calling the API." }),
|
|
261
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: { display: "inline-flex", gap: 8, alignItems: "center" }, children: [
|
|
262
|
+
Picker,
|
|
263
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { style: { ...btn("primary"), ...disabledStyle(creating) }, disabled: creating, onClick: create, children: creating ? "Creating\u2026" : "Create API key" })
|
|
264
|
+
] })
|
|
265
|
+
] }),
|
|
266
|
+
!denied && keys !== null && keys.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("ul", { style: { listStyle: "none", margin: 0, padding: 0 }, children: keys.map((k) => {
|
|
267
|
+
const anyBusy = busyAny;
|
|
268
|
+
const used = relTime(k.last_used);
|
|
269
|
+
const created = k.created_at ? new Date(k.created_at).toLocaleDateString() : null;
|
|
270
|
+
const isConfirm = confirming?.keyId === k.key_id;
|
|
271
|
+
const showable = !!k.expires_at;
|
|
272
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("li", { style: { padding: "12px 16px", borderTop: `1px solid ${t.border}`, opacity: k.disabled ? 0.6 : 1 }, children: [
|
|
273
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: { display: "flex", gap: 10, alignItems: "center", flexWrap: "wrap" }, children: [
|
|
274
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("code", { "aria-label": `API key ending in ${k.key_suffix ?? ""}`, style: { flex: 1, minWidth: 150, fontFamily: t.monoFontFamily, color: t.muted }, children: [
|
|
275
|
+
k.key_prefix ?? "\u2022\u2022\u2022",
|
|
276
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { "aria-hidden": "true", children: "\u2022\u2022\u2022\u2022\u2022\u2022" }),
|
|
277
|
+
k.key_suffix ?? "\u2022\u2022\u2022"
|
|
278
|
+
] }),
|
|
279
|
+
k.environment && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { style: { fontSize: 11, padding: "2px 7px", borderRadius: 999, border: `1px solid ${t.border}`, color: t.muted }, children: k.environment }),
|
|
280
|
+
k.disabled ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { style: { fontSize: 11, fontWeight: 600, color: t.danger }, children: "Revoked" }) : isConfirm ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: { display: "flex", gap: 8, alignItems: "center", flexBasis: "100%", flexWrap: "wrap", marginTop: 6 }, children: [
|
|
281
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { style: { fontSize: 12, color: t.text, flex: 1, minWidth: 200 }, children: confirming.action === "rotate" ? "Rotate this key? A new secret is generated now. The current key keeps working for a short grace period, then stops \u2014 anything still using it will break." : "Revoke this key? It stops working immediately and can\u2019t be undone." }),
|
|
282
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { style: btn(confirming.action === "revoke" ? "danger" : "primary", true), onClick: () => confirming.action === "rotate" ? rotate(k.key_id) : revoke(k.key_id), children: confirming.action === "rotate" ? "Rotate key" : "Revoke key" }),
|
|
283
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { style: btn("ghost", true), onClick: () => setConfirming(null), children: "Cancel" })
|
|
284
|
+
] }) : /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: { display: "flex", gap: 8 }, children: [
|
|
285
|
+
showable && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
286
|
+
"button",
|
|
287
|
+
{
|
|
288
|
+
style: { ...btn("ghost", true), ...disabledStyle(anyBusy) },
|
|
289
|
+
disabled: anyBusy,
|
|
290
|
+
"aria-label": `Show key ending in ${k.key_suffix ?? ""}`,
|
|
291
|
+
onClick: () => reveal(k.key_id),
|
|
292
|
+
children: pending === `reveal:${k.key_id}` ? "Showing\u2026" : "Show"
|
|
293
|
+
}
|
|
294
|
+
),
|
|
295
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
296
|
+
"button",
|
|
297
|
+
{
|
|
298
|
+
style: { ...btn("ghost", true), ...disabledStyle(anyBusy) },
|
|
299
|
+
disabled: anyBusy,
|
|
300
|
+
"aria-label": `Rotate key ending in ${k.key_suffix ?? ""}`,
|
|
301
|
+
onClick: () => setConfirming({ keyId: k.key_id, action: "rotate" }),
|
|
302
|
+
children: pending === `rotate:${k.key_id}` ? "Rotating\u2026" : "Rotate"
|
|
303
|
+
}
|
|
304
|
+
),
|
|
305
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
306
|
+
"button",
|
|
307
|
+
{
|
|
308
|
+
style: { ...btn("danger", true), ...disabledStyle(anyBusy) },
|
|
309
|
+
disabled: anyBusy,
|
|
310
|
+
"aria-label": `Revoke key ending in ${k.key_suffix ?? ""}`,
|
|
311
|
+
onClick: () => setConfirming({ keyId: k.key_id, action: "revoke" }),
|
|
312
|
+
children: "Revoke"
|
|
313
|
+
}
|
|
314
|
+
)
|
|
315
|
+
] })
|
|
316
|
+
] }),
|
|
317
|
+
(created || used) && !isConfirm && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: { fontSize: 11, color: t.muted, marginTop: 5 }, children: [
|
|
318
|
+
created ? `Created ${created}` : "",
|
|
319
|
+
created && used ? " \xB7 " : "",
|
|
320
|
+
used ? `Last used ${used}` : created ? " \xB7 Never used" : "Never used"
|
|
321
|
+
] }),
|
|
322
|
+
rowError?.keyId === k.key_id && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { role: "alert", style: { fontSize: 12, color: t.danger, marginTop: 6 }, children: rowError.msg })
|
|
323
|
+
] }, k.key_id);
|
|
324
|
+
}) }),
|
|
325
|
+
!denied && rowError && rowError.keyId === "" && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { role: "alert", style: { padding: "0 16px 14px", fontSize: 12, color: t.danger }, children: rowError.msg })
|
|
326
|
+
] })
|
|
327
|
+
] });
|
|
328
|
+
});
|
|
329
|
+
function byNewest(a, b) {
|
|
330
|
+
return new Date(b.created_at ?? 0).getTime() - new Date(a.created_at ?? 0).getTime();
|
|
331
|
+
}
|
|
332
|
+
var react_default = ApiKeyWidget;
|
|
333
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
334
|
+
0 && (module.exports = {
|
|
335
|
+
ApiKeyWidget
|
|
336
|
+
});
|