支持批量创建同名称环境变量,日志和脚本管理PC页支持宽度拖拽

This commit is contained in:
hanhh 2021-08-17 18:34:55 +08:00
parent 30cea84886
commit 3525b1d92a
9 changed files with 114 additions and 89 deletions

View File

@ -25,11 +25,13 @@ export default (app: Router) => {
route.post( route.post(
'/envs', '/envs',
celebrate({ celebrate({
body: Joi.object({ body: Joi.array().items(
value: Joi.string().required(), Joi.object({
name: Joi.string().required(), value: Joi.string().required(),
remarks: Joi.string().optional(), name: Joi.string().required(),
}), remarks: Joi.string().optional().allow(''),
}),
),
}), }),
async (req: Request, res: Response, next: NextFunction) => { async (req: Request, res: Response, next: NextFunction) => {
const logger: Logger = Container.get('logger'); const logger: Logger = Container.get('logger');

View File

@ -16,25 +16,29 @@ export default class EnvService {
}); });
} }
public async create(payload: Env): Promise<Env> { public async create(payloads: Env[]): Promise<Env[]> {
const envs = await this.envs(); const envs = await this.envs();
let position = initEnvPosition; let position = initEnvPosition;
if (envs && envs.length > 0 && envs[envs.length - 1].position) { if (envs && envs.length > 0 && envs[envs.length - 1].position) {
position = envs[envs.length - 1].position; position = envs[envs.length - 1].position;
} }
const tab = new Env({ ...payload, position: position / 2 }); const tabs = payloads.map((x) => {
const doc = await this.insert(tab); position = position / 2;
const tab = new Env({ ...x, position });
return tab;
});
const docs = await this.insert(tabs);
await this.set_envs(); await this.set_envs();
return doc; return docs;
} }
public async insert(payload: Env): Promise<Env> { public async insert(payloads: Env[]): Promise<Env[]> {
return new Promise((resolve) => { return new Promise((resolve) => {
this.cronDb.insert(payload, (err, doc) => { this.cronDb.insert(payloads, (err, docs) => {
if (err) { if (err) {
this.logger.error(err); this.logger.error(err);
} else { } else {
resolve(doc); resolve(docs);
} }
}); });
}); });

View File

@ -191,7 +191,7 @@ input:-webkit-autofill:active {
} }
.Resizer { .Resizer {
background: #000; background: #fff;
opacity: 0.2; opacity: 0.2;
z-index: 1; z-index: 1;
-moz-box-sizing: border-box; -moz-box-sizing: border-box;
@ -200,6 +200,8 @@ input:-webkit-autofill:active {
-moz-background-clip: padding; -moz-background-clip: padding;
-webkit-background-clip: padding; -webkit-background-clip: padding;
background-clip: padding-box; background-clip: padding-box;
border-left: 5px solid rgba(42, 161, 255, 0);
border-right: 5px solid rgba(42, 161, 255, 0);
} }
.Resizer:hover { .Resizer:hover {
@ -210,29 +212,22 @@ input:-webkit-autofill:active {
.Resizer.horizontal { .Resizer.horizontal {
height: 11px; height: 11px;
margin: -5px 0; margin: -5px 0;
border-top: 5px solid rgba(255, 255, 255, 0);
border-bottom: 5px solid rgba(255, 255, 255, 0);
cursor: row-resize; cursor: row-resize;
width: 100%; width: 100%;
} }
.Resizer.horizontal:hover {
border-top: 5px solid rgba(0, 0, 0, 0.5);
border-bottom: 5px solid rgba(0, 0, 0, 0.5);
}
.Resizer.vertical { .Resizer.vertical {
width: 11px; width: 11px;
margin: 0 -5px; margin: 0 -5px;
border-left: 5px solid rgba(255, 255, 255, 0);
border-right: 5px solid rgba(255, 255, 255, 0);
cursor: col-resize; cursor: col-resize;
} }
.Resizer.horizontal:hover,
.Resizer.vertical:hover { .Resizer.vertical:hover {
border-left: 5px solid rgba(0, 0, 0, 0.5); border-left-color: rgba(42, 161, 255, 0.5);
border-right: 5px solid rgba(0, 0, 0, 0.5); border-right-color: rgba(42, 161, 255, 0.5);
} }
.Resizer.disabled { .Resizer.disabled {
cursor: not-allowed; cursor: not-allowed;
} }

View File

@ -317,7 +317,8 @@ const Env = () => {
const result = [...value]; const result = [...value];
const index = value.findIndex((x) => x._id === env._id); const index = value.findIndex((x) => x._id === env._id);
if (index === -1) { if (index === -1) {
result.push(env); env = Array.isArray(env) ? env : [env];
result.push(...env);
} else { } else {
result.splice(index, 1, { result.splice(index, 1, {
...env, ...env,

View File

@ -1,5 +1,5 @@
import React, { useEffect, useState } from 'react'; import React, { useEffect, useState } from 'react';
import { Modal, message, Input, Form } from 'antd'; import { Modal, message, Input, Form, Radio } from 'antd';
import { request } from '@/utils/http'; import { request } from '@/utils/http';
import config from '@/utils/config'; import config from '@/utils/config';
@ -17,8 +17,19 @@ const EnvModal = ({
const handleOk = async (values: any) => { const handleOk = async (values: any) => {
setLoading(true); setLoading(true);
const { value, split, name, remarks } = values;
const method = env ? 'put' : 'post'; const method = env ? 'put' : 'post';
const payload = env ? { ...values, _id: env._id } : values; let payload = env ? { ...values, _id: env._id } : values;
if (!env && split === '1') {
const symbol = value.includes('&') ? '&' : '\n';
payload = value.split(symbol).map((x: any) => {
return {
name: name,
value: x,
remarks: remarks,
};
});
}
const { code, data } = await request[method](`${config.apiPrefix}envs`, { const { code, data } = await request[method](`${config.apiPrefix}envs`, {
data: payload, data: payload,
}); });
@ -61,6 +72,14 @@ const EnvModal = ({
> >
<Input placeholder="请输入环境变量名称" /> <Input placeholder="请输入环境变量名称" />
</Form.Item> </Form.Item>
{!env && (
<Form.Item name="split" label="自动拆分" initialValue="0">
<Radio.Group>
<Radio value="1"></Radio>
<Radio value="0"></Radio>
</Radio.Group>
</Form.Item>
)}
<Form.Item <Form.Item
name="value" name="value"
label="值" label="值"

View File

@ -7,7 +7,6 @@
background-color: #fff; background-color: #fff;
height: calc(100vh - 128px); height: calc(100vh - 128px);
height: calc(100vh - var(--vh-offset, 0px) - 128px); height: calc(100vh - var(--vh-offset, 0px) - 128px);
width: @tree-width;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
} }
@ -20,4 +19,5 @@
.log-container { .log-container {
display: flex; display: flex;
position: relative;
} }

View File

@ -7,6 +7,7 @@ import { request } from '@/utils/http';
import styles from './index.module.less'; import styles from './index.module.less';
import { Controlled as CodeMirror } from 'react-codemirror2'; import { Controlled as CodeMirror } from 'react-codemirror2';
import { useCtx, useTheme } from '@/utils/hooks'; import { useCtx, useTheme } from '@/utils/hooks';
import SplitPane from 'react-split-pane';
function getFilterData(keyword: string, data: any) { function getFilterData(keyword: string, data: any) {
const expandedKeys: string[] = []; const expandedKeys: string[] = [];
@ -133,24 +134,40 @@ const Log = () => {
> >
<div className={`${styles['log-container']} log-container`}> <div className={`${styles['log-container']} log-container`}>
{!isPhone && ( {!isPhone && (
<div className={styles['left-tree-container']}> <SplitPane split="vertical" size={200} maxSize={-100}>
<Input.Search <div className={styles['left-tree-container']}>
className={styles['left-tree-search']} <Input.Search
onChange={onSearch} className={styles['left-tree-search']}
></Input.Search> onChange={onSearch}
<div className={styles['left-tree-scroller']} ref={treeDom}> ></Input.Search>
<Tree <div className={styles['left-tree-scroller']} ref={treeDom}>
className={styles['left-tree']} <Tree
treeData={filterData} className={styles['left-tree']}
showIcon={true} treeData={filterData}
height={height} showIcon={true}
showLine={{ showLeafIcon: true }} height={height}
onSelect={onTreeSelect} showLine={{ showLeafIcon: true }}
></Tree> onSelect={onTreeSelect}
></Tree>
</div>
</div> </div>
</div> <Editor
language="shell"
theme={theme}
value={value}
options={{
readOnly: true,
fontSize: 12,
lineNumbersMinChars: 3,
fontFamily: 'Source Code Pro',
folding: false,
glyphMargin: false,
wordWrap: 'on',
}}
/>
</SplitPane>
)} )}
{isPhone ? ( {isPhone && (
<CodeMirror <CodeMirror
value={value} value={value}
options={{ options={{
@ -165,21 +182,6 @@ const Log = () => {
}} }}
onChange={(editor, data, value) => {}} onChange={(editor, data, value) => {}}
/> />
) : (
<Editor
language="shell"
theme={theme}
value={value}
options={{
readOnly: true,
fontSize: 12,
lineNumbersMinChars: 3,
fontFamily: 'Source Code Pro',
folding: false,
glyphMargin: false,
wordWrap: 'on',
}}
/>
)} )}
</div> </div>
</PageContainer> </PageContainer>

View File

@ -7,7 +7,6 @@
background-color: #fff; background-color: #fff;
height: calc(100vh - 128px); height: calc(100vh - 128px);
height: calc(100vh - var(--vh-offset, 0px) - 128px); height: calc(100vh - var(--vh-offset, 0px) - 128px);
width: @tree-width;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
} }
@ -20,4 +19,5 @@
.log-container { .log-container {
display: flex; display: flex;
position: relative;
} }

View File

@ -8,6 +8,7 @@ import styles from './index.module.less';
import EditModal from './editModal'; import EditModal from './editModal';
import { Controlled as CodeMirror } from 'react-codemirror2'; import { Controlled as CodeMirror } from 'react-codemirror2';
import { useCtx, useTheme } from '@/utils/hooks'; import { useCtx, useTheme } from '@/utils/hooks';
import SplitPane from 'react-split-pane';
function getFilterData(keyword: string, data: any) { function getFilterData(keyword: string, data: any) {
if (keyword) { if (keyword) {
@ -122,24 +123,38 @@ const Script = () => {
> >
<div className={`${styles['log-container']} log-container`}> <div className={`${styles['log-container']} log-container`}>
{!isPhone && ( {!isPhone && (
<div className={styles['left-tree-container']}> <SplitPane split="vertical" size={200} maxSize={-100}>
<Input.Search <div className={styles['left-tree-container']}>
className={styles['left-tree-search']} <Input.Search
onChange={onSearch} className={styles['left-tree-search']}
></Input.Search> onChange={onSearch}
<div className={styles['left-tree-scroller']} ref={treeDom}> ></Input.Search>
<Tree <div className={styles['left-tree-scroller']} ref={treeDom}>
className={styles['left-tree']} <Tree
treeData={filterData} className={styles['left-tree']}
showIcon={true} treeData={filterData}
height={height} showIcon={true}
showLine={{ showLeafIcon: true }} height={height}
onSelect={onTreeSelect} showLine={{ showLeafIcon: true }}
></Tree> onSelect={onTreeSelect}
></Tree>
</div>
</div> </div>
</div> <Editor
language={mode}
value={value}
theme={theme}
options={{
readOnly: true,
fontSize: 12,
lineNumbersMinChars: 3,
folding: false,
glyphMargin: false,
}}
/>
</SplitPane>
)} )}
{isPhone ? ( {isPhone && (
<CodeMirror <CodeMirror
value={value} value={value}
options={{ options={{
@ -155,19 +170,6 @@ const Script = () => {
}} }}
onChange={(editor, data, value) => {}} onChange={(editor, data, value) => {}}
/> />
) : (
<Editor
language={mode}
value={value}
theme={theme}
options={{
readOnly: true,
fontSize: 12,
lineNumbersMinChars: 3,
folding: false,
glyphMargin: false,
}}
/>
)} )}
<EditModal <EditModal
visible={isLogModalVisible} visible={isLogModalVisible}