0.10.2-11 Layout
This commit is contained in:
Generated
+2
-2
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "ripster-backend",
|
||||
"version": "0.10.2-10",
|
||||
"version": "0.10.2-11",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "ripster-backend",
|
||||
"version": "0.10.2-10",
|
||||
"version": "0.10.2-11",
|
||||
"dependencies": {
|
||||
"archiver": "^7.0.1",
|
||||
"cors": "^2.8.5",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "ripster-backend",
|
||||
"version": "0.10.2-10",
|
||||
"version": "0.10.2-11",
|
||||
"private": true,
|
||||
"type": "commonjs",
|
||||
"scripts": {
|
||||
|
||||
Generated
+2
-2
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "ripster-frontend",
|
||||
"version": "0.10.2-10",
|
||||
"version": "0.10.2-11",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "ripster-frontend",
|
||||
"version": "0.10.2-10",
|
||||
"version": "0.10.2-11",
|
||||
"dependencies": {
|
||||
"primeicons": "^7.0.0",
|
||||
"primereact": "^10.9.2",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "ripster-frontend",
|
||||
"version": "0.10.2-10",
|
||||
"version": "0.10.2-11",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { useEffect, useRef, useState } from 'react';
|
||||
import { TabView, TabPanel } from 'primereact/tabview';
|
||||
import { Accordion, AccordionTab } from 'primereact/accordion';
|
||||
import { InputText } from 'primereact/inputtext';
|
||||
import { InputNumber } from 'primereact/inputnumber';
|
||||
import { InputSwitch } from 'primereact/inputswitch';
|
||||
@@ -68,7 +69,8 @@ const EXPERT_ONLY_SETTING_KEYS = new Set([
|
||||
'makemkv_analyze_extra_args_dvd',
|
||||
'makemkv_rip_extra_args_bluray',
|
||||
'makemkv_rip_extra_args_dvd',
|
||||
'cdparanoia_command'
|
||||
'cdparanoia_command',
|
||||
'script_test_timeout_ms'
|
||||
]);
|
||||
|
||||
function toBoolean(value) {
|
||||
@@ -331,51 +333,6 @@ function SettingField({
|
||||
);
|
||||
}
|
||||
|
||||
function PathMediumCard({ title, pathSettings, settingsByKey, values, errors, dirtyKeys, onChange }) {
|
||||
// Filter out _owner keys since they're rendered inline
|
||||
const visibleSettings = pathSettings.filter(
|
||||
(s) => !String(s?.key || '').endsWith('_owner')
|
||||
);
|
||||
|
||||
if (visibleSettings.length === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="path-medium-card">
|
||||
<div className="path-medium-card-header">
|
||||
<h4>{title}</h4>
|
||||
</div>
|
||||
<div className="settings-grid">
|
||||
{visibleSettings.map((setting) => {
|
||||
const value = values?.[setting.key];
|
||||
const error = errors?.[setting.key] || null;
|
||||
const dirty = Boolean(dirtyKeys?.has?.(setting.key));
|
||||
const ownerKey = `${setting.key}_owner`;
|
||||
const ownerSetting = settingsByKey.get(ownerKey) || null;
|
||||
const ownerValue = values?.[ownerKey];
|
||||
const ownerError = errors?.[ownerKey] || null;
|
||||
const ownerDirty = Boolean(dirtyKeys?.has?.(ownerKey));
|
||||
|
||||
return (
|
||||
<SettingField
|
||||
key={setting.key}
|
||||
setting={setting}
|
||||
value={value}
|
||||
error={error}
|
||||
dirty={dirty}
|
||||
ownerSetting={ownerSetting}
|
||||
ownerValue={ownerValue}
|
||||
ownerError={ownerError}
|
||||
ownerDirty={ownerDirty}
|
||||
onChange={onChange}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function PathCategoryTab({ settings, values, errors, dirtyKeys, onChange, effectivePaths }) {
|
||||
const list = Array.isArray(settings) ? settings : [];
|
||||
@@ -478,84 +435,55 @@ function PathCategoryTab({ settings, values, errors, dirtyKeys, onChange, effect
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Medium-Karten */}
|
||||
<div className="path-medium-cards">
|
||||
<PathMediumCard
|
||||
title="Blu-ray"
|
||||
pathSettings={bluraySettings}
|
||||
settingsByKey={settingsByKey}
|
||||
values={values}
|
||||
errors={errors}
|
||||
dirtyKeys={dirtyKeys}
|
||||
onChange={onChange}
|
||||
/>
|
||||
<PathMediumCard
|
||||
title="DVD"
|
||||
pathSettings={dvdSettings}
|
||||
settingsByKey={settingsByKey}
|
||||
values={values}
|
||||
errors={errors}
|
||||
dirtyKeys={dirtyKeys}
|
||||
onChange={onChange}
|
||||
/>
|
||||
<PathMediumCard
|
||||
title="CD / Audio"
|
||||
pathSettings={cdSettings}
|
||||
settingsByKey={settingsByKey}
|
||||
values={values}
|
||||
errors={errors}
|
||||
dirtyKeys={dirtyKeys}
|
||||
onChange={onChange}
|
||||
/>
|
||||
<PathMediumCard
|
||||
title="Audiobook"
|
||||
pathSettings={audiobookSettings}
|
||||
settingsByKey={settingsByKey}
|
||||
values={values}
|
||||
errors={errors}
|
||||
dirtyKeys={dirtyKeys}
|
||||
onChange={onChange}
|
||||
/>
|
||||
<PathMediumCard
|
||||
title="Downloads"
|
||||
pathSettings={downloadSettings}
|
||||
settingsByKey={settingsByKey}
|
||||
values={values}
|
||||
errors={errors}
|
||||
dirtyKeys={dirtyKeys}
|
||||
onChange={onChange}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Log-Ordner */}
|
||||
{logSettings.length > 0 && (
|
||||
<div className="path-medium-card">
|
||||
<div className="path-medium-card-header">
|
||||
<h4>Logs</h4>
|
||||
</div>
|
||||
<div className="settings-grid">
|
||||
{logSettings.map((setting) => {
|
||||
const value = values?.[setting.key];
|
||||
const error = errors?.[setting.key] || null;
|
||||
const dirty = Boolean(dirtyKeys?.has?.(setting.key));
|
||||
return (
|
||||
<SettingField
|
||||
key={setting.key}
|
||||
setting={setting}
|
||||
value={value}
|
||||
error={error}
|
||||
dirty={dirty}
|
||||
ownerSetting={null}
|
||||
ownerValue={null}
|
||||
ownerError={null}
|
||||
ownerDirty={false}
|
||||
onChange={onChange}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
{/* Medium-Karten als Accordion */}
|
||||
<Accordion className="path-medium-accordion">
|
||||
{[
|
||||
{ title: 'Blu-ray', pathSettings: bluraySettings },
|
||||
{ title: 'DVD', pathSettings: dvdSettings },
|
||||
{ title: 'CD / Audio', pathSettings: cdSettings },
|
||||
{ title: 'Audiobook', pathSettings: audiobookSettings },
|
||||
{ title: 'Downloads', pathSettings: downloadSettings },
|
||||
...(logSettings.length > 0 ? [{ title: 'Logs', pathSettings: logSettings }] : [])
|
||||
]
|
||||
.filter(({ pathSettings }) =>
|
||||
pathSettings.filter((s) => !String(s?.key || '').endsWith('_owner')).length > 0
|
||||
)
|
||||
.map(({ title, pathSettings }) => {
|
||||
const visibleSettings = pathSettings.filter(
|
||||
(s) => !String(s?.key || '').endsWith('_owner')
|
||||
);
|
||||
return (
|
||||
<AccordionTab key={title} header={title}>
|
||||
<div className="settings-grid">
|
||||
{visibleSettings.map((setting) => {
|
||||
const value = values?.[setting.key];
|
||||
const error = errors?.[setting.key] || null;
|
||||
const dirty = Boolean(dirtyKeys?.has?.(setting.key));
|
||||
const ownerKey = `${setting.key}_owner`;
|
||||
const ownerSetting = settingsByKey.get(ownerKey) || null;
|
||||
const ownerValue = values?.[ownerKey];
|
||||
const ownerError = errors?.[ownerKey] || null;
|
||||
const ownerDirty = Boolean(dirtyKeys?.has?.(ownerKey));
|
||||
return (
|
||||
<SettingField
|
||||
key={setting.key}
|
||||
setting={setting}
|
||||
value={value}
|
||||
error={error}
|
||||
dirty={dirty}
|
||||
ownerSetting={ownerSetting}
|
||||
ownerValue={ownerValue}
|
||||
ownerError={ownerError}
|
||||
ownerDirty={ownerDirty}
|
||||
onChange={onChange}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</AccordionTab>
|
||||
);
|
||||
})}
|
||||
</Accordion>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -2113,165 +2113,6 @@ export default function DashboardPage({
|
||||
<div className="page-grid">
|
||||
<Toast ref={toastRef} />
|
||||
|
||||
{monitoringState.enabled ? (
|
||||
<Card title="Hardware Monitoring">
|
||||
<div className="hardware-monitor-head">
|
||||
<Tag value={`Letztes Update: ${formatUpdatedAt(monitoringState.updatedAt)}`} severity="warning" />
|
||||
</div>
|
||||
|
||||
{monitoringState.error ? (
|
||||
<small className="error-text">{monitoringState.error}</small>
|
||||
) : null}
|
||||
|
||||
{!monitoringState.enabled ? (
|
||||
<p>Monitoring ist deaktiviert. Aktivierung in den Settings unter Kategorie "Monitoring".</p>
|
||||
) : !monitoringSample ? (
|
||||
<p>Monitoring ist aktiv. Erste Messwerte werden gesammelt ...</p>
|
||||
) : (
|
||||
<div className="hardware-monitor-grid">
|
||||
<section className="hardware-monitor-block">
|
||||
<h4>CPU</h4>
|
||||
<div className="hardware-cpu-summary">
|
||||
<div className="hardware-cpu-chip" title="CPU Gesamtauslastung">
|
||||
<i className="pi pi-chart-line" />
|
||||
<span>{formatPercent(cpuMetrics?.overallUsagePercent)}</span>
|
||||
</div>
|
||||
<div className="hardware-cpu-chip" title="CPU Gesamttemperatur">
|
||||
<i className="pi pi-bolt" />
|
||||
<span>{formatTemperature(cpuMetrics?.overallTemperatureC)}</span>
|
||||
</div>
|
||||
<div className="hardware-cpu-load-group">
|
||||
<div className="hardware-cpu-chip" title="CPU Load Average">
|
||||
<i className="pi pi-chart-bar" />
|
||||
<span>{Array.isArray(cpuMetrics?.loadAverage) ? cpuMetrics.loadAverage.join(' / ') : '-'}</span>
|
||||
</div>
|
||||
{cpuPerCoreMetrics.length > 0 ? (
|
||||
<button
|
||||
type="button"
|
||||
className="hardware-cpu-core-toggle-btn"
|
||||
onClick={() => setCpuCoresExpanded((prev) => !prev)}
|
||||
aria-label={cpuCoresExpanded ? 'CPU-Kerne ausblenden' : 'CPU-Kerne einblenden'}
|
||||
aria-expanded={cpuCoresExpanded}
|
||||
>
|
||||
<i className={`pi ${cpuCoresExpanded ? 'pi-angle-up' : 'pi-angle-down'}`} />
|
||||
</button>
|
||||
) : null}
|
||||
</div>
|
||||
</div>
|
||||
{cpuPerCoreMetrics.length === 0 ? (
|
||||
<small>Pro-Core-Daten sind noch nicht verfuegbar.</small>
|
||||
) : null}
|
||||
{cpuPerCoreMetrics.length > 0 && cpuCoresExpanded ? (
|
||||
<div className="hardware-core-grid compact">
|
||||
{cpuPerCoreMetrics.map((core) => (
|
||||
<div key={`core-${core.index}`} className="hardware-core-item compact">
|
||||
<div className="hardware-core-title">C{core.index}</div>
|
||||
<div className="hardware-core-metric" title="Auslastung">
|
||||
<i className="pi pi-chart-line" />
|
||||
<small>{formatPercent(core.usagePercent)}</small>
|
||||
</div>
|
||||
<div className="hardware-core-metric" title="Temperatur">
|
||||
<i className="pi pi-bolt" />
|
||||
<small>{formatTemperature(core.temperatureC)}</small>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
) : null}
|
||||
</section>
|
||||
|
||||
<section className="hardware-monitor-block">
|
||||
<h4>RAM</h4>
|
||||
<div className="hardware-cpu-summary">
|
||||
<div className="hardware-cpu-chip" title="RAM Auslastung">
|
||||
<i className="pi pi-chart-pie" />
|
||||
<span>{formatPercent(memoryMetrics?.usagePercent)}</span>
|
||||
</div>
|
||||
<div className="hardware-cpu-chip" title="RAM Belegt">
|
||||
<i className="pi pi-arrow-up" />
|
||||
<span>{formatBytes(memoryMetrics?.usedBytes)}</span>
|
||||
</div>
|
||||
<div className="hardware-cpu-chip" title="RAM Frei">
|
||||
<i className="pi pi-arrow-down" />
|
||||
<span>{formatBytes(memoryMetrics?.freeBytes)}</span>
|
||||
</div>
|
||||
<div className="hardware-cpu-chip" title="RAM Gesamt">
|
||||
<i className="pi pi-database" />
|
||||
<span>{formatBytes(memoryMetrics?.totalBytes)}</span>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section className="hardware-monitor-block">
|
||||
<h4>GPU</h4>
|
||||
{!gpuMetrics?.available ? (
|
||||
<small>{gpuMetrics?.message || 'Keine GPU-Metriken verfuegbar.'}</small>
|
||||
) : (
|
||||
<div className="hardware-gpu-list">
|
||||
{gpuDevices.map((gpu, index) => (
|
||||
<div key={`gpu-${gpu?.index ?? index}`} className="hardware-gpu-item">
|
||||
<strong>
|
||||
GPU {gpu?.index ?? index}
|
||||
{gpu?.name ? ` | ${gpu.name}` : ''}
|
||||
</strong>
|
||||
<small>Load: {formatPercent(gpu?.utilizationPercent)}</small>
|
||||
<small>Mem-Load: {formatPercent(gpu?.memoryUtilizationPercent)}</small>
|
||||
<small>Temp: {formatTemperature(gpu?.temperatureC)}</small>
|
||||
<small>VRAM: {formatBytes(gpu?.memoryUsedBytes)} / {formatBytes(gpu?.memoryTotalBytes)}</small>
|
||||
<small>Power: {Number.isFinite(Number(gpu?.powerDrawW)) ? `${gpu.powerDrawW} W` : 'n/a'} / {Number.isFinite(Number(gpu?.powerLimitW)) ? `${gpu.powerLimitW} W` : 'n/a'}</small>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</section>
|
||||
|
||||
<section className="hardware-monitor-block">
|
||||
<h4>Freier Speicher</h4>
|
||||
<div className="hardware-storage-list">
|
||||
{storageGroups.map((group) => {
|
||||
const rep = group.representative;
|
||||
const tone = getStorageUsageTone(rep?.usagePercent);
|
||||
const usagePercent = Number(rep?.usagePercent);
|
||||
const barValue = Number.isFinite(usagePercent)
|
||||
? Math.max(0, Math.min(100, usagePercent))
|
||||
: 0;
|
||||
const hasError = group.entries.every((e) => e?.error);
|
||||
const groupKey = group.mountPoint || group.entries.map((e) => e?.key).join('-');
|
||||
const labels = group.entries.map((e) => e?.label || e?.key || 'Pfad').join(' · ');
|
||||
return (
|
||||
<div
|
||||
key={`storage-group-${groupKey}`}
|
||||
className={`hardware-storage-item compact${hasError ? ' has-error' : ''}`}
|
||||
>
|
||||
<small className="hardware-storage-group-label">{labels}</small>
|
||||
{hasError ? (
|
||||
<small className="error-text">{rep?.error}</small>
|
||||
) : (
|
||||
<>
|
||||
<div className="hardware-storage-bar-row">
|
||||
<div className={`hardware-storage-bar tone-${tone}`}>
|
||||
<ProgressBar value={barValue} showValue={false} />
|
||||
</div>
|
||||
<span className={`hardware-storage-percent tone-${tone}`}>
|
||||
{formatPercent(rep?.usagePercent)}
|
||||
</span>
|
||||
</div>
|
||||
<div className="hardware-storage-summary">
|
||||
<small>Frei: {formatBytes(rep?.freeBytes)}</small>
|
||||
<small>Gesamt: {formatBytes(rep?.totalBytes)}</small>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
)}
|
||||
</Card>
|
||||
) : null}
|
||||
|
||||
<div className="dashboard-3col-grid">
|
||||
<div className="dashboard-col dashboard-col-left">
|
||||
<Card title="Audiobook Upload">
|
||||
@@ -2390,6 +2231,48 @@ export default function DashboardPage({
|
||||
<p>Aktuell keine Disk erkannt.</p>
|
||||
)}
|
||||
</Card>
|
||||
|
||||
<Card title="Freier Speicher">
|
||||
<div className="hardware-storage-list">
|
||||
{storageGroups.map((group) => {
|
||||
const rep = group.representative;
|
||||
const tone = getStorageUsageTone(rep?.usagePercent);
|
||||
const usagePercent = Number(rep?.usagePercent);
|
||||
const barValue = Number.isFinite(usagePercent)
|
||||
? Math.max(0, Math.min(100, usagePercent))
|
||||
: 0;
|
||||
const hasError = group.entries.every((e) => e?.error);
|
||||
const groupKey = group.mountPoint || group.entries.map((e) => e?.key).join('-');
|
||||
const labels = group.entries.map((e) => e?.label || e?.key || 'Pfad').join(' · ');
|
||||
return (
|
||||
<div
|
||||
key={`storage-group-${groupKey}`}
|
||||
className={`hardware-storage-item compact${hasError ? ' has-error' : ''}`}
|
||||
>
|
||||
<small className="hardware-storage-group-label">{labels}</small>
|
||||
{hasError ? (
|
||||
<small className="error-text">{rep?.error}</small>
|
||||
) : (
|
||||
<>
|
||||
<div className="hardware-storage-bar-row">
|
||||
<div className={`hardware-storage-bar tone-${tone}`}>
|
||||
<ProgressBar value={barValue} showValue={false} />
|
||||
</div>
|
||||
<span className={`hardware-storage-percent tone-${tone}`}>
|
||||
{formatPercent(rep?.usagePercent)}
|
||||
</span>
|
||||
</div>
|
||||
<div className="hardware-storage-summary">
|
||||
<small>Frei: {formatBytes(rep?.freeBytes)}</small>
|
||||
<small>Gesamt: {formatBytes(rep?.totalBytes)}</small>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</Card>
|
||||
</div>
|
||||
|
||||
<div className="dashboard-col dashboard-col-center">
|
||||
@@ -2923,6 +2806,116 @@ export default function DashboardPage({
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{monitoringState.enabled ? (
|
||||
<Card title="Hardware Monitoring">
|
||||
<div className="hardware-monitor-head">
|
||||
{!monitoringState.enabled ? (
|
||||
<small>Hardware-Monitoring ist deaktiviert.</small>
|
||||
) : !monitoringSample ? (
|
||||
<small>Warte auf erste Messung ...</small>
|
||||
) : (
|
||||
<div className="hardware-monitor-grid">
|
||||
<section className="hardware-monitor-block">
|
||||
<h4>CPU</h4>
|
||||
<div className="hardware-cpu-summary">
|
||||
<div className="hardware-cpu-chip" title="CPU Gesamtauslastung">
|
||||
<i className="pi pi-chart-line" />
|
||||
<span>{formatPercent(cpuMetrics?.overallUsagePercent)}</span>
|
||||
</div>
|
||||
<div className="hardware-cpu-chip" title="CPU Gesamttemperatur">
|
||||
<i className="pi pi-bolt" />
|
||||
<span>{formatTemperature(cpuMetrics?.overallTemperatureC)}</span>
|
||||
</div>
|
||||
<div className="hardware-cpu-load-group">
|
||||
<div className="hardware-cpu-chip" title="CPU Load Average">
|
||||
<i className="pi pi-chart-bar" />
|
||||
<span>{Array.isArray(cpuMetrics?.loadAverage) ? cpuMetrics.loadAverage.join(' / ') : '-'}</span>
|
||||
</div>
|
||||
{cpuPerCoreMetrics.length > 0 ? (
|
||||
<button
|
||||
type="button"
|
||||
className="hardware-cpu-core-toggle-btn"
|
||||
onClick={() => setCpuCoresExpanded((prev) => !prev)}
|
||||
aria-label={cpuCoresExpanded ? 'CPU-Kerne ausblenden' : 'CPU-Kerne einblenden'}
|
||||
aria-expanded={cpuCoresExpanded}
|
||||
>
|
||||
<i className={`pi ${cpuCoresExpanded ? 'pi-angle-up' : 'pi-angle-down'}`} />
|
||||
</button>
|
||||
) : null}
|
||||
</div>
|
||||
</div>
|
||||
{cpuPerCoreMetrics.length === 0 ? (
|
||||
<small>Pro-Core-Daten sind noch nicht verfuegbar.</small>
|
||||
) : null}
|
||||
{cpuPerCoreMetrics.length > 0 && cpuCoresExpanded ? (
|
||||
<div className="hardware-core-grid compact">
|
||||
{cpuPerCoreMetrics.map((core) => (
|
||||
<div key={`core-${core.index}`} className="hardware-core-item compact">
|
||||
<div className="hardware-core-title">C{core.index}</div>
|
||||
<div className="hardware-core-metric" title="Auslastung">
|
||||
<i className="pi pi-chart-line" />
|
||||
<small>{formatPercent(core.usagePercent)}</small>
|
||||
</div>
|
||||
<div className="hardware-core-metric" title="Temperatur">
|
||||
<i className="pi pi-bolt" />
|
||||
<small>{formatTemperature(core.temperatureC)}</small>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
) : null}
|
||||
</section>
|
||||
|
||||
<section className="hardware-monitor-block">
|
||||
<h4>RAM</h4>
|
||||
<div className="hardware-cpu-summary">
|
||||
<div className="hardware-cpu-chip" title="RAM Auslastung">
|
||||
<i className="pi pi-chart-pie" />
|
||||
<span>{formatPercent(memoryMetrics?.usagePercent)}</span>
|
||||
</div>
|
||||
<div className="hardware-cpu-chip" title="RAM Belegt">
|
||||
<i className="pi pi-arrow-up" />
|
||||
<span>{formatBytes(memoryMetrics?.usedBytes)}</span>
|
||||
</div>
|
||||
<div className="hardware-cpu-chip" title="RAM Frei">
|
||||
<i className="pi pi-arrow-down" />
|
||||
<span>{formatBytes(memoryMetrics?.freeBytes)}</span>
|
||||
</div>
|
||||
<div className="hardware-cpu-chip" title="RAM Gesamt">
|
||||
<i className="pi pi-database" />
|
||||
<span>{formatBytes(memoryMetrics?.totalBytes)}</span>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section className="hardware-monitor-block">
|
||||
<h4>GPU</h4>
|
||||
{!gpuMetrics?.available ? (
|
||||
<small>{gpuMetrics?.message || 'Keine GPU-Metriken verfuegbar.'}</small>
|
||||
) : (
|
||||
<div className="hardware-gpu-list">
|
||||
{gpuDevices.map((gpu, index) => (
|
||||
<div key={`gpu-${gpu?.index ?? index}`} className="hardware-gpu-item">
|
||||
<strong>
|
||||
GPU {gpu?.index ?? index}
|
||||
{gpu?.name ? ` | ${gpu.name}` : ''}
|
||||
</strong>
|
||||
<small>Load: {formatPercent(gpu?.utilizationPercent)}</small>
|
||||
<small>Mem-Load: {formatPercent(gpu?.memoryUtilizationPercent)}</small>
|
||||
<small>Temp: {formatTemperature(gpu?.temperatureC)}</small>
|
||||
<small>VRAM: {formatBytes(gpu?.memoryUsedBytes)} / {formatBytes(gpu?.memoryTotalBytes)}</small>
|
||||
<small>Power: {Number.isFinite(Number(gpu?.powerDrawW)) ? `${gpu.powerDrawW} W` : 'n/a'} / {Number.isFinite(Number(gpu?.powerLimitW)) ? `${gpu.powerLimitW} W` : 'n/a'}</small>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</section>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</Card>
|
||||
) : null}
|
||||
|
||||
<MetadataSelectionDialog
|
||||
visible={metadataDialogVisible}
|
||||
context={effectiveMetadataDialogContext}
|
||||
|
||||
@@ -1744,7 +1744,7 @@ export default function SettingsPage() {
|
||||
</TabView>
|
||||
</Card>
|
||||
|
||||
{activationBytes.length > 0 && (
|
||||
{expertModeEnabled && activationBytes.length > 0 && (
|
||||
<Card
|
||||
title="Activation Bytes Cache"
|
||||
subTitle="Lokal gespeicherte AAX-Activation Bytes. Werden beim ersten Upload automatisch über die Audible-Tools API ermittelt."
|
||||
|
||||
Generated
+2
-2
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "ripster",
|
||||
"version": "0.10.2-10",
|
||||
"version": "0.10.2-11",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "ripster",
|
||||
"version": "0.10.2-10",
|
||||
"version": "0.10.2-11",
|
||||
"devDependencies": {
|
||||
"concurrently": "^9.1.2"
|
||||
}
|
||||
|
||||
+1
-1
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "ripster",
|
||||
"private": true,
|
||||
"version": "0.10.2-10",
|
||||
"version": "0.10.2-11",
|
||||
"scripts": {
|
||||
"dev": "concurrently \"npm run dev --prefix backend\" \"npm run dev --prefix frontend\"",
|
||||
"dev:backend": "npm run dev --prefix backend",
|
||||
|
||||
Reference in New Issue
Block a user