From 2fde8406ed1f2ca1beefc0f12ecd00e5c6f25d6f Mon Sep 17 00:00:00 2001
From: Justin Hurst
Date: Tue, 14 Sep 2021 21:20:13 -0400
Subject: [PATCH 01/17] Added disable module checkbox to UI
---
src/factories.ts | 1 +
src/pages/ModuleEditor/ModuleEditor.tsx | 3 +++
src/pages/ModuleEditor/ModuleEditorSchema.ts | 1 +
src/pages/PublicModule/PublicModule.tsx | 1 +
src/pages/UserModulePage/UserModulePage.tsx | 1 +
src/types/Module.ts | 1 +
6 files changed, 8 insertions(+)
diff --git a/src/factories.ts b/src/factories.ts
index f069a025..cd5d95e6 100644
--- a/src/factories.ts
+++ b/src/factories.ts
@@ -16,6 +16,7 @@ export function makeModuleForm(): ModuleForm {
description: '',
name: '',
published: false,
+ disabled: false,
specialCode: uuid(),
type: 'SingleUser',
userId: 0
diff --git a/src/pages/ModuleEditor/ModuleEditor.tsx b/src/pages/ModuleEditor/ModuleEditor.tsx
index d4adb48d..b8b574b9 100644
--- a/src/pages/ModuleEditor/ModuleEditor.tsx
+++ b/src/pages/ModuleEditor/ModuleEditor.tsx
@@ -125,6 +125,9 @@ export default function ModuleEditor({match: {params: {moduleId}}}: Props) {
('published')} label='Publish (Display on the explore page)' disabled={!editing}/>
+
+ ('disabled')} label='Disable Module' disabled={!editing}/>
+
Type
('type')} dropdownData={moduleTypeOptions} disabled={!editing}/>
diff --git a/src/pages/ModuleEditor/ModuleEditorSchema.ts b/src/pages/ModuleEditor/ModuleEditorSchema.ts
index 01906d0a..056e5305 100644
--- a/src/pages/ModuleEditor/ModuleEditorSchema.ts
+++ b/src/pages/ModuleEditor/ModuleEditorSchema.ts
@@ -7,6 +7,7 @@ export const ModuleEditorSchema = object({
name: string().min(3, 'Must at least be 3 characters').required('Required'),
specialCode : string().required('Required'),
description: string().required('Required').min(4, 'Required'),
+ disabled: boolean(),
updatedAt: string().notRequired(),
createdAt: string().notRequired(),
id: number(),
diff --git a/src/pages/PublicModule/PublicModule.tsx b/src/pages/PublicModule/PublicModule.tsx
index b79c9d7b..1243815f 100644
--- a/src/pages/PublicModule/PublicModule.tsx
+++ b/src/pages/PublicModule/PublicModule.tsx
@@ -28,6 +28,7 @@ class PublicModule extends Component {
id: 0,
createdAt: '',
description: '',
+ disabled: false,
name: 'Loading',
published: false,
userId: 0,
diff --git a/src/pages/UserModulePage/UserModulePage.tsx b/src/pages/UserModulePage/UserModulePage.tsx
index bdacf139..f68bae6b 100644
--- a/src/pages/UserModulePage/UserModulePage.tsx
+++ b/src/pages/UserModulePage/UserModulePage.tsx
@@ -34,6 +34,7 @@ class UserModulePage extends Component
Date: Thu, 16 Sep 2021 13:30:37 -0400
Subject: [PATCH 02/17] Disabled modules will not show on explore page. Title
of diabled modules contains '(disabled)'
---
src/components/ModuleCard/ModuleCard.tsx | 5 ++++-
src/pages/Explore/Explore.tsx | 8 +++++++-
2 files changed, 11 insertions(+), 2 deletions(-)
diff --git a/src/components/ModuleCard/ModuleCard.tsx b/src/components/ModuleCard/ModuleCard.tsx
index bebe2d5d..d5b7c58b 100644
--- a/src/components/ModuleCard/ModuleCard.tsx
+++ b/src/components/ModuleCard/ModuleCard.tsx
@@ -26,7 +26,10 @@ class ModuleCardComponent extends Component {
{/**/}
- {module.name}
+ {module.disabled
+ ? {module.name} (disabled)
+ : {module.name}
+ }
{module.description.substring(0, 150)}
diff --git a/src/pages/Explore/Explore.tsx b/src/pages/Explore/Explore.tsx
index 4a2a3d11..6dedae1e 100644
--- a/src/pages/Explore/Explore.tsx
+++ b/src/pages/Explore/Explore.tsx
@@ -27,7 +27,13 @@ class Explore extends React.Component<{}, ExploreState> {
async loadModules() {
try {
const modules = await getPublicModules();
- this.setState({modules: modules, state: 'success'});
+ const enabledModules = [];
+ for (const i of modules) {
+ if (!i.disabled) {
+ enabledModules.push(i);
+ }
+ }
+ this.setState({modules: enabledModules, state: 'success'});
} catch (_) {
this.setState({state: 'error'});
}
From 6004fb69b4bd935ff6d2c9948ad5d59b403850d5 Mon Sep 17 00:00:00 2001
From: Justin Hurst
Date: Thu, 16 Sep 2021 22:09:30 -0400
Subject: [PATCH 03/17] Start button grayed out with tooltip on disable. Added
react-tooltip library.
---
package.json | 1 +
src/App.tsx | 6 ++-
.../LabEnvironment/LabEnvironment.tsx | 23 ++++++++++-
src/types/UserLab.ts | 1 +
src/util/LoadingButton.tsx | 41 ++++++++++++++-----
yarn.lock | 13 ++++++
6 files changed, 71 insertions(+), 14 deletions(-)
diff --git a/package.json b/package.json
index 7bff9625..51756af5 100644
--- a/package.json
+++ b/package.json
@@ -66,6 +66,7 @@
"react-router-bootstrap": "^0.25.0",
"react-router-dom": "^5.1.0",
"react-select": "^3.1.0",
+ "react-tooltip": "^4.2.21",
"redux": "^4.0.4",
"redux-persist": "^6.0.0",
"redux-thunk": "^2.3.0",
diff --git a/src/App.tsx b/src/App.tsx
index 3b8517f2..e276ca7e 100644
--- a/src/App.tsx
+++ b/src/App.tsx
@@ -2,6 +2,7 @@ import * as React from 'react';
import * as Sentry from '@sentry/browser';
import Routes from './router/Routes';
import {CapsLockContextProvider} from './components/CapsLockContext/CapsLockContext';
+// import LabEditorGUI from './pages/LabEditorGUI/LabEditorGUI';
// Configure Sentry
// Sentry.init({
@@ -26,9 +27,10 @@ class App extends React.Component {
public render() {
return (
-
+
-
+
+ //
);
}
}
diff --git a/src/components/LabEnvironment/LabEnvironment.tsx b/src/components/LabEnvironment/LabEnvironment.tsx
index f140c689..53f30fe3 100644
--- a/src/components/LabEnvironment/LabEnvironment.tsx
+++ b/src/components/LabEnvironment/LabEnvironment.tsx
@@ -4,7 +4,7 @@ import {Button, Col, Container, Dropdown, ButtonGroup, ListGroup, Row, Tab} from
import {Status} from '../../pages/Status/Status';
import {Document, Page, pdfjs} from 'react-pdf';
import {PDFDocumentProxy} from 'pdfjs-dist';
-import {getUserLabReadmeUrl, getUserLabTopologyUrl} from '../../api';
+import {getPublicModule, getUserLabReadmeUrl, getUserLabTopologyUrl} from '../../api';
import {UserLab} from '../../types/UserLab';
import {LoadingButton} from '../../util/LoadingButton';
import {combineClasses, getRemainingLabTime} from '../../util';
@@ -12,6 +12,7 @@ import {ConsoleWindowContainer} from '../ConsoleWindow/ConsoleWindowContainer';
import {VmActionsMenu} from '../VmActionsMenu/VmActionsMenu';
import {VmStatusIndicator} from '../util/VmStatusIndicator/VmStatusIndicator';
import styles from './LabEnvironment.module.scss';
+import {Module} from '../../types/Module';
pdfjs.GlobalWorkerOptions.workerSrc = `//cdnjs.cloudflare.com/ajax/libs/pdf.js/${pdfjs.version}/pdf.worker.js`;
@@ -28,6 +29,7 @@ interface LabEnvironmentState {
readmeLoaded: boolean;
show_vm: boolean;
eventKey: string;
+ module: Module;
}
@@ -38,9 +40,25 @@ export class LabEnvironment extends Component this.state.pageNumber > 1;
canGoToNextPage = () => this.state.pageNumber < this.state.numPages;
@@ -71,6 +89,7 @@ export class LabEnvironment extends Component : Lab's time remaining: {getRemainingLabTime(this.props.userLab.endDateTime!)}
}
diff --git a/src/types/UserLab.ts b/src/types/UserLab.ts
index ed840855..9b5eaf7f 100644
--- a/src/types/UserLab.ts
+++ b/src/types/UserLab.ts
@@ -9,6 +9,7 @@ export interface UserLab extends Entity {
hasReadme: boolean;
endDateTime?: string;
status: UserLabStatus;
+ userModuleId: number;
}
export type UserLabStatus = 'Started' | 'NotStarted' | 'Completed';
diff --git a/src/util/LoadingButton.tsx b/src/util/LoadingButton.tsx
index 1f0792bc..d5c33739 100644
--- a/src/util/LoadingButton.tsx
+++ b/src/util/LoadingButton.tsx
@@ -1,6 +1,7 @@
import * as React from 'react';
import {Button, ButtonProps, Spinner} from 'react-bootstrap';
import {SyntheticEvent} from 'react';
+import ReactTooltip from 'react-tooltip';
type onClick = ((e: SyntheticEvent) => void) | (() => void) | (() => any);
@@ -14,14 +15,34 @@ interface Props {
}
export const LoadingButton = (props: Props) => (
-
+ <>
+
+ {props.disabled
+ ? <>
+
+
+
+
+ >
+ :
+ }
+ >
);
diff --git a/yarn.lock b/yarn.lock
index 2a13d31c..1ba706c1 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -11655,6 +11655,14 @@ react-test-renderer@^16.0.0-0:
react-is "^16.8.6"
scheduler "^0.18.0"
+react-tooltip@^4.2.21:
+ version "4.2.21"
+ resolved "https://registry.yarnpkg.com/react-tooltip/-/react-tooltip-4.2.21.tgz#840123ed86cf33d50ddde8ec8813b2960bfded7f"
+ integrity sha512-zSLprMymBDowknr0KVDiJ05IjZn9mQhhg4PRsqln0OZtURAJ1snt1xi5daZfagsh6vfsziZrc9pErPTDY1ACig==
+ dependencies:
+ prop-types "^15.7.2"
+ uuid "^7.0.3"
+
react-transition-group@^4.0.0, react-transition-group@^4.3.0:
version "4.3.0"
resolved "https://registry.yarnpkg.com/react-transition-group/-/react-transition-group-4.3.0.tgz#fea832e386cf8796c58b61874a3319704f5ce683"
@@ -14069,6 +14077,11 @@ uuid@^3.0.1, uuid@^3.3.2, uuid@^3.4.0:
resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.4.0.tgz#b23e4358afa8a202fe7a100af1f5f883f02007ee"
integrity sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==
+uuid@^7.0.3:
+ version "7.0.3"
+ resolved "https://registry.yarnpkg.com/uuid/-/uuid-7.0.3.tgz#c5c9f2c8cf25dc0a372c4df1441c41f5bd0c680b"
+ integrity sha512-DPSke0pXhTZgoF/d+WSt2QaKMCFSfx7QegxEWT+JOuHF5aWrKEn0G+ztjuJg/gG8/ItK+rbPCD/yNv8yyih6Cg==
+
v8-compile-cache@^2.0.3, v8-compile-cache@^2.1.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/v8-compile-cache/-/v8-compile-cache-2.1.0.tgz#e14de37b31a6d194f5690d67efc4e7f6fc6ab30e"
From 17b4892117dfedbaa842250415186e879001dea6 Mon Sep 17 00:00:00 2001
From: Justin Hurst
Date: Mon, 20 Sep 2021 21:39:31 -0400
Subject: [PATCH 04/17] Creators should be able to disable only their own
modules. Admins can disable any modules in the system
---
src/api/index.ts | 4 +--
.../LabEnvironment/LabEnvironment.tsx | 2 +-
.../LabListEditor/LabListEditor.tsx | 28 ++++++++++++++-----
src/components/ModuleCard/ModuleCard.tsx | 2 +-
src/factories.ts | 2 +-
src/pages/ModuleEditor/ModuleEditor.tsx | 24 +++++++++++++---
src/pages/ModuleEditor/ModuleEditorSchema.ts | 2 +-
src/pages/PublicModule/PublicModule.tsx | 2 +-
src/pages/UserModulePage/UserModulePage.tsx | 2 +-
src/types/Module.ts | 2 +-
10 files changed, 49 insertions(+), 21 deletions(-)
diff --git a/src/api/index.ts b/src/api/index.ts
index f8fbbece..480f0952 100644
--- a/src/api/index.ts
+++ b/src/api/index.ts
@@ -55,8 +55,6 @@ export async function turnOnUserLab(id: number): Promise {
return (await retry.post(`/user-lab/${id}/turn-on`)).data;
}
-
-
export async function shutdownVm(id: number): Promise {
return (await api.post(`/virtual-machine/${id}/shutdown`)).data;
}
@@ -74,7 +72,7 @@ export async function resetVm(id: number): Promise {
}
export async function getModule(id: number) {
- return ( await api.get(`/modules/${id}`)).data;
+ return ( await api.get(`/module/${id}`)).data;
}
export async function getUserList() {
diff --git a/src/components/LabEnvironment/LabEnvironment.tsx b/src/components/LabEnvironment/LabEnvironment.tsx
index 53f30fe3..4c0fb4ee 100644
--- a/src/components/LabEnvironment/LabEnvironment.tsx
+++ b/src/components/LabEnvironment/LabEnvironment.tsx
@@ -48,7 +48,7 @@ export class LabEnvironment extends Component
Labs
-
+ { disabledModule ?
+ <>
+
+
+
+
+ >
+ :
+ }
{labs.map((lab,index) =>
diff --git a/src/components/ModuleCard/ModuleCard.tsx b/src/components/ModuleCard/ModuleCard.tsx
index d5b7c58b..0ef47dc2 100644
--- a/src/components/ModuleCard/ModuleCard.tsx
+++ b/src/components/ModuleCard/ModuleCard.tsx
@@ -17,6 +17,7 @@ interface ModuleCardProps extends ReturnType {
class ModuleCardComponent extends Component {
+
render() {
let module: Module = this.props.module as Module;
if (this.props.module['module'] !== undefined) {
@@ -42,7 +43,6 @@ class ModuleCardComponent extends Component {
);
}
-
getStartButton() {
if (this.props.buttonLink) {
return (
diff --git a/src/factories.ts b/src/factories.ts
index cd5d95e6..08cabd55 100644
--- a/src/factories.ts
+++ b/src/factories.ts
@@ -19,7 +19,7 @@ export function makeModuleForm(): ModuleForm {
disabled: false,
specialCode: uuid(),
type: 'SingleUser',
- userId: 0
+ ownerId: 0
};
}
diff --git a/src/pages/ModuleEditor/ModuleEditor.tsx b/src/pages/ModuleEditor/ModuleEditor.tsx
index b8b574b9..17e680a3 100644
--- a/src/pages/ModuleEditor/ModuleEditor.tsx
+++ b/src/pages/ModuleEditor/ModuleEditor.tsx
@@ -24,6 +24,7 @@ import { LinkContainer } from 'react-router-bootstrap';
import CheckBoxInput from '../../components/util/CheckBoxInput/CheckBoxInput';
import {LabListEditor} from '../../components/LabListEditor/LabListEditor';
import {PageTitle} from '../../components/util/PageTitle';
+import {useSelector} from 'react-redux';
const moduleTypeOptions: DropdownOption[] = [
{value: 'SingleUser', label: 'Single User'},
@@ -38,12 +39,16 @@ export default function ModuleEditor({match: {params: {moduleId}}}: Props) {
const [message, setMessage] = useMessage();
const [editing, setEditing] = useState(false);
const [redirect, setRedirect] = useState();
+ const [canDisable, setCanDisable] = useState(false);
function completeLoading() {
setLoading(false);
setMessage(undefined);
}
+ const userRole = useSelector((state: any) => state.entities.currentUser.role);
+ const userId = useSelector((state: any) => state.entities.currentUser.id);
+
const onSubmit = async (form: ModuleForm, {setErrors}: FormikHelpers) => {
setMessage(undefined);
try {
@@ -88,6 +93,15 @@ export default function ModuleEditor({match: {params: {moduleId}}}: Props) {
LoadModule();
}, [moduleId]);
+ useEffect(() => {
+ if (userRole === 'Admin') {
+ setCanDisable(true);
+ }
+ else if (userRole === 'Creator' && userId === initialValues.ownerId) {
+ setCanDisable(true);
+ }
+ }, [initialValues]);
+
const renderForm = () => (
('published')} label='Publish (Display on the explore page)' disabled={!editing}/>
-
- ('disabled')} label='Disable Module' disabled={!editing}/>
-
+ {canDisable &&
+
+ ('disabled')} label='Disable Module' disabled={!editing}/>
+
+ }
Type
('type')} dropdownData={moduleTypeOptions} disabled={!editing}/>
@@ -141,7 +157,7 @@ export default function ModuleEditor({match: {params: {moduleId}}}: Props) {
Once you save your changes you can add and remove labs from this module. Note: Adding or editing a lab will cancel changes on this page
{values.id !== 0 &&
- ('labs')} moduleId={values.id}/>
+ ('labs')} moduleId={values.id} disabledModule={values.disabled}/>
}
diff --git a/src/pages/ModuleEditor/ModuleEditorSchema.ts b/src/pages/ModuleEditor/ModuleEditorSchema.ts
index 056e5305..6a381bb7 100644
--- a/src/pages/ModuleEditor/ModuleEditorSchema.ts
+++ b/src/pages/ModuleEditor/ModuleEditorSchema.ts
@@ -11,7 +11,7 @@ export const ModuleEditorSchema = object({
updatedAt: string().notRequired(),
createdAt: string().notRequired(),
id: number(),
- userId: number(),
+ ownerId: number(),
type: string().oneOf(moduleTypes) as StringSchema,
published: boolean(),
labs: array()
diff --git a/src/pages/PublicModule/PublicModule.tsx b/src/pages/PublicModule/PublicModule.tsx
index 1243815f..5a2a6b08 100644
--- a/src/pages/PublicModule/PublicModule.tsx
+++ b/src/pages/PublicModule/PublicModule.tsx
@@ -31,7 +31,7 @@ class PublicModule extends Component {
disabled: false,
name: 'Loading',
published: false,
- userId: 0,
+ ownerId: 0,
specialCode: '',
type: 'SingleUser',
updatedAt: ''
diff --git a/src/pages/UserModulePage/UserModulePage.tsx b/src/pages/UserModulePage/UserModulePage.tsx
index f68bae6b..82f0dcf1 100644
--- a/src/pages/UserModulePage/UserModulePage.tsx
+++ b/src/pages/UserModulePage/UserModulePage.tsx
@@ -35,7 +35,7 @@ class UserModulePage extends Component
Date: Sat, 30 Oct 2021 20:00:47 -0400
Subject: [PATCH 05/17] Remove code smells
---
src/App.tsx | 2 -
src/pages/ModuleEditor/ModuleEditor.tsx | 5 +--
src/util/LoadingButton.tsx | 49 ++++++++++++++-----------
3 files changed, 29 insertions(+), 27 deletions(-)
diff --git a/src/App.tsx b/src/App.tsx
index e276ca7e..9421e062 100644
--- a/src/App.tsx
+++ b/src/App.tsx
@@ -2,7 +2,6 @@ import * as React from 'react';
import * as Sentry from '@sentry/browser';
import Routes from './router/Routes';
import {CapsLockContextProvider} from './components/CapsLockContext/CapsLockContext';
-// import LabEditorGUI from './pages/LabEditorGUI/LabEditorGUI';
// Configure Sentry
// Sentry.init({
@@ -30,7 +29,6 @@ class App extends React.Component {
- //
);
}
}
diff --git a/src/pages/ModuleEditor/ModuleEditor.tsx b/src/pages/ModuleEditor/ModuleEditor.tsx
index bcd1b684..cbddd0f2 100644
--- a/src/pages/ModuleEditor/ModuleEditor.tsx
+++ b/src/pages/ModuleEditor/ModuleEditor.tsx
@@ -93,10 +93,7 @@ export default function ModuleEditor({match: {params: {moduleId}}}: Props) {
}, [moduleId]);
React.useEffect(() => {
- if (userRole === 'Admin') {
- setCanDisable(true);
- }
- else if (userRole === 'Creator' && userId === initialValues.ownerId) {
+ if (userRole === 'Admin' || (userRole === 'Creator' && userId === initialValues.ownerId)) {
setCanDisable(true);
}
}, [initialValues]);
diff --git a/src/util/LoadingButton.tsx b/src/util/LoadingButton.tsx
index 336cedf4..6fe1802d 100644
--- a/src/util/LoadingButton.tsx
+++ b/src/util/LoadingButton.tsx
@@ -13,26 +13,27 @@ interface Props {
onClick?: onClick;
}
-export const LoadingButton = (props: Props) => (
- <>
-
- {props.disabled
- ? <>
-
-
-
-
- >
- :