KeyValueArray.tsx 11 KB

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