Compare commits
53 Commits
disconnect
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
aefb7a84ca | ||
|
|
bdd0b9cfd0 | ||
| 4483898115 | |||
| d8eda00a1e | |||
|
|
0f3291266a | ||
| f9dc51770f | |||
| c0d6da48c9 | |||
| f07716b946 | |||
| d26cc1492d | |||
| 0a885f1750 | |||
|
|
92c2306b2a | ||
|
|
445c60bd65 | ||
| f701797639 | |||
|
|
d3df9d5a30 | ||
|
|
6a7ee244a2 | ||
| 3358b8ce93 | |||
| bbf30ec6f2 | |||
|
|
770942542f | ||
|
|
6b7b9f8f09 | ||
| 39e7bbd098 | |||
|
|
42366def40 | ||
| a9a94d6f3c | |||
| c071591d82 | |||
| 5792af21a5 | |||
|
|
5505e823c4 | ||
|
|
546bc6f754 | ||
|
|
dd7754e059 | ||
| 1d12e56093 | |||
| 36c8e40622 | |||
| ab4693acd7 | |||
|
|
fb65086adb | ||
|
|
55e2e9f6b9 | ||
| 6f0a2af15d | |||
| 2f7f865378 | |||
|
|
f4b9768729 | ||
|
|
7022ab738b | ||
| 03b9637e65 | |||
| a9c07b628d | |||
| 4a97efce6f | |||
|
|
c374ae1796 | ||
| fccf120950 | |||
| 98f5d3f39b | |||
|
|
654c78ff7c | ||
|
|
c043918fb8 | ||
|
|
a15e0c2d50 | ||
|
|
9a91f40c65 | ||
|
|
91edb27529 | ||
| 5332940a5b | |||
| c0b53decc2 | |||
|
|
1ee42cb01b | ||
| 102db7ae0c | |||
| e04da5b273 | |||
| 2cff1db5cb |
139
.gitignore
vendored
139
.gitignore
vendored
@@ -1,21 +1,120 @@
|
|||||||
.DS_Store
|
app.db
|
||||||
node_modules
|
# Byte-compiled / optimized / DLL files
|
||||||
/dist
|
|
||||||
|
|
||||||
# local env files
|
|
||||||
.env.local
|
|
||||||
.env.*.local
|
|
||||||
|
|
||||||
# Log files
|
|
||||||
npm-debug.log*
|
|
||||||
yarn-debug.log*
|
|
||||||
yarn-error.log*
|
|
||||||
|
|
||||||
# Editor directories and files
|
|
||||||
.idea
|
.idea
|
||||||
.vscode
|
__pycache__/
|
||||||
*.suo
|
node_modules/
|
||||||
*.ntvs*
|
frontend/node_modules/
|
||||||
*.njsproj
|
backend/uploads/*
|
||||||
*.sln
|
*.py[cod]
|
||||||
*.sw*
|
*$py.class
|
||||||
|
|
||||||
|
# C extensions
|
||||||
|
*.so
|
||||||
|
|
||||||
|
# Distribution / packaging
|
||||||
|
.Python
|
||||||
|
develop-eggs/
|
||||||
|
dist/
|
||||||
|
downloads/
|
||||||
|
eggs/
|
||||||
|
.eggs/
|
||||||
|
lib/
|
||||||
|
lib64/
|
||||||
|
parts/
|
||||||
|
sdist/
|
||||||
|
var/
|
||||||
|
wheels/
|
||||||
|
*.egg-info/
|
||||||
|
.installed.cfg
|
||||||
|
*.egg
|
||||||
|
MANIFEST
|
||||||
|
|
||||||
|
# PyInstaller
|
||||||
|
# Usually these files are written by a python script from a template
|
||||||
|
# before PyInstaller builds the exe, so as to inject date/other infos into it.
|
||||||
|
*.manifest
|
||||||
|
*.spec
|
||||||
|
|
||||||
|
# Installer logs
|
||||||
|
pip-log.txt
|
||||||
|
pip-delete-this-directory.txt
|
||||||
|
|
||||||
|
# Unit test / coverage reports
|
||||||
|
htmlcov/
|
||||||
|
.tox/
|
||||||
|
.nox/
|
||||||
|
.coverage
|
||||||
|
.coverage.*
|
||||||
|
.cache
|
||||||
|
nosetests.xml
|
||||||
|
coverage.xml
|
||||||
|
*.cover
|
||||||
|
.hypothesis/
|
||||||
|
.pytest_cache/
|
||||||
|
|
||||||
|
# Translations
|
||||||
|
*.mo
|
||||||
|
*.pot
|
||||||
|
|
||||||
|
# Django stuff:
|
||||||
|
*.log
|
||||||
|
local_settings.py
|
||||||
|
db.sqlite3
|
||||||
|
|
||||||
|
# Flask stuff:
|
||||||
|
instance/
|
||||||
|
.webassets-cache
|
||||||
|
|
||||||
|
# Scrapy stuff:
|
||||||
|
.scrapy
|
||||||
|
|
||||||
|
# Sphinx documentation
|
||||||
|
docs/_build/
|
||||||
|
|
||||||
|
# PyBuilder
|
||||||
|
target/
|
||||||
|
|
||||||
|
# Jupyter Notebook
|
||||||
|
.ipynb_checkpoints
|
||||||
|
|
||||||
|
# IPython
|
||||||
|
profile_default/
|
||||||
|
ipython_config.py
|
||||||
|
|
||||||
|
# pyenv
|
||||||
|
.python-version
|
||||||
|
|
||||||
|
# celery beat schedule file
|
||||||
|
celerybeat-schedule
|
||||||
|
|
||||||
|
# SageMath parsed files
|
||||||
|
*.sage.py
|
||||||
|
|
||||||
|
# Environments
|
||||||
|
.env
|
||||||
|
.venv
|
||||||
|
env/
|
||||||
|
venv/
|
||||||
|
ENV/
|
||||||
|
env.bak/
|
||||||
|
venv.bak/
|
||||||
|
|
||||||
|
# Spyder project settings
|
||||||
|
.spyderproject
|
||||||
|
.spyproject
|
||||||
|
|
||||||
|
# Rope project settings
|
||||||
|
.ropeproject
|
||||||
|
|
||||||
|
# mkdocs documentation
|
||||||
|
/site
|
||||||
|
|
||||||
|
# mypy
|
||||||
|
.mypy_cache/
|
||||||
|
.dmypy.json
|
||||||
|
dmypy.json
|
||||||
|
|
||||||
|
# Pyre type checker
|
||||||
|
.pyre/
|
||||||
|
|
||||||
|
package-lock.json
|
||||||
|
|||||||
BIN
2019-08-26-125627_1200x1920_scrot.png
Normal file
BIN
2019-08-26-125627_1200x1920_scrot.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 386 KiB |
@@ -1,4 +1,4 @@
|
|||||||
# vue_ts_test
|
# lrc frontend
|
||||||
|
|
||||||
## Project setup
|
## Project setup
|
||||||
```
|
```
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
module.exports = {
|
module.exports = {
|
||||||
presets: [
|
presets: [
|
||||||
'@vue/app'
|
'@vue/cli-plugin-babel/preset'
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
17803
package-lock.json
generated
17803
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
74
package.json
74
package.json
@@ -1,36 +1,64 @@
|
|||||||
{
|
{
|
||||||
"name": "vue_ts_test",
|
"name": "lrc",
|
||||||
"version": "0.1.0",
|
"version": "0.1.0",
|
||||||
"private": true,
|
"private": true,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"serve": "vue-cli-service serve",
|
"serve": "vue-cli-service serve",
|
||||||
"build": "vue-cli-service build",
|
"build": "vue-cli-service build",
|
||||||
"lint": "vue-cli-service lint",
|
"test:unit": "vue-cli-service test:unit",
|
||||||
"test:unit": "vue-cli-service test:unit"
|
"lint": "vue-cli-service lint"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@vue/cli": "^3.4.1",
|
"@fortawesome/fontawesome-svg-core": "^1.2.30",
|
||||||
"bootstrap-vue": "^2.0.0-rc.13",
|
"@fortawesome/free-brands-svg-icons": "^5.14.0",
|
||||||
"vue": "^2.6.6",
|
"@fortawesome/free-regular-svg-icons": "^5.14.0",
|
||||||
"vue-class-component": "^6.0.0",
|
"@fortawesome/free-solid-svg-icons": "^5.14.0",
|
||||||
|
"@fortawesome/vue-fontawesome": "^0.1.10",
|
||||||
|
"@vue/cli": "^4.4.6",
|
||||||
|
"axios": "^0.19.2",
|
||||||
|
"bootstrap": "^4.5.0",
|
||||||
|
"bootstrap-vue": "^2.16.0",
|
||||||
|
"core-js": "^3.6.5",
|
||||||
|
"i": "^0.3.6",
|
||||||
|
"is-ip": "^3.1.0",
|
||||||
|
"jquery": "^3.5.1",
|
||||||
|
"js-cookie": "^2.2.1",
|
||||||
|
"node-sass": "^4.14.1",
|
||||||
|
"npm": "^6.14.7",
|
||||||
|
"npm-sass": "^2.3.0",
|
||||||
|
"popper.js": "^1.16.1",
|
||||||
|
"sass": "^1.26.10",
|
||||||
|
"socket.io-client": "^2.3.0",
|
||||||
|
"vee-validate": "^3.3.7",
|
||||||
|
"vue": "^2.6.11",
|
||||||
|
"vue-axios": "^2.1.5",
|
||||||
|
"vue-class-component": "^7.2.5",
|
||||||
|
"vue-cookies": "^1.7.3",
|
||||||
"vue-flag-icon": "^1.0.6",
|
"vue-flag-icon": "^1.0.6",
|
||||||
"vue-i18n": "^8.9.0",
|
"vue-i18n": "^8.20.0",
|
||||||
"vue-property-decorator": "^7.0.0",
|
"vue-moment": "^4.1.0",
|
||||||
"vue-router": "^3.0.1",
|
"vue-property-decorator": "^9.0.0",
|
||||||
"vuex": "^3.0.1"
|
"vue-router": "^3.3.4",
|
||||||
|
"vue-socket.io-extended": "^4.0.4",
|
||||||
|
"vue-spinner": "^1.0.4",
|
||||||
|
"vue-sweetalert2": "^3.0.6",
|
||||||
|
"vuejs-logger": "^1.5.4",
|
||||||
|
"vuex": "^3.5.1",
|
||||||
|
"vuex-persistedstate": "^3.0.1",
|
||||||
|
"vuex-typex": "^3.1.5"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/chai": "^4.1.0",
|
"@types/chai": "^4.2.12",
|
||||||
"@types/mocha": "^5.2.4",
|
"@types/mocha": "^8.0.0",
|
||||||
"@vue/cli-plugin-babel": "^3.4.0",
|
"@vue/cli-plugin-babel": "^4.4.6",
|
||||||
"@vue/cli-plugin-typescript": "^3.4.0",
|
"@vue/cli-plugin-typescript": "^4.4.6",
|
||||||
"@vue/cli-plugin-unit-mocha": "^3.4.0",
|
"@vue/cli-plugin-unit-mocha": "^4.4.6",
|
||||||
"@vue/cli-service": "^3.4.0",
|
"@vue/cli-service": "^4.4.6",
|
||||||
"@vue/test-utils": "^1.0.0-beta.20",
|
"@vue/test-utils": "^1.0.3",
|
||||||
"chai": "^4.1.2",
|
"bootstrap-sass": "^3.4.1",
|
||||||
"node-sass": "^4.9.0",
|
"chai": "^4.2.0",
|
||||||
"sass-loader": "^7.1.0",
|
"sass-loader": "^9.0.2",
|
||||||
"typescript": "^3.0.0",
|
"typescript": "^3.9.7",
|
||||||
"vue-template-compiler": "^2.5.21"
|
"vue-template-compiler": "^2.6.11"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,6 +5,9 @@
|
|||||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||||
<meta name="viewport" content="width=device-width,initial-scale=1.0">
|
<meta name="viewport" content="width=device-width,initial-scale=1.0">
|
||||||
<link rel="icon" href="<%= BASE_URL %>favicon.ico">
|
<link rel="icon" href="<%= BASE_URL %>favicon.ico">
|
||||||
|
<style>
|
||||||
|
a { cursor: pointer; }
|
||||||
|
</style>
|
||||||
<title>vue_ts_test</title>
|
<title>vue_ts_test</title>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
|
|||||||
0
src/@types/vue-native-websocket/index.d.ts
vendored
Normal file
0
src/@types/vue-native-websocket/index.d.ts
vendored
Normal file
312
src/App.vue
312
src/App.vue
@@ -1,29 +1,327 @@
|
|||||||
<template>
|
<template>
|
||||||
<div id="app">
|
<div id="app">
|
||||||
|
<div id="bg">
|
||||||
|
<img src="./assets/lens.jpg" alt="">
|
||||||
|
</div>
|
||||||
|
<sync-loader :loading="isLoading"></sync-loader>
|
||||||
|
<!--
|
||||||
|
<b-alert
|
||||||
|
:show="dismissCountDown"
|
||||||
|
dismissible
|
||||||
|
variant="danger"
|
||||||
|
@dismissed="dismissCountDown=0"
|
||||||
|
@dismiss-count-down="countDownChanged"
|
||||||
|
>
|
||||||
|
{{alertMessage}}
|
||||||
|
<b-progress
|
||||||
|
variant="error"
|
||||||
|
:max="dismissSecs"
|
||||||
|
:value="dismissCountDown"
|
||||||
|
height="4px"
|
||||||
|
></b-progress>
|
||||||
|
</b-alert>
|
||||||
|
<b-alert
|
||||||
|
:show="messageDismissCountDown"
|
||||||
|
dismissible
|
||||||
|
variant="info"
|
||||||
|
@dismissed="messageDismissCountDown=0"
|
||||||
|
@dismiss-count-down="messageCountDownChanged"
|
||||||
|
>
|
||||||
|
{{message}}
|
||||||
|
<b-progress
|
||||||
|
variant="info"
|
||||||
|
:max="messageDismissSecs"
|
||||||
|
:value="messageDismissCountDown"
|
||||||
|
height="4px"
|
||||||
|
></b-progress>
|
||||||
|
</b-alert>-->
|
||||||
|
|
||||||
<div id="nav">
|
<div id="nav">
|
||||||
<router-link to="/">Home</router-link> |
|
<b-navbar toggleable="lg" type="dark" variant="dark">
|
||||||
<router-link to="/about">About</router-link>
|
<b-navbar-brand to="/">
|
||||||
|
<img src="https://placekitten.com/g/30/30" class="d-inline-block align-top" alt="Kitten">
|
||||||
|
LRC - <strong>L</strong>ecture <strong>R</strong>ecord <strong>C</strong>ontrol
|
||||||
|
</b-navbar-brand>
|
||||||
|
|
||||||
|
<b-navbar-toggle target="nav-collapse"></b-navbar-toggle>
|
||||||
|
|
||||||
|
<b-collapse id="nav-collapse" is-nav>
|
||||||
|
<b-navbar-nav>
|
||||||
|
<b-nav-item to="/about">About</b-nav-item>
|
||||||
|
|
||||||
|
<b-nav-item v-if="authenticated" :to="{name: 'rooms'}">{{ $t('Rooms') }}</b-nav-item>
|
||||||
|
<b-nav-item v-if="authenticated" :to="{name: 'recorders'}">{{ $t('Recorders') }}</b-nav-item>
|
||||||
|
<b-nav-item v-if="authenticated" :to="{name: 'commands'}">{{ $t('Commands') }}</b-nav-item>
|
||||||
|
<b-nav-item :to="{name: 'test'}">Test</b-nav-item>
|
||||||
|
</b-navbar-nav>
|
||||||
|
|
||||||
|
<!-- Right aligned nav items -->
|
||||||
|
<b-navbar-nav class="ml-auto">
|
||||||
|
<b-nav-form>
|
||||||
|
<b-form-input size="sm" class="mr-sm-2" placeholder="Search"></b-form-input>
|
||||||
|
<b-button size="sm" class="my-2 my-sm-0" type="submit">Search</b-button>
|
||||||
|
</b-nav-form>
|
||||||
|
|
||||||
|
<b-nav-item-dropdown split split-to="admin" v-if="authenticated" variant="outline-danger" :text="$t('admin')">
|
||||||
|
<b-dropdown-item :to="{name: 'admin.users'}">{{ $t('users') }}</b-dropdown-item>
|
||||||
|
<b-dropdown-item :to="{name: 'admin.group'}">{{ $t('groups') }}</b-dropdown-item>
|
||||||
|
<b-dropdown-item :to="{name: 'admin.permission'}">{{ $t('permissions') }}</b-dropdown-item>
|
||||||
|
<b-dropdown-item href="#">Something else here...</b-dropdown-item>
|
||||||
|
</b-nav-item-dropdown>
|
||||||
|
|
||||||
|
<b-nav-item-dropdown text="Lang" right>
|
||||||
|
<b-dropdown-item v-for="(lang, i) in langs"
|
||||||
|
v-bind:key="lang"
|
||||||
|
@click="setLang(lang)">{{ lang }}
|
||||||
|
</b-dropdown-item>
|
||||||
|
</b-nav-item-dropdown>
|
||||||
|
|
||||||
|
<b-nav-item-dropdown v-if="authenticated" right>
|
||||||
|
<!-- Using 'button-content' slot -->
|
||||||
|
<template slot="button-content"><em>User</em></template>
|
||||||
|
<b-dropdown-item :to="{name: 'profile'}">Profile</b-dropdown-item>
|
||||||
|
<b-dropdown-item to="/logout" @click.prevent="logout()">Sign Out</b-dropdown-item>
|
||||||
|
</b-nav-item-dropdown>
|
||||||
|
<b-nav-item v-else to="/login">Login</b-nav-item>
|
||||||
|
</b-navbar-nav>
|
||||||
|
</b-collapse>
|
||||||
|
</b-navbar>
|
||||||
|
|
||||||
|
<span v-if="tokenValidity">({{$t('Session_timeout_in')}}:{{tokenValidity}})</span>
|
||||||
|
<span v-else>{{$t('Session_expired')}}: <a v-if="refreshTokenValidity" href="~"
|
||||||
|
@click.prevent="refreshToken()">{{$t('Click here to refresh session')}}</a></span>
|
||||||
|
<span v-if="refreshTokenValidity"> | ({{refreshTokenValidity}})
|
||||||
|
<input type="checkbox" id="auto_renew_cb" v-model="autoRenewSession">
|
||||||
|
<label for="auto_renew_cb">{{$t('auto_renew_session')}}</label>
|
||||||
|
</span>
|
||||||
|
<span v-else>{{$t('Can\'t renew session – please login again!')}}</span>
|
||||||
</div>
|
</div>
|
||||||
<router-view/>
|
<div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
<div id="content_frame">
|
||||||
|
<router-view :style="main_style"/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import {EventBus, getRemainingJwtValiditySeconds} from '@/utils';
|
||||||
|
import { localize } from 'vee-validate';
|
||||||
|
import SyncLoader from 'vue-spinner/src/SyncLoader.vue';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
components: {
|
||||||
|
SyncLoader,
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
isLoading: false,
|
||||||
|
tokenValidity: -1,
|
||||||
|
refreshFailed: false,
|
||||||
|
refreshTokenValidity: -1,
|
||||||
|
showAlert: true,
|
||||||
|
alertMessage: 'NO MESSAGE PROVIDED',
|
||||||
|
message: 'NO MESSAGE PROVIDED',
|
||||||
|
langs: ['de', 'en', 'es'],
|
||||||
|
dismissSecs: 5,
|
||||||
|
dismissCountDown: 0,
|
||||||
|
messageDismissSecs: 5,
|
||||||
|
messageDismissCountDown: 0,
|
||||||
|
autoRenewSession: true,
|
||||||
|
main_style: {}
|
||||||
|
};
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
showErrorMessage(msg) {
|
||||||
|
this.isLoading = false;
|
||||||
|
this.dismissCountDown = this.dismissSecs;
|
||||||
|
this.alertMessage = msg;
|
||||||
|
this.makeToast(msg, 'error')
|
||||||
|
},
|
||||||
|
countDownChanged(dismissCountDown) {
|
||||||
|
this.dismissCountDown = dismissCountDown;
|
||||||
|
},
|
||||||
|
showMessage(msg, dismissTime=null) {
|
||||||
|
this.isLoading = false;
|
||||||
|
if(dismissTime == null) this.messageDismissCountDown = this.messageDismissSecs;
|
||||||
|
else {
|
||||||
|
this.messageDismissSecs = dismissTime;
|
||||||
|
this.messageDismissCountDown = dismissTime;
|
||||||
|
}
|
||||||
|
if(typeof msg === 'object' && msg !== null){
|
||||||
|
if("time" in msg) {
|
||||||
|
this.messageDismissCountDown = msg['time'];
|
||||||
|
this.messageDismissSecs = msg['time'];
|
||||||
|
}
|
||||||
|
if("msg" in msg) msg = msg['msg'];
|
||||||
|
}
|
||||||
|
this.message = msg;
|
||||||
|
this.makeToast(msg, 'info')
|
||||||
|
},
|
||||||
|
messageCountDownChanged(dismissCountDown) {
|
||||||
|
this.messageDismissCountDown = dismissCountDown;
|
||||||
|
},
|
||||||
|
|
||||||
|
logout() {
|
||||||
|
this.$store.dispatch('logout', {revokeRefreshToken: true});
|
||||||
|
this.$router.push({name: 'home'});
|
||||||
|
},
|
||||||
|
refreshToken() {
|
||||||
|
this.$store.dispatch('refreshToken');
|
||||||
|
},
|
||||||
|
setLang(lang) {
|
||||||
|
this.$i18n.locale=lang;
|
||||||
|
localize(lang);
|
||||||
|
},
|
||||||
|
makeToast(message, variant = null, hide_time=5000, title = null) {
|
||||||
|
if(title == null){
|
||||||
|
let now = new Date();
|
||||||
|
if(variant == null){
|
||||||
|
title = this.$t('message from') + " " + now.getHours() + ":" + now.getMinutes() + ":" + now.getSeconds();
|
||||||
|
} else {
|
||||||
|
title = this.$t(variant) + " (" + now.getHours() + ":" + now.getMinutes() + ":" + now.getSeconds() + ")";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if(variant === 'error') variant = 'danger';
|
||||||
|
this.$bvToast.toast(message, {
|
||||||
|
title: title,
|
||||||
|
toaster: 'b-toaster-bottom-right',
|
||||||
|
autoHideDelay: hide_time,
|
||||||
|
variant: variant,
|
||||||
|
appendToast: false
|
||||||
|
})
|
||||||
|
},
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
EventBus.$on('failedLoadingRecorders', (msg) => {
|
||||||
|
this.showErrorMessage(msg);
|
||||||
|
});
|
||||||
|
EventBus.$on('failedLoadingRooms', (msg) => {
|
||||||
|
this.showErrorMessage(msg);
|
||||||
|
});
|
||||||
|
EventBus.$on('failedRefreshingToken', (msg) => {
|
||||||
|
this.refreshFailed = true;
|
||||||
|
});
|
||||||
|
EventBus.$on('errorMessage', (msg)=> {
|
||||||
|
this.showErrorMessage(msg);
|
||||||
|
})
|
||||||
|
EventBus.$on('normalMessage', (msg)=> {
|
||||||
|
this.showMessage(msg);
|
||||||
|
})
|
||||||
|
this.$nextTick(() => {
|
||||||
|
window.setInterval(() => {
|
||||||
|
const tokenValidity = getRemainingJwtValiditySeconds(this.$store.state.access_token);
|
||||||
|
|
||||||
|
// this.tokenValidity = this.tokenValidity.format('mm:ss');
|
||||||
|
const refreshTokenValidity = getRemainingJwtValiditySeconds(this.$store.state.refresh_token);
|
||||||
|
|
||||||
|
if (tokenValidity < 50 && refreshTokenValidity > 30 && this.autoRenewSession && !this.refreshFailed) {
|
||||||
|
this.$store.dispatch('refreshToken'); // renew access token
|
||||||
|
}
|
||||||
|
if (this.autoRenewSession && this.refreshFailed) {
|
||||||
|
this.$store.dispatch('resetToken'); // delete all token info if refresh fails
|
||||||
|
this.$router.push({name: 'login'});
|
||||||
|
this.refreshFailed = false;
|
||||||
|
}
|
||||||
|
if (isNaN(tokenValidity)) {
|
||||||
|
this.tokenValidity = false;
|
||||||
|
} else {
|
||||||
|
this.tokenValidity = new Date(1000 * tokenValidity).toISOString().substr(14, 5);
|
||||||
|
}
|
||||||
|
if (isNaN(tokenValidity)) {
|
||||||
|
this.refreshTokenValidity = false;
|
||||||
|
} else {
|
||||||
|
this.refreshTokenValidity = new Date(1000 * refreshTokenValidity).toISOString().substr(11, 8);
|
||||||
|
}
|
||||||
|
|
||||||
|
}, 1000);
|
||||||
|
});
|
||||||
|
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
authenticated() {
|
||||||
|
return this.$store.getters.isAuthenticated;
|
||||||
|
},
|
||||||
|
profile() {
|
||||||
|
return this.$store.state.profile;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
|
@import '../node_modules/bootstrap/scss/bootstrap.scss';
|
||||||
|
|
||||||
#app {
|
#app {
|
||||||
font-family: 'Avenir', Helvetica, Arial, sans-serif;
|
font-family: 'Avenir', Helvetica, Arial, sans-serif;
|
||||||
-webkit-font-smoothing: antialiased;
|
-webkit-font-smoothing: antialiased;
|
||||||
-moz-osx-font-smoothing: grayscale;
|
-moz-osx-font-smoothing: grayscale;
|
||||||
text-align: center;
|
color: #385875;
|
||||||
color: #2c3e50;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#bg {
|
||||||
|
z-index: -100;
|
||||||
|
position: fixed;
|
||||||
|
top: -50%;
|
||||||
|
left: -50%;
|
||||||
|
width: 200%;
|
||||||
|
height: 200%;
|
||||||
|
}
|
||||||
|
#bg img {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
margin: auto;
|
||||||
|
min-width: 50%;
|
||||||
|
min-height: 50%;
|
||||||
|
}
|
||||||
|
|
||||||
|
h1 {
|
||||||
|
color: #275d37;
|
||||||
|
}
|
||||||
|
|
||||||
#nav {
|
#nav {
|
||||||
padding: 30px;
|
padding: 0px;
|
||||||
|
text-align: center;
|
||||||
|
|
||||||
a {
|
a {
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
color: #2c3e50;
|
color: #7ea8d6;
|
||||||
|
|
||||||
&.router-link-exact-active {
|
&.router-link-exact-active {
|
||||||
color: #42b983;
|
color: #42b983;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Absolute Center Spinner */
|
||||||
|
.v-spinner {
|
||||||
|
position: fixed;
|
||||||
|
z-index: 999;
|
||||||
|
// height: 2em;
|
||||||
|
// width: 2em;
|
||||||
|
overflow: visible;
|
||||||
|
margin: auto;
|
||||||
|
top: 50%;
|
||||||
|
left: 50%;
|
||||||
|
bottom: 0;
|
||||||
|
right: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Transparent Overlay */
|
||||||
|
.v-spinner:before {
|
||||||
|
content: '';
|
||||||
|
display: block;
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
background-color: rgba(0, 0, 0, 0.2);
|
||||||
|
}
|
||||||
|
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
80
src/api/Repository.js
Normal file
80
src/api/Repository.js
Normal file
@@ -0,0 +1,80 @@
|
|||||||
|
// Repository.js
|
||||||
|
|
||||||
|
import Vue from "vue";
|
||||||
|
import axios from "axios";
|
||||||
|
import store from "@/store";
|
||||||
|
|
||||||
|
//const baseDomain = "http://localhost:5443";
|
||||||
|
const baseDomain = "";
|
||||||
|
const API_URL = `${baseDomain}/api/v1`;
|
||||||
|
|
||||||
|
const api = axios.create({
|
||||||
|
baseURL: API_URL,
|
||||||
|
});
|
||||||
|
|
||||||
|
api.interceptors.request.use(async function(config) {
|
||||||
|
if (store.getters.isAuthenticated) {
|
||||||
|
const token = store.state.access_token;
|
||||||
|
config.headers.Authorization = `Bearer ${token}`;
|
||||||
|
} else {
|
||||||
|
if(store.getters.isRefreshTokenValid){
|
||||||
|
await store.dispatch('refreshToken');
|
||||||
|
const token = store.state.access_token;
|
||||||
|
config.headers.Authorization = `Bearer ${token}`;
|
||||||
|
/*store.dispatch('refreshToken').then( () => {
|
||||||
|
const token = store.state.access_token;
|
||||||
|
config.headers.Authorization = `Bearer ${token}`;
|
||||||
|
}).catch( () => {
|
||||||
|
window.location = '/login';
|
||||||
|
});*/
|
||||||
|
} else {
|
||||||
|
Vue.swal({
|
||||||
|
title: "Session Expired",
|
||||||
|
text: "Your token/session has expired. Would you like to be redirected to the login page?",
|
||||||
|
type: "warning",
|
||||||
|
showCancelButton: true,
|
||||||
|
confirmButtonColor: "#DD6B55",
|
||||||
|
confirmButtonText: "Yes",
|
||||||
|
}).then( (result) => {
|
||||||
|
if(result.value) {
|
||||||
|
window.location = '/login';
|
||||||
|
} else {
|
||||||
|
window.location = '/';
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return config;
|
||||||
|
});
|
||||||
|
|
||||||
|
api.interceptors.response.use(function (response) {
|
||||||
|
return response;
|
||||||
|
}, function (error) {
|
||||||
|
if (401 === error.response.status || 422 === error.response.status) {
|
||||||
|
if(store.getters.isAuthenticated){ // if 401 even though the token is valid, the user might have been deleted, etc.
|
||||||
|
store.dispatch('resetToken');
|
||||||
|
}
|
||||||
|
Vue.swal({
|
||||||
|
title: "Session Expired",
|
||||||
|
text: "Your token/session has expired. Would you like to be redirected to the login page?",
|
||||||
|
type: "warning",
|
||||||
|
showCancelButton: true,
|
||||||
|
confirmButtonColor: "#DD6B55",
|
||||||
|
confirmButtonText: "Yes",
|
||||||
|
}).then( (result) => {
|
||||||
|
if(result.value) {
|
||||||
|
window.location = '/login';
|
||||||
|
} else {
|
||||||
|
window.location = '/';
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
//console.error(error);
|
||||||
|
//console.error(error.response);
|
||||||
|
console.error(error.response.data.message);
|
||||||
|
//return Promise.reject(error);
|
||||||
|
return Promise.reject(error.response);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
export default api;
|
||||||
42
src/api/RepositoryFactory.ts
Normal file
42
src/api/RepositoryFactory.ts
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
// RepositoryFactory.js
|
||||||
|
|
||||||
|
import GroupRepository from './groupRepository';
|
||||||
|
import UserRepository from './userRepository';
|
||||||
|
import PermissionRepository from './permissionsRepository';
|
||||||
|
|
||||||
|
import RoomRepository from './roomRepository';
|
||||||
|
import RecorderRepository from './recorderRepository';
|
||||||
|
import CommandRepository from './commandRepository';
|
||||||
|
|
||||||
|
import ControlRepository from './controlRepository';
|
||||||
|
|
||||||
|
|
||||||
|
export default function get(name: string) {
|
||||||
|
switch (name) {
|
||||||
|
case 'group': {
|
||||||
|
return GroupRepository;
|
||||||
|
}
|
||||||
|
case 'recorder': {
|
||||||
|
return RecorderRepository;
|
||||||
|
}
|
||||||
|
case 'room': {
|
||||||
|
return RoomRepository;
|
||||||
|
}
|
||||||
|
case 'user': {
|
||||||
|
return UserRepository;
|
||||||
|
}
|
||||||
|
case 'permission': {
|
||||||
|
return PermissionRepository;
|
||||||
|
}
|
||||||
|
case 'command': {
|
||||||
|
return CommandRepository;
|
||||||
|
}
|
||||||
|
case 'control': {
|
||||||
|
return ControlRepository;
|
||||||
|
}
|
||||||
|
default: {
|
||||||
|
// statements;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
30
src/api/commandRepository.ts
Normal file
30
src/api/commandRepository.ts
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
// groupRepository.ts
|
||||||
|
|
||||||
|
// @ts-ignore
|
||||||
|
import Repository from './Repository';
|
||||||
|
|
||||||
|
const commandResource = '/virtual_command';
|
||||||
|
|
||||||
|
import {dictEmptyValToNull} from '@/utils';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
getCommands() {
|
||||||
|
return Repository.get(`${commandResource}`);
|
||||||
|
},
|
||||||
|
|
||||||
|
getCommand(commandId: number) {
|
||||||
|
return Repository.get(`${commandResource}/${commandId}`);
|
||||||
|
},
|
||||||
|
|
||||||
|
deleteCommand(commandId: number) {
|
||||||
|
return Repository.delete(`${commandResource}/${commandId}`);
|
||||||
|
},
|
||||||
|
|
||||||
|
createCommand(commandData: any) {
|
||||||
|
return Repository.post(`${commandResource}`, dictEmptyValToNull(commandData));
|
||||||
|
},
|
||||||
|
|
||||||
|
updateCommand(commandId: number, commandData: any) {
|
||||||
|
return Repository.put(`${commandResource}/${commandId}`, dictEmptyValToNull(commandData));
|
||||||
|
},
|
||||||
|
};
|
||||||
16
src/api/controlRepository.ts
Normal file
16
src/api/controlRepository.ts
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
// groupRepository.ts
|
||||||
|
|
||||||
|
// @ts-ignore
|
||||||
|
import Repository from './Repository';
|
||||||
|
|
||||||
|
const commandResource = '/control';
|
||||||
|
|
||||||
|
import {dictEmptyValToNull} from '@/utils';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
runRecorderCommand(recorderId: any, commandId: any, parameters: any) {
|
||||||
|
return Repository.post(`${commandResource}?recorder_id=${recorderId}&command_id=${commandId}`,
|
||||||
|
{parameters: dictEmptyValToNull(parameters)});
|
||||||
|
},
|
||||||
|
|
||||||
|
};
|
||||||
20
src/api/groupRepository.ts
Normal file
20
src/api/groupRepository.ts
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
// groupRepository.js
|
||||||
|
|
||||||
|
// @ts-ignore
|
||||||
|
import Repository from './Repository';
|
||||||
|
|
||||||
|
const resource = '/group';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
getGroups() {
|
||||||
|
return Repository.get(`${resource}`);
|
||||||
|
},
|
||||||
|
|
||||||
|
getGroup(groupId: number) {
|
||||||
|
return Repository.get(`${resource}/${groupId}`);
|
||||||
|
},
|
||||||
|
|
||||||
|
createGroup(groupData: any) {
|
||||||
|
return Repository.post(`${resource}`, groupData);
|
||||||
|
},
|
||||||
|
};
|
||||||
69
src/api/index.ts
Normal file
69
src/api/index.ts
Normal file
@@ -0,0 +1,69 @@
|
|||||||
|
import Vue from 'vue';
|
||||||
|
import axios from 'axios';
|
||||||
|
|
||||||
|
import { API_URL } from '@/main.ts'
|
||||||
|
|
||||||
|
export function fetchSurveys() {
|
||||||
|
return axios.get(`${API_URL}/surveys/`);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function fetchSurvey(surveyId: string) {
|
||||||
|
return axios.get(`${API_URL}/surveys/${surveyId}/`);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function saveSurveyResponse(surveyResponse: any) {
|
||||||
|
return axios.put(`${API_URL}/surveys/${surveyResponse.id}/`, surveyResponse);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function postNewSurvey(survey: any, jwt: any) {
|
||||||
|
return axios.post(`${API_URL}/surveys/`, survey, { headers: { Authorization: `Bearer ${jwt}` } });
|
||||||
|
}
|
||||||
|
|
||||||
|
export function authenticate(userData: any) {
|
||||||
|
return axios.post(`${API_URL}/auth/login`, userData);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function revokeRefreshKey(jwt: any) {
|
||||||
|
//return axios.post(`${API_URL}/auth/revokeRefreshToken`, { headers: { Authorization: `Bearer ${jwt}` } });
|
||||||
|
return axios.get(`${API_URL}/auth/revokeRefreshToken`, { headers: { Authorization: `Bearer ${jwt}` } });
|
||||||
|
}
|
||||||
|
|
||||||
|
export function register(userData: any) {
|
||||||
|
return axios.post(`${API_URL}/auth/v1/register/`, userData);
|
||||||
|
}
|
||||||
|
|
||||||
|
// This function probably isn't really useful here, as a user must be redirected to the actual OIDC provider.
|
||||||
|
// -> So login involves user interaction through the frontend (and not just API calls).
|
||||||
|
export function oidc_login(redirectionUrl: any) {
|
||||||
|
return axios.get(`${API_URL}/auth/oidc`, redirectionUrl);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getFreshToken(refreshToken: any) {
|
||||||
|
return axios.get(`${API_URL}/auth/refresh`, { headers: { Authorization: `Bearer ${refreshToken}` } });
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getProviders() {
|
||||||
|
return axios.get(`${API_URL}/auth/providers`);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function fetchUsers(jwt: any) {
|
||||||
|
return axios.get(`${API_URL}/v1/user`, { headers: { Authorization: `Bearer ${jwt}` } });
|
||||||
|
}
|
||||||
|
|
||||||
|
export function createUser(jwt: any, userData: any) {
|
||||||
|
return axios.post(`${API_URL}/v1/user`, userData, { headers: { Authorization: `Bearer ${jwt}` } });
|
||||||
|
}
|
||||||
|
|
||||||
|
export function fetchUserGroups(jwt: any) {
|
||||||
|
return axios.get(`${API_URL}/v1/group`, { headers: { Authorization: `Bearer ${jwt}` } });
|
||||||
|
}
|
||||||
|
|
||||||
|
export function fetchUserGroup(jwt: any, groupId: any) {
|
||||||
|
return axios.get(`${API_URL}/v1/group/${groupId}`, { headers: { Authorization: `Bearer ${jwt}` } });
|
||||||
|
}
|
||||||
|
|
||||||
|
export function fetchProfile(jwt: any) {
|
||||||
|
return axios.get(`${API_URL}/v1/user/profile`, { headers: { Authorization: `Bearer ${jwt}` } });
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
20
src/api/permissionsRepository.ts
Normal file
20
src/api/permissionsRepository.ts
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
// groupRepository.js
|
||||||
|
|
||||||
|
// @ts-ignore
|
||||||
|
import Repository from './Repository';
|
||||||
|
|
||||||
|
const resource = '/permissions';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
getPermissions() {
|
||||||
|
return Repository.get(`${resource}`);
|
||||||
|
},
|
||||||
|
|
||||||
|
getGroup(permissionId: number) {
|
||||||
|
return Repository.get(`${resource}/${permissionId}`);
|
||||||
|
},
|
||||||
|
|
||||||
|
createGroup(permissionData: any) {
|
||||||
|
return Repository.post(`${resource}`, permissionData);
|
||||||
|
},
|
||||||
|
};
|
||||||
75
src/api/recorderRepository.ts
Normal file
75
src/api/recorderRepository.ts
Normal file
@@ -0,0 +1,75 @@
|
|||||||
|
// groupRepository.ts
|
||||||
|
|
||||||
|
// @ts-ignore
|
||||||
|
import Repository from './Repository';
|
||||||
|
|
||||||
|
const recorderResource = '/recorder';
|
||||||
|
const recorderModelResource = '/recorder/model';
|
||||||
|
const recorderCommandResource = '/recorder/command';
|
||||||
|
|
||||||
|
import {dictEmptyValToNull} from '@/utils';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
getRecorders() {
|
||||||
|
return Repository.get(`${recorderResource}`);
|
||||||
|
},
|
||||||
|
|
||||||
|
getRecorder(recorderId: number) {
|
||||||
|
return Repository.get(`${recorderResource}/${recorderId}`);
|
||||||
|
},
|
||||||
|
|
||||||
|
deleteRecorder(recorderId: number) {
|
||||||
|
return Repository.delete(`${recorderResource}/${recorderId}`);
|
||||||
|
},
|
||||||
|
|
||||||
|
createRecorder(recorderData: any) {
|
||||||
|
return Repository.post(`${recorderResource}`, dictEmptyValToNull(recorderData));
|
||||||
|
},
|
||||||
|
|
||||||
|
updateRecorder(recorderId: number, recorderData: any) {
|
||||||
|
return Repository.put(`${recorderResource}/${recorderId}`, dictEmptyValToNull(recorderData));
|
||||||
|
},
|
||||||
|
|
||||||
|
|
||||||
|
getRecorderModels() {
|
||||||
|
return Repository.get(`${recorderModelResource}`);
|
||||||
|
},
|
||||||
|
|
||||||
|
getRecorderModel(recorderModelId: number) {
|
||||||
|
return Repository.get(`${recorderModelResource}/${recorderModelId}`);
|
||||||
|
},
|
||||||
|
|
||||||
|
deleteRecorderModel(recorderModelId: number) {
|
||||||
|
return Repository.delete(`${recorderModelResource}/${recorderModelId}`);
|
||||||
|
},
|
||||||
|
|
||||||
|
createRecorderModel(recorderModelData: any) {
|
||||||
|
return Repository.post(`${recorderModelResource}`, dictEmptyValToNull(recorderModelData));
|
||||||
|
},
|
||||||
|
|
||||||
|
updateRecorderModel(recorderModelId: number, recorderModelData: any) {
|
||||||
|
return Repository.put(`${recorderModelResource}/${recorderModelId}`, dictEmptyValToNull(recorderModelData));
|
||||||
|
},
|
||||||
|
|
||||||
|
|
||||||
|
getRecorderCommands() {
|
||||||
|
return Repository.get(`${recorderCommandResource}`);
|
||||||
|
},
|
||||||
|
|
||||||
|
getRecorderCommand(recorderCommandId: number) {
|
||||||
|
return Repository.get(`${recorderCommandResource}/${recorderCommandId}`);
|
||||||
|
},
|
||||||
|
|
||||||
|
deleteRecorderCommand(recorderCommandId: number) {
|
||||||
|
return Repository.delete(`${recorderCommandResource}/${recorderCommandId}`);
|
||||||
|
},
|
||||||
|
|
||||||
|
createRecorderCommand(recorderCommandData: any) {
|
||||||
|
return Repository.post(`${recorderCommandResource}`, dictEmptyValToNull(recorderCommandData));
|
||||||
|
},
|
||||||
|
|
||||||
|
updateRecorderCommand(recorderCommandId: number, recorderCommandData: any) {
|
||||||
|
return Repository.put(`${recorderCommandResource}/${recorderCommandId}`,
|
||||||
|
dictEmptyValToNull(recorderCommandData));
|
||||||
|
},
|
||||||
|
};
|
||||||
29
src/api/roomRepository.ts
Normal file
29
src/api/roomRepository.ts
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
// groupRepository.ts
|
||||||
|
|
||||||
|
// @ts-ignore
|
||||||
|
import Repository from './Repository';
|
||||||
|
import {dictEmptyValToNull} from '@/utils';
|
||||||
|
|
||||||
|
const resource = '/room';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
getRooms() {
|
||||||
|
return Repository.get(`${resource}`);
|
||||||
|
},
|
||||||
|
|
||||||
|
getRoom(roomId: number) {
|
||||||
|
return Repository.get(`${resource}/${roomId}`);
|
||||||
|
},
|
||||||
|
|
||||||
|
deleteRoom(roomId: number) {
|
||||||
|
return Repository.delete(`${resource}/${roomId}`);
|
||||||
|
},
|
||||||
|
|
||||||
|
createRoom(roomData: any) {
|
||||||
|
return Repository.post(`${resource}`, dictEmptyValToNull(roomData));
|
||||||
|
},
|
||||||
|
|
||||||
|
updateRoom(roomId: number, roomData: any) {
|
||||||
|
return Repository.put(`${resource}/${roomId}`, dictEmptyValToNull(roomData));
|
||||||
|
},
|
||||||
|
};
|
||||||
17
src/api/stateRepository.ts
Normal file
17
src/api/stateRepository.ts
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
// groupRepository.ts
|
||||||
|
|
||||||
|
// @ts-ignore
|
||||||
|
import Repository from './Repository';
|
||||||
|
|
||||||
|
const recorderStateResource = '/state/recorder';
|
||||||
|
|
||||||
|
|
||||||
|
export default {
|
||||||
|
getRecordersStates() {
|
||||||
|
return Repository.get(`${recorderStateResource}`);
|
||||||
|
},
|
||||||
|
|
||||||
|
getRecorderState(recorderId: number) {
|
||||||
|
return Repository.get(`${recorderStateResource}/${recorderId}`);
|
||||||
|
},
|
||||||
|
};
|
||||||
41
src/api/userRepository.ts
Normal file
41
src/api/userRepository.ts
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
// groupRepository.ts
|
||||||
|
|
||||||
|
// @ts-ignore
|
||||||
|
import Repository from './Repository';
|
||||||
|
import Vue from 'vue/types/vue';
|
||||||
|
import axios from 'axios';
|
||||||
|
import {dictEmptyValToNull} from '@/utils';
|
||||||
|
|
||||||
|
const resource = '/user';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
getUsers() {
|
||||||
|
return Repository.get(`${resource}`);
|
||||||
|
},
|
||||||
|
|
||||||
|
getUser(userId: number) {
|
||||||
|
return Repository.get(`${resource}/${userId}`);
|
||||||
|
},
|
||||||
|
|
||||||
|
deleteUser(userId: number) {
|
||||||
|
return Repository.delete(`${resource}/${userId}`);
|
||||||
|
},
|
||||||
|
|
||||||
|
createUser(userData: any) {
|
||||||
|
return Repository.post(`${resource}`, userData);
|
||||||
|
},
|
||||||
|
|
||||||
|
getProfile() {
|
||||||
|
return Repository.get(`${resource}/profile`);
|
||||||
|
},
|
||||||
|
updateProfile(userData: any) {
|
||||||
|
return Repository.put(`${resource}/profile`, dictEmptyValToNull(userData));
|
||||||
|
},
|
||||||
|
getFavoriteRecorders() {
|
||||||
|
return Repository.get(`${resource}/profile/favorite_recorders`);
|
||||||
|
},
|
||||||
|
|
||||||
|
addFavoriteRecorder(recorderId: any) {
|
||||||
|
return Repository.put(`${resource}/profile/favorite_recorders`, {id: recorderId});
|
||||||
|
},
|
||||||
|
};
|
||||||
32
src/api/virtualCommandRepository.ts
Normal file
32
src/api/virtualCommandRepository.ts
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
// groupRepository.ts
|
||||||
|
|
||||||
|
// @ts-ignore
|
||||||
|
import Repository from './Repository';
|
||||||
|
|
||||||
|
const virtualCommandResource = '/virtual_command';
|
||||||
|
|
||||||
|
import {dictEmptyValToNull} from '@/utils';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
|
||||||
|
getVirtualCommands() {
|
||||||
|
return Repository.get(`${virtualCommandResource}`);
|
||||||
|
},
|
||||||
|
|
||||||
|
getVirtualCommand(virtualCommandId: number) {
|
||||||
|
return Repository.get(`${virtualCommandResource}/${virtualCommandId}`);
|
||||||
|
},
|
||||||
|
|
||||||
|
deleteVirtualCommand(virtualCommandId: number) {
|
||||||
|
return Repository.delete(`${virtualCommandResource}/${virtualCommandId}`);
|
||||||
|
},
|
||||||
|
|
||||||
|
createVirtualCommand(virtualCommandData: any) {
|
||||||
|
return Repository.post(`${virtualCommandResource}`, dictEmptyValToNull(virtualCommandData));
|
||||||
|
},
|
||||||
|
|
||||||
|
updateVirtualCommand(virtualCommandId: number, virtualCommandData: any) {
|
||||||
|
return Repository.put(`${virtualCommandResource}/${virtualCommandId}`,
|
||||||
|
dictEmptyValToNull(virtualCommandData));
|
||||||
|
},
|
||||||
|
};
|
||||||
BIN
src/assets/background.jpg
Normal file
BIN
src/assets/background.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 231 KiB |
BIN
src/assets/cube-background.jpg
Normal file
BIN
src/assets/cube-background.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 492 KiB |
BIN
src/assets/hexagon.jpg
Normal file
BIN
src/assets/hexagon.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 124 KiB |
BIN
src/assets/lens.jpg
Normal file
BIN
src/assets/lens.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 554 KiB |
58
src/components/Admin.vue
Normal file
58
src/components/Admin.vue
Normal file
@@ -0,0 +1,58 @@
|
|||||||
|
<!-- components/Admin.vue -->
|
||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<section class="hero is-primary">
|
||||||
|
<div class="hero-body">
|
||||||
|
<div class="container has-text-centered">
|
||||||
|
<h2 class="title">Admin Tools</h2>
|
||||||
|
<p class="subtitle error-msg">{{ errorMsg }}</p>
|
||||||
|
<router-link :to="{ name: 'admin.groups'}">{{ $t('groups') }}</router-link>
|
||||||
|
<br>
|
||||||
|
<router-link :to="{ name: 'admin.users'}">{{ $t('users') }}</router-link>
|
||||||
|
<br>
|
||||||
|
<router-link :to="{ name: 'admin.permission'}">{{ $t('permissions') }}</router-link>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<br/>
|
||||||
|
<section>
|
||||||
|
<router-view></router-view>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import {EventBus} from '@/utils';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
email: '',
|
||||||
|
errorMsg: '',
|
||||||
|
};
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
authenticate() {
|
||||||
|
this.$store.dispatch('login', {email: this.email, password: this.password})
|
||||||
|
.then(() => this.$router.push('/'));
|
||||||
|
},
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
},
|
||||||
|
beforeDestroy() {
|
||||||
|
// todo ...
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
// todo ...
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss">
|
||||||
|
.error-msg {
|
||||||
|
color: red;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
172
src/components/Admin/Group.vue
Normal file
172
src/components/Admin/Group.vue
Normal file
@@ -0,0 +1,172 @@
|
|||||||
|
<!-- components/groups.vue -->
|
||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<section class="hero is-primary">
|
||||||
|
<div class="hero-body">
|
||||||
|
<div class="container has-text-centered">
|
||||||
|
<h3>
|
||||||
|
<div class="text-center">
|
||||||
|
<font-awesome-icon class="float-left" icon="arrow-circle-left" @click="previousgroup()"/>
|
||||||
|
<input type="number" style="font-size: small; max-width: 48px; text-align: center;"
|
||||||
|
v-model="new_group_id" @blur="manually_set_group_id()"
|
||||||
|
@input="manually_set_group_id()">
|
||||||
|
<font-awesome-icon class="float-right" icon="arrow-circle-right" @click="nextgroup()"/>
|
||||||
|
</div>
|
||||||
|
</h3>
|
||||||
|
|
||||||
|
<h3 class="title">{{ group.id }} – {{ group.name }}</h3>
|
||||||
|
|
||||||
|
<p><strong>{{ $t('nickname') }}: </strong>{{ group.nickname }}</p>
|
||||||
|
<p><strong>{{ $t('first_name') }}: </strong>{{ group.first_name }}</p>
|
||||||
|
<p><strong>{{ $t('last_name') }}: </strong>{{ group.last_name }}</p>
|
||||||
|
|
||||||
|
<hr/>
|
||||||
|
<!--<p v-if="profile.role"><strong>{{$t('role')}}: </strong>{{profile.role}}</p>-->
|
||||||
|
<p><strong>{{ $t('role') }}: </strong><span v-if="group.role">{{ group.role }}</span><span
|
||||||
|
v-else>{{ $t('no specific role') }}</span></p>
|
||||||
|
<p><strong>{{ $t('groups') }}: </strong><span v-for="g in group.groups">{{ g.name }}, </span></p>
|
||||||
|
<p><strong>{{ $t('permissions') }}: </strong></p>
|
||||||
|
<ul>
|
||||||
|
<li v-for="p in group.effective_permissions">{{ p.name }}</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<hr/>
|
||||||
|
|
||||||
|
<p class="subtitle error-msg">{{ errorMsg }}</p>
|
||||||
|
<b-form inline @submit="delete_group">
|
||||||
|
<b-button style="margin-right: 5px;" type="submit" :disabled="!delete_checked" variant="danger">{{ $t('delete.group') }}</b-button>
|
||||||
|
<b-form-checkbox v-model="delete_checked">{{ $t('check.to.allow.deletion') }}</b-form-checkbox>
|
||||||
|
</b-form>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import {EventBus} from '@/utils';
|
||||||
|
import {getRemainingJwtValiditySeconds} from '@/utils';
|
||||||
|
import getRepository from '@/api/RepositoryFactory';
|
||||||
|
|
||||||
|
const groupRepository = getRepository('group');
|
||||||
|
|
||||||
|
export default {
|
||||||
|
props: ['group_id'],
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
current_group_id: this.group_id == null ? 1 : parseInt(this.group_id),
|
||||||
|
current_group_index: null,
|
||||||
|
new_group_id: this.current_group_id,
|
||||||
|
isLoading: false,
|
||||||
|
groups: [],
|
||||||
|
delete_checked: false,
|
||||||
|
errorMsg: '',
|
||||||
|
};
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
fetch() {
|
||||||
|
this.isLoading = true;
|
||||||
|
// const { data } = await GroupRepository.get();
|
||||||
|
groupRepository.getgroups()
|
||||||
|
.then((response) => {
|
||||||
|
this.groups = response.data;
|
||||||
|
this.isLoading = false;
|
||||||
|
});
|
||||||
|
|
||||||
|
},
|
||||||
|
calculate_current_group_index() {
|
||||||
|
this.current_group_index = this.groupIds.findIndex((id) => {
|
||||||
|
return id === this.current_group_id;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
previousgroup() {
|
||||||
|
if (null == this.current_group_index) {
|
||||||
|
this.calculate_current_group_index();
|
||||||
|
}
|
||||||
|
if (this.current_group_index === 0) this.current_group_index = this.groupIds.length - 1;
|
||||||
|
else this.current_group_index = this.current_group_index - 1;
|
||||||
|
this.current_group_id = this.groupIds[this.current_group_index];
|
||||||
|
this.new_group_id = this.current_group_id;
|
||||||
|
},
|
||||||
|
nextgroup() {
|
||||||
|
if (null == this.current_group_index) {
|
||||||
|
this.calculate_current_group_index();
|
||||||
|
}
|
||||||
|
if (this.current_group_index === this.groupIds.length - 1) this.current_group_index = 0;
|
||||||
|
else this.current_group_index = this.current_group_index + 1;
|
||||||
|
this.current_group_id = this.groupIds[this.current_group_index];
|
||||||
|
this.new_group_id = this.current_group_id;
|
||||||
|
},
|
||||||
|
manually_set_group_id() {
|
||||||
|
if (this.new_group_id == null || this.new_group_id === '') {
|
||||||
|
this.new_group_id = this.current_group_id;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (this.groupIds.includes(parseInt(this.new_group_id))) {
|
||||||
|
this.current_group_id = parseInt(this.new_group_id);
|
||||||
|
}
|
||||||
|
this.new_group_id = this.current_group_id;
|
||||||
|
},
|
||||||
|
delete_group(e){
|
||||||
|
e.preventDefault();
|
||||||
|
this.delete_checked = false;
|
||||||
|
groupRepository.deletegroup(this.current_group_id)
|
||||||
|
.then(()=>{
|
||||||
|
this.fetch();
|
||||||
|
this.nextgroup();
|
||||||
|
EventBus.$emit('normalMessage', this.$t('group.deleted'));
|
||||||
|
})
|
||||||
|
.catch((error)=>{
|
||||||
|
EventBus.$emit('errorMessage', this.$t('unable.to.delete.group') + ": "+ error.data.message);
|
||||||
|
})
|
||||||
|
},
|
||||||
|
test(e){
|
||||||
|
e.preventDefault();
|
||||||
|
this.delete_checked = false;
|
||||||
|
this.fetch();
|
||||||
|
this.nextGroup();
|
||||||
|
EventBus.$emit('normalMessage', 'test.msg');
|
||||||
|
}
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
this.fetch();
|
||||||
|
},
|
||||||
|
beforeDestroy() {
|
||||||
|
// todo
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
columns() {
|
||||||
|
if (this.groups.length === 0) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
let columns = Object.keys(this.groups[0]);
|
||||||
|
columns = columns.filter((value) => { // filter to only show some fields
|
||||||
|
return ["id", "first_name", "last_name", "email", "nickname", "role"].includes(value);
|
||||||
|
});
|
||||||
|
return columns;
|
||||||
|
},
|
||||||
|
group() {
|
||||||
|
if (this.groups.length === 0) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
return this.groups.filter((value) => value.id.toString() === this.current_group_id.toString()).pop();
|
||||||
|
},
|
||||||
|
groupIds() {
|
||||||
|
let ids = [];
|
||||||
|
this.groups.forEach((elem) => {
|
||||||
|
ids.push(parseInt(elem.id));
|
||||||
|
});
|
||||||
|
return ids;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss">
|
||||||
|
.error-msg {
|
||||||
|
color: red;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
259
src/components/Admin/Groups.vue
Normal file
259
src/components/Admin/Groups.vue
Normal file
@@ -0,0 +1,259 @@
|
|||||||
|
<!-- components/Groups.vue -->
|
||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<section class="hero is-primary">
|
||||||
|
<div class="hero-body">
|
||||||
|
<div class="container has-text-centered">
|
||||||
|
<h2 class="title">Groups</h2>
|
||||||
|
|
||||||
|
|
||||||
|
<div id="groupTable">
|
||||||
|
<table>
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th v-for="col in group_columns" v-on:click="sortTable(col)">{{col}}
|
||||||
|
<div class="arrow" v-if="col == sortColumn"
|
||||||
|
v-bind:class="[ascending ? 'arrow_up' : 'arrow_down']"></div>
|
||||||
|
</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr v-for="row in get_group_rows()">
|
||||||
|
<td v-for="(col, index) in group_columns">
|
||||||
|
<router-link v-if="index < 2" :to="{name: 'admin.group', params: {group_id: row[col]}}">{{row[col]}}
|
||||||
|
</router-link>
|
||||||
|
<span v-else>{{row[col]}}</span>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
<div class="pagination">
|
||||||
|
<div class="number"
|
||||||
|
v-for="i in num_group_pages()"
|
||||||
|
v-bind:class="[i == currentPage ? 'active' : '']"
|
||||||
|
v-on:click="change_page(i)">{{i}}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<p class="subtitle error-msg">{{ errorMsg }}</p>
|
||||||
|
<h3 class="title">Add Group</h3>
|
||||||
|
<label>
|
||||||
|
<input v-model="group.name" placeholder="Group name">
|
||||||
|
</label>
|
||||||
|
<p>Name is: {{ group.name }}</p>
|
||||||
|
<span>Multiline message is:</span>
|
||||||
|
<p style="white-space: pre-line;">{{ group.description }}</p>
|
||||||
|
<br>
|
||||||
|
<textarea v-model="group.description" placeholder="Description for group"></textarea>
|
||||||
|
<button v-on:click="createGroup">{{$t('create_group')}}</button>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import {EventBus} from '@/utils';
|
||||||
|
import getRepository from '@/api/RepositoryFactory';
|
||||||
|
|
||||||
|
const GroupRepository = getRepository('group');
|
||||||
|
|
||||||
|
export default {
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
isLoading: false,
|
||||||
|
groups: [],
|
||||||
|
errorMsg: '',
|
||||||
|
group: {name: '', description: ''},
|
||||||
|
sortColumn: '',
|
||||||
|
currentPage: 1,
|
||||||
|
elementsPerPage: 5,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
fetch() {
|
||||||
|
this.isLoading = true;
|
||||||
|
// const { data } = await GroupRepository.get();
|
||||||
|
GroupRepository.getGroups()
|
||||||
|
.then((response) => {
|
||||||
|
this.groups = response.data;
|
||||||
|
console.error(this.groups);
|
||||||
|
this.isLoading = false;
|
||||||
|
});
|
||||||
|
|
||||||
|
},
|
||||||
|
get_group_rows() {
|
||||||
|
const start = (this.currentPage - 1) * this.elementsPerPage;
|
||||||
|
const end = start + this.elementsPerPage;
|
||||||
|
return this.groups.slice(start, end);
|
||||||
|
},
|
||||||
|
num_group_pages() {
|
||||||
|
return Math.ceil(this.groups.length / this.elementsPerPage);
|
||||||
|
},
|
||||||
|
change_page(page) {
|
||||||
|
this.currentPage = page;
|
||||||
|
},
|
||||||
|
sortTable(col) {
|
||||||
|
if (this.sortColumn === col) {
|
||||||
|
this.ascending = !this.ascending;
|
||||||
|
} else {
|
||||||
|
this.ascending = true;
|
||||||
|
this.sortColumn = col;
|
||||||
|
}
|
||||||
|
|
||||||
|
const ascending = this.ascending;
|
||||||
|
|
||||||
|
this.groups.sort((a, b) => {
|
||||||
|
if (a[col] > b[col]) {
|
||||||
|
return ascending ? 1 : -1;
|
||||||
|
} else if (a[col] < b[col]) {
|
||||||
|
return ascending ? -1 : 1;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
createGroup() {
|
||||||
|
GroupRepository.createGroup(this.group);
|
||||||
|
this.fetch();
|
||||||
|
},
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
this.fetch();
|
||||||
|
},
|
||||||
|
beforeDestroy() {
|
||||||
|
// todo....or not
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
group_columns() {
|
||||||
|
if (this.groups.length === 0) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
let columns = Object.keys(this.groups[0]);
|
||||||
|
columns = columns.filter((value) => { // filter to only show some fields
|
||||||
|
return ["id", "name", "description", "users"].includes(value);
|
||||||
|
});
|
||||||
|
return columns;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss">
|
||||||
|
.error-msg {
|
||||||
|
color: red;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
table {
|
||||||
|
font-family: 'Open Sans', sans-serif;
|
||||||
|
width: 750px;
|
||||||
|
border-collapse: collapse;
|
||||||
|
border: 3px solid #44475C;
|
||||||
|
margin: 10px 10px 0 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
table th {
|
||||||
|
text-transform: uppercase;
|
||||||
|
text-align: left;
|
||||||
|
background: #44475C;
|
||||||
|
color: #FFF;
|
||||||
|
cursor: pointer;
|
||||||
|
padding: 8px;
|
||||||
|
min-width: 30px;
|
||||||
|
}
|
||||||
|
|
||||||
|
table th:hover {
|
||||||
|
background: #717699;
|
||||||
|
}
|
||||||
|
|
||||||
|
table td {
|
||||||
|
text-align: left;
|
||||||
|
padding: 8px;
|
||||||
|
border-right: 2px solid #7D82A8;
|
||||||
|
}
|
||||||
|
|
||||||
|
table td:last-child {
|
||||||
|
border-right: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
table tbody tr:nth-child(2n) td {
|
||||||
|
background: #D4D8F9;
|
||||||
|
}
|
||||||
|
|
||||||
|
table {
|
||||||
|
font-family: 'Open Sans', sans-serif;
|
||||||
|
width: 750px;
|
||||||
|
border-collapse: collapse;
|
||||||
|
border: 3px solid #44475C;
|
||||||
|
margin: 10px 10px 0 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
table th {
|
||||||
|
text-transform: uppercase;
|
||||||
|
text-align: left;
|
||||||
|
background: #44475C;
|
||||||
|
color: #FFF;
|
||||||
|
cursor: pointer;
|
||||||
|
padding: 8px;
|
||||||
|
min-width: 30px;
|
||||||
|
}
|
||||||
|
|
||||||
|
table th:hover {
|
||||||
|
background: #717699;
|
||||||
|
}
|
||||||
|
|
||||||
|
table td {
|
||||||
|
text-align: left;
|
||||||
|
padding: 8px;
|
||||||
|
border-right: 2px solid #7D82A8;
|
||||||
|
}
|
||||||
|
|
||||||
|
table td:last-child {
|
||||||
|
border-right: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
table tbody tr:nth-child(2n) td {
|
||||||
|
background: #D4D8F9;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pagination {
|
||||||
|
font-family: 'Open Sans', sans-serif;
|
||||||
|
text-align: right;
|
||||||
|
width: 750px;
|
||||||
|
padding: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.arrow_down {
|
||||||
|
background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAB8AAAAaCAYAAABPY4eKAAAAAXNSR0IArs4c6QAAAvlJREFUSA29Vk1PGlEUHQaiiewslpUJiyYs2yb9AyRuJGm7c0VJoFXSX9A0sSZN04ULF12YEBQDhMCuSZOm1FhTiLY2Rky0QPlQBLRUsICoIN/0PCsGyox26NC3eTNn3r3n3TvnvvsE1PkwGo3yUqkkEQqFgw2Mz7lWqwng7ztN06mxsTEv8U0Aam5u7r5EInkplUol/f391wAJCc7nEAgE9Uwmkzo4OPiJMa1Wq6cFs7Ozt0H6RqlUDmJXfPIx+qrX69Ti4mIyHA5r6Wq1egND+j+IyW6QAUoul18XiUTDNHaSyGazKcZtdgk8wqhUKh9o/OMvsVgsfHJy0iWqVrcQNRUMBnd6enqc9MjISAmRP3e73T9al3XnbWNjIw2+KY1Gc3imsNHR0YV4PP5+d3e32h3K316TySQFoX2WyWR2glzIO5fLTSD6IElLNwbqnFpbWyO/96lCoai0cZjN5kfYQAYi5H34fL6cxWIZbya9iJyAhULBHAqFVlMpfsV/fHxMeb3er+Vy+VUzeduzwWC45XA4dlD/vEXvdDrj8DvURsYEWK3WF4FA4JQP9mg0WrHZbEYmnpa0NxYgPVObm5teiLABdTQT8a6vrwdRWhOcHMzMzCiXlpb2/yV6qDttMpkeshEzRk4Wo/bfoe4X9vb2amzGl+HoXNT29vZqsVi0sK1jJScG+Xx+HGkL4Tew2TPi5zUdQQt9otPpuBk3e0TaHmMDh1zS7/f780S0zX6Yni+NnBj09fUZUfvudDrNZN+GkQbl8Xi8RLRtHzsB9Hr9nfn5+SjSeWUCXC7XPq5kw53wsNogjZNohYXL2EljstvtrAL70/mVaW8Y4OidRO1/gwgbUMvcqGmcDc9aPvD1gnTeQ+0nmaInokRj0nHh+uvIiVOtVvt2a2vLv7Ky0tL3cRTXIcpPAwMDpq6R4/JXE4vFQ5FI5CN+QTaRSFCYc8vLy1l0rge4ARe5kJ/d27kYkLXoy2Jo4C7K8CZOsEBvb+9rlUp1xNXPL7v3IDwxvPD6AAAAAElFTkSuQmCC')
|
||||||
|
}
|
||||||
|
|
||||||
|
.arrow_up {
|
||||||
|
background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAB4AAAAaCAYAAACgoey0AAAAAXNSR0IArs4c6QAAAwpJREFUSA21Vt1PUmEYP4dvkQ8JFMwtBRocWAkDbiqXrUWXzU1rrTt0bdVqXbb1tbW16C9IBUSmm27cODdneoXjputa6069qwuW6IIBIdLvdaF4OAcOiGeDc87zPs/vd57P96WpFq7p6enbGo1mjKZpeTabjU1MTCRagGnOZHFxcXxtbe1XKpUq7+zslJeXl//Mz8+Hy+Uy3RxSE9qTk5M3otFooVQqgef4Wl9f343FYoEmoISrxuNxFX5f9vb2jhn/PxUKhfLS0tIPfFifUESRUMV8Pv/M6XReRm5rTGQyGeXxeGxYe1ezeBpBOBx2rKysbO7v79d4Wy3Y2Nj4GQqFbgnhaugxwiuGJx99Pp9FLBbXxYTXvTqd7v3MzIy6riIWGxJnMpl7AwMD14xGYyMsSq1WUyQdUqn0eSPlusQIsbGrq+vl4OCgvhFQZd1utyv1en0gEolcqsi47nWJlUrlG5fLZVcoFFy2nDKSDpIWlUoVTCQSEk4lCHmJMZ2GTCbTiMVikfIZ88l7enoos9l8dXt7+z6fDicxSJUokqDX6xXcl2wCROoc0vQCWL3sNfLOSdzR0fHY4XC4tVotl40gmVwup9xuN4OQv+UyqCFGH9rg7SOGYVRcBs3IEG4J0nVnamrqOtvuBDGGgQg9+wHFcVEi4a0LNkbdd6TrPKo8ODc311mteIIYjT/a398/jK+s1jnVM0kXoufCFvq0GuiIGEVgQIhfoygM1QrteEa9dAL7ITiYCt4RMabOK5AyKKzKWtvupLcRciu8D5J0EuDDPyT/Snd39yh6VtY2NhYQSR9G79Ds7OxdskRjEyAufvb7/cPoO5Z6e1+xtVKrq6vfcFzyi/A3ZrPZ3GdNSlwgo5ekE4X2RIQGf2C1WlufFE0GBeGWYQ8YERWLxQtnUVB830MKLZfL9RHir8lkssCn2G751tZWEWe03zTKm15YWPiEiXXTYDB0Ig/t7yd8PRws4EicwWHxO4jHD8/C5HiTTqd1BwcHFozKU89origB+y/kmzgYpgOBQP4fGmUiZmJ+WNgAAAAASUVORK5CYII=')
|
||||||
|
}
|
||||||
|
|
||||||
|
.arrow {
|
||||||
|
float: right;
|
||||||
|
width: 12px;
|
||||||
|
height: 15px;
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
background-size: contain;
|
||||||
|
background-position-y: bottom;
|
||||||
|
}
|
||||||
|
|
||||||
|
.number {
|
||||||
|
display: inline-block;
|
||||||
|
padding: 4px 10px;
|
||||||
|
color: #FFF;
|
||||||
|
border-radius: 4px;
|
||||||
|
background: #44475C;
|
||||||
|
margin: 0px 5px;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.number:hover, .number.active {
|
||||||
|
background: #717699;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
248
src/components/Admin/Permission.vue
Normal file
248
src/components/Admin/Permission.vue
Normal file
@@ -0,0 +1,248 @@
|
|||||||
|
<!-- components/Permission.vue -->
|
||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<section class="hero is-primary">
|
||||||
|
<div class="hero-body">
|
||||||
|
<div class="container has-text-centered">
|
||||||
|
<h2 class="title">Permissions</h2>
|
||||||
|
|
||||||
|
|
||||||
|
<div id="permissionTable">
|
||||||
|
<table>
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th v-for="col in permission_columns" v-on:click="sortTable(col)">{{col}}
|
||||||
|
<div class="arrow" v-if="col == sortColumn"
|
||||||
|
v-bind:class="[ascending ? 'arrow_up' : 'arrow_down']"></div>
|
||||||
|
</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr v-for="row in get_permission_rows()">
|
||||||
|
<td v-for="col in permission_columns">{{row[col]}}</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
<div class="pagination">
|
||||||
|
<div class="number"
|
||||||
|
v-for="i in num_permission_pages()"
|
||||||
|
v-bind:class="[i == currentPage ? 'active' : '']"
|
||||||
|
v-on:click="change_page(i)">{{i}}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<p class="subtitle error-msg">{{ errorMsg }}</p>
|
||||||
|
<h3 class="title">Add Permission</h3>
|
||||||
|
<label>
|
||||||
|
<input v-model="permission.name" placeholder="Permission name">
|
||||||
|
</label>
|
||||||
|
<p>Name is: {{ permission.name }}</p>
|
||||||
|
<span>Multiline message is:</span>
|
||||||
|
<p style="white-space: pre-line;">{{ permission.description }}</p>
|
||||||
|
<br>
|
||||||
|
<textarea v-model="permission.description" placeholder="Description for permission"></textarea>
|
||||||
|
<button v-on:click="createPermission">{{$t('create_permission')}}</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import {EventBus} from '@/utils';
|
||||||
|
import getRepository from '@/api/RepositoryFactory';
|
||||||
|
|
||||||
|
const PermissionRepository = getRepository('permission');
|
||||||
|
|
||||||
|
export default {
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
isLoading: false,
|
||||||
|
permissions: [],
|
||||||
|
errorMsg: '',
|
||||||
|
permission: {name: '', description: ''},
|
||||||
|
sortColumn: '',
|
||||||
|
currentPage: 1,
|
||||||
|
elementsPerPage: 10,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
fetch() {
|
||||||
|
this.isLoading = true;
|
||||||
|
// const { data } = await PermissionRepository.get();
|
||||||
|
PermissionRepository.getPermissions()
|
||||||
|
.then((response) => {
|
||||||
|
this.permissions = response.data;
|
||||||
|
this.isLoading = false;
|
||||||
|
});
|
||||||
|
|
||||||
|
},
|
||||||
|
get_permission_rows() {
|
||||||
|
const start = (this.currentPage - 1) * this.elementsPerPage;
|
||||||
|
const end = start + this.elementsPerPage;
|
||||||
|
return this.permissions.slice(start, end);
|
||||||
|
},
|
||||||
|
num_permission_pages() {
|
||||||
|
return Math.ceil(this.permissions.length / this.elementsPerPage);
|
||||||
|
},
|
||||||
|
change_page(page) {
|
||||||
|
this.currentPage = page;
|
||||||
|
},
|
||||||
|
sortTable(col) {
|
||||||
|
if (this.sortColumn === col) {
|
||||||
|
this.ascending = !this.ascending;
|
||||||
|
} else {
|
||||||
|
this.ascending = true;
|
||||||
|
this.sortColumn = col;
|
||||||
|
}
|
||||||
|
|
||||||
|
const ascending = this.ascending;
|
||||||
|
|
||||||
|
this.permissions.sort((a, b) => {
|
||||||
|
if (a[col] > b[col]) {
|
||||||
|
return ascending ? 1 : -1;
|
||||||
|
} else if (a[col] < b[col]) {
|
||||||
|
return ascending ? -1 : 1;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
createPermission() {
|
||||||
|
PermissionRepository.createPermission(this.permission);
|
||||||
|
this.fetch();
|
||||||
|
},
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
this.fetch();
|
||||||
|
},
|
||||||
|
beforeDestroy() {
|
||||||
|
// todo....or not
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
permission_columns() {
|
||||||
|
if (this.permissions.length === 0) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
return Object.keys(this.permissions[0]);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss">
|
||||||
|
.error-msg {
|
||||||
|
color: red;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
table {
|
||||||
|
font-family: 'Open Sans', sans-serif;
|
||||||
|
width: 750px;
|
||||||
|
border-collapse: collapse;
|
||||||
|
border: 3px solid #44475C;
|
||||||
|
margin: 10px 10px 0 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
table th {
|
||||||
|
text-transform: uppercase;
|
||||||
|
text-align: left;
|
||||||
|
background: #44475C;
|
||||||
|
color: #FFF;
|
||||||
|
cursor: pointer;
|
||||||
|
padding: 8px;
|
||||||
|
min-width: 30px;
|
||||||
|
}
|
||||||
|
|
||||||
|
table th:hover {
|
||||||
|
background: #717699;
|
||||||
|
}
|
||||||
|
|
||||||
|
table td {
|
||||||
|
text-align: left;
|
||||||
|
padding: 8px;
|
||||||
|
border-right: 2px solid #7D82A8;
|
||||||
|
}
|
||||||
|
|
||||||
|
table td:last-child {
|
||||||
|
border-right: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
table tbody tr:nth-child(2n) td {
|
||||||
|
background: #D4D8F9;
|
||||||
|
}
|
||||||
|
|
||||||
|
table {
|
||||||
|
font-family: 'Open Sans', sans-serif;
|
||||||
|
width: 750px;
|
||||||
|
border-collapse: collapse;
|
||||||
|
border: 3px solid #44475C;
|
||||||
|
margin: 10px 10px 0 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
table th {
|
||||||
|
text-transform: uppercase;
|
||||||
|
text-align: left;
|
||||||
|
background: #44475C;
|
||||||
|
color: #FFF;
|
||||||
|
cursor: pointer;
|
||||||
|
padding: 8px;
|
||||||
|
min-width: 30px;
|
||||||
|
}
|
||||||
|
|
||||||
|
table th:hover {
|
||||||
|
background: #717699;
|
||||||
|
}
|
||||||
|
|
||||||
|
table td {
|
||||||
|
text-align: left;
|
||||||
|
padding: 8px;
|
||||||
|
border-right: 2px solid #7D82A8;
|
||||||
|
}
|
||||||
|
|
||||||
|
table td:last-child {
|
||||||
|
border-right: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
table tbody tr:nth-child(2n) td {
|
||||||
|
background: #D4D8F9;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pagination {
|
||||||
|
font-family: 'Open Sans', sans-serif;
|
||||||
|
text-align: right;
|
||||||
|
width: 750px;
|
||||||
|
padding: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.arrow_down {
|
||||||
|
background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAB8AAAAaCAYAAABPY4eKAAAAAXNSR0IArs4c6QAAAvlJREFUSA29Vk1PGlEUHQaiiewslpUJiyYs2yb9AyRuJGm7c0VJoFXSX9A0sSZN04ULF12YEBQDhMCuSZOm1FhTiLY2Rky0QPlQBLRUsICoIN/0PCsGyox26NC3eTNn3r3n3TvnvvsE1PkwGo3yUqkkEQqFgw2Mz7lWqwng7ztN06mxsTEv8U0Aam5u7r5EInkplUol/f391wAJCc7nEAgE9Uwmkzo4OPiJMa1Wq6cFs7Ozt0H6RqlUDmJXfPIx+qrX69Ti4mIyHA5r6Wq1egND+j+IyW6QAUoul18XiUTDNHaSyGazKcZtdgk8wqhUKh9o/OMvsVgsfHJy0iWqVrcQNRUMBnd6enqc9MjISAmRP3e73T9al3XnbWNjIw2+KY1Gc3imsNHR0YV4PP5+d3e32h3K316TySQFoX2WyWR2glzIO5fLTSD6IElLNwbqnFpbWyO/96lCoai0cZjN5kfYQAYi5H34fL6cxWIZbya9iJyAhULBHAqFVlMpfsV/fHxMeb3er+Vy+VUzeduzwWC45XA4dlD/vEXvdDrj8DvURsYEWK3WF4FA4JQP9mg0WrHZbEYmnpa0NxYgPVObm5teiLABdTQT8a6vrwdRWhOcHMzMzCiXlpb2/yV6qDttMpkeshEzRk4Wo/bfoe4X9vb2amzGl+HoXNT29vZqsVi0sK1jJScG+Xx+HGkL4Tew2TPi5zUdQQt9otPpuBk3e0TaHmMDh1zS7/f780S0zX6Yni+NnBj09fUZUfvudDrNZN+GkQbl8Xi8RLRtHzsB9Hr9nfn5+SjSeWUCXC7XPq5kw53wsNogjZNohYXL2EljstvtrAL70/mVaW8Y4OidRO1/gwgbUMvcqGmcDc9aPvD1gnTeQ+0nmaInokRj0nHh+uvIiVOtVvt2a2vLv7Ky0tL3cRTXIcpPAwMDpq6R4/JXE4vFQ5FI5CN+QTaRSFCYc8vLy1l0rge4ARe5kJ/d27kYkLXoy2Jo4C7K8CZOsEBvb+9rlUp1xNXPL7v3IDwxvPD6AAAAAElFTkSuQmCC')
|
||||||
|
}
|
||||||
|
|
||||||
|
.arrow_up {
|
||||||
|
background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAB4AAAAaCAYAAACgoey0AAAAAXNSR0IArs4c6QAAAwpJREFUSA21Vt1PUmEYP4dvkQ8JFMwtBRocWAkDbiqXrUWXzU1rrTt0bdVqXbb1tbW16C9IBUSmm27cODdneoXjputa6069qwuW6IIBIdLvdaF4OAcOiGeDc87zPs/vd57P96WpFq7p6enbGo1mjKZpeTabjU1MTCRagGnOZHFxcXxtbe1XKpUq7+zslJeXl//Mz8+Hy+Uy3RxSE9qTk5M3otFooVQqgef4Wl9f343FYoEmoISrxuNxFX5f9vb2jhn/PxUKhfLS0tIPfFifUESRUMV8Pv/M6XReRm5rTGQyGeXxeGxYe1ezeBpBOBx2rKysbO7v79d4Wy3Y2Nj4GQqFbgnhaugxwiuGJx99Pp9FLBbXxYTXvTqd7v3MzIy6riIWGxJnMpl7AwMD14xGYyMsSq1WUyQdUqn0eSPlusQIsbGrq+vl4OCgvhFQZd1utyv1en0gEolcqsi47nWJlUrlG5fLZVcoFFy2nDKSDpIWlUoVTCQSEk4lCHmJMZ2GTCbTiMVikfIZ88l7enoos9l8dXt7+z6fDicxSJUokqDX6xXcl2wCROoc0vQCWL3sNfLOSdzR0fHY4XC4tVotl40gmVwup9xuN4OQv+UyqCFGH9rg7SOGYVRcBs3IEG4J0nVnamrqOtvuBDGGgQg9+wHFcVEi4a0LNkbdd6TrPKo8ODc311mteIIYjT/a398/jK+s1jnVM0kXoufCFvq0GuiIGEVgQIhfoygM1QrteEa9dAL7ITiYCt4RMabOK5AyKKzKWtvupLcRciu8D5J0EuDDPyT/Snd39yh6VtY2NhYQSR9G79Ds7OxdskRjEyAufvb7/cPoO5Z6e1+xtVKrq6vfcFzyi/A3ZrPZ3GdNSlwgo5ekE4X2RIQGf2C1WlufFE0GBeGWYQ8YERWLxQtnUVB830MKLZfL9RHir8lkssCn2G751tZWEWe03zTKm15YWPiEiXXTYDB0Ig/t7yd8PRws4EicwWHxO4jHD8/C5HiTTqd1BwcHFozKU89origB+y/kmzgYpgOBQP4fGmUiZmJ+WNgAAAAASUVORK5CYII=')
|
||||||
|
}
|
||||||
|
|
||||||
|
.arrow {
|
||||||
|
float: right;
|
||||||
|
width: 12px;
|
||||||
|
height: 15px;
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
background-size: contain;
|
||||||
|
background-position-y: bottom;
|
||||||
|
}
|
||||||
|
|
||||||
|
.number {
|
||||||
|
display: inline-block;
|
||||||
|
padding: 4px 10px;
|
||||||
|
color: #FFF;
|
||||||
|
border-radius: 4px;
|
||||||
|
background: #44475C;
|
||||||
|
margin: 0px 5px;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.number:hover, .number.active {
|
||||||
|
background: #717699;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
195
src/components/Admin/User.vue
Normal file
195
src/components/Admin/User.vue
Normal file
@@ -0,0 +1,195 @@
|
|||||||
|
<!-- components/Users.vue -->
|
||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<section class="hero is-primary">
|
||||||
|
<div class="hero-body">
|
||||||
|
<div class="container has-text-centered">
|
||||||
|
<h3>
|
||||||
|
<div class="text-center">
|
||||||
|
<font-awesome-icon class="float-left" icon="arrow-circle-left" @click="previousUser()"/>
|
||||||
|
<input type="number" style="font-size: small; max-width: 48px; text-align: center;"
|
||||||
|
v-model="new_user_id" @blur="manually_set_user_id()"
|
||||||
|
@input="manually_set_user_id()">
|
||||||
|
<font-awesome-icon class="float-right" icon="arrow-circle-right" @click="nextUser()"/>
|
||||||
|
</div>
|
||||||
|
</h3>
|
||||||
|
|
||||||
|
<h3 class="title">{{ user.id }} – {{ get_user_display_name() }}</h3>
|
||||||
|
|
||||||
|
<p><strong>{{ $t('nickname') }}: </strong>{{ user.nickname }}</p>
|
||||||
|
<p><strong>{{ $t('first_name') }}: </strong>{{ user.first_name }}</p>
|
||||||
|
<p><strong>{{ $t('last_name') }}: </strong>{{ user.last_name }}</p>
|
||||||
|
|
||||||
|
<hr/>
|
||||||
|
<!--<p v-if="profile.role"><strong>{{$t('role')}}: </strong>{{profile.role}}</p>-->
|
||||||
|
<p><strong>{{ $t('role') }}: </strong><span v-if="user.role">{{ user.role }}</span><span
|
||||||
|
v-else>{{ $t('no specific role') }}</span></p>
|
||||||
|
<p><strong>{{ $t('groups') }}: </strong><span v-for="g in user.groups">{{ g.name }}, </span></p>
|
||||||
|
<p><strong>{{ $t('permissions') }}: </strong></p>
|
||||||
|
<ul>
|
||||||
|
<li v-for="p in user.effective_permissions">{{ p.name }}</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<hr/>
|
||||||
|
|
||||||
|
<p class="subtitle error-msg">{{ errorMsg }}</p>
|
||||||
|
<b-form inline @submit="delete_user">
|
||||||
|
<b-button style="margin-right: 5px;" type="submit" :disabled="!delete_checked" variant="danger">{{ $t('delete.user') }}</b-button>
|
||||||
|
<b-form-checkbox v-model="delete_checked">{{ $t('check.to.allow.deletion') }}</b-form-checkbox>
|
||||||
|
</b-form>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import {EventBus} from '@/utils';
|
||||||
|
import {getRemainingJwtValiditySeconds} from '@/utils';
|
||||||
|
import getRepository from '@/api/RepositoryFactory';
|
||||||
|
|
||||||
|
const UserRepository = getRepository('user');
|
||||||
|
|
||||||
|
export default {
|
||||||
|
props: ['user_id'],
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
current_user_id: this.user_id == null ? 1 : parseInt(this.user_id),
|
||||||
|
current_user_index: null,
|
||||||
|
new_user_id: this.current_user_id,
|
||||||
|
isLoading: false,
|
||||||
|
users: [],
|
||||||
|
delete_checked: false,
|
||||||
|
errorMsg: '',
|
||||||
|
};
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
fetch() {
|
||||||
|
this.isLoading = true;
|
||||||
|
// const { data } = await GroupRepository.get();
|
||||||
|
UserRepository.getUsers()
|
||||||
|
.then((response) => {
|
||||||
|
this.users = response.data;
|
||||||
|
this.isLoading = false;
|
||||||
|
});
|
||||||
|
|
||||||
|
},
|
||||||
|
get_user_display_name() {
|
||||||
|
let display_name = null;
|
||||||
|
if(this.user.first_name != null)
|
||||||
|
display_name = this.user.first_name;
|
||||||
|
if(this.user.last_name != null)
|
||||||
|
if(display_name != null) {
|
||||||
|
display_name += " " + this.user.last_name;
|
||||||
|
} else {
|
||||||
|
display_name = this.user.last_name;
|
||||||
|
}
|
||||||
|
if (this.user.nickname != null && this.user.nickname !== "") {
|
||||||
|
if (display_name != null && display_name.trim() === "") {
|
||||||
|
display_name = this.user.nickname;
|
||||||
|
} else {
|
||||||
|
display_name += " (" + this.user.nickname + ")";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (display_name == null || display_name.trim() === "") {
|
||||||
|
display_name = this.user.email;
|
||||||
|
}
|
||||||
|
|
||||||
|
return display_name
|
||||||
|
},
|
||||||
|
calculate_current_user_index() {
|
||||||
|
this.current_user_index = this.userIds.findIndex((id) => {
|
||||||
|
return id === this.current_user_id;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
previousUser() {
|
||||||
|
if (null == this.current_user_index) {
|
||||||
|
this.calculate_current_user_index();
|
||||||
|
}
|
||||||
|
if (this.current_user_index === 0) this.current_user_index = this.userIds.length - 1;
|
||||||
|
else this.current_user_index = this.current_user_index - 1;
|
||||||
|
this.current_user_id = this.userIds[this.current_user_index];
|
||||||
|
this.new_user_id = this.current_user_id;
|
||||||
|
},
|
||||||
|
nextUser() {
|
||||||
|
if (null == this.current_user_index) {
|
||||||
|
this.calculate_current_user_index();
|
||||||
|
}
|
||||||
|
if (this.current_user_index === this.userIds.length - 1) this.current_user_index = 0;
|
||||||
|
else this.current_user_index = this.current_user_index + 1;
|
||||||
|
this.current_user_id = this.userIds[this.current_user_index];
|
||||||
|
this.new_user_id = this.current_user_id;
|
||||||
|
},
|
||||||
|
manually_set_user_id() {
|
||||||
|
if (this.new_user_id == null || this.new_user_id === '') {
|
||||||
|
this.new_user_id = this.current_user_id;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (this.userIds.includes(parseInt(this.new_user_id))) {
|
||||||
|
this.current_user_id = parseInt(this.new_user_id);
|
||||||
|
}
|
||||||
|
this.new_user_id = this.current_user_id;
|
||||||
|
},
|
||||||
|
delete_user(e){
|
||||||
|
e.preventDefault();
|
||||||
|
this.delete_checked = false;
|
||||||
|
UserRepository.deleteUser(this.current_user_id)
|
||||||
|
.then(()=>{
|
||||||
|
this.fetch();
|
||||||
|
this.nextUser();
|
||||||
|
EventBus.$emit('normalMessage', this.$t('user.deleted'));
|
||||||
|
})
|
||||||
|
.catch((error)=>{
|
||||||
|
EventBus.$emit('errorMessage', this.$t('unable.to.delete.user') + ": "+ error.data.message);
|
||||||
|
})
|
||||||
|
},
|
||||||
|
test(e){
|
||||||
|
e.preventDefault();
|
||||||
|
this.delete_checked = false;
|
||||||
|
this.fetch();
|
||||||
|
this.nextUser();
|
||||||
|
EventBus.$emit('normalMessage', 'test.msg');
|
||||||
|
}
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
this.fetch();
|
||||||
|
},
|
||||||
|
beforeDestroy() {
|
||||||
|
// todo
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
columns() {
|
||||||
|
if (this.users.length === 0) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
let columns = Object.keys(this.users[0]);
|
||||||
|
columns = columns.filter((value) => { // filter to only show some fields
|
||||||
|
return ["id", "first_name", "last_name", "email", "nickname", "role"].includes(value);
|
||||||
|
});
|
||||||
|
return columns;
|
||||||
|
},
|
||||||
|
user() {
|
||||||
|
if (this.users.length === 0) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
return this.users.filter((value) => value.id.toString() === this.current_user_id.toString()).pop();
|
||||||
|
},
|
||||||
|
userIds() {
|
||||||
|
let ids = [];
|
||||||
|
this.users.forEach((elem) => {
|
||||||
|
ids.push(parseInt(elem.id));
|
||||||
|
});
|
||||||
|
return ids;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss">
|
||||||
|
.error-msg {
|
||||||
|
color: red;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
146
src/components/Admin/Users.vue
Normal file
146
src/components/Admin/Users.vue
Normal file
@@ -0,0 +1,146 @@
|
|||||||
|
<!-- components/Users.vue -->
|
||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<section class="hero is-primary">
|
||||||
|
<div class="hero-body">
|
||||||
|
<div class="container has-text-centered">
|
||||||
|
<h3 class="title">Manage users</h3>
|
||||||
|
<h3>Users</h3>
|
||||||
|
|
||||||
|
<div id="userTable">
|
||||||
|
<table>
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th v-for="col in columns" v-on:click="sortTable(col)">{{col}}
|
||||||
|
<div class="arrow" v-if="col == sortColumn"
|
||||||
|
v-bind:class="[ascending ? 'arrow_up' : 'arrow_down']"></div>
|
||||||
|
</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr v-for="row in get_rows()">
|
||||||
|
<td v-for="(col, index) in columns">
|
||||||
|
<router-link v-if="index < 1" :to="{name: 'admin.user', params: {user_id: row[col]}}">{{row[col]}}
|
||||||
|
</router-link>
|
||||||
|
<span v-else>{{row[col]}}</span>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
<div class="pagination">
|
||||||
|
<div class="number"
|
||||||
|
v-for="i in num_pages()"
|
||||||
|
v-bind:class="[i === currentPage ? 'active' : '']"
|
||||||
|
v-on:click="change_page(i)">{{i}}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h3 class="title">Add User</h3>
|
||||||
|
<label>
|
||||||
|
<input v-model="user.first_name" placeholder="First name">
|
||||||
|
</label>
|
||||||
|
<p>Name is: {{ user.first_name }}</p>
|
||||||
|
|
||||||
|
<button v-on:click="createUser">{{$t('create_user')}}</button>
|
||||||
|
|
||||||
|
<p class="subtitle error-msg">{{ errorMsg }}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import {EventBus} from '@/utils';
|
||||||
|
import {getRemainingJwtValiditySeconds} from '../../utils';
|
||||||
|
import getRepository from '@/api/RepositoryFactory';
|
||||||
|
|
||||||
|
const UserRepository = getRepository('user');
|
||||||
|
|
||||||
|
export default {
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
isLoading: false,
|
||||||
|
user: {},
|
||||||
|
users: [],
|
||||||
|
errorMsg: '',
|
||||||
|
sortColumn: '',
|
||||||
|
currentPage: 1,
|
||||||
|
elementsPerPage: 5,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
fetch() {
|
||||||
|
this.isLoading = true;
|
||||||
|
// const { data } = await GroupRepository.get();
|
||||||
|
UserRepository.getUsers()
|
||||||
|
.then((response) => {
|
||||||
|
this.users = response.data;
|
||||||
|
this.isLoading = false;
|
||||||
|
});
|
||||||
|
|
||||||
|
},
|
||||||
|
get_rows() {
|
||||||
|
const start = (this.currentPage - 1) * this.elementsPerPage;
|
||||||
|
const end = start + this.elementsPerPage;
|
||||||
|
return this.users.slice(start, end);
|
||||||
|
},
|
||||||
|
num_pages() {
|
||||||
|
return Math.ceil(this.users.length / this.elementsPerPage);
|
||||||
|
},
|
||||||
|
change_page(page) {
|
||||||
|
this.currentPage = page;
|
||||||
|
},
|
||||||
|
sortTable(col) {
|
||||||
|
if (this.sortColumn === col) {
|
||||||
|
this.ascending = !this.ascending;
|
||||||
|
} else {
|
||||||
|
this.ascending = true;
|
||||||
|
this.sortColumn = col;
|
||||||
|
}
|
||||||
|
|
||||||
|
const ascending = this.ascending;
|
||||||
|
|
||||||
|
this.users.sort((a, b) => {
|
||||||
|
if (a[col] > b[col]) {
|
||||||
|
return ascending ? 1 : -1;
|
||||||
|
} else if (a[col] < b[col]) {
|
||||||
|
return ascending ? -1 : 1;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
createUser() {
|
||||||
|
UserRepository.createUser(this.user);
|
||||||
|
this.fetch();
|
||||||
|
},
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
this.fetch();
|
||||||
|
},
|
||||||
|
beforeDestroy() {
|
||||||
|
// todo
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
columns() {
|
||||||
|
if (this.users.length === 0) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
let columns = Object.keys(this.users[0]);
|
||||||
|
columns = columns.filter((value)=>{ // filter to only show some fields
|
||||||
|
return ["id", "first_name", "last_name", "email", "nickname", "role"].includes(value);
|
||||||
|
});
|
||||||
|
return columns;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss">
|
||||||
|
.error-msg {
|
||||||
|
color: red;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
291
src/components/Commands.vue
Normal file
291
src/components/Commands.vue
Normal file
@@ -0,0 +1,291 @@
|
|||||||
|
<template>
|
||||||
|
<div class="container">
|
||||||
|
<section class="section">
|
||||||
|
|
||||||
|
|
||||||
|
<h1 class="title">{{ $t('Manage Commands') }}</h1>
|
||||||
|
<p class="lead">
|
||||||
|
{{ $t('List, create and delete') }} <strong>{{ $t('commands') }}</strong>
|
||||||
|
</p>
|
||||||
|
<br/>
|
||||||
|
|
||||||
|
<b-tabs v-model="tabIndex" card>
|
||||||
|
<b-tab title="Command list" active>
|
||||||
|
<template slot="title">
|
||||||
|
<font-awesome-icon icon="list"/> <font-awesome-icon icon="cogs"/>
|
||||||
|
<strong>command</strong> <i>list</i>
|
||||||
|
</template>
|
||||||
|
<p>{{ $t('There are')}} {{commands.length}} {{ $t('recorder commands defined')}}:</p>
|
||||||
|
<b-card-group deck>
|
||||||
|
<b-card class="mb-2" style="max-width: 30rem; min-width:20rem;" v-for="(command) in commands"
|
||||||
|
:header="command.name + ' (' +command.recorder_model.name+ ')'" v-bind:key="command.id">
|
||||||
|
<b-card-text>
|
||||||
|
<h5 class="card-title">{{ $t('name') }}:
|
||||||
|
<span>{{command.name}} </span>
|
||||||
|
</h5>
|
||||||
|
|
||||||
|
<p class="card-text">
|
||||||
|
<small><strong>{{ $t('description') }}:</strong>
|
||||||
|
<span v-if="!formEditField[command.id+'_description']">{{command.description}}
|
||||||
|
<a class="float-right badge badge-pill badge-info">
|
||||||
|
<font-awesome-icon
|
||||||
|
@click="initRecCommandUpdate(command, 'description')"
|
||||||
|
icon="pencil-alt"/>
|
||||||
|
</a>
|
||||||
|
</span>
|
||||||
|
<textarea class="textarea form-control" v-else name="comment"
|
||||||
|
v-model="updateValues[command.id+'_description']"
|
||||||
|
:placeholder="'Notes ('+command.description +')'"
|
||||||
|
@blur="updateRecCommand(command.id, 'description')"></textarea>
|
||||||
|
</small>
|
||||||
|
</p>
|
||||||
|
<hr/>
|
||||||
|
|
||||||
|
<div v-if="command.recorder_model">
|
||||||
|
<p class="card-text"><strong>{{ $t('Recorder') }} {{ $t('model') }}:</strong> {{command.recorder_model.name}} </p>
|
||||||
|
</div>
|
||||||
|
<div v-else>
|
||||||
|
<p class="card-text"><strong>{{ $t('Recorder') }}:</strong> {{
|
||||||
|
$t('no_recorder_model_defined')}}</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
</b-card-text>
|
||||||
|
<div slot="footer">
|
||||||
|
<small class="text-muted">
|
||||||
|
<p>{{ $t('created')}}: {{command.created_at | moment("dddd, MMMM Do YYYY")}}
|
||||||
|
|
||||||
|
</p>
|
||||||
|
</small>
|
||||||
|
</div>
|
||||||
|
</b-card>
|
||||||
|
</b-card-group>
|
||||||
|
</b-tab>
|
||||||
|
|
||||||
|
<b-tab title="Create command">
|
||||||
|
<template slot="title">
|
||||||
|
<font-awesome-icon icon="plus"/>
|
||||||
|
<i>{{$t('create')}}</i> <font-awesome-icon icon="cogs"/>
|
||||||
|
<strong>{{$t('virtual')}} {{$t('command')}}</strong>
|
||||||
|
</template>
|
||||||
|
<b-card-text>
|
||||||
|
<p>{{ $t('Create a new command')}}:</p>
|
||||||
|
<!-- form starts here -->
|
||||||
|
<ValidationObserver v-slot="{ invalid }">
|
||||||
|
<form v-on:submit.prevent="saveCommand()">
|
||||||
|
<section class="form">
|
||||||
|
|
||||||
|
<ValidationProvider :name="$t('name')" rules="required|min:3" v-slot="{ errors }" slim>
|
||||||
|
<div class="form-group row">
|
||||||
|
<label class="label required col-sm-2 col-form-label">{{ $t('name') }}</label>
|
||||||
|
<div class="col-sm-6">
|
||||||
|
<input name="name"
|
||||||
|
v-model="form.name"
|
||||||
|
v-bind:class="{'is-danger': errors.length > 0, 'is-invalid': errors.length > 0}"
|
||||||
|
class="form-control" type="text" placeholder="command name" required>
|
||||||
|
</div>
|
||||||
|
<p class="col-sm-4" v-show="errors.length > 0">
|
||||||
|
{{ errors[0] }}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</ValidationProvider>
|
||||||
|
|
||||||
|
<div class="form-group row">
|
||||||
|
<label class="label col-sm-2 col-form-label">{{ $t('notes') }}</label>
|
||||||
|
<div class="col-sm-6">
|
||||||
|
<textarea class="textarea form-control" placeholder="Comments, remarks, notes, etc."
|
||||||
|
v-model="form.description"></textarea>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-group row">
|
||||||
|
<label class="label col-sm-2 col-form-label">{{ $t('recorder') }}</label>
|
||||||
|
<div class="col-sm-6">
|
||||||
|
<select class="form-control" v-model="form.recorder_id">
|
||||||
|
<option value="">No recorder selected</option>
|
||||||
|
<option v-for="recorder in recorders" v-bind:value="recorder.id">
|
||||||
|
{{ recorder.name }}
|
||||||
|
</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
<div class="field is-grouped">
|
||||||
|
<div class="control">
|
||||||
|
<button
|
||||||
|
v-bind:disabled="invalid"
|
||||||
|
type="submit"
|
||||||
|
class="btn btn-primary">
|
||||||
|
Create command
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</section>
|
||||||
|
</form>
|
||||||
|
</ValidationObserver>
|
||||||
|
</b-card-text>
|
||||||
|
</b-tab>
|
||||||
|
</b-tabs>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<hr>
|
||||||
|
|
||||||
|
<!--
|
||||||
|
<div class="column">
|
||||||
|
<section class="section" id="results">
|
||||||
|
<div class="box">
|
||||||
|
<ul>
|
||||||
|
<li v-for="(item, k) in form">
|
||||||
|
<strong>{{ k }}:</strong> {{ item }}
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="column">
|
||||||
|
<section class="section">
|
||||||
|
{{recorders}}
|
||||||
|
</section>
|
||||||
|
</div>
|
||||||
|
-->
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import {EventBus} from '@/utils';
|
||||||
|
import PulseLoader from 'vue-spinner/src/PulseLoader.vue';
|
||||||
|
import getRepository from '@/api/RepositoryFactory';
|
||||||
|
|
||||||
|
const recorderRepository = getRepository('recorder');
|
||||||
|
const commandRepository = getRepository('command');
|
||||||
|
|
||||||
|
export default {
|
||||||
|
components: {
|
||||||
|
PulseLoader,
|
||||||
|
},
|
||||||
|
props: [],
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
tabIndex: 0,
|
||||||
|
updateValues: {},
|
||||||
|
formEditField: {},
|
||||||
|
show_assigned_recorders: false,
|
||||||
|
form: {
|
||||||
|
name: null,
|
||||||
|
description: null,
|
||||||
|
recorder_id: null,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
saveCommand() {
|
||||||
|
this.$parent.$data.isLoading = true;
|
||||||
|
commandRepository.createCommand(this.form)
|
||||||
|
.then(() => {
|
||||||
|
this.$store.dispatch('loadCommands')
|
||||||
|
.then(() => {
|
||||||
|
this.$parent.$data.isLoading = false;
|
||||||
|
this.tabIndex = 0;
|
||||||
|
});
|
||||||
|
}).catch((msg) => {
|
||||||
|
this.showErrorMessage('Could not safe command!');
|
||||||
|
});
|
||||||
|
},
|
||||||
|
initCommandUpdate(command, fieldName) {
|
||||||
|
this.$set(this.formEditField, command.id + '_' + fieldName, true);
|
||||||
|
this.$set(this.updateValues, command.id + '_' + fieldName, command[fieldName]);
|
||||||
|
},
|
||||||
|
updateCommand(id, fieldName) {
|
||||||
|
this.$parent.$data.isLoading = true;
|
||||||
|
const data = {};
|
||||||
|
data[fieldName] = this.updateValues[id + '_' + fieldName];
|
||||||
|
commandRepository.updateCommand(id, data)
|
||||||
|
.then(() => {
|
||||||
|
this.$store.dispatch('loadCommands')
|
||||||
|
.then(() => {
|
||||||
|
this.$parent.$data.isLoading = false;
|
||||||
|
this.formEditField[id + '_' + fieldName] = false;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
},
|
||||||
|
deleteCommand(id) {
|
||||||
|
this.$parent.$data.isLoading = true;
|
||||||
|
commandRepository.deleteCommand(id)
|
||||||
|
.then(() => {
|
||||||
|
this.$store.dispatch('loadCommands')
|
||||||
|
.then(() => {
|
||||||
|
this.$parent.$data.isLoading = false;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
},
|
||||||
|
initRecCommandUpdate(command, fieldName) {
|
||||||
|
this.$set(this.formEditField, command.id + '_' + fieldName, true);
|
||||||
|
this.$set(this.updateValues, command.id + '_' + fieldName, command[fieldName]);
|
||||||
|
},
|
||||||
|
updateRecCommand(id, fieldName) {
|
||||||
|
this.$parent.$data.isLoading = true;
|
||||||
|
const data = {};
|
||||||
|
data[fieldName] = this.updateValues[id + '_' + fieldName];
|
||||||
|
recorderRepository.updateRecorderCommand(id, data)
|
||||||
|
.then(() => {
|
||||||
|
this.$store.dispatch('loadRecorderCommands')
|
||||||
|
.then(() => {
|
||||||
|
this.$parent.$data.isLoading = false;
|
||||||
|
this.formEditField[id + '_' + fieldName] = false;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
},
|
||||||
|
showErrorMessage(msg) {
|
||||||
|
this.$parent.$data.isLoading = false;
|
||||||
|
this.$parent.$data.showAlert = true;
|
||||||
|
this.$parent.$data.alertMessage = msg;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
this.$parent.$data.isLoading = true;
|
||||||
|
this.$parent.$data.showAlert = false;
|
||||||
|
this.$store.dispatch('loadCommands')
|
||||||
|
.then(() => {
|
||||||
|
this.$store.dispatch('loadRecorders')
|
||||||
|
.then(() => {
|
||||||
|
this.$parent.$data.isLoading = false;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
options() {
|
||||||
|
return {
|
||||||
|
recorders: [
|
||||||
|
{value: 8, text: 'SMP 351 AudiMax'},
|
||||||
|
{value: 13, text: 'SMP 351 HMU'},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
},
|
||||||
|
commands() {
|
||||||
|
return this.$store.state.recorderCommands;
|
||||||
|
},
|
||||||
|
recorders() {
|
||||||
|
return this.$store.state.recorders;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.comment {
|
||||||
|
white-space: pre-wrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card:hover {
|
||||||
|
background-color: whitesmoke;
|
||||||
|
}
|
||||||
|
|
||||||
|
.required:after {
|
||||||
|
content: " *";
|
||||||
|
}
|
||||||
|
</style>
|
||||||
71
src/components/ErroneousRecorders.vue
Normal file
71
src/components/ErroneousRecorders.vue
Normal file
@@ -0,0 +1,71 @@
|
|||||||
|
<template>
|
||||||
|
<div v-if="authenticated && faulty_recorders_states.length > 0">
|
||||||
|
<h2>Recorders with detected problems:</h2>
|
||||||
|
<b-card-group deck>
|
||||||
|
<b-card bg-variant="danger" text-variant="white" v-for="r_s in faulty_recorders_states" class="mb-2" style="max-width: 30rem; min-width:20rem;"
|
||||||
|
:header="r_s.name"
|
||||||
|
v-bind:key="r_s.id">
|
||||||
|
<b-card-text>
|
||||||
|
<h5 class="card-title">
|
||||||
|
<strong>{{$t('Problem')}}:</strong> {{r_s.msg}}
|
||||||
|
</h5>
|
||||||
|
<b-button :to="{name: 'recorder', params: {recorder_id: r_s.id}}" variant="primary">Go to recorder
|
||||||
|
</b-button>
|
||||||
|
</b-card-text>
|
||||||
|
<div slot="footer">
|
||||||
|
<small>
|
||||||
|
<p>
|
||||||
|
<span v-if="r_s.time_stamp"><strong>{{$t('time stamp')}}:</strong> {{r_s.time_stamp}}</span>
|
||||||
|
</p>
|
||||||
|
</small>
|
||||||
|
</div>
|
||||||
|
</b-card>
|
||||||
|
</b-card-group>
|
||||||
|
<hr/>
|
||||||
|
</div>
|
||||||
|
<div v-else>
|
||||||
|
<p>You must be signed in in order to see recorders!</p>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
import {Component, Prop, Vue} from 'vue-property-decorator';
|
||||||
|
import StateRepository from '@/api/stateRepository';
|
||||||
|
import VirtualCommandRepository from "@/api/virtualCommandRepository";
|
||||||
|
|
||||||
|
@Component
|
||||||
|
export default class ErroneousRecorders extends Vue {
|
||||||
|
|
||||||
|
get authenticated() {
|
||||||
|
return this.$store.getters.isAuthenticated;
|
||||||
|
}
|
||||||
|
|
||||||
|
get faulty_recorders_states() {
|
||||||
|
return this.$store.state.recorderStates.filter((s: any) => {
|
||||||
|
return !s.state_ok
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private mounted() {
|
||||||
|
this.$store.dispatch('loadRecorderStates');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<!-- Add "scoped" attribute to limit CSS to this component only -->
|
||||||
|
<style scoped lang="scss">
|
||||||
|
h3 {
|
||||||
|
margin: 40px 0 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
ul {
|
||||||
|
list-style-type: none;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
li {
|
||||||
|
display: inline-block;
|
||||||
|
margin: 0 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
</style>
|
||||||
@@ -1,6 +1,16 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="hello">
|
<div>
|
||||||
|
<b-alert show dismissible>Default Alert</b-alert>
|
||||||
|
|
||||||
|
<!--<img alt="Vue logo" src="../assets/logo.png">-->
|
||||||
|
<div v-if="authenticated">
|
||||||
|
<h2>Yeah, you are logged in!</h2>
|
||||||
|
</div>
|
||||||
|
<div v-else class="hello">
|
||||||
|
|
||||||
<h1>{{ $t('welcomeMsg') }}</h1>
|
<h1>{{ $t('welcomeMsg') }}</h1>
|
||||||
|
<h3>{{msg}}</h3>
|
||||||
|
<p>Go to <router-link :to="{name: 'login'}">login page</router-link> to login!</p>
|
||||||
<p>
|
<p>
|
||||||
{{ $t('guide') }}<br>
|
{{ $t('guide') }}<br>
|
||||||
{{ $t('checkout') }}
|
{{ $t('checkout') }}
|
||||||
@@ -8,9 +18,12 @@
|
|||||||
</p>
|
</p>
|
||||||
<h3>{{ $t('plugins') }}</h3>
|
<h3>{{ $t('plugins') }}</h3>
|
||||||
<ul>
|
<ul>
|
||||||
<li><a href="https://github.com/vuejs/vue-cli/tree/dev/packages/%40vue/cli-plugin-babel" target="_blank" rel="noopener">babel</a></li>
|
<li><a href="https://github.com/vuejs/vue-cli/tree/dev/packages/%40vue/cli-plugin-babel" target="_blank"
|
||||||
<li><a href="https://github.com/vuejs/vue-cli/tree/dev/packages/%40vue/cli-plugin-typescript" target="_blank" rel="noopener">typescript</a></li>
|
rel="noopener">babel</a></li>
|
||||||
<li><a href="https://github.com/vuejs/vue-cli/tree/dev/packages/%40vue/cli-plugin-unit-mocha" target="_blank" rel="noopener">unit-mocha</a></li>
|
<li><a href="https://github.com/vuejs/vue-cli/tree/dev/packages/%40vue/cli-plugin-typescript"
|
||||||
|
target="_blank" rel="noopener">typescript</a></li>
|
||||||
|
<li><a href="https://github.com/vuejs/vue-cli/tree/dev/packages/%40vue/cli-plugin-unit-mocha"
|
||||||
|
target="_blank" rel="noopener">unit-mocha</a></li>
|
||||||
</ul>
|
</ul>
|
||||||
<h3>{{ $t('links') }}</h3>
|
<h3>{{ $t('links') }}</h3>
|
||||||
<ul>
|
<ul>
|
||||||
@@ -24,11 +37,13 @@
|
|||||||
<ul>
|
<ul>
|
||||||
<li><a href="https://router.vuejs.org" target="_blank" rel="noopener">vue-router</a></li>
|
<li><a href="https://router.vuejs.org" target="_blank" rel="noopener">vue-router</a></li>
|
||||||
<li><a href="https://vuex.vuejs.org" target="_blank" rel="noopener">vuex</a></li>
|
<li><a href="https://vuex.vuejs.org" target="_blank" rel="noopener">vuex</a></li>
|
||||||
<li><a href="https://github.com/vuejs/vue-devtools#vue-devtools" target="_blank" rel="noopener">vue-devtools</a></li>
|
<li><a href="https://github.com/vuejs/vue-devtools#vue-devtools" target="_blank"
|
||||||
|
rel="noopener">vue-devtools</a></li>
|
||||||
<li><a href="https://vue-loader.vuejs.org" target="_blank" rel="noopener">vue-loader</a></li>
|
<li><a href="https://vue-loader.vuejs.org" target="_blank" rel="noopener">vue-loader</a></li>
|
||||||
<li><a href="https://github.com/vuejs/awesome-vue" target="_blank" rel="noopener">awesome-vue</a></li>
|
<li><a href="https://github.com/vuejs/awesome-vue" target="_blank" rel="noopener">awesome-vue</a></li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
@@ -37,6 +52,10 @@ import { Component, Prop, Vue } from 'vue-property-decorator';
|
|||||||
@Component
|
@Component
|
||||||
export default class HelloWorld extends Vue {
|
export default class HelloWorld extends Vue {
|
||||||
@Prop() private msg!: string;
|
@Prop() private msg!: string;
|
||||||
|
|
||||||
|
get authenticated() {
|
||||||
|
return this.$store.getters.isAuthenticated;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
@@ -45,14 +64,17 @@ export default class HelloWorld extends Vue {
|
|||||||
h3 {
|
h3 {
|
||||||
margin: 40px 0 0;
|
margin: 40px 0 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
ul {
|
ul {
|
||||||
list-style-type: none;
|
list-style-type: none;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
li {
|
li {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
margin: 0 10px;
|
margin: 0 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
a {
|
a {
|
||||||
color: #42b983;
|
color: #42b983;
|
||||||
}
|
}
|
||||||
|
|||||||
250
src/components/Login.vue
Normal file
250
src/components/Login.vue
Normal file
@@ -0,0 +1,250 @@
|
|||||||
|
<!-- components/Login.vue -->
|
||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<section class="hero is-primary">
|
||||||
|
<div class="hero-body">
|
||||||
|
<div class="container">
|
||||||
|
<div v-if="!authenticated">
|
||||||
|
|
||||||
|
<div class="has-text-centered">
|
||||||
|
<h2 class="title">Login or Register</h2>
|
||||||
|
<p class="subtitle error-msg">{{ errorMsg }}</p>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!--
|
||||||
|
<ul>
|
||||||
|
<li v-for="(provider, index) in loginProviders" v-bind:id="index">
|
||||||
|
<a :href="provider.url">{{ index }} ({{provider.type}})</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
-->
|
||||||
|
<b-tabs>
|
||||||
|
<b-tab title="KIT Login" active>
|
||||||
|
<p>{{$t('Use your KIT account to connect to LRC. If this is your first login to LRC this creates a new account')}}!</p>
|
||||||
|
|
||||||
|
<div v-if="!this.$isProduction" style="background-color: lightcoral; padding: 10px; margin: 10px;">
|
||||||
|
<h4>DEBUG DEBUG</h4>
|
||||||
|
<button v-on:click="oidc_login">Login via OIDC (function)</button><br/>
|
||||||
|
<a class="btn btn-info" role="button"
|
||||||
|
href="//localhost:5443/api/auth/oidc?redirect_url=/login">OIDC with redirect (localhost)</a><br/>
|
||||||
|
<a href="https://ubkaps154.ubka.uni-karlsruhe.de:5443/api/auth/oidc?redirect_url=/login">OIDC with redirect (ubkaps154.ubka.uni-karlsruhe.de)</a><br/>
|
||||||
|
<br/>
|
||||||
|
</div>
|
||||||
|
<a class="btn btn-primary" role="button" href="/api/auth/oidc?redirect_url=/login">{{$t('Login with KIT account')}}</a>
|
||||||
|
</b-tab>
|
||||||
|
<b-tab title="Alternative Login">
|
||||||
|
<form
|
||||||
|
@submit="checkLoginForm">
|
||||||
|
<div class="field">
|
||||||
|
<label class="label is-large" for="email">Email:</label>
|
||||||
|
<div class="control">
|
||||||
|
<input type="email" class="input is-large" id="email" v-model="email">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="field">
|
||||||
|
<label class="label is-large" for="password">Password:</label>
|
||||||
|
<div class="control">
|
||||||
|
<input type="password" name="current-password" class="input is-large" id="password" v-model="password" autocomplete="on">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="control">
|
||||||
|
<a class="btn btn-primary is-large is-primary" type="button" @click="authenticate">Login</a>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</b-tab>
|
||||||
|
<b-tab :title="$t('Register')" v-bind:disabled="this.$isProduction">
|
||||||
|
<b-form
|
||||||
|
@submit="checkRegisterForm"
|
||||||
|
@reset="resetRegisterForm">
|
||||||
|
<b-form-group
|
||||||
|
id="input-group-1"
|
||||||
|
label="Email address:"
|
||||||
|
label-for="input-1"
|
||||||
|
description="We'll never share your email with anyone else."
|
||||||
|
>
|
||||||
|
<b-form-input
|
||||||
|
id="input-1"
|
||||||
|
v-model="regEmail"
|
||||||
|
type="email"
|
||||||
|
required
|
||||||
|
placeholder="Enter email"
|
||||||
|
></b-form-input>
|
||||||
|
</b-form-group>
|
||||||
|
<b-form-group
|
||||||
|
label-for="text-password"
|
||||||
|
label="Password"
|
||||||
|
>
|
||||||
|
<b-form-input
|
||||||
|
id="text-password"
|
||||||
|
name="current-password"
|
||||||
|
v-model="regPassword"
|
||||||
|
type="password"
|
||||||
|
autocomplete="on"
|
||||||
|
required
|
||||||
|
aria-describedby="password-help-block"></b-form-input>
|
||||||
|
<b-form-text id="password-help-block">
|
||||||
|
Your password must be 8-20 characters long, contain letters and numbers, and must not
|
||||||
|
contain spaces, special characters, or emoji.
|
||||||
|
</b-form-text>
|
||||||
|
</b-form-group>
|
||||||
|
|
||||||
|
<b-button type="submit" variant="primary">{{$t('Register')}}</b-button>
|
||||||
|
<b-button type="reset" variant="danger">{{$t('Reset')}}</b-button>
|
||||||
|
|
||||||
|
</b-form>
|
||||||
|
</b-tab>
|
||||||
|
</b-tabs>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
<div v-else>
|
||||||
|
<h1>{{ $t('You are already logged in!') }}</h1>
|
||||||
|
<h2>{{$t('Redirecting you to')}}
|
||||||
|
<router-link :to="{name: redirectTarget}">{{redirectTarget}}</router-link>
|
||||||
|
{{$t('in')}} {{redirectTime}} {{$t('seconds')}}.
|
||||||
|
</h2>
|
||||||
|
<p><a href="" @click.prevent="logout()">{{$t('logout')}}</a></p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import {EventBus} from '@/utils';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
email: '',
|
||||||
|
password: '',
|
||||||
|
regEmail: '',
|
||||||
|
regPassword: '',
|
||||||
|
errorMsg: '',
|
||||||
|
redirectMsg: '',
|
||||||
|
redirectTarget: 'profile',
|
||||||
|
redirectTime: 5,
|
||||||
|
loginProviders: [],
|
||||||
|
redirect_interval: null,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
authenticate() {
|
||||||
|
this.$store.dispatch('login', {email: this.email, password: this.password})
|
||||||
|
.then(() => this.$router.push('/'))
|
||||||
|
.catch((msg)=> console.error(msg));
|
||||||
|
},
|
||||||
|
register() {
|
||||||
|
this.$store.dispatch('register', {email: this.regEmail, password: this.regPassword})
|
||||||
|
.then(() => this.$router.push('/'))
|
||||||
|
.catch((msg)=> console.error(msg));
|
||||||
|
},
|
||||||
|
oidc_login() {
|
||||||
|
this.$store.dispatch('oidc_login', '\\oidc_login_redirection');
|
||||||
|
},
|
||||||
|
logout() {
|
||||||
|
this.$store.dispatch('logout', {revokeRefreshToken: true});
|
||||||
|
this.$router.push({name: 'home'});
|
||||||
|
},
|
||||||
|
checkLoginForm(e) {
|
||||||
|
if (this.email && this.password){
|
||||||
|
this.authenticate();
|
||||||
|
}
|
||||||
|
this.errors = [];
|
||||||
|
|
||||||
|
if (!this.email){
|
||||||
|
this.errors.push('E-Mail required')
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!this.password){
|
||||||
|
this.errors.push('Password required')
|
||||||
|
}
|
||||||
|
|
||||||
|
e.preventDefault();
|
||||||
|
},
|
||||||
|
checkRegisterForm(e) {
|
||||||
|
e.preventDefault();
|
||||||
|
if (this.regEmail && this.regPassword){
|
||||||
|
this.register();
|
||||||
|
}
|
||||||
|
this.errors = [];
|
||||||
|
|
||||||
|
if (!this.regEmail){
|
||||||
|
this.errors.push('E-Mail required')
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!this.regPassword){
|
||||||
|
this.errors.push('Password required')
|
||||||
|
}
|
||||||
|
|
||||||
|
},
|
||||||
|
resetRegisterForm(){
|
||||||
|
this.regEmail = "";
|
||||||
|
this.regPassword = "";
|
||||||
|
this.errors = [];
|
||||||
|
}
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
// this.$parent.$data.isLoading = true;
|
||||||
|
EventBus.$on('failedRegistering', (msg) => {
|
||||||
|
this.errorMsg = msg;
|
||||||
|
});
|
||||||
|
EventBus.$on('failedAuthentication', (msg) => {
|
||||||
|
this.errorMsg = msg;
|
||||||
|
});
|
||||||
|
EventBus.$on('loginProvidersLoaded', (providers) => {
|
||||||
|
this.loginProviders = providers;
|
||||||
|
});
|
||||||
|
EventBus.$on('storedTokens', () => {
|
||||||
|
this.$store.dispatch('loadUsers');
|
||||||
|
this.$store.dispatch('loadProfile');
|
||||||
|
});
|
||||||
|
|
||||||
|
this.$store.dispatch('loadLoginProviders');
|
||||||
|
|
||||||
|
// get tokens
|
||||||
|
if (this.$cookies.isKey('tokens')) {
|
||||||
|
this.$store.dispatch('storeTokens', JSON.parse(atob(this.$cookies.get('tokens'))));
|
||||||
|
this.$cookies.remove('tokens');
|
||||||
|
}
|
||||||
|
|
||||||
|
this.$nextTick(() => {
|
||||||
|
if (this.authenticated) {
|
||||||
|
if (this.$route.query.redirectionTarget) {
|
||||||
|
this.redirectTarget = this.$route.query.redirectionTarget;
|
||||||
|
}
|
||||||
|
this.redirect_interval = window.setInterval(() => {
|
||||||
|
this.redirectTime = this.redirectTime - 1;
|
||||||
|
if (this.redirectTime < 0) {
|
||||||
|
clearInterval(this.redirect_interval);
|
||||||
|
this.$router.push({name: this.redirectTarget});
|
||||||
|
}
|
||||||
|
}, 1000);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
beforeDestroy() {
|
||||||
|
clearInterval(this.redirect_interval);
|
||||||
|
EventBus.$off('failedRegistering');
|
||||||
|
EventBus.$off('failedAuthentication');
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
users() {
|
||||||
|
return this.$store.state.users;
|
||||||
|
},
|
||||||
|
authenticated() {
|
||||||
|
return this.$store.getters.isAuthenticated;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss">
|
||||||
|
.error-msg {
|
||||||
|
color: red;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
152
src/components/OldLogin.vue
Normal file
152
src/components/OldLogin.vue
Normal file
@@ -0,0 +1,152 @@
|
|||||||
|
<!-- components/Login.vue -->
|
||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<div v-if="!authenticated">
|
||||||
|
<section class="hero is-primary">
|
||||||
|
<div class="hero-body">
|
||||||
|
<div class="container has-text-centered">
|
||||||
|
<h2 class="title">Login or Register</h2>
|
||||||
|
<p class="subtitle error-msg">{{ errorMsg }}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<ul>
|
||||||
|
<li v-for="(provider, index) in loginProviders" v-bind:id="index">
|
||||||
|
<a :href="provider.url">{{ index }} ({{provider.type}})</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<h3>Users</h3>
|
||||||
|
<ul>
|
||||||
|
<li v-for="(user) in users">
|
||||||
|
<a href="#">({{user.id}}) {{user.first_name}}</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
<button v-on:click="oidc_login">Login via OIDC</button>
|
||||||
|
<a href="http://localhost:5443/api/auth/oidc?redirect_url=/login">OIDC with redirect</a>
|
||||||
|
</section>
|
||||||
|
<section class="section">
|
||||||
|
<div class="container">
|
||||||
|
<div class="field">
|
||||||
|
<label class="label is-large" for="email">Email:</label>
|
||||||
|
<div class="control">
|
||||||
|
<input type="email" class="input is-large" id="email" v-model="email">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="field">
|
||||||
|
<label class="label is-large" for="password">Password:</label>
|
||||||
|
<div class="control">
|
||||||
|
<input type="password" class="input is-large" id="password" v-model="password">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="control">
|
||||||
|
<a class="button is-large is-primary" @click="authenticate">Login</a>
|
||||||
|
<a class="button is-large is-success" @click="register">Register</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
</div>
|
||||||
|
<div v-else>
|
||||||
|
<h1>{{ $t('You are already logged in!') }}</h1>
|
||||||
|
<h2>{{$t('Redirecting you to')}} <router-link :to="{name: redirectTarget}">{{redirectTarget}}</router-link> {{$t('in')}} {{redirectTime}} {{$t('seconds')}}.</h2>
|
||||||
|
<p><a href="" @click.prevent="logout()">{{$t('logout')}}</a></p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import {EventBus} from '@/utils';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
email: '',
|
||||||
|
password: '',
|
||||||
|
errorMsg: '',
|
||||||
|
redirectMsg: '',
|
||||||
|
redirectTarget: 'profile',
|
||||||
|
redirectTime: 5,
|
||||||
|
loginProviders: [],
|
||||||
|
redirect_interval: null,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
authenticate() {
|
||||||
|
this.$store.dispatch('login', {email: this.email, password: this.password})
|
||||||
|
.then(() => this.$router.push('/'));
|
||||||
|
},
|
||||||
|
register() {
|
||||||
|
this.$store.dispatch('register', {email: this.email, password: this.password})
|
||||||
|
.then(() => this.$router.push('/'));
|
||||||
|
},
|
||||||
|
oidc_login() {
|
||||||
|
this.$store.dispatch('oidc_login', '\\oidc_login_redirection');
|
||||||
|
},
|
||||||
|
logout() {
|
||||||
|
this.$store.dispatch('logout', {revokeRefreshToken: true});
|
||||||
|
this.$router.push({name: 'home'});
|
||||||
|
},
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
// this.$parent.$data.isLoading = true;
|
||||||
|
EventBus.$on('failedRegistering', (msg) => {
|
||||||
|
this.errorMsg = msg;
|
||||||
|
});
|
||||||
|
EventBus.$on('failedAuthentication', (msg) => {
|
||||||
|
this.errorMsg = msg;
|
||||||
|
});
|
||||||
|
EventBus.$on('loginProvidersLoaded', (providers) => {
|
||||||
|
this.loginProviders = providers;
|
||||||
|
});
|
||||||
|
EventBus.$on('storedTokens', () => {
|
||||||
|
this.$store.dispatch('loadUsers');
|
||||||
|
this.$store.dispatch('loadProfile');
|
||||||
|
});
|
||||||
|
|
||||||
|
this.$store.dispatch('loadLoginProviders');
|
||||||
|
|
||||||
|
// get tokens
|
||||||
|
if (this.$cookies.isKey('tokens')) {
|
||||||
|
this.$store.dispatch('storeTokens', JSON.parse(atob(this.$cookies.get('tokens'))));
|
||||||
|
this.$cookies.remove('tokens');
|
||||||
|
}
|
||||||
|
|
||||||
|
this.$nextTick(() => {
|
||||||
|
if (this.authenticated) {
|
||||||
|
if (this.$route.query.redirectionTarget) {
|
||||||
|
this.redirectTarget = this.$route.query.redirectionTarget;
|
||||||
|
}
|
||||||
|
this.redirect_interval = window.setInterval(() => {
|
||||||
|
this.redirectTime = this.redirectTime - 1;
|
||||||
|
if (this.redirectTime < 0) {
|
||||||
|
clearInterval(this.redirect_interval);
|
||||||
|
this.$router.push({name: this.redirectTarget});
|
||||||
|
}
|
||||||
|
}, 1000);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
beforeDestroy() {
|
||||||
|
clearInterval(this.redirect_interval);
|
||||||
|
EventBus.$off('failedRegistering');
|
||||||
|
EventBus.$off('failedAuthentication');
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
users() {
|
||||||
|
return this.$store.state.users;
|
||||||
|
},
|
||||||
|
authenticated() {
|
||||||
|
return this.$store.getters.isAuthenticated;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss">
|
||||||
|
.error-msg {
|
||||||
|
color: red;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
196
src/components/Profile.vue
Normal file
196
src/components/Profile.vue
Normal file
@@ -0,0 +1,196 @@
|
|||||||
|
<!-- components/Profile.vue -->
|
||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<section class="hero is-primary">
|
||||||
|
<div class="hero-body">
|
||||||
|
<div class="container has-text-centered">
|
||||||
|
<div class="clearfix">
|
||||||
|
<b-img left style="margin-right: 20px;" src="https://picsum.photos/160/160/?image=58"
|
||||||
|
alt="Left image"></b-img>
|
||||||
|
<h2 class="title" v-if="profile.nickname">
|
||||||
|
{{ profile.nickname }}
|
||||||
|
</h2>
|
||||||
|
<h2 class="title" v-else-if="profile.first_name">
|
||||||
|
{{ profile.first_name }} {{ profile.last_name }}
|
||||||
|
</h2>
|
||||||
|
<h2 class="title" v-else>
|
||||||
|
{{ profile.email }}
|
||||||
|
</h2>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
<font-awesome-icon icon="id-badge"/>
|
||||||
|
<span v-b-tooltip.hover :title="$t('external user')" v-if="profile.external_user"> {{ profile.external_user_id }}
|
||||||
|
(<font-awesome-icon :icon="['fab', 'openid']" />)</span>
|
||||||
|
<span v-b-tooltip.hover :title="$t('internal user')" v-else> {{ profile.id }}
|
||||||
|
(<font-awesome-icon :icon="['far', 'dot-circle']"/>)</span>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
<font-awesome-icon icon="envelope"/> {{ profile.email }}
|
||||||
|
<a class="badge badge-pill badge-info">
|
||||||
|
<font-awesome-icon icon="pencil-alt"/>
|
||||||
|
</a>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p v-if="!formEditField['nickname'] && profile.nickname">
|
||||||
|
<font-awesome-icon v-b-tooltip.hover :title="$t('nickname')" icon="user-tag"/> {{ profile.nickname }}
|
||||||
|
<a class="badge badge-pill badge-info"
|
||||||
|
@click="()=>{form.nickname=profile.nickname; formEditField['nickname']=true;}">
|
||||||
|
<font-awesome-icon icon="pencil-alt"/>
|
||||||
|
</a>
|
||||||
|
</p>
|
||||||
|
<ValidationProvider v-slot="v" rules="required" v-else>
|
||||||
|
<b-input-group style="max-width: 50%;" size="sm">
|
||||||
|
|
||||||
|
<b-form-input name="nickname"
|
||||||
|
v-model="form.nickname"
|
||||||
|
class="form-control" type="text"
|
||||||
|
:placeholder="$t('set_your_nickname') + ' ('+profile.nickname +')'"></b-form-input>
|
||||||
|
<b-input-group-append>
|
||||||
|
<b-button :disabled="v.errors.length > 0"
|
||||||
|
@click="updateProfile('nickname')"
|
||||||
|
variant="outline-success">
|
||||||
|
<font-awesome-icon icon="check"></font-awesome-icon>
|
||||||
|
</b-button>
|
||||||
|
</b-input-group-append>
|
||||||
|
</b-input-group>
|
||||||
|
</ValidationProvider>
|
||||||
|
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<hr/>
|
||||||
|
|
||||||
|
<p><strong>{{ $t('first_name') }}: </strong>{{ profile.first_name }}</p>
|
||||||
|
<p><strong>{{ $t('last_name') }}: </strong>{{ profile.last_name }}</p>
|
||||||
|
|
||||||
|
<hr/>
|
||||||
|
<!--<p v-if="profile.role"><strong>{{$t('role')}}: </strong>{{profile.role}}</p>-->
|
||||||
|
<p><strong>{{ $t('role') }}: </strong><span v-if="profile.role">{{ profile.role }}</span><span
|
||||||
|
v-else>{{ $t('no specifc role') }}</span></p>
|
||||||
|
<p><strong>{{ $t('groups') }}: </strong><span v-for="g in profile.groups">{{ g.name }}, </span></p>
|
||||||
|
<p><strong>{{ $t('permissions') }}: </strong></p>
|
||||||
|
<ul>
|
||||||
|
<li v-for="p in profile.effective_permissions">{{ p.name }}</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<hr/>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<button class="lang-btn" v-for="entry in languages" :key="entry.title"
|
||||||
|
@click="changeLocale(entry.language)">
|
||||||
|
<flag :iso="entry.flag" v-bind:squared="false"/>
|
||||||
|
{{ entry.title }}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<p class="title">Token validity ({{ tokenValidity }})</p>
|
||||||
|
<p class="title">Refresh token validity ({{ refreshTokenValidity }})</p>
|
||||||
|
<p class="title">access_token ({{ access_token }})</p>
|
||||||
|
<p>{{ profile.id }}</p>
|
||||||
|
<p class="subtitle error-msg">{{ errorMsg }}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
<!--{{ profile }}-->
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import {EventBus} from '@/utils';
|
||||||
|
import {getRemainingJwtValiditySeconds} from '@/utils';
|
||||||
|
import getRepository from '@/api/RepositoryFactory';
|
||||||
|
//import { extend } from 'vee-validate';
|
||||||
|
import '@/validation';
|
||||||
|
|
||||||
|
|
||||||
|
const userRepository = getRepository('user');
|
||||||
|
|
||||||
|
export default {
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
email: '',
|
||||||
|
errorMsg: '',
|
||||||
|
tokenValidity: -1,
|
||||||
|
refreshTokenValidity: -1,
|
||||||
|
formEditField: {},
|
||||||
|
languages: [
|
||||||
|
{flag: 'us', language: 'en', title: 'English'},
|
||||||
|
{flag: 'es', language: 'es', title: 'Español'},
|
||||||
|
{flag: 'de', language: 'de', title: 'Deutsch'},
|
||||||
|
],
|
||||||
|
form: {
|
||||||
|
first_name: '',
|
||||||
|
last_name: '',
|
||||||
|
nickname: '',
|
||||||
|
email: '',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
authenticate() {
|
||||||
|
this.$store.dispatch('login', {email: this.email, password: this.password})
|
||||||
|
.then(() => this.$router.push('/'));
|
||||||
|
},
|
||||||
|
updateProfile(fieldName) {
|
||||||
|
this.$parent.$data.isLoading = true;
|
||||||
|
this.$set(this.formEditField, fieldName, false);
|
||||||
|
const data = {};
|
||||||
|
data[fieldName] = this.form[fieldName];
|
||||||
|
userRepository.updateProfile(data)
|
||||||
|
.then(() => {
|
||||||
|
this.$store.dispatch('loadProfile')
|
||||||
|
.then(() => {
|
||||||
|
this.$parent.$data.isLoading = false;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
},
|
||||||
|
changeLocale(locale) {
|
||||||
|
this.$i18n.locale = locale;
|
||||||
|
// Vue.$moment.locale(locale);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
EventBus.$on('failedLoadingProfile', (msg) => {
|
||||||
|
this.errorMsg = msg;
|
||||||
|
EventBus.$emit('errorMessage', this.$t('unable.to.load.profile') + ": "+ msg);
|
||||||
|
this.$store.dispatch('resetToken');
|
||||||
|
EventBus.$emit('normalMessage', this.$t('credentials.reset!'));
|
||||||
|
});
|
||||||
|
this.$store.dispatch('loadProfile');
|
||||||
|
|
||||||
|
this.$nextTick(() => {
|
||||||
|
window.setInterval(() => {
|
||||||
|
this.tokenValidity = getRemainingJwtValiditySeconds(this.$store.state.access_token);
|
||||||
|
this.refreshTokenValidity = getRemainingJwtValiditySeconds(this.$store.state.refresh_token);
|
||||||
|
}, 1000);
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
},
|
||||||
|
beforeDestroy() {
|
||||||
|
EventBus.$off('failedLoadingProfile');
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
profile() {
|
||||||
|
return this.$store.state.profile;
|
||||||
|
},
|
||||||
|
access_token() {
|
||||||
|
return this.$store.state.access_token;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss">
|
||||||
|
.error-msg {
|
||||||
|
color: red;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
.lang-btn {
|
||||||
|
padding: 15px;
|
||||||
|
border: 2px solid green;
|
||||||
|
font-size: 18px;
|
||||||
|
margin: 15px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
323
src/components/Recorder.vue
Normal file
323
src/components/Recorder.vue
Normal file
@@ -0,0 +1,323 @@
|
|||||||
|
<!-- components/Profile.vue -->
|
||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<section class="hero is-primary">
|
||||||
|
<div class="hero-body">
|
||||||
|
<div class="container has-text-centered">
|
||||||
|
<h3>
|
||||||
|
<div class="text-center">
|
||||||
|
<font-awesome-icon class="float-left" icon="arrow-circle-left" @click="previousRecorder()"/>
|
||||||
|
<input type="number" style="font-size: small; max-width: 48px; text-align: center;"
|
||||||
|
v-model="new_recorder_id" @blur="manually_set_recorder_id()"
|
||||||
|
@input="manually_set_recorder_id()">
|
||||||
|
<font-awesome-icon class="float-right" icon="arrow-circle-right" @click="nextRecorder()"/>
|
||||||
|
</div>
|
||||||
|
</h3>
|
||||||
|
|
||||||
|
<div class="clearfix">
|
||||||
|
<!--Route params: {{$route.params.recorder_id}}-->
|
||||||
|
<b-img left style="margin-right: 20px;" src="https://picsum.photos/164/164/?image=58"
|
||||||
|
alt="Left image"></b-img>
|
||||||
|
<h2 class="title">
|
||||||
|
<font-awesome-icon v-if="recorder.locked" style="color: red;" icon="lock"
|
||||||
|
@dblclick="unlock()"/>
|
||||||
|
<font-awesome-icon v-else icon="lock-open" style="color: green;" @click="lock()"/>
|
||||||
|
{{recorder.name}}
|
||||||
|
</h2>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
<span v-b-tooltip.hover :title="$t('model_name')">
|
||||||
|
<font-awesome-icon icon="tag"/> {{recorder.model_name ? recorder.model_name : $t("undefined")}}<br/>
|
||||||
|
</span>
|
||||||
|
<span v-b-tooltip.hover :title="$t('firmware_version')">
|
||||||
|
<font-awesome-icon icon="code"/> {{recorder.firmware_version ? recorder.firmware_version : $t("undefined")}}<br/>
|
||||||
|
</span>
|
||||||
|
<span v-b-tooltip.hover :title="$t('additional_camera_connected')">
|
||||||
|
<font-awesome-icon icon="video"/> {{recorder.additional_camera_connected ? $t("yes") : $t("no")}}<br/>
|
||||||
|
</span>
|
||||||
|
<span v-b-tooltip.hover :title="$t('serial_number')">
|
||||||
|
<font-awesome-icon icon="barcode"/> {{recorder.serial_number ? recorder.serial_number : $t("undefined")}}<br/>
|
||||||
|
</span>
|
||||||
|
<span v-b-tooltip.hover :title="$t('room')">
|
||||||
|
<font-awesome-icon icon="home"/> {{recorder.room ? recorder.room.name : $t("undefined")}}
|
||||||
|
</span>
|
||||||
|
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<strong>{{$t('created_at')}}: </strong>{{recorder.created_at}}<br/>
|
||||||
|
<strong>{{$t('last_time_modified')}}: </strong>{{recorder.last_time_modified}}<br/>
|
||||||
|
|
||||||
|
<strong>State: </strong>
|
||||||
|
<span v-if="state">
|
||||||
|
<font-awesome-icon v-if="state.state_ok" style="color: green;" icon="thumbs-up"/>
|
||||||
|
<font-awesome-icon v-else icon="thumbs-down" style="color: red;"/>
|
||||||
|
{{state.msg}} <small>(Last update: {{state.time_stamp}})</small>
|
||||||
|
</span>
|
||||||
|
<span v-else>{{$t('State is unknown')}}!</span>
|
||||||
|
<br/>
|
||||||
|
|
||||||
|
|
||||||
|
<hr/>
|
||||||
|
<h3 style="color: orangered;" v-if="recorder.offline">
|
||||||
|
<font-awesome-icon icon="wrench"/>
|
||||||
|
{{recorder.offline ? 'The recorder is in offline / maintenance mode' : ''}}
|
||||||
|
</h3>
|
||||||
|
<hr v-if="recorder.offline"/>
|
||||||
|
|
||||||
|
<h3 style="color: red;" v-if="recorder.lock_message">
|
||||||
|
<font-awesome-icon icon="lock"/>
|
||||||
|
{{recorder.lock_message}}
|
||||||
|
</h3>
|
||||||
|
<hr v-if="recorder.lock_message"/>
|
||||||
|
|
||||||
|
|
||||||
|
<div role="tablist">
|
||||||
|
<b-card no-body class="mb-1">
|
||||||
|
<b-card-header header-tag="header" class="p-1" role="tab">
|
||||||
|
<b-button block href="#" v-b-toggle.accordion-1 variant="info">Network</b-button>
|
||||||
|
</b-card-header>
|
||||||
|
<b-collapse id="accordion-1" accordion="my-accordion" role="tabpanel">
|
||||||
|
<b-card-body>
|
||||||
|
<strong>{{$t('ip')}}: </strong>{{recorder.ip}}<br/>
|
||||||
|
<strong>{{$t('mac')}}: </strong>{{recorder.mac}}<br/>
|
||||||
|
<strong>{{$t('network_name')}}: </strong>{{recorder.network_name}}<br/>
|
||||||
|
|
||||||
|
<strong>{{$t('ssh_port')}}: </strong>{{recorder.ssh_port}}<br/>
|
||||||
|
<strong>{{$t('telnet_port')}}: </strong>{{recorder.telnet_port}}<br/>
|
||||||
|
</b-card-body>
|
||||||
|
</b-collapse>
|
||||||
|
</b-card>
|
||||||
|
|
||||||
|
<b-card v-if="recorder.description" no-body class="mb-1">
|
||||||
|
<b-card-header header-tag="header" class="p-1" role="tab">
|
||||||
|
<b-button block href="#" v-b-toggle.accordion-2 variant="info">Description</b-button>
|
||||||
|
</b-card-header>
|
||||||
|
<b-collapse id="accordion-2" accordion="my-accordion" role="tabpanel">
|
||||||
|
<b-card-body>
|
||||||
|
<b-card-text>{{recorder.description}}</b-card-text>
|
||||||
|
</b-card-body>
|
||||||
|
</b-collapse>
|
||||||
|
</b-card>
|
||||||
|
|
||||||
|
<b-card v-if="recorderModel" no-body class="mb-1">
|
||||||
|
<b-card-header header-tag="header" class="p-1" role="tab">
|
||||||
|
<b-button block href="#" v-b-toggle.accordion-2 variant="outline-primary">Model /
|
||||||
|
Recorder Adapter Info
|
||||||
|
</b-button>
|
||||||
|
</b-card-header>
|
||||||
|
<b-collapse id="accordion-2" accordion="my-accordion" role="tabpanel">
|
||||||
|
<b-card-body>
|
||||||
|
<strong>{{$t('name')}}: </strong>{{recorderModel.name}}<br/>
|
||||||
|
<strong>{{$t('created_at')}}: </strong>{{recorderModel.created_at}}<br/>
|
||||||
|
<strong>{{$t('last_time_modified')}}: </strong>{{recorderModel.last_time_modified
|
||||||
|
? recorderModel.last_time_modified : 'never'}}<br/>
|
||||||
|
<strong>{{$t('requires_username')}}: </strong>{{recorderModel.requires_username
|
||||||
|
? 'Yes' : 'No'}}<br/>
|
||||||
|
<strong>{{$t('requires_password')}}: </strong>{{recorderModel.requires_password
|
||||||
|
? 'Yes' : 'No'}}<br/>
|
||||||
|
<span v-if="recorderModel.notes"><strong>{{$t('notes')}}: </strong>{{recorderModel.notes}}<br/></span>
|
||||||
|
</b-card-body>
|
||||||
|
</b-collapse>
|
||||||
|
</b-card>
|
||||||
|
|
||||||
|
<b-card v-if="recorderModel" no-body class="mb-1">
|
||||||
|
<b-card-header header-tag="header" class="p-1" role="tab">
|
||||||
|
<b-button block href="#" v-b-toggle.accordion-3 variant="danger">
|
||||||
|
<span>Commands</span>
|
||||||
|
<span v-if="recorder.locked"> ({{$t('locked')}})</span>
|
||||||
|
<span v-if="recorder.offline"> ({{$t('offline')}})</span>
|
||||||
|
</b-button>
|
||||||
|
</b-card-header>
|
||||||
|
<b-collapse id="accordion-3" visible accordion="my-accordion" role="tabpanel">
|
||||||
|
<b-card-body>
|
||||||
|
{{cmd_params}}
|
||||||
|
<b-list-group style="max-height: 400px; overflow-y:scroll;"
|
||||||
|
v-if="!(recorder.locked || recorder.offline)">
|
||||||
|
<b-list-group-item v-for="command in recorderModel.commands"
|
||||||
|
v-bind:key="command.id">
|
||||||
|
<h5>{{command.name}}</h5>
|
||||||
|
<b-row>
|
||||||
|
<b-col sm="3" v-for="(a_type, arg) in command.parameters"
|
||||||
|
v-bind:key="arg"
|
||||||
|
v-if="command.parameters !== null">
|
||||||
|
<b-form-input @focus="setup_params(command.id, arg)"
|
||||||
|
@blur="set_param(command.id, arg, $event.target.value)"
|
||||||
|
:placeholder="arg + ' ('+a_type+')'"
|
||||||
|
:type="a_type==='int'?'number':'text'"
|
||||||
|
style="margin-right: 10px;">
|
||||||
|
<small>{{arg}}: {{a_type}}</small>
|
||||||
|
</b-form-input>
|
||||||
|
</b-col>
|
||||||
|
</b-row>
|
||||||
|
<button class="float-right" @click="execute_command(command.id)">
|
||||||
|
<font-awesome-icon icon="play"/>
|
||||||
|
</button>
|
||||||
|
<p v-if="cmd_res[command.id]">
|
||||||
|
<font-awesome-icon style="color: green;" v-if="cmd_res[command.id].ok"
|
||||||
|
icon="smile"/>
|
||||||
|
<font-awesome-icon style="color: red;" v-else icon="frown"/>
|
||||||
|
<span>{{cmd_res[command.id].time | moment("HH:mm:ss")}}</span>
|
||||||
|
- Output:
|
||||||
|
<strong>
|
||||||
|
<span v-if="cmd_res[command.id].ok">{{cmd_res[command.id].output}}</span>
|
||||||
|
<span v-else>{{cmd_res[command.id].error}}</span>
|
||||||
|
</strong>
|
||||||
|
</p>
|
||||||
|
</b-list-group-item>
|
||||||
|
</b-list-group>
|
||||||
|
<span v-else>The recorder is either locked or in offline mode – commands are disabled!</span>
|
||||||
|
</b-card-body>
|
||||||
|
</b-collapse>
|
||||||
|
</b-card>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import {EventBus} from '@/utils';
|
||||||
|
import getRepository from '@/api/RepositoryFactory';
|
||||||
|
|
||||||
|
const controlRepository = getRepository('control');
|
||||||
|
|
||||||
|
export default {
|
||||||
|
props: ['recorder_id'],
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
current_recorder_id: this.recorder_id == null ? 1 : parseInt(this.recorder_id),
|
||||||
|
current_recorder_index: null,
|
||||||
|
new_recorder_id: this.current_recorder_id,
|
||||||
|
cmd_params: {},
|
||||||
|
cmd_res: {}
|
||||||
|
};
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
setup_params(command_id, argument) {
|
||||||
|
if (!(command_id in this.cmd_params)) {
|
||||||
|
this.$set(this.cmd_params, command_id, {});
|
||||||
|
}
|
||||||
|
this.$set(this.cmd_params[command_id], argument, null)
|
||||||
|
},
|
||||||
|
set_param(command_id, argument, val) {
|
||||||
|
this.$set(this.cmd_params[command_id], argument, val)
|
||||||
|
},
|
||||||
|
manually_set_recorder_id() {
|
||||||
|
if (this.new_recorder_id == null || this.new_recorder_id === '') {
|
||||||
|
this.new_recorder_id = this.current_recorder_id;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (this.recorderIds.includes(parseInt(this.new_recorder_id))) {
|
||||||
|
this.current_recorder_id = parseInt(this.new_recorder_id);
|
||||||
|
}
|
||||||
|
this.new_recorder_id = this.current_recorder_id;
|
||||||
|
},
|
||||||
|
lock() {
|
||||||
|
this.recorder.locked = true;
|
||||||
|
},
|
||||||
|
unlock() {
|
||||||
|
this.recorder.locked = false;
|
||||||
|
},
|
||||||
|
calculate_current_recorder_index() {
|
||||||
|
this.current_recorder_index = this.recorderIds.findIndex((id) => {
|
||||||
|
return id === this.current_recorder_id
|
||||||
|
});
|
||||||
|
},
|
||||||
|
previousRecorder() {
|
||||||
|
if (null == this.current_recorder_index) {
|
||||||
|
this.calculate_current_recorder_index();
|
||||||
|
}
|
||||||
|
if (this.current_recorder_index === 0) this.current_recorder_index = this.recorderIds.length - 1;
|
||||||
|
else this.current_recorder_index = this.current_recorder_index - 1;
|
||||||
|
this.current_recorder_id = this.recorderIds[this.current_recorder_index];
|
||||||
|
this.new_recorder_id = this.current_recorder_id;
|
||||||
|
},
|
||||||
|
nextRecorder() {
|
||||||
|
if (null == this.current_recorder_index) {
|
||||||
|
this.calculate_current_recorder_index();
|
||||||
|
}
|
||||||
|
if (this.current_recorder_index === this.recorderIds.length - 1) this.current_recorder_index = 0;
|
||||||
|
else this.current_recorder_index = this.current_recorder_index + 1;
|
||||||
|
this.current_recorder_id = this.recorderIds[this.current_recorder_index];
|
||||||
|
this.new_recorder_id = this.current_recorder_id;
|
||||||
|
},
|
||||||
|
execute_command(command_id) {
|
||||||
|
console.log("recorder: " + this.current_recorder_id + "; " + command_id);
|
||||||
|
console.log(this.cmd_params);
|
||||||
|
console.log(this.cmd_params[command_id]);
|
||||||
|
this.$parent.$data.isLoading = true;
|
||||||
|
controlRepository.runRecorderCommand(this.current_recorder_id, command_id, this.cmd_params[command_id]).then((out) => {
|
||||||
|
this.$parent.$data.isLoading = false;
|
||||||
|
console.log(out.data);
|
||||||
|
this.$set(this.cmd_res, command_id, out.data);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
this.$store.dispatch('loadRecorders');
|
||||||
|
this.$store.dispatch('loadRecorderModels');
|
||||||
|
this.$store.dispatch('loadRecorderState', this.current_recorder_id);
|
||||||
|
},
|
||||||
|
beforeDestroy() {
|
||||||
|
EventBus.$off('failedLoadingProfile');
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
recorders() {
|
||||||
|
return this.$store.state.recorders;
|
||||||
|
},
|
||||||
|
recorderIds() {
|
||||||
|
let ids = [];
|
||||||
|
this.recorders.forEach((elem) => {
|
||||||
|
ids.push(parseInt(elem.id));
|
||||||
|
});
|
||||||
|
return ids;
|
||||||
|
},
|
||||||
|
recorder() {
|
||||||
|
const recorder = this.$store.state.recorders.filter((item) => {
|
||||||
|
return parseInt(item.id) === this.current_recorder_id;
|
||||||
|
});
|
||||||
|
if (recorder.length < 1) {
|
||||||
|
this.$router.replace({name: 'notFound'});
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
return recorder.shift();
|
||||||
|
},
|
||||||
|
recorderModel() {
|
||||||
|
if (!this.recorder || !this.recorder.recorder_model) return null;
|
||||||
|
const model = this.recorderModels.filter((item) => {
|
||||||
|
return parseInt(item.id) === this.recorder.recorder_model.id;
|
||||||
|
});
|
||||||
|
if (model.length < 1) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return model.shift();
|
||||||
|
},
|
||||||
|
recorderModels() {
|
||||||
|
return this.$store.state.recorderModels;
|
||||||
|
},
|
||||||
|
access_token() {
|
||||||
|
return this.$store.state.access_token;
|
||||||
|
},
|
||||||
|
state() {
|
||||||
|
return this.$store.state.recorderStates.filter((s) => {
|
||||||
|
return parseInt(s.id) === this.current_recorder_id
|
||||||
|
}).pop();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss">
|
||||||
|
.error-msg {
|
||||||
|
color: red;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
.lang-btn {
|
||||||
|
padding: 15px;
|
||||||
|
border: 2px solid green;
|
||||||
|
font-size: 18px;
|
||||||
|
margin: 15px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
134
src/components/RecorderModel.vue
Normal file
134
src/components/RecorderModel.vue
Normal file
@@ -0,0 +1,134 @@
|
|||||||
|
<!-- components/RecorderModel.vue -->
|
||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<section class="hero is-primary">
|
||||||
|
<div class="hero-body">
|
||||||
|
<div class="container has-text-centered">
|
||||||
|
<h3>
|
||||||
|
<div class="text-center">
|
||||||
|
<font-awesome-icon class="float-left" icon="arrow-circle-left" @click="previousRecorderModel()"/>
|
||||||
|
<input type="number" style="font-size: small; max-width: 48px; text-align: center;"
|
||||||
|
v-model="new_recorder_model_id" @blur="manually_set_recorder_model_id()"
|
||||||
|
@input="manually_set_recorder_model_id()">
|
||||||
|
<font-awesome-icon class="float-right" icon="arrow-circle-right" @click="nextRecorderModel()"/>
|
||||||
|
</div>
|
||||||
|
</h3>
|
||||||
|
|
||||||
|
<div class="clearfix">
|
||||||
|
<!--Route params: {{$route.params.recorder_id}}-->
|
||||||
|
<b-img left style="margin-right: 20px;" src="https://picsum.photos/164/164/?image=58"
|
||||||
|
alt="Left image"></b-img>
|
||||||
|
<h2 class="title">
|
||||||
|
{{recorderModel.name}}
|
||||||
|
</h2>
|
||||||
|
|
||||||
|
|
||||||
|
</div>
|
||||||
|
<strong>{{$t('created_at')}}: </strong>{{recorderModel.created_at}}<br/>
|
||||||
|
<strong>{{$t('last_time_modified')}}: </strong>{{recorderModel.last_time_modified}}<br/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import {EventBus} from '@/utils';
|
||||||
|
import getRepository from '@/api/RepositoryFactory';
|
||||||
|
|
||||||
|
const controlRepository = getRepository('control');
|
||||||
|
|
||||||
|
export default {
|
||||||
|
props: ['recorder_model_id'],
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
current_recorder_model_id: this.recorder_model_id == null ? 1 : parseInt(this.recorder_model_id),
|
||||||
|
current_recorder_model_index: null,
|
||||||
|
new_recorder_model_id: this.current_recorder_model_id,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
|
||||||
|
manually_set_recorder_model_id() {
|
||||||
|
if (this.new_recorder_model_id == null || this.new_recorder_model_id === '') {
|
||||||
|
this.new_recorder_model_id = this.current_recorder_model_id;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (this.recorderModelIds.includes(parseInt(this.new_recorder_model_id))) {
|
||||||
|
this.current_recorder_model_id = parseInt(this.new_recorder_model_id);
|
||||||
|
}
|
||||||
|
this.new_recorder_model_id = this.current_recorder_model_id;
|
||||||
|
},
|
||||||
|
calculate_current_recorder_model_index() {
|
||||||
|
this.current_recorder_model_index = this.recorderModelIds.findIndex((id) => {
|
||||||
|
return id === this.current_recorder_model_id
|
||||||
|
});
|
||||||
|
},
|
||||||
|
previousRecorderModel() {
|
||||||
|
if (null == this.current_recorder_model_index) {
|
||||||
|
this.calculate_current_recorder_model_index();
|
||||||
|
}
|
||||||
|
if (this.current_recorder_model_index === 0) this.current_recorder_model_index = this.recorderModelIds.length - 1;
|
||||||
|
else this.current_recorder_model_index = this.current_recorder_model_index - 1;
|
||||||
|
this.current_recorder_model_id = this.recorderModelIds[this.current_recorder_model_index];
|
||||||
|
this.new_recorder_model_id = this.current_recorder_model_id;
|
||||||
|
},
|
||||||
|
nextRecorderModel() {
|
||||||
|
if (null == this.current_recorder_model_index) {
|
||||||
|
this.calculate_current_recorder_model_index();
|
||||||
|
}
|
||||||
|
if (this.current_recorder_model_index === this.recorderModelIds.length - 1) this.current_recorder_model_index = 0;
|
||||||
|
else this.current_recorder_index = this.current_recorder_model_index + 1;
|
||||||
|
this.current_recorder_model_id = this.recorderModelIds[this.current_recorder_model_index];
|
||||||
|
this.new_recorder_model_id = this.current_recorder_model_id;
|
||||||
|
},
|
||||||
|
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
this.$store.dispatch('loadRecorderModels');
|
||||||
|
},
|
||||||
|
beforeDestroy() {
|
||||||
|
EventBus.$off('failedLoadingRecorderModels');
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
recorderModelIds() {
|
||||||
|
let ids = [];
|
||||||
|
this.recorderModels.forEach((elem) => {
|
||||||
|
ids.push(parseInt(elem.id));
|
||||||
|
});
|
||||||
|
return ids;
|
||||||
|
},
|
||||||
|
recorderModel() {
|
||||||
|
const recorderModel = this.$store.state.recorderModels.filter((item) => {
|
||||||
|
return parseInt(item.id) === this.current_recorder_model_id;
|
||||||
|
});
|
||||||
|
if (recorderModel.length < 1) {
|
||||||
|
this.$router.replace({name: 'notFound'});
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
return recorderModel.shift();
|
||||||
|
},
|
||||||
|
recorderModels() {
|
||||||
|
return this.$store.state.recorderModels;
|
||||||
|
},
|
||||||
|
access_token() {
|
||||||
|
return this.$store.state.access_token;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss">
|
||||||
|
.error-msg {
|
||||||
|
color: red;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
.lang-btn {
|
||||||
|
padding: 15px;
|
||||||
|
border: 2px solid green;
|
||||||
|
font-size: 18px;
|
||||||
|
margin: 15px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
117
src/components/RecorderState.vue
Normal file
117
src/components/RecorderState.vue
Normal file
@@ -0,0 +1,117 @@
|
|||||||
|
<template>
|
||||||
|
<b-card class="mb-2" style="max-width: 30rem; min-width:20rem;"
|
||||||
|
:header="recorder.name"
|
||||||
|
v-bind:key="recorder.id">
|
||||||
|
<b-card-text>
|
||||||
|
<h5 class="card-title">
|
||||||
|
<strong>{{ $t('State') }}:</strong> <span v-if="recState.state_ok" style="color: green;">OK</span>
|
||||||
|
<span v-else-if="recState.time_stamp!==''" style="color: red;">ERROR</span><span v-else style="color: orange;">Unknown</span>
|
||||||
|
<a class=" badge badge-pill badge-info" @click="forceUpdate()">
|
||||||
|
Update <font-awesome-icon icon="sync"/>
|
||||||
|
</a><br/>
|
||||||
|
<span v-if="recState.time_stamp"><strong>{{$t('time stamp')}}:</strong> {{recState.time_stamp}}<br/></span>
|
||||||
|
<strong v-if="!recState.state_ok">{{$t('Problem')}}: {{recState.msg}}</strong>
|
||||||
|
<span v-else><strong>{{$t('Message')}}: </strong>{{recState.msg}}</span>
|
||||||
|
<hr/>
|
||||||
|
<div v-if="recState.previous"><small>
|
||||||
|
<strong>{{ $t('Previous State') }}:</strong> <span v-if="recState.previous.state_ok">OK</span>
|
||||||
|
<span v-else-if="recState.previous.time_stamp!==''">ERROR</span><span v-else>Unknown</span>
|
||||||
|
<br/>
|
||||||
|
<span v-if="recState.previous.time_stamp"><strong>{{$t('time stamp')}}:</strong> {{recState.previous.time_stamp}}<br/></span>
|
||||||
|
<strong v-if="!recState.previous.state_ok">{{$t('Problem')}}: {{recState.previous.msg}}</strong>
|
||||||
|
<span v-else><strong>{{$t('Message')}}: </strong>{{recState.previous.msg}}</span>
|
||||||
|
</small>
|
||||||
|
</div>
|
||||||
|
<!--<router-link :to="{ name: 'recorders'}"> ({{$t('recorders')}} 
|
||||||
|
<font-awesome-icon icon="external-link-alt"/>
|
||||||
|
)
|
||||||
|
</router-link>-->
|
||||||
|
</h5>
|
||||||
|
<p v-if="recorder.offline">{{$t('recorder is in offline mode')}}!</p>
|
||||||
|
|
||||||
|
<b-button :to="{name: 'recorder', params: {recorder_id: recorder.id}}" variant="primary">Go to recorder</b-button>
|
||||||
|
</b-card-text>
|
||||||
|
<div slot="footer">
|
||||||
|
<small class="text-muted">
|
||||||
|
<p>{{ $t('created')}}: {{recorder.created_at | moment("dddd, MMMM Do YYYY")}}<br/>
|
||||||
|
{{ $t('last_time_modified')}}: {{recorder.last_time_modified | moment("dddd, MMMM Do YYYY")}}<br/>
|
||||||
|
</p>
|
||||||
|
</small>
|
||||||
|
</div>
|
||||||
|
</b-card>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
props: ['recorder'],
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
connectedWebsocket: false,
|
||||||
|
recState: {
|
||||||
|
state_ok: false,
|
||||||
|
msg: 'unknown (please wait for server update, might take a few minutes)',
|
||||||
|
time_stamp: '',
|
||||||
|
previous: false,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
mounted() {
|
||||||
|
this.$socket.client.on('connect', (msg) => {
|
||||||
|
this.$socket.client.emit('request_recorder_state_updates', this.recorder.id);
|
||||||
|
this.$socket.client.emit('', "unnamed msg!?!");
|
||||||
|
console.log('recorder_state_update_' + this.recorder.id);
|
||||||
|
this.$socket.client.on('recorder_state_update_' + this.recorder.id, (payload) => {
|
||||||
|
this.handleReply(payload);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
if (!this.$socket.connected) {
|
||||||
|
this.connectWebsocket();
|
||||||
|
this.connectedWebsocket = true;
|
||||||
|
} else {
|
||||||
|
this.$socket.client.emit('request_recorder_state_updates', this.recorder.id);
|
||||||
|
this.$socket.client.emit('', "unnamed msg!?!");
|
||||||
|
console.log('recorder_state_update_' + this.recorder.id);
|
||||||
|
this.$socket.client.on('recorder_state_update_' + this.recorder.id, (payload) => {
|
||||||
|
this.handleReply(payload);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
this.forceUpdate();
|
||||||
|
},
|
||||||
|
|
||||||
|
beforeDestroy() {
|
||||||
|
if (this.connectedWebsocket) {
|
||||||
|
this.disconnectWebsocket();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
methods: {
|
||||||
|
forceUpdate() {
|
||||||
|
if (this.$socket.connected){
|
||||||
|
this.$socket.client.emit('force_recorder_state_update', this.recorder.id);
|
||||||
|
this.$socket.client.emit('', "unnamed msg!?!");
|
||||||
|
this.$socket.client.on('recorder_state_update_' + this.recorder.id, (payload) => {
|
||||||
|
this.handleReply(payload);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
connectWebsocket() {
|
||||||
|
this.$socket.client.connect();
|
||||||
|
},
|
||||||
|
|
||||||
|
handleReply(state_reply) {
|
||||||
|
this.recState = JSON.parse(state_reply);
|
||||||
|
},
|
||||||
|
|
||||||
|
disconnectWebsocket() {
|
||||||
|
if (this.$socket.connected) {
|
||||||
|
this.$socket.client.disconnect();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
|
||||||
|
</style>
|
||||||
877
src/components/Recorders.vue
Normal file
877
src/components/Recorders.vue
Normal file
@@ -0,0 +1,877 @@
|
|||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<b-form-group class="container">
|
||||||
|
<section class="section">
|
||||||
|
|
||||||
|
|
||||||
|
<h1 class="title">{{ $t('Manage_recorders') }}</h1>
|
||||||
|
<p class="lead">
|
||||||
|
{{ $t('List_create_and_delete') }} <strong>{{ $t('recorders') }}</strong> {{$t('and')}} {{ $t('recorder_models')
|
||||||
|
}}
|
||||||
|
</p>
|
||||||
|
<br/>
|
||||||
|
|
||||||
|
<b-tabs v-model="tabIndex" card justified>
|
||||||
|
<b-tab title="Recorder list" active>
|
||||||
|
<template slot="title">
|
||||||
|
<font-awesome-icon icon="list"/> <font-awesome-icon icon="circle"/>
|
||||||
|
<strong>{{$t('Recorders')}}</strong> {{$t('list')}}
|
||||||
|
</template>
|
||||||
|
<div class="mt-3">
|
||||||
|
<b-input-group>
|
||||||
|
<b-input-group-prepend>
|
||||||
|
<b-button variant="success" @click="filterShowAllRecorders()">{{$t('All recorders')}}</b-button>
|
||||||
|
<b-button variant="info" @click="filterOnlyShowOfflineRecorders()">{{$t('Offline recorders')}}</b-button>
|
||||||
|
<b-button variant="warning" @click="filterOnlyShowNonOfflineRecorders()">{{$t('Non offline recorders')}}</b-button>
|
||||||
|
</b-input-group-prepend>
|
||||||
|
<b-form-input type="text" v-model="filter"></b-form-input>
|
||||||
|
<b-input-group-append>
|
||||||
|
<b-button variant="outline-primary" @click="filter=''">Clear Filter</b-button>
|
||||||
|
</b-input-group-append>
|
||||||
|
</b-input-group>
|
||||||
|
</div>
|
||||||
|
<p>{{ $tc('recorders_defined', recorders.length, {num: recorders.length})}}:</p>
|
||||||
|
<div class="pagination">
|
||||||
|
<div class="number"
|
||||||
|
v-for="i in num_pages()"
|
||||||
|
v-bind:class="[i === currentPage ? 'active' : '']"
|
||||||
|
v-on:click="change_page(i)">{{i}}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<b-card-group deck>
|
||||||
|
<b-card class="mb-2" style="max-width: 30rem; min-width:20rem;"
|
||||||
|
v-for="(recorder) in get_rows()"
|
||||||
|
v-bind:key="recorder.id">
|
||||||
|
<template v-slot:header>
|
||||||
|
<router-link :to="{name: 'recorder', params: {recorder_id: recorder.id}}">
|
||||||
|
<h4>{{recorder.name}}</h4></router-link>
|
||||||
|
<h6>{{' (' + recorder.ip + ' / ' + recorder.network_name + ')'}}</h6>
|
||||||
|
</template>
|
||||||
|
<b-card-text>
|
||||||
|
<h5 class="card-title"><strong>{{ $t('name') }}: </strong>
|
||||||
|
<span v-if="!formEditField[recorder.id+'_name']">{{recorder.name}}
|
||||||
|
<a class="float-right badge badge-pill badge-primary">
|
||||||
|
<font-awesome-icon
|
||||||
|
@click="initRecorderUpdate(recorder, 'name')"
|
||||||
|
icon="pencil-alt"/>
|
||||||
|
</a>
|
||||||
|
</span>
|
||||||
|
<ValidationProvider :name="$t('name')" rules="required|min:3" v-slot="{ errors }" v-else slim>
|
||||||
|
<b-input-group>
|
||||||
|
<b-form-input :id="recorder.id+'_name'" name="name"
|
||||||
|
v-model="updateValues[recorder.id+'_name']"
|
||||||
|
v-bind:class="{'is-danger': errors.length > 0, 'is-invalid': errors.length > 0}"
|
||||||
|
class="form-control" type="text"
|
||||||
|
:placeholder="'Name ('+recorder.name +')'"
|
||||||
|
@blur="updateRecorder(recorder.id, 'name')"
|
||||||
|
@keyup.enter="updateRecorder(recorder.id, 'name')"
|
||||||
|
required></b-form-input>
|
||||||
|
<b-input-group-append>
|
||||||
|
<b-button :disabled="errors.length > 0"
|
||||||
|
@click="updateRecorder(recorder.id, 'name')"
|
||||||
|
variant="outline-success">
|
||||||
|
<font-awesome-icon icon="check"></font-awesome-icon>
|
||||||
|
</b-button>
|
||||||
|
</b-input-group-append>
|
||||||
|
</b-input-group>
|
||||||
|
</ValidationProvider>
|
||||||
|
</h5>
|
||||||
|
<hr/>
|
||||||
|
<p class="card-text"><strong>{{ $t('Model') }}:</strong>
|
||||||
|
{{recorder.model_name}}
|
||||||
|
</p>
|
||||||
|
<p class="card-text"><strong>{{ $t('firmware_version') }}:</strong>
|
||||||
|
{{recorder.firmware_version}}
|
||||||
|
</p>
|
||||||
|
<p class="card-text"><strong>{{ $t('serial_number') }}:</strong>
|
||||||
|
{{recorder.serial_number}}
|
||||||
|
</p>
|
||||||
|
<p class="card-text"><strong>{{ $t('network_name') }}:</strong>
|
||||||
|
<span v-if="!formEditField[recorder.id+'_network_name']">{{recorder.network_name}}
|
||||||
|
<a class="float-right badge badge-pill badge-info" @click="initRecorderUpdate(recorder, 'network_name')">
|
||||||
|
<font-awesome-icon
|
||||||
|
icon="pencil-alt"/>
|
||||||
|
</a>
|
||||||
|
</span>
|
||||||
|
<ValidationProvider :name="$t('network_name')" rules="required|ip_or_fqdn" v-slot="{ errors }" v-else slim>
|
||||||
|
<b-input-group size="sm">
|
||||||
|
<b-form-input name="network_name"
|
||||||
|
style="min-width: 80%;"
|
||||||
|
v-model="updateValues[recorder.id+'_network_name']"
|
||||||
|
v-bind:class="{'is-danger': errors.length > 0, 'is-invalid': errors.length > 0}"
|
||||||
|
class="form-control" type="text"
|
||||||
|
:placeholder="'Network name ('+recorder.network_name +')'"
|
||||||
|
@keyup.enter="updateRecorder(recorder.id, 'network_name')"></b-form-input>
|
||||||
|
<b-input-group-append>
|
||||||
|
<b-button :disabled="errors.length > 0"
|
||||||
|
@click="updateRecorder(recorder.id, 'network_name')"
|
||||||
|
variant="outline-success">
|
||||||
|
<font-awesome-icon icon="check"></font-awesome-icon>
|
||||||
|
</b-button>
|
||||||
|
</b-input-group-append>
|
||||||
|
<span>{{ errors[0] }}</span>
|
||||||
|
</b-input-group>
|
||||||
|
</ValidationProvider>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p class="card-text"><strong>{{ $t('ip') }}:</strong>
|
||||||
|
<span v-if="!formEditField[recorder.id+'_ip']">{{recorder.ip}}
|
||||||
|
<a class="float-right badge badge-pill badge-info" @click="initRecorderUpdate(recorder, 'ip')">
|
||||||
|
<font-awesome-icon
|
||||||
|
icon="pencil-alt"/>
|
||||||
|
</a>
|
||||||
|
</span>
|
||||||
|
<ValidationProvider :name="$t('ip')" rules="ip" v-slot="{ errors }" v-else slim>
|
||||||
|
<b-input-group size="sm">
|
||||||
|
<b-form-input name="ip"
|
||||||
|
v-model="updateValues[recorder.id+'_ip']"
|
||||||
|
v-bind:class="{'is-danger': errors.length > 0, 'is-invalid': errors.length > 0}"
|
||||||
|
class="form-control" type="text"
|
||||||
|
:placeholder="$t('ip address') +' ('+recorder.ip +')'"
|
||||||
|
@keyup.enter="updateRecorder(recorder.id, 'ip')"
|
||||||
|
required></b-form-input>
|
||||||
|
<b-input-group-append>
|
||||||
|
<b-button :disabled="errors.length > 0"
|
||||||
|
@click="updateRecorder(recorder.id, 'ip')"
|
||||||
|
variant="outline-success">
|
||||||
|
<font-awesome-icon icon="check"></font-awesome-icon>
|
||||||
|
</b-button>
|
||||||
|
</b-input-group-append>
|
||||||
|
<span>{{ errors[0] }}</span>
|
||||||
|
</b-input-group>
|
||||||
|
</ValidationProvider>
|
||||||
|
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p class="card-text"><strong>{{ $t('ip v6') }}:</strong>
|
||||||
|
<span v-if="!formEditField[recorder.id+'_ip6']">{{recorder.ip6}}
|
||||||
|
<a class="float-right badge badge-pill badge-info" @click="initRecorderUpdate(recorder, 'ip6')">
|
||||||
|
<font-awesome-icon
|
||||||
|
icon="pencil-alt"/>
|
||||||
|
</a>
|
||||||
|
</span>
|
||||||
|
<ValidationProvider :name="$t('ip6')" rules="ip6" v-slot="{ errors }" v-else slim>
|
||||||
|
<b-input-group size="sm">
|
||||||
|
<b-form-input name="ip6"
|
||||||
|
v-model="updateValues[recorder.id+'_ip6']"
|
||||||
|
v-bind:class="{'is-danger': errors.length > 0, 'is-invalid': errors.length > 0}"
|
||||||
|
class="form-control" type="text"
|
||||||
|
:placeholder="$t('ip v6 address') +' ('+recorder.ip6 +')'"
|
||||||
|
@keyup.enter="updateRecorder(recorder.id, 'ip6')"
|
||||||
|
required></b-form-input>
|
||||||
|
<b-input-group-append>
|
||||||
|
<b-button :disabled="errors.length > 0"
|
||||||
|
@click="updateRecorder(recorder.id, 'ip6')"
|
||||||
|
variant="outline-success">
|
||||||
|
<font-awesome-icon icon="check"></font-awesome-icon>
|
||||||
|
</b-button>
|
||||||
|
</b-input-group-append>
|
||||||
|
<span>{{ errors[0] }}</span>
|
||||||
|
</b-input-group>
|
||||||
|
</ValidationProvider>
|
||||||
|
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p class="card-text"><strong>{{ $t('ssh_port') }}:</strong>
|
||||||
|
<span v-if="!formEditField[recorder.id+'_ssh_port']">{{recorder.ssh_port}}
|
||||||
|
<a class="float-right badge badge-pill badge-info" @click="initRecorderUpdate(recorder, 'ssh_port')">
|
||||||
|
<font-awesome-icon
|
||||||
|
icon="pencil-alt"/>
|
||||||
|
</a>
|
||||||
|
</span>
|
||||||
|
<ValidationProvider :name="$t('ssh_port')" rules="required|between:0,65535" v-slot="{ errors }" v-else slim>
|
||||||
|
<b-input-group size="sm">
|
||||||
|
<b-form-input name="ssh_port"
|
||||||
|
v-model="updateValues[recorder.id+'_ssh_port']"
|
||||||
|
v-bind:class="{'is-danger': errors.length > 0, 'is-invalid': errors.length > 0}"
|
||||||
|
class="form-control" type="text"
|
||||||
|
:placeholder="'SSH Port ('+recorder.ssh_port +')'"
|
||||||
|
@blur="updateRecorder(recorder.id, 'ssh_port')"
|
||||||
|
@keyup.enter="updateRecorder(recorder.id, 'ssh_port')"
|
||||||
|
required></b-form-input>
|
||||||
|
<b-input-group-append>
|
||||||
|
<b-button :disabled="errors.length > 0"
|
||||||
|
@click="updateRecorder(recorder.id, 'ssh_port')"
|
||||||
|
variant="outline-success">
|
||||||
|
<font-awesome-icon icon="check"></font-awesome-icon>
|
||||||
|
</b-button>
|
||||||
|
</b-input-group-append>
|
||||||
|
<span>{{ errors[0] }}</span>
|
||||||
|
</b-input-group>
|
||||||
|
|
||||||
|
</ValidationProvider>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p class="card-text"><strong>{{ $t('telnet_port') }}:</strong>
|
||||||
|
<span v-if="!formEditField[recorder.id+'_telnet_port']">{{recorder.telnet_port}}
|
||||||
|
<a class="float-right badge badge-pill badge-info">
|
||||||
|
<font-awesome-icon
|
||||||
|
@click="initRecorderUpdate(recorder, 'telnet_port')"
|
||||||
|
icon="pencil-alt"/>
|
||||||
|
</a>
|
||||||
|
</span>
|
||||||
|
<ValidationProvider :name="$t('telnet_port')" rules="required|between:0,65535" v-slot="{ errors }" v-else slim>
|
||||||
|
<b-input-group size="sm">
|
||||||
|
<b-form-input name="telnet_port"
|
||||||
|
v-model="updateValues[recorder.id+'_telnet_port']"
|
||||||
|
v-bind:class="{'is-danger': errors.length > 0, 'is-invalid': errors.length > 0}"
|
||||||
|
class="form-control" type="text"
|
||||||
|
:placeholder="'Telnet Port ('+recorder.telnet_port +')'"
|
||||||
|
@blur="updateRecorder(recorder.id, 'telnet_port')"
|
||||||
|
@keyup.enter="updateRecorder(recorder.id, 'telnet_port')"
|
||||||
|
required></b-form-input>
|
||||||
|
<b-input-group-append>
|
||||||
|
<b-button :disabled="errors.length > 0"
|
||||||
|
@click="updateRecorder(recorder.id, 'telnet_port')"
|
||||||
|
variant="outline-success">
|
||||||
|
<font-awesome-icon icon="check"></font-awesome-icon>
|
||||||
|
</b-button>
|
||||||
|
</b-input-group-append>
|
||||||
|
<span>{{ errors[0] }}</span>
|
||||||
|
</b-input-group>
|
||||||
|
</ValidationProvider>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<hr/>
|
||||||
|
<div v-if="recorder.room && !formEditField[recorder.id+'_room_id']">
|
||||||
|
<p class="card-text"><strong>{{ $t('Room') }}:</strong> {{recorder.room.name}}
|
||||||
|
<a class="float-right badge badge-pill badge-info" @click="initRecorderUpdate(recorder, 'room_id')">
|
||||||
|
<font-awesome-icon
|
||||||
|
icon="pencil-alt"/>
|
||||||
|
</a></p>
|
||||||
|
</div>
|
||||||
|
<div v-else>
|
||||||
|
<p class="card-text"><strong>{{ $t('Room') }}:</strong>
|
||||||
|
<span v-if="recorder.room && recorder.room.name">{{recorder.room.name}}</span>
|
||||||
|
<span v-else>{{$t('no_room_defined')}}</span>
|
||||||
|
</p>
|
||||||
|
<div class="form-group row">
|
||||||
|
<div class="col-sm-8">
|
||||||
|
<select class="form-control"
|
||||||
|
v-model="updateValues[recorder.id+'_room_id']"
|
||||||
|
@change="updateRecorder(recorder.id, 'room_id')">
|
||||||
|
<option value="">{{$t('No room selected')}}</option>
|
||||||
|
<option v-for="room in rooms" v-bind:value="room.id">
|
||||||
|
{{ room.building_number + " " + room.name }}
|
||||||
|
</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<label class="label col-sm-4 col-form-label">{{ $t('room') }}</label>
|
||||||
|
</div>
|
||||||
|
<div class="form-check">
|
||||||
|
<input class="form-check-input" type="checkbox"
|
||||||
|
v-model="show_assigned_rooms" id="assignedRoomsCheck">
|
||||||
|
<label class="form-check-label" for="assignedRoomsCheck">
|
||||||
|
Show already assigned rooms
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<hr/>
|
||||||
|
<b-form-group label-cols-sm="4"
|
||||||
|
label-cols-lg="3"
|
||||||
|
description="Set to offline / maintenance mode."
|
||||||
|
label-for="offline"
|
||||||
|
:label="$t('offline')">
|
||||||
|
<b-form-checkbox :id="'offline' + recorder.id"
|
||||||
|
v-model="recorder.offline" name="check-button_offline" switch
|
||||||
|
@input="()=>{initRecorderUpdate(recorder, 'offline'); updateRecorder(recorder.id, 'offline')}">
|
||||||
|
<b>({{$t('Offline')}}: {{ recorder.offline }})</b>
|
||||||
|
</b-form-checkbox>
|
||||||
|
</b-form-group>
|
||||||
|
|
||||||
|
<b-form-group label-cols-sm="4"
|
||||||
|
label-cols-lg="3"
|
||||||
|
description="Lock recorder: No changes can be made."
|
||||||
|
label-for="locked"
|
||||||
|
:label="$t('locked')">
|
||||||
|
<b-form-checkbox :id="'locked' + recorder.id"
|
||||||
|
v-model="recorder.locked" name="check-button_locked" switch
|
||||||
|
@input="()=>{initRecorderUpdate(recorder, 'locked'); updateRecorder(recorder.id, 'locked')}">
|
||||||
|
<b>({{$t('Locked')}}: {{ recorder.locked }})</b>
|
||||||
|
</b-form-checkbox>
|
||||||
|
</b-form-group>
|
||||||
|
|
||||||
|
<strong>{{ $t('lock_message') }}:</strong>
|
||||||
|
<span v-if="!formEditField[recorder.id+'_lock_message']">{{recorder.lock_message}}
|
||||||
|
<a class="float-right badge badge-pill badge-info">
|
||||||
|
<font-awesome-icon
|
||||||
|
@click="initRecorderUpdate(recorder, 'lock_message')"
|
||||||
|
icon="pencil-alt"/>
|
||||||
|
</a>
|
||||||
|
</span>
|
||||||
|
<textarea class="textarea form-control" v-else name="lock_message"
|
||||||
|
v-model="updateValues[recorder.id+'_lock_message']"
|
||||||
|
:placeholder="$t('lock_message') +' ('+recorder.lock_message +')'"
|
||||||
|
@blur="updateRecorder(recorder.id, 'lock_message')"></textarea>
|
||||||
|
|
||||||
|
<hr/>
|
||||||
|
<div v-if="recorder.recorder_model">
|
||||||
|
<p class="card-text"><strong>{{ $t('Model Adadpter') }}:</strong> {{recorder.recorder_model.name}}
|
||||||
|
<a class="float-right badge badge-pill badge-info">
|
||||||
|
<font-awesome-icon icon="pencil-alt"/>
|
||||||
|
</a></p>
|
||||||
|
</div>
|
||||||
|
<div v-else>
|
||||||
|
<p class="card-text"><strong>{{ $t('Model Adadpter') }}:</strong> {{
|
||||||
|
$t('no_model_defined')}}</p>
|
||||||
|
<div class="form-group row">
|
||||||
|
<div class="col-sm-8">
|
||||||
|
<select class="form-control" v-model="form.recorder_model">
|
||||||
|
<option value="">{{$t('No Model Adadpter selected')}}</option>
|
||||||
|
<option v-for="recorderModel in recorderModels"
|
||||||
|
v-bind:value="recorderModel.id">
|
||||||
|
{{ recorderModel.name }}
|
||||||
|
</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<label class="label col-sm-4 col-form-label">{{ $t('model') }}</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<hr/>
|
||||||
|
|
||||||
|
<strong>{{ $t('description') }}:</strong>
|
||||||
|
<span v-if="!formEditField[recorder.id+'_description']">{{recorder.description}}
|
||||||
|
<a class="float-right badge badge-pill badge-info" @click="initRecorderUpdate(recorder, 'description')">
|
||||||
|
<font-awesome-icon
|
||||||
|
icon="pencil-alt"/>
|
||||||
|
</a>
|
||||||
|
</span>
|
||||||
|
<textarea class="textarea form-control" v-else name="description"
|
||||||
|
v-model="updateValues[recorder.id+'_description']"
|
||||||
|
:placeholder="$t('description') +' ('+recorder.description +')'"
|
||||||
|
@blur="updateRecorder(recorder.id, 'description')"></textarea>
|
||||||
|
|
||||||
|
<hr/>
|
||||||
|
|
||||||
|
<strong>{{ $t('virtual_commands') }}:</strong>
|
||||||
|
|
||||||
|
<b-list-group v-if="recorder.virtual_commands.length"
|
||||||
|
style="max-height: 400px; overflow-y:scroll;">
|
||||||
|
<b-list-group-item v-for="command in recorder.virtual_commands"
|
||||||
|
v-bind:key="command.id">
|
||||||
|
{{command.name}}
|
||||||
|
</b-list-group-item>
|
||||||
|
|
||||||
|
</b-list-group>
|
||||||
|
<span>
|
||||||
|
{{$t('No virtual commands defined yet.')}}
|
||||||
|
</span>
|
||||||
|
|
||||||
|
</b-card-text>
|
||||||
|
<div slot="footer">
|
||||||
|
<small class="text-muted">
|
||||||
|
<p>{{ $t('created')}}: {{recorder.created_at | moment("dddd, MMMM Do YYYY")}}<br/>
|
||||||
|
{{ $t('last_time_modified')}}: {{recorder.last_time_modified | moment("dddd, MMMM Do YYYY")}}<br/>
|
||||||
|
<span class="pull-right">
|
||||||
|
<button type="button" v-on:click="deleteRecorder(recorder.id)"
|
||||||
|
class="btn btn-sm btn-danger">{{
|
||||||
|
$t('delete') }} <font-awesome-icon
|
||||||
|
icon="trash"/>
|
||||||
|
</button>
|
||||||
|
</span>
|
||||||
|
</p>
|
||||||
|
</small>
|
||||||
|
</div>
|
||||||
|
</b-card>
|
||||||
|
</b-card-group>
|
||||||
|
</b-tab>
|
||||||
|
<b-tab title="Create recorder">
|
||||||
|
<template slot="title">
|
||||||
|
<font-awesome-icon icon="plus"/>
|
||||||
|
<i>{{ $t('create') }}</i> <font-awesome-icon icon="circle"/>
|
||||||
|
<strong>{{ $t('Recorder') }}</strong>
|
||||||
|
</template>
|
||||||
|
<b-card-text>
|
||||||
|
<p>{{ $t('Create_a_new_recorder')}}:</p>
|
||||||
|
<!-- form starts here -->
|
||||||
|
<ValidationObserver v-slot="{ invalid }">
|
||||||
|
<form v-on:submit.prevent="saveRecorder()">
|
||||||
|
<section class="form">
|
||||||
|
|
||||||
|
<ValidationProvider :name="$t('name')" rules="required|min:3" v-slot="{ errors }" slim>
|
||||||
|
<div class="form-group row">
|
||||||
|
<label class="label required col-sm-2 col-form-label">{{ $t('name') }}</label>
|
||||||
|
<div class="col-sm-6">
|
||||||
|
<input name="name"
|
||||||
|
v-model="form.name"
|
||||||
|
v-bind:class="{'is-danger': errors.length > 0, 'is-invalid': errors.length > 0}"
|
||||||
|
class="form-control" type="text" :placeholder="$t('Recorder_name')"
|
||||||
|
required>
|
||||||
|
</div>
|
||||||
|
<p class="col-sm-4" v-show="errors.length > 0">
|
||||||
|
{{ errors[0] }}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</ValidationProvider>
|
||||||
|
|
||||||
|
|
||||||
|
<div class="form-group row">
|
||||||
|
<label class="label col-sm-2 col-form-label">{{ $t('description') }}</label>
|
||||||
|
<div class="col-sm-6">
|
||||||
|
<textarea class="textarea form-control"
|
||||||
|
:placeholder="$t('Comments') +', ' + $t('notes') + ', etc.'"
|
||||||
|
v-model="form.description"></textarea>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<ValidationProvider :name="$t('network_name')" rules="required|ip_or_fqdn" v-slot="{ errors }" slim>
|
||||||
|
<div class="form-group row">
|
||||||
|
<label class="label required col-sm-2 col-form-label">{{ $t('network_name') }}</label>
|
||||||
|
<div class="col-sm-6">
|
||||||
|
<input name="network_name"
|
||||||
|
v-model="form.network_name"
|
||||||
|
v-bind:class="{'is-danger': errors.length > 0, 'is-invalid': errors.length > 0}"
|
||||||
|
class="form-control" type="text" :placeholder="$t('network_name')"
|
||||||
|
required
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
<p class="col-sm-4" v-show="errors.length > 0">
|
||||||
|
{{ errors[0] }}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</ValidationProvider>
|
||||||
|
|
||||||
|
<ValidationProvider :name="$t('mac')" rules="mac" v-slot="{ errors }" slim>
|
||||||
|
<div class="form-group row">
|
||||||
|
<label class="label col-sm-2 col-form-label">{{ $t('mac') }}</label>
|
||||||
|
<div class="col-sm-6">
|
||||||
|
<input name="mac"
|
||||||
|
v-model="form.mac"
|
||||||
|
v-bind:class="{'is-danger': errors.length > 0, 'is-invalid': errors.length > 0}"
|
||||||
|
class="form-control" type="text" :placeholder="$t('Recorder') + ' ' + $t('mac')">
|
||||||
|
</div>
|
||||||
|
<p class="col-sm-4" v-show="errors.length > 0">
|
||||||
|
{{ errors[0] }}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</ValidationProvider>
|
||||||
|
|
||||||
|
<ValidationProvider :name="$t('ip')" rules="ip" v-slot="{ errors }" slim>
|
||||||
|
<div class="form-group row">
|
||||||
|
<label class="label col-sm-2 col-form-label">{{ $t('ip') }}</label>
|
||||||
|
<div class="col-sm-6">
|
||||||
|
<input name="ip"
|
||||||
|
v-model="form.ip"
|
||||||
|
v-bind:class="{'is-danger': errors.length > 0, 'is-invalid': errors.length > 0}"
|
||||||
|
class="form-control" type="text" :placeholder="$t('Recorder') + ' ' + $t('ip')">
|
||||||
|
</div>
|
||||||
|
<p class="col-sm-4" v-show="errors.length > 0">
|
||||||
|
{{ errors[0] }}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</ValidationProvider>
|
||||||
|
|
||||||
|
<ValidationProvider :name="$t('ipv6')" rules="ip6" v-slot="{ errors }" slim>
|
||||||
|
<div class="form-group row">
|
||||||
|
<label class="label col-sm-2 col-form-label">{{ $t('ipv6') }}</label>
|
||||||
|
<div class="col-sm-6">
|
||||||
|
<input name="ip6"
|
||||||
|
v-model="form.ip6"
|
||||||
|
v-bind:class="{'is-danger': errors.length > 0, 'is-invalid': errors.length > 0}"
|
||||||
|
class="form-control" type="text" :placeholder="$t('Recorder') + ' ' + $t('ipv6')">
|
||||||
|
</div>
|
||||||
|
<p class="col-sm-4" v-show="errors.length > 0">
|
||||||
|
{{ errors[0] }}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</ValidationProvider>
|
||||||
|
|
||||||
|
<ValidationProvider :name="$t('telnet_port')" rules="required|between:0,65535" v-slot="{ errors }" slim>
|
||||||
|
<div class="form-group row">
|
||||||
|
<label class="label col-sm-2 col-form-label">{{ $t('telnet_port') }}</label>
|
||||||
|
<div class="col-sm-6">
|
||||||
|
<input name="telnet_port"
|
||||||
|
v-model="form.telnet_port"
|
||||||
|
v-bind:class="{'is-danger': errors.length > 0, 'is-invalid': errors.length > 0}"
|
||||||
|
class="form-control" type="number" value="22"
|
||||||
|
placeholder="Telnet Port (23)">
|
||||||
|
</div>
|
||||||
|
<p class="col-sm-4" v-show="errors.length > 0">
|
||||||
|
{{ errors[0] }}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</ValidationProvider>
|
||||||
|
|
||||||
|
<ValidationProvider :name="$t('ssh_port')" rules="required|between:0,65535" v-slot="{ errors }" slim>
|
||||||
|
<div class="form-group row">
|
||||||
|
<label class="label col-sm-2 col-form-label">{{ $t('ssh_port') }}</label>
|
||||||
|
<div class="col-sm-6">
|
||||||
|
<input name="ssh_port"
|
||||||
|
v-model="form.ssh_port"
|
||||||
|
v-bind:class="{'is-danger': errors.length > 0, 'is-invalid': errors.length > 0}"
|
||||||
|
class="form-control" type="number" value="23"
|
||||||
|
placeholder="SSH Port (22)">
|
||||||
|
</div>
|
||||||
|
<p class="col-sm-4" v-show="errors.length > 0">
|
||||||
|
{{ errors[0] }}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</ValidationProvider>
|
||||||
|
|
||||||
|
<div class="form-group row">
|
||||||
|
<label class="label col-sm-2 col-form-label">{{ $t('Model') }}</label>
|
||||||
|
<div class="col-sm-6">
|
||||||
|
<select class="form-control" v-model="form.recorder_model_id">
|
||||||
|
<option disabled value="">{{$t('No_recorder_model_selected')}}</option>
|
||||||
|
<option v-for="recorderModel in recorderModels"
|
||||||
|
v-bind:value="recorderModel.id">
|
||||||
|
{{ recorderModel.name }}
|
||||||
|
</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-group row">
|
||||||
|
<label class="label col-sm-2 col-form-label">{{ $t('room') }}</label>
|
||||||
|
<div class="col-sm-6">
|
||||||
|
<select class="form-control" v-model="form.room_id">
|
||||||
|
<option disabled value="">Not assigned to room</option>
|
||||||
|
<option v-for="room in rooms" v-bind:value="room.id">
|
||||||
|
{{ room.name }}
|
||||||
|
</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<ValidationProvider :name="$t('username')" rules="" v-slot="{ errors }" slim>
|
||||||
|
<div class="form-group row">
|
||||||
|
<label class="label col-sm-2 col-form-label">{{ $t('username') }}</label>
|
||||||
|
<div class="col-sm-6">
|
||||||
|
<input name="username"
|
||||||
|
v-model="form.username"
|
||||||
|
v-bind:class="{'is-danger': errors.length > 0, 'is-invalid': errors.length > 0}"
|
||||||
|
class="form-control" type="text" :placeholder="$t('username')"
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
<p class="col-sm-4">
|
||||||
|
{{$t('may_be_left_blank')}} (-> {{$t('Model')}})
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</ValidationProvider>
|
||||||
|
|
||||||
|
<ValidationProvider :name="$t('password')" rules="" v-slot="{ errors }" slim>
|
||||||
|
<div class="form-group row">
|
||||||
|
<label class="label col-sm-2 col-form-label">{{ $t('password') }}</label>
|
||||||
|
<div class="col-sm-6">
|
||||||
|
<input name="password"
|
||||||
|
v-model="form.password"
|
||||||
|
v-bind:class="{'is-danger': errors.length > 0, 'is-invalid': errors.length > 0}"
|
||||||
|
class="form-control" type="text" :placeholder="$t('password')"
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
<p class="col-sm-4">
|
||||||
|
{{$t('may_be_left_blank')}} (-> {{$t('Model')}})
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</ValidationProvider>
|
||||||
|
|
||||||
|
|
||||||
|
<div class="field is-grouped">
|
||||||
|
<div class="control">
|
||||||
|
<button
|
||||||
|
v-bind:disabled="invalid"
|
||||||
|
type="submit"
|
||||||
|
class="btn btn-primary">
|
||||||
|
Create recorder
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</section>
|
||||||
|
</form>
|
||||||
|
</ValidationObserver>
|
||||||
|
</b-card-text>
|
||||||
|
</b-tab>
|
||||||
|
<b-tab title="Recorder model list">
|
||||||
|
<template slot="title">
|
||||||
|
<font-awesome-icon icon="list"/> {{$t('Recorder')}} <font-awesome-icon icon="scroll"/>
|
||||||
|
<strong>{{$t('Model')}}</strong> <i>{{$t('list')}}</i>
|
||||||
|
</template>
|
||||||
|
<p>{{ $tc('recorder_models_defined', recorderModels.length, {num: recorderModels.length} ) }}:</p>
|
||||||
|
<b-card-group deck>
|
||||||
|
<b-card class="mb-2" style="max-width: 30rem; min-width:20rem;"
|
||||||
|
v-for="(recorderModel) in recorderModels"
|
||||||
|
v-bind:key="recorderModel.id">
|
||||||
|
<template v-slot:header>
|
||||||
|
<router-link :to="{name: 'recorder_model', params: {recorder_model_id: recorderModel.id}}">
|
||||||
|
<h4>{{recorderModel.name}}</h4></router-link>
|
||||||
|
</template>
|
||||||
|
<b-card-text>
|
||||||
|
<p class="card-text">
|
||||||
|
<strong>{{ $t('notes') }}:</strong>
|
||||||
|
<span v-if="!formRecModelEditField[recorderModel.id+'_notes']">{{recorderModel.notes}}
|
||||||
|
<a class="float-right badge badge-pill badge-info" @click="initRecorderModelUpdate(recorderModel, 'notes')">
|
||||||
|
<font-awesome-icon
|
||||||
|
icon="pencil-alt"/>
|
||||||
|
</a>
|
||||||
|
</span>
|
||||||
|
<textarea class="textarea form-control" v-else name="notes"
|
||||||
|
v-model="updateRecModelValues[recorderModel.id+'_notes']"
|
||||||
|
:placeholder="'Notes ('+recorderModel.notes +')'"
|
||||||
|
@blur="updateRecorderModel(recorderModel.id, 'notes')"></textarea>
|
||||||
|
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p class="card-text">
|
||||||
|
<strong>{{ $t('requires_username') }}:</strong> <span
|
||||||
|
v-if="recorderModel.requires_username">{{$t('yes')}}</span><span v-else>{{$t('no')}}</span>
|
||||||
|
</p>
|
||||||
|
<p class="card-text">
|
||||||
|
<strong>{{ $t('requires_password') }}:</strong> <span
|
||||||
|
v-if="recorderModel.requires_password">{{$t('yes')}}</span><span v-else>{{$t('no')}}</span>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<hr/>
|
||||||
|
|
||||||
|
<b-list-group style="max-height: 400px; overflow-y:scroll;">
|
||||||
|
<strong>Recorder Model {{ $t('commands') }}:</strong>
|
||||||
|
<b-list-group-item v-for="command in recorderModel.commands"
|
||||||
|
v-bind:key="command.id">
|
||||||
|
{{command.name}}
|
||||||
|
<b-badge v-for="(a_type, arg) in command.parameters"
|
||||||
|
v-bind:key="arg"
|
||||||
|
v-if="command.parameters !== null" style="margin-right: 10px;">
|
||||||
|
<small>{{arg}}: {{a_type}}</small>
|
||||||
|
</b-badge>
|
||||||
|
|
||||||
|
</b-list-group-item>
|
||||||
|
</b-list-group>
|
||||||
|
|
||||||
|
</b-card-text>
|
||||||
|
<div slot="footer">
|
||||||
|
<small class="text-muted">
|
||||||
|
<p>{{ $t('created')}}: {{recorderModel.created_at | moment("dddd, MMMM Do YYYY")}}</p>
|
||||||
|
</small>
|
||||||
|
</div>
|
||||||
|
</b-card>
|
||||||
|
</b-card-group>
|
||||||
|
</b-tab>
|
||||||
|
</b-tabs>
|
||||||
|
</section>
|
||||||
|
</b-form-group>
|
||||||
|
<hr>
|
||||||
|
|
||||||
|
<!--
|
||||||
|
<div class="column">
|
||||||
|
<section class="section" id="results">
|
||||||
|
<div class="box">
|
||||||
|
<ul>
|
||||||
|
|
||||||
|
<li v-for="(item, k) in form">
|
||||||
|
<strong>{{ k }}:</strong> {{ item }}
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
<div class="column">
|
||||||
|
<section class="section">
|
||||||
|
{{recorders}}
|
||||||
|
</section>
|
||||||
|
</div>
|
||||||
|
-->
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import {EventBus} from '@/utils';
|
||||||
|
import PulseLoader from 'vue-spinner/src/PulseLoader.vue';
|
||||||
|
import getRepository from '@/api/RepositoryFactory';
|
||||||
|
|
||||||
|
const recorderRepository = getRepository('recorder');
|
||||||
|
|
||||||
|
export default {
|
||||||
|
components: {
|
||||||
|
PulseLoader,
|
||||||
|
},
|
||||||
|
props: [],
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
onlyShowOfflineRecorders: false,
|
||||||
|
onlyShowNonOfflineRecorders: false,
|
||||||
|
tabIndex: 0,
|
||||||
|
filter: '',
|
||||||
|
updateValues: {},
|
||||||
|
formEditField: {},
|
||||||
|
updateRecModelValues: {},
|
||||||
|
formRecModelEditField: {},
|
||||||
|
show_assigned_recorders: false,
|
||||||
|
show_assigned_rooms: true,
|
||||||
|
currentPage: 1,
|
||||||
|
elementsPerPage: 6,
|
||||||
|
form: {
|
||||||
|
name: '',
|
||||||
|
description: '',
|
||||||
|
network_name: '',
|
||||||
|
ip: '',
|
||||||
|
ip6: '',
|
||||||
|
telnet_port: 23,
|
||||||
|
ssh_port: 22,
|
||||||
|
locked: false,
|
||||||
|
offline: false,
|
||||||
|
username: '',
|
||||||
|
password: '',
|
||||||
|
recorder_model: null,
|
||||||
|
room: null,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
get_rows() {
|
||||||
|
const start = (this.currentPage - 1) * this.elementsPerPage;
|
||||||
|
const end = start + this.elementsPerPage;
|
||||||
|
return this.recorders.slice(start, end);
|
||||||
|
},
|
||||||
|
num_pages() {
|
||||||
|
return Math.ceil(this.recorders.length / this.elementsPerPage);
|
||||||
|
},
|
||||||
|
change_page(page) {
|
||||||
|
this.currentPage = page;
|
||||||
|
},
|
||||||
|
initRecorderUpdate(recorder, fieldName) {
|
||||||
|
this.$set(this.formEditField, recorder.id + '_' + fieldName, true);
|
||||||
|
this.$set(this.updateValues, recorder.id + '_' + fieldName, recorder[fieldName]);
|
||||||
|
},
|
||||||
|
initRecorderModelUpdate(recorderModel, fieldName) {
|
||||||
|
this.$set(this.formRecModelEditField, recorderModel.id + '_' + fieldName, true);
|
||||||
|
this.$set(this.updateRecModelValues, recorderModel.id + '_' + fieldName, recorderModel[fieldName]);
|
||||||
|
},
|
||||||
|
updateRecorder(id, fieldName) {
|
||||||
|
this.$parent.$data.isLoading = true;
|
||||||
|
const data = {};
|
||||||
|
data[fieldName] = this.updateValues[id + '_' + fieldName];
|
||||||
|
recorderRepository.updateRecorder(id, data)
|
||||||
|
.then(() => {
|
||||||
|
this.$store.dispatch('loadRecorders')
|
||||||
|
.then(() => {
|
||||||
|
this.$parent.$data.isLoading = false;
|
||||||
|
this.formEditField[id + '_' + fieldName] = false;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
},
|
||||||
|
updateRecorderModel(id, fieldName) {
|
||||||
|
this.$parent.$data.isLoading = true;
|
||||||
|
const data = {};
|
||||||
|
data[fieldName] = this.updateRecModelValues[id + '_' + fieldName];
|
||||||
|
recorderRepository.updateRecorderModel(id, data)
|
||||||
|
.then(() => {
|
||||||
|
this.$store.dispatch('loadRecorderModels')
|
||||||
|
.then(() => {
|
||||||
|
this.$parent.$data.isLoading = false;
|
||||||
|
this.formRecModelEditField[id + '_' + fieldName] = false;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
},
|
||||||
|
saveRecorder() {
|
||||||
|
this.$parent.$data.isLoading = true;
|
||||||
|
recorderRepository.createRecorder(this.form)
|
||||||
|
.then((result) => {
|
||||||
|
console.info(result);
|
||||||
|
this.$store.dispatch('loadRecorders')
|
||||||
|
.then(() => {
|
||||||
|
this.$parent.$data.isLoading = false;
|
||||||
|
this.tabIndex = 0;
|
||||||
|
});
|
||||||
|
}).catch((error) => {
|
||||||
|
EventBus.$emit('errorMessage', 'Could not safe recorder: '+error.data.message+'!');
|
||||||
|
});
|
||||||
|
},
|
||||||
|
deleteRecorder(id) {
|
||||||
|
this.$parent.$data.isLoading = true;
|
||||||
|
recorderRepository.deleteRecorder(id)
|
||||||
|
.then(() => {
|
||||||
|
this.$store.dispatch('loadRecorders')
|
||||||
|
.then(() => {
|
||||||
|
this.$parent.$data.isLoading = false;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
},
|
||||||
|
//showErrorMessage(msg) {
|
||||||
|
// this.$parent.$data.isLoading = false;
|
||||||
|
// this.$parent.$data.showAlert = true;
|
||||||
|
// this.$parent.$data.alertMessage = msg;
|
||||||
|
//},
|
||||||
|
filterShowAllRecorders(){
|
||||||
|
this.onlyShowOfflineRecorders = false;
|
||||||
|
this.onlyShowNonOfflineRecorders = false;
|
||||||
|
},
|
||||||
|
filterOnlyShowOfflineRecorders(){
|
||||||
|
this.onlyShowOfflineRecorders = true;
|
||||||
|
this.onlyShowNonOfflineRecorders = false;
|
||||||
|
},
|
||||||
|
filterOnlyShowNonOfflineRecorders(){
|
||||||
|
this.onlyShowOfflineRecorders = false;
|
||||||
|
this.onlyShowNonOfflineRecorders = true;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
this.$parent.$data.isLoading = true;
|
||||||
|
this.$parent.$data.showAlert = false;
|
||||||
|
this.$store.dispatch('loadRecorders')
|
||||||
|
.then(() => {
|
||||||
|
this.$store.dispatch('loadRooms')
|
||||||
|
.then(() => {
|
||||||
|
this.$parent.$data.isLoading = false;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
this.$store.dispatch('loadRecorderModels');
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
rooms() {
|
||||||
|
return this.$store.state.rooms;
|
||||||
|
},
|
||||||
|
recorders() {
|
||||||
|
if(this.onlyShowOfflineRecorders){
|
||||||
|
return this.$store.state.recorders.filter((item) => {
|
||||||
|
if(this.filter !== '') {
|
||||||
|
return item.name.includes(this.filter) && item.offline;
|
||||||
|
}
|
||||||
|
return item.offline;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
else if(this.onlyShowNonOfflineRecorders){
|
||||||
|
return this.$store.state.recorders.filter((item) => {
|
||||||
|
if(this.filter !== '') {
|
||||||
|
return item.name.includes(this.filter) && !item.offline;
|
||||||
|
}
|
||||||
|
return !item.offline;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if(this.filter !== '') {
|
||||||
|
return this.$store.state.recorders.filter((item)=>{
|
||||||
|
return item.name.includes(this.filter);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return this.$store.state.recorders;
|
||||||
|
},
|
||||||
|
recorderModels() {
|
||||||
|
return this.$store.state.recorderModels;
|
||||||
|
},
|
||||||
|
recorderCommands() {
|
||||||
|
return this.$store.state.recorderCommands;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.comment {
|
||||||
|
white-space: pre-wrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card:hover {
|
||||||
|
background-color: whitesmoke;
|
||||||
|
}
|
||||||
|
|
||||||
|
.required:after {
|
||||||
|
content: " *";
|
||||||
|
}
|
||||||
|
|
||||||
|
b-form-input {
|
||||||
|
min-width: 80%;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
548
src/components/Rooms.vue
Normal file
548
src/components/Rooms.vue
Normal file
@@ -0,0 +1,548 @@
|
|||||||
|
<template>
|
||||||
|
<div class="container">
|
||||||
|
<section class="section">
|
||||||
|
|
||||||
|
|
||||||
|
<h1 class="title">{{ $t('Manage_rooms') }}</h1>
|
||||||
|
<p class="lead">
|
||||||
|
{{ $t('List_create_and_delete') }} <strong>{{ $t('rooms') }}</strong>
|
||||||
|
</p>
|
||||||
|
<br/>
|
||||||
|
|
||||||
|
<b-tabs v-model="tabIndex" card>
|
||||||
|
<b-tab title="Room list" active>
|
||||||
|
<template slot="title">
|
||||||
|
<font-awesome-icon icon="list"/> <font-awesome-icon icon="door-open"/>
|
||||||
|
<strong>Room</strong> <i>list</i>
|
||||||
|
</template>
|
||||||
|
<div class="mt-3">
|
||||||
|
<b-input-group>
|
||||||
|
<b-input-group-prepend>
|
||||||
|
<b-button variant="success" @click="showAllRooms()">{{ $t('All rooms') }}</b-button>
|
||||||
|
<b-button variant="info" @click="showRoomsWithRecorder()">{{ $t('Rooms with recorders') }}</b-button>
|
||||||
|
<b-button variant="warning" @click="showRoomsWithoutRecorder()">{{ $t('Rooms without recorders') }}
|
||||||
|
</b-button>
|
||||||
|
</b-input-group-prepend>
|
||||||
|
<b-form-input type="text" v-model="filter"></b-form-input>
|
||||||
|
<b-input-group-append>
|
||||||
|
<b-button variant="outline-primary" @click="filter=''">Clear Filter</b-button>
|
||||||
|
</b-input-group-append>
|
||||||
|
</b-input-group>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
<p>{{ $tc('rooms_defined', rooms.length, {num: rooms.length}) }}:</p>
|
||||||
|
<div class="pagination">
|
||||||
|
<div class="number"
|
||||||
|
v-for="i in num_pages()"
|
||||||
|
v-bind:class="[i === currentPage ? 'active' : '']"
|
||||||
|
v-on:click="change_page(i)">{{i}}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<b-card-group deck>
|
||||||
|
<b-card class="mb-2" style="max-width: 30rem; min-width:20rem;" v-for="(room) in get_rows()"
|
||||||
|
:header="room.name + ' (' +room.building_number+ ')'" v-bind:key="room.id">
|
||||||
|
<b-card-text>
|
||||||
|
<h5 class="card-title"><strong>{{ $t('name') }}</strong>:
|
||||||
|
<span v-if="!formEditField[room.id+'_name']">{{ room.name }}
|
||||||
|
<a class="float-right badge badge-pill badge-primary">
|
||||||
|
<font-awesome-icon
|
||||||
|
@click="initRoomUpdate(room, 'name')"
|
||||||
|
icon="pencil-alt"/>
|
||||||
|
</a>
|
||||||
|
</span>
|
||||||
|
<b-input-group v-else>
|
||||||
|
<ValidationProvider v-slot="{ errors }" rules="required|min:3">
|
||||||
|
<b-form-input :id="room.id+'_name'" name="name"
|
||||||
|
v-model="updateValues[room.id+'_name']"
|
||||||
|
v-bind:class="{'is-danger': errors.length > 0, 'is-invalid': errors.length > 0}"
|
||||||
|
class="form-control" type="text"
|
||||||
|
:placeholder="'Name ('+room.name +')'"
|
||||||
|
required></b-form-input>
|
||||||
|
<b-input-group-append>
|
||||||
|
<b-button :disabled="errors.length > 0"
|
||||||
|
@click="updateRoom(room.id, 'name')"
|
||||||
|
variant="outline-success">
|
||||||
|
<font-awesome-icon icon="check"></font-awesome-icon>
|
||||||
|
</b-button>
|
||||||
|
</b-input-group-append>
|
||||||
|
</ValidationProvider>
|
||||||
|
</b-input-group>
|
||||||
|
</h5>
|
||||||
|
|
||||||
|
|
||||||
|
<p class="card-text"><strong>{{ $t('building_number') }}</strong>:
|
||||||
|
<span v-if="!formEditField[room.id+'_building_number']">{{ room.building_number }}
|
||||||
|
<a class="float-right badge badge-pill badge-primary">
|
||||||
|
<font-awesome-icon
|
||||||
|
@click="initRoomUpdate(room, 'building_number')"
|
||||||
|
icon="pencil-alt"/>
|
||||||
|
</a>
|
||||||
|
</span>
|
||||||
|
<b-input-group v-else>
|
||||||
|
<ValidationProvider v-slot="{ errors }" rules="required|min:3">
|
||||||
|
<b-form-input :id="room.id+'_building_number'" name="building_number"
|
||||||
|
v-model="updateValues[room.id+'_building_number']"
|
||||||
|
v-bind:class="{'is-danger': errors.length > 0, 'is-invalid': errors.length > 0}"
|
||||||
|
class="form-control" type="text"
|
||||||
|
:placeholder="$t('Building_number') +'('+room.building_number +')'"
|
||||||
|
required></b-form-input>
|
||||||
|
<b-input-group-append>
|
||||||
|
<b-button :disabled="errors.length > 0"
|
||||||
|
@click="updateRoom(room.id, 'building_number')"
|
||||||
|
variant="outline-success">
|
||||||
|
<font-awesome-icon icon="check"></font-awesome-icon>
|
||||||
|
</b-button>
|
||||||
|
</b-input-group-append>
|
||||||
|
</ValidationProvider>
|
||||||
|
</b-input-group>
|
||||||
|
</p>
|
||||||
|
<p class="card-text"><strong>{{ $t('building_name') }}</strong>:
|
||||||
|
<span v-if="!formEditField[room.id+'_building_name']">{{ room.building_name }}
|
||||||
|
<a class="float-right badge badge-pill badge-primary">
|
||||||
|
<font-awesome-icon
|
||||||
|
@click="initRoomUpdate(room, 'building_name')"
|
||||||
|
icon="pencil-alt"/>
|
||||||
|
</a>
|
||||||
|
</span>
|
||||||
|
<b-input-group v-else>
|
||||||
|
<ValidationProvider v-slot="{ errors }" rules="required|min:3">
|
||||||
|
<b-form-input :id="room.id+'_building_name'" name="building_name"
|
||||||
|
v-model="updateValues[room.id+'_building_name']"
|
||||||
|
v-bind:class="{'is-danger': errors.length > 0, 'is-invalid': errors.length > 0}"
|
||||||
|
class="form-control" type="text"
|
||||||
|
:placeholder="'Building name ('+room.building_name +')'"
|
||||||
|
required></b-form-input>
|
||||||
|
<b-input-group-append>
|
||||||
|
<b-button :disabled="errors.length > 0"
|
||||||
|
@click="updateRoom(room.id, 'building_name')"
|
||||||
|
variant="outline-success">
|
||||||
|
<font-awesome-icon icon="check"></font-awesome-icon>
|
||||||
|
</b-button>
|
||||||
|
</b-input-group-append>
|
||||||
|
</ValidationProvider>
|
||||||
|
</b-input-group>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
|
||||||
|
<p class="card-text"><strong>{{ $t('alternate_name') }}:</strong>
|
||||||
|
<span v-if="!formEditField[room.id+'_alternate_name']">{{ room.alternate_name }}
|
||||||
|
<a class="float-right badge badge-pill badge-info">
|
||||||
|
<font-awesome-icon
|
||||||
|
@click="initRoomUpdate(room, 'alternate_name')"
|
||||||
|
icon="pencil-alt"/>
|
||||||
|
</a>
|
||||||
|
</span>
|
||||||
|
<b-input-group v-else size="sm">
|
||||||
|
<ValidationProvider v-slot="{ errors }">
|
||||||
|
<b-form-input name="alternate_name"
|
||||||
|
v-model="updateValues[room.id+'_alternate_name']"
|
||||||
|
class="form-control" type="text"
|
||||||
|
:placeholder="'Alternate name ('+room.alternate_name +')'"
|
||||||
|
@blur="updateRoom(room.id, 'alternate_name')"
|
||||||
|
@keyup.enter="updateRoom(room.id, 'alternate_name')"></b-form-input>
|
||||||
|
<b-input-group-append>
|
||||||
|
<b-button :disabled="errors.length > 0"
|
||||||
|
@click="updateRoom(room.id, 'alternate_name')"
|
||||||
|
variant="outline-success">
|
||||||
|
<font-awesome-icon icon="check"></font-awesome-icon>
|
||||||
|
</b-button>
|
||||||
|
</b-input-group-append>
|
||||||
|
</ValidationProvider>
|
||||||
|
</b-input-group>
|
||||||
|
</p>
|
||||||
|
<p class="card-text"><strong>{{ $t('room_number') }}:</strong>
|
||||||
|
<span v-if="!formEditField[room.id+'_number']">{{ room.number }}
|
||||||
|
<a class="float-right badge badge-pill badge-info">
|
||||||
|
<font-awesome-icon
|
||||||
|
@click="initRoomUpdate(room, 'number')"
|
||||||
|
icon="pencil-alt"/>
|
||||||
|
</a>
|
||||||
|
</span>
|
||||||
|
<b-input-group v-else size="sm">
|
||||||
|
<ValidationProvider v-slot="{ errors }" rules="required|min:3">
|
||||||
|
<b-form-input name="number"
|
||||||
|
v-model="updateValues[room.id+'_number']"
|
||||||
|
v-bind:class="{'is-danger': errors.length > 0, 'is-invalid': errors.length > 0}"
|
||||||
|
class="form-control" type="text"
|
||||||
|
:placeholder="'Room number ('+room.number +')'"
|
||||||
|
@blur="updateRoom(room.id, 'number')"
|
||||||
|
@keyup.enter="updateRoom(room.id, 'number')"
|
||||||
|
required></b-form-input>
|
||||||
|
<b-input-group-append>
|
||||||
|
<b-button :disabled="errors.length > 0"
|
||||||
|
@click="updateRoom(room.id, 'number')"
|
||||||
|
variant="outline-success">
|
||||||
|
<font-awesome-icon icon="check"></font-awesome-icon>
|
||||||
|
</b-button>
|
||||||
|
</b-input-group-append>
|
||||||
|
</ValidationProvider>
|
||||||
|
</b-input-group>
|
||||||
|
</p>
|
||||||
|
<p class="card-text">
|
||||||
|
<small><strong>{{ $t('comment') }}:</strong>
|
||||||
|
<span v-if="!formEditField[room.id+'_comment']">{{ room.comment }}
|
||||||
|
<a class="float-right badge badge-pill badge-info">
|
||||||
|
<font-awesome-icon
|
||||||
|
@click="initRoomUpdate(room, 'comment')"
|
||||||
|
icon="pencil-alt"/>
|
||||||
|
</a>
|
||||||
|
</span>
|
||||||
|
<textarea class="textarea form-control" v-else name="comment"
|
||||||
|
v-model="updateValues[room.id+'_comment']"
|
||||||
|
:placeholder="'Comment ('+room.comment +')'"
|
||||||
|
@blur="updateRoom(room.id, 'comment')"></textarea>
|
||||||
|
</small>
|
||||||
|
</p>
|
||||||
|
<hr/>
|
||||||
|
<span v-if="!formEditField[room.id+'_recorder_id']">{{ room.recorder_id }}
|
||||||
|
<div v-if="room.recorder">
|
||||||
|
<p class="card-text"><strong>{{
|
||||||
|
$t('Recorder')
|
||||||
|
}}:</strong> {{ room.recorder.name }}
|
||||||
|
({{ room.recorder.ip }}) / ({{ room.recorder.network_name }})</p>
|
||||||
|
</div>
|
||||||
|
<div v-else>
|
||||||
|
<p class="card-text"><strong>{{ $t('Recorder') }}:</strong> {{
|
||||||
|
$t('no_recorder_defined')
|
||||||
|
}}</p>
|
||||||
|
</div>
|
||||||
|
<a class="float-right badge badge-pill badge-info">
|
||||||
|
<font-awesome-icon
|
||||||
|
@click="initRoomUpdate(room, 'recorder_id')" icon="pencil-alt"/>
|
||||||
|
</a>
|
||||||
|
</span>
|
||||||
|
<b-form v-else>
|
||||||
|
<b-input-group>
|
||||||
|
<ValidationProvider v-slot="{ errors }">
|
||||||
|
<select class="form-control" v-model="updateValues[room.id+'_recorder_id']">
|
||||||
|
<option value="">No recorder selected</option>
|
||||||
|
<option v-for="recorder in recorders" v-bind:value="recorder.id">
|
||||||
|
{{ recorder.name }}
|
||||||
|
</option>
|
||||||
|
</select>
|
||||||
|
<b-input-group-append>
|
||||||
|
<b-button :disabled="errors.length > 0"
|
||||||
|
@click="updateRoom(room.id, 'recorder_id')"
|
||||||
|
variant="outline-success">
|
||||||
|
<font-awesome-icon icon="check"></font-awesome-icon>
|
||||||
|
</b-button>
|
||||||
|
</b-input-group-append>
|
||||||
|
</ValidationProvider>
|
||||||
|
</b-input-group>
|
||||||
|
|
||||||
|
<div class="form-check">
|
||||||
|
<input class="form-check-input" type="checkbox"
|
||||||
|
v-model="show_assigned_recorders" id="defaultCheck1">
|
||||||
|
<label class="form-check-label" for="defaultCheck1">
|
||||||
|
Show already assigned recorders
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</b-form>
|
||||||
|
|
||||||
|
</b-card-text>
|
||||||
|
<div slot="footer">
|
||||||
|
<small class="text-muted">
|
||||||
|
<p>{{ $t('created') }}: {{ room.created_at | moment("dddd, MMMM Do YYYY") }}
|
||||||
|
<span class="pull-right">
|
||||||
|
<button type="button" v-on:click="deleteRoom(room.id)"
|
||||||
|
class="btn btn-sm btn-danger">{{
|
||||||
|
$t('delete')
|
||||||
|
}} <font-awesome-icon
|
||||||
|
icon="trash"/>
|
||||||
|
</button>
|
||||||
|
</span>
|
||||||
|
</p>
|
||||||
|
</small>
|
||||||
|
</div>
|
||||||
|
</b-card>
|
||||||
|
</b-card-group>
|
||||||
|
</b-tab>
|
||||||
|
|
||||||
|
<b-tab title="Create room">
|
||||||
|
<template slot="title">
|
||||||
|
<font-awesome-icon icon="plus"/>
|
||||||
|
<i>{{ $t('create') }}</i> <font-awesome-icon icon="door-open"/>
|
||||||
|
<strong>{{ $t('Room') }}</strong>
|
||||||
|
</template>
|
||||||
|
<b-card-text>
|
||||||
|
<p>{{ $t('Create_a_new_room') }}:</p>
|
||||||
|
<!-- form starts here -->
|
||||||
|
<ValidationObserver v-slot="{ invalid }">
|
||||||
|
<form v-on:submit.prevent="saveRoom()">
|
||||||
|
<section class="form">
|
||||||
|
|
||||||
|
<ValidationProvider :name="$t('name')" rules="required|min:3" v-slot="{ errors }" slim>
|
||||||
|
<div class="form-group row">
|
||||||
|
<label class="label required col-sm-2 col-form-label">{{ $t('name') }}</label>
|
||||||
|
|
||||||
|
<div class="col-sm-6">
|
||||||
|
<input name="name"
|
||||||
|
v-model="form.name"
|
||||||
|
v-bind:class="{'is-danger': errors.length > 0, 'is-invalid': errors.length > 0}"
|
||||||
|
class="form-control" type="text" placeholder="Room name" required>
|
||||||
|
</div>
|
||||||
|
<p class="col-sm-4" v-show="errors.length > 0">
|
||||||
|
{{ errors[0] }}
|
||||||
|
</p>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</ValidationProvider>
|
||||||
|
|
||||||
|
<div class="form-group row">
|
||||||
|
<label class="label col-sm-2 col-form-label">{{ $t('alternate_name') }}</label>
|
||||||
|
<div class="col-sm-6">
|
||||||
|
<input name="alternate_name"
|
||||||
|
v-model="form.alternate_name"
|
||||||
|
class="form-control" type="text" placeholder="Alternate name">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<ValidationProvider :name="$t('number')" rules="required|min:3" v-slot="{ errors }" slim>
|
||||||
|
<div class="form-group row">
|
||||||
|
<label class="label required col-sm-2 col-form-label">{{ $t('number') }}</label>
|
||||||
|
|
||||||
|
<div class="col-sm-6">
|
||||||
|
<input name="number"
|
||||||
|
v-model="form.number"
|
||||||
|
v-bind:class="{'is-danger': errors.length > 0, 'is-invalid': errors.length > 0}"
|
||||||
|
class="form-control" type="text" placeholder="Room number" required>
|
||||||
|
</div>
|
||||||
|
<p class="col-sm-4" v-show="errors.length > 0">
|
||||||
|
{{ errors[0] }}
|
||||||
|
</p>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</ValidationProvider>
|
||||||
|
|
||||||
|
<div class="form-group row">
|
||||||
|
<label class="label col-sm-2 col-form-label">{{ $t('comment') }}</label>
|
||||||
|
<div class="col-sm-6">
|
||||||
|
<textarea class="textarea form-control"
|
||||||
|
:placeholder="$t('Comments') +', ' + $t('notes') + ', etc.'"
|
||||||
|
v-model="form.comment"></textarea>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-group row">
|
||||||
|
<label class="label col-sm-2 col-form-label">{{ $t('recorder') }}</label>
|
||||||
|
<div class="col-sm-6">
|
||||||
|
<select class="form-control" v-model="form.recorder_id">
|
||||||
|
<option value="">No recorder selected</option>
|
||||||
|
<option v-for="recorder in recorders" v-bind:value="recorder.id">
|
||||||
|
{{ recorder.name }}
|
||||||
|
</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
<div class="field is-grouped">
|
||||||
|
<div class="control">
|
||||||
|
<button
|
||||||
|
v-bind:disabled="invalid"
|
||||||
|
type="submit"
|
||||||
|
class="btn btn-primary">
|
||||||
|
Create room
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</section>
|
||||||
|
</form>
|
||||||
|
</ValidationObserver>
|
||||||
|
</b-card-text>
|
||||||
|
</b-tab>
|
||||||
|
</b-tabs>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<!--
|
||||||
|
<hr>
|
||||||
|
|
||||||
|
<div class="column">
|
||||||
|
<section class="section" id="results">
|
||||||
|
<div class="box">
|
||||||
|
<ul>
|
||||||
|
<li v-for="(item, k) in form">
|
||||||
|
<strong>{{ k }}:</strong> {{ item }}
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="column">
|
||||||
|
<section class="section">
|
||||||
|
{{ rooms }}
|
||||||
|
</section>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
-->
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import {EventBus} from '@/utils';
|
||||||
|
import PulseLoader from 'vue-spinner/src/PulseLoader.vue';
|
||||||
|
import getRepository from '@/api/RepositoryFactory';
|
||||||
|
|
||||||
|
const RoomRepository = getRepository('room');
|
||||||
|
|
||||||
|
export default {
|
||||||
|
components: {
|
||||||
|
PulseLoader,
|
||||||
|
},
|
||||||
|
props: [],
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
onlyShowRoomsWithRecorder: false,
|
||||||
|
onlyShowRoomsWithoutRecorder: false,
|
||||||
|
tabIndex: 0,
|
||||||
|
filter: '',
|
||||||
|
updateValues: {},
|
||||||
|
formEditField: {},
|
||||||
|
show_assigned_recorders: false,
|
||||||
|
currentPage: 1,
|
||||||
|
elementsPerPage: 30,
|
||||||
|
form: {
|
||||||
|
name: null,
|
||||||
|
alternate_name: null,
|
||||||
|
number: null,
|
||||||
|
comment: null,
|
||||||
|
recorder_id: null,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
get_rows() {
|
||||||
|
const start = (this.currentPage - 1) * this.elementsPerPage;
|
||||||
|
const end = start + this.elementsPerPage;
|
||||||
|
return this.rooms.slice(start, end);
|
||||||
|
},
|
||||||
|
num_pages() {
|
||||||
|
return Math.ceil(this.rooms.length / this.elementsPerPage);
|
||||||
|
},
|
||||||
|
change_page(page) {
|
||||||
|
this.currentPage = page;
|
||||||
|
},
|
||||||
|
showAllRooms() {
|
||||||
|
this.onlyShowRoomsWithRecorder = false;
|
||||||
|
this.onlyShowRoomsWithoutRecorder = false;
|
||||||
|
},
|
||||||
|
showRoomsWithRecorder() {
|
||||||
|
this.onlyShowRoomsWithRecorder = true;
|
||||||
|
this.onlyShowRoomsWithoutRecorder = false;
|
||||||
|
},
|
||||||
|
showRoomsWithoutRecorder() {
|
||||||
|
this.onlyShowRoomsWithRecorder = false;
|
||||||
|
this.onlyShowRoomsWithoutRecorder = true;
|
||||||
|
},
|
||||||
|
saveRoom() {
|
||||||
|
this.$parent.$data.isLoading = true;
|
||||||
|
RoomRepository.createRoom(this.form)
|
||||||
|
.then(() => {
|
||||||
|
this.$store.dispatch('loadRooms')
|
||||||
|
.then(() => {
|
||||||
|
this.$parent.$data.isLoading = false;
|
||||||
|
this.tabIndex = 0;
|
||||||
|
});
|
||||||
|
}).catch((msg) => {
|
||||||
|
this.showErrorMessage('Could not safe room!');
|
||||||
|
});
|
||||||
|
},
|
||||||
|
initRoomUpdate(room, fieldName) {
|
||||||
|
this.$set(this.formEditField, room.id + '_' + fieldName, true);
|
||||||
|
this.$set(this.updateValues, room.id + '_' + fieldName, room[fieldName]);
|
||||||
|
},
|
||||||
|
updateRoom(id, fieldName) {
|
||||||
|
this.$parent.$data.isLoading = true;
|
||||||
|
const data = {};
|
||||||
|
data[fieldName] = this.updateValues[id + '_' + fieldName];
|
||||||
|
RoomRepository.updateRoom(id, data)
|
||||||
|
.then(() => {
|
||||||
|
this.$store.dispatch('loadRooms')
|
||||||
|
.then(() => {
|
||||||
|
this.$parent.$data.isLoading = false;
|
||||||
|
this.formEditField[id + '_' + fieldName] = false;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
},
|
||||||
|
deleteRoom(id) {
|
||||||
|
this.$parent.$data.isLoading = true;
|
||||||
|
RoomRepository.deleteRoom(id)
|
||||||
|
.then(() => {
|
||||||
|
this.$store.dispatch('loadRooms')
|
||||||
|
.then(() => {
|
||||||
|
this.$parent.$data.isLoading = false;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
},
|
||||||
|
showErrorMessage(msg) {
|
||||||
|
this.$parent.$data.isLoading = false;
|
||||||
|
this.$parent.$data.showAlert = true;
|
||||||
|
this.$parent.$data.alertMessage = msg;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
this.$parent.$data.isLoading = true;
|
||||||
|
this.$parent.$data.showAlert = false;
|
||||||
|
this.$store.dispatch('loadRooms')
|
||||||
|
.then(() => {
|
||||||
|
this.$store.dispatch('loadRecorders')
|
||||||
|
.then(() => {
|
||||||
|
this.$parent.$data.isLoading = false;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
options() {
|
||||||
|
return {
|
||||||
|
recorders: [
|
||||||
|
{value: 8, text: 'SMP 351 AudiMax'},
|
||||||
|
{value: 13, text: 'SMP 351 HMU'},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
},
|
||||||
|
rooms() {
|
||||||
|
if (this.onlyShowRoomsWithRecorder) {
|
||||||
|
return this.$store.state.rooms.filter((item) => {
|
||||||
|
if(this.filter !== '') {
|
||||||
|
return (item.name.includes(this.filter) || item.building_name != null && item.building_name.includes(this.filter)) && item.recorder;
|
||||||
|
}
|
||||||
|
return item.recorder;
|
||||||
|
});
|
||||||
|
} else if (this.onlyShowRoomsWithoutRecorder) {
|
||||||
|
return this.$store.state.rooms.filter((item) => {
|
||||||
|
if(this.filter !== '') {
|
||||||
|
return (item.name.includes(this.filter) || item.building_name != null && item.building_name.includes(this.filter)) && !item.recorder;
|
||||||
|
}
|
||||||
|
return !item.recorder;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if(this.filter !== ''){
|
||||||
|
return this.$store.state.rooms.filter((item)=>{
|
||||||
|
return (item.name.includes(this.filter) || item.building_name != null && item.building_name.includes(this.filter));
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return this.$store.state.rooms;
|
||||||
|
},
|
||||||
|
recorders() {
|
||||||
|
return this.$store.state.recorders;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.comment {
|
||||||
|
white-space: pre-wrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card:hover {
|
||||||
|
background-color: whitesmoke;
|
||||||
|
}
|
||||||
|
|
||||||
|
.required:after {
|
||||||
|
content: " *";
|
||||||
|
}
|
||||||
|
</style>
|
||||||
72
src/components/SelectRecorder.vue
Normal file
72
src/components/SelectRecorder.vue
Normal file
@@ -0,0 +1,72 @@
|
|||||||
|
<template>
|
||||||
|
<div v-if="authenticated">
|
||||||
|
<b-form>
|
||||||
|
<b-input-group>
|
||||||
|
<select class="form-control" v-model="favoriteRecorderId">
|
||||||
|
<option value="">No recorder selected</option>
|
||||||
|
<option v-for="recorder in recorders" v-bind:value="recorder.id">
|
||||||
|
{{ recorder.name }}
|
||||||
|
</option>
|
||||||
|
</select>
|
||||||
|
<b-input-group-append>
|
||||||
|
<b-button variant="outline-success"
|
||||||
|
@click="recorderSelected()">
|
||||||
|
<font-awesome-icon icon="check"/>
|
||||||
|
</b-button>
|
||||||
|
</b-input-group-append>
|
||||||
|
</b-input-group>
|
||||||
|
</b-form>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
<div v-else>
|
||||||
|
<p>You must sign in in order to select a recorder!</p>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
import {Component, Prop, Vue} from 'vue-property-decorator';
|
||||||
|
|
||||||
|
@Component
|
||||||
|
export default class SelectRecorder extends Vue {
|
||||||
|
@Prop() private msg!: string;
|
||||||
|
private favoriteRecorderId = '';
|
||||||
|
|
||||||
|
get authenticated() {
|
||||||
|
return this.$store.getters.isAuthenticated;
|
||||||
|
}
|
||||||
|
|
||||||
|
get recorders() {
|
||||||
|
return this.$store.state.recorders;
|
||||||
|
}
|
||||||
|
|
||||||
|
private mounted() {
|
||||||
|
this.$store.dispatch('loadProfile');
|
||||||
|
this.$store.dispatch('loadRecorders');
|
||||||
|
}
|
||||||
|
|
||||||
|
private recorderSelected() {
|
||||||
|
this.$emit('recorderSelected', {recorder_id: this.favoriteRecorderId});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<!-- Add "scoped" attribute to limit CSS to this component only -->
|
||||||
|
<style scoped lang="scss">
|
||||||
|
h3 {
|
||||||
|
margin: 40px 0 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
ul {
|
||||||
|
list-style-type: none;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
li {
|
||||||
|
display: inline-block;
|
||||||
|
margin: 0 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
a {
|
||||||
|
color: #42b983;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
120
src/components/Test.vue
Normal file
120
src/components/Test.vue
Normal file
@@ -0,0 +1,120 @@
|
|||||||
|
<!-- components/Test.vue -->
|
||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<section class="hero is-primary">
|
||||||
|
<div class="hero-body">
|
||||||
|
<div class="container has-text-centered">
|
||||||
|
{{api}}
|
||||||
|
{{api1}}
|
||||||
|
<b-alert
|
||||||
|
:show="dismissCountDown"
|
||||||
|
dismissible
|
||||||
|
variant="warning"
|
||||||
|
@dismissed="dismissCountDown=0"
|
||||||
|
@dismiss-count-down="countDownChanged"
|
||||||
|
>
|
||||||
|
This alert will dismiss after {{ dismissCountDown }} seconds...
|
||||||
|
</b-alert>
|
||||||
|
<b-button @click="showAlert" variant="info" class="m-1">
|
||||||
|
Show alert with count-down timer
|
||||||
|
</b-button>
|
||||||
|
<b-button @click="$parent.showErrorMessage('tolle error message')" variant="warning" class="m-1">
|
||||||
|
Show error message
|
||||||
|
</b-button>
|
||||||
|
<b-button @click="showTestMessageLonger" variant="danger" class="m-1">
|
||||||
|
Show test message longer
|
||||||
|
</b-button>
|
||||||
|
<h1>Toller Test</h1>
|
||||||
|
<span>{{ $socket.connected ? 'Connected' : 'Disconnected' }}</span>
|
||||||
|
<button v-if="$socket.connected" @click="disconnectWebsocket">Disconnect</button>
|
||||||
|
<button v-else="$socket.connected" @click="connectWebsocket">Connect</button>
|
||||||
|
<p>
|
||||||
|
<button v-if="$socket.connected" @click="sendWsMessage('Tolle Test Nachricht')">Send Message</button>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
|
||||||
|
<router-link :to="{name: 'recorder', params: {recorder_id: 1}}">rec 1</router-link>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
|
||||||
|
import {EventBus} from '@/utils';
|
||||||
|
import { API_V1_URL, API_URL } from '@/main.ts'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
email: '',
|
||||||
|
dismissSecs: 5,
|
||||||
|
dismissCountDown: 0,
|
||||||
|
api: API_URL,
|
||||||
|
api1: API_V1_URL,
|
||||||
|
|
||||||
|
};
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
changeLocale(locale) {
|
||||||
|
this.$i18n.locale = locale;
|
||||||
|
// Vue.$moment.locale(locale);
|
||||||
|
},
|
||||||
|
sendWsMessage(msg) {
|
||||||
|
this.$socket.client.emit('my_event', msg);
|
||||||
|
},
|
||||||
|
connectWebsocket() {
|
||||||
|
this.$socket.client.connect();
|
||||||
|
},
|
||||||
|
disconnectWebsocket() {
|
||||||
|
this.$socket.client.disconnect();
|
||||||
|
},
|
||||||
|
countDownChanged(dismissCountDown) {
|
||||||
|
this.dismissCountDown = dismissCountDown;
|
||||||
|
},
|
||||||
|
showAlert() {
|
||||||
|
this.dismissCountDown = this.dismissSecs;
|
||||||
|
},
|
||||||
|
showTestMessageLonger(){
|
||||||
|
EventBus.$emit('normalMessage', {msg: 'message', time:100})
|
||||||
|
},
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
this.$socket.client.on('my_response', function(msg) {
|
||||||
|
});
|
||||||
|
this.$socket.client.on('server_event', function(msg) {
|
||||||
|
});
|
||||||
|
this.$socket.client.on('connect', function(msg) {
|
||||||
|
});
|
||||||
|
|
||||||
|
// this.$socket.$unsubscribe('even_name');
|
||||||
|
|
||||||
|
},
|
||||||
|
beforeDestroy() {
|
||||||
|
EventBus.$off('failedLoadingProfile');
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
profile() {
|
||||||
|
return this.$store.state.profile;
|
||||||
|
},
|
||||||
|
access_token() {
|
||||||
|
return this.$store.state.access_token;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss">
|
||||||
|
.error-msg {
|
||||||
|
color: red;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
.lang-btn {
|
||||||
|
padding: 15px;
|
||||||
|
border: 2px solid green;
|
||||||
|
font-size: 18px;
|
||||||
|
margin: 15px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
142
src/main.ts
142
src/main.ts
@@ -1,20 +1,162 @@
|
|||||||
import Vue from 'vue';
|
import Vue from 'vue';
|
||||||
|
import axios from 'axios';
|
||||||
|
import VueAxios from 'vue-axios';
|
||||||
|
import BootstrapVue from 'bootstrap-vue';
|
||||||
import App from './App.vue';
|
import App from './App.vue';
|
||||||
import router from './router';
|
import router from './router';
|
||||||
import store from './store';
|
import store from './store';
|
||||||
|
import VueSweetalert2 from 'vue-sweetalert2';
|
||||||
|
import VueCookies from 'vue-cookies';
|
||||||
|
import VueSocketIOExt from 'vue-socket.io-extended';
|
||||||
|
import io from 'socket.io-client';
|
||||||
import i18n from '@/plugins/i18n';
|
import i18n from '@/plugins/i18n';
|
||||||
|
import { ValidationObserver, ValidationProvider, extend, localize } from 'vee-validate';
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
import FlagIcon from 'vue-flag-icon';
|
import FlagIcon from 'vue-flag-icon';
|
||||||
|
// @ts-ignore
|
||||||
|
import VueMoment from 'vue-moment';
|
||||||
// following is to avoid missing type definitions
|
// following is to avoid missing type definitions
|
||||||
// const FlagIcon = require('vue-flag-icon');
|
// const FlagIcon = require('vue-flag-icon');
|
||||||
|
|
||||||
|
|
||||||
|
import {library} from '@fortawesome/fontawesome-svg-core';
|
||||||
|
import {
|
||||||
|
faCoffee,
|
||||||
|
faDoorOpen,
|
||||||
|
faCheck,
|
||||||
|
faPlus,
|
||||||
|
faScroll,
|
||||||
|
faCircle,
|
||||||
|
faArrowCircleLeft,
|
||||||
|
faArrowCircleRight,
|
||||||
|
faList,
|
||||||
|
faTrash,
|
||||||
|
faPencilAlt,
|
||||||
|
faCogs,
|
||||||
|
faAt,
|
||||||
|
faIdBadge,
|
||||||
|
faUser,
|
||||||
|
faEnvelope,
|
||||||
|
faHome,
|
||||||
|
faSync,
|
||||||
|
faCode,
|
||||||
|
faBarcode,
|
||||||
|
faTag,
|
||||||
|
faTags,
|
||||||
|
faVideo,
|
||||||
|
faLock,
|
||||||
|
faLockOpen,
|
||||||
|
faWrench,
|
||||||
|
faPlay,
|
||||||
|
faFrown,
|
||||||
|
faSmile,
|
||||||
|
faUserTag,
|
||||||
|
faExternalLinkAlt,
|
||||||
|
faThumbsUp,
|
||||||
|
faThumbsDown,
|
||||||
|
} from '@fortawesome/free-solid-svg-icons';
|
||||||
|
|
||||||
|
import { faOpenid,
|
||||||
|
} from '@fortawesome/free-brands-svg-icons';
|
||||||
|
|
||||||
|
import {
|
||||||
|
faDotCircle,
|
||||||
|
} from '@fortawesome/free-regular-svg-icons';
|
||||||
|
|
||||||
|
import {FontAwesomeIcon} from '@fortawesome/vue-fontawesome';
|
||||||
|
|
||||||
|
|
||||||
|
// import 'bootstrap';
|
||||||
|
// import 'bootstrap/dist/css/bootstrap.min.css';
|
||||||
|
|
||||||
|
import 'bootstrap/dist/css/bootstrap.css';
|
||||||
|
import 'bootstrap-vue/dist/bootstrap-vue.css';
|
||||||
|
|
||||||
|
const isProduction = process.env.NODE_ENV === 'production';
|
||||||
|
Vue.prototype.$isProduction = isProduction;
|
||||||
|
|
||||||
|
library.add(faCoffee, faTrash, faPencilAlt, faScroll, faCheck, faCircle, faList, faPlus, faDoorOpen, faCogs, faAt,
|
||||||
|
faUser, faEnvelope, faUserTag, faExternalLinkAlt, faSync, faHome, faCode, faBarcode, faTag, faTags, faVideo, faLock,
|
||||||
|
faLockOpen, faArrowCircleLeft, faArrowCircleRight, faWrench, faPlay, faFrown, faSmile, faThumbsUp, faThumbsDown,
|
||||||
|
faOpenid, faDotCircle, faIdBadge);
|
||||||
|
|
||||||
|
Vue.component('font-awesome-icon', FontAwesomeIcon);
|
||||||
|
|
||||||
|
const loggerOptions = {
|
||||||
|
isEnabled: true,
|
||||||
|
logLevel : isProduction ? 'error' : 'debug',
|
||||||
|
//logLevel: 'debug',
|
||||||
|
stringifyArguments: false,
|
||||||
|
showLogLevel: true,
|
||||||
|
showMethodName: true,
|
||||||
|
separator: '|',
|
||||||
|
showConsoleColors: true,
|
||||||
|
};
|
||||||
|
|
||||||
|
//Vue.use(VueLogger, loggerOptions);
|
||||||
|
Vue.use(VueAxios, axios);
|
||||||
|
Vue.use(BootstrapVue);
|
||||||
Vue.use(FlagIcon);
|
Vue.use(FlagIcon);
|
||||||
|
Vue.use(VueCookies);
|
||||||
|
Vue.use(VueSweetalert2);
|
||||||
|
Vue.use(VueMoment);
|
||||||
|
|
||||||
|
|
||||||
|
import en from 'vee-validate/dist/locale/en.json';
|
||||||
|
import de from 'vee-validate/dist/locale/de.json';
|
||||||
|
import es from 'vee-validate/dist/locale/es.json';
|
||||||
|
import * as rules from 'vee-validate/dist/rules';
|
||||||
|
import '@/validation.js';
|
||||||
|
|
||||||
|
// install rules and localization
|
||||||
|
Object.keys(rules).forEach((rule) => {
|
||||||
|
// @ts-ignore
|
||||||
|
extend(rule, rules[rule]);
|
||||||
|
});
|
||||||
|
|
||||||
|
localize('de', de);
|
||||||
|
|
||||||
|
// Install components globally
|
||||||
|
Vue.component('ValidationObserver', ValidationObserver);
|
||||||
|
Vue.component('ValidationProvider', ValidationProvider);
|
||||||
|
|
||||||
|
let baseDomain = "";
|
||||||
|
let wsURL = "";
|
||||||
|
|
||||||
|
if(process.env.NODE_ENV == "development"){
|
||||||
|
baseDomain = "http://localhost:5443";
|
||||||
|
wsURL = "ws://localhost:5443";
|
||||||
|
}
|
||||||
|
|
||||||
|
//const API_URL = 'http://localhost:5443/api';
|
||||||
|
//const baseDomain = "http://localhost:5443";
|
||||||
|
|
||||||
|
export const API_URL = `${baseDomain}/api`;
|
||||||
|
export const API_V1_URL = `${baseDomain}/api/v1`;
|
||||||
|
|
||||||
|
|
||||||
|
// const socket = io('ws://localhost:5000',{autoConnect: false, reconnectionAttempts: 3});
|
||||||
|
const socket = io(wsURL, {
|
||||||
|
autoConnect: false,
|
||||||
|
reconnectionAttempts: 3,
|
||||||
|
query: {jwt: store.state.access_token},
|
||||||
|
});
|
||||||
|
Vue.use(VueSocketIOExt, socket);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
Vue.config.productionTip = false;
|
Vue.config.productionTip = false;
|
||||||
|
|
||||||
|
|
||||||
|
// @ts-ignore
|
||||||
|
String.prototype.isEmpty = function() {
|
||||||
|
return (this.length === 0 || !this.trim());
|
||||||
|
};
|
||||||
|
|
||||||
new Vue({
|
new Vue({
|
||||||
i18n,
|
i18n,
|
||||||
router,
|
router,
|
||||||
store,
|
store,
|
||||||
render: (h) => h(App),
|
render: (h) => h(App),
|
||||||
}).$mount('#app');
|
}).$mount('#app');
|
||||||
|
|
||||||
|
|||||||
@@ -5,6 +5,94 @@ Vue.use(VueI18n);
|
|||||||
|
|
||||||
|
|
||||||
const messages = {
|
const messages = {
|
||||||
|
de: {
|
||||||
|
welcomeMsg: 'Willkommen zu deiner Vue.js + TypeScript App',
|
||||||
|
guide: 'For a guide and recipes on how to configure / customize this project,',
|
||||||
|
checkout: 'check out the',
|
||||||
|
plugins: 'Installed CLI Plugins',
|
||||||
|
links: 'Essential Links',
|
||||||
|
ecosystem: 'Ecosystem',
|
||||||
|
Building_number: 'Gebäudenummer',
|
||||||
|
building_number: 'Gebäude #',
|
||||||
|
building_name: 'Gebäude',
|
||||||
|
Manage_rooms: 'Räume verwalten',
|
||||||
|
Manage_recorders: 'Aufzeichnungsgeräte verwalten',
|
||||||
|
Create_a_new_recorder: 'Ein neues Aufzeichnungsgerät anlegen',
|
||||||
|
Create_a_new_room: 'Einen neuen Raum anlegen',
|
||||||
|
Recorder_name: 'Name des Aufzeichnungsgerätes',
|
||||||
|
Recorder: 'Aufzeichnungsgerät',
|
||||||
|
recorder: 'Aufzeichnungsgerät',
|
||||||
|
Recorders: 'Aufzeichnungsgeräte',
|
||||||
|
recorders: 'Aufzeichnungsgeräte',
|
||||||
|
Register: 'Registrieren',
|
||||||
|
command: 'Befehl',
|
||||||
|
commands: 'Befehle',
|
||||||
|
Commands: 'Befehle',
|
||||||
|
virtual_commands: 'virtuelle Befehle',
|
||||||
|
admin: 'Admin',
|
||||||
|
name: 'Name',
|
||||||
|
alternate_name: 'alternativer Name',
|
||||||
|
group: 'Gruppe',
|
||||||
|
groups: 'Gruppen',
|
||||||
|
permissions: 'Berechtigungen',
|
||||||
|
created: 'erstellt',
|
||||||
|
Comments: 'Kommentare',
|
||||||
|
Comment: 'Kommentar',
|
||||||
|
comment: 'Kommentar',
|
||||||
|
Room: 'Raum',
|
||||||
|
room: 'Raum',
|
||||||
|
Rooms: 'Räume',
|
||||||
|
rooms: 'Räume',
|
||||||
|
Room_number: 'Raumnummer',
|
||||||
|
room_number: 'Raumnummer',
|
||||||
|
user: 'Benutzer',
|
||||||
|
username: 'Benutzername',
|
||||||
|
password: 'Passwort',
|
||||||
|
first_name: 'Vorname',
|
||||||
|
last_name: 'Nachname',
|
||||||
|
nickname: 'Spitzname',
|
||||||
|
set_your_nickname: 'Lege deinen Spitznamen fest',
|
||||||
|
notes: 'Anmerkungen',
|
||||||
|
List_create_and_delete: 'Auflisten, anlegen und löschen von',
|
||||||
|
no_recorder_defined: 'Kein Aufzeichnungsgerät definiert',
|
||||||
|
No_recorder_selected: 'Kein Aufzeichnungsgerät ausgewählt',
|
||||||
|
No_recorder_model_selected: 'Kein Aufzeichnungsgerätemodell ausgewählt',
|
||||||
|
Model: 'Modell',
|
||||||
|
list: 'Liste',
|
||||||
|
recorder_model: 'Aufzeichnungsgerätemodell',
|
||||||
|
recorder_models: 'Aufzeichnungsgerätemodellen',
|
||||||
|
description: 'Beschreibung',
|
||||||
|
network_name: 'Netzwerkname',
|
||||||
|
ip: 'IP (v4)',
|
||||||
|
ipv6: 'IP v6',
|
||||||
|
may_be_left_blank: 'kann ggf. leer gelassen werden',
|
||||||
|
rooms_defined: 'Es ist kein Raum definiert! | Ein Raum ist definiert | {num} Räume sind definiert',
|
||||||
|
recorders_defined: 'Es ist kein Aufzeichnungsgerät definiert! | Ein Aufzeichnungsgerät ist definiert | ' +
|
||||||
|
'{num} Aufzeichnungsgeräte sind definiert',
|
||||||
|
recorder_models_defined: 'Es ist kein Aufzeichnungsgerätemodell definiert | ' +
|
||||||
|
'Ein Aufzeichnungsgerätemodell ist definiert' +
|
||||||
|
' | {num} Aufzeichnungsgerätemodelle sind definiert',
|
||||||
|
no: 'nein',
|
||||||
|
yes: 'ja',
|
||||||
|
undefined: 'unbekannt',
|
||||||
|
create: 'anlegen',
|
||||||
|
and: 'und',
|
||||||
|
number: 'Nummer',
|
||||||
|
delete: 'löschen',
|
||||||
|
requires_username: 'Nutzername benötigt',
|
||||||
|
requires_password: 'Passwort benötigt',
|
||||||
|
ssh_port: 'SSH Port',
|
||||||
|
telnet_port: 'Telnet Port',
|
||||||
|
serial_number: 'Seriennummer',
|
||||||
|
additional_camera_connected: 'zusätzliche Kamera verbunden',
|
||||||
|
firmware_version: 'Firmwareversion',
|
||||||
|
model_name: 'Modellname',
|
||||||
|
created_at: 'Erstellt um',
|
||||||
|
last_time_modified: 'Letzter Änderungszeitpunkt',
|
||||||
|
Session_timeout_in: 'Session läuft ab in',
|
||||||
|
Session_expired: 'Session ist abgelaufen',
|
||||||
|
auto_renew_session: 'Session automatisch erneuern',
|
||||||
|
},
|
||||||
en: {
|
en: {
|
||||||
welcomeMsg: 'Welcome to Your Vue.js + TypeScript App',
|
welcomeMsg: 'Welcome to Your Vue.js + TypeScript App',
|
||||||
guide: 'For a guide and recipes on how to configure / customize this project,',
|
guide: 'For a guide and recipes on how to configure / customize this project,',
|
||||||
@@ -12,6 +100,75 @@ const messages = {
|
|||||||
plugins: 'Installed CLI Plugins',
|
plugins: 'Installed CLI Plugins',
|
||||||
links: 'Essential Links',
|
links: 'Essential Links',
|
||||||
ecosystem: 'Ecosystem',
|
ecosystem: 'Ecosystem',
|
||||||
|
Building_number: 'Building number',
|
||||||
|
building_number: 'Building #',
|
||||||
|
building_name: 'Building',
|
||||||
|
Manage_rooms: 'Manage rooms',
|
||||||
|
Manage_recorders: 'Manage recorders',
|
||||||
|
Create_a_new_recorder: 'Create a new recorder',
|
||||||
|
Create_a_new_room: 'Create a new room',
|
||||||
|
Recorder_name: 'Recorder name',
|
||||||
|
Recorder: 'Recorder',
|
||||||
|
recorder: 'recorder',
|
||||||
|
Recorders: 'Recorders',
|
||||||
|
recorders: 'recorders',
|
||||||
|
command: 'command',
|
||||||
|
commands: 'commands',
|
||||||
|
Commands: 'Commands',
|
||||||
|
virtual_commands: 'virtual commands',
|
||||||
|
admin: 'admin',
|
||||||
|
name: 'name',
|
||||||
|
alternate_name: 'alternate name',
|
||||||
|
group: 'group',
|
||||||
|
created: 'created',
|
||||||
|
Comments: 'comments',
|
||||||
|
comments: 'comments',
|
||||||
|
comment: 'comment',
|
||||||
|
Room: 'room',
|
||||||
|
room: 'room',
|
||||||
|
Rooms: 'Rooms',
|
||||||
|
rooms: 'rooms',
|
||||||
|
Room_number: 'Room number',
|
||||||
|
room_number: 'room number',
|
||||||
|
user: 'user',
|
||||||
|
username: 'username',
|
||||||
|
password: 'password',
|
||||||
|
first_name: 'first name',
|
||||||
|
last_name: 'last name',
|
||||||
|
nickname: 'nickname',
|
||||||
|
set_your_nickname: 'Set your nickname',
|
||||||
|
notes: 'notes',
|
||||||
|
List_create_and_delete: 'List_create_and_delete',
|
||||||
|
no_recorder_defined: 'no recorder defined',
|
||||||
|
No_recorder_selected: 'No recorder selected',
|
||||||
|
No_recorder_model_selected: 'No recorder model selected',
|
||||||
|
Model: 'Model',
|
||||||
|
list: 'list',
|
||||||
|
recorder_model: 'recorder model',
|
||||||
|
recorder_models: 'recorder models',
|
||||||
|
description: 'description',
|
||||||
|
network_name: 'network name',
|
||||||
|
ip: 'IP (v4)',
|
||||||
|
ipv6: 'IP v6',
|
||||||
|
may_be_left_blank: 'may be left blank',
|
||||||
|
rooms_defined: 'No room defined yet! | There is one room defined | There are {num} rooms defined',
|
||||||
|
recorders_defined: 'No recorder defined yet! | There is one recorder defined | ' +
|
||||||
|
'There are {num} recorders defined',
|
||||||
|
recorder_models_defined: 'No recorder model defined yet! | There is one recorder model defined | ' +
|
||||||
|
'There are {num} recorder models defined',
|
||||||
|
no: 'no',
|
||||||
|
yes: 'yes',
|
||||||
|
and: 'and',
|
||||||
|
delete: 'delete',
|
||||||
|
create: 'create',
|
||||||
|
number: 'number',
|
||||||
|
requires_username: 'requires username',
|
||||||
|
requires_password: 'requires password',
|
||||||
|
ssh_port: 'SSH Port',
|
||||||
|
telnet_port: 'Telnet Port',
|
||||||
|
Session_timeout_in: 'Session will timeout in',
|
||||||
|
Session_expired: 'Session has expired',
|
||||||
|
auto_renew_session: 'auto renew session',
|
||||||
},
|
},
|
||||||
es: {
|
es: {
|
||||||
welcomeMsg: 'Bienvenido a tu aplicación Vue.js + TypeScript',
|
welcomeMsg: 'Bienvenido a tu aplicación Vue.js + TypeScript',
|
||||||
@@ -25,7 +182,7 @@ const messages = {
|
|||||||
|
|
||||||
|
|
||||||
export default new VueI18n({
|
export default new VueI18n({
|
||||||
locale: 'en', // set locale
|
locale: 'de', // set locale
|
||||||
fallbackLocale: 'es', // set fallback locale
|
fallbackLocale: 'en', // set fallback locale
|
||||||
messages, // set locale messages
|
messages, // set locale messages
|
||||||
});
|
});
|
||||||
|
|||||||
121
src/router.ts
121
src/router.ts
@@ -3,9 +3,28 @@ import Router from 'vue-router';
|
|||||||
import Home from './views/Home.vue';
|
import Home from './views/Home.vue';
|
||||||
import NotFound from './views/NotFound.vue';
|
import NotFound from './views/NotFound.vue';
|
||||||
|
|
||||||
|
import NewSurvey from '@/components/Rooms.vue';
|
||||||
|
import Login from '@/components/Login.vue';
|
||||||
|
import Admin from '@/components/Admin.vue';
|
||||||
|
import Profile from '@/components/Profile.vue';
|
||||||
|
import Users from '@/components/Admin/Users.vue';
|
||||||
|
import User from '@/components/Admin/User.vue';
|
||||||
|
import Test from '@/components/Test.vue';
|
||||||
|
import Groups from '@/components/Admin/Groups.vue';
|
||||||
|
import Group from '@/components/Admin/Group.vue';
|
||||||
|
import Permission from '@/components/Admin/Permission.vue';
|
||||||
|
import Rooms from '@/components/Rooms.vue';
|
||||||
|
import Recorders from '@/components/Recorders.vue';
|
||||||
|
import Recorder from '@/components/Recorder.vue';
|
||||||
|
import RecorderModel from '@/components/RecorderModel.vue';
|
||||||
|
import Commands from '@/components/Commands.vue';
|
||||||
|
import store from '@/store';
|
||||||
|
|
||||||
|
|
||||||
Vue.use(Router);
|
Vue.use(Router);
|
||||||
|
|
||||||
export default new Router({
|
export const router = new Router({
|
||||||
|
// export default new Router({
|
||||||
mode: 'history',
|
mode: 'history',
|
||||||
base: process.env.BASE_URL,
|
base: process.env.BASE_URL,
|
||||||
routes: [
|
routes: [
|
||||||
@@ -14,6 +33,45 @@ export default new Router({
|
|||||||
name: 'home',
|
name: 'home',
|
||||||
component: Home,
|
component: Home,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path: '/login',
|
||||||
|
name: 'login',
|
||||||
|
component: Login,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: '/admin',
|
||||||
|
name: 'admin',
|
||||||
|
component: Admin,
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
name: 'admin.users',
|
||||||
|
path: 'users',
|
||||||
|
component: Users,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'admin.user',
|
||||||
|
path: 'user/:user_id?',
|
||||||
|
props: true,
|
||||||
|
component: User,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'admin.groups',
|
||||||
|
path: 'groups',
|
||||||
|
component: Groups,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'admin.group',
|
||||||
|
path: 'group/:group_id?',
|
||||||
|
props: true,
|
||||||
|
component: Group,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'admin.permission',
|
||||||
|
path: 'permission',
|
||||||
|
component: Permission,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
{
|
{
|
||||||
path: '/about',
|
path: '/about',
|
||||||
name: 'about',
|
name: 'about',
|
||||||
@@ -21,6 +79,65 @@ export default new Router({
|
|||||||
// this generates a separate chunk (about.[hash].js) for this route
|
// this generates a separate chunk (about.[hash].js) for this route
|
||||||
// which is lazy-loaded when the route is visited.
|
// which is lazy-loaded when the route is visited.
|
||||||
component: () => import(/* webpackChunkName: "about" */ './views/About.vue'),
|
component: () => import(/* webpackChunkName: "about" */ './views/About.vue'),
|
||||||
|
}, {
|
||||||
|
path: '/surveys/:id',
|
||||||
|
name: 'Survey',
|
||||||
|
props: true,
|
||||||
|
component: Rooms,
|
||||||
|
}, {
|
||||||
|
path: '/rooms',
|
||||||
|
name: 'rooms',
|
||||||
|
component: Rooms,
|
||||||
|
}, {
|
||||||
|
path: '/recorders',
|
||||||
|
name: 'recorders',
|
||||||
|
component: Recorders,
|
||||||
|
}, {
|
||||||
|
path: '/recorder/:recorder_id?',
|
||||||
|
name: 'recorder',
|
||||||
|
props: true,
|
||||||
|
component: Recorder,
|
||||||
|
}, {
|
||||||
|
path: '/recorder_model/:recorder_model_id?',
|
||||||
|
name: 'recorder_model',
|
||||||
|
props: true,
|
||||||
|
component: RecorderModel,
|
||||||
|
}, {
|
||||||
|
path: '/test',
|
||||||
|
name: 'test',
|
||||||
|
component: Test,
|
||||||
|
}, {
|
||||||
|
path: '/commands',
|
||||||
|
name: 'commands',
|
||||||
|
component: Commands,
|
||||||
|
}, {
|
||||||
|
path: '/surveys',
|
||||||
|
name: 'NewSurvey',
|
||||||
|
component: NewSurvey,
|
||||||
|
beforeEnter(to, from, next) {
|
||||||
|
if (!store.getters.isAuthenticated) {
|
||||||
|
next('/login');
|
||||||
|
} else {
|
||||||
|
next();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}, {
|
||||||
|
path: '/profile',
|
||||||
|
name: 'profile',
|
||||||
|
component: Profile,
|
||||||
|
beforeEnter(to, from, next) {
|
||||||
|
if (!store.getters.isAuthenticated) {
|
||||||
|
if (store.getters.isRefreshTokenValid) {
|
||||||
|
store.dispatch('refreshToken')
|
||||||
|
.then(() => next())
|
||||||
|
.catch(() => next('/login'));
|
||||||
|
} else {
|
||||||
|
next('/login');
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
next();
|
||||||
|
}
|
||||||
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: '*',
|
path: '*',
|
||||||
@@ -29,3 +146,5 @@ export default new Router({
|
|||||||
},
|
},
|
||||||
],
|
],
|
||||||
});
|
});
|
||||||
|
|
||||||
|
export default router;
|
||||||
|
|||||||
7
src/scss/_base.scss
Normal file
7
src/scss/_base.scss
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2019. Tobias Kurze
|
||||||
|
*/
|
||||||
|
|
||||||
|
.router-link-exact-active {
|
||||||
|
color: #42b983;
|
||||||
|
}
|
||||||
358
src/store.ts
358
src/store.ts
@@ -1,16 +1,360 @@
|
|||||||
import Vue from 'vue';
|
import Vue from 'vue';
|
||||||
import Vuex from 'vuex';
|
import Vuex from 'vuex';
|
||||||
|
import { createLogger } from 'vuex';
|
||||||
|
import createPersistedState from 'vuex-persistedstate';
|
||||||
|
import getRepository from '@/api/RepositoryFactory';
|
||||||
|
import RoomRepository from '@/api/roomRepository';
|
||||||
|
import RecordRepository from '@/api/recorderRepository';
|
||||||
|
import VirtualCommandRepository from '@/api/virtualCommandRepository';
|
||||||
|
import StateRepository from '@/api/stateRepository';
|
||||||
|
|
||||||
|
// imports of AJAX functions will go here
|
||||||
|
import {
|
||||||
|
fetchSurveys,
|
||||||
|
fetchSurvey,
|
||||||
|
getProviders,
|
||||||
|
saveSurveyResponse,
|
||||||
|
postNewSurvey,
|
||||||
|
authenticate,
|
||||||
|
register,
|
||||||
|
oidc_login, fetchUsers, getFreshToken, fetchProfile, fetchUserGroups, revokeRefreshKey,
|
||||||
|
} from '@/api';
|
||||||
|
import {isValidJwt, EventBus} from '@/utils';
|
||||||
|
|
||||||
Vue.use(Vuex);
|
Vue.use(Vuex);
|
||||||
|
|
||||||
export default new Vuex.Store({
|
const state = {
|
||||||
state: {
|
// single source of data
|
||||||
|
surveys: [],
|
||||||
|
rooms: [],
|
||||||
|
recorders: [],
|
||||||
|
recorderModels: [],
|
||||||
|
recorderCommands: [],
|
||||||
|
recorderStates: [],
|
||||||
|
virtualCommands: [],
|
||||||
|
loginProviders: [],
|
||||||
|
currentSurvey: {},
|
||||||
|
profile: {},
|
||||||
|
users: [],
|
||||||
|
userGroups: [],
|
||||||
|
access_token: '',
|
||||||
|
refresh_token: '',
|
||||||
|
};
|
||||||
|
|
||||||
},
|
|
||||||
mutations: {
|
|
||||||
|
|
||||||
},
|
// const RoomRepository = getRepository('room');
|
||||||
actions: {
|
|
||||||
|
|
||||||
},
|
|
||||||
|
const actions = {
|
||||||
|
// asynchronous operations
|
||||||
|
loadRooms(context: any) {
|
||||||
|
return RoomRepository.getRooms()
|
||||||
|
.then((response: any) => {
|
||||||
|
context.commit('setRooms', {rooms: response.data});
|
||||||
|
EventBus.$emit('roomsLoaded', response.data);
|
||||||
|
})
|
||||||
|
.catch((error: any) => {
|
||||||
|
EventBus.$emit('failedLoadingRooms', error);
|
||||||
});
|
});
|
||||||
|
},
|
||||||
|
loadRecorders(context: any) {
|
||||||
|
return RecordRepository.getRecorders()
|
||||||
|
.then((response: any) => {
|
||||||
|
context.commit('setRecorders', {recorders: response.data});
|
||||||
|
EventBus.$emit('recordersLoaded', response.data);
|
||||||
|
})
|
||||||
|
.catch((error: any) => {
|
||||||
|
EventBus.$emit('failedLoadingRecorders', error);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
loadRecorderStates(context: any) {
|
||||||
|
return StateRepository.getRecordersStates()
|
||||||
|
.then((response: any) => {
|
||||||
|
context.commit('setRecorderStates', {recorderStates: response.data});
|
||||||
|
EventBus.$emit('recorderStatesLoaded', response.data);
|
||||||
|
})
|
||||||
|
.catch((error: any) => {
|
||||||
|
EventBus.$emit('failedLoadingRecorderStates', error);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
loadRecorderState(context: any, recorderId: number) {
|
||||||
|
return StateRepository.getRecorderState(recorderId)
|
||||||
|
.then((response: any) => {
|
||||||
|
context.commit('setRecorderState', {recorderState: response.data});
|
||||||
|
EventBus.$emit('recorderStateLoaded', response.data);
|
||||||
|
})
|
||||||
|
.catch((error: any) => {
|
||||||
|
EventBus.$emit('failedLoadingRecorderStates', error);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
loadRecorderModels(context: any) {
|
||||||
|
return RecordRepository.getRecorderModels()
|
||||||
|
.then((response: any) => {
|
||||||
|
context.commit('setRecorderModels', {recorderModels: response.data});
|
||||||
|
EventBus.$emit('recorderModelsLoaded', response.data);
|
||||||
|
})
|
||||||
|
.catch((error: any) => {
|
||||||
|
EventBus.$emit('failedLoadingRecorderModels', error);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
loadRecorderCommands(context: any) {
|
||||||
|
return RecordRepository.getRecorderCommands()
|
||||||
|
.then((response: any) => {
|
||||||
|
context.commit('setRecorderCommands', {recorderCommands: response.data});
|
||||||
|
EventBus.$emit('recorderCommandsLoaded', response.data);
|
||||||
|
})
|
||||||
|
.catch((error: any) => {
|
||||||
|
EventBus.$emit('failedLoadingRecorderCommands', error);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
loadVirtualCommands(context: any) {
|
||||||
|
return VirtualCommandRepository.getVirtualCommands()
|
||||||
|
.then((response: any) => {
|
||||||
|
context.commit('setVirtualCommands', {virtualCommands: response.data});
|
||||||
|
EventBus.$emit('virtualCommandsLoaded', response.data);
|
||||||
|
})
|
||||||
|
.catch((error: any) => {
|
||||||
|
EventBus.$emit('failedLoadingVirtualCommands', error);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
loadCommands(context: any) {
|
||||||
|
return context.dispatch('loadRecorderCommands').then(() => {
|
||||||
|
return context.dispatch('loadVirtualCommands');
|
||||||
|
});
|
||||||
|
},
|
||||||
|
loadUsers(context: any) {
|
||||||
|
return fetchUsers(context.state.access_token)
|
||||||
|
.then((response) => {
|
||||||
|
context.commit('setUsers', {users: response.data});
|
||||||
|
EventBus.$emit('usersLoaded', response.data);
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
EventBus.$emit('failedLoadingUsers', error);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
loadUserGroups(context: any) {
|
||||||
|
return fetchUserGroups(context.state.access_token)
|
||||||
|
.then((response) => {
|
||||||
|
context.commit('setUserGroups', {groups: response.data});
|
||||||
|
EventBus.$emit('groupsLoaded', response.data);
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
EventBus.$emit('failedLoadingUserGroups', error);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
loadProfileAuthCheck(context: any) {
|
||||||
|
if (!getters.isAuthenticated) {
|
||||||
|
EventBus.$emit('accessTokenInvalid');
|
||||||
|
if (!getters.isRefreshTokenValid) {
|
||||||
|
EventBus.$emit('refreshTokenInvalid');
|
||||||
|
EventBus.$emit('accessAndRefreshTokenInvalid');
|
||||||
|
} else {
|
||||||
|
return context.dispatch('refreshToken').then(() => {
|
||||||
|
context.commit('loadProfile');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
context.commit('loadProfile');
|
||||||
|
}
|
||||||
|
},
|
||||||
|
loadProfile(context: any) {
|
||||||
|
return fetchProfile(context.state.access_token)
|
||||||
|
.then((response) => {
|
||||||
|
context.commit('setProfile', {profile: response.data});
|
||||||
|
EventBus.$emit('profileLoaded', response.data);
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
EventBus.$emit('failedLoadingProfile', error);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
loadLoginProviders(context: any) {
|
||||||
|
return getProviders()
|
||||||
|
.then((response) => {
|
||||||
|
context.commit('setLoginProviderData', {providers: response.data});
|
||||||
|
EventBus.$emit('loginProvidersLoaded', response.data);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
login(context: any, userData: any) {
|
||||||
|
context.commit('setUserData', {userData});
|
||||||
|
return authenticate(userData)
|
||||||
|
.then((response) => context.commit('setJwtToken', {tokens: response.data}))
|
||||||
|
.catch((error) => {
|
||||||
|
EventBus.$emit('failedAuthentication', error);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
logout(context: any, revokeRefreshToken: any) {
|
||||||
|
context.commit('setTokens', {tokens: {access_token: '', refresh_token: ''}});
|
||||||
|
if (revokeRefreshToken) {
|
||||||
|
return revokeRefreshKey(context.state.access_token)
|
||||||
|
.catch((error) => {
|
||||||
|
EventBus.$emit('failedRevokingRefreshToken', error);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
// this.clearAll();
|
||||||
|
},
|
||||||
|
oidc_login(context: any, redirectionUrl: any) {
|
||||||
|
// context.commit('setUserData', { userData });
|
||||||
|
return oidc_login(redirectionUrl)
|
||||||
|
.then((response) => context.commit('setJwtToken', {tokens: response.data}))
|
||||||
|
.catch((error) => {
|
||||||
|
EventBus.$emit('failedAuthentication', error);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
refreshToken(context: any) {
|
||||||
|
EventBus.$emit('refreshingToken');
|
||||||
|
return getFreshToken(context.state.refresh_token)
|
||||||
|
.then((response) => {
|
||||||
|
context.commit('setTokens', {tokens: response.data});
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
EventBus.$emit('failedRefreshingToken', error);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
resetToken(context: any) {
|
||||||
|
EventBus.$emit('resettingTokens');
|
||||||
|
context.commit('setTokens', {tokens: {access_token: '', refresh_token: ''}});
|
||||||
|
EventBus.$emit('tokensReset');
|
||||||
|
},
|
||||||
|
storeTokens(context: any, tokens: any) {
|
||||||
|
context.commit('setTokens', {tokens});
|
||||||
|
EventBus.$emit('storedTokens');
|
||||||
|
},
|
||||||
|
register(context: any, userData: any) {
|
||||||
|
context.commit('setUserData', {userData});
|
||||||
|
return register(userData)
|
||||||
|
.then(context.dispatch('login', userData))
|
||||||
|
.catch((error) => {
|
||||||
|
EventBus.$emit('failedRegistering: ', error);
|
||||||
|
throw error;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
submitNewSurvey(context: any, survey: any) {
|
||||||
|
return postNewSurvey(survey, context.state.access_token);
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const mutations = {
|
||||||
|
// isolated data mutations
|
||||||
|
setSurveys(sState: any, payload: any) {
|
||||||
|
sState.surveys = payload.surveys;
|
||||||
|
},
|
||||||
|
setSurvey(sState: any, payload: any) {
|
||||||
|
const nQuestions = payload.survey.questions.length;
|
||||||
|
for (let i = 0; i < nQuestions; i++) {
|
||||||
|
payload.survey.questions[i].choice = null;
|
||||||
|
}
|
||||||
|
sState.currentSurvey = payload.survey;
|
||||||
|
},
|
||||||
|
setRooms(sState: any, payload: any) {
|
||||||
|
sState.rooms = payload.rooms;
|
||||||
|
},
|
||||||
|
setRecorders(sState: any, payload: any) {
|
||||||
|
sState.recorders = payload.recorders;
|
||||||
|
},
|
||||||
|
setRecorderModels(sState: any, payload: any) {
|
||||||
|
sState.recorderModels = payload.recorderModels;
|
||||||
|
},
|
||||||
|
setRecorderCommands(sState: any, payload: any) {
|
||||||
|
sState.recorderCommands = payload.recorderCommands;
|
||||||
|
},
|
||||||
|
setRecorderStates(sState: any, payload: any) {
|
||||||
|
sState.recorderStates = payload.recorderStates;
|
||||||
|
},
|
||||||
|
setRecorderState(sState: any, payload: any) {
|
||||||
|
const n_rec_states = sState.recorderStates.length;
|
||||||
|
const rec_id = payload.recorderState.id;
|
||||||
|
let found = false;
|
||||||
|
for (let i = 0; i < n_rec_states; i++) {
|
||||||
|
if (sState.recorderStates[i].id === rec_id) {
|
||||||
|
sState.recorderStates[i] = payload.recorderState;
|
||||||
|
found = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if(!found){
|
||||||
|
sState.recorderStates.push(payload.recorderState);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
setVirtualCommands(sState: any, payload: any) {
|
||||||
|
sState.virtualCommands = payload.virtualCommands;
|
||||||
|
},
|
||||||
|
setUsers(sState: any, payload: any) {
|
||||||
|
sState.users = payload.users;
|
||||||
|
},
|
||||||
|
setUserGroups(sState: any, payload: any) {
|
||||||
|
sState.userGroups = payload.groups;
|
||||||
|
},
|
||||||
|
setChoice(sState: any, payload: any) {
|
||||||
|
const {questionId, choice} = payload;
|
||||||
|
const nQuestions = sState.currentSurvey.questions.length;
|
||||||
|
for (let i = 0; i < nQuestions; i++) {
|
||||||
|
if (sState.currentSurvey.questions[i].id === questionId) {
|
||||||
|
sState.currentSurvey.questions[i].choice = choice;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
setLoginProviderData(sState: any, payload: any) {
|
||||||
|
sState.loginProviders = payload.providers;
|
||||||
|
},
|
||||||
|
// probably old ...
|
||||||
|
setUserData(sState: any, payload: any) {
|
||||||
|
sState.userData = payload.userData;
|
||||||
|
},
|
||||||
|
setProfile(sState: any, payload: any) {
|
||||||
|
sState.profile = payload.profile;
|
||||||
|
},
|
||||||
|
setJwtToken(sState: any, payload: any) {
|
||||||
|
localStorage.tokens = payload.tokens;
|
||||||
|
sState.access_token = payload.tokens.access_token;
|
||||||
|
sState.refresh_token = payload.tokens.refresh_token;
|
||||||
|
},
|
||||||
|
setTokens(sState: any, payload: any) {
|
||||||
|
if ('access_token' in payload.tokens) {
|
||||||
|
sState.access_token = payload.tokens.access_token;
|
||||||
|
}
|
||||||
|
if ('refresh_token' in payload.tokens) {
|
||||||
|
sState.refresh_token = payload.tokens.refresh_token;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const getters = {
|
||||||
|
// reusable data accessors
|
||||||
|
isAuthenticated(sState: any) {
|
||||||
|
return isValidJwt(sState.access_token);
|
||||||
|
},
|
||||||
|
isRefreshTokenValid(sState: any) {
|
||||||
|
return isValidJwt(sState.refresh_token);
|
||||||
|
},
|
||||||
|
getLoginProviders(sState: any) {
|
||||||
|
return sState.loginProviders;
|
||||||
|
},
|
||||||
|
getUserName(sState: any) {
|
||||||
|
if (sState.profile == null || Object.keys(sState.profile).length === 0) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
if (sState.profile.nickname) {
|
||||||
|
return sState.profile.nickname;
|
||||||
|
}
|
||||||
|
return sState.profile.first_name + ' ' + sState.profile.last_name;
|
||||||
|
},
|
||||||
|
// this is probably wrong!!
|
||||||
|
hasAccessRight(sState: any) {
|
||||||
|
(requestedPermission: string) => {
|
||||||
|
return sState.profile.effective_permissions.find((permission: any) => {
|
||||||
|
permission.name === requestedPermission;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const store = new Vuex.Store({
|
||||||
|
state,
|
||||||
|
actions,
|
||||||
|
mutations,
|
||||||
|
getters,
|
||||||
|
plugins: [createPersistedState(), createLogger()],
|
||||||
|
});
|
||||||
|
|
||||||
|
export default store;
|
||||||
|
|||||||
52
src/utils/index.ts
Normal file
52
src/utils/index.ts
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
// utils/index.js
|
||||||
|
|
||||||
|
import Vue from 'vue';
|
||||||
|
|
||||||
|
// @ts-ignore
|
||||||
|
String.prototype.isEmpty = function() {
|
||||||
|
return (this.length === 0 || !this.trim());
|
||||||
|
};
|
||||||
|
|
||||||
|
export const EventBus = new Vue();
|
||||||
|
|
||||||
|
export function isValidJwt(jwt: any) {
|
||||||
|
if (!jwt || jwt.split('.').length < 3) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
const data = JSON.parse(atob(jwt.split('.')[1]));
|
||||||
|
const exp = new Date(data.exp * 1000);
|
||||||
|
const now = new Date();
|
||||||
|
return now < exp;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getRemainingJwtValiditySeconds(jwt: any) {
|
||||||
|
if (!jwt || jwt.split('.').length < 3) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
const data = JSON.parse(atob(jwt.split('.')[1]));
|
||||||
|
const exp = new Date(data.exp * 1000);
|
||||||
|
const diff = exp.getTime() - new Date().getTime();
|
||||||
|
if (diff <= 0) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
return Math.round(diff / 1000 * 100) / 100;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function isEmpty(str: string) {
|
||||||
|
return (0 === str.length || !str.trim());
|
||||||
|
}
|
||||||
|
|
||||||
|
export function dictEmptyValToNull(dict: object): object {
|
||||||
|
if (dict == null) return {};
|
||||||
|
const newDict: { [key: string]: string | null | object; } = Object.assign({}, dict);
|
||||||
|
for (const [key, value] of Object.entries(dict)) {
|
||||||
|
if (typeof value === 'string') {
|
||||||
|
if (isEmpty(value)) {
|
||||||
|
newDict[key] = null;
|
||||||
|
}
|
||||||
|
} else if (value && value.constructor === Object) { // check if "dictionary"
|
||||||
|
newDict[key] = dictEmptyValToNull(value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return newDict;
|
||||||
|
}
|
||||||
44
src/validation.js
Normal file
44
src/validation.js
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
import {extend} from 'vee-validate';
|
||||||
|
import {required, email} from 'vee-validate/dist/rules';
|
||||||
|
import isIp from 'is-ip';
|
||||||
|
|
||||||
|
extend('positive', value => {
|
||||||
|
return value >= 0;
|
||||||
|
});
|
||||||
|
|
||||||
|
const ip_re = new RegExp("^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$");
|
||||||
|
const ip6_re = new RegExp("(([0-9a-fA-F]{1,4}:){7,7}[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,7}:|([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|:((:[0-9a-fA-F]{1,4}){1,7}|:)|fe80:(:[0-9a-fA-F]{0,4}){0,4}%[0-9a-zA-Z]{1,}|::(ffff(:0{1,4}){0,1}:){0,1}((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])|([0-9a-fA-F]{1,4}:){1,4}:((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9]))")
|
||||||
|
|
||||||
|
const mac_re = new RegExp("^([0-9A-Fa-f]{2}[:-]){5}([0-9A-Fa-f]{2})$");
|
||||||
|
|
||||||
|
const fqdn_re = new RegExp("^(?!:\\/\\/)(?=.{1,255}$)((.{1,63}\\.){1,127}(?![0-9]*$)[a-z0-9-]+\\.?)$");
|
||||||
|
|
||||||
|
extend('ip', value => {
|
||||||
|
return isIp.v4(value);
|
||||||
|
});
|
||||||
|
|
||||||
|
extend('ip6', value => {
|
||||||
|
return isIp.v6(value);
|
||||||
|
});
|
||||||
|
extend('ip6', value => {
|
||||||
|
return isIp.v6(value);
|
||||||
|
});
|
||||||
|
|
||||||
|
extend('mac', value => {
|
||||||
|
return mac_re.test(value);
|
||||||
|
});
|
||||||
|
|
||||||
|
extend('ip_or_fqdn', value => {
|
||||||
|
if (value === null) return true;
|
||||||
|
if (isIp.v4(value)) {
|
||||||
|
return true;
|
||||||
|
} else if (isIp.v6(value)) {
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
return fqdn_re.test(value);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
//extend('email', email);
|
||||||
|
//extend('required', required);
|
||||||
@@ -9,3 +9,21 @@
|
|||||||
background: $bg-classroom;
|
background: $bg-classroom;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import {Vue} from "vue-property-decorator";
|
||||||
|
|
||||||
|
export default class Home extends Vue {
|
||||||
|
data() {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
mounted() {
|
||||||
|
this.$parent.$data.isLoading = true;
|
||||||
|
this.$parent.$data.dismissCountDown = 5;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|||||||
@@ -1,48 +1,93 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="home">
|
<div class="home">
|
||||||
<div>
|
<div class="container">
|
||||||
<button v-for="entry in languages" :key="entry.title" @click="changeLocale(entry.language)">
|
<section class="section">
|
||||||
<flag :iso="entry.flag" v-bind:squared="false"/>
|
<HelloWorld v-if="!authenticated" msg="You are not authenticated!"/>
|
||||||
{{entry.title}}
|
<div v-else>
|
||||||
</button>
|
<h1>Welcome <span v-if="profile.last_seen!=null">back</span> {{$store.getters.getUserName}}!
|
||||||
|
</h1>
|
||||||
|
<h4 v-if="profile.last_seen!=null">Last login: {{profile.last_seen | moment("dddd, MMMM Do YYYY")}}</h4>
|
||||||
|
<hr/>
|
||||||
|
<ErroneousRecorders/>
|
||||||
|
|
||||||
|
<h2>{{$t('Favorite recorders:')}}</h2>
|
||||||
|
<p>{{$t('Add favorite recorder:')}}</p>
|
||||||
|
<SelectRecorder @recorderSelected="addFavoriteRecorderToProfile"/>
|
||||||
|
<br/>
|
||||||
|
<div v-if="profile.favorite_recorders.length >0">
|
||||||
|
<b-card-group deck>
|
||||||
|
<RecorderState v-for="recorder in profile.favorite_recorders" :recorder="recorder" v-bind:key="recorder.id"/>
|
||||||
|
</b-card-group>
|
||||||
</div>
|
</div>
|
||||||
<img alt="Vue logo" src="../assets/logo.png">
|
</div>
|
||||||
<HelloWorld msg="Welcome to Your Vue.js + TypeScript App"/>
|
</section>
|
||||||
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script>
|
||||||
import {Component, Vue} from 'vue-property-decorator';
|
import {Component, Vue} from 'vue-property-decorator';
|
||||||
import HelloWorld from '@/components/HelloWorld.vue'; // @ is an alias to /src
|
import HelloWorld from '@/components/HelloWorld.vue'; // @ is an alias to /src
|
||||||
import i18n from '@/plugins/i18n';
|
import i18n from '@/plugins/i18n';
|
||||||
|
import ErroneousRecorders from '@/components/ErroneousRecorders.vue';
|
||||||
|
import SelectRecorder from '@/components/SelectRecorder.vue';
|
||||||
|
import RecorderState from '@/components/RecorderState.vue';
|
||||||
|
import getRepository from '@/api/RepositoryFactory';
|
||||||
|
|
||||||
|
const userRepository = getRepository('user');
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
components: {
|
components: {
|
||||||
|
ErroneousRecorders,
|
||||||
|
RecorderState,
|
||||||
|
SelectRecorder,
|
||||||
HelloWorld,
|
HelloWorld,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
export default class Home extends Vue {
|
export default class Home extends Vue {
|
||||||
public data() {
|
data() {
|
||||||
return {
|
return {};
|
||||||
languages: [
|
|
||||||
{flag: 'us', language: 'en', title: 'English'},
|
|
||||||
{flag: 'es', language: 'es', title: 'Español'},
|
|
||||||
{flag: 'de', language: 'de', title: 'Deutsch'},
|
|
||||||
],
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public changeLocale(locale: string): void {
|
mounted() {
|
||||||
i18n.locale = locale;
|
console.log(process.env.NODE_ENV);
|
||||||
|
if (this.authenticated) {
|
||||||
|
if (this.profile == null || Object.keys(this.profile).length === 0) {
|
||||||
|
this.$parent.$data.isLoading = true;
|
||||||
|
this.$store.dispatch('loadProfile').then(() => {
|
||||||
|
this.$parent.$data.isLoading = false;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
addFavoriteRecorderToProfile(recorder) {
|
||||||
|
userRepository.addFavoriteRecorder(recorder.recorder_id).then(() => {
|
||||||
|
this.$store.dispatch('loadProfile');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
get authenticated() {
|
||||||
|
return this.$store.getters.isAuthenticated;
|
||||||
|
}
|
||||||
|
|
||||||
|
get profile() {
|
||||||
|
return this.$store.state.profile;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
button {
|
.container {
|
||||||
padding: 15px;
|
border-radius: 25px;
|
||||||
border: 2px solid green;
|
padding: 20px;
|
||||||
font-size: 18px;
|
margin: auto;
|
||||||
margin: 15px;
|
width: 80%;
|
||||||
|
background-color: hsla(0, 0%, 100%, 0.51);
|
||||||
|
fill: #044b94;
|
||||||
|
fill-opacity: 0.9;
|
||||||
}
|
}
|
||||||
|
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -1,5 +1,22 @@
|
|||||||
<template>
|
<template>
|
||||||
<div>
|
<div>
|
||||||
<p>404 - Not Found</p>
|
<section class="hero is-primary">
|
||||||
|
<div class="hero-body">
|
||||||
|
<div class="container has-text-centered">
|
||||||
|
<div class="clearfix">
|
||||||
|
<h1>Requested resource could not be found!</h1>
|
||||||
|
<p>404 - Not Found -> have you meant... blabla (GT)</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
created() {
|
||||||
|
// window.location.href = "/my-new-404-page.html";
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|||||||
@@ -7,6 +7,7 @@
|
|||||||
"importHelpers": true,
|
"importHelpers": true,
|
||||||
"moduleResolution": "node",
|
"moduleResolution": "node",
|
||||||
"experimentalDecorators": true,
|
"experimentalDecorators": true,
|
||||||
|
"resolveJsonModule": true,
|
||||||
"esModuleInterop": true,
|
"esModuleInterop": true,
|
||||||
"allowSyntheticDefaultImports": true,
|
"allowSyntheticDefaultImports": true,
|
||||||
"sourceMap": true,
|
"sourceMap": true,
|
||||||
|
|||||||
@@ -1,11 +1,15 @@
|
|||||||
|
var path = require('path');
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
|
|
||||||
css: {
|
css: {
|
||||||
loaderOptions: {
|
loaderOptions: {
|
||||||
sass: {
|
sass: {
|
||||||
data: `
|
additionalData: `
|
||||||
@import "@/scss/_variables.scss";
|
@import "@/scss/_variables.scss";
|
||||||
`
|
@import "@/scss/_base.scss";
|
||||||
}
|
`,
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user