1st Version
This commit is contained in:
commit
d79d2aefee
1948 files changed
+361921
No files matched your search
@@ -0,0 +1,2 @@
|
||||
/node_modules/
|
||||
/tmpDir/
|
||||
Vendored
+46
@@ -0,0 +1,46 @@
|
||||
{
|
||||
// Use IntelliSense to learn about possible attributes.
|
||||
// Hover to view descriptions of existing attributes.
|
||||
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
|
||||
"version": "0.2.0",
|
||||
"configurations": [
|
||||
{
|
||||
"name": "Launch firefox",
|
||||
"type": "firefox",
|
||||
"request": "launch",
|
||||
"url": "http://localhost:5001",
|
||||
"webRoot": "${workspaceFolder}",
|
||||
},
|
||||
{
|
||||
"name": "Launch Firfox new",
|
||||
"type": "firefox",
|
||||
"request": "launch",
|
||||
"url": "http://localhost/index.html",
|
||||
"webRoot": "${workspaceFolder}"
|
||||
}
|
||||
{
|
||||
"name": "Launch Firefox new2",
|
||||
"type": "firefox",
|
||||
"request": "launch",
|
||||
"port": 5173,
|
||||
"url": "http://localhost/index.html",
|
||||
"webRoot": "${workspaceFolder}"
|
||||
}
|
||||
{
|
||||
"name": "Launch index.html",
|
||||
"type": "firefox",
|
||||
"request": "launch",
|
||||
"port": 5173,
|
||||
"file": "${workspaceFolder}/index.html"
|
||||
},
|
||||
{
|
||||
"type": "firefox",
|
||||
"request": "launch",
|
||||
"name": "Launch My Firefox",
|
||||
"url": "http://localhost:5173",
|
||||
"webRoot": "${workspaceFolder}",
|
||||
"timeout": 90000,
|
||||
"tmpDir": "/home/markus/git/vrpmdvweb/vrpmdvfrontend/tmpDir"
|
||||
},
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
# This Dockerfile uses `serve` npm package to serve the static files with node process.
|
||||
# You can find the Dockerfile for nginx in the following link:
|
||||
# https://github.com/refinedev/dockerfiles/blob/main/vite/Dockerfile.nginx
|
||||
FROM refinedev/node:18 AS base
|
||||
|
||||
FROM base as deps
|
||||
|
||||
COPY package.json yarn.lock* package-lock.json* pnpm-lock.yaml* .npmrc* ./
|
||||
|
||||
RUN \
|
||||
if [ -f yarn.lock ]; then yarn --frozen-lockfile; \
|
||||
elif [ -f package-lock.json ]; then npm ci; \
|
||||
elif [ -f pnpm-lock.yaml ]; then yarn global add pnpm && pnpm i --frozen-lockfile; \
|
||||
else echo "Lockfile not found." && exit 1; \
|
||||
fi
|
||||
|
||||
FROM base as builder
|
||||
|
||||
ENV NODE_ENV production
|
||||
|
||||
COPY --from=deps /app/refine/node_modules ./node_modules
|
||||
|
||||
COPY . .
|
||||
|
||||
RUN npm run build
|
||||
|
||||
FROM base as runner
|
||||
|
||||
ENV NODE_ENV production
|
||||
|
||||
RUN npm install -g serve
|
||||
|
||||
COPY --from=builder /app/refine/dist ./
|
||||
|
||||
USER refine
|
||||
|
||||
CMD ["serve"]
|
||||
@@ -0,0 +1,49 @@
|
||||
# refine-project
|
||||
|
||||
<div align="center" style="margin: 30px;">
|
||||
<a href="https://refine.dev">
|
||||
<img alt="refine logo" src="https://refine.ams3.cdn.digitaloceanspaces.com/readme/refine-readme-banner.png">
|
||||
</a>
|
||||
</div>
|
||||
<br/>
|
||||
|
||||
This [Refine](https://github.com/refinedev/refine) project was generated with [create refine-app](https://github.com/refinedev/refine/tree/master/packages/create-refine-app).
|
||||
|
||||
## Getting Started
|
||||
|
||||
A React Framework for building internal tools, admin panels, dashboards & B2B apps with unmatched flexibility ✨
|
||||
|
||||
Refine's hooks and components simplifies the development process and eliminates the repetitive tasks by providing industry-standard solutions for crucial aspects of a project, including authentication, access control, routing, networking, state management, and i18n.
|
||||
|
||||
## Available Scripts
|
||||
|
||||
### Running the development server.
|
||||
|
||||
```bash
|
||||
npm run dev
|
||||
```
|
||||
|
||||
### Building for production.
|
||||
|
||||
```bash
|
||||
npm run build
|
||||
```
|
||||
|
||||
### Running the production server.
|
||||
|
||||
```bash
|
||||
npm run start
|
||||
```
|
||||
|
||||
## Learn More
|
||||
|
||||
To learn more about **Refine**, please check out the [Documentation](https://refine.dev/docs)
|
||||
|
||||
- **REST Data Provider** [Docs](https://refine.dev/docs/core/providers/data-provider/#overview)
|
||||
- **Material UI** [Docs](https://refine.dev/docs/ui-frameworks/mui/tutorial/)
|
||||
- **Custom Auth Provider** [Docs](https://refine.dev/docs/core/providers/auth-provider/)
|
||||
- **React Router** [Docs](https://refine.dev/docs/core/providers/router-provider/)
|
||||
|
||||
## License
|
||||
|
||||
MIT
|
||||
@@ -0,0 +1,2 @@
|
||||
/favicon.ico
|
||||
/index.html
|
||||
@@ -0,0 +1 @@
|
||||
/index-a25c81c8.js
|
||||
@@ -0,0 +1,41 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<link rel="icon" href="/favicon.ico" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<meta name="theme-color" content="#000000" />
|
||||
<meta
|
||||
name="description"
|
||||
content="Markus Lehr | VR Predictive Maintenance Device."
|
||||
/>
|
||||
<meta
|
||||
data-rh="true"
|
||||
property="og:image"
|
||||
content="https://refine.dev/img/refine_social.png"
|
||||
/>
|
||||
<meta
|
||||
data-rh="true"
|
||||
name="twitter:image"
|
||||
content="https://refine.dev/img/refine_social.png"
|
||||
/>
|
||||
<title>
|
||||
Markus Lehr | VR Predictive Maintenance Device.
|
||||
</title>
|
||||
</head>
|
||||
<body>
|
||||
<noscript>You need to enable JavaScript to run this app.</noscript>
|
||||
<div id="root"></div>
|
||||
<script type="module" src="/src/index.tsx"></script>
|
||||
<!--
|
||||
This HTML file is a template.
|
||||
If you open it directly in the browser, you will see an empty page.
|
||||
|
||||
You can add webfonts, meta tags, or analytics to this file.
|
||||
The build step will place the bundled scripts into the <body> tag.
|
||||
|
||||
To begin the development, run `npm dev` or `yarn start`.
|
||||
To create a production bundle, use `npm run build` or `yarn build`.
|
||||
-->
|
||||
</body>
|
||||
</html>
|
||||
Generated
+11266
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,60 @@
|
||||
{
|
||||
"name": "vrpmdv",
|
||||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"dependencies": {
|
||||
"@emotion/react": "^11.8.2",
|
||||
"@emotion/styled": "^11.8.1",
|
||||
"@mui/icons-material": "^5.8.3",
|
||||
"@mui/lab": "^5.0.0-alpha.85",
|
||||
"@mui/material": "^5.8.6",
|
||||
"@mui/x-data-grid": "^6.6.0",
|
||||
"@refinedev/cli": "^2.16.1",
|
||||
"@refinedev/core": "^4.47.1",
|
||||
"@refinedev/devtools": "^1.1.35",
|
||||
"@refinedev/kbar": "^1.3.6",
|
||||
"@refinedev/mui": "^5.14.4",
|
||||
"@refinedev/react-hook-form": "^4.8.14",
|
||||
"@refinedev/react-router-v6": "^4.5.5",
|
||||
"@refinedev/simple-rest": "^5.0.1",
|
||||
"react": "^18.0.0",
|
||||
"react-dom": "^18.0.0",
|
||||
"react-hook-form": "^7.30.0",
|
||||
"react-router-dom": "^6.8.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^18.16.2",
|
||||
"@types/react": "^18.0.0",
|
||||
"@types/react-dom": "^18.0.0",
|
||||
"@typescript-eslint/eslint-plugin": "^5.57.1",
|
||||
"@typescript-eslint/parser": "^5.57.1",
|
||||
"@vitejs/plugin-react": "^4.0.0",
|
||||
"eslint": "^8.38.0",
|
||||
"eslint-plugin-react-hooks": "^4.6.0",
|
||||
"eslint-plugin-react-refresh": "^0.3.4",
|
||||
"typescript": "^4.7.4",
|
||||
"vite": "^4.3.1"
|
||||
},
|
||||
"scripts": {
|
||||
"dev": "refine dev",
|
||||
"build": "tsc && refine build",
|
||||
"preview": "refine start",
|
||||
"refine": "refine"
|
||||
},
|
||||
"browserslist": {
|
||||
"production": [
|
||||
">0.2%",
|
||||
"not dead",
|
||||
"not op_mini all"
|
||||
],
|
||||
"development": [
|
||||
"last 1 chrome version",
|
||||
"last 1 firefox version",
|
||||
"last 1 safari version"
|
||||
]
|
||||
},
|
||||
"refine": {
|
||||
"projectId": "4LRbGi-1874iA-m2rbd1"
|
||||
}
|
||||
}
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 99 KiB |
@@ -0,0 +1,184 @@
|
||||
import { Authenticated, GitHubBanner, Refine } from "@refinedev/core";
|
||||
import { DevtoolsPanel, DevtoolsProvider } from "@refinedev/devtools";
|
||||
import { RefineKbar, RefineKbarProvider } from "@refinedev/kbar";
|
||||
|
||||
import {
|
||||
ErrorComponent,
|
||||
notificationProvider,
|
||||
RefineSnackbarProvider,
|
||||
ThemedLayoutV2,
|
||||
ThemedTitleV2,
|
||||
} from "@refinedev/mui";
|
||||
|
||||
import CssBaseline from "@mui/material/CssBaseline";
|
||||
import GlobalStyles from "@mui/material/GlobalStyles";
|
||||
import routerBindings, {
|
||||
CatchAllNavigate,
|
||||
DocumentTitleHandler,
|
||||
NavigateToResource,
|
||||
UnsavedChangesNotifier,
|
||||
} from "@refinedev/react-router-v6";
|
||||
// import dataProvider from "@refinedev/simple-rest";
|
||||
import { dataProvider } from './dataprovider/dataprovider'
|
||||
import { BrowserRouter, Outlet, Route, Routes } from "react-router-dom";
|
||||
import { authProvider } from "./authProvider";
|
||||
import { AppIcon } from "./components/app-icon";
|
||||
import { Header } from "./components/header";
|
||||
import { ColorModeContextProvider } from "./contexts/color-mode";
|
||||
import {
|
||||
BlogPostCreate,
|
||||
BlogPostEdit,
|
||||
BlogPostList,
|
||||
BlogPostShow,
|
||||
} from "./pages/blog-posts";
|
||||
import {
|
||||
CategoryCreate,
|
||||
CategoryEdit,
|
||||
CategoryList,
|
||||
CategoryShow,
|
||||
} from "./pages/categories";
|
||||
import { ForgotPassword } from "./pages/forgotPassword";
|
||||
import { Login } from "./pages/login";
|
||||
import { Register } from "./pages/register";
|
||||
import { MonitoringList } from "./pages/monitorings/list";
|
||||
import { MonitoringCreate } from "./pages/monitorings/create";
|
||||
import { MonitoringEdit } from "./pages/monitorings/edit";
|
||||
import { MonitoringShow } from "./pages/monitorings/show";
|
||||
|
||||
const API_URL = "https://api.fake-rest.refine.dev";
|
||||
const MONITORINGS_API_URL = "http://127.0.0.1:5000//vrpmdvapi/1_0";
|
||||
|
||||
function App() {
|
||||
return (
|
||||
<BrowserRouter>
|
||||
<RefineKbarProvider>
|
||||
<ColorModeContextProvider>
|
||||
<CssBaseline />
|
||||
<GlobalStyles styles={{ html: { WebkitFontSmoothing: "auto" } }} />
|
||||
<RefineSnackbarProvider>
|
||||
<DevtoolsProvider>
|
||||
<Refine
|
||||
dataProvider={{
|
||||
default: dataProvider(API_URL),
|
||||
monitorings: dataProvider(MONITORINGS_API_URL),
|
||||
}}
|
||||
notificationProvider={notificationProvider}
|
||||
authProvider={authProvider}
|
||||
routerProvider={routerBindings}
|
||||
resources={[
|
||||
{
|
||||
name: "monitorings",
|
||||
list: "/monitorings",
|
||||
create: "/monitorings/create",
|
||||
edit: "/monitorings/edit/:id",
|
||||
meta: {
|
||||
canDelete: true,
|
||||
dataProviderName: "monitorings"
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "blog_posts",
|
||||
list: "/blog-posts",
|
||||
create: "/blog-posts/create",
|
||||
edit: "/blog-posts/edit/:id",
|
||||
show: "/blog-posts/show/:id",
|
||||
meta: {
|
||||
canDelete: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "categories",
|
||||
list: "/categories",
|
||||
create: "/categories/create",
|
||||
edit: "/categories/edit/:id",
|
||||
show: "/categories/show/:id",
|
||||
meta: {
|
||||
canDelete: true,
|
||||
},
|
||||
},
|
||||
]}
|
||||
options={{
|
||||
syncWithLocation: true,
|
||||
warnWhenUnsavedChanges: true,
|
||||
useNewQueryKeys: true,
|
||||
projectId: "4LRbGi-1874iA-m2rbd1",
|
||||
}}
|
||||
>
|
||||
<Routes>
|
||||
<Route
|
||||
element={
|
||||
<Authenticated
|
||||
key="authenticated-inner"
|
||||
fallback={<CatchAllNavigate to="/login" />}
|
||||
>
|
||||
<ThemedLayoutV2
|
||||
Header={() => <Header sticky />}
|
||||
Title={({ collapsed }) => (
|
||||
<ThemedTitleV2
|
||||
collapsed={collapsed}
|
||||
text="Refine Project"
|
||||
icon={<AppIcon />}
|
||||
/>
|
||||
)}
|
||||
>
|
||||
<Outlet />
|
||||
</ThemedLayoutV2>
|
||||
</Authenticated>
|
||||
}
|
||||
>
|
||||
<Route
|
||||
index
|
||||
element={<NavigateToResource resource="monitorings" />}
|
||||
/>
|
||||
<Route path="/monitorings">
|
||||
<Route index element={<MonitoringList />} />
|
||||
<Route path="create" element={<MonitoringCreate />} />
|
||||
<Route path="edit/:id" element={<MonitoringEdit />} />
|
||||
</Route>
|
||||
<Route path="/blog-posts">
|
||||
<Route index element={<BlogPostList />} />
|
||||
<Route path="create" element={<BlogPostCreate />} />
|
||||
<Route path="edit/:id" element={<BlogPostEdit />} />
|
||||
<Route path="show/:id" element={<BlogPostShow />} />
|
||||
</Route>
|
||||
<Route path="/categories">
|
||||
<Route index element={<CategoryList />} />
|
||||
<Route path="create" element={<CategoryCreate />} />
|
||||
<Route path="edit/:id" element={<CategoryEdit />} />
|
||||
<Route path="show/:id" element={<CategoryShow />} />
|
||||
</Route>
|
||||
<Route path="*" element={<ErrorComponent />} />
|
||||
</Route>
|
||||
<Route
|
||||
element={
|
||||
<Authenticated
|
||||
key="authenticated-outer"
|
||||
fallback={<Outlet />}
|
||||
>
|
||||
<NavigateToResource />
|
||||
</Authenticated>
|
||||
}
|
||||
>
|
||||
<Route path="/login" element={<Login />} />
|
||||
<Route path="/register" element={<Register />} />
|
||||
<Route
|
||||
path="/forgot-password"
|
||||
element={<ForgotPassword />}
|
||||
/>
|
||||
</Route>
|
||||
</Routes>
|
||||
|
||||
<RefineKbar />
|
||||
<UnsavedChangesNotifier />
|
||||
<DocumentTitleHandler />
|
||||
</Refine>
|
||||
<DevtoolsPanel />
|
||||
</DevtoolsProvider>
|
||||
</RefineSnackbarProvider>
|
||||
</ColorModeContextProvider>
|
||||
</RefineKbarProvider>
|
||||
</BrowserRouter>
|
||||
);
|
||||
}
|
||||
|
||||
export default App;
|
||||
@@ -0,0 +1,59 @@
|
||||
import { AuthBindings } from "@refinedev/core";
|
||||
|
||||
export const TOKEN_KEY = "refine-auth";
|
||||
|
||||
export const authProvider: AuthBindings = {
|
||||
login: async ({ username, email, password }) => {
|
||||
if ((username || email) && password) {
|
||||
localStorage.setItem(TOKEN_KEY, username);
|
||||
return {
|
||||
success: true,
|
||||
redirectTo: "/",
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
success: false,
|
||||
error: {
|
||||
name: "LoginError",
|
||||
message: "Invalid username or password",
|
||||
},
|
||||
};
|
||||
},
|
||||
logout: async () => {
|
||||
localStorage.removeItem(TOKEN_KEY);
|
||||
return {
|
||||
success: true,
|
||||
redirectTo: "/login",
|
||||
};
|
||||
},
|
||||
check: async () => {
|
||||
const token = localStorage.getItem(TOKEN_KEY);
|
||||
if (token) {
|
||||
return {
|
||||
authenticated: true,
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
authenticated: false,
|
||||
redirectTo: "/login",
|
||||
};
|
||||
},
|
||||
getPermissions: async () => null,
|
||||
getIdentity: async () => {
|
||||
const token = localStorage.getItem(TOKEN_KEY);
|
||||
if (token) {
|
||||
return {
|
||||
id: 1,
|
||||
name: "John Doe",
|
||||
avatar: "https://i.pravatar.cc/300",
|
||||
};
|
||||
}
|
||||
return null;
|
||||
},
|
||||
onError: async (error) => {
|
||||
console.error(error);
|
||||
return { error };
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,22 @@
|
||||
import React from "react";
|
||||
|
||||
export const AppIcon: React.FC = () => {
|
||||
return (
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="24"
|
||||
height="24"
|
||||
viewBox="0 0 16 16"
|
||||
fill="none"
|
||||
>
|
||||
<g fill="currentColor">
|
||||
<path d="M8 6a2 2 0 1 0 0-4 2 2 0 0 0 0 4z" />
|
||||
<path
|
||||
fill-rule="evenodd"
|
||||
d="M16 8A8 8 0 1 1 0 8a8 8 0 0 1 16 0zM5.333 4a2.667 2.667 0 0 1 5.334 0v8a2.667 2.667 0 1 1-5.334 0z"
|
||||
clip-rule="evenodd"
|
||||
/>
|
||||
</g>
|
||||
</svg>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,80 @@
|
||||
import DarkModeOutlined from "@mui/icons-material/DarkModeOutlined";
|
||||
import LightModeOutlined from "@mui/icons-material/LightModeOutlined";
|
||||
import AppBar from "@mui/material/AppBar";
|
||||
import Avatar from "@mui/material/Avatar";
|
||||
import IconButton from "@mui/material/IconButton";
|
||||
import Stack from "@mui/material/Stack";
|
||||
import Toolbar from "@mui/material/Toolbar";
|
||||
import Typography from "@mui/material/Typography";
|
||||
import { useGetIdentity } from "@refinedev/core";
|
||||
import { HamburgerMenu, RefineThemedLayoutV2HeaderProps } from "@refinedev/mui";
|
||||
import React, { useContext } from "react";
|
||||
import { ColorModeContext } from "../../contexts/color-mode";
|
||||
|
||||
type IUser = {
|
||||
id: number;
|
||||
name: string;
|
||||
avatar: string;
|
||||
};
|
||||
|
||||
export const Header: React.FC<RefineThemedLayoutV2HeaderProps> = ({
|
||||
sticky = true,
|
||||
}) => {
|
||||
const { mode, setMode } = useContext(ColorModeContext);
|
||||
|
||||
const { data: user } = useGetIdentity<IUser>();
|
||||
|
||||
return (
|
||||
<AppBar position={sticky ? "sticky" : "relative"}>
|
||||
<Toolbar>
|
||||
<Stack
|
||||
direction="row"
|
||||
width="100%"
|
||||
justifyContent="flex-end"
|
||||
alignItems="center"
|
||||
>
|
||||
<HamburgerMenu />
|
||||
<Stack
|
||||
direction="row"
|
||||
width="100%"
|
||||
justifyContent="flex-end"
|
||||
alignItems="center"
|
||||
>
|
||||
<IconButton
|
||||
color="inherit"
|
||||
onClick={() => {
|
||||
setMode();
|
||||
}}
|
||||
>
|
||||
{mode === "dark" ? <LightModeOutlined /> : <DarkModeOutlined />}
|
||||
</IconButton>
|
||||
|
||||
{(user?.avatar || user?.name) && (
|
||||
<Stack
|
||||
direction="row"
|
||||
gap="16px"
|
||||
alignItems="center"
|
||||
justifyContent="center"
|
||||
>
|
||||
{user?.name && (
|
||||
<Typography
|
||||
sx={{
|
||||
display: {
|
||||
xs: "none",
|
||||
sm: "inline-block",
|
||||
},
|
||||
}}
|
||||
variant="subtitle2"
|
||||
>
|
||||
{user?.name}
|
||||
</Typography>
|
||||
)}
|
||||
<Avatar src={user?.avatar} alt={user?.name} />
|
||||
</Stack>
|
||||
)}
|
||||
</Stack>
|
||||
</Stack>
|
||||
</Toolbar>
|
||||
</AppBar>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1 @@
|
||||
export { Header } from "./header";
|
||||
@@ -0,0 +1,51 @@
|
||||
|
||||
|
||||
import TextField from "@mui/material/TextField";
|
||||
|
||||
interface NumericInputProps {
|
||||
label: string;
|
||||
value: string;
|
||||
}
|
||||
|
||||
export const NumericInput = ({label:string, value}) : NumericInputProps => {
|
||||
|
||||
return (
|
||||
<TextField
|
||||
type="number"
|
||||
label={label}
|
||||
value={value}
|
||||
//defaultValue="10"
|
||||
// error={!!(errors as any)?.samplerate}
|
||||
// helperText={(errors as any)?.samplerate?.message}
|
||||
margin="normal"
|
||||
fullWidth
|
||||
InputLabelProps={{ shrink: true }}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
// function NumericInput() {
|
||||
// return (
|
||||
// <TextField
|
||||
// type="number"
|
||||
// label="Enter a number"
|
||||
// defaultValue="10"
|
||||
// // Other props
|
||||
// />
|
||||
// );
|
||||
// }
|
||||
|
||||
|
||||
|
||||
// {...register("samplerate", {
|
||||
// required: "The samplerate is required",
|
||||
// })}
|
||||
// error={!!(errors as any)?.samplerate}
|
||||
// helperText={(errors as any)?.samplerate?.message}
|
||||
// margin="normal"
|
||||
// fullWidth
|
||||
// InputLabelProps={{ shrink: true }}
|
||||
// type="number"
|
||||
// label={"Samplerate"}
|
||||
// name="samplerate"
|
||||
@@ -0,0 +1,59 @@
|
||||
import { ThemeProvider } from "@mui/material/styles";
|
||||
import { RefineThemes } from "@refinedev/mui";
|
||||
import React, {
|
||||
createContext,
|
||||
PropsWithChildren,
|
||||
useEffect,
|
||||
useState,
|
||||
} from "react";
|
||||
|
||||
type ColorModeContextType = {
|
||||
mode: string;
|
||||
setMode: () => void;
|
||||
};
|
||||
|
||||
export const ColorModeContext = createContext<ColorModeContextType>(
|
||||
{} as ColorModeContextType
|
||||
);
|
||||
|
||||
export const ColorModeContextProvider: React.FC<PropsWithChildren> = ({
|
||||
children,
|
||||
}) => {
|
||||
const colorModeFromLocalStorage = localStorage.getItem("colorMode");
|
||||
const isSystemPreferenceDark = window?.matchMedia(
|
||||
"(prefers-color-scheme: dark)"
|
||||
).matches;
|
||||
|
||||
const systemPreference = isSystemPreferenceDark ? "dark" : "light";
|
||||
const [mode, setMode] = useState(
|
||||
colorModeFromLocalStorage || systemPreference
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
window.localStorage.setItem("colorMode", mode);
|
||||
}, [mode]);
|
||||
|
||||
const setColorMode = () => {
|
||||
if (mode === "light") {
|
||||
setMode("dark");
|
||||
} else {
|
||||
setMode("light");
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<ColorModeContext.Provider
|
||||
value={{
|
||||
setMode: setColorMode,
|
||||
mode,
|
||||
}}
|
||||
>
|
||||
<ThemeProvider
|
||||
// you can change the theme colors here. example: mode === "light" ? RefineThemes.Magenta : RefineThemes.MagentaDark
|
||||
theme={mode === "light" ? RefineThemes.Blue : RefineThemes.BlueDark}
|
||||
>
|
||||
{children}
|
||||
</ThemeProvider>
|
||||
</ColorModeContext.Provider>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,197 @@
|
||||
import { AxiosInstance } from "axios";
|
||||
import { stringify } from "query-string";
|
||||
import { DataProvider } from "@refinedev/core";
|
||||
import { axiosInstance, generateSort, generateFilter } from "./utils";
|
||||
|
||||
type MethodTypes = "get" | "delete" | "head" | "options";
|
||||
type MethodTypesWithBody = "post" | "put" | "patch";
|
||||
|
||||
export const dataProvider = (
|
||||
apiUrl: string,
|
||||
httpClient: AxiosInstance = axiosInstance,
|
||||
): Omit<
|
||||
Required<DataProvider>,
|
||||
"createMany" | "updateMany" | "deleteMany"
|
||||
> => ({
|
||||
getList: async ({ resource, pagination, filters, sorters, meta }) => {
|
||||
const url = `${apiUrl}/${resource}`;
|
||||
|
||||
const { current = 1, pageSize = 10, mode = "server" } = pagination ?? {};
|
||||
|
||||
const { headers: headersFromMeta, method } = meta ?? {};
|
||||
const requestMethod = (method as MethodTypes) ?? "get";
|
||||
|
||||
const queryFilters = generateFilter(filters);
|
||||
|
||||
const query: {
|
||||
_start?: number;
|
||||
_end?: number;
|
||||
_sort?: string;
|
||||
_order?: string;
|
||||
} = {};
|
||||
|
||||
if (mode === "server") {
|
||||
query._start = (current - 1) * pageSize;
|
||||
query._end = current * pageSize;
|
||||
}
|
||||
|
||||
const generatedSort = generateSort(sorters);
|
||||
if (generatedSort) {
|
||||
const { _sort, _order } = generatedSort;
|
||||
query._sort = _sort.join(",");
|
||||
query._order = _order.join(",");
|
||||
}
|
||||
|
||||
const combinedQuery = { ...query, ...queryFilters };
|
||||
const urlWithQuery = Object.keys(combinedQuery).length
|
||||
? `${url}?${stringify(combinedQuery)}`
|
||||
: url;
|
||||
|
||||
const { data, headers } = await httpClient[requestMethod](urlWithQuery, {
|
||||
headers: headersFromMeta,
|
||||
});
|
||||
|
||||
const total = +headers["x-total-count"];
|
||||
|
||||
return {
|
||||
data,
|
||||
total: total || data.length,
|
||||
};
|
||||
},
|
||||
|
||||
getMany: async ({ resource, ids, meta }) => {
|
||||
const { headers, method } = meta ?? {};
|
||||
const requestMethod = (method as MethodTypes) ?? "get";
|
||||
|
||||
const { data } = await httpClient[requestMethod](
|
||||
`${apiUrl}/${resource}?${stringify({ id: ids })}`,
|
||||
{ headers },
|
||||
);
|
||||
|
||||
return {
|
||||
data,
|
||||
};
|
||||
},
|
||||
|
||||
create: async ({ resource, variables, meta }) => {
|
||||
const url = `${apiUrl}/${resource}`;
|
||||
|
||||
const { headers, method } = meta ?? {};
|
||||
const requestMethod = (method as MethodTypesWithBody) ?? "post";
|
||||
|
||||
const { data } = await httpClient[requestMethod](url, variables, {
|
||||
headers,
|
||||
});
|
||||
|
||||
return {
|
||||
data,
|
||||
};
|
||||
},
|
||||
|
||||
update: async ({ resource, id, variables, meta }) => {
|
||||
const url = `${apiUrl}/${resource}/${id}`;
|
||||
|
||||
const { headers, method } = meta ?? {};
|
||||
const requestMethod = (method as MethodTypesWithBody) ?? "patch";
|
||||
|
||||
const { data } = await httpClient[requestMethod](url, variables, {
|
||||
headers,
|
||||
});
|
||||
|
||||
return {
|
||||
data,
|
||||
};
|
||||
},
|
||||
|
||||
getOne: async ({ resource, id, meta }) => {
|
||||
const url = `${apiUrl}/${resource}/${id}`;
|
||||
|
||||
const { headers, method } = meta ?? {};
|
||||
const requestMethod = (method as MethodTypes) ?? "get";
|
||||
|
||||
const { data } = await httpClient[requestMethod](url, { headers });
|
||||
|
||||
return {
|
||||
data,
|
||||
};
|
||||
},
|
||||
|
||||
deleteOne: async ({ resource, id, variables, meta }) => {
|
||||
const url = `${apiUrl}/${resource}/${id}`;
|
||||
|
||||
const { headers, method } = meta ?? {};
|
||||
const requestMethod = (method as MethodTypesWithBody) ?? "delete";
|
||||
|
||||
const { data } = await httpClient[requestMethod](url, {
|
||||
data: variables,
|
||||
headers,
|
||||
});
|
||||
|
||||
return {
|
||||
data,
|
||||
};
|
||||
},
|
||||
|
||||
getApiUrl: () => {
|
||||
return apiUrl;
|
||||
},
|
||||
|
||||
custom: async ({
|
||||
url,
|
||||
method,
|
||||
filters,
|
||||
sorters,
|
||||
payload,
|
||||
query,
|
||||
headers,
|
||||
}) => {
|
||||
let requestUrl = `${url}?`;
|
||||
|
||||
if (sorters) {
|
||||
const generatedSort = generateSort(sorters);
|
||||
if (generatedSort) {
|
||||
const { _sort, _order } = generatedSort;
|
||||
const sortQuery = {
|
||||
_sort: _sort.join(","),
|
||||
_order: _order.join(","),
|
||||
};
|
||||
requestUrl = `${requestUrl}&${stringify(sortQuery)}`;
|
||||
}
|
||||
}
|
||||
|
||||
if (filters) {
|
||||
const filterQuery = generateFilter(filters);
|
||||
requestUrl = `${requestUrl}&${stringify(filterQuery)}`;
|
||||
}
|
||||
|
||||
if (query) {
|
||||
requestUrl = `${requestUrl}&${stringify(query)}`;
|
||||
}
|
||||
|
||||
let axiosResponse;
|
||||
switch (method) {
|
||||
case "put":
|
||||
case "post":
|
||||
case "patch":
|
||||
axiosResponse = await httpClient[method](url, payload, {
|
||||
headers,
|
||||
});
|
||||
break;
|
||||
case "delete":
|
||||
axiosResponse = await httpClient.delete(url, {
|
||||
data: payload,
|
||||
headers: headers,
|
||||
});
|
||||
break;
|
||||
default:
|
||||
axiosResponse = await httpClient.get(requestUrl, {
|
||||
headers,
|
||||
});
|
||||
break;
|
||||
}
|
||||
|
||||
const { data } = axiosResponse;
|
||||
|
||||
return Promise.resolve({ data });
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,21 @@
|
||||
import { HttpError } from "@refinedev/core";
|
||||
import axios from "axios";
|
||||
|
||||
const axiosInstance = axios.create();
|
||||
|
||||
axiosInstance.interceptors.response.use(
|
||||
(response) => {
|
||||
return response;
|
||||
},
|
||||
(error) => {
|
||||
const customError: HttpError = {
|
||||
...error,
|
||||
message: error.response?.data?.message,
|
||||
statusCode: error.response?.status,
|
||||
};
|
||||
|
||||
return Promise.reject(customError);
|
||||
},
|
||||
);
|
||||
|
||||
export { axiosInstance };
|
||||
@@ -0,0 +1,30 @@
|
||||
import { CrudFilters } from "@refinedev/core";
|
||||
import { mapOperator } from "./mapOperator";
|
||||
|
||||
export const generateFilter = (filters?: CrudFilters) => {
|
||||
const queryFilters: { [key: string]: string } = {};
|
||||
|
||||
if (filters) {
|
||||
filters.map((filter) => {
|
||||
if (filter.operator === "or" || filter.operator === "and") {
|
||||
throw new Error(
|
||||
`[@refinedev/simple-rest]: \`operator: ${filter.operator}\` is not supported. You can create custom data provider. https://refine.dev/docs/api-reference/core/providers/data-provider/#creating-a-data-provider`,
|
||||
);
|
||||
}
|
||||
|
||||
if ("field" in filter) {
|
||||
const { field, operator, value } = filter;
|
||||
|
||||
if (field === "q") {
|
||||
queryFilters[field] = value;
|
||||
return;
|
||||
}
|
||||
|
||||
const mappedOperator = mapOperator(operator);
|
||||
queryFilters[`${field}${mappedOperator}`] = value;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return queryFilters;
|
||||
};
|
||||
@@ -0,0 +1,20 @@
|
||||
import { CrudSorting } from "@refinedev/core";
|
||||
|
||||
export const generateSort = (sorters?: CrudSorting) => {
|
||||
if (sorters && sorters.length > 0) {
|
||||
const _sort: string[] = [];
|
||||
const _order: string[] = [];
|
||||
|
||||
sorters.map((item) => {
|
||||
_sort.push(item.field);
|
||||
_order.push(item.order);
|
||||
});
|
||||
|
||||
return {
|
||||
_sort,
|
||||
_order,
|
||||
};
|
||||
}
|
||||
|
||||
return;
|
||||
};
|
||||
@@ -0,0 +1,4 @@
|
||||
export { mapOperator } from "./mapOperator";
|
||||
export { generateSort } from "./generateSort";
|
||||
export { generateFilter } from "./generateFilter";
|
||||
export { axiosInstance } from "./axios";
|
||||
@@ -0,0 +1,14 @@
|
||||
import { CrudOperators } from "@refinedev/core";
|
||||
|
||||
export const mapOperator = (operator: CrudOperators): string => {
|
||||
switch (operator) {
|
||||
case "ne":
|
||||
case "gte":
|
||||
case "lte":
|
||||
return `_${operator}`;
|
||||
case "contains":
|
||||
return "_like";
|
||||
default:
|
||||
return "";
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,13 @@
|
||||
import React from "react";
|
||||
import { createRoot } from "react-dom/client";
|
||||
|
||||
import App from "./App";
|
||||
|
||||
const container = document.getElementById("root") as HTMLElement;
|
||||
const root = createRoot(container);
|
||||
|
||||
root.render(
|
||||
<React.StrictMode>
|
||||
<App />
|
||||
</React.StrictMode>
|
||||
);
|
||||
@@ -0,0 +1,119 @@
|
||||
import { Autocomplete, Box, MenuItem, Select, TextField } from "@mui/material";
|
||||
import { Create, useAutocomplete } from "@refinedev/mui";
|
||||
import { useForm } from "@refinedev/react-hook-form";
|
||||
import { Controller } from "react-hook-form";
|
||||
|
||||
export const BlogPostCreate = () => {
|
||||
const {
|
||||
saveButtonProps,
|
||||
refineCore: { formLoading },
|
||||
register,
|
||||
control,
|
||||
formState: { errors },
|
||||
} = useForm({});
|
||||
|
||||
const { autocompleteProps: categoryAutocompleteProps } = useAutocomplete({
|
||||
resource: "categories",
|
||||
});
|
||||
|
||||
return (
|
||||
<Create isLoading={formLoading} saveButtonProps={saveButtonProps}>
|
||||
<Box
|
||||
component="form"
|
||||
sx={{ display: "flex", flexDirection: "column" }}
|
||||
autoComplete="off"
|
||||
>
|
||||
<TextField
|
||||
{...register("title", {
|
||||
required: "This field is required",
|
||||
})}
|
||||
error={!!(errors as any)?.title}
|
||||
helperText={(errors as any)?.title?.message}
|
||||
margin="normal"
|
||||
fullWidth
|
||||
InputLabelProps={{ shrink: true }}
|
||||
type="text"
|
||||
label={"Title"}
|
||||
name="title"
|
||||
/>
|
||||
<TextField
|
||||
{...register("content", {
|
||||
required: "This field is required",
|
||||
})}
|
||||
error={!!(errors as any)?.content}
|
||||
helperText={(errors as any)?.content?.message}
|
||||
margin="normal"
|
||||
fullWidth
|
||||
InputLabelProps={{ shrink: true }}
|
||||
multiline
|
||||
label={"Content"}
|
||||
name="content"
|
||||
/>
|
||||
<Controller
|
||||
control={control}
|
||||
name={"category.id"}
|
||||
rules={{ required: "This field is required" }}
|
||||
// eslint-disable-next-line
|
||||
defaultValue={null as any}
|
||||
render={({ field }) => (
|
||||
<Autocomplete
|
||||
{...categoryAutocompleteProps}
|
||||
{...field}
|
||||
onChange={(_, value) => {
|
||||
field.onChange(value.id);
|
||||
}}
|
||||
getOptionLabel={(item) => {
|
||||
return (
|
||||
categoryAutocompleteProps?.options?.find((p) => {
|
||||
const itemId =
|
||||
typeof item === "object"
|
||||
? item?.id?.toString()
|
||||
: item?.toString();
|
||||
const pId = p?.id?.toString();
|
||||
return itemId === pId;
|
||||
})?.title ?? ""
|
||||
);
|
||||
}}
|
||||
isOptionEqualToValue={(option, value) => {
|
||||
const optionId = option?.id?.toString();
|
||||
const valueId =
|
||||
typeof value === "object"
|
||||
? value?.id?.toString()
|
||||
: value?.toString();
|
||||
return value === undefined || optionId === valueId;
|
||||
}}
|
||||
renderInput={(params) => (
|
||||
<TextField
|
||||
{...params}
|
||||
label={"Category"}
|
||||
margin="normal"
|
||||
variant="outlined"
|
||||
error={!!(errors as any)?.category?.id}
|
||||
helperText={(errors as any)?.category?.id?.message}
|
||||
required
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
<Controller
|
||||
name="status"
|
||||
control={control}
|
||||
render={({ field }) => {
|
||||
return (
|
||||
<Select
|
||||
{...field}
|
||||
value={field?.value || "draft"}
|
||||
label={"Status"}
|
||||
>
|
||||
<MenuItem value="draft">Draft</MenuItem>
|
||||
<MenuItem value="published">Published</MenuItem>
|
||||
<MenuItem value="rejected">Rejected</MenuItem>
|
||||
</Select>
|
||||
);
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
</Create>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,124 @@
|
||||
import { Autocomplete, Box, Select, TextField } from "@mui/material";
|
||||
import MenuItem from "@mui/material/MenuItem";
|
||||
import { Edit, useAutocomplete } from "@refinedev/mui";
|
||||
import { useForm } from "@refinedev/react-hook-form";
|
||||
import { Controller } from "react-hook-form";
|
||||
|
||||
export const BlogPostEdit = () => {
|
||||
const {
|
||||
saveButtonProps,
|
||||
refineCore: { queryResult, formLoading },
|
||||
register,
|
||||
control,
|
||||
formState: { errors },
|
||||
} = useForm({});
|
||||
|
||||
const blogPostsData = queryResult?.data?.data;
|
||||
|
||||
const { autocompleteProps: categoryAutocompleteProps } = useAutocomplete({
|
||||
resource: "categories",
|
||||
defaultValue: blogPostsData?.category?.id,
|
||||
});
|
||||
|
||||
return (
|
||||
<Edit isLoading={formLoading} saveButtonProps={saveButtonProps}>
|
||||
<Box
|
||||
component="form"
|
||||
sx={{ display: "flex", flexDirection: "column" }}
|
||||
autoComplete="off"
|
||||
>
|
||||
<TextField
|
||||
{...register("title", {
|
||||
required: "This field is required",
|
||||
})}
|
||||
error={!!(errors as any)?.title}
|
||||
helperText={(errors as any)?.title?.message}
|
||||
margin="normal"
|
||||
fullWidth
|
||||
InputLabelProps={{ shrink: true }}
|
||||
type="text"
|
||||
label={"Title"}
|
||||
name="title"
|
||||
/>
|
||||
<TextField
|
||||
{...register("content", {
|
||||
required: "This field is required",
|
||||
})}
|
||||
error={!!(errors as any)?.content}
|
||||
helperText={(errors as any)?.content?.message}
|
||||
margin="normal"
|
||||
fullWidth
|
||||
InputLabelProps={{ shrink: true }}
|
||||
multiline
|
||||
label={"Content"}
|
||||
name="content"
|
||||
rows={4}
|
||||
/>
|
||||
<Controller
|
||||
control={control}
|
||||
name={"category.id"}
|
||||
rules={{ required: "This field is required" }}
|
||||
// eslint-disable-next-line
|
||||
defaultValue={null as any}
|
||||
render={({ field }) => (
|
||||
<Autocomplete
|
||||
{...categoryAutocompleteProps}
|
||||
{...field}
|
||||
onChange={(_, value) => {
|
||||
field.onChange(value.id);
|
||||
}}
|
||||
getOptionLabel={(item) => {
|
||||
return (
|
||||
categoryAutocompleteProps?.options?.find((p) => {
|
||||
const itemId =
|
||||
typeof item === "object"
|
||||
? item?.id?.toString()
|
||||
: item?.toString();
|
||||
const pId = p?.id?.toString();
|
||||
return itemId === pId;
|
||||
})?.title ?? ""
|
||||
);
|
||||
}}
|
||||
isOptionEqualToValue={(option, value) => {
|
||||
const optionId = option?.id?.toString();
|
||||
const valueId =
|
||||
typeof value === "object"
|
||||
? value?.id?.toString()
|
||||
: value?.toString();
|
||||
return value === undefined || optionId === valueId;
|
||||
}}
|
||||
renderInput={(params) => (
|
||||
<TextField
|
||||
{...params}
|
||||
label={"Category"}
|
||||
margin="normal"
|
||||
variant="outlined"
|
||||
error={!!(errors as any)?.category?.id}
|
||||
helperText={(errors as any)?.category?.id?.message}
|
||||
required
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
<Controller
|
||||
name="status"
|
||||
control={control}
|
||||
render={({ field }) => {
|
||||
return (
|
||||
<Select
|
||||
{...field}
|
||||
value={field?.value || "draft"}
|
||||
label={"Status"}
|
||||
>
|
||||
<MenuItem value="draft">Draft</MenuItem>
|
||||
<MenuItem value="published">Published</MenuItem>
|
||||
<MenuItem value="rejected">Rejected</MenuItem>
|
||||
</Select>
|
||||
);
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
</Edit>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,4 @@
|
||||
export * from "./create";
|
||||
export * from "./edit";
|
||||
export * from "./list";
|
||||
export * from "./show";
|
||||
@@ -0,0 +1,112 @@
|
||||
import { DataGrid, GridColDef } from "@mui/x-data-grid";
|
||||
import { useMany } from "@refinedev/core";
|
||||
import {
|
||||
DateField,
|
||||
DeleteButton,
|
||||
EditButton,
|
||||
List,
|
||||
MarkdownField,
|
||||
ShowButton,
|
||||
useDataGrid,
|
||||
} from "@refinedev/mui";
|
||||
import React from "react";
|
||||
|
||||
export const BlogPostList = () => {
|
||||
const { dataGridProps } = useDataGrid({
|
||||
syncWithLocation: true,
|
||||
});
|
||||
|
||||
const { data: categoryData, isLoading: categoryIsLoading } = useMany({
|
||||
resource: "categories",
|
||||
ids:
|
||||
dataGridProps?.rows
|
||||
?.map((item: any) => item?.category?.id)
|
||||
.filter(Boolean) ?? [],
|
||||
queryOptions: {
|
||||
enabled: !!dataGridProps?.rows,
|
||||
},
|
||||
});
|
||||
|
||||
const columns = React.useMemo<GridColDef[]>(
|
||||
() => [
|
||||
{
|
||||
field: "id",
|
||||
headerName: "ID",
|
||||
type: "number",
|
||||
minWidth: 50,
|
||||
},
|
||||
{
|
||||
field: "title",
|
||||
flex: 1,
|
||||
headerName: "Title",
|
||||
minWidth: 200,
|
||||
},
|
||||
{
|
||||
field: "content",
|
||||
flex: 1,
|
||||
headerName: "content",
|
||||
minWidth: 250,
|
||||
renderCell: function render({ value }) {
|
||||
if (!value) return "-";
|
||||
return <MarkdownField value={value?.slice(0, 80) + "..." || ""} />;
|
||||
},
|
||||
},
|
||||
{
|
||||
field: "category",
|
||||
flex: 1,
|
||||
headerName: "Category",
|
||||
minWidth: 300,
|
||||
valueGetter: ({ row }) => {
|
||||
const value = row?.category;
|
||||
return value;
|
||||
},
|
||||
renderCell: function render({ value }) {
|
||||
return categoryIsLoading ? (
|
||||
<>Loading...</>
|
||||
) : (
|
||||
categoryData?.data?.find((item) => item.id === value?.id)?.title
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
field: "status",
|
||||
flex: 1,
|
||||
headerName: "Status",
|
||||
minWidth: 200,
|
||||
},
|
||||
{
|
||||
field: "createdAt",
|
||||
flex: 1,
|
||||
headerName: "Created at",
|
||||
minWidth: 250,
|
||||
renderCell: function render({ value }) {
|
||||
return <DateField value={value} />;
|
||||
},
|
||||
},
|
||||
{
|
||||
field: "actions",
|
||||
headerName: "Actions",
|
||||
sortable: false,
|
||||
renderCell: function render({ row }) {
|
||||
return (
|
||||
<>
|
||||
<EditButton hideText recordItemId={row.id} />
|
||||
<ShowButton hideText recordItemId={row.id} />
|
||||
<DeleteButton hideText recordItemId={row.id} />
|
||||
</>
|
||||
);
|
||||
},
|
||||
align: "center",
|
||||
headerAlign: "center",
|
||||
minWidth: 80,
|
||||
},
|
||||
],
|
||||
[categoryData]
|
||||
);
|
||||
|
||||
return (
|
||||
<List>
|
||||
<DataGrid {...dataGridProps} columns={columns} autoHeight />
|
||||
</List>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,59 @@
|
||||
import { Stack, Typography } from "@mui/material";
|
||||
import { useOne, useShow } from "@refinedev/core";
|
||||
import {
|
||||
DateField,
|
||||
MarkdownField,
|
||||
NumberField,
|
||||
Show,
|
||||
TextFieldComponent as TextField,
|
||||
} from "@refinedev/mui";
|
||||
|
||||
export const BlogPostShow = () => {
|
||||
const { queryResult } = useShow({});
|
||||
|
||||
const { data, isLoading } = queryResult;
|
||||
|
||||
const record = data?.data;
|
||||
|
||||
const { data: categoryData, isLoading: categoryIsLoading } = useOne({
|
||||
resource: "categories",
|
||||
id: record?.category?.id || "",
|
||||
queryOptions: {
|
||||
enabled: !!record,
|
||||
},
|
||||
});
|
||||
|
||||
return (
|
||||
<Show isLoading={isLoading}>
|
||||
<Stack gap={1}>
|
||||
<Typography variant="body1" fontWeight="bold">
|
||||
{"ID"}
|
||||
</Typography>
|
||||
<NumberField value={record?.id ?? ""} />
|
||||
|
||||
<Typography variant="body1" fontWeight="bold">
|
||||
{"Title"}
|
||||
</Typography>
|
||||
<TextField value={record?.title} />
|
||||
|
||||
<Typography variant="body1" fontWeight="bold">
|
||||
{"Content"}
|
||||
</Typography>
|
||||
<MarkdownField value={record?.content} />
|
||||
|
||||
<Typography variant="body1" fontWeight="bold">
|
||||
{"Category"}
|
||||
</Typography>
|
||||
{categoryIsLoading ? <>Loading...</> : <>{categoryData?.data?.title}</>}
|
||||
<Typography variant="body1" fontWeight="bold">
|
||||
{"Status"}
|
||||
</Typography>
|
||||
<TextField value={record?.status} />
|
||||
<Typography variant="body1" fontWeight="bold">
|
||||
{"CreatedAt"}
|
||||
</Typography>
|
||||
<DateField value={record?.createdAt} />
|
||||
</Stack>
|
||||
</Show>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,36 @@
|
||||
import { Box, TextField } from "@mui/material";
|
||||
import { Create } from "@refinedev/mui";
|
||||
import { useForm } from "@refinedev/react-hook-form";
|
||||
|
||||
export const CategoryCreate = () => {
|
||||
const {
|
||||
saveButtonProps,
|
||||
refineCore: { formLoading },
|
||||
register,
|
||||
formState: { errors },
|
||||
} = useForm({});
|
||||
|
||||
return (
|
||||
<Create isLoading={formLoading} saveButtonProps={saveButtonProps}>
|
||||
<Box
|
||||
component="form"
|
||||
sx={{ display: "flex", flexDirection: "column" }}
|
||||
autoComplete="off"
|
||||
>
|
||||
<TextField
|
||||
{...register("title", {
|
||||
required: "This field is required",
|
||||
})}
|
||||
error={!!(errors as any)?.title}
|
||||
helperText={(errors as any)?.title?.message}
|
||||
margin="normal"
|
||||
fullWidth
|
||||
InputLabelProps={{ shrink: true }}
|
||||
type="text"
|
||||
label={"Title"}
|
||||
name="title"
|
||||
/>
|
||||
</Box>
|
||||
</Create>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,35 @@
|
||||
import { Box, TextField } from "@mui/material";
|
||||
import { Edit } from "@refinedev/mui";
|
||||
import { useForm } from "@refinedev/react-hook-form";
|
||||
|
||||
export const CategoryEdit = () => {
|
||||
const {
|
||||
saveButtonProps,
|
||||
register,
|
||||
formState: { errors },
|
||||
} = useForm({});
|
||||
|
||||
return (
|
||||
<Edit saveButtonProps={saveButtonProps}>
|
||||
<Box
|
||||
component="form"
|
||||
sx={{ display: "flex", flexDirection: "column" }}
|
||||
autoComplete="off"
|
||||
>
|
||||
<TextField
|
||||
{...register("title", {
|
||||
required: "This field is required",
|
||||
})}
|
||||
error={!!(errors as any)?.title}
|
||||
helperText={(errors as any)?.title?.message}
|
||||
margin="normal"
|
||||
fullWidth
|
||||
InputLabelProps={{ shrink: true }}
|
||||
type="text"
|
||||
label={"Title"}
|
||||
name="title"
|
||||
/>
|
||||
</Box>
|
||||
</Edit>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,4 @@
|
||||
export * from "./create";
|
||||
export * from "./edit";
|
||||
export * from "./list";
|
||||
export * from "./show";
|
||||
@@ -0,0 +1,58 @@
|
||||
import { DataGrid, GridColDef } from "@mui/x-data-grid";
|
||||
import {
|
||||
DeleteButton,
|
||||
EditButton,
|
||||
List,
|
||||
ShowButton,
|
||||
useDataGrid,
|
||||
} from "@refinedev/mui";
|
||||
import React from "react";
|
||||
|
||||
export const CategoryList = () => {
|
||||
const { dataGridProps } = useDataGrid({
|
||||
pagination: {
|
||||
mode: "client",
|
||||
},
|
||||
});
|
||||
|
||||
const columns = React.useMemo<GridColDef[]>(
|
||||
() => [
|
||||
{
|
||||
field: "id",
|
||||
headerName: "ID",
|
||||
type: "number",
|
||||
minWidth: 50,
|
||||
},
|
||||
{
|
||||
field: "title",
|
||||
flex: 1,
|
||||
headerName: "Title",
|
||||
minWidth: 200,
|
||||
},
|
||||
{
|
||||
field: "actions",
|
||||
headerName: "Actions",
|
||||
sortable: false,
|
||||
renderCell: function render({ row }) {
|
||||
return (
|
||||
<>
|
||||
<EditButton hideText recordItemId={row.id} />
|
||||
<ShowButton hideText recordItemId={row.id} />
|
||||
<DeleteButton hideText recordItemId={row.id} />
|
||||
</>
|
||||
);
|
||||
},
|
||||
align: "center",
|
||||
headerAlign: "center",
|
||||
minWidth: 80,
|
||||
},
|
||||
],
|
||||
[]
|
||||
);
|
||||
|
||||
return (
|
||||
<List>
|
||||
<DataGrid {...dataGridProps} columns={columns} autoHeight />
|
||||
</List>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,29 @@
|
||||
import { Stack, Typography } from "@mui/material";
|
||||
import { useShow } from "@refinedev/core";
|
||||
import {
|
||||
NumberField,
|
||||
Show,
|
||||
TextFieldComponent as TextField,
|
||||
} from "@refinedev/mui";
|
||||
|
||||
export const CategoryShow = () => {
|
||||
const { queryResult } = useShow({});
|
||||
const { data, isLoading } = queryResult;
|
||||
|
||||
const record = data?.data;
|
||||
|
||||
return (
|
||||
<Show isLoading={isLoading}>
|
||||
<Stack gap={1}>
|
||||
<Typography variant="body1" fontWeight="bold">
|
||||
{"ID"}
|
||||
</Typography>
|
||||
<NumberField value={record?.id ?? ""} />
|
||||
<Typography variant="body1" fontWeight="bold">
|
||||
{"Title"}
|
||||
</Typography>
|
||||
<TextField value={record?.title} />
|
||||
</Stack>
|
||||
</Show>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,17 @@
|
||||
import { AuthPage, ThemedTitleV2 } from "@refinedev/mui";
|
||||
import { AppIcon } from "../../components/app-icon";
|
||||
|
||||
export const ForgotPassword = () => {
|
||||
return (
|
||||
<AuthPage
|
||||
type="forgotPassword"
|
||||
title={
|
||||
<ThemedTitleV2
|
||||
collapsed={false}
|
||||
text="Refine Project"
|
||||
icon={<AppIcon />}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,20 @@
|
||||
import { AuthPage, ThemedTitleV2 } from "@refinedev/mui";
|
||||
import { AppIcon } from "../../components/app-icon";
|
||||
|
||||
export const Login = () => {
|
||||
return (
|
||||
<AuthPage
|
||||
type="login"
|
||||
title={
|
||||
<ThemedTitleV2
|
||||
collapsed={false}
|
||||
text="Refine Project"
|
||||
icon={<AppIcon />}
|
||||
/>
|
||||
}
|
||||
formProps={{
|
||||
defaultValues: { email: "demo@refine.dev", password: "demodemo" },
|
||||
}}
|
||||
/>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,179 @@
|
||||
import { Autocomplete, Box, InputAdornment, MenuItem, Select, TextField } from "@mui/material";
|
||||
import { Create, NumberField, useAutocomplete } from "@refinedev/mui";
|
||||
import { useForm } from "@refinedev/react-hook-form";
|
||||
import { Controller } from "react-hook-form";
|
||||
import { IMonitoring } from "./monitorings.types";
|
||||
|
||||
|
||||
export const MonitoringCreate = () => {
|
||||
const {
|
||||
saveButtonProps,
|
||||
refineCore: { formLoading },
|
||||
register,
|
||||
control,
|
||||
formState: { errors },
|
||||
} = useForm<IMonitoring>({});
|
||||
|
||||
// const { autocompleteProps: categoryAutocompleteProps } = useAutocomplete({
|
||||
// resource: "categories",
|
||||
// });
|
||||
|
||||
return (
|
||||
<Create isLoading={formLoading} saveButtonProps={saveButtonProps}>
|
||||
<Box
|
||||
component="form"
|
||||
sx={{ display: "flex", flexDirection: "column" }}
|
||||
autoComplete="off"
|
||||
>
|
||||
<TextField
|
||||
{...register("name", {
|
||||
required: "The name is required",
|
||||
})}
|
||||
error={!!(errors as any)?.name}
|
||||
helperText={(errors as any)?.name?.message}
|
||||
margin="normal"
|
||||
fullWidth
|
||||
InputLabelProps={{ shrink: true }}
|
||||
type="text"
|
||||
label={"Name"}
|
||||
name="name"
|
||||
/>
|
||||
<TextField
|
||||
{...register("samplerate", {
|
||||
required: "The samplerate is required",
|
||||
})}
|
||||
error={!!(errors as any)?.samplerate}
|
||||
helperText={(errors as any)?.samplerate?.message}
|
||||
margin="normal"
|
||||
fullWidth
|
||||
InputLabelProps={{ shrink: true }}
|
||||
type="number"
|
||||
label={"Samplerate"}
|
||||
name="samplerate"
|
||||
InputProps={{
|
||||
endAdornment:
|
||||
<InputAdornment position="end">hz</InputAdornment>
|
||||
}}
|
||||
/>
|
||||
<TextField
|
||||
{...register("sampleperiod", {
|
||||
required: "The sampleperiod is required",
|
||||
})}
|
||||
error={!!(errors as any)?.sampleperiod}
|
||||
helperText={(errors as any)?.sampleperiod?.message}
|
||||
margin="normal"
|
||||
fullWidth
|
||||
InputLabelProps={{ shrink: true }}
|
||||
type="number"
|
||||
label={"Sampleperiod"}
|
||||
name="sampleperiod"
|
||||
InputProps={{
|
||||
endAdornment:
|
||||
<InputAdornment position="end">s</InputAdornment>
|
||||
}}
|
||||
/>
|
||||
<TextField
|
||||
{...register("downtime", {
|
||||
required: "The downtime is required",
|
||||
})}
|
||||
error={!!(errors as any)?.downtime}
|
||||
helperText={(errors as any)?.downtime?.message}
|
||||
margin="normal"
|
||||
fullWidth
|
||||
InputLabelProps={{ shrink: true }}
|
||||
type="number"
|
||||
label={"Downtime"}
|
||||
name="downtime"
|
||||
InputProps={{
|
||||
endAdornment:
|
||||
<InputAdornment position="end">s</InputAdornment>
|
||||
}}
|
||||
/>
|
||||
<TextField
|
||||
{...register("owner", {
|
||||
required: "The downtime is required",
|
||||
})}
|
||||
error={!!(errors as any)?.downtime}
|
||||
helperText={(errors as any)?.downtime?.message}
|
||||
margin="normal"
|
||||
fullWidth
|
||||
InputLabelProps={{ shrink: true }}
|
||||
multiline
|
||||
label={"Owner"}
|
||||
name="owner"
|
||||
/>
|
||||
</Box>
|
||||
</Create>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
|
||||
|
||||
// <Controller
|
||||
// name="status"
|
||||
// control={control}
|
||||
// render={({ field }) => {
|
||||
// return (
|
||||
// <Select
|
||||
// {...field}
|
||||
// value={field?.value || "stopped"}
|
||||
// label={"Status"}
|
||||
// >
|
||||
// <MenuItem value="started">Draft</MenuItem>
|
||||
// <MenuItem value="stoppedd">Published</MenuItem>
|
||||
// </Select>
|
||||
// );
|
||||
// }}
|
||||
// />
|
||||
|
||||
|
||||
|
||||
|
||||
// <Controller
|
||||
// control={control}
|
||||
// name={"category.id"}
|
||||
// rules={{ required: "This field is required" }}
|
||||
// // eslint-disable-next-line
|
||||
// defaultValue={null as any}
|
||||
// render={({ field }) => (
|
||||
// <Autocomplete
|
||||
// {...categoryAutocompleteProps}
|
||||
// {...field}
|
||||
// onChange={(_, value) => {
|
||||
// field.onChange(value.id);
|
||||
// }}
|
||||
// getOptionLabel={(item) => {
|
||||
// return (
|
||||
// categoryAutocompleteProps?.options?.find((p) => {
|
||||
// const itemId =
|
||||
// typeof item === "object"
|
||||
// ? item?.id?.toString()
|
||||
// : item?.toString();
|
||||
// const pId = p?.id?.toString();
|
||||
// return itemId === pId;
|
||||
// })?.title ?? ""
|
||||
// );
|
||||
// }}
|
||||
// isOptionEqualToValue={(option, value) => {
|
||||
// const optionId = option?.id?.toString();
|
||||
// const valueId =
|
||||
// typeof value === "object"
|
||||
// ? value?.id?.toString()
|
||||
// : value?.toString();
|
||||
// return value === undefined || optionId === valueId;
|
||||
// }}
|
||||
// renderInput={(params) => (
|
||||
// <TextField
|
||||
// {...params}
|
||||
// label={"Category"}
|
||||
// margin="normal"
|
||||
// variant="outlined"
|
||||
// error={!!(errors as any)?.category?.id}
|
||||
// helperText={(errors as any)?.category?.id?.message}
|
||||
// required
|
||||
// />
|
||||
// )}
|
||||
// />
|
||||
// )}
|
||||
// />
|
||||
@@ -0,0 +1,163 @@
|
||||
import { Autocomplete, Box, InputAdornment, Select, TextField } from "@mui/material";
|
||||
import MenuItem from "@mui/material/MenuItem";
|
||||
import { Edit, useAutocomplete } from "@refinedev/mui";
|
||||
import { useForm } from "@refinedev/react-hook-form";
|
||||
import { Controller } from "react-hook-form";
|
||||
import { IMonitoring } from "./monitorings.types";
|
||||
|
||||
|
||||
|
||||
export const MonitoringEdit = () => {
|
||||
const {
|
||||
saveButtonProps,
|
||||
refineCore: { queryResult, formLoading },
|
||||
register,
|
||||
control,
|
||||
formState: { errors },
|
||||
} = useForm<IMonitoring>({});
|
||||
|
||||
const blogPostsData = queryResult?.data?.data;
|
||||
|
||||
// const { autocompleteProps: categoryAutocompleteProps } = useAutocomplete({
|
||||
// resource: "categories",
|
||||
// defaultValue: blogPostsData?.category?.id,
|
||||
// });
|
||||
|
||||
return (
|
||||
<Edit dataProviderName="monitorings" isLoading={formLoading} saveButtonProps={saveButtonProps}>
|
||||
<Box
|
||||
component="form"
|
||||
sx={{ display: "flex", flexDirection: "column" }}
|
||||
autoComplete="off"
|
||||
>
|
||||
<TextField
|
||||
{...register("name", {
|
||||
required: "The name is required",
|
||||
})}
|
||||
error={!!(errors as any)?.name}
|
||||
helperText={(errors as any)?.name?.message}
|
||||
margin="normal"
|
||||
fullWidth
|
||||
InputLabelProps={{ shrink: true }}
|
||||
type="text"
|
||||
label={"Name"}
|
||||
name="name"
|
||||
/>
|
||||
<TextField
|
||||
{...register("samplerate", {
|
||||
required: "The samplerate is required",
|
||||
})}
|
||||
error={!!(errors as any)?.samplerate}
|
||||
helperText={(errors as any)?.samplerate?.message}
|
||||
margin="normal"
|
||||
fullWidth
|
||||
InputLabelProps={{ shrink: true }}
|
||||
type="number"
|
||||
label={"Samplerate"}
|
||||
name="samplerate"
|
||||
InputProps={{
|
||||
endAdornment:
|
||||
<InputAdornment position="end">hz</InputAdornment>
|
||||
}}
|
||||
/>
|
||||
<TextField
|
||||
{...register("sampleperiod", {
|
||||
required: "The sampleperiod is required",
|
||||
})}
|
||||
error={!!(errors as any)?.sampleperiod}
|
||||
helperText={(errors as any)?.sampleperiod?.message}
|
||||
margin="normal"
|
||||
fullWidth
|
||||
InputLabelProps={{ shrink: true }}
|
||||
type="number"
|
||||
label={"Sampleperiod"}
|
||||
name="sampleperiod"
|
||||
InputProps={{
|
||||
endAdornment:
|
||||
<InputAdornment position="end">s</InputAdornment>
|
||||
}}
|
||||
/>
|
||||
<TextField
|
||||
{...register("downtime", {
|
||||
required: "The downtime is required",
|
||||
})}
|
||||
error={!!(errors as any)?.downtime}
|
||||
helperText={(errors as any)?.downtime?.message}
|
||||
margin="normal"
|
||||
fullWidth
|
||||
InputLabelProps={{ shrink: true }}
|
||||
type="number"
|
||||
label={"Downtime"}
|
||||
name="downtime"
|
||||
InputProps={{
|
||||
endAdornment:
|
||||
<InputAdornment position="end">s</InputAdornment>
|
||||
}}
|
||||
/>
|
||||
<TextField
|
||||
{...register("owner", {
|
||||
required: "The downtime is required",
|
||||
})}
|
||||
error={!!(errors as any)?.downtime}
|
||||
helperText={(errors as any)?.downtime?.message}
|
||||
margin="normal"
|
||||
fullWidth
|
||||
InputLabelProps={{ shrink: true }}
|
||||
multiline
|
||||
label={"Owner"}
|
||||
name="owner"
|
||||
/>
|
||||
</Box>
|
||||
</Edit>
|
||||
);
|
||||
};
|
||||
|
||||
/*
|
||||
{ <Controller
|
||||
control={control}
|
||||
name={"category.id"}
|
||||
rules={{ required: "This field is required" }}
|
||||
// eslint-disable-next-line
|
||||
defaultValue={null as any}
|
||||
render={({ field }) => (
|
||||
<Autocomplete
|
||||
{...categoryAutocompleteProps}
|
||||
{...field}
|
||||
onChange={(_, value) => {
|
||||
field.onChange(value.id);
|
||||
}}
|
||||
getOptionLabel={(item) => {
|
||||
return (
|
||||
categoryAutocompleteProps?.options?.find((p) => {
|
||||
const itemId =
|
||||
typeof item === "object"
|
||||
? item?.id?.toString()
|
||||
: item?.toString();
|
||||
const pId = p?.id?.toString();
|
||||
return itemId === pId;
|
||||
})?.title ?? ""
|
||||
);
|
||||
}}
|
||||
isOptionEqualToValue={(option, value) => {
|
||||
const optionId = option?.id?.toString();
|
||||
const valueId =
|
||||
typeof value === "object"
|
||||
? value?.id?.toString()
|
||||
: value?.toString();
|
||||
return value === undefined || optionId === valueId;
|
||||
}}
|
||||
renderInput={(params) => (
|
||||
<TextField
|
||||
{...params}
|
||||
label={"Category"}
|
||||
margin="normal"
|
||||
variant="outlined"
|
||||
error={!!(errors as any)?.category?.id}
|
||||
helperText={(errors as any)?.category?.id?.message}
|
||||
required
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
)}
|
||||
/> }
|
||||
*/
|
||||
@@ -0,0 +1,4 @@
|
||||
export * from "./create";
|
||||
export * from "./edit";
|
||||
export * from "./list";
|
||||
export * from "./show";
|
||||
@@ -0,0 +1,147 @@
|
||||
import { DataGrid, GridColDef } from "@mui/x-data-grid";
|
||||
import {
|
||||
DateField,
|
||||
DeleteButton,
|
||||
EditButton,
|
||||
List,
|
||||
MarkdownField,
|
||||
NumberField,
|
||||
ShowButton,
|
||||
useDataGrid,
|
||||
} from "@refinedev/mui";
|
||||
import React from "react";
|
||||
import { IMonitoring } from "./monitorings.types";
|
||||
|
||||
|
||||
export const MonitoringList = () => {
|
||||
const { dataGridProps } = useDataGrid<IMonitoring>({
|
||||
syncWithLocation: true,
|
||||
dataProviderName: "monitorings",
|
||||
pagination: {
|
||||
mode: "client",
|
||||
pageSize: 10,
|
||||
},
|
||||
});
|
||||
|
||||
// const { data: categoryData, isLoading: categoryIsLoading } = useMany({
|
||||
// resource: "categories",
|
||||
// ids:
|
||||
// dataGridProps?.rows
|
||||
// ?.map((item: any) => item?.category?.id)
|
||||
// .filter(Boolean) ?? [],
|
||||
// queryOptions: {
|
||||
// enabled: !!dataGridProps?.rows,
|
||||
// },
|
||||
// });
|
||||
|
||||
const columns = React.useMemo<GridColDef<IMonitoring>[]>(
|
||||
() => [
|
||||
{
|
||||
field: "name",
|
||||
flex: 1,
|
||||
headerName: "Name",
|
||||
type:"string",
|
||||
minWidth: 250,
|
||||
},
|
||||
{
|
||||
field: "id",
|
||||
headerName: "ID",
|
||||
type: "string",
|
||||
minWidth: 300,
|
||||
},
|
||||
{
|
||||
field: "created_at",
|
||||
flex: 1,
|
||||
headerName: "Created at",
|
||||
minWidth: 30,
|
||||
renderCell: function render({ value }) {
|
||||
return <DateField value={value} />;
|
||||
},
|
||||
},
|
||||
{
|
||||
field: "samplerate",
|
||||
flex: 0.3,
|
||||
headerName: "Samplerate in Hz",
|
||||
renderCell: function render({ row }) {
|
||||
return (
|
||||
<NumberField
|
||||
value={row.samplerate}
|
||||
options={{
|
||||
minimumIntegerDigits: 1,
|
||||
minimumFractionDigits:0,
|
||||
maximumFractionDigits: 0,
|
||||
}}
|
||||
/>
|
||||
);
|
||||
},
|
||||
minWidth: 120,
|
||||
},
|
||||
{
|
||||
field: "sampleperiod",
|
||||
flex: 0.3,
|
||||
headerName: "Period in s",
|
||||
renderCell: function render({ row }) {
|
||||
return (
|
||||
<NumberField
|
||||
value={row.sampleperiod}
|
||||
options={{
|
||||
minimumIntegerDigits: 1,
|
||||
minimumFractionDigits:1,
|
||||
maximumFractionDigits: 3,
|
||||
}}
|
||||
/>
|
||||
);
|
||||
},
|
||||
minWidth: 120,
|
||||
},
|
||||
{
|
||||
field: "downtime",
|
||||
flex: 0.3,
|
||||
headerName: "Downtime in s",
|
||||
renderCell: function render({ row }) {
|
||||
return (
|
||||
<NumberField
|
||||
value={row.sampleperiod}
|
||||
options={{
|
||||
minimumIntegerDigits: 1,
|
||||
minimumFractionDigits:1,
|
||||
maximumFractionDigits: 3,
|
||||
}}
|
||||
/>
|
||||
);
|
||||
},
|
||||
minWidth: 120,
|
||||
},
|
||||
{
|
||||
field: "owner",
|
||||
flex: 1,
|
||||
headerName: "Owner",
|
||||
type:"string",
|
||||
minWidth: 80,
|
||||
},
|
||||
{
|
||||
field: "actions",
|
||||
headerName: "Actions",
|
||||
sortable: false,
|
||||
renderCell: function render({ row }) {
|
||||
return (
|
||||
<>
|
||||
<EditButton hideText recordItemId={row.id} />
|
||||
<DeleteButton hideText recordItemId={row.id} />
|
||||
</>
|
||||
);
|
||||
},
|
||||
align: "center",
|
||||
headerAlign: "center",
|
||||
minWidth: 80,
|
||||
},
|
||||
],
|
||||
[]
|
||||
);
|
||||
|
||||
return (
|
||||
<List>
|
||||
<DataGrid {...dataGridProps} columns={columns} autoHeight />
|
||||
</List>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,12 @@
|
||||
|
||||
|
||||
export interface IMonitoring {
|
||||
id: string;
|
||||
created_at: string;
|
||||
name: string;
|
||||
samplerate: number;
|
||||
sampleperiod: number;
|
||||
downtime: number;
|
||||
owner: string;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,59 @@
|
||||
import { Stack, Typography } from "@mui/material";
|
||||
import { useOne, useShow } from "@refinedev/core";
|
||||
import {
|
||||
DateField,
|
||||
MarkdownField,
|
||||
NumberField,
|
||||
Show,
|
||||
TextFieldComponent as TextField,
|
||||
} from "@refinedev/mui";
|
||||
|
||||
export const MonitoringShow = () => {
|
||||
const { queryResult } = useShow({});
|
||||
|
||||
const { data, isLoading } = queryResult;
|
||||
|
||||
const record = data?.data;
|
||||
|
||||
const { data: categoryData, isLoading: categoryIsLoading } = useOne({
|
||||
resource: "categories",
|
||||
id: record?.category?.id || "",
|
||||
queryOptions: {
|
||||
enabled: !!record,
|
||||
},
|
||||
});
|
||||
|
||||
return (
|
||||
<Show isLoading={isLoading}>
|
||||
<Stack gap={1}>
|
||||
<Typography variant="body1" fontWeight="bold">
|
||||
{"ID"}
|
||||
</Typography>
|
||||
<NumberField value={record?.id ?? ""} />
|
||||
|
||||
<Typography variant="body1" fontWeight="bold">
|
||||
{"Title"}
|
||||
</Typography>
|
||||
<TextField value={record?.title} />
|
||||
|
||||
<Typography variant="body1" fontWeight="bold">
|
||||
{"Content"}
|
||||
</Typography>
|
||||
<MarkdownField value={record?.content} />
|
||||
|
||||
<Typography variant="body1" fontWeight="bold">
|
||||
{"Category"}
|
||||
</Typography>
|
||||
{categoryIsLoading ? <>Loading...</> : <>{categoryData?.data?.title}</>}
|
||||
<Typography variant="body1" fontWeight="bold">
|
||||
{"Status"}
|
||||
</Typography>
|
||||
<TextField value={record?.status} />
|
||||
<Typography variant="body1" fontWeight="bold">
|
||||
{"CreatedAt"}
|
||||
</Typography>
|
||||
<DateField value={record?.createdAt} />
|
||||
</Stack>
|
||||
</Show>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,17 @@
|
||||
import { AuthPage, ThemedTitleV2 } from "@refinedev/mui";
|
||||
import { AppIcon } from "../../components/app-icon";
|
||||
|
||||
export const Register = () => {
|
||||
return (
|
||||
<AuthPage
|
||||
type="register"
|
||||
title={
|
||||
<ThemedTitleV2
|
||||
collapsed={false}
|
||||
text="Refine Project"
|
||||
icon={<AppIcon />}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
);
|
||||
};
|
||||
Vendored
+1
@@ -0,0 +1 @@
|
||||
/// <reference types="vite/client" />
|
||||
Whitespace-only changes.
+741
@@ -0,0 +1,741 @@
|
||||
import "/node_modules/vite/dist/client/env.mjs";
|
||||
|
||||
const base$1 = "/" || '/';
|
||||
// set :host styles to make playwright detect the element as visible
|
||||
const template = /*html*/ `
|
||||
<style>
|
||||
:host {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
z-index: 99999;
|
||||
--monospace: 'SFMono-Regular', Consolas,
|
||||
'Liberation Mono', Menlo, Courier, monospace;
|
||||
--red: #ff5555;
|
||||
--yellow: #e2aa53;
|
||||
--purple: #cfa4ff;
|
||||
--cyan: #2dd9da;
|
||||
--dim: #c9c9c9;
|
||||
|
||||
--window-background: #181818;
|
||||
--window-color: #d8d8d8;
|
||||
}
|
||||
|
||||
.backdrop {
|
||||
position: fixed;
|
||||
z-index: 99999;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
overflow-y: scroll;
|
||||
margin: 0;
|
||||
background: rgba(0, 0, 0, 0.66);
|
||||
}
|
||||
|
||||
.window {
|
||||
font-family: var(--monospace);
|
||||
line-height: 1.5;
|
||||
width: 800px;
|
||||
color: var(--window-color);
|
||||
margin: 30px auto;
|
||||
padding: 25px 40px;
|
||||
position: relative;
|
||||
background: var(--window-background);
|
||||
border-radius: 6px 6px 8px 8px;
|
||||
box-shadow: 0 19px 38px rgba(0,0,0,0.30), 0 15px 12px rgba(0,0,0,0.22);
|
||||
overflow: hidden;
|
||||
border-top: 8px solid var(--red);
|
||||
direction: ltr;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
pre {
|
||||
font-family: var(--monospace);
|
||||
font-size: 16px;
|
||||
margin-top: 0;
|
||||
margin-bottom: 1em;
|
||||
overflow-x: scroll;
|
||||
scrollbar-width: none;
|
||||
}
|
||||
|
||||
pre::-webkit-scrollbar {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.message {
|
||||
line-height: 1.3;
|
||||
font-weight: 600;
|
||||
white-space: pre-wrap;
|
||||
}
|
||||
|
||||
.message-body {
|
||||
color: var(--red);
|
||||
}
|
||||
|
||||
.plugin {
|
||||
color: var(--purple);
|
||||
}
|
||||
|
||||
.file {
|
||||
color: var(--cyan);
|
||||
margin-bottom: 0;
|
||||
white-space: pre-wrap;
|
||||
word-break: break-all;
|
||||
}
|
||||
|
||||
.frame {
|
||||
color: var(--yellow);
|
||||
}
|
||||
|
||||
.stack {
|
||||
font-size: 13px;
|
||||
color: var(--dim);
|
||||
}
|
||||
|
||||
.tip {
|
||||
font-size: 13px;
|
||||
color: #999;
|
||||
border-top: 1px dotted #999;
|
||||
padding-top: 13px;
|
||||
line-height: 1.8;
|
||||
}
|
||||
|
||||
code {
|
||||
font-size: 13px;
|
||||
font-family: var(--monospace);
|
||||
color: var(--yellow);
|
||||
}
|
||||
|
||||
.file-link {
|
||||
text-decoration: underline;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
kbd {
|
||||
line-height: 1.5;
|
||||
font-family: ui-monospace, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
|
||||
font-size: 0.75rem;
|
||||
font-weight: 700;
|
||||
background-color: rgb(38, 40, 44);
|
||||
color: rgb(166, 167, 171);
|
||||
padding: 0.15rem 0.3rem;
|
||||
border-radius: 0.25rem;
|
||||
border-width: 0.0625rem 0.0625rem 0.1875rem;
|
||||
border-style: solid;
|
||||
border-color: rgb(54, 57, 64);
|
||||
border-image: initial;
|
||||
}
|
||||
</style>
|
||||
<div class="backdrop" part="backdrop">
|
||||
<div class="window" part="window">
|
||||
<pre class="message" part="message"><span class="plugin" part="plugin"></span><span class="message-body" part="message-body"></span></pre>
|
||||
<pre class="file" part="file"></pre>
|
||||
<pre class="frame" part="frame"></pre>
|
||||
<pre class="stack" part="stack"></pre>
|
||||
<div class="tip" part="tip">
|
||||
Click outside, press <kbd>Esc</kbd> key, or fix the code to dismiss.<br>
|
||||
You can also disable this overlay by setting
|
||||
<code part="config-option-name">server.hmr.overlay</code> to <code part="config-option-value">false</code> in <code part="config-file-name">vite.config.js.</code>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
const fileRE = /(?:[a-zA-Z]:\\|\/).*?:\d+:\d+/g;
|
||||
const codeframeRE = /^(?:>?\s+\d+\s+\|.*|\s+\|\s*\^.*)\r?\n/gm;
|
||||
// Allow `ErrorOverlay` to extend `HTMLElement` even in environments where
|
||||
// `HTMLElement` was not originally defined.
|
||||
const { HTMLElement = class {
|
||||
} } = globalThis;
|
||||
class ErrorOverlay extends HTMLElement {
|
||||
constructor(err, links = true) {
|
||||
var _a;
|
||||
super();
|
||||
this.root = this.attachShadow({ mode: 'open' });
|
||||
this.root.innerHTML = template;
|
||||
codeframeRE.lastIndex = 0;
|
||||
const hasFrame = err.frame && codeframeRE.test(err.frame);
|
||||
const message = hasFrame
|
||||
? err.message.replace(codeframeRE, '')
|
||||
: err.message;
|
||||
if (err.plugin) {
|
||||
this.text('.plugin', `[plugin:${err.plugin}] `);
|
||||
}
|
||||
this.text('.message-body', message.trim());
|
||||
const [file] = (((_a = err.loc) === null || _a === void 0 ? void 0 : _a.file) || err.id || 'unknown file').split(`?`);
|
||||
if (err.loc) {
|
||||
this.text('.file', `${file}:${err.loc.line}:${err.loc.column}`, links);
|
||||
}
|
||||
else if (err.id) {
|
||||
this.text('.file', file);
|
||||
}
|
||||
if (hasFrame) {
|
||||
this.text('.frame', err.frame.trim());
|
||||
}
|
||||
this.text('.stack', err.stack, links);
|
||||
this.root.querySelector('.window').addEventListener('click', (e) => {
|
||||
e.stopPropagation();
|
||||
});
|
||||
this.addEventListener('click', () => {
|
||||
this.close();
|
||||
});
|
||||
this.closeOnEsc = (e) => {
|
||||
if (e.key === 'Escape' || e.code === 'Escape') {
|
||||
this.close();
|
||||
}
|
||||
};
|
||||
document.addEventListener('keydown', this.closeOnEsc);
|
||||
}
|
||||
text(selector, text, linkFiles = false) {
|
||||
const el = this.root.querySelector(selector);
|
||||
if (!linkFiles) {
|
||||
el.textContent = text;
|
||||
}
|
||||
else {
|
||||
let curIndex = 0;
|
||||
let match;
|
||||
fileRE.lastIndex = 0;
|
||||
while ((match = fileRE.exec(text))) {
|
||||
const { 0: file, index } = match;
|
||||
if (index != null) {
|
||||
const frag = text.slice(curIndex, index);
|
||||
el.appendChild(document.createTextNode(frag));
|
||||
const link = document.createElement('a');
|
||||
link.textContent = file;
|
||||
link.className = 'file-link';
|
||||
link.onclick = () => {
|
||||
fetch(`${base$1}__open-in-editor?file=` + encodeURIComponent(file));
|
||||
};
|
||||
el.appendChild(link);
|
||||
curIndex += frag.length + file.length;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
close() {
|
||||
var _a;
|
||||
(_a = this.parentNode) === null || _a === void 0 ? void 0 : _a.removeChild(this);
|
||||
document.removeEventListener('keydown', this.closeOnEsc);
|
||||
}
|
||||
}
|
||||
const overlayId = 'vite-error-overlay';
|
||||
const { customElements } = globalThis; // Ensure `customElements` is defined before the next line.
|
||||
if (customElements && !customElements.get(overlayId)) {
|
||||
customElements.define(overlayId, ErrorOverlay);
|
||||
}
|
||||
|
||||
console.debug('[vite] connecting...');
|
||||
const importMetaUrl = new URL(import.meta.url);
|
||||
// use server configuration, then fallback to inference
|
||||
const serverHost = "localhost:undefined/";
|
||||
const socketProtocol = null || (importMetaUrl.protocol === 'https:' ? 'wss' : 'ws');
|
||||
const hmrPort = null;
|
||||
const socketHost = `${null || importMetaUrl.hostname}:${hmrPort || importMetaUrl.port}${"/"}`;
|
||||
const directSocketHost = "localhost:undefined/";
|
||||
const base = "/" || '/';
|
||||
const messageBuffer = [];
|
||||
let socket;
|
||||
try {
|
||||
let fallback;
|
||||
// only use fallback when port is inferred to prevent confusion
|
||||
if (!hmrPort) {
|
||||
fallback = () => {
|
||||
// fallback to connecting directly to the hmr server
|
||||
// for servers which does not support proxying websocket
|
||||
socket = setupWebSocket(socketProtocol, directSocketHost, () => {
|
||||
const currentScriptHostURL = new URL(import.meta.url);
|
||||
const currentScriptHost = currentScriptHostURL.host +
|
||||
currentScriptHostURL.pathname.replace(/@vite\/client$/, '');
|
||||
console.error('[vite] failed to connect to websocket.\n' +
|
||||
'your current setup:\n' +
|
||||
` (browser) ${currentScriptHost} <--[HTTP]--> ${serverHost} (server)\n` +
|
||||
` (browser) ${socketHost} <--[WebSocket (failing)]--> ${directSocketHost} (server)\n` +
|
||||
'Check out your Vite / network configuration and https://vitejs.dev/config/server-options.html#server-hmr .');
|
||||
});
|
||||
socket.addEventListener('open', () => {
|
||||
console.info('[vite] Direct websocket connection fallback. Check out https://vitejs.dev/config/server-options.html#server-hmr to remove the previous connection error.');
|
||||
}, { once: true });
|
||||
};
|
||||
}
|
||||
socket = setupWebSocket(socketProtocol, socketHost, fallback);
|
||||
}
|
||||
catch (error) {
|
||||
console.error(`[vite] failed to connect to websocket (${error}). `);
|
||||
}
|
||||
function setupWebSocket(protocol, hostAndPath, onCloseWithoutOpen) {
|
||||
const socket = new WebSocket(`${protocol}://${hostAndPath}`, 'vite-hmr');
|
||||
let isOpened = false;
|
||||
socket.addEventListener('open', () => {
|
||||
isOpened = true;
|
||||
notifyListeners('vite:ws:connect', { webSocket: socket });
|
||||
}, { once: true });
|
||||
// Listen for messages
|
||||
socket.addEventListener('message', async ({ data }) => {
|
||||
handleMessage(JSON.parse(data));
|
||||
});
|
||||
// ping server
|
||||
socket.addEventListener('close', async ({ wasClean }) => {
|
||||
if (wasClean)
|
||||
return;
|
||||
if (!isOpened && onCloseWithoutOpen) {
|
||||
onCloseWithoutOpen();
|
||||
return;
|
||||
}
|
||||
notifyListeners('vite:ws:disconnect', { webSocket: socket });
|
||||
console.log(`[vite] server connection lost. polling for restart...`);
|
||||
await waitForSuccessfulPing(protocol, hostAndPath);
|
||||
location.reload();
|
||||
});
|
||||
return socket;
|
||||
}
|
||||
function warnFailedFetch(err, path) {
|
||||
if (!err.message.match('fetch')) {
|
||||
console.error(err);
|
||||
}
|
||||
console.error(`[hmr] Failed to reload ${path}. ` +
|
||||
`This could be due to syntax errors or importing non-existent ` +
|
||||
`modules. (see errors above)`);
|
||||
}
|
||||
function cleanUrl(pathname) {
|
||||
const url = new URL(pathname, location.toString());
|
||||
url.searchParams.delete('direct');
|
||||
return url.pathname + url.search;
|
||||
}
|
||||
let isFirstUpdate = true;
|
||||
const outdatedLinkTags = new WeakSet();
|
||||
const debounceReload = (time) => {
|
||||
let timer;
|
||||
return () => {
|
||||
if (timer) {
|
||||
clearTimeout(timer);
|
||||
timer = null;
|
||||
}
|
||||
timer = setTimeout(() => {
|
||||
location.reload();
|
||||
}, time);
|
||||
};
|
||||
};
|
||||
const pageReload = debounceReload(50);
|
||||
async function handleMessage(payload) {
|
||||
switch (payload.type) {
|
||||
case 'connected':
|
||||
console.debug(`[vite] connected.`);
|
||||
sendMessageBuffer();
|
||||
// proxy(nginx, docker) hmr ws maybe caused timeout,
|
||||
// so send ping package let ws keep alive.
|
||||
setInterval(() => {
|
||||
if (socket.readyState === socket.OPEN) {
|
||||
socket.send('{"type":"ping"}');
|
||||
}
|
||||
}, 30000);
|
||||
break;
|
||||
case 'update':
|
||||
notifyListeners('vite:beforeUpdate', payload);
|
||||
// if this is the first update and there's already an error overlay, it
|
||||
// means the page opened with existing server compile error and the whole
|
||||
// module script failed to load (since one of the nested imports is 500).
|
||||
// in this case a normal update won't work and a full reload is needed.
|
||||
if (isFirstUpdate && hasErrorOverlay()) {
|
||||
window.location.reload();
|
||||
return;
|
||||
}
|
||||
else {
|
||||
clearErrorOverlay();
|
||||
isFirstUpdate = false;
|
||||
}
|
||||
await Promise.all(payload.updates.map(async (update) => {
|
||||
if (update.type === 'js-update') {
|
||||
return queueUpdate(fetchUpdate(update));
|
||||
}
|
||||
// css-update
|
||||
// this is only sent when a css file referenced with <link> is updated
|
||||
const { path, timestamp } = update;
|
||||
const searchUrl = cleanUrl(path);
|
||||
// can't use querySelector with `[href*=]` here since the link may be
|
||||
// using relative paths so we need to use link.href to grab the full
|
||||
// URL for the include check.
|
||||
const el = Array.from(document.querySelectorAll('link')).find((e) => !outdatedLinkTags.has(e) && cleanUrl(e.href).includes(searchUrl));
|
||||
if (!el) {
|
||||
return;
|
||||
}
|
||||
const newPath = `${base}${searchUrl.slice(1)}${searchUrl.includes('?') ? '&' : '?'}t=${timestamp}`;
|
||||
// rather than swapping the href on the existing tag, we will
|
||||
// create a new link tag. Once the new stylesheet has loaded we
|
||||
// will remove the existing link tag. This removes a Flash Of
|
||||
// Unstyled Content that can occur when swapping out the tag href
|
||||
// directly, as the new stylesheet has not yet been loaded.
|
||||
return new Promise((resolve) => {
|
||||
const newLinkTag = el.cloneNode();
|
||||
newLinkTag.href = new URL(newPath, el.href).href;
|
||||
const removeOldEl = () => {
|
||||
el.remove();
|
||||
console.debug(`[vite] css hot updated: ${searchUrl}`);
|
||||
resolve();
|
||||
};
|
||||
newLinkTag.addEventListener('load', removeOldEl);
|
||||
newLinkTag.addEventListener('error', removeOldEl);
|
||||
outdatedLinkTags.add(el);
|
||||
el.after(newLinkTag);
|
||||
});
|
||||
}));
|
||||
notifyListeners('vite:afterUpdate', payload);
|
||||
break;
|
||||
case 'custom': {
|
||||
notifyListeners(payload.event, payload.data);
|
||||
break;
|
||||
}
|
||||
case 'full-reload':
|
||||
notifyListeners('vite:beforeFullReload', payload);
|
||||
if (payload.path && payload.path.endsWith('.html')) {
|
||||
// if html file is edited, only reload the page if the browser is
|
||||
// currently on that page.
|
||||
const pagePath = decodeURI(location.pathname);
|
||||
const payloadPath = base + payload.path.slice(1);
|
||||
if (pagePath === payloadPath ||
|
||||
payload.path === '/index.html' ||
|
||||
(pagePath.endsWith('/') && pagePath + 'index.html' === payloadPath)) {
|
||||
pageReload();
|
||||
}
|
||||
return;
|
||||
}
|
||||
else {
|
||||
pageReload();
|
||||
}
|
||||
break;
|
||||
case 'prune':
|
||||
notifyListeners('vite:beforePrune', payload);
|
||||
// After an HMR update, some modules are no longer imported on the page
|
||||
// but they may have left behind side effects that need to be cleaned up
|
||||
// (.e.g style injections)
|
||||
// TODO Trigger their dispose callbacks.
|
||||
payload.paths.forEach((path) => {
|
||||
const fn = pruneMap.get(path);
|
||||
if (fn) {
|
||||
fn(dataMap.get(path));
|
||||
}
|
||||
});
|
||||
break;
|
||||
case 'error': {
|
||||
notifyListeners('vite:error', payload);
|
||||
const err = payload.err;
|
||||
if (enableOverlay) {
|
||||
createErrorOverlay(err);
|
||||
}
|
||||
else {
|
||||
console.error(`[vite] Internal Server Error\n${err.message}\n${err.stack}`);
|
||||
}
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
const check = payload;
|
||||
return check;
|
||||
}
|
||||
}
|
||||
}
|
||||
function notifyListeners(event, data) {
|
||||
const cbs = customListenersMap.get(event);
|
||||
if (cbs) {
|
||||
cbs.forEach((cb) => cb(data));
|
||||
}
|
||||
}
|
||||
const enableOverlay = true;
|
||||
function createErrorOverlay(err) {
|
||||
if (!enableOverlay)
|
||||
return;
|
||||
clearErrorOverlay();
|
||||
document.body.appendChild(new ErrorOverlay(err));
|
||||
}
|
||||
function clearErrorOverlay() {
|
||||
document
|
||||
.querySelectorAll(overlayId)
|
||||
.forEach((n) => n.close());
|
||||
}
|
||||
function hasErrorOverlay() {
|
||||
return document.querySelectorAll(overlayId).length;
|
||||
}
|
||||
let pending = false;
|
||||
let queued = [];
|
||||
/**
|
||||
* buffer multiple hot updates triggered by the same src change
|
||||
* so that they are invoked in the same order they were sent.
|
||||
* (otherwise the order may be inconsistent because of the http request round trip)
|
||||
*/
|
||||
async function queueUpdate(p) {
|
||||
queued.push(p);
|
||||
if (!pending) {
|
||||
pending = true;
|
||||
await Promise.resolve();
|
||||
pending = false;
|
||||
const loading = [...queued];
|
||||
queued = [];
|
||||
(await Promise.all(loading)).forEach((fn) => fn && fn());
|
||||
}
|
||||
}
|
||||
async function waitForSuccessfulPing(socketProtocol, hostAndPath, ms = 1000) {
|
||||
const pingHostProtocol = socketProtocol === 'wss' ? 'https' : 'http';
|
||||
const ping = async () => {
|
||||
// A fetch on a websocket URL will return a successful promise with status 400,
|
||||
// but will reject a networking error.
|
||||
// When running on middleware mode, it returns status 426, and an cors error happens if mode is not no-cors
|
||||
try {
|
||||
await fetch(`${pingHostProtocol}://${hostAndPath}`, {
|
||||
mode: 'no-cors',
|
||||
headers: {
|
||||
// Custom headers won't be included in a request with no-cors so (ab)use one of the
|
||||
// safelisted headers to identify the ping request
|
||||
Accept: 'text/x-vite-ping',
|
||||
},
|
||||
});
|
||||
return true;
|
||||
}
|
||||
catch { }
|
||||
return false;
|
||||
};
|
||||
if (await ping()) {
|
||||
return;
|
||||
}
|
||||
await wait(ms);
|
||||
// eslint-disable-next-line no-constant-condition
|
||||
while (true) {
|
||||
if (document.visibilityState === 'visible') {
|
||||
if (await ping()) {
|
||||
break;
|
||||
}
|
||||
await wait(ms);
|
||||
}
|
||||
else {
|
||||
await waitForWindowShow();
|
||||
}
|
||||
}
|
||||
}
|
||||
function wait(ms) {
|
||||
return new Promise((resolve) => setTimeout(resolve, ms));
|
||||
}
|
||||
function waitForWindowShow() {
|
||||
return new Promise((resolve) => {
|
||||
const onChange = async () => {
|
||||
if (document.visibilityState === 'visible') {
|
||||
resolve();
|
||||
document.removeEventListener('visibilitychange', onChange);
|
||||
}
|
||||
};
|
||||
document.addEventListener('visibilitychange', onChange);
|
||||
});
|
||||
}
|
||||
const sheetsMap = new Map();
|
||||
// collect existing style elements that may have been inserted during SSR
|
||||
// to avoid FOUC or duplicate styles
|
||||
if ('document' in globalThis) {
|
||||
document.querySelectorAll('style[data-vite-dev-id]').forEach((el) => {
|
||||
sheetsMap.set(el.getAttribute('data-vite-dev-id'), el);
|
||||
});
|
||||
}
|
||||
// all css imports should be inserted at the same position
|
||||
// because after build it will be a single css file
|
||||
let lastInsertedStyle;
|
||||
function updateStyle(id, content) {
|
||||
let style = sheetsMap.get(id);
|
||||
if (!style) {
|
||||
style = document.createElement('style');
|
||||
style.setAttribute('type', 'text/css');
|
||||
style.setAttribute('data-vite-dev-id', id);
|
||||
style.textContent = content;
|
||||
if (!lastInsertedStyle) {
|
||||
document.head.appendChild(style);
|
||||
// reset lastInsertedStyle after async
|
||||
// because dynamically imported css will be splitted into a different file
|
||||
setTimeout(() => {
|
||||
lastInsertedStyle = undefined;
|
||||
}, 0);
|
||||
}
|
||||
else {
|
||||
lastInsertedStyle.insertAdjacentElement('afterend', style);
|
||||
}
|
||||
lastInsertedStyle = style;
|
||||
}
|
||||
else {
|
||||
style.textContent = content;
|
||||
}
|
||||
sheetsMap.set(id, style);
|
||||
}
|
||||
function removeStyle(id) {
|
||||
const style = sheetsMap.get(id);
|
||||
if (style) {
|
||||
document.head.removeChild(style);
|
||||
sheetsMap.delete(id);
|
||||
}
|
||||
}
|
||||
async function fetchUpdate({ path, acceptedPath, timestamp, explicitImportRequired, }) {
|
||||
const mod = hotModulesMap.get(path);
|
||||
if (!mod) {
|
||||
// In a code-splitting project,
|
||||
// it is common that the hot-updating module is not loaded yet.
|
||||
// https://github.com/vitejs/vite/issues/721
|
||||
return;
|
||||
}
|
||||
let fetchedModule;
|
||||
const isSelfUpdate = path === acceptedPath;
|
||||
// determine the qualified callbacks before we re-import the modules
|
||||
const qualifiedCallbacks = mod.callbacks.filter(({ deps }) => deps.includes(acceptedPath));
|
||||
if (isSelfUpdate || qualifiedCallbacks.length > 0) {
|
||||
const disposer = disposeMap.get(acceptedPath);
|
||||
if (disposer)
|
||||
await disposer(dataMap.get(acceptedPath));
|
||||
const [acceptedPathWithoutQuery, query] = acceptedPath.split(`?`);
|
||||
try {
|
||||
fetchedModule = await import(
|
||||
/* @vite-ignore */
|
||||
base +
|
||||
acceptedPathWithoutQuery.slice(1) +
|
||||
`?${explicitImportRequired ? 'import&' : ''}t=${timestamp}${query ? `&${query}` : ''}`);
|
||||
}
|
||||
catch (e) {
|
||||
warnFailedFetch(e, acceptedPath);
|
||||
}
|
||||
}
|
||||
return () => {
|
||||
for (const { deps, fn } of qualifiedCallbacks) {
|
||||
fn(deps.map((dep) => (dep === acceptedPath ? fetchedModule : undefined)));
|
||||
}
|
||||
const loggedPath = isSelfUpdate ? path : `${acceptedPath} via ${path}`;
|
||||
console.debug(`[vite] hot updated: ${loggedPath}`);
|
||||
};
|
||||
}
|
||||
function sendMessageBuffer() {
|
||||
if (socket.readyState === 1) {
|
||||
messageBuffer.forEach((msg) => socket.send(msg));
|
||||
messageBuffer.length = 0;
|
||||
}
|
||||
}
|
||||
const hotModulesMap = new Map();
|
||||
const disposeMap = new Map();
|
||||
const pruneMap = new Map();
|
||||
const dataMap = new Map();
|
||||
const customListenersMap = new Map();
|
||||
const ctxToListenersMap = new Map();
|
||||
function createHotContext(ownerPath) {
|
||||
if (!dataMap.has(ownerPath)) {
|
||||
dataMap.set(ownerPath, {});
|
||||
}
|
||||
// when a file is hot updated, a new context is created
|
||||
// clear its stale callbacks
|
||||
const mod = hotModulesMap.get(ownerPath);
|
||||
if (mod) {
|
||||
mod.callbacks = [];
|
||||
}
|
||||
// clear stale custom event listeners
|
||||
const staleListeners = ctxToListenersMap.get(ownerPath);
|
||||
if (staleListeners) {
|
||||
for (const [event, staleFns] of staleListeners) {
|
||||
const listeners = customListenersMap.get(event);
|
||||
if (listeners) {
|
||||
customListenersMap.set(event, listeners.filter((l) => !staleFns.includes(l)));
|
||||
}
|
||||
}
|
||||
}
|
||||
const newListeners = new Map();
|
||||
ctxToListenersMap.set(ownerPath, newListeners);
|
||||
function acceptDeps(deps, callback = () => { }) {
|
||||
const mod = hotModulesMap.get(ownerPath) || {
|
||||
id: ownerPath,
|
||||
callbacks: [],
|
||||
};
|
||||
mod.callbacks.push({
|
||||
deps,
|
||||
fn: callback,
|
||||
});
|
||||
hotModulesMap.set(ownerPath, mod);
|
||||
}
|
||||
const hot = {
|
||||
get data() {
|
||||
return dataMap.get(ownerPath);
|
||||
},
|
||||
accept(deps, callback) {
|
||||
if (typeof deps === 'function' || !deps) {
|
||||
// self-accept: hot.accept(() => {})
|
||||
acceptDeps([ownerPath], ([mod]) => deps === null || deps === void 0 ? void 0 : deps(mod));
|
||||
}
|
||||
else if (typeof deps === 'string') {
|
||||
// explicit deps
|
||||
acceptDeps([deps], ([mod]) => callback === null || callback === void 0 ? void 0 : callback(mod));
|
||||
}
|
||||
else if (Array.isArray(deps)) {
|
||||
acceptDeps(deps, callback);
|
||||
}
|
||||
else {
|
||||
throw new Error(`invalid hot.accept() usage.`);
|
||||
}
|
||||
},
|
||||
// export names (first arg) are irrelevant on the client side, they're
|
||||
// extracted in the server for propagation
|
||||
acceptExports(_, callback) {
|
||||
acceptDeps([ownerPath], ([mod]) => callback === null || callback === void 0 ? void 0 : callback(mod));
|
||||
},
|
||||
dispose(cb) {
|
||||
disposeMap.set(ownerPath, cb);
|
||||
},
|
||||
prune(cb) {
|
||||
pruneMap.set(ownerPath, cb);
|
||||
},
|
||||
// Kept for backward compatibility (#11036)
|
||||
// @ts-expect-error untyped
|
||||
// eslint-disable-next-line @typescript-eslint/no-empty-function
|
||||
decline() { },
|
||||
// tell the server to re-perform hmr propagation from this module as root
|
||||
invalidate(message) {
|
||||
notifyListeners('vite:invalidate', { path: ownerPath, message });
|
||||
this.send('vite:invalidate', { path: ownerPath, message });
|
||||
console.debug(`[vite] invalidate ${ownerPath}${message ? `: ${message}` : ''}`);
|
||||
},
|
||||
// custom events
|
||||
on(event, cb) {
|
||||
const addToMap = (map) => {
|
||||
const existing = map.get(event) || [];
|
||||
existing.push(cb);
|
||||
map.set(event, existing);
|
||||
};
|
||||
addToMap(customListenersMap);
|
||||
addToMap(newListeners);
|
||||
},
|
||||
send(event, data) {
|
||||
messageBuffer.push(JSON.stringify({ type: 'custom', event, data }));
|
||||
sendMessageBuffer();
|
||||
},
|
||||
};
|
||||
return hot;
|
||||
}
|
||||
/**
|
||||
* urls here are dynamic import() urls that couldn't be statically analyzed
|
||||
*/
|
||||
function injectQuery(url, queryToInject) {
|
||||
// skip urls that won't be handled by vite
|
||||
if (url[0] !== '.' && url[0] !== '/') {
|
||||
return url;
|
||||
}
|
||||
// can't use pathname from URL since it may be relative like ../
|
||||
const pathname = url.replace(/#.*$/, '').replace(/\?.*$/, '');
|
||||
const { search, hash } = new URL(url, 'http://vitejs.dev');
|
||||
return `${pathname}?${queryToInject}${search ? `&` + search.slice(1) : ''}${hash || ''}`;
|
||||
}
|
||||
|
||||
export { ErrorOverlay, createHotContext, injectQuery, removeStyle, updateStyle };
|
||||
|
||||
|
||||
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiY2xpZW50Lm1qcyIsInNvdXJjZXMiOlsib3ZlcmxheS50cyIsImNsaWVudC50cyJdLCJzb3VyY2VzQ29udGVudCI6WyJpbXBvcnQgdHlwZSB7IEVycm9yUGF5bG9hZCB9IGZyb20gJ3R5cGVzL2htclBheWxvYWQnXG5cbi8vIGluamVjdGVkIGJ5IHRoZSBobXIgcGx1Z2luIHdoZW4gc2VydmVkXG5kZWNsYXJlIGNvbnN0IF9fQkFTRV9fOiBzdHJpbmdcblxuY29uc3QgYmFzZSA9IF9fQkFTRV9fIHx8ICcvJ1xuXG4vLyBzZXQgOmhvc3Qgc3R5bGVzIHRvIG1ha2UgcGxheXdyaWdodCBkZXRlY3QgdGhlIGVsZW1lbnQgYXMgdmlzaWJsZVxuY29uc3QgdGVtcGxhdGUgPSAvKmh0bWwqLyBgXG48c3R5bGU+XG46aG9zdCB7XG4gIHBvc2l0aW9uOiBmaXhlZDtcbiAgdG9wOiAwO1xuICBsZWZ0OiAwO1xuICB3aWR0aDogMTAwJTtcbiAgaGVpZ2h0OiAxMDAlO1xuICB6LWluZGV4OiA5OTk5OTtcbiAgLS1tb25vc3BhY2U6ICdTRk1vbm8tUmVndWxhcicsIENvbnNvbGFzLFxuICAnTGliZXJhdGlvbiBNb25vJywgTWVubG8sIENvdXJpZXIsIG1vbm9zcGFjZTtcbiAgLS1yZWQ6ICNmZjU1NTU7XG4gIC0teWVsbG93OiAjZTJhYTUzO1xuICAtLXB1cnBsZTogI2NmYTRmZjtcbiAgLS1jeWFuOiAjMmRkOWRhO1xuICAtLWRpbTogI2M5YzljOTtcblxuICAtLXdpbmRvdy1iYWNrZ3JvdW5kOiAjMTgxODE4O1xuICAtLXdpbmRvdy1jb2xvcjogI2Q4ZDhkODtcbn1cblxuLmJhY2tkcm9wIHtcbiAgcG9zaXRpb246IGZpeGVkO1xuICB6LWluZGV4OiA5OTk5OTtcbiAgdG9wOiAwO1xuICBsZWZ0OiAwO1xuICB3aWR0aDogMTAwJTtcbiAgaGVpZ2h0OiAxMDAlO1xuICBvdmVyZmxvdy15OiBzY3JvbGw7XG4gIG1hcmdpbjogMDtcbiAgYmFja2dyb3VuZDogcmdiYSgwLCAwLCAwLCAwLjY2KTtcbn1cblxuLndpbmRvdyB7XG4gIGZvbnQtZmFtaWx5OiB2YXIoLS1tb25vc3BhY2UpO1xuICBsaW5lLWhlaWdodDogMS41O1xuICB3aWR0aDogODAwcHg7XG4gIGNvbG9yOiB2YXIoLS13aW5kb3ctY29sb3IpO1xuICBtYXJnaW46IDMwcHggYXV0bztcbiAgcGFkZGluZzogMjVweCA0MHB4O1xuICBwb3NpdGlvbjogcmVsYXRpdmU7XG4gIGJhY2tncm91bmQ6IHZhcigtLXdpbmRvdy1iYWNrZ3JvdW5kKTtcbiAgYm9yZGVyLXJhZGl1czogNnB4IDZweCA4cHggOHB4O1xuICBib3gtc2hhZG93OiAwIDE5cHggMzhweCByZ2JhKDAsMCwwLDAuMzApLCAwIDE1cHggMTJweCByZ2JhKDAsMCwwLDAuMjIpO1xuICBvdmVyZmxvdzogaGlkZGVuO1xuICBib3JkZXItdG9wOiA4cHggc29saWQgdmFyKC0tcmVkKTtcbiAgZGlyZWN0aW9uOiBsdHI7XG4gIHRleHQtYWxpZ246IGxlZnQ7XG59XG5cbnByZSB7XG4gIGZvbnQtZmFtaWx5OiB2YXIoLS1tb25vc3BhY2UpO1xuICBmb250LXNpemU6IDE2cHg7XG4gIG1hcmdpbi10b3A6IDA7XG4gIG1hcmdpbi1ib3R0b206IDFlbTtcbiAgb3ZlcmZsb3cteDogc2Nyb2xsO1xuICBzY3JvbGxiYXItd2lkdGg6IG5vbmU7XG59XG5cbnByZTo6LXdlYmtpdC1zY3JvbGxiYXIge1xuICBkaXNwbGF5OiBub25lO1xufVxuXG4ubWVzc2FnZSB7XG4gIGxpbmUtaGVpZ2h0OiAxLjM7XG4gIGZvbnQtd2VpZ2h0OiA2MDA7XG4gIHdoaXRlLXNwYWNlOiBwcmUtd3JhcDtcbn1cblxuLm1lc3NhZ2UtYm9keSB7XG4gIGNvbG9yOiB2YXIoLS1yZWQpO1xufVxuXG4ucGx1Z2luIHtcbiAgY29sb3I6IHZhcigtLXB1cnBsZSk7XG59XG5cbi5maWxlIHtcbiAgY29sb3I6IHZhcigtLWN5YW4pO1xuICBtYXJnaW4tYm90dG9tOiAwO1xuICB3aGl0ZS1zcGFjZTogcHJlLXdyYXA7XG4gIHdvcmQtYnJlYWs6IGJyZWFrLWFsbDtcbn1cblxuLmZyYW1lIHtcbiAgY29sb3I6IHZhcigtLXllbGxvdyk7XG59XG5cbi5zdGFjayB7XG4gIGZvbnQtc2l6ZTogMTNweDtcbiAgY29sb3I6IHZhcigtLWRpbSk7XG59XG5cbi50aXAge1xuICBmb250LXNpemU6IDEzcHg7XG4gIGNvbG9yOiAjOTk5O1xuICBib3JkZXItdG9wOiAxcHggZG90dGVkICM5OTk7XG4gIHBhZGRpbmctdG9wOiAxM3B4O1xuICBsaW5lLWhlaWdodDogMS44O1xufVxuXG5jb2RlIHtcbiAgZm9udC1zaXplOiAxM3B4O1xuICBmb250LWZhbWlseTogdmFyKC0tbW9ub3NwYWNlKTtcbiAgY29sb3I6IHZhcigtLXllbGxvdyk7XG59XG5cbi5maWxlLWxpbmsge1xuICB0ZXh0LWRlY29yYXRpb246IHVuZGVybGluZTtcbiAgY3Vyc29yOiBwb2ludGVyO1xufVxuXG5rYmQge1xuICBsaW5lLWhlaWdodDogMS41O1xuICBmb250LWZhbWlseTogdWktbW9ub3NwYWNlLCBNZW5sbywgTW9uYWNvLCBDb25zb2xhcywgXCJMaWJlcmF0aW9uIE1vbm9cIiwgXCJDb3VyaWVyIE5ld1wiLCBtb25vc3BhY2U7XG4gIGZvbnQtc2l6ZTogMC43NXJlbTtcbiAgZm9udC13ZWlnaHQ6IDcwMDtcbiAgYmFja2dyb3VuZC1jb2xvcjogcmdiKDM4LCA0MCwgNDQpO1xuICBjb2xvcjogcmdiKDE2NiwgMTY3LCAxNzEpO1xuICBwYWRkaW5nOiAwLjE1cmVtIDAuM3JlbTtcbiAgYm9yZGVyLXJhZGl1czogMC4yNXJlbTtcbiAgYm9yZGVyLXdpZHRoOiAwLjA2MjVyZW0gMC4wNjI1cmVtIDAuMTg3NXJlbTtcbiAgYm9yZGVyLXN0eWxlOiBzb2xpZDtcbiAgYm9yZGVyLWNvbG9yOiByZ2IoNTQsIDU3LCA2NCk7XG4gIGJvcmRlci1pbWFnZTogaW5pdGlhbDtcbn1cbjwvc3R5bGU+XG48ZGl2IGNsYXNzPVwiYmFja2Ryb3BcIiBwYXJ0PVwiYmFja2Ryb3BcIj5cbiAgPGRpdiBjbGFzcz1cIndpbmRvd1wiIHBhcnQ9XCJ3aW5kb3dcIj5cbiAgICA8cHJlIGNsYXNzPVwibWVzc2FnZVwiIHBhcnQ9XCJtZXNzYWdlXCI+PHNwYW4gY2xhc3M9XCJwbHVnaW5cIiBwYXJ0PVwicGx1Z2luXCI+PC9zcGFuPjxzcGFuIGNsYXNzPVwibWVzc2FnZS1ib2R5XCIgcGFydD1cIm1lc3NhZ2UtYm9keVwiPjwvc3Bhbj48L3ByZT5cbiAgICA8cHJlIGNsYXNzPVwiZmlsZVwiIHBhcnQ9XCJmaWxlXCI+PC9wcmU+XG4gICAgPHByZSBjbGFzcz1cImZyYW1lXCIgcGFydD1cImZyYW1lXCI+PC9wcmU+XG4gICAgPHByZSBjbGFzcz1cInN0YWNrXCIgcGFydD1cInN0YWNrXCI+PC9wcmU+XG4gICAgPGRpdiBjbGFzcz1cInRpcFwiIHBhcnQ9XCJ0aXBcIj5cbiAgICAgIENsaWNrIG91dHNpZGUsIHByZXNzIDxrYmQ+RXNjPC9rYmQ+IGtleSwgb3IgZml4IHRoZSBjb2RlIHRvIGRpc21pc3MuPGJyPlxuICAgICAgWW91IGNhbiBhbHNvIGRpc2FibGUgdGhpcyBvdmVybGF5IGJ5IHNldHRpbmdcbiAgICAgIDxjb2RlIHBhcnQ9XCJjb25maWctb3B0aW9uLW5hbWVcIj5zZXJ2ZXIuaG1yLm92ZXJsYXk8L2NvZGU+IHRvIDxjb2RlIHBhcnQ9XCJjb25maWctb3B0aW9uLXZhbHVlXCI+ZmFsc2U8L2NvZGU+IGluIDxjb2RlIHBhcnQ9XCJjb25maWctZmlsZS1uYW1lXCI+dml0ZS5jb25maWcuanMuPC9jb2RlPlxuICAgIDwvZGl2PlxuICA8L2Rpdj5cbjwvZGl2PlxuYFxuXG5jb25zdCBmaWxlUkUgPSAvKD86W2EtekEtWl06XFxcXHxcXC8pLio/OlxcZCs6XFxkKy9nXG5jb25zdCBjb2RlZnJhbWVSRSA9IC9eKD86Pj9cXHMrXFxkK1xccytcXHwuKnxcXHMrXFx8XFxzKlxcXi4qKVxccj9cXG4vZ21cblxuLy8gQWxsb3cgYEVycm9yT3ZlcmxheWAgdG8gZXh0ZW5kIGBIVE1MRWxlbWVudGAgZXZlbiBpbiBlbnZpcm9ubWVudHMgd2hlcmVcbi8vIGBIVE1MRWxlbWVudGAgd2FzIG5vdCBvcmlnaW5hbGx5IGRlZmluZWQuXG5jb25zdCB7IEhUTUxFbGVtZW50ID0gY2xhc3Mge30gYLine truncated
|
||||
Access-Control-Allow-Origin: *
|
||||
Content-Type: application/javascript
|
||||
Cache-Control: no-cache
|
||||
Etag: W/"62fb-sfz4JhSKWW1gLUlilrp/Gcy44jo"
|
||||
Date: Fri, 15 Mar 2024 14:43:29 GMT
|
||||
Content-Length: 86398
|
||||
| ||||