KeyValueArray.tsx 11 KB

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