diff --git a/.github/DockerfileBackendTests b/.github/DockerfileBackendTests index 00082bf42f..89ef4b79e2 100644 --- a/.github/DockerfileBackendTests +++ b/.github/DockerfileBackendTests @@ -58,7 +58,7 @@ RUN /usr/local/bin/python3 -m pip install nltk RUN mkdir -p /nsjail_data/python && HOME=/nsjail_data/python /usr/local/bin/python3 -m nltk.downloader vader_lexicon COPY --from=nsjail /nsjail/nsjail /bin/nsjail - +COPY --from=oven/bun:1.0.0 /usr/local/bin/bun /usr/bin/bun COPY --from=denoland/deno:latest /usr/bin/deno /usr/bin/deno RUN apt-get update \ diff --git a/backend/.sqlx/query-584cb984ea6528baac48c5c437ad2ee3bef92e3fada73dcf519147964c0f4f4a.json b/backend/.sqlx/query-584cb984ea6528baac48c5c437ad2ee3bef92e3fada73dcf519147964c0f4f4a.json new file mode 100644 index 0000000000..c4d5eb1f75 --- /dev/null +++ b/backend/.sqlx/query-584cb984ea6528baac48c5c437ad2ee3bef92e3fada73dcf519147964c0f4f4a.json @@ -0,0 +1,14 @@ +{ + "db_name": "PostgreSQL", + "query": "INSERT INTO folder (workspace_id, name, display_name, owners, extra_perms) VALUES ($1, 'app_themes', 'App Themes', ARRAY[]::TEXT[], '{\"g/all\": false}') ON CONFLICT DO NOTHING", + "describe": { + "columns": [], + "parameters": { + "Left": [ + "Varchar" + ] + }, + "nullable": [] + }, + "hash": "584cb984ea6528baac48c5c437ad2ee3bef92e3fada73dcf519147964c0f4f4a" +} diff --git a/backend/.sqlx/query-d956b7525f83e6d03beadc4bb3ee2798f53d990b01b17bdbc044719d4908e3f4.json b/backend/.sqlx/query-d956b7525f83e6d03beadc4bb3ee2798f53d990b01b17bdbc044719d4908e3f4.json new file mode 100644 index 0000000000..a1e9f97ed7 --- /dev/null +++ b/backend/.sqlx/query-d956b7525f83e6d03beadc4bb3ee2798f53d990b01b17bdbc044719d4908e3f4.json @@ -0,0 +1,14 @@ +{ + "db_name": "PostgreSQL", + "query": "INSERT INTO resource (workspace_id, path, value, description, resource_type) VALUES ($1, 'f/app_themes/theme_0', '{\"name\": \"Default Theme\", \"value\": \"\"}', 'The default app theme', 'app_theme') ON CONFLICT DO NOTHING", + "describe": { + "columns": [], + "parameters": { + "Left": [ + "Varchar" + ] + }, + "nullable": [] + }, + "hash": "d956b7525f83e6d03beadc4bb3ee2798f53d990b01b17bdbc044719d4908e3f4" +} diff --git a/backend/migrations/20230915084031_add_themes.down.sql b/backend/migrations/20230915084031_add_themes.down.sql new file mode 100644 index 0000000000..d2f607c5b8 --- /dev/null +++ b/backend/migrations/20230915084031_add_themes.down.sql @@ -0,0 +1 @@ +-- Add down migration script here diff --git a/backend/migrations/20230915084031_add_themes.up.sql b/backend/migrations/20230915084031_add_themes.up.sql new file mode 100644 index 0000000000..323b31a049 --- /dev/null +++ b/backend/migrations/20230915084031_add_themes.up.sql @@ -0,0 +1,3 @@ +-- Add up migration script here +INSERT INTO folder (workspace_id, name, display_name, owners, extra_perms) SELECT id, 'app_themes', 'App Themes', ARRAY[]::TEXT[], '{"g/all": false}' FROM workspace ON CONFLICT DO NOTHING; +INSERT INTO resource (workspace_id, path, value, description, resource_type) SELECT id, 'f/app_themes/theme_0', '{"name": "Default Theme", "value": ""}', 'The default app theme', 'app_theme' FROM workspace ON CONFLICT DO NOTHING \ No newline at end of file diff --git a/backend/windmill-api/openapi.yaml b/backend/windmill-api/openapi.yaml index 479ebc23a1..4dd7f35b95 100644 --- a/backend/windmill-api/openapi.yaml +++ b/backend/windmill-api/openapi.yaml @@ -3519,6 +3519,22 @@ paths: schema: $ref: "#/components/schemas/AppWithLastVersion" + /w/{workspace}/apps_u/public_resource/{path}: + get: + summary: get public resource + operationId: get public resource + tags: + - app + parameters: + - $ref: "#/components/parameters/WorkspaceId" + - $ref: "#/components/parameters/Path" + responses: + "200": + description: resource value + content: + application/json: + schema: {} + /w/{workspace}/apps/secret_of/{path}: get: summary: get public secret of app diff --git a/backend/windmill-api/src/apps.rs b/backend/windmill-api/src/apps.rs index 1e8e3e8768..370b093f7c 100644 --- a/backend/windmill-api/src/apps.rs +++ b/backend/windmill-api/src/apps.rs @@ -57,6 +57,7 @@ pub fn unauthed_service() -> Router { Router::new() .route("/execute_component/*path", post(execute_component)) .route("/public_app/:secret", get(get_public_app_by_secret)) + .route("/public_resource/*path", get(get_public_resource)) } pub fn global_service() -> Router { @@ -341,6 +342,27 @@ async fn get_public_app_by_secret( Ok(Json(app)) } +async fn get_public_resource( + Extension(db): Extension, + Path((w_id, path)): Path<(String, StripPath)>, +) -> JsonResult> { + let path = path.to_path(); + if !path.starts_with("f/app_themes/") { + return Err(Error::BadRequest( + "Only app themes are public resources".to_string(), + )); + } + let res = sqlx::query_scalar!( + "SELECT value from resource WHERE path = $1 AND workspace_id = $2", + path.to_owned(), + &w_id + ) + .fetch_optional(&db) + .await? + .flatten(); + Ok(Json(res)) +} + async fn get_secret_id( authed: ApiAuthed, Extension(user_db): Extension, diff --git a/backend/windmill-api/src/workspaces.rs b/backend/windmill-api/src/workspaces.rs index 019e658efc..97a289580a 100644 --- a/backend/windmill-api/src/workspaces.rs +++ b/backend/windmill-api/src/workspaces.rs @@ -950,6 +950,21 @@ async fn create_workspace( .execute(&mut *tx) .await?; + sqlx::query!( + "INSERT INTO folder (workspace_id, name, display_name, owners, extra_perms) VALUES ($1, 'app_themes', 'App Themes', ARRAY[]::TEXT[], '{\"g/all\": false}') ON CONFLICT DO NOTHING", + nw.id, + ) + .execute(&mut *tx) + .await?; + + + sqlx::query!( + "INSERT INTO resource (workspace_id, path, value, description, resource_type) VALUES ($1, 'f/app_themes/theme_0', '{\"name\": \"Default Theme\", \"value\": \"\"}', 'The default app theme', 'app_theme') ON CONFLICT DO NOTHING", + nw.id, + ) + .execute(&mut *tx) + .await?; + audit_log( &mut *tx, &authed.username, diff --git a/backend/windmill-worker/src/worker.rs b/backend/windmill-worker/src/worker.rs index ba88e79349..d4466622f1 100644 --- a/backend/windmill-worker/src/worker.rs +++ b/backend/windmill-worker/src/worker.rs @@ -608,7 +608,7 @@ pub async fn run_workerDownload diff --git a/frontend/src/lib/components/Editor.svelte b/frontend/src/lib/components/Editor.svelte index 06915ee038..2d7e85a544 100644 --- a/frontend/src/lib/components/Editor.svelte +++ b/frontend/src/lib/components/Editor.svelte @@ -24,6 +24,8 @@ import 'monaco-editor/esm/vs/basic-languages/graphql/graphql.contribution' import 'monaco-editor/esm/vs/basic-languages/powershell/powershell.contribution' import 'monaco-editor/esm/vs/language/typescript/monaco.contribution' + import 'monaco-editor/esm/vs/basic-languages/css/css.contribution' + import { MonacoLanguageClient, initServices } from 'monaco-languageclient' import { toSocket, WebSocketMessageReader, WebSocketMessageWriter } from 'vscode-ws-jsonrpc' import { CloseAction, ErrorAction, RequestType, NotificationType } from 'vscode-languageclient' @@ -49,7 +51,15 @@ let divEl: HTMLDivElement | null = null let editor: meditor.IStandaloneCodeEditor - export let lang: 'typescript' | 'python' | 'go' | 'shell' | 'sql' | 'graphql' | 'powershell' + export let lang: + | 'typescript' + | 'python' + | 'go' + | 'shell' + | 'sql' + | 'graphql' + | 'powershell' + | 'css' export let deno: boolean export let code: string = '' export let cmdEnterAction: (() => void) | undefined = undefined diff --git a/frontend/src/lib/components/HighlightCode.svelte b/frontend/src/lib/components/HighlightCode.svelte index 4602e7fe8f..977115b830 100644 --- a/frontend/src/lib/components/HighlightCode.svelte +++ b/frontend/src/lib/components/HighlightCode.svelte @@ -42,6 +42,7 @@ return sql case 'powershell': return powershell + default: return typescript } diff --git a/frontend/src/lib/components/SimpleEditor.svelte b/frontend/src/lib/components/SimpleEditor.svelte index 4319c8893f..c41362f872 100644 --- a/frontend/src/lib/components/SimpleEditor.svelte +++ b/frontend/src/lib/components/SimpleEditor.svelte @@ -18,6 +18,9 @@ import 'monaco-editor/esm/vs/basic-languages/graphql/graphql.contribution' import 'monaco-editor/esm/vs/language/json/monaco.contribution' import 'monaco-editor/esm/vs/language/typescript/monaco.contribution' + import 'monaco-editor/esm/vs/basic-languages/css/css.contribution' + import 'monaco-editor/esm/vs/language/css/monaco.contribution' + import { allClasses, authorizedClassnames } from './apps/editor/componentsPanel/cssUtils' import { createEventDispatcher, onDestroy, onMount } from 'svelte' @@ -185,6 +188,40 @@ }) } + $: lang == 'css' && addCSSClassCompletions() + + function addCSSClassCompletions() { + languages.registerCompletionItemProvider('css', { + provideCompletionItems: function (model, position, context, token) { + const word = model.getWordUntilPosition(position) + const range = { + startLineNumber: position.lineNumber, + startColumn: word.startColumn, + endLineNumber: position.lineNumber, + endColumn: word.endColumn + } + + if (word && word.word) { + const currentWord = word.word + + const suggestions = allClasses + .filter((className) => className.includes(currentWord)) + .map((className) => ({ + label: className, + kind: languages.CompletionItemKind.Class, + insertText: className, + documentation: 'Custom CSS class', + range: range + })) + + return { suggestions } + } + + return { suggestions: [] } + } + }) + } + function loadExtraLib() { if (lang == 'javascript') { const stdLib = { content: libStdContent, filePath: 'es5.d.ts' } @@ -199,6 +236,38 @@ } else { languages.typescript.javascriptDefaults.setExtraLibs([stdLib]) } + } else if (lang === 'css') { + const cssClasses = authorizedClassnames.map((className) => '.' + className) + + languages.registerCompletionItemProvider('css', { + provideCompletionItems: function (model, position, context, token) { + const word = model.getWordUntilPosition(position) + const range = { + startLineNumber: position.lineNumber, + startColumn: word.startColumn, + endLineNumber: position.lineNumber, + endColumn: word.endColumn + } + + if (word && word.word) { + const currentWord = word.word + + const suggestions = cssClasses + .filter((className) => className.includes(currentWord)) + .map((className) => ({ + label: className, + kind: languages.CompletionItemKind.Class, + insertText: className, + documentation: 'Custom CSS class', + range: range + })) + + return { suggestions } + } + + return { suggestions: [] } + } + }) } } diff --git a/frontend/src/lib/components/TableSimple.svelte b/frontend/src/lib/components/TableSimple.svelte index 464be0a514..49f35f0edb 100644 --- a/frontend/src/lib/components/TableSimple.svelte +++ b/frontend/src/lib/components/TableSimple.svelte @@ -6,10 +6,11 @@ export let headers: string[] | undefined export let data: any[] | undefined // Object containing the data export let keys: string[] + export let size: 'sm' | 'md' | 'lg' = 'md' -
- +
+ {#if headers} diff --git a/frontend/src/lib/components/Tooltip.svelte b/frontend/src/lib/components/Tooltip.svelte index a97fb3b67e..cc8ac82c15 100644 --- a/frontend/src/lib/components/Tooltip.svelte +++ b/frontend/src/lib/components/Tooltip.svelte @@ -7,6 +7,7 @@ export let wrapperClass = '' export let placement: PopoverPlacement | undefined = undefined export let documentationLink: string | undefined = undefined + export let small = false @@ -15,7 +16,7 @@ ? 'text-tertiary-inverse' : 'text-tertiary'} {$$props.class} relative" > - +
diff --git a/frontend/src/lib/components/apps/components/buttons/AppButton.svelte b/frontend/src/lib/components/apps/components/buttons/AppButton.svelte index fb5b6eafad..59c0b2a4d6 100644 --- a/frontend/src/lib/components/apps/components/buttons/AppButton.svelte +++ b/frontend/src/lib/components/apps/components/buttons/AppButton.svelte @@ -17,7 +17,8 @@ import { initConfig, initOutput } from '../../editor/appUtils' import { components } from '../../editor/component' import ResolveConfig from '../helpers/ResolveConfig.svelte' - import { concatCustomCss } from '../../utils' + import ResolveStyle from '../helpers/ResolveStyle.svelte' + import { initCss } from '../../utils' export let id: string export let componentInput: AppInput | undefined @@ -123,7 +124,7 @@ } let loading = false - $: css = concatCustomCss($app.css?.buttoncomponent, customCss) + let css = initCss($app.css?.buttoncomponent, customCss) {#each Object.keys(components['buttoncomponent'].initialData.configuration) as key (key)} @@ -136,6 +137,16 @@ /> {/each} +{#each Object.keys(css ?? {}) as key (key)} + +{/each} + - + {#if errorsMessage}
{errorsMessage}
{/if}