forked from AkkomaGang/admin-fe
Merge branch 'feature/add-prometheus-settings' into 'develop'
Add Pleroma.Web.Endpoint.MetricsExporter settings Closes #173 See merge request pleroma/admin-fe!186
This commit is contained in:
commit
bcf48997fe
9 changed files with 91 additions and 39 deletions
|
@ -13,7 +13,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
|
||||||
- Add Report show page and link Moderation log references to the respective reports
|
- Add Report show page and link Moderation log references to the respective reports
|
||||||
- Add Unconfimed filter for Users table
|
- Add Unconfimed filter for Users table
|
||||||
- Filter users by actor type: Person, Bot or Application
|
- Filter users by actor type: Person, Bot or Application
|
||||||
- Add ability to configure Media Preview Proxy, User Backup and Websocket based federation settings
|
- 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
|
- Mobile and Tablet UI for Single Report show page
|
||||||
### Changed
|
### Changed
|
||||||
|
|
||||||
|
@ -21,6 +21,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
|
||||||
- Move `:restrict_unauthenticated` settings from Authentication tab to Instance tab
|
- Move `:restrict_unauthenticated` settings from Authentication tab to Instance tab
|
||||||
- Replace regular inputs with textareas for setting welcome messages in the Settings section
|
- Replace regular inputs with textareas for setting welcome messages in the Settings section
|
||||||
- Update rendering Moderation Log Messages so that all usernames are links to the pages of the corresponding users in Admin-FE
|
- Update rendering Moderation Log Messages so that all usernames are links to the pages of the corresponding users in Admin-FE
|
||||||
|
- Remove Websocket based federation settings
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
|
|
||||||
|
|
|
@ -57,10 +57,18 @@ export const parseNonTuples = (key, value) => {
|
||||||
// REFACTOR
|
// REFACTOR
|
||||||
export const parseTuples = (tuples, key) => {
|
export const parseTuples = (tuples, key) => {
|
||||||
return tuples.reduce((accum, item) => {
|
return tuples.reduce((accum, item) => {
|
||||||
if (key === ':rate_limit') {
|
if (key === ':rate_limit' ||
|
||||||
accum[item.tuple[0]] = Array.isArray(item.tuple[1])
|
(key === 'Pleroma.Web.Endpoint.MetricsExporter' && item.tuple[0] === ':auth')) {
|
||||||
? item.tuple[1].map(el => el.tuple)
|
const getValue = () => {
|
||||||
: item.tuple[1].tuple
|
if (typeof item.tuple[1] === 'boolean') {
|
||||||
|
return item.tuple[1]
|
||||||
|
} else if (Array.isArray(item.tuple[1])) {
|
||||||
|
return item.tuple[1].map(el => el.tuple)
|
||||||
|
} else {
|
||||||
|
return item.tuple[1].tuple
|
||||||
|
}
|
||||||
|
}
|
||||||
|
accum[item.tuple[0]] = getValue()
|
||||||
} else if (item.tuple[0] === ':mascots') {
|
} else if (item.tuple[0] === ':mascots') {
|
||||||
accum[item.tuple[0]] = item.tuple[1].reduce((acc, mascot) => {
|
accum[item.tuple[0]] = item.tuple[1].reduce((acc, mascot) => {
|
||||||
return [...acc, { [mascot.tuple[0]]: { ...mascot.tuple[1], id: `f${(~~(Math.random() * 1e8)).toString(16)}` }}]
|
return [...acc, { [mascot.tuple[0]]: { ...mascot.tuple[1], id: `f${(~~(Math.random() * 1e8)).toString(16)}` }}]
|
||||||
|
@ -92,6 +100,8 @@ export const parseTuples = (tuples, key) => {
|
||||||
accum[item.tuple[0]] = parseStringOrTupleValue(item.tuple[0], item.tuple[1])
|
accum[item.tuple[0]] = parseStringOrTupleValue(item.tuple[0], item.tuple[1])
|
||||||
} else if (item.tuple[0] === ':args') {
|
} else if (item.tuple[0] === ':args') {
|
||||||
accum[item.tuple[0]] = parseNonTuples(item.tuple[0], item.tuple[1])
|
accum[item.tuple[0]] = parseNonTuples(item.tuple[0], item.tuple[1])
|
||||||
|
} else if (item.tuple[0] === ':ip_whitelist') {
|
||||||
|
accum[item.tuple[0]] = item.tuple[1].map(ip => typeof ip === 'string' ? ip : ip.tuple.join('.'))
|
||||||
} else if (Array.isArray(item.tuple[1]) &&
|
} else if (Array.isArray(item.tuple[1]) &&
|
||||||
(typeof item.tuple[1][0] === 'object' && !Array.isArray(item.tuple[1][0])) && item.tuple[1][0]['tuple']) {
|
(typeof item.tuple[1][0] === 'object' && !Array.isArray(item.tuple[1][0])) && item.tuple[1][0]['tuple']) {
|
||||||
accum[item.tuple[0]] = parseTuples(item.tuple[1], item.tuple[0])
|
accum[item.tuple[0]] = parseTuples(item.tuple[1], item.tuple[0])
|
||||||
|
@ -237,8 +247,9 @@ const wrapValues = (settings, currentState) => {
|
||||||
return { 'tuple': [setting, wrapValues(value, currentState)] }
|
return { 'tuple': [setting, wrapValues(value, currentState)] }
|
||||||
} else if (prependWithСolon(type, value)) {
|
} else if (prependWithСolon(type, value)) {
|
||||||
return { 'tuple': [setting, `:${value}`] }
|
return { 'tuple': [setting, `:${value}`] }
|
||||||
} else if (type.includes('tuple') && (type.includes('string') || type.includes('atom'))) {
|
} else if (type.includes('tuple') &&
|
||||||
return typeof value === 'string'
|
(type.includes('string') || type.includes('atom') || type.includes('boolean'))) {
|
||||||
|
return typeof value === 'string' || typeof value === 'boolean'
|
||||||
? { 'tuple': [setting, value] }
|
? { 'tuple': [setting, value] }
|
||||||
: { 'tuple': [setting, { 'tuple': value }] }
|
: { 'tuple': [setting, { 'tuple': value }] }
|
||||||
} else if (type === 'reversed_tuple') {
|
} else if (type === 'reversed_tuple') {
|
||||||
|
|
|
@ -11,14 +11,10 @@
|
||||||
<el-form :model="httpSecurityData" :label-position="labelPosition" :label-width="labelWidth">
|
<el-form :model="httpSecurityData" :label-position="labelPosition" :label-width="labelWidth">
|
||||||
<setting :setting-group="httpSecurity" :data="httpSecurityData"/>
|
<setting :setting-group="httpSecurity" :data="httpSecurityData"/>
|
||||||
</el-form>
|
</el-form>
|
||||||
<el-divider v-if="httpSecurity" class="divider thick-line"/>
|
<el-divider v-if="webCacheTtl" class="divider thick-line"/>
|
||||||
<el-form :model="webCacheTtlData" :label-position="labelPosition" :label-width="labelWidth">
|
<el-form :model="webCacheTtlData" :label-position="labelPosition" :label-width="labelWidth">
|
||||||
<setting :setting-group="webCacheTtl" :data="webCacheTtlData"/>
|
<setting :setting-group="webCacheTtl" :data="webCacheTtlData"/>
|
||||||
</el-form>
|
</el-form>
|
||||||
<el-divider v-if="fedSockets" class="divider thick-line"/>
|
|
||||||
<el-form :model="fedSocketsData" :label-position="labelPosition" :label-width="labelWidth">
|
|
||||||
<setting :setting-group="fedSockets" :data="fedSocketsData"/>
|
|
||||||
</el-form>
|
|
||||||
<div class="submit-button-container">
|
<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">Submit</el-button>
|
||||||
</div>
|
</div>
|
||||||
|
@ -44,12 +40,6 @@ export default {
|
||||||
corsPlugData() {
|
corsPlugData() {
|
||||||
return _.get(this.settings.settings, [':cors_plug']) || {}
|
return _.get(this.settings.settings, [':cors_plug']) || {}
|
||||||
},
|
},
|
||||||
fedSockets() {
|
|
||||||
return this.settings.description.find(setting => setting.key === ':fed_sockets')
|
|
||||||
},
|
|
||||||
fedSocketsData() {
|
|
||||||
return _.get(this.settings.settings, [':pleroma', ':fed_sockets']) || {}
|
|
||||||
},
|
|
||||||
http() {
|
http() {
|
||||||
return this.settings.description.find(setting => setting.key === ':http')
|
return this.settings.description.find(setting => setting.key === ':http')
|
||||||
},
|
},
|
||||||
|
|
|
@ -113,7 +113,7 @@
|
||||||
<!-- special inputs -->
|
<!-- special inputs -->
|
||||||
<editable-keyword-input v-if="editableKeyword(setting.key, setting.type)" :data="keywordData" :setting-group="settingGroup" :setting="setting" :parents="settingParent"/>
|
<editable-keyword-input v-if="editableKeyword(setting.key, setting.type)" :data="keywordData" :setting-group="settingGroup" :setting="setting" :parents="settingParent"/>
|
||||||
<icons-input v-if="setting.key === ':icons'" :data="iconsData" :setting-group="settingGroup" :setting="setting"/>
|
<icons-input v-if="setting.key === ':icons'" :data="iconsData" :setting-group="settingGroup" :setting="setting"/>
|
||||||
<link-formatter-input v-if="booleanCombinedInput" :data="data" :setting-group="settingGroup" :setting="setting"/>
|
<boolean-combined-input v-if="booleanCombinedInput" :data="data" :setting-group="settingGroup" :setting="setting"/>
|
||||||
<mascots-input v-if="setting.key === ':mascots'" :data="keywordData" :setting-group="settingGroup" :setting="setting"/>
|
<mascots-input v-if="setting.key === ':mascots'" :data="keywordData" :setting-group="settingGroup" :setting="setting"/>
|
||||||
<proxy-url-input v-if="setting.key === ':proxy_url'" :data="data[setting.key]" :setting-group="settingGroup" :setting="setting" :parents="settingParent"/>
|
<proxy-url-input v-if="setting.key === ':proxy_url'" :data="data[setting.key]" :setting-group="settingGroup" :setting="setting" :parents="settingParent"/>
|
||||||
<prune-input v-if="setting.key === ':prune'" :data="data[setting.key]" :setting-group="settingGroup" :setting="setting"/>
|
<prune-input v-if="setting.key === ':prune'" :data="data[setting.key]" :setting-group="settingGroup" :setting="setting"/>
|
||||||
|
@ -141,7 +141,7 @@ import {
|
||||||
EditableKeywordInput,
|
EditableKeywordInput,
|
||||||
IconsInput,
|
IconsInput,
|
||||||
ImageUploadInput,
|
ImageUploadInput,
|
||||||
LinkFormatterInput,
|
BooleanCombinedInput,
|
||||||
MascotsInput,
|
MascotsInput,
|
||||||
ProxyUrlInput,
|
ProxyUrlInput,
|
||||||
PruneInput,
|
PruneInput,
|
||||||
|
@ -160,7 +160,7 @@ export default {
|
||||||
EditableKeywordInput,
|
EditableKeywordInput,
|
||||||
IconsInput,
|
IconsInput,
|
||||||
ImageUploadInput,
|
ImageUploadInput,
|
||||||
LinkFormatterInput,
|
BooleanCombinedInput,
|
||||||
MascotsInput,
|
MascotsInput,
|
||||||
ProxyUrlInput,
|
ProxyUrlInput,
|
||||||
PruneInput,
|
PruneInput,
|
||||||
|
@ -363,6 +363,7 @@ export default {
|
||||||
},
|
},
|
||||||
renderMultipleSelect(type) {
|
renderMultipleSelect(type) {
|
||||||
return !this.reducedSelects && Array.isArray(type) && this.setting.key !== ':backends' && this.setting.key !== ':args' && (
|
return !this.reducedSelects && Array.isArray(type) && this.setting.key !== ':backends' && this.setting.key !== ':args' && (
|
||||||
|
this.setting.key === ':ip_whitelist' ||
|
||||||
type.includes('module') ||
|
type.includes('module') ||
|
||||||
(type.includes('list') && type.includes('string')) ||
|
(type.includes('list') && type.includes('string')) ||
|
||||||
(type.includes('list') && type.includes('atom')) ||
|
(type.includes('list') && type.includes('atom')) ||
|
||||||
|
|
|
@ -2,6 +2,10 @@
|
||||||
<div v-if="!loading" :class="isSidebarOpen" class="form-container">
|
<div v-if="!loading" :class="isSidebarOpen" class="form-container">
|
||||||
<editor-input v-model="termsOfServicesContent" :name="'terms-of-service'" @input="handleEditorUpdate"/>
|
<editor-input v-model="termsOfServicesContent" :name="'terms-of-service'" @input="handleEditorUpdate"/>
|
||||||
<el-divider class="divider thick-line"/>
|
<el-divider class="divider thick-line"/>
|
||||||
|
<el-form :model="prometheusMetricsData" :label-position="labelPosition" :label-width="labelWidth">
|
||||||
|
<setting :setting-group="prometheusMetrics" :data="prometheusMetricsData"/>
|
||||||
|
</el-form>
|
||||||
|
<el-divider v-if="prometheusMetrics" class="divider thick-line"/>
|
||||||
<el-form :model="backupData" :label-position="labelPosition" :label-width="labelWidth">
|
<el-form :model="backupData" :label-position="labelPosition" :label-width="labelWidth">
|
||||||
<setting :setting-group="backup" :data="backupData"/>
|
<setting :setting-group="backup" :data="backupData"/>
|
||||||
</el-form>
|
</el-form>
|
||||||
|
@ -94,6 +98,12 @@ export default {
|
||||||
modulesData() {
|
modulesData() {
|
||||||
return _.get(this.settings.settings, [':pleroma', ':modules']) || {}
|
return _.get(this.settings.settings, [':pleroma', ':modules']) || {}
|
||||||
},
|
},
|
||||||
|
prometheusMetrics() {
|
||||||
|
return this.settings.description.find(setting => setting.key === 'Pleroma.Web.Endpoint.MetricsExporter')
|
||||||
|
},
|
||||||
|
prometheusMetricsData() {
|
||||||
|
return _.get(this.settings.settings, [':prometheus', 'Pleroma.Web.Endpoint.MetricsExporter']) || {}
|
||||||
|
},
|
||||||
remoteIp() {
|
remoteIp() {
|
||||||
return this.settings.description.find(setting => setting.key === 'Pleroma.Web.Plugs.RemoteIp')
|
return this.settings.description.find(setting => setting.key === 'Pleroma.Web.Plugs.RemoteIp')
|
||||||
},
|
},
|
||||||
|
|
|
@ -1,34 +1,46 @@
|
||||||
<template>
|
<template>
|
||||||
<div>
|
<div>
|
||||||
<div v-if="setting.type.includes('string')" :data-search="setting.key || setting.group">
|
<div v-if="setting.type.includes('string')" :data-search="setting.key || setting.group">
|
||||||
<el-switch :value="autoLinkerBooleanValue" @change="processTwoTypeValue($event, setting.key)"/>
|
<el-switch :value="booleanValue" @change="processTwoTypeValue($event, setting.key)"/>
|
||||||
<el-input
|
<el-input
|
||||||
v-if="autoLinkerBooleanValue"
|
v-if="booleanValue"
|
||||||
:value="autoLinkerStringValue"
|
:value="stringValue"
|
||||||
@input="processTwoTypeValue($event, setting.key)"/>
|
@input="processTwoTypeValue($event, setting.key)"/>
|
||||||
</div>
|
</div>
|
||||||
<div v-if="setting.type.includes('integer')" :data-search="setting.key || setting.group">
|
<div v-if="setting.type.includes('integer')" :data-search="setting.key || setting.group">
|
||||||
<el-switch :value="autoLinkerBooleanValue" @change="processTwoTypeValue($event, setting.key)"/>
|
<el-switch :value="booleanValue" @change="processTwoTypeValue($event, setting.key)"/>
|
||||||
<el-input-number
|
<el-input-number
|
||||||
v-if="autoLinkerBooleanValue"
|
v-if="booleanValue"
|
||||||
:value="autoLinkerIntegerValue"
|
:value="integerValue"
|
||||||
@input="processTwoTypeValue($event, setting.key)"/>
|
@input="processTwoTypeValue($event, setting.key)"/>
|
||||||
</div>
|
</div>
|
||||||
<div v-if="setting.type.includes('atom')" :data-search="setting.key || setting.group">
|
<div v-if="setting.type.includes('atom')" :data-search="setting.key || setting.group">
|
||||||
<el-switch :value="autoLinkerBooleanValue" @change="processTwoTypeValue($event, setting.key)"/>
|
<el-switch :value="booleanValue" @change="processTwoTypeValue($event, setting.key)"/>
|
||||||
<el-input
|
<el-input
|
||||||
v-if="autoLinkerBooleanValue"
|
v-if="booleanValue"
|
||||||
:value="autoLinkerAtomValue"
|
:value="atomValue"
|
||||||
@input="processTwoTypeValue($event, setting.key)">
|
@input="processTwoTypeValue($event, setting.key)">
|
||||||
<template slot="prepend">:</template>
|
<template slot="prepend">:</template>
|
||||||
</el-input>
|
</el-input>
|
||||||
</div>
|
</div>
|
||||||
|
<div v-if="setting.type.includes('tuple')" :data-search="setting.key || setting.group">
|
||||||
|
<el-switch :value="booleanValue" @change="processTupleTwoTypeValue($event, setting.key)"/>
|
||||||
|
<div v-if="booleanValue" class="tuple-input-container">
|
||||||
|
<el-input
|
||||||
|
v-for="(item, index) in tupleValue"
|
||||||
|
:value="item"
|
||||||
|
:key="index"
|
||||||
|
:placeholder="getPlaceholder[index]"
|
||||||
|
class="tuple-input"
|
||||||
|
@input="processTupleTwoTypeValue($event, setting.key, index)"/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
export default {
|
export default {
|
||||||
name: 'LinkFormatterInput',
|
name: 'BooleanCombinedInput',
|
||||||
props: {
|
props: {
|
||||||
data: {
|
data: {
|
||||||
type: [Object, Array],
|
type: [Object, Array],
|
||||||
|
@ -50,24 +62,42 @@ export default {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
autoLinkerAtomValue() {
|
atomValue() {
|
||||||
return this.data[this.setting.key] &&
|
return this.data[this.setting.key] &&
|
||||||
this.data[this.setting.key][0] === ':' ? this.data[this.setting.key].substr(1) : this.data[this.setting.key]
|
this.data[this.setting.key][0] === ':' ? this.data[this.setting.key].substr(1) : this.data[this.setting.key]
|
||||||
},
|
},
|
||||||
autoLinkerBooleanValue() {
|
booleanValue() {
|
||||||
const value = this.data[this.setting.key]
|
const value = this.data[this.setting.key]
|
||||||
return typeof value === 'string' || typeof value === 'number'
|
return typeof value !== 'boolean'
|
||||||
},
|
},
|
||||||
autoLinkerIntegerValue() {
|
getPlaceholder() {
|
||||||
|
return { 0: ':basic', 1: 'username', 2: 'password' }
|
||||||
|
},
|
||||||
|
integerValue() {
|
||||||
const value = this.data[this.setting.key]
|
const value = this.data[this.setting.key]
|
||||||
return value || 0
|
return value || 0
|
||||||
},
|
},
|
||||||
autoLinkerStringValue() {
|
stringValue() {
|
||||||
const value = this.data[this.setting.key]
|
const value = this.data[this.setting.key]
|
||||||
return value || ''
|
return value || ''
|
||||||
|
},
|
||||||
|
tupleValue() {
|
||||||
|
const value = this.data[this.setting.key]
|
||||||
|
return value || ['', '', '']
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
processTupleTwoTypeValue(value, input, _index) {
|
||||||
|
if (value === false) {
|
||||||
|
this.updateSetting(value, this.settingGroup.group, this.settingGroup.key, input, this.setting.type)
|
||||||
|
} else if (value === true) {
|
||||||
|
this.updateSetting(['', '', ''], this.settingGroup.group, this.settingGroup.key, input, this.setting.type)
|
||||||
|
} else {
|
||||||
|
const data = [...this.tupleValue]
|
||||||
|
data[_index] = value
|
||||||
|
this.updateSetting(data, this.settingGroup.group, this.settingGroup.key, input, this.setting.type)
|
||||||
|
}
|
||||||
|
},
|
||||||
processTwoTypeValue(value, input) {
|
processTwoTypeValue(value, input) {
|
||||||
if (value === true) {
|
if (value === true) {
|
||||||
const data = input === ':truncate' ? 0 : ''
|
const data = input === ':truncate' ? 0 : ''
|
|
@ -1,8 +1,8 @@
|
||||||
|
export { default as BooleanCombinedInput } from './BooleanCombinedInput'
|
||||||
export { default as EditableKeywordInput } from './EditableKeywordInput'
|
export { default as EditableKeywordInput } from './EditableKeywordInput'
|
||||||
export { default as EditorInput } from './EditorInput'
|
export { default as EditorInput } from './EditorInput'
|
||||||
export { default as IconsInput } from './IconsInput'
|
export { default as IconsInput } from './IconsInput'
|
||||||
export { default as ImageUploadInput } from './ImageUploadInput'
|
export { default as ImageUploadInput } from './ImageUploadInput'
|
||||||
export { default as LinkFormatterInput } from './LinkFormatterInput'
|
|
||||||
export { default as MascotsInput } from './MascotsInput'
|
export { default as MascotsInput } from './MascotsInput'
|
||||||
export { default as ProxyUrlInput } from './ProxyUrlInput'
|
export { default as ProxyUrlInput } from './ProxyUrlInput'
|
||||||
export { default as PruneInput } from './PruneInput'
|
export { default as PruneInput } from './PruneInput'
|
||||||
|
|
|
@ -26,7 +26,7 @@ export const tabs = description => {
|
||||||
},
|
},
|
||||||
'http': {
|
'http': {
|
||||||
label: 'settings.http',
|
label: 'settings.http',
|
||||||
settings: [':cors_plug', ':http', ':fed_sockets', ':http_security', ':web_cache_ttl']
|
settings: [':cors_plug', ':http', ':http_security', ':web_cache_ttl']
|
||||||
},
|
},
|
||||||
'instance': {
|
'instance': {
|
||||||
label: 'settings.instance',
|
label: 'settings.instance',
|
||||||
|
@ -78,7 +78,7 @@ export const tabs = description => {
|
||||||
},
|
},
|
||||||
'other': {
|
'other': {
|
||||||
label: 'settings.other',
|
label: 'settings.other',
|
||||||
settings: [':mime', 'Pleroma.User.Backup', 'Pleroma.Web.Plugs.RemoteIp', ':modules', 'Pleroma.Web.ApiSpec.CastAndValidate', ':terms_of_services']
|
settings: [':mime', 'Pleroma.User.Backup', 'Pleroma.Web.Plugs.RemoteIp', 'Pleroma.Web.Endpoint.MetricsExporter', ':modules', 'Pleroma.Web.ApiSpec.CastAndValidate', ':terms_of_services']
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -352,6 +352,15 @@
|
||||||
line-height: 20px;
|
line-height: 20px;
|
||||||
margin-right: 15px
|
margin-right: 15px
|
||||||
}
|
}
|
||||||
|
.tuple-input {
|
||||||
|
margin-right: 15px;
|
||||||
|
}
|
||||||
|
.tuple-input:last-child {
|
||||||
|
margin-right: 0;
|
||||||
|
}
|
||||||
|
.tuple-input-container {
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
.upload-container {
|
.upload-container {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: baseline;
|
align-items: baseline;
|
||||||
|
|
Loading…
Reference in a new issue