add Diagnosis view/route

This commit is contained in:
Axolotle 2020-09-25 09:20:41 +02:00
parent 258941646e
commit 6fef804fe1
6 changed files with 251 additions and 10 deletions

View file

@ -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 }
)
}

View file

@ -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)

View file

@ -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"

View file

@ -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' }
]
}
}
]

View file

@ -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;

View 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>