├── backend ├── .gitignore ├── csv-files │ ├── groups.csv │ ├── accounts.csv │ ├── holidays.csv │ └── resources.csv ├── config.js ├── package.json ├── router │ ├── todos.js │ ├── groups.js │ ├── resources.js │ ├── holidays.js │ ├── tasks.js │ ├── auth.js │ └── accounts.js ├── tools │ └── import-users.js ├── utils │ ├── TaskUtil.js │ └── TodoUtil.js ├── common.js ├── app.js └── yarn.lock ├── frontend ├── @types │ ├── splitpane.d.ts │ ├── xl-formula.d.ts │ ├── vue-nestable.d.ts │ ├── vue-drag-resize.d.ts │ └── vue-draggable-resizable.d.ts ├── .prettierrc ├── static │ ├── v.png │ ├── icon.png │ ├── favicon.ico │ └── vuetify-logo.svg ├── models │ ├── Group.ts │ ├── Resource.ts │ ├── Todo.ts │ ├── Config.ts │ └── Task.ts ├── env.development.js ├── pages │ ├── account.vue │ ├── settings.vue │ ├── login.vue │ ├── index.vue │ └── print-gantt.vue ├── assets │ └── variables.scss ├── middleware │ └── authenticator.js ├── .editorconfig ├── layouts │ ├── printGantt.vue │ ├── error.vue │ └── default.vue ├── plugins │ └── TiptapVuetify.js ├── .eslintrc.js ├── store │ ├── README.md │ ├── GroupStore.ts │ ├── ResourceStore.ts │ ├── HolidayStore.ts │ ├── TaskRecordStore.ts │ ├── TodoStore.ts │ └── ConfigStore.ts ├── tsconfig.json ├── content │ └── hello.md ├── .gitignore ├── components │ ├── scss │ │ ├── wbs-common.scss │ │ └── wbs-pane.scss │ ├── LoginComponent.vue │ ├── AccountComponent.vue │ ├── dialogs │ │ ├── TaskDetailDialog.vue │ │ ├── AddProjectDialog.vue │ │ └── TodoDialog.vue │ ├── ResourceComponent.vue │ ├── SettingComponent.vue │ └── PrintGanttComponent.vue ├── package.json ├── README.md ├── nuxt.config.js └── utils │ └── DateUtil.ts ├── docs └── images │ └── overview.gif ├── yarn.lock ├── .gitignore └── README.md /backend/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | db/ 3 | -------------------------------------------------------------------------------- /frontend/@types/splitpane.d.ts: -------------------------------------------------------------------------------- 1 | declare module 'splitpanes' 2 | -------------------------------------------------------------------------------- /frontend/@types/xl-formula.d.ts: -------------------------------------------------------------------------------- 1 | declare module 'xl-formula' 2 | -------------------------------------------------------------------------------- /frontend/@types/vue-nestable.d.ts: -------------------------------------------------------------------------------- 1 | declare module 'vue-nestable' 2 | -------------------------------------------------------------------------------- /frontend/@types/vue-drag-resize.d.ts: -------------------------------------------------------------------------------- 1 | declare module 'vue-drag-resize' 2 | -------------------------------------------------------------------------------- /frontend/.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "semi": false, 3 | "singleQuote": true 4 | } 5 | -------------------------------------------------------------------------------- /frontend/@types/vue-draggable-resizable.d.ts: -------------------------------------------------------------------------------- 1 | declare module 'vue-draggable-resizable' 2 | -------------------------------------------------------------------------------- /frontend/static/v.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kaku3/agile-gantt/HEAD/frontend/static/v.png -------------------------------------------------------------------------------- /docs/images/overview.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kaku3/agile-gantt/HEAD/docs/images/overview.gif -------------------------------------------------------------------------------- /frontend/static/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kaku3/agile-gantt/HEAD/frontend/static/icon.png -------------------------------------------------------------------------------- /frontend/static/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kaku3/agile-gantt/HEAD/frontend/static/favicon.ico -------------------------------------------------------------------------------- /yarn.lock: -------------------------------------------------------------------------------- 1 | # THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. 2 | # yarn lockfile v1 3 | 4 | 5 | -------------------------------------------------------------------------------- /frontend/models/Group.ts: -------------------------------------------------------------------------------- 1 | export interface GroupEntity { 2 | id: number 3 | group: string 4 | name: string 5 | } 6 | -------------------------------------------------------------------------------- /frontend/env.development.js: -------------------------------------------------------------------------------- 1 | const apiServer = 'http://localhost:3010' 2 | module.exports = { 3 | apiServer, 4 | apiBaseUrl: `${apiServer}/api` 5 | } 6 | -------------------------------------------------------------------------------- /frontend/models/Resource.ts: -------------------------------------------------------------------------------- 1 | export interface ResourceEntity { 2 | id: number 3 | groupId: number 4 | name: string 5 | email: string 6 | memo: string 7 | } 8 | -------------------------------------------------------------------------------- /frontend/pages/account.vue: -------------------------------------------------------------------------------- 1 | 8 | -------------------------------------------------------------------------------- /frontend/pages/settings.vue: -------------------------------------------------------------------------------- 1 | 8 | -------------------------------------------------------------------------------- /frontend/pages/login.vue: -------------------------------------------------------------------------------- 1 | 8 | -------------------------------------------------------------------------------- /frontend/assets/variables.scss: -------------------------------------------------------------------------------- 1 | // Ref: https://github.com/nuxt-community/vuetify-module#customvariables 2 | // 3 | // The variables you want to modify 4 | // $font-size-root: 20px; 5 | -------------------------------------------------------------------------------- /frontend/pages/index.vue: -------------------------------------------------------------------------------- 1 | 10 | -------------------------------------------------------------------------------- /backend/csv-files/groups.csv: -------------------------------------------------------------------------------- 1 | id,group,name 2 | 100,Your Great Company,部署1 3 | 200,Your Great Company,部署2 4 | 300,Your Great Company,部署3 5 | 1000,BP,協力会社1 6 | 1010,BP,協力会社2 7 | 1020,BP,協力会社3 8 | 1030,BP,協力会社4 9 | -------------------------------------------------------------------------------- /frontend/models/Todo.ts: -------------------------------------------------------------------------------- 1 | export interface TodoEntity { 2 | id: number 3 | subject: string 4 | body: string 5 | assigneeId: number 6 | createDate: number // yyyymmdd 7 | dueDate: number // yyyymmdd 8 | } 9 | -------------------------------------------------------------------------------- /frontend/middleware/authenticator.js: -------------------------------------------------------------------------------- 1 | export default async ({ $auth, store, route, redirect }) => { 2 | console.log(route) 3 | // 未ログイン時はログインページへ redirect 4 | if (!$auth.loggedIn && route.path !== '/login') { 5 | return redirect('/login') 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /frontend/.editorconfig: -------------------------------------------------------------------------------- 1 | # editorconfig.org 2 | root = true 3 | 4 | [*] 5 | indent_style = space 6 | indent_size = 2 7 | end_of_line = lf 8 | charset = utf-8 9 | trim_trailing_whitespace = true 10 | insert_final_newline = true 11 | 12 | [*.md] 13 | trim_trailing_whitespace = false 14 | -------------------------------------------------------------------------------- /backend/config.js: -------------------------------------------------------------------------------- 1 | const config = { 2 | cors: { 3 | origin: 'http://localhost:3000', //アクセス許可するオリジン 4 | credentials: true, //レスポンスヘッダーにAccess-Control-Allow-Credentials追加 5 | optionsSuccessStatus: 200 //レスポンスstatusを200に設定 6 | } 7 | } 8 | module.exports = config 9 | -------------------------------------------------------------------------------- /frontend/layouts/printGantt.vue: -------------------------------------------------------------------------------- 1 | 10 | 15 | -------------------------------------------------------------------------------- /frontend/plugins/TiptapVuetify.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import { TiptapVuetifyPlugin } from 'tiptap-vuetify' 3 | import 'tiptap-vuetify/dist/main.css' 4 | 5 | export default ({ app }) => { 6 | Vue.use(TiptapVuetifyPlugin, { 7 | vuetify: app.vuetify, 8 | iconsGroup: 'mdi' 9 | }) 10 | } 11 | -------------------------------------------------------------------------------- /frontend/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | env: { 4 | browser: true, 5 | node: true 6 | }, 7 | extends: [ 8 | '@nuxtjs/eslint-config-typescript', 9 | 'plugin:nuxt/recommended', 10 | 'prettier' 11 | ], 12 | plugins: [ 13 | ], 14 | // add your custom rules here 15 | rules: {} 16 | } 17 | -------------------------------------------------------------------------------- /backend/csv-files/accounts.csv: -------------------------------------------------------------------------------- 1 | id,groupId,name,email,memo 2 | 100,100,M1-0,user0@m1.example.co.jp, 3 | 101,100,M1-1,user1@m1.example.co.jp, 4 | 102,100,M1-2,user2@m1.example.co.jp, 5 | 200,200,M2-0,user0@m2.example.co.jp, 6 | 201,200,M2-1,user1@m2.example.co.jp, 7 | 202,200,M2-2,user2@m2.example.co.jp, 8 | 300,300,M3-0,user0@m3.example.co.jp, 9 | 301,300,M3-1,user1@m3.example.co.jp, 10 | -------------------------------------------------------------------------------- /backend/csv-files/holidays.csv: -------------------------------------------------------------------------------- 1 | date,day,note 2 | 2022-1-1,土,元日 3 | 2022-1-10,月,成人の日 4 | 2022-2-11,金,建国記念の日 5 | 2022-2-23,水,天皇誕生日 6 | 2022-3-21,月,春分の日 7 | 2022-4-29,金,昭和の日 8 | 2022-5-3,火,憲法記念日 9 | 2022-5-4,水,みどりの日 10 | 2022-5-5,木,こどもの日 11 | 2022-7-18,月,海の日 12 | 2022-8-11,木,山の日 13 | 2022-9-19,月,敬老の日 14 | 2022-9-23,金,秋分の日 15 | 2022-10-10,月,スポーツの日 16 | 2022-11-3,木,文化の日 17 | 2022-11-23,水,勤労感謝の日 18 | -------------------------------------------------------------------------------- /frontend/pages/print-gantt.vue: -------------------------------------------------------------------------------- 1 | 8 | 19 | -------------------------------------------------------------------------------- /frontend/store/README.md: -------------------------------------------------------------------------------- 1 | # STORE 2 | 3 | **This directory is not required, you can delete it if you don't want to use it.** 4 | 5 | This directory contains your Vuex Store files. 6 | Vuex Store option is implemented in the Nuxt.js framework. 7 | 8 | Creating a file in this directory automatically activates the option in the framework. 9 | 10 | More information about the usage of this directory in [the documentation](https://nuxtjs.org/guide/vuex-store). 11 | -------------------------------------------------------------------------------- /backend/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "backend", 3 | "version": "1.0.0", 4 | "main": "app.js", 5 | "license": "MIT", 6 | "dependencies": { 7 | "bcrypt": "^5.0.1", 8 | "cors": "^2.8.5", 9 | "crypto": "^1.0.1", 10 | "csv-parse": "^5.0.4", 11 | "express": "^4.17.2", 12 | "express-session": "^1.17.2", 13 | "fs": "^0.0.1-security", 14 | "jsonwebtoken": "^8.5.1", 15 | "path": "^0.12.7", 16 | "socket.io": "^4.4.1" 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /backend/router/todos.js: -------------------------------------------------------------------------------- 1 | const path = require('path') 2 | const fs = require('fs') 3 | const router = require('express').Router() 4 | 5 | const TodoUtil = require('../utils/TodoUtil') 6 | 7 | const db = path.join(process.cwd(), 'db') 8 | 9 | router.get('/', (req, res, next) => { 10 | res.json(TodoUtil.get()) 11 | }) 12 | router.post('/', (req, res, next) => { 13 | TodoUtil.save(req.body) 14 | res.json({ 15 | result: 'success' 16 | }) 17 | }) 18 | 19 | module.exports = router -------------------------------------------------------------------------------- /frontend/store/GroupStore.ts: -------------------------------------------------------------------------------- 1 | import { Module, VuexModule, Mutation, Action } from 'vuex-module-decorators' 2 | import { GroupEntity } from '~/models/Group' 3 | 4 | @Module({ 5 | name: 'GroupStore', 6 | stateFactory: true, 7 | namespaced: true 8 | }) 9 | export default class GroupStore extends VuexModule { 10 | _groups: GroupEntity[] = [] 11 | 12 | get groups(): GroupEntity[] { 13 | return this._groups 14 | } 15 | 16 | @Mutation 17 | setGroups(es: GroupEntity[]) { 18 | this._groups = es 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /frontend/static/vuetify-logo.svg: -------------------------------------------------------------------------------- 1 | Artboard 46 2 | -------------------------------------------------------------------------------- /backend/router/groups.js: -------------------------------------------------------------------------------- 1 | const path = require('path') 2 | const fs = require('fs') 3 | const router = require('express').Router() 4 | 5 | const db = path.join(process.cwd(), 'db') 6 | 7 | router.get('/', (req, res, next) => { 8 | const json = fs.readFileSync(path.join(db, 'groups.json'), 'utf8') 9 | res.json(JSON.parse(json)) 10 | }) 11 | router.post('/', (req, res, next) => { 12 | fs.writeFileSync(path.join(db, 'groups.json'), JSON.stringify(req.body)) 13 | res.json({ 14 | result: 'success' 15 | }) 16 | }) 17 | 18 | module.exports = router -------------------------------------------------------------------------------- /frontend/store/ResourceStore.ts: -------------------------------------------------------------------------------- 1 | import { Module, VuexModule, Mutation, Action } from 'vuex-module-decorators' 2 | import { ResourceEntity } from '~/models/Resource' 3 | 4 | @Module({ 5 | name: 'ResourceStore', 6 | stateFactory: true, 7 | namespaced: true 8 | }) 9 | export default class ResourceStore extends VuexModule { 10 | _resources: ResourceEntity[] = [] 11 | 12 | get resources(): ResourceEntity[] { 13 | return this._resources 14 | } 15 | 16 | @Mutation 17 | setResources(es: ResourceEntity[]) { 18 | this._resources = es 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /frontend/store/HolidayStore.ts: -------------------------------------------------------------------------------- 1 | import { Module, VuexModule, Mutation, Action } from 'vuex-module-decorators' 2 | 3 | @Module({ 4 | name: 'HolidayStore', 5 | stateFactory: true, 6 | namespaced: true 7 | }) 8 | export default class HolidayStore extends VuexModule { 9 | _holidays: any[] = [] 10 | 11 | get holidays(): any[] { 12 | return this._holidays 13 | } 14 | 15 | get holidayDates(): String[] { 16 | return this._holidays.map(h => h.date) 17 | } 18 | 19 | @Mutation 20 | setHolidays(es: any[]) { 21 | this._holidays = es 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /frontend/store/TaskRecordStore.ts: -------------------------------------------------------------------------------- 1 | import { Module, VuexModule, Mutation, Action } from 'vuex-module-decorators' 2 | import { TaskEntity } from '~/models/Task' 3 | 4 | @Module({ 5 | name: 'TaskRecordStore', 6 | stateFactory: true, 7 | namespaced: true 8 | }) 9 | export default class TaskRecordStore extends VuexModule { 10 | _taskRecords: TaskEntity[] = []; 11 | 12 | public get taskRecords(): TaskEntity[] { 13 | return this._taskRecords; 14 | } 15 | 16 | @Mutation 17 | setTaskRecords(es: TaskEntity[]) { 18 | this._taskRecords = es; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /backend/router/resources.js: -------------------------------------------------------------------------------- 1 | const path = require('path') 2 | const fs = require('fs') 3 | const router = require('express').Router() 4 | 5 | const db = path.join(process.cwd(), 'db') 6 | 7 | router.get('/', (req, res, next) => { 8 | const json = fs.readFileSync(path.join(db, 'resources.json'), 'utf8') 9 | res.json(JSON.parse(json)) 10 | }) 11 | router.post('/', (req, res, next) => { 12 | fs.writeFileSync(path.join(db, 'resources.json'), JSON.stringify(req.body)) 13 | res.json({ 14 | result: 'success' 15 | }) 16 | }) 17 | 18 | module.exports = router -------------------------------------------------------------------------------- /backend/router/holidays.js: -------------------------------------------------------------------------------- 1 | const path = require('path') 2 | const fs = require('fs') 3 | const router = require('express').Router() 4 | 5 | const db = path.join(process.cwd(), 'db') 6 | 7 | router.get('/', (req, res, next) => { 8 | const json = fs.readFileSync(path.join(db, '/holidays.json'), 'utf8') 9 | res.json(JSON.parse(json)) 10 | }) 11 | router.post('/', (req, res, next) => { 12 | fs.writeFileSync(path.join(db, '/holidays.json'), JSON.stringify(req.body)) 13 | res.json({ 14 | result: 'success' 15 | }) 16 | }) 17 | 18 | module.exports = router 19 | -------------------------------------------------------------------------------- /backend/csv-files/resources.csv: -------------------------------------------------------------------------------- 1 | id,groupId,name,email,memo 2 | 100,100,M1-0,user0@m1.example.co.jp, 3 | 101,100,M1-1,user1@m1.example.co.jp, 4 | 102,100,M1-2,user2@m1.example.co.jp, 5 | 200,200,M2-0,user0@m2.example.co.jp, 6 | 201,200,M2-1,user1@m2.example.co.jp, 7 | 202,200,M2-2,user2@m2.example.co.jp, 8 | 300,300,M3-0,user0@m3.example.co.jp, 9 | 301,300,M3-1,user1@m3.example.co.jp, 10 | 1000,1000,B1-1,B1-1@bp1.example.co.jp, 11 | 1010,1010,B2-1,B2-1@bp2.example.co.jp, 12 | 1020,1020,B3-1,B3-1@bp3.example.co.jp, 13 | 1021,1020,B3-2,B3-2@bp3.example.co.jp, 14 | 1030,1030,B4-1,B4-1@bp4.example.co.jp, 15 | 1031,1030,B4-2,B4-2@bp4.example.co.jp, 16 | 1032,1030,B4-3,B4-3@bp4.example.co.jp, 17 | 1033,1030,B4-4,B4-4@bp4.example.co.jp, 18 | -------------------------------------------------------------------------------- /frontend/store/TodoStore.ts: -------------------------------------------------------------------------------- 1 | import { Module, VuexModule, Mutation, Action } from 'vuex-module-decorators' 2 | import { TodoEntity } from '~/models/Todo' 3 | 4 | @Module({ 5 | name: 'TodoStore', 6 | stateFactory: true, 7 | namespaced: true 8 | }) 9 | export default class TodoStore extends VuexModule { 10 | _todos: TodoEntity[] = []; 11 | 12 | get todos(): TodoEntity[] { 13 | return this._todos 14 | } 15 | 16 | @Mutation 17 | setTodos(todos: TodoEntity[]) { 18 | this._todos = todos.map(t => JSON.parse(JSON.stringify(t))) 19 | } 20 | 21 | @Action 22 | async loadTodos($axios: any) { 23 | const { data } = await $axios.get('/todos') 24 | this.setTodos(data) 25 | return this._todos 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /backend/tools/import-users.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs') 2 | const { parse } = require('csv-parse/sync') 3 | const bcrypt = require('bcrypt') 4 | 5 | const accounts = require('../db/accounts.json') 6 | 7 | // accounts.csv に含まれるユーザー 8 | let importAccounts = parse( 9 | fs.readFileSync('../csv-files/accounts.csv', 'utf-8'), 10 | { 11 | columns: true 12 | } 13 | ) 14 | // default password 追加 15 | importAccounts = importAccounts.map(u => { 16 | return { 17 | ...u, 18 | password: bcrypt.hashSync('testtest', 10) 19 | } 20 | }) 21 | // 存在しないユーザーであれば追加 22 | importAccounts.forEach(u => { 23 | if(!accounts.find(uu => uu.id == u.id)) { 24 | accounts.push(u) 25 | } 26 | }) 27 | fs.writeFileSync('../db/accounts.json', JSON.stringify(accounts), 'utf-8') 28 | -------------------------------------------------------------------------------- /frontend/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES2018", 4 | "module": "ESNext", 5 | "moduleResolution": "Node", 6 | "lib": [ 7 | "ESNext", 8 | "ESNext.AsyncIterable", 9 | "DOM" 10 | ], 11 | "esModuleInterop": true, 12 | "allowJs": true, 13 | "sourceMap": true, 14 | "strict": true, 15 | "noEmit": true, 16 | "experimentalDecorators": true, 17 | "baseUrl": ".", 18 | "paths": { 19 | "~/*": [ 20 | "./*" 21 | ], 22 | "@/*": [ 23 | "./*" 24 | ] 25 | }, 26 | "types": [ 27 | "@nuxt/types", 28 | "@nuxtjs/axios", 29 | "@nuxt/content", 30 | "@types/node" 31 | ] 32 | }, 33 | "exclude": [ 34 | "node_modules", 35 | ".nuxt", 36 | "dist" 37 | ] 38 | } 39 | -------------------------------------------------------------------------------- /frontend/store/ConfigStore.ts: -------------------------------------------------------------------------------- 1 | import { Module, VuexModule, Mutation, Action } from 'vuex-module-decorators' 2 | import { ConfigEntity, newConfigEntity, PluginEntity } from '~/models/Config' 3 | 4 | @Module({ 5 | name: 'ConfigStore', 6 | stateFactory: true, 7 | namespaced: true 8 | }) 9 | export default class ConfigStore extends VuexModule { 10 | _config: ConfigEntity = newConfigEntity(); 11 | 12 | get config(): ConfigEntity { 13 | return JSON.parse(JSON.stringify(this._config)); 14 | } 15 | 16 | @Mutation 17 | setConfig(e: ConfigEntity) { 18 | this._config = e; 19 | } 20 | 21 | @Mutation 22 | setPlugins(plugins: PluginEntity[]) { 23 | this._config.plugins = plugins.map(p => Object.assign({}, p)) 24 | } 25 | 26 | @Mutation 27 | setZoom(zoom: number) { 28 | this._config.zoom = zoom 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /backend/utils/TaskUtil.js: -------------------------------------------------------------------------------- 1 | const path = require('path') 2 | const fs = require('fs') 3 | 4 | const db = path.join(process.cwd(), 'db') 5 | 6 | class TaskUtil { 7 | static _tasks = null 8 | 9 | static init() { 10 | TaskUtil.load() 11 | } 12 | 13 | static load() { 14 | TaskUtil._tasks = JSON.parse( 15 | fs.readFileSync(path.join(db, 'tasks.json'), 'utf8') 16 | ) 17 | } 18 | static get() { 19 | return TaskUtil._tasks 20 | } 21 | static set(tasks) { 22 | TaskUtil._tasks = tasks 23 | } 24 | 25 | static save(tasks = null) { 26 | if(tasks) { 27 | TaskUtil.set(tasks) 28 | } 29 | fs.writeFileSync( 30 | path.join(db, 'tasks.json'), 31 | JSON.stringify(TaskUtil._tasks) 32 | ) 33 | } 34 | } 35 | 36 | module.exports = TaskUtil 37 | -------------------------------------------------------------------------------- /backend/utils/TodoUtil.js: -------------------------------------------------------------------------------- 1 | const path = require('path') 2 | const fs = require('fs') 3 | 4 | const db = path.join(process.cwd(), 'db') 5 | 6 | class TodoUtil { 7 | static _todos = null 8 | 9 | static init() { 10 | TodoUtil.load() 11 | } 12 | 13 | static load() { 14 | TodoUtil._todos = JSON.parse( 15 | fs.readFileSync(path.join(db, 'todos.json'), 'utf8') 16 | ) 17 | } 18 | static get() { 19 | return TodoUtil._todos 20 | } 21 | static set(todos) { 22 | TodoUtil._todos = todos 23 | } 24 | 25 | static save(todos = null) { 26 | if(todos) { 27 | TodoUtil.set(todos) 28 | } 29 | fs.writeFileSync( 30 | path.join(db, 'todos.json'), 31 | JSON.stringify(TodoUtil._todos) 32 | ) 33 | } 34 | } 35 | 36 | module.exports = TodoUtil 37 | -------------------------------------------------------------------------------- /frontend/layouts/error.vue: -------------------------------------------------------------------------------- 1 | 14 | 15 | 39 | 40 | 45 | -------------------------------------------------------------------------------- /frontend/content/hello.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Getting started 3 | description: 'Empower your NuxtJS application with @nuxt/content module: write in a content/ directory and fetch your Markdown, JSON, YAML and CSV files through a MongoDB like API, acting as a Git-based Headless CMS.' 4 | --- 5 | 6 | Empower your NuxtJS application with `@nuxtjs/content` module: write in a `content/` directory and fetch your Markdown, JSON, YAML and CSV files through a MongoDB like API, acting as a **Git-based Headless CMS**. 7 | 8 | ## Writing content 9 | 10 | Learn how to write your `content/`, supporting Markdown, YAML, CSV and JSON: https://content.nuxtjs.org/writing. 11 | 12 | ## Fetching content 13 | 14 | Learn how to fetch your content with `$content`: https://content.nuxtjs.org/fetching. 15 | 16 | ## Displaying content 17 | 18 | Learn how to display your Markdown content with the `` component directly in your template: https://content.nuxtjs.org/displaying. -------------------------------------------------------------------------------- /backend/router/tasks.js: -------------------------------------------------------------------------------- 1 | const path = require('path') 2 | const fs = require('fs') 3 | const router = require('express').Router() 4 | 5 | const TaskUtil = require('../utils/TaskUtil') 6 | 7 | const db = path.join(process.cwd(), 'db') 8 | 9 | router.get('/', (req, res, next) => { 10 | res.json(TaskUtil.get()) 11 | }) 12 | router.post('/', (req, res, next) => { 13 | TaskUtil.save(req.body) 14 | res.json({ 15 | result: 'success' 16 | }) 17 | }) 18 | router.get('/:id', (req, res, next) => { 19 | const id = req.params.id 20 | 21 | try { 22 | const json = fs.readFileSync(path.join(db, `tasks/${id}.json`), 'utf8') 23 | res.json(JSON.parse(json)) 24 | } catch(e) { 25 | res.json({ 26 | content: '' 27 | }) 28 | } 29 | }) 30 | router.post('/:id', (req, res, next) => { 31 | const id = req.params.id 32 | 33 | fs.writeFileSync(path.join(db, `tasks/${id}.json`), JSON.stringify(req.body)) 34 | res.json({ 35 | result: 'success' 36 | }) 37 | }) 38 | 39 | module.exports = router -------------------------------------------------------------------------------- /backend/router/auth.js: -------------------------------------------------------------------------------- 1 | const path = require('path') 2 | const bcrypt = require('bcrypt') 3 | 4 | const router = require('express').Router() 5 | 6 | const common = require('../common') 7 | 8 | const db = path.join(process.cwd(), 'db') 9 | const users = require(path.join(db, 'accounts.json')) 10 | 11 | router.post('/login', (req, res) => { 12 | const { email, password } = req.body 13 | 14 | const user = users.find(u => u.email === email) 15 | if(!user || !bcrypt.compareSync(password, user.password)) { 16 | return res.status(400).json({"error": "invalid"}) 17 | } 18 | const payload = { 19 | id: user.id, 20 | name: user.name, 21 | email: user.email 22 | } 23 | const token = common.getToken(payload) 24 | return res.json({ token }) 25 | }) 26 | router.post('/logout', (req, res) => { 27 | return res.sendStatus(200) 28 | }) 29 | 30 | router.get('/user', (req, res) => { 31 | const user = common.getUserFromBearerToken(req.headers['authorization']) 32 | if(user) { 33 | return res.json({ 34 | user 35 | }) 36 | } 37 | return res.sendStatus(403) 38 | }) 39 | 40 | module.exports = router -------------------------------------------------------------------------------- /backend/common.js: -------------------------------------------------------------------------------- 1 | const jwt = require('jsonwebtoken') 2 | 3 | // jwttoken secret. 置き換えてください。 4 | const secret = 'secret' 5 | 6 | class Common { 7 | static getUserFromBearerToken (bearerToken) { 8 | if(!bearerToken) { 9 | return false 10 | } 11 | const bearer = bearerToken.split(' ') 12 | if(bearer.length < 2) { 13 | return false 14 | } 15 | return jwt.verify(bearer[1], secret) 16 | } 17 | 18 | /** 19 | * 前処理 20 | * - ログ 21 | * - 認証 22 | * @param {*} req 23 | * @param {*} res 24 | * @param {*} next 25 | * @returns 26 | */ 27 | static preProcess (req, res, next) { 28 | console.log(`+ ${req.method} ${req.originalUrl} params=${JSON.stringify(req.params)}`) 29 | 30 | const user = Common.getUserFromBearerToken(req.headers['authorization']) 31 | if(!user) { 32 | if(![ '/', '/login' ].includes(req.originalUrl)) { 33 | res.status(401).send('Unauthorized') 34 | return 35 | } 36 | } 37 | next() 38 | } 39 | 40 | static getToken (payload) { 41 | return jwt.sign(payload, secret) 42 | } 43 | } 44 | module.exports = Common 45 | -------------------------------------------------------------------------------- /frontend/models/Config.ts: -------------------------------------------------------------------------------- 1 | export interface PluginEntity { 2 | name: string 3 | icon: string 4 | url: string 5 | enabled: boolean 6 | } 7 | 8 | export interface ConfigEntity { 9 | zoom: number 10 | closeProject: number[] 11 | managementBeginDate: string 12 | 13 | plugins: PluginEntity[] 14 | } 15 | 16 | export const PLUGINS = [ 17 | { 18 | name: 'Google Calendar', 19 | icon: 'mdi-calendar', 20 | url: 'https://calendar.google.com/calendar/u/0/r', 21 | enabled: false 22 | }, 23 | { 24 | name: 'Google Drive', 25 | icon: 'mdi-folder-google-drive', 26 | url: 'https://drive.google.com/drive/my-drive', 27 | enabled: false 28 | }, 29 | { 30 | name: 'Google meet', 31 | icon: 'mdi-video', 32 | url: 'https://meet.google.com/', 33 | enabled: false 34 | }, 35 | { 36 | name: 'slack', 37 | icon: 'mdi-slack', 38 | url: 'https://app.slack.com/client/', 39 | enabled: false 40 | }, 41 | { 42 | name: 'clickup', 43 | icon: 'cu', 44 | url: 'https://app.clickup.com/', 45 | enabled: false 46 | }, 47 | 48 | 49 | ] 50 | 51 | export function newConfigEntity() { 52 | return { 53 | zoom: 1, 54 | closeProject: [], 55 | managementBeginDate: '', 56 | 57 | plugins: PLUGINS 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /frontend/.gitignore: -------------------------------------------------------------------------------- 1 | # Created by .ignore support plugin (hsz.mobi) 2 | ### Node template 3 | # Logs 4 | /logs 5 | *.log 6 | npm-debug.log* 7 | yarn-debug.log* 8 | yarn-error.log* 9 | 10 | # Runtime data 11 | pids 12 | *.pid 13 | *.seed 14 | *.pid.lock 15 | 16 | # Directory for instrumented libs generated by jscoverage/JSCover 17 | lib-cov 18 | 19 | # Coverage directory used by tools like istanbul 20 | coverage 21 | 22 | # nyc test coverage 23 | .nyc_output 24 | 25 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 26 | .grunt 27 | 28 | # Bower dependency directory (https://bower.io/) 29 | bower_components 30 | 31 | # node-waf configuration 32 | .lock-wscript 33 | 34 | # Compiled binary addons (https://nodejs.org/api/addons.html) 35 | build/Release 36 | 37 | # Dependency directories 38 | node_modules/ 39 | jspm_packages/ 40 | 41 | # TypeScript v1 declaration files 42 | typings/ 43 | 44 | # Optional npm cache directory 45 | .npm 46 | 47 | # Optional eslint cache 48 | .eslintcache 49 | 50 | # Optional REPL history 51 | .node_repl_history 52 | 53 | # Output of 'npm pack' 54 | *.tgz 55 | 56 | # Yarn Integrity file 57 | .yarn-integrity 58 | 59 | # dotenv environment variables file 60 | .env 61 | 62 | # parcel-bundler cache (https://parceljs.org/) 63 | .cache 64 | 65 | # next.js build output 66 | .next 67 | 68 | # nuxt.js build output 69 | .nuxt 70 | 71 | # Nuxt generate 72 | dist 73 | 74 | # vuepress build output 75 | .vuepress/dist 76 | 77 | # Serverless directories 78 | .serverless 79 | 80 | # IDE / Editor 81 | .idea 82 | 83 | # Service worker 84 | sw.* 85 | 86 | # macOS 87 | .DS_Store 88 | 89 | # Vim swap files 90 | *.swp 91 | -------------------------------------------------------------------------------- /backend/router/accounts.js: -------------------------------------------------------------------------------- 1 | const path = require('path') 2 | const fs = require('fs') 3 | const bcrypt = require('bcrypt') 4 | 5 | const router = require('express').Router() 6 | 7 | const db = path.join(process.cwd(), 'db') 8 | const accounts = require(path.join(db, 'accounts.json')) 9 | 10 | router.post('/:id/change-password', (req, res, next) => { 11 | const { id } = req.params 12 | const { oldPassword, password } = req.body 13 | 14 | const account = accounts.find(u => u.id === id) 15 | if(!account || !bcrypt.compareSync(oldPassword, account.password)) { 16 | return res.status(400).json({"error": "invalid"}) 17 | } 18 | account.password = bcrypt.hashSync(password, 10) 19 | 20 | fs.writeFileSync(path.join(db, `accounts.json`), JSON.stringify(accounts), 'utf-8') 21 | res.json({ 22 | result: 'success' 23 | }) 24 | }) 25 | 26 | router.get('/:id/config', (req, res, next) => { 27 | const { id } = req.params 28 | const f = path.join(db, `accounts/${id}/config.json`) 29 | 30 | let json = {} 31 | if(fs.existsSync(f)) { 32 | json = JSON.parse(fs.readFileSync(f, 'utf8')) 33 | } 34 | // data 変更対応 35 | if(!json.zoom) { 36 | json.zoom = 1 37 | } 38 | res.json(json) 39 | }) 40 | 41 | 42 | router.post('/:id/config', (req, res, next) => { 43 | const { id } = req.params 44 | const config = req.body 45 | const d = path.join(db, `accounts/${id}/`) 46 | 47 | if(!fs.existsSync(d)) { 48 | fs.mkdirSync(d) 49 | } 50 | 51 | fs.writeFileSync(path.join(d, 'config.json'), JSON.stringify(config), 'utf-8') 52 | res.json({ 53 | result: 'success' 54 | }) 55 | }) 56 | 57 | module.exports = router -------------------------------------------------------------------------------- /frontend/components/scss/wbs-common.scss: -------------------------------------------------------------------------------- 1 | $color-holiday: #F8BBD0; 2 | 3 | $color-header: #eee; 4 | 5 | $time-grid-x: 5px; 6 | $line-height: 24px; 7 | $task-height: 20px; 8 | $plan-height: 16px; 9 | $project-height: 12px; 10 | 11 | $task-margin-top: 4px; 12 | $task-indent-size: 16px; 13 | 14 | $time-line-max-term: 580; 15 | 16 | // d : 1 - 7 17 | @mixin background-date-grid($d, $zoom) { 18 | $xx: $time-grid-x * $zoom; 19 | $base-grid-colors: (#ddd #ddd #ddd #ddd #ddd #ddd #ddd); 20 | $grid-colors: set-nth($base-grid-colors, $d, #888); 21 | 22 | $sun: ($d + 6) % 7 + 1; 23 | $sat: ($d + 5) % 7 + 1; 24 | 25 | background-image: 26 | linear-gradient( 27 | 90deg, 28 | transparent #{$xx - 1}, nth($grid-colors, 1), $xx, transparent #{$xx + 1}, 29 | transparent #{$xx * 2 - 1}, nth($grid-colors, 2), $xx * 2, transparent #{$xx * 2 + 1}, 30 | transparent #{$xx * 3 - 1}, nth($grid-colors, 3), $xx * 3, transparent #{$xx * 3 + 1}, 31 | transparent #{$xx * 4 - 1}, nth($grid-colors, 4), $xx * 4, transparent #{$xx * 4 + 1}, 32 | transparent #{$xx * 5 - 1}, nth($grid-colors, 5), $xx * 5, transparent #{$xx * 5 + 1}, 33 | transparent #{$xx * 6 - 1}, nth($grid-colors, 6), $xx * 6, transparent #{$xx * 6 + 1}, 34 | transparent #{$xx * 7 - 1}, nth($grid-colors, 7) 35 | ), 36 | linear-gradient( 37 | 90deg, 38 | transparent #{$xx * $sat - $xx}, $color-holiday #{$xx * $sat - ($xx - 1)}, $color-holiday, $xx * $sat, transparent #{$xx * $sat + 1} 39 | ), 40 | linear-gradient( 41 | 90deg, 42 | transparent #{$xx * $sun - $xx}, $color-holiday #{$xx * $sun - ($xx - 1)}, $color-holiday, $xx * $sun, transparent #{$xx * $sun + 1}, 43 | ); 44 | } 45 | -------------------------------------------------------------------------------- /frontend/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "wbs", 3 | "version": "1.0.0", 4 | "private": true, 5 | "scripts": { 6 | "dev": "nuxt", 7 | "build": "nuxt build", 8 | "build:prod": "cross-env NODE_ENV=production nuxt build", 9 | "start": "nuxt start", 10 | "start:prod": "cross-env NODE_ENV=production nuxt start", 11 | "generate": "nuxt generate", 12 | "lint:js": "eslint --ext \".js,.vue\" --ignore-path .gitignore .", 13 | "lint": "yarn lint:js" 14 | }, 15 | "dependencies": { 16 | "@nuxt/content": "^1.14.0", 17 | "@nuxtjs/auth": "^4.9.1", 18 | "@nuxtjs/axios": "^5.13.6", 19 | "@nuxtjs/pwa": "^3.3.5", 20 | "axios": "^0.24.0", 21 | "core-js": "^3.15.1", 22 | "cross-env": "^7.0.3", 23 | "csv-parse": "^5.0.4", 24 | "dateformat": "^5.0.2", 25 | "nuxt": "^2.15.7", 26 | "socket.io-client": "^4.4.1", 27 | "splitpanes": "^2.3.8", 28 | "tiptap-vuetify": "^2.24.0", 29 | "vue-draggable-resizable": "^2.3.0", 30 | "vue-json-to-csv": "^1.1.8", 31 | "vue-nestable": "^2.6.0", 32 | "vuetify": "^2.5.5", 33 | "vuex-module-decorators": "^2.0.0", 34 | "xl-formula": "^1.0.3" 35 | }, 36 | "devDependencies": { 37 | "@babel/eslint-parser": "^7.14.7", 38 | "@nuxt/types": "^2.15.7", 39 | "@nuxt/typescript-build": "^2.1.0", 40 | "@nuxtjs/eslint-config-typescript": "^6.0.1", 41 | "@nuxtjs/eslint-module": "^3.0.2", 42 | "@nuxtjs/vuetify": "^1.12.1", 43 | "@types/dateformat": "^5.0.0", 44 | "@types/splitpanes": "^2.2.1", 45 | "eslint": "^7.29.0", 46 | "eslint-config-prettier": "^8.3.0", 47 | "eslint-plugin-nuxt": "^2.0.0", 48 | "eslint-plugin-vue": "^7.12.1", 49 | "prettier": "^2.3.2" 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /frontend/components/LoginComponent.vue: -------------------------------------------------------------------------------- 1 | 45 | 75 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | lerna-debug.log* 8 | 9 | # Diagnostic reports (https://nodejs.org/api/report.html) 10 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 11 | 12 | # Runtime data 13 | pids 14 | *.pid 15 | *.seed 16 | *.pid.lock 17 | 18 | # Directory for instrumented libs generated by jscoverage/JSCover 19 | lib-cov 20 | 21 | # Coverage directory used by tools like istanbul 22 | coverage 23 | *.lcov 24 | 25 | # nyc test coverage 26 | .nyc_output 27 | 28 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 29 | .grunt 30 | 31 | # Bower dependency directory (https://bower.io/) 32 | bower_components 33 | 34 | # node-waf configuration 35 | .lock-wscript 36 | 37 | # Compiled binary addons (https://nodejs.org/api/addons.html) 38 | build/Release 39 | 40 | # Dependency directories 41 | node_modules/ 42 | jspm_packages/ 43 | 44 | # TypeScript v1 declaration files 45 | typings/ 46 | 47 | # TypeScript cache 48 | *.tsbuildinfo 49 | 50 | # Optional npm cache directory 51 | .npm 52 | 53 | # Optional eslint cache 54 | .eslintcache 55 | 56 | # Microbundle cache 57 | .rpt2_cache/ 58 | .rts2_cache_cjs/ 59 | .rts2_cache_es/ 60 | .rts2_cache_umd/ 61 | 62 | # Optional REPL history 63 | .node_repl_history 64 | 65 | # Output of 'npm pack' 66 | *.tgz 67 | 68 | # Yarn Integrity file 69 | .yarn-integrity 70 | 71 | # dotenv environment variables file 72 | .env 73 | .env.test 74 | 75 | # parcel-bundler cache (https://parceljs.org/) 76 | .cache 77 | 78 | # Next.js build output 79 | .next 80 | 81 | # Nuxt.js build / generate output 82 | .nuxt 83 | dist 84 | 85 | # Gatsby files 86 | .cache/ 87 | # Comment in the public line in if your project uses Gatsby and *not* Next.js 88 | # https://nextjs.org/blog/next-9-1#public-directory-support 89 | # public 90 | 91 | # vuepress build output 92 | .vuepress/dist 93 | 94 | # Serverless directories 95 | .serverless/ 96 | 97 | # FuseBox cache 98 | .fusebox/ 99 | 100 | # DynamoDB Local files 101 | .dynamodb/ 102 | 103 | # TernJS port file 104 | .tern-port 105 | frontend/env.production.js 106 | -------------------------------------------------------------------------------- /frontend/README.md: -------------------------------------------------------------------------------- 1 | # wbs 2 | 3 | ## Build Setup 4 | 5 | ```bash 6 | # install dependencies 7 | $ yarn install 8 | 9 | # serve with hot reload at localhost:3000 10 | $ yarn dev 11 | 12 | # build for production and launch server 13 | $ yarn build 14 | $ yarn start 15 | 16 | # generate static project 17 | $ yarn generate 18 | ``` 19 | 20 | For detailed explanation on how things work, check out the [documentation](https://nuxtjs.org). 21 | 22 | ## Special Directories 23 | 24 | You can create the following extra directories, some of which have special behaviors. Only `pages` is required; you can delete them if you don't want to use their functionality. 25 | 26 | ### `assets` 27 | 28 | The assets directory contains your uncompiled assets such as Stylus or Sass files, images, or fonts. 29 | 30 | More information about the usage of this directory in [the documentation](https://nuxtjs.org/docs/2.x/directory-structure/assets). 31 | 32 | ### `components` 33 | 34 | The components directory contains your Vue.js components. Components make up the different parts of your page and can be reused and imported into your pages, layouts and even other components. 35 | 36 | More information about the usage of this directory in [the documentation](https://nuxtjs.org/docs/2.x/directory-structure/components). 37 | 38 | ### `layouts` 39 | 40 | Layouts are a great help when you want to change the look and feel of your Nuxt app, whether you want to include a sidebar or have distinct layouts for mobile and desktop. 41 | 42 | More information about the usage of this directory in [the documentation](https://nuxtjs.org/docs/2.x/directory-structure/layouts). 43 | 44 | 45 | ### `pages` 46 | 47 | This directory contains your application views and routes. Nuxt will read all the `*.vue` files inside this directory and setup Vue Router automatically. 48 | 49 | More information about the usage of this directory in [the documentation](https://nuxtjs.org/docs/2.x/get-started/routing). 50 | 51 | ### `plugins` 52 | 53 | The plugins directory contains JavaScript plugins that you want to run before instantiating the root Vue.js Application. This is the place to add Vue plugins and to inject functions or constants. Every time you need to use `Vue.use()`, you should create a file in `plugins/` and add its path to plugins in `nuxt.config.js`. 54 | 55 | More information about the usage of this directory in [the documentation](https://nuxtjs.org/docs/2.x/directory-structure/plugins). 56 | 57 | ### `static` 58 | 59 | This directory contains your static files. Each file inside this directory is mapped to `/`. 60 | 61 | Example: `/static/robots.txt` is mapped as `/robots.txt`. 62 | 63 | More information about the usage of this directory in [the documentation](https://nuxtjs.org/docs/2.x/directory-structure/static). 64 | 65 | ### `store` 66 | 67 | This directory contains your Vuex store files. Creating a file in this directory automatically activates Vuex. 68 | 69 | More information about the usage of this directory in [the documentation](https://nuxtjs.org/docs/2.x/directory-structure/store). 70 | -------------------------------------------------------------------------------- /frontend/layouts/default.vue: -------------------------------------------------------------------------------- 1 | 61 | 62 | 110 | 115 | -------------------------------------------------------------------------------- /backend/app.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs') 2 | const crypto = require('crypto') 3 | const express = require('express') 4 | const cors = require('cors') 5 | const session = require('express-session') 6 | 7 | const TaskUtil = require('./utils/TaskUtil') 8 | const TodoUtil = require('./utils/TodoUtil') 9 | 10 | 11 | const config = require('./config.js') 12 | 13 | const _sessionSetting = { 14 | secret: 'simple-wbs-api-server', 15 | resave: false, 16 | saveUninitialized: false, 17 | cookie:{ 18 | httpOnly: true, 19 | secure: false, 20 | maxage: 1000 * 60 * 30 21 | } 22 | } 23 | 24 | const app = express() 25 | const http = require('http').Server(app) 26 | const io = require('socket.io')(http, { 27 | cors: { 28 | origin: config.cors.origin, 29 | methods: ['GET', 'POST'] 30 | } 31 | }) 32 | app.use(cors(config.cors)) 33 | app.use(express.json()) 34 | app.use(express.urlencoded({extended: true})) 35 | 36 | app.use(session(_sessionSetting)) 37 | 38 | 39 | const common = require('./common.js') 40 | 41 | app.get('/', common.preProcess, (req, res) => { 42 | if(!req.session.viewCount) { 43 | req.session.viewCount = 0 44 | } 45 | req.session.viewCount++ 46 | res.status(200).send(`Hello : ${req.session.viewCount}`) 47 | }) 48 | 49 | app.use('/api/auth', require('./router/auth')) 50 | 51 | app.use('/api/accounts', common.preProcess, require('./router/accounts')) 52 | 53 | app.use('/api/tasks', common.preProcess, require('./router/tasks')) 54 | app.use('/api/resources', common.preProcess, require('./router/resources')) 55 | app.use('/api/groups', common.preProcess, require('./router/groups')) 56 | app.use('/api/holidays', common.preProcess, require('./router/holidays')) 57 | app.use('/api/todos', common.preProcess, require('./router/todos')) 58 | 59 | TaskUtil.init() 60 | TodoUtil.init() 61 | 62 | let clients = [] 63 | io.on('connection', (socket) => { 64 | console.log('connect : ' + socket.id) 65 | clients.push({ id: socket.id }) 66 | 67 | // トークンを作成 68 | const token = makeToken(socket.id) 69 | // 本人にトークンを送付 70 | io.to(socket.id).emit("onToken", {token:token}) 71 | 72 | socket.on('join', (o) => { 73 | const client = clients.find(c => c.id === socket.id) 74 | client.name = o.name 75 | console.log('# join()') 76 | io.emit('onUpdateClients', clients) 77 | console.log(clients) 78 | }) 79 | 80 | socket.on('updateTasks', (o) => { 81 | TaskUtil.set(o.tasks) 82 | io.emit('onUpdateTasks', o) 83 | }) 84 | 85 | socket.on('updateTodos', (o) => { 86 | TodoUtil.save(o.todos) 87 | io.emit('onUpdateTodos', o) 88 | }) 89 | 90 | socket.on('disconnect', () => { 91 | console.log('disconnect : ' + socket.id) 92 | clients = clients.filter(c => c.id !== socket.id) 93 | io.emit('onUpdateClients', clients) 94 | console.log(clients) 95 | }) 96 | }) 97 | 98 | http.listen(3010, () => { 99 | console.log('+ app start') 100 | }) 101 | 102 | 103 | /** 104 | * トークンを作成する 105 | * 106 | * @param {string} id - socket.id 107 | * @return {string} 108 | */ 109 | function makeToken(id){ 110 | const str = "aqwsedrftgyhujiko" + id; 111 | return( crypto.createHash("sha1").update(str).digest('hex') ); 112 | } -------------------------------------------------------------------------------- /frontend/nuxt.config.js: -------------------------------------------------------------------------------- 1 | import colors from 'vuetify/es5/util/colors' 2 | 3 | const environment = process.env.NODE_ENV || 'development'; 4 | const env = require(`./env.${environment}.js`) 5 | 6 | console.log(env) 7 | 8 | export default { 9 | env, 10 | 11 | publicRuntimeConfig: { 12 | apiServer: env.apiServer 13 | }, 14 | 15 | // Disable server-side rendering: https://go.nuxtjs.dev/ssr-mode 16 | ssr: false, 17 | 18 | // Target: https://go.nuxtjs.dev/config-target 19 | target: 'static', 20 | 21 | // Global page headers: https://go.nuxtjs.dev/config-head 22 | head: { 23 | titleTemplate: '%s - agile gantt', 24 | title: 'wbs', 25 | meta: [ 26 | { charset: 'utf-8' }, 27 | { name: 'viewport', content: 'width=device-width, initial-scale=1' }, 28 | { hid: 'description', name: 'description', content: '' }, 29 | { name: 'format-detection', content: 'telephone=no' } 30 | ], 31 | link: [ 32 | { rel: 'icon', type: 'image/x-icon', href: '/favicon.ico' } 33 | ] 34 | }, 35 | 36 | // Global CSS: https://go.nuxtjs.dev/config-css 37 | css: [ 38 | ], 39 | 40 | // Plugins to run before rendering page: https://go.nuxtjs.dev/config-plugins 41 | plugins: [ 42 | { src: '~/plugins/TiptapVuetify', mode: 'client' } 43 | ], 44 | 45 | // Auto import components: https://go.nuxtjs.dev/config-components 46 | components: true, 47 | 48 | // Modules for dev and build (recommended): https://go.nuxtjs.dev/config-modules 49 | buildModules: [ 50 | // https://go.nuxtjs.dev/typescript 51 | '@nuxt/typescript-build', 52 | // https://go.nuxtjs.dev/vuetify 53 | '@nuxtjs/vuetify', 54 | ], 55 | 56 | // Modules: https://go.nuxtjs.dev/config-modules 57 | modules: [ 58 | '@nuxtjs/axios', 59 | '@nuxtjs/auth', 60 | '@nuxtjs/pwa', 61 | '@nuxt/content', 62 | ], 63 | axios: { 64 | baseURL: env.apiBaseUrl 65 | }, 66 | 67 | auth: { 68 | strategies: { 69 | local: { 70 | endpoints: { 71 | login: { 72 | url: '/auth/login', 73 | method: 'post', 74 | propertyName: 'token' 75 | }, 76 | logout: { 77 | url: '/auth/logout', 78 | method: 'post', 79 | propertyName: 'token' 80 | }, 81 | user: { 82 | url: '/auth/user', 83 | method: 'get', 84 | propertyName: 'user' 85 | } 86 | } 87 | } 88 | } 89 | }, 90 | 91 | // PWA module configuration: https://go.nuxtjs.dev/pwa 92 | pwa: { 93 | manifest: { 94 | lang: 'en' 95 | } 96 | }, 97 | 98 | // Content module configuration: https://go.nuxtjs.dev/config-content 99 | content: {}, 100 | 101 | // Vuetify module configuration: https://go.nuxtjs.dev/config-vuetify 102 | vuetify: { 103 | treeShake: false, 104 | customVariables: ['~/assets/variables.scss'], 105 | theme: { 106 | dark: false, 107 | themes: { 108 | dark: { 109 | primary: colors.blue.darken2, 110 | accent: colors.grey.darken3, 111 | secondary: colors.amber.darken3, 112 | info: colors.teal.lighten1, 113 | warning: colors.amber.base, 114 | error: colors.deepOrange.accent4, 115 | success: colors.green.accent3 116 | } 117 | } 118 | } 119 | }, 120 | 121 | // Build Configuration: https://go.nuxtjs.dev/config-build 122 | build: { 123 | extractCSS: true 124 | }, 125 | 126 | router: { 127 | middleware: [ 128 | 'authenticator' 129 | ] 130 | } 131 | } 132 | -------------------------------------------------------------------------------- /frontend/models/Task.ts: -------------------------------------------------------------------------------- 1 | import { ResourceEntity } from './Resource' 2 | import DateUtil from '~/utils/DateUtil' 3 | 4 | export interface Timeline { 5 | x: number 6 | w: number 7 | } 8 | 9 | export interface TaskEntity { 10 | id: number 11 | name: string 12 | assignee: ResourceEntity | null 13 | estimate: number // 見積時点の数字 14 | plan: number // 計画の実日数(休日含む) 15 | planManHour: number 16 | assignRate: number // 稼働(期間と稼働は別) 17 | progress: number 18 | beginDate: number // yyyymmww 19 | timeline: Timeline 20 | children: TaskEntity[] // sub tasks. 21 | parent: TaskEntity | null 22 | select: boolean 23 | showChildren: boolean 24 | show: boolean 25 | hasDetail: boolean 26 | } 27 | 28 | export interface TaskRecord { 29 | id: number 30 | name: string 31 | assigneeId: number 32 | estimate: number // 見積時点の数字 33 | plan: number // 計画の実日数(休日含む) 34 | planManHour: number 35 | assignRate: number // 稼働(期間と稼働は別) 36 | progress: number 37 | beginDate: number // yyyymmww 38 | parentId: number 39 | hasDetail: boolean 40 | } 41 | 42 | export function newTaskEntity(id: number): TaskEntity { 43 | const beginDate = new Date() 44 | const plan = 28 45 | const planManHour = DateUtil.newtworkDays(beginDate, plan) 46 | const timeline = { 47 | x: 0, 48 | w: 0 49 | } 50 | 51 | return { 52 | id, 53 | name: `Task - ${id}`, 54 | assignee: null, 55 | estimate: 1, 56 | plan, 57 | planManHour, 58 | assignRate: 1, 59 | progress: 0, 60 | beginDate: DateUtil.yyyymmddFromDate(beginDate), 61 | timeline, 62 | children: [], 63 | parent: null, 64 | select: false, 65 | showChildren: true, 66 | show: true, 67 | hasDetail: false 68 | } 69 | } 70 | 71 | export function cloneTaskEntity(id: number, o: TaskEntity): TaskEntity { 72 | const t = { 73 | id, 74 | name: o.name, 75 | assignee: o.assignee, 76 | estimate: o.estimate, 77 | plan: o.plan, 78 | planManHour: o.planManHour, 79 | assignRate: o.assignRate, 80 | progress: o.progress, 81 | beginDate: o.beginDate, 82 | timeline: { ...o.timeline }, 83 | children: [], 84 | parent: o.parent, 85 | select: false, 86 | showChildren: o.showChildren, 87 | show: o.show, 88 | hasDetail: false // clone は memo を持たない 89 | } 90 | return t; 91 | } 92 | 93 | export function toTaskRecords(tasks:TaskEntity[]) { 94 | const __tasks:TaskEntity[] = [] 95 | tasks.forEach((t:TaskEntity) => { 96 | __tasks.push(t) 97 | if(t.children.length > 0) { 98 | Array.prototype.push.apply(__tasks, t.children) 99 | } 100 | }) 101 | 102 | const _tasks:any[] = []; 103 | __tasks.forEach(o => { 104 | _tasks.push({ 105 | id: o.id, 106 | name: o.name, 107 | assigneeId: o.assignee?.id || 0, 108 | estimate: o.estimate, 109 | plan: o.plan, 110 | planManHour: o.planManHour, 111 | assignRate: o.assignRate, 112 | progress: o.progress, 113 | beginDate: o.beginDate, 114 | parentId: o.parent?.id, 115 | hasDetail: o.hasDetail 116 | }) 117 | }) 118 | return _tasks; 119 | } 120 | 121 | export function fromTaskRecords(tasks:any[], RESOURCES:any[]) { 122 | tasks.forEach(t => { 123 | t.assignee = t.assigneeId ? RESOURCES.find(r => r.id == t.assigneeId) : null ; 124 | Object.assign(t, { 125 | timeline: { x:0, w:0 }, 126 | children: [], 127 | parent: null, 128 | select: false, 129 | showChildren: true, 130 | show: true 131 | }) 132 | }); 133 | 134 | const parents = tasks.filter(t => !t.parentId) 135 | parents.forEach(p => { 136 | p.children = tasks.filter(t => t.parentId === p.id); 137 | }); 138 | 139 | tasks.forEach(t => { 140 | delete t['assigneeId'] 141 | delete t['parentId'] 142 | }); 143 | 144 | console.log(parents); 145 | return parents; 146 | } 147 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # agile-gantt 2 | 3 | 現役プレイングマネージャのぼくがかんがえたほぼさいきょうのガントチャートツールです。 4 | 5 | ![alt agile gantt](./docs/images/overview.gif) 6 | 7 | アジャイル開発の台頭[^1]によって「柔軟」かつ「短期」の案件が同時多発的に発生[^2]するようになり、また、各案件で要求されるスキルも多様化しており、昔の様に1人月の仕事に1人を適当(?)にアサインするだけでは炎上してしまう案件が増えている様に感じます。 8 | 9 | 開発手法の変化に合わせて、管理手法も変化させなければ対応ができないはずだが、なぜだか[^3]ガントチャートやWBSのツールは進化しおらずDXの波から取り残されている様に思います。 10 | 11 | タスク管理ツールとしては、[clickup](https://clickup.com/) や [Aasana](https://asana.com/) といった優秀なツールがあり愛用していますが、チケットベースの管理ツールは積み上げ型のツールであるため、プロジェクトの予算の概算や人員の割当をざっくり検討する用途では物足りなく感じています。 12 | 13 | 現状は、[taskline](http://mitsuyahiromi.sakura.ne.jp/fswiki/wiki.cgi?page=EXCEL%A5%DE%A5%AF%A5%ED%A4%C7%A5%AC%A5%F3%A5%C8%A5%C1%A5%E3%A1%BC%A5%C8%A4%F2%BA%EE%A4%C3%A4%C6%A4%DF%A4%BF)や、[ProjectLibre](https://ja.osdn.net/projects/sfnet_projectlibre/)などを使い続けたり、高額な[Microsoft Project](https://www.microsoft.com/ja-jp/microsoft-365/project/project-management-software)を横目で見ながらフリーのWebサービスを探すということをしていましたが、「パンがなければケーキを食べればいいじゃない」ということで、自分の管理で必要なものを自分で作ることにしました。 14 | 15 | 必ずしも最適解ではないかもしれませんが、そんなモチベーションで開発した [agile-gantt](https://github.com/kaku3/agile-gantt) のソースを公開したいと思います。 16 | 同じ様な業務を行っている方の助けになれば幸いです。 17 | 18 | 19 | ## 環境要件 20 | 21 | ``` 22 | node 16.13.2 23 | yarn 1.22.4 24 | ``` 25 | 26 | ## 実行 27 | 28 | ### フロントエンド 29 | 30 | ``` 31 | cd frontend 32 | # インストール 33 | yarn install 34 | # 実行 35 | yarn dev 36 | ``` 37 | 38 | http://localhost:3000 にアクセス 39 | 40 | - release 版 41 | 42 | ``` 43 | # env.development.js をコピーして env.production.js を作成し適切に修正。 44 | npm generate # 実行ファイルを dist/ に生成 45 | npm start # サーバ実行 46 | ``` 47 | 48 | 49 | ### バックエンド 50 | 51 | ``` 52 | cd backend 53 | yarn install 54 | node app.js 55 | ``` 56 | 57 | ## コンセプト 58 | 59 | - **「思ったとおり入力・編集できる」** 60 | - ツール作成のモチベーション 61 | - 必要な機能からつくる 62 | - 工数見合いで優先順位をつける 63 | - 設定画面などは適当。動けばいい。ユーザーがついたら改善する 64 | - モチベーション維持のため技術的に面白そうなことの優先順位が上がることがある 65 | - とりあえずDBは使わない 66 | - 導入しやすくするため、環境は最低限に抑える 67 | - そのうちバックエンドはまるっと差し替えできるように実装 68 | - お金があれば firebase を使いたい 69 | - ソースコードが学習サンプルになること 70 | - Nuxt.js 71 | - store 72 | - auth 73 | - vuetify 74 | - express 75 | - socket.io 76 | 77 | 78 | ## ゴール 79 | 80 | WBSが更新されない最大の理由は「めんどうくさい上にメリットが分かりにくいから」だと思います。可能な限りめんどうくさいを取り除いて適切にWBSが更新される状況を作り出して、プロジェクトが見える化されるメリットを共有できる状態をつくることです。 81 | 82 | - 入力が簡単 83 | - リスケが容易にできるドラッグ&ドロップ 84 | - プロジェクトテンプレートで抜け漏れをなくす 85 | - プロジェクトコピペで類似プロジェクト管理を容易に 86 | - リソースの人月管理 87 | - 稼働率 88 | - 工数自動計算 89 | - 割当状況確認 90 | - ざっくり進捗把握 91 | - 警告表示 92 | - 一括管理 93 | - 多人数同時更新対応 94 | - 個人個人で別々のフォーマットでWBS管理を回避 95 | 96 | 97 | ## 要素技術 98 | 99 | - フロントエンド 100 | - [Nuxt.js](https://nuxtjs.org/) 101 | フロントエンドフレームワーク。認証やaxiosなど結局細かく足さないとなので vue-cli で作り始めるより楽。 102 | - [Vuetify](https://vuetifyjs.com/) 103 | UIフレームワーク。element-ui よりモダン。 104 | - [splitpanes](https://www.npmjs.com/package/splitpanes) 105 | スプリッタ 106 | - [vue-nestable](https://www.npmjs.com/package/vue-nestable) 107 | タスク部階層つきドラッグ&ドロップ。VueDraggable(Sortable.jsベース)も試したがこちらを採用。 108 | - [vue-draggable-resizable](https://www.npmjs.com/package/vue-draggable-resizable) 109 | タイムライン部ドラッグ&ドロップ。vue-drag-resize も試したがこちらを採用。 110 | - [socket.io-client](https://www.npmjs.com/package/socket.io-client) 111 | Webリアルタイム通信(クライアント側)。 112 | サンプルコードとして読める様に、jsonをざっくり投げる簡単な実装にとどめた。 113 | - [excel-formula](https://www.npmjs.com/package/excel-formula) 114 | node 用 EXCEL関数。NETWORKDAYS が最高に便利。 115 | - [vue-json-to-csv](https://www.npmjs.com/package/vue-json-to-csv) 116 | jsonデータを csvファイルダウンロード 117 | 118 | - バックエンド 119 | - [express](https://www.npmjs.com/package/express) 120 | Webサーバ 121 | - [socket.io](https://www.npmjs.com/package/socket.io) 122 | Webリアルタイム通信(サーバ側) 123 | サンプルコードとして読める様に、jsonをざっくり投げる簡単な実装にとどめた。 124 | - [jsonwebtoken](https://www.npmjs.com/package/jsonwebtoken) 125 | jwttoken エンコード/デコード 126 | - [bcrypt](https://www.npmjs.com/package/bcrypt) 127 | パスワードハッシュ 128 | 129 | [^1]: 実際は、景気の低迷に伴いIT投資への予算が削減されたことで「低予算短納期」の案件が増え、それに対する対応が求められているという方が正確だと思うが、働き方改革とかDXとかアジャイルとか表現した方がポジティブでよいと思う。 130 | 131 | [^2]: 今にはじまったことではない。予算が半分や1/3になればやり方を変えなければ対応できないと多くの人が感じることができるが、5%や10%カットを繰り返して徐々に下がっているため、なかなかやり方を変える機会がない。 132 | 133 | [^3]: マネジメント層がITに疎く、変化を好まない。またプレイヤー層に比べてマネジメント層は母数も少ないため需要も少ない。 -------------------------------------------------------------------------------- /frontend/components/AccountComponent.vue: -------------------------------------------------------------------------------- 1 | 68 | 140 | -------------------------------------------------------------------------------- /frontend/utils/DateUtil.ts: -------------------------------------------------------------------------------- 1 | import { NETWORKDAYS } from 'xl-formula' 2 | 3 | export default class DateUtil { 4 | /** 5 | * Date を yyyymmww 形式に変換 6 | * @param d Date 7 | * @returns yyyymmdd : number 8 | */ 9 | static yyyymmddFromDate(d: Date) : number { 10 | return d.getFullYear() * 10000 + 11 | (d.getMonth() + 1) * 100 + 12 | d.getDate() 13 | } 14 | /** 15 | * Date を yyyy-mm-dd 形式に変換 16 | * @param d Date 17 | */ 18 | static yyyy_mm_ddFromDate(d: Date) : string { 19 | return `${d.getFullYear()}-${d.getMonth() + 1}-${d.getDate()}` 20 | } 21 | /** 22 | * 23 | * @param yyyymmdd 24 | * @returns 25 | */ 26 | static dateFromYYYYMMDD(yyyymmdd: number) : Date { 27 | const yyyy = Math.floor(yyyymmdd / 10000) 28 | const mm = Math.floor(yyyymmdd / 100) % 100 29 | const dd = yyyymmdd % 100 30 | return new Date(yyyy, mm - 1, dd, 0, 0, 0) 31 | } 32 | 33 | /** 34 | * yyyymmdd : number を { yyyy, mm, dd } として取得 35 | * @param date 36 | * @returns 37 | */ 38 | static yyyymmddObject(date: number) : any { 39 | const yyyy = Math.floor(date / 10000) 40 | const mm = Math.floor(date / 100) % 100 41 | const dd = date % 100 42 | return { 43 | yyyy, 44 | mm, 45 | dd 46 | } 47 | } 48 | 49 | 50 | /** 51 | * 基準日からの日数差分の count:number を返却 52 | * @param baseDate 基準日 53 | * @param date yyyymmdd: number 54 | * @returns 差分 55 | */ 56 | static dateCountFromBaseDate(baseDate: Date, date: number) : number { 57 | const { yyyy, mm, dd } = this.yyyymmddObject(date) 58 | const d:Date = new Date(yyyy, mm - 1, dd) 59 | return (d.getTime() - baseDate.getTime()) / (24 * 60 * 60 * 1000) 60 | } 61 | /** 62 | * 基準日+日数差分の date:Date を返却 63 | * @param baseDate 64 | * @param count 65 | * @returns 66 | */ 67 | static dateFromDateCount(baseDate: Date, count: number) : Date { 68 | return new Date(baseDate.getTime() + count * (24 * 60 * 60 * 1000)) 69 | } 70 | 71 | static yyyymmddFormDateCount(base: number, count: number) : number { 72 | const e = this.dateFromDateCount(this.dateFromYYYYMMDD(base), count) 73 | return this.yyyymmddFromDate(e) 74 | } 75 | 76 | /** 77 | * 基準日+日数差分の yyyymmdd:number を返却 78 | * @param baseDate 79 | * @param count 80 | * @returns 81 | */ 82 | static yyyymmddFromDateCount(baseDate: Date, count: number) : number { 83 | return this.yyyymmddFromDate(this.dateFromDateCount(baseDate, count)) 84 | } 85 | 86 | /** 87 | * 日付の差分を求める 88 | * @param v1 基準日 89 | * @param v2 差分対象日 90 | * @returns 91 | */ 92 | static dateDiff(v1: Date, v2: Date): number { 93 | return (v2.getTime() - v1.getTime()) / (24 * 60 * 60 * 1000) 94 | } 95 | /** 96 | * 日付の差分を求める 97 | * @param v1 yyyymmdd : 基準日 98 | * @param v2 yyyymmdd : 差分対象日 99 | * @returns 100 | */ 101 | static yyyymmddDiff(v1: number, v2: number): number { 102 | return this.dateDiff( 103 | this.dateFromYYYYMMDD(v1), 104 | this.dateFromYYYYMMDD(v2) 105 | ) 106 | } 107 | 108 | static newtworkDays(f: Date, count: number, holidays = []): number { 109 | const t = this.dateFromDateCount(f, count) 110 | return NETWORKDAYS(f, t, holidays) 111 | } 112 | static networkDaysFromYYYYMMDD(f: number, count: number, holidays = []): number { 113 | const b = this.dateFromYYYYMMDD(f) 114 | const e = this.dateFromDateCount(b, count) 115 | return NETWORKDAYS(b, e, holidays) 116 | } 117 | static monthlyNetworkDaysFromYYYYMMDD(f: number, count: number, rate: number, holidays = []): any[] { 118 | const rets = [] 119 | let b = this.dateFromYYYYMMDD(f) 120 | const e = this.dateFromDateCount(b, count) 121 | e.setDate(e.getDate() - 1) 122 | e.setHours(23, 59, 59) 123 | 124 | let d = new Date(b.getFullYear(), b.getMonth() + 1, 0, 23, 59, 59); 125 | if(d > e) { 126 | d = e 127 | } 128 | while(b < e) { 129 | rets.push({ 130 | yyyy: b.getFullYear(), 131 | mm: b.getMonth() + 1, 132 | manHour: NETWORKDAYS(b, d, holidays) * rate 133 | }) 134 | 135 | b = new Date(b.getFullYear(), b.getMonth() + 1, 1, 0, 0, 0); 136 | d = new Date(d.getFullYear(), d.getMonth() + 2, 0, 23, 59, 59); 137 | if(d > e) { 138 | d = e 139 | } 140 | } 141 | return rets 142 | } 143 | 144 | static workloadUnit(v: number): string { 145 | return ((Math.ceil((v / 20) * 4) / 4)).toFixed(2) 146 | } 147 | } 148 | -------------------------------------------------------------------------------- /frontend/components/dialogs/TaskDetailDialog.vue: -------------------------------------------------------------------------------- 1 | 23 | 172 | 212 | -------------------------------------------------------------------------------- /frontend/components/dialogs/AddProjectDialog.vue: -------------------------------------------------------------------------------- 1 | 81 | 200 | 211 | -------------------------------------------------------------------------------- /frontend/components/ResourceComponent.vue: -------------------------------------------------------------------------------- 1 | 71 | 291 | 348 | -------------------------------------------------------------------------------- /frontend/components/SettingComponent.vue: -------------------------------------------------------------------------------- 1 | 192 | 334 | 336 | -------------------------------------------------------------------------------- /frontend/components/dialogs/TodoDialog.vue: -------------------------------------------------------------------------------- 1 | 154 | 373 | 416 | -------------------------------------------------------------------------------- /frontend/components/scss/wbs-pane.scss: -------------------------------------------------------------------------------- 1 | @import './wbs-common'; 2 | 3 | .wbs-pane { 4 | 5 | header { 6 | z-index: 6; 7 | } 8 | 9 | .tasks-pane, .timelines-pane { 10 | max-height: calc(100vh - 48px); 11 | } 12 | .tasks-pane { 13 | overflow-y: hidden; 14 | overflow-x: scroll; 15 | .tasks-header, .tasks-container { 16 | min-width: 640px; 17 | } 18 | 19 | .tasks-header { 20 | position: sticky; 21 | top: 0; 22 | display: flex; 23 | z-index: 1; 24 | > * { 25 | padding: .25rem; 26 | font-size: .7rem; 27 | overflow: hidden; 28 | background-color: $color-header; 29 | border-bottom: 1px solid #888; 30 | border-right: 1px solid #888; 31 | 32 | &:nth-child(1) { 33 | flex: 1; 34 | padding: 0; 35 | min-width: 216px; 36 | > *:first-child { 37 | padding: .25rem; 38 | } 39 | } 40 | &:nth-child(2) { 41 | width: 4rem; 42 | } 43 | &:nth-child(3) { 44 | width: 3rem; 45 | } 46 | &:nth-child(4) { 47 | width: 3rem; 48 | } 49 | &:nth-child(5) { 50 | width: 3rem; 51 | } 52 | &:nth-child(6) { 53 | width: 3rem; 54 | } 55 | &:nth-child(7) { 56 | width: 3rem; 57 | } 58 | &:nth-child(8) { 59 | width: 3rem; 60 | } 61 | } 62 | } 63 | .tasks-container { 64 | font-size: .8rem; 65 | 66 | ::v-deep ol { 67 | list-style-type: none; 68 | padding-left: 0; 69 | 70 | &:not(.nestable-group) { 71 | padding-left: $task-indent-size; 72 | } 73 | .nestable-item-content { 74 | position: relative; 75 | height: $task-height; 76 | } 77 | 78 | ol:not(.show) { 79 | display: none; 80 | } 81 | } 82 | .task-item-select { 83 | transform: translate(2px, 4px); 84 | } 85 | 86 | .nestable-handle { 87 | display: inline-block; 88 | height: $task-height; 89 | 90 | background: #f8f8f8; 91 | border-top-left-radius: 8px; 92 | border-bottom-left-radius: 8px; 93 | } 94 | 95 | .task-item { 96 | position: absolute; 97 | top: 0; 98 | left: 40px; 99 | display: flex; 100 | width: calc(100% - 40px); 101 | background: white; 102 | 103 | &.project { 104 | background: #B2DFDB; 105 | 106 | &:not(.show-children) + ol { 107 | display: none; 108 | } 109 | 110 | &.has-children { 111 | .task-assign-rate { 112 | display: none; 113 | } 114 | } 115 | } 116 | 117 | > * { 118 | padding: .125rem .25rem; 119 | text-align: center; 120 | border-right: 1px solid #ddd; 121 | border-bottom: 1px solid #eee; 122 | 123 | > input { 124 | width: 100%; 125 | text-align: center; 126 | } 127 | 128 | &.task-info { 129 | display: flex; 130 | padding: 0; 131 | flex: 1; 132 | min-width: 160px; 133 | > input { 134 | padding: .125rem .25rem; 135 | text-align: left; 136 | } 137 | .task-info-tool { 138 | cursor: pointer; 139 | } 140 | 141 | &:not(:hover) { 142 | .task-info-tool.hover-tool { 143 | display: none; 144 | } 145 | } 146 | } 147 | &:nth-child(2) { 148 | cursor: pointer; 149 | width: 4rem; 150 | } 151 | &:nth-child(3) { 152 | width: 3rem; 153 | } 154 | &:nth-child(4) { 155 | width: 3rem; 156 | } 157 | &:nth-child(5) { 158 | width: 3rem; 159 | } 160 | &:nth-child(6) { 161 | width: 3rem; 162 | } 163 | &:nth-child(7) { 164 | width: 3rem; 165 | } 166 | &:nth-child(8) { 167 | width: 3rem; 168 | } 169 | } 170 | } 171 | } 172 | } 173 | .timelines-pane { 174 | position: relative; 175 | overflow: auto; 176 | .timelines-header { 177 | position: sticky; 178 | top: 0; 179 | display: flex; 180 | background: $color-header; 181 | z-index: 5; 182 | 183 | @for $zoom from 1 through 5 { 184 | &.z#{$zoom} { 185 | width: #{ $time-line-max-term * $time-grid-x * $zoom }; 186 | } 187 | } 188 | 189 | .header-month { 190 | position: relative; 191 | height: 25.8px; 192 | border-right: 1px solid #888; 193 | border-bottom: 1px solid #888; 194 | 195 | > div { 196 | position: absolute; 197 | top: 0; 198 | left: 2px; 199 | font-size: .6rem; 200 | } 201 | } 202 | .header-monday { 203 | position: absolute; 204 | top: 11px; 205 | font-size: .7rem; 206 | } 207 | } 208 | 209 | .timelines-container { 210 | position: relative; 211 | 212 | .timeline-month { 213 | position: absolute; 214 | width: 1px; 215 | height: 100%; 216 | background-color: #888; 217 | } 218 | 219 | 220 | @for $zoom from 1 through 5 { 221 | &.z#{$zoom} { 222 | width: #{ $time-line-max-term * $time-grid-x * $zoom }; 223 | 224 | .holiday { 225 | position: absolute; 226 | width: #{ $time-grid-x * $zoom }; 227 | height: 100%; 228 | background-color: rgba($color-holiday, .75); 229 | } 230 | } 231 | } 232 | 233 | .today { 234 | position: absolute; 235 | width: 1px; 236 | height: 100%; 237 | background-color: #263238; 238 | opacity: .7; 239 | z-index: 1; 240 | } 241 | 242 | .timeline-container { 243 | position: relative; 244 | display: block; 245 | height: $line-height; 246 | border-bottom: 1px solid white; 247 | 248 | background-repeat: repeat-x; 249 | 250 | @for $zoom from 1 through 5 { 251 | &.z#{$zoom} { 252 | background-size: #{$time-grid-x * $zoom * 7} $line-height; 253 | } 254 | 255 | @for $d from 1 through 7 { 256 | &.d#{$d - 1}.z#{$zoom} { 257 | @include background-date-grid($d, $zoom); 258 | } 259 | } 260 | } 261 | 262 | 263 | ::v-deep .timeline-item { 264 | padding-top: 2px; 265 | 266 | .handle { 267 | border-color: #666; 268 | border-radius: 50%; 269 | } 270 | .task-item { 271 | padding: .1rem .25rem; 272 | width: 100%; 273 | height: $plan-height; 274 | font-size: .7rem; 275 | color: white; 276 | background: #42A5F5; 277 | border-radius: 4px; 278 | 279 | &.project { 280 | margin-top: 4px; 281 | padding: 0 .25rem; 282 | font-size: .6rem; 283 | color: white; 284 | background: rgba(#80CBC4, .8); 285 | border-bottom: 1px solid #004D40; 286 | height: $project-height; 287 | border-radius: 1px; 288 | 289 | &.complete { 290 | background: rgba(#004D40, .8); 291 | } 292 | &.advance { 293 | background: rgba(#4CAF50, .8); 294 | } 295 | &.delay { 296 | background: rgba(#FF5722, .8); 297 | } 298 | } 299 | 300 | @for $c from 0 through 10 { 301 | &.assign-rate-#{$c} { 302 | background-color: rgba(#42A5F5, ($c / 10 + 0.3)); 303 | } 304 | } 305 | } 306 | .task-info { 307 | display: flex; 308 | position: absolute; 309 | top: 1px; 310 | left: calc(100% + 32px); 311 | padding: 2px .2rem; 312 | white-space: nowrap; 313 | font-size: .7rem; 314 | background: rgba(#E0F2F1, .9); 315 | border-radius: 4px; 316 | 317 | &.project { 318 | background: rgba(#B2DFDB, .9); 319 | } 320 | 321 | > * + * { 322 | padding-left: .5rem; 323 | } 324 | > .task-info-task-name { 325 | padding-top: 2px; 326 | font-size: .6rem; 327 | font-weight: bold; 328 | } 329 | > .task-info-task-assignee { 330 | padding-top: 1px; 331 | } 332 | } 333 | } 334 | } 335 | } 336 | } 337 | } 338 | 339 | .v-toolbar { 340 | .connect-users { 341 | margin-left: 2rem; 342 | .client { 343 | display: inline-block; 344 | padding: .25rem; 345 | width: 32px; 346 | 347 | font-size: .5rem; 348 | color: white; 349 | background-color: #607D8B; 350 | border-radius: 100vh; 351 | 352 | white-space: nowrap; 353 | overflow: hidden; 354 | 355 | &::first-letter { 356 | font-size: 1rem; 357 | } 358 | } 359 | .client + .client { 360 | margin-left: .25rem; 361 | } 362 | } 363 | .toolbar { 364 | &.timeline-zoom { 365 | margin-right: 1rem; 366 | padding-top: .75rem; 367 | width: 5rem; 368 | 369 | * { 370 | font-size: .8rem; 371 | } 372 | } 373 | &.management-begin-date { 374 | margin-right: 1rem; 375 | padding-top: 14px; 376 | width: 5rem; 377 | 378 | ::v-deep input[role=button] { 379 | font-size: .8rem; 380 | } 381 | } 382 | } 383 | .toolbar-button { 384 | cursor: pointer; 385 | margin: 0 8px; 386 | display: flex; 387 | flex-direction: column; 388 | justify-content: center; 389 | align-items: center; 390 | font-size: .6rem; 391 | text-align: center; 392 | 393 | color: #666; 394 | 395 | &.disabled { 396 | color: #aaa; 397 | } 398 | 399 | .tool-name { 400 | padding-top: .2rem; 401 | height: 24px; 402 | line-height: .9; 403 | } 404 | } 405 | } 406 | 407 | .resource-pane { 408 | ::v-deep table { 409 | tbody { 410 | tr { 411 | td { 412 | padding: .1rem; 413 | font-size: .8rem; 414 | height: 24px; 415 | } 416 | } 417 | } 418 | } 419 | } 420 | 421 | ::v-deep { 422 | .nestable { 423 | position: relative; 424 | } 425 | .nestable .nestable-list { 426 | margin: 0; 427 | padding: 0 0 0 $task-indent-size; 428 | list-style-type: none; 429 | } 430 | .nestable > .nestable-list { 431 | padding: 0; 432 | } 433 | .nestable-item, 434 | .nestable-item-copy { 435 | margin: $task-margin-top 0 0; 436 | } 437 | .nestable-item:first-child, 438 | .nestable-item-copy:first-child { 439 | margin-top: 0; 440 | } 441 | .nestable-item .nestable-list, 442 | .nestable-item-copy .nestable-list { 443 | margin-top: $task-margin-top; 444 | } 445 | .nestable-item { 446 | position: relative; 447 | } 448 | .nestable-item.is-dragging .nestable-list { 449 | pointer-events: none; 450 | } 451 | .nestable-item.is-dragging * { 452 | opacity: 0; 453 | filter: alpha(opacity=0); 454 | } 455 | .nestable-item.is-dragging:before { 456 | content: ' '; 457 | position: absolute; 458 | top: 0; 459 | left: 0; 460 | right: 0; 461 | bottom: 0; 462 | background-color: rgba(106, 127, 233, 0.274); 463 | border: 1px dashed rgb(73, 100, 241); 464 | -webkit-border-radius: 5px; 465 | border-radius: 5px; 466 | } 467 | .nestable-drag-layer { 468 | position: fixed; 469 | top: 0; 470 | left: 0; 471 | z-index: 100; 472 | pointer-events: none; 473 | } 474 | .nestable-drag-layer > .nestable-list { 475 | position: absolute; 476 | top: 0; 477 | left: 0; 478 | padding: 0; 479 | background-color: rgba(106, 127, 233, 0.274); 480 | } 481 | .nestable [draggable="true"] { 482 | cursor: move; 483 | } 484 | .nestable-handle { 485 | display: inline; 486 | } 487 | } 488 | 489 | .task-assignee-select-menu { 490 | position: absolute; 491 | top: 0; 492 | left: 0; 493 | width: 480px; 494 | opacity: 0; 495 | z-index: 100; 496 | 497 | padding: .5rem .5rem 0; 498 | 499 | font-size: .8rem; 500 | 501 | transition: opacity .25s ease; 502 | &.show { 503 | opacity: 1; 504 | } 505 | 506 | .buttons-container { 507 | padding-right: .5rem; 508 | } 509 | } 510 | 511 | -------------------------------------------------------------------------------- /frontend/components/PrintGanttComponent.vue: -------------------------------------------------------------------------------- 1 | 90 | 385 | 734 | -------------------------------------------------------------------------------- /backend/yarn.lock: -------------------------------------------------------------------------------- 1 | # THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. 2 | # yarn lockfile v1 3 | 4 | 5 | "@mapbox/node-pre-gyp@^1.0.0": 6 | version "1.0.8" 7 | resolved "https://registry.yarnpkg.com/@mapbox/node-pre-gyp/-/node-pre-gyp-1.0.8.tgz#32abc8a5c624bc4e46c43d84dfb8b26d33a96f58" 8 | integrity sha512-CMGKi28CF+qlbXh26hDe6NxCd7amqeAzEqnS6IHeO6LoaKyM/n+Xw3HT1COdq8cuioOdlKdqn/hCmqPUOMOywg== 9 | dependencies: 10 | detect-libc "^1.0.3" 11 | https-proxy-agent "^5.0.0" 12 | make-dir "^3.1.0" 13 | node-fetch "^2.6.5" 14 | nopt "^5.0.0" 15 | npmlog "^5.0.1" 16 | rimraf "^3.0.2" 17 | semver "^7.3.5" 18 | tar "^6.1.11" 19 | 20 | "@socket.io/base64-arraybuffer@~1.0.2": 21 | version "1.0.2" 22 | resolved "https://registry.yarnpkg.com/@socket.io/base64-arraybuffer/-/base64-arraybuffer-1.0.2.tgz#568d9beae00b0d835f4f8c53fd55714986492e61" 23 | integrity sha512-dOlCBKnDw4iShaIsH/bxujKTM18+2TOAsYz+KSc11Am38H4q5Xw8Bbz97ZYdrVNM+um3p7w86Bvvmcn9q+5+eQ== 24 | 25 | "@types/component-emitter@^1.2.10": 26 | version "1.2.11" 27 | resolved "https://registry.yarnpkg.com/@types/component-emitter/-/component-emitter-1.2.11.tgz#50d47d42b347253817a39709fef03ce66a108506" 28 | integrity sha512-SRXjM+tfsSlA9VuG8hGO2nft2p8zjXCK1VcC6N4NXbBbYbSia9kzCChYQajIjzIqOOOuh5Ock6MmV2oux4jDZQ== 29 | 30 | "@types/cookie@^0.4.1": 31 | version "0.4.1" 32 | resolved "https://registry.yarnpkg.com/@types/cookie/-/cookie-0.4.1.tgz#bfd02c1f2224567676c1545199f87c3a861d878d" 33 | integrity sha512-XW/Aa8APYr6jSVVA1y/DEIZX0/GMKLEVekNG727R8cs56ahETkRAy/3DR7+fJyh7oUgGwNQaRfXCun0+KbWY7Q== 34 | 35 | "@types/cors@^2.8.12": 36 | version "2.8.12" 37 | resolved "https://registry.yarnpkg.com/@types/cors/-/cors-2.8.12.tgz#6b2c510a7ad7039e98e7b8d3d6598f4359e5c080" 38 | integrity sha512-vt+kDhq/M2ayberEtJcIN/hxXy1Pk+59g2FV/ZQceeaTyCtCucjL2Q7FXlFjtWn4n15KCr1NE2lNNFhp0lEThw== 39 | 40 | "@types/node@>=10.0.0": 41 | version "17.0.14" 42 | resolved "https://registry.yarnpkg.com/@types/node/-/node-17.0.14.tgz#33b9b94f789a8fedd30a68efdbca4dbb06b61f20" 43 | integrity sha512-SbjLmERksKOGzWzPNuW7fJM7fk3YXVTFiZWB/Hs99gwhk+/dnrQRPBQjPW9aO+fi1tAffi9PrwFvsmOKmDTyng== 44 | 45 | abbrev@1: 46 | version "1.1.1" 47 | resolved "https://registry.yarnpkg.com/abbrev/-/abbrev-1.1.1.tgz#f8f2c887ad10bf67f634f005b6987fed3179aac8" 48 | integrity sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q== 49 | 50 | accepts@~1.3.4, accepts@~1.3.7: 51 | version "1.3.7" 52 | resolved "https://registry.yarnpkg.com/accepts/-/accepts-1.3.7.tgz#531bc726517a3b2b41f850021c6cc15eaab507cd" 53 | integrity sha512-Il80Qs2WjYlJIBNzNkK6KYqlVMTbZLXgHx2oT0pU/fjRHyEp+PEfEPY0R3WCwAGVOtauxh1hOxNgIf5bv7dQpA== 54 | dependencies: 55 | mime-types "~2.1.24" 56 | negotiator "0.6.2" 57 | 58 | agent-base@6: 59 | version "6.0.2" 60 | resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-6.0.2.tgz#49fff58577cfee3f37176feab4c22e00f86d7f77" 61 | integrity sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ== 62 | dependencies: 63 | debug "4" 64 | 65 | ansi-regex@^5.0.1: 66 | version "5.0.1" 67 | resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-5.0.1.tgz#082cb2c89c9fe8659a311a53bd6a4dc5301db304" 68 | integrity sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ== 69 | 70 | "aproba@^1.0.3 || ^2.0.0": 71 | version "2.0.0" 72 | resolved "https://registry.yarnpkg.com/aproba/-/aproba-2.0.0.tgz#52520b8ae5b569215b354efc0caa3fe1e45a8adc" 73 | integrity sha512-lYe4Gx7QT+MKGbDsA+Z+he/Wtef0BiwDOlK/XkBrdfsh9J/jPPXbX0tE9x9cl27Tmu5gg3QUbUrQYa/y+KOHPQ== 74 | 75 | are-we-there-yet@^2.0.0: 76 | version "2.0.0" 77 | resolved "https://registry.yarnpkg.com/are-we-there-yet/-/are-we-there-yet-2.0.0.tgz#372e0e7bd279d8e94c653aaa1f67200884bf3e1c" 78 | integrity sha512-Ci/qENmwHnsYo9xKIcUJN5LeDKdJ6R1Z1j9V/J5wyq8nh/mYPEpIKJbBZXtZjG04HiK7zV/p6Vs9952MrMeUIw== 79 | dependencies: 80 | delegates "^1.0.0" 81 | readable-stream "^3.6.0" 82 | 83 | array-flatten@1.1.1: 84 | version "1.1.1" 85 | resolved "https://registry.yarnpkg.com/array-flatten/-/array-flatten-1.1.1.tgz#9a5f699051b1e7073328f2a008968b64ea2955d2" 86 | integrity sha1-ml9pkFGx5wczKPKgCJaLZOopVdI= 87 | 88 | balanced-match@^1.0.0: 89 | version "1.0.2" 90 | resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.2.tgz#e83e3a7e3f300b34cb9d87f615fa0cbf357690ee" 91 | integrity sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw== 92 | 93 | base64id@2.0.0, base64id@~2.0.0: 94 | version "2.0.0" 95 | resolved "https://registry.yarnpkg.com/base64id/-/base64id-2.0.0.tgz#2770ac6bc47d312af97a8bf9a634342e0cd25cb6" 96 | integrity sha512-lGe34o6EHj9y3Kts9R4ZYs/Gr+6N7MCaMlIFA3F1R2O5/m7K06AxfSeO5530PEERE6/WyEg3lsuyw4GHlPZHog== 97 | 98 | bcrypt@^5.0.1: 99 | version "5.0.1" 100 | resolved "https://registry.yarnpkg.com/bcrypt/-/bcrypt-5.0.1.tgz#f1a2c20f208e2ccdceea4433df0c8b2c54ecdf71" 101 | integrity sha512-9BTgmrhZM2t1bNuDtrtIMVSmmxZBrJ71n8Wg+YgdjHuIWYF7SjjmCPZFB+/5i/o/PIeRpwVJR3P+NrpIItUjqw== 102 | dependencies: 103 | "@mapbox/node-pre-gyp" "^1.0.0" 104 | node-addon-api "^3.1.0" 105 | 106 | body-parser@1.19.1: 107 | version "1.19.1" 108 | resolved "https://registry.yarnpkg.com/body-parser/-/body-parser-1.19.1.tgz#1499abbaa9274af3ecc9f6f10396c995943e31d4" 109 | integrity sha512-8ljfQi5eBk8EJfECMrgqNGWPEY5jWP+1IzkzkGdFFEwFQZZyaZ21UqdaHktgiMlH0xLHqIFtE/u2OYE5dOtViA== 110 | dependencies: 111 | bytes "3.1.1" 112 | content-type "~1.0.4" 113 | debug "2.6.9" 114 | depd "~1.1.2" 115 | http-errors "1.8.1" 116 | iconv-lite "0.4.24" 117 | on-finished "~2.3.0" 118 | qs "6.9.6" 119 | raw-body "2.4.2" 120 | type-is "~1.6.18" 121 | 122 | brace-expansion@^1.1.7: 123 | version "1.1.11" 124 | resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd" 125 | integrity sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA== 126 | dependencies: 127 | balanced-match "^1.0.0" 128 | concat-map "0.0.1" 129 | 130 | buffer-equal-constant-time@1.0.1: 131 | version "1.0.1" 132 | resolved "https://registry.yarnpkg.com/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz#f8e71132f7ffe6e01a5c9697a4c6f3e48d5cc819" 133 | integrity sha1-+OcRMvf/5uAaXJaXpMbz5I1cyBk= 134 | 135 | bytes@3.1.1: 136 | version "3.1.1" 137 | resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.1.1.tgz#3f018291cb4cbad9accb6e6970bca9c8889e879a" 138 | integrity sha512-dWe4nWO/ruEOY7HkUJ5gFt1DCFV9zPRoJr8pV0/ASQermOZjtq8jMjOprC0Kd10GLN+l7xaUPvxzJFWtxGu8Fg== 139 | 140 | chownr@^2.0.0: 141 | version "2.0.0" 142 | resolved "https://registry.yarnpkg.com/chownr/-/chownr-2.0.0.tgz#15bfbe53d2eab4cf70f18a8cd68ebe5b3cb1dece" 143 | integrity sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ== 144 | 145 | color-support@^1.1.2: 146 | version "1.1.3" 147 | resolved "https://registry.yarnpkg.com/color-support/-/color-support-1.1.3.tgz#93834379a1cc9a0c61f82f52f0d04322251bd5a2" 148 | integrity sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg== 149 | 150 | component-emitter@~1.3.0: 151 | version "1.3.0" 152 | resolved "https://registry.yarnpkg.com/component-emitter/-/component-emitter-1.3.0.tgz#16e4070fba8ae29b679f2215853ee181ab2eabc0" 153 | integrity sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg== 154 | 155 | concat-map@0.0.1: 156 | version "0.0.1" 157 | resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" 158 | integrity sha1-2Klr13/Wjfd5OnMDajug1UBdR3s= 159 | 160 | console-control-strings@^1.0.0, console-control-strings@^1.1.0: 161 | version "1.1.0" 162 | resolved "https://registry.yarnpkg.com/console-control-strings/-/console-control-strings-1.1.0.tgz#3d7cf4464db6446ea644bf4b39507f9851008e8e" 163 | integrity sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4= 164 | 165 | content-disposition@0.5.4: 166 | version "0.5.4" 167 | resolved "https://registry.yarnpkg.com/content-disposition/-/content-disposition-0.5.4.tgz#8b82b4efac82512a02bb0b1dcec9d2c5e8eb5bfe" 168 | integrity sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ== 169 | dependencies: 170 | safe-buffer "5.2.1" 171 | 172 | content-type@~1.0.4: 173 | version "1.0.4" 174 | resolved "https://registry.yarnpkg.com/content-type/-/content-type-1.0.4.tgz#e138cc75e040c727b1966fe5e5f8c9aee256fe3b" 175 | integrity sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA== 176 | 177 | cookie-signature@1.0.6: 178 | version "1.0.6" 179 | resolved "https://registry.yarnpkg.com/cookie-signature/-/cookie-signature-1.0.6.tgz#e303a882b342cc3ee8ca513a79999734dab3ae2c" 180 | integrity sha1-4wOogrNCzD7oylE6eZmXNNqzriw= 181 | 182 | cookie@0.4.1, cookie@~0.4.1: 183 | version "0.4.1" 184 | resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.4.1.tgz#afd713fe26ebd21ba95ceb61f9a8116e50a537d1" 185 | integrity sha512-ZwrFkGJxUR3EIoXtO+yVE69Eb7KlixbaeAWfBQB9vVsNn/o+Yw69gBWSSDK825hQNdN+wF8zELf3dFNl/kxkUA== 186 | 187 | cors@^2.8.5, cors@~2.8.5: 188 | version "2.8.5" 189 | resolved "https://registry.yarnpkg.com/cors/-/cors-2.8.5.tgz#eac11da51592dd86b9f06f6e7ac293b3df875d29" 190 | integrity sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g== 191 | dependencies: 192 | object-assign "^4" 193 | vary "^1" 194 | 195 | crypto@^1.0.1: 196 | version "1.0.1" 197 | resolved "https://registry.yarnpkg.com/crypto/-/crypto-1.0.1.tgz#2af1b7cad8175d24c8a1b0778255794a21803037" 198 | integrity sha512-VxBKmeNcqQdiUQUW2Tzq0t377b54N2bMtXO/qiLa+6eRRmmC4qT3D4OnTGoT/U6O9aklQ/jTwbOtRMTTY8G0Ig== 199 | 200 | csv-parse@^5.0.4: 201 | version "5.0.4" 202 | resolved "https://registry.yarnpkg.com/csv-parse/-/csv-parse-5.0.4.tgz#97e5e654413bcf95f2714ce09bcb2be6de0eb8e3" 203 | integrity sha512-5AIdl8l6n3iYQYxan5djB5eKDa+vBnhfWZtRpJTcrETWfVLYN0WSj3L9RwvgYt+psoO77juUr8TG8qpfGZifVQ== 204 | 205 | debug@2.6.9: 206 | version "2.6.9" 207 | resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f" 208 | integrity sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA== 209 | dependencies: 210 | ms "2.0.0" 211 | 212 | debug@4, debug@~4.3.1, debug@~4.3.2: 213 | version "4.3.3" 214 | resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.3.tgz#04266e0b70a98d4462e6e288e38259213332b664" 215 | integrity sha512-/zxw5+vh1Tfv+4Qn7a5nsbcJKPaSvCDhojn6FEl9vupwK2VCSDtEiEtqr8DFtzYFOdz63LBkxec7DYuc2jon6Q== 216 | dependencies: 217 | ms "2.1.2" 218 | 219 | delegates@^1.0.0: 220 | version "1.0.0" 221 | resolved "https://registry.yarnpkg.com/delegates/-/delegates-1.0.0.tgz#84c6e159b81904fdca59a0ef44cd870d31250f9a" 222 | integrity sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o= 223 | 224 | depd@~1.1.2: 225 | version "1.1.2" 226 | resolved "https://registry.yarnpkg.com/depd/-/depd-1.1.2.tgz#9bcd52e14c097763e749b274c4346ed2e560b5a9" 227 | integrity sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak= 228 | 229 | depd@~2.0.0: 230 | version "2.0.0" 231 | resolved "https://registry.yarnpkg.com/depd/-/depd-2.0.0.tgz#b696163cc757560d09cf22cc8fad1571b79e76df" 232 | integrity sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw== 233 | 234 | destroy@~1.0.4: 235 | version "1.0.4" 236 | resolved "https://registry.yarnpkg.com/destroy/-/destroy-1.0.4.tgz#978857442c44749e4206613e37946205826abd80" 237 | integrity sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA= 238 | 239 | detect-libc@^1.0.3: 240 | version "1.0.3" 241 | resolved "https://registry.yarnpkg.com/detect-libc/-/detect-libc-1.0.3.tgz#fa137c4bd698edf55cd5cd02ac559f91a4c4ba9b" 242 | integrity sha1-+hN8S9aY7fVc1c0CrFWfkaTEups= 243 | 244 | ecdsa-sig-formatter@1.0.11: 245 | version "1.0.11" 246 | resolved "https://registry.yarnpkg.com/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz#ae0f0fa2d85045ef14a817daa3ce9acd0489e5bf" 247 | integrity sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ== 248 | dependencies: 249 | safe-buffer "^5.0.1" 250 | 251 | ee-first@1.1.1: 252 | version "1.1.1" 253 | resolved "https://registry.yarnpkg.com/ee-first/-/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d" 254 | integrity sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0= 255 | 256 | emoji-regex@^8.0.0: 257 | version "8.0.0" 258 | resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-8.0.0.tgz#e818fd69ce5ccfcb404594f842963bf53164cc37" 259 | integrity sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A== 260 | 261 | encodeurl@~1.0.2: 262 | version "1.0.2" 263 | resolved "https://registry.yarnpkg.com/encodeurl/-/encodeurl-1.0.2.tgz#ad3ff4c86ec2d029322f5a02c3a9a606c95b3f59" 264 | integrity sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k= 265 | 266 | engine.io-parser@~5.0.0: 267 | version "5.0.3" 268 | resolved "https://registry.yarnpkg.com/engine.io-parser/-/engine.io-parser-5.0.3.tgz#ca1f0d7b11e290b4bfda251803baea765ed89c09" 269 | integrity sha512-BtQxwF27XUNnSafQLvDi0dQ8s3i6VgzSoQMJacpIcGNrlUdfHSKbgm3jmjCVvQluGzqwujQMPAoMai3oYSTurg== 270 | dependencies: 271 | "@socket.io/base64-arraybuffer" "~1.0.2" 272 | 273 | engine.io@~6.1.0: 274 | version "6.1.2" 275 | resolved "https://registry.yarnpkg.com/engine.io/-/engine.io-6.1.2.tgz#e7b9d546d90c62246ffcba4d88594be980d3855a" 276 | integrity sha512-v/7eGHxPvO2AWsksyx2PUsQvBafuvqs0jJJQ0FdmJG1b9qIvgSbqDRGwNhfk2XHaTTbTXiC4quRE8Q9nRjsrQQ== 277 | dependencies: 278 | "@types/cookie" "^0.4.1" 279 | "@types/cors" "^2.8.12" 280 | "@types/node" ">=10.0.0" 281 | accepts "~1.3.4" 282 | base64id "2.0.0" 283 | cookie "~0.4.1" 284 | cors "~2.8.5" 285 | debug "~4.3.1" 286 | engine.io-parser "~5.0.0" 287 | ws "~8.2.3" 288 | 289 | escape-html@~1.0.3: 290 | version "1.0.3" 291 | resolved "https://registry.yarnpkg.com/escape-html/-/escape-html-1.0.3.tgz#0258eae4d3d0c0974de1c169188ef0051d1d1988" 292 | integrity sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg= 293 | 294 | etag@~1.8.1: 295 | version "1.8.1" 296 | resolved "https://registry.yarnpkg.com/etag/-/etag-1.8.1.tgz#41ae2eeb65efa62268aebfea83ac7d79299b0887" 297 | integrity sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc= 298 | 299 | express-session@^1.17.2: 300 | version "1.17.2" 301 | resolved "https://registry.yarnpkg.com/express-session/-/express-session-1.17.2.tgz#397020374f9bf7997f891b85ea338767b30d0efd" 302 | integrity sha512-mPcYcLA0lvh7D4Oqr5aNJFMtBMKPLl++OKKxkHzZ0U0oDq1rpKBnkR5f5vCHR26VeArlTOEF9td4x5IjICksRQ== 303 | dependencies: 304 | cookie "0.4.1" 305 | cookie-signature "1.0.6" 306 | debug "2.6.9" 307 | depd "~2.0.0" 308 | on-headers "~1.0.2" 309 | parseurl "~1.3.3" 310 | safe-buffer "5.2.1" 311 | uid-safe "~2.1.5" 312 | 313 | express@^4.17.2: 314 | version "4.17.2" 315 | resolved "https://registry.yarnpkg.com/express/-/express-4.17.2.tgz#c18369f265297319beed4e5558753cc8c1364cb3" 316 | integrity sha512-oxlxJxcQlYwqPWKVJJtvQiwHgosH/LrLSPA+H4UxpyvSS6jC5aH+5MoHFM+KABgTOt0APue4w66Ha8jCUo9QGg== 317 | dependencies: 318 | accepts "~1.3.7" 319 | array-flatten "1.1.1" 320 | body-parser "1.19.1" 321 | content-disposition "0.5.4" 322 | content-type "~1.0.4" 323 | cookie "0.4.1" 324 | cookie-signature "1.0.6" 325 | debug "2.6.9" 326 | depd "~1.1.2" 327 | encodeurl "~1.0.2" 328 | escape-html "~1.0.3" 329 | etag "~1.8.1" 330 | finalhandler "~1.1.2" 331 | fresh "0.5.2" 332 | merge-descriptors "1.0.1" 333 | methods "~1.1.2" 334 | on-finished "~2.3.0" 335 | parseurl "~1.3.3" 336 | path-to-regexp "0.1.7" 337 | proxy-addr "~2.0.7" 338 | qs "6.9.6" 339 | range-parser "~1.2.1" 340 | safe-buffer "5.2.1" 341 | send "0.17.2" 342 | serve-static "1.14.2" 343 | setprototypeof "1.2.0" 344 | statuses "~1.5.0" 345 | type-is "~1.6.18" 346 | utils-merge "1.0.1" 347 | vary "~1.1.2" 348 | 349 | finalhandler@~1.1.2: 350 | version "1.1.2" 351 | resolved "https://registry.yarnpkg.com/finalhandler/-/finalhandler-1.1.2.tgz#b7e7d000ffd11938d0fdb053506f6ebabe9f587d" 352 | integrity sha512-aAWcW57uxVNrQZqFXjITpW3sIUQmHGG3qSb9mUah9MgMC4NeWhNOlNjXEYq3HjRAvL6arUviZGGJsBg6z0zsWA== 353 | dependencies: 354 | debug "2.6.9" 355 | encodeurl "~1.0.2" 356 | escape-html "~1.0.3" 357 | on-finished "~2.3.0" 358 | parseurl "~1.3.3" 359 | statuses "~1.5.0" 360 | unpipe "~1.0.0" 361 | 362 | forwarded@0.2.0: 363 | version "0.2.0" 364 | resolved "https://registry.yarnpkg.com/forwarded/-/forwarded-0.2.0.tgz#2269936428aad4c15c7ebe9779a84bf0b2a81811" 365 | integrity sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow== 366 | 367 | fresh@0.5.2: 368 | version "0.5.2" 369 | resolved "https://registry.yarnpkg.com/fresh/-/fresh-0.5.2.tgz#3d8cadd90d976569fa835ab1f8e4b23a105605a7" 370 | integrity sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac= 371 | 372 | fs-minipass@^2.0.0: 373 | version "2.1.0" 374 | resolved "https://registry.yarnpkg.com/fs-minipass/-/fs-minipass-2.1.0.tgz#7f5036fdbf12c63c169190cbe4199c852271f9fb" 375 | integrity sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg== 376 | dependencies: 377 | minipass "^3.0.0" 378 | 379 | fs.realpath@^1.0.0: 380 | version "1.0.0" 381 | resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" 382 | integrity sha1-FQStJSMVjKpA20onh8sBQRmU6k8= 383 | 384 | fs@^0.0.1-security: 385 | version "0.0.1-security" 386 | resolved "https://registry.yarnpkg.com/fs/-/fs-0.0.1-security.tgz#8a7bd37186b6dddf3813f23858b57ecaaf5e41d4" 387 | integrity sha1-invTcYa23d84E/I4WLV+yq9eQdQ= 388 | 389 | gauge@^3.0.0: 390 | version "3.0.2" 391 | resolved "https://registry.yarnpkg.com/gauge/-/gauge-3.0.2.tgz#03bf4441c044383908bcfa0656ad91803259b395" 392 | integrity sha512-+5J6MS/5XksCuXq++uFRsnUd7Ovu1XenbeuIuNRJxYWjgQbPuFhT14lAvsWfqfAmnwluf1OwMjz39HjfLPci0Q== 393 | dependencies: 394 | aproba "^1.0.3 || ^2.0.0" 395 | color-support "^1.1.2" 396 | console-control-strings "^1.0.0" 397 | has-unicode "^2.0.1" 398 | object-assign "^4.1.1" 399 | signal-exit "^3.0.0" 400 | string-width "^4.2.3" 401 | strip-ansi "^6.0.1" 402 | wide-align "^1.1.2" 403 | 404 | glob@^7.1.3: 405 | version "7.2.0" 406 | resolved "https://registry.yarnpkg.com/glob/-/glob-7.2.0.tgz#d15535af7732e02e948f4c41628bd910293f6023" 407 | integrity sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q== 408 | dependencies: 409 | fs.realpath "^1.0.0" 410 | inflight "^1.0.4" 411 | inherits "2" 412 | minimatch "^3.0.4" 413 | once "^1.3.0" 414 | path-is-absolute "^1.0.0" 415 | 416 | has-unicode@^2.0.1: 417 | version "2.0.1" 418 | resolved "https://registry.yarnpkg.com/has-unicode/-/has-unicode-2.0.1.tgz#e0e6fe6a28cf51138855e086d1691e771de2a8b9" 419 | integrity sha1-4Ob+aijPUROIVeCG0Wkedx3iqLk= 420 | 421 | http-errors@1.8.1: 422 | version "1.8.1" 423 | resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-1.8.1.tgz#7c3f28577cbc8a207388455dbd62295ed07bd68c" 424 | integrity sha512-Kpk9Sm7NmI+RHhnj6OIWDI1d6fIoFAtFt9RLaTMRlg/8w49juAStsrBgp0Dp4OdxdVbRIeKhtCUvoi/RuAhO4g== 425 | dependencies: 426 | depd "~1.1.2" 427 | inherits "2.0.4" 428 | setprototypeof "1.2.0" 429 | statuses ">= 1.5.0 < 2" 430 | toidentifier "1.0.1" 431 | 432 | https-proxy-agent@^5.0.0: 433 | version "5.0.0" 434 | resolved "https://registry.yarnpkg.com/https-proxy-agent/-/https-proxy-agent-5.0.0.tgz#e2a90542abb68a762e0a0850f6c9edadfd8506b2" 435 | integrity sha512-EkYm5BcKUGiduxzSt3Eppko+PiNWNEpa4ySk9vTC6wDsQJW9rHSa+UhGNJoRYp7bz6Ht1eaRIa6QaJqO5rCFbA== 436 | dependencies: 437 | agent-base "6" 438 | debug "4" 439 | 440 | iconv-lite@0.4.24: 441 | version "0.4.24" 442 | resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.24.tgz#2022b4b25fbddc21d2f524974a474aafe733908b" 443 | integrity sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA== 444 | dependencies: 445 | safer-buffer ">= 2.1.2 < 3" 446 | 447 | inflight@^1.0.4: 448 | version "1.0.6" 449 | resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9" 450 | integrity sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk= 451 | dependencies: 452 | once "^1.3.0" 453 | wrappy "1" 454 | 455 | inherits@2, inherits@2.0.4, inherits@^2.0.3: 456 | version "2.0.4" 457 | resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" 458 | integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== 459 | 460 | inherits@2.0.3: 461 | version "2.0.3" 462 | resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.3.tgz#633c2c83e3da42a502f52466022480f4208261de" 463 | integrity sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4= 464 | 465 | ipaddr.js@1.9.1: 466 | version "1.9.1" 467 | resolved "https://registry.yarnpkg.com/ipaddr.js/-/ipaddr.js-1.9.1.tgz#bff38543eeb8984825079ff3a2a8e6cbd46781b3" 468 | integrity sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g== 469 | 470 | is-fullwidth-code-point@^3.0.0: 471 | version "3.0.0" 472 | resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz#f116f8064fe90b3f7844a38997c0b75051269f1d" 473 | integrity sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg== 474 | 475 | jsonwebtoken@^8.5.1: 476 | version "8.5.1" 477 | resolved "https://registry.yarnpkg.com/jsonwebtoken/-/jsonwebtoken-8.5.1.tgz#00e71e0b8df54c2121a1f26137df2280673bcc0d" 478 | integrity sha512-XjwVfRS6jTMsqYs0EsuJ4LGxXV14zQybNd4L2r0UvbVnSF9Af8x7p5MzbJ90Ioz/9TI41/hTCvznF/loiSzn8w== 479 | dependencies: 480 | jws "^3.2.2" 481 | lodash.includes "^4.3.0" 482 | lodash.isboolean "^3.0.3" 483 | lodash.isinteger "^4.0.4" 484 | lodash.isnumber "^3.0.3" 485 | lodash.isplainobject "^4.0.6" 486 | lodash.isstring "^4.0.1" 487 | lodash.once "^4.0.0" 488 | ms "^2.1.1" 489 | semver "^5.6.0" 490 | 491 | jwa@^1.4.1: 492 | version "1.4.1" 493 | resolved "https://registry.yarnpkg.com/jwa/-/jwa-1.4.1.tgz#743c32985cb9e98655530d53641b66c8645b039a" 494 | integrity sha512-qiLX/xhEEFKUAJ6FiBMbes3w9ATzyk5W7Hvzpa/SLYdxNtng+gcurvrI7TbACjIXlsJyr05/S1oUhZrc63evQA== 495 | dependencies: 496 | buffer-equal-constant-time "1.0.1" 497 | ecdsa-sig-formatter "1.0.11" 498 | safe-buffer "^5.0.1" 499 | 500 | jws@^3.2.2: 501 | version "3.2.2" 502 | resolved "https://registry.yarnpkg.com/jws/-/jws-3.2.2.tgz#001099f3639468c9414000e99995fa52fb478304" 503 | integrity sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA== 504 | dependencies: 505 | jwa "^1.4.1" 506 | safe-buffer "^5.0.1" 507 | 508 | lodash.includes@^4.3.0: 509 | version "4.3.0" 510 | resolved "https://registry.yarnpkg.com/lodash.includes/-/lodash.includes-4.3.0.tgz#60bb98a87cb923c68ca1e51325483314849f553f" 511 | integrity sha1-YLuYqHy5I8aMoeUTJUgzFISfVT8= 512 | 513 | lodash.isboolean@^3.0.3: 514 | version "3.0.3" 515 | resolved "https://registry.yarnpkg.com/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz#6c2e171db2a257cd96802fd43b01b20d5f5870f6" 516 | integrity sha1-bC4XHbKiV82WgC/UOwGyDV9YcPY= 517 | 518 | lodash.isinteger@^4.0.4: 519 | version "4.0.4" 520 | resolved "https://registry.yarnpkg.com/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz#619c0af3d03f8b04c31f5882840b77b11cd68343" 521 | integrity sha1-YZwK89A/iwTDH1iChAt3sRzWg0M= 522 | 523 | lodash.isnumber@^3.0.3: 524 | version "3.0.3" 525 | resolved "https://registry.yarnpkg.com/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz#3ce76810c5928d03352301ac287317f11c0b1ffc" 526 | integrity sha1-POdoEMWSjQM1IwGsKHMX8RwLH/w= 527 | 528 | lodash.isplainobject@^4.0.6: 529 | version "4.0.6" 530 | resolved "https://registry.yarnpkg.com/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz#7c526a52d89b45c45cc690b88163be0497f550cb" 531 | integrity sha1-fFJqUtibRcRcxpC4gWO+BJf1UMs= 532 | 533 | lodash.isstring@^4.0.1: 534 | version "4.0.1" 535 | resolved "https://registry.yarnpkg.com/lodash.isstring/-/lodash.isstring-4.0.1.tgz#d527dfb5456eca7cc9bb95d5daeaf88ba54a5451" 536 | integrity sha1-1SfftUVuynzJu5XV2ur4i6VKVFE= 537 | 538 | lodash.once@^4.0.0: 539 | version "4.1.1" 540 | resolved "https://registry.yarnpkg.com/lodash.once/-/lodash.once-4.1.1.tgz#0dd3971213c7c56df880977d504c88fb471a97ac" 541 | integrity sha1-DdOXEhPHxW34gJd9UEyI+0cal6w= 542 | 543 | lru-cache@^6.0.0: 544 | version "6.0.0" 545 | resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-6.0.0.tgz#6d6fe6570ebd96aaf90fcad1dafa3b2566db3a94" 546 | integrity sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA== 547 | dependencies: 548 | yallist "^4.0.0" 549 | 550 | make-dir@^3.1.0: 551 | version "3.1.0" 552 | resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-3.1.0.tgz#415e967046b3a7f1d185277d84aa58203726a13f" 553 | integrity sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw== 554 | dependencies: 555 | semver "^6.0.0" 556 | 557 | media-typer@0.3.0: 558 | version "0.3.0" 559 | resolved "https://registry.yarnpkg.com/media-typer/-/media-typer-0.3.0.tgz#8710d7af0aa626f8fffa1ce00168545263255748" 560 | integrity sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g= 561 | 562 | merge-descriptors@1.0.1: 563 | version "1.0.1" 564 | resolved "https://registry.yarnpkg.com/merge-descriptors/-/merge-descriptors-1.0.1.tgz#b00aaa556dd8b44568150ec9d1b953f3f90cbb61" 565 | integrity sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E= 566 | 567 | methods@~1.1.2: 568 | version "1.1.2" 569 | resolved "https://registry.yarnpkg.com/methods/-/methods-1.1.2.tgz#5529a4d67654134edcc5266656835b0f851afcee" 570 | integrity sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4= 571 | 572 | mime-db@1.51.0: 573 | version "1.51.0" 574 | resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.51.0.tgz#d9ff62451859b18342d960850dc3cfb77e63fb0c" 575 | integrity sha512-5y8A56jg7XVQx2mbv1lu49NR4dokRnhZYTtL+KGfaa27uq4pSTXkwQkFJl4pkRMyNFz/EtYDSkiiEHx3F7UN6g== 576 | 577 | mime-types@~2.1.24: 578 | version "2.1.34" 579 | resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.34.tgz#5a712f9ec1503511a945803640fafe09d3793c24" 580 | integrity sha512-6cP692WwGIs9XXdOO4++N+7qjqv0rqxxVvJ3VHPh/Sc9mVZcQP+ZGhkKiTvWMQRr2tbHkJP/Yn7Y0npb3ZBs4A== 581 | dependencies: 582 | mime-db "1.51.0" 583 | 584 | mime@1.6.0: 585 | version "1.6.0" 586 | resolved "https://registry.yarnpkg.com/mime/-/mime-1.6.0.tgz#32cd9e5c64553bd58d19a568af452acff04981b1" 587 | integrity sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg== 588 | 589 | minimatch@^3.0.4: 590 | version "3.0.4" 591 | resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.4.tgz#5166e286457f03306064be5497e8dbb0c3d32083" 592 | integrity sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA== 593 | dependencies: 594 | brace-expansion "^1.1.7" 595 | 596 | minipass@^3.0.0: 597 | version "3.1.6" 598 | resolved "https://registry.yarnpkg.com/minipass/-/minipass-3.1.6.tgz#3b8150aa688a711a1521af5e8779c1d3bb4f45ee" 599 | integrity sha512-rty5kpw9/z8SX9dmxblFA6edItUmwJgMeYDZRrwlIVN27i8gysGbznJwUggw2V/FVqFSDdWy040ZPS811DYAqQ== 600 | dependencies: 601 | yallist "^4.0.0" 602 | 603 | minizlib@^2.1.1: 604 | version "2.1.2" 605 | resolved "https://registry.yarnpkg.com/minizlib/-/minizlib-2.1.2.tgz#e90d3466ba209b932451508a11ce3d3632145931" 606 | integrity sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg== 607 | dependencies: 608 | minipass "^3.0.0" 609 | yallist "^4.0.0" 610 | 611 | mkdirp@^1.0.3: 612 | version "1.0.4" 613 | resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-1.0.4.tgz#3eb5ed62622756d79a5f0e2a221dfebad75c2f7e" 614 | integrity sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw== 615 | 616 | ms@2.0.0: 617 | version "2.0.0" 618 | resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8" 619 | integrity sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g= 620 | 621 | ms@2.1.2: 622 | version "2.1.2" 623 | resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009" 624 | integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w== 625 | 626 | ms@2.1.3, ms@^2.1.1: 627 | version "2.1.3" 628 | resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2" 629 | integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA== 630 | 631 | negotiator@0.6.2: 632 | version "0.6.2" 633 | resolved "https://registry.yarnpkg.com/negotiator/-/negotiator-0.6.2.tgz#feacf7ccf525a77ae9634436a64883ffeca346fb" 634 | integrity sha512-hZXc7K2e+PgeI1eDBe/10Ard4ekbfrrqG8Ep+8Jmf4JID2bNg7NvCPOZN+kfF574pFQI7mum2AUqDidoKqcTOw== 635 | 636 | node-addon-api@^3.1.0: 637 | version "3.2.1" 638 | resolved "https://registry.yarnpkg.com/node-addon-api/-/node-addon-api-3.2.1.tgz#81325e0a2117789c0128dab65e7e38f07ceba161" 639 | integrity sha512-mmcei9JghVNDYydghQmeDX8KoAm0FAiYyIcUt/N4nhyAipB17pllZQDOJD2fotxABnt4Mdz+dKTO7eftLg4d0A== 640 | 641 | node-fetch@^2.6.5: 642 | version "2.6.7" 643 | resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.7.tgz#24de9fba827e3b4ae44dc8b20256a379160052ad" 644 | integrity sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ== 645 | dependencies: 646 | whatwg-url "^5.0.0" 647 | 648 | nopt@^5.0.0: 649 | version "5.0.0" 650 | resolved "https://registry.yarnpkg.com/nopt/-/nopt-5.0.0.tgz#530942bb58a512fccafe53fe210f13a25355dc88" 651 | integrity sha512-Tbj67rffqceeLpcRXrT7vKAN8CwfPeIBgM7E6iBkmKLV7bEMwpGgYLGv0jACUsECaa/vuxP0IjEont6umdMgtQ== 652 | dependencies: 653 | abbrev "1" 654 | 655 | npmlog@^5.0.1: 656 | version "5.0.1" 657 | resolved "https://registry.yarnpkg.com/npmlog/-/npmlog-5.0.1.tgz#f06678e80e29419ad67ab964e0fa69959c1eb8b0" 658 | integrity sha512-AqZtDUWOMKs1G/8lwylVjrdYgqA4d9nu8hc+0gzRxlDb1I10+FHBGMXs6aiQHFdCUUlqH99MUMuLfzWDNDtfxw== 659 | dependencies: 660 | are-we-there-yet "^2.0.0" 661 | console-control-strings "^1.1.0" 662 | gauge "^3.0.0" 663 | set-blocking "^2.0.0" 664 | 665 | object-assign@^4, object-assign@^4.1.1: 666 | version "4.1.1" 667 | resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863" 668 | integrity sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM= 669 | 670 | on-finished@~2.3.0: 671 | version "2.3.0" 672 | resolved "https://registry.yarnpkg.com/on-finished/-/on-finished-2.3.0.tgz#20f1336481b083cd75337992a16971aa2d906947" 673 | integrity sha1-IPEzZIGwg811M3mSoWlxqi2QaUc= 674 | dependencies: 675 | ee-first "1.1.1" 676 | 677 | on-headers@~1.0.2: 678 | version "1.0.2" 679 | resolved "https://registry.yarnpkg.com/on-headers/-/on-headers-1.0.2.tgz#772b0ae6aaa525c399e489adfad90c403eb3c28f" 680 | integrity sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA== 681 | 682 | once@^1.3.0: 683 | version "1.4.0" 684 | resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" 685 | integrity sha1-WDsap3WWHUsROsF9nFC6753Xa9E= 686 | dependencies: 687 | wrappy "1" 688 | 689 | parseurl@~1.3.3: 690 | version "1.3.3" 691 | resolved "https://registry.yarnpkg.com/parseurl/-/parseurl-1.3.3.tgz#9da19e7bee8d12dff0513ed5b76957793bc2e8d4" 692 | integrity sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ== 693 | 694 | path-is-absolute@^1.0.0: 695 | version "1.0.1" 696 | resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f" 697 | integrity sha1-F0uSaHNVNP+8es5r9TpanhtcX18= 698 | 699 | path-to-regexp@0.1.7: 700 | version "0.1.7" 701 | resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-0.1.7.tgz#df604178005f522f15eb4490e7247a1bfaa67f8c" 702 | integrity sha1-32BBeABfUi8V60SQ5yR6G/qmf4w= 703 | 704 | path@^0.12.7: 705 | version "0.12.7" 706 | resolved "https://registry.yarnpkg.com/path/-/path-0.12.7.tgz#d4dc2a506c4ce2197eb481ebfcd5b36c0140b10f" 707 | integrity sha1-1NwqUGxM4hl+tIHr/NWzbAFAsQ8= 708 | dependencies: 709 | process "^0.11.1" 710 | util "^0.10.3" 711 | 712 | process@^0.11.1: 713 | version "0.11.10" 714 | resolved "https://registry.yarnpkg.com/process/-/process-0.11.10.tgz#7332300e840161bda3e69a1d1d91a7d4bc16f182" 715 | integrity sha1-czIwDoQBYb2j5podHZGn1LwW8YI= 716 | 717 | proxy-addr@~2.0.7: 718 | version "2.0.7" 719 | resolved "https://registry.yarnpkg.com/proxy-addr/-/proxy-addr-2.0.7.tgz#f19fe69ceab311eeb94b42e70e8c2070f9ba1025" 720 | integrity sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg== 721 | dependencies: 722 | forwarded "0.2.0" 723 | ipaddr.js "1.9.1" 724 | 725 | qs@6.9.6: 726 | version "6.9.6" 727 | resolved "https://registry.yarnpkg.com/qs/-/qs-6.9.6.tgz#26ed3c8243a431b2924aca84cc90471f35d5a0ee" 728 | integrity sha512-TIRk4aqYLNoJUbd+g2lEdz5kLWIuTMRagAXxl78Q0RiVjAOugHmeKNGdd3cwo/ktpf9aL9epCfFqWDEKysUlLQ== 729 | 730 | random-bytes@~1.0.0: 731 | version "1.0.0" 732 | resolved "https://registry.yarnpkg.com/random-bytes/-/random-bytes-1.0.0.tgz#4f68a1dc0ae58bd3fb95848c30324db75d64360b" 733 | integrity sha1-T2ih3Arli9P7lYSMMDJNt11kNgs= 734 | 735 | range-parser@~1.2.1: 736 | version "1.2.1" 737 | resolved "https://registry.yarnpkg.com/range-parser/-/range-parser-1.2.1.tgz#3cf37023d199e1c24d1a55b84800c2f3e6468031" 738 | integrity sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg== 739 | 740 | raw-body@2.4.2: 741 | version "2.4.2" 742 | resolved "https://registry.yarnpkg.com/raw-body/-/raw-body-2.4.2.tgz#baf3e9c21eebced59dd6533ac872b71f7b61cb32" 743 | integrity sha512-RPMAFUJP19WIet/99ngh6Iv8fzAbqum4Li7AD6DtGaW2RpMB/11xDoalPiJMTbu6I3hkbMVkATvZrqb9EEqeeQ== 744 | dependencies: 745 | bytes "3.1.1" 746 | http-errors "1.8.1" 747 | iconv-lite "0.4.24" 748 | unpipe "1.0.0" 749 | 750 | readable-stream@^3.6.0: 751 | version "3.6.0" 752 | resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-3.6.0.tgz#337bbda3adc0706bd3e024426a286d4b4b2c9198" 753 | integrity sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA== 754 | dependencies: 755 | inherits "^2.0.3" 756 | string_decoder "^1.1.1" 757 | util-deprecate "^1.0.1" 758 | 759 | rimraf@^3.0.2: 760 | version "3.0.2" 761 | resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-3.0.2.tgz#f1a5402ba6220ad52cc1282bac1ae3aa49fd061a" 762 | integrity sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA== 763 | dependencies: 764 | glob "^7.1.3" 765 | 766 | safe-buffer@5.2.1, safe-buffer@^5.0.1, safe-buffer@~5.2.0: 767 | version "5.2.1" 768 | resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6" 769 | integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ== 770 | 771 | "safer-buffer@>= 2.1.2 < 3": 772 | version "2.1.2" 773 | resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a" 774 | integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg== 775 | 776 | semver@^5.6.0: 777 | version "5.7.1" 778 | resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.1.tgz#a954f931aeba508d307bbf069eff0c01c96116f7" 779 | integrity sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ== 780 | 781 | semver@^6.0.0: 782 | version "6.3.0" 783 | resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.0.tgz#ee0a64c8af5e8ceea67687b133761e1becbd1d3d" 784 | integrity sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw== 785 | 786 | semver@^7.3.5: 787 | version "7.3.5" 788 | resolved "https://registry.yarnpkg.com/semver/-/semver-7.3.5.tgz#0b621c879348d8998e4b0e4be94b3f12e6018ef7" 789 | integrity sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ== 790 | dependencies: 791 | lru-cache "^6.0.0" 792 | 793 | send@0.17.2: 794 | version "0.17.2" 795 | resolved "https://registry.yarnpkg.com/send/-/send-0.17.2.tgz#926622f76601c41808012c8bf1688fe3906f7820" 796 | integrity sha512-UJYB6wFSJE3G00nEivR5rgWp8c2xXvJ3OPWPhmuteU0IKj8nKbG3DrjiOmLwpnHGYWAVwA69zmTm++YG0Hmwww== 797 | dependencies: 798 | debug "2.6.9" 799 | depd "~1.1.2" 800 | destroy "~1.0.4" 801 | encodeurl "~1.0.2" 802 | escape-html "~1.0.3" 803 | etag "~1.8.1" 804 | fresh "0.5.2" 805 | http-errors "1.8.1" 806 | mime "1.6.0" 807 | ms "2.1.3" 808 | on-finished "~2.3.0" 809 | range-parser "~1.2.1" 810 | statuses "~1.5.0" 811 | 812 | serve-static@1.14.2: 813 | version "1.14.2" 814 | resolved "https://registry.yarnpkg.com/serve-static/-/serve-static-1.14.2.tgz#722d6294b1d62626d41b43a013ece4598d292bfa" 815 | integrity sha512-+TMNA9AFxUEGuC0z2mevogSnn9MXKb4fa7ngeRMJaaGv8vTwnIEkKi+QGvPt33HSnf8pRS+WGM0EbMtCJLKMBQ== 816 | dependencies: 817 | encodeurl "~1.0.2" 818 | escape-html "~1.0.3" 819 | parseurl "~1.3.3" 820 | send "0.17.2" 821 | 822 | set-blocking@^2.0.0: 823 | version "2.0.0" 824 | resolved "https://registry.yarnpkg.com/set-blocking/-/set-blocking-2.0.0.tgz#045f9782d011ae9a6803ddd382b24392b3d890f7" 825 | integrity sha1-BF+XgtARrppoA93TgrJDkrPYkPc= 826 | 827 | setprototypeof@1.2.0: 828 | version "1.2.0" 829 | resolved "https://registry.yarnpkg.com/setprototypeof/-/setprototypeof-1.2.0.tgz#66c9a24a73f9fc28cbe66b09fed3d33dcaf1b424" 830 | integrity sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw== 831 | 832 | signal-exit@^3.0.0: 833 | version "3.0.6" 834 | resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.6.tgz#24e630c4b0f03fea446a2bd299e62b4a6ca8d0af" 835 | integrity sha512-sDl4qMFpijcGw22U5w63KmD3cZJfBuFlVNbVMKje2keoKML7X2UzWbc4XrmEbDwg0NXJc3yv4/ox7b+JWb57kQ== 836 | 837 | socket.io-adapter@~2.3.3: 838 | version "2.3.3" 839 | resolved "https://registry.yarnpkg.com/socket.io-adapter/-/socket.io-adapter-2.3.3.tgz#4d6111e4d42e9f7646e365b4f578269821f13486" 840 | integrity sha512-Qd/iwn3VskrpNO60BeRyCyr8ZWw9CPZyitW4AQwmRZ8zCiyDiL+znRnWX6tDHXnWn1sJrM1+b6Mn6wEDJJ4aYQ== 841 | 842 | socket.io-parser@~4.0.4: 843 | version "4.0.4" 844 | resolved "https://registry.yarnpkg.com/socket.io-parser/-/socket.io-parser-4.0.4.tgz#9ea21b0d61508d18196ef04a2c6b9ab630f4c2b0" 845 | integrity sha512-t+b0SS+IxG7Rxzda2EVvyBZbvFPBCjJoyHuE0P//7OAsN23GItzDRdWa6ALxZI/8R5ygK7jAR6t028/z+7295g== 846 | dependencies: 847 | "@types/component-emitter" "^1.2.10" 848 | component-emitter "~1.3.0" 849 | debug "~4.3.1" 850 | 851 | socket.io@^4.4.1: 852 | version "4.4.1" 853 | resolved "https://registry.yarnpkg.com/socket.io/-/socket.io-4.4.1.tgz#cd6de29e277a161d176832bb24f64ee045c56ab8" 854 | integrity sha512-s04vrBswdQBUmuWJuuNTmXUVJhP0cVky8bBDhdkf8y0Ptsu7fKU2LuLbts9g+pdmAdyMMn8F/9Mf1/wbtUN0fg== 855 | dependencies: 856 | accepts "~1.3.4" 857 | base64id "~2.0.0" 858 | debug "~4.3.2" 859 | engine.io "~6.1.0" 860 | socket.io-adapter "~2.3.3" 861 | socket.io-parser "~4.0.4" 862 | 863 | "statuses@>= 1.5.0 < 2", statuses@~1.5.0: 864 | version "1.5.0" 865 | resolved "https://registry.yarnpkg.com/statuses/-/statuses-1.5.0.tgz#161c7dac177659fd9811f43771fa99381478628c" 866 | integrity sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow= 867 | 868 | "string-width@^1.0.2 || 2 || 3 || 4", string-width@^4.2.3: 869 | version "4.2.3" 870 | resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" 871 | integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== 872 | dependencies: 873 | emoji-regex "^8.0.0" 874 | is-fullwidth-code-point "^3.0.0" 875 | strip-ansi "^6.0.1" 876 | 877 | string_decoder@^1.1.1: 878 | version "1.3.0" 879 | resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.3.0.tgz#42f114594a46cf1a8e30b0a84f56c78c3edac21e" 880 | integrity sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA== 881 | dependencies: 882 | safe-buffer "~5.2.0" 883 | 884 | strip-ansi@^6.0.1: 885 | version "6.0.1" 886 | resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" 887 | integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== 888 | dependencies: 889 | ansi-regex "^5.0.1" 890 | 891 | tar@^6.1.11: 892 | version "6.1.11" 893 | resolved "https://registry.yarnpkg.com/tar/-/tar-6.1.11.tgz#6760a38f003afa1b2ffd0ffe9e9abbd0eab3d621" 894 | integrity sha512-an/KZQzQUkZCkuoAA64hM92X0Urb6VpRhAFllDzz44U2mcD5scmT3zBc4VgVpkugF580+DQn8eAFSyoQt0tznA== 895 | dependencies: 896 | chownr "^2.0.0" 897 | fs-minipass "^2.0.0" 898 | minipass "^3.0.0" 899 | minizlib "^2.1.1" 900 | mkdirp "^1.0.3" 901 | yallist "^4.0.0" 902 | 903 | toidentifier@1.0.1: 904 | version "1.0.1" 905 | resolved "https://registry.yarnpkg.com/toidentifier/-/toidentifier-1.0.1.tgz#3be34321a88a820ed1bd80dfaa33e479fbb8dd35" 906 | integrity sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA== 907 | 908 | tr46@~0.0.3: 909 | version "0.0.3" 910 | resolved "https://registry.yarnpkg.com/tr46/-/tr46-0.0.3.tgz#8184fd347dac9cdc185992f3a6622e14b9d9ab6a" 911 | integrity sha1-gYT9NH2snNwYWZLzpmIuFLnZq2o= 912 | 913 | type-is@~1.6.18: 914 | version "1.6.18" 915 | resolved "https://registry.yarnpkg.com/type-is/-/type-is-1.6.18.tgz#4e552cd05df09467dcbc4ef739de89f2cf37c131" 916 | integrity sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g== 917 | dependencies: 918 | media-typer "0.3.0" 919 | mime-types "~2.1.24" 920 | 921 | uid-safe@~2.1.5: 922 | version "2.1.5" 923 | resolved "https://registry.yarnpkg.com/uid-safe/-/uid-safe-2.1.5.tgz#2b3d5c7240e8fc2e58f8aa269e5ee49c0857bd3a" 924 | integrity sha512-KPHm4VL5dDXKz01UuEd88Df+KzynaohSL9fBh096KWAxSKZQDI2uBrVqtvRM4rwrIrRRKsdLNML/lnaaVSRioA== 925 | dependencies: 926 | random-bytes "~1.0.0" 927 | 928 | unpipe@1.0.0, unpipe@~1.0.0: 929 | version "1.0.0" 930 | resolved "https://registry.yarnpkg.com/unpipe/-/unpipe-1.0.0.tgz#b2bf4ee8514aae6165b4817829d21b2ef49904ec" 931 | integrity sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw= 932 | 933 | util-deprecate@^1.0.1: 934 | version "1.0.2" 935 | resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" 936 | integrity sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8= 937 | 938 | util@^0.10.3: 939 | version "0.10.4" 940 | resolved "https://registry.yarnpkg.com/util/-/util-0.10.4.tgz#3aa0125bfe668a4672de58857d3ace27ecb76901" 941 | integrity sha512-0Pm9hTQ3se5ll1XihRic3FDIku70C+iHUdT/W926rSgHV5QgXsYbKZN8MSC3tJtSkhuROzvsQjAaFENRXr+19A== 942 | dependencies: 943 | inherits "2.0.3" 944 | 945 | utils-merge@1.0.1: 946 | version "1.0.1" 947 | resolved "https://registry.yarnpkg.com/utils-merge/-/utils-merge-1.0.1.tgz#9f95710f50a267947b2ccc124741c1028427e713" 948 | integrity sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM= 949 | 950 | vary@^1, vary@~1.1.2: 951 | version "1.1.2" 952 | resolved "https://registry.yarnpkg.com/vary/-/vary-1.1.2.tgz#2299f02c6ded30d4a5961b0b9f74524a18f634fc" 953 | integrity sha1-IpnwLG3tMNSllhsLn3RSShj2NPw= 954 | 955 | webidl-conversions@^3.0.0: 956 | version "3.0.1" 957 | resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-3.0.1.tgz#24534275e2a7bc6be7bc86611cc16ae0a5654871" 958 | integrity sha1-JFNCdeKnvGvnvIZhHMFq4KVlSHE= 959 | 960 | whatwg-url@^5.0.0: 961 | version "5.0.0" 962 | resolved "https://registry.yarnpkg.com/whatwg-url/-/whatwg-url-5.0.0.tgz#966454e8765462e37644d3626f6742ce8b70965d" 963 | integrity sha1-lmRU6HZUYuN2RNNib2dCzotwll0= 964 | dependencies: 965 | tr46 "~0.0.3" 966 | webidl-conversions "^3.0.0" 967 | 968 | wide-align@^1.1.2: 969 | version "1.1.5" 970 | resolved "https://registry.yarnpkg.com/wide-align/-/wide-align-1.1.5.tgz#df1d4c206854369ecf3c9a4898f1b23fbd9d15d3" 971 | integrity sha512-eDMORYaPNZ4sQIuuYPDHdQvf4gyCF9rEEV/yPxGfwPkRodwEgiMUUXTx/dex+Me0wxx53S+NgUHaP7y3MGlDmg== 972 | dependencies: 973 | string-width "^1.0.2 || 2 || 3 || 4" 974 | 975 | wrappy@1: 976 | version "1.0.2" 977 | resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" 978 | integrity sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8= 979 | 980 | ws@~8.2.3: 981 | version "8.2.3" 982 | resolved "https://registry.yarnpkg.com/ws/-/ws-8.2.3.tgz#63a56456db1b04367d0b721a0b80cae6d8becbba" 983 | integrity sha512-wBuoj1BDpC6ZQ1B7DWQBYVLphPWkm8i9Y0/3YdHjHKHiohOJ1ws+3OccDWtH+PoC9DZD5WOTrJvNbWvjS6JWaA== 984 | 985 | yallist@^4.0.0: 986 | version "4.0.0" 987 | resolved "https://registry.yarnpkg.com/yallist/-/yallist-4.0.0.tgz#9bb92790d9c0effec63be73519e11a35019a3a72" 988 | integrity sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A== 989 | --------------------------------------------------------------------------------