mirror of https://github.com/jetkvm/kvm.git
Popovers and sidebar
This commit is contained in:
parent
474cb70e80
commit
985b53c02b
|
|
@ -6,6 +6,10 @@
|
|||
"jetkvm_logo": "JetKVM Logo",
|
||||
"load": "Load",
|
||||
"unknown_error": "Unknown error",
|
||||
|
||||
"close": "Close",
|
||||
"cancel": "Cancel",
|
||||
|
||||
"action_bar_virtual_media": "Virtual Media",
|
||||
"action_bar_paste_text": "Paste text",
|
||||
"action_bar_web_terminal": "Web Terminal",
|
||||
|
|
@ -16,6 +20,7 @@
|
|||
"action_bar_settings": "Settings",
|
||||
"action_bar_fullscreen": "Fullscreen",
|
||||
"action_bar_exit_fullscreen": "Exit Fullscreen",
|
||||
|
||||
"extensions_popover_extensions": "Extensions",
|
||||
"extension_popover_set_error_notification": "Failed to set active extension: {error}",
|
||||
"extension_popover_unload_extension": "Unload Extension",
|
||||
|
|
@ -26,6 +31,7 @@
|
|||
"extensions_dc_power_control_description": "Control your DC Power extension",
|
||||
"extension_serial_console": "Serial Console",
|
||||
"extension_serial_console_description": "Access your serial console extension",
|
||||
|
||||
"atx_power_control_get_state_error": "Failed to get ATX power state: {error}",
|
||||
"atx_power_control_send_action_error": "Failed to send ATX power action {action}: {error}",
|
||||
"atx_power_control_power_button": "Power",
|
||||
|
|
@ -34,6 +40,7 @@
|
|||
"atx_power_control_reset_button": "Reset",
|
||||
"atx_power_control_power_led": "Power LED",
|
||||
"atx_power_control_hdd_led": "HDD LED",
|
||||
|
||||
"dc_power_control_get_state_error": "Failed to get DC power state: {error}",
|
||||
"dc_power_control_set_power_state_error": "Failed to send DC power state to {enabled}: {error}",
|
||||
"dc_power_control_set_restore_state_error": "Failed to send DC power restore state to {state}: {error}",
|
||||
|
|
@ -49,6 +56,7 @@
|
|||
"dc_power_control_current_unit": "A",
|
||||
"dc_power_control_power": "Power",
|
||||
"dc_power_control_power_unit": "W",
|
||||
|
||||
"serial_console_get_settings_error": "Failed to get serial console settings: {error}",
|
||||
"serial_console_set_settings_error": "Failed to set serial console settings to {settings}: {error}",
|
||||
"serial_console_configure_description": "Configure your serial console settings",
|
||||
|
|
@ -61,5 +69,76 @@
|
|||
"serial_console_parity_odd": "Odd Parity",
|
||||
"serial_console_parity_none": "No Parity",
|
||||
"serial_console_parity_mark": "Mark Parity",
|
||||
"serial_console_parity_space": "Space Parity"
|
||||
"serial_console_parity_space": "Space Parity",
|
||||
|
||||
"wake_on_lan_add_device_device_name": "Device Name",
|
||||
"wake_on_lan_add_device_example_device_name": "Plex Media Server",
|
||||
"wake_on_lan_add_device_mac_address": "MAC Address",
|
||||
"wake_on_lan_add_device_back": "Back",
|
||||
"wake_on_lan_add_device_save_device": "Save Device",
|
||||
|
||||
"paste_modal_paste_text": "Paste text",
|
||||
"paste_modal_paste_text_description": "Paste text from your client to the remote host",
|
||||
"paste_modal_paste_from_host": "Paste from host",
|
||||
"paste_modal_invalid_chars_intro": "The following characters won't be pasted:",
|
||||
"paste_modal_delay_between_keys": "Delay between keys",
|
||||
"paste_modal_delay_out_of_range": "Delay must be between {min} and {max}",
|
||||
"paste_modal_sending_using_layout": "Sending text using keyboard layout: {iso}-{name}",
|
||||
"paste_modal_confirm_paste": "Confirm Paste",
|
||||
|
||||
"mount_virtual_media": "Virtual Media",
|
||||
"mount_virtual_media_description": "Mount an image to boot from or install an operating system.",
|
||||
"mount_no_mounted_media": "No mounted media",
|
||||
"mount_add_file_to_get_started": "Add a file to get started",
|
||||
"mount_streaming_from_url": "Streaming from URL",
|
||||
"mount_mounted_from_storage": "Mounted from JetKVM Storage",
|
||||
"mount_unmount": "Unmount",
|
||||
"mount_add_new_media": "Add New Media",
|
||||
|
||||
"mount_get_state_error": "Failed to get virtual media state: {error}",
|
||||
"mount_unmount_error": "Failed to unmount image: {error}",
|
||||
"mount_mounted_as": "Mounted as",
|
||||
"mount_mode_disk": "Disk",
|
||||
"mount_mode_cdrom": "CD-ROM",
|
||||
|
||||
"wake_on_lan": "Wake On LAN",
|
||||
"wake_on_lan_description": "Send a Magic Packet to wake up a remote device.",
|
||||
|
||||
"wake_on_lan_invalid_mac": "Invalid MAC address",
|
||||
"wake_on_lan_failed_send_magic": "Failed to send Magic Packet",
|
||||
"wake_on_lan_magic_sent_success": "Magic Packet sent successfully",
|
||||
|
||||
"wake_on_lan_failed_add_device": "Failed to add device",
|
||||
|
||||
"wake_on_lan_empty_no_devices_added": "No devices added",
|
||||
"wake_on_lan_empty_add_device_to_start": "Add a device to start using Wake-on-LAN",
|
||||
"wake_on_lan_empty_add_new_device": "Add New Device",
|
||||
|
||||
"wake_on_lan_device_list_wake": "Wake",
|
||||
"wake_on_lan_device_list_delete_device": "Delete device",
|
||||
"wake_on_lan_device_list_add_new_device": "Add New Device",
|
||||
|
||||
"connection_stats_sidebar": "Connection Stats",
|
||||
|
||||
"connection_stats_connection": "Connection",
|
||||
"connection_stats_connection_description": "The connection between the client and the JetKVM.",
|
||||
"connection_stats_round_trip_time": "Round-Trip Time",
|
||||
"connection_stats_round_trip_time_description": "Round-trip time for the active ICE candidate pair between peers.",
|
||||
|
||||
"connection_stats_video": "Video",
|
||||
"connection_stats_video_description": "The video stream from the JetKVM to the client.",
|
||||
|
||||
"connection_stats_network_stability": "Network Stability",
|
||||
"connection_stats_network_stability_description": "How steady the flow of inbound video packets is across the network.",
|
||||
"connection_stats_badge_jitter": "Jitter",
|
||||
|
||||
"connection_stats_playback_delay": "Playback Delay",
|
||||
"connection_stats_playback_delay_description": "Delay added by the jitter buffer to smooth playback when frames arrive unevenly.",
|
||||
"connection_stats_badge_jitter_buffer_avg_delay": "Jitter Buffer Avg. Delay",
|
||||
|
||||
"connection_stats_packets_lost": "Packets Lost",
|
||||
"connection_stats_packets_lost_description": "Count of lost inbound video RTP packets.",
|
||||
|
||||
"connection_stats_frames_per_second": "Frames per second",
|
||||
"connection_stats_frames_per_second_description": "Number of inbound video frames displayed per second."
|
||||
}
|
||||
|
|
@ -1,20 +1,17 @@
|
|||
import { PlusCircleIcon } from "@heroicons/react/20/solid";
|
||||
import { forwardRef, useEffect, useCallback } from "react";
|
||||
import {
|
||||
LuLink,
|
||||
LuPlus,
|
||||
LuRadioReceiver,
|
||||
} from "react-icons/lu";
|
||||
import { LuLink, LuPlus, LuRadioReceiver } from "react-icons/lu";
|
||||
import { useClose } from "@headlessui/react";
|
||||
import { useLocation } from "react-router";
|
||||
|
||||
import { m } from "@localizations/messages.js";
|
||||
import { Button } from "@components/Button";
|
||||
import Card, { GridCard } from "@components/Card";
|
||||
import { formatters } from "@/utils";
|
||||
import { RemoteVirtualMediaState, useMountMediaStore } from "@/hooks/stores";
|
||||
import { RemoteVirtualMediaState, useMountMediaStore } from "@hooks/stores";
|
||||
import { SettingsPageHeader } from "@components/SettingsPageheader";
|
||||
import { JsonRpcResponse, useJsonRpc } from "@/hooks/useJsonRpc";
|
||||
import { useDeviceUiNavigation } from "@/hooks/useAppNavigation";
|
||||
import { JsonRpcResponse, useJsonRpc } from "@hooks/useJsonRpc";
|
||||
import { useDeviceUiNavigation } from "@hooks/useAppNavigation";
|
||||
import notifications from "@/notifications";
|
||||
|
||||
const MountPopopover = forwardRef<HTMLDivElement, object>((_props, ref) => {
|
||||
|
|
@ -25,9 +22,7 @@ const MountPopopover = forwardRef<HTMLDivElement, object>((_props, ref) => {
|
|||
const syncRemoteVirtualMediaState = useCallback(() => {
|
||||
send("getVirtualMediaState", {}, (response: JsonRpcResponse) => {
|
||||
if ("error" in response) {
|
||||
notifications.error(
|
||||
`Failed to get virtual media state: ${response.error.message}`,
|
||||
);
|
||||
notifications.error(m.mount_get_state_error({ error: response.error.message }));
|
||||
} else {
|
||||
setRemoteVirtualMediaState(response.result as unknown as RemoteVirtualMediaState);
|
||||
}
|
||||
|
|
@ -37,7 +32,7 @@ const MountPopopover = forwardRef<HTMLDivElement, object>((_props, ref) => {
|
|||
const handleUnmount = () => {
|
||||
send("unmountImage", {}, (response: JsonRpcResponse) => {
|
||||
if ("error" in response) {
|
||||
notifications.error(`Failed to unmount image: ${response.error.message}`);
|
||||
notifications.error(m.mount_unmount_error({ error: response.error.message }));
|
||||
} else {
|
||||
syncRemoteVirtualMediaState();
|
||||
}
|
||||
|
|
@ -57,10 +52,10 @@ const MountPopopover = forwardRef<HTMLDivElement, object>((_props, ref) => {
|
|||
</div>
|
||||
<div className="space-y-1">
|
||||
<h3 className="text-sm font-semibold leading-none text-black dark:text-white">
|
||||
No mounted media
|
||||
{m.mount_no_mounted_media()}
|
||||
</h3>
|
||||
<p className="text-xs leading-none text-slate-700 dark:text-slate-300">
|
||||
Add a file to get started
|
||||
{m.mount_add_file_to_get_started()}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -81,7 +76,7 @@ const MountPopopover = forwardRef<HTMLDivElement, object>((_props, ref) => {
|
|||
</Card>
|
||||
</div>
|
||||
<h3 className="text-base font-semibold text-black dark:text-white">
|
||||
Streaming from URL
|
||||
{m.mount_streaming_from_url()}
|
||||
</h3>
|
||||
<p className="truncate text-sm text-slate-900 dark:text-slate-100">
|
||||
{formatters.truncateMiddle(url, 55)}
|
||||
|
|
@ -105,7 +100,7 @@ const MountPopopover = forwardRef<HTMLDivElement, object>((_props, ref) => {
|
|||
</Card>
|
||||
</div>
|
||||
<h3 className="text-base font-semibold text-black dark:text-white">
|
||||
Mounted from JetKVM Storage
|
||||
{m.mount_mounted_from_storage()}
|
||||
</h3>
|
||||
<p className="text-sm text-slate-900 dark:text-slate-100">
|
||||
{formatters.truncateMiddle(path, 50)}
|
||||
|
|
@ -138,8 +133,8 @@ const MountPopopover = forwardRef<HTMLDivElement, object>((_props, ref) => {
|
|||
<div className="h-full space-y-4">
|
||||
<div className="space-y-4">
|
||||
<SettingsPageHeader
|
||||
title="Virtual Media"
|
||||
description="Mount an image to boot from or install an operating system."
|
||||
title={m.mount_virtual_media()}
|
||||
description={m.mount_virtual_media_description()}
|
||||
/>
|
||||
|
||||
<div
|
||||
|
|
@ -162,10 +157,10 @@ const MountPopopover = forwardRef<HTMLDivElement, object>((_props, ref) => {
|
|||
</div>
|
||||
{remoteVirtualMediaState ? (
|
||||
<div className="flex select-none items-center justify-between text-xs">
|
||||
<div className="select-none text-white dark:text-slate-300">
|
||||
<span>Mounted as</span>{" "}
|
||||
<div className="select-none text-white dark:text-slate-300">
|
||||
<span>{m.mount_mounted_as()}</span>{" "}
|
||||
<span className="font-semibold">
|
||||
{remoteVirtualMediaState.mode === "Disk" ? "Disk" : "CD-ROM"}
|
||||
{remoteVirtualMediaState.mode === "Disk" ? m.mount_mode_disk() : m.mount_mode_cdrom()}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
|
|
@ -173,7 +168,7 @@ const MountPopopover = forwardRef<HTMLDivElement, object>((_props, ref) => {
|
|||
<Button
|
||||
size="SM"
|
||||
theme="blank"
|
||||
text="Close"
|
||||
text={m.close()}
|
||||
onClick={() => {
|
||||
close();
|
||||
}}
|
||||
|
|
@ -181,7 +176,7 @@ const MountPopopover = forwardRef<HTMLDivElement, object>((_props, ref) => {
|
|||
<Button
|
||||
size="SM"
|
||||
theme="light"
|
||||
text="Unmount"
|
||||
text={m.mount_unmount()}
|
||||
LeadingIcon={({ className }) => (
|
||||
<svg
|
||||
className={`${className} h-2.5 w-2.5 shrink-0`}
|
||||
|
|
@ -227,7 +222,7 @@ const MountPopopover = forwardRef<HTMLDivElement, object>((_props, ref) => {
|
|||
<Button
|
||||
size="SM"
|
||||
theme="blank"
|
||||
text="Close"
|
||||
text={m.close()}
|
||||
onClick={() => {
|
||||
close();
|
||||
}}
|
||||
|
|
@ -235,7 +230,7 @@ const MountPopopover = forwardRef<HTMLDivElement, object>((_props, ref) => {
|
|||
<Button
|
||||
size="SM"
|
||||
theme="primary"
|
||||
text="Add New Media"
|
||||
text={m.mount_add_new_media()}
|
||||
onClick={() => {
|
||||
setModalView("mode");
|
||||
navigateTo("/mount");
|
||||
|
|
|
|||
|
|
@ -1,13 +1,14 @@
|
|||
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
||||
import { useClose } from "@headlessui/react";
|
||||
import { ExclamationCircleIcon } from "@heroicons/react/16/solid";
|
||||
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
||||
import { LuCornerDownLeft } from "react-icons/lu";
|
||||
|
||||
import { cx } from "@/cva.config";
|
||||
import { useHidStore, useSettingsStore, useUiStore } from "@/hooks/stores";
|
||||
import { JsonRpcResponse, useJsonRpc } from "@/hooks/useJsonRpc";
|
||||
import useKeyboard, { type MacroStep } from "@/hooks/useKeyboard";
|
||||
import useKeyboardLayout from "@/hooks/useKeyboardLayout";
|
||||
import { m } from "@localizations/messages.js";
|
||||
import { useHidStore, useSettingsStore, useUiStore } from "@hooks/stores";
|
||||
import { JsonRpcResponse, useJsonRpc } from "@hooks/useJsonRpc";
|
||||
import useKeyboard, { type MacroStep } from "@hooks/useKeyboard";
|
||||
import useKeyboardLayout from "@hooks/useKeyboardLayout";
|
||||
import notifications from "@/notifications";
|
||||
import { Button } from "@components/Button";
|
||||
import { GridCard } from "@components/Card";
|
||||
|
|
@ -122,8 +123,8 @@ export default function PasteModal() {
|
|||
<div className="h-full space-y-4">
|
||||
<div className="space-y-4">
|
||||
<SettingsPageHeader
|
||||
title="Paste text"
|
||||
description="Paste text from your client to the remote host"
|
||||
title={m.paste_modal_paste_text()}
|
||||
description={m.paste_modal_paste_text_description()}
|
||||
/>
|
||||
|
||||
<div
|
||||
|
|
@ -143,7 +144,7 @@ export default function PasteModal() {
|
|||
>
|
||||
<TextAreaWithLabel
|
||||
ref={TextAreaRef}
|
||||
label="Paste from host"
|
||||
label={m.paste_modal_paste_from_host()}
|
||||
rows={4}
|
||||
onKeyUp={e => e.stopPropagation()}
|
||||
maxLength={pasteMaxLength}
|
||||
|
|
@ -176,7 +177,7 @@ export default function PasteModal() {
|
|||
<div className="mt-2 flex items-center gap-x-2">
|
||||
<ExclamationCircleIcon className="h-4 w-4 text-red-500 dark:text-red-400" />
|
||||
<span className="text-xs text-red-500 dark:text-red-400">
|
||||
The following characters won't be pasted:{" "}
|
||||
{m.paste_modal_invalid_chars_intro()}{" "}
|
||||
{invalidChars.join(", ")}
|
||||
</span>
|
||||
</div>
|
||||
|
|
@ -186,8 +187,8 @@ export default function PasteModal() {
|
|||
<div className={cx("text-xs text-slate-600 dark:text-slate-400", delayClassName)}>
|
||||
<InputFieldWithLabel
|
||||
type="number"
|
||||
label="Delay between keys"
|
||||
placeholder="Delay between keys"
|
||||
label={m.paste_modal_delay_between_keys()}
|
||||
placeholder={m.paste_modal_delay_between_keys()}
|
||||
min={50}
|
||||
max={65534}
|
||||
value={delayValue}
|
||||
|
|
@ -199,15 +200,14 @@ export default function PasteModal() {
|
|||
<div className="mt-2 flex items-center gap-x-2">
|
||||
<ExclamationCircleIcon className="h-4 w-4 text-red-500 dark:text-red-400" />
|
||||
<span className="text-xs text-red-500 dark:text-red-400">
|
||||
Delay must be between 50 and 65534
|
||||
{m.paste_modal_delay_out_of_range({ min: 50, max: 65534 })}
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<div className="space-y-4">
|
||||
<p className="text-xs text-slate-600 dark:text-slate-400">
|
||||
Sending text using keyboard layout: {selectedKeyboard.isoCode}-
|
||||
{selectedKeyboard.name}
|
||||
{m.paste_modal_sending_using_layout({ iso: selectedKeyboard.isoCode, name: selectedKeyboard.name })}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -224,7 +224,7 @@ export default function PasteModal() {
|
|||
<Button
|
||||
size="SM"
|
||||
theme="blank"
|
||||
text="Cancel"
|
||||
text={m.cancel()}
|
||||
onClick={() => {
|
||||
onCancelPasteMode();
|
||||
close();
|
||||
|
|
@ -233,7 +233,7 @@ export default function PasteModal() {
|
|||
<Button
|
||||
size="SM"
|
||||
theme="primary"
|
||||
text="Confirm Paste"
|
||||
text={m.paste_modal_confirm_paste()}
|
||||
disabled={isPasteInProgress}
|
||||
onClick={onConfirmPaste}
|
||||
LeadingIcon={LuCornerDownLeft}
|
||||
|
|
|
|||
|
|
@ -1,8 +1,9 @@
|
|||
import { useState, useRef } from "react";
|
||||
import { LuPlus, LuArrowLeft } from "react-icons/lu";
|
||||
|
||||
import { InputFieldWithLabel } from "@/components/InputField";
|
||||
import { Button } from "@/components/Button";
|
||||
import { m } from "@localizations/messages.js";
|
||||
import { InputFieldWithLabel } from "@components/InputField";
|
||||
import { Button } from "@components/Button";
|
||||
|
||||
interface AddDeviceFormProps {
|
||||
onAddDevice: (name: string, macAddress: string) => void;
|
||||
|
|
@ -34,8 +35,8 @@ export default function AddDeviceForm({
|
|||
>
|
||||
<InputFieldWithLabel
|
||||
ref={nameInputRef}
|
||||
placeholder="Plex Media Server"
|
||||
label="Device Name"
|
||||
placeholder={m.wake_on_lan_add_device_example_device_name()}
|
||||
label={m.wake_on_lan_add_device_device_name()}
|
||||
required
|
||||
onChange={e => {
|
||||
setIsDeviceNameValid(e.target.validity.valid);
|
||||
|
|
@ -46,7 +47,7 @@ export default function AddDeviceForm({
|
|||
<InputFieldWithLabel
|
||||
ref={macInputRef}
|
||||
placeholder="00:b0:d0:63:c2:26"
|
||||
label="MAC Address"
|
||||
label={m.wake_on_lan_add_device_mac_address()}
|
||||
onKeyUp={e => e.stopPropagation()}
|
||||
required
|
||||
pattern="^([0-9a-fA-F][0-9a-fA-F]:){5}([0-9a-fA-F][0-9a-fA-F])$"
|
||||
|
|
@ -82,14 +83,14 @@ export default function AddDeviceForm({
|
|||
<Button
|
||||
size="SM"
|
||||
theme="light"
|
||||
text="Back"
|
||||
text={m.wake_on_lan_add_device_back()}
|
||||
LeadingIcon={LuArrowLeft}
|
||||
onClick={() => setShowAddForm(false)}
|
||||
/>
|
||||
<Button
|
||||
size="SM"
|
||||
theme="primary"
|
||||
text="Save Device"
|
||||
text={m.wake_on_lan_add_device_save_device()}
|
||||
disabled={!isDeviceNameValid || !isMacAddressValid}
|
||||
onClick={() => {
|
||||
const deviceName = nameInputRef.current?.value || "";
|
||||
|
|
|
|||
|
|
@ -1,8 +1,9 @@
|
|||
import { LuPlus, LuSend, LuTrash2 } from "react-icons/lu";
|
||||
|
||||
import { Button } from "@/components/Button";
|
||||
import Card from "@/components/Card";
|
||||
import { FieldError } from "@/components/InputField";
|
||||
import { m } from "@localizations/messages.js";
|
||||
import { Button } from "@components/Button";
|
||||
import Card from "@components/Card";
|
||||
import { FieldError } from "@components/InputField";
|
||||
|
||||
export interface StoredDevice {
|
||||
name: string;
|
||||
|
|
@ -46,7 +47,7 @@ export default function DeviceList({
|
|||
<Button
|
||||
size="XS"
|
||||
theme="light"
|
||||
text="Wake"
|
||||
text={m.wake_on_lan_device_list_wake()}
|
||||
LeadingIcon={LuSend}
|
||||
onClick={() => onSendMagicPacket(device.macAddress)}
|
||||
/>
|
||||
|
|
@ -55,7 +56,7 @@ export default function DeviceList({
|
|||
theme="danger"
|
||||
LeadingIcon={LuTrash2}
|
||||
onClick={() => onDeleteDevice(index)}
|
||||
aria-label="Delete device"
|
||||
aria-label={m.wake_on_lan_device_list_delete_device()}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -69,11 +70,11 @@ export default function DeviceList({
|
|||
animationDelay: "0.2s",
|
||||
}}
|
||||
>
|
||||
<Button size="SM" theme="blank" text="Close" onClick={onCancelWakeOnLanModal} />
|
||||
<Button size="SM" theme="blank" text={m.close()} onClick={onCancelWakeOnLanModal} />
|
||||
<Button
|
||||
size="SM"
|
||||
theme="primary"
|
||||
text="Add New Device"
|
||||
text={m.wake_on_lan_device_list_add_new_device()}
|
||||
onClick={() => setShowAddForm(true)}
|
||||
LeadingIcon={LuPlus}
|
||||
/>
|
||||
|
|
|
|||
|
|
@ -1,8 +1,9 @@
|
|||
import { PlusCircleIcon } from "@heroicons/react/16/solid";
|
||||
import { LuPlus } from "react-icons/lu";
|
||||
|
||||
import Card from "@/components/Card";
|
||||
import { Button } from "@/components/Button";
|
||||
import { m } from "@localizations/messages.js";
|
||||
import Card from "@components/Card";
|
||||
import { Button } from "@components/Button";
|
||||
|
||||
export default function EmptyStateCard({
|
||||
onCancelWakeOnLanModal,
|
||||
|
|
@ -25,10 +26,10 @@ export default function EmptyStateCard({
|
|||
</Card>
|
||||
</div>
|
||||
<h3 className="text-sm font-semibold leading-none text-black dark:text-white">
|
||||
No devices added
|
||||
{m.wake_on_lan_empty_no_devices_added()}
|
||||
</h3>
|
||||
<p className="text-xs leading-none text-slate-700 dark:text-slate-300">
|
||||
Add a device to start using Wake-on-LAN
|
||||
{m.wake_on_lan_empty_add_device_to_start()}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -41,11 +42,11 @@ export default function EmptyStateCard({
|
|||
animationDelay: "0.2s",
|
||||
}}
|
||||
>
|
||||
<Button size="SM" theme="blank" text="Close" onClick={onCancelWakeOnLanModal} />
|
||||
<Button size="SM" theme="blank" text={m.close()} onClick={onCancelWakeOnLanModal} />
|
||||
<Button
|
||||
size="SM"
|
||||
theme="primary"
|
||||
text="Add New Device"
|
||||
text={m.wake_on_lan_empty_add_new_device()}
|
||||
onClick={() => setShowAddForm(true)}
|
||||
LeadingIcon={LuPlus}
|
||||
/>
|
||||
|
|
|
|||
|
|
@ -1,10 +1,11 @@
|
|||
import { useCallback, useEffect, useState } from "react";
|
||||
import { useClose } from "@headlessui/react";
|
||||
|
||||
import { m } from "@localizations/messages.js";
|
||||
import { GridCard } from "@components/Card";
|
||||
import { SettingsPageHeader } from "@components/SettingsPageheader";
|
||||
import { JsonRpcResponse, useJsonRpc } from "@/hooks/useJsonRpc";
|
||||
import { useRTCStore, useUiStore } from "@/hooks/stores";
|
||||
import { JsonRpcResponse, useJsonRpc } from "@hooks/useJsonRpc";
|
||||
import { useRTCStore, useUiStore } from "@hooks/stores";
|
||||
import notifications from "@/notifications";
|
||||
|
||||
import EmptyStateCard from "./EmptyStateCard";
|
||||
|
|
@ -35,12 +36,12 @@ export default function WakeOnLanModal() {
|
|||
if ("error" in resp) {
|
||||
const isInvalid = resp.error.data?.includes("invalid MAC address");
|
||||
if (isInvalid) {
|
||||
setErrorMessage("Invalid MAC address");
|
||||
setErrorMessage(m.wake_on_lan_invalid_mac());
|
||||
} else {
|
||||
setErrorMessage("Failed to send Magic Packet");
|
||||
setErrorMessage(m.wake_on_lan_failed_send_magic());
|
||||
}
|
||||
} else {
|
||||
notifications.success("Magic Packet sent successfully");
|
||||
notifications.success(m.wake_on_lan_magic_sent_success());
|
||||
setDisableVideoFocusTrap(false);
|
||||
close();
|
||||
}
|
||||
|
|
@ -87,7 +88,7 @@ export default function WakeOnLanModal() {
|
|||
send("setWakeOnLanDevices", { params: { devices: updatedDevices } }, (resp: JsonRpcResponse) => {
|
||||
if ("error" in resp) {
|
||||
console.error("Failed to add Wake-on-LAN device:", resp.error);
|
||||
setAddDeviceErrorMessage("Failed to add device");
|
||||
setAddDeviceErrorMessage(m.wake_on_lan_failed_add_device());
|
||||
} else {
|
||||
setShowAddForm(false);
|
||||
syncStoredDevices();
|
||||
|
|
@ -103,8 +104,8 @@ export default function WakeOnLanModal() {
|
|||
<div className="grid h-full grid-rows-(--grid-headerBody)">
|
||||
<div className="space-y-4">
|
||||
<SettingsPageHeader
|
||||
title="Wake On LAN"
|
||||
description="Send a Magic Packet to wake up a remote device."
|
||||
title={m.wake_on_lan()}
|
||||
description={m.wake_on_lan_description()}
|
||||
/>
|
||||
|
||||
{showAddForm ? (
|
||||
|
|
|
|||
|
|
@ -1,12 +1,12 @@
|
|||
import { useInterval } from "usehooks-ts";
|
||||
|
||||
import SidebarHeader from "@/components/SidebarHeader";
|
||||
import { useRTCStore, useUiStore } from "@/hooks/stores";
|
||||
import { m } from "@localizations/messages.js";
|
||||
import { createChartArray, Metric } from "@components/Metric";
|
||||
import { SettingsSectionHeader } from "@components/SettingsSectionHeader";
|
||||
import SidebarHeader from "@components/SidebarHeader";
|
||||
import { useRTCStore, useUiStore } from "@hooks/stores";
|
||||
import { someIterable } from "@/utils";
|
||||
|
||||
import { createChartArray, Metric } from "../Metric";
|
||||
import { SettingsSectionHeader } from "../SettingsSectionHeader";
|
||||
|
||||
export default function ConnectionStatsSidebar() {
|
||||
const { sidebarView, setSidebarView } = useUiStore();
|
||||
const {
|
||||
|
|
@ -95,7 +95,7 @@ export default function ConnectionStatsSidebar() {
|
|||
|
||||
return (
|
||||
<div className="grid h-full grid-rows-(--grid-headerBody) shadow-xs">
|
||||
<SidebarHeader title="Connection Stats" setSidebarView={setSidebarView} />
|
||||
<SidebarHeader title={m.connection_stats_sidebar()} setSidebarView={setSidebarView} />
|
||||
<div className="h-full space-y-4 overflow-y-scroll bg-white px-4 py-2 pb-8 dark:bg-slate-900">
|
||||
<div className="space-y-4">
|
||||
{sidebarView === "connection-stats" && (
|
||||
|
|
@ -103,12 +103,12 @@ export default function ConnectionStatsSidebar() {
|
|||
{/* Connection Group */}
|
||||
<div className="space-y-3">
|
||||
<SettingsSectionHeader
|
||||
title="Connection"
|
||||
description="The connection between the client and the JetKVM."
|
||||
title={m.connection_stats_connection()}
|
||||
description={m.connection_stats_connection_description()}
|
||||
/>
|
||||
<Metric
|
||||
title="Round-Trip Time"
|
||||
description="Round-trip time for the active ICE candidate pair between peers."
|
||||
title={m.connection_stats_round_trip_time()}
|
||||
description={m.connection_stats_round_trip_time_description()}
|
||||
stream={iceCandidatePairStats}
|
||||
metric="currentRoundTripTime"
|
||||
map={x => ({
|
||||
|
|
@ -123,16 +123,16 @@ export default function ConnectionStatsSidebar() {
|
|||
{/* Video Group */}
|
||||
<div className="space-y-3">
|
||||
<SettingsSectionHeader
|
||||
title="Video"
|
||||
description="The video stream from the JetKVM to the client."
|
||||
title={m.connection_stats_video()}
|
||||
description={m.connection_stats_video_description()}
|
||||
/>
|
||||
|
||||
{/* RTP Jitter */}
|
||||
<Metric
|
||||
title="Network Stability"
|
||||
badge="Jitter"
|
||||
title={m.connection_stats_network_stability()}
|
||||
badge={m.connection_stats_badge_jitter()}
|
||||
badgeTheme="light"
|
||||
description="How steady the flow of inbound video packets is across the network."
|
||||
description={m.connection_stats_network_stability_description()}
|
||||
stream={inboundVideoRtpStats}
|
||||
metric="jitter"
|
||||
map={x => ({
|
||||
|
|
@ -145,9 +145,9 @@ export default function ConnectionStatsSidebar() {
|
|||
|
||||
{/* Playback Delay */}
|
||||
<Metric
|
||||
title="Playback Delay"
|
||||
description="Delay added by the jitter buffer to smooth playback when frames arrive unevenly."
|
||||
badge="Jitter Buffer Avg. Delay"
|
||||
title={m.connection_stats_playback_delay()}
|
||||
description={m.connection_stats_playback_delay_description()}
|
||||
badge={m.connection_stats_badge_jitter_buffer_avg_delay()}
|
||||
badgeTheme="light"
|
||||
data={jitterBufferAvgDelayData}
|
||||
gate={inboundVideoRtpStats}
|
||||
|
|
@ -167,8 +167,8 @@ export default function ConnectionStatsSidebar() {
|
|||
|
||||
{/* Packets Lost */}
|
||||
<Metric
|
||||
title="Packets Lost"
|
||||
description="Count of lost inbound video RTP packets."
|
||||
title={m.connection_stats_packets_lost()}
|
||||
description={m.connection_stats_packets_lost_description()}
|
||||
stream={inboundVideoRtpStats}
|
||||
metric="packetsLost"
|
||||
domain={[0, 100]}
|
||||
|
|
@ -177,8 +177,8 @@ export default function ConnectionStatsSidebar() {
|
|||
|
||||
{/* Frames Per Second */}
|
||||
<Metric
|
||||
title="Frames per second"
|
||||
description="Number of inbound video frames displayed per second."
|
||||
title={m.connection_stats_frames_per_second()}
|
||||
description={m.connection_stats_frames_per_second_description()}
|
||||
stream={inboundVideoRtpStats}
|
||||
metric="framesPerSecond"
|
||||
domain={[0, 80]}
|
||||
|
|
|
|||
Loading…
Reference in New Issue