mirror of https://github.com/jetkvm/kvm.git
Merge branch 'dev' into chore/es-lint-redux
This commit is contained in:
commit
02ba560027
4
Makefile
4
Makefile
|
|
@ -2,8 +2,8 @@ BRANCH := $(shell git rev-parse --abbrev-ref HEAD)
|
||||||
BUILDDATE := $(shell date -u +%FT%T%z)
|
BUILDDATE := $(shell date -u +%FT%T%z)
|
||||||
BUILDTS := $(shell date -u +%s)
|
BUILDTS := $(shell date -u +%s)
|
||||||
REVISION := $(shell git rev-parse HEAD)
|
REVISION := $(shell git rev-parse HEAD)
|
||||||
VERSION_DEV := 0.4.9-dev$(shell date +%Y%m%d%H%M)
|
VERSION_DEV := 0.5.0-dev$(shell date +%Y%m%d%H%M)
|
||||||
VERSION := 0.4.8
|
VERSION := 0.4.9
|
||||||
|
|
||||||
PROMETHEUS_TAG := github.com/prometheus/common/version
|
PROMETHEUS_TAG := github.com/prometheus/common/version
|
||||||
KVM_PKG_NAME := github.com/jetkvm/kvm
|
KVM_PKG_NAME := github.com/jetkvm/kvm
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,8 @@ import (
|
||||||
"os"
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
|
"github.com/jetkvm/kvm/internal/supervisor"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
|
@ -78,6 +80,10 @@ func checkFailsafeReason() {
|
||||||
|
|
||||||
// TODO: read the goroutine stack trace and check which goroutine is panicking
|
// TODO: read the goroutine stack trace and check which goroutine is panicking
|
||||||
failsafeModeActive = true
|
failsafeModeActive = true
|
||||||
|
if strings.Contains(failsafeCrashLog, supervisor.FailsafeReasonVideoMaxRestartAttemptsReached) {
|
||||||
|
failsafeModeReason = "video"
|
||||||
|
return
|
||||||
|
}
|
||||||
if strings.Contains(failsafeCrashLog, "runtime.cgocall") {
|
if strings.Contains(failsafeCrashLog, "runtime.cgocall") {
|
||||||
failsafeModeReason = "video"
|
failsafeModeReason = "video"
|
||||||
return
|
return
|
||||||
|
|
|
||||||
|
|
@ -79,6 +79,18 @@ func NewGRPCClient(opts grpcClientOptions) (*GRPCClient, error) {
|
||||||
// Start event stream
|
// Start event stream
|
||||||
go grpcClient.startEventStream()
|
go grpcClient.startEventStream()
|
||||||
|
|
||||||
|
// Start event handler to process events from the channel
|
||||||
|
go func() {
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case event := <-grpcClient.eventCh:
|
||||||
|
grpcClient.handleEvent(event)
|
||||||
|
case <-grpcClient.eventDone:
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
return grpcClient, nil
|
return grpcClient, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -234,20 +246,6 @@ func (c *GRPCClient) handleEvent(event *pb.Event) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// OnEvent registers an event handler
|
|
||||||
func (c *GRPCClient) OnEvent(eventType string, handler func(data interface{})) {
|
|
||||||
go func() {
|
|
||||||
for {
|
|
||||||
select {
|
|
||||||
case event := <-c.eventCh:
|
|
||||||
c.handleEvent(event)
|
|
||||||
case <-c.eventDone:
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Close closes the gRPC client
|
// Close closes the gRPC client
|
||||||
func (c *GRPCClient) Close() error {
|
func (c *GRPCClient) Close() error {
|
||||||
c.closeM.Lock()
|
c.closeM.Lock()
|
||||||
|
|
|
||||||
|
|
@ -17,8 +17,10 @@ type grpcServer struct {
|
||||||
pb.UnimplementedNativeServiceServer
|
pb.UnimplementedNativeServiceServer
|
||||||
native *Native
|
native *Native
|
||||||
logger *zerolog.Logger
|
logger *zerolog.Logger
|
||||||
eventChs []chan *pb.Event
|
eventStreamChan chan *pb.Event
|
||||||
eventM sync.Mutex
|
eventStreamMu sync.Mutex
|
||||||
|
eventStreamCtx context.Context
|
||||||
|
eventStreamCancel context.CancelFunc
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewGRPCServer creates a new gRPC server for the native service
|
// NewGRPCServer creates a new gRPC server for the native service
|
||||||
|
|
@ -26,7 +28,7 @@ func NewGRPCServer(n *Native, logger *zerolog.Logger) *grpcServer {
|
||||||
s := &grpcServer{
|
s := &grpcServer{
|
||||||
native: n,
|
native: n,
|
||||||
logger: logger,
|
logger: logger,
|
||||||
eventChs: make([]chan *pb.Event, 0),
|
eventStreamChan: make(chan *pb.Event, 100),
|
||||||
}
|
}
|
||||||
|
|
||||||
// Store original callbacks and wrap them to also broadcast events
|
// Store original callbacks and wrap them to also broadcast events
|
||||||
|
|
@ -82,16 +84,7 @@ func NewGRPCServer(n *Native, logger *zerolog.Logger) *grpcServer {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *grpcServer) broadcastEvent(event *pb.Event) {
|
func (s *grpcServer) broadcastEvent(event *pb.Event) {
|
||||||
s.eventM.Lock()
|
s.eventStreamChan <- event
|
||||||
defer s.eventM.Unlock()
|
|
||||||
|
|
||||||
for _, ch := range s.eventChs {
|
|
||||||
select {
|
|
||||||
case ch <- event:
|
|
||||||
default:
|
|
||||||
// Channel full, skip
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *grpcServer) IsReady(ctx context.Context, req *pb.IsReadyRequest) (*pb.IsReadyResponse, error) {
|
func (s *grpcServer) IsReady(ctx context.Context, req *pb.IsReadyRequest) (*pb.IsReadyResponse, error) {
|
||||||
|
|
@ -103,35 +96,49 @@ func (s *grpcServer) StreamEvents(req *pb.Empty, stream pb.NativeService_StreamE
|
||||||
setProcTitle("connected")
|
setProcTitle("connected")
|
||||||
defer setProcTitle("waiting")
|
defer setProcTitle("waiting")
|
||||||
|
|
||||||
eventCh := make(chan *pb.Event, 100)
|
// Cancel previous stream if exists
|
||||||
|
s.eventStreamMu.Lock()
|
||||||
|
if s.eventStreamCancel != nil {
|
||||||
|
s.logger.Debug().Msg("cancelling previous StreamEvents call")
|
||||||
|
s.eventStreamCancel()
|
||||||
|
}
|
||||||
|
|
||||||
// Register this channel for events
|
// Create a cancellable context for this stream
|
||||||
s.eventM.Lock()
|
ctx, cancel := context.WithCancel(stream.Context())
|
||||||
s.eventChs = append(s.eventChs, eventCh)
|
s.eventStreamCtx = ctx
|
||||||
s.eventM.Unlock()
|
s.eventStreamCancel = cancel
|
||||||
|
s.eventStreamMu.Unlock()
|
||||||
|
|
||||||
// Unregister on exit
|
// Clean up when this stream ends
|
||||||
defer func() {
|
defer func() {
|
||||||
s.eventM.Lock()
|
s.eventStreamMu.Lock()
|
||||||
defer s.eventM.Unlock()
|
defer s.eventStreamMu.Unlock()
|
||||||
for i, ch := range s.eventChs {
|
if s.eventStreamCtx == ctx {
|
||||||
if ch == eventCh {
|
s.eventStreamCancel = nil
|
||||||
s.eventChs = append(s.eventChs[:i], s.eventChs[i+1:]...)
|
s.eventStreamCtx = nil
|
||||||
break
|
|
||||||
}
|
}
|
||||||
}
|
cancel()
|
||||||
close(eventCh)
|
|
||||||
}()
|
}()
|
||||||
|
|
||||||
// Stream events
|
// Stream events
|
||||||
for {
|
for {
|
||||||
select {
|
select {
|
||||||
case event := <-eventCh:
|
case event := <-s.eventStreamChan:
|
||||||
|
// Check if this stream is still the active one
|
||||||
|
s.eventStreamMu.Lock()
|
||||||
|
isActive := s.eventStreamCtx == ctx
|
||||||
|
s.eventStreamMu.Unlock()
|
||||||
|
|
||||||
|
if !isActive {
|
||||||
|
s.logger.Debug().Msg("stream replaced by new call, exiting")
|
||||||
|
return context.Canceled
|
||||||
|
}
|
||||||
|
|
||||||
if err := stream.Send(event); err != nil {
|
if err := stream.Send(event); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
case <-stream.Context().Done():
|
case <-ctx.Done():
|
||||||
return stream.Context().Err()
|
return ctx.Err()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -15,6 +15,7 @@ import (
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/Masterminds/semver/v3"
|
"github.com/Masterminds/semver/v3"
|
||||||
|
"github.com/jetkvm/kvm/internal/supervisor"
|
||||||
"github.com/jetkvm/kvm/internal/utils"
|
"github.com/jetkvm/kvm/internal/utils"
|
||||||
"github.com/rs/zerolog"
|
"github.com/rs/zerolog"
|
||||||
)
|
)
|
||||||
|
|
@ -422,7 +423,7 @@ func (p *NativeProxy) restartProcess() error {
|
||||||
logger := p.logger.With().Uint("attempt", p.restarts).Uint("maxAttempts", p.options.MaxRestartAttempts).Logger()
|
logger := p.logger.With().Uint("attempt", p.restarts).Uint("maxAttempts", p.options.MaxRestartAttempts).Logger()
|
||||||
|
|
||||||
if p.restarts >= p.options.MaxRestartAttempts {
|
if p.restarts >= p.options.MaxRestartAttempts {
|
||||||
logger.Fatal().Msg("max restart attempts reached, exiting")
|
logger.Fatal().Msgf("max restart attempts reached, exiting: %s", supervisor.FailsafeReasonVideoMaxRestartAttemptsReached)
|
||||||
return fmt.Errorf("max restart attempts reached")
|
return fmt.Errorf("max restart attempts reached")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -30,7 +30,7 @@ type MDNSListenOptions struct {
|
||||||
|
|
||||||
// NetworkConfig represents the complete network configuration for an interface
|
// NetworkConfig represents the complete network configuration for an interface
|
||||||
type NetworkConfig struct {
|
type NetworkConfig struct {
|
||||||
DHCPClient null.String `json:"dhcp_client,omitempty" one_of:"jetdhcpc,udhcpc" default:"jetdhcpc"`
|
DHCPClient null.String `json:"dhcp_client,omitempty" one_of:"jetdhcpc,udhcpc" default:"udhcpc"`
|
||||||
|
|
||||||
Hostname null.String `json:"hostname,omitempty" validate_type:"hostname"`
|
Hostname null.String `json:"hostname,omitempty" validate_type:"hostname"`
|
||||||
HTTPProxy null.String `json:"http_proxy,omitempty" validate_type:"proxy"`
|
HTTPProxy null.String `json:"http_proxy,omitempty" validate_type:"proxy"`
|
||||||
|
|
|
||||||
|
|
@ -6,4 +6,6 @@ const (
|
||||||
ErrorDumpDir = "/userdata/jetkvm/crashdump" // The error dump directory is the directory where the error dumps are stored
|
ErrorDumpDir = "/userdata/jetkvm/crashdump" // The error dump directory is the directory where the error dumps are stored
|
||||||
ErrorDumpLastFile = "last-crash.log" // The error dump last file is the last error dump file
|
ErrorDumpLastFile = "last-crash.log" // The error dump last file is the last error dump file
|
||||||
ErrorDumpTemplate = "jetkvm-%s.log" // The error dump template is the template for the error dump file
|
ErrorDumpTemplate = "jetkvm-%s.log" // The error dump template is the template for the error dump file
|
||||||
|
|
||||||
|
FailsafeReasonVideoMaxRestartAttemptsReached = "failsafe::video.max_restart_attempts_reached"
|
||||||
)
|
)
|
||||||
|
|
|
||||||
3
main.go
3
main.go
|
|
@ -70,9 +70,6 @@ func Main() {
|
||||||
|
|
||||||
initOta()
|
initOta()
|
||||||
|
|
||||||
initNative(systemVersionLocal, appVersionLocal)
|
|
||||||
initDisplay()
|
|
||||||
|
|
||||||
http.DefaultClient.Timeout = 1 * time.Minute
|
http.DefaultClient.Timeout = 1 * time.Minute
|
||||||
|
|
||||||
// Initialize network
|
// Initialize network
|
||||||
|
|
|
||||||
|
|
@ -284,6 +284,11 @@ func shouldRebootForNetworkChange(oldConfig, newConfig *types.NetworkConfig) (re
|
||||||
l.Info().Msg("IPv6 mode changed with udhcpc, reboot required")
|
l.Info().Msg("IPv6 mode changed with udhcpc, reboot required")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if newConfig.Hostname.String != oldConfig.Hostname.String {
|
||||||
|
rebootRequired = true
|
||||||
|
l.Info().Msg("Hostname changed, reboot required")
|
||||||
|
}
|
||||||
|
|
||||||
return rebootRequired, postRebootAction
|
return rebootRequired, postRebootAction
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,9 @@
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
import { ExclamationTriangleIcon } from "@heroicons/react/24/solid";
|
import { ExclamationTriangleIcon } from "@heroicons/react/24/solid";
|
||||||
import { motion, AnimatePresence } from "framer-motion";
|
import { motion, AnimatePresence } from "framer-motion";
|
||||||
import { LuInfo } from "react-icons/lu";
|
|
||||||
|
|
||||||
import { Button } from "@/components/Button";
|
import { Button } from "@/components/Button";
|
||||||
import Card, { GridCard } from "@components/Card";
|
import { GridCard } from "@components/Card";
|
||||||
import { JsonRpcResponse, useJsonRpc } from "@/hooks/useJsonRpc";
|
import { JsonRpcResponse, useJsonRpc } from "@/hooks/useJsonRpc";
|
||||||
import { useDeviceUiNavigation } from "@/hooks/useAppNavigation";
|
import { useDeviceUiNavigation } from "@/hooks/useAppNavigation";
|
||||||
import { useVersion } from "@/hooks/useVersion";
|
import { useVersion } from "@/hooks/useVersion";
|
||||||
|
|
@ -32,39 +31,12 @@ function OverlayContent({ children }: OverlayContentProps) {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
interface TooltipProps {
|
|
||||||
readonly children: React.ReactNode;
|
|
||||||
readonly text: string;
|
|
||||||
readonly show: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
function Tooltip({ children, text, show }: TooltipProps) {
|
|
||||||
if (!show) {
|
|
||||||
return <>{children}</>;
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="group/tooltip relative">
|
|
||||||
{children}
|
|
||||||
<div className="pointer-events-none absolute bottom-full left-1/2 mb-2 hidden -translate-x-1/2 opacity-0 transition-opacity group-hover/tooltip:block group-hover/tooltip:opacity-100">
|
|
||||||
<Card>
|
|
||||||
<div className="flex items-center justify-center gap-1 px-2 py-1 text-xs whitespace-nowrap">
|
|
||||||
<LuInfo className="h-3 w-3 text-slate-700 dark:text-slate-300" />
|
|
||||||
{text}
|
|
||||||
</div>
|
|
||||||
</Card>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function FailSafeModeOverlay({ reason }: FailSafeModeOverlayProps) {
|
export function FailSafeModeOverlay({ reason }: FailSafeModeOverlayProps) {
|
||||||
const { send } = useJsonRpc();
|
const { send } = useJsonRpc();
|
||||||
const { navigateTo } = useDeviceUiNavigation();
|
const { navigateTo } = useDeviceUiNavigation();
|
||||||
const { appVersion } = useVersion();
|
const { appVersion } = useVersion();
|
||||||
const { systemVersion } = useDeviceStore();
|
const { systemVersion } = useDeviceStore();
|
||||||
const [isDownloadingLogs, setIsDownloadingLogs] = useState(false);
|
const [isDownloadingLogs, setIsDownloadingLogs] = useState(false);
|
||||||
const [hasDownloadedLogs, setHasDownloadedLogs] = useState(false);
|
|
||||||
|
|
||||||
const getReasonCopy = () => {
|
const getReasonCopy = () => {
|
||||||
switch (reason) {
|
switch (reason) {
|
||||||
|
|
@ -112,7 +84,6 @@ export function FailSafeModeOverlay({ reason }: FailSafeModeOverlayProps) {
|
||||||
URL.revokeObjectURL(url);
|
URL.revokeObjectURL(url);
|
||||||
|
|
||||||
notifications.success("Crash logs downloaded successfully");
|
notifications.success("Crash logs downloaded successfully");
|
||||||
setHasDownloadedLogs(true);
|
|
||||||
|
|
||||||
// Open GitHub issue
|
// Open GitHub issue
|
||||||
const issueBody = `## Issue Description
|
const issueBody = `## Issue Description
|
||||||
|
|
@ -143,7 +114,7 @@ Please attach the recovery logs file that was downloaded to your computer:
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleDowngrade = () => {
|
const handleDowngrade = () => {
|
||||||
navigateTo(`/settings/general/update?app=${DOWNGRADE_VERSION}`);
|
navigateTo(`/settings/general/update?custom_app_version=${DOWNGRADE_VERSION}`);
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|
|
||||||
|
|
@ -5,13 +5,22 @@ interface SettingsItemProps {
|
||||||
readonly title: string;
|
readonly title: string;
|
||||||
readonly description: string | React.ReactNode;
|
readonly description: string | React.ReactNode;
|
||||||
readonly badge?: string;
|
readonly badge?: string;
|
||||||
|
readonly badgeTheme?: keyof typeof badgeTheme;
|
||||||
readonly className?: string;
|
readonly className?: string;
|
||||||
readonly loading?: boolean;
|
readonly loading?: boolean;
|
||||||
readonly children?: React.ReactNode;
|
readonly children?: React.ReactNode;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const badgeTheme = {
|
||||||
|
info: "bg-blue-500 text-white",
|
||||||
|
success: "bg-green-500 text-white",
|
||||||
|
warning: "bg-yellow-500 text-white",
|
||||||
|
danger: "bg-red-500 text-white",
|
||||||
|
};
|
||||||
|
|
||||||
export function SettingsItem(props: SettingsItemProps) {
|
export function SettingsItem(props: SettingsItemProps) {
|
||||||
const { title, description, badge, children, className, loading } = props;
|
const { title, description, badge, badgeTheme: badgeThemeProp = "danger", children, className, loading } = props;
|
||||||
|
const badgeThemeClass = badgeTheme[badgeThemeProp];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<label
|
<label
|
||||||
|
|
@ -22,7 +31,7 @@ export function SettingsItem(props: SettingsItemProps) {
|
||||||
<div className="flex items-center text-base font-semibold text-black dark:text-white">
|
<div className="flex items-center text-base font-semibold text-black dark:text-white">
|
||||||
{title}
|
{title}
|
||||||
{badge && (
|
{badge && (
|
||||||
<span className="ml-2 rounded-full bg-red-500 px-2 py-1 text-[10px] leading-none font-medium text-white dark:border dark:border-red-700 dark:bg-red-800 dark:text-red-50">
|
<span className={cx("ml-2 rounded-full px-2 py-1 text-[10px] font-medium leading-none text-white", badgeThemeClass)}>
|
||||||
{badge}
|
{badge}
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
|
|
|
||||||
|
|
@ -528,7 +528,7 @@ export default function WebRTCVideo({ hasConnectionIssues }: { hasConnectionIssu
|
||||||
controlsList="nofullscreen"
|
controlsList="nofullscreen"
|
||||||
style={videoStyle}
|
style={videoStyle}
|
||||||
className={cx(
|
className={cx(
|
||||||
"max-h-full min-h-[384px] max-w-full min-w-[512px] bg-black/50 object-contain transition-all duration-1000",
|
"max-h-full max-w-full sm:min-h-[384px] sm:min-w-[512px] bg-black/50 object-contain transition-all duration-1000",
|
||||||
{
|
{
|
||||||
"cursor-none": settings.isCursorHidden,
|
"cursor-none": settings.isCursorHidden,
|
||||||
"!opacity-0":
|
"!opacity-0":
|
||||||
|
|
|
||||||
|
|
@ -83,6 +83,8 @@ export default function SettingsGeneralRoute() {
|
||||||
<div className="space-y-4 pb-2">
|
<div className="space-y-4 pb-2">
|
||||||
<div className="space-y-4">
|
<div className="space-y-4">
|
||||||
<SettingsItem
|
<SettingsItem
|
||||||
|
badge="Beta"
|
||||||
|
badgeTheme="info"
|
||||||
title={m.user_interface_language_title()}
|
title={m.user_interface_language_title()}
|
||||||
description={m.user_interface_language_description()}
|
description={m.user_interface_language_description()}
|
||||||
>
|
>
|
||||||
|
|
|
||||||
|
|
@ -291,6 +291,14 @@ export default function SettingsNetworkRoute() {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (dirty.hostname) {
|
||||||
|
changes.push({
|
||||||
|
label: m.network_hostname_title(),
|
||||||
|
from: initialSettingsRef.current?.hostname?.toString() ?? "",
|
||||||
|
to: data.hostname?.toString() ?? "",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
// If no critical fields are changed, save immediately
|
// If no critical fields are changed, save immediately
|
||||||
if (changes.length === 0) return onSubmit(settings);
|
if (changes.length === 0) return onSubmit(settings);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -705,7 +705,14 @@ export default function KvmIdRoute() {
|
||||||
if (resp.method === "willReboot") {
|
if (resp.method === "willReboot") {
|
||||||
const postRebootAction = resp.params as unknown as PostRebootAction;
|
const postRebootAction = resp.params as unknown as PostRebootAction;
|
||||||
console.debug("Setting reboot state", postRebootAction);
|
console.debug("Setting reboot state", postRebootAction);
|
||||||
setRebootState({ isRebooting: true, postRebootAction });
|
|
||||||
|
setRebootState({
|
||||||
|
isRebooting: true,
|
||||||
|
postRebootAction: {
|
||||||
|
healthCheck: postRebootAction?.healthCheck || `${window.location.origin}/device/status`,
|
||||||
|
redirectTo: postRebootAction?.redirectTo || window.location.href,
|
||||||
|
}
|
||||||
|
});
|
||||||
navigateTo("/");
|
navigateTo("/");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -830,6 +837,10 @@ export default function KvmIdRoute() {
|
||||||
return <RebootingOverlay show={true} postRebootAction={rebootState.postRebootAction} />;
|
return <RebootingOverlay show={true} postRebootAction={rebootState.postRebootAction} />;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (isFailsafeMode && failsafeReason) {
|
||||||
|
return <FailSafeModeOverlay reason={failsafeReason} />;
|
||||||
|
}
|
||||||
|
|
||||||
const hasConnectionFailed =
|
const hasConnectionFailed =
|
||||||
connectionFailed || ["failed", "closed"].includes(peerConnectionState ?? "");
|
connectionFailed || ["failed", "closed"].includes(peerConnectionState ?? "");
|
||||||
|
|
||||||
|
|
@ -851,16 +862,7 @@ export default function KvmIdRoute() {
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
}, [
|
}, [location.pathname, rebootState?.isRebooting, rebootState?.postRebootAction, isFailsafeMode, failsafeReason, connectionFailed, peerConnectionState, peerConnection, setupPeerConnection, loadingMessage]);
|
||||||
location.pathname,
|
|
||||||
rebootState?.isRebooting,
|
|
||||||
rebootState?.postRebootAction,
|
|
||||||
connectionFailed,
|
|
||||||
peerConnectionState,
|
|
||||||
peerConnection,
|
|
||||||
setupPeerConnection,
|
|
||||||
loadingMessage,
|
|
||||||
]);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<FeatureFlagProvider appVersion={appVersion}>
|
<FeatureFlagProvider appVersion={appVersion}>
|
||||||
|
|
@ -909,12 +911,8 @@ export default function KvmIdRoute() {
|
||||||
style={{ animationDuration: "500ms" }}
|
style={{ animationDuration: "500ms" }}
|
||||||
className="pointer-events-none absolute inset-0 flex animate-slideUpFade items-center justify-center p-4"
|
className="pointer-events-none absolute inset-0 flex animate-slideUpFade items-center justify-center p-4"
|
||||||
>
|
>
|
||||||
<div className="relative h-full max-h-[720px] w-full max-w-7xl rounded-md">
|
<div className="relative h-full max-h-[720px] w-full max-w-[1280px] rounded-md">
|
||||||
{isFailsafeMode && failsafeReason ? (
|
{!!ConnectionStatusElement && ConnectionStatusElement}
|
||||||
<FailSafeModeOverlay reason={failsafeReason} />
|
|
||||||
) : (
|
|
||||||
!!ConnectionStatusElement && ConnectionStatusElement
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<SidebarContainer sidebarView={sidebarView} />
|
<SidebarContainer sidebarView={sidebarView} />
|
||||||
|
|
|
||||||
|
|
@ -33,7 +33,7 @@ export default defineConfig(({ mode, command }) => {
|
||||||
outdir: "./localization/paraglide",
|
outdir: "./localization/paraglide",
|
||||||
outputStructure: 'message-modules',
|
outputStructure: 'message-modules',
|
||||||
cookieName: 'JETKVM_LOCALE',
|
cookieName: 'JETKVM_LOCALE',
|
||||||
strategy: ['cookie', 'preferredLanguage', 'baseLocale'],
|
strategy: ['cookie', 'baseLocale'],
|
||||||
}))
|
}))
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue