finish config edit page
This commit is contained in:
parent
529885498d
commit
04962ba56a
@ -1,6 +1,6 @@
|
||||
# syntax=docker/dockerfile:1
|
||||
# Build commands:
|
||||
# - $VERSION=0.9
|
||||
# - $VERSION=1.0
|
||||
# - rm build -r -Force ; rm ./node_modules/.cache/babel-loader -r -Force ; yarn build
|
||||
# - docker build --pull --rm -t oncharterliz/multi-frigate:latest -t oncharterliz/multi-frigate:$VERSION "."
|
||||
# - docker image push --all-tags oncharterliz/multi-frigate
|
||||
|
||||
@ -1,28 +1,61 @@
|
||||
import React, { useCallback, useContext, useEffect, useRef, useState } from 'react';
|
||||
import { Button, Flex, Text, useMantineTheme } from '@mantine/core';
|
||||
import { useClipboard } from '@mantine/hooks';
|
||||
import Editor, { Monaco } from '@monaco-editor/react';
|
||||
import { useMutation, useQuery } from '@tanstack/react-query';
|
||||
import { observer } from 'mobx-react-lite';
|
||||
import * as monaco from "monaco-editor";
|
||||
import { SchemasSettings, configureMonacoYaml } from 'monaco-yaml';
|
||||
import { useCallback, useContext, useEffect, useRef, useState } from 'react';
|
||||
import { useParams } from 'react-router-dom';
|
||||
import { Context } from '..';
|
||||
import { useMutation, useQuery } from '@tanstack/react-query';
|
||||
import { frigateApi, frigateQueryKeys, mapHostToHostname, proxyApi } from '../services/frigate.proxy/frigate.api';
|
||||
import { Button, Flex, useMantineTheme, Text } from '@mantine/core';
|
||||
import { useClipboard } from '@mantine/hooks';
|
||||
import Editor, { Monaco } from '@monaco-editor/react'
|
||||
import * as monaco from "monaco-editor";
|
||||
import CenterLoader from '../shared/components/loaders/CenterLoader';
|
||||
import RetryErrorPage from './RetryErrorPage';
|
||||
import { useAdminRole } from '../hooks/useAdminRole';
|
||||
import Forbidden from './403';
|
||||
import { observer } from 'mobx-react-lite';
|
||||
import { frigateApi, frigateQueryKeys, mapHostToHostname, proxyApi } from '../services/frigate.proxy/frigate.api';
|
||||
import { GetFrigateHost } from '../services/frigate.proxy/frigate.schema';
|
||||
import CenterLoader from '../shared/components/loaders/CenterLoader';
|
||||
import { isProduction } from '../shared/env.const';
|
||||
import { SaveOption } from '../types/saveConfig';
|
||||
import { GetFrigateHost } from '../services/frigate.proxy/frigate.schema';
|
||||
import { error } from 'console';
|
||||
import Forbidden from './403';
|
||||
import RetryErrorPage from './RetryErrorPage';
|
||||
import { notifications } from '@mantine/notifications';
|
||||
import { IconAlertCircle, IconCircleCheck } from '@tabler/icons-react';
|
||||
|
||||
|
||||
window.MonacoEnvironment = {
|
||||
getWorker(moduleId, label) {
|
||||
switch (label) {
|
||||
case 'editorWorkerService':
|
||||
return new Worker(new URL('monaco-editor/esm/vs/editor/editor.worker', import.meta.url))
|
||||
case 'css':
|
||||
case 'less':
|
||||
case 'scss':
|
||||
return new Worker(new URL('monaco-editor/esm/vs/language/css/css.worker', import.meta.url))
|
||||
case 'handlebars':
|
||||
case 'html':
|
||||
case 'razor':
|
||||
return new Worker(
|
||||
new URL('monaco-editor/esm/vs/language/html/html.worker', import.meta.url)
|
||||
)
|
||||
case 'json':
|
||||
return new Worker(
|
||||
new URL('monaco-editor/esm/vs/language/json/json.worker', import.meta.url)
|
||||
)
|
||||
case 'javascript':
|
||||
case 'typescript':
|
||||
return new Worker(
|
||||
new URL('monaco-editor/esm/vs/language/typescript/ts.worker', import.meta.url)
|
||||
)
|
||||
case 'yaml':
|
||||
return new Worker(new URL('monaco-yaml/yaml.worker', import.meta.url))
|
||||
default:
|
||||
throw new Error(`Unknown label ${label}`)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const HostConfigPage = () => {
|
||||
const executed = useRef(false)
|
||||
const host = useRef<GetFrigateHost | undefined>()
|
||||
const { sideBarsStore } = useContext(Context)
|
||||
const [saveMessage, setSaveMessage] = useState<string>()
|
||||
|
||||
let { id } = useParams<'id'>()
|
||||
const { isAdmin, isLoading: adminLoading } = useAdminRole()
|
||||
@ -51,10 +84,26 @@ const HostConfigPage = () => {
|
||||
})
|
||||
},
|
||||
onSuccess: (data) => {
|
||||
setSaveMessage(data?.message)
|
||||
notifications.show({
|
||||
id: data?.message,
|
||||
withCloseButton: true,
|
||||
autoClose: 5000,
|
||||
title: `Sucess: ${data?.success}`,
|
||||
message: data?.message,
|
||||
color: 'green',
|
||||
icon: <IconCircleCheck />
|
||||
})
|
||||
},
|
||||
onError: (error) => {
|
||||
setSaveMessage(error.message)
|
||||
onError: (e) => {
|
||||
notifications.show({
|
||||
id: e.message,
|
||||
withCloseButton: true,
|
||||
autoClose: false,
|
||||
title: "Error",
|
||||
message: e.message,
|
||||
color: 'red',
|
||||
icon: <IconAlertCircle />,
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
@ -72,28 +121,24 @@ const HostConfigPage = () => {
|
||||
const editorRef = useRef<monaco.editor.IStandaloneCodeEditor | null>(null)
|
||||
|
||||
function handleEditorWillMount(monaco: Monaco) {
|
||||
monaco.languages.typescript.javascriptDefaults.setEagerModelSync(true);
|
||||
|
||||
// TODO add yaml schema
|
||||
// const modelUri = monaco.Uri.parse("http://localhost:4000/proxy/api/config/schema.json?hostName=localhost:5001")
|
||||
// configureMonacoYaml(monaco, {
|
||||
// enableSchemaRequest: true,
|
||||
// hover: true,
|
||||
// completion: true,
|
||||
// validate: true,
|
||||
// format: true,
|
||||
// schemas: [
|
||||
// {
|
||||
// uri: `http://localhost:4000/proxy/api/config/schema.json?hostName=localhost:5001`,
|
||||
// fileMatch: ['**/.schema.*'],
|
||||
// },
|
||||
// ],
|
||||
// })
|
||||
const hostName = mapHostToHostname(host.current)
|
||||
if (!hostName) return
|
||||
const schemaURL = proxyApi.configSchemaURL(hostName)
|
||||
const defaultSchema: SchemasSettings = {
|
||||
uri: schemaURL,
|
||||
fileMatch: ['monaco-yaml.yaml']
|
||||
}
|
||||
configureMonacoYaml(monaco, {
|
||||
enableSchemaRequest: true,
|
||||
schemas: [defaultSchema]
|
||||
})
|
||||
}
|
||||
|
||||
function handleEditorDidMount(editor: monaco.editor.IStandaloneCodeEditor, monaco: Monaco) {
|
||||
// here is another way to get monaco instance
|
||||
// you can also store it in `useRef` for further usage
|
||||
const model = monaco.editor.createModel(config, 'yaml', monaco.Uri.parse('monaco-yaml.yaml'))
|
||||
editor.setModel(model)
|
||||
editorRef.current = editor;
|
||||
}
|
||||
|
||||
@ -115,7 +160,7 @@ const HostConfigPage = () => {
|
||||
saveConfig({ saveOption: saveOption, config: editorRef.current.getValue() })
|
||||
}, [editorRef])
|
||||
|
||||
if (configPending || adminLoading) return <CenterLoader />
|
||||
if (configPending || adminLoading ) return <CenterLoader />
|
||||
|
||||
if (configError) return <RetryErrorPage onRetry={refetch} />
|
||||
if (!isAdmin) return <Forbidden />
|
||||
@ -133,11 +178,6 @@ const HostConfigPage = () => {
|
||||
Save Only
|
||||
</Button>
|
||||
</Flex>
|
||||
{!saveMessage ? null :
|
||||
<Flex w='100%' justify='center' wrap='nowrap' mt='1rem'>
|
||||
<Text>{saveMessage}</Text>
|
||||
</Flex>
|
||||
}
|
||||
<Flex h='100%' mt='1rem'>
|
||||
<Editor
|
||||
defaultLanguage='yaml'
|
||||
|
||||
@ -1,40 +1,122 @@
|
||||
import { Flex, Grid, Group } from '@mantine/core';
|
||||
import { Flex, Group, useMantineTheme } from '@mantine/core';
|
||||
import { Editor, Monaco } from '@monaco-editor/react';
|
||||
import { observer } from 'mobx-react-lite';
|
||||
import * as monaco from 'monaco-editor';
|
||||
import { SchemasSettings, configureMonacoYaml } from 'monaco-yaml';
|
||||
import { useContext, useEffect, useRef } from 'react';
|
||||
import { Context } from '..';
|
||||
import HeadSearch from '../shared/components/inputs/HeadSearch';
|
||||
import TestItem from './TestItem';
|
||||
|
||||
const Test = () => {
|
||||
const executed = useRef(false)
|
||||
const editorRef = useRef<monaco.editor.IStandaloneCodeEditor | null>(null)
|
||||
const theme = useMantineTheme();
|
||||
|
||||
const { sideBarsStore } = useContext(Context)
|
||||
sideBarsStore.rightVisible = true
|
||||
|
||||
useEffect(() => {
|
||||
if (!executed.current) {
|
||||
sideBarsStore.rightVisible = false
|
||||
sideBarsStore.setLeftChildren(null)
|
||||
sideBarsStore.setRightChildren(null)
|
||||
executed.current = true
|
||||
sideBarsStore.rightVisible = false
|
||||
sideBarsStore.setLeftChildren(null)
|
||||
sideBarsStore.setRightChildren(null)
|
||||
executed.current = true
|
||||
}
|
||||
}, [sideBarsStore])
|
||||
}, [sideBarsStore])
|
||||
|
||||
|
||||
// const handleClick = () => {
|
||||
// const startOfDay = new Date();
|
||||
// startOfDay.setHours(0, 0, 0, 0);
|
||||
// setValue([startOfDay, startOfDay])
|
||||
// }
|
||||
|
||||
const cards = (qty: number) => {
|
||||
let items = []
|
||||
for (let i = 0; i < qty; i++) {
|
||||
items.push(<TestItem key={i} />)
|
||||
|
||||
const value = `
|
||||
# Property descriptions are displayed when hovering over properties using your cursor
|
||||
property: This property has a JSON schema description
|
||||
|
||||
|
||||
# Titles work too!
|
||||
titledProperty: Titles work too!
|
||||
|
||||
|
||||
# Even markdown descriptions work
|
||||
markdown: hover me to get a markdown based description 😮
|
||||
|
||||
|
||||
# Enums can be autocompleted by placing the cursor after the colon and pressing Ctrl+Space
|
||||
enum:
|
||||
|
||||
|
||||
# Unused anchors will be reported
|
||||
unused anchor: &unused anchor
|
||||
|
||||
|
||||
# Of course numbers are supported!
|
||||
number: 12
|
||||
|
||||
|
||||
# As well as booleans!
|
||||
boolean: true
|
||||
|
||||
|
||||
# And strings
|
||||
string: I am a string
|
||||
|
||||
|
||||
# This property is using the JSON schema recursively
|
||||
reference:
|
||||
boolean: Not a boolean
|
||||
|
||||
|
||||
# Also works in arrays
|
||||
array:
|
||||
- string: 12
|
||||
enum: Mewtwo
|
||||
reference:
|
||||
reference:
|
||||
boolean: true
|
||||
|
||||
|
||||
# JSON referenses can be clicked for navigation
|
||||
pointer:
|
||||
$ref: '#/array'
|
||||
|
||||
|
||||
# This anchor can be referenced
|
||||
anchorRef: &anchor can be clicked as well
|
||||
|
||||
|
||||
# Press control while hovering over the anchor
|
||||
anchorPointer: *anchor
|
||||
|
||||
|
||||
formatting: Formatting is supported too! Under the hood this is powered by Prettier. Just press Ctrl+Shift+I or right click and press Format to format this document.
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
`.replace(/:$/m, ': ')
|
||||
|
||||
function handleEditorWillMount(monaco: Monaco) {
|
||||
const defaultSchema: SchemasSettings = {
|
||||
uri: 'https://github.com/remcohaszing/monaco-yaml/blob/HEAD/examples/demo/src/schema.json',
|
||||
fileMatch: ['monaco-yaml.yaml']
|
||||
}
|
||||
return items
|
||||
|
||||
configureMonacoYaml(monaco, {
|
||||
enableSchemaRequest: true,
|
||||
schemas: [defaultSchema]
|
||||
})
|
||||
}
|
||||
|
||||
function handleEditorDidMount(editor: monaco.editor.IStandaloneCodeEditor, monaco: Monaco) {
|
||||
// here is another way to get monaco instance
|
||||
// you can also store it in `useRef` for further usage
|
||||
const model = monaco.editor.createModel(value, 'yaml', monaco.Uri.parse('monaco-yaml.yaml'))
|
||||
editor.setModel(model)
|
||||
editorRef.current = editor;
|
||||
}
|
||||
|
||||
|
||||
return (
|
||||
<Flex direction='column' h='100%' >
|
||||
<Flex justify='space-between' align='center' w='100%'>
|
||||
@ -54,9 +136,14 @@ const Test = () => {
|
||||
</Group>
|
||||
</Flex>
|
||||
<Flex justify='center' h='100%' direction='column' >
|
||||
<Grid mt='sm' justify="center" mb='sm' align='stretch'>
|
||||
{cards(60)}
|
||||
</Grid>
|
||||
<Editor
|
||||
defaultLanguage='yaml'
|
||||
value={value}
|
||||
defaultValue="// Data empty"
|
||||
theme={theme.colorScheme === "dark" ? "vs-dark" : "vs-light"}
|
||||
beforeMount={handleEditorWillMount}
|
||||
onMount={handleEditorDidMount}
|
||||
/>
|
||||
</Flex>
|
||||
</Flex>
|
||||
);
|
||||
|
||||
@ -143,6 +143,7 @@ export const proxyApi = {
|
||||
|
||||
getEventsSummary: (hostName: string, cameraName: string) =>
|
||||
instanceApi.get(`proxy/${hostName}/api/${cameraName}/events/summary`).then(res => res.data),
|
||||
configSchemaURL: (hostName: string) => `${proxyPrefix}${hostName}/api/config/schema.json`,
|
||||
cameraWsURL: (hostName: string, cameraName: string) => {
|
||||
const protocol = proxyURL.protocol === 'https:' ? 'wss' : 'ws';
|
||||
return `${protocol}://${proxyURL.host}/proxy-ws/${hostName}/live/jsmpeg/${cameraName}`
|
||||
|
||||
@ -10857,7 +10857,12 @@ yaml@^1.10.0, yaml@^1.10.2, yaml@^1.7.2:
|
||||
resolved "https://registry.yarnpkg.com/yaml/-/yaml-1.10.2.tgz#2301c5ffbf12b467de8da2333a459e29e7920e4b"
|
||||
integrity sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==
|
||||
|
||||
yaml@^2.0.0, yaml@^2.3.4:
|
||||
yaml@^2.0.0:
|
||||
version "2.4.1"
|
||||
resolved "https://registry.yarnpkg.com/yaml/-/yaml-2.4.1.tgz#2e57e0b5e995292c25c75d2658f0664765210eed"
|
||||
integrity sha512-pIXzoImaqmfOrL7teGUBt/T7ZDnyeGBWyXQBvOVhLkWLN37GXv8NMLK406UY6dS51JfcQHsmcW5cJ441bHg6Lg==
|
||||
|
||||
yaml@^2.3.4:
|
||||
version "2.4.0"
|
||||
resolved "https://registry.yarnpkg.com/yaml/-/yaml-2.4.0.tgz#2376db1083d157f4b3a452995803dbcf43b08140"
|
||||
integrity sha512-j9iR8g+/t0lArF4V6NE/QCfT+CO7iLqrXAHZbJdo+LfjqP1vR8Fg5bSiaq6Q2lOD1AUEVrEVIgABvBFYojJVYQ==
|
||||
|
||||
Loading…
Reference in New Issue
Block a user