feat: aggiunta tracciatura delle modifiche effettuate
This commit is contained in:
@@ -1,2 +1,5 @@
|
|||||||
node_modules/
|
node_modules/
|
||||||
.env
|
.env
|
||||||
|
internal.db
|
||||||
|
internal.db-shm
|
||||||
|
internal.db-wal
|
||||||
|
|||||||
@@ -0,0 +1,64 @@
|
|||||||
|
/**
|
||||||
|
* activityDb.js
|
||||||
|
* Local SQLite activity log database.
|
||||||
|
* Opens (or creates) internal.db and provides logAttivita() for logging
|
||||||
|
* every significant action sent to OTRS.
|
||||||
|
*/
|
||||||
|
const Database = require('better-sqlite3');
|
||||||
|
const path = require('path');
|
||||||
|
const crypto = require('crypto');
|
||||||
|
|
||||||
|
const DB_PATH = path.join(__dirname, 'internal.db');
|
||||||
|
const db = new Database(DB_PATH);
|
||||||
|
|
||||||
|
// Ensure WAL mode for better concurrent access
|
||||||
|
db.pragma('journal_mode = WAL');
|
||||||
|
|
||||||
|
// Create table if it does not exist
|
||||||
|
db.exec(`
|
||||||
|
CREATE TABLE IF NOT EXISTS attivita (
|
||||||
|
id TEXT PRIMARY KEY,
|
||||||
|
agente_id INTEGER NOT NULL DEFAULT 0,
|
||||||
|
agente_nome TEXT NOT NULL DEFAULT '',
|
||||||
|
titolo_azione TEXT NOT NULL,
|
||||||
|
azione TEXT NOT NULL DEFAULT '{}',
|
||||||
|
esito TEXT NOT NULL DEFAULT 'successo',
|
||||||
|
creato_il DATETIME NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ', 'now'))
|
||||||
|
)
|
||||||
|
`);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generate a UUID v7 (time-ordered).
|
||||||
|
*/
|
||||||
|
function uuidV7() {
|
||||||
|
const tsMs = BigInt(Date.now());
|
||||||
|
const tsMsHex = tsMs.toString(16).padStart(12, '0');
|
||||||
|
const rand = crypto.randomBytes(10).toString('hex');
|
||||||
|
const p1 = tsMsHex.slice(0, 8);
|
||||||
|
const p2 = tsMsHex.slice(8, 12);
|
||||||
|
const p3 = '7' + rand.slice(0, 3);
|
||||||
|
const p4 = ((parseInt(rand.slice(3, 4), 16) & 0x3) | 0x8).toString(16) + rand.slice(4, 7);
|
||||||
|
const p5 = rand.slice(7, 19);
|
||||||
|
return `${p1}-${p2}-${p3}-${p4}-${p5}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
const insertStmt = db.prepare(`
|
||||||
|
INSERT INTO attivita (id, agente_id, agente_nome, titolo_azione, azione, esito)
|
||||||
|
VALUES (?, ?, ?, ?, ?, ?)
|
||||||
|
`);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Log an activity record.
|
||||||
|
* @param {Object} params
|
||||||
|
*/
|
||||||
|
function logAttivita({ agente_id = 0, agente_nome = '', titolo_azione, azione = {}, esito = 'successo' }) {
|
||||||
|
try {
|
||||||
|
const id = uuidV7();
|
||||||
|
const azioneStr = typeof azione === 'string' ? azione : JSON.stringify(azione, null, 2);
|
||||||
|
insertStmt.run(id, agente_id, agente_nome, titolo_azione, azioneStr, esito);
|
||||||
|
} catch (err) {
|
||||||
|
console.error('[activityDb] Failed to log activity:', err.message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = { db, logAttivita };
|
||||||
Generated
+427
@@ -9,6 +9,7 @@
|
|||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"license": "AGPL-3.0",
|
"license": "AGPL-3.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"better-sqlite3": "^12.11.1",
|
||||||
"cors": "^2.8.5",
|
"cors": "^2.8.5",
|
||||||
"dotenv": "^16.4.5",
|
"dotenv": "^16.4.5",
|
||||||
"express": "^4.21.0",
|
"express": "^4.21.0",
|
||||||
@@ -54,6 +55,60 @@
|
|||||||
"node": ">= 6.0.0"
|
"node": ">= 6.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/base64-js": {
|
||||||
|
"version": "1.5.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz",
|
||||||
|
"integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==",
|
||||||
|
"funding": [
|
||||||
|
{
|
||||||
|
"type": "github",
|
||||||
|
"url": "https://github.com/sponsors/feross"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "patreon",
|
||||||
|
"url": "https://www.patreon.com/feross"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "consulting",
|
||||||
|
"url": "https://feross.org/support"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
|
"node_modules/better-sqlite3": {
|
||||||
|
"version": "12.11.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/better-sqlite3/-/better-sqlite3-12.11.1.tgz",
|
||||||
|
"integrity": "sha512-dq9AtApgg5PGFtBzPFSBl3HZQjHok5gaQCM6zh2Yk0aSmDCs1CbnVI8/HgASQkNKsWFpseIO9beg5xxpYhbIfA==",
|
||||||
|
"hasInstallScript": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"bindings": "^1.5.0",
|
||||||
|
"prebuild-install": "^7.1.1"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": "20.x || 22.x || 23.x || 24.x || 25.x || 26.x"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/bindings": {
|
||||||
|
"version": "1.5.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/bindings/-/bindings-1.5.0.tgz",
|
||||||
|
"integrity": "sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"file-uri-to-path": "1.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/bl": {
|
||||||
|
"version": "4.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz",
|
||||||
|
"integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"buffer": "^5.5.0",
|
||||||
|
"inherits": "^2.0.4",
|
||||||
|
"readable-stream": "^3.4.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/body-parser": {
|
"node_modules/body-parser": {
|
||||||
"version": "1.20.5",
|
"version": "1.20.5",
|
||||||
"resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.5.tgz",
|
"resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.5.tgz",
|
||||||
@@ -78,6 +133,30 @@
|
|||||||
"npm": "1.2.8000 || >= 1.4.16"
|
"npm": "1.2.8000 || >= 1.4.16"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/buffer": {
|
||||||
|
"version": "5.7.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz",
|
||||||
|
"integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==",
|
||||||
|
"funding": [
|
||||||
|
{
|
||||||
|
"type": "github",
|
||||||
|
"url": "https://github.com/sponsors/feross"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "patreon",
|
||||||
|
"url": "https://www.patreon.com/feross"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "consulting",
|
||||||
|
"url": "https://feross.org/support"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"base64-js": "^1.3.1",
|
||||||
|
"ieee754": "^1.1.13"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/bytes": {
|
"node_modules/bytes": {
|
||||||
"version": "3.1.2",
|
"version": "3.1.2",
|
||||||
"resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz",
|
"resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz",
|
||||||
@@ -116,6 +195,12 @@
|
|||||||
"url": "https://github.com/sponsors/ljharb"
|
"url": "https://github.com/sponsors/ljharb"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/chownr": {
|
||||||
|
"version": "1.1.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz",
|
||||||
|
"integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==",
|
||||||
|
"license": "ISC"
|
||||||
|
},
|
||||||
"node_modules/content-disposition": {
|
"node_modules/content-disposition": {
|
||||||
"version": "0.5.4",
|
"version": "0.5.4",
|
||||||
"resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz",
|
"resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz",
|
||||||
@@ -178,6 +263,30 @@
|
|||||||
"ms": "2.0.0"
|
"ms": "2.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/decompress-response": {
|
||||||
|
"version": "6.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-6.0.0.tgz",
|
||||||
|
"integrity": "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"mimic-response": "^3.1.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=10"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/sindresorhus"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/deep-extend": {
|
||||||
|
"version": "0.6.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz",
|
||||||
|
"integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=4.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/denque": {
|
"node_modules/denque": {
|
||||||
"version": "2.1.0",
|
"version": "2.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/denque/-/denque-2.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/denque/-/denque-2.1.0.tgz",
|
||||||
@@ -206,6 +315,15 @@
|
|||||||
"npm": "1.2.8000 || >= 1.4.16"
|
"npm": "1.2.8000 || >= 1.4.16"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/detect-libc": {
|
||||||
|
"version": "2.1.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz",
|
||||||
|
"integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==",
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=8"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/dotenv": {
|
"node_modules/dotenv": {
|
||||||
"version": "16.6.1",
|
"version": "16.6.1",
|
||||||
"resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.6.1.tgz",
|
"resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.6.1.tgz",
|
||||||
@@ -247,6 +365,15 @@
|
|||||||
"node": ">= 0.8"
|
"node": ">= 0.8"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/end-of-stream": {
|
||||||
|
"version": "1.4.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.5.tgz",
|
||||||
|
"integrity": "sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"once": "^1.4.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/es-define-property": {
|
"node_modules/es-define-property": {
|
||||||
"version": "1.0.1",
|
"version": "1.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz",
|
||||||
@@ -292,6 +419,15 @@
|
|||||||
"node": ">= 0.6"
|
"node": ">= 0.6"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/expand-template": {
|
||||||
|
"version": "2.0.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/expand-template/-/expand-template-2.0.3.tgz",
|
||||||
|
"integrity": "sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg==",
|
||||||
|
"license": "(MIT OR WTFPL)",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=6"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/express": {
|
"node_modules/express": {
|
||||||
"version": "4.22.2",
|
"version": "4.22.2",
|
||||||
"resolved": "https://registry.npmjs.org/express/-/express-4.22.2.tgz",
|
"resolved": "https://registry.npmjs.org/express/-/express-4.22.2.tgz",
|
||||||
@@ -338,6 +474,12 @@
|
|||||||
"url": "https://opencollective.com/express"
|
"url": "https://opencollective.com/express"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/file-uri-to-path": {
|
||||||
|
"version": "1.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz",
|
||||||
|
"integrity": "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/finalhandler": {
|
"node_modules/finalhandler": {
|
||||||
"version": "1.3.2",
|
"version": "1.3.2",
|
||||||
"resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.2.tgz",
|
"resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.2.tgz",
|
||||||
@@ -374,6 +516,12 @@
|
|||||||
"node": ">= 0.6"
|
"node": ">= 0.6"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/fs-constants": {
|
||||||
|
"version": "1.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz",
|
||||||
|
"integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/function-bind": {
|
"node_modules/function-bind": {
|
||||||
"version": "1.1.2",
|
"version": "1.1.2",
|
||||||
"resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
|
"resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
|
||||||
@@ -429,6 +577,12 @@
|
|||||||
"node": ">= 0.4"
|
"node": ">= 0.4"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/github-from-package": {
|
||||||
|
"version": "0.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/github-from-package/-/github-from-package-0.0.0.tgz",
|
||||||
|
"integrity": "sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/gopd": {
|
"node_modules/gopd": {
|
||||||
"version": "1.2.0",
|
"version": "1.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz",
|
||||||
@@ -497,12 +651,38 @@
|
|||||||
"node": ">=0.10.0"
|
"node": ">=0.10.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/ieee754": {
|
||||||
|
"version": "1.2.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz",
|
||||||
|
"integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==",
|
||||||
|
"funding": [
|
||||||
|
{
|
||||||
|
"type": "github",
|
||||||
|
"url": "https://github.com/sponsors/feross"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "patreon",
|
||||||
|
"url": "https://www.patreon.com/feross"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "consulting",
|
||||||
|
"url": "https://feross.org/support"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"license": "BSD-3-Clause"
|
||||||
|
},
|
||||||
"node_modules/inherits": {
|
"node_modules/inherits": {
|
||||||
"version": "2.0.4",
|
"version": "2.0.4",
|
||||||
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
|
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
|
||||||
"integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
|
"integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
|
||||||
"license": "ISC"
|
"license": "ISC"
|
||||||
},
|
},
|
||||||
|
"node_modules/ini": {
|
||||||
|
"version": "1.3.8",
|
||||||
|
"resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz",
|
||||||
|
"integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==",
|
||||||
|
"license": "ISC"
|
||||||
|
},
|
||||||
"node_modules/ipaddr.js": {
|
"node_modules/ipaddr.js": {
|
||||||
"version": "1.9.1",
|
"version": "1.9.1",
|
||||||
"resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz",
|
"resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz",
|
||||||
@@ -608,6 +788,33 @@
|
|||||||
"node": ">= 0.6"
|
"node": ">= 0.6"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/mimic-response": {
|
||||||
|
"version": "3.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-3.1.0.tgz",
|
||||||
|
"integrity": "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=10"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/sindresorhus"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/minimist": {
|
||||||
|
"version": "1.2.8",
|
||||||
|
"resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz",
|
||||||
|
"integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/ljharb"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/mkdirp-classic": {
|
||||||
|
"version": "0.5.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz",
|
||||||
|
"integrity": "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/ms": {
|
"node_modules/ms": {
|
||||||
"version": "2.0.0",
|
"version": "2.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
|
||||||
@@ -664,6 +871,12 @@
|
|||||||
"node": ">=8.0.0"
|
"node": ">=8.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/napi-build-utils": {
|
||||||
|
"version": "2.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/napi-build-utils/-/napi-build-utils-2.0.0.tgz",
|
||||||
|
"integrity": "sha512-GEbrYkbfF7MoNaoh2iGG84Mnf/WZfB0GdGEsM8wz7Expx/LlWf5U8t9nvJKXSp3qr5IsEbK04cBGhol/KwOsWA==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/negotiator": {
|
"node_modules/negotiator": {
|
||||||
"version": "0.6.3",
|
"version": "0.6.3",
|
||||||
"resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz",
|
"resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz",
|
||||||
@@ -673,6 +886,18 @@
|
|||||||
"node": ">= 0.6"
|
"node": ">= 0.6"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/node-abi": {
|
||||||
|
"version": "3.94.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/node-abi/-/node-abi-3.94.0.tgz",
|
||||||
|
"integrity": "sha512-W5ZNO5KRPB5TkYmGVD9F6YqhsglXJzE6etpbmT+f6EQElhiX/UTG551cnsRGvLG3fyZEg9HwaDmNmj5nwJ4z9g==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"semver": "^7.3.5"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=10"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/object-assign": {
|
"node_modules/object-assign": {
|
||||||
"version": "4.1.1",
|
"version": "4.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
|
||||||
@@ -706,6 +931,15 @@
|
|||||||
"node": ">= 0.8"
|
"node": ">= 0.8"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/once": {
|
||||||
|
"version": "1.4.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
|
||||||
|
"integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==",
|
||||||
|
"license": "ISC",
|
||||||
|
"dependencies": {
|
||||||
|
"wrappy": "1"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/parseurl": {
|
"node_modules/parseurl": {
|
||||||
"version": "1.3.3",
|
"version": "1.3.3",
|
||||||
"resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz",
|
"resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz",
|
||||||
@@ -849,6 +1083,33 @@
|
|||||||
"node": ">=0.10.0"
|
"node": ">=0.10.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/prebuild-install": {
|
||||||
|
"version": "7.1.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/prebuild-install/-/prebuild-install-7.1.3.tgz",
|
||||||
|
"integrity": "sha512-8Mf2cbV7x1cXPUILADGI3wuhfqWvtiLA1iclTDbFRZkgRQS0NqsPZphna9V+HyTEadheuPmjaJMsbzKQFOzLug==",
|
||||||
|
"deprecated": "No longer maintained. Please contact the author of the relevant native addon; alternatives are available.",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"detect-libc": "^2.0.0",
|
||||||
|
"expand-template": "^2.0.3",
|
||||||
|
"github-from-package": "0.0.0",
|
||||||
|
"minimist": "^1.2.3",
|
||||||
|
"mkdirp-classic": "^0.5.3",
|
||||||
|
"napi-build-utils": "^2.0.0",
|
||||||
|
"node-abi": "^3.3.0",
|
||||||
|
"pump": "^3.0.0",
|
||||||
|
"rc": "^1.2.7",
|
||||||
|
"simple-get": "^4.0.0",
|
||||||
|
"tar-fs": "^2.0.0",
|
||||||
|
"tunnel-agent": "^0.6.0"
|
||||||
|
},
|
||||||
|
"bin": {
|
||||||
|
"prebuild-install": "bin.js"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=10"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/proxy-addr": {
|
"node_modules/proxy-addr": {
|
||||||
"version": "2.0.7",
|
"version": "2.0.7",
|
||||||
"resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz",
|
"resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz",
|
||||||
@@ -862,6 +1123,16 @@
|
|||||||
"node": ">= 0.10"
|
"node": ">= 0.10"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/pump": {
|
||||||
|
"version": "3.0.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/pump/-/pump-3.0.4.tgz",
|
||||||
|
"integrity": "sha512-VS7sjc6KR7e1ukRFhQSY5LM2uBWAUPiOPa/A3mkKmiMwSmRFUITt0xuj+/lesgnCv+dPIEYlkzrcyXgquIHMcA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"end-of-stream": "^1.1.0",
|
||||||
|
"once": "^1.3.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/qs": {
|
"node_modules/qs": {
|
||||||
"version": "6.15.3",
|
"version": "6.15.3",
|
||||||
"resolved": "https://registry.npmjs.org/qs/-/qs-6.15.3.tgz",
|
"resolved": "https://registry.npmjs.org/qs/-/qs-6.15.3.tgz",
|
||||||
@@ -902,6 +1173,35 @@
|
|||||||
"node": ">= 0.8"
|
"node": ">= 0.8"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/rc": {
|
||||||
|
"version": "1.2.8",
|
||||||
|
"resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz",
|
||||||
|
"integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==",
|
||||||
|
"license": "(BSD-2-Clause OR MIT OR Apache-2.0)",
|
||||||
|
"dependencies": {
|
||||||
|
"deep-extend": "^0.6.0",
|
||||||
|
"ini": "~1.3.0",
|
||||||
|
"minimist": "^1.2.0",
|
||||||
|
"strip-json-comments": "~2.0.1"
|
||||||
|
},
|
||||||
|
"bin": {
|
||||||
|
"rc": "cli.js"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/readable-stream": {
|
||||||
|
"version": "3.6.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz",
|
||||||
|
"integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"inherits": "^2.0.3",
|
||||||
|
"string_decoder": "^1.1.1",
|
||||||
|
"util-deprecate": "^1.0.1"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 6"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/safe-buffer": {
|
"node_modules/safe-buffer": {
|
||||||
"version": "5.2.1",
|
"version": "5.2.1",
|
||||||
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
|
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
|
||||||
@@ -928,6 +1228,18 @@
|
|||||||
"integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==",
|
"integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==",
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/semver": {
|
||||||
|
"version": "7.8.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/semver/-/semver-7.8.5.tgz",
|
||||||
|
"integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==",
|
||||||
|
"license": "ISC",
|
||||||
|
"bin": {
|
||||||
|
"semver": "bin/semver.js"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=10"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/send": {
|
"node_modules/send": {
|
||||||
"version": "0.19.2",
|
"version": "0.19.2",
|
||||||
"resolved": "https://registry.npmjs.org/send/-/send-0.19.2.tgz",
|
"resolved": "https://registry.npmjs.org/send/-/send-0.19.2.tgz",
|
||||||
@@ -1051,6 +1363,51 @@
|
|||||||
"url": "https://github.com/sponsors/ljharb"
|
"url": "https://github.com/sponsors/ljharb"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/simple-concat": {
|
||||||
|
"version": "1.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/simple-concat/-/simple-concat-1.0.1.tgz",
|
||||||
|
"integrity": "sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q==",
|
||||||
|
"funding": [
|
||||||
|
{
|
||||||
|
"type": "github",
|
||||||
|
"url": "https://github.com/sponsors/feross"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "patreon",
|
||||||
|
"url": "https://www.patreon.com/feross"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "consulting",
|
||||||
|
"url": "https://feross.org/support"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
|
"node_modules/simple-get": {
|
||||||
|
"version": "4.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/simple-get/-/simple-get-4.0.1.tgz",
|
||||||
|
"integrity": "sha512-brv7p5WgH0jmQJr1ZDDfKDOSeWWg+OVypG99A/5vYGPqJ6pxiaHLy8nxtFjBA7oMa01ebA9gfh1uMCFqOuXxvA==",
|
||||||
|
"funding": [
|
||||||
|
{
|
||||||
|
"type": "github",
|
||||||
|
"url": "https://github.com/sponsors/feross"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "patreon",
|
||||||
|
"url": "https://www.patreon.com/feross"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "consulting",
|
||||||
|
"url": "https://feross.org/support"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"decompress-response": "^6.0.0",
|
||||||
|
"once": "^1.3.1",
|
||||||
|
"simple-concat": "^1.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/split2": {
|
"node_modules/split2": {
|
||||||
"version": "4.2.0",
|
"version": "4.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/split2/-/split2-4.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/split2/-/split2-4.2.0.tgz",
|
||||||
@@ -1084,6 +1441,52 @@
|
|||||||
"node": ">= 0.8"
|
"node": ">= 0.8"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/string_decoder": {
|
||||||
|
"version": "1.3.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz",
|
||||||
|
"integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"safe-buffer": "~5.2.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/strip-json-comments": {
|
||||||
|
"version": "2.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz",
|
||||||
|
"integrity": "sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=0.10.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/tar-fs": {
|
||||||
|
"version": "2.1.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.5.tgz",
|
||||||
|
"integrity": "sha512-OboTd8mmMhZDNPV+UjQcK9yKAatXu2aJ+r1w4im1Otd4M4fl2hwvdoXUxIYHFTHWK/3y3FarBP70v3vwmGlOxw==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"chownr": "^1.1.1",
|
||||||
|
"mkdirp-classic": "^0.5.2",
|
||||||
|
"pump": "^3.0.0",
|
||||||
|
"tar-stream": "^2.1.4"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/tar-stream": {
|
||||||
|
"version": "2.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.2.0.tgz",
|
||||||
|
"integrity": "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"bl": "^4.0.3",
|
||||||
|
"end-of-stream": "^1.4.1",
|
||||||
|
"fs-constants": "^1.0.0",
|
||||||
|
"inherits": "^2.0.3",
|
||||||
|
"readable-stream": "^3.1.1"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=6"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/toidentifier": {
|
"node_modules/toidentifier": {
|
||||||
"version": "1.0.1",
|
"version": "1.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz",
|
||||||
@@ -1093,6 +1496,18 @@
|
|||||||
"node": ">=0.6"
|
"node": ">=0.6"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/tunnel-agent": {
|
||||||
|
"version": "0.6.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz",
|
||||||
|
"integrity": "sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==",
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"dependencies": {
|
||||||
|
"safe-buffer": "^5.0.1"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": "*"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/type-is": {
|
"node_modules/type-is": {
|
||||||
"version": "1.6.18",
|
"version": "1.6.18",
|
||||||
"resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz",
|
"resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz",
|
||||||
@@ -1122,6 +1537,12 @@
|
|||||||
"node": ">= 0.8"
|
"node": ">= 0.8"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/util-deprecate": {
|
||||||
|
"version": "1.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
|
||||||
|
"integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/utils-merge": {
|
"node_modules/utils-merge": {
|
||||||
"version": "1.0.1",
|
"version": "1.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz",
|
||||||
@@ -1140,6 +1561,12 @@
|
|||||||
"node": ">= 0.8"
|
"node": ">= 0.8"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/wrappy": {
|
||||||
|
"version": "1.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
|
||||||
|
"integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==",
|
||||||
|
"license": "ISC"
|
||||||
|
},
|
||||||
"node_modules/xtend": {
|
"node_modules/xtend": {
|
||||||
"version": "4.0.2",
|
"version": "4.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz",
|
||||||
|
|||||||
@@ -8,6 +8,7 @@
|
|||||||
"dev": "npx -y nodemon server.js"
|
"dev": "npx -y nodemon server.js"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"better-sqlite3": "^12.11.1",
|
||||||
"cors": "^2.8.5",
|
"cors": "^2.8.5",
|
||||||
"dotenv": "^16.4.5",
|
"dotenv": "^16.4.5",
|
||||||
"express": "^4.21.0",
|
"express": "^4.21.0",
|
||||||
|
|||||||
@@ -79,6 +79,15 @@
|
|||||||
<span>Apertura Massiva</span>
|
<span>Apertura Massiva</span>
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
|
<li>
|
||||||
|
<a href="#/activity" class="nav-link" data-view="activity" id="nav-activity">
|
||||||
|
<svg class="nav-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||||
|
<circle cx="12" cy="12" r="10" />
|
||||||
|
<polyline points="12 6 12 12 16 14" />
|
||||||
|
</svg>
|
||||||
|
<span>Storico Attività</span>
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
<div class="sidebar-footer">
|
<div class="sidebar-footer">
|
||||||
@@ -158,6 +167,7 @@
|
|||||||
<script src="/js/views/ticketDetail.js"></script>
|
<script src="/js/views/ticketDetail.js"></script>
|
||||||
<script src="/js/views/ticketCreate.js"></script>
|
<script src="/js/views/ticketCreate.js"></script>
|
||||||
<script src="/js/views/ticketBulk.js"></script>
|
<script src="/js/views/ticketBulk.js"></script>
|
||||||
|
<script src="/js/views/activityLog.js"></script>
|
||||||
<script src="/js/app.js"></script>
|
<script src="/js/app.js"></script>
|
||||||
</body>
|
</body>
|
||||||
|
|
||||||
|
|||||||
@@ -138,6 +138,11 @@ const App = {
|
|||||||
titleEl.textContent = 'Apertura Massiva Ticket';
|
titleEl.textContent = 'Apertura Massiva Ticket';
|
||||||
TicketBulkView.render();
|
TicketBulkView.render();
|
||||||
|
|
||||||
|
} else if (hash === '#/activity') {
|
||||||
|
document.getElementById('nav-activity')?.classList.add('active');
|
||||||
|
titleEl.textContent = 'Storico Attività';
|
||||||
|
ActivityLogView.render();
|
||||||
|
|
||||||
} else if (hash.match(/^#\/tickets\/(\d+)$/)) {
|
} else if (hash.match(/^#\/tickets\/(\d+)$/)) {
|
||||||
const id = hash.match(/^#\/tickets\/(\d+)$/)[1];
|
const id = hash.match(/^#\/tickets\/(\d+)$/)[1];
|
||||||
document.getElementById('nav-tickets')?.classList.add('active');
|
document.getElementById('nav-tickets')?.classList.add('active');
|
||||||
|
|||||||
@@ -0,0 +1,213 @@
|
|||||||
|
/**
|
||||||
|
* ActivityLogView — Storico Attività
|
||||||
|
* Displays the local SQLite activity log with filters, dual pagination,
|
||||||
|
* and an expandable JSON detail panel.
|
||||||
|
*/
|
||||||
|
const ActivityLogView = {
|
||||||
|
currentPage: 1,
|
||||||
|
perPage: 50,
|
||||||
|
filters: { esito: '', agente_id: '', da: '', a: '' },
|
||||||
|
|
||||||
|
async render() {
|
||||||
|
this.currentPage = 1;
|
||||||
|
const container = document.getElementById('view-container');
|
||||||
|
container.innerHTML = '<div class="loading-screen"><div class="spinner"></div><p>Caricamento storico...</p></div>';
|
||||||
|
await this._draw();
|
||||||
|
},
|
||||||
|
|
||||||
|
async _draw() {
|
||||||
|
const container = document.getElementById('view-container');
|
||||||
|
try {
|
||||||
|
const params = new URLSearchParams({
|
||||||
|
page: this.currentPage,
|
||||||
|
per_page: this.perPage,
|
||||||
|
});
|
||||||
|
if (this.filters.esito) params.set('esito', this.filters.esito);
|
||||||
|
if (this.filters.agente_id) params.set('agente_id', this.filters.agente_id);
|
||||||
|
if (this.filters.da) params.set('da', this.filters.da);
|
||||||
|
if (this.filters.a) params.set('a', this.filters.a);
|
||||||
|
|
||||||
|
const data = await App.api(`/api/attivita?${params}`);
|
||||||
|
const { rows, total, page, per_page, total_pages } = data;
|
||||||
|
|
||||||
|
container.innerHTML = this._buildHtml(rows, total, page, per_page, total_pages);
|
||||||
|
this._bind();
|
||||||
|
} catch (err) {
|
||||||
|
container.innerHTML = `<div class="empty-state"><p style="color:var(--danger)">Errore caricamento: ${App.escapeHtml(err.message)}</p></div>`;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
_buildHtml(rows, total, page, per_page, total_pages) {
|
||||||
|
const filtersHtml = this._buildFilters();
|
||||||
|
const paginationHtml = this._buildPagination(total, page, per_page, total_pages);
|
||||||
|
const tableHtml = rows.length === 0
|
||||||
|
? `<div class="empty-state" style="padding:var(--space-2xl);text-align:center;color:var(--text-muted);">
|
||||||
|
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" style="width:48px;height:48px;margin:0 auto var(--space-md);display:block;opacity:0.4;"><circle cx="12" cy="12" r="10"/><polyline points="12 6 12 12 16 14"/></svg>
|
||||||
|
<p>Nessuna attività registrata.</p>
|
||||||
|
</div>`
|
||||||
|
: `<div class="table-wrapper" style="overflow-x:auto;">
|
||||||
|
<table class="tickets-table" id="activity-table" style="width:100%;">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th style="width:160px;">Data/Ora</th>
|
||||||
|
<th style="width:160px;">Agente</th>
|
||||||
|
<th style="width:180px;">Azione</th>
|
||||||
|
<th style="min-width:60px;text-align:center;">Esito</th>
|
||||||
|
<th style="width:48px;text-align:center;">≡</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
${rows.map(r => this._buildRow(r)).join('')}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>`;
|
||||||
|
|
||||||
|
return `
|
||||||
|
<div class="view-header" style="padding:var(--space-md) var(--space-xl);display:flex;align-items:center;justify-content:space-between;flex-wrap:wrap;gap:var(--space-sm);border-bottom:1px solid var(--border-subtle);">
|
||||||
|
<div style="display:flex;align-items:center;gap:var(--space-sm);">
|
||||||
|
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" style="width:20px;height:20px;color:var(--primary);"><circle cx="12" cy="12" r="10"/><polyline points="12 6 12 12 16 14"/></svg>
|
||||||
|
<span style="font-weight:600;font-size:1rem;">Storico Attività</span>
|
||||||
|
<span class="badge" style="background:var(--bg-tertiary);color:var(--text-secondary);font-size:0.75rem;padding:2px 8px;border-radius:12px;">${total} record</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
${filtersHtml}
|
||||||
|
${paginationHtml}
|
||||||
|
${tableHtml}
|
||||||
|
${rows.length > 0 ? paginationHtml.replace(/id="pagination-top"/g,'id="pagination-bottom"') : ''}
|
||||||
|
`;
|
||||||
|
},
|
||||||
|
|
||||||
|
_buildRow(r) {
|
||||||
|
const dt = r.creato_il ? new Date(r.creato_il).toLocaleString('it-IT') : '—';
|
||||||
|
const esitoBadge = r.esito === 'successo'
|
||||||
|
? `<span style="display:inline-block;padding:2px 10px;border-radius:12px;background:rgba(34,197,94,0.15);color:#16a34a;font-size:0.75rem;font-weight:600;">✓ successo</span>`
|
||||||
|
: `<span style="display:inline-block;padding:2px 10px;border-radius:12px;background:rgba(239,68,68,0.15);color:#dc2626;font-size:0.75rem;font-weight:600;">✕ errore</span>`;
|
||||||
|
|
||||||
|
return `
|
||||||
|
<tr id="row-${r.id}" data-id="${r.id}">
|
||||||
|
<td style="font-size:0.8rem;color:var(--text-secondary);white-space:nowrap;">${dt}</td>
|
||||||
|
<td style="font-size:0.85rem;">${App.escapeHtml(r.agente_nome || '—')}</td>
|
||||||
|
<td style="font-size:0.85rem;font-weight:500;">${App.escapeHtml(r.titolo_azione)}</td>
|
||||||
|
<td style="text-align:center;">${esitoBadge}</td>
|
||||||
|
<td style="text-align:center;">
|
||||||
|
<button class="btn btn-ghost btn-sm act-detail-btn" data-id="${r.id}" title="Mostra dettaglio" style="padding:4px 8px;font-size:0.85rem;">≡</button>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr id="detail-${r.id}" class="act-detail-row" style="display:none;">
|
||||||
|
<td colspan="5" style="padding:0 var(--space-md) var(--space-md);background:var(--bg-secondary);">
|
||||||
|
<pre style="margin:0;padding:var(--space-md);background:var(--bg-tertiary);border-radius:var(--radius-md);font-size:0.78rem;overflow-x:auto;white-space:pre-wrap;word-break:break-all;color:var(--text-primary);border:1px solid var(--border-subtle);">${App.escapeHtml(this._prettyJson(r.azione))}</pre>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
`;
|
||||||
|
},
|
||||||
|
|
||||||
|
_prettyJson(str) {
|
||||||
|
try {
|
||||||
|
return JSON.stringify(JSON.parse(str), null, 2);
|
||||||
|
} catch (_) {
|
||||||
|
return str || '';
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
_buildFilters() {
|
||||||
|
return `
|
||||||
|
<div id="activity-filters" style="display:flex;flex-wrap:wrap;gap:var(--space-sm);padding:var(--space-md) var(--space-xl);border-bottom:1px solid var(--border-subtle);background:var(--bg-secondary);align-items:flex-end;">
|
||||||
|
<div style="display:flex;flex-direction:column;gap:4px;">
|
||||||
|
<label style="font-size:0.75rem;color:var(--text-muted);font-weight:500;">Esito</label>
|
||||||
|
<select id="filter-esito" class="form-select" style="min-width:120px;height:36px;font-size:0.85rem;padding:6px 28px 6px 10px;">
|
||||||
|
<option value="">Tutti</option>
|
||||||
|
<option value="successo" ${this.filters.esito === 'successo' ? 'selected' : ''}>✓ Successo</option>
|
||||||
|
<option value="errore" ${this.filters.esito === 'errore' ? 'selected' : ''}>✕ Errore</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div style="display:flex;flex-direction:column;gap:4px;">
|
||||||
|
<label style="font-size:0.75rem;color:var(--text-muted);font-weight:500;">Da data</label>
|
||||||
|
<input type="datetime-local" id="filter-da" class="form-input" value="${this.filters.da}" style="height:36px;font-size:0.85rem;padding:6px 10px;">
|
||||||
|
</div>
|
||||||
|
<div style="display:flex;flex-direction:column;gap:4px;">
|
||||||
|
<label style="font-size:0.75rem;color:var(--text-muted);font-weight:500;">A data</label>
|
||||||
|
<input type="datetime-local" id="filter-a" class="form-input" value="${this.filters.a}" style="height:36px;font-size:0.85rem;padding:6px 10px;">
|
||||||
|
</div>
|
||||||
|
<div style="display:flex;gap:var(--space-xs);align-self:flex-end;">
|
||||||
|
<button id="btn-apply-filters" class="btn btn-primary btn-sm" style="height:36px;">Applica</button>
|
||||||
|
<button id="btn-reset-filters" class="btn btn-ghost btn-sm" style="height:36px;">Reset</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
},
|
||||||
|
|
||||||
|
_buildPagination(total, page, per_page, total_pages) {
|
||||||
|
if (total_pages <= 1) return '';
|
||||||
|
const from = (page - 1) * per_page + 1;
|
||||||
|
const to = Math.min(page * per_page, total);
|
||||||
|
|
||||||
|
const pageBtn = (p, label, disabled, active) => {
|
||||||
|
const isDisabled = disabled || p === page;
|
||||||
|
return `<button class="btn btn-ghost btn-sm page-btn" data-page="${p}"
|
||||||
|
style="min-width:36px;height:32px;${active ? 'background:var(--primary);color:#fff;' : ''}${isDisabled ? 'opacity:0.4;pointer-events:none;' : ''}"
|
||||||
|
${disabled || active ? 'disabled' : ''}>${label}</button>`;
|
||||||
|
};
|
||||||
|
|
||||||
|
const pages = [];
|
||||||
|
pages.push(pageBtn(1, '«', page === 1, false));
|
||||||
|
pages.push(pageBtn(page - 1, '‹', page === 1, false));
|
||||||
|
|
||||||
|
const rangeStart = Math.max(1, page - 2);
|
||||||
|
const rangeEnd = Math.min(total_pages, page + 2);
|
||||||
|
for (let p = rangeStart; p <= rangeEnd; p++) {
|
||||||
|
pages.push(pageBtn(p, p, false, p === page));
|
||||||
|
}
|
||||||
|
|
||||||
|
pages.push(pageBtn(page + 1, '›', page === total_pages, false));
|
||||||
|
pages.push(pageBtn(total_pages, '»', page === total_pages, false));
|
||||||
|
|
||||||
|
return `
|
||||||
|
<div id="pagination-top" style="display:flex;align-items:center;justify-content:space-between;padding:var(--space-sm) var(--space-xl);flex-wrap:wrap;gap:var(--space-sm);">
|
||||||
|
<span style="font-size:0.8rem;color:var(--text-muted);">Record ${from}–${to} di ${total}</span>
|
||||||
|
<div style="display:flex;gap:4px;flex-wrap:wrap;">${pages.join('')}</div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
},
|
||||||
|
|
||||||
|
_bind() {
|
||||||
|
// Filter apply
|
||||||
|
document.getElementById('btn-apply-filters')?.addEventListener('click', () => {
|
||||||
|
this.filters.esito = document.getElementById('filter-esito')?.value || '';
|
||||||
|
this.filters.da = document.getElementById('filter-da')?.value || '';
|
||||||
|
this.filters.a = document.getElementById('filter-a')?.value || '';
|
||||||
|
this.currentPage = 1;
|
||||||
|
this._draw();
|
||||||
|
});
|
||||||
|
|
||||||
|
// Filter reset
|
||||||
|
document.getElementById('btn-reset-filters')?.addEventListener('click', () => {
|
||||||
|
this.filters = { esito: '', agente_id: '', da: '', a: '' };
|
||||||
|
this.currentPage = 1;
|
||||||
|
this._draw();
|
||||||
|
});
|
||||||
|
|
||||||
|
// Detail expand/collapse
|
||||||
|
document.querySelectorAll('.act-detail-btn').forEach(btn => {
|
||||||
|
btn.addEventListener('click', () => {
|
||||||
|
const rowId = btn.dataset.id;
|
||||||
|
const detailRow = document.getElementById(`detail-${rowId}`);
|
||||||
|
if (!detailRow) return;
|
||||||
|
const isOpen = detailRow.style.display !== 'none';
|
||||||
|
detailRow.style.display = isOpen ? 'none' : 'table-row';
|
||||||
|
btn.textContent = isOpen ? '≡' : '✕';
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// Pagination buttons (top and bottom share same .page-btn class)
|
||||||
|
document.querySelectorAll('.page-btn').forEach(btn => {
|
||||||
|
btn.addEventListener('click', () => {
|
||||||
|
const p = parseInt(btn.dataset.page, 10);
|
||||||
|
if (!isNaN(p)) {
|
||||||
|
this.currentPage = p;
|
||||||
|
this._draw();
|
||||||
|
document.getElementById('view-container')?.scrollIntoView({ behavior: 'smooth', block: 'start' });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
},
|
||||||
|
};
|
||||||
@@ -0,0 +1,66 @@
|
|||||||
|
/**
|
||||||
|
* routes/activity.js
|
||||||
|
* GET /api/attivita — Paginates and filters the local SQLite activity log.
|
||||||
|
*/
|
||||||
|
const express = require('express');
|
||||||
|
const router = express.Router();
|
||||||
|
const { db } = require('../activityDb');
|
||||||
|
|
||||||
|
router.get('/', (req, res) => {
|
||||||
|
try {
|
||||||
|
const {
|
||||||
|
page = 1,
|
||||||
|
per_page = 50,
|
||||||
|
agente_id,
|
||||||
|
esito,
|
||||||
|
da,
|
||||||
|
a,
|
||||||
|
} = req.query;
|
||||||
|
|
||||||
|
const pageNum = Math.max(1, parseInt(page, 10));
|
||||||
|
const perPageNum = Math.min(200, Math.max(1, parseInt(per_page, 10)));
|
||||||
|
const offset = (pageNum - 1) * perPageNum;
|
||||||
|
|
||||||
|
const conditions = [];
|
||||||
|
const params = [];
|
||||||
|
|
||||||
|
if (agente_id) {
|
||||||
|
conditions.push('agente_id = ?');
|
||||||
|
params.push(parseInt(agente_id, 10));
|
||||||
|
}
|
||||||
|
if (esito) {
|
||||||
|
conditions.push('esito = ?');
|
||||||
|
params.push(esito);
|
||||||
|
}
|
||||||
|
if (da) {
|
||||||
|
conditions.push('creato_il >= ?');
|
||||||
|
params.push(da);
|
||||||
|
}
|
||||||
|
if (a) {
|
||||||
|
conditions.push('creato_il <= ?');
|
||||||
|
params.push(a);
|
||||||
|
}
|
||||||
|
|
||||||
|
const whereClause = conditions.length > 0 ? `WHERE ${conditions.join(' AND ')}` : '';
|
||||||
|
|
||||||
|
const totalRow = db.prepare(`SELECT COUNT(*) AS cnt FROM attivita ${whereClause}`).get(...params);
|
||||||
|
const total = totalRow ? totalRow.cnt : 0;
|
||||||
|
|
||||||
|
const rows = db
|
||||||
|
.prepare(`SELECT * FROM attivita ${whereClause} ORDER BY creato_il DESC LIMIT ? OFFSET ?`)
|
||||||
|
.all(...params, perPageNum, offset);
|
||||||
|
|
||||||
|
res.json({
|
||||||
|
rows,
|
||||||
|
total,
|
||||||
|
page: pageNum,
|
||||||
|
per_page: perPageNum,
|
||||||
|
total_pages: Math.ceil(total / perPageNum),
|
||||||
|
});
|
||||||
|
} catch (err) {
|
||||||
|
console.error('[activity] Error fetching activities:', err);
|
||||||
|
res.status(500).json({ error: err.message });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
module.exports = router;
|
||||||
@@ -1,6 +1,23 @@
|
|||||||
const express = require('express');
|
const express = require('express');
|
||||||
const router = express.Router();
|
const router = express.Router();
|
||||||
const pool = require('../db');
|
const pool = require('../db');
|
||||||
|
const { logAttivita } = require('../activityDb');
|
||||||
|
|
||||||
|
// Helper: resolve agent name from DB (best-effort, non-blocking)
|
||||||
|
async function resolveAgentName(agentId) {
|
||||||
|
try {
|
||||||
|
const r = await pool.query(
|
||||||
|
`SELECT first_name, last_name, login FROM users WHERE id = $1`,
|
||||||
|
[agentId]
|
||||||
|
);
|
||||||
|
if (r.rows.length > 0) {
|
||||||
|
const u = r.rows[0];
|
||||||
|
return (u.first_name || '') + ' ' + (u.last_name || '') || u.login || `Agent #${agentId}`;
|
||||||
|
}
|
||||||
|
} catch (_) { /* ignore */ }
|
||||||
|
return `Agent #${agentId}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
// Helper for OTRS CE GenericInterface REST API calls
|
// Helper for OTRS CE GenericInterface REST API calls
|
||||||
async function otrsRequest(method, path, bodyData = {}) {
|
async function otrsRequest(method, path, bodyData = {}) {
|
||||||
@@ -301,6 +318,7 @@ router.get('/:id', async (req, res) => {
|
|||||||
// POST /api/tickets — Create new ticket
|
// POST /api/tickets — Create new ticket
|
||||||
// ============================================================
|
// ============================================================
|
||||||
router.post('/', async (req, res) => {
|
router.post('/', async (req, res) => {
|
||||||
|
const operatorId = parseInt(req.headers['x-agent-id'], 10) || 1;
|
||||||
const client = await pool.connect();
|
const client = await pool.connect();
|
||||||
try {
|
try {
|
||||||
const {
|
const {
|
||||||
@@ -528,6 +546,17 @@ router.post('/', async (req, res) => {
|
|||||||
|
|
||||||
await client.query('COMMIT');
|
await client.query('COMMIT');
|
||||||
|
|
||||||
|
// Log activity
|
||||||
|
resolveAgentName(operatorId).then(agente_nome => {
|
||||||
|
logAttivita({
|
||||||
|
agente_id: operatorId,
|
||||||
|
agente_nome,
|
||||||
|
titolo_azione: 'Creazione Ticket',
|
||||||
|
azione: { ticket_id: ticketId, tn: ticketResult.rows[0].tn, title, queue_id, state_id, priority_id, customer_id, customer_user_id },
|
||||||
|
esito: 'successo',
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
res.status(201).json({
|
res.status(201).json({
|
||||||
id: ticketId,
|
id: ticketId,
|
||||||
tn: ticketResult.rows[0].tn,
|
tn: ticketResult.rows[0].tn,
|
||||||
@@ -536,6 +565,9 @@ router.post('/', async (req, res) => {
|
|||||||
} catch (err) {
|
} catch (err) {
|
||||||
await client.query('ROLLBACK');
|
await client.query('ROLLBACK');
|
||||||
console.error('Error creating ticket:', err);
|
console.error('Error creating ticket:', err);
|
||||||
|
resolveAgentName(operatorId).then(agente_nome => {
|
||||||
|
logAttivita({ agente_id: operatorId, agente_nome, titolo_azione: 'Creazione Ticket', azione: { error: err.message }, esito: 'errore' });
|
||||||
|
});
|
||||||
res.status(500).json({ error: err.message });
|
res.status(500).json({ error: err.message });
|
||||||
} finally {
|
} finally {
|
||||||
client.release();
|
client.release();
|
||||||
@@ -620,6 +652,16 @@ router.patch('/:id', async (req, res) => {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
const result = await otrsRequest('PATCH', `/Ticket/${id}`, reqBody);
|
const result = await otrsRequest('PATCH', `/Ticket/${id}`, reqBody);
|
||||||
|
const isClosing = updates.ticket_state_id && current.ticket_state_id !== updates.ticket_state_id;
|
||||||
|
resolveAgentName(operatorId).then(agente_nome => {
|
||||||
|
logAttivita({
|
||||||
|
agente_id: operatorId,
|
||||||
|
agente_nome,
|
||||||
|
titolo_azione: isClosing ? 'Chiusura Ticket' : 'Modifica Rapida Ticket',
|
||||||
|
azione: { ticket_id: id, ...updates },
|
||||||
|
esito: 'successo',
|
||||||
|
});
|
||||||
|
});
|
||||||
return res.json({ message: 'Ticket aggiornato! (via API REST)', result });
|
return res.json({ message: 'Ticket aggiornato! (via API REST)', result });
|
||||||
}
|
}
|
||||||
return res.json({ message: 'Nessuna modifica rilevata' });
|
return res.json({ message: 'Nessuna modifica rilevata' });
|
||||||
@@ -819,10 +861,23 @@ router.patch('/:id', async (req, res) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
await client.query('COMMIT');
|
await client.query('COMMIT');
|
||||||
|
const isClosingDB = updates.ticket_state_id && current.ticket_state_id !== updates.ticket_state_id;
|
||||||
|
resolveAgentName(operatorId).then(agente_nome => {
|
||||||
|
logAttivita({
|
||||||
|
agente_id: operatorId,
|
||||||
|
agente_nome,
|
||||||
|
titolo_azione: isClosingDB ? 'Chiusura Ticket' : 'Modifica Rapida Ticket',
|
||||||
|
azione: { ticket_id: id, ...updates },
|
||||||
|
esito: 'successo',
|
||||||
|
});
|
||||||
|
});
|
||||||
res.json({ message: 'Ticket aggiornato! (via DB)' });
|
res.json({ message: 'Ticket aggiornato! (via DB)' });
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
await client.query('ROLLBACK');
|
await client.query('ROLLBACK');
|
||||||
console.error('Error updating ticket:', err);
|
console.error('Error updating ticket:', err);
|
||||||
|
resolveAgentName(operatorId).then(agente_nome => {
|
||||||
|
logAttivita({ agente_id: operatorId, agente_nome, titolo_azione: 'Modifica Rapida Ticket', azione: { ticket_id: id, error: err.message }, esito: 'errore' });
|
||||||
|
});
|
||||||
res.status(500).json({ error: err.message });
|
res.status(500).json({ error: err.message });
|
||||||
} finally {
|
} finally {
|
||||||
client.release();
|
client.release();
|
||||||
@@ -902,6 +957,15 @@ router.post('/:id/articles', async (req, res) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const result = await otrsRequest('PATCH', `/Ticket/${id}`, payload);
|
const result = await otrsRequest('PATCH', `/Ticket/${id}`, payload);
|
||||||
|
resolveAgentName(operatorId).then(agente_nome => {
|
||||||
|
logAttivita({
|
||||||
|
agente_id: operatorId,
|
||||||
|
agente_nome,
|
||||||
|
titolo_azione: 'Aggiunta Nota',
|
||||||
|
azione: { ticket_id: id, subject, time_unit, has_attachments: !!(attachments && attachments.length) },
|
||||||
|
esito: 'successo',
|
||||||
|
});
|
||||||
|
});
|
||||||
return res.status(201).json({
|
return res.status(201).json({
|
||||||
message: 'Nota aggiunta! (via API REST)',
|
message: 'Nota aggiunta! (via API REST)',
|
||||||
article_id: result.ArticleID,
|
article_id: result.ArticleID,
|
||||||
@@ -1065,6 +1129,16 @@ router.post('/:id/articles', async (req, res) => {
|
|||||||
|
|
||||||
await client.query('COMMIT');
|
await client.query('COMMIT');
|
||||||
|
|
||||||
|
resolveAgentName(operatorId).then(agente_nome => {
|
||||||
|
logAttivita({
|
||||||
|
agente_id: operatorId,
|
||||||
|
agente_nome,
|
||||||
|
titolo_azione: 'Aggiunta Nota',
|
||||||
|
azione: { ticket_id: id, subject, time_unit, has_attachments: !!(attachments && attachments.length) },
|
||||||
|
esito: 'successo',
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
res.status(201).json({
|
res.status(201).json({
|
||||||
article_id: articleId,
|
article_id: articleId,
|
||||||
message: 'Nota aggiunta! (via DB)',
|
message: 'Nota aggiunta! (via DB)',
|
||||||
@@ -1072,6 +1146,9 @@ router.post('/:id/articles', async (req, res) => {
|
|||||||
} catch (err) {
|
} catch (err) {
|
||||||
await client.query('ROLLBACK');
|
await client.query('ROLLBACK');
|
||||||
console.error('Error adding article:', err);
|
console.error('Error adding article:', err);
|
||||||
|
resolveAgentName(operatorId).then(agente_nome => {
|
||||||
|
logAttivita({ agente_id: operatorId, agente_nome, titolo_azione: 'Aggiunta Nota', azione: { ticket_id: id, error: err.message }, esito: 'errore' });
|
||||||
|
});
|
||||||
res.status(500).json({ error: err.message });
|
res.status(500).json({ error: err.message });
|
||||||
} finally {
|
} finally {
|
||||||
client.release();
|
client.release();
|
||||||
@@ -1123,6 +1200,15 @@ router.patch('/batch/update', async (req, res) => {
|
|||||||
Ticket: ticketFields
|
Ticket: ticketFields
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
resolveAgentName(operatorId).then(agente_nome => {
|
||||||
|
logAttivita({
|
||||||
|
agente_id: operatorId,
|
||||||
|
agente_nome,
|
||||||
|
titolo_azione: 'Azione Massiva',
|
||||||
|
azione: { ticket_ids, updates },
|
||||||
|
esito: 'successo',
|
||||||
|
});
|
||||||
|
});
|
||||||
return res.json({
|
return res.json({
|
||||||
message: `${ticket_ids.length} ticket aggiornati! (via API REST)`,
|
message: `${ticket_ids.length} ticket aggiornati! (via API REST)`,
|
||||||
updated_count: ticket_ids.length,
|
updated_count: ticket_ids.length,
|
||||||
@@ -1172,6 +1258,16 @@ router.patch('/batch/update', async (req, res) => {
|
|||||||
|
|
||||||
await client.query('COMMIT');
|
await client.query('COMMIT');
|
||||||
|
|
||||||
|
resolveAgentName(operatorId).then(agente_nome => {
|
||||||
|
logAttivita({
|
||||||
|
agente_id: operatorId,
|
||||||
|
agente_nome,
|
||||||
|
titolo_azione: 'Azione Massiva',
|
||||||
|
azione: { ticket_ids, updates },
|
||||||
|
esito: 'successo',
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
res.json({
|
res.json({
|
||||||
message: `${ticket_ids.length} ticket aggiornati! (via DB)`,
|
message: `${ticket_ids.length} ticket aggiornati! (via DB)`,
|
||||||
updated_count: ticket_ids.length,
|
updated_count: ticket_ids.length,
|
||||||
@@ -1179,6 +1275,9 @@ router.patch('/batch/update', async (req, res) => {
|
|||||||
} catch (err) {
|
} catch (err) {
|
||||||
await client.query('ROLLBACK');
|
await client.query('ROLLBACK');
|
||||||
console.error('Error batch updating tickets:', err);
|
console.error('Error batch updating tickets:', err);
|
||||||
|
resolveAgentName(operatorId).then(agente_nome => {
|
||||||
|
logAttivita({ agente_id: operatorId, agente_nome, titolo_azione: 'Azione Massiva', azione: { ticket_ids, error: err.message }, esito: 'errore' });
|
||||||
|
});
|
||||||
res.status(500).json({ error: err.message });
|
res.status(500).json({ error: err.message });
|
||||||
} finally {
|
} finally {
|
||||||
client.release();
|
client.release();
|
||||||
@@ -1382,10 +1481,22 @@ router.post('/merge', async (req, res) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
await client.query('COMMIT');
|
await client.query('COMMIT');
|
||||||
|
resolveAgentName(operatorId).then(agente_nome => {
|
||||||
|
logAttivita({
|
||||||
|
agente_id: operatorId,
|
||||||
|
agente_nome,
|
||||||
|
titolo_azione: 'Unione Ticket',
|
||||||
|
azione: { target_id: targetId, source_ids: sourceIds },
|
||||||
|
esito: 'successo',
|
||||||
|
});
|
||||||
|
});
|
||||||
res.json({ message: 'Ticket uniti con successo!' });
|
res.json({ message: 'Ticket uniti con successo!' });
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
await client.query('ROLLBACK');
|
await client.query('ROLLBACK');
|
||||||
console.error('Error merging tickets:', err);
|
console.error('Error merging tickets:', err);
|
||||||
|
resolveAgentName(operatorId).then(agente_nome => {
|
||||||
|
logAttivita({ agente_id: operatorId, agente_nome, titolo_azione: 'Unione Ticket', azione: { target_id: targetId, source_ids: sourceIds, error: err.message }, esito: 'errore' });
|
||||||
|
});
|
||||||
res.status(500).json({ error: err.message });
|
res.status(500).json({ error: err.message });
|
||||||
} finally {
|
} finally {
|
||||||
client.release();
|
client.release();
|
||||||
@@ -1446,10 +1557,22 @@ router.put('/articles/:articleId/time', async (req, res) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
await client.query('COMMIT');
|
await client.query('COMMIT');
|
||||||
|
resolveAgentName(operatorId).then(agente_nome => {
|
||||||
|
logAttivita({
|
||||||
|
agente_id: operatorId,
|
||||||
|
agente_nome,
|
||||||
|
titolo_azione: 'Modifica Tempo Articolo',
|
||||||
|
azione: { article_id: articleId, time_unit: parsedTime },
|
||||||
|
esito: 'successo',
|
||||||
|
});
|
||||||
|
});
|
||||||
res.json({ message: 'Tempo aggiornato con successo!' });
|
res.json({ message: 'Tempo aggiornato con successo!' });
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
await client.query('ROLLBACK');
|
await client.query('ROLLBACK');
|
||||||
console.error('Error updating article time:', err);
|
console.error('Error updating article time:', err);
|
||||||
|
resolveAgentName(operatorId).then(agente_nome => {
|
||||||
|
logAttivita({ agente_id: operatorId, agente_nome, titolo_azione: 'Modifica Tempo Articolo', azione: { article_id: articleId, error: err.message }, esito: 'errore' });
|
||||||
|
});
|
||||||
res.status(500).json({ error: err.message });
|
res.status(500).json({ error: err.message });
|
||||||
} finally {
|
} finally {
|
||||||
client.release();
|
client.release();
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ const path = require('path');
|
|||||||
const ticketsRouter = require('./routes/tickets');
|
const ticketsRouter = require('./routes/tickets');
|
||||||
const lookupsRouter = require('./routes/lookups');
|
const lookupsRouter = require('./routes/lookups');
|
||||||
const dashboardRouter = require('./routes/dashboard');
|
const dashboardRouter = require('./routes/dashboard');
|
||||||
|
const activityRouter = require('./routes/activity');
|
||||||
|
|
||||||
const app = express();
|
const app = express();
|
||||||
const PORT = process.env.PORT || 3000;
|
const PORT = process.env.PORT || 3000;
|
||||||
@@ -21,6 +22,7 @@ app.use(express.static(path.join(__dirname, 'public')));
|
|||||||
app.use('/api/tickets', ticketsRouter);
|
app.use('/api/tickets', ticketsRouter);
|
||||||
app.use('/api', lookupsRouter);
|
app.use('/api', lookupsRouter);
|
||||||
app.use('/api/dashboard', dashboardRouter);
|
app.use('/api/dashboard', dashboardRouter);
|
||||||
|
app.use('/api/attivita', activityRouter);
|
||||||
|
|
||||||
// SPA fallback — serve index.html for all non-API routes
|
// SPA fallback — serve index.html for all non-API routes
|
||||||
app.get('*', (req, res) => {
|
app.get('*', (req, res) => {
|
||||||
|
|||||||
Reference in New Issue
Block a user