Web interface: dates and times in the user's Nextcloud locale
This commit is contained in:
@@ -14,6 +14,8 @@ instead carried by the app description, the git tag, the release, and this file.
|
|||||||
which location, setup and size you used, and how it went.
|
which location, setup and size you used, and how it went.
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
|
- Dates and times in the web interface follow the locale in the user's Nextcloud settings instead
|
||||||
|
of the browser's language, so a 24-hour locale shows 16:00 rather than 4:00 PM.
|
||||||
- The list of backup runs (web interface and `occ backup:status`) shows the runs of the last 7
|
- The list of backup runs (web interface and `occ backup:status`) shows the runs of the last 7
|
||||||
days, those still running, and at least the 5 newest; a failed run stays visible for that week.
|
days, those still running, and at least the 5 newest; a failed run stays visible for that week.
|
||||||
`occ backup:status --all` and "Show older runs" in the web interface list all of them.
|
`occ backup:status --all` and "Show older runs" in the web interface list all of them.
|
||||||
|
|||||||
@@ -22,8 +22,8 @@
|
|||||||
<p class="ngb-muted">{{ t('ng_backup', 'Removed restore points can be brought back until they are deleted permanently.') }}</p>
|
<p class="ngb-muted">{{ t('ng_backup', 'Removed restore points can be brought back until they are deleted permanently.') }}</p>
|
||||||
<ul>
|
<ul>
|
||||||
<li v-for="item in trash[target.id]" :key="item.id">
|
<li v-for="item in trash[target.id]" :key="item.id">
|
||||||
{{ item.time ? new Date(item.time).toLocaleString() : item.id }}{{ item.label ? ' – ' + item.label : '' }}
|
{{ item.time ? formatDateTime(item.time) : item.id }}{{ item.label ? ' – ' + item.label : '' }}
|
||||||
<span class="ngb-muted">{{ t('ng_backup', 'removed by {by}, deleted after {date}', { by: item.by, date: new Date(item.deleteAfter * 1000).toLocaleDateString() }) }}</span>
|
<span class="ngb-muted">{{ t('ng_backup', 'removed by {by}, deleted after {date}', { by: item.by, date: formatDate(item.deleteAfter) }) }}</span>
|
||||||
<NcButton variant="tertiary" @click="untrash(target, item)">{{ t('ng_backup', 'Restore') }}</NcButton>
|
<NcButton variant="tertiary" @click="untrash(target, item)">{{ t('ng_backup', 'Restore') }}</NcButton>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
@@ -46,10 +46,10 @@
|
|||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
<tr v-for="run in status.runs" :key="run.id" :title="run.error || ''">
|
<tr v-for="run in status.runs" :key="run.id" :title="run.error || ''">
|
||||||
<td>{{ new Date(run.startedAt * 1000).toLocaleString() }}</td>
|
<td>{{ formatDateTime(run.startedAt) }}</td>
|
||||||
<td>{{ targetName(run.targetId) }}</td>
|
<td>{{ targetName(run.targetId) }}</td>
|
||||||
<td>{{ run.kind }}</td>
|
<td>{{ run.kind }}</td>
|
||||||
<td>{{ run.snapshot ? new Date(run.snapshot.createdAt * 1000).toLocaleString() + (run.snapshot.label ? ' – ' + run.snapshot.label : '') : '' }}</td>
|
<td>{{ run.snapshot ? formatDateTime(run.snapshot.createdAt) + (run.snapshot.label ? ' – ' + run.snapshot.label : '') : '' }}</td>
|
||||||
<td :class="'ngb-' + run.status">{{ run.status }}{{ run.status === 'running' ? ' (' + run.phase + percent(run) + ')' : '' }}{{ run.error ? ': ' + run.error : '' }}</td>
|
<td :class="'ngb-' + run.status">{{ run.status }}{{ run.status === 'running' ? ' (' + run.phase + percent(run) + ')' : '' }}{{ run.error ? ': ' + run.error : '' }}</td>
|
||||||
<td>{{ run.stats?.files?.files ?? run.stats?.restored ?? '' }}</td>
|
<td>{{ run.stats?.files?.files ?? run.stats?.restored ?? '' }}</td>
|
||||||
<td>{{ run.stats?.files ? formatSize(run.stats.files.bytes) : '' }}</td>
|
<td>{{ run.stats?.files ? formatSize(run.stats.files.bytes) : '' }}</td>
|
||||||
@@ -79,7 +79,7 @@ import NcNoteCard from '@nextcloud/vue/components/NcNoteCard'
|
|||||||
import NcSettingsSection from '@nextcloud/vue/components/NcSettingsSection'
|
import NcSettingsSection from '@nextcloud/vue/components/NcSettingsSection'
|
||||||
import NcTextField from '@nextcloud/vue/components/NcTextField'
|
import NcTextField from '@nextcloud/vue/components/NcTextField'
|
||||||
import api from '../api.js'
|
import api from '../api.js'
|
||||||
import { formatSize } from '../format.js'
|
import { formatDateTime, formatDate, formatSize } from '../format.js'
|
||||||
import { confirmPassword } from '@nextcloud/password-confirmation'
|
import { confirmPassword } from '@nextcloud/password-confirmation'
|
||||||
import { reactive } from 'vue'
|
import { reactive } from 'vue'
|
||||||
|
|
||||||
|
|||||||
@@ -35,7 +35,7 @@
|
|||||||
<tr v-for="slot in status.key.slots" :key="slot.slot">
|
<tr v-for="slot in status.key.slots" :key="slot.slot">
|
||||||
<td>{{ t('ng_backup', 'Slot {n}', { n: slot.slot }) }}</td>
|
<td>{{ t('ng_backup', 'Slot {n}', { n: slot.slot }) }}</td>
|
||||||
<td><strong>{{ slot.label }}</strong></td>
|
<td><strong>{{ slot.label }}</strong></td>
|
||||||
<td class="ngb-muted">{{ slot.created ? new Date(slot.created * 1000).toLocaleDateString() : '' }}</td>
|
<td class="ngb-muted">{{ slot.created ? formatDate(slot.created) : '' }}</td>
|
||||||
<td class="ngb-actions">
|
<td class="ngb-actions">
|
||||||
<NcButton variant="tertiary" @click="openSlotForm(slot)">{{ t('ng_backup', 'New passphrase') }}</NcButton>
|
<NcButton variant="tertiary" @click="openSlotForm(slot)">{{ t('ng_backup', 'New passphrase') }}</NcButton>
|
||||||
<NcButton v-if="status.key.slots.length > 1" variant="tertiary" @click="removeSlot(slot)">{{ t('ng_backup', 'Remove') }}</NcButton>
|
<NcButton v-if="status.key.slots.length > 1" variant="tertiary" @click="removeSlot(slot)">{{ t('ng_backup', 'Remove') }}</NcButton>
|
||||||
@@ -69,7 +69,7 @@
|
|||||||
<NcButton variant="primary" :disabled="!downloaded || !accepted || code.length < 8 || busy" @click="confirm">{{ status.confirmationPhrase }}</NcButton>
|
<NcButton variant="primary" :disabled="!downloaded || !accepted || code.length < 8 || busy" @click="confirm">{{ status.confirmationPhrase }}</NcButton>
|
||||||
</div>
|
</div>
|
||||||
<p v-else>
|
<p v-else>
|
||||||
{{ t('ng_backup', 'Recovery kit confirmed by {user} on {date}.', { user: status.key.confirmation.uid, date: new Date(status.key.confirmation.time * 1000).toLocaleString() }) }}
|
{{ t('ng_backup', 'Recovery kit confirmed by {user} on {date}.', { user: status.key.confirmation.uid, date: formatDateTime(status.key.confirmation.time) }) }}
|
||||||
<NcButton variant="tertiary" @click="downloadKit">{{ t('ng_backup', 'Download the kit again') }}</NcButton>
|
<NcButton variant="tertiary" @click="downloadKit">{{ t('ng_backup', 'Download the kit again') }}</NcButton>
|
||||||
<span class="ngb-muted">{{ t('ng_backup', 'Requires your password; all administrators are notified.') }}</span>
|
<span class="ngb-muted">{{ t('ng_backup', 'Requires your password; all administrators are notified.') }}</span>
|
||||||
</p>
|
</p>
|
||||||
@@ -90,6 +90,7 @@ import NcPasswordField from '@nextcloud/vue/components/NcPasswordField'
|
|||||||
import NcSettingsSection from '@nextcloud/vue/components/NcSettingsSection'
|
import NcSettingsSection from '@nextcloud/vue/components/NcSettingsSection'
|
||||||
import NcTextField from '@nextcloud/vue/components/NcTextField'
|
import NcTextField from '@nextcloud/vue/components/NcTextField'
|
||||||
import api from '../api.js'
|
import api from '../api.js'
|
||||||
|
import { formatDateTime, formatDate } from '../format.js'
|
||||||
|
|
||||||
const props = defineProps({ status: { type: Object, required: true } })
|
const props = defineProps({ status: { type: Object, required: true } })
|
||||||
const emit = defineEmits(['changed'])
|
const emit = defineEmits(['changed'])
|
||||||
|
|||||||
@@ -52,7 +52,7 @@ import NcSettingsSection from '@nextcloud/vue/components/NcSettingsSection'
|
|||||||
import { confirmPassword } from '@nextcloud/password-confirmation'
|
import { confirmPassword } from '@nextcloud/password-confirmation'
|
||||||
import '@nextcloud/password-confirmation/style.css'
|
import '@nextcloud/password-confirmation/style.css'
|
||||||
import api from '../api.js'
|
import api from '../api.js'
|
||||||
import { formatSize } from '../format.js'
|
import { formatDateTime, formatSize } from '../format.js'
|
||||||
|
|
||||||
const props = defineProps({ status: { type: Object, required: true } })
|
const props = defineProps({ status: { type: Object, required: true } })
|
||||||
const emit = defineEmits(['changed'])
|
const emit = defineEmits(['changed'])
|
||||||
@@ -74,7 +74,7 @@ const modes = [
|
|||||||
{ id: 'replace', label: t('ng_backup', 'Into the original place, overwrite and move files that are not in the backup to the trash') },
|
{ id: 'replace', label: t('ng_backup', 'Into the original place, overwrite and move files that are not in the backup to the trash') },
|
||||||
]
|
]
|
||||||
|
|
||||||
const snapshotLabel = (s) => new Date(s.createdAt * 1000).toLocaleString() + (s.label ? ' – ' + s.label : '')
|
const snapshotLabel = (s) => formatDateTime(s.createdAt) + (s.label ? ' – ' + s.label : '')
|
||||||
const join = (name) => path.value + '/' + name
|
const join = (name) => path.value + '/' + name
|
||||||
const restorable = (p) => /^data\/[^/]+\/files(\/.*)?$/.test(p)
|
const restorable = (p) => /^data\/[^/]+\/files(\/.*)?$/.test(p)
|
||||||
const crumbs = computed(() => {
|
const crumbs = computed(() => {
|
||||||
|
|||||||
@@ -1,5 +1,18 @@
|
|||||||
// SPDX-FileCopyrightText: 2026 Dillard Blom
|
// SPDX-FileCopyrightText: 2026 Dillard Blom
|
||||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||||
|
import { getCanonicalLocale } from '@nextcloud/l10n'
|
||||||
|
|
||||||
|
// The locale of the user's Nextcloud settings (Personal info > Locale), not the browser's:
|
||||||
|
// a browser set to English would otherwise show 4:00 PM to someone with a 24-hour locale.
|
||||||
|
const locale = getCanonicalLocale()
|
||||||
|
|
||||||
|
// A number is a unix timestamp in seconds, a string an ISO 8601 date (snapshot times).
|
||||||
|
const toDate = (value) => typeof value === 'number' ? new Date(value * 1000) : new Date(value)
|
||||||
|
|
||||||
|
export const formatDateTime = (value) => toDate(value).toLocaleString(locale)
|
||||||
|
|
||||||
|
export const formatDate = (value) => toDate(value).toLocaleDateString(locale)
|
||||||
|
|
||||||
export function formatSize(bytes) {
|
export function formatSize(bytes) {
|
||||||
if (bytes === null || bytes === undefined) {
|
if (bytes === null || bytes === undefined) {
|
||||||
return ''
|
return ''
|
||||||
|
|||||||
Reference in New Issue
Block a user