Skip to content

Commit 86a6097

Browse files
authored
Merge pull request #145 from Miamiohlibs/dev
version 2.4.0 - admin web console
2 parents ac68753 + 1b581d1 commit 86a6097

50 files changed

Lines changed: 2219 additions & 277 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,3 +12,4 @@ config/appConf.js
1212
config/campusConf.js
1313
config/jamf.js
1414

15+
www/.next/*

CHANGELOG.md

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
# Changelog
2+
3+
## 2.4.0 - 2024-10-30
4+
5+
### Added
6+
7+
- New feature: Web-based admin console, including System Status, Logs, and Stats. Uses Express.js and EJS. This is an optional feature not required to run the core license management scripts functionality, but should make it easier to monitor and troubleshoot the system. Setting up the admin console requires additional configuration steps, added as an "admin" property. See the [documentation](https://miamiohlib.gitbook.io/software-checkout/setup/admin-web-console) for details.
8+
9+
### Changed
10+
11+
- Updated logEachCheckout, getUsageData, and anonymizeStats to be cron-friendly (no longer requires the user to be in the same directory being evaluated).
12+
13+
## 2.3.0 - 2024-06-12
14+
15+
### Added
16+
17+
- New feature: "demo" scripts allow user to manually add, remove, and list license assignments in LibCal and Jamf on the command line. Also allows lookup of reservations in LibCal, and manage known email aliases.
18+
19+
### Changed
20+
21+
- Jamf uses bearer token for authentication instead of username and password.
22+
- More consistent log format.
23+
- Improved error handling for stats functions and emailConverter.
24+
- Expanded documentation.
25+
26+
## 2.2.0 - 2023-08-15
27+
28+
### Changed
29+
30+
- Adobe API uses Oauth2 for authentication instead of deprecated JWT.

CITATION.cff

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,5 +28,5 @@ keywords:
2828
- springshare
2929
- libcal
3030
license: GPL-3.0
31-
version: 2.2.0
32-
date-released: '2023-08-15'
31+
version: 2.4.0
32+
date-released: '2024-10-30'

config/appConf.sample.js

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,29 @@ module.exports = {
33
secret: 'you should replace this with gibberish of your own',
44
note: 'this is used to encrypt the user data in the logs',
55
},
6+
admin: {
7+
onServer: false,
8+
server: {
9+
key: '/path/to/public_key.key',
10+
cert: '/path/to/certificate.crt',
11+
note: 'if onServer is true, you need to provide the key and cert paths',
12+
},
13+
port: 3010,
14+
requireLogin: true,
15+
allowedUsers: [], // list allowed emails here
16+
apiKey: 'writeYourOwnKeyHereTheValueIsNotImportant',
17+
hostname: 'localhost', // or 'your.hostname.edu'
18+
googleClientId:
19+
'get a google client id from https://console.developers.google.com/apis/credentials',
20+
googleClientSecret: 'get a google client secret from the same place',
21+
authCallback: 'http://localhost:3010/google/callback', // listed as "Authorized redirect URIs" in the google console
22+
navbarTheme: {
23+
// see Bootstrap 5 docs for explanation of these color classes:
24+
// https://getbootstrap.com/docs/5.0/utilities/background/
25+
backgroundColor: 'bg-secondary', // opts: bg-primary, bg-secondary, bg-success, bg-danger, bg-warning, bg-info, bg-light, bg-dark
26+
textColor: 'navbar-dark', // use 'navbar-light' for light backgrounds, 'navbar-dark' for dark backgrounds
27+
},
28+
},
629
emailConverter: {
730
active: false, // set to true to use the email converter, values below also need to be configured
831
baseUrl: 'https://yourEmailConverterApi/?q=',

dailyUsageSummary.js

Lines changed: 12 additions & 82 deletions
Original file line numberDiff line numberDiff line change
@@ -1,84 +1,14 @@
1-
const fs = require('fs');
2-
const config = require('./config/appConf');
3-
let software = config.software;
4-
let softwareTitles = software.map((item) => item.libCalName);
5-
let allData = [];
6-
const { Parser } = require('json2csv');
7-
8-
// softwareTitles = ['Adobe Creative Cloud', 'Logic Pro'];
9-
10-
let json = summarizeStats(softwareTitles);
11-
// console.log(JSON.stringify(json, null, 2));
12-
const parser = new Parser();
13-
const csv = parser.parse(json);
14-
console.log(csv);
15-
16-
function summarizeStats(softwareTitles) {
17-
softwareTitles.forEach((softwareTitle) => {
18-
let folder = softwareTitle.replace(/ /g, '');
19-
// get all files that aren't directories
20-
let files, anonFiles;
21-
if (fs.existsSync(`./logs/dailyStats/${folder}`) != false) {
22-
files = fs.readdirSync(`./logs/dailyStats/${folder}`, {
23-
withFileTypes: true,
24-
});
25-
try {
26-
anonFiles = fs.readdirSync(`./logs/dailyStats/${folder}/anon`, {
27-
withFileTypes: true,
28-
});
29-
} catch (err) {
30-
anonFiles = [];
31-
}
32-
files.push(...anonFiles); // all files, anonymous and not
33-
files = [...new Set(files)]; // remove duplicates
34-
files = files.filter((file) => file.isFile()); // remove directories
35-
files.sort((a, b) => (a.name > b.name ? 1 : -1)); // sort by date
36-
files.forEach((file) => {
37-
let date = file.name.split('.')[0];
38-
let usage = getDateStats(softwareTitle, date);
39-
updateOrAddEntry({ date, softwareTitle, usage });
40-
});
41-
}
42-
});
43-
return allData;
44-
}
45-
46-
function updateOrAddEntry(newEntry) {
47-
let found = false;
48-
49-
for (let i = 0; i < allData.length; i++) {
50-
if (allData[i].date === newEntry.date) {
51-
allData[i][newEntry.softwareTitle] = newEntry.usage;
52-
found = true;
53-
break;
54-
}
55-
}
56-
57-
if (!found) {
58-
let obj = { date: newEntry.date, [newEntry.softwareTitle]: newEntry.usage };
59-
allData.push(obj);
60-
}
61-
}
62-
63-
function getDateStats(softwareTitle, date) {
64-
let folder = softwareTitle.replace(/ /g, '');
65-
let filepath = './logs/dailyStats/' + folder + '/' + date + '.json';
66-
let anonpath = './logs/dailyStats/' + folder + '/anon/' + date + '.json';
67-
let data;
68-
if (fs.existsSync(filepath)) {
69-
data = require(`./logs/dailyStats/${folder}/${date}.json`);
70-
} else if (fs.existsSync(anonpath)) {
71-
data = require(`./logs/dailyStats/${folder}/anon/${date}.json`);
72-
} else {
73-
console.log('No file found for ' + softwareTitle + ' on ' + date);
74-
return;
1+
const dailyStatsService = require('./services/dailyStatsService');
2+
const yargs = require('yargs');
3+
const argv = yargs(process.argv.slice(2)).argv;
4+
5+
const allowedFormats = ['json', 'csv']; // allowed formats
6+
if (argv.format) {
7+
if (!allowedFormats.includes(argv.format)) {
8+
console.log('Invalid format. Use json or csv');
9+
process.exit(1);
7510
}
76-
let confirmedBookings = data.filter((item) => item.status == 'Confirmed');
77-
let skipCheckins = confirmedBookings.filter(
78-
(item) => !item.toDate.match(date + 'T00:00:00')
79-
);
80-
let distinctUsers = [...new Set(skipCheckins.map((item) => item.email))];
81-
let totalUsage = distinctUsers.length;
82-
// console.log(softwareTitle + ',' + date + ',' + totalUsage);
83-
return totalUsage;
11+
console.log(dailyStatsService(argv.format));
12+
} else {
13+
console.log(dailyStatsService());
8414
}

docs/README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22

33
**Software Checkout** is a Node.js app for libraries and other institutions to allow dynamic request and assignment of software licenses. Users can request license access by signing up for a time-slot using SpringShare's [LibCal](https://www.springshare.com/libcal/); the app then updates license assignments with the vendor's user management API to assign or a revoke licenses at the appropriate time.
44

5+
The main Software Checkout app does not have its own web interface, but there is an admin web console to provide an easy way for admins to check on the status of license assignments, usage statistics, and examination of system logs.
6+
57
## Current Supported Vendors
68

79
* [Adobe Creative Cloud](https://www.adobe.com/creativecloud.html)

docs/SUMMARY.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,9 @@
66
* [LibCal](setup/libcal.md)
77
* [Adobe](setup/adobe.md)
88
* [Jamf](setup/jamf.md)
9+
* [Admin Web Console](setup/admin-web-console.md)
910
* [Component Demos/Tests](setup/component-demos-tests.md)
10-
* [Running the App](running-the-app.md)
11+
* [Running the App & Admin Web Console](running-the-app-and-admin-web-console.md)
1112
* [Testing](testing.md)
1213
* [Usage Stats](usage-stats.md)
1314
* [Troubleshooting](troubleshooting/README.md)
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
# Running the App & Admin Web Console
2+
3+
### Running the app
4+
5+
There are several ways to run the app from the command line:
6+
7+
* `node app` - run once
8+
* PRODUCTION: `npm run server`: will add the `--listen` flag as well as `--name=software-checkout` so we can see which node process it is
9+
* `node jamfUserUpdate` - for unknown reasons, the process for adding Jamf users wasn't working in the main app, so we spun it off as its own process that should run prior to running the app. It should be run _before_ the main app.
10+
11+
#### Running as a cron job
12+
13+
The app just runs once when you run one of the above commands. To make the service useful, you should run it frequently -- every 10-15 minutes. Set up a cron job to run the service in production a few times an hour, and run the jamfUserUpdate before it (if using Jamf):
14+
15+
`1,13,25,37,49 * * * * /usr/bin/node /opt/dev/SoftwareCheckout2/jamfUserUpdate.js > /dev/null 2>&1` \
16+
`2,14,26,38,50 * * * * /usr/bin/node /opt/dev/SoftwareCheckout2/app.js --name=Software-Checkout > /dev/null 2>&1`&#x20;
17+
18+
You will likely want to run the daily stats update as a cron job too -- that is described in the [Usage Stats](usage-stats.md) page.
19+
20+
#### Killing / restarting the app
21+
22+
* run `./killapp` -- finds the relevant process and kills it (only works if you used `npm run server` to start the app
23+
* `./restart` or `./killapp -r`: kill and restart (or use `npm run server` as above)
24+
25+
## Running the admin web console
26+
27+
Unlike the main app, which runs as a "headless" process with no native user interface, the admin web console is a web-based service. For information on setting up and running the web process see [Express Web Setup](setup/admin-web-console.md#express-server-setup).

docs/running-the-app.md

Lines changed: 0 additions & 12 deletions
This file was deleted.

docs/setup/README.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,17 @@ Request API keys from LibCal and Adobe. You will enter these values in the confi
1414

1515
* `config/appConf.js`:
1616
* `cryptoConfig`: The value defined here will be used as an encryption key to keep any anonymized data encrypted, while keeping values consistent so you can identify the number of distinct users of the system over time. Change the `cryptoConfig.secret` value to any random long string when you set up the app. You should not change it afterwards.&#x20;
17+
* `admin:` setup of the admin web console, including authentication details, port number, and server information.
18+
* `onServer:` use `false` for running over http://localhost, `true` for https:// on a server
19+
* `server:` https key/cert pair
20+
* `port:` port on which Express will listen for web calls; default is 3010
21+
* `requireLogin:` whether or not to require Google login for users to access the admin web console. Only set this to `false` when running on localhost and/or for debugging purposes. Should be set to `true` in production.
22+
* `allowedUsers:` array of permitted users; if `requiredLogin: true`, user email must be included in this array to access the web admin console.
23+
* `apiKey:` This is the API key required to access the API that powers the admin web console. Make this value up for yourself, the app uses it to authenticate to its own API. You will only need it if you want to connect to the web admin console API externally.&#x20;
24+
* `hostname:` set to 'localhost' or the server address without protocol (e.g. 'sc.lib.myorg.edu')
25+
* googleClientId: create a googleClientId from the Google Develop Console: [https://console.developers.google.com/apis/credentials](https://console.developers.google.com/apis/credentials)
26+
* googleClientSecret: this will be generate when you generate the googleClientId
27+
* authCallback: you will set this value in the Google Developer Console. Set it to a value like 'http://localhost:3010/google/callback' or 'https://sc.lib.yourorg.edu:3010/google/callback' -- use the server and port you've specified above + '/google/callback'.
1728
* `db_connection`: mongodb connection string (optional; used for caching converted email aliases)
1829
* `logLevels`: for each level, set one of: false, 'daily', 'monthly'; false for no logs at that level; 'daily' for logs that start a new logfile each day; 'monthly' for a new logfile per year. Use daily logs for levels that output obnoxiously large amounts of data like 'debug'. Currently, only 'info','error', and 'debug' are used by the app.
1930
* `emailConverter`: LibCal may accept users' aliased email addresss (e.g. my.full.name@fake.org) even though license providers may only use the uniqueId version of a user Id (e.g. namemf@fake.org). If your organization has an API that will convert email aliases to the uniqueId/authoritative email address, use these settings to configure the use of the API.&#x20;

0 commit comments

Comments
 (0)