@gntik-ai/templates 0.8.0 → 0.10.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +13 -5
- package/dist/index.js +136 -103
- package/dist/index.js.map +1 -1
- package/package.json +5 -5
- package/src/audit-log/Page.test.tsx +53 -0
- package/src/audit-log/Page.tsx +13 -5
- package/src/audit-log/template.meta.ts +1 -1
- package/src/help-home/Page.tsx +3 -1
- package/src/help-home/data.ts +3 -4
- package/src/incident-board/Page.tsx +6 -1
- package/src/incident-board/data.ts +9 -3
- package/src/index.ts +1 -1
- package/src/keyboard-shortcuts/Page.test.tsx +39 -1
- package/src/keyboard-shortcuts/ShortcutSheet.tsx +4 -1
- package/src/keyboard-shortcuts/data.ts +29 -14
- package/src/live-event-stream/Page.test.tsx +8 -4
- package/src/resource-detail/Page.labels.test.tsx +67 -0
- package/src/resource-detail/Page.tsx +18 -7
- package/src/resource-detail/examples/Labels.tsx +18 -0
- package/src/resource-detail/examples/Spanish.tsx +11 -0
- package/src/resource-detail/template.meta.ts +1 -1
- package/src/resource-gallery/Page.tsx +3 -1
- package/src/resource-gallery/data.ts +3 -4
- package/src/resource-index/Page.tsx +3 -1
- package/src/resource-index/data.ts +7 -4
- package/src/search-results/Page.tsx +5 -5
- package/src/settings-profile/fixtures/editable.snap +1 -1
- package/src/shared/ConsoleShell.test.tsx +13 -0
- package/src/shared/ConsoleShell.tsx +35 -30
- package/src/test/match-text.test.ts +51 -0
- package/src/usage-analytics/Page.tsx +3 -1
- package/src/usage-analytics/data.ts +9 -3
- package/src/workflow-builder/Page.test.tsx +103 -2
- package/src/workflow-builder/Page.tsx +34 -8
- package/src/workflow-builder/examples/SaveGraph.tsx +17 -0
- package/src/workflow-builder/template.meta.ts +1 -1
package/dist/index.js
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
'use client';
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
2
|
+
import { ChartLocaleProvider, Heatmap, MiniBar, RadarChart, chartFmt } from "@gntik-ai/charts";
|
|
3
|
+
import { ActivityFeed, AppSidebarFooter, AppSidebarHeader, AppSidebarNav, AppTopbar, AuthHeader, BulkActionBar, ChartCard, CodePanel, CommentThread, ConfirmDestructive, CreateApiKeyDialog, DEPLOYMENT_COLUMNS, DEPLOYMENT_DETAILS, DEPLOYMENT_EVENTS, DEPLOYMENT_ROWS, DangerZone, DataTable, DateRangeFilter, DescriptionListCard, ErrorPanel, EvaluationScorecard, Faq, FeatureGrid, FileUploadPanel, FilterBar, FirstRunEmpty, FlowBuilder, ForgotPasswordForm, FormSection, Hero, InlineCallout, InviteMembersDialog, InvoiceTable, KPI_ITEMS, KpiRow, ListSkeleton, LogViewer, MembersTable, MfaChallenge, ModelKeysList, NoResultsEmpty, NotificationMatrix, PROJECT_ACTIVITY, PageHeader, PageToolbar, PaymentMethodCard, PendingInvitations, PlanCard, PricingTable, ProfileHeader, PromptEditor, QuotaMeters, RESOURCE_CARDS, ResetPasswordForm, ResourceCardGrid, SectionHeader, SessionsDevices, SettingsRow, SignInForm, SignUpForm, SsoButtons, StatusTimeline, StickyActionBar, SystemBanner, TokenCostCard, TraceWaterfall, TrendDelta, ValidationSummary, apiKeyExpiries, apiKeyScopes, appSidebarNavGroups, appSidebarUser, appSidebarUser as appSidebarUser$1, appSidebarWorkspaces, appTopbarNotifications, builderEdges, builderNodes, cleanFlowGraph, firstRunSteps, formatSpanDuration, generateDemoSecret, memberRoles, noAccessSample, notificationChannels, notificationDefaults, notificationEvents, sampleDeviceSessions, sampleInvitations, sampleInvoices, sampleMembers, samplePaymentMethod, samplePlan, samplePricingTiers, sampleProfile, sampleQuotas, ssoProviders, ssoProviders as ssoProviders$1 } from "@gntik-ai/blocks";
|
|
4
|
+
import { Alert, AuthLayout, Avatar, AvatarGroup, Badge, Blockquote, Breadcrumbs, Button, CanvasLayout, Card, CardBody, CardDescription, CardHeader, CardTitle, Checkbox, CheckboxGroup, ClickableCard, CodeBlock, ContextMenu, ContextMenuContent, ContextMenuTrigger, Dialog, DialogBody, DialogClose, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, DialogTrigger, DocsLayout, Drawer, DrawerBody, DrawerContent, DrawerDescription, DrawerHeader, DrawerTitle, EmptyState, Field, FieldDescription, FieldError, FieldLabel, Grid, GridItem, HStack, Heading, IconButton, Input, InspectorLayout, InspectorToggle, JsonViewer, KanbanBoard, Kbd, KbdCombo, Link, ListInput, LiveAnnouncer, Logo, MegaMenu, MenuItem, MenuSeparator, Meter, MobileNav, NavList, OtpInput, OverflowList, Page, PasswordInput, PowerSearch, Progress, Section, Select, SelectContent, SelectItem, SelectTrigger, SelectableCard, SelectableCardGroup, Separator, SettingsLayout, SidebarLayout, SimpleSelect, Skeleton, Spinner, SplitLayout, Stack, StackedLayout, StatusDot, StatusLayout, StatusTag, Switch, Table, TableBody, TableCaption, TableCell, TableHead, TableHeader, TableRow, Tabs, TabsList, TabsPanel, TabsTab, Text, Textarea, ThemeSwitcher, Thumbnail, ThumbnailList, Timer, Timestamp, Toggle, ToggleGroup, TokenInput, TooltipProvider, TransferList, TreeList, VisuallyHidden, WizardLayout, cn, emptyPowerSearchQuery, formatPowerSearch, matchPowerSearch, matchText, megaMenuNavItems, moveKanbanCard, parsePowerSearch, useAnnounce, useHasLiveAnnouncer, useI18n, useInspectorLayout, useOs, useTheme } from "@gntik-ai/ui";
|
|
4
5
|
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
5
6
|
import { Fragment as Fragment$1, createElement, useEffect, useEffectEvent, useId, useLayoutEffect, useMemo, useRef, useState } from "react";
|
|
6
7
|
import { ChatAnnouncer, ChatComposer, ChatLayout, ChatMessage, CitationList, Markdown, SuggestionChips, ToolCallCard } from "@gntik-ai/chat";
|
|
7
8
|
import { Archive, ArrowLeft, ArrowRight, BarChart3, Bell, BellOff, BellRing, BookOpen, Braces, Building2, CalendarClock, Check, CheckCheck, CircleCheck, Clock, Code2, Columns3, Copy, Cpu, CreditCard, Database, Download, ExternalLink, FileCode, FileDiff, FileJson, FileQuestion, FileSearch, FileText, Folder, FolderKanban, FolderOpen, GitBranch, Globe, GripVertical, Hand, HardDrive, Home, KeyRound, Keyboard, LayoutGrid, LayoutTemplate, LifeBuoy, ListOrdered, ListTodo, Lock, Mail, MailOpen, MapPin, MessageSquare, MessagesSquare, Minus, Palette, Phone, Play, PlayCircle, Plug, Plus, RefreshCw, Rocket, RotateCcw, RotateCw, Rss, Save, Search, SearchX, Send, Server, ServerCrash, Shield, ShieldCheck, Square, SquarePen, Table2, ThumbsDown, ThumbsUp, Trash2, Upload, User, UserPlus, Users, Wrench, X } from "@gntik-ai/icons";
|
|
8
|
-
import { Heatmap, MiniBar, RadarChart, chartFmt } from "@gntik-ai/charts";
|
|
9
9
|
import { DiffEditor } from "@gntik-ai/editor";
|
|
10
10
|
import { flushSync } from "react-dom";
|
|
11
11
|
//#region src/shared/ConsoleShell.tsx
|
|
@@ -16,38 +16,42 @@ import { flushSync } from "react-dom";
|
|
|
16
16
|
* link and user-menu items.
|
|
17
17
|
*/
|
|
18
18
|
function ConsoleShell({ children, nav, currentHref, breadcrumbs, workspaces, currentWorkspaceId, onWorkspaceChange, onCreateWorkspace, user, usage, helpHref, helpLabel, userMenuItems, onSignOut, topbar, storageKey, sidebarLabel, collapseMode }) {
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
collapsed
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
19
|
+
const { locale } = useI18n();
|
|
20
|
+
return /* @__PURE__ */ jsx(ChartLocaleProvider, {
|
|
21
|
+
locale,
|
|
22
|
+
children: /* @__PURE__ */ jsx(SidebarLayout, {
|
|
23
|
+
fullScreen: true,
|
|
24
|
+
storageKey,
|
|
25
|
+
sidebarLabel,
|
|
26
|
+
collapseMode,
|
|
27
|
+
sidebarHeader: ({ collapsed }) => /* @__PURE__ */ jsx(AppSidebarHeader, {
|
|
28
|
+
collapsed,
|
|
29
|
+
workspaces,
|
|
30
|
+
currentWorkspaceId,
|
|
31
|
+
onWorkspaceChange,
|
|
32
|
+
onCreateWorkspace
|
|
33
|
+
}),
|
|
34
|
+
sidebar: ({ collapsed }) => /* @__PURE__ */ jsx(AppSidebarNav, {
|
|
35
|
+
groups: nav,
|
|
36
|
+
currentHref,
|
|
37
|
+
collapsed
|
|
38
|
+
}),
|
|
39
|
+
sidebarFooter: ({ collapsed }) => /* @__PURE__ */ jsx(AppSidebarFooter, {
|
|
40
|
+
collapsed,
|
|
41
|
+
user,
|
|
42
|
+
usage,
|
|
43
|
+
helpHref,
|
|
44
|
+
helpLabel,
|
|
45
|
+
userMenuItems,
|
|
46
|
+
onSignOut
|
|
47
|
+
}),
|
|
48
|
+
topbar: /* @__PURE__ */ jsx(AppTopbar, {
|
|
49
|
+
breadcrumbs,
|
|
50
|
+
user,
|
|
51
|
+
...topbar
|
|
52
|
+
}),
|
|
53
|
+
children
|
|
54
|
+
})
|
|
51
55
|
});
|
|
52
56
|
}
|
|
53
57
|
//#endregion
|
|
@@ -721,8 +725,8 @@ const DAY$1 = 864e5;
|
|
|
721
725
|
const startOfDay = (d) => new Date(d.getFullYear(), d.getMonth(), d.getDate());
|
|
722
726
|
const fieldValue$1 = (e, field) => field === "actor" ? e.actor.name : field === "resource" ? e.resource : field === "result" ? e.result : "";
|
|
723
727
|
/** Applies the search text, the filter chips (OR within a field, AND across fields) and the date range. */
|
|
724
|
-
function filterAuditEvents(events, query, filters, range) {
|
|
725
|
-
const q = query.trim()
|
|
728
|
+
function filterAuditEvents(events, query, filters, range, locale) {
|
|
729
|
+
const q = query.trim();
|
|
726
730
|
const fields = [...new Set(filters.map((f) => f.field))];
|
|
727
731
|
const from = range.start ? startOfDay(range.start).getTime() : -Infinity;
|
|
728
732
|
const to = range.end ? startOfDay(range.end).getTime() + DAY$1 : Infinity;
|
|
@@ -732,13 +736,17 @@ function filterAuditEvents(events, query, filters, range) {
|
|
|
732
736
|
e.actor.email,
|
|
733
737
|
e.action,
|
|
734
738
|
e.target
|
|
735
|
-
].some((v) => v
|
|
739
|
+
].some((v) => matchText(v, q, { locale }))) return false;
|
|
736
740
|
if (!fields.every((field) => filters.some((f) => f.field === field && f.value === fieldValue$1(e, field)))) return false;
|
|
737
741
|
const t = e.at.getTime();
|
|
738
742
|
return t >= from && t < to;
|
|
739
743
|
});
|
|
740
744
|
}
|
|
741
|
-
|
|
745
|
+
/** Neutralises leading spreadsheet formula triggers before applying CSV quoting. */
|
|
746
|
+
function escapeCsvCell(value) {
|
|
747
|
+
const safe = /^[=+@\t\r-]/.test(value) ? `'${value}` : value;
|
|
748
|
+
return /[",\n]/.test(safe) ? `"${safe.replace(/"/g, "\"\"")}"` : safe;
|
|
749
|
+
}
|
|
742
750
|
/** CSV of the events (time, actor, action, resource, target, ip, result). */
|
|
743
751
|
function auditEventsToCsv(events) {
|
|
744
752
|
return [[
|
|
@@ -759,7 +767,7 @@ function auditEventsToCsv(events) {
|
|
|
759
767
|
e.target,
|
|
760
768
|
e.ip,
|
|
761
769
|
e.result
|
|
762
|
-
])].map((r) => r.map(
|
|
770
|
+
])].map((r) => r.map(escapeCsvCell).join(",")).join("\n");
|
|
763
771
|
}
|
|
764
772
|
function downloadCsv(events) {
|
|
765
773
|
if (typeof URL.createObjectURL !== "function") return;
|
|
@@ -772,6 +780,7 @@ function downloadCsv(events) {
|
|
|
772
780
|
}
|
|
773
781
|
/** Audit log: ConsoleShell + Page with FilterBar + DateRangeFilter, an events DataTable and a diff drawer. */
|
|
774
782
|
function AuditLogPage(props) {
|
|
783
|
+
const { locale } = useI18n();
|
|
775
784
|
const { events = auditEvents, filterFields = auditFilterFields, defaultRange, today, onExport = downloadCsv, breadcrumbs = auditBreadcrumbs, currentHref = "/activity", title = "Audit log", description = "Every change made in this workspace: who did it, when, from where and what changed.", shell } = props;
|
|
776
785
|
const [query, setQuery] = useState("");
|
|
777
786
|
const [filters, setFilters] = useState([]);
|
|
@@ -784,7 +793,7 @@ function AuditLogPage(props) {
|
|
|
784
793
|
};
|
|
785
794
|
});
|
|
786
795
|
const [diffEvent, setDiffEvent] = useState(null);
|
|
787
|
-
const rows = filterAuditEvents(events, query, filters, range);
|
|
796
|
+
const rows = filterAuditEvents(events, query, filters, range, locale);
|
|
788
797
|
const columns = [
|
|
789
798
|
{
|
|
790
799
|
id: "at",
|
|
@@ -973,7 +982,7 @@ const meta$2 = {
|
|
|
973
982
|
family: "Work",
|
|
974
983
|
priority: "P2",
|
|
975
984
|
status: "beta",
|
|
976
|
-
description: "Workspace audit trail: FilterBar (search, actor / resource / result filters) and DateRangeFilter over a DataTable of events with actor, action, target and result; a drawer shows the change diff (CodeBlock); export to CSV.",
|
|
985
|
+
description: "Workspace audit trail: FilterBar (search, actor / resource / result filters) and DateRangeFilter over a DataTable of events with actor, action, target and result; a drawer shows the change diff (CodeBlock); export to CSV. CSV export neutralises leading spreadsheet formula triggers.",
|
|
977
986
|
layout: "SidebarLayout · Page",
|
|
978
987
|
blocks: [
|
|
979
988
|
"app-sidebar",
|
|
@@ -1933,15 +1942,24 @@ const flowBuilderBreadcrumbs = [{
|
|
|
1933
1942
|
* '@gntik-ai/flow/styles.css' once for the React Flow canvas.
|
|
1934
1943
|
*/
|
|
1935
1944
|
function FlowBuilderPage(props) {
|
|
1936
|
-
const { title = flowBuilderContent.title, status = flowBuilderContent.status, nodes, edges, palette, onRun, consoleEntries, defaultConsoleEntries, onConsoleChange, onConsoleClear, onSave, onPublish, nodeTypes, onConnectEdge, onConnect, isValidConnection, onConnectionRejected, validationIssues, mode, nodeStatus, onNodeSelect, codePanel, breadcrumbs = flowBuilderBreadcrumbs, height = 640, fullScreen = true } = props;
|
|
1945
|
+
const { title = flowBuilderContent.title, status = flowBuilderContent.status, nodes = builderNodes, edges = builderEdges, palette, onRun, consoleEntries, defaultConsoleEntries, onConsoleChange, onConsoleClear, onSave, onPublish, nodeTypes, onConnectEdge, onConnect, isValidConnection, onConnectionRejected, validationIssues, mode, nodeStatus, onNodeSelect, graph, onGraphChange, codePanel, breadcrumbs = flowBuilderBreadcrumbs, height = 640, fullScreen = true } = props;
|
|
1937
1946
|
const [saving, setSaving] = useState(false);
|
|
1938
|
-
const [
|
|
1947
|
+
const [initialGraph] = useState(() => ({
|
|
1948
|
+
nodes,
|
|
1949
|
+
edges
|
|
1950
|
+
}));
|
|
1951
|
+
const [latestGraph, setLatestGraph] = useState();
|
|
1952
|
+
const [saveState, setSaveState] = useState("initial");
|
|
1953
|
+
const revision = useRef(0);
|
|
1954
|
+
const { t } = useI18n();
|
|
1955
|
+
const savedNote = saveState === "initial" ? flowBuilderContent.lastSaved : t(saveState === "unsaved" ? "actionBar.unsaved" : "actionBar.saved");
|
|
1939
1956
|
const save = async () => {
|
|
1940
1957
|
setSaving(true);
|
|
1958
|
+
const savedRevision = revision.current;
|
|
1941
1959
|
try {
|
|
1942
|
-
await onSave?.();
|
|
1943
|
-
|
|
1944
|
-
} finally {
|
|
1960
|
+
await onSave?.(cleanFlowGraph(graph ?? latestGraph ?? initialGraph));
|
|
1961
|
+
if (revision.current === savedRevision) setSaveState("saved");
|
|
1962
|
+
} catch {} finally {
|
|
1945
1963
|
setSaving(false);
|
|
1946
1964
|
}
|
|
1947
1965
|
};
|
|
@@ -1989,6 +2007,13 @@ function FlowBuilderPage(props) {
|
|
|
1989
2007
|
title,
|
|
1990
2008
|
nodes,
|
|
1991
2009
|
edges,
|
|
2010
|
+
graph,
|
|
2011
|
+
onGraphChange: (next, change) => {
|
|
2012
|
+
revision.current += 1;
|
|
2013
|
+
setLatestGraph(next);
|
|
2014
|
+
setSaveState("unsaved");
|
|
2015
|
+
onGraphChange?.(next, change);
|
|
2016
|
+
},
|
|
1992
2017
|
palette,
|
|
1993
2018
|
onRun,
|
|
1994
2019
|
consoleEntries,
|
|
@@ -2020,7 +2045,7 @@ const meta$60 = {
|
|
|
2020
2045
|
family: "Builders",
|
|
2021
2046
|
priority: "P3",
|
|
2022
2047
|
status: "experimental",
|
|
2023
|
-
description: "Full-screen workflow editor with optional nodeTypes, onConnectEdge, onConnect, isValidConnection, onConnectionRejected, validationIssues, mode=\"run\", nodeStatus and onNodeSelect passed to FlowBuilder; codePanel passes readOnly, markers, onValidityChange, onMonacoReady and loader to an optional CodePanel (branch, parallel, sequence and worker-backed YAML examples). The app must import '@gntik-ai/flow/styles.css' once for the canvas styles. Optional nodeTypes, onConnectEdge, onConnect, isValidConnection, onConnectionRejected, validationIssues, mode, nodeStatus and onNodeSelect pass to FlowBuilder unchanged (see its branch, parallel and sequence examples). mode=\"run\" hides Save, Publish and the saved notice, and preserves focus and node selection. Optional codePanel renders a companion CodePanel and passes all its extensions: readOnly, markers, onValidityChange, onMonacoReady and loader; run mode forces its readOnly to true. For example, WorkflowBuilderPage mode=\"run\" nodeStatus={{step:\"running\"}} onNodeSelect={openDetails}; codePanel={{files,problems:[],loader:yamlLoader,markers}} uses consumer-owned same-origin YAML workers and inline schemas.",
|
|
2048
|
+
description: "onSave(graph) receives the graph on screen (controlled graph, latest reported edit, or initial starter graph), without selected, dragging or measured node fields. Zero-argument handlers remain valid. Optional graph and onGraphChange pass through to FlowBuilder. Committed edits show actionBar.unsaved, a resolved save shows actionBar.saved, and a rejected save retains unsaved status and releases loading. Edits during a pending save remain unsaved. examples/SaveGraph.tsx shows a controlled graph and onSave={(graph) => localStorage.setItem(\"workflow-builder-example\", JSON.stringify(graph))}; hosts own persistence. Full-screen workflow editor with optional nodeTypes, onConnectEdge, onConnect, isValidConnection, onConnectionRejected, validationIssues, mode=\"run\", nodeStatus and onNodeSelect passed to FlowBuilder; codePanel passes readOnly, markers, onValidityChange, onMonacoReady and loader to an optional CodePanel (branch, parallel, sequence and worker-backed YAML examples). The app must import '@gntik-ai/flow/styles.css' once for the canvas styles. Optional nodeTypes, onConnectEdge, onConnect, isValidConnection, onConnectionRejected, validationIssues, mode, nodeStatus and onNodeSelect pass to FlowBuilder unchanged (see its branch, parallel and sequence examples). mode=\"run\" hides Save, Publish and the saved notice, and preserves focus and node selection. Optional codePanel renders a companion CodePanel and passes all its extensions: readOnly, markers, onValidityChange, onMonacoReady and loader; run mode forces its readOnly to true. For example, WorkflowBuilderPage mode=\"run\" nodeStatus={{step:\"running\"}} onNodeSelect={openDetails}; codePanel={{files,problems:[],loader:yamlLoader,markers}} uses consumer-owned same-origin YAML workers and inline schemas.",
|
|
2024
2049
|
layout: "CanvasLayout",
|
|
2025
2050
|
blocks: ["flow-builder", "code-panel"]
|
|
2026
2051
|
};
|
|
@@ -3029,23 +3054,15 @@ const detailDangerActions = [{
|
|
|
3029
3054
|
//#endregion
|
|
3030
3055
|
//#region src/resource-detail/Page.tsx
|
|
3031
3056
|
const TABS = [
|
|
3032
|
-
|
|
3033
|
-
|
|
3034
|
-
|
|
3035
|
-
},
|
|
3036
|
-
{
|
|
3037
|
-
value: "activity",
|
|
3038
|
-
label: "Activity"
|
|
3039
|
-
},
|
|
3040
|
-
{
|
|
3041
|
-
value: "settings",
|
|
3042
|
-
label: "Settings"
|
|
3043
|
-
}
|
|
3057
|
+
"overview",
|
|
3058
|
+
"activity",
|
|
3059
|
+
"settings"
|
|
3044
3060
|
];
|
|
3045
3061
|
/** Resource detail: editable tabs by default, or explicit-only Overview when readOnly is true. */
|
|
3046
3062
|
function ResourceDetailPage(props) {
|
|
3063
|
+
const { t } = useI18n();
|
|
3047
3064
|
const readOnly = props.readOnly ?? false;
|
|
3048
|
-
const { name = readOnly ? "" : detailResource.name, description = readOnly ? "" : detailResource.description, status = readOnly ? "" : detailResource.status, breadcrumbs = readOnly ? [] : detailBreadcrumbs, meta: metaProp, actions: actionsProp, details: detailsProp, overviewTitle = "Configuration", overviewDescription = readOnly ? "" : "Settings of this resource.", historyTitle = "Status history", events = readOnly ? [] : detailEvents, activity = readOnly ? [] : detailActivity, dangerActions = readOnly ? [] : detailDangerActions, defaultTab = "overview", onTabChange, onEditDetails, onSaveSettings, onDangerAction, shell } = props;
|
|
3065
|
+
const { name = readOnly ? "" : detailResource.name, description = readOnly ? "" : detailResource.description, status = readOnly ? "" : detailResource.status, breadcrumbs = readOnly ? [] : detailBreadcrumbs, meta: metaProp, actions: actionsProp, details: detailsProp, overviewTitle = "Configuration", overviewDescription = readOnly ? "" : "Settings of this resource.", historyTitle = "Status history", events = readOnly ? [] : detailEvents, activity = readOnly ? [] : detailActivity, dangerActions = readOnly ? [] : detailDangerActions, defaultTab = "overview", onTabChange, onEditDetails, onSaveSettings, onDangerAction, shell, labels } = props;
|
|
3049
3066
|
const meta = metaProp ?? (readOnly ? [] : detailMeta);
|
|
3050
3067
|
const actions = actionsProp ?? (readOnly ? [] : [{
|
|
3051
3068
|
label: "View logs",
|
|
@@ -3068,7 +3085,10 @@ function ResourceDetailPage(props) {
|
|
|
3068
3085
|
description
|
|
3069
3086
|
});
|
|
3070
3087
|
const dirty = settings.name !== saved.name || settings.description !== saved.description;
|
|
3071
|
-
const tabs = readOnly ? TABS.filter((
|
|
3088
|
+
const tabs = (readOnly ? TABS.filter((value) => value === "overview") : TABS).map((value) => ({
|
|
3089
|
+
value,
|
|
3090
|
+
label: labels?.[value] ?? t(`resourceDetail.${value}`)
|
|
3091
|
+
}));
|
|
3072
3092
|
return /* @__PURE__ */ jsx(ConsoleShell, {
|
|
3073
3093
|
currentHref: "/deployments",
|
|
3074
3094
|
breadcrumbs,
|
|
@@ -3093,7 +3113,7 @@ function ResourceDetailPage(props) {
|
|
|
3093
3113
|
},
|
|
3094
3114
|
children: [
|
|
3095
3115
|
/* @__PURE__ */ jsx(TabsList, {
|
|
3096
|
-
"aria-label": "
|
|
3116
|
+
"aria-label": labels?.sections ?? t("resourceDetail.sections"),
|
|
3097
3117
|
children: tabs.map((t) => /* @__PURE__ */ jsx(TabsTab, {
|
|
3098
3118
|
value: t.value,
|
|
3099
3119
|
children: t.label
|
|
@@ -3210,7 +3230,7 @@ const meta$30 = {
|
|
|
3210
3230
|
family: "Resources",
|
|
3211
3231
|
priority: "P1",
|
|
3212
3232
|
status: "beta",
|
|
3213
|
-
description: "Resource detail with editable Overview, Activity and Settings tabs by default; optional readOnly (boolean, default false) uses only explicit header and detail props and one Overview tab, with optional history (readonly StatusEvent[], default undefined): omitted hides the history card, [] shows the named empty state, and events render exactly as supplied without invented actors. Read-only ignores activity, events, dangerActions, defaultTab, onSaveSettings and onDangerAction; explicit actions and onEditDetails are honored. The Resource sections tablist retains Tab, arrow, Home/End and Enter/Space keyboard navigation; history is a named group containing the Status history list or empty-state region. Uses token styling in dark (default), light and high_contrast. Examples: runtime-with-transition maps state and lastTransitionAt to one actor-free event, runtime-without-transition maps null to [], and realtime-configuration and postgres-table-detail omit history. ConsoleShell retains sample frame defaults unless explicit shell props are supplied, as in the examples.",
|
|
3233
|
+
description: "Resource detail with editable Overview, Activity and Settings tabs by default; optional readOnly (boolean, default false) uses only explicit header and detail props and one Overview tab, with optional history (readonly StatusEvent[], default undefined): omitted hides the history card, [] shows the named empty state, and events render exactly as supplied without invented actors. Read-only ignores activity, events, dangerActions, defaultTab, onSaveSettings and onDangerAction; explicit actions and onEditDetails are honored. The Resource sections tablist retains Tab, arrow, Home/End and Enter/Space keyboard navigation; history is a named group containing the Status history list or empty-state region. Uses token styling in dark (default), light and high_contrast. Examples: runtime-with-transition maps state and lastTransitionAt to one actor-free event, runtime-without-transition maps null to [], and realtime-configuration and postgres-table-detail omit history. Optional labels?: Partial<ResourceDetailLabels> overrides overview, activity, settings and sections (all strings). Unset keys use I18nProvider (en/es); no provider keeps English. Only the tabs and tablist are localized by this template; fixtures and other section text are unchanged. readOnly keeps only the localized overview tab. See the Catalog labels live preview. ConsoleShell retains sample frame defaults unless explicit shell props are supplied, as in the examples.",
|
|
3214
3234
|
layout: "SidebarLayout + Page",
|
|
3215
3235
|
blocks: [
|
|
3216
3236
|
"app-sidebar",
|
|
@@ -3541,19 +3561,18 @@ const galleryColumns = [
|
|
|
3541
3561
|
}
|
|
3542
3562
|
];
|
|
3543
3563
|
/** Case-insensitive search over name and description. */
|
|
3544
|
-
function searchResources(items, query) {
|
|
3545
|
-
|
|
3546
|
-
if (!q) return [...items];
|
|
3547
|
-
return items.filter((i) => i.name.toLowerCase().includes(q) || (i.description ?? "").toLowerCase().includes(q));
|
|
3564
|
+
function searchResources(items, query, locale) {
|
|
3565
|
+
return items.filter((i) => matchText(i.name, query, { locale }) || matchText(i.description ?? "", query, { locale }));
|
|
3548
3566
|
}
|
|
3549
3567
|
//#endregion
|
|
3550
3568
|
//#region src/resource-gallery/Page.tsx
|
|
3551
3569
|
/** Resource gallery: toolbar with search and a grid/table view toggle over the same resources. */
|
|
3552
3570
|
function ResourceGalleryPage({ title = "Resources", description = "Services, databases and jobs that make up this project.", breadcrumbs = galleryBreadcrumbs, items: initialItems = galleryItems, defaultView = "grid", onViewChange, createLabel = "New resource", onCreate, onAction, shell }) {
|
|
3571
|
+
const { locale } = useI18n();
|
|
3553
3572
|
const [items, setItems] = useState(initialItems);
|
|
3554
3573
|
const [view, setView] = useState(defaultView);
|
|
3555
3574
|
const [query, setQuery] = useState("");
|
|
3556
|
-
const visible = searchResources(items, query);
|
|
3575
|
+
const visible = searchResources(items, query, locale);
|
|
3557
3576
|
const handleAction = (action, item) => {
|
|
3558
3577
|
if (onAction) onAction(action, item);
|
|
3559
3578
|
else if (action === "delete") setItems((prev) => prev.filter((i) => i.id !== item.id));
|
|
@@ -3728,16 +3747,17 @@ const text = (v) => typeof v === "string" || typeof v === "number" ? String(v).t
|
|
|
3728
3747
|
* A filter field reads the column with the same id (its `accessor`), else the row property of that
|
|
3729
3748
|
* name, so statuses come from the columns.
|
|
3730
3749
|
*/
|
|
3731
|
-
function filterResources(rows, query, filters, columns) {
|
|
3732
|
-
const q = query.trim()
|
|
3750
|
+
function filterResources(rows, query, filters, columns, locale) {
|
|
3751
|
+
const q = query.trim();
|
|
3733
3752
|
const byField = /* @__PURE__ */ new Map();
|
|
3734
3753
|
for (const f of filters) byField.set(f.field, [...byField.get(f.field) ?? [], f.value.toLowerCase()]);
|
|
3735
3754
|
return rows.filter((row) => {
|
|
3736
3755
|
if (q) {
|
|
3737
3756
|
const r = row;
|
|
3738
|
-
|
|
3739
|
-
|
|
3740
|
-
if (
|
|
3757
|
+
const searchText = (v) => typeof v === "string" || typeof v === "number" ? String(v) : void 0;
|
|
3758
|
+
let haystack = [r.name, r.id].map(searchText).filter((v) => v !== void 0);
|
|
3759
|
+
if (haystack.length === 0 && columns) haystack = columns.map((c) => searchText(c.accessor?.(row))).filter((v) => v !== void 0);
|
|
3760
|
+
if (!haystack.some((v) => matchText(v, q, { locale }))) return false;
|
|
3741
3761
|
}
|
|
3742
3762
|
for (const [field, values] of byField) {
|
|
3743
3763
|
const value = text(fieldValue(row, field, columns));
|
|
@@ -3758,11 +3778,12 @@ const defaultArchive = (row) => "status" in row ? {
|
|
|
3758
3778
|
} : row;
|
|
3759
3779
|
/** Resource index: header, filter bar, selectable sortable table with pagination, bulk action bar and a no-results state. */
|
|
3760
3780
|
function ResourceIndexPage({ title = "Deployments", description = "Every service deployed in this workspace, across regions.", breadcrumbs = resourceBreadcrumbs, rows: initialRows = resourceRows, columns = resourceColumns, filterFields = resourceFilterFields, savedViews = resourceSavedViews, getRowId = defaultRowId, getRowHref, onRowClick, linkColumnId, noun = ["deployment", "deployments"], createLabel = "New deployment", onCreate, onOpen, onBulkAction, archiveRow = defaultArchive, loading = false, shell }) {
|
|
3781
|
+
const { locale } = useI18n();
|
|
3761
3782
|
const [rows, setRows] = useState(initialRows);
|
|
3762
3783
|
const [query, setQuery] = useState("");
|
|
3763
3784
|
const [filters, setFilters] = useState([]);
|
|
3764
3785
|
const [selected, setSelected] = useState([]);
|
|
3765
|
-
const visible = filterResources(rows, query, filters, columns);
|
|
3786
|
+
const visible = filterResources(rows, query, filters, columns, locale);
|
|
3766
3787
|
const visibleSelected = selected.filter((id) => visible.some((r) => getRowId(r) === id));
|
|
3767
3788
|
const tableColumns = useMemo(() => linkColumns(columns, linkColumnId ?? columns[0]?.id, getRowHref, onRowClick), [
|
|
3768
3789
|
columns,
|
|
@@ -4048,19 +4069,18 @@ const EMPTY_SELECTION = {
|
|
|
4048
4069
|
};
|
|
4049
4070
|
const facetValue = (hit, facet) => facet === "type" ? hit.type : hit.owner;
|
|
4050
4071
|
/** Hits whose title or snippet contain the query (case-insensitive). */
|
|
4051
|
-
function matchQuery(hits, query) {
|
|
4052
|
-
|
|
4053
|
-
return q ? hits.filter((h) => `${h.title} ${h.snippet}`.toLowerCase().includes(q)) : [...hits];
|
|
4072
|
+
function matchQuery(hits, query, locale) {
|
|
4073
|
+
return hits.filter((h) => matchText(`${h.title} ${h.snippet}`, query, { locale }));
|
|
4054
4074
|
}
|
|
4055
4075
|
/** Search results: ConsoleShell + Page with Checkbox-group facets and results grouped by type. */
|
|
4056
4076
|
function SearchResultsPage(props) {
|
|
4057
|
-
const { t } = useI18n();
|
|
4077
|
+
const { t, locale } = useI18n();
|
|
4058
4078
|
const { hits = searchHits, facets = searchFacets, defaultQuery = searchDefaultQuery, onQueryChange, onSelectionChange, breadcrumbs = searchBreadcrumbs, currentHref = "/search", shell } = props;
|
|
4059
4079
|
const id = useId();
|
|
4060
4080
|
const resultsRef = useRef(null);
|
|
4061
4081
|
const [query, setQuery] = useState(defaultQuery);
|
|
4062
4082
|
const [selection, setSelection] = useState(EMPTY_SELECTION);
|
|
4063
|
-
const matched = matchQuery(hits, query);
|
|
4083
|
+
const matched = matchQuery(hits, query, locale);
|
|
4064
4084
|
const results = matched.filter((h) => Object.keys(selection).every((f) => selection[f].length === 0 || selection[f].includes(facetValue(h, f))));
|
|
4065
4085
|
const groups = searchTypeOrder.map((type) => ({
|
|
4066
4086
|
type,
|
|
@@ -7158,12 +7178,12 @@ const usageSavedViews = [
|
|
|
7158
7178
|
* Search by project name; filters match the row field named by the filter id (same field OR,
|
|
7159
7179
|
* across fields AND). Filters on fields the row does not have are ignored.
|
|
7160
7180
|
*/
|
|
7161
|
-
function filterUsage(rows, query, filters) {
|
|
7162
|
-
const q = query.trim()
|
|
7181
|
+
function filterUsage(rows, query, filters, locale) {
|
|
7182
|
+
const q = query.trim();
|
|
7163
7183
|
const byField = /* @__PURE__ */ new Map();
|
|
7164
7184
|
for (const f of filters) byField.set(f.field, [...byField.get(f.field) ?? [], f.value]);
|
|
7165
7185
|
return rows.filter((row) => {
|
|
7166
|
-
if (q && !row.project
|
|
7186
|
+
if (q && !matchText(row.project, q, { locale })) return false;
|
|
7167
7187
|
for (const [field, values] of byField) {
|
|
7168
7188
|
const value = row[field];
|
|
7169
7189
|
if (value !== void 0 && value !== null && !values.includes(String(value))) return false;
|
|
@@ -7286,6 +7306,7 @@ const TRAFFIC_CHART = {
|
|
|
7286
7306
|
};
|
|
7287
7307
|
/** Usage analytics: date range + filters, two charts and a sortable breakdown table with export. */
|
|
7288
7308
|
function UsageAnalyticsPage({ title = "Usage analytics", description = "Traffic, errors and spend by project and environment.", breadcrumbs = usageBreadcrumbs, rows = usageRows, columns = usageColumns, filterFields = usageFilterFields, searchPlaceholder = "Search projects…", savedViews = usageSavedViews, primaryChart, trafficByRange: traffic = trafficByRange, trafficRanges: ranges = trafficRanges, secondaryChart, onRangeChange, onExport, shell }) {
|
|
7309
|
+
const { locale } = useI18n();
|
|
7289
7310
|
const secondary = {
|
|
7290
7311
|
...TRAFFIC_CHART,
|
|
7291
7312
|
...secondaryChart
|
|
@@ -7293,7 +7314,7 @@ function UsageAnalyticsPage({ title = "Usage analytics", description = "Traffic,
|
|
|
7293
7314
|
const [range, setRange] = useState(null);
|
|
7294
7315
|
const [query, setQuery] = useState("");
|
|
7295
7316
|
const [filters, setFilters] = useState([]);
|
|
7296
|
-
const visible = filterUsage(rows, query, filters);
|
|
7317
|
+
const visible = filterUsage(rows, query, filters, locale);
|
|
7297
7318
|
const clearFilters = () => setFilters([]);
|
|
7298
7319
|
const exportAction = {
|
|
7299
7320
|
label: "Export CSV",
|
|
@@ -12412,15 +12433,14 @@ const helpArticles = [
|
|
|
12412
12433
|
}
|
|
12413
12434
|
];
|
|
12414
12435
|
/** Articles whose title or excerpt contains the query. */
|
|
12415
|
-
function searchArticles(articles, query) {
|
|
12416
|
-
|
|
12417
|
-
if (!q) return [...articles];
|
|
12418
|
-
return articles.filter((a) => a.title.toLowerCase().includes(q) || a.excerpt.toLowerCase().includes(q));
|
|
12436
|
+
function searchArticles(articles, query, locale) {
|
|
12437
|
+
return articles.filter((a) => matchText(a.title, query, { locale }) || matchText(a.excerpt, query, { locale }));
|
|
12419
12438
|
}
|
|
12420
12439
|
//#endregion
|
|
12421
12440
|
//#region src/help-home/Page.tsx
|
|
12422
12441
|
/** Help center home on DocsLayout: search, category cards and popular articles. */
|
|
12423
12442
|
function HelpHomePage(props) {
|
|
12443
|
+
const { locale } = useI18n();
|
|
12424
12444
|
const { brand = /* @__PURE__ */ jsx(Logo, {
|
|
12425
12445
|
size: 22,
|
|
12426
12446
|
wordmark: true
|
|
@@ -12438,7 +12458,7 @@ function HelpHomePage(props) {
|
|
|
12438
12458
|
}) } = props;
|
|
12439
12459
|
const [query, setQuery] = useState("");
|
|
12440
12460
|
const ids = useId();
|
|
12441
|
-
const results = searchArticles(articles, query);
|
|
12461
|
+
const results = searchArticles(articles, query, locale);
|
|
12442
12462
|
const searching = query.trim() !== "";
|
|
12443
12463
|
const categoryTitle = (id) => categories.find((c) => c.id === id)?.title ?? id;
|
|
12444
12464
|
const submit = (e) => {
|
|
@@ -13259,7 +13279,7 @@ const shortcutGroups = [
|
|
|
13259
13279
|
{
|
|
13260
13280
|
id: "palette",
|
|
13261
13281
|
description: "Open the command palette",
|
|
13262
|
-
keys: ["
|
|
13282
|
+
keys: ["mod", "K"]
|
|
13263
13283
|
},
|
|
13264
13284
|
{
|
|
13265
13285
|
id: "help",
|
|
@@ -13269,13 +13289,13 @@ const shortcutGroups = [
|
|
|
13269
13289
|
{
|
|
13270
13290
|
id: "sidebar",
|
|
13271
13291
|
description: "Collapse or expand the sidebar",
|
|
13272
|
-
keys: ["
|
|
13292
|
+
keys: ["mod", "B"]
|
|
13273
13293
|
},
|
|
13274
13294
|
{
|
|
13275
13295
|
id: "theme",
|
|
13276
13296
|
description: "Switch theme",
|
|
13277
13297
|
keys: [
|
|
13278
|
-
"
|
|
13298
|
+
"mod",
|
|
13279
13299
|
"⇧",
|
|
13280
13300
|
"L"
|
|
13281
13301
|
]
|
|
@@ -13350,12 +13370,12 @@ const shortcutGroups = [
|
|
|
13350
13370
|
{
|
|
13351
13371
|
id: "save",
|
|
13352
13372
|
description: "Save changes",
|
|
13353
|
-
keys: ["
|
|
13373
|
+
keys: ["mod", "S"]
|
|
13354
13374
|
},
|
|
13355
13375
|
{
|
|
13356
13376
|
id: "submit",
|
|
13357
13377
|
description: "Submit a form or comment",
|
|
13358
|
-
keys: ["
|
|
13378
|
+
keys: ["mod", "↵"]
|
|
13359
13379
|
},
|
|
13360
13380
|
{
|
|
13361
13381
|
id: "cancel",
|
|
@@ -13365,27 +13385,39 @@ const shortcutGroups = [
|
|
|
13365
13385
|
{
|
|
13366
13386
|
id: "undo",
|
|
13367
13387
|
description: "Undo",
|
|
13368
|
-
keys: ["
|
|
13388
|
+
keys: ["mod", "Z"]
|
|
13369
13389
|
}
|
|
13370
13390
|
]
|
|
13371
13391
|
}
|
|
13372
13392
|
];
|
|
13373
|
-
/** Groups
|
|
13374
|
-
function filterShortcuts(groups, query) {
|
|
13393
|
+
/** Groups matching descriptions, titles or keys; mod accepts both Command and Ctrl spellings. */
|
|
13394
|
+
function filterShortcuts(groups, query, locale, os = "undetermined") {
|
|
13375
13395
|
const q = query.trim().toLowerCase();
|
|
13376
13396
|
if (!q) return [...groups];
|
|
13397
|
+
const modifier = os === "macos" || os === "ios" ? "⌘" : "Ctrl";
|
|
13398
|
+
const alias = modifier === "⌘" ? "Ctrl" : "⌘";
|
|
13399
|
+
const matchesKeys = (shortcut) => {
|
|
13400
|
+
const keys = [...shortcut.keys, ...shortcut.then ?? []];
|
|
13401
|
+
return [
|
|
13402
|
+
"mod",
|
|
13403
|
+
modifier,
|
|
13404
|
+
alias
|
|
13405
|
+
].some((mod) => keys.map((key) => key === "mod" ? mod : key).join(" ").toLowerCase() === q);
|
|
13406
|
+
};
|
|
13377
13407
|
return groups.map((g) => ({
|
|
13378
13408
|
...g,
|
|
13379
|
-
shortcuts: g.shortcuts.filter((s) => s.description
|
|
13409
|
+
shortcuts: g.shortcuts.filter((s) => matchText(s.description, query, { locale }) || matchText(g.title, query, { locale }) || matchesKeys(s))
|
|
13380
13410
|
})).filter((g) => g.shortcuts.length > 0);
|
|
13381
13411
|
}
|
|
13382
13412
|
//#endregion
|
|
13383
13413
|
//#region src/keyboard-shortcuts/ShortcutSheet.tsx
|
|
13384
13414
|
/** Searchable shortcut sheet shared by the page and the dialog. */
|
|
13385
13415
|
function ShortcutSheet({ groups, headingLevel = 2, columns = 2, autoFocus }) {
|
|
13416
|
+
const { locale } = useI18n();
|
|
13417
|
+
const os = useOs();
|
|
13386
13418
|
const [query, setQuery] = useState("");
|
|
13387
13419
|
const countId = useId();
|
|
13388
|
-
const visible = filterShortcuts(groups, query);
|
|
13420
|
+
const visible = filterShortcuts(groups, query, locale, os);
|
|
13389
13421
|
const total = visible.reduce((n, g) => n + g.shortcuts.length, 0);
|
|
13390
13422
|
return /* @__PURE__ */ jsxs(Stack, {
|
|
13391
13423
|
gap: 5,
|
|
@@ -14508,14 +14540,14 @@ function triageFieldValue(i, field) {
|
|
|
14508
14540
|
if (field === "owner") return i.owner;
|
|
14509
14541
|
}
|
|
14510
14542
|
/** True when the incident matches the search text and every filtered field (OR within a field). */
|
|
14511
|
-
function matchesTriage(i, query, filters) {
|
|
14512
|
-
const q = query.trim()
|
|
14543
|
+
function matchesTriage(i, query, filters, locale) {
|
|
14544
|
+
const q = query.trim();
|
|
14513
14545
|
if (q && ![
|
|
14514
14546
|
i.id,
|
|
14515
14547
|
i.title,
|
|
14516
14548
|
i.service,
|
|
14517
14549
|
i.owner
|
|
14518
|
-
].some((v) => v
|
|
14550
|
+
].some((v) => matchText(v, q, { locale }))) return false;
|
|
14519
14551
|
const byField = /* @__PURE__ */ new Map();
|
|
14520
14552
|
for (const f of filters) byField.set(f.field, [...byField.get(f.field) ?? [], f.value]);
|
|
14521
14553
|
return [...byField].every(([field, values]) => values.includes(triageFieldValue(i, field) ?? ""));
|
|
@@ -14619,6 +14651,7 @@ function IncidentCard({ incident, now }) {
|
|
|
14619
14651
|
});
|
|
14620
14652
|
}
|
|
14621
14653
|
function IncidentBoard({ title, description, defaultColumns, filterFields, defaultFilters, now, onMove, defaultOpenId, onDeclare, onExport }) {
|
|
14654
|
+
const { locale } = useI18n();
|
|
14622
14655
|
const announce = useAnnounce();
|
|
14623
14656
|
const [columns, setColumns] = useState(defaultColumns);
|
|
14624
14657
|
const [query, setQuery] = useState("");
|
|
@@ -14626,7 +14659,7 @@ function IncidentBoard({ title, description, defaultColumns, filterFields, defau
|
|
|
14626
14659
|
const [openId, setOpenId] = useState(defaultOpenId);
|
|
14627
14660
|
const visible = columns.map((c) => ({
|
|
14628
14661
|
...c,
|
|
14629
|
-
cards: c.cards.filter((i) => matchesTriage(i, query, filters))
|
|
14662
|
+
cards: c.cards.filter((i) => matchesTriage(i, query, filters, locale))
|
|
14630
14663
|
}));
|
|
14631
14664
|
const shown = visible.reduce((n, c) => n + c.cards.length, 0);
|
|
14632
14665
|
const openColumn = columns.find((c) => c.cards.some((i) => i.id === openId));
|