Initial commit
This commit is contained in:
commit
bf2f060b94
212 files changed
+100448
No files matched your search
+25
@@ -0,0 +1,25 @@
|
|||||||
|
# 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?
|
||||||
|
proxy-ui-app/yarn.lock
|
||||||
@@ -0,0 +1,59 @@
|
|||||||
|
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
|
||||||
|
|
||||||
|
pre_proxy_hook:
|
||||||
|
@echo "Setting up pre-push hook..."
|
||||||
|
@rm -f $(HOOK_PATH)
|
||||||
|
@echo '#!/bin/sh' >> $(HOOK_PATH)
|
||||||
|
@echo 'echo "Running lint for proxy-ui"' >> $(HOOK_PATH)
|
||||||
|
@echo 'cd ./proxy-ui-app' >> $(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."
|
||||||
|
|
||||||
|
pre_users_hook:
|
||||||
|
@echo "Setting up pre-push hook..."
|
||||||
|
@rm -f $(HOOK_PATH)
|
||||||
|
@echo '#!/bin/sh' >> $(HOOK_PATH)
|
||||||
|
@echo 'echo "Running lint for proxy-ui"' >> $(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."
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
VITE_API_ADDR="http://172.25.78.64:8087"
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
module.exports = {
|
||||||
|
extends: [
|
||||||
|
"eslint:recommended",
|
||||||
|
"plugin:vue/vue3-recommended",
|
||||||
|
],
|
||||||
|
rules: {
|
||||||
|
"array-callback-return": "warn",
|
||||||
|
"for-direction": "error",
|
||||||
|
"getter-return": "warn",
|
||||||
|
"no-async-promise-executor": "warn",
|
||||||
|
"no-class-assign": "warn",
|
||||||
|
"no-multiple-empty-lines": ["error", {max: 2, "maxBOF": 0}],
|
||||||
|
"indent": ["error", 2],
|
||||||
|
"no-mixed-spaces-and-tabs": "error",
|
||||||
|
"vue/multi-word-component-names": "off",
|
||||||
|
"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/no-use-v-if-with-v-for": "off",
|
||||||
|
"vue/html-indent": ["error", 2, {
|
||||||
|
"attribute": 1,
|
||||||
|
"baseIndent": 1,
|
||||||
|
"closeBracket": 0,
|
||||||
|
"alignAttributesVertically": true,
|
||||||
|
"ignores": []
|
||||||
|
}]
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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?
|
||||||
Vendored
+3
@@ -0,0 +1,3 @@
|
|||||||
|
{
|
||||||
|
"recommendations": ["Vue.volar", "Vue.vscode-typescript-vue-plugin"]
|
||||||
|
}
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
# этап сборки (build stage)
|
||||||
|
FROM node:lts-alpine as build-stage
|
||||||
|
WORKDIR /app
|
||||||
|
COPY package*.json ./
|
||||||
|
RUN npm install
|
||||||
|
COPY . .
|
||||||
|
RUN npm run build
|
||||||
|
|
||||||
|
# этап production (production-stage)
|
||||||
|
FROM nginx:stable-alpine as production-stage
|
||||||
|
COPY --from=build-stage /app/dist /usr/share/nginx/html
|
||||||
|
EXPOSE 80
|
||||||
|
CMD ["nginx", "-g", "daemon off;"]
|
||||||
@@ -0,0 +1,47 @@
|
|||||||
|
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 proxy-ui-app"' >> $(HOOK_PATH)
|
||||||
|
@echo 'cd ./proxy-ui-app' >> $(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
|
||||||
@@ -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).
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
<!doctype html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8" />
|
||||||
|
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
|
||||||
|
<link rel="stylesheet" href="/css/remixicons/remixicon.css">
|
||||||
|
<link rel="stylesheet" href="/css/fonts.css">
|
||||||
|
<link rel="stylesheet" href="/css/global.css">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
|
<title>Vite + Vue</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div id="app"></div>
|
||||||
|
<script type="module" src="/src/main.js"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
@@ -0,0 +1,51 @@
|
|||||||
|
{
|
||||||
|
"name": "proxy-ui",
|
||||||
|
"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.2.1",
|
||||||
|
"flowbite-vue": "^0.1.2",
|
||||||
|
"postcss-import": "^16.0.0",
|
||||||
|
"ramda": "^0.29.1",
|
||||||
|
"ramda-vue": "^0.0.9",
|
||||||
|
"tippy.js": "^6.3.7",
|
||||||
|
"vue": "^3.3.11",
|
||||||
|
"vue-multiselect": "^3.0.0-beta.3",
|
||||||
|
"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.30.0",
|
||||||
|
"eslint-plugin-vue": "^9.22.0",
|
||||||
|
"happy-dom": "^13.6.2",
|
||||||
|
"postcss": "^8.4.34",
|
||||||
|
"postcss-advanced-variables": "^3.0.1",
|
||||||
|
"postcss-apply": "^0.12.0",
|
||||||
|
"postcss-nesting": "^12.0.2",
|
||||||
|
"tailwindcss": "^3.4.1",
|
||||||
|
"vite": "^5.0.8",
|
||||||
|
"vitest": "^1.2.2"
|
||||||
|
},
|
||||||
|
"resolutions": {
|
||||||
|
"strip-ansi": "6.0.1",
|
||||||
|
"string-width": "4.2.2",
|
||||||
|
"wrap-ansi": "7.0.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
export default {
|
||||||
|
plugins: {
|
||||||
|
"postcss-import": {},
|
||||||
|
"postcss-advanced-variables": {},
|
||||||
|
"tailwindcss/nesting": {},
|
||||||
|
tailwindcss: {},
|
||||||
|
autoprefixer: {},
|
||||||
|
},
|
||||||
|
}
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
@font-face {
|
||||||
|
font-family: Nunito;
|
||||||
|
src: url(../fonts/Nunito.ttf);
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
@@ -0,0 +1,156 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<title>remixicon(4.1.0)</title>
|
||||||
|
<meta name="viewport" content="width=device-width,minimum-scale=1.0,maximum-scale=1.0,user-scalable=no">
|
||||||
|
|
||||||
|
<meta name="description" content="Remix Icon is a set of neutral system symbols carefully crafted for designers and developers, each icon is designed based on 24×24 grid">
|
||||||
|
|
||||||
|
<meta name="keywords" content="remixicon,TTF,EOT,WOFF,WOFF2,SVG">
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<link rel="stylesheet" href="remixicon.css" />
|
||||||
|
|
||||||
|
<style>
|
||||||
|
*{margin: 0;padding: 0;list-style: none;}
|
||||||
|
body { color: #696969; font: 12px/1.5 tahoma, arial, \5b8b\4f53, sans-serif; }
|
||||||
|
a { color: #333; text-decoration: underline; }
|
||||||
|
a:hover { color: rgb(9, 73, 209); }
|
||||||
|
.header { color: #333; text-align: center; padding: 80px 0 60px 0; min-height: 153px; font-size: 14px; }
|
||||||
|
.header .logo svg { height: 120px; width: 120px; }
|
||||||
|
.header h1 { font-size: 42px; padding: 26px 0 10px 0; }
|
||||||
|
.header sup {font-size: 14px; margin: 8px 0 0 8px; position: absolute; color: #7b7b7b; }
|
||||||
|
.info {
|
||||||
|
color: #999;
|
||||||
|
font-weight: normal;
|
||||||
|
max-width: 346px;
|
||||||
|
margin: 0 auto;
|
||||||
|
padding: 20px 0;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.icons { max-width: 1190px; margin: 0 auto; }
|
||||||
|
.icons ul { text-align: center; }
|
||||||
|
.icons ul li {
|
||||||
|
vertical-align: top;
|
||||||
|
width: 120px;
|
||||||
|
display: inline-block;
|
||||||
|
text-align: center;
|
||||||
|
background-color: rgba(0,0,0,.02);
|
||||||
|
border-radius: 3px;
|
||||||
|
padding: 29px 0 10px 0;
|
||||||
|
margin-right: 10px;
|
||||||
|
margin-top: 10px;
|
||||||
|
transition: all 0.6s ease;
|
||||||
|
}
|
||||||
|
.icons ul li:hover { background-color: rgba(0,0,0,.06); }
|
||||||
|
.icons ul li:hover span { color: #3c75e4; opacity: 1; }
|
||||||
|
.icons ul li .unicode { color: #8c8c8c; opacity: 0.3; }
|
||||||
|
.icons ul li h4 {
|
||||||
|
font-weight: normal;
|
||||||
|
padding: 10px 0 5px 0;
|
||||||
|
display: block;
|
||||||
|
color: #8c8c8c;
|
||||||
|
font-size: 14px;
|
||||||
|
line-height: 12px;
|
||||||
|
opacity: 0.8;
|
||||||
|
}
|
||||||
|
.icons ul li:hover h4 { opacity: 1; }
|
||||||
|
.icons ul li svg { width: 24px; height: 24px; }
|
||||||
|
.icons ul li:hover { color: #3c75e4; }
|
||||||
|
.footer { text-align: center; padding: 10px 0 90px 0; }
|
||||||
|
.footer a { text-align: center; padding: 10px 0 90px 0; color: #696969;}
|
||||||
|
.footer a:hover { color: #0949d1; }
|
||||||
|
.links { text-align: center; padding: 50px 0 0 0; font-size: 14px; }
|
||||||
|
|
||||||
|
.icons ul li.class-icon { font-size: 21px; line-height: 21px; padding-bottom: 20px; }
|
||||||
|
.icons ul li.class-icon p{ font-size: 12px; }
|
||||||
|
.icons ul li.class-icon [class^="ri-"]{ font-size: 26px; }
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
</style>
|
||||||
|
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
|
||||||
|
<div class="header">
|
||||||
|
|
||||||
|
<div class="logo">
|
||||||
|
<a href="./"><svg width="160" height="32" viewBox="0 0 160 32" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M121.5 9.80005C123.33 9.80005 124.984 10.5797 126.165 11.8344L124.75 13.2496C123.929 12.3549 122.773 11.8 121.5 11.8C119.029 11.8 117 13.891 117 16.5C117 19.1091 119.029 21.2 121.5 21.2C122.773 21.2 123.929 20.6452 124.75 19.7505L126.165 21.1657C124.984 22.4204 123.33 23.2 121.5 23.2C117.91 23.2 115 20.2004 115 16.5C115 12.7997 117.91 9.80005 121.5 9.80005Z" fill="#1A2947"/>
|
||||||
|
<path d="M113 10V23H111V10H113Z" fill="#1A2947"/>
|
||||||
|
<path d="M154 23V9.99988H152L152 19.6019L144.997 9.99988L143 10.0039V23H145L145 13.3969L152 23H154Z" fill="#1A2947"/>
|
||||||
|
<path d="M68 10V12H60V15.5H67V17.5H60V21H68V23H58V10H68Z" fill="#1A2947"/>
|
||||||
|
<path d="M82 10L77.4999 17.201L73 10H71V23H73V13.775L77.4999 20.975L82 13.773V23H84V10H82Z" fill="#1A2947"/>
|
||||||
|
<path d="M89 10V23H87V10H89Z" fill="#1A2947"/>
|
||||||
|
<path d="M96.9999 14.7565L93.6689 9.99951H91.2279L95.7789 16.4995L91.2289 22.9995H93.6689L96.9999 18.2435L100.33 22.9995H102.772L98.2209 16.4995L102.773 9.99951H100.33L96.9999 14.7565Z" fill="#1A2947"/>
|
||||||
|
<path fill-rule="evenodd" clip-rule="evenodd" d="M141 16.5C141 12.7997 138.09 9.80005 134.5 9.80005C130.91 9.80005 128 12.7997 128 16.5C128 20.2004 130.91 23.2 134.5 23.2C138.09 23.2 141 20.2004 141 16.5ZM130 16.5C130 13.891 132.029 11.8 134.5 11.8C136.971 11.8 139 13.891 139 16.5C139 19.1091 136.971 21.2 134.5 21.2C132.029 21.2 130 19.1091 130 16.5Z" fill="#1A2947"/>
|
||||||
|
<path fill-rule="evenodd" clip-rule="evenodd" d="M51 10C53.2091 10 55 11.7909 55 14C55 15.7175 53.9176 17.1821 52.3977 17.749L55.1901 22.999H52.9251L50.267 18H46V23H44V10H51ZM46 11.999V15.999L51 16C52.1046 16 53 15.1046 53 14C53 12.8955 52.1 12 51 12L46 11.999Z" fill="#1A2947"/>
|
||||||
|
<path fill-rule="evenodd" clip-rule="evenodd" d="M19.2887 3.00389C19.1928 3.0013 19.0965 3 19 3H3V24H19C24.799 24 29.5 19.299 29.5 13.5C29.5 12.7108 29.4129 11.942 29.2479 11.2025C28.2772 11.7119 27.1722 12 26 12C22.134 12 19 8.86599 19 5C19 4.30645 19.1009 3.63646 19.2887 3.00389Z" fill="url(#paint0_linear_960_1054)"/>
|
||||||
|
<g filter="url(#filter0_b_960_1054)">
|
||||||
|
<path fill-rule="evenodd" clip-rule="evenodd" d="M3 3L29 29H3V3Z" fill="url(#paint1_linear_960_1054)"/>
|
||||||
|
</g>
|
||||||
|
<circle cx="26" cy="5" r="4" fill="url(#paint2_linear_960_1054)"/>
|
||||||
|
<defs>
|
||||||
|
<filter id="filter0_b_960_1054" x="-2.43656" y="-2.43656" width="36.8731" height="36.8731" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
|
||||||
|
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
|
||||||
|
<feGaussianBlur in="BackgroundImage" stdDeviation="2.71828"/>
|
||||||
|
<feComposite in2="SourceAlpha" operator="in" result="effect1_backgroundBlur_960_1054"/>
|
||||||
|
<feBlend mode="normal" in="SourceGraphic" in2="effect1_backgroundBlur_960_1054" result="shape"/>
|
||||||
|
</filter>
|
||||||
|
<linearGradient id="paint0_linear_960_1054" x1="3" y1="3" x2="3" y2="24" gradientUnits="userSpaceOnUse">
|
||||||
|
<stop stop-color="#05A0FA"/>
|
||||||
|
<stop offset="1" stop-color="#4785FF"/>
|
||||||
|
</linearGradient>
|
||||||
|
<linearGradient id="paint1_linear_960_1054" x1="3" y1="3" x2="3" y2="29" gradientUnits="userSpaceOnUse">
|
||||||
|
<stop stop-color="#1A8CFF"/>
|
||||||
|
<stop offset="1" stop-color="#0557FA"/>
|
||||||
|
</linearGradient>
|
||||||
|
<linearGradient id="paint2_linear_960_1054" x1="22" y1="1" x2="22" y2="9" gradientUnits="userSpaceOnUse">
|
||||||
|
<stop stop-color="#FF463A"/>
|
||||||
|
<stop offset="1" stop-color="#FF1F5A"/>
|
||||||
|
</linearGradient>
|
||||||
|
</defs>
|
||||||
|
</svg>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h1>remixicon(4.1.0)<sup>1.0.0</sup></h1>
|
||||||
|
<div class="info">
|
||||||
|
Remix Icon is a set of neutral system symbols carefully crafted for designers and developers, each icon is designed based on 24×24 grid
|
||||||
|
</div>
|
||||||
|
<p>
|
||||||
|
|
||||||
|
<a href="https://github.com/Remix-Design/remixicon.git">GitHub</a> ·
|
||||||
|
|
||||||
|
<a href="index.html">Font Class</a> ·
|
||||||
|
|
||||||
|
<a href="unicode.html">Unicode</a>
|
||||||
|
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div class="icons">
|
||||||
|
<ul>
|
||||||
|
<li class="class-icon"><i class="ri-24-hours-fill"></i><p class="name">24-hours-fill</p></li><li class="class-icon"><i class="ri-24-hours-line"></i><p class="name">24-hours-line</p></li><li class="class-icon"><i class="ri-4k-fill"></i><p class="name">4k-fill</p></li><li class="class-icon"><i class="ri-4k-line"></i><p class="name">4k-line</p></li><li class="class-icon"><i class="ri-a-b"></i><p class="name">a-b</p></li><li class="class-icon"><i class="ri-account-box-fill"></i><p class="name">account-box-fill</p></li><li class="class-icon"><i class="ri-account-box-line"></i><p class="name">account-box-line</p></li><li class="class-icon"><i class="ri-account-circle-fill"></i><p class="name">account-circle-fill</p></li><li class="class-icon"><i class="ri-account-circle-line"></i><p class="name">account-circle-line</p></li><li class="class-icon"><i class="ri-account-pin-box-fill"></i><p class="name">account-pin-box-fill</p></li><li class="class-icon"><i class="ri-account-pin-box-line"></i><p class="name">account-pin-box-line</p></li><li class="class-icon"><i class="ri-account-pin-circle-fill"></i><p class="name">account-pin-circle-fill</p></li><li class="class-icon"><i class="ri-account-pin-circle-line"></i><p class="name">account-pin-circle-line</p></li><li class="class-icon"><i class="ri-add-box-fill"></i><p class="name">add-box-fill</p></li><li class="class-icon"><i class="ri-add-box-line"></i><p class="name">add-box-line</p></li><li class="class-icon"><i class="ri-add-circle-fill"></i><p class="name">add-circle-fill</p></li><li class="class-icon"><i class="ri-add-circle-line"></i><p class="name">add-circle-line</p></li><li class="class-icon"><i class="ri-add-fill"></i><p class="name">add-fill</p></li><li class="class-icon"><i class="ri-add-line"></i><p class="name">add-line</p></li><li class="class-icon"><i class="ri-admin-fill"></i><p class="name">admin-fill</p></li><li class="class-icon"><i class="ri-admin-line"></i><p class="name">admin-line</p></li><li class="class-icon"><i class="ri-advertisement-fill"></i><p class="name">advertisement-fill</p></li><li class="class-icon"><i class="ri-advertisement-line"></i><p class="name">advertisement-line</p></li><li class="class-icon"><i class="ri-airplay-fill"></i><p class="name">airplay-fill</p></li><li class="class-icon"><i class="ri-airplay-line"></i><p class="name">airplay-line</p></li><li class="class-icon"><i class="ri-alarm-fill"></i><p class="name">alarm-fill</p></li><li class="class-icon"><i class="ri-alarm-line"></i><p class="name">alarm-line</p></li><li class="class-icon"><i class="ri-alarm-warning-fill"></i><p class="name">alarm-warning-fill</p></li><li class="class-icon"><i class="ri-alarm-warning-line"></i><p class="name">alarm-warning-line</p></li><li class="class-icon"><i class="ri-album-fill"></i><p class="name">album-fill</p></li><li class="class-icon"><i class="ri-album-line"></i><p class="name">album-line</p></li><li class="class-icon"><i class="ri-alert-fill"></i><p class="name">alert-fill</p></li><li class="class-icon"><i class="ri-alert-line"></i><p class="name">alert-line</p></li><li class="class-icon"><i class="ri-aliens-fill"></i><p class="name">aliens-fill</p></li><li class="class-icon"><i class="ri-aliens-line"></i><p class="name">aliens-line</p></li><li class="class-icon"><i class="ri-align-bottom"></i><p class="name">align-bottom</p></li><li class="class-icon"><i class="ri-align-center"></i><p class="name">align-center</p></li><li class="class-icon"><i class="ri-align-justify"></i><p class="name">align-justify</p></li><li class="class-icon"><i class="ri-align-left"></i><p class="name">align-left</p></li><li class="class-icon"><i class="ri-align-right"></i><p class="name">align-right</p></li><li class="class-icon"><i class="ri-align-top"></i><p class="name">align-top</p></li><li class="class-icon"><i class="ri-align-vertically"></i><p class="name">align-vertically</p></li><li class="class-icon"><i class="ri-alipay-fill"></i><p class="name">alipay-fill</p></li><li class="class-icon"><i class="ri-alipay-line"></i><p class="name">alipay-line</p></li><li class="class-icon"><i class="ri-amazon-fill"></i><p class="name">amazon-fill</p></li><li class="class-icon"><i class="ri-amazon-line"></i><p class="name">amazon-line</p></li><li class="class-icon"><i class="ri-anchor-fill"></i><p class="name">anchor-fill</p></li><li class="class-icon"><i class="ri-anchor-line"></i><p class="name">anchor-line</p></li><li class="class-icon"><i class="ri-ancient-gate-fill"></i><p class="name">ancient-gate-fill</p></li><li class="class-icon"><i class="ri-ancient-gate-line"></i><p class="name">ancient-gate-line</p></li><li class="class-icon"><i class="ri-ancient-pavilion-fill"></i><p class="name">ancient-pavilion-fill</p></li><li class="class-icon"><i class="ri-ancient-pavilion-line"></i><p class="name">ancient-pavilion-line</p></li><li class="class-icon"><i class="ri-android-fill"></i><p class="name">android-fill</p></li><li class="class-icon"><i class="rLine truncated
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
<p class="links">
|
||||||
|
|
||||||
|
<a href="https://github.com/Remix-Design/remixicon.git">GitHub</a> ·
|
||||||
|
|
||||||
|
<a href="index.html">Font Class</a> ·
|
||||||
|
|
||||||
|
<a href="unicode.html">Unicode</a>
|
||||||
|
|
||||||
|
</p>
|
||||||
|
<div class="footer">
|
||||||
|
Licensed under MIT. (Yes it's free and <a href="https://github.com/Remix-Design/remixicon">open-sourced</a>)
|
||||||
|
<div><a target="_blank" href="https://github.com/jaywcjlove/svgtofont">Created By svgtofont</a></div>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
|
||||||
|
</html>
|
||||||
File diff suppressed because it is too large.
Load diff
Binary file not shown.
@@ -0,0 +1 @@
|
|||||||
|
{"24-hours-fill":{"path":["M12.0049 13.0028C13.6617 13.0028 15.0049 14.346 15.0049 16.0028C15.0049 16.8519 14.6521 17.6187 14.0851 18.1645L12.175 20.0024L15.0049 20.0028V22.0028H9.00488L9.00398 20.2784L12.6983 16.7234C12.8874 16.5411 13.0049 16.2857 13.0049 16.0028C13.0049 15.4505 12.5572 15.0028 12.0049 15.0028C11.4526 15.0028 11.0049 15.4505 11.0049 16.0028H9.00488C9.00488 14.346 10.348 13.0028 12.0049 13.0028ZM18.0049 13.0028V17.0028H20.0049V13.0028H22.0049V22.0028H20.0049V19.0028H16.0049V13.0028H18.0049ZM4.00488 12.0028C4.00488 14.5294 5.17612 16.7824 7.00527 18.2485L7.0049 20.665C4.01588 18.9359 2.00488 15.7042 2.00488 12.0028H4.00488ZM12.0049 2.00281C17.1902 2.00281 21.4537 5.94943 21.9555 11.0027L19.943 11.0029C19.4509 7.05652 16.0845 4.00281 12.0049 4.00281C9.54102 4.00281 7.33731 5.11664 5.8698 6.86824L8.00488 9.00281H2.00488V3.00281L4.45144 5.44929C6.28491 3.3379 8.98898 2.00281 12.0049 2.00281Z"],"unicode":"","glyph":"M600.0005000000001 370.0002500000001C682.8405000000001 370.0002500000001 750.0005000000001 302.8402500000001 750.0005000000001 220.0002500000001C750.0005000000001 177.54525 732.3605000000001 139.20525 704.0105000000001 111.91525L608.5055000000001 20.0202499999999L750.0005000000001 20.0002500000001V-79.9997500000001H449.9995000000002L449.9545000000001 6.22025L634.6705000000001 183.97025C644.1255000000001 193.08525 650.0005000000001 205.8552500000001 650.0005000000001 220.0002500000001C650.0005000000001 247.6152500000001 627.6155000000001 270.0002500000001 600.0005000000001 270.0002500000001C572.3855000000001 270.0002500000001 550.0005000000001 247.6152500000001 550.0005000000001 220.0002500000001H449.9995000000002C449.9995000000002 302.8402500000001 517.1555000000002 370.0002500000001 600.0005000000001 370.0002500000001zM900.0005000000001 370.0002500000001V170.0002500000001H1000.0005000000002V370.0002500000001H1100.0004999999999V-79.9997500000001H1000.0005000000002V70.0002500000001H800.0005000000001V370.0002500000001H900.0005000000001zM199.9995000000001 420.0002500000001C199.9995000000001 293.67025 258.5615 181.02025 350.0190000000001 107.7152500000001L350.0005000000001 -13.10975C200.5495000000001 73.3452500000001 99.9995000000001 234.93025 99.9995000000001 420.0002500000001H199.9995000000001zM600.0005000000001 919.99975C859.2655000000001 919.99975 1072.4405000000002 722.6687499999999 1097.5305 470.00525L996.9055000000002 469.9952500000001C972.3005000000002 667.31425 803.9805 819.9997500000001 600.0005000000001 819.9997500000001C476.8065 819.9997500000001 366.6210000000001 764.30825 293.2455000000001 676.72825L399.9995000000002 569.9997500000001H99.9995000000001V869.9997500000001L222.3275000000001 747.67575C314.0010000000001 853.24525 449.2045000000001 919.99975 600.0005000000001 919.99975z","horizAdvX":"1200"},"24-hours-line":{"path":["M12.0049 13.0028C13.6617 13.0028 15.0049 14.346 15.0049 16.0028C15.0049 16.8519 14.6521 17.6187 14.0851 18.1645L12.175 20.0024L15.0049 20.0028V22.0028H9.00488L9.00398 20.2784L12.6983 16.7234C12.8874 16.5411 13.0049 16.2857 13.0049 16.0028C13.0049 15.4505 12.5572 15.0028 12.0049 15.0028C11.4526 15.0028 11.0049 15.4505 11.0049 16.0028H9.00488C9.00488 14.346 10.348 13.0028 12.0049 13.0028ZM18.0049 13.0028V17.0028H20.0049V13.0028H22.0049V22.0028H20.0049V19.0028H16.0049V13.0028H18.0049ZM4.00488 12.0028C4.00488 14.5294 5.17612 16.7824 7.00527 18.2485L7.0049 20.665C4.01588 18.9359 2.00488 15.7042 2.00488 12.0028H4.00488ZM12.0049 2.00281C17.1902 2.00281 21.4537 5.94943 21.9555 11.0027L19.943 11.0029C19.4509 7.05652 16.0845 4.00281 12.0049 4.00281C9.2551 4.00281 6.82935 5.39014 5.3894 7.50305L8.00488 7.50281V9.50281H2.00488V3.50281H4.00488L4.00477 6.00198C5.82922 3.57356 8.73362 2.00281 12.0049 2.00281Z"],"unicode":"","glyph":"M600.0005000000001 370.0002500000001C682.8405000000001 370.0002500000001 750.0005000000001 302.8402500000001 750.0005000000001 220.0002500000001C750.0005000000001 177.54525 732.3605000000001 139.20525 704.0105000000001 111.91525L608.5055000000001 20.0202499999999L750.0005000000001 20.0002500000001V-79.9997500000001H449.9995000000002L449.9545000000001 6.22025L634.6705000000001 183.97025C644.1255000000001 193.08525 650.0005000000001 205.8552500000001 650.0005000000001 220.0002500000001C650.0005000000001 247.6152500000001 627.6155000000001 270.0002500000001 600.0005000000001 270.0002500000001C572.3855000000001 270.0002500000001 550.0005000000001 247.6152500000001 550.0005000000001 220.0002500000001H449.9995000000002C449.9995000000002 302.8402500000001 517.1555000000002 370.0002500000001 600.0005000000001 370.0002500000001zM900.0005000000001 370.0002500000001V170.0002500000001H1000.0005000000002V370.0002500000001H1100.0004999999999V-79.9997500000001H1000.0005000000002V70.0002500000001H800.0005000000001V370.0002500000001H900.0005000000001zM199.9995000000001 420.0002500000001C199.9995000000001 293.67025 258.5615 181.02025 350.0190000000001 107.7152500000001L350.0005000000001 -13.10975C200.5495000000001 73.3452500000001 99.9995Line truncated
|
||||||
File diff suppressed because it is too large.
Load diff
File diff suppressed because it is too large.
Load diff
File diff suppressed because it is too large.
Load diff
File diff suppressed because it is too large.
Load diff
File diff suppressed because it is too large.
Load diff
|
After Width: | Height: | Size: 2.3 MiB |
@@ -0,0 +1,11 @@
|
|||||||
|
<!--
|
||||||
|
Remix Icon v4.1.0
|
||||||
|
https://remixicon.com
|
||||||
|
https://github.com/Remix-Design/RemixIcon
|
||||||
|
|
||||||
|
Copyright RemixIcon.com
|
||||||
|
Released under the Apache License Version 2.0
|
||||||
|
|
||||||
|
Date: 2024-01-14
|
||||||
|
-->
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="0" height="0" style="display:none;"><symbol viewBox="0 0 24 24" id="ri-24-hours-fill"><path d="M12.0049 13.0028C13.6617 13.0028 15.0049 14.346 15.0049 16.0028C15.0049 16.8519 14.6521 17.6187 14.0851 18.1645L12.175 20.0024L15.0049 20.0028V22.0028H9.00488L9.00398 20.2784L12.6983 16.7234C12.8874 16.5411 13.0049 16.2857 13.0049 16.0028C13.0049 15.4505 12.5572 15.0028 12.0049 15.0028C11.4526 15.0028 11.0049 15.4505 11.0049 16.0028H9.00488C9.00488 14.346 10.348 13.0028 12.0049 13.0028ZM18.0049 13.0028V17.0028H20.0049V13.0028H22.0049V22.0028H20.0049V19.0028H16.0049V13.0028H18.0049ZM4.00488 12.0028C4.00488 14.5294 5.17612 16.7824 7.00527 18.2485L7.0049 20.665C4.01588 18.9359 2.00488 15.7042 2.00488 12.0028H4.00488ZM12.0049 2.00281C17.1902 2.00281 21.4537 5.94943 21.9555 11.0027L19.943 11.0029C19.4509 7.05652 16.0845 4.00281 12.0049 4.00281C9.54102 4.00281 7.33731 5.11664 5.8698 6.86824L8.00488 9.00281H2.00488V3.00281L4.45144 5.44929C6.28491 3.3379 8.98898 2.00281 12.0049 2.00281Z"></path></symbol><symbol viewBox="0 0 24 24" id="ri-24-hours-line"><path d="M12.0049 13.0028C13.6617 13.0028 15.0049 14.346 15.0049 16.0028C15.0049 16.8519 14.6521 17.6187 14.0851 18.1645L12.175 20.0024L15.0049 20.0028V22.0028H9.00488L9.00398 20.2784L12.6983 16.7234C12.8874 16.5411 13.0049 16.2857 13.0049 16.0028C13.0049 15.4505 12.5572 15.0028 12.0049 15.0028C11.4526 15.0028 11.0049 15.4505 11.0049 16.0028H9.00488C9.00488 14.346 10.348 13.0028 12.0049 13.0028ZM18.0049 13.0028V17.0028H20.0049V13.0028H22.0049V22.0028H20.0049V19.0028H16.0049V13.0028H18.0049ZM4.00488 12.0028C4.00488 14.5294 5.17612 16.7824 7.00527 18.2485L7.0049 20.665C4.01588 18.9359 2.00488 15.7042 2.00488 12.0028H4.00488ZM12.0049 2.00281C17.1902 2.00281 21.4537 5.94943 21.9555 11.0027L19.943 11.0029C19.4509 7.05652 16.0845 4.00281 12.0049 4.00281C9.2551 4.00281 6.82935 5.39014 5.3894 7.50305L8.00488 7.50281V9.50281H2.00488V3.50281H4.00488L4.00477 6.00198C5.82922 3.57356 8.73362 2.00281 12.0049 2.00281Z"></path></symbol><symbol viewBox="0 0 24 24" id="ri-4k-fill"><path d="M3 3H21C21.5523 3 22 3.44772 22 4V20C22 20.5523 21.5523 21 21 21H3C2.44772 21 2 20.5523 2 20V4C2 3.44772 2.44772 3 3 3ZM11.5 13.5V12H10.5V9H9V12H7.5V9H6V13.5H9V15H10.5V13.5H11.5ZM18 15L15.75 12L18 9H16.25L14.5 11.25V9H13V15H14.5V12.75L16.25 15H18Z"></path></symbol><symbol viewBox="0 0 24 24" id="ri-4k-line"><path d="M4 5V19H20V5H4ZM3 3H21C21.5523 3 22 3.44772 22 4V20C22 20.5523 21.5523 21 21 21H3C2.44772 21 2 20.5523 2 20V4C2 3.44772 2.44772 3 3 3ZM11.5 13.5H10.5V15H9V13.5H6V9H7.5V12H9V9H10.5V12H11.5V13.5ZM18 15H16.25L14.5 12.75V15H13V9H14.5V11.25L16.25 9H18L15.75 12L18 15Z"></path></symbol><symbol viewBox="0 0 24 24" id="ri-a-b"><path d="M5 15V17C5 18.0544 5.95 19 7 19H10V21H7C4.79086 21 3 19.2091 3 17V15H5ZM18 10L22.4 21H20.245L19.044 18H14.954L13.755 21H11.601L16 10H18ZM17 12.8852L15.753 16H18.245L17 12.8852ZM3 3H9C10.6569 3 12 4.34315 12 6C12 6.76878 11.7108 7.47002 11.2353 8.00088C11.7112 8.53084 12 9.23169 12 10C12 11.6569 10.6569 13 9 13H3V3ZM9 9H5V11H9C9.55228 11 10 10.5523 10 10C10 9.45 9.55 9 9 9ZM17 3C19.2091 3 21 4.79086 21 7V9H19V7C19 5.89543 18.1046 5 17 5H14V3H17ZM9 5H5V7H9C9.55228 7 10 6.55228 10 6C10 5.45 9.55 5 9 5Z"></path></symbol><symbol viewBox="0 0 24 24" id="ri-account-box-fill"><path d="M3 4.99509C3 3.89323 3.89262 3 4.99509 3H19.0049C20.1068 3 21 3.89262 21 4.99509V19.0049C21 20.1068 20.1074 21 19.0049 21H4.99509C3.89323 21 3 20.1074 3 19.0049V4.99509ZM6.35687 18H17.8475C16.5825 16.1865 14.4809 15 12.1022 15C9.72344 15 7.62182 16.1865 6.35687 18ZM12 13C13.933 13 15.5 11.433 15.5 9.5C15.5 7.567 13.933 6 12 6C10.067 6 8.5 7.567 8.5 9.5C8.5 11.433 10.067 13 12 13Z"></path></symbol><symbol viewBox="0 0 24 24" id="ri-account-box-line"><path d="M3 4.99509C3 3.89323 3.89262 3 4.99509 3H19.0049C20.1068 3 21 3.89262 21 4.99509V19.0049C21 20.1068 20.1074 21 19.0049 21H4.99509C3.89323 21 3 20.1074 3 19.0049V4.99509ZM5 5V19H19V5H5ZM7.97216 18.1808C7.35347 17.9129 6.76719 17.5843 6.22083 17.2024C7.46773 15.2753 9.63602 14 12.1022 14C14.5015 14 16.6189 15.2071 17.8801 17.0472C17.3438 17.4436 16.7664 17.7877 16.1555 18.0718C15.2472 16.8166 13.77 16 12.1022 16C10.3865 16 8.87271 16.8641 7.97216 18.1808ZM12 13C10.067 13 8.5 11.433 8.5 9.5C8.5 7.567 10.067 6 12 6C13.933 6 15.5 7.567 15.5 9.5C15.5 11.433 13.933 13 12 13ZM12 11C12.8284 11 13.5 10.3284 13.5 9.5C13.5 8.67157 12.8284 8 12 8C11.1716 8 10.5 8.67157 10.5 9.5C10.5 10.3284 11.1716 11 12 11Z"></path></symbol><symbol viewBox="0 0 24 24" id="ri-account-circle-fill"><path d="M12 2C17.52 2 22 6.48 22 12C22 17.52 17.52 22 12 22C6.48 22 2 17.52 2 12C2 6.48 6.48 2 12 2ZM6.02332 15.4163C7.49083 17.6069 9.69511 19 12.1597 19C14.6243 19 16.8286 17.6069 18.2961 15.4163C16.6885 13.9172 14.5312 13 12.1597 13C9.78821 13 7.63095 13.9172 6.02332 15.4163ZM12 11C13.6569 11 15 9.65685 15 8C15 6.34315 13.6569 5 12 5C10.3431 5 9 6.34315 9 8C9 9.65685 10.3431 11 12 11Z"></paLine truncated
|
||||||
|
After Width: | Height: | Size: 1.4 MiB |
Binary file not shown.
Binary file not shown.
Binary file not shown.
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,175 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<title>remixicon(4.1.0)</title>
|
||||||
|
<meta name="viewport" content="width=device-width,minimum-scale=1.0,maximum-scale=1.0,user-scalable=no">
|
||||||
|
|
||||||
|
<meta name="description" content="Remix Icon is a set of neutral system symbols carefully crafted for designers and developers, each icon is designed based on 24×24 grid">
|
||||||
|
|
||||||
|
<meta name="keywords" content="remixicon,TTF,EOT,WOFF,WOFF2,SVG">
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<style>
|
||||||
|
*{margin: 0;padding: 0;list-style: none;}
|
||||||
|
body { color: #696969; font: 12px/1.5 tahoma, arial, \5b8b\4f53, sans-serif; }
|
||||||
|
a { color: #333; text-decoration: underline; }
|
||||||
|
a:hover { color: rgb(9, 73, 209); }
|
||||||
|
.header { color: #333; text-align: center; padding: 80px 0 60px 0; min-height: 153px; font-size: 14px; }
|
||||||
|
.header .logo svg { height: 120px; width: 120px; }
|
||||||
|
.header h1 { font-size: 42px; padding: 26px 0 10px 0; }
|
||||||
|
.header sup {font-size: 14px; margin: 8px 0 0 8px; position: absolute; color: #7b7b7b; }
|
||||||
|
.info {
|
||||||
|
color: #999;
|
||||||
|
font-weight: normal;
|
||||||
|
max-width: 346px;
|
||||||
|
margin: 0 auto;
|
||||||
|
padding: 20px 0;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.icons { max-width: 1190px; margin: 0 auto; }
|
||||||
|
.icons ul { text-align: center; }
|
||||||
|
.icons ul li {
|
||||||
|
vertical-align: top;
|
||||||
|
width: 120px;
|
||||||
|
display: inline-block;
|
||||||
|
text-align: center;
|
||||||
|
background-color: rgba(0,0,0,.02);
|
||||||
|
border-radius: 3px;
|
||||||
|
padding: 29px 0 10px 0;
|
||||||
|
margin-right: 10px;
|
||||||
|
margin-top: 10px;
|
||||||
|
transition: all 0.6s ease;
|
||||||
|
}
|
||||||
|
.icons ul li:hover { background-color: rgba(0,0,0,.06); }
|
||||||
|
.icons ul li:hover span { color: #3c75e4; opacity: 1; }
|
||||||
|
.icons ul li .unicode { color: #8c8c8c; opacity: 0.3; }
|
||||||
|
.icons ul li h4 {
|
||||||
|
font-weight: normal;
|
||||||
|
padding: 10px 0 5px 0;
|
||||||
|
display: block;
|
||||||
|
color: #8c8c8c;
|
||||||
|
font-size: 14px;
|
||||||
|
line-height: 12px;
|
||||||
|
opacity: 0.8;
|
||||||
|
}
|
||||||
|
.icons ul li:hover h4 { opacity: 1; }
|
||||||
|
.icons ul li svg { width: 24px; height: 24px; }
|
||||||
|
.icons ul li:hover { color: #3c75e4; }
|
||||||
|
.footer { text-align: center; padding: 10px 0 90px 0; }
|
||||||
|
.footer a { text-align: center; padding: 10px 0 90px 0; color: #696969;}
|
||||||
|
.footer a:hover { color: #0949d1; }
|
||||||
|
.links { text-align: center; padding: 50px 0 0 0; font-size: 14px; }
|
||||||
|
|
||||||
|
|
||||||
|
.icons ul .unicode-icon span { display: block; }
|
||||||
|
.icons ul .unicode-icon h4 { font-size: 12px; }
|
||||||
|
@font-face {
|
||||||
|
font-family: "remixicon";
|
||||||
|
src: url("remixicon.eot");
|
||||||
|
/* IE9*/
|
||||||
|
src: url("remixicon.eot#iefix") format("embedded-opentype"),
|
||||||
|
/* IE6-IE8 */
|
||||||
|
url("remixicon.woff2?1705244689829") format("woff2"),
|
||||||
|
url("remixicon.woff?1705244689829") format("woff"),
|
||||||
|
/* chrome, firefox */
|
||||||
|
url("remixicon.ttf?1705244689829") format("truetype"),
|
||||||
|
/* chrome, firefox, opera, Safari, Android, iOS 4.2+*/
|
||||||
|
url("remixicon.svg#remixicon?1705244689829") format("svg");
|
||||||
|
/* iOS 4.1- */
|
||||||
|
}
|
||||||
|
.iconfont {
|
||||||
|
font-family: "remixicon" !important;
|
||||||
|
font-size: 26px;
|
||||||
|
font-style: normal;
|
||||||
|
-webkit-font-smoothing: antialiased;
|
||||||
|
-webkit-text-stroke-width: 0.2px;
|
||||||
|
-moz-osx-font-smoothing: grayscale;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
</style>
|
||||||
|
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
|
||||||
|
<div class="header">
|
||||||
|
|
||||||
|
<div class="logo">
|
||||||
|
<a href="./"><svg width="160" height="32" viewBox="0 0 160 32" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M121.5 9.80005C123.33 9.80005 124.984 10.5797 126.165 11.8344L124.75 13.2496C123.929 12.3549 122.773 11.8 121.5 11.8C119.029 11.8 117 13.891 117 16.5C117 19.1091 119.029 21.2 121.5 21.2C122.773 21.2 123.929 20.6452 124.75 19.7505L126.165 21.1657C124.984 22.4204 123.33 23.2 121.5 23.2C117.91 23.2 115 20.2004 115 16.5C115 12.7997 117.91 9.80005 121.5 9.80005Z" fill="#1A2947"/>
|
||||||
|
<path d="M113 10V23H111V10H113Z" fill="#1A2947"/>
|
||||||
|
<path d="M154 23V9.99988H152L152 19.6019L144.997 9.99988L143 10.0039V23H145L145 13.3969L152 23H154Z" fill="#1A2947"/>
|
||||||
|
<path d="M68 10V12H60V15.5H67V17.5H60V21H68V23H58V10H68Z" fill="#1A2947"/>
|
||||||
|
<path d="M82 10L77.4999 17.201L73 10H71V23H73V13.775L77.4999 20.975L82 13.773V23H84V10H82Z" fill="#1A2947"/>
|
||||||
|
<path d="M89 10V23H87V10H89Z" fill="#1A2947"/>
|
||||||
|
<path d="M96.9999 14.7565L93.6689 9.99951H91.2279L95.7789 16.4995L91.2289 22.9995H93.6689L96.9999 18.2435L100.33 22.9995H102.772L98.2209 16.4995L102.773 9.99951H100.33L96.9999 14.7565Z" fill="#1A2947"/>
|
||||||
|
<path fill-rule="evenodd" clip-rule="evenodd" d="M141 16.5C141 12.7997 138.09 9.80005 134.5 9.80005C130.91 9.80005 128 12.7997 128 16.5C128 20.2004 130.91 23.2 134.5 23.2C138.09 23.2 141 20.2004 141 16.5ZM130 16.5C130 13.891 132.029 11.8 134.5 11.8C136.971 11.8 139 13.891 139 16.5C139 19.1091 136.971 21.2 134.5 21.2C132.029 21.2 130 19.1091 130 16.5Z" fill="#1A2947"/>
|
||||||
|
<path fill-rule="evenodd" clip-rule="evenodd" d="M51 10C53.2091 10 55 11.7909 55 14C55 15.7175 53.9176 17.1821 52.3977 17.749L55.1901 22.999H52.9251L50.267 18H46V23H44V10H51ZM46 11.999V15.999L51 16C52.1046 16 53 15.1046 53 14C53 12.8955 52.1 12 51 12L46 11.999Z" fill="#1A2947"/>
|
||||||
|
<path fill-rule="evenodd" clip-rule="evenodd" d="M19.2887 3.00389C19.1928 3.0013 19.0965 3 19 3H3V24H19C24.799 24 29.5 19.299 29.5 13.5C29.5 12.7108 29.4129 11.942 29.2479 11.2025C28.2772 11.7119 27.1722 12 26 12C22.134 12 19 8.86599 19 5C19 4.30645 19.1009 3.63646 19.2887 3.00389Z" fill="url(#paint0_linear_960_1054)"/>
|
||||||
|
<g filter="url(#filter0_b_960_1054)">
|
||||||
|
<path fill-rule="evenodd" clip-rule="evenodd" d="M3 3L29 29H3V3Z" fill="url(#paint1_linear_960_1054)"/>
|
||||||
|
</g>
|
||||||
|
<circle cx="26" cy="5" r="4" fill="url(#paint2_linear_960_1054)"/>
|
||||||
|
<defs>
|
||||||
|
<filter id="filter0_b_960_1054" x="-2.43656" y="-2.43656" width="36.8731" height="36.8731" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
|
||||||
|
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
|
||||||
|
<feGaussianBlur in="BackgroundImage" stdDeviation="2.71828"/>
|
||||||
|
<feComposite in2="SourceAlpha" operator="in" result="effect1_backgroundBlur_960_1054"/>
|
||||||
|
<feBlend mode="normal" in="SourceGraphic" in2="effect1_backgroundBlur_960_1054" result="shape"/>
|
||||||
|
</filter>
|
||||||
|
<linearGradient id="paint0_linear_960_1054" x1="3" y1="3" x2="3" y2="24" gradientUnits="userSpaceOnUse">
|
||||||
|
<stop stop-color="#05A0FA"/>
|
||||||
|
<stop offset="1" stop-color="#4785FF"/>
|
||||||
|
</linearGradient>
|
||||||
|
<linearGradient id="paint1_linear_960_1054" x1="3" y1="3" x2="3" y2="29" gradientUnits="userSpaceOnUse">
|
||||||
|
<stop stop-color="#1A8CFF"/>
|
||||||
|
<stop offset="1" stop-color="#0557FA"/>
|
||||||
|
</linearGradient>
|
||||||
|
<linearGradient id="paint2_linear_960_1054" x1="22" y1="1" x2="22" y2="9" gradientUnits="userSpaceOnUse">
|
||||||
|
<stop stop-color="#FF463A"/>
|
||||||
|
<stop offset="1" stop-color="#FF1F5A"/>
|
||||||
|
</linearGradient>
|
||||||
|
</defs>
|
||||||
|
</svg>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h1>remixicon(4.1.0)<sup>1.0.0</sup></h1>
|
||||||
|
<div class="info">
|
||||||
|
Remix Icon is a set of neutral system symbols carefully crafted for designers and developers, each icon is designed based on 24×24 grid
|
||||||
|
</div>
|
||||||
|
<p>
|
||||||
|
|
||||||
|
<a href="https://github.com/Remix-Design/remixicon.git">GitHub</a> ·
|
||||||
|
|
||||||
|
<a href="index.html">Font Class</a> ·
|
||||||
|
|
||||||
|
<a href="unicode.html">Unicode</a>
|
||||||
|
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div class="icons">
|
||||||
|
<ul>
|
||||||
|
<li class="unicode-icon"><span class="iconfont"></span><h4>24-hours-fill</h4><span class="unicode">&#59905;</span></li><li class="unicode-icon"><span class="iconfont"></span><h4>24-hours-line</h4><span class="unicode">&#59906;</span></li><li class="unicode-icon"><span class="iconfont"></span><h4>4k-fill</h4><span class="unicode">&#59907;</span></li><li class="unicode-icon"><span class="iconfont"></span><h4>4k-line</h4><span class="unicode">&#59908;</span></li><li class="unicode-icon"><span class="iconfont"></span><h4>a-b</h4><span class="unicode">&#59909;</span></li><li class="unicode-icon"><span class="iconfont"></span><h4>account-box-fill</h4><span class="unicode">&#59910;</span></li><li class="unicode-icon"><span class="iconfont"></span><h4>account-box-line</h4><span class="unicode">&#59911;</span></li><li class="unicode-icon"><span class="iconfont"></span><h4>account-circle-fill</h4><span class="unicode">&#59912;</span></li><li class="unicode-icon"><span class="iconfont"></span><h4>account-circle-line</h4><span class="unicode">&#59913;</span></li><li class="unicode-icon"><span class="iconfont"></span><h4>account-pin-box-fill</h4><span class="unicode">&#59914;</span></li><li class="unicode-icon"><span class="iconfont"></span><h4>account-pin-box-line</h4><span class="unicode">&#59915;</span></li><li class="unicode-icon"><span class="iconfont"></span><h4>account-pin-circle-fill</h4><span class="unicode">&#59916;</span></li><li class="unicode-icon"><span class="iconfont"></span><h4>account-pin-circle-line</h4><span class="unicode">&#59917;</span></li><li class="unicode-icon"><span class="iconfont"></span><h4>add-box-fill</h4><span class="unicode">&#59918;</span></li><li class="unicode-icon"><span class="iconfont"></span><h4>add-box-line</h4><span class="unicode">&#59919;</span></li><li class="unicode-icon"><span class="iconfont"></span><h4>add-circle-fill</h4><span class="unicode">&#59920;</span></li><li class="unicode-icon"><span class="iconfont"></span><h4>add-circle-line</h4><span class="unicode">&#59921;</span></li><li class="unicode-icon"><span class="iconfont"></span><h4>add-fill</h4><span class="unicode">&#59922;</span></li><li class="unicode-icon"><span class="iconfont"></span><h4>add-line</h4><span class="unicode">&#59923;</span></li><li class="unicode-icon"><span class="iconfont"></span><h4>admin-fill</h4><span class="unicode">&#59924;</span></li><li class="unicode-icon"><span class="iconfont"></span><h4>admin-line</h4><span class="unicode">&#59925;</span></li><li class="unicode-icon"><span class="iconfont"></span><h4>advertisement-fill</h4><span class="unicode">&#59926;</span></li><li class="unicode-icon"><span class="iconfont"></span><h4>advertisement-line</h4><span class="unicode">&#59927;</span></li><li class="unicode-icon"><span class="iconfont"></span><h4>airplay-fill</h4><span class="unicode">&#59928;</span></li><li class="unicode-icon"><span class="iconfont"></span><h4>airplay-line</h4><span class="unicode">&#59929;</span></li><li class="unicode-icon"><span class="iconfont"></span><h4>alarm-fill</h4><span class="unicode">&#59930;</span></li><li class="unicode-icon"><span class="iconfont"></span><h4>alarm-line</h4><span class="unicode">&#59931;</span></li><li class="unicode-icon"><span class="iconfont"></span><h4>alarm-warning-fill</h4><span class="unicode">&#59932;</span></li><li class="unicode-icon"><span class="iconfont"></span><h4>alarm-warning-line</h4><span class="unicode">&#59933;</span></li><li class="unicode-icon"><span class="iconfont"></span><h4>album-fill</h4><span class="unicode">&#59934;</span></li><li class="unicode-icon"><span class="iconfont"></span><h4>album-line</h4><span class="unicode">&#59935;</span></li><li class="unicode-icon"><span class="iconfont"></span><h4>alert-fill</h4><span class="unicode">&#59936;</span></li><li class="unicode-icon"><span class="iconfont"></span><h4>alert-line</h4><span class="unicode">&#59937;</span></li><li class="unicode-icon"><span class="iconfont"></span><h4>aliens-fill</h4><span class="unicode">&#59938;</span></li><li class="unicode-icon"><span class="iconfont"></span><h4>aliens-line</h4><span class="unicode">&#59939;</span></li><li class="unicode-icon"><span class="iconfont"></span><h4>align-bottom</h4><span class="unicode">&#59940;</span></li><li class="unicode-icon"><span class="iconfont"></span><h4>align-center</h4><span class="unicode">&#59941;</span></li><li class="unicode-icon"><span class="iconfont"></span><h4>align-justify</h4><span class="unicode">&#59942;</span></li><li class="unicode-icon"><span class="iconfont"></span><h4>align-left</h4><span class="unicode">&#59943;</span></li><li class="unicode-icon"><span class="iconfont"></span><h4>align-righLine truncated
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
<p class="links">
|
||||||
|
|
||||||
|
<a href="https://github.com/Remix-Design/remixicon.git">GitHub</a> ·
|
||||||
|
|
||||||
|
<a href="index.html">Font Class</a> ·
|
||||||
|
|
||||||
|
<a href="unicode.html">Unicode</a>
|
||||||
|
|
||||||
|
</p>
|
||||||
|
<div class="footer">
|
||||||
|
Licensed under MIT. (Yes it's free and <a href="https://github.com/Remix-Design/remixicon">open-sourced</a>)
|
||||||
|
<div><a target="_blank" href="https://github.com/jaywcjlove/svgtofont">Created By svgtofont</a></div>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
|
||||||
|
</html>
|
||||||
Binary file not shown.
Binary file not shown.
@@ -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.
|
||||||
@@ -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.
|
||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -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 |
@@ -0,0 +1,41 @@
|
|||||||
|
<script>
|
||||||
|
import RoutesList from "@organisms/RoutersEditor/index.vue"
|
||||||
|
import PageHeader from "@atoms/PageHeader.vue"
|
||||||
|
import RouterRow from "@organisms/RoutersEditor/RouterRow.vue"
|
||||||
|
import SiteList from "@organisms/SitesEditor/SiteList.vue"
|
||||||
|
import {mapActions, mapGetters} from 'vuex'
|
||||||
|
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'App',
|
||||||
|
components: {RoutesList, SiteList, RouterRow, PageHeader},
|
||||||
|
computed: {
|
||||||
|
...mapGetters('proxy', ["sites", "routes"]),
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
this.uploadSites()
|
||||||
|
// const routes = new Users("http://172.25.78.64:8087")
|
||||||
|
// routes.test().then((testRes) => {
|
||||||
|
// console.log(testRes)
|
||||||
|
// })
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
...mapActions('proxy', ["uploadSites"])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="p-6">
|
||||||
|
<PageHeader class="me-2 mb-6" />
|
||||||
|
<SiteList />
|
||||||
|
<RoutesList>
|
||||||
|
<RouterRow
|
||||||
|
v-for="route in routes"
|
||||||
|
:id="route.id"
|
||||||
|
:key="`key-${route.id}`"
|
||||||
|
/>
|
||||||
|
</RoutesList>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
@@ -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 |
@@ -0,0 +1,102 @@
|
|||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
name: 'DoubleSwitch',
|
||||||
|
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>
|
||||||
@@ -0,0 +1,59 @@
|
|||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
name: 'Input',
|
||||||
|
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 border-zinc-300 focus:border-zinc-400 focus:ring-zinc-800/5 ${inputClass}`"
|
||||||
|
:placeholder="placeholder"
|
||||||
|
:required="required"
|
||||||
|
:value="value"
|
||||||
|
:on:change="setValue"
|
||||||
|
>
|
||||||
|
</template>
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
<script>
|
||||||
|
import {mapMutations, mapGetters} from 'vuex'
|
||||||
|
export default {
|
||||||
|
name: 'PageHeader',
|
||||||
|
computed: {
|
||||||
|
...mapGetters('proxy', ['selectedSite']),
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
...mapMutations('proxy', ['setIsSaveData', 'setIsDeleteData']),
|
||||||
|
saveData: function () {
|
||||||
|
console.log('saveData')
|
||||||
|
return this.setIsSaveData(true)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="flex">
|
||||||
|
<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>
|
||||||
|
<button
|
||||||
|
:class="`w-fit text-white font-medium rounded-full text-sm px-5 py-2.5 text-center me-2 mx-2 dark:bg-blue-600 dark:hover:bg-blue-700 dark:focus:ring-blue-800 ${!selectedSite ? 'bg-blue-200' : 'bg-blue-700 hover:bg-blue-800 focus:outline-none focus:ring-4 focus:ring-blue-300'}`"
|
||||||
|
:disabled="!selectedSite"
|
||||||
|
@click="saveData"
|
||||||
|
>
|
||||||
|
<i class="ri-save-line cursor-pointer" />
|
||||||
|
Сохранить изменения
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
@@ -0,0 +1,69 @@
|
|||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
name: 'Textarea',
|
||||||
|
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
|
||||||
|
},
|
||||||
|
focus: {
|
||||||
|
default: () => {},
|
||||||
|
type: Function
|
||||||
|
},
|
||||||
|
blur: {
|
||||||
|
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-300 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"
|
||||||
|
@focus="focus"
|
||||||
|
@blur="blur"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
<script>
|
||||||
|
import {mapActions} from 'vuex'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'Control',
|
||||||
|
props: { },
|
||||||
|
methods: {
|
||||||
|
...mapActions('proxy', ["breakSavingSite", "addNewRouteLayout"]),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="mb-4 flex items-center">
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
class="w-fit text-white bg-blue-700 hover:bg-blue-800 focus:outline-none focus:ring-4 focus:ring-blue-300 font-medium rounded-full text-sm px-5 py-2.5 text-center me-2 mr-2 dark:bg-blue-600 dark:hover:bg-blue-700 dark:focus:ring-blue-800"
|
||||||
|
@click="addNewRouteLayout"
|
||||||
|
>
|
||||||
|
Добавить роут
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
class="w-fit text-white bg-slate-700 hover:bg-slate-800 focus:outline-none focus:ring-4 focus:ring-slate-300 font-medium rounded-full text-sm px-5 py-2.5 text-center me-2 dark:bg-slate-600 dark:hover:bg-slate-700 dark:focus:ring-slate-800"
|
||||||
|
@click="breakSavingSite"
|
||||||
|
>
|
||||||
|
Закрыть
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
@@ -0,0 +1,306 @@
|
|||||||
|
<script>
|
||||||
|
import {mapActions, mapGetters} from 'vuex'
|
||||||
|
import { FwbSpinner } from 'flowbite-vue'
|
||||||
|
import Input from '@atoms/Input.vue'
|
||||||
|
import DoubleSwitch from '@atoms/DoubleSwitch.vue'
|
||||||
|
import Textarea from '@atoms/Textarea.vue'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'RouterRow',
|
||||||
|
components: {Input, Textarea, DoubleSwitch, FwbSpinner},
|
||||||
|
props: {
|
||||||
|
id: {
|
||||||
|
default: -1,
|
||||||
|
type: Number
|
||||||
|
},
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
timer: null,
|
||||||
|
state: "active",
|
||||||
|
textAreaRows: 1,
|
||||||
|
isRemoving: false,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
...mapGetters('proxy', ["selectedSite", "routes", 'routeOptions', 'routesLib']),
|
||||||
|
path() {
|
||||||
|
return this.routesLib[this.id]?.path || ""
|
||||||
|
},
|
||||||
|
role() {
|
||||||
|
return this.routesLib[this.id]?.role || ""
|
||||||
|
},
|
||||||
|
description() {
|
||||||
|
return this.routesLib[this.id]?.description || ""
|
||||||
|
},
|
||||||
|
errorPercent() {
|
||||||
|
if (!this.routesLib[this.id]?.cb_error_threshold_percentage && this.routesLib[this.id]?.cb_error_threshold_percentage !== 0) return ""
|
||||||
|
return this.routesLib[this.id]?.cb_error_threshold_percentage
|
||||||
|
},
|
||||||
|
intervalDuration() {
|
||||||
|
if (!this.routesLib[this.id]?.cb_interval_duration && this.routesLib[this.id]?.cb_interval_duration !== 0) return ""
|
||||||
|
return this.routesLib[this.id]?.cb_interval_duration
|
||||||
|
},
|
||||||
|
minRequests() {
|
||||||
|
if (!this.routesLib[this.id]?.cb_min_requests && this.routesLib[this.id]?.cb_min_requests !== 0) return ""
|
||||||
|
return this.routesLib[this.id]?.cb_min_requests
|
||||||
|
},
|
||||||
|
openStateTimeout() {
|
||||||
|
if (!this.routesLib[this.id]?.cb_open_state_timeout && this.routesLib[this.id]?.cb_open_state_timeout !== 0) return ""
|
||||||
|
return this.routesLib[this.id]?.cb_open_state_timeout
|
||||||
|
},
|
||||||
|
requestLimit() {
|
||||||
|
if (!this.routesLib[this.id]?.cb_request_limit && this.routesLib[this.id]?.cb_request_limit !== 0) return ""
|
||||||
|
return this.routesLib[this.id]?.cb_request_limit
|
||||||
|
},
|
||||||
|
deepness() {
|
||||||
|
if (!this.routesLib[this.id]?.deepness && this.routesLib[this.id]?.deepness !== 0) return ""
|
||||||
|
return this.routesLib[this.id]?.deepness
|
||||||
|
},
|
||||||
|
order() {
|
||||||
|
if (!this.routesLib[this.id]?.order && this.routesLib[this.id]?.order !== 0) return ""
|
||||||
|
return this.routesLib[this.id]?.order
|
||||||
|
},
|
||||||
|
isCbOn() {
|
||||||
|
return this.routesLib[this.id]?.is_cb_on
|
||||||
|
},
|
||||||
|
isOnline() {
|
||||||
|
return this.routesLib[this.id]?.is_online
|
||||||
|
},
|
||||||
|
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
...mapActions('proxy', ["updateRouteRow", "removeRoute", "breateAddingRoute"]),
|
||||||
|
toggle() {
|
||||||
|
this.open = !this.open
|
||||||
|
this.onToggle({isOpen: this.open})
|
||||||
|
},
|
||||||
|
setValue(key, value) {
|
||||||
|
this.updateRouteRow({
|
||||||
|
...this.routesLib[this.id],
|
||||||
|
[key]: value
|
||||||
|
})
|
||||||
|
},
|
||||||
|
setInputValue(params) {
|
||||||
|
const value = params.isNumber ? parseFloat(params.value) : params.value
|
||||||
|
this.updateRouteRow({
|
||||||
|
...this.routesLib[this.id],
|
||||||
|
[params.key]: value
|
||||||
|
})
|
||||||
|
},
|
||||||
|
removeCurrentRoute() {
|
||||||
|
this.removeRoute({id: this.id})
|
||||||
|
},
|
||||||
|
expandTextArea() {
|
||||||
|
this.textAreaRows = 8
|
||||||
|
},
|
||||||
|
decreaseTextArea() {
|
||||||
|
this.textAreaRows = 1
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="w-full flex flex-row gap-2 mb-2 items-end">
|
||||||
|
<div class="w-fit">
|
||||||
|
<DoubleSwitch
|
||||||
|
name="isCbOn"
|
||||||
|
firstColor="#8b5cf6"
|
||||||
|
secondColor="#2563eb"
|
||||||
|
firstTitle="Без защиты"
|
||||||
|
secondTitle="Защита"
|
||||||
|
:isCheck="isCbOn"
|
||||||
|
position="col"
|
||||||
|
labelClass="pb-2"
|
||||||
|
switchClass="mt-2.5"
|
||||||
|
@switched="(v) => setValue('is_cb_on', v)"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div class="w-fit">
|
||||||
|
<DoubleSwitch
|
||||||
|
name="isOnline"
|
||||||
|
firstColor="#8b5cf6"
|
||||||
|
secondColor="#2563eb"
|
||||||
|
firstTitle="Офлайн"
|
||||||
|
secondTitle="Онлайн"
|
||||||
|
:isCheck="isOnline"
|
||||||
|
position="col"
|
||||||
|
labelClass="pb-2"
|
||||||
|
switchClass="mt-2.5"
|
||||||
|
@switched="(v) => setValue('is_online', v)"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div class="w-full">
|
||||||
|
<div class="mr-2 mb-1 min-w-[80px] text-xs">
|
||||||
|
Путь
|
||||||
|
</div>
|
||||||
|
<Input
|
||||||
|
name="path"
|
||||||
|
:value="path"
|
||||||
|
inputClass="!w-[100%] py-1.5"
|
||||||
|
placeholder="Указать путь"
|
||||||
|
:onChange="setInputValue"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div class="w-fit min-w-[70px]">
|
||||||
|
<div class="mr-2 mb-1 text-xs">
|
||||||
|
Роль
|
||||||
|
</div>
|
||||||
|
<select
|
||||||
|
:value="role"
|
||||||
|
class="w-full rounded-lg border-gray-300 py-1.5 px-2"
|
||||||
|
@change="(e) => setValue('role', parseFloat(e.target.value))"
|
||||||
|
>
|
||||||
|
<option
|
||||||
|
v-for="(el, idx) in routeOptions"
|
||||||
|
:key="{idx}"
|
||||||
|
:value="el.value"
|
||||||
|
>
|
||||||
|
{{ el.name }}
|
||||||
|
</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div class="w-full translate-y-1.5">
|
||||||
|
<div class="mr-2 mb-1 max-w-[80px] text-xs">
|
||||||
|
Описание
|
||||||
|
</div>
|
||||||
|
<Textarea
|
||||||
|
name="description"
|
||||||
|
:value="description"
|
||||||
|
textareaClass="py-2 resize-none "
|
||||||
|
placeholder="Описание..."
|
||||||
|
:rows="textAreaRows"
|
||||||
|
:focus="expandTextArea"
|
||||||
|
:blur="decreaseTextArea"
|
||||||
|
:onChange="setInputValue"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div class="w-fit min-w-[80px]">
|
||||||
|
<div class="mr-2 mb-1 text-xs">
|
||||||
|
Ошибка, %
|
||||||
|
</div>
|
||||||
|
<Input
|
||||||
|
name="cb_error_threshold_percentage"
|
||||||
|
:value="`${errorPercent}`"
|
||||||
|
inputClass="!w-[100%] py-1.5"
|
||||||
|
placeholder="Процент ошибки"
|
||||||
|
:onChange="(v) => setInputValue({...v, isNumber: true})"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div class="w-fit min-w-[80px]">
|
||||||
|
<div class="mr-2 mb-1 text-xs">
|
||||||
|
Интервал
|
||||||
|
</div>
|
||||||
|
<Input
|
||||||
|
name="cb_interval_duration"
|
||||||
|
:value="`${intervalDuration}`"
|
||||||
|
inputClass="!w-[100%] py-1.5"
|
||||||
|
placeholder="Интервал"
|
||||||
|
:onChange="(v) => setInputValue({...v, isNumber: true})"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div class="w-fit min-w-[80px]">
|
||||||
|
<div class="mr-2 mb-1 text-xs">
|
||||||
|
Кол-во запросов
|
||||||
|
</div>
|
||||||
|
<Input
|
||||||
|
name="cb_min_requests"
|
||||||
|
:value="`${minRequests}`"
|
||||||
|
inputClass="!w-[100%] py-1.5"
|
||||||
|
placeholder="Кол-во запросов"
|
||||||
|
:onChange="(v) => setInputValue({...v, isNumber: true})"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div class="w-fit min-w-[80px]">
|
||||||
|
<div class="mr-2 mb-1 text-xs">
|
||||||
|
Тайм-аут состояния
|
||||||
|
</div>
|
||||||
|
<Input
|
||||||
|
name="cb_open_state_timeout"
|
||||||
|
:value="`${openStateTimeout}`"
|
||||||
|
inputClass="!w-[100%] py-1.5"
|
||||||
|
placeholder="Тайм-аут состояния"
|
||||||
|
:onChange="(v) => setInputValue({...v, isNumber: true})"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div class="w-fit min-w-[80px]">
|
||||||
|
<div class="mr-2 mb-1 text-xs">
|
||||||
|
Лимит запросов
|
||||||
|
</div>
|
||||||
|
<Input
|
||||||
|
name="cb_request_limit"
|
||||||
|
:value="`${requestLimit}`"
|
||||||
|
inputClass="!w-[100%] py-1.5"
|
||||||
|
placeholder="Лимит запросов"
|
||||||
|
:onChange="(v) => setInputValue({...v, isNumber: true})"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div class="w-fit min-w-[80px]">
|
||||||
|
<div class="mr-2 mb-1 text-xs">
|
||||||
|
Глубина запросов
|
||||||
|
</div>
|
||||||
|
<Input
|
||||||
|
name="deepness"
|
||||||
|
:value="`${deepness}`"
|
||||||
|
inputClass="!w-[100%] py-1.5"
|
||||||
|
placeholder="Глубина запросов"
|
||||||
|
:onChange="(v) => setInputValue({...v, isNumber: true})"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div class="w-fit min-w-[80px]">
|
||||||
|
<div class="mr-2 mb-1 text-xs">
|
||||||
|
Порядок сортировки
|
||||||
|
</div>
|
||||||
|
<Input
|
||||||
|
name="order"
|
||||||
|
:value="`${order}`"
|
||||||
|
inputClass="!w-[100%] py-1.5"
|
||||||
|
placeholder="Порядок сортировки"
|
||||||
|
:onChange="(v) => setInputValue({...v, isNumber: true})"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div class="flex items-center col-span-1">
|
||||||
|
<div
|
||||||
|
v-if="!isRemoving"
|
||||||
|
class=" transition-all h-9 w-9 mr-4 rounded-md bg-red-700 hover:bg-red-600 cursor-pointer text-white flex items-center justify-center"
|
||||||
|
@click="() => isRemoving = true"
|
||||||
|
>
|
||||||
|
<i class="ri-delete-bin-line" />
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
v-if="isRemoving"
|
||||||
|
class=" transition-all h-9 w-24 mr-4 rounded-md bg-gray-700 hover:bg-gray-600 cursor-pointer text-white flex items-center justify-center"
|
||||||
|
@click="() => isRemoving = false"
|
||||||
|
>
|
||||||
|
Отменить
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
v-if="isRemoving"
|
||||||
|
class=" transition-all h-9 w-24 mr-4 rounded-md bg-red-700 hover:bg-red-600 cursor-pointer text-white flex items-center justify-center"
|
||||||
|
@click="removeCurrentRoute({id})"
|
||||||
|
>
|
||||||
|
Удалить
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
v-if="timer"
|
||||||
|
class="flex items-center opacity-75"
|
||||||
|
>
|
||||||
|
<span class="mr-2 font-w-700 text-slate-900">Сохранение</span>
|
||||||
|
<fwb-spinner
|
||||||
|
v-if="timer"
|
||||||
|
size="6"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
v-if="state === 'success'"
|
||||||
|
class="flex items-center "
|
||||||
|
>
|
||||||
|
<span class="mr-2 text-slate-700 font-w-700">Сохранено</span>
|
||||||
|
<i
|
||||||
|
class="ri-check-line text-green-600"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
<script>
|
||||||
|
import { FwbSpinner } from 'flowbite-vue'
|
||||||
|
import { mapGetters} from 'vuex'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'RoutesLoader',
|
||||||
|
components: {FwbSpinner},
|
||||||
|
computed: {
|
||||||
|
...mapGetters('proxy', ["routesState"]),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div
|
||||||
|
v-if="routesState === 'loading'"
|
||||||
|
class="w-full flex justify-center items-center"
|
||||||
|
>
|
||||||
|
<div class="flex items-center">
|
||||||
|
<fwb-spinner
|
||||||
|
size="8"
|
||||||
|
class="mr-2"
|
||||||
|
/> Загрузка...
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
@@ -0,0 +1,51 @@
|
|||||||
|
<script>
|
||||||
|
import {mapGetters} from 'vuex'
|
||||||
|
import Control from './Controll.vue'
|
||||||
|
import RoutesLoader from './RoutesLoader.vue'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'RoutesList',
|
||||||
|
components: {RoutesLoader, Control},
|
||||||
|
props: {
|
||||||
|
name: {
|
||||||
|
default: "",
|
||||||
|
type: String
|
||||||
|
},
|
||||||
|
port: {
|
||||||
|
default: "",
|
||||||
|
type: String
|
||||||
|
},
|
||||||
|
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
open: false,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
...mapGetters('proxy', ['routesState']),
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
routesState: function (newVal) {
|
||||||
|
this.open = newVal === 'active' ? true : false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
toggle () {
|
||||||
|
this.open = !this.open
|
||||||
|
this.onToggle({isOpen: this.open})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div
|
||||||
|
v-if="open"
|
||||||
|
class="flex flex-col w-full"
|
||||||
|
>
|
||||||
|
<Control />
|
||||||
|
<slot />
|
||||||
|
</div>
|
||||||
|
<RoutesLoader />
|
||||||
|
</template>
|
||||||
@@ -0,0 +1,108 @@
|
|||||||
|
<!-- eslint-disable vue/prop-name-casing -->
|
||||||
|
<script>
|
||||||
|
import {mapActions, mapGetters} from 'vuex'
|
||||||
|
import Input from '@atoms/Input.vue'
|
||||||
|
import Textarea from '@atoms/Textarea.vue'
|
||||||
|
import DoubleSwitch from '@atoms/DoubleSwitch.vue'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'EditCard',
|
||||||
|
components: {Input, Textarea, DoubleSwitch},
|
||||||
|
props: {
|
||||||
|
id: {
|
||||||
|
default: -1,
|
||||||
|
type: Number
|
||||||
|
},
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
...mapGetters('proxy', ["selectedSite"]),
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
...mapActions('proxy', ["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">
|
||||||
|
<DoubleSwitch
|
||||||
|
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 ml-2 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,165 @@
|
|||||||
|
<!-- eslint-disable vue/prop-name-casing -->
|
||||||
|
<script>
|
||||||
|
import {mapActions, mapGetters} from 'vuex'
|
||||||
|
import EditCard from './EditCard.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
|
||||||
|
}
|
||||||
|
},
|
||||||
|
data () {
|
||||||
|
return {
|
||||||
|
isDelete: false,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
...mapGetters('proxy', ["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('proxy', ["uploadSiteRoutes", "saveSite", "createNewSite", 'removeSite', 'updateRoutesWithApi']),
|
||||||
|
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"
|
||||||
|
href="#"
|
||||||
|
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="uploadSiteRoutes(site)"
|
||||||
|
/>
|
||||||
|
<i
|
||||||
|
v-tippy="{ content: 'удалить сервис' }"
|
||||||
|
class="ri-delete-bin-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,82 @@
|
|||||||
|
<script>
|
||||||
|
// import {ref} from 'vue'
|
||||||
|
import {mapActions, mapGetters} from 'vuex'
|
||||||
|
import { FwbSpinner } from 'flowbite-vue'
|
||||||
|
import SiteCard from "./SiteCard.vue"
|
||||||
|
import NewSiteButton from "../../NewSiteButton.vue"
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'SiteList',
|
||||||
|
components: {FwbSpinner, SiteCard, NewSiteButton},
|
||||||
|
data () {
|
||||||
|
return {
|
||||||
|
dynamicHeight: null
|
||||||
|
}
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
...mapGetters('proxy', ["sites", "routes", "sitesState", "newSite", 'isDeleteData']),
|
||||||
|
},
|
||||||
|
mounted () {
|
||||||
|
this.$nextTick(function () {
|
||||||
|
this.maxHeight()
|
||||||
|
})
|
||||||
|
},
|
||||||
|
updated () {
|
||||||
|
this.$nextTick(function () {
|
||||||
|
this.maxHeight()
|
||||||
|
})
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
...mapActions('proxy', ["uploadSites"]),
|
||||||
|
maxHeight () {
|
||||||
|
setTimeout(() => {
|
||||||
|
const containerHeight = this.$refs.sitesList?.offsetHeight
|
||||||
|
const clientHeight = document.documentElement.clientHeight
|
||||||
|
const delHeight = 400
|
||||||
|
const currHeight = containerHeight + delHeight
|
||||||
|
if ((currHeight) > clientHeight) {
|
||||||
|
this.dynamicHeight = clientHeight - delHeight
|
||||||
|
}
|
||||||
|
}, 100)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
{{
|
||||||
|
|
||||||
|
}}
|
||||||
|
<div
|
||||||
|
v-if="sitesState === 'active'"
|
||||||
|
ref="sitesList"
|
||||||
|
:class="`${dynamicHeight ? 'shadow-lg p-3' : ''} 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`"
|
||||||
|
:style="{maxHeight: `${dynamicHeight}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"
|
||||||
|
/>
|
||||||
|
</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>
|
||||||
Whitespace-only changes.
@@ -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('proxy', ['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>
|
||||||
@@ -0,0 +1,160 @@
|
|||||||
|
import {get, post, put, remove} from './apiHelpers.js'
|
||||||
|
import {convertObject, convertList} from './adapter/adapter.js'
|
||||||
|
import { config} from '@store/modules/proxy/StaticData.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
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Testing class Services
|
||||||
|
*/
|
||||||
|
|
||||||
|
// eslint-disable-next-line no-unused-vars
|
||||||
|
const apiTest = async () => {
|
||||||
|
|
||||||
|
const services = new Services(import.meta.env.VITE_API_ADDR, config)
|
||||||
|
|
||||||
|
// Testing get services
|
||||||
|
console.log('Uploaded services, method getServices - start')
|
||||||
|
const uploadedServices = await services.getServices()
|
||||||
|
console.log('Uploaded services, method getServices - result:', uploadedServices)
|
||||||
|
|
||||||
|
// Testing create service
|
||||||
|
console.log('Added new service, method createService - start')
|
||||||
|
const newService = await services.createService({
|
||||||
|
name: "Test site for testing createService",
|
||||||
|
port: 7777,
|
||||||
|
site_ip: "172.25.78.151",
|
||||||
|
proxy_ip: "172.25.78.151",
|
||||||
|
description: 'Testing createService method',
|
||||||
|
})
|
||||||
|
let updatedServices = [...uploadedServices, newService]
|
||||||
|
console.log('Added new service, method createService - result:', newService)
|
||||||
|
|
||||||
|
// Testing update service
|
||||||
|
console.log('Updated services, method updateService - start')
|
||||||
|
const serviceWithNewParams = {...newService, name: 'Test site for testing updateService', description: 'Testing updateService method', port: 9999}
|
||||||
|
const updatedService = await services.updateService(serviceWithNewParams)
|
||||||
|
updatedServices = [...updatedServices.filter(service => service.id !== newService.id), updatedService]
|
||||||
|
console.log('Updated services, method updateService - result:', updatedService)
|
||||||
|
|
||||||
|
// Testing delete service
|
||||||
|
console.log('Deleted service, method deleteService - start')
|
||||||
|
const deletedServiceId = await services.deleteService(newService.id)
|
||||||
|
console.log('Deleted service, method deleteService, get id from deleted service - result:', deletedServiceId)
|
||||||
|
|
||||||
|
// Testing get services after delete
|
||||||
|
const withoutDeletedService = updatedServices.filter(service => service.id !== newService.id)
|
||||||
|
const updatedServicesAfterDelete = await services.getServices()
|
||||||
|
console.log('Services after all tests:', updatedServicesAfterDelete)
|
||||||
|
|
||||||
|
// Equal results
|
||||||
|
|
||||||
|
const reg = new RegExp (`${updatedServicesAfterDelete.length}`, 'g')
|
||||||
|
const lengthCurrServices = withoutDeletedService.length.toString()
|
||||||
|
|
||||||
|
console.log('reg', reg)
|
||||||
|
|
||||||
|
const isEqual = reg.test(lengthCurrServices)
|
||||||
|
|
||||||
|
console.log('isCountServicesEqual', isEqual)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Запуск теста api - расскомментировать функцию apiTest
|
||||||
|
// apiTest()
|
||||||
|
// тест api end
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Testing class Services end
|
||||||
|
*/
|
||||||
@@ -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}
|
||||||
@@ -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}
|
||||||
@@ -0,0 +1,232 @@
|
|||||||
|
import {get, post, put, remove} from './apiHelpers.js'
|
||||||
|
import {convertList} from './adapter/adapter.js'
|
||||||
|
import {equals, cond} from "ramda";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Интерфейс роутера
|
||||||
|
* @typedef {Object} Router
|
||||||
|
* @param {Number} id
|
||||||
|
* @param {Number} server_id
|
||||||
|
* @param {String} path
|
||||||
|
* @param {Number} role
|
||||||
|
* @param {String} description
|
||||||
|
* @param {Number} deepness
|
||||||
|
* @param {Number} order
|
||||||
|
* @param {Boolean} is_cb_on
|
||||||
|
* @param {Number} cb_request_limit
|
||||||
|
* @param {Number} cb_min_requests
|
||||||
|
* @param {Number} cb_error_threshold_percentage
|
||||||
|
* @param {Number} cb_interval_duration
|
||||||
|
* @param {Number} cb_open_state_timeout
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Интерфейс роутера
|
||||||
|
* @typedef {Object} Action
|
||||||
|
* @property {'remove' | 'create' | 'update' | 'delete'} action
|
||||||
|
*
|
||||||
|
* @typedef {Router & Action} ActionRouter
|
||||||
|
*/
|
||||||
|
|
||||||
|
class Routes {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Класс управления роутерами
|
||||||
|
* @param {String} apiAddr - path to service
|
||||||
|
* @param {Object | undefined} adapter_config - oldKey: newKey
|
||||||
|
*/
|
||||||
|
constructor(apiAddr, adapter_config = {}) {
|
||||||
|
this.apiAddr = apiAddr
|
||||||
|
this.config = adapter_config
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @returns {Promise<*[]>}
|
||||||
|
*/
|
||||||
|
async getRoutes() {
|
||||||
|
let res = await get(`${this.apiAddr}/routers`)
|
||||||
|
let updatedRoutes = convertList(res.data, {config: this.config})
|
||||||
|
return updatedRoutes
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param {Number} id - Сервис id, если id не указан, отображается список всех роутеров
|
||||||
|
* @returns {Promise<*[]>}
|
||||||
|
*/
|
||||||
|
async getRouterById(id) {
|
||||||
|
let res = await get(`${this.apiAddr}/routers/by_server/${id}`)
|
||||||
|
let updatedRoutes = convertList(res.data, {config: this.config})
|
||||||
|
return updatedRoutes
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param {Router} routerData
|
||||||
|
* @returns {Promise<void>}
|
||||||
|
*/
|
||||||
|
async createRoute(routerData) {
|
||||||
|
const newRoute = await post(`${this.apiAddr}/routers`, routerData)
|
||||||
|
return newRoute
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param {Router} routerData
|
||||||
|
* @returns {Promise<void>}
|
||||||
|
*/
|
||||||
|
async updateRoute(routerData) {
|
||||||
|
const updatedRouterData = {...routerData}
|
||||||
|
delete updatedRouterData.id
|
||||||
|
const newRoute = await put(`${this.apiAddr}/routers/${routerData.id}`, updatedRouterData)
|
||||||
|
return newRoute
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param {Number} routerId - Сервис id, если id не указан, отображается список всех роутеров
|
||||||
|
* @returns {Promise<*[]>}
|
||||||
|
*/
|
||||||
|
async removeRoute(routerId) {
|
||||||
|
const removedRoute = await remove(`${this.apiAddr}/routers/${routerId}`)
|
||||||
|
return removedRoute
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param {Array<ActionRouter>} routesActions - Группа роутеров для обновления
|
||||||
|
* @returns {Promise<Array<Router>>}
|
||||||
|
*/
|
||||||
|
async updateGroupRoutes(routesActions) {
|
||||||
|
const responses = routesActions.map(async (mutation) => {
|
||||||
|
return cond([
|
||||||
|
[equals('create'), () => this.createRoute(mutation)],
|
||||||
|
[equals('delete'), () => this.removeRoute(mutation.id)],
|
||||||
|
[equals('remove'), () => this.removeRoute(mutation.id)],
|
||||||
|
[equals('update'), () => this.updateRoute(mutation)],
|
||||||
|
])(mutation.action)
|
||||||
|
})
|
||||||
|
|
||||||
|
return Promise.all(responses)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Функция запускает список запросов к апишке и логает ответы
|
||||||
|
* @returns {Promise<String>}
|
||||||
|
*/
|
||||||
|
async test() {
|
||||||
|
console.log("_______START TEST_______")
|
||||||
|
|
||||||
|
const allRoutes = await this.getRoutes()
|
||||||
|
console.log("allRoutes", allRoutes)
|
||||||
|
const serverRouter = await this.getRouterById(1)
|
||||||
|
console.log("getRouterById 1", serverRouter)
|
||||||
|
const newRoute = await this.createRoute({
|
||||||
|
"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("newRoute", newRoute)
|
||||||
|
const updatedRoute = await this.updateRoute({
|
||||||
|
"path": "/updated_path/",
|
||||||
|
"description": "updated_description",
|
||||||
|
"id": newRoute.id
|
||||||
|
})
|
||||||
|
console.log("updatedRoute", updatedRoute)
|
||||||
|
const removedRoute = await this.removeRoute(newRoute.id)
|
||||||
|
console.log("removedRoute", removedRoute)
|
||||||
|
|
||||||
|
const newRoute1 = await this.createRoute({
|
||||||
|
"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 newRoute2 = await this.createRoute({
|
||||||
|
"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 newRoute3 = await this.createRoute({
|
||||||
|
"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 = [
|
||||||
|
{
|
||||||
|
...newRoute1,
|
||||||
|
action: "update",
|
||||||
|
"path": "/updated_path2/",
|
||||||
|
"description": "updated_description2",
|
||||||
|
}, {
|
||||||
|
...newRoute2,
|
||||||
|
action: "update",
|
||||||
|
"path": "/updated_path3/",
|
||||||
|
"description": "updated_description3",
|
||||||
|
}, {
|
||||||
|
...newRoute3,
|
||||||
|
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.updateGroupRoutes(actions)
|
||||||
|
console.log("mutationsList", mutationsList)
|
||||||
|
console.log("________END TEST________")
|
||||||
|
|
||||||
|
return "ok"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Routes
|
||||||
@@ -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}
|
||||||
@@ -0,0 +1,38 @@
|
|||||||
|
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, onError) => {
|
||||||
|
return await axios.put(`${path}`, data)
|
||||||
|
.then(r => r.data)
|
||||||
|
.catch(error => {
|
||||||
|
onError && onError(error)
|
||||||
|
console.error('Error put request:', error)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const remove = async (path, onError) => {
|
||||||
|
return await axios.delete(`${path}`)
|
||||||
|
.then(r => r.data)
|
||||||
|
.catch(error => {
|
||||||
|
onError && onError(error)
|
||||||
|
console.error('Error delete request:', error)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export {get, post, put, remove}
|
||||||
Whitespace-only changes.
@@ -0,0 +1,14 @@
|
|||||||
|
import './styles/main.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'
|
||||||
|
|
||||||
|
createApp(App)
|
||||||
|
.use(ramdaVue)
|
||||||
|
.use(store)
|
||||||
|
.use(VueTippy)
|
||||||
|
.mount('#app')
|
||||||
@@ -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;
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
import {createStore, createLogger} from 'vuex';
|
||||||
|
import {store as proxy} from '@/store/modules/proxy';
|
||||||
|
|
||||||
|
// 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: {
|
||||||
|
proxy
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
export function useStore() {
|
||||||
|
return store;
|
||||||
|
}
|
||||||
@@ -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}
|
||||||
@@ -0,0 +1,67 @@
|
|||||||
|
const addedSite = (addedSite, sites) => {
|
||||||
|
return [addedSite,...sites]
|
||||||
|
}
|
||||||
|
|
||||||
|
const updatedSite = (updatedSite, sites) => {
|
||||||
|
if (updatedSite.id) {
|
||||||
|
const editIdx = sites.findIndex(service => service.id === updatedSite.id)
|
||||||
|
const beforeEdit = sites.slice(0, editIdx)
|
||||||
|
const afterEdit = sites.slice(editIdx + 1)
|
||||||
|
const withEdit = [...beforeEdit, updatedSite]
|
||||||
|
return [...withEdit, ...afterEdit]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const removedNewSite = (sites) => {
|
||||||
|
if (sites.length > 0) {
|
||||||
|
const firstSite = sites[0]
|
||||||
|
const isNewSite = firstSite.id === -1
|
||||||
|
sites = isNewSite ? sites.slice(1) : sites
|
||||||
|
return sites
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const deletedSite = (deletedSiteId, sites) => {
|
||||||
|
if (deletedSiteId) {
|
||||||
|
const deleteIdx = sites.findIndex(service => service.id === deletedSiteId)
|
||||||
|
return sites.slice(0, deleteIdx).concat(sites.slice(deleteIdx + 1))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const addedNewRoute = (selectedSite, routes) => {
|
||||||
|
let newRoute = routes.find((route) => route.action === 'create')
|
||||||
|
if (newRoute) {
|
||||||
|
newRoute = {...newRoute, server_id: selectedSite.id}
|
||||||
|
const withoutNewRoute = routes.filter(route => route.action !== 'create')
|
||||||
|
return [newRoute, ...withoutNewRoute]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const updatedRoute = (updatedRoute, routes) => {
|
||||||
|
return routes.reduce((acc, cur) => {
|
||||||
|
if (cur.id === updatedRoute.id) {
|
||||||
|
return [...acc, {
|
||||||
|
...updatedRoute,
|
||||||
|
action: updatedRoute.action === "create" ? "create" : "update"
|
||||||
|
}]
|
||||||
|
}
|
||||||
|
return [...acc, cur]
|
||||||
|
}, [])
|
||||||
|
}
|
||||||
|
|
||||||
|
const deletedRoute = (deletedRouteId, routes) => {
|
||||||
|
return routes.reduce((acc, cur) => {
|
||||||
|
if (cur.id !== deletedRouteId) return [...acc, cur]
|
||||||
|
if (cur.action === "create") return acc
|
||||||
|
return [...acc, {
|
||||||
|
...cur,
|
||||||
|
action: "remove"
|
||||||
|
}]
|
||||||
|
}, [])
|
||||||
|
}
|
||||||
|
|
||||||
|
const sortedRoutes = (routes) => {
|
||||||
|
return routes.sort(({updated_at: a}, {updated_at: b}) => new Date(b).valueOf() - new Date(a).valueOf())
|
||||||
|
}
|
||||||
|
|
||||||
|
export { addedSite, updatedSite, removedNewSite, deletedSite, addedNewRoute, updatedRoute, sortedRoutes, deletedRoute }
|
||||||
@@ -0,0 +1,153 @@
|
|||||||
|
import { config, configRoutes } from './StaticData.js';
|
||||||
|
import routeOptions from './routeOptions.json'
|
||||||
|
import Services from '@helpers/Services/Services.js';
|
||||||
|
import {addedSite, updatedSite, removedNewSite, deletedSite, addedNewRoute, updatedRoute, sortedRoutes, deletedRoute} from './helpers.js';
|
||||||
|
import {isEmpty, dissoc} from 'ramda';
|
||||||
|
import RoutesClass from "@helpers/server-routes/Routes.js";
|
||||||
|
|
||||||
|
const routerManager = new RoutesClass(import.meta.env.VITE_API_ADDR, configRoutes)
|
||||||
|
const services = new Services(import.meta.env.VITE_API_ADDR, config)
|
||||||
|
|
||||||
|
const initState = {
|
||||||
|
sites: [],
|
||||||
|
sitesState: "loading",
|
||||||
|
isSaveData: false,
|
||||||
|
selectedSite: null,
|
||||||
|
|
||||||
|
routes: [],
|
||||||
|
routesState: "await",
|
||||||
|
routesLib: {},
|
||||||
|
};
|
||||||
|
|
||||||
|
const state = {
|
||||||
|
...initState
|
||||||
|
};
|
||||||
|
|
||||||
|
const getters = {
|
||||||
|
isSaveData: (state) => state.isSaveData,
|
||||||
|
sites: (state) => state.sites,
|
||||||
|
routes: (state) => state.routes.filter(({action}) => !["remove", "delete"].includes(action)),
|
||||||
|
routesLib: (state) => state.routes.reduce((acc, cur) => ({
|
||||||
|
...acc,
|
||||||
|
[cur.id]: cur
|
||||||
|
}), {}),
|
||||||
|
routesState: (state) => state.routesState,
|
||||||
|
sitesState: (state) => state.sitesState,
|
||||||
|
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,
|
||||||
|
setRoutes: (state, payload) => state.routes = payload,
|
||||||
|
setRoutesState: (state, payload) => state.routesState = payload,
|
||||||
|
};
|
||||||
|
|
||||||
|
const actions = {
|
||||||
|
addNewSiteLayout: ({commit, getters}) => {
|
||||||
|
const newSite = {"port": "", "name": "", id: -1}
|
||||||
|
commit('setSites', [newSite,...getters.sites])
|
||||||
|
commit('setSelectedSite', newSite)
|
||||||
|
commit('setRoutes', [])
|
||||||
|
commit('setRoutesState', "active")
|
||||||
|
},
|
||||||
|
createNewSite: async ({dispatch, commit, getters, state}, payload) => {
|
||||||
|
commit('setIsSaveData', false)
|
||||||
|
commit('setSelectedSite', null)
|
||||||
|
const newSite = await services.createService(payload)
|
||||||
|
const updatedSites = addedSite(newSite, getters.sites)
|
||||||
|
if (!isEmpty(updatedSites)) {
|
||||||
|
const newSite = updatedSites[0]
|
||||||
|
const updatedRoutes = addedNewRoute(newSite, state.routes)
|
||||||
|
commit('setRoutes', updatedRoutes)
|
||||||
|
dispatch('updateRoutesWithApi', newSite)
|
||||||
|
return commit('setSites', updatedSites)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
breakeAddingSite: ({commit}) => {
|
||||||
|
commit('setSelectedSite', null)
|
||||||
|
},
|
||||||
|
uploadSites: async ({commit}) => {
|
||||||
|
const sites = await services.getServices()
|
||||||
|
commit('setSites', sites)
|
||||||
|
commit('setSitesState', 'active')
|
||||||
|
},
|
||||||
|
editSelectedSite: async ({commit, getters}, payload) => {
|
||||||
|
const selectedSite = getters.selectedSite
|
||||||
|
selectedSite[payload.key] = payload.value
|
||||||
|
commit('setSelectedSite', selectedSite)
|
||||||
|
},
|
||||||
|
saveSite: async ({commit, getters}, payload) => {
|
||||||
|
commit('setIsSaveData', false)
|
||||||
|
commit('setSelectedSite', null)
|
||||||
|
const editedSite = await services.updateService(payload)
|
||||||
|
const updatedSites = !isEmpty(editedSite) ? updatedSite(editedSite, getters.sites) : getters.sites
|
||||||
|
commit('setSites', updatedSites)
|
||||||
|
},
|
||||||
|
breakSavingSite: ({commit}) => {
|
||||||
|
commit('setSelectedSite', null)
|
||||||
|
commit('setRoutesState', "await")
|
||||||
|
},
|
||||||
|
removeSite: async ({commit, getters}, id) => {
|
||||||
|
const deletedSiteId = await services.deleteService(id)
|
||||||
|
const updatedSites = deletedSite(deletedSiteId, getters.sites)
|
||||||
|
if (!isEmpty(updatedSites)) return commit('setSites', updatedSites)
|
||||||
|
},
|
||||||
|
uploadSiteRoutes: async ({commit, getters}, siteProps) => {
|
||||||
|
const sites = removedNewSite(getters.sites)
|
||||||
|
commit('setSites', sites)
|
||||||
|
commit('setSelectedSite', siteProps)
|
||||||
|
commit('setRoutesState', "loading")
|
||||||
|
let routes = await routerManager.getRouterById(siteProps.id)
|
||||||
|
routes = sortedRoutes(routes)
|
||||||
|
commit('setRoutes', routes)
|
||||||
|
commit('setRoutesState', "active")
|
||||||
|
},
|
||||||
|
addNewRouteLayout: ({commit, getters}) => {
|
||||||
|
const newRoute = {
|
||||||
|
"path": null,
|
||||||
|
"role": null,
|
||||||
|
id: Math.random().toString(36).slice(4),
|
||||||
|
action: "create",
|
||||||
|
server_id: getters.selectedSite.id
|
||||||
|
}
|
||||||
|
commit('setRoutes', [newRoute, ...state.routes])
|
||||||
|
},
|
||||||
|
updateRouteRow: ({commit, state}, editRoute) => {
|
||||||
|
const updatedRoutes = updatedRoute(editRoute, state.routes)
|
||||||
|
commit('setRoutes', updatedRoutes)
|
||||||
|
},
|
||||||
|
updateRoutesWithApi: async ({commit, state}, payload) => {
|
||||||
|
commit('setRoutesState', "loading")
|
||||||
|
let updatedRoutes = state.routes.filter(({action}) => action)
|
||||||
|
updatedRoutes = updatedRoutes.map((el) => {
|
||||||
|
if (el.action === "create") return dissoc('id', el)
|
||||||
|
return el
|
||||||
|
})
|
||||||
|
await routerManager.updateGroupRoutes(updatedRoutes)
|
||||||
|
let routes = await routerManager.getRouterById(payload.id)
|
||||||
|
routes = sortedRoutes(routes)
|
||||||
|
commit('setRoutes', routes)
|
||||||
|
commit('setRoutesState', "await")
|
||||||
|
},
|
||||||
|
removeRoute: ({commit, state}, {id}) => {
|
||||||
|
const updatedRoutes = deletedRoute(id, state.routes)
|
||||||
|
commit('setRoutes', updatedRoutes)
|
||||||
|
},
|
||||||
|
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": 1, "value": 1 },
|
||||||
|
{ "name": 2, "value": 2 },
|
||||||
|
{ "name": 3, "value": 3 }
|
||||||
|
]
|
||||||
+19
@@ -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
@@ -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"}
|
||||||
|
]
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
@tailwind base;
|
||||||
|
@tailwind components;
|
||||||
|
@tailwind utilities;
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
/** @type {import('tailwindcss').Config} */
|
||||||
|
export default {
|
||||||
|
content: [
|
||||||
|
"./index.html",
|
||||||
|
"./src/**/*.{vue,js,ts,jsx,tsx}",
|
||||||
|
'node_modules/flowbite-vue/**/*.{js,jsx,ts,tsx,vue}',
|
||||||
|
'node_modules/flowbite/**/*.{js,jsx,ts,tsx}'
|
||||||
|
],
|
||||||
|
darkMode: "class", // or 'media' or 'class'
|
||||||
|
// eslint-disable-next-line no-undef
|
||||||
|
plugins: [require("@tailwindcss/forms"), require('flowbite/plugin')],
|
||||||
|
theme: {
|
||||||
|
screens: {
|
||||||
|
'sm': '420px',
|
||||||
|
'md': '768px',
|
||||||
|
'lg': '1024px',
|
||||||
|
'xl': '1366px',
|
||||||
|
'2xl': '1536px', //local
|
||||||
|
'3xl': '2048px', //local
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
+204
@@ -0,0 +1,204 @@
|
|||||||
|
import { expect, test } from 'vitest'
|
||||||
|
import { convertObject, convertList } from '@helpers/server-routes/adapter/adapter.js'
|
||||||
|
|
||||||
|
|
||||||
|
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)
|
||||||
|
})
|
||||||
|
|
||||||
@@ -0,0 +1,310 @@
|
|||||||
|
import {describe, expect, beforeEach} from 'vitest'
|
||||||
|
import {createStore} from 'vuex'
|
||||||
|
import {store} from '@store/modules/proxy/index.js'
|
||||||
|
import {addedNewRoute, updatedRoute, sortedRoutes, deletedRoute} from '@store/modules/proxy/helpers.js'
|
||||||
|
|
||||||
|
const selectedSite = store.state.selectedSite
|
||||||
|
const routes = store.state.routes
|
||||||
|
const routesState = store.state.routesState
|
||||||
|
|
||||||
|
const setSelectedSite = store.mutations.setSelectedSite
|
||||||
|
const setRoutes = store.mutations.setRoutes
|
||||||
|
const setRoutesState = store.mutations.setRoutesState
|
||||||
|
|
||||||
|
const addNewRouteLayout = store.actions.addNewRouteLayout
|
||||||
|
const uploadSiteRoutes = store.actions.uploadSiteRoutes
|
||||||
|
const updateRouteRow = store.actions.updateRouteRow
|
||||||
|
const updateRoutesWithApi = store.actions.updateRoutesWithApi
|
||||||
|
const removeRoute = store.actions.removeRoute
|
||||||
|
const resetStore = store.actions.resetStore
|
||||||
|
|
||||||
|
const defaultRoutes = [{
|
||||||
|
"id": 7,
|
||||||
|
"created_at": "2024-02-27T12:02:34.666042252+03:00",
|
||||||
|
"updated_at": "2024-02-27T12:02:34.666042252+03:00",
|
||||||
|
"deleted_at": null,
|
||||||
|
"server_id": -1,
|
||||||
|
"path": "/route",
|
||||||
|
"role": 0,
|
||||||
|
"description": "with route",
|
||||||
|
"order": 0,
|
||||||
|
"deepness": 0,
|
||||||
|
"is_cb_on": true,
|
||||||
|
"cb_request_limit": 0,
|
||||||
|
"cb_min_requests": 7,
|
||||||
|
"cb_error_threshold_percentage": 0,
|
||||||
|
"cb_interval_duration": 0,
|
||||||
|
"cb_open_state_timeout": 0,
|
||||||
|
"is_online": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 8,
|
||||||
|
"created_at": "2024-02-27T12:05:45.268921795+03:00",
|
||||||
|
"updated_at": "2024-02-27T12:05:45.268921795+03:00",
|
||||||
|
"deleted_at": null,
|
||||||
|
"server_id": 7,
|
||||||
|
"path": "new/3",
|
||||||
|
"role": 0,
|
||||||
|
"description": "",
|
||||||
|
"order": 0,
|
||||||
|
"deepness": 0,
|
||||||
|
"is_cb_on": false,
|
||||||
|
"cb_request_limit": 3,
|
||||||
|
"cb_min_requests": 5,
|
||||||
|
"cb_error_threshold_percentage": 0,
|
||||||
|
"cb_interval_duration": 0,
|
||||||
|
"cb_open_state_timeout": 0,
|
||||||
|
"is_online": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 9,
|
||||||
|
"created_at": "2024-02-27T12:11:09.940033992+03:00",
|
||||||
|
"updated_at": "2024-02-27T12:11:09.940033992+03:00",
|
||||||
|
"deleted_at": null,
|
||||||
|
"server_id": -1,
|
||||||
|
"path": "new/1",
|
||||||
|
"role": 0,
|
||||||
|
"description": "",
|
||||||
|
"order": 0,
|
||||||
|
"deepness": 9,
|
||||||
|
"is_cb_on": true,
|
||||||
|
"cb_request_limit": 0,
|
||||||
|
"cb_min_requests": 0,
|
||||||
|
"cb_error_threshold_percentage": 0,
|
||||||
|
"cb_interval_duration": 0,
|
||||||
|
"cb_open_state_timeout": 5,
|
||||||
|
"is_online": false
|
||||||
|
}]
|
||||||
|
|
||||||
|
const mockStore = createStore({
|
||||||
|
state: {
|
||||||
|
selectedSite: selectedSite,
|
||||||
|
routes: routes,
|
||||||
|
routesState: routesState,
|
||||||
|
},
|
||||||
|
getters: {
|
||||||
|
selectedSite: (state) => state.selectedSite,
|
||||||
|
},
|
||||||
|
mutations: {
|
||||||
|
setSelectedSite: setSelectedSite,
|
||||||
|
setRoutes: setRoutes,
|
||||||
|
setRoutesState: setRoutesState
|
||||||
|
},
|
||||||
|
actions: {
|
||||||
|
addNewRouteLayout: addNewRouteLayout,
|
||||||
|
uploadSiteRoutes: uploadSiteRoutes,
|
||||||
|
updateRouteRow: updateRouteRow,
|
||||||
|
updateRoutesWithApi: updateRoutesWithApi,
|
||||||
|
removeRoute: removeRoute,
|
||||||
|
resetStore
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('actions', () => {
|
||||||
|
//eslint-disable-next-line no-undef
|
||||||
|
it('action addNewRouteLayout - added fields for new Route', async () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
mockStore.dispatch('resetStore')
|
||||||
|
})
|
||||||
|
mockStore.commit('setSelectedSite', {id: 9}) // added id for selected site, need to this action for bind server_id
|
||||||
|
mockStore.dispatch('addNewRouteLayout')
|
||||||
|
expect(mockStore.state.routes[0]).toMatchObject({action: "create", server_id: 9})
|
||||||
|
})
|
||||||
|
//eslint-disable-next-line no-undef
|
||||||
|
it('action updateRouteRow - edited fields values, in selected row', async () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
mockStore.dispatch('resetStore')
|
||||||
|
})
|
||||||
|
|
||||||
|
const editedRoute = {
|
||||||
|
"id": 8,
|
||||||
|
"created_at": "2024-02-27T12:05:45.268921795+03:00",
|
||||||
|
"updated_at": "2024-02-27T12:05:45.268921795+03:00",
|
||||||
|
"deleted_at": null,
|
||||||
|
"server_id": 7,
|
||||||
|
"path": "new/3",
|
||||||
|
"role": 0,
|
||||||
|
"description": "Testing edited route",
|
||||||
|
"order": 0,
|
||||||
|
"deepness": 0,
|
||||||
|
"is_cb_on": true,
|
||||||
|
"cb_request_limit": 3,
|
||||||
|
"cb_min_requests": 5,
|
||||||
|
"cb_error_threshold_percentage": 0,
|
||||||
|
"cb_interval_duration": 7,
|
||||||
|
"cb_open_state_timeout": 0,
|
||||||
|
"is_online": true
|
||||||
|
}
|
||||||
|
mockStore.commit('setRoutes', defaultRoutes)
|
||||||
|
const currentRoute = mockStore.state.routes.find(route => route.id === 8)
|
||||||
|
expect(currentRoute).toMatchObject({"description": "", "is_cb_on": false, "cb_interval_duration": 0}) // check default values fields in selected route
|
||||||
|
mockStore.dispatch('updateRouteRow', editedRoute)
|
||||||
|
const updatedRoute = mockStore.state.routes.find(route => route.id === 8)
|
||||||
|
expect(updatedRoute).toMatchObject({"description": "Testing edited route", "is_cb_on": true, "cb_interval_duration": 7})
|
||||||
|
})
|
||||||
|
//eslint-disable-next-line no-undef
|
||||||
|
it('action updateRouteRow - edited fields, empty values, in selected row', async () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
mockStore.dispatch('resetStore')
|
||||||
|
})
|
||||||
|
|
||||||
|
const editedRoute = {
|
||||||
|
"id": 8,
|
||||||
|
"created_at": "2024-02-27T12:05:45.268921795+03:00",
|
||||||
|
"updated_at": "2024-02-27T12:05:45.268921795+03:00",
|
||||||
|
"deleted_at": null,
|
||||||
|
"server_id": 7,
|
||||||
|
"path": "",
|
||||||
|
"role": 0,
|
||||||
|
"description": null,
|
||||||
|
"order": 0,
|
||||||
|
"deepness": 0,
|
||||||
|
"is_cb_on": true,
|
||||||
|
"cb_request_limit": 0,
|
||||||
|
"cb_min_requests": 5,
|
||||||
|
"cb_error_threshold_percentage": 0,
|
||||||
|
"cb_interval_duration": 7,
|
||||||
|
"cb_open_state_timeout": 0,
|
||||||
|
"is_online": true
|
||||||
|
}
|
||||||
|
mockStore.commit('setRoutes', defaultRoutes)
|
||||||
|
const currentRoute = mockStore.state.routes.find(route => route.id === 8)
|
||||||
|
expect(currentRoute).toMatchObject({"cb_request_limit": 3, "path": "new/3", "description": ""}) // check default values fields in selected route
|
||||||
|
mockStore.dispatch('updateRouteRow', editedRoute)
|
||||||
|
const updatedRoute = mockStore.state.routes.find(route => route.id === 8)
|
||||||
|
expect(updatedRoute).toMatchObject({"cb_request_limit": 0, "path": "", "description": null})
|
||||||
|
})
|
||||||
|
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('helpers', () => {
|
||||||
|
//eslint-disable-next-line no-undef
|
||||||
|
it('added new route to site', async () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
mockStore.dispatch('resetStore')
|
||||||
|
})
|
||||||
|
|
||||||
|
const editedSite = {
|
||||||
|
"id": 3,
|
||||||
|
"created_at": "2024-02-26T17:50:07.191225008+03:00",
|
||||||
|
"updated_at": "2024-02-28T09:41:49.274089436+03:00",
|
||||||
|
"deleted_at": null,
|
||||||
|
"name": "new",
|
||||||
|
"port": 3645,
|
||||||
|
"proxy_ip": "172.25.78.151",
|
||||||
|
"site_ip": "172.25.78.151",
|
||||||
|
"internet_uri": "",
|
||||||
|
"description": "new updated...",
|
||||||
|
"is_online": false
|
||||||
|
}
|
||||||
|
|
||||||
|
const newRoute = {
|
||||||
|
'action': "create",
|
||||||
|
"path": "",
|
||||||
|
"role": 0,
|
||||||
|
"description": "9",
|
||||||
|
"order": 0,
|
||||||
|
"deepness": 7,
|
||||||
|
"is_cb_on": false,
|
||||||
|
"cb_request_limit": 0,
|
||||||
|
"cb_min_requests": 0,
|
||||||
|
"cb_error_threshold_percentage": 0,
|
||||||
|
"cb_interval_duration": 3,
|
||||||
|
"cb_open_state_timeout": 0,
|
||||||
|
"is_online": true
|
||||||
|
}
|
||||||
|
|
||||||
|
mockStore.commit('setRoutes', [newRoute, ...defaultRoutes])
|
||||||
|
const updatedRoutes = addedNewRoute(editedSite, mockStore.state.routes)
|
||||||
|
mockStore.commit('setRoutes', updatedRoutes)
|
||||||
|
const addedNewRouteToStore = mockStore.state.routes.find(route => route.action === 'create')
|
||||||
|
expect(addedNewRouteToStore).not.toBe(undefined)
|
||||||
|
expect(editedSite.id).toEqual(addedNewRouteToStore.server_id) // if true then added new route successfully
|
||||||
|
})
|
||||||
|
//eslint-disable-next-line no-undef
|
||||||
|
it('updated route', async () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
mockStore.dispatch('resetStore')
|
||||||
|
})
|
||||||
|
|
||||||
|
const editedRoute = {
|
||||||
|
"id": 8,
|
||||||
|
"created_at": "2024-02-27T12:05:45.268921795+03:00",
|
||||||
|
"updated_at": "2024-02-27T12:05:45.268921795+03:00",
|
||||||
|
"deleted_at": null,
|
||||||
|
"server_id": 7,
|
||||||
|
"path": "new/3",
|
||||||
|
"role": 0,
|
||||||
|
"description": "",
|
||||||
|
"order": 0,
|
||||||
|
"deepness": 9,
|
||||||
|
"is_cb_on": true,
|
||||||
|
"cb_request_limit": 3,
|
||||||
|
"cb_min_requests": 5,
|
||||||
|
"cb_error_threshold_percentage": 0,
|
||||||
|
"cb_interval_duration": 7,
|
||||||
|
"cb_open_state_timeout": 0,
|
||||||
|
"is_online": true
|
||||||
|
}
|
||||||
|
|
||||||
|
mockStore.commit('setRoutes', defaultRoutes)
|
||||||
|
const currentRouteBeforeEdit = mockStore.state.routes.find(route => route.id === 8)
|
||||||
|
expect(currentRouteBeforeEdit).toMatchObject({"deepness": 0, is_cb_on: false, "cb_interval_duration": 0}) // default params route
|
||||||
|
const updatedRoutes = updatedRoute(editedRoute, mockStore.state.routes)
|
||||||
|
mockStore.commit('setRoutes', updatedRoutes)
|
||||||
|
const updatedRouteToStore = mockStore.state.routes.find(route => route.id === 8)
|
||||||
|
expect(updatedRouteToStore).not.toBe(undefined)
|
||||||
|
expect(updatedRouteToStore).toMatchObject({"deepness": 9, is_cb_on: true, "cb_interval_duration": 7}) // check new params updated route
|
||||||
|
})
|
||||||
|
//eslint-disable-next-line no-undef
|
||||||
|
it('deleted route', async () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
mockStore.dispatch('resetStore')
|
||||||
|
})
|
||||||
|
|
||||||
|
const deleteRouteId = 9
|
||||||
|
|
||||||
|
mockStore.commit('setRoutes', defaultRoutes)
|
||||||
|
const currentRouteBeforeDelete = mockStore.state.routes.find(route => route.id === 9)
|
||||||
|
expect(currentRouteBeforeDelete).toMatchObject({id: 9}) // exists route before deleteting
|
||||||
|
const updatedRoutes = deletedRoute(deleteRouteId, mockStore.state.routes)
|
||||||
|
mockStore.commit('setRoutes', updatedRoutes)
|
||||||
|
const deletedRouteToStore = mockStore.state.routes.find(route => route.id === 9)
|
||||||
|
expect(deletedRouteToStore).toMatchObject({action: 'remove'}) // to deleted route to be added prop action with value 'remove'
|
||||||
|
})
|
||||||
|
//eslint-disable-next-line no-undef
|
||||||
|
it('sorted routes - sort in descending order, sorted prop: updated_at', async () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
mockStore.dispatch('resetStore')
|
||||||
|
})
|
||||||
|
|
||||||
|
const newRoute = {
|
||||||
|
"id": 10,
|
||||||
|
"created_at": "2024-02-27T12:12:53.295785444+03:00",
|
||||||
|
"updated_at": "2024-02-27T12:12:53.295785444+03:00",
|
||||||
|
"deleted_at": null,
|
||||||
|
"server_id": -1,
|
||||||
|
"path": "new/12",
|
||||||
|
"role": 0,
|
||||||
|
"description": "",
|
||||||
|
"order": 1,
|
||||||
|
"deepness": 0,
|
||||||
|
"is_cb_on": false,
|
||||||
|
"cb_request_limit": 3,
|
||||||
|
"cb_min_requests": 5,
|
||||||
|
"cb_error_threshold_percentage": 0,
|
||||||
|
"cb_interval_duration": 0,
|
||||||
|
"cb_open_state_timeout": 0,
|
||||||
|
"is_online": true
|
||||||
|
}
|
||||||
|
|
||||||
|
mockStore.commit('setRoutes', [...defaultRoutes, newRoute])
|
||||||
|
const newRouteIdxBeforeSorting = mockStore.state.routes.findIndex(route => route.id === 10)
|
||||||
|
expect(newRouteIdxBeforeSorting).toBe(3) // default last index this new route in test
|
||||||
|
const updatedRoutes = sortedRoutes(mockStore.state.routes)
|
||||||
|
mockStore.commit('setRoutes', updatedRoutes)
|
||||||
|
const newRouteIdxAfterSorting = mockStore.state.routes.findIndex(route => route.id === 10)
|
||||||
|
expect(newRouteIdxAfterSorting).toBe(0) // check route with id 10 moved to first index after sorting, because new route have updated_at === created_at for nre routes
|
||||||
|
})
|
||||||
|
|
||||||
|
})
|
||||||
@@ -0,0 +1,345 @@
|
|||||||
|
import {describe, expect, beforeEach} from 'vitest'
|
||||||
|
import {createStore} from 'vuex'
|
||||||
|
import {store} from '@store/modules/proxy/index.js'
|
||||||
|
import {addedSite, updatedSite, removedNewSite, deletedSite} from '@store/modules/proxy/helpers.js'
|
||||||
|
|
||||||
|
const sites = store.state.sites
|
||||||
|
const selectedSite = store.state.selectedSite
|
||||||
|
const isSaveData = store.state.isSaveData
|
||||||
|
const routes = store.state.routes
|
||||||
|
const routesState = store.state.routesState
|
||||||
|
|
||||||
|
const setSites = store.mutations.setSites
|
||||||
|
const setSelectedSite = store.mutations.setSelectedSite
|
||||||
|
const setIsSaveData = store.mutations.setIsSaveData
|
||||||
|
const setRoutes = store.mutations.setRoutes
|
||||||
|
const setRoutesState = store.mutations.setRoutesState
|
||||||
|
|
||||||
|
const addNewSiteLayout = store.actions.addNewSiteLayout
|
||||||
|
const createNewSite = store.actions.createNewSite
|
||||||
|
const breakeAddingSite = store.actions.breakeAddingSite
|
||||||
|
const editSelectedSite = store.actions.editSelectedSite
|
||||||
|
const saveSite = store.actions.saveSite
|
||||||
|
const breakSavingSite = store.actions.breakSavingSite
|
||||||
|
const removeSite = store.actions.removeSite
|
||||||
|
const resetStore = store.actions.resetStore
|
||||||
|
|
||||||
|
const defaultSites = [ {
|
||||||
|
"id": 1,
|
||||||
|
"created_at": "2024-02-22T17:08:37.715772388+03:00",
|
||||||
|
"updated_at": "2024-02-26T14:11:38.64094899+03:00",
|
||||||
|
"deleted_at": null,
|
||||||
|
"name": "jsonplaceholder.typicode.com",
|
||||||
|
"port": 9965,
|
||||||
|
"proxy_ip": "172.25.78.153",
|
||||||
|
"site_ip": "172.25.78.153",
|
||||||
|
"internet_uri": "localhost",
|
||||||
|
"description": "localhost",
|
||||||
|
"is_online": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 2,
|
||||||
|
"created_at": "0001-01-01T00:00:00Z",
|
||||||
|
"updated_at": "2024-02-27T17:53:14.070484767+03:00",
|
||||||
|
"deleted_at": null,
|
||||||
|
"name": "new",
|
||||||
|
"port": 3645,
|
||||||
|
"proxy_ip": "172.25.78.151",
|
||||||
|
"site_ip": "172.25.78.151",
|
||||||
|
"internet_uri": "",
|
||||||
|
"description": "create... upd...",
|
||||||
|
"is_online": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 3,
|
||||||
|
"created_at": "2024-02-26T17:50:07.191225008+03:00",
|
||||||
|
"updated_at": "2024-02-28T09:41:49.274089436+03:00",
|
||||||
|
"deleted_at": null,
|
||||||
|
"name": "new",
|
||||||
|
"port": 3645,
|
||||||
|
"proxy_ip": "172.25.78.151",
|
||||||
|
"site_ip": "172.25.78.151",
|
||||||
|
"internet_uri": "",
|
||||||
|
"description": "new updated...",
|
||||||
|
"is_online": false
|
||||||
|
}]
|
||||||
|
|
||||||
|
const mockStore = createStore({
|
||||||
|
state: {
|
||||||
|
sites: sites,
|
||||||
|
selectedSite: selectedSite,
|
||||||
|
isSaveData: isSaveData,
|
||||||
|
routes: routes,
|
||||||
|
routesState: routesState,
|
||||||
|
},
|
||||||
|
getters: {
|
||||||
|
sites: (state) => state.sites,
|
||||||
|
selectedSite: (state) => state.selectedSite,
|
||||||
|
},
|
||||||
|
mutations: {
|
||||||
|
setSites: setSites,
|
||||||
|
setSelectedSite: setSelectedSite,
|
||||||
|
setIsSaveData: setIsSaveData,
|
||||||
|
setRoutes: setRoutes,
|
||||||
|
setRoutesState: setRoutesState
|
||||||
|
},
|
||||||
|
actions: {
|
||||||
|
addNewSiteLayout: addNewSiteLayout,
|
||||||
|
createNewSite: createNewSite,
|
||||||
|
breakeAddingSite: breakeAddingSite,
|
||||||
|
editSelectedSite: editSelectedSite,
|
||||||
|
saveSite: saveSite,
|
||||||
|
breakSavingSite: breakSavingSite,
|
||||||
|
removeSite: removeSite,
|
||||||
|
resetStore
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('mutations', () => {
|
||||||
|
//eslint-disable-next-line no-undef
|
||||||
|
it('Set to empty Services', () => {
|
||||||
|
const state = { sites: sites }
|
||||||
|
setSites(state, null)
|
||||||
|
expect(state.sites).to.equal(null)
|
||||||
|
})
|
||||||
|
//eslint-disable-next-line no-undef
|
||||||
|
it('Add new Service', () => {
|
||||||
|
const newSite = {
|
||||||
|
"id": 34,
|
||||||
|
"created_at": "2024-02-28T15:32:21.187767309+03:00",
|
||||||
|
"updated_at": "2024-02-28T15:32:50.820118413+03:00",
|
||||||
|
"deleted_at": null,
|
||||||
|
"name": "Test site 10",
|
||||||
|
"port": 7575,
|
||||||
|
"proxy_ip": "172.25.78.151",
|
||||||
|
"site_ip": "172.25.78.151",
|
||||||
|
"internet_uri": "",
|
||||||
|
"description": "10 test create",
|
||||||
|
"is_online": true
|
||||||
|
}
|
||||||
|
const updatedSites = [...sites, newSite]
|
||||||
|
const state = { sites: sites }
|
||||||
|
setSites(state, updatedSites)
|
||||||
|
expect(state.sites[0]).to.toMatchObject({"name": "Test site 10", "port": 7575, "id": 34, "is_online": true})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('actions', () => {
|
||||||
|
//eslint-disable-next-line no-undef
|
||||||
|
it('action addNewSiteLayout - added fields for new Service', async () => {
|
||||||
|
mockStore.dispatch('addNewSiteLayout')
|
||||||
|
expect(mockStore.state.sites[0]).toMatchObject({id: -1})
|
||||||
|
})
|
||||||
|
//eslint-disable-next-line no-undef
|
||||||
|
it('action editSelectedSite - edited fields values in selected site', async () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
mockStore.dispatch('resetStore')
|
||||||
|
})
|
||||||
|
mockStore.commit('setSelectedSite', {id: 7, name: 'custom test name', port: 3333, description: 'default'}) // added params - key, vakue for selected site
|
||||||
|
mockStore.dispatch('editSelectedSite', {key: 'name', value: 'edited test name'})
|
||||||
|
mockStore.dispatch('editSelectedSite', {key: 'port', value: 5555})
|
||||||
|
mockStore.dispatch('editSelectedSite', {key: 'description', value: 'updated'})
|
||||||
|
expect(mockStore.state.selectedSite).toMatchObject({name: 'edited test name', port: 5555, description: 'updated'})
|
||||||
|
})
|
||||||
|
//eslint-disable-next-line no-undef
|
||||||
|
it('action editSelectedSite - edited fields empty values in selected site', async () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
mockStore.dispatch('resetStore')
|
||||||
|
})
|
||||||
|
mockStore.commit('setSelectedSite', {id: 7, name: '', port: '', description: ''}) // added params - key, vakue for selected site
|
||||||
|
mockStore.dispatch('editSelectedSite', {key: 'name', value: ''})
|
||||||
|
mockStore.dispatch('editSelectedSite', {key: 'port', value: ''})
|
||||||
|
mockStore.dispatch('editSelectedSite', {key: 'description', value: ''})
|
||||||
|
expect(mockStore.state.selectedSite).toMatchObject({name: '', port: '', description: ''})
|
||||||
|
})
|
||||||
|
//eslint-disable-next-line no-undef
|
||||||
|
it('action breakSavingSite - cleared selected site, replaced to default status routesState', async () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
mockStore.dispatch('resetStore')
|
||||||
|
})
|
||||||
|
mockStore.commit('setSelectedSite', {id: 9, name: 'custom test name', port: 3333, description: 'default'}) // added params - key, vakue for selected site
|
||||||
|
mockStore.commit('setRoutesState', 'active') // added status for RoutesState
|
||||||
|
|
||||||
|
expect(mockStore.state.routesState).toBe('active') // check RoutesState status
|
||||||
|
|
||||||
|
mockStore.dispatch('breakSavingSite', {key: 'name', value: 'edited test name'})
|
||||||
|
expect(mockStore.state.selectedSite).toBeNull()
|
||||||
|
expect(mockStore.state.routesState).toBe('await')
|
||||||
|
})
|
||||||
|
//eslint-disable-next-line no-undef
|
||||||
|
it('action resetStore - cleared all values in store', async () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
mockStore.dispatch('resetStore')
|
||||||
|
})
|
||||||
|
|
||||||
|
expect(mockStore.state.selectedSite).toBeNull() // check default statuses
|
||||||
|
expect(mockStore.state.routesState).toBe('await') // check default statuses
|
||||||
|
expect(mockStore.state.isSaveData).toBe(false) // check default statuses
|
||||||
|
|
||||||
|
mockStore.commit('setSelectedSite', {id: 9, name: 'custom test name', port: 3333, description: 'default'}) // added params - key, vakue for selected site
|
||||||
|
mockStore.commit('setRoutesState', 'active') // updated status for RoutesState
|
||||||
|
mockStore.commit('setIsSaveData', true) // updated status for isSaveData
|
||||||
|
|
||||||
|
expect(mockStore.state.selectedSite).toMatchObject({id: 9, name: 'custom test name', port: 3333, description: 'default'}) // check updated statuses
|
||||||
|
expect(mockStore.state.routesState).toBe('active') // check updated statuses
|
||||||
|
expect(mockStore.state.isSaveData).toBe(true) // check updated statuses
|
||||||
|
|
||||||
|
mockStore.dispatch('resetStore')
|
||||||
|
expect(mockStore.state.selectedSite).toBeNull() // again check default statuses
|
||||||
|
expect(mockStore.state.routesState).toBe('await') // again check default statuses
|
||||||
|
expect(mockStore.state.isSaveData).toBe(false) // again check default statuses
|
||||||
|
})
|
||||||
|
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('helpers', () => {
|
||||||
|
//eslint-disable-next-line no-undef
|
||||||
|
it('added new site', async () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
mockStore.dispatch('resetStore')
|
||||||
|
})
|
||||||
|
|
||||||
|
const newSite = {
|
||||||
|
"id": 34,
|
||||||
|
"created_at": "2024-02-28T15:32:21.187767309+03:00",
|
||||||
|
"updated_at": "2024-02-28T15:32:50.820118413+03:00",
|
||||||
|
"deleted_at": null,
|
||||||
|
"name": "new site 9",
|
||||||
|
"port": 7475,
|
||||||
|
"proxy_ip": "172.25.78.151",
|
||||||
|
"site_ip": "172.25.78.151",
|
||||||
|
"internet_uri": "",
|
||||||
|
"description": "9 create",
|
||||||
|
"is_online": true
|
||||||
|
}
|
||||||
|
mockStore.commit('setSites', defaultSites)
|
||||||
|
const updatedSites = addedSite(newSite, mockStore.state.sites)
|
||||||
|
mockStore.commit('setRoutes', updatedSites)
|
||||||
|
const addedNewSite = mockStore.state.routes.find(route => route.id === 34)
|
||||||
|
expect(addedNewSite).not.toBe(undefined)
|
||||||
|
expect(addedNewSite).toMatchObject(newSite)
|
||||||
|
|
||||||
|
})
|
||||||
|
//eslint-disable-next-line no-undef
|
||||||
|
it('updated site', async () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
mockStore.dispatch('resetStore')
|
||||||
|
})
|
||||||
|
|
||||||
|
const editedSite = {
|
||||||
|
"id": 3,
|
||||||
|
"created_at": "2024-02-26T17:50:07.191225008+03:00",
|
||||||
|
"updated_at": "2024-02-28T09:41:49.274089436+03:00",
|
||||||
|
"deleted_at": null,
|
||||||
|
"name": "edited test name",
|
||||||
|
"port": 5555,
|
||||||
|
"proxy_ip": "172.25.78.151",
|
||||||
|
"site_ip": "172.25.78.151",
|
||||||
|
"internet_uri": "",
|
||||||
|
"description": "new updated... updated site",
|
||||||
|
"is_online": true
|
||||||
|
}
|
||||||
|
|
||||||
|
mockStore.commit('setSites', defaultSites)
|
||||||
|
const currentSiteBeforeEdit = mockStore.state.sites.find(site => site.id === 3)
|
||||||
|
expect(currentSiteBeforeEdit).toMatchObject({name: 'new', port: 3645, description: 'new updated...'}) // default params site
|
||||||
|
const updatedSites = updatedSite(editedSite, mockStore.state.sites)
|
||||||
|
mockStore.commit('setSites', updatedSites)
|
||||||
|
const updatedSiteToStore = mockStore.state.sites.find(site => site.id === 3)
|
||||||
|
expect(updatedSiteToStore).not.toBe(undefined)
|
||||||
|
expect(updatedSiteToStore).toMatchObject(editedSite)
|
||||||
|
})
|
||||||
|
//eslint-disable-next-line no-undef
|
||||||
|
it('updated site - empty params', async () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
mockStore.dispatch('resetStore')
|
||||||
|
})
|
||||||
|
|
||||||
|
const editedSite = {
|
||||||
|
"id": 3,
|
||||||
|
"created_at": "2024-02-26T17:50:07.191225008+03:00",
|
||||||
|
"updated_at": "2024-02-28T09:41:49.274089436+03:00",
|
||||||
|
"deleted_at": null,
|
||||||
|
"name": "",
|
||||||
|
"port": null,
|
||||||
|
"proxy_ip": "172.25.78.151",
|
||||||
|
"site_ip": "172.25.78.151",
|
||||||
|
"internet_uri": "",
|
||||||
|
"description": "",
|
||||||
|
"is_online": true
|
||||||
|
}
|
||||||
|
|
||||||
|
mockStore.commit('setSites', defaultSites)
|
||||||
|
const currentSiteBeforeEdit = mockStore.state.sites.find(site => site.id === 3)
|
||||||
|
expect(currentSiteBeforeEdit).toMatchObject({name: 'new', port: 3645, description: 'new updated...'}) // default params site
|
||||||
|
const updatedSites = updatedSite(editedSite, mockStore.state.sites)
|
||||||
|
mockStore.commit('setSites', updatedSites)
|
||||||
|
const updatedSiteToStore = mockStore.state.sites.find(site => site.id === 3)
|
||||||
|
expect(updatedSiteToStore).not.toBe(undefined)
|
||||||
|
expect(updatedSiteToStore).toMatchObject(editedSite)
|
||||||
|
})
|
||||||
|
//eslint-disable-next-line no-undef
|
||||||
|
it('removed new site before saving to server', async () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
mockStore.dispatch('resetStore')
|
||||||
|
})
|
||||||
|
|
||||||
|
const newSite = {"port": "", "name": "", id: -1}
|
||||||
|
|
||||||
|
mockStore.commit('setSites', defaultSites)
|
||||||
|
const sitesWithNewSite = addedSite(newSite, mockStore.state.sites)
|
||||||
|
mockStore.commit('setSites', sitesWithNewSite)
|
||||||
|
const currentSiteBeforeDelete = mockStore.state.sites[0]
|
||||||
|
expect(currentSiteBeforeDelete).toMatchObject({id: -1}) // default id for new site if this not saving to server
|
||||||
|
const updatedSites = removedNewSite(mockStore.state.sites)
|
||||||
|
mockStore.commit('setSites', updatedSites)
|
||||||
|
const removedSiteToStore = mockStore.state.sites.find(site => site.id === -1)
|
||||||
|
expect(removedSiteToStore).toBe(undefined) // removed site to be undefined in array sites to store
|
||||||
|
})
|
||||||
|
//eslint-disable-next-line no-undef
|
||||||
|
it('deleted selected site before saving to server', async () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
mockStore.dispatch('resetStore')
|
||||||
|
})
|
||||||
|
|
||||||
|
const deleteSiteId = 3
|
||||||
|
|
||||||
|
mockStore.commit('setSites', defaultSites)
|
||||||
|
const currentSiteBeforeDelete = mockStore.state.sites.find(site => site.id === 3)
|
||||||
|
expect(currentSiteBeforeDelete).toMatchObject({id: 3}) // exists site before deleteting
|
||||||
|
const updatedSites = deletedSite(deleteSiteId, mockStore.state.sites)
|
||||||
|
mockStore.commit('setSites', updatedSites)
|
||||||
|
const deletedSiteToStore = mockStore.state.sites.find(site => site.id === 3)
|
||||||
|
expect(deletedSiteToStore).toBe(undefined) // deleted site to be undefined in array sites to store
|
||||||
|
})
|
||||||
|
|
||||||
|
})
|
||||||
|
|
||||||
|
// describe('class Services', () => {
|
||||||
|
// it('upload Sites', async () => {
|
||||||
|
// // const services = new Services(import.meta.env.VITE_API_ADDR, config)
|
||||||
|
// const equalService = [{
|
||||||
|
// "id": 1,
|
||||||
|
// "created_at": "2024-02-22T17:08:37.715772388+03:00",
|
||||||
|
// "updated_at": "2024-02-26T14:11:38.64094899+03:00",
|
||||||
|
// "deleted_at": null,
|
||||||
|
// "name": "jsonplaceholder.typicode.com",
|
||||||
|
// "port": 9965,
|
||||||
|
// "proxy_ip": "172.25.78.153",
|
||||||
|
// "site_ip": "172.25.78.153",
|
||||||
|
// "internet_uri": "localhost",
|
||||||
|
// "description": "localhost",
|
||||||
|
// "is_online": true
|
||||||
|
// }]
|
||||||
|
|
||||||
|
// vi.fn(axios.get).mockResolvedValue({
|
||||||
|
// data: equalService,
|
||||||
|
// })
|
||||||
|
|
||||||
|
// const services = new Services(import.meta.env.VITE_API_ADDR, config)
|
||||||
|
// const uploadedServices = await services.getServices()
|
||||||
|
|
||||||
|
// mockStore.dispatch('uploadSites')
|
||||||
|
// expect(uploadedServices[0]).to.toMatchObject(equalService[0])
|
||||||
|
// })
|
||||||
|
// })
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
import { mount } from '@vue/test-utils'
|
||||||
|
import { expect, test, describe } from 'vitest'
|
||||||
|
import PageHeader from "@atoms/PageHeader.vue"
|
||||||
|
import { createStore } from 'vuex'
|
||||||
|
import {store as proxy} from '@/store/modules/proxy';
|
||||||
|
|
||||||
|
describe("tests PageHeader with vuex", () => {
|
||||||
|
const store = createStore({
|
||||||
|
plugins: [],
|
||||||
|
modules: {
|
||||||
|
proxy
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
test('tests PageHeader with vuex', async () => {
|
||||||
|
const wrapper = mount(PageHeader, {
|
||||||
|
global: {
|
||||||
|
plugins: [store]
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
expect(wrapper.text()).toContain('На главную')
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
@@ -0,0 +1,177 @@
|
|||||||
|
import {mount} from '@vue/test-utils'
|
||||||
|
import {expect, it, describe} from 'vitest'
|
||||||
|
import RoutesList from "@organisms/RoutersEditor/index.vue"
|
||||||
|
// import RoutersEditor from "@organisms/RoutersEditor/index.vue"
|
||||||
|
import routeOptions from '@store/modules/proxy/routeOptions.json'
|
||||||
|
import {createStore} from 'vuex'
|
||||||
|
|
||||||
|
const defaultRoutes = [{
|
||||||
|
"id": 7,
|
||||||
|
"created_at": "2024-02-27T12:02:34.666042252+03:00",
|
||||||
|
"updated_at": "2024-02-27T12:02:34.666042252+03:00",
|
||||||
|
"deleted_at": null,
|
||||||
|
"server_id": -1,
|
||||||
|
"path": "/route",
|
||||||
|
"role": 0,
|
||||||
|
"description": "with route",
|
||||||
|
"order": 0,
|
||||||
|
"deepness": 0,
|
||||||
|
"is_cb_on": true,
|
||||||
|
"cb_request_limit": 0,
|
||||||
|
"cb_min_requests": 7,
|
||||||
|
"cb_error_threshold_percentage": 0,
|
||||||
|
"cb_interval_duration": 0,
|
||||||
|
"cb_open_state_timeout": 0,
|
||||||
|
"is_online": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 8,
|
||||||
|
"created_at": "2024-02-27T12:05:45.268921795+03:00",
|
||||||
|
"updated_at": "2024-02-27T12:05:45.268921795+03:00",
|
||||||
|
"deleted_at": null,
|
||||||
|
"server_id": 7,
|
||||||
|
"path": "new/3",
|
||||||
|
"role": 0,
|
||||||
|
"description": "",
|
||||||
|
"order": 0,
|
||||||
|
"deepness": 0,
|
||||||
|
"is_cb_on": false,
|
||||||
|
"cb_request_limit": 3,
|
||||||
|
"cb_min_requests": 5,
|
||||||
|
"cb_error_threshold_percentage": 0,
|
||||||
|
"cb_interval_duration": 0,
|
||||||
|
"cb_open_state_timeout": 0,
|
||||||
|
"is_online": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 9,
|
||||||
|
"created_at": "2024-02-27T12:11:09.940033992+03:00",
|
||||||
|
"updated_at": "2024-02-27T12:11:09.940033992+03:00",
|
||||||
|
"deleted_at": null,
|
||||||
|
"server_id": -1,
|
||||||
|
"path": "new/1",
|
||||||
|
"role": 0,
|
||||||
|
"description": "",
|
||||||
|
"order": 0,
|
||||||
|
"deepness": 9,
|
||||||
|
"is_cb_on": true,
|
||||||
|
"cb_request_limit": 0,
|
||||||
|
"cb_min_requests": 0,
|
||||||
|
"cb_error_threshold_percentage": 0,
|
||||||
|
"cb_interval_duration": 0,
|
||||||
|
"cb_open_state_timeout": 5,
|
||||||
|
"is_online": false
|
||||||
|
}]
|
||||||
|
|
||||||
|
const store = createStore({
|
||||||
|
plugins: [],
|
||||||
|
modules: {
|
||||||
|
proxy: {
|
||||||
|
state: {
|
||||||
|
routes: [],
|
||||||
|
routesState: "await",
|
||||||
|
routesLib: {},
|
||||||
|
routeOptions: routeOptions
|
||||||
|
},
|
||||||
|
getters: {
|
||||||
|
routes: (state) => state.routes,
|
||||||
|
routesState: (state) => state.routesState,
|
||||||
|
routesLib: (state) => state.routesLib,
|
||||||
|
routeOptions: (state) => state.routeOptions,
|
||||||
|
},
|
||||||
|
mutations: {
|
||||||
|
setRoutes: (state, updval) => state.routes = updval,
|
||||||
|
setRoutesState: (state, updval) => state.routesState = updval,
|
||||||
|
setRoutesLib: (state, updval) => state.routesLib = updval,
|
||||||
|
},
|
||||||
|
actions: {
|
||||||
|
uploadSiteRoutes: async ({commit}, siteProps) => {
|
||||||
|
commit('setSelectedSite', siteProps)
|
||||||
|
commit('setRoutes', defaultRoutes)
|
||||||
|
},
|
||||||
|
updateRouteRow: () => ({}),
|
||||||
|
removeRoute: () => ({}),
|
||||||
|
breateAddingRoute: () => ({}),
|
||||||
|
},
|
||||||
|
namespaced: true,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
describe("Routes", () => {
|
||||||
|
|
||||||
|
it("Not renders routes and buttons in RoutesList, if routesState value not 'active'", async () => {
|
||||||
|
|
||||||
|
const wrapper = mount(RoutesList, {
|
||||||
|
global: {
|
||||||
|
plugins: [store],
|
||||||
|
},
|
||||||
|
shallow: true,
|
||||||
|
})
|
||||||
|
|
||||||
|
expect(wrapper.html()).not.toContain('Добавить роут')
|
||||||
|
expect(wrapper.html()).not.toContain('Закрыть')
|
||||||
|
})
|
||||||
|
|
||||||
|
it("Not renders routes - empty array in RoutesList, if routesState value 'active', renders buttons", async () => {
|
||||||
|
|
||||||
|
store.commit('proxy/setRoutesState', 'active')
|
||||||
|
|
||||||
|
const getters = {...store.getters }
|
||||||
|
const routesState = getters['proxy/routesState']
|
||||||
|
|
||||||
|
const wrapper = mount(RoutesList, {
|
||||||
|
global: {
|
||||||
|
plugins: [store],
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
open: false,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
if (routesState === 'active') {
|
||||||
|
await wrapper.setData({ open: true })
|
||||||
|
}
|
||||||
|
|
||||||
|
expect(wrapper.html()).toContain('Добавить роут')
|
||||||
|
expect(wrapper.html()).toContain('Закрыть')
|
||||||
|
expect(wrapper.html()).not.toContain('ri-delete-bin-line')
|
||||||
|
})
|
||||||
|
|
||||||
|
it("Renders routes and buttons in RoutesList, if routesState value 'active' and not empty routes array", async () => {
|
||||||
|
|
||||||
|
store.commit('proxy/setRoutesState', 'active')
|
||||||
|
store.commit('proxy/setRoutes', defaultRoutes)
|
||||||
|
|
||||||
|
const getters = {...store.getters }
|
||||||
|
const routesState = getters['proxy/routesState']
|
||||||
|
const routes = getters['proxy/routes']
|
||||||
|
|
||||||
|
const wrapper = mount(RoutesList, {
|
||||||
|
global: {
|
||||||
|
plugins: [store],
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
open: false,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
console.log('routes 3 test', routes)
|
||||||
|
|
||||||
|
if (routesState === 'active') {
|
||||||
|
await wrapper.setData({ open: true })
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('wrapper 3 test', wrapper.html())
|
||||||
|
|
||||||
|
expect(wrapper.html()).toContain('Добавить роут')
|
||||||
|
expect(wrapper.html()).toContain('Закрыть')
|
||||||
|
// expect(wrapper.html()).toContain('ri-delete-bin-line')
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
@@ -0,0 +1,306 @@
|
|||||||
|
import SiteList from '@organisms/SitesEditor/SiteList.vue'
|
||||||
|
import SiteCard from '@organisms/SitesEditor/SiteCard.vue'
|
||||||
|
import EditCard from '@organisms/SitesEditor/EditCard.vue'
|
||||||
|
import Input from '@atoms/Input.vue'
|
||||||
|
import Textarea from '@atoms/Textarea.vue'
|
||||||
|
import DoubleSwitch from '@atoms/DoubleSwitch.vue'
|
||||||
|
import {mount} from '@vue/test-utils'
|
||||||
|
import {createStore} from 'vuex'
|
||||||
|
import {describe, expect, it} from 'vitest'
|
||||||
|
|
||||||
|
const defaultSites = [ {
|
||||||
|
"id": 1,
|
||||||
|
"created_at": "2024-02-22T17:08:37.715772388+03:00",
|
||||||
|
"updated_at": "2024-02-26T14:11:38.64094899+03:00",
|
||||||
|
"deleted_at": null,
|
||||||
|
"name": "jsonplaceholder.typicode.com",
|
||||||
|
"port": 9965,
|
||||||
|
"proxy_ip": "172.25.78.153",
|
||||||
|
"site_ip": "172.25.78.153",
|
||||||
|
"internet_uri": "localhost",
|
||||||
|
"description": "localhost",
|
||||||
|
"is_online": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 2,
|
||||||
|
"created_at": "0001-01-01T00:00:00Z",
|
||||||
|
"updated_at": "2024-02-27T17:53:14.070484767+03:00",
|
||||||
|
"deleted_at": null,
|
||||||
|
"name": "new",
|
||||||
|
"port": 3645,
|
||||||
|
"proxy_ip": "172.25.78.151",
|
||||||
|
"site_ip": "172.25.78.151",
|
||||||
|
"internet_uri": "",
|
||||||
|
"description": "create... upd...",
|
||||||
|
"is_online": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 3,
|
||||||
|
"created_at": "2024-02-26T17:50:07.191225008+03:00",
|
||||||
|
"updated_at": "2024-02-28T09:41:49.274089436+03:00",
|
||||||
|
"deleted_at": null,
|
||||||
|
"name": "new",
|
||||||
|
"port": 3645,
|
||||||
|
"proxy_ip": "172.25.78.151",
|
||||||
|
"site_ip": "172.25.78.151",
|
||||||
|
"internet_uri": "",
|
||||||
|
"description": "new updated...",
|
||||||
|
"is_online": false
|
||||||
|
}]
|
||||||
|
|
||||||
|
const store = createStore({
|
||||||
|
plugins: [],
|
||||||
|
modules: {
|
||||||
|
proxy: {
|
||||||
|
state: {
|
||||||
|
sites: [],
|
||||||
|
sitesState: 'loading',
|
||||||
|
selectedSite: null,
|
||||||
|
isSaveData: false,
|
||||||
|
},
|
||||||
|
getters: {
|
||||||
|
sites: (state) => state.sites,
|
||||||
|
sitesState: (state) => state.sitesState,
|
||||||
|
selectedSite: (state) => state.selectedSite,
|
||||||
|
isSaveData: (state) => state.isSaveData,
|
||||||
|
},
|
||||||
|
mutations: {
|
||||||
|
setSites: (state, updval) => state.sites = updval,
|
||||||
|
setSitesState: (state, updval) => state.sitesState = updval,
|
||||||
|
setSelectedSite: (state, updval) => state.selectedSite = updval,
|
||||||
|
setIsSaveData: (state, updval) => state.isSaveData = updval,
|
||||||
|
},
|
||||||
|
actions: {
|
||||||
|
uploadSites({commit}, sites) {
|
||||||
|
commit('setSites', sites)
|
||||||
|
},
|
||||||
|
uploadSiteRoutes: async ({commit}, siteProps) => {
|
||||||
|
commit('setSelectedSite', siteProps)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
namespaced: true,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
describe("Services", () => {
|
||||||
|
|
||||||
|
it("Not renders SiteCard and NewSiteButton in SiteList, if sitesState value not 'active', renders SiteList, fwb-spinner", async () => {
|
||||||
|
|
||||||
|
const wrapper = mount(SiteList, {
|
||||||
|
global: {
|
||||||
|
plugins: [store],
|
||||||
|
},
|
||||||
|
shallow: true,
|
||||||
|
})
|
||||||
|
|
||||||
|
expect(wrapper.html()).not.toContain('Добавить сайт')
|
||||||
|
expect(wrapper.find(".text-slate-700").text()).toBe("Загрузка сайтов...")
|
||||||
|
})
|
||||||
|
|
||||||
|
it("Not renders fwb-spinner, SiteCard if sitesState value 'active', sites empty array, renders SiteList, NewSiteButton", async () => {
|
||||||
|
|
||||||
|
store.commit('proxy/setSitesState', 'active')
|
||||||
|
|
||||||
|
const wrapper = mount(SiteList, {
|
||||||
|
global: {
|
||||||
|
plugins: [store],
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
expect(wrapper.html()).not.toContain('Загрузка сайтов...')
|
||||||
|
expect(wrapper.find('span').exists()).toBe(false)
|
||||||
|
expect(wrapper.html()).toContain('Добавить сайт')
|
||||||
|
})
|
||||||
|
|
||||||
|
it("Not renders fwb-spinner, sites array not empty, sitesState value 'active', renders SiteList, NewSiteButton, SiteCard", async () => {
|
||||||
|
|
||||||
|
store.dispatch('proxy/uploadSites', defaultSites)
|
||||||
|
store.commit('proxy/setSitesState', 'active')
|
||||||
|
|
||||||
|
const wrapper = mount(SiteList, {
|
||||||
|
global: {
|
||||||
|
mocks: {
|
||||||
|
$store: store
|
||||||
|
}
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
expect(wrapper.find('span').exists()).toBe(true)
|
||||||
|
expect(wrapper.find('i').exists()).toBe(true)
|
||||||
|
expect(wrapper.html()).toContain('ri-pencil-line')
|
||||||
|
expect(wrapper.html()).toContain('Добавить сайт')
|
||||||
|
|
||||||
|
})
|
||||||
|
|
||||||
|
it("dynamicHeight null in SiteList component", async () => {
|
||||||
|
|
||||||
|
store.dispatch('proxy/uploadSites', defaultSites)
|
||||||
|
store.commit('proxy/setSitesState', 'active')
|
||||||
|
|
||||||
|
// const getters = {...store.getters }
|
||||||
|
// console.log('sites 3 test', getters['proxy/sites'])
|
||||||
|
|
||||||
|
const wrapper = mount(SiteList, {
|
||||||
|
global: {
|
||||||
|
mocks: {
|
||||||
|
$store: store
|
||||||
|
}
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
dynamicHeight: null
|
||||||
|
}
|
||||||
|
},
|
||||||
|
shallow: true,
|
||||||
|
})
|
||||||
|
|
||||||
|
console.log('wrapper.html', wrapper.html())
|
||||||
|
|
||||||
|
expect(wrapper.classes()).not.toContain('shadow-lg')
|
||||||
|
expect(wrapper.classes()).not.toContain('p-3')
|
||||||
|
expect(wrapper.html()).not.toContain('style="max-height: 100px;"')
|
||||||
|
|
||||||
|
})
|
||||||
|
|
||||||
|
it("set dynamicHeight, this data prop not null in SiteList component", async () => {
|
||||||
|
|
||||||
|
console.log('store - 5 test', store)
|
||||||
|
store.dispatch('proxy/uploadSites', defaultSites)
|
||||||
|
store.commit('proxy/setSitesState', 'active')
|
||||||
|
|
||||||
|
const wrapper = mount(SiteList, {
|
||||||
|
global: {
|
||||||
|
plugins: [store],
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
dynamicHeight: null
|
||||||
|
}
|
||||||
|
},
|
||||||
|
shallow: true,
|
||||||
|
})
|
||||||
|
|
||||||
|
await wrapper.setData({ dynamicHeight: 100 })
|
||||||
|
|
||||||
|
expect(wrapper.html()).toContain('style="max-height: 100px;"')
|
||||||
|
|
||||||
|
})
|
||||||
|
|
||||||
|
it("renders buttons - 'Отменить' and 'Удалить' in this site, after click to delete button", async () => {
|
||||||
|
const wrapper = mount(SiteCard, {
|
||||||
|
global: {
|
||||||
|
plugins: [store],
|
||||||
|
},
|
||||||
|
data () {
|
||||||
|
return {
|
||||||
|
isDelete: false,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
await wrapper.setData({ isDelete: true })
|
||||||
|
|
||||||
|
await wrapper.get('.ri-delete-bin-line').trigger('click')
|
||||||
|
|
||||||
|
expect(wrapper.html()).toContain('Отменить')
|
||||||
|
expect(wrapper.html()).toContain('Удалить')
|
||||||
|
})
|
||||||
|
|
||||||
|
it("render EditCard, ids selectedSite and prop id is Equal", async () => {
|
||||||
|
|
||||||
|
const editSite = {
|
||||||
|
"id": 1,
|
||||||
|
"created_at": "2024-03-04T14:30:40.64845074+03:00",
|
||||||
|
"updated_at": "2024-03-04T14:30:40.64845074+03:00",
|
||||||
|
"deleted_at": null,
|
||||||
|
"name": "jsonplaceholder.typicode.com",
|
||||||
|
"port": 9965,
|
||||||
|
"proxy_ip": "172.25.78.153",
|
||||||
|
"site_ip": "https://jsonplaceholder.typicode.com/",
|
||||||
|
"internet_uri": "localhost",
|
||||||
|
"description": "localhost",
|
||||||
|
"is_online": true
|
||||||
|
}
|
||||||
|
|
||||||
|
store.dispatch('proxy/uploadSites', defaultSites)
|
||||||
|
store.commit('proxy/setSitesState', 'active')
|
||||||
|
store.dispatch('proxy/uploadSiteRoutes', editSite)
|
||||||
|
|
||||||
|
const wrapper = mount(EditCard, {
|
||||||
|
global: {
|
||||||
|
plugins: [store],
|
||||||
|
stubs: ['SiteCard', 'EditCard'],
|
||||||
|
},
|
||||||
|
props: {
|
||||||
|
id: 1,
|
||||||
|
},
|
||||||
|
|
||||||
|
})
|
||||||
|
|
||||||
|
const editCard = wrapper.getComponent(EditCard)
|
||||||
|
|
||||||
|
expect(editCard.props('id')).toEqual(editSite.id)
|
||||||
|
expect(wrapper.html()).toContain('ri-close-line')
|
||||||
|
expect(wrapper.html()).toContain('Онлайн')
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
it("render EditCard, set fields in selectedSite", async () => {
|
||||||
|
|
||||||
|
const editSite = {
|
||||||
|
"id": 1,
|
||||||
|
"created_at": "2024-03-04T14:30:40.64845074+03:00",
|
||||||
|
"updated_at": "2024-03-04T14:30:40.64845074+03:00",
|
||||||
|
"deleted_at": null,
|
||||||
|
"name": "jsonplaceholder.typicode.com",
|
||||||
|
"port": 9965,
|
||||||
|
"proxy_ip": "172.25.78.153",
|
||||||
|
"site_ip": "https://jsonplaceholder.typicode.com/",
|
||||||
|
"internet_uri": "localhost",
|
||||||
|
"description": "localhost",
|
||||||
|
"is_online": true
|
||||||
|
}
|
||||||
|
|
||||||
|
store.dispatch('proxy/uploadSites', defaultSites)
|
||||||
|
store.commit('proxy/setSitesState', 'active')
|
||||||
|
store.dispatch('proxy/uploadSiteRoutes', editSite)
|
||||||
|
|
||||||
|
expect(store.getters['proxy/selectedSite']).toEqual(editSite) // check default props values in selected site
|
||||||
|
|
||||||
|
store.commit('proxy/setSelectedSite', {...editSite, is_online: false, name: 'edit name in EditCard', description: 'edit description in EditCard'})
|
||||||
|
|
||||||
|
const wrapper = mount(EditCard, {
|
||||||
|
global: {
|
||||||
|
plugins: [store],
|
||||||
|
stubs: ['Input', 'Textarea', 'DoubleSwitch', 'EditCard'],
|
||||||
|
},
|
||||||
|
props: {
|
||||||
|
id: 1,
|
||||||
|
},
|
||||||
|
|
||||||
|
})
|
||||||
|
|
||||||
|
const editCard = wrapper.getComponent(EditCard)
|
||||||
|
|
||||||
|
const inputField = wrapper.getComponent(Input)
|
||||||
|
const textareaField = wrapper.getComponent(Textarea)
|
||||||
|
const doubleSwitchField = wrapper.getComponent(DoubleSwitch)
|
||||||
|
|
||||||
|
const nameField = inputField.props().value
|
||||||
|
const descriptionField = textareaField.props().value
|
||||||
|
const isOnlineField = doubleSwitchField.props().isCheck
|
||||||
|
|
||||||
|
const getters = {...store.getters }
|
||||||
|
const name = getters['proxy/selectedSite'].name // selected site name
|
||||||
|
const description = getters['proxy/selectedSite'].description // selected site description
|
||||||
|
const isOnline = getters['proxy/selectedSite'].is_online // selected site is_online
|
||||||
|
|
||||||
|
expect(editCard.props('id')).toEqual(editSite.id)
|
||||||
|
expect(name).toEqual(nameField)
|
||||||
|
expect(description).toEqual(descriptionField)
|
||||||
|
expect(isOnline).toEqual(isOnlineField)
|
||||||
|
})
|
||||||
|
|
||||||
|
})
|
||||||
@@ -0,0 +1,65 @@
|
|||||||
|
import { mount} from '@vue/test-utils'
|
||||||
|
import { expect, test, describe, vi } from 'vitest'
|
||||||
|
import App from '@/App.vue';
|
||||||
|
import { createStore } from 'vuex'
|
||||||
|
import {store as proxy} from '@/store/modules/proxy';
|
||||||
|
import axios from "axios";
|
||||||
|
|
||||||
|
vi.mock('axios')
|
||||||
|
|
||||||
|
describe("tests App mounted with vuex", () => {
|
||||||
|
const store = createStore({
|
||||||
|
global: {
|
||||||
|
stubs: {
|
||||||
|
|
||||||
|
}
|
||||||
|
},
|
||||||
|
plugins: [],
|
||||||
|
modules: {
|
||||||
|
proxy
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
axios.get.mockResolvedValue({
|
||||||
|
data: [
|
||||||
|
{
|
||||||
|
"id": 1,
|
||||||
|
"created_at": "2024-02-22T17:08:37.715772388+03:00",
|
||||||
|
"updated_at": "2024-02-26T14:11:38.64094899+03:00",
|
||||||
|
"deleted_at": null,
|
||||||
|
"name": "jsonplaceholder.typicode.com",
|
||||||
|
"port": 9965,
|
||||||
|
"proxy_ip": "172.25.78.153",
|
||||||
|
"site_ip": "172.25.78.153",
|
||||||
|
"internet_uri": "localhost",
|
||||||
|
"description": "localhost",
|
||||||
|
"is_online": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 2,
|
||||||
|
"created_at": "0001-01-01T00:00:00Z",
|
||||||
|
"updated_at": "2024-02-26T17:38:00.255479017+03:00",
|
||||||
|
"deleted_at": null,
|
||||||
|
"name": "new",
|
||||||
|
"port": 3645,
|
||||||
|
"proxy_ip": "172.25.78.151",
|
||||||
|
"site_ip": "172.25.78.151",
|
||||||
|
"internet_uri": "",
|
||||||
|
"description": "create...",
|
||||||
|
"is_online": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
})
|
||||||
|
|
||||||
|
test('tests App mounted with vuex', async () => {
|
||||||
|
const wrapper = mount(App, {
|
||||||
|
global: {
|
||||||
|
plugins: [store]
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
expect(wrapper.text()).toContain('На главную')
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
import { defineConfig } from 'vite'
|
||||||
|
import vue from '@vitejs/plugin-vue'
|
||||||
|
import {fileURLToPath, URL} from 'node:url';
|
||||||
|
|
||||||
|
// https://vitejs.dev/config/
|
||||||
|
export default defineConfig({
|
||||||
|
plugins: [vue()],
|
||||||
|
resolve: {
|
||||||
|
alias: {
|
||||||
|
'@': fileURLToPath(new URL('/src', import.meta.url)),
|
||||||
|
'@store': fileURLToPath(new URL('/src/store', import.meta.url)),
|
||||||
|
'@cmp': fileURLToPath(new URL('/src/components', import.meta.url)),
|
||||||
|
'@helpers': fileURLToPath(new URL('/src/helpers', import.meta.url)),
|
||||||
|
'@atoms': fileURLToPath(new URL('./src/components/1_atoms', import.meta.url)),
|
||||||
|
'@molecules': fileURLToPath(new URL('./src/components/2_molecules', import.meta.url)),
|
||||||
|
'@organisms': fileURLToPath(new URL('./src/components/3_organisms', import.meta.url)),
|
||||||
|
'@pages': fileURLToPath(new URL('./src/components/4_pages', import.meta.url)),
|
||||||
|
}
|
||||||
|
},
|
||||||
|
test: {
|
||||||
|
globals:true,
|
||||||
|
environment: 'happy-dom',
|
||||||
|
}
|
||||||
|
})
|
||||||
@@ -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"
|
||||||
|
|
||||||
@@ -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": []
|
||||||
|
}]
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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?
|
||||||
Vendored
+3
@@ -0,0 +1,3 @@
|
|||||||
|
{
|
||||||
|
"recommendations": ["Vue.volar", "Vue.vscode-typescript-vue-plugin"]
|
||||||
|
}
|
||||||
@@ -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
|
||||||
@@ -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).
|
||||||
@@ -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>
|
||||||
@@ -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"
|
||||||
|
}
|
||||||
|
}
|
||||||
Loaded 100 of 212 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user