lots of changes with docker
All checks were successful
Build and Push LST Docker Image / docker (push) Successful in 2m57s

This commit is contained in:
2026-04-03 09:51:52 -05:00
parent 82ab735982
commit beae6eb648
36 changed files with 2284 additions and 36 deletions

View File

@@ -7,3 +7,6 @@ docker-compose.yml
npm-debug.log npm-debug.log
builds builds
testFiles testFiles
nssm.exe
postgresql-17.9-2-windows-x64.exe
VSCodeUserSetup-x64-1.112.0.msi

1
.gitignore vendored
View File

@@ -8,6 +8,7 @@ temp
node-v24.14.0-x64.msi node-v24.14.0-x64.msi
postgresql-17.9-2-windows-x64.exe postgresql-17.9-2-windows-x64.exe
VSCodeUserSetup-x64-1.112.0.exe VSCodeUserSetup-x64-1.112.0.exe
nssm.exe
# Logs # Logs
logs logs

View File

@@ -9,10 +9,13 @@ WORKDIR /app
# Copy package files # Copy package files
COPY . . COPY . .
# Install production dependencies only # build backend
RUN npm ci RUN npm ci
RUN npm run build:docker
RUN npm run build # build frontend
RUN npm --prefix frontend ci
RUN npm --prefix frontend run build
########### ###########
# Stage 2 # # Stage 2 #
@@ -33,6 +36,9 @@ RUN npm ci --omit=dev
COPY --from=build /app/dist ./dist COPY --from=build /app/dist ./dist
COPY --from=build /app/frontend/dist ./frontend/dist
# TODO add in drizzle migrates
ENV RUNNING_IN_DOCKER=true ENV RUNNING_IN_DOCKER=true
EXPOSE 3000 EXPOSE 3000

View File

@@ -0,0 +1,6 @@
import { integer, pgTable, text } from "drizzle-orm/pg-core";
export const opendockApt = pgTable("printer_log", {
id: integer().primaryKey().generatedAlwaysAsIdentity(),
name: text("name").notNull(),
});

View File

@@ -9,6 +9,8 @@ import { tryCatch } from "../utils/trycatch.utils.js";
const r = Router(); const r = Router();
r.get("/", async (req, res: Response) => { r.get("/", async (req, res: Response) => {
const { userId } = req.query;
const hasPermissions = await auth.api.userHasPermission({ const hasPermissions = await auth.api.userHasPermission({
body: { body: {
//userId: req?.user?.id, //userId: req?.user?.id,
@@ -24,7 +26,7 @@ r.get("/", async (req, res: Response) => {
.select() .select()
.from(notificationSub) .from(notificationSub)
.where( .where(
!hasPermissions.success userId || !hasPermissions.success
? eq(notificationSub.userId, `${req?.user?.id ?? ""}`) ? eq(notificationSub.userId, `${req?.user?.id ?? ""}`)
: undefined, : undefined,
), ),
@@ -47,7 +49,7 @@ r.get("/", async (req, res: Response) => {
level: "info", level: "info",
module: "notification", module: "notification",
subModule: "post", subModule: "post",
message: `Subscription deleted`, message: `Subscriptions`,
data: data ?? [], data: data ?? [],
status: 200, status: 200,
}); });

View File

@@ -0,0 +1,36 @@
/**
* the route that listens for the printers post.
*
* and http-post alert should be setup on each printer pointing to at min you will want to make the alert for
* pause printer, you can have all on here as it will also monitor and do things on all messages
*
* http://{serverIP}:2222/lst/api/ocp/printer/listener/{printerName}
*
* the messages will be sent over to the db for logging as well as specific ones will do something
*
* pause will validate if can print
* close head will repause the printer so it wont print a label
* power up will just repause the printer so it wont print a label
*/
import { Router } from "express";
import { apiReturn } from "../utils/returnHelper.utils.js";
const r = Router();
r.post("/printer/listener/:printer", async (req, res) => {
const { printer: printerName } = req.params;
console.log(req.body);
return apiReturn(res, {
success: true,
level: "info",
module: "ocp",
subModule: "printing",
message: `${printerName} just passed over a message`,
data: req.body ?? [],
status: 200,
});
});
export default r;

View File

@@ -0,0 +1,19 @@
/**
* this will do a prod sync, update or add alerts to the printer, validate the next pm intervale as well as head replacement.
*
* if a printer is upcoming on a pm or head replacement send to the plant to address.
*
* a trigger on the printer table will have the ability to run this as well
*
* heat beats on all assigned printers
*
* printer status will live here this will be how we manage all the levels of status like 3 paused, 1 printing, 8 error, 10 power up, etc...
*/
export const printerManager = async () => {};
export const printerHeartBeat = async () => {
// heat heats will be defaulted to 60 seconds no reason to allow anything else
};
//export const printerStatus = async (statusNr: number, printerId: number) => {};

22
backend/ocp/ocp.routes.ts Normal file
View File

@@ -0,0 +1,22 @@
import { type Express, Router } from "express";
import { requireAuth } from "../middleware/auth.middleware.js";
import { featureCheck } from "../middleware/featureActive.middleware.js";
import listener from "./ocp.printer.listener.js";
export const setupOCPRoutes = (baseUrl: string, app: Express) => {
//setup all the routes
const router = Router();
// is the feature even on?
router.use(featureCheck("ocp"));
// non auth routes up here
router.use(listener);
// auth routes below here
router.use(requireAuth);
//router.use("");
app.use(`${baseUrl}/api/ocp`, router);
};

View File

@@ -5,6 +5,7 @@ import { setupAuthRoutes } from "./auth/auth.routes.js";
import { setupApiDocsRoutes } from "./configs/scaler.config.js"; import { setupApiDocsRoutes } from "./configs/scaler.config.js";
import { setupDatamartRoutes } from "./datamart/datamart.routes.js"; import { setupDatamartRoutes } from "./datamart/datamart.routes.js";
import { setupNotificationRoutes } from "./notification/notification.routes.js"; import { setupNotificationRoutes } from "./notification/notification.routes.js";
import { setupOCPRoutes } from "./ocp/ocp.routes.js";
import { setupOpendockRoutes } from "./opendock/opendock.routes.js"; import { setupOpendockRoutes } from "./opendock/opendock.routes.js";
import { setupProdSqlRoutes } from "./prodSql/prodSql.routes.js"; import { setupProdSqlRoutes } from "./prodSql/prodSql.routes.js";
import { setupSystemRoutes } from "./system/system.routes.js"; import { setupSystemRoutes } from "./system/system.routes.js";
@@ -20,4 +21,5 @@ export const setupRoutes = (baseUrl: string, app: Express) => {
setupUtilsRoutes(baseUrl, app); setupUtilsRoutes(baseUrl, app);
setupOpendockRoutes(baseUrl, app); setupOpendockRoutes(baseUrl, app);
setupNotificationRoutes(baseUrl, app); setupNotificationRoutes(baseUrl, app);
setupOCPRoutes(baseUrl, app);
}; };

View File

@@ -9,7 +9,7 @@ const newSettings: NewSetting[] = [
{ {
name: "opendock_sync", name: "opendock_sync",
value: "0", value: "0",
active: true, active: false,
description: "Dock Scheduling system", description: "Dock Scheduling system",
moduleName: "opendock", moduleName: "opendock",
settingType: "feature", settingType: "feature",
@@ -19,7 +19,7 @@ const newSettings: NewSetting[] = [
{ {
name: "ocp", name: "ocp",
value: "1", value: "1",
active: true, active: false,
description: "One click print", description: "One click print",
moduleName: "ocp", moduleName: "ocp",
settingType: "feature", settingType: "feature",
@@ -29,7 +29,7 @@ const newSettings: NewSetting[] = [
{ {
name: "ocme", name: "ocme",
value: "0", value: "0",
active: true, active: false,
description: "Dayton Agv system", description: "Dayton Agv system",
moduleName: "ocme", moduleName: "ocme",
settingType: "feature", settingType: "feature",
@@ -39,7 +39,7 @@ const newSettings: NewSetting[] = [
{ {
name: "demandManagement", name: "demandManagement",
value: "1", value: "1",
active: true, active: false,
description: "Fake EDI System", description: "Fake EDI System",
moduleName: "demandManagement", moduleName: "demandManagement",
settingType: "feature", settingType: "feature",
@@ -49,7 +49,7 @@ const newSettings: NewSetting[] = [
{ {
name: "qualityRequest", name: "qualityRequest",
value: "0", value: "0",
active: true, active: false,
description: "Quality System", description: "Quality System",
moduleName: "qualityRequest", moduleName: "qualityRequest",
settingType: "feature", settingType: "feature",
@@ -59,7 +59,7 @@ const newSettings: NewSetting[] = [
{ {
name: "tms", name: "tms",
value: "0", value: "0",
active: true, active: false,
description: "Transport system integration", description: "Transport system integration",
moduleName: "tms", moduleName: "tms",
settingType: "feature", settingType: "feature",

View File

@@ -15,6 +15,7 @@ export const allowedOrigins = [
`http://${process.env.PROD_SERVER}:3000`, `http://${process.env.PROD_SERVER}:3000`,
`http://${process.env.PROD_SERVER}:3100`, // temp `http://${process.env.PROD_SERVER}:3100`, // temp
`http://usmcd1olp082:3000`, `http://usmcd1olp082:3000`,
`http://localhost:3600`, // internal docker
]; ];
export const lstCors = () => { export const lstCors = () => {
return cors({ return cors({

View File

@@ -28,7 +28,8 @@ interface Data<T = unknown[]> {
| "delete" | "delete"
| "post" | "post"
| "notification" | "notification"
| "delete"; | "delete"
| "printing";
level: "info" | "error" | "debug" | "fatal"; level: "info" | "error" | "debug" | "fatal";
message: string; message: string;
room?: string; room?: string;

View File

@@ -14,7 +14,7 @@ body:json {
{ {
"userId":"m6AbQXFwOXoX3YKLfwWgq2LIdDqS5jqv", "userId":"m6AbQXFwOXoX3YKLfwWgq2LIdDqS5jqv",
"notificationId": "0399eb2a-39df-48b7-9f1c-d233cec94d2e", "notificationId": "0399eb2a-39df-48b7-9f1c-d233cec94d2e",
"emails": ["blake.mattes@alpla.com"] "emails": ["blake.mattes@alpla.com","cowchmonkey@gmail.com"]
} }
} }

View File

@@ -0,0 +1,22 @@
meta {
name: Printer Listenter
type: http
seq: 1
}
post {
url: {{url}}/api/ocp/printer/listener/line_1
body: json
auth: inherit
}
body:json {
{
"message":"xnvjdhhgsdfr"
}
}
settings {
encodeUrl: true
timeout: 0
}

8
brunoApi/ocp/folder.bru Normal file
View File

@@ -0,0 +1,8 @@
meta {
name: ocp
seq: 9
}
auth {
mode: inherit
}

View File

@@ -1,16 +1,20 @@
services: services:
app: lst:
build: image: git.tuffraid.net/cowch/lst_v3:latest
context: .
dockerfile: Dockerfile
container_name: lst_app container_name: lst_app
restart: unless-stopped
# app:
# build:
# context: .
# dockerfile: Dockerfile
# container_name: lst_app
ports: ports:
#- "${VITE_PORT:-4200}:4200" #- "${VITE_PORT:-4200}:4200"
- "3600:3000" - "3600:3000"
environment: environment:
- NODE_ENV=production - NODE_ENV=production
- LOG_LEVEL=info - LOG_LEVEL=info
- DATABASE_HOST=host.docker.internal - DATABASE_HOST=host.docker.internal # if running on the same docker then do this
- DATABASE_PORT=5433 - DATABASE_PORT=5433
- DATABASE_USER=${DATABASE_USER} - DATABASE_USER=${DATABASE_USER}
- DATABASE_PASSWORD=${DATABASE_PASSWORD} - DATABASE_PASSWORD=${DATABASE_PASSWORD}

View File

@@ -11,15 +11,15 @@ import {
useSidebar, useSidebar,
} from "../ui/sidebar"; } from "../ui/sidebar";
type AdminSidebarProps = { // type AdminSidebarProps = {
session: { // session: {
user: { // user: {
name?: string | null; // name?: string | null;
email?: string | null; // email?: string | null;
role?: string | string[]; // role?: string | string[];
}; // };
} | null; // } | null;
}; //};
export default function AdminSidebar({ session }: any) { export default function AdminSidebar({ session }: any) {
const { setOpen } = useSidebar(); const { setOpen } = useSidebar();

View File

@@ -0,0 +1,190 @@
import * as React from "react"
import { Select as SelectPrimitive } from "radix-ui"
import { cn } from "@/lib/utils"
import { ChevronDownIcon, CheckIcon, ChevronUpIcon } from "lucide-react"
function Select({
...props
}: React.ComponentProps<typeof SelectPrimitive.Root>) {
return <SelectPrimitive.Root data-slot="select" {...props} />
}
function SelectGroup({
className,
...props
}: React.ComponentProps<typeof SelectPrimitive.Group>) {
return (
<SelectPrimitive.Group
data-slot="select-group"
className={cn("scroll-my-1 p-1", className)}
{...props}
/>
)
}
function SelectValue({
...props
}: React.ComponentProps<typeof SelectPrimitive.Value>) {
return <SelectPrimitive.Value data-slot="select-value" {...props} />
}
function SelectTrigger({
className,
size = "default",
children,
...props
}: React.ComponentProps<typeof SelectPrimitive.Trigger> & {
size?: "sm" | "default"
}) {
return (
<SelectPrimitive.Trigger
data-slot="select-trigger"
data-size={size}
className={cn(
"flex w-fit items-center justify-between gap-1.5 rounded-lg border border-input bg-transparent py-2 pr-2 pl-2.5 text-sm whitespace-nowrap transition-colors outline-none select-none focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 disabled:cursor-not-allowed disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-3 aria-invalid:ring-destructive/20 data-placeholder:text-muted-foreground data-[size=default]:h-8 data-[size=sm]:h-7 data-[size=sm]:rounded-[min(var(--radius-md),10px)] *:data-[slot=select-value]:line-clamp-1 *:data-[slot=select-value]:flex *:data-[slot=select-value]:items-center *:data-[slot=select-value]:gap-1.5 dark:bg-input/30 dark:hover:bg-input/50 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
className
)}
{...props}
>
{children}
<SelectPrimitive.Icon asChild>
<ChevronDownIcon className="pointer-events-none size-4 text-muted-foreground" />
</SelectPrimitive.Icon>
</SelectPrimitive.Trigger>
)
}
function SelectContent({
className,
children,
position = "item-aligned",
align = "center",
...props
}: React.ComponentProps<typeof SelectPrimitive.Content>) {
return (
<SelectPrimitive.Portal>
<SelectPrimitive.Content
data-slot="select-content"
data-align-trigger={position === "item-aligned"}
className={cn("relative z-50 max-h-(--radix-select-content-available-height) min-w-36 origin-(--radix-select-content-transform-origin) overflow-x-hidden overflow-y-auto rounded-lg bg-popover text-popover-foreground shadow-md ring-1 ring-foreground/10 duration-100 data-[align-trigger=true]:animate-none 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-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", position ==="popper"&&"data-[side=bottom]:translate-y-1 data-[side=left]:-translate-x-1 data-[side=right]:translate-x-1 data-[side=top]:-translate-y-1", className )}
position={position}
align={align}
{...props}
>
<SelectScrollUpButton />
<SelectPrimitive.Viewport
data-position={position}
className={cn(
"data-[position=popper]:h-(--radix-select-trigger-height) data-[position=popper]:w-full data-[position=popper]:min-w-(--radix-select-trigger-width)",
position === "popper" && ""
)}
>
{children}
</SelectPrimitive.Viewport>
<SelectScrollDownButton />
</SelectPrimitive.Content>
</SelectPrimitive.Portal>
)
}
function SelectLabel({
className,
...props
}: React.ComponentProps<typeof SelectPrimitive.Label>) {
return (
<SelectPrimitive.Label
data-slot="select-label"
className={cn("px-1.5 py-1 text-xs text-muted-foreground", className)}
{...props}
/>
)
}
function SelectItem({
className,
children,
...props
}: React.ComponentProps<typeof SelectPrimitive.Item>) {
return (
<SelectPrimitive.Item
data-slot="select-item"
className={cn(
"relative flex w-full cursor-default items-center gap-1.5 rounded-md py-1 pr-8 pl-1.5 text-sm outline-hidden select-none focus:bg-accent focus:text-accent-foreground not-data-[variant=destructive]:focus:**:text-accent-foreground data-disabled:pointer-events-none data-disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4 *:[span]:last:flex *:[span]:last:items-center *:[span]:last:gap-2",
className
)}
{...props}
>
<span className="pointer-events-none absolute right-2 flex size-4 items-center justify-center">
<SelectPrimitive.ItemIndicator>
<CheckIcon className="pointer-events-none" />
</SelectPrimitive.ItemIndicator>
</span>
<SelectPrimitive.ItemText>{children}</SelectPrimitive.ItemText>
</SelectPrimitive.Item>
)
}
function SelectSeparator({
className,
...props
}: React.ComponentProps<typeof SelectPrimitive.Separator>) {
return (
<SelectPrimitive.Separator
data-slot="select-separator"
className={cn("pointer-events-none -mx-1 my-1 h-px bg-border", className)}
{...props}
/>
)
}
function SelectScrollUpButton({
className,
...props
}: React.ComponentProps<typeof SelectPrimitive.ScrollUpButton>) {
return (
<SelectPrimitive.ScrollUpButton
data-slot="select-scroll-up-button"
className={cn(
"z-10 flex cursor-default items-center justify-center bg-popover py-1 [&_svg:not([class*='size-'])]:size-4",
className
)}
{...props}
>
<ChevronUpIcon
/>
</SelectPrimitive.ScrollUpButton>
)
}
function SelectScrollDownButton({
className,
...props
}: React.ComponentProps<typeof SelectPrimitive.ScrollDownButton>) {
return (
<SelectPrimitive.ScrollDownButton
data-slot="select-scroll-down-button"
className={cn(
"z-10 flex cursor-default items-center justify-center bg-popover py-1 [&_svg:not([class*='size-'])]:size-4",
className
)}
{...props}
>
<ChevronDownIcon
/>
</SelectPrimitive.ScrollDownButton>
)
}
export {
Select,
SelectContent,
SelectGroup,
SelectItem,
SelectLabel,
SelectScrollDownButton,
SelectScrollUpButton,
SelectSeparator,
SelectTrigger,
SelectValue,
}

View File

@@ -0,0 +1,87 @@
import { Trash2 } from "lucide-react";
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
import { Button } from "../../components/ui/button";
import { useFieldContext } from ".";
import { FieldErrors } from "./Errors.Field";
type DynamicInputField = {
name?: string;
label: string;
inputType: "text" | "email" | "password" | "number" | "username";
required?: boolean;
description?: string;
addLabel?: string;
placeholder?: string;
disabled?: boolean;
};
const autoCompleteMap: Record<string, string> = {
email: "email",
password: "current-password",
text: "off",
username: "username",
};
export const DynamicInputField = ({
label,
inputType = "text",
required = false,
description,
addLabel,
}: DynamicInputField) => {
const field = useFieldContext<any>();
const values = Array.isArray(field.state.value) ? field.state.value : [];
return (
<div className="grid gap-3 mt-2">
<div className="flex items-start justify-between gap-4">
<div className="space-y-1">
<Label>{label}</Label>
{description ? (
<p className="text-sm text-muted-foreground">{description}</p>
) : null}
</div>
<Button
type="button"
variant="secondary"
onClick={() => {
field.pushValue("");
}}
>
{addLabel}
</Button>
</div>
<div className="grid gap-3">
{values.map((_: string, index: number) => (
<div key={`${field.name}-${index}`} className="grid gap-2">
<div className="flex items-center gap-2">
<Label htmlFor={field.name}>{label}</Label>
<Input
id={field.name}
autoComplete={autoCompleteMap[inputType] ?? "off"}
value={field.state.value?.[index] ?? ""}
onChange={(e) => field.replaceValue(index, e.target.value)}
onBlur={field.handleBlur}
type={inputType}
required={required}
/>
{values.length > 1 ? (
<Button
type="button"
size={"icon"}
variant="destructive"
onClick={() => field.removeValue(index)}
>
<Trash2 className="w-32 h-32" />
</Button>
) : null}
<FieldErrors meta={field.state.meta} />
</div>
</div>
))}
</div>
</div>
);
};

View File

@@ -0,0 +1,57 @@
import { Label } from "../../components/ui/label";
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from "../../components/ui/select";
import { useFieldContext } from ".";
import { FieldErrors } from "./Errors.Field";
type SelectOption = {
value: string;
label: string;
};
type SelectFieldProps = {
label: string;
options: SelectOption[];
placeholder?: string;
};
export const SelectField = ({
label,
options,
placeholder,
}: SelectFieldProps) => {
const field = useFieldContext<string>();
return (
<div className="grid gap-3">
<div className="grid gap-3">
<Label htmlFor={field.name}>{label}</Label>
<Select
value={field.state.value}
onValueChange={(value) => field.handleChange(value)}
>
<SelectTrigger
id={field.name}
onBlur={field.handleBlur}
className="w-min-2/3 w-max-fit"
>
<SelectValue placeholder={placeholder} />
</SelectTrigger>
<SelectContent>
{options.map((option) => (
<SelectItem key={option.value} value={option.value}>
{option.label}
</SelectItem>
))}
</SelectContent>
</Select>
</div>
<FieldErrors meta={field.state.meta} />
</div>
);
};

View File

@@ -1,7 +1,9 @@
import { createFormHook, createFormHookContexts } from "@tanstack/react-form"; import { createFormHook, createFormHookContexts } from "@tanstack/react-form";
import { CheckboxField } from "./CheckBox.Field"; import { CheckboxField } from "./CheckBox.Field";
import { DynamicInputField } from "./DynamicInput.Field";
import { InputField } from "./Input.Field"; import { InputField } from "./Input.Field";
import { InputPasswordField } from "./InputPassword.Field"; import { InputPasswordField } from "./InputPassword.Field";
import { SelectField } from "./Select.Field";
import { SubmitButton } from "./SubmitButton"; import { SubmitButton } from "./SubmitButton";
import { SwitchField } from "./Switch.Field"; import { SwitchField } from "./Switch.Field";
@@ -12,12 +14,13 @@ export const { useAppForm } = createFormHook({
fieldComponents: { fieldComponents: {
InputField, InputField,
InputPasswordField, InputPasswordField,
//SelectField, SelectField,
CheckboxField, CheckboxField,
//DateField, //DateField,
//TextArea, //TextArea,
//Searchable, //Searchable,
SwitchField, SwitchField,
DynamicInputField,
}, },
formComponents: { SubmitButton }, formComponents: { SubmitButton },
fieldContext, fieldContext,

View File

@@ -0,0 +1,24 @@
import { keepPreviousData, queryOptions } from "@tanstack/react-query";
import axios from "axios";
export function notificationSubs(userId?: string) {
return queryOptions({
queryKey: ["notificationSubs"],
queryFn: () => fetch(userId),
staleTime: 5000,
refetchOnWindowFocus: true,
placeholderData: keepPreviousData,
});
}
const fetch = async (userId?: string) => {
if (window.location.hostname === "localhost") {
await new Promise((res) => setTimeout(res, 5000));
}
const { data } = await axios.get(
`/lst/api/notification/sub${userId ? `?userId=${userId}` : ""}`,
);
return data.data;
};

View File

@@ -0,0 +1,22 @@
import { keepPreviousData, queryOptions } from "@tanstack/react-query";
import axios from "axios";
export function notifications() {
return queryOptions({
queryKey: ["notifications"],
queryFn: () => fetch(),
staleTime: 5000,
refetchOnWindowFocus: true,
placeholderData: keepPreviousData,
});
}
const fetch = async () => {
if (window.location.hostname === "localhost") {
await new Promise((res) => setTimeout(res, 5000));
}
const { data } = await axios.get("/lst/api/notification");
return data.data;
};

View File

@@ -1,6 +1,6 @@
import type { Column } from "@tanstack/react-table"; import type { Column } from "@tanstack/react-table";
import { ArrowDown, ArrowUp, Search } from "lucide-react"; import { ArrowDown, ArrowUp, Search } from "lucide-react";
import React, { useState } from "react"; import { useState } from "react";
import { Button } from "../../components/ui/button"; import { Button } from "../../components/ui/button";
import { import {
DropdownMenu, DropdownMenu,

View File

@@ -0,0 +1,87 @@
import { useSuspenseQuery } from "@tanstack/react-query";
import {
Card,
CardContent,
CardDescription,
CardHeader,
CardTitle,
} from "../../../components/ui/card";
import { useAppForm } from "../../../lib/formSutff";
import { notificationSubs } from "../../../lib/queries/notificationSubs";
import { notifications } from "../../../lib/queries/notifications";
export default function NotificationsSubCard({ user }: any) {
const { data } = useSuspenseQuery(notifications());
const { data: ns } = useSuspenseQuery(notificationSubs(user.id));
const form = useAppForm({
defaultValues: {
notificationId: "",
emails: [user.email],
},
onSubmit: async ({ value }) => {
const postD = { ...value, userId: user.id };
console.log(postD);
},
});
let n: any = [];
if (data) {
n = data.map((i: any) => ({
label: i.name,
value: i.id,
}));
}
console.log(ns);
return (
<div>
<Card className="p-3 w-128">
<CardHeader>
<CardTitle>Notifications</CardTitle>
<CardDescription>
All currently active notifications you can subscribe to. selecting a
notification will give you a brief description on how it works
</CardDescription>
</CardHeader>
<CardContent>
<form
onSubmit={(e) => {
e.preventDefault();
form.handleSubmit();
}}
>
<div>
<form.AppField name="notificationId">
{(field) => (
<field.SelectField
label="Notifications"
placeholder="Select Notification"
options={n}
/>
)}
</form.AppField>
</div>
<form.AppField name="emails" mode="array">
{(field) => (
<field.DynamicInputField
label="Notification Emails"
description="Add more email addresses for notification delivery."
inputType="email"
addLabel="Add Email"
//initialValue={session?.user.email}
/>
)}
</form.AppField>
<div className="flex justify-end mt-6">
<form.AppForm>
<form.SubmitButton>Subscribe</form.SubmitButton>
</form.AppForm>
</div>
</form>
</CardContent>
</Card>
</div>
);
}

View File

@@ -1,4 +1,5 @@
import { createFileRoute, redirect } from "@tanstack/react-router"; import { createFileRoute, redirect } from "@tanstack/react-router";
import { Suspense } from "react";
import { toast } from "sonner"; import { toast } from "sonner";
import { import {
Card, Card,
@@ -9,7 +10,9 @@ import {
} from "@/components/ui/card"; } from "@/components/ui/card";
import { authClient, useSession } from "@/lib/auth-client"; import { authClient, useSession } from "@/lib/auth-client";
import { useAppForm } from "@/lib/formSutff"; import { useAppForm } from "@/lib/formSutff";
import { Spinner } from "../../components/ui/spinner";
import ChangePassword from "./-components/ChangePassword"; import ChangePassword from "./-components/ChangePassword";
import NotificationsSubCard from "./-components/NotificationsSubCard";
export const Route = createFileRoute("/(auth)/user/profile")({ export const Route = createFileRoute("/(auth)/user/profile")({
beforeLoad: async () => { beforeLoad: async () => {
@@ -93,6 +96,26 @@ function RouteComponent() {
<div> <div>
<ChangePassword /> <ChangePassword />
</div> </div>
<div>
<Suspense
fallback={
<Card className="p-3 w-96">
<CardHeader>
<CardTitle>Notifications</CardTitle>
</CardHeader>
<CardContent>
<div className="flex justify-center m-auto">
<div>
<Spinner className="size-32" />
</div>
</div>
</CardContent>
</Card>
}
>
{session && <NotificationsSubCard user={session.user} />}
</Suspense>
</div>
</div> </div>
); );
} }

View File

@@ -3,7 +3,7 @@ import { TanStackRouterDevtools } from "@tanstack/react-router-devtools";
import { Toaster } from "sonner"; import { Toaster } from "sonner";
import Header from "@/components/Header"; import Header from "@/components/Header";
import { AppSidebar } from "@/components/Sidebar/sidebar"; import { AppSidebar } from "@/components/Sidebar/sidebar";
import { SidebarInset, SidebarProvider } from "@/components/ui/sidebar"; import { SidebarProvider } from "@/components/ui/sidebar";
import { ThemeProvider } from "@/lib/theme-provider"; import { ThemeProvider } from "@/lib/theme-provider";
const RootLayout = () => ( const RootLayout = () => (

View File

@@ -1,6 +1,5 @@
import { useSuspenseQuery } from "@tanstack/react-query"; import { useSuspenseQuery } from "@tanstack/react-query";
import axios from "axios"; import axios from "axios";
import React from "react";
import { toast } from "sonner"; import { toast } from "sonner";
import { Card, CardDescription, CardHeader } from "../../../components/ui/card"; import { Card, CardDescription, CardHeader } from "../../../components/ui/card";
import { useAppForm } from "../../../lib/formSutff"; import { useAppForm } from "../../../lib/formSutff";
@@ -33,7 +32,9 @@ export default function FeatureCard({ item }: { item: Setting }) {
const { data } = await axios.patch(`/lst/api/settings/${item.name}`, { const { data } = await axios.patch(`/lst/api/settings/${item.name}`, {
value: value.value, value: value.value,
active: value.active ? "true" : "false", active: value.active ? "true" : "false",
}); }, {
withCredentials: true,
});
refetch(); refetch();
toast.success( toast.success(

View File

@@ -34,7 +34,7 @@ function Index() {
<p> <p>
This is active in your plant today due to having warehousing activated This is active in your plant today due to having warehousing activated
and new functions needed to be introduced, you should be still using LST and new functions needed to be introduced, you should be still using LST
as you were before as you were before.
</p> </p>
<br></br> <br></br>
<p> <p>
@@ -50,7 +50,7 @@ function Index() {
rel="noopener" rel="noopener"
> >
<b> <b>
<strong> Here</strong> <strong> Here.</strong>
</b> </b>
</a> </a>
</p> </p>

View File

@@ -0,0 +1,4 @@
CREATE TABLE "printer_log" (
"id" integer PRIMARY KEY GENERATED ALWAYS AS IDENTITY (sequence name "printer_log_id_seq" INCREMENT BY 1 MINVALUE 1 MAXVALUE 2147483647 START WITH 1 CACHE 1),
"name" text NOT NULL
);

File diff suppressed because it is too large Load Diff

View File

@@ -134,6 +134,13 @@
"when": 1774032587305, "when": 1774032587305,
"tag": "0018_lowly_wallow", "tag": "0018_lowly_wallow",
"breakpoints": true "breakpoints": true
},
{
"idx": 19,
"version": "7",
"when": 1775159956510,
"tag": "0019_large_thunderbird",
"breakpoints": true
} }
] ]
} }

271
package-lock.json generated
View File

@@ -63,6 +63,7 @@
"@types/swagger-jsdoc": "^6.0.4", "@types/swagger-jsdoc": "^6.0.4",
"@types/swagger-ui-express": "^4.1.8", "@types/swagger-ui-express": "^4.1.8",
"commitizen": "^4.3.1", "commitizen": "^4.3.1",
"cpy-cli": "^7.0.0",
"cz-conventional-changelog": "^3.3.0", "cz-conventional-changelog": "^3.3.0",
"npm-check-updates": "^19.6.5", "npm-check-updates": "^19.6.5",
"openapi-types": "^12.1.3", "openapi-types": "^12.1.3",
@@ -2388,6 +2389,19 @@
"url": "https://ko-fi.com/dangreen" "url": "https://ko-fi.com/dangreen"
} }
}, },
"node_modules/@sindresorhus/merge-streams": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/@sindresorhus/merge-streams/-/merge-streams-4.0.0.tgz",
"integrity": "sha512-tlqY9xq5ukxTUZBmoOp+m61cqwQD5pHJtFY3Mn8CA8ps6yghLH/Hw8UPdqg4OLmFW3IFlcXnQNmo/dh8HzXYIQ==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=18"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/@socket.io/admin-ui": { "node_modules/@socket.io/admin-ui": {
"version": "0.5.1", "version": "0.5.1",
"resolved": "https://registry.npmjs.org/@socket.io/admin-ui/-/admin-ui-0.5.1.tgz", "resolved": "https://registry.npmjs.org/@socket.io/admin-ui/-/admin-ui-0.5.1.tgz",
@@ -3838,6 +3852,23 @@
"node": ">=6.6.0" "node": ">=6.6.0"
} }
}, },
"node_modules/copy-file": {
"version": "11.1.0",
"resolved": "https://registry.npmjs.org/copy-file/-/copy-file-11.1.0.tgz",
"integrity": "sha512-X8XDzyvYaA6msMyAM575CUoygY5b44QzLcGRKsK3MFmXcOvQa518dNPLsKYwkYsn72g3EiW+LE0ytd/FlqWmyw==",
"dev": true,
"license": "MIT",
"dependencies": {
"graceful-fs": "^4.2.11",
"p-event": "^6.0.0"
},
"engines": {
"node": ">=18"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/cors": { "node_modules/cors": {
"version": "2.8.6", "version": "2.8.6",
"resolved": "https://registry.npmjs.org/cors/-/cors-2.8.6.tgz", "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.6.tgz",
@@ -3900,6 +3931,178 @@
"typescript": ">=5" "typescript": ">=5"
} }
}, },
"node_modules/cpy": {
"version": "13.2.1",
"resolved": "https://registry.npmjs.org/cpy/-/cpy-13.2.1.tgz",
"integrity": "sha512-/H2B3WW9gccZJKjKoDZsIrDU3MkkHlxgheT82hUbInC5fEdi4+54zyYpFueZT9pLfr5ObrtgN4MsYYrmTmHzeg==",
"dev": true,
"license": "MIT",
"dependencies": {
"copy-file": "^11.1.0",
"globby": "^16.1.0",
"junk": "^4.0.1",
"micromatch": "^4.0.8",
"p-filter": "^4.1.0",
"p-map": "^7.0.4"
},
"engines": {
"node": ">=20"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/cpy-cli": {
"version": "7.0.0",
"resolved": "https://registry.npmjs.org/cpy-cli/-/cpy-cli-7.0.0.tgz",
"integrity": "sha512-uGCdhIxGfZcPXidCuT8w1jBknVXFx0un7NLjzqBZcdnkIWtLUnWMPk5TC37ceoVjwASLSNsRtTXXNTuFIyE2ng==",
"dev": true,
"license": "MIT",
"dependencies": {
"cpy": "^13.2.0",
"globby": "^16.1.0",
"meow": "^14.0.0"
},
"bin": {
"cpy": "cli.js"
},
"engines": {
"node": ">=20"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/cpy-cli/node_modules/globby": {
"version": "16.2.0",
"resolved": "https://registry.npmjs.org/globby/-/globby-16.2.0.tgz",
"integrity": "sha512-QrJia2qDf5BB/V6HYlDTs0I0lBahyjLzpGQg3KT7FnCdTonAyPy2RtY802m2k4ALx6Dp752f82WsOczEVr3l6Q==",
"dev": true,
"license": "MIT",
"dependencies": {
"@sindresorhus/merge-streams": "^4.0.0",
"fast-glob": "^3.3.3",
"ignore": "^7.0.5",
"is-path-inside": "^4.0.0",
"slash": "^5.1.0",
"unicorn-magic": "^0.4.0"
},
"engines": {
"node": ">=20"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/cpy-cli/node_modules/ignore": {
"version": "7.0.5",
"resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.5.tgz",
"integrity": "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">= 4"
}
},
"node_modules/cpy-cli/node_modules/meow": {
"version": "14.1.0",
"resolved": "https://registry.npmjs.org/meow/-/meow-14.1.0.tgz",
"integrity": "sha512-EDYo6VlmtnumlcBCbh1gLJ//9jvM/ndXHfVXIFrZVr6fGcwTUyCTFNTLCKuY3ffbK8L/+3Mzqnd58RojiZqHVw==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=20"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/cpy-cli/node_modules/slash": {
"version": "5.1.0",
"resolved": "https://registry.npmjs.org/slash/-/slash-5.1.0.tgz",
"integrity": "sha512-ZA6oR3T/pEyuqwMgAKT0/hAv8oAXckzbkmR0UkUosQ+Mc4RxGoJkRmwHgHufaenlyAgE1Mxgpdcrf75y6XcnDg==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=14.16"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/cpy/node_modules/globby": {
"version": "16.2.0",
"resolved": "https://registry.npmjs.org/globby/-/globby-16.2.0.tgz",
"integrity": "sha512-QrJia2qDf5BB/V6HYlDTs0I0lBahyjLzpGQg3KT7FnCdTonAyPy2RtY802m2k4ALx6Dp752f82WsOczEVr3l6Q==",
"dev": true,
"license": "MIT",
"dependencies": {
"@sindresorhus/merge-streams": "^4.0.0",
"fast-glob": "^3.3.3",
"ignore": "^7.0.5",
"is-path-inside": "^4.0.0",
"slash": "^5.1.0",
"unicorn-magic": "^0.4.0"
},
"engines": {
"node": ">=20"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/cpy/node_modules/ignore": {
"version": "7.0.5",
"resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.5.tgz",
"integrity": "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">= 4"
}
},
"node_modules/cpy/node_modules/p-filter": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/p-filter/-/p-filter-4.1.0.tgz",
"integrity": "sha512-37/tPdZ3oJwHaS3gNJdenCDB3Tz26i9sjhnguBtvN0vYlRIiDNnvTWkuh+0hETV9rLPdJ3rlL3yVOYPIAnM8rw==",
"dev": true,
"license": "MIT",
"dependencies": {
"p-map": "^7.0.1"
},
"engines": {
"node": ">=18"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/cpy/node_modules/p-map": {
"version": "7.0.4",
"resolved": "https://registry.npmjs.org/p-map/-/p-map-7.0.4.tgz",
"integrity": "sha512-tkAQEw8ysMzmkhgw8k+1U/iPhWNhykKnSk4Rd5zLoPJCuJaGRPo6YposrZgaxHKzDHdDWWZvE/Sk7hsL2X/CpQ==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=18"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/cpy/node_modules/slash": {
"version": "5.1.0",
"resolved": "https://registry.npmjs.org/slash/-/slash-5.1.0.tgz",
"integrity": "sha512-ZA6oR3T/pEyuqwMgAKT0/hAv8oAXckzbkmR0UkUosQ+Mc4RxGoJkRmwHgHufaenlyAgE1Mxgpdcrf75y6XcnDg==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=14.16"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/create-require": { "node_modules/create-require": {
"version": "1.1.1", "version": "1.1.1",
"resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz",
@@ -6427,6 +6630,19 @@
"node": ">=8" "node": ">=8"
} }
}, },
"node_modules/is-path-inside": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-4.0.0.tgz",
"integrity": "sha512-lJJV/5dYS+RcL8uQdBDW9c9uWFLLBNRyFhnAKXw5tVqLlKZ4RMGZKv+YQ/IA3OhD+RpbJa1LLFM1FQPGyIXvOA==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=12"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/is-plain-obj": { "node_modules/is-plain-obj": {
"version": "4.1.0", "version": "4.1.0",
"resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-4.1.0.tgz", "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-4.1.0.tgz",
@@ -6637,6 +6853,19 @@
"npm": ">=6" "npm": ">=6"
} }
}, },
"node_modules/junk": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/junk/-/junk-4.0.1.tgz",
"integrity": "sha512-Qush0uP+G8ZScpGMZvHUiRfI0YBWuB3gVBYlI0v0vvOJt5FLicco+IkP0a50LqTTQhmts/m6tP5SWE+USyIvcQ==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=12.20"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/jwa": { "node_modules/jwa": {
"version": "2.0.1", "version": "2.0.1",
"resolved": "https://registry.npmjs.org/jwa/-/jwa-2.0.1.tgz", "resolved": "https://registry.npmjs.org/jwa/-/jwa-2.0.1.tgz",
@@ -7467,6 +7696,22 @@
"dev": true, "dev": true,
"license": "MIT" "license": "MIT"
}, },
"node_modules/p-event": {
"version": "6.0.1",
"resolved": "https://registry.npmjs.org/p-event/-/p-event-6.0.1.tgz",
"integrity": "sha512-Q6Bekk5wpzW5qIyUP4gdMEujObYstZl6DMMOSenwBvV0BlE5LkDwkjs5yHbZmdCEq2o4RJx4tE1vwxFVf2FG1w==",
"dev": true,
"license": "MIT",
"dependencies": {
"p-timeout": "^6.1.2"
},
"engines": {
"node": ">=16.17"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/p-filter": { "node_modules/p-filter": {
"version": "2.1.0", "version": "2.1.0",
"resolved": "https://registry.npmjs.org/p-filter/-/p-filter-2.1.0.tgz", "resolved": "https://registry.npmjs.org/p-filter/-/p-filter-2.1.0.tgz",
@@ -7519,6 +7764,19 @@
"node": ">=6" "node": ">=6"
} }
}, },
"node_modules/p-timeout": {
"version": "6.1.4",
"resolved": "https://registry.npmjs.org/p-timeout/-/p-timeout-6.1.4.tgz",
"integrity": "sha512-MyIV3ZA/PmyBN/ud8vV9XzwTrNtR4jFrObymZYnZqMmW0zA8Z17vnT0rBgFE/TlohB+YCHqXMgZzb3Csp49vqg==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=14.16"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/p-try": { "node_modules/p-try": {
"version": "2.2.0", "version": "2.2.0",
"resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz",
@@ -9256,6 +9514,19 @@
"dev": true, "dev": true,
"license": "MIT" "license": "MIT"
}, },
"node_modules/unicorn-magic": {
"version": "0.4.0",
"resolved": "https://registry.npmjs.org/unicorn-magic/-/unicorn-magic-0.4.0.tgz",
"integrity": "sha512-wH590V9VNgYH9g3lH9wWjTrUoKsjLF6sGLjhR4sH1LWpLmCOH0Zf7PukhDA8BiS7KHe4oPNkcTHqYkj7SOGUOw==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=20"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/universalify": { "node_modules/universalify": {
"version": "0.1.2", "version": "0.1.2",
"resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz",

View File

@@ -13,8 +13,8 @@
"build": "rimraf dist && npm run dev:db:generate && npm run dev:db:migrate && npm run build:app && npm run build:copySql && cd frontend && npm run build", "build": "rimraf dist && npm run dev:db:generate && npm run dev:db:migrate && npm run build:app && npm run build:copySql && cd frontend && npm run build",
"build:app": "tsc", "build:app": "tsc",
"agent": "powershell -ExecutionPolicy Bypass -File scripts/agentController.ps1", "agent": "powershell -ExecutionPolicy Bypass -File scripts/agentController.ps1",
"build:docker": "docker compose up --force-recreate --build -d", "build:docker": "rimraf dist && npm run build:app && npm run build:copySql",
"build:copySql": "xcopy backend\\prodSql\\queries dist\\prodSql\\queries\\ /E /I /Y ", "build:copySql": "cpy \"backend/prodSql/queries/**/*\" dist/prodSql/queries --parents",
"lint": "tsc && biome lint", "lint": "tsc && biome lint",
"start": "npm run start:server", "start": "npm run start:server",
"start:server": "dotenvx run -f .env -- node dist/server.js", "start:server": "dotenvx run -f .env -- node dist/server.js",
@@ -57,6 +57,7 @@
"@types/swagger-jsdoc": "^6.0.4", "@types/swagger-jsdoc": "^6.0.4",
"@types/swagger-ui-express": "^4.1.8", "@types/swagger-ui-express": "^4.1.8",
"commitizen": "^4.3.1", "commitizen": "^4.3.1",
"cpy-cli": "^7.0.0",
"cz-conventional-changelog": "^3.3.0", "cz-conventional-changelog": "^3.3.0",
"npm-check-updates": "^19.6.5", "npm-check-updates": "^19.6.5",
"openapi-types": "^12.1.3", "openapi-types": "^12.1.3",

View File

@@ -17,6 +17,11 @@ $Servers = @(
token = "uslim1" token = "uslim1"
loc = "D$\LST_V3" loc = "D$\LST_V3"
}, },
[PSCustomObject]@{
server = "ushou1vms006"
token = "ushou1"
loc = "D$\LST_V3"
},
[PSCustomObject]@{ [PSCustomObject]@{
server = "usday1vms006" server = "usday1vms006"
token = "usday1" token = "usday1"

6
scripts/dockerscripts.md Normal file
View File

@@ -0,0 +1,6 @@
docker build -t git.tuffraid.net/cowch/lst_v3:latest .
docker push git.tuffraid.net/cowch/lst_v3:latest
docker compose pull && docker compose up -d --force-recreate