mirror of
https://github.com/sasjs/server.git
synced 2025-12-15 21:14:35 +00:00
Merge branch 'main' into issue-139
This commit is contained in:
@@ -1 +1 @@
|
||||
v16.14.0
|
||||
v16.15.1
|
||||
463
web/package-lock.json
generated
463
web/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -9,7 +9,6 @@
|
||||
"dependencies": {
|
||||
"@emotion/react": "^11.4.1",
|
||||
"@emotion/styled": "^11.3.0",
|
||||
"@monaco-editor/react": "^4.3.1",
|
||||
"@mui/icons-material": "^5.0.3",
|
||||
"@mui/lab": "^5.0.0-alpha.50",
|
||||
"@mui/material": "^5.0.3",
|
||||
@@ -21,9 +20,14 @@
|
||||
"@types/node": "^12.20.28",
|
||||
"@types/react": "^17.0.27",
|
||||
"axios": "^0.24.0",
|
||||
"monaco-editor": "^0.33.0",
|
||||
"monaco-editor-webpack-plugin": "^7.0.1",
|
||||
"react": "^17.0.2",
|
||||
"react-copy-to-clipboard": "^5.1.0",
|
||||
"react-dom": "^17.0.2",
|
||||
"react-router-dom": "^5.3.0"
|
||||
"react-monaco-editor": "^0.48.0",
|
||||
"react-router-dom": "^5.3.0",
|
||||
"react-toastify": "^9.0.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.16.0",
|
||||
@@ -35,6 +39,7 @@
|
||||
"@types/dotenv-webpack": "^7.0.3",
|
||||
"@types/prismjs": "^1.16.6",
|
||||
"@types/react": "^17.0.37",
|
||||
"@types/react-copy-to-clipboard": "^5.0.2",
|
||||
"@types/react-dom": "^17.0.11",
|
||||
"@types/react-router-dom": "^5.3.1",
|
||||
"babel-loader": "^8.2.3",
|
||||
|
||||
@@ -11,6 +11,8 @@ import Studio from './containers/Studio'
|
||||
import Settings from './containers/Settings'
|
||||
|
||||
import { AppContext } from './context/appContext'
|
||||
import AuthCode from './containers/AuthCode'
|
||||
import { ToastContainer } from 'react-toastify'
|
||||
|
||||
function App() {
|
||||
const appContext = useContext(AppContext)
|
||||
@@ -21,9 +23,6 @@ function App() {
|
||||
<HashRouter>
|
||||
<Header />
|
||||
<Switch>
|
||||
<Route exact path="/SASjsLogon">
|
||||
<Login getCodeOnly />
|
||||
</Route>
|
||||
<Route path="/">
|
||||
<Login />
|
||||
</Route>
|
||||
@@ -51,9 +50,10 @@ function App() {
|
||||
<Settings />
|
||||
</Route>
|
||||
<Route exact path="/SASjsLogon">
|
||||
<Login getCodeOnly />
|
||||
<AuthCode />
|
||||
</Route>
|
||||
</Switch>
|
||||
<ToastContainer />
|
||||
</HashRouter>
|
||||
</ThemeProvider>
|
||||
)
|
||||
|
||||
@@ -21,17 +21,21 @@ const PORT_API = process.env.PORT_API
|
||||
const baseUrl =
|
||||
NODE_ENV === 'development' ? `http://localhost:${PORT_API ?? 5000}` : ''
|
||||
|
||||
const validTabs = ['/', '/SASjsDrive', '/SASjsStudio']
|
||||
|
||||
const Header = (props: any) => {
|
||||
const history = useHistory()
|
||||
const { pathname } = useLocation()
|
||||
const appContext = useContext(AppContext)
|
||||
const [tabValue, setTabValue] = useState(pathname)
|
||||
const [tabValue, setTabValue] = useState(
|
||||
validTabs.includes(pathname) ? pathname : '/'
|
||||
)
|
||||
const [anchorEl, setAnchorEl] = useState<
|
||||
(EventTarget & HTMLButtonElement) | null
|
||||
>(null)
|
||||
|
||||
useEffect(() => {
|
||||
setTabValue(pathname)
|
||||
setTabValue(validTabs.includes(pathname) ? pathname : '/')
|
||||
}, [pathname])
|
||||
|
||||
const handleMenu = (
|
||||
@@ -49,7 +53,10 @@ const Header = (props: any) => {
|
||||
}
|
||||
|
||||
const handleLogout = () => {
|
||||
if (appContext.logout) appContext.logout()
|
||||
if (appContext.logout) {
|
||||
handleClose()
|
||||
appContext.logout()
|
||||
}
|
||||
}
|
||||
return (
|
||||
<AppBar
|
||||
@@ -141,11 +148,12 @@ const Header = (props: any) => {
|
||||
<Button
|
||||
component={Link}
|
||||
to="/SASjsSettings"
|
||||
onClick={handleClose}
|
||||
variant="contained"
|
||||
color="primary"
|
||||
startIcon={<SettingsIcon />}
|
||||
>
|
||||
Setting
|
||||
Settings
|
||||
</Button>
|
||||
</MenuItem>
|
||||
<MenuItem onClick={handleLogout} sx={{ justifyContent: 'center' }}>
|
||||
|
||||
@@ -9,8 +9,8 @@ const Home = () => {
|
||||
<CssBaseline />
|
||||
<h2>Welcome to SASjs Server!</h2>
|
||||
<p>
|
||||
This portal provides an interface for executing Stored Programs (drive)
|
||||
and ad hoc code (studio) against a SAS executable. The source code is
|
||||
SASjs Server provides a REST interface for executing Stored Programs and
|
||||
ad hoc code (studio) against SAS and JS executables. The source is
|
||||
available on{' '}
|
||||
<a
|
||||
href="https://github.com/sasjs/server"
|
||||
|
||||
@@ -1,88 +1,39 @@
|
||||
import axios from 'axios'
|
||||
import React, { useState, useContext } from 'react'
|
||||
import { useLocation } from 'react-router-dom'
|
||||
import PropTypes from 'prop-types'
|
||||
|
||||
import { CssBaseline, Box, TextField, Button, Typography } from '@mui/material'
|
||||
import { CssBaseline, Box, TextField, Button } from '@mui/material'
|
||||
import { AppContext } from '../context/appContext'
|
||||
|
||||
const getAuthCode = async (credentials: any) =>
|
||||
axios.post('/SASjsApi/auth/authorize', credentials).then((res) => res.data)
|
||||
|
||||
const login = async (payload: { username: string; password: string }) =>
|
||||
axios.get('/form').then((res1) =>
|
||||
axios
|
||||
.post('/login', payload, {
|
||||
headers: { 'csrf-token': res1.data.csrfToken }
|
||||
})
|
||||
.then((res2) => res2.data)
|
||||
)
|
||||
axios.post('/SASLogon/login', payload).then((res) => res.data)
|
||||
|
||||
const Login = ({ getCodeOnly }: any) => {
|
||||
const location = useLocation()
|
||||
const Login = () => {
|
||||
const appContext = useContext(AppContext)
|
||||
const [username, setUsername] = useState('')
|
||||
const [password, setPassword] = useState('')
|
||||
const [errorMessage, setErrorMessage] = useState('')
|
||||
let error: boolean
|
||||
const [displayCode, setDisplayCode] = useState(null)
|
||||
|
||||
const handleSubmit = async (e: any) => {
|
||||
error = false
|
||||
setErrorMessage('')
|
||||
e.preventDefault()
|
||||
|
||||
if (getCodeOnly) {
|
||||
const params = new URLSearchParams(location.search)
|
||||
const responseType = params.get('response_type')
|
||||
if (responseType === 'code') {
|
||||
const clientId = params.get('client_id')
|
||||
|
||||
const { code } = await getAuthCode({
|
||||
clientId,
|
||||
username,
|
||||
password
|
||||
}).catch((err: any) => {
|
||||
error = true
|
||||
setErrorMessage(err.response.data)
|
||||
return {}
|
||||
})
|
||||
if (!error) return setDisplayCode(code)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
const { loggedIn, user } = await login({
|
||||
username,
|
||||
password
|
||||
}).catch((err: any) => {
|
||||
error = true
|
||||
setErrorMessage(err.response.data)
|
||||
return {}
|
||||
})
|
||||
|
||||
if (loggedIn) {
|
||||
appContext.setLoggedIn?.(loggedIn)
|
||||
appContext.setUserId?.(user.id)
|
||||
appContext.setUsername?.(user.username)
|
||||
appContext.setDisplayName?.(user.displayName)
|
||||
appContext.setLoggedIn?.(loggedIn)
|
||||
}
|
||||
}
|
||||
|
||||
if (displayCode) {
|
||||
return (
|
||||
<Box className="main">
|
||||
<CssBaseline />
|
||||
<br />
|
||||
<h2>Authorization Code</h2>
|
||||
<Typography m={2} p={3} style={{ overflowWrap: 'anywhere' }}>
|
||||
{displayCode}
|
||||
</Typography>
|
||||
|
||||
<br />
|
||||
</Box>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<Box
|
||||
className="main"
|
||||
@@ -95,13 +46,6 @@ const Login = ({ getCodeOnly }: any) => {
|
||||
<CssBaseline />
|
||||
<br />
|
||||
<h2 style={{ width: 'auto' }}>Welcome to SASjs Server!</h2>
|
||||
{getCodeOnly && (
|
||||
<p style={{ width: 'auto' }}>
|
||||
Provide credentials to get authorization code.
|
||||
</p>
|
||||
)}
|
||||
<br />
|
||||
|
||||
<TextField
|
||||
id="username"
|
||||
label="Username"
|
||||
|
||||
78
web/src/containers/AuthCode/index.tsx
Normal file
78
web/src/containers/AuthCode/index.tsx
Normal file
@@ -0,0 +1,78 @@
|
||||
import axios from 'axios'
|
||||
import { CopyToClipboard } from 'react-copy-to-clipboard'
|
||||
import React, { useEffect, useState } from 'react'
|
||||
import { toast } from 'react-toastify'
|
||||
import 'react-toastify/dist/ReactToastify.css'
|
||||
import { useLocation } from 'react-router-dom'
|
||||
|
||||
import { CssBaseline, Box, Typography, Button } from '@mui/material'
|
||||
|
||||
const getAuthCode = async (credentials: any) =>
|
||||
axios.post('/SASLogon/authorize', credentials).then((res) => res.data)
|
||||
|
||||
const AuthCode = () => {
|
||||
const location = useLocation()
|
||||
const [displayCode, setDisplayCode] = useState('')
|
||||
const [errorMessage, setErrorMessage] = useState('')
|
||||
|
||||
useEffect(() => {
|
||||
requestAuthCode()
|
||||
}, [])
|
||||
|
||||
const requestAuthCode = async () => {
|
||||
setErrorMessage('')
|
||||
|
||||
const params = new URLSearchParams(location.search)
|
||||
|
||||
const responseType = params.get('response_type')
|
||||
if (responseType !== 'code')
|
||||
return setErrorMessage('response type is not support')
|
||||
|
||||
const clientId = params.get('client_id')
|
||||
if (!clientId) return setErrorMessage('clientId is not provided')
|
||||
|
||||
setErrorMessage('Fetching auth code... ')
|
||||
const { code } = await getAuthCode({
|
||||
clientId
|
||||
})
|
||||
.then((res) => {
|
||||
setErrorMessage('')
|
||||
return res
|
||||
})
|
||||
.catch((err: any) => {
|
||||
setErrorMessage(err.response.data)
|
||||
return { code: null }
|
||||
})
|
||||
return setDisplayCode(code)
|
||||
}
|
||||
|
||||
return (
|
||||
<Box className="main">
|
||||
<CssBaseline />
|
||||
<br />
|
||||
<h2>Authorization Code</h2>
|
||||
{displayCode && (
|
||||
<Typography m={2} p={3} style={{ overflowWrap: 'anywhere' }}>
|
||||
{displayCode}
|
||||
</Typography>
|
||||
)}
|
||||
{errorMessage && <Typography>{errorMessage}</Typography>}
|
||||
|
||||
<br />
|
||||
|
||||
<CopyToClipboard
|
||||
text={displayCode}
|
||||
onCopy={() =>
|
||||
toast.info('Code copied to ClipBoard', {
|
||||
theme: 'dark',
|
||||
position: toast.POSITION.BOTTOM_RIGHT
|
||||
})
|
||||
}
|
||||
>
|
||||
<Button variant="contained">Copy to Clipboard</Button>
|
||||
</CopyToClipboard>
|
||||
</Box>
|
||||
)
|
||||
}
|
||||
|
||||
export default AuthCode
|
||||
@@ -2,7 +2,7 @@ import React, { useState, useEffect } from 'react'
|
||||
import { Link } from 'react-router-dom'
|
||||
import axios from 'axios'
|
||||
|
||||
import Editor from '@monaco-editor/react'
|
||||
import Editor from 'react-monaco-editor'
|
||||
|
||||
import Box from '@mui/material/Box'
|
||||
import Paper from '@mui/material/Paper'
|
||||
@@ -94,10 +94,7 @@ const Main = (props: Props) => {
|
||||
setEditMode(false)
|
||||
} else {
|
||||
window.open(
|
||||
`${baseUrl}/SASjsApi/stp/execute?_program=${props.selectedFilePath.replace(
|
||||
/.sas$/,
|
||||
''
|
||||
)}`
|
||||
`${baseUrl}/SASjsApi/stp/execute?_program=${props.selectedFilePath}`
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -125,6 +122,7 @@ const Main = (props: Props) => {
|
||||
{!isLoading && props?.selectedFilePath && editMode && (
|
||||
<Editor
|
||||
height="95%"
|
||||
language="sas"
|
||||
value={fileContent}
|
||||
onChange={(val) => {
|
||||
if (val) setFileContent(val)
|
||||
|
||||
@@ -6,6 +6,7 @@ import TabList from '@mui/lab/TabList'
|
||||
import TabPanel from '@mui/lab/TabPanel'
|
||||
|
||||
import Permission from './permission'
|
||||
import Profile from './profile'
|
||||
|
||||
const StyledTab = styled(Tab)({
|
||||
background: 'black',
|
||||
@@ -45,7 +46,9 @@ const Settings = () => {
|
||||
<StyledTab label="Permission" value="permission" />
|
||||
</TabList>
|
||||
</Box>
|
||||
<StyledTabpanel value="profile">Profile Page</StyledTabpanel>
|
||||
<StyledTabpanel value="profile">
|
||||
<Profile />
|
||||
</StyledTabpanel>
|
||||
<StyledTabpanel value="permission">
|
||||
<Permission />
|
||||
</StyledTabpanel>
|
||||
|
||||
150
web/src/containers/Settings/profile.tsx
Normal file
150
web/src/containers/Settings/profile.tsx
Normal file
@@ -0,0 +1,150 @@
|
||||
import React, { useState, useEffect, useContext } from 'react'
|
||||
import axios from 'axios'
|
||||
import {
|
||||
Grid,
|
||||
CircularProgress,
|
||||
Card,
|
||||
CardHeader,
|
||||
Divider,
|
||||
CardContent,
|
||||
TextField,
|
||||
CardActions,
|
||||
Button,
|
||||
FormGroup,
|
||||
FormControlLabel,
|
||||
Checkbox
|
||||
} from '@mui/material'
|
||||
import { toast } from 'react-toastify'
|
||||
|
||||
import { AppContext, ModeType } from '../../context/appContext'
|
||||
|
||||
const Profile = () => {
|
||||
const [isLoading, setIsLoading] = useState(false)
|
||||
const appContext = useContext(AppContext)
|
||||
const [user, setUser] = useState({} as any)
|
||||
|
||||
useEffect(() => {
|
||||
setIsLoading(true)
|
||||
axios
|
||||
.get(`/SASjsApi/user/${appContext.userId}`)
|
||||
.then((res: any) => {
|
||||
setUser(res.data)
|
||||
})
|
||||
.catch((err) => {
|
||||
console.log(err)
|
||||
})
|
||||
.finally(() => {
|
||||
setIsLoading(false)
|
||||
})
|
||||
}, [])
|
||||
|
||||
const handleChange = (event: any) => {
|
||||
const { name, value } = event.target
|
||||
|
||||
setUser({ ...user, [name]: value })
|
||||
}
|
||||
const handleSubmit = () => {
|
||||
setIsLoading(true)
|
||||
axios
|
||||
.patch(`/SASjsApi/user/${appContext.userId}`, {
|
||||
username: user.username,
|
||||
displayName: user.displayName,
|
||||
autoExec: user.autoExec
|
||||
})
|
||||
.then((res: any) => {
|
||||
toast.success('User information updated', {
|
||||
theme: 'dark',
|
||||
position: toast.POSITION.BOTTOM_RIGHT
|
||||
})
|
||||
})
|
||||
.catch((err) => {
|
||||
toast.error('Failed: ' + err.response?.data || err.text, {
|
||||
theme: 'dark',
|
||||
position: toast.POSITION.BOTTOM_RIGHT
|
||||
})
|
||||
})
|
||||
.finally(() => {
|
||||
setIsLoading(false)
|
||||
})
|
||||
}
|
||||
|
||||
return isLoading ? (
|
||||
<CircularProgress
|
||||
style={{ position: 'absolute', left: '50%', top: '50%' }}
|
||||
/>
|
||||
) : (
|
||||
<Card>
|
||||
<CardHeader title="Profile Information" />
|
||||
<Divider />
|
||||
<CardContent>
|
||||
<Grid container spacing={4}>
|
||||
<Grid item md={6} xs={12}>
|
||||
<TextField
|
||||
fullWidth
|
||||
error={user.displayName?.length === 0}
|
||||
helperText="Please specify display name"
|
||||
label="Display Name"
|
||||
name="displayName"
|
||||
onChange={handleChange}
|
||||
required
|
||||
value={user.displayName}
|
||||
variant="outlined"
|
||||
disabled={appContext.mode === ModeType.Desktop}
|
||||
/>
|
||||
</Grid>
|
||||
|
||||
<Grid item md={6} xs={12}>
|
||||
<TextField
|
||||
fullWidth
|
||||
error={user.username?.length === 0}
|
||||
helperText="Please specify username"
|
||||
label="Username"
|
||||
name="username"
|
||||
onChange={handleChange}
|
||||
required
|
||||
value={user.username}
|
||||
variant="outlined"
|
||||
disabled={appContext.mode === ModeType.Desktop}
|
||||
/>
|
||||
</Grid>
|
||||
|
||||
<Grid item lg={6} md={8} sm={12} xs={12}>
|
||||
<TextField
|
||||
fullWidth
|
||||
label="autoExec"
|
||||
name="autoExec"
|
||||
onChange={handleChange}
|
||||
multiline
|
||||
rows="10"
|
||||
value={user.autoExec}
|
||||
variant="outlined"
|
||||
/>
|
||||
</Grid>
|
||||
|
||||
<Grid item xs={6}>
|
||||
<FormGroup row>
|
||||
<FormControlLabel
|
||||
disabled
|
||||
control={<Checkbox checked={user.isActive} />}
|
||||
label="isActive"
|
||||
/>
|
||||
<FormControlLabel
|
||||
disabled
|
||||
control={<Checkbox checked={user.isAdmin} />}
|
||||
label="isAdmin"
|
||||
/>
|
||||
</FormGroup>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</CardContent>
|
||||
<Divider />
|
||||
<CardActions>
|
||||
<Button type="submit" variant="contained" onClick={handleSubmit}>
|
||||
Save Changes
|
||||
</Button>
|
||||
</CardActions>
|
||||
</Card>
|
||||
)
|
||||
}
|
||||
|
||||
export default Profile
|
||||
@@ -1,13 +1,24 @@
|
||||
import React, { useEffect, useRef, useState } from 'react'
|
||||
import React, { useEffect, useRef, useState, useContext } from 'react'
|
||||
import axios from 'axios'
|
||||
|
||||
import Box from '@mui/material/Box'
|
||||
import { Button, Paper, Stack, Tab, Tooltip } from '@mui/material'
|
||||
import {
|
||||
Box,
|
||||
MenuItem,
|
||||
FormControl,
|
||||
Select,
|
||||
SelectChangeEvent,
|
||||
Button,
|
||||
Paper,
|
||||
Tab,
|
||||
Tooltip
|
||||
} from '@mui/material'
|
||||
import { makeStyles } from '@mui/styles'
|
||||
import Editor, { OnMount } from '@monaco-editor/react'
|
||||
import Editor, { EditorDidMount } from 'react-monaco-editor'
|
||||
import { useLocation } from 'react-router-dom'
|
||||
import { TabContext, TabList, TabPanel } from '@mui/lab'
|
||||
|
||||
import { AppContext, RunTimeType } from '../../context/appContext'
|
||||
|
||||
const useStyles = makeStyles(() => ({
|
||||
root: {
|
||||
fontSize: '1rem',
|
||||
@@ -30,19 +41,30 @@ const useStyles = makeStyles(() => ({
|
||||
}))
|
||||
|
||||
const Studio = () => {
|
||||
const appContext = useContext(AppContext)
|
||||
const location = useLocation()
|
||||
const [fileContent, setFileContent] = useState('')
|
||||
const [log, setLog] = useState('')
|
||||
const [ctrlPressed, setCtrlPressed] = useState(false)
|
||||
const [webout, setWebout] = useState('')
|
||||
const [tab, setTab] = React.useState('1')
|
||||
const [tab, setTab] = useState('1')
|
||||
const [runTimes, setRunTimes] = useState<string[]>([])
|
||||
const [selectedRunTime, setSelectedRunTime] = useState('')
|
||||
|
||||
useEffect(() => {
|
||||
setRunTimes(Object.values(appContext.runTimes))
|
||||
}, [appContext.runTimes])
|
||||
|
||||
useEffect(() => {
|
||||
if (runTimes.length) setSelectedRunTime(runTimes[0])
|
||||
}, [runTimes])
|
||||
|
||||
const handleTabChange = (_e: any, newValue: string) => {
|
||||
setTab(newValue)
|
||||
}
|
||||
|
||||
const editorRef = useRef(null as any)
|
||||
const handleEditorDidMount: OnMount = (editor) => {
|
||||
const handleEditorDidMount: EditorDidMount = (editor) => {
|
||||
editor.focus()
|
||||
editorRef.current = editor
|
||||
}
|
||||
@@ -57,7 +79,7 @@ const Studio = () => {
|
||||
|
||||
const runCode = (code: string) => {
|
||||
axios
|
||||
.post(`/SASjsApi/code/execute`, { code })
|
||||
.post(`/SASjsApi/code/execute`, { code, runTime: selectedRunTime })
|
||||
.then((res: any) => {
|
||||
const parsedLog = res?.data?.log
|
||||
.map((logLine: any) => logLine.line)
|
||||
@@ -89,6 +111,10 @@ const Studio = () => {
|
||||
if (!event.ctrlKey && ctrlPressed) setCtrlPressed(false)
|
||||
}
|
||||
|
||||
const handleChangeRunTime = (event: SelectChangeEvent) => {
|
||||
setSelectedRunTime(event.target.value as RunTimeType)
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
const content = localStorage.getItem('fileContent') ?? ''
|
||||
setFileContent(content)
|
||||
@@ -136,11 +162,12 @@ const Studio = () => {
|
||||
</TabList>
|
||||
</Box>
|
||||
|
||||
<TabPanel style={{ paddingBottom: 0 }} value="1">
|
||||
<TabPanel sx={{ paddingBottom: 0 }} value="1">
|
||||
<div className={classes.subMenu}>
|
||||
<Tooltip title="CTRL+ENTER will also run SAS code">
|
||||
<Button onClick={handleRunBtnClick} className={classes.runButton}>
|
||||
<img
|
||||
alt=""
|
||||
draggable="false"
|
||||
style={{ width: '25px' }}
|
||||
src="/running-sas.png"
|
||||
@@ -148,8 +175,23 @@ const Studio = () => {
|
||||
<span style={{ fontSize: '12px' }}>RUN</span>
|
||||
</Button>
|
||||
</Tooltip>
|
||||
<Box sx={{ minWidth: '75px', marginLeft: '10px' }}>
|
||||
<FormControl variant="standard">
|
||||
<Select
|
||||
labelId="run-time-select-label"
|
||||
id="run-time-select"
|
||||
value={selectedRunTime}
|
||||
onChange={handleChangeRunTime}
|
||||
>
|
||||
{runTimes.map((runTime) => (
|
||||
<MenuItem key={runTime} value={runTime}>
|
||||
{runTime}
|
||||
</MenuItem>
|
||||
))}
|
||||
</Select>
|
||||
</FormControl>
|
||||
</Box>
|
||||
</div>
|
||||
{/* <Toolbar /> */}
|
||||
<Paper
|
||||
sx={{
|
||||
height: 'calc(100vh - 170px)',
|
||||
@@ -161,8 +203,9 @@ const Studio = () => {
|
||||
>
|
||||
<Editor
|
||||
height="98%"
|
||||
language="sas"
|
||||
value={fileContent}
|
||||
onMount={handleEditorDidMount}
|
||||
editorDidMount={handleEditorDidMount}
|
||||
options={{ readOnly: ctrlPressed }}
|
||||
onChange={(val) => {
|
||||
if (val) setFileContent(val)
|
||||
|
||||
@@ -9,14 +9,28 @@ import React, {
|
||||
} from 'react'
|
||||
import axios from 'axios'
|
||||
|
||||
export enum ModeType {
|
||||
Server = 'server',
|
||||
Desktop = 'desktop'
|
||||
}
|
||||
|
||||
export enum RunTimeType {
|
||||
SAS = 'sas',
|
||||
JS = 'js'
|
||||
}
|
||||
|
||||
interface AppContextProps {
|
||||
checkingSession: boolean
|
||||
loggedIn: boolean
|
||||
setLoggedIn: Dispatch<SetStateAction<boolean>> | null
|
||||
userId: number
|
||||
setUserId: Dispatch<SetStateAction<number>> | null
|
||||
username: string
|
||||
setUsername: Dispatch<SetStateAction<string>> | null
|
||||
displayName: string
|
||||
setDisplayName: Dispatch<SetStateAction<string>> | null
|
||||
mode: ModeType
|
||||
runTimes: RunTimeType[]
|
||||
logout: (() => void) | null
|
||||
}
|
||||
|
||||
@@ -24,10 +38,14 @@ export const AppContext = createContext<AppContextProps>({
|
||||
checkingSession: false,
|
||||
loggedIn: false,
|
||||
setLoggedIn: null,
|
||||
userId: 0,
|
||||
setUserId: null,
|
||||
username: '',
|
||||
setUsername: null,
|
||||
displayName: '',
|
||||
setDisplayName: null,
|
||||
mode: ModeType.Server,
|
||||
runTimes: [],
|
||||
logout: null
|
||||
})
|
||||
|
||||
@@ -35,8 +53,11 @@ const AppContextProvider = (props: { children: ReactNode }) => {
|
||||
const { children } = props
|
||||
const [checkingSession, setCheckingSession] = useState(false)
|
||||
const [loggedIn, setLoggedIn] = useState(false)
|
||||
const [userId, setUserId] = useState(0)
|
||||
const [username, setUsername] = useState('')
|
||||
const [displayName, setDisplayName] = useState('')
|
||||
const [mode, setMode] = useState(ModeType.Server)
|
||||
const [runTimes, setRunTimes] = useState<RunTimeType[]>([])
|
||||
|
||||
useEffect(() => {
|
||||
setCheckingSession(true)
|
||||
@@ -46,17 +67,28 @@ const AppContextProvider = (props: { children: ReactNode }) => {
|
||||
.then((res) => res.data)
|
||||
.then((data: any) => {
|
||||
setCheckingSession(false)
|
||||
setLoggedIn(true)
|
||||
setUserId(data.id)
|
||||
setUsername(data.username)
|
||||
setDisplayName(data.displayName)
|
||||
setLoggedIn(true)
|
||||
})
|
||||
.catch(() => {
|
||||
setLoggedIn(false)
|
||||
axios.get('/') // get CSRF TOKEN
|
||||
})
|
||||
|
||||
axios
|
||||
.get('/SASjsApi/info')
|
||||
.then((res) => res.data)
|
||||
.then((data: any) => {
|
||||
setMode(data.mode)
|
||||
setRunTimes(data.runTimes)
|
||||
})
|
||||
.catch(() => {})
|
||||
}, [])
|
||||
|
||||
const logout = useCallback(() => {
|
||||
axios.get('/logout').then(() => {
|
||||
axios.get('/SASLogon/logout').then(() => {
|
||||
setLoggedIn(false)
|
||||
setUsername('')
|
||||
setDisplayName('')
|
||||
@@ -69,10 +101,14 @@ const AppContextProvider = (props: { children: ReactNode }) => {
|
||||
checkingSession,
|
||||
loggedIn,
|
||||
setLoggedIn,
|
||||
userId,
|
||||
setUserId,
|
||||
username,
|
||||
setUsername,
|
||||
displayName,
|
||||
setDisplayName,
|
||||
mode,
|
||||
runTimes,
|
||||
logout
|
||||
}}
|
||||
>
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import path from 'path'
|
||||
import MonacoWebpackPlugin from 'monaco-editor-webpack-plugin'
|
||||
import { Configuration } from 'webpack'
|
||||
import HtmlWebpackPlugin from 'html-webpack-plugin'
|
||||
import CopyPlugin from 'copy-webpack-plugin'
|
||||
@@ -53,7 +54,8 @@ const config: Configuration = {
|
||||
new CopyPlugin({
|
||||
patterns: [{ from: 'public' }]
|
||||
}),
|
||||
new dotenv()
|
||||
new dotenv(),
|
||||
new MonacoWebpackPlugin()
|
||||
]
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user