Convert most of user directory view to vuejs
This commit is contained in:
112
dashboard/src/components/ExposedLdap.vue
Normal file
112
dashboard/src/components/ExposedLdap.vue
Normal file
@@ -0,0 +1,112 @@
|
||||
<script setup>
|
||||
|
||||
const API_ORIGIN = import.meta.env.VITE_API_ORIGIN ? import.meta.env.VITE_API_ORIGIN : window.location.origin;
|
||||
|
||||
import { ref, onMounted, computed } from 'vue';
|
||||
import { Button, FormGroup, Checkbox, PasswordInput, TextInput } from 'pankow';
|
||||
import { copyToClipboard } from 'pankow/utils';
|
||||
import Section from './Section.vue';
|
||||
import DomainsModel from '../models/DomainsModel.js';
|
||||
import DashboardModel from '../models/DashboardModel.js';
|
||||
import UserDirectoryModel from '../models/UserDirectoryModel.js';
|
||||
|
||||
const domainsModel = DomainsModel.create(API_ORIGIN, localStorage.token);
|
||||
const dashboardModel = DashboardModel.create(API_ORIGIN, localStorage.token);
|
||||
const userDirectoryModel = UserDirectoryModel.create(API_ORIGIN, localStorage.token);
|
||||
|
||||
const adminDomain = ref({});
|
||||
|
||||
// form
|
||||
const editError = ref({});
|
||||
const busy = ref(false);
|
||||
const enabled = ref(false);
|
||||
const ldapUrl = ref('');
|
||||
const secret = ref('');
|
||||
const allowlist = ref('');
|
||||
|
||||
const isValid = computed(() => {
|
||||
// TODO check all
|
||||
return true;
|
||||
});
|
||||
|
||||
function onCopyToClipboard(value) {
|
||||
copyToClipboard(value);
|
||||
window.pankow.notify({ type: 'success', text: 'LDAP Url copied!' });
|
||||
}
|
||||
|
||||
async function onSubmit() {
|
||||
if (!isValid.value) return;
|
||||
|
||||
busy.value = true;
|
||||
editError.value = {};
|
||||
|
||||
const [error] = await userDirectoryModel.setExposedLdapConfig({ enabled: enabled.value, allowlist: allowlist.value, secret: secret.value });
|
||||
busy.value = false;
|
||||
|
||||
if (error) {
|
||||
if (error.status === 400) {
|
||||
if (error.body.message.indexOf('secret') !== -1) editError.value.secret = error.body.message;
|
||||
else editError.value.allowlist = error.body.message;
|
||||
} else {
|
||||
editError.value.generic = error.body ? error.body.message : 'Internal error';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(async () => {
|
||||
const domains = await domainsModel.list();
|
||||
const config = await dashboardModel.getConfig();
|
||||
|
||||
ldapUrl.value = 'ldaps://' + config.adminFqdn + ':636';
|
||||
adminDomain.value = domains.find(d => d.domain === config.adminDomain) || domains[0];
|
||||
|
||||
const [error, result] = await userDirectoryModel.getExposedLdapConfig();
|
||||
if (error) return console.error(error);
|
||||
|
||||
enabled.value = result.enabled;
|
||||
secret.value = result.secret;
|
||||
allowlist.value = result.allowlist;
|
||||
});
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<Section :title="$t('users.exposedLdap.title')">
|
||||
<p>{{ $t('users.exposedLdap.description') }}</p>
|
||||
|
||||
<form novalidate @submit.prevent="onSubmit()" autocomplete="off">
|
||||
<fieldset :disabled="busy">
|
||||
<input style="display: none" type="submit" :disabled="busy || !isValid" />
|
||||
|
||||
<Checkbox v-model="enabled" :label="$t('users.exposedLdap.enabled')" /><sup><a ng-href="https://docs.cloudron.io/user-directory/#directory-server" class="help" target="_blank"><i class="fa fa-question-circle"></i></a></sup>
|
||||
|
||||
<FormGroup>
|
||||
<label for="ldapUrlInput">{{ $t('users.exposedLdap.secret.url') }}</label>
|
||||
<TextInput id="ldapUrlInput" v-model="ldapUrl" readonly @click="onCopyToClipboard(ldapUrl)" style="cursor: copy" />
|
||||
<p class="text-small text-warning" v-show="adminDomain.provider === 'cloudflare'">{{ $t('users.exposedLdap.cloudflarePortWarning') }} </p>
|
||||
</FormGroup>
|
||||
|
||||
<FormGroup>
|
||||
<label for="secretInput">{{ $t('users.exposedLdap.secret.label') }}</label>
|
||||
<p class="small" v-html="$t('users.exposedLdap.secret.description', { userDN: 'cn=admin,ou=system,dc=cloudron' })"></p>
|
||||
<PasswordInput id="secretInput" v-model="secret" />
|
||||
<div class="has-error" v-show="editError.secret">{{ editError.secret }}</div>
|
||||
</FormGroup>
|
||||
|
||||
<FormGroup>
|
||||
<label for="allowlistInput">{{ $t('users.exposedLdap.ipRestriction.label') }}</label>
|
||||
<p class="small" v-html="$t('users.exposedLdap.ipRestriction.description')"></p>
|
||||
<textarea id="allowlistInput" v-model="allowlist" :placeholder="$t('users.exposedLdap.ipRestriction.placeholder')" rows="4"></textarea>
|
||||
<div class="has-error" v-show="editError.allowlist">{{ editError.allowlist }}</div>
|
||||
</FormGroup>
|
||||
</fieldset>
|
||||
</form>
|
||||
|
||||
<br/>
|
||||
|
||||
<p class="has-error" v-show="editError.generic">{{ editError.generic }}</p>
|
||||
<Button :loading="busy" :disabled="!isValid || busy" @click="onSubmit()">{{ $t('users.settings.saveAction') }}</Button>
|
||||
</Section>
|
||||
</div>
|
||||
</template>
|
||||
410
dashboard/src/components/ExternalLdap.vue
Normal file
410
dashboard/src/components/ExternalLdap.vue
Normal file
@@ -0,0 +1,410 @@
|
||||
<script setup>
|
||||
|
||||
const API_ORIGIN = import.meta.env.VITE_API_ORIGIN ? import.meta.env.VITE_API_ORIGIN : window.location.origin;
|
||||
|
||||
import { ref, useTemplateRef, onMounted, computed } from 'vue';
|
||||
import { Dialog, Button, Icon, FormGroup, Dropdown, Checkbox, TextInput, ProgressBar } from 'pankow';
|
||||
import { prettyLongDate } from 'pankow/utils';
|
||||
import Section from './Section.vue';
|
||||
import UserDirectoryModel from '../models/UserDirectoryModel.js';
|
||||
import TasksModel from '../models/TasksModel.js';
|
||||
import { TASK_TYPES } from '../constants.js';
|
||||
|
||||
const userDirectoryModel = UserDirectoryModel.create(API_ORIGIN, localStorage.token);
|
||||
const tasksModel = TasksModel.create(API_ORIGIN, localStorage.token);
|
||||
|
||||
const availableProviders = [
|
||||
{ name: 'Active Directory', value: 'ad' },
|
||||
{ name: 'Cloudron', value: 'cloudron' },
|
||||
{ name: 'Jumpcloud', value: 'jumpcloud' },
|
||||
{ name: 'Okta', value: 'okta' },
|
||||
{ name: 'Univention Corporate Server (UCS)', value: 'univention' },
|
||||
{ name: 'Other', value: 'other' },
|
||||
{ name: 'Disabled', value: 'noop' }
|
||||
];
|
||||
|
||||
const dialog = useTemplateRef('dialog');
|
||||
|
||||
const taskLogsMenu = ref([]);
|
||||
const tasks = ref([]);
|
||||
const config = ref({});
|
||||
|
||||
// for edit dialog
|
||||
const editBusy = ref(false);
|
||||
const editError = ref({});
|
||||
const provider = ref('');
|
||||
const url = ref('');
|
||||
const acceptSelfSignedCerts = ref(false);
|
||||
const baseDn = ref('');
|
||||
const filter = ref('');
|
||||
const usernameField = ref('');
|
||||
const syncGroups = ref(false);
|
||||
const groupBaseDn = ref('');
|
||||
const groupFilter = ref('');
|
||||
const groupnameField = ref('');
|
||||
const bindDn = ref('');
|
||||
const bindPassword = ref('');
|
||||
const autoCreate = ref(false);
|
||||
|
||||
const isValid = computed(() => {
|
||||
if (!provider.value) return false;
|
||||
if (!url.value) return false;
|
||||
if (!baseDn.value) return false;
|
||||
if (!filter.value) return false;
|
||||
|
||||
// TODO check all
|
||||
|
||||
return true;
|
||||
});
|
||||
|
||||
async function onConfigure() {
|
||||
editBusy.value = false;
|
||||
editError.value = {};
|
||||
provider.value = config.value.provider;
|
||||
url.value = config.value.url;
|
||||
acceptSelfSignedCerts.value = config.value.acceptSelfSignedCerts;
|
||||
baseDn.value = config.value.baseDn;
|
||||
filter.value = config.value.filter;
|
||||
usernameField.value = config.value.usernameField;
|
||||
syncGroups.value = config.value.syncGroups;
|
||||
groupBaseDn.value = config.value.groupBaseDn;
|
||||
groupFilter.value = config.value.groupFilter;
|
||||
groupnameField.value = config.value.groupnameField;
|
||||
bindDn.value = config.value.bindDn;
|
||||
bindPassword.value = config.value.bindPassword;
|
||||
autoCreate.value = config.value.autoCreate;
|
||||
|
||||
dialog.value.open();
|
||||
}
|
||||
|
||||
async function onSubmit() {
|
||||
if (!isValid.value) return;
|
||||
|
||||
editBusy.value = true;
|
||||
editError.value = {};
|
||||
|
||||
const config = { provider: provider.value };
|
||||
|
||||
if (provider.value === 'cloudron') {
|
||||
config.url = url.value;
|
||||
config.acceptSelfSignedCerts = acceptSelfSignedCerts.value;
|
||||
config.autoCreate = autoCreate.value;
|
||||
config.syncGroups = syncGroups.value;
|
||||
config.bindPassword = bindPassword.value;
|
||||
|
||||
// those values are known and thus overwritten
|
||||
config.baseDn = 'ou=users,dc=cloudron';
|
||||
config.filter = '(objectClass=inetOrgPerson)';
|
||||
config.usernameField = 'username';
|
||||
config.groupBaseDn = 'ou=groups,dc=cloudron';
|
||||
config.groupFilter = '(objectClass=group)';
|
||||
config.groupnameField = 'cn';
|
||||
config.bindDn = 'cn=admin,ou=system,dc=cloudron';
|
||||
} else if (provider.value !== 'noop') {
|
||||
config.url = url.value;
|
||||
config.acceptSelfSignedCerts = acceptSelfSignedCerts.value;
|
||||
config.baseDn = baseDn.value;
|
||||
config.filter = filter.value;
|
||||
config.usernameField = usernameField.value;
|
||||
config.syncGroups = syncGroups.value;
|
||||
config.groupBaseDn = groupBaseDn.value;
|
||||
config.groupFilter = groupFilter.value;
|
||||
config.groupnameField = groupnameField.value;
|
||||
config.autoCreate = autoCreate.value;
|
||||
|
||||
if (bindDn.value) {
|
||||
config.bindDn = bindDn.value;
|
||||
config.bindPassword = bindPassword.value;
|
||||
}
|
||||
}
|
||||
|
||||
const [error] = await userDirectoryModel.setExternalLdapConfig(config);
|
||||
if (error) {
|
||||
editBusy.value = false;
|
||||
|
||||
if (error.status === 424) {
|
||||
if (error.code === 'SELF_SIGNED_CERT_IN_CHAIN') editError.value.acceptSelfSignedCerts = true;
|
||||
else editError.value.url = true;
|
||||
editError.value.generic = error.body.message;
|
||||
} else if (error.status === 400 && error.body.message === 'invalid baseDn') {
|
||||
editError.value.baseDn = true;
|
||||
} else if (error.status === 400 && error.body.message === 'invalid filter') {
|
||||
editError.value.filter = true;
|
||||
} else if (error.status === 400 && error.body.message === 'invalid groupBaseDn') {
|
||||
editError.value.groupBaseDn = true;
|
||||
} else if (error.status === 400 && error.body.message === 'invalid groupFilter') {
|
||||
editError.value.groupFilter = true;
|
||||
} else if (error.status === 400 && error.body.message === 'invalid groupnameField') {
|
||||
editError.value.groupnameField = true;
|
||||
} else if (error.status === 400 && error.body.message === 'invalid bind credentials') {
|
||||
editError.value.credentials = true;
|
||||
} else if (error.status === 400 && error.body.message === 'invalid usernameField') {
|
||||
editError.value.usernameField = true;
|
||||
} else {
|
||||
console.error('Failed to set external LDAP config:', error);
|
||||
editError.value.generic = error.body.message;
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
await refresh();
|
||||
editBusy.value = false;
|
||||
dialog.value.close();
|
||||
}
|
||||
|
||||
const syncBusy = ref(false);
|
||||
const syncPercent = ref(0);
|
||||
const syncMessage = ref('');
|
||||
const syncError = ref('');
|
||||
|
||||
async function onSync() {
|
||||
syncBusy.value = true;
|
||||
syncPercent.value = 0;
|
||||
syncMessage.value = '';
|
||||
syncError.value = '';
|
||||
|
||||
const [error] = await userDirectoryModel.startExternalLdapSync();
|
||||
if (error && error.body) syncMessage.value = error.body.message;
|
||||
else if (error) console.error(error);
|
||||
else await refreshTasks();
|
||||
}
|
||||
|
||||
async function updateTaskStatus(id) {
|
||||
const [error, result] = await tasksModel.get(id);
|
||||
if (error) return setTimeout(updateTaskStatus.bind(null, id), 5000);
|
||||
|
||||
if (!result.active) {
|
||||
syncBusy.value = false;
|
||||
syncMessage.value = '';
|
||||
syncPercent.value = 100; // indicates that 'result' is valid
|
||||
syncError.value = result.success ? '' : result.error.message;
|
||||
|
||||
await refreshTasks(); // update the tasks list dropdown
|
||||
return;
|
||||
}
|
||||
|
||||
syncBusy.value = true;
|
||||
syncPercent.value = result.percent;
|
||||
syncMessage.value = result.message;
|
||||
|
||||
setTimeout(updateTaskStatus.bind(null, id), 500);
|
||||
}
|
||||
|
||||
async function refreshTasks() {
|
||||
const [error, result] = await tasksModel.getByType(TASK_TYPES.TASK_SYNC_EXTERNAL_LDAP);
|
||||
if (error) return console.error(error);
|
||||
|
||||
// limit to last 10
|
||||
tasks.value = result.slice(0, 10);
|
||||
|
||||
if (tasks.value.length && tasks.value[0].active) updateTaskStatus(tasks.value[0].id);
|
||||
|
||||
taskLogsMenu.value = tasks.value.map(t => {
|
||||
return {
|
||||
icon: 'fa-solid ' + ((!t.active && t.success) ? 'status-active fa-check-circle' : (t.active ? 'fa-circle-notch fa-spin' : 'status-error fa-times-circle')),
|
||||
label: prettyLongDate(t.ts),
|
||||
action: () => { window.open(`/logs.html?taskId=${t.id}`); }
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
async function refresh() {
|
||||
const [error, result] = await userDirectoryModel.getExternalLdapConfig();
|
||||
if (error) return console.error(error);
|
||||
|
||||
config.value = result;
|
||||
}
|
||||
|
||||
onMounted(async () => {
|
||||
await refresh();
|
||||
await refreshTasks();
|
||||
});
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<Dialog ref="dialog"
|
||||
:title="$t('users.externalLdapDialog.title')"
|
||||
:confirm-label="$t('main.dialog.save')"
|
||||
:confirm-busy="editBusy"
|
||||
:confirm-active="!editBusy && isValid"
|
||||
:reject-label="$t('main.dialog.cancel')"
|
||||
@confirm="onSubmit()"
|
||||
>
|
||||
<p class="has-error text-center" v-show="editError.generic">{{ editError.generic }}</p>
|
||||
|
||||
<FormGroup>
|
||||
<label for="ldapProvider">{{ $t('users.externalLdap.provider') }} <sup><a href="https://docs.cloudron.io/user-directory/#external-directory" target="_blank"><i class="fa fa-question-circle"></i></a></sup></label>
|
||||
<Dropdown id="ldapProvider" v-model="provider" :options="availableProviders" option-key="value" option-label="name" />
|
||||
</FormGroup>
|
||||
|
||||
<p class="text-small text-warning" v-show="provider === 'noop' && config.provider !== 'noop'">{{ $t('users.externalLdap.disableWarning') }}</p>
|
||||
|
||||
<div v-show="provider !== 'noop'">
|
||||
<form novalidate @submit.prevent="onSubmit()" autocomplete="off">
|
||||
<fieldset :disabled="editBusy">
|
||||
<input style="display: none" type="submit" :disabled="editBusy || !isValid" />
|
||||
|
||||
<FormGroup :class="{ 'has-error': editError.url }">
|
||||
<label class="control-label" for="configUrlInput">{{ $t('users.externalLdap.server') }}</label>
|
||||
<TextInput id="configUrlInput" v-model="url" placeholder="ldaps://example.com:636" />
|
||||
</FormGroup>
|
||||
|
||||
<Checkbox v-model="acceptSelfSignedCerts" :label="$t('users.externalLdap.acceptSelfSignedCert')" />
|
||||
<p class="has-error" v-show="editError.acceptSelfSignedCerts">{{ $t('users.externalLdap.errorSelfSignedCert') }}</p>
|
||||
|
||||
<FormGroup :class="{ 'has-error': editError.baseDn }" v-show="provider !== 'cloudron'">
|
||||
<label for="baseDnInput">{{ $t('users.externalLdap.baseDn') }}</label>
|
||||
<TextInput v-model="baseDn" id="baseDnInput" placeholder="ou=users,dc=example,dc=com" :required="provider !== 'cloudron'" />
|
||||
</FormGroup>
|
||||
|
||||
<FormGroup :class="{ 'has-error': editError.filter }" v-show="provider !== 'cloudron'">
|
||||
<label for="filterInput">{{ $t('users.externalLdap.filter') }}</label>
|
||||
<TextInput v-model="filter" id="filterInput" placeholder="(objectClass=inetOrgPerson)" :required="provider !== 'cloudron'" />
|
||||
</FormGroup>
|
||||
|
||||
<FormGroup :class="{ 'has-error': editError.usernameField }" v-show="provider !== 'cloudron'">
|
||||
<label for="usernameFieldInput">{{ $t('users.externalLdap.usernameField') }}</label>
|
||||
<TextInput v-model="usernameField" id="usernameFieldInput" placeholder="uid or sAMAcountName" />
|
||||
</FormGroup>
|
||||
|
||||
<Checkbox v-model="syncGroups" :label="$t('users.externalLdap.syncGroups')" />
|
||||
|
||||
<FormGroup :class="{ 'has-error': editError.groupBaseDn }" v-show="syncGroups && provider !== 'cloudron'">
|
||||
<label for="groupBaseDnInput">{{ $t('users.externalLdap.groupBaseDn') }}</label>
|
||||
<TextInput v-model="groupBaseDn" id="groupBaseDnInput" placeholder="ou=groups,dc=example,dc=com" :required="syncGroups && provider !== 'cloudron'" />
|
||||
</FormGroup>
|
||||
|
||||
<FormGroup :class="{ 'has-error': editError.groupFilter }" v-show="syncGroups && provider !== 'cloudron'">
|
||||
<label for="groupFilterInput">{{ $t('users.externalLdap.groupFilter') }}</label>
|
||||
<TextInput v-model="groupFilter" id="groupFilterInput" placeholder="(objectClass=groupOfNames)" :required="syncGroups && provider !== 'cloudron'" />
|
||||
</FormGroup>
|
||||
|
||||
<FormGroup :class="{ 'has-error': editError.groupnameField }" v-show="syncGroups && provider !== 'cloudron'">
|
||||
<label for="groupnameFieldInput">{{ $t('users.externalLdap.groupnameField') }}</label>
|
||||
<TextInput v-model="groupnameField" id="groupnameFieldInput" placeholder="cn" v-required="syncGroups && provider !== 'cloudron'" />
|
||||
</FormGroup>
|
||||
|
||||
<FormGroup :class="{ 'has-error': editError.credentials }" v-show="provider !== 'cloudron'">
|
||||
<label for="bindDnInput">{{ $t('users.externalLdap.bindUsername') }}</label>
|
||||
<TextInput v-model="bindDn" id="bindDnInput" placeholder="uid=admin,ou=Users,dc=example,dc=com" />
|
||||
</FormGroup>
|
||||
|
||||
<FormGroup :class="{ 'has-error': editError.credentials }">
|
||||
<label for="bindPasswordInput">{{ $t('users.externalLdap.bindPassword') }}</label>
|
||||
<PasswordInput v-model="bindPassword" id="bindPasswordInput" />
|
||||
</FormGroup>
|
||||
|
||||
<Checkbox v-model="autoCreate" :label="$t('users.externalLdap.autocreateUsersOnLogin')" />
|
||||
</fieldset>
|
||||
</form>
|
||||
</div>
|
||||
</Dialog>
|
||||
|
||||
<Section :title="$t('users.externalLdap.title')">
|
||||
<template #header-buttons>
|
||||
<Button tool icon="fas fa-align-left" v-tooltip="$t('domains.renewCerts.showLogsAction')" :menu="taskLogsMenu" :disabled="!taskLogsMenu.length"/>
|
||||
</template>
|
||||
|
||||
<p>{{ $t('users.externalLdap.description') }}</p>
|
||||
|
||||
<div>
|
||||
<div v-show="config.provider === 'noop'">
|
||||
{{ $t('users.externalLdap.noopInfo') }}
|
||||
</div>
|
||||
|
||||
<div class="info-row" v-show="config.provider !== 'noop'">
|
||||
<div class="info-label">{{ $t('users.externalLdap.provider') }}</div>
|
||||
<div class="info-value">{{ config.provider }}</div>
|
||||
</div>
|
||||
|
||||
<div class="info-row" v-show="config.provider !== 'noop'">
|
||||
<div class="info-label">{{ $t('users.externalLdap.server') }}</div>
|
||||
<div class="info-value">{{ config.url }}</div>
|
||||
</div>
|
||||
|
||||
<div class="info-row" v-show="config.provider !== 'noop'">
|
||||
<div class="info-label">{{ $t('users.externalLdap.acceptSelfSignedCert') }}</div>
|
||||
<div class="info-value"><Icon :icon="config.acceptSelfSignedCerts ? 'fa-solid fa-check' : 'fa-solid fa-xmark'" /></div>
|
||||
</div>
|
||||
|
||||
<div class="info-row" v-show="config.provider !== 'noop' && config.provider !== 'cloudron'">
|
||||
<div class="info-label">{{ $t('users.externalLdap.baseDn') }}</div>
|
||||
<div class="info-value">{{ config.baseDn }}</div>
|
||||
</div>
|
||||
|
||||
<div class="info-row" v-show="config.provider !== 'noop' && config.provider !== 'cloudron'">
|
||||
<div class="info-label">{{ $t('users.externalLdap.filter') }}</div>
|
||||
<div class="info-value">{{ config.filter }}</div>
|
||||
</div>
|
||||
|
||||
<div class="info-row" v-show="config.provider !== 'noop' && config.provider !== 'cloudron'">
|
||||
<div class="info-label">{{ $t('users.externalLdap.usernameField') }}</div>
|
||||
<div class="info-value">{{ config.usernameField || 'uid' }}</div>
|
||||
</div>
|
||||
|
||||
<div class="info-row" v-show="config.provider !== 'noop'">
|
||||
<div class="info-label">{{ $t('users.externalLdap.syncGroups') }}</div>
|
||||
<div class="info-value"><Icon :icon="config.syncGroups ? 'fa-solid fa-check' : 'fa-solid fa-xmark'" /></div>
|
||||
</div>
|
||||
|
||||
<div class="info-row" v-show="config.provider !== 'noop' && config.provider !== 'cloudron' && config.syncGroups">
|
||||
<div class="info-label">{{ $t('users.externalLdap.groupBaseDn') }}</div>
|
||||
<div class="info-value">{{ config.groupBaseDn }}</div>
|
||||
</div>
|
||||
|
||||
<div class="info-row" v-show="config.provider !== 'noop' && config.provider !== 'cloudron' && config.syncGroups">
|
||||
<div class="info-label">{{ $t('users.externalLdap.groupFilter') }}</div>
|
||||
<div class="info-value">{{ config.groupFilter }}</div>
|
||||
</div>
|
||||
|
||||
<div class="info-row" v-show="config.provider !== 'noop' && config.provider !== 'cloudron' && config.syncGroups">
|
||||
<div class="info-label">{{ $t('users.externalLdap.groupnameField') }}</div>
|
||||
<div class="info-value">{{ config.groupnameField }}</div>
|
||||
</div>
|
||||
|
||||
<div class="info-row" v-show="config.provider !== 'noop' && config.provider !== 'cloudron'">
|
||||
<div class="info-label">{{ $t('users.externalLdap.auth') }}</div>
|
||||
<div class="info-value"><Icon :icon="config.bindDn ? 'fa-solid fa-check' : 'fa-solid fa-xmark'" /></div>
|
||||
</div>
|
||||
|
||||
<div class="info-row" v-show="config.provider !== 'noop'">
|
||||
<div class="info-label">{{ $t('users.externalLdap.autocreateUsersOnLogin') }}</div>
|
||||
<div class="info-value"><Icon :icon="config.autoCreate ? 'fa-solid fa-check' : 'fa-solid fa-xmark'" /></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<br/>
|
||||
|
||||
<ProgressBar :value="syncPercent" v-show="syncBusy"/>
|
||||
<p v-show="syncBusy">{{ syncMessage || 'Queued' }}</p>
|
||||
<p v-show="!syncBusy && syncError" class="has-error">{{ syncError }}</p>
|
||||
|
||||
<Button @click="onSync()" :loading="syncBusy" :disabled="syncBusy || config.provider === 'noop'">{{ $t('users.externalLdap.syncAction') }}</Button>
|
||||
<Button @click="onConfigure()">{{ $t('users.externalLdap.configureAction') }}</Button>
|
||||
</Section>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
|
||||
.info-row {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.info-label {
|
||||
font-weight: bold;
|
||||
flex-basis: 50%;
|
||||
}
|
||||
|
||||
.info-value {
|
||||
flex-basis: 50%;
|
||||
text-align: right;
|
||||
text-overflow: ellipsis;
|
||||
overflow: hidden;
|
||||
text-wrap: nowrap;
|
||||
}
|
||||
|
||||
</style>
|
||||
@@ -6,6 +6,7 @@ import AppsView from './AppsView.vue';
|
||||
import AppstoreView from './AppstoreView.vue';
|
||||
import ProfileView from './ProfileView.vue';
|
||||
import SupportView from './SupportView.vue';
|
||||
import UserDirectoryView from './UserDirectoryView.vue';
|
||||
import VolumesView from './VolumesView.vue';
|
||||
|
||||
import ProfileModel from '../models/ProfileModel.js';
|
||||
@@ -17,6 +18,7 @@ const VIEWS = {
|
||||
APPSTORE: 'appstore',
|
||||
PROFILE: 'profile',
|
||||
SUPPORT: 'support',
|
||||
USER_DIRECTORY: 'user-directory',
|
||||
VOLUMES: 'volumes',
|
||||
};
|
||||
|
||||
@@ -28,6 +30,7 @@ export default {
|
||||
Notification,
|
||||
ProfileView,
|
||||
SupportView,
|
||||
UserDirectoryView,
|
||||
VolumesView,
|
||||
},
|
||||
data() {
|
||||
@@ -61,6 +64,8 @@ export default {
|
||||
that.view = VIEWS.PROFILE;
|
||||
} else if (view === VIEWS.SUPPORT) {
|
||||
that.view = VIEWS.SUPPORT;
|
||||
} else if (view === VIEWS.USER_DIRECTORY) {
|
||||
that.view = VIEWS.USER_DIRECTORY;
|
||||
} else if (view === VIEWS.VOLUMES) {
|
||||
that.view = VIEWS.VOLUMES;
|
||||
} else {
|
||||
@@ -85,6 +90,7 @@ export default {
|
||||
<AppstoreView v-if="view === VIEWS.APPSTORE" />
|
||||
<ProfileView v-if="view === VIEWS.PROFILE" />
|
||||
<SupportView v-if="view === VIEWS.SUPPORT" />
|
||||
<UserDirectoryView v-if="view === VIEWS.USER_DIRECTORY" />
|
||||
<VolumesView v-if="view === VIEWS.VOLUMES" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
60
dashboard/src/components/UserDirectoryView.vue
Normal file
60
dashboard/src/components/UserDirectoryView.vue
Normal file
@@ -0,0 +1,60 @@
|
||||
<script setup>
|
||||
|
||||
const API_ORIGIN = import.meta.env.VITE_API_ORIGIN ? import.meta.env.VITE_API_ORIGIN : window.location.origin;
|
||||
|
||||
import { ref, onMounted } from 'vue';
|
||||
import { Button, Checkbox } from 'pankow';
|
||||
import Section from './Section.vue';
|
||||
import ExternalLdap from './ExternalLdap.vue';
|
||||
import ExposedLdap from './ExposedLdap.vue';
|
||||
import UserDirectoryModel from '../models/UserDirectoryModel.js';
|
||||
|
||||
const userDirectoryModel = UserDirectoryModel.create(API_ORIGIN, localStorage.token);
|
||||
|
||||
const editableUserProfiles = ref(false);
|
||||
const mandatory2FA = ref(false);
|
||||
const configBusy = ref(false);
|
||||
|
||||
async function onSubmitConfig() {
|
||||
configBusy.value = true;
|
||||
|
||||
const [error] = await userDirectoryModel.setGlobalProfileConfig({ mandatory2FA: mandatory2FA.value, lockUserProfiles: !editableUserProfiles.value });
|
||||
if (error) console.error(error);
|
||||
|
||||
configBusy.value = false;
|
||||
}
|
||||
|
||||
onMounted(async () => {
|
||||
const [error, result] = await userDirectoryModel.getGlobalProfileConfig();
|
||||
if (error) return console.error(error);
|
||||
|
||||
editableUserProfiles.value = !result.lockUserProfiles;
|
||||
mandatory2FA.value = result.mandatory2FA;
|
||||
});
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="content">
|
||||
<Section :title="$t('users.title')">
|
||||
<form role="form" novalidate @submit="onSubmitConfig()" autocomplete="off">
|
||||
<fieldset :disabled="configBusy">
|
||||
<div>
|
||||
<Checkbox v-model="editableUserProfiles" :label="$t('users.settings.allowProfileEditCheckbox')" /><sup><a href="https://docs.cloudron.io/user-directory/#lock-profile" target="_blank"><i class="fa fa-question-circle"></i></a></sup>
|
||||
</div>
|
||||
<div>
|
||||
<Checkbox v-model="mandatory2FA" :label="$t('users.settings.require2FACheckbox')" />
|
||||
</div>
|
||||
|
||||
<br/>
|
||||
|
||||
<Button @click="onSubmitConfig()" :disabled="configBusy" :loading="configBusy">{{ $t('users.settings.saveAction') }}</Button>
|
||||
</fieldset>
|
||||
</form>
|
||||
</Section>
|
||||
|
||||
<ExternalLdap />
|
||||
<ExposedLdap />
|
||||
<!-- TODO oidc clients -->
|
||||
</div>
|
||||
</template>
|
||||
Reference in New Issue
Block a user