} />
} />
} />
- } />
+ {/* } /> */}
+ } />
+ } />
} />
diff --git a/ui/src/features/exclusions/exclusions.js b/ui/src/features/exclusions/exclusions.js
new file mode 100644
index 0000000..59d2568
--- /dev/null
+++ b/ui/src/features/exclusions/exclusions.js
@@ -0,0 +1,297 @@
+import * as React from "react";
+import { styled } from "@mui/material/styles";
+
+import { useSnackbar } from "notistack";
+
+import { useMsal } from "@azure/msal-react";
+import { InteractionRequiredAuthError } from "@azure/msal-browser";
+
+import { isEqual } from 'lodash';
+
+import { DataGrid, GridOverlay } from "@mui/x-data-grid";
+
+import {
+ Box,
+ Typography,
+ LinearProgress,
+ Tooltip,
+ IconButton
+} from "@mui/material";
+
+import {
+ SaveAlt
+} from "@mui/icons-material";
+
+import { fetchSubscriptions } from "../ipam/ipamAPI";
+
+import { apiRequest } from "../../msal/authConfig";
+
+// Page Styles
+
+const Wrapper = styled("div")(({ theme }) => ({
+ display: "flex",
+ flexGrow: 1,
+ height: "calc(100vh - 160px)"
+}));
+
+const MainBody = styled("div")({
+ display: "flex",
+ height: "100%",
+ width: "100%",
+ flexDirection: "column",
+});
+
+const FloatingHeader = styled("div")(({ theme }) => ({
+ ...theme.typography.h6,
+ display: "flex",
+ flexDirection: "row",
+ height: "7%",
+ width: "100%",
+ border: "1px solid rgba(224, 224, 224, 1)",
+ borderRadius: "4px",
+ marginBottom: theme.spacing(3)
+}));
+
+const HeaderTitle = styled("div")(({ theme }) => ({
+ ...theme.typography.h6,
+ width: "80%",
+ textAlign: "center",
+ alignSelf: "center",
+}));
+
+const TopSection = styled("div")(({ theme }) => ({
+ display: "flex",
+ flexDirection: "column",
+ height: "50%",
+ width: "100%",
+ border: "1px solid rgba(224, 224, 224, 1)",
+ borderRadius: "4px",
+ marginBottom: theme.spacing(1.5)
+}));
+
+const BottomSection = styled("div")(({ theme }) => ({
+ display: "flex",
+ flexDirection: "column",
+ height: "50%",
+ width: "100%",
+ border: "1px solid rgba(224, 224, 224, 1)",
+ borderRadius: "4px",
+ marginTop: theme.spacing(1.5)
+}));
+
+// Grid Styles
+
+const GridHeader = styled("div")({
+ height: "50px",
+ width: "100%",
+ display: "flex",
+ borderBottom: "1px solid rgba(224, 224, 224, 1)",
+});
+
+const GridTitle = styled("div")(({ theme }) => ({
+ ...theme.typography.subtitle1,
+ width: "80%",
+ textAlign: "center",
+ alignSelf: "center",
+}));
+
+const GridBody = styled("div")({
+ height: "100%",
+ width: "100%",
+});
+
+const StyledGridOverlay = styled("div")({
+ display: "flex",
+ flexDirection: "column",
+ alignItems: "center",
+ justifyContent: "center",
+ height: "100%",
+});
+
+function GridSection(props) {
+ const { title, action, columns, rows, loading, onClick } = props;
+
+ function CustomLoadingOverlay() {
+ return (
+
+
+
+
+
+ );
+ }
+
+ function CustomNoRowsOverlay() {
+ return (
+
+
+ No Subscriptions Selected
+
+
+ );
+ }
+
+ const message = `Click to ${action}`;
+
+ return (
+
+
+
+ {title}
+
+
+
+
+ onClick(rowData.row)}
+ loading={loading}
+ components={{
+ LoadingOverlay: CustomLoadingOverlay,
+ NoRowsOverlay: CustomNoRowsOverlay,
+ }}
+ initialState={{
+ sorting: {
+ sortModel: [{ field: 'name', sort: 'asc' }],
+ },
+ }}
+ sx={{
+ "&.MuiDataGrid-root .MuiDataGrid-columnHeader:focus, &.MuiDataGrid-root .MuiDataGrid-cell:focus":
+ {
+ outline: "none",
+ },
+ border: "none",
+ }}
+ />
+
+
+
+ );
+}
+
+const columns = [
+ { field: "subscription_id", headerName: "Subscription ID", headerAlign: "left", align: "left", flex: 1 },
+ { field: "name", headerName: "Subscription Name", headerAlign: "left", align: "left", flex: 2 },
+ { field: "type", headerName: "Subscription Type", headerAlign: "left", align: "left", flex: 0.75 },
+];
+
+export default function ManageExclusions() {
+ const { instance, inProgress, accounts } = useMsal();
+ const { enqueueSnackbar } = useSnackbar();
+
+ const [loading, setLoading] = React.useState(false);
+ const [included, setIncluded] = React.useState([]);
+ const [excluded, setExcluded] = React.useState([]);
+ const [loadedExclusions, setLoadedExclusions] = React.useState([]);
+ const [sending, setSending] = React.useState(false);
+
+ const unchanged = isEqual(excluded, loadedExclusions);
+
+ React.useEffect(() => {
+ const request = {
+ scopes: apiRequest.scopes,
+ account: accounts[0],
+ };
+
+ (async () => {
+ try {
+ setLoading(true);
+ const response = await instance.acquireTokenSilent(request);
+ const data = await fetchSubscriptions(response.accessToken);
+
+ setIncluded(data);
+ } catch (e) {
+ if (e instanceof InteractionRequiredAuthError) {
+ instance.acquireTokenRedirect(request);
+ } else {
+ console.log("ERROR");
+ console.log("------------------");
+ console.log(e);
+ console.log("------------------");
+ enqueueSnackbar("Error fetching Subscriptions", { variant: "error" });
+ }
+ } finally {
+ setLoading(false);
+ }
+ })();
+ }, []);
+
+ function subscriptionExclude(elem) {
+ const newArr = included.filter(object => {
+ return object.id !== elem.id;
+ });
+
+ setIncluded(newArr);
+
+ setExcluded(excluded => [...excluded, elem]);
+ }
+
+ function subscriptionInclude(elem) {
+ const newArr = excluded.filter(object => {
+ return object.id !== elem.id;
+ });
+
+ setExcluded(newArr);
+
+ setIncluded(included => [...included, elem]);
+ }
+
+ return (
+
+
+
+
+ Subscription Management
+
+
+ console.log("CLICK")}
+ >
+
+
+
+
+
+
+ ({...x, renderCell: renderExclude}))}
+ columns={columns}
+ rows={included}
+ loading={loading}
+ onClick={subscriptionExclude}
+ />
+
+
+ ({...x, renderCell: renderInclude}))}
+ columns={columns}
+ rows={excluded}
+ loading={false}
+ onClick={subscriptionInclude}
+ />
+
+
+
+ );
+}
diff --git a/ui/src/features/ipam/ipamAPI.js b/ui/src/features/ipam/ipamAPI.js
index c0ed3ff..97a4f7f 100644
--- a/ui/src/features/ipam/ipamAPI.js
+++ b/ui/src/features/ipam/ipamAPI.js
@@ -235,6 +235,23 @@ export function deleteBlockResvs(token, space, block, body) {
});
}
+export function fetchSubscriptions(token) {
+ var url = new URL(`${ENGINE_URL}/api/azure/subscription`);
+
+ return axios
+ .get(url, {
+ headers: {
+ Authorization: `Bearer ${token}`
+ }
+ })
+ .then(response => response.data)
+ .catch(error => {
+ console.log("ERROR FETCHING SUBSCRIPTIONS FROM API");
+ console.log(error);
+ throw error;
+ });
+}
+
export function fetchVNets(token) {
var url = new URL(`${ENGINE_URL}/api/azure/vnet`);
diff --git a/ui/src/features/tabs/adminTabs.js b/ui/src/features/tabs/adminTabs.js
new file mode 100644
index 0000000..dc37c2d
--- /dev/null
+++ b/ui/src/features/tabs/adminTabs.js
@@ -0,0 +1,64 @@
+import * as React from 'react';
+import { Link, useLocation } from "react-router-dom";
+
+import PropTypes from 'prop-types';
+import Tabs from '@mui/material/Tabs';
+import Tab from '@mui/material/Tab';
+import Box from '@mui/material/Box';
+
+import Administration from '../admin/admin2';
+import ManageExclusions from '../exclusions/exclusions';
+
+function TabPanel(props) {
+ const { children, value, index, ...other } = props;
+
+ return (
+
+ {value === index && (
+
+ {children}
+
+ )}
+
+ );
+}
+
+TabPanel.propTypes = {
+ children: PropTypes.node,
+ index: PropTypes.number.isRequired,
+ value: PropTypes.number.isRequired,
+};
+
+function a11yProps(index) {
+ return {
+ id: `simple-tab-${index}`,
+ 'aria-controls': `simple-tabpanel-${index}`,
+ };
+}
+
+export default function AdminTabs() {
+ const allTabs = ['/admin/admins', '/admin/subscriptions'];
+
+ let location = useLocation();
+
+ return (
+
+
+
+
+
+
+
+
+
+
+
+
+ );
+}
diff --git a/ui/src/img/Account.js b/ui/src/img/Account.js
index 965e2a8..eee5652 100644
--- a/ui/src/img/Account.js
+++ b/ui/src/img/Account.js
@@ -8,8 +8,9 @@ function Account() {
height="24"
viewBox="0 0 24 24"
>
-
-
+
+
+
);
}
diff --git a/ui/src/img/Admin.js b/ui/src/img/Admin.js
index a60ad45..f4ad10a 100644
--- a/ui/src/img/Admin.js
+++ b/ui/src/img/Admin.js
@@ -9,8 +9,11 @@ function Admin() {
viewBox="0 0 24 24"
>
-
-
+
+
+
+
+
);
}
diff --git a/ui/src/img/Configure.js b/ui/src/img/Configure.js
index 62900d8..1bb62d2 100644
--- a/ui/src/img/Configure.js
+++ b/ui/src/img/Configure.js
@@ -8,8 +8,8 @@ function Configure() {
height="24"
viewBox="0 0 24 24"
>
-
-
+
+
);
}
diff --git a/ui/src/img/Exclude.js b/ui/src/img/Exclude.js
new file mode 100644
index 0000000..281f926
--- /dev/null
+++ b/ui/src/img/Exclude.js
@@ -0,0 +1,17 @@
+import React from "react";
+
+function Exclude() {
+ return (
+
+ );
+}
+
+export default Exclude;
diff --git a/ui/src/img/Home.js b/ui/src/img/Home.js
index b937e7f..232b928 100644
--- a/ui/src/img/Home.js
+++ b/ui/src/img/Home.js
@@ -8,8 +8,8 @@ function Home() {
height="24"
viewBox="0 0 24 24"
>
-
-
+
+
);
}
diff --git a/ui/src/img/Person.js b/ui/src/img/Person.js
new file mode 100644
index 0000000..8c3a936
--- /dev/null
+++ b/ui/src/img/Person.js
@@ -0,0 +1,17 @@
+import React from "react";
+
+function Person() {
+ return (
+
+ );
+}
+
+export default Person;
diff --git a/ui/src/img/Rule.js b/ui/src/img/Rule.js
new file mode 100644
index 0000000..ed111a7
--- /dev/null
+++ b/ui/src/img/Rule.js
@@ -0,0 +1,17 @@
+import React from "react";
+
+function Rule() {
+ return (
+
+ );
+}
+
+export default Rule;
diff --git a/ui/src/img/account.svg b/ui/src/img/account.svg
index 11302b6..858c764 100644
--- a/ui/src/img/account.svg
+++ b/ui/src/img/account.svg
@@ -1 +1 @@
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/ui/src/img/admin.svg b/ui/src/img/admin.svg
index b47637e..f990551 100644
--- a/ui/src/img/admin.svg
+++ b/ui/src/img/admin.svg
@@ -1 +1 @@
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/ui/src/img/configure.svg b/ui/src/img/configure.svg
index 2ed12a8..27d3c04 100644
--- a/ui/src/img/configure.svg
+++ b/ui/src/img/configure.svg
@@ -1 +1 @@
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/ui/src/img/exclude.svg b/ui/src/img/exclude.svg
new file mode 100644
index 0000000..1bbb881
--- /dev/null
+++ b/ui/src/img/exclude.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/ui/src/img/home.svg b/ui/src/img/home.svg
index 19f6372..b3bbd60 100644
--- a/ui/src/img/home.svg
+++ b/ui/src/img/home.svg
@@ -1 +1 @@
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/ui/src/img/rule.svg b/ui/src/img/rule.svg
new file mode 100644
index 0000000..6e8233a
--- /dev/null
+++ b/ui/src/img/rule.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/ui/src/msal/graph.js b/ui/src/msal/graph.js
index 4b93d4e..32f3b2c 100644
--- a/ui/src/msal/graph.js
+++ b/ui/src/msal/graph.js
@@ -35,24 +35,52 @@ export async function callMsGraphUsers(accessToken) {
.catch((error) => console.log(error));
}
-export async function callMsGraphUsersFilter(accessToken, search) {
- const headers = new Headers();
- const bearer = `Bearer ${accessToken}`;
+export async function callMsGraphUsersFilter(accessToken, nameFilter = "") {
+ const headers = new Headers();
+ const bearer = `Bearer ${accessToken}`;
- headers.append("Authorization", bearer);
+ var endpoint = graphConfig.graphUsersEndpoint + "?";
- const options = {
- method: "GET",
- headers: headers,
- };
+ headers.append("Authorization", bearer);
+ headers.append("ConsistencyLevel", "eventual");
- let filter = `?$filter=startsWith(userPrincipalName,'${search}') OR startsWith(displayName, '${search}')`
+ const options = {
+ method: "GET",
+ headers: headers,
+ };
- return fetch((graphConfig.graphUsersEndpoint + filter), options)
- .then((response) => response.json())
- .catch((error) => console.log(error));
+ if (nameFilter != "") {
+ endpoint += `$filter=startsWith(userPrincipalName,'${nameFilter}') OR startsWith(displayName, '${nameFilter}')&`;
+ }
+
+ endpoint += "$orderby=displayName&$count=true";
+
+ return fetch(endpoint, options)
+ .then((response) => response.json())
+ .catch((error) => console.log(error));
}
+// export async function callMsGraphUsersFilter(accessToken, search) {
+// const headers = new Headers();
+// const bearer = `Bearer ${accessToken}`;
+
+// headers.append("Authorization", bearer);
+// headers.append("ConsistencyLevel", "eventual");
+
+// const options = {
+// method: "GET",
+// headers: headers,
+// };
+
+// let filter = `?$filter=startsWith(userPrincipalName,'${search}') OR startsWith(displayName, '${search}')`
+
+// let sort = "&$orderby=displayName&$count=true";
+
+// return fetch((graphConfig.graphUsersEndpoint + filter + sort), options)
+// .then((response) => response.json())
+// .catch((error) => console.log(error));
+// }
+
export async function callMsGraphPhoto(accessToken) {
const headers = new Headers();
const bearer = `Bearer ${accessToken}`;