[FE UI] Fix some bugs about new FE UI (#4830)
1. Add a search boxer in the left tree view of Playground. 2. Fix some visual bugs of UI. 3. Fix bugs that link failed in QueryProfile view. 4. Fix bugs that cookie is always invalid. 5. Set cookie to HTTP_ONLY to make it more safe.
This commit is contained in:
@ -106,6 +106,7 @@ public class BaseController {
|
||||
Cookie cookie = new Cookie(PALO_SESSION_ID, key);
|
||||
cookie.setMaxAge(PALO_SESSION_EXPIRED_TIME);
|
||||
cookie.setPath("/");
|
||||
cookie.setHttpOnly(true);
|
||||
response.addCookie(cookie);
|
||||
LOG.debug("add session cookie: {} {}", PALO_SESSION_ID, key);
|
||||
HttpAuthManager.getInstance().addSessionValue(key, value);
|
||||
@ -170,6 +171,7 @@ public class BaseController {
|
||||
for (Cookie cookie : cookies) {
|
||||
if (cookie.getName() != null && cookie.getName().equals(cookieName)) {
|
||||
cookie.setMaxAge(age);
|
||||
cookie.setPath("/");
|
||||
response.addCookie(cookie);
|
||||
LOG.debug("get update cookie: {} {}", cookie.getName(), cookie.getValue());
|
||||
}
|
||||
|
||||
@ -47,5 +47,7 @@
|
||||
"tips":"Tips",
|
||||
"fileSizeWarning": "File size cannot exceed 100M",
|
||||
"selectWarning": "Please select a table",
|
||||
"executionTime": "Execution Time"
|
||||
"executionTime": "Execution Time",
|
||||
"search":"Search",
|
||||
"executionFailed":"Execution failed"
|
||||
}
|
||||
|
||||
@ -47,5 +47,7 @@
|
||||
"tips": "提示",
|
||||
"fileSizeWarning": "文件大小不能超过100m",
|
||||
"selectWarning": "请选择表",
|
||||
"executionTime": "执行时间"
|
||||
"executionTime": "执行时间",
|
||||
"search":"查询",
|
||||
"executionFailed": "执行失败"
|
||||
}
|
||||
|
||||
@ -50,7 +50,6 @@ export default function SortFilterTable(props: any) {
|
||||
<FilterFilled/>
|
||||
</Popover>:''}
|
||||
<Table
|
||||
rowKey={allTableData?.column_names?.[0]}
|
||||
columns={localColumns}
|
||||
dataSource={tableData}
|
||||
scroll={{x:true}}
|
||||
|
||||
@ -30,18 +30,19 @@ function getLinkItem(text, record, index, isInner, item, hrefColumn, path){
|
||||
if (record.__hrefPaths[hrefColumn.indexOf(item)].includes('http')) {
|
||||
return <a href={record.__hrefPaths[hrefColumn.indexOf(item)]} target="_blank">{text}</a>;
|
||||
}
|
||||
return <Link to={path+location.search+'/'+text}>{text}</Link>;
|
||||
return <Link to={path+(location.search?location.search:isInner)+'/'+text}>{text}</Link>;
|
||||
}
|
||||
return text === '\\N' ? '-' : text;
|
||||
}
|
||||
export function getColumns(params: string[], isSort: boolean, isInner, hrefColumn, path) {
|
||||
if(!params||params.length === 0){return [];}
|
||||
let arr = params.map(item=> {
|
||||
let arr = params.map((item,idx)=> {
|
||||
if (isSort) {
|
||||
return {
|
||||
title: item,
|
||||
dataIndex: item,
|
||||
className: 'pr-25',
|
||||
key: item+idx,
|
||||
sorter: (a,b)=>sortItems(a, b, item),
|
||||
render:(text, record, index)=>getLinkItem(text,record, index, isInner, item, hrefColumn, path),
|
||||
};
|
||||
@ -50,6 +51,7 @@ export function getColumns(params: string[], isSort: boolean, isInner, hrefColum
|
||||
title: item,
|
||||
dataIndex: item,
|
||||
className: 'pr-25',
|
||||
key: item+idx,
|
||||
render:(text, record, index)=>getLinkItem(text, record, index, isInner, item, hrefColumn, path),
|
||||
};
|
||||
});
|
||||
|
||||
@ -91,7 +91,7 @@ function Layouts(props: any) {
|
||||
);
|
||||
return (
|
||||
<Layout>
|
||||
<Header style={{position: 'fixed', zIndex: 1, width: '100%'}}>
|
||||
<Header style={{position: 'fixed', zIndex: 99, width: '100%'}}>
|
||||
<div className={styles['logo']} onClick={()=>{history.replace('/home');setCurrent('')}}></div>
|
||||
<span className='userSet'>
|
||||
<Button style={{'color':'#000'}} type="text" size='small' onClick={changeLanguage}>{localStorage.getItem('I18N_LANGUAGE') === 'zh-CN' ? 'English' : '中文'}</Button>
|
||||
|
||||
@ -20,26 +20,68 @@
|
||||
import React,{useState,useEffect} from 'react';
|
||||
import {AdHocAPI} from 'Src/api/api';
|
||||
import {getDbName} from 'Utils/utils';
|
||||
import {Row, Empty} from 'antd';
|
||||
import {Row, Empty, notification, Table} from 'antd';
|
||||
import {FlatBtn} from 'Components/flatbtn';
|
||||
import {useTranslation} from 'react-i18next';
|
||||
export function DataPrev(props: any) {
|
||||
let { t } = useTranslation();
|
||||
const {db_name,tbl_name} = getDbName();
|
||||
const [tableData,setTableData] = useState([]);
|
||||
const [columns,setColumns] = useState([]);
|
||||
function toQuery(): void {
|
||||
if (!tbl_name){
|
||||
notification.error({message: t('selectWarning')});
|
||||
return;
|
||||
}
|
||||
AdHocAPI.doQuery({
|
||||
db_name,
|
||||
body:{stmt:`SELECT * FROM ${db_name}.${tbl_name} LIMIT 10`},
|
||||
}).then(res=>{
|
||||
if (res && res.msg === 'success') {
|
||||
setTableData(res.data);
|
||||
console.log(getColumns(res.data?.meta),2222)
|
||||
setColumns(getColumns(res.data?.meta))
|
||||
setTableData(getTabledata(res.data));
|
||||
}
|
||||
})
|
||||
.catch(()=>{
|
||||
setTableData([]);
|
||||
});
|
||||
}
|
||||
function getColumns(params: string[]) {
|
||||
console.log(params,2222)
|
||||
if(!params||params.length === 0){return [];}
|
||||
|
||||
let arr = params.map(item=> {
|
||||
return {
|
||||
title: item.name,
|
||||
dataIndex: item.name,
|
||||
key: item.name,
|
||||
width: 150,
|
||||
render:(text, record, index)=>{return text === '\\N' ? '-' : text}
|
||||
};
|
||||
});
|
||||
return arr;
|
||||
}
|
||||
function getTabledata(data){
|
||||
let meta = data.meta;
|
||||
let source = data.data;
|
||||
let res = [];
|
||||
if(!source||source.length === 0){return [];}
|
||||
let metaArr = meta.map(item=>item.name)
|
||||
for (let i=0;i<source.length;i++) {
|
||||
let node = source[i];
|
||||
if(node.length !== meta.length){
|
||||
return {}
|
||||
}
|
||||
let obj = {}
|
||||
metaArr.map((item,idx)=>{
|
||||
obj[item] = node[idx]
|
||||
})
|
||||
obj['key'] = i
|
||||
res.push(obj)
|
||||
}
|
||||
return res;
|
||||
}
|
||||
useEffect(()=>{
|
||||
toQuery();
|
||||
},[location.pathname]);
|
||||
@ -58,42 +100,16 @@ export function DataPrev(props: any) {
|
||||
{t('refresh')}
|
||||
</FlatBtn>
|
||||
</Row>
|
||||
<div
|
||||
className="ant-table ant-table-small ant-table-bordered"
|
||||
style={{marginTop: 10}}
|
||||
>
|
||||
{tableData?.meta?.length
|
||||
?<div className="ant-table-container" >
|
||||
<div className='ant-table-content'>
|
||||
<table style={{width: '100%'}}>
|
||||
<thead className="ant-table-thead">
|
||||
<tr>
|
||||
{tableData?.meta?.map(item => (
|
||||
<th className="ant-table-cell" key={item.name}>
|
||||
{item.name}
|
||||
</th>
|
||||
))}
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody className="ant-table-tbody">
|
||||
{tableData.data?.map((item,index) => (
|
||||
<tr className="ant-table-row" key={index}>
|
||||
{item.map((tdData,index) => (
|
||||
<td
|
||||
className="ant-table-cell"
|
||||
key={tdData+index}
|
||||
>
|
||||
{tdData == '\\N'?'-':tdData}
|
||||
</td>
|
||||
))}
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>:<Empty image={Empty.PRESENTED_IMAGE_SIMPLE}/>}
|
||||
</div>
|
||||
</div>
|
||||
<Table
|
||||
bordered
|
||||
columns={columns}
|
||||
style={{maxWidth:' calc(100vw - 350px)'}}
|
||||
// scroll={{ x:'500', y: '36vh'}}
|
||||
scroll={{ x: 1500, y: 300 }}
|
||||
dataSource={tableData}
|
||||
size="small"
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@ -123,7 +123,7 @@ export function AdhocContentResult(props) {
|
||||
) : (
|
||||
<TextWithIcon
|
||||
icon={<CloseCircleFilled/>}
|
||||
text={"执行失败: "+runningQueryInfo.msg +' '+ runningQueryInfo.data}
|
||||
text={`${t('executionFailed')}: `+runningQueryInfo.msg +' '+ runningQueryInfo.data}
|
||||
color="red"
|
||||
style={{
|
||||
marginBottom: 10,
|
||||
@ -141,7 +141,7 @@ export function AdhocContentResult(props) {
|
||||
</Row> */}
|
||||
<Row justify="start">
|
||||
<Col span={3}>{t('executionTime')}:</Col>
|
||||
<Col>{runningQueryInfo.data?.time + ' ms'}</Col>
|
||||
<Col>{(runningQueryInfo.data?.time?runningQueryInfo.data?.time:0) + ' ms'}</Col>
|
||||
</Row>
|
||||
{/* <Row justify="start">
|
||||
<Col span={3}>{t('endTime')}:</Col>
|
||||
|
||||
@ -86,6 +86,7 @@ export function ContentStructure(props: any) {
|
||||
bordered
|
||||
rowKey='Field'
|
||||
columns={columns}
|
||||
scroll={{ y: '36vh' }}
|
||||
loading={{
|
||||
spinning: getTableInfoRequest.loading,
|
||||
delay: TABLE_DELAY,
|
||||
|
||||
@ -23,6 +23,7 @@ under the License. */
|
||||
&-operator {
|
||||
margin-top: 10px;
|
||||
}
|
||||
width: calc(100vw - 350px);
|
||||
}
|
||||
|
||||
.adhoc-content-favorite {
|
||||
|
||||
@ -206,7 +206,7 @@ export function AdHocContent(props: any) {
|
||||
<Route
|
||||
path={`${match.path}/${AdhocContentRouteKeyEnum.Structure}/:table`}
|
||||
render={props => (
|
||||
<div style={{display:'flex',height:'300px'}}>
|
||||
<div style={{display:'flex',height:'53vh'}}>
|
||||
<div style={{flex:3}}>
|
||||
<ContentStructure
|
||||
queryTable={tableName =>
|
||||
|
||||
@ -19,5 +19,5 @@ under the License. */
|
||||
flex: 0 0 300px;
|
||||
height: 100%;
|
||||
background: #fff;
|
||||
min-height: calc(100vh - 60px);
|
||||
min-height: calc(100vh - 64px);
|
||||
}
|
||||
@ -34,7 +34,8 @@ export function PageSide(props: any) {
|
||||
<div className={styles['side']} >
|
||||
<ResizableBox
|
||||
width={sideBoxWidth}
|
||||
height={800}
|
||||
height={Infinity}
|
||||
style={{'minHeight':'calc(100vh - 64px)','overflow':'hidden'}}
|
||||
resizeHandles={['e']}
|
||||
onResizeStart={onResize}
|
||||
minConstraints={[300, 300]}
|
||||
|
||||
3
ui/src/pages/playground/tree/index.css
Normal file
3
ui/src/pages/playground/tree/index.css
Normal file
@ -0,0 +1,3 @@
|
||||
.site-tree-search-value {
|
||||
color: #f50;
|
||||
}
|
||||
@ -16,11 +16,12 @@
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
import React, {useState,useEffect} from 'react';
|
||||
import {Tree, Spin, Space} from 'antd';
|
||||
import {TableOutlined, HddOutlined} from '@ant-design/icons';
|
||||
import {Tree, Spin, Input} from 'antd';
|
||||
const { Search } = Input;
|
||||
import {TableOutlined, HddOutlined, ReloadOutlined} from '@ant-design/icons';
|
||||
import {AdHocAPI} from 'Src/api/api';
|
||||
import {useTranslation} from 'react-i18next';
|
||||
import {
|
||||
AdhocContentRouteKeyEnum,
|
||||
} from '../adhoc.data';
|
||||
@ -30,53 +31,57 @@ interface DataNode {
|
||||
isLeaf?: boolean;
|
||||
children?: DataNode[];
|
||||
}
|
||||
|
||||
import './index.css';
|
||||
const initTreeDate: DataNode[] = [];
|
||||
function updateTreeData(list: DataNode[], key: React.Key, children: DataNode[]): DataNode[] {
|
||||
function updateTreeData(list: DataNode[], key, children) {
|
||||
return list.map(node => {
|
||||
if (node.key === key) {
|
||||
return {
|
||||
...node,
|
||||
children,
|
||||
};
|
||||
} else if (node.children) {
|
||||
return {
|
||||
...node,
|
||||
children: updateTreeData(node.children, key, children),
|
||||
};
|
||||
}
|
||||
}
|
||||
return node;
|
||||
});
|
||||
}
|
||||
export function AdHocTree(props: any) {
|
||||
|
||||
let { t } = useTranslation();
|
||||
const [treeData, setTreeData] = useState(initTreeDate);
|
||||
const [realTree, setRealTree] = useState(initTreeDate);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [expandedKeys, setExpandedKeys] = useState([]);
|
||||
const [searchValue, setSearchValue] = useState('');
|
||||
const [autoExpandParent, setAutoExpandParent] = useState(true);
|
||||
useEffect(() => {
|
||||
initTreeData()
|
||||
}, []);
|
||||
function initTreeData(){
|
||||
AdHocAPI.getDatabaseList().then(res=>{
|
||||
if (res.msg === 'success' && Array.isArray(res.data)) {
|
||||
const num = Math.random()
|
||||
const treeData = res.data.map((item,index)=>{
|
||||
return {
|
||||
title: item,
|
||||
key: `1-${index}-${item}`,
|
||||
key: `${num}-1-${index}-${item}`,
|
||||
icon: <HddOutlined/>,
|
||||
};
|
||||
});
|
||||
setTreeData(treeData);
|
||||
getRealTree(treeData);
|
||||
}
|
||||
setLoading(false);
|
||||
});
|
||||
}, []);
|
||||
}
|
||||
function onLoadData({key, children}) {
|
||||
const [storey, index, db_name, tbl_name] = key.split('-');
|
||||
const [random, storey, index, db_name] = key.split('-');
|
||||
const param = {
|
||||
db_name,
|
||||
tbl_name,
|
||||
// tbl_name,
|
||||
};
|
||||
return AdHocAPI.getDatabaseList(param).then(res=>{
|
||||
if (res.msg=='success' && Array.isArray(res.data)) {
|
||||
const treeData = res.data.map((item,index)=>{
|
||||
if(storey==1){
|
||||
const children = res.data.map((item,index)=>{
|
||||
if (storey === '1'){
|
||||
return {
|
||||
title: item,
|
||||
key: `2-${index}-${param.db_name}-${item}`,
|
||||
@ -86,9 +91,9 @@ export function AdHocTree(props: any) {
|
||||
}
|
||||
|
||||
});
|
||||
setTreeData(origin =>
|
||||
updateTreeData(origin, key, treeData),
|
||||
);
|
||||
const trData = updateTreeData(treeData, key, children);
|
||||
setTreeData(trData);
|
||||
getRealTree(trData);
|
||||
}
|
||||
});
|
||||
|
||||
@ -102,14 +107,103 @@ export function AdHocTree(props: any) {
|
||||
props.history.push(`/Playground/${path}/${keys[0].split(':')[1]}`);
|
||||
}
|
||||
}
|
||||
function onSearch(e){
|
||||
const { value } = e.target;
|
||||
const expandedKeys = treeData
|
||||
.map((item, index) => {
|
||||
if (getParentKey(value, treeData[index].children, index)) {
|
||||
return item.key
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
})
|
||||
setExpandedKeys(expandedKeys);
|
||||
setSearchValue(value);
|
||||
setAutoExpandParent(true);
|
||||
getRealTree(treeData, value);
|
||||
};
|
||||
function onExpand(expandedKeys) {
|
||||
setExpandedKeys(expandedKeys);
|
||||
setAutoExpandParent(false);
|
||||
};
|
||||
const getParentKey = (key, tree, idx) => {
|
||||
if (!tree) {
|
||||
return false;
|
||||
}
|
||||
for (let i = 0; i < tree.length; i++) {
|
||||
const node = tree[i];
|
||||
if (node.title.includes(key)) {
|
||||
return true
|
||||
} else {
|
||||
treeData[idx].children ? treeData[idx].children[i].title = node.title : ''
|
||||
}
|
||||
}
|
||||
return false;
|
||||
};
|
||||
function getRealTree(treeData, value){
|
||||
const realTree = inner(treeData);
|
||||
function inner(treeData){
|
||||
return treeData.map(item => {
|
||||
const search = value || '';
|
||||
const index = item.title.indexOf(search);
|
||||
const beforeStr = item.title.substr(0, index);
|
||||
const afterStr = item.title.substr(index + search.length);
|
||||
const title =
|
||||
index > -1 ? (
|
||||
<span>
|
||||
{beforeStr}
|
||||
<span className="site-tree-search-value">{search}</span>
|
||||
{afterStr}
|
||||
</span>
|
||||
) : (
|
||||
item.title
|
||||
);
|
||||
if (item.children) {
|
||||
return {...item, title, children: inner(item.children)};
|
||||
}
|
||||
return {
|
||||
...item,
|
||||
title
|
||||
};
|
||||
});
|
||||
}
|
||||
debounce(setRealTree(realTree),300);
|
||||
}
|
||||
function debounce(fn, wait) {
|
||||
var timer = null;
|
||||
return function () {
|
||||
var context = this
|
||||
var args = arguments
|
||||
if (timer) {
|
||||
clearTimeout(timer);
|
||||
timer = null;
|
||||
}
|
||||
timer = setTimeout(function () {
|
||||
fn.apply(context, args)
|
||||
}, wait)
|
||||
}
|
||||
}
|
||||
return (
|
||||
<>
|
||||
<Spin spinning={loading} size="small"/>
|
||||
<div>
|
||||
<Search
|
||||
size="small"
|
||||
style={{ padding: 5, position: 'fixed', zIndex: '99', width: '300px'}}
|
||||
placeholder={t('search')}
|
||||
enterButton={<ReloadOutlined />}
|
||||
onSearch={initTreeData}
|
||||
onChange={onSearch} />
|
||||
</div>
|
||||
|
||||
<Tree
|
||||
showIcon={true}
|
||||
loadData={onLoadData}
|
||||
treeData={treeData}
|
||||
style={{'width':'100%', height:'100%' ,paddingTop:'15px'}}
|
||||
treeData={realTree}
|
||||
onExpand={onExpand}
|
||||
expandedKeys={expandedKeys}
|
||||
autoExpandParent={autoExpandParent}
|
||||
style={{'width':'100%', height:'86vh' ,paddingTop:'35px',overflowY:'scroll'}}
|
||||
onSelect={(selectedKeys, info) =>
|
||||
handleTreeSelect(
|
||||
selectedKeys,
|
||||
|
||||
@ -86,7 +86,7 @@ export default function QueryProfile(params: any) {
|
||||
:<Table
|
||||
isSort={true}
|
||||
isFilter={true}
|
||||
isInner={true}
|
||||
isInner={'/QueryProfile'}
|
||||
allTableData={allTableData}
|
||||
/>
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user