├── .gitignore ├── public ├── demo.png ├── favicon.ico ├── parroticon.png ├── usericon.png ├── favicon-16x16.png ├── favicon-32x32.png ├── sitenodes.json └── index.html ├── src ├── assets │ ├── images │ │ ├── sharing.png │ │ ├── flowise_logo.png │ │ ├── open-csg-white.png │ │ ├── flowise_logo_dark.png │ │ ├── google-login-white.png │ │ ├── cURL.svg │ │ ├── javascript.svg │ │ ├── python.svg │ │ └── embed.svg │ └── scss │ │ ├── style.scss │ │ └── _themes-vars.module.scss ├── store │ ├── context │ │ ├── MetaContext.js │ │ ├── ConfirmContext.js │ │ ├── ConfirmContextProvider.js │ │ ├── MetaContextProvider.js │ │ └── ReactFlowContext.js │ ├── index.js │ ├── constant.js │ ├── reducers │ │ ├── metaRedicer.js │ │ ├── dialogReducer.js │ │ ├── canvasReducer.js │ │ ├── notifierReducer.js │ │ └── customizationReducer.js │ ├── reducer.js │ └── actions.js ├── layout │ ├── MainLayout │ │ ├── Header │ │ │ ├── UserSection │ │ │ │ └── index.css │ │ │ └── ProfileSection │ │ │ │ └── index.css │ │ ├── LogoSection │ │ │ └── index.js │ │ ├── Sidebar │ │ │ ├── MenuList │ │ │ │ ├── index.js │ │ │ │ ├── NavGroup │ │ │ │ │ └── index.js │ │ │ │ ├── NavCollapse │ │ │ │ │ └── index.js │ │ │ │ └── NavItem │ │ │ │ │ └── index.js │ │ │ └── index.js │ │ └── index.js │ ├── MinimalLayout │ │ └── index.js │ ├── NavigationScroll.js │ └── NavMotion.js ├── api │ ├── auth.js │ ├── config.js │ ├── workloads.js │ ├── prediction.js │ ├── dashboard.js │ ├── nodes.js │ ├── jobqueue.js │ ├── client.js │ ├── jobs.js │ └── useraccount.js ├── ui-component │ ├── dialog │ │ ├── EditPromptValuesDialog.css │ │ ├── ConfirmDialog.js │ │ ├── SourceDocDialog.js │ │ ├── AdditionalParamsDialog.js │ │ ├── LoginDialog.js │ │ ├── AboutDialog.js │ │ └── SaveChatflowDialog.js │ ├── markdown │ │ ├── MemoizedReactMarkdown.js │ │ └── CodeBlock.js │ ├── alert │ │ └── Alert.js │ ├── button │ │ ├── StyledFab.js │ │ ├── StyledButton.js │ │ └── AnimateButton.js │ ├── loading │ │ ├── Loadable.js │ │ ├── BackdropLoader.js │ │ └── Loader.js │ ├── extended │ │ ├── Logo.js │ │ ├── Avatar.js │ │ └── Transitions.js │ ├── switch │ │ └── Switch.js │ ├── tooltip │ │ └── TooltipWithParser.js │ ├── checkbox │ │ └── Checkbox.js │ ├── cards │ │ ├── Skeleton │ │ │ └── ChatflowCard.js │ │ └── MainCard.js │ ├── grid │ │ └── Grid.js │ ├── table │ │ └── Table.js │ ├── editor │ │ ├── DarkCodeEditor.js │ │ ├── LightCodeEditor.js │ │ └── prism-light.css │ ├── menu │ │ └── StyledMenu.js │ ├── json │ │ └── JsonEditor.js │ ├── input │ │ └── Input.js │ ├── dropdown │ │ └── Dropdown.js │ └── file │ │ └── File.js ├── views │ ├── dashboard │ │ └── Loading.js │ ├── qosmanage │ │ └── TableToolbar.js │ ├── callback │ │ └── index.js │ ├── usermanage │ │ └── TableToolbar.js │ ├── orgmanage │ │ ├── enhancedtabletoolbar.js │ │ └── enhancedtablehead.js │ ├── k8sjobs │ │ ├── enhanceddeploytablehead.js │ │ ├── enhancedpodtablehead.js │ │ ├── enhancedservicetablehead.js │ │ ├── deploytablebody.js │ │ ├── podtablebody.js │ │ ├── enhancedtabletoolbar.js │ │ └── servicetablebody.js │ ├── hpcjobs │ │ ├── JobDetails.js │ │ └── enhancedtablehead.js │ └── resources │ │ ├── enhancedtabletoolbar.js │ │ └── enhancedtablehead.js ├── menu-items │ ├── index.js │ └── dashboard.js ├── auth │ └── AuthProvider.js ├── hooks │ ├── useScriptRef.js │ ├── useApi.js │ ├── useConfirm.js │ └── useAuth.js ├── routes │ ├── index.js │ ├── AuthRoutes.js │ └── MainRoutes.js ├── config.js ├── App.js ├── utils │ ├── usePrompt.js │ └── useNotifier.js ├── index.js └── themes │ ├── index.js │ ├── typography.js │ └── palette.js ├── .env ├── .env.example ├── jsconfig.json ├── craco.config.js ├── README.md └── package.json /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | -------------------------------------------------------------------------------- /public/demo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OpenCSGs/llm-scheduler-ui/HEAD/public/demo.png -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OpenCSGs/llm-scheduler-ui/HEAD/public/favicon.ico -------------------------------------------------------------------------------- /public/parroticon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OpenCSGs/llm-scheduler-ui/HEAD/public/parroticon.png -------------------------------------------------------------------------------- /public/usericon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OpenCSGs/llm-scheduler-ui/HEAD/public/usericon.png -------------------------------------------------------------------------------- /public/favicon-16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OpenCSGs/llm-scheduler-ui/HEAD/public/favicon-16x16.png -------------------------------------------------------------------------------- /public/favicon-32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OpenCSGs/llm-scheduler-ui/HEAD/public/favicon-32x32.png -------------------------------------------------------------------------------- /src/assets/images/sharing.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OpenCSGs/llm-scheduler-ui/HEAD/src/assets/images/sharing.png -------------------------------------------------------------------------------- /src/assets/images/flowise_logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OpenCSGs/llm-scheduler-ui/HEAD/src/assets/images/flowise_logo.png -------------------------------------------------------------------------------- /src/assets/images/open-csg-white.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OpenCSGs/llm-scheduler-ui/HEAD/src/assets/images/open-csg-white.png -------------------------------------------------------------------------------- /src/assets/images/flowise_logo_dark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OpenCSGs/llm-scheduler-ui/HEAD/src/assets/images/flowise_logo_dark.png -------------------------------------------------------------------------------- /src/assets/images/google-login-white.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OpenCSGs/llm-scheduler-ui/HEAD/src/assets/images/google-login-white.png -------------------------------------------------------------------------------- /src/store/context/MetaContext.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | const MetaContext = React.createContext() 4 | 5 | export default MetaContext 6 | -------------------------------------------------------------------------------- /src/store/context/ConfirmContext.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | const ConfirmContext = React.createContext() 4 | 5 | export default ConfirmContext 6 | -------------------------------------------------------------------------------- /src/layout/MainLayout/Header/UserSection/index.css: -------------------------------------------------------------------------------- 1 | .ps__rail-x { 2 | display: none !important; 3 | } 4 | .ps__thumb-x { 5 | display: none !important; 6 | } 7 | -------------------------------------------------------------------------------- /src/api/auth.js: -------------------------------------------------------------------------------- 1 | import client from './client' 2 | 3 | const signIn = (user) => client.post('/api/v1/auth/signin', user) 4 | 5 | export default { 6 | signIn 7 | } 8 | -------------------------------------------------------------------------------- /src/api/config.js: -------------------------------------------------------------------------------- 1 | import client from './client' 2 | 3 | const getConfig = (id) => client.get(`/flow-config/${id}`) 4 | 5 | export default { 6 | getConfig 7 | } 8 | -------------------------------------------------------------------------------- /src/layout/MainLayout/Header/ProfileSection/index.css: -------------------------------------------------------------------------------- 1 | .ps__rail-x { 2 | display: none !important; 3 | } 4 | .ps__thumb-x { 5 | display: none !important; 6 | } 7 | -------------------------------------------------------------------------------- /.env: -------------------------------------------------------------------------------- 1 | PORT=8080 2 | REACT_APP_SCHEDULER_API=http://localhost:3000 3 | REACT_APP_ORGNAME=OpenCSG 4 | REACT_APP_APPNAME=StarCloud 5 | REACT_APP_OUTPUT_LOCATION=/home/users 6 | 7 | -------------------------------------------------------------------------------- /.env.example: -------------------------------------------------------------------------------- 1 | PORT=8080 2 | REACT_APP_SCHEDULER_API=http://localhost:3000 3 | REACT_APP_ORGNAME=OpenCSG 4 | REACT_APP_APPNAME=StarCloud 5 | REACT_APP_OUTPUT_LOCATION=/home/users -------------------------------------------------------------------------------- /src/ui-component/dialog/EditPromptValuesDialog.css: -------------------------------------------------------------------------------- 1 | .editor__textarea { 2 | outline: 0; 3 | } 4 | .editor__textarea::placeholder { 5 | color: rgba(120, 120, 120, 0.5); 6 | } 7 | -------------------------------------------------------------------------------- /src/api/workloads.js: -------------------------------------------------------------------------------- 1 | import client from './client' 2 | 3 | const applyWorkloads = (data) => client.post('/api/v1/k8s/workload/workloads/', data) 4 | 5 | export default { 6 | applyWorkloads, 7 | } -------------------------------------------------------------------------------- /src/api/prediction.js: -------------------------------------------------------------------------------- 1 | import client from './client' 2 | 3 | const sendMessageAndGetPrediction = (id, input) => client.post(`/internal-prediction/${id}`, input) 4 | 5 | export default { 6 | sendMessageAndGetPrediction 7 | } 8 | -------------------------------------------------------------------------------- /jsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "esnext", 4 | "module": "commonjs", 5 | "baseUrl": "src" 6 | }, 7 | "include": ["src/**/*"], 8 | "exclude": ["node_modules"] 9 | } 10 | -------------------------------------------------------------------------------- /src/views/dashboard/Loading.js: -------------------------------------------------------------------------------- 1 | import { Box, CircularProgress } from "@mui/material"; 2 | 3 | export default () => ( 4 | 5 | 6 | 7 | ) -------------------------------------------------------------------------------- /src/menu-items/index.js: -------------------------------------------------------------------------------- 1 | import dashboard from './dashboard' 2 | 3 | // ==============================|| MENU ITEMS ||============================== // 4 | 5 | const menuItems = { 6 | items: [dashboard()] 7 | } 8 | 9 | export default menuItems 10 | -------------------------------------------------------------------------------- /src/ui-component/markdown/MemoizedReactMarkdown.js: -------------------------------------------------------------------------------- 1 | import { memo } from 'react' 2 | import ReactMarkdown from 'react-markdown' 3 | 4 | export const MemoizedReactMarkdown = memo(ReactMarkdown, (prevProps, nextProps) => prevProps.children === nextProps.children) 5 | -------------------------------------------------------------------------------- /src/api/dashboard.js: -------------------------------------------------------------------------------- 1 | import client from './client' 2 | 3 | const getHPCDashboard = () => client.get('/api/v1/dashboard/hpc') 4 | const getK8SDashboard = () => client.get('/api/v1/dashboard/k8s') 5 | 6 | export default { 7 | getHPCDashboard, 8 | getK8SDashboard 9 | } -------------------------------------------------------------------------------- /src/ui-component/alert/Alert.js: -------------------------------------------------------------------------------- 1 | import * as React from 'react' 2 | import MuiAlert from '@mui/material/Alert' 3 | 4 | export const Alert = React.forwardRef(function Alert(props, ref) { 5 | return 6 | }) 7 | -------------------------------------------------------------------------------- /src/layout/MinimalLayout/index.js: -------------------------------------------------------------------------------- 1 | import { Outlet } from 'react-router-dom' 2 | 3 | // ==============================|| MINIMAL LAYOUT ||============================== // 4 | 5 | const MinimalLayout = () => ( 6 | <> 7 | 8 | 9 | ) 10 | 11 | export default MinimalLayout 12 | -------------------------------------------------------------------------------- /src/store/index.js: -------------------------------------------------------------------------------- 1 | import { createStore } from 'redux' 2 | import reducer from './reducer' 3 | 4 | // ==============================|| REDUX - MAIN STORE ||============================== // 5 | 6 | const store = createStore(reducer) 7 | const persister = 'Free' 8 | 9 | export { store, persister } 10 | -------------------------------------------------------------------------------- /public/sitenodes.json: -------------------------------------------------------------------------------- 1 | { 2 | "node": { 3 | "openAI": -1, 4 | "azureOpenAI": -1, 5 | "openAIFunctionAgent": -1, 6 | "azureChatOpenAI": -1, 7 | "chatOpenAI": -1, 8 | "openAIEmbeddings": -1, 9 | "azureOpenAIEmbeddings": -1 10 | }, 11 | "tip": "因为政策原因OpenAI相关服务暂时无法使用,敬请谅解" 12 | } 13 | -------------------------------------------------------------------------------- /src/store/constant.js: -------------------------------------------------------------------------------- 1 | // constant 2 | export const gridSpacing = 3 3 | export const drawerWidth = 260 4 | export const appDrawerWidth = 320 5 | export const maxScroll = 100000 6 | export const baseURL = process.env.REACT_APP_SCHEDULER_API 7 | export const uiBaseURL = window.location.origin 8 | export const slurmContext = '/slurm/v0.0.41' 9 | export const slurmDBContext = '/slurmdb/v0.0.41' 10 | -------------------------------------------------------------------------------- /src/auth/AuthProvider.js: -------------------------------------------------------------------------------- 1 | import useAuth from 'hooks/useAuth' 2 | 3 | const AuthContext = createContext() 4 | 5 | const useAuthContext = () => useContext(AuthContext) 6 | 7 | const AuthProvider = ({ children }) => { 8 | const auth = useAuth() 9 | 10 | return {children} 11 | } 12 | 13 | export { useAuthContext, AuthProvider } 14 | -------------------------------------------------------------------------------- /src/ui-component/button/StyledFab.js: -------------------------------------------------------------------------------- 1 | import { styled } from '@mui/material/styles' 2 | import { Fab } from '@mui/material' 3 | 4 | export const StyledFab = styled(Fab)(({ theme, color = 'primary' }) => ({ 5 | color: 'white', 6 | backgroundColor: theme.palette[color].main, 7 | '&:hover': { 8 | backgroundColor: theme.palette[color].main, 9 | backgroundImage: `linear-gradient(rgb(0 0 0/10%) 0 0)` 10 | } 11 | })) 12 | -------------------------------------------------------------------------------- /src/ui-component/button/StyledButton.js: -------------------------------------------------------------------------------- 1 | import { styled } from '@mui/material/styles' 2 | import { Button } from '@mui/material' 3 | 4 | export const StyledButton = styled(Button)(({ theme, color = 'primary' }) => ({ 5 | color: 'white', 6 | backgroundColor: theme.palette[color].main, 7 | '&:hover': { 8 | backgroundColor: theme.palette[color].main, 9 | backgroundImage: `linear-gradient(rgb(0 0 0/10%) 0 0)` 10 | } 11 | })) 12 | -------------------------------------------------------------------------------- /src/hooks/useScriptRef.js: -------------------------------------------------------------------------------- 1 | import { useEffect, useRef } from 'react' 2 | 3 | // ==============================|| ELEMENT REFERENCE HOOKS ||============================== // 4 | 5 | const useScriptRef = () => { 6 | const scripted = useRef(true) 7 | 8 | useEffect( 9 | () => () => { 10 | scripted.current = false 11 | }, 12 | [] 13 | ) 14 | 15 | return scripted 16 | } 17 | 18 | export default useScriptRef 19 | -------------------------------------------------------------------------------- /src/store/reducers/metaRedicer.js: -------------------------------------------------------------------------------- 1 | import { SET_TAGS } from '../actions' 2 | 3 | export const initialState = { 4 | tags: [], 5 | } 6 | 7 | const metaReducer = (state = initialState, action) => { 8 | switch (action.type) { 9 | case SET_TAGS: 10 | return { 11 | ...state, 12 | tags: [...action.tags] 13 | } 14 | default: 15 | return state 16 | } 17 | } 18 | 19 | export default metaReducer 20 | -------------------------------------------------------------------------------- /src/ui-component/loading/Loadable.js: -------------------------------------------------------------------------------- 1 | import { Suspense } from 'react' 2 | 3 | // project imports 4 | import Loader from './Loader' 5 | 6 | // ==============================|| LOADABLE - LAZY LOADING ||============================== // 7 | 8 | const Loadable = (Component) => 9 | function WithLoader(props) { 10 | return ( 11 | }> 12 | 13 | 14 | ) 15 | } 16 | 17 | export default Loadable 18 | -------------------------------------------------------------------------------- /src/ui-component/loading/BackdropLoader.js: -------------------------------------------------------------------------------- 1 | import PropTypes from 'prop-types' 2 | import { Backdrop, CircularProgress } from '@mui/material' 3 | 4 | export const BackdropLoader = ({ open }) => { 5 | return ( 6 |
7 | theme.zIndex.drawer + 1 }} open={open}> 8 | 9 | 10 |
11 | ) 12 | } 13 | 14 | BackdropLoader.propTypes = { 15 | open: PropTypes.bool 16 | } 17 | -------------------------------------------------------------------------------- /src/api/nodes.js: -------------------------------------------------------------------------------- 1 | import client from './client' 2 | import { slurmContext } from 'store/constant' 3 | 4 | const getHPCNodes = () => client.get(slurmContext + '/nodes') 5 | 6 | const getK8sNodes = () => client.get('/api/v1/k8s/nodes') 7 | 8 | const getSpecificNode = (name) => client.get(slurmContext + `/nodes/${name}`) 9 | 10 | const updateNode = (name, body) => client.post(slurmContext + `/nodes/${name}`, body) 11 | 12 | export default { 13 | getHPCNodes, 14 | getK8sNodes, 15 | getSpecificNode, 16 | updateNode 17 | } 18 | -------------------------------------------------------------------------------- /src/api/jobqueue.js: -------------------------------------------------------------------------------- 1 | import client from './client' 2 | 3 | import { slurmContext, slurmDBContext } from 'store/constant' 4 | 5 | const getMyQueue = () => client.get('/api/v1/jobs/queues') 6 | 7 | const getAllQueue = () => client.get(slurmContext + '/partitions') 8 | 9 | const getQosInfo = (name) => client.get(slurmDBContext + `/qos/${name}`) 10 | 11 | const getSpecificQueue = (name) => client.get(`/api/v1/jobs/queue/${name}`) 12 | 13 | export default { 14 | getAllQueue, 15 | getMyQueue, 16 | getQosInfo, 17 | getSpecificQueue 18 | } 19 | -------------------------------------------------------------------------------- /src/layout/MainLayout/LogoSection/index.js: -------------------------------------------------------------------------------- 1 | import { Link } from 'react-router-dom' 2 | 3 | // material-ui 4 | import { ButtonBase } from '@mui/material' 5 | 6 | // project imports 7 | import config from 'config' 8 | import Logo from 'ui-component/extended/Logo' 9 | 10 | // ==============================|| MAIN LOGO ||============================== // 11 | 12 | const LogoSection = () => ( 13 | 14 | 15 | 16 | ) 17 | 18 | export default LogoSection 19 | -------------------------------------------------------------------------------- /src/ui-component/loading/Loader.js: -------------------------------------------------------------------------------- 1 | // material-ui 2 | import LinearProgress from '@mui/material/LinearProgress' 3 | import { styled } from '@mui/material/styles' 4 | 5 | // styles 6 | const LoaderWrapper = styled('div')({ 7 | position: 'fixed', 8 | top: 0, 9 | left: 0, 10 | zIndex: 1301, 11 | width: '100%' 12 | }) 13 | 14 | // ==============================|| LOADER ||============================== // 15 | const Loader = () => ( 16 | 17 | 18 | 19 | ) 20 | 21 | export default Loader 22 | -------------------------------------------------------------------------------- /src/routes/index.js: -------------------------------------------------------------------------------- 1 | import { useRoutes } from 'react-router-dom' 2 | 3 | // routes 4 | import MainRoutes from './MainRoutes' 5 | import AuthRoutes from './AuthRoutes' 6 | import config from 'config' 7 | 8 | // Hooks 9 | import useAuth from 'hooks/useAuth' 10 | 11 | // ==============================|| ROUTING RENDER ||============================== // 12 | 13 | export default function ThemeRoutes() { 14 | const { getIDToken } = useAuth() 15 | const idToken = getIDToken() 16 | const isLoggedIn = idToken ? true : false 17 | return useRoutes([MainRoutes(isLoggedIn), AuthRoutes], config.basename) 18 | } 19 | -------------------------------------------------------------------------------- /src/store/context/ConfirmContextProvider.js: -------------------------------------------------------------------------------- 1 | import { useReducer } from 'react' 2 | import PropTypes from 'prop-types' 3 | import alertReducer, { initialState } from '../reducers/dialogReducer' 4 | import ConfirmContext from './ConfirmContext' 5 | 6 | const ConfirmContextProvider = ({ children }) => { 7 | const [state, dispatch] = useReducer(alertReducer, initialState) 8 | 9 | return {children} 10 | } 11 | 12 | ConfirmContextProvider.propTypes = { 13 | children: PropTypes.any 14 | } 15 | 16 | export default ConfirmContextProvider 17 | -------------------------------------------------------------------------------- /craco.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | webpack: { 3 | configure: { 4 | module: { 5 | rules: [ 6 | { 7 | test: /\.m?js$/, 8 | resolve: { 9 | fullySpecified: false 10 | } 11 | } 12 | ] 13 | } 14 | }, 15 | devServer: { 16 | host: '0.0.0.0', 17 | disableHostCheck: true 18 | }, 19 | headers: { 20 | 'X-Frame-Options': 'ALLOWALL' 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/store/reducer.js: -------------------------------------------------------------------------------- 1 | import { combineReducers } from 'redux' 2 | 3 | // reducer import 4 | import customizationReducer from './reducers/customizationReducer' 5 | import canvasReducer from './reducers/canvasReducer' 6 | import notifierReducer from './reducers/notifierReducer' 7 | import dialogReducer from './reducers/dialogReducer' 8 | 9 | // ==============================|| COMBINE REDUCER ||============================== // 10 | 11 | const reducer = combineReducers({ 12 | customization: customizationReducer, 13 | canvas: canvasReducer, 14 | notifier: notifierReducer, 15 | dialog: dialogReducer 16 | }) 17 | 18 | export default reducer 19 | -------------------------------------------------------------------------------- /src/assets/images/cURL.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/hooks/useApi.js: -------------------------------------------------------------------------------- 1 | import { useState } from 'react' 2 | 3 | export default (apiFunc) => { 4 | const [data, setData] = useState(null) 5 | const [error, setError] = useState(null) 6 | const [loading, setLoading] = useState(false) 7 | 8 | const request = async (...args) => { 9 | setLoading(true) 10 | try { 11 | const result = await apiFunc(...args) 12 | setData(result.data) 13 | } catch (err) { 14 | setError(err || 'Unexpected Error!') 15 | } finally { 16 | setLoading(false) 17 | } 18 | } 19 | 20 | return { 21 | data, 22 | error, 23 | loading, 24 | request 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/store/context/MetaContextProvider.js: -------------------------------------------------------------------------------- 1 | import { useReducer } from 'react' 2 | import PropTypes from 'prop-types' 3 | import metaReducer, { initialState } from '../reducers/metaRedicer' 4 | import MetaContext from './MetaContext' 5 | import Cookies from 'js-cookie' 6 | import axios from 'axios' 7 | import { baseURL } from 'store/constant' 8 | import { SET_TAGS } from 'store/actions' 9 | 10 | const MetaContextProvider = ({ children }) => { 11 | const [state, dispatch] = useReducer(metaReducer, initialState) 12 | return {children} 13 | } 14 | 15 | MetaContextProvider.propTypes = { 16 | children: PropTypes.any 17 | } 18 | 19 | export default MetaContextProvider 20 | -------------------------------------------------------------------------------- /src/layout/NavigationScroll.js: -------------------------------------------------------------------------------- 1 | import PropTypes from 'prop-types' 2 | import { useEffect } from 'react' 3 | import { useLocation } from 'react-router-dom' 4 | 5 | // ==============================|| NAVIGATION SCROLL TO TOP ||============================== // 6 | 7 | const NavigationScroll = ({ children }) => { 8 | const location = useLocation() 9 | const { pathname } = location 10 | 11 | useEffect(() => { 12 | window.scrollTo({ 13 | top: 0, 14 | left: 0, 15 | behavior: 'smooth' 16 | }) 17 | }, [pathname]) 18 | 19 | return children || null 20 | } 21 | 22 | NavigationScroll.propTypes = { 23 | children: PropTypes.node 24 | } 25 | 26 | export default NavigationScroll 27 | -------------------------------------------------------------------------------- /src/assets/images/javascript.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # LLM scheduler UI 2 | 3 | ## Overview 4 | 5 | LLM scheduler UI is an open source web interface to LLM jobs. the scheduler engine is using [Slurm](https://slurm.schedmd.com/) as default engine 6 | It performs large model jobs scheduling based on GPU resources. 7 | 8 | [](https://www.bilibili.com/video/BV1hH4y1G73D/?vd_source=998127e911ff03443d75c8df2b3d33e8) 9 | 10 | ## Prerequisite 11 | 12 | please setup scheduler API fisrtly [llm-scheduler-api](https://github.com/OpenCSGs/llm-scheduler-api) 13 | then configure the environment variable in .env 14 | 15 | ## Quick Start 16 | 1. install dependency 17 | ```bash 18 | npm install yarn -g 19 | yarn install 20 | ``` 21 | 2. install dependency 22 | ```bash 23 | yarn start 24 | ``` 25 | -------------------------------------------------------------------------------- /src/ui-component/extended/Logo.js: -------------------------------------------------------------------------------- 1 | import logo from 'assets/images/flowise_logo.png' 2 | import logoDark from 'assets/images/flowise_logo_dark.png' 3 | 4 | import { useSelector } from 'react-redux' 5 | 6 | // ==============================|| LOGO ||============================== // 7 | 8 | const Logo = () => { 9 | const customization = useSelector((state) => state.customization) 10 | 11 | return ( 12 |
13 | Flowise 18 |
19 | ) 20 | } 21 | 22 | export default Logo 23 | -------------------------------------------------------------------------------- /src/routes/AuthRoutes.js: -------------------------------------------------------------------------------- 1 | import { lazy } from 'react' 2 | 3 | // project imports 4 | import Loadable from 'ui-component/loading/Loadable' 5 | import MinimalLayout from 'layout/MinimalLayout' 6 | 7 | // canvas routing 8 | const Login = Loadable(lazy(() => import('views/login'))) 9 | const Callback = Loadable(lazy(() => import('views/callback'))) 10 | 11 | // ==============================|| CANVAS ROUTING ||============================== // 12 | 13 | const AuthRoutes = { 14 | path: '/', 15 | element: , 16 | children: [ 17 | { 18 | path: '/login', 19 | element: 20 | }, 21 | { 22 | path: '/callback', 23 | element: 24 | } 25 | ] 26 | } 27 | 28 | export default AuthRoutes -------------------------------------------------------------------------------- /src/config.js: -------------------------------------------------------------------------------- 1 | export const authConfig = { 2 | serverUrl: process.env.REACT_APP_ENDPOINT, 3 | clientId: process.env.REACT_APP_CLIENTID, 4 | organizationName: process.env.REACT_APP_ORGNAME, 5 | appName: process.env.REACT_APP_APPNAME, 6 | redirectPath: '/callback', // in accordance with casdoor configuration 7 | signinPath: '/api/v1/auth/signin' 8 | } 9 | 10 | export const config = { 11 | // basename: only at build time to set, and Don't add '/' at end off BASENAME for breadcrumbs, also Don't put only '/' use blank('') instead, 12 | basename: '', 13 | defaultPath: '/dashboard', 14 | fontFamily: `'Roboto', sans-serif`, 15 | borderRadius: 12, 16 | ON_PREMISE: JSON.parse(process.env.REACT_APP_ON_PREMISE | true), 17 | FEATURE_TOGGLE_K8S: JSON.parse(process.env.REACT_APP_FEATURE_TOGGLE_K8S | false) 18 | } 19 | 20 | export default config 21 | -------------------------------------------------------------------------------- /src/layout/MainLayout/Sidebar/MenuList/index.js: -------------------------------------------------------------------------------- 1 | // material-ui 2 | import { Typography } from '@mui/material' 3 | 4 | // project imports 5 | import NavGroup from './NavGroup' 6 | import menuItem from 'menu-items' 7 | 8 | // ==============================|| SIDEBAR MENU LIST ||============================== // 9 | 10 | const MenuList = () => { 11 | const navItems = menuItem.items.map((item) => { 12 | switch (item.type) { 13 | case 'group': 14 | return 15 | default: 16 | return ( 17 | 18 | Menu Items Error 19 | 20 | ) 21 | } 22 | }) 23 | 24 | return <>{navItems} 25 | } 26 | 27 | export default MenuList 28 | -------------------------------------------------------------------------------- /src/store/reducers/dialogReducer.js: -------------------------------------------------------------------------------- 1 | import { SHOW_CONFIRM, HIDE_CONFIRM } from '../actions' 2 | 3 | export const initialState = { 4 | show: false, 5 | title: '', 6 | description: '', 7 | confirmButtonName: 'OK', 8 | cancelButtonName: 'Cancel' 9 | } 10 | 11 | const alertReducer = (state = initialState, action) => { 12 | switch (action.type) { 13 | case SHOW_CONFIRM: 14 | return { 15 | show: true, 16 | title: action.payload.title, 17 | description: action.payload.description, 18 | confirmButtonName: action.payload.confirmButtonName, 19 | cancelButtonName: action.payload.cancelButtonName 20 | } 21 | case HIDE_CONFIRM: 22 | return initialState 23 | default: 24 | return state 25 | } 26 | } 27 | 28 | export default alertReducer 29 | -------------------------------------------------------------------------------- /src/ui-component/switch/Switch.js: -------------------------------------------------------------------------------- 1 | import { useState } from 'react' 2 | import PropTypes from 'prop-types' 3 | import { FormControl, Switch } from '@mui/material' 4 | 5 | export const SwitchInput = ({ value, onChange, disabled = false }) => { 6 | const [myValue, setMyValue] = useState(!!value ?? false) 7 | 8 | return ( 9 | <> 10 | 11 | { 15 | setMyValue(event.target.checked) 16 | onChange(event.target.checked) 17 | }} 18 | /> 19 | 20 | 21 | ) 22 | } 23 | 24 | SwitchInput.propTypes = { 25 | value: PropTypes.string, 26 | onChange: PropTypes.func, 27 | disabled: PropTypes.bool 28 | } 29 | -------------------------------------------------------------------------------- /src/store/reducers/canvasReducer.js: -------------------------------------------------------------------------------- 1 | // action - state management 2 | import * as actionTypes from '../actions' 3 | 4 | export const initialState = { 5 | isDirty: false, 6 | chatflow: null 7 | } 8 | 9 | // ==============================|| CANVAS REDUCER ||============================== // 10 | 11 | const canvasReducer = (state = initialState, action) => { 12 | switch (action.type) { 13 | case actionTypes.SET_DIRTY: 14 | return { 15 | ...state, 16 | isDirty: true 17 | } 18 | case actionTypes.REMOVE_DIRTY: 19 | return { 20 | ...state, 21 | isDirty: false 22 | } 23 | case actionTypes.SET_CHATFLOW: 24 | return { 25 | ...state, 26 | chatflow: action.chatflow 27 | } 28 | default: 29 | return state 30 | } 31 | } 32 | 33 | export default canvasReducer 34 | -------------------------------------------------------------------------------- /src/App.js: -------------------------------------------------------------------------------- 1 | import { useSelector } from 'react-redux' 2 | 3 | import { ThemeProvider } from '@mui/material/styles' 4 | import { CssBaseline, StyledEngineProvider } from '@mui/material' 5 | 6 | // routing 7 | import Routes from 'routes' 8 | 9 | // defaultTheme 10 | import themes from 'themes' 11 | 12 | // project imports 13 | import NavigationScroll from 'layout/NavigationScroll' 14 | 15 | // ==============================|| APP ||============================== // 16 | 17 | const App = () => { 18 | const customization = useSelector((state) => state.customization) 19 | 20 | return ( 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | ) 30 | } 31 | 32 | export default App 33 | -------------------------------------------------------------------------------- /src/layout/NavMotion.js: -------------------------------------------------------------------------------- 1 | import PropTypes from 'prop-types' 2 | import { motion } from 'framer-motion' 3 | 4 | // ==============================|| ANIMATION FOR CONTENT ||============================== // 5 | 6 | const NavMotion = ({ children }) => { 7 | const motionVariants = { 8 | initial: { 9 | opacity: 0, 10 | scale: 0.99 11 | }, 12 | in: { 13 | opacity: 1, 14 | scale: 1 15 | }, 16 | out: { 17 | opacity: 0, 18 | scale: 1.01 19 | } 20 | } 21 | 22 | const motionTransition = { 23 | type: 'tween', 24 | ease: 'anticipate', 25 | duration: 0.4 26 | } 27 | 28 | return ( 29 | 30 | {children} 31 | 32 | ) 33 | } 34 | 35 | NavMotion.propTypes = { 36 | children: PropTypes.node 37 | } 38 | 39 | export default NavMotion 40 | -------------------------------------------------------------------------------- /src/hooks/useConfirm.js: -------------------------------------------------------------------------------- 1 | import { useContext } from 'react' 2 | import ConfirmContext from 'store/context/ConfirmContext' 3 | import { HIDE_CONFIRM, SHOW_CONFIRM } from 'store/actions' 4 | 5 | let resolveCallback 6 | const useConfirm = () => { 7 | const [confirmState, dispatch] = useContext(ConfirmContext) 8 | 9 | const closeConfirm = () => { 10 | dispatch({ 11 | type: HIDE_CONFIRM 12 | }) 13 | } 14 | 15 | const onConfirm = () => { 16 | closeConfirm() 17 | resolveCallback(true) 18 | } 19 | 20 | const onCancel = () => { 21 | closeConfirm() 22 | resolveCallback(false) 23 | } 24 | const confirm = (confirmPayload) => { 25 | dispatch({ 26 | type: SHOW_CONFIRM, 27 | payload: confirmPayload 28 | }) 29 | return new Promise((res) => { 30 | resolveCallback = res 31 | }) 32 | } 33 | 34 | return { confirm, onConfirm, onCancel, confirmState } 35 | } 36 | 37 | export default useConfirm 38 | -------------------------------------------------------------------------------- /src/assets/images/python.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/ui-component/tooltip/TooltipWithParser.js: -------------------------------------------------------------------------------- 1 | import { Info } from '@mui/icons-material' 2 | import { IconButton, Tooltip } from '@mui/material' 3 | import parser from 'html-react-parser' 4 | import PropTypes from 'prop-types' 5 | import { useSelector } from 'react-redux' 6 | 7 | export const TooltipWithParser = ({ title, style }) => { 8 | const customization = useSelector((state) => state.customization) 9 | 10 | return ( 11 | 12 | 13 | 22 | 23 | 24 | ) 25 | } 26 | 27 | TooltipWithParser.propTypes = { 28 | title: PropTypes.node, 29 | style: PropTypes.any 30 | } 31 | -------------------------------------------------------------------------------- /src/utils/usePrompt.js: -------------------------------------------------------------------------------- 1 | import { useCallback, useContext, useEffect } from 'react' 2 | import { UNSAFE_NavigationContext as NavigationContext } from 'react-router-dom' 3 | 4 | // https://stackoverflow.com/questions/71572678/react-router-v-6-useprompt-typescript 5 | 6 | export function useBlocker(blocker, when = true) { 7 | const { navigator } = useContext(NavigationContext) 8 | 9 | useEffect(() => { 10 | if (!when) return 11 | 12 | const unblock = navigator.block((tx) => { 13 | const autoUnblockingTx = { 14 | ...tx, 15 | retry() { 16 | unblock() 17 | tx.retry() 18 | } 19 | } 20 | 21 | blocker(autoUnblockingTx) 22 | }) 23 | 24 | return unblock 25 | }, [navigator, blocker, when]) 26 | } 27 | 28 | export function usePrompt(message, when = true) { 29 | const blocker = useCallback( 30 | (tx) => { 31 | if (window.confirm(message)) tx.retry() 32 | }, 33 | [message] 34 | ) 35 | 36 | useBlocker(blocker, when) 37 | } 38 | -------------------------------------------------------------------------------- /src/ui-component/checkbox/Checkbox.js: -------------------------------------------------------------------------------- 1 | import { useState } from 'react' 2 | import PropTypes from 'prop-types' 3 | import { FormControlLabel, Checkbox } from '@mui/material' 4 | 5 | export const CheckboxInput = ({ value, label, onChange, disabled = false }) => { 6 | const [myValue, setMyValue] = useState(value) 7 | 8 | return ( 9 | <> 10 | { 18 | setMyValue(event.target.checked) 19 | onChange(event.target.checked) 20 | }} 21 | /> 22 | } 23 | label={label} 24 | /> 25 | 26 | ) 27 | } 28 | 29 | CheckboxInput.propTypes = { 30 | value: PropTypes.bool, 31 | label: PropTypes.string, 32 | onChange: PropTypes.func, 33 | disabled: PropTypes.bool 34 | } 35 | -------------------------------------------------------------------------------- /src/api/client.js: -------------------------------------------------------------------------------- 1 | import axios from 'axios' 2 | import Cookies from 'js-cookie' 3 | import { baseURL } from 'store/constant' 4 | 5 | const apiClient = axios.create({ 6 | baseURL: `${baseURL}`, 7 | headers: { 8 | 'Content-type': 'application/json' 9 | // 'Connection': 'keep-alive' 10 | } 11 | }) 12 | 13 | const logout = () => { 14 | localStorage.removeItem('idToken') 15 | window.location.reload() 16 | } 17 | 18 | apiClient.interceptors.request.use(function (config) { 19 | const IDToken = localStorage.getItem('idToken') 20 | if (IDToken) { 21 | const userInfo = JSON.parse(localStorage.getItem('userinfos')) 22 | config.headers['X-SLURM-USER-NAME'] = userInfo.name 23 | config.headers['X-SLURM-USER-TOKEN'] = IDToken 24 | config.headers['isAdmin'] = userInfo.isAdmin 25 | } 26 | return config 27 | }) 28 | 29 | apiClient.interceptors.response.use( 30 | (response) => { 31 | return response 32 | }, 33 | (error) => { 34 | if (error.response.status === 401) { 35 | logout() 36 | } 37 | return error 38 | } 39 | ) 40 | 41 | export default apiClient 42 | -------------------------------------------------------------------------------- /src/views/qosmanage/TableToolbar.js: -------------------------------------------------------------------------------- 1 | import { alpha } from '@mui/material/styles' 2 | import Toolbar from '@mui/material/Toolbar' 3 | import Typography from '@mui/material/Typography' 4 | import PropTypes from 'prop-types' 5 | import Button from '@mui/material/Button' 6 | import { Remove, Add } from '@mui/icons-material' 7 | 8 | const TableToolbar = (props) => { 9 | const { title, operation } = props 10 | 11 | return ( 12 | <> 13 | 20 | 21 | {title} 22 | 23 | 24 | 27 | 28 | 29 | ) 30 | } 31 | 32 | TableToolbar.propTypes = { 33 | title: PropTypes.string 34 | } 35 | 36 | export default TableToolbar 37 | -------------------------------------------------------------------------------- /src/ui-component/cards/Skeleton/ChatflowCard.js: -------------------------------------------------------------------------------- 1 | // material-ui 2 | import { Card, CardContent, Grid } from '@mui/material' 3 | import Skeleton from '@mui/material/Skeleton' 4 | 5 | // ==============================|| SKELETON - BRIDGE CARD ||============================== // 6 | 7 | const ChatflowCard = () => ( 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | ) 31 | 32 | export default ChatflowCard 33 | -------------------------------------------------------------------------------- /src/ui-component/grid/Grid.js: -------------------------------------------------------------------------------- 1 | import PropTypes from 'prop-types' 2 | import { DataGrid } from '@mui/x-data-grid' 3 | import { IconPlus } from '@tabler/icons' 4 | import { Button } from '@mui/material' 5 | 6 | export const Grid = ({ columns, rows, style, onRowUpdate, addNewRow }) => { 7 | const handleProcessRowUpdate = (newRow) => { 8 | onRowUpdate(newRow) 9 | return newRow 10 | } 11 | 12 | return ( 13 | <> 14 | 17 | {rows && columns && ( 18 |
19 | console.error(error)} 22 | rows={rows} 23 | columns={columns} 24 | /> 25 |
26 | )} 27 | 28 | ) 29 | } 30 | 31 | Grid.propTypes = { 32 | rows: PropTypes.array, 33 | columns: PropTypes.array, 34 | style: PropTypes.any, 35 | addNewRow: PropTypes.func, 36 | onRowUpdate: PropTypes.func 37 | } 38 | -------------------------------------------------------------------------------- /src/views/callback/index.js: -------------------------------------------------------------------------------- 1 | import { AuthCallback } from 'casdoor-react-sdk' 2 | 3 | import React, { useEffect } from 'react' 4 | import { useNavigate } from 'react-router-dom' 5 | import { authConfig, config } from '../../config' 6 | import SDK from 'casdoor-js-sdk' 7 | import { baseURL } from 'store/constant' 8 | 9 | // Hooks 10 | import useAuth from 'hooks/useAuth' 11 | 12 | export default function Callback() { 13 | const navigate = useNavigate() 14 | const { userInfo, setUserInfo, setIDToken } = useAuth() 15 | 16 | return ( 17 | { 21 | // @ts-ignore 22 | // save token 23 | setUserInfo(res.userinfos) 24 | setIDToken(res.token) 25 | navigate(config.defaultPath, { replace: true }) 26 | }} 27 | isGetTokenSuccessful={(res) => { 28 | // @ts-ignore 29 | // according to the data returned by the server, 30 | // determine whether the `token` is successfully obtained through `code` and `state`. 31 | return res.success === true 32 | }} 33 | /> 34 | ) 35 | } 36 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import App from './App' 3 | import { store } from 'store' 4 | import { createRoot } from 'react-dom/client' 5 | 6 | // style + assets 7 | import 'assets/scss/style.scss' 8 | 9 | // third party 10 | import { BrowserRouter } from 'react-router-dom' 11 | import { Provider } from 'react-redux' 12 | import { SnackbarProvider } from 'notistack' 13 | import ConfirmContextProvider from 'store/context/ConfirmContextProvider' 14 | import MetaContextProvider from 'store/context/MetaContextProvider' 15 | import { ReactFlowContext } from 'store/context/ReactFlowContext' 16 | 17 | const container = document.getElementById('root') 18 | const root = createRoot(container) 19 | 20 | root.render( 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | ) 37 | -------------------------------------------------------------------------------- /src/store/reducers/notifierReducer.js: -------------------------------------------------------------------------------- 1 | import { ENQUEUE_SNACKBAR, CLOSE_SNACKBAR, REMOVE_SNACKBAR } from '../actions' 2 | 3 | export const initialState = { 4 | notifications: [] 5 | } 6 | 7 | const notifierReducer = (state = initialState, action) => { 8 | switch (action.type) { 9 | case ENQUEUE_SNACKBAR: 10 | return { 11 | ...state, 12 | notifications: [ 13 | ...state.notifications, 14 | { 15 | key: action.key, 16 | ...action.notification 17 | } 18 | ] 19 | } 20 | 21 | case CLOSE_SNACKBAR: 22 | return { 23 | ...state, 24 | notifications: state.notifications.map((notification) => 25 | action.dismissAll || notification.key === action.key ? { ...notification, dismissed: true } : { ...notification } 26 | ) 27 | } 28 | 29 | case REMOVE_SNACKBAR: 30 | return { 31 | ...state, 32 | notifications: state.notifications.filter((notification) => notification.key !== action.key) 33 | } 34 | 35 | default: 36 | return state 37 | } 38 | } 39 | 40 | export default notifierReducer 41 | -------------------------------------------------------------------------------- /src/assets/images/embed.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/api/jobs.js: -------------------------------------------------------------------------------- 1 | import client from './client' 2 | import { slurmContext, slurmDBContext } from 'store/constant' 3 | 4 | const getHPCJobs = (params) => client.get(`/api/v1/jobs/list?${params}`) 5 | 6 | const getSpecificJob = (jobid) => client.get(slurmContext + `/job/${jobid}`) 7 | 8 | const getHPCUser = (username) => client.get(slurmDBContext + `/user/${username}?with_assocs`) 9 | 10 | const submitJob = (dataBody) => client.post(slurmContext + `/job/submit`, dataBody) 11 | 12 | const stopJob = (jobid) => client.delete(slurmContext + `/job/${jobid}`) 13 | 14 | const getJobOutput = (jobid, filepath) => client.get(`/api/v1/hpc/job_output/${jobid}?filepath=${filepath}`) 15 | 16 | const getHPCJobFileList = (outputDIR) => client.get(`/api/v1/hpc/filelist?output_dir=${outputDIR}`) 17 | 18 | const getK8SPods = (namespace) => client.get(`/api/v1/k8s/workload/pods/?namespace=${namespace}`) 19 | 20 | const getK8SDeploy = (namespace) => client.get(`/api/v1/k8s/workload/deployments/?namespace=${namespace}`) 21 | 22 | const getK8SSvs = (namespace) => client.get(`/api/v1/k8s/workload/services/?namespace=${namespace}`) 23 | 24 | const getK8SNs = () => client.get(`/api/v1/k8s/workload/namespaces`) 25 | 26 | export default { 27 | getHPCJobs, 28 | getK8SPods, 29 | getK8SDeploy, 30 | getK8SSvs, 31 | getK8SNs, 32 | getSpecificJob, 33 | submitJob, 34 | stopJob, 35 | getJobOutput, 36 | getHPCUser, 37 | getHPCJobFileList 38 | } 39 | -------------------------------------------------------------------------------- /src/api/useraccount.js: -------------------------------------------------------------------------------- 1 | import client from './client' 2 | import { slurmDBContext, slurmContext } from 'store/constant' 3 | 4 | const getAccounts = (params) => client.get(slurmDBContext + `/accounts?${params}`) 5 | const addAccounts = (data) => client.post(slurmDBContext + '/accounts', data) 6 | const deleteAccount = (account) => client.delete(slurmDBContext + `/account/${account}`) 7 | const setAdmin = (data) => client.post('/api/v1/users/set_admins', data) 8 | const getUsers = () => client.get(slurmDBContext + '/users') 9 | const updateUsers = (data) => client.post(slurmDBContext + '/users', data) 10 | const getQOS = () => client.get(slurmDBContext + '/qos') 11 | const addQOS = (data) => client.post(slurmDBContext + '/qos', data) 12 | const removeQOS = (name) => client.delete(slurmDBContext + `/qos/${name}`) 13 | const getPartitions = () => client.get(slurmContext + '/partitions') 14 | const getAssociation = (params) => client.get(slurmDBContext + '/associations?' + params) 15 | const addAssociations = (data) => client.post(slurmDBContext + '/associations', data) 16 | const removeAssociations = (params) => client.delete(slurmDBContext + '/associations?' + params) 17 | 18 | export default { 19 | getAccounts, 20 | addAccounts, 21 | deleteAccount, 22 | setAdmin, 23 | getUsers, 24 | updateUsers, 25 | getQOS, 26 | addQOS, 27 | removeQOS, 28 | getPartitions, 29 | getAssociation, 30 | addAssociations, 31 | removeAssociations 32 | } 33 | -------------------------------------------------------------------------------- /src/ui-component/table/Table.js: -------------------------------------------------------------------------------- 1 | import PropTypes from 'prop-types' 2 | import { TableContainer, Table, TableHead, TableCell, TableRow, TableBody, Paper } from '@mui/material' 3 | 4 | export const TableViewOnly = ({ columns, rows }) => { 5 | return ( 6 | <> 7 | 8 | 9 | 10 | 11 | {columns.map((col, index) => ( 12 | {col.charAt(0).toUpperCase() + col.slice(1)} 13 | ))} 14 | 15 | 16 | 17 | {rows.map((row, index) => ( 18 | 19 | {Object.keys(row).map((key, index) => ( 20 | {row[key]} 21 | ))} 22 | 23 | ))} 24 | 25 |
26 |
27 | 28 | ) 29 | } 30 | 31 | TableViewOnly.propTypes = { 32 | rows: PropTypes.array, 33 | columns: PropTypes.array 34 | } 35 | -------------------------------------------------------------------------------- /src/ui-component/dialog/ConfirmDialog.js: -------------------------------------------------------------------------------- 1 | import { createPortal } from 'react-dom' 2 | import { Button, Dialog, DialogActions, DialogContent, DialogTitle } from '@mui/material' 3 | import useConfirm from 'hooks/useConfirm' 4 | import { StyledButton } from 'ui-component/button/StyledButton' 5 | 6 | const ConfirmDialog = () => { 7 | const { onConfirm, onCancel, confirmState } = useConfirm() 8 | const portalElement = document.getElementById('portal') 9 | 10 | const component = confirmState.show ? ( 11 | 19 | 20 | {confirmState.title} 21 | 22 | 23 | {confirmState.description} 24 | 25 | 26 | 27 | 28 | {confirmState.confirmButtonName} 29 | 30 | 31 | 32 | ) : null 33 | 34 | return createPortal(component, portalElement) 35 | } 36 | 37 | export default ConfirmDialog 38 | -------------------------------------------------------------------------------- /src/ui-component/editor/DarkCodeEditor.js: -------------------------------------------------------------------------------- 1 | import Editor from 'react-simple-code-editor' 2 | import { highlight, languages } from 'prismjs/components/prism-core' 3 | import 'prismjs/components/prism-clike' 4 | import 'prismjs/components/prism-javascript' 5 | import 'prismjs/components/prism-json' 6 | import 'prismjs/components/prism-markup' 7 | import './prism-dark.css' 8 | import PropTypes from 'prop-types' 9 | import { useTheme } from '@mui/material/styles' 10 | 11 | export const DarkCodeEditor = ({ value, placeholder, disabled = false, type, style, onValueChange, onMouseUp, onBlur }) => { 12 | const theme = useTheme() 13 | 14 | return ( 15 | highlight(code, type === 'json' ? languages.json : languages.js)} 20 | padding={10} 21 | onValueChange={onValueChange} 22 | onMouseUp={onMouseUp} 23 | onBlur={onBlur} 24 | tabSize={4} 25 | style={{ 26 | ...style, 27 | background: theme.palette.codeEditor.main 28 | }} 29 | textareaClassName='editor__textarea' 30 | /> 31 | ) 32 | } 33 | 34 | DarkCodeEditor.propTypes = { 35 | value: PropTypes.string, 36 | placeholder: PropTypes.string, 37 | disabled: PropTypes.bool, 38 | type: PropTypes.string, 39 | style: PropTypes.object, 40 | onValueChange: PropTypes.func, 41 | onMouseUp: PropTypes.func, 42 | onBlur: PropTypes.func 43 | } 44 | -------------------------------------------------------------------------------- /src/ui-component/editor/LightCodeEditor.js: -------------------------------------------------------------------------------- 1 | import Editor from 'react-simple-code-editor' 2 | import { highlight, languages } from 'prismjs/components/prism-core' 3 | import 'prismjs/components/prism-clike' 4 | import 'prismjs/components/prism-javascript' 5 | import 'prismjs/components/prism-json' 6 | import 'prismjs/components/prism-markup' 7 | import './prism-light.css' 8 | import PropTypes from 'prop-types' 9 | import { useTheme } from '@mui/material/styles' 10 | 11 | export const LightCodeEditor = ({ value, placeholder, disabled = false, type, style, onValueChange, onMouseUp, onBlur }) => { 12 | const theme = useTheme() 13 | 14 | return ( 15 | highlight(code, type === 'json' ? languages.json : languages.js)} 20 | padding={10} 21 | onValueChange={onValueChange} 22 | onMouseUp={onMouseUp} 23 | onBlur={onBlur} 24 | tabSize={4} 25 | style={{ 26 | ...style, 27 | background: theme.palette.card.main 28 | }} 29 | textareaClassName='editor__textarea' 30 | /> 31 | ) 32 | } 33 | 34 | LightCodeEditor.propTypes = { 35 | value: PropTypes.string, 36 | placeholder: PropTypes.string, 37 | disabled: PropTypes.bool, 38 | type: PropTypes.string, 39 | style: PropTypes.object, 40 | onValueChange: PropTypes.func, 41 | onMouseUp: PropTypes.func, 42 | onBlur: PropTypes.func 43 | } 44 | -------------------------------------------------------------------------------- /src/hooks/useAuth.js: -------------------------------------------------------------------------------- 1 | import { useState } from 'react' 2 | import Cookies from 'js-cookie' 3 | 4 | export default function useAuth() { 5 | let parentDomain = window.location.hostname.substring(window.location.hostname.indexOf('.')) 6 | 7 | const getUserInfo = () => { 8 | const userInfoString = localStorage.getItem('userinfos') 9 | if (userInfoString) { 10 | return JSON.parse(userInfoString) 11 | } 12 | 13 | return {} 14 | } 15 | 16 | const [userInfo, setUserInfo] = useState(getUserInfo()) 17 | 18 | const saveUserInfo = (userInfo) => { 19 | delete userInfo['token'] 20 | localStorage.setItem('userinfos', JSON.stringify(userInfo)) 21 | setUserInfo(userInfo) 22 | } 23 | 24 | const getIDToken = () => { 25 | const idToken = localStorage.getItem('idToken') 26 | return idToken 27 | } 28 | 29 | const [IDToken, setIDToken] = useState(getIDToken()) 30 | 31 | const saveIDToken = (idToken) => { 32 | localStorage.setItem('idToken', idToken) 33 | setIDToken(idToken) 34 | } 35 | 36 | const removeDToken = () => { 37 | localStorage.removeItem('idToken') 38 | setIDToken(null) 39 | } 40 | 41 | const saveAuthToken = (authToken) => { 42 | setIDToken(authToken) 43 | } 44 | 45 | return { 46 | setUserInfo: saveUserInfo, 47 | setIDToken: saveIDToken, 48 | removeDToken: removeDToken, 49 | setAuthToken: saveAuthToken, 50 | getIDToken: getIDToken, 51 | userInfo, 52 | IDToken 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/store/actions.js: -------------------------------------------------------------------------------- 1 | // action - customization reducer 2 | export const SET_MENU = '@customization/SET_MENU' 3 | export const MENU_TOGGLE = '@customization/MENU_TOGGLE' 4 | export const MENU_OPEN = '@customization/MENU_OPEN' 5 | export const SET_FONT_FAMILY = '@customization/SET_FONT_FAMILY' 6 | export const SET_BORDER_RADIUS = '@customization/SET_BORDER_RADIUS' 7 | export const SET_LAYOUT = '@customization/SET_LAYOUT ' 8 | export const SET_DARKMODE = '@customization/SET_DARKMODE' 9 | 10 | // action - canvas reducer 11 | export const SET_DIRTY = '@canvas/SET_DIRTY' 12 | export const REMOVE_DIRTY = '@canvas/REMOVE_DIRTY' 13 | export const SET_CHATFLOW = '@canvas/SET_CHATFLOW' 14 | 15 | // action - notifier reducer 16 | export const ENQUEUE_SNACKBAR = 'ENQUEUE_SNACKBAR' 17 | export const CLOSE_SNACKBAR = 'CLOSE_SNACKBAR' 18 | export const REMOVE_SNACKBAR = 'REMOVE_SNACKBAR' 19 | 20 | // action - dialog reducer 21 | export const SHOW_CONFIRM = 'SHOW_CONFIRM' 22 | export const HIDE_CONFIRM = 'HIDE_CONFIRM' 23 | 24 | // action - meta reducer 25 | export const SET_TAGS = 'SET_TAGS' 26 | 27 | export const enqueueSnackbar = (notification) => { 28 | const key = notification.options && notification.options.key 29 | 30 | return { 31 | type: ENQUEUE_SNACKBAR, 32 | notification: { 33 | ...notification, 34 | key: key || new Date().getTime() + Math.random() 35 | } 36 | } 37 | } 38 | 39 | export const closeSnackbar = (key) => ({ 40 | type: CLOSE_SNACKBAR, 41 | dismissAll: !key, // dismiss all if no key has been defined 42 | key 43 | }) 44 | 45 | export const removeSnackbar = (key) => ({ 46 | type: REMOVE_SNACKBAR, 47 | key 48 | }) 49 | -------------------------------------------------------------------------------- /src/store/reducers/customizationReducer.js: -------------------------------------------------------------------------------- 1 | // project imports 2 | import config from 'config' 3 | 4 | // action - state management 5 | import * as actionTypes from '../actions' 6 | 7 | export const initialState = { 8 | isOpen: [], // for active default menu 9 | fontFamily: config.fontFamily, 10 | borderRadius: config.borderRadius, 11 | opened: true, 12 | isHorizontal: localStorage.getItem('isHorizontal') === 'true' ? true : false, 13 | isDarkMode: localStorage.getItem('isDarkMode') === 'true' ? true : false 14 | } 15 | 16 | // ==============================|| CUSTOMIZATION REDUCER ||============================== // 17 | 18 | const customizationReducer = (state = initialState, action) => { 19 | let id 20 | switch (action.type) { 21 | case actionTypes.MENU_OPEN: 22 | id = action.id 23 | return { 24 | ...state, 25 | isOpen: [id] 26 | } 27 | case actionTypes.SET_MENU: 28 | return { 29 | ...state, 30 | opened: action.opened 31 | } 32 | case actionTypes.SET_FONT_FAMILY: 33 | return { 34 | ...state, 35 | fontFamily: action.fontFamily 36 | } 37 | case actionTypes.SET_BORDER_RADIUS: 38 | return { 39 | ...state, 40 | borderRadius: action.borderRadius 41 | } 42 | case actionTypes.SET_LAYOUT: 43 | return { 44 | ...state, 45 | isHorizontal: action.isHorizontal 46 | } 47 | case actionTypes.SET_DARKMODE: 48 | return { 49 | ...state, 50 | isDarkMode: action.isDarkMode 51 | } 52 | default: 53 | return state 54 | } 55 | } 56 | 57 | export default customizationReducer 58 | -------------------------------------------------------------------------------- /src/utils/useNotifier.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { useDispatch, useSelector } from 'react-redux' 3 | import { useSnackbar } from 'notistack' 4 | import { removeSnackbar } from 'store/actions' 5 | 6 | let displayed = [] 7 | 8 | const useNotifier = () => { 9 | const dispatch = useDispatch() 10 | const notifier = useSelector((state) => state.notifier) 11 | const { notifications } = notifier 12 | 13 | const { enqueueSnackbar, closeSnackbar } = useSnackbar() 14 | 15 | const storeDisplayed = (id) => { 16 | displayed = [...displayed, id] 17 | } 18 | 19 | const removeDisplayed = (id) => { 20 | displayed = [...displayed.filter((key) => id !== key)] 21 | } 22 | 23 | React.useEffect(() => { 24 | notifications.forEach(({ key, message, options = {}, dismissed = false }) => { 25 | if (dismissed) { 26 | // dismiss snackbar using notistack 27 | closeSnackbar(key) 28 | return 29 | } 30 | 31 | // do nothing if snackbar is already displayed 32 | if (displayed.includes(key)) return 33 | 34 | // display snackbar using notistack 35 | enqueueSnackbar(message, { 36 | key, 37 | ...options, 38 | onClose: (event, reason, myKey) => { 39 | if (options.onClose) { 40 | options.onClose(event, reason, myKey) 41 | } 42 | }, 43 | onExited: (event, myKey) => { 44 | // remove this snackbar from redux store 45 | dispatch(removeSnackbar(myKey)) 46 | removeDisplayed(myKey) 47 | } 48 | }) 49 | 50 | // keep track of snackbars that we've displayed 51 | storeDisplayed(key) 52 | }) 53 | }, [notifications, closeSnackbar, enqueueSnackbar, dispatch]) 54 | } 55 | 56 | export default useNotifier 57 | -------------------------------------------------------------------------------- /src/ui-component/dialog/SourceDocDialog.js: -------------------------------------------------------------------------------- 1 | import { createPortal } from 'react-dom' 2 | import { useState, useEffect } from 'react' 3 | import { useSelector } from 'react-redux' 4 | import PropTypes from 'prop-types' 5 | import { Dialog, DialogContent, DialogTitle } from '@mui/material' 6 | import ReactJson from 'react-json-view' 7 | 8 | const SourceDocDialog = ({ show, dialogProps, onCancel }) => { 9 | const portalElement = document.getElementById('portal') 10 | const customization = useSelector((state) => state.customization) 11 | 12 | const [data, setData] = useState({}) 13 | 14 | useEffect(() => { 15 | if (dialogProps.data) setData(dialogProps.data) 16 | 17 | return () => { 18 | setData({}) 19 | } 20 | }, [dialogProps]) 21 | 22 | const component = show ? ( 23 | 31 | 32 | Source Document 33 | 34 | 35 | 44 | 45 | 46 | ) : null 47 | 48 | return createPortal(component, portalElement) 49 | } 50 | 51 | SourceDocDialog.propTypes = { 52 | show: PropTypes.bool, 53 | dialogProps: PropTypes.object, 54 | onCancel: PropTypes.func 55 | } 56 | 57 | export default SourceDocDialog 58 | -------------------------------------------------------------------------------- /src/ui-component/menu/StyledMenu.js: -------------------------------------------------------------------------------- 1 | import { useState } from 'react' 2 | import * as React from 'react' 3 | import PropTypes from 'prop-types' 4 | import { IconButton, Menu, MenuItem } from '@mui/material' 5 | import MoreVertIcon from '@mui/icons-material/MoreVert' 6 | 7 | export const StyledMenu = ({ operation, menuAction, row }) => { 8 | const [anchorEl, setAnchorEl] = React.useState(null) 9 | const open = Boolean(anchorEl) 10 | const handleClick = (event) => { 11 | event.stopPropagation() 12 | setAnchorEl(event.currentTarget) 13 | } 14 | const handleClose = (event, actionId) => { 15 | event.stopPropagation() 16 | setAnchorEl(null) 17 | operation(actionId, row) 18 | } 19 | 20 | return ( 21 | <> 22 | 30 | 31 | 32 | 47 | {menuAction.map((row) => { 48 | return ( 49 | handleClose(event, row.key)}> 50 | {row.label} 51 | 52 | ) 53 | })} 54 | 55 | 56 | ) 57 | } 58 | 59 | StyledMenu.propTypes = { 60 | operation: PropTypes.func, 61 | menuAction: PropTypes.array 62 | } 63 | -------------------------------------------------------------------------------- /src/views/usermanage/TableToolbar.js: -------------------------------------------------------------------------------- 1 | import { alpha } from '@mui/material/styles' 2 | import Toolbar from '@mui/material/Toolbar' 3 | import Typography from '@mui/material/Typography' 4 | import PropTypes from 'prop-types' 5 | import Button from '@mui/material/Button' 6 | import SearchIcon from '@mui/icons-material/Search' 7 | import { IconButton, InputBase, Paper } from '@mui/material' 8 | 9 | const TableToolbar = (props) => { 10 | const { title, setSearchKeyword } = props 11 | 12 | return ( 13 | <> 14 | 21 | 22 | {title} 23 | 24 | 25 | 36 | { 41 | setSearchKeyword(event.target.value) 42 | }} 43 | /> 44 | 45 | 46 | 47 | 48 | 49 | 50 | ) 51 | } 52 | 53 | TableToolbar.propTypes = { 54 | title: PropTypes.string, 55 | setSearchKeyword: PropTypes.func 56 | } 57 | 58 | export default TableToolbar 59 | -------------------------------------------------------------------------------- /src/routes/MainRoutes.js: -------------------------------------------------------------------------------- 1 | import { lazy } from 'react' 2 | import { Navigate } from 'react-router-dom' 3 | 4 | // project imports 5 | import Loadable from 'ui-component/loading/Loadable' 6 | 7 | const MainLayout = Loadable(lazy(() => import('layout/MainLayout'))) 8 | const Dashboard = Loadable(lazy(() => import('views/dashboard'))) 9 | const Resources = Loadable(lazy(() => import('views/resources'))) 10 | 11 | const HPCjobs = Loadable(lazy(() => import('views/hpcjobs'))) 12 | 13 | const K8sjobs = Loadable(lazy(() => import('views/k8sjobs'))) 14 | 15 | // apikey routing 16 | const JobQueue = Loadable(lazy(() => import('views/jobqueue'))) 17 | 18 | const OrgManage = Loadable(lazy(() => import('views/orgmanage'))) 19 | 20 | const UserManage = Loadable(lazy(() => import('views/usermanage'))) 21 | 22 | const QOSManage = Loadable(lazy(() => import('views/qosmanage'))) 23 | 24 | // ==============================|| MAIN ROUTING ||============================== // 25 | 26 | const MainRoutes = (isLoggedIn) => { 27 | return { 28 | path: '/', 29 | element: isLoggedIn ? : , 30 | children: [ 31 | { 32 | path: '/', 33 | element: 34 | }, 35 | { 36 | path: '/dashboard', 37 | element: 38 | }, 39 | { 40 | path: '/resources', 41 | element: 42 | }, 43 | { 44 | path: '/hpcjobs', 45 | element: 46 | }, 47 | { 48 | path: '/k8sjobs', 49 | element: 50 | }, 51 | { 52 | path: '/jobqueue', 53 | element: 54 | }, 55 | { 56 | path: '/orgmanage', 57 | element: 58 | }, 59 | { 60 | path: '/usermanage', 61 | element: 62 | }, 63 | { 64 | path: '/qosmanage', 65 | element: 66 | } 67 | ] 68 | } 69 | } 70 | 71 | export default MainRoutes 72 | -------------------------------------------------------------------------------- /src/ui-component/extended/Avatar.js: -------------------------------------------------------------------------------- 1 | import PropTypes from 'prop-types' 2 | 3 | // material-ui 4 | import { useTheme } from '@mui/material/styles' 5 | import MuiAvatar from '@mui/material/Avatar' 6 | 7 | // ==============================|| AVATAR ||============================== // 8 | 9 | const Avatar = ({ color, outline, size, sx, ...others }) => { 10 | const theme = useTheme() 11 | 12 | const colorSX = color && !outline && { color: theme.palette.background.paper, bgcolor: `${color}.main` } 13 | const outlineSX = outline && { 14 | color: color ? `${color}.main` : `primary.main`, 15 | bgcolor: theme.palette.background.paper, 16 | border: '2px solid', 17 | borderColor: color ? `${color}.main` : `primary.main` 18 | } 19 | let sizeSX = {} 20 | switch (size) { 21 | case 'badge': 22 | sizeSX = { 23 | width: theme.spacing(3.5), 24 | height: theme.spacing(3.5) 25 | } 26 | break 27 | case 'xs': 28 | sizeSX = { 29 | width: theme.spacing(4.25), 30 | height: theme.spacing(4.25) 31 | } 32 | break 33 | case 'sm': 34 | sizeSX = { 35 | width: theme.spacing(5), 36 | height: theme.spacing(5) 37 | } 38 | break 39 | case 'lg': 40 | sizeSX = { 41 | width: theme.spacing(9), 42 | height: theme.spacing(9) 43 | } 44 | break 45 | case 'xl': 46 | sizeSX = { 47 | width: theme.spacing(10.25), 48 | height: theme.spacing(10.25) 49 | } 50 | break 51 | case 'md': 52 | sizeSX = { 53 | width: theme.spacing(7.5), 54 | height: theme.spacing(7.5) 55 | } 56 | break 57 | default: 58 | sizeSX = {} 59 | } 60 | 61 | return 62 | } 63 | 64 | Avatar.propTypes = { 65 | className: PropTypes.string, 66 | color: PropTypes.string, 67 | outline: PropTypes.bool, 68 | size: PropTypes.string, 69 | sx: PropTypes.object 70 | } 71 | 72 | export default Avatar 73 | -------------------------------------------------------------------------------- /src/views/orgmanage/enhancedtabletoolbar.js: -------------------------------------------------------------------------------- 1 | import { alpha } from '@mui/material/styles' 2 | import Toolbar from '@mui/material/Toolbar' 3 | import Typography from '@mui/material/Typography' 4 | import PropTypes from 'prop-types' 5 | import Button from '@mui/material/Button' 6 | import { Remove, Add } from '@mui/icons-material' 7 | 8 | const EnhancedTableToolbar = (props) => { 9 | const { numSelected, rowsNumber, currentLimit, operation, title, isOrgFocused } = props 10 | 11 | return ( 12 | <> 13 | 0 && { bgcolor: (theme) => alpha(theme.palette.primary.main, theme.palette.action.activatedOpacity) }) 19 | }} 20 | > 21 | 22 | {title} 23 | 24 | 25 | {!isOrgFocused && ( 26 | <> 27 | 32 | 35 | 38 | 39 | )} 40 | 41 | 42 | ) 43 | } 44 | 45 | EnhancedTableToolbar.propTypes = { 46 | numSelected: PropTypes.number, 47 | rowsNumber: PropTypes.number, 48 | rows_operation: PropTypes.func, 49 | title: PropTypes.string, 50 | isOrgFocused: PropTypes.bool 51 | } 52 | 53 | export default EnhancedTableToolbar 54 | -------------------------------------------------------------------------------- /src/ui-component/dialog/AdditionalParamsDialog.js: -------------------------------------------------------------------------------- 1 | import { createPortal } from 'react-dom' 2 | import { useState, useEffect } from 'react' 3 | import PropTypes from 'prop-types' 4 | import { Dialog, DialogContent } from '@mui/material' 5 | import PerfectScrollbar from 'react-perfect-scrollbar' 6 | import NodeInputHandler from 'views/canvas/NodeInputHandler' 7 | 8 | const AdditionalParamsDialog = ({ show, dialogProps, onCancel }) => { 9 | const portalElement = document.getElementById('portal') 10 | 11 | const [inputParams, setInputParams] = useState([]) 12 | const [data, setData] = useState({}) 13 | 14 | useEffect(() => { 15 | if (dialogProps.inputParams) setInputParams(dialogProps.inputParams) 16 | if (dialogProps.data) setData(dialogProps.data) 17 | 18 | return () => { 19 | setInputParams([]) 20 | setData({}) 21 | } 22 | }, [dialogProps]) 23 | 24 | const component = show ? ( 25 | 33 | 34 | 41 | {inputParams.map((inputParam, index) => ( 42 | 49 | ))} 50 | 51 | 52 | 53 | ) : null 54 | 55 | return createPortal(component, portalElement) 56 | } 57 | 58 | AdditionalParamsDialog.propTypes = { 59 | show: PropTypes.bool, 60 | dialogProps: PropTypes.object, 61 | onCancel: PropTypes.func 62 | } 63 | 64 | export default AdditionalParamsDialog 65 | -------------------------------------------------------------------------------- /src/themes/index.js: -------------------------------------------------------------------------------- 1 | import { createTheme } from '@mui/material/styles' 2 | 3 | // assets 4 | import colors from 'assets/scss/_themes-vars.module.scss' 5 | 6 | // project imports 7 | import componentStyleOverrides from './compStyleOverride' 8 | import themePalette from './palette' 9 | import themeTypography from './typography' 10 | 11 | /** 12 | * Represent theme style and structure as per Material-UI 13 | * @param {JsonObject} customization customization parameter object 14 | */ 15 | 16 | export const theme = (customization) => { 17 | const color = colors 18 | 19 | const themeOption = customization.isDarkMode 20 | ? { 21 | colors: color, 22 | heading: color.paper, 23 | paper: color.darkPrimaryLight, 24 | backgroundDefault: color.darkPaper, 25 | background: color.darkPrimaryLight, 26 | darkTextPrimary: color.paper, 27 | darkTextSecondary: color.paper, 28 | textDark: color.paper, 29 | menuSelected: color.darkSecondaryDark, 30 | menuSelectedBack: color.darkSecondaryLight, 31 | divider: color.darkPaper, 32 | customization 33 | } 34 | : { 35 | colors: color, 36 | heading: color.grey900, 37 | paper: color.paper, 38 | backgroundDefault: color.paper, 39 | background: color.primaryLight, 40 | darkTextPrimary: color.grey700, 41 | darkTextSecondary: color.grey500, 42 | textDark: color.grey900, 43 | menuSelected: color.secondaryIconText, 44 | menuSelectedBack: color.secondaryIcon, 45 | divider: color.grey200, 46 | customization 47 | } 48 | 49 | const themeOptions = { 50 | direction: 'ltr', 51 | palette: themePalette(themeOption), 52 | mixins: { 53 | toolbar: { 54 | minHeight: '48px', 55 | padding: '16px', 56 | '@media (min-width: 600px)': { 57 | minHeight: '48px' 58 | } 59 | } 60 | }, 61 | typography: themeTypography(themeOption) 62 | } 63 | 64 | const themes = createTheme(themeOptions) 65 | themes.components = componentStyleOverrides(themeOption) 66 | 67 | return themes 68 | } 69 | 70 | export default theme 71 | -------------------------------------------------------------------------------- /src/layout/MainLayout/Sidebar/MenuList/NavGroup/index.js: -------------------------------------------------------------------------------- 1 | import PropTypes from 'prop-types' 2 | 3 | // material-ui 4 | import { useTheme } from '@mui/material/styles' 5 | import { Divider, List, Typography } from '@mui/material' 6 | 7 | // project imports 8 | import NavItem from '../NavItem' 9 | import NavCollapse from '../NavCollapse' 10 | 11 | // ==============================|| SIDEBAR MENU LIST GROUP ||============================== // 12 | 13 | const NavGroup = ({ item }) => { 14 | const theme = useTheme() 15 | 16 | // menu list collapse & items 17 | const items = item.children 18 | ?.filter((item) => { 19 | if (Object.hasOwn(item, 'hide') && item.hide) { 20 | return false 21 | } 22 | return true 23 | }) 24 | .map((menu) => { 25 | switch (menu.type) { 26 | case 'collapse': 27 | return 28 | case 'item': 29 | return 30 | default: 31 | return ( 32 | 33 | Menu Items Error 34 | 35 | ) 36 | } 37 | }) 38 | 39 | return ( 40 | <> 41 | 45 | {item.title} 46 | {item.caption && ( 47 | 48 | {item.caption} 49 | 50 | )} 51 | 52 | ) 53 | } 54 | > 55 | {items} 56 | 57 | 58 | {/* group divider */} 59 | 60 | 61 | ) 62 | } 63 | 64 | NavGroup.propTypes = { 65 | item: PropTypes.object 66 | } 67 | 68 | export default NavGroup 69 | -------------------------------------------------------------------------------- /src/ui-component/cards/MainCard.js: -------------------------------------------------------------------------------- 1 | import PropTypes from 'prop-types' 2 | import { forwardRef } from 'react' 3 | 4 | // material-ui 5 | import { useTheme } from '@mui/material/styles' 6 | import { Card, CardContent, CardHeader, Divider, Typography } from '@mui/material' 7 | 8 | // constant 9 | const headerSX = { 10 | '& .MuiCardHeader-action': { mr: 0 } 11 | } 12 | 13 | // ==============================|| CUSTOM MAIN CARD ||============================== // 14 | 15 | const MainCard = forwardRef(function MainCard( 16 | { 17 | border = true, 18 | boxShadow, 19 | children, 20 | content = true, 21 | contentClass = '', 22 | contentSX = {}, 23 | darkTitle, 24 | secondary, 25 | shadow, 26 | sx = {}, 27 | title, 28 | ...others 29 | }, 30 | ref 31 | ) { 32 | const theme = useTheme() 33 | 34 | return ( 35 | 47 | {/* card header and action */} 48 | {!darkTitle && title && } 49 | {darkTitle && title && {title}} action={secondary} />} 50 | 51 | {/* content & header divider */} 52 | {title && } 53 | 54 | {/* card content */} 55 | {content && ( 56 | 57 | {children} 58 | 59 | )} 60 | {!content && children} 61 | 62 | ) 63 | }) 64 | 65 | MainCard.propTypes = { 66 | border: PropTypes.bool, 67 | boxShadow: PropTypes.bool, 68 | children: PropTypes.node, 69 | content: PropTypes.bool, 70 | contentClass: PropTypes.string, 71 | contentSX: PropTypes.object, 72 | darkTitle: PropTypes.bool, 73 | secondary: PropTypes.oneOfType([PropTypes.node, PropTypes.string, PropTypes.object]), 74 | shadow: PropTypes.string, 75 | sx: PropTypes.object, 76 | title: PropTypes.oneOfType([PropTypes.node, PropTypes.string, PropTypes.object]) 77 | } 78 | 79 | export default MainCard 80 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "llmscheduler-ui", 3 | "version": "1.2.12", 4 | "license": "SEE LICENSE IN LICENSE.md", 5 | "homepage": "https://www.opencsg.com", 6 | "dependencies": { 7 | "@ant-design/plots": "^1.0.9", 8 | "@emotion/cache": "^11.4.0", 9 | "@emotion/react": "^11.10.6", 10 | "@emotion/styled": "^11.10.6", 11 | "@mui/icons-material": "^5.0.3", 12 | "@mui/lab": "^5.0.0-alpha.152", 13 | "@mui/material": "^5.11.12", 14 | "@mui/x-data-grid": "^6.8.0", 15 | "@mui/x-tree-view": "^6.17.0", 16 | "@tabler/icons": "^1.39.1", 17 | "axios": "^1.6.0", 18 | "casdoor-js-sdk": "^0.11.0", 19 | "casdoor-react-sdk": "^1.2.0", 20 | "history": "^5.0.0", 21 | "html-react-parser": "^3.0.4", 22 | "js-cookie": "^3.0.5", 23 | "jwt-decode": "^3.1.2", 24 | "lodash": "^4.17.21", 25 | "moment": "^2.29.3", 26 | "notistack": "^2.0.4", 27 | "prismjs": "^1.28.0", 28 | "prop-types": "^15.7.2", 29 | "react": "^18.2.0", 30 | "react-code-blocks": "^0.0.9-0", 31 | "react-device-detect": "^1.17.0", 32 | "react-dom": "^18.2.0", 33 | "react-json-view": "^1.21.3", 34 | "react-markdown": "^9.0.0", 35 | "react-perfect-scrollbar": "^1.5.8", 36 | "react-redux": "^8.0.5", 37 | "react-router": "~6.3.0", 38 | "react-router-dom": "~6.3.0", 39 | "react-simple-code-editor": "^0.11.2", 40 | "react-syntax-highlighter": "^15.5.0", 41 | "redux": "^4.0.5", 42 | "socket.io-client": "^4.6.1", 43 | "yup": "^0.32.9" 44 | }, 45 | "peerDependencies": { 46 | "react": ">=16.8.4", 47 | "react-dom": ">=16.8.4" 48 | }, 49 | "scripts": { 50 | "start": "craco start", 51 | "dev": "craco start", 52 | "build": "craco build", 53 | "test": "craco test", 54 | "eject": "craco eject" 55 | }, 56 | "babel": { 57 | "presets": [ 58 | "@babel/preset-react" 59 | ] 60 | }, 61 | "browserslist": { 62 | "production": [ 63 | ">0.2%", 64 | "not dead", 65 | "not op_mini all" 66 | ], 67 | "development": [ 68 | "last 1 chrome version", 69 | "last 1 firefox version", 70 | "last 1 safari version" 71 | ] 72 | }, 73 | "devDependencies": { 74 | "@babel/eslint-parser": "^7.15.8", 75 | "@babel/plugin-proposal-private-property-in-object": "^7.21.11", 76 | "@craco/craco": "^7.1.0", 77 | "@testing-library/jest-dom": "^5.11.10", 78 | "@testing-library/react": "^14.0.0", 79 | "@testing-library/user-event": "^12.8.3", 80 | "pretty-quick": "^3.1.3", 81 | "react-scripts": "^5.0.1", 82 | "sass": "^1.42.1", 83 | "typescript": "^4.8.4" 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /src/ui-component/json/JsonEditor.js: -------------------------------------------------------------------------------- 1 | import { useState } from 'react' 2 | import PropTypes from 'prop-types' 3 | import { FormControl } from '@mui/material' 4 | import ReactJson from 'react-json-view' 5 | 6 | export const JsonEditorInput = ({ value, onChange, disabled = false, isDarkMode = false }) => { 7 | const [myValue, setMyValue] = useState(value ? JSON.parse(value) : {}) 8 | 9 | const onClipboardCopy = (e) => { 10 | const src = e.src 11 | if (Array.isArray(src) || typeof src === 'object') { 12 | navigator.clipboard.writeText(JSON.stringify(src, null, ' ')) 13 | } else { 14 | navigator.clipboard.writeText(src) 15 | } 16 | } 17 | 18 | return ( 19 | <> 20 | 21 | {disabled && ( 22 | onClipboardCopy(e)} 28 | quotesOnKeys={false} 29 | displayDataTypes={false} 30 | /> 31 | )} 32 | {!disabled && ( 33 | onClipboardCopy(e)} 41 | onEdit={(edit) => { 42 | setMyValue(edit.updated_src) 43 | onChange(JSON.stringify(edit.updated_src)) 44 | }} 45 | onAdd={() => { 46 | //console.log(add) 47 | }} 48 | onDelete={(deleteobj) => { 49 | setMyValue(deleteobj.updated_src) 50 | onChange(JSON.stringify(deleteobj.updated_src)) 51 | }} 52 | /> 53 | )} 54 | 55 | 56 | ) 57 | } 58 | 59 | JsonEditorInput.propTypes = { 60 | value: PropTypes.string, 61 | onChange: PropTypes.func, 62 | disabled: PropTypes.bool, 63 | isDarkMode: PropTypes.bool 64 | } 65 | -------------------------------------------------------------------------------- /src/ui-component/dialog/LoginDialog.js: -------------------------------------------------------------------------------- 1 | import { createPortal } from 'react-dom' 2 | import { useState } from 'react' 3 | import PropTypes from 'prop-types' 4 | 5 | import { Dialog, DialogActions, DialogContent, Typography, DialogTitle } from '@mui/material' 6 | import { StyledButton } from 'ui-component/button/StyledButton' 7 | import { Input } from 'ui-component/input/Input' 8 | 9 | const LoginDialog = ({ show, dialogProps, onConfirm }) => { 10 | const portalElement = document.getElementById('portal') 11 | const usernameInput = { 12 | label: 'Username', 13 | name: 'username', 14 | type: 'string', 15 | placeholder: 'john doe' 16 | } 17 | const passwordInput = { 18 | label: 'Password', 19 | name: 'password', 20 | type: 'password' 21 | } 22 | const [usernameVal, setUsernameVal] = useState('') 23 | const [passwordVal, setPasswordVal] = useState('') 24 | 25 | const component = show ? ( 26 | { 28 | if (e.key === 'Enter') { 29 | onConfirm(usernameVal, passwordVal) 30 | } 31 | }} 32 | open={show} 33 | fullWidth 34 | maxWidth='xs' 35 | aria-labelledby='alert-dialog-title' 36 | aria-describedby='alert-dialog-description' 37 | > 38 | 39 | {dialogProps.title} 40 | 41 | 42 | Username 43 | setUsernameVal(newValue)} 46 | value={usernameVal} 47 | showDialog={false} 48 | /> 49 |
50 | Password 51 | setPasswordVal(newValue)} value={passwordVal} /> 52 |
53 | 54 | onConfirm(usernameVal, passwordVal)}> 55 | {dialogProps.confirmButtonName} 56 | 57 | 58 |
59 | ) : null 60 | 61 | return createPortal(component, portalElement) 62 | } 63 | 64 | LoginDialog.propTypes = { 65 | show: PropTypes.bool, 66 | dialogProps: PropTypes.object, 67 | onConfirm: PropTypes.func 68 | } 69 | 70 | export default LoginDialog 71 | -------------------------------------------------------------------------------- /src/ui-component/dialog/AboutDialog.js: -------------------------------------------------------------------------------- 1 | import { createPortal } from 'react-dom' 2 | import { useState, useEffect } from 'react' 3 | import PropTypes from 'prop-types' 4 | import { Dialog, DialogContent, DialogTitle, TableContainer, Table, TableHead, TableRow, TableCell, TableBody, Paper } from '@mui/material' 5 | import moment from 'moment' 6 | 7 | const AboutDialog = ({ show, onCancel }) => { 8 | const portalElement = document.getElementById('portal') 9 | 10 | const [data, setData] = useState({}) 11 | 12 | const component = show ? ( 13 | 21 | 22 | Version Info 23 | 24 | 25 | {data && ( 26 | 27 | 28 | 29 | 30 | Latest Version 31 | Publish Time 32 | 33 | 34 | 35 | 36 | 37 | 38 | StarCloud 1.0 39 | 40 | {/* 41 | {data.name} 42 | */} 43 | 44 | {moment('20240103').fromNow()} 45 | 46 | 47 |
48 |
49 | )} 50 |
51 |
52 | ) : null 53 | 54 | return createPortal(component, portalElement) 55 | } 56 | 57 | AboutDialog.propTypes = { 58 | show: PropTypes.bool, 59 | onCancel: PropTypes.func 60 | } 61 | 62 | export default AboutDialog 63 | -------------------------------------------------------------------------------- /src/ui-component/input/Input.js: -------------------------------------------------------------------------------- 1 | import { useState } from 'react' 2 | import PropTypes from 'prop-types' 3 | import { FormControl, OutlinedInput } from '@mui/material' 4 | import EditPromptValuesDialog from 'ui-component/dialog/EditPromptValuesDialog' 5 | 6 | export const Input = ({ inputParam, value, onChange, disabled = false, showDialog, dialogProps, onDialogCancel, onDialogConfirm }) => { 7 | const [myValue, setMyValue] = useState(value ?? '') 8 | 9 | const getInputType = (type) => { 10 | switch (type) { 11 | case 'string': 12 | return 'text' 13 | case 'password': 14 | return 'password' 15 | case 'number': 16 | return 'number' 17 | default: 18 | return 'text' 19 | } 20 | } 21 | 22 | return ( 23 | <> 24 | 25 | { 36 | setMyValue(e.target.value) 37 | onChange(e.target.value) 38 | }} 39 | inputProps={{ 40 | step: 0.1, 41 | style: { 42 | height: inputParam.rows ? '90px' : 'inherit' 43 | } 44 | }} 45 | /> 46 | 47 | {showDialog && ( 48 | { 53 | setMyValue(newValue) 54 | onDialogConfirm(newValue, inputParamName) 55 | }} 56 | > 57 | )} 58 | 59 | ) 60 | } 61 | 62 | Input.propTypes = { 63 | inputParam: PropTypes.object, 64 | value: PropTypes.string, 65 | onChange: PropTypes.func, 66 | disabled: PropTypes.bool, 67 | showDialog: PropTypes.bool, 68 | dialogProps: PropTypes.object, 69 | onDialogCancel: PropTypes.func, 70 | onDialogConfirm: PropTypes.func 71 | } 72 | -------------------------------------------------------------------------------- /src/ui-component/dropdown/Dropdown.js: -------------------------------------------------------------------------------- 1 | import { useState } from 'react' 2 | import { useSelector } from 'react-redux' 3 | 4 | import { Popper, FormControl, TextField, Box, Typography } from '@mui/material' 5 | import Autocomplete, { autocompleteClasses } from '@mui/material/Autocomplete' 6 | import { styled } from '@mui/material/styles' 7 | import PropTypes from 'prop-types' 8 | 9 | const StyledPopper = styled(Popper)({ 10 | boxShadow: '0px 8px 10px -5px rgb(0 0 0 / 20%), 0px 16px 24px 2px rgb(0 0 0 / 14%), 0px 6px 30px 5px rgb(0 0 0 / 12%)', 11 | borderRadius: '10px', 12 | [`& .${autocompleteClasses.listbox}`]: { 13 | boxSizing: 'border-box', 14 | '& ul': { 15 | padding: 10, 16 | margin: 10 17 | } 18 | } 19 | }) 20 | 21 | export const Dropdown = ({ name, value, options, onSelect, disabled = false, disableClearable = false }) => { 22 | const customization = useSelector((state) => state.customization) 23 | const findMatchingOptions = (options = [], value) => options.find((option) => option.name === value) 24 | const getDefaultOptionValue = () => '' 25 | let [internalValue, setInternalValue] = useState(value ?? 'choose an option') 26 | 27 | return ( 28 | 29 | { 37 | const value = selection ? selection.name : '' 38 | setInternalValue(value) 39 | onSelect(value) 40 | }} 41 | PopperComponent={StyledPopper} 42 | renderInput={(params) => } 43 | renderOption={(props, option) => ( 44 | 45 |
46 | {option.label} 47 | {option.description && ( 48 | {option.description} 49 | )} 50 |
51 |
52 | )} 53 | /> 54 |
55 | ) 56 | } 57 | 58 | Dropdown.propTypes = { 59 | name: PropTypes.string, 60 | value: PropTypes.string, 61 | options: PropTypes.array, 62 | onSelect: PropTypes.func, 63 | disabled: PropTypes.bool, 64 | disableClearable: PropTypes.bool 65 | } 66 | -------------------------------------------------------------------------------- /src/layout/MainLayout/Sidebar/index.js: -------------------------------------------------------------------------------- 1 | import PropTypes from 'prop-types' 2 | 3 | // material-ui 4 | import { useTheme } from '@mui/material/styles' 5 | import { Box, Drawer, useMediaQuery } from '@mui/material' 6 | 7 | // third-party 8 | import PerfectScrollbar from 'react-perfect-scrollbar' 9 | import { BrowserView, MobileView } from 'react-device-detect' 10 | 11 | // project imports 12 | import MenuList from './MenuList' 13 | // import LogoSection from '../LogoSection' 14 | import { drawerWidth } from 'store/constant' 15 | 16 | // ==============================|| SIDEBAR DRAWER ||============================== // 17 | 18 | const Sidebar = ({ drawerOpen, drawerToggle, window }) => { 19 | const theme = useTheme() 20 | const matchUpMd = useMediaQuery(theme.breakpoints.up('md')) 21 | 22 | const drawer = ( 23 | <> 24 | 25 | 26 | {/* */} 27 |

LLM scheduler 

28 |
29 |
30 | 31 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | ) 49 | 50 | const container = window !== undefined ? () => window.document.body : undefined 51 | 52 | return ( 53 | 54 | 74 | {drawer} 75 | 76 | 77 | ) 78 | } 79 | 80 | Sidebar.propTypes = { 81 | drawerOpen: PropTypes.bool, 82 | drawerToggle: PropTypes.func, 83 | window: PropTypes.object 84 | } 85 | 86 | export default Sidebar 87 | -------------------------------------------------------------------------------- /src/assets/scss/style.scss: -------------------------------------------------------------------------------- 1 | // color variants 2 | @import 'themes-vars.module.scss'; 3 | 4 | // third-party 5 | @import '~react-perfect-scrollbar/dist/css/styles.css'; 6 | 7 | // ==============================|| LIGHT BOX ||============================== // 8 | .fullscreen .react-images__blanket { 9 | z-index: 1200; 10 | } 11 | 12 | // ==============================|| PERFECT SCROLLBAR ||============================== // 13 | 14 | .scrollbar-container { 15 | .ps__rail-y { 16 | &:hover > .ps__thumb-y, 17 | &:focus > .ps__thumb-y, 18 | &.ps--clicking .ps__thumb-y { 19 | background-color: $grey500; 20 | width: 5px; 21 | } 22 | } 23 | .ps__thumb-y { 24 | background-color: $grey500; 25 | border-radius: 6px; 26 | width: 5px; 27 | right: 0; 28 | } 29 | } 30 | 31 | .scrollbar-container.ps, 32 | .scrollbar-container > .ps { 33 | &.ps--active-y > .ps__rail-y { 34 | width: 5px; 35 | background-color: transparent !important; 36 | z-index: 999; 37 | &:hover, 38 | &.ps--clicking { 39 | width: 5px; 40 | background-color: transparent; 41 | } 42 | } 43 | &.ps--scrolling-y > .ps__rail-y, 44 | &.ps--scrolling-x > .ps__rail-x { 45 | opacity: 0.4; 46 | background-color: transparent; 47 | } 48 | } 49 | 50 | // ==============================|| ANIMATION KEYFRAMES ||============================== // 51 | 52 | @keyframes wings { 53 | 50% { 54 | transform: translateY(-40px); 55 | } 56 | 100% { 57 | transform: translateY(0px); 58 | } 59 | } 60 | 61 | @keyframes blink { 62 | 50% { 63 | opacity: 0; 64 | } 65 | 100% { 66 | opacity: 1; 67 | } 68 | } 69 | 70 | @keyframes bounce { 71 | 0%, 72 | 20%, 73 | 53%, 74 | to { 75 | animation-timing-function: cubic-bezier(0.215, 0.61, 0.355, 1); 76 | transform: translateZ(0); 77 | } 78 | 40%, 79 | 43% { 80 | animation-timing-function: cubic-bezier(0.755, 0.05, 0.855, 0.06); 81 | transform: translate3d(0, -5px, 0); 82 | } 83 | 70% { 84 | animation-timing-function: cubic-bezier(0.755, 0.05, 0.855, 0.06); 85 | transform: translate3d(0, -7px, 0); 86 | } 87 | 80% { 88 | transition-timing-function: cubic-bezier(0.215, 0.61, 0.355, 1); 89 | transform: translateZ(0); 90 | } 91 | 90% { 92 | transform: translate3d(0, -2px, 0); 93 | } 94 | } 95 | 96 | @keyframes slideY { 97 | 0%, 98 | 50%, 99 | 100% { 100 | transform: translateY(0px); 101 | } 102 | 25% { 103 | transform: translateY(-10px); 104 | } 105 | 75% { 106 | transform: translateY(10px); 107 | } 108 | } 109 | 110 | @keyframes slideX { 111 | 0%, 112 | 50%, 113 | 100% { 114 | transform: translateX(0px); 115 | } 116 | 25% { 117 | transform: translateX(-10px); 118 | } 119 | 75% { 120 | transform: translateX(10px); 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /src/ui-component/file/File.js: -------------------------------------------------------------------------------- 1 | import { useState } from 'react' 2 | import PropTypes from 'prop-types' 3 | import { useTheme } from '@mui/material/styles' 4 | import { FormControl, Button } from '@mui/material' 5 | import { IconUpload } from '@tabler/icons' 6 | import { getFileName } from 'utils/genericHelper' 7 | 8 | export const File = ({ value, fileType, onChange, disabled = false }) => { 9 | const theme = useTheme() 10 | 11 | const [myValue, setMyValue] = useState(value ?? '') 12 | 13 | const handleFileUpload = async (e) => { 14 | if (!e.target.files) return 15 | 16 | if (e.target.files.length === 1) { 17 | const file = e.target.files[0] 18 | const { name } = file 19 | 20 | const reader = new FileReader() 21 | reader.onload = (evt) => { 22 | if (!evt?.target?.result) { 23 | return 24 | } 25 | const { result } = evt.target 26 | 27 | const value = result + `,filename:${name}` 28 | 29 | setMyValue(value) 30 | onChange(value) 31 | } 32 | reader.readAsDataURL(file) 33 | } else if (e.target.files.length > 0) { 34 | let files = Array.from(e.target.files).map((file) => { 35 | const reader = new FileReader() 36 | const { name } = file 37 | 38 | return new Promise((resolve) => { 39 | reader.onload = (evt) => { 40 | if (!evt?.target?.result) { 41 | return 42 | } 43 | const { result } = evt.target 44 | const value = result + `,filename:${name}` 45 | resolve(value) 46 | } 47 | reader.readAsDataURL(file) 48 | }) 49 | }) 50 | 51 | const res = await Promise.all(files) 52 | setMyValue(JSON.stringify(res)) 53 | onChange(JSON.stringify(res)) 54 | } 55 | } 56 | 57 | return ( 58 | 59 | 66 | {myValue ? getFileName(myValue) : 'Choose a file to upload'} 67 | 68 | 79 | 80 | ) 81 | } 82 | 83 | File.propTypes = { 84 | value: PropTypes.string, 85 | fileType: PropTypes.string, 86 | onChange: PropTypes.func, 87 | disabled: PropTypes.bool 88 | } 89 | -------------------------------------------------------------------------------- /src/ui-component/button/AnimateButton.js: -------------------------------------------------------------------------------- 1 | import PropTypes from 'prop-types' 2 | import { forwardRef } from 'react' 3 | // third-party 4 | import { motion, useCycle } from 'framer-motion' 5 | 6 | // ==============================|| ANIMATION BUTTON ||============================== // 7 | 8 | const AnimateButton = forwardRef(function AnimateButton({ children, type, direction, offset, scale }, ref) { 9 | let offset1 10 | let offset2 11 | switch (direction) { 12 | case 'up': 13 | case 'left': 14 | offset1 = offset 15 | offset2 = 0 16 | break 17 | case 'right': 18 | case 'down': 19 | default: 20 | offset1 = 0 21 | offset2 = offset 22 | break 23 | } 24 | 25 | const [x, cycleX] = useCycle(offset1, offset2) 26 | const [y, cycleY] = useCycle(offset1, offset2) 27 | 28 | switch (type) { 29 | case 'rotate': 30 | return ( 31 | 41 | {children} 42 | 43 | ) 44 | case 'slide': 45 | if (direction === 'up' || direction === 'down') { 46 | return ( 47 | cycleY()} 51 | onHoverStart={() => cycleY()} 52 | > 53 | {children} 54 | 55 | ) 56 | } 57 | return ( 58 | cycleX()} onHoverStart={() => cycleX()}> 59 | {children} 60 | 61 | ) 62 | 63 | case 'scale': 64 | default: 65 | if (typeof scale === 'number') { 66 | scale = { 67 | hover: scale, 68 | tap: scale 69 | } 70 | } 71 | return ( 72 | 73 | {children} 74 | 75 | ) 76 | } 77 | }) 78 | 79 | AnimateButton.propTypes = { 80 | children: PropTypes.node, 81 | offset: PropTypes.number, 82 | type: PropTypes.oneOf(['slide', 'scale', 'rotate']), 83 | direction: PropTypes.oneOf(['up', 'down', 'left', 'right']), 84 | scale: PropTypes.oneOfType([PropTypes.number, PropTypes.object]) 85 | } 86 | 87 | AnimateButton.defaultProps = { 88 | type: 'scale', 89 | offset: 10, 90 | direction: 'right', 91 | scale: { 92 | hover: 1, 93 | tap: 0.9 94 | } 95 | } 96 | 97 | export default AnimateButton 98 | -------------------------------------------------------------------------------- /src/views/k8sjobs/enhanceddeploytablehead.js: -------------------------------------------------------------------------------- 1 | import TableHead from '@mui/material/TableHead' 2 | import TableRow from '@mui/material/TableRow' 3 | import TableCell from '@mui/material/TableCell' 4 | import Checkbox from '@mui/material/Checkbox' 5 | import TableSortLabel from '@mui/material/TableSortLabel' 6 | import Box from '@mui/material/Box' 7 | import { visuallyHidden } from '@mui/utils' 8 | 9 | export const DeployHeadCells = [ 10 | { 11 | id: 'name', 12 | align: 'left', 13 | disablePadding: true, 14 | label: '名称', 15 | sortable: true 16 | }, 17 | { 18 | id: 'namespace', 19 | align: 'left', 20 | disablePadding: false, 21 | label: '命名空间', 22 | sortable: true 23 | }, 24 | // { 25 | // id: 'images', 26 | // align: 'left', 27 | // disablePadding: false, 28 | // label: '镜像', 29 | // sortable: true 30 | // }, 31 | { 32 | id: 'lable', 33 | align: 'left', 34 | disablePadding: false, 35 | label: '标签', 36 | sortable: false 37 | }, 38 | { 39 | id: 'status', 40 | align: 'left', 41 | disablePadding: false, 42 | label: '状态', 43 | sortable: false 44 | } 45 | ] 46 | 47 | const EnhancedDeployTableHead = (props) => { 48 | const { order, orderBy, onRequestSort } = props 49 | const createSortHandler = (property) => (event) => { 50 | onRequestSort(event, property) 51 | } 52 | 53 | return ( 54 | 55 | 56 | 57 | 58 | {DeployHeadCells.map((headCell) => ( 59 | 65 | {headCell.sortable ? ( 66 | 71 | {headCell.label} 72 | {orderBy === headCell.id ? ( 73 | 74 | {order === 'desc' ? 'sorted descending' : 'sorted ascending'} 75 | 76 | ) : null} 77 | 78 | ) : ( 79 | {headCell.label} 80 | )} 81 | 82 | ))} 83 | 84 | 85 | ) 86 | } 87 | 88 | export default EnhancedDeployTableHead 89 | -------------------------------------------------------------------------------- /src/views/hpcjobs/JobDetails.js: -------------------------------------------------------------------------------- 1 | import React, { useState, useEffect } from 'react' 2 | import { IconButton, Box, Tab, Dialog, DialogTitle, Slide } from '@mui/material' 3 | import { TabContext, TabList, TabPanel } from '@mui/lab' 4 | import CloseIcon from '@mui/icons-material/Close' 5 | import JobTab from './JobTab' 6 | import JobDataTab from './JobDataTab' 7 | // Hooks 8 | import useApi from 'hooks/useApi' 9 | import jobAPI from 'api/jobs' 10 | 11 | const Transition = React.forwardRef(function Transition(props, ref) { 12 | return 13 | }) 14 | 15 | const JobDetails = (props) => { 16 | const getSpecificJob = useApi(jobAPI.getSpecificJob) 17 | const { showDetails, onCancel, jobData } = props 18 | const [summaryTable, setSummaryTable] = useState('summary') 19 | const [jobDetails, setjobDetails] = useState({}) 20 | const handleTabChange = (event, newValue) => { 21 | setSummaryTable(newValue) 22 | } 23 | 24 | useEffect(() => { 25 | if (showDetails) { 26 | setSummaryTable('summary') 27 | getSpecificJob.request(jobData.job_id) 28 | } 29 | }, [showDetails]) 30 | 31 | useEffect(() => { 32 | if (getSpecificJob.data) { 33 | setjobDetails(getSpecificJob.data.jobs[0]) 34 | } 35 | }, [getSpecificJob.data]) 36 | 37 | return ( 38 | 56 | {'Job Details'} 57 | theme.palette.grey[500] 66 | }} 67 | onClick={onCancel} 68 | > 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | ) 87 | } 88 | 89 | export default JobDetails 90 | -------------------------------------------------------------------------------- /src/ui-component/extended/Transitions.js: -------------------------------------------------------------------------------- 1 | import PropTypes from 'prop-types' 2 | import { forwardRef } from 'react' 3 | 4 | // material-ui 5 | import { Collapse, Fade, Box, Grow, Slide, Zoom } from '@mui/material' 6 | 7 | // ==============================|| TRANSITIONS ||============================== // 8 | 9 | const Transitions = forwardRef(function Transitions({ children, position, type, direction, ...others }, ref) { 10 | let positionSX = { 11 | transformOrigin: '0 0 0' 12 | } 13 | 14 | switch (position) { 15 | case 'top-right': 16 | positionSX = { 17 | transformOrigin: 'top right' 18 | } 19 | break 20 | case 'top': 21 | positionSX = { 22 | transformOrigin: 'top' 23 | } 24 | break 25 | case 'bottom-left': 26 | positionSX = { 27 | transformOrigin: 'bottom left' 28 | } 29 | break 30 | case 'bottom-right': 31 | positionSX = { 32 | transformOrigin: 'bottom right' 33 | } 34 | break 35 | case 'bottom': 36 | positionSX = { 37 | transformOrigin: 'bottom' 38 | } 39 | break 40 | case 'top-left': 41 | default: 42 | positionSX = { 43 | transformOrigin: '0 0 0' 44 | } 45 | break 46 | } 47 | 48 | return ( 49 | 50 | {type === 'grow' && ( 51 | 52 | {children} 53 | 54 | )} 55 | {type === 'collapse' && ( 56 | 57 | {children} 58 | 59 | )} 60 | {type === 'fade' && ( 61 | 69 | {children} 70 | 71 | )} 72 | {type === 'slide' && ( 73 | 82 | {children} 83 | 84 | )} 85 | {type === 'zoom' && ( 86 | 87 | {children} 88 | 89 | )} 90 | 91 | ) 92 | }) 93 | 94 | Transitions.propTypes = { 95 | children: PropTypes.node, 96 | type: PropTypes.oneOf(['grow', 'fade', 'collapse', 'slide', 'zoom']), 97 | position: PropTypes.oneOf(['top-left', 'top-right', 'top', 'bottom-left', 'bottom-right', 'bottom']), 98 | direction: PropTypes.oneOf(['up', 'down', 'left', 'right']) 99 | } 100 | 101 | Transitions.defaultProps = { 102 | type: 'grow', 103 | position: 'top-left', 104 | direction: 'up' 105 | } 106 | 107 | export default Transitions 108 | -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | LLM scheduler 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 39 | 40 | 45 | 46 | 47 | 48 | 49 |
50 |
51 | 61 | 62 | 63 | -------------------------------------------------------------------------------- /src/views/orgmanage/enhancedtablehead.js: -------------------------------------------------------------------------------- 1 | import TableHead from '@mui/material/TableHead' 2 | import TableRow from '@mui/material/TableRow' 3 | import TableCell from '@mui/material/TableCell' 4 | import Checkbox from '@mui/material/Checkbox' 5 | import TableSortLabel from '@mui/material/TableSortLabel' 6 | import Box from '@mui/material/Box' 7 | import { visuallyHidden } from '@mui/utils' 8 | import { useEffect, useState, useMemo } from 'react' 9 | 10 | export const HeadCells = [ 11 | { 12 | id: 'name', 13 | align: 'left', 14 | disablePadding: true, 15 | label: 'User Name', 16 | sortable: true 17 | }, 18 | 19 | { 20 | id: 'accounts', 21 | align: 'left', 22 | disablePadding: false, 23 | label: 'User accounts', 24 | sortable: true 25 | }, 26 | { 27 | id: 'partitions', 28 | align: 'left', 29 | disablePadding: false, 30 | label: 'Available partitions', 31 | sortable: false 32 | }, 33 | { 34 | id: 'cluster', 35 | align: 'left', 36 | disablePadding: false, 37 | label: 'Cluster', 38 | sortable: true 39 | }, 40 | { 41 | id: 'operation', 42 | align: 'right', 43 | disablePadding: false, 44 | label: 'Operation', 45 | sortable: false 46 | } 47 | ] 48 | 49 | const EnhancedTableHead = (props) => { 50 | const { onSelectAllClick, order, orderBy, numSelected, rowCount, onRequestSort } = props 51 | const createSortHandler = (property) => (event) => { 52 | onRequestSort(event, property) 53 | } 54 | 55 | return ( 56 | 57 | 58 | 59 | 0 && numSelected < rowCount} 62 | onChange={onSelectAllClick} 63 | inputProps={{ 'aria-label': 'select all desserts' }} 64 | /> 65 | 66 | {HeadCells.map((headCell) => ( 67 | 73 | {headCell.sortable ? ( 74 | 79 | {headCell.label} 80 | {orderBy === headCell.id ? ( 81 | 82 | {order === 'desc' ? 'sorted descending' : 'sorted ascending'} 83 | 84 | ) : null} 85 | 86 | ) : ( 87 | {headCell.label} 88 | )} 89 | 90 | ))} 91 | 92 | 93 | ) 94 | } 95 | 96 | export default EnhancedTableHead 97 | -------------------------------------------------------------------------------- /src/views/k8sjobs/enhancedpodtablehead.js: -------------------------------------------------------------------------------- 1 | import TableHead from '@mui/material/TableHead' 2 | import TableRow from '@mui/material/TableRow' 3 | import TableCell from '@mui/material/TableCell' 4 | import Checkbox from '@mui/material/Checkbox' 5 | import TableSortLabel from '@mui/material/TableSortLabel' 6 | import Box from '@mui/material/Box' 7 | import { visuallyHidden } from '@mui/utils' 8 | 9 | export const PodHeadCells = [ 10 | { 11 | id: 'podname', 12 | align: 'left', 13 | disablePadding: true, 14 | label: '名称', 15 | sortable: true 16 | }, 17 | { 18 | id: 'namespace', 19 | align: 'left', 20 | disablePadding: false, 21 | label: '命名空间', 22 | sortable: true 23 | }, 24 | { 25 | id: 'state', 26 | align: 'center', 27 | disablePadding: false, 28 | label: '状态', 29 | sortable: false 30 | }, 31 | { 32 | id: 'pod_ip', 33 | align: 'left', 34 | disablePadding: false, 35 | label: 'Pod IP', 36 | sortable: true 37 | }, 38 | { 39 | id: 'lable', 40 | align: 'left', 41 | disablePadding: false, 42 | label: 'Lable', 43 | sortable: false 44 | }, 45 | { 46 | id: 'node', 47 | align: 'left', 48 | disablePadding: false, 49 | label: 'Node', 50 | sortable: false 51 | }, 52 | { 53 | id: 'node_ip', 54 | align: 'left', 55 | disablePadding: false, 56 | label: 'Node IP', 57 | sortable: false 58 | }, 59 | { 60 | id: 'start_time', 61 | align: 'center', 62 | disablePadding: false, 63 | label: 'Creation Time', 64 | sortable: true 65 | } 66 | ] 67 | 68 | const EnhancedPodTableHead = (props) => { 69 | const { order, orderBy, onRequestSort } = props 70 | const createSortHandler = (property) => (event) => { 71 | onRequestSort(event, property) 72 | } 73 | 74 | return ( 75 | 76 | 77 | 78 | 79 | {PodHeadCells.map((headCell) => ( 80 | 86 | {headCell.sortable ? ( 87 | 92 | {headCell.label} 93 | {orderBy === headCell.id ? ( 94 | 95 | {order === 'desc' ? 'sorted descending' : 'sorted ascending'} 96 | 97 | ) : null} 98 | 99 | ) : ( 100 | {headCell.label} 101 | )} 102 | 103 | ))} 104 | 105 | 106 | ) 107 | } 108 | 109 | export default EnhancedPodTableHead 110 | -------------------------------------------------------------------------------- /src/views/k8sjobs/enhancedservicetablehead.js: -------------------------------------------------------------------------------- 1 | import TableHead from '@mui/material/TableHead' 2 | import TableRow from '@mui/material/TableRow' 3 | import TableCell from '@mui/material/TableCell' 4 | import Checkbox from '@mui/material/Checkbox' 5 | import TableSortLabel from '@mui/material/TableSortLabel' 6 | import Box from '@mui/material/Box' 7 | import { visuallyHidden } from '@mui/utils' 8 | 9 | export const ServiceHeadCells = [ 10 | { 11 | id: 'name', 12 | align: 'left', 13 | disablePadding: true, 14 | label: '名称', 15 | sortable: true 16 | }, 17 | { 18 | id: 'namespace', 19 | align: 'left', 20 | disablePadding: false, 21 | label: '命名空间', 22 | sortable: true 23 | }, 24 | { 25 | id: 'type', 26 | align: 'left', 27 | disablePadding: false, 28 | label: '类型', 29 | sortable: true 30 | }, 31 | { 32 | id: 'cluster_ip', 33 | align: 'left', 34 | disablePadding: false, 35 | label: 'Cluster IP', 36 | sortable: true 37 | }, 38 | { 39 | id: 'external_ip', 40 | align: 'left', 41 | disablePadding: false, 42 | label: 'External IP', 43 | sortable: true 44 | }, 45 | { 46 | id: 'lable', 47 | align: 'left', 48 | disablePadding: false, 49 | label: '标签', 50 | sortable: false 51 | }, 52 | { 53 | id: 'ports', 54 | align: 'left', 55 | disablePadding: false, 56 | label: '端口', 57 | sortable: false 58 | }, 59 | { 60 | id: 'created_at', 61 | align: 'left', 62 | disablePadding: false, 63 | label: '创建日期', 64 | sortable: false 65 | } 66 | ] 67 | 68 | const EnhancedServiceTableHead = (props) => { 69 | const { order, orderBy, onRequestSort } = props 70 | const createSortHandler = (property) => (event) => { 71 | onRequestSort(event, property) 72 | } 73 | 74 | return ( 75 | 76 | 77 | 78 | 79 | {ServiceHeadCells.map((headCell) => ( 80 | 86 | {headCell.sortable ? ( 87 | 92 | {headCell.label} 93 | {orderBy === headCell.id ? ( 94 | 95 | {order === 'desc' ? 'sorted descending' : 'sorted ascending'} 96 | 97 | ) : null} 98 | 99 | ) : ( 100 | {headCell.label} 101 | )} 102 | 103 | ))} 104 | 105 | 106 | ) 107 | } 108 | 109 | export default EnhancedServiceTableHead 110 | -------------------------------------------------------------------------------- /src/views/k8sjobs/deploytablebody.js: -------------------------------------------------------------------------------- 1 | import React, { useState } from 'react' 2 | // material-ui 3 | import { TableBody, TableCell, TableRow, Checkbox } from '@mui/material' 4 | import { DeployHeadCells } from './enhanceddeploytablehead' 5 | 6 | const DeployTableBody = (props) => { 7 | const { visibleRows, emptyRows } = props 8 | // console.log(visibleRows) 9 | const [selected, setSelected] = useState([]) 10 | 11 | const isRowSelected = (name) => selected.findIndex((selData) => selData.name === name) !== -1 12 | 13 | const handlePodRowSelectClick = (event, name) => { 14 | // const selectedIndex = selected.indexOf(name) 15 | const selectedIndex = selected.findIndex((selData) => selData.name === name) 16 | let newSelected = [] 17 | 18 | if (selectedIndex === -1) { 19 | newSelected = newSelected.concat(selected, { name: name }) 20 | } else if (selectedIndex === 0) { 21 | newSelected = newSelected.concat(selected.slice(1)) 22 | } else if (selectedIndex === selected.length - 1) { 23 | newSelected = newSelected.concat(selected.slice(0, -1)) 24 | } else if (selectedIndex > 0) { 25 | newSelected = newSelected.concat(selected.slice(0, selectedIndex), selected.slice(selectedIndex + 1)) 26 | } 27 | setSelected(newSelected) 28 | } 29 | return ( 30 | 31 | {visibleRows.map((row, index) => { 32 | const isItemSelected = isRowSelected(row.name) 33 | const labelId = `enhanced-table-checkbox-${index}` 34 | return ( 35 | handlePodRowSelectClick(event, row.id, row.name)} 38 | role='checkbox' 39 | aria-checked={isItemSelected} 40 | tabIndex={-1} 41 | key={row.name} 42 | selected={isItemSelected} 43 | sx={{ cursor: 'pointer' }} 44 | > 45 | 46 | 47 | 48 | {row.name} 49 | 50 | 51 | {row.namespace ? ( 52 |
53 | {row.namespace} 54 |
55 | ) : ( 56 | '-' 57 | )} 58 |
59 | {/* {row.images ? JSON.stringify(row.images) : '-'} */} 60 | {row.labels ? Object.keys(row.labels).map(function (key) { 61 | return "" + key + "=" + row.labels[key]; 62 | }).join(" ") : '-'} 63 | {row.status ? Object.keys(row.status).map(function (key) { 64 | return "" + key + "=" + row.status[key]; 65 | }).join(" ") : '-'} 66 | 67 |
68 | ) 69 | })} 70 | {emptyRows > 0 ? ( 71 | 72 | 73 | 74 | ) : null} 75 |
76 | ) 77 | } 78 | 79 | export default DeployTableBody 80 | -------------------------------------------------------------------------------- /src/ui-component/editor/prism-light.css: -------------------------------------------------------------------------------- 1 | code[class*='language-'], 2 | pre[class*='language-'] { 3 | text-align: left; 4 | white-space: pre; 5 | word-spacing: normal; 6 | word-break: normal; 7 | word-wrap: normal; 8 | color: #90a4ae; 9 | background: #fafafa; 10 | font-family: Roboto Mono, monospace; 11 | font-size: 1em; 12 | line-height: 1.5em; 13 | 14 | -moz-tab-size: 4; 15 | -o-tab-size: 4; 16 | tab-size: 4; 17 | 18 | -webkit-hyphens: none; 19 | -moz-hyphens: none; 20 | -ms-hyphens: none; 21 | hyphens: none; 22 | } 23 | 24 | code[class*='language-']::-moz-selection, 25 | pre[class*='language-']::-moz-selection, 26 | code[class*='language-'] ::-moz-selection, 27 | pre[class*='language-'] ::-moz-selection { 28 | background: #cceae7; 29 | color: #263238; 30 | } 31 | 32 | code[class*='language-']::selection, 33 | pre[class*='language-']::selection, 34 | code[class*='language-'] ::selection, 35 | pre[class*='language-'] ::selection { 36 | background: #cceae7; 37 | color: #263238; 38 | } 39 | 40 | :not(pre) > code[class*='language-'] { 41 | white-space: normal; 42 | border-radius: 0.2em; 43 | padding: 0.1em; 44 | } 45 | 46 | pre[class*='language-'] { 47 | overflow: auto; 48 | position: relative; 49 | margin: 0.5em 0; 50 | padding: 1.25em 1em; 51 | } 52 | 53 | .language-css > code, 54 | .language-sass > code, 55 | .language-scss > code { 56 | color: #f76d47; 57 | } 58 | 59 | [class*='language-'] .namespace { 60 | opacity: 0.7; 61 | } 62 | 63 | .token.atrule { 64 | color: #7c4dff; 65 | } 66 | 67 | .token.attr-name { 68 | color: #39adb5; 69 | } 70 | 71 | .token.attr-value { 72 | color: #f6a434; 73 | } 74 | 75 | .token.attribute { 76 | color: #f6a434; 77 | } 78 | 79 | .token.boolean { 80 | color: #7c4dff; 81 | } 82 | 83 | .token.builtin { 84 | color: #39adb5; 85 | } 86 | 87 | .token.cdata { 88 | color: #39adb5; 89 | } 90 | 91 | .token.char { 92 | color: #39adb5; 93 | } 94 | 95 | .token.class { 96 | color: #39adb5; 97 | } 98 | 99 | .token.class-name { 100 | color: #6182b8; 101 | } 102 | 103 | .token.comment { 104 | color: #aabfc9; 105 | } 106 | 107 | .token.constant { 108 | color: #7c4dff; 109 | } 110 | 111 | .token.deleted { 112 | color: #e53935; 113 | } 114 | 115 | .token.doctype { 116 | color: #aabfc9; 117 | } 118 | 119 | .token.entity { 120 | color: #e53935; 121 | } 122 | 123 | .token.function { 124 | color: #7c4dff; 125 | } 126 | 127 | .token.hexcode { 128 | color: #f76d47; 129 | } 130 | 131 | .token.id { 132 | color: #7c4dff; 133 | font-weight: bold; 134 | } 135 | 136 | .token.important { 137 | color: #7c4dff; 138 | font-weight: bold; 139 | } 140 | 141 | .token.inserted { 142 | color: #39adb5; 143 | } 144 | 145 | .token.keyword { 146 | color: #7c4dff; 147 | } 148 | 149 | .token.number { 150 | color: #f76d47; 151 | } 152 | 153 | .token.operator { 154 | color: #39adb5; 155 | } 156 | 157 | .token.prolog { 158 | color: #aabfc9; 159 | } 160 | 161 | .token.property { 162 | color: #39adb5; 163 | } 164 | 165 | .token.pseudo-class { 166 | color: #f6a434; 167 | } 168 | 169 | .token.pseudo-element { 170 | color: #f6a434; 171 | } 172 | 173 | .token.punctuation { 174 | color: #39adb5; 175 | } 176 | 177 | .token.regex { 178 | color: #6182b8; 179 | } 180 | 181 | .token.selector { 182 | color: #e53935; 183 | } 184 | 185 | .token.string { 186 | color: #f6a434; 187 | } 188 | 189 | .token.symbol { 190 | color: #7c4dff; 191 | } 192 | 193 | .token.tag { 194 | color: #e53935; 195 | } 196 | 197 | .token.unit { 198 | color: #f76d47; 199 | } 200 | 201 | .token.url { 202 | color: #e53935; 203 | } 204 | 205 | .token.variable { 206 | color: #e53935; 207 | } 208 | -------------------------------------------------------------------------------- /src/views/resources/enhancedtabletoolbar.js: -------------------------------------------------------------------------------- 1 | import { alpha } from '@mui/material/styles' 2 | import Toolbar from '@mui/material/Toolbar' 3 | import Typography from '@mui/material/Typography' 4 | import Tooltip from '@mui/material/Tooltip' 5 | import IconButton from '@mui/material/IconButton' 6 | import { DoDisturb, ArrowCircleUp } from '@mui/icons-material' 7 | import useConfirm from 'hooks/useConfirm' 8 | import ConfirmDialog from 'ui-component/dialog/ConfirmDialog' 9 | 10 | const EnhancedTableToolbar = (props) => { 11 | const { numSelected, selectRows, closeNode, openNode, setSelected } = props 12 | const { confirm } = useConfirm() 13 | 14 | const getShowAppNames = () => { 15 | let allNames = '' 16 | if (selectRows) { 17 | selectRows.forEach((d) => { 18 | allNames += ',' + d.name 19 | }) 20 | } 21 | if (allNames.length > 0) { 22 | allNames = allNames.substring(1) 23 | } 24 | return allNames 25 | } 26 | 27 | const closeNodes = async () => { 28 | let names = getShowAppNames() 29 | const confirmPayload = { 30 | title: 'Close', 31 | description: `are you sure to close ${names}?`, 32 | confirmButtonName: 'Yes', 33 | cancelButtonName: 'No' 34 | } 35 | const isConfirmed = await confirm(confirmPayload) 36 | if (isConfirmed) { 37 | selectRows.forEach((item) => { 38 | closeNode(item) 39 | }) 40 | setSelected([]) 41 | } 42 | } 43 | 44 | const openNodes = async () => { 45 | let names = getShowAppNames() 46 | const confirmPayload = { 47 | title: 'Open', 48 | description: `确认要打开${names}吗?`, 49 | confirmButtonName: '确定', 50 | cancelButtonName: '取消' 51 | } 52 | const isConfirmed = await confirm(confirmPayload) 53 | console.log('isConfirmed', isConfirmed, selectRows) 54 | if (isConfirmed) { 55 | selectRows.forEach((item) => { 56 | openNode(item) 57 | }) 58 | setSelected([]) 59 | } 60 | } 61 | 62 | return ( 63 | <> 64 | {/* 0 && { bgcolor: (theme) => alpha(theme.palette.primary.main, theme.palette.action.activatedOpacity) }) 69 | }} 70 | > 71 | {numSelected > 0 ? ( 72 | 73 | {numSelected} 条被选择 74 | 75 | ) : ( 76 | 77 | 未选择条目 78 | 79 | )} 80 | {numSelected > 0 && ( 81 | <> 82 | 83 | closeNodes()}> 84 | 85 | 86 | 87 | 88 | openNodes()}> 89 | 90 | 91 | 92 | 93 | )} 94 | */} 95 | 96 | 97 | ) 98 | } 99 | 100 | export default EnhancedTableToolbar 101 | -------------------------------------------------------------------------------- /src/views/k8sjobs/podtablebody.js: -------------------------------------------------------------------------------- 1 | import React, { useState } from 'react' 2 | // material-ui 3 | import { TableBody, TableCell, TableRow, Checkbox } from '@mui/material' 4 | import { PodHeadCells } from './enhancedpodtablehead' 5 | 6 | const PodTableBody = (props) => { 7 | const { visibleRows, emptyRows } = props 8 | // console.log(visibleRows) 9 | const [selected, setSelected] = useState([]) 10 | 11 | const isRowSelected = (name) => selected.findIndex((selData) => selData.name === name) !== -1 12 | 13 | const handlePodRowSelectClick = (event, name) => { 14 | // const selectedIndex = selected.indexOf(name) 15 | const selectedIndex = selected.findIndex((selData) => selData.name === name) 16 | let newSelected = [] 17 | 18 | if (selectedIndex === -1) { 19 | newSelected = newSelected.concat(selected, { name: name }) 20 | } else if (selectedIndex === 0) { 21 | newSelected = newSelected.concat(selected.slice(1)) 22 | } else if (selectedIndex === selected.length - 1) { 23 | newSelected = newSelected.concat(selected.slice(0, -1)) 24 | } else if (selectedIndex > 0) { 25 | newSelected = newSelected.concat(selected.slice(0, selectedIndex), selected.slice(selectedIndex + 1)) 26 | } 27 | setSelected(newSelected) 28 | } 29 | return ( 30 | 31 | {visibleRows.map((row, index) => { 32 | const isItemSelected = isRowSelected(row.name) 33 | const labelId = `enhanced-table-checkbox-${index}` 34 | return ( 35 | handlePodRowSelectClick(event, row.id, row.name)} 38 | role='checkbox' 39 | aria-checked={isItemSelected} 40 | tabIndex={-1} 41 | key={row.name} 42 | selected={isItemSelected} 43 | sx={{ cursor: 'pointer' }} 44 | > 45 | 46 | 47 | 48 | {row.name} 49 | 50 | 51 | {row.namespace ? ( 52 |
53 | {row.namespace} 54 |
55 | ) : ( 56 | '-' 57 | )} 58 |
59 | {row.status ? row.status : '-'} 60 | {row.pod_ip ? row.pod_ip : '-'} 61 | {row.labels ? Object.keys(row.labels).map(function (key) { 62 | return "" + key + "=" + row.labels[key]; 63 | }).join(" ") : '-'} 64 | {row.node_name ? row.node_name : '-'} 65 | {row.node_ip ? row.node_ip : '-'} 66 | {row.start_time ? row.start_time : '-'} 67 |
68 | ) 69 | })} 70 | {emptyRows > 0 ? ( 71 | 72 | 73 | 74 | ) : null} 75 |
76 | ) 77 | } 78 | 79 | export default PodTableBody 80 | -------------------------------------------------------------------------------- /src/views/k8sjobs/enhancedtabletoolbar.js: -------------------------------------------------------------------------------- 1 | import { alpha } from '@mui/material/styles' 2 | import Toolbar from '@mui/material/Toolbar' 3 | import Typography from '@mui/material/Typography' 4 | import Tooltip from '@mui/material/Tooltip' 5 | import IconButton from '@mui/material/IconButton' 6 | import FilterListIcon from '@mui/icons-material/FilterList' 7 | import StopCircleIcon from '@mui/icons-material/StopCircle' 8 | import useConfirm from 'hooks/useConfirm' 9 | import ConfirmDialog from 'ui-component/dialog/ConfirmDialog' 10 | 11 | const EnhancedTableToolbar = (props) => { 12 | const { numSelected, selectRows, stopJob, setSelected } = props 13 | const { confirm } = useConfirm() 14 | 15 | const getShowAppNames = () => { 16 | let allNames = '' 17 | if (selectRows) { 18 | selectRows.forEach((d) => { 19 | allNames += ',' + d.job_id 20 | }) 21 | } 22 | if (allNames.length > 0) { 23 | allNames = allNames.substring(1) 24 | } 25 | return allNames 26 | } 27 | 28 | 29 | const stopApps = async () => { 30 | let names = getShowAppNames() 31 | const confirmPayload = { 32 | title: '停止', 33 | description: `确认要停止${numSelected}个作业"${names}"吗?`, 34 | confirmButtonName: '停止' 35 | // cancelButtonName: '取消' 36 | } 37 | const isConfirmed = await confirm(confirmPayload) 38 | console.log('isConfirmed', isConfirmed, selectRows) 39 | if (isConfirmed) { 40 | let jobids = [] 41 | selectRows.forEach((item) => { 42 | jobids.push(item.job_id) 43 | }) 44 | stopJob(jobids) 45 | setSelected([]) 46 | } 47 | } 48 | 49 | 50 | return ( 51 | <> 52 | 0 && { bgcolor: (theme) => alpha(theme.palette.primary.main, theme.palette.action.activatedOpacity) }) 57 | }} 58 | > 59 | {numSelected > 0 ? ( 60 | 61 | {numSelected} selected 62 | 63 | ) : ( 64 | 65 | No selected items 66 | 67 | )} 68 | {numSelected > 0 ? ( 69 | <> 70 | {/* 71 | susApps()}> 72 | 73 | 74 | 75 | 76 | resumeApps()}> 77 | 78 | 79 | */} 80 | 81 | stopApps()}> 82 | 83 | 84 | 85 | 86 | ) : ( 87 | 88 | 89 | 90 | 91 | 92 | )} 93 | 94 | 95 | 96 | ) 97 | } 98 | 99 | export default EnhancedTableToolbar 100 | -------------------------------------------------------------------------------- /src/layout/MainLayout/index.js: -------------------------------------------------------------------------------- 1 | import { useEffect } from 'react' 2 | import { useDispatch, useSelector } from 'react-redux' 3 | import { Outlet } from 'react-router-dom' 4 | 5 | // material-ui 6 | import { styled, useTheme } from '@mui/material/styles' 7 | import { AppBar, Box, CssBaseline, Toolbar, useMediaQuery } from '@mui/material' 8 | 9 | // project imports 10 | import Header from './Header' 11 | import Sidebar from './Sidebar' 12 | import { drawerWidth } from 'store/constant' 13 | import { SET_MENU } from 'store/actions' 14 | 15 | // styles 16 | const Main = styled('main', { shouldForwardProp: (prop) => prop !== 'open' })(({ theme, open }) => ({ 17 | ...theme.typography.mainContent, 18 | ...(!open && { 19 | borderBottomLeftRadius: 0, 20 | borderBottomRightRadius: 0, 21 | transition: theme.transitions.create('margin', { 22 | easing: theme.transitions.easing.sharp, 23 | duration: theme.transitions.duration.leavingScreen 24 | }), 25 | [theme.breakpoints.up('md')]: { 26 | marginLeft: -(drawerWidth - 20), 27 | width: `calc(100% - ${drawerWidth}px)` 28 | }, 29 | [theme.breakpoints.down('md')]: { 30 | marginLeft: '20px', 31 | width: `calc(100% - ${drawerWidth}px)`, 32 | padding: '16px' 33 | }, 34 | [theme.breakpoints.down('sm')]: { 35 | marginLeft: '10px', 36 | width: `calc(100% - ${drawerWidth}px)`, 37 | padding: '16px', 38 | marginRight: '10px' 39 | } 40 | }), 41 | ...(open && { 42 | transition: theme.transitions.create('margin', { 43 | easing: theme.transitions.easing.easeOut, 44 | duration: theme.transitions.duration.enteringScreen 45 | }), 46 | marginLeft: 0, 47 | borderBottomLeftRadius: 0, 48 | borderBottomRightRadius: 0, 49 | width: `calc(100% - ${drawerWidth}px)`, 50 | [theme.breakpoints.down('md')]: { 51 | marginLeft: '20px' 52 | }, 53 | [theme.breakpoints.down('sm')]: { 54 | marginLeft: '10px' 55 | } 56 | }) 57 | })) 58 | 59 | // ==============================|| MAIN LAYOUT ||============================== // 60 | 61 | const MainLayout = () => { 62 | const theme = useTheme() 63 | const matchDownMd = useMediaQuery(theme.breakpoints.down('lg')) 64 | 65 | // Handle left drawer 66 | const leftDrawerOpened = useSelector((state) => state.customization.opened) 67 | const dispatch = useDispatch() 68 | const handleLeftDrawerToggle = () => { 69 | dispatch({ type: SET_MENU, opened: !leftDrawerOpened }) 70 | } 71 | useEffect(() => { 72 | setTimeout(() => dispatch({ type: SET_MENU, opened: !matchDownMd }), 0) 73 | }, [matchDownMd]) 74 | 75 | return ( 76 | 77 | 78 | {/* header */} 79 | 89 | 90 |
91 | 92 | 93 | 94 | {/* drawer */} 95 | 96 | 97 | {/* main content */} 98 |
99 | 100 |
101 | 102 | ) 103 | } 104 | 105 | export default MainLayout 106 | -------------------------------------------------------------------------------- /src/views/k8sjobs/servicetablebody.js: -------------------------------------------------------------------------------- 1 | import React, { useState } from 'react' 2 | // material-ui 3 | import { TableBody, TableCell, TableRow, Checkbox } from '@mui/material' 4 | import { ServiceHeadCells } from './enhancedservicetablehead' 5 | 6 | const ServiceTableBody = (props) => { 7 | const { visibleRows, emptyRows } = props 8 | // console.log(visibleRows) 9 | const [selected, setSelected] = useState([]) 10 | 11 | const isRowSelected = (name) => selected.findIndex((selData) => selData.name === name) !== -1 12 | 13 | const handlePodRowSelectClick = (event, name) => { 14 | // const selectedIndex = selected.indexOf(name) 15 | const selectedIndex = selected.findIndex((selData) => selData.name === name) 16 | let newSelected = [] 17 | 18 | if (selectedIndex === -1) { 19 | newSelected = newSelected.concat(selected, { name: name }) 20 | } else if (selectedIndex === 0) { 21 | newSelected = newSelected.concat(selected.slice(1)) 22 | } else if (selectedIndex === selected.length - 1) { 23 | newSelected = newSelected.concat(selected.slice(0, -1)) 24 | } else if (selectedIndex > 0) { 25 | newSelected = newSelected.concat(selected.slice(0, selectedIndex), selected.slice(selectedIndex + 1)) 26 | } 27 | setSelected(newSelected) 28 | } 29 | return ( 30 | 31 | {visibleRows.map((row, index) => { 32 | const isItemSelected = isRowSelected(row.name) 33 | const labelId = `enhanced-table-checkbox-${index}` 34 | return ( 35 | handlePodRowSelectClick(event, row.id, row.name)} 38 | role='checkbox' 39 | aria-checked={isItemSelected} 40 | tabIndex={-1} 41 | key={row.name} 42 | selected={isItemSelected} 43 | sx={{ cursor: 'pointer' }} 44 | > 45 | 46 | 47 | 48 | {row.name} 49 | 50 | 51 | {row.namespace ? ( 52 |
53 | {row.namespace} 54 |
55 | ) : ( 56 | '-' 57 | )} 58 |
59 | {row.type ? row.type : '-'} 60 | {row.cluster_ip ? row.cluster_ip : '-'} 61 | {row.external_ip ? row.external_ip : '-'} 62 | {row.labels ? Object.keys(row.labels).map(function (key) { 63 | return "" + key + "=" + row.labels[key]; 64 | }).join(" ") : '-'} 65 | {row.ports ? JSON.stringify(row.ports) : '-'} 66 | {row.created_at ? row.created_at : '-'} 67 |
68 | ) 69 | })} 70 | {emptyRows > 0 ? ( 71 | 72 | 73 | 74 | ) : null} 75 |
76 | ) 77 | } 78 | 79 | export default ServiceTableBody 80 | -------------------------------------------------------------------------------- /src/themes/typography.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Typography used in theme 3 | * @param {JsonObject} theme theme customization object 4 | */ 5 | 6 | export default function themeTypography(theme) { 7 | return { 8 | fontFamily: theme?.customization?.fontFamily, 9 | h6: { 10 | fontWeight: 500, 11 | color: theme.heading, 12 | fontSize: '0.75rem' 13 | }, 14 | h5: { 15 | fontSize: '0.875rem', 16 | color: theme.heading, 17 | fontWeight: 500 18 | }, 19 | h4: { 20 | fontSize: '1rem', 21 | color: theme.heading, 22 | fontWeight: 600 23 | }, 24 | h3: { 25 | fontSize: '1.25rem', 26 | color: theme.heading, 27 | fontWeight: 600 28 | }, 29 | h2: { 30 | fontSize: '1.5rem', 31 | color: theme.heading, 32 | fontWeight: 700 33 | }, 34 | h1: { 35 | fontSize: '2.125rem', 36 | color: theme.heading, 37 | fontWeight: 700 38 | }, 39 | subtitle1: { 40 | fontSize: '0.875rem', 41 | fontWeight: 500, 42 | color: theme.textDark 43 | }, 44 | subtitle2: { 45 | fontSize: '0.75rem', 46 | fontWeight: 400, 47 | color: theme.darkTextSecondary 48 | }, 49 | caption: { 50 | fontSize: '0.75rem', 51 | color: theme.darkTextSecondary, 52 | fontWeight: 400 53 | }, 54 | body1: { 55 | fontSize: '0.875rem', 56 | fontWeight: 400, 57 | lineHeight: '1.334em' 58 | }, 59 | body2: { 60 | letterSpacing: '0em', 61 | fontWeight: 400, 62 | lineHeight: '1.5em', 63 | color: theme.darkTextPrimary 64 | }, 65 | button: { 66 | textTransform: 'capitalize' 67 | }, 68 | customInput: { 69 | marginTop: 1, 70 | marginBottom: 1, 71 | '& > label': { 72 | top: 23, 73 | left: 0, 74 | color: theme.grey500, 75 | '&[data-shrink="false"]': { 76 | top: 5 77 | } 78 | }, 79 | '& > div > input': { 80 | padding: '30.5px 14px 11.5px !important' 81 | }, 82 | '& legend': { 83 | display: 'none' 84 | }, 85 | '& fieldset': { 86 | top: 0 87 | } 88 | }, 89 | mainContent: { 90 | backgroundColor: theme.background, 91 | width: '100%', 92 | minHeight: 'calc(100vh - 75px)', 93 | flexGrow: 1, 94 | padding: '20px', 95 | marginTop: '75px', 96 | marginRight: '20px', 97 | borderRadius: `${theme?.customization?.borderRadius}px` 98 | }, 99 | menuCaption: { 100 | fontSize: '0.875rem', 101 | fontWeight: 500, 102 | color: theme.heading, 103 | padding: '6px', 104 | textTransform: 'capitalize', 105 | marginTop: '10px' 106 | }, 107 | subMenuCaption: { 108 | fontSize: '0.6875rem', 109 | fontWeight: 500, 110 | color: theme.darkTextSecondary, 111 | textTransform: 'capitalize' 112 | }, 113 | commonAvatar: { 114 | cursor: 'pointer', 115 | borderRadius: '8px' 116 | }, 117 | smallAvatar: { 118 | width: '22px', 119 | height: '22px', 120 | fontSize: '1rem' 121 | }, 122 | mediumAvatar: { 123 | width: '34px', 124 | height: '34px', 125 | fontSize: '1.2rem' 126 | }, 127 | largeAvatar: { 128 | width: '44px', 129 | height: '44px', 130 | fontSize: '1.5rem' 131 | } 132 | } 133 | } 134 | -------------------------------------------------------------------------------- /src/views/resources/enhancedtablehead.js: -------------------------------------------------------------------------------- 1 | import TableHead from '@mui/material/TableHead' 2 | import TableRow from '@mui/material/TableRow' 3 | import TableCell from '@mui/material/TableCell' 4 | // import Checkbox from '@mui/material/Checkbox' 5 | import TableSortLabel from '@mui/material/TableSortLabel' 6 | import Box from '@mui/material/Box' 7 | import { visuallyHidden } from '@mui/utils' 8 | 9 | export const HeadCells = [ 10 | { 11 | id: 'name', 12 | align: 'center', 13 | disablePadding: true, 14 | label: 'Host Name', 15 | sortable: true 16 | }, 17 | { 18 | id: 'coreNum', 19 | align: 'left', 20 | disablePadding: false, 21 | label: 'Cores', 22 | sortable: true 23 | }, 24 | { 25 | id: 'cpus', 26 | align: 'left', 27 | disablePadding: false, 28 | label: 'CPUs', 29 | sortable: false 30 | }, 31 | { 32 | id: 'gpus', 33 | align: 'left', 34 | disablePadding: false, 35 | label: 'GPUs', 36 | sortable: false 37 | }, 38 | { 39 | id: 'cpuload', 40 | align: 'right', 41 | disablePadding: false, 42 | label: 'cpu loads', 43 | sortable: false 44 | }, 45 | { 46 | id: 'mem', 47 | align: 'right', 48 | disablePadding: false, 49 | label: 'Memory', 50 | sortable: true 51 | }, 52 | { 53 | id: 'weight', 54 | align: 'right', 55 | disablePadding: false, 56 | label: 'Weight', 57 | sortable: true 58 | }, 59 | { 60 | id: 'status', 61 | align: 'right', 62 | disablePadding: false, 63 | label: 'State', 64 | sortable: true 65 | } 66 | ] 67 | 68 | const EnhancedTableHead = (props) => { 69 | const { onSelectAllClick, order, orderBy, numSelected, rowCount, onRequestSort } = props 70 | const createSortHandler = (property) => (event) => { 71 | onRequestSort(event, property) 72 | } 73 | 74 | return ( 75 | 76 | 77 | {/* 78 | 0 && numSelected < rowCount} 81 | checked={rowCount > 0 && numSelected === rowCount} 82 | onChange={onSelectAllClick} 83 | inputProps={{ 'aria-label': 'select all desserts' }} 84 | /> 85 | */} 86 | {HeadCells.map((headCell) => ( 87 | 93 | {headCell.sortable ? ( 94 | 99 | {headCell.label} 100 | {orderBy === headCell.id ? ( 101 | 102 | {order === 'desc' ? 'sorted descending' : 'sorted ascending'} 103 | 104 | ) : null} 105 | 106 | ) : ( 107 | {headCell.label} 108 | )} 109 | 110 | ))} 111 | 112 | 113 | ) 114 | } 115 | 116 | export default EnhancedTableHead 117 | -------------------------------------------------------------------------------- /src/assets/scss/_themes-vars.module.scss: -------------------------------------------------------------------------------- 1 | // paper & background 2 | $paper: #ffffff; 3 | 4 | // primary 5 | $primaryLight: #e3f2fd; 6 | $primaryMain: #2196f3; 7 | $primaryDark: #1e88e5; 8 | $primary200: #90caf9; 9 | $primary800: #1565c0; 10 | 11 | // secondary 12 | $secondaryLight: #ede7f6; 13 | $secondaryMain: #673ab7; 14 | $secondaryDark: #5e35b1; 15 | $secondary200: #b39ddb; 16 | $secondary800: #4527a0; 17 | $secondaryIcon: #1c8b7c; 18 | $secondaryIconHover: #2e9486; 19 | $secondaryIconText: #ffffff; 20 | 21 | // success Colors 22 | $successLight: #cdf5d8; 23 | $success200: #69f0ae; 24 | $successMain: #00e676; 25 | $successDark: #00c853; 26 | 27 | // error 28 | $errorLight: #f3d2d2; 29 | $errorMain: #f44336; 30 | $errorDark: #c62828; 31 | 32 | // orange 33 | $orangeLight: #fbe9e7; 34 | $orangeMain: #ffab91; 35 | $orangeDark: #d84315; 36 | 37 | // warning 38 | $warningLight: #fff8e1; 39 | $warningMain: #ffe57f; 40 | $warningDark: #ffc107; 41 | 42 | // grey 43 | $grey50: #fafafa; 44 | $grey100: #f5f5f5; 45 | $grey200: #eeeeee; 46 | $grey300: #e0e0e0; 47 | $grey500: #9e9e9e; 48 | $grey600: #757575; 49 | $grey700: #616161; 50 | $grey900: #212121; 51 | 52 | // ==============================|| DARK THEME VARIANTS ||============================== // 53 | 54 | // paper & background 55 | $darkBackground: #191b1f; 56 | $darkPaper: #191b1f; 57 | 58 | // dark 800 & 900 59 | $darkLevel1: #252525; // level 1 60 | $darkLevel2: #242424; // level 2 61 | 62 | // primary dark 63 | $darkPrimaryLight: #23262c; 64 | $darkPrimaryMain: #23262c; 65 | $darkPrimaryDark: #191b1f; 66 | $darkPrimary200: #c9d4e9; 67 | $darkPrimary800: #32353b; 68 | 69 | // secondary dark 70 | $darkSecondaryLight: #454c59; 71 | $darkSecondaryMain: #7c4dff; 72 | $darkSecondaryDark: #ffffff; 73 | $darkSecondary200: #32353b; 74 | $darkSecondary800: #6200ea; 75 | 76 | // text variants 77 | $darkTextTitle: #d7dcec; 78 | $darkTextPrimary: #bdc8f0; 79 | $darkTextSecondary: #8492c4; 80 | 81 | // ==============================|| JAVASCRIPT ||============================== // 82 | 83 | :export { 84 | // paper & background 85 | paper: $paper; 86 | 87 | // primary 88 | primaryLight: $primaryLight; 89 | primary200: $primary200; 90 | primaryMain: $primaryMain; 91 | primaryDark: $primaryDark; 92 | primary800: $primary800; 93 | 94 | // secondary 95 | secondaryLight: $secondaryLight; 96 | secondary200: $secondary200; 97 | secondaryMain: $secondaryMain; 98 | secondaryDark: $secondaryDark; 99 | secondary800: $secondary800; 100 | secondaryIcon: $secondaryIcon; 101 | secondaryIconHover: $secondaryIconHover; 102 | secondaryIconText: $secondaryIconText; 103 | 104 | // success 105 | successLight: $successLight; 106 | success200: $success200; 107 | successMain: $successMain; 108 | successDark: $successDark; 109 | 110 | // error 111 | errorLight: $errorLight; 112 | errorMain: $errorMain; 113 | errorDark: $errorDark; 114 | 115 | // orange 116 | orangeLight: $orangeLight; 117 | orangeMain: $orangeMain; 118 | orangeDark: $orangeDark; 119 | 120 | // warning 121 | warningLight: $warningLight; 122 | warningMain: $warningMain; 123 | warningDark: $warningDark; 124 | 125 | // grey 126 | grey50: $grey50; 127 | grey100: $grey100; 128 | grey200: $grey200; 129 | grey300: $grey300; 130 | grey500: $grey500; 131 | grey600: $grey600; 132 | grey700: $grey700; 133 | grey900: $grey900; 134 | 135 | // ==============================|| DARK THEME VARIANTS ||============================== // 136 | 137 | // paper & background 138 | darkPaper: $darkPaper; 139 | darkBackground: $darkBackground; 140 | 141 | // dark 800 & 900 142 | darkLevel1: $darkLevel1; 143 | darkLevel2: $darkLevel2; 144 | 145 | // text variants 146 | darkTextTitle: $darkTextTitle; 147 | darkTextPrimary: $darkTextPrimary; 148 | darkTextSecondary: $darkTextSecondary; 149 | 150 | // primary dark 151 | darkPrimaryLight: $darkPrimaryLight; 152 | darkPrimaryMain: $darkPrimaryMain; 153 | darkPrimaryDark: $darkPrimaryDark; 154 | darkPrimary200: $darkPrimary200; 155 | darkPrimary800: $darkPrimary800; 156 | 157 | // secondary dark 158 | darkSecondaryLight: $darkSecondaryLight; 159 | darkSecondaryMain: $darkSecondaryMain; 160 | darkSecondaryDark: $darkSecondaryDark; 161 | darkSecondary200: $darkSecondary200; 162 | darkSecondary800: $darkSecondary800; 163 | } 164 | -------------------------------------------------------------------------------- /src/themes/palette.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Color intention that you want to used in your theme 3 | * @param {JsonObject} theme Theme customization object 4 | */ 5 | 6 | export default function themePalette(theme) { 7 | return { 8 | mode: theme?.customization?.navType, 9 | common: { 10 | black: theme.colors?.darkPaper, 11 | dark: theme.colors?.darkPrimaryMain 12 | }, 13 | primary: { 14 | light: theme.customization.isDarkMode ? theme.colors?.darkPrimaryLight : theme.colors?.primaryLight, 15 | main: theme.colors?.primaryMain, 16 | dark: theme.customization.isDarkMode ? theme.colors?.darkPrimaryDark : theme.colors?.primaryDark, 17 | 200: theme.customization.isDarkMode ? theme.colors?.darkPrimary200 : theme.colors?.primary200, 18 | 800: theme.customization.isDarkMode ? theme.colors?.darkPrimary800 : theme.colors?.primary800 19 | }, 20 | secondary: { 21 | light: theme.customization.isDarkMode ? theme.colors?.darkSecondaryLight : theme.colors?.secondaryLight, 22 | main: theme.customization.isDarkMode ? theme.colors?.darkSecondaryMain : theme.colors?.secondaryMain, 23 | dark: theme.customization.isDarkMode ? theme.colors?.darkSecondaryDark : theme.colors?.secondaryDark, 24 | iconBg: theme.customization.isDarkMode ? theme.colors?.darkSecondaryLight : theme.colors?.secondaryIcon, 25 | iconHover: theme.customization.isDarkMode ? theme.colors?.darkSecondaryLight : theme.colors?.secondaryIconHover, 26 | iconText: theme.customization.isDarkMode ? theme.colors?.darkSecondaryLight : theme.colors?.secondaryIconText, 27 | 200: theme.colors?.secondary200, 28 | 800: theme.colors?.secondary800 29 | }, 30 | error: { 31 | light: theme.colors?.errorLight, 32 | main: theme.colors?.errorMain, 33 | dark: theme.colors?.errorDark 34 | }, 35 | orange: { 36 | light: theme.colors?.orangeLight, 37 | main: theme.colors?.orangeMain, 38 | dark: theme.colors?.orangeDark 39 | }, 40 | warning: { 41 | light: theme.colors?.warningLight, 42 | main: theme.colors?.warningMain, 43 | dark: theme.colors?.warningDark 44 | }, 45 | success: { 46 | light: theme.colors?.successLight, 47 | 200: theme.colors?.success200, 48 | main: theme.colors?.successMain, 49 | dark: theme.colors?.successDark 50 | }, 51 | grey: { 52 | 50: theme.colors?.grey50, 53 | 100: theme.colors?.grey100, 54 | 200: theme.colors?.grey200, 55 | 300: theme.colors?.grey300, 56 | 500: theme.darkTextSecondary, 57 | 600: theme.heading, 58 | 700: theme.darkTextPrimary, 59 | 900: theme.textDark 60 | }, 61 | dark: { 62 | light: theme.colors?.darkTextPrimary, 63 | main: theme.colors?.darkLevel1, 64 | dark: theme.colors?.darkLevel2, 65 | 800: theme.colors?.darkBackground, 66 | 900: theme.colors?.darkPaper 67 | }, 68 | text: { 69 | primary: theme.darkTextPrimary, 70 | secondary: theme.darkTextSecondary, 71 | dark: theme.textDark, 72 | hint: theme.colors?.grey100 73 | }, 74 | background: { 75 | paper: theme.paper, 76 | default: theme.backgroundDefault 77 | }, 78 | card: { 79 | main: theme.customization.isDarkMode ? theme.colors?.darkPrimaryMain : theme.colors?.paper, 80 | light: theme.customization.isDarkMode ? theme.colors?.darkPrimary200 : theme.colors?.paper, 81 | hover: theme.customization.isDarkMode ? theme.colors?.darkPrimary800 : theme.colors?.paper 82 | }, 83 | asyncSelect: { 84 | main: theme.customization.isDarkMode ? theme.colors?.darkPrimary800 : theme.colors?.grey50 85 | }, 86 | canvasHeader: { 87 | deployLight: theme.colors?.primaryLight, 88 | deployDark: theme.colors?.primaryDark, 89 | saveLight: theme.colors?.secondaryLight, 90 | saveDark: theme.colors?.secondaryDark, 91 | settingsLight: theme.colors?.grey300, 92 | settingsDark: theme.colors?.grey700 93 | }, 94 | codeEditor: { 95 | main: theme.customization.isDarkMode ? theme.colors?.darkPrimary800 : theme.colors?.primaryLight 96 | } 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /src/ui-component/markdown/CodeBlock.js: -------------------------------------------------------------------------------- 1 | import { IconClipboard, IconDownload } from '@tabler/icons' 2 | import { memo, useState } from 'react' 3 | import { Prism as SyntaxHighlighter } from 'react-syntax-highlighter' 4 | import { oneDark } from 'react-syntax-highlighter/dist/esm/styles/prism' 5 | import PropTypes from 'prop-types' 6 | import { Box, IconButton, Popover, Typography } from '@mui/material' 7 | import { useTheme } from '@mui/material/styles' 8 | 9 | const programmingLanguages = { 10 | javascript: '.js', 11 | python: '.py', 12 | java: '.java', 13 | c: '.c', 14 | cpp: '.cpp', 15 | 'c++': '.cpp', 16 | 'c#': '.cs', 17 | ruby: '.rb', 18 | php: '.php', 19 | swift: '.swift', 20 | 'objective-c': '.m', 21 | kotlin: '.kt', 22 | typescript: '.ts', 23 | go: '.go', 24 | perl: '.pl', 25 | rust: '.rs', 26 | scala: '.scala', 27 | haskell: '.hs', 28 | lua: '.lua', 29 | shell: '.sh', 30 | sql: '.sql', 31 | html: '.html', 32 | css: '.css' 33 | } 34 | 35 | export const CodeBlock = memo(({ language, chatflowid, isDialog, value }) => { 36 | const theme = useTheme() 37 | const [anchorEl, setAnchorEl] = useState(null) 38 | const openPopOver = Boolean(anchorEl) 39 | 40 | const handleClosePopOver = () => { 41 | setAnchorEl(null) 42 | } 43 | 44 | const copyToClipboard = (event) => { 45 | if (!navigator.clipboard || !navigator.clipboard.writeText) { 46 | return 47 | } 48 | 49 | navigator.clipboard.writeText(value) 50 | setAnchorEl(event.currentTarget) 51 | setTimeout(() => { 52 | handleClosePopOver() 53 | }, 1500) 54 | } 55 | 56 | const downloadAsFile = () => { 57 | const fileExtension = programmingLanguages[language] || '.file' 58 | const suggestedFileName = `file-${chatflowid}${fileExtension}` 59 | const fileName = suggestedFileName 60 | 61 | if (!fileName) { 62 | // user pressed cancel on prompt 63 | return 64 | } 65 | 66 | const blob = new Blob([value], { type: 'text/plain' }) 67 | const url = URL.createObjectURL(blob) 68 | const link = document.createElement('a') 69 | link.download = fileName 70 | link.href = url 71 | link.style.display = 'none' 72 | document.body.appendChild(link) 73 | link.click() 74 | document.body.removeChild(link) 75 | URL.revokeObjectURL(url) 76 | } 77 | 78 | return ( 79 |
80 | 81 |
82 | {language} 83 |
84 | 85 | 86 | 87 | 100 | 101 | Copied! 102 | 103 | 104 | 105 | 106 | 107 |
108 |
109 | 110 | 111 | {value} 112 | 113 |
114 | ) 115 | }) 116 | CodeBlock.displayName = 'CodeBlock' 117 | 118 | CodeBlock.propTypes = { 119 | language: PropTypes.string, 120 | chatflowid: PropTypes.string, 121 | isDialog: PropTypes.bool, 122 | value: PropTypes.string 123 | } 124 | -------------------------------------------------------------------------------- /src/views/hpcjobs/enhancedtablehead.js: -------------------------------------------------------------------------------- 1 | import TableHead from '@mui/material/TableHead' 2 | import TableRow from '@mui/material/TableRow' 3 | import TableCell from '@mui/material/TableCell' 4 | import Checkbox from '@mui/material/Checkbox' 5 | import TableSortLabel from '@mui/material/TableSortLabel' 6 | import Box from '@mui/material/Box' 7 | import { visuallyHidden } from '@mui/utils' 8 | 9 | export const HeadCells = [ 10 | { 11 | id: 'jobid', 12 | align: 'left', 13 | disablePadding: true, 14 | label: 'Job ID', 15 | sortable: true 16 | }, 17 | { 18 | id: 'partition', 19 | align: 'left', 20 | disablePadding: false, 21 | label: 'Queues', 22 | sortable: true 23 | }, 24 | { 25 | id: 'jobname', 26 | align: 'left', 27 | disablePadding: false, 28 | label: 'Job Name', 29 | sortable: true 30 | }, 31 | { 32 | id: 'status', 33 | align: 'left', 34 | disablePadding: false, 35 | label: 'Job Status', 36 | sortable: false 37 | }, 38 | { 39 | id: 'reason', 40 | align: 'left', 41 | disablePadding: false, 42 | label: 'State Reason', 43 | sortable: false 44 | }, 45 | { 46 | id: 'hostNum', 47 | align: 'center', 48 | disablePadding: false, 49 | label: 'Resource', 50 | sortable: false 51 | }, 52 | { 53 | id: 'submitted', 54 | align: 'center', 55 | disablePadding: false, 56 | label: 'Submission Time', 57 | sortable: true 58 | }, 59 | { 60 | id: 'started', 61 | align: 'center', 62 | disablePadding: false, 63 | label: 'Start Time', 64 | sortable: true 65 | }, 66 | { 67 | id: 'usergroup', 68 | align: 'center', 69 | disablePadding: false, 70 | label: 'Account', 71 | sortable: true 72 | }, 73 | { 74 | id: 'user', 75 | align: 'center', 76 | disablePadding: false, 77 | label: 'Submission User', 78 | sortable: true 79 | } 80 | ] 81 | 82 | const EnhancedTableHead = (props) => { 83 | const { onSelectAllClick, order, orderBy, numSelected, rowCount, onRequestSort } = props 84 | const createSortHandler = (property) => (event) => { 85 | onRequestSort(event, property) 86 | } 87 | 88 | return ( 89 | 90 | 91 | 92 | 0 && numSelected < rowCount} 95 | checked={rowCount > 0 && numSelected === rowCount} 96 | onChange={onSelectAllClick} 97 | inputProps={{ 'aria-label': 'select all desserts' }} 98 | /> 99 | 100 | {HeadCells.map((headCell) => ( 101 | 107 | {headCell.sortable ? ( 108 | 113 | {headCell.label} 114 | {orderBy === headCell.id ? ( 115 | 116 | {order === 'desc' ? 'sorted descending' : 'sorted ascending'} 117 | 118 | ) : null} 119 | 120 | ) : ( 121 | {headCell.label} 122 | )} 123 | 124 | ))} 125 | 126 | 127 | ) 128 | } 129 | 130 | export default EnhancedTableHead 131 | -------------------------------------------------------------------------------- /src/ui-component/dialog/SaveChatflowDialog.js: -------------------------------------------------------------------------------- 1 | import { createPortal } from 'react-dom' 2 | import { useState, useEffect, useContext } from 'react' 3 | import PropTypes from 'prop-types' 4 | 5 | import { Button, Dialog, DialogActions, DialogContent, OutlinedInput, DialogTitle, FormGroup, FormControlLabel, Checkbox, FormLabel, FormControl } from '@mui/material' 6 | import { StyledButton } from 'ui-component/button/StyledButton' 7 | import MetaContext from 'store/context/MetaContext' 8 | 9 | const SaveChatflowDialog = ({ show, dialogProps, onCancel, onConfirm }) => { 10 | const portalElement = document.getElementById('portal') 11 | 12 | const [chatflowName, setChatflowName] = useState('') 13 | const [isReadyToSave, setIsReadyToSave] = useState(false) 14 | const [selected, setSelected] = useState([]) 15 | const [metaData] = useContext(MetaContext) 16 | const [tags, setTags] = useState([]) 17 | 18 | useEffect(() => { 19 | if (dialogProps.data) { 20 | let metatags = metaData.tags 21 | setChatflowName(dialogProps.data.name) 22 | if (dialogProps.data.tags) { 23 | let selectedTemp = dialogProps.data.tags.split(",") 24 | for (const tag of metatags) { 25 | if (selectedTemp.indexOf(tag.label) > -1) { 26 | tag.selected = true 27 | } else { 28 | tag.selected = false 29 | } 30 | } 31 | setSelected(selectedTemp) 32 | } else { 33 | for (const tag of metatags) { 34 | tag.selected = false 35 | } 36 | setSelected([]) 37 | } 38 | setTags(metatags) 39 | } 40 | }, [dialogProps]) 41 | 42 | useEffect(() => { 43 | if (chatflowName) setIsReadyToSave(true) 44 | else setIsReadyToSave(false) 45 | }, [chatflowName]) 46 | 47 | const handleChange = (event, tag) => { 48 | tag.selected = !tag.selected 49 | if (tag.selected) { 50 | selected.push(tag.label) 51 | } else { 52 | let index = selected.indexOf(tag.label) 53 | selected.splice(index, 1); 54 | } 55 | setSelected(selected) 56 | let newObj = Object.assign([], tags); 57 | setTags(newObj) 58 | console.log(selected) 59 | } 60 | 61 | const component = show ? ( 62 | 70 | 71 | {dialogProps.title} 72 | 73 | 74 | setChatflowName(e.target.value)} 82 | /> 83 | 84 | 91 | 标签 92 | 93 | {tags && tags.map((tag, index) => { 94 | return ( handleChange(e, tag)} control={} label={tag.label} />) 95 | })} 96 | 97 | 98 | 99 | 100 | 101 | onConfirm(chatflowName, selected)}> 102 | {dialogProps.confirmButtonName} 103 | 104 | 105 | 106 | ) : null 107 | 108 | return createPortal(component, portalElement) 109 | } 110 | 111 | SaveChatflowDialog.propTypes = { 112 | show: PropTypes.bool, 113 | dialogProps: PropTypes.object, 114 | onCancel: PropTypes.func, 115 | onConfirm: PropTypes.func 116 | } 117 | 118 | export default SaveChatflowDialog 119 | -------------------------------------------------------------------------------- /src/store/context/ReactFlowContext.js: -------------------------------------------------------------------------------- 1 | import { createContext, useState } from 'react' 2 | import PropTypes from 'prop-types' 3 | import { getUniqueNodeId } from 'utils/genericHelper' 4 | import { cloneDeep } from 'lodash' 5 | 6 | const initialValue = { 7 | reactFlowInstance: null, 8 | setReactFlowInstance: () => {}, 9 | duplicateNode: () => {}, 10 | deleteNode: () => {}, 11 | deleteEdge: () => {} 12 | } 13 | 14 | export const flowContext = createContext(initialValue) 15 | 16 | export const ReactFlowContext = ({ children }) => { 17 | const [reactFlowInstance, setReactFlowInstance] = useState(null) 18 | 19 | const deleteNode = (nodeid) => { 20 | deleteConnectedInput(nodeid, 'node') 21 | reactFlowInstance.setNodes(reactFlowInstance.getNodes().filter((n) => n.id !== nodeid)) 22 | reactFlowInstance.setEdges(reactFlowInstance.getEdges().filter((ns) => ns.source !== nodeid && ns.target !== nodeid)) 23 | } 24 | 25 | const deleteEdge = (edgeid) => { 26 | deleteConnectedInput(edgeid, 'edge') 27 | reactFlowInstance.setEdges(reactFlowInstance.getEdges().filter((edge) => edge.id !== edgeid)) 28 | } 29 | 30 | const deleteConnectedInput = (id, type) => { 31 | const connectedEdges = 32 | type === 'node' 33 | ? reactFlowInstance.getEdges().filter((edge) => edge.source === id) 34 | : reactFlowInstance.getEdges().filter((edge) => edge.id === id) 35 | 36 | for (const edge of connectedEdges) { 37 | const targetNodeId = edge.target 38 | const sourceNodeId = edge.source 39 | const targetInput = edge.targetHandle.split('-')[2] 40 | 41 | reactFlowInstance.setNodes((nds) => 42 | nds.map((node) => { 43 | if (node.id === targetNodeId) { 44 | let value 45 | const inputAnchor = node.data.inputAnchors.find((ancr) => ancr.name === targetInput) 46 | const inputParam = node.data.inputParams.find((param) => param.name === targetInput) 47 | 48 | if (inputAnchor && inputAnchor.list) { 49 | const values = node.data.inputs[targetInput] || [] 50 | value = values.filter((item) => !item.includes(sourceNodeId)) 51 | } else if (inputParam && inputParam.acceptVariable) { 52 | value = node.data.inputs[targetInput].replace(`{{${sourceNodeId}.data.instance}}`, '') || '' 53 | } else { 54 | value = '' 55 | } 56 | node.data = { 57 | ...node.data, 58 | inputs: { 59 | ...node.data.inputs, 60 | [targetInput]: value 61 | } 62 | } 63 | } 64 | return node 65 | }) 66 | ) 67 | } 68 | } 69 | 70 | const duplicateNode = (id) => { 71 | const nodes = reactFlowInstance.getNodes() 72 | const originalNode = nodes.find((n) => n.id === id) 73 | if (originalNode) { 74 | const newNodeId = getUniqueNodeId(originalNode.data, nodes) 75 | const clonedNode = cloneDeep(originalNode) 76 | 77 | const duplicatedNode = { 78 | ...clonedNode, 79 | id: newNodeId, 80 | position: { 81 | x: clonedNode.position.x + 400, 82 | y: clonedNode.position.y 83 | }, 84 | positionAbsolute: { 85 | x: clonedNode.positionAbsolute.x + 400, 86 | y: clonedNode.positionAbsolute.y 87 | }, 88 | data: { 89 | ...clonedNode.data, 90 | id: newNodeId 91 | }, 92 | selected: false 93 | } 94 | 95 | const dataKeys = ['inputParams', 'inputAnchors', 'outputAnchors'] 96 | 97 | for (const key of dataKeys) { 98 | for (const item of duplicatedNode.data[key]) { 99 | if (item.id) { 100 | item.id = item.id.replace(id, newNodeId) 101 | } 102 | } 103 | } 104 | 105 | reactFlowInstance.setNodes([...nodes, duplicatedNode]) 106 | } 107 | } 108 | 109 | return ( 110 | 119 | {children} 120 | 121 | ) 122 | } 123 | 124 | ReactFlowContext.propTypes = { 125 | children: PropTypes.any 126 | } 127 | -------------------------------------------------------------------------------- /src/menu-items/dashboard.js: -------------------------------------------------------------------------------- 1 | import Cookies from 'js-cookie' 2 | // assets 3 | import LineStyleOutlinedIcon from '@mui/icons-material/LineStyleOutlined' 4 | import ManageAccountsIcon from '@mui/icons-material/ManageAccounts' 5 | import GroupsIcon from '@mui/icons-material/Groups' 6 | import AccountBoxIcon from '@mui/icons-material/AccountBox' 7 | import SocialDistanceIcon from '@mui/icons-material/SocialDistance' 8 | import StorageIcon from '@mui/icons-material/Storage' 9 | import FitbitIcon from '@mui/icons-material/Fitbit' 10 | import AccountCircleIcon from '@mui/icons-material/AccountCircle' 11 | import { 12 | IconBabyCarriage, 13 | IconBuildingFactory2, 14 | IconBuildingStore, 15 | IconContainer, 16 | IconCpu, 17 | IconHierarchy, 18 | IconKey, 19 | IconTool, 20 | IconServer2, 21 | IconSettingsAutomation 22 | } from '@tabler/icons' 23 | // constant 24 | const icons = { 25 | IconCpu, 26 | IconContainer, 27 | IconHierarchy, 28 | IconBuildingStore, 29 | IconKey, 30 | IconTool, 31 | IconBuildingFactory2, 32 | IconBabyCarriage, 33 | LineStyleOutlinedIcon, 34 | ManageAccountsIcon, 35 | GroupsIcon, 36 | AccountBoxIcon, 37 | SocialDistanceIcon, 38 | IconServer2, 39 | StorageIcon, 40 | FitbitIcon, 41 | IconSettingsAutomation, 42 | AccountCircleIcon 43 | } 44 | import config from 'config' 45 | 46 | // ==============================|| DASHBOARD MENU ITEMS ||============================== // 47 | 48 | const dashboard = () => { 49 | let userInfo = JSON.parse(localStorage.getItem('userinfos')) 50 | let isAdmin = userInfo.isAdmin 51 | let db = { 52 | id: 'dashboard', 53 | title: '', 54 | type: 'group', 55 | children: [ 56 | { 57 | id: 'dashboard', 58 | title: 'Dashboard', 59 | type: 'item', 60 | url: '/dashboard', 61 | icon: icons.LineStyleOutlinedIcon, 62 | breadcrumbs: true 63 | }, 64 | { 65 | id: 'resources', 66 | title: 'Resources', 67 | type: 'item', 68 | url: '/resources', 69 | icon: icons.StorageIcon, 70 | breadcrumbs: true 71 | }, 72 | { 73 | id: 'workload', 74 | title: 'Tasks', 75 | type: 'collapse', 76 | url: '', 77 | icon: icons.FitbitIcon, 78 | breadcrumbs: true, 79 | children: [ 80 | { 81 | id: 'hpcjobs', 82 | title: 'Jobs', 83 | type: 'item', 84 | url: '/hpcjobs', 85 | icon: icons.IconCpu, 86 | breadcrumbs: true 87 | }, 88 | { 89 | id: 'k8sjobs', 90 | title: 'Services', 91 | type: 'item', 92 | url: '/k8sjobs', 93 | icon: icons.IconContainer, 94 | breadcrumbs: true, 95 | hide: !isAdmin || !config.FEATURE_TOGGLE_K8S 96 | } 97 | ] 98 | }, 99 | { 100 | id: 'tools', 101 | title: 'Partitions', 102 | type: 'item', 103 | url: '/jobqueue', 104 | icon: AccountCircleIcon, 105 | breadcrumbs: true 106 | }, 107 | { 108 | id: 'users', 109 | title: 'Organizations', 110 | type: 'collapse', 111 | icon: icons.ManageAccountsIcon, 112 | breadcrumbs: true, 113 | hide: !isAdmin, 114 | children: [ 115 | { 116 | id: 'orgmanage', 117 | title: 'Organization', 118 | type: 'item', 119 | url: '/orgmanage', 120 | icon: icons.GroupsIcon, 121 | breadcrumbs: true 122 | }, 123 | { 124 | id: 'usermanage', 125 | title: 'Members', 126 | type: 'item', 127 | url: '/usermanage', 128 | icon: icons.AccountBoxIcon, 129 | breadcrumbs: true 130 | }, 131 | { 132 | id: 'qosmanage', 133 | title: 'QOS', 134 | type: 'item', 135 | url: '/qosmanage', 136 | icon: icons.IconSettingsAutomation, 137 | breadcrumbs: true 138 | } 139 | ] 140 | } 141 | ] 142 | } 143 | let res = { 144 | ...db 145 | } 146 | return res 147 | } 148 | 149 | export default dashboard 150 | -------------------------------------------------------------------------------- /src/layout/MainLayout/Sidebar/MenuList/NavCollapse/index.js: -------------------------------------------------------------------------------- 1 | import PropTypes from 'prop-types' 2 | import { useState } from 'react' 3 | import { useSelector } from 'react-redux' 4 | 5 | // material-ui 6 | import { useTheme } from '@mui/material/styles' 7 | import { Collapse, List, ListItemButton, ListItemIcon, ListItemText, Typography } from '@mui/material' 8 | 9 | // project imports 10 | import NavItem from '../NavItem' 11 | 12 | // assets 13 | import FiberManualRecordIcon from '@mui/icons-material/FiberManualRecord' 14 | import { IconChevronDown, IconChevronUp } from '@tabler/icons' 15 | 16 | // ==============================|| SIDEBAR MENU LIST COLLAPSE ITEMS ||============================== // 17 | 18 | const NavCollapse = ({ menu, level }) => { 19 | const theme = useTheme() 20 | const customization = useSelector((state) => state.customization) 21 | 22 | const [open, setOpen] = useState(false) 23 | // const [selected, setSelected] = useState(null) 24 | 25 | const handleClick = () => { 26 | setOpen(!open) 27 | // setSelected(!selected ? menu.id : null) 28 | } 29 | 30 | // menu collapse & item 31 | const menus = menu.children 32 | ?.filter((item) => { 33 | if (Object.hasOwn(item, 'hide') && item.hide) { 34 | return false 35 | } 36 | return true 37 | }) 38 | .map((item) => { 39 | switch (item.type) { 40 | case 'collapse': 41 | return 42 | case 'item': 43 | return 44 | default: 45 | return ( 46 | 47 | Menu Items Error 48 | 49 | ) 50 | } 51 | }) 52 | 53 | const Icon = menu.icon 54 | const menuIcon = menu.icon ? ( 55 | 56 | ) : ( 57 | 0 ? 'inherit' : 'medium'} 63 | /> 64 | ) 65 | 66 | return ( 67 | <> 68 | 1 ? 'transparent !important' : 'inherit', 74 | py: level > 1 ? 1 : 1.25, 75 | pl: `${level * 24}px` 76 | }} 77 | // selected={selected === menu.id} 78 | onClick={handleClick} 79 | > 80 | {menuIcon} 81 | 84 | {menu.title} 85 | 86 | } 87 | secondary={ 88 | menu.caption && ( 89 | 90 | {menu.caption} 91 | 92 | ) 93 | } 94 | /> 95 | {open ? ( 96 | 97 | ) : ( 98 | 99 | )} 100 | 101 | 102 | 119 | {menus} 120 | 121 | 122 | 123 | ) 124 | } 125 | 126 | NavCollapse.propTypes = { 127 | menu: PropTypes.object, 128 | level: PropTypes.number 129 | } 130 | 131 | export default NavCollapse 132 | -------------------------------------------------------------------------------- /src/layout/MainLayout/Sidebar/MenuList/NavItem/index.js: -------------------------------------------------------------------------------- 1 | import PropTypes from 'prop-types' 2 | import { forwardRef, useEffect } from 'react' 3 | import { Link } from 'react-router-dom' 4 | import { useDispatch, useSelector } from 'react-redux' 5 | 6 | // material-ui 7 | import { useTheme } from '@mui/material/styles' 8 | import { Avatar, Chip, ListItemButton, ListItemIcon, ListItemText, Typography, useMediaQuery } from '@mui/material' 9 | 10 | // project imports 11 | import { MENU_OPEN, SET_MENU } from 'store/actions' 12 | import config from 'config' 13 | 14 | // assets 15 | import FiberManualRecordIcon from '@mui/icons-material/FiberManualRecord' 16 | 17 | // ==============================|| SIDEBAR MENU LIST ITEMS ||============================== // 18 | 19 | const NavItem = ({ item, level, navType, onClick, onUploadFile }) => { 20 | const theme = useTheme() 21 | const dispatch = useDispatch() 22 | const customization = useSelector((state) => state.customization) 23 | const matchesSM = useMediaQuery(theme.breakpoints.down('lg')) 24 | 25 | const Icon = item.icon 26 | const itemIcon = item?.icon ? ( 27 | 28 | ) : ( 29 | id === item?.id) > -1 ? 8 : 6, 32 | height: customization.isOpen.findIndex((id) => id === item?.id) > -1 ? 8 : 6 33 | }} 34 | fontSize={level > 0 ? 'inherit' : 'medium'} 35 | /> 36 | ) 37 | 38 | let itemTarget = '_self' 39 | if (item.target) { 40 | itemTarget = '_blank' 41 | } 42 | 43 | let listItemProps = { 44 | component: forwardRef(function ListItemPropsComponent(props, ref) { 45 | return 46 | }) 47 | } 48 | if (item?.external) { 49 | listItemProps = { component: 'a', href: item.url, target: itemTarget } 50 | } 51 | if (item?.id === 'loadChatflow') { 52 | listItemProps.component = 'label' 53 | } 54 | 55 | const handleFileUpload = (e) => { 56 | if (!e.target.files) return 57 | 58 | const file = e.target.files[0] 59 | 60 | const reader = new FileReader() 61 | reader.onload = (evt) => { 62 | if (!evt?.target?.result) { 63 | return 64 | } 65 | const { result } = evt.target 66 | onUploadFile(result) 67 | } 68 | reader.readAsText(file) 69 | } 70 | 71 | const itemHandler = (id) => { 72 | if (navType === 'SETTINGS' && id !== 'loadChatflow') { 73 | onClick(id) 74 | } else { 75 | dispatch({ type: MENU_OPEN, id }) 76 | if (matchesSM) dispatch({ type: SET_MENU, opened: false }) 77 | } 78 | } 79 | 80 | // active menu item on page load 81 | useEffect(() => { 82 | if (navType === 'MENU') { 83 | const currentIndex = document.location.pathname 84 | .toString() 85 | .split('/') 86 | .findIndex((id) => id === item.id) 87 | if (currentIndex > -1) { 88 | dispatch({ type: MENU_OPEN, id: item.id }) 89 | } 90 | } 91 | }, [navType]) 92 | 93 | return ( 94 | 1 ? 'inherit' : 'inherit', 102 | py: level > 1 ? 1 : 1.25, 103 | pl: `${level * 24}px` 104 | }} 105 | selected={customization.isOpen.findIndex((id) => id === item.id) > -1} 106 | onClick={() => itemHandler(item.id)} 107 | > 108 | {itemIcon} 109 | id === item.id) > -1 ? 'h5' : 'body1'} color='inherit'> 112 | {item.title} 113 | 114 | } 115 | secondary={ 116 | item.caption && ( 117 | 118 | {item.caption} 119 | 120 | ) 121 | } 122 | /> 123 | {item.chip && ( 124 | {item.chip.avatar}} 130 | /> 131 | )} 132 | 133 | ) 134 | } 135 | 136 | NavItem.propTypes = { 137 | item: PropTypes.object, 138 | level: PropTypes.number, 139 | navType: PropTypes.string, 140 | onClick: PropTypes.func, 141 | onUploadFile: PropTypes.func 142 | } 143 | 144 | export default NavItem 145 | --------------------------------------------------------------------------------