@k2b/cloud 0.40.0 → 0.42.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/package.json +9 -5
- package/src/_internal/bounded-json.ts +14 -2
- package/src/_internal/capabilities.ts +87 -8
- package/src/_internal/capability-reviews.ts +6 -0
- package/src/_internal/capability-sentences.ts +2 -1
- package/src/_internal/capability-timeout.ts +71 -0
- package/src/_internal/define-app.ts +158 -92
- package/src/_internal/heartbeat.ts +203 -14
- package/src/_internal/http-drain.ts +56 -0
- package/src/_internal/registry-validation.ts +3 -0
- package/src/_internal/registry.ts +28 -1
- package/src/_internal/runtime-context.ts +1 -0
- package/src/_internal/setup-lock.ts +103 -0
- package/src/_internal/status-preserving-ssr.ts +2 -0
- package/src/access/PermissionEditor.tsx +16 -17
- package/src/access/PrincipalPicker.tsx +45 -10
- package/src/access/messages.ts +8 -0
- package/src/access/principal-label.ts +18 -0
- package/src/access/ui.ts +1 -0
- package/src/account/EntitySearch.tsx +5 -4
- package/src/ai/action-review.ts +2 -1
- package/src/ai/app-skill-store.ts +1 -1
- package/src/ai/capabilities.ts +21 -3
- package/src/ai/capability-execution.ts +14 -12
- package/src/ai/chat/action-card.ts +76 -4
- package/src/ai/chat/action-messages.ts +14 -0
- package/src/ai/chat/action-question.browser-harness.tsx +12 -0
- package/src/ai/chat/action-question.tsx +214 -23
- package/src/ai/chat/action-text.ts +288 -0
- package/src/ai/chat/tool-action-words.ts +31 -0
- package/src/ai/chat/tool-action.tsx +1 -26
- package/src/ai/chat-tasks.ts +2 -1
- package/src/ai/client/controller.ts +150 -32
- package/src/ai/client/transport.ts +14 -2
- package/src/ai/code-capability-routes.ts +27 -23
- package/src/ai/code-runtime-tools.ts +14 -16
- package/src/ai/code-source-tools.ts +16 -18
- package/src/ai/executor.ts +64 -21
- package/src/ai/fetch-file-tool.ts +67 -76
- package/src/ai/firecrawl-tools.ts +15 -2
- package/src/ai/http.ts +1 -0
- package/src/ai/index.ts +1 -0
- package/src/ai/migrate.ts +28 -0
- package/src/ai/open-questions.ts +168 -0
- package/src/ai/projects.ts +1 -2
- package/src/ai/protocol.ts +2 -2
- package/src/ai/question-text.ts +8 -0
- package/src/ai/recent-resources.ts +12 -5
- package/src/ai/review-content.ts +77 -0
- package/src/ai/routes.ts +119 -35
- package/src/ai/runtime.ts +1 -0
- package/src/ai/skill-tool.ts +17 -6
- package/src/ai/skills-routes.ts +72 -27
- package/src/ai/skills.ts +139 -60
- package/src/ai/store.ts +184 -31
- package/src/ai/testing.ts +36 -0
- package/src/ai/tool-sentences.ts +152 -34
- package/src/ai/tool-wording.ts +12 -6
- package/src/ai/tools.ts +8 -3
- package/src/ai/types.ts +25 -4
- package/src/ai/usage.ts +2 -2
- package/src/api/admin-core-settings.ts +15 -2
- package/src/api/admin-geo.ts +54 -0
- package/src/api/admin-linux-identities.ts +6 -3
- package/src/api/admin-outgoing-mail.ts +29 -15
- package/src/api/app-approval-actions.ts +31 -7
- package/src/api/approvals-loader.ts +3 -3
- package/src/api/approvals.ts +51 -0
- package/src/api/apps.ts +4 -2
- package/src/api/auth/schemas.ts +3 -0
- package/src/api/auth.ts +54 -18
- package/src/api/capabilities.ts +258 -181
- package/src/api/capability-streams.ts +21 -13
- package/src/api/cli-plugins.ts +4 -1
- package/src/api/geo-glyphs.ts +11 -0
- package/src/api/geo.ts +128 -0
- package/src/api/help.ts +12 -4
- package/src/api/index.ts +5 -0
- package/src/api/mcp.ts +73 -41
- package/src/api/me-panel-consent.ts +8 -5
- package/src/api/me.ts +79 -3
- package/src/api/search.ts +21 -14
- package/src/api/sync-ops.ts +19 -13
- package/src/api/widgets.ts +42 -18
- package/src/approvals/Approvals.tsx +30 -66
- package/src/approvals/approval-card.ts +68 -0
- package/src/browser/CloudResourceSearch.browser-harness.tsx +53 -1
- package/src/browser/CloudResourceSearch.tsx +3 -13
- package/src/browser/PanelLeaveNotice.tsx +18 -0
- package/src/browser/navigation-search.ts +78 -25
- package/src/browser/panel-leave.ts +69 -0
- package/src/browser/panel-messages.ts +12 -0
- package/src/browser/panel-slot.ts +173 -24
- package/src/browser/panel.ts +3 -0
- package/src/browser/resource-search-messages.ts +2 -0
- package/src/capabilities/client.ts +28 -1
- package/src/capabilities/executions.ts +22 -3
- package/src/capabilities/geo-client.ts +57 -0
- package/src/capabilities/response.ts +2 -1
- package/src/capabilities/review-storage.ts +24 -0
- package/src/capabilities/reviews.ts +17 -3
- package/src/capabilities/server.ts +36 -16
- package/src/cli/access.ts +15 -23
- package/src/cli/admin/linux.ts +13 -2
- package/src/cli/admin/outgoing-mail.ts +26 -4
- package/src/cli/capabilities.ts +19 -0
- package/src/config/env.ts +0 -6
- package/src/contracts/app-access.ts +48 -0
- package/src/contracts/app-approval.ts +3 -3
- package/src/contracts/app.ts +4 -1
- package/src/contracts/approvals.ts +2 -2
- package/src/contracts/avatar.ts +186 -0
- package/src/contracts/capabilities.ts +149 -55
- package/src/contracts/geo-admin.ts +127 -0
- package/src/contracts/geo-map.ts +19 -0
- package/src/contracts/geo-route.ts +168 -0
- package/src/contracts/geo.ts +248 -0
- package/src/contracts/index.ts +4 -0
- package/src/contracts/outgoing-mail.ts +17 -4
- package/src/contracts/registry.ts +2 -0
- package/src/contracts/shared.ts +3 -0
- package/src/events/app-access.ts +68 -0
- package/src/events/live-delivery.ts +128 -0
- package/src/events/live.ts +14 -130
- package/src/geo/CloudMap.tsx +95 -0
- package/src/geo/map-messages.ts +17 -0
- package/src/geo/ui.ts +1 -0
- package/src/index.ts +2 -0
- package/src/server/client-ip.ts +36 -0
- package/src/server/index.ts +3 -4
- package/src/server/middleware/app-access.ts +312 -0
- package/src/server/middleware/auth.ts +5 -3
- package/src/server/middleware/invocation.ts +11 -0
- package/src/server/middleware/rate-limit.ts +4 -4
- package/src/server/services/access.ts +13 -9
- package/src/server/services/index.ts +0 -2
- package/src/server/services/services.ts +0 -2
- package/src/services/accounts/app.ts +23 -3
- package/src/services/accounts/posix.ts +21 -7
- package/src/services/accounts/users.ts +12 -1
- package/src/services/allowed-network-fetch.ts +16 -10
- package/src/services/app-access-metrics.ts +24 -0
- package/src/services/app-access.ts +123 -0
- package/src/services/app-discovery.ts +27 -0
- package/src/services/approvals/content.ts +40 -0
- package/src/services/approvals/device.ts +49 -33
- package/src/services/approvals/index.ts +15 -6
- package/src/services/approvals/list.ts +6 -11
- package/src/services/approvals/question.ts +72 -0
- package/src/services/audit/index.ts +1 -0
- package/src/services/auth-flows/ipa.ts +39 -2
- package/src/services/auth-flows/password-reset.ts +32 -6
- package/src/services/downstream-budget.ts +87 -0
- package/src/services/geo/admin.ts +262 -0
- package/src/services/geo/bucket.ts +67 -0
- package/src/services/geo/cache.ts +57 -0
- package/src/services/geo/costs.ts +5 -0
- package/src/services/geo/errors.ts +41 -0
- package/src/services/geo/fixtures/README.md +17 -0
- package/src/services/geo/fixtures/along.json +355 -0
- package/src/services/geo/fixtures/autocomplete.json +218 -0
- package/src/services/geo/fixtures/geocode.json +129 -0
- package/src/services/geo/fixtures/places.json +355 -0
- package/src/services/geo/fixtures/reverse.json +61 -0
- package/src/services/geo/fixtures/routing.json +57 -0
- package/src/services/geo/geoapify-route.ts +93 -0
- package/src/services/geo/geoapify.ts +281 -0
- package/src/services/geo/ledger.ts +122 -0
- package/src/services/geo/map.ts +160 -0
- package/src/services/geo/migrate.ts +25 -0
- package/src/services/geo/polyline.ts +184 -0
- package/src/services/geo/route-chain.ts +145 -0
- package/src/services/geo/route-costs.ts +9 -0
- package/src/services/geo/route-geometry.ts +39 -0
- package/src/services/geo/route-plan.ts +172 -0
- package/src/services/geo/route-service.ts +433 -0
- package/src/services/geo/service.ts +300 -0
- package/src/services/geo/settings.ts +49 -0
- package/src/services/help/index.ts +16 -7
- package/src/services/help/types.ts +6 -1
- package/src/services/identity/invocation-actor.ts +20 -2
- package/src/services/identity/issue-invocation.ts +67 -0
- package/src/services/identity/key-ring.ts +5 -3
- package/src/services/identity/runtime-config.ts +23 -0
- package/src/services/index.ts +11 -0
- package/src/services/mandates/index.ts +40 -3
- package/src/services/network-security.ts +6 -3
- package/src/services/network-transport.ts +99 -0
- package/src/services/notifications/catalog.ts +3 -2
- package/src/services/notifications/runtime.ts +1 -0
- package/src/services/notifications/web-push-transport.ts +45 -38
- package/src/services/outgoing-mail/dispatcher.ts +6 -1
- package/src/services/outgoing-mail/index.ts +55 -1
- package/src/services/outgoing-mail/messages.ts +82 -3
- package/src/services/outgoing-mail/resolve-host.ts +2 -2
- package/src/services/outgoing-mail/retention.ts +13 -1
- package/src/services/outgoing-mail/store.ts +36 -6
- package/src/services/outgoing-mail/test-send.ts +34 -4
- package/src/services/outgoing-mail/transport.ts +1 -1
- package/src/services/pdf/gotenberg.ts +11 -38
- package/src/services/pdf/markdown.ts +160 -2
- package/src/services/pdf/template-preview.ts +1 -1
- package/src/services/public-http.ts +28 -40
- package/src/services/session/index.ts +203 -29
- package/src/services/settings/app.ts +4 -1
- package/src/services/settings/core-settings.ts +88 -0
- package/src/services/settings/defaults.ts +8 -2
- package/src/services/settings/errors.ts +11 -0
- package/src/services/settings/geo-budget.ts +40 -0
- package/src/services/settings/index.ts +4 -3
- package/src/services/settings/store.ts +25 -17
- package/src/services/weather/geo.ts +29 -111
- package/src/services/weather/index.ts +4 -3
- package/src/services/weather/locations.ts +8 -5
- package/src/services/weather/migrate.ts +1 -0
- package/src/services/weather/settings.ts +0 -16
- package/src/shared/__snapshots__/avatar-art.test.ts.snap +5 -0
- package/src/shared/avatar-art.ts +400 -0
- package/src/shared/index.ts +2 -0
- package/src/shared/network-address.ts +22 -3
- package/src/shared/template-rendering.ts +36 -4
- package/src/shared-text/documents.ts +1 -1
- package/src/shared-text/index.ts +2 -0
- package/src/shared-text/log.ts +51 -3
- package/src/shared-text/snapshots.ts +340 -0
- package/src/ssr/AdminLayout.tsx +4 -2
- package/src/ssr/CloudShell.island.tsx +6 -0
- package/src/ssr/HelpPanelArticle.island.tsx +308 -0
- package/src/ssr/HelpPanelBack.island.tsx +32 -0
- package/src/ssr/HelpPanelMenu.island.tsx +133 -0
- package/src/ssr/HelpPanelSearch.island.tsx +123 -0
- package/src/ssr/HelpPanelShortcuts.island.tsx +22 -0
- package/src/ssr/HelpPanelView.island.tsx +27 -0
- package/src/ssr/Layout.tsx +65 -15
- package/src/ssr/LayoutHelp.browser-harness.tsx +1 -1
- package/src/ssr/LayoutHelp.tsx +185 -542
- package/src/ssr/LayoutHelpPanel.browser-harness.tsx +6 -7
- package/src/ssr/LayoutHelpPanel.server.browser-harness.tsx +131 -0
- package/src/ssr/LayoutHelpPanel.server.tsx +115 -0
- package/src/ssr/PanelRuntime.island.tsx +11 -7
- package/src/ssr/PwaLayout.tsx +2 -2
- package/src/ssr/RailApps.tsx +6 -0
- package/src/ssr/ShellRuntime.island.tsx +23 -7
- package/src/ssr/__snapshots__/Layout.unchanged.render.test.tsx.snap +151 -0
- package/src/ssr/__snapshots__/LayoutHelp.ssr.render.test.tsx.snap +29 -0
- package/src/ssr/admin-navigation.ts +1 -0
- package/src/ssr/app-badges.ts +6 -1
- package/src/ssr/compact-shell.browser-harness.ts +44 -5
- package/src/ssr/help.ts +7 -5
- package/src/ssr/index.ts +2 -1
- package/src/ssr/islands/index.ts +3 -4
- package/src/ssr/layout-context.ts +2 -0
- package/src/ssr/layout-help-panel-controls.tsx +43 -0
- package/src/ssr/layout-help-panel-document.ts +67 -0
- package/src/ssr/layout-help-panel-lifecycle.ts +32 -0
- package/src/ssr/layout-help-panel-menu.tsx +113 -0
- package/src/ssr/layout-help-panel-modules.ts +7 -0
- package/src/ssr/layout-help-panel-reader.tsx +159 -0
- package/src/ssr/layout-help-panel-search.tsx +69 -0
- package/src/ssr/layout-help-panel-shortcuts.ts +7 -0
- package/src/ssr/layout-help-panel-state.ts +141 -0
- package/src/ssr/layout-help-panel-types.ts +19 -0
- package/src/ssr/layout-help-views.tsx +484 -0
- package/src/ssr/mobile-menu-history.ts +4 -0
- package/src/ssr/page-swap-fixture/Column.island.tsx +15 -0
- package/src/ssr/page-swap-fixture/ProviderProbe.island.tsx +31 -0
- package/src/ssr/page-swap.browser-harness.tsx +177 -0
- package/src/ssr/page-swap.ts +28 -0
- package/src/ssr/page-trail-runtime.ts +32 -13
- package/src/ssr/panel-consent.ts +2 -2
- package/src/ssr/panel-idle.ts +9 -0
- package/src/ssr/panel-slot.browser-harness.tsx +3 -0
- package/src/ssr/platform-messages.ts +8 -0
- package/src/ssr/runtime.ts +17 -0
- package/src/ssr/shell-data.tsx +10 -2
- package/src/styles/approvals.css +6 -0
- package/src/styles/effects.css +15 -0
- package/src/styles/global.css +70 -6
- package/src/server/services/geo.ts +0 -215
package/src/api/search.ts
CHANGED
|
@@ -2,7 +2,7 @@ import { Hono, type MiddlewareHandler } from "hono";
|
|
|
2
2
|
import { describeRoute, resolver } from "hono-openapi";
|
|
3
3
|
import { readBoundedJson } from "../_internal/bounded-json";
|
|
4
4
|
import { resolveCapabilityManifestPresentation } from "../_internal/capabilities";
|
|
5
|
-
import { listCapabilities } from "../_internal/registry";
|
|
5
|
+
import { type listApps, listCapabilities } from "../_internal/registry";
|
|
6
6
|
import type { CapabilityRegistryEntry } from "../contracts";
|
|
7
7
|
import {
|
|
8
8
|
CAPABILITY_FRAMEWORK_ERROR_CODES,
|
|
@@ -14,9 +14,12 @@ import {
|
|
|
14
14
|
import { type AuthContext, auth, expectUserBackedActor, jsonResponse, preferredLocale, requiresAuth, v } from "../server";
|
|
15
15
|
import { noTransform } from "../server/middleware/no-transform";
|
|
16
16
|
import { logger } from "../services";
|
|
17
|
+
import { appAccessAuthority } from "../services/app-access";
|
|
18
|
+
import { discoverableAppEntries } from "../services/app-discovery";
|
|
17
19
|
import { invocationAuthorityFromRequest } from "../services/identity/invocation-authority";
|
|
18
20
|
import { searchInvocationOperation } from "../services/identity/invocation-operations";
|
|
19
|
-
import { normalizeInvocationRequestId
|
|
21
|
+
import { normalizeInvocationRequestId } from "../services/identity/invocation-token";
|
|
22
|
+
import { issueInvocationToken as signInvocationToken } from "../services/identity/issue-invocation";
|
|
20
23
|
import { withActiveIdentitySigner } from "../services/identity/key-ring";
|
|
21
24
|
import { LOCALE_HEADER } from "../shared/locale";
|
|
22
25
|
import { ndjsonLine, ndjsonStream, ndjsonStreamHeaders, startBounded, waitWithin } from "./fanout";
|
|
@@ -83,6 +86,7 @@ type SearchRouteDependencies = {
|
|
|
83
86
|
listCapabilities?: () => Promise<CapabilityRegistryEntry[]>;
|
|
84
87
|
fetch?: (input: string | URL | Request, init?: RequestInit) => Promise<Response>;
|
|
85
88
|
authenticate?: MiddlewareHandler<AuthContext>;
|
|
89
|
+
listApps?: typeof listApps;
|
|
86
90
|
signInvocation?: typeof signInvocationToken;
|
|
87
91
|
withActiveSigner?: typeof withActiveIdentitySigner;
|
|
88
92
|
};
|
|
@@ -137,7 +141,7 @@ export const createSearchRoutes = (dependencies: SearchRouteDependencies = {}) =
|
|
|
137
141
|
const invocationAuthority = invocationAuthorityFromRequest(auth.getAuthority(c));
|
|
138
142
|
let entries: CapabilityRegistryEntry[];
|
|
139
143
|
try {
|
|
140
|
-
entries = await registry();
|
|
144
|
+
entries = await discoverableAppEntries(appAccessAuthority(c), await registry(), dependencies.listApps);
|
|
141
145
|
} catch (error) {
|
|
142
146
|
log.warn("Search capability registry unavailable", { error: error instanceof Error ? error.message : String(error) });
|
|
143
147
|
return c.json(
|
|
@@ -237,22 +241,25 @@ export const createSearchRoutes = (dependencies: SearchRouteDependencies = {}) =
|
|
|
237
241
|
signedInvocations = await waitWithin(
|
|
238
242
|
(dependencies.withActiveSigner ?? withActiveIdentitySigner)(
|
|
239
243
|
"invocation",
|
|
240
|
-
(signer) =>
|
|
244
|
+
(signer, db) =>
|
|
241
245
|
Promise.all(
|
|
242
246
|
startBounded(
|
|
243
247
|
active,
|
|
244
248
|
PROVIDER_CONCURRENCY,
|
|
245
249
|
(provider) =>
|
|
246
|
-
(dependencies.signInvocation ?? signInvocationToken)(
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
250
|
+
(dependencies.signInvocation ?? signInvocationToken)(
|
|
251
|
+
{
|
|
252
|
+
targetAppId: provider.appId,
|
|
253
|
+
callingAppId: "core",
|
|
254
|
+
operation: searchInvocationOperation,
|
|
255
|
+
schemaHash: provider.schemaHash,
|
|
256
|
+
authority: invocationAuthority,
|
|
257
|
+
requestId,
|
|
258
|
+
signer,
|
|
259
|
+
issuer: signer.issuer,
|
|
260
|
+
},
|
|
261
|
+
{ db, groupsAdmin: signer.groupsAdmin },
|
|
262
|
+
),
|
|
256
263
|
fanoutSignal,
|
|
257
264
|
),
|
|
258
265
|
),
|
package/src/api/sync-ops.ts
CHANGED
|
@@ -2,10 +2,12 @@ import { Hono, type MiddlewareHandler } from "hono";
|
|
|
2
2
|
import { readBoundedJson } from "../_internal/bounded-json";
|
|
3
3
|
import { getApp, listApps } from "../_internal/registry";
|
|
4
4
|
import { type AuthContext, auth, rejectReservedWorkloadCredential } from "../server";
|
|
5
|
+
import { AppAccessDeniedError, appAccessDeniedBody } from "../server/middleware/app-access";
|
|
5
6
|
import { noTransform } from "../server/middleware/no-transform";
|
|
6
7
|
import { invocationAuthorityFromRequest } from "../services/identity/invocation-authority";
|
|
7
8
|
import { syncInvocationOperation } from "../services/identity/invocation-operations";
|
|
8
|
-
import { normalizeInvocationRequestId
|
|
9
|
+
import { normalizeInvocationRequestId } from "../services/identity/invocation-token";
|
|
10
|
+
import { issueInvocationToken as signInvocationToken } from "../services/identity/issue-invocation";
|
|
9
11
|
import { withActiveIdentitySigner } from "../services/identity/key-ring";
|
|
10
12
|
|
|
11
13
|
type Dependencies = {
|
|
@@ -53,17 +55,20 @@ export const createSyncOpsProxyRoutes = (dependencies: Dependencies = {}) =>
|
|
|
53
55
|
try {
|
|
54
56
|
const signed = await (dependencies.withActiveSigner ?? withActiveIdentitySigner)(
|
|
55
57
|
"invocation",
|
|
56
|
-
(signer) =>
|
|
57
|
-
(dependencies.signInvocation ?? signInvocationToken)(
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
58
|
+
(signer, db) =>
|
|
59
|
+
(dependencies.signInvocation ?? signInvocationToken)(
|
|
60
|
+
{
|
|
61
|
+
targetAppId: app.id,
|
|
62
|
+
callingAppId: "core",
|
|
63
|
+
operation,
|
|
64
|
+
schemaHash: null,
|
|
65
|
+
authority: invocationAuthorityFromRequest(auth.getAuthority(c)),
|
|
66
|
+
requestId,
|
|
67
|
+
signer,
|
|
68
|
+
issuer: signer.issuer,
|
|
69
|
+
},
|
|
70
|
+
{ db, groupsAdmin: signer.groupsAdmin },
|
|
71
|
+
),
|
|
67
72
|
{ signal, timeoutMs: 5_000 },
|
|
68
73
|
);
|
|
69
74
|
const headers = new Headers({ authorization: `Bearer ${signed.token}`, accept: "application/json" });
|
|
@@ -75,7 +80,8 @@ export const createSyncOpsProxyRoutes = (dependencies: Dependencies = {}) =>
|
|
|
75
80
|
const result = await readBoundedJson(response, 4 * 1024 * 1024);
|
|
76
81
|
if (!result.ok) return c.json({ message: "Invalid Sync operations response" }, 502);
|
|
77
82
|
return Response.json(result.data, { status: response.status, headers: { "cache-control": "no-store" } });
|
|
78
|
-
} catch {
|
|
83
|
+
} catch (error) {
|
|
84
|
+
if (error instanceof AppAccessDeniedError) return c.json(appAccessDeniedBody(error.app), 403);
|
|
79
85
|
return c.json({ message: "Sync operations unavailable" }, 502);
|
|
80
86
|
}
|
|
81
87
|
});
|
package/src/api/widgets.ts
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
|
+
import type { SQL } from "bun";
|
|
1
2
|
import { type Context, Hono, type MiddlewareHandler } from "hono";
|
|
2
3
|
import { bodyLimit } from "hono/body-limit";
|
|
3
4
|
import { z } from "zod";
|
|
4
5
|
import { readBoundedJson } from "../_internal/bounded-json";
|
|
5
|
-
import { type DashboardWidget, listWidgets } from "../_internal/registry";
|
|
6
|
+
import { type DashboardWidget, type listApps, listWidgets } from "../_internal/registry";
|
|
6
7
|
import { parseWidgetSettings } from "../_internal/widget-settings";
|
|
7
8
|
import {
|
|
8
9
|
type DashboardWidgetSize,
|
|
@@ -20,12 +21,16 @@ import {
|
|
|
20
21
|
widgetKey,
|
|
21
22
|
} from "../contracts/widgets";
|
|
22
23
|
import { type AuthContext, auth, preferredLocale, rejectReservedWorkloadCredential } from "../server";
|
|
24
|
+
import { AppAccessDeniedError, appAccessDeniedBody } from "../server/middleware/app-access";
|
|
23
25
|
import { noTransform } from "../server/middleware/no-transform";
|
|
24
26
|
import { WIDGET_SETTINGS_PARAM, WIDGET_SETTINGS_PARAM_MAX_LENGTH, WIDGET_SIZE_PARAM } from "../server/widget-request";
|
|
27
|
+
import { appAccessAuthority } from "../services/app-access";
|
|
28
|
+
import { discoverableAppEntries } from "../services/app-discovery";
|
|
25
29
|
import { CLOUD_INVOCATION_TOKEN_TTL_SECONDS } from "../services/identity/constants";
|
|
26
30
|
import { invocationAuthorityFromRequest } from "../services/identity/invocation-authority";
|
|
27
31
|
import { widgetInvocationOperation } from "../services/identity/invocation-operations";
|
|
28
|
-
import { normalizeInvocationRequestId
|
|
32
|
+
import { normalizeInvocationRequestId } from "../services/identity/invocation-token";
|
|
33
|
+
import { issueInvocationToken as signInvocationToken } from "../services/identity/issue-invocation";
|
|
29
34
|
import { withActiveIdentitySigner } from "../services/identity/key-ring";
|
|
30
35
|
import { logger } from "../services/logging";
|
|
31
36
|
import { LOCALE_HEADER } from "../shared/locale";
|
|
@@ -35,6 +40,7 @@ type WidgetRouteDependencies = {
|
|
|
35
40
|
listWidgets?: typeof listWidgets;
|
|
36
41
|
fetch?: (input: string | URL | Request, init?: RequestInit) => Promise<Response>;
|
|
37
42
|
authenticate?: MiddlewareHandler<AuthContext>;
|
|
43
|
+
listApps?: typeof listApps;
|
|
38
44
|
signInvocation?: typeof signInvocationToken;
|
|
39
45
|
withActiveSigner?: typeof withActiveIdentitySigner;
|
|
40
46
|
timeoutMs?: number;
|
|
@@ -196,17 +202,21 @@ export const createWidgetRoutes = (dependencies: WidgetRouteDependencies = {}) =
|
|
|
196
202
|
widget: DashboardWidget,
|
|
197
203
|
requestId: string | undefined,
|
|
198
204
|
signer: Parameters<Parameters<typeof withActiveIdentitySigner>[1]>[0],
|
|
205
|
+
db: SQL,
|
|
199
206
|
) =>
|
|
200
|
-
signInvocation(
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
207
|
+
signInvocation(
|
|
208
|
+
{
|
|
209
|
+
targetAppId: widget.appId,
|
|
210
|
+
callingAppId: "core",
|
|
211
|
+
operation: widgetInvocationOperation(widget.widgetId),
|
|
212
|
+
schemaHash: null,
|
|
213
|
+
authority: invocationAuthorityFromRequest(auth.getAuthority(c)),
|
|
214
|
+
requestId,
|
|
215
|
+
signer,
|
|
216
|
+
issuer: signer.issuer,
|
|
217
|
+
},
|
|
218
|
+
{ db, groupsAdmin: signer.groupsAdmin },
|
|
219
|
+
);
|
|
210
220
|
|
|
211
221
|
const stream = async (c: Context<AuthContext>, entries: WidgetEntry[], requestId: string | undefined) => {
|
|
212
222
|
const streamDeadline = AbortSignal.timeout(WIDGET_STREAM_DEADLINE_MS);
|
|
@@ -221,8 +231,10 @@ export const createWidgetRoutes = (dependencies: WidgetRouteDependencies = {}) =
|
|
|
221
231
|
tokens = await waitWithin(
|
|
222
232
|
withActiveSigner(
|
|
223
233
|
"invocation",
|
|
224
|
-
(signer) =>
|
|
225
|
-
Promise.all(
|
|
234
|
+
(signer, db) =>
|
|
235
|
+
Promise.all(
|
|
236
|
+
startBounded(entries, WIDGET_CONCURRENCY, async (entry) => sign(c, entry.widget, requestId, signer, db), signing),
|
|
237
|
+
),
|
|
226
238
|
{ signal: signing, timeoutMs },
|
|
227
239
|
),
|
|
228
240
|
signing,
|
|
@@ -316,7 +328,10 @@ export const createWidgetRoutes = (dependencies: WidgetRouteDependencies = {}) =
|
|
|
316
328
|
let requested = new Map<string, DashboardWidgetSize | undefined>();
|
|
317
329
|
try {
|
|
318
330
|
const keys = new Set<string>();
|
|
319
|
-
const declared = await waitWithin(
|
|
331
|
+
const declared = await waitWithin(
|
|
332
|
+
registry().then((entries) => discoverableAppEntries(appAccessAuthority(c), entries, dependencies.listApps)),
|
|
333
|
+
setup,
|
|
334
|
+
);
|
|
320
335
|
const declaredKeys = new Set(declared.map((widget) => widgetKey(widget.appId, widget.widgetId)));
|
|
321
336
|
requested = new Map((c.req.queries("widget") ?? []).map((value) => parseRequestedWidget(value, declaredKeys)));
|
|
322
337
|
widgets = declared.filter((widget) => {
|
|
@@ -351,7 +366,10 @@ export const createWidgetRoutes = (dependencies: WidgetRouteDependencies = {}) =
|
|
|
351
366
|
const setup = AbortSignal.any([c.req.raw.signal, AbortSignal.timeout(timeoutMs)]);
|
|
352
367
|
let declared: DashboardWidget[];
|
|
353
368
|
try {
|
|
354
|
-
declared = await waitWithin(
|
|
369
|
+
declared = await waitWithin(
|
|
370
|
+
registry().then((entries) => discoverableAppEntries(appAccessAuthority(c), entries, dependencies.listApps)),
|
|
371
|
+
setup,
|
|
372
|
+
);
|
|
355
373
|
} catch {
|
|
356
374
|
log.warn("Widget registry unavailable", { reason: setup.aborted ? "deadline_exceeded" : "operation_failed" });
|
|
357
375
|
return c.json({ message: "Widget registry is currently unavailable" }, 503);
|
|
@@ -372,12 +390,17 @@ export const createWidgetRoutes = (dependencies: WidgetRouteDependencies = {}) =
|
|
|
372
390
|
const requestId = normalizeInvocationRequestId(c.req.header("x-request-id"));
|
|
373
391
|
let phase: WidgetPhase = "registry";
|
|
374
392
|
try {
|
|
375
|
-
const widget = (
|
|
393
|
+
const widget = (
|
|
394
|
+
await waitWithin(
|
|
395
|
+
registry().then((entries) => discoverableAppEntries(appAccessAuthority(c), entries, dependencies.listApps)),
|
|
396
|
+
signal,
|
|
397
|
+
)
|
|
398
|
+
).find((entry) => entry.appId === appId && entry.widgetId === widgetId);
|
|
376
399
|
if (!widget) return c.json({ message: "Widget not found" }, 404);
|
|
377
400
|
|
|
378
401
|
phase = "signing";
|
|
379
402
|
const signed = await waitWithin(
|
|
380
|
-
withActiveSigner("invocation", (signer) => sign(c, widget, requestId, signer), { signal, timeoutMs }),
|
|
403
|
+
withActiveSigner("invocation", (signer, db) => sign(c, widget, requestId, signer, db), { signal, timeoutMs }),
|
|
381
404
|
signal,
|
|
382
405
|
);
|
|
383
406
|
|
|
@@ -405,6 +428,7 @@ export const createWidgetRoutes = (dependencies: WidgetRouteDependencies = {}) =
|
|
|
405
428
|
: "Widget is unavailable";
|
|
406
429
|
return Response.json({ message }, { status: 502 });
|
|
407
430
|
} catch (error) {
|
|
431
|
+
if (error instanceof AppAccessDeniedError) return Response.json(appAccessDeniedBody(error.app), { status: 403 });
|
|
408
432
|
const isTimeout = timeout.aborted || (error instanceof Error && error.name === "TimeoutError");
|
|
409
433
|
const reason: WidgetRejectionReason = isTimeout ? "deadline_exceeded" : signal.aborted ? "request_cancelled" : "operation_failed";
|
|
410
434
|
logRejection({ appId, widgetId }, phase, reason);
|
|
@@ -1,12 +1,9 @@
|
|
|
1
1
|
import { dates } from "@k2b/stdlib";
|
|
2
2
|
import { Button, dialogCore, PanelDialog, Placeholder, panelDialogOptions, prompts, useLocale } from "@k2b/ui";
|
|
3
3
|
import { createMemo, createSignal, For, type JSX, onCleanup, onMount, Show } from "solid-js";
|
|
4
|
-
import {
|
|
5
|
-
import { isLegacyActionReview, storedActionReview } from "../ai/action-review";
|
|
6
|
-
import { type ActionCard, legacyActionCard, reviewActionCard } from "../ai/chat/action-card";
|
|
4
|
+
import type { ActionCard } from "../ai/chat/action-card";
|
|
7
5
|
import { actionMessages } from "../ai/chat/action-messages";
|
|
8
6
|
import { ActionQuestion, ActionReceipt } from "../ai/chat/action-question";
|
|
9
|
-
import { aiChatMessages } from "../ai/chat/messages";
|
|
10
7
|
import { refreshAppBadges } from "../browser/app-badges";
|
|
11
8
|
import { coreClient } from "../clients/core";
|
|
12
9
|
import {
|
|
@@ -17,6 +14,7 @@ import {
|
|
|
17
14
|
ApprovalPageSchema,
|
|
18
15
|
type PendingApproval,
|
|
19
16
|
} from "../contracts/approvals";
|
|
17
|
+
import { approvalCard } from "./approval-card";
|
|
20
18
|
import { approvalMessages } from "./messages";
|
|
21
19
|
|
|
22
20
|
/** How a decided request ends in this list. It stays in place as a receipt until the next load. */
|
|
@@ -57,60 +55,13 @@ const useMessages = () => {
|
|
|
57
55
|
return () => approvalMessages.resolve([locale()]).t;
|
|
58
56
|
};
|
|
59
57
|
|
|
60
|
-
const
|
|
61
|
-
item.sentence
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
const approvalTitle = (item: PendingApproval): string => {
|
|
68
|
-
const first = lines(item)[0] ?? item.action;
|
|
69
|
-
const prefix = `${item.app.name}: `;
|
|
70
|
-
return first.startsWith(prefix) && first.length > prefix.length ? first.slice(prefix.length) : first;
|
|
71
|
-
};
|
|
72
|
-
|
|
73
|
-
/** Without a review, the sentence's later `Label: value` lines are what the person approves. */
|
|
74
|
-
const sentenceRows = (item: PendingApproval) =>
|
|
75
|
-
lines(item)
|
|
76
|
-
.slice(1)
|
|
77
|
-
.map((line) => {
|
|
78
|
-
const match = /^([^:]{1,40}):\s+(.+)$/.exec(line);
|
|
79
|
-
return match ? { label: match[1]!, value: match[2] ?? "" } : { label: "", value: line };
|
|
80
|
-
});
|
|
81
|
-
|
|
82
|
-
/**
|
|
83
|
-
* The same card as the chat: a new review gives the app's sentence, reason, and preview; an old review its literal
|
|
84
|
-
* message and rows; a request without a review its sentence. Times show in the reader's zone (UTC until the page
|
|
85
|
-
* passes one, the same on the server and in the first browser render).
|
|
86
|
-
*/
|
|
87
|
-
const approvalCard = (item: PendingApproval, locale: string, timeZone: string): ActionCard => {
|
|
88
|
-
const app = { name: item.app.name, icon: item.app.icon ?? "ti ti-shield-check" };
|
|
89
|
-
const context = { locale, timeZone, app };
|
|
90
|
-
const review = item.review ? storedActionReview(item.review) : null;
|
|
91
|
-
if (review && !isLegacyActionReview(review)) return reviewActionCard(review, context);
|
|
92
|
-
const title = approvalTitle(item);
|
|
93
|
-
const t = aiChatMessages(locale);
|
|
94
|
-
return legacyActionCard(
|
|
95
|
-
{
|
|
96
|
-
question: review?.legacy.question ?? capabilityQuestionText(title, locale),
|
|
97
|
-
rows: review?.legacy.rows ?? sentenceRows(item),
|
|
98
|
-
...(review?.legacy.markdown ? { markdown: review.legacy.markdown } : {}),
|
|
99
|
-
...(review?.legacy.links ? { links: review.legacy.links } : {}),
|
|
100
|
-
subject: title,
|
|
101
|
-
title,
|
|
102
|
-
outcome: {
|
|
103
|
-
running: t.receiptRunning({ title }),
|
|
104
|
-
done: t.receiptDone({ title }),
|
|
105
|
-
rejected: t.receiptRejected({ title }),
|
|
106
|
-
notRun: t.receiptNotRun({ title, stopped: false }),
|
|
107
|
-
unknown: capabilityUnknownText(title, locale),
|
|
108
|
-
unknownStill: capabilityUnknownText(title, locale, true),
|
|
109
|
-
},
|
|
110
|
-
},
|
|
111
|
-
context,
|
|
112
|
-
);
|
|
113
|
-
};
|
|
58
|
+
const cardInput = (item: PendingApproval) => ({
|
|
59
|
+
sentence: item.sentence,
|
|
60
|
+
action: item.action,
|
|
61
|
+
appName: item.app.name,
|
|
62
|
+
appIcon: item.app.icon,
|
|
63
|
+
review: item.review,
|
|
64
|
+
});
|
|
114
65
|
|
|
115
66
|
/** Origin of the request: owning app, the chat it waits in, when Cloud asked, and when it expires. */
|
|
116
67
|
function Origin(props: { item: PendingApproval; withApp?: boolean }) {
|
|
@@ -170,7 +121,7 @@ function ApprovalCard(props: {
|
|
|
170
121
|
}) {
|
|
171
122
|
const t = useMessages();
|
|
172
123
|
const locale = useLocale();
|
|
173
|
-
const card = createMemo(() => approvalCard(props.item, locale(), READER_TIME_ZONE));
|
|
124
|
+
const card = createMemo(() => approvalCard(cardInput(props.item), locale(), READER_TIME_ZONE));
|
|
174
125
|
const current = () => props.outcome ?? (props.failed ? "failed" : "sending");
|
|
175
126
|
const state = () => (props.outcome || props.busy || props.failed ? "decided" : "open");
|
|
176
127
|
// `confirm` asks every time, whatever the stored options say.
|
|
@@ -228,9 +179,11 @@ function BandDecision(props: {
|
|
|
228
179
|
<Button size="sm" variant="ghost" disabled={props.busy} onClick={() => props.decide({ approved: false })}>
|
|
229
180
|
{t().reject}
|
|
230
181
|
</Button>
|
|
231
|
-
<
|
|
232
|
-
{
|
|
233
|
-
|
|
182
|
+
<Show when={!(props.item.review && "contentRef" in props.item.review && props.item.review.contentRef)}>
|
|
183
|
+
<Button size="sm" variant="primary" loading={props.busy} onClick={() => props.decide({ approved: true })}>
|
|
184
|
+
{t().approve}
|
|
185
|
+
</Button>
|
|
186
|
+
</Show>
|
|
234
187
|
</div>
|
|
235
188
|
);
|
|
236
189
|
}
|
|
@@ -251,7 +204,7 @@ const openDetails = (item: PendingApproval, locale: string) =>
|
|
|
251
204
|
dialogCore.open<ApprovalDecisionInput>(
|
|
252
205
|
(close) => {
|
|
253
206
|
const t = () => approvalMessages.resolve([locale]).t;
|
|
254
|
-
const card = approvalCard(item, locale, READER_TIME_ZONE);
|
|
207
|
+
const card = approvalCard(cardInput(item), locale, READER_TIME_ZONE);
|
|
255
208
|
return (
|
|
256
209
|
<PanelDialog>
|
|
257
210
|
<PanelDialog.Header
|
|
@@ -290,7 +243,13 @@ const openDetails = (item: PendingApproval, locale: string) =>
|
|
|
290
243
|
</PanelDialog>
|
|
291
244
|
);
|
|
292
245
|
},
|
|
293
|
-
{
|
|
246
|
+
{
|
|
247
|
+
...panelDialogOptions,
|
|
248
|
+
history: true,
|
|
249
|
+
ariaLabel: approvalMessages
|
|
250
|
+
.resolve([locale])
|
|
251
|
+
.t.approvalFor({ title: approvalCard(cardInput(item), locale, READER_TIME_ZONE).questionText }),
|
|
252
|
+
},
|
|
294
253
|
);
|
|
295
254
|
|
|
296
255
|
function Row(props: {
|
|
@@ -304,6 +263,8 @@ function Row(props: {
|
|
|
304
263
|
}) {
|
|
305
264
|
const t = useMessages();
|
|
306
265
|
const locale = useLocale();
|
|
266
|
+
// The band words the request as the card does: the plain question and Cloud's reason line.
|
|
267
|
+
const card = createMemo(() => approvalCard(cardInput(props.item), locale(), READER_TIME_ZONE));
|
|
307
268
|
const details = async () => {
|
|
308
269
|
const input = await openDetails(props.item, locale());
|
|
309
270
|
if (input) props.decide(input);
|
|
@@ -322,10 +283,13 @@ function Row(props: {
|
|
|
322
283
|
/>
|
|
323
284
|
}
|
|
324
285
|
>
|
|
325
|
-
<article class="cloud-approval-band" data-decided={props.outcome} aria-label={t().approvalFor({ title:
|
|
286
|
+
<article class="cloud-approval-band" data-decided={props.outcome} aria-label={t().approvalFor({ title: card().questionText })}>
|
|
326
287
|
<i class={props.outcome ? outcomeIcon(props.outcome, true) : "ti ti-alert-triangle"} aria-hidden="true" />
|
|
327
288
|
<div class="cloud-approval-band__text">
|
|
328
|
-
<strong>{
|
|
289
|
+
<strong lang={card().locale}>{card().questionText}</strong>
|
|
290
|
+
<span class="cloud-approval-band__reason" lang={card().locale}>
|
|
291
|
+
{card().reason.text}
|
|
292
|
+
</span>
|
|
329
293
|
<Origin item={props.item} withApp={false} />
|
|
330
294
|
</div>
|
|
331
295
|
<div class="cloud-approval__foot">
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { capabilityQuestionText, capabilityUnknownText } from "../_internal/capability-sentences";
|
|
2
|
+
import { type AiStoredActionReview, isLegacyActionReview, storedActionReview } from "../ai/action-review";
|
|
3
|
+
import { type ActionCard, legacyActionCard, reviewActionCard } from "../ai/chat/action-card";
|
|
4
|
+
import { aiChatMessages } from "../ai/chat/messages";
|
|
5
|
+
import type { CapabilityActionReviewData } from "../contracts/capabilities";
|
|
6
|
+
|
|
7
|
+
export type ApprovalCardInput = {
|
|
8
|
+
sentence: string;
|
|
9
|
+
action: string;
|
|
10
|
+
appName: string;
|
|
11
|
+
appIcon?: string | null;
|
|
12
|
+
review: CapabilityActionReviewData | AiStoredActionReview | null;
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
const lines = (item: ApprovalCardInput) =>
|
|
16
|
+
item.sentence
|
|
17
|
+
.split("\n")
|
|
18
|
+
.map((line) => line.trim())
|
|
19
|
+
.filter(Boolean);
|
|
20
|
+
|
|
21
|
+
/** The first line names the app and the Action; the app already shows as the row's icon and origin. */
|
|
22
|
+
const approvalTitle = (item: ApprovalCardInput): string => {
|
|
23
|
+
const first = lines(item)[0] ?? item.action;
|
|
24
|
+
const prefix = `${item.appName}: `;
|
|
25
|
+
return first.startsWith(prefix) && first.length > prefix.length ? first.slice(prefix.length) : first;
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
/** Without a review, the sentence's later `Label: value` lines are what the person approves. */
|
|
29
|
+
const sentenceRows = (item: ApprovalCardInput) =>
|
|
30
|
+
lines(item)
|
|
31
|
+
.slice(1)
|
|
32
|
+
.map((line) => {
|
|
33
|
+
const match = /^([^:]{1,40}):\s+(\S.*)$/.exec(line);
|
|
34
|
+
return match ? { label: match[1]!, value: match[2] ?? "" } : { label: "", value: line };
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* The same card as the chat: a new review gives the app's sentence, reason, and preview; an old review its literal
|
|
39
|
+
* message and rows; a request without a review its sentence. Times show in the reader's zone (UTC until the page
|
|
40
|
+
* passes one, the same on the server and in the first browser render).
|
|
41
|
+
*/
|
|
42
|
+
export const approvalCard = (item: ApprovalCardInput, locale: string, timeZone: string): ActionCard => {
|
|
43
|
+
const app = { name: item.appName, icon: item.appIcon ?? "ti ti-shield-check" };
|
|
44
|
+
const context = { locale, timeZone, app };
|
|
45
|
+
const review = item.review ? ("legacy" in item.review ? item.review : storedActionReview(item.review)) : null;
|
|
46
|
+
if (review && !isLegacyActionReview(review)) return reviewActionCard(review, context);
|
|
47
|
+
const title = approvalTitle(item);
|
|
48
|
+
const t = aiChatMessages(locale);
|
|
49
|
+
return legacyActionCard(
|
|
50
|
+
{
|
|
51
|
+
question: review?.legacy.question ?? capabilityQuestionText(title, locale),
|
|
52
|
+
rows: review?.legacy.rows ?? sentenceRows(item),
|
|
53
|
+
...(review?.legacy.markdown ? { markdown: review.legacy.markdown } : {}),
|
|
54
|
+
...(review?.legacy.links ? { links: review.legacy.links } : {}),
|
|
55
|
+
subject: title,
|
|
56
|
+
title,
|
|
57
|
+
outcome: {
|
|
58
|
+
running: t.receiptRunning({ title }),
|
|
59
|
+
done: t.receiptDone({ title }),
|
|
60
|
+
rejected: t.receiptRejected({ title }),
|
|
61
|
+
notRun: t.receiptNotRun({ title, stopped: false }),
|
|
62
|
+
unknown: capabilityUnknownText(title, locale),
|
|
63
|
+
unknownStill: capabilityUnknownText(title, locale, true),
|
|
64
|
+
},
|
|
65
|
+
},
|
|
66
|
+
context,
|
|
67
|
+
);
|
|
68
|
+
};
|
|
@@ -1,6 +1,12 @@
|
|
|
1
|
+
import { LocaleProvider } from "@k2b/ui";
|
|
2
|
+
import { render } from "solid-js/web";
|
|
1
3
|
import { appAccentStyle } from "../ssr/app-appearance";
|
|
2
4
|
import { createGlobalSearchHost } from "../ssr/global-search-host";
|
|
5
|
+
import { platformMessages } from "../ssr/platform-messages";
|
|
6
|
+
import ShellRuntime from "../ssr/ShellRuntime.island";
|
|
7
|
+
import type { ShellData } from "../ssr/shell-data";
|
|
3
8
|
import { openCloudResourcePicker } from "./resource-picker";
|
|
9
|
+
import { openGlobalSearch } from "./search";
|
|
4
10
|
|
|
5
11
|
/**
|
|
6
12
|
* Opens the real global search, or with `?picker` the resource picker, for a browser test that answers
|
|
@@ -8,7 +14,53 @@ import { openCloudResourcePicker } from "./resource-picker";
|
|
|
8
14
|
*/
|
|
9
15
|
const params = new URLSearchParams(location.search);
|
|
10
16
|
const app = params.get("app") ?? undefined;
|
|
11
|
-
if (params.has("
|
|
17
|
+
if (params.has("launcher")) {
|
|
18
|
+
// Invented authorized shell data, rendered by the real ShellRuntime and global-search host.
|
|
19
|
+
// Role filtering itself is covered at Layout's SSR seam; this harness does not simulate authentication.
|
|
20
|
+
const locale = document.documentElement.lang || "en";
|
|
21
|
+
const de = locale === "de";
|
|
22
|
+
const data: ShellData = {
|
|
23
|
+
apps: [
|
|
24
|
+
{ id: "tools", label: de ? "Werkzeuge" : "Tools", iconClass: "ti ti-tools", href: "/tools", match: "/tools", defaultVisible: false },
|
|
25
|
+
...(params.has("admin")
|
|
26
|
+
? [
|
|
27
|
+
{
|
|
28
|
+
id: "admin",
|
|
29
|
+
label: platformMessages.resolve([locale]).t.admin,
|
|
30
|
+
iconClass: "ti ti-settings",
|
|
31
|
+
href: "/admin",
|
|
32
|
+
match: "/admin",
|
|
33
|
+
defaultVisible: false,
|
|
34
|
+
},
|
|
35
|
+
]
|
|
36
|
+
: []),
|
|
37
|
+
],
|
|
38
|
+
searchLinks: {
|
|
39
|
+
tools: [
|
|
40
|
+
{ label: "Tools", href: "/tools", keywords: ["utilities", "werkzeuge"] },
|
|
41
|
+
{ label: "QR Code", href: "/tools/qr", keywords: ["qr"] },
|
|
42
|
+
],
|
|
43
|
+
},
|
|
44
|
+
legalLinks: [],
|
|
45
|
+
theme: "light",
|
|
46
|
+
};
|
|
47
|
+
const script = document.createElement("script");
|
|
48
|
+
script.id = "cloud-shell-data";
|
|
49
|
+
script.type = "application/json";
|
|
50
|
+
script.textContent = JSON.stringify(data);
|
|
51
|
+
document.body.append(script);
|
|
52
|
+
const host = document.createElement("div");
|
|
53
|
+
document.body.append(host);
|
|
54
|
+
render(
|
|
55
|
+
() => (
|
|
56
|
+
<LocaleProvider locale={locale}>
|
|
57
|
+
<ShellRuntime commands={{ searchResources: !params.has("public") }} />
|
|
58
|
+
</LocaleProvider>
|
|
59
|
+
),
|
|
60
|
+
host,
|
|
61
|
+
);
|
|
62
|
+
openGlobalSearch(app ? { scope: { appId: app, label: app } } : { query: params.get("q") ?? "" });
|
|
63
|
+
} else if (params.has("picker")) {
|
|
12
64
|
// The page shell renders `<body class="k2b-ui app-accent-scope" style="…">` for an app page.
|
|
13
65
|
const style = appAccentStyle(params.get("accent") ?? undefined);
|
|
14
66
|
if (style) {
|
|
@@ -51,15 +51,6 @@ const isLinkableCommand = (command: PaletteCommand) => typeof command.action !==
|
|
|
51
51
|
|
|
52
52
|
const itemKey = (item: SearchItem) => `${item.ref.type}:${item.ref.id}`;
|
|
53
53
|
const URL_BASE = "https://cloud.invalid";
|
|
54
|
-
const groupByApp = (items: SearchItem[]) => {
|
|
55
|
-
const groups = new Map<string, SearchItem[]>();
|
|
56
|
-
for (const item of items) {
|
|
57
|
-
const group = groups.get(item.appId) ?? [];
|
|
58
|
-
group.push(item);
|
|
59
|
-
groups.set(item.appId, group);
|
|
60
|
-
}
|
|
61
|
-
return [...groups.values()].flat();
|
|
62
|
-
};
|
|
63
54
|
|
|
64
55
|
export default function CloudResourceSearch(props: CloudResourceSearchProps) {
|
|
65
56
|
const locale = useLocale();
|
|
@@ -236,9 +227,7 @@ export default function CloudResourceSearch(props: CloudResourceSearchProps) {
|
|
|
236
227
|
/** Rows of the run for the current input; anything older is visible but cannot be chosen. */
|
|
237
228
|
const freshResults = () => current() && shownRun();
|
|
238
229
|
const results = createMemo(() =>
|
|
239
|
-
canSearch()
|
|
240
|
-
? [...groupByApp(navigation()), ...shownBlocks().flatMap((block) => filterCloudResourceSearchItems(block.items, props))]
|
|
241
|
-
: [],
|
|
230
|
+
canSearch() ? [...navigation(), ...shownBlocks().flatMap((block) => filterCloudResourceSearchItems(block.items, props))] : [],
|
|
242
231
|
);
|
|
243
232
|
const searchContext = () => JSON.stringify([desiredUrl(), commandMode(), commandQuery()]);
|
|
244
233
|
/**
|
|
@@ -460,6 +449,7 @@ export default function CloudResourceSearch(props: CloudResourceSearchProps) {
|
|
|
460
449
|
}
|
|
461
450
|
};
|
|
462
451
|
const updateCaret = () => setCaret(inputRef.selectionStart ?? input().length);
|
|
452
|
+
const isApp = (item: SearchItem) => navigation().some((link) => link === item && link.kind === "app");
|
|
463
453
|
const previewMetadata = () => (previewItem()?.metadata ?? []).filter((entry) => entry.label.trim() && entry.value.trim()).slice(0, 5);
|
|
464
454
|
|
|
465
455
|
return (
|
|
@@ -670,7 +660,7 @@ export default function CloudResourceSearch(props: CloudResourceSearchProps) {
|
|
|
670
660
|
<i class={item.icon ?? item.appIcon} aria-hidden="true" />
|
|
671
661
|
<span class="cloud-resource-search__result-copy">
|
|
672
662
|
<span>{item.title}</span>
|
|
673
|
-
<small>{item.preview ?? item.appName}</small>
|
|
663
|
+
<small>{isApp(item) ? t().app : (item.preview ?? item.appName)}</small>
|
|
674
664
|
</span>
|
|
675
665
|
<Show when={commandFor(item)?.shortcut}>{(shortcut) => <Kbd keys={shortcut()} />}</Show>
|
|
676
666
|
<Show when={props.selectionMode && selectedKey() === itemKey(item)}>
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { Button, NoticeCard } from "@k2b/ui";
|
|
2
|
+
import { Show } from "solid-js";
|
|
3
|
+
import { platformMessages } from "../ssr/platform-messages";
|
|
4
|
+
|
|
5
|
+
export default function PanelLeaveNotice(props: { blocked: boolean; locale: string; resume: () => void }) {
|
|
6
|
+
const t = () => platformMessages.resolve([props.locale]).t;
|
|
7
|
+
return (
|
|
8
|
+
<Show when={props.blocked}>
|
|
9
|
+
<div role="status" data-cloud-panel-leave="blocked">
|
|
10
|
+
<NoticeCard tone="warning" title={t().panelLeaveBlocked} detail={t().panelLeaveDetail}>
|
|
11
|
+
<Button variant="ghost" onClick={props.resume}>
|
|
12
|
+
{t().panelLeaveResume}
|
|
13
|
+
</Button>
|
|
14
|
+
</NoticeCard>
|
|
15
|
+
</div>
|
|
16
|
+
</Show>
|
|
17
|
+
);
|
|
18
|
+
}
|