Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion css/main-DP1vptHW.chunk.css

This file was deleted.

1 change: 1 addition & 0 deletions css/main-DtGZuPjJ.chunk.css

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion css/serverinfo-main.css
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
/* extracted by css-entry-points-plugin */
@import './main-DP1vptHW.chunk.css';
@import './main-DtGZuPjJ.chunk.css';
26 changes: 13 additions & 13 deletions js/serverinfo-main.mjs

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion js/serverinfo-main.mjs.map

Large diffs are not rendered by default.

57 changes: 57 additions & 0 deletions src/components/SettingsNavigation.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
<!--
- SPDX-FileCopyrightText: 2026 Nextcloud GmbH and Nextcloud contributors
- SPDX-License-Identifier: AGPL-3.0-or-later
-->
<template>
<!-- TRANSLATORS: Accessible name of the switch between the two Monitoring pages, noun -->
<NcRadioGroup
v-model="view"
class="settings-navigation"
:label="t('serverinfo', 'Monitoring pages')"
labelHidden>
<!-- TRANSLATORS: Name of the page showing the server's current state: CPU, memory, disks, network, noun -->
<NcRadioGroupButton value="status" :label="t('serverinfo', 'Status')">
<template #icon>
<Monitor :size="20" />
</template>
</NcRadioGroupButton>
<!-- TRANSLATORS: Name of the page showing how the server's background jobs are doing, noun -->
<NcRadioGroupButton value="background-jobs" :label="t('serverinfo', 'Background jobs')">
<template #icon>
<Update :size="20" />
</template>
</NcRadioGroupButton>
</NcRadioGroup>
</template>

<script setup lang="ts">
import type { SettingsView } from '../types.ts'

import { t } from '@nextcloud/l10n'
import NcRadioGroup from '@nextcloud/vue/components/NcRadioGroup'
import NcRadioGroupButton from '@nextcloud/vue/components/NcRadioGroupButton'
import Monitor from 'vue-material-design-icons/Monitor.vue'
import Update from 'vue-material-design-icons/Update.vue'

const view = defineModel<SettingsView>({ required: true })
</script>

<style scoped>
.settings-navigation {
/* Core floats #app-navigation-toggle, the button that collapses the settings
navigation, over the top-inline-start corner of the content area. Its own
sections clear it vertically with their 30px padding rather than dodging it
sideways, so do the same: reserve the toggle's height above the switch and
line the buttons up with the section content below. */
width: fit-content;
padding-block-start: var(--default-clickable-area, 44px);
padding-inline-start: 30px;
margin-block-end: 12px;
}

/* "Background jobs" otherwise wraps onto a second line and leaves the two
buttons looking mismatched. */
.settings-navigation :deep([class*='radioGroupButton']) {
white-space: nowrap;
}
</style>
3 changes: 3 additions & 0 deletions src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,9 @@
* SPDX-License-Identifier: AGPL-3.0-or-later
*/

/** The pages the admin settings can show, selected by the navigation at the top. */
export type SettingsView = 'status' | 'background-jobs'

export interface ThermalZone {
zone: string
type: string
Expand Down
24 changes: 24 additions & 0 deletions src/views/BackgroundJobsView.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
<!--
- SPDX-FileCopyrightText: 2026 Nextcloud GmbH and Nextcloud contributors
- SPDX-License-Identifier: AGPL-3.0-or-later
-->
<template>
<div class="background-jobs-view">
<BackgroundJobsSection
v-if="periodicData"
:cron="periodicData.cron"
:jobs="periodicData.backgroundJobs" />
<SectionSkeleton v-else />
</div>
</template>

<script setup lang="ts">
import type { PeriodicData } from '../types.ts'

import BackgroundJobsSection from '../components/BackgroundJobsSection.vue'
import SectionSkeleton from '../components/SectionSkeleton.vue'

defineProps<{
periodicData: PeriodicData | null
}>()
</script>
188 changes: 19 additions & 169 deletions src/views/SettingsAdmin.vue
Original file line number Diff line number Diff line change
Expand Up @@ -4,186 +4,36 @@
-->
<template>
<div class="server-info-wrapper">
<template v-if="staticData">
<!-- Server info + resource usage -->
<div class="section server-infos-two">
<div class="row">
<div>
<SystemSection
:hostname="staticData.hostname"
:osname="staticData.osname"
:cpu="staticData.cpu"
:servertime="liveData?.servertime ?? ''"
:uptime="liveData?.uptime ?? ''" />
</div>
<div>
<ResourceOverviewSection
v-if="liveData"
:cpuload="cpuload"
:cpunum="staticData.cpu.threads"
:memTotal="liveData.memory.total"
:memFree="liveData.memory.free"
:swapTotal="liveData.memory.swap_total"
:swapFree="liveData.memory.swap_free" />
<SectionSkeleton v-else />
</div>
</div>
</div>

<!-- CPU + Memory charts -->
<div class="section server-infos-two">
<div class="row">
<div>
<CpuChartSection
v-if="liveData"
:cpuload="cpuload"
:cpunum="staticData.cpu.threads"
:tick="tick" />
<SectionSkeleton v-else />
</div>
<div>
<MemoryChartSection
v-if="liveData"
:memTotal="liveData.memory.total"
:memFree="liveData.memory.free"
:swapTotal="liveData.memory.swap_total"
:swapFree="liveData.memory.swap_free"
:tick="tick" />
<SectionSkeleton v-else />
</div>
</div>
</div>

<!-- Disk -->
<DiskSection :disks="staticData.diskinfo" :freespace="staticData.freeSpace" :storage="staticData.storage" />

<!-- Network -->
<NetworkSection :networkinfo="staticData.networkinfo" :interfaces="staticData.networkinterfaces" />

<!-- Background jobs -->
<BackgroundJobsSection
v-if="periodicData"
:cron="periodicData.cron"
:jobs="periodicData.backgroundJobs" />
<SectionSkeleton v-else />

<!-- Active users -->
<ActiveUsersSection :activeUsers="staticData.activeUsers" :numUsers="staticData.storage.num_users" />

<!-- Shares -->
<SharesSection v-if="staticData.shares.num_shares > 0" :shares="staticData.shares" />

<!-- Temperature -->
<div v-if="!liveData || liveData.thermalzones.length > 0" class="section">
<SectionSkeleton v-if="!liveData" />
<ThermalSection v-else :thermalzones="liveData.thermalzones" />
</div>

<!-- PHP + Opcache -->
<div class="section php-opcache">
<div class="row">
<div>
<PhpSection
:php="staticData.php"
:fpm="staticData.fpm"
:phpinfo="staticData.phpinfo"
:phpinfoUrl="staticData.phpinfoUrl" />
</div>
<div>
<OpcacheSection :opcache="staticData.opcache" />
</div>
</div>
</div>

<!-- Database -->
<div class="section">
<div class="row">
<div>
<DatabaseSection :database="staticData.database" />
</div>
</div>
</div>

<!-- External monitoring -->
<MonitoringSection :ocs="staticData.ocs" />
</template>

<template v-else>
<div class="section server-infos-two">
<div class="row">
<div>
<SectionSkeleton />
</div>
<div>
<SectionSkeleton />
</div>
</div>
</div>
<div class="section server-infos-two">
<div class="row">
<div>
<SectionSkeleton />
</div>
<div>
<SectionSkeleton />
</div>
</div>
</div>
<SectionSkeleton />
<SectionSkeleton />
<div class="section php-opcache">
<div class="row">
<div>
<SectionSkeleton />
</div>
<div>
<SectionSkeleton />
</div>
</div>
</div>
<div class="section">
<div class="row">
<div>
<SectionSkeleton />
</div>
</div>
</div>
</template>
<SettingsNavigation v-model="view" />

<!-- v-show rather than v-if: switching pages must not throw away the
chart history useLiveData has been accumulating since page load. -->
<StatusView
v-show="view === 'status'"
:staticData="staticData"
:liveData="liveData"
:tick="tick" />
<BackgroundJobsView
v-show="view === 'background-jobs'"
:periodicData="periodicData" />
</div>
</template>

<script setup lang="ts">
import type { LiveData, PeriodicData } from '../types.ts'
import type { LiveData, PeriodicData, SettingsView } from '../types.ts'

import { computed } from 'vue'
import ActiveUsersSection from '../components/ActiveUsersSection.vue'
import BackgroundJobsSection from '../components/BackgroundJobsSection.vue'
import CpuChartSection from '../components/CpuChartSection.vue'
import DatabaseSection from '../components/DatabaseSection.vue'
import DiskSection from '../components/DiskSection.vue'
import MemoryChartSection from '../components/MemoryChartSection.vue'
import MonitoringSection from '../components/MonitoringSection.vue'
import NetworkSection from '../components/NetworkSection.vue'
import OpcacheSection from '../components/OpcacheSection.vue'
import PhpSection from '../components/PhpSection.vue'
import ResourceOverviewSection from '../components/ResourceOverviewSection.vue'
import SectionSkeleton from '../components/SectionSkeleton.vue'
import SharesSection from '../components/SharesSection.vue'
import SystemSection from '../components/SystemSection.vue'
import ThermalSection from '../components/ThermalSection.vue'
import { ref } from 'vue'
import SettingsNavigation from '../components/SettingsNavigation.vue'
import BackgroundJobsView from './BackgroundJobsView.vue'
import StatusView from './StatusView.vue'
import { useLiveData } from '../composables/useLiveData.ts'
import { useStaticData } from '../composables/useStaticData.ts'

defineOptions({ name: 'ServerInfo' })

const view = ref<SettingsView>('status')

const { data: staticData } = useStaticData()
const { data: liveData, tick } = useLiveData<LiveData>('/apps/serverinfo/update')
const { data: periodicData } = useLiveData<PeriodicData>('/apps/serverinfo/periodic', 60000)

// The API reports "no CPU data" as both false and an empty array; collapsing
// the two keeps every consumer down to a single check.
const cpuload = computed(() => {
const load = liveData.value?.cpu.load
return Array.isArray(load) && load.length > 0 ? load : false
})
</script>
Loading
Loading