Fleet P2b: run agent sandboxes on connected nodes (RemoteDriver + placement)
Agents can now provision their sandbox on a connected fleet node instead of the gateway host. Local stays the strict default, so existing agents are byte-for- byte unaffected until explicitly placed elsewhere. Security parity: the daemon links the REAL cm-sandbox DockerDriver and runs the typed container ops (sb_provision/sb_exec/sb_destroy/sb_health/sb_list) through it — identical hardening (cap-drop ALL, seccomp, no-net, read-only, non-root) to local sandboxes. cm-sandbox spec types are now Serialize/Deserialize so the spec crosses the channel. - cm-api: RemoteDriver (impl SandboxDriver over the node channel) + HubDriverProvider (impl cm_runtime::NodeDriverProvider, hands out a driver only for connected nodes via a sync online set) + NodeHub.call/is_connected. AppState.with_node_hub so the hub is shared with the placement provider. - cm-runtime SandboxManager: driver_for(node_id) routes by the recorded agent_containers.node_id (local default = existing driver, identical path); placement_node() reads the workspace setting and falls back to local if the node is offline; exec/release route accordingly. NodeDriverProvider trait. - DB: 0020_workspace_placement + repo (for_agent/get/set/clear). - main.rs: build the NodeHub first; inject HubDriverProvider into the agent manager + share the hub with AppState. - API+UI: GET/PUT /api/fleet/placement + a "Run agents on: Local / <node>" selector in the Fleet overview. Note: a node must be able to pull the agent image (the daemon docker-pulls it); interactive PTY for agent containers on remote nodes is not wired (Terminal app stays local) — the in-dashboard node shell already covers host access. Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
33aa9c0693
commit
fb59378aa2
@@ -0,0 +1,146 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<script src="./support.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<x-dc>
|
||||
<helmet>
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||
<link href="https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;500;600;700&display=swap" rel="stylesheet">
|
||||
<style>
|
||||
* { box-sizing: border-box; }
|
||||
html, body { margin: 0; padding: 0; background: #08080a; }
|
||||
body { font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; -webkit-font-smoothing: antialiased; }
|
||||
input { outline: none; font-family: inherit; }
|
||||
input::placeholder { color: #5a5a62; }
|
||||
@keyframes cm-flow { to { stroke-dashoffset: -24; } }
|
||||
@keyframes cm-blink { 0%,100% { opacity: 1; } 50% { opacity: .3; } }
|
||||
@keyframes cm-halo { 0% { transform: scale(.7); opacity: .5; } 100% { transform: scale(1.9); opacity: 0; } }
|
||||
</style>
|
||||
</helmet>
|
||||
|
||||
<div style="display:flex; width:100%; min-height:100vh; background:#08080a; color:#f3f3f5;">
|
||||
|
||||
<!-- LEFT BRAND PANEL -->
|
||||
<div style="flex:1; position:relative; border-right:1px solid rgba(255,255,255,.06); background:radial-gradient(120% 90% at 35% 30%, #0f0f14, #08080a 72%); padding:40px; display:flex; flex-direction:column; overflow:hidden;">
|
||||
<div style="display:flex; align-items:center; gap:10px; position:relative; z-index:2;">
|
||||
<svg width="24" height="24" viewBox="0 0 22 22" fill="none"><path d="M11 3 L18.5 17 L3.5 17 Z" stroke="#ff6f61" stroke-width="1.3" stroke-linejoin="round" opacity="0.55"></path><circle cx="11" cy="3.5" r="2.4" fill="#ff6f61"></circle><circle cx="18" cy="17" r="2.4" fill="#ff6f61"></circle><circle cx="4" cy="17" r="2.4" fill="#ff6f61"></circle></svg>
|
||||
<span style="font-size:17px; font-weight:700; letter-spacing:-.01em;">Clawmates</span>
|
||||
</div>
|
||||
|
||||
<div style="flex:1; position:relative; min-height:280px;">
|
||||
<svg viewBox="0 0 100 100" preserveAspectRatio="none" style="position:absolute; inset:0; width:100%; height:100%;">
|
||||
<path d="M50,52 C40,38 34,32 24,26" fill="none" stroke="rgba(94,200,216,.45)" stroke-width="1.3" stroke-dasharray="3 4" vector-effect="non-scaling-stroke" style="animation:cm-flow 1.2s linear infinite;"></path>
|
||||
<path d="M50,52 C40,64 34,70 26,76" fill="none" stroke="rgba(94,200,216,.45)" stroke-width="1.3" stroke-dasharray="3 4" vector-effect="non-scaling-stroke" style="animation:cm-flow 1.5s linear infinite;"></path>
|
||||
<path d="M50,52 C52,40 53,30 54,20" fill="none" stroke="rgba(255,255,255,.1)" stroke-width="1.2" vector-effect="non-scaling-stroke"></path>
|
||||
<path d="M50,52 C64,46 72,40 78,32" fill="none" stroke="rgba(255,255,255,.1)" stroke-width="1.2" vector-effect="non-scaling-stroke"></path>
|
||||
<path d="M50,52 C64,58 72,66 78,74" fill="none" stroke="rgba(255,111,97,.55)" stroke-width="1.5" stroke-dasharray="3 4" vector-effect="non-scaling-stroke" style="animation:cm-flow .9s linear infinite;"></path>
|
||||
</svg>
|
||||
<div style="position:absolute; left:50%; top:52%; transform:translate(-50%,-50%); width:52px; height:52px; border-radius:50%; background:linear-gradient(135deg,#ff9a6a,#ff6f4a); display:flex; align-items:center; justify-content:center; font-size:19px; font-weight:700; color:#2a0d05; box-shadow:0 0 36px rgba(255,111,97,.5);">A</div>
|
||||
<div style="position:absolute; left:24%; top:26%; transform:translate(-50%,-50%); width:36px; height:36px; border-radius:50%;"><div style="position:absolute; inset:0; border-radius:50%; background:rgba(94,200,216,.4); animation:cm-halo 1.9s ease-out infinite;"></div><div style="position:relative; width:36px; height:36px; border-radius:50%; background:linear-gradient(135deg,#6fd0c0,#4aa3b8); display:flex; align-items:center; justify-content:center; font-size:13px; font-weight:700; color:#06201f;">I</div></div>
|
||||
<div style="position:absolute; left:54%; top:20%; transform:translate(-50%,-50%); width:32px; height:32px; border-radius:50%; background:linear-gradient(135deg,#e8c46a,#d89a3a); display:flex; align-items:center; justify-content:center; font-size:12px; font-weight:700; color:#2a1d05;">N</div>
|
||||
<div style="position:absolute; left:78%; top:32%; transform:translate(-50%,-50%); width:32px; height:32px; border-radius:50%; background:linear-gradient(135deg,#c98af0,#9a5ad8); display:flex; align-items:center; justify-content:center; font-size:12px; font-weight:700; color:#1a0a2a; opacity:.7;">S</div>
|
||||
<div style="position:absolute; left:26%; top:76%; transform:translate(-50%,-50%); width:32px; height:32px; border-radius:50%; background:linear-gradient(135deg,#8a9af0,#5a6ad8); display:flex; align-items:center; justify-content:center; font-size:12px; font-weight:700; color:#0a0e2a;">E</div>
|
||||
<div style="position:absolute; left:78%; top:74%; transform:translate(-50%,-50%); width:38px; height:38px; border-radius:50%; background:linear-gradient(135deg,#ff8a7a,#ff5f57); display:flex; align-items:center; justify-content:center; font-size:14px; font-weight:700; color:#2a0d0a; border:2px solid #ff6f61;">M</div>
|
||||
</div>
|
||||
|
||||
<div style="position:relative; z-index:2; max-width:380px;">
|
||||
<div style="font-size:24px; font-weight:700; letter-spacing:-.02em; line-height:1.25; margin-bottom:10px; text-wrap:balance;">Deploy agents at any scale — a single claw to a whole org.</div>
|
||||
<div style="font-family:'JetBrains Mono',monospace; font-size:11px; color:#6a6a72; letter-spacing:.04em;">12 topologies · durable runner · §15-safe</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- RIGHT FORM PANEL -->
|
||||
<div style="flex:none; width:520px; max-width:48vw; display:flex; align-items:center; justify-content:center; padding:40px;">
|
||||
<div style="width:100%; max-width:380px;">
|
||||
|
||||
<h1 style="font-size:28px; font-weight:700; letter-spacing:-.02em; margin:0 0 8px;">{{ title }}</h1>
|
||||
<p style="font-size:15px; color:#a8a8b0; margin:0 0 28px;">{{ subtitle }}</p>
|
||||
|
||||
<!-- OAUTH -->
|
||||
<div style="display:flex; flex-direction:column; gap:10px;">
|
||||
<div style="display:flex; align-items:center; justify-content:center; gap:10px; height:46px; border-radius:10px; border:1px solid rgba(255,255,255,.12); background:#141417; font-size:14px; font-weight:600; color:#f3f3f5; cursor:pointer; transition:background .15s;" style-hover="background:#1a1a1f;">
|
||||
<svg width="18" height="18" viewBox="0 0 18 18"><path fill="#4285F4" d="M17.64 9.2c0-.64-.06-1.25-.16-1.84H9v3.48h4.84a4.14 4.14 0 01-1.8 2.72v2.26h2.92c1.7-1.57 2.68-3.88 2.68-6.62z"></path><path fill="#34A853" d="M9 18c2.43 0 4.47-.8 5.96-2.18l-2.92-2.26c-.8.54-1.84.86-3.04.86-2.34 0-4.32-1.58-5.03-3.7H.96v2.33A9 9 0 009 18z"></path><path fill="#FBBC05" d="M3.97 10.72a5.4 5.4 0 010-3.44V4.95H.96a9 9 0 000 8.1l3.01-2.33z"></path><path fill="#EA4335" d="M9 3.58c1.32 0 2.5.45 3.44 1.35l2.58-2.58A9 9 0 00.96 4.95l3.01 2.33C4.68 5.16 6.66 3.58 9 3.58z"></path></svg>
|
||||
Continue with Google
|
||||
</div>
|
||||
<div style="display:flex; align-items:center; justify-content:center; gap:10px; height:46px; border-radius:10px; border:1px solid rgba(255,255,255,.12); background:#141417; font-size:14px; font-weight:600; color:#f3f3f5; cursor:pointer; transition:background .15s;" style-hover="background:#1a1a1f;">
|
||||
<svg width="17" height="17" viewBox="0 0 24 24" fill="#fff"><path d="M16.36 12.78c.02 2.5 2.2 3.33 2.23 3.34-.02.06-.35 1.2-1.16 2.37-.7 1.02-1.43 2.03-2.58 2.05-1.13.02-1.5-.67-2.78-.67-1.29 0-1.69.65-2.76.69-1.11.04-1.96-1.1-2.66-2.11-1.45-2.1-2.56-5.92-1.07-8.51.74-1.28 2.06-2.1 3.49-2.12 1.09-.02 2.12.73 2.78.73.67 0 1.92-.9 3.24-.77.55.02 2.1.22 3.1 1.68-.08.05-1.85 1.08-1.83 3.22zM14.2 5.18c.59-.71.99-1.7.88-2.68-.85.03-1.88.57-2.49 1.28-.55.63-1.03 1.63-.9 2.6.95.07 1.92-.48 2.51-1.2z"></path></svg>
|
||||
Continue with Apple
|
||||
</div>
|
||||
<div style="display:flex; align-items:center; justify-content:center; gap:10px; height:46px; border-radius:10px; border:1px solid rgba(255,255,255,.12); background:#141417; font-size:14px; font-weight:600; color:#f3f3f5; cursor:pointer; transition:background .15s;" style-hover="background:#1a1a1f;">
|
||||
<svg width="17" height="17" viewBox="0 0 24 24" fill="#fff"><path d="M12 .5C5.37.5 0 5.87 0 12.5c0 5.3 3.44 9.8 8.21 11.39.6.11.82-.26.82-.58v-2.03c-3.34.73-4.04-1.61-4.04-1.61-.55-1.39-1.34-1.76-1.34-1.76-1.09-.75.08-.73.08-.73 1.2.09 1.84 1.24 1.84 1.24 1.07 1.83 2.81 1.3 3.5.99.11-.78.42-1.3.76-1.6-2.67-.3-5.47-1.33-5.47-5.93 0-1.31.47-2.38 1.24-3.22-.12-.3-.54-1.52.12-3.18 0 0 1.01-.32 3.3 1.23a11.5 11.5 0 016 0c2.29-1.55 3.3-1.23 3.3-1.23.66 1.66.24 2.88.12 3.18.77.84 1.24 1.91 1.24 3.22 0 4.61-2.81 5.62-5.49 5.92.43.37.81 1.1.81 2.22v3.29c0 .32.22.7.83.58A12.01 12.01 0 0024 12.5C24 5.87 18.63.5 12 .5z"></path></svg>
|
||||
Continue with GitHub
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- DIVIDER -->
|
||||
<div style="display:flex; align-items:center; gap:14px; margin:22px 0;">
|
||||
<div style="flex:1; height:1px; background:rgba(255,255,255,.08);"></div>
|
||||
<span style="font-family:'JetBrains Mono',monospace; font-size:10px; letter-spacing:.1em; color:#5a5a62;">OR</span>
|
||||
<div style="flex:1; height:1px; background:rgba(255,255,255,.08);"></div>
|
||||
</div>
|
||||
|
||||
<!-- EMAIL FORM -->
|
||||
<div style="display:flex; flex-direction:column; gap:14px;">
|
||||
<sc-if value="{{ isSignup }}" hint-placeholder-val="{{ false }}">
|
||||
<div>
|
||||
<div style="font-size:12px; font-weight:600; color:#b5b5bd; margin-bottom:7px;">Full name</div>
|
||||
<input type="text" placeholder="Ada Lovelace" style="width:100%; height:44px; border-radius:10px; border:1px solid rgba(255,255,255,.12); background:#0d0d10; color:#f3f3f5; font-size:14px; padding:0 14px;" style-focus="border-color:#ff6f61;">
|
||||
</div>
|
||||
</sc-if>
|
||||
<div>
|
||||
<div style="font-size:12px; font-weight:600; color:#b5b5bd; margin-bottom:7px;">Email address</div>
|
||||
<input type="email" placeholder="[email protected]" style="width:100%; height:44px; border-radius:10px; border:1px solid rgba(255,255,255,.12); background:#0d0d10; color:#f3f3f5; font-size:14px; padding:0 14px;" style-focus="border-color:#ff6f61;">
|
||||
</div>
|
||||
<div>
|
||||
<div style="display:flex; align-items:center; margin-bottom:7px;">
|
||||
<span style="font-size:12px; font-weight:600; color:#b5b5bd;">Password</span>
|
||||
<span style="flex:1;"></span>
|
||||
<sc-if value="{{ isSignin }}" hint-placeholder-val="{{ true }}">
|
||||
<span style="font-size:12px; color:#ff8a7a; cursor:pointer;">Forgot?</span>
|
||||
</sc-if>
|
||||
</div>
|
||||
<input type="password" placeholder="••••••••••" style="width:100%; height:44px; border-radius:10px; border:1px solid rgba(255,255,255,.12); background:#0d0d10; color:#f3f3f5; font-size:14px; padding:0 14px;" style-focus="border-color:#ff6f61;">
|
||||
</div>
|
||||
<div style="display:flex; align-items:center; justify-content:center; height:46px; border-radius:10px; background:linear-gradient(135deg,#ff8a7a,#ff5f57); color:#2a0d0a; font-size:15px; font-weight:700; cursor:pointer; margin-top:4px;" style-hover="filter:brightness(1.07);">{{ cta }}</div>
|
||||
</div>
|
||||
|
||||
<!-- TOGGLE -->
|
||||
<div style="text-align:center; margin-top:22px; font-size:14px; color:#8a8a92;">
|
||||
{{ toggleText }} <span style="color:#ff8a7a; font-weight:600; cursor:pointer;" onClick="{{ toggle }}">{{ toggleLink }}</span>
|
||||
</div>
|
||||
|
||||
<!-- CLERK FOOTER -->
|
||||
<div style="display:flex; align-items:center; justify-content:center; gap:6px; margin-top:30px; font-family:'JetBrains Mono',monospace; font-size:10px; color:#4a4a52;">
|
||||
<svg width="11" height="11" viewBox="0 0 20 20" fill="none"><rect x="4" y="9" width="12" height="8" rx="2" stroke="#4a4a52" stroke-width="1.5"></rect><path d="M7 9V6.5a3 3 0 016 0V9" stroke="#4a4a52" stroke-width="1.5"></path></svg>
|
||||
Secured by Clerk
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</x-dc>
|
||||
<script type="text/x-dc" data-dc-script data-props="{"$preview":{"width":1100,"height":760}}">
|
||||
class Component extends DCLogic {
|
||||
state = { mode: 'signin' };
|
||||
renderVals() {
|
||||
const signup = this.state.mode === 'signup';
|
||||
return {
|
||||
isSignup: signup,
|
||||
isSignin: !signup,
|
||||
title: signup ? 'Create your account' : 'Welcome back',
|
||||
subtitle: signup ? 'Deploy your first claw in minutes.' : 'Sign in to your Clawmates workspace.',
|
||||
cta: signup ? 'Create account' : 'Sign in',
|
||||
toggleText: signup ? 'Already have an account?' : "Don't have an account?",
|
||||
toggleLink: signup ? 'Sign in' : 'Sign up',
|
||||
toggle: () => this.setState({ mode: signup ? 'signin' : 'signup' }),
|
||||
};
|
||||
}
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user