Co-authored-by: Yeuoly <admin@srmxy.cn>tags/0.6.6
| API_TOOL_DEFAULT_CONNECT_TIMEOUT=10 | API_TOOL_DEFAULT_CONNECT_TIMEOUT=10 | ||||
| API_TOOL_DEFAULT_READ_TIMEOUT=60 | API_TOOL_DEFAULT_READ_TIMEOUT=60 | ||||
| # HTTP Node configuration | |||||
| HTTP_REQUEST_MAX_CONNECT_TIMEOUT=300 | |||||
| HTTP_REQUEST_MAX_READ_TIMEOUT=600 | |||||
| HTTP_REQUEST_MAX_WRITE_TIMEOUT=600 | |||||
| # Log file path | # Log file path | ||||
| LOG_FILE= | LOG_FILE= |
| type: Literal['none', 'form-data', 'x-www-form-urlencoded', 'raw-text', 'json'] | type: Literal['none', 'form-data', 'x-www-form-urlencoded', 'raw-text', 'json'] | ||||
| data: Union[None, str] | data: Union[None, str] | ||||
| class Timeout(BaseModel): | |||||
| connect: int | |||||
| read: int | |||||
| write: int | |||||
| method: Literal['get', 'post', 'put', 'patch', 'delete', 'head'] | method: Literal['get', 'post', 'put', 'patch', 'delete', 'head'] | ||||
| url: str | url: str | ||||
| authorization: Authorization | authorization: Authorization | ||||
| headers: str | headers: str | ||||
| params: str | params: str | ||||
| body: Optional[Body] | |||||
| body: Optional[Body] | |||||
| timeout: Optional[Timeout] |
| from core.workflow.nodes.http_request.entities import HttpRequestNodeData | from core.workflow.nodes.http_request.entities import HttpRequestNodeData | ||||
| from core.workflow.utils.variable_template_parser import VariableTemplateParser | from core.workflow.utils.variable_template_parser import VariableTemplateParser | ||||
| HTTP_REQUEST_DEFAULT_TIMEOUT = (10, 60) | |||||
| MAX_BINARY_SIZE = 1024 * 1024 * 10 # 10MB | MAX_BINARY_SIZE = 1024 * 1024 * 10 # 10MB | ||||
| READABLE_MAX_BINARY_SIZE = '10MB' | READABLE_MAX_BINARY_SIZE = '10MB' | ||||
| MAX_TEXT_SIZE = 1024 * 1024 // 10 # 0.1MB | MAX_TEXT_SIZE = 1024 * 1024 // 10 # 0.1MB | ||||
| files: Union[None, dict[str, Any]] | files: Union[None, dict[str, Any]] | ||||
| boundary: str | boundary: str | ||||
| variable_selectors: list[VariableSelector] | variable_selectors: list[VariableSelector] | ||||
| timeout: HttpRequestNodeData.Timeout | |||||
| def __init__(self, node_data: HttpRequestNodeData, variable_pool: Optional[VariablePool] = None): | |||||
| def __init__(self, node_data: HttpRequestNodeData, timeout: HttpRequestNodeData.Timeout, variable_pool: Optional[VariablePool] = None): | |||||
| """ | """ | ||||
| init | init | ||||
| """ | """ | ||||
| self.server_url = node_data.url | self.server_url = node_data.url | ||||
| self.method = node_data.method | self.method = node_data.method | ||||
| self.authorization = node_data.authorization | self.authorization = node_data.authorization | ||||
| self.timeout = timeout | |||||
| self.params = {} | self.params = {} | ||||
| self.headers = {} | self.headers = {} | ||||
| self.body = None | self.body = None | ||||
| 'url': self.server_url, | 'url': self.server_url, | ||||
| 'headers': headers, | 'headers': headers, | ||||
| 'params': self.params, | 'params': self.params, | ||||
| 'timeout': HTTP_REQUEST_DEFAULT_TIMEOUT, | |||||
| 'timeout': (self.timeout.connect, self.timeout.read, self.timeout.write), | |||||
| 'follow_redirects': True | 'follow_redirects': True | ||||
| } | } | ||||
| import logging | import logging | ||||
| import os | |||||
| from mimetypes import guess_extension | from mimetypes import guess_extension | ||||
| from os import path | from os import path | ||||
| from typing import cast | from typing import cast | ||||
| from core.workflow.nodes.http_request.http_executor import HttpExecutor, HttpExecutorResponse | from core.workflow.nodes.http_request.http_executor import HttpExecutor, HttpExecutorResponse | ||||
| from models.workflow import WorkflowNodeExecutionStatus | from models.workflow import WorkflowNodeExecutionStatus | ||||
| MAX_CONNECT_TIMEOUT = int(os.environ.get('HTTP_REQUEST_MAX_CONNECT_TIMEOUT', '300')) | |||||
| MAX_READ_TIMEOUT = int(os.environ.get('HTTP_REQUEST_MAX_READ_TIMEOUT', '600')) | |||||
| MAX_WRITE_TIMEOUT = int(os.environ.get('HTTP_REQUEST_MAX_WRITE_TIMEOUT', '600')) | |||||
| HTTP_REQUEST_DEFAULT_TIMEOUT = HttpRequestNodeData.Timeout(connect=min(10, MAX_CONNECT_TIMEOUT), | |||||
| read=min(60, MAX_READ_TIMEOUT), | |||||
| write=min(20, MAX_WRITE_TIMEOUT)) | |||||
| class HttpRequestNode(BaseNode): | class HttpRequestNode(BaseNode): | ||||
| _node_data_cls = HttpRequestNodeData | _node_data_cls = HttpRequestNodeData | ||||
| node_type = NodeType.HTTP_REQUEST | node_type = NodeType.HTTP_REQUEST | ||||
| @classmethod | |||||
| def get_default_config(cls) -> dict: | |||||
| return { | |||||
| "type": "http-request", | |||||
| "config": { | |||||
| "method": "get", | |||||
| "authorization": { | |||||
| "type": "no-auth", | |||||
| }, | |||||
| "body": { | |||||
| "type": "none" | |||||
| }, | |||||
| "timeout": { | |||||
| **HTTP_REQUEST_DEFAULT_TIMEOUT.dict(), | |||||
| "max_connect_timeout": MAX_CONNECT_TIMEOUT, | |||||
| "max_read_timeout": MAX_READ_TIMEOUT, | |||||
| "max_write_timeout": MAX_WRITE_TIMEOUT, | |||||
| } | |||||
| }, | |||||
| } | |||||
| def _run(self, variable_pool: VariablePool) -> NodeRunResult: | def _run(self, variable_pool: VariablePool) -> NodeRunResult: | ||||
| node_data: HttpRequestNodeData = cast(self._node_data_cls, self.node_data) | node_data: HttpRequestNodeData = cast(self._node_data_cls, self.node_data) | ||||
| # init http executor | # init http executor | ||||
| http_executor = None | http_executor = None | ||||
| try: | try: | ||||
| http_executor = HttpExecutor(node_data=node_data, variable_pool=variable_pool) | |||||
| http_executor = HttpExecutor(node_data=node_data, | |||||
| timeout=self._get_request_timeout(node_data), | |||||
| variable_pool=variable_pool) | |||||
| # invoke http executor | # invoke http executor | ||||
| response = http_executor.invoke() | response = http_executor.invoke() | ||||
| error=str(e), | error=str(e), | ||||
| process_data=process_data | process_data=process_data | ||||
| ) | ) | ||||
| files = self.extract_files(http_executor.server_url, response) | files = self.extract_files(http_executor.server_url, response) | ||||
| return NodeRunResult( | return NodeRunResult( | ||||
| } | } | ||||
| ) | ) | ||||
| def _get_request_timeout(self, node_data: HttpRequestNodeData) -> HttpRequestNodeData.Timeout: | |||||
| timeout = node_data.timeout | |||||
| if timeout is None: | |||||
| return HTTP_REQUEST_DEFAULT_TIMEOUT | |||||
| timeout.connect = min(timeout.connect, MAX_CONNECT_TIMEOUT) | |||||
| timeout.read = min(timeout.read, MAX_READ_TIMEOUT) | |||||
| timeout.write = min(timeout.write, MAX_WRITE_TIMEOUT) | |||||
| return timeout | |||||
| @classmethod | @classmethod | ||||
| def _extract_variable_selector_to_variable_mapping(cls, node_data: HttpRequestNodeData) -> dict[str, list[str]]: | def _extract_variable_selector_to_variable_mapping(cls, node_data: HttpRequestNodeData) -> dict[str, list[str]]: | ||||
| """ | """ | ||||
| :return: | :return: | ||||
| """ | """ | ||||
| try: | try: | ||||
| http_executor = HttpExecutor(node_data=node_data) | |||||
| http_executor = HttpExecutor(node_data=node_data, timeout=HTTP_REQUEST_DEFAULT_TIMEOUT) | |||||
| variable_selectors = http_executor.variable_selectors | variable_selectors = http_executor.variable_selectors | ||||
| # if not image, return directly | # if not image, return directly | ||||
| if 'image' not in mimetype: | if 'image' not in mimetype: | ||||
| return files | return files | ||||
| if mimetype: | if mimetype: | ||||
| # extract filename from url | # extract filename from url | ||||
| filename = path.basename(url) | filename = path.basename(url) |
| 'use client' | |||||
| import type { FC } from 'react' | |||||
| import React from 'react' | |||||
| import cn from 'classnames' | |||||
| import { useTranslation } from 'react-i18next' | |||||
| import { useBoolean } from 'ahooks' | |||||
| import type { Timeout as TimeoutPayloadType } from '../../types' | |||||
| import { ChevronRight } from '@/app/components/base/icons/src/vender/line/arrows' | |||||
| type Props = { | |||||
| readonly: boolean | |||||
| nodeId: string | |||||
| payload: TimeoutPayloadType | |||||
| onChange: (payload: TimeoutPayloadType) => void | |||||
| } | |||||
| const i18nPrefix = 'workflow.nodes.http' | |||||
| const InputField: FC<{ | |||||
| title: string | |||||
| description: string | |||||
| placeholder: string | |||||
| value?: number | |||||
| onChange: (value: number) => void | |||||
| readOnly?: boolean | |||||
| min: number | |||||
| max: number | |||||
| }> = ({ title, description, placeholder, value, onChange, readOnly, min, max }) => { | |||||
| return ( | |||||
| <div className="space-y-1"> | |||||
| <div className="flex items-center h-[18px] space-x-2"> | |||||
| <span className="text-[13px] font-medium text-gray-900">{title}</span> | |||||
| <span className="text-xs font-normal text-gray-500">{description}</span> | |||||
| </div> | |||||
| <input className="w-full px-3 text-sm leading-9 text-gray-900 border-0 rounded-lg grow h-9 bg-gray-100 focus:outline-none focus:ring-1 focus:ring-inset focus:ring-gray-200" value={value} onChange={(e) => { | |||||
| const value = Math.max(min, Math.min(max, parseInt(e.target.value, 10))) | |||||
| onChange(value) | |||||
| }} placeholder={placeholder} type='number' readOnly={readOnly} min={min} max={max} /> | |||||
| </div> | |||||
| ) | |||||
| } | |||||
| const Timeout: FC<Props> = ({ readonly, payload, onChange }) => { | |||||
| const { t } = useTranslation() | |||||
| const { connect, read, write, max_connect_timeout, max_read_timeout, max_write_timeout } = payload ?? {} | |||||
| const [isFold, { | |||||
| toggle: toggleFold, | |||||
| }] = useBoolean(true) | |||||
| return ( | |||||
| <> | |||||
| <div> | |||||
| <div | |||||
| onClick={toggleFold} | |||||
| className={cn('flex justify-between leading-[18px] text-[13px] font-semibold text-gray-700 uppercase cursor-pointer')}> | |||||
| <div>{t(`${i18nPrefix}.timeout.title`)}</div> | |||||
| <ChevronRight className='w-4 h-4 text-gray-500 transform transition-transform' style={{ transform: isFold ? 'rotate(0deg)' : 'rotate(90deg)' }} /> | |||||
| </div> | |||||
| {!isFold && ( | |||||
| <div className='mt-2 space-y-1'> | |||||
| <div className="space-y-3"> | |||||
| <InputField | |||||
| title={t('workflow.nodes.http.timeout.connectLabel')!} | |||||
| description={t('workflow.nodes.http.timeout.connectPlaceholder')!} | |||||
| placeholder={t('workflow.nodes.http.timeout.connectPlaceholder')!} | |||||
| readOnly={readonly} | |||||
| value={connect} | |||||
| onChange={v => onChange?.({ ...payload, connect: v })} | |||||
| min={1} | |||||
| max={max_connect_timeout ?? 300} | |||||
| /> | |||||
| <InputField | |||||
| title={t('workflow.nodes.http.timeout.readLabel')!} | |||||
| description={t('workflow.nodes.http.timeout.readPlaceholder')!} | |||||
| placeholder={t('workflow.nodes.http.timeout.readPlaceholder')!} | |||||
| readOnly={readonly} | |||||
| value={read} | |||||
| onChange={v => onChange?.({ ...payload, read: v })} | |||||
| min={1} | |||||
| max={max_read_timeout ?? 600} | |||||
| /> | |||||
| <InputField | |||||
| title={t('workflow.nodes.http.timeout.writeLabel')!} | |||||
| description={t('workflow.nodes.http.timeout.writePlaceholder')!} | |||||
| placeholder={t('workflow.nodes.http.timeout.writePlaceholder')!} | |||||
| readOnly={readonly} | |||||
| value={write} | |||||
| onChange={v => onChange?.({ ...payload, write: v })} | |||||
| min={1} | |||||
| max={max_write_timeout ?? 600} | |||||
| /> | |||||
| </div> | |||||
| </div> | |||||
| )} | |||||
| </div> | |||||
| </> | |||||
| ) | |||||
| } | |||||
| export default React.memo(Timeout) |
| type: BodyType.none, | type: BodyType.none, | ||||
| data: '', | data: '', | ||||
| }, | }, | ||||
| timeout: { | |||||
| max_connect_timeout: 0, | |||||
| max_read_timeout: 0, | |||||
| max_write_timeout: 0, | |||||
| }, | |||||
| }, | }, | ||||
| getAvailablePrevNodes(isChatMode: boolean) { | getAvailablePrevNodes(isChatMode: boolean) { | ||||
| const nodes = isChatMode | const nodes = isChatMode |
| import EditBody from './components/edit-body' | import EditBody from './components/edit-body' | ||||
| import AuthorizationModal from './components/authorization' | import AuthorizationModal from './components/authorization' | ||||
| import type { HttpNodeType } from './types' | import type { HttpNodeType } from './types' | ||||
| import Timeout from './components/timeout' | |||||
| import Field from '@/app/components/workflow/nodes/_base/components/field' | import Field from '@/app/components/workflow/nodes/_base/components/field' | ||||
| import Split from '@/app/components/workflow/nodes/_base/components/split' | import Split from '@/app/components/workflow/nodes/_base/components/split' | ||||
| import OutputVars, { VarItem } from '@/app/components/workflow/nodes/_base/components/output-vars' | import OutputVars, { VarItem } from '@/app/components/workflow/nodes/_base/components/output-vars' | ||||
| showAuthorization, | showAuthorization, | ||||
| hideAuthorization, | hideAuthorization, | ||||
| setAuthorization, | setAuthorization, | ||||
| setTimeout, | |||||
| // single run | // single run | ||||
| isShowSingleRun, | isShowSingleRun, | ||||
| hideSingleRun, | hideSingleRun, | ||||
| /> | /> | ||||
| </Field> | </Field> | ||||
| </div> | </div> | ||||
| <Split /> | |||||
| <div className='px-4 pt-4 pb-4'> | |||||
| <Timeout | |||||
| nodeId={id} | |||||
| readonly={readOnly} | |||||
| payload={inputs.timeout} | |||||
| onChange={setTimeout} | |||||
| /> | |||||
| </div> | |||||
| {(isShowAuthorization && !readOnly) && ( | {(isShowAuthorization && !readOnly) && ( | ||||
| <AuthorizationModal | <AuthorizationModal | ||||
| isShow | isShow |
| } | null | } | null | ||||
| } | } | ||||
| export type Timeout = { | |||||
| connect?: number | |||||
| read?: number | |||||
| write?: number | |||||
| max_connect_timeout?: number | |||||
| max_read_timeout?: number | |||||
| max_write_timeout?: number | |||||
| } | |||||
| export type HttpNodeType = CommonNodeType & { | export type HttpNodeType = CommonNodeType & { | ||||
| variables: Variable[] | variables: Variable[] | ||||
| method: Method | method: Method | ||||
| params: string | params: string | ||||
| body: Body | body: Body | ||||
| authorization: Authorization | authorization: Authorization | ||||
| timeout: Timeout | |||||
| } | } |
| import { useCallback } from 'react' | |||||
| import { useCallback, useEffect } from 'react' | |||||
| import produce from 'immer' | import produce from 'immer' | ||||
| import { useBoolean } from 'ahooks' | import { useBoolean } from 'ahooks' | ||||
| import useVarList from '../_base/hooks/use-var-list' | import useVarList from '../_base/hooks/use-var-list' | ||||
| import { VarType } from '../../types' | import { VarType } from '../../types' | ||||
| import type { Var } from '../../types' | import type { Var } from '../../types' | ||||
| import type { Authorization, Body, HttpNodeType, Method } from './types' | |||||
| import { useStore } from '../../store' | |||||
| import type { Authorization, Body, HttpNodeType, Method, Timeout } from './types' | |||||
| import useKeyValueList from './hooks/use-key-value-list' | import useKeyValueList from './hooks/use-key-value-list' | ||||
| import useNodeCrud from '@/app/components/workflow/nodes/_base/hooks/use-node-crud' | import useNodeCrud from '@/app/components/workflow/nodes/_base/hooks/use-node-crud' | ||||
| import useOneStepRun from '@/app/components/workflow/nodes/_base/hooks/use-one-step-run' | import useOneStepRun from '@/app/components/workflow/nodes/_base/hooks/use-one-step-run' | ||||
| const useConfig = (id: string, payload: HttpNodeType) => { | const useConfig = (id: string, payload: HttpNodeType) => { | ||||
| const { nodesReadOnly: readOnly } = useNodesReadOnly() | const { nodesReadOnly: readOnly } = useNodesReadOnly() | ||||
| const defaultConfig = useStore(s => s.nodesDefaultConfigs)[payload.type] | |||||
| const { inputs, setInputs } = useNodeCrud<HttpNodeType>(id, payload) | const { inputs, setInputs } = useNodeCrud<HttpNodeType>(id, payload) | ||||
| const { handleVarListChange, handleAddVariable } = useVarList<HttpNodeType>({ | const { handleVarListChange, handleAddVariable } = useVarList<HttpNodeType>({ | ||||
| setInputs, | setInputs, | ||||
| }) | }) | ||||
| useEffect(() => { | |||||
| const isReady = defaultConfig && Object.keys(defaultConfig).length > 0 | |||||
| if (isReady) { | |||||
| setInputs({ | |||||
| ...inputs, | |||||
| ...defaultConfig, | |||||
| }) | |||||
| } | |||||
| // eslint-disable-next-line react-hooks/exhaustive-deps | |||||
| }, [defaultConfig]) | |||||
| const handleMethodChange = useCallback((method: Method) => { | const handleMethodChange = useCallback((method: Method) => { | ||||
| const newInputs = produce(inputs, (draft: HttpNodeType) => { | const newInputs = produce(inputs, (draft: HttpNodeType) => { | ||||
| draft.method = method | draft.method = method | ||||
| setInputs(newInputs) | setInputs(newInputs) | ||||
| }, [inputs, setInputs]) | }, [inputs, setInputs]) | ||||
| const setTimeout = useCallback((timeout: Timeout) => { | |||||
| const newInputs = produce(inputs, (draft: HttpNodeType) => { | |||||
| draft.timeout = timeout | |||||
| }) | |||||
| setInputs(newInputs) | |||||
| }, [inputs, setInputs]) | |||||
| const filterVar = useCallback((varPayload: Var) => { | const filterVar = useCallback((varPayload: Var) => { | ||||
| return [VarType.string, VarType.number].includes(varPayload.type) | return [VarType.string, VarType.number].includes(varPayload.type) | ||||
| }, []) | }, []) | ||||
| showAuthorization, | showAuthorization, | ||||
| hideAuthorization, | hideAuthorization, | ||||
| setAuthorization, | setAuthorization, | ||||
| setTimeout, | |||||
| // single run | // single run | ||||
| isShowSingleRun, | isShowSingleRun, | ||||
| hideSingleRun, | hideSingleRun, |
| 'header': 'Kopfzeile', | 'header': 'Kopfzeile', | ||||
| }, | }, | ||||
| insertVarPlaceholder: 'Tippen Sie ‘/’, um eine Variable einzufügen', | insertVarPlaceholder: 'Tippen Sie ‘/’, um eine Variable einzufügen', | ||||
| timeout: { | |||||
| title: 'Zeitüberschreitung', | |||||
| connectLabel: 'Verbindungszeitüberschreitung', | |||||
| connectPlaceholder: 'Geben Sie die Verbindungszeitüberschreitung in Sekunden ein', | |||||
| readLabel: 'Lesezeitüberschreitung', | |||||
| readPlaceholder: 'Geben Sie die Lesezeitüberschreitung in Sekunden ein', | |||||
| writeLabel: 'Schreibzeitüberschreitung', | |||||
| writePlaceholder: 'Geben Sie die Schreibzeitüberschreitung in Sekunden ein', | |||||
| }, | |||||
| }, | }, | ||||
| code: { | code: { | ||||
| inputVars: 'Eingabevariablen', | inputVars: 'Eingabevariablen', |
| 'header': 'Header', | 'header': 'Header', | ||||
| }, | }, | ||||
| insertVarPlaceholder: 'type \'/\' to insert variable', | insertVarPlaceholder: 'type \'/\' to insert variable', | ||||
| timeout: { | |||||
| title: 'Timeout', | |||||
| connectLabel: 'Connection Timeout', | |||||
| connectPlaceholder: 'Enter connection timeout in seconds', | |||||
| readLabel: 'Read Timeout', | |||||
| readPlaceholder: 'Enter read timeout in seconds', | |||||
| writeLabel: 'Write Timeout', | |||||
| writePlaceholder: 'Enter write timeout in seconds', | |||||
| }, | |||||
| }, | }, | ||||
| code: { | code: { | ||||
| inputVars: 'Input Variables', | inputVars: 'Input Variables', |
| 'header': 'En-tête', | 'header': 'En-tête', | ||||
| }, | }, | ||||
| insertVarPlaceholder: 'tapez \'/\' pour insérer une variable', | insertVarPlaceholder: 'tapez \'/\' pour insérer une variable', | ||||
| timeout: { | |||||
| title: 'Délai d\'expiration', | |||||
| connectLabel: 'Délai de connexion', | |||||
| connectPlaceholder: 'Entrez le délai de connexion en secondes', | |||||
| readLabel: 'Délai de lecture', | |||||
| readPlaceholder: 'Entrez le délai de lecture en secondes', | |||||
| writeLabel: 'Délai d\'écriture', | |||||
| writePlaceholder: 'Entrez le délai d\'écriture en secondes', | |||||
| }, | |||||
| }, | }, | ||||
| code: { | code: { | ||||
| inputVars: 'Variables d\'entrée', | inputVars: 'Variables d\'entrée', |
| 'header': 'ヘッダー', | 'header': 'ヘッダー', | ||||
| }, | }, | ||||
| insertVarPlaceholder: '変数を挿入するには\'/\'を入力してください', | insertVarPlaceholder: '変数を挿入するには\'/\'を入力してください', | ||||
| timeout: { | |||||
| title: 'タイムアウト', | |||||
| connectLabel: '接続タイムアウト', | |||||
| connectPlaceholder: '接続タイムアウトを秒で入力', | |||||
| readLabel: '読み取りタイムアウト', | |||||
| readPlaceholder: '読み取りタイムアウトを秒で入力', | |||||
| writeLabel: '書き込みタイムアウト', | |||||
| writePlaceholder: '書き込みタイムアウトを秒で入力', | |||||
| }, | |||||
| }, | }, | ||||
| code: { | code: { | ||||
| inputVars: '入力変数', | inputVars: '入力変数', |
| 'header': 'Cabeçalho', | 'header': 'Cabeçalho', | ||||
| }, | }, | ||||
| insertVarPlaceholder: 'digite \'/\' para inserir variável', | insertVarPlaceholder: 'digite \'/\' para inserir variável', | ||||
| timeout: { | |||||
| title: 'Tempo esgotado', | |||||
| connectLabel: 'Tempo de conexão', | |||||
| connectPlaceholder: 'Insira o tempo de conexão em segundos', | |||||
| readLabel: 'Tempo de leitura', | |||||
| readPlaceholder: 'Insira o tempo de leitura em segundos', | |||||
| writeLabel: 'Tempo de escrita', | |||||
| writePlaceholder: 'Insira o tempo de escrita em segundos', | |||||
| }, | |||||
| }, | }, | ||||
| code: { | code: { | ||||
| inputVars: 'Variáveis de entrada', | inputVars: 'Variáveis de entrada', |
| 'header': 'Заголовок', | 'header': 'Заголовок', | ||||
| }, | }, | ||||
| insertVarPlaceholder: 'наберіть \'/\' для вставки змінної', | insertVarPlaceholder: 'наберіть \'/\' для вставки змінної', | ||||
| timeout: { | |||||
| title: 'Час вичерпано', | |||||
| connectLabel: 'Тайм-аут з’єднання', | |||||
| connectPlaceholder: 'Введіть час тайм-ауту з’єднання у секундах', | |||||
| readLabel: 'Тайм-аут читання', | |||||
| readPlaceholder: 'Введіть час тайм-ауту читання у секундах', | |||||
| writeLabel: 'Тайм-аут запису', | |||||
| writePlaceholder: 'Введіть час тайм-ауту запису у секундах', | |||||
| }, | |||||
| }, | }, | ||||
| code: { | code: { | ||||
| inputVars: 'Вхідні змінні', | inputVars: 'Вхідні змінні', |
| 'header': 'Tiêu đề', | 'header': 'Tiêu đề', | ||||
| }, | }, | ||||
| insertVarPlaceholder: 'nhập \'/\' để chèn biến', | insertVarPlaceholder: 'nhập \'/\' để chèn biến', | ||||
| timeout: { | |||||
| title: 'Hết thời gian', | |||||
| connectLabel: 'Hết thời gian kết nối', | |||||
| connectPlaceholder: 'Nhập thời gian kết nối bằng giây', | |||||
| readLabel: 'Hết thời gian đọc', | |||||
| readPlaceholder: 'Nhập thời gian đọc bằng giây', | |||||
| writeLabel: 'Hết thời gian ghi', | |||||
| writePlaceholder: 'Nhập thời gian ghi bằng giây', | |||||
| }, | |||||
| }, | }, | ||||
| code: { | code: { | ||||
| inputVars: 'Biến đầu vào', | inputVars: 'Biến đầu vào', |
| 'header': 'Header', | 'header': 'Header', | ||||
| }, | }, | ||||
| insertVarPlaceholder: '键入 \'/\' 键快速插入变量', | insertVarPlaceholder: '键入 \'/\' 键快速插入变量', | ||||
| timeout: { | |||||
| title: '超时设置', | |||||
| connectLabel: '连接超时', | |||||
| connectPlaceholder: '输入连接超时(以秒为单位)', | |||||
| readLabel: '读取超时', | |||||
| readPlaceholder: '输入读取超时(以秒为单位)', | |||||
| writeLabel: '写入超时', | |||||
| writePlaceholder: '输入写入超时(以秒为单位)', | |||||
| }, | |||||
| }, | }, | ||||
| code: { | code: { | ||||
| inputVars: '输入变量', | inputVars: '输入变量', |