mirror of
https://github.com/windmill-labs/windmill.git
synced 2026-10-21 00:03:31 +00:00
add tags to custom instance dbs
This commit is contained in:
@@ -1,4 +1,17 @@
|
||||
-- Add up migration script here
|
||||
UPDATE global_settings
|
||||
SET value = jsonb_build_object(
|
||||
'status', (
|
||||
SELECT jsonb_object_agg(
|
||||
key,
|
||||
value - 'tag'
|
||||
)
|
||||
FROM jsonb_each(value->'databases')
|
||||
),
|
||||
'user_pwd', value->'user_pwd'
|
||||
)
|
||||
WHERE name = 'custom_instance_pg_databases';
|
||||
|
||||
|
||||
UPDATE global_settings
|
||||
SET name = 'ducklake_settings',
|
||||
value = jsonb_build_object(
|
||||
|
||||
@@ -12,3 +12,16 @@ SET name = 'custom_instance_pg_databases',
|
||||
'status', value->'instance_catalog_db_status'
|
||||
)
|
||||
WHERE name = 'ducklake_settings';
|
||||
|
||||
UPDATE global_settings
|
||||
SET value = jsonb_build_object(
|
||||
'databases', (
|
||||
SELECT jsonb_object_agg(
|
||||
key,
|
||||
value || jsonb_build_object('tag', 'ducklake')
|
||||
)
|
||||
FROM jsonb_each(value->'status')
|
||||
),
|
||||
'user_pwd', value->'user_pwd'
|
||||
)
|
||||
WHERE name = 'custom_instance_pg_databases';
|
||||
|
||||
@@ -831,10 +831,10 @@ paths:
|
||||
schema:
|
||||
type: boolean
|
||||
|
||||
/settings/get_custom_instance_pg_databases_status:
|
||||
/settings/get_custom_instance_pg_databases:
|
||||
post:
|
||||
summary: Returns the set-up statuses of custom instance pg databases
|
||||
operationId: getCustomInstanceDbStatus
|
||||
operationId: getCustomInstanceDbs
|
||||
tags:
|
||||
- setting
|
||||
responses:
|
||||
@@ -845,7 +845,7 @@ paths:
|
||||
schema:
|
||||
type: object
|
||||
additionalProperties:
|
||||
$ref: "#/components/schemas/CustomInstanceDbStatus"
|
||||
$ref: "#/components/schemas/CustomInstanceDb"
|
||||
|
||||
/settings/setup_custom_instance_pg_database/{name}:
|
||||
post:
|
||||
@@ -853,6 +853,15 @@ paths:
|
||||
operationId: setupCustomInstanceDb
|
||||
tags:
|
||||
- setting
|
||||
requestBody:
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
properties:
|
||||
tag:
|
||||
$ref: "#/components/schemas/CustomInstanceDbTag"
|
||||
parameters:
|
||||
- in: path
|
||||
name: name
|
||||
@@ -866,7 +875,7 @@ paths:
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/CustomInstanceDbStatus"
|
||||
$ref: "#/components/schemas/CustomInstanceDb"
|
||||
|
||||
/settings/global/{key}:
|
||||
get:
|
||||
@@ -18181,7 +18190,7 @@ components:
|
||||
- SKIP
|
||||
- FAIL
|
||||
|
||||
CustomInstanceDbStatusLogs:
|
||||
CustomInstanceDbLogs:
|
||||
type: object
|
||||
properties:
|
||||
super_admin:
|
||||
@@ -18192,20 +18201,25 @@ components:
|
||||
$ref: "#/components/schemas/LoggedWizardStatus"
|
||||
created_database:
|
||||
$ref: "#/components/schemas/LoggedWizardStatus"
|
||||
description: Created database status log
|
||||
db_connect:
|
||||
$ref: "#/components/schemas/LoggedWizardStatus"
|
||||
grant_permissions:
|
||||
$ref: "#/components/schemas/LoggedWizardStatus"
|
||||
|
||||
CustomInstanceDbStatus:
|
||||
CustomInstanceDbTag:
|
||||
type: string
|
||||
enum:
|
||||
- ducklake
|
||||
- datatable
|
||||
|
||||
CustomInstanceDb:
|
||||
type: object
|
||||
required:
|
||||
- logs
|
||||
- success
|
||||
properties:
|
||||
logs:
|
||||
$ref: "#/components/schemas/CustomInstanceDbStatusLogs"
|
||||
$ref: "#/components/schemas/CustomInstanceDbLogs"
|
||||
success:
|
||||
type: boolean
|
||||
description: Whether the operation completed successfully
|
||||
@@ -18215,6 +18229,8 @@ components:
|
||||
nullable: true
|
||||
description: Error message if the operation failed
|
||||
example: "Connection timeout"
|
||||
tag:
|
||||
$ref: "#/components/schemas/CustomInstanceDbTag"
|
||||
|
||||
NewSqsTrigger:
|
||||
type: object
|
||||
|
||||
@@ -70,8 +70,8 @@ pub fn global_service() -> Router {
|
||||
post(acknowledge_critical_alert),
|
||||
)
|
||||
.route(
|
||||
"/get_custom_instance_pg_databases_status",
|
||||
post(get_custom_instance_pg_databases_status),
|
||||
"/get_custom_instance_pg_databases",
|
||||
post(get_custom_instance_pg_databases),
|
||||
)
|
||||
.route(
|
||||
"/setup_custom_instance_pg_database/:name",
|
||||
@@ -583,15 +583,16 @@ pub async fn acknowledge_all_critical_alerts() -> error::Error {
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Debug, Serialize)]
|
||||
struct CustomInstanceDbStatus {
|
||||
logs: CustomInstanceDbStatusLogs, // (Step, Message)[]
|
||||
struct CustomInstanceDb {
|
||||
logs: CustomInstanceDbLogs, // (Step, Message)[]
|
||||
success: bool,
|
||||
error: Option<String>,
|
||||
tag: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Debug, Serialize, Default)]
|
||||
#[serde(default)]
|
||||
struct CustomInstanceDbStatusLogs {
|
||||
struct CustomInstanceDbLogs {
|
||||
super_admin: String,
|
||||
#[serde(skip_serializing_if = "String::is_empty")]
|
||||
database_credentials: String,
|
||||
@@ -605,39 +606,45 @@ struct CustomInstanceDbStatusLogs {
|
||||
grant_permissions: String,
|
||||
}
|
||||
|
||||
async fn get_custom_instance_pg_databases_status(
|
||||
async fn get_custom_instance_pg_databases(
|
||||
_authed: ApiAuthed,
|
||||
Extension(db): Extension<DB>,
|
||||
) -> JsonResult<HashMap<String, CustomInstanceDbStatus>> {
|
||||
) -> JsonResult<HashMap<String, CustomInstanceDb>> {
|
||||
let result = sqlx::query_scalar!(
|
||||
r#"SELECT value->'status' FROM global_settings WHERE name = 'custom_instance_pg_databases'"#,
|
||||
r#"SELECT value->'databases' FROM global_settings WHERE name = 'custom_instance_pg_databases'"#,
|
||||
)
|
||||
.fetch_one(&db)
|
||||
.await?
|
||||
.ok_or_else(|| error::Error::ExecutionErr("Couldn't find custom_instance_pg_databases".to_string()))?;
|
||||
let result = serde_json::from_value(result).map_err(|e| {
|
||||
error::Error::ExecutionErr(format!(
|
||||
"couldn't parse custom_instance_pg_databases.status : {}",
|
||||
"couldn't parse custom_instance_pg_databases.databases : {}",
|
||||
e.to_string()
|
||||
))
|
||||
})?;
|
||||
return Ok(Json(result));
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
struct SetupCustomInstanceDbBody {
|
||||
tag: Option<String>,
|
||||
}
|
||||
|
||||
async fn setup_custom_instance_pg_database(
|
||||
authed: ApiAuthed,
|
||||
Extension(db): Extension<DB>,
|
||||
Path(dbname): Path<String>,
|
||||
) -> JsonResult<CustomInstanceDbStatus> {
|
||||
let mut logs = CustomInstanceDbStatusLogs::default();
|
||||
Json(body): Json<SetupCustomInstanceDbBody>,
|
||||
) -> JsonResult<CustomInstanceDb> {
|
||||
let mut logs = CustomInstanceDbLogs::default();
|
||||
let result = setup_custom_instance_pg_database_inner(authed, &db, &dbname, &mut logs).await;
|
||||
let success = result.is_ok();
|
||||
let error = result.err().map(|e| e.to_string());
|
||||
let status = CustomInstanceDbStatus { logs, success, error };
|
||||
let status = CustomInstanceDb { logs, success, error, tag: body.tag };
|
||||
let status_json = serde_json::to_value(&status).map_err(to_anyhow)?;
|
||||
// Save that the database was setup successfully
|
||||
sqlx::query!(
|
||||
r#"UPDATE global_settings SET value = jsonb_set(value, '{status}', (COALESCE(value->'status', '{}'::jsonb) || to_jsonb($1::json))) WHERE name = 'custom_instance_pg_databases'"#,
|
||||
r#"UPDATE global_settings SET value = jsonb_set(value, '{databases}', (COALESCE(value->'databases', '{}'::jsonb) || to_jsonb($1::json))) WHERE name = 'custom_instance_pg_databases'"#,
|
||||
json!({ dbname: status_json })
|
||||
).execute(&db).await?;
|
||||
|
||||
@@ -648,7 +655,7 @@ async fn setup_custom_instance_pg_database_inner(
|
||||
authed: ApiAuthed,
|
||||
db: &DB,
|
||||
dbname: &str,
|
||||
logs: &mut CustomInstanceDbStatusLogs,
|
||||
logs: &mut CustomInstanceDbLogs,
|
||||
) -> Result<()> {
|
||||
require_super_admin(db, &authed.email).await?;
|
||||
logs.super_admin = "OK".to_string();
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<script lang="ts">
|
||||
import type { GetCustomInstanceDbStatusResponse } from '$lib/gen'
|
||||
import type { CustomInstanceDbTag, GetCustomInstanceDbsResponse } from '$lib/gen'
|
||||
import type { ResourceReturn } from 'runed'
|
||||
import Select from '../select/Select.svelte'
|
||||
import { safeSelectItems } from '../select/utils.svelte'
|
||||
@@ -13,24 +13,26 @@
|
||||
|
||||
type Props = {
|
||||
value: string | undefined
|
||||
customInstanceDbStatuses: ResourceReturn<GetCustomInstanceDbStatusResponse>
|
||||
customInstanceDbs: ResourceReturn<GetCustomInstanceDbsResponse>
|
||||
confirmationModal: ConfirmationModalHandle
|
||||
dbManagerDrawer: DBManagerDrawer | undefined
|
||||
wizardBottomHint?: Snippet | undefined
|
||||
class?: string
|
||||
tag?: CustomInstanceDbTag
|
||||
}
|
||||
let {
|
||||
value = $bindable(),
|
||||
customInstanceDbStatuses,
|
||||
customInstanceDbs,
|
||||
confirmationModal,
|
||||
dbManagerDrawer,
|
||||
wizardBottomHint,
|
||||
class: className
|
||||
class: className,
|
||||
tag
|
||||
}: Props = $props()
|
||||
|
||||
let openedDbNameWizard = $state(false)
|
||||
|
||||
let status = $derived(customInstanceDbStatuses.current?.[value ?? ''])
|
||||
let status = $derived(customInstanceDbs.current?.[value ?? ''])
|
||||
</script>
|
||||
|
||||
<div class="flex relative items-center {className}">
|
||||
@@ -40,7 +42,7 @@
|
||||
bind:value
|
||||
onCreateItem={(i) => (value = i)}
|
||||
placeholder="PostgreSQL database name"
|
||||
items={safeSelectItems(Object.keys(customInstanceDbStatuses.current ?? {}))}
|
||||
items={safeSelectItems(Object.keys(customInstanceDbs.current ?? {}))}
|
||||
disabled={!$isCustomInstanceDbEnabled}
|
||||
/>
|
||||
|
||||
@@ -65,14 +67,15 @@
|
||||
</div>
|
||||
|
||||
<CustomInstanceDbWizardModal
|
||||
{customInstanceDbStatuses}
|
||||
{customInstanceDbs}
|
||||
{confirmationModal}
|
||||
{dbManagerDrawer}
|
||||
{tag}
|
||||
bottomHint={wizardBottomHint}
|
||||
bind:opened={
|
||||
() =>
|
||||
openedDbNameWizard
|
||||
? { dbname: value ?? '', status: customInstanceDbStatuses.current?.[value ?? ''] }
|
||||
? { dbname: value ?? '', status: customInstanceDbs.current?.[value ?? ''] }
|
||||
: undefined,
|
||||
(v) => !v && (openedDbNameWizard = false)
|
||||
}
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
<script lang="ts">
|
||||
import {
|
||||
SettingService,
|
||||
type CustomInstanceDbStatus,
|
||||
type GetCustomInstanceDbStatusResponse
|
||||
type CustomInstanceDb,
|
||||
type CustomInstanceDbTag,
|
||||
type GetCustomInstanceDbsResponse
|
||||
} from '$lib/gen'
|
||||
import { slide } from 'svelte/transition'
|
||||
import Modal2 from '../common/modal/Modal2.svelte'
|
||||
@@ -20,19 +21,21 @@
|
||||
import type { Snippet } from 'svelte'
|
||||
|
||||
type Props = {
|
||||
customInstanceDbStatuses: ResourceReturn<GetCustomInstanceDbStatusResponse>
|
||||
customInstanceDbs: ResourceReturn<GetCustomInstanceDbsResponse>
|
||||
confirmationModal: ConfirmationModalHandle
|
||||
dbManagerDrawer: DBManagerDrawer | undefined
|
||||
bottomHint?: Snippet | undefined
|
||||
opened: { status: CustomInstanceDbStatus | undefined; dbname: string } | undefined
|
||||
opened: { status: CustomInstanceDb | undefined; dbname: string } | undefined
|
||||
tag?: CustomInstanceDbTag
|
||||
}
|
||||
|
||||
let {
|
||||
customInstanceDbStatuses,
|
||||
customInstanceDbs,
|
||||
confirmationModal,
|
||||
dbManagerDrawer,
|
||||
bottomHint,
|
||||
opened = $bindable()
|
||||
opened = $bindable(),
|
||||
tag
|
||||
}: Props = $props()
|
||||
|
||||
let customInstanceDbSetupIsRunning = $state(false)
|
||||
@@ -154,8 +157,11 @@
|
||||
|
||||
try {
|
||||
customInstanceDbSetupIsRunning = true
|
||||
let result = await SettingService.setupCustomInstanceDb({ name: dbname })
|
||||
await customInstanceDbStatuses.refetch()
|
||||
let result = await SettingService.setupCustomInstanceDb({
|
||||
name: dbname,
|
||||
requestBody: { tag }
|
||||
})
|
||||
await customInstanceDbs.refetch()
|
||||
if (result.success) {
|
||||
if (!wasAlreadySuccessful) sendUserToast('Setup successful')
|
||||
else sendUserToast('Check successful')
|
||||
|
||||
@@ -97,7 +97,8 @@
|
||||
}
|
||||
})
|
||||
}
|
||||
const instanceCatalogStatuses = resource([], SettingService.getCustomInstanceDbStatus)
|
||||
|
||||
const instanceDbs = resource([], SettingService.getCustomInstanceDbs)
|
||||
|
||||
async function onSave() {
|
||||
try {
|
||||
@@ -106,7 +107,7 @@
|
||||
dataTableSettings.dataTables.some(
|
||||
(d) =>
|
||||
d.database.resource_type === 'instance' &&
|
||||
!instanceCatalogStatuses.current?.[d.database.resource_path ?? '']?.success
|
||||
!instanceDbs.current?.[d.database.resource_path ?? '']?.success
|
||||
)
|
||||
) {
|
||||
let confirm = await confirmationModal.ask({
|
||||
|
||||
@@ -72,7 +72,7 @@
|
||||
import Tooltip from '../Tooltip.svelte'
|
||||
import ConfirmationModal from '../common/confirmationModal/ConfirmationModal.svelte'
|
||||
import { createAsyncConfirmationModal } from '../common/confirmationModal/asyncConfirmationModal.svelte'
|
||||
import { clone } from '$lib/utils'
|
||||
import { clone, filterObject } from '$lib/utils'
|
||||
import Alert from '../common/alert/Alert.svelte'
|
||||
import { deepEqual } from 'fast-equals'
|
||||
import Popover from '../meltComponents/Popover.svelte'
|
||||
@@ -125,7 +125,9 @@
|
||||
)
|
||||
)
|
||||
|
||||
const customInstanceDbStatuses = resource([], SettingService.getCustomInstanceDbStatus)
|
||||
const customInstanceDbs = resource([], async () =>
|
||||
filterObject(await SettingService.getCustomInstanceDbs(), (_, v) => v.tag === 'ducklake')
|
||||
)
|
||||
|
||||
async function onSave() {
|
||||
try {
|
||||
@@ -134,7 +136,7 @@
|
||||
ducklakeSettings.ducklakes.some(
|
||||
(d) =>
|
||||
d.catalog.resource_type === 'instance' &&
|
||||
!customInstanceDbStatuses.current?.[d.catalog.resource_path ?? '']?.success
|
||||
!customInstanceDbs.current?.[d.catalog.resource_path ?? '']?.success
|
||||
)
|
||||
) {
|
||||
let confirm = await confirmationModal.ask({
|
||||
@@ -276,9 +278,10 @@
|
||||
<CustomInstanceDbSelect
|
||||
class="flex-1"
|
||||
bind:value={ducklake.catalog.resource_path}
|
||||
{customInstanceDbStatuses}
|
||||
{customInstanceDbs}
|
||||
{confirmationModal}
|
||||
{dbManagerDrawer}
|
||||
tag="ducklake"
|
||||
>
|
||||
{#snippet wizardBottomHint()}
|
||||
Note: the 'Manage' button below is different from the Manage Ducklake button.
|
||||
|
||||
@@ -1655,3 +1655,12 @@ export function getCssColor(
|
||||
}
|
||||
|
||||
export type IconType = Component<{ size?: number }> | typeof import('lucide-svelte').Dot
|
||||
|
||||
export function filterObject<Key extends string, Value>(
|
||||
obj: Record<Key, Value>,
|
||||
f: (k: Key, v: Value) => boolean
|
||||
): Record<Key, Value> {
|
||||
return Object.fromEntries(
|
||||
Object.entries(obj).filter(([k, v]) => f(k as Key, v as Value))
|
||||
) as Record<Key, Value>
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user