File size: 3,950 Bytes
cd8bd0a
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
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
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
export type KeyStatus = "active" | "disabled" | "banned" | "expired";

// "manage" scope = management key; "restricted" = has model/connection allowlists;
// "standard" = no manage scope and no allowlists.
// Note: a "manage" key with allowlists is still classified as "manage" (manage takes priority).
export type KeyType = "standard" | "manage" | "restricted";

export interface ApiKeyShape {
  isActive?: boolean;
  isBanned?: boolean;
  expiresAt?: string | null;
  scopes?: string[];
  allowedModels?: string[] | null;
  allowedConnections?: string[] | null;
}

export function isKeyActive(k: ApiKeyShape): boolean {
  if (k.isBanned === true) return false;
  if (k.isActive === false) return false;
  if (k.expiresAt) {
    return new Date(k.expiresAt).getTime() > Date.now();
  }
  return true;
}

export function isExpired(k: ApiKeyShape): boolean {
  if (!k.expiresAt) return false;
  const ts = new Date(k.expiresAt).getTime();
  if (Number.isNaN(ts)) return false;
  return ts < Date.now();
}

export function isRestricted(k: ApiKeyShape): boolean {
  const hasModelRestrictions = Array.isArray(k.allowedModels) && k.allowedModels.length > 0;
  const hasConnectionRestrictions =
    Array.isArray(k.allowedConnections) && k.allowedConnections.length > 0;
  return hasModelRestrictions || hasConnectionRestrictions;
}

export function classifyKeyStatus(k: ApiKeyShape): KeyStatus {
  if (k.isBanned === true) return "banned";
  if (isExpired(k)) return "expired";
  if (k.isActive === false) return "disabled";
  return "active";
}

export function classifyKeyType(k: ApiKeyShape): KeyType {
  if (Array.isArray(k.scopes) && k.scopes.includes("manage")) return "manage";
  if (isRestricted(k)) return "restricted";
  return "standard";
}

export interface ApiKeyCounts {
  total: number;
  active: number;
  disabled: number;
  banned: number;
  expired: number;
  standard: number;
  manage: number;
  restricted: number;
}

export function computeApiKeyCounts(keys: ApiKeyShape[]): ApiKeyCounts {
  const counts: ApiKeyCounts = {
    total: keys.length,
    active: 0,
    disabled: 0,
    banned: 0,
    expired: 0,
    standard: 0,
    manage: 0,
    restricted: 0,
  };

  for (const k of keys) {
    const status = classifyKeyStatus(k);
    counts[status] += 1;

    const type = classifyKeyType(k);
    counts[type] += 1;
  }

  return counts;
}

export function toLocalDateTimeInputValue(value: string | null | undefined): string {
  if (!value) return "";
  const date = new Date(value);
  if (Number.isNaN(date.getTime())) return "";
  const pad = (n: number) => String(n).padStart(2, "0");
  return `${date.getFullYear()}-${pad(date.getMonth() + 1)}-${pad(date.getDate())}T${pad(
    date.getHours()
  )}:${pad(date.getMinutes())}`;
}

export function formatUsdCost(value: number, locale: string): string {
  const amount = Number.isFinite(value) ? value : 0;
  return new Intl.NumberFormat(locale, {
    style: "currency",
    currency: "USD",
    minimumFractionDigits: amount > 0 && amount < 1 ? 4 : 2,
    maximumFractionDigits: amount > 0 && amount < 1 ? 4 : 2,
  }).format(amount);
}

/**
 * Mask a fully revealed API key for the at-rest display: keep the first 8 chars
 * (provider prefix + a few entropy bits, e.g. `sk-or-12...`), append an ellipsis.
 * Returns "" for empty/missing input so the UI can render an empty `<code>` cleanly.
 */
export function maskKey(fullKey: string | null | undefined): string {
  if (!fullKey) return "";
  if (fullKey.includes("****")) return fullKey;
  return fullKey.length > 8 ? `${fullKey.slice(0, 8)}...` : fullKey;
}

/**
 * Immutable Set toggle helper for the "which keys are currently revealed" state.
 * Returns a NEW Set so React state setters always see a fresh reference.
 */
export function toggleKeyVisibility(prev: Set<string>, keyId: string): Set<string> {
  const next = new Set(prev);
  if (next.has(keyId)) next.delete(keyId);
  else next.add(keyId);
  return next;
}