Compare commits
17 Commits
f5bae2c0c2
...
v0.1.0-alp
| Author | SHA1 | Date | |
|---|---|---|---|
| 67f36c5499 | |||
| 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
|
||||
node-v24.14.0-x64.msi
|
||||
postgresql-17.9-2-windows-x64.exe
|
||||
VSCodeUserSetup-x64-1.112.0.exe
|
||||
VSCodeSetup-x64-1.120.0.exe
|
||||
nssm.exe
|
||||
frontend/.tanstack
|
||||
|
||||
|
||||
57
CHANGELOG.md
@@ -1,5 +1,62 @@
|
||||
# All Changes to LST can be found below.
|
||||
|
||||
## [0.1.0-alpha.0](https://git.tuffraid.net/cowch/lst_v3/compare/v0.0.2-alpha.10...v0.1.0-alpha.0) (2026-05-14)
|
||||
|
||||
|
||||
### ⚠ BREAKING CHANGES
|
||||
|
||||
* **app:** moved teh middleware to call the api hits to the main app and removed from
|
||||
everywhere else
|
||||
|
||||
### 🌟 Enhancements
|
||||
|
||||
* **notification:** migrated sql cleanup ([3e66c39](https://git.tuffraid.net/cowch/lst_v3/commits/3e66c3920d65cee7a0a788f3910c1ddf09a07805))
|
||||
* **scan users:** added in the place to add the new scanner users in ([ce9d8ea](https://git.tuffraid.net/cowch/lst_v3/commits/ce9d8eaaf5bcb8f53ea4bdc191347df8d589fdfa))
|
||||
|
||||
|
||||
### 🐛 Bug fixes
|
||||
|
||||
* **anaylistics:** changes to the daily section so it populates correctly now ([f5bae2c](https://git.tuffraid.net/cowch/lst_v3/commits/f5bae2c0c24b85423c5c421164d94d58159ff70a))
|
||||
* **anaylitics:** unique values were missing causing a weird crash ([13718fe](https://git.tuffraid.net/cowch/lst_v3/commits/13718fe70293c039bd1d9bf8cf395852e6ea6c21))
|
||||
* **app:** emit.maxlistener issue ([7c31b43](https://git.tuffraid.net/cowch/lst_v3/commits/7c31b43a4a313237fa63c0c9bbc3690b74f63a6f)), closes [#18](https://git.tuffraid.net/cowch/lst_v3/issues/18)
|
||||
* **app:** required auth was in wrong spot caused entire app to want you logged in ([d2a9e1d](https://git.tuffraid.net/cowch/lst_v3/commits/d2a9e1d1107ea05f13725e9528bc6ab1566c8efb))
|
||||
* **notification subs:** made it so only acitve show ([2616acf](https://git.tuffraid.net/cowch/lst_v3/commits/2616acf106530f5c5ee04d1b79033795cf06b42d)), closes [#14](https://git.tuffraid.net/cowch/lst_v3/issues/14)
|
||||
* **scanner:** changed to not crash on logging ([0de2579](https://git.tuffraid.net/cowch/lst_v3/commits/0de25799420f38a293ee9acc70eb36e3287145c4)), closes [#19](https://git.tuffraid.net/cowch/lst_v3/issues/19)
|
||||
* **scanner:** fixes to be more clear that you need to scan a command to start ([0575879](https://git.tuffraid.net/cowch/lst_v3/commits/05758791be7a50e90b5da05d4977e618c311f654)), closes [#16](https://git.tuffraid.net/cowch/lst_v3/issues/16)
|
||||
* **scanner:** logut out corrections ([85e96f5](https://git.tuffraid.net/cowch/lst_v3/commits/85e96f5ed13a81fd466c6bbff31c539244750838)), closes [#17](https://git.tuffraid.net/cowch/lst_v3/issues/17)
|
||||
* **table:** skelly table causing hydration error ([1bbf5c2](https://git.tuffraid.net/cowch/lst_v3/commits/1bbf5c2a4955107a36ace05595886d19cc8e64f4))
|
||||
|
||||
|
||||
### 📝 Chore
|
||||
|
||||
* **mobile:** removed console log that shouldnt be there ([9631736](https://git.tuffraid.net/cowch/lst_v3/commits/9631736e263ed00189f8118f686690cab25f09d3))
|
||||
|
||||
|
||||
### 📚 Documentation
|
||||
|
||||
* **scanner:** added in instructions on how to update the scanner ([b0c7277](https://git.tuffraid.net/cowch/lst_v3/commits/b0c7277a6cdb5becec3a994ea1d5cc2d7b0326aa))
|
||||
* **scanner:** added in westbend and dayton commands to scan for updates ([eb9d77c](https://git.tuffraid.net/cowch/lst_v3/commits/eb9d77c3d4767fd961759662ef44c3e09e00946b))
|
||||
|
||||
|
||||
### 🛠️ Code Refactor
|
||||
|
||||
* **api:** changes to call a helper api to quit and redirect if needed ([c64392f](https://git.tuffraid.net/cowch/lst_v3/commits/c64392f45769108aa4134c7fd865f3d4bc664179))
|
||||
* **app:** changed ways we get data so we can have better reasons why app no worky ([30ff7b7](https://git.tuffraid.net/cowch/lst_v3/commits/30ff7b71d9d159ced263a5330d70d53b97393157))
|
||||
* **mobile:** scanner response ([a9c6925](https://git.tuffraid.net/cowch/lst_v3/commits/a9c69250bd3272ad682751e41b671c119cb678f1)), closes [#16](https://git.tuffraid.net/cowch/lst_v3/issues/16)
|
||||
* **scanner:** logging - version of app ([d61be61](https://git.tuffraid.net/cowch/lst_v3/commits/d61be61f4433a2be2678d724f4724301931614c9))
|
||||
* **scanner:** more scanner admin stuff ([eb950d2](https://git.tuffraid.net/cowch/lst_v3/commits/eb950d2c29f692b806d5cc4ab7014bd59a726a8d))
|
||||
* **scanner:** removed 69 as an option lol ([e7af3d1](https://git.tuffraid.net/cowch/lst_v3/commits/e7af3d11824b42915cf6789f9c508a727511d678))
|
||||
* **servers:** server name now links to the actual server:port ([ebf1060](https://git.tuffraid.net/cowch/lst_v3/commits/ebf1060475d37627b371bc6c79507cdde411600b))
|
||||
* **users:** some user refactoring and configuring ([342a97f](https://git.tuffraid.net/cowch/lst_v3/commits/342a97f6b1054443b9126186d2c7872fbd8586da))
|
||||
|
||||
|
||||
### 📈 Project changes
|
||||
|
||||
* **mobile:** added in ehs config to make it more easy for users to update the scanner app on the fly ([dc95e50](https://git.tuffraid.net/cowch/lst_v3/commits/dc95e50a8412b4fbc629fd44fcb5c77295583ca8))
|
||||
* **notification:** removal of more console logs that shouldnt be here ([51026e3](https://git.tuffraid.net/cowch/lst_v3/commits/51026e3e2cce4d7f696d26aae305b3fd221f5bb1))
|
||||
* **servives:** helpers moved around ([e9e73c8](https://git.tuffraid.net/cowch/lst_v3/commits/e9e73c829c2e5726650c0ac7ffa6a9055dbc982b))
|
||||
* **updateserver:** changes to actually add the new env stuff ([bcb7773](https://git.tuffraid.net/cowch/lst_v3/commits/bcb7773007894ac2f85fe2a0b47faf14c7b474ad))
|
||||
|
||||
## [0.0.2-alpha.10](https://git.tuffraid.net/cowch/lst_v3/compare/v0.0.2-alpha.9...v0.0.2-alpha.10) (2026-05-08)
|
||||
|
||||
|
||||
|
||||
@@ -3,16 +3,14 @@ import { requireAuth } from "../middleware/auth.middleware.js";
|
||||
|
||||
import build from "./admin.build.js";
|
||||
import update from "./admin.updateServer.js";
|
||||
import users from "./admin.users.js";
|
||||
|
||||
export const setupAdminRoutes = (baseUrl: string, app: Express) => {
|
||||
//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/*
|
||||
};
|
||||
|
||||
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.all(`${baseUrl}/api/auth/*splat`, toNodeHandler(auth));
|
||||
app.use(express.json());
|
||||
setupRoutes(baseUrl, app);
|
||||
|
||||
app.get(`${baseUrl}/api/lst-config.js`, (_, res) => {
|
||||
res.type("application/javascript");
|
||||
@@ -52,6 +51,8 @@ const createApp = async () => {
|
||||
`);
|
||||
});
|
||||
|
||||
setupRoutes(baseUrl, app);
|
||||
|
||||
app.use(
|
||||
`${baseUrl}/app`,
|
||||
express.static(join(__dirname, "../frontend/dist")),
|
||||
|
||||
@@ -11,6 +11,7 @@ export const scanLog = pgTable("scan_log", {
|
||||
commandDescription: text("command_description"),
|
||||
runningNumber: text("running_number").default("0"),
|
||||
status: text("status"),
|
||||
scannerVersion: text("scanner_version").default("0"),
|
||||
lines: jsonb("lines").default([]),
|
||||
add_Date: timestamp("add_date").defaultNow(),
|
||||
});
|
||||
|
||||
@@ -8,11 +8,10 @@ export const setupGPSqlRoutes = (baseUrl: string, app: Express) => {
|
||||
//setup all the routes
|
||||
// Apply auth to entire router
|
||||
const router = Router();
|
||||
router.use(requireAuth);
|
||||
|
||||
router.use(start);
|
||||
router.use(stop);
|
||||
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();
|
||||
|
||||
// scanners that are dedicated to specific users.
|
||||
const SPECIAL_SCANNERS = [69, 98];
|
||||
const SPECIAL_SCANNERS = [98];
|
||||
|
||||
const buildAllowedScannerIds = (scannerCount: number) => {
|
||||
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-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);
|
||||
});
|
||||
|
||||
@@ -1,6 +1,12 @@
|
||||
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 { apiReturn } from "../utils/returnHelper.utils.js";
|
||||
import { apiReturn, returnFunc } from "../utils/returnHelper.utils.js";
|
||||
import { tryCatch } from "../utils/trycatch.utils.js";
|
||||
|
||||
const router = Router();
|
||||
|
||||
@@ -9,7 +15,27 @@ router.post("/", async (req, res) => {
|
||||
|
||||
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({
|
||||
method: "post",
|
||||
endpoint: "/public/v1.1/Warehousing/GetWarehouseUnits",
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import type { Express } from "express";
|
||||
import { featureCheck } from "../middleware/featureActive.middleware.js";
|
||||
import available from "./availableScanIds.route.js";
|
||||
import downloads from "./downloadApps.route.js";
|
||||
import lanes from "./laneCheck.js";
|
||||
@@ -10,13 +11,13 @@ import version from "./version.route.js";
|
||||
export const setupMobileRoutes = (baseUrl: string, app: Express) => {
|
||||
//stats will be like this as we dont need to change this
|
||||
|
||||
app.use(`${baseUrl}/api/mobile/version`, version);
|
||||
app.use(`${baseUrl}/api/mobile/apk`, downloads);
|
||||
app.use(`${baseUrl}/api/mobile/logs`, logs);
|
||||
app.use(`${baseUrl}/api/mobile/auth`, authPin);
|
||||
app.use(`${baseUrl}/api/mobile/pin`, newPin);
|
||||
app.use(`${baseUrl}/api/mobile/laneCheck`, lanes);
|
||||
app.use(`${baseUrl}/api/mobile/available`, available);
|
||||
app.use(`${baseUrl}/api/mobile/version`, featureCheck("mobile"), version);
|
||||
app.use(`${baseUrl}/api/mobile/apk`, featureCheck("mobile"), downloads);
|
||||
app.use(`${baseUrl}/api/mobile/logs`, featureCheck("mobile"), logs);
|
||||
app.use(`${baseUrl}/api/mobile/auth`, featureCheck("mobile"), authPin);
|
||||
app.use(`${baseUrl}/api/mobile/pin`, featureCheck("mobile"), newPin);
|
||||
app.use(`${baseUrl}/api/mobile/laneCheck`, featureCheck("mobile"), lanes);
|
||||
app.use(`${baseUrl}/api/mobile/available`, featureCheck("mobile"), available);
|
||||
|
||||
// all other system should be under /api/system/*
|
||||
};
|
||||
|
||||
@@ -1,14 +1,22 @@
|
||||
import { eq, sql } from "drizzle-orm";
|
||||
import { Router } from "express";
|
||||
|
||||
import { db } from "../db/db.controller.js";
|
||||
import { scanLog } from "../db/schema/scanlog.schema.js";
|
||||
import { scanUser } from "../db/schema/scanUsers.js";
|
||||
import { apiReturn } from "../utils/returnHelper.utils.js";
|
||||
|
||||
const router = Router();
|
||||
|
||||
router.post("/", async (req, res) => {
|
||||
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
|
||||
.insert(scanLog)
|
||||
.values({
|
||||
@@ -20,6 +28,7 @@ router.post("/", async (req, res) => {
|
||||
lines: body.lines ?? "",
|
||||
user: body.user ?? "",
|
||||
runningNumber: body.runningNumber ?? "",
|
||||
scannerVersion: body.scannerVersion ?? "0",
|
||||
})
|
||||
.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 event: PrinterEvent = parseZebraAlert(req.body);
|
||||
|
||||
|
||||
@@ -21,7 +21,7 @@ import { printerSync } from "./ocp.printer.manage.js";
|
||||
|
||||
const r = Router();
|
||||
|
||||
r.post("/printer/update", async (_, res) => {
|
||||
r.post("/update", async (_, res) => {
|
||||
printerSync();
|
||||
return apiReturn(res, {
|
||||
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 { featureCheck } from "../middleware/featureActive.middleware.js";
|
||||
|
||||
@@ -6,20 +6,11 @@ import listener from "./ocp.printer.listener.js";
|
||||
import update from "./ocp.printer.update.js";
|
||||
|
||||
export const setupOCPRoutes = (baseUrl: string, app: Express) => {
|
||||
//setup all the routes
|
||||
const router = Router();
|
||||
|
||||
// is the feature even on?
|
||||
router.use(featureCheck("ocp"));
|
||||
|
||||
// non auth routes up here
|
||||
router.use(listener);
|
||||
|
||||
// auth routes below here
|
||||
router.use(requireAuth);
|
||||
|
||||
router.use(update);
|
||||
//router.use("");
|
||||
|
||||
app.use(`${baseUrl}/api/ocp`, router);
|
||||
app.use(`${baseUrl}/api/ocp/printer/listener`, featureCheck("ocp"), listener);
|
||||
app.use(
|
||||
`${baseUrl}/api/ocp/printer`,
|
||||
featureCheck("ocp"),
|
||||
requireAuth,
|
||||
update,
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { type Express, Router } from "express";
|
||||
import type { Express } from "express";
|
||||
import { requireAuth } from "../middleware/auth.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) => {
|
||||
//setup all the routes
|
||||
// Apply auth to entire router
|
||||
const router = Router();
|
||||
|
||||
// is the feature even on?
|
||||
router.use(featureCheck("opendock_sync"));
|
||||
|
||||
// we need to make sure we are authenticated to see the releases
|
||||
router.use(requireAuth);
|
||||
|
||||
router.use(getApt);
|
||||
app.use(`${baseUrl}/api/opendock`, router);
|
||||
app.use(
|
||||
`${baseUrl}/api/opendock`,
|
||||
featureCheck("opendock_sync"),
|
||||
requireAuth,
|
||||
getApt,
|
||||
);
|
||||
};
|
||||
|
||||
@@ -10,9 +10,7 @@ export const setupProdSqlRoutes = (baseUrl: string, app: Express) => {
|
||||
const router = Router();
|
||||
router.use(requireAuth);
|
||||
|
||||
router.use(start);
|
||||
router.use(stop);
|
||||
router.use(restart);
|
||||
|
||||
app.use(`${baseUrl}/api/system/prodSql`, router);
|
||||
app.use(`${baseUrl}/api/system/prodSql/start`, requireAuth, start);
|
||||
app.use(`${baseUrl}/api/system/prodSql/stop`, requireAuth, stop);
|
||||
app.use(`${baseUrl}/api/system/prodSql/restart`, requireAuth, restart);
|
||||
};
|
||||
|
||||
@@ -4,7 +4,7 @@ import { closePool, connectProdSql } from "./prodSqlConnection.controller.js";
|
||||
|
||||
const r = Router();
|
||||
|
||||
r.post("/restart", async (_, res) => {
|
||||
r.post("/", async (_, res) => {
|
||||
await closePool();
|
||||
|
||||
await new Promise((r) => setTimeout(r, 2000));
|
||||
|
||||
@@ -4,7 +4,7 @@ import { connectProdSql } from "./prodSqlConnection.controller.js";
|
||||
|
||||
const r = Router();
|
||||
|
||||
r.post("/start", async (_, res) => {
|
||||
r.post("/", async (_, res) => {
|
||||
const connect = await connectProdSql();
|
||||
apiReturn(res, {
|
||||
success: connect.success,
|
||||
|
||||
@@ -4,7 +4,7 @@ import { closePool } from "./prodSqlConnection.controller.js";
|
||||
|
||||
const r = Router();
|
||||
|
||||
r.post("/stop", async (_, res) => {
|
||||
r.post("/", async (_, res) => {
|
||||
const connect = await closePool();
|
||||
apiReturn(res, {
|
||||
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) => {
|
||||
//routes that are on by default
|
||||
setupMobileRoutes(baseUrl, app);
|
||||
setupSystemRoutes(baseUrl, app);
|
||||
setupAdminRoutes(baseUrl, app);
|
||||
setupApiDocsRoutes(baseUrl, app);
|
||||
@@ -28,5 +29,4 @@ export const setupRoutes = (baseUrl: string, app: Express) => {
|
||||
setupNotificationRoutes(baseUrl, app);
|
||||
setupOCPRoutes(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 { historicalSchedule } from "./logistics/logistics.historicalInv.js";
|
||||
import { startNotifications } from "./notification/notification.controller.js";
|
||||
import { sqlJobCleanUp } from "./notification/notification.SqlJobCleanUp.js";
|
||||
import { createNotifications } from "./notification/notifications.master.js";
|
||||
import { printerSync } from "./ocp/ocp.printer.manage.js";
|
||||
import { monitorReleaseChanges } from "./opendock/openDockRreleaseMonitor.utils.js";
|
||||
@@ -83,6 +84,9 @@ const start = async () => {
|
||||
startNotifications();
|
||||
serversChecks();
|
||||
aggregateRouteHitsForBusinessDay();
|
||||
|
||||
// can be removed at a later date
|
||||
sqlJobCleanUp();
|
||||
}, 5 * 1000);
|
||||
|
||||
process.on("uncaughtException", async (err) => {
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
import { createAccessControl } from "better-auth/plugins/access";
|
||||
import { adminAc } from "better-auth/plugins/admin/access";
|
||||
|
||||
export const statement = {
|
||||
app: ["read", "create", "share", "update", "delete", "readAll"],
|
||||
user: ["ban"],
|
||||
//user: ["ban"],
|
||||
quality: ["read", "create", "share", "update", "delete", "readAll"],
|
||||
notifications: ["read", "create", "share", "update", "delete", "readAll"],
|
||||
} as const;
|
||||
@@ -20,7 +21,8 @@ export const admin = ac.newRole({
|
||||
|
||||
export const systemAdmin = ac.newRole({
|
||||
app: ["read", "create", "share", "update", "delete", "readAll"],
|
||||
user: ["ban"],
|
||||
//user: ["ban"],
|
||||
quality: ["read", "create", "share", "update", "delete", "readAll"],
|
||||
notifications: ["read", "create", "share", "update", "delete", "readAll"],
|
||||
...adminAc.statements,
|
||||
});
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
<title>Logistics Support Tool</title>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<script>
|
||||
const configScript = document.createElement("script");
|
||||
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 { signOut } = authClient;
|
||||
const router = useRouterState();
|
||||
|
||||
const navigate = useNavigate();
|
||||
const currentPath = router.location.href;
|
||||
|
||||
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 items-center gap-2 px-4">
|
||||
<div className="flex flex-row">
|
||||
@@ -48,6 +51,20 @@ export default function Header() {
|
||||
<span className="font-semibold text-2xl">Logistics Support Tool</span>
|
||||
</div>
|
||||
<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>
|
||||
<ModeToggle />
|
||||
</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 { Bell, Logs, Server, Settings, UsersRound } from "lucide-react";
|
||||
|
||||
import { getSettings } from "../../lib/queries/getSettings";
|
||||
import {
|
||||
SidebarGroup,
|
||||
SidebarGroupContent,
|
||||
@@ -23,6 +24,7 @@ import {
|
||||
|
||||
export default function AdminSidebar({ session }: any) {
|
||||
const { setOpen } = useSidebar();
|
||||
const { data: settings, isLoading } = useSuspenseQuery(getSettings());
|
||||
const items = [
|
||||
{
|
||||
title: "Notifications",
|
||||
@@ -70,7 +72,9 @@ export default function AdminSidebar({ session }: any) {
|
||||
icon: UsersRound,
|
||||
role: ["systemAdmin", "admin", "manager"],
|
||||
module: "admin",
|
||||
active: true,
|
||||
active:
|
||||
!isLoading &&
|
||||
settings.filter((n: any) => n.name === "mobile")[0].active,
|
||||
},
|
||||
];
|
||||
return (
|
||||
@@ -80,7 +84,7 @@ export default function AdminSidebar({ session }: any) {
|
||||
<SidebarMenu>
|
||||
{items.map((item) => (
|
||||
<div key={item.title}>
|
||||
{item.role.includes(session.user.role) && (
|
||||
{item.role.includes(session.user.role) && item.active && (
|
||||
<SidebarMenuItem>
|
||||
<SidebarMenuButton asChild>
|
||||
<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 { ChevronRight } from "lucide-react";
|
||||
import { ChevronRight, ScrollText } from "lucide-react";
|
||||
import { getSettings } from "../../lib/queries/getSettings";
|
||||
import {
|
||||
Collapsible,
|
||||
CollapsibleContent,
|
||||
CollapsibleTrigger,
|
||||
} from "../ui/collapsible";
|
||||
|
||||
import {
|
||||
SidebarGroup,
|
||||
SidebarGroupContent,
|
||||
@@ -19,43 +20,55 @@ import {
|
||||
useSidebar,
|
||||
} 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() {
|
||||
const { setOpen } = useSidebar();
|
||||
const { data: settings, isLoading } = useSuspenseQuery(getSettings());
|
||||
const pathname = useRouterState({
|
||||
select: (s) => s.location.pathname,
|
||||
});
|
||||
|
||||
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 (
|
||||
<SidebarGroup>
|
||||
<SidebarGroupLabel>Docs</SidebarGroupLabel>
|
||||
@@ -72,8 +85,9 @@ export default function DocBar() {
|
||||
</SidebarMenu>
|
||||
<SidebarMenu>
|
||||
{docs.map((item) => (
|
||||
<div key={item.title}>
|
||||
{item.isActive && (
|
||||
<Collapsible
|
||||
key={item.title}
|
||||
asChild
|
||||
defaultOpen={isNotifications}
|
||||
className="group/collapsible"
|
||||
@@ -81,10 +95,7 @@ export default function DocBar() {
|
||||
<SidebarMenuItem>
|
||||
<CollapsibleTrigger asChild>
|
||||
<SidebarMenuButton tooltip={item.title}>
|
||||
<Link
|
||||
to={"/docs/$"}
|
||||
params={{ _splat: `notifications${item.url}` }}
|
||||
>
|
||||
<Link to={"/docs/$"} params={{ _splat: `${item.url}` }}>
|
||||
{item.title}
|
||||
</Link>
|
||||
<ChevronRight className="ml-auto transition-transform duration-200 group-data-[state=open]/collapsible:rotate-90" />
|
||||
@@ -97,9 +108,11 @@ export default function DocBar() {
|
||||
<SidebarMenuSubButton asChild>
|
||||
<Link
|
||||
to={"/docs/$"}
|
||||
params={{ _splat: `notifications${subItem.url}` }}
|
||||
params={{ _splat: `${subItem.url}` }}
|
||||
onClick={() => setOpen(false)}
|
||||
>
|
||||
{subItem.title}
|
||||
<subItem.icon />
|
||||
<span>{subItem.title}</span>
|
||||
</Link>
|
||||
</SidebarMenuSubButton>
|
||||
</SidebarMenuSubItem>
|
||||
@@ -108,6 +121,8 @@ export default function DocBar() {
|
||||
</CollapsibleContent>
|
||||
</SidebarMenuItem>
|
||||
</Collapsible>
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
</SidebarMenu>
|
||||
</SidebarGroupContent>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { Link } from "@tanstack/react-router";
|
||||
import { ScanText, ScrollText } from "lucide-react";
|
||||
import { ScanText } from "lucide-react";
|
||||
import {
|
||||
SidebarGroup,
|
||||
SidebarGroupContent,
|
||||
@@ -10,14 +10,14 @@ import {
|
||||
useSidebar,
|
||||
} from "../ui/sidebar";
|
||||
|
||||
export default function MobileBar({ session }: any) {
|
||||
export default function MobileBar() {
|
||||
const { setOpen } = useSidebar();
|
||||
const items = [
|
||||
{
|
||||
title: "Update Instructions",
|
||||
url: "/",
|
||||
icon: ScrollText,
|
||||
},
|
||||
// {
|
||||
// title: "Update Instructions",
|
||||
// url: "/",
|
||||
// icon: ScrollText,
|
||||
// },
|
||||
{
|
||||
title: "Scan Log",
|
||||
url: "/",
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { useSuspenseQuery } from "@tanstack/react-query";
|
||||
import {
|
||||
Sidebar,
|
||||
SidebarContent,
|
||||
@@ -6,12 +7,14 @@ import {
|
||||
SidebarMenuItem,
|
||||
} from "@/components/ui/sidebar";
|
||||
import { useSession } from "@/lib/auth-client";
|
||||
import { getSettings } from "../../lib/queries/getSettings";
|
||||
import AdminSidebar from "./AdminBar";
|
||||
import DocBar from "./DocBar";
|
||||
import MobileBar from "./MobileBar";
|
||||
|
||||
export function AppSidebar() {
|
||||
const { data: session } = useSession();
|
||||
const { data: settings, isLoading } = useSuspenseQuery(getSettings());
|
||||
|
||||
return (
|
||||
<Sidebar
|
||||
@@ -24,7 +27,11 @@ export function AppSidebar() {
|
||||
<SidebarMenuItem>
|
||||
<SidebarContent>
|
||||
<DocBar />
|
||||
<MobileBar session={session} />
|
||||
{!isLoading &&
|
||||
settings.filter((n: any) => n.name === "mobile")[0].active && (
|
||||
<MobileBar />
|
||||
)}
|
||||
|
||||
{session &&
|
||||
(session.user.role === "admin" ||
|
||||
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 { 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({
|
||||
baseURL: `${window.location.origin}/lst/api/auth`,
|
||||
@@ -10,11 +11,20 @@ export const authClient = createAuthClient({
|
||||
roles: {
|
||||
admin,
|
||||
user,
|
||||
manager,
|
||||
systemAdmin,
|
||||
},
|
||||
}),
|
||||
genericOAuthClient(),
|
||||
],
|
||||
fetchOptions: {
|
||||
onError() {
|
||||
redirect({
|
||||
to: "/app-down",
|
||||
replace: true,
|
||||
});
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
export const { useSession, signUp, signIn, signOut } = authClient;
|
||||
|
||||
@@ -1,21 +1,53 @@
|
||||
import { createAccessControl } from "better-auth/plugins/access";
|
||||
import { adminAc } from "better-auth/plugins/admin/access";
|
||||
|
||||
export const statement = {
|
||||
project: ["create", "share", "update", "delete"],
|
||||
user: ["ban"],
|
||||
app: ["read", "create", "share", "update", "delete", "readAll"],
|
||||
//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;
|
||||
|
||||
export const ac = createAccessControl(statement);
|
||||
|
||||
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({
|
||||
project: ["create", "update"],
|
||||
app: ["read", "create", "update"],
|
||||
});
|
||||
|
||||
export const systemAdmin = ac.newRole({
|
||||
project: ["create", "update", "delete"],
|
||||
user: ["ban"],
|
||||
app: ["read", "create", "share", "update", "delete", "readAll"],
|
||||
//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) {
|
||||
const mod = modules[path] as DocModule;
|
||||
|
||||
const slug = path
|
||||
.replace("../docs/", "")
|
||||
.replace(".tsx", "");
|
||||
const slug = path.replace("../docs/", "").replace(".tsx", "");
|
||||
|
||||
// "notifications/intro"
|
||||
docsMap[slug] = mod.default;
|
||||
|
||||
@@ -1,25 +1,25 @@
|
||||
import { keepPreviousData, queryOptions } from "@tanstack/react-query";
|
||||
import axios from "axios";
|
||||
import { api } from "../apiHelper";
|
||||
|
||||
export function getScanUsers() {
|
||||
return queryOptions({
|
||||
queryKey: ["getScanUsers"],
|
||||
queryFn: () => fetch(),
|
||||
queryFn: () => dataFetch(),
|
||||
staleTime: 5000,
|
||||
refetchOnWindowFocus: true,
|
||||
placeholderData: keepPreviousData,
|
||||
});
|
||||
}
|
||||
|
||||
const fetch = async () => {
|
||||
const dataFetch = async () => {
|
||||
if (window.location.hostname === "localhost") {
|
||||
await new Promise((res) => setTimeout(res, 1500));
|
||||
}
|
||||
|
||||
const { data } = await axios.get("/lst/api/mobile/auth/user", {
|
||||
withCredentials: true,
|
||||
timeout: 5000,
|
||||
});
|
||||
const { data } = await api.get("/mobile/auth/user");
|
||||
if (!data.success) {
|
||||
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 axios from "axios";
|
||||
|
||||
import { api } from "../apiHelper";
|
||||
|
||||
export function getScannerIds() {
|
||||
return queryOptions({
|
||||
@@ -16,10 +17,7 @@ const fetch = async () => {
|
||||
await new Promise((res) => setTimeout(res, 1500));
|
||||
}
|
||||
|
||||
const { data } = await axios.get("/lst/api/mobile/available", {
|
||||
withCredentials: true,
|
||||
timeout: 5000,
|
||||
});
|
||||
const { data } = await api.get("/mobile/available");
|
||||
|
||||
return data.data;
|
||||
};
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { keepPreviousData, queryOptions } from "@tanstack/react-query";
|
||||
import axios from "axios";
|
||||
|
||||
import { api } from "../apiHelper";
|
||||
|
||||
export function getSettings() {
|
||||
return queryOptions({
|
||||
@@ -16,7 +17,7 @@ const fetch = async () => {
|
||||
await new Promise((res) => setTimeout(res, 1500));
|
||||
}
|
||||
|
||||
const { data } = await axios.get("/lst/api/settings");
|
||||
const { data } = await api.get("/settings");
|
||||
|
||||
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 axios from "axios";
|
||||
|
||||
import { api } from "../apiHelper";
|
||||
|
||||
export function notificationSubs(userId?: string) {
|
||||
return queryOptions({
|
||||
@@ -16,8 +17,8 @@ const fetch = async (userId?: string) => {
|
||||
await new Promise((res) => setTimeout(res, 1500));
|
||||
}
|
||||
|
||||
const { data } = await axios.get(
|
||||
`/lst/api/notification/sub${userId ? `?userId=${userId}` : ""}`,
|
||||
const { data } = await api.get(
|
||||
`/notification/sub${userId ? `?userId=${userId}` : ""}`,
|
||||
);
|
||||
|
||||
return data.data;
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { keepPreviousData, queryOptions } from "@tanstack/react-query";
|
||||
import axios from "axios";
|
||||
|
||||
import { api } from "../apiHelper";
|
||||
|
||||
export function notifications() {
|
||||
return queryOptions({
|
||||
@@ -16,7 +17,7 @@ const fetch = async () => {
|
||||
await new Promise((res) => setTimeout(res, 1500));
|
||||
}
|
||||
|
||||
const { data } = await axios.get("/lst/api/notification");
|
||||
const { data } = await api.get("/notification");
|
||||
|
||||
return data.data;
|
||||
};
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { keepPreviousData, queryOptions } from "@tanstack/react-query";
|
||||
import axios from "axios";
|
||||
|
||||
import { api } from "../apiHelper";
|
||||
|
||||
export function servers() {
|
||||
return queryOptions({
|
||||
@@ -16,7 +17,7 @@ const fetch = async () => {
|
||||
await new Promise((res) => setTimeout(res, 1500));
|
||||
}
|
||||
|
||||
const { data } = await axios.get("/lst/api/servers");
|
||||
const { data } = await api.get("/servers");
|
||||
|
||||
return data.data;
|
||||
};
|
||||
|
||||
@@ -3,6 +3,8 @@ import { StrictMode } from "react";
|
||||
import ReactDOM from "react-dom/client";
|
||||
import "./index.css";
|
||||
import { createRouter, RouterProvider } from "@tanstack/react-router";
|
||||
import NotFound from "./components/NotFound";
|
||||
import { setApiRouter } from "./lib/apiHelper";
|
||||
import socket from "./lib/socket.io";
|
||||
import { loadUmami } from "./lib/umami.utils";
|
||||
// Import the generated route tree
|
||||
@@ -13,8 +15,9 @@ const queryClient = new QueryClient({
|
||||
defaultOptions: {
|
||||
queries: {
|
||||
staleTime: 1000 * 60 * 5,
|
||||
retry: 0,
|
||||
retry: 2,
|
||||
refetchOnWindowFocus: true,
|
||||
retryDelay: (attempt) => Math.min(1000 * 2 ** attempt, 5000),
|
||||
},
|
||||
},
|
||||
});
|
||||
@@ -27,8 +30,11 @@ const router = createRouter({
|
||||
context: {
|
||||
queryClient,
|
||||
},
|
||||
defaultNotFoundComponent: NotFound,
|
||||
});
|
||||
|
||||
setApiRouter(router);
|
||||
|
||||
// Register the router instance for type safety
|
||||
declare module "@tanstack/react-router" {
|
||||
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.
|
||||
|
||||
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 IndexRouteImport } from './routes/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 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({
|
||||
id: '/about',
|
||||
path: '/about',
|
||||
@@ -98,6 +110,8 @@ const authUserProfileRoute = authUserProfileRouteImport.update({
|
||||
export interface FileRoutesByFullPath {
|
||||
'/': typeof IndexRoute
|
||||
'/about': typeof AboutRoute
|
||||
'/app-down': typeof AppDownRoute
|
||||
'/forbidden': typeof ForbiddenRoute
|
||||
'/login': typeof authLoginRoute
|
||||
'/admin/logs': typeof AdminLogsRoute
|
||||
'/admin/notifications': typeof AdminNotificationsRoute
|
||||
@@ -114,6 +128,8 @@ export interface FileRoutesByFullPath {
|
||||
export interface FileRoutesByTo {
|
||||
'/': typeof IndexRoute
|
||||
'/about': typeof AboutRoute
|
||||
'/app-down': typeof AppDownRoute
|
||||
'/forbidden': typeof ForbiddenRoute
|
||||
'/login': typeof authLoginRoute
|
||||
'/admin/logs': typeof AdminLogsRoute
|
||||
'/admin/notifications': typeof AdminNotificationsRoute
|
||||
@@ -131,6 +147,8 @@ export interface FileRoutesById {
|
||||
__root__: typeof rootRouteImport
|
||||
'/': typeof IndexRoute
|
||||
'/about': typeof AboutRoute
|
||||
'/app-down': typeof AppDownRoute
|
||||
'/forbidden': typeof ForbiddenRoute
|
||||
'/(auth)/login': typeof authLoginRoute
|
||||
'/admin/logs': typeof AdminLogsRoute
|
||||
'/admin/notifications': typeof AdminNotificationsRoute
|
||||
@@ -149,6 +167,8 @@ export interface FileRouteTypes {
|
||||
fullPaths:
|
||||
| '/'
|
||||
| '/about'
|
||||
| '/app-down'
|
||||
| '/forbidden'
|
||||
| '/login'
|
||||
| '/admin/logs'
|
||||
| '/admin/notifications'
|
||||
@@ -165,6 +185,8 @@ export interface FileRouteTypes {
|
||||
to:
|
||||
| '/'
|
||||
| '/about'
|
||||
| '/app-down'
|
||||
| '/forbidden'
|
||||
| '/login'
|
||||
| '/admin/logs'
|
||||
| '/admin/notifications'
|
||||
@@ -181,6 +203,8 @@ export interface FileRouteTypes {
|
||||
| '__root__'
|
||||
| '/'
|
||||
| '/about'
|
||||
| '/app-down'
|
||||
| '/forbidden'
|
||||
| '/(auth)/login'
|
||||
| '/admin/logs'
|
||||
| '/admin/notifications'
|
||||
@@ -198,6 +222,8 @@ export interface FileRouteTypes {
|
||||
export interface RootRouteChildren {
|
||||
IndexRoute: typeof IndexRoute
|
||||
AboutRoute: typeof AboutRoute
|
||||
AppDownRoute: typeof AppDownRoute
|
||||
ForbiddenRoute: typeof ForbiddenRoute
|
||||
authLoginRoute: typeof authLoginRoute
|
||||
AdminLogsRoute: typeof AdminLogsRoute
|
||||
AdminNotificationsRoute: typeof AdminNotificationsRoute
|
||||
@@ -214,6 +240,20 @@ export interface RootRouteChildren {
|
||||
|
||||
declare module '@tanstack/react-router' {
|
||||
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': {
|
||||
id: '/about'
|
||||
path: '/about'
|
||||
@@ -318,6 +358,8 @@ declare module '@tanstack/react-router' {
|
||||
const rootRouteChildren: RootRouteChildren = {
|
||||
IndexRoute: IndexRoute,
|
||||
AboutRoute: AboutRoute,
|
||||
AppDownRoute: AppDownRoute,
|
||||
ForbiddenRoute: ForbiddenRoute,
|
||||
authLoginRoute: authLoginRoute,
|
||||
AdminLogsRoute: AdminLogsRoute,
|
||||
AdminNotificationsRoute: AdminNotificationsRoute,
|
||||
|
||||
@@ -45,14 +45,14 @@ export default function NotificationsSubCard({ user }: any) {
|
||||
|
||||
let n: any = [];
|
||||
if (data) {
|
||||
n = data.map((i: any) => ({
|
||||
n = data
|
||||
.filter((n: any) => n.active)
|
||||
.map((i: any) => ({
|
||||
label: i.name,
|
||||
value: i.id,
|
||||
}));
|
||||
}
|
||||
|
||||
console.log(n);
|
||||
|
||||
return (
|
||||
<div>
|
||||
<Card className="p-3 w-lg">
|
||||
|
||||
@@ -10,6 +10,7 @@ import {
|
||||
} from "@/components/ui/card";
|
||||
import { authClient, useSession } from "@/lib/auth-client";
|
||||
import { useAppForm } from "@/lib/formSutff";
|
||||
|
||||
import { Spinner } from "../../components/ui/spinner";
|
||||
import ChangePassword from "./-components/ChangePassword";
|
||||
import NotificationsSubCard from "./-components/NotificationsSubCard";
|
||||
@@ -37,6 +38,7 @@ export const Route = createFileRoute("/(auth)/user/profile")({
|
||||
|
||||
function RouteComponent() {
|
||||
const { data: session } = useSession();
|
||||
|
||||
const form = useAppForm({
|
||||
defaultValues: {
|
||||
name: session?.user.name,
|
||||
|
||||
@@ -3,7 +3,7 @@ import { createFileRoute, redirect } from "@tanstack/react-router";
|
||||
import { createColumnHelper } from "@tanstack/react-table";
|
||||
import axios from "axios";
|
||||
import { format } from "date-fns-tz";
|
||||
import { CircleFadingArrowUp, Trash } from "lucide-react";
|
||||
import { Trash } from "lucide-react";
|
||||
import { Suspense, useState } from "react";
|
||||
import { toast } from "sonner";
|
||||
import { Button } from "../../components/ui/button";
|
||||
@@ -19,7 +19,7 @@ import NewScanUser from "./-components/NewScanUser";
|
||||
export const Route = createFileRoute("/admin/scanUsers")({
|
||||
beforeLoad: async ({ location }) => {
|
||||
const { data: session } = await authClient.getSession();
|
||||
const allowedRole = ["systemAdmin", "admin"];
|
||||
const allowedRole = ["systemAdmin", "admin", "manager"];
|
||||
|
||||
if (!session?.user) {
|
||||
throw redirect({
|
||||
@@ -111,7 +111,7 @@ const ScanUserTable = () => {
|
||||
<div>
|
||||
<EditableCellInput
|
||||
value={getValue()}
|
||||
id={row.original.name}
|
||||
id={row.original.id}
|
||||
field="value"
|
||||
onSubmit={({ id, field, value }) => {
|
||||
updateSetting.mutate({ id, field, value });
|
||||
|
||||
@@ -55,7 +55,17 @@ const ServerTable = () => {
|
||||
<SearchableHeader column={column} title="Name" searchable={true} />
|
||||
),
|
||||
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", {
|
||||
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 z from "zod";
|
||||
|
||||
import { Button } from "../components/ui/button";
|
||||
import { useSession } from "../lib/auth-client";
|
||||
import { trackLstEvent } from "../lib/umami.utils";
|
||||
import { runtimeConfig, trackLstEvent } from "../lib/umami.utils";
|
||||
|
||||
export const Route = createFileRoute("/")({
|
||||
validateSearch: z.object({
|
||||
@@ -14,7 +14,7 @@ export const Route = createFileRoute("/")({
|
||||
});
|
||||
|
||||
function Index() {
|
||||
const { isPending } = useSession();
|
||||
const { data: session, isPending } = useSession();
|
||||
|
||||
if (isPending)
|
||||
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 (
|
||||
<div className="flex justify-center m-10 flex-col">
|
||||
<h3 className="w-2xl text-3xl">Welcome Lst - V3</h3>
|
||||
@@ -55,7 +65,7 @@ function Index() {
|
||||
<strong>Click</strong>
|
||||
</b>
|
||||
</a>{" "}
|
||||
<button onClick={click}>
|
||||
<button onClick={click} type="button">
|
||||
<a
|
||||
href={`https://www.youtube.com/watch?v=dQw4w9WgXcQ`}
|
||||
target="_blank"
|
||||
@@ -67,6 +77,9 @@ function Index() {
|
||||
</a>
|
||||
</button>
|
||||
</p>
|
||||
{session && session.user.role === "systemAdmin" && (
|
||||
<Button onClick={checkConfig}>Check config</Button>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
"foregroundImage": "./assets/adaptive-icon-white.png",
|
||||
"backgroundColor": "#ffffff"
|
||||
},
|
||||
"versionCode": 33,
|
||||
"versionCode": 37,
|
||||
"minSupportedVersionCode": 33,
|
||||
"predictiveBackGestureEnabled": false,
|
||||
"package": "net.alpla.lst.mobile"
|
||||
|
||||
@@ -22,7 +22,7 @@ export default function TabsLayout() {
|
||||
const isUnlocked = useMobileAuthStore((s) => s.isUnlocked);
|
||||
|
||||
const port = parseInt(serverPort || "0", 10) >= 50000;
|
||||
console.log(port);
|
||||
|
||||
if (!port) {
|
||||
if (!user || !isUnlocked) {
|
||||
return <Redirect href="/login" />;
|
||||
@@ -58,14 +58,14 @@ export default function TabsLayout() {
|
||||
// },
|
||||
}}
|
||||
/>
|
||||
<Tabs.Screen
|
||||
{/* <Tabs.Screen
|
||||
name="ppoo"
|
||||
options={{
|
||||
title: "PPOO",
|
||||
href: isNormalScanner ? null : "/(tabs)/ppoo",
|
||||
tabBarIcon: ({ color, size }) => <Boxes size={size} color={color} />,
|
||||
}}
|
||||
/>
|
||||
/> */}
|
||||
<Tabs.Screen
|
||||
name="laneCheck"
|
||||
options={{
|
||||
|
||||
@@ -1,18 +1,210 @@
|
||||
import React from "react";
|
||||
import { Text, View } from "react-native";
|
||||
import axios from "axios";
|
||||
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 { 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() {
|
||||
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 (
|
||||
<View
|
||||
style={{
|
||||
flex: 1,
|
||||
//justifyContent: "center",
|
||||
alignItems: "center",
|
||||
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>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import axios from "axios";
|
||||
import { format } from "date-fns-tz";
|
||||
import Constants from "expo-constants";
|
||||
import { Redirect, useFocusEffect, useRouter } from "expo-router";
|
||||
import { useCallback, useEffect, useState } from "react";
|
||||
import { Alert, Button, Text, View } from "react-native";
|
||||
@@ -31,6 +32,7 @@ export default function LSTScanner() {
|
||||
const [tagScans, setTagScans] = useState<any>([]);
|
||||
const serverIp = useAppStore((s) => s.serverIp);
|
||||
const [bgColor, setBGColor] = useState<string | null>(null);
|
||||
const build = Constants.expoConfig?.android?.versionCode ?? 1;
|
||||
|
||||
const handleScan = useCallback(
|
||||
async (scan: ZebraScanResult) => {
|
||||
@@ -93,6 +95,7 @@ export default function LSTScanner() {
|
||||
: scan.data.startsWith("loc")
|
||||
? scan.data
|
||||
: "0",
|
||||
scannerVersion: build,
|
||||
});
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
@@ -146,6 +149,7 @@ export default function LSTScanner() {
|
||||
user?.name,
|
||||
user?.excludedCommand?.some,
|
||||
user?.excludedCommand,
|
||||
build,
|
||||
],
|
||||
);
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import axios from "axios";
|
||||
import { format } from "date-fns-tz";
|
||||
import Constants from "expo-constants";
|
||||
import { useFocusEffect } from "expo-router";
|
||||
import { useCallback, useEffect, useState } from "react";
|
||||
import { Text, View } from "react-native";
|
||||
@@ -25,6 +26,7 @@ export default function ProdScanner() {
|
||||
const serverIp = useAppStore((s) => s.serverIp);
|
||||
const serverPort = useAppStore((s) => s.serverPort);
|
||||
const [bgColor, setBGColor] = useState<string | null>(null);
|
||||
const build = Constants.expoConfig?.android?.versionCode ?? 1;
|
||||
|
||||
const handleScan = useCallback(
|
||||
async (scan: ZebraScanResult) => {
|
||||
@@ -62,6 +64,7 @@ export default function ProdScanner() {
|
||||
: scan.data.startsWith("loc")
|
||||
? scan.data
|
||||
: "0",
|
||||
scannerVersion: build,
|
||||
};
|
||||
try {
|
||||
await axios.post(
|
||||
@@ -112,7 +115,7 @@ export default function ProdScanner() {
|
||||
setTagScans([]);
|
||||
}
|
||||
},
|
||||
[scannerIdFromStore, serverIp, serverPort, setLastScan],
|
||||
[scannerIdFromStore, serverIp, serverPort, setLastScan, build],
|
||||
);
|
||||
|
||||
const clearScans = () => {
|
||||
|
||||
@@ -6,7 +6,7 @@ import TcpSocket from "react-native-tcp-socket";
|
||||
type TcpResponse = {
|
||||
success: boolean;
|
||||
message: string;
|
||||
data: string[];
|
||||
data: ScannerEvent;
|
||||
};
|
||||
|
||||
type ScannerEvent = {
|
||||
@@ -232,7 +232,7 @@ export async function sendTcpMessage(
|
||||
timeoutMs = 5000,
|
||||
): Promise<TcpResponse> {
|
||||
return new Promise((resolve) => {
|
||||
const responses: any = [];
|
||||
//const responses: any = [];
|
||||
|
||||
const client = TcpSocket.createConnection({ host, port }, () => {
|
||||
//console.log("Sending TCP (visible):", `${command}`);
|
||||
@@ -240,17 +240,53 @@ export async function sendTcpMessage(
|
||||
client.write(command);
|
||||
});
|
||||
|
||||
const timeout = setTimeout(() => {
|
||||
client.destroy();
|
||||
let settled = false;
|
||||
|
||||
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,
|
||||
message: "TCP timeout",
|
||||
data: responses,
|
||||
message: "No response from scanner server",
|
||||
data: {
|
||||
scannerId: "999",
|
||||
commandDescription: "TCP Command",
|
||||
prompt: command,
|
||||
message: "Invalid command",
|
||||
status: "error",
|
||||
lines: [
|
||||
"SYSTEM",
|
||||
"TCP Command",
|
||||
`Scan: ${command}`,
|
||||
"Invalid command",
|
||||
],
|
||||
},
|
||||
});
|
||||
}, timeoutMs);
|
||||
|
||||
client.on("data", (data) => {
|
||||
client.on("data", (data: any) => {
|
||||
//console.log("TCP received:", text);
|
||||
const parsed = parseScannerText(data);
|
||||
//console.log("scanned:", parsed);
|
||||
@@ -260,32 +296,69 @@ export async function sendTcpMessage(
|
||||
const cleaned = parseScannerEvent(parsed);
|
||||
|
||||
//console.log(responses);
|
||||
clearTimeout(timeout);
|
||||
resolve({
|
||||
success: true,
|
||||
message: "TCP Response",
|
||||
data: cleaned as any,
|
||||
// clearTimeout(timeout);
|
||||
// resolve({
|
||||
// success: true,
|
||||
// message: "TCP Response",
|
||||
// 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) => {
|
||||
clearTimeout(timeout);
|
||||
client.destroy();
|
||||
// resolve({
|
||||
// success: false,
|
||||
// message: err.message,
|
||||
// data: ["Error", "Please try again"] as any,
|
||||
// });
|
||||
|
||||
resolve({
|
||||
done({
|
||||
success: false,
|
||||
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", () => {
|
||||
clearTimeout(timeout);
|
||||
if (settled) return;
|
||||
|
||||
resolve({
|
||||
success: true,
|
||||
message: "TCP complete",
|
||||
data: ["Error", "Please try again"],
|
||||
// resolve({
|
||||
// success: true,
|
||||
// message: "TCP complete",
|
||||
// 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,
|
||||
"tag": "0051_sad_war_machine",
|
||||
"breakpoints": true
|
||||
},
|
||||
{
|
||||
"idx": 52,
|
||||
"version": "7",
|
||||
"when": 1778533475205,
|
||||
"tag": "0052_numerous_wasp",
|
||||
"breakpoints": true
|
||||
}
|
||||
]
|
||||
}
|
||||
4
package-lock.json
generated
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "lst_v3",
|
||||
"version": "0.0.2-alpha.10",
|
||||
"version": "0.1.0-alpha.0",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "lst_v3",
|
||||
"version": "0.0.2-alpha.10",
|
||||
"version": "0.1.0-alpha.0",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"@dotenvx/dotenvx": "^1.57.0",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "lst_v3",
|
||||
"version": "0.0.2-alpha.10",
|
||||
"version": "0.1.0-alpha.0",
|
||||
"description": "The tool that supports us in our everyday alplaprod",
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
|
||||
@@ -15,7 +15,7 @@ param (
|
||||
|
||||
# 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 "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"
|
||||
|
||||
$nssmPath = $AppPath + "\nssm.exe"
|
||||
|
||||
@@ -87,7 +87,15 @@ function Update-Server {
|
||||
param ($Server, $Token, $Destination, $BuildFile)
|
||||
|
||||
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)) {
|
||||
Write-Host ".env not found, creating..."
|
||||
@@ -197,7 +205,7 @@ function Update-Server {
|
||||
Write-Host "Install/update completed."
|
||||
|
||||
# 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
|
||||
# Push-Location $LocalPath
|
||||
|
||||