0.11.0-4 lw

This commit is contained in:
2026-03-17 20:28:38 +00:00
parent 654a77b8ba
commit 38fc1778f7
11 changed files with 156 additions and 11 deletions
+2 -1
View File
@@ -15,7 +15,8 @@
"Bash(node --check /home/michael/ripster/backend/src/services/diskDetectionService.js)", "Bash(node --check /home/michael/ripster/backend/src/services/diskDetectionService.js)",
"Bash(node --check /home/michael/ripster/backend/src/services/settingsService.js)", "Bash(node --check /home/michael/ripster/backend/src/services/settingsService.js)",
"Bash(node --check /home/michael/ripster/backend/src/services/pipelineService.js)", "Bash(node --check /home/michael/ripster/backend/src/services/pipelineService.js)",
"Bash(node --check /home/michael/ripster/backend/src/db/database.js)" "Bash(node --check /home/michael/ripster/backend/src/db/database.js)",
"Bash(node --check /home/michael/ripster/backend/src/routes/settingsRoutes.js)"
] ]
} }
} }
+2 -2
View File
@@ -1,12 +1,12 @@
{ {
"name": "ripster-backend", "name": "ripster-backend",
"version": "0.11.0-3", "version": "0.11.0-4",
"lockfileVersion": 3, "lockfileVersion": 3,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "ripster-backend", "name": "ripster-backend",
"version": "0.11.0-3", "version": "0.11.0-4",
"dependencies": { "dependencies": {
"archiver": "^7.0.1", "archiver": "^7.0.1",
"cors": "^2.8.5", "cors": "^2.8.5",
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "ripster-backend", "name": "ripster-backend",
"version": "0.11.0-3", "version": "0.11.0-4",
"private": true, "private": true,
"type": "commonjs", "type": "commonjs",
"scripts": { "scripts": {
+27
View File
@@ -9,6 +9,7 @@ const wsService = require('../services/websocketService');
const hardwareMonitorService = require('../services/hardwareMonitorService'); const hardwareMonitorService = require('../services/hardwareMonitorService');
const userPresetService = require('../services/userPresetService'); const userPresetService = require('../services/userPresetService');
const activationBytesService = require('../services/activationBytesService'); const activationBytesService = require('../services/activationBytesService');
const diskDetectionService = require('../services/diskDetectionService');
const logger = require('../services/logger').child('SETTINGS_ROUTE'); const logger = require('../services/logger').child('SETTINGS_ROUTE');
const { getDb } = require('../db/database'); const { getDb } = require('../db/database');
@@ -402,6 +403,32 @@ router.post(
}) })
); );
// ── Optical drive scan ────────────────────────────────────────────────────────
router.get(
'/drives',
asyncHandler(async (req, res) => {
logger.debug('get:settings:drives', { reqId: req.reqId });
const devices = await diskDetectionService.getBlockDeviceInfo();
const drives = devices
.filter((d) => d.type === 'rom')
.map((d) => {
const name = String(d.name || '');
const devicePath = d.path || (name ? `/dev/${name}` : null);
const match = name.match(/sr(\d+)$/);
const discIndex = match ? Number(match[1]) : null;
return {
path: devicePath,
name,
model: d.model || null,
discIndex,
discArg: discIndex != null ? `disc:${discIndex}` : null
};
})
.filter((d) => d.path);
res.json({ drives });
})
);
// User preferences (UI state, persisted per-installation) // User preferences (UI state, persisted per-installation)
router.get( router.get(
'/prefs/:key', '/prefs/:key',
+2 -2
View File
@@ -1,12 +1,12 @@
{ {
"name": "ripster-frontend", "name": "ripster-frontend",
"version": "0.11.0-3", "version": "0.11.0-4",
"lockfileVersion": 3, "lockfileVersion": 3,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "ripster-frontend", "name": "ripster-frontend",
"version": "0.11.0-3", "version": "0.11.0-4",
"dependencies": { "dependencies": {
"primeicons": "^7.0.0", "primeicons": "^7.0.0",
"primereact": "^10.9.2", "primereact": "^10.9.2",
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "ripster-frontend", "name": "ripster-frontend",
"version": "0.11.0-3", "version": "0.11.0-4",
"private": true, "private": true,
"type": "module", "type": "module",
"scripts": { "scripts": {
+3
View File
@@ -321,6 +321,9 @@ export const api = {
getPendingActivation() { getPendingActivation() {
return request('/pipeline/audiobook/pending-activation'); return request('/pipeline/audiobook/pending-activation');
}, },
getDetectedDrives() {
return request('/settings/drives');
},
getHandBrakePresets(options = {}) { getHandBrakePresets(options = {}) {
return requestCachedGet('/settings/handbrake-presets', { return requestCachedGet('/settings/handbrake-presets', {
ttlMs: 10 * 60 * 1000, ttlMs: 10 * 60 * 1000,
@@ -7,6 +7,7 @@ import { InputSwitch } from 'primereact/inputswitch';
import { Dropdown } from 'primereact/dropdown'; import { Dropdown } from 'primereact/dropdown';
import { Tag } from 'primereact/tag'; import { Tag } from 'primereact/tag';
import { Button } from 'primereact/button'; import { Button } from 'primereact/button';
import { api } from '../api/client';
function normalizeText(value) { function normalizeText(value) {
return String(value || '').trim().toLowerCase(); return String(value || '').trim().toLowerCase();
@@ -49,6 +50,7 @@ const PUSHOVER_ENABLED_SETTING_KEY = 'pushover_enabled';
const EXPERT_MODE_SETTING_KEY = 'ui_expert_mode'; const EXPERT_MODE_SETTING_KEY = 'ui_expert_mode';
const ALWAYS_HIDDEN_SETTING_KEYS = new Set([ const ALWAYS_HIDDEN_SETTING_KEYS = new Set([
'drive_device', 'drive_device',
'makemkv_source_index', // auto-derived from drive path — shown as read-only in drive info
'makemkv_rip_mode', 'makemkv_rip_mode',
'makemkv_rip_mode_bluray', 'makemkv_rip_mode_bluray',
'makemkv_rip_mode_dvd', 'makemkv_rip_mode_dvd',
@@ -58,7 +60,6 @@ const EXPERT_ONLY_SETTING_KEYS = new Set([
'pushover_device', 'pushover_device',
'pushover_priority', 'pushover_priority',
'pushover_timeout_ms', 'pushover_timeout_ms',
'makemkv_source_index',
'disc_poll_interval_ms', 'disc_poll_interval_ms',
'hardware_monitoring_interval_ms', 'hardware_monitoring_interval_ms',
'makemkv_command', 'makemkv_command',
@@ -177,6 +178,64 @@ function DriveDevicesEditor({ value, onChange, settingKey }) {
); );
} }
function DetectedDrivesInfo() {
const [drives, setDrives] = useState(null);
const [loading, setLoading] = useState(false);
const load = () => {
setLoading(true);
api.getDetectedDrives()
.then((res) => setDrives(Array.isArray(res?.drives) ? res.drives : []))
.catch(() => setDrives([]))
.finally(() => setLoading(false));
};
useEffect(() => { load(); }, []);
return (
<div className="detected-drives-info">
<div className="detected-drives-header">
<span className="detected-drives-label">Erkannte optische Laufwerke</span>
<Button
icon="pi pi-refresh"
text
rounded
size="small"
loading={loading}
onClick={load}
type="button"
aria-label="Laufwerke neu einlesen"
title="Laufwerke neu einlesen"
/>
</div>
{drives === null || loading
? <span className="detected-drives-empty">Wird geladen</span>
: drives.length === 0
? <span className="detected-drives-empty">Keine optischen Laufwerke erkannt.</span>
: (
<table className="detected-drives-table">
<thead>
<tr>
<th>Gerät</th>
<th>Modell</th>
<th>MakeMKV Index</th>
</tr>
</thead>
<tbody>
{drives.map((d) => (
<tr key={d.path}>
<td><code>{d.path}</code></td>
<td>{d.model || '—'}</td>
<td><Tag value={d.discArg ?? '—'} severity="info" /></td>
</tr>
))}
</tbody>
</table>
)}
</div>
);
}
function shouldHideSettingByExpertMode(settingKey, expertModeEnabled) { function shouldHideSettingByExpertMode(settingKey, expertModeEnabled) {
const key = normalizeSettingKey(settingKey); const key = normalizeSettingKey(settingKey);
if (!key) { if (!key) {
@@ -688,8 +747,10 @@ export default function DynamicSettingsForm({
const isNotificationCategory = normalizeText(category?.category) === 'benachrichtigungen'; const isNotificationCategory = normalizeText(category?.category) === 'benachrichtigungen';
const pushoverEnabled = toBoolean(values?.[PUSHOVER_ENABLED_SETTING_KEY]); const pushoverEnabled = toBoolean(values?.[PUSHOVER_ENABLED_SETTING_KEY]);
const isDriveCategory = normalizeText(category?.category) === 'laufwerk';
return ( return (
<div className="settings-sections"> <div className="settings-sections">
{isDriveCategory && <DetectedDrivesInfo />}
{sections.map((section) => ( {sections.map((section) => (
<section <section
key={`${category?.category || 'category'}-${section.id}`} key={`${category?.category || 'category'}-${section.id}`}
+53
View File
@@ -1723,6 +1723,59 @@ body {
margin-top: 1rem; margin-top: 1rem;
} }
/* Detected drives info block (Settings page → Laufwerk) */
.detected-drives-info {
margin-bottom: 1.25rem;
padding: 0.6rem 0.75rem;
border: 1px solid var(--rip-border);
border-radius: 0.5rem;
background: var(--rip-panel-soft);
}
.detected-drives-header {
display: flex;
align-items: center;
gap: 0.4rem;
margin-bottom: 0.5rem;
}
.detected-drives-label {
font-size: 0.8rem;
font-weight: 600;
color: var(--rip-muted);
text-transform: uppercase;
letter-spacing: 0.04em;
flex: 1;
}
.detected-drives-empty {
font-size: 0.85rem;
color: var(--rip-muted);
}
.detected-drives-table {
width: 100%;
border-collapse: collapse;
font-size: 0.85rem;
}
.detected-drives-table th {
text-align: left;
padding: 0.2rem 0.5rem;
color: var(--rip-muted);
font-weight: 600;
border-bottom: 1px solid var(--rip-border);
}
.detected-drives-table td {
padding: 0.25rem 0.5rem;
vertical-align: middle;
}
.detected-drives-table tr:not(:last-child) td {
border-bottom: 1px solid var(--rip-border);
}
/* Drive devices list editor (Settings page) */ /* Drive devices list editor (Settings page) */
.drive-devices-editor { .drive-devices-editor {
display: flex; display: flex;
+2 -2
View File
@@ -1,12 +1,12 @@
{ {
"name": "ripster", "name": "ripster",
"version": "0.11.0-3", "version": "0.11.0-4",
"lockfileVersion": 3, "lockfileVersion": 3,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "ripster", "name": "ripster",
"version": "0.11.0-3", "version": "0.11.0-4",
"devDependencies": { "devDependencies": {
"concurrently": "^9.1.2" "concurrently": "^9.1.2"
} }
+1 -1
View File
@@ -1,7 +1,7 @@
{ {
"name": "ripster", "name": "ripster",
"private": true, "private": true,
"version": "0.11.0-3", "version": "0.11.0-4",
"scripts": { "scripts": {
"dev": "concurrently \"npm run dev --prefix backend\" \"npm run dev --prefix frontend\"", "dev": "concurrently \"npm run dev --prefix backend\" \"npm run dev --prefix frontend\"",
"dev:backend": "npm run dev --prefix backend", "dev:backend": "npm run dev --prefix backend",