feat: localize settings workspace and wizard

This commit is contained in:
a.tolmachev
2026-04-03 00:03:40 +03:00
parent 581d50c33c
commit 140548bb20
16 changed files with 763 additions and 247 deletions
+37 -37
View File
@@ -34,12 +34,12 @@
<svg width="11" height="11" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M4 6l4 4 4-4"/></svg>
</button>
<div class="ws-dropdown" id="ws-dropdown" style="display:none">
<div class="ws-dropdown-label">Your workspaces</div>
<div class="ws-dropdown-label" data-i18n="nav.workspaces">Your workspaces</div>
<div id="ws-dropdown-list"></div>
<div class="ws-dropdown-footer">
<button class="ws-dropdown-create" onclick="window.location.href='/workspace-setup?mode=create'">
<svg width="12" height="12" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round"><path d="M8 3v10M3 8h10"/></svg>
Create workspace
<span data-i18n="nav.create_workspace">Create workspace</span>
</button>
</div>
</div>
@@ -58,7 +58,7 @@
<svg width="12" height="12"><use href="icons/general/grid.svg#icon"/></svg>
<span>⌘K</span>
</div>
<button class="nav-icon-btn" title="Notifications">
<button class="nav-icon-btn" data-i18n-title="nav.notifications" title="Notifications">
<svg width="15" height="15"><use href="icons/general/bell.svg#icon"/></svg>
</button>
<div class="nav-divider"></div>
@@ -95,8 +95,8 @@
<div class="page-header" style="margin-bottom: 24px;">
<div class="page-header-text">
<h1 class="page-title">Account settings</h1>
<p class="page-subtitle">Manage the live profile, password and workspace settings available in this build. Planned capabilities stay visible, but they do not pretend to be wired yet.</p>
<h1 class="page-title" data-i18n="settings.page.title">Account settings</h1>
<p class="page-subtitle" data-i18n="settings.page.subtitle">Manage the live profile, password and workspace settings available in this build. Planned capabilities stay visible, but they do not pretend to be wired yet.</p>
</div>
</div>
@@ -108,7 +108,7 @@
<svg width="14" height="14" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round">
<path d="M2 5.5h12M4 2.5h8a1.5 1.5 0 011.5 1.5v8A1.5 1.5 0 0112 13.5H4A1.5 1.5 0 012.5 12V4A1.5 1.5 0 014 2.5z"/>
</svg>
<span>Workspace</span>
<span data-i18n="settings.nav.workspace">Workspace</span>
</button>
<button class="settings-nav-item active" data-section="profile">
<svg width="14" height="14" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round">
@@ -147,15 +147,15 @@
<div class="field-group">
<label class="field-label" data-i18n="settings.ws.name">Workspace name</label>
<input class="field-input" id="settings-ws-slug" type="text" value="acme-workspace" autocomplete="off">
<div class="field-hint">Used in API endpoints and across the console. Only lowercase letters, numbers and hyphens.</div>
<div class="field-hint" data-i18n="settings.ws.slug_hint">Used in API endpoints and across the console. Only lowercase letters, numbers and hyphens.</div>
</div>
<div class="field-group">
<label class="field-label" data-i18n="settings.ws.display">Display name</label>
<input class="field-input" id="settings-ws-display-name" type="text" value="Acme Corp" autocomplete="off">
</div>
<div class="field-group">
<label class="field-label">Description <span style="font-size:11px;color:var(--text-muted);">(optional)</span></label>
<textarea class="field-textarea" id="settings-ws-description" rows="2" placeholder="What does this workspace do?">Primary production workspace for CRM and e-commerce API operations.</textarea>
<label class="field-label"><span data-i18n="settings.ws.description_optional">Description (optional)</span></label>
<textarea class="field-textarea" id="settings-ws-description" rows="2" data-i18n-ph="settings.ws.description_placeholder" placeholder="What does this workspace do?">Primary production workspace for CRM and e-commerce API operations.</textarea>
</div>
<div class="field-row">
<div class="field-group">
@@ -172,7 +172,7 @@
<div class="field-group">
<label class="field-label" data-i18n="settings.ws.protocol">Default protocol</label>
<select class="field-select" id="settings-ws-protocol">
<option value="rest" selected>REST / HTTP</option>
<option value="rest" selected>REST / HTTP</option>
<option value="graphql">GraphQL</option>
<option value="grpc">gRPC</option>
</select>
@@ -215,31 +215,31 @@
<div class="avatar-upload-actions">
<div class="avatar-name" id="profile-display-name">Operator</div>
<div class="avatar-sub" id="profile-email">operator@acme-workspace</div>
<div class="field-hint">Your avatar is generated from your display name and email.</div>
<div class="field-hint" data-i18n="settings.profile.avatar_hint">Your avatar is generated from your display name and email.</div>
</div>
</div>
<div class="field-hint" style="margin-bottom:16px;">Your name and email are stored on the backend and used across the console and session identity.</div>
<div class="field-hint" style="margin-bottom:16px;" data-i18n="settings.profile.backend_hint">Your name and email are stored on the backend and used across the console and session identity.</div>
<div class="field-row">
<div class="field-group">
<label class="field-label">First name</label>
<label class="field-label" data-i18n="settings.profile.first_name">First name</label>
<input class="field-input" id="field-firstname" type="text" value="Operator" autocomplete="given-name">
</div>
<div class="field-group">
<label class="field-label">Last name</label>
<input class="field-input" id="field-lastname" type="text" value="" autocomplete="family-name" placeholder="Optional">
<label class="field-label" data-i18n="settings.profile.last_name">Last name</label>
<input class="field-input" id="field-lastname" type="text" value="" autocomplete="family-name" data-i18n-ph="settings.profile.last_name_placeholder" placeholder="Optional">
</div>
</div>
<div class="field-group">
<label class="field-label">Email address</label>
<label class="field-label" data-i18n="settings.profile.email_label">Email address</label>
<input class="field-input" id="field-email" type="email" value="operator@acme.com" autocomplete="email">
<div class="field-hint">Changing email updates the login identity for the current account.</div>
<div class="field-hint" data-i18n="settings.profile.email_hint">Changing email updates the login identity for the current account.</div>
</div>
<div class="field-hint" id="settings-profile-status" style="margin-bottom:12px;"></div>
<div style="display:flex;justify-content:flex-end;">
<button class="btn-primary" id="settings-profile-save-btn" type="button">Save profile</button>
<button class="btn-primary" id="settings-profile-save-btn" type="button" data-i18n="settings.profile.save">Save profile</button>
</div>
</div>
</div>
@@ -249,38 +249,38 @@
<div id="section-security" class="section-anchor">
<div class="section-card" style="margin-bottom: 20px;">
<div class="section-card-header">
<div class="section-card-title">Security</div>
<div class="section-card-title" data-i18n="settings.security.title">Security</div>
</div>
<div class="section-card-body" style="padding-bottom:8px;">
<div class="field-group">
<label class="field-label">Current password</label>
<label class="field-label" data-i18n="settings.security.current_password">Current password</label>
<input class="field-input" id="security-current-password" type="password" placeholder="••••••••" autocomplete="current-password">
</div>
<div class="field-row">
<div class="field-group">
<label class="field-label">New password</label>
<input class="field-input" id="security-new-password" type="password" placeholder="min. 12 characters" autocomplete="new-password">
<label class="field-label" data-i18n="settings.security.new_password">New password</label>
<input class="field-input" id="security-new-password" type="password" data-i18n-ph="settings.security.new_password_placeholder" placeholder="min. 12 characters" autocomplete="new-password">
</div>
<div class="field-group">
<label class="field-label">Confirm new password</label>
<label class="field-label" data-i18n="settings.security.confirm_password">Confirm new password</label>
<input class="field-input" id="security-confirm-password" type="password" placeholder="••••••••" autocomplete="new-password">
</div>
</div>
<div class="field-hint" id="settings-password-status" style="margin-bottom:12px;"></div>
<div style="display:flex;justify-content:flex-end;padding-bottom:8px;">
<button class="btn-primary" id="settings-password-save-btn" type="button">Change password</button>
<button class="btn-primary" id="settings-password-save-btn" type="button" data-i18n="settings.security.change_password">Change password</button>
</div>
</div>
<div style="padding: 4px 20px 16px;">
<div class="settings-section-divider" style="margin-top:0;"></div>
<div style="font-size:13.5px;font-weight:600;color:var(--text-primary);margin-bottom:12px;">Planned security capabilities</div>
<div style="font-size:13.5px;font-weight:600;color:var(--text-primary);margin-bottom:12px;" data-i18n="settings.security.capabilities_title">Planned security capabilities</div>
<div class="callout warning" style="margin-bottom:0;">
<svg class="callout-icon" width="15" height="15" viewBox="0 0 16 16" fill="none" stroke="var(--amber)" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round">
<polygon points="8,1.5 15.5,14.5 0.5,14.5" fill="none"/><path d="M8 6v4M8 11.5v.5"/>
</svg>
<div>
<strong>Not available in this build.</strong> Two-factor authentication, passkeys and multi-session inventory are planned, but the current live flow is password-based login with one HttpOnly browser session.
<strong data-i18n="settings.security.not_available">Not available in this build.</strong> <span data-i18n="settings.security.capabilities_body">Two-factor authentication, passkeys and multi-session inventory are planned, but the current live flow is password-based login with one HttpOnly browser session.</span>
</div>
</div>
</div>
@@ -288,13 +288,13 @@
<div class="section-card" style="margin-bottom: 20px;">
<div class="section-card-header">
<div class="section-card-title">Current session</div>
<div class="section-card-title" data-i18n="settings.session.title">Current session</div>
</div>
<div style="padding: 8px 20px 16px;">
<div class="member-row">
<div class="member-info">
<div class="member-name">Browser session — <span style="color:var(--green);font-size:12px;">current</span></div>
<div class="member-email" id="settings-session-summary">Loading current session details…</div>
<div class="member-name">Browser session — <span style="color:var(--green);font-size:12px;" data-i18n="settings.session.current">current</span></div>
<div class="member-email" id="settings-session-summary" data-i18n="settings.session.loading">Loading current session details…</div>
</div>
</div>
</div>
@@ -305,7 +305,7 @@
<div id="section-notifications" class="section-anchor">
<div class="section-card" style="margin-bottom: 20px;">
<div class="section-card-header">
<div class="section-card-title">Notification preferences</div>
<div class="section-card-title" data-i18n="settings.notifications.title">Notification preferences</div>
</div>
<div style="padding: 8px 20px 16px;">
<div class="callout info" style="margin-bottom:14px;">
@@ -314,21 +314,21 @@
<path d="M8 11V8M8 5.5V5"/>
</svg>
<div>
<strong>Notifications are not wired yet.</strong> Alert routing and per-user preferences are planned, but this build does not persist or deliver notification settings.
<strong data-i18n="settings.notifications.not_ready_title">Notifications are not wired yet.</strong> <span data-i18n="settings.notifications.not_ready_body">Alert routing and per-user preferences are planned, but this build does not persist or deliver notification settings.</span>
</div>
</div>
<div class="planned-list">
<div class="planned-item">
<div class="planned-item-title">Operation error spike</div>
<div class="planned-item-desc">Alert when error rate exceeds a rolling threshold for a published tool.</div>
<div class="planned-item-title" data-i18n="settings.notifications.spike_title">Operation error spike</div>
<div class="planned-item-desc" data-i18n="settings.notifications.spike_body">Alert when error rate exceeds a rolling threshold for a published tool.</div>
</div>
<div class="planned-item">
<div class="planned-item-title">Upstream latency degradation</div>
<div class="planned-item-desc">Notify operators when p99 latency rises sharply above the baseline.</div>
<div class="planned-item-title" data-i18n="settings.notifications.latency_title">Upstream latency degradation</div>
<div class="planned-item-desc" data-i18n="settings.notifications.latency_body">Notify operators when p99 latency rises sharply above the baseline.</div>
</div>
<div class="planned-item">
<div class="planned-item-title">Quota and usage digests</div>
<div class="planned-item-desc">Deliver periodic summaries for quota consumption, errors and workspace activity.</div>
<div class="planned-item-title" data-i18n="settings.notifications.digest_title">Quota and usage digests</div>
<div class="planned-item-desc" data-i18n="settings.notifications.digest_body">Deliver periodic summaries for quota consumption, errors and workspace activity.</div>
</div>
</div>
</div>
+24 -24
View File
@@ -35,12 +35,12 @@
<svg width="11" height="11" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M4 6l4 4 4-4"/></svg>
</button>
<div class="ws-dropdown" id="ws-dropdown" style="display:none">
<div class="ws-dropdown-label">Your workspaces</div>
<div class="ws-dropdown-label" data-i18n="nav.workspaces">Your workspaces</div>
<div id="ws-dropdown-list"></div>
<div class="ws-dropdown-footer">
<button class="ws-dropdown-create" onclick="window.location.href='/workspace-setup?mode=create'">
<svg width="12" height="12" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round"><path d="M8 3v10M3 8h10"/></svg>
Create workspace
<span data-i18n="nav.create_workspace">Create workspace</span>
</button>
</div>
</div>
@@ -62,7 +62,7 @@
<span>⌘K</span>
</div>
<button class="nav-icon-btn" title="Notifications">
<button class="nav-icon-btn" data-i18n-title="nav.notifications" title="Notifications">
<svg width="15" height="15"><use href="../../icons/general/bell.svg#icon"/></svg>
</button>
@@ -102,17 +102,17 @@
<!-- ══ TOP PROGRESS STRIP ══ -->
<div class="progress-strip">
<button class="back-to-catalog-btn" id="back-to-catalog" title="Back to catalog">
<button class="back-to-catalog-btn" id="back-to-catalog" data-i18n-title="wizard.back_catalog" title="Back to catalog">
<svg width="13" height="13" viewBox="0 0 16 16" fill="currentColor"><path d="M9.78 12.78a.75.75 0 01-1.06 0L4.47 8.53a.75.75 0 010-1.06l4.25-4.25a.75.75 0 011.06 1.06L6.06 8l3.72 3.72a.75.75 0 010 1.06z"/></svg>
Operations
<span data-i18n="wizard.back_catalog">Operations</span>
</button>
<div class="progress-divider"></div>
<span class="progress-label">Create operation</span>
<span class="progress-label" data-i18n="wizard.progress.create">Create operation</span>
<div class="progress-bar-wrap">
<div class="progress-bar-fill"></div>
</div>
<span class="progress-pct">20%</span>
<button class="progress-close" title="Exit wizard" aria-label="Exit wizard">
<button class="progress-close" data-i18n-title="wizard.exit" title="Exit wizard" aria-label="Exit wizard">
<svg width="12" height="12" viewBox="0 0 12 12" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round">
<line x1="1" y1="1" x2="11" y2="11"/><line x1="11" y1="1" x2="1" y2="11"/>
</svg>
@@ -128,13 +128,13 @@
<div class="step-sidebar-header">
<div class="step-sidebar-logo-mark">M</div>
<div class="step-sidebar-brand">
<div class="step-sidebar-brand" data-i18n="wizard.sidebar.brand.create">
Create <span>operation</span>
</div>
</div>
<div class="step-sidebar-body">
<div class="step-sidebar-section-label">Progress</div>
<div class="step-sidebar-section-label" data-i18n="wizard.progress.section">Progress</div>
<div class="steps-list">
@@ -142,8 +142,8 @@
<div class="step-indicator">1</div>
<div class="step-content">
<div class="step-number">Step 1</div>
<div class="step-name">Protocol</div>
<div class="step-status-text">In progress</div>
<div class="step-name" data-i18n="wizard.step_name.protocol">Protocol</div>
<div class="step-status-text" data-i18n="wizard.status.in_progress">In progress</div>
</div>
</div>
@@ -151,8 +151,8 @@
<div class="step-indicator">2</div>
<div class="step-content">
<div class="step-number">Step 2</div>
<div class="step-name">Upstream target</div>
<div class="step-status-text">Not started</div>
<div class="step-name" data-i18n="wizard.step_name.upstream">Upstream target</div>
<div class="step-status-text" data-i18n="wizard.status.not_started">Not started</div>
</div>
</div>
@@ -161,7 +161,7 @@
<div class="step-content">
<div class="step-number">Step 3</div>
<div class="step-name" id="sidebar-step-3-name">Request config</div>
<div class="step-status-text">Not started</div>
<div class="step-status-text" data-i18n="wizard.status.not_started">Not started</div>
</div>
</div>
@@ -169,8 +169,8 @@
<div class="step-indicator">4</div>
<div class="step-content">
<div class="step-number">Step 4</div>
<div class="step-name">Tool config</div>
<div class="step-status-text">Not started</div>
<div class="step-name" data-i18n="wizard.step_name.tool">Tool config</div>
<div class="step-status-text" data-i18n="wizard.status.not_started">Not started</div>
</div>
</div>
@@ -178,8 +178,8 @@
<div class="step-indicator">5</div>
<div class="step-content">
<div class="step-number">Step 5</div>
<div class="step-name">Mapping</div>
<div class="step-status-text">Not started</div>
<div class="step-name" data-i18n="wizard.step_name.mapping">Mapping</div>
<div class="step-status-text" data-i18n="wizard.status.not_started">Not started</div>
</div>
</div>
@@ -191,11 +191,11 @@
<circle cx="8" cy="8" r="7"/>
<path d="M8 12v-4M8 5v-.5"/>
</svg>
Need help?
<span data-i18n="wizard.help.title">Need help?</span>
</div>
<div class="sidebar-help-text">
Unsure which protocol to choose? Read the
<a href="#" class="sidebar-help-link">protocol comparison guide →</a>
<span data-i18n="wizard.help.body">Unsure which protocol to choose? Read the</span>
<a href="#" class="sidebar-help-link" data-i18n="wizard.help.link">protocol comparison guide →</a>
</div>
</div>
</div><!-- /step-sidebar-body -->
@@ -214,17 +214,17 @@
<svg width="13" height="13" viewBox="0 0 16 16" fill="currentColor">
<path d="M9.78 12.78a.75.75 0 01-1.06 0L4.47 8.53a.75.75 0 010-1.06l4.25-4.25a.75.75 0 011.06 1.06L6.06 8l3.72 3.72a.75.75 0 010 1.06z"/>
</svg>
Back
<span data-i18n="wizard.button.back">Back</span>
</button>
<button class="btn-save-draft" type="button">Save draft</button>
<button class="btn-save-draft" type="button" data-i18n="wizard.button.save_draft">Save draft</button>
<div class="action-bar-spacer"></div>
<span class="step-counter">Step <strong>1</strong> of 5</span>
<button id="btn-continue" class="btn btn-continue" type="button">
Continue
<span data-i18n="wizard.button.continue">Continue</span>
<svg width="13" height="13" viewBox="0 0 16 16" fill="currentColor">
<path d="M6.22 3.22a.75.75 0 011.06 0l4.25 4.25a.75.75 0 010 1.06l-4.25 4.25a.75.75 0 01-1.06-1.06L9.94 8 6.22 4.28a.75.75 0 010-1.06z"/>
</svg>
+5 -5
View File
@@ -3,10 +3,10 @@
<div class="step-panel-header">
<div class="step-panel-eyebrow">
<div class="step-panel-eyebrow-line"></div>
Step 1 of 5
<span data-i18n-html="wizard.step_of">Step 1 of 5</span>
</div>
<h1 class="step-panel-title">Choose a protocol</h1>
<p class="step-panel-subtitle">Select the transport protocol your upstream service uses. This determines how Crank will communicate with your API and which configuration options are available in subsequent steps.</p>
<h1 class="step-panel-title" data-i18n="wizard.step1.title">Choose a protocol</h1>
<p class="step-panel-subtitle" data-i18n="wizard.step1.subtitle">Select the transport protocol your upstream service uses. This determines how Crank will communicate with your API and which configuration options are available in subsequent steps.</p>
</div>
<!-- Protocol selection cards -->
@@ -85,9 +85,9 @@
<path d="M8 6v4M8 11.5v.5"/>
</svg>
<div class="info-callout-body">
<div class="info-callout-title">You can change this later</div>
<div class="info-callout-title" data-i18n="wizard.step1.tip_title">You can change this later</div>
<div class="info-callout-text">
Switching protocol after configuring subsequent steps will clear schema and mapping data. Save your operation as a draft before experimenting. See <a href="#">protocol migration guide</a> for details.
<span data-i18n="wizard.step1.tip_body">Switching protocol after configuring subsequent steps will clear schema and mapping data. Save your operation as a draft before experimenting. See protocol migration guide for details.</span>
</div>
</div>
</div>
+3 -3
View File
@@ -3,10 +3,10 @@
<div class="step-panel-header">
<div class="step-panel-eyebrow">
<div class="step-panel-eyebrow-line"></div>
Step 2 of 5
<span data-i18n-html="wizard.step_of">Step 2 of 5</span>
</div>
<h1 class="step-panel-title">Select upstream &amp; endpoint</h1>
<p class="step-panel-subtitle">Choose an existing upstream — a shared host with its auth config — or register a new one. Then define the specific endpoint path this operation will call.</p>
<h1 class="step-panel-title" data-i18n="wizard.step2.title">Select upstream &amp; endpoint</h1>
<p class="step-panel-subtitle" data-i18n="wizard.step2.subtitle">Choose an existing upstream — a shared host with its auth config — or register a new one. Then define the specific endpoint path this operation will call.</p>
</div>
<!-- ── Upstream selector ── -->
+3 -3
View File
@@ -2,10 +2,10 @@
<div class="step-panel-header">
<div class="step-panel-eyebrow">
<div class="step-panel-eyebrow-line"></div>
Step 3 of 5
<span data-i18n-html="wizard.step_of">Step 3 of 5</span>
</div>
<h1 class="step-panel-title">GraphQL operation</h1>
<p class="step-panel-subtitle">Define the GraphQL operation this tool will execute. All requests are sent as HTTP POST to the endpoint configured in the previous step. Variables are populated from MCP tool arguments at runtime.</p>
<h1 class="step-panel-title" data-i18n="wizard.step3.graphql.title">GraphQL operation</h1>
<p class="step-panel-subtitle" data-i18n="wizard.step3.graphql.subtitle">Define the GraphQL operation this tool will execute. All requests are sent as HTTP POST to the endpoint configured in the previous step. Variables are populated from MCP tool arguments at runtime.</p>
</div>
<!-- Operation type -->
+3 -3
View File
@@ -2,10 +2,10 @@
<div class="step-panel-header">
<div class="step-panel-eyebrow">
<div class="step-panel-eyebrow-line"></div>
Step 3 of 5
<span data-i18n-html="wizard.step_of">Step 3 of 5</span>
</div>
<h1 class="step-panel-title">RPC method</h1>
<p class="step-panel-subtitle">Choose how to describe the gRPC service contract. Upload a <code style="font-family:monospace;font-size:13px;color:var(--accent);">.proto</code> file, discover methods from a live server via reflection, or enter the route manually. Only unary RPCs are supported — streaming connections cannot be held open during MCP tool invocations.</p>
<h1 class="step-panel-title" data-i18n="wizard.step3.grpc.title">RPC method</h1>
<p class="step-panel-subtitle" data-i18n="wizard.step3.grpc.subtitle">Choose how to describe the gRPC service contract. Upload a .proto file, discover methods from a live server via reflection, or enter the route manually. Only unary RPCs are supported.</p>
</div>
<!-- ── Source selector ── -->
+3 -3
View File
@@ -3,10 +3,10 @@
<div class="step-panel-header">
<div class="step-panel-eyebrow">
<div class="step-panel-eyebrow-line"></div>
Step 3 of 5
<span data-i18n-html="wizard.step_of">Step 3 of 5</span>
</div>
<h1 class="step-panel-title">HTTP method &amp; format</h1>
<p class="step-panel-subtitle">Choose the HTTP verb this operation sends and configure content negotiation headers. Crank will serialize MCP tool arguments into the appropriate request body format.</p>
<h1 class="step-panel-title" data-i18n="wizard.step3.rest.title">HTTP method &amp; format</h1>
<p class="step-panel-subtitle" data-i18n="wizard.step3.rest.subtitle">Choose the HTTP verb this operation sends and configure content negotiation headers. Crank will serialize MCP tool arguments into the appropriate request body format.</p>
</div>
<!-- HTTP method picker -->
+3 -3
View File
@@ -2,10 +2,10 @@
<div class="step-panel-header">
<div class="step-panel-eyebrow">
<div class="step-panel-eyebrow-line"></div>
Step 4 of 5
<span data-i18n-html="wizard.step_of">Step 4 of 5</span>
</div>
<h1 class="step-panel-title">Tool config</h1>
<p class="step-panel-subtitle">Name your tool, write the LLM-facing description, and define the input/output schemas. The description is the primary signal the model uses when deciding whether to call this tool.</p>
<h1 class="step-panel-title" data-i18n="wizard.step4.title">Tool config</h1>
<p class="step-panel-subtitle" data-i18n="wizard.step4.subtitle">Name your tool, write the LLM-facing description, and define the input/output schemas. The description is the primary signal the model uses when deciding whether to call this tool.</p>
</div>
<div class="config-card" style="margin-bottom: 20px;">
+3 -3
View File
@@ -2,10 +2,10 @@
<div class="step-panel-header">
<div class="step-panel-eyebrow">
<div class="step-panel-eyebrow-line"></div>
Step 5 of 5
<span data-i18n-html="wizard.step_of">Step 5 of 5</span>
</div>
<h1 class="step-panel-title">Mapping and execution</h1>
<p class="step-panel-subtitle">Define how MCP tool arguments map to upstream request fields, and how the upstream response maps back to MCP output. Then set execution parameters — auth is configured per-upstream in step 3.</p>
<h1 class="step-panel-title" data-i18n="wizard.step5.title">Mapping and execution</h1>
<p class="step-panel-subtitle" data-i18n="wizard.step5.subtitle">Define how MCP tool arguments map to upstream request fields, and how the upstream response maps back to MCP output. Then set execution parameters — auth is configured per-upstream in step 3.</p>
</div>
<div class="section-divider" style="margin-bottom: 16px;">
+48 -48
View File
@@ -31,7 +31,7 @@
</a>
<a href="javascript:history.back()" class="ws-setup-back">
<svg width="13" height="13" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><path d="M10 4l-4 4 4 4"/></svg>
Back
<span data-i18n="workspace_setup.back">Back</span>
</a>
</div>
@@ -40,18 +40,18 @@
<div class="ws-setup-container">
<div class="ws-setup-header">
<div class="ws-setup-title" id="page-title">Workspace settings</div>
<div class="ws-setup-subtitle" id="page-subtitle">Configure your workspace identity, default settings, and team members.</div>
<div class="ws-setup-title" id="page-title" data-i18n="workspace_setup.title">Workspace settings</div>
<div class="ws-setup-subtitle" id="page-subtitle" data-i18n="workspace_setup.page_subtitle">Configure your workspace identity, default settings, and team members.</div>
</div>
<!-- ── Identity ── -->
<div class="ws-form-section">
<div class="ws-form-section-title">Workspace identity</div>
<div class="ws-form-section-title" data-i18n="workspace_setup.identity.title">Workspace identity</div>
<div class="ws-avatar-row">
<div class="ws-avatar-preview" id="ws-avatar-preview">A</div>
<div>
<div class="ws-avatar-hint">Avatar is derived from your workspace name.</div>
<div class="ws-avatar-hint" data-i18n="workspace_setup.identity.avatar_hint">Avatar is derived from your workspace name.</div>
<div class="ws-color-swatches">
<div class="ws-color-swatch active" style="background:#0d9488;" data-color="#0d9488" onclick="pickColor(this)" title="Teal"></div>
<div class="ws-color-swatch" style="background:#7c3aed;" data-color="#7c3aed" onclick="pickColor(this)" title="Purple"></div>
@@ -65,41 +65,41 @@
</div>
<div class="form-group" style="margin-bottom:14px;">
<label class="form-label">Workspace name <span class="form-label-required">required</span></label>
<label class="form-label"><span data-i18n="workspace_setup.name">Workspace name</span> <span class="form-label-required" data-i18n="workspace_setup.required">required</span></label>
<input class="form-input" id="ws-name" type="text" placeholder="Acme Inc" autocomplete="off" oninput="onWsNameInput(this.value)">
</div>
<div class="form-group" style="margin-bottom:14px;">
<label class="form-label">Slug <span class="form-label-required">required</span></label>
<label class="form-label"><span data-i18n="workspace_setup.slug">Slug</span> <span class="form-label-required" data-i18n="workspace_setup.required">required</span></label>
<div style="position:relative;">
<span style="position:absolute;left:12px;top:50%;transform:translateY(-50%);font-size:13px;color:var(--text-muted);font-family:monospace;pointer-events:none;">mcp.crank.io/</span>
<input class="form-input input-mono" id="ws-slug" type="text" placeholder="acme-inc" autocomplete="off" style="padding-left: 112px;" oninput="onWsSlugInput(this.value)">
</div>
<div class="form-hint">Only lowercase letters, numbers, and hyphens. Used in MCP endpoint URLs.</div>
<div class="form-hint" data-i18n="workspace_setup.slug_hint">Only lowercase letters, numbers, and hyphens. Used in MCP endpoint URLs.</div>
</div>
<div class="form-group">
<label class="form-label">Description <span class="form-label-optional">(optional)</span></label>
<textarea class="form-textarea" id="ws-desc" rows="2" placeholder="What does this workspace do?"></textarea>
<label class="form-label"><span data-i18n="workspace_setup.description">Description</span> <span class="form-label-optional" data-i18n="workspace_setup.optional">(optional)</span></label>
<textarea class="form-textarea" id="ws-desc" rows="2" data-i18n-ph="workspace_setup.description_placeholder" placeholder="What does this workspace do?"></textarea>
</div>
</div>
<!-- ── Default settings ── -->
<div class="ws-form-section">
<div class="ws-form-section-title">Default settings</div>
<div class="ws-form-section-title" data-i18n="workspace_setup.defaults.title">Default settings</div>
<div class="form-row">
<div class="form-group">
<label class="form-label">Default protocol</label>
<label class="form-label" data-i18n="workspace_setup.protocol">Default protocol</label>
<select class="form-input" id="ws-protocol">
<option value="rest" selected>REST</option>
<option value="graphql">GraphQL</option>
<option value="grpc">gRPC</option>
</select>
<div class="form-hint">Pre-selected in the operation wizard.</div>
<div class="form-hint" data-i18n="workspace_setup.protocol_hint">Pre-selected in the operation wizard.</div>
</div>
<div class="form-group">
<label class="form-label">Timezone</label>
<label class="form-label" data-i18n="workspace_setup.timezone">Timezone</label>
<select class="form-input" id="ws-timezone">
<option value="UTC" selected>UTC</option>
<option value="America/New_York">Eastern Time</option>
@@ -114,7 +114,7 @@
</div>
<div class="form-group" style="margin-top:4px;margin-bottom:0;">
<label class="form-label">Interface language</label>
<label class="form-label" data-i18n="workspace_setup.language">Interface language</label>
<div class="lang-switcher">
<button class="lang-btn active" data-lang="en" onclick="setLang('en')">
<span class="lang-flag">🇬🇧</span>
@@ -132,12 +132,12 @@
<div id="section-members" class="ws-form-section" style="display:none; padding:0; overflow:hidden;">
<div style="display:flex;align-items:center;justify-content:space-between;padding:20px 24px 16px;">
<div>
<div class="ws-form-section-title" style="margin-bottom:2px;">Team members</div>
<div class="ws-form-section-title" style="margin-bottom:2px;" data-i18n="workspace_setup.members.title">Team members</div>
<div style="font-size:12px;color:var(--text-muted);" id="members-count-label">4 members</div>
</div>
<button class="btn-primary" type="button" style="padding:7px 14px;font-size:13px;" onclick="toggleInviteForm()">
<svg width="12" height="12" viewBox="0 0 16 16" fill="currentColor" style="margin-right:4px;"><path d="M7.75 2a.75.75 0 01.75.75V7h4.25a.75.75 0 010 1.5H8.5v4.25a.75.75 0 01-1.5 0V8.5H2.75a.75.75 0 010-1.5H7V2.75A.75.75 0 017.75 2z"/></svg>
Invite member
<span data-i18n="workspace_setup.members.invite">Invite member</span>
</button>
</div>
@@ -145,21 +145,21 @@
<div id="invite-form" style="display:none; padding: 0 24px 16px; border-bottom: 1px solid var(--border-subtle);">
<div style="display:flex; gap:10px; align-items:flex-end; flex-wrap:wrap;">
<div class="form-group" style="flex:1; min-width:200px; margin-bottom:0;">
<label class="form-label">Email address</label>
<label class="form-label" data-i18n="workspace_setup.members.email">Email address</label>
<input class="form-input" id="invite-email" type="email" placeholder="colleague@company.com" autocomplete="off">
</div>
<div class="form-group" style="width:140px; margin-bottom:0;">
<label class="form-label">Role</label>
<label class="form-label" data-i18n="workspace_setup.members.role">Role</label>
<select class="form-input" id="invite-role">
<option value="admin">Admin</option>
<option value="developer" selected>Developer</option>
<option value="viewer">Viewer</option>
<option value="admin" data-i18n="workspace_setup.role.admin">Admin</option>
<option value="developer" selected data-i18n="workspace_setup.role.operator">Developer</option>
<option value="viewer" data-i18n="workspace_setup.role.viewer">Viewer</option>
</select>
</div>
<button class="btn-primary" type="button" style="padding:7px 14px;font-size:13px;" onclick="sendInvite()">Send invite</button>
<button class="btn-ghost-sm" type="button" style="padding:7px 12px;" onclick="toggleInviteForm()">Cancel</button>
<button class="btn-primary" type="button" style="padding:7px 14px;font-size:13px;" onclick="sendInvite()" data-i18n="workspace_setup.members.send_invite">Send invite</button>
<button class="btn-ghost-sm" type="button" style="padding:7px 12px;" onclick="toggleInviteForm()" data-i18n="workspace_setup.members.cancel">Cancel</button>
</div>
<div id="invite-success" style="display:none; margin-top:10px; font-size:13px; color: #3fb950;">✓ Invite sent.</div>
<div id="invite-success" style="display:none; margin-top:10px; font-size:13px; color: #3fb950;" data-i18n="workspace_setup.members.invite_sent">✓ Invite sent.</div>
</div>
<!-- Members list -->
@@ -226,7 +226,7 @@
<!-- Pending invites -->
<div id="pending-invites" style="border-top: 1px solid var(--border-subtle); padding: 12px 24px 16px;">
<div style="font-size:11px;font-weight:600;text-transform:uppercase;letter-spacing:0.06em;color:var(--text-muted);margin-bottom:10px;">
Pending invites <span id="pending-count" style="background:rgba(139,148,158,0.15);border-radius:10px;padding:1px 7px;font-size:10px;">1</span>
<span data-i18n="workspace_setup.members.pending">Pending invites</span> <span id="pending-count" style="background:rgba(139,148,158,0.15);border-radius:10px;padding:1px 7px;font-size:10px;">1</span>
</div>
<div class="member-row" id="invite-john">
<div class="member-avatar" style="background:var(--bg-canvas);border:1.5px dashed var(--border);color:var(--text-muted);font-size:16px;">?</div>
@@ -244,22 +244,22 @@
<!-- Roles reference -->
<div style="border-top:1px solid var(--border-subtle);padding:16px 24px 20px;">
<div style="font-size:12px;font-weight:600;color:var(--text-muted);text-transform:uppercase;letter-spacing:0.06em;margin-bottom:14px;">Roles &amp; permissions</div>
<div style="font-size:12px;font-weight:600;color:var(--text-muted);text-transform:uppercase;letter-spacing:0.06em;margin-bottom:14px;" data-i18n="workspace_setup.roles.title">Roles &amp; permissions</div>
<div class="role-def-row">
<span class="member-role-badge role-owner" style="min-width:80px;text-align:center;">Owner</span>
<div class="role-def-text">Full workspace control, billing access, can transfer or delete the workspace.</div>
<div class="role-def-text" data-i18n="workspace_setup.roles.owner_body">Full workspace control, billing access, can transfer or delete the workspace.</div>
</div>
<div class="role-def-row">
<span class="member-role-badge role-admin" style="min-width:80px;text-align:center;">Admin</span>
<div class="role-def-text">Manage members and roles, create and edit all operations and agents, manage API keys.</div>
<div class="role-def-text" data-i18n="workspace_setup.roles.admin_body">Manage members and roles, create and edit all operations and agents, manage API keys.</div>
</div>
<div class="role-def-row">
<span class="member-role-badge role-developer" style="min-width:80px;text-align:center;">Developer</span>
<div class="role-def-text">Create and edit operations and agents. Cannot manage members or workspace settings.</div>
<div class="role-def-text" data-i18n="workspace_setup.roles.developer_body">Create and edit operations and agents. Cannot manage members or workspace settings.</div>
</div>
<div class="role-def-row">
<span class="member-role-badge role-viewer" style="min-width:80px;text-align:center;">Viewer</span>
<div class="role-def-text">Read-only access to operations, agents, and logs.</div>
<div class="role-def-text" data-i18n="workspace_setup.roles.viewer_body">Read-only access to operations, agents, and logs.</div>
</div>
</div>
</div>
@@ -267,19 +267,19 @@
<!-- ── Invite team members (create mode only) ── -->
<div id="section-invite" class="ws-form-section" style="display:none;">
<div style="display:flex;align-items:center;justify-content:space-between;margin-bottom:4px;">
<div class="ws-form-section-title" style="margin-bottom:0;">Invite team members <span style="font-size:12px;font-weight:400;color:var(--text-muted);">(optional)</span></div>
<button class="btn-ghost-sm" type="button" onclick="addInviteRow()" style="font-size:12px;padding:4px 10px;">+ Add person</button>
<div class="ws-form-section-title" style="margin-bottom:0;"><span data-i18n="workspace_setup.invite.title">Invite team members</span> <span style="font-size:12px;font-weight:400;color:var(--text-muted);" data-i18n="workspace_setup.optional">(optional)</span></div>
<button class="btn-ghost-sm" type="button" onclick="addInviteRow()" style="font-size:12px;padding:4px 10px;" data-i18n="workspace_setup.invite.add_person">+ Add person</button>
</div>
<div class="form-hint" style="margin-bottom:14px;">You can invite more people later from Workspace settings.</div>
<div class="form-hint" style="margin-bottom:14px;" data-i18n="workspace_setup.invite.later">You can invite more people later from Workspace settings.</div>
<div class="invite-rows" id="invite-rows">
<div class="invite-row">
<input class="form-input" type="email" placeholder="colleague@company.com" autocomplete="off" style="flex:1;margin-bottom:0;">
<select class="form-input" style="width:130px;margin-bottom:0;">
<option value="admin">Admin</option>
<option value="developer" selected>Developer</option>
<option value="viewer">Viewer</option>
<option value="admin" data-i18n="workspace_setup.role.admin">Admin</option>
<option value="developer" selected data-i18n="workspace_setup.role.operator">Developer</option>
<option value="viewer" data-i18n="workspace_setup.role.viewer">Viewer</option>
</select>
<button class="invite-row-remove" onclick="removeInviteRow(this)" title="Remove">
<button class="invite-row-remove" onclick="removeInviteRow(this)" data-i18n-title="workspace_setup.members.remove" title="Remove">
<svg width="12" height="12" viewBox="0 0 12 12" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round"><line x1="1" y1="1" x2="11" y2="11"/><line x1="11" y1="1" x2="1" y2="11"/></svg>
</button>
</div>
@@ -288,33 +288,33 @@
<!-- ── Actions ── -->
<div class="ws-setup-actions">
<a href="javascript:history.back()" class="btn-ghost-sm" style="padding:9px 18px;font-size:13px;text-decoration:none;color:var(--text-secondary);">Cancel</a>
<button class="btn-primary" id="submit-btn" type="button" onclick="submitForm()" style="padding:9px 22px;font-size:13px;">Save changes</button>
<a href="javascript:history.back()" class="btn-ghost-sm" style="padding:9px 18px;font-size:13px;text-decoration:none;color:var(--text-secondary);" data-i18n="btn.cancel">Cancel</a>
<button class="btn-primary" id="submit-btn" type="button" onclick="submitForm()" style="padding:9px 22px;font-size:13px;" data-i18n="workspace_setup.actions.save">Save changes</button>
</div>
<div class="ws-setup-footer-note" id="footer-note" style="display:none;">
You'll be the Owner of this workspace. You can invite additional members after creation.
<span data-i18n="workspace_setup.create.footer">You'll be the Owner of this workspace. You can invite additional members after creation.</span>
</div>
<!-- ── Danger zone (edit mode only) ── -->
<div id="section-danger" style="display:none; margin-top:32px;">
<div class="ws-form-section" style="border-color: rgba(248,81,73,0.25); padding:0; overflow:hidden;">
<div style="padding:16px 24px; border-bottom: 1px solid rgba(248,81,73,0.15);">
<div style="font-size:14px;font-weight:600;color:var(--red);">Danger zone</div>
<div style="font-size:14px;font-weight:600;color:var(--red);" data-i18n="workspace_setup.danger.title">Danger zone</div>
</div>
<div class="danger-zone-action">
<div class="danger-zone-text">
<div class="danger-zone-title">Export all data</div>
<div class="danger-zone-desc">Download a JSON snapshot of workspace settings, memberships, invitations, operations, agents and platform API keys.</div>
<div class="danger-zone-title" data-i18n="workspace_setup.danger.export_title">Export all data</div>
<div class="danger-zone-desc" data-i18n="workspace_setup.danger.export_body">Download a JSON snapshot of workspace settings, memberships, invitations, operations, agents and platform API keys.</div>
</div>
<button class="btn-danger" id="export-workspace-btn" type="button">Export</button>
<button class="btn-danger" id="export-workspace-btn" type="button" data-i18n="workspace_setup.export">Export</button>
</div>
<div class="danger-zone-action">
<div class="danger-zone-text">
<div class="danger-zone-title">Delete workspace</div>
<div class="danger-zone-desc">Permanently deletes all operations, keys, logs and settings. This action cannot be undone. You will be logged out immediately.</div>
<div class="danger-zone-title" data-i18n="workspace_setup.danger.delete_title">Delete workspace</div>
<div class="danger-zone-desc" data-i18n="workspace_setup.danger.delete_body">Permanently deletes all operations, keys, logs and settings. This action cannot be undone. You will be logged out immediately.</div>
</div>
<button class="btn-danger" id="delete-workspace-btn" type="button">Delete workspace</button>
<button class="btn-danger" id="delete-workspace-btn" type="button" data-i18n="workspace_setup.delete">Delete workspace</button>
</div>
</div>
</div>