diff --git a/ui/src/components/Header/EntHeader.tsx b/ui/src/components/Header/EntHeader.tsx index edf0edcd9..6c94ca0ba 100644 --- a/ui/src/components/Header/EntHeader.tsx +++ b/ui/src/components/Header/EntHeader.tsx @@ -1,4 +1,4 @@ -import React from "react"; +import React, { useEffect, useState } from "react"; import logo from '../assets/MizuEntLogo.svg'; import './Header.sass'; import userImg from '../assets/user-circle.svg'; @@ -12,6 +12,7 @@ import {useSetRecoilState} from "recoil"; import entPageAtom, {Page} from "../../recoil/entPage"; import {useNavigate} from "react-router-dom"; import {RouterRoutes} from "../../helpers/routes"; +import { SettingsModal } from "../SettingsModal/SettingModal"; const api = Api.getInstance(); @@ -23,6 +24,19 @@ interface EntHeaderProps { export const EntHeader: React.FC = ({isFirstLogin, setIsFirstLogin}) => { const navigate = useNavigate(); + const [isSettingsModalOpen, setIsSettingsModalOpen] = useState(false); + + useEffect(() => { + if(isFirstLogin) { + setIsSettingsModalOpen(true) + } + }, [isFirstLogin]) + + const onSettingsModalClose = () => { + setIsSettingsModalOpen(false); + setIsFirstLogin(false); + } + return
@@ -33,9 +47,11 @@ export const EntHeader: React.FC = ({isFirstLogin, setIsFirstLog settings navigate(RouterRoutes.SETTINGS)}/>
+
; } + const ProfileButton = () => { const setEntPage = useSetRecoilState(entPageAtom); diff --git a/ui/src/components/Modals/AddUserModal/AddUserModal.tsx b/ui/src/components/Modals/AddUserModal/AddUserModal.tsx index 502ad5a8c..659e84119 100644 --- a/ui/src/components/Modals/AddUserModal/AddUserModal.tsx +++ b/ui/src/components/Modals/AddUserModal/AddUserModal.tsx @@ -49,6 +49,7 @@ export const AddUserModal: FC = ({isOpen, onCloseModal, userD useEffect(() => { (async () => { try { +<<<<<<< HEAD // const workspacesList = [ // { // "id": "f54b18ec-aa15-4b2c-a4d5-8eda17e44c93", @@ -63,6 +64,21 @@ export const AddUserModal: FC = ({isOpen, onCloseModal, userD const list = await api.getWorkspaces() const workspacesList = list.map((obj) => {return {key:obj.id, value:obj.name,isChecked:false}}) setWorkspaces(workspacesList) +======= + const workspacesList = [ + { + "id": "f54b18ec-aa15-4b2c-a4d5-8eda17e44c93", + "name": "sock-shop" + }, + { + "id": "c7ad9158-d840-46c0-b5ce-2487c013723f", + "name": "test" + } + ].map((obj) => {return {key:obj.id, value:obj.name}}) + //await api.getWorkspaces() + setWorkspaces(workspacesList) + +>>>>>>> origin/feature/ui/TRA-4192_workspace_management } catch (e) { toast.error("Error finding workspaces") } @@ -256,8 +272,8 @@ export const AddUserModal: FC = ({isOpen, onCloseModal, userD setSearchValue(event.target.value)}/>
- + diff --git a/ui/src/components/Modals/AddWorkspaceModal/AddWorkspaceModal.tsx b/ui/src/components/Modals/AddWorkspaceModal/AddWorkspaceModal.tsx index eaf2d0614..cfcfb5fd9 100644 --- a/ui/src/components/Modals/AddWorkspaceModal/AddWorkspaceModal.tsx +++ b/ui/src/components/Modals/AddWorkspaceModal/AddWorkspaceModal.tsx @@ -4,8 +4,10 @@ import { useCommonStyles } from '../../../helpers/commonStyle'; import ConfirmationModal from '../../UI/Modals/ConfirmationModal'; import SelectList from '../../UI/SelectList'; import './AddWorkspaceModal.sass' +import { toast } from "react-toastify"; export type WorkspaceData = { + id:string; name:string; namespaces: string[]; } @@ -13,34 +15,38 @@ export type WorkspaceData = { interface AddWorkspaceModalProp { isOpen : boolean, onCloseModal: () => void, - workspaceData: WorkspaceData, + workspaceId: string, onEdit: boolean } - const api = Api.getInstance(); -const AddWorkspaceModal: FC = ({isOpen,onCloseModal, workspaceData ={}, onEdit}) => { +const AddWorkspaceModal: FC = ({isOpen,onCloseModal, workspaceId, onEdit}) => { - const [workspaceDataModel, setUserData] = useState(workspaceData as WorkspaceData); const [searchValue, setSearchValue] = useState(""); - const classes = useCommonStyles() - const [namespaces, setNamespaces] = useState({}); + + const [workspaceName, setWorkspaceName] = useState(""); + const [checkedNamespacesKeys, setCheckedNamespacesKeys] = useState([]); + const [namespaces, setNamespaces] = useState([]); + + const classes = useCommonStyles(); const title = onEdit ? "Edit Workspace" : "Add Workspace"; - useEffect(() => { if(!isOpen) return; (async () => { try { - setSearchValue(""); - const tapConfig = await api.getTapConfig(); - const namespacesObj = {...tapConfig?.tappedNamespaces} - Object.keys(tapConfig?.tappedNamespaces ?? {}).forEach(namespace => { - namespacesObj[namespace] = true; + if(onEdit){ + const workspace = await api.getSpecificWorkspace(workspaceId); + setWorkspaceName(workspace.name); + setCheckedNamespacesKeys(workspace.namespaces); + } + setSearchValue(""); + const namespaces = await api.getNamespaces(); + const namespacesMapped = namespaces.map(namespace => { + return {key: namespace, value: namespace} }) - setNamespaces(namespacesObj); - setNamespaces(tapConfig?.tappedNamespaces); + setNamespaces(namespacesMapped); } catch (e) { console.error(e); } finally { @@ -48,23 +54,70 @@ const AddWorkspaceModal: FC = ({isOpen,onCloseModal, work })() }, [isOpen]) - const onConfirm = () => {} + const onWorkspaceNameChange = (event) => { + setWorkspaceName(event.target.value); + } + + const isFormValid = () : boolean => { + return (workspaceName.length > 0) && (checkedNamespacesKeys.length > 0); + } + + const onConfirm = async () => { + try{ + const workspaceData = { + name: workspaceName, + namespaces: checkedNamespacesKeys + } + if(onEdit){ + await api.editWorkspace(workspaceId, workspaceData); + toast.success("Workspace Succesesfully Updated"); + } + else{ + await api.createWorkspace(workspaceData); + toast.success("Workspace Succesesfully Created "); + } + resetForm(); + onCloseModal(); + } catch{ + toast.error("Couldn't Creat The Worksapce"); + } + } + + const onClose = () => { + onCloseModal(); + resetForm(); + } + + const resetForm = () => { + setWorkspaceName(""); + setCheckedNamespacesKeys([]); + setNamespaces([]); + } return (<> - -

DETAILS

-
- {}}> -
-

TAP SETTINGS

-
-
- setSearchValue(event.target.value)}/> -
- + +

DETAILS

+
+
+
+
+

TAP SETTINGS

+
+
+ setSearchValue(event.target.value)}/> +
+ + +
); }; diff --git a/ui/src/components/Pages/AuthPage/InstallPage.tsx b/ui/src/components/Pages/AuthPage/InstallPage.tsx index 5565c2299..f4936e401 100644 --- a/ui/src/components/Pages/AuthPage/InstallPage.tsx +++ b/ui/src/components/Pages/AuthPage/InstallPage.tsx @@ -38,7 +38,7 @@ export const InstallPage: React.FC = ({onFirstLogin}) => { try { setIsLoading(true); - await api.register(adminUsername, password); + await api.setupAdminUser(adminUsername, password); if (!await api.isAuthenticationNeeded()) { setEntPage(Page.Traffic); onFirstLogin(); diff --git a/ui/src/components/SettingsModal/SettingModal.tsx b/ui/src/components/SettingsModal/SettingModal.tsx index 9535bb8fb..0dfbb9ad1 100644 --- a/ui/src/components/SettingsModal/SettingModal.tsx +++ b/ui/src/components/SettingsModal/SettingModal.tsx @@ -19,7 +19,8 @@ const api = Api.getInstance(); export const SettingsModal: React.FC = ({isOpen, onClose, isFirstLogin}) => { const classes = useCommonStyles(); - const [namespaces, setNamespaces] = useState({}); + const [namespaces, setNamespaces] = useState([]); + const [checkedNamespacesKeys, setCheckedNamespacesKeys] = useState([]); const [isLoading, setIsLoading] = useState(false); const [searchValue, setSearchValue] = useState(""); @@ -29,16 +30,21 @@ export const SettingsModal: React.FC = ({isOpen, onClose, is try { setSearchValue(""); setIsLoading(true); - const tapConfig = await api.getTapConfig() - if(isFirstLogin) { - const namespacesObj = {...tapConfig?.tappedNamespaces} - Object.keys(tapConfig?.tappedNamespaces ?? {}).forEach(namespace => { - namespacesObj[namespace] = true; - }) - setNamespaces(namespacesObj); - } else { - setNamespaces(tapConfig?.tappedNamespaces); - } + // const tapConfig = await api.getTapConfig() + const namespaces = await api.getNamespaces(); + const namespacesMapped = namespaces.map(namespace => { + return {key: namespace, value: namespace} + }) + setNamespaces(namespacesMapped); + // if(isFirstLogin) { + // const namespacesObj = {...tapConfig?.tappedNamespaces} + // Object.keys(tapConfig?.tappedNamespaces ?? {}).forEach(namespace => { + // namespacesObj[namespace] = true; + // }) + // setNamespaces(namespacesObj); + // } else { + // setNamespaces(tapConfig?.tappedNamespaces); + // } } catch (e) { console.error(e); } finally { @@ -49,7 +55,11 @@ export const SettingsModal: React.FC = ({isOpen, onClose, is const updateTappingSettings = async () => { try { - await api.setTapConfig(namespaces); + const defaultWorkspace = { + name: "default", + namespaces: checkedNamespacesKeys + } + await api.createWorkspace(defaultWorkspace); onClose(); toast.success("Saved successfully"); } catch (e) { @@ -78,7 +88,7 @@ export const SettingsModal: React.FC = ({isOpen, onClose, is
Tapping Settings
- Please choose from below the namespaces for tapping, traffic for namespaces selected will be displayed + Please choose from below the namespaces for tapping, traffic for namespaces selected will be displayed as default workspace.
{isLoading ?
spinner @@ -87,7 +97,7 @@ export const SettingsModal: React.FC = ({isOpen, onClose, is
setSearchValue(event.target.value)}/>
- +
}
diff --git a/ui/src/components/UI/SelectList.tsx b/ui/src/components/UI/SelectList.tsx index 08f79367f..c1ac426c4 100644 --- a/ui/src/components/UI/SelectList.tsx +++ b/ui/src/components/UI/SelectList.tsx @@ -1,70 +1,64 @@ -import { useMemo, useState } from "react"; +import { useEffect, useMemo } from "react"; import Checkbox from "./Checkbox" import Radio from "./Radio"; import './style/SelectList.sass'; -export interface Props { - valuesListInput; - tableName:string; - multiSelect:boolean; - searchValue?:string; - setValues: (newValues)=> void; - tabelClassName -} - export type ValuesListInput = { key: string; value: string; - isChecked: boolean; }[] +export interface Props { + items; + tableName:string; + checkedValues?:string[]; + multiSelect:boolean; + searchValue?:string; + setCheckedValues: (newValues)=> void; + tabelClassName +} -const SelectList: React.FC = ({valuesListInput ,tableName,multiSelect=true,searchValue="",setValues,tabelClassName}) => { - const [valuesList, setValuesList] = useState(valuesListInput as ValuesListInput); +const SelectList: React.FC = ({items ,tableName,checkedValues=[],multiSelect=true,searchValue="",setCheckedValues,tabelClassName}) => { + + const filteredValues = useMemo(() => { + return items.filter((listValue) => listValue?.value?.includes(searchValue)); + },[items, searchValue]) - const toggleValues = (checkedKey) => { + const toggleValue = (checkedKey) => { if (!multiSelect){ - unToggleAll(checkedKey); - } - else { - const newValues: ValuesListInput = [...valuesList]; - newValues.map(item => item.key === checkedKey ? item.isChecked = !item.isChecked : item.isChecked); - setValuesList(newValues); - setValues(newValues); + unToggleAll(); } + const newCheckedValues = [...checkedValues]; + let index = newCheckedValues.indexOf(checkedKey); + if(index > -1) newCheckedValues.splice(index,1); + else newCheckedValues.push(checkedKey); + setCheckedValues(newCheckedValues); } - const unToggleAll = (checkedKey) => { - const list = valuesList.map((obj) => { - return {...obj, isChecked:checkedKey === obj.key} - }) - setValuesList(list); - setValues(list); + const unToggleAll = () => { + setCheckedValues([]); } - const toggleAll = () => { - const list = valuesList.map((obj) => { - return {...obj, isChecked: true} - }) - setValuesList(list); - setValues(list); + const newCheckedValues = [...checkedValues]; + if(newCheckedValues.length === items.length) setCheckedValues([]); + else { + items.forEach((obj) => { + if(!newCheckedValues.includes(obj.key)) + newCheckedValues.push(obj.key); + }) + setCheckedValues(newCheckedValues); + } } - - const tableHead = multiSelect ? - - valueTap.isChecked === false)} + const tableHead = multiSelect ? + {tableName} : - + {tableName} - const filteredValues = useMemo(() => { - return valuesList.filter((listValue) => listValue?.value?.includes(searchValue)); - },[valuesList, searchValue]) - return
@@ -74,8 +68,8 @@ const SelectList: React.FC = ({valuesListInput ,tableName,multiSelect=tru {filteredValues?.map(listValue => { return diff --git a/ui/src/components/UserSettings/UserSettings.tsx b/ui/src/components/UserSettings/UserSettings.tsx index b14aba6bb..9021c7074 100644 --- a/ui/src/components/UserSettings/UserSettings.tsx +++ b/ui/src/components/UserSettings/UserSettings.tsx @@ -4,15 +4,6 @@ import {ColsType, FilterableTableAction} from "../UI/FilterableTableAction" import { useEffect, useState } from "react"; import { UserData,AddUserModal } from "../Modals/AddUserModal/AddUserModal"; import Api from '../../helpers/api'; - -import {Snackbar} from "@material-ui/core"; -import MuiAlert from "@material-ui/lab/Alert"; -import { Select } from "../UI/Select"; -import { MenuItem } from "@material-ui/core"; -import { settings } from "cluster"; -import { SettingsModal } from "../SettingsModal/SettingModal"; -import OasModal from "../Modals/OasModal/OasModal"; -import { apiDefineProperty } from "mobx/dist/internal"; import { toast } from "react-toastify"; import ConfirmationModal from "../UI/Modals/ConfirmationModal"; diff --git a/ui/src/components/WorkspaceSettings/WorkspaceSettings.tsx b/ui/src/components/WorkspaceSettings/WorkspaceSettings.tsx index 8c3a0ff9c..937757027 100644 --- a/ui/src/components/WorkspaceSettings/WorkspaceSettings.tsx +++ b/ui/src/components/WorkspaceSettings/WorkspaceSettings.tsx @@ -1,45 +1,64 @@ import "../UserSettings/UserSettings.sass" import {ColsType, FilterableTableAction} from "../UI/FilterableTableAction" -// import Api from "../../helpers/api" +import Api from "../../helpers/api" import { useEffect, useState } from "react"; import AddWorkspaceModal, { WorkspaceData } from "../Modals/AddWorkspaceModal/AddWorkspaceModal"; +import { toast } from "react-toastify"; +import ConfirmationModal from "../UI/Modals/ConfirmationModal"; interface Props {} -// const api = Api.getInstance(); +const api = Api.getInstance(); export const WorkspaceSettings : React.FC = ({}) => { const [workspacesRows, setWorkspacesRows] = useState([]); + const cols : ColsType[] = [{field : "name",header:"Name"}]; + const [workspaceData,SetWorkspaceData] = useState({} as WorkspaceData); const [isOpenModal,setIsOpen] = useState(false); const [isEditMode,setIsEditMode] = useState(false); - - const cols : ColsType[] = [{field : "id",header:"Id"},{field : "name",header:"Name"}]; + const [isOpenDeleteModal, setIsOpenDeleteModal] = useState(false); const buttonConfig = {onClick: () => {setIsOpen(true); setIsEditMode(false);SetWorkspaceData({} as WorkspaceData)}, text:"Add Workspace"} useEffect(() => { (async () => { try { - const workspacesDemo = [{id:"1", name:"Worksapce1"}] - setWorkspacesRows(workspacesDemo) + const workspaces = await api.getWorkspaces(); + setWorkspacesRows(workspaces) } catch (e) { console.error(e); } })(); - },[]) + },[isOpenModal]) const filterFuncFactory = (searchQuery: string) => { - return (row) => row.name.toLowerCase().includes(searchQuery.toLowerCase()) + return (row) => { + return row.name.toLowerCase().includes(searchQuery.toLowerCase()); } + } - const searchConfig = { searchPlaceholder: "Search Workspace",filterRows: filterFuncFactory} + const searchConfig = { searchPlaceholder: "Search Workspace",filterRows: filterFuncFactory}; - const onRowDelete = (row) => { - const filterFunc = filterFuncFactory(row.name) - const newWorkspaceList = workspacesRows.filter(filterFunc) - setWorkspacesRows(newWorkspaceList) + const onRowDelete = async (workspace) => { + setIsOpenDeleteModal(true); + SetWorkspaceData(workspace); + } + + const onDeleteConfirmation = () => { + (async() => { + try{ + const workspaceLeft = workspacesRows.filter(ws => ws.id != workspaceData.id); + setWorkspacesRows(workspaceLeft); + await api.deleteWorkspace(workspaceData.id); + setIsOpenDeleteModal(false); + SetWorkspaceData({} as WorkspaceData); + toast.success("Workspace Succesesfully Deleted "); + } catch { + toast.error("Workspace hasn't deleted"); + } + })(); } const onRowEdit = (row) => { @@ -47,16 +66,18 @@ export const WorkspaceSettings : React.FC = ({}) => { setIsEditMode(true); SetWorkspaceData(row); } - - + return (<> - { setIsOpen(false);} } > - - + { setIsOpen(false);} } > + setIsOpenDeleteModal(false)} + onConfirm={onDeleteConfirmation} confirmButtonText="Delete Workspace" title="Delete Workspace" + confirmButtonColor="#DB2156"> +

Are you sure you want to delete this workspace?

+
); } diff --git a/ui/src/helpers/api.js b/ui/src/helpers/api.js index b0b6c3762..65349201c 100644 --- a/ui/src/helpers/api.js +++ b/ui/src/helpers/api.js @@ -83,6 +83,31 @@ export default class Api { return response.data; } + getSpecificWorkspace = async(workspaceId) =>{ + const response = await this.client.get(`/workspace/${workspaceId}`); + return response.data; + } + + createWorkspace = async(workspaceData) =>{ + const response = await this.client.post(`/workspace`,workspaceData); + return response.data; + } + + editWorkspace = async(workspaceId, workspaceData) =>{ + const response = await this.client.put(`/workspace/${workspaceId}`,workspaceData); + return response.data; + } + + deleteWorkspace = async(workspaceId) => { + const response = await this.client.delete(`/workspace/${workspaceId}`); + return response.data; + } + + getNamespaces = async() =>{ + const response = await this.client.get(`/config/namespaces`); + return response.data; + } + analyzeStatus = async () => { const response = await this.client.get("/status/analyze"); return response.data; @@ -145,7 +170,7 @@ export default class Api { } getTapConfig = async () => { - const response = await this.client.get("/config/tapConfig"); + const response = await this.client.get("/config/tap"); return response.data; } @@ -171,27 +196,48 @@ export default class Api { } } - register = async (username, password) => { + // register = async (username, password) => { + // const form = new FormData(); + // form.append('username', username); + // form.append('password', password); - const form = new FormData(); - form.append('password', password); - - try { - const response = await this.client.post(`/install/admin`, form); - this.persistToken(response.data.token); - return response; - } catch (e) { - if (e.response.status === 400) { - const error = { - 'type': FormValidationErrorType, - 'messages': e.response.data - }; - throw error; - } else { - throw e; - } + // try { + // const response = await this.client.post(`/user/register`, form); + // this.persistToken(response.data.token); + // return response; + // } catch (e) { + // if (e.response.status === 400) { + // const error = { + // 'type': FormValidationErrorType, + // 'messages': e.response.data + // }; + // throw error; + // } else { + // throw e; + // } + // } + // } + + setupAdminUser = async (password) => { + const form = new FormData(); + form.append('password', password); + + try { + const response = await this.client.post(`/install/admin`, form); + this.persistToken(response.data.token); + return response; + } catch (e) { + if (e.response.status === 400) { + const error = { + 'type': FormValidationErrorType, + 'messages': e.response.data + }; + throw error; + } else { + throw e; } } + } login = async (username, password) => {
- {multiSelect && item.key === listValue.key)?.isChecked} onToggle={() => toggleValues(listValue.key)}/>} - {!multiSelect && item.key === listValue.key)?.isChecked} onToggle={() => toggleValues(listValue.key)}/>} + {multiSelect && toggleValue(listValue.key)}/>} + {!multiSelect && toggleValue(listValue.key)}/>} {listValue.value}