Compare commits
53 commits
sse-featur
...
master
Author | SHA1 | Date | |
---|---|---|---|
920bccb45e | |||
ab2b508e98 | |||
aa706bfcaf | |||
9cfba93f65 | |||
b60f33b454 | |||
e82a083c86 | |||
3544f44a74 | |||
84e6cb106c | |||
47032055b6 | |||
46194ab4fe | |||
a18f1b0fb2 | |||
|
9145059e46 | ||
|
935eab8d97 | ||
b9cfb52098 | |||
12c8ceb5e1 | |||
5977940a39 | |||
7af0422937 | |||
7a6854bfc7 | |||
206ef4c503 | |||
6a96ef55c0 | |||
98d19e5e0b | |||
edc54207d1 | |||
dc8d0803b2 | |||
0433ef1c28 | |||
cee0139e06 | |||
d435d5db04 | |||
2770f0e8d8 | |||
1f4bc947b4 | |||
97e0bd4c43 | |||
706d3a9641 | |||
cb14c24d58 | |||
167a002f95 | |||
7b71848e52 | |||
c17c6cfa02 | |||
18c4991c52 | |||
9d4d08d0a5 | |||
7c422d58af | |||
2cba0279e6 | |||
db6dc225cf | |||
20a1378e03 | |||
e9154924f8 | |||
8c884779bf | |||
2525b3c805 | |||
978fa507bd | |||
d9cd7ae327 | |||
cac93173fc | |||
e3e3651c56 | |||
e178ba4ac9 | |||
a0ef6e7dff | |||
8e189a9e7c | |||
2556a424a1 | |||
6e179766a8 | |||
2e2074ea1e |
47 changed files with 1671 additions and 1108 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -29,4 +29,3 @@ graphics
|
||||||
.vscode
|
.vscode
|
||||||
juggl-vue/package-lock.json
|
juggl-vue/package-lock.json
|
||||||
package-lock.json
|
package-lock.json
|
||||||
public/api/vendor/
|
|
19
.prettierrc
Normal file
19
.prettierrc
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
{
|
||||||
|
"arrowParens": "avoid",
|
||||||
|
"bracketSpacing": true,
|
||||||
|
"endOfLine": "lf",
|
||||||
|
"htmlWhitespaceSensitivity": "css",
|
||||||
|
"insertPragma": false,
|
||||||
|
"jsxBracketSameLine": false,
|
||||||
|
"jsxSingleQuote": false,
|
||||||
|
"printWidth": 80,
|
||||||
|
"proseWrap": "preserve",
|
||||||
|
"quoteProps": "as-needed",
|
||||||
|
"requirePragma": false,
|
||||||
|
"semi": true,
|
||||||
|
"singleQuote": false,
|
||||||
|
"tabWidth": 2,
|
||||||
|
"trailingComma": "none",
|
||||||
|
"useTabs": false,
|
||||||
|
"vueIndentScriptAndStyle": false
|
||||||
|
}
|
420
package-lock.json
generated
420
package-lock.json
generated
|
@ -1855,9 +1855,9 @@
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"ssri": {
|
"ssri": {
|
||||||
"version": "7.1.0",
|
"version": "7.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/ssri/-/ssri-7.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/ssri/-/ssri-7.1.1.tgz",
|
||||||
"integrity": "sha512-77/WrDZUWocK0mvA5NTRQyveUf+wsrIc6vyrxpS8tVvYBcX215QbafrJR3KtkpskIzoFLqqNuuYQvxaMjXJ/0g==",
|
"integrity": "sha512-w+daCzXN89PseTL99MkA+fxJEcU3wfaE/ah0i0lnOlpG1CYLJ2ZjzEry68YBKfLs4JfoTShrTEsJkAZuNZ/stw==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"figgy-pudding": "^3.5.1",
|
"figgy-pudding": "^3.5.1",
|
||||||
|
@ -2585,11 +2585,18 @@
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"axios": {
|
"axios": {
|
||||||
"version": "0.21.1",
|
"version": "0.21.4",
|
||||||
"resolved": "https://registry.npmjs.org/axios/-/axios-0.21.1.tgz",
|
"resolved": "https://registry.npmjs.org/axios/-/axios-0.21.4.tgz",
|
||||||
"integrity": "sha512-dKQiRHxGD9PPRIUNIWvZhPTPpl1rf/OxTYKsqKUDjBwYylTvV7SjSHJb9ratfyzM6wCdLCOYLzs73qpg5c4iGA==",
|
"integrity": "sha512-ut5vewkiu8jjGBdqpM44XxjuCjq9LAKeHVmoVfHVzy8eHgxxq8SbAVQNovDA8mVi05kP0Ea/n/UzcSHcTJQfNg==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"follow-redirects": "^1.10.0"
|
"follow-redirects": "^1.14.0"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"follow-redirects": {
|
||||||
|
"version": "1.14.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.14.5.tgz",
|
||||||
|
"integrity": "sha512-wtphSXy7d4/OR+MvIFbCVBDzZ5520qV8XfPklSN5QtxuMUJZ+b0Wnst1e1lCDocfzuCkHqj8k0FpZqO+UIaKNA=="
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"babel-eslint": {
|
"babel-eslint": {
|
||||||
|
@ -2979,16 +2986,30 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"browserslist": {
|
"browserslist": {
|
||||||
"version": "4.16.0",
|
"version": "4.16.6",
|
||||||
"resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.16.0.tgz",
|
"resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.16.6.tgz",
|
||||||
"integrity": "sha512-/j6k8R0p3nxOC6kx5JGAxsnhc9ixaWJfYc+TNTzxg6+ARaESAvQGV7h0uNOB4t+pLQJZWzcrMxXOxjgsCj3dqQ==",
|
"integrity": "sha512-Wspk/PqO+4W9qp5iUTJsa1B/QrYn1keNCcEP5OvP7WBwT4KaDly0uONYmC6Xa3Z5IqnUgS0KcgLYu1l74x0ZXQ==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"caniuse-lite": "^1.0.30001165",
|
"caniuse-lite": "^1.0.30001219",
|
||||||
"colorette": "^1.2.1",
|
"colorette": "^1.2.2",
|
||||||
"electron-to-chromium": "^1.3.621",
|
"electron-to-chromium": "^1.3.723",
|
||||||
"escalade": "^3.1.1",
|
"escalade": "^3.1.1",
|
||||||
"node-releases": "^1.1.67"
|
"node-releases": "^1.1.71"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"caniuse-lite": {
|
||||||
|
"version": "1.0.30001245",
|
||||||
|
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001245.tgz",
|
||||||
|
"integrity": "sha512-768fM9j1PKXpOCKws6eTo3RHmvTUsG9UrpT4WoREFeZgJBTi4/X9g565azS/rVUGtqb8nt7FjLeF5u4kukERnA==",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
|
"colorette": {
|
||||||
|
"version": "1.2.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/colorette/-/colorette-1.2.2.tgz",
|
||||||
|
"integrity": "sha512-MKGMzyfeuutC/ZJ1cba9NqcNpfeqMUcYmyF1ZFY6/Cn7CNSAKx6a+s48sqLqyAiZuaP2TcqMhoo+dlwFnVxT9w==",
|
||||||
|
"dev": true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"buffer": {
|
"buffer": {
|
||||||
|
@ -3555,9 +3576,9 @@
|
||||||
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="
|
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="
|
||||||
},
|
},
|
||||||
"color-string": {
|
"color-string": {
|
||||||
"version": "1.5.4",
|
"version": "1.6.0",
|
||||||
"resolved": "https://registry.npmjs.org/color-string/-/color-string-1.5.4.tgz",
|
"resolved": "https://registry.npmjs.org/color-string/-/color-string-1.6.0.tgz",
|
||||||
"integrity": "sha512-57yF5yt8Xa3czSEW1jfQDE79Idk0+AkN/4KWad6tbdxUmAs3MvjxlWSWD4deYytcRfoZ9nhKyFl1kj5tBvidbw==",
|
"integrity": "sha512-c/hGS+kRWJutUBEngKKmk4iH3sD59MBkoxVapS/0wgpCz2u7XsNloxknyvBhzwEs1IbV36D9PwqLPJ2DTu3vMA==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"color-name": "^1.0.0",
|
"color-name": "^1.0.0",
|
||||||
|
@ -4138,15 +4159,72 @@
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"cssnano": {
|
"cssnano": {
|
||||||
"version": "4.1.10",
|
"version": "4.1.11",
|
||||||
"resolved": "https://registry.npmjs.org/cssnano/-/cssnano-4.1.10.tgz",
|
"resolved": "https://registry.npmjs.org/cssnano/-/cssnano-4.1.11.tgz",
|
||||||
"integrity": "sha512-5wny+F6H4/8RgNlaqab4ktc3e0/blKutmq8yNlBFXA//nSFFAqAngjNVRzUvCgYROULmZZUoosL/KSoZo5aUaQ==",
|
"integrity": "sha512-6gZm2htn7xIPJOHY824ERgj8cNPgPxyCSnkXc4v7YvNW+TdVfzgngHcEhy/8D11kUWRUMbke+tC+AUcUsnMz2g==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"cosmiconfig": "^5.0.0",
|
"cosmiconfig": "^5.0.0",
|
||||||
"cssnano-preset-default": "^4.0.7",
|
"cssnano-preset-default": "^4.0.8",
|
||||||
"is-resolvable": "^1.0.0",
|
"is-resolvable": "^1.0.0",
|
||||||
"postcss": "^7.0.0"
|
"postcss": "^7.0.0"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"cssnano-preset-default": {
|
||||||
|
"version": "4.0.8",
|
||||||
|
"resolved": "https://registry.npmjs.org/cssnano-preset-default/-/cssnano-preset-default-4.0.8.tgz",
|
||||||
|
"integrity": "sha512-LdAyHuq+VRyeVREFmuxUZR1TXjQm8QQU/ktoo/x7bz+SdOge1YKc5eMN6pRW7YWBmyq59CqYba1dJ5cUukEjLQ==",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"css-declaration-sorter": "^4.0.1",
|
||||||
|
"cssnano-util-raw-cache": "^4.0.1",
|
||||||
|
"postcss": "^7.0.0",
|
||||||
|
"postcss-calc": "^7.0.1",
|
||||||
|
"postcss-colormin": "^4.0.3",
|
||||||
|
"postcss-convert-values": "^4.0.1",
|
||||||
|
"postcss-discard-comments": "^4.0.2",
|
||||||
|
"postcss-discard-duplicates": "^4.0.2",
|
||||||
|
"postcss-discard-empty": "^4.0.1",
|
||||||
|
"postcss-discard-overridden": "^4.0.1",
|
||||||
|
"postcss-merge-longhand": "^4.0.11",
|
||||||
|
"postcss-merge-rules": "^4.0.3",
|
||||||
|
"postcss-minify-font-values": "^4.0.2",
|
||||||
|
"postcss-minify-gradients": "^4.0.2",
|
||||||
|
"postcss-minify-params": "^4.0.2",
|
||||||
|
"postcss-minify-selectors": "^4.0.2",
|
||||||
|
"postcss-normalize-charset": "^4.0.1",
|
||||||
|
"postcss-normalize-display-values": "^4.0.2",
|
||||||
|
"postcss-normalize-positions": "^4.0.2",
|
||||||
|
"postcss-normalize-repeat-style": "^4.0.2",
|
||||||
|
"postcss-normalize-string": "^4.0.2",
|
||||||
|
"postcss-normalize-timing-functions": "^4.0.2",
|
||||||
|
"postcss-normalize-unicode": "^4.0.1",
|
||||||
|
"postcss-normalize-url": "^4.0.1",
|
||||||
|
"postcss-normalize-whitespace": "^4.0.2",
|
||||||
|
"postcss-ordered-values": "^4.1.2",
|
||||||
|
"postcss-reduce-initial": "^4.0.3",
|
||||||
|
"postcss-reduce-transforms": "^4.0.2",
|
||||||
|
"postcss-svgo": "^4.0.3",
|
||||||
|
"postcss-unique-selectors": "^4.0.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"postcss-svgo": {
|
||||||
|
"version": "4.0.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/postcss-svgo/-/postcss-svgo-4.0.3.tgz",
|
||||||
|
"integrity": "sha512-NoRbrcMWTtUghzuKSoIm6XV+sJdvZ7GZSc3wdBN0W19FTtp2ko8NqLsgoh/m9CzNhU3KLPvQmjIwtaNFkaFTvw==",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"postcss": "^7.0.0",
|
||||||
|
"postcss-value-parser": "^3.0.0",
|
||||||
|
"svgo": "^1.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"postcss-value-parser": {
|
||||||
|
"version": "3.3.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz",
|
||||||
|
"integrity": "sha512-pISE66AbVkp4fDQ7VHBwRNXzAAKJjw4Vw7nWI/+Q3vuly7SNfgYXvm6i5IgFylHGK5sP/xHAbB7N49OS4gWNyQ==",
|
||||||
|
"dev": true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"cssnano-preset-default": {
|
"cssnano-preset-default": {
|
||||||
|
@ -4617,9 +4695,9 @@
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"dns-packet": {
|
"dns-packet": {
|
||||||
"version": "1.3.1",
|
"version": "1.3.4",
|
||||||
"resolved": "https://registry.npmjs.org/dns-packet/-/dns-packet-1.3.1.tgz",
|
"resolved": "https://registry.npmjs.org/dns-packet/-/dns-packet-1.3.4.tgz",
|
||||||
"integrity": "sha512-0UxfQkMhYAUaZI+xrNZOz/as5KgDU0M/fQ9b6SpkyLbk3GEswDi6PADJVaYJradtRVsRIlF1zLyOodbcTCDzUg==",
|
"integrity": "sha512-BQ6F4vycLXBvdrJZ6S3gZewt6rcrks9KBgM9vrhW+knGRqc8uEdT7fuCwloc7nny5xNoMJ17HGH0R/6fpo8ECA==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"ip": "^1.1.0",
|
"ip": "^1.1.0",
|
||||||
|
@ -4684,12 +4762,20 @@
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"domhandler": {
|
"domhandler": {
|
||||||
"version": "2.4.2",
|
"version": "4.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/domhandler/-/domhandler-2.4.2.tgz",
|
"resolved": "https://registry.npmjs.org/domhandler/-/domhandler-4.2.0.tgz",
|
||||||
"integrity": "sha512-JiK04h0Ht5u/80fdLMCEmV4zkNh2BcoMFBmZ/91WtYZ8qVXSKjiw7fXMgFPnHcSZgOo3XdinHvmnDUeMf5R4wA==",
|
"integrity": "sha512-zk7sgt970kzPks2Bf+dwT/PLzghLnsivb9CcxkvR8Mzr66Olr0Ofd8neSbglHJHaHa2MadfoSdNlKYAaafmWfA==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"domelementtype": "1"
|
"domelementtype": "^2.2.0"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"domelementtype": {
|
||||||
|
"version": "2.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.2.0.tgz",
|
||||||
|
"integrity": "sha512-DtBMo82pv1dFtUmHyr48beiuq792Sxohr+8Hm9zoxklYPfa6n0Z3Byjj2IV7bmr2IyqClnqEQhfgHJJ5QF0R5A==",
|
||||||
|
"dev": true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"domutils": {
|
"domutils": {
|
||||||
|
@ -4770,30 +4856,30 @@
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"electron-to-chromium": {
|
"electron-to-chromium": {
|
||||||
"version": "1.3.633",
|
"version": "1.3.775",
|
||||||
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.633.tgz",
|
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.775.tgz",
|
||||||
"integrity": "sha512-bsVCsONiVX1abkWdH7KtpuDAhsQ3N3bjPYhROSAXE78roJKet0Y5wznA14JE9pzbwSZmSMAW6KiKYf1RvbTJkA==",
|
"integrity": "sha512-EGuiJW4yBPOTj2NtWGZcX93ZE8IGj33HJAx4d3ouE2zOfW2trbWU+t1e0yzLr1qQIw81++txbM3BH52QwSRE6Q==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"elliptic": {
|
"elliptic": {
|
||||||
"version": "6.5.3",
|
"version": "6.5.4",
|
||||||
"resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.5.3.tgz",
|
"resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.5.4.tgz",
|
||||||
"integrity": "sha512-IMqzv5wNQf+E6aHeIqATs0tOLeOTwj1QKbRcS3jBbYkl5oLAserA8yJTT7/VyHUYG91PRmPyeQDObKLPpeS4dw==",
|
"integrity": "sha512-iLhC6ULemrljPZb+QutR5TQGB+pdW6KGD5RSegS+8sorOZT+rdQFbsQFJgvN3eRqNALqJer4oQ16YvJHlU8hzQ==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"bn.js": "^4.4.0",
|
"bn.js": "^4.11.9",
|
||||||
"brorand": "^1.0.1",
|
"brorand": "^1.1.0",
|
||||||
"hash.js": "^1.0.0",
|
"hash.js": "^1.0.0",
|
||||||
"hmac-drbg": "^1.0.0",
|
"hmac-drbg": "^1.0.1",
|
||||||
"inherits": "^2.0.1",
|
"inherits": "^2.0.4",
|
||||||
"minimalistic-assert": "^1.0.0",
|
"minimalistic-assert": "^1.0.1",
|
||||||
"minimalistic-crypto-utils": "^1.0.0"
|
"minimalistic-crypto-utils": "^1.0.1"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"bn.js": {
|
"bn.js": {
|
||||||
"version": "4.11.9",
|
"version": "4.12.0",
|
||||||
"resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.9.tgz",
|
"resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz",
|
||||||
"integrity": "sha512-E6QoYqCKZfgatHTdHzs1RRKP7ip4vvm+EyRUeE2RF0NblwVvb0p6jSVeNTOFxPn26QXN2o6SMfNxKp6kU8zQaw==",
|
"integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==",
|
||||||
"dev": true
|
"dev": true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -5726,7 +5812,8 @@
|
||||||
"follow-redirects": {
|
"follow-redirects": {
|
||||||
"version": "1.13.1",
|
"version": "1.13.1",
|
||||||
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.13.1.tgz",
|
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.13.1.tgz",
|
||||||
"integrity": "sha512-SSG5xmZh1mkPGyKzjZP8zLjltIfpW32Y5QpdNJyjcfGxK3qo3NDDkZOZSFiGn1A6SclQxY9GzEwAHQ3dmYRWpg=="
|
"integrity": "sha512-SSG5xmZh1mkPGyKzjZP8zLjltIfpW32Y5QpdNJyjcfGxK3qo3NDDkZOZSFiGn1A6SclQxY9GzEwAHQ3dmYRWpg==",
|
||||||
|
"dev": true
|
||||||
},
|
},
|
||||||
"for-in": {
|
"for-in": {
|
||||||
"version": "1.0.2",
|
"version": "1.0.2",
|
||||||
|
@ -5981,9 +6068,9 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"glob-parent": {
|
"glob-parent": {
|
||||||
"version": "5.1.1",
|
"version": "5.1.2",
|
||||||
"resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz",
|
||||||
"integrity": "sha512-FnI+VGOpnlGHWZxthPGR+QhR78fuiK0sNLkHQv+bL9fQi57lNNdquIbna/WrfROrolq8GK5Ek6BiMwqL/voRYQ==",
|
"integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"is-glob": "^4.0.1"
|
"is-glob": "^4.0.1"
|
||||||
|
@ -6223,9 +6310,9 @@
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"hosted-git-info": {
|
"hosted-git-info": {
|
||||||
"version": "2.8.8",
|
"version": "2.8.9",
|
||||||
"resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.8.tgz",
|
"resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.9.tgz",
|
||||||
"integrity": "sha512-f/wzC2QaWBs7t9IYqB4T3sR1xviIViXJRJTWBlx2Gf3g0Xi5vI7Yy4koXQ1c9OYDGHN9sBy1DQ2AB8fqZBWhUg==",
|
"integrity": "sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"hpack.js": {
|
"hpack.js": {
|
||||||
|
@ -6351,34 +6438,43 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"htmlparser2": {
|
"htmlparser2": {
|
||||||
"version": "3.10.1",
|
"version": "6.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-3.10.1.tgz",
|
"resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-6.1.0.tgz",
|
||||||
"integrity": "sha512-IgieNijUMbkDovyoKObU1DUhm1iwNYE/fuifEoEHfd1oZKZDaONBSkal7Y01shxsM49R4XaMdGez3WnF9UfiCQ==",
|
"integrity": "sha512-gyyPk6rgonLFEDGoeRgQNaEUvdJ4ktTmmUh/h2t7s+M8oPpIPxgNACWa+6ESR57kXstwqPiCut0V8NRpcwgU7A==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"domelementtype": "^1.3.1",
|
"domelementtype": "^2.0.1",
|
||||||
"domhandler": "^2.3.0",
|
"domhandler": "^4.0.0",
|
||||||
"domutils": "^1.5.1",
|
"domutils": "^2.5.2",
|
||||||
"entities": "^1.1.1",
|
"entities": "^2.0.0"
|
||||||
"inherits": "^2.0.1",
|
|
||||||
"readable-stream": "^3.1.1"
|
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"entities": {
|
"dom-serializer": {
|
||||||
"version": "1.1.2",
|
"version": "1.3.2",
|
||||||
"resolved": "https://registry.npmjs.org/entities/-/entities-1.1.2.tgz",
|
"resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-1.3.2.tgz",
|
||||||
"integrity": "sha512-f2LZMYl1Fzu7YSBKg+RoROelpOaNrcGmE9AZubeDfrCEia483oW4MI4VyFd5VNHIgQ/7qm1I0wUHK1eJnn2y2w==",
|
"integrity": "sha512-5c54Bk5Dw4qAxNOI1pFEizPSjVsx5+bpJKmL2kPn8JhBUq2q09tTCa3mjijun2NfK78NMouDYNMBkOrPZiS+ig==",
|
||||||
"dev": true
|
|
||||||
},
|
|
||||||
"readable-stream": {
|
|
||||||
"version": "3.6.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz",
|
|
||||||
"integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==",
|
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"inherits": "^2.0.3",
|
"domelementtype": "^2.0.1",
|
||||||
"string_decoder": "^1.1.1",
|
"domhandler": "^4.2.0",
|
||||||
"util-deprecate": "^1.0.1"
|
"entities": "^2.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"domelementtype": {
|
||||||
|
"version": "2.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.2.0.tgz",
|
||||||
|
"integrity": "sha512-DtBMo82pv1dFtUmHyr48beiuq792Sxohr+8Hm9zoxklYPfa6n0Z3Byjj2IV7bmr2IyqClnqEQhfgHJJ5QF0R5A==",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
|
"domutils": {
|
||||||
|
"version": "2.7.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/domutils/-/domutils-2.7.0.tgz",
|
||||||
|
"integrity": "sha512-8eaHa17IwJUPAiB+SoTYBo5mCdeMgdcAoXJ59m6DT1vw+5iLS3gNoqYaRowaBKtGVrOF1Jz4yDTgYKLK2kvfJg==",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"dom-serializer": "^1.0.1",
|
||||||
|
"domelementtype": "^2.2.0",
|
||||||
|
"domhandler": "^4.2.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -7416,9 +7512,9 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"lodash": {
|
"lodash": {
|
||||||
"version": "4.17.20",
|
"version": "4.17.21",
|
||||||
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.20.tgz",
|
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
|
||||||
"integrity": "sha512-PlhdFcillOINfeV7Ni6oF1TAEayyZBoZ8bcshTHqOYJYlrqzRK5hagpagky5o4HfCzzd1TRkXPMFq6cKk9rGmA==",
|
"integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"lodash.defaultsdeep": {
|
"lodash.defaultsdeep": {
|
||||||
|
@ -8102,9 +8198,9 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node-releases": {
|
"node-releases": {
|
||||||
"version": "1.1.67",
|
"version": "1.1.73",
|
||||||
"resolved": "https://registry.npmjs.org/node-releases/-/node-releases-1.1.67.tgz",
|
"resolved": "https://registry.npmjs.org/node-releases/-/node-releases-1.1.73.tgz",
|
||||||
"integrity": "sha512-V5QF9noGFl3EymEwUYzO+3NTDpGfQB4ve6Qfnzf3UNydMhjQRVPR1DZTuvWiLzaFJYw2fmDwAfnRNEVb64hSIg==",
|
"integrity": "sha512-uW7fodD6pyW2FZNZnp/Z3hvWKeEW1Y8R1+1CnErE8cXFXzl5blBOoVB41CvMer6P6Q0S5FXDwcHgFd1Wj0U9zg==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"node-sass": {
|
"node-sass": {
|
||||||
|
@ -8788,9 +8884,9 @@
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"path-parse": {
|
"path-parse": {
|
||||||
"version": "1.0.6",
|
"version": "1.0.7",
|
||||||
"resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.6.tgz",
|
"resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz",
|
||||||
"integrity": "sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw==",
|
"integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"path-to-regexp": {
|
"path-to-regexp": {
|
||||||
|
@ -8920,9 +9016,9 @@
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"postcss": {
|
"postcss": {
|
||||||
"version": "7.0.35",
|
"version": "7.0.36",
|
||||||
"resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.35.tgz",
|
"resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.36.tgz",
|
||||||
"integrity": "sha512-3QT8bBJeX/S5zKTTjTCIjRF3If4avAT6kqxcASlTWEtAFCb9NH0OUxNDfgZSWdP5fJnBYCMEWkIFfWeugjzYMg==",
|
"integrity": "sha512-BebJSIUMwJHRH0HAQoxN4u1CN86glsrwsW0q7T+/m44eXOUAxSNdHRkNZPYz5vVUbg17hFgOQDE7fZk7li3pZw==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"chalk": "^2.4.2",
|
"chalk": "^2.4.2",
|
||||||
|
@ -10042,16 +10138,16 @@
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"renderkid": {
|
"renderkid": {
|
||||||
"version": "2.0.4",
|
"version": "2.0.7",
|
||||||
"resolved": "https://registry.npmjs.org/renderkid/-/renderkid-2.0.4.tgz",
|
"resolved": "https://registry.npmjs.org/renderkid/-/renderkid-2.0.7.tgz",
|
||||||
"integrity": "sha512-K2eXrSOJdq+HuKzlcjOlGoOarUu5SDguDEhE7+Ah4zuOWL40j8A/oHvLlLob9PSTNvVnBd+/q0Er1QfpEuem5g==",
|
"integrity": "sha512-oCcFyxaMrKsKcTY59qnCAtmDVSLfPbrv6A3tVbPdFMMrv5jaK10V6m40cKsoPNhAqN6rmHW9sswW4o3ruSrwUQ==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"css-select": "^1.1.0",
|
"css-select": "^4.1.3",
|
||||||
"dom-converter": "^0.2",
|
"dom-converter": "^0.2.0",
|
||||||
"htmlparser2": "^3.3.0",
|
"htmlparser2": "^6.1.0",
|
||||||
"lodash": "^4.17.20",
|
"lodash": "^4.17.21",
|
||||||
"strip-ansi": "^3.0.0"
|
"strip-ansi": "^3.0.1"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"ansi-regex": {
|
"ansi-regex": {
|
||||||
|
@ -10061,31 +10157,59 @@
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"css-select": {
|
"css-select": {
|
||||||
"version": "1.2.0",
|
"version": "4.1.3",
|
||||||
"resolved": "https://registry.npmjs.org/css-select/-/css-select-1.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/css-select/-/css-select-4.1.3.tgz",
|
||||||
"integrity": "sha1-KzoRBTnFNV8c2NMUYj6HCxIeyFg=",
|
"integrity": "sha512-gT3wBNd9Nj49rAbmtFHj1cljIAOLYSX1nZ8CB7TBO3INYckygm5B7LISU/szY//YmdiSLbJvDLOx9VnMVpMBxA==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"boolbase": "~1.0.0",
|
"boolbase": "^1.0.0",
|
||||||
"css-what": "2.1",
|
"css-what": "^5.0.0",
|
||||||
"domutils": "1.5.1",
|
"domhandler": "^4.2.0",
|
||||||
"nth-check": "~1.0.1"
|
"domutils": "^2.6.0",
|
||||||
|
"nth-check": "^2.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"css-what": {
|
"css-what": {
|
||||||
"version": "2.1.3",
|
"version": "5.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/css-what/-/css-what-2.1.3.tgz",
|
"resolved": "https://registry.npmjs.org/css-what/-/css-what-5.0.1.tgz",
|
||||||
"integrity": "sha512-a+EPoD+uZiNfh+5fxw2nO9QwFa6nJe2Or35fGY6Ipw1R3R4AGz1d1TEZrCegvw2YTmZ0jXirGYlzxxpYSHwpEg==",
|
"integrity": "sha512-FYDTSHb/7KXsWICVsxdmiExPjCfRC4qRFBdVwv7Ax9hMnvMmEjP9RfxTEZ3qPZGmADDn2vAKSo9UcN1jKVYscg==",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
|
"dom-serializer": {
|
||||||
|
"version": "1.3.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-1.3.2.tgz",
|
||||||
|
"integrity": "sha512-5c54Bk5Dw4qAxNOI1pFEizPSjVsx5+bpJKmL2kPn8JhBUq2q09tTCa3mjijun2NfK78NMouDYNMBkOrPZiS+ig==",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"domelementtype": "^2.0.1",
|
||||||
|
"domhandler": "^4.2.0",
|
||||||
|
"entities": "^2.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"domelementtype": {
|
||||||
|
"version": "2.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.2.0.tgz",
|
||||||
|
"integrity": "sha512-DtBMo82pv1dFtUmHyr48beiuq792Sxohr+8Hm9zoxklYPfa6n0Z3Byjj2IV7bmr2IyqClnqEQhfgHJJ5QF0R5A==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"domutils": {
|
"domutils": {
|
||||||
"version": "1.5.1",
|
"version": "2.7.0",
|
||||||
"resolved": "https://registry.npmjs.org/domutils/-/domutils-1.5.1.tgz",
|
"resolved": "https://registry.npmjs.org/domutils/-/domutils-2.7.0.tgz",
|
||||||
"integrity": "sha1-3NhIiib1Y9YQeeSMn3t+Mjc2gs8=",
|
"integrity": "sha512-8eaHa17IwJUPAiB+SoTYBo5mCdeMgdcAoXJ59m6DT1vw+5iLS3gNoqYaRowaBKtGVrOF1Jz4yDTgYKLK2kvfJg==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"dom-serializer": "0",
|
"dom-serializer": "^1.0.1",
|
||||||
"domelementtype": "1"
|
"domelementtype": "^2.2.0",
|
||||||
|
"domhandler": "^4.2.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"nth-check": {
|
||||||
|
"version": "2.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.0.1.tgz",
|
||||||
|
"integrity": "sha512-it1vE95zF6dTT9lBsYbxvqh0Soy4SPowchj0UBGj/V6cTPnXXtQOPUbhZ6CmGzAD/rW22LQK6E96pcdJXk4A4w==",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"boolbase": "^1.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"strip-ansi": {
|
"strip-ansi": {
|
||||||
|
@ -11147,9 +11271,9 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"ssri": {
|
"ssri": {
|
||||||
"version": "6.0.1",
|
"version": "6.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/ssri/-/ssri-6.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/ssri/-/ssri-6.0.2.tgz",
|
||||||
"integrity": "sha512-3Wge10hNcT1Kur4PDFwEieXSCMCJs/7WvSACcrMYrNp+b8kDL1/0wJch5Ni2WrtwEa2IO8OsVfeKIciKCDx/QA==",
|
"integrity": "sha512-cepbSq/neFK7xB6A50KHN0xHDotYzq58wWCa5LeWqnPrHG8GzfEjO/4O8kpmcGW+oaxkvhEJCWgbgNk4/ZV93Q==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"figgy-pudding": "^3.5.1"
|
"figgy-pudding": "^3.5.1"
|
||||||
|
@ -11298,9 +11422,9 @@
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"ansi-regex": {
|
"ansi-regex": {
|
||||||
"version": "5.0.0",
|
"version": "5.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
|
||||||
"integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==",
|
"integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
|
||||||
"dev": true
|
"dev": true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -12082,9 +12206,9 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"url-parse": {
|
"url-parse": {
|
||||||
"version": "1.4.7",
|
"version": "1.5.3",
|
||||||
"resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.4.7.tgz",
|
"resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.5.3.tgz",
|
||||||
"integrity": "sha512-d3uaVyzDB9tQoSXFvuSUNFibTd9zxd2bkVrDRvF5TmvWWQwqE4lgYJ5m+x1DbecWkw+LK4RNl2CU1hHuOKPVlg==",
|
"integrity": "sha512-IIORyIQD9rvj0A4CLWsHkBBJuNqWpFQe224b6j9t/ABmquIS0qDU2pY6kl6AuOrL5OkCXHMCFNe1jBcuAggjvQ==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"querystringify": "^2.1.1",
|
"querystringify": "^2.1.1",
|
||||||
|
@ -12280,9 +12404,9 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"vue-loader-v16": {
|
"vue-loader-v16": {
|
||||||
"version": "npm:vue-loader@16.1.2",
|
"version": "npm:vue-loader@16.8.3",
|
||||||
"resolved": "https://registry.npmjs.org/vue-loader/-/vue-loader-16.1.2.tgz",
|
"resolved": "https://registry.npmjs.org/vue-loader/-/vue-loader-16.8.3.tgz",
|
||||||
"integrity": "sha512-8QTxh+Fd+HB6fiL52iEVLKqE9N1JSlMXLR92Ijm6g8PZrwIxckgpqjPDWRP5TWxdiPaHR+alUWsnu1ShQOwt+Q==",
|
"integrity": "sha512-7vKN45IxsKxe5GcVCbc2qFU5aWzyiLrYJyUuMz4BQLKctCj/fmCa0w6fGiiQ2cLFetNcek1ppGJQDCup0c1hpA==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
|
@ -12292,9 +12416,9 @@
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"loader-utils": {
|
"loader-utils": {
|
||||||
"version": "2.0.0",
|
"version": "2.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-2.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-2.0.2.tgz",
|
||||||
"integrity": "sha512-rP4F0h2RaWSvPEkD7BLDFQnvSf+nK+wr3ESUjNTyAGobqrijmW92zc+SO6d4p4B1wh7+B/Jg1mkQe5NYUEHtHQ==",
|
"integrity": "sha512-TM57VeHptv569d/GKh6TAYdzKblwDNiumOdkFnejjD0XwTH87K90w3O7AiJRqdQoXygvi1VQTJTLGhJl7WqA7A==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
|
@ -12344,6 +12468,11 @@
|
||||||
"integrity": "sha512-4gDntzrifFnCEvyoO8PqyJDmguXgVPxKiIxrBKjIowvL9l+N66196+72XVYR8BBf1Uv1Fgt3bGevJ+sEmxfZzw==",
|
"integrity": "sha512-4gDntzrifFnCEvyoO8PqyJDmguXgVPxKiIxrBKjIowvL9l+N66196+72XVYR8BBf1Uv1Fgt3bGevJ+sEmxfZzw==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
|
"vue-timers": {
|
||||||
|
"version": "2.0.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/vue-timers/-/vue-timers-2.0.4.tgz",
|
||||||
|
"integrity": "sha512-QOEVdO4V4o9WjFG6C0Kn9tfdTeeECjqvEQozcQlfL1Tn8v0qx4uUPhTYoc1+s6qoJnSbu8f68x8+nm1ZEir0kw=="
|
||||||
|
},
|
||||||
"vuex": {
|
"vuex": {
|
||||||
"version": "3.6.0",
|
"version": "3.6.0",
|
||||||
"resolved": "https://registry.npmjs.org/vuex/-/vuex-3.6.0.tgz",
|
"resolved": "https://registry.npmjs.org/vuex/-/vuex-3.6.0.tgz",
|
||||||
|
@ -13093,45 +13222,12 @@
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"wide-align": {
|
"wide-align": {
|
||||||
"version": "1.1.3",
|
"version": "1.1.5",
|
||||||
"resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.3.tgz",
|
"resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.5.tgz",
|
||||||
"integrity": "sha512-QGkOQc8XL6Bt5PwnsExKBPuMKBxnGxWWW3fU55Xt4feHozMUhdUMaBCk290qpm/wG5u/RSKzwdAC4i51YigihA==",
|
"integrity": "sha512-eDMORYaPNZ4sQIuuYPDHdQvf4gyCF9rEEV/yPxGfwPkRodwEgiMUUXTx/dex+Me0wxx53S+NgUHaP7y3MGlDmg==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"string-width": "^1.0.2 || 2"
|
"string-width": "^1.0.2 || 2 || 3 || 4"
|
||||||
},
|
|
||||||
"dependencies": {
|
|
||||||
"ansi-regex": {
|
|
||||||
"version": "3.0.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz",
|
|
||||||
"integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=",
|
|
||||||
"dev": true
|
|
||||||
},
|
|
||||||
"is-fullwidth-code-point": {
|
|
||||||
"version": "2.0.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz",
|
|
||||||
"integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=",
|
|
||||||
"dev": true
|
|
||||||
},
|
|
||||||
"string-width": {
|
|
||||||
"version": "2.1.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz",
|
|
||||||
"integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==",
|
|
||||||
"dev": true,
|
|
||||||
"requires": {
|
|
||||||
"is-fullwidth-code-point": "^2.0.0",
|
|
||||||
"strip-ansi": "^4.0.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"strip-ansi": {
|
|
||||||
"version": "4.0.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz",
|
|
||||||
"integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=",
|
|
||||||
"dev": true,
|
|
||||||
"requires": {
|
|
||||||
"ansi-regex": "^3.0.0"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"word-wrap": {
|
"word-wrap": {
|
||||||
|
@ -13176,9 +13272,9 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"ws": {
|
"ws": {
|
||||||
"version": "6.2.1",
|
"version": "6.2.2",
|
||||||
"resolved": "https://registry.npmjs.org/ws/-/ws-6.2.1.tgz",
|
"resolved": "https://registry.npmjs.org/ws/-/ws-6.2.2.tgz",
|
||||||
"integrity": "sha512-GIyAXC2cB7LjvpgMt9EKS2ldqr0MTrORaleiOno6TweZ6r3TKtoFQWay/2PceJ3RuBasOHzXNn5Lrw1X0bEjqA==",
|
"integrity": "sha512-zmhltoSR8u1cnDsD43TX59mzoMZsLKqUweyYBAIvTngR3shc0W6aOZylZmq/7hqyVxPdi+5Ud2QInblgyE72fw==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"async-limiter": "~1.0.0"
|
"async-limiter": "~1.0.0"
|
||||||
|
|
|
@ -9,12 +9,13 @@
|
||||||
"lint:fix": "eslint --fix --ext .js,.vue ."
|
"lint:fix": "eslint --fix --ext .js,.vue ."
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"axios": "^0.21.0",
|
"axios": "^0.21.4",
|
||||||
"bootstrap": "^4.5.3",
|
"bootstrap": "^4.5.3",
|
||||||
"bootstrap-vue": "^2.21.1",
|
"bootstrap-vue": "^2.21.1",
|
||||||
"core-js": "^3.6.5",
|
"core-js": "^3.6.5",
|
||||||
"vue": "^2.6.12",
|
"vue": "^2.6.12",
|
||||||
"vue-router": "^3.2.0",
|
"vue-router": "^3.2.0",
|
||||||
|
"vue-timers": "^2.0.4",
|
||||||
"vuex": "^3.4.0"
|
"vuex": "^3.4.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
|
|
@ -1,5 +0,0 @@
|
||||||
{
|
|
||||||
"require": {
|
|
||||||
"tonyhhyip/sse": "^2.1"
|
|
||||||
}
|
|
||||||
}
|
|
678
public/api/composer.lock
generated
678
public/api/composer.lock
generated
|
@ -1,678 +0,0 @@
|
||||||
{
|
|
||||||
"_readme": [
|
|
||||||
"This file locks the dependencies of your project to a known state",
|
|
||||||
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
|
|
||||||
"This file is @generated automatically"
|
|
||||||
],
|
|
||||||
"content-hash": "361f9482844d6c22d6efec567d317681",
|
|
||||||
"packages": [
|
|
||||||
{
|
|
||||||
"name": "symfony/deprecation-contracts",
|
|
||||||
"version": "v2.2.0",
|
|
||||||
"source": {
|
|
||||||
"type": "git",
|
|
||||||
"url": "https://github.com/symfony/deprecation-contracts.git",
|
|
||||||
"reference": "5fa56b4074d1ae755beb55617ddafe6f5d78f665"
|
|
||||||
},
|
|
||||||
"dist": {
|
|
||||||
"type": "zip",
|
|
||||||
"url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/5fa56b4074d1ae755beb55617ddafe6f5d78f665",
|
|
||||||
"reference": "5fa56b4074d1ae755beb55617ddafe6f5d78f665",
|
|
||||||
"shasum": ""
|
|
||||||
},
|
|
||||||
"require": {
|
|
||||||
"php": ">=7.1"
|
|
||||||
},
|
|
||||||
"type": "library",
|
|
||||||
"extra": {
|
|
||||||
"branch-alias": {
|
|
||||||
"dev-master": "2.2-dev"
|
|
||||||
},
|
|
||||||
"thanks": {
|
|
||||||
"name": "symfony/contracts",
|
|
||||||
"url": "https://github.com/symfony/contracts"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"autoload": {
|
|
||||||
"files": [
|
|
||||||
"function.php"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"notification-url": "https://packagist.org/downloads/",
|
|
||||||
"license": [
|
|
||||||
"MIT"
|
|
||||||
],
|
|
||||||
"authors": [
|
|
||||||
{
|
|
||||||
"name": "Nicolas Grekas",
|
|
||||||
"email": "p@tchwork.com"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "Symfony Community",
|
|
||||||
"homepage": "https://symfony.com/contributors"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"description": "A generic function and convention to trigger deprecation notices",
|
|
||||||
"homepage": "https://symfony.com",
|
|
||||||
"funding": [
|
|
||||||
{
|
|
||||||
"url": "https://symfony.com/sponsor",
|
|
||||||
"type": "custom"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"url": "https://github.com/fabpot",
|
|
||||||
"type": "github"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
|
|
||||||
"type": "tidelift"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"time": "2020-09-07T11:33:47+00:00"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "symfony/http-foundation",
|
|
||||||
"version": "v4.4.19",
|
|
||||||
"source": {
|
|
||||||
"type": "git",
|
|
||||||
"url": "https://github.com/symfony/http-foundation.git",
|
|
||||||
"reference": "8888741b633f6c3d1e572b7735ad2cae3e03f9c5"
|
|
||||||
},
|
|
||||||
"dist": {
|
|
||||||
"type": "zip",
|
|
||||||
"url": "https://api.github.com/repos/symfony/http-foundation/zipball/8888741b633f6c3d1e572b7735ad2cae3e03f9c5",
|
|
||||||
"reference": "8888741b633f6c3d1e572b7735ad2cae3e03f9c5",
|
|
||||||
"shasum": ""
|
|
||||||
},
|
|
||||||
"require": {
|
|
||||||
"php": ">=7.1.3",
|
|
||||||
"symfony/mime": "^4.3|^5.0",
|
|
||||||
"symfony/polyfill-mbstring": "~1.1",
|
|
||||||
"symfony/polyfill-php80": "^1.15"
|
|
||||||
},
|
|
||||||
"require-dev": {
|
|
||||||
"predis/predis": "~1.0",
|
|
||||||
"symfony/expression-language": "^3.4|^4.0|^5.0"
|
|
||||||
},
|
|
||||||
"type": "library",
|
|
||||||
"autoload": {
|
|
||||||
"psr-4": {
|
|
||||||
"Symfony\\Component\\HttpFoundation\\": ""
|
|
||||||
},
|
|
||||||
"exclude-from-classmap": [
|
|
||||||
"/Tests/"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"notification-url": "https://packagist.org/downloads/",
|
|
||||||
"license": [
|
|
||||||
"MIT"
|
|
||||||
],
|
|
||||||
"authors": [
|
|
||||||
{
|
|
||||||
"name": "Fabien Potencier",
|
|
||||||
"email": "fabien@symfony.com"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "Symfony Community",
|
|
||||||
"homepage": "https://symfony.com/contributors"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"description": "Defines an object-oriented layer for the HTTP specification",
|
|
||||||
"homepage": "https://symfony.com",
|
|
||||||
"funding": [
|
|
||||||
{
|
|
||||||
"url": "https://symfony.com/sponsor",
|
|
||||||
"type": "custom"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"url": "https://github.com/fabpot",
|
|
||||||
"type": "github"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
|
|
||||||
"type": "tidelift"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"time": "2021-01-27T09:09:26+00:00"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "symfony/mime",
|
|
||||||
"version": "v5.2.3",
|
|
||||||
"source": {
|
|
||||||
"type": "git",
|
|
||||||
"url": "https://github.com/symfony/mime.git",
|
|
||||||
"reference": "7dee6a43493f39b51ff6c5bb2bd576fe40a76c86"
|
|
||||||
},
|
|
||||||
"dist": {
|
|
||||||
"type": "zip",
|
|
||||||
"url": "https://api.github.com/repos/symfony/mime/zipball/7dee6a43493f39b51ff6c5bb2bd576fe40a76c86",
|
|
||||||
"reference": "7dee6a43493f39b51ff6c5bb2bd576fe40a76c86",
|
|
||||||
"shasum": ""
|
|
||||||
},
|
|
||||||
"require": {
|
|
||||||
"php": ">=7.2.5",
|
|
||||||
"symfony/deprecation-contracts": "^2.1",
|
|
||||||
"symfony/polyfill-intl-idn": "^1.10",
|
|
||||||
"symfony/polyfill-mbstring": "^1.0",
|
|
||||||
"symfony/polyfill-php80": "^1.15"
|
|
||||||
},
|
|
||||||
"conflict": {
|
|
||||||
"phpdocumentor/reflection-docblock": "<3.2.2",
|
|
||||||
"phpdocumentor/type-resolver": "<1.4.0",
|
|
||||||
"symfony/mailer": "<4.4"
|
|
||||||
},
|
|
||||||
"require-dev": {
|
|
||||||
"egulias/email-validator": "^2.1.10",
|
|
||||||
"phpdocumentor/reflection-docblock": "^3.0|^4.0|^5.0",
|
|
||||||
"symfony/dependency-injection": "^4.4|^5.0",
|
|
||||||
"symfony/property-access": "^4.4|^5.1",
|
|
||||||
"symfony/property-info": "^4.4|^5.1",
|
|
||||||
"symfony/serializer": "^5.2"
|
|
||||||
},
|
|
||||||
"type": "library",
|
|
||||||
"autoload": {
|
|
||||||
"psr-4": {
|
|
||||||
"Symfony\\Component\\Mime\\": ""
|
|
||||||
},
|
|
||||||
"exclude-from-classmap": [
|
|
||||||
"/Tests/"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"notification-url": "https://packagist.org/downloads/",
|
|
||||||
"license": [
|
|
||||||
"MIT"
|
|
||||||
],
|
|
||||||
"authors": [
|
|
||||||
{
|
|
||||||
"name": "Fabien Potencier",
|
|
||||||
"email": "fabien@symfony.com"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "Symfony Community",
|
|
||||||
"homepage": "https://symfony.com/contributors"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"description": "Allows manipulating MIME messages",
|
|
||||||
"homepage": "https://symfony.com",
|
|
||||||
"keywords": [
|
|
||||||
"mime",
|
|
||||||
"mime-type"
|
|
||||||
],
|
|
||||||
"funding": [
|
|
||||||
{
|
|
||||||
"url": "https://symfony.com/sponsor",
|
|
||||||
"type": "custom"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"url": "https://github.com/fabpot",
|
|
||||||
"type": "github"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
|
|
||||||
"type": "tidelift"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"time": "2021-02-02T06:10:15+00:00"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "symfony/polyfill-intl-idn",
|
|
||||||
"version": "v1.22.1",
|
|
||||||
"source": {
|
|
||||||
"type": "git",
|
|
||||||
"url": "https://github.com/symfony/polyfill-intl-idn.git",
|
|
||||||
"reference": "2d63434d922daf7da8dd863e7907e67ee3031483"
|
|
||||||
},
|
|
||||||
"dist": {
|
|
||||||
"type": "zip",
|
|
||||||
"url": "https://api.github.com/repos/symfony/polyfill-intl-idn/zipball/2d63434d922daf7da8dd863e7907e67ee3031483",
|
|
||||||
"reference": "2d63434d922daf7da8dd863e7907e67ee3031483",
|
|
||||||
"shasum": ""
|
|
||||||
},
|
|
||||||
"require": {
|
|
||||||
"php": ">=7.1",
|
|
||||||
"symfony/polyfill-intl-normalizer": "^1.10",
|
|
||||||
"symfony/polyfill-php72": "^1.10"
|
|
||||||
},
|
|
||||||
"suggest": {
|
|
||||||
"ext-intl": "For best performance"
|
|
||||||
},
|
|
||||||
"type": "library",
|
|
||||||
"extra": {
|
|
||||||
"branch-alias": {
|
|
||||||
"dev-main": "1.22-dev"
|
|
||||||
},
|
|
||||||
"thanks": {
|
|
||||||
"name": "symfony/polyfill",
|
|
||||||
"url": "https://github.com/symfony/polyfill"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"autoload": {
|
|
||||||
"psr-4": {
|
|
||||||
"Symfony\\Polyfill\\Intl\\Idn\\": ""
|
|
||||||
},
|
|
||||||
"files": [
|
|
||||||
"bootstrap.php"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"notification-url": "https://packagist.org/downloads/",
|
|
||||||
"license": [
|
|
||||||
"MIT"
|
|
||||||
],
|
|
||||||
"authors": [
|
|
||||||
{
|
|
||||||
"name": "Laurent Bassin",
|
|
||||||
"email": "laurent@bassin.info"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "Trevor Rowbotham",
|
|
||||||
"email": "trevor.rowbotham@pm.me"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "Symfony Community",
|
|
||||||
"homepage": "https://symfony.com/contributors"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"description": "Symfony polyfill for intl's idn_to_ascii and idn_to_utf8 functions",
|
|
||||||
"homepage": "https://symfony.com",
|
|
||||||
"keywords": [
|
|
||||||
"compatibility",
|
|
||||||
"idn",
|
|
||||||
"intl",
|
|
||||||
"polyfill",
|
|
||||||
"portable",
|
|
||||||
"shim"
|
|
||||||
],
|
|
||||||
"funding": [
|
|
||||||
{
|
|
||||||
"url": "https://symfony.com/sponsor",
|
|
||||||
"type": "custom"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"url": "https://github.com/fabpot",
|
|
||||||
"type": "github"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
|
|
||||||
"type": "tidelift"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"time": "2021-01-22T09:19:47+00:00"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "symfony/polyfill-intl-normalizer",
|
|
||||||
"version": "v1.22.1",
|
|
||||||
"source": {
|
|
||||||
"type": "git",
|
|
||||||
"url": "https://github.com/symfony/polyfill-intl-normalizer.git",
|
|
||||||
"reference": "43a0283138253ed1d48d352ab6d0bdb3f809f248"
|
|
||||||
},
|
|
||||||
"dist": {
|
|
||||||
"type": "zip",
|
|
||||||
"url": "https://api.github.com/repos/symfony/polyfill-intl-normalizer/zipball/43a0283138253ed1d48d352ab6d0bdb3f809f248",
|
|
||||||
"reference": "43a0283138253ed1d48d352ab6d0bdb3f809f248",
|
|
||||||
"shasum": ""
|
|
||||||
},
|
|
||||||
"require": {
|
|
||||||
"php": ">=7.1"
|
|
||||||
},
|
|
||||||
"suggest": {
|
|
||||||
"ext-intl": "For best performance"
|
|
||||||
},
|
|
||||||
"type": "library",
|
|
||||||
"extra": {
|
|
||||||
"branch-alias": {
|
|
||||||
"dev-main": "1.22-dev"
|
|
||||||
},
|
|
||||||
"thanks": {
|
|
||||||
"name": "symfony/polyfill",
|
|
||||||
"url": "https://github.com/symfony/polyfill"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"autoload": {
|
|
||||||
"psr-4": {
|
|
||||||
"Symfony\\Polyfill\\Intl\\Normalizer\\": ""
|
|
||||||
},
|
|
||||||
"files": [
|
|
||||||
"bootstrap.php"
|
|
||||||
],
|
|
||||||
"classmap": [
|
|
||||||
"Resources/stubs"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"notification-url": "https://packagist.org/downloads/",
|
|
||||||
"license": [
|
|
||||||
"MIT"
|
|
||||||
],
|
|
||||||
"authors": [
|
|
||||||
{
|
|
||||||
"name": "Nicolas Grekas",
|
|
||||||
"email": "p@tchwork.com"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "Symfony Community",
|
|
||||||
"homepage": "https://symfony.com/contributors"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"description": "Symfony polyfill for intl's Normalizer class and related functions",
|
|
||||||
"homepage": "https://symfony.com",
|
|
||||||
"keywords": [
|
|
||||||
"compatibility",
|
|
||||||
"intl",
|
|
||||||
"normalizer",
|
|
||||||
"polyfill",
|
|
||||||
"portable",
|
|
||||||
"shim"
|
|
||||||
],
|
|
||||||
"funding": [
|
|
||||||
{
|
|
||||||
"url": "https://symfony.com/sponsor",
|
|
||||||
"type": "custom"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"url": "https://github.com/fabpot",
|
|
||||||
"type": "github"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
|
|
||||||
"type": "tidelift"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"time": "2021-01-22T09:19:47+00:00"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "symfony/polyfill-mbstring",
|
|
||||||
"version": "v1.22.1",
|
|
||||||
"source": {
|
|
||||||
"type": "git",
|
|
||||||
"url": "https://github.com/symfony/polyfill-mbstring.git",
|
|
||||||
"reference": "5232de97ee3b75b0360528dae24e73db49566ab1"
|
|
||||||
},
|
|
||||||
"dist": {
|
|
||||||
"type": "zip",
|
|
||||||
"url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/5232de97ee3b75b0360528dae24e73db49566ab1",
|
|
||||||
"reference": "5232de97ee3b75b0360528dae24e73db49566ab1",
|
|
||||||
"shasum": ""
|
|
||||||
},
|
|
||||||
"require": {
|
|
||||||
"php": ">=7.1"
|
|
||||||
},
|
|
||||||
"suggest": {
|
|
||||||
"ext-mbstring": "For best performance"
|
|
||||||
},
|
|
||||||
"type": "library",
|
|
||||||
"extra": {
|
|
||||||
"branch-alias": {
|
|
||||||
"dev-main": "1.22-dev"
|
|
||||||
},
|
|
||||||
"thanks": {
|
|
||||||
"name": "symfony/polyfill",
|
|
||||||
"url": "https://github.com/symfony/polyfill"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"autoload": {
|
|
||||||
"psr-4": {
|
|
||||||
"Symfony\\Polyfill\\Mbstring\\": ""
|
|
||||||
},
|
|
||||||
"files": [
|
|
||||||
"bootstrap.php"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"notification-url": "https://packagist.org/downloads/",
|
|
||||||
"license": [
|
|
||||||
"MIT"
|
|
||||||
],
|
|
||||||
"authors": [
|
|
||||||
{
|
|
||||||
"name": "Nicolas Grekas",
|
|
||||||
"email": "p@tchwork.com"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "Symfony Community",
|
|
||||||
"homepage": "https://symfony.com/contributors"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"description": "Symfony polyfill for the Mbstring extension",
|
|
||||||
"homepage": "https://symfony.com",
|
|
||||||
"keywords": [
|
|
||||||
"compatibility",
|
|
||||||
"mbstring",
|
|
||||||
"polyfill",
|
|
||||||
"portable",
|
|
||||||
"shim"
|
|
||||||
],
|
|
||||||
"funding": [
|
|
||||||
{
|
|
||||||
"url": "https://symfony.com/sponsor",
|
|
||||||
"type": "custom"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"url": "https://github.com/fabpot",
|
|
||||||
"type": "github"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
|
|
||||||
"type": "tidelift"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"time": "2021-01-22T09:19:47+00:00"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "symfony/polyfill-php72",
|
|
||||||
"version": "v1.22.1",
|
|
||||||
"source": {
|
|
||||||
"type": "git",
|
|
||||||
"url": "https://github.com/symfony/polyfill-php72.git",
|
|
||||||
"reference": "cc6e6f9b39fe8075b3dabfbaf5b5f645ae1340c9"
|
|
||||||
},
|
|
||||||
"dist": {
|
|
||||||
"type": "zip",
|
|
||||||
"url": "https://api.github.com/repos/symfony/polyfill-php72/zipball/cc6e6f9b39fe8075b3dabfbaf5b5f645ae1340c9",
|
|
||||||
"reference": "cc6e6f9b39fe8075b3dabfbaf5b5f645ae1340c9",
|
|
||||||
"shasum": ""
|
|
||||||
},
|
|
||||||
"require": {
|
|
||||||
"php": ">=7.1"
|
|
||||||
},
|
|
||||||
"type": "library",
|
|
||||||
"extra": {
|
|
||||||
"branch-alias": {
|
|
||||||
"dev-main": "1.22-dev"
|
|
||||||
},
|
|
||||||
"thanks": {
|
|
||||||
"name": "symfony/polyfill",
|
|
||||||
"url": "https://github.com/symfony/polyfill"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"autoload": {
|
|
||||||
"psr-4": {
|
|
||||||
"Symfony\\Polyfill\\Php72\\": ""
|
|
||||||
},
|
|
||||||
"files": [
|
|
||||||
"bootstrap.php"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"notification-url": "https://packagist.org/downloads/",
|
|
||||||
"license": [
|
|
||||||
"MIT"
|
|
||||||
],
|
|
||||||
"authors": [
|
|
||||||
{
|
|
||||||
"name": "Nicolas Grekas",
|
|
||||||
"email": "p@tchwork.com"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "Symfony Community",
|
|
||||||
"homepage": "https://symfony.com/contributors"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"description": "Symfony polyfill backporting some PHP 7.2+ features to lower PHP versions",
|
|
||||||
"homepage": "https://symfony.com",
|
|
||||||
"keywords": [
|
|
||||||
"compatibility",
|
|
||||||
"polyfill",
|
|
||||||
"portable",
|
|
||||||
"shim"
|
|
||||||
],
|
|
||||||
"funding": [
|
|
||||||
{
|
|
||||||
"url": "https://symfony.com/sponsor",
|
|
||||||
"type": "custom"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"url": "https://github.com/fabpot",
|
|
||||||
"type": "github"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
|
|
||||||
"type": "tidelift"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"time": "2021-01-07T16:49:33+00:00"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "symfony/polyfill-php80",
|
|
||||||
"version": "v1.22.1",
|
|
||||||
"source": {
|
|
||||||
"type": "git",
|
|
||||||
"url": "https://github.com/symfony/polyfill-php80.git",
|
|
||||||
"reference": "dc3063ba22c2a1fd2f45ed856374d79114998f91"
|
|
||||||
},
|
|
||||||
"dist": {
|
|
||||||
"type": "zip",
|
|
||||||
"url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/dc3063ba22c2a1fd2f45ed856374d79114998f91",
|
|
||||||
"reference": "dc3063ba22c2a1fd2f45ed856374d79114998f91",
|
|
||||||
"shasum": ""
|
|
||||||
},
|
|
||||||
"require": {
|
|
||||||
"php": ">=7.1"
|
|
||||||
},
|
|
||||||
"type": "library",
|
|
||||||
"extra": {
|
|
||||||
"branch-alias": {
|
|
||||||
"dev-main": "1.22-dev"
|
|
||||||
},
|
|
||||||
"thanks": {
|
|
||||||
"name": "symfony/polyfill",
|
|
||||||
"url": "https://github.com/symfony/polyfill"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"autoload": {
|
|
||||||
"psr-4": {
|
|
||||||
"Symfony\\Polyfill\\Php80\\": ""
|
|
||||||
},
|
|
||||||
"files": [
|
|
||||||
"bootstrap.php"
|
|
||||||
],
|
|
||||||
"classmap": [
|
|
||||||
"Resources/stubs"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"notification-url": "https://packagist.org/downloads/",
|
|
||||||
"license": [
|
|
||||||
"MIT"
|
|
||||||
],
|
|
||||||
"authors": [
|
|
||||||
{
|
|
||||||
"name": "Ion Bazan",
|
|
||||||
"email": "ion.bazan@gmail.com"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "Nicolas Grekas",
|
|
||||||
"email": "p@tchwork.com"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "Symfony Community",
|
|
||||||
"homepage": "https://symfony.com/contributors"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"description": "Symfony polyfill backporting some PHP 8.0+ features to lower PHP versions",
|
|
||||||
"homepage": "https://symfony.com",
|
|
||||||
"keywords": [
|
|
||||||
"compatibility",
|
|
||||||
"polyfill",
|
|
||||||
"portable",
|
|
||||||
"shim"
|
|
||||||
],
|
|
||||||
"funding": [
|
|
||||||
{
|
|
||||||
"url": "https://symfony.com/sponsor",
|
|
||||||
"type": "custom"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"url": "https://github.com/fabpot",
|
|
||||||
"type": "github"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
|
|
||||||
"type": "tidelift"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"time": "2021-01-07T16:49:33+00:00"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "tonyhhyip/sse",
|
|
||||||
"version": "2.1.3",
|
|
||||||
"source": {
|
|
||||||
"type": "git",
|
|
||||||
"url": "https://github.com/tonyhhyip/libSSE-php.git",
|
|
||||||
"reference": "804af074e46247b5d9290b509c5cab8de523c93a"
|
|
||||||
},
|
|
||||||
"dist": {
|
|
||||||
"type": "zip",
|
|
||||||
"url": "https://api.github.com/repos/tonyhhyip/libSSE-php/zipball/804af074e46247b5d9290b509c5cab8de523c93a",
|
|
||||||
"reference": "804af074e46247b5d9290b509c5cab8de523c93a",
|
|
||||||
"shasum": ""
|
|
||||||
},
|
|
||||||
"require": {
|
|
||||||
"php": ">=5.3.9",
|
|
||||||
"symfony/http-foundation": "~2.7 | ~3.0 | ~4.0"
|
|
||||||
},
|
|
||||||
"require-dev": {
|
|
||||||
"phpunit/phpunit": "~4.2 | ~5.0"
|
|
||||||
},
|
|
||||||
"suggest": {
|
|
||||||
"predis/predis": "For using RedisMechnism",
|
|
||||||
"symfony/polyfill-apcu": "For APCMecnism on PHP < 7.0"
|
|
||||||
},
|
|
||||||
"type": "library",
|
|
||||||
"autoload": {
|
|
||||||
"psr-4": {
|
|
||||||
"Sse\\": "src/"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"notification-url": "https://packagist.org/downloads/",
|
|
||||||
"license": [
|
|
||||||
"MIT"
|
|
||||||
],
|
|
||||||
"authors": [
|
|
||||||
{
|
|
||||||
"name": "Licson Lee",
|
|
||||||
"email": "licson0729@gmail.com",
|
|
||||||
"homepage": "https://licson.net/",
|
|
||||||
"role": "Owner"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "Tony Yip",
|
|
||||||
"email": "tony@opensource.hk",
|
|
||||||
"homepage": "https://github.com/tonyhhyip",
|
|
||||||
"role": "Developer"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"description": "An easy-to-use, object-oriented library for Server-Sent Events",
|
|
||||||
"homepage": "https://github.com/licson0729/libSSE-php",
|
|
||||||
"keywords": [
|
|
||||||
"sse"
|
|
||||||
],
|
|
||||||
"time": "2018-03-08T02:55:29+00:00"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"packages-dev": [],
|
|
||||||
"aliases": [],
|
|
||||||
"minimum-stability": "stable",
|
|
||||||
"stability-flags": [],
|
|
||||||
"prefer-stable": false,
|
|
||||||
"prefer-lowest": false,
|
|
||||||
"platform": [],
|
|
||||||
"platform-dev": [],
|
|
||||||
"plugin-api-version": "1.1.0"
|
|
||||||
}
|
|
|
@ -5,8 +5,8 @@ error_reporting(E_ALL | E_STRICT);
|
||||||
|
|
||||||
$config = [
|
$config = [
|
||||||
"host" => "localhost",
|
"host" => "localhost",
|
||||||
"dbname" => "juggl",
|
"dbname" => "admin_juggl",
|
||||||
"username" => "juggl",
|
"username" => "admin_juggl",
|
||||||
"password" => "?=5,}f_F&){;@xthx-[i",
|
"password" => "}dyn{5O!tUlZD;9R?lbi$.@=I,_a2L",
|
||||||
"table_prefix" => "ju_"
|
"table_prefix" => "ju_"
|
||||||
];
|
];
|
||||||
|
|
|
@ -1,44 +0,0 @@
|
||||||
<?php
|
|
||||||
require_once(__DIR__.'/vendor/autoload.php');
|
|
||||||
require_once(__DIR__."/services/authenticator.inc.php");
|
|
||||||
require_once(__DIR__."/services/responses.inc.php");
|
|
||||||
|
|
||||||
use Sse\Event;
|
|
||||||
use Sse\SSE;
|
|
||||||
|
|
||||||
//create the event handler
|
|
||||||
class YourEventHandler implements Event {
|
|
||||||
public function update(){
|
|
||||||
//Here's the place to send data
|
|
||||||
return 'Hello, world!';
|
|
||||||
}
|
|
||||||
|
|
||||||
public function check(){
|
|
||||||
//Here's the place to check when the data needs update
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
$SSE_KEY_KEY = "sse_key";
|
|
||||||
$auth = new Authenticator();
|
|
||||||
if (!isset($_GET["user_id"]) || !isset($_GET[$SSE_KEY_KEY]) || !$auth->isSseAuthenticated($_GET["user_id"], $_GET[$SSE_KEY_KEY])) {
|
|
||||||
respondStatus(403);
|
|
||||||
}
|
|
||||||
|
|
||||||
$sse = new SSE(); //create a libSSE instance
|
|
||||||
|
|
||||||
$sse->exec_limit = 600; //the execution time of the loop in seconds. Default: 600. Set to 0 to allow the script to run as long as possible.
|
|
||||||
$sse->sleep_time = 5; //The time to sleep after the data has been sent in seconds. Default: 0.5.
|
|
||||||
$sse->client_reconnect = 10; //the time for the client to reconnect after the connection has lost in seconds. Default: 1.
|
|
||||||
$sse->use_chunked_encoding = false; //Use chunked encoding. Some server may get problems with this and it defaults to false
|
|
||||||
$sse->keep_alive_time = 600; //The interval of sending a signal to keep the connection alive. Default: 300 seconds.
|
|
||||||
$sse->allow_cors = true; //Allow cross-domain access? Default: false. If you want others to access this must set to true.
|
|
||||||
|
|
||||||
$sse->addEventListener('change', new YourEventHandler());//register your event handler
|
|
||||||
$sse->start();//start the event loop
|
|
||||||
|
|
||||||
?>
|
|
|
@ -24,8 +24,12 @@ class GetRecordsBranch extends ApiBranch
|
||||||
if ($params->exists(["finished"])) {
|
if ($params->exists(["finished"])) {
|
||||||
$finished = $params->get("finished");
|
$finished = $params->get("finished");
|
||||||
}
|
}
|
||||||
|
$visible = NULL;
|
||||||
|
if ($params->exists(["visible"])) {
|
||||||
|
$visible = $params->get("visible");
|
||||||
|
}
|
||||||
|
|
||||||
$records = getRecords($user_id, $limit, $finished);
|
$records = getRecords($user_id, $limit, $finished, $visible);
|
||||||
|
|
||||||
$json = new JsonBuilder();
|
$json = new JsonBuilder();
|
||||||
$json->addRecords($records);
|
$json->addRecords($records);
|
||||||
|
|
38
public/api/getStats.php
Normal file
38
public/api/getStats.php
Normal file
|
@ -0,0 +1,38 @@
|
||||||
|
<?php
|
||||||
|
session_start();
|
||||||
|
require_once(__DIR__ . "/services/apiBranch.inc.php");
|
||||||
|
require_once(__DIR__ . "/services/jsonBuilder.inc.php");
|
||||||
|
require_once(__DIR__ . "/services/responses.inc.php");
|
||||||
|
require_once(__DIR__ . "/services/jugglDbApi.inc.php");
|
||||||
|
|
||||||
|
class GetStatsBranch extends ApiBranch
|
||||||
|
{
|
||||||
|
function get(ParamCleaner $params)
|
||||||
|
{
|
||||||
|
respondStatus(405);
|
||||||
|
}
|
||||||
|
|
||||||
|
function post(ParamCleaner $params)
|
||||||
|
{
|
||||||
|
$user_id = $params->get("user_id");
|
||||||
|
|
||||||
|
$from_date = date("Y-m-d");
|
||||||
|
if ($params->exists(["from_date"])) {
|
||||||
|
$from_date = $params->get("from_date");
|
||||||
|
}
|
||||||
|
$until_date = date("Y-m-d");
|
||||||
|
if ($params->exists(["until_date"])) {
|
||||||
|
$until_date = $params->get("until_date");
|
||||||
|
}
|
||||||
|
|
||||||
|
$stats = getStats($user_id, $from_date, $until_date);
|
||||||
|
|
||||||
|
$json = new JsonBuilder();
|
||||||
|
$json->addStats($stats);
|
||||||
|
|
||||||
|
respondJson($json);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$branch = new GetStatsBranch();
|
||||||
|
$branch->execute();
|
|
@ -1,7 +1,5 @@
|
||||||
<?php
|
<?php
|
||||||
require_once(__DIR__."/dbOperations.inc.php");
|
require_once(__DIR__."/dbOperations.inc.php");
|
||||||
require_once(__DIR__."/paramCleaner.inc.php");
|
|
||||||
require_once(__DIR__."/jugglDbApi.inc.php");
|
|
||||||
|
|
||||||
class Authenticator {
|
class Authenticator {
|
||||||
function isApiKeyAuthenticated($api_key, $user_id) {
|
function isApiKeyAuthenticated($api_key, $user_id) {
|
||||||
|
@ -18,10 +16,5 @@ class Authenticator {
|
||||||
function isAuthenticated($params) {
|
function isAuthenticated($params) {
|
||||||
return $this->isApiKeyAuthenticated($params->get('api_key'), $params->get('user_id'));
|
return $this->isApiKeyAuthenticated($params->get('api_key'), $params->get('user_id'));
|
||||||
}
|
}
|
||||||
|
|
||||||
function isSseAuthenticated($user_id, $sse_key) {
|
|
||||||
$params = new ParamCleaner(["sse_key" => $sse_key]);
|
|
||||||
return validatedSseKey($user_id, $params);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
?>
|
?>
|
|
@ -7,6 +7,7 @@ class DbOperations
|
||||||
{
|
{
|
||||||
$this->resetQuery();
|
$this->resetQuery();
|
||||||
$this->tablePrefix = $tablePrefix;
|
$this->tablePrefix = $tablePrefix;
|
||||||
|
$this->customValueId = 0;
|
||||||
|
|
||||||
require(__DIR__ . "/../config/config.php");
|
require(__DIR__ . "/../config/config.php");
|
||||||
$this->config = $config;
|
$this->config = $config;
|
||||||
|
@ -55,6 +56,22 @@ class DbOperations
|
||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function groupBy(array $attributes, bool $addTableName = true)
|
||||||
|
{
|
||||||
|
for ($i = 0; $i < count($attributes); $i++) {
|
||||||
|
$a = $attributes[$i];
|
||||||
|
// Add table name prefix if missing
|
||||||
|
if ($addTableName && strpos($a, ".") === false) {
|
||||||
|
$attributes[$i] = "$this->table.$a";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$formattedAttributes = implode(', ', $attributes);
|
||||||
|
|
||||||
|
$this->addToQuery("GROUP BY $formattedAttributes");
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
function orderBy(string $attribute, string $order = Order::ASC)
|
function orderBy(string $attribute, string $order = Order::ASC)
|
||||||
{
|
{
|
||||||
|
|
|
@ -78,24 +78,6 @@ class JsonBuilder
|
||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
function addSseKey(array $sse)
|
|
||||||
{
|
|
||||||
if ($sse === null) return;
|
|
||||||
|
|
||||||
$columns = array(
|
|
||||||
"sse_key_id" => "",
|
|
||||||
"sse_key" => "",
|
|
||||||
"user_id" => "",
|
|
||||||
"created" => ""
|
|
||||||
);
|
|
||||||
|
|
||||||
$this->jsonData['sse'] = array();
|
|
||||||
foreach ($sse as $sse) {
|
|
||||||
$this->jsonData['sse'][] = $this->createJsonArray($sse, $columns);
|
|
||||||
}
|
|
||||||
return $this;
|
|
||||||
}
|
|
||||||
|
|
||||||
function addRecordTags(array $record_tags)
|
function addRecordTags(array $record_tags)
|
||||||
{
|
{
|
||||||
if ($record_tags === null) return;
|
if ($record_tags === null) return;
|
||||||
|
@ -104,7 +86,8 @@ class JsonBuilder
|
||||||
"record_tag_id" => "",
|
"record_tag_id" => "",
|
||||||
"name" => "",
|
"name" => "",
|
||||||
"user_id" => "",
|
"user_id" => "",
|
||||||
"visible" => ""
|
"visible" => "",
|
||||||
|
"bundle" => ""
|
||||||
);
|
);
|
||||||
|
|
||||||
$this->jsonData['record_tags'] = array();
|
$this->jsonData['record_tags'] = array();
|
||||||
|
@ -114,6 +97,27 @@ class JsonBuilder
|
||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function addStats(array $stats)
|
||||||
|
{
|
||||||
|
if ($stats === null) return;
|
||||||
|
|
||||||
|
$columns = array(
|
||||||
|
"name" => "",
|
||||||
|
"project_id" => "",
|
||||||
|
"color" => "",
|
||||||
|
"visible" => "",
|
||||||
|
"duration" => "",
|
||||||
|
"record_count" => "",
|
||||||
|
"date" => ""
|
||||||
|
);
|
||||||
|
|
||||||
|
$this->jsonData['stats'] = array();
|
||||||
|
foreach ($stats as $tag) {
|
||||||
|
$this->jsonData['stats'][] = $this->createJsonArray($tag, $columns);
|
||||||
|
}
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
private function createJsonArray(array $data, array $columns)
|
private function createJsonArray(array $data, array $columns)
|
||||||
{
|
{
|
||||||
$jsonArray = array();
|
$jsonArray = array();
|
||||||
|
|
|
@ -160,11 +160,17 @@ function getRunningRecords($user_id)
|
||||||
return $results;
|
return $results;
|
||||||
}
|
}
|
||||||
|
|
||||||
function getRecords($user_id, $limit = NULL, $finished = NULL)
|
function getRecords($user_id, $limit = NULL, $finished = NULL, $visible = NULL)
|
||||||
{
|
{
|
||||||
$db = new DbOperations();
|
$db = new DbOperations();
|
||||||
$db->select("time_records");
|
$db->select("time_records", ["record_id", "start_time", "end_time", "duration", "user_id", "project_id", "start_device_id"]);
|
||||||
$db->where("user_id", Comparison::EQUAL, $user_id);
|
if ($visible != NULL) {
|
||||||
|
$db->innerJoin("projects", "project_id");
|
||||||
|
}
|
||||||
|
$db->where("ju_time_records.user_id", Comparison::EQUAL, $user_id);
|
||||||
|
if ($visible != NULL) {
|
||||||
|
$db->where("ju_projects.visible", Comparison::EQUAL, $visible);
|
||||||
|
}
|
||||||
if ($finished != NULL) {
|
if ($finished != NULL) {
|
||||||
if ($finished) {
|
if ($finished) {
|
||||||
$db->addSql(" AND end_time IS NOT NULL");
|
$db->addSql(" AND end_time IS NOT NULL");
|
||||||
|
@ -210,7 +216,7 @@ function updateRecord($user_id, $record)
|
||||||
$data = [];
|
$data = [];
|
||||||
$props = ["end_time", "start_time", "duration", "project_id"];
|
$props = ["end_time", "start_time", "duration", "project_id"];
|
||||||
foreach ($props as $p) {
|
foreach ($props as $p) {
|
||||||
if (array_key_exists ($p, $record)) {
|
if (array_key_exists($p, $record)) {
|
||||||
$data[$p] = $record[$p];
|
$data[$p] = $record[$p];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -230,7 +236,7 @@ function updateProject($user_id, $project)
|
||||||
$data = [];
|
$data = [];
|
||||||
$props = ["name", "start_date", "color", "visible"];
|
$props = ["name", "start_date", "color", "visible"];
|
||||||
foreach ($props as $p) {
|
foreach ($props as $p) {
|
||||||
if (array_key_exists ($p, $project)) {
|
if (array_key_exists($p, $project)) {
|
||||||
$data[$p] = $project[$p];
|
$data[$p] = $project[$p];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -248,9 +254,9 @@ function updateRecordTag($user_id, $tag)
|
||||||
|
|
||||||
// Update given parameters
|
// Update given parameters
|
||||||
$data = [];
|
$data = [];
|
||||||
$props = ["name", "visible"];
|
$props = ["name", "visible", "bundle"];
|
||||||
foreach ($props as $p) {
|
foreach ($props as $p) {
|
||||||
if (array_key_exists ($p, $tag)) {
|
if (array_key_exists($p, $tag)) {
|
||||||
$data[$p] = $tag[$p];
|
$data[$p] = $tag[$p];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -286,108 +292,6 @@ function removeProject($user_id, $params)
|
||||||
removeRecordsOfProject($user_id, $project_id);
|
removeRecordsOfProject($user_id, $project_id);
|
||||||
}
|
}
|
||||||
|
|
||||||
function secureRandomBytes ()
|
|
||||||
{
|
|
||||||
$pr_bits = '';
|
|
||||||
|
|
||||||
// Unix/Linux platform?
|
|
||||||
$fp = @fopen('/dev/urandom','rb');
|
|
||||||
if ($fp !== FALSE) {
|
|
||||||
$pr_bits .= @fread($fp,16);
|
|
||||||
@fclose($fp);
|
|
||||||
}
|
|
||||||
|
|
||||||
// MS-Windows platform?
|
|
||||||
if (@class_exists('COM')) {
|
|
||||||
// http://msdn.microsoft.com/en-us/library/aa388176(VS.85).aspx
|
|
||||||
try {
|
|
||||||
$CAPI_Util = new COM('CAPICOM.Utilities.1');
|
|
||||||
$pr_bits .= $CAPI_Util->GetRandom(16,0);
|
|
||||||
|
|
||||||
// if we ask for binary data PHP munges it, so we
|
|
||||||
// request base64 return value. We squeeze out the
|
|
||||||
// redundancy and useless ==CRLF by hashing...
|
|
||||||
if ($pr_bits) { $pr_bits = md5($pr_bits,TRUE); }
|
|
||||||
} catch (Exception $ex) {
|
|
||||||
// echo 'Exception: ' . $ex->getMessage();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return $pr_bits;
|
|
||||||
}
|
|
||||||
|
|
||||||
function createSseKey($user_id)
|
|
||||||
{
|
|
||||||
// Generate random key
|
|
||||||
$sse_key = secureRandomBytes();
|
|
||||||
|
|
||||||
$data = [
|
|
||||||
"user_id" => $user_id,
|
|
||||||
"sse_key" => $sse_key
|
|
||||||
];
|
|
||||||
|
|
||||||
$db = new DbOperations();
|
|
||||||
$db->insert("sse_keys", $data);
|
|
||||||
$db->execute();
|
|
||||||
|
|
||||||
return $sse_key;
|
|
||||||
}
|
|
||||||
|
|
||||||
function deleteSseKey($user_id, $params)
|
|
||||||
{
|
|
||||||
$sse_key_id = $params->get("sse_key_id");
|
|
||||||
$sse_key = $params->get("sse_key");
|
|
||||||
|
|
||||||
$db = new DbOperations();
|
|
||||||
$db->delete("sse_keys");
|
|
||||||
$db->where("user_id", Comparison::EQUAL, $user_id);
|
|
||||||
if ($sse_key_id != null) {
|
|
||||||
$db->where("sse_key_id", Comparison::EQUAL, $sse_key_id);
|
|
||||||
}
|
|
||||||
if ($sse_key != null) {
|
|
||||||
$db->where("sse_key", Comparison::EQUAL, $sse_key);
|
|
||||||
}
|
|
||||||
$db->execute();
|
|
||||||
}
|
|
||||||
|
|
||||||
function getSseKey($user_id, $params)
|
|
||||||
{
|
|
||||||
$sse_key_id = $params->get("sse_key_id");
|
|
||||||
$sse_key = $params->get("sse_key");
|
|
||||||
|
|
||||||
$db = new DbOperations();
|
|
||||||
$db->select("sse_keys");
|
|
||||||
$db->where("user_id", Comparison::EQUAL, $user_id);
|
|
||||||
if ($sse_key_id != null) {
|
|
||||||
$db->where("sse_key_id", Comparison::EQUAL, $sse_key_id);
|
|
||||||
}
|
|
||||||
if ($sse_key != null) {
|
|
||||||
$db->where("sse_key", Comparison::EQUAL, $sse_key);
|
|
||||||
}
|
|
||||||
$result = $db->execute();
|
|
||||||
|
|
||||||
if (count($result) <= 0) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
$result = $result[0];
|
|
||||||
|
|
||||||
return $result;
|
|
||||||
}
|
|
||||||
|
|
||||||
function getAllSseKeys($user_id)
|
|
||||||
{
|
|
||||||
$db = new DbOperations();
|
|
||||||
$db->select("sse_keys");
|
|
||||||
$db->where("user_id", Comparison::EQUAL, $user_id);
|
|
||||||
return $db->execute();
|
|
||||||
}
|
|
||||||
|
|
||||||
function validatedSseKey($user_id, $params)
|
|
||||||
{
|
|
||||||
$key_entry = getSseKey($user_id, $params);
|
|
||||||
return $key_entry != null;
|
|
||||||
}
|
|
||||||
|
|
||||||
function removeRecordsOfProject($user_id, $project_id)
|
function removeRecordsOfProject($user_id, $project_id)
|
||||||
{
|
{
|
||||||
$db = new DbOperations();
|
$db = new DbOperations();
|
||||||
|
@ -475,7 +379,9 @@ function getRecordExternalData($record)
|
||||||
$data = [
|
$data = [
|
||||||
"record_tag_id" => $tag["record_tag_id"],
|
"record_tag_id" => $tag["record_tag_id"],
|
||||||
"name" => $tag["name"],
|
"name" => $tag["name"],
|
||||||
"user_id" => $tag["user_id"]
|
"user_id" => $tag["user_id"],
|
||||||
|
"visible" => $tag["visible"],
|
||||||
|
"bundle" => $tag["bundle"]
|
||||||
];
|
];
|
||||||
$tags[] = $data;
|
$tags[] = $data;
|
||||||
}
|
}
|
||||||
|
@ -558,3 +464,30 @@ function removeTagFromRecord($tag_id, $record_id)
|
||||||
$db->where("record_id", Comparison::EQUAL, $record_id);
|
$db->where("record_id", Comparison::EQUAL, $record_id);
|
||||||
$db->execute();
|
$db->execute();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getStats($user_id, $from_date, $until_date)
|
||||||
|
{
|
||||||
|
$sum_duration = "SUM(ju_time_records.duration) AS duration";
|
||||||
|
$date = "DATE(ju_time_records.start_time) AS date";
|
||||||
|
$record_count = "COUNT(*) AS record_count";
|
||||||
|
|
||||||
|
$where1 = " AND DATE(ju_time_records.start_time) >= DATE( ";
|
||||||
|
$where2 = " ) AND DATE(ju_time_records.start_time) <= DATE( ";
|
||||||
|
$where3 = " ) AND ju_time_records.end_time IS NOT NULL ";
|
||||||
|
|
||||||
|
$db = new DbOperations();
|
||||||
|
$db->select("projects", ["ju_projects.name AS name", "ju_projects.project_id", "color", "visible", $sum_duration, $date, $record_count], false);
|
||||||
|
$db->innerJoin("time_records", "project_id");
|
||||||
|
// $db->innerJoin("tags_on_records", "record_id", "record_id", "time_records");
|
||||||
|
// $db->innerJoin("record_tags", "record_tag_id", "record_tag_id", "tags_on_records");
|
||||||
|
|
||||||
|
$db->where("ju_projects.user_id", Comparison::EQUAL, $user_id);
|
||||||
|
$db->addSql($where1);
|
||||||
|
$db->addValue($from_date);
|
||||||
|
$db->addSql($where2);
|
||||||
|
$db->addValue($until_date);
|
||||||
|
$db->addSql($where3);
|
||||||
|
|
||||||
|
$db->groupBy(["ju_projects.project_id", "date"], false);
|
||||||
|
return $db->execute();
|
||||||
|
}
|
||||||
|
|
|
@ -1,14 +0,0 @@
|
||||||
<!DOCTYPE html>
|
|
||||||
<html>
|
|
||||||
<head>
|
|
||||||
<meta charset="utf-8" />
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<script>
|
|
||||||
var source = new EventSource('https://juggl.giller.dev/api/events.php?sse_key=9f8u592845vn982z4&user_id=1');
|
|
||||||
source.onmessage = function(e) {
|
|
||||||
document.body.innerHTML += e.data + '<br>';
|
|
||||||
};
|
|
||||||
</script>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
2
public/robots.txt
Normal file
2
public/robots.txt
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
User-agent: *
|
||||||
|
Disallow:
|
Binary file not shown.
Before Width: | Height: | Size: 67 KiB After Width: | Height: | Size: 25 KiB |
BIN
src/assets/title.png
Normal file
BIN
src/assets/title.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 67 KiB |
|
@ -1,17 +1,15 @@
|
||||||
<template>
|
<template>
|
||||||
<div>
|
<div>
|
||||||
<b-link to="/">
|
<b-link to="/">
|
||||||
<b-img
|
<b-img :src="image" alt="Juggl" :width="widthSize" :center="center" />
|
||||||
:src="require('../../assets/logo.png')"
|
|
||||||
alt="Juggl"
|
|
||||||
:width="widthSize"
|
|
||||||
:center="center"
|
|
||||||
/>
|
|
||||||
</b-link>
|
</b-link>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
import logo from "./../../assets/logo.png";
|
||||||
|
import title from "./../../assets/title.png";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: "BaseLogo",
|
name: "BaseLogo",
|
||||||
props: {
|
props: {
|
||||||
|
@ -22,11 +20,15 @@ export default {
|
||||||
center: {
|
center: {
|
||||||
default: false,
|
default: false,
|
||||||
type: Boolean
|
type: Boolean
|
||||||
|
},
|
||||||
|
iconOnly: {
|
||||||
|
default: false,
|
||||||
|
type: Boolean
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
widthSize: function() {
|
widthSize: function() {
|
||||||
let sizes = {
|
let titleSizes = {
|
||||||
mini: "35px",
|
mini: "35px",
|
||||||
tiny: "80px",
|
tiny: "80px",
|
||||||
smaller: "110px",
|
smaller: "110px",
|
||||||
|
@ -38,9 +40,34 @@ export default {
|
||||||
huge: "800px",
|
huge: "800px",
|
||||||
massive: "960px"
|
massive: "960px"
|
||||||
};
|
};
|
||||||
|
let logoSizes = {
|
||||||
|
mini: "12px",
|
||||||
|
tiny: "30px",
|
||||||
|
smaller: "50px",
|
||||||
|
small: "65px",
|
||||||
|
normal: "80x",
|
||||||
|
medium: "100px",
|
||||||
|
large: "150px",
|
||||||
|
big: "200px",
|
||||||
|
huge: "300px",
|
||||||
|
massive: "400px"
|
||||||
|
};
|
||||||
|
|
||||||
|
var sizes = titleSizes;
|
||||||
|
if (this.iconOnly) {
|
||||||
|
sizes = logoSizes;
|
||||||
|
}
|
||||||
|
|
||||||
let targetSize = sizes[this.size];
|
let targetSize = sizes[this.size];
|
||||||
if (targetSize === undefined) return sizes["small"];
|
if (targetSize === undefined) return sizes["small"];
|
||||||
else return targetSize;
|
else return targetSize;
|
||||||
|
},
|
||||||
|
image: function() {
|
||||||
|
if (this.iconOnly) {
|
||||||
|
return logo;
|
||||||
|
} else {
|
||||||
|
return title;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
<template>
|
<template>
|
||||||
<section>
|
<section>
|
||||||
<BaseTitle v-if="title" center size="large">{{ title }}</BaseTitle>
|
<BaseTitle v-if="title" center size="huge">{{ title }}</BaseTitle>
|
||||||
<slot />
|
<slot />
|
||||||
</section>
|
</section>
|
||||||
</template>
|
</template>
|
||||||
|
|
|
@ -1,5 +1,9 @@
|
||||||
<template>
|
<template>
|
||||||
<h1 id="title" :class="[size, { center: center }]" class="bold pt-5 pb-3">
|
<h1
|
||||||
|
id="title"
|
||||||
|
:class="[size, { center: center }, { outline: outline }]"
|
||||||
|
class="bold"
|
||||||
|
>
|
||||||
<slot />
|
<slot />
|
||||||
</h1>
|
</h1>
|
||||||
</template>
|
</template>
|
||||||
|
@ -15,12 +19,18 @@ export default {
|
||||||
center: {
|
center: {
|
||||||
default: true,
|
default: true,
|
||||||
type: Boolean
|
type: Boolean
|
||||||
|
},
|
||||||
|
outline: {
|
||||||
|
default: false,
|
||||||
|
type: Boolean
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="sass" scoped>
|
<style lang="sass" scoped>
|
||||||
|
@import '@/style/theme.sass'
|
||||||
|
|
||||||
#title.tiny
|
#title.tiny
|
||||||
font-size: 1em
|
font-size: 1em
|
||||||
#title.small
|
#title.small
|
||||||
|
@ -31,10 +41,17 @@ export default {
|
||||||
font-size: 1.7em
|
font-size: 1.7em
|
||||||
#title.huge
|
#title.huge
|
||||||
font-size: 2em
|
font-size: 2em
|
||||||
|
#title.giant
|
||||||
|
font-size: 3em
|
||||||
|
|
||||||
#title.center
|
#title.center
|
||||||
text-align: center
|
text-align: center
|
||||||
|
|
||||||
|
#title.outline
|
||||||
|
color: transparent
|
||||||
|
-webkit-text-stroke-width: 1.5px
|
||||||
|
-webkit-text-stroke-color: $secondary
|
||||||
|
|
||||||
.bold
|
.bold
|
||||||
font-weight: bold
|
font-weight: bold
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -5,6 +5,7 @@
|
||||||
variant="outline-primary"
|
variant="outline-primary"
|
||||||
right
|
right
|
||||||
>
|
>
|
||||||
|
<b-dropdown-item to="/statistics">Statistics</b-dropdown-item>
|
||||||
<b-dropdown-item to="/logout">Log out</b-dropdown-item>
|
<b-dropdown-item to="/logout">Log out</b-dropdown-item>
|
||||||
</b-dropdown>
|
</b-dropdown>
|
||||||
</template>
|
</template>
|
||||||
|
|
|
@ -1,9 +1,12 @@
|
||||||
<template>
|
<template>
|
||||||
<b-form @submit="submitForm">
|
<b-form @submit="submitForm">
|
||||||
<b-form-group id="id-group" label-for="id" label="Record ID">
|
<b-form-group id="id-group" label-for="id" label="Project ID">
|
||||||
<b-form-input id="id" v-model="form.project_id" required trim disabled>
|
<b-form-input id="id" v-model="form.project_id" required trim disabled>
|
||||||
</b-form-input>
|
</b-form-input>
|
||||||
</b-form-group>
|
</b-form-group>
|
||||||
|
<b-form-checkbox id="visible" v-model="form.visible" dark>
|
||||||
|
Visible
|
||||||
|
</b-form-checkbox>
|
||||||
<b-form-group id="name-group" label-for="name" label="Name">
|
<b-form-group id="name-group" label-for="name" label="Name">
|
||||||
<b-form-input id="name" v-model="form.name" required trim> </b-form-input>
|
<b-form-input id="name" v-model="form.name" required trim> </b-form-input>
|
||||||
</b-form-group>
|
</b-form-group>
|
||||||
|
@ -63,7 +66,8 @@ export default {
|
||||||
project_id: undefined,
|
project_id: undefined,
|
||||||
start_date: undefined,
|
start_date: undefined,
|
||||||
name: undefined,
|
name: undefined,
|
||||||
color: undefined
|
color: undefined,
|
||||||
|
visible: undefined
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
@ -105,6 +109,7 @@ export default {
|
||||||
this.form.name = this.project.name;
|
this.form.name = this.project.name;
|
||||||
this.form.start_date = this.project.start_date;
|
this.form.start_date = this.project.start_date;
|
||||||
this.form.color = this.project.color;
|
this.form.color = this.project.color;
|
||||||
|
this.form.visible = this.project.visible;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -156,6 +156,7 @@ export default {
|
||||||
start_time: this.times.start.date + " " + this.times.start.time,
|
start_time: this.times.start.date + " " + this.times.start.time,
|
||||||
end_time: this.times.end.date + " " + this.times.end.time,
|
end_time: this.times.end.date + " " + this.times.end.time,
|
||||||
duration: this.times.duration,
|
duration: this.times.duration,
|
||||||
|
tags: this.record.tags,
|
||||||
start_device_id: this.record.start_device_id // TODO: Remove at some time
|
start_device_id: this.record.start_device_id // TODO: Remove at some time
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -1,9 +1,12 @@
|
||||||
<template>
|
<template>
|
||||||
<b-form @submit="submitForm">
|
<b-form @submit="submitForm">
|
||||||
<b-form-group id="id-group" label-for="id" label="Record ID">
|
<b-form-group id="id-group" label-for="id" label="Tag ID">
|
||||||
<b-form-input id="id" v-model="form.record_tag_id" required trim disabled>
|
<b-form-input id="id" v-model="form.record_tag_id" required trim disabled>
|
||||||
</b-form-input>
|
</b-form-input>
|
||||||
</b-form-group>
|
</b-form-group>
|
||||||
|
<b-form-checkbox id="visible" v-model="form.visible" dark>
|
||||||
|
Visible
|
||||||
|
</b-form-checkbox>
|
||||||
<b-form-group id="name-group" label-for="name" label="Name">
|
<b-form-group id="name-group" label-for="name" label="Name">
|
||||||
<b-form-input id="name" v-model="form.name" required trim> </b-form-input>
|
<b-form-input id="name" v-model="form.name" required trim> </b-form-input>
|
||||||
</b-form-group>
|
</b-form-group>
|
||||||
|
@ -48,7 +51,8 @@ export default {
|
||||||
working: false,
|
working: false,
|
||||||
form: {
|
form: {
|
||||||
record_tag_id: undefined,
|
record_tag_id: undefined,
|
||||||
name: undefined
|
name: undefined,
|
||||||
|
visible: undefined
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
@ -84,6 +88,7 @@ export default {
|
||||||
created: function() {
|
created: function() {
|
||||||
this.form.record_tag_id = this.tag.record_tag_id;
|
this.form.record_tag_id = this.tag.record_tag_id;
|
||||||
this.form.name = this.tag.name;
|
this.form.name = this.tag.name;
|
||||||
|
this.form.visible = this.tag.visible;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
151
src/components/juggl/JugglDailyStatisticsList.vue
Normal file
151
src/components/juggl/JugglDailyStatisticsList.vue
Normal file
|
@ -0,0 +1,151 @@
|
||||||
|
<template>
|
||||||
|
<b-table
|
||||||
|
:items="aggregatedStatistics"
|
||||||
|
primary-key="day"
|
||||||
|
hover
|
||||||
|
:busy="isLoading"
|
||||||
|
:fields="statistic_fields"
|
||||||
|
sort-by="day"
|
||||||
|
sort-desc
|
||||||
|
>
|
||||||
|
<template #table-busy>
|
||||||
|
<div class="text-center">
|
||||||
|
<b-spinner></b-spinner>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<!-- Custom data -->
|
||||||
|
<template #cell(duration)="data">
|
||||||
|
{{ getDurationTimestamp(data.item.duration) }}
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<template #cell(distribution)="data">
|
||||||
|
<JugglProjectDistribution :projects="data.item.projects" />
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<template #cell(details)="data">
|
||||||
|
<b-button
|
||||||
|
size="sm"
|
||||||
|
@click="data.toggleDetails"
|
||||||
|
variant="outline-secondary"
|
||||||
|
>
|
||||||
|
<b-icon class="icon-btn" icon="bar-chart-steps" />
|
||||||
|
</b-button>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<template #row-details="data">
|
||||||
|
<b-card>
|
||||||
|
<JugglProjectDistribution
|
||||||
|
:projects="data.item.projects"
|
||||||
|
names
|
||||||
|
class="mb-3"
|
||||||
|
/>
|
||||||
|
<JugglProjectStatisticsList :statistics="data.item.statistics" />
|
||||||
|
</b-card>
|
||||||
|
</template>
|
||||||
|
</b-table>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import { helperService } from "@/services/helper.service.js";
|
||||||
|
import JugglProjectStatisticsList from "./JugglProjectStatisticsList.vue";
|
||||||
|
import JugglProjectDistribution from "./JugglProjectDistribution.vue";
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: "JugglDailyStatisticsList",
|
||||||
|
components: { JugglProjectStatisticsList, JugglProjectDistribution },
|
||||||
|
props: {
|
||||||
|
statistics: {
|
||||||
|
required: true,
|
||||||
|
type: Array
|
||||||
|
}
|
||||||
|
},
|
||||||
|
data: () => {
|
||||||
|
return {
|
||||||
|
statistic_fields: [
|
||||||
|
{
|
||||||
|
key: "day",
|
||||||
|
label: "Day"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: "duration",
|
||||||
|
label: "Duration"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: "distribution",
|
||||||
|
lavel: "Distribution"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: "record_count",
|
||||||
|
label: "Records"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: "details",
|
||||||
|
label: "Details"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
};
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
isLoading: function() {
|
||||||
|
return this.statistics === undefined;
|
||||||
|
},
|
||||||
|
aggregatedStatistics: function() {
|
||||||
|
if (this.statistics === undefined) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
var aggregated = {};
|
||||||
|
this.statistics.forEach(stat => {
|
||||||
|
var dayName = stat.date;
|
||||||
|
|
||||||
|
if (aggregated[dayName] === undefined) {
|
||||||
|
aggregated[dayName] = {
|
||||||
|
day: dayName,
|
||||||
|
duration: 0,
|
||||||
|
record_count: 0,
|
||||||
|
statistics: [],
|
||||||
|
projects: {}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (aggregated[dayName].projects[stat.project_id] === undefined) {
|
||||||
|
aggregated[dayName].projects[stat.project_id] = {
|
||||||
|
project_id: stat.project_id,
|
||||||
|
duration: 0,
|
||||||
|
record_count: 0,
|
||||||
|
color: stat.color,
|
||||||
|
name: stat.name,
|
||||||
|
visible: stat.visible,
|
||||||
|
statistics: []
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
aggregated[dayName].duration += Number(stat.duration);
|
||||||
|
aggregated[dayName].record_count += Number(stat.record_count);
|
||||||
|
aggregated[dayName].statistics.push(stat);
|
||||||
|
|
||||||
|
aggregated[dayName].projects[stat.project_id].duration += Number(
|
||||||
|
stat.duration
|
||||||
|
);
|
||||||
|
aggregated[dayName].projects[stat.project_id].record_count += Number(
|
||||||
|
stat.record_count
|
||||||
|
);
|
||||||
|
aggregated[dayName].projects[stat.project_id].statistics.push(stat);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Simplyfying object to lists
|
||||||
|
aggregated = Object.values(aggregated);
|
||||||
|
aggregated.forEach(stat => {
|
||||||
|
stat.projects = Object.values(stat.projects);
|
||||||
|
});
|
||||||
|
return aggregated;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
getDurationTimestamp: helperService.getDurationTimestamp
|
||||||
|
}
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="sass"></style>
|
151
src/components/juggl/JugglMonthlyStatisticsList.vue
Normal file
151
src/components/juggl/JugglMonthlyStatisticsList.vue
Normal file
|
@ -0,0 +1,151 @@
|
||||||
|
<template>
|
||||||
|
<b-table
|
||||||
|
:items="aggregatedStatistics"
|
||||||
|
primary-key="month"
|
||||||
|
hover
|
||||||
|
:busy="isLoading"
|
||||||
|
:fields="statistic_fields"
|
||||||
|
sort-by="month"
|
||||||
|
sort-desc
|
||||||
|
>
|
||||||
|
<template #table-busy>
|
||||||
|
<div class="text-center">
|
||||||
|
<b-spinner></b-spinner>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<!-- Custom data -->
|
||||||
|
<template #cell(duration)="data">
|
||||||
|
{{ getDurationTimestamp(data.item.duration) }}
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<template #cell(distribution)="data">
|
||||||
|
<JugglProjectDistribution :projects="data.item.projects" class="mb-3" />
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<template #cell(details)="data">
|
||||||
|
<b-button
|
||||||
|
size="sm"
|
||||||
|
@click="data.toggleDetails"
|
||||||
|
variant="outline-secondary"
|
||||||
|
>
|
||||||
|
<b-icon class="icon-btn" icon="bar-chart-steps" />
|
||||||
|
</b-button>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<template #row-details="data">
|
||||||
|
<b-card>
|
||||||
|
<JugglProjectDistribution
|
||||||
|
:projects="data.item.projects"
|
||||||
|
names
|
||||||
|
class="mb-3"
|
||||||
|
/>
|
||||||
|
<JugglProjectStatisticsList :statistics="data.item.statistics" />
|
||||||
|
</b-card>
|
||||||
|
</template>
|
||||||
|
</b-table>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import { helperService } from "@/services/helper.service.js";
|
||||||
|
import JugglProjectStatisticsList from "./JugglProjectStatisticsList.vue";
|
||||||
|
import JugglProjectDistribution from "./JugglProjectDistribution.vue";
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: "JugglMonthlyStatisticsList",
|
||||||
|
components: { JugglProjectStatisticsList, JugglProjectDistribution },
|
||||||
|
props: {
|
||||||
|
statistics: {
|
||||||
|
required: true,
|
||||||
|
type: Array
|
||||||
|
}
|
||||||
|
},
|
||||||
|
data: () => {
|
||||||
|
return {
|
||||||
|
statistic_fields: [
|
||||||
|
{
|
||||||
|
key: "month",
|
||||||
|
label: "Month"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: "duration",
|
||||||
|
label: "Duration"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: "distribution",
|
||||||
|
lavel: "Distribution"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: "record_count",
|
||||||
|
label: "Records"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: "details",
|
||||||
|
label: "Details"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
};
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
isLoading: function() {
|
||||||
|
return this.statistics === undefined;
|
||||||
|
},
|
||||||
|
aggregatedStatistics: function() {
|
||||||
|
if (this.statistics === undefined) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
var aggregated = {};
|
||||||
|
this.statistics.forEach(stat => {
|
||||||
|
var monthName = stat.date.substring(0, 7); // date is in the format "YYYY-MM-DD", so simply cutting of the day
|
||||||
|
|
||||||
|
if (aggregated[monthName] === undefined) {
|
||||||
|
aggregated[monthName] = {
|
||||||
|
month: monthName,
|
||||||
|
duration: 0,
|
||||||
|
record_count: 0,
|
||||||
|
statistics: [],
|
||||||
|
projects: {}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (aggregated[monthName].projects[stat.project_id] === undefined) {
|
||||||
|
aggregated[monthName].projects[stat.project_id] = {
|
||||||
|
project_id: stat.project_id,
|
||||||
|
duration: 0,
|
||||||
|
record_count: 0,
|
||||||
|
color: stat.color,
|
||||||
|
name: stat.name,
|
||||||
|
visible: stat.visible,
|
||||||
|
statistics: []
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
aggregated[monthName].duration += Number(stat.duration);
|
||||||
|
aggregated[monthName].record_count += Number(stat.record_count);
|
||||||
|
aggregated[monthName].statistics.push(stat);
|
||||||
|
|
||||||
|
aggregated[monthName].projects[stat.project_id].duration += Number(
|
||||||
|
stat.duration
|
||||||
|
);
|
||||||
|
aggregated[monthName].projects[stat.project_id].record_count += Number(
|
||||||
|
stat.record_count
|
||||||
|
);
|
||||||
|
aggregated[monthName].projects[stat.project_id].statistics.push(stat);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Simplyfying object to lists
|
||||||
|
aggregated = Object.values(aggregated);
|
||||||
|
aggregated.forEach(stat => {
|
||||||
|
stat.projects = Object.values(stat.projects);
|
||||||
|
});
|
||||||
|
return aggregated;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
getDurationTimestamp: helperService.getDurationTimestamp
|
||||||
|
}
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="sass"></style>
|
45
src/components/juggl/JugglProjectDistribution.vue
Normal file
45
src/components/juggl/JugglProjectDistribution.vue
Normal file
|
@ -0,0 +1,45 @@
|
||||||
|
<template>
|
||||||
|
<b-progress :max="totalDuration">
|
||||||
|
<b-progress-bar
|
||||||
|
v-for="(project, index) in projects"
|
||||||
|
v-bind:key="project.project_id"
|
||||||
|
:value="project.duration"
|
||||||
|
:style="'background-color: ' + project.color + ' !important'"
|
||||||
|
:variant="index % 2 == 0 ? 'primary' : 'dark'"
|
||||||
|
v-b-tooltip.hover
|
||||||
|
:title="project.name + ' · ' + getDurationTimestamp(project.duration)"
|
||||||
|
><span v-if="names">{{ project.name }}</span></b-progress-bar
|
||||||
|
>
|
||||||
|
</b-progress>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import { helperService } from "@/services/helper.service.js";
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: "JugglProjectDistribution",
|
||||||
|
props: {
|
||||||
|
projects: {
|
||||||
|
required: true,
|
||||||
|
type: Array
|
||||||
|
},
|
||||||
|
names: {
|
||||||
|
required: false,
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
totalDuration: function() {
|
||||||
|
var duration = 0;
|
||||||
|
this.projects.forEach(stat => (duration += Number(stat.duration)));
|
||||||
|
return duration;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
getDurationTimestamp: helperService.getDurationTimestamp
|
||||||
|
}
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style></style>
|
112
src/components/juggl/JugglProjectStatisticsList.vue
Normal file
112
src/components/juggl/JugglProjectStatisticsList.vue
Normal file
|
@ -0,0 +1,112 @@
|
||||||
|
<template>
|
||||||
|
<b-table
|
||||||
|
:items="projectStatistics"
|
||||||
|
primary-key="project_id"
|
||||||
|
hover
|
||||||
|
:busy="isLoading"
|
||||||
|
:fields="statistic_fields"
|
||||||
|
sort-by="duration"
|
||||||
|
sort-desc
|
||||||
|
>
|
||||||
|
<template #table-busy>
|
||||||
|
<div class="text-center">
|
||||||
|
<b-spinner></b-spinner>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<!-- Custom data -->
|
||||||
|
<template #cell(project)="data">
|
||||||
|
<JugglProjectName :project="data.item" />
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<template #cell(duration)="data">
|
||||||
|
{{ getDurationTimestamp(data.item.duration) }}
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<template #cell(distribution)="data">
|
||||||
|
{{ ((data.item.duration / totalDuration) * 100).toFixed(0) }}%
|
||||||
|
</template>
|
||||||
|
</b-table>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import JugglProjectName from "@/components/juggl/JugglProjectName";
|
||||||
|
import { helperService } from "@/services/helper.service.js";
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: "JugglProjectStatisticsList",
|
||||||
|
components: {
|
||||||
|
JugglProjectName
|
||||||
|
},
|
||||||
|
props: {
|
||||||
|
statistics: {
|
||||||
|
required: true,
|
||||||
|
type: Array
|
||||||
|
}
|
||||||
|
},
|
||||||
|
data: () => {
|
||||||
|
return {
|
||||||
|
statistic_fields: [
|
||||||
|
{
|
||||||
|
key: "project",
|
||||||
|
label: "Project"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: "duration",
|
||||||
|
label: "Duration"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: "record_count",
|
||||||
|
label: "Records"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: "distribution",
|
||||||
|
label: "Distribution"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
};
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
isLoading: function() {
|
||||||
|
return this.statistics === undefined;
|
||||||
|
},
|
||||||
|
totalDuration: function() {
|
||||||
|
var duration = 0;
|
||||||
|
this.statistics.forEach(stat => (duration += Number(stat.duration)));
|
||||||
|
return duration;
|
||||||
|
},
|
||||||
|
projectStatistics: function() {
|
||||||
|
if (this.statistics === undefined) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
var projects = {};
|
||||||
|
this.statistics.forEach(stat => {
|
||||||
|
if (projects[stat.project_id] === undefined) {
|
||||||
|
projects[stat.project_id] = {
|
||||||
|
project_id: stat.project_id,
|
||||||
|
duration: 0,
|
||||||
|
record_count: 0,
|
||||||
|
color: stat.color,
|
||||||
|
name: stat.name,
|
||||||
|
visible: stat.visible,
|
||||||
|
statistics: []
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
projects[stat.project_id].duration += Number(stat.duration);
|
||||||
|
projects[stat.project_id].record_count += Number(stat.record_count);
|
||||||
|
projects[stat.project_id].statistics.push(stat);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Simplyfying object to lists
|
||||||
|
return Object.values(projects);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
getDurationTimestamp: helperService.getDurationTimestamp
|
||||||
|
}
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="sass"></style>
|
|
@ -1,6 +1,7 @@
|
||||||
<template>
|
<template>
|
||||||
<b-table
|
<b-table
|
||||||
:items="records"
|
:items="records"
|
||||||
|
primary-key="record_id"
|
||||||
hover
|
hover
|
||||||
:busy="isLoading"
|
:busy="isLoading"
|
||||||
:fields="fields"
|
:fields="fields"
|
||||||
|
@ -27,7 +28,10 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<template #cell(tags)="data">
|
<template #cell(tags)="data">
|
||||||
<JugglTagField :recordId="data.item.record_id" />
|
<JugglTagField
|
||||||
|
:recordId="data.item.record_id"
|
||||||
|
:onlyVisible="onlyVisibleTags"
|
||||||
|
/>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<template #cell(details)="data">
|
<template #cell(details)="data">
|
||||||
|
@ -90,11 +94,17 @@ export default {
|
||||||
required: false,
|
required: false,
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
default: false
|
default: false
|
||||||
|
},
|
||||||
|
onlyVisibleTags: {
|
||||||
|
required: false,
|
||||||
|
type: Boolean,
|
||||||
|
default: true
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
iconScale: 1.6,
|
iconScale: 1.6,
|
||||||
|
timerDelta: 0,
|
||||||
requiredFields: [
|
requiredFields: [
|
||||||
{
|
{
|
||||||
key: "project",
|
key: "project",
|
||||||
|
@ -139,8 +149,18 @@ export default {
|
||||||
return fields;
|
return fields;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
timers: {
|
||||||
|
"increaseTimer": { time: 1000, autostart: true, repeat: true }
|
||||||
|
},
|
||||||
methods: {
|
methods: {
|
||||||
getDurationTimestamp: helperService.getDurationTimestamp,
|
getDurationTimestamp: function(duration) {
|
||||||
|
var localDuration = duration;
|
||||||
|
if (this.running) {
|
||||||
|
localDuration += this.timerDelta;
|
||||||
|
}
|
||||||
|
|
||||||
|
return helperService.getDurationTimestamp(localDuration);
|
||||||
|
},
|
||||||
getProject: function(id) {
|
getProject: function(id) {
|
||||||
var project = store.getters.getProjectById(id);
|
var project = store.getters.getProjectById(id);
|
||||||
|
|
||||||
|
@ -155,6 +175,15 @@ export default {
|
||||||
},
|
},
|
||||||
detailsRecord: function(id) {
|
detailsRecord: function(id) {
|
||||||
this.$router.push("/record/" + id);
|
this.$router.push("/record/" + id);
|
||||||
|
},
|
||||||
|
increaseTimer: function() {
|
||||||
|
if (this.records.length <= 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var record = this.records[0];
|
||||||
|
var liveDuration = (new Date().getTime() - new Date(record.start_time).getTime()) / 1000;
|
||||||
|
this.timerDelta = liveDuration - record.duration;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
<template>
|
<template>
|
||||||
<div :id="containerId" class="tag-container">
|
<div :id="containerId" class="tag-container">
|
||||||
|
<!-- Tag item list -->
|
||||||
<div
|
<div
|
||||||
class="tag-item"
|
class="tag-item"
|
||||||
v-for="tag in addedTags"
|
v-for="tag in addedTags"
|
||||||
|
@ -9,10 +10,12 @@
|
||||||
{{ tag.name }}
|
{{ tag.name }}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- Add-button -->
|
||||||
<div :id="btnId">
|
<div :id="btnId">
|
||||||
<b-icon icon="plus" />
|
<b-icon icon="plus" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- Popover -->
|
||||||
<b-popover
|
<b-popover
|
||||||
:target="btnId"
|
:target="btnId"
|
||||||
triggers="click"
|
triggers="click"
|
||||||
|
@ -53,6 +56,11 @@ export default {
|
||||||
props: {
|
props: {
|
||||||
recordId: {
|
recordId: {
|
||||||
required: true
|
required: true
|
||||||
|
},
|
||||||
|
onlyVisible: {
|
||||||
|
required: false,
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
|
@ -95,10 +103,16 @@ export default {
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
allTags: function() {
|
allTags: function() {
|
||||||
return store.getters.tags;
|
if (this.onlyVisible) {
|
||||||
|
return store.getters.getFilteredTags({ visible: true });
|
||||||
|
} else {
|
||||||
|
return store.getters.tags;
|
||||||
|
}
|
||||||
},
|
},
|
||||||
addedTags: function() {
|
addedTags: function() {
|
||||||
return this.record.tags;
|
return Object.values(this.record.tags).filter(
|
||||||
|
t => !this.onlyVisible || t.visible
|
||||||
|
);
|
||||||
},
|
},
|
||||||
usedTagIds: function() {
|
usedTagIds: function() {
|
||||||
var ids = [];
|
var ids = [];
|
||||||
|
|
33
src/components/layout/LayoutFooter.vue
Normal file
33
src/components/layout/LayoutFooter.vue
Normal file
|
@ -0,0 +1,33 @@
|
||||||
|
<template>
|
||||||
|
<footer>
|
||||||
|
<BaseContainer
|
||||||
|
width="medium"
|
||||||
|
center
|
||||||
|
>
|
||||||
|
<b-link to="/tools" class="surround-space">Tools</b-link>
|
||||||
|
<b-link to="/changelog" class="surround-space">Changelog</b-link>
|
||||||
|
<b-link to="/credits" class="surround-space">Credits</b-link>
|
||||||
|
</BaseContainer>
|
||||||
|
</footer>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import BaseContainer from "@/components/base/BaseContainer.vue";
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: "LayoutFooter",
|
||||||
|
components: {
|
||||||
|
BaseContainer,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="sass" scoped>
|
||||||
|
footer
|
||||||
|
margin-bottom: 3rem
|
||||||
|
text-align: center
|
||||||
|
|
||||||
|
.surround-space
|
||||||
|
margin-left: 1rem
|
||||||
|
margin-right: 1rem
|
||||||
|
</style>
|
|
@ -1,35 +1,46 @@
|
||||||
<template>
|
<template>
|
||||||
<main>
|
<div>
|
||||||
<BaseLogo id="logo" size="medium" center class="space-top" />
|
<main>
|
||||||
<BaseContainer
|
<BaseLogo id="logo" size="medium" center class="space-top space-bottom-small" icon-only />
|
||||||
:width="width"
|
<BaseContainer
|
||||||
center
|
:width="width"
|
||||||
class="space-bottom"
|
center
|
||||||
:class="{ 'center-content': center }"
|
class="space-bottom"
|
||||||
>
|
:class="{ 'center-content': center }"
|
||||||
<BaseTitle v-if="title" center size="huge" class="centered">
|
>
|
||||||
{{ title }}
|
<BaseTitle
|
||||||
</BaseTitle>
|
v-if="title"
|
||||||
<slot />
|
center
|
||||||
</BaseContainer>
|
size="giant"
|
||||||
</main>
|
class="centered space-bottom"
|
||||||
|
outline
|
||||||
|
>
|
||||||
|
{{ title.toUpperCase() }}
|
||||||
|
</BaseTitle>
|
||||||
|
<slot />
|
||||||
|
</BaseContainer>
|
||||||
|
</main>
|
||||||
|
<LayoutFooter />
|
||||||
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import BaseContainer from "@/components/base/BaseContainer";
|
import BaseContainer from "@/components/base/BaseContainer";
|
||||||
import BaseLogo from "@/components/base/BaseLogo";
|
import BaseLogo from "@/components/base/BaseLogo";
|
||||||
import BaseTitle from "@/components/base/BaseTitle";
|
import BaseTitle from "@/components/base/BaseTitle";
|
||||||
|
import LayoutFooter from "@/components/layout/LayoutFooter";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: "LayoutMinimal",
|
name: "LayoutMinimal",
|
||||||
components: {
|
components: {
|
||||||
BaseContainer,
|
BaseContainer,
|
||||||
BaseLogo,
|
BaseLogo,
|
||||||
BaseTitle
|
BaseTitle,
|
||||||
|
LayoutFooter
|
||||||
},
|
},
|
||||||
props: {
|
props: {
|
||||||
width: {
|
width: {
|
||||||
default: "",
|
default: "slim",
|
||||||
type: String
|
type: String
|
||||||
},
|
},
|
||||||
title: {
|
title: {
|
||||||
|
@ -54,4 +65,7 @@ export default {
|
||||||
|
|
||||||
.center-content
|
.center-content
|
||||||
text-align: center
|
text-align: center
|
||||||
|
|
||||||
|
.space-bottom-small
|
||||||
|
margin-bottom: 1rem
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -18,12 +18,13 @@
|
||||||
center
|
center
|
||||||
:class="{ 'center-content': center }"
|
:class="{ 'center-content': center }"
|
||||||
>
|
>
|
||||||
<BaseTitle v-if="title" center size="huge" class="centered">
|
<BaseTitle v-if="title" center size="giant" class="centered" outline>
|
||||||
{{ title }}
|
{{ title.toUpperCase() }}
|
||||||
</BaseTitle>
|
</BaseTitle>
|
||||||
<slot />
|
<slot />
|
||||||
</BaseContainer>
|
</BaseContainer>
|
||||||
</main>
|
</main>
|
||||||
|
<LayoutFooter/>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
@ -32,6 +33,7 @@ import BaseContainer from "@/components/base/BaseContainer.vue";
|
||||||
import BaseLogo from "@/components/base/BaseLogo.vue";
|
import BaseLogo from "@/components/base/BaseLogo.vue";
|
||||||
import BaseUserDropdown from "@/components/base/BaseUserDropdown.vue";
|
import BaseUserDropdown from "@/components/base/BaseUserDropdown.vue";
|
||||||
import BaseTitle from "@/components/base/BaseTitle";
|
import BaseTitle from "@/components/base/BaseTitle";
|
||||||
|
import LayoutFooter from "@/components/layout/LayoutFooter";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: "LayoutNavbarPrivate",
|
name: "LayoutNavbarPrivate",
|
||||||
|
@ -39,7 +41,8 @@ export default {
|
||||||
BaseContainer,
|
BaseContainer,
|
||||||
BaseLogo,
|
BaseLogo,
|
||||||
BaseUserDropdown,
|
BaseUserDropdown,
|
||||||
BaseTitle
|
BaseTitle,
|
||||||
|
LayoutFooter
|
||||||
},
|
},
|
||||||
props: {
|
props: {
|
||||||
title: {
|
title: {
|
||||||
|
@ -86,6 +89,14 @@ main
|
||||||
margin-top: 6rem
|
margin-top: 6rem
|
||||||
margin-bottom: 6rem
|
margin-bottom: 6rem
|
||||||
|
|
||||||
|
footer
|
||||||
|
margin-bottom: 3rem
|
||||||
|
text-align: center
|
||||||
|
|
||||||
|
*
|
||||||
|
margin-left: 1rem
|
||||||
|
margin-right: 1rem
|
||||||
|
|
||||||
.center-content
|
.center-content
|
||||||
text-align: center
|
text-align: center
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -3,9 +3,12 @@ import App from "./App.vue";
|
||||||
import router from "./router";
|
import router from "./router";
|
||||||
import store from "./store";
|
import store from "./store";
|
||||||
import { BootstrapVue, BootstrapVueIcons } from "bootstrap-vue";
|
import { BootstrapVue, BootstrapVueIcons } from "bootstrap-vue";
|
||||||
|
import VueTimers from 'vue-timers'
|
||||||
|
|
||||||
Vue.config.productionTip = false;
|
Vue.config.productionTip = false;
|
||||||
|
|
||||||
|
Vue.use(VueTimers);
|
||||||
|
|
||||||
// Install BootstrapVue
|
// Install BootstrapVue
|
||||||
Vue.use(BootstrapVue);
|
Vue.use(BootstrapVue);
|
||||||
Vue.use(BootstrapVueIcons);
|
Vue.use(BootstrapVueIcons);
|
||||||
|
|
|
@ -6,6 +6,10 @@ import NotFound from "../views/NotFound.vue";
|
||||||
import Home from "../views/Home.vue";
|
import Home from "../views/Home.vue";
|
||||||
import History from "../views/History.vue";
|
import History from "../views/History.vue";
|
||||||
import Manage from "../views/Manage.vue";
|
import Manage from "../views/Manage.vue";
|
||||||
|
import Statistics from "../views/Statistics.vue";
|
||||||
|
import Changelog from "../views/Changelog.vue";
|
||||||
|
import Credits from "../views/Credits.vue";
|
||||||
|
import ToolsDocumentation from "../views/ToolsDocumentation.vue";
|
||||||
|
|
||||||
Vue.use(VueRouter);
|
Vue.use(VueRouter);
|
||||||
|
|
||||||
|
@ -37,6 +41,27 @@ const routes = [
|
||||||
component: Home,
|
component: Home,
|
||||||
beforeEnter: requireAuth
|
beforeEnter: requireAuth
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path: "/statistics",
|
||||||
|
name: "Statistics",
|
||||||
|
component: Statistics,
|
||||||
|
beforeEnter: requireAuth
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: "/changelog",
|
||||||
|
name: "Changelog",
|
||||||
|
component: Changelog
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: "/credits",
|
||||||
|
name: "Credits",
|
||||||
|
component: Credits
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: "/tools",
|
||||||
|
name: "ToolsDocumentation",
|
||||||
|
component: ToolsDocumentation
|
||||||
|
},
|
||||||
{
|
{
|
||||||
path: "/logout",
|
path: "/logout",
|
||||||
name: "Logout",
|
name: "Logout",
|
||||||
|
|
|
@ -64,13 +64,28 @@ export const helperService = {
|
||||||
* @returns Date as string in the used format
|
* @returns Date as string in the used format
|
||||||
*/
|
*/
|
||||||
toISODate(date) {
|
toISODate(date) {
|
||||||
var timezoneOffset = date.getMinutes() + date.getTimezoneOffset();
|
return (
|
||||||
var timestamp = date.getTime() + timezoneOffset * 1000;
|
date.getFullYear() +
|
||||||
var correctDate = new Date(timestamp);
|
"-" +
|
||||||
|
(date.getMonth() + 1).toString().padStart(2, "0") +
|
||||||
|
"-" +
|
||||||
|
date
|
||||||
|
.getDate()
|
||||||
|
.toString()
|
||||||
|
.padStart(2, "0") +
|
||||||
|
"T00:00.000Z"
|
||||||
|
);
|
||||||
|
},
|
||||||
|
|
||||||
correctDate.setUTCHours(0, 0, 0, 0);
|
/**
|
||||||
|
* Converts a date object into a date-time-timezone string and only keeps the date.
|
||||||
return correctDate.toISOString();
|
*
|
||||||
|
* @param {*} date A date object
|
||||||
|
*
|
||||||
|
* @returns Date as string in YYYY-MM-DD format
|
||||||
|
*/
|
||||||
|
toISODateOnly(date) {
|
||||||
|
return helperService.toISODate(date).substring(0, 10);
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -22,7 +22,7 @@ export const jugglService = {
|
||||||
getProjects() {
|
getProjects() {
|
||||||
return apiService.post("/getProjects.php").then(r => {
|
return apiService.post("/getProjects.php").then(r => {
|
||||||
return {
|
return {
|
||||||
data: r.data,
|
data: { projects: processProjects(r.data.projects) },
|
||||||
msg: ""
|
msg: ""
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
@ -31,12 +31,33 @@ export const jugglService = {
|
||||||
getTags() {
|
getTags() {
|
||||||
return apiService.post("/getRecordTags.php").then(r => {
|
return apiService.post("/getRecordTags.php").then(r => {
|
||||||
return {
|
return {
|
||||||
data: r.data,
|
data: { record_tags: processTags(r.data.record_tags) },
|
||||||
msg: ""
|
msg: ""
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
|
getStatistics(options = {}) {
|
||||||
|
if (options.from === undefined) {
|
||||||
|
options.from = new Date();
|
||||||
|
}
|
||||||
|
if (options.until === undefined) {
|
||||||
|
options.until = new Date();
|
||||||
|
}
|
||||||
|
|
||||||
|
return apiService
|
||||||
|
.post("/getStats.php", {
|
||||||
|
from_date: helperService.toISODateOnly(options.from),
|
||||||
|
until_date: helperService.toISODateOnly(options.until)
|
||||||
|
})
|
||||||
|
.then(r => {
|
||||||
|
return {
|
||||||
|
data: { statistics: processVisibility(r.data.stats) },
|
||||||
|
msg: ""
|
||||||
|
};
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
getRecord(recordId) {
|
getRecord(recordId) {
|
||||||
return apiService
|
return apiService
|
||||||
.post("/getRecord.php", {
|
.post("/getRecord.php", {
|
||||||
|
@ -98,6 +119,9 @@ export const jugglService = {
|
||||||
if (options.finished !== undefined) {
|
if (options.finished !== undefined) {
|
||||||
payload.finished = options.finished;
|
payload.finished = options.finished;
|
||||||
}
|
}
|
||||||
|
if (options.visible !== undefined) {
|
||||||
|
payload.visible = options.visible;
|
||||||
|
}
|
||||||
|
|
||||||
return apiService.post("/getRecords.php", payload).then(r => {
|
return apiService.post("/getRecords.php", payload).then(r => {
|
||||||
return {
|
return {
|
||||||
|
@ -248,7 +272,26 @@ function processRecords(data) {
|
||||||
rec.duration = helperService.calcDurationInSeconds(rec.start_time);
|
rec.duration = helperService.calcDurationInSeconds(rec.start_time);
|
||||||
}
|
}
|
||||||
|
|
||||||
rec.tags = Object.values(rec.tags);
|
rec.tags = processTags(Object.values(rec.tags));
|
||||||
});
|
});
|
||||||
return data;
|
return data;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function processTags(tags) {
|
||||||
|
return processVisibility(tags);
|
||||||
|
}
|
||||||
|
|
||||||
|
function processProjects(projects) {
|
||||||
|
return processVisibility(projects);
|
||||||
|
}
|
||||||
|
|
||||||
|
function processVisibility(items) {
|
||||||
|
Object.values(items).forEach(item => {
|
||||||
|
if (item.visible === "1") {
|
||||||
|
item.visible = true;
|
||||||
|
} else {
|
||||||
|
item.visible = false;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return items;
|
||||||
|
}
|
||||||
|
|
|
@ -6,6 +6,7 @@ export const juggl = {
|
||||||
projects: [],
|
projects: [],
|
||||||
records: [],
|
records: [],
|
||||||
tags: [],
|
tags: [],
|
||||||
|
statistics: [],
|
||||||
user: undefined,
|
user: undefined,
|
||||||
auth: undefined,
|
auth: undefined,
|
||||||
recordsLimit: 0
|
recordsLimit: 0
|
||||||
|
@ -20,6 +21,9 @@ export const juggl = {
|
||||||
setTags(state, tags) {
|
setTags(state, tags) {
|
||||||
state.tags = tags;
|
state.tags = tags;
|
||||||
},
|
},
|
||||||
|
setStatistics(state, statistics) {
|
||||||
|
state.statistics = statistics;
|
||||||
|
},
|
||||||
setRecordsLimit(state, limit) {
|
setRecordsLimit(state, limit) {
|
||||||
state.recordsLimit = limit;
|
state.recordsLimit = limit;
|
||||||
},
|
},
|
||||||
|
@ -44,10 +48,55 @@ export const juggl = {
|
||||||
finishedRecords: state => {
|
finishedRecords: state => {
|
||||||
return Object.values(state.records).filter(record => !record.running);
|
return Object.values(state.records).filter(record => !record.running);
|
||||||
},
|
},
|
||||||
|
getFilteredRecords: (state, getters) => ({
|
||||||
|
running = undefined,
|
||||||
|
projectVisible = undefined,
|
||||||
|
records = undefined
|
||||||
|
}) => {
|
||||||
|
if (records == undefined) {
|
||||||
|
records = getters.records;
|
||||||
|
}
|
||||||
|
|
||||||
|
var visibleProjects = getters.visibleProjects;
|
||||||
|
var visibleIds = [];
|
||||||
|
Object.values(visibleProjects)
|
||||||
|
.filter(p => p.visible)
|
||||||
|
.forEach(p => {
|
||||||
|
visibleIds.push(p.project_id);
|
||||||
|
});
|
||||||
|
|
||||||
|
return Object.values(records).filter(rec => {
|
||||||
|
if (running !== undefined && running !== rec.running) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
var recProjectVisible = visibleIds.includes(rec.project_id);
|
||||||
|
if (
|
||||||
|
projectVisible !== undefined &&
|
||||||
|
projectVisible !== recProjectVisible
|
||||||
|
) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
getFilteredStatistics: (state, getters) => ({
|
||||||
|
projectVisible = undefined
|
||||||
|
}) => {
|
||||||
|
return Object.values(getters.statistics).filter(statistic => {
|
||||||
|
if (
|
||||||
|
projectVisible !== undefined &&
|
||||||
|
statistic.visible !== projectVisible
|
||||||
|
) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
},
|
||||||
auth: state => state.auth,
|
auth: state => state.auth,
|
||||||
apiUrl: state => state.apiUrl,
|
apiUrl: state => state.apiUrl,
|
||||||
user: state => state.user,
|
user: state => state.user,
|
||||||
isLoggedIn: state => !!state.auth,
|
isLoggedIn: state => !!state.auth,
|
||||||
|
statistics: state => state.statistics,
|
||||||
records: state => state.records,
|
records: state => state.records,
|
||||||
projects: state => state.projects,
|
projects: state => state.projects,
|
||||||
tags: state => state.tags,
|
tags: state => state.tags,
|
||||||
|
@ -73,10 +122,10 @@ export const juggl = {
|
||||||
return getters.projectIds.filter(id => !runningProjectIds.includes(id));
|
return getters.projectIds.filter(id => !runningProjectIds.includes(id));
|
||||||
},
|
},
|
||||||
finishedProjects: (state, getters) => {
|
finishedProjects: (state, getters) => {
|
||||||
var ids = getters.finishedProjectIds;
|
return getters.getFilteredProjects({ finished: true });
|
||||||
return Object.values(state.projects).filter(project =>
|
},
|
||||||
ids.includes(project.project_id)
|
visibleProjects: (state, getters) => {
|
||||||
);
|
return getters.getFilteredProjects({ visible: true });
|
||||||
},
|
},
|
||||||
runningProjects: (state, getters) => {
|
runningProjects: (state, getters) => {
|
||||||
var ids = getters.runningProjectIds;
|
var ids = getters.runningProjectIds;
|
||||||
|
@ -89,6 +138,43 @@ export const juggl = {
|
||||||
project => project.project_id === id
|
project => project.project_id === id
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
getFilteredProjects: (state, getters) => ({
|
||||||
|
finished = undefined,
|
||||||
|
visible = undefined,
|
||||||
|
projects = undefined
|
||||||
|
}) => {
|
||||||
|
if (projects == undefined) {
|
||||||
|
projects = getters.projects;
|
||||||
|
}
|
||||||
|
|
||||||
|
var runningIds = getters.runningProjectIds;
|
||||||
|
return Object.values(projects).filter(project => {
|
||||||
|
var projectFinished = !runningIds.includes(project.project_id);
|
||||||
|
|
||||||
|
if (finished !== undefined && finished !== projectFinished) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (visible !== undefined && visible !== project.visible) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
getFilteredTags: (state, getters) => ({
|
||||||
|
visible = undefined,
|
||||||
|
tags = undefined
|
||||||
|
}) => {
|
||||||
|
if (tags == undefined) {
|
||||||
|
tags = getters.tags;
|
||||||
|
}
|
||||||
|
|
||||||
|
return Object.values(tags).filter(tag => {
|
||||||
|
if (visible != undefined && visible !== tag.visible) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
},
|
||||||
getTagById: (state, getters) => id => {
|
getTagById: (state, getters) => id => {
|
||||||
return Object.values(getters.tags).find(tag => tag.record_tag_id === id);
|
return Object.values(getters.tags).find(tag => tag.record_tag_id === id);
|
||||||
},
|
},
|
||||||
|
@ -135,12 +221,16 @@ export const juggl = {
|
||||||
return true;
|
return true;
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
loadRecords({ commit, state, getters }, { limit, finished }) {
|
loadRecords({ commit, state, getters }, { limit, finished, visible }) {
|
||||||
if (limit !== undefined) {
|
if (limit !== undefined) {
|
||||||
commit("setRecordsLimit", limit);
|
commit("setRecordsLimit", limit);
|
||||||
}
|
}
|
||||||
|
|
||||||
var payload = { limit: state.recordsLimit, finished: finished };
|
var payload = {
|
||||||
|
limit: state.recordsLimit,
|
||||||
|
finished: finished,
|
||||||
|
visible: visible
|
||||||
|
};
|
||||||
|
|
||||||
return jugglService.getRecords(payload).then(r => {
|
return jugglService.getRecords(payload).then(r => {
|
||||||
var allRecords = Object.values(r.data.records);
|
var allRecords = Object.values(r.data.records);
|
||||||
|
@ -154,6 +244,29 @@ export const juggl = {
|
||||||
commit("setRecords", allRecords);
|
commit("setRecords", allRecords);
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
loadDailyStatistics({ dispatch }, { date }) {
|
||||||
|
dispatch("loadStatistics", { from: date, until: date });
|
||||||
|
},
|
||||||
|
loadMonthlyStatistics(
|
||||||
|
{ dispatch },
|
||||||
|
{ startYear, startMonth, endYear, endMonth }
|
||||||
|
) {
|
||||||
|
|
||||||
|
// Month in date object goes from 0 - 11
|
||||||
|
var options = {
|
||||||
|
from: new Date(startYear, startMonth - 1, 1),
|
||||||
|
until: new Date(endYear, endMonth, 0) // 0 leads to the last day of the previous month
|
||||||
|
};
|
||||||
|
|
||||||
|
dispatch("loadStatistics", options);
|
||||||
|
},
|
||||||
|
async loadStatistics({ commit }, options) {
|
||||||
|
var results = Object.values(
|
||||||
|
(await jugglService.getStatistics(options)).data.statistics
|
||||||
|
);
|
||||||
|
|
||||||
|
commit("setStatistics", results);
|
||||||
|
},
|
||||||
loadRunningRecords({ commit, getters }) {
|
loadRunningRecords({ commit, getters }) {
|
||||||
return jugglService.getRunningRecords().then(r => {
|
return jugglService.getRunningRecords().then(r => {
|
||||||
var allRecords = {
|
var allRecords = {
|
||||||
|
|
82
src/views/Changelog.vue
Normal file
82
src/views/Changelog.vue
Normal file
|
@ -0,0 +1,82 @@
|
||||||
|
<template>
|
||||||
|
<LayoutMinimal title="Changelog">
|
||||||
|
<BaseSection>
|
||||||
|
<BaseTitle size="small">
|
||||||
|
11.02.2022
|
||||||
|
</BaseTitle>
|
||||||
|
<ul>
|
||||||
|
<li>Implemented daily and monthly statistics</li>
|
||||||
|
<li>Added tool</li>
|
||||||
|
</ul>
|
||||||
|
</BaseSection>
|
||||||
|
|
||||||
|
<BaseSection>
|
||||||
|
<BaseTitle size="small">
|
||||||
|
23.11.2021
|
||||||
|
</BaseTitle>
|
||||||
|
<ul>
|
||||||
|
<li>Added tools page</li>
|
||||||
|
<li>Visual tweaks</li>
|
||||||
|
</ul>
|
||||||
|
</BaseSection>
|
||||||
|
|
||||||
|
<BaseSection>
|
||||||
|
<BaseTitle size="small">
|
||||||
|
07.11.2021
|
||||||
|
</BaseTitle>
|
||||||
|
<ul>
|
||||||
|
<li>Added credits page</li>
|
||||||
|
<li>Created basic footer</li>
|
||||||
|
<li>Added live record timer</li>
|
||||||
|
</ul>
|
||||||
|
</BaseSection>
|
||||||
|
|
||||||
|
<BaseSection>
|
||||||
|
<BaseTitle size="small">
|
||||||
|
27.07.2021
|
||||||
|
</BaseTitle>
|
||||||
|
<ul>
|
||||||
|
<li>Added simple statistics</li>
|
||||||
|
</ul>
|
||||||
|
</BaseSection>
|
||||||
|
|
||||||
|
<BaseSection>
|
||||||
|
<BaseTitle size="small">
|
||||||
|
21.05.2021
|
||||||
|
</BaseTitle>
|
||||||
|
<ul>
|
||||||
|
<li>Visual tweaks</li>
|
||||||
|
</ul>
|
||||||
|
</BaseSection>
|
||||||
|
|
||||||
|
<BaseSection>
|
||||||
|
<BaseTitle size="small">
|
||||||
|
12.04.2021
|
||||||
|
</BaseTitle>
|
||||||
|
<ul>
|
||||||
|
<li>Added toggle to change visibility of single projects and tags</li>
|
||||||
|
</ul>
|
||||||
|
</BaseSection>
|
||||||
|
</LayoutMinimal>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
// @ is an alias to /src
|
||||||
|
import LayoutMinimal from "@/components/layout/LayoutMinimal";
|
||||||
|
import BaseTitle from "@/components/base/BaseTitle";
|
||||||
|
import BaseSection from "../components/base/BaseSection.vue";
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: "Changelog",
|
||||||
|
components: {
|
||||||
|
LayoutMinimal,
|
||||||
|
BaseTitle,
|
||||||
|
BaseSection
|
||||||
|
}
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="sass" scoped>
|
||||||
|
ul
|
||||||
|
list-style-type: '+ '
|
||||||
|
</style>
|
51
src/views/Credits.vue
Normal file
51
src/views/Credits.vue
Normal file
|
@ -0,0 +1,51 @@
|
||||||
|
<template>
|
||||||
|
<LayoutMinimal title="Credits" >
|
||||||
|
<BaseSection>
|
||||||
|
<BaseTitle>
|
||||||
|
vue-timers
|
||||||
|
</BaseTitle>
|
||||||
|
<BaseTitle size="tiny">
|
||||||
|
<b-link href="https://github.com/Kelin2025/vue-timers">
|
||||||
|
<b-icon-github></b-icon-github>
|
||||||
|
GitHub
|
||||||
|
</b-link>
|
||||||
|
</BaseTitle>
|
||||||
|
<p class="monospace">
|
||||||
|
MIT License
|
||||||
|
<br/>
|
||||||
|
<br/>
|
||||||
|
Copyright (c) 2017 Anton Kosykh
|
||||||
|
<br/>
|
||||||
|
<br/>
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||||
|
<br/>
|
||||||
|
<br/>
|
||||||
|
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
||||||
|
<br/>
|
||||||
|
<br/>
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
</p>
|
||||||
|
</BaseSection>
|
||||||
|
</LayoutMinimal>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
// @ is an alias to /src
|
||||||
|
import LayoutMinimal from "@/components/layout/LayoutMinimal";
|
||||||
|
import BaseTitle from "@/components/base/BaseTitle";
|
||||||
|
import BaseSection from "@/components/base/BaseSection";
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: "Credits",
|
||||||
|
components: {
|
||||||
|
LayoutMinimal,
|
||||||
|
BaseTitle,
|
||||||
|
BaseSection
|
||||||
|
}
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="sass" scoped>
|
||||||
|
.monospace
|
||||||
|
font-family: Courier New, Courier, monospace
|
||||||
|
</style>
|
|
@ -26,7 +26,7 @@ import { helperService } from "@/services/helper.service.js";
|
||||||
import store from "@/store";
|
import store from "@/store";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: "Home",
|
name: "History",
|
||||||
data: () => {
|
data: () => {
|
||||||
return {
|
return {
|
||||||
working: true
|
working: true
|
||||||
|
@ -60,7 +60,7 @@ export default {
|
||||||
store.dispatch("loadTags");
|
store.dispatch("loadTags");
|
||||||
store.dispatch("loadProjects");
|
store.dispatch("loadProjects");
|
||||||
store
|
store
|
||||||
.dispatch("loadRecords", { limit: 0, finished: true })
|
.dispatch("loadRecords", { limit: 0, finished: true, visible: true })
|
||||||
.then(() => {
|
.then(() => {
|
||||||
this.working = false;
|
this.working = false;
|
||||||
})
|
})
|
||||||
|
|
|
@ -4,8 +4,8 @@
|
||||||
<JugglRecordsList :records="runningRecords" running />
|
<JugglRecordsList :records="runningRecords" running />
|
||||||
</BaseSection>
|
</BaseSection>
|
||||||
<BaseSection title="Projects">
|
<BaseSection title="Projects">
|
||||||
<div v-if="finishedProjects.length > 0">
|
<div v-if="availableProjects.length > 0">
|
||||||
<JugglProjectsPanel :projects="finishedProjects" />
|
<JugglProjectsPanel :projects="availableProjects" />
|
||||||
</div>
|
</div>
|
||||||
<div id="add-project-form">
|
<div id="add-project-form">
|
||||||
<FormProjectAdd />
|
<FormProjectAdd />
|
||||||
|
@ -43,21 +43,30 @@ export default {
|
||||||
BaseSection
|
BaseSection
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
finishedProjects: () => {
|
availableProjects: () => {
|
||||||
return store.getters.finishedProjects;
|
return store.getters.getFilteredProjects({
|
||||||
|
finished: true,
|
||||||
|
visible: true
|
||||||
|
});
|
||||||
},
|
},
|
||||||
finishedRecords: () => {
|
finishedRecords: () => {
|
||||||
return store.getters.finishedRecords;
|
return store.getters.getFilteredRecords({
|
||||||
|
running: false,
|
||||||
|
projectVisible: true
|
||||||
|
});
|
||||||
},
|
},
|
||||||
runningRecords: () => {
|
runningRecords: () => {
|
||||||
return store.getters.runningRecords;
|
return store.getters.getFilteredRecords({
|
||||||
|
running: true,
|
||||||
|
projectVisible: true
|
||||||
|
});
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
created: () => {
|
created: () => {
|
||||||
store.dispatch("loadProjects");
|
store.dispatch("loadProjects");
|
||||||
store.dispatch("loadTags");
|
store.dispatch("loadTags");
|
||||||
store.dispatch("loadRunningRecords");
|
store.dispatch("loadRunningRecords");
|
||||||
store.dispatch("loadRecords", { limit: 10, finished: true });
|
store.dispatch("loadRecords", { limit: 10, finished: true, visible: true });
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -97,7 +97,7 @@ import FormTagAdd from "@/components/forms/FormTagAdd";
|
||||||
import store from "@/store";
|
import store from "@/store";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: "Home",
|
name: "Manage",
|
||||||
components: {
|
components: {
|
||||||
LayoutNavbarPrivate,
|
LayoutNavbarPrivate,
|
||||||
FormProjectDetails,
|
FormProjectDetails,
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
<template>
|
<template>
|
||||||
<LayoutMinimal center title="Couldn't find what you were looking for :(">
|
<LayoutMinimal center title="404">
|
||||||
404
|
Couldn't find what you were looking for :(
|
||||||
</LayoutMinimal>
|
</LayoutMinimal>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
|
180
src/views/Statistics.vue
Normal file
180
src/views/Statistics.vue
Normal file
|
@ -0,0 +1,180 @@
|
||||||
|
<template>
|
||||||
|
<LayoutNavbarPrivate title="Statistics">
|
||||||
|
<BaseContainer class="centered mb-5">
|
||||||
|
<b-form-radio-group
|
||||||
|
id="mode-radio"
|
||||||
|
v-model="mode"
|
||||||
|
:options="options"
|
||||||
|
button-variant="outline-primary"
|
||||||
|
name="radio-btn-outline"
|
||||||
|
buttons
|
||||||
|
size="sm"
|
||||||
|
class="mb-3"
|
||||||
|
@input="updateStatistics"
|
||||||
|
></b-form-radio-group>
|
||||||
|
<b-form id="form-daily" v-if="mode == 'daily'" inline>
|
||||||
|
<b-form-datepicker
|
||||||
|
id="startdate"
|
||||||
|
v-model="daily.startDate"
|
||||||
|
required
|
||||||
|
placeholder="Choose a start date"
|
||||||
|
:max="daily.endDate"
|
||||||
|
dark
|
||||||
|
@input="updateStatistics"
|
||||||
|
>
|
||||||
|
</b-form-datepicker>
|
||||||
|
→
|
||||||
|
<b-form-datepicker
|
||||||
|
id="enddate"
|
||||||
|
v-model="daily.endDate"
|
||||||
|
required
|
||||||
|
placeholder="Choose an end date"
|
||||||
|
:min="daily.startDate"
|
||||||
|
dark
|
||||||
|
@input="updateStatistics"
|
||||||
|
>
|
||||||
|
</b-form-datepicker>
|
||||||
|
</b-form>
|
||||||
|
<b-form id="form-monthly" v-if="mode == 'monthly'" inline>
|
||||||
|
<b-input-group>
|
||||||
|
<b-input
|
||||||
|
id="start-year"
|
||||||
|
type="number"
|
||||||
|
placeholder="Start year"
|
||||||
|
v-model="monthly.startYear"
|
||||||
|
required
|
||||||
|
trim
|
||||||
|
@input="updateStatistics"
|
||||||
|
class="slim"
|
||||||
|
>
|
||||||
|
</b-input>
|
||||||
|
<b-input
|
||||||
|
id="start-month"
|
||||||
|
type="number"
|
||||||
|
placeholder="Start month"
|
||||||
|
v-model="monthly.startMonth"
|
||||||
|
required
|
||||||
|
trim
|
||||||
|
@input="updateStatistics"
|
||||||
|
class="slim"
|
||||||
|
>
|
||||||
|
</b-input>
|
||||||
|
</b-input-group>
|
||||||
|
→
|
||||||
|
<b-input-group>
|
||||||
|
<b-input
|
||||||
|
id="end-year"
|
||||||
|
type="number"
|
||||||
|
placeholder="End year"
|
||||||
|
v-model="monthly.endYear"
|
||||||
|
required
|
||||||
|
trim
|
||||||
|
@input="updateStatistics"
|
||||||
|
class="slim"
|
||||||
|
>
|
||||||
|
</b-input>
|
||||||
|
<b-input
|
||||||
|
id="end-month"
|
||||||
|
type="number"
|
||||||
|
placeholder="End month"
|
||||||
|
v-model="monthly.endMonth"
|
||||||
|
required
|
||||||
|
trim
|
||||||
|
@input="updateStatistics"
|
||||||
|
class="slim"
|
||||||
|
>
|
||||||
|
</b-input>
|
||||||
|
</b-input-group>
|
||||||
|
</b-form>
|
||||||
|
</BaseContainer>
|
||||||
|
|
||||||
|
<BaseSection id="projects" :title="modeTitle">
|
||||||
|
<JugglDailyStatisticsList
|
||||||
|
:statistics="visibleStatistics"
|
||||||
|
v-if="mode == 'daily'"
|
||||||
|
/>
|
||||||
|
<JugglMonthlyStatisticsList
|
||||||
|
:statistics="visibleStatistics"
|
||||||
|
v-if="mode == 'monthly'"
|
||||||
|
/>
|
||||||
|
</BaseSection>
|
||||||
|
</LayoutNavbarPrivate>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import LayoutNavbarPrivate from "@/components/layout/LayoutNavbarPrivate";
|
||||||
|
import JugglDailyStatisticsList from "@/components/juggl/JugglDailyStatisticsList";
|
||||||
|
import JugglMonthlyStatisticsList from "@/components/juggl/JugglMonthlyStatisticsList";
|
||||||
|
import { helperService } from "@/services/helper.service.js";
|
||||||
|
import BaseSection from "@/components/base/BaseSection";
|
||||||
|
import BaseContainer from "@/components/base/BaseContainer";
|
||||||
|
import store from "@/store";
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: "Statistics",
|
||||||
|
components: {
|
||||||
|
LayoutNavbarPrivate,
|
||||||
|
JugglMonthlyStatisticsList,
|
||||||
|
JugglDailyStatisticsList,
|
||||||
|
BaseSection,
|
||||||
|
BaseContainer
|
||||||
|
},
|
||||||
|
data: () => {
|
||||||
|
return {
|
||||||
|
working: true,
|
||||||
|
mode: "daily",
|
||||||
|
options: [
|
||||||
|
{ text: "Daily", value: "daily" },
|
||||||
|
{ text: "Monthly", value: "monthly" }
|
||||||
|
],
|
||||||
|
daily: {
|
||||||
|
startDate: new Date(),
|
||||||
|
endDate: new Date()
|
||||||
|
},
|
||||||
|
monthly: {
|
||||||
|
startYear: new Date().getFullYear(),
|
||||||
|
startMonth: new Date().getMonth() + 1,
|
||||||
|
endYear: new Date().getFullYear(),
|
||||||
|
endMonth: new Date().getMonth() + 1
|
||||||
|
}
|
||||||
|
};
|
||||||
|
},
|
||||||
|
mounted: function() {
|
||||||
|
this.updateStatistics();
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
visibleStatistics: () => {
|
||||||
|
return store.getters.getFilteredStatistics({
|
||||||
|
projectVisible: true
|
||||||
|
});
|
||||||
|
},
|
||||||
|
modeTitle: function() {
|
||||||
|
return this.mode[0].toUpperCase() + this.mode.substring(1);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
getDurationTimestamp: helperService.getDurationTimestamp,
|
||||||
|
updateStatistics: function() {
|
||||||
|
if (this.mode == "daily") {
|
||||||
|
store.dispatch("loadStatistics", {
|
||||||
|
from: new Date(this.daily.startDate),
|
||||||
|
until: new Date(this.daily.endDate)
|
||||||
|
});
|
||||||
|
} else if (this.mode == "monthly") {
|
||||||
|
store.dispatch("loadMonthlyStatistics", this.monthly);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="sass">
|
||||||
|
.centered
|
||||||
|
text-align: center
|
||||||
|
|
||||||
|
#form-daily *, #form-monthly *
|
||||||
|
margin: auto
|
||||||
|
|
||||||
|
.slim
|
||||||
|
max-width: 6rem
|
||||||
|
</style>
|
61
src/views/ToolsDocumentation.vue
Normal file
61
src/views/ToolsDocumentation.vue
Normal file
|
@ -0,0 +1,61 @@
|
||||||
|
<template>
|
||||||
|
<LayoutMinimal title="Tools">
|
||||||
|
<BaseTitle>
|
||||||
|
Graph Day Distribution
|
||||||
|
<br />
|
||||||
|
<small>by Linus Kämmerer</small>
|
||||||
|
</BaseTitle>
|
||||||
|
<BaseSection>
|
||||||
|
<BaseTitle size="tiny">
|
||||||
|
<b-link
|
||||||
|
href="https://gist.github.com/linuskmr/f89ac638da036e25d67e147ece67577c"
|
||||||
|
>
|
||||||
|
<b-icon-github></b-icon-github>
|
||||||
|
GitHub
|
||||||
|
</b-link>
|
||||||
|
</BaseTitle>
|
||||||
|
<p class="monospace">
|
||||||
|
Python script to generate a time distribution graph from the exported
|
||||||
|
Juggl data. It accumulates the tracked record and shows the total
|
||||||
|
working hours for different times for each weekday.
|
||||||
|
</p>
|
||||||
|
</BaseSection>
|
||||||
|
|
||||||
|
<BaseSection>
|
||||||
|
<BaseTitle>
|
||||||
|
JSON to CSV
|
||||||
|
<br />
|
||||||
|
<small>by Linus Kämmerer</small>
|
||||||
|
</BaseTitle>
|
||||||
|
<BaseTitle size="tiny">
|
||||||
|
<b-link
|
||||||
|
href="https://gist.github.com/linuskmr/856459cdf6c7ea460503b92e00b2aa26"
|
||||||
|
>
|
||||||
|
<b-icon-github></b-icon-github>
|
||||||
|
GitHub
|
||||||
|
</b-link>
|
||||||
|
</BaseTitle>
|
||||||
|
<p class="monospace">
|
||||||
|
Python script to convert exported Juggl data from a JSON to a CSV file.
|
||||||
|
</p>
|
||||||
|
</BaseSection>
|
||||||
|
</LayoutMinimal>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
// @ is an alias to /src
|
||||||
|
import LayoutMinimal from "@/components/layout/LayoutMinimal";
|
||||||
|
import BaseTitle from "@/components/base/BaseTitle";
|
||||||
|
import BaseSection from "../components/base/BaseSection.vue";
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: "ToolsDocumentation",
|
||||||
|
components: {
|
||||||
|
LayoutMinimal,
|
||||||
|
BaseTitle,
|
||||||
|
BaseSection
|
||||||
|
}
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="sass" scoped></style>
|
Loading…
Reference in a new issue