mirror of
https://github.com/empayre/fleet.git
synced 2024-11-06 00:45:19 +00:00
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.
This commit is contained in:
parent
50a2739609
commit
b8fa08b53c
2
.vscode/typescriptreact.code-snippets
vendored
2
.vscode/typescriptreact.code-snippets
vendored
@ -22,7 +22,7 @@
|
|||||||
"scope": "typescriptreact,javascriptreact",
|
"scope": "typescriptreact,javascriptreact",
|
||||||
"prefix": "bc",
|
"prefix": "bc",
|
||||||
"body": [
|
"body": [
|
||||||
"`\\${baseClass}__$0`"
|
"className={`\\${baseClass}__$0`}"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
57
frontend/components/icons/FileBash.tsx
Normal file
57
frontend/components/icons/FileBash.tsx
Normal file
@ -0,0 +1,57 @@
|
|||||||
|
import React from "react";
|
||||||
|
|
||||||
|
const FileBash = () => {
|
||||||
|
return (
|
||||||
|
<svg width="34" height="40" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<g clipPath="url(#a)">
|
||||||
|
<path
|
||||||
|
d="M29.333 39.751H4.667a2.417 2.417 0 0 1-2.417-2.417V2.668A2.417 2.417 0 0 1 4.667.25h19.562c.64 0 1.255.255 1.709.708l5.104 5.104c.453.454.708 1.068.708 1.71v29.561a2.417 2.417 0 0 1-2.417 2.417Z"
|
||||||
|
fill="#fff"
|
||||||
|
stroke="#192147"
|
||||||
|
strokeWidth=".5"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
d="M23.5.501h.834l.5 6.5 6.666.5v1h-6a2 2 0 0 1-2-2v-6Z"
|
||||||
|
fill="#C5C7D1"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
d="M24.5.334v5.667c0 .737.597 1.333 1.333 1.333h6"
|
||||||
|
stroke="#192147"
|
||||||
|
strokeWidth=".5"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
d="M2.5 20h19a2 2 0 0 1 2 2v13a2 2 0 0 1-2 2h-19V20Z"
|
||||||
|
fill="#C5C7D1"
|
||||||
|
/>
|
||||||
|
<rect
|
||||||
|
x=".25"
|
||||||
|
y="18.584"
|
||||||
|
width="21.02"
|
||||||
|
height="16.351"
|
||||||
|
rx="1.75"
|
||||||
|
fill="#515774"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
d="m10.004 26.514-1.913.052a.714.714 0 0 0-.162-.363.87.87 0 0 0-.345-.251 1.176 1.176 0 0 0-.486-.094c-.241 0-.447.048-.618.145-.168.097-.25.227-.247.392a.419.419 0 0 0 .153.332c.108.094.3.17.576.226l1.261.239c.653.125 1.14.332 1.457.622.321.29.483.673.486 1.15-.003.45-.136.84-.4 1.173-.262.332-.62.59-1.074.775-.455.182-.975.273-1.56.273-.935 0-1.672-.192-2.212-.575A2.149 2.149 0 0 1 4 29.058l2.058-.05c.046.238.164.42.354.545.19.125.433.187.729.187.267 0 .484-.05.652-.149.167-.1.252-.231.255-.396a.42.42 0 0 0-.196-.354c-.128-.09-.328-.162-.6-.213l-1.143-.217c-.656-.12-1.145-.34-1.466-.66-.32-.325-.48-.737-.477-1.237-.003-.437.114-.81.35-1.12.235-.313.57-.551 1.005-.716.435-.165.948-.247 1.539-.247.886 0 1.585.186 2.096.558.512.37.794.878.848 1.525ZM13.276 27.328v3.733h-2.084v-8.727h2.016v3.384h.072c.148-.404.39-.72.725-.947.338-.227.751-.34 1.24-.34.46 0 .86.102 1.201.306.341.202.605.488.793.857.19.37.284.801.281 1.295v4.172h-2.084V27.3c.003-.364-.088-.648-.272-.853-.185-.204-.445-.307-.78-.307a1.14 1.14 0 0 0-.58.145.988.988 0 0 0-.388.405c-.09.176-.137.39-.14.64Z"
|
||||||
|
fill="#fff"
|
||||||
|
/>
|
||||||
|
<rect
|
||||||
|
x=".25"
|
||||||
|
y="18.584"
|
||||||
|
width="21.02"
|
||||||
|
height="16.351"
|
||||||
|
rx="1.75"
|
||||||
|
stroke="#192147"
|
||||||
|
strokeWidth=".5"
|
||||||
|
/>
|
||||||
|
</g>
|
||||||
|
<defs>
|
||||||
|
<clipPath id="a">
|
||||||
|
<path fill="#fff" d="M0 0h34v40H0z" />
|
||||||
|
</clipPath>
|
||||||
|
</defs>
|
||||||
|
</svg>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default FileBash;
|
68
frontend/components/icons/FileGeneric.tsx
Normal file
68
frontend/components/icons/FileGeneric.tsx
Normal file
@ -0,0 +1,68 @@
|
|||||||
|
import React from "react";
|
||||||
|
|
||||||
|
const FileGeneric = () => {
|
||||||
|
return (
|
||||||
|
<svg width="34" height="40" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<g clipPath="url(#a)">
|
||||||
|
<path
|
||||||
|
d="M29.333 39.751H4.667a2.417 2.417 0 0 1-2.417-2.417V2.668A2.417 2.417 0 0 1 4.667.25h19.562c.64 0 1.255.255 1.709.708l5.104 5.104c.453.454.708 1.068.708 1.71v29.561a2.417 2.417 0 0 1-2.417 2.417Z"
|
||||||
|
fill="#fff"
|
||||||
|
stroke="#192147"
|
||||||
|
strokeWidth=".5"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
d="M23.5.501h.834l.5 6.5 6.666.5v1h-6a2 2 0 0 1-2-2v-6Z"
|
||||||
|
fill="#C5C7D1"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
d="M24.5.334v5.667c0 .737.597 1.333 1.333 1.333h6"
|
||||||
|
stroke="#192147"
|
||||||
|
strokeWidth=".5"
|
||||||
|
/>
|
||||||
|
<g clipPath="url(#b)">
|
||||||
|
<path
|
||||||
|
d="M15.113 26.751c.829.806 1.549 1.507 2.018 1.952.545.52.85 1.242.818 1.984-.033.743-.447 1.624-1.909 1.953-2.924.647-3.218-2.154-3.218-2.154l-.087-.817 2.389-2.918h-.011Z"
|
||||||
|
fill="#070708"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
d="m8.916 17.298 8.302-7.714s2.073-.891 3.142.308c1.07 1.199.502 2.79.502 2.79l-7.506 7.3-4.44-2.684Z"
|
||||||
|
fill="#E2E4EA"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
d="M15.113 26.751c-2.378-2.313-5.63-5.475-6.044-5.91-.665-.7-1.484-2.674.196-3.82a2.68 2.68 0 0 1 1.844-.413c2.716.371 2.236 3.374 2.236 3.374l5.902-5.698 5.608 5.496s1.418 1.507.632 2.525c-.785 1.019-7.952 9.518-7.952 9.518s1.003-1.305-.131-2.833c-.655-.891-2.302-2.239-2.302-2.239h.01Z"
|
||||||
|
fill="#C5C7D1"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
d="M17.633 31.717c.84-1.008 7.112-8.456 7.854-9.411.797-1.019-.632-2.526-.632-2.526l-5.608-5.496 1.615-1.602s.567-1.591-.502-2.79c-1.07-1.199-3.142-.308-3.142-.308l-7.92 7.342-.382.372c-1.21 1.167-.458 2.896.142 3.544.415.434 3.666 3.596 6.044 5.91l-2.346 2.928.044.806s.294 2.812 3.218 2.154c.698-.16 1.156-.435 1.451-.764l.153-.16h.01Z"
|
||||||
|
stroke="#515774"
|
||||||
|
strokeWidth=".5"
|
||||||
|
strokeMiterlimit="10"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
d="M15.113 26.751s1.898 1.655 2.269 2.196c.37.541 1.036 1.507.316 2.674"
|
||||||
|
stroke="#515774"
|
||||||
|
strokeWidth=".5"
|
||||||
|
strokeMiterlimit="10"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
d="m19.345 14.189-6.021 5.835s.458-1.336-.557-2.578c-1.167-1.443-2.934-.764-2.934-.764a3.714 3.714 0 0 0-.666.372"
|
||||||
|
stroke="#515774"
|
||||||
|
strokeWidth=".5"
|
||||||
|
strokeLinecap="round"
|
||||||
|
strokeLinejoin="round"
|
||||||
|
/>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
<defs>
|
||||||
|
<clipPath id="a">
|
||||||
|
<path fill="#fff" d="M0 0h34v40H0z" />
|
||||||
|
</clipPath>
|
||||||
|
<clipPath id="b">
|
||||||
|
<path fill="#fff" d="M8 9h18v24H8z" />
|
||||||
|
</clipPath>
|
||||||
|
</defs>
|
||||||
|
</svg>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default FileGeneric;
|
57
frontend/components/icons/FilePython.tsx
Normal file
57
frontend/components/icons/FilePython.tsx
Normal file
@ -0,0 +1,57 @@
|
|||||||
|
import React from "react";
|
||||||
|
|
||||||
|
const FilePython = () => {
|
||||||
|
return (
|
||||||
|
<svg width="34" height="40" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<g clipPath="url(#a)">
|
||||||
|
<path
|
||||||
|
d="M29.333 39.751H4.667a2.417 2.417 0 0 1-2.417-2.417V2.668A2.417 2.417 0 0 1 4.667.25h19.562c.64 0 1.255.255 1.709.708l5.104 5.104c.453.454.708 1.068.708 1.71v29.561a2.417 2.417 0 0 1-2.417 2.417Z"
|
||||||
|
fill="#fff"
|
||||||
|
stroke="#192147"
|
||||||
|
strokeWidth=".5"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
d="M23.5.501h.834l.5 6.5 6.666.5v1h-6a2 2 0 0 1-2-2v-6Z"
|
||||||
|
fill="#C5C7D1"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
d="M24.5.334v5.667c0 .737.597 1.333 1.333 1.333h6"
|
||||||
|
stroke="#192147"
|
||||||
|
strokeWidth=".5"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
d="M2.5 20h19a2 2 0 0 1 2 2v13a2 2 0 0 1-2 2h-19V20Z"
|
||||||
|
fill="#C5C7D1"
|
||||||
|
/>
|
||||||
|
<rect
|
||||||
|
x=".25"
|
||||||
|
y="18.584"
|
||||||
|
width="21.314"
|
||||||
|
height="16.585"
|
||||||
|
rx="1.75"
|
||||||
|
fill="#5CABDF"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
d="M4 31.42v-9h2.063v1.12h.063c.086-.199.206-.39.363-.575.159-.185.36-.336.605-.452a2.02 2.02 0 0 1 .886-.179c.455 0 .88.12 1.274.358.398.239.719.607.963 1.104.245.497.367 1.13.367 1.9 0 .742-.118 1.362-.354 1.862-.233.5-.548.875-.946 1.125-.395.25-.834.375-1.317.375-.33 0-.615-.054-.856-.162a1.864 1.864 0 0 1-.61-.426 2.046 2.046 0 0 1-.375-.566h-.042v3.515H4Zm2.041-5.728c0 .352.047.659.14.92.097.262.235.465.414.61.182.142.4.213.652.213.256 0 .473-.071.652-.213.18-.145.314-.348.405-.61.094-.261.14-.568.14-.92a2.69 2.69 0 0 0-.14-.916 1.29 1.29 0 0 0-.405-.601 1.005 1.005 0 0 0-.652-.213c-.256 0-.473.07-.652.209-.179.139-.317.338-.413.596-.094.259-.14.567-.14.925ZM12.739 31.42c-.25 0-.486-.02-.707-.06a2.863 2.863 0 0 1-.571-.15l.46-1.512c.204.068.389.108.554.119a.772.772 0 0 0 .43-.081.615.615 0 0 0 .281-.337l.081-.196-2.326-6.784h2.181l1.207 4.67h.068l1.223-4.67h2.194l-2.467 7.172c-.12.358-.288.673-.507.946a2.202 2.202 0 0 1-.84.648c-.34.156-.761.234-1.261.234Z"
|
||||||
|
fill="#fff"
|
||||||
|
/>
|
||||||
|
<rect
|
||||||
|
x=".25"
|
||||||
|
y="18.584"
|
||||||
|
width="21.314"
|
||||||
|
height="16.585"
|
||||||
|
rx="1.75"
|
||||||
|
stroke="#192147"
|
||||||
|
strokeWidth=".5"
|
||||||
|
/>
|
||||||
|
</g>
|
||||||
|
<defs>
|
||||||
|
<clipPath id="a">
|
||||||
|
<path fill="#fff" d="M0 0h34v40H0z" />
|
||||||
|
</clipPath>
|
||||||
|
</defs>
|
||||||
|
</svg>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default FilePython;
|
57
frontend/components/icons/FileZsh.tsx
Normal file
57
frontend/components/icons/FileZsh.tsx
Normal file
@ -0,0 +1,57 @@
|
|||||||
|
import React from "react";
|
||||||
|
|
||||||
|
const FileZsh = () => {
|
||||||
|
return (
|
||||||
|
<svg width="34" height="40" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<g clipPath="url(#a)">
|
||||||
|
<path
|
||||||
|
d="M29.333 39.751H4.667a2.417 2.417 0 0 1-2.417-2.417V2.668A2.417 2.417 0 0 1 4.667.25h19.562c.64 0 1.255.255 1.709.708l5.104 5.104c.453.454.708 1.068.708 1.71v29.561a2.417 2.417 0 0 1-2.417 2.417Z"
|
||||||
|
fill="#fff"
|
||||||
|
stroke="#192147"
|
||||||
|
strokeWidth=".5"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
d="M23.5.501h.834l.5 6.5 6.666.5v1h-6a2 2 0 0 1-2-2v-6Z"
|
||||||
|
fill="#C5C7D1"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
d="M24.5.334v5.667c0 .737.597 1.333 1.333 1.333h6"
|
||||||
|
stroke="#192147"
|
||||||
|
strokeWidth=".5"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
d="M2.5 20h25a2 2 0 0 1 2 2v13a2 2 0 0 1-2 2h-25V20Z"
|
||||||
|
fill="#C5C7D1"
|
||||||
|
/>
|
||||||
|
<rect
|
||||||
|
x=".25"
|
||||||
|
y="18.581"
|
||||||
|
width="27.698"
|
||||||
|
height="16.351"
|
||||||
|
rx="1.75"
|
||||||
|
fill="#FAA669"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
d="M4 31.058v-1.19l3.034-3.694v-.042H4.107v-1.62h5.411v1.304l-2.812 3.58v.043h2.915v1.619H4ZM16.682 26.511l-1.913.051a.713.713 0 0 0-.162-.362.87.87 0 0 0-.346-.252 1.176 1.176 0 0 0-.485-.093c-.242 0-.448.048-.618.145-.168.096-.25.227-.247.392a.419.419 0 0 0 .153.332c.108.094.3.17.575.226l1.262.239c.653.125 1.139.332 1.457.622.321.29.483.673.486 1.15-.003.45-.137.84-.4 1.172-.262.332-.62.591-1.075.776-.454.181-.974.272-1.56.272-.934 0-1.671-.191-2.211-.575a2.148 2.148 0 0 1-.92-1.551l2.058-.051c.045.238.163.42.353.545.19.125.434.188.73.188.266 0 .484-.05.651-.15.168-.099.253-.23.256-.396a.42.42 0 0 0-.196-.353c-.128-.091-.328-.162-.601-.213l-1.142-.218c-.656-.119-1.145-.34-1.466-.66-.321-.324-.48-.736-.477-1.236-.003-.438.113-.811.35-1.12.235-.313.57-.552 1.005-.717.434-.165.947-.247 1.538-.247.887 0 1.585.186 2.097.558.511.37.794.878.848 1.526ZM19.953 27.325v3.733H17.87V22.33h2.015v3.383h.073c.148-.403.389-.719.724-.946.338-.227.752-.34 1.24-.34.46 0 .861.101 1.202.306.34.202.605.487.793.856.19.37.284.802.28 1.296v4.172h-2.083v-3.763c.003-.364-.088-.648-.273-.852-.184-.205-.444-.307-.78-.307a1.14 1.14 0 0 0-.58.145.987.987 0 0 0-.387.405c-.09.176-.138.389-.14.639Z"
|
||||||
|
fill="#fff"
|
||||||
|
/>
|
||||||
|
<rect
|
||||||
|
x=".25"
|
||||||
|
y="18.581"
|
||||||
|
width="27.698"
|
||||||
|
height="16.351"
|
||||||
|
rx="1.75"
|
||||||
|
stroke="#192147"
|
||||||
|
strokeWidth=".5"
|
||||||
|
/>
|
||||||
|
</g>
|
||||||
|
<defs>
|
||||||
|
<clipPath id="a">
|
||||||
|
<path fill="#fff" d="M0 0h34v40H0z" />
|
||||||
|
</clipPath>
|
||||||
|
</defs>
|
||||||
|
</svg>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default FileZsh;
|
203
frontend/components/icons/Files.tsx
Normal file
203
frontend/components/icons/Files.tsx
Normal file
@ -0,0 +1,203 @@
|
|||||||
|
import React from "react";
|
||||||
|
|
||||||
|
const Files = () => {
|
||||||
|
return (
|
||||||
|
<svg width="142" height="40" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<g clipPath="url(#a)">
|
||||||
|
<path
|
||||||
|
d="M29.333 39.75H4.667a2.417 2.417 0 0 1-2.417-2.416V2.667A2.417 2.417 0 0 1 4.667.25h19.562c.64 0 1.255.255 1.709.708l5.104 5.105c.453.453.708 1.068.708 1.709v29.562a2.417 2.417 0 0 1-2.417 2.416Z"
|
||||||
|
fill="#fff"
|
||||||
|
stroke="#192147"
|
||||||
|
strokeWidth=".5"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
d="M23.5.5h.834l.5 6.5 6.666.5v1h-6a2 2 0 0 1-2-2v-6Z"
|
||||||
|
fill="#C5C7D1"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
d="M24.5.334v5.667c0 .736.597 1.333 1.333 1.333h6"
|
||||||
|
stroke="#192147"
|
||||||
|
strokeWidth=".5"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
d="M2.5 20h19a2 2 0 0 1 2 2v13a2 2 0 0 1-2 2h-19V20Z"
|
||||||
|
fill="#C5C7D1"
|
||||||
|
/>
|
||||||
|
<rect
|
||||||
|
x=".25"
|
||||||
|
y="18.584"
|
||||||
|
width="21.314"
|
||||||
|
height="16.585"
|
||||||
|
rx="1.75"
|
||||||
|
fill="#5CABDF"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
d="M4 31.419v-9h2.063v1.12h.063c.086-.198.206-.39.363-.575.159-.184.36-.335.605-.451.247-.12.542-.18.886-.18.455 0 .88.12 1.274.359.398.238.719.606.963 1.103.245.497.367 1.13.367 1.9 0 .742-.118 1.363-.354 1.863-.233.5-.548.875-.946 1.125-.395.25-.834.375-1.317.375-.33 0-.615-.054-.856-.162a1.865 1.865 0 0 1-.61-.426 2.048 2.048 0 0 1-.375-.567h-.042v3.516H4Zm2.041-5.727c0 .352.047.659.14.92.097.261.235.464.414.61.182.141.4.212.652.212.256 0 .473-.07.652-.213.18-.145.314-.348.405-.61.094-.26.14-.567.14-.92 0-.352-.046-.657-.14-.916a1.29 1.29 0 0 0-.405-.6 1.006 1.006 0 0 0-.652-.214c-.256 0-.473.07-.652.21-.179.138-.317.337-.413.596-.094.258-.14.567-.14.925ZM12.739 31.419c-.25 0-.486-.02-.707-.06a2.863 2.863 0 0 1-.571-.15l.46-1.512c.204.068.389.108.554.12a.773.773 0 0 0 .43-.082.615.615 0 0 0 .281-.336l.081-.196-2.326-6.784h2.181l1.207 4.67h.068l1.223-4.67h2.194l-2.467 7.172c-.12.358-.288.673-.507.946a2.202 2.202 0 0 1-.84.647c-.34.157-.761.235-1.261.235Z"
|
||||||
|
fill="#fff"
|
||||||
|
/>
|
||||||
|
<rect
|
||||||
|
x=".25"
|
||||||
|
y="18.584"
|
||||||
|
width="21.314"
|
||||||
|
height="16.585"
|
||||||
|
rx="1.75"
|
||||||
|
stroke="#192147"
|
||||||
|
strokeWidth=".5"
|
||||||
|
/>
|
||||||
|
</g>
|
||||||
|
<g clipPath="url(#b)">
|
||||||
|
<path
|
||||||
|
d="M65.333 39.75H40.667a2.417 2.417 0 0 1-2.417-2.416V2.667A2.417 2.417 0 0 1 40.667.25h19.562c.64 0 1.255.255 1.709.708l5.104 5.105c.453.453.708 1.068.708 1.709v29.562a2.417 2.417 0 0 1-2.417 2.416Z"
|
||||||
|
fill="#fff"
|
||||||
|
stroke="#192147"
|
||||||
|
strokeWidth=".5"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
d="M59.5.5h.834l.5 6.5 6.666.5v1h-6a2 2 0 0 1-2-2v-6Z"
|
||||||
|
fill="#C5C7D1"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
d="M60.5.334v5.667c0 .736.597 1.333 1.333 1.333h6"
|
||||||
|
stroke="#192147"
|
||||||
|
strokeWidth=".5"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
d="M38.5 20h25a2 2 0 0 1 2 2v13a2 2 0 0 1-2 2h-25V20Z"
|
||||||
|
fill="#C5C7D1"
|
||||||
|
/>
|
||||||
|
<rect
|
||||||
|
x="36.25"
|
||||||
|
y="18.58"
|
||||||
|
width="27.698"
|
||||||
|
height="16.351"
|
||||||
|
rx="1.75"
|
||||||
|
fill="#FAA669"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
d="M40 31.057v-1.189l3.034-3.694v-.043h-2.928v-1.62h5.413v1.305l-2.813 3.58v.042h2.915v1.62H40ZM52.682 26.51l-1.913.052a.713.713 0 0 0-.163-.363.87.87 0 0 0-.345-.251 1.176 1.176 0 0 0-.485-.094c-.242 0-.448.049-.618.145-.168.097-.25.227-.248.392a.419.419 0 0 0 .154.333c.108.093.3.169.575.225l1.262.239c.653.125 1.139.332 1.457.622.321.29.483.674.486 1.15-.003.45-.137.84-.4 1.173-.262.332-.62.59-1.075.775-.454.182-.974.273-1.56.273-.934 0-1.671-.192-2.211-.575a2.148 2.148 0 0 1-.92-1.552l2.058-.05c.045.238.163.42.354.545.19.125.433.187.728.187.267 0 .485-.05.652-.149.168-.1.253-.231.256-.396a.42.42 0 0 0-.196-.354c-.128-.09-.328-.162-.601-.213l-1.142-.217c-.656-.12-1.145-.34-1.466-.66-.321-.325-.48-.736-.477-1.236-.003-.438.113-.812.35-1.121.235-.313.57-.551 1.005-.716.434-.165.947-.247 1.538-.247.887 0 1.585.186 2.097.558.511.37.794.878.848 1.526ZM55.953 27.324v3.733H53.87V22.33h2.015v3.384h.073c.148-.404.389-.72.724-.946.338-.228.752-.341 1.24-.341.46 0 .861.102 1.202.306.34.202.605.488.792.857.19.37.285.801.282 1.296v4.171h-2.084v-3.762c.003-.364-.088-.648-.273-.853-.184-.204-.444-.306-.78-.306a1.14 1.14 0 0 0-.58.144.988.988 0 0 0-.387.405c-.09.176-.138.39-.14.64Z"
|
||||||
|
fill="#fff"
|
||||||
|
/>
|
||||||
|
<rect
|
||||||
|
x="36.25"
|
||||||
|
y="18.58"
|
||||||
|
width="27.698"
|
||||||
|
height="16.351"
|
||||||
|
rx="1.75"
|
||||||
|
stroke="#192147"
|
||||||
|
strokeWidth=".5"
|
||||||
|
/>
|
||||||
|
</g>
|
||||||
|
<g clipPath="url(#c)">
|
||||||
|
<path
|
||||||
|
d="M101.333 39.75H76.667a2.417 2.417 0 0 1-2.417-2.416V2.667A2.417 2.417 0 0 1 76.667.25h19.562c.64 0 1.255.255 1.709.708l5.104 5.105c.453.453.708 1.068.708 1.709v29.562a2.417 2.417 0 0 1-2.417 2.416Z"
|
||||||
|
fill="#fff"
|
||||||
|
stroke="#192147"
|
||||||
|
strokeWidth=".5"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
d="M95.5.5h.834l.5 6.5 6.666.5v1h-6a2 2 0 0 1-2-2v-6Z"
|
||||||
|
fill="#C5C7D1"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
d="M96.5.334v5.667c0 .736.597 1.333 1.333 1.333h6"
|
||||||
|
stroke="#192147"
|
||||||
|
strokeWidth=".5"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
d="M74.5 20h19a2 2 0 0 1 2 2v13a2 2 0 0 1-2 2h-19V20Z"
|
||||||
|
fill="#C5C7D1"
|
||||||
|
/>
|
||||||
|
<rect
|
||||||
|
x="72.25"
|
||||||
|
y="18.584"
|
||||||
|
width="21.02"
|
||||||
|
height="16.351"
|
||||||
|
rx="1.75"
|
||||||
|
fill="#515774"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
d="m82.004 26.514-1.913.051a.714.714 0 0 0-.162-.362.873.873 0 0 0-.345-.252 1.176 1.176 0 0 0-.486-.093c-.242 0-.447.048-.618.145-.168.096-.25.227-.247.392a.418.418 0 0 0 .153.332c.108.094.3.169.576.226l1.261.239c.653.125 1.14.332 1.457.622.321.29.483.673.486 1.15-.003.449-.136.84-.4 1.172-.262.332-.62.591-1.074.776-.455.181-.975.272-1.56.272-.935 0-1.672-.191-2.212-.575a2.148 2.148 0 0 1-.92-1.551l2.058-.051c.046.238.164.42.354.545.19.125.433.188.729.188.267 0 .484-.05.652-.15.167-.099.252-.23.255-.396a.42.42 0 0 0-.196-.353c-.128-.091-.328-.162-.6-.213l-1.143-.218c-.656-.12-1.144-.34-1.465-.66-.322-.324-.48-.736-.478-1.236-.003-.438.114-.811.35-1.12.235-.313.57-.552 1.005-.717.435-.165.948-.247 1.539-.247.886 0 1.585.186 2.096.558.512.37.794.878.848 1.526ZM85.276 27.328v3.733h-2.084v-8.727h2.016v3.383h.072c.148-.403.39-.719.725-.946.338-.227.751-.34 1.24-.34.46 0 .86.101 1.201.306.341.202.606.487.793.856.19.37.284.802.281 1.296v4.172h-2.084v-3.763c.003-.364-.088-.648-.272-.852-.185-.205-.445-.307-.78-.307a1.14 1.14 0 0 0-.58.145.988.988 0 0 0-.388.405c-.09.176-.137.389-.14.639Z"
|
||||||
|
fill="#fff"
|
||||||
|
/>
|
||||||
|
<rect
|
||||||
|
x="72.25"
|
||||||
|
y="18.584"
|
||||||
|
width="21.02"
|
||||||
|
height="16.351"
|
||||||
|
rx="1.75"
|
||||||
|
stroke="#192147"
|
||||||
|
strokeWidth=".5"
|
||||||
|
/>
|
||||||
|
</g>
|
||||||
|
<g clipPath="url(#d)">
|
||||||
|
<path
|
||||||
|
d="M137.333 39.75h-24.666a2.417 2.417 0 0 1-2.417-2.416V2.667A2.417 2.417 0 0 1 112.667.25h19.562c.641 0 1.255.255 1.709.708l5.104 5.105c.453.453.708 1.068.708 1.709v29.562a2.417 2.417 0 0 1-2.417 2.416Z"
|
||||||
|
fill="#fff"
|
||||||
|
stroke="#192147"
|
||||||
|
strokeWidth=".5"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
d="M131.5.5h.833l.5 6.5 6.667.5v1h-6a2 2 0 0 1-2-2v-6Z"
|
||||||
|
fill="#C5C7D1"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
d="M132.5.334v5.667c0 .736.597 1.333 1.333 1.333h6"
|
||||||
|
stroke="#192147"
|
||||||
|
strokeWidth=".5"
|
||||||
|
/>
|
||||||
|
<g clipPath="url(#e)">
|
||||||
|
<path
|
||||||
|
d="M123.113 26.75c.829.807 1.549 1.507 2.018 1.953.545.52.851 1.241.818 1.984-.033.742-.447 1.623-1.909 1.952-2.924.647-3.218-2.154-3.218-2.154l-.087-.817 2.389-2.918h-.011Z"
|
||||||
|
fill="#8B8FA2"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
d="m116.916 17.297 8.302-7.713s2.073-.892 3.142.307c1.069 1.2.502 2.79.502 2.79l-7.506 7.3-4.44-2.684Z"
|
||||||
|
fill="#E2E4EA"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
d="M123.113 26.75c-2.378-2.313-5.629-5.474-6.044-5.91-.665-.7-1.484-2.673.196-3.819.535-.36 1.2-.499 1.844-.414 2.716.372 2.236 3.374 2.236 3.374l5.902-5.697 5.608 5.496s1.418 1.506.632 2.525c-.785 1.018-7.952 9.517-7.952 9.517s1.003-1.305-.131-2.833c-.655-.891-2.302-2.239-2.302-2.239h.011Z"
|
||||||
|
fill="#C5C7D1"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
d="M125.633 31.716c.84-1.008 7.112-8.456 7.854-9.41.797-1.02-.633-2.526-.633-2.526l-5.607-5.496 1.615-1.602s.567-1.592-.502-2.79c-1.069-1.2-3.142-.308-3.142-.308l-7.92 7.342-.382.371c-1.211 1.167-.458 2.897.142 3.544.415.435 3.666 3.597 6.044 5.91l-2.346 2.928.044.807s.294 2.811 3.218 2.153c.698-.159 1.156-.435 1.451-.764l.153-.159h.011Z"
|
||||||
|
stroke="#515774"
|
||||||
|
strokeWidth=".5"
|
||||||
|
strokeMiterlimit="10"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
d="M123.113 26.75s1.898 1.656 2.269 2.197c.371.54 1.036 1.506.316 2.674"
|
||||||
|
stroke="#515774"
|
||||||
|
strokeWidth=".5"
|
||||||
|
strokeMiterlimit="10"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
d="m127.345 14.188-6.021 5.836s.458-1.337-.557-2.578c-1.167-1.443-2.934-.764-2.934-.764a3.691 3.691 0 0 0-.666.371"
|
||||||
|
stroke="#515774"
|
||||||
|
strokeWidth=".5"
|
||||||
|
strokeLinecap="round"
|
||||||
|
strokeLinejoin="round"
|
||||||
|
/>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
<defs>
|
||||||
|
<clipPath id="a">
|
||||||
|
<path fill="#fff" d="M0 0h34v40H0z" />
|
||||||
|
</clipPath>
|
||||||
|
<clipPath id="b">
|
||||||
|
<path fill="#fff" d="M36 0h34v40H36z" />
|
||||||
|
</clipPath>
|
||||||
|
<clipPath id="c">
|
||||||
|
<path fill="#fff" d="M72 0h34v40H72z" />
|
||||||
|
</clipPath>
|
||||||
|
<clipPath id="d">
|
||||||
|
<path fill="#fff" d="M108 0h34v40h-34z" />
|
||||||
|
</clipPath>
|
||||||
|
<clipPath id="e">
|
||||||
|
<path fill="#fff" d="M116 9h18v24h-18z" />
|
||||||
|
</clipPath>
|
||||||
|
</defs>
|
||||||
|
</svg>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Files;
|
16
frontend/components/icons/Refresh.tsx
Normal file
16
frontend/components/icons/Refresh.tsx
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
import React from "react";
|
||||||
|
|
||||||
|
const Refresh = () => {
|
||||||
|
return (
|
||||||
|
<svg width="16" height="16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path
|
||||||
|
fillRule="evenodd"
|
||||||
|
clipRule="evenodd"
|
||||||
|
d="M13.996 1.004c0-.55.45-1 1-1s.999.45.999 1v3.998c0 .55-.45 1-1 1h-3.997c-.55 0-1-.45-1-1s.45-1 1-1h1.46A5.995 5.995 0 0 0 8 2.004 6.001 6.001 0 0 0 2.004 8c0 .55-.45 1-1 1s-1-.45-1-1A7.993 7.993 0 0 1 8 .005a7.945 7.945 0 0 1 5.996 2.738V1.004Zm0 6.996c0-.55.45-1 1-1s.999.45.999 1A7.993 7.993 0 0 1 8 15.995a7.945 7.945 0 0 1-5.996-2.738v1.739c0 .55-.45.999-1 .999s-1-.45-1-1v-3.997c0-.55.45-1 1-1h3.998c.55 0 1 .45 1 1s-.45 1-1 1h-1.46A5.995 5.995 0 0 0 8 13.995 6.001 6.001 0 0 0 13.996 8Z"
|
||||||
|
fill="#515774"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Refresh;
|
@ -42,6 +42,12 @@ import Pencil from "./Pencil";
|
|||||||
import TrashCan from "./TrashCan";
|
import TrashCan from "./TrashCan";
|
||||||
import Profile from "./Profile";
|
import Profile from "./Profile";
|
||||||
import Download from "./Download";
|
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.
|
// a mapping of the usable names of icons to the icon source.
|
||||||
export const ICON_MAP = {
|
export const ICON_MAP = {
|
||||||
@ -86,6 +92,12 @@ export const ICON_MAP = {
|
|||||||
"linux-green": LinuxGreen,
|
"linux-green": LinuxGreen,
|
||||||
profile: Profile,
|
profile: Profile,
|
||||||
download: Download,
|
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;
|
export type IconNames = keyof typeof ICON_MAP;
|
||||||
|
@ -71,12 +71,23 @@ export interface IMdmProfilesResponse {
|
|||||||
export type MacMdmProfileStatus = "applied" | "pending" | "failed";
|
export type MacMdmProfileStatus = "applied" | "pending" | "failed";
|
||||||
export type MacMdmProfileOperationType = "remove" | "install";
|
export type MacMdmProfileOperationType = "remove" | "install";
|
||||||
|
|
||||||
export type IHostMacMdmProfile = {
|
export interface IHostMacMdmProfile {
|
||||||
profile_id: number;
|
profile_id: number;
|
||||||
name: string;
|
name: string;
|
||||||
operation_type: MacMdmProfileOperationType;
|
operation_type: MacMdmProfileOperationType;
|
||||||
status: MacMdmProfileStatus;
|
status: MacMdmProfileStatus;
|
||||||
detail: string;
|
detail: string;
|
||||||
};
|
}
|
||||||
export type IMacSettings = IHostMacMdmProfile[];
|
export type IMacSettings = IHostMacMdmProfile[];
|
||||||
export type MacSettingsStatus = "Failing" | "Latest" | "Pending";
|
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;
|
||||||
|
}
|
||||||
|
111
frontend/pages/ManageControlsPage/MacOSScripts/MacOSScripts.tsx
Normal file
111
frontend/pages/ManageControlsPage/MacOSScripts/MacOSScripts.tsx
Normal file
@ -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<IMdmScript | null>(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 (
|
||||||
|
<div className={baseClass}>
|
||||||
|
<p className={`${baseClass}__description`}>
|
||||||
|
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. <CustomLink text="Learn more" url="#" newTab />
|
||||||
|
</p>
|
||||||
|
<UploadList
|
||||||
|
listItems={scripts}
|
||||||
|
HeadingComponent={ScriptListHeading}
|
||||||
|
ListItemComponent={({ listItem }) => (
|
||||||
|
<ScriptListItem
|
||||||
|
script={listItem}
|
||||||
|
onRerun={onClickRerun}
|
||||||
|
onDelete={onClickDelete}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
<FileUploader
|
||||||
|
icon="files"
|
||||||
|
message="Any type of script supported by macOS. If you If you don’t specify a shell or interpreter (e.g. #!/bin/sh), the script will run in /bin/sh."
|
||||||
|
onFileUpload={() => {
|
||||||
|
return null;
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
{showRerunScriptModal && selectedScript.current && (
|
||||||
|
<RerunScriptModal
|
||||||
|
scriptName={selectedScript.current?.name}
|
||||||
|
scriptId={selectedScript.current?.id}
|
||||||
|
onCancel={onCancelRerun}
|
||||||
|
onRerun={onRerunScript}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
{showDeleteScriptModal && selectedScript.current && (
|
||||||
|
<DeleteScriptModal
|
||||||
|
scriptName={selectedScript.current?.name}
|
||||||
|
scriptId={selectedScript.current?.id}
|
||||||
|
onCancel={onCancelDelete}
|
||||||
|
onDelete={onDeleteScript}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default MacOSScripts;
|
@ -0,0 +1,7 @@
|
|||||||
|
.mac-os-scripts {
|
||||||
|
font-size: $x-small;
|
||||||
|
|
||||||
|
&__description {
|
||||||
|
margin: $pad-xxlarge 0;
|
||||||
|
}
|
||||||
|
}
|
@ -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 (
|
||||||
|
<Modal
|
||||||
|
className={baseClass}
|
||||||
|
title={"Delete script"}
|
||||||
|
onExit={onCancel}
|
||||||
|
onEnter={() => onDelete(scriptId)}
|
||||||
|
>
|
||||||
|
<>
|
||||||
|
<p>
|
||||||
|
This action will cancel script{" "}
|
||||||
|
<span className={`${baseClass}__script-name`}>{scriptName}</span> from
|
||||||
|
running on macOS hosts on which the scrupt hasn't run yet.
|
||||||
|
</p>
|
||||||
|
<div className="modal-cta-wrap">
|
||||||
|
<Button
|
||||||
|
type="button"
|
||||||
|
onClick={() => onDelete(scriptId)}
|
||||||
|
variant="alert"
|
||||||
|
className="delete-loading"
|
||||||
|
>
|
||||||
|
Delete
|
||||||
|
</Button>
|
||||||
|
<Button onClick={onCancel} variant="inverse-alert">
|
||||||
|
Cancel
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
</Modal>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default DeleteScriptModal;
|
@ -0,0 +1,5 @@
|
|||||||
|
.delete-script-modal {
|
||||||
|
&__script-name {
|
||||||
|
font-weight: $bold;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1 @@
|
|||||||
|
export { default } from "./DeleteScriptModal";
|
@ -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 (
|
||||||
|
<Modal
|
||||||
|
className={baseClass}
|
||||||
|
title={"Rerun Script"}
|
||||||
|
onExit={onCancel}
|
||||||
|
onEnter={() => onRerun(scriptId)}
|
||||||
|
>
|
||||||
|
<>
|
||||||
|
<p>
|
||||||
|
This action will rerun script{" "}
|
||||||
|
<span className={`${baseClass}__script-name`}>{scriptName}</span> on
|
||||||
|
all macOS hosts {messageSuffix}.
|
||||||
|
</p>
|
||||||
|
<p>This may cause the script to run more than once on some hosts.</p>
|
||||||
|
<div className="modal-cta-wrap">
|
||||||
|
<Button type="button" onClick={() => onRerun(scriptId)}>
|
||||||
|
Rerun
|
||||||
|
</Button>
|
||||||
|
<Button onClick={onCancel} variant="inverse">
|
||||||
|
Cancel
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
</Modal>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default RerunScriptModal;
|
@ -0,0 +1,10 @@
|
|||||||
|
.rerun-script-modal {
|
||||||
|
p {
|
||||||
|
margin-top: 0;
|
||||||
|
margin-bottom: $pad-xlarge;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__script-name {
|
||||||
|
font-weight: $bold;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1 @@
|
|||||||
|
export { default } from "./RerunScriptModal";
|
@ -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 (
|
||||||
|
<div className={baseClass}>
|
||||||
|
<div className={`${baseClass}__heading-group`}>
|
||||||
|
<span>Script</span>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
className={`${baseClass}__heading-group ${baseClass}__script-statuses`}
|
||||||
|
>
|
||||||
|
<div className={`${baseClass}__status`}>
|
||||||
|
<div data-tip data-for="ran">
|
||||||
|
<Icon name="success" />
|
||||||
|
<span>Ran</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className={`${baseClass}__status`}>
|
||||||
|
<div data-tip data-for="pending">
|
||||||
|
<Icon name="pending" />
|
||||||
|
<span>Pending</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className={`${baseClass}__status`}>
|
||||||
|
<div data-tip data-for="errors">
|
||||||
|
<Icon name="error" />
|
||||||
|
<span>Errors</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
className={`${baseClass}__heading-group ${baseClass}__actions-heading`}
|
||||||
|
>
|
||||||
|
<span>Actions</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<ReactTooltip
|
||||||
|
type="dark"
|
||||||
|
effect="solid"
|
||||||
|
id="ran"
|
||||||
|
backgroundColor={COLORS["tooltip-bg"]}
|
||||||
|
>
|
||||||
|
<span className={`${baseClass}__tooltip-text`}>
|
||||||
|
Script ran and exited with status code 0.
|
||||||
|
</span>
|
||||||
|
</ReactTooltip>
|
||||||
|
<ReactTooltip
|
||||||
|
type="dark"
|
||||||
|
effect="solid"
|
||||||
|
id="pending"
|
||||||
|
backgroundColor={COLORS["tooltip-bg"]}
|
||||||
|
>
|
||||||
|
<span className={`${baseClass}__tooltip-text`}>
|
||||||
|
Script will run when the host comes online.
|
||||||
|
</span>
|
||||||
|
</ReactTooltip>
|
||||||
|
<ReactTooltip
|
||||||
|
type="dark"
|
||||||
|
effect="solid"
|
||||||
|
id="errors"
|
||||||
|
backgroundColor={COLORS["tooltip-bg"]}
|
||||||
|
>
|
||||||
|
<span className={`${baseClass}__tooltip-text`}>
|
||||||
|
Script ran and exited with a non-zero status code. Click on a host to
|
||||||
|
view error(s).
|
||||||
|
</span>
|
||||||
|
</ReactTooltip>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ScriptListHeading;
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1 @@
|
|||||||
|
export { default } from "./ScriptListHeading";
|
@ -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 (
|
||||||
|
<div className={baseClass}>
|
||||||
|
<div className={`${baseClass}__value-group ${baseClass}__script-data`}>
|
||||||
|
<Icon name={getFileIconName(script.name)} />
|
||||||
|
<div className={`${baseClass}__script-info`}>
|
||||||
|
<span className={`${baseClass}__script-name`}>{script.name}</span>
|
||||||
|
<span className={`${baseClass}__script-uploaded`}>
|
||||||
|
{`Uploaded ${formatDistanceToNow(new Date(script.created_at))} ago`}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
className={`${baseClass}__value-group ${baseClass}__script-statuses`}
|
||||||
|
>
|
||||||
|
<span className={getStatusClassName(script.ran)}>{script.ran}</span>
|
||||||
|
<span className={getStatusClassName(script.pending)}>
|
||||||
|
{script.pending}
|
||||||
|
</span>
|
||||||
|
<span className={getStatusClassName(script.errors)}>
|
||||||
|
{script.errors}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className={`${baseClass}__value-group ${baseClass}__script-actions`}>
|
||||||
|
<Button
|
||||||
|
className={`${baseClass}__refresh-button`}
|
||||||
|
variant="text-icon"
|
||||||
|
onClick={() => onRerun(script)}
|
||||||
|
>
|
||||||
|
<Icon name="refresh" />
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
className={`${baseClass}__download-button`}
|
||||||
|
variant="text-icon"
|
||||||
|
onClick={onClickDownload}
|
||||||
|
>
|
||||||
|
<Icon name="download" />
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
className={`${baseClass}__delete-button`}
|
||||||
|
variant="text-icon"
|
||||||
|
onClick={() => onDelete(script)}
|
||||||
|
>
|
||||||
|
<Icon name="trash" color="ui-fleet-black-75" />
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ScriptListItem;
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1 @@
|
|||||||
|
export { default } from "./ScriptListItem";
|
1
frontend/pages/ManageControlsPage/MacOSScripts/index.ts
Normal file
1
frontend/pages/ManageControlsPage/MacOSScripts/index.ts
Normal file
@ -0,0 +1 @@
|
|||||||
|
export { default } from "./MacOSScripts";
|
@ -1,9 +1,6 @@
|
|||||||
import React, { useContext, useRef, useState } from "react";
|
import React, { useContext, useRef, useState } from "react";
|
||||||
import { useQuery } from "react-query";
|
import { useQuery } from "react-query";
|
||||||
import { AxiosResponse } from "axios";
|
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 { IApiError } from "interfaces/errors";
|
||||||
import { IMdmProfile, IMdmProfilesResponse } from "interfaces/mdm";
|
import { IMdmProfile, IMdmProfilesResponse } from "interfaces/mdm";
|
||||||
@ -12,11 +9,14 @@ import { AppContext } from "context/app";
|
|||||||
import { NotificationContext } from "context/notification";
|
import { NotificationContext } from "context/notification";
|
||||||
|
|
||||||
import CustomLink from "components/CustomLink";
|
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 { UPLOAD_ERROR_MESSAGES, getErrorMessage } from "./helpers";
|
||||||
import DeleteProfileModal from "./components/DeleteProfileModal/DeleteProfileModal";
|
import DeleteProfileModal from "./components/DeleteProfileModal/DeleteProfileModal";
|
||||||
|
import ProfileListItem from "./components/ProfileListItem";
|
||||||
|
import ProfileListHeading from "./components/ProfileListHeading";
|
||||||
|
|
||||||
const baseClass = "custom-settings";
|
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) => {
|
const onClickDelete = (profile: IMdmProfile) => {
|
||||||
selectedProfile.current = profile;
|
selectedProfile.current = profile;
|
||||||
setShowDeleteProfileModal(true);
|
setShowDeleteProfileModal(true);
|
||||||
};
|
};
|
||||||
|
|
||||||
const renderProfiles = () => {
|
|
||||||
if (!profiles || profiles.length === 0) return null;
|
|
||||||
|
|
||||||
const profileListItems = profiles.map((profile) => {
|
|
||||||
return (
|
|
||||||
<li key={profile.profile_id} className={`${baseClass}__profile`}>
|
|
||||||
<div className={`${baseClass}__profile-data`}>
|
|
||||||
<Icon name="profile" />
|
|
||||||
<div className={`${baseClass}__profile-info`}>
|
|
||||||
<span className={`${baseClass}__profile-name`}>
|
|
||||||
{profile.name}
|
|
||||||
</span>
|
|
||||||
<span className={`${baseClass}__profile-uploaded`}>
|
|
||||||
{`Uploaded ${formatDistanceToNow(
|
|
||||||
new Date(profile.created_at)
|
|
||||||
)} ago`}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className={`${baseClass}__profile-actions`}>
|
|
||||||
<Button
|
|
||||||
className={`${baseClass}__download-button`}
|
|
||||||
variant="text-icon"
|
|
||||||
onClick={() => onClickDownload(profile)}
|
|
||||||
>
|
|
||||||
<Icon name="download" />
|
|
||||||
</Button>
|
|
||||||
<Button
|
|
||||||
className={`${baseClass}__delete-button`}
|
|
||||||
variant="text-icon"
|
|
||||||
onClick={() => onClickDelete(profile)}
|
|
||||||
>
|
|
||||||
<Icon name="trash" color="ui-fleet-black-75" />
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
</li>
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className={`${baseClass}__profiles`}>
|
|
||||||
<div className={`${baseClass}__profiles-header`}>
|
|
||||||
<span>Configuration profile</span>
|
|
||||||
<span>Actions</span>
|
|
||||||
</div>
|
|
||||||
<ul className={`${baseClass}__profile-list`}>{profileListItems}</ul>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
const onFileUpload = async (files: FileList | null) => {
|
const onFileUpload = async (files: FileList | null) => {
|
||||||
setShowLoading(true);
|
setShowLoading(true);
|
||||||
|
|
||||||
@ -163,21 +105,22 @@ const CustomSettings = () => {
|
|||||||
/>
|
/>
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
{renderProfiles()}
|
{profiles && (
|
||||||
|
<UploadList
|
||||||
<div className={`${baseClass}__profile-uploader`}>
|
listItems={profiles}
|
||||||
<Icon name="profile" />
|
HeadingComponent={ProfileListHeading}
|
||||||
<p>Configuration profile (.mobileconfig)</p>
|
ListItemComponent={({ listItem }) => (
|
||||||
<Button isLoading={showLoading}>
|
<ProfileListItem profile={listItem} onDelete={onClickDelete} />
|
||||||
<label htmlFor="upload-profile">Upload</label>
|
)}
|
||||||
</Button>
|
|
||||||
<input
|
|
||||||
accept=".mobileconfig,application/x-apple-aspen-config"
|
|
||||||
id="upload-profile"
|
|
||||||
type="file"
|
|
||||||
onChange={(e) => onFileUpload(e.target.files)}
|
|
||||||
/>
|
/>
|
||||||
</div>
|
)}
|
||||||
|
|
||||||
|
<FileUploader
|
||||||
|
icon="profile"
|
||||||
|
message="Configuration profile (.mobileconfig)"
|
||||||
|
isLoading={showLoading}
|
||||||
|
onFileUpload={onFileUpload}
|
||||||
|
/>
|
||||||
{showDeleteProfileModal && selectedProfile.current && (
|
{showDeleteProfileModal && selectedProfile.current && (
|
||||||
<DeleteProfileModal
|
<DeleteProfileModal
|
||||||
profileName={selectedProfile.current?.name}
|
profileName={selectedProfile.current?.name}
|
||||||
|
@ -28,55 +28,4 @@
|
|||||||
padding: 0;
|
padding: 0;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
&__profile {
|
|
||||||
padding: $pad-medium $pad-large;
|
|
||||||
display: flex;
|
|
||||||
justify-content: space-between;
|
|
||||||
align-items: center;
|
|
||||||
border-bottom: 1px solid $ui-fleet-black-10;
|
|
||||||
}
|
|
||||||
|
|
||||||
&__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;
|
|
||||||
}
|
|
||||||
|
|
||||||
&__profile-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;
|
|
||||||
|
|
||||||
input {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,14 @@
|
|||||||
|
import React from "react";
|
||||||
|
|
||||||
|
const baseClass = "profile-list-heading";
|
||||||
|
|
||||||
|
const ProfileListHeading = () => {
|
||||||
|
return (
|
||||||
|
<div className={baseClass}>
|
||||||
|
<span>Configuration profile</span>
|
||||||
|
<span className={`${baseClass}__actions-heading`}>Actions</span>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ProfileListHeading;
|
@ -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
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1 @@
|
|||||||
|
export { default } from "./ProfileListHeading";
|
@ -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 (
|
||||||
|
<div className={baseClass}>
|
||||||
|
<div className={`${baseClass}__profile-data`}>
|
||||||
|
<Icon name="profile" />
|
||||||
|
<div className={`${baseClass}__profile-info`}>
|
||||||
|
<span className={`${baseClass}__profile-name`}>{profile.name}</span>
|
||||||
|
<span className={`${baseClass}__profile-uploaded`}>
|
||||||
|
{`Uploaded ${formatDistanceToNow(
|
||||||
|
new Date(profile.created_at)
|
||||||
|
)} ago`}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className={`${baseClass}__profile-actions`}>
|
||||||
|
<Button
|
||||||
|
className={`${baseClass}__download-button`}
|
||||||
|
variant="text-icon"
|
||||||
|
onClick={onClickDownload}
|
||||||
|
>
|
||||||
|
<Icon name="download" />
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
className={`${baseClass}__delete-button`}
|
||||||
|
variant="text-icon"
|
||||||
|
onClick={() => onDelete(profile)}
|
||||||
|
>
|
||||||
|
<Icon name="trash" color="ui-fleet-black-75" />
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ProfileListItem;
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1 @@
|
|||||||
|
export { default } from "./ProfileListItem";
|
@ -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 (
|
||||||
|
<div className={baseClass}>
|
||||||
|
<Icon name={icon} />
|
||||||
|
<p>{message}</p>
|
||||||
|
<Button isLoading={isLoading}>
|
||||||
|
<label htmlFor="upload-profile">Upload</label>
|
||||||
|
</Button>
|
||||||
|
<input
|
||||||
|
accept=".mobileconfig,application/x-apple-aspen-config"
|
||||||
|
id="upload-profile"
|
||||||
|
type="file"
|
||||||
|
onChange={(e) => onFileUpload(e.target.files)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default FileUploader;
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1 @@
|
|||||||
|
export { default } from "./FileUploader";
|
@ -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 (
|
||||||
|
<li key={`${listItem.id}`} className={`${baseClass}__list-item`}>
|
||||||
|
<ListItemComponent listItem={listItem} />
|
||||||
|
</li>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={baseClass}>
|
||||||
|
<div className={`${baseClass}__header`}>
|
||||||
|
<HeadingComponent />
|
||||||
|
</div>
|
||||||
|
<ul className={`${baseClass}__list`}>{items}</ul>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default UploadList;
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1 @@
|
|||||||
|
export { default } from "./UploadList";
|
@ -8,6 +8,7 @@ export default {
|
|||||||
CONTROLS_MAC_OS_UPDATES: `${URL_PREFIX}/controls/mac-os-updates`,
|
CONTROLS_MAC_OS_UPDATES: `${URL_PREFIX}/controls/mac-os-updates`,
|
||||||
CONTROLS_MAC_SETTINGS: `${URL_PREFIX}/controls/mac-settings`,
|
CONTROLS_MAC_SETTINGS: `${URL_PREFIX}/controls/mac-settings`,
|
||||||
CONTROLS_CUSTOM_SETTINGS: `${URL_PREFIX}/controls/mac-settings/custom-settings`,
|
CONTROLS_CUSTOM_SETTINGS: `${URL_PREFIX}/controls/mac-settings/custom-settings`,
|
||||||
|
CONTROLS_MAC_SCRIPTS: `${URL_PREFIX}/controls/mac-scripts`,
|
||||||
DASHBOARD: `${URL_PREFIX}/dashboard`,
|
DASHBOARD: `${URL_PREFIX}/dashboard`,
|
||||||
DASHBOARD_LINUX: `${URL_PREFIX}/dashboard/linux`,
|
DASHBOARD_LINUX: `${URL_PREFIX}/dashboard/linux`,
|
||||||
DASHBOARD_MAC: `${URL_PREFIX}/dashboard/mac`,
|
DASHBOARD_MAC: `${URL_PREFIX}/dashboard/mac`,
|
||||||
|
Loading…
Reference in New Issue
Block a user