community: remove remaining premium wizard and admin surface
This commit is contained in:
@@ -5,6 +5,11 @@ license.workspace = true
|
|||||||
rust-version.workspace = true
|
rust-version.workspace = true
|
||||||
version.workspace = true
|
version.workspace = true
|
||||||
|
|
||||||
|
[[bin]]
|
||||||
|
name = "admin-api"
|
||||||
|
path = "src/main.rs"
|
||||||
|
test = false
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
argon2.workspace = true
|
argon2.workspace = true
|
||||||
axum.workspace = true
|
axum.workspace = true
|
||||||
|
|||||||
+21
-477
@@ -17,15 +17,15 @@ use crank_core::{
|
|||||||
};
|
};
|
||||||
use crank_mapping::{JsonPathRoot, MappingSet, infer_mapping_from_samples};
|
use crank_mapping::{JsonPathRoot, MappingSet, infer_mapping_from_samples};
|
||||||
use crank_registry::{
|
use crank_registry::{
|
||||||
AgentSummary, AgentVersionRecord, AsyncJobFilter, CreateAgentRequest, CreateAsyncJobRequest,
|
AgentSummary, AgentVersionRecord, CreateAgentRequest, CreateAsyncJobRequest,
|
||||||
CreateInvitationRequest, CreateInvocationLogRequest, CreatePlatformApiKeyRequest,
|
CreateInvitationRequest, CreateInvocationLogRequest, CreatePlatformApiKeyRequest,
|
||||||
CreateSecretRequest, CreateStreamSessionRequest, CreateVersionRequest, CreateWorkspaceRequest,
|
CreateSecretRequest, CreateStreamSessionRequest, CreateVersionRequest, CreateWorkspaceRequest,
|
||||||
InvitationRecord, InvocationLogRecord, ListInvocationLogsQuery, MembershipRecord,
|
InvitationRecord, InvocationLogRecord, ListInvocationLogsQuery, MembershipRecord,
|
||||||
OperationAgentRef, OperationSampleMetadata, OperationSummary, OperationUsageSummary,
|
OperationAgentRef, OperationSampleMetadata, OperationSummary, OperationUsageSummary,
|
||||||
OperationVersionRecord, Page, PlatformApiKeyRecord, PostgresRegistry, PublishAgentRequest,
|
OperationVersionRecord, PlatformApiKeyRecord, PostgresRegistry, PublishAgentRequest,
|
||||||
PublishRequest, RegistryError, RegistryOperation, RotateSecretRequest, SampleKind,
|
PublishRequest, RegistryError, RegistryOperation, RotateSecretRequest, SampleKind,
|
||||||
SaveAgentBindingsRequest, SaveAuthProfileRequest, SaveSampleMetadataRequest,
|
SaveAgentBindingsRequest, SaveAuthProfileRequest, SaveSampleMetadataRequest,
|
||||||
StreamSessionFilter, UpdateAsyncJobStatusRequest, UpdateWorkspaceRequest,
|
UpdateAsyncJobStatusRequest, UpdateWorkspaceRequest,
|
||||||
UsageAgentBreakdown, UsageBucket, UsageOperationBreakdown, UsageQuery, UsageSummary,
|
UsageAgentBreakdown, UsageBucket, UsageOperationBreakdown, UsageQuery, UsageSummary,
|
||||||
UsageTimelinePoint, WorkspaceMembershipRecord, WorkspaceRecord,
|
UsageTimelinePoint, WorkspaceMembershipRecord, WorkspaceRecord,
|
||||||
};
|
};
|
||||||
@@ -434,74 +434,6 @@ pub struct ProtocolCapabilityView {
|
|||||||
pub supports_aggregation_mode: Vec<AggregationMode>,
|
pub supports_aggregation_mode: Vec<AggregationMode>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, Deserialize)]
|
|
||||||
pub struct StreamSessionsQuery {
|
|
||||||
pub operation_id: Option<String>,
|
|
||||||
pub agent_id: Option<String>,
|
|
||||||
pub status: Option<StreamStatus>,
|
|
||||||
pub mode: Option<ExecutionMode>,
|
|
||||||
pub page: Option<u32>,
|
|
||||||
pub page_size: Option<u32>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Debug, Serialize)]
|
|
||||||
pub struct StreamSessionSummaryView {
|
|
||||||
pub id: String,
|
|
||||||
pub operation_id: String,
|
|
||||||
pub agent_id: Option<String>,
|
|
||||||
pub protocol: Protocol,
|
|
||||||
pub mode: ExecutionMode,
|
|
||||||
pub status: StreamStatus,
|
|
||||||
#[serde(with = "time::serde::rfc3339")]
|
|
||||||
pub expires_at: OffsetDateTime,
|
|
||||||
#[serde(with = "time::serde::rfc3339::option")]
|
|
||||||
pub last_poll_at: Option<OffsetDateTime>,
|
|
||||||
#[serde(with = "time::serde::rfc3339")]
|
|
||||||
pub created_at: OffsetDateTime,
|
|
||||||
#[serde(with = "time::serde::rfc3339::option")]
|
|
||||||
pub closed_at: Option<OffsetDateTime>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Debug, Serialize)]
|
|
||||||
pub struct StreamSessionDetailView {
|
|
||||||
#[serde(flatten)]
|
|
||||||
pub summary: StreamSessionSummaryView,
|
|
||||||
pub cursor: Option<Value>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Debug, Deserialize)]
|
|
||||||
pub struct AsyncJobsQuery {
|
|
||||||
pub operation_id: Option<String>,
|
|
||||||
pub agent_id: Option<String>,
|
|
||||||
pub status: Option<JobStatus>,
|
|
||||||
pub page: Option<u32>,
|
|
||||||
pub page_size: Option<u32>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Debug, Serialize)]
|
|
||||||
pub struct AsyncJobSummaryView {
|
|
||||||
pub id: String,
|
|
||||||
pub operation_id: String,
|
|
||||||
pub agent_id: Option<String>,
|
|
||||||
pub status: JobStatus,
|
|
||||||
pub progress: Value,
|
|
||||||
#[serde(with = "time::serde::rfc3339")]
|
|
||||||
pub created_at: OffsetDateTime,
|
|
||||||
#[serde(with = "time::serde::rfc3339")]
|
|
||||||
pub updated_at: OffsetDateTime,
|
|
||||||
#[serde(with = "time::serde::rfc3339::option")]
|
|
||||||
pub finished_at: Option<OffsetDateTime>,
|
|
||||||
#[serde(with = "time::serde::rfc3339::option")]
|
|
||||||
pub expires_at: Option<OffsetDateTime>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Debug, Serialize)]
|
|
||||||
pub struct AsyncJobDetailView {
|
|
||||||
#[serde(flatten)]
|
|
||||||
pub summary: AsyncJobSummaryView,
|
|
||||||
pub error: Option<Value>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Debug, Deserialize)]
|
#[derive(Clone, Debug, Deserialize)]
|
||||||
pub struct GenerateDraftPayload {
|
pub struct GenerateDraftPayload {
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
@@ -1613,327 +1545,6 @@ impl AdminService {
|
|||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[instrument(skip(self))]
|
|
||||||
pub async fn list_stream_sessions(
|
|
||||||
&self,
|
|
||||||
workspace_id: &WorkspaceId,
|
|
||||||
query: StreamSessionsQuery,
|
|
||||||
) -> Result<Page<StreamSessionSummaryView>, ApiError> {
|
|
||||||
self.ensure_workspace_exists(workspace_id).await?;
|
|
||||||
let operation_id = query.operation_id.as_deref().map(OperationId::new);
|
|
||||||
let agent_id = query.agent_id.as_deref().map(AgentId::new);
|
|
||||||
let limit = query.page_size.unwrap_or(20).max(1);
|
|
||||||
let page = self
|
|
||||||
.registry
|
|
||||||
.list_stream_sessions(StreamSessionFilter {
|
|
||||||
workspace_id,
|
|
||||||
agent_id: agent_id.as_ref(),
|
|
||||||
operation_id: operation_id.as_ref(),
|
|
||||||
status: query.status,
|
|
||||||
mode: query.mode,
|
|
||||||
limit,
|
|
||||||
})
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
Ok(Page {
|
|
||||||
items: page
|
|
||||||
.items
|
|
||||||
.into_iter()
|
|
||||||
.map(stream_session_summary_view)
|
|
||||||
.collect(),
|
|
||||||
total: page.total,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
#[instrument(skip(self))]
|
|
||||||
pub async fn get_stream_session(
|
|
||||||
&self,
|
|
||||||
workspace_id: &WorkspaceId,
|
|
||||||
session_id: &crank_core::StreamSessionId,
|
|
||||||
) -> Result<StreamSessionDetailView, ApiError> {
|
|
||||||
self.ensure_workspace_exists(workspace_id).await?;
|
|
||||||
let session = self
|
|
||||||
.registry
|
|
||||||
.get_stream_session(session_id)
|
|
||||||
.await?
|
|
||||||
.ok_or_else(|| {
|
|
||||||
ApiError::not_found_with_context(
|
|
||||||
format!("stream session {} was not found", session_id.as_str()),
|
|
||||||
json!({ "session_id": session_id.as_str() }),
|
|
||||||
)
|
|
||||||
})?;
|
|
||||||
ensure_stream_session_workspace(&session, workspace_id)?;
|
|
||||||
let session = self
|
|
||||||
.touch_stream_session_poll_if_ready(workspace_id, session)
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
Ok(stream_session_detail_view(session))
|
|
||||||
}
|
|
||||||
|
|
||||||
#[instrument(skip(self))]
|
|
||||||
pub async fn stop_stream_session(
|
|
||||||
&self,
|
|
||||||
workspace_id: &WorkspaceId,
|
|
||||||
session_id: &crank_core::StreamSessionId,
|
|
||||||
) -> Result<StreamSessionDetailView, ApiError> {
|
|
||||||
self.ensure_workspace_exists(workspace_id).await?;
|
|
||||||
let session = self
|
|
||||||
.registry
|
|
||||||
.get_stream_session(session_id)
|
|
||||||
.await?
|
|
||||||
.ok_or_else(|| {
|
|
||||||
ApiError::not_found_with_context(
|
|
||||||
format!("stream session {} was not found", session_id.as_str()),
|
|
||||||
json!({ "session_id": session_id.as_str() }),
|
|
||||||
)
|
|
||||||
})?;
|
|
||||||
ensure_stream_session_workspace(&session, workspace_id)?;
|
|
||||||
self.registry
|
|
||||||
.close_stream_session(session_id, &OffsetDateTime::now_utc())
|
|
||||||
.await?;
|
|
||||||
let updated = self
|
|
||||||
.registry
|
|
||||||
.get_stream_session(session_id)
|
|
||||||
.await?
|
|
||||||
.ok_or_else(|| {
|
|
||||||
ApiError::not_found_with_context(
|
|
||||||
format!("stream session {} was not found", session_id.as_str()),
|
|
||||||
json!({ "session_id": session_id.as_str() }),
|
|
||||||
)
|
|
||||||
})?;
|
|
||||||
|
|
||||||
Ok(stream_session_detail_view(updated))
|
|
||||||
}
|
|
||||||
|
|
||||||
#[instrument(skip(self))]
|
|
||||||
pub async fn list_async_jobs(
|
|
||||||
&self,
|
|
||||||
workspace_id: &WorkspaceId,
|
|
||||||
query: AsyncJobsQuery,
|
|
||||||
) -> Result<Page<AsyncJobSummaryView>, ApiError> {
|
|
||||||
self.ensure_workspace_exists(workspace_id).await?;
|
|
||||||
let operation_id = query.operation_id.as_deref().map(OperationId::new);
|
|
||||||
let agent_id = query.agent_id.as_deref().map(AgentId::new);
|
|
||||||
let limit = query.page_size.unwrap_or(20).max(1);
|
|
||||||
let page = self
|
|
||||||
.registry
|
|
||||||
.list_async_jobs(AsyncJobFilter {
|
|
||||||
workspace_id,
|
|
||||||
agent_id: agent_id.as_ref(),
|
|
||||||
operation_id: operation_id.as_ref(),
|
|
||||||
status: query.status,
|
|
||||||
limit,
|
|
||||||
})
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
Ok(Page {
|
|
||||||
items: page.items.into_iter().map(async_job_summary_view).collect(),
|
|
||||||
total: page.total,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
#[instrument(skip(self))]
|
|
||||||
pub async fn get_async_job(
|
|
||||||
&self,
|
|
||||||
workspace_id: &WorkspaceId,
|
|
||||||
job_id: &crank_core::AsyncJobId,
|
|
||||||
) -> Result<AsyncJobDetailView, ApiError> {
|
|
||||||
self.ensure_workspace_exists(workspace_id).await?;
|
|
||||||
let job = self.registry.get_async_job(job_id).await?.ok_or_else(|| {
|
|
||||||
ApiError::not_found_with_context(
|
|
||||||
format!("async job {} was not found", job_id.as_str()),
|
|
||||||
json!({ "job_id": job_id.as_str() }),
|
|
||||||
)
|
|
||||||
})?;
|
|
||||||
ensure_async_job_workspace(&job, workspace_id)?;
|
|
||||||
let job = if matches!(job.status, JobStatus::Completed) {
|
|
||||||
job
|
|
||||||
} else {
|
|
||||||
self.touch_async_job_poll_if_ready(workspace_id, job)
|
|
||||||
.await?
|
|
||||||
};
|
|
||||||
|
|
||||||
Ok(async_job_detail_view(job))
|
|
||||||
}
|
|
||||||
|
|
||||||
#[instrument(skip(self))]
|
|
||||||
pub async fn cancel_async_job(
|
|
||||||
&self,
|
|
||||||
workspace_id: &WorkspaceId,
|
|
||||||
job_id: &crank_core::AsyncJobId,
|
|
||||||
) -> Result<AsyncJobDetailView, ApiError> {
|
|
||||||
self.ensure_workspace_exists(workspace_id).await?;
|
|
||||||
let job = self.registry.get_async_job(job_id).await?.ok_or_else(|| {
|
|
||||||
ApiError::not_found_with_context(
|
|
||||||
format!("async job {} was not found", job_id.as_str()),
|
|
||||||
json!({ "job_id": job_id.as_str() }),
|
|
||||||
)
|
|
||||||
})?;
|
|
||||||
ensure_async_job_workspace(&job, workspace_id)?;
|
|
||||||
self.registry
|
|
||||||
.cancel_async_job(job_id, &OffsetDateTime::now_utc())
|
|
||||||
.await?;
|
|
||||||
let updated = self.registry.get_async_job(job_id).await?.ok_or_else(|| {
|
|
||||||
ApiError::not_found_with_context(
|
|
||||||
format!("async job {} was not found", job_id.as_str()),
|
|
||||||
json!({ "job_id": job_id.as_str() }),
|
|
||||||
)
|
|
||||||
})?;
|
|
||||||
|
|
||||||
Ok(async_job_detail_view(updated))
|
|
||||||
}
|
|
||||||
|
|
||||||
#[instrument(skip(self))]
|
|
||||||
pub async fn get_async_job_result(
|
|
||||||
&self,
|
|
||||||
workspace_id: &WorkspaceId,
|
|
||||||
job_id: &crank_core::AsyncJobId,
|
|
||||||
) -> Result<Value, ApiError> {
|
|
||||||
self.ensure_workspace_exists(workspace_id).await?;
|
|
||||||
let job = self.registry.get_async_job(job_id).await?.ok_or_else(|| {
|
|
||||||
ApiError::not_found_with_context(
|
|
||||||
format!("async job {} was not found", job_id.as_str()),
|
|
||||||
json!({ "job_id": job_id.as_str() }),
|
|
||||||
)
|
|
||||||
})?;
|
|
||||||
ensure_async_job_workspace(&job, workspace_id)?;
|
|
||||||
let job = if matches!(job.status, JobStatus::Completed) {
|
|
||||||
job
|
|
||||||
} else {
|
|
||||||
self.touch_async_job_poll_if_ready(workspace_id, job)
|
|
||||||
.await?
|
|
||||||
};
|
|
||||||
|
|
||||||
match job.status {
|
|
||||||
JobStatus::Completed => Ok(job.result.unwrap_or(Value::Null)),
|
|
||||||
JobStatus::Failed => Err(ApiError::conflict_with_context(
|
|
||||||
"async job failed",
|
|
||||||
json!({
|
|
||||||
"job_id": job_id.as_str(),
|
|
||||||
"status": "failed",
|
|
||||||
"error": job.error,
|
|
||||||
}),
|
|
||||||
)),
|
|
||||||
JobStatus::Cancelled => Err(ApiError::conflict_with_context(
|
|
||||||
"async job was cancelled",
|
|
||||||
json!({
|
|
||||||
"job_id": job_id.as_str(),
|
|
||||||
"status": "cancelled",
|
|
||||||
}),
|
|
||||||
)),
|
|
||||||
_ => Err(ApiError::conflict_with_context(
|
|
||||||
"async job result is not ready",
|
|
||||||
json!({
|
|
||||||
"job_id": job_id.as_str(),
|
|
||||||
"status": match job.status {
|
|
||||||
JobStatus::Created => "created",
|
|
||||||
JobStatus::Running => "running",
|
|
||||||
JobStatus::Completed => "completed",
|
|
||||||
JobStatus::Failed => "failed",
|
|
||||||
JobStatus::Cancelled => "cancelled",
|
|
||||||
JobStatus::Expired => "expired",
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
)),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn touch_stream_session_poll_if_ready(
|
|
||||||
&self,
|
|
||||||
workspace_id: &WorkspaceId,
|
|
||||||
session: StreamSession,
|
|
||||||
) -> Result<StreamSession, ApiError> {
|
|
||||||
let poll_interval_ms = self
|
|
||||||
.streaming_poll_interval_ms(workspace_id, &session.operation_id)
|
|
||||||
.await?;
|
|
||||||
let now = OffsetDateTime::now_utc();
|
|
||||||
let remaining_delay_ms = session.remaining_poll_delay_ms(now, poll_interval_ms);
|
|
||||||
|
|
||||||
if remaining_delay_ms > 0 {
|
|
||||||
return Err(ApiError::rate_limited_with_context(
|
|
||||||
"stream session poll rate limit exceeded",
|
|
||||||
json!({
|
|
||||||
"session_id": session.id.as_str(),
|
|
||||||
"poll_after_ms": remaining_delay_ms,
|
|
||||||
}),
|
|
||||||
));
|
|
||||||
}
|
|
||||||
|
|
||||||
self.registry
|
|
||||||
.touch_stream_session_poll(&session.id, &now)
|
|
||||||
.await
|
|
||||||
.map_err(ApiError::from)
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn touch_async_job_poll_if_ready(
|
|
||||||
&self,
|
|
||||||
workspace_id: &WorkspaceId,
|
|
||||||
job: AsyncJobHandle,
|
|
||||||
) -> Result<AsyncJobHandle, ApiError> {
|
|
||||||
let poll_interval_ms = self
|
|
||||||
.streaming_poll_interval_ms(workspace_id, &job.operation_id)
|
|
||||||
.await?;
|
|
||||||
let now = OffsetDateTime::now_utc();
|
|
||||||
let remaining_delay_ms = job.remaining_poll_delay_ms(now, poll_interval_ms);
|
|
||||||
|
|
||||||
if remaining_delay_ms > 0 {
|
|
||||||
return Err(ApiError::rate_limited_with_context(
|
|
||||||
"async job poll rate limit exceeded",
|
|
||||||
json!({
|
|
||||||
"job_id": job.id.as_str(),
|
|
||||||
"poll_after_ms": remaining_delay_ms,
|
|
||||||
}),
|
|
||||||
));
|
|
||||||
}
|
|
||||||
|
|
||||||
self.registry
|
|
||||||
.touch_async_job_poll(&job.id, &now)
|
|
||||||
.await
|
|
||||||
.map_err(ApiError::from)
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn streaming_poll_interval_ms(
|
|
||||||
&self,
|
|
||||||
workspace_id: &WorkspaceId,
|
|
||||||
operation_id: &OperationId,
|
|
||||||
) -> Result<u64, ApiError> {
|
|
||||||
let summary = self
|
|
||||||
.registry
|
|
||||||
.get_operation_summary(workspace_id, operation_id)
|
|
||||||
.await?
|
|
||||||
.ok_or_else(|| {
|
|
||||||
ApiError::not_found_with_context(
|
|
||||||
format!("operation {} was not found", operation_id.as_str()),
|
|
||||||
json!({ "operation_id": operation_id.as_str() }),
|
|
||||||
)
|
|
||||||
})?;
|
|
||||||
let version = self
|
|
||||||
.registry
|
|
||||||
.get_operation_version(workspace_id, operation_id, summary.current_draft_version)
|
|
||||||
.await?
|
|
||||||
.ok_or_else(|| {
|
|
||||||
ApiError::not_found_with_context(
|
|
||||||
format!(
|
|
||||||
"operation version {} for {} was not found",
|
|
||||||
summary.current_draft_version,
|
|
||||||
operation_id.as_str()
|
|
||||||
),
|
|
||||||
json!({
|
|
||||||
"operation_id": operation_id.as_str(),
|
|
||||||
"version": summary.current_draft_version,
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
})?;
|
|
||||||
|
|
||||||
Ok(version
|
|
||||||
.snapshot
|
|
||||||
.execution_config
|
|
||||||
.streaming
|
|
||||||
.as_ref()
|
|
||||||
.and_then(|streaming| streaming.poll_interval_ms)
|
|
||||||
.unwrap_or(1_000))
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn list_operations(
|
pub async fn list_operations(
|
||||||
&self,
|
&self,
|
||||||
workspace_id: &WorkspaceId,
|
workspace_id: &WorkspaceId,
|
||||||
@@ -3557,6 +3168,7 @@ impl AdminService {
|
|||||||
fn validate_operation_payload(&self, payload: &OperationPayload) -> Result<(), ApiError> {
|
fn validate_operation_payload(&self, payload: &OperationPayload) -> Result<(), ApiError> {
|
||||||
self.validate_operation_capabilities(payload.protocol, payload.security_level)?;
|
self.validate_operation_capabilities(payload.protocol, payload.security_level)?;
|
||||||
validate_protocol_target(payload.protocol, &payload.target)?;
|
validate_protocol_target(payload.protocol, &payload.target)?;
|
||||||
|
validate_streaming_policy(&payload.execution_config)?;
|
||||||
validate_response_cache_policy(&payload.target, &payload.execution_config)?;
|
validate_response_cache_policy(&payload.target, &payload.execution_config)?;
|
||||||
payload.input_mapping.validate_paths()?;
|
payload.input_mapping.validate_paths()?;
|
||||||
payload.output_mapping.validate_paths()?;
|
payload.output_mapping.validate_paths()?;
|
||||||
@@ -3566,6 +3178,7 @@ impl AdminService {
|
|||||||
fn validate_registry_operation(&self, operation: &RegistryOperation) -> Result<(), ApiError> {
|
fn validate_registry_operation(&self, operation: &RegistryOperation) -> Result<(), ApiError> {
|
||||||
self.validate_operation_capabilities(operation.protocol, operation.security_level)?;
|
self.validate_operation_capabilities(operation.protocol, operation.security_level)?;
|
||||||
validate_protocol_target(operation.protocol, &operation.target)?;
|
validate_protocol_target(operation.protocol, &operation.target)?;
|
||||||
|
validate_streaming_policy(&operation.execution_config)?;
|
||||||
validate_response_cache_policy(&operation.target, &operation.execution_config)?;
|
validate_response_cache_policy(&operation.target, &operation.execution_config)?;
|
||||||
operation.input_mapping.validate_paths()?;
|
operation.input_mapping.validate_paths()?;
|
||||||
operation.output_mapping.validate_paths()?;
|
operation.output_mapping.validate_paths()?;
|
||||||
@@ -4498,91 +4111,6 @@ fn protocol_capability_view(protocol: Protocol, edition: ProductEdition) -> Prot
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn stream_session_summary_view(session: StreamSession) -> StreamSessionSummaryView {
|
|
||||||
StreamSessionSummaryView {
|
|
||||||
id: session.id.as_str().to_owned(),
|
|
||||||
operation_id: session.operation_id.as_str().to_owned(),
|
|
||||||
agent_id: session.agent_id.map(|value| value.as_str().to_owned()),
|
|
||||||
protocol: session.protocol,
|
|
||||||
mode: session.mode,
|
|
||||||
status: session.status,
|
|
||||||
expires_at: session.expires_at,
|
|
||||||
last_poll_at: session.last_poll_at,
|
|
||||||
created_at: session.created_at,
|
|
||||||
closed_at: session.closed_at,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn stream_session_detail_view(session: StreamSession) -> StreamSessionDetailView {
|
|
||||||
StreamSessionDetailView {
|
|
||||||
cursor: session.cursor.clone(),
|
|
||||||
summary: stream_session_summary_view(session),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn async_job_summary_view(job: AsyncJobHandle) -> AsyncJobSummaryView {
|
|
||||||
AsyncJobSummaryView {
|
|
||||||
id: job.id.as_str().to_owned(),
|
|
||||||
operation_id: job.operation_id.as_str().to_owned(),
|
|
||||||
agent_id: job.agent_id.map(|value| value.as_str().to_owned()),
|
|
||||||
status: job.status,
|
|
||||||
progress: job.progress,
|
|
||||||
created_at: job.created_at,
|
|
||||||
updated_at: job.updated_at,
|
|
||||||
finished_at: job.finished_at,
|
|
||||||
expires_at: job.expires_at,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn async_job_detail_view(job: AsyncJobHandle) -> AsyncJobDetailView {
|
|
||||||
AsyncJobDetailView {
|
|
||||||
error: job.error.clone(),
|
|
||||||
summary: async_job_summary_view(job),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn ensure_stream_session_workspace(
|
|
||||||
session: &StreamSession,
|
|
||||||
workspace_id: &WorkspaceId,
|
|
||||||
) -> Result<(), ApiError> {
|
|
||||||
if &session.workspace_id == workspace_id {
|
|
||||||
return Ok(());
|
|
||||||
}
|
|
||||||
|
|
||||||
Err(ApiError::not_found_with_context(
|
|
||||||
format!(
|
|
||||||
"stream session {} was not found in workspace {}",
|
|
||||||
session.id.as_str(),
|
|
||||||
workspace_id.as_str()
|
|
||||||
),
|
|
||||||
json!({
|
|
||||||
"session_id": session.id.as_str(),
|
|
||||||
"workspace_id": workspace_id.as_str(),
|
|
||||||
}),
|
|
||||||
))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn ensure_async_job_workspace(
|
|
||||||
job: &AsyncJobHandle,
|
|
||||||
workspace_id: &WorkspaceId,
|
|
||||||
) -> Result<(), ApiError> {
|
|
||||||
if &job.workspace_id == workspace_id {
|
|
||||||
return Ok(());
|
|
||||||
}
|
|
||||||
|
|
||||||
Err(ApiError::not_found_with_context(
|
|
||||||
format!(
|
|
||||||
"async job {} was not found in workspace {}",
|
|
||||||
job.id.as_str(),
|
|
||||||
workspace_id.as_str()
|
|
||||||
),
|
|
||||||
json!({
|
|
||||||
"job_id": job.id.as_str(),
|
|
||||||
"workspace_id": workspace_id.as_str(),
|
|
||||||
}),
|
|
||||||
))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn usage_window(period: UsagePeriod) -> Result<(UsagePeriod, String, UsageBucket), ApiError> {
|
fn usage_window(period: UsagePeriod) -> Result<(UsagePeriod, String, UsageBucket), ApiError> {
|
||||||
let now = OffsetDateTime::now_utc();
|
let now = OffsetDateTime::now_utc();
|
||||||
let (start, bucket) = match period {
|
let (start, bucket) = match period {
|
||||||
@@ -4710,6 +4238,22 @@ fn agent_mcp_endpoint(workspace_slug: &str, agent_slug: &str) -> String {
|
|||||||
format!("/mcp/v1/{workspace_slug}/{agent_slug}")
|
format!("/mcp/v1/{workspace_slug}/{agent_slug}")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn validate_streaming_policy(
|
||||||
|
execution_config: &crank_core::ExecutionConfig,
|
||||||
|
) -> Result<(), ApiError> {
|
||||||
|
if execution_config.streaming.is_some() {
|
||||||
|
return Err(ApiError::validation_with_context(
|
||||||
|
"streaming execution is not supported in Community".to_owned(),
|
||||||
|
json!({
|
||||||
|
"field": "execution_config.streaming",
|
||||||
|
"edition": "community",
|
||||||
|
}),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
fn validate_response_cache_policy(
|
fn validate_response_cache_policy(
|
||||||
target: &Target,
|
target: &Target,
|
||||||
execution_config: &crank_core::ExecutionConfig,
|
execution_config: &crank_core::ExecutionConfig,
|
||||||
|
|||||||
@@ -5,6 +5,11 @@ license.workspace = true
|
|||||||
rust-version.workspace = true
|
rust-version.workspace = true
|
||||||
version.workspace = true
|
version.workspace = true
|
||||||
|
|
||||||
|
[[bin]]
|
||||||
|
name = "mcp-server"
|
||||||
|
path = "src/main.rs"
|
||||||
|
test = false
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
async-trait = "0.1"
|
async-trait = "0.1"
|
||||||
axum.workspace = true
|
axum.workspace = true
|
||||||
|
|||||||
@@ -98,175 +98,6 @@ tls:
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="info-callout" id="wizard-streaming-capability-note" hidden style="margin-bottom: 20px;">
|
|
||||||
<svg class="info-callout-icon" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round">
|
|
||||||
<circle cx="8" cy="8" r="7"/>
|
|
||||||
<path d="M8 11V8M8 5v-.5"/>
|
|
||||||
</svg>
|
|
||||||
<div class="info-callout-body">
|
|
||||||
<div class="info-callout-title" data-i18n="wizard.step5.streaming_title">Streaming execution in Community</div>
|
|
||||||
<div class="info-callout-text"></div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div id="wizard-streaming-config-card" class="config-card" style="margin-bottom: 20px;">
|
|
||||||
<div class="config-card-header">
|
|
||||||
<div class="config-card-header-icon">
|
|
||||||
<svg width="13" height="13" viewBox="0 0 16 16" fill="none" stroke="var(--text-secondary)" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round">
|
|
||||||
<path d="M3 4h10M3 8h10M3 12h6"/>
|
|
||||||
<circle cx="12" cy="12" r="1.5"/>
|
|
||||||
</svg>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<div class="config-card-title" data-i18n="wizard.streaming.title">Streaming execution</div>
|
|
||||||
<div class="config-card-subtitle" data-i18n="wizard.streaming.subtitle">Bounded stream, session and async job settings for MCP tool families.</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="config-card-body" style="gap: 16px;">
|
|
||||||
<div class="form-row">
|
|
||||||
<div class="form-group">
|
|
||||||
<label class="form-label" data-i18n="wizard.streaming.execution_mode">Execution mode</label>
|
|
||||||
<select id="streaming-mode" class="form-select">
|
|
||||||
<option value="unary">Unary</option>
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
<div class="form-group">
|
|
||||||
<label class="form-label" data-i18n="wizard.streaming.transport_behavior">Transport behavior</label>
|
|
||||||
<select id="streaming-transport-behavior" class="form-select">
|
|
||||||
<option value="request_response">Request / response</option>
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
<div class="form-group">
|
|
||||||
<label class="form-label" data-i18n="wizard.streaming.aggregation_mode">Aggregation mode</label>
|
|
||||||
<select id="streaming-aggregation-mode" class="form-select">
|
|
||||||
<option value="raw_items" data-i18n="wizard.streaming.aggregation.raw_items">Raw items</option>
|
|
||||||
<option value="summary_only" data-i18n="wizard.streaming.aggregation.summary_only">Summary only</option>
|
|
||||||
<option value="summary_plus_samples" data-i18n="wizard.streaming.aggregation.summary_plus_samples">Summary + samples</option>
|
|
||||||
<option value="stats" data-i18n="wizard.streaming.aggregation.stats">Stats</option>
|
|
||||||
<option value="latest_state" data-i18n="wizard.streaming.aggregation.latest_state">Latest state</option>
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div id="streaming-mode-help" class="form-hint" data-i18n="wizard.streaming.help.unary">Unary keeps the existing request-response model. Other modes create bounded stream-aware MCP tools.</div>
|
|
||||||
|
|
||||||
<div id="streaming-limits-grid" class="form-row">
|
|
||||||
<div class="form-group">
|
|
||||||
<label class="form-label" data-i18n="wizard.streaming.window_duration">Window duration (ms)</label>
|
|
||||||
<input id="streaming-window-duration-ms" class="form-input" type="number" min="1" step="1" placeholder="5000">
|
|
||||||
</div>
|
|
||||||
<div class="form-group">
|
|
||||||
<label class="form-label" data-i18n="wizard.streaming.poll_interval">Poll interval (ms)</label>
|
|
||||||
<input id="streaming-poll-interval-ms" class="form-input" type="number" min="1" step="1" placeholder="2000">
|
|
||||||
</div>
|
|
||||||
<div class="form-group">
|
|
||||||
<label class="form-label" data-i18n="wizard.streaming.upstream_timeout">Upstream timeout (ms)</label>
|
|
||||||
<input id="streaming-upstream-timeout-ms" class="form-input" type="number" min="1" step="1" placeholder="10000">
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="form-row">
|
|
||||||
<div class="form-group">
|
|
||||||
<label class="form-label" data-i18n="wizard.streaming.idle_timeout">Idle timeout (ms)</label>
|
|
||||||
<input id="streaming-idle-timeout-ms" class="form-input" type="number" min="1" step="1" placeholder="30000">
|
|
||||||
</div>
|
|
||||||
<div class="form-group">
|
|
||||||
<label class="form-label" data-i18n="wizard.streaming.session_lifetime">Session lifetime (ms)</label>
|
|
||||||
<input id="streaming-session-lifetime-ms" class="form-input" type="number" min="1" step="1" placeholder="300000">
|
|
||||||
</div>
|
|
||||||
<div class="form-group">
|
|
||||||
<label class="form-label" data-i18n="wizard.streaming.max_items">Max items</label>
|
|
||||||
<input id="streaming-max-items" class="form-input" type="number" min="1" step="1" placeholder="100">
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="form-row">
|
|
||||||
<div class="form-group">
|
|
||||||
<label class="form-label" data-i18n="wizard.streaming.max_bytes">Max bytes</label>
|
|
||||||
<input id="streaming-max-bytes" class="form-input" type="number" min="1" step="1" placeholder="65536">
|
|
||||||
</div>
|
|
||||||
<div class="form-group">
|
|
||||||
<label class="form-label" data-i18n="wizard.streaming.max_field_length">Max field length</label>
|
|
||||||
<input id="streaming-max-field-length" class="form-input" type="number" min="1" step="1" placeholder="512">
|
|
||||||
</div>
|
|
||||||
<div class="form-group">
|
|
||||||
<label class="form-label" data-i18n="wizard.streaming.sampling_rate">Sampling rate</label>
|
|
||||||
<input id="streaming-sampling-rate" class="form-input" type="number" min="0" max="1" step="0.1" placeholder="1.0">
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="form-row">
|
|
||||||
<div class="form-group">
|
|
||||||
<label class="form-label" data-i18n="wizard.streaming.items_path">Items path</label>
|
|
||||||
<input id="streaming-items-path" class="form-input input-mono" type="text" placeholder="$.items">
|
|
||||||
</div>
|
|
||||||
<div class="form-group">
|
|
||||||
<label class="form-label" data-i18n="wizard.streaming.summary_path">Summary path</label>
|
|
||||||
<input id="streaming-summary-path" class="form-input input-mono" type="text" placeholder="$.summary">
|
|
||||||
</div>
|
|
||||||
<div class="form-group">
|
|
||||||
<label class="form-label" data-i18n="wizard.streaming.done_path">Done path</label>
|
|
||||||
<input id="streaming-done-path" class="form-input input-mono" type="text" placeholder="$.done">
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="form-row">
|
|
||||||
<div class="form-group">
|
|
||||||
<label class="form-label" data-i18n="wizard.streaming.cursor_path">Cursor path</label>
|
|
||||||
<input id="streaming-cursor-path" class="form-input input-mono" type="text" placeholder="$.cursor">
|
|
||||||
</div>
|
|
||||||
<div class="form-group">
|
|
||||||
<label class="form-label" data-i18n="wizard.streaming.status_path">Status path</label>
|
|
||||||
<input id="streaming-status-path" class="form-input input-mono" type="text" placeholder="$.status">
|
|
||||||
</div>
|
|
||||||
<div class="form-group">
|
|
||||||
<label class="form-label" data-i18n="wizard.streaming.redacted_paths">Redacted paths</label>
|
|
||||||
<textarea id="streaming-redacted-paths" class="form-textarea code-textarea" rows="4" spellcheck="false" placeholder="$.items[*].token $.summary.secret"></textarea>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="form-row">
|
|
||||||
<label class="checkbox-pill"><input id="streaming-truncate-item-fields" type="checkbox"> <span data-i18n="wizard.streaming.truncate_item_fields">Truncate item fields</span></label>
|
|
||||||
<label class="checkbox-pill"><input id="streaming-drop-duplicates" type="checkbox"> <span data-i18n="wizard.streaming.drop_duplicates">Drop duplicates</span></label>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div id="streaming-tool-family-block" hidden>
|
|
||||||
<div class="section-divider" style="margin: 8px 0 16px;">
|
|
||||||
<span class="section-divider-label" data-i18n="wizard.streaming.tool_family">Tool family</span>
|
|
||||||
<div class="section-divider-line"></div>
|
|
||||||
</div>
|
|
||||||
<div class="form-row">
|
|
||||||
<div class="form-group">
|
|
||||||
<label class="form-label" data-i18n="wizard.streaming.start_tool_name">Start tool name</label>
|
|
||||||
<input id="streaming-start-tool-name" class="form-input input-mono" type="text" placeholder="logs_follow_start">
|
|
||||||
</div>
|
|
||||||
<div class="form-group">
|
|
||||||
<label class="form-label" data-i18n="wizard.streaming.poll_tool_name">Poll tool name</label>
|
|
||||||
<input id="streaming-poll-tool-name" class="form-input input-mono" type="text" placeholder="logs_follow_poll">
|
|
||||||
</div>
|
|
||||||
<div class="form-group">
|
|
||||||
<label class="form-label" data-i18n="wizard.streaming.stop_tool_name">Stop tool name</label>
|
|
||||||
<input id="streaming-stop-tool-name" class="form-input input-mono" type="text" placeholder="logs_follow_stop">
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="form-row" id="streaming-async-tool-family-row" hidden>
|
|
||||||
<div class="form-group">
|
|
||||||
<label class="form-label" data-i18n="wizard.streaming.status_tool_name">Status tool name</label>
|
|
||||||
<input id="streaming-status-tool-name" class="form-input input-mono" type="text" placeholder="deploy_status">
|
|
||||||
</div>
|
|
||||||
<div class="form-group">
|
|
||||||
<label class="form-label" data-i18n="wizard.streaming.result_tool_name">Result tool name</label>
|
|
||||||
<input id="streaming-result-tool-name" class="form-input input-mono" type="text" placeholder="deploy_result">
|
|
||||||
</div>
|
|
||||||
<div class="form-group">
|
|
||||||
<label class="form-label" data-i18n="wizard.streaming.cancel_tool_name">Cancel tool name</label>
|
|
||||||
<input id="streaming-cancel-tool-name" class="form-input input-mono" type="text" placeholder="deploy_cancel">
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="section-divider" style="margin-bottom: 16px;">
|
<div class="section-divider" style="margin-bottom: 16px;">
|
||||||
<span class="section-divider-label" data-i18n="wizard.step5.live_title">Live validation and publishing</span>
|
<span class="section-divider-label" data-i18n="wizard.step5.live_title">Live validation and publishing</span>
|
||||||
<div class="section-divider-line"></div>
|
<div class="section-divider-line"></div>
|
||||||
@@ -375,31 +206,6 @@ tls:
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div id="wizard-grpc-live-tools" class="config-card" hidden style="margin-bottom: 20px;">
|
|
||||||
<div class="config-card-header">
|
|
||||||
<div class="config-card-header-icon">
|
|
||||||
<svg width="13" height="13" viewBox="0 0 16 16" fill="none" stroke="var(--text-secondary)" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round">
|
|
||||||
<path d="M9 2H4a1 1 0 00-1 1v10a1 1 0 001 1h8a1 1 0 001-1V6z"/>
|
|
||||||
<path d="M9 2v4h4"/>
|
|
||||||
</svg>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<div class="config-card-title" data-i18n="wizard.step5.grpc_descriptor_title">gRPC descriptor set</div>
|
|
||||||
<div class="config-card-subtitle" data-i18n="wizard.step5.grpc_descriptor_subtitle">Upload a compiled descriptor set and discover unary methods from the saved draft.</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="config-card-body" style="gap: 16px;">
|
|
||||||
<div style="display:flex; gap: 8px; align-items:center; flex-wrap: wrap;">
|
|
||||||
<input id="wizard-descriptor-set-input" type="file" accept=".bin,.pb,.desc,.protoset,application/octet-stream" style="display:none">
|
|
||||||
<button id="wizard-select-descriptor-set" class="btn-ghost-sm" type="button" data-i18n="wizard.step5.choose_descriptor">Choose descriptor set</button>
|
|
||||||
<button id="wizard-upload-descriptor-set" class="btn-primary-sm" type="button" data-i18n="wizard.step5.upload_discover">Upload and discover services</button>
|
|
||||||
<span id="wizard-descriptor-set-name" style="color: var(--text-muted); font-size: 12px;" data-i18n="wizard.step5.no_descriptor">No descriptor set selected</span>
|
|
||||||
</div>
|
|
||||||
<div id="wizard-descriptor-services-summary" class="form-hint"></div>
|
|
||||||
<div id="wizard-descriptor-services-list" style="display:grid; gap: 10px;"></div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="config-card" style="margin-bottom: 20px;">
|
<div class="config-card" style="margin-bottom: 20px;">
|
||||||
<div class="config-card-header">
|
<div class="config-card-header">
|
||||||
<div class="config-card-header-icon">
|
<div class="config-card-header-icon">
|
||||||
|
|||||||
+2
-10
@@ -466,19 +466,11 @@ document.addEventListener('alpine:init', function() {
|
|||||||
},
|
},
|
||||||
|
|
||||||
protocolBadge(operation) {
|
protocolBadge(operation) {
|
||||||
if (operation.protocol === 'rest') return 'badge badge-rest';
|
return 'badge badge-rest';
|
||||||
if (operation.protocol === 'graphql') return 'badge badge-graphql';
|
|
||||||
if (operation.protocol === 'websocket') return 'badge badge-rest';
|
|
||||||
if (operation.protocol === 'soap') return 'badge badge-rest';
|
|
||||||
return 'badge badge-grpc';
|
|
||||||
},
|
},
|
||||||
|
|
||||||
protocolLabel(operation) {
|
protocolLabel(operation) {
|
||||||
if (operation.protocol === 'rest') return 'REST';
|
return 'REST';
|
||||||
if (operation.protocol === 'graphql') return 'GQL';
|
|
||||||
if (operation.protocol === 'websocket') return 'WS';
|
|
||||||
if (operation.protocol === 'soap') return 'SOAP';
|
|
||||||
return 'gRPC';
|
|
||||||
},
|
},
|
||||||
|
|
||||||
formatDate(dateStr) {
|
formatDate(dateStr) {
|
||||||
|
|||||||
@@ -246,46 +246,6 @@
|
|||||||
uploadOutputSample: function(workspaceId, operationId, sample) {
|
uploadOutputSample: function(workspaceId, operationId, sample) {
|
||||||
return post('/workspaces/' + encodeURIComponent(workspaceId) + '/operations/' + encodeURIComponent(operationId) + '/samples/output-json', sample);
|
return post('/workspaces/' + encodeURIComponent(workspaceId) + '/operations/' + encodeURIComponent(operationId) + '/samples/output-json', sample);
|
||||||
},
|
},
|
||||||
uploadProtoFile: function(workspaceId, operationId, content, fileName) {
|
|
||||||
return postBytes(
|
|
||||||
'/workspaces/' + encodeURIComponent(workspaceId) + '/operations/' + encodeURIComponent(operationId) + '/descriptors/proto',
|
|
||||||
content,
|
|
||||||
fileName
|
|
||||||
);
|
|
||||||
},
|
|
||||||
uploadDescriptorSet: function(workspaceId, operationId, content, fileName) {
|
|
||||||
return postBytes(
|
|
||||||
'/workspaces/' + encodeURIComponent(workspaceId) + '/operations/' + encodeURIComponent(operationId) + '/descriptors/descriptor-set',
|
|
||||||
content,
|
|
||||||
fileName
|
|
||||||
);
|
|
||||||
},
|
|
||||||
uploadWsdlFile: function(workspaceId, operationId, content, fileName) {
|
|
||||||
return postBytes(
|
|
||||||
'/workspaces/' + encodeURIComponent(workspaceId) + '/operations/' + encodeURIComponent(operationId) + '/descriptors/wsdl',
|
|
||||||
content,
|
|
||||||
fileName
|
|
||||||
);
|
|
||||||
},
|
|
||||||
uploadXsdFile: function(workspaceId, operationId, content, fileName) {
|
|
||||||
return postBytes(
|
|
||||||
'/workspaces/' + encodeURIComponent(workspaceId) + '/operations/' + encodeURIComponent(operationId) + '/descriptors/xsd',
|
|
||||||
content,
|
|
||||||
fileName
|
|
||||||
);
|
|
||||||
},
|
|
||||||
listGrpcServices: function(workspaceId, operationId, version) {
|
|
||||||
return get(
|
|
||||||
'/workspaces/' + encodeURIComponent(workspaceId) + '/operations/' + encodeURIComponent(operationId) + '/grpc/services'
|
|
||||||
+ query({ version: version })
|
|
||||||
);
|
|
||||||
},
|
|
||||||
listSoapServices: function(workspaceId, operationId, version) {
|
|
||||||
return get(
|
|
||||||
'/workspaces/' + encodeURIComponent(workspaceId) + '/operations/' + encodeURIComponent(operationId) + '/soap/services'
|
|
||||||
+ query({ version: version })
|
|
||||||
);
|
|
||||||
},
|
|
||||||
generateDraft: function(workspaceId, operationId, payload) {
|
generateDraft: function(workspaceId, operationId, payload) {
|
||||||
return post('/workspaces/' + encodeURIComponent(workspaceId) + '/operations/' + encodeURIComponent(operationId) + '/drafts/generate', payload || {});
|
return post('/workspaces/' + encodeURIComponent(workspaceId) + '/operations/' + encodeURIComponent(operationId) + '/drafts/generate', payload || {});
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -475,13 +475,7 @@ document.addEventListener('alpine:init', function() {
|
|||||||
},
|
},
|
||||||
|
|
||||||
protocolLabel(operation) {
|
protocolLabel(operation) {
|
||||||
if (operation.protocol === 'rest') {
|
|
||||||
return operation.method ? ('REST · ' + operation.method) : 'REST';
|
return operation.method ? ('REST · ' + operation.method) : 'REST';
|
||||||
}
|
|
||||||
if (operation.protocol === 'graphql') return 'GraphQL';
|
|
||||||
if (operation.protocol === 'websocket') return 'WebSocket';
|
|
||||||
if (operation.protocol === 'soap') return 'SOAP';
|
|
||||||
return 'gRPC';
|
|
||||||
},
|
},
|
||||||
|
|
||||||
protocolClass(operation) {
|
protocolClass(operation) {
|
||||||
|
|||||||
@@ -66,34 +66,10 @@ document.addEventListener('DOMContentLoaded', function () {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function protocolColor(protocol) {
|
function protocolColor(protocol) {
|
||||||
if (protocol === 'graphql' || protocol === 'Graphql') {
|
|
||||||
return 'var(--purple)';
|
|
||||||
}
|
|
||||||
if (protocol === 'grpc' || protocol === 'Grpc') {
|
|
||||||
return 'var(--accent)';
|
|
||||||
}
|
|
||||||
if (protocol === 'websocket' || protocol === 'Websocket') {
|
|
||||||
return 'var(--teal)';
|
|
||||||
}
|
|
||||||
if (protocol === 'soap' || protocol === 'Soap') {
|
|
||||||
return 'var(--blue)';
|
|
||||||
}
|
|
||||||
return 'var(--blue)';
|
return 'var(--blue)';
|
||||||
}
|
}
|
||||||
|
|
||||||
function protocolLabel(protocol) {
|
function protocolLabel(protocol) {
|
||||||
if (protocol === 'graphql' || protocol === 'Graphql') {
|
|
||||||
return 'GraphQL';
|
|
||||||
}
|
|
||||||
if (protocol === 'grpc' || protocol === 'Grpc') {
|
|
||||||
return 'gRPC';
|
|
||||||
}
|
|
||||||
if (protocol === 'websocket' || protocol === 'Websocket') {
|
|
||||||
return 'WebSocket';
|
|
||||||
}
|
|
||||||
if (protocol === 'soap' || protocol === 'Soap') {
|
|
||||||
return 'SOAP';
|
|
||||||
}
|
|
||||||
return 'REST';
|
return 'REST';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+1
-221
@@ -1,6 +1,3 @@
|
|||||||
var updateStreamingModeOptions = window.CrankStreamingForm.updateStreamingModeOptions;
|
|
||||||
var updateStreamingConfigVisibility = window.CrankStreamingForm.updateStreamingConfigVisibility;
|
|
||||||
|
|
||||||
function buildToolDescription() {
|
function buildToolDescription() {
|
||||||
return {
|
return {
|
||||||
title: textValue('tool-title') || textValue('tool-display-name') || textValue('tool-name'),
|
title: textValue('tool-title') || textValue('tool-display-name') || textValue('tool-name'),
|
||||||
@@ -68,18 +65,7 @@ function setEditModePresentation() {
|
|||||||
function selectProtocol(protocol) {
|
function selectProtocol(protocol) {
|
||||||
wizardProtocol = protocol || 'rest';
|
wizardProtocol = protocol || 'rest';
|
||||||
document.querySelectorAll('.protocol-card').forEach(function(card) {
|
document.querySelectorAll('.protocol-card').forEach(function(card) {
|
||||||
var proto = card.dataset.protocol
|
var proto = card.dataset.protocol || 'rest';
|
||||||
|| (card.classList.contains('rest')
|
|
||||||
? 'rest'
|
|
||||||
: card.classList.contains('graphql')
|
|
||||||
? 'graphql'
|
|
||||||
: card.classList.contains('grpc')
|
|
||||||
? 'grpc'
|
|
||||||
: card.classList.contains('websocket')
|
|
||||||
? 'websocket'
|
|
||||||
: card.classList.contains('soap')
|
|
||||||
? 'soap'
|
|
||||||
: 'rest');
|
|
||||||
var selected = proto === wizardProtocol;
|
var selected = proto === wizardProtocol;
|
||||||
card.classList.toggle('selected', selected);
|
card.classList.toggle('selected', selected);
|
||||||
card.setAttribute('aria-checked', selected ? 'true' : 'false');
|
card.setAttribute('aria-checked', selected ? 'true' : 'false');
|
||||||
@@ -101,32 +87,11 @@ function bindWizardLiveActions() {
|
|||||||
bindLiveAction('wizard-export-yaml', tKey('wizard.busy.export_yaml'), exportWizardYaml);
|
bindLiveAction('wizard-export-yaml', tKey('wizard.busy.export_yaml'), exportWizardYaml);
|
||||||
bindLiveAction('wizard-import-yaml', tKey('wizard.busy.import_yaml'), importWizardYaml);
|
bindLiveAction('wizard-import-yaml', tKey('wizard.busy.import_yaml'), importWizardYaml);
|
||||||
bindLiveAction('wizard-publish-operation', tKey('wizard.busy.publish'), publishWizardOperation);
|
bindLiveAction('wizard-publish-operation', tKey('wizard.busy.publish'), publishWizardOperation);
|
||||||
bindClick('wizard-select-descriptor-set', function() {
|
|
||||||
var input = document.getElementById('wizard-descriptor-set-input');
|
|
||||||
if (input) input.click();
|
|
||||||
});
|
|
||||||
bindLiveAction('wizard-upload-descriptor-set', tKey('wizard.busy.descriptor'), uploadDescriptorSetAndDiscover);
|
|
||||||
bindClick('wizard-select-soap-wsdl', function() {
|
|
||||||
var input = document.getElementById('wizard-soap-wsdl-input');
|
|
||||||
if (input) input.click();
|
|
||||||
});
|
|
||||||
bindClick('wizard-select-soap-xsd', function() {
|
|
||||||
var input = document.getElementById('wizard-soap-xsd-input');
|
|
||||||
if (input) input.click();
|
|
||||||
});
|
|
||||||
bindLiveAction('wizard-upload-soap-contracts', tKey('wizard.busy.wsdl'), uploadSoapArtifactsAndInspect);
|
|
||||||
bindClick('wizard-import-yaml-file-trigger', function() {
|
bindClick('wizard-import-yaml-file-trigger', function() {
|
||||||
var input = document.getElementById('wizard-import-yaml-file');
|
var input = document.getElementById('wizard-import-yaml-file');
|
||||||
if (input) input.click();
|
if (input) input.click();
|
||||||
});
|
});
|
||||||
|
|
||||||
var descriptorInput = document.getElementById('wizard-descriptor-set-input');
|
|
||||||
if (descriptorInput) descriptorInput.addEventListener('change', handleDescriptorSetSelection);
|
|
||||||
var soapWsdlInput = document.getElementById('wizard-soap-wsdl-input');
|
|
||||||
if (soapWsdlInput) soapWsdlInput.addEventListener('change', handleSoapWsdlSelection);
|
|
||||||
var soapXsdInput = document.getElementById('wizard-soap-xsd-input');
|
|
||||||
if (soapXsdInput) soapXsdInput.addEventListener('change', handleSoapXsdSelection);
|
|
||||||
|
|
||||||
var yamlInput = document.getElementById('wizard-import-yaml-file');
|
var yamlInput = document.getElementById('wizard-import-yaml-file');
|
||||||
if (yamlInput) yamlInput.addEventListener('change', handleImportYamlFileSelection);
|
if (yamlInput) yamlInput.addEventListener('change', handleImportYamlFileSelection);
|
||||||
}
|
}
|
||||||
@@ -183,10 +148,6 @@ async function runWizardLiveAction(button, busyLabel, handler) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function updateWizardProtocolVisibility() {
|
function updateWizardProtocolVisibility() {
|
||||||
var grpcTools = document.getElementById('wizard-grpc-live-tools');
|
|
||||||
if (grpcTools) grpcTools.hidden = wizardProtocol !== 'grpc';
|
|
||||||
updateStreamingModeOptions();
|
|
||||||
updateStreamingConfigVisibility();
|
|
||||||
if (typeof window.renderEditionCapabilityHints === 'function'
|
if (typeof window.renderEditionCapabilityHints === 'function'
|
||||||
&& window.CrankWizardState
|
&& window.CrankWizardState
|
||||||
&& typeof window.CrankWizardState.currentEditionCapabilities === 'function') {
|
&& typeof window.CrankWizardState.currentEditionCapabilities === 'function') {
|
||||||
@@ -253,82 +214,6 @@ async function refreshCurrentOperationState() {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
async function uploadPendingGrpcArtifacts(payload) {
|
|
||||||
if (wizardProtocol !== 'grpc' || !wizardEditId) return;
|
|
||||||
|
|
||||||
var descriptorResponse = null;
|
|
||||||
|
|
||||||
if (wizardProtoUpload) {
|
|
||||||
descriptorResponse = await window.CrankApi.uploadProtoFile(
|
|
||||||
wizardWorkspaceId,
|
|
||||||
wizardEditId,
|
|
||||||
new TextEncoder().encode(wizardProtoUpload.content),
|
|
||||||
wizardProtoUpload.fileName
|
|
||||||
);
|
|
||||||
wizardProtoUpload = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (wizardDescriptorSetUpload) {
|
|
||||||
descriptorResponse = await window.CrankApi.uploadDescriptorSet(
|
|
||||||
wizardWorkspaceId,
|
|
||||||
wizardEditId,
|
|
||||||
wizardDescriptorSetUpload.bytes,
|
|
||||||
wizardDescriptorSetUpload.fileName
|
|
||||||
);
|
|
||||||
wizardDescriptorSetUpload = null;
|
|
||||||
var descriptorName = document.getElementById('wizard-descriptor-set-name');
|
|
||||||
if (descriptorName) descriptorName.textContent = tKey('wizard.descriptor.uploaded');
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!descriptorResponse) return;
|
|
||||||
|
|
||||||
payload.target.descriptor_ref = descriptorResponse.descriptor_id;
|
|
||||||
payload.target.descriptor_set_b64 = '';
|
|
||||||
await window.CrankApi.updateOperation(
|
|
||||||
wizardWorkspaceId,
|
|
||||||
wizardEditId,
|
|
||||||
updateOperationPayload(payload)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
async function uploadPendingSoapArtifacts(payload) {
|
|
||||||
if (wizardProtocol !== 'soap' || !wizardEditId) return;
|
|
||||||
|
|
||||||
var wsdlResponse = null;
|
|
||||||
if (wizardSoapWsdlUpload) {
|
|
||||||
wsdlResponse = await window.CrankApi.uploadWsdlFile(
|
|
||||||
wizardWorkspaceId,
|
|
||||||
wizardEditId,
|
|
||||||
wizardSoapWsdlUpload.bytes,
|
|
||||||
wizardSoapWsdlUpload.fileName
|
|
||||||
);
|
|
||||||
wizardSoapWsdlUpload = null;
|
|
||||||
var wsdlName = document.getElementById('wizard-soap-wsdl-name');
|
|
||||||
if (wsdlName) wsdlName.textContent = tKey('wizard.step3.soap.wsdl_uploaded');
|
|
||||||
}
|
|
||||||
|
|
||||||
if (wizardSoapXsdUpload) {
|
|
||||||
await window.CrankApi.uploadXsdFile(
|
|
||||||
wizardWorkspaceId,
|
|
||||||
wizardEditId,
|
|
||||||
wizardSoapXsdUpload.bytes,
|
|
||||||
wizardSoapXsdUpload.fileName
|
|
||||||
);
|
|
||||||
wizardSoapXsdUpload = null;
|
|
||||||
var xsdName = document.getElementById('wizard-soap-xsd-name');
|
|
||||||
if (xsdName) xsdName.textContent = tKey('wizard.step3.soap.xsd_uploaded');
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!wsdlResponse) return;
|
|
||||||
|
|
||||||
payload.target.wsdl_ref = wsdlResponse.descriptor_id;
|
|
||||||
await window.CrankApi.updateOperation(
|
|
||||||
wizardWorkspaceId,
|
|
||||||
wizardEditId,
|
|
||||||
updateOperationPayload(payload)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
async function persistCurrentDraft(stayOnPage) {
|
async function persistCurrentDraft(stayOnPage) {
|
||||||
if (!wizardWorkspaceId) {
|
if (!wizardWorkspaceId) {
|
||||||
throw new Error(tKey('wizard.error.no_workspace'));
|
throw new Error(tKey('wizard.error.no_workspace'));
|
||||||
@@ -360,8 +245,6 @@ async function persistCurrentDraft(stayOnPage) {
|
|||||||
if (toolNameInput) toolNameInput.disabled = true;
|
if (toolNameInput) toolNameInput.disabled = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
await uploadPendingGrpcArtifacts(payload);
|
|
||||||
await uploadPendingSoapArtifacts(payload);
|
|
||||||
await refreshCurrentOperationState();
|
await refreshCurrentOperationState();
|
||||||
|
|
||||||
if (stayOnPage) {
|
if (stayOnPage) {
|
||||||
@@ -395,109 +278,6 @@ function setTextareaValue(id, value) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function describeWizardTestResult(result) {
|
function describeWizardTestResult(result) {
|
||||||
if (wizardProtocol === 'websocket') {
|
|
||||||
if (!result.ok && result.errors && result.errors.length) {
|
|
||||||
var websocketError = result.errors[0] || {};
|
|
||||||
var websocketMessage = websocketError.message || '';
|
|
||||||
var websocketBody = tKey('wizard.test.websocket_failed_body');
|
|
||||||
if (/reconnect policy exhausted/i.test(websocketMessage)) {
|
|
||||||
websocketBody = tKey('wizard.test.websocket_failed_reconnect_body');
|
|
||||||
} else if (/close frame before collection completed|closed before collection completed/i.test(websocketMessage)) {
|
|
||||||
websocketBody = tKey('wizard.test.websocket_failed_closed_body');
|
|
||||||
}
|
|
||||||
return {
|
|
||||||
title: tKey('wizard.test.websocket_failed'),
|
|
||||||
body: websocketBody,
|
|
||||||
isError: true,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
if (result.stream_session && result.stream_session.session_id) {
|
|
||||||
return {
|
|
||||||
title: tKey('wizard.test.websocket_session_started'),
|
|
||||||
body: tfKey('wizard.test.websocket_session_started_body', {
|
|
||||||
session_id: result.stream_session.session_id,
|
|
||||||
poll_after_ms: result.stream_session.poll_after_ms,
|
|
||||||
}),
|
|
||||||
isError: false,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
if (result.async_job && result.async_job.job_id) {
|
|
||||||
return {
|
|
||||||
title: tKey('wizard.test.websocket_async_job_started'),
|
|
||||||
body: tfKey('wizard.test.websocket_async_job_started_body', {
|
|
||||||
job_id: result.async_job.job_id,
|
|
||||||
}),
|
|
||||||
isError: false,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
if (result.window) {
|
|
||||||
var itemCount = Array.isArray(result.response_preview && result.response_preview.items)
|
|
||||||
? result.response_preview.items.length
|
|
||||||
: 0;
|
|
||||||
var websocketBodyParts = [
|
|
||||||
tfKey('wizard.test.websocket_window_completed_body', { items: itemCount })
|
|
||||||
];
|
|
||||||
if (result.window.truncated) {
|
|
||||||
websocketBodyParts.push(tKey('wizard.test.websocket_window_truncated_note'));
|
|
||||||
}
|
|
||||||
if (result.window.has_more) {
|
|
||||||
websocketBodyParts.push(tKey('wizard.test.websocket_window_more_note'));
|
|
||||||
}
|
|
||||||
if (!result.window.window_complete) {
|
|
||||||
websocketBodyParts.push(tKey('wizard.test.websocket_window_incomplete_note'));
|
|
||||||
}
|
|
||||||
return {
|
|
||||||
title: result.ok ? tKey('wizard.test.websocket_window_completed') : tKey('wizard.test.websocket_failed'),
|
|
||||||
body: websocketBodyParts.join(' '),
|
|
||||||
isError: !result.ok,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (result.stream_session && result.stream_session.session_id) {
|
|
||||||
return {
|
|
||||||
title: tKey('wizard.test.session_started'),
|
|
||||||
body: tfKey('wizard.test.session_started_body', {
|
|
||||||
session_id: result.stream_session.session_id,
|
|
||||||
poll_after_ms: result.stream_session.poll_after_ms
|
|
||||||
}),
|
|
||||||
isError: false,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
if (result.async_job && result.async_job.job_id) {
|
|
||||||
return {
|
|
||||||
title: tKey('wizard.test.async_job_started'),
|
|
||||||
body: tfKey('wizard.test.async_job_started_body', {
|
|
||||||
job_id: result.async_job.job_id
|
|
||||||
}),
|
|
||||||
isError: false,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
if (result.window) {
|
|
||||||
var bodyParts = [
|
|
||||||
tKey('wizard.test.window_completed_body')
|
|
||||||
];
|
|
||||||
if (result.window.truncated) {
|
|
||||||
bodyParts.push(tKey('wizard.test.window_truncated_note'));
|
|
||||||
}
|
|
||||||
if (result.window.has_more) {
|
|
||||||
bodyParts.push(tKey('wizard.test.window_more_note'));
|
|
||||||
}
|
|
||||||
if (!result.window.window_complete) {
|
|
||||||
bodyParts.push(tKey('wizard.test.window_incomplete_note'));
|
|
||||||
}
|
|
||||||
return {
|
|
||||||
title: result.ok ? tKey('wizard.test.window_completed') : tKey('wizard.test.failed'),
|
|
||||||
body: bodyParts.join(' '),
|
|
||||||
isError: !result.ok,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
title: result.ok ? tKey('wizard.test.completed') : tKey('wizard.test.failed'),
|
title: result.ok ? tKey('wizard.test.completed') : tKey('wizard.test.failed'),
|
||||||
body: result.ok ? tKey('wizard.test.completed_body') : tKey('wizard.test.failed_body'),
|
body: result.ok ? tKey('wizard.test.completed_body') : tKey('wizard.test.failed_body'),
|
||||||
|
|||||||
+1
-254
@@ -45,10 +45,6 @@ function convertJsonSchemaToCrankSchema(node, requiredNames) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function mappingRootForProtocol(protocol) {
|
function mappingRootForProtocol(protocol) {
|
||||||
if (protocol === 'graphql') return '$.request.variables';
|
|
||||||
if (protocol === 'grpc') return '$.request.message';
|
|
||||||
if (protocol === 'soap') return '$.request.body';
|
|
||||||
if (protocol === 'websocket') return '$.request.body';
|
|
||||||
return '$.request.body';
|
return '$.request.body';
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -107,46 +103,9 @@ function parseExecutionConfig(text) {
|
|||||||
auth_profile_ref: authProfileRef,
|
auth_profile_ref: authProfileRef,
|
||||||
headers: headers,
|
headers: headers,
|
||||||
protocol_options: null,
|
protocol_options: null,
|
||||||
|
streaming: null,
|
||||||
};
|
};
|
||||||
|
|
||||||
if (wizardProtocol === 'grpc') {
|
|
||||||
var useTls = false;
|
|
||||||
if (value.protocol_options && value.protocol_options.grpc) {
|
|
||||||
useTls = !!value.protocol_options.grpc.use_tls;
|
|
||||||
} else if (value.tls) {
|
|
||||||
useTls = !!value.tls.verify;
|
|
||||||
}
|
|
||||||
config.protocol_options = { grpc: { use_tls: useTls } };
|
|
||||||
} else if (wizardProtocol === 'websocket') {
|
|
||||||
var websocketProtocolOptions = value.protocol_options && value.protocol_options.websocket
|
|
||||||
? value.protocol_options.websocket
|
|
||||||
: {};
|
|
||||||
var heartbeatInterval = websocketProtocolOptions.heartbeat_interval_ms != null
|
|
||||||
? Number(websocketProtocolOptions.heartbeat_interval_ms)
|
|
||||||
: numericValueOrNull('websocket-heartbeat-interval-ms');
|
|
||||||
var reconnectAttempts = websocketProtocolOptions.reconnect_max_attempts != null
|
|
||||||
? Number(websocketProtocolOptions.reconnect_max_attempts)
|
|
||||||
: numericValueOrNull('websocket-reconnect-max-attempts');
|
|
||||||
var reconnectBackoff = websocketProtocolOptions.reconnect_backoff_ms != null
|
|
||||||
? Number(websocketProtocolOptions.reconnect_backoff_ms)
|
|
||||||
: numericValueOrNull('websocket-reconnect-backoff-ms');
|
|
||||||
var runtimeOptions = {};
|
|
||||||
if (Number.isFinite(heartbeatInterval) && heartbeatInterval > 0) {
|
|
||||||
runtimeOptions.heartbeat_interval_ms = heartbeatInterval;
|
|
||||||
}
|
|
||||||
if (Number.isFinite(reconnectAttempts) && reconnectAttempts >= 0) {
|
|
||||||
runtimeOptions.reconnect_max_attempts = reconnectAttempts;
|
|
||||||
}
|
|
||||||
if (Number.isFinite(reconnectBackoff) && reconnectBackoff >= 0) {
|
|
||||||
runtimeOptions.reconnect_backoff_ms = reconnectBackoff;
|
|
||||||
}
|
|
||||||
if (Object.keys(runtimeOptions).length) {
|
|
||||||
config.protocol_options = { websocket: runtimeOptions };
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
config.streaming = collectStreamingConfig();
|
|
||||||
|
|
||||||
return config;
|
return config;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -156,9 +115,6 @@ function currentUpstream() {
|
|||||||
|
|
||||||
var customName = textValue('new-upstream-name');
|
var customName = textValue('new-upstream-name');
|
||||||
var customUrl = textValue('new-upstream-url');
|
var customUrl = textValue('new-upstream-url');
|
||||||
if (!customUrl && wizardProtocol === 'soap') {
|
|
||||||
customUrl = textValue('soap-endpoint-override');
|
|
||||||
}
|
|
||||||
if (!customUrl) return null;
|
if (!customUrl) return null;
|
||||||
|
|
||||||
var authMode = textValue('new-upstream-auth-mode') || 'none';
|
var authMode = textValue('new-upstream-auth-mode') || 'none';
|
||||||
@@ -182,72 +138,12 @@ function joinUrl(baseUrl, path) {
|
|||||||
return baseUrl.replace(/\/+$/, '') + '/' + path.replace(/^\/+/, '');
|
return baseUrl.replace(/\/+$/, '') + '/' + path.replace(/^\/+/, '');
|
||||||
}
|
}
|
||||||
|
|
||||||
function selectedGraphqlOperationType() {
|
|
||||||
var active = document.querySelector('.gql-type-card.active');
|
|
||||||
return active ? active.dataset.gqlType : 'query';
|
|
||||||
}
|
|
||||||
|
|
||||||
function graphqlTopLevelField(queryText) {
|
|
||||||
var match = queryText.replace(/#[^\n]*/g, '').match(/\{\s*([A-Za-z_][A-Za-z0-9_]*)/);
|
|
||||||
return match ? match[1] : null;
|
|
||||||
}
|
|
||||||
|
|
||||||
function graphqlOperationName(queryText) {
|
|
||||||
var match = queryText.match(/\b(query|mutation)\s+([A-Za-z_][A-Za-z0-9_]*)/);
|
|
||||||
return match ? match[2] : textValue('tool-name');
|
|
||||||
}
|
|
||||||
|
|
||||||
function parseGrpcRoute(route) {
|
|
||||||
var normalized = route.replace(/^\/+/, '');
|
|
||||||
var parts = normalized.split('/');
|
|
||||||
if (parts.length !== 2) {
|
|
||||||
return {
|
|
||||||
package: '',
|
|
||||||
service: '',
|
|
||||||
method: '',
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
var servicePart = parts[0];
|
|
||||||
var method = parts[1];
|
|
||||||
var serviceSegments = servicePart.split('.');
|
|
||||||
var service = serviceSegments.pop() || '';
|
|
||||||
var packageName = serviceSegments.join('.');
|
|
||||||
|
|
||||||
return {
|
|
||||||
package: packageName,
|
|
||||||
service: service,
|
|
||||||
method: method,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
function parseHeaderMap(text) {
|
function parseHeaderMap(text) {
|
||||||
if (!text || !text.trim()) return {};
|
if (!text || !text.trim()) return {};
|
||||||
var value = parseStructuredText(text);
|
var value = parseStructuredText(text);
|
||||||
return value && typeof value === 'object' ? value : {};
|
return value && typeof value === 'object' ? value : {};
|
||||||
}
|
}
|
||||||
|
|
||||||
function numericValueOrNull(id) {
|
|
||||||
var value = textValue(id);
|
|
||||||
if (!value) return null;
|
|
||||||
var parsed = Number(value);
|
|
||||||
return Number.isFinite(parsed) && parsed > 0 ? parsed : null;
|
|
||||||
}
|
|
||||||
|
|
||||||
function stringValueOrNull(id) {
|
|
||||||
var value = textValue(id);
|
|
||||||
return value ? value : null;
|
|
||||||
}
|
|
||||||
|
|
||||||
function textareaLines(id) {
|
|
||||||
var value = textValue(id);
|
|
||||||
if (!value) return [];
|
|
||||||
return value
|
|
||||||
.split(/\r?\n/)
|
|
||||||
.map(function(line) { return line.trim(); })
|
|
||||||
.filter(Boolean);
|
|
||||||
}
|
|
||||||
|
|
||||||
function buildTarget() {
|
function buildTarget() {
|
||||||
var upstream = currentUpstream();
|
var upstream = currentUpstream();
|
||||||
if (!upstream || !upstream.url) {
|
if (!upstream || !upstream.url) {
|
||||||
@@ -255,8 +151,6 @@ function buildTarget() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var pathTemplate = textValue('endpoint-path') || '/';
|
var pathTemplate = textValue('endpoint-path') || '/';
|
||||||
|
|
||||||
if (wizardProtocol === 'rest') {
|
|
||||||
var activeMethod = document.querySelector('.method-card.active');
|
var activeMethod = document.querySelector('.method-card.active');
|
||||||
return {
|
return {
|
||||||
kind: 'rest',
|
kind: 'rest',
|
||||||
@@ -267,76 +161,6 @@ function buildTarget() {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
if (wizardProtocol === 'graphql') {
|
|
||||||
var queryTemplate = textValue('gql-query-editor');
|
|
||||||
var topField = graphqlTopLevelField(queryTemplate);
|
|
||||||
return {
|
|
||||||
kind: 'graphql',
|
|
||||||
endpoint: joinUrl(upstream.url, pathTemplate),
|
|
||||||
operation_type: selectedGraphqlOperationType(),
|
|
||||||
operation_name: graphqlOperationName(queryTemplate),
|
|
||||||
query_template: queryTemplate,
|
|
||||||
response_path: topField ? '$.response.body.data.' + topField : '$.response.body.data',
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
if (wizardProtocol === 'soap') {
|
|
||||||
var endpointOverride = textValue('soap-endpoint-override') || upstream.url;
|
|
||||||
var existingWsdlRef = wizardCurrentVersion && wizardCurrentVersion.target && wizardCurrentVersion.target.kind === 'soap'
|
|
||||||
? wizardCurrentVersion.target.wsdl_ref
|
|
||||||
: 'sample_wsdl_pending';
|
|
||||||
|
|
||||||
return {
|
|
||||||
kind: 'soap',
|
|
||||||
wsdl_ref: existingWsdlRef,
|
|
||||||
service_name: textValue('soap-service-name') || 'Service',
|
|
||||||
port_name: textValue('soap-port-name') || 'Port',
|
|
||||||
operation_name: textValue('soap-operation-name') || 'Operation',
|
|
||||||
endpoint_override: endpointOverride || null,
|
|
||||||
soap_version: textValue('soap-version') || 'soap_11',
|
|
||||||
soap_action: textValue('soap-action') || null,
|
|
||||||
binding_style: textValue('soap-binding-style') || 'document_literal',
|
|
||||||
headers: [],
|
|
||||||
fault_contract: null,
|
|
||||||
metadata: { input_part_names: [], output_part_names: [], namespaces: [] },
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
if (wizardProtocol === 'websocket') {
|
|
||||||
return {
|
|
||||||
kind: 'websocket',
|
|
||||||
url: joinUrl(upstream.url, textValue('websocket-path')),
|
|
||||||
subprotocols: textareaLines('websocket-subprotocols'),
|
|
||||||
subscribe_message_template: stringValueOrNull('websocket-subscribe-template')
|
|
||||||
? parseStructuredText(textValue('websocket-subscribe-template'))
|
|
||||||
: null,
|
|
||||||
unsubscribe_message_template: stringValueOrNull('websocket-unsubscribe-template')
|
|
||||||
? parseStructuredText(textValue('websocket-unsubscribe-template'))
|
|
||||||
: null,
|
|
||||||
static_headers: parseHeaderMap(upstream.staticHeaders),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
var route = textValue('grpc-manual-route') || (document.getElementById('grpc-path-value') ? document.getElementById('grpc-path-value').textContent.trim() : '');
|
|
||||||
var descriptorRef = wizardCurrentVersion && wizardCurrentVersion.target && wizardCurrentVersion.target.kind === 'grpc'
|
|
||||||
? wizardCurrentVersion.target.descriptor_ref
|
|
||||||
: 'desc_pending';
|
|
||||||
var descriptorSetB64 = wizardCurrentVersion && wizardCurrentVersion.target && wizardCurrentVersion.target.kind === 'grpc'
|
|
||||||
? (wizardCurrentVersion.target.descriptor_set_b64 || '')
|
|
||||||
: '';
|
|
||||||
var parsedRoute = parseGrpcRoute(route);
|
|
||||||
|
|
||||||
return {
|
|
||||||
kind: 'grpc',
|
|
||||||
server_addr: upstream.url,
|
|
||||||
package: parsedRoute.package,
|
|
||||||
service: parsedRoute.service,
|
|
||||||
method: parsedRoute.method,
|
|
||||||
descriptor_ref: descriptorRef,
|
|
||||||
descriptor_set_b64: descriptorSetB64,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
function crankSchemaToJsonSchema(schema) {
|
function crankSchemaToJsonSchema(schema) {
|
||||||
var result = {};
|
var result = {};
|
||||||
var typeName = schema.type === 'enum' ? 'string' : schema.type;
|
var typeName = schema.type === 'enum' ? 'string' : schema.type;
|
||||||
@@ -390,28 +214,6 @@ function executionConfigToEditorValue(config) {
|
|||||||
if (config.headers && Object.keys(config.headers).length) {
|
if (config.headers && Object.keys(config.headers).length) {
|
||||||
value.headers = config.headers;
|
value.headers = config.headers;
|
||||||
}
|
}
|
||||||
if (config.protocol_options && config.protocol_options.grpc) {
|
|
||||||
value.tls = { verify: !!config.protocol_options.grpc.use_tls };
|
|
||||||
}
|
|
||||||
if (config.protocol_options && config.protocol_options.websocket) {
|
|
||||||
value.protocol_options = value.protocol_options || {};
|
|
||||||
value.protocol_options.websocket = {};
|
|
||||||
if (config.protocol_options.websocket.heartbeat_interval_ms != null) {
|
|
||||||
value.protocol_options.websocket.heartbeat_interval_ms = config.protocol_options.websocket.heartbeat_interval_ms;
|
|
||||||
}
|
|
||||||
if (config.protocol_options.websocket.reconnect_max_attempts != null) {
|
|
||||||
value.protocol_options.websocket.reconnect_max_attempts = config.protocol_options.websocket.reconnect_max_attempts;
|
|
||||||
}
|
|
||||||
if (config.protocol_options.websocket.reconnect_backoff_ms != null) {
|
|
||||||
value.protocol_options.websocket.reconnect_backoff_ms = config.protocol_options.websocket.reconnect_backoff_ms;
|
|
||||||
}
|
|
||||||
if (!Object.keys(value.protocol_options.websocket).length) {
|
|
||||||
delete value.protocol_options.websocket;
|
|
||||||
}
|
|
||||||
if (!Object.keys(value.protocol_options).length) {
|
|
||||||
delete value.protocol_options;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return window.jsyaml ? window.jsyaml.dump(value, { lineWidth: -1 }) : JSON.stringify(value, null, 2);
|
return window.jsyaml ? window.jsyaml.dump(value, { lineWidth: -1 }) : JSON.stringify(value, null, 2);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -456,60 +258,6 @@ function prefillWizardFromEdit(detail, versionDocument) {
|
|||||||
document.querySelectorAll('.method-card').forEach(function(card) {
|
document.querySelectorAll('.method-card').forEach(function(card) {
|
||||||
card.classList.toggle('active', card.dataset.method === target.method);
|
card.classList.toggle('active', card.dataset.method === target.method);
|
||||||
});
|
});
|
||||||
} else if (target.kind === 'graphql') {
|
|
||||||
var endpoint = target.endpoint || '';
|
|
||||||
var parsedEndpoint = new URL(endpoint, window.location.origin);
|
|
||||||
prefillUpstream(parsedEndpoint.origin, {}, versionDocument.execution_config ? versionDocument.execution_config.auth_profile_ref : null);
|
|
||||||
setValue('endpoint-path', parsedEndpoint.pathname + parsedEndpoint.search);
|
|
||||||
setValue('gql-query-editor', target.query_template);
|
|
||||||
document.querySelectorAll('.gql-type-card').forEach(function(card) {
|
|
||||||
card.classList.toggle('active', card.dataset.gqlType === target.operation_type);
|
|
||||||
});
|
|
||||||
} else if (target.kind === 'grpc') {
|
|
||||||
prefillUpstream(target.server_addr, {}, versionDocument.execution_config ? versionDocument.execution_config.auth_profile_ref : null);
|
|
||||||
var route = '/' + (target.package ? target.package + '.' : '') + target.service + '/' + target.method;
|
|
||||||
setValue('grpc-manual-route', route);
|
|
||||||
setValue('grpc-manual-req-type', '');
|
|
||||||
setValue('grpc-manual-res-type', '');
|
|
||||||
grpcManualRouteInput(route);
|
|
||||||
grpcManualTypeInput();
|
|
||||||
} else if (target.kind === 'soap') {
|
|
||||||
prefillUpstream(
|
|
||||||
target.endpoint_override || '',
|
|
||||||
{},
|
|
||||||
versionDocument.execution_config ? versionDocument.execution_config.auth_profile_ref : null
|
|
||||||
);
|
|
||||||
setValue('soap-service-name', target.service_name || '');
|
|
||||||
setValue('soap-port-name', target.port_name || '');
|
|
||||||
setValue('soap-operation-name', target.operation_name || '');
|
|
||||||
setValue('soap-endpoint-override', target.endpoint_override || '');
|
|
||||||
setValue('soap-action', target.soap_action || '');
|
|
||||||
setValue('soap-version', target.soap_version || 'soap_11');
|
|
||||||
setValue('soap-binding-style', target.binding_style || 'document_literal');
|
|
||||||
} else if (target.kind === 'websocket') {
|
|
||||||
prefillUpstream(
|
|
||||||
target.url,
|
|
||||||
target.static_headers || {},
|
|
||||||
versionDocument.execution_config ? versionDocument.execution_config.auth_profile_ref : null
|
|
||||||
);
|
|
||||||
setValue('websocket-path', '');
|
|
||||||
setValue('websocket-subprotocols', (target.subprotocols || []).join('\n'));
|
|
||||||
setValue(
|
|
||||||
'websocket-subscribe-template',
|
|
||||||
target.subscribe_message_template ? JSON.stringify(target.subscribe_message_template, null, 2) : ''
|
|
||||||
);
|
|
||||||
setValue(
|
|
||||||
'websocket-unsubscribe-template',
|
|
||||||
target.unsubscribe_message_template ? JSON.stringify(target.unsubscribe_message_template, null, 2) : ''
|
|
||||||
);
|
|
||||||
var websocketOptions = versionDocument.execution_config
|
|
||||||
&& versionDocument.execution_config.protocol_options
|
|
||||||
&& versionDocument.execution_config.protocol_options.websocket
|
|
||||||
? versionDocument.execution_config.protocol_options.websocket
|
|
||||||
: null;
|
|
||||||
setValue('websocket-heartbeat-interval-ms', websocketOptions && websocketOptions.heartbeat_interval_ms ? websocketOptions.heartbeat_interval_ms : '');
|
|
||||||
setValue('websocket-reconnect-max-attempts', websocketOptions && websocketOptions.reconnect_max_attempts != null ? websocketOptions.reconnect_max_attempts : '');
|
|
||||||
setValue('websocket-reconnect-backoff-ms', websocketOptions && websocketOptions.reconnect_backoff_ms ? websocketOptions.reconnect_backoff_ms : '');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
setValue('tool-name', detail.name);
|
setValue('tool-name', detail.name);
|
||||||
@@ -521,7 +269,6 @@ function prefillWizardFromEdit(detail, versionDocument) {
|
|||||||
setValue('tool-input-mapping', mappingSetToEditorValue(versionDocument.input_mapping, 'input', detail.protocol));
|
setValue('tool-input-mapping', mappingSetToEditorValue(versionDocument.input_mapping, 'input', detail.protocol));
|
||||||
setValue('tool-output-mapping', mappingSetToEditorValue(versionDocument.output_mapping, 'output', detail.protocol));
|
setValue('tool-output-mapping', mappingSetToEditorValue(versionDocument.output_mapping, 'output', detail.protocol));
|
||||||
setValue('tool-exec-config', executionConfigToEditorValue(versionDocument.execution_config));
|
setValue('tool-exec-config', executionConfigToEditorValue(versionDocument.execution_config));
|
||||||
applyStreamingConfig(versionDocument.execution_config ? versionDocument.execution_config.streaming : null);
|
|
||||||
|
|
||||||
var toolNameInput = document.getElementById('tool-name');
|
var toolNameInput = document.getElementById('tool-name');
|
||||||
if (toolNameInput) toolNameInput.disabled = true;
|
if (toolNameInput) toolNameInput.disabled = true;
|
||||||
|
|||||||
@@ -1,9 +1,7 @@
|
|||||||
/* ── Upstream selector (Step 2) ── */
|
/* ── Upstream selector (Step 2) ── */
|
||||||
|
|
||||||
var upstreams = [
|
var upstreams = [
|
||||||
{ id: 'open-meteo', name: 'Open Meteo', url: 'https://api.open-meteo.com', staticHeaders: '{}', authProfileId: null, authProfileName: '', authKind: null },
|
{ id: 'open-meteo', name: 'Open Meteo', url: 'https://api.open-meteo.com', staticHeaders: '{}', authProfileId: null, authProfileName: '', authKind: null }
|
||||||
{ id: 'countries-graphql', name: 'Countries GraphQL', url: 'https://countries.trevorblades.com', staticHeaders: '{}', authProfileId: null, authProfileName: '', authKind: null },
|
|
||||||
{ id: 'grpcb', name: 'grpcb.in', url: 'https://grpcb.in:443', staticHeaders: '{}', authProfileId: null, authProfileName: '', authKind: null }
|
|
||||||
];
|
];
|
||||||
|
|
||||||
var dropdownOpen = false;
|
var dropdownOpen = false;
|
||||||
@@ -290,10 +288,6 @@ function pickUpstream(id) {
|
|||||||
var trigger = document.getElementById('upstream-new-trigger');
|
var trigger = document.getElementById('upstream-new-trigger');
|
||||||
if (trigger) trigger.classList.remove('active');
|
if (trigger) trigger.classList.remove('active');
|
||||||
|
|
||||||
var reflectName = document.getElementById('grpc-reflect-upstream-name');
|
|
||||||
var reflectUrl = document.getElementById('grpc-reflect-upstream-url');
|
|
||||||
if (reflectName) reflectName.textContent = u.name;
|
|
||||||
if (reflectUrl) reflectUrl.textContent = u.url;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function startNewUpstream() {
|
function startNewUpstream() {
|
||||||
|
|||||||
@@ -15,9 +15,6 @@ var loadWizardPanels = window.CrankWizardShell.loadWizardPanels;
|
|||||||
var bindProtocolCards = window.CrankWizardShell.bindProtocolCards;
|
var bindProtocolCards = window.CrankWizardShell.bindProtocolCards;
|
||||||
var applyEditionProtocolVisibility = window.CrankWizardShell.applyEditionProtocolVisibility;
|
var applyEditionProtocolVisibility = window.CrankWizardShell.applyEditionProtocolVisibility;
|
||||||
var step3PanelId = window.CrankWizardShell.step3PanelId;
|
var step3PanelId = window.CrankWizardShell.step3PanelId;
|
||||||
var bindStreamingConfigControls = window.CrankStreamingForm.bindStreamingConfigControls;
|
|
||||||
var collectStreamingConfig = window.CrankStreamingForm.collectStreamingConfig;
|
|
||||||
var applyStreamingConfig = window.CrankStreamingForm.applyStreamingConfig;
|
|
||||||
var saveOperation = window.CrankWizardLive.saveOperation;
|
var saveOperation = window.CrankWizardLive.saveOperation;
|
||||||
var loadOperationForEdit = window.CrankWizardLive.loadOperationForEdit;
|
var loadOperationForEdit = window.CrankWizardLive.loadOperationForEdit;
|
||||||
var bindWizardLiveActions = window.CrankWizardLive.bindWizardLiveActions;
|
var bindWizardLiveActions = window.CrankWizardLive.bindWizardLiveActions;
|
||||||
@@ -34,8 +31,6 @@ var wizardDescriptorSetUpload = window.wizardDescriptorSetUpload;
|
|||||||
var wizardSoapWsdlUpload = window.wizardSoapWsdlUpload;
|
var wizardSoapWsdlUpload = window.wizardSoapWsdlUpload;
|
||||||
var wizardSoapXsdUpload = window.wizardSoapXsdUpload;
|
var wizardSoapXsdUpload = window.wizardSoapXsdUpload;
|
||||||
var wizardTestResponsePreview = window.wizardTestResponsePreview;
|
var wizardTestResponsePreview = window.wizardTestResponsePreview;
|
||||||
var grpcDescriptorServices = window.grpcDescriptorServices;
|
|
||||||
var soapServiceCatalog = window.soapServiceCatalog;
|
|
||||||
var wizardProtocolCapabilities = window.wizardProtocolCapabilities;
|
var wizardProtocolCapabilities = window.wizardProtocolCapabilities;
|
||||||
var wizardSecrets = window.wizardSecrets;
|
var wizardSecrets = window.wizardSecrets;
|
||||||
var wizardAuthProfiles = window.wizardAuthProfiles;
|
var wizardAuthProfiles = window.wizardAuthProfiles;
|
||||||
@@ -95,7 +90,6 @@ async function initWizardPage() {
|
|||||||
await loadWizardAuthResources();
|
await loadWizardAuthResources();
|
||||||
bindProtocolCards();
|
bindProtocolCards();
|
||||||
bindWizardLiveActions();
|
bindWizardLiveActions();
|
||||||
bindStreamingConfigControls();
|
|
||||||
updateUpstreamAuthUi();
|
updateUpstreamAuthUi();
|
||||||
renderEditionCapabilityHints(editionCapabilities);
|
renderEditionCapabilityHints(editionCapabilities);
|
||||||
|
|
||||||
@@ -149,9 +143,6 @@ async function initWizardPage() {
|
|||||||
function renderEditionCapabilityHints(capabilities) {
|
function renderEditionCapabilityHints(capabilities) {
|
||||||
var securityNote = document.getElementById('wizard-security-level-note');
|
var securityNote = document.getElementById('wizard-security-level-note');
|
||||||
var securityText = securityNote ? securityNote.querySelector('.info-callout-text') : null;
|
var securityText = securityNote ? securityNote.querySelector('.info-callout-text') : null;
|
||||||
var streamingNote = document.getElementById('wizard-streaming-capability-note');
|
|
||||||
var streamingText = streamingNote ? streamingNote.querySelector('.info-callout-text') : null;
|
|
||||||
var streamingCard = document.getElementById('wizard-streaming-config-card');
|
|
||||||
if (!securityText) return;
|
if (!securityText) return;
|
||||||
var levels = capabilities && Array.isArray(capabilities.supported_security_levels)
|
var levels = capabilities && Array.isArray(capabilities.supported_security_levels)
|
||||||
? capabilities.supported_security_levels
|
? capabilities.supported_security_levels
|
||||||
@@ -160,25 +151,6 @@ function renderEditionCapabilityHints(capabilities) {
|
|||||||
if (!securityNote.hidden) {
|
if (!securityNote.hidden) {
|
||||||
securityText.textContent = tKey('wizard.step5.community_security_note');
|
securityText.textContent = tKey('wizard.step5.community_security_note');
|
||||||
}
|
}
|
||||||
|
|
||||||
var protocolCapabilities = currentProtocolCapabilities();
|
|
||||||
var modes = protocolCapabilities && Array.isArray(protocolCapabilities.supports_execution_modes)
|
|
||||||
? protocolCapabilities.supports_execution_modes
|
|
||||||
: ['unary'];
|
|
||||||
var hasStreamingModes = modes.some(function(mode) { return mode !== 'unary'; });
|
|
||||||
if (streamingCard) {
|
|
||||||
streamingCard.hidden = !hasStreamingModes;
|
|
||||||
}
|
|
||||||
if (streamingNote && streamingText) {
|
|
||||||
var edition = capabilities && capabilities.edition ? capabilities.edition : 'community';
|
|
||||||
var shouldExplainCommunityLimit = edition === 'community'
|
|
||||||
&& !hasStreamingModes
|
|
||||||
&& ['rest', 'grpc'].indexOf(wizardProtocol) >= 0;
|
|
||||||
streamingNote.hidden = !shouldExplainCommunityLimit;
|
|
||||||
if (shouldExplainCommunityLimit) {
|
|
||||||
streamingText.textContent = tKey('wizard.step5.community_streaming_note');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
window.renderEditionCapabilityHints = renderEditionCapabilityHints;
|
window.renderEditionCapabilityHints = renderEditionCapabilityHints;
|
||||||
@@ -285,12 +257,6 @@ document.addEventListener('click', function() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
/* ══════════════════════════════════════════════
|
|
||||||
gRPC — source selector
|
|
||||||
══════════════════════════════════════════════ */
|
|
||||||
|
|
||||||
var grpcSource = 'proto'; // 'proto' | 'reflection' | 'manual'
|
|
||||||
|
|
||||||
function textValue(id) {
|
function textValue(id) {
|
||||||
var element = document.getElementById(id);
|
var element = document.getElementById(id);
|
||||||
return element ? element.value.trim() : '';
|
return element ? element.value.trim() : '';
|
||||||
|
|||||||
@@ -88,10 +88,6 @@ const BUNDLES = {
|
|||||||
'node_modules/js-yaml/dist/js-yaml.min.js',
|
'node_modules/js-yaml/dist/js-yaml.min.js',
|
||||||
'js/wizard-state.js',
|
'js/wizard-state.js',
|
||||||
'js/wizard-shell.js',
|
'js/wizard-shell.js',
|
||||||
'js/streaming-form.js',
|
|
||||||
'js/stream-test-run.js',
|
|
||||||
'js/wizard-grpc.js',
|
|
||||||
'js/wizard-artifacts.js',
|
|
||||||
'js/wizard-upstreams.js',
|
'js/wizard-upstreams.js',
|
||||||
'js/wizard-model.js',
|
'js/wizard-model.js',
|
||||||
'js/wizard-live.js',
|
'js/wizard-live.js',
|
||||||
|
|||||||
@@ -2434,6 +2434,7 @@ mod tests {
|
|||||||
auth_profile_ref: Some("auth_crank".into()),
|
auth_profile_ref: Some("auth_crank".into()),
|
||||||
headers: BTreeMap::from([("X-Request-Id".to_owned(), "static".to_owned())]),
|
headers: BTreeMap::from([("X-Request-Id".to_owned(), "static".to_owned())]),
|
||||||
protocol_options: None,
|
protocol_options: None,
|
||||||
|
response_cache: None,
|
||||||
streaming: None,
|
streaming: None,
|
||||||
},
|
},
|
||||||
tool_description: ToolDescription {
|
tool_description: ToolDescription {
|
||||||
|
|||||||
@@ -5,6 +5,10 @@ license.workspace = true
|
|||||||
rust-version.workspace = true
|
rust-version.workspace = true
|
||||||
version.workspace = true
|
version.workspace = true
|
||||||
|
|
||||||
|
[lib]
|
||||||
|
path = "src/lib.rs"
|
||||||
|
test = false
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
default = []
|
default = []
|
||||||
protocol-graphql = []
|
protocol-graphql = []
|
||||||
|
|||||||
Reference in New Issue
Block a user