| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455 |
- import React, { Component } from "react";
- import styled from "styled-components";
- import Modal from "../../main/home/modals/Modal";
- import LoadEnvGroupModal from "../../main/home/modals/LoadEnvGroupModal";
- import EnvEditorModal from "../../main/home/modals/EnvEditorModal";
- import sliders from "assets/sliders.svg";
- import upload from "assets/upload.svg";
- import { keysIn } from "lodash";
- export type KeyValue = {
- key: string;
- value: string;
- };
- type PropsType = {
- label?: string;
- values: any;
- setValues?: (x: any) => void;
- width?: string;
- disabled?: boolean;
- externalValues?: any;
- envLoader?: boolean;
- fileUpload?: boolean;
- secretOption?: boolean;
- };
- type StateType = {
- values: any[];
- showEnvModal: boolean;
- showEditorModal: boolean;
- };
- export default class KeyValueArray extends Component<PropsType, StateType> {
- state = {
- values: [] as any[],
- showEnvModal: false,
- showEditorModal: false,
- };
- componentDidMount() {
- let arr = [] as any[];
- if (this.props.values) {
- Object.keys(this.props.values).forEach((key: string, i: number) => {
- arr.push({ key, value: this.props.values[key] });
- });
- }
- this.setState({ values: arr });
- }
- valuesToObject = () => {
- let obj = {} as any;
- const rg = /(?:^|[^\\])(\\n)/g;
- const fixNewlines = (s: string) => {
- while (rg.test(s)) {
- s = s.replace(rg, (str) => {
- if (str.length == 2) return "\n";
- if (str[0] != "\\") return str[0] + "\n";
- return "\\n";
- });
- }
- return s;
- };
- const isNumber = (s: string) => {
- return !isNaN(!s ? NaN : Number(String(s).trim()));
- };
- this.state.values.forEach((entry: any, i: number) => {
- if (isNumber(entry.value)) {
- obj[entry.key] = entry.value;
- } else {
- obj[entry.key] = fixNewlines(entry.value);
- }
- });
- return obj;
- };
- objectToValues = (obj: Record<string, string>): KeyValue[] => {
- return Object.entries(obj).map(([key, value]) => ({ key, value }));
- };
- renderDeleteButton = (i: number) => {
- if (!this.props.disabled) {
- return (
- <DeleteButton
- onClick={() => {
- this.state.values.splice(i, 1);
- this.setState({ values: this.state.values });
- let obj = this.valuesToObject();
- this.props.setValues(obj);
- }}
- >
- <i className="material-icons">cancel</i>
- </DeleteButton>
- );
- }
- };
- renderHiddenOption = (hidden: boolean, i: number) => {
- if (this.props.secretOption && hidden) {
- return (
- <HideButton>
- <i className="material-icons">lock</i>
- </HideButton>
- );
- }
- };
- renderInputList = () => {
- return (
- <>
- {this.state.values.map((entry: any, i: number) => {
- // Preprocess non-string env values set via raw Helm values
- let { value } = entry;
- if (typeof value === "object") {
- value = JSON.stringify(value);
- } else if (typeof value === "number" || typeof value === "boolean") {
- value = value.toString();
- }
- return (
- <InputWrapper key={i}>
- <Input
- placeholder="ex: key"
- width="270px"
- value={entry.key}
- onChange={(e: any) => {
- this.state.values[i].key = e.target.value;
- this.setState({ values: this.state.values });
- let obj = this.valuesToObject();
- this.props.setValues(obj);
- }}
- disabled={this.props.disabled || value.includes("PORTERSECRET")}
- spellCheck={false}
- />
- <Spacer />
- <Input
- placeholder="ex: value"
- width="270px"
- value={value}
- onChange={(e: any) => {
- this.state.values[i].value = e.target.value;
- this.setState({ values: this.state.values });
- let obj = this.valuesToObject();
- this.props.setValues(obj);
- }}
- disabled={this.props.disabled || value.includes("PORTERSECRET")}
- type={value.includes("PORTERSECRET") ? "password" : "text"}
- spellCheck={false}
- />
- {this.renderDeleteButton(i)}
- {this.renderHiddenOption(value.includes("PORTERSECRET"), i)}
- </InputWrapper>
- );
- })}
- </>
- );
- };
- renderEnvModal = () => {
- if (this.state.showEnvModal) {
- return (
- <Modal
- onRequestClose={() => this.setState({ showEnvModal: false })}
- width="765px"
- height="542px"
- >
- <LoadEnvGroupModal
- existingValues={this.props.values}
- namespace={this.props.externalValues?.namespace}
- clusterId={this.props.externalValues?.clusterId}
- closeModal={() => this.setState({ showEnvModal: false })}
- setValues={(values) => {
- const newValues = { ...this.props.values, ...values };
- this.props.setValues(newValues);
- this.setState({ values: this.objectToValues(newValues) });
- }}
- />
- </Modal>
- );
- }
- };
- renderEditorModal = () => {
- if (this.state.showEditorModal) {
- return (
- <Modal
- onRequestClose={() => this.setState({ showEditorModal: false })}
- width="60%"
- height="80%"
- >
- <EnvEditorModal
- closeModal={() => this.setState({ showEditorModal: false })}
- setEnvVariables={(envFile: string) => this.readFile(envFile)}
- />
- </Modal>
- );
- }
- };
- // Parses src into an Object
- parseEnv = (src: any, options: any) => {
- const debug = Boolean(options && options.debug);
- const obj = {} as Record<string, string>;
- const NEWLINE = "\n";
- const RE_INI_KEY_VAL = /^\s*([\w.-]+)\s*=\s*(.*)?\s*$/;
- const RE_NEWLINES = /\\n/g;
- const NEWLINES_MATCH = /\n|\r|\r\n/;
- // convert Buffers before splitting into lines and processing
- src
- .toString()
- .split(NEWLINES_MATCH)
- .forEach(function (line: any, idx: any) {
- // matching "KEY' and 'VAL' in 'KEY=VAL'
- const keyValueArr = line.match(RE_INI_KEY_VAL);
- // matched?
- if (keyValueArr != null) {
- const key = keyValueArr[1];
- // default undefined or missing values to empty string
- let val = keyValueArr[2] || "";
- const end = val.length - 1;
- const isDoubleQuoted = val[0] === '"' && val[end] === '"';
- const isSingleQuoted = val[0] === "'" && val[end] === "'";
- // if single or double quoted, remove quotes
- if (isSingleQuoted || isDoubleQuoted) {
- val = val.substring(1, end);
- // if double quoted, expand newlines
- if (isDoubleQuoted) {
- val = val.replace(RE_NEWLINES, NEWLINE);
- }
- } else {
- // remove surrounding whitespace
- val = val.trim();
- }
- obj[key] = val;
- } else if (debug) {
- console.log(
- `did not match key and value when parsing line ${idx + 1}: ${line}`
- );
- }
- });
- return obj;
- };
- readFile = (env: string) => {
- let envObj = this.parseEnv(env, null);
- let push = true;
- for (let key in envObj) {
- for (var i = 0; i < this.state.values.length; i++) {
- let existingKey = this.state.values[i]["key"];
- if (key === existingKey) {
- this.state.values[i]["value"] = envObj[key];
- push = false;
- }
- }
- if (push) {
- this.state.values.push({ key, value: envObj[key] });
- }
- }
- this.setState({ values: this.state.values }, () => {
- let obj = this.valuesToObject();
- this.props.setValues(obj);
- });
- };
- render() {
- return (
- <>
- <StyledInputArray>
- <Label>{this.props.label}</Label>
- {this.state.values.length === 0 ? <></> : this.renderInputList()}
- {this.props.disabled ? (
- <></>
- ) : (
- <InputWrapper>
- <AddRowButton
- onClick={() => {
- this.state.values.push({ key: "", value: "" });
- this.setState({ values: this.state.values });
- }}
- >
- <i className="material-icons">add</i> Add Row
- </AddRowButton>
- <Spacer />
- {this.props.externalValues?.namespace && this.props.envLoader && (
- <LoadButton
- onClick={() =>
- this.setState({ showEnvModal: !this.state.showEnvModal })
- }
- >
- <img src={sliders} /> Load from Env Group
- </LoadButton>
- )}
- {this.props.fileUpload && (
- <UploadButton
- onClick={() => {
- this.setState({ showEditorModal: true });
- }}
- >
- <img src={upload} /> Copy from File
- </UploadButton>
- )}
- </InputWrapper>
- )}
- </StyledInputArray>
- {this.renderEnvModal()}
- {this.renderEditorModal()}
- </>
- );
- }
- }
- const Spacer = styled.div`
- width: 10px;
- height: 20px;
- `;
- const AddRowButton = styled.div`
- display: flex;
- align-items: center;
- width: 270px;
- font-size: 13px;
- color: #aaaabb;
- height: 32px;
- border-radius: 3px;
- cursor: pointer;
- background: #ffffff11;
- :hover {
- background: #ffffff22;
- }
- > i {
- color: #ffffff44;
- font-size: 16px;
- margin-left: 8px;
- margin-right: 10px;
- display: flex;
- align-items: center;
- justify-content: center;
- }
- `;
- const LoadButton = styled(AddRowButton)`
- background: none;
- border: 1px solid #ffffff55;
- > i {
- color: #ffffff44;
- font-size: 16px;
- margin-left: 8px;
- margin-right: 10px;
- display: flex;
- align-items: center;
- justify-content: center;
- }
- > img {
- width: 14px;
- margin-left: 10px;
- margin-right: 12px;
- }
- `;
- const UploadButton = styled(AddRowButton)`
- background: none;
- position: relative;
- border: 1px solid #ffffff55;
- > i {
- color: #ffffff44;
- font-size: 16px;
- margin-left: 8px;
- margin-right: 10px;
- display: flex;
- align-items: center;
- justify-content: center;
- }
- > img {
- width: 14px;
- margin-left: 10px;
- margin-right: 12px;
- }
- `;
- const DeleteButton = styled.div`
- width: 15px;
- height: 15px;
- display: flex;
- align-items: center;
- margin-left: 8px;
- margin-top: -3px;
- justify-content: center;
- > i {
- font-size: 17px;
- color: #ffffff44;
- display: flex;
- align-items: center;
- justify-content: center;
- cursor: pointer;
- :hover {
- color: #ffffff88;
- }
- }
- `;
- const HideButton = styled(DeleteButton)`
- margin-top: -5px;
- > i {
- font-size: 19px;
- cursor: default;
- :hover {
- color: #ffffff44;
- }
- }
- `;
- const InputWrapper = styled.div`
- display: flex;
- align-items: center;
- margin-top: 5px;
- `;
- const Input = styled.input`
- outline: none;
- border: none;
- margin-bottom: 5px;
- font-size: 13px;
- background: #ffffff11;
- border: 1px solid #ffffff55;
- border-radius: 3px;
- width: ${(props: { disabled?: boolean; width: string }) =>
- props.width ? props.width : "270px"};
- color: ${(props: { disabled?: boolean; width: string }) =>
- props.disabled ? "#ffffff44" : "white"};
- padding: 5px 10px;
- height: 35px;
- `;
- const Label = styled.div`
- color: #ffffff;
- margin-bottom: 10px;
- `;
- const StyledInputArray = styled.div`
- margin-bottom: 15px;
- margin-top: 22px;
- `;
|