refactor: pivot from ssh proxy to distributed api model

- Delete SSHFileAdapter, IFileAdapter, LocalFileAdapter (SSH/SFTP stack)
- Remove ssh2, ssh2-sftp-client dependencies; add http-proxy-middleware, http-proxy
- NodeRegistry: remote nodes no longer use Dockerode TCP; new getProxyTarget() returns {apiUrl, apiToken}
- NodeRegistry.testConnection: remote nodes use HTTP GET /api/auth/check instead of docker.info()
- DatabaseService: Node interface swaps SSH/TLS fields for api_url + api_token; legacy columns preserved for DB compat
- FileSystemService: reverted to clean local-only fs.promises; adapter pattern fully removed
- ComposeService: executeRemote() and SSH log streaming deleted; local-only execution remains
- index.ts: add /api/auth/generate-node-token endpoint (long-lived JWT, scope:node_proxy)
- index.ts: authMiddleware now accepts Bearer token in addition to cookie (Sencho-to-Sencho auth)
- index.ts: remote HTTP proxy middleware intercepts all /api/ requests for remote nodes, strips x-node-id, injects Authorization header, proxies to api_url
- index.ts: WS upgrade handler proxies WebSocket connections for remote nodes via http-proxy wsProxyServer
- NodeManager.tsx: form reduced to Name, API URL, API Token; Generate Node Token button added inline
- NodeContext.tsx: Node interface updated to api_url/api_token

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
SaelixCode
2026-03-19 12:20:32 -04:00
parent 23730430d7
commit c91c9ed7fd
14 changed files with 522 additions and 899 deletions
+2 -12
View File
@@ -5,19 +5,12 @@ export interface Node {
id: number;
name: string;
type: 'local' | 'remote';
host: string;
port: number;
ssh_port: number;
compose_dir: string;
is_default: boolean;
status: 'online' | 'offline' | 'unknown';
created_at: number;
ssh_user?: string;
ssh_password?: string;
ssh_key?: string;
tls_ca?: string;
tls_cert?: string;
tls_key?: string;
api_url?: string;
api_token?: string;
}
interface NodeContextType {
@@ -42,7 +35,6 @@ export function NodeProvider({ children }: { children: React.ReactNode }) {
const data = await res.json();
setNodes(data);
// If no active node is set, select the default node
if (!activeNode) {
const defaultNode = data.find((n: Node) => n.is_default);
if (defaultNode) {
@@ -51,12 +43,10 @@ export function NodeProvider({ children }: { children: React.ReactNode }) {
setActiveNodeState(data[0]);
}
} else {
// Refresh the active node's data in case its status changed
const updatedActive = data.find((n: Node) => n.id === activeNode.id);
if (updatedActive) {
setActiveNodeState(updatedActive);
} else {
// The active node was deleted! Fallback to default
const defaultNode = data.find((n: Node) => n.is_default);
if (defaultNode) {
setActiveNodeState(defaultNode);