This commit is contained in:
Benjamin Toby 2026-03-09 14:35:16 +00:00
parent 3a131cb30c
commit 8491c52639
13 changed files with 132 additions and 15 deletions

View File

@ -0,0 +1,25 @@
import H1 from "@/twui/components/layout/H1";
import Row from "@/twui/components/layout/Row";
import Span from "@/twui/components/layout/Span";
import Stack from "@/twui/components/layout/Stack";
import { ReactNode } from "react";
type Props = {
title: string | ReactNode;
description?: string | ReactNode;
ctas?: ReactNode;
};
export default function AdminHero({ title, ctas, description }: Props) {
return (
<Row className="w-full p-10 justify-between">
<Stack className="gap-2">
<H1 className="admin-h1">{title}</H1>
{description ? (
<Span variant="faded">{description}</Span>
) : null}
</Stack>
<Row>{ctas}</Row>
</Row>
);
}

View File

@ -0,0 +1,11 @@
import { AppContext } from "@/src/pages/_app";
import Row from "@/twui/components/layout/Row";
import { useContext } from "react";
export default function AdminSummary() {
const { pageProps } = useContext(AppContext);
console.log("pageProps", pageProps);
return <Row></Row>;
}

View File

@ -0,0 +1,19 @@
import Stack from "@/twui/components/layout/Stack";
import AdminSummary from "./(sections)/summary";
import AdminHero from "../../general/admin/hero";
export default function Main() {
return (
<Stack>
<AdminHero
title={`Dashboard`}
description={
<>
Deployment <code>ad9asd</code>
</>
}
/>
<AdminSummary />
</Stack>
);
}

View File

@ -1,5 +1,6 @@
import { EJSON } from "@/src/exports/client-exports"; import { EJSON } from "@/src/exports/client-exports";
import { PagePropsType, URLQueryType, User } from "@/src/types"; import { PagePropsType, URLQueryType, User } from "@/src/types";
import grabTurboCiConfig from "@/src/utils/grab-turboci-config";
import parsePageUrl from "@/src/utils/parse-page-url"; import parsePageUrl from "@/src/utils/parse-page-url";
import userAuth from "@/src/utils/user-auth"; import userAuth from "@/src/utils/user-auth";
import _ from "lodash"; import _ from "lodash";
@ -24,9 +25,12 @@ export default async function defaultAdminProps({
props, props,
propsFn, propsFn,
}: Params): Promise<GetServerSidePropsResult<PagePropsType>> { }: Params): Promise<GetServerSidePropsResult<PagePropsType>> {
const { req, query, res } = ctx; const { req, res } = ctx;
const query: URLQueryType = ctx.query;
const { singleRes: user } = await userAuth({ req }); const { singleRes: user } = await userAuth({ req });
const config = grabTurboCiConfig();
if (!user?.id) { if (!user?.id) {
return { return {
redirect: { redirect: {
@ -64,6 +68,7 @@ export default async function defaultAdminProps({
query, query,
user, user,
pageUrl: finalAdminUrl, pageUrl: finalAdminUrl,
config,
}; };
let finalProps = _.merge(props, propsFnProps, defaultPageProps); let finalProps = _.merge(props, propsFnProps, defaultPageProps);

View File

@ -15,8 +15,8 @@ export default function Layout({ children }: Props) {
return ( return (
<Main className="w-screen h-screen overflow-hidden"> <Main className="w-screen h-screen overflow-hidden">
<Section className="w-full h-full"> <Section className="w-full h-full">
<Container className="grid-frame section-grid grid-cols-1 h-full"> <Container className="grid-frame grid-cols-1 h-full">
<Stack className="w-full justify-between h-full"> <Stack className="w-full justify-between h-full grid-cell">
<Stack className="gap-0"> <Stack className="gap-0">
<Row> <Row>
<Row className="p-6"> <Row className="p-6">

View File

@ -1,6 +1,16 @@
import "@/src/styles/globals.css"; import "@/src/styles/globals.css";
import type { AppProps } from "next/app"; import type { AppProps } from "next/app";
import { createContext } from "react";
import { PagePropsType, TurboCIAdminAppContextType } from "../types";
export default function App({ Component, pageProps }: AppProps) { export const AppContext = createContext<TurboCIAdminAppContextType>(
return <Component {...pageProps} />; {} as TurboCIAdminAppContextType,
);
export default function App({ Component, pageProps }: AppProps<PagePropsType>) {
return (
<AppContext.Provider value={{ pageProps }}>
<Component {...pageProps} />
</AppContext.Provider>
);
} }

View File

@ -1,4 +1,4 @@
import Main from "@/src/components/pages/home"; import Main from "@/src/components/pages/admin";
import defaultAdminProps from "@/src/functions/pages/admin/default-admin-props"; import defaultAdminProps from "@/src/functions/pages/admin/default-admin-props";
import Layout from "@/src/layouts/admin"; import Layout from "@/src/layouts/admin";
import { GetServerSideProps } from "next"; import { GetServerSideProps } from "next";

View File

@ -85,3 +85,12 @@
.turboci-admin-aside-link.active * { .turboci-admin-aside-link.active * {
@apply font-semibold; @apply font-semibold;
} }
.admin-h1 {
@apply text-3xl;
}
code {
@apply bg-slate-100 dark:bg-white/10 px-3 py-1 rounded-default;
@apply my-1;
}

View File

@ -169,9 +169,15 @@ export type ServiceScriptObject = {
work_dir?: string; work_dir?: string;
}; };
export type PagePropsType = {};
export type URLQueryType = {}; export type URLQueryType = {};
export type PagePropsType = {
config?: TCIGlobalConfig | null;
query?: URLQueryType | null;
user: User;
pageUrl?: string | null;
};
export type APIReqObject = { export type APIReqObject = {
username?: string; username?: string;
email?: string; email?: string;
@ -201,3 +207,7 @@ export type TurboCISignupFormObject = {
password?: string; password?: string;
confirmed_password?: string; confirmed_password?: string;
}; };
export type TurboCIAdminAppContextType = {
pageProps: PagePropsType;
};

View File

@ -0,0 +1,23 @@
import { existsSync, readFileSync } from "fs";
import grabDirNames from "./grab-dir-names";
export default function grabCookieNames() {
const { TURBOCI_DEPLOYMENT_ID_FILE } = grabDirNames();
if (!existsSync(TURBOCI_DEPLOYMENT_ID_FILE)) {
throw new Error(`\`${TURBOCI_DEPLOYMENT_ID_FILE}\` does not exist.`);
}
const deployment_id = readFileSync(TURBOCI_DEPLOYMENT_ID_FILE, "utf-8");
const deplyment_uid_short = deployment_id.split("-").shift();
if (!deplyment_uid_short) {
throw new Error(`Invalid deployment_id`);
}
const auth_key_cookie_name = `turboci-admin-${deplyment_uid_short}-auth-key`;
const csrf_cookie_name = `turboci-admin-${deplyment_uid_short}-csrf`;
return { auth_key_cookie_name, csrf_cookie_name };
}

View File

@ -8,6 +8,7 @@ export default function grabDirNames() {
TURBOCI_CONFIG_DIR, TURBOCI_CONFIG_DIR,
"turboci.json", "turboci.json",
); );
const TURBOCI_DEPLOYMENT_ID_FILE = path.join(TURBOCI_DIR, "deployment_id");
const TURBOCI_SSH_DIR = path.join(TURBOCI_DIR, ".ssh"); const TURBOCI_SSH_DIR = path.join(TURBOCI_DIR, ".ssh");
const TURBOCI_SSH_KEY_FILE = path.join(TURBOCI_SSH_DIR, "turboci"); const TURBOCI_SSH_KEY_FILE = path.join(TURBOCI_SSH_DIR, "turboci");
@ -19,5 +20,6 @@ export default function grabDirNames() {
TURBOCI_DIR, TURBOCI_DIR,
TURBOCI_SSH_DIR, TURBOCI_SSH_DIR,
TURBOCI_SSH_KEY_FILE, TURBOCI_SSH_KEY_FILE,
TURBOCI_DEPLOYMENT_ID_FILE,
}; };
} }

View File

@ -3,13 +3,13 @@ import { ServerResponse } from "http";
import NSQLite from "@moduletrace/nsqlite"; import NSQLite from "@moduletrace/nsqlite";
import { NSQLITE_TEST_DB_USERS, NSQLiteTables } from "../db/types"; import { NSQLITE_TEST_DB_USERS, NSQLiteTables } from "../db/types";
import { User } from "../types"; import { User } from "../types";
import { AppData } from "../data/app-data";
import { setCookie } from "./cookies-actions"; import { setCookie } from "./cookies-actions";
import { EJSON } from "../exports/client-exports"; import { EJSON } from "../exports/client-exports";
import encrypt from "@moduletrace/datasquirel/dist/package-shared/functions/dsql/encrypt"; import encrypt from "@moduletrace/datasquirel/dist/package-shared/functions/dsql/encrypt";
import { APIResponseObject } from "@moduletrace/datasquirel/dist/package-shared/types"; import { APIResponseObject } from "@moduletrace/datasquirel/dist/package-shared/types";
import hashPassword from "@moduletrace/datasquirel/dist/package-shared/functions/dsql/hashPassword"; import hashPassword from "@moduletrace/datasquirel/dist/package-shared/functions/dsql/hashPassword";
import dayjs from "dayjs"; import dayjs from "dayjs";
import grabCookieNames from "./grab-cookie-names";
type Params = { type Params = {
res: NextApiResponse | ServerResponse; res: NextApiResponse | ServerResponse;
@ -111,9 +111,11 @@ export default async function loginUser({
const expiration_date = dayjs(Date.now()).add(7, "days"); const expiration_date = dayjs(Date.now()).add(7, "days");
expiration_date.add(7, "days"); expiration_date.add(7, "days");
const { auth_key_cookie_name, csrf_cookie_name } = grabCookieNames();
setCookie(res, [ setCookie(res, [
{ {
name: AppData["AuthCookieName"], name: auth_key_cookie_name,
value: encrypted_payload || "", value: encrypted_payload || "",
options: { options: {
secure: process.env.DOMAIN !== "localhost", secure: process.env.DOMAIN !== "localhost",
@ -123,7 +125,7 @@ export default async function loginUser({
}, },
}, },
{ {
name: AppData["AuthCSRFCookieName"], name: csrf_cookie_name,
value: csrf_k, value: csrf_k,
options: { options: {
path: "/", path: "/",

View File

@ -1,11 +1,11 @@
import { NextApiRequest } from "next"; import { NextApiRequest } from "next";
import { User } from "../types"; import { User } from "../types";
import { IncomingMessage } from "http"; import { IncomingMessage } from "http";
import { AppData } from "../data/app-data";
import { getCookie } from "./cookies-actions"; import { getCookie } from "./cookies-actions";
import { APIResponseObject } from "@moduletrace/datasquirel/dist/package-shared/types"; import { APIResponseObject } from "@moduletrace/datasquirel/dist/package-shared/types";
import decrypt from "@moduletrace/datasquirel/dist/package-shared/functions/dsql/decrypt"; import decrypt from "@moduletrace/datasquirel/dist/package-shared/functions/dsql/decrypt";
import { EJSON } from "../exports/client-exports"; import { EJSON } from "../exports/client-exports";
import grabCookieNames from "./grab-cookie-names";
type Params = { type Params = {
req: req:
@ -17,12 +17,13 @@ export default async function userAuth({
req, req,
}: Params): Promise<APIResponseObject<User>> { }: Params): Promise<APIResponseObject<User>> {
try { try {
const key = getCookie(req, AppData["AuthCookieName"]); const { auth_key_cookie_name, csrf_cookie_name } = grabCookieNames();
const key = getCookie(req, auth_key_cookie_name);
if (!key) { if (!key) {
return { return {
success: false, success: false,
msg: `No ${AppData["AuthCookieName"]} found in request object.`, msg: `No ${auth_key_cookie_name} found in request object.`,
}; };
} }
@ -36,12 +37,12 @@ export default async function userAuth({
}; };
} }
const csrf = getCookie(req, AppData["AuthCSRFCookieName"]); const csrf = getCookie(req, csrf_cookie_name);
if (!csrf) { if (!csrf) {
return { return {
success: false, success: false,
msg: `No ${AppData["AuthCSRFCookieName"]} found in request object.`, msg: `No ${csrf_cookie_name} found in request object.`,
}; };
} }