mirror of
https://github.com/maziggy/bambuddy.git
synced 2026-10-04 21:21:54 +02:00
Feature/multiple switches per prtinter (#786)
Feature/multiple switches per prtinter (#786)
This commit is contained in:
@@ -201,6 +201,23 @@ async def get_script_plugs_by_printer(
|
||||
return ha_entities
|
||||
|
||||
|
||||
@router.get("/by-printer/{printer_id}/all", response_model=list[SmartPlugResponse])
|
||||
async def get_all_smart_plugs_by_printer(
|
||||
printer_id: int,
|
||||
db: AsyncSession = Depends(get_db),
|
||||
_: User | None = RequirePermissionIfAuthEnabled(Permission.SMART_PLUGS_READ),
|
||||
):
|
||||
"""Get all smart plugs assigned to a printer that should appear on the printer card.
|
||||
|
||||
Returns power plugs (tasmota, mqtt) plus HA entities with show_on_printer_card enabled.
|
||||
"""
|
||||
result = await db.execute(select(SmartPlug).where(SmartPlug.printer_id == printer_id))
|
||||
plugs = result.scalars().all()
|
||||
return [
|
||||
plug for plug in plugs if plug.plug_type != "homeassistant" or (plug.ha_entity_id and plug.show_on_printer_card)
|
||||
]
|
||||
|
||||
|
||||
# Tasmota Discovery Endpoints
|
||||
# NOTE: These must be defined BEFORE /{plug_id} routes to avoid path conflicts
|
||||
|
||||
|
||||
@@ -3277,6 +3277,7 @@ export const api = {
|
||||
getSmartPlug: (id: number) => request<SmartPlug>(`/smart-plugs/${id}`),
|
||||
getSmartPlugByPrinter: (printerId: number) => request<SmartPlug | null>(`/smart-plugs/by-printer/${printerId}`),
|
||||
getScriptPlugsByPrinter: (printerId: number) => request<SmartPlug[]>(`/smart-plugs/by-printer/${printerId}/scripts`),
|
||||
getAllSmartPlugsByPrinter: (printerId: number) => request<SmartPlug[]>(`/smart-plugs/by-printer/${printerId}/all`),
|
||||
createSmartPlug: (data: SmartPlugCreate) =>
|
||||
request<SmartPlug>('/smart-plugs/', {
|
||||
method: 'POST',
|
||||
|
||||
@@ -255,7 +255,7 @@ export function AddSmartPlugModal({ plug, onClose }: AddSmartPlugModalProps) {
|
||||
onSuccess: () => {
|
||||
queryClient.invalidateQueries({ queryKey: ['smart-plugs'] });
|
||||
// Also invalidate printer card HA entity queries
|
||||
queryClient.invalidateQueries({ queryKey: ['scriptPlugsByPrinter'] });
|
||||
queryClient.invalidateQueries({ queryKey: ['smartPlugsByPrinter'] });
|
||||
onClose();
|
||||
},
|
||||
onError: (err: Error) => {
|
||||
@@ -269,7 +269,7 @@ export function AddSmartPlugModal({ plug, onClose }: AddSmartPlugModalProps) {
|
||||
onSuccess: () => {
|
||||
queryClient.invalidateQueries({ queryKey: ['smart-plugs'] });
|
||||
// Also invalidate printer card HA entity queries
|
||||
queryClient.invalidateQueries({ queryKey: ['scriptPlugsByPrinter'] });
|
||||
queryClient.invalidateQueries({ queryKey: ['smartPlugsByPrinter'] });
|
||||
onClose();
|
||||
},
|
||||
onError: (err: Error) => {
|
||||
|
||||
@@ -80,8 +80,7 @@ export function SmartPlugCard({ plug, onEdit }: SmartPlugCardProps) {
|
||||
queryClient.invalidateQueries({ queryKey: ['smart-plugs'] });
|
||||
// Also invalidate printer-specific smart plug queries to keep PrintersPage in sync
|
||||
if (plug.printer_id) {
|
||||
queryClient.invalidateQueries({ queryKey: ['smartPlugByPrinter', plug.printer_id] });
|
||||
queryClient.invalidateQueries({ queryKey: ['scriptPlugsByPrinter', plug.printer_id] });
|
||||
queryClient.invalidateQueries({ queryKey: ['smartPlugsByPrinter', plug.printer_id] });
|
||||
}
|
||||
},
|
||||
});
|
||||
@@ -93,7 +92,7 @@ export function SmartPlugCard({ plug, onEdit }: SmartPlugCardProps) {
|
||||
queryClient.invalidateQueries({ queryKey: ['smart-plugs'] });
|
||||
// Also invalidate printer card HA entity queries
|
||||
if (plug.printer_id) {
|
||||
queryClient.invalidateQueries({ queryKey: ['scriptPlugsByPrinter', plug.printer_id] });
|
||||
queryClient.invalidateQueries({ queryKey: ['smartPlugsByPrinter', plug.printer_id] });
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
@@ -374,12 +374,12 @@ export default {
|
||||
resumeTitle: 'Druck fortsetzen',
|
||||
resumeMessage: 'Möchten Sie den Druck auf "{{name}}" fortsetzen?',
|
||||
resumeButton: 'Druck fortsetzen',
|
||||
powerOnTitle: 'Drucker einschalten',
|
||||
powerOnMessage: 'Möchten Sie die Stromversorgung für "{{name}}" wirklich EINSCHALTEN?',
|
||||
powerOnTitle: 'Schalter einschalten',
|
||||
powerOnMessage: 'Möchten Sie "{{name}}" wirklich EINSCHALTEN?',
|
||||
powerOnButton: 'Einschalten',
|
||||
powerOffTitle: 'Drucker ausschalten',
|
||||
powerOffMessage: 'Möchten Sie die Stromversorgung für "{{name}}" wirklich AUSSCHALTEN?',
|
||||
powerOffWarning: 'WARNUNG: "{{name}}" druckt gerade! Möchten Sie die Stromversorgung wirklich AUSSCHALTEN? Dies unterbricht den Druck und kann den Drucker beschädigen.',
|
||||
powerOffTitle: 'Schalter ausschalten',
|
||||
powerOffMessage: 'Möchten Sie "{{name}}" wirklich AUSSCHALTEN?',
|
||||
powerOffWarning: 'WARNUNG: "{{name}}" druckt gerade! Möchten Sie wirklich AUSSCHALTEN? Dies unterbricht den Druck und kann den Drucker beschädigen.',
|
||||
powerOffButton: 'Ausschalten',
|
||||
},
|
||||
// Discovery
|
||||
|
||||
@@ -374,12 +374,12 @@ export default {
|
||||
resumeTitle: 'Resume Print',
|
||||
resumeMessage: 'Are you sure you want to resume the print on "{{name}}"?',
|
||||
resumeButton: 'Resume Print',
|
||||
powerOnTitle: 'Power On Printer',
|
||||
powerOnMessage: 'Are you sure you want to turn ON the power for "{{name}}"?',
|
||||
powerOnTitle: 'Power On Switch',
|
||||
powerOnMessage: 'Are you sure you want to turn ON "{{name}}"?',
|
||||
powerOnButton: 'Power On',
|
||||
powerOffTitle: 'Power Off Printer',
|
||||
powerOffMessage: 'Are you sure you want to turn OFF the power for "{{name}}"?',
|
||||
powerOffWarning: 'WARNING: "{{name}}" is currently printing! Are you sure you want to turn OFF the power? This will interrupt the print and may damage the printer.',
|
||||
powerOffTitle: 'Power Off Switch',
|
||||
powerOffMessage: 'Are you sure you want to turn OFF "{{name}}"?',
|
||||
powerOffWarning: 'WARNING: "{{name}}" is currently printing! Are you sure you want to turn it OFF? This will interrupt the print and may damage the printer.',
|
||||
powerOffButton: 'Power Off',
|
||||
},
|
||||
// Discovery
|
||||
|
||||
@@ -374,12 +374,12 @@ export default {
|
||||
resumeTitle: 'Reprendre l\'impression',
|
||||
resumeMessage: 'Reprendre l\'impression sur "{{name}}" ?',
|
||||
resumeButton: 'Reprendre',
|
||||
powerOnTitle: 'Allumer l\'imprimante',
|
||||
powerOnTitle: 'Allumer Interrupteur',
|
||||
powerOnMessage: 'Allumer "{{name}}" ?',
|
||||
powerOnButton: 'Allumer',
|
||||
powerOffTitle: 'Éteindre l\'imprimante',
|
||||
powerOffTitle: 'Éteindre Interrupteur',
|
||||
powerOffMessage: 'Éteindre "{{name}}" ?',
|
||||
powerOffWarning: 'ATTENTION : "{{name}}" imprime ! L\'éteindre maintenant peut endommager l\'imprimante.',
|
||||
powerOffWarning: 'ATTENTION : "{{name}}" imprime actuellement ! Êtes-vous sûr de vouloir l\'éteindre ? Cela interrompra l\'impression et pourrait endommager l\'imprimante.',
|
||||
powerOffButton: 'Éteindre',
|
||||
},
|
||||
// Discovery
|
||||
|
||||
@@ -374,12 +374,12 @@ export default {
|
||||
resumeTitle: 'Riprendi Stampa',
|
||||
resumeMessage: 'Sei sicuro di riprendere la stampa su "{{name}}"?',
|
||||
resumeButton: 'Riprendi Stampa',
|
||||
powerOnTitle: 'Accendi Stampante',
|
||||
powerOnTitle: 'Accendi Interruttore',
|
||||
powerOnMessage: 'Sei sicuro di accendere "{{name}}"?',
|
||||
powerOnButton: 'Accendi',
|
||||
powerOffTitle: 'Spegni Stampante',
|
||||
powerOffTitle: 'Spegni Interruttore',
|
||||
powerOffMessage: 'Sei sicuro di spegnere "{{name}}"?',
|
||||
powerOffWarning: 'AVVISO: "{{name}}" sta stampando! Sei sicuro di spegnere? Questo interromperà la stampa e potrebbe danneggiare la stampante.',
|
||||
powerOffWarning: 'AVVISO: "{{name}}" sta stampando! Sei sicuro di spegnerlo? Questo interromperà la stampa e potrebbe danneggiare la stampante.',
|
||||
powerOffButton: 'Spegni',
|
||||
},
|
||||
// Discovery
|
||||
|
||||
@@ -373,12 +373,12 @@ export default {
|
||||
resumeTitle: '印刷を再開',
|
||||
resumeMessage: '「{{name}}」の印刷を再開しますか?',
|
||||
resumeButton: '印刷を再開',
|
||||
powerOnTitle: 'プリンターの電源をオン',
|
||||
powerOnMessage: '「{{name}}」の電源をオンにしますか?',
|
||||
powerOnTitle: 'スイッチをオン',
|
||||
powerOnMessage: '「{{name}}」をオンにしますか?',
|
||||
powerOnButton: '電源オン',
|
||||
powerOffTitle: 'プリンターの電源をオフ',
|
||||
powerOffMessage: '「{{name}}」の電源をオフにしますか?',
|
||||
powerOffWarning: '警告: 「{{name}}」は現在印刷中です!電源をオフにしますか?印刷が中断され、プリンターが損傷する可能性があります。',
|
||||
powerOffTitle: 'スイッチをオフ',
|
||||
powerOffMessage: '「{{name}}」をオフにしますか?',
|
||||
powerOffWarning: '警告: 「{{name}}」は現在印刷中です!オフにしますか?印刷が中断され、プリンターが損傷する可能性があります。',
|
||||
powerOffButton: '電源オフ',
|
||||
},
|
||||
// Discovery
|
||||
|
||||
@@ -374,12 +374,12 @@ export default {
|
||||
resumeTitle: 'Retomar Impressão',
|
||||
resumeMessage: 'Tem certeza de que deseja retomar a impressão em "{{name}}"?',
|
||||
resumeButton: 'Retomar Impressão',
|
||||
powerOnTitle: 'Ligar Impressora',
|
||||
powerOnMessage: 'Tem certeza de que deseja ligar a impressora "{{name}}"?',
|
||||
powerOnTitle: 'Ligar Interruptor',
|
||||
powerOnMessage: 'Tem certeza de que deseja ligar "{{name}}"?',
|
||||
powerOnButton: 'Ligar',
|
||||
powerOffTitle: 'Desligar Impressora',
|
||||
powerOffMessage: 'Tem certeza de que deseja desligar a impressora "{{name}}"?',
|
||||
powerOffWarning: 'AVISO: "{{name}}" está imprimindo no momento! Tem certeza de que deseja desligar a impressora? Isso interromperá a impressão e pode danificar a impressora.',
|
||||
powerOffTitle: 'Desligar Interruptor',
|
||||
powerOffMessage: 'Tem certeza de que deseja desligar "{{name}}"?',
|
||||
powerOffWarning: 'AVISO: "{{name}}" está imprimindo! Tem certeza de que deseja desligá-lo? Isso interromperá a impressão e pode danificar a impressora.',
|
||||
powerOffButton: 'Desligar',
|
||||
},
|
||||
// Discovery
|
||||
|
||||
@@ -374,12 +374,12 @@ export default {
|
||||
resumeTitle: '继续打印',
|
||||
resumeMessage: '确定要继续"{{name}}"上的打印吗?',
|
||||
resumeButton: '继续打印',
|
||||
powerOnTitle: '开启打印机',
|
||||
powerOnMessage: '确定要打开"{{name}}"的电源吗?',
|
||||
powerOnButton: '开机',
|
||||
powerOffTitle: '关闭打印机',
|
||||
powerOffMessage: '确定要关闭"{{name}}"的电源吗?',
|
||||
powerOffWarning: '警告:"{{name}}"正在打印中!确定要关闭电源吗?这将中断打印并可能损坏打印机。',
|
||||
powerOnTitle: '开启开关',
|
||||
powerOnMessage: '确定要开启"{{name}}"吗?',
|
||||
powerOnButton: '开启',
|
||||
powerOffTitle: '关闭开关',
|
||||
powerOffMessage: '确定要关闭"{{name}}"吗?',
|
||||
powerOffWarning: '警告:"{{name}}"正在打印中!确定要关闭吗?这将中断打印并可能损坏打印机。',
|
||||
powerOffButton: '关机',
|
||||
},
|
||||
// Discovery
|
||||
|
||||
+131
-151
@@ -1,5 +1,5 @@
|
||||
import { useState, useEffect, useMemo, useRef, useCallback } from 'react';
|
||||
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
|
||||
import { useQuery, useQueries, useMutation, useQueryClient } from '@tanstack/react-query';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useTheme } from '../contexts/ThemeContext';
|
||||
import { useAuth } from '../contexts/AuthContext';
|
||||
@@ -41,7 +41,6 @@ import {
|
||||
CheckCircle,
|
||||
XCircle,
|
||||
User,
|
||||
Home,
|
||||
Printer as PrinterIcon,
|
||||
Info,
|
||||
Cable,
|
||||
@@ -1554,8 +1553,8 @@ function PrinterCard({
|
||||
const [showEditModal, setShowEditModal] = useState(false);
|
||||
const [showFileManager, setShowFileManager] = useState(false);
|
||||
const [showMQTTDebug, setShowMQTTDebug] = useState(false);
|
||||
const [showPowerOnConfirm, setShowPowerOnConfirm] = useState(false);
|
||||
const [showPowerOffConfirm, setShowPowerOffConfirm] = useState(false);
|
||||
const [showPowerOnConfirm, setShowPowerOnConfirm] = useState<number | null>(null);
|
||||
const [showPowerOffConfirm, setShowPowerOffConfirm] = useState<number | null>(null);
|
||||
const [showHMSModal, setShowHMSModal] = useState(false);
|
||||
const [showStopConfirm, setShowStopConfirm] = useState(false);
|
||||
const [showPauseConfirm, setShowPauseConfirm] = useState(false);
|
||||
@@ -1780,24 +1779,19 @@ function PrinterCard({
|
||||
? currentTrayNow
|
||||
: cachedTrayNow.current;
|
||||
|
||||
// Fetch smart plug for this printer
|
||||
const { data: smartPlug } = useQuery({
|
||||
queryKey: ['smartPlugByPrinter', printer.id],
|
||||
queryFn: () => api.getSmartPlugByPrinter(printer.id),
|
||||
// Fetch smart plugs for this printer
|
||||
const { data: smartPlugs } = useQuery({
|
||||
queryKey: ['smartPlugsByPrinter', printer.id],
|
||||
queryFn: () => api.getAllSmartPlugsByPrinter(printer.id),
|
||||
});
|
||||
|
||||
// Fetch script plugs for this printer (for multi-device control)
|
||||
const { data: scriptPlugs } = useQuery({
|
||||
queryKey: ['scriptPlugsByPrinter', printer.id],
|
||||
queryFn: () => api.getScriptPlugsByPrinter(printer.id),
|
||||
});
|
||||
|
||||
// Fetch smart plug status if plug exists (faster refresh for energy monitoring)
|
||||
const { data: plugStatus } = useQuery({
|
||||
queryKey: ['smartPlugStatus', smartPlug?.id],
|
||||
queryFn: () => smartPlug ? api.getSmartPlugStatus(smartPlug.id) : null,
|
||||
enabled: !!smartPlug,
|
||||
refetchInterval: 10000, // 10 seconds for real-time power display
|
||||
// Fetch smart plug status for all plugs (faster refresh for energy monitoring)
|
||||
const plugStatusResults = useQueries({
|
||||
queries: (smartPlugs || []).map(plug => ({
|
||||
queryKey: ['smartPlugStatus', plug.id],
|
||||
queryFn: () => api.getSmartPlugStatus(plug.id),
|
||||
refetchInterval: 10000, // 10 seconds for real-time power display
|
||||
})),
|
||||
});
|
||||
|
||||
// Fetch queue count for this printer
|
||||
@@ -1892,26 +1886,26 @@ function PrinterCard({
|
||||
|
||||
// Smart plug control mutations
|
||||
const powerControlMutation = useMutation({
|
||||
mutationFn: (action: 'on' | 'off') =>
|
||||
smartPlug ? api.controlSmartPlug(smartPlug.id, action) : Promise.reject('No plug'),
|
||||
onSuccess: () => {
|
||||
queryClient.invalidateQueries({ queryKey: ['smartPlugStatus', smartPlug?.id] });
|
||||
mutationFn: ({ plugId, action }: { plugId: number; action: 'on' | 'off' }) =>
|
||||
api.controlSmartPlug(plugId, action),
|
||||
onSuccess: (_data, variables) => {
|
||||
queryClient.invalidateQueries({ queryKey: ['smartPlugsByPrinter', printer.id] });
|
||||
queryClient.invalidateQueries({ queryKey: ['smartPlugStatus', variables.plugId] });
|
||||
},
|
||||
});
|
||||
|
||||
const toggleAutoOffMutation = useMutation({
|
||||
mutationFn: (enabled: boolean) =>
|
||||
smartPlug ? api.updateSmartPlug(smartPlug.id, { auto_off: enabled }) : Promise.reject('No plug'),
|
||||
mutationFn: ({ plugId, enabled }: { plugId: number; enabled: boolean }) =>
|
||||
api.updateSmartPlug(plugId, { auto_off: enabled }),
|
||||
onSuccess: () => {
|
||||
queryClient.invalidateQueries({ queryKey: ['smartPlugByPrinter', printer.id] });
|
||||
// Also invalidate the smart-plugs list to keep Settings page in sync
|
||||
queryClient.invalidateQueries({ queryKey: ['smartPlugsByPrinter', printer.id] });
|
||||
queryClient.invalidateQueries({ queryKey: ['smart-plugs'] });
|
||||
},
|
||||
});
|
||||
|
||||
// Run HA entity mutation — scripts use 'on' (trigger), switches use 'toggle'
|
||||
// Run HA script mutation
|
||||
const runScriptMutation = useMutation({
|
||||
mutationFn: ({ id, action }: { id: number; action: 'on' | 'toggle' }) => api.controlSmartPlug(id, action),
|
||||
mutationFn: (id: number) => api.controlSmartPlug(id, 'on'),
|
||||
onSuccess: () => {
|
||||
showToast(t('printers.toast.scriptTriggered'));
|
||||
},
|
||||
@@ -3969,118 +3963,104 @@ function PrinterCard({
|
||||
)}
|
||||
|
||||
{/* Smart Plug Controls - hidden in compact mode */}
|
||||
{smartPlug && viewMode === 'expanded' && (
|
||||
<div className="mt-4 pt-4 border-t border-bambu-dark-tertiary">
|
||||
<div className="flex items-center gap-3">
|
||||
{/* Plug name and status */}
|
||||
<div className="flex items-center gap-2 min-w-0">
|
||||
<Zap className="w-4 h-4 text-bambu-gray flex-shrink-0" />
|
||||
<span className="text-sm text-white truncate">{smartPlug.name}</span>
|
||||
{plugStatus && (
|
||||
<span
|
||||
className={`text-xs px-1.5 py-0.5 rounded flex-shrink-0 ${
|
||||
plugStatus.state === 'ON'
|
||||
? 'bg-bambu-green/20 text-bambu-green'
|
||||
: plugStatus.state === 'OFF'
|
||||
? 'bg-red-500/20 text-red-400'
|
||||
: 'bg-bambu-gray/20 text-bambu-gray'
|
||||
}`}
|
||||
>
|
||||
{plugStatus.state || '?'}
|
||||
{plugStatus.state === 'ON' && plugStatus.energy?.power != null && (
|
||||
<span className="text-yellow-400 ml-1.5">· {plugStatus.energy.power}W</span>
|
||||
{smartPlugs && smartPlugs.length > 0 && viewMode === 'expanded' && (
|
||||
<div className="mt-4 pt-4 border-t border-bambu-dark-tertiary space-y-2">
|
||||
{smartPlugs.map((plug, index) => {
|
||||
const plugStatus = plugStatusResults[index]?.data;
|
||||
const isScript = plug.plug_type === 'homeassistant' && plug.ha_entity_id?.startsWith('script.');
|
||||
return (
|
||||
<div key={plug.id} className="flex items-center gap-3">
|
||||
<div className="flex items-center gap-2 min-w-0">
|
||||
<Zap className="w-4 h-4 text-bambu-gray flex-shrink-0" />
|
||||
<span className="text-sm text-white truncate">{plug.name}</span>
|
||||
{plugStatus && (
|
||||
<span className={`text-xs px-1.5 py-0.5 rounded flex-shrink-0 ${
|
||||
plugStatus.state === 'ON'
|
||||
? 'bg-bambu-green/20 text-bambu-green'
|
||||
: plugStatus.state === 'OFF'
|
||||
? 'bg-red-500/20 text-red-400'
|
||||
: 'bg-bambu-gray/20 text-bambu-gray'
|
||||
}`}>
|
||||
{plugStatus.state || '?'}
|
||||
{plugStatus.state === 'ON' && plugStatus.energy?.power != null && (
|
||||
<span className="text-yellow-400 ml-1.5">· {plugStatus.energy.power}W</span>
|
||||
)}
|
||||
</span>
|
||||
)}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Spacer */}
|
||||
<div className="flex-1" />
|
||||
|
||||
{/* Power buttons */}
|
||||
<div className="flex items-center gap-1">
|
||||
<button
|
||||
onClick={() => setShowPowerOnConfirm(true)}
|
||||
disabled={powerControlMutation.isPending || plugStatus?.state === 'ON' || !hasPermission('smart_plugs:control')}
|
||||
className={`px-2 py-1 text-xs rounded transition-colors flex items-center gap-1 ${
|
||||
!hasPermission('smart_plugs:control')
|
||||
? 'bg-bambu-dark text-bambu-gray/50 cursor-not-allowed'
|
||||
: plugStatus?.state === 'ON'
|
||||
? 'bg-bambu-green text-white'
|
||||
: 'bg-bambu-dark text-bambu-gray hover:text-white hover:bg-bambu-dark-tertiary'
|
||||
}`}
|
||||
title={!hasPermission('smart_plugs:control') ? t('printers.permission.noSmartPlugControl') : undefined}
|
||||
>
|
||||
<Power className="w-3 h-3" />
|
||||
On
|
||||
</button>
|
||||
<button
|
||||
onClick={() => setShowPowerOffConfirm(true)}
|
||||
disabled={powerControlMutation.isPending || plugStatus?.state === 'OFF' || !hasPermission('smart_plugs:control')}
|
||||
className={`px-2 py-1 text-xs rounded transition-colors flex items-center gap-1 ${
|
||||
!hasPermission('smart_plugs:control')
|
||||
? 'bg-bambu-dark text-bambu-gray/50 cursor-not-allowed'
|
||||
: plugStatus?.state === 'OFF'
|
||||
? 'bg-red-500/30 text-red-400'
|
||||
: 'bg-bambu-dark text-bambu-gray hover:text-white hover:bg-bambu-dark-tertiary'
|
||||
}`}
|
||||
title={!hasPermission('smart_plugs:control') ? t('printers.permission.noSmartPlugControl') : undefined}
|
||||
>
|
||||
<PowerOff className="w-3 h-3" />
|
||||
Off
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{/* Auto-off toggle */}
|
||||
<div className="flex items-center gap-2 flex-shrink-0">
|
||||
<span className={`text-xs hidden sm:inline ${smartPlug.auto_off_executed ? 'text-bambu-green' : 'text-bambu-gray'}`}>
|
||||
{smartPlug.auto_off_executed ? 'Auto-off done' : 'Auto-off'}
|
||||
</span>
|
||||
<button
|
||||
onClick={() => toggleAutoOffMutation.mutate(!smartPlug.auto_off)}
|
||||
disabled={toggleAutoOffMutation.isPending || smartPlug.auto_off_executed || !hasPermission('smart_plugs:control')}
|
||||
title={!hasPermission('smart_plugs:control') ? t('printers.permission.noSmartPlugControl') : (smartPlug.auto_off_executed ? t('printers.autoOffExecuted') : t('printers.autoOffAfterPrint'))}
|
||||
className={`relative w-9 h-5 rounded-full transition-colors flex-shrink-0 ${
|
||||
!hasPermission('smart_plugs:control')
|
||||
? 'bg-bambu-dark-tertiary/50 cursor-not-allowed'
|
||||
: smartPlug.auto_off_executed
|
||||
? 'bg-bambu-green/50 cursor-not-allowed'
|
||||
: smartPlug.auto_off ? 'bg-bambu-green' : 'bg-bambu-dark-tertiary'
|
||||
}`}
|
||||
>
|
||||
<span
|
||||
className={`absolute top-[2px] left-[2px] w-4 h-4 bg-white rounded-full transition-transform ${
|
||||
smartPlug.auto_off || smartPlug.auto_off_executed ? 'translate-x-4' : 'translate-x-0'
|
||||
}`}
|
||||
/>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* HA entity buttons row */}
|
||||
{scriptPlugs && scriptPlugs.length > 0 && (
|
||||
<div className="flex items-center gap-2 mt-2 pt-2 border-t border-bambu-dark-tertiary/50">
|
||||
<Home className="w-3.5 h-3.5 text-blue-400 flex-shrink-0" />
|
||||
<span className="text-xs text-bambu-gray">HA:</span>
|
||||
<div className="flex flex-wrap gap-1">
|
||||
{scriptPlugs.map(script => {
|
||||
const isScript = script.ha_entity_id?.startsWith('script.');
|
||||
return (
|
||||
<button
|
||||
key={script.id}
|
||||
onClick={() => runScriptMutation.mutate({ id: script.id, action: isScript ? 'on' : 'toggle' })}
|
||||
disabled={runScriptMutation.isPending}
|
||||
title={`${isScript ? 'Run' : 'Toggle'} ${script.ha_entity_id}`}
|
||||
className="px-2 py-0.5 text-xs bg-blue-500/20 text-blue-400 hover:bg-blue-500/30 rounded transition-colors flex items-center gap-1"
|
||||
>
|
||||
<Play className="w-2.5 h-2.5" />
|
||||
{script.name}
|
||||
</button>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
<div className="flex-1" />
|
||||
{isScript ? (
|
||||
<button
|
||||
onClick={() => runScriptMutation.mutate(plug.id)}
|
||||
disabled={runScriptMutation.isPending || !hasPermission('smart_plugs:control')}
|
||||
className="px-2 py-1 text-xs rounded transition-colors flex items-center gap-1 bg-blue-500/20 text-blue-400 hover:bg-blue-500/30"
|
||||
title={!hasPermission('smart_plugs:control') ? t('printers.permission.noSmartPlugControl') : 'Run script'}
|
||||
>
|
||||
<Play className="w-3 h-3" />
|
||||
Run
|
||||
</button>
|
||||
) : (
|
||||
<div className="flex items-center gap-1">
|
||||
<button
|
||||
onClick={() => setShowPowerOnConfirm(plug.id)}
|
||||
disabled={powerControlMutation.isPending || plugStatus?.state === 'ON' || !hasPermission('smart_plugs:control')}
|
||||
className={`px-2 py-1 text-xs rounded transition-colors flex items-center gap-1 ${
|
||||
!hasPermission('smart_plugs:control')
|
||||
? 'bg-bambu-dark text-bambu-gray/50 cursor-not-allowed'
|
||||
: plugStatus?.state === 'ON'
|
||||
? 'bg-bambu-green text-white'
|
||||
: 'bg-bambu-dark text-bambu-gray hover:text-white hover:bg-bambu-dark-tertiary'
|
||||
}`}
|
||||
title={!hasPermission('smart_plugs:control') ? t('printers.permission.noSmartPlugControl') : undefined}
|
||||
>
|
||||
<Power className="w-3 h-3" />
|
||||
On
|
||||
</button>
|
||||
<button
|
||||
onClick={() => setShowPowerOffConfirm(plug.id)}
|
||||
disabled={powerControlMutation.isPending || plugStatus?.state === 'OFF' || !hasPermission('smart_plugs:control')}
|
||||
className={`px-2 py-1 text-xs rounded transition-colors flex items-center gap-1 ${
|
||||
!hasPermission('smart_plugs:control')
|
||||
? 'bg-bambu-dark text-bambu-gray/50 cursor-not-allowed'
|
||||
: plugStatus?.state === 'OFF'
|
||||
? 'bg-red-500/30 text-red-400'
|
||||
: 'bg-bambu-dark text-bambu-gray hover:text-white hover:bg-bambu-dark-tertiary'
|
||||
}`}
|
||||
title={!hasPermission('smart_plugs:control') ? t('printers.permission.noSmartPlugControl') : undefined}
|
||||
>
|
||||
<PowerOff className="w-3 h-3" />
|
||||
Off
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
{!isScript && (
|
||||
<div className="flex items-center gap-2 flex-shrink-0">
|
||||
<span className={`text-xs hidden sm:inline ${plug.auto_off_executed ? 'text-bambu-green' : 'text-bambu-gray'}`}>
|
||||
{plug.auto_off_executed ? 'Auto-off done' : 'Auto-off'}
|
||||
</span>
|
||||
<button
|
||||
onClick={() => toggleAutoOffMutation.mutate({ plugId: plug.id, enabled: !plug.auto_off })}
|
||||
disabled={toggleAutoOffMutation.isPending || plug.auto_off_executed || !hasPermission('smart_plugs:control')}
|
||||
title={!hasPermission('smart_plugs:control') ? t('printers.permission.noSmartPlugControl') : (plug.auto_off_executed ? t('printers.autoOffExecuted') : t('printers.autoOffAfterPrint'))}
|
||||
className={`relative w-9 h-5 rounded-full transition-colors flex-shrink-0 ${
|
||||
!hasPermission('smart_plugs:control')
|
||||
? 'bg-bambu-dark-tertiary/50 cursor-not-allowed'
|
||||
: plug.auto_off_executed
|
||||
? 'bg-bambu-green/50 cursor-not-allowed'
|
||||
: plug.auto_off ? 'bg-bambu-green' : 'bg-bambu-dark-tertiary'
|
||||
}`}
|
||||
>
|
||||
<span
|
||||
className={`absolute top-[2px] left-[2px] w-4 h-4 bg-white rounded-full transition-transform ${
|
||||
plug.auto_off || plug.auto_off_executed ? 'translate-x-4' : 'translate-x-0'
|
||||
}`}
|
||||
/>
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
)}
|
||||
|
||||
@@ -4514,36 +4494,36 @@ function PrinterCard({
|
||||
)}
|
||||
|
||||
{/* Power On Confirmation */}
|
||||
{showPowerOnConfirm && smartPlug && (
|
||||
{showPowerOnConfirm !== null && (
|
||||
<ConfirmModal
|
||||
title={t('printers.confirm.powerOnTitle')}
|
||||
message={t('printers.confirm.powerOnMessage', { name: printer.name })}
|
||||
message={t('printers.confirm.powerOnMessage', { name: smartPlugs?.find(p => p.id === showPowerOnConfirm)?.name || '' })}
|
||||
confirmText={t('printers.confirm.powerOnButton')}
|
||||
variant="default"
|
||||
onConfirm={() => {
|
||||
powerControlMutation.mutate('on');
|
||||
setShowPowerOnConfirm(false);
|
||||
powerControlMutation.mutate({ plugId: showPowerOnConfirm, action: 'on' });
|
||||
setShowPowerOnConfirm(null);
|
||||
}}
|
||||
onCancel={() => setShowPowerOnConfirm(false)}
|
||||
onCancel={() => setShowPowerOnConfirm(null)}
|
||||
/>
|
||||
)}
|
||||
|
||||
{/* Power Off Confirmation */}
|
||||
{showPowerOffConfirm && smartPlug && (
|
||||
{showPowerOffConfirm !== null && (
|
||||
<ConfirmModal
|
||||
title={t('printers.confirm.powerOffTitle')}
|
||||
message={
|
||||
status?.state === 'RUNNING'
|
||||
? t('printers.confirm.powerOffWarning', { name: printer.name })
|
||||
: t('printers.confirm.powerOffMessage', { name: printer.name })
|
||||
? t('printers.confirm.powerOffWarning', { name: smartPlugs?.find(p => p.id === showPowerOffConfirm)?.name || '' })
|
||||
: t('printers.confirm.powerOffMessage', { name: smartPlugs?.find(p => p.id === showPowerOffConfirm)?.name || '' })
|
||||
}
|
||||
confirmText={t('printers.confirm.powerOffButton')}
|
||||
variant="danger"
|
||||
onConfirm={() => {
|
||||
powerControlMutation.mutate('off');
|
||||
setShowPowerOffConfirm(false);
|
||||
powerControlMutation.mutate({ plugId: showPowerOffConfirm, action: 'off' });
|
||||
setShowPowerOffConfirm(null);
|
||||
}}
|
||||
onCancel={() => setShowPowerOffConfirm(false)}
|
||||
onCancel={() => setShowPowerOffConfirm(null)}
|
||||
/>
|
||||
)}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user