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",
|
||||
"prefix": "bc",
|
||||
"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 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;
|
||||
|
@ -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;
|
||||
}
|
||||
|
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 { 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 (
|
||||
<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) => {
|
||||
setShowLoading(true);
|
||||
|
||||
@ -163,21 +105,22 @@ const CustomSettings = () => {
|
||||
/>
|
||||
</p>
|
||||
|
||||
{renderProfiles()}
|
||||
|
||||
<div className={`${baseClass}__profile-uploader`}>
|
||||
<Icon name="profile" />
|
||||
<p>Configuration profile (.mobileconfig)</p>
|
||||
<Button isLoading={showLoading}>
|
||||
<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)}
|
||||
{profiles && (
|
||||
<UploadList
|
||||
listItems={profiles}
|
||||
HeadingComponent={ProfileListHeading}
|
||||
ListItemComponent={({ listItem }) => (
|
||||
<ProfileListItem profile={listItem} onDelete={onClickDelete} />
|
||||
)}
|
||||
/>
|
||||
)}
|
||||
|
||||
<FileUploader
|
||||
icon="profile"
|
||||
message="Configuration profile (.mobileconfig)"
|
||||
isLoading={showLoading}
|
||||
onFileUpload={onFileUpload}
|
||||
/>
|
||||
</div>
|
||||
{showDeleteProfileModal && selectedProfile.current && (
|
||||
<DeleteProfileModal
|
||||
profileName={selectedProfile.current?.name}
|
||||
|
@ -28,55 +28,4 @@
|
||||
padding: 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_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`,
|
||||
|
Loading…
Reference in New Issue
Block a user