feat: introducing user guidance and error management helpers in admin dashboard (#4548)
This commit is contained in:
committed by
GitHub
parent
dad15133f4
commit
73f3e54c00
@@ -96,7 +96,7 @@
|
||||
"last_used_on": "Last used on",
|
||||
"no_expiration": "No expiration",
|
||||
"no_expiration_verbose": "This token will never expire!",
|
||||
"section_description": "Manage your Hoppscotch users through APIs with Infra tokens",
|
||||
"section_description": "Manage your Hoppscotch users through APIs with Infra tokens.",
|
||||
"section_title": "Infra Tokens",
|
||||
"tab_title": "Infra Tokens",
|
||||
"token_expires_on": "This token will expire on",
|
||||
@@ -247,6 +247,11 @@
|
||||
"users_to_admin_success": "Selected users are elevated to admin status!!",
|
||||
"users_to_admin_failure": "Failed to elevate selected users to admin status!!"
|
||||
},
|
||||
"support": {
|
||||
"description": "Get help from the Hoppscotch community",
|
||||
"documentation": "Documentation",
|
||||
"more_info": "More Info"
|
||||
},
|
||||
"teams": {
|
||||
"add_member": "Add Member",
|
||||
"add_members": "Add Members",
|
||||
@@ -325,6 +330,7 @@
|
||||
"invalid_user": "Invalid User",
|
||||
"invite_load_list_error": "Unable to Load Invited Users List",
|
||||
"invite_user": "Invite User",
|
||||
"invite_users_description": "Invite your team members to join Hoppscotch.",
|
||||
"invited_by": "Invited By",
|
||||
"invited_on": "Invited On",
|
||||
"invited_users": "Invited Users",
|
||||
@@ -341,6 +347,7 @@
|
||||
"not_available": "Not Available",
|
||||
"not_found": "User not found",
|
||||
"pending_invites": "Pending Invites",
|
||||
"pending_invites_description": "Manage and track pending user invitations with clear status and actions.",
|
||||
"remove_admin_privilege": "Remove Admin Privilege",
|
||||
"remove_admin_status": "Remove Admin Status",
|
||||
"rename": "Rename",
|
||||
|
||||
@@ -14,6 +14,7 @@ declare module 'vue' {
|
||||
AppSidebar: typeof import('./components/app/Sidebar.vue')['default']
|
||||
AppToast: typeof import('./components/app/Toast.vue')['default']
|
||||
DashboardMetricsCard: typeof import('./components/dashboard/MetricsCard.vue')['default']
|
||||
FallbackComponent: typeof import('./components/FallbackComponent.vue')['default']
|
||||
HoppButtonPrimary: typeof import('@hoppscotch/ui')['HoppButtonPrimary']
|
||||
HoppButtonSecondary: typeof import('@hoppscotch/ui')['HoppButtonSecondary']
|
||||
HoppSmartAnchor: typeof import('@hoppscotch/ui')['HoppSmartAnchor']
|
||||
@@ -34,6 +35,7 @@ declare module 'vue' {
|
||||
HoppSmartTabs: typeof import('@hoppscotch/ui')['HoppSmartTabs']
|
||||
HoppSmartToggle: typeof import('@hoppscotch/ui')['HoppSmartToggle']
|
||||
IconLucideArrowLeft: typeof import('~icons/lucide/arrow-left')['default']
|
||||
IconLucideArrowUpRight: typeof import('~icons/lucide/arrow-up-right')['default']
|
||||
IconLucideCheck: typeof import('~icons/lucide/check')['default']
|
||||
IconLucideChevronDown: typeof import('~icons/lucide/chevron-down')['default']
|
||||
IconLucideHelpCircle: typeof import('~icons/lucide/help-circle')['default']
|
||||
|
||||
@@ -24,6 +24,16 @@
|
||||
</div>
|
||||
|
||||
<div class="flex items-center">
|
||||
<div class="inline-flex items-center mr-5">
|
||||
<HoppButtonSecondary
|
||||
to="https://docs.hoppscotch.io/documentation"
|
||||
blank
|
||||
v-tippy="{ theme: 'tooltip' }"
|
||||
:title="t('support.documentation')"
|
||||
:icon="IconHelpCircle"
|
||||
class="rounded hover:bg-primaryDark focus-visible:bg-primaryDark"
|
||||
/>
|
||||
</div>
|
||||
<div v-if="currentUser" class="relative">
|
||||
<tippy
|
||||
interactive
|
||||
@@ -69,6 +79,7 @@ import { useSidebar } from '~/composables/useSidebar';
|
||||
import { auth } from '~/helpers/auth';
|
||||
import IconMenu from '~icons/lucide/menu';
|
||||
import IconSidebarOpen from '~icons/lucide/sidebar-open';
|
||||
import IconHelpCircle from '~icons/lucide/help-circle';
|
||||
import IconSidebarClose from '~icons/lucide/sidebar-close';
|
||||
import { useI18n } from '~/composables/i18n';
|
||||
|
||||
|
||||
@@ -17,13 +17,21 @@
|
||||
v-for="provider in workingConfigs.providers"
|
||||
class="space-y-4 py-4"
|
||||
>
|
||||
<div class="flex items-center">
|
||||
<div class="flex justify-between">
|
||||
<HoppSmartToggle
|
||||
:on="provider.enabled"
|
||||
@change="provider.enabled = !provider.enabled"
|
||||
>
|
||||
{{ capitalize(provider.name) }}
|
||||
</HoppSmartToggle>
|
||||
<HoppButtonSecondary
|
||||
v-tippy="{ theme: 'tooltip', allowHTML: true }"
|
||||
to="https://docs.hoppscotch.io/documentation/self-host/community-edition/prerequisites#oauth"
|
||||
blank
|
||||
:title="t('support.documentation')"
|
||||
:icon="IconCircleHelp"
|
||||
class="rounded hover:bg-primaryDark focus-visible:bg-primaryDark"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div v-if="provider.enabled" class="ml-12">
|
||||
@@ -67,6 +75,7 @@ import { useVModel } from '@vueuse/core';
|
||||
import { reactive } from 'vue';
|
||||
import { useI18n } from '~/composables/i18n';
|
||||
import { ServerConfigs, SsoAuthProviders } from '~/helpers/configs';
|
||||
import IconCircleHelp from '~icons/lucide/circle-help';
|
||||
import IconEye from '~icons/lucide/eye';
|
||||
import IconEyeOff from '~icons/lucide/eye-off';
|
||||
|
||||
|
||||
@@ -13,12 +13,22 @@
|
||||
</h4>
|
||||
|
||||
<div class="flex items-center space-y-4 py-4">
|
||||
<HoppSmartToggle
|
||||
:on="dataSharingConfigs.enabled"
|
||||
@change="dataSharingConfigs.enabled = !dataSharingConfigs.enabled"
|
||||
>
|
||||
{{ t('configs.data_sharing.toggle_description') }}
|
||||
</HoppSmartToggle>
|
||||
<div class="flex justify-between w-full">
|
||||
<HoppSmartToggle
|
||||
:on="dataSharingConfigs.enabled"
|
||||
@change="dataSharingConfigs.enabled = !dataSharingConfigs.enabled"
|
||||
>
|
||||
{{ t('configs.data_sharing.toggle_description') }}
|
||||
</HoppSmartToggle>
|
||||
<HoppButtonSecondary
|
||||
v-tippy="{ theme: 'tooltip', allowHTML: true }"
|
||||
blank
|
||||
to="https://docs.hoppscotch.io/documentation/self-host/community-edition/telemetry"
|
||||
:title="t('support.documentation')"
|
||||
:icon="IconHelpCircle"
|
||||
class="rounded hover:bg-primaryDark focus-visible:bg-primaryDark"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<HoppButtonSecondary
|
||||
@@ -30,6 +40,9 @@
|
||||
blank
|
||||
class="w-min my-2"
|
||||
/>
|
||||
<p class="my-1 text-secondaryLight">
|
||||
{{ t('configs.data_sharing.description') }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
@@ -40,6 +53,7 @@ import { computed } from 'vue';
|
||||
import { useI18n } from '~/composables/i18n';
|
||||
import { ServerConfigs } from '~/helpers/configs';
|
||||
import IconShieldQuestion from '~icons/lucide/shield-question';
|
||||
import IconHelpCircle from '~icons/lucide/help-circle';
|
||||
|
||||
const t = useI18n();
|
||||
|
||||
|
||||
@@ -15,12 +15,22 @@
|
||||
|
||||
<div class="space-y-4 py-4">
|
||||
<div class="flex items-center">
|
||||
<HoppSmartToggle
|
||||
:on="smtpConfigs.enabled"
|
||||
@change="smtpConfigs.enabled = !smtpConfigs.enabled"
|
||||
>
|
||||
{{ t('configs.mail_configs.enable_smtp') }}
|
||||
</HoppSmartToggle>
|
||||
<div class="flex justify-between w-full">
|
||||
<HoppSmartToggle
|
||||
:on="smtpConfigs.enabled"
|
||||
@change="smtpConfigs.enabled = !smtpConfigs.enabled"
|
||||
>
|
||||
{{ t('configs.mail_configs.enable_smtp') }}
|
||||
</HoppSmartToggle>
|
||||
<HoppButtonSecondary
|
||||
blank
|
||||
v-tippy="{ theme: 'tooltip', allowHTML: true }"
|
||||
to="https://docs.hoppscotch.io/documentation/self-host/community-edition/prerequisites#email-delivery"
|
||||
:title="t('support.documentation')"
|
||||
:icon="IconHelpCircle"
|
||||
class="rounded hover:bg-primaryDark focus-visible:bg-primaryDark"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="smtpConfigs.enabled" class="ml-12">
|
||||
@@ -94,6 +104,7 @@ import { useI18n } from '~/composables/i18n';
|
||||
import { ServerConfigs } from '~/helpers/configs';
|
||||
import IconEye from '~icons/lucide/eye';
|
||||
import IconEyeOff from '~icons/lucide/eye-off';
|
||||
import IconHelpCircle from '~icons/lucide/help-circle';
|
||||
|
||||
const t = useI18n();
|
||||
|
||||
|
||||
@@ -5,9 +5,18 @@
|
||||
{{ t('infra_tokens.section_title') }}
|
||||
</h4>
|
||||
|
||||
<p class="text-secondaryLight">
|
||||
{{ t('infra_tokens.section_description') }}
|
||||
</p>
|
||||
<div class="flex">
|
||||
<p class="text-secondaryLight">
|
||||
{{ t('infra_tokens.section_description') }}
|
||||
</p>
|
||||
<HoppSmartAnchor
|
||||
blank
|
||||
to="https://docs.hoppscotch.io/documentation/self-host/community-edition/admin-dashboard#infratokens"
|
||||
:label="t('support.more_info')"
|
||||
class="underline ml-1"
|
||||
/>
|
||||
<icon-lucide-arrow-up-right class="underline w-4 h-4" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<HoppButtonSecondary
|
||||
|
||||
@@ -13,18 +13,34 @@
|
||||
/>
|
||||
</template>
|
||||
<template #footer>
|
||||
<span class="flex space-x-2">
|
||||
<HoppButtonPrimary
|
||||
:label="t('users.add_user')"
|
||||
@click="emit('send-invite', email)"
|
||||
/>
|
||||
<HoppButtonSecondary
|
||||
:label="t('users.cancel')"
|
||||
outline
|
||||
filled
|
||||
@click="hideModal"
|
||||
/>
|
||||
</span>
|
||||
<div class="w-full">
|
||||
<p class="text-secondaryLight mb-5 text-center">
|
||||
{{ t('users.invite_users_description') }}
|
||||
</p>
|
||||
|
||||
<div class="flex justify-between">
|
||||
<HoppButtonSecondary
|
||||
v-tippy="{ theme: 'tooltip', allowHTML: true }"
|
||||
to="https://docs.hoppscotch.io/documentation"
|
||||
blank
|
||||
:title="t('support.documentation')"
|
||||
:icon="IconCircleHelp"
|
||||
class="rounded hover:bg-primaryDark focus-visible:bg-primaryDark"
|
||||
/>
|
||||
<span class="flex space-x-2">
|
||||
<HoppButtonPrimary
|
||||
:label="t('users.add_user')"
|
||||
@click="emit('send-invite', email)"
|
||||
/>
|
||||
<HoppButtonSecondary
|
||||
:label="t('users.cancel')"
|
||||
outline
|
||||
filled
|
||||
@click="hideModal"
|
||||
/>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</HoppSmartModal>
|
||||
</template>
|
||||
@@ -32,6 +48,7 @@
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue';
|
||||
import { useI18n } from '~/composables/i18n';
|
||||
import IconCircleHelp from '~icons/lucide/circle-help';
|
||||
|
||||
const t = useI18n();
|
||||
|
||||
|
||||
@@ -1,7 +1,15 @@
|
||||
/*
|
||||
* Type used to send error data to the Fallback catch-all component
|
||||
*/
|
||||
export type ErrorPageData = {
|
||||
message: string;
|
||||
statusCode?: number;
|
||||
};
|
||||
|
||||
/* No cookies were found in the auth request
|
||||
* (AuthService)
|
||||
*/
|
||||
export const COOKIES_NOT_FOUND = 'auth/cookies_not_found' as const;
|
||||
export const COOKIES_NOT_FOUND = '[GraphQL] auth/cookies_not_found' as const;
|
||||
|
||||
export const UNAUTHORIZED = 'Unauthorized' as const;
|
||||
|
||||
|
||||
@@ -1,65 +1,74 @@
|
||||
import { createApp } from 'vue';
|
||||
import urql, { createClient, cacheExchange, fetchExchange } from '@urql/vue';
|
||||
import { authExchange } from '@urql/exchange-auth';
|
||||
import urql, { cacheExchange, createClient, fetchExchange } from '@urql/vue';
|
||||
import { createApp, h } from 'vue';
|
||||
import App from './App.vue';
|
||||
import ErrorComponent from './pages/_.vue';
|
||||
|
||||
// STYLES
|
||||
import '@hoppscotch/ui/style.css';
|
||||
import '../assets/scss/styles.scss';
|
||||
import '../assets/scss/tailwind.scss';
|
||||
import '@fontsource-variable/inter';
|
||||
import '@fontsource-variable/material-symbols-rounded';
|
||||
import '@fontsource-variable/roboto-mono';
|
||||
import '@hoppscotch/ui/style.css';
|
||||
import '../assets/scss/styles.scss';
|
||||
import '../assets/scss/tailwind.scss';
|
||||
// END STYLES
|
||||
|
||||
import { HOPP_MODULES } from './modules';
|
||||
import { auth } from './helpers/auth';
|
||||
import { pipe } from 'fp-ts/function';
|
||||
import * as O from 'fp-ts/Option';
|
||||
import { auth } from './helpers/auth';
|
||||
import { GRAPHQL_UNAUTHORIZED } from './helpers/errors';
|
||||
import { HOPP_MODULES } from './modules';
|
||||
|
||||
// Top-level await is not available in our targets
|
||||
(async () => {
|
||||
const app = createApp(App).use(
|
||||
urql,
|
||||
createClient({
|
||||
try {
|
||||
// Create URQL client
|
||||
const urqlClient = createClient({
|
||||
url: import.meta.env.VITE_BACKEND_GQL_URL,
|
||||
requestPolicy: 'network-only',
|
||||
fetchOptions: () => {
|
||||
return {
|
||||
credentials: 'include',
|
||||
};
|
||||
},
|
||||
fetchOptions: () => ({
|
||||
credentials: 'include',
|
||||
}),
|
||||
exchanges: [
|
||||
cacheExchange,
|
||||
authExchange(async () => {
|
||||
return {
|
||||
addAuthToOperation(operation) {
|
||||
return operation;
|
||||
},
|
||||
|
||||
async refreshAuth() {
|
||||
pipe(
|
||||
await auth.performAuthRefresh(),
|
||||
O.getOrElseW(() => auth.signOutUser(true))
|
||||
);
|
||||
},
|
||||
|
||||
didAuthError(error, _operation) {
|
||||
return error.message === GRAPHQL_UNAUTHORIZED;
|
||||
},
|
||||
};
|
||||
}),
|
||||
authExchange(async () => ({
|
||||
addAuthToOperation(operation) {
|
||||
return operation;
|
||||
},
|
||||
async refreshAuth() {
|
||||
pipe(
|
||||
await auth.performAuthRefresh(),
|
||||
O.getOrElseW(() => auth.signOutUser(true))
|
||||
);
|
||||
},
|
||||
didAuthError(error, _operation) {
|
||||
return error.message === GRAPHQL_UNAUTHORIZED;
|
||||
},
|
||||
})),
|
||||
fetchExchange,
|
||||
],
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
// Initialize auth
|
||||
await auth.performAuthInit();
|
||||
// Initialize auth
|
||||
await auth.performAuthInit();
|
||||
|
||||
// Initialize modules
|
||||
HOPP_MODULES.forEach((mod) => mod.onVueAppInit?.(app));
|
||||
const app = createApp({
|
||||
render: () => h(App),
|
||||
}).use(urql, urqlClient);
|
||||
|
||||
app.mount('#app');
|
||||
// Initialize modules
|
||||
HOPP_MODULES.forEach((mod) => mod.onVueAppInit?.(app));
|
||||
|
||||
app.mount('#app');
|
||||
} catch (error) {
|
||||
// Mount the fallback component in case of an error
|
||||
createApp({
|
||||
render: () =>
|
||||
h(ErrorComponent, {
|
||||
error: {
|
||||
message:
|
||||
'Failed to connect to the backend server, make sure the backend is setup correctly',
|
||||
},
|
||||
}),
|
||||
}).mount('#app');
|
||||
}
|
||||
})();
|
||||
|
||||
@@ -1,41 +1,44 @@
|
||||
<!-- The Catch-All Page -->
|
||||
<!-- Reserved for Critical Errors and 404 ONLY -->
|
||||
<!-- The Fallback Catch-All Page -->
|
||||
<template>
|
||||
<div
|
||||
class="flex flex-col items-center justify-center"
|
||||
:class="{ 'min-h-screen': statusCode !== 404 }"
|
||||
class="flex flex-col items-center h-screen"
|
||||
:class="{ 'min-h-screen': props.error?.statusCode !== 404 }"
|
||||
>
|
||||
<img
|
||||
:src="imgUrl"
|
||||
loading="lazy"
|
||||
class="inline-flex flex-col object-contain object-center mb-2 h-46 w-46"
|
||||
:alt="message"
|
||||
/>
|
||||
<h1 class="mb-2 text-4xl heading">
|
||||
{{ statusCode }}
|
||||
</h1>
|
||||
<p class="mb-4 text-secondaryLight">{{ message }}</p>
|
||||
<p class="mt-4 space-x-2">
|
||||
<HoppButtonSecondary to="/" :icon="IconHome" filled label="Home" />
|
||||
<HoppButtonSecondary
|
||||
:icon="IconRefreshCW"
|
||||
label="Reload"
|
||||
filled
|
||||
@click="reloadApplication"
|
||||
<div class="flex flex-col items-center justify-center h-full">
|
||||
<img
|
||||
:src="imgUrl"
|
||||
loading="lazy"
|
||||
class="inline-flex flex-col object-contain object-center mb-2 h-46 w-46"
|
||||
:alt="message"
|
||||
/>
|
||||
</p>
|
||||
<h1 v-if="props.error?.statusCode" class="mb-2 text-4xl heading">
|
||||
{{ props.error.statusCode }}
|
||||
</h1>
|
||||
<p class="mb-4 text-lg text-secondaryDark">{{ message }}</p>
|
||||
<p class="mt-4 space-x-2">
|
||||
<HoppButtonSecondary
|
||||
to="https://docs.hoppscotch.io/documentation"
|
||||
:icon="IconTextSearch"
|
||||
filled
|
||||
blank
|
||||
label="Documentation"
|
||||
/>
|
||||
<HoppButtonSecondary
|
||||
:icon="IconRefreshCW"
|
||||
label="Reload"
|
||||
filled
|
||||
@click="reloadApplication"
|
||||
/>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import IconRefreshCW from '~icons/lucide/refresh-cw';
|
||||
import IconHome from '~icons/lucide/home';
|
||||
import { PropType, computed } from 'vue';
|
||||
|
||||
export type ErrorPageData = {
|
||||
message: string;
|
||||
statusCode: number;
|
||||
};
|
||||
import { ErrorPageData } from '~/helpers/errors';
|
||||
import IconRefreshCW from '~icons/lucide/refresh-cw';
|
||||
import IconTextSearch from '~icons/lucide/text-search';
|
||||
|
||||
const props = defineProps({
|
||||
error: {
|
||||
@@ -44,9 +47,7 @@ const props = defineProps({
|
||||
},
|
||||
});
|
||||
|
||||
const imgUrl = `${import.meta.env.BASE_URL}images/youre_lost.svg`
|
||||
|
||||
const statusCode = computed(() => props.error?.statusCode ?? 404);
|
||||
const imgUrl = `${import.meta.env.BASE_URL}images/youre_lost.svg`;
|
||||
|
||||
const message = computed(
|
||||
() => props.error?.message ?? 'The page you are looking for does not exist.'
|
||||
|
||||
@@ -7,9 +7,14 @@
|
||||
</div>
|
||||
|
||||
<div class="flex justify-between items-center py-6">
|
||||
<h3 class="text-lg font-bold text-accentContrast">
|
||||
{{ t('users.pending_invites') }}
|
||||
</h3>
|
||||
<div>
|
||||
<h3 class="text-lg font-bold text-accentContrast">
|
||||
{{ t('users.pending_invites') }}
|
||||
</h3>
|
||||
<p class="my-1 text-secondaryLight">
|
||||
{{ t('users.pending_invites_description') }}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<HoppButtonSecondary
|
||||
v-if="pendingInvites?.length"
|
||||
|
||||
Reference in New Issue
Block a user