Compare commits

..

30 Commits

Author SHA1 Message Date
5111897b78 Experimental custom filter - select
Some checks reported errors
continuous-integration/drone Build was killed
2022-10-11 23:16:26 +03:00
be79d178ff Get all possible filters from backend 2022-10-11 23:15:52 +03:00
97ad29ade0 Moved ExamineLog content into separate component, made ExamineLog use ag-grid 2022-10-11 21:33:22 +03:00
f31951c478 Rearranged columns 2022-10-11 19:54:24 +03:00
d0c4272f51 Flash added rows 2022-10-11 19:43:46 +03:00
eeb104ee60 Improve row examination 2022-10-11 19:43:12 +03:00
a51fc71990 Use Vuetify dialog for seeing log content 2022-10-11 19:16:27 +03:00
c2f53a4703 Remove row editing, add more tooltips 2022-10-11 19:16:03 +03:00
3141586477 Material theme for ag-grid 2022-10-11 19:15:29 +03:00
c5a76c28ad Added Vuetify 2022-10-11 19:14:59 +03:00
b02b20528a Remove .drone.yml from backend 2022-10-11 17:39:01 +03:00
1a89440624 Updated backend Dockerfile 2022-10-11 17:07:51 +03:00
fb7d8fdd37 Optimized backend Dockerfile
Some checks reported errors
continuous-integration/drone Build was killed
2022-10-11 17:01:01 +03:00
a014b0b531 Attempt to use Drone
Some checks reported errors
continuous-integration/drone Build was killed
2022-10-11 16:25:05 +03:00
693ab863c2 Staged Docker builds for dev and prod 2022-10-11 16:24:15 +03:00
dde5a13550 Different k8s manifests for dev and staging (skaffold run) 2022-10-11 16:23:29 +03:00
3326e0e9e3 Initial support for deploy via skaffold run 2022-10-11 04:05:05 +03:00
845773f3f1 Regular filters instead of experimental combobox, sort by timestamp 2022-10-11 03:21:32 +03:00
d4052f03a1 Regular filters instead of experimental combobox, sort by timestamp 2022-10-11 03:20:12 +03:00
c9cf7ae603 Removed base.css so ag-grid filtering works 2022-10-11 03:19:22 +03:00
b2439a7a49 Experimental combobox, pagination 2022-10-11 02:50:03 +03:00
274620c994 Experimental update mechanism for a single page view 2022-10-11 02:27:31 +03:00
26e79bbb20 Handle non-insert changes 2022-10-11 02:24:41 +03:00
4b8591f51e Make MongoClient prefer secondaries 2022-10-11 02:24:16 +03:00
cfcd17d45e Make MongoClient prefer secondaries 2022-10-11 02:23:54 +03:00
Erki Aas
7797eb9210 Workaround for message id 2022-10-05 18:18:22 +03:00
6cb78c657f Vue.js 3 based frontend 2022-10-05 17:44:28 +03:00
b6f21b8192 Vue.js 3 based frontend 2022-10-05 17:43:41 +03:00
5fe7f45728 Skaffold + k8s configuration for development 2022-10-05 17:41:06 +03:00
23471d5bb3 Updated backend to use nodemon for development, removed minio integration and made other changes 2022-10-05 17:38:34 +03:00
46 changed files with 8705 additions and 21081 deletions

17
.dockerignore Normal file
View File

@ -0,0 +1,17 @@
.kpt-pipeline/
k8s/
skaffold.yaml
README.md
.git/
node_modules/
# Editor directories and files
.vscode/*
!.vscode/extensions.json
.idea
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?
*.kpt-pipeline

View File

@ -5,10 +5,12 @@ name: default
steps:
- name: backend
image: harbor.k-space.ee/k-space/drone-kaniko
image: plugins/docker
settings:
repo: ${DRONE_REPO}
repo: harbor.k-space.ee/${DRONE_REPO}
registry: harbor.k-space.ee
mtu: 1300
storage_driver: vfs
context: backend/
dockerfile: backend/Dockerfile
username:
@ -16,10 +18,12 @@ steps:
password:
from_secret: docker_password
- name: frontend
image: harbor.k-space.ee/k-space/drone-kaniko
image: plugins/docker
settings:
repo: ${DRONE_REPO}-frontend
repo: harbor.k-space.ee/${DRONE_REPO}-frontend
registry: harbor.k-space.ee
mtu: 1300
storage_driver: vfs
context: frontend/
dockerfile: frontend/Dockerfile
username:

View File

@ -1,5 +1,5 @@
# pull official node image
FROM node
FROM node AS dev
# define /app as working directory
WORKDIR /app
@ -13,4 +13,8 @@ RUN npm install
COPY . /app
# launch node server
ENTRYPOINT node server.js
ENTRYPOINT npm run dev
# production
FROM dev AS prod
ENTRYPOINT npm run start

View File

@ -13,6 +13,9 @@
"express": "^4.17.2",
"minio": "^7.0.26",
"mongodb": "^4.3.1"
},
"devDependencies": {
"nodemon": "^2.0.20"
}
},
"node_modules/@types/node": {
@ -40,6 +43,12 @@
"integrity": "sha512-U/4aVJ2mxI0aDNI8Uq0wEhMgY+u4CNtEb0om3+y3+niDAsoTCOB33UF0sxpzqzdqXLqmvc+vZyAt4O8pPdfkwA==",
"optional": true
},
"node_modules/abbrev": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz",
"integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==",
"dev": true
},
"node_modules/accepts": {
"version": "1.3.7",
"resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.7.tgz",
@ -52,6 +61,19 @@
"node": ">= 0.6"
}
},
"node_modules/anymatch": {
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.2.tgz",
"integrity": "sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg==",
"dev": true,
"dependencies": {
"normalize-path": "^3.0.0",
"picomatch": "^2.0.4"
},
"engines": {
"node": ">= 8"
}
},
"node_modules/array-flatten": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz",
@ -89,6 +111,12 @@
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/balanced-match": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
"integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
"dev": true
},
"node_modules/base64-js": {
"version": "1.5.1",
"resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz",
@ -108,6 +136,15 @@
}
]
},
"node_modules/binary-extensions": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz",
"integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==",
"dev": true,
"engines": {
"node": ">=8"
}
},
"node_modules/block-stream2": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/block-stream2/-/block-stream2-2.1.0.tgz",
@ -141,6 +178,28 @@
"node": ">= 0.8"
}
},
"node_modules/brace-expansion": {
"version": "1.1.11",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
"integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
"dev": true,
"dependencies": {
"balanced-match": "^1.0.0",
"concat-map": "0.0.1"
}
},
"node_modules/braces": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz",
"integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==",
"dev": true,
"dependencies": {
"fill-range": "^7.0.1"
},
"engines": {
"node": ">=8"
}
},
"node_modules/brorand": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/brorand/-/brorand-1.1.0.tgz",
@ -269,6 +328,33 @@
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/chokidar": {
"version": "3.5.3",
"resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz",
"integrity": "sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==",
"dev": true,
"funding": [
{
"type": "individual",
"url": "https://paulmillr.com/funding/"
}
],
"dependencies": {
"anymatch": "~3.1.2",
"braces": "~3.0.2",
"glob-parent": "~5.1.2",
"is-binary-path": "~2.1.0",
"is-glob": "~4.0.1",
"normalize-path": "~3.0.0",
"readdirp": "~3.6.0"
},
"engines": {
"node": ">= 8.10.0"
},
"optionalDependencies": {
"fsevents": "~2.3.2"
}
},
"node_modules/cipher-base": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/cipher-base/-/cipher-base-1.0.4.tgz",
@ -278,6 +364,12 @@
"safe-buffer": "^5.0.1"
}
},
"node_modules/concat-map": {
"version": "0.0.1",
"resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
"integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==",
"dev": true
},
"node_modules/content-disposition": {
"version": "0.5.4",
"resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz",
@ -609,6 +701,18 @@
"url": "https://paypal.me/naturalintelligence"
}
},
"node_modules/fill-range": {
"version": "7.0.1",
"resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz",
"integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==",
"dev": true,
"dependencies": {
"to-regex-range": "^5.0.1"
},
"engines": {
"node": ">=8"
}
},
"node_modules/finalhandler": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.2.tgz",
@ -647,6 +751,20 @@
"node": ">= 0.6"
}
},
"node_modules/fsevents": {
"version": "2.3.2",
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz",
"integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==",
"dev": true,
"hasInstallScript": true,
"optional": true,
"os": [
"darwin"
],
"engines": {
"node": "^8.16.0 || ^10.6.0 || >=11.0.0"
}
},
"node_modules/function-bind": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz",
@ -680,6 +798,18 @@
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/glob-parent": {
"version": "5.1.2",
"resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz",
"integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==",
"dev": true,
"dependencies": {
"is-glob": "^4.0.1"
},
"engines": {
"node": ">= 6"
}
},
"node_modules/has": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz",
@ -699,6 +829,15 @@
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/has-flag": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz",
"integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==",
"dev": true,
"engines": {
"node": ">=4"
}
},
"node_modules/has-symbols": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.2.tgz",
@ -801,6 +940,12 @@
}
]
},
"node_modules/ignore-by-default": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/ignore-by-default/-/ignore-by-default-1.0.1.tgz",
"integrity": "sha512-Ius2VYcGNk7T90CppJqcIkS5ooHUZyIQK+ClZfMfMNFEF9VSE73Fq+906u/CWu92x4gzZMWOwfFYckPObzdEbA==",
"dev": true
},
"node_modules/inherits": {
"version": "2.0.4",
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
@ -858,6 +1003,18 @@
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/is-binary-path": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz",
"integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==",
"dev": true,
"dependencies": {
"binary-extensions": "^2.0.0"
},
"engines": {
"node": ">=8"
}
},
"node_modules/is-boolean-object": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.1.2.tgz",
@ -898,6 +1055,15 @@
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/is-extglob": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
"integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==",
"dev": true,
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/is-generator-function": {
"version": "1.0.10",
"resolved": "https://registry.npmjs.org/is-generator-function/-/is-generator-function-1.0.10.tgz",
@ -912,6 +1078,18 @@
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/is-glob": {
"version": "4.0.3",
"resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz",
"integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==",
"dev": true,
"dependencies": {
"is-extglob": "^2.1.1"
},
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/is-negative-zero": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.2.tgz",
@ -923,6 +1101,15 @@
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/is-number": {
"version": "7.0.0",
"resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz",
"integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==",
"dev": true,
"engines": {
"node": ">=0.12.0"
}
},
"node_modules/is-number-object": {
"version": "1.0.6",
"resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.0.6.tgz",
@ -1121,6 +1308,18 @@
"resolved": "https://registry.npmjs.org/minimalistic-crypto-utils/-/minimalistic-crypto-utils-1.0.1.tgz",
"integrity": "sha1-9sAMHAsIIkblxNmd+4x8CDsrWCo="
},
"node_modules/minimatch": {
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
"integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
"dev": true,
"dependencies": {
"brace-expansion": "^1.1.7"
},
"engines": {
"node": "*"
}
},
"node_modules/minimist": {
"version": "1.2.5",
"resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz",
@ -1210,6 +1409,73 @@
"node": ">= 0.6"
}
},
"node_modules/nodemon": {
"version": "2.0.20",
"resolved": "https://registry.npmjs.org/nodemon/-/nodemon-2.0.20.tgz",
"integrity": "sha512-Km2mWHKKY5GzRg6i1j5OxOHQtuvVsgskLfigG25yTtbyfRGn/GNvIbRyOf1PSCKJ2aT/58TiuUsuOU5UToVViw==",
"dev": true,
"dependencies": {
"chokidar": "^3.5.2",
"debug": "^3.2.7",
"ignore-by-default": "^1.0.1",
"minimatch": "^3.1.2",
"pstree.remy": "^1.1.8",
"semver": "^5.7.1",
"simple-update-notifier": "^1.0.7",
"supports-color": "^5.5.0",
"touch": "^3.1.0",
"undefsafe": "^2.0.5"
},
"bin": {
"nodemon": "bin/nodemon.js"
},
"engines": {
"node": ">=8.10.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/nodemon"
}
},
"node_modules/nodemon/node_modules/debug": {
"version": "3.2.7",
"resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz",
"integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==",
"dev": true,
"dependencies": {
"ms": "^2.1.1"
}
},
"node_modules/nodemon/node_modules/ms": {
"version": "2.1.3",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
"integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
"dev": true
},
"node_modules/nopt": {
"version": "1.0.10",
"resolved": "https://registry.npmjs.org/nopt/-/nopt-1.0.10.tgz",
"integrity": "sha512-NWmpvLSqUrgrAC9HCuxEvb+PSloHpqVu+FqcO4eeF2h5qYRhA7ev6KvelyQAKtegUbC6RypJnlEOhd8vloNKYg==",
"dev": true,
"dependencies": {
"abbrev": "1"
},
"bin": {
"nopt": "bin/nopt.js"
},
"engines": {
"node": "*"
}
},
"node_modules/normalize-path": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz",
"integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==",
"dev": true,
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/object-assign": {
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
@ -1302,6 +1568,18 @@
"node": ">=0.12"
}
},
"node_modules/picomatch": {
"version": "2.3.1",
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz",
"integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==",
"dev": true,
"engines": {
"node": ">=8.6"
},
"funding": {
"url": "https://github.com/sponsors/jonschlinkert"
}
},
"node_modules/proxy-addr": {
"version": "2.0.7",
"resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz",
@ -1314,6 +1592,12 @@
"node": ">= 0.10"
}
},
"node_modules/pstree.remy": {
"version": "1.1.8",
"resolved": "https://registry.npmjs.org/pstree.remy/-/pstree.remy-1.1.8.tgz",
"integrity": "sha512-77DZwxQmxKnu3aR542U+X8FypNzbfJ+C5XQDk3uWjWxn6151aIMGthWYRXTqT1E5oJvg+ljaa2OJi+VfvCOQ8w==",
"dev": true
},
"node_modules/public-encrypt": {
"version": "4.0.3",
"resolved": "https://registry.npmjs.org/public-encrypt/-/public-encrypt-4.0.3.tgz",
@ -1412,6 +1696,18 @@
"node": ">= 6"
}
},
"node_modules/readdirp": {
"version": "3.6.0",
"resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz",
"integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==",
"dev": true,
"dependencies": {
"picomatch": "^2.2.1"
},
"engines": {
"node": ">=8.10.0"
}
},
"node_modules/ripemd160": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/ripemd160/-/ripemd160-2.0.2.tgz",
@ -1462,6 +1758,15 @@
"resolved": "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz",
"integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw=="
},
"node_modules/semver": {
"version": "5.7.1",
"resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz",
"integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==",
"dev": true,
"bin": {
"semver": "bin/semver"
}
},
"node_modules/send": {
"version": "0.17.2",
"resolved": "https://registry.npmjs.org/send/-/send-0.17.2.tgz",
@ -1534,6 +1839,27 @@
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/simple-update-notifier": {
"version": "1.0.7",
"resolved": "https://registry.npmjs.org/simple-update-notifier/-/simple-update-notifier-1.0.7.tgz",
"integrity": "sha512-BBKgR84BJQJm6WjWFMHgLVuo61FBDSj1z/xSFUIozqO6wO7ii0JxCqlIud7Enr/+LhlbNI0whErq96P2qHNWew==",
"dev": true,
"dependencies": {
"semver": "~7.0.0"
},
"engines": {
"node": ">=8.10.0"
}
},
"node_modules/simple-update-notifier/node_modules/semver": {
"version": "7.0.0",
"resolved": "https://registry.npmjs.org/semver/-/semver-7.0.0.tgz",
"integrity": "sha512-+GB6zVA9LWh6zovYQLALHwv5rb2PHGlJi3lfiqIHxR0uuwCgefcOJc59v9fv1w8GbStwxuuqqAjI9NMAOOgq1A==",
"dev": true,
"bin": {
"semver": "bin/semver.js"
}
},
"node_modules/smart-buffer": {
"version": "4.2.0",
"resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.2.0.tgz",
@ -1610,6 +1936,18 @@
"resolved": "https://registry.npmjs.org/strnum/-/strnum-1.0.5.tgz",
"integrity": "sha512-J8bbNyKKXl5qYcR36TIO8W3mVGVHrmmxsd5PAItGkmyzwJvybiw2IVq5nqd0i4LSNSkB/sx9VHllbfFdr9k1JA=="
},
"node_modules/supports-color": {
"version": "5.5.0",
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz",
"integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==",
"dev": true,
"dependencies": {
"has-flag": "^3.0.0"
},
"engines": {
"node": ">=4"
}
},
"node_modules/through2": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/through2/-/through2-3.0.2.tgz",
@ -1619,6 +1957,18 @@
"readable-stream": "2 || 3"
}
},
"node_modules/to-regex-range": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
"integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==",
"dev": true,
"dependencies": {
"is-number": "^7.0.0"
},
"engines": {
"node": ">=8.0"
}
},
"node_modules/toidentifier": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz",
@ -1627,6 +1977,18 @@
"node": ">=0.6"
}
},
"node_modules/touch": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/touch/-/touch-3.1.0.tgz",
"integrity": "sha512-WBx8Uy5TLtOSRtIq+M03/sKDrXCLHxwDcquSP2c43Le03/9serjQBIztjRz6FkJez9D/hleyAXTBGLwwZUw9lA==",
"dev": true,
"dependencies": {
"nopt": "~1.0.10"
},
"bin": {
"nodetouch": "bin/nodetouch.js"
}
},
"node_modules/tr46": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/tr46/-/tr46-3.0.0.tgz",
@ -1664,6 +2026,12 @@
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/undefsafe": {
"version": "2.0.5",
"resolved": "https://registry.npmjs.org/undefsafe/-/undefsafe-2.0.5.tgz",
"integrity": "sha512-WxONCrssBM8TSPRqN5EmsjVrsv4A8X12J4ArBiiayv3DyyG3ZlIg6yysuuSYdZsVz3TKcTg2fd//Ujd4CHV1iA==",
"dev": true
},
"node_modules/unpipe": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz",
@ -1823,6 +2191,12 @@
"integrity": "sha512-U/4aVJ2mxI0aDNI8Uq0wEhMgY+u4CNtEb0om3+y3+niDAsoTCOB33UF0sxpzqzdqXLqmvc+vZyAt4O8pPdfkwA==",
"optional": true
},
"abbrev": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz",
"integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==",
"dev": true
},
"accepts": {
"version": "1.3.7",
"resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.7.tgz",
@ -1832,6 +2206,16 @@
"negotiator": "0.6.2"
}
},
"anymatch": {
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.2.tgz",
"integrity": "sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg==",
"dev": true,
"requires": {
"normalize-path": "^3.0.0",
"picomatch": "^2.0.4"
}
},
"array-flatten": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz",
@ -1865,11 +2249,23 @@
"resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.5.tgz",
"integrity": "sha512-DMD0KiN46eipeziST1LPP/STfDU0sufISXmjSgvVsoU2tqxctQeASejWcfNtxYKqETM1UxQ8sp2OrSBWpHY6sw=="
},
"balanced-match": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
"integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
"dev": true
},
"base64-js": {
"version": "1.5.1",
"resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz",
"integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA=="
},
"binary-extensions": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz",
"integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==",
"dev": true
},
"block-stream2": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/block-stream2/-/block-stream2-2.1.0.tgz",
@ -1900,6 +2296,25 @@
"type-is": "~1.6.18"
}
},
"brace-expansion": {
"version": "1.1.11",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
"integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
"dev": true,
"requires": {
"balanced-match": "^1.0.0",
"concat-map": "0.0.1"
}
},
"braces": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz",
"integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==",
"dev": true,
"requires": {
"fill-range": "^7.0.1"
}
},
"brorand": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/brorand/-/brorand-1.1.0.tgz",
@ -2005,6 +2420,22 @@
"get-intrinsic": "^1.0.2"
}
},
"chokidar": {
"version": "3.5.3",
"resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz",
"integrity": "sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==",
"dev": true,
"requires": {
"anymatch": "~3.1.2",
"braces": "~3.0.2",
"fsevents": "~2.3.2",
"glob-parent": "~5.1.2",
"is-binary-path": "~2.1.0",
"is-glob": "~4.0.1",
"normalize-path": "~3.0.0",
"readdirp": "~3.6.0"
}
},
"cipher-base": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/cipher-base/-/cipher-base-1.0.4.tgz",
@ -2014,6 +2445,12 @@
"safe-buffer": "^5.0.1"
}
},
"concat-map": {
"version": "0.0.1",
"resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
"integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==",
"dev": true
},
"content-disposition": {
"version": "0.5.4",
"resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz",
@ -2299,6 +2736,15 @@
"strnum": "^1.0.4"
}
},
"fill-range": {
"version": "7.0.1",
"resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz",
"integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==",
"dev": true,
"requires": {
"to-regex-range": "^5.0.1"
}
},
"finalhandler": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.2.tgz",
@ -2328,6 +2774,13 @@
"resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz",
"integrity": "sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac="
},
"fsevents": {
"version": "2.3.2",
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz",
"integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==",
"dev": true,
"optional": true
},
"function-bind": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz",
@ -2352,6 +2805,15 @@
"get-intrinsic": "^1.1.1"
}
},
"glob-parent": {
"version": "5.1.2",
"resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz",
"integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==",
"dev": true,
"requires": {
"is-glob": "^4.0.1"
}
},
"has": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz",
@ -2365,6 +2827,12 @@
"resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.0.1.tgz",
"integrity": "sha512-LSBS2LjbNBTf6287JEbEzvJgftkF5qFkmCo9hDRpAzKhUOlJ+hx8dd4USs00SgsUNwc4617J9ki5YtEClM2ffA=="
},
"has-flag": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz",
"integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==",
"dev": true
},
"has-symbols": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.2.tgz",
@ -2432,6 +2900,12 @@
"resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz",
"integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA=="
},
"ignore-by-default": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/ignore-by-default/-/ignore-by-default-1.0.1.tgz",
"integrity": "sha512-Ius2VYcGNk7T90CppJqcIkS5ooHUZyIQK+ClZfMfMNFEF9VSE73Fq+906u/CWu92x4gzZMWOwfFYckPObzdEbA==",
"dev": true
},
"inherits": {
"version": "2.0.4",
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
@ -2474,6 +2948,15 @@
"has-bigints": "^1.0.1"
}
},
"is-binary-path": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz",
"integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==",
"dev": true,
"requires": {
"binary-extensions": "^2.0.0"
}
},
"is-boolean-object": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.1.2.tgz",
@ -2496,6 +2979,12 @@
"has-tostringtag": "^1.0.0"
}
},
"is-extglob": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
"integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==",
"dev": true
},
"is-generator-function": {
"version": "1.0.10",
"resolved": "https://registry.npmjs.org/is-generator-function/-/is-generator-function-1.0.10.tgz",
@ -2504,11 +2993,26 @@
"has-tostringtag": "^1.0.0"
}
},
"is-glob": {
"version": "4.0.3",
"resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz",
"integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==",
"dev": true,
"requires": {
"is-extglob": "^2.1.1"
}
},
"is-negative-zero": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.2.tgz",
"integrity": "sha512-dqJvarLawXsFbNDeJW7zAz8ItJ9cd28YufuuFzh0G8pNHjJMnY08Dv7sYX2uF5UpQOwieAeOExEYAWWfu7ZZUA=="
},
"is-number": {
"version": "7.0.0",
"resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz",
"integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==",
"dev": true
},
"is-number-object": {
"version": "1.0.6",
"resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.0.6.tgz",
@ -2652,6 +3156,15 @@
"resolved": "https://registry.npmjs.org/minimalistic-crypto-utils/-/minimalistic-crypto-utils-1.0.1.tgz",
"integrity": "sha1-9sAMHAsIIkblxNmd+4x8CDsrWCo="
},
"minimatch": {
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
"integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
"dev": true,
"requires": {
"brace-expansion": "^1.1.7"
}
},
"minimist": {
"version": "1.2.5",
"resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz",
@ -2726,6 +3239,56 @@
"resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.2.tgz",
"integrity": "sha512-hZXc7K2e+PgeI1eDBe/10Ard4ekbfrrqG8Ep+8Jmf4JID2bNg7NvCPOZN+kfF574pFQI7mum2AUqDidoKqcTOw=="
},
"nodemon": {
"version": "2.0.20",
"resolved": "https://registry.npmjs.org/nodemon/-/nodemon-2.0.20.tgz",
"integrity": "sha512-Km2mWHKKY5GzRg6i1j5OxOHQtuvVsgskLfigG25yTtbyfRGn/GNvIbRyOf1PSCKJ2aT/58TiuUsuOU5UToVViw==",
"dev": true,
"requires": {
"chokidar": "^3.5.2",
"debug": "^3.2.7",
"ignore-by-default": "^1.0.1",
"minimatch": "^3.1.2",
"pstree.remy": "^1.1.8",
"semver": "^5.7.1",
"simple-update-notifier": "^1.0.7",
"supports-color": "^5.5.0",
"touch": "^3.1.0",
"undefsafe": "^2.0.5"
},
"dependencies": {
"debug": {
"version": "3.2.7",
"resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz",
"integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==",
"dev": true,
"requires": {
"ms": "^2.1.1"
}
},
"ms": {
"version": "2.1.3",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
"integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
"dev": true
}
}
},
"nopt": {
"version": "1.0.10",
"resolved": "https://registry.npmjs.org/nopt/-/nopt-1.0.10.tgz",
"integrity": "sha512-NWmpvLSqUrgrAC9HCuxEvb+PSloHpqVu+FqcO4eeF2h5qYRhA7ev6KvelyQAKtegUbC6RypJnlEOhd8vloNKYg==",
"dev": true,
"requires": {
"abbrev": "1"
}
},
"normalize-path": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz",
"integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==",
"dev": true
},
"object-assign": {
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
@ -2794,6 +3357,12 @@
"sha.js": "^2.4.8"
}
},
"picomatch": {
"version": "2.3.1",
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz",
"integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==",
"dev": true
},
"proxy-addr": {
"version": "2.0.7",
"resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz",
@ -2803,6 +3372,12 @@
"ipaddr.js": "1.9.1"
}
},
"pstree.remy": {
"version": "1.1.8",
"resolved": "https://registry.npmjs.org/pstree.remy/-/pstree.remy-1.1.8.tgz",
"integrity": "sha512-77DZwxQmxKnu3aR542U+X8FypNzbfJ+C5XQDk3uWjWxn6151aIMGthWYRXTqT1E5oJvg+ljaa2OJi+VfvCOQ8w==",
"dev": true
},
"public-encrypt": {
"version": "4.0.3",
"resolved": "https://registry.npmjs.org/public-encrypt/-/public-encrypt-4.0.3.tgz",
@ -2881,6 +3456,15 @@
"util-deprecate": "^1.0.1"
}
},
"readdirp": {
"version": "3.6.0",
"resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz",
"integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==",
"dev": true,
"requires": {
"picomatch": "^2.2.1"
}
},
"ripemd160": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/ripemd160/-/ripemd160-2.0.2.tgz",
@ -2914,6 +3498,12 @@
"resolved": "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz",
"integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw=="
},
"semver": {
"version": "5.7.1",
"resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz",
"integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==",
"dev": true
},
"send": {
"version": "0.17.2",
"resolved": "https://registry.npmjs.org/send/-/send-0.17.2.tgz",
@ -2976,6 +3566,23 @@
"object-inspect": "^1.9.0"
}
},
"simple-update-notifier": {
"version": "1.0.7",
"resolved": "https://registry.npmjs.org/simple-update-notifier/-/simple-update-notifier-1.0.7.tgz",
"integrity": "sha512-BBKgR84BJQJm6WjWFMHgLVuo61FBDSj1z/xSFUIozqO6wO7ii0JxCqlIud7Enr/+LhlbNI0whErq96P2qHNWew==",
"dev": true,
"requires": {
"semver": "~7.0.0"
},
"dependencies": {
"semver": {
"version": "7.0.0",
"resolved": "https://registry.npmjs.org/semver/-/semver-7.0.0.tgz",
"integrity": "sha512-+GB6zVA9LWh6zovYQLALHwv5rb2PHGlJi3lfiqIHxR0uuwCgefcOJc59v9fv1w8GbStwxuuqqAjI9NMAOOgq1A==",
"dev": true
}
}
},
"smart-buffer": {
"version": "4.2.0",
"resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.2.0.tgz",
@ -3035,6 +3642,15 @@
"resolved": "https://registry.npmjs.org/strnum/-/strnum-1.0.5.tgz",
"integrity": "sha512-J8bbNyKKXl5qYcR36TIO8W3mVGVHrmmxsd5PAItGkmyzwJvybiw2IVq5nqd0i4LSNSkB/sx9VHllbfFdr9k1JA=="
},
"supports-color": {
"version": "5.5.0",
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz",
"integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==",
"dev": true,
"requires": {
"has-flag": "^3.0.0"
}
},
"through2": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/through2/-/through2-3.0.2.tgz",
@ -3044,11 +3660,29 @@
"readable-stream": "2 || 3"
}
},
"to-regex-range": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
"integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==",
"dev": true,
"requires": {
"is-number": "^7.0.0"
}
},
"toidentifier": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz",
"integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA=="
},
"touch": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/touch/-/touch-3.1.0.tgz",
"integrity": "sha512-WBx8Uy5TLtOSRtIq+M03/sKDrXCLHxwDcquSP2c43Le03/9serjQBIztjRz6FkJez9D/hleyAXTBGLwwZUw9lA==",
"dev": true,
"requires": {
"nopt": "~1.0.10"
}
},
"tr46": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/tr46/-/tr46-3.0.0.tgz",
@ -3077,6 +3711,12 @@
"which-boxed-primitive": "^1.0.2"
}
},
"undefsafe": {
"version": "2.0.5",
"resolved": "https://registry.npmjs.org/undefsafe/-/undefsafe-2.0.5.tgz",
"integrity": "sha512-WxONCrssBM8TSPRqN5EmsjVrsv4A8X12J4ArBiiayv3DyyG3ZlIg6yysuuSYdZsVz3TKcTg2fd//Ujd4CHV1iA==",
"dev": true
},
"unpipe": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz",

View File

@ -5,8 +5,8 @@
"main": "server.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"start": "node server.js",
"build": "cd frontend && npm install && npm run build"
"dev": "nodemon server.js -e ejs,js,css,html,jpg,png,scss",
"prod": "node.js server.js"
},
"keywords": [],
"author": "",
@ -16,5 +16,8 @@
"express": "^4.17.2",
"minio": "^7.0.26",
"mongodb": "^4.3.1"
},
"devDependencies": {
"nodemon": "^2.0.20"
}
}

View File

@ -1,21 +1,12 @@
const { MongoClient } = require("mongodb");
const { MongoClient, MongoClientOptions } = require("mongodb");
const express = require('express');
const minio = require('minio');
/*============== VARIABLE DECLARATION ==============*/
// Mongo set-up variables
const mongoCollection = process.env.MONGO_COLLECTION || 'eventlog';
const mongoUri = process.env.MONGO_URI || 'mongodb://127.0.0.1:27017/default?replicaSet=rs0';
const mongoCollection = process.env.MONGO_COLLECTION || 'log';
const mongoUri = process.env.MONGODB_HOST || 'mongodb://127.0.0.1:27017/default?replicaSet=rs0';
// Minio set-up variables
const minioAccessKey = process.env.MINIO_ACCESS_KEY || 'kspace-mugshot';
const minioSecretKey = process.env.MINIO_SECRET_KEY || '2mSI6HdbJ8';
const minioHostname = process.env.MINIO_HOSTNAME || '127.0.0.1';
const minioPort = process.env.MINIO_PORT || 9000;
const minioScheme = process.env.MINIO_SCHEME || 'http';
const minioBucket = process.env.MINIO_BUCKET || 'kspace-mugshot';
console.info("Using bucket:", minioBucket);
const historyNumber = parseInt(process.env.HISTORY_AMOUNT) || 10;
const historyNumber = parseInt(process.env.HISTORY_AMOUNT) || 50;
// Stream set-up variables
let changeStream;
@ -24,8 +15,10 @@ const pipeline = [];
const PORT = process.env.PORT || 3002;
// Create Mongo client
const mongoClient = new MongoClient(mongoUri);
const mongoOptions = {
readPreference: 'secondaryPreferred'
}
const mongoClient = new MongoClient(mongoUri, mongoOptions);
/*============== CODE ==============*/
async function run() {
@ -38,63 +31,47 @@ async function run() {
changeStream = collection.watch(pipeline, options);
console.log("Started watching changes in database");
console.log("Minio hostname:", minioHostname);
console.log("Minio port:", minioPort);
console.log("Minio protocol:", minioScheme);
const filterOptions = {
'kubernetes.namespace': await collection.distinct('kubernetes.namespace'),
'kubernetes.pod.name': await collection.distinct('kubernetes.pod.name'),
'kubernetes.container.name': await collection.distinct('kubernetes.container.name')
}
const writeMessage = (response, blob) => {
const id = blob._id || null
const message = `id: ${id}\nevent: message\ndata: ${JSON.stringify(blob)}\n\n`
response.write(message)
}
// Triggers on GET at /event route
app.get('/events', async function (request, response) {
let minioClient = new minio.Client({
endPoint: minioHostname,
port: parseInt(minioPort),
useSSL: minioScheme == 'https',
accessKey: minioAccessKey,
secretKey: minioSecretKey
});
async function wrapEvent(doc) {
let arr = [];
let blob;
if (doc && doc.screenshot_count) {
for (let i = 1; i <= doc.screenshot_count ; i++) {
arr.push({
url: await minioClient.presignedUrl('GET', minioBucket,
`${doc.camera}/${doc._id}/${i}.jpg`, 60 * 60)
});
}
} else if (doc && doc.screenshots) {
for (let j = 0; j < doc.screenshots.length; j++) {
let path = doc.screenshots[j];
arr.push({
url: await minioClient.presignedUrl('GET', minioBucket,
`thumb/${path}`, 60 * 60),
orig: await minioClient.presignedUrl('GET', minioBucket,
`${path}`, 60 * 60)
});
}
};
if (arr.length > 0) {
blob = JSON.stringify({...doc, screenshots: [...arr]});
} else {
blob = JSON.stringify({...doc});
}
return `event: log-entry\ndata: ${blob}\n\n`
}
// Notify SSE to React
const header = { 'Content-Type': 'text/event-stream', 'Connection': 'keep-alive' };
response.writeHead(200, "OK", header);
const historyCursor = collection.find().sort({$natural:-1}).limit(historyNumber);
const message = `id: 1\nevent: filters\ndata: ${JSON.stringify(filterOptions)}\n\n`
response.write(message)
historyCursor.forEach(async (document) => {
response.write(await wrapEvent(document));
const historyCursor = collection.find()
.sort({$natural:-1})
.limit(historyNumber).toArray().then((res) => {
res.reverse().forEach((document) => {
writeMessage(response, document)
})
changeStream.on("change", async(data) => {
response.write(await wrapEvent(data.fullDocument));
});
const changeListener = async (change) => {
// Ignore events without fullDocument, e.g. deletes.
if (change.fullDocument) {
writeMessage(response, change.fullDocument)
}
}
changeStream.on("change", changeListener);
response.on('close', () => {
changeStream.removeListener("change", changeListener)
})
});
app.listen(PORT);

View File

@ -1,5 +1,17 @@
./node_modules
./build
.git
*.md
.gitignore
.kpt-pipeline/
k8s/
skaffold.yaml
README.md
.git/
node_modules/
# Editor directories and files
.vscode/*
!.vscode/extensions.json
.idea
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?
*.kpt-pipeline

29
frontend/.gitignore vendored Normal file
View File

@ -0,0 +1,29 @@
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*
node_modules
.DS_Store
dist
dist-ssr
coverage
*.local
/cypress/videos/
/cypress/screenshots/
# Editor directories and files
.vscode/*
!.vscode/extensions.json
.idea
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?
*.kpt-pipeline

View File

@ -1,39 +1,22 @@
# pull official base image
FROM node AS builder
FROM node AS dev
# set working directory
WORKDIR /app
EXPOSE 8080
# install app dependencies
#copies package.json and package-lock.json to Docker environment
COPY package.json ./
# Installs all node packages
COPY package* ./
RUN npm install
COPY . .
ENTRYPOINT ["npm", "run", "dev"]
# Copies everything over to Docker environment
COPY . ./
# builder
FROM dev AS builder
RUN npm run build
#Stage 2
#######################################
#pull the official nginx:1.19.0 base image
FROM nginx
#copies React to the container directory
# Set working directory to nginx resources directory
# serve
FROM nginx AS prod
WORKDIR /usr/share/nginx/html
# Remove default nginx static resources
RUN rm -rf ./*
RUN apt-get install bash
# Copies configuration files
COPY ./default.conf /etc/nginx/conf.d/default.conf
# Copies static resources from builder stage
COPY --from=builder /app/build .
# Containers run nginx with global directives and daemon off
COPY ./nginx.conf /etc/nginx/conf.d/default.conf
COPY --from=builder /app/dist .
ENTRYPOINT ["nginx", "-g", "daemon off;"]

67
frontend/README.md Normal file
View File

@ -0,0 +1,67 @@
# log-viewer microfrontend (WIP)
This is an experimental microfrontend to view logs coming from camtiler, testing microfrontends concept and developing
Javascript/Node application directly on Kubernetes cluster using Skaffold (hot-reloading source files works!)
Currently the application itself is a really minimalistic frontend based on Vue.js 3 framework.
## Getting started
0. Have access with kubectl to desired Kubernetes cluster
0. Have local Docker daemon ready along with access to your image registry (building in-cluster with Kaniko is currently not tested)
1. Get Skaffold 2.0.0-beta*: https://github.com/GoogleContainerTools/skaffold/releases
2. Run `skaffold dev --namespace {desired K8S namespace}`
And you should be good to go. Skaffold would create a deployment, build the container and upload it to your registry
and deploy it.
## Good to know
### Skaffold
All Skaffold configuration is in skaffold.yml. There we have:
1. App/deployment name (metadata)
2. Build artifacts - artifact that Skaffold would build and use, either in case of development or also actual deploy
3. Profiles - similar to `npm` commands/profiles, you can have many different actions (run `skaffold {action}`).
We only have `dev` which will build and deploy the `log-viewer-frontend` container
(in manner how it's defined in Kubernetes manifest), and then enable `manual sync`, which defines which
changed files will be copied to the container when it's running - change in everything else will trigger
rebuilding the container.
### Hot-reload vs rebuilding the container
Currently the files that will be copied are fine-tuned as they are known to work well with Vue.js hot-reload feature
(eg actual frontend code). Other files, such as framework configuration files (`vite.config.js`, `vue.config.js`)
will trigger rebuilding and more importantly, restarting the container, to provide stability. It might not be necessary
to restart the container, this is just testing at this point. You can try it by introducing less explicit `sync`
config in `skaffold.yml`.
Change in `packages*` files will also trigger rebuilding and restarting the container, with the difference of
caching the result of the `npm install` command - `node_modules` directory. So in case of changing framework config,
the container will be quickly rebuilt and restarted, but when changing packages in `package.json`, `npm install`
will also be ran when re-building the container.
### Dockerfile
This is a really simple Dockerfile based on the official Node Docker image.
Only extra stuff it does is:
1. exposing the port for reference
2. changing workdir(?)
3. running `npm install` on-demand
4. copying other code into the container
5. defining the run command (important!)
We also have the `.dockerignore` to filter out files that never need to make into the container, such as IDE
metadata directories, Git, readme etc.
With the current config, your local Docker daemon will be used to build the image and upload it to your image registry.
Building in-cluster with Kaniko should be supported (https://skaffold.dev/docs/pipeline-stages/builders/docker/)
but currently not tested.
### Kubernetes
The Kubernetes manifest(s) are supposed to be in the `k8s/` directory.
Skaffold will pick them up automatically from there, apply them (also cleaning up after itself),
and match the container images from it's manifest and Kubernetes manifest, so it knows when to make the Kubernetes
redeploy the image.
Here we have a simple deployment, where `https://playground.k-space.ee/` takes us to the (development) frontend
container and `https://playground.k-space.ee/events` gets data from the `camtiler log-backend`.
The actual production usage may vary a lot in case of frontend - one would usually not run a
Node.js server to serve frontend assets, unless it's a SSR frontend (Nuxt, Next frameworks).
But for Node.js backends, it's pretty close to the standard.

View File

@ -1,16 +0,0 @@
server {
listen 3003;
server_name localhost;
location / {
root /usr/share/nginx/html;
index index.html index.htm;
}
location /events {
proxy_buffering off;
proxy_pass http://127.0.0.1:3002/events;
}
}

13
frontend/index.html Normal file
View File

@ -0,0 +1,13 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" href="/favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Log Viewer</title>
</head>
<body>
<div id="app"></div>
<script type="module" src="/src/main.js"></script>
</body>
</html>

9
frontend/nginx.conf Normal file
View File

@ -0,0 +1,9 @@
server {
listen 8080;
server_name _;
location / {
root /usr/share/nginx/html;
index index.html index.htm;
}
}

27839
frontend/package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -1,40 +1,31 @@
{
"name": "frontend",
"version": "0.1.0",
"private": true,
"dependencies": {
"@testing-library/jest-dom": "^5.16.1",
"@testing-library/react": "^12.1.2",
"@testing-library/user-event": "^13.5.0",
"react": "^17.0.2",
"react-dom": "^17.0.2",
"react-hooks-sse": "^2.0.0",
"react-scripts": "5.0.0",
"web-vitals": "^2.1.3"
},
"name": "@k-space/log-viewer",
"version": "0.0.0",
"scripts": {
"start": "react-scripts start",
"build": "react-scripts build",
"test": "react-scripts test",
"eject": "react-scripts eject"
"serve": "vue-cli-service serve",
"build": "vite build",
"dev": "vite --port 8080 --host",
"preview": "vite preview --port 3003"
},
"proxy": "http://localhost:3002",
"eslintConfig": {
"extends": [
"react-app",
"react-app/jest"
]
"dependencies": {
"@ag-grid-community/core": "^28.2.0",
"@mdi/font": "5.9.55",
"@vue/cli-service": "^5.0.8",
"ag-grid-vue3": "^28.2.0",
"core-js": "^3.25.1",
"event-hooks-webpack-plugin": "^2.2.0",
"pinia": "^2.0.21",
"roboto-fontface": "*",
"single-spa-vue": "^2.5.1",
"systemjs-webpack-interop": "^2.3.7",
"vue": "^3.2.39",
"vuetify": "^3.0.0-beta.0",
"webfontloader": "^1.0.0"
},
"browserslist": {
"production": [
">0.2%",
"not dead",
"not op_mini all"
],
"development": [
"last 1 chrome version",
"last 1 firefox version",
"last 1 safari version"
]
"devDependencies": {
"@vitejs/plugin-vue": "^3.0.3",
"vite": "^3.0.9",
"vue-cli-plugin-vuetify": "~2.5.8",
"webpack-plugin-vuetify": "^2.0.0-alpha.0"
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.8 KiB

After

Width:  |  Height:  |  Size: 4.2 KiB

View File

@ -1,41 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="theme-color" content="#000000" />
<meta
name="Event logging"
content="Web site created using create-react-app"
/>
<!--
manifest.json provides metadata used when your web app is installed on a
user's mobile device or desktop. See https://developers.google.com/web/fundamentals/web-app-manifest/
-->
<link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
<!--
Notice the use of %PUBLIC_URL% in the tags above.
It will be replaced with the URL of the `public` folder during the build.
Only files inside the `public` folder can be referenced from the HTML.
Unlike "/favicon.ico" or "favicon.ico", "%PUBLIC_URL%/favicon.ico" will
work correctly both with client-side routing and a non-root public URL.
Learn how to configure a non-root public URL by running `npm run build`.
-->
<title>Event log</title>
</head>
<body>
<noscript>You need to enable JavaScript to run this app.</noscript>
<div id="root"></div>
<!--
This HTML file is a template.
If you open it directly in the browser, you will see an empty page.
You can add webfonts, meta tags, or analytics to this file.
The build step will place the bundled scripts into the <body> tag.
To begin the development, run `npm start` or `yarn start`.
To create a production bundle, use `npm run build` or `yarn build`.
-->
</body>
</html>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.4 KiB

View File

@ -1,25 +0,0 @@
{
"short_name": "React App",
"name": "Create React App Sample",
"icons": [
{
"src": "favicon.ico",
"sizes": "64x64 32x32 24x24 16x16",
"type": "image/x-icon"
},
{
"src": "logo192.png",
"type": "image/png",
"sizes": "192x192"
},
{
"src": "logo512.png",
"type": "image/png",
"sizes": "512x512"
}
],
"start_url": ".",
"display": "standalone",
"theme_color": "#000000",
"background_color": "#ffffff"
}

View File

@ -1,3 +0,0 @@
# https://www.robotstxt.org/robotstxt.html
User-agent: *
Disallow:

15
frontend/src/App.vue Normal file
View File

@ -0,0 +1,15 @@
<script setup>
import LogViewer from './components/LogViewer.vue'
</script>
<template>
<LogViewer />
</template>
<script>
export default {
name: 'app1',
}
</script>
<style scoped>
</style>

View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 261.76 226.69" xmlns:v="https://vecta.io/nano"><path d="M161.096.001l-30.225 52.351L100.647.001H-.005l130.877 226.688L261.749.001z" fill="#41b883"/><path d="M161.096.001l-30.225 52.351L100.647.001H52.346l78.526 136.01L209.398.001z" fill="#34495e"/></svg>

After

Width:  |  Height:  |  Size: 308 B

View File

@ -0,0 +1,15 @@
div#app {
width: 100%;
height: 100vh;
}
.screenshots-drawer {
position: fixed;
display: flex;
flex-direction: column;
}
.ag-theme-material {
--ag-value-change-value-highlight-background-color: #f9ff99;
}

View File

@ -0,0 +1,29 @@
export default {
template: `
<select v-model="filter" class="v-select">
<option value=""> </option>
<option v-for="option in params.options" :value="option">
{{ option }}
</option>
</select>
`,
data: function () {
return {
filter: '',
};
},
methods: {
updateFilter() {
this.params.filterChangedCallback();
},
doesFilterPass(params) {
const value = this.params.field.split('.').reduce((a, b) => a[b], params.data);
return value === this.filter;
},
isFilterActive() {
return this.filter !== ''
},
},
};

View File

@ -0,0 +1,83 @@
<template>
<v-dialog
v-model="examineLog"
width="50wv"
>
<v-card>
<v-card-text style="height: 70vh">
<ag-grid-vue
style="width: 100%; height: 100%;"
class="ag-theme-material"
@grid-ready="onGridReady"
:columnDefs="columnDefs"
:row-data="examineLogContent"
:supress-horisontal-scroll="true"
:enable-scrolling="true"
:enableCellTextSelection="true"
:ensureDomOrder="true"
></ag-grid-vue>
</v-card-text>
<v-card-actions>
<v-btn color="primary" block @click="closeModal">Close</v-btn>
</v-card-actions>
</v-card>
</v-dialog>
</template>
<script>
import { AgGridVue } from "ag-grid-vue3";
import "ag-grid-community/styles//ag-grid.css";
import "ag-grid-community/styles//ag-theme-material.css";
import ScreenshotCell from "./ScreenshotCell.js";
import { VCard, VCardText, VCardActions } from 'vuetify/components/VCard'
import { VDialog } from 'vuetify/components/VDialog'
import { VBtn } from 'vuetify/components/VBtn'
import { VTable } from 'vuetify/components/VTable'
export default {
components: {
AgGridVue,
VCard,
VCardText,
VCardActions,
VBtn,
VDialog,
VTable,
ScreenshotCell: ScreenshotCell
},
data() {
return {
columnDefs: [
{
field: 'key',
sortable: true,
filter: 'agTextColumnFilter',
resizable: true
},
{
field: 'value',
sortable: true,
filter: 'agTextColumnFilter',
resizable: true
},
]
}
},
props: {
examineLogContent: Array,
closeModal: Function
},
computed: {
examineLog() {
return !!this.examineLogContent
}
},
methods: {
onGridReady(params) {
params.api.sizeColumnsToFit()
},
}
}
</script>

View File

@ -0,0 +1,173 @@
<template>
<div style="height: 100%; width: 100%">
<ag-grid-vue
style="width: 100%; height: 100%;"
class="ag-theme-material"
@grid-ready="onGridReady"
:defaultColDef="defaultColDef"
:columnDefs="columnDefs"
:pagination="true"
:paginationAutoPageSize=true
:row-data="null"
row-selection="single"
:onRowSelected="openExamineLog"
:supress-horisontal-scroll="true"
:enable-scrolling="true"
></ag-grid-vue>
<ExamineLogModal :examine-log-content="examineLogContent" :close-modal="closeExamineLog" />
</div>
</template>
<script>
import { AgGridVue } from "ag-grid-vue3";
import "ag-grid-community/styles//ag-grid.css";
import "ag-grid-community/styles//ag-theme-material.css";
import ScreenshotCell from "./ScreenshotCell.js";
import ExamineLogModal from "./ExamineLogModal.vue";
import ComboboxFilter from "./ComboboxFilter.js";
export default {
components: {
ExamineLogModal,
AgGridVue,
ComboboxFilter,
ScreenshotCell: ScreenshotCell
},
data() {
return {
examineLogContent: null,
gridApi: null,
gridColumnApi: null,
defaultColDef: {
width: 50,
initialPinned: true,
resizable: true,
enableCellChangeFlash: true
},
currentRowCount: 0,
comboBoxOptions: {},
viewRowCount: 20,
}
},
computed: {
columnDefs() {
return [
{
field: '@timestamp',
width: 70,
sortable: true
},
{
field: 'kubernetes.namespace',
headerName: 'namespace',
tooltipValueGetter: (params) => params.value,
filter: ComboboxFilter,
filterParams: {
options: this.comboBoxOptions['kubernetes.namespace'],
field: 'kubernetes.namespace',
}
},
{
field: 'kubernetes.pod.name',
headerName: 'pod',
tooltipValueGetter: (params) => params.value,
filter: ComboboxFilter,
filterParams: {
options: this.comboBoxOptions['kubernetes.pod.name'],
field: 'kubernetes.pod.name',
}
},
{
field: 'kubernetes.container.name',
headerName: 'container',
tooltipValueGetter: (params) => params.value,
filter: ComboboxFilter,
filterParams: {
options: this.comboBoxOptions['kubernetes.container.name'],
field: 'kubernetes.container.name',
}
},
{
field: 'message',
tooltipValueGetter: (params) => params.value,
width: 500,
},
{
field: 'stream',
},
];
}
},
created() {
this.setupStream()
},
methods: {
setupStream() {
let es = new EventSource('/events');
es.onmessage = (e) => this.handleReceiveMessage(e)
es.addEventListener("filters", (e) => this.handleReceiveFilters(e))
},
onGridReady(params) {
this.gridApi = params.api;
this.gridColumnApi = params.columnApi;
},
handleReceiveMessage (event) {
const eventData = this.parseEventData(event.data);
const res = this.gridApi.applyTransaction({
add: [eventData]
});
const rowNode = res.add[0]
this.gridApi.flashCells({ rowNodes: [rowNode]});
this.gridApi.sizeColumnsToFit()
},
handleReceiveFilters (event) {
this.comboBoxOptions = this.parseEventData(event.data);
},
parseEventData (eventData) {
try {
let json = JSON.parse(eventData)
if (!json.message) {
json.message = JSON.stringify(json.json)
}
return json
} catch (e) {
console.error(e, eventData)
}
},
openExamineLog (row) {
const selectedRow = row.data
row.node.setSelected(false)
this.examineLog = true
const flattened = flattenObj(selectedRow)
const pairs = [];
Object.keys(flattened).map((key) => {
pairs.push({
key: key,
value: flattened[key]
})
})
this.examineLogContent = pairs
},
closeExamineLog () {
this.examineLogContent = null
}
},
}
const flattenObj = (ob) => {
let result = {};
for (const i in ob) {
if ((typeof ob[i]) === 'object' && !Array.isArray(ob[i])) {
const temp = flattenObj(ob[i]);
for (const j in temp) {
result[i + '.' + j] = temp[j];
}
}
else {
result[i] = ob[i];
}
}
return result;
};
</script>

View File

@ -0,0 +1,30 @@
export default {
template: `<div>
<a @click="openDrawer">View screenshots</a>
<div v-if="drawerOpen" class="screenshots-drawer">
<img v-for="screenshot in screenshots" :src="screenshot.orig"/>
</div>
</div>`,
data: function () {
return {
screenshots: [],
drawerOpen: false,
};
},
beforeMount() {
this.updateImage(this.params);
this.updateImage(this.params);
},
methods: {
updateImage(params) {
this.screenshots = params.value
this.value = params.value;
},
refresh(params) {
this.updateImage(params);
},
openDrawer () {
this.drawerOpen = true
}
},
};

View File

@ -1,3 +0,0 @@
body {
background-color: #eafaf9;
}

View File

@ -1,47 +0,0 @@
import React, { useState, useEffect } from 'react';
import { useSSE, SSEProvider } from 'react-hooks-sse';
import './App.css';
import EventList from '../eventList/EventList.js';
import './App.css';
const Component = () => {
const event = useSSE('log-entry')
const [events, setEvents] = useState([])
useEffect(() =>{
console.info(event);
if (event) {
for (var j = 0; j < events.length; j++) {
if (events[j].timestamp <= event.timestamp) {
if (events[j]._id == event._id) {
setEvents([...events.slice(0, j), event, ...events.slice(j+1)]);
return;
} else {
setEvents([...events.slice(0, j), event, ...events.slice(j)]);
return;
}
}
}
setEvents([event, ...events]);
}
}, [event])
return <EventList data={events} />
}
function App() {
return (
<SSEProvider endpoint="/events" >
<Component />
</SSEProvider>
)
}
export default App;

View File

@ -1,18 +0,0 @@
.eventDiv {
background-color: aliceblue;
border: 2px solid #26A69A;
border-radius: 15px;
text-align: left;
padding: 5px;
max-width: fit-content;
font-family: apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,Oxygen-Sans,Ubuntu,Cantarell,"Helvetica Neue",sans-serif;
color: #092a26;
margin: 7px 0px;
}
.eventPic {
border-radius: 15px;
border: 1px groove #d5f6f2;
max-width:100%;
max-height:100%;
}

View File

@ -1,23 +0,0 @@
import React from "react";
import "./Event.css";
function Event(props) {
let imgArray = [];
if (props.data.hasOwnProperty('screenshots')) {
props.data.screenshots.forEach(element => {
imgArray = [...imgArray, (<a href={element.orig} target="_blank"><img src={element.url} className="eventPic"></img></a>)]
});
}
return (
<div className="eventDiv">
{JSON.stringify(props.data.source)}
<br></br>
{[...imgArray]}
</div>
);
}
export default Event;

View File

@ -1,11 +0,0 @@
.eventListUl {
background-color: #c1f1ec;
max-width: 75%;
padding: 5px;
border: 5px solid #26A69A;
border-radius: 5px;
}
.eventListLi {
list-style: none;
}

View File

@ -1,15 +0,0 @@
import React from 'react';
import Event from '../event/Event.js';
import './EventList.css';
function EventList(props) {
return (
<ul className="eventListUl">
{props.data.map((event) => {
return <li className="eventListLi"><Event data={event} /></li>
})}
</ul>
)
}
export default EventList;

View File

@ -1,3 +0,0 @@
body {
}

View File

@ -1,17 +0,0 @@
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './components/app/App.js';
import reportWebVitals from './reportWebVitals';
ReactDOM.render(
<React.StrictMode>
<App />
</React.StrictMode>,
document.getElementById('root')
);
// If you want to start measuring performance in your app, pass a function
// to log results (for example: reportWebVitals(console.log))
// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
reportWebVitals();

10
frontend/src/main.js Normal file
View File

@ -0,0 +1,10 @@
import { createApp } from 'vue'
import App from './App.vue'
import vuetify from './plugins/vuetify'
import { loadFonts } from './plugins/webfontloader'
import './assets/main.css'
loadFonts()
createApp(App)
.use(vuetify)
.mount('#app')

View File

@ -1,13 +0,0 @@
const reportWebVitals = onPerfEntry => {
if (onPerfEntry && onPerfEntry instanceof Function) {
import('web-vitals').then(({ getCLS, getFID, getFCP, getLCP, getTTFB }) => {
getCLS(onPerfEntry);
getFID(onPerfEntry);
getFCP(onPerfEntry);
getLCP(onPerfEntry);
getTTFB(onPerfEntry);
});
}
};
export default reportWebVitals;

View File

@ -0,0 +1,3 @@
import { setPublicPath } from "systemjs-webpack-interop";
setPublicPath("app1");

View File

@ -1,5 +0,0 @@
// jest-dom adds custom jest matchers for asserting on DOM nodes.
// allows you to do things like:
// expect(element).toHaveTextContent(/react/i)
// learn more: https://github.com/testing-library/jest-dom
import '@testing-library/jest-dom';

View File

@ -0,0 +1,14 @@
import { ref, computed } from 'vue'
import { defineStore } from 'pinia'
export const useCounterStore = defineStore('counter', () => {
const count = ref(0)
const doubleCount = computed(() => count.value * 2)
function increment() {
count.value++
}
return { count, doubleCount, increment }
})

26
frontend/vite.config.js Normal file
View File

@ -0,0 +1,26 @@
import vue from '@vitejs/plugin-vue'
export default {
resolve: {
alias: {
vue: 'vue/dist/vue.esm-bundler.js'
}
},
rollupOptions: {
input: 'src/main.js',
format: 'system',
preserveEntrySignatures: true
},
plugins: [vue({
template: {
transformAssetUrls: {
base: '/src'
}
}
})],
pluginOptions: {
vuetify: {
// https://github.com/vuetifyjs/vuetify-loader/tree/next/packages/vuetify-loader
}
}
}

43
frontend/vue.config.js Normal file
View File

@ -0,0 +1,43 @@
const path = require('path');
const fs = require('fs');
const EventHooksPlugin = require('event-hooks-webpack-plugin');
module.exports = {
publicPath: '/logs',
chainWebpack: (config) => {
config.devServer.headers({
'Access-Control-Allow-Origin': '*',
});
config.devServer.set('port', 8080);
config.devServer.set('hot', true);
config.output.filename('[name].js');
config.output.publicPath('/logs');
config.externals([
'vue',
'vue-router'
]);
},
lintOnSave: true,
filenameHashing: false,
configureWebpack: {
plugins: [
new EventHooksPlugin({
done: () => {
if (process.env.NODE_ENV !== 'development') {
const buildDir = path.join(__dirname, '/dist');
fs.unlinkSync(`${buildDir}/index.html`);
}
},
}),
],
},
pluginOptions: {
vuetify: {
// https://github.com/vuetifyjs/vuetify-loader/tree/next/packages/vuetify-loader
}
}
};

102
k8s/dev/deployment.yaml Normal file
View File

@ -0,0 +1,102 @@
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: playground
annotations:
kubernetes.io/ingress.class: traefik
cert-manager.io/cluster-issuer: default
traefik.ingress.kubernetes.io/router.entrypoints: websecure
traefik.ingress.kubernetes.io/router.middlewares: traefik-sso@kubernetescrd
traefik.ingress.kubernetes.io/router.tls: "true"
external-dns.alpha.kubernetes.io/target: traefik.k-space.ee
spec:
rules:
- host: playground.k-space.ee
http:
paths:
- pathType: Prefix
path: "/"
backend:
service:
name: log-viewer-frontend
port:
number: 8080
- pathType: Prefix
path: "/events"
backend:
service:
name: log-viewer-backend
port:
number: 3002
tls:
- hosts:
- playground.k-space.ee
secretName: playground-tls
---
apiVersion: v1
kind: Service
metadata:
name: log-viewer-frontend
spec:
type: ClusterIP
selector:
app: log-viewer-frontend
ports:
- protocol: TCP
port: 8080
---
apiVersion: v1
kind: Service
metadata:
name: log-viewer-backend
spec:
type: ClusterIP
selector:
app: log-viewer-backend
ports:
- protocol: TCP
port: 3002
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: log-viewer-frontend
spec:
selector:
matchLabels:
app: log-viewer-frontend
template:
metadata:
labels:
app: log-viewer-frontend
spec:
containers:
- name: log-viewer-frontend
image: harbor.k-space.ee/playground/log-viewer-frontend
ports:
- containerPort: 8080
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: log-viewer-backend
spec:
selector:
matchLabels:
app: log-viewer-backend
template:
metadata:
labels:
app: log-viewer-backend
spec:
containers:
- name: log-viewer-backend
image: harbor.k-space.ee/playground/log-viewer-backend
ports:
- containerPort: 3002
env:
- name: MONGODB_HOST
valueFrom:
secretKeyRef:
name: mongodb-application-readwrite
key: connectionString.standard

102
k8s/staging/deployment.yaml Normal file
View File

@ -0,0 +1,102 @@
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: playground
annotations:
kubernetes.io/ingress.class: traefik
cert-manager.io/cluster-issuer: default
traefik.ingress.kubernetes.io/router.entrypoints: websecure
traefik.ingress.kubernetes.io/router.middlewares: traefik-sso@kubernetescrd
traefik.ingress.kubernetes.io/router.tls: "true"
external-dns.alpha.kubernetes.io/target: traefik.k-space.ee
spec:
rules:
- host: playground.k-space.ee
http:
paths:
- pathType: Prefix
path: "/"
backend:
service:
name: log-viewer-frontend
port:
number: 8080
- pathType: Prefix
path: "/events"
backend:
service:
name: log-viewer-backend
port:
number: 3002
tls:
- hosts:
- playground.k-space.ee
secretName: playground-tls
---
apiVersion: v1
kind: Service
metadata:
name: log-viewer-frontend
spec:
type: ClusterIP
selector:
app: log-viewer-frontend
ports:
- protocol: TCP
port: 8080
---
apiVersion: v1
kind: Service
metadata:
name: log-viewer-backend
spec:
type: ClusterIP
selector:
app: log-viewer-backend
ports:
- protocol: TCP
port: 3002
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: log-viewer-frontend
spec:
selector:
matchLabels:
app: log-viewer-frontend
template:
metadata:
labels:
app: log-viewer-frontend
spec:
containers:
- name: log-viewer-frontend
image: harbor.k-space.ee/playground/log-viewer-frontend
ports:
- containerPort: 8080
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: log-viewer-backend
spec:
selector:
matchLabels:
app: log-viewer-backend
template:
metadata:
labels:
app: log-viewer-backend
spec:
containers:
- name: log-viewer-backend
image: harbor.k-space.ee/playground/log-viewer-backend
ports:
- containerPort: 3002
env:
- name: MONGODB_HOST
valueFrom:
secretKeyRef:
name: mongodb-application-readwrite
key: connectionString.standard

56
skaffold.yaml Normal file
View File

@ -0,0 +1,56 @@
apiVersion: skaffold/v3alpha1
kind: Config
metadata:
name: log-viewer-backend
build:
artifacts:
- image: harbor.k-space.ee/playground/log-viewer-backend
context: backend
docker:
dockerfile: Dockerfile
- image: harbor.k-space.ee/playground/log-viewer-frontend
context: frontend
docker:
dockerfile: Dockerfile
deploy:
kubectl: {}
manifests:
rawYaml:
- k8s/staging/deployment.yaml
profiles:
- name: dev
activation:
- command: dev
build:
artifacts:
- image: harbor.k-space.ee/playground/log-viewer-backend
context: backend
docker:
target: dev
sync:
manual:
- src: '**/*.js'
dest: .
- image: harbor.k-space.ee/playground/log-viewer-frontend
context: frontend
docker:
target: dev
sync:
manual:
- src: 'src/**/*.vue'
dest: .
- src: 'src/**/*.js'
dest: .
- src: 'src/**/*.css'
dest: .
- src: 'src/**/*.svg'
dest: .
- src: 'index.html'
dest: .
manifests:
rawYaml:
- k8s/dev/deployment.yaml