Compare commits
10 Commits
v0.0.2-alp
...
f5bae2c0c2
| Author | SHA1 | Date | |
|---|---|---|---|
| f5bae2c0c2 | |||
| 05758791be | |||
| 51026e3e2c | |||
| 9631736e26 | |||
| ce9d8eaaf5 | |||
| 1bbf5c2a49 | |||
| 13718fe702 | |||
| 0de2579942 | |||
| 7c31b43a4a | |||
| 85e96f5ed1 |
1
.gitignore
vendored
1
.gitignore
vendored
@@ -11,6 +11,7 @@ node-v24.14.0-x64.msi
|
|||||||
postgresql-17.9-2-windows-x64.exe
|
postgresql-17.9-2-windows-x64.exe
|
||||||
VSCodeUserSetup-x64-1.112.0.exe
|
VSCodeUserSetup-x64-1.112.0.exe
|
||||||
nssm.exe
|
nssm.exe
|
||||||
|
frontend/.tanstack
|
||||||
|
|
||||||
# Logs
|
# Logs
|
||||||
logs
|
logs
|
||||||
|
|||||||
@@ -1,16 +1,16 @@
|
|||||||
import type { Express } from "express";
|
import type { Express } from "express";
|
||||||
import { requireAuth } from "../middleware/auth.middleware.js";
|
import { requireAuth } from "../middleware/auth.middleware.js";
|
||||||
import { routeHitMiddleware } from "../middleware/routeHit.middleware.js";
|
|
||||||
import build from "./admin.build.js";
|
import build from "./admin.build.js";
|
||||||
import update from "./admin.updateServer.js";
|
import update from "./admin.updateServer.js";
|
||||||
|
|
||||||
export const setupAdminRoutes = (baseUrl: string, app: Express) => {
|
export const setupAdminRoutes = (baseUrl: string, app: Express) => {
|
||||||
//stats will be like this as we dont need to change this
|
//stats will be like this as we dont need to change this
|
||||||
app.use(`${baseUrl}/api/admin/build`, requireAuth, routeHitMiddleware, build);
|
app.use(`${baseUrl}/api/admin/build`, requireAuth, build);
|
||||||
app.use(
|
app.use(
|
||||||
`${baseUrl}/api/admin/build`,
|
`${baseUrl}/api/admin/build`,
|
||||||
requireAuth,
|
requireAuth,
|
||||||
routeHitMiddleware,
|
|
||||||
update,
|
update,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import express from "express";
|
|||||||
import morgan from "morgan";
|
import morgan from "morgan";
|
||||||
import { umamiConfig } from "./configs/umami.config.js";
|
import { umamiConfig } from "./configs/umami.config.js";
|
||||||
import { createLogger } from "./logger/logger.controller.js";
|
import { createLogger } from "./logger/logger.controller.js";
|
||||||
|
import { routeHitMiddleware } from "./middleware/routeHit.middleware.js";
|
||||||
import { setupRoutes } from "./routeHandler.routes.js";
|
import { setupRoutes } from "./routeHandler.routes.js";
|
||||||
import { auth } from "./utils/auth.utils.js";
|
import { auth } from "./utils/auth.utils.js";
|
||||||
import { lstCors } from "./utils/cors.utils.js";
|
import { lstCors } from "./utils/cors.utils.js";
|
||||||
@@ -30,6 +31,7 @@ const createApp = async () => {
|
|||||||
app.use(morgan("dev"));
|
app.use(morgan("dev"));
|
||||||
app.set("trust proxy", true);
|
app.set("trust proxy", true);
|
||||||
app.use(lstCors());
|
app.use(lstCors());
|
||||||
|
app.use(routeHitMiddleware);
|
||||||
app.all(`${baseUrl}/api/auth/*splat`, toNodeHandler(auth));
|
app.all(`${baseUrl}/api/auth/*splat`, toNodeHandler(auth));
|
||||||
app.use(express.json());
|
app.use(express.json());
|
||||||
setupRoutes(baseUrl, app);
|
setupRoutes(baseUrl, app);
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
import type { Express } from "express";
|
import type { Express } from "express";
|
||||||
import { routeHitMiddleware } from "../middleware/routeHit.middleware.js";
|
|
||||||
import login from "./login.route.js";
|
import login from "./login.route.js";
|
||||||
import register from "./register.route.js";
|
import register from "./register.route.js";
|
||||||
|
|
||||||
export const setupAuthRoutes = (baseUrl: string, app: Express) => {
|
export const setupAuthRoutes = (baseUrl: string, app: Express) => {
|
||||||
//setup all the routes
|
//setup all the routes
|
||||||
app.use(routeHitMiddleware);
|
|
||||||
app.use(`${baseUrl}/api/authentication/login`, login);
|
app.use(`${baseUrl}/api/authentication/login`, login);
|
||||||
app.use(`${baseUrl}/api/authentication/register`, register);
|
app.use(`${baseUrl}/api/authentication/register`, register);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import type { Express } from "express";
|
import type { Express } from "express";
|
||||||
import { routeHitMiddleware } from "../middleware/routeHit.middleware.js";
|
|
||||||
import { apiReturn } from "../utils/returnHelper.utils.js";
|
import { apiReturn } from "../utils/returnHelper.utils.js";
|
||||||
import { datamartData } from "./datamartData.utlis.js";
|
import { datamartData } from "./datamartData.utlis.js";
|
||||||
import runQuery from "./getDatamart.route.js";
|
import runQuery from "./getDatamart.route.js";
|
||||||
@@ -30,7 +30,7 @@ export const setupDatamartRoutes = (baseUrl: string, app: Express) => {
|
|||||||
// });
|
// });
|
||||||
|
|
||||||
//setup all the routes
|
//setup all the routes
|
||||||
app.use(routeHitMiddleware);
|
|
||||||
app.use(`${baseUrl}/api/datamart`, runQuery);
|
app.use(`${baseUrl}/api/datamart`, runQuery);
|
||||||
|
|
||||||
// just sending a get on datamart will return all the queries that we can call.
|
// just sending a get on datamart will return all the queries that we can call.
|
||||||
|
|||||||
@@ -4,13 +4,16 @@ import {
|
|||||||
pgTable,
|
pgTable,
|
||||||
text,
|
text,
|
||||||
timestamp,
|
timestamp,
|
||||||
|
unique,
|
||||||
uuid,
|
uuid,
|
||||||
} from "drizzle-orm/pg-core";
|
} from "drizzle-orm/pg-core";
|
||||||
|
|
||||||
export const analyticsDaily = pgTable("analytics_daily", {
|
export const analyticsDaily = pgTable(
|
||||||
|
"analytics_daily",
|
||||||
|
{
|
||||||
id: uuid("id").defaultRandom().primaryKey(),
|
id: uuid("id").defaultRandom().primaryKey(),
|
||||||
|
|
||||||
businessDate: date("business_date").notNull(),
|
businessDate: date("business_date", { mode: "string" }).notNull(),
|
||||||
|
|
||||||
method: text("method").notNull(),
|
method: text("method").notNull(),
|
||||||
routePattern: text("route_pattern").notNull(),
|
routePattern: text("route_pattern").notNull(),
|
||||||
@@ -30,4 +33,13 @@ export const analyticsDaily = pgTable("analytics_daily", {
|
|||||||
|
|
||||||
createdAt: timestamp("created_at").defaultNow().notNull(),
|
createdAt: timestamp("created_at").defaultNow().notNull(),
|
||||||
updatedAt: timestamp("updated_at").defaultNow().notNull(),
|
updatedAt: timestamp("updated_at").defaultNow().notNull(),
|
||||||
});
|
},
|
||||||
|
(table) => [
|
||||||
|
unique("analytics_daily_business_route_unique").on(
|
||||||
|
table.businessDate,
|
||||||
|
table.method,
|
||||||
|
table.routePattern,
|
||||||
|
table.module,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { type Express, Router } from "express";
|
import { type Express, Router } from "express";
|
||||||
import { requireAuth } from "../middleware/auth.middleware.js";
|
import { requireAuth } from "../middleware/auth.middleware.js";
|
||||||
import { routeHitMiddleware } from "../middleware/routeHit.middleware.js";
|
|
||||||
import restart from "./gpSqlRestart.route.js";
|
import restart from "./gpSqlRestart.route.js";
|
||||||
import start from "./gpSqlStart.route.js";
|
import start from "./gpSqlStart.route.js";
|
||||||
import stop from "./gpSqlStop.route.js";
|
import stop from "./gpSqlStop.route.js";
|
||||||
@@ -9,7 +9,6 @@ export const setupGPSqlRoutes = (baseUrl: string, app: Express) => {
|
|||||||
// Apply auth to entire router
|
// Apply auth to entire router
|
||||||
const router = Router();
|
const router = Router();
|
||||||
router.use(requireAuth);
|
router.use(requireAuth);
|
||||||
app.use(routeHitMiddleware);
|
|
||||||
|
|
||||||
router.use(start);
|
router.use(start);
|
||||||
router.use(stop);
|
router.use(stop);
|
||||||
|
|||||||
54
backend/mobile/availableScanIds.route.ts
Normal file
54
backend/mobile/availableScanIds.route.ts
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
import { eq } from "drizzle-orm";
|
||||||
|
import { Router } from "express";
|
||||||
|
import { db } from "../db/db.controller.js";
|
||||||
|
import { scanUser } from "../db/schema/scanUsers.js";
|
||||||
|
import { settings } from "../db/schema/settings.schema.js";
|
||||||
|
import { apiReturn } from "../utils/returnHelper.utils.js";
|
||||||
|
|
||||||
|
const r = Router();
|
||||||
|
|
||||||
|
// scanners that are dedicated to specific users.
|
||||||
|
const SPECIAL_SCANNERS = [69, 98];
|
||||||
|
|
||||||
|
const buildAllowedScannerIds = (scannerCount: number) => {
|
||||||
|
const generatedIds = Array.from({ length: scannerCount }, (_, i) => i + 1);
|
||||||
|
|
||||||
|
return Array.from(new Set([...generatedIds, ...SPECIAL_SCANNERS])).sort(
|
||||||
|
(a, b) => a - b,
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
r.get("/", async (_, res) => {
|
||||||
|
// get the scan users and setting
|
||||||
|
const scanusers = await db.select().from(scanUser);
|
||||||
|
const scannerIdSetting = await db
|
||||||
|
.select()
|
||||||
|
.from(settings)
|
||||||
|
.where(eq(settings.name, "scannerIds"));
|
||||||
|
|
||||||
|
const usedScannerIds = scanusers.map((x) => Number(x.scannerId));
|
||||||
|
const allowedScannerIds = buildAllowedScannerIds(
|
||||||
|
Number(scannerIdSetting[0]?.value ?? 0),
|
||||||
|
);
|
||||||
|
|
||||||
|
const availableScannerIds = allowedScannerIds.filter(
|
||||||
|
(id) => !usedScannerIds.includes(id),
|
||||||
|
);
|
||||||
|
|
||||||
|
const data = availableScannerIds.map((id) => ({
|
||||||
|
label: `${id}`,
|
||||||
|
value: id,
|
||||||
|
}));
|
||||||
|
|
||||||
|
return apiReturn(res, {
|
||||||
|
success: true,
|
||||||
|
level: "info",
|
||||||
|
module: "mobile",
|
||||||
|
subModule: "scanner",
|
||||||
|
message: `There are ${availableScannerIds.length} scanner id's`,
|
||||||
|
data,
|
||||||
|
status: 200,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
export default r;
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
import type { Express } from "express";
|
import type { Express } from "express";
|
||||||
import { routeHitMiddleware } from "../middleware/routeHit.middleware.js";
|
import available from "./availableScanIds.route.js";
|
||||||
import downloads from "./downloadApps.route.js";
|
import downloads from "./downloadApps.route.js";
|
||||||
import lanes from "./laneCheck.js";
|
import lanes from "./laneCheck.js";
|
||||||
import authPin from "./mobileAuth.route.js";
|
import authPin from "./mobileAuth.route.js";
|
||||||
@@ -10,14 +10,13 @@ import version from "./version.route.js";
|
|||||||
export const setupMobileRoutes = (baseUrl: string, app: Express) => {
|
export const setupMobileRoutes = (baseUrl: string, app: Express) => {
|
||||||
//stats will be like this as we dont need to change this
|
//stats will be like this as we dont need to change this
|
||||||
|
|
||||||
app.use(routeHitMiddleware);
|
|
||||||
|
|
||||||
app.use(`${baseUrl}/api/mobile/version`, version);
|
app.use(`${baseUrl}/api/mobile/version`, version);
|
||||||
app.use(`${baseUrl}/api/mobile/apk`, downloads);
|
app.use(`${baseUrl}/api/mobile/apk`, downloads);
|
||||||
app.use(`${baseUrl}/api/mobile/logs`, logs);
|
app.use(`${baseUrl}/api/mobile/logs`, logs);
|
||||||
app.use(`${baseUrl}/api/mobile/auth`, authPin);
|
app.use(`${baseUrl}/api/mobile/auth`, authPin);
|
||||||
app.use(`${baseUrl}/api/mobile/pin`, newPin);
|
app.use(`${baseUrl}/api/mobile/pin`, newPin);
|
||||||
app.use(`${baseUrl}/api/mobile/laneCheck`, lanes);
|
app.use(`${baseUrl}/api/mobile/laneCheck`, lanes);
|
||||||
|
app.use(`${baseUrl}/api/mobile/available`, available);
|
||||||
|
|
||||||
// all other system should be under /api/system/*
|
// all other system should be under /api/system/*
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -12,14 +12,14 @@ router.post("/", async (req, res) => {
|
|||||||
const newLog = await db
|
const newLog = await db
|
||||||
.insert(scanLog)
|
.insert(scanLog)
|
||||||
.values({
|
.values({
|
||||||
scannerId: body.scannerId,
|
scannerId: body.scannerId ?? "",
|
||||||
message: body.message,
|
message: body.message ?? "",
|
||||||
prompt: body.prompt,
|
prompt: body.prompt ?? "",
|
||||||
commandDescription: body.commandDescription,
|
commandDescription: body.commandDescription ?? "",
|
||||||
status: body.status,
|
status: body.status ?? "",
|
||||||
lines: body.lines,
|
lines: body.lines ?? "",
|
||||||
user: body.user,
|
user: body.user ?? "",
|
||||||
runningNumber: body.runningNumber,
|
runningNumber: body.runningNumber ?? "",
|
||||||
})
|
})
|
||||||
.returning();
|
.returning();
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import type { Express } from "express";
|
import type { Express } from "express";
|
||||||
import { requireAuth } from "../middleware/auth.middleware.js";
|
import { requireAuth } from "../middleware/auth.middleware.js";
|
||||||
import { routeHitMiddleware } from "../middleware/routeHit.middleware.js";
|
|
||||||
import manual from "./notification.manualTrigger.js";
|
import manual from "./notification.manualTrigger.js";
|
||||||
import getNotifications from "./notification.route.js";
|
import getNotifications from "./notification.route.js";
|
||||||
import updateNote from "./notification.update.route.js";
|
import updateNote from "./notification.update.route.js";
|
||||||
@@ -14,43 +14,43 @@ export const setupNotificationRoutes = (baseUrl: string, app: Express) => {
|
|||||||
app.use(
|
app.use(
|
||||||
`${baseUrl}/api/notification`,
|
`${baseUrl}/api/notification`,
|
||||||
requireAuth,
|
requireAuth,
|
||||||
routeHitMiddleware,
|
|
||||||
getNotifications,
|
getNotifications,
|
||||||
);
|
);
|
||||||
app.use(
|
app.use(
|
||||||
`${baseUrl}/api/notification`,
|
`${baseUrl}/api/notification`,
|
||||||
requireAuth,
|
requireAuth,
|
||||||
routeHitMiddleware,
|
|
||||||
updateNote,
|
updateNote,
|
||||||
);
|
);
|
||||||
app.use(
|
app.use(
|
||||||
`${baseUrl}/api/notification/manual`,
|
`${baseUrl}/api/notification/manual`,
|
||||||
requireAuth,
|
requireAuth,
|
||||||
routeHitMiddleware,
|
|
||||||
manual,
|
manual,
|
||||||
);
|
);
|
||||||
app.use(
|
app.use(
|
||||||
`${baseUrl}/api/notification/sub`,
|
`${baseUrl}/api/notification/sub`,
|
||||||
requireAuth,
|
requireAuth,
|
||||||
routeHitMiddleware,
|
|
||||||
subs,
|
subs,
|
||||||
);
|
);
|
||||||
app.use(
|
app.use(
|
||||||
`${baseUrl}/api/notification/sub`,
|
`${baseUrl}/api/notification/sub`,
|
||||||
requireAuth,
|
requireAuth,
|
||||||
routeHitMiddleware,
|
|
||||||
newSub,
|
newSub,
|
||||||
);
|
);
|
||||||
app.use(
|
app.use(
|
||||||
`${baseUrl}/api/notification/sub`,
|
`${baseUrl}/api/notification/sub`,
|
||||||
requireAuth,
|
requireAuth,
|
||||||
routeHitMiddleware,
|
|
||||||
updateSub,
|
updateSub,
|
||||||
);
|
);
|
||||||
app.use(
|
app.use(
|
||||||
`${baseUrl}/api/notification/sub`,
|
`${baseUrl}/api/notification/sub`,
|
||||||
requireAuth,
|
requireAuth,
|
||||||
routeHitMiddleware,
|
|
||||||
deleteSub,
|
deleteSub,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { type Express, Router } from "express";
|
import { type Express, Router } from "express";
|
||||||
import { requireAuth } from "../middleware/auth.middleware.js";
|
import { requireAuth } from "../middleware/auth.middleware.js";
|
||||||
import { featureCheck } from "../middleware/featureActive.middleware.js";
|
import { featureCheck } from "../middleware/featureActive.middleware.js";
|
||||||
import { routeHitMiddleware } from "../middleware/routeHit.middleware.js";
|
|
||||||
import listener from "./ocp.printer.listener.js";
|
import listener from "./ocp.printer.listener.js";
|
||||||
import update from "./ocp.printer.update.js";
|
import update from "./ocp.printer.update.js";
|
||||||
|
|
||||||
@@ -18,8 +18,6 @@ export const setupOCPRoutes = (baseUrl: string, app: Express) => {
|
|||||||
// auth routes below here
|
// auth routes below here
|
||||||
router.use(requireAuth);
|
router.use(requireAuth);
|
||||||
|
|
||||||
app.use(routeHitMiddleware);
|
|
||||||
|
|
||||||
router.use(update);
|
router.use(update);
|
||||||
//router.use("");
|
//router.use("");
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { type Express, Router } from "express";
|
import { type Express, Router } from "express";
|
||||||
import { requireAuth } from "../middleware/auth.middleware.js";
|
import { requireAuth } from "../middleware/auth.middleware.js";
|
||||||
import { featureCheck } from "../middleware/featureActive.middleware.js";
|
import { featureCheck } from "../middleware/featureActive.middleware.js";
|
||||||
import { routeHitMiddleware } from "../middleware/routeHit.middleware.js";
|
|
||||||
import getApt from "./opendockGetRelease.route.js";
|
import getApt from "./opendockGetRelease.route.js";
|
||||||
|
|
||||||
export const setupOpendockRoutes = (baseUrl: string, app: Express) => {
|
export const setupOpendockRoutes = (baseUrl: string, app: Express) => {
|
||||||
@@ -14,7 +14,6 @@ export const setupOpendockRoutes = (baseUrl: string, app: Express) => {
|
|||||||
|
|
||||||
// we need to make sure we are authenticated to see the releases
|
// we need to make sure we are authenticated to see the releases
|
||||||
router.use(requireAuth);
|
router.use(requireAuth);
|
||||||
app.use(routeHitMiddleware);
|
|
||||||
|
|
||||||
router.use(getApt);
|
router.use(getApt);
|
||||||
app.use(`${baseUrl}/api/opendock`, router);
|
app.use(`${baseUrl}/api/opendock`, router);
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { type Express, Router } from "express";
|
import { type Express, Router } from "express";
|
||||||
import { requireAuth } from "../middleware/auth.middleware.js";
|
import { requireAuth } from "../middleware/auth.middleware.js";
|
||||||
import { routeHitMiddleware } from "../middleware/routeHit.middleware.js";
|
|
||||||
import restart from "./prodSqlRestart.route.js";
|
import restart from "./prodSqlRestart.route.js";
|
||||||
import start from "./prodSqlStart.route.js";
|
import start from "./prodSqlStart.route.js";
|
||||||
import stop from "./prodSqlStop.route.js";
|
import stop from "./prodSqlStop.route.js";
|
||||||
@@ -9,7 +9,6 @@ export const setupProdSqlRoutes = (baseUrl: string, app: Express) => {
|
|||||||
// Apply auth to entire router
|
// Apply auth to entire router
|
||||||
const router = Router();
|
const router = Router();
|
||||||
router.use(requireAuth);
|
router.use(requireAuth);
|
||||||
app.use(routeHitMiddleware);
|
|
||||||
|
|
||||||
router.use(start);
|
router.use(start);
|
||||||
router.use(stop);
|
router.use(stop);
|
||||||
|
|||||||
@@ -346,6 +346,17 @@ const newSettings: NewSetting[] = [
|
|||||||
roles: ["admin"],
|
roles: ["admin"],
|
||||||
seedVersion: 1,
|
seedVersion: 1,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: "scannerIds",
|
||||||
|
value: "10",
|
||||||
|
active: false,
|
||||||
|
description:
|
||||||
|
"How many scanners ids are setup for this, there should be a lst_scanner instance created.",
|
||||||
|
moduleName: "mobile",
|
||||||
|
settingType: "standard",
|
||||||
|
roles: ["admin"],
|
||||||
|
seedVersion: 1,
|
||||||
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
export const baseSettingValidationCheck = async () => {
|
export const baseSettingValidationCheck = async () => {
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import type { Express } from "express";
|
import type { Express } from "express";
|
||||||
import { requireAuth } from "../middleware/auth.middleware.js";
|
import { requireAuth } from "../middleware/auth.middleware.js";
|
||||||
import { routeHitMiddleware } from "../middleware/routeHit.middleware.js";
|
|
||||||
import getServers from "./serverData.route.js";
|
import getServers from "./serverData.route.js";
|
||||||
import getSettings from "./settings.route.js";
|
import getSettings from "./settings.route.js";
|
||||||
import updSetting from "./settingsUpdate.route.js";
|
import updSetting from "./settingsUpdate.route.js";
|
||||||
@@ -8,7 +8,6 @@ import stats from "./stats.route.js";
|
|||||||
|
|
||||||
export const setupSystemRoutes = (baseUrl: string, app: Express) => {
|
export const setupSystemRoutes = (baseUrl: string, app: Express) => {
|
||||||
//stats will be like this as we dont need to change this
|
//stats will be like this as we dont need to change this
|
||||||
app.use(routeHitMiddleware);
|
|
||||||
app.use(`${baseUrl}/api/stats`, stats);
|
app.use(`${baseUrl}/api/stats`, stats);
|
||||||
app.use(`${baseUrl}/api/settings`, getSettings);
|
app.use(`${baseUrl}/api/settings`, getSettings);
|
||||||
app.use(`${baseUrl}/api/servers`, getServers);
|
app.use(`${baseUrl}/api/servers`, getServers);
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import type { Express } from "express";
|
import type { Express } from "express";
|
||||||
import { requireAuth } from "../middleware/auth.middleware.js";
|
import { requireAuth } from "../middleware/auth.middleware.js";
|
||||||
import { routeHitMiddleware } from "../middleware/routeHit.middleware.js";
|
|
||||||
import restart from "./tcpRestart.route.js";
|
import restart from "./tcpRestart.route.js";
|
||||||
import start from "./tcpStart.route.js";
|
import start from "./tcpStart.route.js";
|
||||||
import stop from "./tcpStop.route.js";
|
import stop from "./tcpStop.route.js";
|
||||||
@@ -8,12 +8,12 @@ import stop from "./tcpStop.route.js";
|
|||||||
export const setupTCPRoutes = (baseUrl: string, app: Express) => {
|
export const setupTCPRoutes = (baseUrl: string, app: Express) => {
|
||||||
//stats will be like this as we dont need to change this
|
//stats will be like this as we dont need to change this
|
||||||
|
|
||||||
app.use(`${baseUrl}/api/tcp/start`, requireAuth, routeHitMiddleware, start);
|
app.use(`${baseUrl}/api/tcp/start`, requireAuth, start);
|
||||||
app.use(`${baseUrl}/api/tcp/stop`, requireAuth, routeHitMiddleware, stop);
|
app.use(`${baseUrl}/api/tcp/stop`, requireAuth, stop);
|
||||||
app.use(
|
app.use(
|
||||||
`${baseUrl}/api/tcp/restart`,
|
`${baseUrl}/api/tcp/restart`,
|
||||||
requireAuth,
|
requireAuth,
|
||||||
routeHitMiddleware,
|
|
||||||
restart,
|
restart,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@@ -61,7 +61,7 @@ export async function aggregateRouteHitsForBusinessDay() {
|
|||||||
|
|
||||||
const rows = await db
|
const rows = await db
|
||||||
.select({
|
.select({
|
||||||
businessDate: sql<string>`${businessDate}`,
|
businessDate: sql<string>`CAST(${businessDate} AS date)`,
|
||||||
method: analytics.method,
|
method: analytics.method,
|
||||||
routePattern: analytics.routePattern,
|
routePattern: analytics.routePattern,
|
||||||
module: sql<string>`COALESCE(${analytics.module}, 'unknown')`,
|
module: sql<string>`COALESCE(${analytics.module}, 'unknown')`,
|
||||||
@@ -105,9 +105,16 @@ export async function aggregateRouteHitsForBusinessDay() {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const values = rows.map((row) => ({
|
||||||
|
...row,
|
||||||
|
businessDate: row.businessDate,
|
||||||
|
firstHitAt: new Date(row.firstHitAt),
|
||||||
|
lastHitAt: new Date(row.lastHitAt),
|
||||||
|
}));
|
||||||
|
|
||||||
await db
|
await db
|
||||||
.insert(analyticsDaily)
|
.insert(analyticsDaily)
|
||||||
.values(rows)
|
.values(values)
|
||||||
.onConflictDoUpdate({
|
.onConflictDoUpdate({
|
||||||
target: [
|
target: [
|
||||||
analyticsDaily.businessDate,
|
analyticsDaily.businessDate,
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ export async function generateUniquePin() {
|
|||||||
const pin = generateSixDigitPin();
|
const pin = generateSixDigitPin();
|
||||||
|
|
||||||
const existing = await db.query.scanUser.findFirst({
|
const existing = await db.query.scanUser.findFirst({
|
||||||
where: (u, { eq }) => eq(u.pinHash, pin), // ⚠️ we'll fix this below
|
where: (u, { eq }) => eq(u.pinHash, pin),
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!existing)
|
if (!existing)
|
||||||
@@ -37,3 +37,13 @@ export async function generateUniquePin() {
|
|||||||
room: "",
|
room: "",
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// export const pinExists = async (pin: string | number) => {
|
||||||
|
// const existing = await db.query.scanUser.findFirst({
|
||||||
|
// where: (u, { eq }) => eq(u.pinHash, pin),
|
||||||
|
// });
|
||||||
|
|
||||||
|
// if (!existing) return true;
|
||||||
|
|
||||||
|
// return false;
|
||||||
|
// };
|
||||||
|
|||||||
@@ -1,9 +1,8 @@
|
|||||||
import type { Express } from "express";
|
import type { Express } from "express";
|
||||||
import { routeHitMiddleware } from "../middleware/routeHit.middleware.js";
|
|
||||||
import getActiveJobs from "./cronerActiveJobs.route.js";
|
import getActiveJobs from "./cronerActiveJobs.route.js";
|
||||||
import jobStatusChange from "./cronerStatusChange.route.js";
|
import jobStatusChange from "./cronerStatusChange.route.js";
|
||||||
export const setupUtilsRoutes = (baseUrl: string, app: Express) => {
|
export const setupUtilsRoutes = (baseUrl: string, app: Express) => {
|
||||||
app.use(routeHitMiddleware);
|
|
||||||
app.use(`${baseUrl}/api/utils/croner`, getActiveJobs);
|
app.use(`${baseUrl}/api/utils/croner`, getActiveJobs);
|
||||||
app.use(`${baseUrl}/api/utils/croner`, jobStatusChange);
|
app.use(`${baseUrl}/api/utils/croner`, jobStatusChange);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -68,7 +68,7 @@ export default function AdminSidebar({ session }: any) {
|
|||||||
title: "Scan users",
|
title: "Scan users",
|
||||||
url: "/admin/scanUsers",
|
url: "/admin/scanUsers",
|
||||||
icon: UsersRound,
|
icon: UsersRound,
|
||||||
role: ["systemAdmin", "admin"],
|
role: ["systemAdmin", "admin", "manager"],
|
||||||
module: "admin",
|
module: "admin",
|
||||||
active: true,
|
active: true,
|
||||||
},
|
},
|
||||||
@@ -79,9 +79,9 @@ export default function AdminSidebar({ session }: any) {
|
|||||||
<SidebarGroupContent>
|
<SidebarGroupContent>
|
||||||
<SidebarMenu>
|
<SidebarMenu>
|
||||||
{items.map((item) => (
|
{items.map((item) => (
|
||||||
<>
|
<div key={item.title}>
|
||||||
{item.role.includes(session.user.role) && (
|
{item.role.includes(session.user.role) && (
|
||||||
<SidebarMenuItem key={item.title}>
|
<SidebarMenuItem>
|
||||||
<SidebarMenuButton asChild>
|
<SidebarMenuButton asChild>
|
||||||
<Link to={item.url} onClick={() => setOpen(false)}>
|
<Link to={item.url} onClick={() => setOpen(false)}>
|
||||||
<item.icon />
|
<item.icon />
|
||||||
@@ -90,7 +90,7 @@ export default function AdminSidebar({ session }: any) {
|
|||||||
</SidebarMenuButton>
|
</SidebarMenuButton>
|
||||||
</SidebarMenuItem>
|
</SidebarMenuItem>
|
||||||
)}
|
)}
|
||||||
</>
|
</div>
|
||||||
))}
|
))}
|
||||||
</SidebarMenu>
|
</SidebarMenu>
|
||||||
</SidebarGroupContent>
|
</SidebarGroupContent>
|
||||||
|
|||||||
@@ -25,8 +25,6 @@ export default function MobileBar({ session }: any) {
|
|||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
console.log(session);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<SidebarGroup>
|
<SidebarGroup>
|
||||||
<SidebarGroupLabel>Mobile</SidebarGroupLabel>
|
<SidebarGroupLabel>Mobile</SidebarGroupLabel>
|
||||||
|
|||||||
@@ -27,7 +27,8 @@ export function AppSidebar() {
|
|||||||
<MobileBar session={session} />
|
<MobileBar session={session} />
|
||||||
{session &&
|
{session &&
|
||||||
(session.user.role === "admin" ||
|
(session.user.role === "admin" ||
|
||||||
session.user.role === "systemAdmin") && (
|
session.user.role === "systemAdmin" ||
|
||||||
|
session.user.role === "manager") && (
|
||||||
<AdminSidebar session={session} />
|
<AdminSidebar session={session} />
|
||||||
)}
|
)}
|
||||||
</SidebarContent>
|
</SidebarContent>
|
||||||
|
|||||||
@@ -1,42 +1,45 @@
|
|||||||
import { cva, type VariantProps } from "class-variance-authority";
|
import * as React from "react"
|
||||||
import { Slot } from "radix-ui";
|
import { cva, type VariantProps } from "class-variance-authority"
|
||||||
import type * as React from "react";
|
import { Slot } from "radix-ui"
|
||||||
|
|
||||||
import { cn } from "@/lib/utils";
|
import { cn } from "@/lib/utils"
|
||||||
|
|
||||||
const buttonVariants = cva(
|
const buttonVariants = cva(
|
||||||
"inline-flex shrink-0 items-center justify-center gap-2 rounded-md text-sm font-medium whitespace-nowrap transition-all outline-none 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 dark:aria-invalid:ring-destructive/40 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
|
"group/button inline-flex shrink-0 items-center justify-center rounded-lg border border-transparent bg-clip-padding text-sm font-medium whitespace-nowrap transition-all outline-none select-none focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 active:not-aria-[haspopup]:translate-y-px disabled:pointer-events-none disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-3 aria-invalid:ring-destructive/20 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",
|
||||||
{
|
{
|
||||||
variants: {
|
variants: {
|
||||||
variant: {
|
variant: {
|
||||||
default: "bg-primary text-primary-foreground hover:bg-primary/90",
|
default: "bg-primary text-primary-foreground [a]:hover:bg-primary/80",
|
||||||
destructive:
|
|
||||||
"bg-destructive text-white hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:bg-destructive/60 dark:focus-visible:ring-destructive/40",
|
|
||||||
outline:
|
outline:
|
||||||
"border bg-background shadow-xs hover:bg-accent hover:text-accent-foreground dark:border-input dark:bg-input/30 dark:hover:bg-input/50",
|
"border-border bg-background hover:bg-muted hover:text-foreground aria-expanded:bg-muted aria-expanded:text-foreground dark:border-input dark:bg-input/30 dark:hover:bg-input/50",
|
||||||
secondary:
|
secondary:
|
||||||
"bg-secondary text-secondary-foreground hover:bg-secondary/80",
|
"bg-secondary text-secondary-foreground hover:bg-secondary/80 aria-expanded:bg-secondary aria-expanded:text-secondary-foreground",
|
||||||
ghost:
|
ghost:
|
||||||
"hover:bg-accent hover:text-accent-foreground dark:hover:bg-accent/50",
|
"hover:bg-muted hover:text-foreground aria-expanded:bg-muted aria-expanded:text-foreground dark:hover:bg-muted/50",
|
||||||
|
destructive:
|
||||||
|
"bg-destructive/10 text-destructive hover:bg-destructive/20 focus-visible:border-destructive/40 focus-visible:ring-destructive/20 dark:bg-destructive/20 dark:hover:bg-destructive/30 dark:focus-visible:ring-destructive/40",
|
||||||
link: "text-primary underline-offset-4 hover:underline",
|
link: "text-primary underline-offset-4 hover:underline",
|
||||||
},
|
},
|
||||||
size: {
|
size: {
|
||||||
default: "h-9 px-4 py-2 has-[>svg]:px-3",
|
default:
|
||||||
xs: "h-6 gap-1 rounded-md px-2 text-xs has-[>svg]:px-1.5 [&_svg:not([class*='size-'])]:size-3",
|
"h-8 gap-1.5 px-2.5 has-data-[icon=inline-end]:pr-2 has-data-[icon=inline-start]:pl-2",
|
||||||
sm: "h-8 gap-1.5 rounded-md px-3 has-[>svg]:px-2.5",
|
xs: "h-6 gap-1 rounded-[min(var(--radius-md),10px)] px-2 text-xs in-data-[slot=button-group]:rounded-lg has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 [&_svg:not([class*='size-'])]:size-3",
|
||||||
lg: "h-10 rounded-md px-6 has-[>svg]:px-4",
|
sm: "h-7 gap-1 rounded-[min(var(--radius-md),12px)] px-2.5 text-[0.8rem] in-data-[slot=button-group]:rounded-lg has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 [&_svg:not([class*='size-'])]:size-3.5",
|
||||||
icon: "size-9",
|
lg: "h-9 gap-1.5 px-2.5 has-data-[icon=inline-end]:pr-2 has-data-[icon=inline-start]:pl-2",
|
||||||
"icon-xs": "size-6 rounded-md [&_svg:not([class*='size-'])]:size-3",
|
icon: "size-8",
|
||||||
"icon-sm": "size-8",
|
"icon-xs":
|
||||||
"icon-lg": "size-10",
|
"size-6 rounded-[min(var(--radius-md),10px)] in-data-[slot=button-group]:rounded-lg [&_svg:not([class*='size-'])]:size-3",
|
||||||
|
"icon-sm":
|
||||||
|
"size-7 rounded-[min(var(--radius-md),12px)] in-data-[slot=button-group]:rounded-lg",
|
||||||
|
"icon-lg": "size-9",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
defaultVariants: {
|
defaultVariants: {
|
||||||
variant: "default",
|
variant: "default",
|
||||||
size: "default",
|
size: "default",
|
||||||
},
|
},
|
||||||
},
|
}
|
||||||
);
|
)
|
||||||
|
|
||||||
function Button({
|
function Button({
|
||||||
className,
|
className,
|
||||||
@@ -46,9 +49,9 @@ function Button({
|
|||||||
...props
|
...props
|
||||||
}: React.ComponentProps<"button"> &
|
}: React.ComponentProps<"button"> &
|
||||||
VariantProps<typeof buttonVariants> & {
|
VariantProps<typeof buttonVariants> & {
|
||||||
asChild?: boolean;
|
asChild?: boolean
|
||||||
}) {
|
}) {
|
||||||
const Comp = asChild ? Slot.Root : "button";
|
const Comp = asChild ? Slot.Root : "button"
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Comp
|
<Comp
|
||||||
@@ -58,7 +61,7 @@ function Button({
|
|||||||
className={cn(buttonVariants({ variant, size, className }))}
|
className={cn(buttonVariants({ variant, size, className }))}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
);
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export { Button, buttonVariants };
|
export { Button, buttonVariants }
|
||||||
|
|||||||
166
frontend/src/components/ui/dialog.tsx
Normal file
166
frontend/src/components/ui/dialog.tsx
Normal file
@@ -0,0 +1,166 @@
|
|||||||
|
import * as React from "react"
|
||||||
|
import { Dialog as DialogPrimitive } from "radix-ui"
|
||||||
|
|
||||||
|
import { cn } from "@/lib/utils"
|
||||||
|
import { Button } from "@/components/ui/button"
|
||||||
|
import { XIcon } from "lucide-react"
|
||||||
|
|
||||||
|
function Dialog({
|
||||||
|
...props
|
||||||
|
}: React.ComponentProps<typeof DialogPrimitive.Root>) {
|
||||||
|
return <DialogPrimitive.Root data-slot="dialog" {...props} />
|
||||||
|
}
|
||||||
|
|
||||||
|
function DialogTrigger({
|
||||||
|
...props
|
||||||
|
}: React.ComponentProps<typeof DialogPrimitive.Trigger>) {
|
||||||
|
return <DialogPrimitive.Trigger data-slot="dialog-trigger" {...props} />
|
||||||
|
}
|
||||||
|
|
||||||
|
function DialogPortal({
|
||||||
|
...props
|
||||||
|
}: React.ComponentProps<typeof DialogPrimitive.Portal>) {
|
||||||
|
return <DialogPrimitive.Portal data-slot="dialog-portal" {...props} />
|
||||||
|
}
|
||||||
|
|
||||||
|
function DialogClose({
|
||||||
|
...props
|
||||||
|
}: React.ComponentProps<typeof DialogPrimitive.Close>) {
|
||||||
|
return <DialogPrimitive.Close data-slot="dialog-close" {...props} />
|
||||||
|
}
|
||||||
|
|
||||||
|
function DialogOverlay({
|
||||||
|
className,
|
||||||
|
...props
|
||||||
|
}: React.ComponentProps<typeof DialogPrimitive.Overlay>) {
|
||||||
|
return (
|
||||||
|
<DialogPrimitive.Overlay
|
||||||
|
data-slot="dialog-overlay"
|
||||||
|
className={cn(
|
||||||
|
"fixed inset-0 isolate z-50 bg-black/10 duration-100 supports-backdrop-filter:backdrop-blur-xs data-open:animate-in data-open:fade-in-0 data-closed:animate-out data-closed:fade-out-0",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function DialogContent({
|
||||||
|
className,
|
||||||
|
children,
|
||||||
|
showCloseButton = true,
|
||||||
|
...props
|
||||||
|
}: React.ComponentProps<typeof DialogPrimitive.Content> & {
|
||||||
|
showCloseButton?: boolean
|
||||||
|
}) {
|
||||||
|
return (
|
||||||
|
<DialogPortal>
|
||||||
|
<DialogOverlay />
|
||||||
|
<DialogPrimitive.Content
|
||||||
|
data-slot="dialog-content"
|
||||||
|
className={cn(
|
||||||
|
"fixed top-1/2 left-1/2 z-50 grid w-full max-w-[calc(100%-2rem)] -translate-x-1/2 -translate-y-1/2 gap-4 rounded-xl bg-popover p-4 text-sm text-popover-foreground ring-1 ring-foreground/10 duration-100 outline-none sm:max-w-sm data-open:animate-in data-open:fade-in-0 data-open:zoom-in-95 data-closed:animate-out data-closed:fade-out-0 data-closed:zoom-out-95",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
{showCloseButton && (
|
||||||
|
<DialogPrimitive.Close data-slot="dialog-close" asChild>
|
||||||
|
<Button
|
||||||
|
variant="ghost"
|
||||||
|
className="absolute top-2 right-2"
|
||||||
|
size="icon-sm"
|
||||||
|
>
|
||||||
|
<XIcon
|
||||||
|
/>
|
||||||
|
<span className="sr-only">Close</span>
|
||||||
|
</Button>
|
||||||
|
</DialogPrimitive.Close>
|
||||||
|
)}
|
||||||
|
</DialogPrimitive.Content>
|
||||||
|
</DialogPortal>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function DialogHeader({ className, ...props }: React.ComponentProps<"div">) {
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
data-slot="dialog-header"
|
||||||
|
className={cn("flex flex-col gap-2", className)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function DialogFooter({
|
||||||
|
className,
|
||||||
|
showCloseButton = false,
|
||||||
|
children,
|
||||||
|
...props
|
||||||
|
}: React.ComponentProps<"div"> & {
|
||||||
|
showCloseButton?: boolean
|
||||||
|
}) {
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
data-slot="dialog-footer"
|
||||||
|
className={cn(
|
||||||
|
"-mx-4 -mb-4 flex flex-col-reverse gap-2 rounded-b-xl border-t bg-muted/50 p-4 sm:flex-row sm:justify-end",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
{showCloseButton && (
|
||||||
|
<DialogPrimitive.Close asChild>
|
||||||
|
<Button variant="outline">Close</Button>
|
||||||
|
</DialogPrimitive.Close>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function DialogTitle({
|
||||||
|
className,
|
||||||
|
...props
|
||||||
|
}: React.ComponentProps<typeof DialogPrimitive.Title>) {
|
||||||
|
return (
|
||||||
|
<DialogPrimitive.Title
|
||||||
|
data-slot="dialog-title"
|
||||||
|
className={cn(
|
||||||
|
"text-base leading-none font-medium",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function DialogDescription({
|
||||||
|
className,
|
||||||
|
...props
|
||||||
|
}: React.ComponentProps<typeof DialogPrimitive.Description>) {
|
||||||
|
return (
|
||||||
|
<DialogPrimitive.Description
|
||||||
|
data-slot="dialog-description"
|
||||||
|
className={cn(
|
||||||
|
"text-sm text-muted-foreground *:[a]:underline *:[a]:underline-offset-3 *:[a]:hover:text-foreground",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export {
|
||||||
|
Dialog,
|
||||||
|
DialogClose,
|
||||||
|
DialogContent,
|
||||||
|
DialogDescription,
|
||||||
|
DialogFooter,
|
||||||
|
DialogHeader,
|
||||||
|
DialogOverlay,
|
||||||
|
DialogPortal,
|
||||||
|
DialogTitle,
|
||||||
|
DialogTrigger,
|
||||||
|
}
|
||||||
25
frontend/src/lib/queries/getScannerIds.ts
Normal file
25
frontend/src/lib/queries/getScannerIds.ts
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
import { keepPreviousData, queryOptions } from "@tanstack/react-query";
|
||||||
|
import axios from "axios";
|
||||||
|
|
||||||
|
export function getScannerIds() {
|
||||||
|
return queryOptions({
|
||||||
|
queryKey: ["getScannerIds"],
|
||||||
|
queryFn: () => fetch(),
|
||||||
|
staleTime: 5000,
|
||||||
|
refetchOnWindowFocus: true,
|
||||||
|
placeholderData: keepPreviousData,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const fetch = async () => {
|
||||||
|
if (window.location.hostname === "localhost") {
|
||||||
|
await new Promise((res) => setTimeout(res, 1500));
|
||||||
|
}
|
||||||
|
|
||||||
|
const { data } = await axios.get("/lst/api/mobile/available", {
|
||||||
|
withCredentials: true,
|
||||||
|
timeout: 5000,
|
||||||
|
});
|
||||||
|
|
||||||
|
return data.data;
|
||||||
|
};
|
||||||
@@ -17,11 +17,13 @@ export default function SkellyTable({ rows = 5, columns = 4 }: TableSkelly) {
|
|||||||
<div className="rounded-md border">
|
<div className="rounded-md border">
|
||||||
<Table>
|
<Table>
|
||||||
<TableHeader>
|
<TableHeader>
|
||||||
|
<TableRow>
|
||||||
{Array.from({ length: columns }).map((_, i) => (
|
{Array.from({ length: columns }).map((_, i) => (
|
||||||
<TableHead key={i}>
|
<TableHead key={i}>
|
||||||
<Skeleton className="h-4 w-[80px]" />
|
<Skeleton className="h-4 w-[80px]" />
|
||||||
</TableHead>
|
</TableHead>
|
||||||
))}
|
))}
|
||||||
|
</TableRow>
|
||||||
</TableHeader>
|
</TableHeader>
|
||||||
<TableBody>
|
<TableBody>
|
||||||
{Array.from({ length: rows }).map((_, r) => (
|
{Array.from({ length: rows }).map((_, r) => (
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ import { Route as AboutRouteImport } from './routes/about'
|
|||||||
import { Route as IndexRouteImport } from './routes/index'
|
import { Route as IndexRouteImport } from './routes/index'
|
||||||
import { Route as DocsIndexRouteImport } from './routes/docs/index'
|
import { Route as DocsIndexRouteImport } from './routes/docs/index'
|
||||||
import { Route as DocsSplatRouteImport } from './routes/docs/$'
|
import { Route as DocsSplatRouteImport } from './routes/docs/$'
|
||||||
|
import { Route as AdminUsersRouteImport } from './routes/admin/users'
|
||||||
import { Route as AdminSettingsRouteImport } from './routes/admin/settings'
|
import { Route as AdminSettingsRouteImport } from './routes/admin/settings'
|
||||||
import { Route as AdminServersRouteImport } from './routes/admin/servers'
|
import { Route as AdminServersRouteImport } from './routes/admin/servers'
|
||||||
import { Route as AdminScanUsersRouteImport } from './routes/admin/scanUsers'
|
import { Route as AdminScanUsersRouteImport } from './routes/admin/scanUsers'
|
||||||
@@ -43,6 +44,11 @@ const DocsSplatRoute = DocsSplatRouteImport.update({
|
|||||||
path: '/docs/$',
|
path: '/docs/$',
|
||||||
getParentRoute: () => rootRouteImport,
|
getParentRoute: () => rootRouteImport,
|
||||||
} as any)
|
} as any)
|
||||||
|
const AdminUsersRoute = AdminUsersRouteImport.update({
|
||||||
|
id: '/admin/users',
|
||||||
|
path: '/admin/users',
|
||||||
|
getParentRoute: () => rootRouteImport,
|
||||||
|
} as any)
|
||||||
const AdminSettingsRoute = AdminSettingsRouteImport.update({
|
const AdminSettingsRoute = AdminSettingsRouteImport.update({
|
||||||
id: '/admin/settings',
|
id: '/admin/settings',
|
||||||
path: '/admin/settings',
|
path: '/admin/settings',
|
||||||
@@ -98,6 +104,7 @@ export interface FileRoutesByFullPath {
|
|||||||
'/admin/scanUsers': typeof AdminScanUsersRoute
|
'/admin/scanUsers': typeof AdminScanUsersRoute
|
||||||
'/admin/servers': typeof AdminServersRoute
|
'/admin/servers': typeof AdminServersRoute
|
||||||
'/admin/settings': typeof AdminSettingsRoute
|
'/admin/settings': typeof AdminSettingsRoute
|
||||||
|
'/admin/users': typeof AdminUsersRoute
|
||||||
'/docs/$': typeof DocsSplatRoute
|
'/docs/$': typeof DocsSplatRoute
|
||||||
'/docs/': typeof DocsIndexRoute
|
'/docs/': typeof DocsIndexRoute
|
||||||
'/user/profile': typeof authUserProfileRoute
|
'/user/profile': typeof authUserProfileRoute
|
||||||
@@ -113,6 +120,7 @@ export interface FileRoutesByTo {
|
|||||||
'/admin/scanUsers': typeof AdminScanUsersRoute
|
'/admin/scanUsers': typeof AdminScanUsersRoute
|
||||||
'/admin/servers': typeof AdminServersRoute
|
'/admin/servers': typeof AdminServersRoute
|
||||||
'/admin/settings': typeof AdminSettingsRoute
|
'/admin/settings': typeof AdminSettingsRoute
|
||||||
|
'/admin/users': typeof AdminUsersRoute
|
||||||
'/docs/$': typeof DocsSplatRoute
|
'/docs/$': typeof DocsSplatRoute
|
||||||
'/docs': typeof DocsIndexRoute
|
'/docs': typeof DocsIndexRoute
|
||||||
'/user/profile': typeof authUserProfileRoute
|
'/user/profile': typeof authUserProfileRoute
|
||||||
@@ -129,6 +137,7 @@ export interface FileRoutesById {
|
|||||||
'/admin/scanUsers': typeof AdminScanUsersRoute
|
'/admin/scanUsers': typeof AdminScanUsersRoute
|
||||||
'/admin/servers': typeof AdminServersRoute
|
'/admin/servers': typeof AdminServersRoute
|
||||||
'/admin/settings': typeof AdminSettingsRoute
|
'/admin/settings': typeof AdminSettingsRoute
|
||||||
|
'/admin/users': typeof AdminUsersRoute
|
||||||
'/docs/$': typeof DocsSplatRoute
|
'/docs/$': typeof DocsSplatRoute
|
||||||
'/docs/': typeof DocsIndexRoute
|
'/docs/': typeof DocsIndexRoute
|
||||||
'/(auth)/user/profile': typeof authUserProfileRoute
|
'/(auth)/user/profile': typeof authUserProfileRoute
|
||||||
@@ -146,6 +155,7 @@ export interface FileRouteTypes {
|
|||||||
| '/admin/scanUsers'
|
| '/admin/scanUsers'
|
||||||
| '/admin/servers'
|
| '/admin/servers'
|
||||||
| '/admin/settings'
|
| '/admin/settings'
|
||||||
|
| '/admin/users'
|
||||||
| '/docs/$'
|
| '/docs/$'
|
||||||
| '/docs/'
|
| '/docs/'
|
||||||
| '/user/profile'
|
| '/user/profile'
|
||||||
@@ -161,6 +171,7 @@ export interface FileRouteTypes {
|
|||||||
| '/admin/scanUsers'
|
| '/admin/scanUsers'
|
||||||
| '/admin/servers'
|
| '/admin/servers'
|
||||||
| '/admin/settings'
|
| '/admin/settings'
|
||||||
|
| '/admin/users'
|
||||||
| '/docs/$'
|
| '/docs/$'
|
||||||
| '/docs'
|
| '/docs'
|
||||||
| '/user/profile'
|
| '/user/profile'
|
||||||
@@ -176,6 +187,7 @@ export interface FileRouteTypes {
|
|||||||
| '/admin/scanUsers'
|
| '/admin/scanUsers'
|
||||||
| '/admin/servers'
|
| '/admin/servers'
|
||||||
| '/admin/settings'
|
| '/admin/settings'
|
||||||
|
| '/admin/users'
|
||||||
| '/docs/$'
|
| '/docs/$'
|
||||||
| '/docs/'
|
| '/docs/'
|
||||||
| '/(auth)/user/profile'
|
| '/(auth)/user/profile'
|
||||||
@@ -192,6 +204,7 @@ export interface RootRouteChildren {
|
|||||||
AdminScanUsersRoute: typeof AdminScanUsersRoute
|
AdminScanUsersRoute: typeof AdminScanUsersRoute
|
||||||
AdminServersRoute: typeof AdminServersRoute
|
AdminServersRoute: typeof AdminServersRoute
|
||||||
AdminSettingsRoute: typeof AdminSettingsRoute
|
AdminSettingsRoute: typeof AdminSettingsRoute
|
||||||
|
AdminUsersRoute: typeof AdminUsersRoute
|
||||||
DocsSplatRoute: typeof DocsSplatRoute
|
DocsSplatRoute: typeof DocsSplatRoute
|
||||||
DocsIndexRoute: typeof DocsIndexRoute
|
DocsIndexRoute: typeof DocsIndexRoute
|
||||||
authUserProfileRoute: typeof authUserProfileRoute
|
authUserProfileRoute: typeof authUserProfileRoute
|
||||||
@@ -229,6 +242,13 @@ declare module '@tanstack/react-router' {
|
|||||||
preLoaderRoute: typeof DocsSplatRouteImport
|
preLoaderRoute: typeof DocsSplatRouteImport
|
||||||
parentRoute: typeof rootRouteImport
|
parentRoute: typeof rootRouteImport
|
||||||
}
|
}
|
||||||
|
'/admin/users': {
|
||||||
|
id: '/admin/users'
|
||||||
|
path: '/admin/users'
|
||||||
|
fullPath: '/admin/users'
|
||||||
|
preLoaderRoute: typeof AdminUsersRouteImport
|
||||||
|
parentRoute: typeof rootRouteImport
|
||||||
|
}
|
||||||
'/admin/settings': {
|
'/admin/settings': {
|
||||||
id: '/admin/settings'
|
id: '/admin/settings'
|
||||||
path: '/admin/settings'
|
path: '/admin/settings'
|
||||||
@@ -304,6 +324,7 @@ const rootRouteChildren: RootRouteChildren = {
|
|||||||
AdminScanUsersRoute: AdminScanUsersRoute,
|
AdminScanUsersRoute: AdminScanUsersRoute,
|
||||||
AdminServersRoute: AdminServersRoute,
|
AdminServersRoute: AdminServersRoute,
|
||||||
AdminSettingsRoute: AdminSettingsRoute,
|
AdminSettingsRoute: AdminSettingsRoute,
|
||||||
|
AdminUsersRoute: AdminUsersRoute,
|
||||||
DocsSplatRoute: DocsSplatRoute,
|
DocsSplatRoute: DocsSplatRoute,
|
||||||
DocsIndexRoute: DocsIndexRoute,
|
DocsIndexRoute: DocsIndexRoute,
|
||||||
authUserProfileRoute: authUserProfileRoute,
|
authUserProfileRoute: authUserProfileRoute,
|
||||||
|
|||||||
@@ -51,6 +51,8 @@ export default function NotificationsSubCard({ user }: any) {
|
|||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
console.log(n);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<Card className="p-3 w-lg">
|
<Card className="p-3 w-lg">
|
||||||
|
|||||||
161
frontend/src/routes/admin/-components/NewScanUser.tsx
Normal file
161
frontend/src/routes/admin/-components/NewScanUser.tsx
Normal file
@@ -0,0 +1,161 @@
|
|||||||
|
import { useSuspenseQuery } from "@tanstack/react-query";
|
||||||
|
import axios from "axios";
|
||||||
|
import { useState } from "react";
|
||||||
|
import { toast } from "sonner";
|
||||||
|
import { Button } from "../../../components/ui/button";
|
||||||
|
import {
|
||||||
|
Dialog,
|
||||||
|
DialogContent,
|
||||||
|
DialogDescription,
|
||||||
|
DialogHeader,
|
||||||
|
DialogTitle,
|
||||||
|
} from "../../../components/ui/dialog";
|
||||||
|
import { useAppForm } from "../../../lib/formSutff";
|
||||||
|
import { getScannerIds } from "../../../lib/queries/getScannerIds";
|
||||||
|
|
||||||
|
export default function NewScanUser({ refetch }: { refetch: any }) {
|
||||||
|
const [open, setOpen] = useState(false);
|
||||||
|
const { data, refetch: scannerFetch } = useSuspenseQuery(getScannerIds());
|
||||||
|
const form = useAppForm({
|
||||||
|
defaultValues: {
|
||||||
|
name: "",
|
||||||
|
scannerId: "",
|
||||||
|
pinNumber: "",
|
||||||
|
},
|
||||||
|
onSubmit: async ({ value }) => {
|
||||||
|
if (value.scannerId === "") {
|
||||||
|
toast.error(
|
||||||
|
"Scanner id is required please select a scanner id before submitting ",
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const { data } = await axios.post(
|
||||||
|
"/lst/api/mobile/auth/user",
|
||||||
|
{
|
||||||
|
name: value.name,
|
||||||
|
pinNumber: value.pinNumber,
|
||||||
|
scannerId: value.scannerId,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
withCredentials: true,
|
||||||
|
timeout: 15000,
|
||||||
|
validateStatus: () => true,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
if (data.success) {
|
||||||
|
toast.success(
|
||||||
|
`${value.name}, was just created and can now log into the scanner with PIN: ${value.pinNumber}`,
|
||||||
|
);
|
||||||
|
form.reset();
|
||||||
|
setOpen(false);
|
||||||
|
refetch();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!data.success) {
|
||||||
|
toast.error(data.message);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const closeModel = (e: boolean) => {
|
||||||
|
setOpen(e);
|
||||||
|
|
||||||
|
if (!e) {
|
||||||
|
form.reset();
|
||||||
|
scannerFetch();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const openForm = () => {
|
||||||
|
setOpen(true);
|
||||||
|
scannerFetch();
|
||||||
|
};
|
||||||
|
|
||||||
|
let n: any = [];
|
||||||
|
if (data) {
|
||||||
|
n = data.map((i: any) => ({
|
||||||
|
label: i.label,
|
||||||
|
value: i.value.toString(),
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Dialog onOpenChange={(e) => closeModel(e)} open={open}>
|
||||||
|
<Button onClick={openForm}>Create new user</Button>
|
||||||
|
|
||||||
|
<DialogContent showCloseButton={false}>
|
||||||
|
<DialogHeader>
|
||||||
|
<DialogTitle>Create New Scan user.</DialogTitle>
|
||||||
|
<DialogDescription></DialogDescription>
|
||||||
|
</DialogHeader>
|
||||||
|
<form
|
||||||
|
onSubmit={(e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
form.handleSubmit();
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div className="mb-2">
|
||||||
|
<form.AppField name="name">
|
||||||
|
{(field) => (
|
||||||
|
<field.InputField
|
||||||
|
label="Name"
|
||||||
|
inputType="text"
|
||||||
|
required={true}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</form.AppField>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="w-32">
|
||||||
|
<form.AppField name="scannerId">
|
||||||
|
{(field) => (
|
||||||
|
<field.SelectField
|
||||||
|
label="Scanner Id"
|
||||||
|
placeholder="Select New scanner Id"
|
||||||
|
options={n}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</form.AppField>
|
||||||
|
</div>
|
||||||
|
<div className="flex flex-row">
|
||||||
|
<div>
|
||||||
|
<form.AppField name="pinNumber">
|
||||||
|
{(field) => (
|
||||||
|
<field.InputField
|
||||||
|
label="Pin Number"
|
||||||
|
inputType="number"
|
||||||
|
required={true}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</form.AppField>
|
||||||
|
</div>
|
||||||
|
<div className="mt-9 ml-2">
|
||||||
|
<Button
|
||||||
|
type="button"
|
||||||
|
onClick={async () => {
|
||||||
|
const { data } = await axios.get("/lst/api/mobile/pin/new");
|
||||||
|
|
||||||
|
form.setFieldValue("pinNumber", data.data[0].pin);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
New Pin
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="flex justify-end mt-2 ">
|
||||||
|
<form.AppForm>
|
||||||
|
<form.SubmitButton>Submit</form.SubmitButton>
|
||||||
|
</form.AppForm>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</DialogContent>
|
||||||
|
</Dialog>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -1,16 +1,258 @@
|
|||||||
import { useSuspenseQuery } from "@tanstack/react-query";
|
import { useMutation, useSuspenseQuery } from "@tanstack/react-query";
|
||||||
import { createFileRoute } from "@tanstack/react-router";
|
import { createFileRoute, redirect } from "@tanstack/react-router";
|
||||||
|
import { createColumnHelper } from "@tanstack/react-table";
|
||||||
|
import axios from "axios";
|
||||||
|
import { format } from "date-fns-tz";
|
||||||
|
import { CircleFadingArrowUp, Trash } from "lucide-react";
|
||||||
|
import { Suspense, useState } from "react";
|
||||||
|
import { toast } from "sonner";
|
||||||
|
import { Button } from "../../components/ui/button";
|
||||||
|
import { Spinner } from "../../components/ui/spinner";
|
||||||
|
import { authClient } from "../../lib/auth-client";
|
||||||
import { getScanUsers } from "../../lib/queries/getScanUsers";
|
import { getScanUsers } from "../../lib/queries/getScanUsers";
|
||||||
|
import EditableCellInput from "../../lib/tableStuff/EditableCellInput";
|
||||||
|
import LstTable from "../../lib/tableStuff/LstTable";
|
||||||
|
import SearchableHeader from "../../lib/tableStuff/SearchableHeader";
|
||||||
|
import SkellyTable from "../../lib/tableStuff/SkellyTable";
|
||||||
|
import NewScanUser from "./-components/NewScanUser";
|
||||||
|
|
||||||
export const Route = createFileRoute("/admin/scanUsers")({
|
export const Route = createFileRoute("/admin/scanUsers")({
|
||||||
|
beforeLoad: async ({ location }) => {
|
||||||
|
const { data: session } = await authClient.getSession();
|
||||||
|
const allowedRole = ["systemAdmin", "admin"];
|
||||||
|
|
||||||
|
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,
|
component: RouteComponent,
|
||||||
});
|
});
|
||||||
|
|
||||||
const ScanUserTable = () => {
|
const updateSettings = async (
|
||||||
const { data } = useSuspenseQuery(getScanUsers());
|
id: string,
|
||||||
console.log(data);
|
data: Record<string, string | number | boolean | null>,
|
||||||
return <div>Hello "/admin/scanUsers"!</div>;
|
) => {
|
||||||
|
//console.log(id, data);
|
||||||
|
try {
|
||||||
|
const res = await axios.patch(`/lst/api/mobile/auth/user/${id}`, data, {
|
||||||
|
withCredentials: true,
|
||||||
|
timeout: 15000,
|
||||||
|
validateStatus: () => true,
|
||||||
|
});
|
||||||
|
toast.success(`User was just updated`);
|
||||||
|
return res;
|
||||||
|
} catch (err) {
|
||||||
|
toast.error("Error in updating the user");
|
||||||
|
return err;
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const ScanUserTable = () => {
|
||||||
|
const { data, refetch } = useSuspenseQuery(getScanUsers());
|
||||||
|
const columnHelper = createColumnHelper<any>();
|
||||||
|
|
||||||
|
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 columns = [
|
||||||
|
columnHelper.accessor("name", {
|
||||||
|
header: ({ column }) => (
|
||||||
|
<SearchableHeader column={column} title="Name" searchable={true} />
|
||||||
|
),
|
||||||
|
filterFn: "includesString",
|
||||||
|
cell: (i) => i.getValue(),
|
||||||
|
}),
|
||||||
|
columnHelper.accessor("scannerId", {
|
||||||
|
header: ({ column }) => (
|
||||||
|
<SearchableHeader
|
||||||
|
column={column}
|
||||||
|
title="Scanner ID"
|
||||||
|
searchable={false}
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
filterFn: "includesString",
|
||||||
|
cell: (i) => i.getValue(),
|
||||||
|
}),
|
||||||
|
columnHelper.accessor("pinNumber", {
|
||||||
|
header: ({ column }) => (
|
||||||
|
<SearchableHeader column={column} title="Pin Number" />
|
||||||
|
),
|
||||||
|
|
||||||
|
filterFn: "includesString",
|
||||||
|
cell: ({ row, getValue }) => (
|
||||||
|
<div className="flex flex-row gap-2">
|
||||||
|
<div>
|
||||||
|
<EditableCellInput
|
||||||
|
value={getValue()}
|
||||||
|
id={row.original.name}
|
||||||
|
field="value"
|
||||||
|
onSubmit={({ id, field, value }) => {
|
||||||
|
updateSetting.mutate({ id, field, value });
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="">
|
||||||
|
<Button
|
||||||
|
type="button"
|
||||||
|
onClick={async () => {
|
||||||
|
const { data } = await axios.get("/lst/api/mobile/pin/new");
|
||||||
|
updateSetting.mutate({
|
||||||
|
id: row.original.id,
|
||||||
|
field: "pinNumber",
|
||||||
|
value: data.data[0].pin,
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
New Pin
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
),
|
||||||
|
}),
|
||||||
|
columnHelper.accessor("lastScan", {
|
||||||
|
header: ({ column }) => (
|
||||||
|
<SearchableHeader column={column} title="Last Scan" />
|
||||||
|
),
|
||||||
|
cell: (i) => <span>{format(i.getValue(), "M/d/yyyy HH:mm")}</span>,
|
||||||
|
}),
|
||||||
|
columnHelper.accessor("excludedCommand", {
|
||||||
|
header: ({ column }) => (
|
||||||
|
<SearchableHeader column={column} title="Command id's Not Allowed" />
|
||||||
|
),
|
||||||
|
cell: (i) => {
|
||||||
|
const commands = i.getValue().join();
|
||||||
|
return (
|
||||||
|
<span>{commands === "" ? "All commands allowed" : commands}</span>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
columnHelper.accessor("deleteUser", {
|
||||||
|
header: ({ column }) => (
|
||||||
|
<SearchableHeader
|
||||||
|
column={column}
|
||||||
|
title="Delete User"
|
||||||
|
searchable={false}
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
filterFn: "includesString",
|
||||||
|
cell: (i) => {
|
||||||
|
// biome-ignore lint: just removing the lint for now to get this going will maybe fix later
|
||||||
|
const [activeToggle, setActiveToggle] = useState(false);
|
||||||
|
|
||||||
|
const onTrigger = async () => {
|
||||||
|
setActiveToggle(true);
|
||||||
|
|
||||||
|
try {
|
||||||
|
const res = await axios.delete(
|
||||||
|
`/lst/api/mobile/auth/user/${i.row.original.id}`,
|
||||||
|
|
||||||
|
{
|
||||||
|
withCredentials: true,
|
||||||
|
timeout: 5000,
|
||||||
|
validateStatus: () => true,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
if (res.data.success) {
|
||||||
|
toast.success(`${i.row.original.name} was deleted.`);
|
||||||
|
refetch();
|
||||||
|
setActiveToggle(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!res.data.success) {
|
||||||
|
toast.error(
|
||||||
|
`${i.row.original.name} encountered an error when trying to delete: ${res.data.message}`,
|
||||||
|
);
|
||||||
|
refetch();
|
||||||
|
setActiveToggle(false);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
setActiveToggle(false);
|
||||||
|
console.error(error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<div className="flex items-center space-x-2">
|
||||||
|
<Button
|
||||||
|
variant="destructive"
|
||||||
|
disabled={activeToggle}
|
||||||
|
onClick={onTrigger}
|
||||||
|
>
|
||||||
|
{activeToggle ? (
|
||||||
|
<span>
|
||||||
|
<Spinner />
|
||||||
|
</span>
|
||||||
|
) : (
|
||||||
|
<span>
|
||||||
|
<Trash />
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
];
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<div className="flex justify-end m-2">
|
||||||
|
<Suspense
|
||||||
|
fallback={
|
||||||
|
<div>
|
||||||
|
<p>Loading...</p>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<NewScanUser refetch={refetch} />
|
||||||
|
</Suspense>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<LstTable data={data} columns={columns} pageSize={50} />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
// const NewUserForm = ()=>{
|
||||||
|
// const { data, refetch } = useSuspenseQuery(getScanUsers());
|
||||||
|
// }
|
||||||
function RouteComponent() {
|
function RouteComponent() {
|
||||||
return <ScanUserTable />;
|
//const { data: session } = useSession();
|
||||||
|
return (
|
||||||
|
<Suspense fallback={<SkellyTable />}>
|
||||||
|
<ScanUserTable />
|
||||||
|
</Suspense>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,8 +15,8 @@
|
|||||||
"foregroundImage": "./assets/adaptive-icon-white.png",
|
"foregroundImage": "./assets/adaptive-icon-white.png",
|
||||||
"backgroundColor": "#ffffff"
|
"backgroundColor": "#ffffff"
|
||||||
},
|
},
|
||||||
"versionCode": 32,
|
"versionCode": 33,
|
||||||
"minSupportedVersionCode": 26,
|
"minSupportedVersionCode": 33,
|
||||||
"predictiveBackGestureEnabled": false,
|
"predictiveBackGestureEnabled": false,
|
||||||
"package": "net.alpla.lst.mobile"
|
"package": "net.alpla.lst.mobile"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import axios from "axios";
|
import axios from "axios";
|
||||||
import { format } from "date-fns-tz";
|
import { format } from "date-fns-tz";
|
||||||
import { useFocusEffect } from "expo-router";
|
import { Redirect, useFocusEffect, useRouter } from "expo-router";
|
||||||
import { useCallback, useEffect, useState } from "react";
|
import { useCallback, useEffect, useState } from "react";
|
||||||
import { Alert, Button, Text, View } from "react-native";
|
import { Alert, Button, Text, View } from "react-native";
|
||||||
import { useAppStore } from "../hooks/useAppStore";
|
import { useAppStore } from "../hooks/useAppStore";
|
||||||
@@ -23,14 +23,13 @@ const formatName = (name?: string) =>
|
|||||||
export default function LSTScanner() {
|
export default function LSTScanner() {
|
||||||
const user = useMobileAuthStore((s) => s.user);
|
const user = useMobileAuthStore((s) => s.user);
|
||||||
const logout = useMobileAuthStore((s) => s.logout);
|
const logout = useMobileAuthStore((s) => s.logout);
|
||||||
|
const router = useRouter();
|
||||||
|
|
||||||
// TODO : move to off tcp stuff after od
|
// TODO : move to off tcp stuff after od
|
||||||
const lastScan = useScannerStore((s) => s.lastScan);
|
const lastScan = useScannerStore((s) => s.lastScan);
|
||||||
const setLastScan = useScannerStore((s) => s.setLastScan);
|
const setLastScan = useScannerStore((s) => s.setLastScan);
|
||||||
const [tagScans, setTagScans] = useState<any>([]);
|
const [tagScans, setTagScans] = useState<any>([]);
|
||||||
const scannerIdFromStore = useAppStore((s) => s.scannerId);
|
|
||||||
const serverIp = useAppStore((s) => s.serverIp);
|
const serverIp = useAppStore((s) => s.serverIp);
|
||||||
const serverPort = useAppStore((s) => s.serverPort);
|
|
||||||
const [bgColor, setBGColor] = useState<string | null>(null);
|
const [bgColor, setBGColor] = useState<string | null>(null);
|
||||||
|
|
||||||
const handleScan = useCallback(
|
const handleScan = useCallback(
|
||||||
@@ -154,6 +153,13 @@ export default function LSTScanner() {
|
|||||||
setTagScans([]);
|
setTagScans([]);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const logoutScanner = () => {
|
||||||
|
setTagScans([]);
|
||||||
|
setLastScan(null);
|
||||||
|
logout();
|
||||||
|
router.replace("/");
|
||||||
|
};
|
||||||
|
|
||||||
//console.log(lastScan);
|
//console.log(lastScan);
|
||||||
|
|
||||||
useFocusEffect(
|
useFocusEffect(
|
||||||
@@ -189,7 +195,11 @@ export default function LSTScanner() {
|
|||||||
{!lastScan ? (
|
{!lastScan ? (
|
||||||
<View style={{ marginTop: 10, alignItems: "center" }}>
|
<View style={{ marginTop: 10, alignItems: "center" }}>
|
||||||
<Text className="text-xl font-bold">Ready to scan</Text>
|
<Text className="text-xl font-bold">Ready to scan</Text>
|
||||||
<Text>Waiting for first scan...</Text>
|
<Text>Please Scan a command to start scanning...</Text>
|
||||||
|
<Text className="text-sm">
|
||||||
|
Scanning a label could cause errors due to incorrect previous
|
||||||
|
command scanned
|
||||||
|
</Text>
|
||||||
</View>
|
</View>
|
||||||
) : (
|
) : (
|
||||||
<View
|
<View
|
||||||
@@ -228,7 +238,7 @@ export default function LSTScanner() {
|
|||||||
<View className="m-2">
|
<View className="m-2">
|
||||||
{user && (
|
{user && (
|
||||||
<View className="items-center">
|
<View className="items-center">
|
||||||
<Button title="Logout" onPress={logout} />
|
<Button title="Logout" onPress={logoutScanner} />
|
||||||
</View>
|
</View>
|
||||||
)}
|
)}
|
||||||
</View>
|
</View>
|
||||||
|
|||||||
1
migrations/0051_sad_war_machine.sql
Normal file
1
migrations/0051_sad_war_machine.sql
Normal file
@@ -0,0 +1 @@
|
|||||||
|
ALTER TABLE "analytics_daily" ADD CONSTRAINT "analytics_daily_business_route_unique" UNIQUE("business_date","method","route_pattern","module");
|
||||||
2360
migrations/meta/0051_snapshot.json
Normal file
2360
migrations/meta/0051_snapshot.json
Normal file
File diff suppressed because it is too large
Load Diff
@@ -358,6 +358,13 @@
|
|||||||
"when": 1778169641819,
|
"when": 1778169641819,
|
||||||
"tag": "0050_concerned_vivisector",
|
"tag": "0050_concerned_vivisector",
|
||||||
"breakpoints": true
|
"breakpoints": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idx": 51,
|
||||||
|
"version": "7",
|
||||||
|
"when": 1778525497824,
|
||||||
|
"tag": "0051_sad_war_machine",
|
||||||
|
"breakpoints": true
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user