verdaccio-stats 0.1.0 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +3 -2
- package/lib/index.js +117 -142
- package/lib/index.mjs +117 -142
- package/lib/middlewares/ui.d.ts +13 -0
- package/lib/models.d.ts +1 -0
- package/package.json +1 -1
- package/lib/middlewares/admin-ui.d.ts +0 -17
package/README.md
CHANGED
|
@@ -36,7 +36,8 @@ Add the plugin to your Verdaccio config file:
|
|
|
36
36
|
|
|
37
37
|
```yaml
|
|
38
38
|
middlewares:
|
|
39
|
-
|
|
39
|
+
stats:
|
|
40
|
+
enabled: true
|
|
40
41
|
file: ./stats.db # Optional, SQLite database
|
|
41
42
|
iso-week: false # Optional, whether to use ISO week format
|
|
42
43
|
count-downloads: true # Optional, whether to count downloads
|
|
@@ -59,7 +60,7 @@ After installing and configuring the plugin, it will automatically begin collect
|
|
|
59
60
|
You can view the statistics by visiting the following URL:
|
|
60
61
|
|
|
61
62
|
```
|
|
62
|
-
http://your-registry.com/-/verdaccio/stats/
|
|
63
|
+
http://your-registry.com/-/verdaccio/stats/ui
|
|
63
64
|
```
|
|
64
65
|
|
|
65
66
|
## License
|
package/lib/index.js
CHANGED
|
@@ -9,14 +9,14 @@ var isoWeek = require('dayjs/plugin/isoWeek');
|
|
|
9
9
|
var weekOfYear = require('dayjs/plugin/weekOfYear');
|
|
10
10
|
var weekYear = require('dayjs/plugin/weekYear');
|
|
11
11
|
var path = require('node:path');
|
|
12
|
-
var sequelize = require('sequelize');
|
|
13
12
|
var core = require('@verdaccio/core');
|
|
14
13
|
var middleware = require('@verdaccio/middleware');
|
|
14
|
+
var sequelize = require('sequelize');
|
|
15
15
|
var buildDebug = require('debug');
|
|
16
16
|
var umzug = require('umzug');
|
|
17
17
|
|
|
18
18
|
var name = "verdaccio-stats";
|
|
19
|
-
var version = "0.
|
|
19
|
+
var version = "0.2.0";
|
|
20
20
|
|
|
21
21
|
const plugin = {
|
|
22
22
|
name,
|
|
@@ -157,144 +157,6 @@ class ParsedPluginConfig {
|
|
|
157
157
|
}
|
|
158
158
|
}
|
|
159
159
|
|
|
160
|
-
class DownloadStats extends sequelize.Model {
|
|
161
|
-
}
|
|
162
|
-
class ManifestViewStats extends sequelize.Model {
|
|
163
|
-
}
|
|
164
|
-
class Package extends sequelize.Model {
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
const rootPath = wrapPath("/admin");
|
|
168
|
-
const defaultActions = {
|
|
169
|
-
new: { isAccessible: false },
|
|
170
|
-
edit: { isAccessible: false },
|
|
171
|
-
delete: { isAccessible: false },
|
|
172
|
-
bulkDelete: { isAccessible: false },
|
|
173
|
-
};
|
|
174
|
-
process.env.ADMIN_JS_SKIP_BUNDLE = "true";
|
|
175
|
-
/**
|
|
176
|
-
* Add Admin UI to the application.
|
|
177
|
-
*/
|
|
178
|
-
class AdminUI {
|
|
179
|
-
adminRouter = null;
|
|
180
|
-
config;
|
|
181
|
-
constructor(config) {
|
|
182
|
-
this.config = config;
|
|
183
|
-
void this.create().then((router) => {
|
|
184
|
-
this.adminRouter = router;
|
|
185
|
-
});
|
|
186
|
-
}
|
|
187
|
-
static populatePackageIdListProperties(response) {
|
|
188
|
-
for (const record of response.records) {
|
|
189
|
-
if (record.populated.packageId?.params) {
|
|
190
|
-
const params = record.populated.packageId.params;
|
|
191
|
-
record.populated.packageId.title = `${params.name}@${params.version}`;
|
|
192
|
-
}
|
|
193
|
-
}
|
|
194
|
-
return response;
|
|
195
|
-
}
|
|
196
|
-
static populatePackageIdSearchProperties(response) {
|
|
197
|
-
for (const record of response.records) {
|
|
198
|
-
if (record.populated.packageId?.params) {
|
|
199
|
-
const params = record.populated.packageId.params;
|
|
200
|
-
record.populated.packageId.title = `${params.name}@${params.version}`;
|
|
201
|
-
}
|
|
202
|
-
}
|
|
203
|
-
return response;
|
|
204
|
-
}
|
|
205
|
-
static populatePackageIdShowProperties(response) {
|
|
206
|
-
if (response.record.populated.packageId?.params) {
|
|
207
|
-
const params = response.record.populated.packageId.params;
|
|
208
|
-
response.record.populated.packageId.title = `${params.name}@${params.version}`;
|
|
209
|
-
}
|
|
210
|
-
return response;
|
|
211
|
-
}
|
|
212
|
-
register_middlewares(app) {
|
|
213
|
-
app.use(rootPath, (req, res, next) => {
|
|
214
|
-
if (this.adminRouter) {
|
|
215
|
-
return this.adminRouter(req, res, next);
|
|
216
|
-
}
|
|
217
|
-
else {
|
|
218
|
-
res.status(503).send("Admin UI is not ready yet.");
|
|
219
|
-
}
|
|
220
|
-
});
|
|
221
|
-
}
|
|
222
|
-
async create() {
|
|
223
|
-
const [AdminJS, AdminJSExpress, AdminJSSequelize] = await Promise.all([
|
|
224
|
-
import('adminjs').then((mod) => mod.default),
|
|
225
|
-
import('@adminjs/express').then((mod) => mod.default),
|
|
226
|
-
import('@adminjs/sequelize').then((mod) => mod.default),
|
|
227
|
-
]);
|
|
228
|
-
AdminJS.registerAdapter({
|
|
229
|
-
Resource: AdminJSSequelize.Resource,
|
|
230
|
-
Database: AdminJSSequelize.Database,
|
|
231
|
-
});
|
|
232
|
-
const admin = new AdminJS({
|
|
233
|
-
resources: [
|
|
234
|
-
{
|
|
235
|
-
resource: Package,
|
|
236
|
-
options: {
|
|
237
|
-
actions: { ...defaultActions },
|
|
238
|
-
listProperties: ["id", "name", "version", "createdAt"],
|
|
239
|
-
showProperties: ["id", "name", "version", "createdAt"],
|
|
240
|
-
filterProperties: ["name", "version"],
|
|
241
|
-
sort: { sortBy: "createdAt", direction: "desc" },
|
|
242
|
-
},
|
|
243
|
-
},
|
|
244
|
-
{
|
|
245
|
-
resource: DownloadStats,
|
|
246
|
-
options: {
|
|
247
|
-
actions: {
|
|
248
|
-
...defaultActions,
|
|
249
|
-
show: { after: AdminUI.populatePackageIdShowProperties },
|
|
250
|
-
list: { after: AdminUI.populatePackageIdListProperties },
|
|
251
|
-
search: { after: AdminUI.populatePackageIdSearchProperties },
|
|
252
|
-
},
|
|
253
|
-
properties: {
|
|
254
|
-
periodType: {
|
|
255
|
-
availableValues: PERIOD_TYPES.map((type) => ({ value: type, label: type })),
|
|
256
|
-
},
|
|
257
|
-
},
|
|
258
|
-
listProperties: ["id", "packageId", "periodType", "periodValue", "count", "createdAt", "updatedAt"],
|
|
259
|
-
showProperties: ["id", "packageId", "periodType", "periodValue", "count", "createdAt", "updatedAt"],
|
|
260
|
-
filterProperties: ["packageId", "periodType", "periodValue", "createdAt", "updatedAt"],
|
|
261
|
-
sort: { sortBy: "updatedAt", direction: "desc" },
|
|
262
|
-
},
|
|
263
|
-
},
|
|
264
|
-
{
|
|
265
|
-
resource: ManifestViewStats,
|
|
266
|
-
options: {
|
|
267
|
-
actions: {
|
|
268
|
-
...defaultActions,
|
|
269
|
-
show: { after: AdminUI.populatePackageIdShowProperties },
|
|
270
|
-
list: { after: AdminUI.populatePackageIdListProperties },
|
|
271
|
-
search: { after: AdminUI.populatePackageIdSearchProperties },
|
|
272
|
-
},
|
|
273
|
-
properties: {
|
|
274
|
-
periodType: {
|
|
275
|
-
availableValues: PERIOD_TYPES.map((type) => ({ value: type, label: type })),
|
|
276
|
-
},
|
|
277
|
-
},
|
|
278
|
-
listProperties: ["id", "packageId", "periodType", "periodValue", "count", "createdAt", "updatedAt"],
|
|
279
|
-
showProperties: ["id", "packageId", "periodType", "periodValue", "count", "createdAt", "updatedAt"],
|
|
280
|
-
filterProperties: ["packageId", "periodType", "periodValue", "createdAt", "updatedAt"],
|
|
281
|
-
sort: { sortBy: "updatedAt", direction: "desc" },
|
|
282
|
-
},
|
|
283
|
-
},
|
|
284
|
-
],
|
|
285
|
-
rootPath: rootPath,
|
|
286
|
-
branding: {
|
|
287
|
-
companyName: this.config.title,
|
|
288
|
-
logo: this.config.logo,
|
|
289
|
-
},
|
|
290
|
-
env: {
|
|
291
|
-
ADMIN_JS_SKIP_BUNDLE: "true",
|
|
292
|
-
},
|
|
293
|
-
});
|
|
294
|
-
return AdminJSExpress.buildRouter(admin);
|
|
295
|
-
}
|
|
296
|
-
}
|
|
297
|
-
|
|
298
160
|
class Hooks {
|
|
299
161
|
config;
|
|
300
162
|
db = null;
|
|
@@ -406,6 +268,110 @@ class Stats {
|
|
|
406
268
|
};
|
|
407
269
|
}
|
|
408
270
|
|
|
271
|
+
class DownloadStats extends sequelize.Model {
|
|
272
|
+
}
|
|
273
|
+
class ManifestViewStats extends sequelize.Model {
|
|
274
|
+
}
|
|
275
|
+
class Package extends sequelize.Model {
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
const rootPath = wrapPath("/ui");
|
|
279
|
+
const defaultActions = {
|
|
280
|
+
new: { isAccessible: false },
|
|
281
|
+
edit: { isAccessible: false },
|
|
282
|
+
delete: { isAccessible: false },
|
|
283
|
+
bulkDelete: { isAccessible: false },
|
|
284
|
+
};
|
|
285
|
+
process.env.ADMIN_JS_SKIP_BUNDLE = "true";
|
|
286
|
+
/**
|
|
287
|
+
* Add Admin UI to the application.
|
|
288
|
+
*/
|
|
289
|
+
class UI {
|
|
290
|
+
adminRouter = null;
|
|
291
|
+
config;
|
|
292
|
+
constructor(config) {
|
|
293
|
+
this.config = config;
|
|
294
|
+
void this.create().then((router) => {
|
|
295
|
+
this.adminRouter = router;
|
|
296
|
+
});
|
|
297
|
+
}
|
|
298
|
+
register_middlewares(app) {
|
|
299
|
+
app.use(rootPath, (req, res, next) => {
|
|
300
|
+
if (this.adminRouter) {
|
|
301
|
+
return this.adminRouter(req, res, next);
|
|
302
|
+
}
|
|
303
|
+
else {
|
|
304
|
+
res.status(503).send("Admin UI is not ready yet.");
|
|
305
|
+
}
|
|
306
|
+
});
|
|
307
|
+
}
|
|
308
|
+
async create() {
|
|
309
|
+
const [AdminJS, AdminJSExpress, AdminJSSequelize] = await Promise.all([
|
|
310
|
+
import('adminjs').then((mod) => mod.default),
|
|
311
|
+
import('@adminjs/express').then((mod) => mod.default),
|
|
312
|
+
import('@adminjs/sequelize').then((mod) => mod.default),
|
|
313
|
+
]);
|
|
314
|
+
AdminJS.registerAdapter({
|
|
315
|
+
Resource: AdminJSSequelize.Resource,
|
|
316
|
+
Database: AdminJSSequelize.Database,
|
|
317
|
+
});
|
|
318
|
+
const admin = new AdminJS({
|
|
319
|
+
resources: [
|
|
320
|
+
{
|
|
321
|
+
resource: Package,
|
|
322
|
+
options: {
|
|
323
|
+
actions: { ...defaultActions },
|
|
324
|
+
titleProperty: "displayName",
|
|
325
|
+
listProperties: ["id", "name", "version", "createdAt"],
|
|
326
|
+
showProperties: ["id", "name", "version", "createdAt"],
|
|
327
|
+
filterProperties: ["name", "version"],
|
|
328
|
+
sort: { sortBy: "createdAt", direction: "desc" },
|
|
329
|
+
},
|
|
330
|
+
},
|
|
331
|
+
{
|
|
332
|
+
resource: DownloadStats,
|
|
333
|
+
options: {
|
|
334
|
+
actions: { ...defaultActions },
|
|
335
|
+
properties: {
|
|
336
|
+
periodType: {
|
|
337
|
+
availableValues: PERIOD_TYPES.map((type) => ({ value: type, label: type })),
|
|
338
|
+
},
|
|
339
|
+
},
|
|
340
|
+
listProperties: ["id", "packageId", "periodType", "periodValue", "count", "createdAt", "updatedAt"],
|
|
341
|
+
showProperties: ["id", "packageId", "periodType", "periodValue", "count", "createdAt", "updatedAt"],
|
|
342
|
+
filterProperties: ["packageId", "periodType", "periodValue", "createdAt", "updatedAt"],
|
|
343
|
+
sort: { sortBy: "updatedAt", direction: "desc" },
|
|
344
|
+
},
|
|
345
|
+
},
|
|
346
|
+
{
|
|
347
|
+
resource: ManifestViewStats,
|
|
348
|
+
options: {
|
|
349
|
+
actions: { ...defaultActions },
|
|
350
|
+
properties: {
|
|
351
|
+
periodType: {
|
|
352
|
+
availableValues: PERIOD_TYPES.map((type) => ({ value: type, label: type })),
|
|
353
|
+
},
|
|
354
|
+
},
|
|
355
|
+
listProperties: ["id", "packageId", "periodType", "periodValue", "count", "createdAt", "updatedAt"],
|
|
356
|
+
showProperties: ["id", "packageId", "periodType", "periodValue", "count", "createdAt", "updatedAt"],
|
|
357
|
+
filterProperties: ["packageId", "periodType", "periodValue", "createdAt", "updatedAt"],
|
|
358
|
+
sort: { sortBy: "updatedAt", direction: "desc" },
|
|
359
|
+
},
|
|
360
|
+
},
|
|
361
|
+
],
|
|
362
|
+
rootPath: rootPath,
|
|
363
|
+
branding: {
|
|
364
|
+
companyName: this.config.title,
|
|
365
|
+
logo: this.config.logo,
|
|
366
|
+
},
|
|
367
|
+
env: {
|
|
368
|
+
ADMIN_JS_SKIP_BUNDLE: "true",
|
|
369
|
+
},
|
|
370
|
+
});
|
|
371
|
+
return AdminJSExpress.buildRouter(admin);
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
|
|
409
375
|
const debug = buildDebug(`verdaccio:plugin:${pluginKey}`);
|
|
410
376
|
function getUmzugLogger() {
|
|
411
377
|
return {
|
|
@@ -609,6 +575,15 @@ class Database {
|
|
|
609
575
|
id: { allowNull: false, autoIncrement: true, primaryKey: true, type: sequelize.DataTypes.INTEGER },
|
|
610
576
|
name: { allowNull: false, type: sequelize.DataTypes.STRING(100) },
|
|
611
577
|
version: { allowNull: false, type: sequelize.DataTypes.STRING(50) },
|
|
578
|
+
displayName: {
|
|
579
|
+
type: sequelize.DataTypes.VIRTUAL,
|
|
580
|
+
get() {
|
|
581
|
+
return `${this.name}@${this.version}`;
|
|
582
|
+
},
|
|
583
|
+
set() {
|
|
584
|
+
throw new Error("Virtual property, cannot be set");
|
|
585
|
+
},
|
|
586
|
+
},
|
|
612
587
|
}, { sequelize: this.sequelize, tableName: "packages", underscored: true }),
|
|
613
588
|
DownloadStats.init({
|
|
614
589
|
count: { allowNull: false, type: sequelize.DataTypes.BIGINT, defaultValue: 0 },
|
|
@@ -666,7 +641,7 @@ class Plugin {
|
|
|
666
641
|
const db = Database.create(this.parsedConfig);
|
|
667
642
|
const hooks = new Hooks(this.parsedConfig);
|
|
668
643
|
const stats = new Stats(this.parsedConfig);
|
|
669
|
-
const
|
|
644
|
+
const ui = new UI(this.parsedConfig);
|
|
670
645
|
db.then((db) => {
|
|
671
646
|
hooks.setDatabase(db);
|
|
672
647
|
stats.setDatabase(db);
|
|
@@ -674,7 +649,7 @@ class Plugin {
|
|
|
674
649
|
logger.error({ err }, "Failed to initialize database; @{err}");
|
|
675
650
|
process.exit(1);
|
|
676
651
|
});
|
|
677
|
-
for (const middleware of [hooks, stats,
|
|
652
|
+
for (const middleware of [hooks, stats, ui]) {
|
|
678
653
|
middleware.register_middlewares(app);
|
|
679
654
|
}
|
|
680
655
|
}
|
package/lib/index.mjs
CHANGED
|
@@ -5,14 +5,14 @@ import isoWeek from 'dayjs/plugin/isoWeek';
|
|
|
5
5
|
import weekOfYear from 'dayjs/plugin/weekOfYear';
|
|
6
6
|
import weekYear from 'dayjs/plugin/weekYear';
|
|
7
7
|
import path from 'node:path';
|
|
8
|
-
import { Model, DataTypes, Sequelize } from 'sequelize';
|
|
9
8
|
import { tarballUtils } from '@verdaccio/core';
|
|
10
9
|
import { PACKAGE_API_ENDPOINTS } from '@verdaccio/middleware';
|
|
10
|
+
import { Model, DataTypes, Sequelize } from 'sequelize';
|
|
11
11
|
import buildDebug from 'debug';
|
|
12
12
|
import { Umzug, SequelizeStorage } from 'umzug';
|
|
13
13
|
|
|
14
14
|
var name = "verdaccio-stats";
|
|
15
|
-
var version = "0.
|
|
15
|
+
var version = "0.2.0";
|
|
16
16
|
|
|
17
17
|
const plugin = {
|
|
18
18
|
name,
|
|
@@ -153,144 +153,6 @@ class ParsedPluginConfig {
|
|
|
153
153
|
}
|
|
154
154
|
}
|
|
155
155
|
|
|
156
|
-
class DownloadStats extends Model {
|
|
157
|
-
}
|
|
158
|
-
class ManifestViewStats extends Model {
|
|
159
|
-
}
|
|
160
|
-
class Package extends Model {
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
const rootPath = wrapPath("/admin");
|
|
164
|
-
const defaultActions = {
|
|
165
|
-
new: { isAccessible: false },
|
|
166
|
-
edit: { isAccessible: false },
|
|
167
|
-
delete: { isAccessible: false },
|
|
168
|
-
bulkDelete: { isAccessible: false },
|
|
169
|
-
};
|
|
170
|
-
process.env.ADMIN_JS_SKIP_BUNDLE = "true";
|
|
171
|
-
/**
|
|
172
|
-
* Add Admin UI to the application.
|
|
173
|
-
*/
|
|
174
|
-
class AdminUI {
|
|
175
|
-
adminRouter = null;
|
|
176
|
-
config;
|
|
177
|
-
constructor(config) {
|
|
178
|
-
this.config = config;
|
|
179
|
-
void this.create().then((router) => {
|
|
180
|
-
this.adminRouter = router;
|
|
181
|
-
});
|
|
182
|
-
}
|
|
183
|
-
static populatePackageIdListProperties(response) {
|
|
184
|
-
for (const record of response.records) {
|
|
185
|
-
if (record.populated.packageId?.params) {
|
|
186
|
-
const params = record.populated.packageId.params;
|
|
187
|
-
record.populated.packageId.title = `${params.name}@${params.version}`;
|
|
188
|
-
}
|
|
189
|
-
}
|
|
190
|
-
return response;
|
|
191
|
-
}
|
|
192
|
-
static populatePackageIdSearchProperties(response) {
|
|
193
|
-
for (const record of response.records) {
|
|
194
|
-
if (record.populated.packageId?.params) {
|
|
195
|
-
const params = record.populated.packageId.params;
|
|
196
|
-
record.populated.packageId.title = `${params.name}@${params.version}`;
|
|
197
|
-
}
|
|
198
|
-
}
|
|
199
|
-
return response;
|
|
200
|
-
}
|
|
201
|
-
static populatePackageIdShowProperties(response) {
|
|
202
|
-
if (response.record.populated.packageId?.params) {
|
|
203
|
-
const params = response.record.populated.packageId.params;
|
|
204
|
-
response.record.populated.packageId.title = `${params.name}@${params.version}`;
|
|
205
|
-
}
|
|
206
|
-
return response;
|
|
207
|
-
}
|
|
208
|
-
register_middlewares(app) {
|
|
209
|
-
app.use(rootPath, (req, res, next) => {
|
|
210
|
-
if (this.adminRouter) {
|
|
211
|
-
return this.adminRouter(req, res, next);
|
|
212
|
-
}
|
|
213
|
-
else {
|
|
214
|
-
res.status(503).send("Admin UI is not ready yet.");
|
|
215
|
-
}
|
|
216
|
-
});
|
|
217
|
-
}
|
|
218
|
-
async create() {
|
|
219
|
-
const [AdminJS, AdminJSExpress, AdminJSSequelize] = await Promise.all([
|
|
220
|
-
import('adminjs').then((mod) => mod.default),
|
|
221
|
-
import('@adminjs/express').then((mod) => mod.default),
|
|
222
|
-
import('@adminjs/sequelize').then((mod) => mod.default),
|
|
223
|
-
]);
|
|
224
|
-
AdminJS.registerAdapter({
|
|
225
|
-
Resource: AdminJSSequelize.Resource,
|
|
226
|
-
Database: AdminJSSequelize.Database,
|
|
227
|
-
});
|
|
228
|
-
const admin = new AdminJS({
|
|
229
|
-
resources: [
|
|
230
|
-
{
|
|
231
|
-
resource: Package,
|
|
232
|
-
options: {
|
|
233
|
-
actions: { ...defaultActions },
|
|
234
|
-
listProperties: ["id", "name", "version", "createdAt"],
|
|
235
|
-
showProperties: ["id", "name", "version", "createdAt"],
|
|
236
|
-
filterProperties: ["name", "version"],
|
|
237
|
-
sort: { sortBy: "createdAt", direction: "desc" },
|
|
238
|
-
},
|
|
239
|
-
},
|
|
240
|
-
{
|
|
241
|
-
resource: DownloadStats,
|
|
242
|
-
options: {
|
|
243
|
-
actions: {
|
|
244
|
-
...defaultActions,
|
|
245
|
-
show: { after: AdminUI.populatePackageIdShowProperties },
|
|
246
|
-
list: { after: AdminUI.populatePackageIdListProperties },
|
|
247
|
-
search: { after: AdminUI.populatePackageIdSearchProperties },
|
|
248
|
-
},
|
|
249
|
-
properties: {
|
|
250
|
-
periodType: {
|
|
251
|
-
availableValues: PERIOD_TYPES.map((type) => ({ value: type, label: type })),
|
|
252
|
-
},
|
|
253
|
-
},
|
|
254
|
-
listProperties: ["id", "packageId", "periodType", "periodValue", "count", "createdAt", "updatedAt"],
|
|
255
|
-
showProperties: ["id", "packageId", "periodType", "periodValue", "count", "createdAt", "updatedAt"],
|
|
256
|
-
filterProperties: ["packageId", "periodType", "periodValue", "createdAt", "updatedAt"],
|
|
257
|
-
sort: { sortBy: "updatedAt", direction: "desc" },
|
|
258
|
-
},
|
|
259
|
-
},
|
|
260
|
-
{
|
|
261
|
-
resource: ManifestViewStats,
|
|
262
|
-
options: {
|
|
263
|
-
actions: {
|
|
264
|
-
...defaultActions,
|
|
265
|
-
show: { after: AdminUI.populatePackageIdShowProperties },
|
|
266
|
-
list: { after: AdminUI.populatePackageIdListProperties },
|
|
267
|
-
search: { after: AdminUI.populatePackageIdSearchProperties },
|
|
268
|
-
},
|
|
269
|
-
properties: {
|
|
270
|
-
periodType: {
|
|
271
|
-
availableValues: PERIOD_TYPES.map((type) => ({ value: type, label: type })),
|
|
272
|
-
},
|
|
273
|
-
},
|
|
274
|
-
listProperties: ["id", "packageId", "periodType", "periodValue", "count", "createdAt", "updatedAt"],
|
|
275
|
-
showProperties: ["id", "packageId", "periodType", "periodValue", "count", "createdAt", "updatedAt"],
|
|
276
|
-
filterProperties: ["packageId", "periodType", "periodValue", "createdAt", "updatedAt"],
|
|
277
|
-
sort: { sortBy: "updatedAt", direction: "desc" },
|
|
278
|
-
},
|
|
279
|
-
},
|
|
280
|
-
],
|
|
281
|
-
rootPath: rootPath,
|
|
282
|
-
branding: {
|
|
283
|
-
companyName: this.config.title,
|
|
284
|
-
logo: this.config.logo,
|
|
285
|
-
},
|
|
286
|
-
env: {
|
|
287
|
-
ADMIN_JS_SKIP_BUNDLE: "true",
|
|
288
|
-
},
|
|
289
|
-
});
|
|
290
|
-
return AdminJSExpress.buildRouter(admin);
|
|
291
|
-
}
|
|
292
|
-
}
|
|
293
|
-
|
|
294
156
|
class Hooks {
|
|
295
157
|
config;
|
|
296
158
|
db = null;
|
|
@@ -402,6 +264,110 @@ class Stats {
|
|
|
402
264
|
};
|
|
403
265
|
}
|
|
404
266
|
|
|
267
|
+
class DownloadStats extends Model {
|
|
268
|
+
}
|
|
269
|
+
class ManifestViewStats extends Model {
|
|
270
|
+
}
|
|
271
|
+
class Package extends Model {
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
const rootPath = wrapPath("/ui");
|
|
275
|
+
const defaultActions = {
|
|
276
|
+
new: { isAccessible: false },
|
|
277
|
+
edit: { isAccessible: false },
|
|
278
|
+
delete: { isAccessible: false },
|
|
279
|
+
bulkDelete: { isAccessible: false },
|
|
280
|
+
};
|
|
281
|
+
process.env.ADMIN_JS_SKIP_BUNDLE = "true";
|
|
282
|
+
/**
|
|
283
|
+
* Add Admin UI to the application.
|
|
284
|
+
*/
|
|
285
|
+
class UI {
|
|
286
|
+
adminRouter = null;
|
|
287
|
+
config;
|
|
288
|
+
constructor(config) {
|
|
289
|
+
this.config = config;
|
|
290
|
+
void this.create().then((router) => {
|
|
291
|
+
this.adminRouter = router;
|
|
292
|
+
});
|
|
293
|
+
}
|
|
294
|
+
register_middlewares(app) {
|
|
295
|
+
app.use(rootPath, (req, res, next) => {
|
|
296
|
+
if (this.adminRouter) {
|
|
297
|
+
return this.adminRouter(req, res, next);
|
|
298
|
+
}
|
|
299
|
+
else {
|
|
300
|
+
res.status(503).send("Admin UI is not ready yet.");
|
|
301
|
+
}
|
|
302
|
+
});
|
|
303
|
+
}
|
|
304
|
+
async create() {
|
|
305
|
+
const [AdminJS, AdminJSExpress, AdminJSSequelize] = await Promise.all([
|
|
306
|
+
import('adminjs').then((mod) => mod.default),
|
|
307
|
+
import('@adminjs/express').then((mod) => mod.default),
|
|
308
|
+
import('@adminjs/sequelize').then((mod) => mod.default),
|
|
309
|
+
]);
|
|
310
|
+
AdminJS.registerAdapter({
|
|
311
|
+
Resource: AdminJSSequelize.Resource,
|
|
312
|
+
Database: AdminJSSequelize.Database,
|
|
313
|
+
});
|
|
314
|
+
const admin = new AdminJS({
|
|
315
|
+
resources: [
|
|
316
|
+
{
|
|
317
|
+
resource: Package,
|
|
318
|
+
options: {
|
|
319
|
+
actions: { ...defaultActions },
|
|
320
|
+
titleProperty: "displayName",
|
|
321
|
+
listProperties: ["id", "name", "version", "createdAt"],
|
|
322
|
+
showProperties: ["id", "name", "version", "createdAt"],
|
|
323
|
+
filterProperties: ["name", "version"],
|
|
324
|
+
sort: { sortBy: "createdAt", direction: "desc" },
|
|
325
|
+
},
|
|
326
|
+
},
|
|
327
|
+
{
|
|
328
|
+
resource: DownloadStats,
|
|
329
|
+
options: {
|
|
330
|
+
actions: { ...defaultActions },
|
|
331
|
+
properties: {
|
|
332
|
+
periodType: {
|
|
333
|
+
availableValues: PERIOD_TYPES.map((type) => ({ value: type, label: type })),
|
|
334
|
+
},
|
|
335
|
+
},
|
|
336
|
+
listProperties: ["id", "packageId", "periodType", "periodValue", "count", "createdAt", "updatedAt"],
|
|
337
|
+
showProperties: ["id", "packageId", "periodType", "periodValue", "count", "createdAt", "updatedAt"],
|
|
338
|
+
filterProperties: ["packageId", "periodType", "periodValue", "createdAt", "updatedAt"],
|
|
339
|
+
sort: { sortBy: "updatedAt", direction: "desc" },
|
|
340
|
+
},
|
|
341
|
+
},
|
|
342
|
+
{
|
|
343
|
+
resource: ManifestViewStats,
|
|
344
|
+
options: {
|
|
345
|
+
actions: { ...defaultActions },
|
|
346
|
+
properties: {
|
|
347
|
+
periodType: {
|
|
348
|
+
availableValues: PERIOD_TYPES.map((type) => ({ value: type, label: type })),
|
|
349
|
+
},
|
|
350
|
+
},
|
|
351
|
+
listProperties: ["id", "packageId", "periodType", "periodValue", "count", "createdAt", "updatedAt"],
|
|
352
|
+
showProperties: ["id", "packageId", "periodType", "periodValue", "count", "createdAt", "updatedAt"],
|
|
353
|
+
filterProperties: ["packageId", "periodType", "periodValue", "createdAt", "updatedAt"],
|
|
354
|
+
sort: { sortBy: "updatedAt", direction: "desc" },
|
|
355
|
+
},
|
|
356
|
+
},
|
|
357
|
+
],
|
|
358
|
+
rootPath: rootPath,
|
|
359
|
+
branding: {
|
|
360
|
+
companyName: this.config.title,
|
|
361
|
+
logo: this.config.logo,
|
|
362
|
+
},
|
|
363
|
+
env: {
|
|
364
|
+
ADMIN_JS_SKIP_BUNDLE: "true",
|
|
365
|
+
},
|
|
366
|
+
});
|
|
367
|
+
return AdminJSExpress.buildRouter(admin);
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
|
|
405
371
|
const debug = buildDebug(`verdaccio:plugin:${pluginKey}`);
|
|
406
372
|
function getUmzugLogger() {
|
|
407
373
|
return {
|
|
@@ -605,6 +571,15 @@ class Database {
|
|
|
605
571
|
id: { allowNull: false, autoIncrement: true, primaryKey: true, type: DataTypes.INTEGER },
|
|
606
572
|
name: { allowNull: false, type: DataTypes.STRING(100) },
|
|
607
573
|
version: { allowNull: false, type: DataTypes.STRING(50) },
|
|
574
|
+
displayName: {
|
|
575
|
+
type: DataTypes.VIRTUAL,
|
|
576
|
+
get() {
|
|
577
|
+
return `${this.name}@${this.version}`;
|
|
578
|
+
},
|
|
579
|
+
set() {
|
|
580
|
+
throw new Error("Virtual property, cannot be set");
|
|
581
|
+
},
|
|
582
|
+
},
|
|
608
583
|
}, { sequelize: this.sequelize, tableName: "packages", underscored: true }),
|
|
609
584
|
DownloadStats.init({
|
|
610
585
|
count: { allowNull: false, type: DataTypes.BIGINT, defaultValue: 0 },
|
|
@@ -662,7 +637,7 @@ class Plugin {
|
|
|
662
637
|
const db = Database.create(this.parsedConfig);
|
|
663
638
|
const hooks = new Hooks(this.parsedConfig);
|
|
664
639
|
const stats = new Stats(this.parsedConfig);
|
|
665
|
-
const
|
|
640
|
+
const ui = new UI(this.parsedConfig);
|
|
666
641
|
db.then((db) => {
|
|
667
642
|
hooks.setDatabase(db);
|
|
668
643
|
stats.setDatabase(db);
|
|
@@ -670,7 +645,7 @@ class Plugin {
|
|
|
670
645
|
logger.error({ err }, "Failed to initialize database; @{err}");
|
|
671
646
|
process.exit(1);
|
|
672
647
|
});
|
|
673
|
-
for (const middleware of [hooks, stats,
|
|
648
|
+
for (const middleware of [hooks, stats, ui]) {
|
|
674
649
|
middleware.register_middlewares(app);
|
|
675
650
|
}
|
|
676
651
|
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { Application } from "express";
|
|
2
|
+
import type { ConfigHolder } from "../config";
|
|
3
|
+
import type { PluginMiddleware } from "../types";
|
|
4
|
+
/**
|
|
5
|
+
* Add Admin UI to the application.
|
|
6
|
+
*/
|
|
7
|
+
export declare class UI implements PluginMiddleware {
|
|
8
|
+
private adminRouter;
|
|
9
|
+
private config;
|
|
10
|
+
constructor(config: ConfigHolder);
|
|
11
|
+
register_middlewares(app: Application): void;
|
|
12
|
+
private create;
|
|
13
|
+
}
|
package/lib/models.d.ts
CHANGED
|
@@ -17,6 +17,7 @@ export declare class ManifestViewStats extends Model<InferAttributes<ManifestVie
|
|
|
17
17
|
periodValue: PeriodValue;
|
|
18
18
|
}
|
|
19
19
|
export declare class Package extends Model<InferAttributes<Package>, InferCreationAttributes<Package>> {
|
|
20
|
+
readonly displayName: string;
|
|
20
21
|
id: CreationOptional<number>;
|
|
21
22
|
name: string;
|
|
22
23
|
version: string;
|
package/package.json
CHANGED
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
import type { ListActionResponse, RecordActionResponse, SearchActionResponse } from "adminjs";
|
|
2
|
-
import type { Application } from "express";
|
|
3
|
-
import type { ConfigHolder } from "../config";
|
|
4
|
-
import type { PluginMiddleware } from "../types";
|
|
5
|
-
/**
|
|
6
|
-
* Add Admin UI to the application.
|
|
7
|
-
*/
|
|
8
|
-
export declare class AdminUI implements PluginMiddleware {
|
|
9
|
-
private adminRouter;
|
|
10
|
-
private config;
|
|
11
|
-
constructor(config: ConfigHolder);
|
|
12
|
-
static populatePackageIdListProperties(this: void, response: ListActionResponse): ListActionResponse;
|
|
13
|
-
static populatePackageIdSearchProperties(this: void, response: SearchActionResponse): SearchActionResponse;
|
|
14
|
-
static populatePackageIdShowProperties(this: void, response: RecordActionResponse): RecordActionResponse;
|
|
15
|
-
register_middlewares(app: Application): void;
|
|
16
|
-
private create;
|
|
17
|
-
}
|