diff --git a/packages/hydrooj/src/handler/domain.ts b/packages/hydrooj/src/handler/domain.ts index 811592e59..2c494b7b4 100644 --- a/packages/hydrooj/src/handler/domain.ts +++ b/packages/hydrooj/src/handler/domain.ts @@ -173,11 +173,14 @@ class DomainUserHandler extends ManageHandler { @requireSudo @param('uids', Types.NumericArray) @param('role', Types.Role) - async postSetUsers(domainId: string, uid: number[], role: string) { + @param('join', Types.Boolean) + async postSetUsers(domainId: string, uid: number[], role: string, join = false) { + if (join) this.checkPriv(PRIV.PRIV_MANAGE_ALL_DOMAIN); await Promise.all([ domain.setUserRole(domainId, uid, role), - oplog.log(this, 'domain.setRole', { uid, role }), + oplog.log(this, 'domain.setRole', { uid, role, join }), ]); + if (join) await domain.setJoin(domainId, uid, true); this.back(); } diff --git a/packages/ui-default/components/dialog/index.tsx b/packages/ui-default/components/dialog/index.tsx index b5b58866f..8cf11aeb8 100644 --- a/packages/ui-default/components/dialog/index.tsx +++ b/packages/ui-default/components/dialog/index.tsx @@ -2,6 +2,7 @@ import $ from 'jquery'; import React from 'react'; import ReactDOM from 'react-dom/client'; +import Notification from 'vj/components/notification'; import { i18n, tpl } from 'vj/utils'; import DomainSelectAutoComplete from '../autocomplete/components/DomainSelectAutoComplete'; import UserSelectAutoComplete from '../autocomplete/components/UserSelectAutoComplete'; @@ -114,11 +115,13 @@ export class ConfirmDialog extends Dialog { export interface Field { type: 'text' | 'checkbox' | 'user' | 'userId' | 'username' | 'domain'; + options?: string[] | Record; placeholder?: string; label?: string; autofocus?: boolean; required?: boolean; default?: string; + columns?: number; } type Result> = { @@ -132,7 +135,25 @@ type Result> = { export async function prompt>(title: string, fields: R): Promise> { let valueCache: Result = {} as any; const defaultValues = Object.fromEntries(Object.entries(fields) - .map(([name, field]: [T, Field]) => [name, field.default || ''])) as Result; + .map(([name, field]: [T, Field]) => { + let firstOption = ''; + if (field.options) { + if (Array.isArray(field.options)) firstOption = field.options[0]; + else firstOption = Object.keys(field.options)[0]; + } + return [name, field.default || firstOption || '']; + })) as Result; + + const layout: [string, Field][][] = []; + let pending: [string, Field][] = []; + for (const [name, field] of Object.entries(fields) as [T, Field][]) { + pending.push([name, field]); + if ((field.columns || -12) < 0) { + layout.push(pending); + pending = []; + } + } + if (pending.length > 0) layout.push(pending); const Component = () => { const [values, setValues] = React.useState(defaultValues); @@ -147,35 +168,44 @@ export async function prompt>(title

{title}

- {Object.entries(fields).map(([name, field]: [string, Field]) =>
-
+ {layout.map((i) =>
+ {i.map(([name, field]: [string, Field]) =>
{['text', 'user', 'userId', 'username', 'domain'].includes(field.type) && } {field.type === 'checkbox' && } -
-
)} +
)}
)} ; }; const div = document.createElement('div'); @@ -198,8 +227,12 @@ export async function prompt>(title $action: [buttonCancel, buttonOk].join('\n'), onDispatch(action) { if (action === 'ok') { - for (const [name, field] of Object.entries(fields)) { - if ((field as any).required && !valueCache[name]) return false; + for (const [name, field] of Object.entries(fields) as [string, Field][]) { + if ((field as any).required && !valueCache[name]) { + console.log('missing ', name); + Notification.error(i18n('{0} is required', field.label || name)); + return false; + } } } return true; diff --git a/packages/ui-default/pages/domain_user.page.js b/packages/ui-default/pages/domain_user.page.js index 4ddd94654..a5db146a4 100644 --- a/packages/ui-default/pages/domain_user.page.js +++ b/packages/ui-default/pages/domain_user.page.js @@ -1,7 +1,6 @@ import $ from 'jquery'; import _ from 'lodash'; -import UserSelectAutoComplete from 'vj/components/autocomplete/UserSelectAutoComplete'; -import { ActionDialog, confirm, InfoDialog } from 'vj/components/dialog'; +import { confirm, InfoDialog, prompt } from 'vj/components/dialog'; import Notification from 'vj/components/notification'; import { NamedPage } from 'vj/misc/Page'; import { @@ -23,60 +22,36 @@ const page = new NamedPage('domain_user', () => { }).open(); }); - const addUserSelector = UserSelectAutoComplete.getOrConstruct($('.dialog__body--add-user [name="user"]')); - const addUserDialog = new ActionDialog({ - $body: $('.dialog__body--add-user > div'), - onDispatch(action) { - const $role = addUserDialog.$dom.find('[name="role"]'); - if (action === 'ok') { - if (addUserSelector.value() === null) { - addUserSelector.focus(); - return false; - } - if (!$role.val()) { - $role.focus(); - return false; - } - } - return true; - }, - }); - const firstRoleForAddUser = addUserDialog.$dom.find('[name="role"] option').first().val(); - addUserDialog.clear = function () { - addUserSelector.clear(); - this.$dom.find('[name="role"]').val(firstRoleForAddUser); - return this; - }; - - const setRolesDialog = new ActionDialog({ - $body: $('.dialog__body--set-role > div'), - onDispatch(action) { - const $role = setRolesDialog.$dom.find('[name="role"]'); - if (action === 'ok' && !$role.val()) { - $role.focus(); - return false; - } - return true; - }, - }); - const firstRoleForSetRole = setRolesDialog.$dom.find('[name="role"] option').first().val(); - setRolesDialog.clear = function () { - this.$dom.find('[name="role"]').val(firstRoleForSetRole); - return this; - }; - async function handleClickAddUser() { - const action = await addUserDialog.clear().open(); - if (action !== 'ok') { - return; - } - const user = addUserSelector.value(); - const role = addUserDialog.$dom.find('[name="role"]').val(); + const res = await prompt('Add User', { + user: { + type: 'userId', + required: true, + autofocus: true, + label: 'Username / UID', + columns: 6, + }, + role: { + type: 'text', + required: true, + label: 'Role', + options: UiContext.roles.filter((i) => !['default', 'guest'].includes(i)), + columns: -6, + }, + ...(UiContext.canForceJoin ? { + join: { + type: 'checkbox', + label: 'Mark user as joined using admin privilege', + }, + } : {}), + }); + if (!res?.user || !res?.role) return; try { await request.post('', { operation: 'set_users', - uids: [user._id], - role, + uids: [res.user], + role: res.role, + join: res.join, }); window.location.reload(); } catch (error) { @@ -98,9 +73,7 @@ const page = new NamedPage('domain_user', () => { async function handleClickRemoveSelected() { const selectedUsers = ensureAndGetSelectedUsers(); - if (selectedUsers === null) { - return; - } + if (selectedUsers === null) return; if (!(await confirm(`${i18n('Confirm removing the selected users?')} ${i18n('Their account will not be deleted and they will be with the guest role until they re-join the domain.')}`))) return; try { @@ -117,22 +90,24 @@ ${i18n('Their account will not be deleted and they will be with the guest role u } async function handleClickSetSelected() { + const res = await prompt('Set Role', { + role: { + type: 'text', + required: true, + label: 'Set Roles for selected users', + options: UiContext.roles.filter((i) => !['guest'].includes(i)), + }, + }); + if (!res?.role) return; const selectedUsers = ensureAndGetSelectedUsers(); - if (selectedUsers === null) { - return; - } - const action = await setRolesDialog.clear().open(); - if (action !== 'ok') { - return; - } - const role = setRolesDialog.$dom.find('[name="role"]').val(); + if (selectedUsers === null) return; try { await request.post('', { operation: 'set_users', uids: selectedUsers, - role, + role: res.role, }); - Notification.success(i18n('Role has been updated to {0} for selected users.', role)); + Notification.success(i18n('Role has been updated to {0} for selected users.', res.role)); await delay(2000); window.location.reload(); } catch (error) { diff --git a/packages/ui-default/templates/domain_user.html b/packages/ui-default/templates/domain_user.html index e45d804d0..450f73a92 100644 --- a/packages/ui-default/templates/domain_user.html +++ b/packages/ui-default/templates/domain_user.html @@ -1,51 +1,8 @@ {% extends "domain_base.html" %} {% block domain_content %} +{{ set(UiContext, 'roles', roles.map(eval('(i) => i._id'))) }} +{{ set(UiContext, 'canForceJoin', handler.user.hasPriv(PRIV.PRIV_MANAGE_ALL_DOMAIN)) }} {% set _rolesSelect = roles.filter(eval("(i) => i._id !== 'guest'")).map(eval('(role) => [role._id, role._id]')) %} -{% set _rolesSelectWithoutDefault = _rolesSelect.filter(eval("(i) => i[0] !== 'default'")) %} - -

{{ _('{0}: Users').format(domain.name) }}