diff --git a/.gitignore b/.gitignore index 664aa40..87fd76f 100644 --- a/.gitignore +++ b/.gitignore @@ -42,4 +42,6 @@ next-env.d.ts /src/db/turboci-admin .backups /secrets -.tmp \ No newline at end of file +.tmp + +/test \ No newline at end of file diff --git a/bun.lock b/bun.lock index c59a485..5a52663 100644 --- a/bun.lock +++ b/bun.lock @@ -19,6 +19,7 @@ "next": "14^", "next-mdx-remote": "^6.0.0", "openai": "^6.25.0", + "prism-themes": "^1.9.0", "react": "19.2.3", "react-code-blocks": "^0.1.6", "react-dom": "19.2.3", @@ -808,6 +809,8 @@ "prebuild-install": ["prebuild-install@7.1.3", "", { "dependencies": { "detect-libc": "^2.0.0", "expand-template": "^2.0.3", "github-from-package": "0.0.0", "minimist": "^1.2.3", "mkdirp-classic": "^0.5.3", "napi-build-utils": "^2.0.0", "node-abi": "^3.3.0", "pump": "^3.0.0", "rc": "^1.2.7", "simple-get": "^4.0.0", "tar-fs": "^2.0.0", "tunnel-agent": "^0.6.0" }, "bin": { "prebuild-install": "bin.js" } }, "sha512-8Mf2cbV7x1cXPUILADGI3wuhfqWvtiLA1iclTDbFRZkgRQS0NqsPZphna9V+HyTEadheuPmjaJMsbzKQFOzLug=="], + "prism-themes": ["prism-themes@1.9.0", "", {}, "sha512-tX2AYsehKDw1EORwBps+WhBFKc2kxfoFpQAjxBndbZKr4fRmMkv47XN0BghC/K1qwodB1otbe4oF23vUTFDokw=="], + "prismjs": ["prismjs@1.30.0", "", {}, "sha512-DEvV2ZF2r2/63V+tK8hQvrR2ZGn10srHbXviTlcv7Kpzw8jWiNTqbVgjO3IY8RxrrOUF8VPMQQFysYYYv0YZxw=="], "process-nextick-args": ["process-nextick-args@2.0.1", "", {}, "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag=="], diff --git a/package.json b/package.json index 3d02477..b43b076 100644 --- a/package.json +++ b/package.json @@ -34,6 +34,7 @@ "next": "14^", "next-mdx-remote": "^6.0.0", "openai": "^6.25.0", + "prism-themes": "^1.9.0", "react": "19.2.3", "react-code-blocks": "^0.1.6", "react-dom": "19.2.3", diff --git a/src/components/general/code-block.tsx b/src/components/general/code-block.tsx new file mode 100644 index 0000000..6d98092 --- /dev/null +++ b/src/components/general/code-block.tsx @@ -0,0 +1,10 @@ +import RemoteCodeBlock from "@/twui/components/elements/RemoteCodeBlock"; + +type Props = { + content: string; + mode: "javascript" | "typescript" | "sh"; +}; + +export default function TurboCICodeBlock({ mode, content }: Props) { + return ; +} diff --git a/src/components/pages/admin/settings/(sections)/settings-form.tsx b/src/components/pages/admin/settings/(sections)/settings-form.tsx index 80efc15..2492a73 100644 --- a/src/components/pages/admin/settings/(sections)/settings-form.tsx +++ b/src/components/pages/admin/settings/(sections)/settings-form.tsx @@ -13,10 +13,12 @@ import useSettingsForm from "../(hooks)/use-settings-form"; import { APIReqObject } from "@/src/types"; export default function SettingsForm() { - const { setToast } = useContext(AppContext); + const { setToast, pageProps } = useContext(AppContext); const { formData, setFormData, loading, setLoading, user } = useSettingsForm(); + const deployment_user = pageProps.deployment_user; + async function handleSubmit() { if (!formData.first_name?.match(/./)) return; @@ -54,6 +56,10 @@ export default function SettingsForm() { } } + if (!deployment_user?.id) { + return null; + } + return ( @@ -62,7 +68,9 @@ export default function SettingsForm() { - {user.super_admin ? "root" : user?.username || "—"} + {user.super_admin + ? "root" + : deployment_user?.username || "—"} diff --git a/src/components/pages/admin/users/deployment-user/(partials)/delete-deployment-user-button.tsx b/src/components/pages/admin/users/deployment-user/(partials)/delete-deployment-user-button.tsx index 8fac7d0..12279b7 100644 --- a/src/components/pages/admin/users/deployment-user/(partials)/delete-deployment-user-button.tsx +++ b/src/components/pages/admin/users/deployment-user/(partials)/delete-deployment-user-button.tsx @@ -26,6 +26,10 @@ export default function DeleteDeplotmentUserButton({ ); + if (dep_user.is_super_admin) { + return null; + } + return (
{ diff --git a/src/components/pages/admin/users/deployment-user/(sections)/ssh-connection.tsx b/src/components/pages/admin/users/deployment-user/(sections)/ssh-connection.tsx new file mode 100644 index 0000000..d0458a9 --- /dev/null +++ b/src/components/pages/admin/users/deployment-user/(sections)/ssh-connection.tsx @@ -0,0 +1,51 @@ +import { useContext } from "react"; +import { AppContext } from "@/src/pages/_app"; +import Stack from "@/twui/components/layout/Stack"; +import H2 from "@/twui/components/layout/H2"; +import Span from "@/twui/components/layout/Span"; +import RemoteCodeBlock from "@/twui/components/elements/RemoteCodeBlock"; +import SingleLineCodeBlock from "@/twui/components/elements/SingleLineCodeBlock"; +import TurboCICodeBlock from "@/src/components/general/code-block"; +import H3 from "@/twui/components/layout/H3"; + +export default function SSHConnection() { + const { pageProps } = useContext(AppContext); + const { deployment_user, deployment } = pageProps; + + if (!deployment_user?.id) { + return null; + } + + const is_super_admin = Boolean(deployment_user.is_super_admin); + + return ( + <> + +

Connection

+ + Use these information to connect this user to the Relay + server + +
+
+ + + +

Admin Port Forwarding

+ + + Use these information to connect this user to the + Relay server + + +
+
+ +
+
+ + ); +} diff --git a/src/components/pages/admin/users/deployment-user/index.tsx b/src/components/pages/admin/users/deployment-user/index.tsx index fccca18..44a5bd9 100644 --- a/src/components/pages/admin/users/deployment-user/index.tsx +++ b/src/components/pages/admin/users/deployment-user/index.tsx @@ -7,6 +7,7 @@ import Row from "@/twui/components/layout/Row"; import SignupForm from "../../../auth/signup/(partials)/signup-form"; import Stack from "@/twui/components/layout/Stack"; import DeleteDeplotmentUserButton from "./(partials)/delete-deployment-user-button"; +import SSHConnection from "./(sections)/ssh-connection"; export default function Main() { const { pageProps } = useContext(AppContext); @@ -43,6 +44,8 @@ export default function Main() { + + ); } diff --git a/src/components/pages/auth/signup/(partials)/signup-form-password.tsx b/src/components/pages/auth/signup/(partials)/signup-form-password.tsx new file mode 100644 index 0000000..8c7863b --- /dev/null +++ b/src/components/pages/auth/signup/(partials)/signup-form-password.tsx @@ -0,0 +1,57 @@ +import Input from "@/twui/components/form/Input"; +import useSignupForm from "../(hooks)/use-signup-form"; + +type Props = ReturnType; + +export default function SignupFormPassword({ + isPasswordConfirmed, + newUser, + setNewUser, + setIsPasswordConfirmed, +}: Props) { + const is_password_valid = Boolean( + isPasswordConfirmed && + Boolean(newUser.password?.match(/./)) && + Boolean(newUser.confirmed_password?.match(/./)), + ); + + return ( + <> + { + setNewUser((prev) => ({ + ...prev, + password: v, + })); + }} + validity={{ + isValid: + !Boolean(newUser.password?.match(/./)) || + !Boolean(newUser.confirmed_password?.match(/./)) + ? true + : is_password_valid, + msg: `Passwords don't match`, + }} + required + showLabel + /> + { + setNewUser((prev) => ({ + ...prev, + confirmed_password: v, + })); + + setIsPasswordConfirmed(v == newUser.password); + }} + showLabel + /> + + ); +} diff --git a/src/components/pages/auth/signup/(partials)/signup-form.tsx b/src/components/pages/auth/signup/(partials)/signup-form.tsx index 4f7cc44..a555e5f 100644 --- a/src/components/pages/auth/signup/(partials)/signup-form.tsx +++ b/src/components/pages/auth/signup/(partials)/signup-form.tsx @@ -5,9 +5,9 @@ import useSignupForm from "../(hooks)/use-signup-form"; import fetchApi from "@/twui/components/utils/fetch/fetchApi"; import { APIReqObject } from "@/src/types"; import { APIResponseObject } from "@moduletrace/datasquirel/dist/package-shared/types"; -import { useEffect } from "react"; import { NSQLITE_TURBOCI_ADMIN_USERS } from "@/src/db/types"; import { twMerge } from "tailwind-merge"; +import SignupFormPassword from "./signup-form-password"; type Props = { new_deployment_user?: boolean; @@ -18,6 +18,7 @@ export default function SignupForm({ new_deployment_user, existing_user, }: Props) { + const init = useSignupForm({ new_deployment_user, existing_user }); const { newUser, setNewUser, @@ -26,7 +27,7 @@ export default function SignupForm({ isPasswordConfirmed, setIsPasswordConfirmed, pageProps, - } = useSignupForm({ new_deployment_user, existing_user }); + } = init; const is_password_valid = Boolean( isPasswordConfirmed && @@ -80,7 +81,7 @@ export default function SignupForm({ required showLabel /> - {pageProps.user.id ? ( + {pageProps.user?.id && !existing_user?.is_super_admin ? ( ) : null} - {existing_user?.id ? null : ( - <> - { - setNewUser((prev) => ({ - ...prev, - password: v, - })); - }} - validity={{ - isValid: - !Boolean(newUser.password?.match(/./)) || - !Boolean( - newUser.confirmed_password?.match(/./), - ) - ? true - : is_password_valid, - msg: `Passwords don't match`, - }} - required - showLabel - /> - { - setNewUser((prev) => ({ - ...prev, - confirmed_password: v, - })); - - setIsPasswordConfirmed(v == newUser.password); - }} - showLabel - /> - - )} + {existing_user?.id ? null : } diff --git a/src/functions/deployment-users/delete-deployment-user.ts b/src/functions/deployment-users/delete-deployment-user.ts index 66d7aad..98155e2 100644 --- a/src/functions/deployment-users/delete-deployment-user.ts +++ b/src/functions/deployment-users/delete-deployment-user.ts @@ -17,8 +17,6 @@ export default async function deleteDeploymentUser({ user_id }: Params) { targetId: _n(user_id), }); - console.log("target_user_res", target_user_res); - const target_user = target_user_res.singleRes; if ( @@ -31,6 +29,8 @@ export default async function deleteDeploymentUser({ user_id }: Params) { const { username } = target_user; + console.log("username", username); + const { force_command_file, sshd_config_file } = grabDeploymentUserDirNames( { user: target_user }, ); @@ -38,12 +38,13 @@ export default async function deleteDeploymentUser({ user_id }: Params) { let cmd = `/bin/bash << 'TURBOCIHEREDOC'\n`; // cmd += `userdel -r ${username}\n`; - cmd += `killall -u ${username}\n`; - cmd += `deluser --remove-all-files ${username}\n`; + // cmd += `pkill -u ${username} || echo "User Deleted!"\n`; + cmd += `deluser --remove-all-files ${username} || echo "User Deleted!"\n`; cmd += `rm -f ${force_command_file}\n`; cmd += `rm -f ${sshd_config_file}\n`; - cmd += `Match User ${username}\n`; + + cmd += `TURBOCIHEREDOC\n`; execSync(cmd); diff --git a/src/functions/deployment-users/setup-deployment-user.ts b/src/functions/deployment-users/setup-deployment-user.ts index 1421d97..b0da6de 100644 --- a/src/functions/deployment-users/setup-deployment-user.ts +++ b/src/functions/deployment-users/setup-deployment-user.ts @@ -18,11 +18,13 @@ export default async function setupDeploymentUser({ user_id }: Params) { targetId: _n(user_id), }); - console.log("target_user_res", target_user_res); - const target_user = target_user_res.singleRes; - if (!target_user?.id || !target_user.username) { + if ( + !target_user?.id || + !target_user.username || + target_user.is_super_admin + ) { return; } @@ -42,7 +44,7 @@ export default async function setupDeploymentUser({ user_id }: Params) { cmd += `useradd --create-home --shell /bin/bash --comment "TurboCI Deployment user ${username}" ${username}\n`; cmd += `passwd --lock "${username}"\n`; cmd += `mkdir -p "${ssh_dir}"\n`; - cmd += `ssh-keygen -t ed25519 -f "${ssh_key_file}" -N ""\n`; + cmd += `ssh-keygen -t ed25519 -f "${ssh_key_file}" -N "" -C "${username}@$(hostname)"\n`; cmd += `cp "${ssh_key_file}.pub" "${ssh_dir}/authorized_keys"\n`; cmd += `chown -R "${username}:${username}" "${ssh_dir}"\n`; diff --git a/src/layouts/admin/(data)/links.ts b/src/layouts/admin/(data)/links.ts index 46066b1..54f5a30 100644 --- a/src/layouts/admin/(data)/links.ts +++ b/src/layouts/admin/(data)/links.ts @@ -1,25 +1,34 @@ +import { User } from "@/src/types"; import { TWUI_LINK_LIST_LINK_OBJECT } from "@/twui/components/elements/LinkList"; -export const AdminAsideLinks: ( - | TWUI_LINK_LIST_LINK_OBJECT - | TWUI_LINK_LIST_LINK_OBJECT[] - | undefined -)[] = [ - { - title: "Dashboard", - url: "/admin", - strict: true, - }, - { - title: "Services", - url: "/admin/services", - }, - { - title: "Users", - url: "/admin/users", - }, - { - title: "Settings", - url: "/admin/settings", - }, -]; +type Params = { user?: User }; + +export function AdminAsideLinks({ user }: Params) { + const links: ( + | TWUI_LINK_LIST_LINK_OBJECT + | TWUI_LINK_LIST_LINK_OBJECT[] + | undefined + )[] = [ + { + title: "Dashboard", + url: "/admin", + strict: true, + }, + { + title: "Services", + url: "/admin/services", + }, + user?.super_admin + ? { + title: "Users", + url: "/admin/users", + } + : undefined, + { + title: "Settings", + url: "/admin/settings", + }, + ]; + + return links; +} diff --git a/src/layouts/admin/index.tsx b/src/layouts/admin/index.tsx index 12cda46..f0d12a8 100644 --- a/src/layouts/admin/index.tsx +++ b/src/layouts/admin/index.tsx @@ -2,16 +2,18 @@ import LinkList from "@/twui/components/elements/LinkList"; import Main from "@/twui/components/layout/Main"; import Row from "@/twui/components/layout/Row"; import Stack from "@/twui/components/layout/Stack"; -import { Fragment, PropsWithChildren } from "react"; +import { Fragment, PropsWithChildren, useContext } from "react"; import { AdminAsideLinks } from "./(data)/links"; import Header from "./header"; import { twMerge } from "tailwind-merge"; -import Spacer from "@/twui/components/layout/Spacer"; import Head from "next/head"; +import { AppContext } from "@/src/pages/_app"; type Props = PropsWithChildren & {}; export default function Layout({ children }: Props) { + const { pageProps } = useContext(AppContext); + return ( @@ -26,7 +28,7 @@ export default function Layout({ children }: Props) {
{ - return await defaultAdminProps({ ctx }); + return await defaultAdminProps({ + ctx, + async propsFn({ user }) { + if (!user.id) { + return `/admin`; + } + + const users_res = await NSQLite.select( + { + table: "users", + targetId: user.id, + }, + ); + + if (!users_res.singleRes?.id) { + return `/admin/users`; + } + + return { + deployment_user: users_res.singleRes, + }; + }, + }); }; diff --git a/src/pages/admin/users/[deployment_user_id]/index.tsx b/src/pages/admin/users/[deployment_user_id]/index.tsx index 1086a7a..69bac06 100644 --- a/src/pages/admin/users/[deployment_user_id]/index.tsx +++ b/src/pages/admin/users/[deployment_user_id]/index.tsx @@ -29,6 +29,10 @@ export const getServerSideProps: GetServerSideProps = async (ctx) => { }, ); + if (!users_res.singleRes?.id) { + return `/admin/users`; + } + return { deployment_user: users_res.singleRes, }; diff --git a/src/pages/api/auth/signup.ts b/src/pages/api/auth/signup.ts index b6f57ab..17ababa 100644 --- a/src/pages/api/auth/signup.ts +++ b/src/pages/api/auth/signup.ts @@ -73,7 +73,7 @@ export default async function handler( email, password: new_user_password, is_super_admin: user?.id ? 0 : 1, - username: slugify(username), + username: username ? slugify(username) : undefined, }, ], table: "users", @@ -90,20 +90,31 @@ export default async function handler( (typeof NSQLiteTables)[number] >({ table: "users", - query: { - query: { - is_super_admin: { value: "1" }, - }, - }, + targetId: new_user_id, }); const newly_inserted_user = newly_inserted_user_res.singleRes; if (!newly_inserted_user?.id) { + console.log("newly_inserted_user_res", newly_inserted_user_res); throw new Error(`Couldn't Find Newly inserted user.`); } + if (user?.id && !newly_inserted_user.username) { + const newly_inserted_user_res = await NSQLite.delete< + NSQLITE_TURBOCI_ADMIN_USERS, + (typeof NSQLiteTables)[number] + >({ + table: "users", + targetId: newly_inserted_user?.id, + }); + + throw new Error(`Couldn't set Newly inserted user username.`); + } + if (user?.id) { + console.log("newly_inserted_user", newly_inserted_user); + await setupDeploymentUser({ user_id: newly_inserted_user.id }); return res.json({