@marimo-team/islands 0.23.16-dev5 → 0.23.16-dev50
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/{ConnectedDataExplorerComponent-CJaUI-S-.js → ConnectedDataExplorerComponent-f9vLjQiF.js} +13 -13
- package/dist/{ErrorBoundary-DXlxsrTF.js → ErrorBoundary-DhLqwSWA.js} +3 -3
- package/dist/{any-language-editor-P9tjkj4n.js → any-language-editor-DifE-fl7.js} +5 -5
- package/dist/{button-BcSml68y.js → button-BSm8IXRU.js} +2 -4
- package/dist/{capabilities-BYLFYTYv.js → capabilities-C6OQ5YBO.js} +1 -1
- package/dist/{chat-ui-BdSDiVjk.js → chat-ui-1e7PcW56.js} +3117 -3120
- package/dist/{check-Cs-KV5XY.js → check-DP7U0H0G.js} +1 -1
- package/dist/{code-visibility-C4wLKnSb.js → common-BcbT1FqC.js} +1046 -1014
- package/dist/{copy-Cs3yt6Vt.js → copy-DNq8q3zw.js} +2 -2
- package/dist/{dist-B96tMAst.js → dist-DIlxLFXG.js} +2 -2
- package/dist/{error-banner-DR7o3CbX.js → error-banner-B_GQfxxD.js} +2 -2
- package/dist/{esm-k8PIvuZW.js → esm-DzZVaNH4.js} +1 -1
- package/dist/{extends-Cq2JYHKy.js → extends-w2_S44bV.js} +4 -4
- package/dist/{formats-BFhkZJwd.js → formats-Dwq1THhr.js} +3 -3
- package/dist/{glide-data-editor--CaN-f27.js → glide-data-editor-B9K6-MJe.js} +9 -9
- package/dist/{html-to-image-DDENewl2.js → html-to-image-3w0AakWW.js} +7164 -6240
- package/dist/{input-DStQIuqD.js → input-C5K4VBvB.js} +7 -7
- package/dist/{label-DIk0QoFe.js → label-DjoYwqmj.js} +2 -2
- package/dist/{loader-CTHCI-qe.js → loader-DX-IDS7n.js} +1 -1
- package/dist/main.js +1010 -1019
- package/dist/{mermaid-CdSvixW2.js → mermaid-hv6IckVw.js} +5 -5
- package/dist/{node-sql-parser-DLl_zNw0.js → node-sql-parser-v1Vi-Fft.js} +16867 -16665
- package/dist/{process-output-2QPcgBQg.js → process-output-BRwR4QGs.js} +32 -28
- package/dist/{reveal-component-BSD4w8YU.js → reveal-component-D7xmRQQ_.js} +725 -643
- package/dist/{spec-CnaxwDqy.js → spec-CQKL_D3i.js} +4 -4
- package/dist/{strings-BYAhZnQi.js → strings-DIy9NAmg.js} +4 -4
- package/dist/style.css +1 -1
- package/dist/{swiper-component-DE94711u.js → swiper-component-CEdAYjb7.js} +2 -2
- package/dist/toDate-CqhBWjvO.js +713 -0
- package/dist/{tooltip-B-qh7UPB.js → tooltip-f-qR0iUx.js} +3 -3
- package/dist/{types-DQl7jVYK.js → types-DicH9Oi0.js} +1 -1
- package/dist/{useAsyncData-CvO8WUy3.js → useAsyncData-CYOQb1EW.js} +1 -1
- package/dist/{useDateFormatter-BSoGim-G.js → useDateFormatter-Bt2qr_XS.js} +2 -2
- package/dist/{useDeepCompareMemoize-J3MKRdA4.js → useDeepCompareMemoize-CSHQeQEI.js} +1 -1
- package/dist/{useIframeCapabilities-POcFXHbw.js → useIframeCapabilities-kA09ykNq.js} +1 -1
- package/dist/{useLifecycle-DqPxHgAN.js → useLifecycle-CMJGqBaY.js} +3 -3
- package/dist/{useTheme-XMq9mNf_.js → useTheme-DYfby65G.js} +2 -2
- package/dist/{vega-component-CrFoZT1S.js → vega-component-C7nO5zcf.js} +10 -10
- package/dist/{zod-D0yOqoGe.js → zod-BUcyXSdR.js} +1 -1
- package/package.json +3 -4
- package/src/__mocks__/requests.ts +45 -4
- package/src/components/data-table/__tests__/__snapshots__/chart-spec-model.test.ts.snap +174 -21
- package/src/components/data-table/__tests__/chart-spec-model.test.ts +102 -1
- package/src/components/data-table/column-summary/chart-spec-model.tsx +163 -103
- package/src/components/data-table/column-summary/legacy-chart-spec.ts +15 -7
- package/src/components/data-table/column-summary/utils.ts +26 -7
- package/src/components/data-table/types.ts +1 -1
- package/src/components/editor/__tests__/viewer-banner.test.tsx +14 -0
- package/src/components/editor/actions/__tests__/pair-with-agent-commands.test.ts +99 -18
- package/src/components/editor/actions/__tests__/pdf-export.test.ts +32 -13
- package/src/components/editor/actions/pair-with-agent-commands.ts +21 -10
- package/src/components/editor/actions/pair-with-agent-modal.tsx +2 -2
- package/src/components/editor/actions/pdf-export.ts +11 -14
- package/src/components/editor/actions/useNotebookActions.tsx +18 -18
- package/src/components/editor/cell/code/__tests__/cell-editor-activity-lifecycle.test.ts +250 -0
- package/src/components/editor/cell/code/cell-editor.tsx +13 -7
- package/src/components/editor/chrome/panels/__tests__/logs-panel.test.tsx +25 -0
- package/src/components/editor/chrome/panels/logs-panel.tsx +12 -2
- package/src/components/editor/chrome/panels/write-secret-modal.tsx +40 -12
- package/src/components/editor/connections/__tests__/json-credentials.test.ts +114 -0
- package/src/components/editor/connections/__tests__/paths.test.ts +39 -0
- package/src/components/editor/connections/components.tsx +6 -2
- package/src/components/editor/connections/database/__tests__/__snapshots__/as-code.test.ts.snap +21 -5
- package/src/components/editor/connections/database/__tests__/as-code.test.ts +17 -0
- package/src/components/editor/connections/database/as-code.ts +16 -2
- package/src/components/editor/connections/database/schemas.ts +6 -5
- package/src/components/editor/connections/form-renderers.tsx +99 -1
- package/src/components/editor/connections/json-credentials.ts +57 -0
- package/src/components/editor/connections/paths.ts +21 -0
- package/src/components/editor/connections/secret-combobox.tsx +41 -20
- package/src/components/editor/connections/storage/__tests__/__snapshots__/as-code.test.ts.snap +88 -2
- package/src/components/editor/connections/storage/__tests__/as-code.test.ts +128 -8
- package/src/components/editor/connections/storage/as-code.ts +79 -36
- package/src/components/editor/connections/storage/schemas.ts +85 -44
- package/src/components/editor/file-tree/__tests__/renderers.test.ts +31 -0
- package/src/components/editor/file-tree/file-viewer.tsx +11 -7
- package/src/components/editor/file-tree/renderers.tsx +21 -0
- package/src/components/editor/renderers/vertical-layout/vertical-layout.tsx +1 -1
- package/src/components/editor/viewer-banner.tsx +4 -1
- package/src/components/forms/options.ts +2 -1
- package/src/components/slides/__tests__/cell-output-block.test.tsx +40 -0
- package/src/components/slides/__tests__/slide-cell-view.test.tsx +33 -0
- package/src/components/slides/__tests__/slide-scroll-hint.test.tsx +198 -0
- package/src/components/slides/reveal-component.tsx +25 -19
- package/src/components/slides/slide-cell-view.tsx +1 -1
- package/src/components/slides/slide-scroll-hint.tsx +161 -0
- package/src/components/slides/slides.css +4 -2
- package/src/components/static-html/share-modal.tsx +1 -1
- package/src/components/ui/input.tsx +1 -1
- package/src/core/ai/context/providers/file.ts +8 -29
- package/src/core/cells/__tests__/cells.test.ts +12 -2
- package/src/core/cells/__tests__/logs.test.ts +5 -5
- package/src/core/cells/cells.ts +17 -4
- package/src/core/cells/document-changes.ts +1 -0
- package/src/core/cells/logs.ts +14 -4
- package/src/core/codemirror/copilot/__tests__/language-server.test.ts +396 -100
- package/src/core/codemirror/copilot/__tests__/transport.test.ts +128 -540
- package/src/core/codemirror/copilot/language-server.ts +182 -70
- package/src/core/codemirror/copilot/transport.ts +15 -207
- package/src/core/codemirror/copilot/types.ts +9 -2
- package/src/core/codemirror/find-replace/__tests__/navigate.test.ts +42 -30
- package/src/core/codemirror/find-replace/navigate.ts +2 -0
- package/src/core/codemirror/go-to-definition/__tests__/commands.test.ts +24 -3
- package/src/core/codemirror/go-to-definition/commands.ts +3 -0
- package/src/core/codemirror/keymaps/__tests__/keymaps.test.ts +31 -2
- package/src/core/codemirror/keymaps/keymaps.ts +12 -0
- package/src/core/codemirror/language/__tests__/sql.test.ts +443 -61
- package/src/core/codemirror/language/languages/python.ts +23 -11
- package/src/core/codemirror/language/languages/sql/completion-store.ts +5 -1
- package/src/core/codemirror/language/languages/sql/sql.ts +210 -3
- package/src/core/codemirror/lsp/__tests__/federated-lsp.test.ts +161 -0
- package/src/core/codemirror/lsp/__tests__/log-messages.test.ts +53 -0
- package/src/core/codemirror/lsp/__tests__/normalize-markdown-math.test.ts +25 -0
- package/src/core/codemirror/lsp/__tests__/notebook-lsp.test.ts +598 -156
- package/src/core/codemirror/lsp/__tests__/transports.test.ts +112 -0
- package/src/core/codemirror/lsp/federated-lsp.ts +76 -23
- package/src/core/codemirror/lsp/log-messages.ts +37 -0
- package/src/core/codemirror/lsp/normalize-markdown-math.ts +8 -4
- package/src/core/codemirror/lsp/notebook-lsp.ts +220 -56
- package/src/core/codemirror/lsp/transports.ts +8 -1
- package/src/core/codemirror/lsp/types.ts +68 -8
- package/src/core/codemirror/lsp/utils.ts +14 -0
- package/src/core/codemirror/utils.ts +28 -0
- package/src/core/edit-app.tsx +1 -2
- package/src/core/export/__tests__/hooks.test.ts +2 -4
- package/src/core/export/hooks.ts +4 -3
- package/src/core/hotkeys/hotkeys.ts +0 -2
- package/src/core/islands/__tests__/bridge.test.ts +107 -0
- package/src/core/islands/bridge.ts +16 -0
- package/src/core/lsp/__tests__/transport.test.ts +509 -337
- package/src/core/lsp/transport.ts +304 -132
- package/src/core/mode.ts +10 -0
- package/src/core/network/__tests__/api.test.ts +42 -0
- package/src/core/network/__tests__/connection.test.ts +45 -0
- package/src/core/network/__tests__/requests-lazy.test.ts +32 -1
- package/src/core/network/__tests__/requests-network.test.ts +21 -0
- package/src/core/network/api.ts +27 -0
- package/src/core/network/connection.ts +15 -0
- package/src/core/network/requests-lazy.ts +1 -0
- package/src/core/network/requests-network.ts +14 -8
- package/src/core/network/requests-static.ts +1 -0
- package/src/core/network/requests-toasting.tsx +1 -0
- package/src/core/network/types.ts +22 -6
- package/src/core/rtc/state.ts +9 -0
- package/src/core/static/download-html.ts +5 -14
- package/src/core/wasm/bridge.ts +4 -2
- package/src/core/wasm/worker/types.ts +5 -2
- package/src/css/app/codemirror.css +16 -2
- package/src/plugins/impl/FormPlugin.tsx +1 -1
- package/src/plugins/impl/__tests__/FormPlugin.test.tsx +162 -0
- package/src/stories/log-viewer.stories.tsx +12 -6
- package/src/utils/__tests__/blob.test.ts +27 -1
- package/src/utils/__tests__/download.test.tsx +44 -4
- package/src/utils/blob.ts +24 -1
- package/src/utils/download.ts +14 -15
- package/dist/toDate-B3IRfHhw.js +0 -692
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
/* Copyright 2026 Marimo. All rights reserved. */
|
|
2
2
|
import { describe, expect, it } from "vitest";
|
|
3
|
+
import { prefixPath } from "../../paths";
|
|
3
4
|
import { prefixSecret } from "../../secrets";
|
|
4
5
|
import { generateStorageCode } from "../as-code";
|
|
5
6
|
import type { StorageConnection } from "../schemas";
|
|
@@ -9,9 +10,13 @@ describe("generateStorageCode", () => {
|
|
|
9
10
|
type: "s3",
|
|
10
11
|
bucket: "my-bucket",
|
|
11
12
|
region: "us-east-1",
|
|
12
|
-
access_key_id: "AKIAIOSFODNN7EXAMPLE",
|
|
13
|
-
secret_access_key: "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY",
|
|
14
13
|
endpoint_url: undefined,
|
|
14
|
+
allow_http: false,
|
|
15
|
+
auth: {
|
|
16
|
+
type: "Access keys",
|
|
17
|
+
access_key_id: "AKIAIOSFODNN7EXAMPLE",
|
|
18
|
+
secret_access_key: "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY",
|
|
19
|
+
},
|
|
15
20
|
};
|
|
16
21
|
|
|
17
22
|
const baseGCS: StorageConnection = {
|
|
@@ -31,8 +36,11 @@ describe("generateStorageCode", () => {
|
|
|
31
36
|
type: "coreweave",
|
|
32
37
|
bucket: "operator-bucket",
|
|
33
38
|
region: "US-EAST-04A",
|
|
34
|
-
|
|
35
|
-
|
|
39
|
+
auth: {
|
|
40
|
+
type: "Access keys",
|
|
41
|
+
access_key_id: "AKIAIOSFODNN7EXAMPLE",
|
|
42
|
+
secret_access_key: "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY",
|
|
43
|
+
},
|
|
36
44
|
};
|
|
37
45
|
|
|
38
46
|
const baseGDrive: StorageConnection = {
|
|
@@ -52,6 +60,8 @@ describe("generateStorageCode", () => {
|
|
|
52
60
|
const conn: StorageConnection = {
|
|
53
61
|
type: "s3",
|
|
54
62
|
bucket: "my-bucket",
|
|
63
|
+
allow_http: false,
|
|
64
|
+
auth: { type: "Access keys" },
|
|
55
65
|
};
|
|
56
66
|
expect(
|
|
57
67
|
generateStorageCode(conn, { library: "obstore" }),
|
|
@@ -68,13 +78,61 @@ describe("generateStorageCode", () => {
|
|
|
68
78
|
).toMatchSnapshot();
|
|
69
79
|
});
|
|
70
80
|
|
|
81
|
+
it("with plain-http endpoint", () => {
|
|
82
|
+
const conn: StorageConnection = {
|
|
83
|
+
...baseS3,
|
|
84
|
+
endpoint_url: "http://cwlota.com",
|
|
85
|
+
allow_http: true,
|
|
86
|
+
};
|
|
87
|
+
expect(
|
|
88
|
+
generateStorageCode(conn, { library: "obstore" }),
|
|
89
|
+
).toMatchSnapshot();
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
it("with container credentials", () => {
|
|
93
|
+
const conn: StorageConnection = {
|
|
94
|
+
...baseS3,
|
|
95
|
+
auth: {
|
|
96
|
+
type: "Container credentials",
|
|
97
|
+
container_credentials_full_uri:
|
|
98
|
+
"http://169.254.170.23/v1/credentials",
|
|
99
|
+
container_authorization_token_file: "/var/run/secrets/token",
|
|
100
|
+
},
|
|
101
|
+
};
|
|
102
|
+
expect(
|
|
103
|
+
generateStorageCode(conn, { library: "obstore" }),
|
|
104
|
+
).toMatchSnapshot();
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
it("with container credentials from secrets", () => {
|
|
108
|
+
const conn: StorageConnection = {
|
|
109
|
+
...baseS3,
|
|
110
|
+
auth: {
|
|
111
|
+
type: "Container credentials",
|
|
112
|
+
container_credentials_full_uri: prefixSecret(
|
|
113
|
+
"AWS_CONTAINER_CREDENTIALS_FULL_URI",
|
|
114
|
+
),
|
|
115
|
+
container_authorization_token_file: prefixSecret(
|
|
116
|
+
"AWS_CONTAINER_AUTHORIZATION_TOKEN_FILE",
|
|
117
|
+
),
|
|
118
|
+
},
|
|
119
|
+
};
|
|
120
|
+
expect(
|
|
121
|
+
generateStorageCode(conn, { library: "obstore" }),
|
|
122
|
+
).toMatchSnapshot();
|
|
123
|
+
});
|
|
124
|
+
|
|
71
125
|
it("with secrets", () => {
|
|
72
126
|
const conn: StorageConnection = {
|
|
73
127
|
type: "s3",
|
|
74
128
|
bucket: "my-bucket",
|
|
75
129
|
region: "us-east-1",
|
|
76
|
-
|
|
77
|
-
|
|
130
|
+
allow_http: false,
|
|
131
|
+
auth: {
|
|
132
|
+
type: "Access keys",
|
|
133
|
+
access_key_id: prefixSecret("AWS_ACCESS_KEY_ID"),
|
|
134
|
+
secret_access_key: prefixSecret("AWS_SECRET_ACCESS_KEY"),
|
|
135
|
+
},
|
|
78
136
|
};
|
|
79
137
|
expect(
|
|
80
138
|
generateStorageCode(conn, { library: "obstore" }),
|
|
@@ -98,6 +156,26 @@ describe("generateStorageCode", () => {
|
|
|
98
156
|
generateStorageCode(conn, { library: "obstore" }),
|
|
99
157
|
).toMatchSnapshot();
|
|
100
158
|
});
|
|
159
|
+
|
|
160
|
+
it("with service account key from secrets", () => {
|
|
161
|
+
const conn: StorageConnection = {
|
|
162
|
+
...baseGCS,
|
|
163
|
+
service_account_key: prefixSecret("GCS_SERVICE_ACCOUNT_KEY"),
|
|
164
|
+
};
|
|
165
|
+
expect(
|
|
166
|
+
generateStorageCode(conn, { library: "obstore" }),
|
|
167
|
+
).toMatchSnapshot();
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
it("with service account key from a file path", () => {
|
|
171
|
+
const conn: StorageConnection = {
|
|
172
|
+
...baseGCS,
|
|
173
|
+
service_account_key: prefixPath("/etc/secrets/gcs.json"),
|
|
174
|
+
};
|
|
175
|
+
expect(
|
|
176
|
+
generateStorageCode(conn, { library: "obstore" }),
|
|
177
|
+
).toMatchSnapshot();
|
|
178
|
+
});
|
|
101
179
|
});
|
|
102
180
|
|
|
103
181
|
describe("Azure", () => {
|
|
@@ -143,6 +221,25 @@ describe("generateStorageCode", () => {
|
|
|
143
221
|
type: "coreweave",
|
|
144
222
|
bucket: "operator-bucket",
|
|
145
223
|
region: "US-EAST-04A",
|
|
224
|
+
auth: { type: "Access keys" },
|
|
225
|
+
};
|
|
226
|
+
expect(
|
|
227
|
+
generateStorageCode(conn, { library: "obstore" }),
|
|
228
|
+
).toMatchSnapshot();
|
|
229
|
+
});
|
|
230
|
+
|
|
231
|
+
it("with container credentials", () => {
|
|
232
|
+
const conn: StorageConnection = {
|
|
233
|
+
...baseCoreWeave,
|
|
234
|
+
auth: {
|
|
235
|
+
type: "Container credentials",
|
|
236
|
+
container_credentials_full_uri: prefixSecret(
|
|
237
|
+
"AWS_CONTAINER_CREDENTIALS_FULL_URI",
|
|
238
|
+
),
|
|
239
|
+
container_authorization_token_file: prefixSecret(
|
|
240
|
+
"AWS_CONTAINER_AUTHORIZATION_TOKEN_FILE",
|
|
241
|
+
),
|
|
242
|
+
},
|
|
146
243
|
};
|
|
147
244
|
expect(
|
|
148
245
|
generateStorageCode(conn, { library: "obstore" }),
|
|
@@ -154,8 +251,11 @@ describe("generateStorageCode", () => {
|
|
|
154
251
|
type: "coreweave",
|
|
155
252
|
bucket: "operator-bucket",
|
|
156
253
|
region: "US-EAST-04A",
|
|
157
|
-
|
|
158
|
-
|
|
254
|
+
auth: {
|
|
255
|
+
type: "Access keys",
|
|
256
|
+
access_key_id: prefixSecret("COREWEAVE_OBJECT_STORAGE_KEY"),
|
|
257
|
+
secret_access_key: prefixSecret("COREWEAVE_OBJECT_STORAGE_SECRET"),
|
|
258
|
+
},
|
|
159
259
|
};
|
|
160
260
|
expect(
|
|
161
261
|
generateStorageCode(conn, { library: "obstore" }),
|
|
@@ -187,6 +287,26 @@ describe("generateStorageCode", () => {
|
|
|
187
287
|
generateStorageCode(conn, { library: "fsspec", isEmbedded: true }),
|
|
188
288
|
).toMatchSnapshot();
|
|
189
289
|
});
|
|
290
|
+
|
|
291
|
+
it("with service account credentials from secrets", () => {
|
|
292
|
+
const conn: StorageConnection = {
|
|
293
|
+
...baseGDrive,
|
|
294
|
+
credentials_json: prefixSecret("GDRIVE_CREDENTIALS_JSON"),
|
|
295
|
+
};
|
|
296
|
+
expect(
|
|
297
|
+
generateStorageCode(conn, { library: "fsspec" }),
|
|
298
|
+
).toMatchSnapshot();
|
|
299
|
+
});
|
|
300
|
+
|
|
301
|
+
it("with service account credentials from a file path", () => {
|
|
302
|
+
const conn: StorageConnection = {
|
|
303
|
+
...baseGDrive,
|
|
304
|
+
credentials_json: prefixPath("/etc/secrets/gdrive.json"),
|
|
305
|
+
};
|
|
306
|
+
expect(
|
|
307
|
+
generateStorageCode(conn, { library: "fsspec" }),
|
|
308
|
+
).toMatchSnapshot();
|
|
309
|
+
});
|
|
190
310
|
});
|
|
191
311
|
|
|
192
312
|
describe("invalid cases", () => {
|
|
@@ -2,8 +2,13 @@
|
|
|
2
2
|
|
|
3
3
|
import dedent from "string-dedent";
|
|
4
4
|
import { assertNever } from "@/utils/assertNever";
|
|
5
|
+
import { escapePythonString, resolveJsonCredential } from "../json-credentials";
|
|
5
6
|
import { isSecret, unprefixSecret } from "../secrets";
|
|
6
|
-
import {
|
|
7
|
+
import {
|
|
8
|
+
type S3Auth,
|
|
9
|
+
type StorageConnection,
|
|
10
|
+
StorageConnectionSchema,
|
|
11
|
+
} from "./schemas";
|
|
7
12
|
|
|
8
13
|
export type StorageLibrary = "obstore" | "fsspec";
|
|
9
14
|
|
|
@@ -50,6 +55,36 @@ class SecretContainer {
|
|
|
50
55
|
}
|
|
51
56
|
}
|
|
52
57
|
|
|
58
|
+
/**
|
|
59
|
+
* Credential kwargs shared by every S3-compatible store.
|
|
60
|
+
*/
|
|
61
|
+
function s3AuthParams(
|
|
62
|
+
auth: S3Auth | undefined,
|
|
63
|
+
secrets: SecretContainer,
|
|
64
|
+
): string[] {
|
|
65
|
+
// obstore polls the credential endpoint with the token read from the file,
|
|
66
|
+
// refreshing credentials for the lifetime of the store.
|
|
67
|
+
if (auth?.type === "Container credentials") {
|
|
68
|
+
return [
|
|
69
|
+
` container_credentials_full_uri=${secrets.print("container_credentials_full_uri", auth.container_credentials_full_uri)},`,
|
|
70
|
+
` container_authorization_token_file=${secrets.print("container_authorization_token_file", auth.container_authorization_token_file)},`,
|
|
71
|
+
];
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
const params: string[] = [];
|
|
75
|
+
if (auth?.access_key_id) {
|
|
76
|
+
params.push(
|
|
77
|
+
` access_key_id=${secrets.print("access_key_id", auth.access_key_id)},`,
|
|
78
|
+
);
|
|
79
|
+
}
|
|
80
|
+
if (auth?.secret_access_key) {
|
|
81
|
+
params.push(
|
|
82
|
+
` secret_access_key=${secrets.print("secret_access_key", auth.secret_access_key)},`,
|
|
83
|
+
);
|
|
84
|
+
}
|
|
85
|
+
return params;
|
|
86
|
+
}
|
|
87
|
+
|
|
53
88
|
function generateS3Code(
|
|
54
89
|
connection: Extract<StorageConnection, { type: "s3" }>,
|
|
55
90
|
secrets: SecretContainer,
|
|
@@ -61,21 +96,15 @@ function generateS3Code(
|
|
|
61
96
|
if (connection.region) {
|
|
62
97
|
params.push(` region=${secrets.print("region", connection.region)},`);
|
|
63
98
|
}
|
|
64
|
-
|
|
65
|
-
params.push(
|
|
66
|
-
` access_key_id=${secrets.print("access_key_id", connection.access_key_id)},`,
|
|
67
|
-
);
|
|
68
|
-
}
|
|
69
|
-
if (connection.secret_access_key) {
|
|
70
|
-
params.push(
|
|
71
|
-
` secret_access_key=${secrets.print("secret_access_key", connection.secret_access_key)},`,
|
|
72
|
-
);
|
|
73
|
-
}
|
|
99
|
+
params.push(...s3AuthParams(connection.auth, secrets));
|
|
74
100
|
if (connection.endpoint_url) {
|
|
75
101
|
params.push(
|
|
76
102
|
` endpoint_url=${secrets.print("endpoint_url", connection.endpoint_url)},`,
|
|
77
103
|
);
|
|
78
104
|
}
|
|
105
|
+
if (connection.allow_http) {
|
|
106
|
+
params.push(" allow_http=True,");
|
|
107
|
+
}
|
|
79
108
|
|
|
80
109
|
const paramsStr = params.length > 0 ? `\n${params.join("\n")}\n` : "";
|
|
81
110
|
|
|
@@ -92,20 +121,36 @@ function generateGCSCode(
|
|
|
92
121
|
const bucket = secrets.print("bucket", connection.bucket);
|
|
93
122
|
const imports = new Set(["from obstore.store import GCSStore"]);
|
|
94
123
|
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
124
|
+
if (!connection.service_account_key) {
|
|
125
|
+
return {
|
|
126
|
+
imports,
|
|
127
|
+
code: dedent(`
|
|
128
|
+
store = GCSStore(${bucket})
|
|
129
|
+
`),
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
const credential = resolveJsonCredential(
|
|
134
|
+
connection.service_account_key,
|
|
135
|
+
(v) => secrets.print("service_account_key", v),
|
|
136
|
+
);
|
|
137
|
+
|
|
138
|
+
if (credential.kind === "path") {
|
|
139
|
+
const code = dedent(`
|
|
100
140
|
store = GCSStore(${bucket},
|
|
101
|
-
|
|
141
|
+
service_account_path="${escapePythonString(credential.path)}",
|
|
102
142
|
)
|
|
103
143
|
`);
|
|
104
|
-
|
|
105
|
-
code = dedent(`
|
|
106
|
-
store = GCSStore(${bucket})
|
|
107
|
-
`);
|
|
144
|
+
return { imports, code };
|
|
108
145
|
}
|
|
146
|
+
|
|
147
|
+
imports.add("import json");
|
|
148
|
+
const code = dedent(`
|
|
149
|
+
_credentials = ${credential.expr}
|
|
150
|
+
store = GCSStore(${bucket},
|
|
151
|
+
service_account_key=_credentials,
|
|
152
|
+
)
|
|
153
|
+
`);
|
|
109
154
|
return { imports, code };
|
|
110
155
|
}
|
|
111
156
|
|
|
@@ -140,16 +185,7 @@ function generateCoreWeaveCode(
|
|
|
140
185
|
` region=${secrets.print("region", connection.region)},`,
|
|
141
186
|
];
|
|
142
187
|
|
|
143
|
-
|
|
144
|
-
params.push(
|
|
145
|
-
` access_key_id=${secrets.print("access_key_id", connection.access_key_id)},`,
|
|
146
|
-
);
|
|
147
|
-
}
|
|
148
|
-
if (connection.secret_access_key) {
|
|
149
|
-
params.push(
|
|
150
|
-
` secret_access_key=${secrets.print("secret_access_key", connection.secret_access_key)},`,
|
|
151
|
-
);
|
|
152
|
-
}
|
|
188
|
+
params.push(...s3AuthParams(connection.auth, secrets));
|
|
153
189
|
|
|
154
190
|
params.push(
|
|
155
191
|
` endpoint="https://${connection.bucket}.cwobject.com",`,
|
|
@@ -176,13 +212,20 @@ function generateGDriveCode(
|
|
|
176
212
|
const imports = new Set(["from gdrive_fsspec import GoogleDriveFileSystem"]);
|
|
177
213
|
|
|
178
214
|
if (connection.credentials_json) {
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
"credentials_json",
|
|
182
|
-
connection.credentials_json,
|
|
215
|
+
const credential = resolveJsonCredential(connection.credentials_json, (v) =>
|
|
216
|
+
secrets.print("credentials_json", v),
|
|
183
217
|
);
|
|
218
|
+
// gdrive-fsspec accepts a filesystem path as `creds` when the string
|
|
219
|
+
// doesn't start with "{"; otherwise it expects a JSON object/string.
|
|
220
|
+
if (credential.kind === "path") {
|
|
221
|
+
const code = dedent(`
|
|
222
|
+
fs = GoogleDriveFileSystem(creds="${escapePythonString(credential.path)}", token="service_account", use_listings_cache=False, skip_instance_cache=True)
|
|
223
|
+
`);
|
|
224
|
+
return { imports, code };
|
|
225
|
+
}
|
|
226
|
+
imports.add("import json");
|
|
184
227
|
const code = dedent(`
|
|
185
|
-
_creds =
|
|
228
|
+
_creds = ${credential.expr}
|
|
186
229
|
fs = GoogleDriveFileSystem(creds=_creds, token="service_account", use_listings_cache=False, skip_instance_cache=True)
|
|
187
230
|
`);
|
|
188
231
|
return { imports, code };
|
|
@@ -2,6 +2,83 @@
|
|
|
2
2
|
import { z } from "zod";
|
|
3
3
|
import { FieldOptions } from "@/components/forms/options";
|
|
4
4
|
|
|
5
|
+
/**
|
|
6
|
+
* Credentials shared by every S3-compatible store (Amazon S3, CoreWeave, ...).
|
|
7
|
+
*
|
|
8
|
+
* Rendered as tabs: either long-lived access keys, or a credential-vending
|
|
9
|
+
* endpoint (ECS/EKS task roles, CoreWeave sandboxes, ...) that obstore polls
|
|
10
|
+
* with a bearer token read from disk.
|
|
11
|
+
*/
|
|
12
|
+
const s3AuthField = () =>
|
|
13
|
+
z
|
|
14
|
+
.discriminatedUnion("type", [
|
|
15
|
+
z.object({
|
|
16
|
+
type: z.literal("Access keys"),
|
|
17
|
+
access_key_id: z
|
|
18
|
+
.string()
|
|
19
|
+
.optional()
|
|
20
|
+
.describe(
|
|
21
|
+
FieldOptions.of({
|
|
22
|
+
label: "Access Key ID",
|
|
23
|
+
description: "Leave empty to use the default credential chain",
|
|
24
|
+
inputType: "password",
|
|
25
|
+
optionRegex:
|
|
26
|
+
"(access.?key.?id|object.?storage.?key|aws.?access.?key)",
|
|
27
|
+
}),
|
|
28
|
+
),
|
|
29
|
+
secret_access_key: z
|
|
30
|
+
.string()
|
|
31
|
+
.optional()
|
|
32
|
+
.describe(
|
|
33
|
+
FieldOptions.of({
|
|
34
|
+
label: "Secret Access Key",
|
|
35
|
+
inputType: "password",
|
|
36
|
+
optionRegex:
|
|
37
|
+
"(secret.?access.?key|object.?storage.?secret|aws.?secret)",
|
|
38
|
+
}),
|
|
39
|
+
),
|
|
40
|
+
}),
|
|
41
|
+
z.object({
|
|
42
|
+
type: z.literal("Container credentials"),
|
|
43
|
+
container_credentials_full_uri: z
|
|
44
|
+
.string()
|
|
45
|
+
.nonempty()
|
|
46
|
+
.describe(
|
|
47
|
+
FieldOptions.of({
|
|
48
|
+
label: "Credentials URI",
|
|
49
|
+
description: "Endpoint that vends credentials to the container",
|
|
50
|
+
placeholder: "http://169.254.170.23/v1/credentials",
|
|
51
|
+
optionRegex: "container.?credentials",
|
|
52
|
+
}),
|
|
53
|
+
),
|
|
54
|
+
container_authorization_token_file: z
|
|
55
|
+
.string()
|
|
56
|
+
.nonempty()
|
|
57
|
+
.describe(
|
|
58
|
+
FieldOptions.of({
|
|
59
|
+
label: "Authorization Token File",
|
|
60
|
+
description: "File holding the token sent to the credentials URI",
|
|
61
|
+
placeholder: "/var/run/secrets/token",
|
|
62
|
+
optionRegex:
|
|
63
|
+
"(container.?authorization.?token.?file|container.?auth.?token)",
|
|
64
|
+
}),
|
|
65
|
+
),
|
|
66
|
+
}),
|
|
67
|
+
])
|
|
68
|
+
.default({ type: "Access keys" })
|
|
69
|
+
.describe(FieldOptions.of({ label: "Credentials", special: "tabs" }));
|
|
70
|
+
|
|
71
|
+
const allowHttpField = () =>
|
|
72
|
+
z
|
|
73
|
+
.boolean()
|
|
74
|
+
.default(false)
|
|
75
|
+
.describe(
|
|
76
|
+
FieldOptions.of({
|
|
77
|
+
label: "Allow HTTP",
|
|
78
|
+
description: "Required for plain-http (non-TLS) endpoints",
|
|
79
|
+
}),
|
|
80
|
+
);
|
|
81
|
+
|
|
5
82
|
export const S3StorageSchema = z
|
|
6
83
|
.object({
|
|
7
84
|
type: z.literal("s3"),
|
|
@@ -25,36 +102,20 @@ export const S3StorageSchema = z
|
|
|
25
102
|
optionRegex: "(region|aws.?region)",
|
|
26
103
|
}),
|
|
27
104
|
),
|
|
28
|
-
access_key_id: z
|
|
29
|
-
.string()
|
|
30
|
-
.optional()
|
|
31
|
-
.describe(
|
|
32
|
-
FieldOptions.of({
|
|
33
|
-
label: "Access Key ID",
|
|
34
|
-
inputType: "password",
|
|
35
|
-
optionRegex: "(access.?key.?id|aws.?access.?key)",
|
|
36
|
-
}),
|
|
37
|
-
),
|
|
38
|
-
secret_access_key: z
|
|
39
|
-
.string()
|
|
40
|
-
.optional()
|
|
41
|
-
.describe(
|
|
42
|
-
FieldOptions.of({
|
|
43
|
-
label: "Secret Access Key",
|
|
44
|
-
inputType: "password",
|
|
45
|
-
optionRegex: "(secret.?access.?key|aws.?secret)",
|
|
46
|
-
}),
|
|
47
|
-
),
|
|
48
105
|
endpoint_url: z
|
|
49
106
|
.string()
|
|
50
107
|
.optional()
|
|
51
108
|
.describe(
|
|
52
109
|
FieldOptions.of({
|
|
53
110
|
label: "Endpoint URL",
|
|
111
|
+
description:
|
|
112
|
+
"Ignored if the AWS_ENDPOINT_URL_S3 environment variable is set",
|
|
54
113
|
placeholder: "https://s3.amazonaws.com",
|
|
55
114
|
optionRegex: "(endpoint|s3.?url|s3.?endpoint)",
|
|
56
115
|
}),
|
|
57
116
|
),
|
|
117
|
+
allow_http: allowHttpField(),
|
|
118
|
+
auth: s3AuthField(),
|
|
58
119
|
})
|
|
59
120
|
.describe(FieldOptions.of({ direction: "two-columns" }));
|
|
60
121
|
|
|
@@ -77,7 +138,7 @@ export const GCSStorageSchema = z
|
|
|
77
138
|
.describe(
|
|
78
139
|
FieldOptions.of({
|
|
79
140
|
label: "Service Account Key (JSON)",
|
|
80
|
-
|
|
141
|
+
special: "secret_textarea",
|
|
81
142
|
}),
|
|
82
143
|
),
|
|
83
144
|
})
|
|
@@ -139,28 +200,7 @@ export const CoreWeaveStorageSchema = z
|
|
|
139
200
|
placeholder: "US-EAST-04A",
|
|
140
201
|
}),
|
|
141
202
|
),
|
|
142
|
-
|
|
143
|
-
.string()
|
|
144
|
-
.optional()
|
|
145
|
-
.describe(
|
|
146
|
-
FieldOptions.of({
|
|
147
|
-
label: "Access Key ID",
|
|
148
|
-
inputType: "password",
|
|
149
|
-
optionRegex:
|
|
150
|
-
"(access.?key.?id|object.?storage.?key|aws.?access.?key)",
|
|
151
|
-
}),
|
|
152
|
-
),
|
|
153
|
-
secret_access_key: z
|
|
154
|
-
.string()
|
|
155
|
-
.optional()
|
|
156
|
-
.describe(
|
|
157
|
-
FieldOptions.of({
|
|
158
|
-
label: "Secret Access Key",
|
|
159
|
-
inputType: "password",
|
|
160
|
-
optionRegex:
|
|
161
|
-
"(secret.?access.?key|object.?storage.?secret|aws.?secret)",
|
|
162
|
-
}),
|
|
163
|
-
),
|
|
203
|
+
auth: s3AuthField(),
|
|
164
204
|
})
|
|
165
205
|
.describe(FieldOptions.of({ direction: "two-columns" }));
|
|
166
206
|
|
|
@@ -174,7 +214,7 @@ export const GoogleDriveStorageSchema = z
|
|
|
174
214
|
FieldOptions.of({
|
|
175
215
|
label: "Service Account JSON",
|
|
176
216
|
description: "Leave empty to use browser-based authentication",
|
|
177
|
-
|
|
217
|
+
special: "secret_textarea",
|
|
178
218
|
}),
|
|
179
219
|
),
|
|
180
220
|
})
|
|
@@ -189,3 +229,4 @@ export const StorageConnectionSchema = z.discriminatedUnion("type", [
|
|
|
189
229
|
]);
|
|
190
230
|
|
|
191
231
|
export type StorageConnection = z.infer<typeof StorageConnectionSchema>;
|
|
232
|
+
export type S3Auth = Extract<StorageConnection, { type: "s3" }>["auth"];
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/* Copyright 2026 Marimo. All rights reserved. */
|
|
2
|
+
import { describe, expect, it } from "vitest";
|
|
3
|
+
import { buildMediaSource } from "../renderers";
|
|
4
|
+
|
|
5
|
+
describe("buildMediaSource", () => {
|
|
6
|
+
it("returns base64 source for binary media", () => {
|
|
7
|
+
expect(
|
|
8
|
+
buildMediaSource({
|
|
9
|
+
contents: "aGVsbG8=",
|
|
10
|
+
mimeType: "image/png",
|
|
11
|
+
isBase64: true,
|
|
12
|
+
}),
|
|
13
|
+
).toEqual({
|
|
14
|
+
base64: "aGVsbG8=",
|
|
15
|
+
mime: "image/png",
|
|
16
|
+
});
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
it("returns UTF-8 data URL for text-based media", () => {
|
|
20
|
+
const svg = '<svg xmlns="http://www.w3.org/2000/svg"></svg>';
|
|
21
|
+
expect(
|
|
22
|
+
buildMediaSource({
|
|
23
|
+
contents: svg,
|
|
24
|
+
mimeType: "image/svg+xml",
|
|
25
|
+
isBase64: false,
|
|
26
|
+
}),
|
|
27
|
+
).toEqual({
|
|
28
|
+
url: `data:image/svg+xml;charset=utf-8,${encodeURIComponent(svg)}`,
|
|
29
|
+
});
|
|
30
|
+
});
|
|
31
|
+
});
|
|
@@ -22,12 +22,11 @@ import { isWasm } from "@/core/wasm/utils";
|
|
|
22
22
|
import { useAsyncData } from "@/hooks/useAsyncData";
|
|
23
23
|
import { ErrorBanner } from "@/plugins/impl/common/error-banner";
|
|
24
24
|
import { copyToClipboard } from "@/utils/copy";
|
|
25
|
-
import type { Base64String } from "@/utils/json/base64";
|
|
26
25
|
import { downloadFile } from "./download";
|
|
27
26
|
import { FilePreviewHeader } from "./file-header";
|
|
28
27
|
import { FilePreviewMetadata } from "./file-preview-metadata";
|
|
29
28
|
import { getFileRenderMode } from "./file-render-mode";
|
|
30
|
-
import { FileContentRenderer } from "./renderers";
|
|
29
|
+
import { buildMediaSource, FileContentRenderer } from "./renderers";
|
|
31
30
|
|
|
32
31
|
export const MAX_FILE_PREVIEW_BYTES = 10 * 1024 * 1024;
|
|
33
32
|
|
|
@@ -226,6 +225,15 @@ export const FileViewer: React.FC<Props> = ({ file, onOpenNotebook }) => {
|
|
|
226
225
|
</Alert>
|
|
227
226
|
);
|
|
228
227
|
|
|
228
|
+
const mediaSource =
|
|
229
|
+
isMedia && data.contents
|
|
230
|
+
? buildMediaSource({
|
|
231
|
+
contents: data.contents,
|
|
232
|
+
mimeType,
|
|
233
|
+
isBase64: data.isBase64 ?? false,
|
|
234
|
+
})
|
|
235
|
+
: undefined;
|
|
236
|
+
|
|
229
237
|
return (
|
|
230
238
|
<>
|
|
231
239
|
{header}
|
|
@@ -239,11 +247,7 @@ export const FileViewer: React.FC<Props> = ({ file, onOpenNotebook }) => {
|
|
|
239
247
|
? (data.contents ?? undefined)
|
|
240
248
|
: undefined
|
|
241
249
|
}
|
|
242
|
-
mediaSource={
|
|
243
|
-
isMedia && data.contents
|
|
244
|
-
? { base64: data.contents as Base64String, mime: mimeType }
|
|
245
|
-
: undefined
|
|
246
|
-
}
|
|
250
|
+
mediaSource={mediaSource}
|
|
247
251
|
readOnly={!isText}
|
|
248
252
|
onChange={isText ? setInternalValue : undefined}
|
|
249
253
|
extensions={isText ? [saveKeymapExtension] : []}
|
|
@@ -60,6 +60,27 @@ function resolveMediaSrc(source: MediaSource): string {
|
|
|
60
60
|
return base64ToDataURL(source.base64, source.mime);
|
|
61
61
|
}
|
|
62
62
|
|
|
63
|
+
/**
|
|
64
|
+
* Build a {@link MediaSource} from file details.
|
|
65
|
+
* Binary media is base64-encoded; text-based media (e.g. SVG) uses a UTF-8 data URL.
|
|
66
|
+
*/
|
|
67
|
+
export function buildMediaSource({
|
|
68
|
+
contents,
|
|
69
|
+
mimeType,
|
|
70
|
+
isBase64,
|
|
71
|
+
}: {
|
|
72
|
+
contents: string;
|
|
73
|
+
mimeType: string;
|
|
74
|
+
isBase64: boolean;
|
|
75
|
+
}): MediaSource {
|
|
76
|
+
if (isBase64) {
|
|
77
|
+
return { base64: contents as Base64String, mime: mimeType };
|
|
78
|
+
}
|
|
79
|
+
return {
|
|
80
|
+
url: `data:${mimeType};charset=utf-8,${encodeURIComponent(contents)}`,
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
|
|
63
84
|
export const CsvViewer: React.FC<{ contents: string }> = ({ contents }) => {
|
|
64
85
|
const data = useMemo(() => parseCsvData(contents), [contents]);
|
|
65
86
|
const [pagination, setPagination] = useState<PaginationState>({
|
|
@@ -51,8 +51,11 @@ export const ViewerBanner = () => {
|
|
|
51
51
|
}
|
|
52
52
|
};
|
|
53
53
|
|
|
54
|
+
// A zero-height sticky row pins the banner to the top-left of the notebook
|
|
55
|
+
// scroll area, so it stays visible while scrolling without reserving space in
|
|
56
|
+
// the flow.
|
|
54
57
|
return (
|
|
55
|
-
<div className="
|
|
58
|
+
<div className="sticky top-2 left-2 z-50 h-0 ml-2 w-fit print:hidden">
|
|
56
59
|
<Banner
|
|
57
60
|
kind="info"
|
|
58
61
|
className="flex items-center gap-2 rounded px-2 py-1 text-xs shadow-sm"
|