Initial commit
This commit is contained in:
5
users-manage/.env
Normal file
5
users-manage/.env
Normal file
@@ -0,0 +1,5 @@
|
||||
VITE_API_ADDR="http://172.25.78.64:8087"
|
||||
VITE_USERS_MODE="dev"
|
||||
VITE_SCAND_API_URL="http://172.25.78.64:9999/scand/api/get_data"
|
||||
VITE_SCAND_API_ADDR="http://172.25.78.64:9999"
|
||||
|
||||
29
users-manage/.eslintrc.cjs
Normal file
29
users-manage/.eslintrc.cjs
Normal file
@@ -0,0 +1,29 @@
|
||||
module.exports = {
|
||||
extends: [
|
||||
"eslint:recommended",
|
||||
"plugin:vue/vue3-recommended",
|
||||
],
|
||||
rules: {
|
||||
"no-multiple-empty-lines": ["error", {max: 2, "maxBOF": 0}],
|
||||
"indent": ["error", 2],
|
||||
"no-mixed-spaces-and-tabs": "error",
|
||||
"vue/attribute-hyphenation": "off",
|
||||
"vue/html-self-closing": ["error", {
|
||||
"html": {
|
||||
"void": "never",
|
||||
"normal": "always",
|
||||
"component": "always"
|
||||
},
|
||||
"svg": "always",
|
||||
"math": "always"
|
||||
}],
|
||||
"vue/no-reserved-component-names": "off",
|
||||
"vue/html-indent": ["error", 2, {
|
||||
"attribute": 1,
|
||||
"baseIndent": 1,
|
||||
"closeBracket": 0,
|
||||
"alignAttributesVertically": true,
|
||||
"ignores": []
|
||||
}]
|
||||
}
|
||||
}
|
||||
24
users-manage/.gitignore
vendored
Normal file
24
users-manage/.gitignore
vendored
Normal file
@@ -0,0 +1,24 @@
|
||||
# Logs
|
||||
logs
|
||||
*.log
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
pnpm-debug.log*
|
||||
lerna-debug.log*
|
||||
|
||||
node_modules
|
||||
dist
|
||||
dist-ssr
|
||||
*.local
|
||||
|
||||
# Editor directories and files
|
||||
.vscode/*
|
||||
!.vscode/extensions.json
|
||||
.idea
|
||||
.DS_Store
|
||||
*.suo
|
||||
*.ntvs*
|
||||
*.njsproj
|
||||
*.sln
|
||||
*.sw?
|
||||
3
users-manage/.vscode/extensions.json
vendored
Normal file
3
users-manage/.vscode/extensions.json
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"recommendations": ["Vue.volar", "Vue.vscode-typescript-vue-plugin"]
|
||||
}
|
||||
48
users-manage/Makefile
Normal file
48
users-manage/Makefile
Normal file
@@ -0,0 +1,48 @@
|
||||
HOOK_NAME=pre-push
|
||||
HOOK_PATH=../.git/hooks/$(HOOK_NAME)
|
||||
|
||||
restart:
|
||||
docker build -t proxy-ui-vue .
|
||||
docker run -d --name proxy-ui-vue --rm -p 5000:80 proxy-ui-vue
|
||||
|
||||
stoprestart:
|
||||
docker stop proxy-ui-vue
|
||||
docker build -t proxy-ui-vue .
|
||||
docker run -d --name proxy-ui-vue --rm -p 5000:80 proxy-ui-vue
|
||||
|
||||
rmrestart:
|
||||
docker image rm proxy-ui-vue
|
||||
docker build -t proxy-ui-vue .
|
||||
docker stop proxy-ui-vue
|
||||
docker run -d --name proxy-ui-vue --rm -p 5000:80 proxy-ui-vue
|
||||
|
||||
|
||||
prehook:
|
||||
@echo "Setting up pre-push hook..."
|
||||
@rm -f $(HOOK_PATH)
|
||||
@echo '#!/bin/sh' >> $(HOOK_PATH)
|
||||
@echo 'echo "Running lint for users-manage"' >> $(HOOK_PATH)
|
||||
@echo 'cd ./users-manage' >> $(HOOK_PATH)
|
||||
@echo 'yarn lint' >> $(HOOK_PATH)
|
||||
@echo 'if [ $$? -ne 0 ]; then' >> $(HOOK_PATH)
|
||||
@echo ' echo "Tests failed, push aborted."' >> $(HOOK_PATH)
|
||||
@echo ' exit 1' >> $(HOOK_PATH)
|
||||
@echo 'fi' >> $(HOOK_PATH)
|
||||
@echo 'echo "Running tests for proxy-ui"' >> $(HOOK_PATH)
|
||||
@echo 'yarn test_ones' >> $(HOOK_PATH)
|
||||
@echo 'if [ $$? -ne 0 ]; then' >> $(HOOK_PATH)
|
||||
@echo ' echo "Tests failed, push aborted."' >> $(HOOK_PATH)
|
||||
@echo ' exit 1' >> $(HOOK_PATH)
|
||||
@echo 'fi' >> $(HOOK_PATH)
|
||||
@echo 'exit 0' >> $(HOOK_PATH)
|
||||
@chmod +x $(HOOK_PATH)
|
||||
@echo "Pre-push hook set successfully."
|
||||
|
||||
|
||||
|
||||
push:
|
||||
ifeq ($(commit),)
|
||||
$(error mn is not set)
|
||||
endif
|
||||
make prehook
|
||||
git add . && git commit -m "feat($(commit)):" && git push
|
||||
7
users-manage/README.md
Normal file
7
users-manage/README.md
Normal file
@@ -0,0 +1,7 @@
|
||||
# Vue 3 + Vite
|
||||
|
||||
This template should help get you started developing with Vue 3 in Vite. The template uses Vue 3 `<script setup>` SFCs, check out the [script setup docs](https://v3.vuejs.org/api/sfc-script-setup.html#sfc-script-setup) to learn more.
|
||||
|
||||
## Recommended IDE Setup
|
||||
|
||||
- [VS Code](https://code.visualstudio.com/) + [Volar](https://marketplace.visualstudio.com/items?itemName=Vue.volar) (and disable Vetur) + [TypeScript Vue Plugin (Volar)](https://marketplace.visualstudio.com/items?itemName=Vue.vscode-typescript-vue-plugin).
|
||||
17
users-manage/index.html
Normal file
17
users-manage/index.html
Normal file
@@ -0,0 +1,17 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<link rel="stylesheet" href="/css/remixicons/remixicon.css">
|
||||
<link rel="stylesheet" href="/css/fonts.css">
|
||||
<link rel="stylesheet" href="/css/global.css">
|
||||
<link rel="stylesheet" href="/css/components/2_tabulator-table.css">
|
||||
<title>Vite + Vue</title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
<script type="module" src="/src/main.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
55
users-manage/package.json
Normal file
55
users-manage/package.json
Normal file
@@ -0,0 +1,55 @@
|
||||
{
|
||||
"name": "users-manage",
|
||||
"private": true,
|
||||
"version": "0.0.0",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
"build": "vite build",
|
||||
"preview": "vite preview",
|
||||
"test_dom": "vitest --dom",
|
||||
"test_dom_ones": "vitest run --dom",
|
||||
"test": "vitest",
|
||||
"test_ones": "vitest run",
|
||||
"lint": "yarn eslint './**/*.{js,vue}'",
|
||||
"lint_fix": "yarn eslint './**/*.{js,vue}' --fix"
|
||||
},
|
||||
"dependencies": {
|
||||
"axios": "^1.6.7",
|
||||
"flowbite": "^2.3.0",
|
||||
"flowbite-vue": "^0.1.2",
|
||||
"handlebars": "^4.7.8",
|
||||
"moment": "^2.30.1",
|
||||
"postcss-import": "^16.0.1",
|
||||
"ramda": "^0.29.1",
|
||||
"ramda-vue": "^0.0.9",
|
||||
"tabulator-tables": "5.5.2",
|
||||
"tippy.js": "^6.3.7",
|
||||
"vue": "^3.4.15",
|
||||
"vue-multiselect": "^2.1.8",
|
||||
"vue-router": "^4.2.5",
|
||||
"vue-tippy": "^6.4.1",
|
||||
"vuex": "^4.1.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@tailwindcss/forms": "^0.5.7",
|
||||
"@vitejs/plugin-vue": "^5.0.3",
|
||||
"@vue/test-utils": "^2.4.4",
|
||||
"autoprefixer": "^10.4.17",
|
||||
"eslint": "^8.56.0",
|
||||
"eslint-plugin-vue": "^9.21.1",
|
||||
"happy-dom": "^13.6.2",
|
||||
"postcss": "^8.4.35",
|
||||
"postcss-advanced-variables": "^3.0.1",
|
||||
"postcss-apply": "^0.12.0",
|
||||
"postcss-nesting": "^12.0.3",
|
||||
"tailwindcss": "^3.4.1",
|
||||
"vite": "^5.1.0",
|
||||
"vitest": "^1.3.1"
|
||||
},
|
||||
"resolutions": {
|
||||
"strip-ansi": "6.0.1",
|
||||
"string-width": "4.2.2",
|
||||
"wrap-ansi": "7.0.0"
|
||||
}
|
||||
}
|
||||
9
users-manage/postcss.config.js
Normal file
9
users-manage/postcss.config.js
Normal file
@@ -0,0 +1,9 @@
|
||||
export default {
|
||||
plugins: {
|
||||
"postcss-import": {},
|
||||
"postcss-advanced-variables": {},
|
||||
"tailwindcss/nesting": {},
|
||||
tailwindcss: {},
|
||||
autoprefixer: {},
|
||||
},
|
||||
}
|
||||
284
users-manage/public/css/components/2_tabulator-table.css
Normal file
284
users-manage/public/css/components/2_tabulator-table.css
Normal file
@@ -0,0 +1,284 @@
|
||||
.tabulator {
|
||||
position: relative;
|
||||
font-size: 14px;
|
||||
text-align: left;
|
||||
overflow: hidden;
|
||||
transform: translateZ(0);
|
||||
}
|
||||
|
||||
.tabulator .tabulator-header {
|
||||
position: relative;
|
||||
box-sizing: border-box;
|
||||
width: 100%;
|
||||
color: rgb(54, 54, 51);
|
||||
font-weight: bold;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
padding-left: 5px;
|
||||
}
|
||||
|
||||
.tabulator-header-hidden {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.tabulator .tabulator-header .tabulator-header-contents {
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.tabulator .tabulator-header .tabulator-col {
|
||||
display: inline-flex;
|
||||
position: relative;
|
||||
box-sizing: border-box;
|
||||
flex-direction: column;
|
||||
justify-content: flex-start;
|
||||
text-align: left;
|
||||
vertical-align: middle;
|
||||
overflow: hidden;
|
||||
height: fit-content !important;
|
||||
margin-right: 3px;
|
||||
}
|
||||
/* .tabulator-col-resize-handle {
|
||||
height: fit-content !important;
|
||||
} */
|
||||
|
||||
.tabulator .tabulator-headers {
|
||||
}
|
||||
|
||||
.tabulator .tabulator-headers .tabulator-col-resize-handle {
|
||||
width: 3px;
|
||||
height: 25px !important;
|
||||
}
|
||||
|
||||
.tabulator .tabulator-tableholder {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
white-space: nowrap;
|
||||
overflow: auto;
|
||||
-webkit-overflow-scrolling: touch;
|
||||
}
|
||||
|
||||
.tabulator .tabulator-tableholder .tabulator-table {
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
white-space: nowrap;
|
||||
overflow: visible;
|
||||
color: #333;
|
||||
}
|
||||
.tabulator-row {
|
||||
position: relative;
|
||||
box-sizing: border-box;
|
||||
min-height: 22px;
|
||||
background-color: rgb(var(--color-slate-50));
|
||||
width: calc(100% - 15px);
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.tabulator-row:first-child {
|
||||
margin-top: 0;
|
||||
}
|
||||
.tabulator-row .tabulator-cell {
|
||||
display: inline-block;
|
||||
position: relative;
|
||||
box-sizing: border-box;
|
||||
vertical-align: middle;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
.tabulator-cell:first-child {
|
||||
padding: 0 0.25rem;
|
||||
}
|
||||
|
||||
.tabulator-placeholder-contents {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.tabulator .tabulator-col-resize-handle {
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
width: 6px;
|
||||
margin-left: -3px;
|
||||
margin-right: -3px;
|
||||
z-index: 10;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.tabulator .tabulator-header .tabulator-col .tabulator-col-content {
|
||||
box-sizing: border-box;
|
||||
position: relative;
|
||||
padding: 4px;
|
||||
}
|
||||
|
||||
.tabulator
|
||||
.tabulator-header
|
||||
.tabulator-col
|
||||
.tabulator-col-content
|
||||
.tabulator-col-title-holder {
|
||||
position: relative;
|
||||
cursor: pointer;
|
||||
}
|
||||
.tabulator-col-title {
|
||||
display: none;
|
||||
text-align: center;
|
||||
color: #7d7d7d;
|
||||
font-size: 14px;
|
||||
font-weight: 900;
|
||||
}
|
||||
/* .tabulator .tabulator-header .tabulator-col.tabulator-sortable .tabulator-col-title { */
|
||||
/* padding-right: 25px; */
|
||||
/* } */
|
||||
/*
|
||||
.tabulator .tabulator-header .tabulator-col .tabulator-col-content .tabulator-col-title {
|
||||
box-sizing: border-box;
|
||||
width: 100%;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
vertical-align: bottom;
|
||||
text-align: center;
|
||||
} */
|
||||
|
||||
.tabulator
|
||||
.tabulator-header
|
||||
.tabulator-col.tabulator-sortable[aria-sort="none"]
|
||||
.tabulator-col-content
|
||||
.tabulator-col-sorter {
|
||||
color: #bbb;
|
||||
}
|
||||
|
||||
.tabulator
|
||||
.tabulator-header
|
||||
.tabulator-col
|
||||
.tabulator-col-content
|
||||
.tabulator-col-sorter {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
position: absolute;
|
||||
top: 10px;
|
||||
bottom: 0;
|
||||
right: 4px;
|
||||
}
|
||||
|
||||
.tabulator
|
||||
.tabulator-header
|
||||
.tabulator-col.tabulator-sortable[aria-sort="none"]
|
||||
.tabulator-col-content
|
||||
.tabulator-col-sorter
|
||||
.tabulator-arrow {
|
||||
border-top: none;
|
||||
border-bottom: 6px solid #bbb;
|
||||
}
|
||||
.tabulator
|
||||
.tabulator-header
|
||||
.tabulator-col
|
||||
.tabulator-col-content
|
||||
.tabulator-col-sorter
|
||||
.tabulator-arrow {
|
||||
width: 0;
|
||||
height: 0;
|
||||
border-left: 6px solid transparent;
|
||||
border-right: 6px solid transparent;
|
||||
border-bottom: 6px solid #bbb;
|
||||
}
|
||||
|
||||
|
||||
|
||||
.tabulator-header-filter > input {
|
||||
padding: 0px 0.5rem !important;
|
||||
font-size: 10px;
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
.tabulator-header-filter {
|
||||
margin-right: 20px;
|
||||
}
|
||||
|
||||
.tabulator-header-filter > input {
|
||||
border-radius: 0;
|
||||
border: none;
|
||||
border-bottom: 1px solid rgb(var(--color-slate-200));
|
||||
}
|
||||
.tabulator
|
||||
.tabulator-header
|
||||
.tabulator-col.tabulator-col-sorter-element[aria-sort="descending"]
|
||||
.tabulator-col-sorter
|
||||
.tabulator-arrow {
|
||||
transform: rotate(180deg);
|
||||
}
|
||||
|
||||
.tabulator-row .tabulator-cell.tabulator-row-handle .tabulator-row-handle-box .tabulator-row-handle-bar {
|
||||
width: 100%;
|
||||
height: 2px;
|
||||
border-radius: 100px;
|
||||
margin-top: 2px;
|
||||
background: rgb(115, 115, 121);
|
||||
}
|
||||
|
||||
.tabulator-row .tabulator-cell.tabulator-row-handle .tabulator-row-handle-box {
|
||||
width: 80%;
|
||||
cursor: grab;
|
||||
}
|
||||
|
||||
.tabulator-row .tabulator-cell.tabulator-frozen {
|
||||
display: inline-block;
|
||||
position: sticky;
|
||||
left: 0;
|
||||
background-color: inherit;
|
||||
z-index: 10;
|
||||
}
|
||||
|
||||
|
||||
|
||||
.tabulator-row .tabulator-cell.tabulator-frozen.tabulator-frozen-left {
|
||||
border-right: 2px solid #aaa;
|
||||
}
|
||||
|
||||
.tabulator-row.tabulator-moving{
|
||||
position:absolute;
|
||||
border-top:1px solid #aaa;
|
||||
border-bottom:1px solid #aaa;
|
||||
pointer-events:none;
|
||||
z-index:15;
|
||||
cursor: grabbing;
|
||||
}
|
||||
|
||||
.tabulator-row .tabulator-row-resize-handle{position:absolute;right:0;bottom:0;left:0;height:5px}
|
||||
|
||||
.tabulator-row .tabulator-row-resize-handle.prev{top:0;bottom:auto}
|
||||
|
||||
.tabulator-row .tabulator-row-resize-handle:hover{cursor:ns-resize}
|
||||
|
||||
.tabulator-row .tabulator-responsive-collapse{box-sizing:border-box;padding:5px;border-top:1px solid #aaa;border-bottom:1px solid #aaa}
|
||||
|
||||
.tabulator-row .tabulator-responsive-collapse:empty{display:none}
|
||||
|
||||
.tabulator-row .tabulator-responsive-collapse table{font-size:14px}
|
||||
|
||||
.tabulator-row .tabulator-responsive-collapse table tr td{position:relative}
|
||||
|
||||
.tabulator-row .tabulator-responsive-collapse table tr td:first-of-type{padding-right:10px}
|
||||
|
||||
.tabulator-row .tabulator-cell{display:inline-block;position:relative;box-sizing:border-box;padding:4px;border-right:1px solid #aaa;vertical-align:middle;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
|
||||
|
||||
.tabulator-row .tabulator-cell.tabulator-frozen{display:inline-block;position:sticky;left:0;background-color:inherit;z-index:10}
|
||||
|
||||
.tabulator-row .tabulator-cell.tabulator-frozen.tabulator-frozen-left{border-right:2px solid #aaa}.tabulator-row .tabulator-cell.tabulator-frozen.tabulator-frozen-right{border-left:2px solid #aaa}
|
||||
|
||||
.tabulator-row .tabulator-cell.tabulator-editing{border:1px solid #1d68cd;outline:none;padding:0}
|
||||
|
||||
|
||||
.tabulator-row .tabulator-cell.tabulator-validation-fail{border:1px solid #d00}
|
||||
|
||||
|
||||
.tabulator-row .tabulator-cell.tabulator-row-handle {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
-moz-user-select: none;
|
||||
-khtml-user-select: none;
|
||||
-webkit-user-select: none;
|
||||
-o-user-select: none;
|
||||
}
|
||||
|
||||
/*# sourceMappingURL=tabulator.min.css.map */
|
||||
4
users-manage/public/css/fonts.css
Normal file
4
users-manage/public/css/fonts.css
Normal file
@@ -0,0 +1,4 @@
|
||||
@font-face {
|
||||
font-family: Nunito;
|
||||
src: url(../fonts/Nunito.ttf);
|
||||
}
|
||||
119
users-manage/public/css/global.css
Normal file
119
users-manage/public/css/global.css
Normal file
@@ -0,0 +1,119 @@
|
||||
|
||||
body {
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
html body {
|
||||
overflow-x: hidden;
|
||||
font-family: Nunito;
|
||||
font-size: 0.875rem;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
padding: 0 !important;
|
||||
}
|
||||
|
||||
.text-ssm {
|
||||
font-size: 0.675rem;
|
||||
line-height: 1.1rem;
|
||||
}
|
||||
|
||||
input[phx-hook='ToggleInput'] {
|
||||
border-color: transparent;
|
||||
}
|
||||
|
||||
|
||||
.fontw-100 {
|
||||
font-weight: 100
|
||||
}
|
||||
|
||||
.fontw-200 {
|
||||
font-weight: 200;
|
||||
}
|
||||
|
||||
.fontw-300 {
|
||||
font-weight: 300;
|
||||
}
|
||||
|
||||
.fontw-400 {
|
||||
font-weight: 400;
|
||||
}
|
||||
|
||||
.fontw-500 {
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.fontw-600 {
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.fontw-700 {
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.fontw-800 {
|
||||
font-weight: 800;
|
||||
}
|
||||
|
||||
.fontw-900 {
|
||||
font-weight: 900;
|
||||
}
|
||||
|
||||
.font-w-100 {
|
||||
font-weight: 100
|
||||
}
|
||||
|
||||
.font-w-200 {
|
||||
font-weight: 200;
|
||||
}
|
||||
|
||||
.font-w-300 {
|
||||
font-weight: 300;
|
||||
}
|
||||
|
||||
.font-w-400 {
|
||||
font-weight: 400;
|
||||
}
|
||||
|
||||
.font-w-500 {
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.font-w-600 {
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.font-w-700 {
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.font-w-800 {
|
||||
font-weight: 800;
|
||||
}
|
||||
|
||||
.font-w-900 {
|
||||
font-weight: 900;
|
||||
}
|
||||
|
||||
.transition-all {
|
||||
transition: all .3s;
|
||||
}
|
||||
|
||||
.transition-3 {
|
||||
transition: all .3s;
|
||||
}
|
||||
|
||||
.transition-4 {
|
||||
transition: all .4s;
|
||||
}
|
||||
|
||||
.transition-5 {
|
||||
transition: all .5s;
|
||||
}
|
||||
|
||||
.transition-6 {
|
||||
transition: all .6s;
|
||||
}
|
||||
|
||||
.transition-7 {
|
||||
transition: all .7s;
|
||||
}
|
||||
156
users-manage/public/css/remixicons/index.html
Normal file
156
users-manage/public/css/remixicons/index.html
Normal file
File diff suppressed because one or more lines are too long
2782
users-manage/public/css/remixicons/remixicon.css
Normal file
2782
users-manage/public/css/remixicons/remixicon.css
Normal file
File diff suppressed because it is too large
Load Diff
BIN
users-manage/public/css/remixicons/remixicon.eot
Normal file
BIN
users-manage/public/css/remixicons/remixicon.eot
Normal file
Binary file not shown.
1
users-manage/public/css/remixicons/remixicon.glyph.json
Normal file
1
users-manage/public/css/remixicons/remixicon.glyph.json
Normal file
File diff suppressed because one or more lines are too long
2784
users-manage/public/css/remixicons/remixicon.less
Normal file
2784
users-manage/public/css/remixicons/remixicon.less
Normal file
File diff suppressed because it is too large
Load Diff
2766
users-manage/public/css/remixicons/remixicon.module.less
Normal file
2766
users-manage/public/css/remixicons/remixicon.module.less
Normal file
File diff suppressed because it is too large
Load Diff
5501
users-manage/public/css/remixicons/remixicon.scss
Normal file
5501
users-manage/public/css/remixicons/remixicon.scss
Normal file
File diff suppressed because it is too large
Load Diff
2753
users-manage/public/css/remixicons/remixicon.styl
Normal file
2753
users-manage/public/css/remixicons/remixicon.styl
Normal file
File diff suppressed because it is too large
Load Diff
8230
users-manage/public/css/remixicons/remixicon.svg
Normal file
8230
users-manage/public/css/remixicons/remixicon.svg
Normal file
File diff suppressed because one or more lines are too long
|
After Width: | Height: | Size: 2.3 MiB |
11
users-manage/public/css/remixicons/remixicon.symbol.svg
Normal file
11
users-manage/public/css/remixicons/remixicon.symbol.svg
Normal file
File diff suppressed because one or more lines are too long
|
After Width: | Height: | Size: 1.4 MiB |
BIN
users-manage/public/css/remixicons/remixicon.ttf
Normal file
BIN
users-manage/public/css/remixicons/remixicon.ttf
Normal file
Binary file not shown.
BIN
users-manage/public/css/remixicons/remixicon.woff
Normal file
BIN
users-manage/public/css/remixicons/remixicon.woff
Normal file
Binary file not shown.
BIN
users-manage/public/css/remixicons/remixicon.woff2
Normal file
BIN
users-manage/public/css/remixicons/remixicon.woff2
Normal file
Binary file not shown.
19306
users-manage/public/css/remixicons/symbol.html
Normal file
19306
users-manage/public/css/remixicons/symbol.html
Normal file
File diff suppressed because it is too large
Load Diff
175
users-manage/public/css/remixicons/unicode.html
Normal file
175
users-manage/public/css/remixicons/unicode.html
Normal file
File diff suppressed because one or more lines are too long
BIN
users-manage/public/fonts/Nunito-Italic-VariableFont_wght.ttf
Normal file
BIN
users-manage/public/fonts/Nunito-Italic-VariableFont_wght.ttf
Normal file
Binary file not shown.
BIN
users-manage/public/fonts/Nunito.ttf
Normal file
BIN
users-manage/public/fonts/Nunito.ttf
Normal file
Binary file not shown.
93
users-manage/public/fonts/OFL.txt
Normal file
93
users-manage/public/fonts/OFL.txt
Normal file
@@ -0,0 +1,93 @@
|
||||
Copyright 2014 The Nunito Project Authors (https://github.com/googlefonts/nunito)
|
||||
|
||||
This Font Software is licensed under the SIL Open Font License, Version 1.1.
|
||||
This license is copied below, and is also available with a FAQ at:
|
||||
http://scripts.sil.org/OFL
|
||||
|
||||
|
||||
-----------------------------------------------------------
|
||||
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
|
||||
-----------------------------------------------------------
|
||||
|
||||
PREAMBLE
|
||||
The goals of the Open Font License (OFL) are to stimulate worldwide
|
||||
development of collaborative font projects, to support the font creation
|
||||
efforts of academic and linguistic communities, and to provide a free and
|
||||
open framework in which fonts may be shared and improved in partnership
|
||||
with others.
|
||||
|
||||
The OFL allows the licensed fonts to be used, studied, modified and
|
||||
redistributed freely as long as they are not sold by themselves. The
|
||||
fonts, including any derivative works, can be bundled, embedded,
|
||||
redistributed and/or sold with any software provided that any reserved
|
||||
names are not used by derivative works. The fonts and derivatives,
|
||||
however, cannot be released under any other type of license. The
|
||||
requirement for fonts to remain under this license does not apply
|
||||
to any document created using the fonts or their derivatives.
|
||||
|
||||
DEFINITIONS
|
||||
"Font Software" refers to the set of files released by the Copyright
|
||||
Holder(s) under this license and clearly marked as such. This may
|
||||
include source files, build scripts and documentation.
|
||||
|
||||
"Reserved Font Name" refers to any names specified as such after the
|
||||
copyright statement(s).
|
||||
|
||||
"Original Version" refers to the collection of Font Software components as
|
||||
distributed by the Copyright Holder(s).
|
||||
|
||||
"Modified Version" refers to any derivative made by adding to, deleting,
|
||||
or substituting -- in part or in whole -- any of the components of the
|
||||
Original Version, by changing formats or by porting the Font Software to a
|
||||
new environment.
|
||||
|
||||
"Author" refers to any designer, engineer, programmer, technical
|
||||
writer or other person who contributed to the Font Software.
|
||||
|
||||
PERMISSION & CONDITIONS
|
||||
Permission is hereby granted, free of charge, to any person obtaining
|
||||
a copy of the Font Software, to use, study, copy, merge, embed, modify,
|
||||
redistribute, and sell modified and unmodified copies of the Font
|
||||
Software, subject to the following conditions:
|
||||
|
||||
1) Neither the Font Software nor any of its individual components,
|
||||
in Original or Modified Versions, may be sold by itself.
|
||||
|
||||
2) Original or Modified Versions of the Font Software may be bundled,
|
||||
redistributed and/or sold with any software, provided that each copy
|
||||
contains the above copyright notice and this license. These can be
|
||||
included either as stand-alone text files, human-readable headers or
|
||||
in the appropriate machine-readable metadata fields within text or
|
||||
binary files as long as those fields can be easily viewed by the user.
|
||||
|
||||
3) No Modified Version of the Font Software may use the Reserved Font
|
||||
Name(s) unless explicit written permission is granted by the corresponding
|
||||
Copyright Holder. This restriction only applies to the primary font name as
|
||||
presented to the users.
|
||||
|
||||
4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
|
||||
Software shall not be used to promote, endorse or advertise any
|
||||
Modified Version, except to acknowledge the contribution(s) of the
|
||||
Copyright Holder(s) and the Author(s) or with their explicit written
|
||||
permission.
|
||||
|
||||
5) The Font Software, modified or unmodified, in part or in whole,
|
||||
must be distributed entirely under this license, and must not be
|
||||
distributed under any other license. The requirement for fonts to
|
||||
remain under this license does not apply to any document created
|
||||
using the Font Software.
|
||||
|
||||
TERMINATION
|
||||
This license becomes null and void if any of the above conditions are
|
||||
not met.
|
||||
|
||||
DISCLAIMER
|
||||
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
|
||||
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
|
||||
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
|
||||
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
|
||||
OTHER DEALINGS IN THE FONT SOFTWARE.
|
||||
79
users-manage/public/fonts/README.txt
Normal file
79
users-manage/public/fonts/README.txt
Normal file
@@ -0,0 +1,79 @@
|
||||
Nunito Variable Font
|
||||
====================
|
||||
|
||||
This download contains Nunito as both variable fonts and static fonts.
|
||||
|
||||
Nunito is a variable font with this axis:
|
||||
wght
|
||||
|
||||
This means all the styles are contained in these files:
|
||||
Nunito-VariableFont_wght.ttf
|
||||
Nunito-Italic-VariableFont_wght.ttf
|
||||
|
||||
If your app fully supports variable fonts, you can now pick intermediate styles
|
||||
that aren’t available as static fonts. Not all apps support variable fonts, and
|
||||
in those cases you can use the static font files for Nunito:
|
||||
static/Nunito-ExtraLight.ttf
|
||||
static/Nunito-Light.ttf
|
||||
static/Nunito-Regular.ttf
|
||||
static/Nunito-Medium.ttf
|
||||
static/Nunito-SemiBold.ttf
|
||||
static/Nunito-Bold.ttf
|
||||
static/Nunito-ExtraBold.ttf
|
||||
static/Nunito-Black.ttf
|
||||
static/Nunito-ExtraLightItalic.ttf
|
||||
static/Nunito-LightItalic.ttf
|
||||
static/Nunito-Italic.ttf
|
||||
static/Nunito-MediumItalic.ttf
|
||||
static/Nunito-SemiBoldItalic.ttf
|
||||
static/Nunito-BoldItalic.ttf
|
||||
static/Nunito-ExtraBoldItalic.ttf
|
||||
static/Nunito-BlackItalic.ttf
|
||||
|
||||
Get started
|
||||
-----------
|
||||
|
||||
1. Install the font files you want to use
|
||||
|
||||
2. Use your app's font picker to view the font family and all the
|
||||
available styles
|
||||
|
||||
Learn more about variable fonts
|
||||
-------------------------------
|
||||
|
||||
https://developers.google.com/web/fundamentals/design-and-ux/typography/variable-fonts
|
||||
https://variablefonts.typenetwork.com
|
||||
https://medium.com/variable-fonts
|
||||
|
||||
In desktop apps
|
||||
|
||||
https://theblog.adobe.com/can-variable-fonts-illustrator-cc
|
||||
https://helpx.adobe.com/nz/photoshop/using/fonts.html#variable_fonts
|
||||
|
||||
Online
|
||||
|
||||
https://developers.google.com/fonts/docs/getting_started
|
||||
https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Fonts/Variable_Fonts_Guide
|
||||
https://developer.microsoft.com/en-us/microsoft-edge/testdrive/demos/variable-fonts
|
||||
|
||||
Installing fonts
|
||||
|
||||
MacOS: https://support.apple.com/en-us/HT201749
|
||||
Linux: https://www.google.com/search?q=how+to+install+a+font+on+gnu%2Blinux
|
||||
Windows: https://support.microsoft.com/en-us/help/314960/how-to-install-or-remove-a-font-in-windows
|
||||
|
||||
Android Apps
|
||||
|
||||
https://developers.google.com/fonts/docs/android
|
||||
https://developer.android.com/guide/topics/ui/look-and-feel/downloadable-fonts
|
||||
|
||||
License
|
||||
-------
|
||||
Please read the full license text (OFL.txt) to understand the permissions,
|
||||
restrictions and requirements for usage, redistribution, and modification.
|
||||
|
||||
You can use them in your products & projects – print or digital,
|
||||
commercial or otherwise.
|
||||
|
||||
This isn't legal advice, please consider consulting a lawyer and see the full
|
||||
license for all details.
|
||||
BIN
users-manage/public/fonts/static/Nunito-Black.ttf
Normal file
BIN
users-manage/public/fonts/static/Nunito-Black.ttf
Normal file
Binary file not shown.
BIN
users-manage/public/fonts/static/Nunito-BlackItalic.ttf
Normal file
BIN
users-manage/public/fonts/static/Nunito-BlackItalic.ttf
Normal file
Binary file not shown.
BIN
users-manage/public/fonts/static/Nunito-Bold.ttf
Normal file
BIN
users-manage/public/fonts/static/Nunito-Bold.ttf
Normal file
Binary file not shown.
BIN
users-manage/public/fonts/static/Nunito-BoldItalic.ttf
Normal file
BIN
users-manage/public/fonts/static/Nunito-BoldItalic.ttf
Normal file
Binary file not shown.
BIN
users-manage/public/fonts/static/Nunito-ExtraBold.ttf
Normal file
BIN
users-manage/public/fonts/static/Nunito-ExtraBold.ttf
Normal file
Binary file not shown.
BIN
users-manage/public/fonts/static/Nunito-ExtraBoldItalic.ttf
Normal file
BIN
users-manage/public/fonts/static/Nunito-ExtraBoldItalic.ttf
Normal file
Binary file not shown.
BIN
users-manage/public/fonts/static/Nunito-ExtraLight.ttf
Normal file
BIN
users-manage/public/fonts/static/Nunito-ExtraLight.ttf
Normal file
Binary file not shown.
BIN
users-manage/public/fonts/static/Nunito-ExtraLightItalic.ttf
Normal file
BIN
users-manage/public/fonts/static/Nunito-ExtraLightItalic.ttf
Normal file
Binary file not shown.
BIN
users-manage/public/fonts/static/Nunito-Italic.ttf
Normal file
BIN
users-manage/public/fonts/static/Nunito-Italic.ttf
Normal file
Binary file not shown.
BIN
users-manage/public/fonts/static/Nunito-Light.ttf
Normal file
BIN
users-manage/public/fonts/static/Nunito-Light.ttf
Normal file
Binary file not shown.
BIN
users-manage/public/fonts/static/Nunito-LightItalic.ttf
Normal file
BIN
users-manage/public/fonts/static/Nunito-LightItalic.ttf
Normal file
Binary file not shown.
BIN
users-manage/public/fonts/static/Nunito-Medium.ttf
Normal file
BIN
users-manage/public/fonts/static/Nunito-Medium.ttf
Normal file
Binary file not shown.
BIN
users-manage/public/fonts/static/Nunito-MediumItalic.ttf
Normal file
BIN
users-manage/public/fonts/static/Nunito-MediumItalic.ttf
Normal file
Binary file not shown.
BIN
users-manage/public/fonts/static/Nunito-Regular.ttf
Normal file
BIN
users-manage/public/fonts/static/Nunito-Regular.ttf
Normal file
Binary file not shown.
BIN
users-manage/public/fonts/static/Nunito-SemiBold.ttf
Normal file
BIN
users-manage/public/fonts/static/Nunito-SemiBold.ttf
Normal file
Binary file not shown.
BIN
users-manage/public/fonts/static/Nunito-SemiBoldItalic.ttf
Normal file
BIN
users-manage/public/fonts/static/Nunito-SemiBoldItalic.ttf
Normal file
Binary file not shown.
1
users-manage/public/vite.svg
Normal file
1
users-manage/public/vite.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="31.88" height="32" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 257"><defs><linearGradient id="IconifyId1813088fe1fbc01fb466" x1="-.828%" x2="57.636%" y1="7.652%" y2="78.411%"><stop offset="0%" stop-color="#41D1FF"></stop><stop offset="100%" stop-color="#BD34FE"></stop></linearGradient><linearGradient id="IconifyId1813088fe1fbc01fb467" x1="43.376%" x2="50.316%" y1="2.242%" y2="89.03%"><stop offset="0%" stop-color="#FFEA83"></stop><stop offset="8.333%" stop-color="#FFDD35"></stop><stop offset="100%" stop-color="#FFA800"></stop></linearGradient></defs><path fill="url(#IconifyId1813088fe1fbc01fb466)" d="M255.153 37.938L134.897 252.976c-2.483 4.44-8.862 4.466-11.382.048L.875 37.958c-2.746-4.814 1.371-10.646 6.827-9.67l120.385 21.517a6.537 6.537 0 0 0 2.322-.004l117.867-21.483c5.438-.991 9.574 4.796 6.877 9.62Z"></path><path fill="url(#IconifyId1813088fe1fbc01fb467)" d="M185.432.063L96.44 17.501a3.268 3.268 0 0 0-2.634 3.014l-5.474 92.456a3.268 3.268 0 0 0 3.997 3.378l24.777-5.718c2.318-.535 4.413 1.507 3.936 3.838l-7.361 36.047c-.495 2.426 1.782 4.5 4.151 3.78l15.304-4.649c2.372-.72 4.652 1.36 4.15 3.788l-11.698 56.621c-.732 3.542 3.979 5.473 5.943 2.437l1.313-2.028l72.516-144.72c1.215-2.423-.88-5.186-3.54-4.672l-25.505 4.922c-2.396.462-4.435-1.77-3.759-4.114l16.646-57.705c.677-2.35-1.37-4.583-3.769-4.113Z"></path></svg>
|
||||
|
After Width: | Height: | Size: 1.5 KiB |
12
users-manage/src/App.vue
Normal file
12
users-manage/src/App.vue
Normal file
@@ -0,0 +1,12 @@
|
||||
<script>
|
||||
|
||||
export default {
|
||||
name: 'App',
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="p-6">
|
||||
<router-view />
|
||||
</div>
|
||||
</template>
|
||||
1
users-manage/src/assets/vue.svg
Normal file
1
users-manage/src/assets/vue.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="37.07" height="36" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 198"><path fill="#41B883" d="M204.8 0H256L128 220.8L0 0h97.92L128 51.2L157.44 0h47.36Z"></path><path fill="#41B883" d="m0 0l128 220.8L256 0h-51.2L128 132.48L50.56 0H0Z"></path><path fill="#35495E" d="M50.56 0L128 133.12L204.8 0h-47.36L128 51.2L97.92 0H50.56Z"></path></svg>
|
||||
|
After Width: | Height: | Size: 496 B |
12
users-manage/src/components/1_atoms/AppPageHeader.vue
Normal file
12
users-manage/src/components/1_atoms/AppPageHeader.vue
Normal file
@@ -0,0 +1,12 @@
|
||||
<script>
|
||||
export default {
|
||||
name: 'AppPageHeader',
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<a
|
||||
href="/#"
|
||||
class="text-white block w-fit bg-slate-700 hover:bg-blue-600 focus:outline-none focus:ring-4 focus:ring-blue-300 font-medium rounded-full text-sm text-center px-5 py-2.5 dark:bg-blue-600 dark:hover:bg-blue-700 dark:focus:ring-blue-800"
|
||||
><i class="ri-reply-line" /> На главную</a>
|
||||
</template>
|
||||
33
users-manage/src/components/1_atoms/NewSiteButton.vue
Normal file
33
users-manage/src/components/1_atoms/NewSiteButton.vue
Normal file
@@ -0,0 +1,33 @@
|
||||
<script>
|
||||
import {mapActions} from 'vuex'
|
||||
|
||||
export default {
|
||||
name: 'NewSiteButton',
|
||||
props: {
|
||||
name: {
|
||||
default: "",
|
||||
type: String
|
||||
},
|
||||
port: {
|
||||
default: "",
|
||||
type: String
|
||||
},
|
||||
site: {
|
||||
default: () => ({}),
|
||||
type: Object
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
...mapActions('services', ['addNewSiteLayout'])
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div
|
||||
class="cursor-pointer flex items-center justify-center w-full p-6 bg-white border border-gray-200 rounded-lg shadow hover:bg-gray-100 dark:bg-gray-800 dark:border-gray-700 dark:hover:bg-gray-700 text-2xl font-w-700 text-slate-700"
|
||||
@click="addNewSiteLayout"
|
||||
>
|
||||
Добавить сайт
|
||||
</div>
|
||||
</template>
|
||||
20
users-manage/src/components/1_atoms/VButton.vue
Normal file
20
users-manage/src/components/1_atoms/VButton.vue
Normal file
@@ -0,0 +1,20 @@
|
||||
<script>
|
||||
export default {
|
||||
name: 'VButton',
|
||||
props: {
|
||||
color: {
|
||||
type: String,
|
||||
default: 'purple'
|
||||
}
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<button
|
||||
type="button"
|
||||
:class="`rounded-md bg-${color}-700 hover:bg-fuchsia-700 transition-all cursor-pointer text-white p-2`"
|
||||
>
|
||||
<slot />
|
||||
</button>
|
||||
</template>
|
||||
102
users-manage/src/components/1_atoms/VDoubleSwitch.vue
Normal file
102
users-manage/src/components/1_atoms/VDoubleSwitch.vue
Normal file
@@ -0,0 +1,102 @@
|
||||
<script>
|
||||
export default {
|
||||
name: 'VDoubleSwitch',
|
||||
components: {
|
||||
// Button,
|
||||
},
|
||||
props: {
|
||||
machine: {
|
||||
type: Object,
|
||||
default: () => ({})
|
||||
},
|
||||
firstTitle: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
secondTitle: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
firstColor: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
secondColor: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
isCheck: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
position: {
|
||||
type: String,
|
||||
default: 'row'
|
||||
},
|
||||
labelClass: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
switchClass: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
},
|
||||
emits: ['switched'],
|
||||
data() {
|
||||
return {
|
||||
isChecked: false
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
},
|
||||
watch: {
|
||||
isCheck: function () {
|
||||
this.isChecked = !this.isChecked
|
||||
},
|
||||
},
|
||||
mounted () {
|
||||
|
||||
},
|
||||
methods: {
|
||||
buttonClass: function(value) {
|
||||
return `border border-slate-400 flex items-center justify-center cursor-pointer rounded transition-all text-xs text-center py-[5px] px-2 ${value}`
|
||||
},
|
||||
setChecked(e) {
|
||||
this.$emit('switched', e.target.checked)
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<label :class="`${position === 'row' ? 'flex-row' : 'flex-col'} ${labelClass} relative flex items-center cursor-pointer`">
|
||||
<input
|
||||
v-bind="$attrs"
|
||||
class="input sr-only peer/checkbox"
|
||||
type="checkbox"
|
||||
:checked="isCheck"
|
||||
@change="setChecked"
|
||||
>
|
||||
<span
|
||||
v-if="position === 'col'"
|
||||
class="block peer-checked/checkbox:hidden ml-0 text-xs font-medium text-gray-900 dark:text-gray-300"
|
||||
> {{ firstTitle }}</span>
|
||||
<span
|
||||
v-if="position === 'col'"
|
||||
class="hidden peer-checked/checkbox:block ml-0 text-xs font-medium text-gray-900 dark:text-gray-300"
|
||||
> {{ secondTitle }} </span>
|
||||
<div
|
||||
:class="`${switchClass} relative w-11 h-6 rounded-full peer peer-focus:ring-4 peer-focus:ring-${secondColor}-300 dark:peer-focus:ring-${secondColor}-800 dark:bg-${firstColor}-700 peer-checked/checkbox:after:translate-x-full peer-checked/checkbox:after:border-white after:content-[''] after:absolute after:top-0.5 after:left-[2px] after:bg-white after:border-${firstColor}-300 after:border after:rounded-full after:h-5 after:w-5 after:transition-all dark:border-${firstColor}-600`"
|
||||
:style="{background: !isCheck ? firstColor : secondColor}"
|
||||
/>
|
||||
<span
|
||||
v-if="position === 'row'"
|
||||
class="block peer-checked/checkbox:hidden ml-2 text-sm font-medium text-gray-900 dark:text-gray-300"
|
||||
> {{ firstTitle }}</span>
|
||||
<span
|
||||
v-if="position === 'row'"
|
||||
class="hidden peer-checked/checkbox:block ml-2 text-sm font-medium text-gray-900 dark:text-gray-300"
|
||||
> {{ secondTitle }} </span>
|
||||
</label>
|
||||
</template>
|
||||
59
users-manage/src/components/1_atoms/VInput.vue
Normal file
59
users-manage/src/components/1_atoms/VInput.vue
Normal file
@@ -0,0 +1,59 @@
|
||||
<script>
|
||||
export default {
|
||||
name: 'VInput',
|
||||
props: {
|
||||
id: {
|
||||
default: 'input',
|
||||
type: String
|
||||
},
|
||||
type: {
|
||||
default: 'text',
|
||||
type: String
|
||||
},
|
||||
name: {
|
||||
default: '',
|
||||
type: String
|
||||
},
|
||||
inputClass: {
|
||||
default: '',
|
||||
type: String
|
||||
},
|
||||
placeholder: {
|
||||
default: '',
|
||||
type: String
|
||||
},
|
||||
required: {
|
||||
default: false,
|
||||
type: Boolean
|
||||
},
|
||||
value: {
|
||||
default: '',
|
||||
type: String
|
||||
},
|
||||
onChange: {
|
||||
default: () => {},
|
||||
type: Function
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
},
|
||||
methods: {
|
||||
setValue: function (e) {
|
||||
this.onChange({key: this.name, value: e.target.value})
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<input
|
||||
:id="id"
|
||||
:type="type"
|
||||
:name="name"
|
||||
:class="`flex w-full rounded-lg border-zinc-300 py-1 px-2 text-zinc-900 focus:outline-none focus:ring-4 text-sm sm:leading-6 focus:border-zinc-400 focus:ring-zinc-800/5 ${inputClass}`"
|
||||
:placeholder="placeholder"
|
||||
:required="required"
|
||||
:value="value"
|
||||
:on:change="setValue"
|
||||
>
|
||||
</template>
|
||||
59
users-manage/src/components/1_atoms/VTextarea.vue
Normal file
59
users-manage/src/components/1_atoms/VTextarea.vue
Normal file
@@ -0,0 +1,59 @@
|
||||
<script>
|
||||
export default {
|
||||
name: 'VTextarea',
|
||||
props: {
|
||||
id: {
|
||||
default: 'input',
|
||||
type: String
|
||||
},
|
||||
type: {
|
||||
default: 'text',
|
||||
type: String
|
||||
},
|
||||
name: {
|
||||
default: '',
|
||||
type: String
|
||||
},
|
||||
textareaClass: {
|
||||
default: '',
|
||||
type: String
|
||||
},
|
||||
placeholder: {
|
||||
default: '',
|
||||
type: String
|
||||
},
|
||||
required: {
|
||||
default: false,
|
||||
type: Boolean
|
||||
},
|
||||
value: {
|
||||
default: '',
|
||||
type: String
|
||||
},
|
||||
onChange: {
|
||||
default: () => {},
|
||||
type: Function
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
},
|
||||
methods: {
|
||||
setValue: function (e) {
|
||||
this.onChange({key: this.name, value: e.target.value})
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<textarea
|
||||
:id="id"
|
||||
:type="type"
|
||||
:name="name"
|
||||
:class="`border p-2.5 w-full text-sm bg-gray-50 rounded-lg border-gray-200 focus:ring-blue-500 focus:border-blue-500 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500 ${textareaClass}`"
|
||||
:placeholder="placeholder"
|
||||
:required="required"
|
||||
:value="value"
|
||||
:on:change="setValue"
|
||||
/>
|
||||
</template>
|
||||
198
users-manage/src/components/2_molecules/Tabulator/VTabulator.vue
Normal file
198
users-manage/src/components/2_molecules/Tabulator/VTabulator.vue
Normal file
@@ -0,0 +1,198 @@
|
||||
<script>
|
||||
|
||||
import {
|
||||
prepColumns,
|
||||
sorter,
|
||||
renderGroupHeader,
|
||||
groupByFunction,
|
||||
tablefy,
|
||||
pipe
|
||||
} from "./helper";
|
||||
|
||||
import {TabulatorFull as Tabulator} from 'tabulator-tables';
|
||||
|
||||
|
||||
import localization from "./localization";
|
||||
import { is, sort, mergeDeepRight } from "ramda";
|
||||
import Pagination from "./VTabulatorPagination.vue";
|
||||
|
||||
export default {
|
||||
name: 'TabulatorFull',
|
||||
components: {
|
||||
Pagination
|
||||
},
|
||||
props: {
|
||||
dataSource: {
|
||||
type: Array,
|
||||
default: () => []
|
||||
},
|
||||
columns: {
|
||||
type: Array,
|
||||
default: () => []
|
||||
},
|
||||
initialSort: {
|
||||
type: String,
|
||||
default: null
|
||||
},
|
||||
groupBy: {
|
||||
type: String,
|
||||
default: null
|
||||
},
|
||||
groupByTemplate: {
|
||||
type: String,
|
||||
default: null
|
||||
},
|
||||
groupValues: {
|
||||
type: String,
|
||||
default: null
|
||||
},
|
||||
height: {
|
||||
type: String,
|
||||
default: "550px"
|
||||
},
|
||||
groupExtraValues: {
|
||||
type: String,
|
||||
default: null
|
||||
},
|
||||
groupHeader: {
|
||||
type: String,
|
||||
default: null
|
||||
},
|
||||
pagination: {
|
||||
type: Object,
|
||||
default: null
|
||||
},
|
||||
selectOption: {
|
||||
type: String,
|
||||
default: null
|
||||
},
|
||||
filterData: {
|
||||
type: String,
|
||||
default: null
|
||||
},
|
||||
withTablefy: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
refId: Math.random().toString(36).slice(4),
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
data () {
|
||||
if (!is(Array, this.dataSource)) return []
|
||||
if (this.withTablefy) return tablefy(this.dataSource)
|
||||
return this.dataSource
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.makeTable()
|
||||
},
|
||||
updated() {
|
||||
this.makeTable()
|
||||
},
|
||||
methods: {
|
||||
makeTable () {
|
||||
console.log(Tabulator)
|
||||
this.table = new Tabulator(this.$refs[this.refId], this.getConfig())
|
||||
},
|
||||
getConfig () {
|
||||
let configurator = pipe(this.__defaultCfg());
|
||||
return configurator(
|
||||
(x) => mergeDeepRight(x, this.__getColsCfg()),
|
||||
(x) => mergeDeepRight(x, this.__getSortCfg()),
|
||||
(x) => mergeDeepRight(x, this.__getGroupCfg()),
|
||||
(x) => mergeDeepRight(x, this.__getGroupHeaderCfg()),
|
||||
// (x) => mergeAll(x, this.__getPaginationCgs())
|
||||
);
|
||||
},
|
||||
__defaultCfg () {
|
||||
return {
|
||||
locale: "ru",
|
||||
langs: localization(),
|
||||
height: this.height || "250px",
|
||||
responsiveLayout: "collapse",
|
||||
placeholder: "Нет записей",
|
||||
movableColumns: true,
|
||||
autoResize: false,
|
||||
data: this.data,
|
||||
groupStartOpen: false,
|
||||
|
||||
groupHeader: function (value, count) {
|
||||
return value + "<span>(" + count + " записей)</span>";
|
||||
},
|
||||
};
|
||||
},
|
||||
__getColsCfg() {
|
||||
let layout, columns = []
|
||||
if (this.autoColumns && is(Object, this.data[0])) {
|
||||
let first = this.data[0];
|
||||
let keys = Object.keys(first).map((key) => ({
|
||||
headerFilter: "input",
|
||||
headerFilterPlaceholder: key,
|
||||
title: key,
|
||||
field: key,
|
||||
download: false,
|
||||
print: false,
|
||||
vertAlign: "middle",
|
||||
sorter: "string",
|
||||
responsive: 0,
|
||||
minWidth: 80,
|
||||
maxWidth: 300,
|
||||
}));
|
||||
layout = "fitDataFill"
|
||||
columns = sort(sorter, keys)
|
||||
} else {
|
||||
layout = "fitDataStretch"
|
||||
columns = prepColumns(this.columns)
|
||||
}
|
||||
if (this.withTablefy) {
|
||||
columns = tablefy(columns)
|
||||
}
|
||||
return {columns, layout}
|
||||
},
|
||||
__getSortCfg() {
|
||||
if (this.initialSort) return { initialSort: this.initialSort };
|
||||
return {};
|
||||
},
|
||||
__getGroupCfg() {
|
||||
if (this.groupByTemplate)
|
||||
return { groupBy: groupByFunction(this.groupByTemplate) };
|
||||
if (this.groupBy) return { groupBy: this.groupBy };
|
||||
return {};
|
||||
},
|
||||
__getGroupHeaderCfg() {
|
||||
if (this.groupHeader)
|
||||
return {
|
||||
groupHeader: (value, count, data, group) =>
|
||||
renderGroupHeader({
|
||||
extra: this.groupExtraValues,
|
||||
groupHeader: this.groupHeader,
|
||||
value,
|
||||
count,
|
||||
data,
|
||||
group,
|
||||
}),
|
||||
};
|
||||
return {};
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="mb-4">
|
||||
<div class="overflow-x-auto w-full">
|
||||
<div
|
||||
:ref="refId"
|
||||
class="w-full"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="pagination">
|
||||
<Pagination :params="pagination" />
|
||||
</div>
|
||||
</template>
|
||||
@@ -0,0 +1,183 @@
|
||||
<!-- eslint-disable -->
|
||||
|
||||
<script>
|
||||
import {mapGetters, mapMutations, mapActions, useStore} from "vuex"
|
||||
import { validation } from './helper'
|
||||
|
||||
export default {
|
||||
name: 'Pagination',
|
||||
components: {
|
||||
},
|
||||
props: {
|
||||
params: {
|
||||
type: Object,
|
||||
default: () => ({})
|
||||
},
|
||||
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
inputValue: null,
|
||||
initLocalStorage: null,
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
...mapGetters('main', ['machinesData', 'historyData', 'historyMachines']),
|
||||
},
|
||||
watch: {
|
||||
params: {
|
||||
handler(value, oldValue) {
|
||||
if(value) {
|
||||
const loader = document.querySelector(`#table_loder_${this.params.id}`)
|
||||
loader.style.display = 'none'
|
||||
}
|
||||
},
|
||||
},
|
||||
},
|
||||
mounted () {
|
||||
|
||||
},
|
||||
methods: {
|
||||
...mapMutations('main', ['setInitHistory', 'setHistoryData', 'setHistoryMachines']),
|
||||
selectOptions: (selectOption, paginationOptions) => {
|
||||
const options = selectOption ? selectOption : optionsDefault
|
||||
const currentOptions = options.filter((el) => {
|
||||
if(el <= Math.ceil(paginationOptions.total / 10) * 10) {
|
||||
return el
|
||||
}
|
||||
})
|
||||
if(paginationOptions.show_all) {
|
||||
currentOptions.push('Все')
|
||||
}
|
||||
return currentOptions
|
||||
},
|
||||
currentEntries: (pagination) => {
|
||||
const endPageDataIdx = pagination.page_size * pagination.page_number
|
||||
const isEndPageDataIdxMore = endPageDataIdx > pagination.total
|
||||
if(isEndPageDataIdxMore || pagination.page_size > pagination.total) {
|
||||
return (`${pagination.page_size * (pagination.page_number - 1) + 1} ... ${pagination.total}`)
|
||||
} else {
|
||||
return (`${pagination.page_size * (pagination.page_number - 1) + 1} ... ${pagination.page_size * pagination.page_number}`)
|
||||
}
|
||||
},
|
||||
setPagination: function ({pageNumber, pageSize}) {
|
||||
const loader = document.querySelector(`#table_loder_${this.params.id}`)
|
||||
loader.style.display = 'block'
|
||||
this.params.updatePagination({pageNumber: pageNumber, pageSize: pageSize})
|
||||
},
|
||||
updateSelectedValue: function (e) {
|
||||
const loader = document.querySelector(`#table_loder_${this.params.id}`)
|
||||
loader.style.display = 'block'
|
||||
this.setPagination({
|
||||
pageNumber: 1, // this.params.paginationOptions.page_number,
|
||||
pageSize: e.target.value
|
||||
})
|
||||
},
|
||||
updateInputValue: function (e) {
|
||||
const pageNumber = e.target.value.trim()
|
||||
const buttonPageInput = document.querySelector(`.page-button-${this.params.id}`)
|
||||
if(pageNumber) {
|
||||
this.inputValue = pageNumber
|
||||
buttonPageInput.removeAttribute('disabled')
|
||||
buttonPageInput.classList.remove('opacity-50')
|
||||
} else {
|
||||
buttonPageInput.setAttribute('disabled', 'disabled')
|
||||
buttonPageInput.classList.add('opacity-50')
|
||||
}
|
||||
},
|
||||
setPaginationButton: function (e) {
|
||||
if(this.inputValue) {
|
||||
const error = validation(this.inputValue, this.params.paginationOptions)
|
||||
if(Number(this.inputValue) === this.params.paginationOptions.page_number) {
|
||||
return false
|
||||
}
|
||||
if(!error) {
|
||||
e.target.removeAttribute('disabled')
|
||||
e.target.classList.remove('opacity-50')
|
||||
const loader = document.querySelector(`#table_loder_${this.params.id}`)
|
||||
loader.style.display = 'block'
|
||||
e.target.focus()
|
||||
const params = {pageNumber: this.inputValue, pageSize: this.params.paginationOptions.page_size}
|
||||
return this.params.updatePagination(params)
|
||||
} else {
|
||||
e.target.setAttribute('disabled', 'disabled')
|
||||
e.target.classList.add('opacity-50')
|
||||
const errorObj = {
|
||||
id: this.params.id,
|
||||
color: "red",
|
||||
icon: "<i class='ri-alert-line'></i>",
|
||||
message: error,
|
||||
delay: 5000,
|
||||
}
|
||||
console.error(errorObj)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<!-- eslint-disable -->
|
||||
|
||||
<template>
|
||||
<div class="pagination w-full flex flex-row items-center justify-between gap-3 mt-2 mb-8 xl:mb-0 pb-8 xl:p-2.5 bg-slate-100 border-2 border-solid border-slate-300 rounded-lg flex-col 2xl:flex-row">
|
||||
<div class="pagination-left flex flex-row gap-1">
|
||||
<div class="current-entries flex text-sm font-bold">Показаны {{currentEntries(params.paginationOptions)}}</div>
|
||||
<div class="count-entries flex text-sm font-bold">(Всего записей - {{params.paginationOptions.total}})</div>
|
||||
</div>
|
||||
<div class="pagination-right flex flex-col xl:flex-row items-center gap-3">
|
||||
<div :id="`table_loder_${params.id}`" class="hidden w-full h-auto bg-light flex items-center justify-center px-2"><div type-node="loader" class="flex my_margin-3 items-center justify-center">
|
||||
<div role="status">
|
||||
<svg aria-hidden="true" class="mr-2 w-6 h-6 text-gray-400 animate-spin dark:text-gray-400 fill-white" viewBox="0 0 100 101" fill="none" >
|
||||
<path d="M100 50.5908C100 78.2051 77.6142 100.591 50 100.591C22.3858 100.591 0 78.2051 0 50.5908C0 22.9766 22.3858 0.59082 50 0.59082C77.6142 0.59082 100 22.9766 100 50.5908ZM9.08144 50.5908C9.08144 73.1895 27.4013 91.5094 50 91.5094C72.5987 91.5094 90.9186 73.1895 90.9186 50.5908C90.9186 27.9921 72.5987 9.67226 50 9.67226C27.4013 9.67226 9.08144 27.9921 9.08144 50.5908Z" fill="currentColor"/>
|
||||
<path d="M93.9676 39.0409C96.393 38.4038 97.8624 35.9116 97.0079 33.5539C95.2932 28.8227 92.871 24.3692 89.8167 20.348C85.8452 15.1192 80.8826 10.7238 75.2124 7.41289C69.5422 4.10194 63.2754 1.94025 56.7698 1.05124C51.7666 0.367541 46.6976 0.446843 41.7345 1.27873C39.2613 1.69328 37.813 4.19778 38.4501 6.62326C39.0873 9.04874 41.5694 10.4717 44.0505 10.1071C47.8511 9.54855 51.7191 9.52689 55.5402 10.0491C60.8642 10.7766 65.9928 12.5457 70.6331 15.2552C75.2735 17.9648 79.3347 21.5619 82.5849 25.841C84.9175 28.9121 86.7997 32.2913 88.1811 35.8758C89.083 38.2158 91.5421 39.6781 93.9676 39.0409Z" fill="currentFill"/>
|
||||
</svg>
|
||||
</div>
|
||||
</div></div>
|
||||
<div class="buttons flex h-fit gap-1 sm:gap-3 text-sm">
|
||||
<div class="flex gap-1 sm:gap-3" v-if="params.paginationOptions.page_number > 1">
|
||||
<div v-if="params.paginationOptions.page_number - 1 !== 1">
|
||||
<button :table_btn="`${params.id}`" class="previous flex items-center text-white font-bold bg-slate-600 hover:bg-slate-700 py-1 px-3 gap-2 rounded" @click="setPagination({pageNumber: 1, pageSize: params.paginationOptions.page_size})">
|
||||
<span>1</span>
|
||||
</button>
|
||||
</div>
|
||||
<button :table_btn="`${params.id}`" class="previous flex items-center text-white font-bold bg-slate-600 hover:bg-slate-700 py-1 px-3 gap-2 rounded" @click="setPagination({pageNumber: params.paginationOptions.page_number - 1, pageSize: params.paginationOptions.page_size})">
|
||||
<span class="hidden sm:flex">Назад</span><i class="ri-arrow-left-line sm:hidden"></i>
|
||||
</button>
|
||||
<button :table_btn="`${params.id}`" class="previous flex items-center text-white font-bold bg-slate-600 hover:bg-slate-700 py-1 px-3 gap-2 rounded" @click="setPagination({pageNumber: params.paginationOptions.page_number - 1, pageSize: params.paginationOptions.page_size})">
|
||||
<span>{{params.paginationOptions.page_number - 1}}</span>
|
||||
</button>
|
||||
</div>
|
||||
<button :table_btn="`${params.id}`" class="active flex items-center text-blue-700 font-bold bg-white py-1 px-3 gap-2 rounded cursor-default">
|
||||
<span>{{params.paginationOptions.page_number}}</span>
|
||||
</button>
|
||||
<div class="flex gap-1 sm:gap-3" v-if="params.paginationOptions.page_number < params.paginationOptions.total_pages">
|
||||
<button :table_btn="`${params.id}`" class="next flex items-center text-white font-bold bg-slate-600 hover:bg-slate-700 py-1 px-3 gap-2 rounded" @click="setPagination({pageNumber: params.paginationOptions.page_number + 1, pageSize: params.paginationOptions.page_size})">
|
||||
<span>{{params.paginationOptions.page_number + 1}}</span>
|
||||
</button>
|
||||
<button :table_btn="`${params.id}`" class="next flex items-center text-white font-bold bg-slate-600 hover:bg-slate-700 py-1 px-3 gap-2 rounded" @click="setPagination({pageNumber: params.paginationOptions.page_number + 1, pageSize: params.paginationOptions.page_size})">
|
||||
<span class="hidden sm:flex">Вперёд</span><i class="ri-arrow-right-line sm:hidden"></i>
|
||||
</button>
|
||||
<div v-if="params.paginationOptions.page_number + 1 !== params.paginationOptions.total_pages">
|
||||
<button :table_btn="`${params.id}`" class="next flex items-center text-white font-bold bg-slate-600 hover:bg-slate-700 py-1 px-3 gap-2 rounded" @click="setPagination({pageNumber: params.paginationOptions.total_pages, pageSize: params.paginationOptions.page_size})">
|
||||
<span>{{params.paginationOptions.total_pages}}</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex flex-row items-center justify-center gap-2"><input v-on:input="updateInputValue" :class="`page-input-${params.id}`" class="flex max-w-[80px] items-center text-slate-700 text-sm font-bold focus-visible:outline-blue-300 bg-white py-1 px-2 gap-2 rounded" value="" placeholder="Страница"/><button @click="setPaginationButton" disabled :class="`page-button-${params.id}`" class="opacity-50 flex items-center text-white font-bold bg-slate-600 hover:bg-slate-700 py-1 px-1 rounded">Перейти</button></div>
|
||||
<div id="select-on-page" class="select-on-page flex items-center justify-center gap-3 p-1 text-sm text-slate-700 font-bold">
|
||||
Показать
|
||||
<select name="on_page" v-bind:value="params.paginationOptions.page_size" v-on:change="updateSelectedValue" :table_option="`${params.id}`" class="flex text-gray-700 pl-3 pr-9 py-0.5 rounded cursor-pointer border-none" style="--tw-ring-color:focus: none" >
|
||||
<option :key={idx} v-for="(el, idx) in selectOptions(params.selectOption, params.paginationOptions)" :value="`${el === 'Все' ? params.paginationOptions.total : el}`">
|
||||
{{ el }}
|
||||
</option>`
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
</style>
|
||||
332
users-manage/src/components/2_molecules/Tabulator/helper.js
Normal file
332
users-manage/src/components/2_molecules/Tabulator/helper.js
Normal file
@@ -0,0 +1,332 @@
|
||||
import { always, cond, equals, is, T, mergeLeft } from "ramda";
|
||||
import Handlebars from "handlebars/dist/cjs/handlebars";
|
||||
import moment from "moment";
|
||||
import presetConfig from './presetConfig'
|
||||
|
||||
const tablefy = (list = []) => {
|
||||
const defaultData = (title) => ({
|
||||
headerFilter: "input",
|
||||
minWidth: 80,
|
||||
headerFilterPlaceholder: title,
|
||||
download: false,
|
||||
print: false,
|
||||
vertAlign: "middle",
|
||||
sorter: "string",
|
||||
responsive: 0
|
||||
})
|
||||
|
||||
if (!is(Array, list)) return []
|
||||
return list.map((el) => mergeLeft(el, defaultData(el?.title || "")))
|
||||
}
|
||||
|
||||
const math = (value, operator, operand) => {
|
||||
var left = parseFloat(value);
|
||||
var right = parseFloat(operand);
|
||||
|
||||
switch(operator) {
|
||||
case "+":
|
||||
return left + right;
|
||||
case "-":
|
||||
return left - right;
|
||||
case "*":
|
||||
return left * right;
|
||||
case "/":
|
||||
return left / right;
|
||||
case "%":
|
||||
return left % right;
|
||||
default:
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
const link = (col) => ({
|
||||
...col,
|
||||
formatter: (cell, { prefix = "", text, key = "id" }, onRendered) => {
|
||||
onRendered(function () {
|
||||
let row = cell.getRow().getData();
|
||||
if (!text && !cell.getValue()) {
|
||||
return null;
|
||||
}
|
||||
if (!row[key]) return null;
|
||||
|
||||
cell.getElement().innerHTML = `<a target="_blank" href="${prefix}/${
|
||||
row[key]
|
||||
}">${text || cell.getValue()}</a>`;
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
[
|
||||
['math', math],
|
||||
['presetConfig', (record, pack) => presetConfig(pack, record)],
|
||||
[
|
||||
"dt_shift",
|
||||
(date, shift, param) => {
|
||||
return date ? moment(date).add(shift, param) : "нд";
|
||||
},
|
||||
],
|
||||
[
|
||||
"dt_format",
|
||||
(date) => {
|
||||
return date ? moment(date).format("DD.MM.YYYY HH:mm") : "нд";
|
||||
},
|
||||
],
|
||||
[
|
||||
"dt_db_format",
|
||||
(date) => {
|
||||
return date ? moment(date).format("YYYY-MM-DDTHH:mm:ss") : "нд";
|
||||
},
|
||||
],
|
||||
[
|
||||
"from_now",
|
||||
(date) => {
|
||||
return moment(date).fromNow()
|
||||
},
|
||||
],
|
||||
[
|
||||
"json_parse",
|
||||
(item) => {
|
||||
return `
|
||||
<div>
|
||||
${item}
|
||||
</div>
|
||||
`
|
||||
},
|
||||
],
|
||||
[
|
||||
"time_from_sec",
|
||||
(ms) => {
|
||||
if (!ms) return "н/д"
|
||||
return `${moment(moment().subtract(Math.round(ms), 'milliseconds')).fromNow()}`.replace("назад", '')
|
||||
},
|
||||
],
|
||||
[
|
||||
"lenght",
|
||||
(list) => {
|
||||
return is(Array, list) ? list.length : 0;
|
||||
},
|
||||
],
|
||||
[
|
||||
"more",
|
||||
(one, two) => {
|
||||
return one > two;
|
||||
},
|
||||
],
|
||||
[
|
||||
"less",
|
||||
(one, two) => {
|
||||
return one < two;
|
||||
},
|
||||
],
|
||||
[
|
||||
"or",
|
||||
(one, two) => {
|
||||
return one || two;
|
||||
},
|
||||
],
|
||||
[
|
||||
"eq",
|
||||
(one, two) => {
|
||||
return one === two;
|
||||
},
|
||||
],
|
||||
[
|
||||
"endOf",
|
||||
(date, param) => moment(date).endOf(param)
|
||||
],
|
||||
[
|
||||
"startOf",
|
||||
(date, param) => moment(date).startOf(param)
|
||||
],
|
||||
[
|
||||
"render",
|
||||
(list, key) => {
|
||||
return list.map(el => el[key]).join(", ")
|
||||
}
|
||||
],
|
||||
[
|
||||
"get",
|
||||
(map, key) => {
|
||||
return map[key] ? `${map[key]}` : ""
|
||||
}
|
||||
],
|
||||
[
|
||||
"get_from",
|
||||
(map, key) => {
|
||||
return map[key] ? `${map[key]}` : ""
|
||||
}
|
||||
],
|
||||
[
|
||||
"uint8parse",
|
||||
(list) => pipe(
|
||||
(x) => Uint8Array.from(x, e => e.charCodeAt(0)),
|
||||
Array.from
|
||||
)(list)
|
||||
],
|
||||
[
|
||||
"filter_aoo",
|
||||
(list = [], key, _availible_list) => {
|
||||
const availible_list = pipe(
|
||||
(x) => Uint8Array.from(x, e => e.charCodeAt(0)),
|
||||
Array.from
|
||||
)(_availible_list)
|
||||
return list.filter((el) =>
|
||||
availible_list.includes(el[key]))
|
||||
}
|
||||
]
|
||||
].forEach(([name, func]) => {
|
||||
Handlebars.registerHelper(name, func);
|
||||
});
|
||||
|
||||
const render = (col) => ({
|
||||
...col,
|
||||
formatter: (cell, _, onRendered) => {
|
||||
|
||||
onRendered(function () {
|
||||
if (`${col.render}`.includes("{{") && `${col.render}`.includes("}}")) {
|
||||
let record = cell.getRow().getData();
|
||||
cell.getElement().innerHTML = Handlebars.compile(col.render)(
|
||||
{...record, __record__: record}
|
||||
);
|
||||
}
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
let sorter = (a, b) => {
|
||||
let [a_field, b_field] = [a["field"], b["field"]];
|
||||
if (a_field.includes("id") && b_field.includes("id"))
|
||||
return a_field.localeCompare(b_field);
|
||||
if (a_field.includes("id")) return -1;
|
||||
if (b_field.includes("id")) return 1;
|
||||
if (a_field.includes("dt") && b_field.includes("dt"))
|
||||
return b_field.localeCompare(a_field);
|
||||
if (a_field.includes("dt")) return -1;
|
||||
if (b_field.includes("dt")) return 1;
|
||||
if (a_field.includes("imei") && b_field.includes("imei"))
|
||||
return b_field.localeCompare(a_field);
|
||||
if (a_field.includes("imei")) return -1;
|
||||
if (b_field.includes("imei")) return 1;
|
||||
return a_field.localeCompare(b_field);
|
||||
};
|
||||
|
||||
const prepCol = (col) =>
|
||||
cond([
|
||||
[() => is(String, col.render), () => render(col)],
|
||||
[equals("link"), () => link(col)],
|
||||
[T, always(col)],
|
||||
])(col.mode);
|
||||
|
||||
const prepColumns = (cols) =>
|
||||
is(Array, cols) ? cols.map((col) => prepCol(col)) : [];
|
||||
|
||||
const renderGroupHeader = ({ groupHeader, value, count, group, extra }) => {
|
||||
const groupData = group._group;
|
||||
|
||||
if (groupData.level == 0) {
|
||||
const {company_id, param, train_info: train, date: _date} = groupData.groupList[0].rows[0].data
|
||||
|
||||
const date = `${_date}`.replace(/\s(.*)/gm, "")
|
||||
|
||||
const icident_group = extra.find((el) =>
|
||||
el.date === date &&
|
||||
el.company_id == company_id &&
|
||||
el.train == train &&
|
||||
el.param == param
|
||||
) || {}
|
||||
|
||||
return Handlebars.compile(groupHeader)({
|
||||
_value: value,
|
||||
_count: count,
|
||||
extra: extra,
|
||||
icident_group: icident_group
|
||||
});
|
||||
}
|
||||
|
||||
return `${value} (${count} записей)`;
|
||||
};
|
||||
|
||||
const groupByFunction = (groupBy) => (data) =>
|
||||
Handlebars.compile(groupBy)(data);
|
||||
|
||||
const makedFiltering = (id, filterParams, selectedFilter, clearFilters) => {
|
||||
|
||||
const table = document.querySelector(`#${id}`)
|
||||
const isFiltering = document.querySelector(`${id}-filters`)
|
||||
|
||||
if(id && filterParams) {
|
||||
filterParams.unshift([])
|
||||
const filtersContainer = document.createElement('div')
|
||||
filtersContainer.setAttribute("id", `${id}-filters`)
|
||||
filtersContainer.classList.add("col-span-12")
|
||||
filtersContainer.innerHTML = `
|
||||
<div id="${id}-filtersContainer" class="filters flex flex-row items-center justify-start gap-3 mb-0 pt-2.5 bg-slate-100 rounded-t-md flex-row 2xl:flex-row">
|
||||
<div id="${id}-selectFilters" class="flex items-center justify-start gap-3 p-1 ml-2 text-base text-slate-700 font-bold">
|
||||
Фильтр по статусу
|
||||
<select name="selectFilter" id="${id}-selectFilter" class="flex text-gray-700 pl-3 pr-9 py-0.5 rounded cursor-pointer border-none" style="--tw-ring-color:focus: none" >
|
||||
${filterParams.map((el) => {
|
||||
return (`<option value='${el}'>
|
||||
${el}
|
||||
</option>`)
|
||||
})}
|
||||
</select>
|
||||
</div>
|
||||
<button id="${id}-filtersClear"class="w-fit h-fit flex items-center text-white text-sm font-bold bg-slate-600 hover:bg-slate-700 py-1 px-3 gap-1 rounded">Очистить фильтр</button>
|
||||
</div>`
|
||||
if(!isFiltering) {
|
||||
table.before(filtersContainer)
|
||||
}
|
||||
else {
|
||||
isFiltering.remove()
|
||||
table.before(filtersContainer)
|
||||
}
|
||||
|
||||
const selectFilters = document.querySelectorAll(`#${id}-selectFilter`)
|
||||
const selectOptions = document.querySelectorAll(`#${id}-selectFilter option`)
|
||||
const buttonClearFilters = document.querySelector(`#${id}-filtersClear`)
|
||||
|
||||
selectFilters.forEach((el) => {
|
||||
el.onchange = (e) => {
|
||||
if(e.target.value) {
|
||||
selectOptions[0].removeAttribute('selected')
|
||||
console.log('e', e)
|
||||
console.log('selectFilters[0]', selectFilters[0])
|
||||
console.log('selectOptions[0][0]', selectOptions[0])
|
||||
selectedFilter(e.target.value)
|
||||
} else {
|
||||
clearFilters()
|
||||
e.target.setAttribute('selected', 'selected')
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
buttonClearFilters.onclick = () => {
|
||||
clearFilters()
|
||||
selectOptions[0].setAttribute('selected', 'selected')
|
||||
console.log('selectFilters[0]', selectFilters[0])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const validation = (value, pagination) => {
|
||||
const pipe = (...fns) => (...x) => fns.reduce((error, validator) => error || validator(...x), undefined)
|
||||
|
||||
const minPage = length => value => {
|
||||
value = value.replace(/\s+/g, '')
|
||||
return value >= length ? undefined : `Номер страницы должен быть больше или равен ${length}`
|
||||
}
|
||||
const maxPage = length => value => {
|
||||
value = value.replace(/\s+/g, '')
|
||||
return length >= value ? undefined : `Номер страницы должен быть меньше или равен ${length}`
|
||||
}
|
||||
const onlyDigits = value => {
|
||||
const pattern = /^([\d])*$/g
|
||||
return pattern.test(value) ? undefined : `Номер страницы должен содержать только цифры`
|
||||
}
|
||||
const composedValidators = pipe(onlyDigits, minPage(1), maxPage(pagination.total_pages))
|
||||
return composedValidators(value)
|
||||
}
|
||||
|
||||
const pipe = (x) => (...fns) => fns.reduce((r, fn) => fn(r), x)
|
||||
|
||||
export {prepColumns, sorter, renderGroupHeader, groupByFunction, makedFiltering, tablefy, validation, pipe};
|
||||
@@ -0,0 +1,29 @@
|
||||
const localization = () => ({
|
||||
ru: {
|
||||
groups: {
|
||||
item: "запись",
|
||||
items: "записей",
|
||||
},
|
||||
"pagination":{
|
||||
"page_size":"Размер таблицы", //label for the page size select element
|
||||
"page_title":"Показать страницу",//tooltip text for the numeric page button, appears in front of the page number (eg. "Show Page" will result in a tool tip of "Show Page 1" on the page 1 button)
|
||||
"first":"К первой", //text for the first page button
|
||||
"first_title":"Показать страницу", //tooltip text for the first page button
|
||||
"last":"К последней",
|
||||
"last_title":"Последняя страница",
|
||||
"prev":"<<",
|
||||
"prev_title":"<<",
|
||||
"next":">>",
|
||||
"next_title":">>",
|
||||
"all":"Все",
|
||||
"counter":{
|
||||
"showing": "Показать",
|
||||
"of": "из",
|
||||
"rows": "строки",
|
||||
"pages": "страницы",
|
||||
}
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
export default localization;
|
||||
@@ -0,0 +1,197 @@
|
||||
import { cond, T } from "ramda";
|
||||
|
||||
const get_f1_preset = (el) => {
|
||||
const r = {
|
||||
0: "Конфигурация не установлена (по умолчанию)",
|
||||
};
|
||||
return r[el] || el;
|
||||
};
|
||||
const sadko_2_preset = (el) => {
|
||||
const r = {
|
||||
0: "Конфигурация, не установлена (по умолчанию)",
|
||||
1: "Стандартная, конфигурация МРТ (МПТ/АВФ)",
|
||||
2: "Стандартная конфигурация ЖДСМ",
|
||||
3: "Стандартная конфигурация УК/ВПО",
|
||||
4: "ЩОМ/РМ (Акселерометр)",
|
||||
5: "СМ (Сибирский ПГУПС)",
|
||||
6: "СМ (зарезервировано)",
|
||||
7: "Эмуляция платы БКП",
|
||||
8: "Конфигурация Блок-М",
|
||||
40: "Эмуляторы",
|
||||
41: "Эмуляторы (CAN1 + CAN2 (Блок-М) + ModBus",
|
||||
50: "Стандартная конфигурация ModBus Master (опрос внешних устройств)",
|
||||
51: "Стандартная конфигурация ModBus Master (опрос плат АС КРСПС)",
|
||||
52: "Конфигурация трекера (электрички и т.п.)",
|
||||
53: "Конфигурация трекер + счетчик импульсов + акселерометр",
|
||||
54: "РадарП",
|
||||
55: "СДРГО",
|
||||
100 : "ПМА-1М РПМ",
|
||||
101 : "ЩОМ-1400 РПМ",
|
||||
102 : "АМ-140 СКРТ РПМ",
|
||||
103 : "АС-01 РПМ",
|
||||
104 : "ТЭС ПСС-1К РПМ",
|
||||
105 : "ПСС-2П РПМ",
|
||||
200 : "РПБ-01 ВНИКТИ",
|
||||
201 : "МПК Спецлоко",
|
||||
202 : "УК 25/25 ВНИКТИ",
|
||||
203 : "СЗ-800 ВНИКТИ",
|
||||
300 : "ЩОМ-1200С ТЖДМ",
|
||||
301 : "ЩОМ-2000 ТЖДМ",
|
||||
302 : "ССГС-1 ТЖДМ",
|
||||
303 : "ВПО-С ТЖДМ",
|
||||
304 : "МПВ ТЖДМ",
|
||||
305 : "УПК ТЖДМ",
|
||||
306 : "ПРЛ-М ТЖДМ",
|
||||
307 : "РУ-700 ТЖДМ",
|
||||
308 : "МПВ Секция 2 ТЖДМ",
|
||||
1911: "Конфигурация для отладки",
|
||||
4096: "Настраиваемая конфигурация",
|
||||
};
|
||||
return r[el] || el;
|
||||
};
|
||||
const sadko_4_preset = (el) => {
|
||||
const r = {
|
||||
0: "Конфигурация не установлена (по умолчанию)",
|
||||
1: "Стандартная конфигурация МРТ (МПТ/АВФ)",
|
||||
2: "Стандартная конфигурация ЖДСМ",
|
||||
3: "Стандартная конфигурация УК/ВПО",
|
||||
4: "ЩОМ/РМ (Акселерометр)",
|
||||
5: "СМ (Сибирский ПГУПС)",
|
||||
6: "СМ (зарезервировано)",
|
||||
7: "Эмуляция платы БКП",
|
||||
8: "Конфигурация Блок-М",
|
||||
40: "Эмуляторы (CAN1 + ОНК-160 + ModBus)",
|
||||
41: "Эмуляторы (CAN1 + CAN2 (Блок-М) + ModBus)",
|
||||
50: "Стандартная конфигурация ModBus Master (опрос внешних устройств)",
|
||||
51: "Стандартная конфигурация ModBus Master (опрос плат АС КРСПС)",
|
||||
52: "Конфигурация трекера (электрички и т.п.)",
|
||||
53: "Конфигурация трекер + счетчик импульсов + акселерометр",
|
||||
54: "РадарП",
|
||||
55: "СДРГО",
|
||||
100: "ПМА-1М РПМ",
|
||||
101: "ЩОМ-1400 РПМ",
|
||||
102: "АМ-140 СКРТ РПМ",
|
||||
103: "АС-01 РПМ",
|
||||
104: "ТЭС ПСС-1К РПМ",
|
||||
105: "ПСС-2П РПМ",
|
||||
200: "РПБ-01 ВНИКТИ",
|
||||
201: "МПК Спецлоко",
|
||||
202: "УК 25/25 ВНИКТИ",
|
||||
203: "СЗ-800 ВНИКТИ",
|
||||
300: "ЩОМ-1200С ТЖДМ",
|
||||
301: "ЩОМ-2000 ТЖДМ",
|
||||
302: "ССГС-1 ТЖДМ",
|
||||
303: "ВПО-С ТЖДМ",
|
||||
304: "МПВ ТЖДМ",
|
||||
305: "УПК ТЖДМ",
|
||||
306: "ПРЛ-М ТЖДМ",
|
||||
307: "РУ-700 ТЖДМ",
|
||||
308: "МПВ Секция 2 ТЖДМ",
|
||||
1911: "Конфигурация для отладки",
|
||||
4096: "Настраиваемая конфигурация",
|
||||
};
|
||||
return r[el] || el;
|
||||
};
|
||||
const sadko_skrt_preset = (el) => {
|
||||
const r = {
|
||||
0: "Конфигурация не установлена (по умолчанию)",
|
||||
1: "Обработка импульсной СКРТ",
|
||||
2: "Конфигурация ModBus Slave Base",
|
||||
4096: "Настраиваемая конфигурация",
|
||||
};
|
||||
return r[el] || el;
|
||||
};
|
||||
const uk_preset = (el) => {
|
||||
const r = {
|
||||
0: "Конфигурация не установлена (по умолчанию)",
|
||||
1: "УК AC с подключением по SPI",
|
||||
2: "УК AC с подключением по LORA",
|
||||
3: "УК DC с подключением по SPI",
|
||||
4: "УК DC с подключением по LORA",
|
||||
5: "ВПО AC/DC с подключением по SPI",
|
||||
6: "ВПО AC/DC с подключением по LORA",
|
||||
4096: "Настраиваемая конфигурация",
|
||||
};
|
||||
return r[el] || el;
|
||||
};
|
||||
const sadko_64_preset = (el) => {
|
||||
const r = {
|
||||
0: "Конфигурация не установлена (по умолчанию)",
|
||||
4096: "Настраиваемая конфигурация",
|
||||
};
|
||||
return r[el] || el;
|
||||
};
|
||||
const sadko_dig_preset = (el) => {
|
||||
const r = {
|
||||
0: "Конфигурация не установлена (по умолчанию)",
|
||||
1: "Конфигурация с эмуляторами",
|
||||
4096: "Настраиваемая конфигурация",
|
||||
};
|
||||
return r[el] || el;
|
||||
};
|
||||
const sadko_69_preset = (el) => {
|
||||
const r = {
|
||||
0: "Конфигурация не установлена (по умолчанию)",
|
||||
4096: "Настраиваемая конфигурация",
|
||||
};
|
||||
return r[el] || el;
|
||||
};
|
||||
const sadko_80_preset = (el) => {
|
||||
const r = {
|
||||
0: "Плата с экраном для СМ",
|
||||
1: "Плата с экраном для ПСС",
|
||||
4096: "Настраиваемая конфигурация",
|
||||
};
|
||||
return r[el] || el;
|
||||
};
|
||||
const sadko_81_preset = (el) => {
|
||||
const r = {
|
||||
0: "Плата с ультразвуковыми датчиками (СМ и т.п.)",
|
||||
4096: "Плата с ультразвуковыми датчиками (СМ и т.п.) настраиваемая конфигурация",
|
||||
};
|
||||
return r[el] || el;
|
||||
};
|
||||
const sadko_82_preset = (el) => {
|
||||
const r = {
|
||||
0: "Плата с дискретными входами + LORA (СМ Tail)",
|
||||
4096: "Плата с дискретными входами + LORA (СМ Tail) настраиваемая конфигурация",
|
||||
};
|
||||
return r[el] || el;
|
||||
};
|
||||
|
||||
const sadko_83_preset = (el) => {
|
||||
const r = {
|
||||
0: "Конфигурация не установлена (по умолчанию)",
|
||||
1: "Конфигурация для МПТ",
|
||||
2: "Конфигурация для ТЭС ПСС-1К",
|
||||
3: "Конфигурация для СМ (СМ-2Б)",
|
||||
4: "Конфигурация для Динамик",
|
||||
5: "Конфигурация для УТМ-5",
|
||||
4096: "Настраиваемая конфигурация",
|
||||
};
|
||||
return r[el] || el;
|
||||
};
|
||||
|
||||
const presetConfig = (el, record) => {
|
||||
const { board_id: board_id } = record;
|
||||
board_id == 64 && console.log(record)
|
||||
|
||||
|
||||
return cond([
|
||||
[(board) => board == 1, () => get_f1_preset(el)],
|
||||
[(board) => board == 2, () => sadko_2_preset(el)],
|
||||
[(board) => board == 4, () => sadko_4_preset(el)],
|
||||
[(board) => board == 16, () => sadko_skrt_preset(el)],
|
||||
[(board) => board == 19, () => uk_preset(el)],
|
||||
[(board) => board == 64, () => sadko_64_preset(el)],
|
||||
[(board) => board == 68, () => sadko_dig_preset(el)],
|
||||
[(board) => board == 69, () => sadko_69_preset(el)],
|
||||
[(board) => board == 80, () => sadko_80_preset(el)],
|
||||
[(board) => board == 81, () => sadko_81_preset(el)],
|
||||
[(board) => board == 82, () => sadko_82_preset(el)],
|
||||
[(board) => board == 83, () => sadko_83_preset(el)],
|
||||
[T, () => el],
|
||||
])(board_id);
|
||||
};
|
||||
|
||||
export default presetConfig
|
||||
168
users-manage/src/components/2_molecules/Tabulator/time_funcs.js
Normal file
168
users-manage/src/components/2_molecules/Tabulator/time_funcs.js
Normal file
@@ -0,0 +1,168 @@
|
||||
import { inc, type, cond, equals, T, pipe } from "ramda";
|
||||
|
||||
export const isDate = (date) =>
|
||||
type(date) === "Date" || new Date(date).getTime() > 0;
|
||||
export const formateDateToString = (date) =>
|
||||
isDate(date) && date.toLocaleString();
|
||||
|
||||
const monthNames = [
|
||||
"Январь",
|
||||
"Февраль",
|
||||
"Март",
|
||||
"Апрель",
|
||||
"Май",
|
||||
"Июнь",
|
||||
"Июль",
|
||||
"Август",
|
||||
"Сентябрь",
|
||||
"Октябрь",
|
||||
"Ноябрь",
|
||||
"Декабрь",
|
||||
];
|
||||
|
||||
export const reformateDateString = (date, format = "full") =>
|
||||
date ? reformate(date, format) : null;
|
||||
|
||||
const reformate = (date, format) =>
|
||||
cond([
|
||||
[
|
||||
equals("time-only"),
|
||||
() => {
|
||||
const { hr, min, sec } = getTime(date);
|
||||
return `${hr}:${min}:${sec}`;
|
||||
},
|
||||
],
|
||||
[
|
||||
equals("date-only"),
|
||||
() => {
|
||||
const { day, mth, year } = getDate(date);
|
||||
return `${day}/${mth}/${year}`;
|
||||
},
|
||||
],
|
||||
[
|
||||
equals("date-report"),
|
||||
() => {
|
||||
const { day, mth, year } = getDate(date);
|
||||
return `${year}-${mth}-${day}`;
|
||||
},
|
||||
],
|
||||
[
|
||||
equals("mth-date-report"),
|
||||
() => {
|
||||
date = new Date(date);
|
||||
return `${getMthName(date)} ${date.getDate()}`;
|
||||
},
|
||||
],
|
||||
[
|
||||
T,
|
||||
() => {
|
||||
const { hr, min, sec } = getTime(date);
|
||||
const { day, mth, year } = getDate(date);
|
||||
return `${day}/${mth}/${year} ${hr}:${min}:${sec}`;
|
||||
},
|
||||
],
|
||||
])(format);
|
||||
|
||||
export const addHours = (oldDate, h) => {
|
||||
const date = new Date(oldDate);
|
||||
date.setHours(date.getHours() + h);
|
||||
return date;
|
||||
};
|
||||
|
||||
export const lastDay = addHours(new Date(), -24);
|
||||
|
||||
const getTime = (date) => {
|
||||
date = new Date(date);
|
||||
return {
|
||||
hr: formatTime(date.getHours()),
|
||||
minutes: formatTime(date.getMinutes()),
|
||||
sec: formatTime(date.getSeconds()),
|
||||
};
|
||||
};
|
||||
|
||||
const getDate = (date) => {
|
||||
date = new Date(date);
|
||||
return {
|
||||
mth: formatDate(date.getMonth()),
|
||||
day: date.getDate(),
|
||||
year: date.getFullYear(),
|
||||
};
|
||||
};
|
||||
|
||||
export const getMthName = (date) => monthNames[date.getMonth()];
|
||||
export const formatDate = (t) => (inc(t) >= 10 ? inc(t) : `0${inc(t)}`);
|
||||
export const formatTime = (t) => `0${t}`.slice(-2);
|
||||
|
||||
export const getDateByType = (date) => {
|
||||
if (type(date) === "String") return new Date(date.replace(/\s/, "T"));
|
||||
return new Date(date);
|
||||
};
|
||||
|
||||
export const parseSepareteDate = (
|
||||
date = new Date(),
|
||||
symbol = ".",
|
||||
mode = "DateDMY"
|
||||
) => {
|
||||
const selfDate = getDateByType(date);
|
||||
const dateBits = {
|
||||
year: selfDate.getFullYear(),
|
||||
month: selfDate.getMonth() + 1,
|
||||
day: selfDate.getDate(),
|
||||
hours: selfDate.getHours(),
|
||||
minutes: selfDate.getMinutes(),
|
||||
seconds: selfDate.getSeconds(),
|
||||
};
|
||||
Object.keys(dateBits).forEach((key) => {
|
||||
if (dateBits[key] < 10) dateBits[key] = `0${dateBits[key]}`;
|
||||
});
|
||||
switch (mode) {
|
||||
case "DateDMY":
|
||||
return dateBits.day + symbol + dateBits.month + symbol + dateBits.year;
|
||||
case "DateDM":
|
||||
return dateBits.day + symbol + dateBits.month;
|
||||
case "DateYMD":
|
||||
return dateBits.year + symbol + dateBits.month + symbol + dateBits.day;
|
||||
case "DateDMYHM":
|
||||
return `${
|
||||
dateBits.day + symbol + dateBits.month + symbol + dateBits.year
|
||||
} ${dateBits.hours}:${dateBits.minutes}`;
|
||||
case "DateDMYHMS":
|
||||
return `${
|
||||
dateBits.day + symbol + dateBits.month + symbol + dateBits.year
|
||||
} ${dateBits.hours}:${dateBits.minutes}:${dateBits.seconds}`;
|
||||
case "DateYMDHM":
|
||||
return `${
|
||||
dateBits.year + symbol + dateBits.month + symbol + dateBits.day
|
||||
} ${dateBits.hours}:${dateBits.minutes}`;
|
||||
case "DateYMDHMS":
|
||||
return `${
|
||||
dateBits.year + symbol + dateBits.month + symbol + dateBits.day
|
||||
} ${dateBits.hours}:${dateBits.minutes}:${dateBits.seconds}`;
|
||||
case "DateHM":
|
||||
return `${dateBits.hours}:${dateBits.minutes}`;
|
||||
case "DateHMDM":
|
||||
return `${dateBits.hours}:${dateBits.minutes} ${dateBits.day}${symbol}${dateBits.month}`;
|
||||
case "DateDataBase":
|
||||
return `${
|
||||
dateBits.year + symbol + dateBits.month + symbol + dateBits.day
|
||||
}T${dateBits.hours}:${dateBits.minutes}:${dateBits.seconds}`;
|
||||
case "DateDataBaseSpace":
|
||||
return `${
|
||||
dateBits.year + symbol + dateBits.month + symbol + dateBits.day
|
||||
} ${dateBits.hours}:${dateBits.minutes}:${dateBits.seconds}`;
|
||||
default:
|
||||
return dateBits.day + symbol + dateBits.month + symbol + dateBits.year;
|
||||
}
|
||||
};
|
||||
|
||||
export const toStartDay = (date = new Date()) => {
|
||||
const selfDate = getDateByType(date);
|
||||
|
||||
return selfDate.setHours(0, 0, 0);
|
||||
};
|
||||
|
||||
export const withTime = (date, time) =>
|
||||
pipe(
|
||||
(x) => parseSepareteDate(x, "-", "DateYMD"),
|
||||
(x) => `${x}T${time}`
|
||||
)(date);
|
||||
@@ -0,0 +1,68 @@
|
||||
<script>
|
||||
import {mapActions, mapGetters, useStore} from 'vuex'
|
||||
import { FwbSpinner } from 'flowbite-vue'
|
||||
import SiteCard from "./SiteListSiteCard.vue"
|
||||
import NewSiteButton from "@atoms/NewSiteButton.vue"
|
||||
|
||||
export default {
|
||||
name: 'SiteList',
|
||||
components: {FwbSpinner, SiteCard, NewSiteButton},
|
||||
props: {
|
||||
selectSite: {
|
||||
type: Function,
|
||||
default: () => {}
|
||||
}
|
||||
},
|
||||
setup () {
|
||||
const store = useStore()
|
||||
store.dispatch('services/uploadSites')
|
||||
},
|
||||
computed: {
|
||||
...mapGetters('services', ["sites", "routes", "sitesState", "newSite", 'isDeleteData']),
|
||||
maxHeight () {
|
||||
return document.documentElement.clientHeight - 400
|
||||
},
|
||||
|
||||
},
|
||||
mounted () {
|
||||
},
|
||||
methods: {
|
||||
...mapActions('services', ["uploadSites", "uploadSiteRoutes"])
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div
|
||||
v-if="sitesState === 'active'"
|
||||
:class="`grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 2xl:grid-cols-5 3xl:grid-cols-6 gap-5 overflow-y-auto mb-14 pr-1`"
|
||||
:style="{maxHeight: `${maxHeight}px`}"
|
||||
>
|
||||
<NewSiteButton />
|
||||
<SiteCard
|
||||
v-for="site in sites"
|
||||
:id="site.id"
|
||||
:key="site.name"
|
||||
:site="site"
|
||||
:name="site.name"
|
||||
:port="`${site.port}`"
|
||||
:device_ip="site.device_ip"
|
||||
:proxy_ip="site.proxy_ip"
|
||||
:status="site.status"
|
||||
:description="site.description"
|
||||
:selectSite="selectSite"
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
v-if="sitesState === 'loading'"
|
||||
class="flex w-full justify-center"
|
||||
>
|
||||
<fwb-spinner
|
||||
size="8"
|
||||
class="mr-2"
|
||||
/>
|
||||
<span class="text-2xl text-slate-700">
|
||||
Загрузка сайтов...
|
||||
</span>
|
||||
</div>
|
||||
</template>
|
||||
@@ -0,0 +1,108 @@
|
||||
<!-- eslint-disable vue/prop-name-casing -->
|
||||
<script>
|
||||
import {mapActions, mapGetters} from 'vuex'
|
||||
import Input from '@atoms/VInput.vue'
|
||||
import Textarea from '@atoms/VTextarea.vue'
|
||||
import VDoubleSwitch from '@atoms/VDoubleSwitch.vue'
|
||||
|
||||
export default {
|
||||
name: 'EditCard',
|
||||
components: {Input, Textarea, VDoubleSwitch},
|
||||
props: {
|
||||
id: {
|
||||
default: -1,
|
||||
type: Number
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
...mapGetters('services', ["selectedSite"]),
|
||||
},
|
||||
mounted() {
|
||||
},
|
||||
methods: {
|
||||
...mapActions('services', ["editSelectedSite", "breakSavingSite", "breakeAddingSite"]),
|
||||
editData (params) {
|
||||
this.editSelectedSite(params)
|
||||
},
|
||||
breakSavingSiteDate() {
|
||||
if (this.id == -1) {
|
||||
this.breakeAddingSite()
|
||||
} else {
|
||||
this.editable_name = this.name
|
||||
this.editable_port = this.port
|
||||
this.breakSavingSite()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="flex justify-between items-center mb-2">
|
||||
<div class="w-fit mr-2">
|
||||
<VDoubleSwitch
|
||||
name="isCbOn"
|
||||
firstColor="#8b5cf6"
|
||||
secondColor="#2563eb"
|
||||
firstTitle="Офлайн"
|
||||
secondTitle="Онлайн"
|
||||
:isCheck="selectedSite.is_online"
|
||||
position="col"
|
||||
labelClass="items-start pb-2"
|
||||
switchClass="mt-1"
|
||||
@switched="(e) => editData({key: 'is_online', value: e})"
|
||||
/>
|
||||
</div>
|
||||
<Input
|
||||
name="name"
|
||||
:value="selectedSite.name"
|
||||
inputClass="!w-[90%] py-2"
|
||||
placeholder="Указать путь"
|
||||
:onChange="editData"
|
||||
/>
|
||||
<div>
|
||||
<i
|
||||
v-tippy="{ content: 'закрыть сервис' }"
|
||||
class="ri-close-line cursor-pointer"
|
||||
@click="breakSavingSiteDate"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex items-center mb-2 w-full">
|
||||
<span class="mr-2 min-w-[80px]">Порт:</span>
|
||||
<Input
|
||||
name="port"
|
||||
:value="`${selectedSite.port}`"
|
||||
inputClass="py-2"
|
||||
placeholder="Порт"
|
||||
:onChange="editData"
|
||||
/>
|
||||
</div>
|
||||
<div class="flex items-center mb-2 w-full">
|
||||
<span class="mr-2 min-w-[80px]">IP proxy:</span>
|
||||
<Input
|
||||
name="proxy_ip"
|
||||
:value="selectedSite.proxy_ip"
|
||||
inputClass="py-2"
|
||||
placeholder="IP proxy"
|
||||
:onChange="editData"
|
||||
/>
|
||||
</div>
|
||||
<div class="flex items-center w-full mb-2">
|
||||
<span class="mr-2 min-w-[80px]">IP устр-ва:</span>
|
||||
<Input
|
||||
name="device_ip"
|
||||
:value="selectedSite.device_ip"
|
||||
inputClass="py-2"
|
||||
placeholder="IP устройства"
|
||||
:onChange="editData"
|
||||
/>
|
||||
</div>
|
||||
<Textarea
|
||||
name="description"
|
||||
:value="selectedSite.description"
|
||||
textareaClass="py-2"
|
||||
placeholder="Описание..."
|
||||
:onChange="editData"
|
||||
/>
|
||||
</template>
|
||||
@@ -0,0 +1,169 @@
|
||||
<!-- eslint-disable vue/prop-name-casing -->
|
||||
<script>
|
||||
import {mapActions, mapGetters} from 'vuex'
|
||||
import EditCard from './SiteListEditCard.vue'
|
||||
|
||||
export default {
|
||||
name: 'SiteCard',
|
||||
components: {EditCard},
|
||||
props: {
|
||||
id: {
|
||||
default: -1,
|
||||
type: Number
|
||||
},
|
||||
name: {
|
||||
default: "",
|
||||
type: String
|
||||
},
|
||||
port: {
|
||||
default: "",
|
||||
type: String
|
||||
},
|
||||
device_ip: {
|
||||
default: "",
|
||||
type: String
|
||||
},
|
||||
proxy_ip: {
|
||||
default: "",
|
||||
type: String
|
||||
},
|
||||
status: {
|
||||
default: "disable",
|
||||
type: String
|
||||
},
|
||||
description: {
|
||||
default: "disable",
|
||||
type: String
|
||||
},
|
||||
site: {
|
||||
default: () => ({}),
|
||||
type: Object
|
||||
},
|
||||
selectSite: {
|
||||
type: Function,
|
||||
default: () => {}
|
||||
}
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
isDelete: false,
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
...mapGetters('services', ["selectedSite", "routes", 'isSaveData']),
|
||||
__btnClass__() {
|
||||
if (this.forcedBtnClass) return this.forcedBtnClass
|
||||
return `${this.btnClass} cursor-pointer w-full bg-transparent hover:bg-primary text-primary font-semibold hover:text-white py-1 text-ssm px-4 border border-primary hover:border-transparent rounded-lg text-center transition_up`
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
isSaveData: function (newVal) {
|
||||
console.log('newVal', newVal)
|
||||
if (newVal) {
|
||||
this.saveSiteData()
|
||||
}
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
},
|
||||
methods: {
|
||||
...mapActions('services', ["saveSite", "createNewSite", 'removeSite', 'updateRoutesWithApi', 'closeRoutesList']),
|
||||
...mapActions('users', ["fetchUsersList"]),
|
||||
toggle () {
|
||||
this.open = !this.open
|
||||
this.onToggle({isOpen: this.open})
|
||||
},
|
||||
saveSiteData () {
|
||||
if (this.selectedSite.id == -1) {
|
||||
const data = {
|
||||
name: this.selectedSite.name,
|
||||
port: this.selectedSite.port,
|
||||
device_ip: this.selectedSite.device_ip,
|
||||
proxy_ip: this.selectedSite.proxy_ip,
|
||||
description: this.selectedSite.description
|
||||
}
|
||||
this.createNewSite(data)
|
||||
} else {
|
||||
this.updateRoutesWithApi(this.selectedSite)
|
||||
this.saveSite(this.selectedSite)
|
||||
}
|
||||
},
|
||||
deleteSite (v) {
|
||||
this.isDelete = v
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div
|
||||
v-if="!selectedSite || selectedSite.id !== id"
|
||||
class="block w-full p-6 bg-white border border-gray-200 rounded-lg shadow dark:bg-gray-800 dark:border-gray-700 dark:hover:bg-gray-700"
|
||||
>
|
||||
<div class="flex justify-between items-center mb-1">
|
||||
<h5
|
||||
v-tippy="{ content: name }"
|
||||
class="max-w-[200px] text-xl font-bold tracking-tight text-slate-600 dark:text-white truncate"
|
||||
>
|
||||
{{ name }}
|
||||
</h5>
|
||||
<div class="relative">
|
||||
<i
|
||||
v-tippy="{ content: 'редактировать сервис' }"
|
||||
class="ri-pencil-line cursor-pointer"
|
||||
@click="selectSite(site.id, 'dev')"
|
||||
/>
|
||||
<i
|
||||
v-tippy="{ content: 'удалить сервис' }"
|
||||
class="ri-close-line cursor-pointer ml-2"
|
||||
@click="deleteSite(true)"
|
||||
/>
|
||||
<div
|
||||
v-if="isDelete"
|
||||
class="absolute flex flex-row top-0 right-0 p-3 bg-white rounded-lg shadow-lg z-10"
|
||||
>
|
||||
<button
|
||||
class="flex items-center text-xs text-white bg-blue-700 p-1 mr-2 rounded-lg shadow"
|
||||
@click="deleteSite(false)"
|
||||
>
|
||||
Отменить
|
||||
</button>
|
||||
<button
|
||||
class="flex items-center p-1 text-xs text-white bg-slate-700 rounded-lg shadow"
|
||||
@click="removeSite(id)"
|
||||
>
|
||||
Удалить
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="w-full h-[1px] bg-slate-200 mb-4" />
|
||||
<div class="font-normal text-sm text-gray-700 dark:text-gray-400 mb-4 flex -translate-x-2">
|
||||
<span class="flex border-slate-300 mr-4 bg-slate-100 rounded-full py-1 px-2 items-center w-full max-w-[50%]">
|
||||
<span class="inline-block flex mr-2 font-w-700"> статус:</span> <span class="mr-2">{{ status }}</span> <div class="min-h-2 min-w-2 bg-green-700 rounded-full" />
|
||||
</span>
|
||||
<span class="flex border-slate-300 bg-slate-100 rounded-full py-1 px-2 grow">
|
||||
<span class="inline-block flex mr-2 font-w-700"> Порт:</span> <span>{{ port }}</span>
|
||||
</span>
|
||||
</div>
|
||||
<p class=" font-normal text-sm text-gray-700 dark:text-gray-400 mb-2 flex">
|
||||
<span class="min-w-[80px] font-w-700 inline-block flex"> IP proxy:</span> {{ proxy_ip }}
|
||||
</p>
|
||||
<p class=" font-normal text-sm text-gray-700 dark:text-gray-400 mb-2 flex">
|
||||
<span class="min-w-[80px] font-w-700 inline-block flex"> IP устр-ва:</span> {{ device_ip }}
|
||||
</p>
|
||||
<p class=" font-normal text-sm text-gray-700 dark:text-gray-400 mb-2 flex ">
|
||||
<span class="min-w-[80px] font-w-700 inline-block flex"> Описание:</span> <span
|
||||
v-tippy="{ content: description }"
|
||||
class="truncate"
|
||||
>{{ description }}</span>
|
||||
</p>
|
||||
</div>
|
||||
<div
|
||||
v-if="selectedSite && selectedSite.id === id"
|
||||
href="#"
|
||||
class="block w-full p-6 bg-white border-4 border-blue-200 rounded-lg shadow dark:bg-gray-800 dark:border-gray-700 dark:hover:bg-gray-700"
|
||||
>
|
||||
<EditCard :id="id" />
|
||||
</div>
|
||||
</template>
|
||||
@@ -0,0 +1,101 @@
|
||||
<script>
|
||||
import ModuleContainer from "@organisms/UsersManager/UsersManagerContainer.vue";
|
||||
import UsersTable from "@organisms/UsersManager/UsersManagerUsersTable.vue";
|
||||
import UsersEditor from "@organisms/UsersManager/UsersManagerUserEditor.vue";
|
||||
import ManagerTitle from "@organisms/UsersManager/UsersManagerTitle.vue";
|
||||
import VButton from "@atoms/VButton.vue";
|
||||
import {mapGetters} from 'vuex'
|
||||
|
||||
export default {
|
||||
name: 'UsersManager',
|
||||
components: {ModuleContainer, UsersTable, UsersEditor, ManagerTitle, VButton},
|
||||
data() {
|
||||
return {
|
||||
componentState: 'view' // view | create | select
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
...mapGetters('users', ["usersList"]),
|
||||
gridCols() {
|
||||
return this.componentState === 'view' ? 'grid-cols-1' : 'grid-cols-2'
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
openUserPanelOfCreate() {
|
||||
this.componentState = 'create'
|
||||
},
|
||||
openUserPanelOfSelect() {
|
||||
this.componentState = 'select'
|
||||
},
|
||||
closeUserPanel() {
|
||||
this.componentState = 'view'
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div
|
||||
class="grid gap-4"
|
||||
:class="`${gridCols}`"
|
||||
>
|
||||
<ModuleContainer>
|
||||
<ManagerTitle>
|
||||
<template #title>
|
||||
<span>Список пользователей</span>
|
||||
</template>
|
||||
<template #control>
|
||||
<div class="flex items-center gap-2">
|
||||
<VButton
|
||||
color="blue"
|
||||
@click="openUserPanelOfCreate"
|
||||
>
|
||||
Добавить пользователя
|
||||
</VButton>
|
||||
<VButton
|
||||
color="purple"
|
||||
@click="openUserPanelOfSelect"
|
||||
>
|
||||
Добавить существующего
|
||||
</VButton>
|
||||
</div>
|
||||
</template>
|
||||
</ManagerTitle>
|
||||
<UsersTable :usersList="usersList" />
|
||||
</ModuleContainer>
|
||||
<ModuleContainer v-if="componentState === 'create'">
|
||||
<ManagerTitle>
|
||||
<template #title>
|
||||
<span>Создать пользователя</span>
|
||||
</template>
|
||||
<template #control>
|
||||
<VButton
|
||||
class="close-button"
|
||||
color="gray"
|
||||
@click="closeUserPanel"
|
||||
>
|
||||
Закрыть
|
||||
</VButton>
|
||||
</template>
|
||||
</ManagerTitle>
|
||||
<UsersEditor />
|
||||
</ModuleContainer>
|
||||
<ModuleContainer v-if="componentState === 'select'">
|
||||
<ManagerTitle>
|
||||
<template #title>
|
||||
<span>Выбрать пользователя</span>
|
||||
</template>
|
||||
<template #control>
|
||||
<VButton
|
||||
class="close-button"
|
||||
color="gray"
|
||||
@click="closeUserPanel"
|
||||
>
|
||||
Закрыть
|
||||
</VButton>
|
||||
</template>
|
||||
</ManagerTitle>
|
||||
<UsersEditor />
|
||||
</ModuleContainer>
|
||||
</div>
|
||||
</template>
|
||||
@@ -0,0 +1,15 @@
|
||||
<script>
|
||||
|
||||
export default {
|
||||
name: 'UsersManagerSiteInfo',
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div
|
||||
class="p-4 shadow-md rounded-md border border-slate-200"
|
||||
>
|
||||
<slot />
|
||||
</div>
|
||||
</template>
|
||||
@@ -0,0 +1,15 @@
|
||||
<script>
|
||||
|
||||
export default {
|
||||
name: 'UsersManagerTitle',
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="flex items-center">
|
||||
<h2 class="text-xl font-w-500 mr-6">
|
||||
<slot name="title" />
|
||||
</h2>
|
||||
<slot name="control" />
|
||||
</div>
|
||||
</template>
|
||||
@@ -0,0 +1,17 @@
|
||||
<script>
|
||||
|
||||
export default {
|
||||
name: 'UsersManagerUserEditor',
|
||||
components: {},
|
||||
data() {
|
||||
return {
|
||||
}
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
UsersManagerUserEditor
|
||||
</div>
|
||||
</template>
|
||||
@@ -0,0 +1,55 @@
|
||||
<script>
|
||||
// import VTabulator from '@molecules/Tabulator/VTabulator.vue';
|
||||
|
||||
export default {
|
||||
name: 'UsersManagerUsersTable',
|
||||
components: {},
|
||||
data() {
|
||||
return {
|
||||
columns: [
|
||||
{
|
||||
field: "id",
|
||||
title: "ID",
|
||||
width: 100,
|
||||
sorter: "number",
|
||||
},
|
||||
{
|
||||
field: "name",
|
||||
title: "Name",
|
||||
width: 200,
|
||||
sorter: "string",
|
||||
},
|
||||
{
|
||||
field: "age",
|
||||
title: "Age",
|
||||
width: 100,
|
||||
sorter: "number",
|
||||
},
|
||||
],
|
||||
dataSource: [{
|
||||
id: 1,
|
||||
name: "Adnrew",
|
||||
age: 15
|
||||
}, {
|
||||
id: 2,
|
||||
name: "Micheal",
|
||||
age: 22
|
||||
}, {
|
||||
id: 3,
|
||||
name: "Sara",
|
||||
age: 32
|
||||
}]
|
||||
}
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div
|
||||
class="flex flex-col max-h-[70vh] mb-14"
|
||||
>
|
||||
<div>
|
||||
users table
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
@@ -0,0 +1,34 @@
|
||||
<script>
|
||||
import PageHeader from "@atoms/AppPageHeader.vue"
|
||||
import SiteList from "@organisms/SiteList/SiteList.vue"
|
||||
import SiteManager from "@organisms/UsersManager/UsersManager.vue"
|
||||
import {mapActions, mapGetters} from 'vuex'
|
||||
|
||||
export default {
|
||||
name: 'SitesManagerPage',
|
||||
components: {SiteManager, SiteList, PageHeader},
|
||||
computed: {
|
||||
...mapGetters('services', ["sites", "routes", "newRoute", "selectedSiteState"]),
|
||||
},
|
||||
mounted () {
|
||||
this.uploadSites()
|
||||
},
|
||||
methods: {
|
||||
...mapActions('services', ["uploadSites", "uploadAndSelectService"]),
|
||||
...mapActions('users', ["fetchUsersList"]),
|
||||
async selectSite(siteId, mode = 'prod') {
|
||||
await this.fetchUsersList({siteId, mode})
|
||||
this.uploadAndSelectService(siteId)
|
||||
return "ok"
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="p-6">
|
||||
<PageHeader class="me-2 mb-6" />
|
||||
<SiteList :selectSite="selectSite" />
|
||||
<SiteManager v-if="selectedSiteState === 'active'" />
|
||||
</div>
|
||||
</template>
|
||||
23
users-manage/src/components/5_pages/UserManage/index.vue
Normal file
23
users-manage/src/components/5_pages/UserManage/index.vue
Normal file
@@ -0,0 +1,23 @@
|
||||
<script>
|
||||
import {mapActions, mapGetters} from 'vuex'
|
||||
|
||||
export default {
|
||||
name: 'UserPage',
|
||||
components: {},
|
||||
computed: {
|
||||
...mapGetters('services', ["sites", "routes", "newRoute"]),
|
||||
},
|
||||
mounted () {
|
||||
this.uploadSites()
|
||||
},
|
||||
methods: {
|
||||
...mapActions('services', ["uploadSites", "uploadSiteData"])
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="p-6">
|
||||
users-list
|
||||
</div>
|
||||
</template>
|
||||
96
users-manage/src/helpers/Services/Services.js
Normal file
96
users-manage/src/helpers/Services/Services.js
Normal file
@@ -0,0 +1,96 @@
|
||||
import {get, post, put, remove} from './apiHelpers.js'
|
||||
import {convertObject, convertList} from './adapter/adapter.js'
|
||||
|
||||
const servConfig = {
|
||||
id: "id",
|
||||
created_at: "created_at",
|
||||
updated_at: "updated_at",
|
||||
deleted_at: "deleted_at",
|
||||
name: "name",
|
||||
port: "port",
|
||||
proxy_ip: "proxy_ip",
|
||||
internet_uri: "internet_uri",
|
||||
description: "description",
|
||||
is_online: "is_online",
|
||||
device_ip: "site_ip",
|
||||
}
|
||||
|
||||
class Services {
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {String} apiAddr - path to service
|
||||
* @param {Object} config - oldKey: newKey
|
||||
*/
|
||||
|
||||
constructor(apiAddr, config) {
|
||||
this.apiAddr = apiAddr
|
||||
this.config = config
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @returns {Array<Object>} res.data - all services
|
||||
*/
|
||||
|
||||
async getServices() {
|
||||
const res = await get(`${this.apiAddr}/servers`)
|
||||
return convertList(res.data, {config: this.config})
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {Object} payload - add new service
|
||||
* @returns {Object} newService - added new service
|
||||
*/
|
||||
|
||||
async createService(payload) {
|
||||
let newService = []
|
||||
const updatedPort = parseFloat(payload.port)
|
||||
const updatedService = {...convertObject(payload, {config: servConfig}), port: updatedPort}
|
||||
await post(`${this.apiAddr}/servers`, updatedService).then(res => {
|
||||
newService = convertObject(res.value, {config: this.config})
|
||||
}).catch(err => {
|
||||
console.log('err', err)
|
||||
})
|
||||
return newService
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {Object} payload - edit params in selected service
|
||||
* @returns {Object} resService - updated service with edited params
|
||||
*/
|
||||
|
||||
async updateService(payload) {
|
||||
let resService = []
|
||||
const updatedPort = parseFloat(payload.port)
|
||||
const updatedService = {...convertObject(payload, {config: servConfig}), port: updatedPort}
|
||||
if (payload.id) {
|
||||
await put(`${this.apiAddr}/servers`, updatedService, payload.id).then(res => {
|
||||
resService = convertObject(res.value, {config: this.config})
|
||||
}).catch(err => {
|
||||
console.log('err', err)
|
||||
})
|
||||
}
|
||||
return resService
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {Number} id - id selected service for remove
|
||||
* @returns {Number} deletedServiceId - id removed service
|
||||
*/
|
||||
|
||||
async deleteService(id) {
|
||||
let deletedServiceId = null
|
||||
await remove(`${this.apiAddr}/servers`, id).then((res) => {
|
||||
deletedServiceId = res.id
|
||||
}).catch(err => {
|
||||
console.log('err', err)
|
||||
})
|
||||
return deletedServiceId
|
||||
}
|
||||
}
|
||||
|
||||
export default Services
|
||||
42
users-manage/src/helpers/Services/adapter/adapter.js
Normal file
42
users-manage/src/helpers/Services/adapter/adapter.js
Normal file
@@ -0,0 +1,42 @@
|
||||
/**
|
||||
* Каллбек изменнеия, ожидает что вернется измененный объект
|
||||
*
|
||||
* @callback adapterCallback
|
||||
* @param {Object} modifiedObject - новый объект
|
||||
* @param {Object} originalObject - изначальный объект
|
||||
* @returns {Object} измененный коллбэком объект
|
||||
*/
|
||||
|
||||
/**
|
||||
*
|
||||
* Если у newKey значение отстутсвует - null, '', false, 0, то oldKey удаляется из объекта
|
||||
*
|
||||
* @param {Object} targetObject
|
||||
* @param {Object} params
|
||||
* @param {Object} params.config - oldKey: null // delete oldKey
|
||||
* @param {adapterCallback | undefined} params.callback
|
||||
* @returns {Object}
|
||||
*/
|
||||
const convertObject = (targetObject, {config, callback = (v) => v}) => {
|
||||
let newObject = {}
|
||||
for (const key in config) {
|
||||
newObject[config[key]] = targetObject[key]
|
||||
}
|
||||
return callback(newObject, targetObject)
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* Если у newKey значение отстутсвует - null, '', false, 0, то oldKey удаляется из объекта
|
||||
*
|
||||
* @param {Array} targetList
|
||||
* @param {Object} options - oldKey: null // delete oldKey
|
||||
* @param {Object} options.config - oldKey: null // delete oldKey
|
||||
* @param {adapterCallback | undefined} options.callback
|
||||
* @returns {Object}
|
||||
*/
|
||||
const convertList = (targetList, {config, callback = (v) => v}) => {
|
||||
return targetList.map((targetObject) => convertObject(targetObject, {config, callback}))
|
||||
}
|
||||
|
||||
export {convertList, convertObject}
|
||||
39
users-manage/src/helpers/Services/apiHelpers.js
Normal file
39
users-manage/src/helpers/Services/apiHelpers.js
Normal file
@@ -0,0 +1,39 @@
|
||||
import axios from "axios";
|
||||
|
||||
const post = async (path, data, onError) => {
|
||||
return await axios.post(path, data)
|
||||
.then(r => r.data)
|
||||
.catch(error => {
|
||||
onError && onError(error)
|
||||
console.error('Error post request:', error)
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
const get = async (path) => {
|
||||
return await axios.get(path)
|
||||
.catch(error => {
|
||||
console.error('Error get request:', error)
|
||||
})
|
||||
}
|
||||
|
||||
const put = async (path, data, id, onError) => {
|
||||
return await axios.put(`${path}/${id}`, data)
|
||||
.then(r => r.data)
|
||||
.catch(error => {
|
||||
onError && onError(error)
|
||||
console.error('Error put request:', error)
|
||||
})
|
||||
}
|
||||
|
||||
const remove = async (path, id, onError) => {
|
||||
return await axios.delete(`${path}/${id}`)
|
||||
.then(r => r.data)
|
||||
.catch(error => {
|
||||
onError && onError(error)
|
||||
console.error('Error delete request:', error)
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
export {get, post, put, remove}
|
||||
28
users-manage/src/helpers/Users/StaticData.js
Normal file
28
users-manage/src/helpers/Users/StaticData.js
Normal file
@@ -0,0 +1,28 @@
|
||||
const devUsersList = [
|
||||
{
|
||||
"id": 1,
|
||||
"first_name": "Leanne",
|
||||
"last_name": "Graham",
|
||||
"email": "test@mail.ru",
|
||||
"role": "admin",
|
||||
"is_active": true
|
||||
},
|
||||
{
|
||||
"id": 2,
|
||||
"first_name": "Leanne",
|
||||
"last_name": "Graham",
|
||||
"email": "test@mail.ru",
|
||||
"role": "admin",
|
||||
"is_active": true
|
||||
},
|
||||
{
|
||||
"id": 3,
|
||||
"first_name": "Leanne",
|
||||
"last_name": "Graham",
|
||||
"email": "test@mail.ru",
|
||||
"role": "admin",
|
||||
"is_active": true
|
||||
},
|
||||
]
|
||||
|
||||
export {devUsersList}
|
||||
206
users-manage/src/helpers/Users/Users.js
Normal file
206
users-manage/src/helpers/Users/Users.js
Normal file
@@ -0,0 +1,206 @@
|
||||
import {get, post, put, remove} from './apiHelpers.js'
|
||||
import {convertList} from './adapter/adapter.js'
|
||||
import {devUsersList} from './StaticData.js'
|
||||
|
||||
/**
|
||||
* Интерфейс пользователя
|
||||
* @typedef {Object} User
|
||||
* @param {Number} id
|
||||
*/
|
||||
|
||||
class Users {
|
||||
/**
|
||||
* Класс управления роутерами
|
||||
* @param {String} apiAddr - path to service
|
||||
* @param {Object | undefined} adapter_config - oldKey: newKey
|
||||
* @param {Object | undefined} params - Конфиг настроек
|
||||
* @param {'prod' | 'test'} params.mode - Конфиг настроек
|
||||
*/
|
||||
constructor(apiAddr, adapter_config = {}, params = {mode: 'prod'}) {
|
||||
this.apiAddr = apiAddr
|
||||
this.config = adapter_config
|
||||
this.mode = params.mode
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {Object} params - Конфиг настроек
|
||||
* @param {'prod' | 'dev'} params.mode - Конфиг настроек
|
||||
* @returns {Promise<*[]> | *[]}
|
||||
*/
|
||||
async getUsers(params) {
|
||||
if (params.mode === "dev") {
|
||||
return devUsersList
|
||||
}
|
||||
let res = await get(`${this.apiAddr}/users`)
|
||||
let updatedUsers = convertList(res.data, {config: this.config})
|
||||
return updatedUsers
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {Number} id - Сервис id, если id не указан, отображается список всех роутеров
|
||||
* @param {'dev' | 'prod'} mode - Сервис id, если id не указан, отображается список всех роутеров
|
||||
* @returns {Promise<*[]>}
|
||||
*/
|
||||
async getUsersBySiteId(id, mode) {
|
||||
if (mode === "dev") {
|
||||
return devUsersList
|
||||
}
|
||||
let res = await get(`${this.apiAddr}/users/by_server/${id}`)
|
||||
let updatedUsers = convertList(res.data, {config: this.config})
|
||||
return updatedUsers
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {User} userData
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
async createUser(userData) {
|
||||
const newUser = await post(`${this.apiAddr}/users`, userData)
|
||||
return newUser
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {User} userData
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
async updateUser(userData) {
|
||||
const updatedUserData = {...userData}
|
||||
delete updatedUserData.id
|
||||
const newUser = await put(`${this.apiAddr}/users/${userData.id}`, updatedUserData)
|
||||
return newUser
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {Number} userId - Сервис id, если id не указан, отображается список всех роутеров
|
||||
* @returns {Promise<*[]>}
|
||||
*/
|
||||
async removeUser(userId) {
|
||||
const removedUser = await remove(`${this.apiAddr}/users/${userId}`)
|
||||
return removedUser
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Функция запускает список запросов к апишке и логает ответы
|
||||
* @returns {Promise<String>}
|
||||
*/
|
||||
async test() {
|
||||
console.log("_______START TEST_______")
|
||||
|
||||
const allUsers = await this.getUsers()
|
||||
console.log("allUsers", allUsers)
|
||||
const serverUser = await this.getUserById(1)
|
||||
console.log("getUserById 1", serverUser)
|
||||
const newUser = await this.createUser({
|
||||
"server_id": 1,
|
||||
"path": "/",
|
||||
"role": 1,
|
||||
"description": "tests swagger",
|
||||
"deepness": 6,
|
||||
"order": 0,
|
||||
"is_cb_on": true,
|
||||
"cb_request_limit": 100,
|
||||
"cb_min_requests": 100,
|
||||
"cb_error_threshold_percentage": 0.35,
|
||||
"cb_interval_duration": 2000000,
|
||||
"cb_open_state_timeout": 1000000
|
||||
})
|
||||
console.log("newUser", newUser)
|
||||
const updatedUser = await this.updateUser({
|
||||
"path": "/updated_path/",
|
||||
"description": "updated_description",
|
||||
"id": newUser.id
|
||||
})
|
||||
console.log("updatedUser", updatedUser)
|
||||
const removedUser = await this.removeUser(newUser.id)
|
||||
console.log("removedUser", removedUser)
|
||||
|
||||
const newUser1 = await this.createUser({
|
||||
"server_id": 1,
|
||||
"path": "/",
|
||||
"role": 1,
|
||||
"description": "tests swagger",
|
||||
"deepness": 6,
|
||||
"order": 0,
|
||||
"is_cb_on": true,
|
||||
"cb_request_limit": 100,
|
||||
"cb_min_requests": 100,
|
||||
"cb_error_threshold_percentage": 0.35,
|
||||
"cb_interval_duration": 2000000,
|
||||
"cb_open_state_timeout": 1000000
|
||||
})
|
||||
const newUser2 = await this.createUser({
|
||||
"server_id": 1,
|
||||
"path": "/",
|
||||
"role": 1,
|
||||
"description": "tests swagger",
|
||||
"deepness": 6,
|
||||
"order": 0,
|
||||
"is_cb_on": true,
|
||||
"cb_request_limit": 100,
|
||||
"cb_min_requests": 100,
|
||||
"cb_error_threshold_percentage": 0.35,
|
||||
"cb_interval_duration": 2000000,
|
||||
"cb_open_state_timeout": 1000000
|
||||
})
|
||||
const newUser3 = await this.createUser({
|
||||
"server_id": 1,
|
||||
"path": "/",
|
||||
"role": 1,
|
||||
"description": "tests swagger",
|
||||
"deepness": 6,
|
||||
"order": 0,
|
||||
"is_cb_on": true,
|
||||
"cb_request_limit": 100,
|
||||
"cb_min_requests": 100,
|
||||
"cb_error_threshold_percentage": 0.35,
|
||||
"cb_interval_duration": 2000000,
|
||||
"cb_open_state_timeout": 1000000
|
||||
})
|
||||
|
||||
const actions = [
|
||||
{
|
||||
...newUser1,
|
||||
action: "update",
|
||||
"path": "/updated_path2/",
|
||||
"description": "updated_description2",
|
||||
}, {
|
||||
...newUser2,
|
||||
action: "update",
|
||||
"path": "/updated_path3/",
|
||||
"description": "updated_description3",
|
||||
}, {
|
||||
...newUser3,
|
||||
action: "remove"
|
||||
},
|
||||
{
|
||||
action: "create",
|
||||
"server_id": 1,
|
||||
"path": "/lalalala/lalalal",
|
||||
"role": 1,
|
||||
"description": "new_route_created",
|
||||
"deepness": 6,
|
||||
"order": 0,
|
||||
"is_cb_on": true,
|
||||
"cb_request_limit": 100,
|
||||
"cb_min_requests": 100,
|
||||
"cb_error_threshold_percentage": 0.35,
|
||||
"cb_interval_duration": 2000000,
|
||||
"cb_open_state_timeout": 1000000
|
||||
}
|
||||
]
|
||||
|
||||
const mutationsList = await this.updateGroupUsers(actions)
|
||||
console.log("mutationsList", mutationsList)
|
||||
console.log("________END TEST________")
|
||||
|
||||
return "ok"
|
||||
}
|
||||
}
|
||||
|
||||
export default Users
|
||||
43
users-manage/src/helpers/Users/adapter/adapter.js
Normal file
43
users-manage/src/helpers/Users/adapter/adapter.js
Normal file
@@ -0,0 +1,43 @@
|
||||
/**
|
||||
* Каллбек изменнеия, ожидает что вернется измененный объект
|
||||
*
|
||||
* @callback adapterCallback
|
||||
* @param {Object} modifiedObject - новый объект
|
||||
* @param {Object} originalObject - изначальный объект
|
||||
* @returns {Object} измененный коллбэком объект
|
||||
*/
|
||||
|
||||
/**
|
||||
*
|
||||
* Если у newKey значение отстутсвует - null, '', false, 0, то oldKey удаляется из объекта
|
||||
*
|
||||
* @param {Object} targetObject
|
||||
* @param {Object} params
|
||||
* @param {Object} params.config - oldKey: null // delete oldKey
|
||||
* @param {adapterCallback | undefined} params.callback
|
||||
* @returns {Object}
|
||||
*/
|
||||
const convertObject = (targetObject, {config, callback = (v) => v}) => {
|
||||
let newObject = {}
|
||||
for (const key in config) {
|
||||
newObject[config[key]] = targetObject[key]
|
||||
}
|
||||
return callback(newObject, targetObject)
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* Если у newKey значение отстутсвует - null, '', false, 0, то oldKey удаляется из объекта
|
||||
*
|
||||
* @param {Array} targetList
|
||||
* @param {Object} options - oldKey: null // delete oldKey
|
||||
* @param {Object} options.config - oldKey: null // delete oldKey
|
||||
* @param {adapterCallback | undefined} options.callback
|
||||
* @returns {Object}
|
||||
*/
|
||||
const convertList = (targetList, {config, callback = (v) => v}) => {
|
||||
return targetList.map((targetObject) => convertObject(targetObject, {config, callback}))
|
||||
}
|
||||
|
||||
|
||||
export {convertList, convertObject}
|
||||
203
users-manage/src/helpers/Users/adapter/adapter.test.js
Normal file
203
users-manage/src/helpers/Users/adapter/adapter.test.js
Normal file
@@ -0,0 +1,203 @@
|
||||
import { expect, test } from 'vitest'
|
||||
import { convertObject, convertList } from './adapter'
|
||||
|
||||
|
||||
test('parse object from api', () => {
|
||||
const targetObject = {
|
||||
"id": 1,
|
||||
"created_at": "2024-02-15T17:24:52.755254148+03:00",
|
||||
"updated_at": "2024-02-15T17:24:52.755254148+03:00",
|
||||
"deleted_at": null,
|
||||
"name": "jsonplaceholder.typicode.com",
|
||||
"port": 9965,
|
||||
"proxy_ip": "172.25.78.153",
|
||||
"site_ip": "172.25.78.36",
|
||||
"internet_uri": "localhost",
|
||||
"description": "localhost",
|
||||
"is_online": true
|
||||
}
|
||||
|
||||
const expectedObject = {
|
||||
id: 1,
|
||||
name2: "jsonplaceholder.typicode.com",
|
||||
port2: 9965,
|
||||
proxy_ip: "172.25.78.153",
|
||||
site_ip: "172.25.78.36",
|
||||
internet_uri: "localhost",
|
||||
description: "localhost",
|
||||
is_online: true,
|
||||
}
|
||||
|
||||
const config = {
|
||||
id: "id",
|
||||
name: "name2",
|
||||
port: "port2",
|
||||
proxy_ip: "proxy_ip",
|
||||
site_ip: "site_ip",
|
||||
internet_uri: "internet_uri",
|
||||
description: "description",
|
||||
is_online: "is_online",
|
||||
}
|
||||
|
||||
expect(convertObject(targetObject, {config})).toEqual(expectedObject)
|
||||
})
|
||||
|
||||
test('adapt list objects without callback', () => {
|
||||
const targetObject1 = {
|
||||
"id": 1,
|
||||
"created_at": "2024-02-15T17:24:52.755254148+03:00",
|
||||
"updated_at": "2024-02-15T17:24:52.755254148+03:00",
|
||||
"deleted_at": null,
|
||||
"name": "jsonplaceholder.typicode.com",
|
||||
"port": 9965,
|
||||
"proxy_ip": "172.25.78.153",
|
||||
"site_ip": "172.25.78.36",
|
||||
"internet_uri": "localhost",
|
||||
"description": "localhost",
|
||||
"is_online": true
|
||||
}
|
||||
const targetObject2 = {
|
||||
"id": 1,
|
||||
"created_at": "2024-02-15T17:24:52.755254148+03:00",
|
||||
"updated_at": "2024-02-15T17:24:52.755254148+03:00",
|
||||
"deleted_at": null,
|
||||
"name": "jsonplaceholder.typicode.com",
|
||||
"port": 9965,
|
||||
"proxy_ip": "172.25.78.153",
|
||||
"site_ip": "172.25.78.36",
|
||||
"internet_uri": "localhost",
|
||||
"description": "localhost",
|
||||
"is_online": true
|
||||
}
|
||||
const targetObject3 = {
|
||||
"id": 1,
|
||||
"created_at": "2024-02-15T17:24:52.755254148+03:00",
|
||||
"updated_at": "2024-02-15T17:24:52.755254148+03:00",
|
||||
"deleted_at": null,
|
||||
"name": "jsonplaceholder.typicode.com",
|
||||
"port": 9965,
|
||||
"proxy_ip": "172.25.78.153",
|
||||
"site_ip": "172.25.78.36",
|
||||
"internet_uri": "localhost",
|
||||
"description": "localhost",
|
||||
"is_online": true
|
||||
}
|
||||
|
||||
const targetList = [targetObject1, targetObject2, targetObject3]
|
||||
|
||||
const selfObject1 = {
|
||||
id: 1,
|
||||
name2: "jsonplaceholder.typicode.com",
|
||||
port2: 9965,
|
||||
proxy_ip: "172.25.78.153",
|
||||
site_ip: "172.25.78.36",
|
||||
internet_uri: "localhost",
|
||||
description: "localhost",
|
||||
is_online: true,
|
||||
}
|
||||
const selfObject2 = {
|
||||
id: 1,
|
||||
name2: "jsonplaceholder.typicode.com",
|
||||
port2: 9965,
|
||||
proxy_ip: "172.25.78.153",
|
||||
site_ip: "172.25.78.36",
|
||||
internet_uri: "localhost",
|
||||
description: "localhost",
|
||||
is_online: true,
|
||||
}
|
||||
const selfObject3 = {
|
||||
id: 1,
|
||||
name2: "jsonplaceholder.typicode.com",
|
||||
port2: 9965,
|
||||
proxy_ip: "172.25.78.153",
|
||||
site_ip: "172.25.78.36",
|
||||
internet_uri: "localhost",
|
||||
description: "localhost",
|
||||
is_online: true,
|
||||
}
|
||||
|
||||
const selfList = [selfObject1, selfObject2, selfObject3]
|
||||
|
||||
const config = {
|
||||
id: "id",
|
||||
name: "name2",
|
||||
port: "port2",
|
||||
proxy_ip: "proxy_ip",
|
||||
site_ip: "site_ip",
|
||||
internet_uri: "internet_uri",
|
||||
description: "description",
|
||||
is_online: "is_online",
|
||||
}
|
||||
|
||||
expect(convertList(targetList, {config})).toEqual(selfList)
|
||||
})
|
||||
|
||||
test('adapt list objects with callback', () => {
|
||||
const targetObject1 = {
|
||||
"id": 1,
|
||||
"created_at": "2024-02-15T17:24:52.755254148+03:00",
|
||||
"updated_at": "2024-02-15T17:24:52.755254148+03:00",
|
||||
"deleted_at": null,
|
||||
"name": "jsonplaceholder.typicode.com",
|
||||
"port": 9965,
|
||||
"proxy_ip": "172.25.78.153",
|
||||
"site_ip": "172.25.78.36",
|
||||
"internet_uri": "localhost",
|
||||
"description": "localhost",
|
||||
"is_online": true
|
||||
}
|
||||
const targetObject2 = {
|
||||
"id": 1,
|
||||
"created_at": "2024-02-15T17:24:52.755254148+03:00",
|
||||
"updated_at": "2024-02-15T17:24:52.755254148+03:00",
|
||||
"deleted_at": null,
|
||||
"name": "jsonplaceholder.typicode.com",
|
||||
"port": 9965,
|
||||
"proxy_ip": "172.25.78.153",
|
||||
"site_ip": "172.25.78.36",
|
||||
"internet_uri": "localhost",
|
||||
"description": "localhost",
|
||||
"is_online": false
|
||||
}
|
||||
|
||||
const targetList = [targetObject1, targetObject2]
|
||||
|
||||
const expectedObject1 = {
|
||||
id: 1,
|
||||
name2: "jsonplaceholder.typicode.com",
|
||||
port2: 9965,
|
||||
proxy_ip: "172.25.78.153",
|
||||
site_ip: "172.25.78.36",
|
||||
internet_uri: "localhost",
|
||||
description: "localhost",
|
||||
state: "active",
|
||||
}
|
||||
|
||||
const expectedObject2 = {
|
||||
id: 1,
|
||||
name2: "jsonplaceholder.typicode.com",
|
||||
port2: 9965,
|
||||
proxy_ip: "172.25.78.153",
|
||||
site_ip: "172.25.78.36",
|
||||
internet_uri: "localhost",
|
||||
description: "localhost",
|
||||
state: "disabled",
|
||||
}
|
||||
|
||||
|
||||
const expectedList = [expectedObject1, expectedObject2]
|
||||
|
||||
const config = {
|
||||
id: "id",
|
||||
name: "name2",
|
||||
port: "port2",
|
||||
proxy_ip: "proxy_ip",
|
||||
site_ip: "site_ip",
|
||||
internet_uri: "internet_uri",
|
||||
description: "description",
|
||||
}
|
||||
|
||||
const callback = (el, {is_online}) => ({...el, state: is_online ? "active": "disabled"})
|
||||
|
||||
expect(convertList(targetList, {config, callback})).toEqual(expectedList)
|
||||
})
|
||||
39
users-manage/src/helpers/Users/apiHelpers.js
Normal file
39
users-manage/src/helpers/Users/apiHelpers.js
Normal file
@@ -0,0 +1,39 @@
|
||||
import axios from "axios";
|
||||
|
||||
const post = async (path, data, onError) => {
|
||||
return await axios.post(path, data)
|
||||
.then(r => r.data)
|
||||
.catch(error => {
|
||||
onError && onError(error)
|
||||
console.error('Error post request:', error)
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
const get = async (path) => {
|
||||
return await axios.get(path)
|
||||
.catch(error => {
|
||||
console.error('Error get request:', error)
|
||||
})
|
||||
}
|
||||
|
||||
const put = async (path, data, id, onError) => {
|
||||
return await axios.put(`${path}/${id}`, data)
|
||||
.then(r => r.data)
|
||||
.catch(error => {
|
||||
onError && onError(error)
|
||||
console.error('Error put request:', error)
|
||||
})
|
||||
}
|
||||
|
||||
const remove = async (path, id, onError) => {
|
||||
return await axios.delete(`${path}/${id}`)
|
||||
.then(r => r.data)
|
||||
.catch(error => {
|
||||
onError && onError(error)
|
||||
console.error('Error delete request:', error)
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
export {get, post, put, remove}
|
||||
43
users-manage/src/helpers/adapter/adapter.js
Normal file
43
users-manage/src/helpers/adapter/adapter.js
Normal file
@@ -0,0 +1,43 @@
|
||||
/**
|
||||
* Каллбек изменнеия, ожидает что вернется измененный объект
|
||||
*
|
||||
* @callback adapterCallback
|
||||
* @param {Object} modifiedObject - новый объект
|
||||
* @param {Object} originalObject - изначальный объект
|
||||
* @returns {Object} измененный коллбэком объект
|
||||
*/
|
||||
|
||||
/**
|
||||
*
|
||||
* Если у newKey значение отстутсвует - null, '', false, 0, то oldKey удаляется из объекта
|
||||
*
|
||||
* @param {Object} targetObject
|
||||
* @param {Object} params
|
||||
* @param {Object} params.config - oldKey: null // delete oldKey
|
||||
* @param {adapterCallback | undefined} params.callback
|
||||
* @returns {Object}
|
||||
*/
|
||||
const convertObject = (targetObject, {config, callback = (v) => v}) => {
|
||||
let newObject = {}
|
||||
for (const key in config) {
|
||||
newObject[config[key]] = targetObject[key]
|
||||
}
|
||||
return callback(newObject, targetObject)
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* Если у newKey значение отстутсвует - null, '', false, 0, то oldKey удаляется из объекта
|
||||
*
|
||||
* @param {Array} targetList
|
||||
* @param {Object} options - oldKey: null // delete oldKey
|
||||
* @param {Object} options.config - oldKey: null // delete oldKey
|
||||
* @param {adapterCallback | undefined} options.callback
|
||||
* @returns {Object}
|
||||
*/
|
||||
const convertList = (targetList, {config, callback = (v) => v}) => {
|
||||
return targetList.map((targetObject) => convertObject(targetObject, {config, callback}))
|
||||
}
|
||||
|
||||
|
||||
export {convertList, convertObject}
|
||||
203
users-manage/src/helpers/adapter/adapter.test.js
Normal file
203
users-manage/src/helpers/adapter/adapter.test.js
Normal file
@@ -0,0 +1,203 @@
|
||||
import { expect, test } from 'vitest'
|
||||
import { convertObject, convertList } from './adapter'
|
||||
|
||||
|
||||
test('parse object from api', () => {
|
||||
const targetObject = {
|
||||
"id": 1,
|
||||
"created_at": "2024-02-15T17:24:52.755254148+03:00",
|
||||
"updated_at": "2024-02-15T17:24:52.755254148+03:00",
|
||||
"deleted_at": null,
|
||||
"name": "jsonplaceholder.typicode.com",
|
||||
"port": 9965,
|
||||
"proxy_ip": "172.25.78.153",
|
||||
"site_ip": "172.25.78.36",
|
||||
"internet_uri": "localhost",
|
||||
"description": "localhost",
|
||||
"is_online": true
|
||||
}
|
||||
|
||||
const expectedObject = {
|
||||
id: 1,
|
||||
name2: "jsonplaceholder.typicode.com",
|
||||
port2: 9965,
|
||||
proxy_ip: "172.25.78.153",
|
||||
site_ip: "172.25.78.36",
|
||||
internet_uri: "localhost",
|
||||
description: "localhost",
|
||||
is_online: true,
|
||||
}
|
||||
|
||||
const config = {
|
||||
id: "id",
|
||||
name: "name2",
|
||||
port: "port2",
|
||||
proxy_ip: "proxy_ip",
|
||||
site_ip: "site_ip",
|
||||
internet_uri: "internet_uri",
|
||||
description: "description",
|
||||
is_online: "is_online",
|
||||
}
|
||||
|
||||
expect(convertObject(targetObject, {config})).toEqual(expectedObject)
|
||||
})
|
||||
|
||||
test('adapt list objects without callback', () => {
|
||||
const targetObject1 = {
|
||||
"id": 1,
|
||||
"created_at": "2024-02-15T17:24:52.755254148+03:00",
|
||||
"updated_at": "2024-02-15T17:24:52.755254148+03:00",
|
||||
"deleted_at": null,
|
||||
"name": "jsonplaceholder.typicode.com",
|
||||
"port": 9965,
|
||||
"proxy_ip": "172.25.78.153",
|
||||
"site_ip": "172.25.78.36",
|
||||
"internet_uri": "localhost",
|
||||
"description": "localhost",
|
||||
"is_online": true
|
||||
}
|
||||
const targetObject2 = {
|
||||
"id": 1,
|
||||
"created_at": "2024-02-15T17:24:52.755254148+03:00",
|
||||
"updated_at": "2024-02-15T17:24:52.755254148+03:00",
|
||||
"deleted_at": null,
|
||||
"name": "jsonplaceholder.typicode.com",
|
||||
"port": 9965,
|
||||
"proxy_ip": "172.25.78.153",
|
||||
"site_ip": "172.25.78.36",
|
||||
"internet_uri": "localhost",
|
||||
"description": "localhost",
|
||||
"is_online": true
|
||||
}
|
||||
const targetObject3 = {
|
||||
"id": 1,
|
||||
"created_at": "2024-02-15T17:24:52.755254148+03:00",
|
||||
"updated_at": "2024-02-15T17:24:52.755254148+03:00",
|
||||
"deleted_at": null,
|
||||
"name": "jsonplaceholder.typicode.com",
|
||||
"port": 9965,
|
||||
"proxy_ip": "172.25.78.153",
|
||||
"site_ip": "172.25.78.36",
|
||||
"internet_uri": "localhost",
|
||||
"description": "localhost",
|
||||
"is_online": true
|
||||
}
|
||||
|
||||
const targetList = [targetObject1, targetObject2, targetObject3]
|
||||
|
||||
const selfObject1 = {
|
||||
id: 1,
|
||||
name2: "jsonplaceholder.typicode.com",
|
||||
port2: 9965,
|
||||
proxy_ip: "172.25.78.153",
|
||||
site_ip: "172.25.78.36",
|
||||
internet_uri: "localhost",
|
||||
description: "localhost",
|
||||
is_online: true,
|
||||
}
|
||||
const selfObject2 = {
|
||||
id: 1,
|
||||
name2: "jsonplaceholder.typicode.com",
|
||||
port2: 9965,
|
||||
proxy_ip: "172.25.78.153",
|
||||
site_ip: "172.25.78.36",
|
||||
internet_uri: "localhost",
|
||||
description: "localhost",
|
||||
is_online: true,
|
||||
}
|
||||
const selfObject3 = {
|
||||
id: 1,
|
||||
name2: "jsonplaceholder.typicode.com",
|
||||
port2: 9965,
|
||||
proxy_ip: "172.25.78.153",
|
||||
site_ip: "172.25.78.36",
|
||||
internet_uri: "localhost",
|
||||
description: "localhost",
|
||||
is_online: true,
|
||||
}
|
||||
|
||||
const selfList = [selfObject1, selfObject2, selfObject3]
|
||||
|
||||
const config = {
|
||||
id: "id",
|
||||
name: "name2",
|
||||
port: "port2",
|
||||
proxy_ip: "proxy_ip",
|
||||
site_ip: "site_ip",
|
||||
internet_uri: "internet_uri",
|
||||
description: "description",
|
||||
is_online: "is_online",
|
||||
}
|
||||
|
||||
expect(convertList(targetList, {config})).toEqual(selfList)
|
||||
})
|
||||
|
||||
test('adapt list objects with callback', () => {
|
||||
const targetObject1 = {
|
||||
"id": 1,
|
||||
"created_at": "2024-02-15T17:24:52.755254148+03:00",
|
||||
"updated_at": "2024-02-15T17:24:52.755254148+03:00",
|
||||
"deleted_at": null,
|
||||
"name": "jsonplaceholder.typicode.com",
|
||||
"port": 9965,
|
||||
"proxy_ip": "172.25.78.153",
|
||||
"site_ip": "172.25.78.36",
|
||||
"internet_uri": "localhost",
|
||||
"description": "localhost",
|
||||
"is_online": true
|
||||
}
|
||||
const targetObject2 = {
|
||||
"id": 1,
|
||||
"created_at": "2024-02-15T17:24:52.755254148+03:00",
|
||||
"updated_at": "2024-02-15T17:24:52.755254148+03:00",
|
||||
"deleted_at": null,
|
||||
"name": "jsonplaceholder.typicode.com",
|
||||
"port": 9965,
|
||||
"proxy_ip": "172.25.78.153",
|
||||
"site_ip": "172.25.78.36",
|
||||
"internet_uri": "localhost",
|
||||
"description": "localhost",
|
||||
"is_online": false
|
||||
}
|
||||
|
||||
const targetList = [targetObject1, targetObject2]
|
||||
|
||||
const expectedObject1 = {
|
||||
id: 1,
|
||||
name2: "jsonplaceholder.typicode.com",
|
||||
port2: 9965,
|
||||
proxy_ip: "172.25.78.153",
|
||||
site_ip: "172.25.78.36",
|
||||
internet_uri: "localhost",
|
||||
description: "localhost",
|
||||
state: "active",
|
||||
}
|
||||
|
||||
const expectedObject2 = {
|
||||
id: 1,
|
||||
name2: "jsonplaceholder.typicode.com",
|
||||
port2: 9965,
|
||||
proxy_ip: "172.25.78.153",
|
||||
site_ip: "172.25.78.36",
|
||||
internet_uri: "localhost",
|
||||
description: "localhost",
|
||||
state: "disabled",
|
||||
}
|
||||
|
||||
|
||||
const expectedList = [expectedObject1, expectedObject2]
|
||||
|
||||
const config = {
|
||||
id: "id",
|
||||
name: "name2",
|
||||
port: "port2",
|
||||
proxy_ip: "proxy_ip",
|
||||
site_ip: "site_ip",
|
||||
internet_uri: "internet_uri",
|
||||
description: "description",
|
||||
}
|
||||
|
||||
const callback = (el, {is_online}) => ({...el, state: is_online ? "active": "disabled"})
|
||||
|
||||
expect(convertList(targetList, {config, callback})).toEqual(expectedList)
|
||||
})
|
||||
16
users-manage/src/main.js
Normal file
16
users-manage/src/main.js
Normal file
@@ -0,0 +1,16 @@
|
||||
import './styles/index.css'
|
||||
import 'tippy.js/dist/tippy.css'
|
||||
|
||||
import { createApp } from 'vue'
|
||||
import App from './App.vue'
|
||||
import ramdaVue from "./ramda-vue.js";
|
||||
import { store } from '@/store'
|
||||
import VueTippy from 'vue-tippy'
|
||||
import Router from "@router"
|
||||
|
||||
createApp(App)
|
||||
.use(ramdaVue)
|
||||
.use(store)
|
||||
.use(VueTippy)
|
||||
.use(Router)
|
||||
.mount('#app')
|
||||
18
users-manage/src/ramda-vue.js
Normal file
18
users-manage/src/ramda-vue.js
Normal file
@@ -0,0 +1,18 @@
|
||||
import * as R from 'ramda'
|
||||
|
||||
const RamdaVue = {
|
||||
install: (app) => {
|
||||
app.$R = R;
|
||||
app.config.globalProperties.$R = R;
|
||||
|
||||
R.ifElse(
|
||||
R.and(R.compose(R.not, R.isNil), R.has("Vue")),
|
||||
(win) => {
|
||||
win.Vue.use(RamdaVue);
|
||||
},
|
||||
() => {}
|
||||
)(window);
|
||||
},
|
||||
};
|
||||
|
||||
export default RamdaVue;
|
||||
25
users-manage/src/router/index.js
Normal file
25
users-manage/src/router/index.js
Normal file
@@ -0,0 +1,25 @@
|
||||
import {createRouter, createWebHistory} from "vue-router";
|
||||
import SitesManagerPage from "@pages/SitesManagerPage/SitesManagerPage.vue"
|
||||
|
||||
const routes = [
|
||||
{
|
||||
path: "/",
|
||||
name: "sites",
|
||||
component: SitesManagerPage
|
||||
},
|
||||
{
|
||||
path: "/users/:mod",
|
||||
name: "users",
|
||||
component: import("@pages/UserManage/index.vue")
|
||||
},
|
||||
];
|
||||
|
||||
const routerHistory = createWebHistory();
|
||||
|
||||
const router = createRouter({
|
||||
history: routerHistory,
|
||||
routes
|
||||
});
|
||||
|
||||
export {routes};
|
||||
export default router;
|
||||
21
users-manage/src/store/index.js
Normal file
21
users-manage/src/store/index.js
Normal file
@@ -0,0 +1,21 @@
|
||||
import { createStore, createLogger } from 'vuex';
|
||||
import { store as services } from '@/store/modules/services';
|
||||
import { store as users } from '@/store/modules/users';
|
||||
|
||||
//eslint-disable-next-line no-undef
|
||||
let debug = process.env.NODE_ENV !== 'production';
|
||||
debug = false;
|
||||
|
||||
const plugins = debug ? [createLogger({})] : [];
|
||||
|
||||
export const store = createStore({
|
||||
plugins,
|
||||
modules: {
|
||||
services,
|
||||
users
|
||||
},
|
||||
});
|
||||
|
||||
export function useStore() {
|
||||
return store;
|
||||
}
|
||||
41
users-manage/src/store/modules/apiHelpers.js
Normal file
41
users-manage/src/store/modules/apiHelpers.js
Normal file
@@ -0,0 +1,41 @@
|
||||
import axios from "axios";
|
||||
|
||||
const post = async (path, data, onError) => {
|
||||
return axios.post(`${import.meta.env.VITE_API_ADDR}${path}`, data)
|
||||
.then(r => r.data)
|
||||
.catch(error => {
|
||||
onError && onError(error)
|
||||
console.error('Error post request:', error)
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {String} path
|
||||
* @returns {Promise<axios.AxiosResponse<Array | Object> | void>}
|
||||
*/
|
||||
const get = async (path) => {
|
||||
return axios.get(`${import.meta.env.VITE_API_ADDR}${path}`)
|
||||
.catch(error => {
|
||||
console.error('Error post request:', error)
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {Object} data
|
||||
* @param {Function} onError
|
||||
* @returns {Promise<axios.AxiosResponse<any> | void>}
|
||||
*/
|
||||
const scand_post = async (data, onError) => {
|
||||
return axios.post(`${import.meta.env.VITE_SCAND_API_URL}`, data)
|
||||
.then(r => r.data)
|
||||
.catch(error => {
|
||||
onError && onError(error)
|
||||
console.error('Error post request:', error)
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
export {get, post, scand_post}
|
||||
65
users-manage/src/store/modules/services/StaticData.js
Normal file
65
users-manage/src/store/modules/services/StaticData.js
Normal file
@@ -0,0 +1,65 @@
|
||||
const config = {
|
||||
id: "id",
|
||||
created_at: "created_at",
|
||||
updated_at: "updated_at",
|
||||
deleted_at: "deleted_at",
|
||||
name: "name",
|
||||
port: "port",
|
||||
proxy_ip: "proxy_ip",
|
||||
internet_uri: "internet_uri",
|
||||
description: "description",
|
||||
is_online: "is_online",
|
||||
site_ip: "device_ip",
|
||||
}
|
||||
|
||||
const configRoutes = {
|
||||
id: "id",
|
||||
created_at: "created_at",
|
||||
updated_at: "updated_at",
|
||||
deleted_at: "deleted_at",
|
||||
server_id: "server_id",
|
||||
path: "path",
|
||||
role: "role",
|
||||
description: "description",
|
||||
order: "order",
|
||||
deepness: "deepness",
|
||||
is_cb_on: "is_cb_on",
|
||||
is_online: "is_online",
|
||||
cb_request_limit: "cb_request_limit",
|
||||
cb_min_requests: "cb_min_requests",
|
||||
cb_error_threshold_percentage: "cb_error_threshold_percentage",
|
||||
cb_interval_duration: "cb_interval_duration",
|
||||
cb_open_state_timeout: "cb_open_state_timeout",
|
||||
site_ip: "device_ip",
|
||||
}
|
||||
|
||||
const routes = [
|
||||
{"path": "/socket.io", "backend": "http://backend:8000", "role": "*", "site": "cdrp"},
|
||||
{"path": "/socket.io", "backend": "http://backend:8000", "role": "*", "site": "cdrp"},
|
||||
{"path": "/socket.io/*", "backend": "http://backend:8000", "role": "*", "site": "cdrp"},
|
||||
{"path": "/api", "backend": "http://backend:8000", "role": "*", "site": "cdrp"},
|
||||
{"path": "/api/*", "backend": "http://backend:8000", "role": "*", "site": "cdrp"},
|
||||
{"path": "/api/*/*", "backend": "http://backend:8000", "role": "*", "site": "cdrp"},
|
||||
{"path": "/api/*/*/*", "backend": "http://backend:8000", "role": "*", "site": "cdrp"},
|
||||
{"path": "/api/*/*/*/*", "backend": "http://backend:8000", "role": "*", "site": "cdrp"},
|
||||
{"path": "/swagger", "backend": "http://backend:8000", "role": "*", "site": "cdrp"},
|
||||
{"path": "/swagger/*", "backend": "http://backend:8000", "role": "*", "site": "cdrp"},
|
||||
{"path": "/swagger/*/*", "backend": "http://backend:8000", "role": "*", "site": "cdrp"},
|
||||
{"path": "/swagger/*/*/*","backend": "http://backend:8000", "role": "*", "site": "cdrp"},
|
||||
{"path": "/", "backend": "http://frontend:8000", "role": "*", "site": "cdrp"},
|
||||
{"path": "/*", "backend": "http://frontend:8000", "role": "*", "site": "cdrp"},
|
||||
{"path": "/*/*", "backend": "http://frontend:8000", "role": "*", "site": "cdrp"},
|
||||
{"path": "/*/*/*", "backend": "http://frontend:8000", "role": "*", "site": "cdrp"},
|
||||
{"path": "/*/*/*/*", "backend": "http://frontend:8000", "role": "*", "site": "cdrp"},
|
||||
{"path": "/*/*/*/*/*", "backend": "http://frontend:8000", "role": "*", "site": "cdrp"}
|
||||
]
|
||||
|
||||
const sites = [
|
||||
{"name": "cdrp"},
|
||||
{"name": "live_monitore"},
|
||||
{"name": "buk"},
|
||||
{"name": "mtso"},
|
||||
{"name": "as_dmps"}
|
||||
]
|
||||
|
||||
export {routes, sites, config, configRoutes}
|
||||
106
users-manage/src/store/modules/services/index.js
Normal file
106
users-manage/src/store/modules/services/index.js
Normal file
@@ -0,0 +1,106 @@
|
||||
import routeOptions from './routeOptions.json'
|
||||
import Services from '@helpers/Services/Services.js';
|
||||
import {config} from './StaticData.js'
|
||||
import {isEmpty} from "ramda";
|
||||
|
||||
const initState = {
|
||||
isSaveData: false,
|
||||
sites: [],
|
||||
sitesState: "loading",
|
||||
|
||||
selectedSiteState: "await",
|
||||
selectedSite: null,
|
||||
|
||||
newSite: null,
|
||||
};
|
||||
|
||||
const state = {
|
||||
...initState
|
||||
};
|
||||
|
||||
const getters = {
|
||||
isSaveData: (state) => state.isSaveData,
|
||||
sites: (state) => state.newSite ? [state.newSite, ...state.sites] : state.sites,
|
||||
routes: (state) => state.routes,
|
||||
routesLib: (state) => state.routesLib,
|
||||
selectedSiteState: (state) => state.selectedSiteState,
|
||||
sitesState: (state) => state.sitesState,
|
||||
newRoute: (state) => state.newRoute,
|
||||
routeOptions: () => routeOptions,
|
||||
selectedSite: (state) => state.selectedSite,
|
||||
};
|
||||
|
||||
const mutations = {
|
||||
setIsSaveData: (state, payload) => state.isSaveData = payload,
|
||||
setSites: (state, payload) => state.sites = payload,
|
||||
setSitesState: (state, payload) => state.sitesState = payload,
|
||||
setSelectedSite: (state, payload) => state.selectedSite = payload,
|
||||
};
|
||||
|
||||
|
||||
const actions = {
|
||||
addNewSiteLayout: ({state}) => {
|
||||
const newSite = {"port": "", "name": "", id: -1}
|
||||
state.newSite = newSite
|
||||
state.selectedSite = newSite
|
||||
},
|
||||
createNewSite: ({state}) => {
|
||||
state.isSaveData = false
|
||||
state.selectedSite = null
|
||||
state.sites = [{
|
||||
...state.newSite,
|
||||
id: state.sites.reduce((acc, {id}) => Math.max(id, acc) + 1, 0)
|
||||
}, ...state.sites]
|
||||
state.newSite = null
|
||||
},
|
||||
breakAddingSite: ({state}) => {
|
||||
state.newSite = null
|
||||
state.selectedSite = null
|
||||
},
|
||||
addNewRouteLayout: ({state}) => {
|
||||
const newRoute = {"path": null, "role": null, id: -1}
|
||||
state.newRoute = newRoute
|
||||
},
|
||||
createNewRoute: ({state}, payload) => {
|
||||
console.log(payload)
|
||||
state.newRoute = null
|
||||
},
|
||||
stopAddingRoute: ({state}) => {
|
||||
state.newRoute = null
|
||||
},
|
||||
uploadSites: async ({commit}) => {
|
||||
const path = import.meta.env.VITE_API_ADDR
|
||||
const services = new Services(path, config)
|
||||
const sites = await services.getServices()
|
||||
commit('setSites', sites)
|
||||
commit('setSitesState', 'active')
|
||||
},
|
||||
uploadAndSelectService: ({state}, id) => {
|
||||
state.selectedSite = state.sites.find(site => site.id === id)
|
||||
state.selectedSiteState = 'active'
|
||||
},
|
||||
saveSite: async ({state}, payload) => {
|
||||
state.isSaveData = false
|
||||
state.selectedSite = null
|
||||
const path = import.meta.env.VITE_API_ADDR
|
||||
const services = new Services(path, config)
|
||||
const updatedSites = await services.setServicesData(payload, state.sites, payload.id)
|
||||
state.sites = isEmpty(updatedSites) ? state.sites : updatedSites
|
||||
},
|
||||
breakSavingSite: ({state}) => {
|
||||
state.selectedSite = null
|
||||
},
|
||||
resetStore: ({state}) => {
|
||||
Object.entries(initState).forEach(([k,v]) => {
|
||||
state[k] = v
|
||||
})
|
||||
},
|
||||
};
|
||||
|
||||
export const store = {
|
||||
namespaced: true,
|
||||
state,
|
||||
getters,
|
||||
mutations,
|
||||
actions,
|
||||
};
|
||||
@@ -0,0 +1,5 @@
|
||||
[
|
||||
{ "name": "admin", "value": "admin" },
|
||||
{ "name": "user", "value": "user" },
|
||||
{ "name": "*", "value": "*" }
|
||||
]
|
||||
19
users-manage/src/store/modules/services/routes.json
Executable file
19
users-manage/src/store/modules/services/routes.json
Executable file
@@ -0,0 +1,19 @@
|
||||
[
|
||||
{"description": "The item is sized according to its width and height properties. It shrinks to its minimum size to fit the container, but does not grow to absorb any extra free space in the flex container. This is equivalent to setting", "id": 1,"path": "/socket.io", "backend": "http://backend:8000", "role": "*", "site": "cdrp"},
|
||||
{"description": "The item is sized according to its width and height properties. It shrinks to its minimum size to fit the container, but does not grow to absorb any extra free space in the flex container. This is equivalent to setting", "id": 2,"path": "/socket.io/*", "backend": "http://backend:8000", "role": "*", "site": "cdrp"},
|
||||
{"description": "The item is sized according to its width and height properties. It shrinks to its minimum size to fit the container, but does not grow to absorb any extra free space in the flex container. This is equivalent to setting", "id": 3,"path": "/api", "backend": "http://backend:8000", "role": "*", "site": "cdrp"},
|
||||
{"description": "The item is sized according to its width and height properties. It shrinks to its minimum size to fit the container, but does not grow to absorb any extra free space in the flex container. This is equivalent to setting", "id": 4,"path": "/api/*", "backend": "http://backend:8000", "role": "*", "site": "cdrp"},
|
||||
{"description": "The item is sized according to its width and height properties. It shrinks to its minimum size to fit the container, but does not grow to absorb any extra free space in the flex container. This is equivalent to setting", "id": 5,"path": "/api/*/*", "backend": "http://backend:8000", "role": "*", "site": "cdrp"},
|
||||
{"description": "The item is sized according to its width and height properties. It shrinks to its minimum size to fit the container, but does not grow to absorb any extra free space in the flex container. This is equivalent to setting", "id": 6,"path": "/api/*/*/*", "backend": "http://backend:8000", "role": "*", "site": "cdrp"},
|
||||
{"description": "The item is sized according to its width and height properties. It shrinks to its minimum size to fit the container, but does not grow to absorb any extra free space in the flex container. This is equivalent to setting", "id": 7,"path": "/api/*/*/*/*", "backend": "http://backend:8000", "role": "*", "site": "cdrp"},
|
||||
{"description": "The item is sized according to its width and height properties. It shrinks to its minimum size to fit the container, but does not grow to absorb any extra free space in the flex container. This is equivalent to setting", "id": 8,"path": "/swagger", "backend": "http://backend:8000", "role": "*", "site": "cdrp"},
|
||||
{"description": "The item is sized according to its width and height properties. It shrinks to its minimum size to fit the container, but does not grow to absorb any extra free space in the flex container. This is equivalent to setting", "id": 9,"path": "/swagger/*", "backend": "http://backend:8000", "role": "*", "site": "cdrp"},
|
||||
{"description": "The item is sized according to its width and height properties. It shrinks to its minimum size to fit the container, but does not grow to absorb any extra free space in the flex container. This is equivalent to setting", "id": 10,"path": "/swagger/*/*", "backend": "http://backend:8000", "role": "*", "site": "cdrp"},
|
||||
{"description": "The item is sized according to its width and height properties. It shrinks to its minimum size to fit the container, but does not grow to absorb any extra free space in the flex container. This is equivalent to setting", "id": 11,"path": "/swagger/*/*/*","backend": "http://backend:8000", "role": "*", "site": "cdrp"},
|
||||
{"description": "The item is sized according to its width and height properties. It shrinks to its minimum size to fit the container, but does not grow to absorb any extra free space in the flex container. This is equivalent to setting", "id": 12,"path": "/", "backend": "http://frontend:8000", "role": "*", "site": "cdrp"},
|
||||
{"description": "The item is sized according to its width and height properties. It shrinks to its minimum size to fit the container, but does not grow to absorb any extra free space in the flex container. This is equivalent to setting", "id": 13,"path": "/*", "backend": "http://frontend:8000", "role": "*", "site": "cdrp"},
|
||||
{"description": "The item is sized according to its width and height properties. It shrinks to its minimum size to fit the container, but does not grow to absorb any extra free space in the flex container. This is equivalent to setting", "id": 14,"path": "/*/*", "backend": "http://frontend:8000", "role": "*", "site": "cdrp"},
|
||||
{"description": "The item is sized according to its width and height properties. It shrinks to its minimum size to fit the container, but does not grow to absorb any extra free space in the flex container. This is equivalent to setting", "id": 15,"path": "/*/*/*", "backend": "http://frontend:8000", "role": "*", "site": "cdrp"},
|
||||
{"description": "The item is sized according to its width and height properties. It shrinks to its minimum size to fit the container, but does not grow to absorb any extra free space in the flex container. This is equivalent to setting", "id": 16,"path": "/*/*/*/*", "backend": "http://frontend:8000", "role": "*", "site": "cdrp"},
|
||||
{"description": "The item is sized according to its width and height properties. It shrinks to its minimum size to fit the container, but does not grow to absorb any extra free space in the flex container. This is equivalent to setting", "id": 17,"path": "/*/*/*/*/*", "backend": "http://frontend:8000", "role": "*", "site": "cdrp"}
|
||||
]
|
||||
7
users-manage/src/store/modules/services/sites.json
Executable file
7
users-manage/src/store/modules/services/sites.json
Executable file
@@ -0,0 +1,7 @@
|
||||
[
|
||||
{"description": "The item is sized according to its width and height properties. It shrinks to its minimum size to fit the container, but does not grow to absorb any extra free space in the flex container. This is equivalent to setting", "device_ip": "172.25.78.36", "proxy_ip": "172.25.78.36", "status": "active", "id": 1,"port": "4000", "name": "cdrp"},
|
||||
{"description": "The item is sized according to its width and height properties. It shrinks to its minimum size to fit the container, but does not grow to absorb any extra free space in the flex container. This is equivalent to setting", "device_ip": "172.25.78.36", "proxy_ip": "172.25.78.36", "status": "active", "id": 2,"port": "4000", "name": "live_monitore"},
|
||||
{"description": "The item is sized according to its width and height properties. It shrinks to its minimum size to fit the container, but does not grow to absorb any extra free space in the flex container. This is equivalent to setting", "device_ip": "172.25.78.36", "proxy_ip": "172.25.78.36", "status": "active", "id": 3,"port": "4000", "name": "buk"},
|
||||
{"description": "The item is sized according to its width and height properties. It shrinks to its minimum size to fit the container, but does not grow to absorb any extra free space in the flex container. This is equivalent to setting", "device_ip": "172.25.78.36", "proxy_ip": "172.25.78.36", "status": "active", "id": 4,"port": "4000", "name": "mtso"},
|
||||
{"description": "The item is sized according to its width and height properties. It shrinks to its minimum size to fit the container, but does not grow to absorb any extra free space in the flex container. This is equivalent to setting", "device_ip": "172.25.78.36", "proxy_ip": "172.25.78.36", "status": "active", "id": 5,"port": "4000", "name": "as_dmps"}
|
||||
]
|
||||
29
users-manage/src/store/modules/users/StaticData.js
Normal file
29
users-manage/src/store/modules/users/StaticData.js
Normal file
@@ -0,0 +1,29 @@
|
||||
const routes = [
|
||||
{"path": "/socket.io", "backend": "http://backend:8000", "role": "*", "site": "cdrp"},
|
||||
{"path": "/socket.io/*", "backend": "http://backend:8000", "role": "*", "site": "cdrp"},
|
||||
{"path": "/api", "backend": "http://backend:8000", "role": "*", "site": "cdrp"},
|
||||
{"path": "/api/*", "backend": "http://backend:8000", "role": "*", "site": "cdrp"},
|
||||
{"path": "/api/*/*", "backend": "http://backend:8000", "role": "*", "site": "cdrp"},
|
||||
{"path": "/api/*/*/*", "backend": "http://backend:8000", "role": "*", "site": "cdrp"},
|
||||
{"path": "/api/*/*/*/*", "backend": "http://backend:8000", "role": "*", "site": "cdrp"},
|
||||
{"path": "/swagger", "backend": "http://backend:8000", "role": "*", "site": "cdrp"},
|
||||
{"path": "/swagger/*", "backend": "http://backend:8000", "role": "*", "site": "cdrp"},
|
||||
{"path": "/swagger/*/*", "backend": "http://backend:8000", "role": "*", "site": "cdrp"},
|
||||
{"path": "/swagger/*/*/*","backend": "http://backend:8000", "role": "*", "site": "cdrp"},
|
||||
{"path": "/", "backend": "http://frontend:8000", "role": "*", "site": "cdrp"},
|
||||
{"path": "/*", "backend": "http://frontend:8000", "role": "*", "site": "cdrp"},
|
||||
{"path": "/*/*", "backend": "http://frontend:8000", "role": "*", "site": "cdrp"},
|
||||
{"path": "/*/*/*", "backend": "http://frontend:8000", "role": "*", "site": "cdrp"},
|
||||
{"path": "/*/*/*/*", "backend": "http://frontend:8000", "role": "*", "site": "cdrp"},
|
||||
{"path": "/*/*/*/*/*", "backend": "http://frontend:8000", "role": "*", "site": "cdrp"}
|
||||
]
|
||||
|
||||
const sites = [
|
||||
{"name": "cdrp"},
|
||||
{"name": "live_monitore"},
|
||||
{"name": "buk"},
|
||||
{"name": "mtso"},
|
||||
{"name": "as_dmps"}
|
||||
]
|
||||
|
||||
export {routes, sites}
|
||||
51
users-manage/src/store/modules/users/index.js
Normal file
51
users-manage/src/store/modules/users/index.js
Normal file
@@ -0,0 +1,51 @@
|
||||
import Users from '@helpers/Users/Users.js';
|
||||
|
||||
const path = import.meta.env.VITE_API_ADDR
|
||||
const UsersService = new Users(path, {
|
||||
id: 'id',
|
||||
first_name: 'first_name',
|
||||
last_name: 'last_name',
|
||||
email: 'email',
|
||||
role: 'role',
|
||||
is_active: 'is_active'
|
||||
})
|
||||
|
||||
const initState = {
|
||||
usersList: [],
|
||||
componentState: 'disabled',
|
||||
};
|
||||
|
||||
const state = {
|
||||
...initState
|
||||
};
|
||||
|
||||
const getters = {
|
||||
usersList: (state) => state.usersList,
|
||||
componentState: (state) => state.componentState
|
||||
};
|
||||
|
||||
const mutations = {
|
||||
setUsersList: (state, payload) => state.usersList = payload,
|
||||
setComponentState: (state, payload) => state.componentState = payload
|
||||
};
|
||||
|
||||
const actions = {
|
||||
fetchUsersList: async ({commit}, {siteId, mode}) => {
|
||||
const usersList = await UsersService.getUsersBySiteId(siteId, mode)
|
||||
commit('setUsersList', usersList)
|
||||
commit('setComponentState', 'active')
|
||||
},
|
||||
resetStore: ({state}) => {
|
||||
Object.entries(initState).forEach(([k,v]) => {
|
||||
state[k] = v
|
||||
})
|
||||
},
|
||||
};
|
||||
|
||||
export const store = {
|
||||
namespaced: true,
|
||||
state,
|
||||
getters,
|
||||
mutations,
|
||||
actions,
|
||||
};
|
||||
5
users-manage/src/store/modules/users/routeOptions.json
Normal file
5
users-manage/src/store/modules/users/routeOptions.json
Normal file
@@ -0,0 +1,5 @@
|
||||
[
|
||||
{ "name": "admin", "value": "admin" },
|
||||
{ "name": "user", "value": "user" },
|
||||
{ "name": "*", "value": "*" }
|
||||
]
|
||||
19
users-manage/src/store/modules/users/routes.json
Executable file
19
users-manage/src/store/modules/users/routes.json
Executable file
@@ -0,0 +1,19 @@
|
||||
[
|
||||
{"description": "The item is sized according to its width and height properties. It shrinks to its minimum size to fit the container, but does not grow to absorb any extra free space in the flex container. This is equivalent to setting", "id": 1,"path": "/socket.io", "backend": "http://backend:8000", "role": "*", "site": "cdrp"},
|
||||
{"description": "The item is sized according to its width and height properties. It shrinks to its minimum size to fit the container, but does not grow to absorb any extra free space in the flex container. This is equivalent to setting", "id": 2,"path": "/socket.io/*", "backend": "http://backend:8000", "role": "*", "site": "cdrp"},
|
||||
{"description": "The item is sized according to its width and height properties. It shrinks to its minimum size to fit the container, but does not grow to absorb any extra free space in the flex container. This is equivalent to setting", "id": 3,"path": "/api", "backend": "http://backend:8000", "role": "*", "site": "cdrp"},
|
||||
{"description": "The item is sized according to its width and height properties. It shrinks to its minimum size to fit the container, but does not grow to absorb any extra free space in the flex container. This is equivalent to setting", "id": 4,"path": "/api/*", "backend": "http://backend:8000", "role": "*", "site": "cdrp"},
|
||||
{"description": "The item is sized according to its width and height properties. It shrinks to its minimum size to fit the container, but does not grow to absorb any extra free space in the flex container. This is equivalent to setting", "id": 5,"path": "/api/*/*", "backend": "http://backend:8000", "role": "*", "site": "cdrp"},
|
||||
{"description": "The item is sized according to its width and height properties. It shrinks to its minimum size to fit the container, but does not grow to absorb any extra free space in the flex container. This is equivalent to setting", "id": 6,"path": "/api/*/*/*", "backend": "http://backend:8000", "role": "*", "site": "cdrp"},
|
||||
{"description": "The item is sized according to its width and height properties. It shrinks to its minimum size to fit the container, but does not grow to absorb any extra free space in the flex container. This is equivalent to setting", "id": 7,"path": "/api/*/*/*/*", "backend": "http://backend:8000", "role": "*", "site": "cdrp"},
|
||||
{"description": "The item is sized according to its width and height properties. It shrinks to its minimum size to fit the container, but does not grow to absorb any extra free space in the flex container. This is equivalent to setting", "id": 8,"path": "/swagger", "backend": "http://backend:8000", "role": "*", "site": "cdrp"},
|
||||
{"description": "The item is sized according to its width and height properties. It shrinks to its minimum size to fit the container, but does not grow to absorb any extra free space in the flex container. This is equivalent to setting", "id": 9,"path": "/swagger/*", "backend": "http://backend:8000", "role": "*", "site": "cdrp"},
|
||||
{"description": "The item is sized according to its width and height properties. It shrinks to its minimum size to fit the container, but does not grow to absorb any extra free space in the flex container. This is equivalent to setting", "id": 10,"path": "/swagger/*/*", "backend": "http://backend:8000", "role": "*", "site": "cdrp"},
|
||||
{"description": "The item is sized according to its width and height properties. It shrinks to its minimum size to fit the container, but does not grow to absorb any extra free space in the flex container. This is equivalent to setting", "id": 11,"path": "/swagger/*/*/*","backend": "http://backend:8000", "role": "*", "site": "cdrp"},
|
||||
{"description": "The item is sized according to its width and height properties. It shrinks to its minimum size to fit the container, but does not grow to absorb any extra free space in the flex container. This is equivalent to setting", "id": 12,"path": "/", "backend": "http://frontend:8000", "role": "*", "site": "cdrp"},
|
||||
{"description": "The item is sized according to its width and height properties. It shrinks to its minimum size to fit the container, but does not grow to absorb any extra free space in the flex container. This is equivalent to setting", "id": 13,"path": "/*", "backend": "http://frontend:8000", "role": "*", "site": "cdrp"},
|
||||
{"description": "The item is sized according to its width and height properties. It shrinks to its minimum size to fit the container, but does not grow to absorb any extra free space in the flex container. This is equivalent to setting", "id": 14,"path": "/*/*", "backend": "http://frontend:8000", "role": "*", "site": "cdrp"},
|
||||
{"description": "The item is sized according to its width and height properties. It shrinks to its minimum size to fit the container, but does not grow to absorb any extra free space in the flex container. This is equivalent to setting", "id": 15,"path": "/*/*/*", "backend": "http://frontend:8000", "role": "*", "site": "cdrp"},
|
||||
{"description": "The item is sized according to its width and height properties. It shrinks to its minimum size to fit the container, but does not grow to absorb any extra free space in the flex container. This is equivalent to setting", "id": 16,"path": "/*/*/*/*", "backend": "http://frontend:8000", "role": "*", "site": "cdrp"},
|
||||
{"description": "The item is sized according to its width and height properties. It shrinks to its minimum size to fit the container, but does not grow to absorb any extra free space in the flex container. This is equivalent to setting", "id": 17,"path": "/*/*/*/*/*", "backend": "http://frontend:8000", "role": "*", "site": "cdrp"}
|
||||
]
|
||||
7
users-manage/src/store/modules/users/sites.json
Executable file
7
users-manage/src/store/modules/users/sites.json
Executable file
@@ -0,0 +1,7 @@
|
||||
[
|
||||
{"description": "The item is sized according to its width and height properties. It shrinks to its minimum size to fit the container, but does not grow to absorb any extra free space in the flex container. This is equivalent to setting", "device_ip": "172.25.78.36", "proxy_ip": "172.25.78.36", "status": "active", "id": 1,"port": "4000", "name": "cdrp"},
|
||||
{"description": "The item is sized according to its width and height properties. It shrinks to its minimum size to fit the container, but does not grow to absorb any extra free space in the flex container. This is equivalent to setting", "device_ip": "172.25.78.36", "proxy_ip": "172.25.78.36", "status": "active", "id": 2,"port": "4000", "name": "live_monitore"},
|
||||
{"description": "The item is sized according to its width and height properties. It shrinks to its minimum size to fit the container, but does not grow to absorb any extra free space in the flex container. This is equivalent to setting", "device_ip": "172.25.78.36", "proxy_ip": "172.25.78.36", "status": "active", "id": 3,"port": "4000", "name": "buk"},
|
||||
{"description": "The item is sized according to its width and height properties. It shrinks to its minimum size to fit the container, but does not grow to absorb any extra free space in the flex container. This is equivalent to setting", "device_ip": "172.25.78.36", "proxy_ip": "172.25.78.36", "status": "active", "id": 4,"port": "4000", "name": "mtso"},
|
||||
{"description": "The item is sized according to its width and height properties. It shrinks to its minimum size to fit the container, but does not grow to absorb any extra free space in the flex container. This is equivalent to setting", "device_ip": "172.25.78.36", "proxy_ip": "172.25.78.36", "status": "active", "id": 5,"port": "4000", "name": "as_dmps"}
|
||||
]
|
||||
79
users-manage/src/style.css
Normal file
79
users-manage/src/style.css
Normal file
@@ -0,0 +1,79 @@
|
||||
:root {
|
||||
font-family: Inter, system-ui, Avenir, Helvetica, Arial, sans-serif;
|
||||
line-height: 1.5;
|
||||
font-weight: 400;
|
||||
|
||||
color-scheme: light dark;
|
||||
color: rgba(255, 255, 255, 0.87);
|
||||
background-color: #242424;
|
||||
|
||||
font-synthesis: none;
|
||||
text-rendering: optimizeLegibility;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
}
|
||||
|
||||
a {
|
||||
font-weight: 500;
|
||||
color: #646cff;
|
||||
text-decoration: inherit;
|
||||
}
|
||||
a:hover {
|
||||
color: #535bf2;
|
||||
}
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
display: flex;
|
||||
place-items: center;
|
||||
min-width: 320px;
|
||||
min-height: 100vh;
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-size: 3.2em;
|
||||
line-height: 1.1;
|
||||
}
|
||||
|
||||
button {
|
||||
border-radius: 8px;
|
||||
border: 1px solid transparent;
|
||||
padding: 0.6em 1.2em;
|
||||
font-size: 1em;
|
||||
font-weight: 500;
|
||||
font-family: inherit;
|
||||
background-color: #1a1a1a;
|
||||
cursor: pointer;
|
||||
transition: border-color 0.25s;
|
||||
}
|
||||
button:hover {
|
||||
border-color: #646cff;
|
||||
}
|
||||
button:focus,
|
||||
button:focus-visible {
|
||||
outline: 4px auto -webkit-focus-ring-color;
|
||||
}
|
||||
|
||||
.card {
|
||||
padding: 2em;
|
||||
}
|
||||
|
||||
#app {
|
||||
max-width: 1280px;
|
||||
margin: 0 auto;
|
||||
padding: 2rem;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: light) {
|
||||
:root {
|
||||
color: #213547;
|
||||
background-color: #ffffff;
|
||||
}
|
||||
a:hover {
|
||||
color: #747bff;
|
||||
}
|
||||
button {
|
||||
background-color: #f9f9f9;
|
||||
}
|
||||
}
|
||||
25
users-manage/src/styles/2_tabulator-table.css
Normal file
25
users-manage/src/styles/2_tabulator-table.css
Normal file
@@ -0,0 +1,25 @@
|
||||
.tabulator .tabulator-header {
|
||||
@apply py-1;
|
||||
}
|
||||
|
||||
.tabulator .tabulator-header .tabulator-header-contents {
|
||||
@apply bg-gray-200 rounded shadow pr-2;
|
||||
}
|
||||
|
||||
|
||||
.tabulator .tabulator-headers .tabulator-col-resize-handle {
|
||||
@apply cursor-move my-auto bg-white rounded-full;
|
||||
}
|
||||
|
||||
|
||||
.tabulator-row {
|
||||
@apply rounded m-1 shadow-lg;
|
||||
}
|
||||
|
||||
.tabulator-row .tabulator-cell {
|
||||
@apply px-2 text-[13px];
|
||||
}
|
||||
|
||||
.tabulator-header-filter > input {
|
||||
@apply rounded-md;
|
||||
}
|
||||
2
users-manage/src/styles/index.css
Normal file
2
users-manage/src/styles/index.css
Normal file
@@ -0,0 +1,2 @@
|
||||
@import './tailwind_setup.css';
|
||||
@import './2_tabulator-table.css';
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user