finish box connector
This commit is contained in:
parent
d263989317
commit
34fc06c283
7 changed files with 350 additions and 56 deletions
|
|
@ -380,7 +380,6 @@ async def poll_google_web_result():
|
|||
|
||||
@manager.route("/box/oauth/web/start", methods=["POST"]) # noqa: F821
|
||||
@login_required
|
||||
@validate_request("")
|
||||
async def start_box_web_oauth():
|
||||
req = await get_request_json()
|
||||
|
||||
|
|
@ -394,7 +393,7 @@ async def start_box_web_oauth():
|
|||
flow_id = str(uuid.uuid4())
|
||||
|
||||
box_auth = BoxOAuth(
|
||||
oauth_config=OAuthConfig(
|
||||
OAuthConfig(
|
||||
client_id=client_id,
|
||||
client_secret=client_secret,
|
||||
)
|
||||
|
|
@ -411,7 +410,7 @@ async def start_box_web_oauth():
|
|||
"user_id": current_user.id,
|
||||
"auth_url": auth_url,
|
||||
"client_id": client_id,
|
||||
"clientsecret": client_secret,
|
||||
"client_secret": client_secret,
|
||||
"created_at": int(time.time()),
|
||||
}
|
||||
REDIS_CONN.set_obj(_web_state_cache_key(flow_id, "box"), cache_payload, WEB_FLOW_TTL_SECS)
|
||||
|
|
@ -425,11 +424,14 @@ async def start_box_web_oauth():
|
|||
@manager.route("/box/oauth/web/callback", methods=["GET"]) # noqa: F821
|
||||
async def box_web_oauth_callback():
|
||||
flow_id = request.args.get("state")
|
||||
|
||||
if not code or not flow_id:
|
||||
if not flow_id:
|
||||
return await _render_web_oauth_popup("", False, "Missing OAuth parameters.", "box")
|
||||
|
||||
cache_payload = REDIS_CONN.get_obj(_web_state_cache_key(flow_id, "box"))
|
||||
code = request.args.get("code")
|
||||
if not code:
|
||||
return await _render_web_oauth_popup(flow_id, False, "Missing authorization code from Box.", "box")
|
||||
|
||||
cache_payload = json.loads(REDIS_CONN.get(_web_state_cache_key(flow_id, "box")))
|
||||
if not cache_payload:
|
||||
return get_json_result(code=RetCode.ARGUMENT_ERROR, message="Box OAuth session expired or invalid.")
|
||||
|
||||
|
|
@ -439,15 +441,21 @@ async def box_web_oauth_callback():
|
|||
REDIS_CONN.delete(_web_state_cache_key(flow_id, "box"))
|
||||
return await _render_web_oauth_popup(flow_id, False, error_description or "Authorization failed.", "box")
|
||||
|
||||
code = request.args.get("code")
|
||||
if not code:
|
||||
return await _render_web_oauth_popup(flow_id, False, "Missing authorization code from Box.", "box")
|
||||
|
||||
auth = BoxOAuth(
|
||||
OAuthConfig(
|
||||
client_id=cache_payload.get("client_id"),
|
||||
client_secret=cache_payload.get("client_secret"),
|
||||
)
|
||||
)
|
||||
|
||||
auth.get_tokens_authorization_code_grant(code)
|
||||
token = auth.retrieve_token()
|
||||
result_payload = {
|
||||
"user_id": cache_payload.get("user_id"),
|
||||
"client_id": cache_payload.get("client_id"),
|
||||
"client_secret": cache_payload.get("clientsecret"),
|
||||
"code": code,
|
||||
"client_secret": cache_payload.get("client_secret"),
|
||||
"access_token": token.access_token,
|
||||
"refresh_token": token.refresh_token,
|
||||
}
|
||||
|
||||
REDIS_CONN.set_obj(_web_result_cache_key(flow_id, "box"), result_payload, WEB_FLOW_TTL_SECS)
|
||||
|
|
@ -462,12 +470,14 @@ async def poll_box_web_result():
|
|||
req = await get_request_json()
|
||||
flow_id = req.get("flow_id")
|
||||
|
||||
cache_raw = REDIS_CONN.get_obj(_web_result_cache_key(flow_id, "box"))
|
||||
if not cache_raw:
|
||||
cache_blob = REDIS_CONN.get(_web_result_cache_key(flow_id, "box"))
|
||||
if not cache_blob:
|
||||
return get_json_result(code=RetCode.RUNNING, message="Authorization is still pending.")
|
||||
|
||||
|
||||
cache_raw = json.loads(cache_blob)
|
||||
if cache_raw.get("user_id") != current_user.id:
|
||||
return get_json_result(code=RetCode.PERMISSION_ERROR, message="You are not allowed to access this authorization result.")
|
||||
|
||||
REDIS_CONN.delete(_web_result_cache_key(flow_id, "box"))
|
||||
|
||||
return get_json_result(data={"credentials": cache_raw})
|
||||
|
|
@ -15,9 +15,9 @@ from common.data_source.models import Document, GenerateDocumentsOutput
|
|||
from common.data_source.utils import get_file_ext
|
||||
|
||||
class BoxConnector(LoadConnector, PollConnector):
|
||||
def __init__(self, folder_id: str = "0", batch_size: int = INDEX_BATCH_SIZE, use_marker: bool = False) -> None:
|
||||
def __init__(self, folder_id: str, batch_size: int = INDEX_BATCH_SIZE, use_marker: bool = False) -> None:
|
||||
self.batch_size = batch_size
|
||||
self.folder_id = folder_id
|
||||
self.folder_id = "0" if not folder_id else folder_id
|
||||
self.use_marker = use_marker
|
||||
|
||||
def load_credentials(self, auth: Any):
|
||||
|
|
@ -49,7 +49,7 @@ class BoxConnector(LoadConnector, PollConnector):
|
|||
result = self.box_client.folders.get_folder_items(
|
||||
folder_id=folder_id,
|
||||
limit=self.batch_size,
|
||||
usemarker=self.usemarker
|
||||
usemarker=self.use_marker
|
||||
)
|
||||
|
||||
while True:
|
||||
|
|
|
|||
|
|
@ -31,6 +31,7 @@ import traceback
|
|||
from datetime import datetime, timezone
|
||||
from typing import Any
|
||||
|
||||
from flask import json
|
||||
import trio
|
||||
|
||||
from api.db.services.connector_service import ConnectorService, SyncLogsService
|
||||
|
|
@ -48,7 +49,7 @@ from common.data_source.utils import load_all_docs_from_checkpoint_connector
|
|||
from common.log_utils import init_root_logger
|
||||
from common.signal_utils import start_tracemalloc_and_snapshot, stop_tracemalloc
|
||||
from common.versions import get_ragflow_version
|
||||
from box_sdk_gen import BoxClient, BoxOAuth, OAuthConfig, GetAuthorizeUrlOptions
|
||||
from box_sdk_gen import BoxOAuth, OAuthConfig, AccessToken
|
||||
|
||||
MAX_CONCURRENT_TASKS = int(os.environ.get("MAX_CONCURRENT_TASKS", "5"))
|
||||
task_limiter = trio.Semaphore(MAX_CONCURRENT_TASKS)
|
||||
|
|
@ -611,14 +612,20 @@ class BOX(SyncBase):
|
|||
use_marker=self.conf.get("use_marker", False)
|
||||
)
|
||||
|
||||
credential = self.conf['credentials']
|
||||
credential = json.loads(self.conf['credentials']['box_tokens'])
|
||||
|
||||
auth = BoxOAuth(
|
||||
OAuthConfig(
|
||||
client_id=credential['client_id'],
|
||||
client_secret=credential['client_secret'],
|
||||
)
|
||||
)
|
||||
auth.get_tokens_authorization_code_grant(credential['code'])
|
||||
|
||||
token = AccessToken(
|
||||
access_token=credential['access_token'],
|
||||
refresh_token=credential['refresh_token'],
|
||||
)
|
||||
auth.token_storage.store(token)
|
||||
|
||||
self.connector.load_credentials(auth)
|
||||
if task["reindex"] == "1" or not task["poll_range_start"]:
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { useCallback, useEffect, useMemo, useState } from 'react';
|
||||
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
||||
|
||||
import { Button } from '@/components/ui/button';
|
||||
import {
|
||||
|
|
@ -11,11 +11,14 @@ import {
|
|||
} from '@/components/ui/dialog';
|
||||
import { Input } from '@/components/ui/input';
|
||||
import message from '@/components/ui/message';
|
||||
import {
|
||||
pollBoxWebAuthResult,
|
||||
startBoxWebAuth,
|
||||
} from '@/services/data-source-service';
|
||||
import { Loader2 } from 'lucide-react';
|
||||
|
||||
export type BoxTokenFieldProps = {
|
||||
/** 存储在表单里的值,约定为 JSON 字符串 */
|
||||
value?: string;
|
||||
/** 表单回写,用 JSON 字符串承载 client_id / client_secret / redirect_uri */
|
||||
onChange: (value: any) => void;
|
||||
placeholder?: string;
|
||||
};
|
||||
|
|
@ -24,8 +27,13 @@ type BoxCredentials = {
|
|||
client_id?: string;
|
||||
client_secret?: string;
|
||||
redirect_uri?: string;
|
||||
authorization_code?: string;
|
||||
access_token?: string;
|
||||
refresh_token?: string;
|
||||
};
|
||||
|
||||
type BoxAuthStatus = 'idle' | 'waiting' | 'success' | 'error';
|
||||
|
||||
const parseBoxCredentials = (content?: string): BoxCredentials | null => {
|
||||
if (!content) return null;
|
||||
try {
|
||||
|
|
@ -34,25 +42,30 @@ const parseBoxCredentials = (content?: string): BoxCredentials | null => {
|
|||
client_id: parsed.client_id,
|
||||
client_secret: parsed.client_secret,
|
||||
redirect_uri: parsed.redirect_uri,
|
||||
authorization_code: parsed.authorization_code ?? parsed.code,
|
||||
access_token: parsed.access_token,
|
||||
refresh_token: parsed.refresh_token,
|
||||
};
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
const BoxTokenField = ({
|
||||
value,
|
||||
onChange,
|
||||
placeholder,
|
||||
}: BoxTokenFieldProps) => {
|
||||
const BoxTokenField = ({ value, onChange }: BoxTokenFieldProps) => {
|
||||
const [dialogOpen, setDialogOpen] = useState(false);
|
||||
const [clientId, setClientId] = useState('');
|
||||
const [clientSecret, setClientSecret] = useState('');
|
||||
const [redirectUri, setRedirectUri] = useState('');
|
||||
const [submitLoading, setSubmitLoading] = useState(false);
|
||||
const [webFlowId, setWebFlowId] = useState<string | null>(null);
|
||||
const webFlowIdRef = useRef<string | null>(null);
|
||||
const webPollTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null);
|
||||
const [webStatus, setWebStatus] = useState<BoxAuthStatus>('idle');
|
||||
const [webStatusMessage, setWebStatusMessage] = useState('');
|
||||
|
||||
const parsed = useMemo(() => parseBoxCredentials(value), [value]);
|
||||
const parsedRedirectUri = useMemo(() => parsed?.redirect_uri ?? '', [parsed]);
|
||||
|
||||
// 当外部 value 变化且弹窗关闭时,同步初始值
|
||||
useEffect(() => {
|
||||
if (!dialogOpen) {
|
||||
setClientId(parsed?.client_id ?? '');
|
||||
|
|
@ -61,6 +74,18 @@ const BoxTokenField = ({
|
|||
}
|
||||
}, [parsed, dialogOpen]);
|
||||
|
||||
useEffect(() => {
|
||||
webFlowIdRef.current = webFlowId;
|
||||
}, [webFlowId]);
|
||||
|
||||
useEffect(() => {
|
||||
return () => {
|
||||
if (webPollTimerRef.current) {
|
||||
clearTimeout(webPollTimerRef.current);
|
||||
}
|
||||
};
|
||||
}, []);
|
||||
|
||||
const hasConfigured = useMemo(
|
||||
() =>
|
||||
Boolean(
|
||||
|
|
@ -69,15 +94,161 @@ const BoxTokenField = ({
|
|||
[parsed],
|
||||
);
|
||||
|
||||
const handleOpenDialog = useCallback(() => {
|
||||
setDialogOpen(true);
|
||||
const hasAuthorized = useMemo(
|
||||
() =>
|
||||
Boolean(
|
||||
parsed?.access_token ||
|
||||
parsed?.refresh_token ||
|
||||
parsed?.authorization_code,
|
||||
),
|
||||
[parsed],
|
||||
);
|
||||
|
||||
const resetWebStatus = useCallback(() => {
|
||||
setWebStatus('idle');
|
||||
setWebStatusMessage('');
|
||||
}, []);
|
||||
|
||||
const clearWebState = useCallback(() => {
|
||||
if (webPollTimerRef.current) {
|
||||
clearTimeout(webPollTimerRef.current);
|
||||
webPollTimerRef.current = null;
|
||||
}
|
||||
webFlowIdRef.current = null;
|
||||
setWebFlowId(null);
|
||||
}, []);
|
||||
|
||||
const fetchWebResult = useCallback(
|
||||
async (flowId: string) => {
|
||||
try {
|
||||
const { data } = await pollBoxWebAuthResult({ flow_id: flowId });
|
||||
if (data.code === 0 && data.data?.credentials) {
|
||||
const credentials = (data.data.credentials || {}) as Record<
|
||||
string,
|
||||
any
|
||||
>;
|
||||
const { user_id: _userId, code, ...rest } = credentials;
|
||||
|
||||
const finalValue: Record<string, any> = {
|
||||
...rest,
|
||||
// 确保客户端配置字段有值(优先后端返回,其次当前输入)
|
||||
client_id: rest.client_id ?? clientId.trim(),
|
||||
client_secret: rest.client_secret ?? clientSecret.trim(),
|
||||
};
|
||||
|
||||
const redirect =
|
||||
redirectUri.trim() || parsedRedirectUri || rest.redirect_uri;
|
||||
if (redirect) {
|
||||
finalValue.redirect_uri = redirect;
|
||||
}
|
||||
|
||||
if (code) {
|
||||
finalValue.authorization_code = code;
|
||||
}
|
||||
|
||||
// access_token / refresh_token 由后端返回,已在 ...rest 中带上,无需额外 state
|
||||
|
||||
onChange(JSON.stringify(finalValue));
|
||||
message.success('Box authorization completed.');
|
||||
clearWebState();
|
||||
resetWebStatus();
|
||||
setDialogOpen(false);
|
||||
return;
|
||||
}
|
||||
|
||||
if (data.code === 106) {
|
||||
setWebStatus('waiting');
|
||||
setWebStatusMessage(
|
||||
'Authorization confirmed. Finalizing credentials...',
|
||||
);
|
||||
if (webPollTimerRef.current) {
|
||||
clearTimeout(webPollTimerRef.current);
|
||||
}
|
||||
webPollTimerRef.current = setTimeout(
|
||||
() => fetchWebResult(flowId),
|
||||
1500,
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
const errorMessage = data.message || 'Authorization failed.';
|
||||
message.error(errorMessage);
|
||||
setWebStatus('error');
|
||||
setWebStatusMessage(errorMessage);
|
||||
clearWebState();
|
||||
} catch (_error) {
|
||||
message.error('Unable to retrieve authorization result.');
|
||||
setWebStatus('error');
|
||||
setWebStatusMessage('Unable to retrieve authorization result.');
|
||||
clearWebState();
|
||||
}
|
||||
},
|
||||
[
|
||||
clearWebState,
|
||||
clientId,
|
||||
clientSecret,
|
||||
parsedRedirectUri,
|
||||
redirectUri,
|
||||
resetWebStatus,
|
||||
onChange,
|
||||
],
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
const handler = (event: MessageEvent) => {
|
||||
const payload = event.data;
|
||||
if (!payload || payload.type !== 'ragflow-box-oauth') {
|
||||
return;
|
||||
}
|
||||
|
||||
const targetFlowId = payload.flowId || webFlowIdRef.current;
|
||||
if (!targetFlowId) return;
|
||||
if (webFlowIdRef.current && webFlowIdRef.current !== targetFlowId) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (payload.status === 'success') {
|
||||
setWebStatus('waiting');
|
||||
setWebStatusMessage(
|
||||
'Authorization confirmed. Finalizing credentials...',
|
||||
);
|
||||
fetchWebResult(targetFlowId);
|
||||
} else {
|
||||
const errorMessage = payload.message || 'Authorization failed.';
|
||||
message.error(errorMessage);
|
||||
setWebStatus('error');
|
||||
setWebStatusMessage(errorMessage);
|
||||
clearWebState();
|
||||
}
|
||||
};
|
||||
|
||||
window.addEventListener('message', handler);
|
||||
return () => window.removeEventListener('message', handler);
|
||||
}, [clearWebState, fetchWebResult]);
|
||||
|
||||
const handleOpenDialog = useCallback(() => {
|
||||
resetWebStatus();
|
||||
clearWebState();
|
||||
setDialogOpen(true);
|
||||
}, [clearWebState, resetWebStatus]);
|
||||
|
||||
const handleCloseDialog = useCallback(() => {
|
||||
setDialogOpen(false);
|
||||
}, []);
|
||||
clearWebState();
|
||||
resetWebStatus();
|
||||
}, [clearWebState, resetWebStatus]);
|
||||
|
||||
const handleSubmit = useCallback(() => {
|
||||
const handleManualWebCheck = useCallback(() => {
|
||||
if (!webFlowId) {
|
||||
message.info('Start browser authorization first.');
|
||||
return;
|
||||
}
|
||||
setWebStatus('waiting');
|
||||
setWebStatusMessage('Checking authorization status...');
|
||||
fetchWebResult(webFlowId);
|
||||
}, [fetchWebResult, webFlowId]);
|
||||
|
||||
const handleSubmit = useCallback(async () => {
|
||||
if (!clientId.trim() || !clientSecret.trim() || !redirectUri.trim()) {
|
||||
message.error(
|
||||
'Please fill in Client ID, Client Secret, and Redirect URI.',
|
||||
|
|
@ -85,31 +256,91 @@ const BoxTokenField = ({
|
|||
return;
|
||||
}
|
||||
|
||||
const payload: BoxCredentials = {
|
||||
client_id: clientId.trim(),
|
||||
client_secret: clientSecret.trim(),
|
||||
redirect_uri: redirectUri.trim(),
|
||||
const trimmedClientId = clientId.trim();
|
||||
const trimmedClientSecret = clientSecret.trim();
|
||||
const trimmedRedirectUri = redirectUri.trim();
|
||||
|
||||
const payloadForStorage: BoxCredentials = {
|
||||
client_id: trimmedClientId,
|
||||
client_secret: trimmedClientSecret,
|
||||
redirect_uri: trimmedRedirectUri,
|
||||
};
|
||||
|
||||
setSubmitLoading(true);
|
||||
resetWebStatus();
|
||||
clearWebState();
|
||||
|
||||
try {
|
||||
onChange(JSON.stringify(payload));
|
||||
message.success('Box credentials saved locally.');
|
||||
setDialogOpen(false);
|
||||
} catch {
|
||||
message.error('Failed to save Box credentials.');
|
||||
const { data } = await startBoxWebAuth({
|
||||
client_id: trimmedClientId,
|
||||
client_secret: trimmedClientSecret,
|
||||
redirect_uri: trimmedRedirectUri,
|
||||
});
|
||||
|
||||
if (data.code === 0 && data.data?.authorization_url) {
|
||||
onChange(JSON.stringify(payloadForStorage));
|
||||
|
||||
const popup = window.open(
|
||||
data.data.authorization_url,
|
||||
'ragflow-box-oauth',
|
||||
'width=600,height=720',
|
||||
);
|
||||
if (!popup) {
|
||||
message.error(
|
||||
'Popup was blocked. Please allow popups for this site.',
|
||||
);
|
||||
clearWebState();
|
||||
return;
|
||||
}
|
||||
popup.focus();
|
||||
|
||||
const flowId = data.data.flow_id;
|
||||
setWebFlowId(flowId);
|
||||
webFlowIdRef.current = flowId;
|
||||
setWebStatus('waiting');
|
||||
setWebStatusMessage(
|
||||
'Complete the Box consent in the opened window and return here.',
|
||||
);
|
||||
message.info(
|
||||
'Authorization window opened. Complete the Box consent to continue.',
|
||||
);
|
||||
} else {
|
||||
message.error(data.message || 'Failed to start Box authorization.');
|
||||
}
|
||||
} catch (_error) {
|
||||
message.error('Failed to start Box authorization.');
|
||||
} finally {
|
||||
setSubmitLoading(false);
|
||||
}
|
||||
}, [clientId, clientSecret, redirectUri, onChange]);
|
||||
}, [
|
||||
clearWebState,
|
||||
clientId,
|
||||
clientSecret,
|
||||
redirectUri,
|
||||
resetWebStatus,
|
||||
onChange,
|
||||
]);
|
||||
|
||||
return (
|
||||
<div className="flex flex-col gap-3">
|
||||
{hasConfigured && (
|
||||
<div className="flex flex-wrap items-center gap-2 rounded-md border border-dashed border-muted-foreground/40 bg-muted/20 px-3 py-2 text-xs text-muted-foreground">
|
||||
<span className="rounded-full bg-emerald-100 px-2 py-0.5 text-[11px] font-semibold uppercase tracking-wide text-emerald-700">
|
||||
Configured
|
||||
</span>
|
||||
{(hasConfigured || hasAuthorized) && (
|
||||
<div className="flex flex-wrap items-center gap-3 rounded-md border border-dashed border-muted-foreground/40 bg-muted/20 px-3 py-2 text-xs text-muted-foreground">
|
||||
<div className="flex flex-wrap items-center gap-2">
|
||||
{hasAuthorized ? (
|
||||
<span className="rounded-full bg-emerald-100 px-2 py-0.5 text-[11px] font-semibold uppercase tracking-wide text-emerald-700">
|
||||
Authorized
|
||||
</span>
|
||||
) : null}
|
||||
{hasConfigured ? (
|
||||
<span className="rounded-full bg-blue-100 px-2 py-0.5 text-[11px] font-semibold uppercase tracking-wide text-blue-700">
|
||||
Configured
|
||||
</span>
|
||||
) : null}
|
||||
</div>
|
||||
<p className="m-0">
|
||||
Box OAuth credentials have been configured. You can update them at
|
||||
any time.
|
||||
{hasAuthorized
|
||||
? 'Box OAuth credentials are authorized and ready to use.'
|
||||
: 'Box OAuth client information has been stored. Run the browser authorization to finalize the setup.'}
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
|
|
@ -143,7 +374,7 @@ const BoxTokenField = ({
|
|||
<label className="text-sm font-medium">Client ID</label>
|
||||
<Input
|
||||
value={clientId}
|
||||
placeholder={placeholder || 'Enter Box Client ID'}
|
||||
placeholder="Enter Box Client ID"
|
||||
onChange={(e) => setClientId(e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
|
|
@ -164,13 +395,49 @@ const BoxTokenField = ({
|
|||
onChange={(e) => setRedirectUri(e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
{webStatus !== 'idle' && (
|
||||
<div className="rounded-md border border-dashed border-muted-foreground/40 bg-muted/10 px-4 py-4 text-sm text-muted-foreground">
|
||||
<div className="text-sm font-semibold text-foreground">
|
||||
Browser authorization
|
||||
</div>
|
||||
<p
|
||||
className={`mt-2 text-xs ${
|
||||
webStatus === 'error'
|
||||
? 'text-destructive'
|
||||
: 'text-muted-foreground'
|
||||
}`}
|
||||
>
|
||||
{webStatusMessage}
|
||||
</p>
|
||||
{webStatus === 'waiting' && webFlowId ? (
|
||||
<div className="mt-3 flex flex-wrap gap-2">
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onClick={handleManualWebCheck}
|
||||
>
|
||||
Refresh status
|
||||
</Button>
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<DialogFooter className="pt-3">
|
||||
<Button variant="ghost" onClick={handleCloseDialog}>
|
||||
<Button
|
||||
variant="ghost"
|
||||
onClick={handleCloseDialog}
|
||||
disabled={submitLoading}
|
||||
>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button onClick={handleSubmit}>Submit</Button>
|
||||
<Button onClick={handleSubmit} disabled={submitLoading}>
|
||||
{submitLoading && (
|
||||
<Loader2 className="mr-2 size-4 animate-spin" />
|
||||
)}
|
||||
Submit & Authorize
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
|
|
|
|||
|
|
@ -721,8 +721,7 @@ export const DataSourceFormDefaultValues = {
|
|||
source: DataSourceKey.BOX,
|
||||
config: {
|
||||
name: '',
|
||||
folder_id: '',
|
||||
index_recursively: false,
|
||||
folder_id: '0',
|
||||
credentials: {
|
||||
box_tokens: '',
|
||||
},
|
||||
|
|
|
|||
|
|
@ -47,4 +47,13 @@ export const startGmailWebAuth = (payload: { credentials: string }) =>
|
|||
export const pollGmailWebAuthResult = (payload: { flow_id: string }) =>
|
||||
request.post(api.googleWebAuthResult('gmail'), { data: payload });
|
||||
|
||||
export const startBoxWebAuth = (payload: {
|
||||
client_id: string;
|
||||
client_secret: string;
|
||||
redirect_uri?: string;
|
||||
}) => request.post(api.boxWebAuthStart(), { data: payload });
|
||||
|
||||
export const pollBoxWebAuthResult = (payload: { flow_id: string }) =>
|
||||
request.post(api.boxWebAuthResult(), { data: payload });
|
||||
|
||||
export default dataSourceService;
|
||||
|
|
|
|||
|
|
@ -46,6 +46,8 @@ export default {
|
|||
`${api_host}/connector/google/oauth/web/start?type=${type}`,
|
||||
googleWebAuthResult: (type: 'google-drive' | 'gmail') =>
|
||||
`${api_host}/connector/google/oauth/web/result?type=${type}`,
|
||||
boxWebAuthStart: () => `${api_host}/connector/box/oauth/web/start`,
|
||||
boxWebAuthResult: () => `${api_host}/connector/box/oauth/web/result`,
|
||||
|
||||
// plugin
|
||||
llm_tools: `${api_host}/plugin/llm_tools`,
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue