KeyValueArray.tsx 11 KB

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