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?",
|
||||
"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": {
|
||||
"title": "Data Sharing",
|
||||
"description": "Help improve Hoppscotch by sharing anonymous data",
|
||||
|
|
@ -48,6 +49,7 @@
|
|||
"enable_email_auth": "Enable Email based authentication",
|
||||
"enable_smtp": "Enable SMTP",
|
||||
"host": "MAILER HOST",
|
||||
"input_validation": "SMTP URL should start with smtp(s)://",
|
||||
"password": "MAILER PASSWORD",
|
||||
"port": "MAILER PORT",
|
||||
"secure": "MAILER SECURE",
|
||||
|
|
|
|||
|
|
@ -40,8 +40,10 @@ declare module 'vue' {
|
|||
IconLucideChevronDown: typeof import('~icons/lucide/chevron-down')['default']
|
||||
IconLucideHelpCircle: typeof import('~icons/lucide/help-circle')['default']
|
||||
IconLucideInbox: typeof import('~icons/lucide/inbox')['default']
|
||||
IconLucideInfo: typeof import('~icons/lucide/info')['default']
|
||||
IconLucideSearch: typeof import('~icons/lucide/search')['default']
|
||||
IconLucideUser: typeof import('~icons/lucide/user')['default']
|
||||
IconLucideX: typeof import('~icons/lucide/x')['default']
|
||||
SettingsAuthProvider: typeof import('./components/settings/AuthProvider.vue')['default']
|
||||
SettingsConfigurations: typeof import('./components/settings/Configurations.vue')['default']
|
||||
SettingsDataSharing: typeof import('./components/settings/DataSharing.vue')['default']
|
||||
|
|
|
|||
|
|
@ -87,6 +87,16 @@
|
|||
@click="toggleMask(field.key)"
|
||||
/>
|
||||
</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>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -99,9 +109,9 @@
|
|||
|
||||
<script setup lang="ts">
|
||||
import { useVModel } from '@vueuse/core';
|
||||
import { computed, reactive } from 'vue';
|
||||
import { computed, reactive, watch } from 'vue';
|
||||
import { useI18n } from '~/composables/i18n';
|
||||
import { ServerConfigs } from '~/helpers/configs';
|
||||
import { hasInputValidationFailed, ServerConfigs } from '~/helpers/configs';
|
||||
import IconEye from '~icons/lucide/eye';
|
||||
import IconEyeOff from '~icons/lucide/eye-off';
|
||||
import IconHelpCircle from '~icons/lucide/help-circle';
|
||||
|
|
@ -132,12 +142,14 @@ const smtpConfigs = computed({
|
|||
type Field = {
|
||||
name: string;
|
||||
key: keyof ServerConfigs['mailConfigs']['fields'];
|
||||
error?: string;
|
||||
};
|
||||
|
||||
const smtpConfigFields = reactive<Field[]>([
|
||||
{
|
||||
name: t('configs.mail_configs.smtp_url'),
|
||||
key: 'mailer_smtp_url',
|
||||
error: t('configs.mail_configs.input_validation'),
|
||||
},
|
||||
{
|
||||
name: t('configs.mail_configs.address_from'),
|
||||
|
|
@ -215,4 +227,25 @@ const isCheckboxField = (field: Field) => {
|
|||
const toggleCheckbox = (field: Field) =>
|
||||
((smtpConfigs.value.fields[field.key] as boolean) =
|
||||
!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>
|
||||
|
|
|
|||
|
|
@ -1,5 +1,9 @@
|
|||
import { ref } from 'vue';
|
||||
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 ServerConfigs = {
|
||||
|
|
|
|||
|
|
@ -33,7 +33,7 @@
|
|||
<div v-if="isConfigUpdated" class="fixed bottom-0 right-0 m-10">
|
||||
<HoppButtonPrimary
|
||||
:label="t('configs.save_changes')"
|
||||
@click="showSaveChangesModal = !showSaveChangesModal"
|
||||
@click="triggerSaveChangesModal"
|
||||
/>
|
||||
</div>
|
||||
|
||||
|
|
@ -57,6 +57,7 @@ import { computed, ref } from 'vue';
|
|||
import { useI18n } from '~/composables/i18n';
|
||||
import { useToast } from '~/composables/toast';
|
||||
import { useConfigHandler } from '~/composables/useConfigHandler';
|
||||
import { hasInputValidationFailed } from '~/helpers/configs';
|
||||
|
||||
const t = useI18n();
|
||||
const toast = useToast();
|
||||
|
|
@ -91,6 +92,17 @@ const areAnyFieldsEmpty = computed(() =>
|
|||
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 = () => {
|
||||
if (areAnyFieldsEmpty.value) {
|
||||
return toast.error(t('configs.input_empty'));
|
||||
|
|
|
|||
|
|
@ -53,7 +53,12 @@
|
|||
|
||||
<td @click.stop class="flex justify-end mr-10">
|
||||
<div class="relative">
|
||||
<tippy interactive trigger="click" theme="popover">
|
||||
<tippy
|
||||
:key="team.id"
|
||||
interactive
|
||||
trigger="click"
|
||||
theme="popover"
|
||||
>
|
||||
<HoppButtonSecondary
|
||||
v-tippy="{ theme: 'tooltip' }"
|
||||
:icon="IconMoreHorizontal"
|
||||
|
|
|
|||
|
|
@ -109,7 +109,12 @@
|
|||
|
||||
<td @click.stop class="flex justify-end w-20">
|
||||
<div class="mt-2 mr-5">
|
||||
<tippy interactive trigger="click" theme="popover">
|
||||
<tippy
|
||||
:key="user.uid"
|
||||
interactive
|
||||
trigger="click"
|
||||
theme="popover"
|
||||
>
|
||||
<HoppButtonSecondary
|
||||
v-tippy="{ theme: 'tooltip' }"
|
||||
:icon="IconMoreHorizontal"
|
||||
|
|
|
|||
Loading…
Reference in a new issue