feat: add list instance emails endpoint and use Select in AddUser

Add a new GET /users/list_instance_emails endpoint that returns all
instance user emails (from the password table). This endpoint is only
available on non-cloud instances.

In the frontend AddUser component, when the instance emails are
available (non-cloud), replace the email text input with a Select
component that allows both selecting from existing instance users and
typing custom emails.

Closes #7902

Co-authored-by: windmill-internal-app[bot] <windmill-internal-app[bot]@users.noreply.github.com>
This commit is contained in:
claude[bot]
2026-02-11 11:24:13 +00:00
parent e26e437dd1
commit 077eb91c0f
4 changed files with 88 additions and 1 deletions

View File

@@ -0,0 +1,20 @@
{
"db_name": "PostgreSQL",
"query": "SELECT email FROM password ORDER BY email",
"describe": {
"columns": [
{
"ordinal": 0,
"name": "email",
"type_info": "Varchar"
}
],
"parameters": {
"Left": []
},
"nullable": [
false
]
},
"hash": "21e9a9fc33dcdb66a2c8e95b1359ca1b3e04b4ecf155757abf5798834ebfd0ce"
}

View File

@@ -86,6 +86,10 @@ pub fn global_service() -> Router {
.route("/decline_invite", post(decline_invite))
.route("/accept_invite", post(accept_invite))
.route("/list_as_super_admin", get(list_users_as_super_admin))
.route(
"/list_instance_emails",
get(list_instance_emails),
)
.route("/set_login_type/:user", post(set_login_type))
.route("/update/:user", post(update_user))
.route("/delete/:user", delete(delete_user))
@@ -420,6 +424,21 @@ async fn list_users_as_super_admin(
Ok(Json(rows))
}
async fn list_instance_emails(
_authed: ApiAuthed,
Extension(db): Extension<DB>,
) -> JsonResult<Vec<String>> {
if *CLOUD_HOSTED {
return Err(Error::BadRequest(
"This endpoint is not available on cloud hosted instances".to_string(),
));
}
let rows = sqlx::query_scalar!("SELECT email FROM password ORDER BY email")
.fetch_all(&db)
.await?;
Ok(Json(rows))
}
#[derive(Serialize, Deserialize)]
struct Progress {
progress: u64,

View File

@@ -2306,6 +2306,22 @@ paths:
items:
$ref: "#/components/schemas/GlobalUserInfo"
/users/list_instance_emails:
get:
summary: list all instance user emails (only on non-cloud instances)
operationId: listInstanceEmails
tags:
- user
responses:
"200":
description: list of instance user emails
content:
application/json:
schema:
type: array
items:
type: string
/w/{workspace}/workspaces/list_pending_invites:
get:
summary: list pending invites for a workspace

View File

@@ -10,6 +10,7 @@
import ToggleButtonGroup from './common/toggleButton-v2/ToggleButtonGroup.svelte'
import ToggleButton from './common/toggleButton-v2/ToggleButton.svelte'
import { UserPlus } from 'lucide-svelte'
import Select from './select/Select.svelte'
const dispatch = createEventDispatcher()
@@ -31,6 +32,19 @@
}
getAutomateUsernameCreationSetting()
let instanceEmails: { label: string; value: string }[] | undefined = $state(undefined)
async function loadInstanceEmails() {
if (isCloudHosted()) return
try {
const emails = await UserService.listInstanceEmails()
instanceEmails = emails.map((e) => ({ label: e, value: e }))
} catch {
instanceEmails = undefined
}
}
loadInstanceEmails()
async function addUser() {
await WorkspaceService.addUser({
workspace: $workspaceStore!,
@@ -84,7 +98,25 @@
<span class="text-sm mb-2 leading-6 font-semibold">Add a new user</span>
<span class="text-xs mb-1 leading-6">Email</span>
<input type="email mb-1" onkeyup={handleKeyUp} placeholder="email" bind:value={email} />
{#if instanceEmails}
<Select
items={instanceEmails}
bind:value={email}
placeholder="Select or type an email"
createText="Use custom email:"
onCreateItem={(v) => {
email = v
}}
disablePortal={true}
/>
{:else}
<input
type="email"
onkeyup={handleKeyUp}
placeholder="email"
bind:value={email}
/>
{/if}
{#if !automateUsernameCreation}
<span class="text-xs mb-1 pt-2 leading-6">Username</span>