Add OIDC sign-in, group-derived roles and declarative instance configuration
Workspaces, group mappings and sign-in methods now come from a configuration file synchronized at startup; workspaces dropped from it are disabled, never deleted. Instance administrators (a new role) see and help every workspace and may delete disabled ones; workspace administrators and employees are derived from the user's groups at every SSO sign-in or administrator edit, and the employee permission set belongs to the workspace. Self-registration is gone: users come from SSO (only when their groups map) or from the administrator. Break-glass bootstrap accounts get an HMAC-derived hourly password that all replicas agree on. First SSO sign-in links a matching email contact to the user. The demo runs on the same model with a generated per-start password. Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
1 parent
e37d583179
commit
4764f3545f
68 files changed
+7220
-964
No files matched your search
+171
-43
@@ -41,22 +41,31 @@
|
||||
</div>
|
||||
<span class="quiet"><span x-text="t('plan_here_stay_in_sync_everywhere_70e528')"></span></span>
|
||||
</div>
|
||||
<form class="login-form" @submit.prevent="signIn">
|
||||
<div class="login-form">
|
||||
<span class="eyebrow"><span x-text="t('your_team_s_home_for_on_call_66a895')"></span></span>
|
||||
<h2 x-text="registering ? t('create_your_account_4046b5') : t('welcome_back_b80783')"></h2>
|
||||
<h2 x-text="t('welcome_back_b80783')"></h2>
|
||||
<p class="muted"><span x-text="t('make_space_for_a_smoother_handover_44adaf')"></span></p>
|
||||
<div class="login-error" role="alert" x-cloak x-show="loginError"><span x-text="loginError ? t('access_login_error_'+loginError.code) : ''"></span><small x-show="loginError?.ref" x-text="loginError?.ref ? t('access_support_reference',{ref:loginError.ref}) : ''"></small></div>
|
||||
<a class="button primary sso-button" href="/api/oidc/login" x-cloak x-show="auth.oidc"><span x-text="t('access_sso_button')"></span></a>
|
||||
<div class="login-divider" x-cloak x-show="auth.oidc && auth.password"><span x-text="t('access_or_divider')"></span></div>
|
||||
<form class="password-login" @submit.prevent="signIn" x-cloak x-show="auth.password">
|
||||
<label><span x-text="t('username_84c290')"></span><input autocomplete="username" x-model="loginForm.username" required maxlength="64">
|
||||
</label>
|
||||
<label x-show="registering"><span x-text="t('display_name_c7874a')"></span><input x-model="loginForm.display_name" :required="registering">
|
||||
<label><span x-text="t('password_8be3c9')"></span><input type="password" autocomplete="current-password" x-model="loginForm.password" required>
|
||||
</label>
|
||||
<label><span x-text="t('password_8be3c9')"></span><input type="password" :autocomplete="registering ? 'new-password' : 'current-password'" x-model="loginForm.password" required :minlength="registering ? 12 : 1">
|
||||
</label>
|
||||
<button class="primary" :disabled="busy" x-text="registering ? t('create_account_aaf374') : t('sign_in_ada2e9')">
|
||||
<button class="primary" :disabled="busy" x-text="t('sign_in_ada2e9')">
|
||||
</button>
|
||||
<button type="button" class="text" x-show="allowSignup" @click="registering=!registering" x-text="registering ? t('already_registered_sign_in_0df05c') : t('new_here_create_an_account_2b2c3f')">
|
||||
</button>
|
||||
<div class="demo-note" x-show="demo && !registering"><strong><span x-text="t('try_the_demo_38cfaf')"></span></strong><dl><div><dt><span x-text="t('alice_522b27')"></span></dt><dd><span x-text="t('lead_both_workspaces_variables_rules_ed9a19')"></span></dd></div><div><dt><span x-text="t('bob_48181a')"></span></dt><dd><span x-text="t('engineering_weekly_on_call_swaps_de49e8')"></span></dd></div><div><dt><span x-text="t('carol_28b92b')"></span></dt><dd><span x-text="t('support_daily_shifts_approvals_c9a0d8')"></span></dd></div><div><dt x-text="t('demo_extra_users')"></dt><dd x-text="t('demo_extra_users_hint')"></dd></div></dl><span><span x-text="t('password_for_all_2f1a67')"></span> <code><span x-text="t('meguri_demo_only_0c7b5d')"></span></code></span></div>
|
||||
</form>
|
||||
<p class="info-note" x-cloak x-show="!auth.password && !auth.oidc"><span x-text="t('access_no_sign_in_method')"></span></p>
|
||||
<div class="demo-note" x-cloak x-show="demo && demoLifecycle?.accounts?.length"><strong><span x-text="t('access_demo_accounts')"></span></strong>
|
||||
<ul class="demo-accounts">
|
||||
<template x-for="account in demoLifecycle?.accounts || []" :key="account.username">
|
||||
<li><button type="button" class="text demo-account" :data-demo-account="account.username" :aria-label="t('access_demo_use_account',{username:account.username})" @click="useDemoAccount(account)"><code x-text="account.username"></code><span x-text="t('access_role_'+account.role)"></span></button></li>
|
||||
</template>
|
||||
</ul>
|
||||
<span class="demo-password"><span x-text="t('access_demo_password')"></span> <code data-demo-password x-text="demoLifecycle?.password"></code> <button type="button" class="text" @click="copyDemoPassword()"><span x-text="t('access_demo_copy_password')"></span></button></span>
|
||||
<small x-text="t('access_demo_restart_hint')"></small></div>
|
||||
</div>
|
||||
</main>
|
||||
<div class="shell" :inert="!!modal" x-cloak x-show="ready && user">
|
||||
<aside class="sidebar">
|
||||
@@ -83,6 +92,7 @@
|
||||
</template>
|
||||
<button class="setup-toggle" :aria-label="t('setup_cdd7bb')" :class="{active:setupView}" @click="setupOpen=!setupOpen" :aria-expanded="setupOpen"><span class="nav-icon"><span x-text="t('symbol_be22a3')"></span></span><span><span x-text="t('setup_cdd7bb')"></span></span><span class="grow"></span><span x-text="setupOpen?'−':'+'"></span></button>
|
||||
<div class="setup-nav" x-show="setupOpen"><template x-for="item in nav.filter(n=>['configuration','settings'].includes(n.id))" :key="item.id"><a :href="menuHref(item.id)" :aria-current="view===item.id?'page':null" :class="{active:view===item.id}" @click="menuClick($event,item.id)" x-text="item.name"></a></template></div>
|
||||
<a class="instance-link" x-cloak x-show="user?.instance_admin" :href="menuHref('instance')" :aria-current="view==='instance'?'page':null" :class="{active:view==='instance'}" @click="menuClick($event,'instance')"><span aria-hidden="true" class="nav-icon" x-text="nav.find(n=>n.id==='instance').icon"></span><span x-text="nav.find(n=>n.id==='instance').name"></span></a>
|
||||
</nav>
|
||||
<div class="sidebar-bottom">
|
||||
|
||||
@@ -115,21 +125,79 @@
|
||||
</span>
|
||||
<div class="top-actions">
|
||||
<span class="display-zone" x-text="t('your_time_31bdbf')+displayZone"></span>
|
||||
<button class="text" @click="open('workspace')"><span x-text="t('new_workspace_b21b86')"></span></button>
|
||||
|
||||
</div>
|
||||
</header>
|
||||
<div class="content">
|
||||
<template x-if="!workspace">
|
||||
<section class="empty welcome">
|
||||
<span class="empty-symbol"><span x-text="t('symbol_4d82f1')"></span></span>
|
||||
<h1><span x-text="t('your_team_s_next_chapter_starts_here_4e986d')"></span></h1>
|
||||
<p><span x-text="t('create_a_workspace_add_a_team_and_give_your_on_call__af9412')"></span></p>
|
||||
<button class="primary" @click="open('workspace')" x-text="t('create_a_workspace_e94c64')">
|
||||
</button>
|
||||
<template x-if="view==='instance' && user?.instance_admin">
|
||||
<section class="instance-page" id="instance">
|
||||
<div class="page-heading"><div><div class="eyebrow" x-text="t('access_instance_eyebrow')"></div><h1 x-text="t('access_instance_title')"></h1></div></div>
|
||||
<div class="warning-banner" role="alert" x-show="admin?.config.bootstrap_usernames.length"><span x-text="t('access_bootstrap_warning')"></span></div>
|
||||
<template x-if="admin">
|
||||
<div>
|
||||
<article class="panel rule-card spaced" id="instance-config">
|
||||
<span class="eyebrow"><span x-text="t('access_config_title')"></span></span>
|
||||
<dl class="config-summary">
|
||||
<div><dt x-text="t('access_config_password_login')"></dt><dd x-text="admin.config.password_login?t('access_enabled'):t('access_disabled')"></dd></div>
|
||||
<div><dt x-text="t('access_config_oidc')"></dt><dd x-text="admin.config.oidc_issuer || t('access_not_configured')"></dd></div>
|
||||
<div><dt x-text="t('access_config_admin_groups')"></dt><dd x-text="admin.config.instance_admin_groups.join(', ') || t('access_none')"></dd></div>
|
||||
<div><dt x-text="t('access_config_bootstrap')"></dt><dd x-text="admin.config.bootstrap_usernames.join(', ') || t('access_none')"></dd></div>
|
||||
</dl>
|
||||
<p class="muted"><span x-text="t('access_config_note')"></span></p>
|
||||
</article>
|
||||
<div class="section-line"><h2><span x-text="t('access_workspaces_title')"></span></h2></div>
|
||||
<div class="panel compact" id="instance-workspaces">
|
||||
<p class="muted padded" x-show="!admin.workspaces.length"><span x-text="t('access_workspaces_empty')"></span></p>
|
||||
<template x-for="w in admin.workspaces" :key="w.id">
|
||||
<div class="list-row admin-workspace" :class="{disabled:w.disabled_at}" :data-workspace-key="w.key">
|
||||
<div class="grow">
|
||||
<strong x-text="w.name"></strong>
|
||||
<small x-text="t('access_workspace_key',{key:w.key || '—'})+' · '+t('access_workspace_members',{count:w.member_count})+(w.sync_state?' · '+t('access_workspace_publication',{state:w.sync_state}):'')"></small>
|
||||
<small x-show="w.in_config"><span x-text="t('access_groups_admin')+': '+(w.admin_groups.join(', ') || t('access_none'))"></span></small>
|
||||
<small x-show="w.in_config"><span x-text="t('access_groups_member')+': '+(w.member_groups.join(', ') || t('access_none'))"></span></small>
|
||||
<small class="disabled-note" x-show="w.disabled_at" x-text="t('access_workspace_disabled')+' · '+t('access_workspace_disabled_since',{date:formatTime(w.disabled_at)})"></small>
|
||||
</div>
|
||||
<span class="badge" x-show="w.in_config" x-text="t('access_workspace_in_config')"></span>
|
||||
<button class="danger-button" x-show="w.disabled_at" @click="open('admin-delete',{id:w.id,name:w.name,expected:w.confirm,confirm:''})"><span x-text="t('access_delete_workspace')"></span></button>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
<div class="section-line"><h2><span x-text="t('access_users_title')"></span></h2>
|
||||
<button class="primary" @click="open('admin-user')"><span x-text="t('access_add_user')"></span></button>
|
||||
</div>
|
||||
<div class="panel compact" id="instance-users">
|
||||
<p class="muted padded" x-show="!admin.users.length"><span x-text="t('access_users_empty')"></span></p>
|
||||
<template x-for="u in admin.users" :key="u.id">
|
||||
<div class="list-row admin-user" :data-username="u.username">
|
||||
<span class="avatar" x-text="u.display_name.slice(0,1)"></span>
|
||||
<div class="grow">
|
||||
<strong x-text="u.display_name"></strong> <small class="inline" x-show="u.id===user.id"><span x-text="t('you_8af56d')"></span></small>
|
||||
<small x-text="'@'+u.username+(u.email?' · '+u.email:'')"></small>
|
||||
<small x-text="t('access_user_groups')+': '+(u.groups.join(', ') || t('access_user_no_groups'))"></small>
|
||||
<small class="muted" x-show="u.sso"><span x-text="t('access_groups_sso_readonly')"></span></small>
|
||||
<small class="muted" x-show="u.bootstrap"><span x-text="t('access_groups_bootstrap_readonly')"></span></small>
|
||||
</div>
|
||||
<span class="badge" x-show="u.instance_admin" x-text="t('access_role_instance_admin')"></span>
|
||||
<span class="badge" x-show="u.sso" x-text="t('access_badge_sso')"></span>
|
||||
<span class="badge" x-show="u.bootstrap" x-text="t('access_badge_bootstrap')"></span>
|
||||
<span class="badge" x-show="!u.sso && !u.bootstrap" x-text="t('access_badge_password')"></span>
|
||||
<button x-show="canEditUserGroups(u)" @click="open('admin-groups',{id:u.id,username:u.username,groups:[...u.groups]})"><span x-text="t('access_edit_groups')"></span></button>
|
||||
<button x-show="!u.sso && !u.bootstrap" @click="open('admin-password',{id:u.id,username:u.username,password:''})"><span x-text="t('access_reset_password')"></span></button>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</section>
|
||||
</template>
|
||||
<template x-if="workspace">
|
||||
<template x-if="view!=='instance' && !workspace">
|
||||
<section class="empty welcome">
|
||||
<span class="empty-symbol"><span x-text="t('symbol_4d82f1')"></span></span>
|
||||
<h1><span x-text="t('access_no_workspace_title')"></span></h1>
|
||||
<p x-text="user?.instance_admin?t('access_no_workspace_admin_text'):t('access_no_workspace_text')"></p>
|
||||
<a class="button primary" x-show="user?.instance_admin" :href="menuHref('instance')" @click="menuClick($event,'instance')"><span x-text="t('access_open_instance_page')"></span></a>
|
||||
</section>
|
||||
</template>
|
||||
<template x-if="view!=='instance' && workspace">
|
||||
<div>
|
||||
<div class="page-heading">
|
||||
<div>
|
||||
@@ -340,23 +408,43 @@
|
||||
</div>
|
||||
<div class="section-line">
|
||||
<h2><span x-text="t('team_access_8fb357')"></span></h2>
|
||||
<button class="primary" x-show="can('manage_members')" @click="open('member')"><span x-text="t('add_teammate_d6bc5a')"></span></button>
|
||||
</div>
|
||||
<div class="panel compact">
|
||||
<p class="info-note" x-show="detail?.instance_admin && !members.some(m=>m.user.id===user?.id)"><span x-text="t('access_instance_admin_note')"></span></p>
|
||||
<div class="panel compact" id="workspace-members">
|
||||
<template x-for="m in members" :key="m.user?.id">
|
||||
<div class="list-row">
|
||||
<div class="list-row" :data-username="m.user.username">
|
||||
<span class="avatar" :class="color(m.user?.id)" x-text="initials(m.user?.id)">
|
||||
</span>
|
||||
<div class="grow">
|
||||
<strong x-text="m.user.display_name">
|
||||
</strong>
|
||||
<small x-text="'@'+m.user.username+' · '+m.permissions.length+t('permissions_607d39')">
|
||||
<small x-text="'@'+m.user.username">
|
||||
</small>
|
||||
</div>
|
||||
<button x-show="can('manage_members')" @click="open('member',{username:m.user.username,permissions:m.permissions})"><span x-text="t('edit_access_e381a8')"></span></button>
|
||||
<span class="badge role-badge" :class="{admin:m.is_owner}" x-text="m.is_owner?t('access_role_workspace_admin'):t('access_role_employee')"></span>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
<p class="muted"><span x-text="t('access_members_hint')"></span></p>
|
||||
<article class="panel rule-card spaced" id="employee-permissions">
|
||||
<span class="eyebrow"><span x-text="t('access_employee_permissions_title')"></span></span>
|
||||
<p class="muted"><span x-text="t('access_employee_permissions_hint')"></span></p>
|
||||
<ul class="permission-list">
|
||||
<template x-for="grant in employeeGrantOptions" :key="grant.id">
|
||||
<li :class="{on:(doc.member_permissions||[]).includes(grant.id)}" :data-permission="grant.id"><span aria-hidden="true" x-text="(doc.member_permissions||[]).includes(grant.id)?'✓':'·'"></span> <span x-text="grant.name"></span></li>
|
||||
</template>
|
||||
</ul>
|
||||
<p class="info-note" x-text="(doc.member_permissions||[]).includes('force_overrides')?t('access_force_enabled'):t('access_force_disabled')"></p>
|
||||
<div class="actions" x-show="can('manage_members')"><button @click="open('member-permissions')"><span x-text="t('access_edit_employee_permissions')"></span></button></div>
|
||||
</article>
|
||||
<article class="panel rule-card spaced" id="access-groups" x-show="detail?.access">
|
||||
<span class="eyebrow"><span x-text="t('access_groups_title')"></span></span>
|
||||
<dl class="config-summary">
|
||||
<div><dt x-text="t('access_groups_admin')"></dt><dd x-text="(detail?.access?.admin_groups||[]).join(', ') || t('access_none')"></dd></div>
|
||||
<div><dt x-text="t('access_groups_member')"></dt><dd x-text="(detail?.access?.member_groups||[]).join(', ') || t('access_none')"></dd></div>
|
||||
</dl>
|
||||
<p class="muted"><span x-text="t('access_groups_hint')"></span></p>
|
||||
</article>
|
||||
<div class="section-line">
|
||||
<h2><span x-text="t('scheduling_directory_e8fdd4')"></span></h2>
|
||||
<span class="muted"><span x-text="t('country_controls_public_holidays_defaults_to_cyprus_e828a2')"></span></span>
|
||||
@@ -407,8 +495,8 @@
|
||||
<form @submit.prevent="save()">
|
||||
<div class="modal-heading">
|
||||
<div>
|
||||
<span class="eyebrow" x-show="modal!=='workspace'" x-text="['request','day','force'].includes(modal)?scheduleName(form.schedule_id || scheduleId):doc?.name || 'MEGURI'"></span>
|
||||
<h2 id="dialog-title" x-text="modal==='request'?(form.force_mode?t('apply_without_approval_d72926'):form.from_user===user?.id?t('who_can_cover_for_you_6e894c'):t('cover_person',{name:shortName(form.from_user)})):modal==='day'?t('choose_a_shift_330348'):({variable:t('workspace_variable_7abc57'),'generation-rule':t('generation_rule_d34ccf'),rotation:t('rotation_906fe5'),priority:t('ordered_project_coverage_cf12c3'),coverage:t('project_coverage_536388'),request:t('request_a_change_3d465f'),force:t('apply_without_confirmation_f0350d'),absence:t('planned_absence_bfcea7'),holiday:t('public_holiday_49ec03'),'holiday-import':t('import_public_holidays_8098e3'),'remove-schedule':t('remove_schedule_ef0a8a'),person:t('scheduling_person_1273c6'),working:t('workspace_working_interval_c7d250'),member:t('team_access_8fb357'),workspace:t('new_workspace_b21b86'),account:t('your_account_4ab291'),policy:t('escalation_policy_bbb0f7'),route:t('routing_rule_b00b22'),integration:t('connected_provider_9702c0'),preferences:t('team_preferences_7fcab5')})[modal]">
|
||||
<span class="eyebrow" x-text="modal.startsWith('admin-')?t('access_instance_eyebrow'):['request','day','force'].includes(modal)?scheduleName(form.schedule_id || scheduleId):doc?.name || 'MEGURI'"></span>
|
||||
<h2 id="dialog-title" x-text="modal==='request'?(form.force_mode?t('apply_without_approval_d72926'):form.from_user===user?.id?t('who_can_cover_for_you_6e894c'):t('cover_person',{name:shortName(form.from_user)})):modal==='day'?t('choose_a_shift_330348'):({variable:t('workspace_variable_7abc57'),'generation-rule':t('generation_rule_d34ccf'),rotation:t('rotation_906fe5'),priority:t('ordered_project_coverage_cf12c3'),coverage:t('project_coverage_536388'),request:t('request_a_change_3d465f'),force:t('apply_without_confirmation_f0350d'),absence:t('planned_absence_bfcea7'),holiday:t('public_holiday_49ec03'),'holiday-import':t('import_public_holidays_8098e3'),'remove-schedule':t('remove_schedule_ef0a8a'),person:t('scheduling_person_1273c6'),working:t('workspace_working_interval_c7d250'),'member-permissions':t('access_employee_permissions_title'),'admin-user':t('access_new_user_title'),'admin-groups':t('access_edit_groups_title'),'admin-password':t('access_reset_password_title'),'admin-delete':t('access_delete_workspace_title'),account:t('your_account_4ab291'),policy:t('escalation_policy_bbb0f7'),route:t('routing_rule_b00b22'),integration:t('connected_provider_9702c0'),preferences:t('team_preferences_7fcab5')})[modal]">
|
||||
</h2>
|
||||
</div>
|
||||
<button type="button" class="icon-button" @click="modal=''" :aria-label="t('close_dialog_7b2902')"><span x-text="t('symbol_67fba2')"></span></button>
|
||||
@@ -471,13 +559,6 @@
|
||||
</div>
|
||||
</div>
|
||||
</template><div x-show="!picker">
|
||||
<template x-if="modal==='workspace'">
|
||||
<div>
|
||||
|
||||
<label><span x-text="t('name_709a23')"></span><input x-model="form.name" required maxlength="100">
|
||||
</label>
|
||||
</div>
|
||||
</template>
|
||||
<template x-if="modal==='account'">
|
||||
<div>
|
||||
<label><span x-text="t('language')"></span><select x-model="form.language" :aria-label="t('language')"><option value="" x-text="t('language_auto')"></option><template x-for="code in I18n.supported"><option :value="code" :selected="form.language===code" x-text="t('language_'+code)"></option></template></select></label>
|
||||
@@ -496,10 +577,13 @@
|
||||
<p class="info-note" x-text="t('dates_and_timestamps_are_displayed_in_77ec74')+(form.timezone || browserZone)+t('this_never_changes_the_workspace_timezone_or_shifts_1de864')">
|
||||
</p>
|
||||
<p class="muted"><span x-text="t('country_changes_update_your_holiday_calendar_in_team_1e4eb8')"></span></p>
|
||||
<template x-if="user?.has_password"><div>
|
||||
<label><span x-text="t('current_password_19dff4')"></span><input type="password" autocomplete="current-password" x-model="form.current_password">
|
||||
</label>
|
||||
<label><span x-text="t('new_password_d850ee')"></span><input type="password" autocomplete="new-password" minlength="12" x-model="form.password">
|
||||
</label>
|
||||
</div></template>
|
||||
<p class="muted" x-show="!user?.has_password"><span x-text="t('access_sso_password_note')"></span></p>
|
||||
</div>
|
||||
</template>
|
||||
<template x-if="modal==='person'">
|
||||
@@ -562,7 +646,7 @@
|
||||
<div class="grow">
|
||||
<strong x-text="person(id)">
|
||||
</strong>
|
||||
<small x-text="countryName(people.find(p=>p.id===id)?.country)">
|
||||
<small x-text="countryName(personById(id)?.country)">
|
||||
</small>
|
||||
</div>
|
||||
<button type="button" @click="editPerson(id)" :aria-label="t('edit_participant_details_73e491')"><span x-text="t('details_dc3dec')"></span></button>
|
||||
@@ -735,18 +819,62 @@
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<template x-if="modal==='member'">
|
||||
<template x-if="modal==='member-permissions'">
|
||||
<div>
|
||||
<label><span x-text="t('registered_username_e29aa9')"></span><input x-model="form.username" required>
|
||||
</label>
|
||||
<p class="muted"><span x-text="t('your_teammate_needs_an_account_before_you_can_add_th_80fa32')"></span></p>
|
||||
<template x-for="grant in grantOptions">
|
||||
<p class="muted"><span x-text="t('access_employee_permissions_hint')"></span></p>
|
||||
<template x-for="grant in employeeGrantOptions" :key="grant.id">
|
||||
<label class="check">
|
||||
<input type="checkbox" :value="grant.id" x-model="form.permissions" :disabled="grant.id==='view'">
|
||||
<span x-text="grant.name">
|
||||
</span>
|
||||
<input type="checkbox" :value="grant.id" x-model="form.permissions" :disabled="grant.id==='view'" :data-permission="grant.id">
|
||||
<span x-text="grant.name"></span>
|
||||
</label>
|
||||
</template>
|
||||
<p class="info-note" x-text="t('access_force_hint')"></p>
|
||||
</div>
|
||||
</template>
|
||||
<template x-if="modal==='admin-user'">
|
||||
<div>
|
||||
<label><span x-text="t('username_84c290')"></span><input x-model="form.username" autocomplete="off" required minlength="3" maxlength="64">
|
||||
</label>
|
||||
<label><span x-text="t('display_name_c7874a')"></span><input x-model="form.display_name" required>
|
||||
</label>
|
||||
<label><span x-text="t('access_email_optional')"></span><input type="email" x-model="form.email" autocomplete="off">
|
||||
</label>
|
||||
<label><span x-text="t('password_8be3c9')"></span><input type="password" x-model="form.password" autocomplete="new-password" required minlength="12" maxlength="256">
|
||||
</label>
|
||||
<p class="muted"><span x-text="t('access_password_hint')"></span></p>
|
||||
<fieldset class="group-picker"><legend x-text="t('access_user_groups')"></legend>
|
||||
<p class="muted" x-show="!admin?.known_groups.length"><span x-text="t('access_no_known_groups')"></span></p>
|
||||
<template x-for="group in admin?.known_groups || []" :key="group">
|
||||
<label class="check"><input type="checkbox" :value="group" x-model="form.groups"><span x-text="group"></span></label>
|
||||
</template>
|
||||
</fieldset>
|
||||
</div>
|
||||
</template>
|
||||
<template x-if="modal==='admin-groups'">
|
||||
<div>
|
||||
<p class="muted"><span x-text="t('access_groups_picker_hint')"></span></p>
|
||||
<fieldset class="group-picker"><legend x-text="'@'+form.username"></legend>
|
||||
<p class="muted" x-show="!admin?.known_groups.length"><span x-text="t('access_no_known_groups')"></span></p>
|
||||
<template x-for="group in admin?.known_groups || []" :key="group">
|
||||
<label class="check"><input type="checkbox" :value="group" x-model="form.groups"><span x-text="group"></span></label>
|
||||
</template>
|
||||
</fieldset>
|
||||
</div>
|
||||
</template>
|
||||
<template x-if="modal==='admin-password'">
|
||||
<div>
|
||||
<p class="muted" x-text="'@'+form.username"></p>
|
||||
<label><span x-text="t('new_password_d850ee')"></span><input type="password" x-model="form.password" autocomplete="new-password" required minlength="12" maxlength="256">
|
||||
</label>
|
||||
<p class="muted"><span x-text="t('access_password_hint')"></span></p>
|
||||
</div>
|
||||
</template>
|
||||
<template x-if="modal==='admin-delete'">
|
||||
<div>
|
||||
<p class="info-note danger-note"><span x-text="t('access_delete_warning')"></span></p>
|
||||
<p><strong x-text="form.name"></strong></p>
|
||||
<label><span x-text="t('access_delete_confirm_label',{confirm:form.expected})"></span><input x-model="form.confirm" autocomplete="off" required>
|
||||
</label>
|
||||
</div>
|
||||
</template>
|
||||
<template x-if="modal==='preferences'">
|
||||
@@ -918,7 +1046,7 @@
|
||||
<button type="button" @click="modal=''"><span x-text="t('cancel_77dfd2')"></span></button>
|
||||
|
||||
<button type="button" class="primary" x-show="rulePeopleTarget" @click="closeRuleEditor()" x-text="t('done_e9b450')"></button>
|
||||
<button x-show="!rulePeopleTarget" class="primary" :class="{'remove-confirm':modal==='remove-schedule'}" :disabled="busy || (modal==='holiday-import' && !canImport) || (modal==='absence' && !canSaveAbsence) || (modal==='remove-schedule' && !canRemoveSchedule)" x-text="busy?t('saving_56a228'):modal==='remove-schedule'?(form.redirect_to?t('remove_redirect_references_f1a241'):removal?.plan.escalation_steps.length?t('remove_schedule_affected_steps_7348af'):t('remove_schedule_ef0a8a')):modal==='holiday-import'?t('import_new_holidays_b36db6'):modal==='request'?t('send_for_confirmation_f3df0f'):modal==='force'?t('apply_replacement_9b0845'):['variable','generation-rule'].includes(modal)?t('use_in_draft_a8f78c'):t('save_changes_179359')">
|
||||
<button x-show="!rulePeopleTarget" class="primary" :class="{'remove-confirm':modal==='remove-schedule'}" :disabled="busy || (modal==='holiday-import' && !canImport) || (modal==='absence' && !canSaveAbsence) || (modal==='remove-schedule' && !canRemoveSchedule) || (modal==='admin-delete' && form.confirm!==form.expected)" x-text="busy?t('saving_56a228'):modal==='admin-user'?t('access_create_user'):modal==='admin-password'?t('access_reset_password'):modal==='admin-delete'?t('access_delete_permanently'):modal==='remove-schedule'?(form.redirect_to?t('remove_redirect_references_f1a241'):removal?.plan.escalation_steps.length?t('remove_schedule_affected_steps_7348af'):t('remove_schedule_ef0a8a')):modal==='holiday-import'?t('import_new_holidays_b36db6'):modal==='request'?t('send_for_confirmation_f3df0f'):modal==='force'?t('apply_replacement_9b0845'):['variable','generation-rule'].includes(modal)?t('use_in_draft_a8f78c'):t('save_changes_179359')">
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
Reference in new issue
Block a user