Skip to content

Commit 36af30f

Browse files
committed
More data sources
1 parent b6725bf commit 36af30f

File tree

12 files changed

+970
-68
lines changed

12 files changed

+970
-68
lines changed

exec/java-exec/src/main/java/org/apache/drill/exec/server/rest/DashboardResources.java

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -982,9 +982,13 @@ public Response getImage(
982982
break;
983983
}
984984

985+
// SVG files can contain embedded scripts; serve them as attachments
986+
// to prevent inline rendering and potential XSS.
987+
String disposition = "svg".equals(ext) ? "attachment" : "inline";
988+
985989
return Response.ok(imageFile, contentType)
986990
.header("Cache-Control", "public, max-age=86400")
987-
.header("Content-Disposition", "inline")
991+
.header("Content-Disposition", disposition)
988992
.header("Content-Security-Policy", "default-src 'none'; style-src 'unsafe-inline'")
989993
.header("X-Content-Type-Options", "nosniff")
990994
.build();
Lines changed: 136 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,136 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one
3+
* or more contributor license agreements. See the NOTICE file
4+
* distributed with this work for additional information
5+
* regarding copyright ownership. The ASF licenses this file
6+
* to you under the Apache License, Version 2.0 (the
7+
* "License"); you may not use this file except in compliance
8+
* with the License. You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing, software
13+
* distributed under the License is distributed on an "AS IS" BASIS,
14+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15+
* See the License for the specific language governing permissions and
16+
* limitations under the License.
17+
*/
18+
import { useState, useEffect, useCallback } from 'react';
19+
import { Form, Input, InputNumber, Select, Tooltip, Typography } from 'antd';
20+
import { QuestionCircleOutlined } from '@ant-design/icons';
21+
22+
const { Text } = Typography;
23+
24+
const helpIcon = { color: '#999', cursor: 'help' as const };
25+
26+
function label(text: string, tip: string) {
27+
return (
28+
<span>
29+
{text}{' '}
30+
<Tooltip title={tip}>
31+
<QuestionCircleOutlined style={helpIcon} />
32+
</Tooltip>
33+
</span>
34+
);
35+
}
36+
37+
interface CassandraFormProps {
38+
config: Record<string, unknown>;
39+
onChange: (config: Record<string, unknown>) => void;
40+
}
41+
42+
export default function CassandraForm({ config, onChange }: CassandraFormProps) {
43+
const [host, setHost] = useState<string>((config.host as string) || 'localhost');
44+
const [port, setPort] = useState<number>((config.port as number) || 9042);
45+
const [username, setUsername] = useState<string>((config.username as string) || '');
46+
const [password, setPassword] = useState<string>((config.password as string) || '');
47+
const [authMode, setAuthMode] = useState<string>((config.authMode as string) || 'SHARED_USER');
48+
49+
useEffect(() => {
50+
setHost((config.host as string) || 'localhost');
51+
setPort((config.port as number) || 9042);
52+
setUsername((config.username as string) || '');
53+
setPassword((config.password as string) || '');
54+
setAuthMode((config.authMode as string) || 'SHARED_USER');
55+
}, [config]);
56+
57+
const emitChange = useCallback(
58+
(updates: Partial<Record<string, unknown>>) => {
59+
onChange({ ...config, ...updates });
60+
},
61+
[config, onChange]
62+
);
63+
64+
return (
65+
<Form layout="vertical">
66+
<Text strong style={{ display: 'block', marginBottom: 12 }}>
67+
Connection
68+
</Text>
69+
70+
<Form.Item label={label('Host', 'The hostname or IP address of the Cassandra / ScyllaDB node to connect to.')}>
71+
<Input
72+
value={host}
73+
onChange={(e) => {
74+
setHost(e.target.value);
75+
emitChange({ host: e.target.value });
76+
}}
77+
placeholder="localhost"
78+
/>
79+
</Form.Item>
80+
81+
<Form.Item label={label('Port', 'The CQL native transport port. The default for Cassandra and ScyllaDB is 9042.')}>
82+
<InputNumber
83+
value={port}
84+
onChange={(val) => {
85+
const v = val || 9042;
86+
setPort(v);
87+
emitChange({ port: v });
88+
}}
89+
min={1}
90+
max={65535}
91+
style={{ width: 200 }}
92+
/>
93+
</Form.Item>
94+
95+
<Text strong style={{ display: 'block', marginTop: 16, marginBottom: 12 }}>
96+
Authentication
97+
</Text>
98+
99+
<Form.Item label={label('Auth Mode', 'Shared User \u2014 All queries use the credentials configured here. User Translation \u2014 Each user provides their own Cassandra credentials.')}>
100+
<Select
101+
value={authMode}
102+
onChange={(val) => {
103+
setAuthMode(val);
104+
emitChange({ authMode: val });
105+
}}
106+
style={{ width: 300 }}
107+
options={[
108+
{ value: 'SHARED_USER', label: 'Shared User' },
109+
{ value: 'USER_TRANSLATION', label: 'User Translation' },
110+
]}
111+
/>
112+
</Form.Item>
113+
114+
<Form.Item label={label('Username', 'Cassandra / ScyllaDB username for authentication. Leave empty if authentication is not enabled on the cluster.')}>
115+
<Input
116+
value={username}
117+
onChange={(e) => {
118+
setUsername(e.target.value);
119+
emitChange({ username: e.target.value || undefined });
120+
}}
121+
placeholder="cassandra"
122+
/>
123+
</Form.Item>
124+
125+
<Form.Item label={label('Password', 'Cassandra / ScyllaDB password for authentication.')}>
126+
<Input.Password
127+
value={password}
128+
onChange={(e) => {
129+
setPassword(e.target.value);
130+
emitChange({ password: e.target.value || undefined });
131+
}}
132+
/>
133+
</Form.Item>
134+
</Form>
135+
);
136+
}
Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one
3+
* or more contributor license agreements. See the NOTICE file
4+
* distributed with this work for additional information
5+
* regarding copyright ownership. The ASF licenses this file
6+
* to you under the Apache License, Version 2.0 (the
7+
* "License"); you may not use this file except in compliance
8+
* with the License. You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing, software
13+
* distributed under the License is distributed on an "AS IS" BASIS,
14+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15+
* See the License for the specific language governing permissions and
16+
* limitations under the License.
17+
*/
18+
import { useState, useEffect, useCallback } from 'react';
19+
import { Form, Input, InputNumber, Collapse, Tooltip, Typography } from 'antd';
20+
import { QuestionCircleOutlined } from '@ant-design/icons';
21+
22+
const { Text } = Typography;
23+
24+
const helpIcon = { color: '#999', cursor: 'help' as const };
25+
26+
function label(text: string, tip: string) {
27+
return (
28+
<span>
29+
{text}{' '}
30+
<Tooltip title={tip}>
31+
<QuestionCircleOutlined style={helpIcon} />
32+
</Tooltip>
33+
</span>
34+
);
35+
}
36+
37+
interface DruidFormProps {
38+
config: Record<string, unknown>;
39+
onChange: (config: Record<string, unknown>) => void;
40+
}
41+
42+
export default function DruidForm({ config, onChange }: DruidFormProps) {
43+
const [brokerAddress, setBrokerAddress] = useState<string>(
44+
(config.brokerAddress as string) || 'http://localhost:8082'
45+
);
46+
const [coordinatorAddress, setCoordinatorAddress] = useState<string>(
47+
(config.coordinatorAddress as string) || 'http://localhost:8081'
48+
);
49+
const [averageRowSizeBytes, setAverageRowSizeBytes] = useState<number>(
50+
(config.averageRowSizeBytes as number) || 100
51+
);
52+
53+
useEffect(() => {
54+
setBrokerAddress((config.brokerAddress as string) || 'http://localhost:8082');
55+
setCoordinatorAddress((config.coordinatorAddress as string) || 'http://localhost:8081');
56+
setAverageRowSizeBytes((config.averageRowSizeBytes as number) || 100);
57+
}, [config]);
58+
59+
const emitChange = useCallback(
60+
(updates: Partial<Record<string, unknown>>) => {
61+
onChange({ ...config, ...updates });
62+
},
63+
[config, onChange]
64+
);
65+
66+
return (
67+
<Form layout="vertical">
68+
<Text strong style={{ display: 'block', marginBottom: 12 }}>
69+
Connection
70+
</Text>
71+
72+
<Form.Item label={label('Broker Address', 'The URL of the Druid Broker node. The Broker handles queries from external clients.')}>
73+
<Input
74+
value={brokerAddress}
75+
onChange={(e) => {
76+
setBrokerAddress(e.target.value);
77+
emitChange({ brokerAddress: e.target.value });
78+
}}
79+
placeholder="http://localhost:8082"
80+
/>
81+
</Form.Item>
82+
83+
<Form.Item label={label('Coordinator Address', 'The URL of the Druid Coordinator node. The Coordinator manages data availability and segment loading.')}>
84+
<Input
85+
value={coordinatorAddress}
86+
onChange={(e) => {
87+
setCoordinatorAddress(e.target.value);
88+
emitChange({ coordinatorAddress: e.target.value });
89+
}}
90+
placeholder="http://localhost:8081"
91+
/>
92+
</Form.Item>
93+
94+
<Collapse
95+
ghost
96+
style={{ marginTop: 16 }}
97+
items={[
98+
{
99+
key: 'advanced',
100+
label: <Text strong>Advanced Options</Text>,
101+
children: (
102+
<Form.Item label={label('Average Row Size (bytes)', 'Estimated average row size in bytes, used by the query planner to calculate scan batch sizes. Default is 100.')}>
103+
<InputNumber
104+
value={averageRowSizeBytes}
105+
onChange={(val) => {
106+
const v = val || 100;
107+
setAverageRowSizeBytes(v);
108+
emitChange({ averageRowSizeBytes: v });
109+
}}
110+
min={1}
111+
style={{ width: 200 }}
112+
/>
113+
</Form.Item>
114+
),
115+
},
116+
]}
117+
/>
118+
</Form>
119+
);
120+
}

0 commit comments

Comments
 (0)