mirror of
https://github.com/unraid/api.git
synced 2026-01-03 15:09:48 -06:00
refactor(navigation): replace window.location with navigate helper for external URL handling across multiple components
This commit is contained in:
@@ -3,6 +3,7 @@ import { defineStore, storeToRefs } from 'pinia';
|
||||
import { useSessionStorage } from '@vueuse/core';
|
||||
|
||||
import { ACTIVATION_CODE_MODAL_HIDDEN_STORAGE_KEY } from '~/consts';
|
||||
import { navigate } from '~/helpers/external-navigation';
|
||||
|
||||
import { useActivationCodeDataStore } from '~/components/Activation/store/activationCodeData';
|
||||
import { useCallbackActionsStore } from '~/store/callbackActions';
|
||||
@@ -66,7 +67,7 @@ export const useActivationCodeModalStore = defineStore('activationCodeModal', ()
|
||||
if (sequenceIndex === keySequence.length) {
|
||||
setIsHidden(true);
|
||||
// Redirect only if explicitly hidden via konami code, not just closed normally
|
||||
window.location.href = '/Tools/Registration';
|
||||
navigate('/Tools/Registration');
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -35,6 +35,7 @@ import {
|
||||
TooltipProvider,
|
||||
TooltipTrigger,
|
||||
} from '@unraid/ui';
|
||||
import { navigate } from '~/helpers/external-navigation';
|
||||
import { extractGraphQLErrorMessage } from '~/helpers/functions';
|
||||
|
||||
import type { ApiKeyFragment, AuthAction, Role } from '~/composables/gql/graphql';
|
||||
@@ -165,7 +166,7 @@ function applyTemplate() {
|
||||
params.forEach((value, key) => {
|
||||
authUrl.searchParams.append(key, value);
|
||||
});
|
||||
window.location.href = authUrl.toString();
|
||||
navigate(authUrl.toString());
|
||||
|
||||
cancelTemplateInput();
|
||||
} catch (_err) {
|
||||
|
||||
@@ -4,6 +4,7 @@ import { storeToRefs } from 'pinia';
|
||||
|
||||
import { ClipboardDocumentIcon, EyeIcon, EyeSlashIcon } from '@heroicons/vue/24/outline';
|
||||
import { Button, Input } from '@unraid/ui';
|
||||
import { navigate } from '~/helpers/external-navigation';
|
||||
|
||||
import ApiKeyCreate from '~/components/ApiKey/ApiKeyCreate.vue';
|
||||
import { useAuthorizationLink } from '~/composables/useAuthorizationLink.js';
|
||||
@@ -93,12 +94,12 @@ const deny = () => {
|
||||
if (hasValidRedirectUri.value) {
|
||||
try {
|
||||
const url = buildCallbackUrl(undefined, 'access_denied');
|
||||
window.location.href = url;
|
||||
navigate(url);
|
||||
} catch {
|
||||
window.location.href = '/';
|
||||
navigate('/');
|
||||
}
|
||||
} else {
|
||||
window.location.href = '/';
|
||||
navigate('/');
|
||||
}
|
||||
};
|
||||
|
||||
@@ -108,7 +109,7 @@ const returnToApp = () => {
|
||||
|
||||
try {
|
||||
const url = buildCallbackUrl(createdApiKey.value, undefined);
|
||||
window.location.href = url;
|
||||
navigate(url);
|
||||
} catch (_err) {
|
||||
error.value = 'Failed to redirect back to application';
|
||||
}
|
||||
|
||||
@@ -22,6 +22,7 @@ import { useDockerConsoleSessions } from '@/composables/useDockerConsoleSessions
|
||||
import { useDockerEditNavigation } from '@/composables/useDockerEditNavigation';
|
||||
import { stripLeadingSlash } from '@/utils/docker';
|
||||
import { useAutoAnimate } from '@formkit/auto-animate/vue';
|
||||
import { navigate } from '@/helpers/external-navigation';
|
||||
|
||||
import type {
|
||||
DockerPortConflictsResult,
|
||||
@@ -258,7 +259,7 @@ function handleAddContainerClick() {
|
||||
const sanitizedPath = rawPath.replace(/\?.*$/, '').replace(/\/+$/, '');
|
||||
const withoutAdd = sanitizedPath.replace(/\/AddContainer$/i, '');
|
||||
const targetPath = withoutAdd ? `${withoutAdd}/AddContainer` : '/AddContainer';
|
||||
window.location.assign(targetPath);
|
||||
navigate(targetPath);
|
||||
}
|
||||
|
||||
async function refreshContainers() {
|
||||
|
||||
@@ -21,6 +21,7 @@ import {
|
||||
DropdownMenuSeparator,
|
||||
DropdownMenuTrigger,
|
||||
} from '@unraid/ui';
|
||||
import { navigate } from '~/helpers/external-navigation';
|
||||
import { getReleaseNotesUrl, WEBGUI_TOOLS_DOWNGRADE, WEBGUI_TOOLS_UPDATE } from '~/helpers/urls';
|
||||
|
||||
import { useActivationCodeDataStore } from '~/components/Activation/store/activationCodeData';
|
||||
@@ -126,7 +127,7 @@ const handleUpdateStatusClick = () => {
|
||||
if (updateOsStatus.value.click) {
|
||||
updateOsStatus.value.click();
|
||||
} else if (updateOsStatus.value.href) {
|
||||
window.location.href = updateOsStatus.value.href;
|
||||
navigate(updateOsStatus.value.href);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
import { computed, reactive, ref, watch } from 'vue';
|
||||
import { useMutation, useQuery, useSubscription } from '@vue/apollo-composable';
|
||||
|
||||
import { navigate } from '~/helpers/external-navigation';
|
||||
|
||||
import type { FragmentType } from '~/composables/gql';
|
||||
import type {
|
||||
NotificationFragmentFragment,
|
||||
@@ -171,7 +173,7 @@ onNotificationAdded(({ data }) => {
|
||||
label: 'Open',
|
||||
onClick: () => {
|
||||
if (notification.link) {
|
||||
window.location.assign(notification.link);
|
||||
navigate(notification.link);
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
@@ -5,6 +5,7 @@ import { useMutation } from '@vue/apollo-composable';
|
||||
import { computedAsync } from '@vueuse/core';
|
||||
|
||||
import { Markdown } from '@/helpers/markdown';
|
||||
import { navigate } from '~/helpers/external-navigation';
|
||||
|
||||
import type { NotificationFragmentFragment } from '~/composables/gql/graphql';
|
||||
|
||||
@@ -66,7 +67,7 @@ const mutationError = computed(() => {
|
||||
|
||||
const openLink = () => {
|
||||
if (props.link) {
|
||||
window.location.assign(props.link);
|
||||
navigate(props.link);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -3,6 +3,8 @@ import { computed, ref } from 'vue';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { useMutation, useQuery, useSubscription } from '@vue/apollo-composable';
|
||||
|
||||
import { navigate } from '~/helpers/external-navigation';
|
||||
|
||||
import ConfirmDialog from '~/components/ConfirmDialog.vue';
|
||||
import {
|
||||
archiveAllNotifications,
|
||||
@@ -104,7 +106,7 @@ onNotificationAdded(({ data }) => {
|
||||
const color = funcMapping[notif.importance];
|
||||
const createOpener = () => ({
|
||||
label: t('notifications.sidebar.toastOpen'),
|
||||
onClick: () => window.location.assign(notif.link as string),
|
||||
onClick: () => navigate(notif.link as string),
|
||||
});
|
||||
|
||||
requestAnimationFrame(() =>
|
||||
@@ -117,10 +119,6 @@ onNotificationAdded(({ data }) => {
|
||||
);
|
||||
});
|
||||
|
||||
const openSettings = () => {
|
||||
window.location.assign('/Settings/Notifications');
|
||||
};
|
||||
|
||||
const overview = computed(() => {
|
||||
if (!result.value) {
|
||||
return;
|
||||
@@ -234,7 +232,7 @@ const tabs = computed(() => [
|
||||
variant="ghost"
|
||||
color="neutral"
|
||||
icon="i-heroicons-cog-6-tooth-20-solid"
|
||||
@click="openSettings"
|
||||
@click="navigate('/Settings/Notifications')"
|
||||
/>
|
||||
</UTooltip>
|
||||
</div>
|
||||
|
||||
@@ -13,6 +13,7 @@ import {
|
||||
XCircleIcon,
|
||||
} from '@heroicons/vue/24/solid';
|
||||
import { Badge, BrandLoading, Button } from '@unraid/ui';
|
||||
import { navigate } from '~/helpers/external-navigation';
|
||||
import { WEBGUI_TOOLS_REGISTRATION } from '~/helpers/urls';
|
||||
|
||||
import useDateTimeHelper from '~/composables/dateTime';
|
||||
@@ -113,7 +114,7 @@ const checkButton = computed(() => {
|
||||
|
||||
const navigateToRegistration = () => {
|
||||
if (typeof window !== 'undefined') {
|
||||
window.location.href = WEBGUI_TOOLS_REGISTRATION;
|
||||
navigate(WEBGUI_TOOLS_REGISTRATION);
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
import { onMounted, ref } from 'vue';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
|
||||
import { navigate } from '~/helpers/external-navigation';
|
||||
|
||||
export type AuthState = 'loading' | 'idle' | 'error';
|
||||
|
||||
export function useSsoAuth() {
|
||||
@@ -75,7 +77,7 @@ export function useSsoAuth() {
|
||||
|
||||
// Redirect to OIDC authorization endpoint with state token and redirect URI
|
||||
const authUrl = `/graphql/api/auth/oidc/authorize/${encodeURIComponent(providerId)}?state=${encodeURIComponent(state)}&redirect_uri=${encodeURIComponent(redirectUri)}`;
|
||||
window.location.href = authUrl;
|
||||
navigate(authUrl);
|
||||
};
|
||||
|
||||
const handleOAuthCallback = async () => {
|
||||
@@ -120,7 +122,7 @@ export function useSsoAuth() {
|
||||
|
||||
// Redirect to our OIDC callback endpoint to exchange the code
|
||||
const callbackUrl = `/graphql/api/auth/oidc/callback?code=${encodeURIComponent(code)}&state=${encodeURIComponent(state)}`;
|
||||
window.location.href = callbackUrl;
|
||||
navigate(callbackUrl);
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { featureFlags } from '@/helpers/env';
|
||||
import { navigate } from '@/helpers/external-navigation';
|
||||
|
||||
import type { DockerContainer } from '@/composables/gql/graphql';
|
||||
|
||||
@@ -39,7 +40,7 @@ export function useDockerEditNavigation() {
|
||||
if (!url) {
|
||||
return false;
|
||||
}
|
||||
window.location.href = url;
|
||||
navigate(url);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
28
web/src/helpers/external-navigation.ts
Normal file
28
web/src/helpers/external-navigation.ts
Normal file
@@ -0,0 +1,28 @@
|
||||
/**
|
||||
* Helper to ensure external URLs have a protocol
|
||||
*/
|
||||
function normalizeUrl(url: string): string {
|
||||
// If it starts with www. and doesn't have a protocol, add https://
|
||||
if (url.startsWith('www.') && !url.match(/^https?:\/\//)) {
|
||||
return `https://${url}`;
|
||||
}
|
||||
return url;
|
||||
}
|
||||
|
||||
/**
|
||||
* Navigates to a new URL.
|
||||
* Equivalent to window.location.assign()
|
||||
*/
|
||||
export const navigate = (url: string) => {
|
||||
const target = normalizeUrl(url);
|
||||
window.location.assign(target);
|
||||
};
|
||||
|
||||
/**
|
||||
* Navigates to a new URL without keeping the current page in history.
|
||||
* Equivalent to window.location.replace()
|
||||
*/
|
||||
export const replace = (url: string) => {
|
||||
const target = normalizeUrl(url);
|
||||
window.location.replace(target);
|
||||
};
|
||||
@@ -2,6 +2,7 @@ import { computed, ref } from 'vue';
|
||||
import { defineStore } from 'pinia';
|
||||
|
||||
import { WEBGUI_REDIRECT } from '~/helpers/urls';
|
||||
import { navigate } from '~/helpers/external-navigation';
|
||||
import dayjs, { extend } from 'dayjs';
|
||||
import customParseFormat from 'dayjs/plugin/customParseFormat';
|
||||
import relativeTime from 'dayjs/plugin/relativeTime';
|
||||
@@ -109,7 +110,7 @@ export const useUpdateOsStore = defineStore('updateOs', () => {
|
||||
// if current path is /Tools/Update, then we should redirect to /Tools
|
||||
// otherwise it will redirect to the account update os page.
|
||||
if (window.location.pathname === '/Tools/Update') {
|
||||
window.location.href = '/Tools';
|
||||
navigate('/Tools');
|
||||
return;
|
||||
}
|
||||
// otherwise refresh the page
|
||||
|
||||
Reference in New Issue
Block a user