KeyValueArray.tsx 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438
  1. import React, { Component } from "react";
  2. import styled from "styled-components";
  3. import Modal from "../../main/home/modals/Modal";
  4. import LoadEnvGroupModal from "../../main/home/modals/LoadEnvGroupModal";
  5. import EnvEditorModal from "../../main/home/modals/EnvEditorModal";
  6. import sliders from "assets/sliders.svg";
  7. import upload from "assets/upload.svg";
  8. import { keysIn } from "lodash";
  9. type PropsType = {
  10. label?: string;
  11. values: any;
  12. setValues: (x: any) => void;
  13. width?: string;
  14. disabled?: boolean;
  15. namespace?: string;
  16. clusterId?: number;
  17. envLoader?: boolean;
  18. fileUpload?: boolean;
  19. secretOption?: boolean;
  20. };
  21. type StateType = {
  22. values: any[];
  23. showEnvModal: boolean;
  24. showEditorModal: boolean;
  25. };
  26. export default class KeyValueArray extends Component<PropsType, StateType> {
  27. state = {
  28. values: [] as any[],
  29. showEnvModal: false,
  30. showEditorModal: false,
  31. };
  32. componentDidMount() {
  33. let arr = [] as any[];
  34. if (this.props.values) {
  35. Object.keys(this.props.values).forEach((key: string, i: number) => {
  36. arr.push({ key, value: this.props.values[key] });
  37. });
  38. }
  39. this.setState({ values: arr });
  40. }
  41. valuesToObject = () => {
  42. let obj = {} as any;
  43. this.state.values.forEach((entry: any, i: number) => {
  44. obj[entry.key] = entry.value;
  45. });
  46. return obj;
  47. };
  48. objectToValues = (obj: any) => {
  49. let values = [] as any[];
  50. Object.keys(obj).forEach((key: string, i: number) => {
  51. let entry = {} as any;
  52. entry.key = key;
  53. entry.value = obj[key];
  54. values.push(entry);
  55. });
  56. return values;
  57. };
  58. renderDeleteButton = (i: number) => {
  59. if (!this.props.disabled) {
  60. return (
  61. <DeleteButton
  62. onClick={() => {
  63. this.state.values.splice(i, 1);
  64. this.setState({ values: this.state.values });
  65. let obj = this.valuesToObject();
  66. this.props.setValues(obj);
  67. }}
  68. >
  69. <i className="material-icons">cancel</i>
  70. </DeleteButton>
  71. );
  72. }
  73. };
  74. renderHiddenOption = (hidden: boolean, i: number) => {
  75. if (this.props.secretOption && hidden) {
  76. return (
  77. <HideButton>
  78. <i className="material-icons">lock</i>
  79. </HideButton>
  80. );
  81. }
  82. };
  83. renderInputList = () => {
  84. return (
  85. <>
  86. {this.state.values.map((entry: any, i: number) => {
  87. // Preprocess non-string env values set via raw Helm values
  88. let { value } = entry;
  89. if (typeof value === "object") {
  90. value = JSON.stringify(value);
  91. } else if (typeof value === "number" || typeof value === "boolean") {
  92. value = value.toString();
  93. }
  94. return (
  95. <InputWrapper key={i}>
  96. <Input
  97. placeholder="ex: key"
  98. width="270px"
  99. value={entry.key}
  100. onChange={(e: any) => {
  101. this.state.values[i].key = e.target.value;
  102. this.setState({ values: this.state.values });
  103. let obj = this.valuesToObject();
  104. this.props.setValues(obj);
  105. }}
  106. disabled={this.props.disabled || value.includes("PORTERSECRET")}
  107. spellCheck={false}
  108. />
  109. <Spacer />
  110. <Input
  111. placeholder="ex: value"
  112. width="270px"
  113. value={value}
  114. onChange={(e: any) => {
  115. this.state.values[i].value = e.target.value;
  116. this.setState({ values: this.state.values });
  117. let obj = this.valuesToObject();
  118. this.props.setValues(obj);
  119. }}
  120. disabled={this.props.disabled || value.includes("PORTERSECRET")}
  121. type={value.includes("PORTERSECRET") ? "password" : "text"}
  122. spellCheck={false}
  123. />
  124. {this.renderDeleteButton(i)}
  125. {this.renderHiddenOption(value.includes("PORTERSECRET"), i)}
  126. </InputWrapper>
  127. );
  128. })}
  129. </>
  130. );
  131. };
  132. renderEnvModal = () => {
  133. if (this.state.showEnvModal) {
  134. return (
  135. <Modal
  136. onRequestClose={() => this.setState({ showEnvModal: false })}
  137. width="665px"
  138. height="342px"
  139. >
  140. <LoadEnvGroupModal
  141. namespace={this.props.namespace}
  142. clusterId={this.props.clusterId}
  143. closeModal={() => this.setState({ showEnvModal: false })}
  144. setValues={(values: any) => {
  145. this.props.setValues(values);
  146. this.setState({ values: this.objectToValues(values) });
  147. }}
  148. />
  149. </Modal>
  150. );
  151. }
  152. };
  153. renderEditorModal = () => {
  154. if (this.state.showEditorModal) {
  155. return (
  156. <Modal
  157. onRequestClose={() => this.setState({ showEditorModal: false })}
  158. width="60%"
  159. height="80%"
  160. >
  161. <EnvEditorModal
  162. closeModal={() => this.setState({ showEditorModal: false })}
  163. setEnvVariables={(envFile: string) => this.readFile(envFile)}
  164. />
  165. </Modal>
  166. );
  167. }
  168. };
  169. // Parses src into an Object
  170. parseEnv = (src: any, options: any) => {
  171. const debug = Boolean(options && options.debug);
  172. const obj = {} as Record<string, string>;
  173. const NEWLINE = "\n";
  174. const RE_INI_KEY_VAL = /^\s*([\w.-]+)\s*=\s*(.*)?\s*$/;
  175. const RE_NEWLINES = /\\n/g;
  176. const NEWLINES_MATCH = /\n|\r|\r\n/;
  177. // convert Buffers before splitting into lines and processing
  178. src
  179. .toString()
  180. .split(NEWLINES_MATCH)
  181. .forEach(function (line: any, idx: any) {
  182. // matching "KEY' and 'VAL' in 'KEY=VAL'
  183. const keyValueArr = line.match(RE_INI_KEY_VAL);
  184. // matched?
  185. if (keyValueArr != null) {
  186. const key = keyValueArr[1];
  187. // default undefined or missing values to empty string
  188. let val = keyValueArr[2] || "";
  189. const end = val.length - 1;
  190. const isDoubleQuoted = val[0] === '"' && val[end] === '"';
  191. const isSingleQuoted = val[0] === "'" && val[end] === "'";
  192. // if single or double quoted, remove quotes
  193. if (isSingleQuoted || isDoubleQuoted) {
  194. val = val.substring(1, end);
  195. // if double quoted, expand newlines
  196. if (isDoubleQuoted) {
  197. val = val.replace(RE_NEWLINES, NEWLINE);
  198. }
  199. } else {
  200. // remove surrounding whitespace
  201. val = val.trim();
  202. }
  203. obj[key] = val;
  204. } else if (debug) {
  205. console.log(
  206. `did not match key and value when parsing line ${idx + 1}: ${line}`
  207. );
  208. }
  209. });
  210. return obj;
  211. };
  212. readFile = (env: string) => {
  213. let envObj = this.parseEnv(env, null);
  214. let push = true;
  215. for (let key in envObj) {
  216. for (var i = 0; i < this.state.values.length; i++) {
  217. let existingKey = this.state.values[i]["key"];
  218. if (key === existingKey) {
  219. this.state.values[i]["value"] = envObj[key];
  220. push = false;
  221. }
  222. }
  223. if (push) {
  224. this.state.values.push({ key, value: envObj[key] });
  225. }
  226. }
  227. this.setState({ values: this.state.values }, () => {
  228. let obj = this.valuesToObject();
  229. this.props.setValues(obj);
  230. });
  231. };
  232. render() {
  233. return (
  234. <>
  235. <StyledInputArray>
  236. <Label>{this.props.label}</Label>
  237. {this.state.values.length === 0 ? <></> : this.renderInputList()}
  238. {this.props.disabled ? (
  239. <></>
  240. ) : (
  241. <InputWrapper>
  242. <AddRowButton
  243. onClick={() => {
  244. this.state.values.push({ key: "", value: "" });
  245. this.setState({ values: this.state.values });
  246. }}
  247. >
  248. <i className="material-icons">add</i> Add Row
  249. </AddRowButton>
  250. <Spacer />
  251. {this.props.namespace && this.props.envLoader && (
  252. <LoadButton
  253. onClick={() =>
  254. this.setState({ showEnvModal: !this.state.showEnvModal })
  255. }
  256. >
  257. <img src={sliders} /> Load from Env Group
  258. </LoadButton>
  259. )}
  260. {this.props.fileUpload && (
  261. <UploadButton
  262. onClick={() => {
  263. this.setState({ showEditorModal: true });
  264. }}
  265. >
  266. <img src={upload} /> Copy from File
  267. </UploadButton>
  268. )}
  269. </InputWrapper>
  270. )}
  271. </StyledInputArray>
  272. {this.renderEnvModal()}
  273. {this.renderEditorModal()}
  274. </>
  275. );
  276. }
  277. }
  278. const Spacer = styled.div`
  279. width: 10px;
  280. height: 20px;
  281. `;
  282. const AddRowButton = styled.div`
  283. display: flex;
  284. align-items: center;
  285. width: 270px;
  286. font-size: 13px;
  287. color: #aaaabb;
  288. height: 32px;
  289. border-radius: 3px;
  290. cursor: pointer;
  291. background: #ffffff11;
  292. :hover {
  293. background: #ffffff22;
  294. }
  295. > i {
  296. color: #ffffff44;
  297. font-size: 16px;
  298. margin-left: 8px;
  299. margin-right: 10px;
  300. display: flex;
  301. align-items: center;
  302. justify-content: center;
  303. }
  304. `;
  305. const LoadButton = styled(AddRowButton)`
  306. background: none;
  307. border: 1px solid #ffffff55;
  308. > i {
  309. color: #ffffff44;
  310. font-size: 16px;
  311. margin-left: 8px;
  312. margin-right: 10px;
  313. display: flex;
  314. align-items: center;
  315. justify-content: center;
  316. }
  317. > img {
  318. width: 14px;
  319. margin-left: 10px;
  320. margin-right: 12px;
  321. }
  322. `;
  323. const UploadButton = styled(AddRowButton)`
  324. background: none;
  325. position: relative;
  326. border: 1px solid #ffffff55;
  327. > i {
  328. color: #ffffff44;
  329. font-size: 16px;
  330. margin-left: 8px;
  331. margin-right: 10px;
  332. display: flex;
  333. align-items: center;
  334. justify-content: center;
  335. }
  336. > img {
  337. width: 14px;
  338. margin-left: 10px;
  339. margin-right: 12px;
  340. }
  341. `;
  342. const DeleteButton = styled.div`
  343. width: 15px;
  344. height: 15px;
  345. display: flex;
  346. align-items: center;
  347. margin-left: 8px;
  348. margin-top: -3px;
  349. justify-content: center;
  350. > i {
  351. font-size: 17px;
  352. color: #ffffff44;
  353. display: flex;
  354. align-items: center;
  355. justify-content: center;
  356. cursor: pointer;
  357. :hover {
  358. color: #ffffff88;
  359. }
  360. }
  361. `;
  362. const HideButton = styled(DeleteButton)`
  363. margin-top: -5px;
  364. > i {
  365. font-size: 19px;
  366. cursor: default;
  367. :hover {
  368. color: #ffffff44;
  369. }
  370. }
  371. `;
  372. const InputWrapper = styled.div`
  373. display: flex;
  374. align-items: center;
  375. margin-top: 5px;
  376. `;
  377. const Input = styled.input`
  378. outline: none;
  379. border: none;
  380. margin-bottom: 5px;
  381. font-size: 13px;
  382. background: #ffffff11;
  383. border: 1px solid #ffffff55;
  384. border-radius: 3px;
  385. width: ${(props: { disabled?: boolean; width: string }) =>
  386. props.width ? props.width : "270px"};
  387. color: ${(props: { disabled?: boolean; width: string }) =>
  388. props.disabled ? "#ffffff44" : "white"};
  389. padding: 5px 10px;
  390. height: 35px;
  391. `;
  392. const Label = styled.div`
  393. color: #ffffff;
  394. margin-bottom: 10px;
  395. `;
  396. const StyledInputArray = styled.div`
  397. margin-bottom: 15px;
  398. margin-top: 22px;
  399. `;