agent starting :D

This commit is contained in:
2026-03-01 14:10:19 -06:00
parent c3379919b9
commit 68d13b03d3
34 changed files with 1905 additions and 254 deletions

View File

@@ -52,7 +52,9 @@
"alpla",
"alplamart",
"alplaprod",
"bookin",
"Datamart",
"dyco",
"intiallally",
"manadatory",
"OCME",
@@ -61,6 +63,7 @@
"opendocks",
"ppoo",
"prodlabels",
"prolink",
"trycatch"
],
"gitea.token": "8456def90e1c651a761a8711763d6ef225d6b2db",

View File

@@ -28,7 +28,7 @@ const createApp = async () => {
app.use(lstCors());
setupRoutes(baseUrl, app);
log.info("Express app created");
log.info("Lst app created");
return { app, baseUrl };
};

View File

@@ -12,6 +12,7 @@ import type { OpenAPIV3_1 } from "openapi-types";
import { cronerActiveJobs } from "../scaler/cronerActiveJobs.spec.js";
import { cronerStatusChange } from "../scaler/cronerStatusChange.spec.js";
import { prodLoginSpec } from "../scaler/login.spec.js";
import { openDockApt } from "../scaler/opendockGetRelease.spec.js";
import { prodRestartSpec } from "../scaler/prodSqlRestart.spec.js";
import { prodStartSpec } from "../scaler/prodSqlStart.spec.js";
import { prodStopSpec } from "../scaler/prodSqlStop.spec.js";
@@ -33,6 +34,7 @@ export const openApiBase: OpenAPIV3_1.Document = {
description: "Development server",
},
],
components: {
securitySchemes: {
bearerAuth: {
@@ -87,6 +89,10 @@ export const openApiBase: OpenAPIV3_1.Document = {
name: "Utils",
description: "All routes related to the utilities on the server",
},
{
name: "Open Dock",
description: "All routes related to the opendock on the server",
},
// { name: "TMS", description: "TMS integration" },
],
paths: {}, // Will be populated
@@ -121,6 +127,7 @@ export const setupApiDocsRoutes = (baseUrl: string, app: Express) => {
//...mergedDatamart,
...cronerActiveJobs,
...cronerStatusChange,
...openDockApt,
// Add more specs here as you build features
},
@@ -134,7 +141,9 @@ export const setupApiDocsRoutes = (baseUrl: string, app: Express) => {
apiReference({
url: `${baseUrl}/api/docs.json`,
theme: "purple",
darkMode: true,
persistAuth: true,
authentication: {
securitySchemes: {
httpBasic: {

View File

@@ -1,5 +1,6 @@
import {
boolean,
integer,
jsonb,
pgEnum,
pgTable,
@@ -13,9 +14,9 @@ import { createInsertSchema, createSelectSchema } from "drizzle-zod";
import { z } from "zod";
export const settingType = pgEnum("setting_type", [
"feature",
"system",
"standard",
"feature", // when changed deals with triggering the croner related to this
"system", // when changed fires a system restart but this should be rare and all these settings should be in the env
"standard", // will be effected by the next process, either croner or manual trigger
]);
export const settings = pgTable(
@@ -27,8 +28,9 @@ export const settings = pgTable(
description: text("description"),
moduleName: text("moduleName"), // what part of lst dose it belong to this is used to split the settings out later
active: boolean("active").default(true),
roles: jsonb("roles").notNull().default(["systemAdmin"]), // role or roles to see this goes along with the moduleName, need to have a x role in module to see this setting.
roles: jsonb("roles").$type<string[]>().notNull().default(["systemAdmin"]), // role or roles to see this goes along with the moduleName, need to have a x role in module to see this setting.
settingType: settingType(),
seedVersion: integer("seed_version").default(1), // this is intended for if we want to update the settings.
add_User: text("add_User").default("LST_System").notNull(),
add_Date: timestamp("add_Date").defaultNow(),
upd_user: text("upd_User").default("LST_System").notNull(),

View File

@@ -0,0 +1,10 @@
import type { InferSelectModel } from "drizzle-orm";
import { integer, pgTable, text, timestamp } from "drizzle-orm/pg-core";
export const serverStats = pgTable("stats", {
id: text("id").primaryKey().default("serverStats"),
build: integer("build").notNull().default(1),
lastUpdate: timestamp("last_update").defaultNow(),
});
export type ServerStats = InferSelectModel<typeof serverStats>;

View File

@@ -0,0 +1,37 @@
import { and, eq } from "drizzle-orm";
import type { NextFunction, Request, Response } from "express";
import { db } from "../db/db.controller.js";
import { settings } from "../db/schema/settings.schema.js";
import { tryCatch } from "../utils/trycatch.utils.js";
/**
*
* @param moduleName name of the module we are checking if is enabled or not.
*/
export const featureCheck = (moduleName: string) => {
// get the features from the settings
return async (_req: Request, res: Response, next: NextFunction) => {
const { data: sData, error: sError } = await tryCatch(
db
.select()
.from(settings)
.where(
and(
eq(settings.settingType, "feature"),
eq(settings.name, moduleName),
),
),
);
if (sError) {
return res.status(403).json({ error: "Internal Error" });
}
if (!sData?.length || !sData[0]?.active) {
return res.status(403).json({ error: "Feature disabled" });
}
next();
};
};

View File

@@ -1,7 +1,8 @@
import axios from "axios";
import { settings } from "backend/db/schema/settings.schema.js";
import { addHours } from "date-fns";
import { formatInTimeZone } from "date-fns-tz";
import { sql } from "drizzle-orm";
import { eq, sql } from "drizzle-orm";
import { db } from "../db/db.controller.js";
import { opendockApt } from "../db/schema/opendock.schema.js";
import { createLogger } from "../logger/logger.controller.js";
@@ -273,7 +274,10 @@ export const monitorReleaseChanges = async () => {
// TODO: validate if the setting for opendocks is active and start / stop the system based on this
// if it changes we set to false and the next loop will stop.
const openDockMonitor = true;
const openDockMonitor = await db
.select()
.from(settings)
.where(eq(settings.name, "opendock_sync"));
// console.info("Starting release monitor", lastCheck);
const sqlQuery = sqlQuerySelector(`releaseChecks`) as SqlQuery;
@@ -290,8 +294,8 @@ export const monitorReleaseChanges = async () => {
});
}
if (openDockMonitor) {
createCronJob("open-dock-monitor", "*/15 * * * * *", async () => {
if (openDockMonitor[0]?.active) {
createCronJob("opendock_sync", "*/15 * * * * *", async () => {
try {
const result = await prodQuery(
sqlQuery.query.replace("[dateCheck]", `'${lastCheck}'`),

View File

@@ -1,11 +1,17 @@
import { type Express, Router } from "express";
import { requireAuth } from "../middleware/auth.middleware.js";
import { featureCheck } from "../middleware/featureActive.middleware.js";
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);

View File

@@ -0,0 +1,36 @@
import type { OpenAPIV3_1 } from "openapi-types";
export const openDockApt: OpenAPIV3_1.PathsObject = {
"/api/opendock": {
get: {
summary: "Open Dock apt",
description: "Returns the last 30 days of apt(s).",
tags: ["Open Dock"],
responses: {
"200": {
description: "Jobs returned",
content: {
"application/json": {
schema: {
type: "object",
properties: {
success: {
type: "boolean",
format: "boolean",
example: true,
},
message: {
type: "string",
format: "string",
example:
"The first 5 Apt(s) that were created in the last 30 days",
},
},
},
},
},
},
},
},
},
};

View File

@@ -7,6 +7,7 @@ import { monitorReleaseChanges } from "./opendock/openDockRreleaseMonitor.utils.
import { opendockSocketMonitor } from "./opendock/opendockSocketMonitor.utils.js";
import { connectProdSql } from "./prodSql/prodSqlConnection.controller.js";
import { setupSocketIORoutes } from "./socket.io/serverSetup.js";
import { baseSettingValidationCheck } from "./system/settingsBase.controller.js";
import { createCronJob } from "./utils/croner.utils.js";
const port = Number(process.env.PORT) || 3000;
@@ -17,15 +18,20 @@ const start = async () => {
// triggering long lived processes
connectProdSql();
// start long live processes
// trigger startup processes these must run before anything else can run
await baseSettingValidationCheck();
//when starting up long lived features the name must match the setting name.
setTimeout(() => {
monitorReleaseChanges(); // this is od monitoring the db for all new releases
opendockSocketMonitor();
createCronJob("JobAuditLogCleanUp", "* 0 5 * * * *", () =>
// cleanup sql jobs
createCronJob("JobAuditLogCleanUp", "0 0 5 * * *", () =>
dbCleanup("jobs", 30),
);
createCronJob("logsCleanup", "* 15 5 * * * *", () => dbCleanup("logs", 30));
createCronJob("opendockAptCleanup", "* 30 5 * * * *", () =>
createCronJob("logsCleanup", "0 15 5 * * *", () => dbCleanup("logs", 120));
createCronJob("opendockAptCleanup", "0 30 5 * * *", () =>
dbCleanup("opendockApt", 90),
);
}, 5 * 1000);

View File

@@ -15,7 +15,7 @@ const r = Router();
r.get("/", async (_, res: Response) => {
const { data: sName, error: sError } = await tryCatch(
db.select().from(settings),
db.select().from(settings).orderBy(settings.name),
);
if (sError) {

View File

@@ -0,0 +1,334 @@
import { sql } from "drizzle-orm";
import { db } from "../db/db.controller.js";
import { type NewSetting, settings } from "../db/schema/settings.schema.js";
import { createLogger } from "../logger/logger.controller.js";
import { tryCatch } from "../utils/trycatch.utils.js";
const newSettings: NewSetting[] = [
// feature settings
{
name: "opendock_sync",
value: "0",
active: true,
description: "Dock Scheduling system",
moduleName: "opendock",
settingType: "feature",
roles: ["admin"],
seedVersion: 1,
},
{
name: "ocp",
value: "1",
active: true,
description: "One click print",
moduleName: "ocp",
settingType: "feature",
roles: ["admin"],
seedVersion: 1,
},
{
name: "ocme",
value: "0",
active: true,
description: "Dayton Agv system",
moduleName: "ocme",
settingType: "feature",
roles: ["admin"],
seedVersion: 1,
},
{
name: "demandManagement",
value: "1",
active: true,
description: "Fake EDI System",
moduleName: "demandManagement",
settingType: "feature",
roles: ["admin"],
seedVersion: 1,
},
{
name: "qualityRequest",
value: "0",
active: true,
description: "Quality System",
moduleName: "qualityRequest",
settingType: "feature",
roles: ["admin"],
seedVersion: 1,
},
{
name: "tms",
value: "0",
active: true,
description: "Transport system integration",
moduleName: "tms",
settingType: "feature",
roles: ["admin"],
seedVersion: 1,
},
// standard settings
{
name: "prolinkCheck",
value: "1",
active: true,
description:
"Will prolink be considered to check if matches, mainly used in plants that do not fully utilize prolink + ocp",
moduleName: "ocp",
settingType: "standard",
roles: ["admin"],
seedVersion: 1,
},
{
name: "bookin",
value: "1",
active: true,
description: "Will we book in the labels after they are printed.",
moduleName: "ocp",
settingType: "standard",
roles: ["admin", "supervisor"],
seedVersion: 2,
},
{
name: "printDelay",
value: "90",
active: true,
description: "The default time between label printing",
moduleName: "ocp",
settingType: "standard",
roles: ["admin"],
seedVersion: 1,
},
{
name: "dualPrinting",
value: "0",
active: true,
description: "Are we producing on 2 lines that pack into the 1 packer",
moduleName: "ocp",
settingType: "standard",
roles: ["admin"],
seedVersion: 1,
},
{
name: "fifoCheck",
value: "45",
active: true,
description:
"This check is used to do a more near fifo check when pulling pallets for the agv's",
moduleName: "ocme",
settingType: "standard",
roles: ["admin"],
seedVersion: 1,
},
{
name: "dayCheck",
value: "3",
active: true,
description: "how many days +/- to check for shipments in alplaprod",
moduleName: "ocme",
settingType: "standard",
roles: ["admin"],
seedVersion: 1,
},
{
name: "maxLotsPerTruck",
value: "3",
active: true,
description:
"What is the maximum amount of lots that can be pulled for a truck this",
moduleName: "ocme",
settingType: "standard",
roles: ["admin"],
seedVersion: 1,
},
{
name: "monitorAddress",
value: "8",
active: true,
description:
"What address(2) are we monitoring for lot restrictions. multiple addresses can be used but should be separated by a comma ,",
moduleName: "ocme",
settingType: "standard",
roles: ["admin"],
seedVersion: 1,
},
{
name: "ocmeCycleCount",
value: "0",
active: true,
description:
"Are you enabling the system count page? meaning that we will allow a 'manual cycle count' vs full auto",
moduleName: "ocme",
settingType: "standard",
roles: ["admin"],
seedVersion: 1,
},
{
name: "inhouseDelivery",
value: "0",
active: true,
description:
"This is for in-house plants that deliver direct to the customer, note if book-in is off this will be ignored ",
moduleName: "ocp",
settingType: "standard",
roles: ["admin"],
seedVersion: 1,
},
{
name: "dycoConnect",
value: "0",
active: true,
description: "Connection to the dyco in dayton for the labeling process",
moduleName: "ocp",
settingType: "standard",
roles: ["admin"],
seedVersion: 1,
},
{
name: "dycoPrint",
value: "0",
active: true,
description:
"This tells us we want to use the dyco system to print the labels",
moduleName: "ocp",
settingType: "standard",
roles: ["admin"],
seedVersion: 1,
},
{
name: "strapperCheck",
value: "0",
active: true,
description:
"Strapper alarm check this is more used in tandem with dycoPrint setting",
moduleName: "ocp",
settingType: "standard",
roles: ["admin"],
seedVersion: 1,
},
{
name: "rfid_ocp",
value: "0",
active: true,
description:
"Enable the rfid pallet mgt system this replaces dycoPrint but can be overridden by dycoPrint",
moduleName: "ocp",
settingType: "standard",
roles: ["admin"],
seedVersion: 1,
},
{
name: "ocpCycleDelay",
value: "10",
active: true,
description:
"How long is the delay to fire off the printer check status, Defaulting to 10 seconds",
moduleName: "ocp",
settingType: "standard",
roles: ["admin"],
seedVersion: 1,
},
{
name: "pNgAddress",
value: "139",
active: true,
description:
"This is the P&G address that is used when uploading the new forecast from P&G",
moduleName: "demandManagement",
settingType: "standard",
roles: ["admin"],
seedVersion: 1,
},
{
name: "zechetti_1",
value: "0",
active: true,
description:
"Active the Zechetti 1 process, to print from and no longer utilize the pc",
moduleName: "ocp",
settingType: "standard",
roles: ["admin"],
seedVersion: 1,
},
{
name: "zechetti_2",
value: "0",
active: true,
description:
"Active the Zechetti 2 process, to print from and no longer utilize the pc",
moduleName: "ocp",
settingType: "standard",
roles: ["admin"],
seedVersion: 1,
},
{
name: "checkColor",
value: "0",
active: true,
description:
"During the material check are we going to check the color has enough provided to create the next pallet",
moduleName: "ocp",
settingType: "standard",
roles: ["admin"],
seedVersion: 1,
},
{
name: "checkPKG",
value: "0",
active: true,
description:
"During the material check are we going to check the packaging has enough provided to create the next pallet",
moduleName: "ocp",
settingType: "standard",
roles: ["admin"],
seedVersion: 1,
},
{
name: "lotPrintDelay",
value: "0",
active: true,
description:
"Override ride the printer printer delay setting, by default if this is on all printers are controlled by this.",
moduleName: "ocp",
settingType: "standard",
roles: ["admin"],
seedVersion: 1,
},
];
export const baseSettingValidationCheck = async () => {
const log = createLogger({ module: "system", subModule: "settings" });
const { data, error } = await tryCatch(
db
.insert(settings)
.values(newSettings)
.onConflictDoUpdate({
target: settings.name,
set: {
roles: sql`excluded.roles`,
description: sql`excluded.description`,
moduleName: sql`excluded."moduleName"`,
settingType: sql`excluded."settingType"`,
seedVersion: sql`excluded.seed_version`,
upd_user: "LST_System",
upd_date: sql`now()`,
},
where: sql`
settings.seed_version IS NULL
OR settings.seed_version < excluded.seed_version
`,
})
.returning(),
);
if (error) {
log.error(
{ error: error },
"There was an error when adding or updating the settings.",
);
}
if (data) {
log.info({}, "All Settings were added/updated");
}
};

View File

@@ -0,0 +1,16 @@
/**
* When a feature setting gets updated we will handle it here.
* we will stop jobs, stop cycles
*/
import type { Setting } from "../db/schema/settings.schema.js";
import { resumeCronJob, stopCronJob } from "../utils/croner.utils.js";
export const featureControl = async (data: Setting) => {
// when a feature is changed to active or deactivated we will update the cron.
if (data.active) {
resumeCronJob(data.name);
} else {
stopCronJob(data.name);
}
};

View File

@@ -5,9 +5,8 @@ import { settings } from "../db/schema/settings.schema.js";
import { apiReturn } from "../utils/returnHelper.utils.js";
import { tryCatch } from "../utils/trycatch.utils.js";
import { featureControl } from "./settingsFeatures.controller.js";
// export const updateSetting = async (setting: Setting) => {
// // TODO: when the setting is a feature setting we will need to have it run each kill switch on the crons well just stop them and during a reset it just wont start them
// // TODO: when the setting is a system we will need to force an app restart
// // TODO: when the setting is standard we don't do anything.
// };
@@ -38,17 +37,17 @@ r.patch("/:name", async (req: Request, res: Response) => {
});
}
// if (sName?.length === 0) {
// return apiReturn(res, {
// success: false,
// level: "error",
// module: "system",
// subModule: "settings",
// message: `The setting "${name}" dose not appear to be a valid setting please check the name and try again. `,
// data: [],
// status: 400,
// });
// }
if (sName?.length === 0) {
return apiReturn(res, {
success: false,
level: "error",
module: "system",
subModule: "settings",
message: `The setting "${name}" dose not appear to be a valid setting please check the name and try again. `,
data: [],
status: 400,
});
}
// manage the actual setting. we will still do an upsert just in case we strangely get past everything
@@ -79,14 +78,32 @@ r.patch("/:name", async (req: Request, res: Response) => {
updates.upd_user = req.user?.username || "lst_user";
updates.upd_date = sql`NOW()`;
const updatedSetting = await db
.update(settings)
.set(updates)
.where(eq(settings.name, name ?? ""))
.returning();
// the switch statment will only run when the setting actually updates
switch (updatedSetting[0]?.settingType) {
case "feature":
await featureControl(updatedSetting[0]);
break;
case "system":
// TODO: add the system control logic in to restart the app if not in dev mode
break;
default:
break;
}
return apiReturn(res, {
success: true,
level: "info",
module: "system",
subModule: "settings",
message: `Setting "${name}" Was just updated. `,
data: [updates],
status: 400,
data: updatedSetting,
status: 200,
});
});

View File

@@ -18,9 +18,10 @@ export interface JobInfo {
// Store running cronjobs
export const runningCrons: Record<string, Cron> = {};
const log = createLogger({ module: "system", subModule: "croner" });
// how to se the times
// * ┌──────────────── (optional) second (0 - 59) \n
// * ┌──────────────── (optional) second (0 - 59)
// * │ ┌────────────── minute (0 - 59)
// * │ │ ┌──────────── hour (0 - 23)
// * │ │ │ ┌────────── day of month (1 - 31)
@@ -30,6 +31,8 @@ export const runningCrons: Record<string, Cron> = {};
// * │ │ │ │ │ │ ┌──── (optional) year (1 - 9999)
// * │ │ │ │ │ │ │
// * * 05 * * * * *
// note that when leaving * anywhere means ever part of that. IE * in the seconds part will run every second inside that min
/**
*
* @param name Name of the job we want to run
@@ -43,7 +46,6 @@ export const createCronJob = async (
) => {
// get the timezone based on the os timezone set
const timeZone = Intl.DateTimeFormat().resolvedOptions().timeZone;
const log = createLogger({ module: "system", subModule: "croner" });
// Destroy existing job if it exist
if (runningCrons[name]) {
@@ -124,11 +126,19 @@ export const removeCronJob = (name: string) => {
export const stopCronJob = (name: string) => {
if (runningCrons[name]) {
runningCrons[name].pause();
log.info(
{},
`${name} was just stopped either manually or due to a setting change.`,
);
}
};
export const resumeCronJob = (name: string) => {
if (runningCrons[name]) {
runningCrons[name].resume();
log.info(
{},
`${name} was just restarted either manually or due to a setting change.`,
);
}
};

View File

@@ -0,0 +1,34 @@
meta {
name: Register
type: http
seq: 2
}
post {
url: {{url}}/lst/api/authentication/register
body: json
auth: inherit
}
body:json {
{
"username": "jane005",
"email": "jane@alpla.com",
"password": "superSecretPassword"
}
}
script:post-response {
// // grab the raw Set-Cookie header
// const cookies = res.headers["set-cookie"];
// const sessionCookie = cookies[0].split(";")[0];
// // Save it as an environment variable
// bru.setEnvVar("session_cookie", sessionCookie);
}
settings {
encodeUrl: true
timeout: 0
}

View File

@@ -0,0 +1,3 @@
docs {
All Api endpoints to the logistics support tool
}

View File

@@ -5,7 +5,7 @@ meta {
}
get {
url: {{url}}/lst/api/datamart/deliveryByDateRange
url: {{url}}/lst/api/datamart
body: none
auth: inherit
}

View File

@@ -0,0 +1,20 @@
meta {
name: Run Query
type: http
seq: 2
}
get {
url: {{url}}/lst/api/datamart/:name
body: none
auth: inherit
}
params:path {
name: activeArticles
}
settings {
encodeUrl: true
timeout: 0
}

View File

@@ -1,31 +0,0 @@
meta {
name: add new query
type: http
seq: 2
}
post {
url: {{url}}/lst/api/datamart
body: multipartForm
auth: inherit
}
body:json {
{
"name": "active av",
"description":"this is just the query on what it dose",
"query": "select * \nfrom tableA"
}
}
body:multipart-form {
name: sqlStats
description: returns all active articles for the server with custom data
options: ""
queryFile: @file(C:\Users\matthes01\Nextcloud2\sqlQuerys\basisArticles.sql)
}
settings {
encodeUrl: true
timeout: 0
}

View File

@@ -1,28 +0,0 @@
meta {
name: update new query
type: http
seq: 3
}
patch {
url: {{url}}/lst/api/datamart/f737aeba-186d-495f-a86e-57412ba6687e
body: multipartForm
auth: inherit
}
body:json {
{
"name": "active av",
"description":"this is just the query on what it dose",
"query": "select * \nfrom tableA"
}
}
body:multipart-form {
setPublicActive: true
}
settings {
encodeUrl: true
timeout: 0
}

View File

@@ -0,0 +1,15 @@
meta {
name: Sql Start
type: http
seq: 4
}
post {
url: {{url}}/lst/api/system/prodsql/start
body: none
auth: inherit
}
settings {
encodeUrl: true
}

View File

@@ -1,19 +1,15 @@
meta {
name: sync queries
name: Sql restart
type: http
seq: 4
}
get {
url: {{url}}/lst/api/datamart/sync?time=15
post {
url: {{url}}/lst/api/system/prodsql/restart
body: none
auth: inherit
}
params:query {
time: 15
}
settings {
encodeUrl: true
timeout: 0

View File

@@ -0,0 +1,16 @@
meta {
name: Sql stop
type: http
seq: 4
}
post {
url: {{url}}/lst/api/system/prodsql/stop
body: none
auth: inherit
}
settings {
encodeUrl: true
timeout: 0
}

View File

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

View File

@@ -14,3 +14,7 @@ settings {
encodeUrl: true
timeout: 0
}
docs {
returns all settings
}

View File

@@ -5,15 +5,15 @@ meta {
}
patch {
url: {{url}}/lst/api/settings/something
url: {{url}}/lst/api/settings/opendock_sync
body: json
auth: inherit
}
body:json {
{
"value" : "true",
"active": "false"
"value" : "0",
"active": "true"
}
}
@@ -21,3 +21,13 @@ settings {
encodeUrl: true
timeout: 0
}
docs {
Allows the changing of a setting based on the parameter.
* when a setting that is being changed is a feature there will be some backgound logic that will stop that features processes and no long work.
* when the setting is being changed is system the entire app will do a full restart
* when a seeting is being changed and is standard nothing will happen until the next action is completed. example someone prints a label and you changed the default to 120 second from 90 seconds
}

View File

@@ -0,0 +1 @@
ALTER TABLE "settings" ADD COLUMN "seed_version" integer DEFAULT 1;

File diff suppressed because it is too large Load Diff

View File

@@ -99,6 +99,13 @@
"when": 1771598443244,
"tag": "0013_flaky_quicksilver",
"breakpoints": true
},
{
"idx": 14,
"version": "7",
"when": 1772012229060,
"tag": "0014_heavy_kitty_pryde",
"breakpoints": true
}
]
}

200
package-lock.json generated
View File

@@ -31,6 +31,7 @@
"morgan": "^1.10.1",
"mssql": "^12.2.0",
"multer": "^2.0.2",
"net-snmp": "^3.26.1",
"nodemailer": "^8.0.1",
"nodemailer-express-handlebars": "^7.0.0",
"pg": "^8.18.0",
@@ -54,6 +55,7 @@
"@types/morgan": "^1.9.10",
"@types/mssql": "^9.1.9",
"@types/multer": "^2.0.0",
"@types/net-snmp": "^3.23.0",
"@types/node": "^25.2.3",
"@types/nodemailer": "^7.0.10",
"@types/nodemailer-express-handlebars": "^4.0.6",
@@ -63,9 +65,7 @@
"@types/swagger-ui-express": "^4.1.8",
"@vercel/ncc": "^0.38.4",
"commitizen": "^4.3.1",
"concurrently": "^9.2.1",
"cz-conventional-changelog": "^3.3.0",
"nodemon": "^3.1.11",
"npm-check-updates": "^19.3.2",
"openapi-types": "^12.1.3",
"supertest": "^7.2.2",
@@ -4596,6 +4596,26 @@
"@types/express": "*"
}
},
"node_modules/@types/net-snmp": {
"version": "3.23.0",
"resolved": "https://registry.npmjs.org/@types/net-snmp/-/net-snmp-3.23.0.tgz",
"integrity": "sha512-54Y5NLlQ8isegHTUZdEaIFhR1iz/qlRkQlpkuXfsWjWDVLNfStLmvraaj9ZS7tmcZEltFkb4ZeDBjnSYeFc3+w==",
"dev": true,
"license": "MIT",
"dependencies": {
"@types/node": "^24.3.0"
}
},
"node_modules/@types/net-snmp/node_modules/@types/node": {
"version": "24.10.14",
"resolved": "https://registry.npmjs.org/@types/node/-/node-24.10.14.tgz",
"integrity": "sha512-OowOUbD1lBCOFIPOZ8xnMIhgqA4sCutMiYOmPHL1PTLt5+y1XA+g2+yC9OOyz8p+deMZqPZLxfMjYIfrKsPeFg==",
"dev": true,
"license": "MIT",
"dependencies": {
"undici-types": "~7.16.0"
}
},
"node_modules/@types/node": {
"version": "25.2.3",
"resolved": "https://registry.npmjs.org/@types/node/-/node-25.2.3.tgz",
@@ -5392,6 +5412,12 @@
"dev": true,
"license": "MIT"
},
"node_modules/asn1-ber": {
"version": "1.2.2",
"resolved": "https://registry.npmjs.org/asn1-ber/-/asn1-ber-1.2.2.tgz",
"integrity": "sha512-CbNem/7hxrjSiOAOOTX4iZxu+0m3jiLqlsERQwwPM1IDR/22M8IPpA1VVndCLw5KtjRYyRODbvAEIfuTogNDng==",
"license": "MIT"
},
"node_modules/assertion-error": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz",
@@ -6398,47 +6424,6 @@
"typedarray": "^0.0.6"
}
},
"node_modules/concurrently": {
"version": "9.2.1",
"resolved": "https://registry.npmjs.org/concurrently/-/concurrently-9.2.1.tgz",
"integrity": "sha512-fsfrO0MxV64Znoy8/l1vVIjjHa29SZyyqPgQBwhiDcaW8wJc2W3XWVOGx4M3oJBnv/zdUZIIp1gDeS98GzP8Ng==",
"dev": true,
"license": "MIT",
"dependencies": {
"chalk": "4.1.2",
"rxjs": "7.8.2",
"shell-quote": "1.8.3",
"supports-color": "8.1.1",
"tree-kill": "1.2.2",
"yargs": "17.7.2"
},
"bin": {
"conc": "dist/bin/concurrently.js",
"concurrently": "dist/bin/concurrently.js"
},
"engines": {
"node": ">=18"
},
"funding": {
"url": "https://github.com/open-cli-tools/concurrently?sponsor=1"
}
},
"node_modules/concurrently/node_modules/supports-color": {
"version": "8.1.1",
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz",
"integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==",
"dev": true,
"license": "MIT",
"dependencies": {
"has-flag": "^4.0.0"
},
"engines": {
"node": ">=10"
},
"funding": {
"url": "https://github.com/chalk/supports-color?sponsor=1"
}
},
"node_modules/content-disposition": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-1.0.1.tgz",
@@ -9107,13 +9092,6 @@
"node": ">= 4"
}
},
"node_modules/ignore-by-default": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/ignore-by-default/-/ignore-by-default-1.0.1.tgz",
"integrity": "sha512-Ius2VYcGNk7T90CppJqcIkS5ooHUZyIQK+ClZfMfMNFEF9VSE73Fq+906u/CWu92x4gzZMWOwfFYckPObzdEbA==",
"dev": true,
"license": "ISC"
},
"node_modules/import-fresh": {
"version": "3.3.1",
"resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz",
@@ -11323,6 +11301,16 @@
"integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==",
"license": "MIT"
},
"node_modules/net-snmp": {
"version": "3.26.1",
"resolved": "https://registry.npmjs.org/net-snmp/-/net-snmp-3.26.1.tgz",
"integrity": "sha512-ZWsfchno5Lxj6OOUt4YS+88KLwce4tpnh4pLxiiwUULg3QQsN1n4OYAli4Ch2C+fkjqQXjhoI8rKltQyBVFzxg==",
"license": "MIT",
"dependencies": {
"asn1-ber": "^1.2.1",
"smart-buffer": "^4.1.0"
}
},
"node_modules/node-int64": {
"version": "0.4.0",
"resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz",
@@ -11361,58 +11349,6 @@
"nodemailer": ">= 6.0.0"
}
},
"node_modules/nodemon": {
"version": "3.1.11",
"resolved": "https://registry.npmjs.org/nodemon/-/nodemon-3.1.11.tgz",
"integrity": "sha512-is96t8F/1//UHAjNPHpbsNY46ELPpftGUoSVNXwUfMk/qdjSylYrWSu1XavVTBOn526kFiOR733ATgNBCQyH0g==",
"dev": true,
"license": "MIT",
"dependencies": {
"chokidar": "^3.5.2",
"debug": "^4",
"ignore-by-default": "^1.0.1",
"minimatch": "^3.1.2",
"pstree.remy": "^1.1.8",
"semver": "^7.5.3",
"simple-update-notifier": "^2.0.0",
"supports-color": "^5.5.0",
"touch": "^3.1.0",
"undefsafe": "^2.0.5"
},
"bin": {
"nodemon": "bin/nodemon.js"
},
"engines": {
"node": ">=10"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/nodemon"
}
},
"node_modules/nodemon/node_modules/has-flag": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz",
"integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=4"
}
},
"node_modules/nodemon/node_modules/supports-color": {
"version": "5.5.0",
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz",
"integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==",
"dev": true,
"license": "MIT",
"dependencies": {
"has-flag": "^3.0.0"
},
"engines": {
"node": ">=4"
}
},
"node_modules/normalize-path": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz",
@@ -12285,13 +12221,6 @@
"integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==",
"license": "MIT"
},
"node_modules/pstree.remy": {
"version": "1.1.8",
"resolved": "https://registry.npmjs.org/pstree.remy/-/pstree.remy-1.1.8.tgz",
"integrity": "sha512-77DZwxQmxKnu3aR542U+X8FypNzbfJ+C5XQDk3uWjWxn6151aIMGthWYRXTqT1E5oJvg+ljaa2OJi+VfvCOQ8w==",
"dev": true,
"license": "MIT"
},
"node_modules/pump": {
"version": "3.0.3",
"resolved": "https://registry.npmjs.org/pump/-/pump-3.0.3.tgz",
@@ -12867,19 +12796,6 @@
"node": ">=8"
}
},
"node_modules/shell-quote": {
"version": "1.8.3",
"resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.8.3.tgz",
"integrity": "sha512-ObmnIF4hXNg1BqhnHmgbDETF8dLPCggZWBjkQfhZpbszZnYur5DUljTcCHii5LC3J5E0yeO/1LIMyH+UvHQgyw==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/side-channel": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz",
@@ -12965,19 +12881,6 @@
"integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==",
"license": "ISC"
},
"node_modules/simple-update-notifier": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/simple-update-notifier/-/simple-update-notifier-2.0.0.tgz",
"integrity": "sha512-a2B9Y0KlNXl9u/vsW6sTIu9vGEpfKu2wRV6l1H3XEas/0gUIzGzBoP/IouTcUQbm9JWZLH3COxyn03TYlFax6w==",
"dev": true,
"license": "MIT",
"dependencies": {
"semver": "^7.5.3"
},
"engines": {
"node": ">=10"
}
},
"node_modules/slash": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz",
@@ -12988,6 +12891,16 @@
"node": ">=8"
}
},
"node_modules/smart-buffer": {
"version": "4.2.0",
"resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.2.0.tgz",
"integrity": "sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==",
"license": "MIT",
"engines": {
"node": ">= 6.0.0",
"npm": ">= 3.0.0"
}
},
"node_modules/socket.io": {
"version": "4.8.3",
"resolved": "https://registry.npmjs.org/socket.io/-/socket.io-4.8.3.tgz",
@@ -13684,16 +13597,6 @@
"node": ">=0.6"
}
},
"node_modules/touch": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/touch/-/touch-3.1.1.tgz",
"integrity": "sha512-r0eojU4bI8MnHr8c5bNo7lJDdI2qXlWWJk6a9EAFG7vbhTjElYhBVS3/miuE0uOuoLdb8Mc/rVfsmm6eo5o9GA==",
"dev": true,
"license": "ISC",
"bin": {
"nodetouch": "bin/nodetouch.js"
}
},
"node_modules/tree-kill": {
"version": "1.2.2",
"resolved": "https://registry.npmjs.org/tree-kill/-/tree-kill-1.2.2.tgz",
@@ -13982,13 +13885,6 @@
"node": ">=0.8.0"
}
},
"node_modules/undefsafe": {
"version": "2.0.5",
"resolved": "https://registry.npmjs.org/undefsafe/-/undefsafe-2.0.5.tgz",
"integrity": "sha512-WxONCrssBM8TSPRqN5EmsjVrsv4A8X12J4ArBiiayv3DyyG3ZlIg6yysuuSYdZsVz3TKcTg2fd//Ujd4CHV1iA==",
"dev": true,
"license": "MIT"
},
"node_modules/undici-types": {
"version": "7.16.0",
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.16.0.tgz",

View File

@@ -10,7 +10,7 @@
"dev:agent": "cd agent/src && tsx watch agent.ts",
"dev:db:migrate": "npx drizzle-kit push",
"dev:db:generate": "tsc && npx drizzle-kit generate --config=drizzle.config.ts",
"build": "rimraf dist && npm run specCheck && npm run lint && npm run dev:db:generate && npm run dev:db:migrate && npm run build:app && npm run build:copySql",
"build": "rimraf dist && npm run dev:db:generate && npm run dev:db:migrate && npm run build:app && npm run build:copySql",
"build:app": "tsc",
"build:agent": "cd agent && tsc -p tsconfig.json",
"build:docker": "docker compose up --force-recreate --build -d",
@@ -50,6 +50,7 @@
"@types/morgan": "^1.9.10",
"@types/mssql": "^9.1.9",
"@types/multer": "^2.0.0",
"@types/net-snmp": "^3.23.0",
"@types/node": "^25.2.3",
"@types/nodemailer": "^7.0.10",
"@types/nodemailer-express-handlebars": "^4.0.6",
@@ -88,6 +89,7 @@
"morgan": "^1.10.1",
"mssql": "^12.2.0",
"multer": "^2.0.2",
"net-snmp": "^3.26.1",
"nodemailer": "^8.0.1",
"nodemailer-express-handlebars": "^7.0.0",
"pg": "^8.18.0",

View File

@@ -0,0 +1,85 @@
Set-StrictMode -Version Latest
$ErrorActionPreference = "Stop"
# config
$Servers = @("appserver1", "appserver2") # hostnames or IPs
$ServiceName = "MyAppService" # Windows service name (or pm2 process name logic)
$AppDir= "C:\Users\matthes01\Documents\lst_v3"
$RemoteTempZip = "C:\Windows\Temp\release.zip"
$RemoteReleasesRoot = "C:\App\releases"
$RemoteCurrent = "C:\App\current"
#--------------------------------------------------
# below this shouldnt really need to be messed with
#--------------------------------------------------
function Show-Menu {
Clear-Host
Write-Host "==============================="
Write-Host " Deployment Menu "
Write-Host "==============================="
Write-Host "1. Build app"
Write-Host "2. Deploy New Release"
Write-Host "3. Restart Service"
Write-Host "4. Exit"
Write-Host ""
}
function Select-Server {
param([string[]]$List)
for ($i = 0; $i -lt $List.Count; $i++) {
Write-Host ("{0}. {1}" -f ($i+1), $List[$i])
}
$sel = Read-Host "Select server by number"
if ($sel -match '^\d+$' -and [int]$sel -ge 1 -and [int]$sel -le $List.Count) {
return $List[[int]$sel - 1]
} else {
Write-Host "Invalid selection"
return $null
}
}
do {
Show-Menu
$choice = Read-Host "Select an option"
switch ($choice) {
"1" {
try {
Push-Location $AppDir
Write-Host "Running: npm run build in $AppDir"
npm run build
Write-Host "Build completed successfully."
Read-Host -Prompt "Press Enter to continue..."
}
catch {
Write-Host "Build failed: $_"
throw
}
finally {
Pop-Location
}
}
"2" {
$server = Select-Server -List $Servers
if ($null -ne $server) {
$zip = Read-Host "Path to zip (local)"
Deploy-ToServer -Server $server -ZipPath $zip
}
}
"3" {
Write-Host "Restart selected"
psql -h localhost -p 5433 -U adm_cowch -d lst_dev -c "SELECT count(*) FROM public.logs;"
}
"4" {
Write-Host "Exiting..."
exit
}
default {
Write-Host "Invalid selection"
Start-Sleep -Seconds 2
}
}
} while ($choice -ne "3")

10
scripts/services.ps1 Normal file
View File

@@ -0,0 +1,10 @@
param (
[string]$serviceName,
[string]$option,
[string]$appPath,
[string]$command, # just the command like run start or what ever you have in npm.
[string]$description,
[string]$remote,
[string]$server,
)