Skip to content
19 changes: 19 additions & 0 deletions migrations/20260417120000_add_page_is_internal.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import type { Knex } from "knex";

export async function up(knex: Knex): Promise<void> {
const hasColumn = await knex.schema.hasColumn("pages", "page_is_internal");
if (!hasColumn) {
await knex.schema.table("pages", (table) => {
table.integer("page_is_internal").notNullable().defaultTo(0);
});
}
}

export async function down(knex: Knex): Promise<void> {
const hasColumn = await knex.schema.hasColumn("pages", "page_is_internal");
if (hasColumn) {
await knex.schema.table("pages", (table) => {
table.dropColumn("page_is_internal");
});
}
}
2 changes: 1 addition & 1 deletion src/lib/components/PageSelector.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@
>
<Spinner class="h-4 w-4" />
</Button>
{:else if pages.length > 0}
{:else if pages.length > 1}
<DropdownMenu.Root>
<DropdownMenu.Trigger>
{#snippet child({ props })}
Expand Down
21 changes: 13 additions & 8 deletions src/lib/server/api-server/pages/get.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,20 @@ import { json } from "@sveltejs/kit";
import type { APIServerRequest } from "$lib/server/types/api-server";
import { GetAllPages } from "$lib/server/controllers/pagesController";
import { GetSiteDataByKey } from "$lib/server/controllers/siteDataController";
import { GetLoggedInSession } from "$lib/server/controllers/userController";
import type { PageNavItem } from "$lib/server/controllers/dashboardController";
import type { PageOrderingSettings } from "$lib/types/site";

/**
* GET /dashboard-apis/pages
* Returns all pages as PageNavItem[] (page_title, page_path)
* Respects pageOrderingSettings if enabled
* Respects pageOrderingSettings if enabled.
* Pages with page_is_internal are only shown to logged-in users.
*/
export default async function get(_req: APIServerRequest): Promise<Response> {
export default async function get(req: APIServerRequest): Promise<Response> {
const allPagesData = await GetAllPages();
const pageOrderingSettings = (await GetSiteDataByKey("pageOrderingSettings")) as PageOrderingSettings | null;
const loggedInUser = await GetLoggedInSession(req.cookies);

let orderedPages = allPagesData;

Expand All @@ -30,11 +33,13 @@ export default async function get(_req: APIServerRequest): Promise<Response> {
});
}

const pages: PageNavItem[] = orderedPages.map((p) => ({
page_title: p.page_title,
page_path: p.page_path,
page_header: p.page_header,
page_logo: p.page_logo,
}));
const pages: PageNavItem[] = orderedPages
.filter((p) => !p.page_is_internal || !!loggedInUser)
.map((p) => ({
page_title: p.page_title,
page_path: p.page_path,
page_header: p.page_header,
page_logo: p.page_logo,
}));
return json(pages);
}
11 changes: 11 additions & 0 deletions src/lib/server/controllers/dashboardController.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
import db from "../db/db.js";
import { redirect } from "@sveltejs/kit";
import { GetMinuteStartNowTimestampUTC, BeginningOfMinute, BeginningOfDay } from "../tool.js";
import { GetPageByPathWithMonitors, GetLatestMonitoringDataAllActive } from "./controller.js";
import { GetMonitorsParsed } from "./monitorsController.js";
import { GetStatusSummary, GetStatusBgColor } from "../../clientTools";
import serverResolve from "../resolver.js";

import type {
IncidentRecord,
Expand Down Expand Up @@ -309,6 +311,15 @@ export const GetPageDashboardData = async (
}

const { page: pageDetails, monitors: pageMonitors } = pageData;

// Check page access restrictions
if (pageDetails.page_is_internal) {
if (!layoutData.loggedInUser) {
const next = encodeURIComponent(serverResolve(pagePath.startsWith("/") ? pagePath : "/" + pagePath));
throw redirect(302, serverResolve("/account/signin") + "?next=" + next);
}
}
Comment thread
greptile-apps[bot] marked this conversation as resolved.

Comment thread
coderabbitai[bot] marked this conversation as resolved.
const monitorTags = pageMonitors.map((pm) => pm.monitor_tag);

// Parse page settings with defaults
Expand Down
1 change: 1 addition & 0 deletions src/lib/server/db/repositories/pages.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ export class PagesRepository extends BaseRepository {
page_subheader: data.page_subheader,
page_logo: data.page_logo,
page_settings_json: data.page_settings_json,
page_is_internal: data.page_is_internal ?? 0,
created_at: this.knex.fn.now(),
updated_at: this.knex.fn.now(),
};
Expand Down
2 changes: 2 additions & 0 deletions src/lib/server/types/db.ts
Original file line number Diff line number Diff line change
Expand Up @@ -456,6 +456,7 @@ export interface PageRecord {
page_subheader: string | null;
page_logo: string | null;
page_settings_json: string | null;
page_is_internal: number;
created_at: Date;
updated_at: Date;
}
Expand All @@ -467,6 +468,7 @@ export interface PageRecordInsert {
page_subheader?: string | null;
page_logo?: string | null;
page_settings_json?: string | null;
page_is_internal?: number;
}

export interface PageSettingsType {
Expand Down
10 changes: 8 additions & 2 deletions src/routes/(account)/account/signin/+page.server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,15 +10,18 @@ import { VerifyPassword, GenerateToken, CookieConfig } from "$lib/server/control
import constants from "$lib/global-constants";
import serverResolve from "$lib/server/resolver.js";

export const load: PageServerLoad = async ({ parent }) => {
export const load: PageServerLoad = async ({ parent, url }) => {
const parentData = await parent();

if (!!parentData.loggedInUser && parentData.isSetupComplete) {
throw redirect(302, serverResolve("/manage/app/site-configurations"));
}

const next = url.searchParams.get("next") ?? "";

return {
...parentData,
next,
};
};

Expand All @@ -27,6 +30,7 @@ export const actions: Actions = {
const formData = await request.formData();
const email = String(formData.get("email") ?? "").trim();
const password = String(formData.get("password") ?? "");
const next = String(formData.get("next") ?? "").trim();

if (!email || !password) {
return fail(400, { error: "Email and password are required", values: { email } });
Expand Down Expand Up @@ -76,7 +80,9 @@ export const actions: Actions = {
sameSite: cookieConfig.sameSite,
});

throw redirect(302, serverResolve("/manage/app/site-configurations"));
const isSafePath = next.startsWith("/") && !/^\/[/\\]/.test(next);
const redirectTo = isSafePath ? next : serverResolve("/manage/app/site-configurations");
throw redirect(302, redirectTo);
},
signup: async ({ request, cookies }) => {
const formData = await request.formData();
Expand Down
4 changes: 4 additions & 0 deletions src/routes/(account)/account/signin/+page.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
const authActionPath = $derived(!isAdminAccountCreated ? "?/signup" : "?/login");
const emailValue = $derived(form?.values?.email ?? "");
const nameValue = $derived(form?.values && "name" in form.values ? form.values.name : "");
const next = $derived(data.next ?? "");

let loading = $state(false);
let showPassword = $state(false);
Expand Down Expand Up @@ -75,6 +76,9 @@
</Alert.Root>
{/if}

{#if next}
<input type="hidden" name="next" value={next} />
{/if}
<Field.Group>
{#if !isAdminAccountCreated}
<Field.Field>
Expand Down
8 changes: 8 additions & 0 deletions src/routes/(manage)/manage/app/pages/+page.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,7 @@
<Table.Head class="w-[340px]">Page</Table.Head>
<Table.Head class="w-[220px]">Path</Table.Head>
<Table.Head class="w-[150px]">Monitors</Table.Head>
<Table.Head class="w-[120px]">Access</Table.Head>
<Table.Head class="w-[120px] text-right"></Table.Head>
</Table.Row>
</Table.Header>
Expand Down Expand Up @@ -122,6 +123,13 @@
<Badge variant="outline" class="text-muted-foreground">No monitors</Badge>
{/if}
</Table.Cell>
<Table.Cell>
{#if page.page_is_internal}
<Badge variant="default">Internal</Badge>
{:else}
<Badge variant="outline" class="text-muted-foreground">Public</Badge>
{/if}
</Table.Cell>

<Table.Cell class="text-right">
<Button variant="ghost" target="_blank" size="sm" href={clientResolver(resolve, `/${page.page_path}`)}>
Expand Down
12 changes: 12 additions & 0 deletions src/routes/(manage)/manage/app/pages/[page_id]/+page.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@
page_path: "",
page_title: "",
page_header: "",
page_is_internal: false,
page_subheader: "",
page_logo: ""
});
Expand Down Expand Up @@ -129,6 +130,7 @@
page_path: foundPage.page_path,
page_title: foundPage.page_title,
page_header: foundPage.page_header,
page_is_internal: !!foundPage.page_is_internal,
page_subheader: foundPage.page_subheader || "",
page_logo: foundPage.page_logo || ""
};
Expand Down Expand Up @@ -191,6 +193,7 @@
page_path: sanitizedPath,
page_title: formData.page_title,
page_header: formData.page_header,
page_is_internal: formData.page_is_internal ? 1 : 0,
page_subheader: formData.page_subheader || null,
page_logo: formData.page_logo || null
};
Expand Down Expand Up @@ -609,6 +612,15 @@
<p class="text-muted-foreground text-xs">Main heading displayed on the page</p>
</div>

<!-- Internal toggle -->
<div class="space-y-2">
<div class="flex items-center justify-between">
<Label for="page-internal">Internal Page</Label>
<Switch id="page-internal" bind:checked={formData.page_is_internal} />
</div>
<p class="text-muted-foreground text-xs">Only logged-in users can view this page.</p>
</div>

<!-- Subheader -->
<div class="space-y-2">
<Label for="page-subheader">Page Content</Label>
Expand Down