Skip to content

Commit 0886259

Browse files
committed
feat: add stream disable button to WebRTC live view cell
Same power-icon disable/re-enable overlay as HLS and MSE cells, now applied to WebRTCVideoCell. Missed in initial implementation.
1 parent 41f121d commit 0886259

1 file changed

Lines changed: 135 additions & 0 deletions

File tree

web/js/components/preact/WebRTCVideoCell.jsx

Lines changed: 135 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import { formatFilenameTimestamp } from '../../utils/date-utils.js';
1616
import { forceNavigation } from '../../utils/navigation-utils.js';
1717
import { formatUtils } from './recordings/formatUtils.js';
1818
import { useI18n } from '../../i18n.js';
19+
import { useQueryClient } from '../../query-client.js';
1920
import 'webrtc-adapter';
2021

2122
// Retry configuration for sending WebRTC offers to go2rtc.
@@ -67,6 +68,8 @@ export function WebRTCVideoCell({
6768
globalShowDetections = true
6869
}) {
6970
const { t } = useI18n();
71+
const queryClient = useQueryClient();
72+
7073
// Component state
7174
const [isLoading, setIsLoading] = useState(() => {
7275
// Derive initial loading state from the incoming stream, so that we
@@ -125,6 +128,11 @@ export function WebRTCVideoCell({
125128
// PTZ controls state
126129
const [showPTZControls, setShowPTZControls] = useState(false);
127130

131+
// Disable/enable stream state
132+
const [showDisableConfirm, setShowDisableConfirm] = useState(false);
133+
const [localIsDisabled, setLocalIsDisabled] = useState(false);
134+
const [isTogglingEnabled, setIsTogglingEnabled] = useState(false);
135+
128136
// Detection overlay visibility state (per-camera toggle, constrained by global toggle)
129137
const [localShowDetections, setLocalShowDetections] = useState(true);
130138
const showDetections = globalShowDetections && localShowDetections;
@@ -933,6 +941,46 @@ export function WebRTCVideoCell({
933941
setRetryCount(prev => prev + 1);
934942
};
935943

944+
/**
945+
* Handle disable stream (soft delete)
946+
*/
947+
const handleDisableStream = async () => {
948+
setIsTogglingEnabled(true);
949+
try {
950+
const res = await fetch(`/api/streams/${encodeURIComponent(stream.name)}`, { method: 'DELETE' });
951+
if (!res.ok) throw new Error(`HTTP ${res.status}`);
952+
setLocalIsDisabled(true);
953+
setShowDisableConfirm(false);
954+
queryClient.invalidateQueries({ queryKey: ['streams'] });
955+
} catch (err) {
956+
showStatusMessage(`${t('live.disableStream')}: ${err.message}`, 'error', 5000);
957+
setShowDisableConfirm(false);
958+
} finally {
959+
setIsTogglingEnabled(false);
960+
}
961+
};
962+
963+
/**
964+
* Handle enable stream
965+
*/
966+
const handleEnableStream = async () => {
967+
setIsTogglingEnabled(true);
968+
try {
969+
const res = await fetch(`/api/streams/${encodeURIComponent(stream.name)}`, {
970+
method: 'PUT',
971+
headers: { 'Content-Type': 'application/json' },
972+
body: JSON.stringify({ enable_disabled: true, enabled: true })
973+
});
974+
if (!res.ok) throw new Error(`HTTP ${res.status}`);
975+
setLocalIsDisabled(false);
976+
queryClient.invalidateQueries({ queryKey: ['streams'] });
977+
} catch (err) {
978+
showStatusMessage(`${t('live.enableStream')}: ${err.message}`, 'error', 5000);
979+
} finally {
980+
setIsTogglingEnabled(false);
981+
}
982+
};
983+
936984
// Start audio level monitoring
937985
const startAudioLevelMonitoring = useCallback((localStream) => {
938986
try {
@@ -1249,6 +1297,31 @@ export function WebRTCVideoCell({
12491297
}}
12501298
/>
12511299
</div>
1300+
{/* Disable stream button */}
1301+
<button
1302+
type="button"
1303+
title={t('live.disableStream')}
1304+
onClick={() => setShowDisableConfirm(true)}
1305+
style={{
1306+
backgroundColor: 'transparent',
1307+
border: 'none',
1308+
padding: '5px',
1309+
borderRadius: '4px',
1310+
color: 'white',
1311+
cursor: 'pointer',
1312+
transition: 'background-color 0.2s ease',
1313+
display: 'flex',
1314+
alignItems: 'center',
1315+
justifyContent: 'center'
1316+
}}
1317+
onMouseOver={(e) => e.currentTarget.style.backgroundColor = 'rgba(255, 255, 255, 0.2)'}
1318+
onMouseOut={(e) => e.currentTarget.style.backgroundColor = 'transparent'}
1319+
>
1320+
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="white" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
1321+
<path d="M18.36 6.64A9 9 0 1 1 5.64 17.36"/>
1322+
<line x1="12" y1="2" x2="12" y2="12"/>
1323+
</svg>
1324+
</button>
12521325
{/* Audio playback toggle button (for hearing camera audio) */}
12531326
{isPlaying && (
12541327
<button
@@ -1630,6 +1703,68 @@ export function WebRTCVideoCell({
16301703
message={t('live.forceRefreshWarning')}
16311704
confirmLabel={t('common.refresh')}
16321705
/>
1706+
1707+
{/* Inline disable confirmation overlay */}
1708+
{showDisableConfirm && (
1709+
<div style={{
1710+
position: 'absolute', top: 0, left: 0, right: 0, bottom: 0,
1711+
backgroundColor: 'rgba(0,0,0,0.75)', zIndex: 20,
1712+
display: 'flex', flexDirection: 'column',
1713+
alignItems: 'center', justifyContent: 'center', gap: '12px',
1714+
padding: '16px', textAlign: 'center'
1715+
}}>
1716+
<p style={{ color: 'white', fontSize: '14px', maxWidth: '240px', lineHeight: '1.4' }}>
1717+
{t('live.disableStreamConfirm')}
1718+
</p>
1719+
<div style={{ display: 'flex', gap: '8px' }}>
1720+
<button
1721+
onClick={handleDisableStream}
1722+
disabled={isTogglingEnabled}
1723+
style={{
1724+
padding: '6px 16px', backgroundColor: '#dc2626', color: 'white',
1725+
border: 'none', borderRadius: '4px', cursor: 'pointer', fontWeight: 'bold', fontSize: '13px'
1726+
}}
1727+
>
1728+
{t('live.disableStream')}
1729+
</button>
1730+
<button
1731+
onClick={() => setShowDisableConfirm(false)}
1732+
style={{
1733+
padding: '6px 16px', backgroundColor: 'rgba(255,255,255,0.2)', color: 'white',
1734+
border: '1px solid rgba(255,255,255,0.4)', borderRadius: '4px', cursor: 'pointer', fontSize: '13px'
1735+
}}
1736+
>
1737+
{t('common.cancel')}
1738+
</button>
1739+
</div>
1740+
</div>
1741+
)}
1742+
1743+
{/* Locally disabled overlay */}
1744+
{localIsDisabled && (
1745+
<div style={{
1746+
position: 'absolute', top: 0, left: 0, right: 0, bottom: 0,
1747+
backgroundColor: 'rgba(0,0,0,0.85)', zIndex: 15,
1748+
display: 'flex', flexDirection: 'column',
1749+
alignItems: 'center', justifyContent: 'center', gap: '12px'
1750+
}}>
1751+
<svg xmlns="http://www.w3.org/2000/svg" width="40" height="40" viewBox="0 0 24 24" fill="none" stroke="rgba(255,255,255,0.5)" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round">
1752+
<path d="M18.36 6.64A9 9 0 1 1 5.64 17.36"/>
1753+
<line x1="12" y1="2" x2="12" y2="12"/>
1754+
</svg>
1755+
<p style={{ color: 'rgba(255,255,255,0.7)', fontSize: '14px' }}>{t('live.streamDisabled')}</p>
1756+
<button
1757+
onClick={handleEnableStream}
1758+
disabled={isTogglingEnabled}
1759+
style={{
1760+
padding: '6px 16px', backgroundColor: '#16a34a', color: 'white',
1761+
border: 'none', borderRadius: '4px', cursor: 'pointer', fontWeight: 'bold', fontSize: '13px'
1762+
}}
1763+
>
1764+
{t('live.enableStream')}
1765+
</button>
1766+
</div>
1767+
)}
16331768
</div>
16341769
);
16351770
}

0 commit comments

Comments
 (0)