diff --git a/README.md b/README.md index 8f6a6bf..639b86e 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -

+

Ctrl-Q makes life easier for Qlik Sense admins and developers.

diff --git a/package-lock.json b/package-lock.json index 2a3b81e..87d5f50 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,7 +9,7 @@ "version": "4.0.0", "license": "MIT", "dependencies": { - "@qlik/api": "^1.24.0", + "@qlik/api": "^1.25.0", "axios": "^1.7.7", "commander": "^12.1.0", "csv-parse": "^5.5.6", @@ -37,7 +37,7 @@ "devDependencies": { "@babel/eslint-parser": "^7.25.9", "@babel/plugin-syntax-import-assertions": "^7.26.0", - "@eslint/js": "^9.14.0", + "@eslint/js": "^9.15.0", "@jest/globals": "^29.7.0", "eslint-config-prettier": "^9.1.0", "eslint-plugin-prettier": "^5.2.1", @@ -1281,9 +1281,9 @@ "peer": true }, "node_modules/@eslint/js": { - "version": "9.14.0", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.14.0.tgz", - "integrity": "sha512-pFoEtFWCPyDOl+C6Ift+wC7Ro89otjigCf5vcuWqWgqNSQbRrpjSvdeE6ofLz4dHmyxD5f7gIdGT4+p36L6Twg==", + "version": "9.15.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.15.0.tgz", + "integrity": "sha512-tMTqrY+EzbXmKJR5ToI8lxu7jaN5EdmrBFJpQk5JmSlyLsx6o4t27r883K5xsLuCYCpfKBCGswMSWXsM+jB7lg==", "dev": true, "license": "MIT", "engines": { @@ -1803,9 +1803,9 @@ } }, "node_modules/@qlik/api": { - "version": "1.24.0", - "resolved": "https://registry.npmjs.org/@qlik/api/-/api-1.24.0.tgz", - "integrity": "sha512-TqN+DDk3x3T9xZBF9ULfctGP1Ki+isEqpAGVnRm5DEGlVBDIxxqpiuJLX9aJzKSnCfA7pXPuOsbLVJItgeVlOQ==", + "version": "1.25.0", + "resolved": "https://registry.npmjs.org/@qlik/api/-/api-1.25.0.tgz", + "integrity": "sha512-BvQv/kou2RJCPXtlKRZzI8l1jHDGEhMaqIrVfnXNg2pwDkXajK4a9oEhFekp5raYs1kZ0u6FM+m83ujGkrm9cw==", "license": "ISC", "dependencies": { "enigma.js": "^2.14.0", diff --git a/package.json b/package.json index 92db133..d8dff5e 100644 --- a/package.json +++ b/package.json @@ -30,7 +30,7 @@ "license": "MIT", "type": "module", "dependencies": { - "@qlik/api": "^1.24.0", + "@qlik/api": "^1.25.0", "axios": "^1.7.7", "commander": "^12.1.0", "csv-parse": "^5.5.6", @@ -58,26 +58,12 @@ "devDependencies": { "@babel/eslint-parser": "^7.25.9", "@babel/plugin-syntax-import-assertions": "^7.26.0", - "@eslint/js": "^9.14.0", + "@eslint/js": "^9.15.0", "@jest/globals": "^29.7.0", "eslint-config-prettier": "^9.1.0", "eslint-plugin-prettier": "^5.2.1", "jest": "^29.7.0", "prettier": "^3.3.3", "snyk": "^1.1294.0" - }, - "pkg": { - "assets": [ - "node_modules/axios/**/*", - "node_modules/fsevents/fsevents.node", - "node_modules/csv-stringify/dist/cjs", - "node_modules/enigma.js/**/*.json", - "src/static", - "package.json" - ], - "scripts": [ - "node_modules/enigma.js/**/*.json", - "node_modules/js-yaml/**/*.js" - ] } } diff --git a/release-please-config.json b/release-please-config.json index b6db725..bba0de1 100644 --- a/release-please-config.json +++ b/release-please-config.json @@ -1,6 +1,4 @@ { - "last-release-sha": "7b1d7a8f1b05f8b3b9d56f79abae725016a0ee82", - "release-as": "4.0.0", "draft": true, "release-search-depth": 10, "commit-search-depth": 200, diff --git a/sea-config.json b/sea-config.json index 1ad9cef..4fcf7e3 100644 --- a/sea-config.json +++ b/sea-config.json @@ -4,6 +4,17 @@ "disableExperimentalSEAWarning": true, "assets": { "package.json": "./package.json", + "/404.html": "./src/static/404.html", + "/android-chrome-192x192.png": "./src/static/android-chrome-192x192.png", + "/android-chrome-512x512.png": "./src/static/android-chrome-512x512.png", + "/apple-touch-icon.png": "./src/static/apple-touch-icon.png", + "/ctrl-q.png": "./src/static/ctrl-q.png", + "/favicon-16x16.png": "./src/static/favicon-16x16.png", + "/favicon-32x32.png": "./src/static/favicon-32x32.png", + "/favicon.ico": "./src/static/favicon.ico", + "/index.html": "./src/static/index.html", + "/vis-network.min.js": "./src/static/vis-network.min.js", + "/vis-network.min.js.map": "./src/static/vis-network.min.js", "enigma_schema_12.170.2.json": "./node_modules/enigma.js/schemas/12.170.2.json", "enigma_schema_12.612.0.json": "./node_modules/enigma.js/schemas/12.612.0.json", "enigma_schema_12.936.0.json": "./node_modules/enigma.js/schemas/12.936.0.json", diff --git a/src/__tests__/app_export_cert.test.js b/src/__tests__/app_export_cert.test.js index 8f011b4..19fc487 100644 --- a/src/__tests__/app_export_cert.test.js +++ b/src/__tests__/app_export_cert.test.js @@ -3,7 +3,7 @@ import { jest, test, expect, describe } from '@jest/globals'; import fs from 'node:fs'; import path from 'node:path'; -import exportAppToFile from '../lib/cmd/qseow/exportapp.js'; +import { exportAppToFile } from '../lib/cmd/qseow/exportapp.js'; const options = { logLevel: process.env.CTRL_Q_LOG_LEVEL || 'info', diff --git a/src/__tests__/app_export_jwt.test.js b/src/__tests__/app_export_jwt.test.js index 68f593a..0c7842c 100644 --- a/src/__tests__/app_export_jwt.test.js +++ b/src/__tests__/app_export_jwt.test.js @@ -3,7 +3,7 @@ import { jest, test, expect, describe } from '@jest/globals'; import fs from 'node:fs'; import path from 'node:path'; -import exportAppToFile from '../lib/cmd/qseow/exportapp.js'; +import { exportAppToFile } from '../lib/cmd/qseow/exportapp.js'; const options = { logLevel: process.env.CTRL_Q_LOG_LEVEL || 'info', diff --git a/src/__tests__/app_import_cert.test.js b/src/__tests__/app_import_cert.test.js index 0671688..d6e31ec 100644 --- a/src/__tests__/app_import_cert.test.js +++ b/src/__tests__/app_import_cert.test.js @@ -1,7 +1,7 @@ /* eslint-disable no-console */ import { jest, test, expect, describe } from '@jest/globals'; -import importAppFromFile from '../lib/cmd/qseow/importapp.js'; +import { importAppFromFile } from '../lib/cmd/qseow/importapp.js'; import { appExistById, deleteAppById } from '../lib/util/qseow/app.js'; const options = { diff --git a/src/__tests__/app_import_jwt.test.js b/src/__tests__/app_import_jwt.test.js index 1ae4730..7fabd6b 100644 --- a/src/__tests__/app_import_jwt.test.js +++ b/src/__tests__/app_import_jwt.test.js @@ -1,7 +1,7 @@ /* eslint-disable no-console */ import { jest, test, expect, describe } from '@jest/globals'; -import importAppFromFile from '../lib/cmd/qseow/importapp.js'; +import { importAppFromFile } from '../lib/cmd/qseow/importapp.js'; import { appExistById, deleteAppById } from '../lib/util/qseow/app.js'; const options = { diff --git a/src/__tests__/app_jwt.test.js b/src/__tests__/app_jwt.test.js index c39676c..c8959f8 100644 --- a/src/__tests__/app_jwt.test.js +++ b/src/__tests__/app_jwt.test.js @@ -2,7 +2,7 @@ import { jest, test, expect, describe } from '@jest/globals'; import { getApps, getAppById, appExistById, deleteAppById } from '../lib/util/qseow/app.js'; -import importAppFromFile from '../lib/cmd/qseow/importapp.js'; +import { importAppFromFile } from '../lib/cmd/qseow/importapp.js'; import { sleep } from '../globals.js'; const options = { diff --git a/src/__tests__/bookmark_get_cert.test.js b/src/__tests__/bookmark_get_cert.test.js index 33cbf38..808654b 100644 --- a/src/__tests__/bookmark_get_cert.test.js +++ b/src/__tests__/bookmark_get_cert.test.js @@ -1,7 +1,7 @@ /* eslint-disable no-console */ import { jest, test, expect, describe } from '@jest/globals'; -import getBookmark from '../lib/cmd/qseow/getbookmark.js'; +import { getBookmark } from '../lib/cmd/qseow/getbookmark.js'; const options = { logLevel: process.env.CTRL_Q_LOG_LEVEL || 'info', diff --git a/src/__tests__/bookmark_get_jwt.test.js b/src/__tests__/bookmark_get_jwt.test.js index 85b8a5d..facc874 100644 --- a/src/__tests__/bookmark_get_jwt.test.js +++ b/src/__tests__/bookmark_get_jwt.test.js @@ -1,6 +1,6 @@ import { jest, test, expect, describe } from '@jest/globals'; -import getBookmark from '../lib/cmd/qseow/getbookmark.js'; +import { getBookmark } from '../lib/cmd/qseow/getbookmark.js'; const options = { logLevel: process.env.CTRL_Q_LOG_LEVEL || 'info', diff --git a/src/__tests__/connection_test_cert.test.js b/src/__tests__/connection_test_cert.test.js index 7c0eb79..877e69c 100644 --- a/src/__tests__/connection_test_cert.test.js +++ b/src/__tests__/connection_test_cert.test.js @@ -1,6 +1,6 @@ import { jest, test, expect, describe } from '@jest/globals'; -import testConnection from '../lib/cmd/qseow/testconnection.js'; +import { testConnection } from '../lib/cmd/qseow/testconnection.js'; const options = { logLevel: process.env.CTRL_Q_LOG_LEVEL || 'info', diff --git a/src/__tests__/connection_test_jwt.test.js b/src/__tests__/connection_test_jwt.test.js index cc1a1d5..2386762 100644 --- a/src/__tests__/connection_test_jwt.test.js +++ b/src/__tests__/connection_test_jwt.test.js @@ -1,6 +1,6 @@ import { jest, test, expect, describe } from '@jest/globals'; -import testConnection from '../lib/cmd/qseow/testconnection.js'; +import { testConnection } from '../lib/cmd/qseow/testconnection.js'; const options = { logLevel: process.env.CTRL_Q_LOG_LEVEL || 'info', diff --git a/src/__tests__/script_get_cert.test.js b/src/__tests__/script_get_cert.test.js index 7162292..40689e4 100644 --- a/src/__tests__/script_get_cert.test.js +++ b/src/__tests__/script_get_cert.test.js @@ -1,6 +1,6 @@ import { jest, test, expect, describe } from '@jest/globals'; -import getScript from '../lib/cmd/qseow/getscript.js'; +import { getScript } from '../lib/cmd/qseow/getscript.js'; const options = { logLevel: process.env.CTRL_Q_LOG_LEVEL || 'info', diff --git a/src/__tests__/script_get_jwt.test.js b/src/__tests__/script_get_jwt.test.js index eda1ce4..dcd9b4c 100644 --- a/src/__tests__/script_get_jwt.test.js +++ b/src/__tests__/script_get_jwt.test.js @@ -1,6 +1,6 @@ import { jest, test, expect, describe } from '@jest/globals'; -import getScript from '../lib/cmd/qseow/getscript.js'; +import { getScript } from '../lib/cmd/qseow/getscript.js'; const options = { logLevel: process.env.CTRL_Q_LOG_LEVEL || 'info', diff --git a/src/__tests__/task_custom_property_set_cert.test.js b/src/__tests__/task_custom_property_set_cert.test.js index 5b77c4c..2a840d4 100644 --- a/src/__tests__/task_custom_property_set_cert.test.js +++ b/src/__tests__/task_custom_property_set_cert.test.js @@ -1,6 +1,6 @@ import { jest, test, expect, describe } from '@jest/globals'; -import setTaskCustomProperty from '../lib/cmd/qseow/settaskcp.js'; +import { setTaskCustomProperty } from '../lib/cmd/qseow/settaskcp.js'; import { getTaskById } from '../lib/util/qseow/task.js'; const options = { diff --git a/src/__tests__/task_custom_property_set_jwt.test.js b/src/__tests__/task_custom_property_set_jwt.test.js index f99ca3c..b6b6461 100644 --- a/src/__tests__/task_custom_property_set_jwt.test.js +++ b/src/__tests__/task_custom_property_set_jwt.test.js @@ -1,6 +1,6 @@ import { jest, test, expect, describe } from '@jest/globals'; -import setTaskCustomProperty from '../lib/cmd/qseow/settaskcp.js'; +import { setTaskCustomProperty } from '../lib/cmd/qseow/settaskcp.js'; import { getTaskById } from '../lib/util/qseow/task.js'; const options = { diff --git a/src/__tests__/task_get_cert.test.js b/src/__tests__/task_get_cert.test.js index 7d690b8..60228b3 100644 --- a/src/__tests__/task_get_cert.test.js +++ b/src/__tests__/task_get_cert.test.js @@ -2,7 +2,7 @@ import { jest, test, expect, describe } from '@jest/globals'; import fs from 'node:fs'; import path from 'node:path'; -import getTask from '../lib/cmd/qseow/gettask.js'; +import { getTask } from '../lib/cmd/qseow/gettask.js'; const options = { logLevel: process.env.CTRL_Q_LOG_LEVEL || 'info', diff --git a/src/__tests__/task_get_jwt.test.js b/src/__tests__/task_get_jwt.test.js index 81e4848..11d60ec 100644 --- a/src/__tests__/task_get_jwt.test.js +++ b/src/__tests__/task_get_jwt.test.js @@ -2,7 +2,7 @@ import { jest, test, expect, describe } from '@jest/globals'; import fs from 'node:fs'; import path from 'node:path'; -import getTask from '../lib/cmd/qseow/gettask.js'; +import { getTask } from '../lib/cmd/qseow/gettask.js'; const options = { logLevel: process.env.CTRL_Q_LOG_LEVEL || 'info', diff --git a/src/__tests__/task_import_cert.test.js b/src/__tests__/task_import_cert.test.js index 29b6d20..7c92b5a 100644 --- a/src/__tests__/task_import_cert.test.js +++ b/src/__tests__/task_import_cert.test.js @@ -1,6 +1,6 @@ import { jest, test, expect, describe } from '@jest/globals'; -import importTaskFromFile from '../lib/cmd/qseow/importtask.js'; +import { importTaskFromFile } from '../lib/cmd/qseow/importtask.js'; import { getTaskById, deleteExternalProgramTaskById, deleteReloadTaskById } from '../lib/util/qseow/task.js'; import { mapTaskType } from '../lib/util/qseow/lookups.js'; diff --git a/src/__tests__/task_import_jwt.test.js b/src/__tests__/task_import_jwt.test.js index 7d9faad..533bb82 100644 --- a/src/__tests__/task_import_jwt.test.js +++ b/src/__tests__/task_import_jwt.test.js @@ -1,6 +1,6 @@ import { jest, test, expect, describe } from '@jest/globals'; -import importTaskFromFile from '../lib/cmd/qseow/importtask.js'; +import { importTaskFromFile } from '../lib/cmd/qseow/importtask.js'; import { getTaskById, deleteExternalProgramTaskById, deleteReloadTaskById } from '../lib/util/qseow/task.js'; import { mapTaskType } from '../lib/util/qseow/lookups.js'; diff --git a/src/globals.js b/src/globals.js index 30499a8..25a1057 100644 --- a/src/globals.js +++ b/src/globals.js @@ -13,19 +13,7 @@ let c; export let appVersion; // Are we running as a packaged app? -if (process.pkg) { - // Get path to JS file - a = process.pkg.defaultEntrypoint; - - // Strip off the filename - b = upath.dirname(a); - - // Add path to package.json file - c = upath.join(b, filenamePackage); - - const { version } = JSON.parse(readFileSync(c)); - appVersion = version; -} else if (sea.isSea()) { +if (sea.isSea()) { // Get contents of package.json file packageJson = sea.getAsset('package.json', 'utf8'); const version = JSON.parse(packageJson).version; @@ -75,8 +63,8 @@ export const logger = winston.createLogger({ }); // Are we running as standalone app or not? -export const isPkg = typeof process.pkg !== 'undefined'; -export const execPath = isPkg ? upath.dirname(process.execPath) : process.cwd(); +export const isSea = sea.isSea(); +export const execPath = isSea ? upath.dirname(process.execPath) : process.cwd(); // Functions to get/set current console logging level export const getLoggingLevel = () => logTransports.find((transport) => transport.name === 'console').level; @@ -85,25 +73,16 @@ export const setLoggingLevel = (newLevel) => { logTransports.find((transport) => transport.name === 'console').level = newLevel; }; -export const verifyFileExists = async (file, silent = false) => { +// Separate handling is needed depending on if we are running as a packaged SEA app or not +// SEA apps cannot list bundled assets as directories, so we need to use a different approach +export async function verifyFileSystemExists(file, silent = false) { let exists = false; try { await Fs.stat(file); exists = true; } catch (err) { if (!silent) { - if (isPkg) { - if (err.message) { - // Make message a bit nicer than what's returned from stat() - if (err.message.includes('no such file or directory')) { - logger.error(`File "${file}" does not exist.`); - } else { - logger.error(`Error while checking if file ${file} exists: ${err.message}`); - } - } else { - logger.error(`Error while checking if file ${file} exists: ${err}`); - } - } else if (err.message) { + if (err.message) { if (err.message.includes('no such file or directory')) { logger.error(`File "${file}" does not exist.`); } else { @@ -116,13 +95,33 @@ export const verifyFileExists = async (file, silent = false) => { } } } + return exists; +} +export function verifySeaAssetExists(file, silent = false) { + let exists = false; + try { + sea.getAsset(file, 'utf8'); + exists = true; + } catch (err) { + if (!silent) { + if (err.message) { + if (err.message.includes('no such file or directory')) { + logger.error(`Asset "${file}" does not exist.`); + } else { + logger.error(`Error while checking if asset ${file} exists: ${err.message}`); + } + } else { + logger.error(`Error while checking if asset ${file} exists: ${err}`); + } + } + } return exists; -}; +} export const mergeDirFilePath = (pathElements) => { let fullPath = ''; - if (isPkg) { + if (isSea) { fullPath = upath.resolve(upath.dirname(process.execPath), ...pathElements); } else { // fullPath = upath.resolve(__dirname, ...pathElements); @@ -179,20 +178,3 @@ export const setCliOptions = (options) => { // Function to get CLI options export const getCliOptions = () => cliOptions; - -// export default { -// logger, -// appVersion, -// getLoggingLevel, -// setLoggingLevel, -// execPath, -// isPkg, -// verifyFileExists, -// generateXrfKey, -// readCert, -// isNumeric, -// mergeDirFilePath, -// sleep, -// getCliOptions, -// setCliOptions, -// }; diff --git a/src/lib/app/class_allapps.js b/src/lib/app/class_allapps.js index 4d0e837..1c99374 100644 --- a/src/lib/app/class_allapps.js +++ b/src/lib/app/class_allapps.js @@ -7,17 +7,17 @@ import fs2 from 'node:fs'; import { v4 as uuidv4, validate } from 'uuid'; import yesno from 'yesno'; -import { logger, execPath, mergeDirFilePath, verifyFileExists, sleep } from '../../globals.js'; +import { logger, execPath, mergeDirFilePath, verifyFileSystemExists, sleep } from '../../globals.js'; import { setupQrsConnection } from '../util/qseow/qrs.js'; import { getAppColumnPosFromHeaderRow } from '../util/qseow/lookups.js'; -import QlikSenseApp from './class_app.js'; +import { QlikSenseApp } from './class_app.js'; import { getTagIdByName } from '../util/qseow/tag.js'; import { getAppById, deleteAppById } from '../util/qseow/app.js'; import { getCustomPropertyDefinitionByName, doesCustomPropertyValueExist } from '../util/qseow/customproperties.js'; import { catchLog } from '../util/log.js'; import { getCertFilePaths } from '../util/qseow/cert.js'; -class QlikSenseApps { +export class QlikSenseApps { constructor() { // } @@ -125,31 +125,25 @@ class QlikSenseApps { let axiosConfig; if (this.options.authType === 'cert') { if (filter === '') { - axiosConfig = await setupQrsConnection(this.options, { + axiosConfig = setupQrsConnection(this.options, { method: 'get', - fileCert: this.fileCert, - fileCertKey: this.fileCertKey, - fileCertCA: this.fileCertCA, path: '/qrs/app/full', }); } else { - axiosConfig = await setupQrsConnection(this.options, { + axiosConfig = setupQrsConnection(this.options, { method: 'get', - fileCert: this.fileCert, - fileCertKey: this.fileCertKey, - fileCertCA: this.fileCertCA, path: '/qrs/app/full', queryParameters: [{ name: 'filter', value: filter }], }); } } else if (this.options.authType === 'jwt') { if (filter === '') { - axiosConfig = await setupQrsConnection(this.options, { + axiosConfig = setupQrsConnection(this.options, { method: 'get', path: '/qrs/app/full', }); } else { - axiosConfig = await setupQrsConnection(this.options, { + axiosConfig = setupQrsConnection(this.options, { method: 'get', path: '/qrs/app/full', queryParameters: [{ name: 'filter', value: filter }], @@ -264,7 +258,7 @@ class QlikSenseApps { }"` ); - const qvfFileExists = await verifyFileExists(currentApp.fullQvfPath); + const qvfFileExists = await verifyFileSystemExists(currentApp.fullQvfPath); if (!qvfFileExists) { logger.error( `Import of app file ${appRow[0][appFileColumnHeaders.appCounter.pos]} failed. QVF file does not exist: "${ @@ -552,9 +546,6 @@ class QlikSenseApps { // Get info about just uploaded app axiosConfigUploadedApp = setupQrsConnection(this.options, { method: 'get', - fileCert: this.fileCert, - fileCertKey: this.fileCertKey, - fileCertCA: this.fileCertCA, path: `/qrs/app/${uploadedAppId}`, }); } else if (this.options.authType === 'jwt') { @@ -594,9 +585,6 @@ class QlikSenseApps { // Get info about just uploaded app axiosConfigUser = setupQrsConnection(this.options, { method: 'get', - fileCert: this.fileCert, - fileCertKey: this.fileCertKey, - fileCertCA: this.fileCertCA, path: '/qrs/user', queryParameters: [{ name: 'filter', value: filter }], }); @@ -643,9 +631,6 @@ class QlikSenseApps { // Uppdate app with tags, custom properties and app owner axiosConfig2 = setupQrsConnection(this.options, { method: 'put', - fileCert: this.fileCert, - fileCertKey: this.fileCertKey, - fileCertCA: this.fileCertCA, path: `/qrs/app/${app.id}`, body: app, }); @@ -865,9 +850,6 @@ class QlikSenseApps { // Build QRS query axiosConfig = setupQrsConnection(this.options, { method: 'put', - fileCert: this.fileCert, - fileCertKey: this.fileCertKey, - fileCertCA: this.fileCertCA, path: `/qrs/app/${appId}/publish`, queryParameters, }); @@ -911,9 +893,6 @@ class QlikSenseApps { // Build QRS query axiosConfig = setupQrsConnection(this.options, { method: 'put', - fileCert: this.fileCert, - fileCertKey: this.fileCertKey, - fileCertCA: this.fileCertCA, path: `/qrs/app/${sourceAppId}/replace`, queryParameters, }); @@ -972,9 +951,6 @@ class QlikSenseApps { // Build QRS query axiosConfig = setupQrsConnection(this.options, { method: 'get', - fileCert: this.fileCert, - fileCertKey: this.fileCertKey, - fileCertCA: this.fileCertCA, path: `/qrs/app`, queryParameters: [{ name: 'filter', value: filter }], }); @@ -1019,9 +995,6 @@ class QlikSenseApps { // Build QRS query axiosConfig = setupQrsConnection(this.options, { method: 'get', - fileCert: this.fileCert, - fileCertKey: this.fileCertKey, - fileCertCA: this.fileCertCA, path: `/qrs/app`, queryParameters: [{ name: 'filter', value: filter }], }); @@ -1082,9 +1055,6 @@ class QlikSenseApps { // Build QRS query axiosConfigPublish = setupQrsConnection(this.options, { method: 'get', - fileCert: this.fileCert, - fileCertKey: this.fileCertKey, - fileCertCA: this.fileCertCA, path: `/qrs/stream/${uploadedAppInfo.appPublishToStream}`, }); } else if (this.options.authType === 'jwt') { @@ -1110,9 +1080,6 @@ class QlikSenseApps { // Build QRS query axiosConfigPublish = setupQrsConnection(this.options, { method: 'get', - fileCert: this.fileCert, - fileCertKey: this.fileCertKey, - fileCertCA: this.fileCertCA, path: '/qrs/stream', queryParameters: [{ name: 'filter', value: filter }], }); @@ -1177,9 +1144,6 @@ class QlikSenseApps { // Build Axios config const axiosConfig = setupQrsConnection(this.options, { method: 'post', - fileCert: this.fileCert, - fileCertKey: this.fileCertKey, - fileCertCA: this.fileCertCA, path: '/qrs/app/upload', body: form, headers: { @@ -1276,9 +1240,6 @@ class QlikSenseApps { // Build QRS query axiosConfig = setupQrsConnection(this.options, { method: 'post', - fileCert: this.fileCert, - fileCertKey: this.fileCertKey, - fileCertCA: this.fileCertCA, path: `/qrs/app/${app.id}/export/${exportToken}`, queryParameters: [{ name: 'skipData', value: excludeData }], }); @@ -1310,7 +1271,7 @@ class QlikSenseApps { } } - async exportAppStep2(resultStep1) { + async exportAppStep2(resultStep1, appCounter, appCountTotal) { // resultStep.downloadPath has format // /tempcontent/d989fffd-5310-43b5-b028-f313b53bb8e2/User%20retention.qvf?serverNodeId=80db9b97-8ea2-4208-a79a-c46b7e16c38c @@ -1358,7 +1319,7 @@ class QlikSenseApps { // Check if destination QVF file already exists // 2nd parameter controls whether to log info or not about file's existence - const fileExists = await verifyFileExists(fileName, true); + const fileExists = await verifyFileSystemExists(fileName, true); let fileSkipped = false; let writer; @@ -1389,9 +1350,6 @@ class QlikSenseApps { // Build QRS query axiosConfig = setupQrsConnection(this.options, { method: 'get', - fileCert: this.fileCert, - fileCertKey: this.fileCertKey, - fileCertCA: this.fileCertCA, path: urlPath, queryParameters: [{ name: paramName, value: paramValue }], }); @@ -1406,7 +1364,9 @@ class QlikSenseApps { axiosConfig.responseType = 'stream'; logger.info('------------------------------------'); - logger.info(`App [${resultStep2.appId}] "${resultStep2.appName}.qvf", download starting`); + logger.info( + `${appCounter} of ${appCountTotal}: App [${resultStep2.appId}] "${resultStep2.appName}.qvf", download starting` + ); const result = await axios.request(axiosConfig); result.data.pipe(writer); @@ -1435,5 +1395,3 @@ class QlikSenseApps { }); } } - -export default QlikSenseApps; diff --git a/src/lib/app/class_app.js b/src/lib/app/class_app.js index c95a58f..d735408 100644 --- a/src/lib/app/class_app.js +++ b/src/lib/app/class_app.js @@ -1,5 +1,4 @@ -class QlikSenseApp { - // eslint-disable-next-line no-useless-constructor +export class QlikSenseApp { constructor() { // } @@ -34,5 +33,3 @@ class QlikSenseApp { this.options = options; } } - -export default QlikSenseApp; diff --git a/src/lib/cli/qseow-delete-master-dimension.js b/src/lib/cli/qseow-delete-master-dimension.js index 66739f2..78c4fd8 100644 --- a/src/lib/cli/qseow-delete-master-dimension.js +++ b/src/lib/cli/qseow-delete-master-dimension.js @@ -2,7 +2,7 @@ import { Option } from 'commander'; import { catchLog } from '../util/log.js'; import { qseowSharedParamAssertOptions, masterItemDimDeleteAssertOptions } from '../util/qseow/assert-options.js'; -import deleteMasterDimension from '../cmd/qseow/getdim.js'; +import { deleteMasterDimension } from '../cmd/qseow/deletedim.js'; export function setupQseowDeleteMasterDimensionCommand(qseow) { qseow diff --git a/src/lib/cli/qseow-delete-master-measure.js b/src/lib/cli/qseow-delete-master-measure.js index cb5c045..e51fc78 100644 --- a/src/lib/cli/qseow-delete-master-measure.js +++ b/src/lib/cli/qseow-delete-master-measure.js @@ -2,7 +2,7 @@ import { Option } from 'commander'; import { catchLog } from '../util/log.js'; import { qseowSharedParamAssertOptions, masterItemMeasureDeleteAssertOptions } from '../util/qseow/assert-options.js'; -import deleteMasterMeasure from '../cmd/qseow/deletemeasure.js'; +import { deleteMasterMeasure } from '../cmd/qseow/deletemeasure.js'; export function setupQseowDeleteMasterMeasureCommand(qseow) { qseow diff --git a/src/lib/cli/qseow-delete-proxy-session.js b/src/lib/cli/qseow-delete-proxy-session.js index 4b23249..9718e3b 100644 --- a/src/lib/cli/qseow-delete-proxy-session.js +++ b/src/lib/cli/qseow-delete-proxy-session.js @@ -2,7 +2,7 @@ import { Option } from 'commander'; import { catchLog } from '../util/log.js'; import { qseowSharedParamAssertOptions, deleteSessionsAssertOptions } from '../util/qseow/assert-options.js'; -import deleteSessions from '../cmd/qseow/deletesessions.js'; +import { deleteSessions } from '../cmd/qseow/deletesessions.js'; export function setupQseowDeleteProxySessionsCommand(qseow) { qseow diff --git a/src/lib/cli/qseow-delete-variable.js b/src/lib/cli/qseow-delete-variable.js index 6046f83..312acaa 100644 --- a/src/lib/cli/qseow-delete-variable.js +++ b/src/lib/cli/qseow-delete-variable.js @@ -2,7 +2,7 @@ import { Option } from 'commander'; import { catchLog } from '../util/log.js'; import { qseowSharedParamAssertOptions, variableDeleteAssertOptions } from '../util/qseow/assert-options.js'; -import deleteVariable from '../cmd/qseow/deletevariable.js'; +import { deleteVariable } from '../cmd/qseow/deletevariable.js'; export function setupQseowDeleteVariableCommand(qseow) { qseow diff --git a/src/lib/cli/qseow-export-app-to-file.js b/src/lib/cli/qseow-export-app-to-file.js index 145646a..82d27f1 100644 --- a/src/lib/cli/qseow-export-app-to-file.js +++ b/src/lib/cli/qseow-export-app-to-file.js @@ -2,7 +2,7 @@ import { Option } from 'commander'; import { catchLog } from '../util/log.js'; import { qseowSharedParamAssertOptions, appExportAssertOptions } from '../util/qseow/assert-options.js'; -import exportAppToFile from '../cmd/qseow/importtask.js'; +import { exportAppToFile } from '../cmd/qseow/exportapp.js'; export function setupQseowExportAppCommand(qseow) { qseow diff --git a/src/lib/cli/qseow-get-bookmark.js b/src/lib/cli/qseow-get-bookmark.js index 1335766..3e7fdc9 100644 --- a/src/lib/cli/qseow-get-bookmark.js +++ b/src/lib/cli/qseow-get-bookmark.js @@ -2,7 +2,7 @@ import { Option } from 'commander'; import { catchLog } from '../util/log.js'; import { qseowSharedParamAssertOptions, getBookmarkAssertOptions } from '../util/qseow/assert-options.js'; -import getBookmark from '../cmd/qseow/getbookmark.js'; +import { getBookmark } from '../cmd/qseow/getbookmark.js'; export function setupQseowGetBookmarkCommand(qseow) { qseow diff --git a/src/lib/cli/qseow-get-master-dimension.js b/src/lib/cli/qseow-get-master-dimension.js index 424c1cf..c5ea752 100644 --- a/src/lib/cli/qseow-get-master-dimension.js +++ b/src/lib/cli/qseow-get-master-dimension.js @@ -2,7 +2,7 @@ import { Option } from 'commander'; import { catchLog } from '../util/log.js'; import { qseowSharedParamAssertOptions } from '../util/qseow/assert-options.js'; -import getMasterDimension from '../cmd/qseow/getdim.js'; +import { getMasterDimension } from '../cmd/qseow/getdim.js'; export function setupQseowGetMasterDimensionCommand(qseow) { qseow diff --git a/src/lib/cli/qseow-get-master-measure.js b/src/lib/cli/qseow-get-master-measure.js index 0d229c8..24ce12e 100644 --- a/src/lib/cli/qseow-get-master-measure.js +++ b/src/lib/cli/qseow-get-master-measure.js @@ -2,7 +2,7 @@ import { Option } from 'commander'; import { catchLog } from '../util/log.js'; import { qseowSharedParamAssertOptions, masterItemGetAssertOptions } from '../util/qseow/assert-options.js'; -import getMasterMeasure from '../cmd/qseow/getmeasure.js'; +import { getMasterMeasure } from '../cmd/qseow/getmeasure.js'; export function setupQseowGetMasterMeasureCommand(qseow) { qseow diff --git a/src/lib/cli/qseow-get-proxy-session.js b/src/lib/cli/qseow-get-proxy-session.js index 766a0dd..6481e8a 100644 --- a/src/lib/cli/qseow-get-proxy-session.js +++ b/src/lib/cli/qseow-get-proxy-session.js @@ -2,7 +2,7 @@ import { Option } from 'commander'; import { catchLog } from '../util/log.js'; import { qseowSharedParamAssertOptions, getSessionsAssertOptions } from '../util/qseow/assert-options.js'; -import getSessions from '../cmd/qseow/getsessions.js'; +import { getSessions } from '../cmd/qseow/getsessions.js'; export function setupQseowGetProxySessionsCommand(qseow) { qseow diff --git a/src/lib/cli/qseow-get-script.js b/src/lib/cli/qseow-get-script.js index d0a76d6..a9ef39e 100644 --- a/src/lib/cli/qseow-get-script.js +++ b/src/lib/cli/qseow-get-script.js @@ -2,7 +2,7 @@ import { Option } from 'commander'; import { catchLog } from '../util/log.js'; import { qseowSharedParamAssertOptions, getScriptAssertOptions } from '../util/qseow/assert-options.js'; -import getScript from '../cmd/qseow/getscript.js'; +import { getScript } from '../cmd/qseow/getscript.js'; export function setupGetScriptCommand(qseow) { qseow diff --git a/src/lib/cli/qseow-get-task.js b/src/lib/cli/qseow-get-task.js index 66d369b..834d855 100644 --- a/src/lib/cli/qseow-get-task.js +++ b/src/lib/cli/qseow-get-task.js @@ -2,7 +2,7 @@ import { Option } from 'commander'; import { catchLog } from '../util/log.js'; import { qseowSharedParamAssertOptions, getTaskAssertOptions } from '../util/qseow/assert-options.js'; -import getTask from '../cmd/qseow/gettask.js'; +import { getTask } from '../cmd/qseow/gettask.js'; export function setupGetTaskCommand(qseow) { qseow diff --git a/src/lib/cli/qseow-get-variable.js b/src/lib/cli/qseow-get-variable.js index 8667ddc..ed42ce8 100644 --- a/src/lib/cli/qseow-get-variable.js +++ b/src/lib/cli/qseow-get-variable.js @@ -2,7 +2,7 @@ import { Option } from 'commander'; import { catchLog } from '../util/log.js'; import { qseowSharedParamAssertOptions, variableGetAssertOptions } from '../util/qseow/assert-options.js'; -import getVariable from '../cmd/qseow/getvariable.js'; +import { getVariable } from '../cmd/qseow/getvariable.js'; export function setpQseowGetVariableCommand(qseow) { qseow diff --git a/src/lib/cli/qseow-import-app-from-file.js b/src/lib/cli/qseow-import-app-from-file.js index a6a1841..b72c5bc 100644 --- a/src/lib/cli/qseow-import-app-from-file.js +++ b/src/lib/cli/qseow-import-app-from-file.js @@ -2,7 +2,7 @@ import { Option } from 'commander'; import { catchLog } from '../util/log.js'; import { qseowSharedParamAssertOptions, appImportAssertOptions } from '../util/qseow/assert-options.js'; -import importAppFromFile from '../cmd/qseow/importapp.js'; +import { importAppFromFile } from '../cmd/qseow/importapp.js'; export function setupQseowImportAppFromFileCommand(qseow) { qseow diff --git a/src/lib/cli/qseow-import-task-from-file.js b/src/lib/cli/qseow-import-task-from-file.js index f0e6214..08a1ab1 100644 --- a/src/lib/cli/qseow-import-task-from-file.js +++ b/src/lib/cli/qseow-import-task-from-file.js @@ -2,7 +2,7 @@ import { Option } from 'commander'; import { catchLog } from '../util/log.js'; import { qseowSharedParamAssertOptions, taskImportAssertOptions } from '../util/qseow/assert-options.js'; -import importTaskFromFile from '../cmd/qseow/importtask.js'; +import { importTaskFromFile } from '../cmd/qseow/importtask.js'; export function setupQseowImportTaskFromFileCommand(qseow) { qseow diff --git a/src/lib/cli/qseow-master-item-import.js b/src/lib/cli/qseow-master-item-import.js index 44643b2..0848766 100644 --- a/src/lib/cli/qseow-master-item-import.js +++ b/src/lib/cli/qseow-master-item-import.js @@ -2,7 +2,7 @@ import { Option } from 'commander'; import { catchLog } from '../util/log.js'; import { qseowSharedParamAssertOptions, masterItemImportAssertOptions } from '../util/qseow/assert-options.js'; -import importMasterItemFromFile from '../cmd/qseow/import-masteritem-excel.js'; +import { importMasterItemFromFile } from '../cmd/qseow/import-masteritem-excel.js'; export function setupQseowMasterItemImportCommand(qseow) { qseow diff --git a/src/lib/cli/qseow-scramble-field.js b/src/lib/cli/qseow-scramble-field.js index a29e286..b3da76f 100644 --- a/src/lib/cli/qseow-scramble-field.js +++ b/src/lib/cli/qseow-scramble-field.js @@ -2,7 +2,7 @@ import { Option } from 'commander'; import { catchLog } from '../util/log.js'; import { qseowSharedParamAssertOptions } from '../util/qseow/assert-options.js'; -import scrambleField from '../cmd/qseow/scramblefield.js'; +import { scrambleField } from '../cmd/qseow/scramblefield.js'; export function setupQseowScrambleFieldCommand(qseow) { qseow diff --git a/src/lib/cli/qseow-set-task-cp.js b/src/lib/cli/qseow-set-task-cp.js index afa5f8d..f6f2a5a 100644 --- a/src/lib/cli/qseow-set-task-cp.js +++ b/src/lib/cli/qseow-set-task-cp.js @@ -2,7 +2,7 @@ import { Option } from 'commander'; import { catchLog } from '../util/log.js'; import { qseowSharedParamAssertOptions, setTaskCustomPropertyAssertOptions } from '../util/qseow/assert-options.js'; -import setTaskCustomProperty from '../cmd/qseow/settaskcp.js'; +import { setTaskCustomProperty } from '../cmd/qseow/settaskcp.js'; export function setupQseowSetTaskCustomPropertyCommand(qseow) { qseow diff --git a/src/lib/cli/qseow-show-version.js b/src/lib/cli/qseow-show-version.js index d5a159f..eda1f26 100644 --- a/src/lib/cli/qseow-show-version.js +++ b/src/lib/cli/qseow-show-version.js @@ -1,5 +1,7 @@ import { Option } from 'commander'; +import { appVersion, logger } from '../../globals.js'; + export function setupQseowShowVersionCommand(qseow) { qseow .command('version') @@ -8,6 +10,6 @@ export function setupQseowShowVersionCommand(qseow) { new Option('--log-level ', 'log level').choices(['error', 'warn', 'info', 'verbose', 'debug', 'silly']).default('info') ) .action(async (options) => { - logger.verbose(`Version: ${appVersion}`); + logger.info(`Version: ${appVersion}`); }); } diff --git a/src/lib/cli/qseow-test-connection.js b/src/lib/cli/qseow-test-connection.js index 41a8b86..cb98684 100644 --- a/src/lib/cli/qseow-test-connection.js +++ b/src/lib/cli/qseow-test-connection.js @@ -2,7 +2,7 @@ import { Option } from 'commander'; import { catchLog } from '../util/log.js'; import { qseowSharedParamAssertOptions } from '../util/qseow/assert-options.js'; -import testConnection from '../cmd/qseow/testconnection.js'; +import { testConnection } from '../cmd/qseow/testconnection.js'; export function setupQseowTestConnectionCommand(qseow) { qseow diff --git a/src/lib/cli/qseow-visualise-task.js b/src/lib/cli/qseow-visualise-task.js index 1a9d8ff..75e9a9c 100644 --- a/src/lib/cli/qseow-visualise-task.js +++ b/src/lib/cli/qseow-visualise-task.js @@ -2,7 +2,7 @@ import { Option } from 'commander'; import { catchLog } from '../util/log.js'; import { qseowSharedParamAssertOptions } from '../util/qseow/assert-options.js'; -import visTask from '../cmd/qseow/vistask.js'; +import { visTask } from '../cmd/qseow/vistask.js'; export function setupQseowVisualiseTaskCommand(qseow) { qseow diff --git a/src/lib/cmd/qscloud/testconnection.js b/src/lib/cmd/qscloud/testconnection.js index c065058..dec292f 100644 --- a/src/lib/cmd/qscloud/testconnection.js +++ b/src/lib/cmd/qscloud/testconnection.js @@ -1,4 +1,4 @@ -import { logger, setLoggingLevel, isPkg, execPath } from '../../../globals.js'; +import { logger, setLoggingLevel, isSea, execPath } from '../../../globals.js'; import { getQscloudCurrentUser } from '../../util/qscloud/user.js'; import { catchLog } from '../../util/log.js'; @@ -7,7 +7,7 @@ export async function qscloudTestConnection(options) { // Set log level setLoggingLevel(options.logLevel); - logger.verbose(`Ctrl-Q was started as a stand-alone binary: ${isPkg}`); + logger.verbose(`Ctrl-Q was started as a stand-alone binary: ${isSea}`); logger.verbose(`Ctrl-Q was started from ${execPath}`); logger.info(`Testing connection to Qlik Sense Cloud tenant "${options.tenantUrl}"`); diff --git a/src/lib/cmd/qseow/createdim.js b/src/lib/cmd/qseow/createdim.js index c6efd18..52dfd87 100644 --- a/src/lib/cmd/qseow/createdim.js +++ b/src/lib/cmd/qseow/createdim.js @@ -1,18 +1,18 @@ import enigma from 'enigma.js'; import setupEnigmaConnection from '../../util/qseow/enigma_util.js'; -import { logger, setLoggingLevel, isPkg, execPath } from '../../../globals.js'; +import { logger, setLoggingLevel, isSea, execPath } from '../../../globals.js'; import { catchLog } from '../../util/log.js'; /** * * @param {*} options */ -const createMasterDimension = async (options) => { +export async function createMasterDimension(options) { try { // Set log level setLoggingLevel(options.logLevel); - logger.verbose(`Ctrl-Q was started as a stand-alone binary: ${isPkg}`); + logger.verbose(`Ctrl-Q was started as a stand-alone binary: ${isSea}`); logger.verbose(`Ctrl-Q was started from ${execPath}`); logger.info('Create master dimension'); @@ -254,6 +254,4 @@ const createMasterDimension = async (options) => { } catch (err) { catchLog('Error creating master dimension', err); } -}; - -export default createMasterDimension; +} diff --git a/src/lib/cmd/qseow/createuseractivitycp.js b/src/lib/cmd/qseow/createuseractivitycp.js index 079394d..b1ab8a9 100644 --- a/src/lib/cmd/qseow/createuseractivitycp.js +++ b/src/lib/cmd/qseow/createuseractivitycp.js @@ -1,6 +1,6 @@ import axios from 'axios'; -import { logger, setLoggingLevel, isPkg, execPath, sleep } from '../../../globals.js'; +import { logger, setLoggingLevel, isSea, execPath, sleep } from '../../../globals.js'; import { setupQrsConnection } from '../../util/qseow/qrs.js'; import { catchLog } from '../../util/log.js'; import { @@ -68,7 +68,7 @@ export async function createUserActivityBucketsCustomProperty(options) { // Set log level setLoggingLevel(options.logLevel); - logger.verbose(`Ctrl-Q was started as a stand-alone binary: ${isPkg}`); + logger.verbose(`Ctrl-Q was started as a stand-alone binary: ${isSea}`); logger.verbose(`Ctrl-Q was started from ${execPath}`); logger.info('== Step 1: Create custom property for tracking user activity in QMC'); @@ -123,18 +123,30 @@ export async function createUserActivityBucketsCustomProperty(options) { // Update existing custom property // First copy existing custom property to a new object, then replace the choiceValues with the new ones const customPropertyDefinition = JSON.parse(JSON.stringify(customPropertyExisting)); + + // Add activity buckets to custom property definition as choice values customPropertyDefinition.choiceValues = activityBucketsSorted; - const result = await updateCustomProperty(options, customPropertyDefinition); - if (result) { - logger.verbose( - ` Updated existing custom property "${options.customPropertyName}" with new allowed values passed in via command line.` + // Add default bucket to choice values. This is the last one in the list + "+" sign + customPropertyDefinition.choiceValues.push(`${activityBucketsSorted[activityBucketsSorted.length - 1]}+`); + + // Is it a dry run? + if (options.dryRun) { + logger.info( + `(${importCount}/${importLimit}) Dry run: Would have updated custom property "${options.customPropertyName}" with activity info` ); } else { - logger.error( - `Failed to update existing custom property "${options.customPropertyName}" with new allowed values.` - ); - return false; + const result = await updateCustomProperty(options, customPropertyDefinition); + if (result) { + logger.verbose( + ` Updated existing custom property "${options.customPropertyName}" with new allowed values passed in via command line.` + ); + } else { + logger.error( + `Failed to update existing custom property "${options.customPropertyName}" with new allowed values.` + ); + return false; + } } } else { // Don't force overwrite the existing custom property. @@ -163,14 +175,23 @@ export async function createUserActivityBucketsCustomProperty(options) { const customPropertyDefinition = JSON.parse(JSON.stringify(customPropertyExisting)); customPropertyDefinition.choiceValues = activityBucketsSorted; - const result = await updateCustomProperty(options, customPropertyDefinition); - if (result) { - logger.verbose( - ` Updated existing custom property "${options.customPropertyName}" with new allowed values passed in via command line.` + // Is it a dry run? + if (options.dryRun) { + logger.info( + `(${importCount}/${importLimit}) Dry run: Would have updated custom property "${options.customPropertyName}" to have new activity bucket values.` ); } else { - logger.error(`Failed to update existing custom property "${options.customPropertyName}" with new allowed values.`); - return false; + const result = await updateCustomProperty(options, customPropertyDefinition); + if (result) { + logger.verbose( + ` Updated existing custom property "${options.customPropertyName}" with new allowed values passed in via command line.` + ); + } else { + logger.error( + `Failed to update existing custom property "${options.customPropertyName}" with new allowed values.` + ); + return false; + } } } } @@ -187,12 +208,17 @@ export async function createUserActivityBucketsCustomProperty(options) { choiceValues: activityBucketsSorted, }; - const result = await createCustomProperty(options, customPropertyDefinition); - if (result) { - logger.verbose(` Created custom property "${options.customPropertyName}"`); + // Is it a dry run? + if (options.dryRun) { + logger.info(`(${importCount}/${importLimit}) Dry run: Would have created custom property "${options.customPropertyName}"`); } else { - logger.error(`Failed to create custom property "${options.customPropertyName}"`); - return false; + const result = await createCustomProperty(options, customPropertyDefinition); + if (result) { + logger.verbose(` Created custom property "${options.customPropertyName}"`); + } else { + logger.error(`Failed to create custom property "${options.customPropertyName}"`); + return false; + } } } @@ -436,6 +462,11 @@ export async function createUserActivityBucketsCustomProperty(options) { break; } } + + // If user is not assigned to a bucket, assign to the default bucket (last one in the list + "+" sign) + if (!user.activityBucket) { + user.activityBucket = `${activityBucketsSorted[activityBucketsSorted.length - 1]}+`; + } } logger.verbose(` Assigned activity buckets to users via custom property ${options.customPropertyName}`); @@ -554,23 +585,32 @@ export async function createUserActivityBucketsCustomProperty(options) { // Loop over the users in the batch, writing the user activity custom property to QRS for (const user of usersBatch) { - // Payload: array of user objects - const axiosConfig = setupQrsConnection(options, { - method: 'put', - path: `qrs/user/${user.id}`, - body: user, - }); - - const result = await axios.request(axiosConfig); - if (result.status === 200) { + // Is it a dry run? + if (options.dryRun) { logger.info( - ` Updated user ${userCounter} of ${outputUserArray.length}, "${user.userDirectory}\\${user.userId}" in batch ${ + `(${importCount}/${importLimit}) Dry run: Would have updated ${userCounter} of ${outputUserArray.length}, "${user.userDirectory}\\${user.userId}" in batch ${ i + 1 } of ${totalBatches}` ); } else { - logger.error(`Error ${result.status} updating user activity custom property for batch ${i + 1} of ${totalBatches}`); - return false; + // Payload: array of user objects + const axiosConfig = setupQrsConnection(options, { + method: 'put', + path: `qrs/user/${user.id}`, + body: user, + }); + + const result = await axios.request(axiosConfig); + if (result.status === 200) { + logger.info( + ` Updated user ${userCounter} of ${outputUserArray.length}, "${user.userDirectory}\\${user.userId}" in batch ${ + i + 1 + } of ${totalBatches}` + ); + } else { + logger.error(`Error ${result.status} updating user activity custom property for batch ${i + 1} of ${totalBatches}`); + return false; + } } userCounter++; diff --git a/src/lib/cmd/qseow/deletedim.js b/src/lib/cmd/qseow/deletedim.js index f2d6f98..3ee687d 100644 --- a/src/lib/cmd/qseow/deletedim.js +++ b/src/lib/cmd/qseow/deletedim.js @@ -1,6 +1,6 @@ import enigma from 'enigma.js'; import { setupEnigmaConnection, addTrafficLogging } from '../../util/qseow/enigma_util.js'; -import { logger, setLoggingLevel, isPkg, execPath } from '../../../globals.js'; +import { logger, setLoggingLevel, isSea, execPath } from '../../../globals.js'; import { catchLog } from '../../util/log.js'; // Variable to keep track of how many dimensions have been deleted @@ -10,12 +10,12 @@ let deleteCount = 0; * * @param {*} options */ -const deleteMasterDimension = async (options) => { +export async function deleteMasterDimension(options) { try { // Set log level setLoggingLevel(options.logLevel); - logger.verbose(`Ctrl-Q was started as a stand-alone binary: ${isPkg}`); + logger.verbose(`Ctrl-Q was started as a stand-alone binary: ${isSea}`); logger.verbose(`Ctrl-Q was started from ${execPath}`); logger.info('Delete master dimensions'); @@ -154,6 +154,4 @@ const deleteMasterDimension = async (options) => { } catch (err) { catchLog('Error in deleteMasterDimension', err); } -}; - -export default deleteMasterDimension; +} diff --git a/src/lib/cmd/qseow/deletemeasure.js b/src/lib/cmd/qseow/deletemeasure.js index d7e6991..f7a7770 100644 --- a/src/lib/cmd/qseow/deletemeasure.js +++ b/src/lib/cmd/qseow/deletemeasure.js @@ -1,6 +1,6 @@ import enigma from 'enigma.js'; import { setupEnigmaConnection, addTrafficLogging } from '../../util/qseow/enigma_util.js'; -import { logger, setLoggingLevel, isPkg, execPath } from '../../../globals.js'; +import { logger, setLoggingLevel, isSea, execPath } from '../../../globals.js'; import { catchLog } from '../../util/log.js'; // Variable to keep track of how many measures have been deleted @@ -10,12 +10,12 @@ let deleteCount = 0; * * @param {*} options */ -const deleteMasterMeasure = async (options) => { +export async function deleteMasterMeasure(options) { try { // Set log level setLoggingLevel(options.logLevel); - logger.verbose(`Ctrl-Q was started as a stand-alone binary: ${isPkg}`); + logger.verbose(`Ctrl-Q was started as a stand-alone binary: ${isSea}`); logger.verbose(`Ctrl-Q was started from ${execPath}`); logger.info('Delete master measures'); @@ -85,7 +85,6 @@ const deleteMasterMeasure = async (options) => { deleteMasterItems = deleteMasterItems.concat(measureObj.qMeasureList.qItems); } else { // Loop over all master items (identified by name or ID) we should get data for - // eslint-disable-next-line no-restricted-syntax for (const masterItem of options.masterItem) { // Can we find this master item in the list retrieved from the app? if (options.idType === 'name') { @@ -115,10 +114,8 @@ const deleteMasterMeasure = async (options) => { if (deleteMasterItems.length === 0) { logger.warn(`No matching master item measures found`); } else { - // eslint-disable-next-line no-restricted-syntax for (const item of deleteMasterItems) { if (options.dryRun === undefined || options.dryRun === false) { - // eslint-disable-next-line no-await-in-loop const res = await app.destroyMeasure(item.qInfo.qId); if (res !== true) { logger.error(`Failed deleting measure "${item.qMeta.title}", id=${item.qInfo.qId} in app "${item.qInfo.qId}"`); @@ -148,6 +145,4 @@ const deleteMasterMeasure = async (options) => { } catch (err) { catchLog('Error in deleteMasterMeasure', err); } -}; - -export default deleteMasterMeasure; +} diff --git a/src/lib/cmd/qseow/deletesessions.js b/src/lib/cmd/qseow/deletesessions.js index 417d76b..83c0c8d 100644 --- a/src/lib/cmd/qseow/deletesessions.js +++ b/src/lib/cmd/qseow/deletesessions.js @@ -1,17 +1,17 @@ import { deleteSessionsFromQSEoWIds } from '../../util/qseow/session.js'; -import { logger, setLoggingLevel, isPkg, execPath } from '../../../globals.js'; +import { logger, setLoggingLevel, isSea, execPath } from '../../../globals.js'; import { catchLog } from '../../util/log.js'; /** * Delete Qlik Sense proxy sessions * @param {object} options - Options object */ -const deleteSessions = async (options) => { +export async function deleteSessions(options) { try { // Set log level setLoggingLevel(options.logLevel); - logger.verbose(`Ctrl-Q was started as a stand-alone binary: ${isPkg}`); + logger.verbose(`Ctrl-Q was started as a stand-alone binary: ${isSea}`); logger.verbose(`Ctrl-Q was started from ${execPath}`); logger.info('Delete Qlik Sense proxy sessions'); @@ -31,6 +31,4 @@ const deleteSessions = async (options) => { return false; } -}; - -export default deleteSessions; +} diff --git a/src/lib/cmd/qseow/deletevariable.js b/src/lib/cmd/qseow/deletevariable.js index d5f1f78..6611539 100644 --- a/src/lib/cmd/qseow/deletevariable.js +++ b/src/lib/cmd/qseow/deletevariable.js @@ -2,19 +2,19 @@ import enigma from 'enigma.js'; import { setupEnigmaConnection, addTrafficLogging } from '../../util/qseow/enigma_util.js'; import { getApps } from '../../util/qseow/app.js'; -import { logger, setLoggingLevel, isPkg, execPath } from '../../../globals.js'; +import { logger, setLoggingLevel, isSea, execPath } from '../../../globals.js'; import { catchLog } from '../../util/log.js'; /** * * @param {*} options */ -const deleteVariable = async (options) => { +export async function deleteVariable(options) { try { // Set log level setLoggingLevel(options.logLevel); - logger.verbose(`Ctrl-Q was started as a stand-alone binary: ${isPkg}`); + logger.verbose(`Ctrl-Q was started as a stand-alone binary: ${isSea}`); logger.verbose(`Ctrl-Q was started from ${execPath}`); logger.debug(`Options: ${JSON.stringify(options, null, 2)}`); @@ -183,5 +183,3 @@ const deleteVariable = async (options) => { catchLog('Error in deleteVariable', err); } }; - -export default deleteVariable; diff --git a/src/lib/cmd/qseow/exportapp.js b/src/lib/cmd/qseow/exportapp.js index 8a13fc9..bf41ab6 100644 --- a/src/lib/cmd/qseow/exportapp.js +++ b/src/lib/cmd/qseow/exportapp.js @@ -3,16 +3,16 @@ import fs from 'node:fs'; import path from 'node:path'; import yesno from 'yesno'; -import { logger, setLoggingLevel, isPkg, execPath, mergeDirFilePath, verifyFileExists, isNumeric, sleep } from '../../../globals.js'; -import QlikSenseApps from '../../app/class_allapps.js'; +import { logger, setLoggingLevel, isSea, execPath, mergeDirFilePath, verifyFileSystemExists, sleep } from '../../../globals.js'; +import { QlikSenseApps } from '../../app/class_allapps.js'; import { catchLog } from '../../util/log.js'; -const exportAppToFile = async (options) => { +export async function exportAppToFile(options) { try { // Set log level setLoggingLevel(options.logLevel); - logger.verbose(`Ctrl-Q was started as a stand-alone binary: ${isPkg}`); + logger.verbose(`Ctrl-Q was started as a stand-alone binary: ${isSea}`); logger.verbose(`Ctrl-Q was started from ${execPath}`); // Verify output directory exists. Create if not. @@ -23,23 +23,9 @@ const exportAppToFile = async (options) => { await fs.promises.mkdir(outputDir); } - // const appFileExists = await verifyFileExists(options.fileName); - // if (appFileExists === false) { - // logger.error(`Missing apps definition file "${options.fileName}". Aborting`); - // process.exit(1); - // } else { - // logger.verbose(`Apps definition file "${options.fileName}" found`); - // } - logger.info(`Export apps to directory "${outputDir}"`); logger.debug(`Options: ${JSON.stringify(options, null, 2)}`); - // Get all tags - // const tagsExisting = await getTagsFromQseow(options); - - // Get all custom properties - // const cpExisting = await getCustomPropertiesFromQseow(options); - // Set up app structure const qlikSenseApps = new QlikSenseApps(); await qlikSenseApps.init(options); @@ -73,39 +59,39 @@ const exportAppToFile = async (options) => { logger.info(`Number of apps to export: ${appsToExport.length}`); let exportCount = 0; - // eslint-disable-next-line no-restricted-syntax for (const app of appsToExport) { - // eslint-disable-next-line no-await-in-loop - const exportAppData = await qlikSenseApps.exportAppStep1(app); - - // eslint-disable-next-line no-await-in-loop - const resultDownloadApp = await qlikSenseApps.exportAppStep2(exportAppData); - - // eslint-disable-next-line no-await-in-loop - await sleep(options.sleepAppExport); - - // keep track of app metadata - appCounter += 1; - appMetadata.push([ - appCounter, - app.name, - app.id, - options.outputDir, - resultDownloadApp.qvfFileName, - options.excludeAppData, - app.tags.map((item) => item.name).join(' / '), - app.customProperties.map((item) => `${item.definition.name}=${item.value}`).join(' / '), - app.owner.userDirectory, - app.owner.userId, - app.stream ? app.stream.name : '', - ]); - - exportCount += 1; - if (exportCount === parseInt(options.limitExportCount, 10)) { - logger.warn( - `Exported ${options.limitExportCount} app(s), which is the limit set by the --limit-export-count parameter.` - ); - break; + try { + appCounter += 1; + const exportAppData = await qlikSenseApps.exportAppStep1(app); + + const resultDownloadApp = await qlikSenseApps.exportAppStep2(exportAppData, appCounter, appsToExport.length); + + await sleep(options.sleepAppExport); + + // keep track of app metadata + appMetadata.push([ + appCounter, + app.name, + app.id, + options.outputDir, + resultDownloadApp.qvfFileName, + options.excludeAppData, + app.tags.map((item) => item.name).join(' / '), + app.customProperties.map((item) => `${item.definition.name}=${item.value}`).join(' / '), + app.owner.userDirectory, + app.owner.userId, + app.stream ? app.stream.name : '', + ]); + + exportCount += 1; + if (exportCount === parseInt(options.limitExportCount, 10)) { + logger.warn( + `Exported ${options.limitExportCount} app(s), which is the limit set by the --limit-export-count parameter.` + ); + break; + } + } catch (error) { + logger.error(`Failed to export app ${app.name} (${app.id}): ${error.message}`); } } @@ -121,7 +107,7 @@ const exportAppToFile = async (options) => { // Check if app metadata file already exists // 2nd parameter = true => don't output anything to log - const fileExists = await verifyFileExists(fileName, true); + const fileExists = await verifyFileSystemExists(fileName, true); logger.info('------------------------------------'); @@ -155,6 +141,4 @@ const exportAppToFile = async (options) => { } catch (err) { catchLog('Export app', err); } -}; - -export default exportAppToFile; +} diff --git a/src/lib/cmd/qseow/getbookmark.js b/src/lib/cmd/qseow/getbookmark.js index 3a43199..36836f3 100644 --- a/src/lib/cmd/qseow/getbookmark.js +++ b/src/lib/cmd/qseow/getbookmark.js @@ -1,7 +1,7 @@ import enigma from 'enigma.js'; import { table } from 'table'; import { setupEnigmaConnection, addTrafficLogging } from '../../util/qseow/enigma_util.js'; -import { logger, setLoggingLevel, isPkg, execPath } from '../../../globals.js'; +import { logger, setLoggingLevel, isSea, execPath } from '../../../globals.js'; import { catchLog } from '../../util/log.js'; const consoleTableConfig = { @@ -37,14 +37,14 @@ const consoleTableConfig = { * * @param {*} options */ -const getBookmark = async (options) => { +export async function getBookmark(options) { let session; try { // Set log level setLoggingLevel(options.logLevel); - logger.verbose(`Ctrl-Q was started as a stand-alone binary: ${isPkg}`); + logger.verbose(`Ctrl-Q was started as a stand-alone binary: ${isSea}`); logger.verbose(`Ctrl-Q was started from ${execPath}`); logger.info('Get bookmarks'); @@ -217,6 +217,4 @@ const getBookmark = async (options) => { } return false; } -}; - -export default getBookmark; +} diff --git a/src/lib/cmd/qseow/getdim.js b/src/lib/cmd/qseow/getdim.js index 8f8e7f4..f7b2bed 100644 --- a/src/lib/cmd/qseow/getdim.js +++ b/src/lib/cmd/qseow/getdim.js @@ -2,7 +2,7 @@ import enigma from 'enigma.js'; import { table } from 'table'; import { setupEnigmaConnection, addTrafficLogging } from '../../util/qseow/enigma_util.js'; -import { logger, setLoggingLevel, isPkg, execPath } from '../../../globals.js'; +import { logger, setLoggingLevel, isSea, execPath } from '../../../globals.js'; import { catchLog } from '../../util/log.js'; const consoleTableConfig = { @@ -39,12 +39,12 @@ const consoleTableConfig = { * * @param {*} options */ -const getMasterDimension = async (options) => { +export async function getMasterDimension(options) { try { // Set log level setLoggingLevel(options.logLevel); - logger.verbose(`Ctrl-Q was started as a stand-alone binary: ${isPkg}`); + logger.verbose(`Ctrl-Q was started as a stand-alone binary: ${isSea}`); logger.verbose(`Ctrl-Q was started from ${execPath}`); logger.info('Get master dimensions'); @@ -267,6 +267,4 @@ const getMasterDimension = async (options) => { } catch (err) { catchLog(`Error getting master dimensions`, err); } -}; - -export default getMasterDimension; +} diff --git a/src/lib/cmd/qseow/getmeasure.js b/src/lib/cmd/qseow/getmeasure.js index 589f733..8724f6d 100644 --- a/src/lib/cmd/qseow/getmeasure.js +++ b/src/lib/cmd/qseow/getmeasure.js @@ -2,7 +2,7 @@ import enigma from 'enigma.js'; import { table } from 'table'; import { setupEnigmaConnection, addTrafficLogging } from '../../util/qseow/enigma_util.js'; -import { logger, setLoggingLevel, isPkg, execPath } from '../../../globals.js'; +import { logger, setLoggingLevel, isSea, execPath } from '../../../globals.js'; import { catchLog } from '../../util/log.js'; const consoleTableConfig = { @@ -38,12 +38,12 @@ const consoleTableConfig = { * * @param {*} options */ -const getMasterMeasure = async (options) => { +export async function getMasterMeasure(options) { try { // Set log level setLoggingLevel(options.logLevel); - logger.verbose(`Ctrl-Q was started as a stand-alone binary: ${isPkg}`); + logger.verbose(`Ctrl-Q was started as a stand-alone binary: ${isSea}`); logger.verbose(`Ctrl-Q was started from ${execPath}`); logger.info('Get master measures'); @@ -242,6 +242,4 @@ const getMasterMeasure = async (options) => { } catch (err) { catchLog('Error in getMasterMeasure', err); } -}; - -export default getMasterMeasure; +} diff --git a/src/lib/cmd/qseow/getscript.js b/src/lib/cmd/qseow/getscript.js index 34f4822..111712b 100644 --- a/src/lib/cmd/qseow/getscript.js +++ b/src/lib/cmd/qseow/getscript.js @@ -1,6 +1,6 @@ import enigma from 'enigma.js'; -import { logger, setLoggingLevel, isPkg, execPath } from '../../../globals.js'; +import { logger, setLoggingLevel, isSea, execPath } from '../../../globals.js'; import { setupEnigmaConnection, addTrafficLogging } from '../../util/qseow/enigma_util.js'; import { catchLog } from '../../util/log.js'; @@ -8,12 +8,12 @@ import { catchLog } from '../../util/log.js'; * * @param {*} options */ -const getScript = async (options) => { +export async function getScript(options) { try { // Set log level setLoggingLevel(options.logLevel); - logger.verbose(`Ctrl-Q was started as a stand-alone binary: ${isPkg}`); + logger.verbose(`Ctrl-Q was started as a stand-alone binary: ${isSea}`); logger.verbose(`Ctrl-Q was started from ${execPath}`); logger.verbose('Get app script'); @@ -92,6 +92,4 @@ const getScript = async (options) => { } catch (err) { catchLog('Error in getScript', err); } -}; - -export default getScript; +} diff --git a/src/lib/cmd/qseow/getsessions.js b/src/lib/cmd/qseow/getsessions.js index f0b4faf..d41e5e5 100644 --- a/src/lib/cmd/qseow/getsessions.js +++ b/src/lib/cmd/qseow/getsessions.js @@ -1,6 +1,6 @@ import { table } from 'table'; import { getSessionsFromQseow } from '../../util/qseow/session.js'; -import { logger, setLoggingLevel, isPkg, execPath } from '../../../globals.js'; +import { logger, setLoggingLevel, isSea, execPath } from '../../../globals.js'; import { catchLog } from '../../util/log.js'; const consoleTableConfig = { @@ -36,12 +36,12 @@ const consoleTableConfig = { * * @param {*} options */ -const getSessions = async (options) => { +export async function getSessions(options) { try { // Set log level setLoggingLevel(options.logLevel); - logger.verbose(`Ctrl-Q was started as a stand-alone binary: ${isPkg}`); + logger.verbose(`Ctrl-Q was started as a stand-alone binary: ${isSea}`); logger.verbose(`Ctrl-Q was started from ${execPath}`); logger.info('Get Qlik Sense proxy sessions'); @@ -160,7 +160,6 @@ const getSessions = async (options) => { } // Add to table that will be printed to console - // eslint-disable-next-line no-restricted-syntax for (const s of sessionsTabular) { sessionsTable.push([ s.vpDescription, @@ -188,6 +187,4 @@ const getSessions = async (options) => { return false; } -}; - -export default getSessions; +} diff --git a/src/lib/cmd/qseow/gettask.js b/src/lib/cmd/qseow/gettask.js index 4869910..0617001 100644 --- a/src/lib/cmd/qseow/gettask.js +++ b/src/lib/cmd/qseow/gettask.js @@ -4,8 +4,8 @@ import { promises as Fs } from 'node:fs'; import xlsx from 'node-xlsx'; import { stringify } from 'csv-stringify'; import yesno from 'yesno'; -import { logger, setLoggingLevel, isPkg, execPath, verifyFileExists } from '../../../globals.js'; -import QlikSenseTasks from '../../task/class_alltasks.js'; +import { logger, setLoggingLevel, isSea, execPath, verifyFileSystemExists } from '../../../globals.js'; +import { QlikSenseTasks } from '../../task/class_alltasks.js'; import { mapEventType, mapIncrementOption, mapDaylightSavingTime, mapRuleState } from '../../util/qseow/lookups.js'; import { getTagsFromQseow } from '../../util/qseow/tag.js'; import { catchLog } from '../../util/log.js'; @@ -85,12 +85,12 @@ function compareTable(a, b) { // get-task command // Options are assumed to be verified before calling this function -const getTask = async (options) => { +export async function getTask(options) { try { // Set log level setLoggingLevel(options.logLevel); - logger.verbose(`Ctrl-Q was started as a stand-alone binary: ${isPkg}`); + logger.verbose(`Ctrl-Q was started as a stand-alone binary: ${isSea}`); logger.verbose(`Ctrl-Q was started from ${execPath}`); logger.verbose('Get tasks'); @@ -257,7 +257,7 @@ const getTask = async (options) => { } // Check if file exists - if ((await verifyFileExists(options.outputFileName, true)) === false) { + if ((await verifyFileSystemExists(options.outputFileName, true)) === false) { // File doesn't exist } else if (!options.outputFileOverwrite) { // Target file exist. Ask if user wants to overwrite @@ -746,7 +746,7 @@ const getTask = async (options) => { } // Check if file exists - if ((await verifyFileExists(options.outputFileName, true)) === false) { + if ((await verifyFileSystemExists(options.outputFileName, true)) === false) { // File doesn't exist } else if (!options.outputFileOverwrite) { // Target file exist. Ask if user wants to overwrite @@ -773,6 +773,4 @@ const getTask = async (options) => { catchLog('Get task', err); return false; } -}; - -export default getTask; +} diff --git a/src/lib/cmd/qseow/getvariable.js b/src/lib/cmd/qseow/getvariable.js index 3ca271b..4ae9c61 100644 --- a/src/lib/cmd/qseow/getvariable.js +++ b/src/lib/cmd/qseow/getvariable.js @@ -3,7 +3,7 @@ import enigma from 'enigma.js'; import { table } from 'table'; import { setupEnigmaConnection, addTrafficLogging } from '../../util/qseow/enigma_util.js'; import { getApps } from '../../util/qseow/app.js'; -import { logger, setLoggingLevel, isPkg, execPath } from '../../../globals.js'; +import { logger, setLoggingLevel, isSea, execPath } from '../../../globals.js'; import { catchLog } from '../../util/log.js'; const consoleTableConfig = { @@ -39,12 +39,12 @@ const consoleTableConfig = { * * @param {*} options */ -const getVariable = async (options) => { +export async function getVariable(options) { try { // Set log level setLoggingLevel(options.logLevel); - logger.verbose(`Ctrl-Q was started as a stand-alone binary: ${isPkg}`); + logger.verbose(`Ctrl-Q was started as a stand-alone binary: ${isSea}`); logger.verbose(`Ctrl-Q was started from ${execPath}`); logger.debug(`Options: ${JSON.stringify(options, null, 2)}`); @@ -279,6 +279,4 @@ const getVariable = async (options) => { } catch (err) { catchLog(`Error in getVariable`, err); } -}; - -export default getVariable; +} diff --git a/src/lib/cmd/qseow/import-masteritem-excel.js b/src/lib/cmd/qseow/import-masteritem-excel.js index 95c1f75..cf65f98 100644 --- a/src/lib/cmd/qseow/import-masteritem-excel.js +++ b/src/lib/cmd/qseow/import-masteritem-excel.js @@ -2,7 +2,7 @@ import enigma from 'enigma.js'; import xlsx from 'node-xlsx'; import { v4 as uuidCreate } from 'uuid'; -import { logger, setLoggingLevel, isPkg, execPath, verifyFileExists, sleep } from '../../../globals.js'; +import { logger, setLoggingLevel, isSea, execPath, verifyFileSystemExists, sleep } from '../../../globals.js'; import { setupEnigmaConnection, addTrafficLogging } from '../../util/qseow/enigma_util.js'; import { catchLog } from '../../util/log.js'; @@ -205,7 +205,7 @@ const updateDimension = async ( [existingColorMapModel] = await Promise.all([existingColorMapPromise]); existingColorMapLayout = await existingColorMapModel.getLayout(); } catch (err) { - catchLog(`No per-value color map exists for existing dimension "${existingDimensionLayout.qMeta.title}"`, err); + logger.verbose(`No per-value color map exists for existing dimension "${existingDimensionLayout.qMeta.title}"`); } // Do we have new per-value color data? @@ -960,14 +960,14 @@ const importMasterItemFromExcel = async (options) => { // Set log level setLoggingLevel(options.logLevel); - logger.verbose(`Ctrl-Q was started as a stand-alone binary: ${isPkg}`); + logger.verbose(`Ctrl-Q was started as a stand-alone binary: ${isSea}`); logger.verbose(`Ctrl-Q was started from ${execPath}`); logger.info(`Import master items from definitions in Excel file "${options.file}"`); logger.debug(`Options: ${JSON.stringify(options, null, 2)}`); // Verify Master items Excel file exists - const excelFileExists = await verifyFileExists(options.file); + const excelFileExists = await verifyFileSystemExists(options.file); if (excelFileExists === false) { logger.error(`Missing master item Excel file ${options.file}. Aborting`); process.exit(1); @@ -1141,11 +1141,9 @@ const importMasterItemFromExcel = async (options) => { } }; -const importMasterItemFromFile = async (options) => { +export async function importMasterItemFromFile(options) { if (options.fileType === 'excel') { // Source file type is Excel await importMasterItemFromExcel(options); } -}; - -export default importMasterItemFromFile; +} diff --git a/src/lib/cmd/qseow/importapp.js b/src/lib/cmd/qseow/importapp.js index 9ff5830..bea1143 100644 --- a/src/lib/cmd/qseow/importapp.js +++ b/src/lib/cmd/qseow/importapp.js @@ -1,18 +1,18 @@ import xlsx from 'node-xlsx'; -import { logger, setLoggingLevel, isPkg, execPath, verifyFileExists, isNumeric } from '../../../globals.js'; -import QlikSenseApps from '../../app/class_allapps.js'; +import { logger, setLoggingLevel, isSea, execPath, verifyFileSystemExists } from '../../../globals.js'; +import { QlikSenseApps } from '../../app/class_allapps.js'; import { getAppColumnPosFromHeaderRow } from '../../util/qseow/lookups.js'; import { getTagsFromQseow } from '../../util/qseow/tag.js'; import { getCustomPropertiesFromQseow } from '../../util/qseow/customproperties.js'; import { catchLog } from '../../util/log.js'; -const importAppFromFile = async (options) => { +export async function importAppFromFile(options) { try { // Set log level setLoggingLevel(options.logLevel); - logger.verbose(`Ctrl-Q was started as a stand-alone binary: ${isPkg}`); + logger.verbose(`Ctrl-Q was started as a stand-alone binary: ${isSea}`); logger.verbose(`Ctrl-Q was started from ${execPath}`); logger.info(`Import apps from definitions in file "${options.fileName}"`); @@ -26,7 +26,7 @@ const importAppFromFile = async (options) => { logger.info(`Successfully retrieved ${cpExisting.length} custom properties from QSEoW`); // Verify file exists - const appFileExists = await verifyFileExists(options.fileName); + const appFileExists = await verifyFileSystemExists(options.fileName); if (appFileExists === false) { logger.error(`Missing apps definition file "${options.fileName}". Aborting`); process.exit(1); @@ -85,6 +85,4 @@ const importAppFromFile = async (options) => { catchLog('IMPORT APP', err); return false; } -}; - -export default importAppFromFile; +} diff --git a/src/lib/cmd/qseow/importtask.js b/src/lib/cmd/qseow/importtask.js index ff047cd..3fc288e 100644 --- a/src/lib/cmd/qseow/importtask.js +++ b/src/lib/cmd/qseow/importtask.js @@ -2,9 +2,9 @@ import xlsx from 'node-xlsx'; import { parse } from 'csv-parse'; import fs from 'node:fs'; -import { logger, setLoggingLevel, isPkg, execPath, verifyFileExists, isNumeric } from '../../../globals.js'; -import QlikSenseTasks from '../../task/class_alltasks.js'; -import QlikSenseApps from '../../app/class_allapps.js'; +import { logger, setLoggingLevel, isSea, execPath, verifyFileSystemExists, isNumeric } from '../../../globals.js'; +import { QlikSenseTasks } from '../../task/class_alltasks.js'; +import { QlikSenseApps } from '../../app/class_allapps.js'; import { getTaskColumnPosFromHeaderRow } from '../../util/qseow/lookups.js'; import { getTagsFromQseow } from '../../util/qseow/tag.js'; import { getCustomPropertiesFromQseow } from '../../util/qseow/customproperties.js'; @@ -299,12 +299,12 @@ const processCsvFile = async (options) => { return records; }; -const importTaskFromFile = async (options) => { +export async function importTaskFromFile(options) { try { // Set log level setLoggingLevel(options.logLevel); - logger.verbose(`Ctrl-Q was started as a stand-alone binary: ${isPkg}`); + logger.verbose(`Ctrl-Q was started as a stand-alone binary: ${isSea}`); logger.verbose(`Ctrl-Q was started from ${execPath}`); logger.info(`Import tasks from definitions in file "${options.fileName}"`); @@ -318,7 +318,7 @@ const importTaskFromFile = async (options) => { logger.info(`Successfully retrieved ${cpExisting.length} custom properties from QSEoW`); // Verify task definitions file exists - const taskFileExists = await verifyFileExists(options.fileName); + const taskFileExists = await verifyFileSystemExists(options.fileName); if (taskFileExists === false) { logger.error(`Missing task file "${options.fileName}". Aborting`); process.exit(1); @@ -415,6 +415,4 @@ const importTaskFromFile = async (options) => { } catch (err) { catchLog('IMPORT TASK 2', err); } -}; - -export default importTaskFromFile; +} diff --git a/src/lib/cmd/qseow/scramblefield.js b/src/lib/cmd/qseow/scramblefield.js index babeff2..d1bb6e0 100644 --- a/src/lib/cmd/qseow/scramblefield.js +++ b/src/lib/cmd/qseow/scramblefield.js @@ -1,18 +1,18 @@ import enigma from 'enigma.js'; import { setupEnigmaConnection, addTrafficLogging } from '../../util/qseow/enigma_util.js'; -import { logger, setLoggingLevel, isPkg, execPath } from '../../../globals.js'; +import { logger, setLoggingLevel, isSea, execPath } from '../../../globals.js'; import { catchLog } from '../../util/log.js'; /** * * @param {*} options */ -const scrambleField = async (options) => { +export async function scrambleField(options) { try { // Set log level setLoggingLevel(options.logLevel); - logger.verbose(`Ctrl-Q was started as a stand-alone binary: ${isPkg}`); + logger.verbose(`Ctrl-Q was started as a stand-alone binary: ${isSea}`); logger.verbose(`Ctrl-Q was started from ${execPath}`); logger.info('Scramble field'); @@ -89,6 +89,4 @@ const scrambleField = async (options) => { } catch (err) { catchLog('Error in scrambleField', err); } -}; - -export default scrambleField; +} diff --git a/src/lib/cmd/qseow/settaskcp.js b/src/lib/cmd/qseow/settaskcp.js index ea3a3fe..ad50e35 100644 --- a/src/lib/cmd/qseow/settaskcp.js +++ b/src/lib/cmd/qseow/settaskcp.js @@ -138,7 +138,7 @@ const updateTask = async (options, customPropertyDef, task) => } }); -const setTaskCustomProperty = async (options) => { +export async function setTaskCustomProperty(options) { try { // == Meta code == // - Get definition for the custom property that should be updated @@ -201,6 +201,4 @@ const setTaskCustomProperty = async (options) => { catchLog(`SET RELOAD TASK CP`, err); return false; } -}; - -export default setTaskCustomProperty; +} diff --git a/src/lib/cmd/qseow/testconnection.js b/src/lib/cmd/qseow/testconnection.js index 21b0ddb..403c91f 100644 --- a/src/lib/cmd/qseow/testconnection.js +++ b/src/lib/cmd/qseow/testconnection.js @@ -1,13 +1,13 @@ -import { logger, setLoggingLevel, isPkg, execPath } from '../../../globals.js'; -import getAboutFromQseow from '../../util/qseow/about.js'; +import { logger, setLoggingLevel, isSea, execPath } from '../../../globals.js'; +import { getAboutFromQseow } from '../../util/qseow/about.js'; import { catchLog } from '../../util/log.js'; -const testConnection = async (options) => { +export async function testConnection(options) { try { // Set log level setLoggingLevel(options.logLevel); - logger.verbose(`Ctrl-Q was started as a stand-alone binary: ${isPkg}`); + logger.verbose(`Ctrl-Q was started as a stand-alone binary: ${isSea}`); logger.verbose(`Ctrl-Q was started from ${execPath}`); logger.info(`Testing connection to Qlik Sense server ${options.host} on port ${options.port}`); @@ -30,6 +30,4 @@ const testConnection = async (options) => { logger.error(`EXPORT APP: ${err.stack}`); return false; } -}; - -export default testConnection; +} diff --git a/src/lib/cmd/qseow/vistask.js b/src/lib/cmd/qseow/vistask.js index d11b58c..a13f0bb 100644 --- a/src/lib/cmd/qseow/vistask.js +++ b/src/lib/cmd/qseow/vistask.js @@ -3,8 +3,10 @@ import path from 'node:path'; import fs from 'node:fs'; import handlebars from 'handlebars'; import { Readable } from 'node:stream'; -import { appVersion, logger, setLoggingLevel, isPkg, execPath, verifyFileExists } from '../../../globals.js'; -import QlikSenseTasks from '../../task/class_alltasks.js'; +import sea from 'node:sea'; + +import { appVersion, logger, setLoggingLevel, isSea, execPath, verifyFileSystemExists, verifySeaAssetExists } from '../../../globals.js'; +import { QlikSenseTasks } from '../../task/class_alltasks.js'; // js: 'application/javascript', const MIME_TYPES = { @@ -20,12 +22,11 @@ const MIME_TYPES = { // Get path to static html files let STATIC_PATH = ''; -if (isPkg) { - // Running as standalone app - STATIC_PATH = path.join(__dirname, 'src/static'); - // STATIC_PATH = path.resolve(`${__dirname}/../../static`); +if (isSea) { + // Running as standalone SEA app + STATIC_PATH = ''; } else { - // Running packaged app + // Running Node.js script STATIC_PATH = path.join(execPath, './src/static'); } @@ -152,39 +153,87 @@ function getSchemaText(incrementOption, incrementDescription) { } const prepareFile = async (url) => { + logger.verbose('-----------------------------------'); + logger.verbose(`==> Preparing file for url: ${url}`); + const paths = [STATIC_PATH, url]; if (url.endsWith('/')) paths.push('index.html'); - const filePath = path.join(...paths); + logger.verbose(`Paths: ${paths}`); + + let filePath = path.join(...paths); logger.verbose(`Serving file ${filePath}`); const pathTraversal = !filePath.startsWith(STATIC_PATH); logger.verbose(`Path traversal: ${pathTraversal}`); - // Verify file filePath exists. Do not use fs.access() as it does not work with pkg. - // let exists = false; - // try { - // await fs.promises.stat(filePath); - // exists = true; - // } catch (error) { - // logger.verbose(`File does not exist: ${filePath}`); - // } - // const exists = await fs.promises.access(filePath).then(...toBool); - const exists = await verifyFileExists(filePath); - logger.verbose(`File exists: ${exists}`); - - const found = !pathTraversal && exists; - logger.verbose(`File found: ${found}`); - - const streamPath = found ? filePath : `${STATIC_PATH}/404.html`; - logger.verbose(`Stream path: ${streamPath}`); + let exists, streamPath, ext, stream; + + if (isSea) { + // Prepend with STATIC_PATH + logger.verbose(`url: ${url}`); + logger.verbose(`STATIC_PATH: ${STATIC_PATH}`); + logger.verbose(`filePath: ${filePath}`); + try { + exists = verifySeaAssetExists(filePath); + logger.verbose(`SEA file exists: ${exists}`); + if (exists) { + ext = path.extname(filePath).substring(1).toLowerCase(); + logger.verbose(`SEA file extension: ${ext}`); + + if (ext === 'html' || ext === 'js' || ext === 'css') { + const asset = sea.getAsset(filePath, 'utf8'); + logger.verbose(`Asset type: ${typeof asset}`); + + stream = Readable.from([asset]); + } else if (ext === 'png' || ext === 'jpg' || ext === 'gif' || ext === 'ico') { + let asset = sea.getAsset(filePath); + + if (asset instanceof ArrayBuffer) { + asset = Buffer.from(asset); + } + stream = Readable.from([asset]); + } else { + logger.warn(`File extension not supported: ${ext}`); + exists = false; + } + + streamPath = filePath; + } else { + logger.error(`Asset not found: ${filePath}`); + throw new Error('Asset not found'); + } + } catch (error) { + logger.error(`Error while getting SEA asset: ${error}`); + exists = false; + streamPath = `${STATIC_PATH}/404.html`; + ext = 'html'; + stream = Readable.from(['

404 Not Found in SEA app

']); + } + } else { + exists = await verifyFileSystemExists(filePath); + logger.verbose(`File system file exists: ${exists}`); + + streamPath = exists ? filePath : `${STATIC_PATH}/404.html`; + ext = path.extname(streamPath).substring(1).toLowerCase(); + stream = fs.createReadStream(streamPath); + } - const ext = path.extname(streamPath).substring(1).toLowerCase(); + logger.verbose(`File exists (2): ${exists}`); + logger.verbose(`File found: ${exists && !pathTraversal}`); + logger.verbose(`Stream path: ${streamPath}`); logger.verbose(`File extension: ${ext}`); - let stream; if (ext === 'html') { - const file = await fs.promises.readFile(streamPath, 'utf8'); + logger.verbose(`Serving html file ${streamPath}`); + let file; + + if (!isSea) { + file = await fs.promises.readFile(streamPath, 'utf8'); + } else if (isSea) { + file = sea.getAsset(streamPath, 'utf8'); + } + const template = handlebars.compile(file, { noEscape: true }); // Get task network model @@ -289,18 +338,61 @@ const prepareFile = async (url) => { const result = template(templateData); stream = Readable.from([result]); - } else { - stream = fs.createReadStream(streamPath); + } else if (ext === 'js' || ext === 'css' || ext === 'svg') { + logger.verbose(`Serving js, css or svg file ${streamPath}`); + + let asset; + if (!isSea) { + asset = await fs.promises.readFile(streamPath, 'utf8'); + } else if (isSea) { + asset = sea.getAsset(streamPath, 'utf8'); + } + + stream = Readable.from([asset]); + } else if (ext === 'png' || ext === 'jpg' || ext === 'gif' || ext === 'ico') { + logger.verbose(`Serving image file ${streamPath}`); + + let asset; + if (!isSea) { + asset = await fs.promises.readFile(streamPath); + } else if (isSea) { + asset = sea.getAsset(streamPath); + } + + if (asset instanceof ArrayBuffer) { + asset = Buffer.from(asset); + + // Show new type of asset + if (asset instanceof Uint8Array) { + logger.verbose('asset is Uint8Array'); + } else if (asset instanceof ArrayBuffer) { + logger.verbose('asset is ArrayBuffer'); + } else if (asset instanceof Buffer) { + logger.verbose('asset is Buffer'); + } + } + + stream = Readable.from([asset]); } - return { found, ext, stream }; + return { found: exists && !pathTraversal, ext, stream }; }; // Request handler for http server const requestHandler = async (req, res) => { const file = await prepareFile(req.url); + + // console.log('File:'); + // console.log(file); + + logger.verbose(`File found: ${file.found}`); + logger.verbose(`File extension: ${file.ext}`); + const statusCode = file.found ? 200 : 404; const mimeType = MIME_TYPES[file.ext] || MIME_TYPES.default; + + logger.verbose(`Serving file ${req.url} with status code ${statusCode} and mime type ${mimeType}`); + res.writeHead(statusCode, { 'Content-Type': mimeType }); file.stream.pipe(res); @@ -323,42 +415,63 @@ const startHttpServer = async (options) => { }); }; -const visTask = async (options) => { +export async function visTask(options) { // Set log level setLoggingLevel(options.logLevel); - logger.verbose(`Ctrl-Q was started as a stand-alone binary: ${isPkg}`); + logger.verbose(`Ctrl-Q was started as a stand-alone binary: ${isSea}`); logger.verbose(`Ctrl-Q was started from ${execPath}`); logger.verbose('Visulise tasks'); logger.debug(`Options: ${JSON.stringify(options, null, 2)}`); - // List all files in __dirname directory if log level is debug logger.verbose(`Path to html files: ${STATIC_PATH}`); - fs.readdir(STATIC_PATH, (err, files) => { - if (err) { - return logger.error(`Unable to scan html directory: ${err}`); - } - files.forEach((file) => { - logger.debug(file); - const stats = fs.statSync(`${STATIC_PATH}/${file}`); - const fileSizeInBytes = stats.size; - logger.debug(`File size: ${fileSizeInBytes}`); - logger.debug('-------------------'); + + // List all files in __dirname directory if log level is debug and not SEA app + if (!isSea) { + fs.readdir(STATIC_PATH, (err, files) => { + if (err) { + return logger.error(`Unable to scan html directory: ${err}`); + } + files.forEach((file) => { + logger.debug(file); + const stats = fs.statSync(`${STATIC_PATH}/${file}`); + const fileSizeInBytes = stats.size; + logger.debug(`File size: ${fileSizeInBytes}`); + logger.debug('-------------------'); + }); + + return true; }); + } - return true; - }); + // NOTE: If running as SEA app, it is not possible to list files in the static directory // Verify files used by http server exist - let fileExists = await verifyFileExists(`${STATIC_PATH}/index.html`); - if (!fileExists) { + logger.verbose(`Verifying that files used by http server exist`); + let fileExists; + if (isSea) { + fileExists = verifySeaAssetExists(`/index.html`); + } else { + fileExists = await verifyFileSystemExists(`${STATIC_PATH}/index.html`); + } + if (!fileExists && isSea) { + logger.error(`File /index.html does not exist`); + return false; + } else if (!fileExists && !isSea) { logger.error(`File ${STATIC_PATH}/index.html does not exist`); return false; } - fileExists = await verifyFileExists(`${STATIC_PATH}/404.html`); - if (!fileExists) { + if (isSea) { + fileExists = verifySeaAssetExists(`/404.html`); + } else { + fileExists = await verifyFileSystemExists(`${STATIC_PATH}/404.html`); + } + if (!fileExists && isSea) { + logger.error(`File /404.html does not exist`); + return false; + } else if (!fileExists && !isSea) { logger.error(`File ${STATIC_PATH}/404.html does not exist`); return false; } @@ -395,6 +508,4 @@ const visTask = async (options) => { startHttpServer(optionsNew); return true; -}; - -export default visTask; +} diff --git a/src/lib/task/class_allcompositeevents.js b/src/lib/task/class_allcompositeevents.js index 7e7e1a1..39cb7c1 100644 --- a/src/lib/task/class_allcompositeevents.js +++ b/src/lib/task/class_allcompositeevents.js @@ -1,13 +1,13 @@ import axios from 'axios'; import path from 'node:path'; -import { logger, execPath, verifyFileExists } from '../../globals.js'; +import { logger } from '../../globals.js'; import { setupQrsConnection } from '../util/qseow/qrs.js'; -import QlikSenseCompositeEvent from './class_compositeevent.js'; +import { QlikSenseCompositeEvent } from './class_compositeevent.js'; import { catchLog } from '../util/log.js'; import { getCertFilePaths } from '../util/qseow/cert.js'; -class QlikSenseCompositeEvents { +export class QlikSenseCompositeEvents { constructor() { // } @@ -46,11 +46,8 @@ class QlikSenseCompositeEvents { try { logger.debug('GET SCHEMAEVENT: Starting get composite events from QSEoW'); - const axiosConfig = await setupQrsConnection(this.options, { + const axiosConfig = setupQrsConnection(this.options, { method: 'get', - fileCert: this.fileCert, - fileCertKey: this.fileCertKey, - fileCertCA: this.fileCertCA, path: '/qrs/compositeevent/full', }); @@ -79,5 +76,3 @@ class QlikSenseCompositeEvents { }); } } - -export default QlikSenseCompositeEvents; diff --git a/src/lib/task/class_allschemaevents.js b/src/lib/task/class_allschemaevents.js index 80007d9..fab1c2f 100644 --- a/src/lib/task/class_allschemaevents.js +++ b/src/lib/task/class_allschemaevents.js @@ -1,11 +1,11 @@ import axios from 'axios'; import { logger } from '../../globals.js'; import { setupQrsConnection } from '../util/qseow/qrs.js'; -import QlikSenseSchemaEvent from './class_schemaevent.js'; +import { QlikSenseSchemaEvent } from './class_schemaevent.js'; import { catchLog } from '../util/log.js'; import { getCertFilePaths } from '../util/qseow/cert.js'; -class QlikSenseSchemaEvents { +export class QlikSenseSchemaEvents { constructor() { // } @@ -58,11 +58,8 @@ class QlikSenseSchemaEvents { try { logger.debug('GET SCHEMA EVENT: Starting get schema events from QSEoW'); - const axiosConfig = await setupQrsConnection(this.options, { + const axiosConfig = setupQrsConnection(this.options, { method: 'get', - fileCert: this.fileCert, - fileCertKey: this.fileCertKey, - fileCertCA: this.fileCertCA, path: '/qrs/schemaevent/full', }); @@ -90,5 +87,3 @@ class QlikSenseSchemaEvents { }); } } - -export default QlikSenseSchemaEvents; diff --git a/src/lib/task/class_alltasks.js b/src/lib/task/class_alltasks.js index 2dccbde..4e35f26 100644 --- a/src/lib/task/class_alltasks.js +++ b/src/lib/task/class_alltasks.js @@ -12,9 +12,9 @@ import { getTaskColumnPosFromHeaderRow, } from '../util/qseow/lookups.js'; -import QlikSenseTask from './class_task.js'; -import QlikSenseSchemaEvents from './class_allschemaevents.js'; -import QlikSenseCompositeEvents from './class_allcompositeevents.js'; +import { QlikSenseTask } from './class_task.js'; +import { QlikSenseSchemaEvents } from './class_allschemaevents.js'; +import { QlikSenseCompositeEvents } from './class_allcompositeevents.js'; import { getTagIdByName } from '../util/qseow/tag.js'; import { getCustomPropertyIdByName } from '../util/qseow/customproperties.js'; import { getAppById } from '../util/qseow/app.js'; @@ -22,7 +22,7 @@ import { taskExistById, getTaskById } from '../util/qseow/task.js'; import { catchLog } from '../util/log.js'; import { getCertFilePaths } from '../util/qseow/cert.js'; -class QlikSenseTasks { +export class QlikSenseTasks { constructor() { // } @@ -1271,9 +1271,6 @@ class QlikSenseTasks { // Save task to QSEoW const axiosConfig = setupQrsConnection(this.options, { method: 'post', - fileCert: this.fileCert, - fileCertKey: this.fileCertKey, - fileCertCA: this.fileCertCA, path: '/qrs/compositeevent', body, }); @@ -1340,9 +1337,6 @@ class QlikSenseTasks { // Save task to QSEoW const axiosConfig = setupQrsConnection(this.options, { method: 'post', - fileCert: this.fileCert, - fileCertKey: this.fileCertKey, - fileCertCA: this.fileCertCA, path: '/qrs/reloadtask/create', body, }); @@ -1402,9 +1396,6 @@ class QlikSenseTasks { // Save task to QSEoW const axiosConfig = setupQrsConnection(this.options, { method: 'post', - fileCert: this.fileCert, - fileCertKey: this.fileCertKey, - fileCertCA: this.fileCertCA, path: '/qrs/externalprogramtask/create', body, }); @@ -1466,9 +1457,6 @@ class QlikSenseTasks { // Save task to QSEoW const axiosConfig = setupQrsConnection(this.options, { method: 'post', - fileCert: this.fileCert, - fileCertKey: this.fileCertKey, - fileCertCA: this.fileCertCA, path: '/qrs/reloadtask/create', body, }); @@ -1572,17 +1560,11 @@ class QlikSenseTasks { if (filter === '') { axiosConfig = setupQrsConnection(this.options, { method: 'get', - fileCert: this.fileCert, - fileCertKey: this.fileCertKey, - fileCertCA: this.fileCertCA, path: '/qrs/reloadtask/full', }); } else { axiosConfig = setupQrsConnection(this.options, { method: 'get', - fileCert: this.fileCert, - fileCertKey: this.fileCertKey, - fileCertCA: this.fileCertCA, path: '/qrs/reloadtask/full', queryParameters: [{ name: 'filter', value: filter }], }); @@ -1602,17 +1584,11 @@ class QlikSenseTasks { if (filter === '') { axiosConfig = setupQrsConnection(this.options, { method: 'get', - fileCert: this.fileCert, - fileCertKey: this.fileCertKey, - fileCertCA: this.fileCertCA, path: '/qrs/externalprogramtask/full', }); } else { axiosConfig = setupQrsConnection(this.options, { method: 'get', - fileCert: this.fileCert, - fileCertKey: this.fileCertKey, - fileCertCA: this.fileCertCA, path: '/qrs/externalprogramtask/full', queryParameters: [{ name: 'filter', value: filter }], }); @@ -2400,5 +2376,3 @@ class QlikSenseTasks { return this.taskNetwork; } } - -export default QlikSenseTasks; diff --git a/src/lib/task/class_compositeevent.js b/src/lib/task/class_compositeevent.js index 414f402..fcde6af 100644 --- a/src/lib/task/class_compositeevent.js +++ b/src/lib/task/class_compositeevent.js @@ -1,6 +1,6 @@ import { logger } from '../../globals.js'; -class QlikSenseCompositeEvent { +export class QlikSenseCompositeEvent { constructor(compositeEvent) { this.compositeEvent = compositeEvent; } @@ -9,5 +9,3 @@ class QlikSenseCompositeEvent { // Other methods } - -export default QlikSenseCompositeEvent; diff --git a/src/lib/task/class_schemaevent.js b/src/lib/task/class_schemaevent.js index 2893ead..5ccbe42 100644 --- a/src/lib/task/class_schemaevent.js +++ b/src/lib/task/class_schemaevent.js @@ -1,6 +1,6 @@ import { logger } from '../../globals.js'; -class QlikSenseSchemaEvent { +export class QlikSenseSchemaEvent { constructor(schemaEvent) { this.schemaEvent = schemaEvent; } @@ -9,5 +9,3 @@ class QlikSenseSchemaEvent { // Other methods } - -export default QlikSenseSchemaEvent; diff --git a/src/lib/task/class_task.js b/src/lib/task/class_task.js index 5e35cdf..acea975 100644 --- a/src/lib/task/class_task.js +++ b/src/lib/task/class_task.js @@ -4,8 +4,7 @@ import { logger } from '../../globals.js'; import { mapTaskExecutionStatus } from '../util/qseow/lookups.js'; // const randomWords2 = (...args) => import('random-words').then(({ default: randomWords }) => randomWords(...args)); -class QlikSenseTask { - // eslint-disable-next-line no-useless-constructor +export class QlikSenseTask { constructor() { // } @@ -184,5 +183,3 @@ class QlikSenseTask { } } } - -export default QlikSenseTask; diff --git a/src/lib/task/task_qrs.js b/src/lib/task/task_qrs.js index d8bfe85..b867d20 100644 --- a/src/lib/task/task_qrs.js +++ b/src/lib/task/task_qrs.js @@ -30,11 +30,8 @@ export const getCustomProperty = async (options) => { // Build QRS query string using custom property name const filter = encodeURIComponent(`name eq '${options.customPropertyName}'`); - const axiosConfig = await setupQrsConnection(options, { + const axiosConfig = setupQrsConnection(options, { method: 'get', - fileCert: certFilesFullPath.fileCert, - fileCertKey: certFilesFullPath.fileCertKey, - fileCertCA: certFilesFullPath.fileCertCA, path: '/qrs/CustomPropertyDefinition/full', queryParameters: [{ name: 'filter', value: filter }], }); @@ -104,7 +101,7 @@ export const getTasksFromQseow = async (options) => { } logger.debug(`GET TASK: Final QRS query filter: ${filter}`); - const axiosConfig = await setupQrsConnection(options, { + const axiosConfig = setupQrsConnection(options, { method: 'get', path: '/qrs/reloadtask/full', queryParameters: [{ name: 'filter', value: filter }], @@ -131,7 +128,7 @@ export const getTasksFromQseow = async (options) => { export const updateReloadTask = async (options, payload) => { try { // TODO Should be using PUT instead of POST if updating an existing task? - const axiosConfig = await setupQrsConnection(options, { + const axiosConfig = setupQrsConnection(options, { method: 'post', path: '/qrs/reloadtask/update', body: payload, diff --git a/src/lib/util/log.js b/src/lib/util/log.js index 28da856..b047c64 100644 --- a/src/lib/util/log.js +++ b/src/lib/util/log.js @@ -1,4 +1,4 @@ -import { logger, appVersion, isPkg, execPath } from '../../globals.js'; +import { logger, appVersion, isSea, execPath } from '../../globals.js'; export const logStartupInfo = (options, cmd, cmdDesc) => { logger.info('-----------------------------------------------------------'); @@ -15,7 +15,7 @@ export const logStartupInfo = (options, cmd, cmdDesc) => { logger.info(`| https://github.com/ptarmiganlabs/ctrl-q`); logger.info('----------------------------------------------------------'); logger.info(``); - logger.verbose(`Ctrl-Q was started as a stand-alone binary: ${isPkg}`); + logger.verbose(`Ctrl-Q was started as a stand-alone binary: ${isSea}`); logger.verbose(`Ctrl-Q was started from ${execPath}`); logger.verbose(`Options: ${JSON.stringify(options, null, 2)}`); logger.verbose(``); @@ -23,7 +23,7 @@ export const logStartupInfo = (options, cmd, cmdDesc) => { // Function used to provide consistent logging to all try-catch blocks export const catchLog = (msgContext, err) => { - if (isPkg) { + if (isSea) { if (err.message) { logger.error(`${msgContext}: ${err.message}`); } else { diff --git a/src/lib/util/qscloud/assert-options.js b/src/lib/util/qscloud/assert-options.js index 51e807d..d10e9b6 100644 --- a/src/lib/util/qscloud/assert-options.js +++ b/src/lib/util/qscloud/assert-options.js @@ -1,6 +1,5 @@ -import path from 'node:path'; import { version as uuidVersion, validate as uuidValidate } from 'uuid'; -import { logger, execPath, verifyFileExists } from '../../../globals.js'; +import { logger, execPath } from '../../../globals.js'; export const qscloudSharedParamAssertOptions = async (options) => { // Ensure that parameters common to all QS Cloud commands are valid diff --git a/src/lib/util/qseow/about.js b/src/lib/util/qseow/about.js index 844e1d9..43769f2 100644 --- a/src/lib/util/qseow/about.js +++ b/src/lib/util/qseow/about.js @@ -4,7 +4,7 @@ import { logger, execPath } from '../../../globals.js'; import { setupQrsConnection } from './qrs.js'; import { catchLog } from '../log.js'; -async function getAboutFromQseow(options) { +export async function getAboutFromQseow(options) { logger.verbose(`Getting about info from QSEoW...`); try { @@ -29,5 +29,3 @@ async function getAboutFromQseow(options) { return false; } } - -export default getAboutFromQseow; diff --git a/src/lib/util/qseow/assert-options.js b/src/lib/util/qseow/assert-options.js index 0e10c4c..089dae0 100644 --- a/src/lib/util/qseow/assert-options.js +++ b/src/lib/util/qseow/assert-options.js @@ -1,6 +1,6 @@ import { version as uuidVersion, validate as uuidValidate } from 'uuid'; -import { logger, execPath, verifyFileExists } from '../../../globals.js'; +import { logger, execPath, verifyFileSystemExists } from '../../../globals.js'; import { getCertFilePaths } from '../qseow/cert.js'; export const qseowSharedParamAssertOptions = async (options) => { @@ -23,7 +23,7 @@ export const qseowSharedParamAssertOptions = async (options) => { // Get certificate paths const { fileCert, fileCertKey, fileCertCA } = getCertFilePaths(options); - const fileCertExists = await verifyFileExists(fileCert); + const fileCertExists = await verifyFileSystemExists(fileCert); if (fileCertExists === false) { logger.error(`Missing certificate file ${fileCert}. Aborting`); process.exit(1); @@ -31,7 +31,7 @@ export const qseowSharedParamAssertOptions = async (options) => { logger.verbose(`Certificate file ${fileCert} found`); } - const fileCertKeyExists = await verifyFileExists(fileCertKey); + const fileCertKeyExists = await verifyFileSystemExists(fileCertKey); if (fileCertKeyExists === false) { logger.error(`Missing certificate key file ${fileCertKey}. Aborting`); process.exit(1); @@ -39,7 +39,7 @@ export const qseowSharedParamAssertOptions = async (options) => { logger.verbose(`Certificate key file ${fileCertKey} found`); } - const fileCertCAExists = await verifyFileExists(fileCertCA); + const fileCertCAExists = await verifyFileSystemExists(fileCertCA); if (fileCertCAExists === false) { logger.error(`Missing certificate CA file ${fileCertCA}. Aborting`); process.exit(1); diff --git a/src/lib/util/qseow/cert.js b/src/lib/util/qseow/cert.js index e025361..699ebdb 100644 --- a/src/lib/util/qseow/cert.js +++ b/src/lib/util/qseow/cert.js @@ -1,5 +1,5 @@ import path from 'node:path'; -import { logger, execPath, verifyFileExists } from '../../../globals.js'; +import { execPath } from '../../../globals.js'; import { catchLog } from '../log.js'; export function getCertFilePaths(options) { diff --git a/src/lib/util/qseow/enigma_util.js b/src/lib/util/qseow/enigma_util.js index 1889906..9d59d8a 100644 --- a/src/lib/util/qseow/enigma_util.js +++ b/src/lib/util/qseow/enigma_util.js @@ -5,11 +5,11 @@ import { fileURLToPath } from 'node:url'; import upath from 'upath'; import sea from 'node:sea'; -import { logger, readCert } from '../../../globals.js'; +import { logger, readCert, isSea } from '../../../globals.js'; import { getCertFilePaths } from '../../util/qseow/cert.js'; // Function to get Enigma.js schema file -const getEnigmaSchema = (processPkgFlag, seaFlag, options) => { +const getEnigmaSchema = (options) => { // Array of supported schema versions const supportedSchemaVersions = ['12.170.2', '12.612.0', '12.936.0', '12.1306.0', '12.1477.0', '12.1657.0', '12.1823.0', '12.2015.0']; @@ -27,25 +27,7 @@ const getEnigmaSchema = (processPkgFlag, seaFlag, options) => { } // Are we running as a packaged app? - if (processPkgFlag) { - const schemaFile = `./node_modules/enigma.js/schemas/${options.schemaVersion}.json`; - logger.debug(`Enigma.js schema file: ${schemaFile}`); - - // Yes, we are running as a packaged app - // Get path to JS file const - const a = process.pkg.defaultEntrypoint; - logger.debug(`APPDUMP schema path a: ${a}`); - - // Strip off the filename - const b = upath.dirname(a); - logger.debug(`APPDUMP schema path b: ${b}`); - - // Add path to schema file - const c = upath.join(b, schemaFile); - logger.debug(`APPDUMP schema path c: ${c}`); - - qixSchemaJson = readFileSync(c); - } else if (seaFlag) { + if (isSea) { // Load schema file qixSchemaJson = sea.getAsset(`enigma_schema_${options.schemaVersion}.json`, 'utf8'); } else { @@ -83,7 +65,7 @@ export const setupEnigmaConnection = (options, sessionId) => { // Set up enigma.js configuration logger.debug(`Enigma.js schema version: ${options.schemaVersion}`); - const qixSchema = getEnigmaSchema(process.pkg, sea.isSea(), options); + const qixSchema = getEnigmaSchema(options); let enigmaConfig; // Should certificates be used for authentication? diff --git a/src/lib/util/qseow/proxy.js b/src/lib/util/qseow/proxy.js index 2eaa563..2ddb486 100644 --- a/src/lib/util/qseow/proxy.js +++ b/src/lib/util/qseow/proxy.js @@ -4,7 +4,7 @@ import { logger, execPath } from '../../../globals.js'; import { setupQrsConnection } from './qrs.js'; import { catchLog } from '../log.js'; -const getProxiesFromQseow = async (options, _sessionCookie) => { +export async function getProxiesFromQseow(options, _sessionCookie) { logger.verbose(`Getting all proxies from QSEoW...`); // TODO Should support JWTs here too? @@ -30,6 +30,4 @@ const getProxiesFromQseow = async (options, _sessionCookie) => { } return proxies; -}; - -export default getProxiesFromQseow; +} diff --git a/src/lib/util/qseow/qps.js b/src/lib/util/qseow/qps.js index efc7126..6deec02 100644 --- a/src/lib/util/qseow/qps.js +++ b/src/lib/util/qseow/qps.js @@ -1,7 +1,18 @@ import https from 'node:https'; + import { logger, generateXrfKey, readCert } from '../../../globals.js'; +import { getCertFilePaths } from './cert.js'; + +export function setupQpsConnection(options, param) { + // Ensure correct auth info is present + if (options.authType === 'cert') { + // options.authUserDir and options.authUserId should be set + if (!options.authUserDir || !options.authUserId) { + logger.error(`Setting up connection to QRS. Missing user directory and/or user ID. Exiting.`); + process.exit(1); + } + } -const setupQPSConnection = (options, param) => { // Ensure valid http method if (!param.method || (param.method.toLowerCase() !== 'get' && param.method.toLowerCase() !== 'delete')) { logger.error(`Setting up connection to QPS. Invalid http method '${param.method}'. Exiting.`); @@ -22,11 +33,29 @@ const setupQPSConnection = (options, param) => { logger.debug(`QPS host: ${options.hostProxy}`); logger.debug(`Reject unauthorized certificate: ${!!options.secure}`); + // Get certificate paths + // If specified in the param object, use those paths + // Otherwise, use the paths from the command line options + let { fileCert, fileCertKey, fileCertCA } = getCertFilePaths(options); + + // If the paths are specified in the param object, use those paths + if (param.fileCert) { + fileCert = param.fileCert; + } + + if (param.fileCertKey) { + fileCertKey = param.fileCertKey; + } + + if (param.fileCertCA) { + fileCertCA = param.fileCertCA; + } + const httpsAgent = new https.Agent({ rejectUnauthorized: options.secure !== 'false', - cert: readCert(param.fileCert), - key: readCert(param.fileCertKey), - ca: readCert(param.fileCertCA), + cert: readCert(fileCert), + key: readCert(fileCertKey), + ca: readCert(fileCertCA), }); axiosConfig = { @@ -73,6 +102,4 @@ const setupQPSConnection = (options, param) => { } return axiosConfig; -}; - -export default setupQPSConnection; +} diff --git a/src/lib/util/qseow/qrs.js b/src/lib/util/qseow/qrs.js index 7b46606..4bfefed 100644 --- a/src/lib/util/qseow/qrs.js +++ b/src/lib/util/qseow/qrs.js @@ -1,7 +1,7 @@ import https from 'node:https'; import { logger, generateXrfKey, readCert } from '../../../globals.js'; -import { getCertFilePaths } from '../qseow/cert.js'; +import { getCertFilePaths } from './cert.js'; // Function to sanitize virtual proxy export function sanitizeVirtualProxy(virtualProxy) { @@ -87,6 +87,7 @@ export function setupQrsConnection(options, param) { // Otherwise, use the paths from the command line options let { fileCert, fileCertKey, fileCertCA } = getCertFilePaths(options); + // If the paths are specified in the param object, use those paths if (param.fileCert) { fileCert = param.fileCert; } diff --git a/src/lib/util/qseow/session.js b/src/lib/util/qseow/session.js index 5bd5532..db6b03f 100644 --- a/src/lib/util/qseow/session.js +++ b/src/lib/util/qseow/session.js @@ -3,10 +3,10 @@ import path from 'node:path'; import { table } from 'table'; import yesno from 'yesno'; import { logger, execPath } from '../../../globals.js'; -import setupQPSConnection from './qps.js'; +import { setupQpsConnection } from './qps.js'; import { setupQrsConnection } from './qrs.js'; import { catchLog } from '../log.js'; -import getProxiesFromQseow from './proxy.js'; +import { getProxiesFromQseow } from './proxy.js'; const consoleProxiesTableConfig = { border: { @@ -207,12 +207,9 @@ export const getSessionsFromQseow = async (options, sessionCookie) => { } // Get sessions for this virtual proxy - axiosConfig = setupQPSConnection(options, { + axiosConfig = setupQpsConnection(options, { hostProxy: proxy.serverNodeConfiguration.hostName, method: 'get', - fileCert, - fileCertKey, - fileCertCA, path: `/qps/${vp.prefix}/session`, sessionCookie: null, }); @@ -336,7 +333,7 @@ export const deleteSessionsFromQSEoWIds = async (options) => { logger.debug(`Session metadata: ${JSON.stringify(s, null, 2)}`); try { - const axiosConfig = setupQPSConnection(options, { + const axiosConfig = setupQpsConnection(options, { hostProxy: options.hostProxy, method: 'delete', path: `/qps/${options.sessionVirtualProxy}/session/${s.sessionId}`, diff --git a/src/static/404.html b/src/static/404.html index 8f58182..5632554 100644 --- a/src/static/404.html +++ b/src/static/404.html @@ -34,7 +34,7 @@
- Ctrl-Q logo + Ctrl-Q logo

Err. That's an error.

The page you were looking for could not be found.

diff --git a/src/static/ctrl-q_2.png b/src/static/ctrl-q.png similarity index 100% rename from src/static/ctrl-q_2.png rename to src/static/ctrl-q.png diff --git a/src/static/index.html b/src/static/index.html index b4b2867..c07435d 100644 --- a/src/static/index.html +++ b/src/static/index.html @@ -216,7 +216,7 @@