Compare commits
11 Commits
2021141967
...
v0.0.1-alp
| Author | SHA1 | Date | |
|---|---|---|---|
| 82eaa23da7 | |||
| b18d1ced6d | |||
| 69c5cf87fd | |||
| 1fadf0ad25 | |||
| beae6eb648 | |||
| 82ab735982 | |||
| dbd56c1b50 | |||
| 037a473ab7 | |||
| 32998d417f | |||
| ddcb7e76a3 | |||
| 191cb2b698 |
@@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
"$schema": "https://unpkg.com/@changesets/config@3.1.2/schema.json",
|
"$schema": "https://unpkg.com/@changesets/config/schema.json",
|
||||||
"changelog": "@changesets/cli/changelog",
|
"changelog": "@changesets/cli/changelog",
|
||||||
"commit": false,
|
"commit": false,
|
||||||
"fixed": [],
|
"fixed": [],
|
||||||
|
|||||||
5
.changeset/neat-years-unite.md
Normal file
5
.changeset/neat-years-unite.md
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
---
|
||||||
|
"lst_v3": patch
|
||||||
|
---
|
||||||
|
|
||||||
|
build stuff
|
||||||
11
.changeset/pre.json
Normal file
11
.changeset/pre.json
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
{
|
||||||
|
"mode": "pre",
|
||||||
|
"tag": "alpha",
|
||||||
|
"initialVersions": {
|
||||||
|
"lst_v3": "1.0.1"
|
||||||
|
},
|
||||||
|
"changesets": [
|
||||||
|
"neat-years-unite",
|
||||||
|
"soft-onions-appear"
|
||||||
|
]
|
||||||
|
}
|
||||||
5
.changeset/soft-onions-appear.md
Normal file
5
.changeset/soft-onions-appear.md
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
---
|
||||||
|
"lst_v3": patch
|
||||||
|
---
|
||||||
|
|
||||||
|
external url added for docker
|
||||||
@@ -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
|
||||||
31
.gitea/workflows/docker-build.yml
Normal file
31
.gitea/workflows/docker-build.yml
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
name: Build and Push LST Docker Image
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- main
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
docker:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Checkout (local)
|
||||||
|
run: |
|
||||||
|
git clone https://git.tuffraid.net/cowch/lst_v3.git .
|
||||||
|
git checkout ${{ gitea.sha }}
|
||||||
|
|
||||||
|
- name: Login to registry
|
||||||
|
run: echo "${{ secrets.PASSWORD }}" | docker login git.tuffraid.net -u "cowch" --password-stdin
|
||||||
|
|
||||||
|
- name: Build image
|
||||||
|
run: |
|
||||||
|
docker build \
|
||||||
|
-t git.tuffraid.net/cowch/lst_v3:latest \
|
||||||
|
-t git.tuffraid.net/cowch/lst_v3:${{ gitea.sha }} \
|
||||||
|
.
|
||||||
|
|
||||||
|
- name: Push
|
||||||
|
run: |
|
||||||
|
docker push git.tuffraid.net/cowch/lst_v3:latest
|
||||||
|
docker push git.tuffraid.net/cowch/lst_v3:${{ gitea.sha }}
|
||||||
4
.gitignore
vendored
4
.gitignore
vendored
@@ -6,6 +6,10 @@ builds
|
|||||||
temp
|
temp
|
||||||
.scriptCreds
|
.scriptCreds
|
||||||
node-v24.14.0-x64.msi
|
node-v24.14.0-x64.msi
|
||||||
|
postgresql-17.9-2-windows-x64.exe
|
||||||
|
VSCodeUserSetup-x64-1.112.0.exe
|
||||||
|
nssm.exe
|
||||||
|
|
||||||
# Logs
|
# Logs
|
||||||
logs
|
logs
|
||||||
*.log
|
*.log
|
||||||
|
|||||||
3
.vscode/settings.json
vendored
3
.vscode/settings.json
vendored
@@ -3,6 +3,8 @@
|
|||||||
"workbench.colorTheme": "Default Dark+",
|
"workbench.colorTheme": "Default Dark+",
|
||||||
"terminal.integrated.env.windows": {},
|
"terminal.integrated.env.windows": {},
|
||||||
"editor.formatOnSave": true,
|
"editor.formatOnSave": true,
|
||||||
|
"typescript.preferences.importModuleSpecifier": "relative",
|
||||||
|
"javascript.preferences.importModuleSpecifier": "relative",
|
||||||
"editor.codeActionsOnSave": {
|
"editor.codeActionsOnSave": {
|
||||||
"source.fixAll.biome": "explicit",
|
"source.fixAll.biome": "explicit",
|
||||||
"source.organizeImports.biome": "explicit"
|
"source.organizeImports.biome": "explicit"
|
||||||
@@ -65,6 +67,7 @@
|
|||||||
"preseed",
|
"preseed",
|
||||||
"prodlabels",
|
"prodlabels",
|
||||||
"prolink",
|
"prolink",
|
||||||
|
"Skelly",
|
||||||
"trycatch"
|
"trycatch"
|
||||||
],
|
],
|
||||||
"gitea.token": "8456def90e1c651a761a8711763d6ef225d6b2db",
|
"gitea.token": "8456def90e1c651a761a8711763d6ef225d6b2db",
|
||||||
|
|||||||
@@ -1,5 +1,12 @@
|
|||||||
# lst_v3
|
# lst_v3
|
||||||
|
|
||||||
|
## 1.0.2-alpha.0
|
||||||
|
|
||||||
|
### Patch Changes
|
||||||
|
|
||||||
|
- build stuff
|
||||||
|
- external url added for docker
|
||||||
|
|
||||||
## 1.0.1
|
## 1.0.1
|
||||||
|
|
||||||
### Patch Changes
|
### Patch Changes
|
||||||
|
|||||||
10
Dockerfile
10
Dockerfile
@@ -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
|
||||||
|
|||||||
6
backend/db/schema/printerLogs.schema.ts
Normal file
6
backend/db/schema/printerLogs.schema.ts
Normal 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(),
|
||||||
|
});
|
||||||
@@ -1,10 +1,10 @@
|
|||||||
import { notificationSub } from "db/schema/notifications.sub.schema.js";
|
|
||||||
import { eq } from "drizzle-orm";
|
import { eq } from "drizzle-orm";
|
||||||
import { createLogger } from "logger/logger.controller.js";
|
|
||||||
import { minutesToCron } from "utils/croner.minConvert.js";
|
|
||||||
import { createCronJob, stopCronJob } from "utils/croner.utils.js";
|
|
||||||
import { db } from "../db/db.controller.js";
|
import { db } from "../db/db.controller.js";
|
||||||
import { notifications } from "../db/schema/notifications.schema.js";
|
import { notifications } from "../db/schema/notifications.schema.js";
|
||||||
|
import { notificationSub } from "../db/schema/notifications.sub.schema.js";
|
||||||
|
import { createLogger } from "../logger/logger.controller.js";
|
||||||
|
import { minutesToCron } from "../utils/croner.minConvert.js";
|
||||||
|
import { createCronJob, stopCronJob } from "../utils/croner.utils.js";
|
||||||
import { tryCatch } from "../utils/trycatch.utils.js";
|
import { tryCatch } from "../utils/trycatch.utils.js";
|
||||||
|
|
||||||
const log = createLogger({ module: "notifications", subModule: "start" });
|
const log = createLogger({ module: "notifications", subModule: "start" });
|
||||||
|
|||||||
@@ -1,6 +1,10 @@
|
|||||||
const reprint = (data: any, emails: string) => {
|
const reprint = (data: any, emails: string) => {
|
||||||
|
// TODO: do the actual logic for the notification.
|
||||||
console.log(data);
|
console.log(data);
|
||||||
console.log(emails);
|
console.log(emails);
|
||||||
|
|
||||||
|
// TODO send the error to systemAdmin users so they do not always need to be on the notifications.
|
||||||
|
// these errors are defined per notification.
|
||||||
};
|
};
|
||||||
|
|
||||||
export default reprint;
|
export default reprint;
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
import { notifications } from "db/schema/notifications.schema.js";
|
|
||||||
import { eq } from "drizzle-orm";
|
import { eq } from "drizzle-orm";
|
||||||
import { type Response, Router } from "express";
|
import { type Response, Router } from "express";
|
||||||
import { auth } from "utils/auth.utils.js";
|
|
||||||
import { db } from "../db/db.controller.js";
|
import { db } from "../db/db.controller.js";
|
||||||
|
import { notifications } from "../db/schema/notifications.schema.js";
|
||||||
|
import { auth } from "../utils/auth.utils.js";
|
||||||
import { apiReturn } from "../utils/returnHelper.utils.js";
|
import { apiReturn } from "../utils/returnHelper.utils.js";
|
||||||
import { tryCatch } from "../utils/trycatch.utils.js";
|
import { tryCatch } from "../utils/trycatch.utils.js";
|
||||||
|
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
import { notifications } from "db/schema/notifications.schema.js";
|
|
||||||
import { eq } from "drizzle-orm";
|
import { eq } from "drizzle-orm";
|
||||||
import { type Response, Router } from "express";
|
import { type Response, Router } from "express";
|
||||||
import z from "zod";
|
import z from "zod";
|
||||||
import { db } from "../db/db.controller.js";
|
import { db } from "../db/db.controller.js";
|
||||||
|
import { notifications } from "../db/schema/notifications.schema.js";
|
||||||
import { requirePermission } from "../middleware/auth.requiredPerms.middleware.js";
|
import { requirePermission } from "../middleware/auth.requiredPerms.middleware.js";
|
||||||
import { apiReturn } from "../utils/returnHelper.utils.js";
|
import { apiReturn } from "../utils/returnHelper.utils.js";
|
||||||
import { tryCatch } from "../utils/trycatch.utils.js";
|
import { tryCatch } from "../utils/trycatch.utils.js";
|
||||||
|
|||||||
@@ -1,23 +1,16 @@
|
|||||||
import { notificationSub } from "db/schema/notifications.sub.schema.js";
|
|
||||||
import { and, eq } from "drizzle-orm";
|
import { and, eq } from "drizzle-orm";
|
||||||
import { type Response, Router } from "express";
|
import { type Response, Router } from "express";
|
||||||
import z from "zod";
|
import z from "zod";
|
||||||
import { db } from "../db/db.controller.js";
|
import { db } from "../db/db.controller.js";
|
||||||
|
import { notificationSub } from "../db/schema/notifications.sub.schema.js";
|
||||||
import { apiReturn } from "../utils/returnHelper.utils.js";
|
import { apiReturn } from "../utils/returnHelper.utils.js";
|
||||||
import { tryCatch } from "../utils/trycatch.utils.js";
|
import { tryCatch } from "../utils/trycatch.utils.js";
|
||||||
import { modifiedNotification } from "./notification.controller.js";
|
import { modifiedNotification } from "./notification.controller.js";
|
||||||
|
|
||||||
const newSubscribe = z.object({
|
const newSubscribe = z.object({
|
||||||
emails: z
|
emails: z.email().array().describe("An array of emails"),
|
||||||
.email()
|
|
||||||
.array()
|
|
||||||
|
|
||||||
.describe("An array of emails"),
|
|
||||||
userId: z.string().describe("User id."),
|
userId: z.string().describe("User id."),
|
||||||
notificationId: z
|
notificationId: z.string().describe("Notification id"),
|
||||||
.string()
|
|
||||||
|
|
||||||
.describe("Notification id"),
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const r = Router();
|
const r = Router();
|
||||||
|
|||||||
@@ -1,14 +1,16 @@
|
|||||||
import { notificationSub } from "db/schema/notifications.sub.schema.js";
|
|
||||||
import { eq } from "drizzle-orm";
|
import { eq } from "drizzle-orm";
|
||||||
import { type Response, Router } from "express";
|
import { type Response, Router } from "express";
|
||||||
import { auth } from "utils/auth.utils.js";
|
|
||||||
import { db } from "../db/db.controller.js";
|
import { db } from "../db/db.controller.js";
|
||||||
|
import { notificationSub } from "../db/schema/notifications.sub.schema.js";
|
||||||
|
import { auth } from "../utils/auth.utils.js";
|
||||||
import { apiReturn } from "../utils/returnHelper.utils.js";
|
import { apiReturn } from "../utils/returnHelper.utils.js";
|
||||||
import { tryCatch } from "../utils/trycatch.utils.js";
|
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,
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { notificationSub } from "db/schema/notifications.sub.schema.js";
|
|
||||||
import { type Response, Router } from "express";
|
import { type Response, Router } from "express";
|
||||||
import z from "zod";
|
import z from "zod";
|
||||||
import { db } from "../db/db.controller.js";
|
import { db } from "../db/db.controller.js";
|
||||||
|
import { notificationSub } from "../db/schema/notifications.sub.schema.js";
|
||||||
import { apiReturn } from "../utils/returnHelper.utils.js";
|
import { apiReturn } from "../utils/returnHelper.utils.js";
|
||||||
import { tryCatch } from "../utils/trycatch.utils.js";
|
import { tryCatch } from "../utils/trycatch.utils.js";
|
||||||
import { modifiedNotification } from "./notification.controller.js";
|
import { modifiedNotification } from "./notification.controller.js";
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
import { notificationSub } from "db/schema/notifications.sub.schema.js";
|
|
||||||
import { and, eq } from "drizzle-orm";
|
import { and, eq } from "drizzle-orm";
|
||||||
import { type Response, Router } from "express";
|
import { type Response, Router } from "express";
|
||||||
import z from "zod";
|
import z from "zod";
|
||||||
import { db } from "../db/db.controller.js";
|
import { db } from "../db/db.controller.js";
|
||||||
|
import { notificationSub } from "../db/schema/notifications.sub.schema.js";
|
||||||
import { apiReturn } from "../utils/returnHelper.utils.js";
|
import { apiReturn } from "../utils/returnHelper.utils.js";
|
||||||
import { tryCatch } from "../utils/trycatch.utils.js";
|
import { tryCatch } from "../utils/trycatch.utils.js";
|
||||||
import { modifiedNotification } from "./notification.controller.js";
|
import { modifiedNotification } from "./notification.controller.js";
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
import { db } from "db/db.controller.js";
|
import { sql } from "drizzle-orm";
|
||||||
|
import { db } from "../db/db.controller.js";
|
||||||
import {
|
import {
|
||||||
type NewNotification,
|
type NewNotification,
|
||||||
notifications,
|
notifications,
|
||||||
} from "db/schema/notifications.schema.js";
|
} from "../db/schema/notifications.schema.js";
|
||||||
import { sql } from "drizzle-orm";
|
|
||||||
import { tryCatch } from "utils/trycatch.utils.js";
|
|
||||||
import { createLogger } from "../logger/logger.controller.js";
|
import { createLogger } from "../logger/logger.controller.js";
|
||||||
|
import { tryCatch } from "../utils/trycatch.utils.js";
|
||||||
|
|
||||||
const note: NewNotification[] = [
|
const note: NewNotification[] = [
|
||||||
{
|
{
|
||||||
|
|||||||
36
backend/ocp/ocp.printer.listener.ts
Normal file
36
backend/ocp/ocp.printer.listener.ts
Normal 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;
|
||||||
19
backend/ocp/ocp.printer.manage.ts
Normal file
19
backend/ocp/ocp.printer.manage.ts
Normal 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
22
backend/ocp/ocp.routes.ts
Normal 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);
|
||||||
|
};
|
||||||
188
backend/rfid/daytonConfig copy.json
Normal file
188
backend/rfid/daytonConfig copy.json
Normal file
@@ -0,0 +1,188 @@
|
|||||||
|
{
|
||||||
|
"GPIO-LED": {
|
||||||
|
"GPODefaults": {
|
||||||
|
"1": "HIGH",
|
||||||
|
"2": "HIGH",
|
||||||
|
"3": "HIGH",
|
||||||
|
"4": "HIGH"
|
||||||
|
},
|
||||||
|
"LEDDefaults": {
|
||||||
|
"3": "GREEN"
|
||||||
|
},
|
||||||
|
"TAG_READ": [
|
||||||
|
{
|
||||||
|
"pin": 1,
|
||||||
|
"state": "HIGH",
|
||||||
|
"type": "GPO"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"READER-GATEWAY": {
|
||||||
|
"batching": [
|
||||||
|
{
|
||||||
|
"maxPayloadSizePerReport": 256000,
|
||||||
|
"reportingInterval": 2000
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"endpointConfig": {
|
||||||
|
"data": {
|
||||||
|
"event": {
|
||||||
|
"connections": [
|
||||||
|
{
|
||||||
|
"additionalOptions": {
|
||||||
|
"batching": {
|
||||||
|
"maxPayloadSizePerReport": 256000,
|
||||||
|
"reportingInterval": 2000
|
||||||
|
},
|
||||||
|
"retention": {
|
||||||
|
"maxEventRetentionTimeInMin": 500,
|
||||||
|
"maxNumEvents": 150000,
|
||||||
|
"throttle": 100
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"description": "",
|
||||||
|
"name": "LST",
|
||||||
|
"options": {
|
||||||
|
"URL": "https://usday1prod.alpla.net/lst/old/api/rfid/taginfo/line3.4",
|
||||||
|
"security": {
|
||||||
|
"CACertificateFileLocation": "",
|
||||||
|
"authenticationOptions": {
|
||||||
|
"privateKeyFileLocation": "/readerconfig/ssl/server.key",
|
||||||
|
"publicKeyFileLocation": "/readerconfig/ssl/server.crt"
|
||||||
|
},
|
||||||
|
"authenticationType": "NONE",
|
||||||
|
"verifyHost": false,
|
||||||
|
"verifyPeer": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"type": "httpPost"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"managementEventConfig": {
|
||||||
|
"errors": {
|
||||||
|
"antenna": false,
|
||||||
|
"cpu": {
|
||||||
|
"reportIntervalInSec": 1800,
|
||||||
|
"threshold": 90
|
||||||
|
},
|
||||||
|
"database": true,
|
||||||
|
"flash": {
|
||||||
|
"reportIntervalInSec": 1800,
|
||||||
|
"threshold": 90
|
||||||
|
},
|
||||||
|
"ntp": true,
|
||||||
|
"radio": true,
|
||||||
|
"radio_control": true,
|
||||||
|
"ram": {
|
||||||
|
"reportIntervalInSec": 1800,
|
||||||
|
"threshold": 90
|
||||||
|
},
|
||||||
|
"reader_gateway": true,
|
||||||
|
"userApp": {
|
||||||
|
"reportIntervalInSec": 1800,
|
||||||
|
"threshold": 120
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"gpiEvents": true,
|
||||||
|
"gpoEvents": true,
|
||||||
|
"heartbeat": {
|
||||||
|
"fields": {
|
||||||
|
"radio_control": [
|
||||||
|
"ANTENNAS",
|
||||||
|
"RADIO_ACTIVITY",
|
||||||
|
"RADIO_CONNECTION",
|
||||||
|
"CPU",
|
||||||
|
"RAM",
|
||||||
|
"UPTIME",
|
||||||
|
"NUM_ERRORS",
|
||||||
|
"NUM_WARNINGS",
|
||||||
|
"NUM_TAG_READS",
|
||||||
|
"NUM_TAG_READS_PER_ANTENNA",
|
||||||
|
"NUM_DATA_MESSAGES_TXED",
|
||||||
|
"NUM_RADIO_PACKETS_RXED"
|
||||||
|
],
|
||||||
|
"reader_gateway": [
|
||||||
|
"NUM_DATA_MESSAGES_RXED",
|
||||||
|
"NUM_MANAGEMENT_EVENTS_TXED",
|
||||||
|
"NUM_DATA_MESSAGES_TXED",
|
||||||
|
"NUM_DATA_MESSAGES_RETAINED",
|
||||||
|
"NUM_DATA_MESSAGES_DROPPED",
|
||||||
|
"CPU",
|
||||||
|
"RAM",
|
||||||
|
"UPTIME",
|
||||||
|
"NUM_ERRORS",
|
||||||
|
"NUM_WARNINGS",
|
||||||
|
"INTERFACE_CONNECTION_STATUS",
|
||||||
|
"NOLOCKQ_DEPTH"
|
||||||
|
],
|
||||||
|
"system": [
|
||||||
|
"CPU",
|
||||||
|
"FLASH",
|
||||||
|
"NTP",
|
||||||
|
"RAM",
|
||||||
|
"SYSTEMTIME",
|
||||||
|
"TEMPERATURE",
|
||||||
|
"UPTIME",
|
||||||
|
"GPO",
|
||||||
|
"GPI",
|
||||||
|
"POWER_NEGOTIATION",
|
||||||
|
"POWER_SOURCE",
|
||||||
|
"MAC_ADDRESS",
|
||||||
|
"HOSTNAME"
|
||||||
|
],
|
||||||
|
"userDefined": null,
|
||||||
|
"userapps": [
|
||||||
|
"STATUS",
|
||||||
|
"CPU",
|
||||||
|
"RAM",
|
||||||
|
"UPTIME",
|
||||||
|
"NUM_DATA_MESSAGES_RXED",
|
||||||
|
"NUM_DATA_MESSAGES_TXED",
|
||||||
|
"INCOMING_DATA_BUFFER_PERCENTAGE_REMAINING",
|
||||||
|
"OUTGOING_DATA_BUFFER_PERCENTAGE_REMAINING"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"interval": 60
|
||||||
|
},
|
||||||
|
"userappEvents": true,
|
||||||
|
"warnings": {
|
||||||
|
"cpu": {
|
||||||
|
"reportIntervalInSec": 1800,
|
||||||
|
"threshold": 80
|
||||||
|
},
|
||||||
|
"database": true,
|
||||||
|
"flash": {
|
||||||
|
"reportIntervalInSec": 1800,
|
||||||
|
"threshold": 80
|
||||||
|
},
|
||||||
|
"ntp": true,
|
||||||
|
"radio_api": true,
|
||||||
|
"radio_control": true,
|
||||||
|
"ram": {
|
||||||
|
"reportIntervalInSec": 1800,
|
||||||
|
"threshold": 80
|
||||||
|
},
|
||||||
|
"reader_gateway": true,
|
||||||
|
"temperature": {
|
||||||
|
"ambient": 75,
|
||||||
|
"pa": 105
|
||||||
|
},
|
||||||
|
"userApp": {
|
||||||
|
"reportIntervalInSec": 1800,
|
||||||
|
"threshold": 60
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"retention": [
|
||||||
|
{
|
||||||
|
"maxEventRetentionTimeInMin": 500,
|
||||||
|
"maxNumEvents": 150000,
|
||||||
|
"throttle": 100
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"xml": "<?xml version='1.0'?>\n<Motorola xmlns:Falcon='http://www.motorola.com/RFID/Readers/Config/Falcon' xmlns='http://www.motorola.com/RFID/Readers/Config/Falcon'>\n<Config>\n<AppVersion major='3' minor='28' build='1' maintenance='0'/>\n<CommConfig EnabledStacks='IPV4' DisableRAPktProcessing='1' EnableDHCPv6='1' IPv6StaticIPAddr='fe80::1' IPv6SubnetMask='64' IPv6StaticGateway='::' IPv6DNSIP='fe80::20' DHCP='1' IPAddr='10.44.14.39' Mask='255.255.255.0' Gateway='10.44.14.252' DNS='10.44.9.250' DomainSearch='example.com' HttpRunning='2' TelnetActive='2' FtpActive='2' usbMode='0' WatchdogEnabled='1' AvahiEnabled='1' NetBIOSEnabled='0' RDMPAgentEnabled='1' SerialConTimeout='0' SNTP='0.0.0.0' SNTPHostName='pool.ntp.org' sntpHostDisplayMode='0' llrpClientMode='0' llrpSecureMode='0' llrpSecureModeValidatePeer='0' llrpPort='5084' llrpHostIP='192.168.127.2' allowllrpConnOverride='0' shouldReconnect='1'/>\n<Bluetooth discoverable='0' pairable='0' PincodeEnabled='0' passkey='165CB22DA5BE7BBEFB77709DD0A94B03FB77709DD0A94B03FB77709DD0A94B03FB77709DD0A94B03FB77709DD0A94B03FB77709DD0A94B03FB77709DD0A94B03' startIP='192.168.0.2' endIP='192.168.0.3'/>\n<WirelessConfig essid='' autoconnect='0'/>\n<RegionConfig RFCountry='United States/Canada' RFRegulatory='US FCC 15' RFScanMode='0' LBTEnable='0' ChannelData='FFFFFFFFFFFFFFFF'/>\n<SnmpConfig snmpVersion='1' heartbeat='1'/>\n<SyslogConfig RemoteIp='0.0.0.0' RemotePort='514' LogMinSeverity='7' ApplyFilter='0' MinimumSeverity='7' ProcessFilter='rmserver.elf,llrpserver.elf,snmpextagent.elf,RDMPAgent'/>\n<UserList>\n<User name='admin' PSWD='$6$weLpDwlv$utr0AwgPIae2O4Gln4cQ2IJJblXye412Xqni0V.ahIFKUOCEDGjzZ4ttthhrw7rmmQYsCXKwA9znyqPkAT.IL/'/>\n<User name='rfidadm' PSWD='15491'/>\n</UserList>\n<IPReader name='FX96007AF832 FX9600 RFID Reader' desc='FX96007AF832 Advanced Reader' flags='0' MonoStatic='0' CheckAntenna='1' gpiDebounceTime='0' gpioMapping='0' idleModeTimeOut='0' diagMode='0' extDiagMode='0' contact='Zebra Technologies Corporation' PowerNegotiation='0' PowerNegotiationProtocol='0' allowGuestLogin='1' configureHostName='0'>\n<ReadPoint name='Read Point 1' flags='0' CableLossPerHundredFt='10' CableLength='10'/>\n<ReadPoint name='Read Point 2' flags='0' CableLossPerHundredFt='10' CableLength='10'/>\n<ReadPoint name='Read Point 3' flags='1' CableLossPerHundredFt='10' CableLength='10'/>\n<ReadPoint name='Read Point 4' flags='1' CableLossPerHundredFt='10' CableLength='10'/>\n</IPReader>\n<SerialPortConf Mode='0' Baudrate='115200' Databits='8' Parity='none' Stopbits='1' Flowcontrol='hardware' TagMetaData='0' InventoryControl='0' IsAutostart='0'/>\n<FXConnectConfig FXConnectMode='0' TagMetaData='0' InventoryControl='None' HeartBeatPeriod='0' IsAutostart='0' PreFilterMode='0' PreFilters='None'/>\n<ProfinetConfig virtualDAP='1'/>\n<NodeJSPortConf Portnumber='8001'/>\n</Config>\n<MOTOROLA_LLRP_CONFIG><LLRP_READER_CONFIG />\n</MOTOROLA_LLRP_CONFIG>\n<IOT_CONNECT_CONFIG><OPERATING_MODE />\n</IOT_CONNECT_CONFIG>\n<RadioProfileData><RadioRegisterData Address='0' Data='00'/>\n</RadioProfileData>\n<CustomProfileData ForceEAPMode='0' FIPS_MODE_ENABLED='0' MaxNumberOfTagsBuffered='512'/>\n</Motorola >\n"
|
||||||
|
}
|
||||||
206
backend/rfid/daytonConfig.json
Normal file
206
backend/rfid/daytonConfig.json
Normal file
@@ -0,0 +1,206 @@
|
|||||||
|
{
|
||||||
|
"GPIO-LED": {
|
||||||
|
"GPODefaults": {
|
||||||
|
"1": "HIGH",
|
||||||
|
"2": "HIGH",
|
||||||
|
"3": "HIGH",
|
||||||
|
"4": "HIGH"
|
||||||
|
},
|
||||||
|
"LEDDefaults": {
|
||||||
|
"3": "GREEN"
|
||||||
|
},
|
||||||
|
"TAG_READ": [
|
||||||
|
{
|
||||||
|
"pin": 1,
|
||||||
|
"state": "HIGH",
|
||||||
|
"type": "GPO"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"READER-GATEWAY": {
|
||||||
|
"batching": [
|
||||||
|
{
|
||||||
|
"maxPayloadSizePerReport": 256000,
|
||||||
|
"reportingInterval": 2000
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"endpointConfig": {
|
||||||
|
"data": {
|
||||||
|
"event": {
|
||||||
|
"connections": [
|
||||||
|
{
|
||||||
|
"additionalOptions": {
|
||||||
|
"retention": {
|
||||||
|
"maxEventRetentionTimeInMin": 500,
|
||||||
|
"maxNumEvents": 150000,
|
||||||
|
"throttle": 100
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"description": "",
|
||||||
|
"name": "lst",
|
||||||
|
"options": {
|
||||||
|
"URL": "http://usday1vms006:3100/api/rfid/taginfo/wrapper1",
|
||||||
|
"security": {
|
||||||
|
"CACertificateFileLocation": "",
|
||||||
|
"authenticationOptions": {},
|
||||||
|
"authenticationType": "NONE",
|
||||||
|
"verifyHost": false,
|
||||||
|
"verifyPeer": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"type": "httpPost"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"additionalOptions": {
|
||||||
|
"retention": {
|
||||||
|
"maxEventRetentionTimeInMin": 500,
|
||||||
|
"maxNumEvents": 150000,
|
||||||
|
"throttle": 100
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"description": "",
|
||||||
|
"name": "mgt",
|
||||||
|
"options": {
|
||||||
|
"URL": "http://usday1vms006:3100/api/rfid/mgtevents/wrapper1",
|
||||||
|
"security": {
|
||||||
|
"CACertificateFileLocation": "",
|
||||||
|
"authenticationOptions": {},
|
||||||
|
"authenticationType": "NONE",
|
||||||
|
"verifyHost": false,
|
||||||
|
"verifyPeer": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"type": "httpPost"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"interfaces": {
|
||||||
|
"tagDataInterface1": "lst",
|
||||||
|
"managementEventsInterface": "mgt"
|
||||||
|
},
|
||||||
|
"managementEventConfig": {
|
||||||
|
"errors": {
|
||||||
|
"antenna": false,
|
||||||
|
"cpu": {
|
||||||
|
"reportIntervalInSec": 1800,
|
||||||
|
"threshold": 90
|
||||||
|
},
|
||||||
|
"database": true,
|
||||||
|
"flash": {
|
||||||
|
"reportIntervalInSec": 1800,
|
||||||
|
"threshold": 90
|
||||||
|
},
|
||||||
|
"ntp": true,
|
||||||
|
"radio": true,
|
||||||
|
"radio_control": true,
|
||||||
|
"ram": {
|
||||||
|
"reportIntervalInSec": 1800,
|
||||||
|
"threshold": 90
|
||||||
|
},
|
||||||
|
"reader_gateway": true,
|
||||||
|
"userApp": {
|
||||||
|
"reportIntervalInSec": 1800,
|
||||||
|
"threshold": 120
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"gpiEvents": true,
|
||||||
|
"gpoEvents": true,
|
||||||
|
"heartbeat": {
|
||||||
|
"fields": {
|
||||||
|
"radio_control": [
|
||||||
|
"ANTENNAS",
|
||||||
|
"RADIO_ACTIVITY",
|
||||||
|
"RADIO_CONNECTION",
|
||||||
|
"CPU",
|
||||||
|
"RAM",
|
||||||
|
"UPTIME",
|
||||||
|
"NUM_ERRORS",
|
||||||
|
"NUM_WARNINGS",
|
||||||
|
"NUM_TAG_READS",
|
||||||
|
"NUM_TAG_READS_PER_ANTENNA",
|
||||||
|
"NUM_DATA_MESSAGES_TXED",
|
||||||
|
"NUM_RADIO_PACKETS_RXED"
|
||||||
|
],
|
||||||
|
"reader_gateway": [
|
||||||
|
"NUM_DATA_MESSAGES_RXED",
|
||||||
|
"NUM_MANAGEMENT_EVENTS_TXED",
|
||||||
|
"NUM_DATA_MESSAGES_TXED",
|
||||||
|
"NUM_DATA_MESSAGES_RETAINED",
|
||||||
|
"NUM_DATA_MESSAGES_DROPPED",
|
||||||
|
"CPU",
|
||||||
|
"RAM",
|
||||||
|
"UPTIME",
|
||||||
|
"NUM_ERRORS",
|
||||||
|
"NUM_WARNINGS",
|
||||||
|
"INTERFACE_CONNECTION_STATUS",
|
||||||
|
"NOLOCKQ_DEPTH"
|
||||||
|
],
|
||||||
|
"system": [
|
||||||
|
"CPU",
|
||||||
|
"FLASH",
|
||||||
|
"NTP",
|
||||||
|
"RAM",
|
||||||
|
"SYSTEMTIME",
|
||||||
|
"TEMPERATURE",
|
||||||
|
"UPTIME",
|
||||||
|
"GPO",
|
||||||
|
"GPI",
|
||||||
|
"POWER_NEGOTIATION",
|
||||||
|
"POWER_SOURCE",
|
||||||
|
"MAC_ADDRESS",
|
||||||
|
"HOSTNAME"
|
||||||
|
],
|
||||||
|
"userDefined": null,
|
||||||
|
"userapps": [
|
||||||
|
"STATUS",
|
||||||
|
"CPU",
|
||||||
|
"RAM",
|
||||||
|
"UPTIME",
|
||||||
|
"NUM_DATA_MESSAGES_RXED",
|
||||||
|
"NUM_DATA_MESSAGES_TXED",
|
||||||
|
"INCOMING_DATA_BUFFER_PERCENTAGE_REMAINING",
|
||||||
|
"OUTGOING_DATA_BUFFER_PERCENTAGE_REMAINING"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"interval": 60
|
||||||
|
},
|
||||||
|
"userappEvents": true,
|
||||||
|
"warnings": {
|
||||||
|
"cpu": {
|
||||||
|
"reportIntervalInSec": 1800,
|
||||||
|
"threshold": 80
|
||||||
|
},
|
||||||
|
"database": true,
|
||||||
|
"flash": {
|
||||||
|
"reportIntervalInSec": 1800,
|
||||||
|
"threshold": 80
|
||||||
|
},
|
||||||
|
"ntp": true,
|
||||||
|
"radio_api": true,
|
||||||
|
"radio_control": true,
|
||||||
|
"ram": {
|
||||||
|
"reportIntervalInSec": 1800,
|
||||||
|
"threshold": 80
|
||||||
|
},
|
||||||
|
"reader_gateway": true,
|
||||||
|
"temperature": {
|
||||||
|
"ambient": 75,
|
||||||
|
"pa": 105
|
||||||
|
},
|
||||||
|
"userApp": {
|
||||||
|
"reportIntervalInSec": 1800,
|
||||||
|
"threshold": 60
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"retention": [
|
||||||
|
{
|
||||||
|
"maxEventRetentionTimeInMin": 500,
|
||||||
|
"maxNumEvents": 150000,
|
||||||
|
"throttle": 100
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,9 +1,11 @@
|
|||||||
import type { Express } from "express";
|
import type { Express } from "express";
|
||||||
import { setupNotificationRoutes } from "notification/notification.routes.js";
|
|
||||||
import { setupAuthRoutes } from "./auth/auth.routes.js";
|
import { setupAuthRoutes } from "./auth/auth.routes.js";
|
||||||
// import the routes and route setups
|
// import the routes and route setups
|
||||||
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 { 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";
|
||||||
@@ -19,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);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
import { createServer } from "node:http";
|
import { createServer } from "node:http";
|
||||||
import os from "node:os";
|
import os from "node:os";
|
||||||
import { startNotifications } from "notification/notification.controller.js";
|
|
||||||
import { createNotifications } from "notification/notifications.master.js";
|
|
||||||
import createApp from "./app.js";
|
import createApp from "./app.js";
|
||||||
import { db } from "./db/db.controller.js";
|
import { db } from "./db/db.controller.js";
|
||||||
import { dbCleanup } from "./db/dbCleanup.controller.js";
|
import { dbCleanup } from "./db/dbCleanup.controller.js";
|
||||||
import { type Setting, settings } from "./db/schema/settings.schema.js";
|
import { type Setting, settings } from "./db/schema/settings.schema.js";
|
||||||
import { createLogger } from "./logger/logger.controller.js";
|
import { createLogger } from "./logger/logger.controller.js";
|
||||||
|
import { startNotifications } from "./notification/notification.controller.js";
|
||||||
|
import { createNotifications } from "./notification/notifications.master.js";
|
||||||
import { monitorReleaseChanges } from "./opendock/openDockRreleaseMonitor.utils.js";
|
import { monitorReleaseChanges } from "./opendock/openDockRreleaseMonitor.utils.js";
|
||||||
import { opendockSocketMonitor } from "./opendock/opendockSocketMonitor.utils.js";
|
import { opendockSocketMonitor } from "./opendock/opendockSocketMonitor.utils.js";
|
||||||
import { connectProdSql } from "./prodSql/prodSqlConnection.controller.js";
|
import { connectProdSql } from "./prodSql/prodSqlConnection.controller.js";
|
||||||
|
|||||||
@@ -8,8 +8,8 @@ type RoomDefinition<T = unknown> = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const protectedRooms: any = {
|
export const protectedRooms: any = {
|
||||||
logs: { requiresAuth: true, role: "admin" },
|
logs: { requiresAuth: true, role: ["admin", "systemAdmin"] },
|
||||||
admin: { requiresAuth: true, role: "admin" },
|
admin: { requiresAuth: true, role: ["admin", "systemAdmin"] },
|
||||||
};
|
};
|
||||||
|
|
||||||
export const roomDefinition: Record<RoomId, RoomDefinition> = {
|
export const roomDefinition: Record<RoomId, RoomDefinition> = {
|
||||||
|
|||||||
@@ -13,9 +13,9 @@ import { createRoomEmitter, preseedRoom } from "./roomService.socket.js";
|
|||||||
//const __dirname = dirname(__filename);
|
//const __dirname = dirname(__filename);
|
||||||
const log = createLogger({ module: "socket.io", subModule: "setup" });
|
const log = createLogger({ module: "socket.io", subModule: "setup" });
|
||||||
|
|
||||||
|
import { auth } from "../utils/auth.utils.js";
|
||||||
//import type { Session, User } from "better-auth"; // adjust if needed
|
//import type { Session, User } from "better-auth"; // adjust if needed
|
||||||
import { protectedRooms } from "./roomDefinitions.socket.js";
|
import { protectedRooms } from "./roomDefinitions.socket.js";
|
||||||
import { auth } from "../utils/auth.utils.js";
|
|
||||||
|
|
||||||
// declare module "socket.io" {
|
// declare module "socket.io" {
|
||||||
// interface Socket {
|
// interface Socket {
|
||||||
@@ -88,7 +88,12 @@ export const setupSocketIORoutes = (baseUrl: string, server: HttpServer) => {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (config?.role && s.user?.role !== config.role) {
|
const roles = Array.isArray(config.role) ? config.role : [config.role];
|
||||||
|
|
||||||
|
console.log(roles, s.user.role);
|
||||||
|
|
||||||
|
//if (config?.role && s.user?.role !== config.role) {
|
||||||
|
if (config?.role && !roles.includes(s.user?.role)) {
|
||||||
return s.emit("room-error", {
|
return s.emit("room-error", {
|
||||||
room: rn,
|
room: rn,
|
||||||
message: `Not authorized to be in room: ${rn}`,
|
message: `Not authorized to be in room: ${rn}`,
|
||||||
|
|||||||
@@ -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",
|
||||||
|
|||||||
@@ -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`,
|
||||||
|
`${process.env.EXTERNAL_URL}`, // internal docker
|
||||||
];
|
];
|
||||||
export const lstCors = () => {
|
export const lstCors = () => {
|
||||||
return cors({
|
return cors({
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -1,3 +1,7 @@
|
|||||||
vars {
|
vars {
|
||||||
url: http://localhost:3000/lst
|
url: http://localhost:3000/lst
|
||||||
|
readerIp: 10.44.14.215
|
||||||
}
|
}
|
||||||
|
vars:secret [
|
||||||
|
token
|
||||||
|
]
|
||||||
|
|||||||
@@ -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"]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -10,14 +10,6 @@ get {
|
|||||||
auth: inherit
|
auth: inherit
|
||||||
}
|
}
|
||||||
|
|
||||||
body:json {
|
|
||||||
{
|
|
||||||
"userId":"0kHd6Kkdub4GW6rK1qa1yjWwqXtvykqT",
|
|
||||||
"notificationId": "0399eb2a-39df-48b7-9f1c-d233cec94d2e",
|
|
||||||
"emails": ["blake.mattes@alpla.com"]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
settings {
|
settings {
|
||||||
encodeUrl: true
|
encodeUrl: true
|
||||||
timeout: 0
|
timeout: 0
|
||||||
|
|||||||
@@ -16,8 +16,8 @@ params:path {
|
|||||||
|
|
||||||
body:json {
|
body:json {
|
||||||
{
|
{
|
||||||
"active" : false,
|
"active" : true,
|
||||||
"options": [{"prodId": 5}]
|
"options": []
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
22
brunoApi/ocp/Printer Listenter.bru
Normal file
22
brunoApi/ocp/Printer Listenter.bru
Normal 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
8
brunoApi/ocp/folder.bru
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
meta {
|
||||||
|
name: ocp
|
||||||
|
seq: 9
|
||||||
|
}
|
||||||
|
|
||||||
|
auth {
|
||||||
|
mode: inherit
|
||||||
|
}
|
||||||
8
brunoApi/rfidReaders/folder.bru
Normal file
8
brunoApi/rfidReaders/folder.bru
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
meta {
|
||||||
|
name: rfidReaders
|
||||||
|
seq: 8
|
||||||
|
}
|
||||||
|
|
||||||
|
auth {
|
||||||
|
mode: inherit
|
||||||
|
}
|
||||||
20
brunoApi/rfidReaders/reader.bru
Normal file
20
brunoApi/rfidReaders/reader.bru
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
meta {
|
||||||
|
name: reader
|
||||||
|
type: http
|
||||||
|
seq: 2
|
||||||
|
}
|
||||||
|
|
||||||
|
post {
|
||||||
|
url: https://usday1prod.alpla.net/lst/old/api/rfid/mgtevents/line3.1
|
||||||
|
body: json
|
||||||
|
auth: inherit
|
||||||
|
}
|
||||||
|
|
||||||
|
body:json {
|
||||||
|
{}
|
||||||
|
}
|
||||||
|
|
||||||
|
settings {
|
||||||
|
encodeUrl: true
|
||||||
|
timeout: 0
|
||||||
|
}
|
||||||
20
brunoApi/rfidReaders/readerSpecific/Config.bru
Normal file
20
brunoApi/rfidReaders/readerSpecific/Config.bru
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
meta {
|
||||||
|
name: Config
|
||||||
|
type: http
|
||||||
|
seq: 2
|
||||||
|
}
|
||||||
|
|
||||||
|
get {
|
||||||
|
url: https://{{readerIp}}/cloud/config
|
||||||
|
body: none
|
||||||
|
auth: bearer
|
||||||
|
}
|
||||||
|
|
||||||
|
auth:bearer {
|
||||||
|
token: {{token}}
|
||||||
|
}
|
||||||
|
|
||||||
|
settings {
|
||||||
|
encodeUrl: true
|
||||||
|
timeout: 0
|
||||||
|
}
|
||||||
32
brunoApi/rfidReaders/readerSpecific/Login.bru
Normal file
32
brunoApi/rfidReaders/readerSpecific/Login.bru
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
meta {
|
||||||
|
name: Login
|
||||||
|
type: http
|
||||||
|
seq: 1
|
||||||
|
}
|
||||||
|
|
||||||
|
get {
|
||||||
|
url: https://{{readerIp}}/cloud/localRestLogin
|
||||||
|
body: none
|
||||||
|
auth: basic
|
||||||
|
}
|
||||||
|
|
||||||
|
auth:basic {
|
||||||
|
username: admin
|
||||||
|
password: Zebra123!
|
||||||
|
}
|
||||||
|
|
||||||
|
script:post-response {
|
||||||
|
const body = res.getBody();
|
||||||
|
|
||||||
|
if (body.message) {
|
||||||
|
bru.setEnvVar("token", body.message);
|
||||||
|
} else {
|
||||||
|
bru.setEnvVar("token", "error");
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
settings {
|
||||||
|
encodeUrl: true
|
||||||
|
timeout: 0
|
||||||
|
}
|
||||||
237
brunoApi/rfidReaders/readerSpecific/Update Config.bru
Normal file
237
brunoApi/rfidReaders/readerSpecific/Update Config.bru
Normal file
@@ -0,0 +1,237 @@
|
|||||||
|
meta {
|
||||||
|
name: Update Config
|
||||||
|
type: http
|
||||||
|
seq: 3
|
||||||
|
}
|
||||||
|
|
||||||
|
put {
|
||||||
|
url: https://{{readerIp}}/cloud/config
|
||||||
|
body: json
|
||||||
|
auth: bearer
|
||||||
|
}
|
||||||
|
|
||||||
|
headers {
|
||||||
|
Content-Type: application/json
|
||||||
|
}
|
||||||
|
|
||||||
|
auth:bearer {
|
||||||
|
token: {{token}}
|
||||||
|
}
|
||||||
|
|
||||||
|
body:json {
|
||||||
|
{
|
||||||
|
"GPIO-LED": {
|
||||||
|
"GPODefaults": {
|
||||||
|
"1": "HIGH",
|
||||||
|
"2": "HIGH",
|
||||||
|
"3": "HIGH",
|
||||||
|
"4": "HIGH"
|
||||||
|
},
|
||||||
|
"LEDDefaults": {
|
||||||
|
"3": "GREEN"
|
||||||
|
},
|
||||||
|
"TAG_READ": [
|
||||||
|
{
|
||||||
|
"pin": 1,
|
||||||
|
"state": "HIGH",
|
||||||
|
"type": "GPO"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"READER-GATEWAY": {
|
||||||
|
"batching": [
|
||||||
|
{
|
||||||
|
"maxPayloadSizePerReport": 256000,
|
||||||
|
"reportingInterval": 2000
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"maxPayloadSizePerReport": 256000,
|
||||||
|
"reportingInterval": 2000
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"endpointConfig": {
|
||||||
|
"data": {
|
||||||
|
"event": {
|
||||||
|
"connections": [
|
||||||
|
{
|
||||||
|
"additionalOptions": {
|
||||||
|
"retention": {
|
||||||
|
"maxEventRetentionTimeInMin": 500,
|
||||||
|
"maxNumEvents": 150000,
|
||||||
|
"throttle": 100
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"description": "",
|
||||||
|
"name": "LST",
|
||||||
|
"options": {
|
||||||
|
"URL": "https://usday1prod.alpla.net/lst/old/api/rfid/taginfo/line3.4",
|
||||||
|
"security": {
|
||||||
|
"CACertificateFileLocation": "",
|
||||||
|
"authenticationOptions": {},
|
||||||
|
"authenticationType": "NONE",
|
||||||
|
"verifyHost": false,
|
||||||
|
"verifyPeer": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"type": "httpPost"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"additionalOptions": {
|
||||||
|
"retention": {
|
||||||
|
"maxEventRetentionTimeInMin": 500,
|
||||||
|
"maxNumEvents": 150000,
|
||||||
|
"throttle": 100
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"description": "",
|
||||||
|
"name": "mgt",
|
||||||
|
"options": {
|
||||||
|
"URL": "https://usday1prod.alpla.net/lst/old/api/rfid/mgtevents/line3.4",
|
||||||
|
"security": {
|
||||||
|
"CACertificateFileLocation": "",
|
||||||
|
"authenticationOptions": {},
|
||||||
|
"authenticationType": "NONE",
|
||||||
|
"verifyHost": false,
|
||||||
|
"verifyPeer": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"type": "httpPost"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"managementEventConfig": {
|
||||||
|
"errors": {
|
||||||
|
"antenna": false,
|
||||||
|
"cpu": {
|
||||||
|
"reportIntervalInSec": 1800,
|
||||||
|
"threshold": 90
|
||||||
|
},
|
||||||
|
"database": true,
|
||||||
|
"flash": {
|
||||||
|
"reportIntervalInSec": 1800,
|
||||||
|
"threshold": 90
|
||||||
|
},
|
||||||
|
"ntp": true,
|
||||||
|
"radio": true,
|
||||||
|
"radio_control": true,
|
||||||
|
"ram": {
|
||||||
|
"reportIntervalInSec": 1800,
|
||||||
|
"threshold": 90
|
||||||
|
},
|
||||||
|
"reader_gateway": true,
|
||||||
|
"userApp": {
|
||||||
|
"reportIntervalInSec": 1800,
|
||||||
|
"threshold": 120
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"gpiEvents": true,
|
||||||
|
"gpoEvents": true,
|
||||||
|
"heartbeat": {
|
||||||
|
"fields": {
|
||||||
|
"radio_control": [
|
||||||
|
"ANTENNAS",
|
||||||
|
"RADIO_ACTIVITY",
|
||||||
|
"RADIO_CONNECTION",
|
||||||
|
"CPU",
|
||||||
|
"RAM",
|
||||||
|
"UPTIME",
|
||||||
|
"NUM_ERRORS",
|
||||||
|
"NUM_WARNINGS",
|
||||||
|
"NUM_TAG_READS",
|
||||||
|
"NUM_TAG_READS_PER_ANTENNA",
|
||||||
|
"NUM_DATA_MESSAGES_TXED",
|
||||||
|
"NUM_RADIO_PACKETS_RXED"
|
||||||
|
],
|
||||||
|
"reader_gateway": [
|
||||||
|
"NUM_DATA_MESSAGES_RXED",
|
||||||
|
"NUM_MANAGEMENT_EVENTS_TXED",
|
||||||
|
"NUM_DATA_MESSAGES_TXED",
|
||||||
|
"NUM_DATA_MESSAGES_RETAINED",
|
||||||
|
"NUM_DATA_MESSAGES_DROPPED",
|
||||||
|
"CPU",
|
||||||
|
"RAM",
|
||||||
|
"UPTIME",
|
||||||
|
"NUM_ERRORS",
|
||||||
|
"NUM_WARNINGS",
|
||||||
|
"INTERFACE_CONNECTION_STATUS",
|
||||||
|
"NOLOCKQ_DEPTH"
|
||||||
|
],
|
||||||
|
"system": [
|
||||||
|
"CPU",
|
||||||
|
"FLASH",
|
||||||
|
"NTP",
|
||||||
|
"RAM",
|
||||||
|
"SYSTEMTIME",
|
||||||
|
"TEMPERATURE",
|
||||||
|
"UPTIME",
|
||||||
|
"GPO",
|
||||||
|
"GPI",
|
||||||
|
"POWER_NEGOTIATION",
|
||||||
|
"POWER_SOURCE",
|
||||||
|
"MAC_ADDRESS",
|
||||||
|
"HOSTNAME"
|
||||||
|
],
|
||||||
|
"userapps": [
|
||||||
|
"STATUS",
|
||||||
|
"CPU",
|
||||||
|
"RAM",
|
||||||
|
"UPTIME",
|
||||||
|
"NUM_DATA_MESSAGES_RXED",
|
||||||
|
"NUM_DATA_MESSAGES_TXED",
|
||||||
|
"INCOMING_DATA_BUFFER_PERCENTAGE_REMAINING",
|
||||||
|
"OUTGOING_DATA_BUFFER_PERCENTAGE_REMAINING"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"interval": 60
|
||||||
|
},
|
||||||
|
"userappEvents": true,
|
||||||
|
"warnings": {
|
||||||
|
"cpu": {
|
||||||
|
"reportIntervalInSec": 1800,
|
||||||
|
"threshold": 80
|
||||||
|
},
|
||||||
|
"database": true,
|
||||||
|
"flash": {
|
||||||
|
"reportIntervalInSec": 1800,
|
||||||
|
"threshold": 80
|
||||||
|
},
|
||||||
|
"ntp": true,
|
||||||
|
"radio_api": true,
|
||||||
|
"radio_control": true,
|
||||||
|
"ram": {
|
||||||
|
"reportIntervalInSec": 1800,
|
||||||
|
"threshold": 80
|
||||||
|
},
|
||||||
|
"reader_gateway": true,
|
||||||
|
"temperature": {
|
||||||
|
"ambient": 75,
|
||||||
|
"pa": 105
|
||||||
|
},
|
||||||
|
"userApp": {
|
||||||
|
"reportIntervalInSec": 1800,
|
||||||
|
"threshold": 60
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"retention": [
|
||||||
|
{
|
||||||
|
"maxEventRetentionTimeInMin": 500,
|
||||||
|
"maxNumEvents": 150000,
|
||||||
|
"throttle": 100
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"maxEventRetentionTimeInMin": 500,
|
||||||
|
"maxNumEvents": 150000,
|
||||||
|
"throttle": 100
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
settings {
|
||||||
|
encodeUrl: true
|
||||||
|
timeout: 0
|
||||||
|
}
|
||||||
12
brunoApi/rfidReaders/readerSpecific/folder.bru
Normal file
12
brunoApi/rfidReaders/readerSpecific/folder.bru
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
meta {
|
||||||
|
name: readerSpecific
|
||||||
|
}
|
||||||
|
|
||||||
|
auth {
|
||||||
|
mode: basic
|
||||||
|
}
|
||||||
|
|
||||||
|
auth:basic {
|
||||||
|
username: admin
|
||||||
|
password: Zebra123!
|
||||||
|
}
|
||||||
@@ -1,16 +1,21 @@
|
|||||||
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
|
- EXTERNAL_URL=192.168.8.222:3600
|
||||||
|
- 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}
|
||||||
@@ -21,7 +26,6 @@ services:
|
|||||||
- PROD_PASSWORD=${PROD_PASSWORD}
|
- PROD_PASSWORD=${PROD_PASSWORD}
|
||||||
- BETTER_AUTH_SECRET=${BETTER_AUTH_SECRET}
|
- BETTER_AUTH_SECRET=${BETTER_AUTH_SECRET}
|
||||||
- BETTER_AUTH_URL=${URL}
|
- BETTER_AUTH_URL=${URL}
|
||||||
restart: unless-stopped
|
|
||||||
# for all host including prod servers, plc's, printers, or other de
|
# for all host including prod servers, plc's, printers, or other de
|
||||||
# extra_hosts:
|
# extra_hosts:
|
||||||
# - "${PROD_SERVER}:${PROD_IP}"
|
# - "${PROD_SERVER}:${PROD_IP}"
|
||||||
|
|||||||
@@ -0,0 +1,9 @@
|
|||||||
|
import { createFileRoute } from '@tanstack/react-router'
|
||||||
|
|
||||||
|
export const Route = createFileRoute('/admin/settings')({
|
||||||
|
component: RouteComponent,
|
||||||
|
})
|
||||||
|
|
||||||
|
function RouteComponent() {
|
||||||
|
return <div>Hello "/admin/settings"!</div>
|
||||||
|
}
|
||||||
@@ -1,69 +0,0 @@
|
|||||||
# React + TypeScript + Vite
|
|
||||||
|
|
||||||
This template provides a minimal setup to get React working in Vite with HMR and some ESLint rules.
|
|
||||||
|
|
||||||
Currently, two official plugins are available:
|
|
||||||
|
|
||||||
- [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react) uses [Babel](https://babeljs.io/) for Fast Refresh
|
|
||||||
- [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react-swc) uses [SWC](https://swc.rs/) for Fast Refresh
|
|
||||||
|
|
||||||
## Expanding the ESLint configuration
|
|
||||||
|
|
||||||
If you are developing a production application, we recommend updating the configuration to enable type-aware lint rules:
|
|
||||||
|
|
||||||
```js
|
|
||||||
export default defineConfig([
|
|
||||||
globalIgnores(['dist']),
|
|
||||||
{
|
|
||||||
files: ['**/*.{ts,tsx}'],
|
|
||||||
extends: [
|
|
||||||
// Other configs...
|
|
||||||
|
|
||||||
// Remove tseslint.configs.recommended and replace with this
|
|
||||||
tseslint.configs.recommendedTypeChecked,
|
|
||||||
// Alternatively, use this for stricter rules
|
|
||||||
tseslint.configs.strictTypeChecked,
|
|
||||||
// Optionally, add this for stylistic rules
|
|
||||||
tseslint.configs.stylisticTypeChecked,
|
|
||||||
|
|
||||||
// Other configs...
|
|
||||||
],
|
|
||||||
languageOptions: {
|
|
||||||
parserOptions: {
|
|
||||||
project: ['./tsconfig.node.json', './tsconfig.app.json'],
|
|
||||||
tsconfigRootDir: import.meta.dirname,
|
|
||||||
},
|
|
||||||
// other options...
|
|
||||||
},
|
|
||||||
},
|
|
||||||
])
|
|
||||||
```
|
|
||||||
|
|
||||||
You can also install [eslint-plugin-react-x](https://github.com/Rel1cx/eslint-react/tree/main/packages/plugins/eslint-plugin-react-x) and [eslint-plugin-react-dom](https://github.com/Rel1cx/eslint-react/tree/main/packages/plugins/eslint-plugin-react-dom) for React-specific lint rules:
|
|
||||||
|
|
||||||
```js
|
|
||||||
// eslint.config.js
|
|
||||||
import reactX from 'eslint-plugin-react-x'
|
|
||||||
import reactDom from 'eslint-plugin-react-dom'
|
|
||||||
|
|
||||||
export default defineConfig([
|
|
||||||
globalIgnores(['dist']),
|
|
||||||
{
|
|
||||||
files: ['**/*.{ts,tsx}'],
|
|
||||||
extends: [
|
|
||||||
// Other configs...
|
|
||||||
// Enable lint rules for React
|
|
||||||
reactX.configs['recommended-typescript'],
|
|
||||||
// Enable lint rules for React DOM
|
|
||||||
reactDom.configs.recommended,
|
|
||||||
],
|
|
||||||
languageOptions: {
|
|
||||||
parserOptions: {
|
|
||||||
project: ['./tsconfig.node.json', './tsconfig.app.json'],
|
|
||||||
tsconfigRootDir: import.meta.dirname,
|
|
||||||
},
|
|
||||||
// other options...
|
|
||||||
},
|
|
||||||
},
|
|
||||||
])
|
|
||||||
```
|
|
||||||
@@ -69,7 +69,7 @@ export default function Header() {
|
|||||||
</DropdownMenuLabel>
|
</DropdownMenuLabel>
|
||||||
<DropdownMenuSeparator />
|
<DropdownMenuSeparator />
|
||||||
<DropdownMenuItem>
|
<DropdownMenuItem>
|
||||||
<Link to="/user/profile">Profile</Link>
|
<Link to="/user/profile">Account</Link>
|
||||||
</DropdownMenuItem>
|
</DropdownMenuItem>
|
||||||
|
|
||||||
{/* <DropdownMenuItem>Billing</DropdownMenuItem>
|
{/* <DropdownMenuItem>Billing</DropdownMenuItem>
|
||||||
|
|||||||
@@ -11,17 +11,27 @@ import {
|
|||||||
useSidebar,
|
useSidebar,
|
||||||
} from "../ui/sidebar";
|
} from "../ui/sidebar";
|
||||||
|
|
||||||
export default function AdminSidebar() {
|
// type AdminSidebarProps = {
|
||||||
|
// session: {
|
||||||
|
// user: {
|
||||||
|
// name?: string | null;
|
||||||
|
// email?: string | null;
|
||||||
|
// role?: string | string[];
|
||||||
|
// };
|
||||||
|
// } | null;
|
||||||
|
//};
|
||||||
|
|
||||||
|
export default function AdminSidebar({ session }: any) {
|
||||||
const { setOpen } = useSidebar();
|
const { setOpen } = useSidebar();
|
||||||
const items = [
|
const items = [
|
||||||
// {
|
{
|
||||||
// title: "Users",
|
title: "Settings",
|
||||||
// url: "/admin/users",
|
url: "/admin/settings",
|
||||||
// icon: User,
|
icon: Logs,
|
||||||
// role: ["systemAdmin", "admin"],
|
role: ["systemAdmin"],
|
||||||
// module: "admin",
|
module: "admin",
|
||||||
// active: true,
|
active: true,
|
||||||
// },
|
},
|
||||||
{
|
{
|
||||||
title: "Logs",
|
title: "Logs",
|
||||||
url: "/admin/logs",
|
url: "/admin/logs",
|
||||||
@@ -53,14 +63,18 @@ export default function AdminSidebar() {
|
|||||||
<SidebarGroupContent>
|
<SidebarGroupContent>
|
||||||
<SidebarMenu>
|
<SidebarMenu>
|
||||||
{items.map((item) => (
|
{items.map((item) => (
|
||||||
<SidebarMenuItem key={item.title}>
|
<>
|
||||||
<SidebarMenuButton asChild>
|
{item.role.includes(session.user.role) && (
|
||||||
<Link to={item.url} onClick={() => setOpen(false)}>
|
<SidebarMenuItem key={item.title}>
|
||||||
<item.icon />
|
<SidebarMenuButton asChild>
|
||||||
<span>{item.title}</span>
|
<Link to={item.url} onClick={() => setOpen(false)}>
|
||||||
</Link>
|
<item.icon />
|
||||||
</SidebarMenuButton>
|
<span>{item.title}</span>
|
||||||
</SidebarMenuItem>
|
</Link>
|
||||||
|
</SidebarMenuButton>
|
||||||
|
</SidebarMenuItem>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
))}
|
))}
|
||||||
</SidebarMenu>
|
</SidebarMenu>
|
||||||
</SidebarGroupContent>
|
</SidebarGroupContent>
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import AdminSidebar from "./AdminBar";
|
|||||||
|
|
||||||
export function AppSidebar() {
|
export function AppSidebar() {
|
||||||
const { data: session } = useSession();
|
const { data: session } = useSession();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Sidebar
|
<Sidebar
|
||||||
variant="sidebar"
|
variant="sidebar"
|
||||||
@@ -20,7 +21,11 @@ export function AppSidebar() {
|
|||||||
<SidebarMenu>
|
<SidebarMenu>
|
||||||
<SidebarMenuItem>
|
<SidebarMenuItem>
|
||||||
<SidebarContent>
|
<SidebarContent>
|
||||||
{session && session.user.role === "admin" && <AdminSidebar />}
|
{session &&
|
||||||
|
(session.user.role === "admin" ||
|
||||||
|
session.user.role === "systemAdmin") && (
|
||||||
|
<AdminSidebar session={session} />
|
||||||
|
)}
|
||||||
</SidebarContent>
|
</SidebarContent>
|
||||||
</SidebarMenuItem>
|
</SidebarMenuItem>
|
||||||
</SidebarMenu>
|
</SidebarMenu>
|
||||||
|
|||||||
53
frontend/src/components/ui/scroll-area.tsx
Normal file
53
frontend/src/components/ui/scroll-area.tsx
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
import * as React from "react"
|
||||||
|
import { ScrollArea as ScrollAreaPrimitive } from "radix-ui"
|
||||||
|
|
||||||
|
import { cn } from "@/lib/utils"
|
||||||
|
|
||||||
|
function ScrollArea({
|
||||||
|
className,
|
||||||
|
children,
|
||||||
|
...props
|
||||||
|
}: React.ComponentProps<typeof ScrollAreaPrimitive.Root>) {
|
||||||
|
return (
|
||||||
|
<ScrollAreaPrimitive.Root
|
||||||
|
data-slot="scroll-area"
|
||||||
|
className={cn("relative", className)}
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
<ScrollAreaPrimitive.Viewport
|
||||||
|
data-slot="scroll-area-viewport"
|
||||||
|
className="size-full rounded-[inherit] transition-[color,box-shadow] outline-none focus-visible:ring-[3px] focus-visible:ring-ring/50 focus-visible:outline-1"
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</ScrollAreaPrimitive.Viewport>
|
||||||
|
<ScrollBar />
|
||||||
|
<ScrollAreaPrimitive.Corner />
|
||||||
|
</ScrollAreaPrimitive.Root>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function ScrollBar({
|
||||||
|
className,
|
||||||
|
orientation = "vertical",
|
||||||
|
...props
|
||||||
|
}: React.ComponentProps<typeof ScrollAreaPrimitive.ScrollAreaScrollbar>) {
|
||||||
|
return (
|
||||||
|
<ScrollAreaPrimitive.ScrollAreaScrollbar
|
||||||
|
data-slot="scroll-area-scrollbar"
|
||||||
|
data-orientation={orientation}
|
||||||
|
orientation={orientation}
|
||||||
|
className={cn(
|
||||||
|
"flex touch-none p-px transition-colors select-none data-horizontal:h-2.5 data-horizontal:flex-col data-horizontal:border-t data-horizontal:border-t-transparent data-vertical:h-full data-vertical:w-2.5 data-vertical:border-l data-vertical:border-l-transparent",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
<ScrollAreaPrimitive.ScrollAreaThumb
|
||||||
|
data-slot="scroll-area-thumb"
|
||||||
|
className="relative flex-1 rounded-full bg-border"
|
||||||
|
/>
|
||||||
|
</ScrollAreaPrimitive.ScrollAreaScrollbar>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export { ScrollArea, ScrollBar }
|
||||||
190
frontend/src/components/ui/select.tsx
Normal file
190
frontend/src/components/ui/select.tsx
Normal 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,
|
||||||
|
}
|
||||||
31
frontend/src/components/ui/switch.tsx
Normal file
31
frontend/src/components/ui/switch.tsx
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
import * as React from "react"
|
||||||
|
import { Switch as SwitchPrimitive } from "radix-ui"
|
||||||
|
|
||||||
|
import { cn } from "@/lib/utils"
|
||||||
|
|
||||||
|
function Switch({
|
||||||
|
className,
|
||||||
|
size = "default",
|
||||||
|
...props
|
||||||
|
}: React.ComponentProps<typeof SwitchPrimitive.Root> & {
|
||||||
|
size?: "sm" | "default"
|
||||||
|
}) {
|
||||||
|
return (
|
||||||
|
<SwitchPrimitive.Root
|
||||||
|
data-slot="switch"
|
||||||
|
data-size={size}
|
||||||
|
className={cn(
|
||||||
|
"peer group/switch relative inline-flex shrink-0 items-center rounded-full border border-transparent transition-all outline-none after:absolute after:-inset-x-3 after:-inset-y-2 focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 aria-invalid:border-destructive aria-invalid:ring-3 aria-invalid:ring-destructive/20 data-[size=default]:h-[18.4px] data-[size=default]:w-[32px] data-[size=sm]:h-[14px] data-[size=sm]:w-[24px] dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40 data-checked:bg-primary data-unchecked:bg-input dark:data-unchecked:bg-input/80 data-disabled:cursor-not-allowed data-disabled:opacity-50",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
<SwitchPrimitive.Thumb
|
||||||
|
data-slot="switch-thumb"
|
||||||
|
className="pointer-events-none block rounded-full bg-background ring-0 transition-transform group-data-[size=default]/switch:size-4 group-data-[size=sm]/switch:size-3 group-data-[size=default]/switch:data-checked:translate-x-[calc(100%-2px)] group-data-[size=sm]/switch:data-checked:translate-x-[calc(100%-2px)] dark:data-checked:bg-primary-foreground group-data-[size=default]/switch:data-unchecked:translate-x-0 group-data-[size=sm]/switch:data-unchecked:translate-x-0 dark:data-unchecked:bg-foreground"
|
||||||
|
/>
|
||||||
|
</SwitchPrimitive.Root>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export { Switch }
|
||||||
114
frontend/src/components/ui/table.tsx
Normal file
114
frontend/src/components/ui/table.tsx
Normal file
@@ -0,0 +1,114 @@
|
|||||||
|
import * as React from "react"
|
||||||
|
|
||||||
|
import { cn } from "@/lib/utils"
|
||||||
|
|
||||||
|
function Table({ className, ...props }: React.ComponentProps<"table">) {
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
data-slot="table-container"
|
||||||
|
className="relative w-full overflow-x-auto"
|
||||||
|
>
|
||||||
|
<table
|
||||||
|
data-slot="table"
|
||||||
|
className={cn("w-full caption-bottom text-sm", className)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function TableHeader({ className, ...props }: React.ComponentProps<"thead">) {
|
||||||
|
return (
|
||||||
|
<thead
|
||||||
|
data-slot="table-header"
|
||||||
|
className={cn("[&_tr]:border-b", className)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function TableBody({ className, ...props }: React.ComponentProps<"tbody">) {
|
||||||
|
return (
|
||||||
|
<tbody
|
||||||
|
data-slot="table-body"
|
||||||
|
className={cn("[&_tr:last-child]:border-0", className)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function TableFooter({ className, ...props }: React.ComponentProps<"tfoot">) {
|
||||||
|
return (
|
||||||
|
<tfoot
|
||||||
|
data-slot="table-footer"
|
||||||
|
className={cn(
|
||||||
|
"border-t bg-muted/50 font-medium [&>tr]:last:border-b-0",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function TableRow({ className, ...props }: React.ComponentProps<"tr">) {
|
||||||
|
return (
|
||||||
|
<tr
|
||||||
|
data-slot="table-row"
|
||||||
|
className={cn(
|
||||||
|
"border-b transition-colors hover:bg-muted/50 data-[state=selected]:bg-muted",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function TableHead({ className, ...props }: React.ComponentProps<"th">) {
|
||||||
|
return (
|
||||||
|
<th
|
||||||
|
data-slot="table-head"
|
||||||
|
className={cn(
|
||||||
|
"h-10 px-2 text-left align-middle font-medium whitespace-nowrap text-foreground [&:has([role=checkbox])]:pr-0",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function TableCell({ className, ...props }: React.ComponentProps<"td">) {
|
||||||
|
return (
|
||||||
|
<td
|
||||||
|
data-slot="table-cell"
|
||||||
|
className={cn(
|
||||||
|
"p-2 align-middle whitespace-nowrap [&:has([role=checkbox])]:pr-0",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function TableCaption({
|
||||||
|
className,
|
||||||
|
...props
|
||||||
|
}: React.ComponentProps<"caption">) {
|
||||||
|
return (
|
||||||
|
<caption
|
||||||
|
data-slot="table-caption"
|
||||||
|
className={cn("mt-4 text-sm text-muted-foreground", className)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export {
|
||||||
|
Table,
|
||||||
|
TableHeader,
|
||||||
|
TableBody,
|
||||||
|
TableFooter,
|
||||||
|
TableHead,
|
||||||
|
TableRow,
|
||||||
|
TableCell,
|
||||||
|
TableCaption,
|
||||||
|
}
|
||||||
88
frontend/src/components/ui/tabs.tsx
Normal file
88
frontend/src/components/ui/tabs.tsx
Normal file
@@ -0,0 +1,88 @@
|
|||||||
|
import * as React from "react"
|
||||||
|
import { cva, type VariantProps } from "class-variance-authority"
|
||||||
|
import { Tabs as TabsPrimitive } from "radix-ui"
|
||||||
|
|
||||||
|
import { cn } from "@/lib/utils"
|
||||||
|
|
||||||
|
function Tabs({
|
||||||
|
className,
|
||||||
|
orientation = "horizontal",
|
||||||
|
...props
|
||||||
|
}: React.ComponentProps<typeof TabsPrimitive.Root>) {
|
||||||
|
return (
|
||||||
|
<TabsPrimitive.Root
|
||||||
|
data-slot="tabs"
|
||||||
|
data-orientation={orientation}
|
||||||
|
className={cn(
|
||||||
|
"group/tabs flex gap-2 data-horizontal:flex-col",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const tabsListVariants = cva(
|
||||||
|
"group/tabs-list inline-flex w-fit items-center justify-center rounded-lg p-[3px] text-muted-foreground group-data-horizontal/tabs:h-8 group-data-vertical/tabs:h-fit group-data-vertical/tabs:flex-col data-[variant=line]:rounded-none",
|
||||||
|
{
|
||||||
|
variants: {
|
||||||
|
variant: {
|
||||||
|
default: "bg-muted",
|
||||||
|
line: "gap-1 bg-transparent",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
defaultVariants: {
|
||||||
|
variant: "default",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
function TabsList({
|
||||||
|
className,
|
||||||
|
variant = "default",
|
||||||
|
...props
|
||||||
|
}: React.ComponentProps<typeof TabsPrimitive.List> &
|
||||||
|
VariantProps<typeof tabsListVariants>) {
|
||||||
|
return (
|
||||||
|
<TabsPrimitive.List
|
||||||
|
data-slot="tabs-list"
|
||||||
|
data-variant={variant}
|
||||||
|
className={cn(tabsListVariants({ variant }), className)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function TabsTrigger({
|
||||||
|
className,
|
||||||
|
...props
|
||||||
|
}: React.ComponentProps<typeof TabsPrimitive.Trigger>) {
|
||||||
|
return (
|
||||||
|
<TabsPrimitive.Trigger
|
||||||
|
data-slot="tabs-trigger"
|
||||||
|
className={cn(
|
||||||
|
"relative inline-flex h-[calc(100%-1px)] flex-1 items-center justify-center gap-1.5 rounded-md border border-transparent px-1.5 py-0.5 text-sm font-medium whitespace-nowrap text-foreground/60 transition-all group-data-vertical/tabs:w-full group-data-vertical/tabs:justify-start hover:text-foreground focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 focus-visible:outline-1 focus-visible:outline-ring disabled:pointer-events-none disabled:opacity-50 dark:text-muted-foreground dark:hover:text-foreground group-data-[variant=default]/tabs-list:data-active:shadow-sm group-data-[variant=line]/tabs-list:data-active:shadow-none [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
|
||||||
|
"group-data-[variant=line]/tabs-list:bg-transparent group-data-[variant=line]/tabs-list:data-active:bg-transparent dark:group-data-[variant=line]/tabs-list:data-active:border-transparent dark:group-data-[variant=line]/tabs-list:data-active:bg-transparent",
|
||||||
|
"data-active:bg-background data-active:text-foreground dark:data-active:border-input dark:data-active:bg-input/30 dark:data-active:text-foreground",
|
||||||
|
"after:absolute after:bg-foreground after:opacity-0 after:transition-opacity group-data-horizontal/tabs:after:inset-x-0 group-data-horizontal/tabs:after:bottom-[-5px] group-data-horizontal/tabs:after:h-0.5 group-data-vertical/tabs:after:inset-y-0 group-data-vertical/tabs:after:-right-1 group-data-vertical/tabs:after:w-0.5 group-data-[variant=line]/tabs-list:data-active:after:opacity-100",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function TabsContent({
|
||||||
|
className,
|
||||||
|
...props
|
||||||
|
}: React.ComponentProps<typeof TabsPrimitive.Content>) {
|
||||||
|
return (
|
||||||
|
<TabsPrimitive.Content
|
||||||
|
data-slot="tabs-content"
|
||||||
|
className={cn("flex-1 text-sm outline-none", className)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export { Tabs, TabsList, TabsTrigger, TabsContent, tabsListVariants }
|
||||||
44
frontend/src/components/ui/toggle.tsx
Normal file
44
frontend/src/components/ui/toggle.tsx
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
import * as React from "react"
|
||||||
|
import { cva, type VariantProps } from "class-variance-authority"
|
||||||
|
import { Toggle as TogglePrimitive } from "radix-ui"
|
||||||
|
|
||||||
|
import { cn } from "@/lib/utils"
|
||||||
|
|
||||||
|
const toggleVariants = cva(
|
||||||
|
"group/toggle inline-flex items-center justify-center gap-1 rounded-lg text-sm font-medium whitespace-nowrap transition-all outline-none hover:bg-muted hover:text-foreground focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 disabled:pointer-events-none disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-destructive/20 aria-pressed:bg-muted data-[state=on]:bg-muted dark:aria-invalid:ring-destructive/40 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
|
||||||
|
{
|
||||||
|
variants: {
|
||||||
|
variant: {
|
||||||
|
default: "bg-transparent",
|
||||||
|
outline: "border border-input bg-transparent hover:bg-muted",
|
||||||
|
},
|
||||||
|
size: {
|
||||||
|
default: "h-8 min-w-8 px-2",
|
||||||
|
sm: "h-7 min-w-7 rounded-[min(var(--radius-md),12px)] px-1.5 text-[0.8rem]",
|
||||||
|
lg: "h-9 min-w-9 px-2.5",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
defaultVariants: {
|
||||||
|
variant: "default",
|
||||||
|
size: "default",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
function Toggle({
|
||||||
|
className,
|
||||||
|
variant = "default",
|
||||||
|
size = "default",
|
||||||
|
...props
|
||||||
|
}: React.ComponentProps<typeof TogglePrimitive.Root> &
|
||||||
|
VariantProps<typeof toggleVariants>) {
|
||||||
|
return (
|
||||||
|
<TogglePrimitive.Root
|
||||||
|
data-slot="toggle"
|
||||||
|
className={cn(toggleVariants({ variant, size, className }))}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export { Toggle, toggleVariants }
|
||||||
87
frontend/src/lib/formSutff/DynamicInput.Field.tsx
Normal file
87
frontend/src/lib/formSutff/DynamicInput.Field.tsx
Normal 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>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -6,7 +6,7 @@ import { FieldErrors } from "./Errors.Field";
|
|||||||
type InputFieldProps = {
|
type InputFieldProps = {
|
||||||
label: string;
|
label: string;
|
||||||
inputType: string;
|
inputType: string;
|
||||||
required: boolean;
|
required?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
const autoCompleteMap: Record<string, string> = {
|
const autoCompleteMap: Record<string, string> = {
|
||||||
@@ -16,7 +16,11 @@ const autoCompleteMap: Record<string, string> = {
|
|||||||
username: "username",
|
username: "username",
|
||||||
};
|
};
|
||||||
|
|
||||||
export const InputField = ({ label, inputType, required }: InputFieldProps) => {
|
export const InputField = ({
|
||||||
|
label,
|
||||||
|
inputType,
|
||||||
|
required = false,
|
||||||
|
}: InputFieldProps) => {
|
||||||
const field = useFieldContext<any>();
|
const field = useFieldContext<any>();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
57
frontend/src/lib/formSutff/Select.Field.tsx
Normal file
57
frontend/src/lib/formSutff/Select.Field.tsx
Normal 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>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -1,13 +1,19 @@
|
|||||||
import { useStore } from "@tanstack/react-form";
|
import { useStore } from "@tanstack/react-form";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import { useFormContext } from ".";
|
|
||||||
import { Spinner } from "@/components/ui/spinner";
|
import { Spinner } from "@/components/ui/spinner";
|
||||||
|
import { useFormContext } from ".";
|
||||||
|
|
||||||
type SubmitButtonProps = {
|
type SubmitButtonProps = {
|
||||||
children: React.ReactNode;
|
children: React.ReactNode;
|
||||||
|
variant?: "default" | "secondary" | "destructive";
|
||||||
|
className?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const SubmitButton = ({ children }: SubmitButtonProps) => {
|
export const SubmitButton = ({
|
||||||
|
children,
|
||||||
|
variant = "default",
|
||||||
|
className,
|
||||||
|
}: SubmitButtonProps) => {
|
||||||
const form = useFormContext();
|
const form = useFormContext();
|
||||||
|
|
||||||
const [isSubmitting] = useStore(form.store, (state) => [
|
const [isSubmitting] = useStore(form.store, (state) => [
|
||||||
@@ -17,10 +23,19 @@ export const SubmitButton = ({ children }: SubmitButtonProps) => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="">
|
<div className="">
|
||||||
<Button type="submit" disabled={isSubmitting}>
|
<Button
|
||||||
{isSubmitting ? <><Spinner data-icon="inline-start" /> Submitting </> : <>{children}</>
|
type="submit"
|
||||||
}
|
disabled={isSubmitting}
|
||||||
|
variant={variant}
|
||||||
|
className={className}
|
||||||
|
>
|
||||||
|
{isSubmitting ? (
|
||||||
|
<>
|
||||||
|
<Spinner data-icon="inline-start" /> Submitting{" "}
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<>{children}</>
|
||||||
|
)}
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
29
frontend/src/lib/formSutff/Switch.Field.tsx
Normal file
29
frontend/src/lib/formSutff/Switch.Field.tsx
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
import { Label } from "../../components/ui/label";
|
||||||
|
import { Switch } from "../../components/ui/switch";
|
||||||
|
import { useFieldContext } from ".";
|
||||||
|
|
||||||
|
type SwitchField = {
|
||||||
|
trueLabel: string;
|
||||||
|
falseLabel: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const SwitchField = ({
|
||||||
|
trueLabel = "True",
|
||||||
|
falseLabel = "False",
|
||||||
|
}: SwitchField) => {
|
||||||
|
const field = useFieldContext<boolean>();
|
||||||
|
|
||||||
|
const checked = field.state.value ?? false;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="flex items-center space-x-2">
|
||||||
|
<Switch
|
||||||
|
id={field.name}
|
||||||
|
checked={checked}
|
||||||
|
onCheckedChange={field.handleChange}
|
||||||
|
onBlur={field.handleBlur}
|
||||||
|
/>
|
||||||
|
<Label htmlFor={field.name}>{checked ? trueLabel : falseLabel}</Label>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -1,8 +1,11 @@
|
|||||||
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";
|
||||||
|
|
||||||
export const { fieldContext, useFieldContext, formContext, useFormContext } =
|
export const { fieldContext, useFieldContext, formContext, useFormContext } =
|
||||||
createFormHookContexts();
|
createFormHookContexts();
|
||||||
@@ -11,11 +14,13 @@ export const { useAppForm } = createFormHook({
|
|||||||
fieldComponents: {
|
fieldComponents: {
|
||||||
InputField,
|
InputField,
|
||||||
InputPasswordField,
|
InputPasswordField,
|
||||||
//SelectField,
|
SelectField,
|
||||||
CheckboxField,
|
CheckboxField,
|
||||||
//DateField,
|
//DateField,
|
||||||
//TextArea,
|
//TextArea,
|
||||||
//Searchable,
|
//Searchable,
|
||||||
|
SwitchField,
|
||||||
|
DynamicInputField,
|
||||||
},
|
},
|
||||||
formComponents: { SubmitButton },
|
formComponents: { SubmitButton },
|
||||||
fieldContext,
|
fieldContext,
|
||||||
|
|||||||
22
frontend/src/lib/queries/getSettings.ts
Normal file
22
frontend/src/lib/queries/getSettings.ts
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
import { keepPreviousData, queryOptions } from "@tanstack/react-query";
|
||||||
|
import axios from "axios";
|
||||||
|
|
||||||
|
export function getSettings() {
|
||||||
|
return queryOptions({
|
||||||
|
queryKey: ["getSettings"],
|
||||||
|
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/settings");
|
||||||
|
|
||||||
|
return data.data;
|
||||||
|
};
|
||||||
24
frontend/src/lib/queries/notificationSubs.ts
Normal file
24
frontend/src/lib/queries/notificationSubs.ts
Normal 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;
|
||||||
|
};
|
||||||
22
frontend/src/lib/queries/notifications.ts
Normal file
22
frontend/src/lib/queries/notifications.ts
Normal 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;
|
||||||
|
};
|
||||||
70
frontend/src/lib/tableStuff/EditableCellInput.tsx
Normal file
70
frontend/src/lib/tableStuff/EditableCellInput.tsx
Normal file
@@ -0,0 +1,70 @@
|
|||||||
|
import { useEffect, useRef, useState } from "react";
|
||||||
|
import { Input } from "../../components/ui/input";
|
||||||
|
|
||||||
|
type EditableCell = {
|
||||||
|
value: string | number | null | undefined;
|
||||||
|
id: string;
|
||||||
|
field: string;
|
||||||
|
className?: string;
|
||||||
|
onSubmit: (args: { id: string; field: string; value: string }) => void;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default function EditableCellInput({
|
||||||
|
value,
|
||||||
|
id,
|
||||||
|
field,
|
||||||
|
className = "w-32",
|
||||||
|
onSubmit,
|
||||||
|
}: EditableCell) {
|
||||||
|
const initialValue = String(value ?? "");
|
||||||
|
const [localValue, setLocalValue] = useState(initialValue);
|
||||||
|
const submitting = useRef(false);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setLocalValue(initialValue);
|
||||||
|
}, [initialValue]);
|
||||||
|
|
||||||
|
const handleSubmit = (nextValue: string) => {
|
||||||
|
const trimmedValue = nextValue.trim();
|
||||||
|
|
||||||
|
if (trimmedValue === initialValue) return;
|
||||||
|
|
||||||
|
onSubmit({
|
||||||
|
id,
|
||||||
|
field,
|
||||||
|
value: trimmedValue,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Input
|
||||||
|
value={localValue}
|
||||||
|
className={className}
|
||||||
|
onChange={(e) => setLocalValue(e.currentTarget.value)}
|
||||||
|
onBlur={(e) => {
|
||||||
|
if (submitting.current) return;
|
||||||
|
|
||||||
|
submitting.current = true;
|
||||||
|
handleSubmit(e.currentTarget.value);
|
||||||
|
setTimeout(() => {
|
||||||
|
submitting.current = false;
|
||||||
|
}, 100);
|
||||||
|
}}
|
||||||
|
onKeyDown={(e) => {
|
||||||
|
if (e.key !== "Enter") return;
|
||||||
|
|
||||||
|
e.preventDefault();
|
||||||
|
|
||||||
|
if (submitting.current) return;
|
||||||
|
|
||||||
|
submitting.current = true;
|
||||||
|
handleSubmit(e.currentTarget.value);
|
||||||
|
e.currentTarget.blur();
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
submitting.current = false;
|
||||||
|
}, 100);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
129
frontend/src/lib/tableStuff/LstTable.tsx
Normal file
129
frontend/src/lib/tableStuff/LstTable.tsx
Normal file
@@ -0,0 +1,129 @@
|
|||||||
|
import {
|
||||||
|
type ColumnFiltersState,
|
||||||
|
flexRender,
|
||||||
|
getCoreRowModel,
|
||||||
|
getFilteredRowModel,
|
||||||
|
getPaginationRowModel,
|
||||||
|
getSortedRowModel,
|
||||||
|
type SortingState,
|
||||||
|
useReactTable,
|
||||||
|
} from "@tanstack/react-table";
|
||||||
|
import React, { useState } from "react";
|
||||||
|
import { Button } from "../../components/ui/button";
|
||||||
|
import { ScrollArea, ScrollBar } from "../../components/ui/scroll-area";
|
||||||
|
import {
|
||||||
|
Table,
|
||||||
|
TableBody,
|
||||||
|
TableCell,
|
||||||
|
TableHead,
|
||||||
|
TableHeader,
|
||||||
|
TableRow,
|
||||||
|
} from "../../components/ui/table";
|
||||||
|
import { cn } from "../utils";
|
||||||
|
|
||||||
|
type LstTableType = {
|
||||||
|
className?: string;
|
||||||
|
tableClassName?: string;
|
||||||
|
data: any;
|
||||||
|
columns: any;
|
||||||
|
};
|
||||||
|
export default function LstTable({
|
||||||
|
className = "",
|
||||||
|
tableClassName = "",
|
||||||
|
data,
|
||||||
|
columns,
|
||||||
|
}: LstTableType) {
|
||||||
|
const [sorting, setSorting] = useState<SortingState>([]);
|
||||||
|
const [columnFilters, setColumnFilters] = useState<ColumnFiltersState>([]);
|
||||||
|
//console.log(data);
|
||||||
|
|
||||||
|
const table = useReactTable({
|
||||||
|
data,
|
||||||
|
columns,
|
||||||
|
getCoreRowModel: getCoreRowModel(),
|
||||||
|
getPaginationRowModel: getPaginationRowModel(),
|
||||||
|
onSortingChange: setSorting,
|
||||||
|
getSortedRowModel: getSortedRowModel(),
|
||||||
|
onColumnFiltersChange: setColumnFilters,
|
||||||
|
getFilteredRowModel: getFilteredRowModel(),
|
||||||
|
//renderSubComponent: ({ row }: { row: any }) => <ExpandedRow row={row} />,
|
||||||
|
//getRowCanExpand: () => true,
|
||||||
|
filterFns: {},
|
||||||
|
state: {
|
||||||
|
sorting,
|
||||||
|
columnFilters,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
return (
|
||||||
|
<div className={className}>
|
||||||
|
<ScrollArea className="w-full rounded-md border whitespace-nowrap">
|
||||||
|
<Table className={cn("w-full", tableClassName)}>
|
||||||
|
<TableHeader>
|
||||||
|
{table.getHeaderGroups().map((headerGroup) => (
|
||||||
|
<TableRow key={headerGroup.id}>
|
||||||
|
{headerGroup.headers.map((header) => {
|
||||||
|
return (
|
||||||
|
<TableHead key={header.id}>
|
||||||
|
{header.isPlaceholder
|
||||||
|
? null
|
||||||
|
: flexRender(
|
||||||
|
header.column.columnDef.header,
|
||||||
|
header.getContext(),
|
||||||
|
)}
|
||||||
|
</TableHead>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</TableRow>
|
||||||
|
))}
|
||||||
|
</TableHeader>
|
||||||
|
<TableBody>
|
||||||
|
{table.getRowModel().rows.length ? (
|
||||||
|
table.getRowModel().rows.map((row) => (
|
||||||
|
<React.Fragment key={row.id}>
|
||||||
|
<TableRow data-state={row.getIsSelected() && "selected"}>
|
||||||
|
{row.getVisibleCells().map((cell) => (
|
||||||
|
<TableCell key={cell.id}>
|
||||||
|
{flexRender(
|
||||||
|
cell.column.columnDef.cell,
|
||||||
|
cell.getContext(),
|
||||||
|
)}
|
||||||
|
</TableCell>
|
||||||
|
))}
|
||||||
|
</TableRow>
|
||||||
|
</React.Fragment>
|
||||||
|
))
|
||||||
|
) : (
|
||||||
|
<TableRow>
|
||||||
|
<TableCell
|
||||||
|
colSpan={columns.length}
|
||||||
|
className="h-24 text-center"
|
||||||
|
>
|
||||||
|
No results.
|
||||||
|
</TableCell>
|
||||||
|
</TableRow>
|
||||||
|
)}
|
||||||
|
</TableBody>
|
||||||
|
</Table>
|
||||||
|
<ScrollBar orientation="horizontal" />
|
||||||
|
</ScrollArea>
|
||||||
|
<div className="flex items-center justify-end space-x-2 py-4">
|
||||||
|
<Button
|
||||||
|
variant="outline"
|
||||||
|
size="sm"
|
||||||
|
onClick={() => table.previousPage()}
|
||||||
|
disabled={!table.getCanPreviousPage()}
|
||||||
|
>
|
||||||
|
Previous
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
variant="outline"
|
||||||
|
size="sm"
|
||||||
|
onClick={() => table.nextPage()}
|
||||||
|
disabled={!table.getCanNextPage()}
|
||||||
|
>
|
||||||
|
Next
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
67
frontend/src/lib/tableStuff/SearchableHeader.tsx
Normal file
67
frontend/src/lib/tableStuff/SearchableHeader.tsx
Normal file
@@ -0,0 +1,67 @@
|
|||||||
|
import type { Column } from "@tanstack/react-table";
|
||||||
|
import { ArrowDown, ArrowUp, Search } from "lucide-react";
|
||||||
|
import { useState } from "react";
|
||||||
|
import { Button } from "../../components/ui/button";
|
||||||
|
import {
|
||||||
|
DropdownMenu,
|
||||||
|
DropdownMenuContent,
|
||||||
|
DropdownMenuTrigger,
|
||||||
|
} from "../../components/ui/dropdown-menu";
|
||||||
|
import { Input } from "../../components/ui/input";
|
||||||
|
import { cn } from "../utils";
|
||||||
|
|
||||||
|
type SearchableHeaderProps<TData> = {
|
||||||
|
column: Column<TData, unknown>;
|
||||||
|
title: string;
|
||||||
|
searchable?: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default function SearchableHeader<TData>({
|
||||||
|
column,
|
||||||
|
title,
|
||||||
|
searchable = false,
|
||||||
|
}: SearchableHeaderProps<TData>) {
|
||||||
|
const [open, setOpen] = useState(false);
|
||||||
|
return (
|
||||||
|
<div className="flex items-center gap-1">
|
||||||
|
<Button
|
||||||
|
variant="ghost"
|
||||||
|
className="px-2"
|
||||||
|
onClick={() => column.toggleSorting(column.getIsSorted() === "asc")}
|
||||||
|
>
|
||||||
|
<span className="flex flex-row items-center gap-2">
|
||||||
|
{title}
|
||||||
|
{column.getIsSorted() === "asc" ? (
|
||||||
|
<ArrowUp className="h-4 w-4" />
|
||||||
|
) : column.getIsSorted() === "desc" ? (
|
||||||
|
<ArrowDown className="h-4 w-4" />
|
||||||
|
) : null}
|
||||||
|
</span>
|
||||||
|
</Button>
|
||||||
|
{searchable && (
|
||||||
|
<DropdownMenu open={open} onOpenChange={setOpen}>
|
||||||
|
<DropdownMenuTrigger asChild>
|
||||||
|
<Button variant="ghost" size="icon" className="h-8 w-8">
|
||||||
|
<Search
|
||||||
|
className={cn(
|
||||||
|
"h-4 w-4",
|
||||||
|
column.getFilterValue() ? "text-primary" : "",
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</Button>
|
||||||
|
</DropdownMenuTrigger>
|
||||||
|
|
||||||
|
<DropdownMenuContent align="start" className="w-56 p-2">
|
||||||
|
<Input
|
||||||
|
autoFocus
|
||||||
|
value={(column.getFilterValue() as string) ?? ""}
|
||||||
|
onChange={(e) => column.setFilterValue(e.target.value)}
|
||||||
|
placeholder={`Search ${title.toLowerCase()}...`}
|
||||||
|
className="h-8"
|
||||||
|
/>
|
||||||
|
</DropdownMenuContent>
|
||||||
|
</DropdownMenu>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
40
frontend/src/lib/tableStuff/SkellyTable.tsx
Normal file
40
frontend/src/lib/tableStuff/SkellyTable.tsx
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
import { Skeleton } from "../../components/ui/skeleton";
|
||||||
|
import {
|
||||||
|
Table,
|
||||||
|
TableBody,
|
||||||
|
TableCell,
|
||||||
|
TableHead,
|
||||||
|
TableHeader,
|
||||||
|
TableRow,
|
||||||
|
} from "../../components/ui/table";
|
||||||
|
|
||||||
|
type TableSkelly = {
|
||||||
|
rows?: number;
|
||||||
|
columns?: number;
|
||||||
|
};
|
||||||
|
export default function SkellyTable({ rows = 5, columns = 4 }: TableSkelly) {
|
||||||
|
return (
|
||||||
|
<div className="rounded-md border">
|
||||||
|
<Table>
|
||||||
|
<TableHeader>
|
||||||
|
{Array.from({ length: columns }).map((_, i) => (
|
||||||
|
<TableHead key={i}>
|
||||||
|
<Skeleton className="h-4 w-[80px]" />
|
||||||
|
</TableHead>
|
||||||
|
))}
|
||||||
|
</TableHeader>
|
||||||
|
<TableBody>
|
||||||
|
{Array.from({ length: rows }).map((_, r) => (
|
||||||
|
<TableRow key={r}>
|
||||||
|
{Array.from({ length: columns }).map((_, c) => (
|
||||||
|
<TableCell key={c}>
|
||||||
|
<Skeleton className="h-4 w-full max-w-[120px]" />
|
||||||
|
</TableCell>
|
||||||
|
))}
|
||||||
|
</TableRow>
|
||||||
|
))}
|
||||||
|
</TableBody>
|
||||||
|
</Table>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -11,6 +11,7 @@
|
|||||||
import { Route as rootRouteImport } from './routes/__root'
|
import { Route as rootRouteImport } from './routes/__root'
|
||||||
import { Route as AboutRouteImport } from './routes/about'
|
import { Route as AboutRouteImport } from './routes/about'
|
||||||
import { Route as IndexRouteImport } from './routes/index'
|
import { Route as IndexRouteImport } from './routes/index'
|
||||||
|
import { Route as AdminSettingsRouteImport } from './routes/admin/settings'
|
||||||
import { Route as AdminLogsRouteImport } from './routes/admin/logs'
|
import { Route as AdminLogsRouteImport } from './routes/admin/logs'
|
||||||
import { Route as authLoginRouteImport } from './routes/(auth)/login'
|
import { Route as authLoginRouteImport } from './routes/(auth)/login'
|
||||||
import { Route as authUserSignupRouteImport } from './routes/(auth)/user.signup'
|
import { Route as authUserSignupRouteImport } from './routes/(auth)/user.signup'
|
||||||
@@ -27,6 +28,11 @@ const IndexRoute = IndexRouteImport.update({
|
|||||||
path: '/',
|
path: '/',
|
||||||
getParentRoute: () => rootRouteImport,
|
getParentRoute: () => rootRouteImport,
|
||||||
} as any)
|
} as any)
|
||||||
|
const AdminSettingsRoute = AdminSettingsRouteImport.update({
|
||||||
|
id: '/admin/settings',
|
||||||
|
path: '/admin/settings',
|
||||||
|
getParentRoute: () => rootRouteImport,
|
||||||
|
} as any)
|
||||||
const AdminLogsRoute = AdminLogsRouteImport.update({
|
const AdminLogsRoute = AdminLogsRouteImport.update({
|
||||||
id: '/admin/logs',
|
id: '/admin/logs',
|
||||||
path: '/admin/logs',
|
path: '/admin/logs',
|
||||||
@@ -58,6 +64,7 @@ export interface FileRoutesByFullPath {
|
|||||||
'/about': typeof AboutRoute
|
'/about': typeof AboutRoute
|
||||||
'/login': typeof authLoginRoute
|
'/login': typeof authLoginRoute
|
||||||
'/admin/logs': typeof AdminLogsRoute
|
'/admin/logs': typeof AdminLogsRoute
|
||||||
|
'/admin/settings': typeof AdminSettingsRoute
|
||||||
'/user/profile': typeof authUserProfileRoute
|
'/user/profile': typeof authUserProfileRoute
|
||||||
'/user/resetpassword': typeof authUserResetpasswordRoute
|
'/user/resetpassword': typeof authUserResetpasswordRoute
|
||||||
'/user/signup': typeof authUserSignupRoute
|
'/user/signup': typeof authUserSignupRoute
|
||||||
@@ -67,6 +74,7 @@ export interface FileRoutesByTo {
|
|||||||
'/about': typeof AboutRoute
|
'/about': typeof AboutRoute
|
||||||
'/login': typeof authLoginRoute
|
'/login': typeof authLoginRoute
|
||||||
'/admin/logs': typeof AdminLogsRoute
|
'/admin/logs': typeof AdminLogsRoute
|
||||||
|
'/admin/settings': typeof AdminSettingsRoute
|
||||||
'/user/profile': typeof authUserProfileRoute
|
'/user/profile': typeof authUserProfileRoute
|
||||||
'/user/resetpassword': typeof authUserResetpasswordRoute
|
'/user/resetpassword': typeof authUserResetpasswordRoute
|
||||||
'/user/signup': typeof authUserSignupRoute
|
'/user/signup': typeof authUserSignupRoute
|
||||||
@@ -77,6 +85,7 @@ export interface FileRoutesById {
|
|||||||
'/about': typeof AboutRoute
|
'/about': typeof AboutRoute
|
||||||
'/(auth)/login': typeof authLoginRoute
|
'/(auth)/login': typeof authLoginRoute
|
||||||
'/admin/logs': typeof AdminLogsRoute
|
'/admin/logs': typeof AdminLogsRoute
|
||||||
|
'/admin/settings': typeof AdminSettingsRoute
|
||||||
'/(auth)/user/profile': typeof authUserProfileRoute
|
'/(auth)/user/profile': typeof authUserProfileRoute
|
||||||
'/(auth)/user/resetpassword': typeof authUserResetpasswordRoute
|
'/(auth)/user/resetpassword': typeof authUserResetpasswordRoute
|
||||||
'/(auth)/user/signup': typeof authUserSignupRoute
|
'/(auth)/user/signup': typeof authUserSignupRoute
|
||||||
@@ -88,6 +97,7 @@ export interface FileRouteTypes {
|
|||||||
| '/about'
|
| '/about'
|
||||||
| '/login'
|
| '/login'
|
||||||
| '/admin/logs'
|
| '/admin/logs'
|
||||||
|
| '/admin/settings'
|
||||||
| '/user/profile'
|
| '/user/profile'
|
||||||
| '/user/resetpassword'
|
| '/user/resetpassword'
|
||||||
| '/user/signup'
|
| '/user/signup'
|
||||||
@@ -97,6 +107,7 @@ export interface FileRouteTypes {
|
|||||||
| '/about'
|
| '/about'
|
||||||
| '/login'
|
| '/login'
|
||||||
| '/admin/logs'
|
| '/admin/logs'
|
||||||
|
| '/admin/settings'
|
||||||
| '/user/profile'
|
| '/user/profile'
|
||||||
| '/user/resetpassword'
|
| '/user/resetpassword'
|
||||||
| '/user/signup'
|
| '/user/signup'
|
||||||
@@ -106,6 +117,7 @@ export interface FileRouteTypes {
|
|||||||
| '/about'
|
| '/about'
|
||||||
| '/(auth)/login'
|
| '/(auth)/login'
|
||||||
| '/admin/logs'
|
| '/admin/logs'
|
||||||
|
| '/admin/settings'
|
||||||
| '/(auth)/user/profile'
|
| '/(auth)/user/profile'
|
||||||
| '/(auth)/user/resetpassword'
|
| '/(auth)/user/resetpassword'
|
||||||
| '/(auth)/user/signup'
|
| '/(auth)/user/signup'
|
||||||
@@ -116,6 +128,7 @@ export interface RootRouteChildren {
|
|||||||
AboutRoute: typeof AboutRoute
|
AboutRoute: typeof AboutRoute
|
||||||
authLoginRoute: typeof authLoginRoute
|
authLoginRoute: typeof authLoginRoute
|
||||||
AdminLogsRoute: typeof AdminLogsRoute
|
AdminLogsRoute: typeof AdminLogsRoute
|
||||||
|
AdminSettingsRoute: typeof AdminSettingsRoute
|
||||||
authUserProfileRoute: typeof authUserProfileRoute
|
authUserProfileRoute: typeof authUserProfileRoute
|
||||||
authUserResetpasswordRoute: typeof authUserResetpasswordRoute
|
authUserResetpasswordRoute: typeof authUserResetpasswordRoute
|
||||||
authUserSignupRoute: typeof authUserSignupRoute
|
authUserSignupRoute: typeof authUserSignupRoute
|
||||||
@@ -137,6 +150,13 @@ declare module '@tanstack/react-router' {
|
|||||||
preLoaderRoute: typeof IndexRouteImport
|
preLoaderRoute: typeof IndexRouteImport
|
||||||
parentRoute: typeof rootRouteImport
|
parentRoute: typeof rootRouteImport
|
||||||
}
|
}
|
||||||
|
'/admin/settings': {
|
||||||
|
id: '/admin/settings'
|
||||||
|
path: '/admin/settings'
|
||||||
|
fullPath: '/admin/settings'
|
||||||
|
preLoaderRoute: typeof AdminSettingsRouteImport
|
||||||
|
parentRoute: typeof rootRouteImport
|
||||||
|
}
|
||||||
'/admin/logs': {
|
'/admin/logs': {
|
||||||
id: '/admin/logs'
|
id: '/admin/logs'
|
||||||
path: '/admin/logs'
|
path: '/admin/logs'
|
||||||
@@ -180,6 +200,7 @@ const rootRouteChildren: RootRouteChildren = {
|
|||||||
AboutRoute: AboutRoute,
|
AboutRoute: AboutRoute,
|
||||||
authLoginRoute: authLoginRoute,
|
authLoginRoute: authLoginRoute,
|
||||||
AdminLogsRoute: AdminLogsRoute,
|
AdminLogsRoute: AdminLogsRoute,
|
||||||
|
AdminSettingsRoute: AdminSettingsRoute,
|
||||||
authUserProfileRoute: authUserProfileRoute,
|
authUserProfileRoute: authUserProfileRoute,
|
||||||
authUserResetpasswordRoute: authUserResetpasswordRoute,
|
authUserResetpasswordRoute: authUserResetpasswordRoute,
|
||||||
authUserSignupRoute: authUserSignupRoute,
|
authUserSignupRoute: authUserSignupRoute,
|
||||||
|
|||||||
@@ -79,7 +79,9 @@ export default function ChangePassword() {
|
|||||||
|
|
||||||
<div className="flex justify-end mt-6">
|
<div className="flex justify-end mt-6">
|
||||||
<form.AppForm>
|
<form.AppForm>
|
||||||
<form.SubmitButton>Update Profile</form.SubmitButton>
|
<form.SubmitButton variant="destructive">
|
||||||
|
Update Password
|
||||||
|
</form.SubmitButton>
|
||||||
</form.AppForm>
|
</form.AppForm>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
|
|||||||
@@ -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 socket from "../../../lib/socket.io";
|
||||||
|
|
||||||
export default function LoginForm({ redirectPath }: { redirectPath: string }) {
|
export default function LoginForm({ redirectPath }: { redirectPath: string }) {
|
||||||
const loginEmail = localStorage.getItem("loginEmail") || "";
|
const loginEmail = localStorage.getItem("loginEmail") || "";
|
||||||
@@ -47,8 +48,12 @@ export default function LoginForm({ redirectPath }: { redirectPath: string }) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
toast.success(`Welcome back ${login.data?.user.name}`);
|
toast.success(`Welcome back ${login.data?.user.name}`);
|
||||||
|
if (login.data) {
|
||||||
|
socket.disconnect();
|
||||||
|
socket.connect();
|
||||||
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.log(error);
|
console.error(error);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -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>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -25,7 +25,7 @@ function RouteComponent() {
|
|||||||
const redirectPath = search.redirect ?? "/";
|
const redirectPath = search.redirect ?? "/";
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex justify-center mt-10">
|
<div className="flex justify-center mt-2">
|
||||||
<LoginForm redirectPath={redirectPath} />
|
<LoginForm redirectPath={redirectPath} />
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -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 () => {
|
||||||
@@ -54,7 +57,7 @@ function RouteComponent() {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
return (
|
return (
|
||||||
<div className="flex justify-center mt-2 gap-2">
|
<div className="flex justify-center flex-col pt-4 gap-2 lg:flex-row">
|
||||||
<div>
|
<div>
|
||||||
<Card className="p-6 w-96">
|
<Card className="p-6 w-96">
|
||||||
<CardHeader>
|
<CardHeader>
|
||||||
@@ -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>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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 = () => (
|
||||||
@@ -11,12 +11,15 @@ const RootLayout = () => (
|
|||||||
<ThemeProvider>
|
<ThemeProvider>
|
||||||
<SidebarProvider className="flex flex-col" defaultOpen={false}>
|
<SidebarProvider className="flex flex-col" defaultOpen={false}>
|
||||||
<Header />
|
<Header />
|
||||||
<div className="flex flex-1">
|
|
||||||
|
<div className="relative min-h-[calc(100svh-var(--header-height))]">
|
||||||
<AppSidebar />
|
<AppSidebar />
|
||||||
|
|
||||||
<SidebarInset>
|
<main className="w-full p-4">
|
||||||
<Outlet />
|
<div className="mx-auto w-full max-w-7xl">
|
||||||
</SidebarInset>
|
<Outlet />
|
||||||
|
</div>
|
||||||
|
</main>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<Toaster expand richColors closeButton />
|
<Toaster expand richColors closeButton />
|
||||||
|
|||||||
92
frontend/src/routes/admin/-components/FeatureCard.tsx
Normal file
92
frontend/src/routes/admin/-components/FeatureCard.tsx
Normal file
@@ -0,0 +1,92 @@
|
|||||||
|
import { useSuspenseQuery } from "@tanstack/react-query";
|
||||||
|
import axios from "axios";
|
||||||
|
import { toast } from "sonner";
|
||||||
|
import { Card, CardDescription, CardHeader } from "../../../components/ui/card";
|
||||||
|
import { useAppForm } from "../../../lib/formSutff";
|
||||||
|
import { getSettings } from "../../../lib/queries/getSettings";
|
||||||
|
|
||||||
|
type Setting = {
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
description?: string;
|
||||||
|
value: string;
|
||||||
|
active: boolean;
|
||||||
|
inputType: "text" | "boolean" | "number" | "select";
|
||||||
|
options?: string[];
|
||||||
|
};
|
||||||
|
|
||||||
|
export default function FeatureCard({ item }: { item: Setting }) {
|
||||||
|
const { refetch } = useSuspenseQuery(getSettings());
|
||||||
|
const form = useAppForm({
|
||||||
|
defaultValues: {
|
||||||
|
value: item.value ?? "",
|
||||||
|
active: item.active,
|
||||||
|
},
|
||||||
|
onSubmit: async ({ value }) => {
|
||||||
|
try {
|
||||||
|
// adding this in as my base as i need to see timers working
|
||||||
|
if (window.location.hostname === "localhost") {
|
||||||
|
await new Promise((res) => setTimeout(res, 1000));
|
||||||
|
}
|
||||||
|
|
||||||
|
const { data } = await axios.patch(`/lst/api/settings/${item.name}`, {
|
||||||
|
value: value.value,
|
||||||
|
active: value.active ? "true" : "false",
|
||||||
|
}, {
|
||||||
|
withCredentials: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
refetch();
|
||||||
|
toast.success(
|
||||||
|
<div>
|
||||||
|
<p>{data.message}</p>
|
||||||
|
<p>
|
||||||
|
This was a feature setting so{" "}
|
||||||
|
{value.active
|
||||||
|
? "processes related to this will start working on there next interval"
|
||||||
|
: "processes related to this will stop working on there next interval"}
|
||||||
|
</p>
|
||||||
|
</div>,
|
||||||
|
);
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
});
|
||||||
|
return (
|
||||||
|
<Card className="p-2 w-96">
|
||||||
|
<CardHeader>
|
||||||
|
<p>{item.name}</p>
|
||||||
|
<CardDescription>
|
||||||
|
<p>{item.description}</p>
|
||||||
|
</CardDescription>
|
||||||
|
</CardHeader>
|
||||||
|
|
||||||
|
<form
|
||||||
|
onSubmit={(e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
e.stopPropagation();
|
||||||
|
void form.handleSubmit();
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div className="flex justify-end mt-2 flex-col gap-4">
|
||||||
|
<form.AppField name="value">
|
||||||
|
{(field) => (
|
||||||
|
<field.InputField label="Setting Value" inputType="string" />
|
||||||
|
)}
|
||||||
|
</form.AppField>
|
||||||
|
<div className="flex flex-row justify-between">
|
||||||
|
<form.AppField name="active">
|
||||||
|
{(field) => (
|
||||||
|
<field.SwitchField trueLabel="Active" falseLabel="Deactivate" />
|
||||||
|
)}
|
||||||
|
</form.AppField>
|
||||||
|
<form.AppForm>
|
||||||
|
<form.SubmitButton>Update</form.SubmitButton>
|
||||||
|
</form.AppForm>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</Card>
|
||||||
|
);
|
||||||
|
}
|
||||||
11
frontend/src/routes/admin/-components/FeatureSettings.tsx
Normal file
11
frontend/src/routes/admin/-components/FeatureSettings.tsx
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
import FeatureCard from "./FeatureCard";
|
||||||
|
|
||||||
|
export default function FeatureSettings({ data }: any) {
|
||||||
|
return (
|
||||||
|
<div className=" flex flex-wrap gap-2">
|
||||||
|
{data.map((i: any) => (
|
||||||
|
<FeatureCard key={i.name} item={i} />
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -5,6 +5,7 @@ import { authClient } from "@/lib/auth-client";
|
|||||||
export const Route = createFileRoute("/admin/logs")({
|
export const Route = createFileRoute("/admin/logs")({
|
||||||
beforeLoad: async ({ location }) => {
|
beforeLoad: async ({ location }) => {
|
||||||
const { data: session } = await authClient.getSession();
|
const { data: session } = await authClient.getSession();
|
||||||
|
const allowedRole = ["admin", "systemAdmin"];
|
||||||
|
|
||||||
if (!session?.user) {
|
if (!session?.user) {
|
||||||
throw redirect({
|
throw redirect({
|
||||||
@@ -15,7 +16,7 @@ export const Route = createFileRoute("/admin/logs")({
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (session.user.role !== "admin") {
|
if (!allowedRole.includes(session.user.role as string)) {
|
||||||
throw redirect({
|
throw redirect({
|
||||||
to: "/",
|
to: "/",
|
||||||
});
|
});
|
||||||
|
|||||||
209
frontend/src/routes/admin/settings.tsx
Normal file
209
frontend/src/routes/admin/settings.tsx
Normal file
@@ -0,0 +1,209 @@
|
|||||||
|
import { useMutation, useSuspenseQuery } from "@tanstack/react-query";
|
||||||
|
import { createFileRoute, redirect } from "@tanstack/react-router";
|
||||||
|
import { createColumnHelper } from "@tanstack/react-table";
|
||||||
|
import axios from "axios";
|
||||||
|
|
||||||
|
import { Suspense, useMemo } from "react";
|
||||||
|
import { toast } from "sonner";
|
||||||
|
|
||||||
|
import {
|
||||||
|
Card,
|
||||||
|
CardContent,
|
||||||
|
CardHeader,
|
||||||
|
CardTitle,
|
||||||
|
} from "../../components/ui/card";
|
||||||
|
|
||||||
|
import {
|
||||||
|
Tabs,
|
||||||
|
TabsContent,
|
||||||
|
TabsList,
|
||||||
|
TabsTrigger,
|
||||||
|
} from "../../components/ui/tabs";
|
||||||
|
import {
|
||||||
|
Tooltip,
|
||||||
|
TooltipContent,
|
||||||
|
TooltipTrigger,
|
||||||
|
} from "../../components/ui/tooltip";
|
||||||
|
import { authClient } from "../../lib/auth-client";
|
||||||
|
import { getSettings } from "../../lib/queries/getSettings";
|
||||||
|
import EditableCellInput from "../../lib/tableStuff/EditableCellInput";
|
||||||
|
import LstTable from "../../lib/tableStuff/LstTable";
|
||||||
|
import SearchableHeader from "../../lib/tableStuff/SearchableHeader";
|
||||||
|
import SkellyTable from "../../lib/tableStuff/SkellyTable";
|
||||||
|
import FeatureSettings from "./-components/FeatureSettings";
|
||||||
|
|
||||||
|
type Settings = {
|
||||||
|
settings_id: string;
|
||||||
|
name: string;
|
||||||
|
active: boolean;
|
||||||
|
value: string;
|
||||||
|
description: string;
|
||||||
|
moduleName: string;
|
||||||
|
roles: string[];
|
||||||
|
};
|
||||||
|
|
||||||
|
const updateSettings = async (
|
||||||
|
id: string,
|
||||||
|
data: Record<string, string | number | boolean | null>,
|
||||||
|
) => {
|
||||||
|
console.log(id, data);
|
||||||
|
try {
|
||||||
|
const res = await axios.patch(`/lst/api/settings/${id}`, data, {
|
||||||
|
withCredentials: true,
|
||||||
|
});
|
||||||
|
toast.success(`Setting just updated`);
|
||||||
|
return res;
|
||||||
|
} catch (err) {
|
||||||
|
toast.error("Error in updating the settings");
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const Route = createFileRoute("/admin/settings")({
|
||||||
|
beforeLoad: async ({ location }) => {
|
||||||
|
const { data: session } = await authClient.getSession();
|
||||||
|
const allowedRole = ["systemAdmin"];
|
||||||
|
|
||||||
|
if (!session?.user) {
|
||||||
|
throw redirect({
|
||||||
|
to: "/",
|
||||||
|
search: {
|
||||||
|
redirect: location.href,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!allowedRole.includes(session.user.role as string)) {
|
||||||
|
throw redirect({
|
||||||
|
to: "/",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return { user: session.user };
|
||||||
|
},
|
||||||
|
component: RouteComponent,
|
||||||
|
});
|
||||||
|
|
||||||
|
function SettingsTableCard() {
|
||||||
|
const { data, refetch } = useSuspenseQuery(getSettings());
|
||||||
|
const columnHelper = createColumnHelper<Settings>();
|
||||||
|
|
||||||
|
const updateSetting = useMutation({
|
||||||
|
mutationFn: ({
|
||||||
|
id,
|
||||||
|
field,
|
||||||
|
value,
|
||||||
|
}: {
|
||||||
|
id: string;
|
||||||
|
field: string;
|
||||||
|
value: string | number | boolean | null;
|
||||||
|
}) => updateSettings(id, { [field]: value }),
|
||||||
|
|
||||||
|
onSuccess: () => {
|
||||||
|
// refetch or update cache
|
||||||
|
refetch();
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const column = [
|
||||||
|
columnHelper.accessor("name", {
|
||||||
|
header: ({ column }) => (
|
||||||
|
<SearchableHeader column={column} title="Name" searchable={true} />
|
||||||
|
),
|
||||||
|
filterFn: "includesString",
|
||||||
|
cell: (i) => i.getValue(),
|
||||||
|
}),
|
||||||
|
columnHelper.accessor("description", {
|
||||||
|
header: ({ column }) => (
|
||||||
|
<SearchableHeader column={column} title="Description" />
|
||||||
|
),
|
||||||
|
cell: (i) => (
|
||||||
|
<Tooltip>
|
||||||
|
<TooltipTrigger>
|
||||||
|
{i.getValue().length > 25 ? (
|
||||||
|
<span>{i.getValue().slice(0, 25)}...</span>
|
||||||
|
) : (
|
||||||
|
<span>{i.getValue()}</span>
|
||||||
|
)}
|
||||||
|
</TooltipTrigger>
|
||||||
|
<TooltipContent>{i.getValue()}</TooltipContent>
|
||||||
|
</Tooltip>
|
||||||
|
),
|
||||||
|
}),
|
||||||
|
columnHelper.accessor("value", {
|
||||||
|
header: ({ column }) => (
|
||||||
|
<SearchableHeader column={column} title="Value" />
|
||||||
|
),
|
||||||
|
|
||||||
|
filterFn: "includesString",
|
||||||
|
cell: ({ row, getValue }) => (
|
||||||
|
<EditableCellInput
|
||||||
|
value={getValue()}
|
||||||
|
id={row.original.name}
|
||||||
|
field="value"
|
||||||
|
onSubmit={({ id, field, value }) => {
|
||||||
|
updateSetting.mutate({ id, field, value });
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
}),
|
||||||
|
];
|
||||||
|
|
||||||
|
const { standardSettings, featureSettings, systemSetting } = useMemo(() => {
|
||||||
|
return {
|
||||||
|
standardSettings: data.filter(
|
||||||
|
(setting: any) => setting.settingType === "standard",
|
||||||
|
),
|
||||||
|
featureSettings: data.filter(
|
||||||
|
(setting: any) => setting.settingType === "feature",
|
||||||
|
),
|
||||||
|
systemSetting: data.filter(
|
||||||
|
(setting: any) => setting.settingType === "system",
|
||||||
|
),
|
||||||
|
};
|
||||||
|
}, [data]);
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<TabsContent value="feature">
|
||||||
|
<FeatureSettings data={featureSettings} />
|
||||||
|
</TabsContent>
|
||||||
|
<TabsContent value="system">
|
||||||
|
<LstTable data={systemSetting} columns={column} />
|
||||||
|
</TabsContent>
|
||||||
|
<TabsContent value="standard">
|
||||||
|
<LstTable data={standardSettings} columns={column} />
|
||||||
|
</TabsContent>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function RouteComponent() {
|
||||||
|
return (
|
||||||
|
<div className="space-y-6">
|
||||||
|
<div className="space-y-2">
|
||||||
|
<h1 className="text-2xl font-semibold">Settings</h1>
|
||||||
|
<p className="text-sm text-muted-foreground">
|
||||||
|
Manage your settings and related data.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Card>
|
||||||
|
<CardHeader>
|
||||||
|
<CardTitle>System Settings</CardTitle>
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent>
|
||||||
|
<Tabs defaultValue="standard" className="w-full">
|
||||||
|
<TabsList>
|
||||||
|
<TabsTrigger value="feature">Features</TabsTrigger>
|
||||||
|
<TabsTrigger value="system">System</TabsTrigger>
|
||||||
|
<TabsTrigger value="standard">Standard</TabsTrigger>
|
||||||
|
</TabsList>
|
||||||
|
<Suspense fallback={<SkellyTable />}>
|
||||||
|
<SettingsTableCard />
|
||||||
|
</Suspense>
|
||||||
|
</Tabs>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -18,10 +18,42 @@ function Index() {
|
|||||||
if (isPending)
|
if (isPending)
|
||||||
return <div className="flex justify-center mt-10">Loading...</div>;
|
return <div className="flex justify-center mt-10">Loading...</div>;
|
||||||
// if (!session) return <button>Sign In</button>
|
// if (!session) return <button>Sign In</button>
|
||||||
|
let url: string;
|
||||||
|
if (window.location.origin.includes("localhost")) {
|
||||||
|
url = `https://www.youtube.com/watch?v=dQw4w9WgXcQ`;
|
||||||
|
} else if (window.location.origin.includes("vms006")) {
|
||||||
|
url = `https://${window.location.hostname.replace("vms006", "prod.alpla.net/")}lst/app/old/ocp`;
|
||||||
|
} else {
|
||||||
|
url = "https://www.youtube.com/watch?v=dQw4w9WgXcQ";
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex justify-center mt-10">
|
<div className="flex justify-center m-10 flex-col">
|
||||||
<h3 className="w-2xl text-3xl">Welcome Home!</h3>
|
<h3 className="w-2xl text-3xl">Welcome Lst - V3</h3>
|
||||||
|
<br></br>
|
||||||
|
<p>
|
||||||
|
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
|
||||||
|
as you were before
|
||||||
|
</p>
|
||||||
|
<br></br>
|
||||||
|
<p>
|
||||||
|
If you dont know why you are here and looking for One Click Print{" "}
|
||||||
|
<a href={`${url}`} target="_blank" rel="noopener">
|
||||||
|
<b>
|
||||||
|
<strong>Click</strong>
|
||||||
|
</b>
|
||||||
|
</a>
|
||||||
|
<a
|
||||||
|
href={`https://www.youtube.com/watch?v=dQw4w9WgXcQ`}
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener"
|
||||||
|
>
|
||||||
|
<b>
|
||||||
|
<strong> Here</strong>
|
||||||
|
</b>
|
||||||
|
</a>
|
||||||
|
</p>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
4
migrations/0019_large_thunderbird.sql
Normal file
4
migrations/0019_large_thunderbird.sql
Normal 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
|
||||||
|
);
|
||||||
1327
migrations/meta/0019_snapshot.json
Normal file
1327
migrations/meta/0019_snapshot.json
Normal file
File diff suppressed because it is too large
Load Diff
@@ -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
271
package-lock.json
generated
@@ -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",
|
||||||
|
|||||||
21
package.json
21
package.json
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "lst_v3",
|
"name": "lst_v3",
|
||||||
"version": "1.0.1",
|
"version": "1.0.2-alpha.0",
|
||||||
"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": {
|
||||||
@@ -13,23 +13,21 @@
|
|||||||
"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",
|
||||||
"start:docker": "node dist/server.js",
|
"start:docker": "node dist/server.js",
|
||||||
"commit": "cz",
|
|
||||||
"changeset": "changeset",
|
|
||||||
"version": "changeset version",
|
"version": "changeset version",
|
||||||
"release": "dotenvx run -f .env -- npm run version && git push --follow-tags && node scripts/create-release.js",
|
"release": "dotenvx run -f .env -- npm run version && git push --follow-tags && node scripts/create-release.js",
|
||||||
"specCheck": "node scripts/check-route-specs.mjs"
|
"specCheck": "node scripts/check-route-specs.mjs",
|
||||||
|
"commit": "cz",
|
||||||
|
"changeset": "changeset",
|
||||||
|
"changeset:add": "changeset",
|
||||||
|
"changeset:version": "changeset version",
|
||||||
|
"changeset:status": "changeset status --verbose"
|
||||||
},
|
},
|
||||||
"workspaces": [
|
|
||||||
"backend",
|
|
||||||
"agent",
|
|
||||||
"shared"
|
|
||||||
],
|
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://git.tuffraid.net/cowch/lst_v3.git"
|
"url": "https://git.tuffraid.net/cowch/lst_v3.git"
|
||||||
@@ -57,6 +55,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",
|
||||||
|
|||||||
@@ -15,7 +15,17 @@ $Servers = @(
|
|||||||
[PSCustomObject]@{
|
[PSCustomObject]@{
|
||||||
server = "uslim1vms006"
|
server = "uslim1vms006"
|
||||||
token = "uslim1"
|
token = "uslim1"
|
||||||
loc = "E$\LST_V3"
|
loc = "D$\LST_V3"
|
||||||
|
},
|
||||||
|
[PSCustomObject]@{
|
||||||
|
server = "ushou1vms006"
|
||||||
|
token = "ushou1"
|
||||||
|
loc = "D$\LST_V3"
|
||||||
|
},
|
||||||
|
[PSCustomObject]@{
|
||||||
|
server = "usday1vms006"
|
||||||
|
token = "usday1"
|
||||||
|
loc = "D$\LST_V3"
|
||||||
},
|
},
|
||||||
[PSCustomObject]@{
|
[PSCustomObject]@{
|
||||||
server = "usmcd1vms036"
|
server = "usmcd1vms036"
|
||||||
@@ -74,8 +84,9 @@ function Show-Menu {
|
|||||||
Write-Host "==============================="
|
Write-Host "==============================="
|
||||||
Write-Host "1. Build app"
|
Write-Host "1. Build app"
|
||||||
Write-Host "2. Deploy New Release"
|
Write-Host "2. Deploy New Release"
|
||||||
Write-Host "3. Restart Service"
|
Write-Host "3. Upgrade Node"
|
||||||
Write-Host "4. Exit"
|
Write-Host "4. Update Postgres"
|
||||||
|
Write-Host "5. Exit"
|
||||||
Write-Host ""
|
Write-Host ""
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -393,9 +404,57 @@ do {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
"3" {
|
"3" {
|
||||||
Write-Host "Restart selected"
|
Write-Host "Choose Server to upgrade node on"
|
||||||
|
$server = Select-Server -List $Servers
|
||||||
|
|
||||||
|
if($server -eq "all") {
|
||||||
|
Write-Host "Updating all servers"
|
||||||
|
for ($i = 0; $i -lt $Servers.Count; $i++) {
|
||||||
|
Write-Host "Updating $($Servers[$i].server)"
|
||||||
|
# Update-Server -Server $Servers[$i].server -Destination $Servers[$i].loc -Token $Servers[$i].token
|
||||||
|
Start-Sleep -Seconds 1
|
||||||
|
}
|
||||||
|
Read-Host -Prompt "Press Enter to continue..."
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($server -ne "all") {
|
||||||
|
Write-Host "You selected $($server.server)"
|
||||||
|
# Update-Server -Server $server.server -Destination $server.loc -Token $server.token
|
||||||
|
# validate we have a node file to install in the folder
|
||||||
|
# stop service
|
||||||
|
# do update script on the server
|
||||||
|
# delete the .exe file
|
||||||
|
|
||||||
|
Read-Host -Prompt "Press Enter to continue..."
|
||||||
|
}
|
||||||
}
|
}
|
||||||
"4" {
|
"4" {
|
||||||
|
Write-Host "Choose Server to upgrade postgres on"
|
||||||
|
$server = Select-Server -List $Servers
|
||||||
|
|
||||||
|
if($server -eq "all") {
|
||||||
|
Write-Host "Updating all servers"
|
||||||
|
for ($i = 0; $i -lt $Servers.Count; $i++) {
|
||||||
|
Write-Host "Updating $($Servers[$i].server)"
|
||||||
|
# Update-Server -Server $Servers[$i].server -Destination $Servers[$i].loc -Token $Servers[$i].token
|
||||||
|
Start-Sleep -Seconds 1
|
||||||
|
}
|
||||||
|
Read-Host -Prompt "Press Enter to continue..."
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($server -ne "all") {
|
||||||
|
Write-Host "You selected $($server.server)"
|
||||||
|
# Update-Server -Server $server.server -Destination $server.loc -Token $server.token
|
||||||
|
# validate we have a postgres file to install in the folder
|
||||||
|
# stop service
|
||||||
|
# do update script on the server
|
||||||
|
# delete the .exe file
|
||||||
|
|
||||||
|
Read-Host -Prompt "Press Enter to continue..."
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
"5" {
|
||||||
Write-Host "Exiting..."
|
Write-Host "Exiting..."
|
||||||
exit
|
exit
|
||||||
}
|
}
|
||||||
|
|||||||
113
scripts/dockerscripts.md
Normal file
113
scripts/dockerscripts.md
Normal file
@@ -0,0 +1,113 @@
|
|||||||
|
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
How to choose the bump
|
||||||
|
|
||||||
|
Use this rule:
|
||||||
|
|
||||||
|
patch = bug fix, small safe improvement
|
||||||
|
minor = new feature, backward compatible
|
||||||
|
major = breaking change
|
||||||
|
|
||||||
|
Changesets uses semver bump ty
|
||||||
|
|
||||||
|
|
||||||
|
### daily process
|
||||||
|
npm commit
|
||||||
|
|
||||||
|
- when closing a issue at the end add
|
||||||
|
Use one of these in the commit body or PR description:
|
||||||
|
|
||||||
|
- - Closes #123
|
||||||
|
- - Fixes #123
|
||||||
|
- - Resolves #123
|
||||||
|
|
||||||
|
Common ones:
|
||||||
|
|
||||||
|
- - Closes #123
|
||||||
|
- - Fixes #123
|
||||||
|
- - Resolves #123
|
||||||
|
Reference an issue without closing it
|
||||||
|
|
||||||
|
Use:
|
||||||
|
|
||||||
|
- - Refs #123
|
||||||
|
- - Related to #123
|
||||||
|
- - See #123
|
||||||
|
|
||||||
|
Good safe one:
|
||||||
|
|
||||||
|
- - Refs #123
|
||||||
|
Good example commit
|
||||||
|
|
||||||
|
Subject:
|
||||||
|
|
||||||
|
- - fix(cors): normalize external url origin
|
||||||
|
|
||||||
|
Body:
|
||||||
|
|
||||||
|
- - Refs #42
|
||||||
|
|
||||||
|
Or if this should close it:
|
||||||
|
|
||||||
|
- - Closes #42
|
||||||
|
|
||||||
|
# Release flow
|
||||||
|
npm run changeset:add
|
||||||
|
|
||||||
|
Pick one:
|
||||||
|
|
||||||
|
- patch = bug fix
|
||||||
|
- minor = new feature, non-breaking
|
||||||
|
- major = breaking change
|
||||||
|
|
||||||
|
Edit the generated .md file in .changeset it will be randomly named and add anything else in here from all the commits that are new to this release
|
||||||
|
|
||||||
|
Recommended release command
|
||||||
|
npm run changeset:version
|
||||||
|
|
||||||
|
stage the change log file
|
||||||
|
|
||||||
|
git commit -m "chore(release): version packages"
|
||||||
|
|
||||||
|
git tag v1.0.1 this will be the new version
|
||||||
|
|
||||||
|
then push it
|
||||||
|
|
||||||
|
git push
|
||||||
|
git push --tags
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
### release type
|
||||||
|
|
||||||
|
when we want to go from alpha to normal well do
|
||||||
|
npx changeset pre enter alpha
|
||||||
|
npx changeset pre enter rc
|
||||||
|
|
||||||
|
go to full production
|
||||||
|
npx changeset pre exit
|
||||||
|
npx changeset version
|
||||||
|
|
||||||
|
### Steps will make it cleaner later
|
||||||
|
Daily work
|
||||||
|
1. Stage files
|
||||||
|
2. npm run commit
|
||||||
|
3. Add issue keyword if needed
|
||||||
|
4. git push when ready
|
||||||
|
|
||||||
|
Release flow
|
||||||
|
1. npx changeset
|
||||||
|
2. pick patch/minor/major
|
||||||
|
3. edit the generated md file with better notes
|
||||||
|
4. npx changeset version
|
||||||
|
5. git add .
|
||||||
|
6. git commit -m "chore(release): version packages"
|
||||||
|
7. git tag vX.X.X
|
||||||
|
8. git push
|
||||||
|
9. git push --tags
|
||||||
@@ -11,7 +11,7 @@ param (
|
|||||||
)
|
)
|
||||||
|
|
||||||
# Example string to run with the parameters in it.
|
# Example string to run with the parameters in it.
|
||||||
# .\scripts\services.ps1 -serviceName "LSTV3_app" -option "install" -appPath "E:\LST_V3" -description "Logistics Support Tool" -command "run start"
|
# .\scripts\services.ps1 -serviceName "LSTV3_app" -option "install" -appPath "D:\LST_V3" -description "Logistics Support Tool" -command "run start"
|
||||||
|
|
||||||
$nssmPath = $AppPath + "\nssm.exe"
|
$nssmPath = $AppPath + "\nssm.exe"
|
||||||
$npmPath = "C:\Program Files\nodejs\npm.cmd" # Path to npm.cmd
|
$npmPath = "C:\Program Files\nodejs\npm.cmd" # Path to npm.cmd
|
||||||
|
|||||||
@@ -18,7 +18,7 @@
|
|||||||
"exactOptionalPropertyTypes": true,
|
"exactOptionalPropertyTypes": true,
|
||||||
"baseUrl": "./backend",
|
"baseUrl": "./backend",
|
||||||
"paths": {
|
"paths": {
|
||||||
"@/*": ["src/*"]
|
"@/*": ["*"]
|
||||||
},
|
},
|
||||||
"esModuleInterop": true,
|
"esModuleInterop": true,
|
||||||
"skipLibCheck": true,
|
"skipLibCheck": true,
|
||||||
@@ -27,8 +27,7 @@
|
|||||||
//"allowImportingTsExtensions": true,
|
//"allowImportingTsExtensions": true,
|
||||||
"noEmit": false
|
"noEmit": false
|
||||||
},
|
},
|
||||||
"include": ["backend/**/*"
|
"include": ["backend/**/*"],
|
||||||
],
|
|
||||||
"exclude": [
|
"exclude": [
|
||||||
"node_modules",
|
"node_modules",
|
||||||
"frontend",
|
"frontend",
|
||||||
|
|||||||
Reference in New Issue
Block a user