PermissionsModal.tsx 8.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287
  1. import { useQueryClient } from "@tanstack/react-query";
  2. import cn from "classnames";
  3. import EasyModal, { type InnerModalProps } from "ez-modal-react";
  4. import { Field, Form, Formik } from "formik";
  5. import { type ReactNode, useState } from "react";
  6. import { Alert } from "react-bootstrap";
  7. import Modal from "react-bootstrap/Modal";
  8. import { setPermissions } from "src/api/backend";
  9. import { Button, Loading } from "src/components";
  10. import { useUser } from "src/hooks";
  11. import { T } from "src/locale";
  12. import styles from "./PermissionsModal.module.css";
  13. const showPermissionsModal = (id: number) => {
  14. EasyModal.show(PermissionsModal, { id });
  15. };
  16. interface Props extends InnerModalProps {
  17. id: number;
  18. }
  19. const PermissionsModal = EasyModal.create(({ id, visible, remove }: Props) => {
  20. const queryClient = useQueryClient();
  21. const [errorMsg, setErrorMsg] = useState<ReactNode | null>(null);
  22. const { data, isLoading, error } = useUser(id);
  23. const [isSubmitting, setIsSubmitting] = useState(false);
  24. const onSubmit = async (values: any, { setSubmitting }: any) => {
  25. if (isSubmitting) return;
  26. setIsSubmitting(true);
  27. setErrorMsg(null);
  28. try {
  29. await setPermissions(id, values);
  30. remove();
  31. queryClient.invalidateQueries({ queryKey: ["users"] });
  32. queryClient.invalidateQueries({ queryKey: ["user"] });
  33. } catch (err: any) {
  34. setErrorMsg(<T id={err.message} />);
  35. }
  36. setSubmitting(false);
  37. setIsSubmitting(false);
  38. };
  39. const getClasses = (active: boolean) => {
  40. return cn("btn", active ? styles.active : null, {
  41. active,
  42. "bg-orange-lt": active,
  43. });
  44. };
  45. // given the field and clicked permission, intelligently set the value, and
  46. // other values that depends on it.
  47. const handleChange = (form: any, field: any, perm: string) => {
  48. if (field.name === "proxyHosts" && perm !== "hidden" && form.values.accessLists === "hidden") {
  49. form.setFieldValue("accessLists", "view");
  50. }
  51. // certs are required for proxy and redirection hosts, and streams
  52. if (
  53. ["proxyHosts", "redirectionHosts", "deadHosts", "streams"].includes(field.name) &&
  54. perm !== "hidden" &&
  55. form.values.certificates === "hidden"
  56. ) {
  57. form.setFieldValue("certificates", "view");
  58. }
  59. form.setFieldValue(field.name, perm);
  60. };
  61. const getPermissionButtons = (field: any, form: any) => {
  62. const isManage = field.value === "manage";
  63. const isView = field.value === "view";
  64. const isHidden = field.value === "hidden";
  65. let hiddenDisabled = false;
  66. if (field.name === "accessLists") {
  67. hiddenDisabled = form.values.proxyHosts !== "hidden";
  68. }
  69. if (field.name === "certificates") {
  70. hiddenDisabled =
  71. form.values.proxyHosts !== "hidden" ||
  72. form.values.redirectionHosts !== "hidden" ||
  73. form.values.deadHosts !== "hidden" ||
  74. form.values.streams !== "hidden";
  75. }
  76. return (
  77. <div>
  78. <div className="btn-group w-100" role="group">
  79. <input
  80. type="radio"
  81. className="btn-check"
  82. name="btn-radio-basic"
  83. id={`${field.name}-manage`}
  84. autoComplete="off"
  85. value="manage"
  86. checked={field.value === "manage"}
  87. onChange={() => handleChange(form, field, "manage")}
  88. />
  89. <label htmlFor={`${field.name}-manage`} className={getClasses(isManage)}>
  90. <T id="permissions.manage" />
  91. </label>
  92. <input
  93. type="radio"
  94. className="btn-check"
  95. name="btn-radio-basic"
  96. id={`${field.name}-view`}
  97. autoComplete="off"
  98. value="view"
  99. checked={field.value === "view"}
  100. onChange={() => handleChange(form, field, "view")}
  101. />
  102. <label htmlFor={`${field.name}-view`} className={getClasses(isView)}>
  103. <T id="permissions.view" />
  104. </label>
  105. <input
  106. type="radio"
  107. className="btn-check"
  108. name="btn-radio-basic"
  109. id={`${field.name}-hidden`}
  110. autoComplete="off"
  111. value="hidden"
  112. checked={field.value === "hidden"}
  113. disabled={hiddenDisabled}
  114. onChange={() => handleChange(form, field, "hidden")}
  115. />
  116. <label htmlFor={`${field.name}-hidden`} className={getClasses(isHidden)}>
  117. <T id="permissions.hidden" />
  118. </label>
  119. </div>
  120. </div>
  121. );
  122. };
  123. const isAdmin = data?.roles.indexOf("admin") !== -1;
  124. return (
  125. <Modal show={visible} onHide={remove}>
  126. {!isLoading && error && (
  127. <Alert variant="danger" className="m-3">
  128. {error?.message || "Unknown error"}
  129. </Alert>
  130. )}
  131. {isLoading && <Loading noLogo />}
  132. {!isLoading && data && (
  133. <Formik
  134. initialValues={
  135. {
  136. visibility: data.permissions?.visibility,
  137. accessLists: data.permissions?.accessLists,
  138. certificates: data.permissions?.certificates,
  139. deadHosts: data.permissions?.deadHosts,
  140. proxyHosts: data.permissions?.proxyHosts,
  141. redirectionHosts: data.permissions?.redirectionHosts,
  142. streams: data.permissions?.streams,
  143. } as any
  144. }
  145. onSubmit={onSubmit}
  146. >
  147. {() => (
  148. <Form>
  149. <Modal.Header closeButton>
  150. <Modal.Title>
  151. <T id="user.set-permissions" data={{ name: data?.name }} />
  152. </Modal.Title>
  153. </Modal.Header>
  154. <Modal.Body>
  155. <Alert variant="danger" show={!!error} onClose={() => setErrorMsg(null)} dismissible>
  156. {errorMsg}
  157. </Alert>
  158. <div className="mb-3">
  159. <label htmlFor="asd" className="form-label">
  160. <T id="permissions.visibility.title" />
  161. </label>
  162. <Field name="visibility">
  163. {({ field, form }: any) => (
  164. <div className="btn-group w-100" role="group">
  165. <input
  166. type="radio"
  167. className="btn-check"
  168. name="btn-radio-basic"
  169. id={`${field.name}-user`}
  170. autoComplete="off"
  171. value="user"
  172. checked={field.value === "user"}
  173. onChange={() => form.setFieldValue(field.name, "user")}
  174. />
  175. <label
  176. htmlFor={`${field.name}-user`}
  177. className={getClasses(field.value === "user")}
  178. >
  179. <T id="permissions.visibility.user" />
  180. </label>
  181. <input
  182. type="radio"
  183. className="btn-check"
  184. name="btn-radio-basic"
  185. id={`${field.name}-all`}
  186. autoComplete="off"
  187. value="all"
  188. checked={field.value === "all"}
  189. onChange={() => form.setFieldValue(field.name, "all")}
  190. />
  191. <label
  192. htmlFor={`${field.name}-all`}
  193. className={getClasses(field.value === "all")}
  194. >
  195. <T id="permissions.visibility.all" />
  196. </label>
  197. </div>
  198. )}
  199. </Field>
  200. </div>
  201. {!isAdmin && (
  202. <>
  203. <div className="mb-3">
  204. <label htmlFor="ignored" className="form-label">
  205. <T id="proxy-hosts" />
  206. </label>
  207. <Field name="proxyHosts">
  208. {({ field, form }: any) => getPermissionButtons(field, form)}
  209. </Field>
  210. </div>
  211. <div className="mb-3">
  212. <label htmlFor="ignored" className="form-label">
  213. <T id="redirection-hosts" />
  214. </label>
  215. <Field name="redirectionHosts">
  216. {({ field, form }: any) => getPermissionButtons(field, form)}
  217. </Field>
  218. </div>
  219. <div className="mb-3">
  220. <label htmlFor="ignored" className="form-label">
  221. <T id="dead-hosts" />
  222. </label>
  223. <Field name="deadHosts">
  224. {({ field, form }: any) => getPermissionButtons(field, form)}
  225. </Field>
  226. </div>
  227. <div className="mb-3">
  228. <label htmlFor="ignored" className="form-label">
  229. <T id="streams" />
  230. </label>
  231. <Field name="streams">
  232. {({ field, form }: any) => getPermissionButtons(field, form)}
  233. </Field>
  234. </div>
  235. <div className="mb-3">
  236. <label htmlFor="ignored" className="form-label">
  237. <T id="access-lists" />
  238. </label>
  239. <Field name="accessLists">
  240. {({ field, form }: any) => getPermissionButtons(field, form)}
  241. </Field>
  242. </div>
  243. <div className="mb-3">
  244. <label htmlFor="ignored" className="form-label">
  245. <T id="certificates" />
  246. </label>
  247. <Field name="certificates">
  248. {({ field, form }: any) => getPermissionButtons(field, form)}
  249. </Field>
  250. </div>
  251. </>
  252. )}
  253. </Modal.Body>
  254. <Modal.Footer>
  255. <Button data-bs-dismiss="modal" onClick={remove} disabled={isSubmitting}>
  256. <T id="cancel" />
  257. </Button>
  258. <Button
  259. type="submit"
  260. className="ms-auto btn-orange"
  261. data-bs-dismiss="modal"
  262. isLoading={isSubmitting}
  263. disabled={isSubmitting}
  264. >
  265. <T id="save" />
  266. </Button>
  267. </Modal.Footer>
  268. </Form>
  269. )}
  270. </Formik>
  271. )}
  272. </Modal>
  273. );
  274. });
  275. export { showPermissionsModal };