Merge remote-tracking branch 'origin/dev'

This commit is contained in:
vbuglov 2024-03-12 10:16:16 +03:00
commit b215f88b2c
19 changed files with 636 additions and 318 deletions

View File

@ -16,6 +16,11 @@ rmrestart:
docker stop proxy-ui-vue
docker run -d --name proxy-ui-vue --rm -p 5000:80 proxy-ui-vue
clearhook:
@echo "clear hooks"
@rm -f $(HOOK_PATH)
@echo "hooks cleared"
prehook:
@echo "Setting up pre-push hook..."
@rm -f $(HOOK_PATH)
@ -37,11 +42,9 @@ prehook:
@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_SSL_NO_VERIFY=true git push
git add . && git commit -m "feat($(commit)):" && git push

View File

@ -16,6 +16,7 @@ export default {
</script>
<template>
<div class="flex justify-between">
<div class="flex">
<a
href="/#"
@ -33,4 +34,8 @@ export default {
Сохранить изменения
</button>
</div>
<div class="flex test-slate-300 text-xs">
Версия 1.0.1
</div>
</div>
</template>

View File

@ -15,7 +15,6 @@ export default {
default: "",
type: String
},
},
data() {
return {
@ -37,6 +36,7 @@ export default {
}
}
}
</script>
<template>

View File

@ -1,6 +1,7 @@
import {describe, expect, beforeEach} from 'vitest'
import {describe, expect, beforeEach, it} from 'vitest'
import {createStore} from 'vuex'
import {store} from '@store/modules/proxy/index.js'
// import {store as services} from '@store/modules/proxy/index.js'
import {addedSite, updatedSite, removedNewSite, deletedSite} from '@store/modules/proxy/helpers.js'
const sites = store.state.sites
@ -24,6 +25,13 @@ const breakSavingSite = store.actions.breakSavingSite
const removeSite = store.actions.removeSite
const resetStore = store.actions.resetStore
// const store = createStore({
// plugins: [],
// modules: {
// services
// },
// })
const defaultSites = [ {
"id": 1,
"created_at": "2024-02-22T17:08:37.715772388+03:00",
@ -33,6 +41,7 @@ const defaultSites = [ {
"port": 9965,
"proxy_ip": "172.25.78.153",
"site_ip": "172.25.78.153",
"device_ip": "172.25.78.153",
"internet_uri": "localhost",
"description": "localhost",
"is_online": true
@ -46,6 +55,7 @@ const defaultSites = [ {
"port": 3645,
"proxy_ip": "172.25.78.151",
"site_ip": "172.25.78.151",
"device_ip": "172.25.78.151",
"internet_uri": "",
"description": "create... upd...",
"is_online": true
@ -59,6 +69,7 @@ const defaultSites = [ {
"port": 3645,
"proxy_ip": "172.25.78.151",
"site_ip": "172.25.78.151",
"device_ip": "172.25.78.151",
"internet_uri": "",
"description": "new updated...",
"is_online": false
@ -96,13 +107,13 @@ const mockStore = createStore({
})
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,
@ -125,12 +136,12 @@ describe('mutations', () => {
})
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')
@ -141,7 +152,7 @@ describe('actions', () => {
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')
@ -152,7 +163,7 @@ describe('actions', () => {
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')
@ -166,7 +177,7 @@ describe('actions', () => {
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')
@ -193,7 +204,7 @@ describe('actions', () => {
})
describe('helpers', () => {
//eslint-disable-next-line no-undef
it('added new site', async () => {
beforeEach(() => {
mockStore.dispatch('resetStore')
@ -220,7 +231,7 @@ describe('helpers', () => {
expect(addedNewSite).toMatchObject(newSite)
})
//eslint-disable-next-line no-undef
it('updated site', async () => {
beforeEach(() => {
mockStore.dispatch('resetStore')
@ -249,7 +260,7 @@ describe('helpers', () => {
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')
@ -278,7 +289,7 @@ describe('helpers', () => {
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')
@ -296,7 +307,7 @@ describe('helpers', () => {
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')

View File

@ -16,6 +16,10 @@ rmrestart:
docker stop proxy-ui-vue
docker run -d --name proxy-ui-vue --rm -p 5000:80 proxy-ui-vue
clearhook:
@echo "clear hooks"
@rm -f $(HOOK_PATH)
@echo "hooks cleared"
prehook:
@echo "Setting up pre-push hook..."
@ -38,7 +42,6 @@ prehook:
@chmod +x $(HOOK_PATH)
@echo "Pre-push hook set successfully."
push:
ifeq ($(commit),)
$(error mn is not set)

View File

@ -15,6 +15,20 @@ const config = {
site_ip: "device_ip",
}
const configToServer = {
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 {
/**
@ -25,6 +39,7 @@ class Services {
constructor(apiAddr) {
this.apiAddr = apiAddr
this.config = config
this.configToServer = configToServer
}
/**
@ -46,7 +61,7 @@ class Services {
async createService(payload) {
let newService = []
const updatedPort = parseFloat(payload.port)
const updatedService = {...convertObject(payload, {config: this.config}), port: updatedPort}
const updatedService = {...convertObject(payload, {config: this.configToServer}), port: updatedPort}
await post(`${this.apiAddr}/servers`, updatedService).then(res => {
newService = convertObject(res.value, {config: this.config})
}).catch(err => {
@ -64,7 +79,7 @@ class Services {
async updateService(payload) {
let resService = []
const updatedPort = parseFloat(payload.port)
const updatedService = {...convertObject(payload, {config: this.config}), port: updatedPort}
const updatedService = {...convertObject(payload, {config: this.configToServer}), port: updatedPort}
if (payload.id) {
await put(`${this.apiAddr}/servers`, updatedService, payload.id).then(res => {
resService = convertObject(res.value, {config: this.config})

View File

@ -1,12 +1,41 @@
<script>
import {mapGetters} from 'vuex'
export default {
name: 'AppPageHeader',
inject: ['serviceOfServices'],
computed: {
...mapGetters('services', ['selectedService']),
},
methods: {
saveData: function () {
console.log('saveData')
return this.serviceOfServices.isSaveServices(true)
},
}
}
</script>
<template>
<div class="flex justify-between">
<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>
>
<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>
<div class="flex">
Версия 0.0.1
</div>
</div>
</template>

View File

@ -1,8 +1,8 @@
<script>
import {mapActions} from 'vuex'
export default {
name: 'NewSiteButton',
inject: ['serviceOfServices'],
props: {
name: {
default: "",
@ -18,7 +18,9 @@ export default {
}
},
methods: {
...mapActions('services', ['addNewSiteLayout'])
addNewServiceLayout() {
this.serviceOfServices.addNewServiceLayout()
}
}
}
</script>
@ -26,7 +28,7 @@ export default {
<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"
@click="addNewServiceLayout"
>
Добавить сайт
</div>

View File

@ -1,37 +1,34 @@
<!-- eslint-disable vue/prop-name-casing -->
<script>
import {mapActions, mapGetters} from 'vuex'
import {mapGetters} from 'vuex'
import Input from '@atoms/VInput.vue'
import Textarea from '@atoms/VTextarea.vue'
import VDoubleSwitch from '@atoms/VDoubleSwitch.vue'
export default {
name: 'EditCard',
name: 'EditServiceCard',
components: {Input, Textarea, VDoubleSwitch},
props: {
id: {
default: -1,
type: Number
},
serviceOfServices: {
default: () => ({}),
type: Object
},
},
computed: {
...mapGetters('services', ["selectedSite"]),
...mapGetters('services', ["selectedService"]),
},
mounted() {
},
methods: {
...mapActions('services', ["editSelectedSite", "breakSavingSite", "breakeAddingSite"]),
editData (params) {
this.editSelectedSite(params)
this.serviceOfServices.editSelectedService(params)
},
breakSavingSiteDate() {
if (this.id == -1) {
this.breakeAddingSite()
} else {
this.editable_name = this.name
this.editable_port = this.port
this.breakSavingSite()
}
cancelEditService() {
this.serviceOfServices.cancelSelectedService(this.id)
}
}
}
@ -46,7 +43,7 @@ export default {
secondColor="#2563eb"
firstTitle="Офлайн"
secondTitle="Онлайн"
:isCheck="selectedSite.is_online"
:isCheck="selectedService.is_online"
position="col"
labelClass="items-start pb-2"
switchClass="mt-1"
@ -55,7 +52,7 @@ export default {
</div>
<Input
name="name"
:value="selectedSite.name"
:value="selectedService.name"
inputClass="!w-[90%] py-2"
placeholder="Указать путь"
:onChange="editData"
@ -64,7 +61,7 @@ export default {
<i
v-tippy="{ content: 'закрыть сервис' }"
class="ri-close-line cursor-pointer"
@click="breakSavingSiteDate"
@click="cancelEditService"
/>
</div>
</div>
@ -72,7 +69,7 @@ export default {
<span class="mr-2 min-w-[80px]">Порт:</span>
<Input
name="port"
:value="`${selectedSite.port}`"
:value="`${selectedService.port}`"
inputClass="py-2"
placeholder="Порт"
:onChange="editData"
@ -82,7 +79,7 @@ export default {
<span class="mr-2 min-w-[80px]">IP proxy:</span>
<Input
name="proxy_ip"
:value="selectedSite.proxy_ip"
:value="selectedService.proxy_ip"
inputClass="py-2"
placeholder="IP proxy"
:onChange="editData"
@ -92,7 +89,7 @@ export default {
<span class="mr-2 min-w-[80px]">IP устр-ва:</span>
<Input
name="device_ip"
:value="selectedSite.device_ip"
:value="selectedService.device_ip"
inputClass="py-2"
placeholder="IP устройства"
:onChange="editData"
@ -100,7 +97,7 @@ export default {
</div>
<Textarea
name="description"
:value="selectedSite.description"
:value="selectedService.description"
textareaClass="py-2"
placeholder="Описание..."
:onChange="editData"

View File

@ -1,12 +1,16 @@
<!-- eslint-disable vue/prop-name-casing -->
<script>
import {mapActions, mapGetters} from 'vuex'
import EditCard from './SiteListEditCard.vue'
import EditCard from './EditServiceCard.vue'
export default {
name: 'SiteCard',
name: 'ServiceCard',
components: {EditCard},
props: {
serviceOfServices: {
default: () => ({}),
type: Object
},
id: {
default: -1,
type: Number
@ -27,30 +31,28 @@ export default {
default: "",
type: String
},
status: {
default: "disable",
type: String
isOnline: {
default: false,
type: Boolean
},
description: {
default: "disable",
type: String
},
site: {
service: {
default: () => ({}),
type: Object
},
selectSite: {
type: Function,
default: () => {}
}
},
data () {
const status = this.setStatus(this.isOnline)
return {
isDelete: false,
status
}
},
computed: {
...mapGetters('services', ["selectedSite", "routes", 'isSaveData']),
...mapGetters('services', ["selectedService", "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`
@ -60,36 +62,47 @@ export default {
isSaveData: function (newVal) {
console.log('newVal', newVal)
if (newVal) {
this.saveSiteData()
this.saveServiceData()
}
},
isOnline: function (newVal) {
console.log('newVal', newVal)
this.status = this.setStatus(newVal)
}
},
mounted() {
},
methods: {
...mapActions('services', ["saveSite", "createNewSite", 'removeSite', 'updateRoutesWithApi', 'closeRoutesList']),
...mapActions('services', ["saveservice", "createNewservice", 'removeservice', 'updateRoutesWithApi', 'closeRoutesList']),
...mapActions('users', ["fetchUsersList"]),
toggle () {
this.open = !this.open
this.onToggle({isOpen: this.open})
},
saveSiteData () {
if (this.selectedSite.id == -1) {
setStatus (isOnline) {
return isOnline ? {title: 'enable', color: 'bg-green-700'} : {title: 'disable', color: 'bg-red-700'}
},
editService (service) {
this.serviceOfServices.selectService(service)
// this.serviceOfServices.openRoutesList()
},
saveServiceData () {
if (this.selectedService.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
name: this.selectedService.name,
port: this.selectedService.port,
device_ip: this.selectedService.device_ip,
proxy_ip: this.selectedService.proxy_ip,
description: this.selectedService.description
}
this.createNewSite(data)
this.serviceOfServices.createNewService(data)
} else {
this.updateRoutesWithApi(this.selectedSite)
this.saveSite(this.selectedSite)
// this.updateRoutesWithApi(this.selectedService)
this.serviceOfServices.saveService(this.selectedService)
}
},
deleteSite (v) {
this.isDelete = v
deleteService (e) {
this.isDelete = e
}
}
}
@ -97,7 +110,7 @@ export default {
<template>
<div
v-if="!selectedSite || selectedSite.id !== id"
v-if="!selectedService || selectedService.id !== id"
class="block w-full p-6 bg-white border border-gray-200 rounded-lg shadow dark:bg-gray-800 dark:border-gray-700 dark:hover:bg-gray-700"
>
<div class="flex justify-between items-center mb-1">
@ -111,12 +124,12 @@ export default {
<i
v-tippy="{ content: 'редактировать сервис' }"
class="ri-pencil-line cursor-pointer"
@click="selectSite(site.id, 'dev')"
@click="editService(service)"
/>
<i
v-tippy="{ content: 'удалить сервис' }"
class="ri-close-line cursor-pointer ml-2"
@click="deleteSite(true)"
@click="deleteService(true)"
/>
<div
v-if="isDelete"
@ -124,13 +137,13 @@ export default {
>
<button
class="flex items-center text-xs text-white bg-blue-700 p-1 mr-2 rounded-lg shadow"
@click="deleteSite(false)"
@click="deleteService(false)"
>
Отменить
</button>
<button
class="flex items-center p-1 text-xs text-white bg-slate-700 rounded-lg shadow"
@click="removeSite(id)"
@click="serviceOfServices.removeService(id)"
>
Удалить
</button>
@ -140,7 +153,11 @@ export default {
<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 class="inline-block flex mr-2 font-w-700"> статус:</span> <span class="mr-2">{{ status.title }}</span>
<div
class="min-h-2 min-w-2 bg-green-700 rounded-full"
:class="status.color"
/>
</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>
@ -160,10 +177,13 @@ export default {
</p>
</div>
<div
v-if="selectedSite && selectedSite.id === id"
v-if="selectedService && selectedService.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" />
<EditCard
:id="id"
:serviceOfServices="serviceOfServices"
/>
</div>
</template>

View File

@ -0,0 +1,68 @@
<script>
import {toRefs} from 'vue'
import {mapGetters} from 'vuex'
import { FwbSpinner } from 'flowbite-vue'
import ServiceCard from "./ServiceCard.vue"
import NewServiceButton from "@atoms/NewServiceButton.vue"
export default {
name: 'ServicesList',
components: {FwbSpinner, ServiceCard, NewServiceButton},
props: {
serviceOfServices: {
type: Object,
default: () => {}
},
},
setup (props) {
const {serviceOfServices} = toRefs(props)
serviceOfServices.value.fetchServices()
},
computed: {
...mapGetters('services', ["services", "routes", "servicesState", "newservice", 'isDeleteData']),
maxHeight () {
return document.documentElement.clientHeight - 400
},
},
mounted () {
},
methods: {
}
}
</script>
<template>
<div
v-if="servicesState === 'active'"
:class="`grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 2xl:grid-cols-5 3xl:grid-cols-6 gap-5 overflow-y-auto mb-14 pr-1`"
:style="{maxHeight: `${maxHeight}px`}"
>
<NewServiceButton :serviceOfServices="serviceOfServices" />
<serviceCard
v-for="service in services"
:id="service.id"
:key="service.name"
:serviceOfServices="serviceOfServices"
:service="service"
:name="service.name"
:port="`${service.port}`"
:device_ip="service.device_ip"
:proxy_ip="service.proxy_ip"
:isOnline="service.is_online"
:description="service.description"
/>
</div>
<div
v-if="servicesState === 'loading'"
class="flex w-full justify-center"
>
<fwb-spinner
size="8"
class="mr-2"
/>
<span class="text-2xl text-slate-700">
Загрузка сайтов...
</span>
</div>
</template>

View File

@ -1,68 +0,0 @@
<script>
import {mapActions, mapGetters, useStore} from 'vuex'
import { FwbSpinner } from 'flowbite-vue'
import SiteCard from "./SiteListSiteCard.vue"
import NewSiteButton from "@atoms/NewSiteButton.vue"
export default {
name: 'SiteList',
components: {FwbSpinner, SiteCard, NewSiteButton},
props: {
selectSite: {
type: Function,
default: () => {}
}
},
setup () {
const store = useStore()
store.dispatch('services/uploadSites')
},
computed: {
...mapGetters('services', ["sites", "routes", "sitesState", "newSite", 'isDeleteData']),
maxHeight () {
return document.documentElement.clientHeight - 400
},
},
mounted () {
},
methods: {
...mapActions('services', ["uploadSites", "uploadSiteRoutes"])
}
}
</script>
<template>
<div
v-if="sitesState === 'active'"
:class="`grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 2xl:grid-cols-5 3xl:grid-cols-6 gap-5 overflow-y-auto mb-14 pr-1`"
:style="{maxHeight: `${maxHeight}px`}"
>
<NewSiteButton />
<SiteCard
v-for="site in sites"
:id="site.id"
:key="site.name"
:site="site"
:name="site.name"
:port="`${site.port}`"
:device_ip="site.device_ip"
:proxy_ip="site.proxy_ip"
:status="site.status"
:description="site.description"
:selectSite="selectSite"
/>
</div>
<div
v-if="sitesState === 'loading'"
class="flex w-full justify-center"
>
<fwb-spinner
size="8"
class="mr-2"
/>
<span class="text-2xl text-slate-700">
Загрузка сайтов...
</span>
</div>
</template>

View File

@ -1,4 +1,5 @@
<script>
import {computed} from 'vue'
import {mapActions, mapGetters} from 'vuex'
import {useStore} from '@store/index.js'
@ -9,14 +10,19 @@ import ServiceOfServices from '@services/serviceOfServices/Services.js'
import CaseOfUsersInService from '@useCases/CaseOfUsersInService.js'
import PageHeader from "@atoms/AppPageHeader.vue"
import SiteList from "@organisms/SiteList/SiteList.vue"
import SiteList from "@organisms/ServicesList/ServicesList.vue"
import SiteManager from "@organisms/UsersManager/UsersManager.vue"
export default {
name: 'SitesManagerPage',
components: {SiteManager, SiteList, PageHeader},
provide() {
return {
serviceOfServices: computed(() => this.serviceOfServices),
}
},
setup() {
const url = import.meta.env.VITE_API_URL
const url = import.meta.env.VITE_API_ADDR
const adapterOfServices = new AdapterOfServices(url)
const adapterOfUsers = new AdapterOfUsers(url)
@ -25,6 +31,7 @@ export default {
const serviceOfUsers = new ServiceOfUsers(adapterOfUsers, store)
const serviceOfServices = new ServiceOfServices(adapterOfServices, store)
// serviceOfServices.fetchServicesList()
const caseOfUsersInService = new CaseOfUsersInService(serviceOfUsers, serviceOfServices)
@ -35,13 +42,13 @@ export default {
}
},
computed: {
...mapGetters('services', ["sites", "routes", "newRoute", "selectedSiteState"]),
...mapGetters('services', ["sites", "routes", "newRoute", "selectedServiceState"]),
},
mounted () {
this.uploadSites()
// this.uploadServices()
},
methods: {
...mapActions('services', ["uploadSites", "uploadAndSelectService"]),
...mapActions('services', ["uploadServices", "uploadAndSelectService"]),
...mapActions('users', ["fetchUsersList"]),
async selectSite(siteId, mode = 'prod') {
await this.fetchUsersList({siteId, mode})
@ -56,12 +63,12 @@ export default {
<div class="p-6">
<PageHeader class="me-2 mb-6" />
<SiteList
:serviceOfUsers="serviceOfServices"
:serviceOfServices="serviceOfServices"
:caseOfUsersInService="caseOfUsersInService"
:selectSite="selectSite"
/>
<SiteManager
v-if="selectedSiteState === 'active'"
v-if="selectedServiceState === 'active'"
:serviceOfUsers="serviceOfUsers"
:caseOfUsersInService="caseOfUsersInService"
/>

View File

@ -1,18 +1,75 @@
import { addedService, updatedService, removedNewService, deletedService } from "./helpers"
import { isEmpty } from "ramda"
class ServiceOfServices {
constructor(adapterOfServices, store) {
this.adapterOfServices = adapterOfServices
this.store = store
}
async fetchServicesList() {
async fetchServices() {
const services = await this.adapterOfServices.getServices()
this.store.dispatch('services/saveServices', services)
return services
}
async addNewServiceLayout() {
const newService = {"port": "", "name": "", id: -1}
this.store.dispatch('services/addedNewServiceLayout', newService)
}
async createNewService(newService) {
this.store.commit('services/setIsSaveData', false)
this.store.commit('services/setSelectedService', null)
const createdService = await this.adapterOfServices.createService(newService)
const updatedServices = addedService(createdService, this.store.getters['services/services'])
this.store.dispatch('services/createNewService', updatedServices)
return createdService
}
async selectService(service) {
this.store.dispatch('services/changeSelectedService', service)
}
async getSelectedService() {
return this.store.getters['services/selectedService']
}
async cancelSelectedService(id) {
const services = this.store.getters['services/services']
const updatedServices = id === -1 ? removedNewService(services) : null
this.store.dispatch('services/cancelSelectedService', updatedServices)
}
async editSelectedService(params) {
const selectedService = this.store.getters['services/selectedService']
// console.log('selectedService', selectedService)
selectedService[params.key] = params.value
this.store.dispatch('services/editSelectedService', selectedService)
}
async isSaveServices(event) {
this.store.dispatch('services/changeIsSaveData', event)
}
async saveService(editService) {
this.store.commit('services/setIsSaveData', false)
this.store.commit('services/setSelectedService', null)
const editedService = await this.adapterOfServices.updateService(editService)
const services = this.store.getters['services/services']
const updatedServices = !isEmpty(editedService) ? updatedService(editedService, services) : services
// console.log('updatedServices', updatedServices)
this.store.dispatch('services/saveService', updatedServices)
return editedService
}
async removeService(id) {
const deletedSiteId = await this.adapterOfServices.deleteService(id)
const services = this.store.getters['services/services']
const updatedServices = deletedSiteId ? deletedService(deletedSiteId, services) : null
this.store.dispatch('services/saveService', updatedServices)
}
}
export default ServiceOfServices

View File

@ -0,0 +1,32 @@
const addedService = (addedSite, sites) => {
const sitesWithoutNewSite = removedNewService(sites)
return [addedSite,...sitesWithoutNewSite]
}
const updatedService = (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 removedNewService = (sites) => {
if (sites.length > 0) {
const firstSite = sites[0]
const isNewSite = firstSite.id === -1
sites = isNewSite ? sites.slice(1) : sites
return sites
}
}
const deletedService = (deletedSiteId, sites) => {
if (deletedSiteId) {
const deleteIdx = sites.findIndex(service => service.id === deletedSiteId)
return sites.slice(0, deleteIdx).concat(sites.slice(deleteIdx + 1))
}
}
export { addedService, updatedService, removedNewService, deletedService }

View File

@ -1,17 +1,17 @@
import routeOptions from './routeOptions.json'
import Services from '@helpers/Services/Services.js';
import {config} from './StaticData.js'
// import Services from '@helpers/Services/Services.js';
// import {config} from './StaticData.js'
import {isEmpty} from "ramda";
const initState = {
isSaveData: false,
sites: [],
sitesState: "loading",
services: [],
servicesState: "loading",
selectedSiteState: "await",
selectedSite: null,
selectedServiceState: "await",
selectedService: null,
newSite: null,
newService: null,
};
const state = {
@ -20,75 +20,87 @@ const state = {
const getters = {
isSaveData: (state) => state.isSaveData,
sites: (state) => state.newSite ? [state.newSite, ...state.sites] : state.sites,
services: (state) => state.newService ? [state.newService, ...state.services] : state.services,
routes: (state) => state.routes,
routesLib: (state) => state.routesLib,
selectedSiteState: (state) => state.selectedSiteState,
sitesState: (state) => state.sitesState,
selectedServiceState: (state) => state.selectedServiceState,
servicesState: (state) => state.servicesState,
newRoute: (state) => state.newRoute,
routeOptions: () => routeOptions,
selectedSite: (state) => state.selectedSite,
selectedService: (state) => state.selectedService,
};
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,
setServices: (state, payload) => state.services = payload,
setServicesState: (state, payload) => state.servicesState = payload,
setSelectedService: (state, payload) => state.selectedService = payload,
setNewService: (state, payload) => state.newService = payload,
};
const actions = {
addNewSiteLayout: ({state}) => {
const newSite = {"port": "", "name": "", id: -1}
state.newSite = newSite
state.selectedSite = newSite
addedNewServiceLayout: ({commit, getters}, newService) => {
console.log('newSevice', newService)
commit('setServices', [newService,...getters.services])
commit('setSelectedService', newService)
// commit('setRoutes', [])
// commit('setRoutesState', "active")
},
createNewSite: ({state}) => {
state.isSaveData = false
state.selectedSite = null
state.sites = [{
...state.newSite,
id: state.sites.reduce((acc, {id}) => Math.max(id, acc) + 1, 0)
}, ...state.sites]
state.newSite = null
createNewService: async ({commit}, updatedServices) => {
if (!isEmpty(updatedServices)) {
// const newService = updatedSites[0]
// const updatedRoutes = addedNewRoute(newService, state.routes)
// commit('setRoutes', updatedRoutes)
// dispatch('updateRoutesWithApi', newService)
return commit('setServices', updatedServices)
}
},
breakAddingSite: ({state}) => {
state.newSite = null
state.selectedSite = null
breakeAddingSite: ({commit}) => {
commit('setSelectedService', null)
},
addNewRouteLayout: ({state}) => {
const newRoute = {"path": null, "role": null, id: -1}
state.newRoute = newRoute
changeSelectedService: async ({commit}, siteProps) => {
commit('setSelectedService', siteProps)
},
createNewRoute: ({state}) => {
state.newRoute = null
editSelectedService: async ({commit}, selectedService) => {
// const selectedSite = getters.selectedSite
// selectedSite[payload.key] = payload.value
console.log('selectedService', selectedService)
commit('setSelectedService', selectedService)
},
stopAddingRoute: ({state}) => {
state.newRoute = null
changeIsSaveData: ({commit}, payload) => {
commit('setIsSaveData', payload)
},
uploadSites: async ({commit}) => {
const path = import.meta.env.VITE_API_ADDR
const services = new Services(path, config)
const sites = await services.getServices()
commit('setSites', sites)
commit('setSitesState', 'active')
saveServices: async ({commit}, services) => {
// const sites = await services.getServices()
commit('setServices', services)
commit('setServicesState', 'active')
console.log('services', services)
},
saveService: async ({commit}, updatedServices) => {
// commit('setIsSaveData', false)
// commit('setSelectedService', null)
commit('setServices', updatedServices)
},
cancelSelectedService: ({commit}, updatedServices) => {
commit('setSelectedService', null)
if (updatedServices) {
commit('setServices', updatedServices)
commit('setNewService', null)
}
// commit('setRoutesState', "await")
},
removeService: async ({commit}, updatedSites) => {
// const deletedSiteId = await services.deleteService(id)
// const updatedSites = deletedSite(deletedSiteId, getters.sites)
if (!isEmpty(updatedSites)) return commit('setServices', updatedSites)
},
uploadAndSelectService: ({state}, id) => {
state.selectedSite = state.sites.find(site => site.id === id)
state.selectedSite = state.services.find(site => site.id === id)
state.selectedSiteState = 'active'
},
saveSite: async ({state}, payload) => {
state.isSaveData = false
state.selectedSite = null
const path = import.meta.env.VITE_API_ADDR
const services = new Services(path, config)
const updatedSites = await services.setServicesData(payload, state.sites, payload.id)
state.sites = isEmpty(updatedSites) ? state.sites : updatedSites
},
breakSavingSite: ({state}) => {
state.selectedSite = null
},
resetStore: ({state}) => {
Object.entries(initState).forEach(([k,v]) => {
state[k] = v

View File

@ -58,7 +58,7 @@ describe('tests for ServiceOfServices', () => {
test('test of fetchServicesList', async () => {
const serviceOfServices = new ServiceOfServices(adapterOfServices, store)
await serviceOfServices.fetchServicesList()
await serviceOfServices.fetchServices()
const usersList = store.getters['services/servicesList']

View File

@ -1,11 +1,96 @@
import { expect, test, describe, vi, beforeEach} from "vitest";
import { expect, test, describe, vi, beforeEach} from "vitest"
import AdapterOfServices from '@adapters/adapterOfServices/Services'
import ServiceOfServices from '@services/serviceOfServices/Services.js'
import { createStore } from 'vuex'
import {store as services} from "@/store/modules/services"
import axios from "axios";
vi.mock('axios')
const defaultServices = [
{
"id": 1,
"created_at": "2024-03-06T17:31:31.948355541+03:00",
"updated_at": "2024-03-06T17:31:31.948355541+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
},
{
"id": 2,
"created_at": "2024-03-07T11:43:51.026265459+03:00",
"updated_at": "2024-03-07T13:35:12.506368972+03:00",
"deleted_at": null,
"name": "new 2",
"port": 4548,
"proxy_ip": "172.25.78.151",
"site_ip": "172.25.78.151",
"internet_uri": "",
"description": "new site 2",
"is_online": true
},
{
"id": 3,
"created_at": "2024-03-07T11:43:51.027148541+03:00",
"updated_at": "2024-03-07T13:35:24.919273428+03:00",
"deleted_at": null,
"name": "new 3",
"port": 2527,
"proxy_ip": "172.25.78.151",
"site_ip": "172.25.78.151",
"internet_uri": "",
"description": "new site 3...",
"is_online": true
}
]
const resServices = [
{
"id": 1,
"created_at": "2024-03-06T17:31:31.948355541+03:00",
"updated_at": "2024-03-06T17:31:31.948355541+03:00",
"deleted_at": null,
"name": "jsonplaceholder.typicode.com",
"port": 9965,
"proxy_ip": "172.25.78.153",
"device_ip": "https://jsonplaceholder.typicode.com/",
"internet_uri": "localhost",
"description": "localhost",
"is_online": true
},
{
"id": 2,
"created_at": "2024-03-07T11:43:51.026265459+03:00",
"updated_at": "2024-03-07T13:35:12.506368972+03:00",
"deleted_at": null,
"name": "new 2",
"port": 4548,
"proxy_ip": "172.25.78.151",
"device_ip": "172.25.78.151",
"internet_uri": "",
"description": "new site 2",
"is_online": true
},
{
"id": 3,
"created_at": "2024-03-07T11:43:51.027148541+03:00",
"updated_at": "2024-03-07T13:35:24.919273428+03:00",
"deleted_at": null,
"name": "new 3",
"port": 2527,
"proxy_ip": "172.25.78.151",
"device_ip": "172.25.78.151",
"internet_uri": "",
"description": "new site 3...",
"is_online": true
}
]
describe("tests services store with vuex", () => {
const store = createStore({
plugins: [],
@ -18,109 +103,149 @@ describe("tests services store with vuex", () => {
store.dispatch('services/resetStore')
})
test('upload sites data', async () => {
const mockData = [
{
"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
}
]
const expectedData = [
{
"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",
"device_ip": "172.25.78.153",
"internet_uri": "localhost",
"description": "localhost",
"is_online": true
}
]
const adapterOfServices = new AdapterOfServices(import.meta.env.VITE_API_ADDR)
const serviceOfServices = new ServiceOfServices(adapterOfServices, store)
axios.get.mockResolvedValue({
data: mockData,
data: defaultServices,
})
await store.dispatch('services/uploadSites')
const sites = store.getters['services/sites']
test('Upload services', async () => {
const services = store.getters['services/services']
expect(services).toEqual([]) // empty services array of store
await serviceOfServices.fetchServices()
const uploadServices = store.getters['services/services']
expect(uploadServices).toEqual(resServices) // full services array of store
expect(sites).toEqual(expectedData)
})
test('test addNewSiteLayout function', async () => {
test('Added new service layout to services of store', async () => {
const expectedSiteData = {"port": "", "name": "", id: -1}
store.dispatch('services/addNewSiteLayout')
const selectedSite = store.getters['services/selectedSite']
const sites = store.getters['services/sites']
await serviceOfServices.addNewServiceLayout()
const selectedService = store.getters['services/selectedService']
const services = store.getters['services/services']
expect(
{
selectedSite,
sites
selectedService,
services
}
).toEqual({
selectedSite: expectedSiteData,
sites: [expectedSiteData]
selectedService: expectedSiteData,
services: [expectedSiteData]
})
})
test("test uploadAndSelectService function", async () => {
const mockData = [
test('Selected service - for edit fields values in this service', async () => {
const editedService =
{
"id": 1,
"created_at": "2024-02-22T17:08:37.715772388+03:00",
"updated_at": "2024-02-26T14:11:38.64094899+03:00",
"id": 3,
"created_at": "2024-03-07T11:43:51.027148541+03:00",
"updated_at": "2024-03-07T13:35:24.919273428+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",
"name": "new 3",
"port": 2527,
"proxy_ip": "172.25.78.151",
"device_ip": "172.25.78.151",
"internet_uri": "",
"description": "new site 3...",
"is_online": true
}
]
axios.get.mockResolvedValue({
data: mockData,
await serviceOfServices.selectService(editedService)
const selectedService = store.getters['services/selectedService']
expect(selectedService).toEqual(editedService)
})
await store.dispatch('services/uploadSites')
store.dispatch('services/uploadAndSelectService', 1)
test('Edited fields values in selected service of store', async () => {
const selectedSite = store.getters['services/selectedSite']
const selectedSiteState = store.getters['services/selectedSiteState']
expect(selectedSite).toEqual({
"id": 1,
"created_at": "2024-02-22T17:08:37.715772388+03:00",
"updated_at": "2024-02-26T14:11:38.64094899+03:00",
const selectedForEditService =
{
"id": 3,
"created_at": "2024-03-07T11:43:51.027148541+03:00",
"updated_at": "2024-03-07T13:35:24.919273428+03:00",
"deleted_at": null,
"name": "jsonplaceholder.typicode.com",
"port": 9965,
"proxy_ip": "172.25.78.153",
"device_ip": "172.25.78.153",
"internet_uri": "localhost",
"description": "localhost",
"name": "new 3",
"port": 2527,
"proxy_ip": "172.25.78.151",
"device_ip": "172.25.78.151",
"internet_uri": "",
"description": "new site 3...",
"is_online": true
}
await serviceOfServices.selectService(selectedForEditService)
await serviceOfServices.editSelectedService({key: 'name', value: 'edited test name'}).then(async () => {
await serviceOfServices.editSelectedService({key: 'port', value: 5555}).then(async () => {
await serviceOfServices.editSelectedService({key: 'description', value: 'edited description field'})
})
expect(selectedSiteState).toEqual('active')
})
const selectedService = store.getters['services/selectedService']
// console.log('Edited fields 4 test', selectedService)
expect(selectedService.name).toEqual('edited test name')
expect(selectedService.port).toEqual(5555)
expect(selectedService.description).toEqual('edited description field')
})
// test("test uploadAndSelectService function", async () => {
// const mockData = [
// {
// "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
// }
// ]
// axios.get.mockResolvedValue({
// data: mockData,
// })
// await store.dispatch('services/uploadSites')
// store.dispatch('services/uploadAndSelectService', 1)
// const selectedSite = store.getters['services/selectedSite']
// const selectedSiteState = store.getters['services/selectedSiteState']
// expect(selectedSite).toEqual({
// "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",
// "device_ip": "172.25.78.153",
// "internet_uri": "localhost",
// "description": "localhost",
// "is_online": true
// })
// expect(selectedSiteState).toEqual('active')
// })
})

View File

@ -57,7 +57,7 @@ describe("tests App mounted with vuex", () => {
"is_active": true
}])
expect(componentState).toEqual('active')
expect(selectedService).not.toBe(null)
expect(selectedService).toBe(null)
expect(answer).toBe("ok")
})
})