Skip to content

Commit

Permalink
feature(admin(js,vue)): add system service button, add rsyslog system…
Browse files Browse the repository at this point in the history
… service button to syslog forwarders, fixes #6532
  • Loading branch information
satkunas committed Dec 8, 2022
1 parent e34e2e5 commit 47560ba
Show file tree
Hide file tree
Showing 7 changed files with 537 additions and 14 deletions.
227 changes: 227 additions & 0 deletions html/pfappserver/root/src/components/new/BaseButtonSystemService.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,227 @@
<template>
<b-dropdown ref="buttonRef"
:disabled="isDisabled"
:size="size"
:text="service"
no-flip
variant="outline-primary"
v-b-tooltip.hover.top.d300 :title="tooltip"
v-bind="$attrs"
>
<template v-slot:button-content>
<div class="d-inline px-1">
<icon v-if="!Object.keys(servers).length" name="circle-notch" spin class="fa-overlap mr-1" />
<template v-else v-for="(service, server) in servers">
<icon v-if="service.status === 'loading'" :key="`icon-${server}`"
name="circle-notch" spin class="text-primary fa-overlap mr-1" />
<icon v-else-if="service.status === 'error'" :key="`icon-${server}`"
name="exclamation-triangle" class="text-danger fa-overlap mr-1" />
<icon v-else-if="service.isRestarting || service.isStarting || service.isStopping" :key="`icon-${server}`"
class="fa-overlap mr-1">
<icon name="circle" class="text-white" />
<icon v-if="service.isRestarting"
name="redo" class="text-primary" scale="0.5" />
<icon v-else-if="service.isStarting"
name="play" class="text-primary" scale="0.5" />
<icon v-else-if="service.isStopping"
name="stop" class="text-primary" scale="0.5" />
</icon>
<icon v-else :key="`icon-${server}`"
name="circle" :class="(service.alive && service.pid) ? 'text-success' : 'text-danger'" class="fa-overlap mr-1" />
</template>
{{ service }}
</div>
</template>

<b-dropdown-group v-if="isAllowed && isCluster"
:header="$i18n.t('CLUSTER')">
<b-dropdown-item v-if="restart && cluster.hasAlive"
@click="doRestartAll" @click.stop="onClick" :disabled="isLoading"><icon name="redo" class="mr-1" /> {{ $t('Restart All Sequentially') }}</b-dropdown-item>
<b-dropdown-item v-if="start && cluster.hasDead"
@click="doStartAll" @click.stop="onClick" :disabled="isLoading"><icon name="play" class="mr-1" /> {{ $t('Start All Sequentially') }}</b-dropdown-item>
<b-dropdown-item v-if="stop && cluster.hasAlive"
@click="doStopAll" @click.stop="onClick" :disabled="isLoading"><icon name="stop" class="mr-1" /> {{ $t('Stop All Sequentially') }}</b-dropdown-item>
</b-dropdown-group>

<template v-for="(service, server) in servers">
<b-dropdown-divider v-if="isCluster" :key="`divider-${server}`" />
<b-dropdown-group :key="`group-${server}`">
<template v-slot:header>
{{ server }}
</template>
<b-dropdown-form style="width: 400px;">
<base-system-service :id="service.id" :server="server" v-bind="{ acl, restart, start, stop }" />
</b-dropdown-form>
</b-dropdown-group>
</template>
</b-dropdown>
</template>
<script>
import BaseSystemService from '@/views/Status/services/_components/BaseSystemService'
const components = {
BaseSystemService
}
import { computed, nextTick, ref, toRefs, watch } from '@vue/composition-api'
import acl from '@/utils/acl'
import i18n from '@/utils/locale'
import { localeStrings } from '@/views/Status/services/config'
const props = {
service: {
type: String
},
restart: {
type: Boolean
},
start: {
type: Boolean
},
stop: {
type: Boolean
},
size: {
type: String,
default: 'md',
validator: value => ['sm', 'md', 'lg'].includes(value)
},
disabled: {
type: Boolean
},
acl: {
type: String,
default: 'SERVICES_READ'
}
}
const setup = (props, context) => {
const buttonRef = ref(null)
const onClick = event => {
event.stopPropagation()
nextTick(() => {
buttonRef.value.show() // keep open on click
})
}
const {
service,
disabled,
acl: _acl
} = toRefs(props)
const { root: { $store } = {}, emit } = context
const isAllowed = computed(() => {
if (_acl.value) {
const [ verb, ...nouns ] = Array.prototype.slice.call(_acl.value.toLowerCase().split('_')).reverse()
const noun = nouns.reverse().join('_')
return verb && nouns.length > 0 && acl.$can(verb, noun)
}
return true
})
const servers = computed(() => {
const { [service.value]: { servers = {} } = {} } = $store.getters['cluster/systemServicesByServer']
return servers
})
watch(service, () => {
if (isAllowed.value) {
$store.dispatch('cluster/getSystemServiceCluster', service.value)
}
}, { immediate: true })
const isCluster = computed(() => $store.getters['cluster/isCluster'])
const isLoading = computed(() => $store.getters['cluster/isLoading'])
const isDisabled = computed(() => disabled.value || !isAllowed.value || !Object.keys(servers.value).length)
const cluster = computed(() => {
const { [service.value]: cluster = {} } = $store.getters['cluster/systemServicesByServer']
return cluster
})
const tooltip = computed(() => {
switch (true) {
case !isAllowed.value:
return i18n.t('No permission, admin role {acl} required.', { acl: _acl.value })
//break
default:
return undefined
}
})
const doRestart = server => $store.dispatch('cluster/restartSystemService', { server, id: service.value }).then(() => {
$store.dispatch('notification/info', { url: server, message: i18n.t(localeStrings.SERVICES_RESTARTED_SUCCESS, { services: `<code>${service.value}</code>` }) })
emit('restart', { server, id: service.value })
}).catch(() => {
$store.dispatch('notification/danger', { url: server, message: i18n.t(localeStrings.SERVICES_RESTARTED_ERROR, { services: `<code>${service.value}</code>` }) })
})
const doStart = server => $store.dispatch('cluster/startSystemService', { server, id: service.value }).then(() => {
$store.dispatch('notification/info', { url: server, message: i18n.t(localeStrings.SERVICES_STARTED_SUCCESS, { services: `<code>${service.value}</code>` }) })
emit('start', { server, id: service.value })
}).catch(() => {
$store.dispatch('notification/danger', { url: server, message: i18n.t(localeStrings.SERVICES_STARTED_ERROR, { services: `<code>${service.value}</code>` }) })
})
const doStop = server => $store.dispatch('cluster/stopSystemService', { server, id: service.value }).then(() => {
$store.dispatch('notification/info', { url: server, message: i18n.t(localeStrings.SERVICES_STOPPED_SUCCESS, { services: `<code>${service.value}</code>` }) })
emit('stop', { server, id: service.value })
}).catch(() => {
$store.dispatch('notification/danger', { url: server, message: i18n.t(localeStrings.SERVICES_STOPPED_ERROR, { services: `<code>${service.value}</code>` }) })
})
const doRestartAll = () => $store.dispatch('cluster/restartSystemServiceCluster', service.value).then(() => {
$store.dispatch('notification/info', { url: 'CLUSTER', message: i18n.t(localeStrings.SERVICES_RESTARTED_SUCCESS, { services: `<code>${service.value}</code>` }) })
emit('restart', { id: service.value })
}).catch(() => {
$store.dispatch('notification/danger', { url: 'CLUSTER', message: i18n.t(localeStrings.SERVICES_RESTARTED_ERROR, { services: `<code>${service.value}</code>` }) })
})
const doStartAll = () => $store.dispatch('cluster/startSystemServiceCluster', service.value).then(() => {
$store.dispatch('notification/info', { url: 'CLUSTER', message: i18n.t(localeStrings.SERVICES_STARTED_SUCCESS, { services: `<code>${service.value}</code>` }) })
emit('start', { id: service.value })
}).catch(() => {
$store.dispatch('notification/danger', { url: 'CLUSTER', message: i18n.t(localeStrings.SERVICES_STARTED_ERROR, { services: `<code>${service.value}</code>` }) })
})
const doStopAll = () => $store.dispatch('cluster/stopSystemServiceCluster', service.value).then(() => {
$store.dispatch('notification/info', { url: 'CLUSTER', message: i18n.t(localeStrings.SERVICES_STOPPED_SUCCESS, { services: `<code>${service.value}</code>` }) })
emit('stop', { id: service.value })
}).catch(() => {
$store.dispatch('notification/danger', { url: 'CLUSTER', message: i18n.t(localeStrings.SERVICES_STOPPED_ERROR, { services: `<code>${service.value}</code>` }) })
})
return {
servers,
buttonRef,
onClick,
isAllowed,
isCluster,
isDisabled,
isLoading,
cluster,
tooltip,
doRestart,
doRestartAll,
doStart,
doStartAll,
doStop,
doStopAll
}
}
// @vue/component
export default {
name: 'base-button-system-service',
inheritAttrs: false,
components,
props,
setup
}
</script>
2 changes: 2 additions & 0 deletions html/pfappserver/root/src/components/new/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import BaseButtonRefresh from './BaseButtonRefresh'
import BaseButtonSave from './BaseButtonSave'
import BaseButtonSaveSearch from './BaseButtonSaveSearch'
import BaseButtonService from './BaseButtonService'
import BaseButtonSystemService from './BaseButtonSystemService'
import BaseButtonUpload from './BaseButtonUpload'
import BaseContainerLoading from './BaseContainerLoading'
import BaseCsvImport from './BaseCsvImport'
Expand Down Expand Up @@ -150,6 +151,7 @@ export {
BaseButtonSave,
BaseButtonSaveSearch,
BaseButtonService,
BaseButtonSystemService,
BaseButtonUpload,

// containers
Expand Down
93 changes: 83 additions & 10 deletions html/pfappserver/root/src/store/modules/cluster.js
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,17 @@ const api = (state, server = store.state.system.hostname) => {
})
})
},
systemService: id => {
return apiCall.getQuiet(['system_service', id, 'status'], { headers }).then(response => {
return response.data
}).catch(error => {
// 500 response error w/ not running
if (error.response && (!['ERR_BAD_RESPONSE'].includes(error.code) || ![500].includes(error.response.status))) {
throw error
}
return error.response.data
})
},
restartSystem: id => {
return apiCall.postQuiet(['system_service', id, 'restart'], { async: true }, { headers })
.then(response => {
Expand Down Expand Up @@ -196,6 +207,33 @@ const getters = {
}
}, sorted)
}, {})
},
systemServicesByServer: state => {
return Object.entries(state.servers).reduce((sorted, [server, {system_services = {}}]) => {
return Object.entries(system_services).reduce((sorted, [id, service]) => {
return {
...sorted,
[id]: {
servers: {
...((id in sorted) ? sorted[id].servers : {} ),
[server]: {
...service,
isDisabling: service.status === types.DISABLING,
isEnabling: service.status === types.ENABLING,
isRestarting: service.status === types.RESTARTING,
isStarting: service.status === types.STARTING,
isStopping: service.status === types.STOPPING,
}
},
hasAlive: Object.values(state.servers).findIndex(({ services: { [id]: service } }) => service && service.alive && service.pid) > -1,
hasDead: Object.values(state.servers).findIndex(({ services: { [id]: service } }) => service && !(service.alive || service.pid)) > -1,
hasEnabled: Object.values(state.servers).findIndex(({ services: { [id]: service } }) => service && service.enabled) > -1,
hasDisabled: Object.values(state.servers).findIndex(({ services: { [id]: service } }) => service && !service.enabled) > -1,
isProtected: !!protectedServices.find(listed => listed === id),
}
}
}, sorted)
}, {})
}
}

Expand Down Expand Up @@ -471,42 +509,68 @@ const actions = {
})
},

restartSystemService: ({ state, commit }, { id, server = store.state.system.hostname }) => {
getSystemService: ({ state, commit }, { server, id }) => {
commit('SYSTEM_SERVICE_REQUEST', { server, id })
return api(state, server).systemService(id).then(service => {
commit('SYSTEM_SERVICE_SUCCESS', { server, id, service })
return state.servers[server].services[id]
}).catch(err => {
const { response: { data: { message: error } = {} } = {} } = err
commit('SYSTEM_SERVICE_ERROR', { server, id, error })
throw err
})
},
getSystemServiceCluster: ({ state, dispatch }, id) => {
return dispatch('getConfig').then(() => {
let promises = []
Object.keys(state.servers).map(server => {
promises.push(dispatch('getSystemService', { server, id }))
})
return Promise.all(promises).then(servers => {
return servers.reduce((assoc, service, index) => {
const server = Object.keys(state.servers)[index]
return { ...assoc, [server]: state.servers[server].system_services[id] }
}, {})
})
})
},
restartSystemService: ({ state, commit, dispatch }, { id, server = store.state.system.hostname }) => {
commit('SYSTEM_SERVICE_REQUEST', { server, id })
commit('SYSTEM_SERVICE_RESTARTING', { server, id })
return api(state, server).restartSystem(id).then(response => {
commit('SYSTEM_SERVICE_RESTARTED', { server, id, response })
return state.servers[server].services[id]
return state.servers[server].system_services[id]
}).catch(err => {
const { response: { data: error } = {} } = err
commit('SYSTEM_SERVICE_ERROR', { server, id, error })
throw err
})
}).finally(() => dispatch('getSystemService', { server, id }))
},
startSystemService: ({ state, commit }, { id, server = store.state.system.hostname }) => {
startSystemService: ({ state, commit, dispatch }, { id, server = store.state.system.hostname }) => {
commit('SYSTEM_SERVICE_REQUEST', { server, id })
commit('SYSTEM_SERVICE_STARTING', { server, id })
return api(state, server).startSystem(id).then(response => {
commit('SYSTEM_SERVICE_STARTED', { server, id, response })
return state.servers[server].services[id]
return state.servers[server].system_services[id]
}).catch(err => {
const { response: { data: error } = {} } = err
commit('SYSTEM_SERVICE_ERROR', { server, id, error })
throw err
})
}).finally(() => dispatch('getSystemService', { server, id }))
},
stopSystemService: ({ state, commit }, { id, server = store.state.system.hostname }) => {
stopSystemService: ({ state, commit, dispatch }, { id, server = store.state.system.hostname }) => {
commit('SYSTEM_SERVICE_REQUEST', { server, id })
commit('SYSTEM_SERVICE_STOPPING', { server, id })
return api(state, server).stopSystem(id).then(response => {
commit('SYSTEM_SERVICE_STOPPED', { server, id, response })
return state.servers[server].services[id]
return state.servers[server].system_services[id]
}).catch(err => {
const { response: { data: error } = {} } = err
commit('SYSTEM_SERVICE_ERROR', { server, id, error })
throw err
})
}).finally(() => dispatch('getSystemService', { server, id }))
},

updateSystemd: ({ state, commit }, { id, server = store.state.system.hostname }) => {
commit('SYSTEMD_REQUEST', { server, id })
return api(state, server).updateSystemd(id).then(response => {
Expand Down Expand Up @@ -640,9 +704,18 @@ const mutations = {
SYSTEM_SERVICE_REQUEST: (state, { server, id }) => {
state.status = types.LOADING
Vue.set(state.servers, server, state.servers[server] || { services: {}, system_services: {} })
Vue.set(state.servers[server].system_services, id, state.servers[server].services[id] || {})
Vue.set(state.servers[server].system_services, id, state.servers[server].system_services[id] || {})
Vue.set(state.servers[server].system_services[id], 'status', types.LOADING)
},
SYSTEM_SERVICE_SUCCESS: (state, { server, id, service }) => {
service.id = id
service.pid = parseInt(service.pid)
service.alive = !!(service.pid)
service.message = ''
state.status = types.SUCCESS
state.message = ''
Vue.set(state.servers[server].system_services, id, { ...state.servers[server].system_services[id], ...service, status: types.SUCCESS })
},
SYSTEM_SERVICE_RESTARTING: (state, { server, id }) => {
state.status = types.LOADING
Vue.set(state.servers[server].system_services[id], 'status', types.RESTARTING)
Expand Down
Loading

0 comments on commit 47560ba

Please sign in to comment.