fix: improve first time setup experience

This commit is contained in:
Ruben Fiszel
2024-09-15 20:45:23 +02:00
parent ad5363375d
commit d533a017a7
12 changed files with 153 additions and 39 deletions
@@ -0,0 +1,20 @@
{
"db_name": "PostgreSQL",
"query": "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'",
"describe": {
"columns": [
{
"ordinal": 0,
"name": "?column?",
"type_info": "Int4"
}
],
"parameters": {
"Left": []
},
"nullable": [
null
]
},
"hash": "0aa47e68f9e21115d96a8a1117e95f800c8bfb9bd6872a657c7e7131a21563c8"
}
@@ -0,0 +1,20 @@
{
"db_name": "PostgreSQL",
"query": "SELECT COUNT(*) FROM global_settings WHERE name = 'base_url'",
"describe": {
"columns": [
{
"ordinal": 0,
"name": "count",
"type_info": "Int8"
}
],
"parameters": {
"Left": []
},
"nullable": [
null
]
},
"hash": "3d60b4f2cbedb61affeff94fd77b7c86493c30a4d3d648d0660a3281824156b9"
}
@@ -0,0 +1,20 @@
{
"db_name": "PostgreSQL",
"query": "SELECT 1 FROM password LIMIT 2",
"describe": {
"columns": [
{
"ordinal": 0,
"name": "?column?",
"type_info": "Int4"
}
],
"parameters": {
"Left": []
},
"nullable": [
null
]
},
"hash": "74d5757014fc7a6818ae857bfdda9f6317cf2b9e0a78cbad80ebdc935d3246fd"
}
+1
View File
@@ -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();
@@ -40,6 +40,7 @@ fn serve_path(path: &str) -> Response<Body> {
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);
+3
View File
@@ -41,6 +41,9 @@ use std::{
time::Duration,
};
#[cfg(feature = "benchmark")]
use std::sync::atomic::AtomicUsize;
use uuid::Uuid;
use windmill_common::{
@@ -7,6 +7,7 @@
export let subtitle: string | undefined = undefined
export let title = 'Windmill'
export let disableLogo = false
export let large = false
setLicense()
</script>
@@ -23,7 +24,9 @@
{/if}
<div
class="border rounded-md shadow-md bg-surface w-full max-w-[640px] p-4 sm:py-8 sm:px-10 mb-6 md:mb-20 z-10"
class="border rounded-md shadow-md bg-surface w-full {large
? 'max-w-5xl'
: 'max-w-[640px]'} p-4 sm:py-8 sm:px-10 mb-6 md:mb-20 z-10"
>
<div class="mb-10">
<h1 class="text-center text-primary">
@@ -1,7 +1,7 @@
<script lang="ts">
import { settings, settingsKeys, type SettingStorage } from './instanceSettings'
import { Button, Tab, TabContent, Tabs } from '$lib/components/common'
import { ConfigService, SettingService } from '$lib/gen'
import { Button, Skeleton, Tab, TabContent, Tabs } from '$lib/components/common'
import { ConfigService, SettingService, SettingsService } from '$lib/gen'
import Toggle from '$lib/components/Toggle.svelte'
import SecondsInput from '$lib/components/common/seconds/SecondsInput.svelte'
import Tooltip from '$lib/components/Tooltip.svelte'
@@ -37,6 +37,8 @@
import Popover from './Popover.svelte'
import { base } from '$lib/base'
import { createEventDispatcher } from 'svelte'
import { setLicense } from '$lib/enterpriseUtils'
export let tab: string = 'Core'
export let hideTabs: boolean = false
@@ -54,8 +56,21 @@
let serverConfig = {}
let initialValues: Record<string, any> = {}
let loading = true
let version: string = ''
loadSettings()
loadVersion()
const dispatch = createEventDispatcher()
async function loadVersion() {
version = await SettingsService.backendVersion()
}
async function loadSettings() {
loading = true
try {
serverConfig = (await ConfigService.getConfig({ name: 'server' })) ?? {}
} catch (e) {
@@ -95,6 +110,8 @@
if (values['base_url'] == undefined) {
values['base_url'] = 'http://localhost'
}
loading = false
latestKeyRenewalAttempt = await SettingService.getLatestKeyRenewalAttempt()
}
@@ -113,6 +130,7 @@
})
serverConfig = JSON.parse(JSON.stringify(newServerConfig))
}
let licenseKeySet = false
await Promise.all(
allSettings
.filter((x) => {
@@ -125,7 +143,13 @@
)
})
.map(async ([_, x]) => {
await SettingService.setGlobal({ key: x.key, requestBody: { value: values?.[x.key] } })
if (x.key == 'license_key') {
licenseKeySet = true
}
return await SettingService.setGlobal({
key: x.key,
requestBody: { value: values?.[x.key] }
})
})
)
initialValues = JSON.parse(JSON.stringify(values))
@@ -145,10 +169,14 @@
requestBody: { value: requirePreexistingUserForOauth }
})
}
if (licenseKeySet) {
setLicense()
}
} else {
console.error('Values not loaded')
}
sendUserToast('Settings updated')
dispatch('saved')
}
let oauths: Record<string, any> = {}
@@ -238,6 +266,15 @@
opening = false
}
}
function showSetting(setting: string, values: Record<string, any>) {
if (setting == 'dev_instance') {
if (values['license_key'] == undefined) {
return false
}
}
return true
}
</script>
<div class="pb-8">
@@ -249,6 +286,7 @@
<svelte:fragment slot="content">
<div class="pt-4" />
{#each Object.keys(settings) as category}
<TabContent value={category}>
{#if category == 'SMTP'}
@@ -285,8 +323,7 @@
size="xs">Send usage</Button
>
{/if}
{/if}
{#if category == 'SSO/OAuth'}
{:else if category == 'SSO/OAuth'}
<div>
<Tabs bind:selected={ssoOrOauth} class="mt-2 mb-4">
<Tab value="sso">SSO</Tab>
@@ -477,7 +514,7 @@
<div>
<div class="flex-col flex gap-2 pb-4">
{#each settings[category] as setting}
{#if !setting.cloudonly || isCloudHosted()}
{#if (!setting.cloudonly || isCloudHosted()) && showSetting(setting.key, values)}
{#if setting.ee_only != undefined && !$enterpriseLicense}
<div class="flex text-xs items-center gap-1 text-yellow-500 whitespace-nowrap">
<AlertTriangle size={16} />
@@ -494,7 +531,9 @@
{/if}
{#if values}
{@const hasError = setting.isValid && !setting.isValid(values[setting.key])}
{#if setting.fieldType == 'text'}
{#if loading}
<Skeleton layout={[[2.5]]} />
{:else if setting.fieldType == 'text'}
<input
disabled={setting.ee_only != undefined && !$enterpriseLicense}
type="text"
@@ -559,14 +598,16 @@
{:else if expiration}
<div class="flex flex-row gap-1 items-center">
<AlertCircle size={12} class="text-red-600" />
<span class="text-red-600 text-xs"
<span class="text-red-600 dark:text-red-400 text-xs"
>License key expired on {expiration}</span
>
</div>
{:else}
<div class="flex flex-row gap-1 items-center">
<AlertCircle size={12} class="text-red-600" />
<span class="text-red-600 text-xs">Invalid license key format</span>
<span class="text-red-600 dark:text-red-400 text-xs"
>Invalid license key format</span
>
</div>
{/if}
{/if}
@@ -617,13 +658,12 @@
</div>
{/if}
{#if licenseKeyChanged && !$enterpriseLicense}
<div class="flex flex-row items-center gap-1">
<AlertCircle size={12} class="text-yellow-600" />
<span class="text-xs text-yellow-600">
Refresh page after setting and saving license key to unlock all
features
</span>
</div>
{#if version.startsWith('CE')}
<div class="text-red-400"
>License key is set but image used is the Community Edition {version}.
Switch image to EE.</div
>
{/if}
{/if}
{#if valid || expiration}
@@ -810,7 +850,7 @@
{/if}
{#if hasError}
<span class="text-red-500 text-xs">
<span class="text-red-500 dark:text-red-400 text-sm">
{setting.error ?? ''}
</span>
{/if}
+12 -7
View File
@@ -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}
<div>
{#if firstTime}
<div class="text-lg text-center w-full pb-6"
>First time login: admin@windmill.dev / changeme</div
>
{/if}
<div class="space-y-6">
{#if isCloudHosted()}
<p class="text-xs text-tertiary italic pb-6">
@@ -91,8 +91,7 @@ export const settings: Record<string, Setting[]> = {
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',
@@ -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
</script>
<CenteredModal title="Instance Settings">
<InstanceSettings />
<CenteredModal large title="Instance Settings">
<InstanceSettings on:saved={() => (saved = true)} />
<p class="text-secondary text-sm px-2 py-4">
You can change these settings later in the instance settings but finishing setup will leave this
page.
</p>
<Button
disabled={!saved}
on:click={() => {
goto('/apps/get/g/all/setup_app?nomenubar=true')
}}>Finish Setup</Button
goto('/apps/get/g/all/setup_app?nomenubar=true&workspace=admins')
}}>Finish Setup {!saved ? '(save settings at least once)' : ''}</Button
>
</CenteredModal>
@@ -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 @@
<div class="flex justify-end">
<DarkModeToggle forcedDarkMode={false} />
</div>
<Login {rd} {error} {password} {email} />
<Login {firstTime} {rd} {error} {password} {email} />
</div>
</div>