Compare commits
16 Commits
f5bae2c0c2
...
ebf1060475
| Author | SHA1 | Date | |
|---|---|---|---|
| ebf1060475 | |||
| c64392f457 | |||
| e9e73c829c | |||
| bcb7773007 | |||
| eb950d2c29 | |||
| 2616acf106 | |||
| 30ff7b71d9 | |||
| e7af3d1182 | |||
| 3e66c3920d | |||
| eb9d77c3d4 | |||
| 342a97f6b1 | |||
| b0c7277a6c | |||
| dc95e50a84 | |||
| d2a9e1d110 | |||
| a9c69250bd | |||
| d61be61f44 |
2
.gitignore
vendored
@@ -9,7 +9,7 @@ downloads
|
|||||||
.scriptCreds
|
.scriptCreds
|
||||||
node-v24.14.0-x64.msi
|
node-v24.14.0-x64.msi
|
||||||
postgresql-17.9-2-windows-x64.exe
|
postgresql-17.9-2-windows-x64.exe
|
||||||
VSCodeUserSetup-x64-1.112.0.exe
|
VSCodeSetup-x64-1.120.0.exe
|
||||||
nssm.exe
|
nssm.exe
|
||||||
frontend/.tanstack
|
frontend/.tanstack
|
||||||
|
|
||||||
|
|||||||
@@ -3,16 +3,14 @@ import { requireAuth } from "../middleware/auth.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";
|
||||||
|
import users from "./admin.users.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, build);
|
|
||||||
app.use(
|
|
||||||
`${baseUrl}/api/admin/build`,
|
|
||||||
requireAuth,
|
|
||||||
|
|
||||||
update,
|
app.use(`${baseUrl}/api/admin/build`, requireAuth, build);
|
||||||
);
|
app.use(`${baseUrl}/api/admin/build`, requireAuth, update);
|
||||||
|
app.use(`${baseUrl}/api/admin/user`, requireAuth, users);
|
||||||
|
|
||||||
// all other system should be under /api/system/*
|
// all other system should be under /api/system/*
|
||||||
};
|
};
|
||||||
|
|||||||
46
backend/admin/admin.users.ts
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
/**
|
||||||
|
* To be able to run this we need to set our dev pc in the .env.
|
||||||
|
* if its empty just ignore it. this will just be the double catch
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { fromNodeHeaders } from "better-auth/node";
|
||||||
|
import { Router } from "express";
|
||||||
|
import { auth } from "../utils/auth.utils.js";
|
||||||
|
import { apiReturn } from "../utils/returnHelper.utils.js";
|
||||||
|
|
||||||
|
const r = Router();
|
||||||
|
|
||||||
|
r.get("/", async (req, res) => {
|
||||||
|
const { users } = await auth.api.listUsers({
|
||||||
|
query: {
|
||||||
|
limit: 50,
|
||||||
|
},
|
||||||
|
headers: fromNodeHeaders(req.headers),
|
||||||
|
});
|
||||||
|
|
||||||
|
// console.log(error);
|
||||||
|
|
||||||
|
// if (error) {
|
||||||
|
// return apiReturn(res, {
|
||||||
|
// success: false,
|
||||||
|
// level: "info",
|
||||||
|
// module: "admin",
|
||||||
|
// subModule: "user",
|
||||||
|
// message: `There was an error getting the users.`,
|
||||||
|
// data: users,
|
||||||
|
// status: 400,
|
||||||
|
// });
|
||||||
|
// }
|
||||||
|
|
||||||
|
return apiReturn(res, {
|
||||||
|
success: true,
|
||||||
|
level: "info",
|
||||||
|
module: "admin",
|
||||||
|
subModule: "users",
|
||||||
|
message: `Current active users.`,
|
||||||
|
data: users,
|
||||||
|
status: 200,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
export default r;
|
||||||
@@ -34,7 +34,6 @@ const createApp = async () => {
|
|||||||
app.use(routeHitMiddleware);
|
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);
|
|
||||||
|
|
||||||
app.get(`${baseUrl}/api/lst-config.js`, (_, res) => {
|
app.get(`${baseUrl}/api/lst-config.js`, (_, res) => {
|
||||||
res.type("application/javascript");
|
res.type("application/javascript");
|
||||||
@@ -52,6 +51,8 @@ const createApp = async () => {
|
|||||||
`);
|
`);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
setupRoutes(baseUrl, app);
|
||||||
|
|
||||||
app.use(
|
app.use(
|
||||||
`${baseUrl}/app`,
|
`${baseUrl}/app`,
|
||||||
express.static(join(__dirname, "../frontend/dist")),
|
express.static(join(__dirname, "../frontend/dist")),
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ export const scanLog = pgTable("scan_log", {
|
|||||||
commandDescription: text("command_description"),
|
commandDescription: text("command_description"),
|
||||||
runningNumber: text("running_number").default("0"),
|
runningNumber: text("running_number").default("0"),
|
||||||
status: text("status"),
|
status: text("status"),
|
||||||
|
scannerVersion: text("scanner_version").default("0"),
|
||||||
lines: jsonb("lines").default([]),
|
lines: jsonb("lines").default([]),
|
||||||
add_Date: timestamp("add_date").defaultNow(),
|
add_Date: timestamp("add_date").defaultNow(),
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -8,11 +8,10 @@ export const setupGPSqlRoutes = (baseUrl: string, app: Express) => {
|
|||||||
//setup all the routes
|
//setup all the routes
|
||||||
// Apply auth to entire router
|
// Apply auth to entire router
|
||||||
const router = Router();
|
const router = Router();
|
||||||
router.use(requireAuth);
|
|
||||||
|
|
||||||
router.use(start);
|
router.use(start);
|
||||||
router.use(stop);
|
router.use(stop);
|
||||||
router.use(restart);
|
router.use(restart);
|
||||||
|
|
||||||
app.use(`${baseUrl}/api/system/gpSql`, router);
|
app.use(`${baseUrl}/api/system/gpSql`, requireAuth, router);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import { apiReturn } from "../utils/returnHelper.utils.js";
|
|||||||
const r = Router();
|
const r = Router();
|
||||||
|
|
||||||
// scanners that are dedicated to specific users.
|
// scanners that are dedicated to specific users.
|
||||||
const SPECIAL_SCANNERS = [69, 98];
|
const SPECIAL_SCANNERS = [98];
|
||||||
|
|
||||||
const buildAllowedScannerIds = (scannerCount: number) => {
|
const buildAllowedScannerIds = (scannerCount: number) => {
|
||||||
const generatedIds = Array.from({ length: scannerCount }, (_, i) => i + 1);
|
const generatedIds = Array.from({ length: scannerCount }, (_, i) => i + 1);
|
||||||
|
|||||||
@@ -38,7 +38,66 @@ router.get("/ehs", (_, res) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
res.setHeader("Content-Type", "application/vnd.android.package-archive");
|
res.setHeader("Content-Type", "application/vnd.android.package-archive");
|
||||||
res.setHeader("Content-Disposition", `attachment; filename="EHS.apk}"`);
|
res.setHeader("Content-Disposition", `attachment; filename="EHS.apk"`);
|
||||||
|
|
||||||
|
return res.sendFile(apkPath);
|
||||||
|
});
|
||||||
|
|
||||||
|
router.get("/ehs/xml", (_, res) => {
|
||||||
|
const xmlPath = path.join(downloadDir, "enterprisehomescreen.xml");
|
||||||
|
|
||||||
|
if (!fs.existsSync(xmlPath)) {
|
||||||
|
return res.status(404).json({
|
||||||
|
success: false,
|
||||||
|
message: "EHS XML not found",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
res.setHeader("Content-Type", "application/xml");
|
||||||
|
res.setHeader(
|
||||||
|
"Content-Disposition",
|
||||||
|
`attachment; filename="enterprisehomescreen.xml"`,
|
||||||
|
);
|
||||||
|
|
||||||
|
return res.sendFile(xmlPath);
|
||||||
|
});
|
||||||
|
|
||||||
|
router.get("/upgrade/android/13", (_, res) => {
|
||||||
|
const apkPath = path.join(
|
||||||
|
downloadDir,
|
||||||
|
"HE_FULL_UPDATE_13-51-16.00-TG-U00-STD-HEL-04.zip",
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!fs.existsSync(apkPath)) {
|
||||||
|
return res.status(404).json({ success: false, message: "APK not found" });
|
||||||
|
}
|
||||||
|
|
||||||
|
//res.setHeader("Content-Type", "application/vnd.android.package-archive");
|
||||||
|
res.setHeader("Content-Type", "application/zip");
|
||||||
|
res.setHeader(
|
||||||
|
"Content-Disposition",
|
||||||
|
`attachment; filename="HE_FULL_UPDATE_13.zip"`,
|
||||||
|
);
|
||||||
|
|
||||||
|
return res.sendFile(apkPath);
|
||||||
|
});
|
||||||
|
|
||||||
|
router.get("/upgrade/android/14", (_, res) => {
|
||||||
|
const apkPath = path.join(
|
||||||
|
downloadDir,
|
||||||
|
"HE_FULL_UPDATE_14-38-04.00-UG-U15-STD-HEL-04.zip",
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!fs.existsSync(apkPath)) {
|
||||||
|
return res.status(404).json({ success: false, message: "APK not found" });
|
||||||
|
}
|
||||||
|
|
||||||
|
//res.setHeader("Content-Type", "application/vnd.android.package-archive");
|
||||||
|
res.setHeader("Content-Type", "application/zip");
|
||||||
|
res.setHeader(
|
||||||
|
"Content-Disposition",
|
||||||
|
`attachment; filename="HE_FULL_UPDATE_14.zip"`,
|
||||||
|
);
|
||||||
|
|
||||||
return res.sendFile(apkPath);
|
return res.sendFile(apkPath);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,6 +1,12 @@
|
|||||||
import { Router } from "express";
|
import { Router } from "express";
|
||||||
|
import { prodQuery } from "../prodSql/prodSqlQuery.controller.js";
|
||||||
|
import {
|
||||||
|
type SqlQuery,
|
||||||
|
sqlQuerySelector,
|
||||||
|
} from "../prodSql/prodSqlQuerySelector.utils.js";
|
||||||
import { runProdApi } from "../utils/prodEndpoint.utils.js";
|
import { runProdApi } from "../utils/prodEndpoint.utils.js";
|
||||||
import { apiReturn } from "../utils/returnHelper.utils.js";
|
import { apiReturn, returnFunc } from "../utils/returnHelper.utils.js";
|
||||||
|
import { tryCatch } from "../utils/trycatch.utils.js";
|
||||||
|
|
||||||
const router = Router();
|
const router = Router();
|
||||||
|
|
||||||
@@ -9,7 +15,27 @@ router.post("/", async (req, res) => {
|
|||||||
|
|
||||||
const lane = body.lane.split("#");
|
const lane = body.lane.split("#");
|
||||||
|
|
||||||
console.log(lane[2]);
|
// check if the plant has warehousing activated
|
||||||
|
const featureQ = sqlQuerySelector(`featureCheck`) as SqlQuery;
|
||||||
|
|
||||||
|
const { data: fd, error: fe } = await tryCatch(
|
||||||
|
prodQuery(featureQ.query, `Running feature check`),
|
||||||
|
);
|
||||||
|
|
||||||
|
if (fe) {
|
||||||
|
return returnFunc({
|
||||||
|
success: false,
|
||||||
|
level: "error",
|
||||||
|
module: "datamart",
|
||||||
|
subModule: "query",
|
||||||
|
message: `feature check failed`,
|
||||||
|
data: fe as any,
|
||||||
|
notify: false,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(fd);
|
||||||
|
|
||||||
const laneData = await runProdApi({
|
const laneData = await runProdApi({
|
||||||
method: "post",
|
method: "post",
|
||||||
endpoint: "/public/v1.1/Warehousing/GetWarehouseUnits",
|
endpoint: "/public/v1.1/Warehousing/GetWarehouseUnits",
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import type { Express } from "express";
|
import type { Express } from "express";
|
||||||
|
import { featureCheck } from "../middleware/featureActive.middleware.js";
|
||||||
import available from "./availableScanIds.route.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";
|
||||||
@@ -10,13 +11,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(`${baseUrl}/api/mobile/version`, version);
|
app.use(`${baseUrl}/api/mobile/version`, featureCheck("mobile"), version);
|
||||||
app.use(`${baseUrl}/api/mobile/apk`, downloads);
|
app.use(`${baseUrl}/api/mobile/apk`, featureCheck("mobile"), downloads);
|
||||||
app.use(`${baseUrl}/api/mobile/logs`, logs);
|
app.use(`${baseUrl}/api/mobile/logs`, featureCheck("mobile"), logs);
|
||||||
app.use(`${baseUrl}/api/mobile/auth`, authPin);
|
app.use(`${baseUrl}/api/mobile/auth`, featureCheck("mobile"), authPin);
|
||||||
app.use(`${baseUrl}/api/mobile/pin`, newPin);
|
app.use(`${baseUrl}/api/mobile/pin`, featureCheck("mobile"), newPin);
|
||||||
app.use(`${baseUrl}/api/mobile/laneCheck`, lanes);
|
app.use(`${baseUrl}/api/mobile/laneCheck`, featureCheck("mobile"), lanes);
|
||||||
app.use(`${baseUrl}/api/mobile/available`, available);
|
app.use(`${baseUrl}/api/mobile/available`, featureCheck("mobile"), available);
|
||||||
|
|
||||||
// all other system should be under /api/system/*
|
// all other system should be under /api/system/*
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,14 +1,22 @@
|
|||||||
|
import { eq, sql } from "drizzle-orm";
|
||||||
import { Router } from "express";
|
import { Router } from "express";
|
||||||
|
|
||||||
import { db } from "../db/db.controller.js";
|
import { db } from "../db/db.controller.js";
|
||||||
import { scanLog } from "../db/schema/scanlog.schema.js";
|
import { scanLog } from "../db/schema/scanlog.schema.js";
|
||||||
|
import { scanUser } from "../db/schema/scanUsers.js";
|
||||||
import { apiReturn } from "../utils/returnHelper.utils.js";
|
import { apiReturn } from "../utils/returnHelper.utils.js";
|
||||||
|
|
||||||
const router = Router();
|
const router = Router();
|
||||||
|
|
||||||
router.post("/", async (req, res) => {
|
router.post("/", async (req, res) => {
|
||||||
const body = req.body;
|
const body = req.body;
|
||||||
|
try {
|
||||||
|
await db
|
||||||
|
.update(scanUser)
|
||||||
|
.set({ lastScan: sql`NOW()` })
|
||||||
|
.where(eq(scanUser.name, body.name));
|
||||||
|
} catch (error) {
|
||||||
|
console.log(error);
|
||||||
|
}
|
||||||
const newLog = await db
|
const newLog = await db
|
||||||
.insert(scanLog)
|
.insert(scanLog)
|
||||||
.values({
|
.values({
|
||||||
@@ -20,6 +28,7 @@ router.post("/", async (req, res) => {
|
|||||||
lines: body.lines ?? "",
|
lines: body.lines ?? "",
|
||||||
user: body.user ?? "",
|
user: body.user ?? "",
|
||||||
runningNumber: body.runningNumber ?? "",
|
runningNumber: body.runningNumber ?? "",
|
||||||
|
scannerVersion: body.scannerVersion ?? "0",
|
||||||
})
|
})
|
||||||
.returning();
|
.returning();
|
||||||
|
|
||||||
|
|||||||
80
backend/notification/notification.SqlJobCleanUp.ts
Normal file
@@ -0,0 +1,80 @@
|
|||||||
|
import { prodQuery } from "../prodSql/prodSqlQuery.controller.js";
|
||||||
|
import {
|
||||||
|
type SqlQuery,
|
||||||
|
sqlQuerySelector,
|
||||||
|
} from "../prodSql/prodSqlQuerySelector.utils.js";
|
||||||
|
import { tryCatch } from "../utils/trycatch.utils.js";
|
||||||
|
|
||||||
|
// disable the jobs
|
||||||
|
const jobNames: string[] = [
|
||||||
|
"monitor_$_lots",
|
||||||
|
"monitor_$_lots_2",
|
||||||
|
"monitor$lots",
|
||||||
|
"Monitor_APO", //listen for people to cry this is no longer a thing
|
||||||
|
"Monitor_APO2",
|
||||||
|
"Monitor_AutoConsumeMaterials", // TODO: migrate to lst
|
||||||
|
"Monitor_AutoConsumeMaterials_iow1",
|
||||||
|
"Monitor_AutoConsumeMaterials_iow2",
|
||||||
|
"Monitor_BlockedINV_Loc",
|
||||||
|
"monitor_inv_cycle",
|
||||||
|
"monitor_inv_cycle_1",
|
||||||
|
"monitor_inv_cycle_2",
|
||||||
|
"monitor_edi_import", // TODO: migrate to lst -- for the query select count(*) from AlplaPROD_test3.dbo.T_EDIDokumente (nolock) where /* IdLieferant > 1 and */ add_date > DATEADD(MINUTE, -30, getdate())
|
||||||
|
"Monitor_Lot_Progression",
|
||||||
|
"Monitor_Lots", // TODO: migrate to lst -- this should be the one where we monitor the when a lot is assigned if its missing some data.
|
||||||
|
"Monitor_MinMax", // TODO:Migrate to lst
|
||||||
|
"Monitor_MinMax_iow2",
|
||||||
|
"Monitor_PM",
|
||||||
|
"Monitor_Purity",
|
||||||
|
"monitor_wastebookings", // TODO: Migrate
|
||||||
|
"LastPriceUpdate", // not even sure what this is
|
||||||
|
"GETLabelsCount", // seems like an old jc job
|
||||||
|
"jobforpuritycount", // was not even working correctly
|
||||||
|
"Monitor_EmptyAutoConsumLocations", // not sure who uses this one
|
||||||
|
"monitor_labelreprint", // Migrated but need to find out who really wants this
|
||||||
|
"test", // not even sure why this is active
|
||||||
|
"UpdateLastMoldUsed", // old jc inserts data into a table but not sure what its used for not linked to any other alert
|
||||||
|
"UpdateWhsePositions3", // old jc inserts data into a table but not sure what its used for not linked to any other alert
|
||||||
|
"UpdateWhsePositions4",
|
||||||
|
"delete_print", // i think this was in here for when we was having lag prints in iowa1
|
||||||
|
"INV_WHSE_1", // something random i wrote long time ago looks like an inv thing to see aged stuff
|
||||||
|
"INV_WHSE_2",
|
||||||
|
"laneAgeCheck", // another strange one thats been since moved to lst
|
||||||
|
"monitor_blocking_2",
|
||||||
|
"monitor_blocking", // already in lst
|
||||||
|
"monitor_min_inv", // do we still want this one? it has a description of: this checks m-f the min inventory of materials based on the min level set in stock
|
||||||
|
"Monitor_MixedLocations",
|
||||||
|
"Monitor_PM",
|
||||||
|
"Monitor_PM2",
|
||||||
|
"wrong_lots_1",
|
||||||
|
"wrong_lots_2",
|
||||||
|
"invenotry check", // spelling error one of my stupids
|
||||||
|
"monitor_hold_monitor",
|
||||||
|
"Monitor_Silo_adjustments",
|
||||||
|
"monitor_qualityLocMonitor", // validating with lima this is still needed
|
||||||
|
"Monitor_Stock_Change",
|
||||||
|
];
|
||||||
|
|
||||||
|
export const sqlJobCleanUp = async () => {
|
||||||
|
// running a query to disable jobs that are moved to lst to be better maintained
|
||||||
|
const sqlQuery = sqlQuerySelector("disableJob") as SqlQuery;
|
||||||
|
|
||||||
|
if (!sqlQuery.success) {
|
||||||
|
console.error("Failed to load the query: ", sqlQuery.message);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
for (const job of jobNames) {
|
||||||
|
const { error } = await tryCatch(
|
||||||
|
prodQuery(
|
||||||
|
sqlQuery.query.replace("[jobName]", `${job}`),
|
||||||
|
`Disabling job: ${job}`,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
if (error) {
|
||||||
|
console.error(error);
|
||||||
|
}
|
||||||
|
|
||||||
|
//console.log(data);
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -43,7 +43,7 @@ const parseZebraAlert = (body: any): PrinterEvent => {
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
r.post("/printer/listener/:printer", upload.any(), async (req, res) => {
|
r.post("/:printer", upload.any(), async (req, res) => {
|
||||||
const { printer: printerName } = req.params;
|
const { printer: printerName } = req.params;
|
||||||
const event: PrinterEvent = parseZebraAlert(req.body);
|
const event: PrinterEvent = parseZebraAlert(req.body);
|
||||||
|
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ import { printerSync } from "./ocp.printer.manage.js";
|
|||||||
|
|
||||||
const r = Router();
|
const r = Router();
|
||||||
|
|
||||||
r.post("/printer/update", async (_, res) => {
|
r.post("/update", async (_, res) => {
|
||||||
printerSync();
|
printerSync();
|
||||||
return apiReturn(res, {
|
return apiReturn(res, {
|
||||||
success: true,
|
success: true,
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { type Express, Router } from "express";
|
import type { Express } 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";
|
||||||
|
|
||||||
@@ -6,20 +6,11 @@ import listener from "./ocp.printer.listener.js";
|
|||||||
import update from "./ocp.printer.update.js";
|
import update from "./ocp.printer.update.js";
|
||||||
|
|
||||||
export const setupOCPRoutes = (baseUrl: string, app: Express) => {
|
export const setupOCPRoutes = (baseUrl: string, app: Express) => {
|
||||||
//setup all the routes
|
app.use(`${baseUrl}/api/ocp/printer/listener`, featureCheck("ocp"), listener);
|
||||||
const router = Router();
|
app.use(
|
||||||
|
`${baseUrl}/api/ocp/printer`,
|
||||||
// is the feature even on?
|
featureCheck("ocp"),
|
||||||
router.use(featureCheck("ocp"));
|
requireAuth,
|
||||||
|
update,
|
||||||
// non auth routes up here
|
);
|
||||||
router.use(listener);
|
|
||||||
|
|
||||||
// auth routes below here
|
|
||||||
router.use(requireAuth);
|
|
||||||
|
|
||||||
router.use(update);
|
|
||||||
//router.use("");
|
|
||||||
|
|
||||||
app.use(`${baseUrl}/api/ocp`, router);
|
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { type Express, Router } from "express";
|
import type { Express } 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";
|
||||||
|
|
||||||
@@ -6,15 +6,11 @@ import getApt from "./opendockGetRelease.route.js";
|
|||||||
|
|
||||||
export const setupOpendockRoutes = (baseUrl: string, app: Express) => {
|
export const setupOpendockRoutes = (baseUrl: string, app: Express) => {
|
||||||
//setup all the routes
|
//setup all the routes
|
||||||
// Apply auth to entire router
|
|
||||||
const router = Router();
|
|
||||||
|
|
||||||
// is the feature even on?
|
app.use(
|
||||||
router.use(featureCheck("opendock_sync"));
|
`${baseUrl}/api/opendock`,
|
||||||
|
featureCheck("opendock_sync"),
|
||||||
// we need to make sure we are authenticated to see the releases
|
requireAuth,
|
||||||
router.use(requireAuth);
|
getApt,
|
||||||
|
);
|
||||||
router.use(getApt);
|
|
||||||
app.use(`${baseUrl}/api/opendock`, router);
|
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -10,9 +10,7 @@ export const setupProdSqlRoutes = (baseUrl: string, app: Express) => {
|
|||||||
const router = Router();
|
const router = Router();
|
||||||
router.use(requireAuth);
|
router.use(requireAuth);
|
||||||
|
|
||||||
router.use(start);
|
app.use(`${baseUrl}/api/system/prodSql/start`, requireAuth, start);
|
||||||
router.use(stop);
|
app.use(`${baseUrl}/api/system/prodSql/stop`, requireAuth, stop);
|
||||||
router.use(restart);
|
app.use(`${baseUrl}/api/system/prodSql/restart`, requireAuth, restart);
|
||||||
|
|
||||||
app.use(`${baseUrl}/api/system/prodSql`, router);
|
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import { closePool, connectProdSql } from "./prodSqlConnection.controller.js";
|
|||||||
|
|
||||||
const r = Router();
|
const r = Router();
|
||||||
|
|
||||||
r.post("/restart", async (_, res) => {
|
r.post("/", async (_, res) => {
|
||||||
await closePool();
|
await closePool();
|
||||||
|
|
||||||
await new Promise((r) => setTimeout(r, 2000));
|
await new Promise((r) => setTimeout(r, 2000));
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import { connectProdSql } from "./prodSqlConnection.controller.js";
|
|||||||
|
|
||||||
const r = Router();
|
const r = Router();
|
||||||
|
|
||||||
r.post("/start", async (_, res) => {
|
r.post("/", async (_, res) => {
|
||||||
const connect = await connectProdSql();
|
const connect = await connectProdSql();
|
||||||
apiReturn(res, {
|
apiReturn(res, {
|
||||||
success: connect.success,
|
success: connect.success,
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import { closePool } from "./prodSqlConnection.controller.js";
|
|||||||
|
|
||||||
const r = Router();
|
const r = Router();
|
||||||
|
|
||||||
r.post("/stop", async (_, res) => {
|
r.post("/", async (_, res) => {
|
||||||
const connect = await closePool();
|
const connect = await closePool();
|
||||||
apiReturn(res, {
|
apiReturn(res, {
|
||||||
success: connect.success,
|
success: connect.success,
|
||||||
|
|||||||
8
backend/prodSql/queries/disableJob.sql
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
/*
|
||||||
|
disables sql jobs.
|
||||||
|
*/
|
||||||
|
EXEC msdb.dbo.sp_update_job @job_name = N'[jobName]', @enabled = 0;
|
||||||
|
-- DECLARE @JobName varchar(max) = '[jobName]'
|
||||||
|
-- UPDATE msdb.dbo.sysjobs
|
||||||
|
-- SET enabled = 0
|
||||||
|
-- WHERE name = @JobName;
|
||||||
@@ -16,6 +16,7 @@ import { setupUtilsRoutes } from "./utils/utils.routes.js";
|
|||||||
|
|
||||||
export const setupRoutes = (baseUrl: string, app: Express) => {
|
export const setupRoutes = (baseUrl: string, app: Express) => {
|
||||||
//routes that are on by default
|
//routes that are on by default
|
||||||
|
setupMobileRoutes(baseUrl, app);
|
||||||
setupSystemRoutes(baseUrl, app);
|
setupSystemRoutes(baseUrl, app);
|
||||||
setupAdminRoutes(baseUrl, app);
|
setupAdminRoutes(baseUrl, app);
|
||||||
setupApiDocsRoutes(baseUrl, app);
|
setupApiDocsRoutes(baseUrl, app);
|
||||||
@@ -28,5 +29,4 @@ export const setupRoutes = (baseUrl: string, app: Express) => {
|
|||||||
setupNotificationRoutes(baseUrl, app);
|
setupNotificationRoutes(baseUrl, app);
|
||||||
setupOCPRoutes(baseUrl, app);
|
setupOCPRoutes(baseUrl, app);
|
||||||
setupTCPRoutes(baseUrl, app);
|
setupTCPRoutes(baseUrl, app);
|
||||||
setupMobileRoutes(baseUrl, app);
|
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import { connectGPSql } from "./gpSql/gpSqlConnection.controller.js";
|
|||||||
import { createLogger } from "./logger/logger.controller.js";
|
import { createLogger } from "./logger/logger.controller.js";
|
||||||
import { historicalSchedule } from "./logistics/logistics.historicalInv.js";
|
import { historicalSchedule } from "./logistics/logistics.historicalInv.js";
|
||||||
import { startNotifications } from "./notification/notification.controller.js";
|
import { startNotifications } from "./notification/notification.controller.js";
|
||||||
|
import { sqlJobCleanUp } from "./notification/notification.SqlJobCleanUp.js";
|
||||||
import { createNotifications } from "./notification/notifications.master.js";
|
import { createNotifications } from "./notification/notifications.master.js";
|
||||||
import { printerSync } from "./ocp/ocp.printer.manage.js";
|
import { printerSync } from "./ocp/ocp.printer.manage.js";
|
||||||
import { monitorReleaseChanges } from "./opendock/openDockRreleaseMonitor.utils.js";
|
import { monitorReleaseChanges } from "./opendock/openDockRreleaseMonitor.utils.js";
|
||||||
@@ -83,6 +84,9 @@ const start = async () => {
|
|||||||
startNotifications();
|
startNotifications();
|
||||||
serversChecks();
|
serversChecks();
|
||||||
aggregateRouteHitsForBusinessDay();
|
aggregateRouteHitsForBusinessDay();
|
||||||
|
|
||||||
|
// can be removed at a later date
|
||||||
|
sqlJobCleanUp();
|
||||||
}, 5 * 1000);
|
}, 5 * 1000);
|
||||||
|
|
||||||
process.on("uncaughtException", async (err) => {
|
process.on("uncaughtException", async (err) => {
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
import { createAccessControl } from "better-auth/plugins/access";
|
import { createAccessControl } from "better-auth/plugins/access";
|
||||||
|
import { adminAc } from "better-auth/plugins/admin/access";
|
||||||
|
|
||||||
export const statement = {
|
export const statement = {
|
||||||
app: ["read", "create", "share", "update", "delete", "readAll"],
|
app: ["read", "create", "share", "update", "delete", "readAll"],
|
||||||
user: ["ban"],
|
//user: ["ban"],
|
||||||
quality: ["read", "create", "share", "update", "delete", "readAll"],
|
quality: ["read", "create", "share", "update", "delete", "readAll"],
|
||||||
notifications: ["read", "create", "share", "update", "delete", "readAll"],
|
notifications: ["read", "create", "share", "update", "delete", "readAll"],
|
||||||
} as const;
|
} as const;
|
||||||
@@ -20,7 +21,8 @@ export const admin = ac.newRole({
|
|||||||
|
|
||||||
export const systemAdmin = ac.newRole({
|
export const systemAdmin = ac.newRole({
|
||||||
app: ["read", "create", "share", "update", "delete", "readAll"],
|
app: ["read", "create", "share", "update", "delete", "readAll"],
|
||||||
user: ["ban"],
|
//user: ["ban"],
|
||||||
quality: ["read", "create", "share", "update", "delete", "readAll"],
|
quality: ["read", "create", "share", "update", "delete", "readAll"],
|
||||||
notifications: ["read", "create", "share", "update", "delete", "readAll"],
|
notifications: ["read", "create", "share", "update", "delete", "readAll"],
|
||||||
|
...adminAc.statements,
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -7,6 +7,7 @@
|
|||||||
<title>Logistics Support Tool</title>
|
<title>Logistics Support Tool</title>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
const configScript = document.createElement("script");
|
const configScript = document.createElement("script");
|
||||||
configScript.src = `${window.location.origin}/lst/api/lst-config.js`;
|
configScript.src = `${window.location.origin}/lst/api/lst-config.js`;
|
||||||
|
|||||||
BIN
frontend/public/imgs/docs/mobile/critical_update.png
Normal file
|
After Width: | Height: | Size: 483 KiB |
BIN
frontend/public/imgs/docs/mobile/ehs_homeScreen.png
Normal file
|
After Width: | Height: | Size: 613 KiB |
BIN
frontend/public/imgs/docs/mobile/ehs_menu.png
Normal file
|
After Width: | Height: | Size: 403 KiB |
BIN
frontend/public/imgs/docs/mobile/stagenow.png
Normal file
|
After Width: | Height: | Size: 238 KiB |
BIN
frontend/public/imgs/docs/mobile/test2-1.png
Normal file
|
After Width: | Height: | Size: 57 KiB |
BIN
frontend/public/imgs/docs/mobile/test2-2.png
Normal file
|
After Width: | Height: | Size: 70 KiB |
BIN
frontend/public/imgs/docs/mobile/test2-3.png
Normal file
|
After Width: | Height: | Size: 17 KiB |
BIN
frontend/public/imgs/docs/mobile/tools.png
Normal file
|
After Width: | Height: | Size: 268 KiB |
BIN
frontend/public/imgs/docs/mobile/update.png
Normal file
|
After Width: | Height: | Size: 259 KiB |
BIN
frontend/public/imgs/docs/mobile/usday1-1.png
Normal file
|
After Width: | Height: | Size: 56 KiB |
BIN
frontend/public/imgs/docs/mobile/usday1-2.png
Normal file
|
After Width: | Height: | Size: 70 KiB |
BIN
frontend/public/imgs/docs/mobile/usday1-3.png
Normal file
|
After Width: | Height: | Size: 16 KiB |
BIN
frontend/public/imgs/docs/mobile/usweb1-1.png
Normal file
|
After Width: | Height: | Size: 56 KiB |
BIN
frontend/public/imgs/docs/mobile/usweb1-2.png
Normal file
|
After Width: | Height: | Size: 70 KiB |
BIN
frontend/public/imgs/docs/mobile/usweb1-3.png
Normal file
|
After Width: | Height: | Size: 17 KiB |
BIN
frontend/public/stage-now/test2-stageNow.pdf
Normal file
BIN
frontend/public/stage-now/usday1-stageNow.pdf
Normal file
BIN
frontend/public/stage-now/usweb1-stageNow.pdf
Normal file
@@ -19,11 +19,14 @@ export default function Header() {
|
|||||||
const { data: session } = useSession();
|
const { data: session } = useSession();
|
||||||
const { signOut } = authClient;
|
const { signOut } = authClient;
|
||||||
const router = useRouterState();
|
const router = useRouterState();
|
||||||
|
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const currentPath = router.location.href;
|
const currentPath = router.location.href;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<header className="sticky top-0 z-50 flex w-full items-center border-b bg-background">
|
<header
|
||||||
|
className={`sticky top-0 z-50 flex w-full items-center border-b ${session?.session.impersonatedBy ? "bg-amber-600" : "bg-background"} `}
|
||||||
|
>
|
||||||
<div className="flex justify-between w-full">
|
<div className="flex justify-between w-full">
|
||||||
<div className="flex items-center gap-2 px-4">
|
<div className="flex items-center gap-2 px-4">
|
||||||
<div className="flex flex-row">
|
<div className="flex flex-row">
|
||||||
@@ -48,6 +51,20 @@ export default function Header() {
|
|||||||
<span className="font-semibold text-2xl">Logistics Support Tool</span>
|
<span className="font-semibold text-2xl">Logistics Support Tool</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="m-1 flex gap-1">
|
<div className="m-1 flex gap-1">
|
||||||
|
<div>
|
||||||
|
{session?.session.impersonatedBy && (
|
||||||
|
<Button
|
||||||
|
onClick={async () => {
|
||||||
|
await authClient.admin.stopImpersonating();
|
||||||
|
await authClient.getSession();
|
||||||
|
|
||||||
|
window.location.assign("/lst/app/admin/users");
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Stop Impersonating
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<ModeToggle />
|
<ModeToggle />
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
51
frontend/src/components/NotFound.tsx
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
import { useRouter } from "@tanstack/react-router";
|
||||||
|
|
||||||
|
import { Card, CardContent, CardHeader } from "./ui/card";
|
||||||
|
|
||||||
|
export default function NotFound() {
|
||||||
|
const router = useRouter();
|
||||||
|
|
||||||
|
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`;
|
||||||
|
} else {
|
||||||
|
url = "https://www.youtube.com/watch?v=dQw4w9WgXcQ";
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<div className="flex items-center justify-center bg-background text-foreground">
|
||||||
|
<Card>
|
||||||
|
<CardHeader>
|
||||||
|
<p className="text-2xl">
|
||||||
|
Oops, Looks like you hit a link you shouldn't have
|
||||||
|
</p>
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent>
|
||||||
|
<p className="mt-3 text-muted-foreground">
|
||||||
|
Your have tried to go to a page that you are not authorized to be
|
||||||
|
at.
|
||||||
|
</p>
|
||||||
|
<div className="flex justify-center">
|
||||||
|
<div>
|
||||||
|
<a href={`${url}`} target="_blank" rel="noopener">
|
||||||
|
<b>
|
||||||
|
<strong>OLD - LST Home</strong>
|
||||||
|
</b>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
className="w-64"
|
||||||
|
onClick={() => router.navigate({ to: "/", replace: true })}
|
||||||
|
>
|
||||||
|
<strong>Home</strong>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
|
import { useSuspenseQuery } from "@tanstack/react-query";
|
||||||
import { Link } from "@tanstack/react-router";
|
import { Link } from "@tanstack/react-router";
|
||||||
import { Bell, Logs, Server, Settings, UsersRound } from "lucide-react";
|
import { Bell, Logs, Server, Settings, UsersRound } from "lucide-react";
|
||||||
|
import { getSettings } from "../../lib/queries/getSettings";
|
||||||
import {
|
import {
|
||||||
SidebarGroup,
|
SidebarGroup,
|
||||||
SidebarGroupContent,
|
SidebarGroupContent,
|
||||||
@@ -23,6 +24,7 @@ import {
|
|||||||
|
|
||||||
export default function AdminSidebar({ session }: any) {
|
export default function AdminSidebar({ session }: any) {
|
||||||
const { setOpen } = useSidebar();
|
const { setOpen } = useSidebar();
|
||||||
|
const { data: settings, isLoading } = useSuspenseQuery(getSettings());
|
||||||
const items = [
|
const items = [
|
||||||
{
|
{
|
||||||
title: "Notifications",
|
title: "Notifications",
|
||||||
@@ -70,7 +72,9 @@ export default function AdminSidebar({ session }: any) {
|
|||||||
icon: UsersRound,
|
icon: UsersRound,
|
||||||
role: ["systemAdmin", "admin", "manager"],
|
role: ["systemAdmin", "admin", "manager"],
|
||||||
module: "admin",
|
module: "admin",
|
||||||
active: true,
|
active:
|
||||||
|
!isLoading &&
|
||||||
|
settings.filter((n: any) => n.name === "mobile")[0].active,
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
return (
|
return (
|
||||||
@@ -80,7 +84,7 @@ export default function AdminSidebar({ session }: any) {
|
|||||||
<SidebarMenu>
|
<SidebarMenu>
|
||||||
{items.map((item) => (
|
{items.map((item) => (
|
||||||
<div key={item.title}>
|
<div key={item.title}>
|
||||||
{item.role.includes(session.user.role) && (
|
{item.role.includes(session.user.role) && item.active && (
|
||||||
<SidebarMenuItem>
|
<SidebarMenuItem>
|
||||||
<SidebarMenuButton asChild>
|
<SidebarMenuButton asChild>
|
||||||
<Link to={item.url} onClick={() => setOpen(false)}>
|
<Link to={item.url} onClick={() => setOpen(false)}>
|
||||||
|
|||||||
@@ -1,11 +1,12 @@
|
|||||||
|
import { useSuspenseQuery } from "@tanstack/react-query";
|
||||||
import { Link, useRouterState } from "@tanstack/react-router";
|
import { Link, useRouterState } from "@tanstack/react-router";
|
||||||
import { ChevronRight } from "lucide-react";
|
import { ChevronRight, ScrollText } from "lucide-react";
|
||||||
|
import { getSettings } from "../../lib/queries/getSettings";
|
||||||
import {
|
import {
|
||||||
Collapsible,
|
Collapsible,
|
||||||
CollapsibleContent,
|
CollapsibleContent,
|
||||||
CollapsibleTrigger,
|
CollapsibleTrigger,
|
||||||
} from "../ui/collapsible";
|
} from "../ui/collapsible";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
SidebarGroup,
|
SidebarGroup,
|
||||||
SidebarGroupContent,
|
SidebarGroupContent,
|
||||||
@@ -19,43 +20,55 @@ import {
|
|||||||
useSidebar,
|
useSidebar,
|
||||||
} from "../ui/sidebar";
|
} from "../ui/sidebar";
|
||||||
|
|
||||||
const docs = [
|
|
||||||
{
|
|
||||||
title: "Notifications",
|
|
||||||
url: "/intro",
|
|
||||||
//icon,
|
|
||||||
isActive: window.location.pathname.includes("notifications") ?? false,
|
|
||||||
items: [
|
|
||||||
{
|
|
||||||
title: "Reprints",
|
|
||||||
url: "/reprints",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: "New Blocking order",
|
|
||||||
url: "/qualityBlocking",
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: "Mobile",
|
|
||||||
url: "/updateInstructions",
|
|
||||||
isActive: false,
|
|
||||||
items: [
|
|
||||||
{
|
|
||||||
title: "Settings",
|
|
||||||
url: "/mobile-settings",
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
];
|
|
||||||
export default function DocBar() {
|
export default function DocBar() {
|
||||||
const { setOpen } = useSidebar();
|
const { setOpen } = useSidebar();
|
||||||
|
const { data: settings, isLoading } = useSuspenseQuery(getSettings());
|
||||||
const pathname = useRouterState({
|
const pathname = useRouterState({
|
||||||
select: (s) => s.location.pathname,
|
select: (s) => s.location.pathname,
|
||||||
});
|
});
|
||||||
|
|
||||||
const isNotifications = pathname.includes("notifications");
|
const isNotifications = pathname.includes("notifications");
|
||||||
|
|
||||||
|
const docs = [
|
||||||
|
{
|
||||||
|
title: "Notifications",
|
||||||
|
url: "notifications/intro",
|
||||||
|
//icon,
|
||||||
|
isActive: true,
|
||||||
|
items: [
|
||||||
|
{
|
||||||
|
title: "Reprints",
|
||||||
|
icon: ScrollText,
|
||||||
|
url: "notifications/reprints",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "New Blocking order",
|
||||||
|
icon: ScrollText,
|
||||||
|
url: "notifications/qualityBlocking",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Mobile",
|
||||||
|
url: "mobile/updateInstructions",
|
||||||
|
isActive:
|
||||||
|
!isLoading &&
|
||||||
|
settings.filter((n: any) => n.name === "mobile")[0].active,
|
||||||
|
items: [
|
||||||
|
{
|
||||||
|
title: "Update Instructions",
|
||||||
|
icon: ScrollText,
|
||||||
|
url: "mobile/updateInstructions",
|
||||||
|
},
|
||||||
|
// {
|
||||||
|
// title: "Settings",
|
||||||
|
// icon: ScrollText,
|
||||||
|
// url: "mobile/mobile-settings",
|
||||||
|
// },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<SidebarGroup>
|
<SidebarGroup>
|
||||||
<SidebarGroupLabel>Docs</SidebarGroupLabel>
|
<SidebarGroupLabel>Docs</SidebarGroupLabel>
|
||||||
@@ -72,42 +85,44 @@ export default function DocBar() {
|
|||||||
</SidebarMenu>
|
</SidebarMenu>
|
||||||
<SidebarMenu>
|
<SidebarMenu>
|
||||||
{docs.map((item) => (
|
{docs.map((item) => (
|
||||||
<Collapsible
|
<div key={item.title}>
|
||||||
key={item.title}
|
{item.isActive && (
|
||||||
asChild
|
<Collapsible
|
||||||
defaultOpen={isNotifications}
|
asChild
|
||||||
className="group/collapsible"
|
defaultOpen={isNotifications}
|
||||||
>
|
className="group/collapsible"
|
||||||
<SidebarMenuItem>
|
>
|
||||||
<CollapsibleTrigger asChild>
|
<SidebarMenuItem>
|
||||||
<SidebarMenuButton tooltip={item.title}>
|
<CollapsibleTrigger asChild>
|
||||||
<Link
|
<SidebarMenuButton tooltip={item.title}>
|
||||||
to={"/docs/$"}
|
<Link to={"/docs/$"} params={{ _splat: `${item.url}` }}>
|
||||||
params={{ _splat: `notifications${item.url}` }}
|
{item.title}
|
||||||
>
|
</Link>
|
||||||
{item.title}
|
<ChevronRight className="ml-auto transition-transform duration-200 group-data-[state=open]/collapsible:rotate-90" />
|
||||||
</Link>
|
</SidebarMenuButton>
|
||||||
<ChevronRight className="ml-auto transition-transform duration-200 group-data-[state=open]/collapsible:rotate-90" />
|
</CollapsibleTrigger>
|
||||||
</SidebarMenuButton>
|
<CollapsibleContent>
|
||||||
</CollapsibleTrigger>
|
<SidebarMenuSub>
|
||||||
<CollapsibleContent>
|
{item.items?.map((subItem) => (
|
||||||
<SidebarMenuSub>
|
<SidebarMenuSubItem key={subItem.title}>
|
||||||
{item.items?.map((subItem) => (
|
<SidebarMenuSubButton asChild>
|
||||||
<SidebarMenuSubItem key={subItem.title}>
|
<Link
|
||||||
<SidebarMenuSubButton asChild>
|
to={"/docs/$"}
|
||||||
<Link
|
params={{ _splat: `${subItem.url}` }}
|
||||||
to={"/docs/$"}
|
onClick={() => setOpen(false)}
|
||||||
params={{ _splat: `notifications${subItem.url}` }}
|
>
|
||||||
>
|
<subItem.icon />
|
||||||
{subItem.title}
|
<span>{subItem.title}</span>
|
||||||
</Link>
|
</Link>
|
||||||
</SidebarMenuSubButton>
|
</SidebarMenuSubButton>
|
||||||
</SidebarMenuSubItem>
|
</SidebarMenuSubItem>
|
||||||
))}
|
))}
|
||||||
</SidebarMenuSub>
|
</SidebarMenuSub>
|
||||||
</CollapsibleContent>
|
</CollapsibleContent>
|
||||||
</SidebarMenuItem>
|
</SidebarMenuItem>
|
||||||
</Collapsible>
|
</Collapsible>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
))}
|
))}
|
||||||
</SidebarMenu>
|
</SidebarMenu>
|
||||||
</SidebarGroupContent>
|
</SidebarGroupContent>
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { Link } from "@tanstack/react-router";
|
import { Link } from "@tanstack/react-router";
|
||||||
import { ScanText, ScrollText } from "lucide-react";
|
import { ScanText } from "lucide-react";
|
||||||
import {
|
import {
|
||||||
SidebarGroup,
|
SidebarGroup,
|
||||||
SidebarGroupContent,
|
SidebarGroupContent,
|
||||||
@@ -10,14 +10,14 @@ import {
|
|||||||
useSidebar,
|
useSidebar,
|
||||||
} from "../ui/sidebar";
|
} from "../ui/sidebar";
|
||||||
|
|
||||||
export default function MobileBar({ session }: any) {
|
export default function MobileBar() {
|
||||||
const { setOpen } = useSidebar();
|
const { setOpen } = useSidebar();
|
||||||
const items = [
|
const items = [
|
||||||
{
|
// {
|
||||||
title: "Update Instructions",
|
// title: "Update Instructions",
|
||||||
url: "/",
|
// url: "/",
|
||||||
icon: ScrollText,
|
// icon: ScrollText,
|
||||||
},
|
// },
|
||||||
{
|
{
|
||||||
title: "Scan Log",
|
title: "Scan Log",
|
||||||
url: "/",
|
url: "/",
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import { useSuspenseQuery } from "@tanstack/react-query";
|
||||||
import {
|
import {
|
||||||
Sidebar,
|
Sidebar,
|
||||||
SidebarContent,
|
SidebarContent,
|
||||||
@@ -6,12 +7,14 @@ import {
|
|||||||
SidebarMenuItem,
|
SidebarMenuItem,
|
||||||
} from "@/components/ui/sidebar";
|
} from "@/components/ui/sidebar";
|
||||||
import { useSession } from "@/lib/auth-client";
|
import { useSession } from "@/lib/auth-client";
|
||||||
|
import { getSettings } from "../../lib/queries/getSettings";
|
||||||
import AdminSidebar from "./AdminBar";
|
import AdminSidebar from "./AdminBar";
|
||||||
import DocBar from "./DocBar";
|
import DocBar from "./DocBar";
|
||||||
import MobileBar from "./MobileBar";
|
import MobileBar from "./MobileBar";
|
||||||
|
|
||||||
export function AppSidebar() {
|
export function AppSidebar() {
|
||||||
const { data: session } = useSession();
|
const { data: session } = useSession();
|
||||||
|
const { data: settings, isLoading } = useSuspenseQuery(getSettings());
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Sidebar
|
<Sidebar
|
||||||
@@ -24,7 +27,11 @@ export function AppSidebar() {
|
|||||||
<SidebarMenuItem>
|
<SidebarMenuItem>
|
||||||
<SidebarContent>
|
<SidebarContent>
|
||||||
<DocBar />
|
<DocBar />
|
||||||
<MobileBar session={session} />
|
{!isLoading &&
|
||||||
|
settings.filter((n: any) => n.name === "mobile")[0].active && (
|
||||||
|
<MobileBar />
|
||||||
|
)}
|
||||||
|
|
||||||
{session &&
|
{session &&
|
||||||
(session.user.role === "admin" ||
|
(session.user.role === "admin" ||
|
||||||
session.user.role === "systemAdmin" ||
|
session.user.role === "systemAdmin" ||
|
||||||
|
|||||||
137
frontend/src/docs/mobile/updateInstructions.tsx
Normal file
@@ -0,0 +1,137 @@
|
|||||||
|
import { useMutation } from "@tanstack/react-query";
|
||||||
|
import { Button } from "../../components/ui/button";
|
||||||
|
import { Separator } from "../../components/ui/separator";
|
||||||
|
|
||||||
|
export default function UpdateInstructions() {
|
||||||
|
const getFile = useMutation({
|
||||||
|
mutationFn: async () => {
|
||||||
|
// 1. Fetch the file from the public folder
|
||||||
|
const response = await fetch(
|
||||||
|
`/lst/app/stage-now/${window.LST_CONFIG?.server}-stageNow.pdf`,
|
||||||
|
);
|
||||||
|
if (!response.ok) throw new Error("Network response was not ok");
|
||||||
|
|
||||||
|
// 2. Convert to blob
|
||||||
|
return await response.blob();
|
||||||
|
},
|
||||||
|
onSuccess: (blob) => {
|
||||||
|
// 3. Create a temporary anchor element to trigger download
|
||||||
|
const url = window.URL.createObjectURL(blob);
|
||||||
|
const a = document.createElement("a");
|
||||||
|
a.href = url;
|
||||||
|
a.download = `${window.LST_CONFIG?.server}-stageNow.pdf`; // Desired filename
|
||||||
|
document.body.appendChild(a);
|
||||||
|
a.click();
|
||||||
|
|
||||||
|
// 4. Cleanup
|
||||||
|
window.URL.revokeObjectURL(url);
|
||||||
|
document.body.removeChild(a);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="flex flex-row gap-2">
|
||||||
|
<div className="w-1/2">
|
||||||
|
<div className="flex flex-col gap-1 justify-center">
|
||||||
|
<div>
|
||||||
|
<p className="text-center text-3xl">
|
||||||
|
Updating the lst mobile scanner app
|
||||||
|
</p>
|
||||||
|
<p className="text-center text-sm">
|
||||||
|
NOTE: LST Mobile only works on TC8300
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div className="flex justify-center">
|
||||||
|
<Button
|
||||||
|
onClick={() => getFile.mutate()}
|
||||||
|
disabled={getFile.isPending}
|
||||||
|
>
|
||||||
|
{getFile.isPending ? "Downloading..." : "Get StageNow Codes"}
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<Separator className="m-3" />
|
||||||
|
<div>
|
||||||
|
<p className="text-2xl text-center">
|
||||||
|
How to know the scanner has an update?
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
The bottom part of the scanner will show a red or orange bar
|
||||||
|
indicating there is an update. As shown below
|
||||||
|
</p>
|
||||||
|
<div className="flex flex-row gap-2 justify-center">
|
||||||
|
<div className="w-1/2">
|
||||||
|
<img
|
||||||
|
src="/lst/app/imgs/docs/mobile/critical_update.png"
|
||||||
|
alt="Home"
|
||||||
|
className="max-w-[50%] h-auto"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="w-1/2">
|
||||||
|
<img
|
||||||
|
src="/lst/app/imgs/docs/mobile/update.png"
|
||||||
|
alt="Home"
|
||||||
|
className="max-w-[50%] h-auto"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Separator className="m-3" />
|
||||||
|
<div>
|
||||||
|
<p className="text-2xl text-center">
|
||||||
|
To update the scanner follow the below steps.
|
||||||
|
</p>
|
||||||
|
<p>Step 1) Tap the 3 dots top right of the home screen</p>
|
||||||
|
<img
|
||||||
|
src="/lst/app/imgs/docs/mobile/ehs_homeScreen.png"
|
||||||
|
alt="Home"
|
||||||
|
className="max-w-[25%] h-auto m-3"
|
||||||
|
/>
|
||||||
|
<p>Step 2) Tap tools</p>
|
||||||
|
<img
|
||||||
|
src="/lst/app/imgs/docs/mobile/ehs_menu.png"
|
||||||
|
alt="Home"
|
||||||
|
className="max-w-[25%] h-auto m-3"
|
||||||
|
/>
|
||||||
|
<p>Step 3) Tap Stage Now</p>
|
||||||
|
<img
|
||||||
|
src="/lst/app/imgs/docs/mobile/tools.png"
|
||||||
|
alt="Home"
|
||||||
|
className="max-w-[25%] h-auto m-3"
|
||||||
|
/>
|
||||||
|
<p>
|
||||||
|
Step 4) Scan the 3 barcode's to the right or from the printed sheet
|
||||||
|
</p>
|
||||||
|
<img
|
||||||
|
src="/lst/app/imgs/docs/mobile/stagenow.png"
|
||||||
|
alt="Home"
|
||||||
|
className="max-w-[25%] h-auto m-3"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="w-1/2">
|
||||||
|
<p>Scan Commands</p>
|
||||||
|
<Separator className="m-3" />
|
||||||
|
<div className="flex flex-col justify-center">
|
||||||
|
<img
|
||||||
|
src={`/lst/app/imgs/docs/mobile/${window.LST_CONFIG?.server}-1.png`}
|
||||||
|
alt="Home"
|
||||||
|
className="m-3"
|
||||||
|
/>
|
||||||
|
<img
|
||||||
|
src={`/lst/app/imgs/docs/mobile/${window.LST_CONFIG?.server}-2.png`}
|
||||||
|
alt="Home"
|
||||||
|
className="m-3"
|
||||||
|
/>
|
||||||
|
<img
|
||||||
|
src={`/lst/app/imgs/docs/mobile/${window.LST_CONFIG?.server}-3.png`}
|
||||||
|
alt="Home"
|
||||||
|
className="m-3"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
export default function updateInstructions() {
|
|
||||||
return <div>updateInstructions</div>;
|
|
||||||
}
|
|
||||||
40
frontend/src/lib/apiHelper.ts
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
import type { Router } from "@tanstack/react-router";
|
||||||
|
import axios from "axios";
|
||||||
|
import { toast } from "sonner";
|
||||||
|
|
||||||
|
let appRouter: Router<any, any> | null = null;
|
||||||
|
|
||||||
|
export function setApiRouter(router: Router<any, any>) {
|
||||||
|
appRouter = router;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const api = axios.create({
|
||||||
|
baseURL: "/lst/api",
|
||||||
|
withCredentials: true,
|
||||||
|
timeout: 15000,
|
||||||
|
});
|
||||||
|
|
||||||
|
api.interceptors.response.use(
|
||||||
|
(response) => response,
|
||||||
|
(error) => {
|
||||||
|
const isNetworkError =
|
||||||
|
error.code === "ERR_NETWORK" ||
|
||||||
|
error.code === "ECONNABORTED" ||
|
||||||
|
error.message === "Network Error" ||
|
||||||
|
error.message === "Failed to fetch" ||
|
||||||
|
!error.response;
|
||||||
|
|
||||||
|
if (error.response?.status === 403) {
|
||||||
|
// redirect, toast, or show forbidden page
|
||||||
|
toast.error("Unauthorized to be here");
|
||||||
|
|
||||||
|
appRouter?.navigate({ to: "/forbidden", replace: true });
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isNetworkError) {
|
||||||
|
appRouter?.navigate({ to: "/app-down", replace: true });
|
||||||
|
}
|
||||||
|
|
||||||
|
return Promise.reject(error);
|
||||||
|
},
|
||||||
|
);
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
|
import { redirect } from "@tanstack/react-router";
|
||||||
import { adminClient, genericOAuthClient } from "better-auth/client/plugins";
|
import { adminClient, genericOAuthClient } from "better-auth/client/plugins";
|
||||||
import { createAuthClient } from "better-auth/react";
|
import { createAuthClient } from "better-auth/react";
|
||||||
import { ac, admin, systemAdmin, user } from "./auth-permissions";
|
import { ac, admin, manager, systemAdmin, user } from "./auth-permissions";
|
||||||
|
|
||||||
export const authClient = createAuthClient({
|
export const authClient = createAuthClient({
|
||||||
baseURL: `${window.location.origin}/lst/api/auth`,
|
baseURL: `${window.location.origin}/lst/api/auth`,
|
||||||
@@ -10,11 +11,20 @@ export const authClient = createAuthClient({
|
|||||||
roles: {
|
roles: {
|
||||||
admin,
|
admin,
|
||||||
user,
|
user,
|
||||||
|
manager,
|
||||||
systemAdmin,
|
systemAdmin,
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
genericOAuthClient(),
|
genericOAuthClient(),
|
||||||
],
|
],
|
||||||
|
fetchOptions: {
|
||||||
|
onError() {
|
||||||
|
redirect({
|
||||||
|
to: "/app-down",
|
||||||
|
replace: true,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
export const { useSession, signUp, signIn, signOut } = authClient;
|
export const { useSession, signUp, signIn, signOut } = authClient;
|
||||||
|
|||||||
@@ -1,21 +1,53 @@
|
|||||||
import { createAccessControl } from "better-auth/plugins/access";
|
import { createAccessControl } from "better-auth/plugins/access";
|
||||||
|
import { adminAc } from "better-auth/plugins/admin/access";
|
||||||
|
|
||||||
export const statement = {
|
export const statement = {
|
||||||
project: ["create", "share", "update", "delete"],
|
app: ["read", "create", "share", "update", "delete", "readAll"],
|
||||||
user: ["ban"],
|
//user: ["ban"],
|
||||||
|
quality: ["read", "create", "share", "update", "delete", "readAll"],
|
||||||
|
logistics: ["read", "create", "share", "update", "delete", "readAll"],
|
||||||
|
mobile: ["read", "create", "share", "update", "delete", "readAll"],
|
||||||
|
notifications: ["read", "create", "share", "update", "delete", "readAll"],
|
||||||
} as const;
|
} as const;
|
||||||
|
|
||||||
export const ac = createAccessControl(statement);
|
export const ac = createAccessControl(statement);
|
||||||
|
|
||||||
export const user = ac.newRole({
|
export const user = ac.newRole({
|
||||||
project: ["create"],
|
app: ["read", "create"],
|
||||||
|
notifications: ["read", "create"],
|
||||||
|
});
|
||||||
|
|
||||||
|
export const manager = ac.newRole({
|
||||||
|
app: ["read", "create", "update"],
|
||||||
});
|
});
|
||||||
|
|
||||||
export const admin = ac.newRole({
|
export const admin = ac.newRole({
|
||||||
project: ["create", "update"],
|
app: ["read", "create", "update"],
|
||||||
});
|
});
|
||||||
|
|
||||||
export const systemAdmin = ac.newRole({
|
export const systemAdmin = ac.newRole({
|
||||||
project: ["create", "update", "delete"],
|
app: ["read", "create", "share", "update", "delete", "readAll"],
|
||||||
user: ["ban"],
|
//user: ["ban"],
|
||||||
|
quality: ["read", "create", "share", "update", "delete", "readAll"],
|
||||||
|
mobile: ["read", "create", "share", "update", "delete", "readAll"],
|
||||||
|
logistics: ["read", "create", "share", "update", "delete", "readAll"],
|
||||||
|
notifications: ["read", "create", "share", "update", "delete", "readAll"],
|
||||||
|
...adminAc.statements,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
/* example usage
|
||||||
|
const canCreateProject = await authClient.admin.hasPermission({
|
||||||
|
permissions: {
|
||||||
|
project: ["create"],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
// You can also check multiple resource permissions at the same time
|
||||||
|
const canCreateProjectAndCreateSale = await authClient.admin.hasPermission({
|
||||||
|
permissions: {
|
||||||
|
project: ["create"],
|
||||||
|
sale: ["create"]
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
*/
|
||||||
|
|||||||
@@ -13,9 +13,7 @@ const docsMap: Record<string, ComponentType> = {};
|
|||||||
for (const path in modules) {
|
for (const path in modules) {
|
||||||
const mod = modules[path] as DocModule;
|
const mod = modules[path] as DocModule;
|
||||||
|
|
||||||
const slug = path
|
const slug = path.replace("../docs/", "").replace(".tsx", "");
|
||||||
.replace("../docs/", "")
|
|
||||||
.replace(".tsx", "");
|
|
||||||
|
|
||||||
// "notifications/intro"
|
// "notifications/intro"
|
||||||
docsMap[slug] = mod.default;
|
docsMap[slug] = mod.default;
|
||||||
@@ -23,4 +21,4 @@ for (const path in modules) {
|
|||||||
|
|
||||||
export function getDoc(slug: string) {
|
export function getDoc(slug: string) {
|
||||||
return docsMap[slug];
|
return docsMap[slug];
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,25 +1,25 @@
|
|||||||
import { keepPreviousData, queryOptions } from "@tanstack/react-query";
|
import { keepPreviousData, queryOptions } from "@tanstack/react-query";
|
||||||
import axios from "axios";
|
import { api } from "../apiHelper";
|
||||||
|
|
||||||
export function getScanUsers() {
|
export function getScanUsers() {
|
||||||
return queryOptions({
|
return queryOptions({
|
||||||
queryKey: ["getScanUsers"],
|
queryKey: ["getScanUsers"],
|
||||||
queryFn: () => fetch(),
|
queryFn: () => dataFetch(),
|
||||||
staleTime: 5000,
|
staleTime: 5000,
|
||||||
refetchOnWindowFocus: true,
|
refetchOnWindowFocus: true,
|
||||||
placeholderData: keepPreviousData,
|
placeholderData: keepPreviousData,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const fetch = async () => {
|
const dataFetch = async () => {
|
||||||
if (window.location.hostname === "localhost") {
|
if (window.location.hostname === "localhost") {
|
||||||
await new Promise((res) => setTimeout(res, 1500));
|
await new Promise((res) => setTimeout(res, 1500));
|
||||||
}
|
}
|
||||||
|
|
||||||
const { data } = await axios.get("/lst/api/mobile/auth/user", {
|
const { data } = await api.get("/mobile/auth/user");
|
||||||
withCredentials: true,
|
if (!data.success) {
|
||||||
timeout: 5000,
|
throw new Error(data.message ?? "Failed to load scan users");
|
||||||
});
|
}
|
||||||
|
|
||||||
return data.data;
|
return data.data ?? [];
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { keepPreviousData, queryOptions } from "@tanstack/react-query";
|
import { keepPreviousData, queryOptions } from "@tanstack/react-query";
|
||||||
import axios from "axios";
|
|
||||||
|
import { api } from "../apiHelper";
|
||||||
|
|
||||||
export function getScannerIds() {
|
export function getScannerIds() {
|
||||||
return queryOptions({
|
return queryOptions({
|
||||||
@@ -16,10 +17,7 @@ const fetch = async () => {
|
|||||||
await new Promise((res) => setTimeout(res, 1500));
|
await new Promise((res) => setTimeout(res, 1500));
|
||||||
}
|
}
|
||||||
|
|
||||||
const { data } = await axios.get("/lst/api/mobile/available", {
|
const { data } = await api.get("/mobile/available");
|
||||||
withCredentials: true,
|
|
||||||
timeout: 5000,
|
|
||||||
});
|
|
||||||
|
|
||||||
return data.data;
|
return data.data;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { keepPreviousData, queryOptions } from "@tanstack/react-query";
|
import { keepPreviousData, queryOptions } from "@tanstack/react-query";
|
||||||
import axios from "axios";
|
|
||||||
|
import { api } from "../apiHelper";
|
||||||
|
|
||||||
export function getSettings() {
|
export function getSettings() {
|
||||||
return queryOptions({
|
return queryOptions({
|
||||||
@@ -16,7 +17,7 @@ const fetch = async () => {
|
|||||||
await new Promise((res) => setTimeout(res, 1500));
|
await new Promise((res) => setTimeout(res, 1500));
|
||||||
}
|
}
|
||||||
|
|
||||||
const { data } = await axios.get("/lst/api/settings");
|
const { data } = await api.get("/settings");
|
||||||
|
|
||||||
return data.data;
|
return data.data;
|
||||||
};
|
};
|
||||||
|
|||||||
40
frontend/src/lib/queries/getUsers.ts
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
import { keepPreviousData, queryOptions } from "@tanstack/react-query";
|
||||||
|
|
||||||
|
import { authClient } from "../auth-client";
|
||||||
|
|
||||||
|
export function getUsers() {
|
||||||
|
return queryOptions({
|
||||||
|
queryKey: ["getUsers"],
|
||||||
|
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, error } = await authClient.admin.listUsers({
|
||||||
|
query: {
|
||||||
|
// searchValue: "some name",
|
||||||
|
// searchField: "name",
|
||||||
|
// searchOperator: "contains",
|
||||||
|
limit: 100,
|
||||||
|
offset: 0,
|
||||||
|
sortBy: "name",
|
||||||
|
// sortDirection: "desc",
|
||||||
|
// filterField: "email",
|
||||||
|
// filterValue: "hello@example.com",
|
||||||
|
// filterOperator: "eq",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
if (error) {
|
||||||
|
return error;
|
||||||
|
}
|
||||||
|
|
||||||
|
return data.users;
|
||||||
|
};
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
import { keepPreviousData, queryOptions } from "@tanstack/react-query";
|
import { keepPreviousData, queryOptions } from "@tanstack/react-query";
|
||||||
import axios from "axios";
|
|
||||||
|
import { api } from "../apiHelper";
|
||||||
|
|
||||||
export function notificationSubs(userId?: string) {
|
export function notificationSubs(userId?: string) {
|
||||||
return queryOptions({
|
return queryOptions({
|
||||||
@@ -16,8 +17,8 @@ const fetch = async (userId?: string) => {
|
|||||||
await new Promise((res) => setTimeout(res, 1500));
|
await new Promise((res) => setTimeout(res, 1500));
|
||||||
}
|
}
|
||||||
|
|
||||||
const { data } = await axios.get(
|
const { data } = await api.get(
|
||||||
`/lst/api/notification/sub${userId ? `?userId=${userId}` : ""}`,
|
`/notification/sub${userId ? `?userId=${userId}` : ""}`,
|
||||||
);
|
);
|
||||||
|
|
||||||
return data.data;
|
return data.data;
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { keepPreviousData, queryOptions } from "@tanstack/react-query";
|
import { keepPreviousData, queryOptions } from "@tanstack/react-query";
|
||||||
import axios from "axios";
|
|
||||||
|
import { api } from "../apiHelper";
|
||||||
|
|
||||||
export function notifications() {
|
export function notifications() {
|
||||||
return queryOptions({
|
return queryOptions({
|
||||||
@@ -16,7 +17,7 @@ const fetch = async () => {
|
|||||||
await new Promise((res) => setTimeout(res, 1500));
|
await new Promise((res) => setTimeout(res, 1500));
|
||||||
}
|
}
|
||||||
|
|
||||||
const { data } = await axios.get("/lst/api/notification");
|
const { data } = await api.get("/notification");
|
||||||
|
|
||||||
return data.data;
|
return data.data;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { keepPreviousData, queryOptions } from "@tanstack/react-query";
|
import { keepPreviousData, queryOptions } from "@tanstack/react-query";
|
||||||
import axios from "axios";
|
|
||||||
|
import { api } from "../apiHelper";
|
||||||
|
|
||||||
export function servers() {
|
export function servers() {
|
||||||
return queryOptions({
|
return queryOptions({
|
||||||
@@ -16,7 +17,7 @@ const fetch = async () => {
|
|||||||
await new Promise((res) => setTimeout(res, 1500));
|
await new Promise((res) => setTimeout(res, 1500));
|
||||||
}
|
}
|
||||||
|
|
||||||
const { data } = await axios.get("/lst/api/servers");
|
const { data } = await api.get("/servers");
|
||||||
|
|
||||||
return data.data;
|
return data.data;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -3,6 +3,8 @@ import { StrictMode } from "react";
|
|||||||
import ReactDOM from "react-dom/client";
|
import ReactDOM from "react-dom/client";
|
||||||
import "./index.css";
|
import "./index.css";
|
||||||
import { createRouter, RouterProvider } from "@tanstack/react-router";
|
import { createRouter, RouterProvider } from "@tanstack/react-router";
|
||||||
|
import NotFound from "./components/NotFound";
|
||||||
|
import { setApiRouter } from "./lib/apiHelper";
|
||||||
import socket from "./lib/socket.io";
|
import socket from "./lib/socket.io";
|
||||||
import { loadUmami } from "./lib/umami.utils";
|
import { loadUmami } from "./lib/umami.utils";
|
||||||
// Import the generated route tree
|
// Import the generated route tree
|
||||||
@@ -13,8 +15,9 @@ const queryClient = new QueryClient({
|
|||||||
defaultOptions: {
|
defaultOptions: {
|
||||||
queries: {
|
queries: {
|
||||||
staleTime: 1000 * 60 * 5,
|
staleTime: 1000 * 60 * 5,
|
||||||
retry: 0,
|
retry: 2,
|
||||||
refetchOnWindowFocus: true,
|
refetchOnWindowFocus: true,
|
||||||
|
retryDelay: (attempt) => Math.min(1000 * 2 ** attempt, 5000),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
@@ -27,8 +30,11 @@ const router = createRouter({
|
|||||||
context: {
|
context: {
|
||||||
queryClient,
|
queryClient,
|
||||||
},
|
},
|
||||||
|
defaultNotFoundComponent: NotFound,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
setApiRouter(router);
|
||||||
|
|
||||||
// Register the router instance for type safety
|
// Register the router instance for type safety
|
||||||
declare module "@tanstack/react-router" {
|
declare module "@tanstack/react-router" {
|
||||||
interface Register {
|
interface Register {
|
||||||
|
|||||||
@@ -9,6 +9,8 @@
|
|||||||
// Additionally, you should also exclude this file from your linter and/or formatter to prevent it from being checked or modified.
|
// Additionally, you should also exclude this file from your linter and/or formatter to prevent it from being checked or modified.
|
||||||
|
|
||||||
import { Route as rootRouteImport } from './routes/__root'
|
import { Route as rootRouteImport } from './routes/__root'
|
||||||
|
import { Route as ForbiddenRouteImport } from './routes/forbidden'
|
||||||
|
import { Route as AppDownRouteImport } from './routes/app-down'
|
||||||
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 DocsIndexRouteImport } from './routes/docs/index'
|
import { Route as DocsIndexRouteImport } from './routes/docs/index'
|
||||||
@@ -24,6 +26,16 @@ import { Route as authUserSignupRouteImport } from './routes/(auth)/user.signup'
|
|||||||
import { Route as authUserResetpasswordRouteImport } from './routes/(auth)/user.resetpassword'
|
import { Route as authUserResetpasswordRouteImport } from './routes/(auth)/user.resetpassword'
|
||||||
import { Route as authUserProfileRouteImport } from './routes/(auth)/user.profile'
|
import { Route as authUserProfileRouteImport } from './routes/(auth)/user.profile'
|
||||||
|
|
||||||
|
const ForbiddenRoute = ForbiddenRouteImport.update({
|
||||||
|
id: '/forbidden',
|
||||||
|
path: '/forbidden',
|
||||||
|
getParentRoute: () => rootRouteImport,
|
||||||
|
} as any)
|
||||||
|
const AppDownRoute = AppDownRouteImport.update({
|
||||||
|
id: '/app-down',
|
||||||
|
path: '/app-down',
|
||||||
|
getParentRoute: () => rootRouteImport,
|
||||||
|
} as any)
|
||||||
const AboutRoute = AboutRouteImport.update({
|
const AboutRoute = AboutRouteImport.update({
|
||||||
id: '/about',
|
id: '/about',
|
||||||
path: '/about',
|
path: '/about',
|
||||||
@@ -98,6 +110,8 @@ const authUserProfileRoute = authUserProfileRouteImport.update({
|
|||||||
export interface FileRoutesByFullPath {
|
export interface FileRoutesByFullPath {
|
||||||
'/': typeof IndexRoute
|
'/': typeof IndexRoute
|
||||||
'/about': typeof AboutRoute
|
'/about': typeof AboutRoute
|
||||||
|
'/app-down': typeof AppDownRoute
|
||||||
|
'/forbidden': typeof ForbiddenRoute
|
||||||
'/login': typeof authLoginRoute
|
'/login': typeof authLoginRoute
|
||||||
'/admin/logs': typeof AdminLogsRoute
|
'/admin/logs': typeof AdminLogsRoute
|
||||||
'/admin/notifications': typeof AdminNotificationsRoute
|
'/admin/notifications': typeof AdminNotificationsRoute
|
||||||
@@ -114,6 +128,8 @@ export interface FileRoutesByFullPath {
|
|||||||
export interface FileRoutesByTo {
|
export interface FileRoutesByTo {
|
||||||
'/': typeof IndexRoute
|
'/': typeof IndexRoute
|
||||||
'/about': typeof AboutRoute
|
'/about': typeof AboutRoute
|
||||||
|
'/app-down': typeof AppDownRoute
|
||||||
|
'/forbidden': typeof ForbiddenRoute
|
||||||
'/login': typeof authLoginRoute
|
'/login': typeof authLoginRoute
|
||||||
'/admin/logs': typeof AdminLogsRoute
|
'/admin/logs': typeof AdminLogsRoute
|
||||||
'/admin/notifications': typeof AdminNotificationsRoute
|
'/admin/notifications': typeof AdminNotificationsRoute
|
||||||
@@ -131,6 +147,8 @@ export interface FileRoutesById {
|
|||||||
__root__: typeof rootRouteImport
|
__root__: typeof rootRouteImport
|
||||||
'/': typeof IndexRoute
|
'/': typeof IndexRoute
|
||||||
'/about': typeof AboutRoute
|
'/about': typeof AboutRoute
|
||||||
|
'/app-down': typeof AppDownRoute
|
||||||
|
'/forbidden': typeof ForbiddenRoute
|
||||||
'/(auth)/login': typeof authLoginRoute
|
'/(auth)/login': typeof authLoginRoute
|
||||||
'/admin/logs': typeof AdminLogsRoute
|
'/admin/logs': typeof AdminLogsRoute
|
||||||
'/admin/notifications': typeof AdminNotificationsRoute
|
'/admin/notifications': typeof AdminNotificationsRoute
|
||||||
@@ -149,6 +167,8 @@ export interface FileRouteTypes {
|
|||||||
fullPaths:
|
fullPaths:
|
||||||
| '/'
|
| '/'
|
||||||
| '/about'
|
| '/about'
|
||||||
|
| '/app-down'
|
||||||
|
| '/forbidden'
|
||||||
| '/login'
|
| '/login'
|
||||||
| '/admin/logs'
|
| '/admin/logs'
|
||||||
| '/admin/notifications'
|
| '/admin/notifications'
|
||||||
@@ -165,6 +185,8 @@ export interface FileRouteTypes {
|
|||||||
to:
|
to:
|
||||||
| '/'
|
| '/'
|
||||||
| '/about'
|
| '/about'
|
||||||
|
| '/app-down'
|
||||||
|
| '/forbidden'
|
||||||
| '/login'
|
| '/login'
|
||||||
| '/admin/logs'
|
| '/admin/logs'
|
||||||
| '/admin/notifications'
|
| '/admin/notifications'
|
||||||
@@ -181,6 +203,8 @@ export interface FileRouteTypes {
|
|||||||
| '__root__'
|
| '__root__'
|
||||||
| '/'
|
| '/'
|
||||||
| '/about'
|
| '/about'
|
||||||
|
| '/app-down'
|
||||||
|
| '/forbidden'
|
||||||
| '/(auth)/login'
|
| '/(auth)/login'
|
||||||
| '/admin/logs'
|
| '/admin/logs'
|
||||||
| '/admin/notifications'
|
| '/admin/notifications'
|
||||||
@@ -198,6 +222,8 @@ export interface FileRouteTypes {
|
|||||||
export interface RootRouteChildren {
|
export interface RootRouteChildren {
|
||||||
IndexRoute: typeof IndexRoute
|
IndexRoute: typeof IndexRoute
|
||||||
AboutRoute: typeof AboutRoute
|
AboutRoute: typeof AboutRoute
|
||||||
|
AppDownRoute: typeof AppDownRoute
|
||||||
|
ForbiddenRoute: typeof ForbiddenRoute
|
||||||
authLoginRoute: typeof authLoginRoute
|
authLoginRoute: typeof authLoginRoute
|
||||||
AdminLogsRoute: typeof AdminLogsRoute
|
AdminLogsRoute: typeof AdminLogsRoute
|
||||||
AdminNotificationsRoute: typeof AdminNotificationsRoute
|
AdminNotificationsRoute: typeof AdminNotificationsRoute
|
||||||
@@ -214,6 +240,20 @@ export interface RootRouteChildren {
|
|||||||
|
|
||||||
declare module '@tanstack/react-router' {
|
declare module '@tanstack/react-router' {
|
||||||
interface FileRoutesByPath {
|
interface FileRoutesByPath {
|
||||||
|
'/forbidden': {
|
||||||
|
id: '/forbidden'
|
||||||
|
path: '/forbidden'
|
||||||
|
fullPath: '/forbidden'
|
||||||
|
preLoaderRoute: typeof ForbiddenRouteImport
|
||||||
|
parentRoute: typeof rootRouteImport
|
||||||
|
}
|
||||||
|
'/app-down': {
|
||||||
|
id: '/app-down'
|
||||||
|
path: '/app-down'
|
||||||
|
fullPath: '/app-down'
|
||||||
|
preLoaderRoute: typeof AppDownRouteImport
|
||||||
|
parentRoute: typeof rootRouteImport
|
||||||
|
}
|
||||||
'/about': {
|
'/about': {
|
||||||
id: '/about'
|
id: '/about'
|
||||||
path: '/about'
|
path: '/about'
|
||||||
@@ -318,6 +358,8 @@ declare module '@tanstack/react-router' {
|
|||||||
const rootRouteChildren: RootRouteChildren = {
|
const rootRouteChildren: RootRouteChildren = {
|
||||||
IndexRoute: IndexRoute,
|
IndexRoute: IndexRoute,
|
||||||
AboutRoute: AboutRoute,
|
AboutRoute: AboutRoute,
|
||||||
|
AppDownRoute: AppDownRoute,
|
||||||
|
ForbiddenRoute: ForbiddenRoute,
|
||||||
authLoginRoute: authLoginRoute,
|
authLoginRoute: authLoginRoute,
|
||||||
AdminLogsRoute: AdminLogsRoute,
|
AdminLogsRoute: AdminLogsRoute,
|
||||||
AdminNotificationsRoute: AdminNotificationsRoute,
|
AdminNotificationsRoute: AdminNotificationsRoute,
|
||||||
|
|||||||
@@ -45,14 +45,14 @@ export default function NotificationsSubCard({ user }: any) {
|
|||||||
|
|
||||||
let n: any = [];
|
let n: any = [];
|
||||||
if (data) {
|
if (data) {
|
||||||
n = data.map((i: any) => ({
|
n = data
|
||||||
label: i.name,
|
.filter((n: any) => n.active)
|
||||||
value: i.id,
|
.map((i: any) => ({
|
||||||
}));
|
label: i.name,
|
||||||
|
value: i.id,
|
||||||
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log(n);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<Card className="p-3 w-lg">
|
<Card className="p-3 w-lg">
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ 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 { Spinner } from "../../components/ui/spinner";
|
||||||
import ChangePassword from "./-components/ChangePassword";
|
import ChangePassword from "./-components/ChangePassword";
|
||||||
import NotificationsSubCard from "./-components/NotificationsSubCard";
|
import NotificationsSubCard from "./-components/NotificationsSubCard";
|
||||||
@@ -37,6 +38,7 @@ export const Route = createFileRoute("/(auth)/user/profile")({
|
|||||||
|
|
||||||
function RouteComponent() {
|
function RouteComponent() {
|
||||||
const { data: session } = useSession();
|
const { data: session } = useSession();
|
||||||
|
|
||||||
const form = useAppForm({
|
const form = useAppForm({
|
||||||
defaultValues: {
|
defaultValues: {
|
||||||
name: session?.user.name,
|
name: session?.user.name,
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import { createFileRoute, redirect } from "@tanstack/react-router";
|
|||||||
import { createColumnHelper } from "@tanstack/react-table";
|
import { createColumnHelper } from "@tanstack/react-table";
|
||||||
import axios from "axios";
|
import axios from "axios";
|
||||||
import { format } from "date-fns-tz";
|
import { format } from "date-fns-tz";
|
||||||
import { CircleFadingArrowUp, Trash } from "lucide-react";
|
import { Trash } from "lucide-react";
|
||||||
import { Suspense, useState } from "react";
|
import { Suspense, useState } from "react";
|
||||||
import { toast } from "sonner";
|
import { toast } from "sonner";
|
||||||
import { Button } from "../../components/ui/button";
|
import { Button } from "../../components/ui/button";
|
||||||
@@ -19,7 +19,7 @@ import NewScanUser from "./-components/NewScanUser";
|
|||||||
export const Route = createFileRoute("/admin/scanUsers")({
|
export const Route = createFileRoute("/admin/scanUsers")({
|
||||||
beforeLoad: async ({ location }) => {
|
beforeLoad: async ({ location }) => {
|
||||||
const { data: session } = await authClient.getSession();
|
const { data: session } = await authClient.getSession();
|
||||||
const allowedRole = ["systemAdmin", "admin"];
|
const allowedRole = ["systemAdmin", "admin", "manager"];
|
||||||
|
|
||||||
if (!session?.user) {
|
if (!session?.user) {
|
||||||
throw redirect({
|
throw redirect({
|
||||||
@@ -111,7 +111,7 @@ const ScanUserTable = () => {
|
|||||||
<div>
|
<div>
|
||||||
<EditableCellInput
|
<EditableCellInput
|
||||||
value={getValue()}
|
value={getValue()}
|
||||||
id={row.original.name}
|
id={row.original.id}
|
||||||
field="value"
|
field="value"
|
||||||
onSubmit={({ id, field, value }) => {
|
onSubmit={({ id, field, value }) => {
|
||||||
updateSetting.mutate({ id, field, value });
|
updateSetting.mutate({ id, field, value });
|
||||||
|
|||||||
@@ -55,7 +55,17 @@ const ServerTable = () => {
|
|||||||
<SearchableHeader column={column} title="Name" searchable={true} />
|
<SearchableHeader column={column} title="Name" searchable={true} />
|
||||||
),
|
),
|
||||||
filterFn: "includesString",
|
filterFn: "includesString",
|
||||||
cell: (i) => i.getValue(),
|
cell: (i) => (
|
||||||
|
<>
|
||||||
|
<a
|
||||||
|
href={`http://${i.row.original.server}:3000/lst/app`}
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener"
|
||||||
|
>
|
||||||
|
{i.getValue()}
|
||||||
|
</a>
|
||||||
|
</>
|
||||||
|
),
|
||||||
}),
|
}),
|
||||||
columnHelper.accessor("greatPlainsPlantCode", {
|
columnHelper.accessor("greatPlainsPlantCode", {
|
||||||
header: ({ column }) => (
|
header: ({ column }) => (
|
||||||
|
|||||||
156
frontend/src/routes/admin/users.tsx
Normal file
@@ -0,0 +1,156 @@
|
|||||||
|
import { useSuspenseQuery } from "@tanstack/react-query";
|
||||||
|
import { createFileRoute, redirect } from "@tanstack/react-router";
|
||||||
|
import { createColumnHelper } from "@tanstack/react-table";
|
||||||
|
import { format } from "date-fns-tz";
|
||||||
|
import { Suspense } from "react";
|
||||||
|
import { Button } from "../../components/ui/button";
|
||||||
|
import { authClient, useSession } from "../../lib/auth-client";
|
||||||
|
import { getUsers } from "../../lib/queries/getUsers";
|
||||||
|
import LstTable from "../../lib/tableStuff/LstTable";
|
||||||
|
import SearchableHeader from "../../lib/tableStuff/SearchableHeader";
|
||||||
|
import SkellyTable from "../../lib/tableStuff/SkellyTable";
|
||||||
|
import { trackLstEvent } from "../../lib/umami.utils";
|
||||||
|
|
||||||
|
export const Route = createFileRoute("/admin/users")({
|
||||||
|
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,
|
||||||
|
});
|
||||||
|
|
||||||
|
const UserTable = () => {
|
||||||
|
const { data } = useSuspenseQuery(getUsers());
|
||||||
|
const { data: session } = useSession();
|
||||||
|
|
||||||
|
const columnHelper = createColumnHelper<any>();
|
||||||
|
|
||||||
|
const columns = [
|
||||||
|
columnHelper.accessor("name", {
|
||||||
|
header: ({ column }) => (
|
||||||
|
<SearchableHeader column={column} title="Name" searchable={true} />
|
||||||
|
),
|
||||||
|
filterFn: "includesString",
|
||||||
|
cell: (i) => i.getValue(),
|
||||||
|
}),
|
||||||
|
columnHelper.accessor("email", {
|
||||||
|
header: ({ column }) => (
|
||||||
|
<SearchableHeader column={column} title="Email" searchable={true} />
|
||||||
|
),
|
||||||
|
filterFn: "includesString",
|
||||||
|
cell: (i) => i.getValue(),
|
||||||
|
}),
|
||||||
|
columnHelper.accessor("role", {
|
||||||
|
header: ({ column }) => (
|
||||||
|
<SearchableHeader column={column} title="Role" searchable={false} />
|
||||||
|
),
|
||||||
|
filterFn: "includesString",
|
||||||
|
cell: (i) => i.getValue(),
|
||||||
|
}),
|
||||||
|
columnHelper.accessor("updatedAt", {
|
||||||
|
header: ({ column }) => (
|
||||||
|
<SearchableHeader
|
||||||
|
column={column}
|
||||||
|
title="Updated at"
|
||||||
|
searchable={false}
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
filterFn: "includesString",
|
||||||
|
cell: (i) => format(i.getValue(), "M/d/yyyy HH:mm"),
|
||||||
|
}),
|
||||||
|
];
|
||||||
|
|
||||||
|
if (session && session.user.role === "systemAdmin") {
|
||||||
|
columns.push(
|
||||||
|
columnHelper.accessor("banned", {
|
||||||
|
header: ({ column }) => (
|
||||||
|
<SearchableHeader column={column} title="Banned" searchable={false} />
|
||||||
|
),
|
||||||
|
filterFn: "includesString",
|
||||||
|
cell: (i) => <span>{i.getValue() ? "True" : "False"}</span>,
|
||||||
|
}),
|
||||||
|
columnHelper.accessor("impersonate", {
|
||||||
|
header: ({ column }) => (
|
||||||
|
<SearchableHeader
|
||||||
|
column={column}
|
||||||
|
title="Impersonate User"
|
||||||
|
searchable={false}
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
filterFn: "includesString",
|
||||||
|
cell: (i) => {
|
||||||
|
const beSomeone = async () => {
|
||||||
|
trackLstEvent("impersonateUser_click", {
|
||||||
|
module: "users",
|
||||||
|
action: "click",
|
||||||
|
label: "impersonating user",
|
||||||
|
page: window.location.pathname,
|
||||||
|
});
|
||||||
|
|
||||||
|
const { data, error } = await authClient.admin.impersonateUser({
|
||||||
|
userId: i.row.original.id, // required
|
||||||
|
});
|
||||||
|
|
||||||
|
if (data) {
|
||||||
|
await authClient.getSession();
|
||||||
|
window.location.replace("/lst/app");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (error) {
|
||||||
|
console.log(error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const cantImpersonate = ["admin", "systemAdmin"];
|
||||||
|
if (cantImpersonate.includes(i.row.original.role)) return;
|
||||||
|
return <Button onClick={beSomeone}>Become user</Button>;
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return <LstTable data={data} columns={columns} pageSize={50} />;
|
||||||
|
};
|
||||||
|
|
||||||
|
function RouteComponent() {
|
||||||
|
// const createUser = async () => {
|
||||||
|
// const { data: newUser, error } = await authClient.admin.createUser({
|
||||||
|
// email: "cowch@gmail.com", // required
|
||||||
|
// password: "crazypassword", // required
|
||||||
|
// name: "James Smith", // required
|
||||||
|
// role: "manager",
|
||||||
|
// });
|
||||||
|
// };
|
||||||
|
|
||||||
|
// const besomeone = async () => {
|
||||||
|
// const { data, error } = await authClient.admin.impersonateUser({
|
||||||
|
// userId: "iswCNVzQ9cWulbmsaMbeX6e7fV6Eme6t", // required
|
||||||
|
// });
|
||||||
|
|
||||||
|
// await authClient.getSession();
|
||||||
|
// window.location.replace("/lst/app");
|
||||||
|
// };
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Suspense fallback={<SkellyTable />}>
|
||||||
|
<UserTable />
|
||||||
|
</Suspense>
|
||||||
|
);
|
||||||
|
}
|
||||||
51
frontend/src/routes/app-down.tsx
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
import { createFileRoute, useRouter } from "@tanstack/react-router";
|
||||||
|
import z from "zod";
|
||||||
|
import { Button } from "../components/ui/button";
|
||||||
|
import { Card, CardContent, CardHeader } from "../components/ui/card";
|
||||||
|
import { trackLstEvent } from "../lib/umami.utils";
|
||||||
|
|
||||||
|
export const Route = createFileRoute("/app-down")({
|
||||||
|
validateSearch: z.object({
|
||||||
|
redirect: z.string().optional(),
|
||||||
|
}),
|
||||||
|
|
||||||
|
component: RouteComponent,
|
||||||
|
});
|
||||||
|
|
||||||
|
function RouteComponent() {
|
||||||
|
const search = Route.useSearch();
|
||||||
|
const redirectPath = search.redirect ?? "/";
|
||||||
|
const router = useRouter();
|
||||||
|
const click = () => {
|
||||||
|
trackLstEvent("app_down_click", {
|
||||||
|
module: "app",
|
||||||
|
action: "click",
|
||||||
|
label: "redirect",
|
||||||
|
page: window.location.pathname,
|
||||||
|
});
|
||||||
|
router.navigate({ to: redirectPath, replace: true });
|
||||||
|
};
|
||||||
|
return (
|
||||||
|
<div className="flex items-center justify-center bg-background text-foreground">
|
||||||
|
<Card>
|
||||||
|
<CardHeader>
|
||||||
|
<p className="text-2xl">Oops, you shouldn't have done that</p>
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent>
|
||||||
|
<p className="mt-3 text-muted-foreground">
|
||||||
|
Your have tried to go to a page that you are not authorized to be
|
||||||
|
at.
|
||||||
|
</p>
|
||||||
|
</CardContent>
|
||||||
|
<div className=" flex justify-center">
|
||||||
|
<Button
|
||||||
|
className="mt-5 rounded-md bg-primary px-4 py-2 text-primary-foreground"
|
||||||
|
onClick={click}
|
||||||
|
>
|
||||||
|
Refresh
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</Card>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
41
frontend/src/routes/forbidden.tsx
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
import { createFileRoute, useRouter } from "@tanstack/react-router";
|
||||||
|
import { Button } from "../components/ui/button";
|
||||||
|
import { Card, CardContent, CardHeader } from "../components/ui/card";
|
||||||
|
import { trackLstEvent } from "../lib/umami.utils";
|
||||||
|
|
||||||
|
export const Route = createFileRoute("/forbidden")({
|
||||||
|
component: RouteComponent,
|
||||||
|
});
|
||||||
|
|
||||||
|
function RouteComponent() {
|
||||||
|
const click = () => {
|
||||||
|
trackLstEvent("forbidden_click", {
|
||||||
|
module: "forbidden",
|
||||||
|
action: "click",
|
||||||
|
label: "redirect",
|
||||||
|
page: window.location.pathname,
|
||||||
|
});
|
||||||
|
router.navigate({ to: "/", replace: true });
|
||||||
|
};
|
||||||
|
const router = useRouter();
|
||||||
|
return (
|
||||||
|
<div className="flex items-center justify-center bg-background text-foreground">
|
||||||
|
<Card>
|
||||||
|
<CardHeader>
|
||||||
|
<p className="text-2xl">Oops, you shouldn't have done that</p>
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent>
|
||||||
|
<p className="mt-3 text-muted-foreground">
|
||||||
|
Your have tried to go to a page that you are not authorized to be
|
||||||
|
at.
|
||||||
|
</p>
|
||||||
|
</CardContent>
|
||||||
|
<div className=" flex justify-center">
|
||||||
|
<Button className="w-64" onClick={click}>
|
||||||
|
Go Back
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</Card>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -1,9 +1,9 @@
|
|||||||
import { createFileRoute } from "@tanstack/react-router";
|
import { createFileRoute } from "@tanstack/react-router";
|
||||||
|
|
||||||
import z from "zod";
|
import z from "zod";
|
||||||
|
import { Button } from "../components/ui/button";
|
||||||
import { useSession } from "../lib/auth-client";
|
import { useSession } from "../lib/auth-client";
|
||||||
import { trackLstEvent } from "../lib/umami.utils";
|
import { runtimeConfig, trackLstEvent } from "../lib/umami.utils";
|
||||||
|
|
||||||
export const Route = createFileRoute("/")({
|
export const Route = createFileRoute("/")({
|
||||||
validateSearch: z.object({
|
validateSearch: z.object({
|
||||||
@@ -14,7 +14,7 @@ export const Route = createFileRoute("/")({
|
|||||||
});
|
});
|
||||||
|
|
||||||
function Index() {
|
function Index() {
|
||||||
const { isPending } = useSession();
|
const { data: session, isPending } = useSession();
|
||||||
|
|
||||||
if (isPending)
|
if (isPending)
|
||||||
return <div className="flex justify-center mt-10">Loading...</div>;
|
return <div className="flex justify-center mt-10">Loading...</div>;
|
||||||
@@ -38,6 +38,16 @@ function Index() {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const checkConfig = () => {
|
||||||
|
console.log(runtimeConfig);
|
||||||
|
trackLstEvent("config_click", {
|
||||||
|
module: "app",
|
||||||
|
action: "click",
|
||||||
|
label: "configCheck",
|
||||||
|
page: window.location.pathname,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex justify-center m-10 flex-col">
|
<div className="flex justify-center m-10 flex-col">
|
||||||
<h3 className="w-2xl text-3xl">Welcome Lst - V3</h3>
|
<h3 className="w-2xl text-3xl">Welcome Lst - V3</h3>
|
||||||
@@ -55,7 +65,7 @@ function Index() {
|
|||||||
<strong>Click</strong>
|
<strong>Click</strong>
|
||||||
</b>
|
</b>
|
||||||
</a>{" "}
|
</a>{" "}
|
||||||
<button onClick={click}>
|
<button onClick={click} type="button">
|
||||||
<a
|
<a
|
||||||
href={`https://www.youtube.com/watch?v=dQw4w9WgXcQ`}
|
href={`https://www.youtube.com/watch?v=dQw4w9WgXcQ`}
|
||||||
target="_blank"
|
target="_blank"
|
||||||
@@ -67,6 +77,9 @@ function Index() {
|
|||||||
</a>
|
</a>
|
||||||
</button>
|
</button>
|
||||||
</p>
|
</p>
|
||||||
|
{session && session.user.role === "systemAdmin" && (
|
||||||
|
<Button onClick={checkConfig}>Check config</Button>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,7 +15,7 @@
|
|||||||
"foregroundImage": "./assets/adaptive-icon-white.png",
|
"foregroundImage": "./assets/adaptive-icon-white.png",
|
||||||
"backgroundColor": "#ffffff"
|
"backgroundColor": "#ffffff"
|
||||||
},
|
},
|
||||||
"versionCode": 33,
|
"versionCode": 37,
|
||||||
"minSupportedVersionCode": 33,
|
"minSupportedVersionCode": 33,
|
||||||
"predictiveBackGestureEnabled": false,
|
"predictiveBackGestureEnabled": false,
|
||||||
"package": "net.alpla.lst.mobile"
|
"package": "net.alpla.lst.mobile"
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ export default function TabsLayout() {
|
|||||||
const isUnlocked = useMobileAuthStore((s) => s.isUnlocked);
|
const isUnlocked = useMobileAuthStore((s) => s.isUnlocked);
|
||||||
|
|
||||||
const port = parseInt(serverPort || "0", 10) >= 50000;
|
const port = parseInt(serverPort || "0", 10) >= 50000;
|
||||||
console.log(port);
|
|
||||||
if (!port) {
|
if (!port) {
|
||||||
if (!user || !isUnlocked) {
|
if (!user || !isUnlocked) {
|
||||||
return <Redirect href="/login" />;
|
return <Redirect href="/login" />;
|
||||||
@@ -58,14 +58,14 @@ export default function TabsLayout() {
|
|||||||
// },
|
// },
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<Tabs.Screen
|
{/* <Tabs.Screen
|
||||||
name="ppoo"
|
name="ppoo"
|
||||||
options={{
|
options={{
|
||||||
title: "PPOO",
|
title: "PPOO",
|
||||||
href: isNormalScanner ? null : "/(tabs)/ppoo",
|
href: isNormalScanner ? null : "/(tabs)/ppoo",
|
||||||
tabBarIcon: ({ color, size }) => <Boxes size={size} color={color} />,
|
tabBarIcon: ({ color, size }) => <Boxes size={size} color={color} />,
|
||||||
}}
|
}}
|
||||||
/>
|
/> */}
|
||||||
<Tabs.Screen
|
<Tabs.Screen
|
||||||
name="laneCheck"
|
name="laneCheck"
|
||||||
options={{
|
options={{
|
||||||
|
|||||||
@@ -1,18 +1,210 @@
|
|||||||
import React from "react";
|
import axios from "axios";
|
||||||
import { Text, View } from "react-native";
|
import { format } from "date-fns-tz";
|
||||||
|
import { useFocusEffect } from "expo-router";
|
||||||
|
import type React from "react";
|
||||||
|
import { useCallback, useEffect, useState } from "react";
|
||||||
|
import { ScrollView, Text, View } from "react-native";
|
||||||
|
import { SafeAreaView } from "react-native-safe-area-context";
|
||||||
|
import Toast from "react-native-toast-message";
|
||||||
|
import { GlobalFooter } from "../../components/UpdateFooter";
|
||||||
import { Button } from "../../components/ui/button";
|
import { Button } from "../../components/ui/button";
|
||||||
|
import { Card, CardContent, CardHeader } from "../../components/ui/card";
|
||||||
|
import {
|
||||||
|
Dialog,
|
||||||
|
DialogContent,
|
||||||
|
DialogDescription,
|
||||||
|
DialogHeader,
|
||||||
|
DialogTitle,
|
||||||
|
DialogTrigger,
|
||||||
|
} from "../../components/ui/dialog";
|
||||||
|
import { useAppStore } from "../../hooks/useAppStore";
|
||||||
|
import { scannerFeedback } from "../../lib/feedbackScan";
|
||||||
|
import { type ZebraScanResult, zebraScanner } from "../../lib/ZebraScanner";
|
||||||
|
|
||||||
|
const InfoRow = ({
|
||||||
|
label,
|
||||||
|
value,
|
||||||
|
}: {
|
||||||
|
label: string;
|
||||||
|
value: React.ReactNode;
|
||||||
|
}) => {
|
||||||
|
return (
|
||||||
|
<View className="flex-row justify-between gap-4 py-2 border-b border-gray-200">
|
||||||
|
<Text className="text-sm text-gray-500">{label}</Text>
|
||||||
|
<Text className="text-sm font-medium text-gray-900 text-right flex-1">
|
||||||
|
{value}
|
||||||
|
</Text>
|
||||||
|
</View>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
export default function PPOO() {
|
export default function PPOO() {
|
||||||
|
const [units, setUnits] = useState<any>(null);
|
||||||
|
const serverIp = useAppStore((s) => s.serverIp);
|
||||||
|
|
||||||
|
const handleScan = useCallback(
|
||||||
|
async (scan: ZebraScanResult) => {
|
||||||
|
setUnits(null);
|
||||||
|
await scannerFeedback({
|
||||||
|
type: "scan",
|
||||||
|
sound: true,
|
||||||
|
vibrate: true,
|
||||||
|
led: true,
|
||||||
|
});
|
||||||
|
if (!scan.data.startsWith("loc")) {
|
||||||
|
Toast.show({
|
||||||
|
type: "error",
|
||||||
|
text1: "Scan error",
|
||||||
|
text2: "The last scan was not a lane please try again",
|
||||||
|
});
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
const res = await axios.post(
|
||||||
|
`http://${serverIp.trim()}:3000/lst/api/mobile/lanecheck`,
|
||||||
|
{
|
||||||
|
lane: "loc#1#0<",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
timeout: 5000,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
if (res.status === 200) {
|
||||||
|
setUnits(res.data);
|
||||||
|
Toast.show({
|
||||||
|
type: "info",
|
||||||
|
text1: "Lane Data",
|
||||||
|
text2: "All Loading Units from this lane will be listed below",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.log(error);
|
||||||
|
Toast.show({
|
||||||
|
type: "error",
|
||||||
|
text1: "Lane Data",
|
||||||
|
text2: "Error getting lane data please try again",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[serverIp.trim],
|
||||||
|
);
|
||||||
|
|
||||||
|
useFocusEffect(
|
||||||
|
useCallback(() => {
|
||||||
|
zebraScanner.startListening();
|
||||||
|
|
||||||
|
const sub = zebraScanner.addScanListener((scan) => {
|
||||||
|
//console.log("SCAN:", scan);
|
||||||
|
handleScan(scan);
|
||||||
|
});
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
sub.remove();
|
||||||
|
zebraScanner.stopListening();
|
||||||
|
//setUnits(null);
|
||||||
|
};
|
||||||
|
}, [handleScan]),
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<View
|
<View
|
||||||
style={{
|
style={{
|
||||||
flex: 1,
|
|
||||||
//justifyContent: "center",
|
//justifyContent: "center",
|
||||||
alignItems: "center",
|
alignItems: "center",
|
||||||
marginTop: 50,
|
marginTop: 50,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Text>Ppo checks</Text>
|
{units ? (
|
||||||
|
// <SafeAreaView className={`flex-1 w-full items-center`}>
|
||||||
|
// <ScrollView className="w-full flex-1">
|
||||||
|
// <View className="flex items-center gap-2 w-full">
|
||||||
|
// {units.data?.map((i: any, index: any) => (
|
||||||
|
// <View key={`${i.runningNumber}-${index}`}>
|
||||||
|
// <Text>example</Text>
|
||||||
|
// </View>
|
||||||
|
// ))}
|
||||||
|
// </View>
|
||||||
|
// </ScrollView>
|
||||||
|
// </SafeAreaView>
|
||||||
|
<SafeAreaView className={`w-full items-center`}>
|
||||||
|
<View style={{ padding: 2 }}>
|
||||||
|
<Text>There Are {units.data.length} units in PPOO</Text>
|
||||||
|
</View>
|
||||||
|
<ScrollView className="w-full" style={{ marginBottom: 20 }}>
|
||||||
|
<View>
|
||||||
|
{units.data.map((i, index) => (
|
||||||
|
<View
|
||||||
|
key={`${i.runningNumber}-${index}`}
|
||||||
|
style={{
|
||||||
|
justifyContent: "center",
|
||||||
|
margin: 2,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Dialog>
|
||||||
|
<DialogTrigger>
|
||||||
|
<Card
|
||||||
|
className="w-full"
|
||||||
|
style={{
|
||||||
|
borderColor:
|
||||||
|
i.state === "QualityBlocked" ? "red" : undefined,
|
||||||
|
borderWidth: 4,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<CardContent>
|
||||||
|
<Text>
|
||||||
|
{i.articleId} - {i.articleName}
|
||||||
|
</Text>
|
||||||
|
|
||||||
|
<Text>
|
||||||
|
Running Number: {i.runningNumber ?? "Non barcoded"}
|
||||||
|
</Text>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
</DialogTrigger>
|
||||||
|
<DialogContent>
|
||||||
|
<DialogHeader>
|
||||||
|
<DialogTitle>
|
||||||
|
Details for Article {i.articleId}, Rn:
|
||||||
|
{i.runningNumber ?? "Non barcoded"}{" "}
|
||||||
|
</DialogTitle>
|
||||||
|
<DialogDescription>
|
||||||
|
<InfoRow
|
||||||
|
label="Production Date"
|
||||||
|
value={format(i.productionDate, "MM/dd/yyyy HH:mm")}
|
||||||
|
/>
|
||||||
|
<InfoRow label="Quantity" value={i.quantity} />
|
||||||
|
{i.state === "QualityBlocked" && (
|
||||||
|
<InfoRow
|
||||||
|
label="Defect"
|
||||||
|
value={i.mainDefectGroupDescription}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
{i.state === "QualityBlocked" && (
|
||||||
|
<InfoRow
|
||||||
|
label="Description"
|
||||||
|
value={i.mainDefectDescription}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</DialogDescription>
|
||||||
|
</DialogHeader>
|
||||||
|
</DialogContent>
|
||||||
|
</Dialog>
|
||||||
|
</View>
|
||||||
|
))}
|
||||||
|
</View>
|
||||||
|
</ScrollView>
|
||||||
|
</SafeAreaView>
|
||||||
|
) : (
|
||||||
|
<View className="mt-50">
|
||||||
|
<Text className="text-2xl text-center">
|
||||||
|
Please scan a lane to see all Units that are in the lane.
|
||||||
|
</Text>
|
||||||
|
</View>
|
||||||
|
)}
|
||||||
|
<View>
|
||||||
|
<GlobalFooter />
|
||||||
|
</View>
|
||||||
</View>
|
</View>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import axios from "axios";
|
import axios from "axios";
|
||||||
import { format } from "date-fns-tz";
|
import { format } from "date-fns-tz";
|
||||||
|
import Constants from "expo-constants";
|
||||||
import { Redirect, useFocusEffect, useRouter } 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";
|
||||||
@@ -31,6 +32,7 @@ export default function LSTScanner() {
|
|||||||
const [tagScans, setTagScans] = useState<any>([]);
|
const [tagScans, setTagScans] = useState<any>([]);
|
||||||
const serverIp = useAppStore((s) => s.serverIp);
|
const serverIp = useAppStore((s) => s.serverIp);
|
||||||
const [bgColor, setBGColor] = useState<string | null>(null);
|
const [bgColor, setBGColor] = useState<string | null>(null);
|
||||||
|
const build = Constants.expoConfig?.android?.versionCode ?? 1;
|
||||||
|
|
||||||
const handleScan = useCallback(
|
const handleScan = useCallback(
|
||||||
async (scan: ZebraScanResult) => {
|
async (scan: ZebraScanResult) => {
|
||||||
@@ -93,6 +95,7 @@ export default function LSTScanner() {
|
|||||||
: scan.data.startsWith("loc")
|
: scan.data.startsWith("loc")
|
||||||
? scan.data
|
? scan.data
|
||||||
: "0",
|
: "0",
|
||||||
|
scannerVersion: build,
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.log(error);
|
console.log(error);
|
||||||
@@ -146,6 +149,7 @@ export default function LSTScanner() {
|
|||||||
user?.name,
|
user?.name,
|
||||||
user?.excludedCommand?.some,
|
user?.excludedCommand?.some,
|
||||||
user?.excludedCommand,
|
user?.excludedCommand,
|
||||||
|
build,
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import axios from "axios";
|
import axios from "axios";
|
||||||
import { format } from "date-fns-tz";
|
import { format } from "date-fns-tz";
|
||||||
|
import Constants from "expo-constants";
|
||||||
import { useFocusEffect } from "expo-router";
|
import { useFocusEffect } from "expo-router";
|
||||||
import { useCallback, useEffect, useState } from "react";
|
import { useCallback, useEffect, useState } from "react";
|
||||||
import { Text, View } from "react-native";
|
import { Text, View } from "react-native";
|
||||||
@@ -25,6 +26,7 @@ export default function ProdScanner() {
|
|||||||
const serverIp = useAppStore((s) => s.serverIp);
|
const serverIp = useAppStore((s) => s.serverIp);
|
||||||
const serverPort = useAppStore((s) => s.serverPort);
|
const serverPort = useAppStore((s) => s.serverPort);
|
||||||
const [bgColor, setBGColor] = useState<string | null>(null);
|
const [bgColor, setBGColor] = useState<string | null>(null);
|
||||||
|
const build = Constants.expoConfig?.android?.versionCode ?? 1;
|
||||||
|
|
||||||
const handleScan = useCallback(
|
const handleScan = useCallback(
|
||||||
async (scan: ZebraScanResult) => {
|
async (scan: ZebraScanResult) => {
|
||||||
@@ -62,6 +64,7 @@ export default function ProdScanner() {
|
|||||||
: scan.data.startsWith("loc")
|
: scan.data.startsWith("loc")
|
||||||
? scan.data
|
? scan.data
|
||||||
: "0",
|
: "0",
|
||||||
|
scannerVersion: build,
|
||||||
};
|
};
|
||||||
try {
|
try {
|
||||||
await axios.post(
|
await axios.post(
|
||||||
@@ -112,7 +115,7 @@ export default function ProdScanner() {
|
|||||||
setTagScans([]);
|
setTagScans([]);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[scannerIdFromStore, serverIp, serverPort, setLastScan],
|
[scannerIdFromStore, serverIp, serverPort, setLastScan, build],
|
||||||
);
|
);
|
||||||
|
|
||||||
const clearScans = () => {
|
const clearScans = () => {
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import TcpSocket from "react-native-tcp-socket";
|
|||||||
type TcpResponse = {
|
type TcpResponse = {
|
||||||
success: boolean;
|
success: boolean;
|
||||||
message: string;
|
message: string;
|
||||||
data: string[];
|
data: ScannerEvent;
|
||||||
};
|
};
|
||||||
|
|
||||||
type ScannerEvent = {
|
type ScannerEvent = {
|
||||||
@@ -232,7 +232,7 @@ export async function sendTcpMessage(
|
|||||||
timeoutMs = 5000,
|
timeoutMs = 5000,
|
||||||
): Promise<TcpResponse> {
|
): Promise<TcpResponse> {
|
||||||
return new Promise((resolve) => {
|
return new Promise((resolve) => {
|
||||||
const responses: any = [];
|
//const responses: any = [];
|
||||||
|
|
||||||
const client = TcpSocket.createConnection({ host, port }, () => {
|
const client = TcpSocket.createConnection({ host, port }, () => {
|
||||||
//console.log("Sending TCP (visible):", `${command}`);
|
//console.log("Sending TCP (visible):", `${command}`);
|
||||||
@@ -240,17 +240,53 @@ export async function sendTcpMessage(
|
|||||||
client.write(command);
|
client.write(command);
|
||||||
});
|
});
|
||||||
|
|
||||||
const timeout = setTimeout(() => {
|
let settled = false;
|
||||||
client.destroy();
|
|
||||||
|
|
||||||
resolve({
|
const done = (response: TcpResponse) => {
|
||||||
|
if (settled) return;
|
||||||
|
|
||||||
|
settled = true;
|
||||||
|
|
||||||
|
clearTimeout(timeout);
|
||||||
|
|
||||||
|
try {
|
||||||
|
client.destroy();
|
||||||
|
} catch {}
|
||||||
|
|
||||||
|
resolve(response);
|
||||||
|
};
|
||||||
|
|
||||||
|
// const timeout = setTimeout(() => {
|
||||||
|
// client.destroy();
|
||||||
|
|
||||||
|
// resolve({
|
||||||
|
// success: false,
|
||||||
|
// message: "TCP timeout",
|
||||||
|
// data: responses,
|
||||||
|
// });
|
||||||
|
// }, timeoutMs);
|
||||||
|
|
||||||
|
const timeout = setTimeout(() => {
|
||||||
|
done({
|
||||||
success: false,
|
success: false,
|
||||||
message: "TCP timeout",
|
message: "No response from scanner server",
|
||||||
data: responses,
|
data: {
|
||||||
|
scannerId: "999",
|
||||||
|
commandDescription: "TCP Command",
|
||||||
|
prompt: command,
|
||||||
|
message: "Invalid command",
|
||||||
|
status: "error",
|
||||||
|
lines: [
|
||||||
|
"SYSTEM",
|
||||||
|
"TCP Command",
|
||||||
|
`Scan: ${command}`,
|
||||||
|
"Invalid command",
|
||||||
|
],
|
||||||
|
},
|
||||||
});
|
});
|
||||||
}, timeoutMs);
|
}, timeoutMs);
|
||||||
|
|
||||||
client.on("data", (data) => {
|
client.on("data", (data: any) => {
|
||||||
//console.log("TCP received:", text);
|
//console.log("TCP received:", text);
|
||||||
const parsed = parseScannerText(data);
|
const parsed = parseScannerText(data);
|
||||||
//console.log("scanned:", parsed);
|
//console.log("scanned:", parsed);
|
||||||
@@ -260,32 +296,69 @@ export async function sendTcpMessage(
|
|||||||
const cleaned = parseScannerEvent(parsed);
|
const cleaned = parseScannerEvent(parsed);
|
||||||
|
|
||||||
//console.log(responses);
|
//console.log(responses);
|
||||||
clearTimeout(timeout);
|
// clearTimeout(timeout);
|
||||||
resolve({
|
// resolve({
|
||||||
success: true,
|
// success: true,
|
||||||
message: "TCP Response",
|
// message: "TCP Response",
|
||||||
data: cleaned as any,
|
// data: cleaned as any,
|
||||||
|
// });
|
||||||
|
|
||||||
|
done({
|
||||||
|
success: cleaned.status !== "error",
|
||||||
|
message:
|
||||||
|
cleaned.status === "error"
|
||||||
|
? (cleaned.message ?? "TCP Error")
|
||||||
|
: "TCP Response",
|
||||||
|
data: cleaned,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
client.on("error", (err) => {
|
client.on("error", (err) => {
|
||||||
clearTimeout(timeout);
|
// resolve({
|
||||||
client.destroy();
|
// success: false,
|
||||||
|
// message: err.message,
|
||||||
|
// data: ["Error", "Please try again"] as any,
|
||||||
|
// });
|
||||||
|
|
||||||
resolve({
|
done({
|
||||||
success: false,
|
success: false,
|
||||||
message: err.message,
|
message: err.message,
|
||||||
data: ["Error", "Please try again"],
|
data: {
|
||||||
|
scannerId: "SYSTEM",
|
||||||
|
commandDescription: "TCP Error",
|
||||||
|
prompt: command,
|
||||||
|
message: err.message,
|
||||||
|
status: "error",
|
||||||
|
lines: ["SYSTEM", "TCP Error", `Scan: ${command}`, err.message],
|
||||||
|
},
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
client.on("close", () => {
|
client.on("close", () => {
|
||||||
clearTimeout(timeout);
|
if (settled) return;
|
||||||
|
|
||||||
resolve({
|
// resolve({
|
||||||
success: true,
|
// success: true,
|
||||||
message: "TCP complete",
|
// message: "TCP complete",
|
||||||
data: ["Error", "Please try again"],
|
// data: ["Error", "Please try again"] as any,
|
||||||
|
// });
|
||||||
|
|
||||||
|
done({
|
||||||
|
success: false,
|
||||||
|
message: "TCP connection closed",
|
||||||
|
data: {
|
||||||
|
scannerId: "SYSTEM",
|
||||||
|
commandDescription: "TCP Closed",
|
||||||
|
prompt: command,
|
||||||
|
message: "Connection closed before response",
|
||||||
|
status: "error",
|
||||||
|
lines: [
|
||||||
|
"SYSTEM",
|
||||||
|
"TCP Closed",
|
||||||
|
`Scan: ${command}`,
|
||||||
|
"Connection closed before response",
|
||||||
|
],
|
||||||
|
},
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
1
migrations/0052_numerous_wasp.sql
Normal file
@@ -0,0 +1 @@
|
|||||||
|
ALTER TABLE "scan_log" ADD COLUMN "scanner_version" text DEFAULT '0';
|
||||||
2367
migrations/meta/0052_snapshot.json
Normal file
@@ -365,6 +365,13 @@
|
|||||||
"when": 1778525497824,
|
"when": 1778525497824,
|
||||||
"tag": "0051_sad_war_machine",
|
"tag": "0051_sad_war_machine",
|
||||||
"breakpoints": true
|
"breakpoints": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idx": 52,
|
||||||
|
"version": "7",
|
||||||
|
"when": 1778533475205,
|
||||||
|
"tag": "0052_numerous_wasp",
|
||||||
|
"breakpoints": true
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
@@ -15,7 +15,7 @@ param (
|
|||||||
|
|
||||||
# server migrations get - reminder to add to old version in pkg "start:lst": "cd lstV2 && npm start",
|
# server migrations get - reminder to add to old version in pkg "start:lst": "cd lstV2 && npm start",
|
||||||
# powershell.exe -ExecutionPolicy Bypass -File .\scripts\services.ps1 -serviceName "LST_app" -option "install" -appPath "D:\LST" -description "Logistics Support Tool" -command "run start"
|
# powershell.exe -ExecutionPolicy Bypass -File .\scripts\services.ps1 -serviceName "LST_app" -option "install" -appPath "D:\LST" -description "Logistics Support Tool" -command "run start"
|
||||||
# powershell.exe -ExecutionPolicy Bypass -File .\scripts\services.ps1 -serviceName "LSTV2" -option "install" -appPath "D:\LST" -description "Logistics Support Tool" -command "run start:lst"
|
# powershell.exe -ExecutionPolicy Bypass -File .\scripts\services.ps1 -serviceName "LSTV3_app" -option "install" -appPath "D:\LST_V3" -description "Logistics Support Tool" -command "run start"
|
||||||
# powershell.exe -ExecutionPolicy Bypass -File .\scripts\services.ps1 -serviceName "LST_ctl" -option "delete" -appPath "D:\LST" -description "Logistics Support Tool" -command "run start:lst"
|
# powershell.exe -ExecutionPolicy Bypass -File .\scripts\services.ps1 -serviceName "LST_ctl" -option "delete" -appPath "D:\LST" -description "Logistics Support Tool" -command "run start:lst"
|
||||||
|
|
||||||
$nssmPath = $AppPath + "\nssm.exe"
|
$nssmPath = $AppPath + "\nssm.exe"
|
||||||
|
|||||||
@@ -87,7 +87,15 @@ function Update-Server {
|
|||||||
param ($Server, $Token, $Destination, $BuildFile)
|
param ($Server, $Token, $Destination, $BuildFile)
|
||||||
|
|
||||||
function Fix-Env {
|
function Fix-Env {
|
||||||
$envFile = ".env"
|
|
||||||
|
param(
|
||||||
|
[Parameter(Mandatory = $true)]
|
||||||
|
[string]$Path
|
||||||
|
)
|
||||||
|
|
||||||
|
$envFile = Join-Path $Path ".env"
|
||||||
|
|
||||||
|
Write-Host "Checking env file: $envFile"
|
||||||
|
|
||||||
if (-not (Test-Path $envFile)) {
|
if (-not (Test-Path $envFile)) {
|
||||||
Write-Host ".env not found, creating..."
|
Write-Host ".env not found, creating..."
|
||||||
@@ -197,7 +205,7 @@ function Update-Server {
|
|||||||
Write-Host "Install/update completed."
|
Write-Host "Install/update completed."
|
||||||
|
|
||||||
# update the env to include the new and missing things silly people and wanting things fixed :(
|
# update the env to include the new and missing things silly people and wanting things fixed :(
|
||||||
Fix-Env #-Path $LocalPath
|
Fix-Env -Path $LocalPath
|
||||||
|
|
||||||
# do the migrations
|
# do the migrations
|
||||||
# Push-Location $LocalPath
|
# Push-Location $LocalPath
|
||||||
|
|||||||