mirror of
https://github.com/linux-odyssey/linux-odyssey.git
synced 2026-11-05 19:20:12 +08:00
Merge pull request #34 from linux-odyssey/feat-analytics
feat: analytics
This commit is contained in:
31 files changed
+788
-17
No files matched your search
@@ -80,6 +80,14 @@ jobs:
|
||||
push: true
|
||||
tags: docker.io/${{ secrets.DOCKERHUB_USERNAME }}/linux-odyssey:app
|
||||
|
||||
- name: Build analytics
|
||||
uses: docker/build-push-action@v2
|
||||
with:
|
||||
context: .
|
||||
file: ./packages/analytics/Dockerfile
|
||||
push: true
|
||||
tags: docker.io/${{ secrets.DOCKERHUB_USERNAME }}/linux-odyssey:analytics
|
||||
|
||||
- name: Build base image
|
||||
uses: docker/build-push-action@v2
|
||||
with:
|
||||
|
||||
@@ -25,6 +25,21 @@ services:
|
||||
environment:
|
||||
- MONGO_URL=mongodb://user:user-password@db:27017/odyssey-test
|
||||
- DOCKER_NETWORK=linux-odyssey-players
|
||||
|
||||
analytics:
|
||||
image: lancatlin/linux-odyssey:analytics
|
||||
build:
|
||||
context: .
|
||||
dockerfile: packages/analytics/Dockerfile
|
||||
depends_on:
|
||||
- db
|
||||
networks:
|
||||
- internal
|
||||
ports:
|
||||
- '3001:3001'
|
||||
environment:
|
||||
- MONGO_URL=mongodb://user:user-password@db:27017/odyssey-test
|
||||
|
||||
db:
|
||||
image: mongo
|
||||
restart: always
|
||||
|
||||
@@ -39,6 +39,23 @@ services:
|
||||
- GOOGLE_CLIENT_SECRET=${GOOGLE_CLIENT_SECRET}
|
||||
- GITHUB_CLIENT_ID=${GITHUB_CLIENT_ID}
|
||||
- GITHUB_CLIENT_SECRET=${GITHUB_CLIENT_SECRET}
|
||||
|
||||
analytics:
|
||||
build:
|
||||
context: .
|
||||
dockerfile: packages/analytics/Dockerfile.dev
|
||||
depends_on:
|
||||
- db
|
||||
networks:
|
||||
- internal
|
||||
ports:
|
||||
- '3001:3001'
|
||||
environment:
|
||||
- MONGO_URL=mongodb://db:27017/odyssey-test
|
||||
volumes:
|
||||
- ./packages/analytics:/app/packages/analytics
|
||||
- ./packages/models:/app/packages/models
|
||||
|
||||
db:
|
||||
image: mongo
|
||||
networks:
|
||||
|
||||
@@ -21,6 +21,7 @@
|
||||
"format": "prettier --ignore-path .prettierignore --write app server",
|
||||
"test": "yarn node --experimental-vm-modules $(yarn bin jest)",
|
||||
"test:e2e": "yarn workspace tests test",
|
||||
"analytics": "yarn workspace analytics dev",
|
||||
"clean": "yarn workspace server clean"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
FROM node
|
||||
|
||||
WORKDIR /app
|
||||
COPY ./package.json yarn.lock ./
|
||||
COPY packages/analytics/package.json ./packages/analytics/package.json
|
||||
COPY packages/models/package.json ./packages/models/package.json
|
||||
RUN yarn install --frozen-lockfile --production
|
||||
|
||||
COPY packages/analytics ./packages/analytics
|
||||
COPY packages/models ./packages/models
|
||||
|
||||
WORKDIR /app/packages/analytics
|
||||
|
||||
ENV NODE_ENV=production
|
||||
ENV HOST=0.0.0.0
|
||||
ENV PORT=3001
|
||||
EXPOSE 3001
|
||||
|
||||
CMD [ "yarn", "start" ]
|
||||
@@ -0,0 +1,16 @@
|
||||
FROM node
|
||||
|
||||
WORKDIR /app
|
||||
COPY ./package.json yarn.lock ./
|
||||
COPY packages/analytics/package.json ./packages/analytics/package.json
|
||||
COPY packages/models/package.json ./packages/models/package.json
|
||||
RUN yarn install --frozen-lockfile
|
||||
|
||||
WORKDIR /app/packages/analytics
|
||||
|
||||
ENV NODE_ENV=development
|
||||
ENV HOST=0.0.0.0
|
||||
ENV PORT=3001
|
||||
EXPOSE 3001
|
||||
|
||||
CMD [ "yarn", "dev" ]
|
||||
@@ -0,0 +1,59 @@
|
||||
# Analytics
|
||||
|
||||
## Information provided
|
||||
|
||||
### 1. **Dashboard Overview**:
|
||||
|
||||
- **Total Users**: Display the total number of registered users.
|
||||
- **Active Users**: Count of users who've been active in the past day, week, or month.
|
||||
- **Completed Quests**: Total quests or challenges completed by all users.
|
||||
- **Average Time Usage**: Average time a user spends on the platform or on challenges.
|
||||
|
||||
### 2. **User Analytics**:
|
||||
|
||||
- **User Growth**: A graph showing user registrations over time (daily, weekly, monthly).
|
||||
- **Retention Rate**: Percentage of users who return after their first visit.
|
||||
- **User Segmentation**: Group users based on criteria like their progress level, frequency of usage, or other custom metrics.
|
||||
|
||||
### 3. **Quest/Challenge Analytics**:
|
||||
|
||||
- **Quest Popularity**: Rank quests by the number of completions or engagements.
|
||||
- **Average Completion Time**: The average time taken by users to complete each quest.
|
||||
- **Drop-Off Points**: Identify quests where users abandon or fail frequently.
|
||||
|
||||
### 4. **Error Analysis**:
|
||||
|
||||
- **Common Errors**: List frequently mistyped or erroneous commands.
|
||||
- **Quest-specific Errors**: Identify challenges where users make the most mistakes.
|
||||
|
||||
### 5. **Feedback & Survey Results**:
|
||||
|
||||
- **Feedback Overview**: Display recent feedback or suggestions.
|
||||
- **Survey Analysis**: If you've integrated a survey, show aggregated results or key insights.
|
||||
|
||||
### 6. **System Health & Usage**:
|
||||
|
||||
- **Server Load**: Monitor server performance metrics like CPU usage, memory usage, etc.
|
||||
- **Error Logs**: View any system errors, glitches, or issues.
|
||||
|
||||
### 7. **User Details**:
|
||||
|
||||
- **Search & View**: Ability to search for specific users and view their profile, progress, and activity logs.
|
||||
- **User Behavior**: For selected users, see a detailed path of their activity, including the quests they've engaged with, time spent, errors made, etc.
|
||||
|
||||
### 8. **Notifications & Alerts**:
|
||||
|
||||
- **System Alerts**: Notifications about server downtimes, high error rates, or other critical issues.
|
||||
- **User Activity Alerts**: Set alerts for specific user activities, e.g., when a user completes all challenges.
|
||||
|
||||
### 9. **Engagement Metrics**:
|
||||
|
||||
- **Daily Active Users**: Users who engage with the platform daily.
|
||||
- **Top Users**: Identify power users or those with high engagement rates.
|
||||
|
||||
### 10. **Export & Reports**:
|
||||
|
||||
- **Data Export**: Option to export user data, analytics, or other metrics.
|
||||
- **Generate Reports**: Create detailed reports based on selected metrics, useful for team meetings or strategy planning.
|
||||
|
||||
When designing this admin dashboard, focus on clarity and ease of use. Utilize visual aids like graphs, charts, and heatmaps to make data interpretation straightforward. Since your backend is built with NodeJS and ExpressJS and you use MongoDB for data storage, you can utilize various NPM packages to aid in data visualization and dashboard creation. Ensure the dashboard is secure and accessible only to authorized personnel.
|
||||
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"ext": "js,handlebars"
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
{
|
||||
"name": "analytics",
|
||||
"version": "1.0.0",
|
||||
"main": "src/index.js",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"license": "GPL",
|
||||
"scripts": {
|
||||
"start": "node src/index.js",
|
||||
"dev": "nodemon src/index.js"
|
||||
},
|
||||
"dependencies": {
|
||||
"@linux-odyssey/models": "*",
|
||||
"express": "^4.18.2",
|
||||
"express-handlebars": "^7.1.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"nodemon": "^3.0.1"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
body {
|
||||
font-family: 'sans';
|
||||
}
|
||||
|
||||
table,
|
||||
tr,
|
||||
td,
|
||||
th {
|
||||
border-collapse: collapse;
|
||||
border-spacing: 0;
|
||||
width: 100%;
|
||||
border: 2px solid black;
|
||||
}
|
||||
|
||||
th,
|
||||
td {
|
||||
width: 1px;
|
||||
white-space: nowrap;
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
const config = {
|
||||
host: process.env.HOST || 'localhost',
|
||||
port: process.env.PORT || 3001,
|
||||
db: process.env.MONGO_URL || 'mongodb://localhost:27017/odyssey-test',
|
||||
}
|
||||
|
||||
config.baseUrl = process.env.BASE_URL || `http://${config.host}:${config.port}`
|
||||
|
||||
export default config
|
||||
@@ -0,0 +1,9 @@
|
||||
import { errorCommands } from '../models/commands.js'
|
||||
|
||||
// eslint-disable-next-line import/prefer-default-export
|
||||
export async function commandListController(req, res) {
|
||||
const payload = {
|
||||
errorCommands: await errorCommands(),
|
||||
}
|
||||
res.render('commands', payload)
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
import { userCount } from '../models/users.js'
|
||||
import { sessionCount, sessionCompleted } from '../models/sessions.js'
|
||||
|
||||
// eslint-disable-next-line import/prefer-default-export
|
||||
export async function homeController(req, res) {
|
||||
const payload = {
|
||||
title: 'Linux Odyssey Admin',
|
||||
userCount: await userCount(),
|
||||
sessionCount: await sessionCount(),
|
||||
sessionCompleted: await sessionCompleted(),
|
||||
}
|
||||
payload.completedRate =
|
||||
Math.round((payload.sessionCompleted / payload.sessionCount) * 10000) / 100
|
||||
res.render('home', payload)
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
import { sessionList, sessionDetail } from '../models/sessions.js'
|
||||
|
||||
export async function sessionListController(req, res) {
|
||||
const sessions = await sessionList()
|
||||
res.render('sessions', { sessions })
|
||||
}
|
||||
|
||||
export async function sessionDetailController(req, res) {
|
||||
const { id } = req.params
|
||||
const session = await sessionDetail(id)
|
||||
res.render('sessionDetail', { session })
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
import { userList } from '../models/users.js'
|
||||
|
||||
// eslint-disable-next-line import/prefer-default-export
|
||||
export async function userListController(req, res) {
|
||||
res.render('users', { users: await userList() })
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
import connectDB from '@linux-odyssey/models'
|
||||
import express from 'express'
|
||||
import { engine } from 'express-handlebars'
|
||||
import config from './config.js'
|
||||
import router from './router.js'
|
||||
|
||||
async function main() {
|
||||
await connectDB(config.db)
|
||||
const app = express()
|
||||
|
||||
// Setting up Handlebars as templating engine
|
||||
app.engine('handlebars', engine())
|
||||
app.set('view engine', 'handlebars')
|
||||
app.set('views', './views')
|
||||
|
||||
app.use(router)
|
||||
app.use(express.static('./public'))
|
||||
|
||||
app.listen(config.port, config.host, () => {
|
||||
console.log(`Server started on ${config.baseUrl}`)
|
||||
})
|
||||
}
|
||||
|
||||
main().catch(console.error)
|
||||
@@ -0,0 +1,28 @@
|
||||
import { Command } from '@linux-odyssey/models'
|
||||
|
||||
// eslint-disable-next-line import/prefer-default-export
|
||||
export async function errorCommands() {
|
||||
const commands = await Command.find({
|
||||
$and: [
|
||||
{ error: { $exists: true } },
|
||||
{ error: { $ne: null } },
|
||||
{ error: { $ne: '' } },
|
||||
],
|
||||
})
|
||||
.populate({
|
||||
path: 'session',
|
||||
populate: { path: 'user' },
|
||||
})
|
||||
.exec()
|
||||
return commands.map(({ command, error, createdAt, session }) => {
|
||||
const { user, quest } = session
|
||||
return {
|
||||
command: command.slice(0, 20),
|
||||
error: error.slice(0, 50),
|
||||
createdAt: createdAt?.toLocaleString(),
|
||||
user: user?.username,
|
||||
quest,
|
||||
session: session._id,
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,91 @@
|
||||
import { Command, Session } from '@linux-odyssey/models'
|
||||
|
||||
export function sessionCount() {
|
||||
return Session.find().count()
|
||||
}
|
||||
|
||||
export function sessionCompleted() {
|
||||
return Session.find({ status: 'finished' }).count()
|
||||
}
|
||||
|
||||
export function averageTimeUsage() {
|
||||
return Session.aggregate([
|
||||
{
|
||||
$group: {
|
||||
_id: '$quest',
|
||||
average: {
|
||||
$avg: {
|
||||
$subtract: ['$finishedAt', '$createdAt'],
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
])
|
||||
}
|
||||
|
||||
function formatTime(time) {
|
||||
const seconds = Math.floor(time / 1000)
|
||||
const minutes = Math.floor(seconds / 60)
|
||||
const hours = Math.floor(minutes / 60)
|
||||
return `${hours}h ${minutes % 60}m ${seconds % 60}s`
|
||||
}
|
||||
|
||||
export async function sessionList() {
|
||||
const sessions = await Session.aggregate([
|
||||
{
|
||||
$lookup: {
|
||||
from: 'users',
|
||||
localField: 'user',
|
||||
foreignField: '_id',
|
||||
as: 'user',
|
||||
},
|
||||
},
|
||||
{ $unwind: '$user' },
|
||||
{
|
||||
$lookup: {
|
||||
from: 'commands',
|
||||
localField: '_id',
|
||||
foreignField: 'session',
|
||||
as: 'commands',
|
||||
},
|
||||
},
|
||||
])
|
||||
return sessions.map(
|
||||
({ _id, user, quest, status, createdAt, finishedAt, commands }) => {
|
||||
return {
|
||||
_id,
|
||||
user: user.username,
|
||||
quest,
|
||||
status,
|
||||
createdAt: createdAt?.toLocaleString(),
|
||||
finishedAt: finishedAt?.toLocaleString(),
|
||||
usedTime: finishedAt ? formatTime(finishedAt - createdAt) : '',
|
||||
commandCount: commands?.length,
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
export async function sessionDetail(id) {
|
||||
const session = await Session.findById(id).populate('user').exec()
|
||||
const commands = (await Command.find({ session: id })).map(
|
||||
({ command, pwd, output, error, createdAt }) => ({
|
||||
command: command?.slice(0, 20),
|
||||
pwd,
|
||||
output: output?.slice(0, 50),
|
||||
error: error?.slice(0, 50),
|
||||
createdAt: createdAt?.toLocaleString(),
|
||||
})
|
||||
)
|
||||
const { _id, user, quest, status, createdAt, finishedAt } = session
|
||||
return {
|
||||
_id,
|
||||
user: user.username,
|
||||
quest,
|
||||
status,
|
||||
createdAt: createdAt?.toLocaleString(),
|
||||
finishedAt: finishedAt?.toLocaleString(),
|
||||
usedTime: finishedAt ? formatTime(finishedAt - createdAt) : '',
|
||||
commands,
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
import { Session, User } from '@linux-odyssey/models'
|
||||
|
||||
function loginMethods(user) {
|
||||
const methods = []
|
||||
if (user.hashedPassword) methods.push('password')
|
||||
if (user.google?.id) methods.push('google')
|
||||
if (user.github?.id) methods.push('github')
|
||||
return methods
|
||||
}
|
||||
|
||||
export function userCount() {
|
||||
return User.find().count()
|
||||
}
|
||||
|
||||
export async function userList() {
|
||||
const users = await Session.aggregate([
|
||||
{
|
||||
$group: {
|
||||
_id: '$user',
|
||||
count: { $sum: 1 },
|
||||
lastActivityAt: { $max: '$lastActivityAt' },
|
||||
},
|
||||
},
|
||||
{
|
||||
$lookup: {
|
||||
from: 'users',
|
||||
localField: '_id',
|
||||
foreignField: '_id',
|
||||
as: 'userData',
|
||||
},
|
||||
},
|
||||
{ $unwind: '$userData' },
|
||||
{ $sort: { 'userData.createdAt': 1 } },
|
||||
])
|
||||
return users.map(({ userData, count, lastActivityAt }) => {
|
||||
const { username, email, createdAt } = userData
|
||||
return {
|
||||
username,
|
||||
email,
|
||||
createdAt: createdAt?.toLocaleString(),
|
||||
method: loginMethods(userData),
|
||||
sessionCount: count,
|
||||
lastActivityAt: lastActivityAt?.toLocaleString(),
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
import { Router } from 'express'
|
||||
|
||||
import { homeController } from './controllers/index.js'
|
||||
import { userListController } from './controllers/users.js'
|
||||
import { commandListController } from './controllers/commands.js'
|
||||
import {
|
||||
sessionDetailController,
|
||||
sessionListController,
|
||||
} from './controllers/sessions.js'
|
||||
|
||||
const router = Router()
|
||||
|
||||
router.get('/', homeController)
|
||||
router.get('/users', userListController)
|
||||
router.get('/commands', commandListController)
|
||||
router.get('/sessions', sessionListController)
|
||||
router.get('/sessions/:id', sessionDetailController)
|
||||
|
||||
export default router
|
||||
@@ -0,0 +1,24 @@
|
||||
<h1>Commands</h1>
|
||||
<div>
|
||||
<h2>Error commands</h2>
|
||||
<table>
|
||||
<tr>
|
||||
<th>Command</th>
|
||||
<th>User</th>
|
||||
<th>Quest</th>
|
||||
<th>Session</th>
|
||||
<th>Error</th>
|
||||
<th>Created At</th>
|
||||
</tr>
|
||||
{{#each errorCommands}}
|
||||
<tr>
|
||||
<td>{{command}}</td>
|
||||
<td>{{user}}</td>
|
||||
<td>{{quest}}</td>
|
||||
<td><a href='/sessions/{{session}}'>Detail</a></td>
|
||||
<td>{{error}}</td>
|
||||
<td>{{createdAt}}</td>
|
||||
</tr>
|
||||
{{/each}}
|
||||
</table>
|
||||
</div>
|
||||
@@ -0,0 +1,22 @@
|
||||
<h1>Welcome to {{title}}</h1>
|
||||
<div>
|
||||
<h2>Users</h2>
|
||||
<table>
|
||||
<tr>
|
||||
<th>Registered Users</th>
|
||||
<td>{{userCount}}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Session Count</th>
|
||||
<td>{{sessionCount}}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Completed Session</th>
|
||||
<td>{{sessionCompleted}}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Completed Rate</th>
|
||||
<td>{{completedRate}}%</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
@@ -0,0 +1,17 @@
|
||||
<html>
|
||||
<head>
|
||||
<title>{{title}}</title>
|
||||
<link rel='stylesheet' href='/css/style.css' />
|
||||
</head>
|
||||
<body>
|
||||
<nav>
|
||||
<ul>
|
||||
<li><a href='/'>Home</a></li>
|
||||
<li><a href='/users'>Users</a></li>
|
||||
<li><a href='/commands'>Commands</a></li>
|
||||
<li><a href='/sessions'>Sessions</a></li>
|
||||
</ul>
|
||||
</nav>
|
||||
{{{body}}}
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,56 @@
|
||||
<h1>{{title}}</h1>
|
||||
<div>
|
||||
{{#with session}}
|
||||
<table>
|
||||
<tr>
|
||||
<th>Quest</th>
|
||||
<td>{{quest}}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>User</th>
|
||||
<td>{{user}}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Status</th>
|
||||
<td>{{status}}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Created At</th>
|
||||
<td>{{createdAt}}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Finished At</th>
|
||||
<td>{{finishedAt}}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Used Time</th>
|
||||
<td>{{usedTime}}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Command Count</th>
|
||||
<td>{{commands.length}}</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<h2>Commands</h2>
|
||||
|
||||
<table>
|
||||
<tr>
|
||||
<th>Command</th>
|
||||
<th>PWD</th>
|
||||
<th>Output</th>
|
||||
<th>Error</th>
|
||||
<th>Created At</th>
|
||||
</tr>
|
||||
{{#each commands}}
|
||||
<tr>
|
||||
<td>{{command}}</td>
|
||||
<td>{{pwd}}</td>
|
||||
<td>{{output}}</td>
|
||||
<td>{{error}}</td>
|
||||
<td>{{createdAt}}</td>
|
||||
</tr>
|
||||
{{/each}}
|
||||
</table>
|
||||
{{/with}}
|
||||
</div>
|
||||
@@ -0,0 +1,27 @@
|
||||
<h1>Sessions</h1>
|
||||
<div>
|
||||
<table>
|
||||
<tr>
|
||||
<th>User</th>
|
||||
<th>Quest</th>
|
||||
<th></th>
|
||||
<th>Status</th>
|
||||
<th>Created At</th>
|
||||
<th>Finished At</th>
|
||||
<th>Used Time</th>
|
||||
<th>Command Count</th>
|
||||
</tr>
|
||||
{{#each sessions}}
|
||||
<tr>
|
||||
<td>{{user}}</td>
|
||||
<td>{{quest}}</td>
|
||||
<td><a href='/sessions/{{_id}}'>Detail</a></td>
|
||||
<td>{{status}}</td>
|
||||
<td>{{createdAt}}</td>
|
||||
<td>{{finishedAt}}</td>
|
||||
<td>{{usedTime}}</td>
|
||||
<td>{{commandCount}}</td>
|
||||
</tr>
|
||||
{{/each}}
|
||||
</table>
|
||||
</div>
|
||||
@@ -0,0 +1,23 @@
|
||||
<h1>Users List</h1>
|
||||
<div>
|
||||
<table>
|
||||
<tr>
|
||||
<th>Username</th>
|
||||
<th>Email</th>
|
||||
<th>Registration Method</th>
|
||||
<th>Registration Time</th>
|
||||
<th>Sessions Count</th>
|
||||
<th>Last Activity</th>
|
||||
</tr>
|
||||
{{#each users}}
|
||||
<tr>
|
||||
<td>{{username}}</td>
|
||||
<td>{{email}}</td>
|
||||
<td>{{method}}</td>
|
||||
<td>{{createdAt}}</td>
|
||||
<td>{{sessionCount}}</td>
|
||||
<td>{{lastActivityAt}}</td>
|
||||
</tr>
|
||||
{{/each}}
|
||||
</table>
|
||||
</div>
|
||||
@@ -6,7 +6,6 @@
|
||||
"license": "GPL",
|
||||
"exports": "./index.js",
|
||||
"dependencies": {
|
||||
"@linux-odyssey/file-graph": "*",
|
||||
"mongoose": "^7.6.0"
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,4 @@
|
||||
import { model, Schema } from 'mongoose'
|
||||
import { FileNode } from '@linux-odyssey/file-graph'
|
||||
|
||||
const nodeSchema = new Schema({
|
||||
path: String,
|
||||
@@ -58,10 +57,10 @@ const Session = model(
|
||||
hints: [String],
|
||||
graph: {
|
||||
type: nodeSchema,
|
||||
default: new FileNode({
|
||||
default: {
|
||||
path: '/',
|
||||
type: 'folder',
|
||||
}),
|
||||
},
|
||||
},
|
||||
},
|
||||
{ timestamps: true }
|
||||
|
||||
@@ -15,7 +15,10 @@ export default async function oauthVerify(provider, profile, query, cb) {
|
||||
newUser[provider] = profile
|
||||
return cb(null, { newUser })
|
||||
}
|
||||
user[provider] = profile
|
||||
user[provider] = {
|
||||
id: profile.id,
|
||||
displayName: profile.displayName,
|
||||
}
|
||||
await user.save()
|
||||
}
|
||||
|
||||
|
||||
@@ -16,7 +16,9 @@ export async function removeExpired() {
|
||||
sessions.map(async (session) => {
|
||||
console.log('Expiring session: ', session._id, session.containerId)
|
||||
await deleteContainer(session.containerId)
|
||||
session.status = 'inactive'
|
||||
if (session.status === 'active') {
|
||||
session.status = 'inactive'
|
||||
}
|
||||
session.containerId = null
|
||||
await session.save()
|
||||
})
|
||||
|
||||
@@ -547,6 +547,18 @@
|
||||
resolved "https://registry.yarnpkg.com/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz#b520529ec21d8e5945a1851dfd1c32e94e39ff45"
|
||||
integrity sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==
|
||||
|
||||
"@isaacs/cliui@^8.0.2":
|
||||
version "8.0.2"
|
||||
resolved "https://registry.yarnpkg.com/@isaacs/cliui/-/cliui-8.0.2.tgz#b37667b7bc181c168782259bab42474fbf52b550"
|
||||
integrity sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==
|
||||
dependencies:
|
||||
string-width "^5.1.2"
|
||||
string-width-cjs "npm:string-width@^4.2.0"
|
||||
strip-ansi "^7.0.1"
|
||||
strip-ansi-cjs "npm:strip-ansi@^6.0.1"
|
||||
wrap-ansi "^8.1.0"
|
||||
wrap-ansi-cjs "npm:wrap-ansi@^7.0.0"
|
||||
|
||||
"@istanbuljs/load-nyc-config@^1.0.0":
|
||||
version "1.1.0"
|
||||
resolved "https://registry.yarnpkg.com/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz#fd3db1d59ecf7cf121e80650bb86712f9b55eced"
|
||||
@@ -835,6 +847,11 @@
|
||||
"@nodelib/fs.scandir" "2.1.5"
|
||||
fastq "^1.6.0"
|
||||
|
||||
"@pkgjs/parseargs@^0.11.0":
|
||||
version "0.11.0"
|
||||
resolved "https://registry.yarnpkg.com/@pkgjs/parseargs/-/parseargs-0.11.0.tgz#a77ea742fab25775145434eb1d2328cf5013ac33"
|
||||
integrity sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==
|
||||
|
||||
"@pkgr/utils@^2.3.1":
|
||||
version "2.4.2"
|
||||
resolved "https://registry.yarnpkg.com/@pkgr/utils/-/utils-2.4.2.tgz#9e638bbe9a6a6f165580dc943f138fd3309a2cbc"
|
||||
@@ -1182,6 +1199,11 @@ ansi-regex@^5.0.1:
|
||||
resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-5.0.1.tgz#082cb2c89c9fe8659a311a53bd6a4dc5301db304"
|
||||
integrity sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==
|
||||
|
||||
ansi-regex@^6.0.1:
|
||||
version "6.0.1"
|
||||
resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-6.0.1.tgz#3183e38fae9a65d7cb5e53945cd5897d0260a06a"
|
||||
integrity sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==
|
||||
|
||||
ansi-styles@^3.2.1:
|
||||
version "3.2.1"
|
||||
resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-3.2.1.tgz#41fbb20243e50b12be0f04b8dedbf07520ce841d"
|
||||
@@ -1201,6 +1223,11 @@ ansi-styles@^5.0.0:
|
||||
resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-5.2.0.tgz#07449690ad45777d1924ac2abb2fc8895dba836b"
|
||||
integrity sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==
|
||||
|
||||
ansi-styles@^6.1.0:
|
||||
version "6.2.1"
|
||||
resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-6.2.1.tgz#0e62320cf99c21afff3b3012192546aacbfb05c5"
|
||||
integrity sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==
|
||||
|
||||
any-promise@^1.0.0:
|
||||
version "1.3.0"
|
||||
resolved "https://registry.yarnpkg.com/any-promise/-/any-promise-1.3.0.tgz#abc6afeedcea52e809cdc0376aed3ce39635d17f"
|
||||
@@ -1547,6 +1574,13 @@ brace-expansion@^1.1.7:
|
||||
balanced-match "^1.0.0"
|
||||
concat-map "0.0.1"
|
||||
|
||||
brace-expansion@^2.0.1:
|
||||
version "2.0.1"
|
||||
resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-2.0.1.tgz#1edc459e0f0c548486ecf9fc99f2221364b9a0ae"
|
||||
integrity sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==
|
||||
dependencies:
|
||||
balanced-match "^1.0.0"
|
||||
|
||||
braces@^3.0.2, braces@~3.0.2:
|
||||
version "3.0.2"
|
||||
resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.2.tgz#3454e1a462ee8d599e236df336cd9ea4f8afe107"
|
||||
@@ -2148,6 +2182,11 @@ doctrine@^3.0.0:
|
||||
dependencies:
|
||||
esutils "^2.0.2"
|
||||
|
||||
eastasianwidth@^0.2.0:
|
||||
version "0.2.0"
|
||||
resolved "https://registry.yarnpkg.com/eastasianwidth/-/eastasianwidth-0.2.0.tgz#696ce2ec0aa0e6ea93a397ffcf24aa7840c827cb"
|
||||
integrity sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==
|
||||
|
||||
ecc-jsbn@~0.1.1:
|
||||
version "0.1.2"
|
||||
resolved "https://registry.yarnpkg.com/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz#3a83a904e54353287874c564b7549386849a98c9"
|
||||
@@ -2183,6 +2222,11 @@ emoji-regex@^8.0.0:
|
||||
resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-8.0.0.tgz#e818fd69ce5ccfcb404594f842963bf53164cc37"
|
||||
integrity sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==
|
||||
|
||||
emoji-regex@^9.2.2:
|
||||
version "9.2.2"
|
||||
resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-9.2.2.tgz#840c8803b0d8047f4ff0cf963176b32d4ef3ed72"
|
||||
integrity sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==
|
||||
|
||||
encodeurl@~1.0.2:
|
||||
version "1.0.2"
|
||||
resolved "https://registry.yarnpkg.com/encodeurl/-/encodeurl-1.0.2.tgz#ad3ff4c86ec2d029322f5a02c3a9a606c95b3f59"
|
||||
@@ -2633,6 +2677,15 @@ expect@^29.6.1:
|
||||
jest-message-util "^29.6.1"
|
||||
jest-util "^29.6.1"
|
||||
|
||||
express-handlebars@^7.1.2:
|
||||
version "7.1.2"
|
||||
resolved "https://registry.yarnpkg.com/express-handlebars/-/express-handlebars-7.1.2.tgz#2471673d11af46f496cba4098a705f0217232fda"
|
||||
integrity sha512-ss9d3mBChOLTEtyfzXCsxlItUxpgS3i4cb/F70G6Q5ohQzmD12XB4x/Y9U6YboeeYBJZt7WQ5yUNu7ZSQ/EGyQ==
|
||||
dependencies:
|
||||
glob "^10.3.3"
|
||||
graceful-fs "^4.2.11"
|
||||
handlebars "^4.7.8"
|
||||
|
||||
express-session@^1.17.3:
|
||||
version "1.17.3"
|
||||
resolved "https://registry.yarnpkg.com/express-session/-/express-session-1.17.3.tgz#14b997a15ed43e5949cb1d073725675dd2777f36"
|
||||
@@ -2837,6 +2890,14 @@ for-each@^0.3.3:
|
||||
dependencies:
|
||||
is-callable "^1.1.3"
|
||||
|
||||
foreground-child@^3.1.0:
|
||||
version "3.1.1"
|
||||
resolved "https://registry.yarnpkg.com/foreground-child/-/foreground-child-3.1.1.tgz#1d173e776d75d2772fed08efe4a0de1ea1b12d0d"
|
||||
integrity sha512-TMKDUnIte6bfb5nWv7V/caI169OHgvwjb7V4WkeUvbQQdjr5rWKqHFiKWb/fcOwB+CzBT+qbWjvj+DVwRskpIg==
|
||||
dependencies:
|
||||
cross-spawn "^7.0.0"
|
||||
signal-exit "^4.0.1"
|
||||
|
||||
forever-agent@~0.6.1:
|
||||
version "0.6.1"
|
||||
resolved "https://registry.yarnpkg.com/forever-agent/-/forever-agent-0.6.1.tgz#fbc71f0c41adeb37f96c577ad1ed42d8fdacca91"
|
||||
@@ -3027,6 +3088,17 @@ glob@7.1.6:
|
||||
once "^1.3.0"
|
||||
path-is-absolute "^1.0.0"
|
||||
|
||||
glob@^10.3.3:
|
||||
version "10.3.10"
|
||||
resolved "https://registry.yarnpkg.com/glob/-/glob-10.3.10.tgz#0351ebb809fd187fe421ab96af83d3a70715df4b"
|
||||
integrity sha512-fa46+tv1Ak0UPK1TOy/pZrIybNNt4HCv7SDzwyfiOZkvZLEbjsZkJBPtDHVshZjbecAoAGSC20MjLDG/qr679g==
|
||||
dependencies:
|
||||
foreground-child "^3.1.0"
|
||||
jackspeak "^2.3.5"
|
||||
minimatch "^9.0.1"
|
||||
minipass "^5.0.0 || ^6.0.2 || ^7.0.0"
|
||||
path-scurry "^1.10.1"
|
||||
|
||||
glob@^7.1.3, glob@^7.1.4:
|
||||
version "7.2.3"
|
||||
resolved "https://registry.yarnpkg.com/glob/-/glob-7.2.3.tgz#b8df0fb802bbfa8e89bd1d938b4e16578ed44f2b"
|
||||
@@ -3079,7 +3151,7 @@ gopd@^1.0.1:
|
||||
dependencies:
|
||||
get-intrinsic "^1.1.3"
|
||||
|
||||
graceful-fs@^4.1.6, graceful-fs@^4.2.0, graceful-fs@^4.2.9:
|
||||
graceful-fs@^4.1.6, graceful-fs@^4.2.0, graceful-fs@^4.2.11, graceful-fs@^4.2.9:
|
||||
version "4.2.11"
|
||||
resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.11.tgz#4183e4e8bf08bb6e05bbb2f7d2e0c8f712ca40e3"
|
||||
integrity sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==
|
||||
@@ -3089,6 +3161,18 @@ graphemer@^1.4.0:
|
||||
resolved "https://registry.yarnpkg.com/graphemer/-/graphemer-1.4.0.tgz#fb2f1d55e0e3a1849aeffc90c4fa0dd53a0e66c6"
|
||||
integrity sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==
|
||||
|
||||
handlebars@^4.7.8:
|
||||
version "4.7.8"
|
||||
resolved "https://registry.yarnpkg.com/handlebars/-/handlebars-4.7.8.tgz#41c42c18b1be2365439188c77c6afae71c0cd9e9"
|
||||
integrity sha512-vafaFqs8MZkRrSX7sFVUdo3ap/eNiLnb4IakshzvP56X5Nr1iGKAIqdX6tMlm6HcNRIkr6AxO5jFEoJzzpT8aQ==
|
||||
dependencies:
|
||||
minimist "^1.2.5"
|
||||
neo-async "^2.6.2"
|
||||
source-map "^0.6.1"
|
||||
wordwrap "^1.0.0"
|
||||
optionalDependencies:
|
||||
uglify-js "^3.1.4"
|
||||
|
||||
has-bigints@^1.0.1, has-bigints@^1.0.2:
|
||||
version "1.0.2"
|
||||
resolved "https://registry.yarnpkg.com/has-bigints/-/has-bigints-1.0.2.tgz#0871bd3e3d51626f6ca0966668ba35d5602d6eaa"
|
||||
@@ -3530,6 +3614,15 @@ istanbul-reports@^3.1.3:
|
||||
html-escaper "^2.0.0"
|
||||
istanbul-lib-report "^3.0.0"
|
||||
|
||||
jackspeak@^2.3.5:
|
||||
version "2.3.6"
|
||||
resolved "https://registry.yarnpkg.com/jackspeak/-/jackspeak-2.3.6.tgz#647ecc472238aee4b06ac0e461acc21a8c505ca8"
|
||||
integrity sha512-N3yCS/NegsOBokc8GAdM8UcmfsKiSS8cipheD/nivzr700H+nsMOxJjQnvwOcRYVuFkdH0wGUvW2WbXGmrZGbQ==
|
||||
dependencies:
|
||||
"@isaacs/cliui" "^8.0.2"
|
||||
optionalDependencies:
|
||||
"@pkgjs/parseargs" "^0.11.0"
|
||||
|
||||
jest-changed-files@^29.5.0:
|
||||
version "29.5.0"
|
||||
resolved "https://registry.yarnpkg.com/jest-changed-files/-/jest-changed-files-29.5.0.tgz#e88786dca8bf2aa899ec4af7644e16d9dcf9b23e"
|
||||
@@ -4178,6 +4271,11 @@ lru-cache@^6.0.0:
|
||||
dependencies:
|
||||
yallist "^4.0.0"
|
||||
|
||||
"lru-cache@^9.1.1 || ^10.0.0":
|
||||
version "10.0.1"
|
||||
resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-10.0.1.tgz#0a3be479df549cca0e5d693ac402ff19537a6b7a"
|
||||
integrity sha512-IJ4uwUTi2qCccrioU6g9g/5rvvVl13bsdczUUcqbciD9iLr095yj8DQKdObriEvuNSx325N1rV1O0sJFszx75g==
|
||||
|
||||
luxon@^3.2.1:
|
||||
version "3.4.1"
|
||||
resolved "https://registry.yarnpkg.com/luxon/-/luxon-3.4.1.tgz#9147374b2c539e7893f906c420e9b080b59c5457"
|
||||
@@ -4281,7 +4379,14 @@ minimatch@^3.0.4, minimatch@^3.0.5, minimatch@^3.1.1, minimatch@^3.1.2:
|
||||
dependencies:
|
||||
brace-expansion "^1.1.7"
|
||||
|
||||
minimist@^1.2.0, minimist@^1.2.6, minimist@^1.2.8:
|
||||
minimatch@^9.0.1:
|
||||
version "9.0.3"
|
||||
resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-9.0.3.tgz#a6e00c3de44c3a542bfaae70abfc22420a6da825"
|
||||
integrity sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==
|
||||
dependencies:
|
||||
brace-expansion "^2.0.1"
|
||||
|
||||
minimist@^1.2.0, minimist@^1.2.5, minimist@^1.2.6, minimist@^1.2.8:
|
||||
version "1.2.8"
|
||||
resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.8.tgz#c1a464e7693302e082a075cee0c057741ac4772c"
|
||||
integrity sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==
|
||||
@@ -4298,6 +4403,11 @@ minipass@^5.0.0:
|
||||
resolved "https://registry.yarnpkg.com/minipass/-/minipass-5.0.0.tgz#3e9788ffb90b694a5d0ec94479a45b5d8738133d"
|
||||
integrity sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ==
|
||||
|
||||
"minipass@^5.0.0 || ^6.0.2 || ^7.0.0":
|
||||
version "7.0.4"
|
||||
resolved "https://registry.yarnpkg.com/minipass/-/minipass-7.0.4.tgz#dbce03740f50a4786ba994c1fb908844d27b038c"
|
||||
integrity sha512-jYofLM5Dam9279rdkWzqHozUo4ybjdZmCsDHePy5V/PbBcVMiSZR97gmAy45aqi8CK1lG2ECd356FU86avfwUQ==
|
||||
|
||||
minizlib@^2.1.1:
|
||||
version "2.1.2"
|
||||
resolved "https://registry.yarnpkg.com/minizlib/-/minizlib-2.1.2.tgz#e90d3466ba209b932451508a11ce3d3632145931"
|
||||
@@ -4428,6 +4538,11 @@ negotiator@0.6.3:
|
||||
resolved "https://registry.yarnpkg.com/negotiator/-/negotiator-0.6.3.tgz#58e323a72fedc0d6f9cd4d31fe49f51479590ccd"
|
||||
integrity sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==
|
||||
|
||||
neo-async@^2.6.2:
|
||||
version "2.6.2"
|
||||
resolved "https://registry.yarnpkg.com/neo-async/-/neo-async-2.6.2.tgz#b4aafb93e3aeb2d8174ca53cf163ab7d7308305f"
|
||||
integrity sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==
|
||||
|
||||
node-addon-api@^5.0.0:
|
||||
version "5.1.0"
|
||||
resolved "https://registry.yarnpkg.com/node-addon-api/-/node-addon-api-5.1.0.tgz#49da1ca055e109a23d537e9de43c09cca21eb762"
|
||||
@@ -4792,6 +4907,14 @@ path-parse@^1.0.7:
|
||||
resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.7.tgz#fbc114b60ca42b30d9daf5858e4bd68bbedb6735"
|
||||
integrity sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==
|
||||
|
||||
path-scurry@^1.10.1:
|
||||
version "1.10.1"
|
||||
resolved "https://registry.yarnpkg.com/path-scurry/-/path-scurry-1.10.1.tgz#9ba6bf5aa8500fe9fd67df4f0d9483b2b0bfc698"
|
||||
integrity sha512-MkhCqzzBEpPvxxQ71Md0b1Kk51W01lrYvlMzSUaIzNsODdd7mqhiimSZlr+VegAz5Z6Vzt9Xg2ttE//XBhH3EQ==
|
||||
dependencies:
|
||||
lru-cache "^9.1.1 || ^10.0.0"
|
||||
minipass "^5.0.0 || ^6.0.2 || ^7.0.0"
|
||||
|
||||
path-to-regexp@0.1.7:
|
||||
version "0.1.7"
|
||||
resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-0.1.7.tgz#df604178005f522f15eb4490e7247a1bfaa67f8c"
|
||||
@@ -5272,6 +5395,11 @@ signal-exit@^3.0.0, signal-exit@^3.0.2, signal-exit@^3.0.3, signal-exit@^3.0.7:
|
||||
resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.7.tgz#a9a1767f8af84155114eaabd73f99273c8f59ad9"
|
||||
integrity sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==
|
||||
|
||||
signal-exit@^4.0.1:
|
||||
version "4.1.0"
|
||||
resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-4.1.0.tgz#952188c1cbd546070e2dd20d0f41c0ae0530cb04"
|
||||
integrity sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==
|
||||
|
||||
simple-update-notifier@^2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/simple-update-notifier/-/simple-update-notifier-2.0.0.tgz#d70b92bdab7d6d90dfd73931195a30b6e3d7cebb"
|
||||
@@ -5444,7 +5572,7 @@ string-length@^4.0.1:
|
||||
char-regex "^1.0.2"
|
||||
strip-ansi "^6.0.0"
|
||||
|
||||
"string-width@^1.0.2 || 2 || 3 || 4", string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3:
|
||||
"string-width-cjs@npm:string-width@^4.2.0", "string-width@^1.0.2 || 2 || 3 || 4", string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3:
|
||||
version "4.2.3"
|
||||
resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010"
|
||||
integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==
|
||||
@@ -5453,6 +5581,15 @@ string-length@^4.0.1:
|
||||
is-fullwidth-code-point "^3.0.0"
|
||||
strip-ansi "^6.0.1"
|
||||
|
||||
string-width@^5.0.1, string-width@^5.1.2:
|
||||
version "5.1.2"
|
||||
resolved "https://registry.yarnpkg.com/string-width/-/string-width-5.1.2.tgz#14f8daec6d81e7221d2a357e668cab73bdbca794"
|
||||
integrity sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==
|
||||
dependencies:
|
||||
eastasianwidth "^0.2.0"
|
||||
emoji-regex "^9.2.2"
|
||||
strip-ansi "^7.0.1"
|
||||
|
||||
string.prototype.trim@^1.2.7:
|
||||
version "1.2.7"
|
||||
resolved "https://registry.yarnpkg.com/string.prototype.trim/-/string.prototype.trim-1.2.7.tgz#a68352740859f6893f14ce3ef1bb3037f7a90533"
|
||||
@@ -5487,13 +5624,20 @@ string_decoder@^1.1.1:
|
||||
dependencies:
|
||||
safe-buffer "~5.2.0"
|
||||
|
||||
strip-ansi@^6.0.0, strip-ansi@^6.0.1:
|
||||
"strip-ansi-cjs@npm:strip-ansi@^6.0.1", strip-ansi@^6.0.0, strip-ansi@^6.0.1:
|
||||
version "6.0.1"
|
||||
resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9"
|
||||
integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==
|
||||
dependencies:
|
||||
ansi-regex "^5.0.1"
|
||||
|
||||
strip-ansi@^7.0.1:
|
||||
version "7.1.0"
|
||||
resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-7.1.0.tgz#d5b6568ca689d8561370b0707685d22434faff45"
|
||||
integrity sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==
|
||||
dependencies:
|
||||
ansi-regex "^6.0.1"
|
||||
|
||||
strip-bom@^3.0.0:
|
||||
version "3.0.0"
|
||||
resolved "https://registry.yarnpkg.com/strip-bom/-/strip-bom-3.0.0.tgz#2334c18e9c759f7bdd56fdef7e9ae3d588e68ed3"
|
||||
@@ -5827,6 +5971,11 @@ typed-array-length@^1.0.4:
|
||||
for-each "^0.3.3"
|
||||
is-typed-array "^1.1.9"
|
||||
|
||||
uglify-js@^3.1.4:
|
||||
version "3.17.4"
|
||||
resolved "https://registry.yarnpkg.com/uglify-js/-/uglify-js-3.17.4.tgz#61678cf5fa3f5b7eb789bb345df29afb8257c22c"
|
||||
integrity sha512-T9q82TJI9e/C1TAxYvfb16xO120tMVFZrGA3f9/P4424DNu6ypK103y0GPFVa17yotwSyZW5iYXgjYHkGrJW/g==
|
||||
|
||||
uid-safe@~2.1.5:
|
||||
version "2.1.5"
|
||||
resolved "https://registry.yarnpkg.com/uid-safe/-/uid-safe-2.1.5.tgz#2b3d5c7240e8fc2e58f8aa269e5ee49c0857bd3a"
|
||||
@@ -6047,6 +6196,20 @@ wide-align@^1.1.2:
|
||||
dependencies:
|
||||
string-width "^1.0.2 || 2 || 3 || 4"
|
||||
|
||||
wordwrap@^1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/wordwrap/-/wordwrap-1.0.0.tgz#27584810891456a4171c8d0226441ade90cbcaeb"
|
||||
integrity sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q==
|
||||
|
||||
"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0", wrap-ansi@^7.0.0:
|
||||
version "7.0.0"
|
||||
resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43"
|
||||
integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==
|
||||
dependencies:
|
||||
ansi-styles "^4.0.0"
|
||||
string-width "^4.1.0"
|
||||
strip-ansi "^6.0.0"
|
||||
|
||||
wrap-ansi@^6.2.0:
|
||||
version "6.2.0"
|
||||
resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-6.2.0.tgz#e9393ba07102e6c91a3b221478f0257cd2856e53"
|
||||
@@ -6056,14 +6219,14 @@ wrap-ansi@^6.2.0:
|
||||
string-width "^4.1.0"
|
||||
strip-ansi "^6.0.0"
|
||||
|
||||
wrap-ansi@^7.0.0:
|
||||
version "7.0.0"
|
||||
resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43"
|
||||
integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==
|
||||
wrap-ansi@^8.1.0:
|
||||
version "8.1.0"
|
||||
resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-8.1.0.tgz#56dc22368ee570face1b49819975d9b9a5ead214"
|
||||
integrity sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==
|
||||
dependencies:
|
||||
ansi-styles "^4.0.0"
|
||||
string-width "^4.1.0"
|
||||
strip-ansi "^6.0.0"
|
||||
ansi-styles "^6.1.0"
|
||||
string-width "^5.0.1"
|
||||
strip-ansi "^7.0.1"
|
||||
|
||||
wrappy@1:
|
||||
version "1.0.2"
|
||||
|
||||
Reference in new issue
Block a user