Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add System token interface #13549

Merged
merged 13 commits into from
May 26, 2022
6 changes: 2 additions & 4 deletions api/src/database/system-data/fields/users.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -135,10 +135,8 @@ fields:
template: '{{ name }}'

- field: token
interface: token
options:
iconRight: vpn_key
placeholder: $t:fields.directus_users.token_placeholder
interface: system-token
special: conceal
width: full

- field: id
Expand Down
13 changes: 13 additions & 0 deletions app/src/interfaces/_system/system-token/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { defineInterface } from '@directus/shared/utils';
import InterfaceSystemToken from './system-token.vue';

export default defineInterface({
id: 'system-token',
name: '$t:interfaces.system-token.system-token',
description: '$t:interfaces.system-token.description',
icon: 'vpn_key',
component: InterfaceSystemToken,
system: true,
types: ['hash'],
options: [],
});
126 changes: 126 additions & 0 deletions app/src/interfaces/_system/system-token/system-token.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
<template>
<div class="system-token">
<v-input
:model-value="localValue"
:type="!isNewTokenGenerated ? 'password' : 'text'"
:placeholder="placeholder"
:disabled="disabled"
readonly
:class="{ saved: value && !localValue }"
@update:model-value="emitValue"
>
<template #append>
<v-icon
v-if="!disabled"
v-tooltip="value ? t('interfaces.system-token.regenerate') : t('interfaces.system-token.generate')"
:name="value ? 'refresh' : 'add'"
class="regenerate-icon"
clickable
:disabled="disabled || loading"
@click="generateToken"
/>
<v-icon
v-tooltip="!disabled && value && t('interfaces.system-token.remove_token')"
:name="!disabled && value ? 'clear' : 'vpn_key'"
:class="{ 'clear-icon': !disabled && !!value, 'default-icon': disabled && value }"
:clickable="!disabled && !!value"
:disabled="loading || !value"
@click="emitValue(null)"
/>
</template>
</v-input>

<v-notice v-if="isNewTokenGenerated && value" type="info">
{{ t('interfaces.system-token.generate_success_copy') }}
</v-notice>
</div>
</template>

<script lang="ts" setup>
import { computed, ref, watch } from 'vue';
import { useI18n } from 'vue-i18n';
import api from '@/api';
import { unexpectedError } from '@/utils/unexpected-error';

interface Props {
value?: string | null;
disabled?: boolean;
}

const props = withDefaults(defineProps<Props>(), { value: () => null, disabled: false });

const emit = defineEmits(['input']);

const { t } = useI18n();

const placeholder = computed(() => {
if (props.disabled && !props.value) return null;
return props.value ? t('interfaces.system-token.value_securely_saved') : t('interfaces.system-token.placeholder');
});

const localValue = ref<string | null>(null);
const loading = ref(false);
const isNewTokenGenerated = ref(false);
const regexp = new RegExp('^[*]+$');

watch(
() => props.value,
(newValue) => {
if (!newValue) {
localValue.value = null;
return;
}

if (newValue && regexp.test(newValue)) {
localValue.value = null;
isNewTokenGenerated.value = false;
}
},
{ immediate: true }
);

async function generateToken() {
loading.value = true;

try {
const response = await api.get('/utils/random/string');
emitValue(response.data.data);
isNewTokenGenerated.value = true;
} catch (err: any) {
unexpectedError(err);
} finally {
loading.value = false;
}
}

function emitValue(newValue: string | null) {
emit('input', newValue);
localValue.value = newValue;
}
</script>

<style lang="scss" scoped>
.v-input {
--v-input-font-family: var(--family-monospace);
}

.saved {
--v-input-placeholder-color: var(--primary);
}

.v-notice {
margin-top: 12px;
}

.regenerate-icon {
margin-right: 4px;
}

.clear-icon {
--v-icon-color-hover: var(--danger);
}

.default-icon {
--v-icon-color: var(--primary);
}
</style>
10 changes: 9 additions & 1 deletion app/src/lang/translations/en-US.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -1042,7 +1042,6 @@ fields:
status_archived: Archived
role: Role
token: Token
token_placeholder: Enter a secure access token...
provider: Provider
external_identifier: External Identifier
last_page: Last Page
Expand Down Expand Up @@ -1644,6 +1643,15 @@ interfaces:
header_color: Header Color
start_open: Start Open
start_closed: Start Closed
system-token:
system-token: Token
description: Manage static access token
placeholder: Click "Generate Token" to create a new static access token
value_securely_saved: Value Securely Saved
generate: Generate Token
regenerate: Regenerate Token
generate_success_copy: Make sure to backup and copy the token above. For security reasons, you will not be able to view the token again after saving and navigate off this page.
remove_token: Remove Token
displays:
translations:
translations: Translations
Expand Down