feat(sh-admin): introduce input validations to server configurations (#4642)
Co-authored-by: jamesgeorge007 <25279263+jamesgeorge007@users.noreply.github.com>
This commit is contained in:
parent
4200b00c04
commit
7f84c52c83
7 changed files with 68 additions and 5 deletions
|
|
@ -31,6 +31,7 @@
|
||||||
},
|
},
|
||||||
"confirm_changes": "Hoppscotch server must restart to reflect the new changes. Confirm changes made to the server configurations?",
|
"confirm_changes": "Hoppscotch server must restart to reflect the new changes. Confirm changes made to the server configurations?",
|
||||||
"input_empty": "Please fill all the fields before updating the configurations",
|
"input_empty": "Please fill all the fields before updating the configurations",
|
||||||
|
"input_validation_error": "Some fields have invalid values. Please correct them before updating the configurations",
|
||||||
"data_sharing": {
|
"data_sharing": {
|
||||||
"title": "Data Sharing",
|
"title": "Data Sharing",
|
||||||
"description": "Help improve Hoppscotch by sharing anonymous data",
|
"description": "Help improve Hoppscotch by sharing anonymous data",
|
||||||
|
|
@ -48,6 +49,7 @@
|
||||||
"enable_email_auth": "Enable Email based authentication",
|
"enable_email_auth": "Enable Email based authentication",
|
||||||
"enable_smtp": "Enable SMTP",
|
"enable_smtp": "Enable SMTP",
|
||||||
"host": "MAILER HOST",
|
"host": "MAILER HOST",
|
||||||
|
"input_validation": "SMTP URL should start with smtp(s)://",
|
||||||
"password": "MAILER PASSWORD",
|
"password": "MAILER PASSWORD",
|
||||||
"port": "MAILER PORT",
|
"port": "MAILER PORT",
|
||||||
"secure": "MAILER SECURE",
|
"secure": "MAILER SECURE",
|
||||||
|
|
|
||||||
|
|
@ -40,8 +40,10 @@ declare module 'vue' {
|
||||||
IconLucideChevronDown: typeof import('~icons/lucide/chevron-down')['default']
|
IconLucideChevronDown: typeof import('~icons/lucide/chevron-down')['default']
|
||||||
IconLucideHelpCircle: typeof import('~icons/lucide/help-circle')['default']
|
IconLucideHelpCircle: typeof import('~icons/lucide/help-circle')['default']
|
||||||
IconLucideInbox: typeof import('~icons/lucide/inbox')['default']
|
IconLucideInbox: typeof import('~icons/lucide/inbox')['default']
|
||||||
|
IconLucideInfo: typeof import('~icons/lucide/info')['default']
|
||||||
IconLucideSearch: typeof import('~icons/lucide/search')['default']
|
IconLucideSearch: typeof import('~icons/lucide/search')['default']
|
||||||
IconLucideUser: typeof import('~icons/lucide/user')['default']
|
IconLucideUser: typeof import('~icons/lucide/user')['default']
|
||||||
|
IconLucideX: typeof import('~icons/lucide/x')['default']
|
||||||
SettingsAuthProvider: typeof import('./components/settings/AuthProvider.vue')['default']
|
SettingsAuthProvider: typeof import('./components/settings/AuthProvider.vue')['default']
|
||||||
SettingsConfigurations: typeof import('./components/settings/Configurations.vue')['default']
|
SettingsConfigurations: typeof import('./components/settings/Configurations.vue')['default']
|
||||||
SettingsDataSharing: typeof import('./components/settings/DataSharing.vue')['default']
|
SettingsDataSharing: typeof import('./components/settings/DataSharing.vue')['default']
|
||||||
|
|
|
||||||
|
|
@ -87,6 +87,16 @@
|
||||||
@click="toggleMask(field.key)"
|
@click="toggleMask(field.key)"
|
||||||
/>
|
/>
|
||||||
</span>
|
</span>
|
||||||
|
|
||||||
|
<div
|
||||||
|
v-if="getFieldError(field.key)"
|
||||||
|
class="flex items-center justify-between bg-red-200 px-2 py-2 font-semibold text-red-700 rounded-lg mt-2 max-w-lg"
|
||||||
|
>
|
||||||
|
<div class="flex items-center">
|
||||||
|
<icon-lucide-info class="mr-2" />
|
||||||
|
<span> {{ field.error }} </span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -99,9 +109,9 @@
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { useVModel } from '@vueuse/core';
|
import { useVModel } from '@vueuse/core';
|
||||||
import { computed, reactive } from 'vue';
|
import { computed, reactive, watch } from 'vue';
|
||||||
import { useI18n } from '~/composables/i18n';
|
import { useI18n } from '~/composables/i18n';
|
||||||
import { ServerConfigs } from '~/helpers/configs';
|
import { hasInputValidationFailed, ServerConfigs } from '~/helpers/configs';
|
||||||
import IconEye from '~icons/lucide/eye';
|
import IconEye from '~icons/lucide/eye';
|
||||||
import IconEyeOff from '~icons/lucide/eye-off';
|
import IconEyeOff from '~icons/lucide/eye-off';
|
||||||
import IconHelpCircle from '~icons/lucide/help-circle';
|
import IconHelpCircle from '~icons/lucide/help-circle';
|
||||||
|
|
@ -132,12 +142,14 @@ const smtpConfigs = computed({
|
||||||
type Field = {
|
type Field = {
|
||||||
name: string;
|
name: string;
|
||||||
key: keyof ServerConfigs['mailConfigs']['fields'];
|
key: keyof ServerConfigs['mailConfigs']['fields'];
|
||||||
|
error?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
const smtpConfigFields = reactive<Field[]>([
|
const smtpConfigFields = reactive<Field[]>([
|
||||||
{
|
{
|
||||||
name: t('configs.mail_configs.smtp_url'),
|
name: t('configs.mail_configs.smtp_url'),
|
||||||
key: 'mailer_smtp_url',
|
key: 'mailer_smtp_url',
|
||||||
|
error: t('configs.mail_configs.input_validation'),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: t('configs.mail_configs.address_from'),
|
name: t('configs.mail_configs.address_from'),
|
||||||
|
|
@ -215,4 +227,25 @@ const isCheckboxField = (field: Field) => {
|
||||||
const toggleCheckbox = (field: Field) =>
|
const toggleCheckbox = (field: Field) =>
|
||||||
((smtpConfigs.value.fields[field.key] as boolean) =
|
((smtpConfigs.value.fields[field.key] as boolean) =
|
||||||
!smtpConfigs.value.fields[field.key]);
|
!smtpConfigs.value.fields[field.key]);
|
||||||
|
|
||||||
|
// Input Validation
|
||||||
|
const fieldErrors = computed(() => {
|
||||||
|
const errors: Record<string, boolean> = {};
|
||||||
|
|
||||||
|
if (smtpConfigs.value?.fields.mailer_smtp_url) {
|
||||||
|
const value = smtpConfigs.value.fields.mailer_smtp_url;
|
||||||
|
errors.mailer_smtp_url =
|
||||||
|
!value.startsWith('smtp://') && !value.startsWith('smtps://');
|
||||||
|
}
|
||||||
|
|
||||||
|
return errors;
|
||||||
|
});
|
||||||
|
|
||||||
|
const getFieldError = (
|
||||||
|
fieldKey: keyof ServerConfigs['mailConfigs']['fields']
|
||||||
|
) => fieldErrors.value[fieldKey];
|
||||||
|
|
||||||
|
watch(fieldErrors, (errors) => {
|
||||||
|
hasInputValidationFailed.value = Object.values(errors).some(Boolean);
|
||||||
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,9 @@
|
||||||
|
import { ref } from 'vue';
|
||||||
import { InfraConfigEnum } from './backend/graphql';
|
import { InfraConfigEnum } from './backend/graphql';
|
||||||
|
|
||||||
|
// Check if any input validation has failed
|
||||||
|
export const hasInputValidationFailed = ref(false);
|
||||||
|
|
||||||
export type SsoAuthProviders = 'google' | 'microsoft' | 'github';
|
export type SsoAuthProviders = 'google' | 'microsoft' | 'github';
|
||||||
|
|
||||||
export type ServerConfigs = {
|
export type ServerConfigs = {
|
||||||
|
|
|
||||||
|
|
@ -33,7 +33,7 @@
|
||||||
<div v-if="isConfigUpdated" class="fixed bottom-0 right-0 m-10">
|
<div v-if="isConfigUpdated" class="fixed bottom-0 right-0 m-10">
|
||||||
<HoppButtonPrimary
|
<HoppButtonPrimary
|
||||||
:label="t('configs.save_changes')"
|
:label="t('configs.save_changes')"
|
||||||
@click="showSaveChangesModal = !showSaveChangesModal"
|
@click="triggerSaveChangesModal"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
@ -57,6 +57,7 @@ import { computed, ref } from 'vue';
|
||||||
import { useI18n } from '~/composables/i18n';
|
import { useI18n } from '~/composables/i18n';
|
||||||
import { useToast } from '~/composables/toast';
|
import { useToast } from '~/composables/toast';
|
||||||
import { useConfigHandler } from '~/composables/useConfigHandler';
|
import { useConfigHandler } from '~/composables/useConfigHandler';
|
||||||
|
import { hasInputValidationFailed } from '~/helpers/configs';
|
||||||
|
|
||||||
const t = useI18n();
|
const t = useI18n();
|
||||||
const toast = useToast();
|
const toast = useToast();
|
||||||
|
|
@ -91,6 +92,17 @@ const areAnyFieldsEmpty = computed(() =>
|
||||||
workingConfigs.value ? AreAnyConfigFieldsEmpty(workingConfigs.value) : false
|
workingConfigs.value ? AreAnyConfigFieldsEmpty(workingConfigs.value) : false
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const triggerSaveChangesModal = () => {
|
||||||
|
if (areAnyFieldsEmpty.value) {
|
||||||
|
return toast.error(t('configs.input_empty'));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (hasInputValidationFailed.value) {
|
||||||
|
return toast.error(t('configs.input_validation_error'));
|
||||||
|
}
|
||||||
|
showSaveChangesModal.value = true;
|
||||||
|
};
|
||||||
|
|
||||||
const restartServer = () => {
|
const restartServer = () => {
|
||||||
if (areAnyFieldsEmpty.value) {
|
if (areAnyFieldsEmpty.value) {
|
||||||
return toast.error(t('configs.input_empty'));
|
return toast.error(t('configs.input_empty'));
|
||||||
|
|
|
||||||
|
|
@ -53,7 +53,12 @@
|
||||||
|
|
||||||
<td @click.stop class="flex justify-end mr-10">
|
<td @click.stop class="flex justify-end mr-10">
|
||||||
<div class="relative">
|
<div class="relative">
|
||||||
<tippy interactive trigger="click" theme="popover">
|
<tippy
|
||||||
|
:key="team.id"
|
||||||
|
interactive
|
||||||
|
trigger="click"
|
||||||
|
theme="popover"
|
||||||
|
>
|
||||||
<HoppButtonSecondary
|
<HoppButtonSecondary
|
||||||
v-tippy="{ theme: 'tooltip' }"
|
v-tippy="{ theme: 'tooltip' }"
|
||||||
:icon="IconMoreHorizontal"
|
:icon="IconMoreHorizontal"
|
||||||
|
|
|
||||||
|
|
@ -109,7 +109,12 @@
|
||||||
|
|
||||||
<td @click.stop class="flex justify-end w-20">
|
<td @click.stop class="flex justify-end w-20">
|
||||||
<div class="mt-2 mr-5">
|
<div class="mt-2 mr-5">
|
||||||
<tippy interactive trigger="click" theme="popover">
|
<tippy
|
||||||
|
:key="user.uid"
|
||||||
|
interactive
|
||||||
|
trigger="click"
|
||||||
|
theme="popover"
|
||||||
|
>
|
||||||
<HoppButtonSecondary
|
<HoppButtonSecondary
|
||||||
v-tippy="{ theme: 'tooltip' }"
|
v-tippy="{ theme: 'tooltip' }"
|
||||||
:icon="IconMoreHorizontal"
|
:icon="IconMoreHorizontal"
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue