mirror of
https://github.com/wavetermdev/waveterm.git
synced 2025-01-17 20:51:55 +01:00
implement a config error button + message modal that shows the errors (#1030)
This commit is contained in:
parent
a15b339f39
commit
a629b28194
6
frontend/app/modals/messagemodal.less
Normal file
6
frontend/app/modals/messagemodal.less
Normal file
@ -0,0 +1,6 @@
|
||||
// Copyright 2024, Command Line Inc.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
.message-modal {
|
||||
min-width: 400px;
|
||||
}
|
24
frontend/app/modals/messagemodal.tsx
Normal file
24
frontend/app/modals/messagemodal.tsx
Normal file
@ -0,0 +1,24 @@
|
||||
// Copyright 2024, Command Line Inc.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import { Modal } from "@/app/modals/modal";
|
||||
import { modalsModel } from "@/app/store/modalmodel";
|
||||
|
||||
import { ReactNode } from "react";
|
||||
import "./messagemodal.less";
|
||||
|
||||
const MessageModal = ({ children }: { children: ReactNode }) => {
|
||||
function closeModal() {
|
||||
modalsModel.popModal();
|
||||
}
|
||||
|
||||
return (
|
||||
<Modal className="message-modal" onOk={() => closeModal()} onClose={() => closeModal()}>
|
||||
{children}
|
||||
</Modal>
|
||||
);
|
||||
};
|
||||
|
||||
MessageModal.displayName = "MessageModal";
|
||||
|
||||
export { MessageModal };
|
@ -1,6 +1,7 @@
|
||||
// Copyright 2024, Command Line Inc.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import { MessageModal } from "@/app/modals/messagemodal";
|
||||
import { AboutModal } from "./about";
|
||||
import { TosModal } from "./tos";
|
||||
import { UserInputModal } from "./userinputmodal";
|
||||
@ -9,6 +10,7 @@ const modalRegistry: { [key: string]: React.ComponentType<any> } = {
|
||||
[TosModal.displayName || "TosModal"]: TosModal,
|
||||
[UserInputModal.displayName || "UserInputModal"]: UserInputModal,
|
||||
[AboutModal.displayName || "AboutModal"]: AboutModal,
|
||||
[MessageModal.displayName || "MessageModal"]: MessageModal,
|
||||
};
|
||||
|
||||
export const getModalComponent = (key: string): React.ComponentType<any> | undefined => {
|
||||
|
@ -12,6 +12,16 @@
|
||||
}
|
||||
}
|
||||
|
||||
.config-error-message {
|
||||
max-width: 500px;
|
||||
|
||||
h3 {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.tab-bar-wrapper {
|
||||
position: relative;
|
||||
user-select: none;
|
||||
@ -53,6 +63,13 @@
|
||||
color: var(--accent-color);
|
||||
}
|
||||
|
||||
.config-error-button {
|
||||
height: 80%;
|
||||
margin: auto 4px;
|
||||
color: black;
|
||||
flex: 0 0 fit-content;
|
||||
}
|
||||
|
||||
.add-tab-btn {
|
||||
width: 22px;
|
||||
height: 100%;
|
||||
|
@ -1,6 +1,8 @@
|
||||
// Copyright 2024, Command Line Inc.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import { Button } from "@/app/element/button";
|
||||
import { modalsModel } from "@/app/store/modalmodel";
|
||||
import { WindowDrag } from "@/element/windowdrag";
|
||||
import { deleteLayoutModelForTab } from "@/layout/index";
|
||||
import { atoms, getApi, isDev, PLATFORM } from "@/store/global";
|
||||
@ -37,6 +39,69 @@ interface TabBarProps {
|
||||
workspace: Workspace;
|
||||
}
|
||||
|
||||
const ConfigErrorMessage = () => {
|
||||
const fullConfig = useAtomValue(atoms.fullConfigAtom);
|
||||
|
||||
if (fullConfig?.configerrors == null || fullConfig?.configerrors.length == 0) {
|
||||
return (
|
||||
<div className="config-error-message">
|
||||
<h3>Configuration Clean</h3>
|
||||
<p>There are no longer any errors detected in your config.</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
if (fullConfig?.configerrors.length == 1) {
|
||||
const singleError = fullConfig.configerrors[0];
|
||||
return (
|
||||
<div className="config-error-message">
|
||||
<h3>Configuration Error</h3>
|
||||
<div>
|
||||
{singleError.file}: {singleError.err}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<div className="config-error-message">
|
||||
<h3>Configuration Error</h3>
|
||||
<ul>
|
||||
{fullConfig.configerrors.map((error, index) => (
|
||||
<li key={index}>
|
||||
{error.file}: {error.err}
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const ConfigErrorIcon = ({ buttonRef }: { buttonRef: React.RefObject<HTMLElement> }) => {
|
||||
const fullConfig = useAtomValue(atoms.fullConfigAtom);
|
||||
|
||||
function handleClick() {
|
||||
modalsModel.pushModal("MessageModal", { children: <ConfigErrorMessage /> });
|
||||
}
|
||||
|
||||
if (fullConfig?.configerrors == null || fullConfig?.configerrors.length == 0) {
|
||||
return null;
|
||||
}
|
||||
return (
|
||||
<Button
|
||||
ref={buttonRef as React.RefObject<HTMLButtonElement>}
|
||||
className="config-error-button red"
|
||||
onClick={handleClick}
|
||||
>
|
||||
<i className="fa fa-solid fa-exclamation-triangle" />
|
||||
Config Error
|
||||
</Button>
|
||||
);
|
||||
return (
|
||||
<div className="config-error" ref={buttonRef as React.RefObject<HTMLDivElement>}>
|
||||
<i className="fa fa-solid fa-exclamation-triangle" />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const TabBar = React.memo(({ workspace }: TabBarProps) => {
|
||||
const [tabIds, setTabIds] = useState<string[]>([]);
|
||||
const [dragStartPositions, setDragStartPositions] = useState<number[]>([]);
|
||||
@ -67,6 +132,7 @@ const TabBar = React.memo(({ workspace }: TabBarProps) => {
|
||||
const tabWidthRef = useRef<number>(TAB_DEFAULT_WIDTH);
|
||||
const scrollableRef = useRef<boolean>(false);
|
||||
const updateStatusButtonRef = useRef<HTMLButtonElement>(null);
|
||||
const configErrorButtonRef = useRef<HTMLElement>(null);
|
||||
const prevAllLoadedRef = useRef<boolean>(false);
|
||||
|
||||
const windowData = useAtomValue(atoms.waveWindow);
|
||||
@ -124,8 +190,10 @@ const TabBar = React.memo(({ workspace }: TabBarProps) => {
|
||||
const windowDragLeftWidth = draggerLeftRef.current.getBoundingClientRect().width;
|
||||
const addBtnWidth = addBtnRef.current.getBoundingClientRect().width;
|
||||
const updateStatusLabelWidth = updateStatusButtonRef.current?.getBoundingClientRect().width ?? 0;
|
||||
const configErrorWidth = configErrorButtonRef.current?.getBoundingClientRect().width ?? 0;
|
||||
const spaceForTabs =
|
||||
tabbarWrapperWidth - (windowDragLeftWidth + DRAGGER_RIGHT_MIN_WIDTH + addBtnWidth + updateStatusLabelWidth);
|
||||
tabbarWrapperWidth -
|
||||
(windowDragLeftWidth + DRAGGER_RIGHT_MIN_WIDTH + addBtnWidth + updateStatusLabelWidth + configErrorWidth);
|
||||
|
||||
const numberOfTabs = tabIds.length;
|
||||
const totalDefaultTabWidth = numberOfTabs * TAB_DEFAULT_WIDTH;
|
||||
@ -510,6 +578,7 @@ const TabBar = React.memo(({ workspace }: TabBarProps) => {
|
||||
</div>
|
||||
<WindowDrag ref={draggerRightRef} className="right" />
|
||||
<UpdateStatusBanner buttonRef={updateStatusButtonRef} />
|
||||
<ConfigErrorIcon buttonRef={configErrorButtonRef} />
|
||||
</div>
|
||||
);
|
||||
});
|
||||
|
@ -6,7 +6,7 @@ import "./updatebanner.less";
|
||||
|
||||
const UpdateStatusBannerComponent = ({ buttonRef }: { buttonRef: React.RefObject<HTMLButtonElement> }) => {
|
||||
const appUpdateStatus = useAtomValue(atoms.updaterStatusAtom);
|
||||
const [updateStatusMessage, setUpdateStatusMessage] = useState<string>();
|
||||
let [updateStatusMessage, setUpdateStatusMessage] = useState<string>();
|
||||
const [dismissBannerTimeout, setDismissBannerTimeout] = useState<NodeJS.Timeout>();
|
||||
|
||||
useEffect(() => {
|
||||
@ -52,7 +52,6 @@ const UpdateStatusBannerComponent = ({ buttonRef }: { buttonRef: React.RefObject
|
||||
function onClick() {
|
||||
getApi().installAppUpdate();
|
||||
}
|
||||
|
||||
if (updateStatusMessage) {
|
||||
return (
|
||||
<Button
|
||||
|
@ -932,3 +932,17 @@ func WriteFileIfDifferent(fileName string, contents []byte) (bool, error) {
|
||||
}
|
||||
return true, nil
|
||||
}
|
||||
|
||||
func GetLineColFromOffset(barr []byte, offset int) (int, int) {
|
||||
line := 1
|
||||
col := 1
|
||||
for i := 0; i < offset && i < len(barr); i++ {
|
||||
if barr[i] == '\n' {
|
||||
line++
|
||||
col = 1
|
||||
} else {
|
||||
col++
|
||||
}
|
||||
}
|
||||
return line, col
|
||||
}
|
||||
|
@ -20,6 +20,13 @@ import (
|
||||
|
||||
const SettingsFile = "settings.json"
|
||||
|
||||
const AnySchema = `
|
||||
{
|
||||
"type": "object",
|
||||
"additionalProperties": true
|
||||
}
|
||||
`
|
||||
|
||||
type MetaSettingsType struct {
|
||||
waveobj.MetaMapType
|
||||
}
|
||||
@ -117,10 +124,37 @@ type FullConfigType struct {
|
||||
ConfigErrors []ConfigError `json:"configerrors" configfile:"-"`
|
||||
}
|
||||
|
||||
func goBackWS(barr []byte, offset int) int {
|
||||
if offset >= len(barr) {
|
||||
offset = offset - 1
|
||||
}
|
||||
for i := offset - 1; i >= 0; i-- {
|
||||
if barr[i] == ' ' || barr[i] == '\t' || barr[i] == '\n' || barr[i] == '\r' {
|
||||
continue
|
||||
}
|
||||
return i
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func isTrailingCommaError(barr []byte, offset int) bool {
|
||||
if offset >= len(barr) {
|
||||
offset = offset - 1
|
||||
}
|
||||
offset = goBackWS(barr, offset)
|
||||
if barr[offset] == '}' {
|
||||
offset = goBackWS(barr, offset)
|
||||
if barr[offset] == ',' {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func readConfigHelper(fileName string, barr []byte, readErr error) (waveobj.MetaMapType, []ConfigError) {
|
||||
var cerrs []ConfigError
|
||||
if readErr != nil && !os.IsNotExist(readErr) {
|
||||
cerrs = append(cerrs, ConfigError{File: "defaults:" + fileName, Err: readErr.Error()})
|
||||
cerrs = append(cerrs, ConfigError{File: fileName, Err: readErr.Error()})
|
||||
}
|
||||
if len(barr) == 0 {
|
||||
return nil, cerrs
|
||||
@ -128,7 +162,20 @@ func readConfigHelper(fileName string, barr []byte, readErr error) (waveobj.Meta
|
||||
var rtn waveobj.MetaMapType
|
||||
err := json.Unmarshal(barr, &rtn)
|
||||
if err != nil {
|
||||
cerrs = append(cerrs, ConfigError{File: "defaults:" + fileName, Err: err.Error()})
|
||||
if syntaxErr, ok := err.(*json.SyntaxError); ok {
|
||||
offset := syntaxErr.Offset
|
||||
if offset > 0 {
|
||||
offset = offset - 1
|
||||
}
|
||||
lineNum, colNum := utilfn.GetLineColFromOffset(barr, int(offset))
|
||||
isTrailingComma := isTrailingCommaError(barr, int(offset))
|
||||
if isTrailingComma {
|
||||
err = fmt.Errorf("json syntax error at line %d, col %d: probably an extra trailing comma: %v", lineNum, colNum, syntaxErr)
|
||||
} else {
|
||||
err = fmt.Errorf("json syntax error at line %d, col %d: %v", lineNum, colNum, syntaxErr)
|
||||
}
|
||||
}
|
||||
cerrs = append(cerrs, ConfigError{File: fileName, Err: err.Error()})
|
||||
}
|
||||
return rtn, cerrs
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user