fix: all admin client-side fetches and redirects use configured adminPath
This commit is contained in:
@@ -4,18 +4,19 @@ import { loadConfig } from '$lib/server/data';
|
|||||||
import type { LayoutServerLoad } from './$types';
|
import type { LayoutServerLoad } from './$types';
|
||||||
|
|
||||||
export const load: LayoutServerLoad = ({ request, url }) => {
|
export const load: LayoutServerLoad = ({ request, url }) => {
|
||||||
|
const adminPath = (loadConfig().adminPath ?? 'admin').replace(/^\/|\/$/g, '') || 'admin';
|
||||||
|
|
||||||
if (url.pathname.endsWith('/login') || url.pathname.endsWith('/login/')) {
|
if (url.pathname.endsWith('/login') || url.pathname.endsWith('/login/')) {
|
||||||
return { authenticated: false };
|
return { authenticated: false, adminPath };
|
||||||
}
|
}
|
||||||
|
|
||||||
if (url.pathname.includes('/api/')) {
|
if (url.pathname.includes('/api/')) {
|
||||||
return {};
|
return { adminPath };
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!authRequest(request)) {
|
if (!authRequest(request)) {
|
||||||
const adminPath = (loadConfig().adminPath ?? 'admin').replace(/^\/|\/$/g, '');
|
|
||||||
throw redirect(302, `/${adminPath}/login`);
|
throw redirect(302, `/${adminPath}/login`);
|
||||||
}
|
}
|
||||||
|
|
||||||
return { authenticated: true };
|
return { authenticated: true, adminPath };
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,5 +1,8 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
let { children } = $props();
|
import { setContext } from 'svelte';
|
||||||
|
let { children, data } = $props();
|
||||||
|
// Make adminPath available to all child components via context
|
||||||
|
setContext('adminPath', data.adminPath ?? 'admin');
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="admin-layout">
|
<div class="admin-layout">
|
||||||
|
|||||||
@@ -1,6 +1,10 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
|
import { getContext } from 'svelte';
|
||||||
import type { Unit, Group, UnitsData } from '$lib/types';
|
import type { Unit, Group, UnitsData } from '$lib/types';
|
||||||
|
|
||||||
|
const adminPath = getContext<string>('adminPath') ?? 'admin';
|
||||||
|
const api = `/${adminPath}/api`;
|
||||||
|
|
||||||
// ── State ────────────────────────────────────────────────────────────────────
|
// ── State ────────────────────────────────────────────────────────────────────
|
||||||
let unitsData = $state<UnitsData>({ units: [], groups: [] });
|
let unitsData = $state<UnitsData>({ units: [], groups: [] });
|
||||||
let selectedItem = $state<{ type: 'unit' | 'group'; id: string } | null>(null);
|
let selectedItem = $state<{ type: 'unit' | 'group'; id: string } | null>(null);
|
||||||
@@ -111,7 +115,7 @@
|
|||||||
dragGroupId = null;
|
dragGroupId = null;
|
||||||
dragOverGroupId = null;
|
dragOverGroupId = null;
|
||||||
// Persist
|
// Persist
|
||||||
fetch('/admin/api/order', {
|
fetch('${api}/order', {
|
||||||
method: 'PUT',
|
method: 'PUT',
|
||||||
headers: { 'Content-Type': 'application/json' },
|
headers: { 'Content-Type': 'application/json' },
|
||||||
body: JSON.stringify({ groups: groups.map((g) => g.id) })
|
body: JSON.stringify({ groups: groups.map((g) => g.id) })
|
||||||
@@ -165,7 +169,7 @@
|
|||||||
// Build ordered IDs for this group
|
// Build ordered IDs for this group
|
||||||
const groupKey = groupId ?? 'null';
|
const groupKey = groupId ?? 'null';
|
||||||
const groupUnitIds = units.filter((u) => (u.group ?? null) === groupId).map((u) => u.id);
|
const groupUnitIds = units.filter((u) => (u.group ?? null) === groupId).map((u) => u.id);
|
||||||
fetch('/admin/api/order', {
|
fetch('${api}/order', {
|
||||||
method: 'PUT',
|
method: 'PUT',
|
||||||
headers: { 'Content-Type': 'application/json' },
|
headers: { 'Content-Type': 'application/json' },
|
||||||
body: JSON.stringify({ units: { [groupKey]: groupUnitIds } })
|
body: JSON.stringify({ units: { [groupKey]: groupUnitIds } })
|
||||||
@@ -181,8 +185,8 @@
|
|||||||
// ── Load data ────────────────────────────────────────────────────────────────
|
// ── Load data ────────────────────────────────────────────────────────────────
|
||||||
async function loadData() {
|
async function loadData() {
|
||||||
const [res, gres] = await Promise.all([
|
const [res, gres] = await Promise.all([
|
||||||
fetch('/admin/api/units'),
|
fetch('${api}/units'),
|
||||||
fetch('/admin/api/groups')
|
fetch('${api}/groups')
|
||||||
]);
|
]);
|
||||||
if (!res.ok) return;
|
if (!res.ok) return;
|
||||||
const units: Unit[] = await res.json();
|
const units: Unit[] = await res.json();
|
||||||
@@ -287,12 +291,12 @@
|
|||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
const res = unitFormNew
|
const res = unitFormNew
|
||||||
? await fetch('/admin/api/units', {
|
? await fetch('${api}/units', {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: { 'Content-Type': 'application/json' },
|
headers: { 'Content-Type': 'application/json' },
|
||||||
body: JSON.stringify(unitForm)
|
body: JSON.stringify(unitForm)
|
||||||
})
|
})
|
||||||
: await fetch(`/admin/api/units/${selectedItem?.id}`, {
|
: await fetch(`${api}/units/${selectedItem?.id}`, {
|
||||||
method: 'PUT',
|
method: 'PUT',
|
||||||
headers: { 'Content-Type': 'application/json' },
|
headers: { 'Content-Type': 'application/json' },
|
||||||
body: JSON.stringify(unitForm)
|
body: JSON.stringify(unitForm)
|
||||||
@@ -318,12 +322,12 @@
|
|||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
const res = groupFormNew
|
const res = groupFormNew
|
||||||
? await fetch('/admin/api/groups', {
|
? await fetch('${api}/groups', {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: { 'Content-Type': 'application/json' },
|
headers: { 'Content-Type': 'application/json' },
|
||||||
body: JSON.stringify(groupForm)
|
body: JSON.stringify(groupForm)
|
||||||
})
|
})
|
||||||
: await fetch(`/admin/api/groups/${selectedItem?.id}`, {
|
: await fetch(`${api}/groups/${selectedItem?.id}`, {
|
||||||
method: 'PUT',
|
method: 'PUT',
|
||||||
headers: { 'Content-Type': 'application/json' },
|
headers: { 'Content-Type': 'application/json' },
|
||||||
body: JSON.stringify(groupForm)
|
body: JSON.stringify(groupForm)
|
||||||
@@ -410,7 +414,7 @@
|
|||||||
const targetGroup = bulkMoveTargetGroupId === '__orphan__' ? null : bulkMoveTargetGroupId;
|
const targetGroup = bulkMoveTargetGroupId === '__orphan__' ? null : bulkMoveTargetGroupId;
|
||||||
const results = await Promise.all(
|
const results = await Promise.all(
|
||||||
[...checkedUnitIds].map((unitId) =>
|
[...checkedUnitIds].map((unitId) =>
|
||||||
fetch(`/admin/api/units/${unitId}`, {
|
fetch(`${api}/units/${unitId}`, {
|
||||||
method: 'PUT',
|
method: 'PUT',
|
||||||
headers: { 'Content-Type': 'application/json' },
|
headers: { 'Content-Type': 'application/json' },
|
||||||
body: JSON.stringify({ group: targetGroup })
|
body: JSON.stringify({ group: targetGroup })
|
||||||
@@ -432,7 +436,7 @@
|
|||||||
formError = null;
|
formError = null;
|
||||||
const ids = [...checkedUnitIds];
|
const ids = [...checkedUnitIds];
|
||||||
const results = await Promise.all(
|
const results = await Promise.all(
|
||||||
ids.map((unitId) => fetch(`/admin/api/units/${unitId}`, { method: 'DELETE' }))
|
ids.map((unitId) => fetch(`${api}/units/${unitId}`, { method: 'DELETE' }))
|
||||||
);
|
);
|
||||||
const failed = results.filter((r) => !r.ok);
|
const failed = results.filter((r) => !r.ok);
|
||||||
if (failed.length) formError = `${failed.length} unit(s) failed to delete`;
|
if (failed.length) formError = `${failed.length} unit(s) failed to delete`;
|
||||||
@@ -455,12 +459,12 @@
|
|||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
const res = unitFormNew
|
const res = unitFormNew
|
||||||
? await fetch('/admin/api/units', {
|
? await fetch('${api}/units', {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: { 'Content-Type': 'application/json' },
|
headers: { 'Content-Type': 'application/json' },
|
||||||
body: JSON.stringify(unitForm)
|
body: JSON.stringify(unitForm)
|
||||||
})
|
})
|
||||||
: await fetch(`/admin/api/units/${selectedItem?.id}`, {
|
: await fetch(`${api}/units/${selectedItem?.id}`, {
|
||||||
method: 'PUT',
|
method: 'PUT',
|
||||||
headers: { 'Content-Type': 'application/json' },
|
headers: { 'Content-Type': 'application/json' },
|
||||||
body: JSON.stringify(unitForm)
|
body: JSON.stringify(unitForm)
|
||||||
@@ -484,7 +488,7 @@
|
|||||||
if (!selectedItem || selectedItem.type !== 'unit') return;
|
if (!selectedItem || selectedItem.type !== 'unit') return;
|
||||||
if (!confirm(`Delete unit "${unitForm.label}"?`)) return;
|
if (!confirm(`Delete unit "${unitForm.label}"?`)) return;
|
||||||
try {
|
try {
|
||||||
const res = await fetch(`/admin/api/units/${selectedItem.id}`, { method: 'DELETE' });
|
const res = await fetch(`${api}/units/${selectedItem.id}`, { method: 'DELETE' });
|
||||||
if (!res.ok) { formError = await extractError(res, 'Delete failed'); return; }
|
if (!res.ok) { formError = await extractError(res, 'Delete failed'); return; }
|
||||||
selectedItem = null;
|
selectedItem = null;
|
||||||
unitForm = {};
|
unitForm = {};
|
||||||
@@ -505,12 +509,12 @@
|
|||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
const res = groupFormNew
|
const res = groupFormNew
|
||||||
? await fetch('/admin/api/groups', {
|
? await fetch('${api}/groups', {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: { 'Content-Type': 'application/json' },
|
headers: { 'Content-Type': 'application/json' },
|
||||||
body: JSON.stringify(groupForm)
|
body: JSON.stringify(groupForm)
|
||||||
})
|
})
|
||||||
: await fetch(`/admin/api/groups/${selectedItem?.id}`, {
|
: await fetch(`${api}/groups/${selectedItem?.id}`, {
|
||||||
method: 'PUT',
|
method: 'PUT',
|
||||||
headers: { 'Content-Type': 'application/json' },
|
headers: { 'Content-Type': 'application/json' },
|
||||||
body: JSON.stringify(groupForm)
|
body: JSON.stringify(groupForm)
|
||||||
@@ -547,7 +551,7 @@
|
|||||||
? { action: 'orphan' }
|
? { action: 'orphan' }
|
||||||
: { action: 'reassign', targetGroupId: deleteGroupTargetId, toBaseAction: deleteGroupToBaseAction };
|
: { action: 'reassign', targetGroupId: deleteGroupTargetId, toBaseAction: deleteGroupToBaseAction };
|
||||||
try {
|
try {
|
||||||
const res = await fetch(`/admin/api/groups/${deleteGroupTarget.id}`, {
|
const res = await fetch(`${api}/groups/${deleteGroupTarget.id}`, {
|
||||||
method: 'DELETE',
|
method: 'DELETE',
|
||||||
headers: { 'Content-Type': 'application/json' },
|
headers: { 'Content-Type': 'application/json' },
|
||||||
body: JSON.stringify(body)
|
body: JSON.stringify(body)
|
||||||
@@ -572,8 +576,8 @@
|
|||||||
});
|
});
|
||||||
|
|
||||||
async function logout() {
|
async function logout() {
|
||||||
await fetch('/admin/api/auth/logout', { method: 'POST' });
|
await fetch(`${api}/auth/logout`, { method: 'POST' });
|
||||||
window.location.href = '/admin/login';
|
window.location.href = `/${adminPath}/login`;
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,8 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { goto } from '$app/navigation';
|
import { goto } from '$app/navigation';
|
||||||
|
import { getContext } from 'svelte';
|
||||||
|
|
||||||
|
const adminPath = getContext<string>('adminPath') ?? 'admin';
|
||||||
|
|
||||||
let password = $state('');
|
let password = $state('');
|
||||||
let error = $state<string | null>(null);
|
let error = $state<string | null>(null);
|
||||||
@@ -11,14 +14,14 @@
|
|||||||
loading = true;
|
loading = true;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const res = await fetch('/admin/api/auth/login', {
|
const res = await fetch(`/${adminPath}/api/auth/login`, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: { 'Content-Type': 'application/json' },
|
headers: { 'Content-Type': 'application/json' },
|
||||||
body: JSON.stringify({ password })
|
body: JSON.stringify({ password })
|
||||||
});
|
});
|
||||||
|
|
||||||
if (res.ok) {
|
if (res.ok) {
|
||||||
await goto('/admin');
|
await goto(`/${adminPath}`);
|
||||||
} else {
|
} else {
|
||||||
const data = await res.json().catch(() => ({}));
|
const data = await res.json().catch(() => ({}));
|
||||||
error = data.error ?? 'Incorrect password';
|
error = data.error ?? 'Incorrect password';
|
||||||
|
|||||||
Reference in New Issue
Block a user