mirror of
https://github.com/YunoHost/yunohost-admin.git
synced 2024-09-03 20:06:15 +02:00
add Diagnosis view/route
This commit is contained in:
parent
258941646e
commit
6fef804fe1
6 changed files with 251 additions and 10 deletions
|
@ -3,10 +3,17 @@ import format from 'date-fns/format'
|
|||
|
||||
import { dateFnsLocale as locale } from '@/i18n/helpers'
|
||||
|
||||
export function distanceToNow (dateStr, addSuffix = true) {
|
||||
return formatDistanceToNow(new Date(dateStr), { addSuffix, locale })
|
||||
export function distanceToNow (date, addSuffix = true, isTimestamp = false) {
|
||||
return formatDistanceToNow(
|
||||
new Date(isTimestamp ? date * 1000 : date),
|
||||
{ addSuffix, locale }
|
||||
)
|
||||
}
|
||||
|
||||
export function readableDate (dateStr) {
|
||||
return format(new Date(dateStr), 'PPPpp', { locale })
|
||||
export function readableDate (date, isTimestamp = false) {
|
||||
return format(
|
||||
new Date(isTimestamp ? date * 1000 : date),
|
||||
'PPPpp',
|
||||
{ locale }
|
||||
)
|
||||
}
|
||||
|
|
|
@ -17,7 +17,11 @@ import store from '@/store'
|
|||
export function objectToParams (object, { addLocale = false } = {}) {
|
||||
const urlParams = new URLSearchParams()
|
||||
for (const [key, value] of Object.entries(object)) {
|
||||
urlParams.append(key, value)
|
||||
if (Array.isArray(value)) {
|
||||
value.forEach(v => urlParams.append(key, v))
|
||||
} else {
|
||||
urlParams.append(key, value)
|
||||
}
|
||||
}
|
||||
if (addLocale) {
|
||||
urlParams.append('locale', store.getters.locale)
|
||||
|
|
|
@ -167,7 +167,7 @@
|
|||
"hook_data_mail_desc": "Raw emails stored on the server",
|
||||
"id": "ID",
|
||||
"ignore": "Ignore",
|
||||
"ignored": "%s ignored",
|
||||
"ignored": "{count} ignored",
|
||||
"inactive": "Inactive",
|
||||
"infos": "Info",
|
||||
"install": "Install",
|
||||
|
@ -178,7 +178,7 @@
|
|||
"internal_exception": "<strong>Yunohost encountered an internal error:/</strong><br><em>Really sorry about that.<br>You should look for help on <a href=\"https://forum.yunohost.org/\">the forum</a> or <a href=\"https://chat.yunohost.org/\">the chat</a> to fix the situation, or report the bug on <a href=\"https://github.com/YunoHost/issues\">the bugtracker</a>.</em><br>The following information might be useful for the person helping you:<h3>Action</h3><pre>%s%s</pre><h3>Traceback</h3><pre>%s</pre>",
|
||||
"ipv4": "IPv4",
|
||||
"ipv6": "IPv6",
|
||||
"issues": "%s issues",
|
||||
"issues": "{count} issues",
|
||||
"label": "Label",
|
||||
"label_for_manifestname": "Label for %s",
|
||||
"last_ran": "Last time ran:",
|
||||
|
@ -361,7 +361,7 @@
|
|||
"users_new": "New user",
|
||||
"users_no": "No users.",
|
||||
"version": "Version",
|
||||
"warnings": "%s warnings",
|
||||
"warnings": "{count} warnings",
|
||||
"warning_first_user": "You probably need to <a href='#/users/create' class='alert-link'>create a user</a> first.",
|
||||
"words": {
|
||||
"collapse": "Collapse"
|
||||
|
|
|
@ -263,6 +263,20 @@ const routes = [
|
|||
{ name: 'tool-power', trad: 'tools_shutdown_reboot' }
|
||||
]
|
||||
}
|
||||
},
|
||||
|
||||
/* ────────────╮
|
||||
│ DIAGNOSIS │
|
||||
╰──────────── */
|
||||
{
|
||||
name: 'diagnosis',
|
||||
path: '/diagnosis',
|
||||
component: () => import(/* webpackChunkName: "views/diagnosis" */ '@/views/diagnosis/Diagnosis'),
|
||||
meta: {
|
||||
breadcrumb: [
|
||||
{ name: 'diagnosis', trad: 'diagnosis' }
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
|
|
|
@ -80,10 +80,10 @@ body {
|
|||
}
|
||||
|
||||
// collapse icon
|
||||
.not-collapsed .icon {
|
||||
.not-collapsed > .icon {
|
||||
transform: rotate(-90deg);
|
||||
}
|
||||
.collapsed .icon {
|
||||
.collapsed > .icon {
|
||||
transform: rotate(90deg);
|
||||
position: relative;
|
||||
top: 2px;
|
||||
|
|
216
app/src/views/diagnosis/Diagnosis.vue
Normal file
216
app/src/views/diagnosis/Diagnosis.vue
Normal file
|
@ -0,0 +1,216 @@
|
|||
<template>
|
||||
<div class="diagnosis">
|
||||
<div class="actions">
|
||||
<div class="buttons ml-auto">
|
||||
<b-button @click="shareLogs">
|
||||
<icon iname="cloud-upload" /> {{ $t('logs_share_with_yunopaste') }}
|
||||
</b-button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<b-alert variant="info" show>
|
||||
{{ $t(reports ? 'diagnosis_explanation' : 'diagnosis_first_run') }}
|
||||
<b-button
|
||||
v-if="reports === null" @click="runFullDiagnosis"
|
||||
class="d-block mt-2" variant="info"
|
||||
>
|
||||
<icon iname="stethoscope" /> {{ $t('run_first_diagnosis') }}
|
||||
</b-button>
|
||||
</b-alert>
|
||||
|
||||
<b-alert
|
||||
class="mb-5" variant="warning" show
|
||||
v-t="'diagnosis_experimental_disclaimer'"
|
||||
/>
|
||||
|
||||
<!-- REPORT CARD -->
|
||||
<b-card no-body v-for="({ id, description, noIssues, errors, warnings, ignoreds, timestamp, items }, r) in reports" :key="id">
|
||||
<!-- REPORT HEADER -->
|
||||
<b-card-header class="d-flex align-items-md-center flex-column flex-md-row">
|
||||
<div class="d-flex align-items-center">
|
||||
<h2>{{ description }}</h2>
|
||||
|
||||
<b-badge
|
||||
v-if="noIssues" pill variant="success"
|
||||
v-t="'everything_good'"
|
||||
/>
|
||||
<b-badge
|
||||
v-if="errors" variant="danger" pill
|
||||
v-t="{ path: 'issues', args: { count: errors } }"
|
||||
/>
|
||||
<b-badge v-if="warnings" variant="warning" v-t="{ path: 'warnings', args: { count: warnings } }" />
|
||||
<b-badge v-if="ignoreds" v-t="{ path: 'ignored', args: { count: ignoreds } }" />
|
||||
</div>
|
||||
|
||||
<div class="d-flex ml-md-auto mt-2 mt-md-0">
|
||||
<b-button size="sm" :variant="items ? 'info' : 'success'" @click="reRunDiagnosis(id)">
|
||||
<icon iname="refresh" /> {{ $t('rerun_diagnosis') }}
|
||||
</b-button>
|
||||
|
||||
<b-button
|
||||
size="sm" variant="outline-secondary" class="ml-auto ml-md-2"
|
||||
v-b-toggle="'collapse-' + id"
|
||||
>
|
||||
<icon iname="chevron-right" /><span class="sr-only">{{ $t('words.collapse') }}</span>
|
||||
</b-button>
|
||||
</div>
|
||||
</b-card-header>
|
||||
|
||||
<!-- REPORT BODY -->
|
||||
<b-collapse :id="'collapse-' + id" :visible="!noIssues">
|
||||
<p class="last-time-run">
|
||||
{{ $t('last_ran') }} {{ timestamp | distanceToNow(true, true) }}
|
||||
</p>
|
||||
|
||||
<b-list-group flush>
|
||||
<!-- REPORT ITEM -->
|
||||
<b-list-group-item
|
||||
v-for="({ status, icon, summary, ignored, issue, details, filterArgs, meta }, i) in items"
|
||||
:key="i" :variant="status"
|
||||
>
|
||||
<div class="item-button d-flex align-items-center">
|
||||
<icon :iname="icon" class="mr-1" /> <p class="mb-0 mr-2" v-html="summary" />
|
||||
|
||||
<div class="d-flex flex-column flex-lg-row ml-auto">
|
||||
<b-button
|
||||
v-if="ignored" size="sm"
|
||||
@click="toggleIgnoreIssue(false, filterArgs, r, i)"
|
||||
>
|
||||
<icon iname="bell" /> <span v-t="'unignore'" />
|
||||
</b-button>
|
||||
<b-button
|
||||
v-else-if="issue"
|
||||
variant="warning" size="sm" @click="toggleIgnoreIssue(true, filterArgs, r, i)"
|
||||
>
|
||||
<icon iname="bell-slash" /> <span v-t="'ignore'" />
|
||||
</b-button>
|
||||
<b-button
|
||||
v-if="details"
|
||||
size="sm" variant="light" class="ml-lg-2 mt-2 mt-lg-0"
|
||||
v-b-toggle="'collapse-' + id + '-item-' + i"
|
||||
>
|
||||
<icon iname="level-down" /> <span v-t="'details'" />
|
||||
</b-button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<b-collapse v-if="details" :id="'collapse-' + id + '-item-' + i">
|
||||
<ul class="mt-2 pl-4">
|
||||
<li v-for="(detail, index) in details" :key="index" v-html="detail" />
|
||||
</ul>
|
||||
</b-collapse>
|
||||
</b-list-group-item>
|
||||
</b-list-group>
|
||||
</b-collapse>
|
||||
</b-card>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import api from '@/helpers/api'
|
||||
import { distanceToNow } from '@/filters/date'
|
||||
|
||||
export default {
|
||||
name: 'Diagnosis',
|
||||
|
||||
data () {
|
||||
return {
|
||||
reports: undefined
|
||||
}
|
||||
},
|
||||
|
||||
filters: {
|
||||
distanceToNow
|
||||
},
|
||||
|
||||
methods: {
|
||||
fetchData () {
|
||||
api.get('diagnosis/show?full').then(({ reports }) => {
|
||||
if (!Array.isArray(reports)) {
|
||||
this.reports = null
|
||||
return
|
||||
}
|
||||
|
||||
for (var report of reports) {
|
||||
report.warnings = 0
|
||||
report.errors = 0
|
||||
report.ignoreds = 0
|
||||
|
||||
for (var item of report.items) {
|
||||
let issue = false
|
||||
let icon = ''
|
||||
const status = item.status = item.status.toLowerCase()
|
||||
|
||||
if (status === 'success') {
|
||||
icon = 'check-circle'
|
||||
} else if (status === 'info') {
|
||||
icon = 'info-circle'
|
||||
} else if (item.ignored) {
|
||||
icon = status !== 'error' ? status : 'times'
|
||||
item.status = 'ignored'
|
||||
report.ignoreds++
|
||||
} else if (status === 'warning') {
|
||||
icon = status
|
||||
issue = true
|
||||
report.warnings++
|
||||
} else if (status === 'error') {
|
||||
item.status = 'danger'
|
||||
icon = 'times'
|
||||
issue = true
|
||||
report.errors++
|
||||
}
|
||||
|
||||
item.issue = issue
|
||||
item.icon = icon
|
||||
item.filterArgs = Object.entries(item.meta).reduce((filterArgs, entries) => {
|
||||
filterArgs.push(entries.join('='))
|
||||
return filterArgs
|
||||
}, [report.id])
|
||||
}
|
||||
report.noIssues = report.warnings + report.errors === 0
|
||||
}
|
||||
this.reports = reports
|
||||
})
|
||||
},
|
||||
|
||||
runFullDiagnosis () {
|
||||
api.post('diagnosis/run').then(this.fetchData)
|
||||
},
|
||||
|
||||
reRunDiagnosis (id) {
|
||||
api.post('diagnosis/run?force', { categories: [id] }).then(this.fetchData)
|
||||
},
|
||||
|
||||
toggleIgnoreIssue (ignore, filterArgs, reportIndex, itemIndex) {
|
||||
const key = (ignore ? 'add' : 'remove') + '_filter'
|
||||
api.post('diagnosis/ignore', { [key]: filterArgs }).then(this.fetchData)
|
||||
},
|
||||
|
||||
shareLogs () {
|
||||
api.get('diagnosis/show?share').then(({ url }) => {
|
||||
window.open(url, '_blank')
|
||||
})
|
||||
}
|
||||
},
|
||||
|
||||
created () {
|
||||
api.post('diagnosis/run?except-if-never-ran-yet').then(this.fetchData)
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.badge {
|
||||
margin-left: .5rem
|
||||
}
|
||||
|
||||
p.last-time-run {
|
||||
margin: .75rem 1rem;
|
||||
}
|
||||
|
||||
.item-button {
|
||||
button {
|
||||
min-width: 6rem;
|
||||
}
|
||||
}
|
||||
</style>
|
Loading…
Add table
Reference in a new issue