Merge pull request #339 from YunoHost/uniformize-actionmap-api

[enh] Uniformize actionmap api + human readable actions
This commit is contained in:
Alexandre Aubin 2021-04-12 17:55:08 +02:00 committed by GitHub
commit c3d86065fa
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
35 changed files with 291 additions and 176 deletions

View file

@ -5,7 +5,6 @@
import store from '@/store'
import { openWebSocket, getResponseData, handleError } from './handlers'
import { objectToParams } from '@/helpers/commons'
/**
@ -31,6 +30,31 @@ import { objectToParams } from '@/helpers/commons'
*/
/**
* Converts an object literal into an `URLSearchParams` that can be turned into a
* query string or used as a body in a `fetch` call.
*
* @param {Object} obj - An object literal to convert.
* @param {Object} options
* @param {Boolean} [options.addLocale=false] - Option to append the locale to the query string.
* @return {URLSearchParams}
*/
export function objectToParams (obj, { addLocale = false } = {}) {
const urlParams = new URLSearchParams()
for (const [key, value] of Object.entries(obj)) {
if (Array.isArray(value)) {
value.forEach(v => urlParams.append(key, v))
} else {
urlParams.append(key, value)
}
}
if (addLocale) {
urlParams.append('locale', store.getters.locale)
}
return urlParams
}
export default {
options: {
credentials: 'include',
@ -55,9 +79,15 @@ export default {
* @param {Options} [options={ wait = true, websocket = true, initial = false, asFormData = false }]
* @return {Promise<Object|Error>} Promise that resolve the api response data or an error.
*/
async fetch (method, uri, data = {}, { wait = true, websocket = true, initial = false, asFormData = false } = {}) {
async fetch (
method,
uri,
data = {},
humanKey = null,
{ wait = true, websocket = true, initial = false, asFormData = false } = {}
) {
// `await` because Vuex actions returns promises by default.
const request = await store.dispatch('INIT_REQUEST', { method, uri, initial, wait, websocket })
const request = await store.dispatch('INIT_REQUEST', { method, uri, humanKey, initial, wait, websocket })
if (websocket) {
await openWebSocket(request)
@ -70,6 +100,10 @@ export default {
options = { ...options, method, body: objectToParams(data, { addLocale: true }) }
}
if (['upgrade', 'postinstall', 'reboot', 'shutdown', 'diagnsosis'].some(action => uri.includes(action))) {
store.dispatch('END_REQUEST', { request, success: true, wait })
return
}
const response = await fetch('/yunohost/api/' + uri, options)
const responseData = await getResponseData(response)
store.dispatch('END_REQUEST', { request, success: response.ok, wait })
@ -92,10 +126,10 @@ export default {
const results = []
if (wait) store.commit('SET_WAITING', true)
try {
for (const [method, uri, data, options = {}] of queries) {
for (const [method, uri, data, humanKey, options = {}] of queries) {
if (wait) options.wait = false
if (initial) options.initial = true
results.push(await this[method.toLowerCase()](uri, data, options))
results.push(await this[method.toLowerCase()](uri, data, humanKey, options))
}
} finally {
// Stop waiting even if there is an error.
@ -114,10 +148,10 @@ export default {
* @param {Options} [options={}] - options to apply to the call (default is `{ websocket: false, wait: false }`)
* @return {Promise<Object|Error>} Promise that resolve the api response data or an error.
*/
get (uri, data = null, options = {}) {
get (uri, data = null, humanKey = null, options = {}) {
options = { websocket: false, wait: false, ...options }
if (typeof uri === 'string') return this.fetch('GET', uri, null, options)
return store.dispatch('GET', { ...uri, options })
if (typeof uri === 'string') return this.fetch('GET', uri, null, humanKey, options)
return store.dispatch('GET', { ...uri, humanKey, options })
},
@ -129,9 +163,9 @@ export default {
* @param {Options} [options={}] - options to apply to the call
* @return {Promise<Object|Error>} Promise that resolve the api response data or an error.
*/
post (uri, data = {}, options = {}) {
if (typeof uri === 'string') return this.fetch('POST', uri, data, options)
return store.dispatch('POST', { ...uri, data, options })
post (uri, data = {}, humanKey = null, options = {}) {
if (typeof uri === 'string') return this.fetch('POST', uri, data, humanKey, options)
return store.dispatch('POST', { ...uri, data, humanKey, options })
},
@ -143,9 +177,9 @@ export default {
* @param {Options} [options={}] - options to apply to the call
* @return {Promise<Object|Error>} Promise that resolve the api response data or an error.
*/
put (uri, data = {}, options = {}) {
if (typeof uri === 'string') return this.fetch('PUT', uri, data, options)
return store.dispatch('PUT', { ...uri, data, options })
put (uri, data = {}, humanKey = null, options = {}) {
if (typeof uri === 'string') return this.fetch('PUT', uri, data, humanKey, options)
return store.dispatch('PUT', { ...uri, data, humanKey, options })
},
@ -157,8 +191,8 @@ export default {
* @param {Options} [options={}] - options to apply to the call (default is `{ websocket: false, wait: false }`)
* @return {Promise<Object|Error>} Promise that resolve the api response data or an error.
*/
delete (uri, data = {}, options = {}) {
if (typeof uri === 'string') return this.fetch('DELETE', uri, data, options)
return store.dispatch('DELETE', { ...uri, data, options })
delete (uri, data = {}, humanKey = null, options = {}) {
if (typeof uri === 'string') return this.fetch('DELETE', uri, data, humanKey, options)
return store.dispatch('DELETE', { ...uri, data, humanKey, options })
}
}

View file

@ -1,2 +1,2 @@
export { default } from './api'
export { default, objectToParams } from './api'
export { handleError, registerGlobalErrorHandlers } from './handlers'

View file

@ -5,8 +5,7 @@
<!-- REQUEST DESCRIPTION -->
<strong class="request-desc">
{{ request.uri | readableUri }}
<small>({{ $t('history.methods.' + request.method) }})</small>
{{ request.humanRoute }}
</strong>
<div v-if="request.errors || request.warnings">

View file

@ -1,6 +1,3 @@
import store from '@/store'
/**
* Allow to set a timeout on a `Promise` expected response.
* The returned Promise will be rejected if the original Promise is not resolved or
@ -19,31 +16,6 @@ export function timeout (promise, delay) {
}
/**
* Converts an object literal into an `URLSearchParams` that can be turned into a
* query string or used as a body in a `fetch` call.
*
* @param {Object} obj - An object literal to convert.
* @param {Object} options
* @param {Boolean} [options.addLocale=false] - Option to append the locale to the query string.
* @return {URLSearchParams}
*/
export function objectToParams (obj, { addLocale = false } = {}) {
const urlParams = new URLSearchParams()
for (const [key, value] of Object.entries(obj)) {
if (Array.isArray(value)) {
value.forEach(v => urlParams.append(key, v))
} else {
urlParams.append(key, value)
}
}
if (addLocale) {
urlParams.append('locale', store.getters.locale)
}
return urlParams
}
/**
* Check if passed value is an object literal.
*

View file

@ -91,8 +91,8 @@
"confirm_app_default": "Are you sure you want to make this app default?",
"confirm_change_maindomain": "Are you sure you want to change the main domain?",
"confirm_delete": "Are you sure you want to delete {name}?",
"confirm_firewall_open": "Are you sure you want to open port {port} (protocol: {protocol}, connection: {connection})",
"confirm_firewall_close": "Are you sure you want to close port {port} (protocol: {protocol}, connection: {connection})",
"confirm_firewall_allow": "Are you sure you want to open port {port} (protocol: {protocol}, connection: {connection})",
"confirm_firewall_disallow": "Are you sure you want to close port {port} (protocol: {protocol}, connection: {connection})",
"confirm_install_custom_app": "WARNING! Installing 3rd party applications may compromise the integrity and security of your system. You should probably NOT install it unless you know what you are doing. Are you willing to take that risk?",
"confirm_install_domain_root": "Are you sure you want to install this application on '/'? You will not be able to install any other app on {domain}",
"confirm_app_install": "Are you sure you want to install this application?",
@ -201,6 +201,7 @@
"groups_and_permissions_manage": "Manage groups and permissions",
"permissions": "Permissions",
"history": {
"is_empty": "Nothing in history for now.",
"title": "History",
"last_action": "Last action:",
"methods": {
@ -330,6 +331,82 @@
"rerun_diagnosis": "Rerun diagnosis",
"restore": "Restore",
"restart": "Restart",
"human_routes": {
"adminpw": "Change admin password",
"apps": {
"change_label": "Change label of '{prevName}' for '{nextName}'",
"change_url": "Change access url of '{name}'",
"install": "Install app '{name}'",
"set_default": "Redirect '{domain}' domain root to '{name}'",
"perform_action": "Perform action '{action}' of app '{name}'",
"uninstall": "Uninstall app '{name}'",
"update_config": "Update app '{name}' configuration"
},
"backups": {
"create": "Create a backup",
"delete": "Delete backup '{name}'",
"restore": "Restore backup '{name}'"
},
"diagnosis": {
"ignore": {
"error": "Ignore an error",
"warning": "Ignore a warning"
},
"run": "Run the diagnosis",
"run_specific": "Run '{description}' diagnosis",
"unignore": {
"error": "Unignore an error",
"warning": "Unignore a warning"
}
},
"domains": {
"add": "Add domain '{name}'",
"delete": "Delete domain '{name}'",
"install_LE": "Install certificate for '{name}'",
"manual_renew_LE": "Renew certificate for '{name}'",
"regen_selfsigned": "Renew self-signed certificate for '{name}'",
"revert_to_selfsigned": "Revert to self-signed certificate for '{name}'",
"set_default": "Set '{name}' as default domain"
},
"firewall": {
"ports": "{action} port {port} ({protocol}, {connection})",
"upnp": "{action} UPnP"
},
"groups": {
"create": "Create group '{name}'",
"delete": "Delete group '{name}'",
"add": "Add '{user}' to group '{name}'",
"remove": "Remove '{user}' from group '{name}'"
},
"migrations": {
"run": "Run migrations",
"skip": "Skip migrations"
},
"permissions": {
"add": "Allow '{name}' to access '{perm}'",
"remove": "Remove '{name}' access to '{perm}'"
},
"postinstall": "Run the post-install",
"reboot": "Reboot the server",
"services": {
"restart": "Restart the service '{name}'",
"start": "Start the service '{name}'",
"stop": "Stop the service '{name}'"
},
"share_logs": "Generate link for log '{name}'",
"shutdown": "Shutdown the server",
"update": "Check for updates",
"upgrade": {
"system": "Upgrade the system",
"apps": "Upgrade all apps",
"app": "Upgrade '{app}' app"
},
"users": {
"create": "Create user '{name}'",
"delete": "Delete user '{name}'",
"update": "Update user '{name}'"
}
},
"run": "Run",
"running": "Running",
"save": "Save",

View file

@ -33,12 +33,10 @@ body {
min-height: 100vh
}
.menu-list {
.list-group-item {
padding: $list-group-item-padding-y 0;
display: flex;
align-items: center;
}
.menu-list .list-group-item {
padding: $list-group-item-padding-y 0;
display: flex;
align-items: center;
}
@ -114,10 +112,6 @@ body {
top: 2px;
}
.list-group-item .icon {
margin-left: 0.3rem;
}
// Fork-awesome overrides
.fa-fw {
width: 1.25em !important;

View file

@ -90,21 +90,21 @@ export default {
},
actions: {
'GET' ({ state, commit, rootState }, { uri, param, storeKey = uri, options = {} }) {
'GET' ({ state, commit, rootState }, { uri, param, humanKey, storeKey = uri, options = {} }) {
const noCache = !rootState.cache || options.noCache || false
const currentState = param ? state[storeKey][param] : state[storeKey]
// if data has already been queried, simply return
if (currentState !== undefined && !noCache) return currentState
return api.fetch('GET', param ? `${uri}/${param}` : uri, null, options).then(responseData => {
return api.fetch('GET', param ? `${uri}/${param}` : uri, null, humanKey, options).then(responseData => {
const data = responseData[storeKey] ? responseData[storeKey] : responseData
commit('SET_' + storeKey.toUpperCase(), param ? [param, data] : data)
return param ? state[storeKey][param] : state[storeKey]
})
},
'POST' ({ state, commit }, { uri, storeKey = uri, data, options }) {
return api.fetch('POST', uri, data, options).then(responseData => {
'POST' ({ state, commit }, { uri, storeKey = uri, data, humanKey, options }) {
return api.fetch('POST', uri, data, humanKey, options).then(responseData => {
// FIXME api/domains returns null
if (responseData === null) responseData = data
responseData = responseData[storeKey] ? responseData[storeKey] : responseData
@ -113,16 +113,16 @@ export default {
})
},
'PUT' ({ state, commit }, { uri, param, storeKey = uri, data, options }) {
return api.fetch('PUT', param ? `${uri}/${param}` : uri, data, options).then(responseData => {
'PUT' ({ state, commit }, { uri, param, storeKey = uri, data, humanKey, options }) {
return api.fetch('PUT', param ? `${uri}/${param}` : uri, data, humanKey, options).then(responseData => {
const data = responseData[storeKey] ? responseData[storeKey] : responseData
commit('UPDATE_' + storeKey.toUpperCase(), param ? [param, data] : data)
return param ? state[storeKey][param] : state[storeKey]
})
},
'DELETE' ({ commit }, { uri, param, storeKey = uri, data, options }) {
return api.fetch('DELETE', param ? `${uri}/${param}` : uri, data, options).then(() => {
'DELETE' ({ commit }, { uri, param, storeKey = uri, data, humanKey, options }) {
return api.fetch('DELETE', param ? `${uri}/${param}` : uri, data, humanKey, options).then(() => {
commit('DEL_' + storeKey.toUpperCase(), param)
})
}

View file

@ -1,7 +1,8 @@
import Vue from 'vue'
import api from '@/api'
import router from '@/router'
import { timeout } from '@/helpers/commons'
import i18n from '@/i18n'
import { timeout, isObjectLiteral } from '@/helpers/commons'
export default {
state: {
@ -107,7 +108,7 @@ export default {
},
'LOGIN' ({ dispatch }, password) {
return api.post('login', { password }, { websocket: false }).then(() => {
return api.post('login', { password }, null, { websocket: false }).then(() => {
dispatch('CONNECT')
})
},
@ -123,8 +124,12 @@ export default {
})
},
'INIT_REQUEST' ({ commit }, { method, uri, initial, wait, websocket }) {
let request = { method, uri, initial, status: 'pending' }
'INIT_REQUEST' ({ commit }, { method, uri, humanKey, initial, wait, websocket }) {
// Try to find a description for an API route to display in history and modals
const { key, ...args } = isObjectLiteral(humanKey) ? humanKey : { key: humanKey }
const humanRoute = key ? i18n.t('human_routes.' + key, args) : `[${method}] /${uri}`
let request = { method, uri, humanRoute, initial, status: 'pending' }
if (websocket) {
request = { ...request, messages: [], date: Date.now(), warnings: 0, errors: 0 }
commit('ADD_HISTORY_ACTION', request)

View file

@ -6,7 +6,7 @@
:key="item.routeName"
:to="{ name: item.routeName }"
>
<icon :iname="item.icon" class="lg" />
<icon :iname="item.icon" class="lg ml-1" />
<h4>{{ $t(item.translation) }}</h4>
<icon iname="chevron-right" class="lg fs-sm ml-auto" />
</b-list-group-item>

View file

@ -33,6 +33,10 @@
class="accordion" role="tablist"
id="history" ref="history"
>
<p v-if="history.length === 0" class="alert m-0 px-2 py-1">
{{ $t('history.is_empty') }}
</p>
<!-- ACTION LIST -->
<b-card
v-for="(action, i) in history" :key="i"

View file

@ -38,11 +38,10 @@
</template>
<script>
import api from '@/api'
import api, { objectToParams } from '@/api'
import { validationMixin } from 'vuelidate'
import { formatI18nField, formatYunoHostArguments, formatFormData } from '@/helpers/yunohostArguments'
import { objectToParams } from '@/helpers/commons'
export default {
name: 'AppActions',
@ -100,7 +99,11 @@ export default {
// FIXME api expects at least one argument ?! (fake one given with { dontmindthis } )
const args = objectToParams(action.form ? formatFormData(action.form) : { dontmindthis: undefined })
api.put(`apps/${this.id}/actions/${action.id}`, { args }).then(response => {
api.put(
`apps/${this.id}/actions/${action.id}`,
{ args },
{ key: 'apps.perform_action', action: action.id, name: this.id }
).then(() => {
this.$refs.view.fetchQueries()
}).catch(err => {
if (err.name !== 'APIBadRequestError') throw err

View file

@ -164,7 +164,7 @@ export default {
data () {
return {
queries: [
['GET', 'appscatalog?full&with_categories']
['GET', 'apps/catalog?full&with_categories']
],
// Data

View file

@ -38,9 +38,8 @@
import { validationMixin } from 'vuelidate'
// FIXME needs test and rework
import api from '@/api'
import api, { objectToParams } from '@/api'
import { formatI18nField, formatYunoHostArguments, formatFormData } from '@/helpers/yunohostArguments'
import { objectToParams } from '@/helpers/commons'
export default {
name: 'AppConfigPanel',
@ -103,7 +102,9 @@ export default {
applyConfig (id_) {
const args = objectToParams(formatFormData(this.forms[id_]))
api.post(`apps/${this.id}/config`, { args }).then(response => {
api.put(
`apps/${this.id}/config`, { args }, { key: 'apps.update_config', name: this.id }
).then(response => {
console.log('SUCCESS', response)
}).catch(err => {
if (err.name !== 'APIBadRequestError') throw err

View file

@ -252,7 +252,11 @@ export default {
changeLabel (permName, data) {
data.show_tile = data.show_tile ? 'True' : 'False'
api.put('users/permissions/' + permName, data).then(this.$refs.view.fetchQueries)
api.put(
'users/permissions/' + permName,
data,
{ key: 'apps.change_label', prevName: this.infos.label, nextName: data.label }
).then(this.$refs.view.fetchQueries)
},
async changeUrl () {
@ -262,7 +266,8 @@ export default {
const { domain, path } = this.form.url
api.put(
`apps/${this.id}/changeurl`,
{ domain, path: '/' + path }
{ domain, path: '/' + path },
{ key: 'apps.change_url', name: this.infos.label }
).then(this.$refs.view.fetchQueries)
},
@ -270,7 +275,11 @@ export default {
const confirmed = await this.$askConfirmation(this.$i18n.t('confirm_app_default'))
if (!confirmed) return
api.put(`apps/${this.id}/default`).then(this.$refs.view.fetchQueries)
api.put(
`apps/${this.id}/default`,
{},
{ key: 'apps.set_default', name: this.infos.label, domain: this.app.domain }
).then(this.$refs.view.fetchQueries)
},
async uninstall () {
@ -279,7 +288,7 @@ export default {
)
if (!confirmed) return
api.delete('apps/' + this.id).then(() => {
api.delete('apps/' + this.id, {}, { key: 'apps.uninstall', name: this.infos.label }).then(() => {
this.$router.push({ name: 'app-list' })
})
}

View file

@ -49,8 +49,7 @@
<script>
import { validationMixin } from 'vuelidate'
import api from '@/api'
import { objectToParams } from '@/helpers/commons'
import api, { objectToParams } from '@/api'
import { formatYunoHostArguments, formatI18nField, formatFormData } from '@/helpers/yunohostArguments'
export default {
@ -93,7 +92,7 @@ export default {
},
getApiManifest () {
return api.get('appscatalog?full').then(response => response.apps[this.id].manifest)
return api.get('apps/catalog?full').then(response => response.apps[this.id].manifest)
},
formatManifestData (manifest) {
@ -129,7 +128,7 @@ export default {
const { data: args, label } = formatFormData(this.form, { extract: ['label'] })
const data = { app: this.id, label, args: objectToParams(args) }
api.post('apps', data).then(response => {
api.post('apps', data, { key: 'apps.install', name: this.name }).then(() => {
this.$router.push({ name: 'app-list' })
}).catch(err => {
if (err.name !== 'APIBadRequestError') throw err

View file

@ -164,7 +164,7 @@ export default {
}
}
api.post('backup', data).then(response => {
api.post('backups', data, 'backups.create').then(() => {
this.$router.push({ name: 'backup-list', params: { id: this.id } })
})
}

View file

@ -132,7 +132,7 @@ export default {
data () {
return {
queries: [
['GET', `backup/archives/${this.name}?with_details`]
['GET', `backups/${this.name}?with_details`]
],
selected: [],
error: '',
@ -210,7 +210,9 @@ export default {
}
}
api.post('backup/restore/' + this.name, data).then(response => {
api.put(
`backups/${this.name}/restore`, data, { key: 'backups.restore', name: this.name }
).then(() => {
this.isValid = null
}).catch(err => {
if (err.name !== 'APIBadRequestError') throw err
@ -223,14 +225,16 @@ export default {
const confirmed = await this.$askConfirmation(this.$i18n.t('confirm_delete', { name: this.name }))
if (!confirmed) return
api.delete('backup/archives/' + this.name).then(() => {
api.delete(
'backups/' + this.name, {}, { key: 'backups.delete', name: this.name }
).then(() => {
this.$router.push({ name: 'backup-list', params: { id: this.id } })
})
},
downloadBackup () {
const host = this.$store.getters.host
window.open(`https://${host}/yunohost/api/backup/download/${this.name}`, '_blank')
window.open(`https://${host}/yunohost/api/backups/${this.name}/download`, '_blank')
}
},

View file

@ -45,7 +45,7 @@ export default {
data () {
return {
queries: [
['GET', 'backup/archives?with_info']
['GET', 'backups?with_info']
],
archives: undefined
}

View file

@ -42,7 +42,7 @@
</template>
<template #header-buttons>
<b-button size="sm" :variant="report.items ? 'info' : 'success'" @click="runDiagnosis(report.id)">
<b-button size="sm" :variant="report.items ? 'info' : 'success'" @click="runDiagnosis(report)">
<icon iname="refresh" /> {{ $t('rerun_diagnosis') }}
</b-button>
</template>
@ -64,13 +64,13 @@
<div class="d-flex flex-column flex-lg-row ml-auto">
<b-button
v-if="item.ignored" size="sm"
@click="toggleIgnoreIssue(false, report, item)"
@click="toggleIgnoreIssue('unignore', report, item)"
>
<icon iname="bell" /> {{ $t('unignore') }}
</b-button>
<b-button
v-else-if="item.issue" variant="warning" size="sm"
@click="toggleIgnoreIssue(true, report, item)"
@click="toggleIgnoreIssue('ignore', report, item)"
>
<icon iname="bell-slash" /> {{ $t('ignore') }}
</b-button>
@ -115,8 +115,8 @@ export default {
data () {
return {
queries: [
['POST', 'diagnosis/run?except_if_never_ran_yet'],
['GET', 'diagnosis/show?full']
['PUT', 'diagnosis/run?except_if_never_ran_yet', {}, 'diagnosis.run'],
['GET', 'diagnosis?full']
],
reports: undefined
}
@ -171,22 +171,30 @@ export default {
this.reports = reports
},
runDiagnosis (id = null) {
runDiagnosis ({ id = null, description } = {}) {
const param = id !== null ? '?force' : ''
const data = id !== null ? { categories: [id] } : {}
api.post('diagnosis/run' + param, data).then(this.$refs.view.fetchQueries)
api.put(
'diagnosis/run' + param,
data,
{ key: 'diagnosis.run' + (id !== null ? '_specific' : ''), description }
).then(this.$refs.view.fetchQueries)
},
toggleIgnoreIssue (ignore, report, item) {
const key = (ignore ? 'add' : 'remove') + '_filter'
toggleIgnoreIssue (action, report, item) {
const filterArgs = Object.entries(item.meta).reduce((filterArgs, entries) => {
filterArgs.push(entries.join('='))
return filterArgs
}, [report.id])
api.post('diagnosis/ignore', { [key]: filterArgs }).then(() => {
item.ignored = ignore
if (ignore) {
api.put(
'diagnosis/' + action,
{ filter: filterArgs },
`diagnosis.${action}.${item.status.toLowerCase()}`
).then(() => {
item.ignored = action === 'ignore'
if (item.ignored) {
report[item.status.toLowerCase() + 's']--
} else {
report.ignoreds--

View file

@ -27,8 +27,7 @@ export default {
onSubmit ({ domain, domainType }) {
const uri = 'domains' + (domainType === 'dynDomain' ? '?dyndns' : '')
api.post(
{ uri, storeKey: 'domains' },
{ domain }
{ uri, storeKey: 'domains' }, { domain }, { key: 'domains.add', name: domain }
).then(() => {
this.$router.push({ name: 'domain-list' })
}).catch(err => {

View file

@ -84,7 +84,7 @@ export default {
data () {
return {
queries: [
['GET', `domains/cert-status/${this.name}?full`]
['GET', `domains/${this.name}/cert?full`]
],
cert: undefined,
actionsEnabled: undefined
@ -147,13 +147,13 @@ export default {
const confirmed = await this.$askConfirmation(this.$i18n.t(`confirm_cert_${action}`))
if (!confirmed) return
let uri = 'domains/cert-install/' + this.name
let uri = `domains/${this.name}/cert`
if (action === 'regen_selfsigned') uri += '?self_signed'
else if (action === 'manual_renew_LE') uri += '?force'
else if (action === 'revert_to_selfsigned') uri += '?self_signed&force'
// FIXME trigger loading ? while posting ? while getting ?
// this.$refs.view.fallback_loading = true
api.post(uri).then(this.$refs.view.fetchQueries)
api.put(
uri, {}, { key: 'domains.' + action, name: this.name }
).then(this.$refs.view.fetchQueries)
}
}
}

View file

@ -83,7 +83,7 @@ export default {
if (!confirmed) return
api.delete(
{ uri: 'domains', param: this.name }
{ uri: 'domains', param: this.name }, {}, { key: 'domains.delete', name: this.name }
).then(() => {
this.$router.push({ name: 'domain-list' })
})
@ -94,8 +94,9 @@ export default {
if (!confirmed) return
api.put(
{ uri: 'domains/main', storeKey: 'main_domain' },
{ new_main_domain: this.name }
{ uri: `domains/${this.name}/main`, storeKey: 'main_domain' },
{},
{ key: 'domains.set_default', name: this.name }
).then(() => {
// FIXME Have to commit by hand here since the response is empty (should return the given name)
this.$store.commit('UPDATE_MAIN_DOMAIN', this.name)

View file

@ -45,7 +45,8 @@ export default {
onSubmit () {
api.post(
{ uri: 'users/groups', storeKey: 'groups' },
this.form
this.form,
{ key: 'groups.create', name: this.form.groupname }
).then(() => {
this.$router.push({ name: 'group-list' })
}).catch(err => {

View file

@ -216,22 +216,28 @@ export default {
},
onPermissionChanged ({ item, index, name, groupType, action }) {
const uri = 'users/permissions/' + item
const data = { [action]: name }
// const uri = 'users/permissions/' + item
// const data = { [action]: name }
const from = action === 'add' ? 'availablePermissions' : 'permissions'
const to = action === 'add' ? 'permissions' : 'availablePermissions'
api.put(uri, data).then(() => {
api.put(
`users/permissions/${item}/${action}/${name}`,
{},
{ key: 'permissions.' + action, perm: item.replace('.main', ''), name }
).then(() => {
this[groupType + 'Groups'][name][from].splice(index, 1)
this[groupType + 'Groups'][name][to].push(item)
})
},
onUserChanged ({ item, index, name, action }) {
const uri = 'users/groups/' + name
const data = { [action]: item }
const from = action === 'add' ? 'availableMembers' : 'members'
const to = action === 'add' ? 'members' : 'availableMembers'
api.put(uri, data).then(() => {
api.put(
`users/groups/${name}/${action}/${item}`,
{},
{ key: 'groups.' + action, user: item, name }
).then(() => {
this.normalGroups[name][from].splice(index, 1)
this.normalGroups[name][to].push(item)
})
@ -255,7 +261,7 @@ export default {
if (!confirmed) return
api.delete(
{ uri: 'users/groups', param: name, storeKey: 'groups' }
{ uri: 'users/groups', param: name, storeKey: 'groups' }, {}, { key: 'groups.delete', name }
).then(() => {
Vue.delete(this.normalGroups, name)
})

View file

@ -120,13 +120,11 @@ export default {
)
if (!confirmed) return
if (!['start', 'restart', 'stop'].includes(action)) return
const method = action === 'stop' ? 'delete' : 'put'
const uri = action === 'restart'
? `services/${this.name}/restart`
: 'services/' + this.name
api[method](uri).then(this.$refs.view.fetchQueries)
api.put(
`services/${this.name}/${action}`,
{},
{ key: 'services.' + action, name: this.name }
).then(this.$refs.view.fetchQueries)
},
shareLogs () {

View file

@ -44,8 +44,8 @@ export default {
this.serverError = ''
api.fetchAll(
[['POST', 'login', { password: currentPassword }, { websocket: false }],
['PUT', 'admisnpw', { new_password: password }]],
[['POST', 'login', { password: currentPassword }, null, { websocket: false }],
['PUT', 'adminpw', { new_password: password }, 'adminpw']],
{ wait: true }
).then(() => {
this.$store.dispatch('DISCONNECT')

View file

@ -115,8 +115,8 @@ export default {
// Ports form data
actionChoices: [
{ value: 'open', text: this.$i18n.t('open') },
{ value: 'close', text: this.$i18n.t('close') }
{ value: 'allow', text: this.$i18n.t('open') },
{ value: 'disallow', text: this.$i18n.t('close') }
],
connectionChoices: [
{ value: 'ipv4', text: this.$i18n.t('ipv4') },
@ -128,7 +128,7 @@ export default {
{ value: 'Both', text: this.$i18n.t('both') }
],
form: {
action: 'open',
action: 'allow',
port: undefined,
connection: 'ipv4',
protocol: 'TCP'
@ -176,27 +176,21 @@ export default {
this.upnpEnabled = data.uPnP.enabled
},
togglePort ({ action, port, protocol, connection }) {
return new Promise((resolve, reject) => {
this.$askConfirmation(
this.$i18n.t('confirm_firewall_' + action, { port, protocol, connection })
).then(confirmed => {
if (confirmed) {
const method = action === 'open' ? 'post' : 'delete'
api[method](
`/firewall/port?${connection}_only`,
{ port, protocol },
{ wait: false }
).then(() => {
resolve(confirmed)
}).catch(error => {
reject(error)
})
} else {
resolve(confirmed)
}
})
})
async togglePort ({ action, port, protocol, connection }) {
const confirmed = await this.$askConfirmation(
this.$i18n.t('confirm_firewall_' + action, { port, protocol, connection })
)
if (!confirmed) {
return Promise.resolve(confirmed)
}
const actionTrad = this.$i18n.t({ allow: 'open', disallow: 'close' }[action])
return api.put(
`firewall/${protocol}/${action}/${port}?${connection}_only`,
{},
{ key: 'firewall.ports', protocol, action: actionTrad, port, connection },
{ wait: false }
).then(() => confirmed)
},
async toggleUpnp (value) {
@ -204,7 +198,11 @@ export default {
const confirmed = await this.$askConfirmation(this.$i18n.t('confirm_upnp_' + action))
if (!confirmed) return
api.get('firewall/upnp?action=' + action, null, { websocket: true, wait: true }).then(() => {
api.put(
'firewall/upnp/' + action,
{},
{ key: 'firewall.upnp', action: this.$i18n.t(action) }
).then(() => {
// FIXME Couldn't test when it works.
this.$refs.view.fetchQueries()
}).catch(err => {
@ -215,7 +213,7 @@ export default {
onTablePortToggling (port, protocol, connection, index, value) {
this.$set(this.protocols[protocol][index], connection, value)
const action = value ? 'open' : 'close'
const action = value ? 'allow' : 'disallow'
this.togglePort({ action, port, protocol, connection }).then(toggled => {
// Revert change on cancel
if (!toggled) {

View file

@ -6,7 +6,7 @@
:key="item.routeName"
:to="{name: item.routeName}"
>
<icon :iname="item.icon" class="lg" />
<icon :iname="item.icon" class="lg ml-1" />
<h4>{{ $t(item.translation) }}</h4>
<icon iname="chevron-right" class="lg fs-sm ml-auto" />
</b-list-group-item>

View file

@ -60,8 +60,8 @@
</template>
<script>
import api from '@/api'
import { objectToParams, escapeHtml } from '@/helpers/commons'
import api, { objectToParams } from '@/api'
import { escapeHtml } from '@/helpers/commons'
import { readableDate } from '@/helpers/filters/date'
export default {
@ -127,7 +127,12 @@ export default {
},
shareLogs () {
api.get(`logs/${this.name}?share`, null, { websocket: true }).then(({ url }) => {
api.get(
`logs/${this.name}/share`,
null,
{ key: 'share_logs', name: this.name },
{ websocket: true }
).then(({ url }) => {
window.open(url, '_blank')
})
}

View file

@ -121,7 +121,7 @@ export default {
}
// Check that every migration's disclaimer has been checked.
if (Object.values(this.checked).every(value => value === true)) {
api.post('migrations/run?accept_disclaimer').then(() => {
api.post('migrations/run?accept_disclaimer', 'migrations.run').then(() => {
this.$refs.view.fetchQueries()
})
}
@ -130,8 +130,7 @@ export default {
async skipMigration (id) {
const confirmed = await this.$askConfirmation(this.$i18n.t('confirm_migrations_skip'))
if (!confirmed) return
api.post('/migrations/run', { skip: '', targets: id }).then(() => {
api.post('/migrations/run', { skip: '', targets: id }, 'migration.skip').then(() => {
this.$refs.view.fetchQueries()
})
}

View file

@ -66,7 +66,7 @@ export default {
if (!confirmed) return
this.action = action
api.put(action + '?force').then(() => {
api.put(action + '?force', {}, action).then(() => {
// Use 'RESET_CONNECTED' and not 'DISCONNECT' else user will be redirect to login
this.$store.dispatch('RESET_CONNECTED')
this.inProcess = true

View file

@ -74,7 +74,7 @@ export default {
return {
queries: [
['GET', 'migrations?pending'],
['PUT', 'update']
['PUT', 'update/all', {}, 'update']
],
// API data
migrationsNotDone: undefined,
@ -95,11 +95,8 @@ export default {
const confirmed = await this.$askConfirmation(confirmMsg)
if (!confirmed) return
const uri = type === 'specific_app'
? 'upgrade/apps?app=' + id
: 'upgrade?' + type
api.put(uri).then(() => {
const uri = id !== null ? `apps/${id}/upgrade` : 'upgrade/' + type
api.put(uri, {}, { key: 'upgrade.' + (id ? 'app' : type), app: id }).then(() => {
this.$router.push({ name: 'tool-logs' })
})
}

View file

@ -175,7 +175,7 @@ export default {
onSubmit () {
const data = formatFormData(this.form, { flatten: true })
api.post({ uri: 'users' }, data).then(() => {
api.post({ uri: 'users' }, data, { key: 'users.create', name: this.form.username }).then(() => {
this.$router.push({ name: 'user-list' })
}).catch(err => {
if (err.name !== 'APIBadRequestError') throw err

View file

@ -296,7 +296,8 @@ export default {
api.put(
{ uri: 'users', param: this.name, storeKey: 'users_details' },
data
data,
{ key: 'users.update', name: this.name }
).then(() => {
this.$router.push({ name: 'user-info', param: { name: this.name } })
}).catch(err => {

View file

@ -108,7 +108,8 @@ export default {
const data = this.purge ? { purge: '' } : {}
api.delete(
{ uri: 'users', param: this.name, storeKey: 'users_details' },
data
data,
{ key: 'users.delete', name: this.name }
).then(() => {
this.$router.push({ name: 'user-list' })
})