Compare commits
6 Commits
v0.1.0-alp
...
v0.1.0-alp
| Author | SHA1 | Date | |
|---|---|---|---|
| 36ac1dccb4 | |||
| 514a44b6de | |||
| a7bb364a2f | |||
| 047cc7cdf0 | |||
| 8dc4d70e28 | |||
| c8931c7249 |
19
CHANGELOG.md
19
CHANGELOG.md
@@ -1,5 +1,24 @@
|
|||||||
# All Changes to LST can be found below.
|
# All Changes to LST can be found below.
|
||||||
|
|
||||||
|
## [0.1.0-alpha.1](https://git.tuffraid.net/cowch/lst_v3/compare/v0.1.0-alpha.0...v0.1.0-alpha.1) (2026-05-19)
|
||||||
|
|
||||||
|
|
||||||
|
### 🐛 Bug fixes
|
||||||
|
|
||||||
|
* **notifications:** reprinting ([c8931c7](https://git.tuffraid.net/cowch/lst_v3/commits/c8931c7249b8f532b5dd37df3271da98f14ee710)), closes [#20](https://git.tuffraid.net/cowch/lst_v3/issues/20)
|
||||||
|
* **settings:** failed build due it dormant import ([a7bb364](https://git.tuffraid.net/cowch/lst_v3/commits/a7bb364a2fd49d96b6195aca0cd58ba57c58f3a6))
|
||||||
|
|
||||||
|
|
||||||
|
### 🛠️ Code Refactor
|
||||||
|
|
||||||
|
* **servers:** changed activeity around and trying to make use of it ([514a44b](https://git.tuffraid.net/cowch/lst_v3/commits/514a44b6de3efe8dd8b308d98bdbc82e31ed8427))
|
||||||
|
* **users:** lots of auth stuff added to make it more easy to manage users ([047cc7c](https://git.tuffraid.net/cowch/lst_v3/commits/047cc7cdf036c39a89a0b87ab59dda8328efe0c0))
|
||||||
|
|
||||||
|
|
||||||
|
### 📈 Project changes
|
||||||
|
|
||||||
|
* **app:** added in chokidar to monitor folders ([8dc4d70](https://git.tuffraid.net/cowch/lst_v3/commits/8dc4d70e2827f0a40d2f54886fd757c8a2dc5ac4))
|
||||||
|
|
||||||
## [0.1.0-alpha.0](https://git.tuffraid.net/cowch/lst_v3/compare/v0.0.2-alpha.10...v0.1.0-alpha.0) (2026-05-14)
|
## [0.1.0-alpha.0](https://git.tuffraid.net/cowch/lst_v3/compare/v0.0.2-alpha.10...v0.1.0-alpha.0) (2026-05-14)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,16 +1,17 @@
|
|||||||
use [test1_AlplaPROD2.0_Read]
|
use [test1_AlplaPROD2.0_Read]
|
||||||
|
|
||||||
SELECT
|
SELECT
|
||||||
--JSON_VALUE(content, '$.EntityId') as labelId
|
JSON_VALUE(content, '$.EntityId') as labelId,
|
||||||
a.id
|
a.id
|
||||||
,ActorName
|
,ActorName
|
||||||
,FORMAT(PrintDate, 'yyyy-MM-dd HH:mm') as printDate
|
--,FORMAT(l.PrintDate, 'yyyy-MM-dd HH:mm') as printDate
|
||||||
|
,Format(COALESCE(l.PrintDate, e.ProductionDate), 'yyyy-MM-dd HH:mm') as printDate
|
||||||
,FORMAT(CreatedDateTime, 'yyyy-MM-dd HH:mm') createdDateTime
|
,FORMAT(CreatedDateTime, 'yyyy-MM-dd HH:mm') createdDateTime
|
||||||
,l.ArticleHumanReadableId as av
|
,COALESCE(l.ArticleHumanReadableId,e.ArticleHumanReadableId) as av
|
||||||
,l.ArticleDescription as alias
|
,COALESCE(l.ArticleDescription, av.Name) as alias
|
||||||
,PrintedCopies
|
,COALESCE(l.PrintedCopies, 0) as PrintedCopies
|
||||||
,p.name as printerName
|
,COALESCE(p.name,'External Label not tracked') as printerName
|
||||||
,RunningNumber
|
,COALESCE(l.RunningNumber, e.RunningNumber) as runningNumber
|
||||||
--,*
|
--,*
|
||||||
FROM [support].[AuditLog] (nolock) as a
|
FROM [support].[AuditLog] (nolock) as a
|
||||||
|
|
||||||
@@ -18,10 +19,20 @@ left join
|
|||||||
[labelling].[InternalLabel] (nolock) as l on
|
[labelling].[InternalLabel] (nolock) as l on
|
||||||
l.id = JSON_VALUE(content, '$.EntityId')
|
l.id = JSON_VALUE(content, '$.EntityId')
|
||||||
|
|
||||||
|
OUTER APPLY (
|
||||||
|
SELECT TOP 1 *
|
||||||
|
FROM labelling.ExternalLabel e
|
||||||
|
WHERE e.id = JSON_VALUE(a.content, '$.EntityId')
|
||||||
|
ORDER BY e.Id DESC
|
||||||
|
) e
|
||||||
|
|
||||||
left join
|
left join
|
||||||
[masterData].[printer] (nolock) as p on
|
[masterData].[printer] (nolock) as p on
|
||||||
p.id = l.PrinterId
|
p.id = l.PrinterId
|
||||||
|
|
||||||
|
left join
|
||||||
|
[masterData].[article] (nolock) as av on
|
||||||
|
av.HumanReadableId = e.ArticleHumanReadableId
|
||||||
where message like '%reprint%'
|
where message like '%reprint%'
|
||||||
and CreatedDateTime > DATEADD(minute, -[intervalCheck], SYSDATETIMEOFFSET())
|
and CreatedDateTime > DATEADD(minute, -[intervalCheck], SYSDATETIMEOFFSET())
|
||||||
and a.id > [ignoreList]
|
and a.id > [ignoreList]
|
||||||
|
|||||||
@@ -1,10 +1,12 @@
|
|||||||
import { createAccessControl } from "better-auth/plugins/access";
|
import { createAccessControl } from "better-auth/plugins/access";
|
||||||
import { adminAc } from "better-auth/plugins/admin/access";
|
import { adminAc, defaultStatements } from "better-auth/plugins/admin/access";
|
||||||
|
|
||||||
export const statement = {
|
export const statement = {
|
||||||
|
...defaultStatements,
|
||||||
app: ["read", "create", "share", "update", "delete", "readAll"],
|
app: ["read", "create", "share", "update", "delete", "readAll"],
|
||||||
//user: ["ban"],
|
|
||||||
quality: ["read", "create", "share", "update", "delete", "readAll"],
|
quality: ["read", "create", "share", "update", "delete", "readAll"],
|
||||||
|
logistics: ["read", "create", "share", "update", "delete", "readAll"],
|
||||||
|
mobile: ["read", "create", "share", "update", "delete", "readAll"],
|
||||||
notifications: ["read", "create", "share", "update", "delete", "readAll"],
|
notifications: ["read", "create", "share", "update", "delete", "readAll"],
|
||||||
} as const;
|
} as const;
|
||||||
|
|
||||||
@@ -15,14 +17,22 @@ export const user = ac.newRole({
|
|||||||
notifications: ["read", "create"],
|
notifications: ["read", "create"],
|
||||||
});
|
});
|
||||||
|
|
||||||
|
export const manager = ac.newRole({
|
||||||
|
app: ["read", "create", "update"],
|
||||||
|
mobile: ["read", "create", "update"],
|
||||||
|
});
|
||||||
|
|
||||||
export const admin = ac.newRole({
|
export const admin = ac.newRole({
|
||||||
app: ["read", "create", "update"],
|
app: ["read", "create", "update"],
|
||||||
|
mobile: ["read", "create", "update"],
|
||||||
|
user: ["create", "update"],
|
||||||
});
|
});
|
||||||
|
|
||||||
export const systemAdmin = ac.newRole({
|
export const systemAdmin = ac.newRole({
|
||||||
app: ["read", "create", "share", "update", "delete", "readAll"],
|
|
||||||
//user: ["ban"],
|
|
||||||
quality: ["read", "create", "share", "update", "delete", "readAll"],
|
|
||||||
notifications: ["read", "create", "share", "update", "delete", "readAll"],
|
|
||||||
...adminAc.statements,
|
...adminAc.statements,
|
||||||
|
app: ["read", "create", "share", "update", "delete", "readAll"],
|
||||||
|
quality: ["read", "create", "share", "update", "delete", "readAll"],
|
||||||
|
mobile: ["read", "create", "share", "update", "delete", "readAll"],
|
||||||
|
logistics: ["read", "create", "share", "update", "delete", "readAll"],
|
||||||
|
notifications: ["read", "create", "share", "update", "delete", "readAll"],
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ import {
|
|||||||
//import { eq } from "drizzle-orm";
|
//import { eq } from "drizzle-orm";
|
||||||
import { db } from "../db/db.controller.js";
|
import { db } from "../db/db.controller.js";
|
||||||
import * as rawSchema from "../db/schema/auth.schema.js";
|
import * as rawSchema from "../db/schema/auth.schema.js";
|
||||||
import { ac, admin, systemAdmin, user } from "./auth.permissions.js";
|
import { ac, admin, manager, systemAdmin, user } from "./auth.permissions.js";
|
||||||
import { allowedOrigins } from "./cors.utils.js";
|
import { allowedOrigins } from "./cors.utils.js";
|
||||||
import { sendEmail } from "./sendEmail.utils.js";
|
import { sendEmail } from "./sendEmail.utils.js";
|
||||||
|
|
||||||
@@ -163,6 +163,7 @@ export const auth = betterAuth({
|
|||||||
roles: {
|
roles: {
|
||||||
admin,
|
admin,
|
||||||
user,
|
user,
|
||||||
|
manager,
|
||||||
systemAdmin,
|
systemAdmin,
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import { Tooltip as TooltipPrimitive } from "radix-ui";
|
import { Tooltip as TooltipPrimitive } from "radix-ui";
|
||||||
import type * as React from "react";
|
|
||||||
|
|
||||||
import { cn } from "@/lib/utils";
|
import { cn } from "@/lib/utils";
|
||||||
|
|
||||||
@@ -40,7 +39,7 @@ function TooltipContent({
|
|||||||
data-slot="tooltip-content"
|
data-slot="tooltip-content"
|
||||||
sideOffset={sideOffset}
|
sideOffset={sideOffset}
|
||||||
className={cn(
|
className={cn(
|
||||||
"z-50 w-fit origin-(--radix-tooltip-content-transform-origin) animate-in rounded-md bg-foreground px-3 py-1.5 text-xs text-balance text-background fade-in-0 zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95",
|
"z-50 inline-flex w-fit max-w-xs origin-(--radix-tooltip-content-transform-origin) items-center gap-1.5 rounded-md bg-foreground px-3 py-1.5 text-xs text-background has-data-[slot=kbd]:pr-1.5 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 **:data-[slot=kbd]:relative **:data-[slot=kbd]:isolate **:data-[slot=kbd]:z-50 **:data-[slot=kbd]:rounded-sm data-[state=delayed-open]:animate-in data-[state=delayed-open]:fade-in-0 data-[state=delayed-open]:zoom-in-95 data-open:animate-in data-open:fade-in-0 data-open:zoom-in-95 data-closed:animate-out data-closed:fade-out-0 data-closed:zoom-out-95",
|
||||||
className,
|
className,
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
@@ -52,4 +51,4 @@ function TooltipContent({
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export { Tooltip, TooltipTrigger, TooltipContent, TooltipProvider };
|
export { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger };
|
||||||
|
|||||||
@@ -31,6 +31,13 @@ api.interceptors.response.use(
|
|||||||
appRouter?.navigate({ to: "/forbidden", replace: true });
|
appRouter?.navigate({ to: "/forbidden", replace: true });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (error.response?.status === 401) {
|
||||||
|
// redirect, toast, or show forbidden page
|
||||||
|
toast.error("Unauthorized to be here");
|
||||||
|
|
||||||
|
appRouter?.navigate({ to: "/login", replace: true });
|
||||||
|
}
|
||||||
|
|
||||||
if (isNetworkError) {
|
if (isNetworkError) {
|
||||||
appRouter?.navigate({ to: "/app-down", replace: true });
|
appRouter?.navigate({ to: "/app-down", replace: true });
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,10 @@
|
|||||||
import { redirect } from "@tanstack/react-router";
|
import { redirect } from "@tanstack/react-router";
|
||||||
import { adminClient, genericOAuthClient } from "better-auth/client/plugins";
|
import {
|
||||||
|
adminClient,
|
||||||
|
genericOAuthClient,
|
||||||
|
usernameClient,
|
||||||
|
} from "better-auth/client/plugins";
|
||||||
|
|
||||||
import { createAuthClient } from "better-auth/react";
|
import { createAuthClient } from "better-auth/react";
|
||||||
import { ac, admin, manager, systemAdmin, user } from "./auth-permissions";
|
import { ac, admin, manager, systemAdmin, user } from "./auth-permissions";
|
||||||
|
|
||||||
@@ -16,6 +21,7 @@ export const authClient = createAuthClient({
|
|||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
genericOAuthClient(),
|
genericOAuthClient(),
|
||||||
|
usernameClient(),
|
||||||
],
|
],
|
||||||
fetchOptions: {
|
fetchOptions: {
|
||||||
onError() {
|
onError() {
|
||||||
|
|||||||
@@ -1,9 +1,25 @@
|
|||||||
import { createAccessControl } from "better-auth/plugins/access";
|
import { createAccessControl } from "better-auth/plugins/access";
|
||||||
import { adminAc } from "better-auth/plugins/admin/access";
|
import { adminAc, defaultStatements } from "better-auth/plugins/admin/access";
|
||||||
|
|
||||||
|
/*
|
||||||
|
When new perms are added based on there criteria make sure they are added here as well
|
||||||
|
*/
|
||||||
|
|
||||||
|
type SelectableRole = {
|
||||||
|
label: string;
|
||||||
|
value: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const selectableRoles: SelectableRole[] = [
|
||||||
|
{ label: "User", value: "user" },
|
||||||
|
{ label: "Manager", value: "manager" },
|
||||||
|
{ label: "Admin", value: "admin" },
|
||||||
|
{ label: "System Admin", value: "systemAdmin" },
|
||||||
|
];
|
||||||
|
|
||||||
export const statement = {
|
export const statement = {
|
||||||
|
...defaultStatements,
|
||||||
app: ["read", "create", "share", "update", "delete", "readAll"],
|
app: ["read", "create", "share", "update", "delete", "readAll"],
|
||||||
//user: ["ban"],
|
|
||||||
quality: ["read", "create", "share", "update", "delete", "readAll"],
|
quality: ["read", "create", "share", "update", "delete", "readAll"],
|
||||||
logistics: ["read", "create", "share", "update", "delete", "readAll"],
|
logistics: ["read", "create", "share", "update", "delete", "readAll"],
|
||||||
mobile: ["read", "create", "share", "update", "delete", "readAll"],
|
mobile: ["read", "create", "share", "update", "delete", "readAll"],
|
||||||
@@ -19,20 +35,22 @@ export const user = ac.newRole({
|
|||||||
|
|
||||||
export const manager = ac.newRole({
|
export const manager = ac.newRole({
|
||||||
app: ["read", "create", "update"],
|
app: ["read", "create", "update"],
|
||||||
|
mobile: ["read", "create", "update"],
|
||||||
});
|
});
|
||||||
|
|
||||||
export const admin = ac.newRole({
|
export const admin = ac.newRole({
|
||||||
app: ["read", "create", "update"],
|
app: ["read", "create", "update"],
|
||||||
|
mobile: ["read", "create", "update"],
|
||||||
|
user: ["create", "update"],
|
||||||
});
|
});
|
||||||
|
|
||||||
export const systemAdmin = ac.newRole({
|
export const systemAdmin = ac.newRole({
|
||||||
|
...adminAc.statements,
|
||||||
app: ["read", "create", "share", "update", "delete", "readAll"],
|
app: ["read", "create", "share", "update", "delete", "readAll"],
|
||||||
//user: ["ban"],
|
|
||||||
quality: ["read", "create", "share", "update", "delete", "readAll"],
|
quality: ["read", "create", "share", "update", "delete", "readAll"],
|
||||||
mobile: ["read", "create", "share", "update", "delete", "readAll"],
|
mobile: ["read", "create", "share", "update", "delete", "readAll"],
|
||||||
logistics: ["read", "create", "share", "update", "delete", "readAll"],
|
logistics: ["read", "create", "share", "update", "delete", "readAll"],
|
||||||
notifications: ["read", "create", "share", "update", "delete", "readAll"],
|
notifications: ["read", "create", "share", "update", "delete", "readAll"],
|
||||||
...adminAc.statements,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
/* example usage
|
/* example usage
|
||||||
|
|||||||
16
frontend/src/lib/queries/permsCheck.ts
Normal file
16
frontend/src/lib/queries/permsCheck.ts
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
import { queryOptions } from "@tanstack/react-query";
|
||||||
|
import { authClient } from "@/lib/auth-client";
|
||||||
|
|
||||||
|
export function permissionQuery(permissions: Record<string, string[]>) {
|
||||||
|
return queryOptions({
|
||||||
|
queryKey: ["permission", permissions],
|
||||||
|
queryFn: async () => {
|
||||||
|
const result = await authClient.admin.hasPermission({
|
||||||
|
permissions,
|
||||||
|
});
|
||||||
|
|
||||||
|
return result.data?.success ?? false;
|
||||||
|
},
|
||||||
|
staleTime: 30_000,
|
||||||
|
});
|
||||||
|
}
|
||||||
@@ -29,30 +29,43 @@ export default function LoginForm({ redirectPath }: { redirectPath: string }) {
|
|||||||
|
|
||||||
const form = useAppForm({
|
const form = useAppForm({
|
||||||
defaultValues: {
|
defaultValues: {
|
||||||
email: loginEmail,
|
login: loginEmail,
|
||||||
password: "",
|
password: "",
|
||||||
rememberMe: rememberMe,
|
rememberMe: rememberMe,
|
||||||
},
|
},
|
||||||
onSubmit: async ({ value }) => {
|
onSubmit: async ({ value }) => {
|
||||||
// set remember me incase we want it later
|
// set remember me incase we want it later
|
||||||
|
const loginValue = value.login.trim();
|
||||||
|
const isEmailLogin = loginValue.includes("@");
|
||||||
|
|
||||||
if (value.rememberMe) {
|
if (value.rememberMe) {
|
||||||
localStorage.setItem("rememberMe", value.rememberMe.toString());
|
localStorage.setItem("rememberMe", value.rememberMe.toString());
|
||||||
localStorage.setItem("loginEmail", value.email.toLocaleLowerCase());
|
localStorage.setItem("loginEmail", loginValue.toLocaleLowerCase());
|
||||||
} else {
|
} else {
|
||||||
localStorage.removeItem("rememberMe");
|
localStorage.removeItem("rememberMe");
|
||||||
localStorage.removeItem("loginEmail");
|
localStorage.removeItem("loginEmail");
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const login = await authClient.signIn.email({
|
const login = isEmailLogin
|
||||||
email: value.email,
|
? await authClient.signIn.email({
|
||||||
password: value.password,
|
email: loginValue.toLowerCase(),
|
||||||
fetchOptions: {
|
password: value.password,
|
||||||
onSuccess: () => {
|
fetchOptions: {
|
||||||
navigate({ to: redirectPath ?? "/" });
|
onSuccess: () => {
|
||||||
},
|
navigate({ to: redirectPath ?? "/" });
|
||||||
},
|
},
|
||||||
});
|
},
|
||||||
|
})
|
||||||
|
: await authClient.signIn.username({
|
||||||
|
username: loginValue,
|
||||||
|
password: value.password,
|
||||||
|
fetchOptions: {
|
||||||
|
onSuccess: () => {
|
||||||
|
navigate({ to: redirectPath ?? "/" });
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
if (login.error) {
|
if (login.error) {
|
||||||
toast.error(`${login.error?.message}`);
|
toast.error(`${login.error?.message}`);
|
||||||
@@ -95,11 +108,11 @@ export default function LoginForm({ redirectPath }: { redirectPath: string }) {
|
|||||||
form.handleSubmit();
|
form.handleSubmit();
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<form.AppField name="email">
|
<form.AppField name="login">
|
||||||
{(field) => (
|
{(field) => (
|
||||||
<field.InputField
|
<field.InputField
|
||||||
label="Email"
|
label="Username or Email Address"
|
||||||
inputType="email"
|
inputType="text"
|
||||||
required={rememberMe}
|
required={rememberMe}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import {
|
|||||||
} from "@/components/ui/card";
|
} from "@/components/ui/card";
|
||||||
import { authClient } from "@/lib/auth-client";
|
import { authClient } from "@/lib/auth-client";
|
||||||
import { useAppForm } from "@/lib/formSutff";
|
import { useAppForm } from "@/lib/formSutff";
|
||||||
|
import { Separator } from "../../components/ui/separator";
|
||||||
|
|
||||||
export const Route = createFileRoute("/(auth)/user/signup")({
|
export const Route = createFileRoute("/(auth)/user/signup")({
|
||||||
component: RouteComponent,
|
component: RouteComponent,
|
||||||
@@ -22,6 +23,7 @@ function RouteComponent() {
|
|||||||
email: "",
|
email: "",
|
||||||
password: "",
|
password: "",
|
||||||
confirmPassword: "",
|
confirmPassword: "",
|
||||||
|
username: "",
|
||||||
},
|
},
|
||||||
onSubmit: async ({ value }) => {
|
onSubmit: async ({ value }) => {
|
||||||
if (value.password !== value.confirmPassword) {
|
if (value.password !== value.confirmPassword) {
|
||||||
@@ -33,6 +35,7 @@ function RouteComponent() {
|
|||||||
name: value.name,
|
name: value.name,
|
||||||
email: value.email,
|
email: value.email,
|
||||||
password: value.password,
|
password: value.password,
|
||||||
|
username: value.username ?? value.name,
|
||||||
callbackURL: `${window.location.origin}/lst/app`,
|
callbackURL: `${window.location.origin}/lst/app`,
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -71,6 +74,15 @@ function RouteComponent() {
|
|||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</form.AppField>
|
</form.AppField>
|
||||||
|
<div className="m-2">
|
||||||
|
<p>Username is option if left blank it will be your name</p>
|
||||||
|
</div>
|
||||||
|
<Separator />
|
||||||
|
<form.AppField name="username">
|
||||||
|
{(field) => (
|
||||||
|
<field.InputField label="Username" inputType="text" />
|
||||||
|
)}
|
||||||
|
</form.AppField>
|
||||||
|
|
||||||
{/* Email */}
|
{/* Email */}
|
||||||
<form.AppField name="email">
|
<form.AppField name="email">
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import Header from "@/components/Header";
|
|||||||
import { AppSidebar } from "@/components/Sidebar/sidebar";
|
import { AppSidebar } from "@/components/Sidebar/sidebar";
|
||||||
import { SidebarProvider } from "@/components/ui/sidebar";
|
import { SidebarProvider } from "@/components/ui/sidebar";
|
||||||
import { ThemeProvider } from "@/lib/theme-provider";
|
import { ThemeProvider } from "@/lib/theme-provider";
|
||||||
|
import { TooltipProvider } from "../components/ui/tooltip";
|
||||||
import { useSession } from "../lib/auth-client";
|
import { useSession } from "../lib/auth-client";
|
||||||
|
|
||||||
const RootLayout = () => {
|
const RootLayout = () => {
|
||||||
@@ -14,16 +15,17 @@ const RootLayout = () => {
|
|||||||
<ThemeProvider>
|
<ThemeProvider>
|
||||||
<SidebarProvider className="flex flex-col" defaultOpen={false}>
|
<SidebarProvider className="flex flex-col" defaultOpen={false}>
|
||||||
<Header />
|
<Header />
|
||||||
|
<TooltipProvider>
|
||||||
|
<div className="relative min-h-[calc(100svh-var(--header-height))]">
|
||||||
|
<AppSidebar />
|
||||||
|
|
||||||
<div className="relative min-h-[calc(100svh-var(--header-height))]">
|
<main className="w-full p-4">
|
||||||
<AppSidebar />
|
<div className="mx-auto w-full max-w-7xl">
|
||||||
|
<Outlet />
|
||||||
<main className="w-full p-4">
|
</div>
|
||||||
<div className="mx-auto w-full max-w-7xl">
|
</main>
|
||||||
<Outlet />
|
</div>
|
||||||
</div>
|
</TooltipProvider>
|
||||||
</main>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<Toaster expand richColors closeButton />
|
<Toaster expand richColors closeButton />
|
||||||
</SidebarProvider>
|
</SidebarProvider>
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import {
|
|||||||
DialogHeader,
|
DialogHeader,
|
||||||
DialogTitle,
|
DialogTitle,
|
||||||
} from "../../../components/ui/dialog";
|
} from "../../../components/ui/dialog";
|
||||||
|
import { api } from "../../../lib/apiHelper";
|
||||||
import { useAppForm } from "../../../lib/formSutff";
|
import { useAppForm } from "../../../lib/formSutff";
|
||||||
import { getScannerIds } from "../../../lib/queries/getScannerIds";
|
import { getScannerIds } from "../../../lib/queries/getScannerIds";
|
||||||
|
|
||||||
@@ -31,7 +32,7 @@ export default function NewScanUser({ refetch }: { refetch: any }) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const { data } = await axios.post(
|
const { data } = await api.post(
|
||||||
"/lst/api/mobile/auth/user",
|
"/lst/api/mobile/auth/user",
|
||||||
{
|
{
|
||||||
name: value.name,
|
name: value.name,
|
||||||
|
|||||||
153
frontend/src/routes/admin/-components/Newuser.tsx
Normal file
153
frontend/src/routes/admin/-components/Newuser.tsx
Normal file
@@ -0,0 +1,153 @@
|
|||||||
|
import { useState } from "react";
|
||||||
|
import { toast } from "sonner";
|
||||||
|
import { Button } from "../../../components/ui/button";
|
||||||
|
import {
|
||||||
|
Dialog,
|
||||||
|
DialogContent,
|
||||||
|
DialogDescription,
|
||||||
|
DialogHeader,
|
||||||
|
DialogTitle,
|
||||||
|
} from "../../../components/ui/dialog";
|
||||||
|
import { authClient } from "../../../lib/auth-client";
|
||||||
|
import { selectableRoles } from "../../../lib/auth-permissions";
|
||||||
|
import { useAppForm } from "../../../lib/formSutff";
|
||||||
|
|
||||||
|
export default function NewUser({ refetch }: { refetch: any }) {
|
||||||
|
const [open, setOpen] = useState(false);
|
||||||
|
|
||||||
|
const form = useAppForm({
|
||||||
|
defaultValues: {
|
||||||
|
name: "",
|
||||||
|
email: "",
|
||||||
|
password: "",
|
||||||
|
role: "",
|
||||||
|
username: "",
|
||||||
|
},
|
||||||
|
onSubmit: async ({ value }) => {
|
||||||
|
if (value.name === "" || value.email === "" || value.password === "") {
|
||||||
|
toast.error("Missing Mandatory data please try again ");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const { data, error } = await authClient.admin.createUser({
|
||||||
|
email: value.email, // required
|
||||||
|
password: value.password, // required
|
||||||
|
name: value.name, // required
|
||||||
|
role: (value.role ?? "user") as any,
|
||||||
|
data: { username: value.username },
|
||||||
|
});
|
||||||
|
|
||||||
|
if (data?.user) {
|
||||||
|
toast.success(`${value.name}, was just created `);
|
||||||
|
form.reset();
|
||||||
|
setOpen(false);
|
||||||
|
refetch();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (error) {
|
||||||
|
toast.error(error.message);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const closeModel = (e: boolean) => {
|
||||||
|
setOpen(e);
|
||||||
|
|
||||||
|
if (!e) {
|
||||||
|
form.reset();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const openForm = () => {
|
||||||
|
setOpen(true);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Dialog onOpenChange={(e) => closeModel(e)} open={open}>
|
||||||
|
<Button onClick={openForm}>Create new user</Button>
|
||||||
|
|
||||||
|
<DialogContent showCloseButton={false}>
|
||||||
|
<DialogHeader>
|
||||||
|
<DialogTitle>Create New Scan user.</DialogTitle>
|
||||||
|
<DialogDescription></DialogDescription>
|
||||||
|
</DialogHeader>
|
||||||
|
<form
|
||||||
|
onSubmit={(e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
form.handleSubmit();
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div className="mb-2">
|
||||||
|
<form.AppField name="name">
|
||||||
|
{(field) => (
|
||||||
|
<field.InputField
|
||||||
|
label="Name"
|
||||||
|
inputType="text"
|
||||||
|
required={true}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</form.AppField>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<p>
|
||||||
|
Username can be your windows or anything, if you do not fill this
|
||||||
|
out your name is used as your username
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div className="mb-2">
|
||||||
|
<form.AppField name="username">
|
||||||
|
{(field) => (
|
||||||
|
<field.InputField label="Username" inputType="text" />
|
||||||
|
)}
|
||||||
|
</form.AppField>
|
||||||
|
</div>
|
||||||
|
<div className="mb-2">
|
||||||
|
<form.AppField name="email">
|
||||||
|
{(field) => (
|
||||||
|
<field.InputField
|
||||||
|
label="Email"
|
||||||
|
inputType="email"
|
||||||
|
required={true}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</form.AppField>
|
||||||
|
</div>
|
||||||
|
<div className="mb-2">
|
||||||
|
<form.AppField name="password">
|
||||||
|
{(field) => (
|
||||||
|
<field.InputField
|
||||||
|
label="Password"
|
||||||
|
inputType="text"
|
||||||
|
required={true}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</form.AppField>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="w-32">
|
||||||
|
<form.AppField name="role">
|
||||||
|
{(field) => (
|
||||||
|
<field.SelectField
|
||||||
|
label="Roles"
|
||||||
|
placeholder="Select role"
|
||||||
|
options={selectableRoles}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</form.AppField>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="flex justify-end mt-2 ">
|
||||||
|
<form.AppForm>
|
||||||
|
<form.SubmitButton>Submit</form.SubmitButton>
|
||||||
|
</form.AppForm>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</DialogContent>
|
||||||
|
</Dialog>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -21,6 +21,7 @@ import {
|
|||||||
TooltipContent,
|
TooltipContent,
|
||||||
TooltipTrigger,
|
TooltipTrigger,
|
||||||
} from "../../components/ui/tooltip";
|
} from "../../components/ui/tooltip";
|
||||||
|
import { api } from "../../lib/apiHelper";
|
||||||
import { authClient } from "../../lib/auth-client";
|
import { authClient } from "../../lib/auth-client";
|
||||||
import { notificationSubs } from "../../lib/queries/notificationSubs";
|
import { notificationSubs } from "../../lib/queries/notificationSubs";
|
||||||
import { notifications } from "../../lib/queries/notifications";
|
import { notifications } from "../../lib/queries/notifications";
|
||||||
@@ -36,7 +37,7 @@ const updateNotifications = async (
|
|||||||
//console.log(id, data);
|
//console.log(id, data);
|
||||||
try {
|
try {
|
||||||
const res = await axios.patch(
|
const res = await axios.patch(
|
||||||
`/lst/api/notification/${id}`,
|
`/notification/${id}`,
|
||||||
{ interval: data.interval },
|
{ interval: data.interval },
|
||||||
{
|
{
|
||||||
withCredentials: true,
|
withCredentials: true,
|
||||||
@@ -110,7 +111,7 @@ const NotificationTable = () => {
|
|||||||
|
|
||||||
const removeNotification = async (ns: any) => {
|
const removeNotification = async (ns: any) => {
|
||||||
try {
|
try {
|
||||||
const res = await axios.delete(`/lst/api/notification/sub`, {
|
const res = await api.delete(`/notification/sub`, {
|
||||||
withCredentials: true,
|
withCredentials: true,
|
||||||
data: {
|
data: {
|
||||||
userId: ns.userId,
|
userId: ns.userId,
|
||||||
@@ -168,7 +169,7 @@ const NotificationTable = () => {
|
|||||||
setActiveToggle(e);
|
setActiveToggle(e);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const res = await axios.patch(
|
const res = await api.patch(
|
||||||
`/lst/api/notification/${i.row.original.id}`,
|
`/lst/api/notification/${i.row.original.id}`,
|
||||||
{
|
{
|
||||||
active: !activeToggle,
|
active: !activeToggle,
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import { Suspense, useState } from "react";
|
|||||||
import { toast } from "sonner";
|
import { toast } from "sonner";
|
||||||
import { Button } from "../../components/ui/button";
|
import { Button } from "../../components/ui/button";
|
||||||
import { Spinner } from "../../components/ui/spinner";
|
import { Spinner } from "../../components/ui/spinner";
|
||||||
|
import { api } from "../../lib/apiHelper";
|
||||||
import { authClient } from "../../lib/auth-client";
|
import { authClient } from "../../lib/auth-client";
|
||||||
import { getScanUsers } from "../../lib/queries/getScanUsers";
|
import { getScanUsers } from "../../lib/queries/getScanUsers";
|
||||||
import EditableCellInput from "../../lib/tableStuff/EditableCellInput";
|
import EditableCellInput from "../../lib/tableStuff/EditableCellInput";
|
||||||
@@ -19,7 +20,13 @@ import NewScanUser from "./-components/NewScanUser";
|
|||||||
export const Route = createFileRoute("/admin/scanUsers")({
|
export const Route = createFileRoute("/admin/scanUsers")({
|
||||||
beforeLoad: async ({ location }) => {
|
beforeLoad: async ({ location }) => {
|
||||||
const { data: session } = await authClient.getSession();
|
const { data: session } = await authClient.getSession();
|
||||||
const allowedRole = ["systemAdmin", "admin", "manager"];
|
//const allowedRole = ["systemAdmin", "admin", "manager"];
|
||||||
|
|
||||||
|
const canAccess = await authClient.admin.hasPermission({
|
||||||
|
permissions: {
|
||||||
|
mobile: ["create"],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
if (!session?.user) {
|
if (!session?.user) {
|
||||||
throw redirect({
|
throw redirect({
|
||||||
@@ -30,7 +37,9 @@ export const Route = createFileRoute("/admin/scanUsers")({
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!allowedRole.includes(session.user.role as string)) {
|
//if (!allowedRole.includes(session.user.role as string)) {
|
||||||
|
|
||||||
|
if (!canAccess) {
|
||||||
throw redirect({
|
throw redirect({
|
||||||
to: "/",
|
to: "/",
|
||||||
});
|
});
|
||||||
@@ -47,7 +56,7 @@ const updateSettings = async (
|
|||||||
) => {
|
) => {
|
||||||
//console.log(id, data);
|
//console.log(id, data);
|
||||||
try {
|
try {
|
||||||
const res = await axios.patch(`/lst/api/mobile/auth/user/${id}`, data, {
|
const res = await axios.patch(`/mobile/auth/user/${id}`, data, {
|
||||||
withCredentials: true,
|
withCredentials: true,
|
||||||
timeout: 15000,
|
timeout: 15000,
|
||||||
validateStatus: () => true,
|
validateStatus: () => true,
|
||||||
@@ -123,7 +132,7 @@ const ScanUserTable = () => {
|
|||||||
<Button
|
<Button
|
||||||
type="button"
|
type="button"
|
||||||
onClick={async () => {
|
onClick={async () => {
|
||||||
const { data } = await axios.get("/lst/api/mobile/pin/new");
|
const { data } = await api.get("/mobile/pin/new");
|
||||||
updateSetting.mutate({
|
updateSetting.mutate({
|
||||||
id: row.original.id,
|
id: row.original.id,
|
||||||
field: "pinNumber",
|
field: "pinNumber",
|
||||||
@@ -171,7 +180,7 @@ const ScanUserTable = () => {
|
|||||||
setActiveToggle(true);
|
setActiveToggle(true);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const res = await axios.delete(
|
const res = await api.delete(
|
||||||
`/lst/api/mobile/auth/user/${i.row.original.id}`,
|
`/lst/api/mobile/auth/user/${i.row.original.id}`,
|
||||||
|
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { useSuspenseQuery } from "@tanstack/react-query";
|
import { useSuspenseQuery } from "@tanstack/react-query";
|
||||||
import { createFileRoute, redirect } from "@tanstack/react-router";
|
import { createFileRoute, redirect } from "@tanstack/react-router";
|
||||||
import { createColumnHelper } from "@tanstack/react-table";
|
import { createColumnHelper } from "@tanstack/react-table";
|
||||||
import axios from "axios";
|
|
||||||
import { format } from "date-fns-tz";
|
import { format } from "date-fns-tz";
|
||||||
import { CircleFadingArrowUp, Trash } from "lucide-react";
|
import { CircleFadingArrowUp, Trash } from "lucide-react";
|
||||||
import { Suspense, useState } from "react";
|
import { Suspense, useState } from "react";
|
||||||
@@ -14,6 +14,7 @@ import {
|
|||||||
TooltipTrigger,
|
TooltipTrigger,
|
||||||
} from "../../components/ui/tooltip";
|
} from "../../components/ui/tooltip";
|
||||||
import { useSocketRoom } from "../../hooks/socket.io.hook";
|
import { useSocketRoom } from "../../hooks/socket.io.hook";
|
||||||
|
import { api } from "../../lib/apiHelper";
|
||||||
import { authClient } from "../../lib/auth-client";
|
import { authClient } from "../../lib/auth-client";
|
||||||
import { servers } from "../../lib/queries/servers";
|
import { servers } from "../../lib/queries/servers";
|
||||||
import LstTable from "../../lib/tableStuff/LstTable";
|
import LstTable from "../../lib/tableStuff/LstTable";
|
||||||
@@ -111,19 +112,20 @@ const ServerTable = () => {
|
|||||||
const [activeToggle, setActiveToggle] = useState(false);
|
const [activeToggle, setActiveToggle] = useState(false);
|
||||||
|
|
||||||
const onToggle = async () => {
|
const onToggle = async () => {
|
||||||
setActiveToggle(true);
|
|
||||||
toast.success(
|
toast.success(
|
||||||
`${i.row.original.name} just started the upgrade monitor logs for errors.`,
|
`${i.row.original.name} just started the upgrade monitor logs for errors.`,
|
||||||
);
|
);
|
||||||
|
setActiveToggle(activeToggle);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const res = await axios.post(
|
const res = await api.post(
|
||||||
`/lst/api/admin/build/updateServer`,
|
`/admin/build/updateServer`,
|
||||||
{
|
{
|
||||||
server: i.row.original.server,
|
server: i.row.original.server,
|
||||||
destination: i.row.original.serverLoc,
|
destination: i.row.original.serverLoc,
|
||||||
token: i.row.original.plantToken,
|
token: i.row.original.plantToken,
|
||||||
},
|
},
|
||||||
{ withCredentials: true },
|
{ withCredentials: true, timeout: 5 * 60 * 1000 },
|
||||||
);
|
);
|
||||||
|
|
||||||
if (res.data.success) {
|
if (res.data.success) {
|
||||||
@@ -218,8 +220,8 @@ function RouteComponent() {
|
|||||||
];
|
];
|
||||||
const triggerBuild = async () => {
|
const triggerBuild = async () => {
|
||||||
try {
|
try {
|
||||||
const res = await axios.post(
|
const res = await api.post(
|
||||||
`/lst/api/admin/build/release`,
|
`/admin/build/release`,
|
||||||
|
|
||||||
{
|
{
|
||||||
withCredentials: true,
|
withCredentials: true,
|
||||||
|
|||||||
@@ -1,8 +1,6 @@
|
|||||||
import { useMutation, useSuspenseQuery } from "@tanstack/react-query";
|
import { useMutation, useSuspenseQuery } from "@tanstack/react-query";
|
||||||
import { createFileRoute, redirect } from "@tanstack/react-router";
|
import { createFileRoute, redirect } from "@tanstack/react-router";
|
||||||
import { createColumnHelper } from "@tanstack/react-table";
|
import { createColumnHelper } from "@tanstack/react-table";
|
||||||
import axios from "axios";
|
|
||||||
|
|
||||||
import { Suspense, useMemo } from "react";
|
import { Suspense, useMemo } from "react";
|
||||||
import { toast } from "sonner";
|
import { toast } from "sonner";
|
||||||
|
|
||||||
@@ -24,6 +22,7 @@ import {
|
|||||||
TooltipContent,
|
TooltipContent,
|
||||||
TooltipTrigger,
|
TooltipTrigger,
|
||||||
} from "../../components/ui/tooltip";
|
} from "../../components/ui/tooltip";
|
||||||
|
import { api } from "../../lib/apiHelper";
|
||||||
import { authClient } from "../../lib/auth-client";
|
import { authClient } from "../../lib/auth-client";
|
||||||
import { getSettings } from "../../lib/queries/getSettings";
|
import { getSettings } from "../../lib/queries/getSettings";
|
||||||
import EditableCellInput from "../../lib/tableStuff/EditableCellInput";
|
import EditableCellInput from "../../lib/tableStuff/EditableCellInput";
|
||||||
@@ -48,7 +47,7 @@ const updateSettings = async (
|
|||||||
) => {
|
) => {
|
||||||
//console.log(id, data);
|
//console.log(id, data);
|
||||||
try {
|
try {
|
||||||
const res = await axios.patch(`/lst/api/settings/${id}`, data, {
|
const res = await api.patch(`/settings/${id}`, data, {
|
||||||
withCredentials: true,
|
withCredentials: true,
|
||||||
});
|
});
|
||||||
toast.success(`Setting just updated`);
|
toast.success(`Setting just updated`);
|
||||||
|
|||||||
@@ -1,20 +1,43 @@
|
|||||||
import { useSuspenseQuery } from "@tanstack/react-query";
|
import { useMutation, useQuery, useSuspenseQuery } from "@tanstack/react-query";
|
||||||
import { createFileRoute, redirect } from "@tanstack/react-router";
|
import { createFileRoute, redirect } from "@tanstack/react-router";
|
||||||
import { createColumnHelper } from "@tanstack/react-table";
|
import { createColumnHelper } from "@tanstack/react-table";
|
||||||
import { format } from "date-fns-tz";
|
import { format } from "date-fns-tz";
|
||||||
|
import { KeyRound } from "lucide-react";
|
||||||
import { Suspense } from "react";
|
import { Suspense } from "react";
|
||||||
|
import { toast } from "sonner";
|
||||||
import { Button } from "../../components/ui/button";
|
import { Button } from "../../components/ui/button";
|
||||||
import { authClient, useSession } from "../../lib/auth-client";
|
import { Input } from "../../components/ui/input";
|
||||||
|
import {
|
||||||
|
Select,
|
||||||
|
SelectContent,
|
||||||
|
SelectItem,
|
||||||
|
SelectTrigger,
|
||||||
|
SelectValue,
|
||||||
|
} from "../../components/ui/select";
|
||||||
|
import {
|
||||||
|
Tooltip,
|
||||||
|
TooltipContent,
|
||||||
|
TooltipTrigger,
|
||||||
|
} from "../../components/ui/tooltip";
|
||||||
|
import { authClient } from "../../lib/auth-client";
|
||||||
|
import { selectableRoles } from "../../lib/auth-permissions";
|
||||||
import { getUsers } from "../../lib/queries/getUsers";
|
import { getUsers } from "../../lib/queries/getUsers";
|
||||||
|
import { permissionQuery } from "../../lib/queries/permsCheck";
|
||||||
import LstTable from "../../lib/tableStuff/LstTable";
|
import LstTable from "../../lib/tableStuff/LstTable";
|
||||||
import SearchableHeader from "../../lib/tableStuff/SearchableHeader";
|
import SearchableHeader from "../../lib/tableStuff/SearchableHeader";
|
||||||
import SkellyTable from "../../lib/tableStuff/SkellyTable";
|
import SkellyTable from "../../lib/tableStuff/SkellyTable";
|
||||||
import { trackLstEvent } from "../../lib/umami.utils";
|
import { trackLstEvent } from "../../lib/umami.utils";
|
||||||
|
import NewUser from "./-components/Newuser";
|
||||||
|
|
||||||
export const Route = createFileRoute("/admin/users")({
|
export const Route = createFileRoute("/admin/users")({
|
||||||
beforeLoad: async ({ location }) => {
|
beforeLoad: async ({ location }) => {
|
||||||
const { data: session } = await authClient.getSession();
|
const { data: session } = await authClient.getSession();
|
||||||
const allowedRole = ["systemAdmin", "admin"];
|
// const allowedRole = ["systemAdmin", "admin"];
|
||||||
|
const canAccess = await authClient.admin.hasPermission({
|
||||||
|
permissions: {
|
||||||
|
user: ["create"],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
if (!session?.user) {
|
if (!session?.user) {
|
||||||
throw redirect({
|
throw redirect({
|
||||||
@@ -25,7 +48,8 @@ export const Route = createFileRoute("/admin/users")({
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!allowedRole.includes(session.user.role as string)) {
|
//if (!allowedRole.includes(session.user.role as string)) {
|
||||||
|
if (!canAccess) {
|
||||||
throw redirect({
|
throw redirect({
|
||||||
to: "/",
|
to: "/",
|
||||||
});
|
});
|
||||||
@@ -37,8 +61,51 @@ export const Route = createFileRoute("/admin/users")({
|
|||||||
});
|
});
|
||||||
|
|
||||||
const UserTable = () => {
|
const UserTable = () => {
|
||||||
const { data } = useSuspenseQuery(getUsers());
|
const { data, refetch } = useSuspenseQuery(getUsers());
|
||||||
const { data: session } = useSession();
|
//const { data: session } = useSession();
|
||||||
|
const { data: canImpersonate = false } = useQuery(
|
||||||
|
permissionQuery({
|
||||||
|
user: ["impersonate"],
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
const { data: canUpdate = false } = useQuery(
|
||||||
|
permissionQuery({
|
||||||
|
user: ["update"],
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
const updatePassword = useMutation({
|
||||||
|
mutationFn: async ({ user, password }: { user: any; password: string }) => {
|
||||||
|
return authClient.admin.setUserPassword({
|
||||||
|
userId: user.id,
|
||||||
|
newPassword: password,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
onSuccess: () => {
|
||||||
|
toast.success("Password updated");
|
||||||
|
},
|
||||||
|
onError: (error) => {
|
||||||
|
toast.error(error.message);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const handleRoleChange = async (row: any, newRole: string) => {
|
||||||
|
//console.log("update this user", row, newRole);
|
||||||
|
|
||||||
|
const { data, error } = await authClient.admin.updateUser({
|
||||||
|
userId: row.id,
|
||||||
|
data: { role: newRole },
|
||||||
|
});
|
||||||
|
|
||||||
|
if (error) {
|
||||||
|
console.error(error);
|
||||||
|
toast.error(error.message);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
toast.success(`${data.name}, role was just changed to: ${newRole}`);
|
||||||
|
refetch();
|
||||||
|
};
|
||||||
|
|
||||||
const columnHelper = createColumnHelper<any>();
|
const columnHelper = createColumnHelper<any>();
|
||||||
|
|
||||||
@@ -50,6 +117,13 @@ const UserTable = () => {
|
|||||||
filterFn: "includesString",
|
filterFn: "includesString",
|
||||||
cell: (i) => i.getValue(),
|
cell: (i) => i.getValue(),
|
||||||
}),
|
}),
|
||||||
|
columnHelper.accessor("username", {
|
||||||
|
header: ({ column }) => (
|
||||||
|
<SearchableHeader column={column} title="Username" searchable={true} />
|
||||||
|
),
|
||||||
|
filterFn: "includesString",
|
||||||
|
cell: (i) => i.getValue(),
|
||||||
|
}),
|
||||||
columnHelper.accessor("email", {
|
columnHelper.accessor("email", {
|
||||||
header: ({ column }) => (
|
header: ({ column }) => (
|
||||||
<SearchableHeader column={column} title="Email" searchable={true} />
|
<SearchableHeader column={column} title="Email" searchable={true} />
|
||||||
@@ -57,27 +131,113 @@ const UserTable = () => {
|
|||||||
filterFn: "includesString",
|
filterFn: "includesString",
|
||||||
cell: (i) => i.getValue(),
|
cell: (i) => i.getValue(),
|
||||||
}),
|
}),
|
||||||
|
|
||||||
|
// columnHelper.accessor("role", {
|
||||||
|
// header: ({ column }) => (
|
||||||
|
// <SearchableHeader column={column} title="Role" searchable={false} />
|
||||||
|
// ),
|
||||||
|
// filterFn: "includesString",
|
||||||
|
// cell: (i) => i.getValue(),
|
||||||
|
// }),
|
||||||
columnHelper.accessor("role", {
|
columnHelper.accessor("role", {
|
||||||
header: ({ column }) => (
|
header: ({ column }) => <SearchableHeader column={column} title="Role" />,
|
||||||
<SearchableHeader column={column} title="Role" searchable={false} />
|
|
||||||
),
|
|
||||||
filterFn: "includesString",
|
filterFn: "includesString",
|
||||||
cell: (i) => i.getValue(),
|
cell: ({ row, getValue }) => {
|
||||||
}),
|
const currentRole = getValue();
|
||||||
columnHelper.accessor("updatedAt", {
|
|
||||||
header: ({ column }) => (
|
return (
|
||||||
<SearchableHeader
|
<Select
|
||||||
column={column}
|
value={currentRole}
|
||||||
title="Updated at"
|
onValueChange={(newRole) => {
|
||||||
searchable={false}
|
handleRoleChange(row.original, newRole);
|
||||||
/>
|
}}
|
||||||
),
|
>
|
||||||
filterFn: "includesString",
|
<SelectTrigger className="w-[180px]">
|
||||||
cell: (i) => format(i.getValue(), "M/d/yyyy HH:mm"),
|
<SelectValue placeholder="Select role" />
|
||||||
|
</SelectTrigger>
|
||||||
|
|
||||||
|
<SelectContent>
|
||||||
|
{selectableRoles.map((role) => (
|
||||||
|
<SelectItem key={role.value} value={role.value}>
|
||||||
|
{role.label}
|
||||||
|
</SelectItem>
|
||||||
|
))}
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
);
|
||||||
|
},
|
||||||
}),
|
}),
|
||||||
];
|
];
|
||||||
|
|
||||||
if (session && session.user.role === "systemAdmin") {
|
if (canUpdate) {
|
||||||
|
columns.push(
|
||||||
|
columnHelper.accessor("changePassword", {
|
||||||
|
header: ({ column }) => (
|
||||||
|
<SearchableHeader column={column} title="Change Password" />
|
||||||
|
),
|
||||||
|
filterFn: "includesString",
|
||||||
|
cell: ({ row }) => {
|
||||||
|
return (
|
||||||
|
<div className="flex flex-row items-center gap-2">
|
||||||
|
<Input
|
||||||
|
type="password"
|
||||||
|
placeholder="New password"
|
||||||
|
className="w-[200px]"
|
||||||
|
onKeyDown={(e) => {
|
||||||
|
if (e.key !== "Enter") return;
|
||||||
|
|
||||||
|
const password = e.currentTarget.value.trim();
|
||||||
|
|
||||||
|
if (!password) return;
|
||||||
|
|
||||||
|
updatePassword.mutate({
|
||||||
|
user: row.original,
|
||||||
|
password,
|
||||||
|
});
|
||||||
|
|
||||||
|
e.currentTarget.value = "";
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<Tooltip>
|
||||||
|
<TooltipTrigger>
|
||||||
|
<Button
|
||||||
|
size="icon"
|
||||||
|
variant="outline"
|
||||||
|
onClick={(e) => {
|
||||||
|
const input =
|
||||||
|
e.currentTarget.parentElement?.querySelector("input");
|
||||||
|
|
||||||
|
const password = input?.value.trim();
|
||||||
|
|
||||||
|
if (!password) return;
|
||||||
|
|
||||||
|
updatePassword.mutate({
|
||||||
|
user: row.original,
|
||||||
|
password,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (input) {
|
||||||
|
input.value = "";
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<KeyRound className="h-4 w-4" />
|
||||||
|
</Button>
|
||||||
|
</TooltipTrigger>
|
||||||
|
<TooltipContent>
|
||||||
|
<p>
|
||||||
|
Update Password, fill out and press enter or update here
|
||||||
|
</p>
|
||||||
|
</TooltipContent>
|
||||||
|
</Tooltip>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (canImpersonate) {
|
||||||
columns.push(
|
columns.push(
|
||||||
columnHelper.accessor("banned", {
|
columnHelper.accessor("banned", {
|
||||||
header: ({ column }) => (
|
header: ({ column }) => (
|
||||||
@@ -126,7 +286,28 @@ const UserTable = () => {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return <LstTable data={data} columns={columns} pageSize={50} />;
|
columns.push(
|
||||||
|
columnHelper.accessor("updatedAt", {
|
||||||
|
header: ({ column }) => (
|
||||||
|
<SearchableHeader
|
||||||
|
column={column}
|
||||||
|
title="Updated at"
|
||||||
|
searchable={false}
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
filterFn: "includesString",
|
||||||
|
cell: (i) => format(i.getValue(), "M/d/yyyy HH:mm"),
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<div className="flex justify-end m-2">
|
||||||
|
<NewUser refetch={refetch} />
|
||||||
|
</div>
|
||||||
|
<LstTable data={data} columns={columns} pageSize={50} />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
function RouteComponent() {
|
function RouteComponent() {
|
||||||
|
|||||||
79
package-lock.json
generated
79
package-lock.json
generated
@@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"name": "lst_v3",
|
"name": "lst_v3",
|
||||||
"version": "0.1.0-alpha.0",
|
"version": "0.1.0-alpha.1",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "lst_v3",
|
"name": "lst_v3",
|
||||||
"version": "0.1.0-alpha.0",
|
"version": "0.1.0-alpha.1",
|
||||||
"license": "ISC",
|
"license": "ISC",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@dotenvx/dotenvx": "^1.57.0",
|
"@dotenvx/dotenvx": "^1.57.0",
|
||||||
@@ -16,6 +16,7 @@
|
|||||||
"axios": "^1.13.6",
|
"axios": "^1.13.6",
|
||||||
"bcryptjs": "^3.0.3",
|
"bcryptjs": "^3.0.3",
|
||||||
"better-auth": "^1.5.5",
|
"better-auth": "^1.5.5",
|
||||||
|
"chokidar": "^5.0.0",
|
||||||
"concurrently": "^9.2.1",
|
"concurrently": "^9.2.1",
|
||||||
"cors": "^2.8.6",
|
"cors": "^2.8.6",
|
||||||
"croner": "^10.0.1",
|
"croner": "^10.0.1",
|
||||||
@@ -3801,28 +3802,18 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/chokidar": {
|
"node_modules/chokidar": {
|
||||||
"version": "3.6.0",
|
"version": "5.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz",
|
"resolved": "https://registry.npmjs.org/chokidar/-/chokidar-5.0.0.tgz",
|
||||||
"integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==",
|
"integrity": "sha512-TQMmc3w+5AxjpL8iIiwebF73dRDF4fBIieAqGn9RGCWaEVwQ6Fb2cGe31Yns0RRIzii5goJ1Y7xbMwo1TxMplw==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"anymatch": "~3.1.2",
|
"readdirp": "^5.0.0"
|
||||||
"braces": "~3.0.2",
|
|
||||||
"glob-parent": "~5.1.2",
|
|
||||||
"is-binary-path": "~2.1.0",
|
|
||||||
"is-glob": "~4.0.1",
|
|
||||||
"normalize-path": "~3.0.0",
|
|
||||||
"readdirp": "~3.6.0"
|
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">= 8.10.0"
|
"node": ">= 20.19.0"
|
||||||
},
|
},
|
||||||
"funding": {
|
"funding": {
|
||||||
"url": "https://paulmillr.com/funding/"
|
"url": "https://paulmillr.com/funding/"
|
||||||
},
|
|
||||||
"optionalDependencies": {
|
|
||||||
"fsevents": "~2.3.2"
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/cli-cursor": {
|
"node_modules/cli-cursor": {
|
||||||
@@ -10734,16 +10725,16 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/readdirp": {
|
"node_modules/readdirp": {
|
||||||
"version": "3.6.0",
|
"version": "5.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz",
|
"resolved": "https://registry.npmjs.org/readdirp/-/readdirp-5.0.0.tgz",
|
||||||
"integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==",
|
"integrity": "sha512-9u/XQ1pvrQtYyMpZe7DXKv2p5CNvyVwzUB6uhLAnQwHMSgKMBR62lc7AHljaeteeHXn11XTAaLLUVZYVZyuRBQ==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
|
||||||
"picomatch": "^2.2.1"
|
|
||||||
},
|
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=8.10.0"
|
"node": ">= 20.19.0"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"type": "individual",
|
||||||
|
"url": "https://paulmillr.com/funding/"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/real-require": {
|
"node_modules/real-require": {
|
||||||
@@ -11978,6 +11969,44 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/ts-node-dev/node_modules/chokidar": {
|
||||||
|
"version": "3.6.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz",
|
||||||
|
"integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"anymatch": "~3.1.2",
|
||||||
|
"braces": "~3.0.2",
|
||||||
|
"glob-parent": "~5.1.2",
|
||||||
|
"is-binary-path": "~2.1.0",
|
||||||
|
"is-glob": "~4.0.1",
|
||||||
|
"normalize-path": "~3.0.0",
|
||||||
|
"readdirp": "~3.6.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 8.10.0"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://paulmillr.com/funding/"
|
||||||
|
},
|
||||||
|
"optionalDependencies": {
|
||||||
|
"fsevents": "~2.3.2"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/ts-node-dev/node_modules/readdirp": {
|
||||||
|
"version": "3.6.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz",
|
||||||
|
"integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"picomatch": "^2.2.1"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=8.10.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/tsconfig": {
|
"node_modules/tsconfig": {
|
||||||
"version": "7.0.0",
|
"version": "7.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/tsconfig/-/tsconfig-7.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/tsconfig/-/tsconfig-7.0.0.tgz",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "lst_v3",
|
"name": "lst_v3",
|
||||||
"version": "0.1.0-alpha.0",
|
"version": "0.1.0-alpha.1",
|
||||||
"description": "The tool that supports us in our everyday alplaprod",
|
"description": "The tool that supports us in our everyday alplaprod",
|
||||||
"main": "index.js",
|
"main": "index.js",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
@@ -71,6 +71,7 @@
|
|||||||
"axios": "^1.13.6",
|
"axios": "^1.13.6",
|
||||||
"bcryptjs": "^3.0.3",
|
"bcryptjs": "^3.0.3",
|
||||||
"better-auth": "^1.5.5",
|
"better-auth": "^1.5.5",
|
||||||
|
"chokidar": "^5.0.0",
|
||||||
"concurrently": "^9.2.1",
|
"concurrently": "^9.2.1",
|
||||||
"cors": "^2.8.6",
|
"cors": "^2.8.6",
|
||||||
"croner": "^10.0.1",
|
"croner": "^10.0.1",
|
||||||
|
|||||||
Reference in New Issue
Block a user