From 2d86dc06ef1eb9258736f7632111853aac291470 Mon Sep 17 00:00:00 2001 From: Ruben Fiszel Date: Sun, 15 Sep 2024 20:45:23 +0200 Subject: [PATCH] fix: improve first time setup experience --- backend/src/main.rs | 1 + backend/windmill-api/src/static_assets.rs | 1 + backend/windmill-api/src/users.rs | 49 +++++++++++- .../src/lib/components/CenteredModal.svelte | 5 +- .../lib/components/InstanceSettings.svelte | 74 ++++++++++++++----- frontend/src/lib/components/Login.svelte | 19 +++-- .../src/lib/components/instanceSettings.ts | 3 +- .../(user)/instance_settings/+page.svelte | 9 ++- .../(logged)/user/(user)/login/+page.svelte | 15 ++-- 9 files changed, 135 insertions(+), 41 deletions(-) diff --git a/backend/src/main.rs b/backend/src/main.rs index 52001a4e0e4f0..608e1870ec961 100644 --- a/backend/src/main.rs +++ b/backend/src/main.rs @@ -371,6 +371,7 @@ async fn windmill_main() -> anyhow::Result<()> { // migration code to avoid break windmill_api::migrate_db(&db).await?; } + let (killpill_tx, mut killpill_rx) = tokio::sync::broadcast::channel::<()>(2); let mut monitor_killpill_rx = killpill_tx.subscribe(); let server_killpill_rx = killpill_tx.subscribe(); diff --git a/backend/windmill-api/src/static_assets.rs b/backend/windmill-api/src/static_assets.rs index ff21119e47773..aa9870907eb41 100644 --- a/backend/windmill-api/src/static_assets.rs +++ b/backend/windmill-api/src/static_assets.rs @@ -40,6 +40,7 @@ fn serve_path(path: &str) -> Response { if path.starts_with("api/") { return Response::builder().status(404).body(Body::empty()).unwrap(); } + match Asset::get(path) { Some(content) => { let body = Body::from(content.data); diff --git a/backend/windmill-api/src/users.rs b/backend/windmill-api/src/users.rs index 99397e6838e3b..b48e1391e4cf3 100644 --- a/backend/windmill-api/src/users.rs +++ b/backend/windmill-api/src/users.rs @@ -8,7 +8,7 @@ #![allow(non_snake_case)] -use std::sync::atomic::{AtomicI64, AtomicU64, Ordering}; +use std::sync::atomic::{AtomicBool, AtomicI64, AtomicU64, Ordering}; use std::sync::Arc; use crate::db::ApiAuthed; @@ -111,6 +111,7 @@ pub fn global_service() -> Router { .route("/leave_instance", post(leave_instance)) .route("/export", get(export_global_users)) .route("/overwrite", post(overwrite_global_users)) + // .route("/list_invite_codes", get(list_invite_codes)) // .route("/create_invite_code", post(create_invite_code)) // .route("/signup", post(signup)) @@ -121,8 +122,8 @@ pub fn global_service() -> Router { pub fn make_unauthed_service() -> Router { Router::new() .route("/login", post(login)) - .route("/logout", post(logout)) - .route("/logout", get(logout)) + .route("/logout", post(logout).get(logout)) + .route("/is_first_time_setup", get(is_first_time_setup)) } fn username_override_from_label(label: Option) -> Option { @@ -826,6 +827,48 @@ pub struct Login { pub password: String, } +lazy_static::lazy_static! { + static ref FIRST_TIME_SETUP: Arc = Arc::new(AtomicBool::new(true)); +} + +pub async fn is_first_time_setup(Extension(db): Extension) -> JsonResult { + if !FIRST_TIME_SETUP.load(std::sync::atomic::Ordering::Relaxed) { + return Ok(Json(false)); + } + let single_user = sqlx::query_scalar!("SELECT 1 FROM password LIMIT 2") + .fetch_all(&db) + .await + .ok() + .unwrap_or_default() + .len() + == 1; + if single_user { + let user_is_admin_and_password_changeme = sqlx::query_scalar!( + "SELECT 1 FROM password WHERE email = 'admin@windmill.dev' AND password_hash = '$argon2id$v=19$m=4096,t=3,p=1$oLJo/lPn/gezXCuFOEyaNw$i0T2tCkw3xUFsrBIKZwr8jVNHlIfoxQe+HfDnLtd12I'" + ).fetch_all(&db) + .await + .ok() + .unwrap_or_default() + .len() == 1; + if user_is_admin_and_password_changeme { + let base_url_is_not_set = + sqlx::query_scalar!("SELECT COUNT(*) FROM global_settings WHERE name = 'base_url'") + .fetch_optional(&db) + .await + .ok() + .flatten() + .flatten() + .unwrap_or(0) + == 0; + if base_url_is_not_set { + return Ok(Json(true)); + } + } + } + FIRST_TIME_SETUP.store(false, std::sync::atomic::Ordering::Relaxed); + Ok(Json(false)) +} + #[derive(Deserialize)] struct WorkspaceUsername { pub username: String, diff --git a/frontend/src/lib/components/CenteredModal.svelte b/frontend/src/lib/components/CenteredModal.svelte index 2be4e896fd2e9..3548a20a8f616 100644 --- a/frontend/src/lib/components/CenteredModal.svelte +++ b/frontend/src/lib/components/CenteredModal.svelte @@ -7,6 +7,7 @@ export let subtitle: string | undefined = undefined export let title = 'Windmill' export let disableLogo = false + export let large = false setLicense() @@ -23,7 +24,9 @@ {/if}

diff --git a/frontend/src/lib/components/InstanceSettings.svelte b/frontend/src/lib/components/InstanceSettings.svelte index 60f4ab4eea458..18d1f6f203954 100644 --- a/frontend/src/lib/components/InstanceSettings.svelte +++ b/frontend/src/lib/components/InstanceSettings.svelte @@ -1,7 +1,7 @@
@@ -249,6 +286,7 @@
+ {#each Object.keys(settings) as category} {#if category == 'SMTP'} @@ -285,8 +323,7 @@ size="xs">Send usage {/if} - {/if} - {#if category == 'SSO/OAuth'} + {:else if category == 'SSO/OAuth'}
SSO @@ -477,7 +514,7 @@
{#each settings[category] as setting} - {#if !setting.cloudonly || isCloudHosted()} + {#if (!setting.cloudonly || isCloudHosted()) && showSetting(setting.key, values)} {#if setting.ee_only != undefined && !$enterpriseLicense}
@@ -494,7 +531,9 @@ {/if} {#if values} {@const hasError = setting.isValid && !setting.isValid(values[setting.key])} - {#if setting.fieldType == 'text'} + {#if loading} + + {:else if setting.fieldType == 'text'} - License key expired on {expiration}
{:else}
- Invalid license key format + Invalid license key format
{/if} {/if} @@ -617,13 +658,12 @@
{/if} {#if licenseKeyChanged && !$enterpriseLicense} -
- - - Refresh page after setting and saving license key to unlock all - features - -
+ {#if version.startsWith('CE')} +
License key is set but image used is the Community Edition {version}. + Switch image to EE.
+ {/if} {/if} {#if valid || expiration} @@ -810,7 +850,7 @@ {/if} {#if hasError} - + {setting.error ?? ''} {/if} diff --git a/frontend/src/lib/components/Login.svelte b/frontend/src/lib/components/Login.svelte index 6714d52b0fecc..7b5699b93ff55 100644 --- a/frontend/src/lib/components/Login.svelte +++ b/frontend/src/lib/components/Login.svelte @@ -22,6 +22,7 @@ export let password: string | undefined = undefined export let error: string | undefined = undefined export let popup: boolean = false + export let firstTime: boolean = false const providers = [ { @@ -75,6 +76,11 @@ return } + if (firstTime) { + goto('/user/first-time') + return + } + // Once logged in, we can fetch the workspaces $usersWorkspaceStore = await WorkspaceService.listUserWorkspaces() // trigger a reload of the user @@ -88,13 +94,6 @@ } async function redirectUser() { - const firstTimeCookie = - document.cookie.match('(^|;)\\s*first_time\\s*=\\s*([^;]+)')?.pop() || '0' - if (Number(firstTimeCookie) > 0 && email === 'admin@windmill.dev') { - goto('/user/first-time') - return - } - if (rd?.startsWith('http')) { window.location.href = rd return @@ -177,6 +176,7 @@ dispatch('login') } } + function storeRedirect(provider: string) { if (rd) { try { @@ -263,6 +263,11 @@ {#if showPassword}
+ {#if firstTime} +
First time login: admin@windmill.dev / changeme
+ {/if}
{#if isCloudHosted()}

diff --git a/frontend/src/lib/components/instanceSettings.ts b/frontend/src/lib/components/instanceSettings.ts index b743eee033c46..21ee3cb098268 100644 --- a/frontend/src/lib/components/instanceSettings.ts +++ b/frontend/src/lib/components/instanceSettings.ts @@ -91,8 +91,7 @@ export const settings: Record = { description: 'Whether we should consider the reported usage of this instance as non-prod', key: 'dev_instance', fieldType: 'boolean', - storage: 'setting', - ee_only: 'This is only relevant for EE' + storage: 'setting' }, { label: 'Retention period in secs', diff --git a/frontend/src/routes/(root)/(logged)/user/(user)/instance_settings/+page.svelte b/frontend/src/routes/(root)/(logged)/user/(user)/instance_settings/+page.svelte index c07e7e74c069f..44bcc5c03eb2b 100644 --- a/frontend/src/routes/(root)/(logged)/user/(user)/instance_settings/+page.svelte +++ b/frontend/src/routes/(root)/(logged)/user/(user)/instance_settings/+page.svelte @@ -3,17 +3,20 @@ import CenteredModal from '$lib/components/CenteredModal.svelte' import InstanceSettings from '$lib/components/InstanceSettings.svelte' import { Button } from '$lib/components/common' + + let saved = false - - + + (saved = true)} />

You can change these settings later in the instance settings but finishing setup will leave this page.

Finish Setup {!saved ? '(save settings at least once)' : ''} diff --git a/frontend/src/routes/(root)/(logged)/user/(user)/login/+page.svelte b/frontend/src/routes/(root)/(logged)/user/(user)/login/+page.svelte index 210e228dc1ae9..a443df6b96c2a 100644 --- a/frontend/src/routes/(root)/(logged)/user/(user)/login/+page.svelte +++ b/frontend/src/routes/(root)/(logged)/user/(user)/login/+page.svelte @@ -19,15 +19,9 @@ const rd = $page.url.searchParams.get('rd') ?? undefined let showPassword = false + let firstTime = false async function redirectUser() { - const firstTimeCookie = - document.cookie.match('(^|;)\\s*first_time\\s*=\\s*([^;]+)')?.pop() || '0' - if (Number(firstTimeCookie) > 0 && email === 'admin@windmill.dev') { - goto('/user/first-time') - return - } - if (rd?.startsWith('http')) { window.location.href = rd return @@ -81,9 +75,14 @@ redirectUser() } + async function checkFirstTimeSetup() { + firstTime = await (await fetch('/api/auth/is_first_time_setup')).json() + } + try { setLicense() redirectIfNecessary() + checkFirstTimeSetup() } catch { clearStores() } @@ -113,6 +112,6 @@
- +