mirror of
https://github.com/sasjs/server.git
synced 2026-04-10 15:43:14 +00:00
chore: rename Web folder to web
This commit is contained in:
0
web/src/App.css
Normal file
0
web/src/App.css
Normal file
9
web/src/App.test.tsx
Normal file
9
web/src/App.test.tsx
Normal file
@@ -0,0 +1,9 @@
|
||||
import React from 'react';
|
||||
import { render, screen } from '@testing-library/react';
|
||||
import App from './App';
|
||||
|
||||
test('renders learn react link', () => {
|
||||
render(<App />);
|
||||
const linkElement = screen.getByText(/learn react/i);
|
||||
expect(linkElement).toBeInTheDocument();
|
||||
});
|
||||
31
web/src/App.tsx
Normal file
31
web/src/App.tsx
Normal file
@@ -0,0 +1,31 @@
|
||||
import React from 'react'
|
||||
import { Route, BrowserRouter, Switch, Redirect } from 'react-router-dom'
|
||||
import { ThemeProvider } from '@mui/material/styles'
|
||||
import { theme } from './theme'
|
||||
|
||||
import Header from './components/header'
|
||||
import Home from './components/home'
|
||||
import SASjsDrive from './containers/SASjsDrive'
|
||||
import SASStudio from './containers/SASjsStudio'
|
||||
function App() {
|
||||
return (
|
||||
<ThemeProvider theme={theme}>
|
||||
<BrowserRouter>
|
||||
<Header />
|
||||
<Switch>
|
||||
<Route exact path="/">
|
||||
<Home />
|
||||
</Route>
|
||||
<Route exact path="/SASjsDrive">
|
||||
<SASjsDrive />
|
||||
</Route>
|
||||
<Route exact path="/SASjsStudio">
|
||||
<SASStudio />
|
||||
</Route>
|
||||
</Switch>
|
||||
</BrowserRouter>
|
||||
</ThemeProvider>
|
||||
)
|
||||
}
|
||||
|
||||
export default App
|
||||
49
web/src/components/header.tsx
Normal file
49
web/src/components/header.tsx
Normal file
@@ -0,0 +1,49 @@
|
||||
import React, { useState } from 'react'
|
||||
import { Link, useHistory, useLocation } from 'react-router-dom'
|
||||
|
||||
import AppBar from '@mui/material/AppBar'
|
||||
import Toolbar from '@mui/material/Toolbar'
|
||||
import Tabs from '@mui/material/Tabs'
|
||||
import Tab from '@mui/material/Tab'
|
||||
|
||||
const Header = (props: any) => {
|
||||
const history = useHistory()
|
||||
const [tabValue, setTabValue] = useState(0)
|
||||
|
||||
const handleTabChange = (event: React.SyntheticEvent, value: number) => {
|
||||
setTabValue(value)
|
||||
}
|
||||
return (
|
||||
<AppBar
|
||||
position="fixed"
|
||||
sx={{ zIndex: (theme) => theme.zIndex.drawer + 1 }}
|
||||
>
|
||||
<Toolbar variant="dense">
|
||||
<img
|
||||
src="logo-white.png"
|
||||
alt="logo"
|
||||
style={{
|
||||
width: '50px',
|
||||
cursor: 'pointer',
|
||||
marginRight: '25px'
|
||||
}}
|
||||
onClick={() => {
|
||||
setTabValue(0)
|
||||
history.push('/')
|
||||
}}
|
||||
/>
|
||||
<Tabs
|
||||
indicatorColor="secondary"
|
||||
value={tabValue}
|
||||
onChange={handleTabChange}
|
||||
>
|
||||
<Tab label="Home" to="/" component={Link} />
|
||||
<Tab label="SASjs Drive" to="/SASjsDrive" component={Link} />
|
||||
<Tab label="SAS Studio" to="/SASjsStudio" component={Link} />
|
||||
</Tabs>
|
||||
</Toolbar>
|
||||
</AppBar>
|
||||
)
|
||||
}
|
||||
|
||||
export default Header
|
||||
15
web/src/components/home.tsx
Normal file
15
web/src/components/home.tsx
Normal file
@@ -0,0 +1,15 @@
|
||||
import React from 'react'
|
||||
|
||||
import CssBaseline from '@mui/material/CssBaseline'
|
||||
import Box from '@mui/material/Box'
|
||||
|
||||
const Home = () => {
|
||||
return (
|
||||
<Box sx={{ display: 'flex' }} className="main">
|
||||
<CssBaseline />
|
||||
<h2>this is home component</h2>
|
||||
</Box>
|
||||
)
|
||||
}
|
||||
|
||||
export default Home
|
||||
21
web/src/containers/SASjsDrive/index.tsx
Normal file
21
web/src/containers/SASjsDrive/index.tsx
Normal file
@@ -0,0 +1,21 @@
|
||||
import React, { useState } from 'react'
|
||||
|
||||
import CssBaseline from '@mui/material/CssBaseline'
|
||||
import Box from '@mui/material/Box'
|
||||
|
||||
import Header from '../../components/header'
|
||||
import SideBar from './sideBar'
|
||||
import Main from './main'
|
||||
|
||||
const SASjsDrive = () => {
|
||||
const [selectedFilePath, setSelectedFilePath] = useState('')
|
||||
return (
|
||||
<Box sx={{ display: 'flex' }}>
|
||||
<CssBaseline />
|
||||
<SideBar setSelectedFilePath={setSelectedFilePath} />
|
||||
<Main selectedFilePath={selectedFilePath} />
|
||||
</Box>
|
||||
)
|
||||
}
|
||||
|
||||
export default SASjsDrive
|
||||
121
web/src/containers/SASjsDrive/main.tsx
Normal file
121
web/src/containers/SASjsDrive/main.tsx
Normal file
@@ -0,0 +1,121 @@
|
||||
import React, { useState, useEffect, useRef } from 'react'
|
||||
import axios from 'axios'
|
||||
|
||||
import Editor from '@monaco-editor/react'
|
||||
|
||||
import Box from '@mui/material/Box'
|
||||
import Paper from '@mui/material/Paper'
|
||||
import Stack from '@mui/material/Stack'
|
||||
import Button from '@mui/material/Button'
|
||||
import Toolbar from '@mui/material/Toolbar'
|
||||
import CircularProgress from '@mui/material/CircularProgress'
|
||||
|
||||
const Main = (props: any) => {
|
||||
const baseUrl = window.location.origin
|
||||
|
||||
const [isLoading, setIsLoading] = useState(false)
|
||||
const [fileContentBeforeEdit, setFileContentBeforeEdit] = useState('')
|
||||
const [fileContent, setFileContent] = useState('')
|
||||
const [editMode, setEditMode] = useState(false)
|
||||
|
||||
useEffect(() => {
|
||||
if (props.selectedFilePath !== '') {
|
||||
setIsLoading(true)
|
||||
axios
|
||||
.get(`${baseUrl}/SASjsApi/files?filePath=${props.selectedFilePath}`)
|
||||
.then((res: any) => {
|
||||
setIsLoading(false)
|
||||
setFileContent(res.data.fileContent)
|
||||
})
|
||||
}
|
||||
}, [props.selectedFilePath])
|
||||
|
||||
const handleEditSaveBtnClick = () => {
|
||||
if (!editMode) {
|
||||
setFileContentBeforeEdit(fileContent)
|
||||
setEditMode(true)
|
||||
} else {
|
||||
setIsLoading(true)
|
||||
axios
|
||||
.post(`${baseUrl}/SASjsApi/files`, {
|
||||
filePath: props.selectedFilePath,
|
||||
fileContent: fileContent
|
||||
})
|
||||
.then((res) => {
|
||||
setIsLoading(false)
|
||||
setEditMode(false)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
const handleCancelExecuteBtnClick = () => {
|
||||
if (editMode) {
|
||||
setFileContent(fileContentBeforeEdit)
|
||||
setEditMode(false)
|
||||
} else {
|
||||
setIsLoading(true)
|
||||
axios
|
||||
.get(`${baseUrl}/SASjsExecutor/do?_program=${props.selectedFilePath}`)
|
||||
.then((res) => {
|
||||
setIsLoading(false)
|
||||
setEditMode(false)
|
||||
console.log(res)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<Box component="main" sx={{ flexGrow: 1, p: 3 }}>
|
||||
<Toolbar />
|
||||
<Paper
|
||||
sx={{
|
||||
height: '75vh',
|
||||
padding: '10px',
|
||||
overflow: 'auto',
|
||||
position: 'relative'
|
||||
}}
|
||||
elevation={3}
|
||||
>
|
||||
{isLoading && (
|
||||
<CircularProgress
|
||||
style={{ position: 'absolute', left: '50%', top: '50%' }}
|
||||
/>
|
||||
)}
|
||||
{!isLoading && props?.selectedFilePath !== '' && !editMode && (
|
||||
<code style={{ whiteSpace: 'break-spaces' }}>{fileContent}</code>
|
||||
)}
|
||||
{!isLoading && props?.selectedFilePath !== '' && editMode && (
|
||||
<Editor
|
||||
height="95%"
|
||||
value={fileContent}
|
||||
onChange={(val) => {
|
||||
if (val) setFileContent(val)
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</Paper>
|
||||
<Stack
|
||||
spacing={3}
|
||||
direction="row"
|
||||
sx={{ justifyContent: 'center', marginTop: '20px' }}
|
||||
>
|
||||
<Button
|
||||
variant="contained"
|
||||
onClick={handleEditSaveBtnClick}
|
||||
disabled={isLoading || props?.selectedFilePath === ''}
|
||||
>
|
||||
{!editMode ? 'Edit' : 'Save'}
|
||||
</Button>
|
||||
<Button
|
||||
variant="contained"
|
||||
onClick={handleCancelExecuteBtnClick}
|
||||
disabled={isLoading || props?.selectedFilePath === ''}
|
||||
>
|
||||
{editMode ? 'Cancel' : 'Execute'}
|
||||
</Button>
|
||||
</Stack>
|
||||
</Box>
|
||||
)
|
||||
}
|
||||
|
||||
export default Main
|
||||
105
web/src/containers/SASjsDrive/sideBar.tsx
Normal file
105
web/src/containers/SASjsDrive/sideBar.tsx
Normal file
@@ -0,0 +1,105 @@
|
||||
import React, { useState, useEffect } from 'react'
|
||||
import axios from 'axios'
|
||||
|
||||
import { makeStyles } from '@mui/styles'
|
||||
|
||||
import Box from '@mui/material/Box'
|
||||
import Drawer from '@mui/material/Drawer'
|
||||
import Toolbar from '@mui/material/Toolbar'
|
||||
import ListItem from '@mui/material/ListItem'
|
||||
import ListItemText from '@mui/material/ListItemText'
|
||||
|
||||
import TreeView from '@mui/lab/TreeView'
|
||||
import TreeItem from '@mui/lab/TreeItem'
|
||||
|
||||
import ExpandMoreIcon from '@mui/icons-material/ExpandMore'
|
||||
import ChevronRightIcon from '@mui/icons-material/ChevronRight'
|
||||
|
||||
interface TreeNode {
|
||||
name: string
|
||||
relativePath: string
|
||||
absolutePath: string
|
||||
children: Array<TreeNode>
|
||||
}
|
||||
|
||||
const useStyles = makeStyles(() => ({
|
||||
root: {
|
||||
'& .MuiTreeItem-content': {
|
||||
width: 'auto'
|
||||
}
|
||||
},
|
||||
listItem: {
|
||||
padding: 0
|
||||
}
|
||||
}))
|
||||
|
||||
const drawerWidth = 240
|
||||
|
||||
const SideBar = (props: any) => {
|
||||
const baseUrl = window.location.origin
|
||||
const classes = useStyles()
|
||||
|
||||
const [directoryData, setDirectoryData] = useState<TreeNode | null>(null)
|
||||
|
||||
useEffect(() => {
|
||||
axios.get(`${baseUrl}/SASjsApi/executor`).then((res: any) => {
|
||||
if (res.data && res.data?.status === 'success') {
|
||||
setDirectoryData(res.data.tree)
|
||||
}
|
||||
})
|
||||
}, [])
|
||||
|
||||
const handleSelect = (node: TreeNode) => {
|
||||
if (!node.children.length) {
|
||||
window.history.pushState(
|
||||
'',
|
||||
'',
|
||||
`${baseUrl}/SASjsDrive?filepath=${node.relativePath}`
|
||||
)
|
||||
props.setSelectedFilePath(node.relativePath)
|
||||
}
|
||||
}
|
||||
|
||||
const renderTree = (nodes: TreeNode) => (
|
||||
<TreeItem
|
||||
classes={{ root: classes.root }}
|
||||
key={nodes.relativePath}
|
||||
nodeId={nodes.relativePath}
|
||||
label={
|
||||
<ListItem
|
||||
className={classes.listItem}
|
||||
onClick={() => handleSelect(nodes)}
|
||||
>
|
||||
<ListItemText primary={nodes.name} />
|
||||
</ListItem>
|
||||
}
|
||||
>
|
||||
{Array.isArray(nodes.children)
|
||||
? nodes.children.map((node) => renderTree(node))
|
||||
: null}
|
||||
</TreeItem>
|
||||
)
|
||||
|
||||
return (
|
||||
<Drawer
|
||||
variant="permanent"
|
||||
sx={{
|
||||
width: drawerWidth,
|
||||
flexShrink: 0,
|
||||
[`& .MuiDrawer-paper`]: { width: drawerWidth, boxSizing: 'border-box' }
|
||||
}}
|
||||
>
|
||||
<Toolbar />
|
||||
<Box sx={{ overflow: 'auto' }}>
|
||||
<TreeView
|
||||
defaultCollapseIcon={<ExpandMoreIcon />}
|
||||
defaultExpandIcon={<ChevronRightIcon />}
|
||||
>
|
||||
{directoryData && renderTree(directoryData)}
|
||||
</TreeView>
|
||||
</Box>
|
||||
</Drawer>
|
||||
)
|
||||
}
|
||||
|
||||
export default SideBar
|
||||
15
web/src/containers/SASjsStudio/index.tsx
Normal file
15
web/src/containers/SASjsStudio/index.tsx
Normal file
@@ -0,0 +1,15 @@
|
||||
import React from 'react'
|
||||
|
||||
import CssBaseline from '@mui/material/CssBaseline'
|
||||
import Box from '@mui/material/Box'
|
||||
|
||||
const SASjsStudio = () => {
|
||||
return (
|
||||
<Box sx={{ display: 'flex' }} className="main">
|
||||
<CssBaseline />
|
||||
<h2>This is container for SAS studio</h2>
|
||||
</Box>
|
||||
)
|
||||
}
|
||||
|
||||
export default SASjsStudio
|
||||
17
web/src/index.css
Normal file
17
web/src/index.css
Normal file
@@ -0,0 +1,17 @@
|
||||
body {
|
||||
margin: 0;
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
|
||||
'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
|
||||
sans-serif;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
}
|
||||
|
||||
code {
|
||||
font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New',
|
||||
monospace;
|
||||
}
|
||||
|
||||
.main {
|
||||
margin-top: 50px;
|
||||
}
|
||||
17
web/src/index.tsx
Normal file
17
web/src/index.tsx
Normal file
@@ -0,0 +1,17 @@
|
||||
import React from 'react'
|
||||
import ReactDOM from 'react-dom'
|
||||
import './index.css'
|
||||
import App from './App'
|
||||
import reportWebVitals from './reportWebVitals'
|
||||
|
||||
ReactDOM.render(
|
||||
<React.StrictMode>
|
||||
<App />
|
||||
</React.StrictMode>,
|
||||
document.getElementById('root')
|
||||
)
|
||||
|
||||
// If you want to start measuring performance in your app, pass a function
|
||||
// to log results (for example: reportWebVitals(console.log))
|
||||
// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
|
||||
reportWebVitals()
|
||||
1
web/src/react-app-env.d.ts
vendored
Normal file
1
web/src/react-app-env.d.ts
vendored
Normal file
@@ -0,0 +1 @@
|
||||
/// <reference types="react-scripts" />
|
||||
15
web/src/reportWebVitals.ts
Normal file
15
web/src/reportWebVitals.ts
Normal file
@@ -0,0 +1,15 @@
|
||||
import { ReportHandler } from 'web-vitals';
|
||||
|
||||
const reportWebVitals = (onPerfEntry?: ReportHandler) => {
|
||||
if (onPerfEntry && onPerfEntry instanceof Function) {
|
||||
import('web-vitals').then(({ getCLS, getFID, getFCP, getLCP, getTTFB }) => {
|
||||
getCLS(onPerfEntry);
|
||||
getFID(onPerfEntry);
|
||||
getFCP(onPerfEntry);
|
||||
getLCP(onPerfEntry);
|
||||
getTTFB(onPerfEntry);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
export default reportWebVitals;
|
||||
5
web/src/setupTests.ts
Normal file
5
web/src/setupTests.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
// jest-dom adds custom jest matchers for asserting on DOM nodes.
|
||||
// allows you to do things like:
|
||||
// expect(element).toHaveTextContent(/react/i)
|
||||
// learn more: https://github.com/testing-library/jest-dom
|
||||
import '@testing-library/jest-dom';
|
||||
19
web/src/theme/index.js
Normal file
19
web/src/theme/index.js
Normal file
@@ -0,0 +1,19 @@
|
||||
import { createTheme } from '@mui/material/styles'
|
||||
import palette from './palette'
|
||||
|
||||
export const theme = createTheme({
|
||||
palette,
|
||||
components: {
|
||||
MuiTab: {
|
||||
styleOverrides: {
|
||||
root: {
|
||||
fontSize: '21px',
|
||||
color: palette.white,
|
||||
'&.Mui-selected': {
|
||||
color: palette.secondary.main
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
55
web/src/theme/palette.js
Normal file
55
web/src/theme/palette.js
Normal file
@@ -0,0 +1,55 @@
|
||||
import { colors } from '@mui/material'
|
||||
|
||||
const white = '#FFFFFF'
|
||||
const black = '#000000'
|
||||
const yellow = '#F6E30F'
|
||||
|
||||
const palette = {
|
||||
black,
|
||||
white,
|
||||
primary: {
|
||||
contrastText: white,
|
||||
main: black
|
||||
},
|
||||
secondary: {
|
||||
contrastText: white,
|
||||
main: yellow
|
||||
},
|
||||
success: {
|
||||
contrastText: white,
|
||||
dark: colors.green[900],
|
||||
main: colors.green[600],
|
||||
light: colors.green[400]
|
||||
},
|
||||
info: {
|
||||
contrastText: white,
|
||||
dark: colors.blue[900],
|
||||
main: colors.blue[600],
|
||||
light: colors.blue[400]
|
||||
},
|
||||
warning: {
|
||||
contrastText: white,
|
||||
dark: colors.orange[900],
|
||||
main: colors.orange[600],
|
||||
light: colors.orange[400]
|
||||
},
|
||||
error: {
|
||||
contrastText: white,
|
||||
dark: colors.red[900],
|
||||
main: colors.red[600],
|
||||
light: colors.red[400]
|
||||
},
|
||||
text: {
|
||||
primary: colors.blueGrey[900],
|
||||
secondary: colors.blueGrey[600],
|
||||
link: colors.blue[600]
|
||||
},
|
||||
background: {
|
||||
default: '#F4F6F8',
|
||||
paper: white
|
||||
},
|
||||
icon: colors.blueGrey[600],
|
||||
divider: colors.grey[200]
|
||||
}
|
||||
|
||||
export default palette
|
||||
Reference in New Issue
Block a user