Compare commits
45 Commits
python-run
...
vscode-ext
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e9967a5795 | ||
|
|
c468740de8 | ||
|
|
d6465fac4b | ||
|
|
13102782a1 | ||
|
|
3700c3d55f | ||
|
|
68f55e8bb9 | ||
|
|
0719fba2e3 | ||
|
|
ca736a66c1 | ||
|
|
fa88ad06be | ||
|
|
80e4068b48 | ||
|
|
fa94b39701 | ||
|
|
f1f6022f9a | ||
|
|
8bf929bc36 | ||
|
|
00b8842439 | ||
|
|
521f58990d | ||
|
|
e14ea52ae8 | ||
|
|
98ca93c58c | ||
|
|
1acaa07788 | ||
|
|
047b551b22 | ||
|
|
1dc2d6b529 | ||
|
|
384101b892 | ||
|
|
201ed60f78 | ||
|
|
e968a7ebed | ||
|
|
4c896abb7a | ||
|
|
777b1a6d40 | ||
|
|
1bf5266f20 | ||
|
|
aa0c7e22c8 | ||
|
|
c310b67421 | ||
|
|
8035c1cc44 | ||
|
|
be17d7ccfa | ||
|
|
d62c6fd2bb | ||
|
|
437b903e23 | ||
|
|
6d2fbaed2b | ||
|
|
7371226402 | ||
|
|
7575671093 | ||
|
|
fc83596ed4 | ||
|
|
7387609734 | ||
|
|
219ac44245 | ||
|
|
620407a07d | ||
|
|
a8b8d57786 | ||
|
|
57cb759a01 | ||
|
|
de82845536 | ||
|
|
3afd3f13b9 | ||
|
|
54c9596ab5 | ||
|
|
a0b8d00c91 |
1
.gitignore
vendored
@@ -172,3 +172,4 @@ CLAUDE.local.md
|
||||
/resources/python-sources/sqlglot-*.dist-info/
|
||||
__pycache__/
|
||||
|
||||
!vscode-extension/.vscode
|
||||
|
||||
@@ -66,6 +66,7 @@
|
||||
[honey.sql :as sql]
|
||||
[java-time.api :as t]
|
||||
[malli.dev :as malli-dev]
|
||||
[metabase.api-routes.sidecar :as sidecar-routes]
|
||||
[metabase.api.common :as api]
|
||||
[metabase.app-db.core :as mdb]
|
||||
[metabase.app-db.env :as mdb.env]
|
||||
@@ -176,14 +177,28 @@
|
||||
(when-let [outdated-ids (seq (map :id (deleted-inmem-databases)))]
|
||||
(t2/delete! :model/Database :id [:in outdated-ids])))
|
||||
|
||||
(defn start-sidecar!
|
||||
"Start Metabase in sidecar mode. Requires MB_SIDECAR_DIR env var to be set
|
||||
to the serdes export directory path."
|
||||
[]
|
||||
(assert config/sidecar-dir
|
||||
"MB_SIDECAR_DIR env var must be set to the serdes export directory path")
|
||||
(let [server-routes (server/make-sidecar-routes #'sidecar-routes/routes)
|
||||
handler (server/make-sidecar-handler server-routes)]
|
||||
(server/start-web-server! handler))
|
||||
((requiring-resolve 'metabase.sidecar/init!)))
|
||||
|
||||
(defn start!
|
||||
"Start Metabase"
|
||||
[]
|
||||
(server/start-web-server! (server.test-handler/test-handler))
|
||||
(init!)
|
||||
(when config/is-dev?
|
||||
(prune-deleted-inmem-databases!)
|
||||
(with-out-str (malli-dev/start!))))
|
||||
(if config/is-sidecar?
|
||||
(start-sidecar!)
|
||||
(do
|
||||
(server/start-web-server! (server.test-handler/test-handler))
|
||||
(init!)
|
||||
(when config/is-dev?
|
||||
(prune-deleted-inmem-databases!)
|
||||
(with-out-str (malli-dev/start!))))))
|
||||
|
||||
(defn stop!
|
||||
"Stop Metabase"
|
||||
|
||||
@@ -99,5 +99,7 @@
|
||||
;; this handler has cider middlewares installed:
|
||||
:handler (apply nrepl-server/default-handler
|
||||
(conj cider-nrepl/cider-middleware 'refactor-nrepl.middleware/wrap-refactor)))))
|
||||
((requiring-resolve 'dev/start!))
|
||||
(if @(requiring-resolve 'metabase.config.core/is-sidecar?)
|
||||
((requiring-resolve 'dev/start-sidecar!))
|
||||
((requiring-resolve 'dev/start!)))
|
||||
(deref (promise)))
|
||||
|
||||
@@ -23,6 +23,7 @@ const validEntityTypes = [
|
||||
"segment",
|
||||
"snippet",
|
||||
"timeline",
|
||||
"transform",
|
||||
"user",
|
||||
] as const;
|
||||
|
||||
|
||||
@@ -0,0 +1,108 @@
|
||||
import { useEffect, useMemo } from "react";
|
||||
import type { WithRouterProps } from "react-router";
|
||||
|
||||
import { skipToken } from "metabase/api";
|
||||
import {
|
||||
useListDatabaseSchemaTablesQuery,
|
||||
useListDatabasesQuery,
|
||||
} from "metabase/api/database";
|
||||
import { useGetTableQueryMetadataQuery } from "metabase/api/table";
|
||||
import { NotFound } from "metabase/common/components/ErrorPages";
|
||||
import { LoadingAndErrorWrapper } from "metabase/common/components/LoadingAndErrorWrapper";
|
||||
import { dataStudioData } from "metabase/lib/urls/data-studio";
|
||||
|
||||
export function CatalogNameRedirect({ location, router }: WithRouterProps) {
|
||||
const { database, schema, table, field } = location.query;
|
||||
|
||||
const { data: databasesResponse, isLoading: isLoadingDatabases } =
|
||||
useListDatabasesQuery();
|
||||
|
||||
const matchedDatabase = useMemo(
|
||||
() => databasesResponse?.data.find((db) => db.name === database),
|
||||
[databasesResponse, database],
|
||||
);
|
||||
|
||||
const databaseId = matchedDatabase?.id;
|
||||
|
||||
const needsTable = !!table;
|
||||
const { data: tables, isLoading: isLoadingTables } =
|
||||
useListDatabaseSchemaTablesQuery(
|
||||
databaseId != null && needsTable && schema
|
||||
? { id: databaseId, schema }
|
||||
: skipToken,
|
||||
);
|
||||
|
||||
const matchedTable = useMemo(
|
||||
() => tables?.find((t) => t.name === table),
|
||||
[tables, table],
|
||||
);
|
||||
|
||||
const tableId = matchedTable?.id;
|
||||
|
||||
const needsField = !!field;
|
||||
const { data: tableMetadata, isLoading: isLoadingMetadata } =
|
||||
useGetTableQueryMetadataQuery(
|
||||
tableId != null && needsField ? { id: tableId } : skipToken,
|
||||
);
|
||||
|
||||
const matchedField = useMemo(
|
||||
() => tableMetadata?.fields?.find((f) => f.name === field),
|
||||
[tableMetadata, field],
|
||||
);
|
||||
|
||||
const isLoading = isLoadingDatabases || isLoadingTables || isLoadingMetadata;
|
||||
|
||||
const resolvedUrl = useMemo(() => {
|
||||
if (!database || databaseId == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!schema) {
|
||||
return dataStudioData({ databaseId });
|
||||
}
|
||||
|
||||
if (!table) {
|
||||
return dataStudioData({ databaseId, schemaName: schema });
|
||||
}
|
||||
|
||||
if (tableId == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!field) {
|
||||
return dataStudioData({ databaseId, schemaName: schema, tableId });
|
||||
}
|
||||
|
||||
const fieldId = matchedField?.id;
|
||||
if (fieldId == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return dataStudioData({
|
||||
databaseId,
|
||||
schemaName: schema,
|
||||
tableId,
|
||||
tab: "field",
|
||||
fieldId,
|
||||
});
|
||||
}, [database, databaseId, schema, table, tableId, field, matchedField]);
|
||||
|
||||
const isNotFound =
|
||||
!isLoading &&
|
||||
(resolvedUrl == null ||
|
||||
(database && !matchedDatabase) ||
|
||||
(needsTable && tables && !matchedTable) ||
|
||||
(needsField && tableMetadata && !matchedField));
|
||||
|
||||
useEffect(() => {
|
||||
if (!isLoading && resolvedUrl) {
|
||||
router.replace(resolvedUrl);
|
||||
}
|
||||
}, [isLoading, resolvedUrl, router]);
|
||||
|
||||
if (isNotFound) {
|
||||
return <NotFound />;
|
||||
}
|
||||
|
||||
return <LoadingAndErrorWrapper loading={isLoading} />;
|
||||
}
|
||||
@@ -11,11 +11,13 @@ import { DataModelSegmentRevisionHistoryPage } from "metabase/data-studio/segmen
|
||||
import { PLUGIN_DEPENDENCIES } from "metabase/plugins";
|
||||
import { IsAdmin } from "metabase/route-guards";
|
||||
|
||||
import { CatalogNameRedirect } from "./CatalogNameRedirect";
|
||||
import { DataModel } from "./pages/DataModel";
|
||||
|
||||
export function getDataStudioMetadataRoutes() {
|
||||
return (
|
||||
<>
|
||||
<Route path="by-name" component={CatalogNameRedirect} />
|
||||
<IndexRoute component={DataModel} />
|
||||
<Route path="database" component={DataModel} />
|
||||
<Route path="database/:databaseId" component={DataModel} />
|
||||
|
||||
@@ -42,8 +42,8 @@ export function ClausePopover({
|
||||
}
|
||||
}, [active]);
|
||||
|
||||
const content = renderPopover(handleClose);
|
||||
const hasPopover = content !== null && !disabled;
|
||||
const content = disabled ? null : renderPopover(handleClose);
|
||||
const hasPopover = content !== null;
|
||||
|
||||
return (
|
||||
<PreventPopoverExitProvider>
|
||||
|
||||
@@ -57,7 +57,7 @@ export const DataStep = ({
|
||||
|
||||
return (
|
||||
<NotebookCell color={color}>
|
||||
{isOpened || !table || isEmbed ? (
|
||||
{!readOnly && (isOpened || !table || isEmbed) ? (
|
||||
<NotebookDataPicker
|
||||
query={query}
|
||||
stageIndex={stageIndex}
|
||||
|
||||
@@ -40,7 +40,7 @@ export function JoinTablePicker({
|
||||
|
||||
return (
|
||||
<Box aria-label={t`Right table`}>
|
||||
{isOpened || !table || isEmbed ? (
|
||||
{!isReadOnly && (isOpened || !table || isEmbed) ? (
|
||||
<NotebookDataPicker
|
||||
title={t`Pick data to join`}
|
||||
query={query}
|
||||
|
||||
@@ -0,0 +1,70 @@
|
||||
// eslint-disable-next-line no-restricted-imports
|
||||
import { Global, css } from "@emotion/react";
|
||||
import { configureStore } from "@reduxjs/toolkit";
|
||||
import { useMemo } from "react";
|
||||
|
||||
import { defaultFontFiles } from "metabase/css/core/fonts.styled";
|
||||
import { MetabaseReduxProvider } from "metabase/lib/redux/custom-context";
|
||||
import { getMetabaseCssVariables } from "metabase/styled-components/theme/css-variables";
|
||||
import { Box, ThemeProvider, useMantineTheme } from "metabase/ui";
|
||||
import type Question from "metabase-lib/v1/Question";
|
||||
|
||||
import { NotebookProvider } from "../Notebook/context";
|
||||
import { NotebookStepList } from "../NotebookStepList";
|
||||
|
||||
interface ReadOnlyNotebookProps {
|
||||
question: Question;
|
||||
}
|
||||
|
||||
const noop = async () => {};
|
||||
|
||||
const readOnlyStore = configureStore({
|
||||
reducer: {
|
||||
currentUser: () => null,
|
||||
settings: () => ({ values: { "site-locale": "en" }, loading: false }),
|
||||
},
|
||||
middleware: () => [],
|
||||
});
|
||||
|
||||
const fontStyles = css`
|
||||
:root {
|
||||
--mb-default-font-family: "Lato", sans-serif;
|
||||
}
|
||||
${defaultFontFiles()}
|
||||
`;
|
||||
|
||||
function ReadOnlyNotebookInner({ question }: ReadOnlyNotebookProps) {
|
||||
const theme = useMantineTheme();
|
||||
|
||||
const cssVariables = useMemo(
|
||||
() => getMetabaseCssVariables({ theme, whitelabelColors: null }),
|
||||
[theme],
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
<Global styles={cssVariables} />
|
||||
<Global styles={fontStyles} />
|
||||
<NotebookProvider>
|
||||
<Box pos="relative">
|
||||
<NotebookStepList
|
||||
question={question}
|
||||
reportTimezone=""
|
||||
updateQuestion={noop}
|
||||
readOnly
|
||||
/>
|
||||
</Box>
|
||||
</NotebookProvider>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export function ReadOnlyNotebook({ question }: ReadOnlyNotebookProps) {
|
||||
return (
|
||||
<MetabaseReduxProvider store={readOnlyStore}>
|
||||
<ThemeProvider>
|
||||
<ReadOnlyNotebookInner question={question} />
|
||||
</ThemeProvider>
|
||||
</MetabaseReduxProvider>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
export { ReadOnlyNotebook } from "./ReadOnlyNotebook";
|
||||
@@ -13,7 +13,12 @@ import {
|
||||
type TranslateEntityIdResponse,
|
||||
useTranslateEntityIdQuery,
|
||||
} from "./api/entity-id";
|
||||
type ResourceType = "dashboard" | "collection" | "card" | "dashboard-tab";
|
||||
type ResourceType =
|
||||
| "dashboard"
|
||||
| "collection"
|
||||
| "card"
|
||||
| "dashboard-tab"
|
||||
| "transform";
|
||||
type ParamType = "param" | "search";
|
||||
|
||||
type ParamConfig = {
|
||||
|
||||
@@ -4,6 +4,7 @@ import {
|
||||
PLUGIN_DEPENDENCIES,
|
||||
PLUGIN_TRANSFORMS_PYTHON,
|
||||
} from "metabase/plugins";
|
||||
import { createEntityIdRedirect } from "metabase/routes-stable-id-aware";
|
||||
|
||||
import { JobListPage } from "./pages/JobListPage";
|
||||
import { JobPage } from "./pages/JobPage";
|
||||
@@ -36,6 +37,18 @@ export function getDataStudioTransformRoutes() {
|
||||
<Route path="new/query" component={NewQueryTransformPage} />
|
||||
<Route path="new/native" component={NewNativeTransformPage} />
|
||||
<Route path="new/card/:cardId" component={NewCardTransformPage} />
|
||||
<Route
|
||||
path="entity/:entity_id"
|
||||
component={createEntityIdRedirect({
|
||||
parametersToTranslate: [
|
||||
{
|
||||
name: "entity_id",
|
||||
resourceType: "transform",
|
||||
type: "param",
|
||||
},
|
||||
],
|
||||
})}
|
||||
/>
|
||||
<Route path=":transformId" component={TransformQueryPage} />
|
||||
<Route path=":transformId/edit" component={TransformQueryPage} />
|
||||
<Route path=":transformId/run" component={TransformRunPage} />
|
||||
|
||||
0
resources/sidecar/data/.gitkeep
Normal file
116
rspack.notebook-component.config.js
Normal file
@@ -0,0 +1,116 @@
|
||||
/* eslint-env node */
|
||||
/* eslint-disable import/no-commonjs */
|
||||
const rspack = require("@rspack/core");
|
||||
|
||||
const mainConfig = require("./rspack.main.config");
|
||||
const { CSS_CONFIG } = require("./frontend/build/shared/rspack/css-config");
|
||||
|
||||
const ROOT = __dirname;
|
||||
const ENTRY_PATH = ROOT + "/vscode-extension/scripts/notebook-entry.tsx";
|
||||
const OUTPUT_PATH = ROOT + "/vscode-extension/vendor";
|
||||
|
||||
/** @type {import('@rspack/cli').Configuration} */
|
||||
const config = {
|
||||
mode: "production",
|
||||
context: ROOT,
|
||||
devtool: false,
|
||||
|
||||
entry: ENTRY_PATH,
|
||||
|
||||
output: {
|
||||
path: OUTPUT_PATH,
|
||||
filename: "notebook-component.esm.js",
|
||||
library: { type: "module" },
|
||||
module: true,
|
||||
clean: false,
|
||||
},
|
||||
|
||||
experiments: {
|
||||
outputModule: true,
|
||||
},
|
||||
|
||||
resolve: {
|
||||
...mainConfig.resolve,
|
||||
alias: {
|
||||
...mainConfig.resolve.alias,
|
||||
"ee-plugins": ROOT + "/frontend/src/metabase/plugins/noop",
|
||||
"ee-overrides": ROOT + "/frontend/src/metabase/lib/noop",
|
||||
"sdk-ee-plugins": ROOT + "/frontend/src/metabase/plugins/noop",
|
||||
"embedding-sdk-shared": ROOT + "/frontend/src/embedding-sdk-shared",
|
||||
},
|
||||
},
|
||||
|
||||
module: {
|
||||
rules: [
|
||||
{
|
||||
test: /\.(tsx?|jsx?)$/,
|
||||
exclude: /node_modules|cljs/,
|
||||
use: [
|
||||
{
|
||||
loader: "builtin:swc-loader",
|
||||
options: {
|
||||
jsc: {
|
||||
loose: true,
|
||||
transform: {
|
||||
react: { runtime: "automatic" },
|
||||
},
|
||||
parser: { syntax: "typescript", tsx: true },
|
||||
experimental: {
|
||||
plugins: [["@swc/plugin-emotion", { sourceMap: false }]],
|
||||
},
|
||||
},
|
||||
minify: false,
|
||||
env: { targets: ["defaults"] },
|
||||
},
|
||||
},
|
||||
],
|
||||
type: "javascript/auto",
|
||||
},
|
||||
{
|
||||
test: /\.css$/,
|
||||
use: [
|
||||
"style-loader",
|
||||
{ loader: "css-loader", options: CSS_CONFIG },
|
||||
"postcss-loader",
|
||||
],
|
||||
type: "javascript/auto",
|
||||
},
|
||||
{
|
||||
test: /\.(svg|png)$/,
|
||||
type: "asset/resource",
|
||||
resourceQuery: { not: [/component|source/] },
|
||||
},
|
||||
{
|
||||
test: /\.svg$/,
|
||||
type: "asset/source",
|
||||
resourceQuery: /source/,
|
||||
},
|
||||
{
|
||||
test: /\.svg$/i,
|
||||
issuer: /\.[jt]sx?$/,
|
||||
resourceQuery: /component/,
|
||||
use: [{ loader: "@svgr/webpack", options: { ref: true } }],
|
||||
},
|
||||
{
|
||||
test: /\.md$/,
|
||||
type: "asset/source",
|
||||
},
|
||||
],
|
||||
},
|
||||
|
||||
externals: ["react", "react/jsx-runtime", "react-dom", "react-dom/client"],
|
||||
|
||||
optimization: {
|
||||
minimize: false,
|
||||
},
|
||||
|
||||
plugins: [
|
||||
new rspack.DefinePlugin({
|
||||
"process.env.NODE_ENV": JSON.stringify("production"),
|
||||
"process.env.IS_EMBEDDING_SDK": JSON.stringify("false"),
|
||||
}),
|
||||
new rspack.optimize.LimitChunkCountPlugin({ maxChunks: 1 }),
|
||||
],
|
||||
};
|
||||
|
||||
module.exports = config;
|
||||
1988
serialization-spec.md
Normal file
72
src/metabase/api_routes/sidecar.clj
Normal file
@@ -0,0 +1,72 @@
|
||||
(ns metabase.api-routes.sidecar
|
||||
"Limited API route map for local sidecar mode. Only includes routes needed
|
||||
by local dev tools (VS Code extension, etc.)."
|
||||
(:require
|
||||
[clojure.string :as str]
|
||||
[metabase.api.macros :as api.macros]
|
||||
[metabase.api.routes.common :as routes.common :refer [+auth]]
|
||||
[metabase.api.util]
|
||||
[metabase.api.util.handlers :as handlers]
|
||||
[metabase.collections-rest.api]
|
||||
[metabase.dashboards-rest.api]
|
||||
[metabase.eid-translation.api]
|
||||
[metabase.measures.api]
|
||||
[metabase.queries-rest.api]
|
||||
[metabase.transforms-rest.api.transform]
|
||||
[metabase.users-rest.api]
|
||||
[metabase.warehouse-schema-rest.api]
|
||||
[metabase.warehouses-rest.api]))
|
||||
|
||||
(comment metabase.api.util/keep-me
|
||||
metabase.collections-rest.api/keep-me
|
||||
metabase.dashboards-rest.api/keep-me
|
||||
metabase.eid-translation.api/keep-me
|
||||
metabase.measures.api/keep-me
|
||||
metabase.queries-rest.api/keep-me
|
||||
metabase.transforms-rest.api.transform/keep-me
|
||||
metabase.users-rest.api/keep-me
|
||||
metabase.warehouse-schema-rest.api/keep-me
|
||||
metabase.warehouses-rest.api/keep-me)
|
||||
|
||||
(defn- ->handler [x]
|
||||
(cond-> x
|
||||
(simple-symbol? x) api.macros/ns-handler))
|
||||
|
||||
(defn- +auth* [handler] (+auth (->handler handler)))
|
||||
|
||||
(defn- +methods
|
||||
"Wrap handler to only allow the given HTTP methods. Returns 405 for others."
|
||||
[handler allowed-methods]
|
||||
(fn [request respond raise]
|
||||
(if (contains? allowed-methods (:request-method request))
|
||||
(handler request respond raise)
|
||||
(respond {:status 405
|
||||
:headers {"Allow" (str/join ", " (map (comp str/upper-case name) allowed-methods))}
|
||||
:body "Method Not Allowed"}))))
|
||||
|
||||
(defn- +get-only
|
||||
"Wrap handler to only allow GET requests."
|
||||
[handler]
|
||||
(+methods handler #{:get}))
|
||||
|
||||
;;; ↓↓↓ KEEP THIS SORTED ↓↓↓
|
||||
(def ^:private route-map
|
||||
{"/card" (+get-only (+auth* metabase.queries-rest.api/card-routes))
|
||||
"/cards" (+get-only (+auth* metabase.queries-rest.api/cards-routes))
|
||||
"/collection" (+get-only (+auth* 'metabase.collections-rest.api))
|
||||
"/dashboard" (+get-only (+auth* 'metabase.dashboards-rest.api))
|
||||
"/database" (+get-only (+auth* 'metabase.warehouses-rest.api))
|
||||
"/eid-translation" (+get-only 'metabase.eid-translation.api)
|
||||
"/field" (+get-only (+auth* metabase.warehouse-schema-rest.api/field-routes))
|
||||
"/measure" (+get-only (+auth* 'metabase.measures.api))
|
||||
"/table" (+get-only (+auth* metabase.warehouse-schema-rest.api/table-routes))
|
||||
"/transform" (+get-only (+auth* metabase.transforms-rest.api.transform/routes))
|
||||
"/transform-job" (+get-only (+auth* metabase.transforms-rest.api.transform/transform-job-routes))
|
||||
"/transform-tag" (+get-only (+auth* metabase.transforms-rest.api.transform/transform-tag-routes))
|
||||
"/user" (+get-only (+auth* 'metabase.users-rest.api))
|
||||
"/util" (+get-only 'metabase.api.util)})
|
||||
;;; ↑↑↑ KEEP THIS SORTED ↑↑↑
|
||||
|
||||
(def ^{:arglists '([request respond raise])} routes
|
||||
"Ring routes for the sidecar API -- a limited subset of the full Metabase API."
|
||||
(handlers/route-map-handler route-map))
|
||||
@@ -72,6 +72,15 @@
|
||||
|
||||
(alter-var-root #'app-defaults merge ee-app-defaults)
|
||||
|
||||
;; In sidecar mode, force in-memory H2 and default to port 3033
|
||||
(def ^:private sidecar-app-defaults
|
||||
{:mb-db-type "h2"
|
||||
:mb-db-in-memory "true"
|
||||
:mb-jetty-port "3033"})
|
||||
|
||||
(when (some? (not-empty (get env/env :mb-sidecar-dir)))
|
||||
(alter-var-root #'app-defaults merge sidecar-app-defaults))
|
||||
|
||||
(defn config-str
|
||||
"Retrieve value for a single configuration key. Accepts either a keyword or a string.
|
||||
|
||||
@@ -103,6 +112,16 @@
|
||||
(def ^Boolean is-dev? "Are we running in `dev` mode (i.e. in a REPL or via `clojure -M:run`)?" (= :dev run-mode))
|
||||
(def ^Boolean is-prod? "Are we running in `prod` mode (i.e. from a JAR)?" (= :prod run-mode))
|
||||
(def ^Boolean is-test? "Are we running in `test` mode (i.e. via `clojure -X:test`)?" (= :test run-mode))
|
||||
|
||||
(def sidecar-dir
|
||||
"Path to the serdes export directory for sidecar mode, or nil if not in sidecar mode."
|
||||
(not-empty (config-str :mb-sidecar-dir)))
|
||||
|
||||
(def ^Boolean is-sidecar?
|
||||
"Are we running in sidecar mode? When true, Metabase runs with a minimal
|
||||
API surface, H2-only app DB, no authentication overhead, and reduced initialization.
|
||||
Set via the MB_SIDECAR_DIR environment variable (value is the path to a serdes export directory)."
|
||||
(some? sidecar-dir))
|
||||
;; In E2E mode, we can customize the token check URL (e.g., use staging license tokens) while still ensuring that core app logic is unaffected.
|
||||
;; This allows us to run Cypress E2E tests with the production-like behavior.
|
||||
(def ^Boolean is-e2e? "Are we running Cypress E2E tests against the production-ready code?" (= :e2e run-mode))
|
||||
|
||||
@@ -29,6 +29,7 @@
|
||||
[metabase.server.core :as server]
|
||||
[metabase.settings.core :as setting]
|
||||
[metabase.setup.core :as setup]
|
||||
[metabase.sidecar :as sidecar]
|
||||
[metabase.startup.core :as startup]
|
||||
[metabase.system.core :as system]
|
||||
[metabase.task.core :as task]
|
||||
@@ -287,5 +288,7 @@
|
||||
;; run a command like `java --add-opens java.base/java.nio=ALL-UNNAMED -jar metabase.jar migrate release-locks` or
|
||||
;; `clojure -M:run migrate release-locks`
|
||||
(run-cmd cmd init! args)
|
||||
;; with no command line args just start Metabase normally
|
||||
(start-normally)))
|
||||
;; with no command line args just start Metabase normally (or in sidecar mode)
|
||||
(if config/is-sidecar?
|
||||
(sidecar/start!)
|
||||
(start-normally))))
|
||||
|
||||
@@ -13,13 +13,16 @@
|
||||
|
||||
(defn- api-model?
|
||||
[model]
|
||||
(isa? (t2/resolve-model model) :hook/entity-id))
|
||||
(let [resolved (t2/resolve-model model)]
|
||||
(or (isa? resolved :hook/entity-id)
|
||||
(= resolved :model/Database))))
|
||||
|
||||
;;; This is no longer calculated dynamically so we don't have to load ~20 model namespaces just to figure out which ones
|
||||
;;; derive from `:hook/entity-id` just to generate Malli schemas. -- Cam
|
||||
(def ^:private api-name->model
|
||||
"Map of model names used on the API to their corresponding model. A test makes sure this map stays in sync."
|
||||
{:action :model/Action
|
||||
{:database :model/Database
|
||||
:action :model/Action
|
||||
:card :model/Card
|
||||
:collection :model/Collection
|
||||
:dashboard :model/Dashboard
|
||||
@@ -78,6 +81,13 @@
|
||||
(eid-translation.settings/entity-id-translation-counter!
|
||||
(merge-with + processed-result (eid-translation.settings/entity-id-translation-counter)))))
|
||||
|
||||
(defn- select-eid->id
|
||||
"Look up entity identifiers. Database uses :name; everything else uses :entity_id."
|
||||
[model eids]
|
||||
(if (= model :model/Database)
|
||||
(into {} (t2/select-fn->fn :name :id [model :id :name] :name [:in eids]))
|
||||
(into {} (t2/select-fn->fn :entity_id :id [model :id :entity_id] :entity_id [:in eids]))))
|
||||
|
||||
(mu/defn- entity-ids->id-for-model :- [:sequential [:tuple
|
||||
;; We want to pass incorrectly formatted entity-ids through here,
|
||||
;; but this is assumed to be an entity-id:
|
||||
@@ -86,17 +96,19 @@
|
||||
"Given a model and a sequence of entity ids on that model, return a pairs of entity-id, id."
|
||||
[api-name eids]
|
||||
(let [model (->model api-name) ;; This lookup is safe because we've already validated the api-names
|
||||
eid->id (into {} (t2/select-fn->fn :entity_id :id [model :id :entity_id] :entity_id [:in eids]))]
|
||||
eid->id (select-eid->id model eids)]
|
||||
(mapv (fn entity-id-info [entity-id]
|
||||
[entity-id (if-let [id (get eid->id entity-id)]
|
||||
{:id id :type api-name :status :ok}
|
||||
;; handle errors
|
||||
(if (mr/validate EntityId entity-id)
|
||||
{:type api-name
|
||||
:status :not-found}
|
||||
{:type api-name
|
||||
:status :invalid-format
|
||||
:reason (me/humanize (mr/explain EntityId entity-id))}))])
|
||||
(if (= model :model/Database)
|
||||
{:type api-name :status :not-found}
|
||||
(if (mr/validate EntityId entity-id)
|
||||
{:type api-name
|
||||
:status :not-found}
|
||||
{:type api-name
|
||||
:status :invalid-format
|
||||
:reason (me/humanize (mr/explain EntityId entity-id))})))])
|
||||
eids)))
|
||||
|
||||
(defn model->entity-ids->ids
|
||||
|
||||
@@ -20,7 +20,8 @@
|
||||
|
||||
(p/import-vars
|
||||
[metabase.server.handler
|
||||
make-handler]
|
||||
make-handler
|
||||
make-sidecar-handler]
|
||||
[metabase.server.instance
|
||||
instance
|
||||
start-web-server!
|
||||
@@ -29,7 +30,8 @@
|
||||
[metabase.server.protocols
|
||||
Respond]
|
||||
[metabase.server.routes
|
||||
make-routes]
|
||||
make-routes
|
||||
make-sidecar-routes]
|
||||
[metabase.server.middleware.json
|
||||
wrap-json-body
|
||||
wrap-streamed-json-response]
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
[metabase.api.macros :as api.macros]
|
||||
[metabase.config.core :as config]
|
||||
[metabase.server.middleware.auth :as mw.auth]
|
||||
[metabase.sidecar.middleware :as mw.sidecar]
|
||||
[metabase.server.middleware.browser-cookie :as mw.browser-cookie]
|
||||
[metabase.server.middleware.exceptions :as mw.exceptions]
|
||||
[metabase.server.middleware.json :as mw.json]
|
||||
@@ -128,3 +129,50 @@
|
||||
(if config/is-dev?
|
||||
(dev-handler server-routes)
|
||||
(apply-middleware server-routes)))
|
||||
|
||||
;;; ------------------------------------------------- Sidecar Handler -------------------------------------------------
|
||||
|
||||
(def ^:private sidecar-middleware
|
||||
"Reduced middleware stack for local sidecar mode. Removes browser cookies, security headers,
|
||||
session management, static API keys, SSL redirect, and other middleware not needed locally.
|
||||
Inserts `wrap-sidecar-user` to inject a synthetic superuser into every request."
|
||||
[#'mw.exceptions/catch-uncaught-exceptions
|
||||
#'mw.exceptions/catch-api-exceptions
|
||||
#'mw.log/log-api-call
|
||||
#'mw.json/wrap-json-body
|
||||
#'mw.offset-paging/handle-paging
|
||||
#'mw.json/wrap-streamed-json-response
|
||||
#'mw.mp-cache/wrap-metadata-provider-cache
|
||||
#'wrap-keyword-params
|
||||
#'wrap-params
|
||||
#'mw.session/bind-current-user
|
||||
#'mw.sidecar/wrap-sidecar-user
|
||||
#'mw.settings-cache/wrap-settings-cache-check
|
||||
#'mw.misc/add-version
|
||||
#'mw.misc/add-content-type
|
||||
#'wrap-gzip
|
||||
#'mw.request-id/wrap-request-id
|
||||
#'mw.misc/bind-request])
|
||||
|
||||
(mu/defn- apply-sidecar-middleware :- ::api.macros/handler
|
||||
[handler :- ::api.macros/handler]
|
||||
(reduce
|
||||
(fn [handler middleware-fn]
|
||||
(middleware-fn handler))
|
||||
handler
|
||||
sidecar-middleware))
|
||||
|
||||
(mu/defn make-sidecar-handler :- ::api.macros/handler
|
||||
"Create the entry point to the Ring HTTP server for sidecar mode."
|
||||
[server-routes :- ::api.macros/handler]
|
||||
(if config/is-dev?
|
||||
;; In dev, support reloading like the normal dev-handler but with sidecar middleware
|
||||
(let [handler (atom (apply-sidecar-middleware server-routes))]
|
||||
(doseq [varr (cons #'sidecar-middleware sidecar-middleware)
|
||||
:when (instance? clojure.lang.IRef varr)]
|
||||
(add-watch varr ::sidecar-reload (fn [_key _ref _old-state _new-state]
|
||||
(log/infof "%s changed, rebuilding sidecar handler" varr)
|
||||
(reset! handler (apply-sidecar-middleware server-routes)))))
|
||||
(fn sidecar-dev-handler [request respond raise]
|
||||
(@handler request respond raise)))
|
||||
(apply-sidecar-middleware server-routes)))
|
||||
|
||||
@@ -118,5 +118,26 @@
|
||||
;; Anything else (e.g. /user/edit_current) should serve up index.html; React app will handle the rest
|
||||
(GET "*" [] index/index)))
|
||||
|
||||
(mu/defn make-sidecar-routes :- ::api.macros/handler
|
||||
"Create a minimal top-level Ring route handler for sidecar mode.
|
||||
Only includes /api/ routes and the health endpoint -- no index pages,
|
||||
static files, public/embed routes, or auth wrapper."
|
||||
[api-routes :- ::api.macros/handler]
|
||||
#_{:clj-kondo/ignore [:discouraged-var]}
|
||||
(compojure/routes
|
||||
(GET "/" []
|
||||
{:status 200
|
||||
:headers {"Content-Type" "text/html; charset=utf-8"}
|
||||
:body (str "<!DOCTYPE html><html><head><title>Metabase Sidecar</title></head>"
|
||||
"<body><h1>Metabase is running in sidecar mode</h1>"
|
||||
"<p>API routes are available under <code>/api/</code>.</p>"
|
||||
"</body></html>")})
|
||||
(GET "/api/health" [] health-handler)
|
||||
(GET "/readyz" [] health-handler)
|
||||
(GET "/livez" [] livez-handler)
|
||||
(OPTIONS "/api/*" [] {:status 200 :body ""})
|
||||
(context "/api" [] (api-handler api-routes))
|
||||
(route/not-found {:status 404 :body "Not found."})))
|
||||
|
||||
;;; TODO -- if anything changes here we should rebuild these routes? We need a version
|
||||
;;; of [[metabase.server.handler/dev-handler]] for these routes
|
||||
|
||||
163
src/metabase/sidecar.clj
Normal file
@@ -0,0 +1,163 @@
|
||||
(ns metabase.sidecar
|
||||
"Central namespace for sidecar mode. Consolidates sidecar initialization,
|
||||
shutdown, serdes import, user management, and database syncing."
|
||||
(:require
|
||||
[clojure.java.io :as io]
|
||||
[metabase.api-routes.sidecar :as sidecar-routes]
|
||||
[metabase.app-db.core :as mdb]
|
||||
[metabase.classloader.core :as classloader]
|
||||
[metabase.config.core :as config]
|
||||
[metabase.initialization-status.core :as init-status]
|
||||
[metabase.plugins.core :as plugins]
|
||||
[metabase.server.core :as server]
|
||||
[metabase.settings.core :as setting]
|
||||
[metabase.sidecar.middleware :as mw.sidecar]
|
||||
[metabase.sidecar.seed :as sidecar-seed]
|
||||
[metabase.sidecar.watcher :as sidecar-watcher]
|
||||
[metabase.util.log :as log]
|
||||
[toucan2.core :as t2]))
|
||||
|
||||
(set! *warn-on-reflection* true)
|
||||
|
||||
(defonce ^:private sidecar-watcher-state (atom nil))
|
||||
|
||||
(def ^:private import-lock (Object.))
|
||||
|
||||
(def ^:private sidecar-user-email "sidecar@metabase.local")
|
||||
|
||||
(defn- ensure-sidecar-user!
|
||||
"Ensure a superuser exists for sidecar mode. Creates one if needed.
|
||||
Sets the sidecar-user-id atom so the middleware can inject it into requests."
|
||||
[]
|
||||
(let [existing-user (t2/select-one :model/User :is_superuser true
|
||||
{:where [:not= :id config/internal-mb-user-id]})]
|
||||
(if existing-user
|
||||
(do
|
||||
(log/infof "Using existing superuser %s (ID %d) for sidecar mode"
|
||||
(:email existing-user) (:id existing-user))
|
||||
(reset! mw.sidecar/sidecar-user-id (:id existing-user)))
|
||||
(let [new-user (t2/insert-returning-instance!
|
||||
:model/User
|
||||
{:email sidecar-user-email
|
||||
:first_name "Sidecar"
|
||||
:last_name "User"
|
||||
:password (str (random-uuid)) ; random password, never used
|
||||
:is_superuser true})]
|
||||
(log/infof "Created sidecar superuser %s (ID %d)" sidecar-user-email (:id new-user))
|
||||
(reset! mw.sidecar/sidecar-user-id (:id new-user))))))
|
||||
|
||||
(defn- ensure-sidecar-databases!
|
||||
"Sync Database records with the sidecar export directory. Creates stub records
|
||||
for new databases and removes records for databases no longer in the export."
|
||||
[]
|
||||
(let [db-dir (io/file config/sidecar-dir "databases")]
|
||||
(when (.isDirectory db-dir)
|
||||
(let [export-db-names (into #{}
|
||||
(comp (filter #(.isDirectory ^java.io.File %))
|
||||
(map #(.getName ^java.io.File %)))
|
||||
(.listFiles db-dir))
|
||||
existing-dbs (t2/select :model/Database)]
|
||||
;; Create stubs for databases in the export that don't exist in the app DB
|
||||
(doseq [db-name export-db-names]
|
||||
(when-not (some #(= db-name (:name %)) existing-dbs)
|
||||
(log/infof "Creating stub database '%s' for sidecar mode" db-name)
|
||||
(t2/insert! :model/Database
|
||||
{:name db-name
|
||||
:engine :h2
|
||||
:details {}
|
||||
:initial_sync_status "complete"})))
|
||||
;; Remove databases no longer in the export directory
|
||||
(doseq [db existing-dbs]
|
||||
(when-not (contains? export-db-names (:name db))
|
||||
(log/infof "Removing stale database '%s' no longer in sidecar export" (:name db))
|
||||
(t2/delete! :model/Database :id (:id db))))))))
|
||||
|
||||
(defn- import-sidecar-serdes!
|
||||
"Import serdes data from the sidecar directory into the H2 app DB."
|
||||
[]
|
||||
(locking import-lock
|
||||
(ensure-sidecar-databases!)
|
||||
(let [dir config/sidecar-dir]
|
||||
(log/infof "Importing serdes data from %s ..." dir)
|
||||
(let [v2-load-internal! (requiring-resolve
|
||||
'metabase-enterprise.serialization.cmd/v2-load-internal!)
|
||||
result (v2-load-internal! dir
|
||||
{:backfill? true
|
||||
:continue-on-error true
|
||||
:reindex? false}
|
||||
:token-check? false
|
||||
:require-initialized-db? false)]
|
||||
(if (seq (:errors result))
|
||||
(log/warnf "Serdes import completed with %d errors" (count (:errors result)))
|
||||
(log/infof "Serdes import completed successfully (%d entities)" (count (:seen result))))))))
|
||||
|
||||
(defn- destroy-sidecar!
|
||||
"Shutdown function for sidecar mode."
|
||||
[]
|
||||
(log/info "Metabase Sidecar Shutting Down ...")
|
||||
(when-let [state @sidecar-watcher-state]
|
||||
(sidecar-watcher/stop-watcher! state))
|
||||
(server/stop-web-server!)
|
||||
(let [timeout-seconds 20]
|
||||
(mdb/release-migration-locks! timeout-seconds))
|
||||
(log/info "Metabase Sidecar Shutdown COMPLETE"))
|
||||
|
||||
(defn init!
|
||||
"Minimal initialization for sidecar mode. Skips scheduler, sample data, Prometheus,
|
||||
audit DB, embedding settings, cloud migration, notification seeding, and other
|
||||
heavyweight startup steps."
|
||||
[]
|
||||
#_{:clj-kondo/ignore [:discouraged-var]}
|
||||
(println (str "\n"
|
||||
"==============================================\n"
|
||||
" !! SIDECAR MODE !! \n"
|
||||
"\n"
|
||||
" Data Dir: " config/sidecar-dir "\n"
|
||||
" Port: " (config/config-int :mb-jetty-port) "\n"
|
||||
"==============================================\n"))
|
||||
(log/infof "Starting Metabase Sidecar version %s ..." config/mb-version-string)
|
||||
(when (not= :h2 (config/config-kw :mb-db-type))
|
||||
(log/warn "Sidecar mode only supports H2. Ignoring MB_DB_TYPE and using H2."))
|
||||
(init-status/set-progress! 0.1)
|
||||
;; Register shutdown hook
|
||||
(.addShutdownHook (Runtime/getRuntime) (Thread. ^Runnable destroy-sidecar!))
|
||||
(init-status/set-progress! 0.2)
|
||||
;; Install classloader
|
||||
(classloader/the-classloader)
|
||||
;; Load plugins (needed for DB drivers)
|
||||
(plugins/load-plugins!)
|
||||
(init-status/set-progress! 0.3)
|
||||
(setting/validate-settings-formatting!)
|
||||
;; Setup & migrate H2 DB
|
||||
(log/info "Setting up sidecar H2 database...")
|
||||
(mdb/setup-db! :create-sample-content? false)
|
||||
(init-status/set-progress! 0.6)
|
||||
;; Load seed data from CSV files
|
||||
(sidecar-seed/load-seed-data!)
|
||||
(init-status/set-progress! 0.7)
|
||||
;; Create/find the sidecar superuser
|
||||
(ensure-sidecar-user!)
|
||||
(init-status/set-progress! 0.8)
|
||||
;; Import serdes data from the sidecar directory
|
||||
(import-sidecar-serdes!)
|
||||
;; Start watching for file changes and re-import on change
|
||||
(reset! sidecar-watcher-state
|
||||
(sidecar-watcher/start-watcher! config/sidecar-dir import-sidecar-serdes!))
|
||||
(init-status/set-progress! 0.9)
|
||||
(init-status/set-complete!)
|
||||
(log/info "Metabase Sidecar Initialization COMPLETE"))
|
||||
|
||||
(defn start!
|
||||
"Start Metabase in sidecar mode. Sets up routes, handler, web server, and
|
||||
runs sidecar initialization."
|
||||
[]
|
||||
(try
|
||||
(let [server-routes (server/make-sidecar-routes #'sidecar-routes/routes)
|
||||
handler (server/make-sidecar-handler server-routes)]
|
||||
(server/start-web-server! handler))
|
||||
(init!)
|
||||
(when (config/config-bool :mb-jetty-join)
|
||||
(.join (server/instance)))
|
||||
(catch Throwable e
|
||||
(log/error e "Metabase Sidecar Initialization FAILED")
|
||||
(System/exit 1))))
|
||||
22
src/metabase/sidecar/middleware.clj
Normal file
@@ -0,0 +1,22 @@
|
||||
(ns metabase.sidecar.middleware
|
||||
"Middleware for local sidecar mode that injects a synthetic superuser into every
|
||||
request. This replaces `wrap-session-key` + `wrap-current-user-info` so that the
|
||||
existing `+auth` checks (which look for `:metabase-user-id`) pass through, and
|
||||
all downstream `*current-user*` bindings work via `bind-current-user`.")
|
||||
|
||||
(def sidecar-user-id
|
||||
"The user ID of the sidecar superuser. Set during sidecar initialization by
|
||||
[[metabase.sidecar/ensure-sidecar-user!]]."
|
||||
(atom nil))
|
||||
|
||||
(defn wrap-sidecar-user
|
||||
"Ring middleware that injects the sidecar superuser into every request.
|
||||
This satisfies the `:metabase-user-id` check in `+auth` and provides
|
||||
superuser privileges for all sidecar requests."
|
||||
[handler]
|
||||
(fn [request respond raise]
|
||||
(handler (assoc request
|
||||
:metabase-user-id @sidecar-user-id
|
||||
:is-superuser? true
|
||||
:is-group-manager? false)
|
||||
respond raise)))
|
||||
67
src/metabase/sidecar/seed.clj
Normal file
@@ -0,0 +1,67 @@
|
||||
(ns metabase.sidecar.seed
|
||||
"Loads CSV files from `resources/sidecar/data/` into the H2 application database
|
||||
on sidecar startup. Each CSV file is loaded into a table whose name matches the
|
||||
file name (without the `.csv` extension). The first row of each CSV is treated as
|
||||
column headers."
|
||||
(:require
|
||||
[clojure.data.csv :as csv]
|
||||
[clojure.java.io :as io]
|
||||
[clojure.java.jdbc :as jdbc]
|
||||
[clojure.string :as str]
|
||||
[metabase.app-db.core :as mdb]
|
||||
[metabase.util.log :as log]))
|
||||
|
||||
(set! *warn-on-reflection* true)
|
||||
|
||||
(defn- csv-resource-files
|
||||
"Return a seq of [table-name, URL] pairs for all CSV files in `resources/sidecar/data/`."
|
||||
[]
|
||||
(when-let [seed-dir (io/resource "sidecar/data")]
|
||||
(let [dir (io/file seed-dir)]
|
||||
(when (.isDirectory dir)
|
||||
(for [^java.io.File f (sort-by #(.getName ^java.io.File %) (.listFiles dir))
|
||||
:when (and (.isFile f)
|
||||
(str/ends-with? (.getName f) ".csv"))]
|
||||
[(str/replace (.getName f) #"\.csv$" "")
|
||||
f])))))
|
||||
|
||||
(defn- read-csv [file]
|
||||
(with-open [reader (io/reader file)]
|
||||
(doall (csv/read-csv reader))))
|
||||
|
||||
(defn- create-table-and-insert!
|
||||
"Create a table with the given name and insert all rows from the CSV data.
|
||||
The first row is treated as column headers. All columns are created as VARCHAR(1024)."
|
||||
[ds table-name csv-data]
|
||||
(let [headers (first csv-data)
|
||||
rows (rest csv-data)
|
||||
columns (map #(str/replace % #"[^a-zA-Z0-9_]" "_") headers)
|
||||
col-defs (str/join ", " (map #(str % " VARCHAR(1024)") columns))]
|
||||
;; Drop existing table if it exists, then create fresh
|
||||
(jdbc/execute! {:datasource ds} [(str "DROP TABLE IF EXISTS " table-name)])
|
||||
(jdbc/execute! {:datasource ds} [(str "CREATE TABLE " table-name " (" col-defs ")")])
|
||||
(when (seq rows)
|
||||
(let [placeholders (str/join ", " (repeat (count columns) "?"))
|
||||
insert-sql (str "INSERT INTO " table-name " VALUES (" placeholders ")")]
|
||||
(jdbc/execute! {:datasource ds}
|
||||
(into [insert-sql] (first rows)))
|
||||
(doseq [row (rest rows)]
|
||||
(jdbc/execute! {:datasource ds}
|
||||
(into [insert-sql] row)))))
|
||||
(log/infof "Loaded %d rows into table %s from CSV" (count rows) table-name)))
|
||||
|
||||
(defn load-seed-data!
|
||||
"Load all CSV files from `resources/sidecar/data/` into the application database.
|
||||
Each file `foo.csv` is loaded into a table called `foo`."
|
||||
[]
|
||||
(let [files (csv-resource-files)]
|
||||
(if (seq files)
|
||||
(let [ds (mdb/data-source)]
|
||||
(doseq [[table-name file] files]
|
||||
(try
|
||||
(let [csv-data (read-csv file)]
|
||||
(when (seq csv-data)
|
||||
(create-table-and-insert! ds table-name csv-data)))
|
||||
(catch Exception e
|
||||
(log/errorf e "Failed to load seed data from %s" (.getName ^java.io.File file))))))
|
||||
(log/info "No sidecar seed CSV files found in resources/sidecar/data/"))))
|
||||
115
src/metabase/sidecar/watcher.clj
Normal file
@@ -0,0 +1,115 @@
|
||||
(ns metabase.sidecar.watcher
|
||||
"Watches a directory tree for file changes and triggers a callback after a
|
||||
debounce period. Used in sidecar mode to re-import serdes data when files
|
||||
change on disk (e.g. from a `git pull`)."
|
||||
(:require
|
||||
[clojure.java.io :as io]
|
||||
[metabase.util.log :as log])
|
||||
(:import
|
||||
(java.io File)
|
||||
(java.nio.file ClosedWatchServiceException FileSystems Path
|
||||
StandardWatchEventKinds WatchEvent$Kind WatchService)))
|
||||
|
||||
(set! *warn-on-reflection* true)
|
||||
|
||||
(defn- register-recursive!
|
||||
"Register all directories under `root` (inclusive) with `watcher` for
|
||||
CREATE, MODIFY, and DELETE events."
|
||||
[^WatchService watcher ^File root]
|
||||
(doseq [^File dir (filter #(.isDirectory ^File %) (file-seq root))]
|
||||
(try
|
||||
(.register (.toPath dir) watcher
|
||||
(into-array WatchEvent$Kind
|
||||
[StandardWatchEventKinds/ENTRY_CREATE
|
||||
StandardWatchEventKinds/ENTRY_MODIFY
|
||||
StandardWatchEventKinds/ENTRY_DELETE]))
|
||||
(catch Exception e
|
||||
(log/debugf e "Failed to register watcher for directory: %s" dir)))))
|
||||
|
||||
(defn- watcher-thread
|
||||
"Blocks on WatchService events, updating `last-change-time` on any file event.
|
||||
When a new directory is created, registers it with the watcher too."
|
||||
[^WatchService watcher state-atom ^File root-dir]
|
||||
(loop []
|
||||
(when (:running? @state-atom)
|
||||
(let [key (try
|
||||
(.take watcher)
|
||||
(catch ClosedWatchServiceException _
|
||||
nil)
|
||||
(catch Exception e
|
||||
(log/warnf e "Unexpected error in sidecar file watcher")
|
||||
nil))]
|
||||
(when key
|
||||
(try
|
||||
(doseq [event (.pollEvents key)]
|
||||
(let [context (.context event)
|
||||
kind (.kind event)]
|
||||
;; If a new directory was created, register it
|
||||
(when (and (= kind StandardWatchEventKinds/ENTRY_CREATE)
|
||||
(instance? Path context))
|
||||
(let [^Path dir-path (.watchable key)
|
||||
child-path (.resolve dir-path ^Path context)
|
||||
child-file (.toFile child-path)]
|
||||
(when (.isDirectory child-file)
|
||||
(register-recursive! watcher child-file))))))
|
||||
;; Mark that a change happened
|
||||
(swap! state-atom assoc :last-change-time (System/currentTimeMillis))
|
||||
(catch Exception e
|
||||
(log/warnf e "Error processing file watcher events")))
|
||||
(.reset key)
|
||||
(recur))))))
|
||||
|
||||
(defn- debounce-thread
|
||||
"Polls `state-atom` every 500ms. When `last-change-time` is set and more than
|
||||
`debounce-ms` ago, resets it and calls `on-change`."
|
||||
[state-atom on-change debounce-ms]
|
||||
(loop []
|
||||
(when (:running? @state-atom)
|
||||
(Thread/sleep 500)
|
||||
(let [{:keys [last-change-time]} @state-atom]
|
||||
(when (and last-change-time
|
||||
(> (- (System/currentTimeMillis) ^long last-change-time)
|
||||
^long debounce-ms))
|
||||
(swap! state-atom assoc :last-change-time nil)
|
||||
(try
|
||||
(on-change)
|
||||
(catch Exception e
|
||||
(log/errorf e "Error in sidecar watcher on-change callback")))))
|
||||
(recur))))
|
||||
|
||||
(defn start-watcher!
|
||||
"Start watching `dir-path` recursively for file changes. Calls `on-change`
|
||||
after `debounce-ms` (default 2000) of quiet. Returns a state atom that should
|
||||
be passed to [[stop-watcher!]] to clean up."
|
||||
([dir-path on-change]
|
||||
(start-watcher! dir-path on-change 2000))
|
||||
([dir-path on-change debounce-ms]
|
||||
(let [root-dir (io/file dir-path)
|
||||
watcher (.newWatchService (FileSystems/getDefault))
|
||||
state (atom {:watcher watcher
|
||||
:running? true
|
||||
:last-change-time nil})]
|
||||
(register-recursive! watcher root-dir)
|
||||
(log/infof "Starting sidecar file watcher on %s (debounce %dms)" dir-path debounce-ms)
|
||||
;; Watcher thread — blocks on OS-level file events
|
||||
(doto (Thread. ^Runnable #(watcher-thread watcher state root-dir)
|
||||
"sidecar-file-watcher")
|
||||
(.setDaemon true)
|
||||
(.start))
|
||||
;; Debounce thread — polls for quiet period then triggers callback
|
||||
(doto (Thread. ^Runnable #(debounce-thread state on-change debounce-ms)
|
||||
"sidecar-file-watcher-debounce")
|
||||
(.setDaemon true)
|
||||
(.start))
|
||||
state)))
|
||||
|
||||
(defn stop-watcher!
|
||||
"Stop the file watcher and its debounce thread."
|
||||
[state-atom]
|
||||
(log/info "Stopping sidecar file watcher")
|
||||
(swap! state-atom assoc :running? false)
|
||||
(when-let [^WatchService watcher (:watcher @state-atom)]
|
||||
(try
|
||||
(.close watcher)
|
||||
(catch Exception e
|
||||
(log/debugf e "Error closing WatchService")))))
|
||||
@@ -132,6 +132,20 @@
|
||||
:timeline [timeline_eid]
|
||||
:user [core_user_eid]}))))))
|
||||
|
||||
(deftest ^:parallel database-translation-test
|
||||
(testing "Database translation by name"
|
||||
(mt/with-temp [:model/Database {db-id :id db-name :name} {:name "test-eid-db" :engine :h2}]
|
||||
(is (= {db-name {:id db-id :type :database :status :ok}}
|
||||
(eid-translation.util/model->entity-ids->ids {:database [db-name]})))))
|
||||
(testing "Database not found returns :not-found (never :invalid-format)"
|
||||
(is (= {"nonexistent-db-name" {:type :database :status :not-found}}
|
||||
(eid-translation.util/model->entity-ids->ids {:database ["nonexistent-db-name"]}))))
|
||||
(testing "->id works with :database and :model/Database"
|
||||
(mt/with-temp [:model/Database {db-id :id db-name :name} {:name "test-eid-db-2" :engine :h2}]
|
||||
(is (= db-id (eid-translation.util/->id :database db-name)))
|
||||
(is (= db-id (eid-translation.util/->id :model/Database db-name)))
|
||||
(is (= db-id (eid-translation.util/->id :database db-id))))))
|
||||
|
||||
(deftest ^:parallel missing-entity-translations-test
|
||||
(is (= {"abcdefghijklmnopqrstu" {:type :card, :status :not-found}}
|
||||
(eid-translation.util/model->entity-ids->ids {:card ["abcdefghijklmnopqrstu"]}))))
|
||||
|
||||
29
vscode-extension/.claude/settings.local.json
Normal file
@@ -0,0 +1,29 @@
|
||||
{
|
||||
"permissions": {
|
||||
"allow": [
|
||||
"Bash(npx tsdown:*)",
|
||||
"Bash(npm ls:*)",
|
||||
"Bash(node:*)",
|
||||
"WebSearch",
|
||||
"WebFetch(domain:kermanx.com)",
|
||||
"WebFetch(domain:github.com)",
|
||||
"WebFetch(domain:hawkticehurst.com)",
|
||||
"WebFetch(domain:githubnext.com)",
|
||||
"WebFetch(domain:www.kenmuse.com)",
|
||||
"WebFetch(domain:code.visualstudio.com)",
|
||||
"WebFetch(domain:www.eliostruyf.com)",
|
||||
"WebFetch(domain:dev.to)",
|
||||
"WebFetch(domain:medium.com)",
|
||||
"WebFetch(domain:vogella.com)",
|
||||
"Bash(find:*)",
|
||||
"Bash(ls:*)",
|
||||
"Bash(pnpm run build:ext)",
|
||||
"Bash(npx tsc --noEmit)",
|
||||
"Bash(npx vitest run)",
|
||||
"Bash(bun run typecheck)",
|
||||
"Bash(bun run build:ext)",
|
||||
"Bash(bun run update)",
|
||||
"Bash(bunx tsc --noEmit)"
|
||||
]
|
||||
}
|
||||
}
|
||||
15
vscode-extension/.gitignore
vendored
Normal file
@@ -0,0 +1,15 @@
|
||||
.cache
|
||||
.DS_Store
|
||||
.idea
|
||||
*.log
|
||||
*.tgz
|
||||
*.vsix
|
||||
coverage
|
||||
dist
|
||||
lib-cov
|
||||
logs
|
||||
node_modules
|
||||
temp
|
||||
src/generated
|
||||
vendor/
|
||||
.env
|
||||
6
vscode-extension/.vscode/extensions.json
vendored
Normal file
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"recommendations": [
|
||||
"amodio.tsl-problem-matcher",
|
||||
"emeraldwalk.runonsave"
|
||||
]
|
||||
}
|
||||
16
vscode-extension/.vscode/launch.json
vendored
Normal file
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"version": "0.1.0",
|
||||
"configurations": [
|
||||
{
|
||||
"name": "Run Extension",
|
||||
"type": "extensionHost",
|
||||
"request": "launch",
|
||||
"args": [
|
||||
"--extensionDevelopmentPath=${workspaceFolder}",
|
||||
"/Users/nickfitzpatrick/Projects/Hackathon-Sync"
|
||||
],
|
||||
"outFiles": ["${workspaceFolder}/dist/**/*.js"],
|
||||
"preLaunchTask": "npm: build"
|
||||
}
|
||||
]
|
||||
}
|
||||
49
vscode-extension/.vscode/settings.json
vendored
Normal file
@@ -0,0 +1,49 @@
|
||||
{
|
||||
// Auto generate metadata
|
||||
"emeraldwalk.runonsave": {
|
||||
"commands": [
|
||||
{
|
||||
"match": "package.json",
|
||||
"isAsync": true,
|
||||
"cmd": "npm run update"
|
||||
}
|
||||
]
|
||||
},
|
||||
|
||||
// Disable the default formatter, use eslint instead
|
||||
"prettier.enable": false,
|
||||
"editor.formatOnSave": false,
|
||||
|
||||
// Auto fix
|
||||
"editor.codeActionsOnSave": {
|
||||
"source.fixAll.eslint": "explicit",
|
||||
"source.organizeImports": "never"
|
||||
},
|
||||
|
||||
// Silent the stylistic rules in you IDE, but still auto fix them
|
||||
"eslint.rules.customizations": [
|
||||
{ "rule": "style/*", "severity": "off" },
|
||||
{ "rule": "*-indent", "severity": "off" },
|
||||
{ "rule": "*-spacing", "severity": "off" },
|
||||
{ "rule": "*-spaces", "severity": "off" },
|
||||
{ "rule": "*-order", "severity": "off" },
|
||||
{ "rule": "*-dangle", "severity": "off" },
|
||||
{ "rule": "*-newline", "severity": "off" },
|
||||
{ "rule": "*quotes", "severity": "off" },
|
||||
{ "rule": "*semi", "severity": "off" }
|
||||
],
|
||||
|
||||
// Enable eslint for all supported languages
|
||||
"eslint.validate": [
|
||||
"javascript",
|
||||
"javascriptreact",
|
||||
"typescript",
|
||||
"typescriptreact",
|
||||
"vue",
|
||||
"html",
|
||||
"markdown",
|
||||
"json",
|
||||
"jsonc",
|
||||
"yaml"
|
||||
]
|
||||
}
|
||||
26
vscode-extension/.vscode/tasks.json
vendored
Normal file
@@ -0,0 +1,26 @@
|
||||
// See https://go.microsoft.com/fwlink/?LinkId=733558
|
||||
// for the documentation about the tasks.json format
|
||||
{
|
||||
"version": "2.0.0",
|
||||
"tasks": [
|
||||
{
|
||||
"type": "npm",
|
||||
"script": "dev",
|
||||
"isBackground": true,
|
||||
"presentation": {
|
||||
"reveal": "never"
|
||||
},
|
||||
"problemMatcher": [
|
||||
{
|
||||
"base": "$tsc-watch",
|
||||
"background": {
|
||||
"activeOnStart": true,
|
||||
"beginsPattern": "Build start",
|
||||
"endsPattern": "(B|Reb)uild complete|Rebuilt in"
|
||||
}
|
||||
}
|
||||
],
|
||||
"group": "build"
|
||||
}
|
||||
]
|
||||
}
|
||||
21
vscode-extension/LICENSE.md
Normal file
@@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2022 Anthony Fu <https://github.com/antfu>
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
53
vscode-extension/README.md
Normal file
@@ -0,0 +1,53 @@
|
||||
# ext-name
|
||||
|
||||
<a href="https://marketplace.visualstudio.com/items?itemName=antfu.ext-name" target="__blank"><img src="https://img.shields.io/visual-studio-marketplace/v/antfu.ext-name.svg?color=eee&label=VS%20Code%20Marketplace&logo=visual-studio-code" alt="Visual Studio Marketplace Version" /></a>
|
||||
<a href="https://kermanx.github.io/reactive-vscode/" target="__blank"><img src="https://img.shields.io/badge/made_with-reactive--vscode-%23007ACC?style=flat&labelColor=%23229863" alt="Made with reactive-vscode" /></a>
|
||||
|
||||
## Configurations
|
||||
|
||||
<!-- configs -->
|
||||
|
||||
| Key | Description | Type | Default |
|
||||
| ----------------- | --------------------- | -------- | ------- |
|
||||
| `metastudio.host` | Metabase instance URL | `string` | `""` |
|
||||
|
||||
<!-- configs -->
|
||||
|
||||
## Commands
|
||||
|
||||
<!-- commands -->
|
||||
|
||||
| Command | Title |
|
||||
| ----------------------------------------- | ------------------------------------------ |
|
||||
| `metastudio.setHost` | Metastudio: Set Host URL |
|
||||
| `metastudio.setApiKey` | Metastudio: Set API Key |
|
||||
| `metastudio.checkConnection` | Metastudio: Check Metabase Connection |
|
||||
| `metastudio.showApiKey` | Metastudio: Show API Key |
|
||||
| `metastudio.clearCredentials` | Metastudio: Clear Credentials |
|
||||
| `metastudio.clearWorkspaceConfigurations` | Metastudio: Clear Workspace Configurations |
|
||||
| `metastudio.checkDependencyGraph` | Metastudio: Check Dependency Graph |
|
||||
| `metastudio.showDependencyGraph` | Metastudio: Show Dependency Graph |
|
||||
| `metastudio.showTransformPreview` | Metastudio: Show Transform Preview |
|
||||
| `metastudio.openInMetabase` | Open in Metabase |
|
||||
| `metastudio.showInDependencyGraph` | Show in Dependency Graph |
|
||||
| `metastudio.runTransform` | Run Transform |
|
||||
| `metastudio.createSqlTransform` | Metastudio: New SQL Transform |
|
||||
| `metastudio.openEmbeddedCode` | Metastudio: Edit Query in Editor |
|
||||
| `metastudio.showNotebook` | Metastudio: Show Notebook Query |
|
||||
| `metastudio.showCardPreview` | Metastudio: Show Card Preview |
|
||||
| `metastudio.viewTableData` | View Data |
|
||||
| `metastudio.refreshWorkspaceData` | Refresh Workspace Data |
|
||||
|
||||
<!-- commands -->
|
||||
|
||||
## Sponsors
|
||||
|
||||
<p align="center">
|
||||
<a href="https://cdn.jsdelivr.net/gh/antfu/static/sponsors.svg">
|
||||
<img src='https://cdn.jsdelivr.net/gh/antfu/static/sponsors.png'/>
|
||||
</a>
|
||||
</p>
|
||||
|
||||
## License
|
||||
|
||||
[MIT](./LICENSE.md) License © 2022 [Anthony Fu](https://github.com/antfu)
|
||||
2175
vscode-extension/bun.lock
Normal file
15
vscode-extension/eslint.config.mjs
Normal file
@@ -0,0 +1,15 @@
|
||||
// @ts-check
|
||||
import antfu from '@antfu/eslint-config'
|
||||
|
||||
export default antfu(
|
||||
{
|
||||
ignores: [
|
||||
// eslint ignore globs here
|
||||
],
|
||||
},
|
||||
{
|
||||
rules: {
|
||||
// overrides
|
||||
},
|
||||
},
|
||||
)
|
||||
1
vscode-extension/media/check-connection.md
Normal file
@@ -0,0 +1 @@
|
||||
Click the button above to test your connection. If successful, you'll see your Metabase user info confirming authentication is working.
|
||||
3
vscode-extension/media/configure-host.md
Normal file
@@ -0,0 +1,3 @@
|
||||
Enter the URL of your Metabase instance, e.g. `http://localhost:3000` or `https://metabase.example.com`.
|
||||
|
||||
This is the base URL you use to access Metabase in your browser.
|
||||
7
vscode-extension/media/set-api-key.md
Normal file
@@ -0,0 +1,7 @@
|
||||
To generate an API key in Metabase:
|
||||
|
||||
1. Go to **Admin** > **Settings** > **Authentication** > **API Keys**
|
||||
2. Click **Create API Key**
|
||||
3. Copy the key and paste it here
|
||||
|
||||
Your API key is stored securely in your operating system's credential manager.
|
||||
478
vscode-extension/package.json
Normal file
@@ -0,0 +1,478 @@
|
||||
{
|
||||
"publisher": "metabase",
|
||||
"name": "metastudio",
|
||||
"displayName": "Metastudio",
|
||||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"packageManager": "bun@1.3.7",
|
||||
"description": "Metabase Studio extension for VS Code",
|
||||
"author": "Metabase",
|
||||
"license": "MIT",
|
||||
"homepage": "https://github.com/metabase/metabase",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/metabase/metabase"
|
||||
},
|
||||
"bugs": {
|
||||
"url": "https://github.com/metabase/metabase/issues"
|
||||
},
|
||||
"categories": [
|
||||
"Other"
|
||||
],
|
||||
"main": "./dist/index.cjs",
|
||||
"icon": "res/icon.png",
|
||||
"files": [
|
||||
"LICENSE.md",
|
||||
"dist/*",
|
||||
"res/*"
|
||||
],
|
||||
"engines": {
|
||||
"vscode": "^1.101.0"
|
||||
},
|
||||
"activationEvents": ["onStartupFinished"],
|
||||
"contributes": {
|
||||
"commands": [
|
||||
{
|
||||
"command": "metastudio.setHost",
|
||||
"title": "Metastudio: Set Host URL",
|
||||
"icon": "$(edit)"
|
||||
},
|
||||
{
|
||||
"command": "metastudio.setApiKey",
|
||||
"title": "Metastudio: Set API Key",
|
||||
"icon": "$(edit)"
|
||||
},
|
||||
{
|
||||
"command": "metastudio.checkConnection",
|
||||
"title": "Metastudio: Check Metabase Connection",
|
||||
"icon": "$(plug)"
|
||||
},
|
||||
{
|
||||
"command": "metastudio.showApiKey",
|
||||
"title": "Metastudio: Show API Key",
|
||||
"icon": "$(key)"
|
||||
},
|
||||
{
|
||||
"command": "metastudio.clearCredentials",
|
||||
"title": "Metastudio: Clear Credentials",
|
||||
"icon": "$(close)"
|
||||
},
|
||||
{
|
||||
"command": "metastudio.clearWorkspaceConfigurations",
|
||||
"title": "Metastudio: Clear Workspace Configurations",
|
||||
"icon": "$(trash)"
|
||||
},
|
||||
{
|
||||
"command": "metastudio.checkDependencyGraph",
|
||||
"title": "Metastudio: Check Dependency Graph"
|
||||
},
|
||||
{
|
||||
"command": "metastudio.showDependencyGraph",
|
||||
"title": "Metastudio: Show Dependency Graph"
|
||||
},
|
||||
{
|
||||
"command": "metastudio.showTransformPreview",
|
||||
"title": "Metastudio: Show Transform Preview"
|
||||
},
|
||||
{
|
||||
"command": "metastudio.openInMetabase",
|
||||
"title": "Open in Metabase"
|
||||
},
|
||||
{
|
||||
"command": "metastudio.showInDependencyGraph",
|
||||
"title": "Show in Dependency Graph"
|
||||
},
|
||||
{
|
||||
"command": "metastudio.runTransform",
|
||||
"title": "Run Transform",
|
||||
"icon": "$(play)"
|
||||
},
|
||||
{
|
||||
"command": "metastudio.createSqlTransform",
|
||||
"title": "Metastudio: New SQL Transform",
|
||||
"icon": "$(add)"
|
||||
},
|
||||
{
|
||||
"command": "metastudio.openEmbeddedCode",
|
||||
"title": "Metastudio: Edit Query in Editor",
|
||||
"icon": "$(edit)"
|
||||
},
|
||||
{
|
||||
"command": "metastudio.showNotebook",
|
||||
"title": "Metastudio: Show Notebook Query"
|
||||
},
|
||||
{
|
||||
"command": "metastudio.showCardPreview",
|
||||
"title": "Metastudio: Show Card Preview"
|
||||
},
|
||||
{
|
||||
"command": "metastudio.viewTableData",
|
||||
"title": "View Data",
|
||||
"icon": "$(table)"
|
||||
},
|
||||
{
|
||||
"command": "metastudio.refreshWorkspaceData",
|
||||
"title": "Refresh Workspace Data",
|
||||
"icon": "$(refresh)"
|
||||
}
|
||||
],
|
||||
"walkthroughs": [
|
||||
{
|
||||
"id": "metastudio-setup",
|
||||
"title": "Get Started with Metastudio",
|
||||
"description": "Connect your Metabase instance to VS Code",
|
||||
"steps": [
|
||||
{
|
||||
"id": "configure-host",
|
||||
"title": "Configure Metabase Host",
|
||||
"description": "Set the URL of your Metabase instance.\n\n[Open Settings](command:workbench.action.openSettings?%5B%22metastudio.host%22%5D)",
|
||||
"media": {
|
||||
"markdown": "media/configure-host.md"
|
||||
},
|
||||
"completionEvents": [
|
||||
"onSettingChanged:metastudio.host"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "set-api-key",
|
||||
"title": "Set API Key",
|
||||
"description": "Securely store your Metabase API key.\n\n[Set API Key](command:metastudio.setApiKey)",
|
||||
"media": {
|
||||
"markdown": "media/set-api-key.md"
|
||||
},
|
||||
"completionEvents": [
|
||||
"onCommand:metastudio.setApiKey"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "check-connection",
|
||||
"title": "Check Connection",
|
||||
"description": "Verify that Metastudio can connect to your Metabase instance.\n\n[Check Connection](command:metastudio.checkConnection)",
|
||||
"media": {
|
||||
"markdown": "media/check-connection.md"
|
||||
},
|
||||
"completionEvents": [
|
||||
"onCommand:metastudio.checkConnection"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"viewsContainers": {
|
||||
"activitybar": [
|
||||
{
|
||||
"id": "metabase",
|
||||
"title": "Metabase",
|
||||
"icon": "res/sidebar-icon.svg"
|
||||
}
|
||||
]
|
||||
},
|
||||
"views": {
|
||||
"metabase": [
|
||||
{
|
||||
"id": "metabase.connection",
|
||||
"name": "Connection",
|
||||
"visibility": "collapsed"
|
||||
},
|
||||
{
|
||||
"id": "metabase.content",
|
||||
"name": "Content"
|
||||
},
|
||||
{
|
||||
"id": "metabase.dataCatalog",
|
||||
"name": "Data Catalog"
|
||||
},
|
||||
{
|
||||
"id": "metabase.workspaceData",
|
||||
"name": "Workspace Data",
|
||||
"visibility": "collapsed"
|
||||
}
|
||||
]
|
||||
},
|
||||
"menus": {
|
||||
"view/title": [
|
||||
{
|
||||
"command": "metastudio.checkConnection",
|
||||
"when": "view == metabase.connection",
|
||||
"group": "navigation"
|
||||
},
|
||||
{
|
||||
"command": "metastudio.refreshWorkspaceData",
|
||||
"when": "view == metabase.workspaceData",
|
||||
"group": "navigation"
|
||||
}
|
||||
],
|
||||
"view/item/context": [
|
||||
{
|
||||
"command": "metastudio.runTransform",
|
||||
"when": "viewItem == transform",
|
||||
"group": "inline"
|
||||
},
|
||||
{
|
||||
"command": "metastudio.createSqlTransform",
|
||||
"when": "viewItem == collectionLibrary",
|
||||
"group": "inline"
|
||||
},
|
||||
{
|
||||
"command": "metastudio.setHost",
|
||||
"when": "view == metabase.connection && viewItem == connectionUrl",
|
||||
"group": "inline@1"
|
||||
},
|
||||
{
|
||||
"command": "metastudio.setApiKey",
|
||||
"when": "view == metabase.connection && viewItem == connectionToken",
|
||||
"group": "inline@1"
|
||||
},
|
||||
{
|
||||
"command": "metastudio.clearCredentials",
|
||||
"when": "view == metabase.connection && viewItem == connectionUrl",
|
||||
"group": "inline@2"
|
||||
},
|
||||
{
|
||||
"command": "metastudio.clearCredentials",
|
||||
"when": "view == metabase.connection && viewItem == connectionToken",
|
||||
"group": "inline@2"
|
||||
},
|
||||
{
|
||||
"command": "metastudio.clearWorkspaceConfigurations",
|
||||
"when": "view == metabase.connection && viewItem == connectionUrl",
|
||||
"group": "inline@3"
|
||||
},
|
||||
{
|
||||
"command": "metastudio.openInMetabase",
|
||||
"when": "view == metabase.content && viewItem =~ /^(card|model|metric|dashboard|collection|collectionLibrary|transform)$/ && metastudio.hostConfigured",
|
||||
"group": "navigation@1"
|
||||
},
|
||||
{
|
||||
"command": "metastudio.openInMetabase",
|
||||
"when": "view == metabase.dataCatalog && viewItem =~ /^(database|schema|table|field)$/ && metastudio.hostConfigured",
|
||||
"group": "navigation@1"
|
||||
},
|
||||
{
|
||||
"command": "metastudio.showNotebook",
|
||||
"when": "view == metabase.content && viewItem =~ /^(card|model|metric|transform|transformStructured)$/",
|
||||
"group": "navigation@2"
|
||||
},
|
||||
{
|
||||
"command": "metastudio.showInDependencyGraph",
|
||||
"when": "view == metabase.content && viewItem =~ /^(card|model|metric|dashboard|collection|collectionLibrary|transform|native_query_snippet|document|action|measure|segment)$/",
|
||||
"group": "navigation@3"
|
||||
},
|
||||
{
|
||||
"command": "metastudio.showInDependencyGraph",
|
||||
"when": "view == metabase.dataCatalog && viewItem == table",
|
||||
"group": "navigation@1"
|
||||
},
|
||||
{
|
||||
"command": "metastudio.viewTableData",
|
||||
"when": "view == metabase.workspaceData && viewItem == outputTable",
|
||||
"group": "inline"
|
||||
},
|
||||
{
|
||||
"command": "metastudio.viewTableData",
|
||||
"when": "view == metabase.workspaceData && viewItem == outputTable",
|
||||
"group": "navigation@1"
|
||||
}
|
||||
]
|
||||
},
|
||||
"viewsWelcome": [
|
||||
{
|
||||
"view": "metabase.content",
|
||||
"contents": "Open a folder with Metabase content exported via Remote Sync.",
|
||||
"when": "!metabase.configExists"
|
||||
},
|
||||
{
|
||||
"view": "metabase.dataCatalog",
|
||||
"contents": "Open a folder with Metabase content exported via Remote Sync.",
|
||||
"when": "!metabase.configExists"
|
||||
}
|
||||
],
|
||||
"chatSkills": [
|
||||
{
|
||||
"path": "./skills/metastudio/SKILL.md"
|
||||
}
|
||||
],
|
||||
"languageModelTools": [
|
||||
{
|
||||
"name": "metastudio_setHost",
|
||||
"displayName": "Set Metabase Host",
|
||||
"toolReferenceName": "metastudioSetHost",
|
||||
"modelDescription": "Sets the Metabase instance URL in workspace configuration. Call this before checking the connection or running transforms.",
|
||||
"canBeReferencedInPrompt": true,
|
||||
"inputSchema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"host": {
|
||||
"type": "string",
|
||||
"description": "The full URL of the Metabase instance, e.g. https://my-metabase.example.com"
|
||||
}
|
||||
},
|
||||
"required": ["host"]
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "metastudio_setApiKey",
|
||||
"displayName": "Set or Check Metabase API Key",
|
||||
"toolReferenceName": "metastudioSetApiKey",
|
||||
"modelDescription": "Optionally sets the Metabase API key in secure storage, then reports the current key status (whether it is set and its last 4 characters). Omit 'apiKey' to check status without changing anything.",
|
||||
"canBeReferencedInPrompt": true,
|
||||
"inputSchema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"apiKey": {
|
||||
"type": "string",
|
||||
"description": "The Metabase API key to store. Omit this property to only check the current status."
|
||||
}
|
||||
},
|
||||
"required": []
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "metastudio_checkConnection",
|
||||
"displayName": "Check Metabase Connection",
|
||||
"toolReferenceName": "metastudioCheckConnection",
|
||||
"modelDescription": "Tests connectivity to the configured Metabase instance using the stored API key. Returns the connection result including the authenticated user's name on success.",
|
||||
"canBeReferencedInPrompt": true,
|
||||
"inputSchema": {
|
||||
"type": "object",
|
||||
"properties": {},
|
||||
"required": []
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "metastudio_runTransform",
|
||||
"displayName": "Run Metabase Transform",
|
||||
"toolReferenceName": "metastudioRunTransform",
|
||||
"modelDescription": "Looks up a transform by name (case-insensitive) or by entityId in the loaded content graph and executes it. Provide 'entityId' when multiple transforms share the same name.",
|
||||
"canBeReferencedInPrompt": true,
|
||||
"inputSchema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"name": {
|
||||
"type": "string",
|
||||
"description": "The display name of the transform to run (case-insensitive). At least one of 'name' or 'entityId' must be provided."
|
||||
},
|
||||
"entityId": {
|
||||
"type": "string",
|
||||
"description": "The unique entity ID of the transform to run. Takes precedence over 'name' when both are supplied."
|
||||
}
|
||||
},
|
||||
"required": []
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "metastudio_getApiKey",
|
||||
"displayName": "Get Metabase API Key Status",
|
||||
"toolReferenceName": "metastudioGetApiKey",
|
||||
"modelDescription": "Returns the current API key with all but the last 4 characters masked (e.g. ********abcd). Returns 'not set' if no key has been stored.",
|
||||
"canBeReferencedInPrompt": true,
|
||||
"inputSchema": {
|
||||
"type": "object",
|
||||
"properties": {},
|
||||
"required": []
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "metastudio_getHost",
|
||||
"displayName": "Get Metabase Host",
|
||||
"toolReferenceName": "metastudioGetHost",
|
||||
"modelDescription": "Returns the currently configured Metabase instance URL, or indicates that no host has been set.",
|
||||
"canBeReferencedInPrompt": true,
|
||||
"inputSchema": {
|
||||
"type": "object",
|
||||
"properties": {},
|
||||
"required": []
|
||||
}
|
||||
}
|
||||
],
|
||||
"configuration": {
|
||||
"type": "object",
|
||||
"title": "Metastudio",
|
||||
"properties": {
|
||||
"metastudio.host": {
|
||||
"type": "string",
|
||||
"format": "uri",
|
||||
"default": "",
|
||||
"description": "Metabase instance URL"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"scripts": {
|
||||
"build": "nr build:notebook-component && nr build:ext && nr build:webview",
|
||||
"build:metabase-lib": "bun run scripts/bundle-metabase-lib.ts",
|
||||
"build:notebook-component": "test -d ../target/cljs_release || (cd .. && yarn build-release:cljs) ; cd .. && rspack --config rspack.notebook-component.config.js",
|
||||
"build:ext": "tsdown src/index.ts --external vscode",
|
||||
"build:webview": "cd webview && vite build",
|
||||
"dev": "nr build:ext --watch --sourcemap",
|
||||
"dev:webview": "cd webview && vite",
|
||||
"prepare": "nr update",
|
||||
"update": "vscode-ext-gen --output src/generated/meta.ts",
|
||||
"lint": "eslint .",
|
||||
"test": "vitest",
|
||||
"typecheck": "tsc --noEmit",
|
||||
"vscode:prepublish": "nr build",
|
||||
"release": "bumpp",
|
||||
"ext:package": "vsce package --no-dependencies",
|
||||
"ext:publish": "vsxpub --no-dependencies"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@antfu/eslint-config": "^6.7.3",
|
||||
"@antfu/ni": "^28.0.0",
|
||||
"@codemirror/autocomplete": "^6.20.0",
|
||||
"@codemirror/commands": "^6.10.2",
|
||||
"@codemirror/language": "^6.12.2",
|
||||
"@codemirror/search": "^6.6.0",
|
||||
"@codemirror/state": "^6.5.4",
|
||||
"@codemirror/view": "^6.39.15",
|
||||
"@dagrejs/dagre": "^1.1.8",
|
||||
"@dnd-kit/core": "^6.3.1",
|
||||
"@dnd-kit/modifiers": "^9.0.0",
|
||||
"@dnd-kit/sortable": "^10.0.0",
|
||||
"@dnd-kit/utilities": "^3.2.2",
|
||||
"@emotion/babel-plugin": "^11.13.5",
|
||||
"@emotion/react": "^11.14.0",
|
||||
"@emotion/styled": "^11.14.1",
|
||||
"@lezer/common": "^1.5.1",
|
||||
"@lezer/highlight": "^1.2.3",
|
||||
"@lezer/lr": "^1.4.8",
|
||||
"@mantine/core": "8.3.5",
|
||||
"@mantine/dates": "8.3.5",
|
||||
"@mantine/hooks": "8.3.5",
|
||||
"@reduxjs/toolkit": "^2.11.2",
|
||||
"@types/node": "^25.0.3",
|
||||
"@types/react": "^19.0.0",
|
||||
"@types/react-dom": "^19.0.0",
|
||||
"@types/vscode": "^1.101.0",
|
||||
"@uiw/react-codemirror": "^4.25.5",
|
||||
"@vitejs/plugin-react": "^4.4.0",
|
||||
"@vscode/vsce": "^3.7.1",
|
||||
"@xyflow/react": "^12.10.1",
|
||||
"bumpp": "^10.3.2",
|
||||
"classnames": "^2.5.1",
|
||||
"dayjs": "^1.11.19",
|
||||
"esbuild": "^0.25.0",
|
||||
"eslint": "^9.39.2",
|
||||
"icepick": "^2.4.0",
|
||||
"ovsx": "^0.10.7",
|
||||
"querystring-es3": "^0.2.1",
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0",
|
||||
"react-markdown": "^10.1.0",
|
||||
"react-redux": "^9.2.0",
|
||||
"react-use": "^17.6.0",
|
||||
"reactive-vscode": "^1.0.0-beta.2",
|
||||
"remark-gfm": "^4.0.1",
|
||||
"slugg": "^1.2.1",
|
||||
"taze": "^19.9.2",
|
||||
"tsdown": "^0.19.0-beta.3",
|
||||
"ttag": "^1.8.12",
|
||||
"typescript": "^5.9.3",
|
||||
"underscore": "^1.13.8",
|
||||
"vite": "^7.3.0",
|
||||
"vitest": "^4.0.16",
|
||||
"vscode-ext-gen": "^1.5.1",
|
||||
"vsxpub": "^0.1.2",
|
||||
"yaml": "^2.8.2"
|
||||
}
|
||||
}
|
||||
BIN
vscode-extension/res/icon.png
Normal file
|
After Width: | Height: | Size: 1.3 KiB |
3
vscode-extension/res/icon.svg
Normal file
@@ -0,0 +1,3 @@
|
||||
<svg width="32" height="32" viewBox="0 0 32 32" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M26.6667 1.66667H24V7H8V9.66667H5.33333V20.3333H8V23H10.6667V28.3333H21.3333V25.6667H26.6667V23H21.3333V20.3333H26.6667V17.6667H21.3333V15H10.6667V20.3333H8V9.66667H24V7H26.6667V1.66667ZM18.6667 25.6667H13.3333V17.6667H18.6667V25.6667Z" fill="#888888"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 366 B |
18
vscode-extension/res/icons/dark/arrow-right-left.svg
Normal file
@@ -0,0 +1,18 @@
|
||||
<!-- @license lucide-static v0.575.0 - ISC -->
|
||||
<svg
|
||||
class="lucide lucide-arrow-right-left"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="24"
|
||||
height="24"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="#C5C5C5"
|
||||
stroke-width="2"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
>
|
||||
<path d="m16 3 4 4-4 4" />
|
||||
<path d="M20 7H4" />
|
||||
<path d="m8 21-4-4 4-4" />
|
||||
<path d="M4 17h16" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 396 B |
3
vscode-extension/res/icons/dark/bolt.svg
Normal file
@@ -0,0 +1,3 @@
|
||||
<svg viewBox="0 0 16 16" fill="#C5C5C5" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<path d="M4.262 2.866A.75.75 0 0 1 5 2.25h5a.75.75 0 0 1 .721.956L9.994 5.75H12.5a.75.75 0 0 1 .55 1.26l-6.5 7a.75.75 0 0 1-1.285-.657l.82-4.103H4a.75.75 0 0 1-.738-.884l1-5.5zm1.364.884l-.727 4H7a.75.75 0 0 1 .735.897l-.48 2.398L10.78 7.25H9a.75.75 0 0 1-.721-.956l.727-2.544h-3.38z" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 416 B |
26
vscode-extension/res/icons/dark/boxes.svg
Normal file
@@ -0,0 +1,26 @@
|
||||
<!-- @license lucide-static v0.575.0 - ISC -->
|
||||
<svg
|
||||
class="lucide lucide-boxes"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="24"
|
||||
height="24"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="#C5C5C5"
|
||||
stroke-width="2"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
>
|
||||
<path d="M2.97 12.92A2 2 0 0 0 2 14.63v3.24a2 2 0 0 0 .97 1.71l3 1.8a2 2 0 0 0 2.06 0L12 19v-5.5l-5-3-4.03 2.42Z" />
|
||||
<path d="m7 16.5-4.74-2.85" />
|
||||
<path d="m7 16.5 5-3" />
|
||||
<path d="M7 16.5v5.17" />
|
||||
<path d="M12 13.5V19l3.97 2.38a2 2 0 0 0 2.06 0l3-1.8a2 2 0 0 0 .97-1.71v-3.24a2 2 0 0 0-.97-1.71L17 10.5l-5 3Z" />
|
||||
<path d="m17 16.5-5-3" />
|
||||
<path d="m17 16.5 4.74-2.85" />
|
||||
<path d="M17 16.5v5.17" />
|
||||
<path d="M7.97 4.42A2 2 0 0 0 7 6.13v4.37l5 3 5-3V6.13a2 2 0 0 0-.97-1.71l-3-1.8a2 2 0 0 0-2.06 0l-3 1.8Z" />
|
||||
<path d="M12 8 7.26 5.15" />
|
||||
<path d="m12 8 4.74-2.85" />
|
||||
<path d="M12 13.5V8" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 896 B |
3
vscode-extension/res/icons/dark/calendar.svg
Normal file
@@ -0,0 +1,3 @@
|
||||
<svg viewBox="0 0 16 16" fill="#C5C5C5" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<path d="M5.25 9.5V8h1.5v1.5h-1.5z" /><path fill-rule="evenodd" clip-rule="evenodd" d="M11.75 2.25H12c.966 0 1.75.784 1.75 1.75v8A1.75 1.75 0 0 1 12 13.75H4A1.75 1.75 0 0 1 2.25 12V4c0-.966.784-1.75 1.75-1.75h.25V1h1.5v1.25h4.5V1h1.5v1.25zM4 3.75a.25.25 0 0 0-.25.25v1.25h8.5V4a.25.25 0 0 0-.25-.25H4zM3.75 12V6.75h8.5V12a.25.25 0 0 1-.25.25H4a.25.25 0 0 1-.25-.25z" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 498 B |
16
vscode-extension/res/icons/dark/chart-line.svg
Normal file
@@ -0,0 +1,16 @@
|
||||
<!-- @license lucide-static v0.575.0 - ISC -->
|
||||
<svg
|
||||
class="lucide lucide-chart-line"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="24"
|
||||
height="24"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="#C5C5C5"
|
||||
stroke-width="2"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
>
|
||||
<path d="M3 3v16a2 2 0 0 0 2 2h16" />
|
||||
<path d="m19 9-5 5-4-4-3 3" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 358 B |
16
vscode-extension/res/icons/dark/code.svg
Normal file
@@ -0,0 +1,16 @@
|
||||
<!-- @license lucide-static v0.575.0 - ISC -->
|
||||
<svg
|
||||
class="lucide lucide-code"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="24"
|
||||
height="24"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="#C5C5C5"
|
||||
stroke-width="2"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
>
|
||||
<path d="m16 18 6-6-6-6" />
|
||||
<path d="m8 6-6 6 6 6" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 337 B |
3
vscode-extension/res/icons/dark/connections.svg
Normal file
@@ -0,0 +1,3 @@
|
||||
<svg viewBox="0 0 16 16" fill="#C5C5C5" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<path d="M3.309 8.75a4.75 4.75 0 0 0 8.05 2.615.75.75 0 1 0-1.06-1.06 3.25 3.25 0 0 1-2.21.95A.77.77 0 0 0 8 11.25a3.25 3.25 0 0 1-3.25-3.246 3.25 3.25 0 0 1 5.548-2.296.75.75 0 0 0 1.06-1.06 4.75 4.75 0 0 0-3.25-1.39A.757.757 0 0 0 8 3.25a4.75 4.75 0 0 0-4.69 4H2a.75.75 0 0 0 0 1.5h1.309z" /> <path d="M14 8.75a.75.75 0 0 0 0-1.5H9.855a2 2 0 1 0 0 1.5H14z" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 490 B |
3
vscode-extension/res/icons/dark/dashboard.svg
Normal file
@@ -0,0 +1,3 @@
|
||||
<svg viewBox="0 0 16 16" fill="#C5C5C5" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<path d="M4.75 9a.75.75 0 0 0 0 1.5h6.5a.75.75 0 0 0 0-1.5h-6.5zM4 6.25a1 1 0 1 1 2 0 1 1 0 0 1-2 0zM7.75 5.5a.75.75 0 0 0 0 1.5h3.5a.75.75 0 0 0 0-1.5h-3.5z" /><path fill-rule="evenodd" clip-rule="evenodd" d="M1.25 3c0-.966.784-1.75 1.75-1.75h10c.966 0 1.75.784 1.75 1.75v10A1.75 1.75 0 0 1 13 14.75H3A1.75 1.75 0 0 1 1.25 13V3zm1.5 10V3.5h10.5V13a.25.25 0 0 1-.25.25H3a.25.25 0 0 1-.25-.25z" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 525 B |
3
vscode-extension/res/icons/dark/database.svg
Normal file
@@ -0,0 +1,3 @@
|
||||
<svg viewBox="0 0 16 16" fill="#C5C5C5" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<path clip-rule="evenodd" fill-rule="evenodd" d="M2.665 13.67A.75.75 0 0 1 2.25 13V3a.75.75 0 0 1 .415-.67l.002-.002.002-.001.008-.004.024-.011a7.465 7.465 0 0 1 .38-.164c.255-.103.623-.236 1.088-.37A14.023 14.023 0 0 1 8 1.25c1.583 0 2.903.264 3.831.529.465.133.833.266 1.088.368a7.5 7.5 0 0 1 .38.165l.024.011.008.004.003.001L13 3l.335-.671A.751.751 0 0 1 13.75 3v10a.75.75 0 0 1-.415.67l-.002.002-.002.001-.008.004-.024.011a7.443 7.443 0 0 1-.38.164 11.33 11.33 0 0 1-1.088.37A14.02 14.02 0 0 1 8 14.75a14.02 14.02 0 0 1-3.831-.529 11.327 11.327 0 0 1-1.088-.368 7.409 7.409 0 0 1-.38-.165l-.024-.011-.008-.004-.002-.001-.002-.001zM3.75 3.497c.207-.079.487-.177.831-.275A12.524 12.524 0 0 1 8 2.75c1.417 0 2.597.236 3.419.471.344.098.624.196.831.275v1.24l-.096.038c-.327.133-.642.26-1.106.388-.674.185-1.604.338-3.048.338s-2.374-.153-3.049-.338a8.97 8.97 0 0 1-1.106-.388l-.095-.039V3.496zm7.695 3.113c.289-.08.559-.171.805-.262v2.388l-.096.04c-.327.132-.642.26-1.106.387-.674.185-1.604.338-3.048.338s-2.374-.153-3.049-.338a8.97 8.97 0 0 1-1.201-.427V6.347c.246.091.516.182.805.262C5.378 6.834 6.444 7 8 7c1.556 0 2.621-.166 3.445-.391zM3.75 10.347v2.157c.207.079.487.177.831.275.822.235 2.002.471 3.419.471 1.417 0 2.597-.236 3.419-.471.344-.098.624-.196.831-.275v-2.157c-.246.09-.516.182-.805.261-.824.226-1.89.393-3.445.393-1.556 0-2.622-.167-3.445-.393a9.777 9.777 0 0 1-.805-.26z" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.5 KiB |
3
vscode-extension/res/icons/dark/document.svg
Normal file
@@ -0,0 +1,3 @@
|
||||
<svg viewBox="0 0 16 16" fill="#C5C5C5" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M8.848 1.25c.451 0 .886.175 1.212.487l3.152 3.027c.344.33.538.786.538 1.262V12.8c0 .527-.219 1.026-.597 1.39-.377.362-.882.56-1.403.56h-7.5c-.52 0-1.026-.199-1.403-.56-.331-.318-.54-.74-.587-1.194l-.01-.196V3.2c0-.527.218-1.026.597-1.39.377-.361.882-.56 1.403-.56h4.598zM4.25 2.75a.528.528 0 0 0-.364.143.427.427 0 0 0-.136.307v9.6l.009.082c.017.081.059.16.127.225a.528.528 0 0 0 .364.143h7.5a.528.528 0 0 0 .364-.143.428.428 0 0 0 .136-.307V6.575H9.249A.575.575 0 0 1 8.674 6V2.75H4.25zm6.4 7.425a.576.576 0 0 1 0 1.15H5.4a.575.575 0 0 1 0-1.15h5.25zm0-2.25a.576.576 0 0 1 0 1.15H5.4a.575.575 0 0 1 0-1.15h5.25zm-3.651-2.25a.576.576 0 0 1 0 1.15h-1.6a.575.575 0 0 1 0-1.15H7zm2.825-.25h1.91L9.824 3.59v1.834z" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 809 B |
17
vscode-extension/res/icons/dark/file-question.svg
Normal file
@@ -0,0 +1,17 @@
|
||||
<!-- @license lucide-static v0.575.0 - ISC -->
|
||||
<svg
|
||||
class="lucide lucide-file-question"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="24"
|
||||
height="24"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="#C5C5C5"
|
||||
stroke-width="2"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
>
|
||||
<path d="M6 22a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h8a2.4 2.4 0 0 1 1.704.706l3.588 3.588A2.4 2.4 0 0 1 20 8v12a2 2 0 0 1-2 2z" />
|
||||
<path d="M12 17h.01" />
|
||||
<path d="M9.1 9a3 3 0 0 1 5.82 1c0 2-3 3-3 3" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 491 B |
19
vscode-extension/res/icons/dark/file-text.svg
Normal file
@@ -0,0 +1,19 @@
|
||||
<!-- @license lucide-static v0.575.0 - ISC -->
|
||||
<svg
|
||||
class="lucide lucide-file-text"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="24"
|
||||
height="24"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="#C5C5C5"
|
||||
stroke-width="2"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
>
|
||||
<path d="M6 22a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h8a2.4 2.4 0 0 1 1.704.706l3.588 3.588A2.4 2.4 0 0 1 20 8v12a2 2 0 0 1-2 2z" />
|
||||
<path d="M14 2v5a1 1 0 0 0 1 1h5" />
|
||||
<path d="M10 9H8" />
|
||||
<path d="M16 13H8" />
|
||||
<path d="M16 17H8" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 520 B |
3
vscode-extension/res/icons/dark/filter.svg
Normal file
@@ -0,0 +1,3 @@
|
||||
<svg viewBox="0 0 16 16" fill="#C5C5C5" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<path d="M1 4.5c0-.69.56-1.25 1.25-1.25h11.5a1.25 1.25 0 1 1 0 2.5H2.25C1.56 5.75 1 5.19 1 4.5zM4 9a1 1 0 0 1 1-1h6a1 1 0 1 1 0 2H5a1 1 0 0 1-1-1zm2 4.25a1 1 0 0 1 1-1h2a1 1 0 1 1 0 2H7a1 1 0 0 1-1-1z" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 333 B |
3
vscode-extension/res/icons/dark/folder.svg
Normal file
@@ -0,0 +1,3 @@
|
||||
<svg viewBox="0 0 16 16" fill="#C5C5C5" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M3.2 3.75a.501.501 0 0 0-.339.126.322.322 0 0 0-.111.235v7.778c0 .076.032.162.111.235a.5.5 0 0 0 .339.126h9.6a.501.501 0 0 0 .339-.126.322.322 0 0 0 .111-.235v-5.96a.322.322 0 0 0-.111-.235.501.501 0 0 0-.339-.126H7.71l-2-1.818H3.2zm-1.358-.975A2 2 0 0 1 3.2 2.25h3.09l2 1.818h4.51a2 2 0 0 1 1.358.525c.371.344.592.823.592 1.336v5.96c0 .513-.22.992-.592 1.336a2 2 0 0 1-1.358.525H3.2a2 2 0 0 1-1.358-.525 1.821 1.821 0 0 1-.592-1.336V4.11c0-.513.22-.992.592-1.336z" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 646 B |
3
vscode-extension/res/icons/dark/folder_database.svg
Normal file
@@ -0,0 +1,3 @@
|
||||
<svg viewBox="0 0 16 16" fill="#C5C5C5" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M11.41 7.25c.92 0 1.79.185 2.457.519.622.31 1.293.88 1.293 1.731v3c0 .851-.67 1.42-1.293 1.731-.668.334-1.538.519-2.457.519-.92 0-1.79-.185-2.457-.519-.623-.31-1.293-.88-1.293-1.731v-3c0-.851.67-1.42 1.293-1.731.667-.334 1.537-.519 2.457-.519zm-6.096-6a1.986 1.986 0 0 1 1.665.887l.515.762.005.006a.558.558 0 0 0 .464.251h5.038a1.985 1.985 0 0 1 1.985 1.984V6c0 .395-.32.75-.715.75-.395 0-.715-.356-.715-.75v-.86a.555.555 0 0 0-.555-.554H7.962a1.988 1.988 0 0 1-1.658-.894l-.51-.756-.004-.006a.556.556 0 0 0-.47-.25H2.835a.556.556 0 0 0-.555.555v8.26a.558.558 0 0 0 .555.555H5.35c.394 0 .9.32.9.715 0 .394-.505.714-.9.714H2.835A1.986 1.986 0 0 1 .85 11.495V3.235A1.985 1.985 0 0 1 2.835 1.25h2.479zm8.346 10.075c-.636.273-1.423.425-2.25.425-.828 0-1.615-.152-2.25-.425v1.179l.005.014a.327.327 0 0 0 .053.082c.069.08.197.185.406.29.418.209 1.048.36 1.786.36.737 0 1.367-.151 1.786-.36.208-.105.337-.21.405-.29a.329.329 0 0 0 .059-.096v-1.179zM11.41 8.75c-.738 0-1.368.151-1.786.36a1.29 1.29 0 0 0-.406.29.328.328 0 0 0-.058.096v.008l.005.015a.328.328 0 0 0 .053.08c.069.081.197.186.406.29.418.21 1.048.361 1.786.361.737 0 1.367-.151 1.786-.36a1.29 1.29 0 0 0 .405-.29.33.33 0 0 0 .059-.096v-.008A.329.329 0 0 0 13.6 9.4a1.29 1.29 0 0 0-.405-.29c-.418-.209-1.05-.36-1.786-.36z" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.3 KiB |
18
vscode-extension/res/icons/dark/hash.svg
Normal file
@@ -0,0 +1,18 @@
|
||||
<!-- @license lucide-static v0.575.0 - ISC -->
|
||||
<svg
|
||||
class="lucide lucide-hash"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="24"
|
||||
height="24"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="#C5C5C5"
|
||||
stroke-width="2"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
>
|
||||
<line x1="4" x2="20" y1="9" y2="9" />
|
||||
<line x1="4" x2="20" y1="15" y2="15" />
|
||||
<line x1="10" x2="8" y1="3" y2="21" />
|
||||
<line x1="16" x2="14" y1="3" y2="21" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 444 B |
3
vscode-extension/res/icons/dark/int.svg
Normal file
@@ -0,0 +1,3 @@
|
||||
<svg viewBox="0 0 16 16" fill="#C5C5C5" fill-rule="evenodd" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<path d="M7.245 3.583a.75.75 0 0 0-1.49-.166L5.55 5.25H3.5a.75.75 0 0 0 0 1.5h1.884l-.277 2.5H3.5a.75.75 0 0 0 0 1.5h1.44l-.185 1.667a.75.75 0 1 0 1.49.166l.204-1.833h2.49l-.184 1.667a.75.75 0 1 0 1.49.166l.204-1.833H12.5a.75.75 0 0 0 0-1.5h-1.884l.278-2.5H12.5a.75.75 0 0 0 0-1.5h-1.44l.185-1.667a.75.75 0 0 0-1.49-.166L9.55 5.25h-2.49l.184-1.667zM6.894 6.75l-.278 2.5h2.49l.278-2.5h-2.49z" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 542 B |
16
vscode-extension/res/icons/dark/key-round.svg
Normal file
@@ -0,0 +1,16 @@
|
||||
<!-- @license lucide-static v0.575.0 - ISC -->
|
||||
<svg
|
||||
class="lucide lucide-key-round"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="24"
|
||||
height="24"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="#C5C5C5"
|
||||
stroke-width="2"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
>
|
||||
<path d="M2.586 17.414A2 2 0 0 0 2 18.828V21a1 1 0 0 0 1 1h3a1 1 0 0 0 1-1v-1a1 1 0 0 1 1-1h1a1 1 0 0 0 1-1v-1a1 1 0 0 1 1-1h.172a2 2 0 0 0 1.414-.586l.814-.814a6.5 6.5 0 1 0-4-4z" />
|
||||
<circle cx="16.5" cy="7.5" r=".5" fill="#C5C5C5" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 524 B |
3
vscode-extension/res/icons/dark/label.svg
Normal file
@@ -0,0 +1,3 @@
|
||||
<svg viewBox="0 0 16 16" fill="#C5C5C5" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<path d="M11 9a1 1 0 1 0 0-2 1 1 0 0 0 0 2z" /><path fill-rule="evenodd" clip-rule="evenodd" d="M3 3.25A1.75 1.75 0 0 0 1.25 5v6c0 .966.784 1.75 1.75 1.75h7.5a2.75 2.75 0 0 0 2.2-1.1l1.725-2.3c.6-.8.6-1.9 0-2.7L12.7 4.35a2.75 2.75 0 0 0-2.2-1.1H3zM2.75 5A.25.25 0 0 1 3 4.75h7.5c.393 0 .764.185 1 .5l1.725 2.3c.2.267.2.633 0 .9l-1.725 2.3a1.25 1.25 0 0 1-1 .5H3a.25.25 0 0 1-.25-.25V5z" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 518 B |
18
vscode-extension/res/icons/dark/layout-dashboard.svg
Normal file
@@ -0,0 +1,18 @@
|
||||
<!-- @license lucide-static v0.575.0 - ISC -->
|
||||
<svg
|
||||
class="lucide lucide-layout-dashboard"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="24"
|
||||
height="24"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="#C5C5C5"
|
||||
stroke-width="2"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
>
|
||||
<rect width="7" height="9" x="3" y="3" rx="1" />
|
||||
<rect width="7" height="5" x="14" y="3" rx="1" />
|
||||
<rect width="7" height="9" x="14" y="12" rx="1" />
|
||||
<rect width="7" height="5" x="3" y="16" rx="1" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 499 B |
16
vscode-extension/res/icons/dark/link.svg
Normal file
@@ -0,0 +1,16 @@
|
||||
<!-- @license lucide-static v0.575.0 - ISC -->
|
||||
<svg
|
||||
class="lucide lucide-link"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="24"
|
||||
height="24"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="#C5C5C5"
|
||||
stroke-width="2"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
>
|
||||
<path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71" />
|
||||
<path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 430 B |
1
vscode-extension/res/icons/dark/metric.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg fill="none" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><path fill-rule="evenodd" clip-rule="evenodd" d="M10.562 5.499 12.25 3.81v8.439H3.81L5.5 10.562l.915.915 1.063-1.063-.915-.915.937-.937.915.915 1.063-1.063-.915-.915.937-.937.915.915 1.063-1.063-.915-.915Zm3.188-2.775c0-.935-1.131-1.404-1.793-.742l-9.975 9.976c-.662.661-.193 1.792.742 1.792H13.75V2.724Z" fill="#C5C5C5"/></svg>
|
||||
|
After Width: | Height: | Size: 401 B |
3
vscode-extension/res/icons/dark/model.svg
Normal file
@@ -0,0 +1,3 @@
|
||||
<svg viewBox="0 0 16 16" fill="#C5C5C5" clip-rule="evenodd" fill-rule="evenodd" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<path d="M8.5 1.423a1 1 0 0 0-1 0L2.554 4.278a1 1 0 0 0-.5.866v5.712a1 1 0 0 0 .5.866L7.5 14.577a1 1 0 0 0 1 0l4.946-2.855a1 1 0 0 0 .5-.866V5.144a1 1 0 0 0-.5-.866L8.5 1.423zM3.554 6.207v4.36l3.696 2.134V8.425L3.554 6.207zM8.75 12.7l3.696-2.134v-4.36L8.75 8.425V12.7zm2.868-7.746L8 2.866 4.382 4.955 8 7.125l3.618-2.17z" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 492 B |
15
vscode-extension/res/icons/dark/play.svg
Normal file
@@ -0,0 +1,15 @@
|
||||
<!-- @license lucide-static v0.575.0 - ISC -->
|
||||
<svg
|
||||
class="lucide lucide-play"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="24"
|
||||
height="24"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="#C5C5C5"
|
||||
stroke-width="2"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
>
|
||||
<path d="M5 5a2 2 0 0 1 3.008-1.728l11.997 6.998a2 2 0 0 1 .003 3.458l-12 7A2 2 0 0 1 5 19z" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 377 B |
3
vscode-extension/res/icons/dark/segment.svg
Normal file
@@ -0,0 +1,3 @@
|
||||
<svg viewBox="0 0 16 16" fill="#C5C5C5" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<path d="M4 3a1 1 0 0 0-1 1v3h6v6h3a1 1 0 0 0 1-1V4a1 1 0 0 0-1-1H4z" /><path d="M8 8H2v5a1 1 0 0 0 1 1h5V8z" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 241 B |
3
vscode-extension/res/icons/dark/snippet.svg
Normal file
@@ -0,0 +1,3 @@
|
||||
<svg viewBox="0 0 16 16" fill="#C5C5C5" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<path d="M1.25 4c0-.966.784-1.75 1.75-1.75a.75.75 0 0 1 0 1.5.25.25 0 0 0-.25.25v2.586c0 .464-.184.909-.513 1.237L2.061 8l.176.177c.329.328.513.773.513 1.237V12c0 .138.112.25.25.25a.75.75 0 0 1 0 1.5A1.75 1.75 0 0 1 1.25 12V9.414a.25.25 0 0 0-.073-.177L.47 8.53a.75.75 0 0 1 0-1.06l.707-.707a.25.25 0 0 0 .073-.177V4zM4.75 11a.75.75 0 0 1 .75-.75H8a.75.75 0 0 1 0 1.5H5.5a.75.75 0 0 1-.75-.75zM4.75 8a.75.75 0 0 1 .75-.75H11a.75.75 0 0 1 0 1.5H5.5A.75.75 0 0 1 4.75 8zM5.5 4.25a.75.75 0 0 0 0 1.5h4a.75.75 0 0 0 0-1.5h-4zM13 13.75A1.75 1.75 0 0 0 14.75 12V9.414a.25.25 0 0 1 .073-.177l.707-.707a.75.75 0 0 0 0-1.06l-.707-.707a.25.25 0 0 1-.073-.177V4A1.75 1.75 0 0 0 13 2.25a.75.75 0 0 0 0 1.5.25.25 0 0 1 .25.25v2.586c0 .464.184.909.513 1.237l.176.177-.176.177a1.75 1.75 0 0 0-.513 1.237V12a.25.25 0 0 1-.25.25.75.75 0 0 0 0 1.5z" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 963 B |
3
vscode-extension/res/icons/dark/table.svg
Normal file
@@ -0,0 +1,3 @@
|
||||
<svg viewBox="0 0 16 16" fill="#C5C5C5" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<path d="M12.667 1.25c1.15 0 2.083.933 2.083 2.083v9.334c0 1.15-.933 2.083-2.083 2.083H3.333a2.083 2.083 0 0 1-2.083-2.083V3.333c0-1.15.933-2.083 2.083-2.083h9.334zm-9.917 9.5v1.917c0 .322.261.583.583.583H7.25v-2.5h-4.5zm6 0v2.5h3.917a.583.583 0 0 0 .583-.583V10.75h-4.5zm-6-1.5h4.5v-2.5h-4.5v2.5zm6 0h4.5v-2.5h-4.5v2.5zm-5.417-6.5a.583.583 0 0 0-.583.583V5.25h4.5v-2.5H3.333zm5.417 2.5h4.5V3.333a.583.583 0 0 0-.583-.583H8.75v2.5z" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 564 B |
3
vscode-extension/res/icons/dark/table2.svg
Normal file
@@ -0,0 +1,3 @@
|
||||
<svg viewBox="0 0 16 16" fill="#C5C5C5" clip-rule="evenodd" fill-rule="evenodd" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<path d="M2 3a1 1 0 0 1 1-1h10a1 1 0 0 1 1 1v10a1 1 0 0 1-1 1H3a1 1 0 0 1-1-1V3zm1.5 6V7h1.947v2H3.5zm0 1.5v2h1.947v-2H3.5zm3.447 0v2H12.5v-2H6.947zM12.5 9V7H6.947v2H12.5zM5.447 5.5H3.5v-2h1.947v2zm1.5 0H12.5v-2H6.947v2z" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 392 B |
3
vscode-extension/res/icons/dark/transform.svg
Normal file
@@ -0,0 +1,3 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="#C5C5C5">
|
||||
<path d="M8.6748 6.83105L12.8584 4.41602L13.5332 5.58398L9.34863 7.99902L13.5332 10.416L12.8584 11.584L8.6748 9.16797V14H7.3252V9.16797L3.1416 11.584L2.4668 10.416L6.65137 7.99902L2.4668 5.58398L3.1416 4.41602L7.3252 6.83105V2H8.6748V6.83105Z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 331 B |
18
vscode-extension/res/icons/light/arrow-right-left.svg
Normal file
@@ -0,0 +1,18 @@
|
||||
<!-- @license lucide-static v0.575.0 - ISC -->
|
||||
<svg
|
||||
class="lucide lucide-arrow-right-left"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="24"
|
||||
height="24"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="#424242"
|
||||
stroke-width="2"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
>
|
||||
<path d="m16 3 4 4-4 4" />
|
||||
<path d="M20 7H4" />
|
||||
<path d="m8 21-4-4 4-4" />
|
||||
<path d="M4 17h16" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 396 B |
3
vscode-extension/res/icons/light/bolt.svg
Normal file
@@ -0,0 +1,3 @@
|
||||
<svg viewBox="0 0 16 16" fill="#424242" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<path d="M4.262 2.866A.75.75 0 0 1 5 2.25h5a.75.75 0 0 1 .721.956L9.994 5.75H12.5a.75.75 0 0 1 .55 1.26l-6.5 7a.75.75 0 0 1-1.285-.657l.82-4.103H4a.75.75 0 0 1-.738-.884l1-5.5zm1.364.884l-.727 4H7a.75.75 0 0 1 .735.897l-.48 2.398L10.78 7.25H9a.75.75 0 0 1-.721-.956l.727-2.544h-3.38z" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 416 B |
26
vscode-extension/res/icons/light/boxes.svg
Normal file
@@ -0,0 +1,26 @@
|
||||
<!-- @license lucide-static v0.575.0 - ISC -->
|
||||
<svg
|
||||
class="lucide lucide-boxes"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="24"
|
||||
height="24"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="#424242"
|
||||
stroke-width="2"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
>
|
||||
<path d="M2.97 12.92A2 2 0 0 0 2 14.63v3.24a2 2 0 0 0 .97 1.71l3 1.8a2 2 0 0 0 2.06 0L12 19v-5.5l-5-3-4.03 2.42Z" />
|
||||
<path d="m7 16.5-4.74-2.85" />
|
||||
<path d="m7 16.5 5-3" />
|
||||
<path d="M7 16.5v5.17" />
|
||||
<path d="M12 13.5V19l3.97 2.38a2 2 0 0 0 2.06 0l3-1.8a2 2 0 0 0 .97-1.71v-3.24a2 2 0 0 0-.97-1.71L17 10.5l-5 3Z" />
|
||||
<path d="m17 16.5-5-3" />
|
||||
<path d="m17 16.5 4.74-2.85" />
|
||||
<path d="M17 16.5v5.17" />
|
||||
<path d="M7.97 4.42A2 2 0 0 0 7 6.13v4.37l5 3 5-3V6.13a2 2 0 0 0-.97-1.71l-3-1.8a2 2 0 0 0-2.06 0l-3 1.8Z" />
|
||||
<path d="M12 8 7.26 5.15" />
|
||||
<path d="m12 8 4.74-2.85" />
|
||||
<path d="M12 13.5V8" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 896 B |
3
vscode-extension/res/icons/light/calendar.svg
Normal file
@@ -0,0 +1,3 @@
|
||||
<svg viewBox="0 0 16 16" fill="#424242" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<path d="M5.25 9.5V8h1.5v1.5h-1.5z" /><path fill-rule="evenodd" clip-rule="evenodd" d="M11.75 2.25H12c.966 0 1.75.784 1.75 1.75v8A1.75 1.75 0 0 1 12 13.75H4A1.75 1.75 0 0 1 2.25 12V4c0-.966.784-1.75 1.75-1.75h.25V1h1.5v1.25h4.5V1h1.5v1.25zM4 3.75a.25.25 0 0 0-.25.25v1.25h8.5V4a.25.25 0 0 0-.25-.25H4zM3.75 12V6.75h8.5V12a.25.25 0 0 1-.25.25H4a.25.25 0 0 1-.25-.25z" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 498 B |
16
vscode-extension/res/icons/light/chart-line.svg
Normal file
@@ -0,0 +1,16 @@
|
||||
<!-- @license lucide-static v0.575.0 - ISC -->
|
||||
<svg
|
||||
class="lucide lucide-chart-line"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="24"
|
||||
height="24"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="#424242"
|
||||
stroke-width="2"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
>
|
||||
<path d="M3 3v16a2 2 0 0 0 2 2h16" />
|
||||
<path d="m19 9-5 5-4-4-3 3" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 358 B |
16
vscode-extension/res/icons/light/code.svg
Normal file
@@ -0,0 +1,16 @@
|
||||
<!-- @license lucide-static v0.575.0 - ISC -->
|
||||
<svg
|
||||
class="lucide lucide-code"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="24"
|
||||
height="24"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="#424242"
|
||||
stroke-width="2"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
>
|
||||
<path d="m16 18 6-6-6-6" />
|
||||
<path d="m8 6-6 6 6 6" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 337 B |
3
vscode-extension/res/icons/light/connections.svg
Normal file
@@ -0,0 +1,3 @@
|
||||
<svg viewBox="0 0 16 16" fill="#424242" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<path d="M3.309 8.75a4.75 4.75 0 0 0 8.05 2.615.75.75 0 1 0-1.06-1.06 3.25 3.25 0 0 1-2.21.95A.77.77 0 0 0 8 11.25a3.25 3.25 0 0 1-3.25-3.246 3.25 3.25 0 0 1 5.548-2.296.75.75 0 0 0 1.06-1.06 4.75 4.75 0 0 0-3.25-1.39A.757.757 0 0 0 8 3.25a4.75 4.75 0 0 0-4.69 4H2a.75.75 0 0 0 0 1.5h1.309z" /> <path d="M14 8.75a.75.75 0 0 0 0-1.5H9.855a2 2 0 1 0 0 1.5H14z" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 490 B |
3
vscode-extension/res/icons/light/dashboard.svg
Normal file
@@ -0,0 +1,3 @@
|
||||
<svg viewBox="0 0 16 16" fill="#424242" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<path d="M4.75 9a.75.75 0 0 0 0 1.5h6.5a.75.75 0 0 0 0-1.5h-6.5zM4 6.25a1 1 0 1 1 2 0 1 1 0 0 1-2 0zM7.75 5.5a.75.75 0 0 0 0 1.5h3.5a.75.75 0 0 0 0-1.5h-3.5z" /><path fill-rule="evenodd" clip-rule="evenodd" d="M1.25 3c0-.966.784-1.75 1.75-1.75h10c.966 0 1.75.784 1.75 1.75v10A1.75 1.75 0 0 1 13 14.75H3A1.75 1.75 0 0 1 1.25 13V3zm1.5 10V3.5h10.5V13a.25.25 0 0 1-.25.25H3a.25.25 0 0 1-.25-.25z" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 525 B |
3
vscode-extension/res/icons/light/database.svg
Normal file
@@ -0,0 +1,3 @@
|
||||
<svg viewBox="0 0 16 16" fill="#424242" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<path clip-rule="evenodd" fill-rule="evenodd" d="M2.665 13.67A.75.75 0 0 1 2.25 13V3a.75.75 0 0 1 .415-.67l.002-.002.002-.001.008-.004.024-.011a7.465 7.465 0 0 1 .38-.164c.255-.103.623-.236 1.088-.37A14.023 14.023 0 0 1 8 1.25c1.583 0 2.903.264 3.831.529.465.133.833.266 1.088.368a7.5 7.5 0 0 1 .38.165l.024.011.008.004.003.001L13 3l.335-.671A.751.751 0 0 1 13.75 3v10a.75.75 0 0 1-.415.67l-.002.002-.002.001-.008.004-.024.011a7.443 7.443 0 0 1-.38.164 11.33 11.33 0 0 1-1.088.37A14.02 14.02 0 0 1 8 14.75a14.02 14.02 0 0 1-3.831-.529 11.327 11.327 0 0 1-1.088-.368 7.409 7.409 0 0 1-.38-.165l-.024-.011-.008-.004-.002-.001-.002-.001zM3.75 3.497c.207-.079.487-.177.831-.275A12.524 12.524 0 0 1 8 2.75c1.417 0 2.597.236 3.419.471.344.098.624.196.831.275v1.24l-.096.038c-.327.133-.642.26-1.106.388-.674.185-1.604.338-3.048.338s-2.374-.153-3.049-.338a8.97 8.97 0 0 1-1.106-.388l-.095-.039V3.496zm7.695 3.113c.289-.08.559-.171.805-.262v2.388l-.096.04c-.327.132-.642.26-1.106.387-.674.185-1.604.338-3.048.338s-2.374-.153-3.049-.338a8.97 8.97 0 0 1-1.201-.427V6.347c.246.091.516.182.805.262C5.378 6.834 6.444 7 8 7c1.556 0 2.621-.166 3.445-.391zM3.75 10.347v2.157c.207.079.487.177.831.275.822.235 2.002.471 3.419.471 1.417 0 2.597-.236 3.419-.471.344-.098.624-.196.831-.275v-2.157c-.246.09-.516.182-.805.261-.824.226-1.89.393-3.445.393-1.556 0-2.622-.167-3.445-.393a9.777 9.777 0 0 1-.805-.26z" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.5 KiB |
3
vscode-extension/res/icons/light/document.svg
Normal file
@@ -0,0 +1,3 @@
|
||||
<svg viewBox="0 0 16 16" fill="#424242" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M8.848 1.25c.451 0 .886.175 1.212.487l3.152 3.027c.344.33.538.786.538 1.262V12.8c0 .527-.219 1.026-.597 1.39-.377.362-.882.56-1.403.56h-7.5c-.52 0-1.026-.199-1.403-.56-.331-.318-.54-.74-.587-1.194l-.01-.196V3.2c0-.527.218-1.026.597-1.39.377-.361.882-.56 1.403-.56h4.598zM4.25 2.75a.528.528 0 0 0-.364.143.427.427 0 0 0-.136.307v9.6l.009.082c.017.081.059.16.127.225a.528.528 0 0 0 .364.143h7.5a.528.528 0 0 0 .364-.143.428.428 0 0 0 .136-.307V6.575H9.249A.575.575 0 0 1 8.674 6V2.75H4.25zm6.4 7.425a.576.576 0 0 1 0 1.15H5.4a.575.575 0 0 1 0-1.15h5.25zm0-2.25a.576.576 0 0 1 0 1.15H5.4a.575.575 0 0 1 0-1.15h5.25zm-3.651-2.25a.576.576 0 0 1 0 1.15h-1.6a.575.575 0 0 1 0-1.15H7zm2.825-.25h1.91L9.824 3.59v1.834z" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 809 B |
17
vscode-extension/res/icons/light/file-question.svg
Normal file
@@ -0,0 +1,17 @@
|
||||
<!-- @license lucide-static v0.575.0 - ISC -->
|
||||
<svg
|
||||
class="lucide lucide-file-question"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="24"
|
||||
height="24"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="#424242"
|
||||
stroke-width="2"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
>
|
||||
<path d="M6 22a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h8a2.4 2.4 0 0 1 1.704.706l3.588 3.588A2.4 2.4 0 0 1 20 8v12a2 2 0 0 1-2 2z" />
|
||||
<path d="M12 17h.01" />
|
||||
<path d="M9.1 9a3 3 0 0 1 5.82 1c0 2-3 3-3 3" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 491 B |
19
vscode-extension/res/icons/light/file-text.svg
Normal file
@@ -0,0 +1,19 @@
|
||||
<!-- @license lucide-static v0.575.0 - ISC -->
|
||||
<svg
|
||||
class="lucide lucide-file-text"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="24"
|
||||
height="24"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="#424242"
|
||||
stroke-width="2"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
>
|
||||
<path d="M6 22a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h8a2.4 2.4 0 0 1 1.704.706l3.588 3.588A2.4 2.4 0 0 1 20 8v12a2 2 0 0 1-2 2z" />
|
||||
<path d="M14 2v5a1 1 0 0 0 1 1h5" />
|
||||
<path d="M10 9H8" />
|
||||
<path d="M16 13H8" />
|
||||
<path d="M16 17H8" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 520 B |
3
vscode-extension/res/icons/light/filter.svg
Normal file
@@ -0,0 +1,3 @@
|
||||
<svg viewBox="0 0 16 16" fill="#424242" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<path d="M1 4.5c0-.69.56-1.25 1.25-1.25h11.5a1.25 1.25 0 1 1 0 2.5H2.25C1.56 5.75 1 5.19 1 4.5zM4 9a1 1 0 0 1 1-1h6a1 1 0 1 1 0 2H5a1 1 0 0 1-1-1zm2 4.25a1 1 0 0 1 1-1h2a1 1 0 1 1 0 2H7a1 1 0 0 1-1-1z" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 333 B |
3
vscode-extension/res/icons/light/folder.svg
Normal file
@@ -0,0 +1,3 @@
|
||||
<svg viewBox="0 0 16 16" fill="#424242" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M3.2 3.75a.501.501 0 0 0-.339.126.322.322 0 0 0-.111.235v7.778c0 .076.032.162.111.235a.5.5 0 0 0 .339.126h9.6a.501.501 0 0 0 .339-.126.322.322 0 0 0 .111-.235v-5.96a.322.322 0 0 0-.111-.235.501.501 0 0 0-.339-.126H7.71l-2-1.818H3.2zm-1.358-.975A2 2 0 0 1 3.2 2.25h3.09l2 1.818h4.51a2 2 0 0 1 1.358.525c.371.344.592.823.592 1.336v5.96c0 .513-.22.992-.592 1.336a2 2 0 0 1-1.358.525H3.2a2 2 0 0 1-1.358-.525 1.821 1.821 0 0 1-.592-1.336V4.11c0-.513.22-.992.592-1.336z" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 646 B |
3
vscode-extension/res/icons/light/folder_database.svg
Normal file
@@ -0,0 +1,3 @@
|
||||
<svg viewBox="0 0 16 16" fill="#424242" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M11.41 7.25c.92 0 1.79.185 2.457.519.622.31 1.293.88 1.293 1.731v3c0 .851-.67 1.42-1.293 1.731-.668.334-1.538.519-2.457.519-.92 0-1.79-.185-2.457-.519-.623-.31-1.293-.88-1.293-1.731v-3c0-.851.67-1.42 1.293-1.731.667-.334 1.537-.519 2.457-.519zm-6.096-6a1.986 1.986 0 0 1 1.665.887l.515.762.005.006a.558.558 0 0 0 .464.251h5.038a1.985 1.985 0 0 1 1.985 1.984V6c0 .395-.32.75-.715.75-.395 0-.715-.356-.715-.75v-.86a.555.555 0 0 0-.555-.554H7.962a1.988 1.988 0 0 1-1.658-.894l-.51-.756-.004-.006a.556.556 0 0 0-.47-.25H2.835a.556.556 0 0 0-.555.555v8.26a.558.558 0 0 0 .555.555H5.35c.394 0 .9.32.9.715 0 .394-.505.714-.9.714H2.835A1.986 1.986 0 0 1 .85 11.495V3.235A1.985 1.985 0 0 1 2.835 1.25h2.479zm8.346 10.075c-.636.273-1.423.425-2.25.425-.828 0-1.615-.152-2.25-.425v1.179l.005.014a.327.327 0 0 0 .053.082c.069.08.197.185.406.29.418.209 1.048.36 1.786.36.737 0 1.367-.151 1.786-.36.208-.105.337-.21.405-.29a.329.329 0 0 0 .059-.096v-1.179zM11.41 8.75c-.738 0-1.368.151-1.786.36a1.29 1.29 0 0 0-.406.29.328.328 0 0 0-.058.096v.008l.005.015a.328.328 0 0 0 .053.08c.069.081.197.186.406.29.418.21 1.048.361 1.786.361.737 0 1.367-.151 1.786-.36a1.29 1.29 0 0 0 .405-.29.33.33 0 0 0 .059-.096v-.008A.329.329 0 0 0 13.6 9.4a1.29 1.29 0 0 0-.405-.29c-.418-.209-1.05-.36-1.786-.36z" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.3 KiB |
18
vscode-extension/res/icons/light/hash.svg
Normal file
@@ -0,0 +1,18 @@
|
||||
<!-- @license lucide-static v0.575.0 - ISC -->
|
||||
<svg
|
||||
class="lucide lucide-hash"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="24"
|
||||
height="24"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="#424242"
|
||||
stroke-width="2"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
>
|
||||
<line x1="4" x2="20" y1="9" y2="9" />
|
||||
<line x1="4" x2="20" y1="15" y2="15" />
|
||||
<line x1="10" x2="8" y1="3" y2="21" />
|
||||
<line x1="16" x2="14" y1="3" y2="21" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 444 B |
3
vscode-extension/res/icons/light/int.svg
Normal file
@@ -0,0 +1,3 @@
|
||||
<svg viewBox="0 0 16 16" fill="#424242" fill-rule="evenodd" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<path d="M7.245 3.583a.75.75 0 0 0-1.49-.166L5.55 5.25H3.5a.75.75 0 0 0 0 1.5h1.884l-.277 2.5H3.5a.75.75 0 0 0 0 1.5h1.44l-.185 1.667a.75.75 0 1 0 1.49.166l.204-1.833h2.49l-.184 1.667a.75.75 0 1 0 1.49.166l.204-1.833H12.5a.75.75 0 0 0 0-1.5h-1.884l.278-2.5H12.5a.75.75 0 0 0 0-1.5h-1.44l.185-1.667a.75.75 0 0 0-1.49-.166L9.55 5.25h-2.49l.184-1.667zM6.894 6.75l-.278 2.5h2.49l.278-2.5h-2.49z" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 542 B |
16
vscode-extension/res/icons/light/key-round.svg
Normal file
@@ -0,0 +1,16 @@
|
||||
<!-- @license lucide-static v0.575.0 - ISC -->
|
||||
<svg
|
||||
class="lucide lucide-key-round"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="24"
|
||||
height="24"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="#424242"
|
||||
stroke-width="2"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
>
|
||||
<path d="M2.586 17.414A2 2 0 0 0 2 18.828V21a1 1 0 0 0 1 1h3a1 1 0 0 0 1-1v-1a1 1 0 0 1 1-1h1a1 1 0 0 0 1-1v-1a1 1 0 0 1 1-1h.172a2 2 0 0 0 1.414-.586l.814-.814a6.5 6.5 0 1 0-4-4z" />
|
||||
<circle cx="16.5" cy="7.5" r=".5" fill="#424242" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 524 B |
3
vscode-extension/res/icons/light/label.svg
Normal file
@@ -0,0 +1,3 @@
|
||||
<svg viewBox="0 0 16 16" fill="#424242" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<path d="M11 9a1 1 0 1 0 0-2 1 1 0 0 0 0 2z" /><path fill-rule="evenodd" clip-rule="evenodd" d="M3 3.25A1.75 1.75 0 0 0 1.25 5v6c0 .966.784 1.75 1.75 1.75h7.5a2.75 2.75 0 0 0 2.2-1.1l1.725-2.3c.6-.8.6-1.9 0-2.7L12.7 4.35a2.75 2.75 0 0 0-2.2-1.1H3zM2.75 5A.25.25 0 0 1 3 4.75h7.5c.393 0 .764.185 1 .5l1.725 2.3c.2.267.2.633 0 .9l-1.725 2.3a1.25 1.25 0 0 1-1 .5H3a.25.25 0 0 1-.25-.25V5z" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 518 B |
18
vscode-extension/res/icons/light/layout-dashboard.svg
Normal file
@@ -0,0 +1,18 @@
|
||||
<!-- @license lucide-static v0.575.0 - ISC -->
|
||||
<svg
|
||||
class="lucide lucide-layout-dashboard"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="24"
|
||||
height="24"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="#424242"
|
||||
stroke-width="2"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
>
|
||||
<rect width="7" height="9" x="3" y="3" rx="1" />
|
||||
<rect width="7" height="5" x="14" y="3" rx="1" />
|
||||
<rect width="7" height="9" x="14" y="12" rx="1" />
|
||||
<rect width="7" height="5" x="3" y="16" rx="1" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 499 B |
16
vscode-extension/res/icons/light/link.svg
Normal file
@@ -0,0 +1,16 @@
|
||||
<!-- @license lucide-static v0.575.0 - ISC -->
|
||||
<svg
|
||||
class="lucide lucide-link"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="24"
|
||||
height="24"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="#424242"
|
||||
stroke-width="2"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
>
|
||||
<path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71" />
|
||||
<path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 430 B |
1
vscode-extension/res/icons/light/metric.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg fill="none" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><path fill-rule="evenodd" clip-rule="evenodd" d="M10.562 5.499 12.25 3.81v8.439H3.81L5.5 10.562l.915.915 1.063-1.063-.915-.915.937-.937.915.915 1.063-1.063-.915-.915.937-.937.915.915 1.063-1.063-.915-.915Zm3.188-2.775c0-.935-1.131-1.404-1.793-.742l-9.975 9.976c-.662.661-.193 1.792.742 1.792H13.75V2.724Z" fill="#424242"/></svg>
|
||||
|
After Width: | Height: | Size: 401 B |
3
vscode-extension/res/icons/light/model.svg
Normal file
@@ -0,0 +1,3 @@
|
||||
<svg viewBox="0 0 16 16" fill="#424242" clip-rule="evenodd" fill-rule="evenodd" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<path d="M8.5 1.423a1 1 0 0 0-1 0L2.554 4.278a1 1 0 0 0-.5.866v5.712a1 1 0 0 0 .5.866L7.5 14.577a1 1 0 0 0 1 0l4.946-2.855a1 1 0 0 0 .5-.866V5.144a1 1 0 0 0-.5-.866L8.5 1.423zM3.554 6.207v4.36l3.696 2.134V8.425L3.554 6.207zM8.75 12.7l3.696-2.134v-4.36L8.75 8.425V12.7zm2.868-7.746L8 2.866 4.382 4.955 8 7.125l3.618-2.17z" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 492 B |
15
vscode-extension/res/icons/light/play.svg
Normal file
@@ -0,0 +1,15 @@
|
||||
<!-- @license lucide-static v0.575.0 - ISC -->
|
||||
<svg
|
||||
class="lucide lucide-play"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="24"
|
||||
height="24"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="#424242"
|
||||
stroke-width="2"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
>
|
||||
<path d="M5 5a2 2 0 0 1 3.008-1.728l11.997 6.998a2 2 0 0 1 .003 3.458l-12 7A2 2 0 0 1 5 19z" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 377 B |
3
vscode-extension/res/icons/light/segment.svg
Normal file
@@ -0,0 +1,3 @@
|
||||
<svg viewBox="0 0 16 16" fill="#424242" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<path d="M4 3a1 1 0 0 0-1 1v3h6v6h3a1 1 0 0 0 1-1V4a1 1 0 0 0-1-1H4z" /><path d="M8 8H2v5a1 1 0 0 0 1 1h5V8z" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 241 B |
3
vscode-extension/res/icons/light/snippet.svg
Normal file
@@ -0,0 +1,3 @@
|
||||
<svg viewBox="0 0 16 16" fill="#424242" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<path d="M1.25 4c0-.966.784-1.75 1.75-1.75a.75.75 0 0 1 0 1.5.25.25 0 0 0-.25.25v2.586c0 .464-.184.909-.513 1.237L2.061 8l.176.177c.329.328.513.773.513 1.237V12c0 .138.112.25.25.25a.75.75 0 0 1 0 1.5A1.75 1.75 0 0 1 1.25 12V9.414a.25.25 0 0 0-.073-.177L.47 8.53a.75.75 0 0 1 0-1.06l.707-.707a.25.25 0 0 0 .073-.177V4zM4.75 11a.75.75 0 0 1 .75-.75H8a.75.75 0 0 1 0 1.5H5.5a.75.75 0 0 1-.75-.75zM4.75 8a.75.75 0 0 1 .75-.75H11a.75.75 0 0 1 0 1.5H5.5A.75.75 0 0 1 4.75 8zM5.5 4.25a.75.75 0 0 0 0 1.5h4a.75.75 0 0 0 0-1.5h-4zM13 13.75A1.75 1.75 0 0 0 14.75 12V9.414a.25.25 0 0 1 .073-.177l.707-.707a.75.75 0 0 0 0-1.06l-.707-.707a.25.25 0 0 1-.073-.177V4A1.75 1.75 0 0 0 13 2.25a.75.75 0 0 0 0 1.5.25.25 0 0 1 .25.25v2.586c0 .464.184.909.513 1.237l.176.177-.176.177a1.75 1.75 0 0 0-.513 1.237V12a.25.25 0 0 1-.25.25.75.75 0 0 0 0 1.5z" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 963 B |
3
vscode-extension/res/icons/light/table.svg
Normal file
@@ -0,0 +1,3 @@
|
||||
<svg viewBox="0 0 16 16" fill="#424242" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<path d="M12.667 1.25c1.15 0 2.083.933 2.083 2.083v9.334c0 1.15-.933 2.083-2.083 2.083H3.333a2.083 2.083 0 0 1-2.083-2.083V3.333c0-1.15.933-2.083 2.083-2.083h9.334zm-9.917 9.5v1.917c0 .322.261.583.583.583H7.25v-2.5h-4.5zm6 0v2.5h3.917a.583.583 0 0 0 .583-.583V10.75h-4.5zm-6-1.5h4.5v-2.5h-4.5v2.5zm6 0h4.5v-2.5h-4.5v2.5zm-5.417-6.5a.583.583 0 0 0-.583.583V5.25h4.5v-2.5H3.333zm5.417 2.5h4.5V3.333a.583.583 0 0 0-.583-.583H8.75v2.5z" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 564 B |