Initial commit
This commit is contained in:
41
proxy-ui-app/src/App.vue
Normal file
41
proxy-ui-app/src/App.vue
Normal file
@@ -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>
|
||||
1
proxy-ui-app/src/assets/vue.svg
Normal file
1
proxy-ui-app/src/assets/vue.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="37.07" height="36" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 198"><path fill="#41B883" d="M204.8 0H256L128 220.8L0 0h97.92L128 51.2L157.44 0h47.36Z"></path><path fill="#41B883" d="m0 0l128 220.8L256 0h-51.2L128 132.48L50.56 0H0Z"></path><path fill="#35495E" d="M50.56 0L128 133.12L204.8 0h-47.36L128 51.2L97.92 0H50.56Z"></path></svg>
|
||||
|
After Width: | Height: | Size: 496 B |
102
proxy-ui-app/src/components/1_atoms/DoubleSwitch.vue
Normal file
102
proxy-ui-app/src/components/1_atoms/DoubleSwitch.vue
Normal file
@@ -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>
|
||||
59
proxy-ui-app/src/components/1_atoms/Input.vue
Normal file
59
proxy-ui-app/src/components/1_atoms/Input.vue
Normal file
@@ -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>
|
||||
36
proxy-ui-app/src/components/1_atoms/PageHeader.vue
Normal file
36
proxy-ui-app/src/components/1_atoms/PageHeader.vue
Normal file
@@ -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>
|
||||
69
proxy-ui-app/src/components/1_atoms/Textarea.vue
Normal file
69
proxy-ui-app/src/components/1_atoms/Textarea.vue
Normal file
@@ -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>
|
||||
108
proxy-ui-app/src/components/3_organisms/SitesEditor/EditCard.vue
Normal file
108
proxy-ui-app/src/components/3_organisms/SitesEditor/EditCard.vue
Normal file
@@ -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>
|
||||
165
proxy-ui-app/src/components/3_organisms/SitesEditor/SiteCard.vue
Normal file
165
proxy-ui-app/src/components/3_organisms/SitesEditor/SiteCard.vue
Normal file
@@ -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>
|
||||
0
proxy-ui-app/src/components/Loader.vue
Normal file
0
proxy-ui-app/src/components/Loader.vue
Normal file
33
proxy-ui-app/src/components/NewSiteButton.vue
Normal file
33
proxy-ui-app/src/components/NewSiteButton.vue
Normal file
@@ -0,0 +1,33 @@
|
||||
<script>
|
||||
import {mapActions} from 'vuex'
|
||||
|
||||
export default {
|
||||
name: 'NewSiteButton',
|
||||
props: {
|
||||
name: {
|
||||
default: "",
|
||||
type: String
|
||||
},
|
||||
port: {
|
||||
default: "",
|
||||
type: String
|
||||
},
|
||||
site: {
|
||||
default: () => ({}),
|
||||
type: Object
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
...mapActions('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>
|
||||
160
proxy-ui-app/src/helpers/Services/Services.js
Normal file
160
proxy-ui-app/src/helpers/Services/Services.js
Normal file
@@ -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
|
||||
*/
|
||||
42
proxy-ui-app/src/helpers/Services/adapter/adapter.js
Normal file
42
proxy-ui-app/src/helpers/Services/adapter/adapter.js
Normal file
@@ -0,0 +1,42 @@
|
||||
/**
|
||||
* Каллбек изменнеия, ожидает что вернется измененный объект
|
||||
*
|
||||
* @callback adapterCallback
|
||||
* @param {Object} modifiedObject - новый объект
|
||||
* @param {Object} originalObject - изначальный объект
|
||||
* @returns {Object} измененный коллбэком объект
|
||||
*/
|
||||
|
||||
/**
|
||||
*
|
||||
* Если у newKey значение отстутсвует - null, '', false, 0, то oldKey удаляется из объекта
|
||||
*
|
||||
* @param {Object} targetObject
|
||||
* @param {Object} params
|
||||
* @param {Object} params.config - oldKey: null // delete oldKey
|
||||
* @param {adapterCallback | undefined} params.callback
|
||||
* @returns {Object}
|
||||
*/
|
||||
const convertObject = (targetObject, {config, callback = (v) => v}) => {
|
||||
let newObject = {}
|
||||
for (const key in config) {
|
||||
newObject[config[key]] = targetObject[key]
|
||||
}
|
||||
return callback(newObject, targetObject)
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* Если у newKey значение отстутсвует - null, '', false, 0, то oldKey удаляется из объекта
|
||||
*
|
||||
* @param {Array} targetList
|
||||
* @param {Object} options - oldKey: null // delete oldKey
|
||||
* @param {Object} options.config - oldKey: null // delete oldKey
|
||||
* @param {adapterCallback | undefined} options.callback
|
||||
* @returns {Object}
|
||||
*/
|
||||
const convertList = (targetList, {config, callback = (v) => v}) => {
|
||||
return targetList.map((targetObject) => convertObject(targetObject, {config, callback}))
|
||||
}
|
||||
|
||||
export {convertList, convertObject}
|
||||
39
proxy-ui-app/src/helpers/Services/apiHelpers.js
Normal file
39
proxy-ui-app/src/helpers/Services/apiHelpers.js
Normal file
@@ -0,0 +1,39 @@
|
||||
import axios from "axios";
|
||||
|
||||
const post = async (path, data, onError) => {
|
||||
return await axios.post(path, data)
|
||||
.then(r => r.data)
|
||||
.catch(error => {
|
||||
onError && onError(error)
|
||||
console.error('Error post request:', error)
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
const get = async (path) => {
|
||||
return await axios.get(path)
|
||||
.catch(error => {
|
||||
console.error('Error get request:', error)
|
||||
})
|
||||
}
|
||||
|
||||
const put = async (path, data, id, onError) => {
|
||||
return await axios.put(`${path}/${id}`, data)
|
||||
.then(r => r.data)
|
||||
.catch(error => {
|
||||
onError && onError(error)
|
||||
console.error('Error put request:', error)
|
||||
})
|
||||
}
|
||||
|
||||
const remove = async (path, id, onError) => {
|
||||
return await axios.delete(`${path}/${id}`)
|
||||
.then(r => r.data)
|
||||
.catch(error => {
|
||||
onError && onError(error)
|
||||
console.error('Error delete request:', error)
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
export {get, post, put, remove}
|
||||
232
proxy-ui-app/src/helpers/server-routes/Routes.js
Normal file
232
proxy-ui-app/src/helpers/server-routes/Routes.js
Normal file
@@ -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
|
||||
43
proxy-ui-app/src/helpers/server-routes/adapter/adapter.js
Normal file
43
proxy-ui-app/src/helpers/server-routes/adapter/adapter.js
Normal file
@@ -0,0 +1,43 @@
|
||||
/**
|
||||
* Каллбек изменнеия, ожидает что вернется измененный объект
|
||||
*
|
||||
* @callback adapterCallback
|
||||
* @param {Object} modifiedObject - новый объект
|
||||
* @param {Object} originalObject - изначальный объект
|
||||
* @returns {Object} измененный коллбэком объект
|
||||
*/
|
||||
|
||||
/**
|
||||
*
|
||||
* Если у newKey значение отстутсвует - null, '', false, 0, то oldKey удаляется из объекта
|
||||
*
|
||||
* @param {Object} targetObject
|
||||
* @param {Object} params
|
||||
* @param {Object} params.config - oldKey: null // delete oldKey
|
||||
* @param {adapterCallback | undefined} params.callback
|
||||
* @returns {Object}
|
||||
*/
|
||||
const convertObject = (targetObject, {config, callback = (v) => v}) => {
|
||||
let newObject = {}
|
||||
for (const key in config) {
|
||||
newObject[config[key]] = targetObject[key]
|
||||
}
|
||||
return callback(newObject, targetObject)
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* Если у newKey значение отстутсвует - null, '', false, 0, то oldKey удаляется из объекта
|
||||
*
|
||||
* @param {Array} targetList
|
||||
* @param {Object} options - oldKey: null // delete oldKey
|
||||
* @param {Object} options.config - oldKey: null // delete oldKey
|
||||
* @param {adapterCallback | undefined} options.callback
|
||||
* @returns {Object}
|
||||
*/
|
||||
const convertList = (targetList, {config, callback = (v) => v}) => {
|
||||
return targetList.map((targetObject) => convertObject(targetObject, {config, callback}))
|
||||
}
|
||||
|
||||
|
||||
export {convertList, convertObject}
|
||||
38
proxy-ui-app/src/helpers/server-routes/apiHelpers.js
Normal file
38
proxy-ui-app/src/helpers/server-routes/apiHelpers.js
Normal file
@@ -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}
|
||||
0
proxy-ui-app/src/helpers/server-routes/helpers.js
Normal file
0
proxy-ui-app/src/helpers/server-routes/helpers.js
Normal file
14
proxy-ui-app/src/main.js
Normal file
14
proxy-ui-app/src/main.js
Normal file
@@ -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')
|
||||
18
proxy-ui-app/src/ramda-vue.js
Normal file
18
proxy-ui-app/src/ramda-vue.js
Normal file
@@ -0,0 +1,18 @@
|
||||
import * as R from 'ramda'
|
||||
|
||||
const RamdaVue = {
|
||||
install: (app) => {
|
||||
app.$R = R;
|
||||
app.config.globalProperties.$R = R;
|
||||
|
||||
R.ifElse(
|
||||
R.and(R.compose(R.not, R.isNil), R.has("Vue")),
|
||||
(win) => {
|
||||
win.Vue.use(RamdaVue);
|
||||
},
|
||||
() => {}
|
||||
)(window);
|
||||
},
|
||||
};
|
||||
|
||||
export default RamdaVue;
|
||||
19
proxy-ui-app/src/store/index.js
Normal file
19
proxy-ui-app/src/store/index.js
Normal file
@@ -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;
|
||||
}
|
||||
65
proxy-ui-app/src/store/modules/proxy/StaticData.js
Normal file
65
proxy-ui-app/src/store/modules/proxy/StaticData.js
Normal file
@@ -0,0 +1,65 @@
|
||||
const config = {
|
||||
id: "id",
|
||||
created_at: "created_at",
|
||||
updated_at: "updated_at",
|
||||
deleted_at: "deleted_at",
|
||||
name: "name",
|
||||
port: "port",
|
||||
proxy_ip: "proxy_ip",
|
||||
internet_uri: "internet_uri",
|
||||
description: "description",
|
||||
is_online: "is_online",
|
||||
site_ip: "device_ip",
|
||||
}
|
||||
|
||||
const configRoutes = {
|
||||
id: "id",
|
||||
created_at: "created_at",
|
||||
updated_at: "updated_at",
|
||||
deleted_at: "deleted_at",
|
||||
server_id: "server_id",
|
||||
path: "path",
|
||||
role: "role",
|
||||
description: "description",
|
||||
order: "order",
|
||||
deepness: "deepness",
|
||||
is_cb_on: "is_cb_on",
|
||||
is_online: "is_online",
|
||||
cb_request_limit: "cb_request_limit",
|
||||
cb_min_requests: "cb_min_requests",
|
||||
cb_error_threshold_percentage: "cb_error_threshold_percentage",
|
||||
cb_interval_duration: "cb_interval_duration",
|
||||
cb_open_state_timeout: "cb_open_state_timeout",
|
||||
site_ip: "device_ip",
|
||||
}
|
||||
|
||||
const routes = [
|
||||
{"path": "/socket.io", "backend": "http://backend:8000", "role": "*", "site": "cdrp"},
|
||||
{"path": "/socket.io", "backend": "http://backend:8000", "role": "*", "site": "cdrp"},
|
||||
{"path": "/socket.io/*", "backend": "http://backend:8000", "role": "*", "site": "cdrp"},
|
||||
{"path": "/api", "backend": "http://backend:8000", "role": "*", "site": "cdrp"},
|
||||
{"path": "/api/*", "backend": "http://backend:8000", "role": "*", "site": "cdrp"},
|
||||
{"path": "/api/*/*", "backend": "http://backend:8000", "role": "*", "site": "cdrp"},
|
||||
{"path": "/api/*/*/*", "backend": "http://backend:8000", "role": "*", "site": "cdrp"},
|
||||
{"path": "/api/*/*/*/*", "backend": "http://backend:8000", "role": "*", "site": "cdrp"},
|
||||
{"path": "/swagger", "backend": "http://backend:8000", "role": "*", "site": "cdrp"},
|
||||
{"path": "/swagger/*", "backend": "http://backend:8000", "role": "*", "site": "cdrp"},
|
||||
{"path": "/swagger/*/*", "backend": "http://backend:8000", "role": "*", "site": "cdrp"},
|
||||
{"path": "/swagger/*/*/*","backend": "http://backend:8000", "role": "*", "site": "cdrp"},
|
||||
{"path": "/", "backend": "http://frontend:8000", "role": "*", "site": "cdrp"},
|
||||
{"path": "/*", "backend": "http://frontend:8000", "role": "*", "site": "cdrp"},
|
||||
{"path": "/*/*", "backend": "http://frontend:8000", "role": "*", "site": "cdrp"},
|
||||
{"path": "/*/*/*", "backend": "http://frontend:8000", "role": "*", "site": "cdrp"},
|
||||
{"path": "/*/*/*/*", "backend": "http://frontend:8000", "role": "*", "site": "cdrp"},
|
||||
{"path": "/*/*/*/*/*", "backend": "http://frontend:8000", "role": "*", "site": "cdrp"}
|
||||
]
|
||||
|
||||
const sites = [
|
||||
{"name": "cdrp"},
|
||||
{"name": "live_monitore"},
|
||||
{"name": "buk"},
|
||||
{"name": "mtso"},
|
||||
{"name": "as_dmps"}
|
||||
]
|
||||
|
||||
export {routes, sites, config, configRoutes}
|
||||
67
proxy-ui-app/src/store/modules/proxy/helpers.js
Normal file
67
proxy-ui-app/src/store/modules/proxy/helpers.js
Normal file
@@ -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 }
|
||||
153
proxy-ui-app/src/store/modules/proxy/index.js
Normal file
153
proxy-ui-app/src/store/modules/proxy/index.js
Normal file
@@ -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,
|
||||
};
|
||||
5
proxy-ui-app/src/store/modules/proxy/routeOptions.json
Normal file
5
proxy-ui-app/src/store/modules/proxy/routeOptions.json
Normal file
@@ -0,0 +1,5 @@
|
||||
[
|
||||
{ "name": 1, "value": 1 },
|
||||
{ "name": 2, "value": 2 },
|
||||
{ "name": 3, "value": 3 }
|
||||
]
|
||||
19
proxy-ui-app/src/store/modules/proxy/routes.json
Executable file
19
proxy-ui-app/src/store/modules/proxy/routes.json
Executable file
@@ -0,0 +1,19 @@
|
||||
[
|
||||
{"description": "The item is sized according to its width and height properties. It shrinks to its minimum size to fit the container, but does not grow to absorb any extra free space in the flex container. This is equivalent to setting", "id": 1,"path": "/socket.io", "backend": "http://backend:8000", "role": "*", "site": "cdrp"},
|
||||
{"description": "The item is sized according to its width and height properties. It shrinks to its minimum size to fit the container, but does not grow to absorb any extra free space in the flex container. This is equivalent to setting", "id": 2,"path": "/socket.io/*", "backend": "http://backend:8000", "role": "*", "site": "cdrp"},
|
||||
{"description": "The item is sized according to its width and height properties. It shrinks to its minimum size to fit the container, but does not grow to absorb any extra free space in the flex container. This is equivalent to setting", "id": 3,"path": "/api", "backend": "http://backend:8000", "role": "*", "site": "cdrp"},
|
||||
{"description": "The item is sized according to its width and height properties. It shrinks to its minimum size to fit the container, but does not grow to absorb any extra free space in the flex container. This is equivalent to setting", "id": 4,"path": "/api/*", "backend": "http://backend:8000", "role": "*", "site": "cdrp"},
|
||||
{"description": "The item is sized according to its width and height properties. It shrinks to its minimum size to fit the container, but does not grow to absorb any extra free space in the flex container. This is equivalent to setting", "id": 5,"path": "/api/*/*", "backend": "http://backend:8000", "role": "*", "site": "cdrp"},
|
||||
{"description": "The item is sized according to its width and height properties. It shrinks to its minimum size to fit the container, but does not grow to absorb any extra free space in the flex container. This is equivalent to setting", "id": 6,"path": "/api/*/*/*", "backend": "http://backend:8000", "role": "*", "site": "cdrp"},
|
||||
{"description": "The item is sized according to its width and height properties. It shrinks to its minimum size to fit the container, but does not grow to absorb any extra free space in the flex container. This is equivalent to setting", "id": 7,"path": "/api/*/*/*/*", "backend": "http://backend:8000", "role": "*", "site": "cdrp"},
|
||||
{"description": "The item is sized according to its width and height properties. It shrinks to its minimum size to fit the container, but does not grow to absorb any extra free space in the flex container. This is equivalent to setting", "id": 8,"path": "/swagger", "backend": "http://backend:8000", "role": "*", "site": "cdrp"},
|
||||
{"description": "The item is sized according to its width and height properties. It shrinks to its minimum size to fit the container, but does not grow to absorb any extra free space in the flex container. This is equivalent to setting", "id": 9,"path": "/swagger/*", "backend": "http://backend:8000", "role": "*", "site": "cdrp"},
|
||||
{"description": "The item is sized according to its width and height properties. It shrinks to its minimum size to fit the container, but does not grow to absorb any extra free space in the flex container. This is equivalent to setting", "id": 10,"path": "/swagger/*/*", "backend": "http://backend:8000", "role": "*", "site": "cdrp"},
|
||||
{"description": "The item is sized according to its width and height properties. It shrinks to its minimum size to fit the container, but does not grow to absorb any extra free space in the flex container. This is equivalent to setting", "id": 11,"path": "/swagger/*/*/*","backend": "http://backend:8000", "role": "*", "site": "cdrp"},
|
||||
{"description": "The item is sized according to its width and height properties. It shrinks to its minimum size to fit the container, but does not grow to absorb any extra free space in the flex container. This is equivalent to setting", "id": 12,"path": "/", "backend": "http://frontend:8000", "role": "*", "site": "cdrp"},
|
||||
{"description": "The item is sized according to its width and height properties. It shrinks to its minimum size to fit the container, but does not grow to absorb any extra free space in the flex container. This is equivalent to setting", "id": 13,"path": "/*", "backend": "http://frontend:8000", "role": "*", "site": "cdrp"},
|
||||
{"description": "The item is sized according to its width and height properties. It shrinks to its minimum size to fit the container, but does not grow to absorb any extra free space in the flex container. This is equivalent to setting", "id": 14,"path": "/*/*", "backend": "http://frontend:8000", "role": "*", "site": "cdrp"},
|
||||
{"description": "The item is sized according to its width and height properties. It shrinks to its minimum size to fit the container, but does not grow to absorb any extra free space in the flex container. This is equivalent to setting", "id": 15,"path": "/*/*/*", "backend": "http://frontend:8000", "role": "*", "site": "cdrp"},
|
||||
{"description": "The item is sized according to its width and height properties. It shrinks to its minimum size to fit the container, but does not grow to absorb any extra free space in the flex container. This is equivalent to setting", "id": 16,"path": "/*/*/*/*", "backend": "http://frontend:8000", "role": "*", "site": "cdrp"},
|
||||
{"description": "The item is sized according to its width and height properties. It shrinks to its minimum size to fit the container, but does not grow to absorb any extra free space in the flex container. This is equivalent to setting", "id": 17,"path": "/*/*/*/*/*", "backend": "http://frontend:8000", "role": "*", "site": "cdrp"}
|
||||
]
|
||||
7
proxy-ui-app/src/store/modules/proxy/sites.json
Executable file
7
proxy-ui-app/src/store/modules/proxy/sites.json
Executable file
@@ -0,0 +1,7 @@
|
||||
[
|
||||
{"description": "The item is sized according to its width and height properties. It shrinks to its minimum size to fit the container, but does not grow to absorb any extra free space in the flex container. This is equivalent to setting", "device_ip": "172.25.78.36", "proxy_ip": "172.25.78.36", "status": "active", "id": 1,"port": "4000", "name": "cdrp"},
|
||||
{"description": "The item is sized according to its width and height properties. It shrinks to its minimum size to fit the container, but does not grow to absorb any extra free space in the flex container. This is equivalent to setting", "device_ip": "172.25.78.36", "proxy_ip": "172.25.78.36", "status": "active", "id": 2,"port": "4000", "name": "live_monitore"},
|
||||
{"description": "The item is sized according to its width and height properties. It shrinks to its minimum size to fit the container, but does not grow to absorb any extra free space in the flex container. This is equivalent to setting", "device_ip": "172.25.78.36", "proxy_ip": "172.25.78.36", "status": "active", "id": 3,"port": "4000", "name": "buk"},
|
||||
{"description": "The item is sized according to its width and height properties. It shrinks to its minimum size to fit the container, but does not grow to absorb any extra free space in the flex container. This is equivalent to setting", "device_ip": "172.25.78.36", "proxy_ip": "172.25.78.36", "status": "active", "id": 4,"port": "4000", "name": "mtso"},
|
||||
{"description": "The item is sized according to its width and height properties. It shrinks to its minimum size to fit the container, but does not grow to absorb any extra free space in the flex container. This is equivalent to setting", "device_ip": "172.25.78.36", "proxy_ip": "172.25.78.36", "status": "active", "id": 5,"port": "4000", "name": "as_dmps"}
|
||||
]
|
||||
3
proxy-ui-app/src/styles/main.css
Normal file
3
proxy-ui-app/src/styles/main.css
Normal file
@@ -0,0 +1,3 @@
|
||||
@tailwind base;
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
Reference in New Issue
Block a user