mirror of
https://github.com/windmill-labs/windmill.git
synced 2026-08-23 08:00:45 +00:00
fix: improve first time setup experience
This commit is contained in:
+20
@@ -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"
|
||||
}
|
||||
+20
@@ -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"
|
||||
}
|
||||
+20
@@ -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"
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user