From b8fa08b53ce5b1cddb204834292354dc75324320 Mon Sep 17 00:00:00 2001
From: Gabriel Hernandez
Date: Mon, 6 Mar 2023 15:03:48 +0000
Subject: [PATCH] implement mdm scripts page UI (#10092)
relates to #9831
Implements the mdm mac OS scripts UI. This is just the UI atm and is not
accessible in the application at the moment.
---
.vscode/typescriptreact.code-snippets | 2 +-
frontend/components/icons/FileBash.tsx | 57 +++++
frontend/components/icons/FileGeneric.tsx | 68 ++++++
frontend/components/icons/FilePython.tsx | 57 +++++
frontend/components/icons/FileZsh.tsx | 57 +++++
frontend/components/icons/Files.tsx | 203 ++++++++++++++++++
frontend/components/icons/Refresh.tsx | 16 ++
frontend/components/icons/index.ts | 12 ++
frontend/interfaces/mdm.ts | 15 +-
.../MacOSScripts/MacOSScripts.tsx | 111 ++++++++++
.../MacOSScripts/_styles.scss | 7 +
.../DeleteScriptModal/DeleteScriptModal.tsx | 52 +++++
.../components/DeleteScriptModal/_styles.scss | 5 +
.../components/DeleteScriptModal/index.ts | 1 +
.../RerunScriptModal/RerunScriptModal.tsx | 61 ++++++
.../components/RerunScriptModal/_styles.scss | 10 +
.../components/RerunScriptModal/index.ts | 1 +
.../ScriptListHeading/ScriptListHeading.tsx | 78 +++++++
.../components/ScriptListHeading/_styles.scss | 47 ++++
.../components/ScriptListHeading/index.ts | 1 +
.../ScriptListItem/ScriptListItem.tsx | 95 ++++++++
.../components/ScriptListItem/_styles.scss | 69 ++++++
.../components/ScriptListItem/index.ts | 1 +
.../ManageControlsPage/MacOSScripts/index.ts | 1 +
.../cards/CustomSettings/CustomSettings.tsx | 97 ++-------
.../cards/CustomSettings/_styles.scss | 51 -----
.../ProfileListHeading/ProfileListHeading.tsx | 14 ++
.../ProfileListHeading/_styles.scss | 11 +
.../components/ProfileListHeading/index.ts | 1 +
.../ProfileListItem/ProfileListItem.tsx | 60 ++++++
.../components/ProfileListItem/_styles.scss | 34 +++
.../components/ProfileListItem/index.ts | 1 +
.../components/FileUploader/FileUploader.tsx | 39 ++++
.../components/FileUploader/_styles.scss | 16 ++
.../components/FileUploader/index.ts | 1 +
.../components/UploadList/UploadList.tsx | 34 +++
.../components/UploadList/_styles.scss | 19 ++
.../components/UploadList/index.ts | 1 +
frontend/router/paths.ts | 1 +
39 files changed, 1276 insertions(+), 131 deletions(-)
create mode 100644 frontend/components/icons/FileBash.tsx
create mode 100644 frontend/components/icons/FileGeneric.tsx
create mode 100644 frontend/components/icons/FilePython.tsx
create mode 100644 frontend/components/icons/FileZsh.tsx
create mode 100644 frontend/components/icons/Files.tsx
create mode 100644 frontend/components/icons/Refresh.tsx
create mode 100644 frontend/pages/ManageControlsPage/MacOSScripts/MacOSScripts.tsx
create mode 100644 frontend/pages/ManageControlsPage/MacOSScripts/_styles.scss
create mode 100644 frontend/pages/ManageControlsPage/MacOSScripts/components/DeleteScriptModal/DeleteScriptModal.tsx
create mode 100644 frontend/pages/ManageControlsPage/MacOSScripts/components/DeleteScriptModal/_styles.scss
create mode 100644 frontend/pages/ManageControlsPage/MacOSScripts/components/DeleteScriptModal/index.ts
create mode 100644 frontend/pages/ManageControlsPage/MacOSScripts/components/RerunScriptModal/RerunScriptModal.tsx
create mode 100644 frontend/pages/ManageControlsPage/MacOSScripts/components/RerunScriptModal/_styles.scss
create mode 100644 frontend/pages/ManageControlsPage/MacOSScripts/components/RerunScriptModal/index.ts
create mode 100644 frontend/pages/ManageControlsPage/MacOSScripts/components/ScriptListHeading/ScriptListHeading.tsx
create mode 100644 frontend/pages/ManageControlsPage/MacOSScripts/components/ScriptListHeading/_styles.scss
create mode 100644 frontend/pages/ManageControlsPage/MacOSScripts/components/ScriptListHeading/index.ts
create mode 100644 frontend/pages/ManageControlsPage/MacOSScripts/components/ScriptListItem/ScriptListItem.tsx
create mode 100644 frontend/pages/ManageControlsPage/MacOSScripts/components/ScriptListItem/_styles.scss
create mode 100644 frontend/pages/ManageControlsPage/MacOSScripts/components/ScriptListItem/index.ts
create mode 100644 frontend/pages/ManageControlsPage/MacOSScripts/index.ts
create mode 100644 frontend/pages/ManageControlsPage/MacOSSettings/cards/CustomSettings/components/ProfileListHeading/ProfileListHeading.tsx
create mode 100644 frontend/pages/ManageControlsPage/MacOSSettings/cards/CustomSettings/components/ProfileListHeading/_styles.scss
create mode 100644 frontend/pages/ManageControlsPage/MacOSSettings/cards/CustomSettings/components/ProfileListHeading/index.ts
create mode 100644 frontend/pages/ManageControlsPage/MacOSSettings/cards/CustomSettings/components/ProfileListItem/ProfileListItem.tsx
create mode 100644 frontend/pages/ManageControlsPage/MacOSSettings/cards/CustomSettings/components/ProfileListItem/_styles.scss
create mode 100644 frontend/pages/ManageControlsPage/MacOSSettings/cards/CustomSettings/components/ProfileListItem/index.ts
create mode 100644 frontend/pages/ManageControlsPage/components/FileUploader/FileUploader.tsx
create mode 100644 frontend/pages/ManageControlsPage/components/FileUploader/_styles.scss
create mode 100644 frontend/pages/ManageControlsPage/components/FileUploader/index.ts
create mode 100644 frontend/pages/ManageControlsPage/components/UploadList/UploadList.tsx
create mode 100644 frontend/pages/ManageControlsPage/components/UploadList/_styles.scss
create mode 100644 frontend/pages/ManageControlsPage/components/UploadList/index.ts
diff --git a/.vscode/typescriptreact.code-snippets b/.vscode/typescriptreact.code-snippets
index 6cde7b7e8d..3b0b77465e 100644
--- a/.vscode/typescriptreact.code-snippets
+++ b/.vscode/typescriptreact.code-snippets
@@ -22,7 +22,7 @@
"scope": "typescriptreact,javascriptreact",
"prefix": "bc",
"body": [
- "`\\${baseClass}__$0`"
+ "className={`\\${baseClass}__$0`}"
]
}
}
diff --git a/frontend/components/icons/FileBash.tsx b/frontend/components/icons/FileBash.tsx
new file mode 100644
index 0000000000..8ffff65e36
--- /dev/null
+++ b/frontend/components/icons/FileBash.tsx
@@ -0,0 +1,57 @@
+import React from "react";
+
+const FileBash = () => {
+ return (
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ );
+};
+
+export default FileBash;
diff --git a/frontend/components/icons/FileGeneric.tsx b/frontend/components/icons/FileGeneric.tsx
new file mode 100644
index 0000000000..c038867e44
--- /dev/null
+++ b/frontend/components/icons/FileGeneric.tsx
@@ -0,0 +1,68 @@
+import React from "react";
+
+const FileGeneric = () => {
+ return (
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ );
+};
+
+export default FileGeneric;
diff --git a/frontend/components/icons/FilePython.tsx b/frontend/components/icons/FilePython.tsx
new file mode 100644
index 0000000000..b7e00f0291
--- /dev/null
+++ b/frontend/components/icons/FilePython.tsx
@@ -0,0 +1,57 @@
+import React from "react";
+
+const FilePython = () => {
+ return (
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ );
+};
+
+export default FilePython;
diff --git a/frontend/components/icons/FileZsh.tsx b/frontend/components/icons/FileZsh.tsx
new file mode 100644
index 0000000000..aee2fe94ad
--- /dev/null
+++ b/frontend/components/icons/FileZsh.tsx
@@ -0,0 +1,57 @@
+import React from "react";
+
+const FileZsh = () => {
+ return (
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ );
+};
+
+export default FileZsh;
diff --git a/frontend/components/icons/Files.tsx b/frontend/components/icons/Files.tsx
new file mode 100644
index 0000000000..61a1522ce5
--- /dev/null
+++ b/frontend/components/icons/Files.tsx
@@ -0,0 +1,203 @@
+import React from "react";
+
+const Files = () => {
+ return (
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ );
+};
+
+export default Files;
diff --git a/frontend/components/icons/Refresh.tsx b/frontend/components/icons/Refresh.tsx
new file mode 100644
index 0000000000..b402beb0b4
--- /dev/null
+++ b/frontend/components/icons/Refresh.tsx
@@ -0,0 +1,16 @@
+import React from "react";
+
+const Refresh = () => {
+ return (
+
+
+
+ );
+};
+
+export default Refresh;
diff --git a/frontend/components/icons/index.ts b/frontend/components/icons/index.ts
index 7329147edc..5644b9cc42 100644
--- a/frontend/components/icons/index.ts
+++ b/frontend/components/icons/index.ts
@@ -42,6 +42,12 @@ import Pencil from "./Pencil";
import TrashCan from "./TrashCan";
import Profile from "./Profile";
import Download from "./Download";
+import Files from "./Files";
+import Refresh from "./Refresh";
+import FilePython from "./FilePython";
+import FileZsh from "./FileZsh";
+import FileBash from "./FileBash";
+import FileGeneric from "./FileGeneric";
// a mapping of the usable names of icons to the icon source.
export const ICON_MAP = {
@@ -86,6 +92,12 @@ export const ICON_MAP = {
"linux-green": LinuxGreen,
profile: Profile,
download: Download,
+ files: Files,
+ "file-python": FilePython,
+ "file-zsh": FileZsh,
+ "file-bash": FileBash,
+ "file-generic": FileGeneric,
+ refresh: Refresh,
};
export type IconNames = keyof typeof ICON_MAP;
diff --git a/frontend/interfaces/mdm.ts b/frontend/interfaces/mdm.ts
index cbc3edd926..fcc546024e 100644
--- a/frontend/interfaces/mdm.ts
+++ b/frontend/interfaces/mdm.ts
@@ -71,12 +71,23 @@ export interface IMdmProfilesResponse {
export type MacMdmProfileStatus = "applied" | "pending" | "failed";
export type MacMdmProfileOperationType = "remove" | "install";
-export type IHostMacMdmProfile = {
+export interface IHostMacMdmProfile {
profile_id: number;
name: string;
operation_type: MacMdmProfileOperationType;
status: MacMdmProfileStatus;
detail: string;
-};
+}
export type IMacSettings = IHostMacMdmProfile[];
export type MacSettingsStatus = "Failing" | "Latest" | "Pending";
+
+// TODO: update when we have API
+export interface IMdmScript {
+ id: number;
+ name: string;
+ ran: number;
+ pending: number;
+ errors: number;
+ created_at: string;
+ updated_at: string;
+}
diff --git a/frontend/pages/ManageControlsPage/MacOSScripts/MacOSScripts.tsx b/frontend/pages/ManageControlsPage/MacOSScripts/MacOSScripts.tsx
new file mode 100644
index 0000000000..5ce01809b7
--- /dev/null
+++ b/frontend/pages/ManageControlsPage/MacOSScripts/MacOSScripts.tsx
@@ -0,0 +1,111 @@
+import React, { useRef, useState } from "react";
+
+import { IMdmScript } from "interfaces/mdm";
+
+import CustomLink from "components/CustomLink";
+
+import ScriptListHeading from "./components/ScriptListHeading";
+import ScriptListItem from "./components/ScriptListItem";
+import DeleteScriptModal from "./components/DeleteScriptModal";
+import FileUploader from "../components/FileUploader";
+import UploadList from "../components/UploadList";
+import RerunScriptModal from "./components/RerunScriptModal";
+
+// TODO: remove when get integrate with API.
+const scripts = [
+ {
+ id: 1,
+ name: "Test.py",
+ ran: 57,
+ pending: 2304,
+ errors: 0,
+ created_at: new Date().toString(),
+ },
+];
+
+const baseClass = "mac-os-scripts";
+
+const MacOSScripts = () => {
+ const [showRerunScriptModal, setShowRerunScriptModal] = useState(false);
+ const [showDeleteScriptModal, setShowDeleteScriptModal] = useState(false);
+
+ const selectedScript = useRef(null);
+
+ const onClickRerun = (script: IMdmScript) => {
+ selectedScript.current = script;
+ setShowRerunScriptModal(true);
+ };
+
+ const onClickDelete = (script: IMdmScript) => {
+ selectedScript.current = script;
+ setShowDeleteScriptModal(true);
+ };
+
+ const onCancelRerun = () => {
+ selectedScript.current = null;
+ setShowRerunScriptModal(false);
+ };
+
+ const onCancelDelete = () => {
+ selectedScript.current = null;
+ setShowDeleteScriptModal(false);
+ };
+
+ // TODO: change when integrating with API
+ const onRerunScript = (scriptId: number) => {
+ console.log("rerun", scriptId);
+ setShowRerunScriptModal(false);
+ };
+
+ // TODO: change when integrating with API
+ const onDeleteScript = (scriptId: number) => {
+ console.log("delete", scriptId);
+ setShowDeleteScriptModal(false);
+ };
+
+ return (
+
+
+ Upload scripts to change configuration and remediate issues on macOS
+ hosts. Each script runs once per host. All scripts can be rerun on end
+ users’ My device page.
+
+
(
+
+ )}
+ />
+ {
+ return null;
+ }}
+ />
+ {showRerunScriptModal && selectedScript.current && (
+
+ )}
+ {showDeleteScriptModal && selectedScript.current && (
+
+ )}
+
+ );
+};
+
+export default MacOSScripts;
diff --git a/frontend/pages/ManageControlsPage/MacOSScripts/_styles.scss b/frontend/pages/ManageControlsPage/MacOSScripts/_styles.scss
new file mode 100644
index 0000000000..f5fa9ef546
--- /dev/null
+++ b/frontend/pages/ManageControlsPage/MacOSScripts/_styles.scss
@@ -0,0 +1,7 @@
+.mac-os-scripts {
+ font-size: $x-small;
+
+ &__description {
+ margin: $pad-xxlarge 0;
+ }
+}
diff --git a/frontend/pages/ManageControlsPage/MacOSScripts/components/DeleteScriptModal/DeleteScriptModal.tsx b/frontend/pages/ManageControlsPage/MacOSScripts/components/DeleteScriptModal/DeleteScriptModal.tsx
new file mode 100644
index 0000000000..c0f5cef6d0
--- /dev/null
+++ b/frontend/pages/ManageControlsPage/MacOSScripts/components/DeleteScriptModal/DeleteScriptModal.tsx
@@ -0,0 +1,52 @@
+import React from "react";
+
+import Modal from "components/Modal";
+import Button from "components/buttons/Button";
+
+const baseClass = "delete-script-modal";
+
+interface IDeleteScriptModalProps {
+ scriptName: string;
+ scriptId: number;
+ onCancel: () => void;
+ onDelete: (scriptId: number) => void;
+}
+
+const DeleteScriptModal = ({
+ scriptName,
+ scriptId,
+ onCancel,
+ onDelete,
+}: IDeleteScriptModalProps) => {
+ return (
+ onDelete(scriptId)}
+ >
+ <>
+
+ This action will cancel script{" "}
+ {scriptName} from
+ running on macOS hosts on which the scrupt hasn't run yet.
+
+
+ onDelete(scriptId)}
+ variant="alert"
+ className="delete-loading"
+ >
+ Delete
+
+
+ Cancel
+
+
+ >
+
+ );
+};
+
+export default DeleteScriptModal;
diff --git a/frontend/pages/ManageControlsPage/MacOSScripts/components/DeleteScriptModal/_styles.scss b/frontend/pages/ManageControlsPage/MacOSScripts/components/DeleteScriptModal/_styles.scss
new file mode 100644
index 0000000000..882315657a
--- /dev/null
+++ b/frontend/pages/ManageControlsPage/MacOSScripts/components/DeleteScriptModal/_styles.scss
@@ -0,0 +1,5 @@
+.delete-script-modal {
+ &__script-name {
+ font-weight: $bold;
+ }
+}
diff --git a/frontend/pages/ManageControlsPage/MacOSScripts/components/DeleteScriptModal/index.ts b/frontend/pages/ManageControlsPage/MacOSScripts/components/DeleteScriptModal/index.ts
new file mode 100644
index 0000000000..6cc0bb99f6
--- /dev/null
+++ b/frontend/pages/ManageControlsPage/MacOSScripts/components/DeleteScriptModal/index.ts
@@ -0,0 +1 @@
+export { default } from "./DeleteScriptModal";
diff --git a/frontend/pages/ManageControlsPage/MacOSScripts/components/RerunScriptModal/RerunScriptModal.tsx b/frontend/pages/ManageControlsPage/MacOSScripts/components/RerunScriptModal/RerunScriptModal.tsx
new file mode 100644
index 0000000000..b8081dbc66
--- /dev/null
+++ b/frontend/pages/ManageControlsPage/MacOSScripts/components/RerunScriptModal/RerunScriptModal.tsx
@@ -0,0 +1,61 @@
+import React, { useContext } from "react";
+
+import { AppContext } from "context/app";
+
+import Modal from "components/Modal";
+import Button from "components/buttons/Button";
+
+const baseClass = "rerun-script-modal";
+
+interface IRerunScriptModalProps {
+ scriptName: string;
+ scriptId: number;
+ onCancel: () => void;
+ onRerun: (scriptId: number) => void;
+}
+
+const generateMessageSuffix = (isPremiumTier?: boolean, teamId?: number) => {
+ if (!isPremiumTier) {
+ return "";
+ }
+ return teamId ? " assigned to this team" : " with no team";
+};
+
+const RerunScriptModal = ({
+ scriptName,
+ scriptId,
+ onCancel,
+ onRerun,
+}: IRerunScriptModalProps) => {
+ const { isPremiumTier, currentTeam } = useContext(AppContext);
+
+ const messageSuffix = generateMessageSuffix(isPremiumTier, currentTeam?.id);
+
+ return (
+ onRerun(scriptId)}
+ >
+ <>
+
+ This action will rerun script{" "}
+ {scriptName} on
+ all macOS hosts {messageSuffix}.
+
+ This may cause the script to run more than once on some hosts.
+
+ onRerun(scriptId)}>
+ Rerun
+
+
+ Cancel
+
+
+ >
+
+ );
+};
+
+export default RerunScriptModal;
diff --git a/frontend/pages/ManageControlsPage/MacOSScripts/components/RerunScriptModal/_styles.scss b/frontend/pages/ManageControlsPage/MacOSScripts/components/RerunScriptModal/_styles.scss
new file mode 100644
index 0000000000..abb85d176e
--- /dev/null
+++ b/frontend/pages/ManageControlsPage/MacOSScripts/components/RerunScriptModal/_styles.scss
@@ -0,0 +1,10 @@
+.rerun-script-modal {
+ p {
+ margin-top: 0;
+ margin-bottom: $pad-xlarge;
+ }
+
+ &__script-name {
+ font-weight: $bold;
+ }
+}
diff --git a/frontend/pages/ManageControlsPage/MacOSScripts/components/RerunScriptModal/index.ts b/frontend/pages/ManageControlsPage/MacOSScripts/components/RerunScriptModal/index.ts
new file mode 100644
index 0000000000..9032eb9c6b
--- /dev/null
+++ b/frontend/pages/ManageControlsPage/MacOSScripts/components/RerunScriptModal/index.ts
@@ -0,0 +1 @@
+export { default } from "./RerunScriptModal";
diff --git a/frontend/pages/ManageControlsPage/MacOSScripts/components/ScriptListHeading/ScriptListHeading.tsx b/frontend/pages/ManageControlsPage/MacOSScripts/components/ScriptListHeading/ScriptListHeading.tsx
new file mode 100644
index 0000000000..8958f7e03a
--- /dev/null
+++ b/frontend/pages/ManageControlsPage/MacOSScripts/components/ScriptListHeading/ScriptListHeading.tsx
@@ -0,0 +1,78 @@
+import React from "react";
+import ReactTooltip from "react-tooltip";
+
+import Icon from "components/Icon";
+import { COLORS } from "styles/var/colors";
+
+const baseClass = "script-list-heading";
+
+const ScriptListHeading = () => {
+ return (
+
+
+ Script
+
+
+
+ Actions
+
+
+
+
+ Script ran and exited with status code 0.
+
+
+
+
+ Script will run when the host comes online.
+
+
+
+
+ Script ran and exited with a non-zero status code. Click on a host to
+ view error(s).
+
+
+
+ );
+};
+
+export default ScriptListHeading;
diff --git a/frontend/pages/ManageControlsPage/MacOSScripts/components/ScriptListHeading/_styles.scss b/frontend/pages/ManageControlsPage/MacOSScripts/components/ScriptListHeading/_styles.scss
new file mode 100644
index 0000000000..19c0502075
--- /dev/null
+++ b/frontend/pages/ManageControlsPage/MacOSScripts/components/ScriptListHeading/_styles.scss
@@ -0,0 +1,47 @@
+.script-list-heading {
+ display: flex;
+ justify-content: space-between;
+
+ &__heading-group {
+ flex: 1;
+ }
+
+ &__script-statuses {
+ display: flex;
+ justify-content: center;
+ }
+
+ &__actions-heading {
+ text-align: right;
+
+ span {
+ margin-right: 95px; // align with left side of buttons below it
+ }
+ }
+
+ &__status > div {
+ display: flex;
+ align-items: center;
+ width: 100px;
+ justify-content: center;
+
+ span {
+ margin-left: 12px;
+ }
+ }
+
+ &__tooltip-text {
+ font-weight: normal;
+ }
+
+ @media (max-width: $break-990) {
+ &__script-statuses {
+ justify-content: flex-end;
+ }
+
+
+ &__actions-heading {
+ display: none;
+ }
+ }
+}
diff --git a/frontend/pages/ManageControlsPage/MacOSScripts/components/ScriptListHeading/index.ts b/frontend/pages/ManageControlsPage/MacOSScripts/components/ScriptListHeading/index.ts
new file mode 100644
index 0000000000..96bbba3ba4
--- /dev/null
+++ b/frontend/pages/ManageControlsPage/MacOSScripts/components/ScriptListHeading/index.ts
@@ -0,0 +1 @@
+export { default } from "./ScriptListHeading";
diff --git a/frontend/pages/ManageControlsPage/MacOSScripts/components/ScriptListItem/ScriptListItem.tsx b/frontend/pages/ManageControlsPage/MacOSScripts/components/ScriptListItem/ScriptListItem.tsx
new file mode 100644
index 0000000000..2928de0259
--- /dev/null
+++ b/frontend/pages/ManageControlsPage/MacOSScripts/components/ScriptListItem/ScriptListItem.tsx
@@ -0,0 +1,95 @@
+import React from "react";
+import { formatDistanceToNow } from "date-fns";
+
+import { IMdmScript } from "interfaces/mdm";
+
+import Icon from "components/Icon";
+import Button from "components/buttons/Button";
+
+const baseClass = "script-list-item";
+
+interface IScriptListItemProps {
+ script: IMdmScript;
+ onRerun: (script: IMdmScript) => void;
+ onDelete: (script: IMdmScript) => void;
+}
+
+const getStatusClassName = (value: number) => {
+ return value !== 0 ? `${baseClass}__has-value` : "";
+};
+
+const getFileIconName = (fileName: string) => {
+ const fileExtension = fileName.split(".").pop();
+
+ switch (fileExtension) {
+ case "py":
+ return "file-python";
+ case "zsh":
+ return "file-zsh";
+ case "sh":
+ return "file-bash";
+ default:
+ return "file-generic";
+ }
+};
+
+const ScriptListItem = ({
+ script,
+ onRerun,
+ onDelete,
+}: IScriptListItemProps) => {
+ const onClickDownload = () => {
+ console.log("download");
+ };
+
+ return (
+
+
+
+
+ {script.name}
+
+ {`Uploaded ${formatDistanceToNow(new Date(script.created_at))} ago`}
+
+
+
+
+ {script.ran}
+
+ {script.pending}
+
+
+ {script.errors}
+
+
+
+
+ onRerun(script)}
+ >
+
+
+
+
+
+ onDelete(script)}
+ >
+
+
+
+
+ );
+};
+
+export default ScriptListItem;
diff --git a/frontend/pages/ManageControlsPage/MacOSScripts/components/ScriptListItem/_styles.scss b/frontend/pages/ManageControlsPage/MacOSScripts/components/ScriptListItem/_styles.scss
new file mode 100644
index 0000000000..fb38997ef7
--- /dev/null
+++ b/frontend/pages/ManageControlsPage/MacOSScripts/components/ScriptListItem/_styles.scss
@@ -0,0 +1,69 @@
+.script-list-item {
+ display: flex;
+ align-items: center;
+
+ &__value-group {
+ flex: 1;
+ }
+
+ &__script-data {
+ display: flex;
+ align-items: center;
+ }
+
+ &__script-info {
+ margin-left: $pad-medium;
+ display: flex;
+ flex-direction: column;
+ }
+
+ &__script-name {
+ font-size: $x-small;
+ }
+
+ &__script-uploaded {
+ font-size: $xx-small;
+ }
+
+ &__script-statuses {
+ display: flex;
+ justify-content: center;
+
+ span {
+ width: 100px;
+ text-align: center;
+ }
+ }
+
+
+ &__has-value {
+ color: $core-vibrant-blue
+ }
+
+ &__script-actions {
+ display: flex;
+ justify-content: flex-end;
+ }
+
+ &__refresh-button,
+ &__download-button,
+ &__delete-button {
+ width: 40px;
+ height: 40px;
+ }
+
+ &__refresh-button,
+ &__download-button {
+ margin-right: $pad-medium;
+ }
+
+ @media (max-width: $break-990) {
+ &__script-statuses {
+ justify-content: flex-end;
+ }
+
+ &__script-actions {
+ display: none;
+ }
+ }
+}
diff --git a/frontend/pages/ManageControlsPage/MacOSScripts/components/ScriptListItem/index.ts b/frontend/pages/ManageControlsPage/MacOSScripts/components/ScriptListItem/index.ts
new file mode 100644
index 0000000000..391ca650f1
--- /dev/null
+++ b/frontend/pages/ManageControlsPage/MacOSScripts/components/ScriptListItem/index.ts
@@ -0,0 +1 @@
+export { default } from "./ScriptListItem";
diff --git a/frontend/pages/ManageControlsPage/MacOSScripts/index.ts b/frontend/pages/ManageControlsPage/MacOSScripts/index.ts
new file mode 100644
index 0000000000..5e3ff82cb5
--- /dev/null
+++ b/frontend/pages/ManageControlsPage/MacOSScripts/index.ts
@@ -0,0 +1 @@
+export { default } from "./MacOSScripts";
diff --git a/frontend/pages/ManageControlsPage/MacOSSettings/cards/CustomSettings/CustomSettings.tsx b/frontend/pages/ManageControlsPage/MacOSSettings/cards/CustomSettings/CustomSettings.tsx
index 95433bcbea..d01aaf7299 100644
--- a/frontend/pages/ManageControlsPage/MacOSSettings/cards/CustomSettings/CustomSettings.tsx
+++ b/frontend/pages/ManageControlsPage/MacOSSettings/cards/CustomSettings/CustomSettings.tsx
@@ -1,9 +1,6 @@
import React, { useContext, useRef, useState } from "react";
import { useQuery } from "react-query";
import { AxiosResponse } from "axios";
-import { format } from "date-fns";
-import formatDistanceToNow from "date-fns/formatDistanceToNow";
-import FileSaver from "file-saver";
import { IApiError } from "interfaces/errors";
import { IMdmProfile, IMdmProfilesResponse } from "interfaces/mdm";
@@ -12,11 +9,14 @@ import { AppContext } from "context/app";
import { NotificationContext } from "context/notification";
import CustomLink from "components/CustomLink";
-import Button from "components/buttons/Button";
-import Icon from "components/Icon";
+
+import FileUploader from "../../../components/FileUploader";
+import UploadList from "../../../components/UploadList";
import { UPLOAD_ERROR_MESSAGES, getErrorMessage } from "./helpers";
import DeleteProfileModal from "./components/DeleteProfileModal/DeleteProfileModal";
+import ProfileListItem from "./components/ProfileListItem";
+import ProfileListHeading from "./components/ProfileListHeading";
const baseClass = "custom-settings";
@@ -42,69 +42,11 @@ const CustomSettings = () => {
}
);
- const onClickDownload = async (profile: IMdmProfile) => {
- const fileContent = await mdmAPI.downloadProfile(profile.profile_id);
- const formatDate = format(new Date(), "yyyy-MM-dd");
- const filename = `${formatDate}_${profile.name}.mobileconfig`;
- const file = new File([fileContent], filename);
- FileSaver.saveAs(file);
- };
-
const onClickDelete = (profile: IMdmProfile) => {
selectedProfile.current = profile;
setShowDeleteProfileModal(true);
};
- const renderProfiles = () => {
- if (!profiles || profiles.length === 0) return null;
-
- const profileListItems = profiles.map((profile) => {
- return (
-
-
-
-
-
- {profile.name}
-
-
- {`Uploaded ${formatDistanceToNow(
- new Date(profile.created_at)
- )} ago`}
-
-
-
-
- onClickDownload(profile)}
- >
-
-
- onClickDelete(profile)}
- >
-
-
-
-
- );
- });
-
- return (
-
-
- Configuration profile
- Actions
-
-
-
- );
- };
-
const onFileUpload = async (files: FileList | null) => {
setShowLoading(true);
@@ -163,21 +105,22 @@ const CustomSettings = () => {
/>
- {renderProfiles()}
-
-
-
-
Configuration profile (.mobileconfig)
-
- Upload
-
-
onFileUpload(e.target.files)}
+ {profiles && (
+
(
+
+ )}
/>
-
+ )}
+
+
{showDeleteProfileModal && selectedProfile.current && (
{
+ return (
+
+ Configuration profile
+ Actions
+
+ );
+};
+
+export default ProfileListHeading;
diff --git a/frontend/pages/ManageControlsPage/MacOSSettings/cards/CustomSettings/components/ProfileListHeading/_styles.scss b/frontend/pages/ManageControlsPage/MacOSSettings/cards/CustomSettings/components/ProfileListHeading/_styles.scss
new file mode 100644
index 0000000000..7fba306038
--- /dev/null
+++ b/frontend/pages/ManageControlsPage/MacOSSettings/cards/CustomSettings/components/ProfileListHeading/_styles.scss
@@ -0,0 +1,11 @@
+.profile-list-heading {
+ display: flex;
+ justify-content: space-between;
+ font-size: $x-small;
+ font-weight: $bold;
+
+ &__actions-heading {
+ text-align: right;
+ margin-right: 40px; // align with left side of buttons below it
+ }
+}
diff --git a/frontend/pages/ManageControlsPage/MacOSSettings/cards/CustomSettings/components/ProfileListHeading/index.ts b/frontend/pages/ManageControlsPage/MacOSSettings/cards/CustomSettings/components/ProfileListHeading/index.ts
new file mode 100644
index 0000000000..9d202fbf6f
--- /dev/null
+++ b/frontend/pages/ManageControlsPage/MacOSSettings/cards/CustomSettings/components/ProfileListHeading/index.ts
@@ -0,0 +1 @@
+export { default } from "./ProfileListHeading";
diff --git a/frontend/pages/ManageControlsPage/MacOSSettings/cards/CustomSettings/components/ProfileListItem/ProfileListItem.tsx b/frontend/pages/ManageControlsPage/MacOSSettings/cards/CustomSettings/components/ProfileListItem/ProfileListItem.tsx
new file mode 100644
index 0000000000..be514e0ae4
--- /dev/null
+++ b/frontend/pages/ManageControlsPage/MacOSSettings/cards/CustomSettings/components/ProfileListItem/ProfileListItem.tsx
@@ -0,0 +1,60 @@
+import React from "react";
+import { format, formatDistanceToNow } from "date-fns";
+import FileSaver from "file-saver";
+
+import { IMdmProfile } from "interfaces/mdm";
+import mdmAPI from "services/entities/mdm";
+
+import Button from "components/buttons/Button";
+import Icon from "components/Icon";
+
+const baseClass = "profile-list-item";
+
+interface IProfileListItemProps {
+ profile: IMdmProfile;
+ onDelete: (profile: IMdmProfile) => void;
+}
+
+const ProfileListItem = ({ profile, onDelete }: IProfileListItemProps) => {
+ const onClickDownload = async () => {
+ const fileContent = await mdmAPI.downloadProfile(profile.profile_id);
+ const formatDate = format(new Date(), "yyyy-MM-dd");
+ const filename = `${formatDate}_${profile.name}.mobileconfig`;
+ const file = new File([fileContent], filename);
+ FileSaver.saveAs(file);
+ };
+
+ return (
+
+
+
+
+ {profile.name}
+
+ {`Uploaded ${formatDistanceToNow(
+ new Date(profile.created_at)
+ )} ago`}
+
+
+
+
+
+
+
+ onDelete(profile)}
+ >
+
+
+
+
+ );
+};
+
+export default ProfileListItem;
diff --git a/frontend/pages/ManageControlsPage/MacOSSettings/cards/CustomSettings/components/ProfileListItem/_styles.scss b/frontend/pages/ManageControlsPage/MacOSSettings/cards/CustomSettings/components/ProfileListItem/_styles.scss
new file mode 100644
index 0000000000..412b2d7787
--- /dev/null
+++ b/frontend/pages/ManageControlsPage/MacOSSettings/cards/CustomSettings/components/ProfileListItem/_styles.scss
@@ -0,0 +1,34 @@
+.profile-list-item {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+
+ &__profile-data {
+ display: flex;
+ align-items: center;
+ }
+
+ &__profile-info {
+ margin-left: $pad-medium;
+ display: flex;
+ flex-direction: column;
+ }
+
+ &__profile-name {
+ font-size: $x-small;
+ }
+
+ &__profile-uploaded {
+ font-size: $xx-small;
+ }
+
+ &__download-button,
+ &__delete-button {
+ width: 40px;
+ height: 40px;
+ }
+
+ &__download-button {
+ margin-right: $pad-medium;
+ }
+}
diff --git a/frontend/pages/ManageControlsPage/MacOSSettings/cards/CustomSettings/components/ProfileListItem/index.ts b/frontend/pages/ManageControlsPage/MacOSSettings/cards/CustomSettings/components/ProfileListItem/index.ts
new file mode 100644
index 0000000000..121ec4b679
--- /dev/null
+++ b/frontend/pages/ManageControlsPage/MacOSSettings/cards/CustomSettings/components/ProfileListItem/index.ts
@@ -0,0 +1 @@
+export { default } from "./ProfileListItem";
diff --git a/frontend/pages/ManageControlsPage/components/FileUploader/FileUploader.tsx b/frontend/pages/ManageControlsPage/components/FileUploader/FileUploader.tsx
new file mode 100644
index 0000000000..57c477037b
--- /dev/null
+++ b/frontend/pages/ManageControlsPage/components/FileUploader/FileUploader.tsx
@@ -0,0 +1,39 @@
+import React from "react";
+
+import Button from "components/buttons/Button";
+import Icon from "components/Icon";
+import { IconNames } from "components/icons";
+
+const baseClass = "file-uploader";
+
+interface IFileUploaderProps {
+ icon: IconNames;
+ message: string;
+ isLoading?: boolean;
+ onFileUpload: (files: FileList | null) => void;
+}
+
+const FileUploader = ({
+ icon,
+ message,
+ isLoading = false,
+ onFileUpload,
+}: IFileUploaderProps) => {
+ return (
+
+
+
{message}
+
+ Upload
+
+
onFileUpload(e.target.files)}
+ />
+
+ );
+};
+
+export default FileUploader;
diff --git a/frontend/pages/ManageControlsPage/components/FileUploader/_styles.scss b/frontend/pages/ManageControlsPage/components/FileUploader/_styles.scss
new file mode 100644
index 0000000000..ecb40a0667
--- /dev/null
+++ b/frontend/pages/ManageControlsPage/components/FileUploader/_styles.scss
@@ -0,0 +1,16 @@
+.file-uploader {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ border-radius: $border-radius;
+ background-color: $ui-fleet-blue-10;
+ border: 1px solid $ui-fleet-black-10;
+ padding: $pad-xlarge $pad-large;
+ font-size: $x-small;
+ margin-top: $pad-xxlarge;
+ text-align: center;
+
+ input {
+ display: none;
+ }
+}
diff --git a/frontend/pages/ManageControlsPage/components/FileUploader/index.ts b/frontend/pages/ManageControlsPage/components/FileUploader/index.ts
new file mode 100644
index 0000000000..fd97e9686f
--- /dev/null
+++ b/frontend/pages/ManageControlsPage/components/FileUploader/index.ts
@@ -0,0 +1 @@
+export { default } from "./FileUploader";
diff --git a/frontend/pages/ManageControlsPage/components/UploadList/UploadList.tsx b/frontend/pages/ManageControlsPage/components/UploadList/UploadList.tsx
new file mode 100644
index 0000000000..c3bd68a2ae
--- /dev/null
+++ b/frontend/pages/ManageControlsPage/components/UploadList/UploadList.tsx
@@ -0,0 +1,34 @@
+import React from "react";
+
+const baseClass = "upload-list";
+
+interface IUploadListProps {
+ listItems: any[]; // TODO: typings
+ HeadingComponent: (props: any) => JSX.Element; // TODO: Typings
+ ListItemComponent: (props: { listItem: any }) => JSX.Element; // TODO: types
+}
+
+const UploadList = ({
+ listItems,
+ HeadingComponent,
+ ListItemComponent,
+}: IUploadListProps) => {
+ const items = listItems.map((listItem) => {
+ return (
+
+
+
+ );
+ });
+
+ return (
+
+ );
+};
+
+export default UploadList;
diff --git a/frontend/pages/ManageControlsPage/components/UploadList/_styles.scss b/frontend/pages/ManageControlsPage/components/UploadList/_styles.scss
new file mode 100644
index 0000000000..fffbda697e
--- /dev/null
+++ b/frontend/pages/ManageControlsPage/components/UploadList/_styles.scss
@@ -0,0 +1,19 @@
+.upload-list {
+ &__header {
+ padding: $pad-medium $pad-large;
+ font-size: $x-small;
+ font-weight: $bold;
+ border-bottom: 1px solid $ui-fleet-black-10;
+ }
+
+ &__list {
+ list-style: none;
+ padding: 0;
+ margin: 0;
+ }
+
+ &__list-item {
+ padding: $pad-medium $pad-large;
+ border-bottom: 1px solid $ui-fleet-black-10;
+ }
+}
diff --git a/frontend/pages/ManageControlsPage/components/UploadList/index.ts b/frontend/pages/ManageControlsPage/components/UploadList/index.ts
new file mode 100644
index 0000000000..2711bc749a
--- /dev/null
+++ b/frontend/pages/ManageControlsPage/components/UploadList/index.ts
@@ -0,0 +1 @@
+export { default } from "./UploadList";
diff --git a/frontend/router/paths.ts b/frontend/router/paths.ts
index e4edcfc6cc..fdde2f05e8 100644
--- a/frontend/router/paths.ts
+++ b/frontend/router/paths.ts
@@ -8,6 +8,7 @@ export default {
CONTROLS_MAC_OS_UPDATES: `${URL_PREFIX}/controls/mac-os-updates`,
CONTROLS_MAC_SETTINGS: `${URL_PREFIX}/controls/mac-settings`,
CONTROLS_CUSTOM_SETTINGS: `${URL_PREFIX}/controls/mac-settings/custom-settings`,
+ CONTROLS_MAC_SCRIPTS: `${URL_PREFIX}/controls/mac-scripts`,
DASHBOARD: `${URL_PREFIX}/dashboard`,
DASHBOARD_LINUX: `${URL_PREFIX}/dashboard/linux`,
DASHBOARD_MAC: `${URL_PREFIX}/dashboard/mac`,