Merge branch 'feature/install-switch-frontends' into 'develop'

Ability to install frontends

Closes #164

See merge request pleroma/admin-fe!197
This commit is contained in:
Angelina Filippova 2021-03-14 11:58:27 +00:00
commit 1e5f128a56
28 changed files with 330 additions and 28 deletions

View file

@ -16,6 +16,8 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
- Add ability to configure Media Preview Proxy, User Backup, Websocket based federation and Pleroma.Web.Endpoint.MetricsExporter settings
- Mobile and Tablet UI for Single Report show page
- Ability to set rules and conditions for rendering settings (e.g. `:proxy_remote` setting is hidden if `:uploader` setting is set to `Pleroma.Uploaders.Local`)
- Ability to install new frontends from the Frontend tab in the Settings section
### Changed
- **Breaking**: AdminAPI changed User field `confirmation_pending` to `is_confirmed`

View file

@ -1,6 +1,7 @@
import request from '@/utils/request'
import { getToken } from '@/utils/auth'
import { baseName } from './utils'
import _ from 'lodash'
export async function deleteInstanceDocument(name, authHost, token) {
return await request({
@ -68,4 +69,24 @@ export async function removeSettings(configs, authHost, token) {
})
}
export async function fetchFrontends(authHost, token) {
return await request({
baseURL: baseName(authHost),
url: `/api/pleroma/admin/frontends`,
method: 'get',
headers: authHeaders(token)
})
}
export async function installFrontend(data, authHost, token) {
const filteredData = _.pickBy(data)
return await request({
baseURL: baseName(authHost),
url: `/api/pleroma/admin/frontends/install`,
method: 'post',
headers: authHeaders(token),
data: filteredData
})
}
const authHeaders = (token) => token ? { 'Authorization': `Bearer ${getToken()}` } : {}

View file

@ -416,6 +416,7 @@ export default {
moderationLog: 'Moderation Log'
},
settings: {
submit: 'Submit',
settings: 'Settings',
instance: 'Instance',
upload: 'Upload',
@ -460,7 +461,27 @@ export default {
uploadImage: 'Upload image',
remove: 'Remove',
instancePanel: 'Instance Panel Document',
termsOfServices: 'Terms of Service'
termsOfServices: 'Terms of Service',
availableFrontends: 'Available Frontends',
installFrontends: 'This is the list of available frontends. You can switch to one of the listed frontends or specify all the required options and install another frontend',
install: 'Install',
installed: 'Installed',
name: 'Name',
git: 'Git',
installAnotherFrontend: 'Install another frontend',
addKeyValuePair: 'Add another `key - value` pair to this icon',
addIconConfig: 'Add another icon configuration',
setLimits: 'Set different limits for unauthenticated and authenticated users',
unauthenticatedUsers: 'Unauthenticated users',
authenticatedUsers: 'Authenticated users',
setLimitsForAll: 'Set limit for all users',
ref: 'Ref',
file: 'File',
buildUrl: 'Build URL',
buildDir: 'Build Directory',
frontendSuccess: 'Frontend installed successfully!',
frontendStartedInstallation: 'Installation started',
inProcess: 'In process'
},
invites: {
inviteTokens: 'Invite tokens',
@ -546,6 +567,5 @@ export default {
emptyPack: 'This emoji pack is empty',
emojiWarning: 'Pack names cannot include any of the following characters: # / < > & +',
image: 'Image'
}
}

View file

@ -1,8 +1,10 @@
import {
deleteInstanceDocument,
fetchDescription,
fetchFrontends,
fetchSettings,
getInstanceDocument,
installFrontend,
removeSettings,
updateInstanceDocument,
updateSettings } from '@/api/settings'
@ -13,6 +15,7 @@ const settings = {
state: {
activeTab: 'instance',
configDisabled: true,
frontends: [],
db: {},
description: [],
instancePanel: '',
@ -41,6 +44,9 @@ const settings = {
SET_DESCRIPTION: (state, data) => {
state.description = data
},
SET_FRONTENDS: (state, data) => {
state.frontends = data
},
SET_LOADING: (state, status) => {
state.loading = status
},
@ -86,6 +92,10 @@ const settings = {
}
},
actions: {
async FetchFrontends({ commit, getters }) {
const { data } = await fetchFrontends(getters.authHost, getters.token)
commit('SET_FRONTENDS', data)
},
async FetchInstanceDocument({ commit, getters }, name) {
const { data } = await getInstanceDocument(name, getters.authHost, getters.token)
if (name === 'instance-panel') {
@ -112,6 +122,10 @@ const settings = {
commit('TOGGLE_TABS', false)
commit('SET_LOADING', false)
},
async InstallFrontend({ commit, getters }, { name, ref, file, buildUrl, buildDir }) {
const { data } = await installFrontend({ name, ref, file, build_url: buildUrl, build_dir: buildDir }, getters.authHost, getters.token)
commit('SET_FRONTENDS', data)
},
async RemoveInstanceDocument({ dispatch, getters }, name) {
await deleteInstanceDocument(name, getters.authHost, getters.token)
await dispatch('FetchInstanceDocument', name)

View file

@ -8,7 +8,7 @@
<setting :setting-group="user" :data="userData"/>
</el-form>
<div class="submit-button-container">
<el-button class="submit-button" type="primary" @click="onSubmit">Submit</el-button>
<el-button class="submit-button" type="primary" @click="onSubmit">{{ $t('settings.submit') }}</el-button>
</div>
</div>
</template>

View file

@ -16,7 +16,7 @@
<setting :setting-group="oauth2" :data="oauth2Data"/>
</el-form>
<div class="submit-button-container">
<el-button class="submit-button" type="primary" @click="onSubmit">Submit</el-button>
<el-button class="submit-button" type="primary" @click="onSubmit">{{ $t('settings.submit') }}</el-button>
</div>
</div>
</template>

View file

@ -8,7 +8,7 @@
<setting :setting-group="kocaptcha" :data="kocaptchaData"/>
</el-form>
<div class="submit-button-container">
<el-button class="submit-button" type="primary" @click="onSubmit">Submit</el-button>
<el-button class="submit-button" type="primary" @click="onSubmit">{{ $t('settings.submit') }}</el-button>
</div>
</div>
</template>

View file

@ -4,7 +4,7 @@
<setting :setting-group="esshd" :data="esshdData"/>
</el-form>
<div class="submit-button-container">
<el-button class="submit-button" type="primary" @click="onSubmit">Submit</el-button>
<el-button class="submit-button" type="primary" @click="onSubmit">{{ $t('settings.submit') }}</el-button>
</div>
</div>
</template>

View file

@ -1,5 +1,7 @@
<template>
<div v-if="!loading" :class="isSidebarOpen" class="form-container">
<frontends-table />
<el-divider v-if="frontend" class="divider thick-line"/>
<el-form :model="frontendData" :label-position="labelPosition" :label-width="labelWidth">
<setting :setting-group="frontend" :data="frontendData"/>
</el-form>
@ -32,7 +34,7 @@
<setting :setting-group="preload" :data="preloadData"/>
</el-form>
<div class="submit-button-container">
<el-button class="submit-button" type="primary" @click="onSubmit">Submit</el-button>
<el-button class="submit-button" type="primary" @click="onSubmit">{{ $t('settings.submit') }}</el-button>
</div>
</div>
</template>
@ -41,11 +43,12 @@
import { mapGetters } from 'vuex'
import i18n from '@/lang'
import Setting from './Setting'
import FrontendsTable from './inputComponents/FrontendsTable'
import _ from 'lodash'
export default {
name: 'Frontend',
components: { Setting },
components: { FrontendsTable, Setting },
computed: {
...mapGetters([
'settings'
@ -80,6 +83,9 @@ export default {
frontendsData() {
return _.get(this.settings.settings, [':pleroma', ':frontends']) || {}
},
isDesktop() {
return this.$store.state.app.device === 'desktop'
},
isMobile() {
return this.$store.state.app.device === 'mobile'
},

View file

@ -4,7 +4,7 @@
<setting :setting-group="gopher" :data="gopherData"/>
</el-form>
<div class="submit-button-container">
<el-button class="submit-button" type="primary" @click="onSubmit">Submit</el-button>
<el-button class="submit-button" type="primary" @click="onSubmit">{{ $t('settings.submit') }}</el-button>
</div>
</div>
</template>

View file

@ -16,7 +16,7 @@
<setting :setting-group="webCacheTtl" :data="webCacheTtlData"/>
</el-form>
<div class="submit-button-container">
<el-button class="submit-button" type="primary" @click="onSubmit">Submit</el-button>
<el-button class="submit-button" type="primary" @click="onSubmit">{{ $t('settings.submit') }}</el-button>
</div>
</div>
</template>

View file

@ -45,7 +45,7 @@
<setting :setting-group="streamer" :data="streamerData"/>
</el-form>
<div class="submit-button-container">
<el-button class="submit-button" type="primary" @click="onSubmit">Submit</el-button>
<el-button class="submit-button" type="primary" @click="onSubmit">{{ $t('settings.submit') }}</el-button>
</div>
</div>
</template>

View file

@ -24,7 +24,7 @@
<setting :setting-group="hackneyPools" :data="hackneyPoolsData"/>
</el-form>
<div class="submit-button-container">
<el-button class="submit-button" type="primary" @click="onSubmit">Submit</el-button>
<el-button class="submit-button" type="primary" @click="onSubmit">{{ $t('settings.submit') }}</el-button>
</div>
</div>
</template>

View file

@ -4,7 +4,7 @@
<setting :setting-group="linkFormatter" :data="linkFormatterData"/>
</el-form>
<div class="submit-button-container">
<el-button class="submit-button" type="primary" @click="onSubmit">Submit</el-button>
<el-button class="submit-button" type="primary" @click="onSubmit">{{ $t('settings.submit') }}</el-button>
</div>
</div>
</template>

View file

@ -16,7 +16,7 @@
<setting :setting-group="quack" :data="quackData"/>
</el-form>
<div class="submit-button-container">
<el-button class="submit-button" type="primary" @click="onSubmit">Submit</el-button>
<el-button class="submit-button" type="primary" @click="onSubmit">{{ $t('settings.submit') }}</el-button>
</div>
</div>
</template>

View file

@ -7,7 +7,7 @@
</el-form>
</div>
<div class="submit-button-container">
<el-button class="submit-button" type="primary" @click="onSubmit">Submit</el-button>
<el-button class="submit-button" type="primary" @click="onSubmit">{{ $t('settings.submit') }}</el-button>
</div>
</div>
</template>

View file

@ -20,7 +20,7 @@
<setting :setting-group="newUsersDigestEmail" :data="newUsersDigestEmailData"/>
</el-form>
<div class="submit-button-container">
<el-button class="submit-button" type="primary" @click="onSubmit">Submit</el-button>
<el-button class="submit-button" type="primary" @click="onSubmit">{{ $t('settings.submit') }}</el-button>
</div>
</div>
</template>

View file

@ -16,7 +16,7 @@
<setting :setting-group="scriptInvalidation" :data="scriptInvalidationData"/>
</el-form>
<div class="submit-button-container">
<el-button class="submit-button" type="primary" @click="onSubmit">Submit</el-button>
<el-button class="submit-button" type="primary" @click="onSubmit">{{ $t('settings.submit') }}</el-button>
</div>
</div>
</template>

View file

@ -8,7 +8,7 @@
<setting :setting-group="richMedia" :data="richMediaData"/>
</el-form>
<div class="submit-button-container">
<el-button class="submit-button" type="primary" @click="onSubmit">Submit</el-button>
<el-button class="submit-button" type="primary" @click="onSubmit">{{ $t('settings.submit') }}</el-button>
</div>
</div>
</template>

View file

@ -26,7 +26,7 @@
<setting :setting-group="castAndValidate" :data="castAndValidateData"/>
</el-form>
<div class="submit-button-container">
<el-button class="submit-button" type="primary" @click="onSubmit">Submit</el-button>
<el-button class="submit-button" type="primary" @click="onSubmit">{{ $t('settings.submit') }}</el-button>
</div>
</div>
</template>

View file

@ -4,7 +4,7 @@
<setting :setting-group="rateLimiters" :data="rateLimitersData"/>
</el-form>
<div class="submit-button-container">
<el-button class="submit-button" type="primary" @click="onSubmit">Submit</el-button>
<el-button class="submit-button" type="primary" @click="onSubmit">{{ $t('settings.submit') }}</el-button>
</div>
</div>
</template>

View file

@ -24,7 +24,7 @@
<setting :setting-group="uploadAnonymizeFilename" :data="uploadAnonymizeFilenameData"/>
</el-form>
<div class="submit-button-container">
<el-button class="submit-button" type="primary" @click="onSubmit">Submit</el-button>
<el-button class="submit-button" type="primary" @click="onSubmit">{{ $t('settings.submit') }}</el-button>
</div>
</div>
</template>

View file

@ -4,7 +4,7 @@
<setting :setting-group="vapidDetails" :data="vapidDetailsData"/>
</el-form>
<div class="submit-button-container">
<el-button class="submit-button" type="primary" @click="onSubmit">Submit</el-button>
<el-button class="submit-button" type="primary" @click="onSubmit">{{ $t('settings.submit') }}</el-button>
</div>
</div>
</template>

View file

@ -0,0 +1,64 @@
<template>
<span>
<el-button
v-if="buttonLoading"
:loading="true"
disabled
type="text"
size="small">
{{ $t('settings.inProcess') }}
</el-button>
<el-button
v-else-if="frontend.installed"
disabled
type="text"
size="small">
{{ $t('settings.installed') }}
</el-button>
<el-button
v-else
type="text"
size="small"
@click="installFrontend(frontend)">
{{ $t('settings.install') }}
</el-button>
</span>
</template>
<script>
import i18n from '@/lang'
export default {
name: 'FrontendStatusButton',
props: {
frontend: {
type: Object,
default: function() {
return {}
}
}
},
data() {
return {
buttonLoading: false
}
},
methods: {
async installFrontend({ name }) {
this.buttonLoading = true
try {
await this.$store.dispatch('InstallFrontend', { name })
} catch (e) {
this.buttonLoading = false
return
}
this.buttonLoading = false
this.$message({
message: i18n.t('settings.frontendSuccess'),
type: 'success',
duration: 5 * 1000
})
}
}
}
</script>

View file

@ -0,0 +1,148 @@
<template>
<el-form :label-position="labelPosition" :label-width="labelWidth" class="frontend-container">
<el-form-item class="description-container">
<span class="setting-label">{{ $t('settings.availableFrontends') }}</span>
<span class="expl no-top-margin"><p>{{ $t('settings.installFrontends') }}</p></span>
</el-form-item>
<el-form-item>
<el-table
:data="availableFrontends"
class="frontends-table">
<el-table-column
:label="$t('settings.name')"
prop="name"
width="120"/>
<el-table-column
:label="$t('settings.git')"
prop="git"/>
<el-table-column
:label="$t('settings.installed')"
prop="installed">
<template slot-scope="scope">
<frontend-status-button :frontend="scope.row"/>
</template>
</el-table-column>
</el-table>
<div class="frontends-button-container">
<el-button
:size="isDesktop ? 'medium' : 'mini'"
:icon="frontendInputOpen ? 'el-icon-minus' : 'el-icon-plus'"
circle
@click="toggleFrontendInput"/>
<span class="icons-button-desc">{{ $t('settings.installAnotherFrontend') }}</span>
</div>
<el-form v-if="frontendInputOpen" ref="frontendFormData" :rules="rules" :model="frontendFormData" label-width="130px">
<el-form-item :label="$t('settings.name')" class="frontend-form-input" prop="name">
<el-input v-model="frontendFormData.name"/>
</el-form-item>
<el-form-item :label="$t('settings.ref')" class="frontend-form-input">
<el-input v-model="frontendFormData.ref"/>
</el-form-item>
<el-form-item :label="$t('settings.file')" class="frontend-form-input">
<el-input v-model="frontendFormData.file"/>
</el-form-item>
<el-form-item :label="$t('settings.buildUrl')" class="frontend-form-input">
<el-input v-model="frontendFormData.buildUrl"/>
</el-form-item>
<el-form-item :label="$t('settings.buildDir')" class="frontend-form-input">
<el-input v-model="frontendFormData.buildDir"/>
</el-form-item>
<el-form-item class="install-frontend-button">
<el-button :loading="buttonLoading" type="primary" @click="installNewFrontend">{{ $t('settings.install') }}</el-button>
</el-form-item>
</el-form>
</el-form-item>
</el-form>
</template>
<script>
import i18n from '@/lang'
import { mapGetters } from 'vuex'
import FrontendStatusButton from './FrontendStatusButton'
export default {
name: 'FrontendsTable',
components: { FrontendStatusButton },
data() {
return {
buttonLoading: false,
frontendInputOpen: false,
frontendFormData: {
name: '',
ref: '',
file: '',
buildUrl: '',
buildDir: ''
},
rules: {
name: { required: true, message: 'Please input Name', trigger: 'blur' }
}
}
},
computed: {
...mapGetters([
'settings'
]),
availableFrontends() {
return this.settings.frontends
},
labelPosition() {
return this.isMobile ? 'top' : 'right'
},
labelWidth() {
if (this.isMobile) {
return '120px'
} else if (this.isTablet) {
return '200px'
} else {
return '280px'
}
},
isDesktop() {
return this.$store.state.app.device === 'desktop'
},
isMobile() {
return this.$store.state.app.device === 'mobile'
},
isTablet() {
return this.$store.state.app.device === 'tablet'
}
},
async mounted() {
await this.$store.dispatch('FetchFrontends')
},
methods: {
installNewFrontend() {
this.$refs['frontendFormData'].validate(async(valid) => {
if (valid) {
this.buttonLoading = true
try {
await this.$store.dispatch('InstallFrontend', this.frontendFormData)
} catch (e) {
this.buttonLoading = false
return
}
this.buttonLoading = false
this.$message({
message: i18n.t('settings.frontendSuccess'),
type: 'success',
duration: 5 * 1000
})
this.frontendFormData = {
name: '',
ref: '',
file: '',
buildUrl: '',
buildDir: ''
}
} else {
return false
}
})
},
toggleFrontendInput() {
this.frontendInputOpen = !this.frontendInputOpen
}
}
}
</script>

View file

@ -12,13 +12,13 @@
</div>
<div class="icons-button-container">
<el-button :size="isDesktop ? 'medium' : 'mini'" icon="el-icon-plus" circle @click="addValueToIcons(index)"/>
<span class="icons-button-desc">Add another `key - value` pair to this icon</span>
<span class="icons-button-desc">{{ $t('settings.addKeyValuePair') }}</span>
</div>
<el-divider class="divider"/>
</div>
<div class="icons-button-container">
<el-button :size="isDesktop ? 'medium' : 'mini'" icon="el-icon-plus" circle @click="addIconToIcons"/>
<span class="icons-button-desc">Add another icon configuration</span>
<span class="icons-button-desc">{{ $t('settings.addIconConfig') }}</span>
</div>
</div>
</template>

View file

@ -16,14 +16,14 @@
@input="parseRateLimiter($event, setting.key, 'limit', 'oneLimit', rateLimitAllUsers)"/>
<div class="limit-button-container">
<el-button :size="isDesktop ? 'medium' : 'mini'" icon="el-icon-plus" circle @click="toggleLimits([['', ''], ['', '']], setting.key)"/>
<p class="expl limit-expl">Set different limits for unauthenticated and authenticated users</p>
<p class="expl limit-expl">{{ $t('settings.setLimits') }}</p>
</div>
</div>
<div v-if="rateLimitAuthUsers">
<el-form-item class="rate-limit">
<div class="rate-limit-label-container">
<span class="rate-limit-label">
Unauthenticated users:
{{ $t('settings.unauthenticatedUsers') }}:
</span>
</div>
<div class="rate-limit-content">
@ -49,7 +49,7 @@
<el-form-item class="rate-limit">
<div class="rate-limit-label-container">
<span class="rate-limit-label">
Authenticated users:
{{ $t('settings.authenticatedUsers') }}:
</span>
</div>
<div class="rate-limit-content">
@ -70,7 +70,7 @@
</el-form-item>
<div class="limit-button-container">
<el-button :size="isDesktop ? 'medium' : 'mini'" class="icon-minus-button" icon="el-icon-minus" circle @click="toggleLimits(['', ''], setting.key)"/>
<p class="expl limit-expl">Set limit for all users</p>
<p class="expl limit-expl">{{ $t('settings.setLimitsForAll') }}</p>
</div>
</div>
</div>

View file

@ -75,6 +75,20 @@
.form-container {
margin-bottom: 80px;
}
.frontend-container {
margin-right: 30px;
}
.frontend-form-input {
margin-top: 20px;
}
.frontends-button-container {
width: 100%;
margin-top: 15px;
}
.frontends-table {
width: 100%;
margin-right: 30px;
}
.grouped-settings-header {
margin: 0 0 14px 0;
}
@ -126,6 +140,10 @@
width: 100%;
}
}
.install-frontend-button {
margin-top: 15px;
float: right;
}
.keyword-container {
width: 100%
}
@ -451,6 +469,15 @@
justify-content: space-between;
margin: 0 5px;
}
.frontend-container {
margin: 0 15px 10px 15px;
.description-container {
margin: 0;
}
}
.frontend-form-input {
margin-top: 0;
}
h1 {
font-size: 24px;
}