0.11.0-4 lw
This commit is contained in:
@@ -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)"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Generated
+2
-2
@@ -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,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": {
|
||||||
|
|||||||
@@ -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',
|
||||||
|
|||||||
Generated
+2
-2
@@ -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,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": {
|
||||||
|
|||||||
@@ -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}`}
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
Generated
+2
-2
@@ -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
@@ -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",
|
||||||
|
|||||||
Reference in New Issue
Block a user