From 1ee9328bf9c5cd4c346674f65b5c978fb3fb3821 Mon Sep 17 00:00:00 2001 From: cproudlock Date: Wed, 29 Jul 2026 10:25:46 -0400 Subject: [PATCH] applications: fix relative installer/link hrefs + serve /installers via IIS Two problems with application download/launch/doc links: 1. Stored paths like 'installers/Foo.exe' are relative, so an on /shopdb/applications/6 resolved to /shopdb/applications/installers/Foo.exe. New basePath.fileHref() mounts a relative path under the app base (-> /shopdb/installers/Foo.exe) while leaving full URLs and UNC/file paths untouched. Applied to installpath, applicationlink, and documentationpath in the list and detail views. 2. Even the correct /shopdb/installers/Foo.exe 404s: httpPlatformHandler is path="*", so IIS forwards it to Flask, which has no such route. Add a web.config that clears the handler and serves that subpath as IIS static (with .exe/.msi MIME), from a physical APP_ROOT\installers folder. --- deploy/windows/web.config | 35 +++++++++++++++++++ frontend/src/utils/basePath.js | 18 ++++++++++ .../frontend/views/ApplicationDetail.vue | 7 ++-- .../frontend/views/ApplicationsList.vue | 7 ++-- 4 files changed, 61 insertions(+), 6 deletions(-) diff --git a/deploy/windows/web.config b/deploy/windows/web.config index 4821797..b5b0704 100644 --- a/deploy/windows/web.config +++ b/deploy/windows/web.config @@ -76,4 +76,39 @@ --> + + + + + + + + + + + + + + + + diff --git a/frontend/src/utils/basePath.js b/frontend/src/utils/basePath.js index b77cbbb..a9db043 100644 --- a/frontend/src/utils/basePath.js +++ b/frontend/src/utils/basePath.js @@ -12,6 +12,24 @@ export function withBase(path) { return BASE_URL + String(path).replace(/^\//, '') } +// Resolve a stored file/link path for use in an . Full URLs (http, +// https, file:, mailto:, ...) and UNC / protocol-relative paths (\\server, +// //host) open as-is. A path relative to the app (e.g. 'installers/x.exe' or +// './installers/x.exe') is mounted under the base so it does NOT resolve +// against the current route - '/shopdb/applications/6' + 'installers/x.exe' +// wrongly became '/shopdb/applications/installers/x.exe'. +export function fileHref(path) { + if (!path) return null + const trimmed = String(path).trim() + if (!trimmed) return null + if (/^[a-z][a-z0-9+.-]*:/i.test(trimmed) + || trimmed.startsWith('\\\\') + || trimmed.startsWith('//')) { + return trimmed + } + return withBase('/' + trimmed.replace(/^(\.?\/)+/, '')) +} + // Inverse of withBase: turn a browser pathname (which includes the mount // base, e.g. '/ops/computers') into a router path ('/computers'). Router // navigation already applies the base; feeding it an un-stripped pathname diff --git a/plugins/applications/frontend/views/ApplicationDetail.vue b/plugins/applications/frontend/views/ApplicationDetail.vue index aa5da35..833df5c 100644 --- a/plugins/applications/frontend/views/ApplicationDetail.vue +++ b/plugins/applications/frontend/views/ApplicationDetail.vue @@ -31,13 +31,13 @@ Hidden @@ -167,6 +167,7 @@ import { useRoute } from 'vue-router' import { applicationsApi } from '@/api' import { getContactEmailDomain } from '@/utils/siteSettings' import { sanitizeNotesHtml } from '@/utils/sanitizeHtml' +import { fileHref } from '@/utils/basePath' const route = useRoute() diff --git a/plugins/applications/frontend/views/ApplicationsList.vue b/plugins/applications/frontend/views/ApplicationsList.vue index 3a7c6ec..5116c12 100644 --- a/plugins/applications/frontend/views/ApplicationsList.vue +++ b/plugins/applications/frontend/views/ApplicationsList.vue @@ -41,15 +41,15 @@ - + - + 🔗 - + 📄 @@ -102,6 +102,7 @@ import { ref, onMounted } from 'vue' import { applicationsApi } from '@/api' import PaginationBar from '@/components/PaginationBar.vue' import { useListQuery } from '@/composables/listQuery' +import { fileHref } from '@/utils/basePath' const applications = ref([]) const loading = ref(true)