mirror of
https://github.com/Ed1s0nZ/CyberStrikeAI.git
synced 2026-05-17 05:33:32 +02:00
Compare commits
11 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 3cea834036 | |||
| e1b594f875 | |||
| 4b105e0bb7 | |||
| 93f0a46d6e | |||
| 314cd005c8 | |||
| c68b72ead2 | |||
| 60846b2152 | |||
| f6525674d2 | |||
| 9c04b0db40 | |||
| 907b87494d | |||
| 97b7b4b932 |
+1
-1
@@ -10,7 +10,7 @@
|
|||||||
# ============================================
|
# ============================================
|
||||||
|
|
||||||
# 前端显示的版本号(可选,不填则显示默认版本)
|
# 前端显示的版本号(可选,不填则显示默认版本)
|
||||||
version: "v1.4.1"
|
version: "v1.4.2"
|
||||||
# 服务器配置
|
# 服务器配置
|
||||||
server:
|
server:
|
||||||
host: 0.0.0.0 # 监听地址,0.0.0.0 表示监听所有网络接口
|
host: 0.0.0.0 # 监听地址,0.0.0.0 表示监听所有网络接口
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ go 1.24.0
|
|||||||
toolchain go1.24.4
|
toolchain go1.24.4
|
||||||
|
|
||||||
require (
|
require (
|
||||||
|
github.com/bytedance/sonic v1.15.0
|
||||||
github.com/cloudwego/eino v0.8.4
|
github.com/cloudwego/eino v0.8.4
|
||||||
github.com/cloudwego/eino-ext/components/model/openai v0.1.10
|
github.com/cloudwego/eino-ext/components/model/openai v0.1.10
|
||||||
github.com/creack/pty v1.1.24
|
github.com/creack/pty v1.1.24
|
||||||
@@ -30,7 +31,6 @@ require (
|
|||||||
github.com/bmatcuk/doublestar/v4 v4.10.0 // indirect
|
github.com/bmatcuk/doublestar/v4 v4.10.0 // indirect
|
||||||
github.com/buger/jsonparser v1.1.1 // indirect
|
github.com/buger/jsonparser v1.1.1 // indirect
|
||||||
github.com/bytedance/gopkg v0.1.3 // indirect
|
github.com/bytedance/gopkg v0.1.3 // indirect
|
||||||
github.com/bytedance/sonic v1.15.0 // indirect
|
|
||||||
github.com/bytedance/sonic/loader v0.5.0 // indirect
|
github.com/bytedance/sonic/loader v0.5.0 // indirect
|
||||||
github.com/cloudwego/base64x v0.1.6 // indirect
|
github.com/cloudwego/base64x v0.1.6 // indirect
|
||||||
github.com/cloudwego/eino-ext/libs/acl/openai v0.1.14 // indirect
|
github.com/cloudwego/eino-ext/libs/acl/openai v0.1.14 // indirect
|
||||||
|
|||||||
@@ -862,7 +862,9 @@ func setupRoutes(
|
|||||||
protected.POST("/webshell/connections", webshellHandler.CreateConnection)
|
protected.POST("/webshell/connections", webshellHandler.CreateConnection)
|
||||||
protected.GET("/webshell/connections/:id/ai-history", webshellHandler.GetAIHistory)
|
protected.GET("/webshell/connections/:id/ai-history", webshellHandler.GetAIHistory)
|
||||||
protected.GET("/webshell/connections/:id/ai-conversations", webshellHandler.ListAIConversations)
|
protected.GET("/webshell/connections/:id/ai-conversations", webshellHandler.ListAIConversations)
|
||||||
|
protected.GET("/webshell/connections/:id/state", webshellHandler.GetConnectionState)
|
||||||
protected.PUT("/webshell/connections/:id", webshellHandler.UpdateConnection)
|
protected.PUT("/webshell/connections/:id", webshellHandler.UpdateConnection)
|
||||||
|
protected.PUT("/webshell/connections/:id/state", webshellHandler.SaveConnectionState)
|
||||||
protected.DELETE("/webshell/connections/:id", webshellHandler.DeleteConnection)
|
protected.DELETE("/webshell/connections/:id", webshellHandler.DeleteConnection)
|
||||||
protected.POST("/webshell/exec", webshellHandler.Exec)
|
protected.POST("/webshell/exec", webshellHandler.Exec)
|
||||||
protected.POST("/webshell/file", webshellHandler.FileOp)
|
protected.POST("/webshell/file", webshellHandler.FileOp)
|
||||||
|
|||||||
@@ -240,6 +240,15 @@ func (db *DB) initTables() error {
|
|||||||
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP
|
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP
|
||||||
);`
|
);`
|
||||||
|
|
||||||
|
// 创建 WebShell 连接扩展状态表(前端工作区/终端状态持久化)
|
||||||
|
createWebshellConnectionStatesTable := `
|
||||||
|
CREATE TABLE IF NOT EXISTS webshell_connection_states (
|
||||||
|
connection_id TEXT PRIMARY KEY,
|
||||||
|
state_json TEXT NOT NULL DEFAULT '{}',
|
||||||
|
updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
FOREIGN KEY (connection_id) REFERENCES webshell_connections(id) ON DELETE CASCADE
|
||||||
|
);`
|
||||||
|
|
||||||
// 创建索引
|
// 创建索引
|
||||||
createIndexes := `
|
createIndexes := `
|
||||||
CREATE INDEX IF NOT EXISTS idx_messages_conversation_id ON messages(conversation_id);
|
CREATE INDEX IF NOT EXISTS idx_messages_conversation_id ON messages(conversation_id);
|
||||||
@@ -267,6 +276,7 @@ func (db *DB) initTables() error {
|
|||||||
CREATE INDEX IF NOT EXISTS idx_batch_task_queues_created_at ON batch_task_queues(created_at);
|
CREATE INDEX IF NOT EXISTS idx_batch_task_queues_created_at ON batch_task_queues(created_at);
|
||||||
CREATE INDEX IF NOT EXISTS idx_batch_task_queues_title ON batch_task_queues(title);
|
CREATE INDEX IF NOT EXISTS idx_batch_task_queues_title ON batch_task_queues(title);
|
||||||
CREATE INDEX IF NOT EXISTS idx_webshell_connections_created_at ON webshell_connections(created_at);
|
CREATE INDEX IF NOT EXISTS idx_webshell_connections_created_at ON webshell_connections(created_at);
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_webshell_connection_states_updated_at ON webshell_connection_states(updated_at);
|
||||||
`
|
`
|
||||||
|
|
||||||
if _, err := db.Exec(createConversationsTable); err != nil {
|
if _, err := db.Exec(createConversationsTable); err != nil {
|
||||||
@@ -329,6 +339,10 @@ func (db *DB) initTables() error {
|
|||||||
return fmt.Errorf("创建webshell_connections表失败: %w", err)
|
return fmt.Errorf("创建webshell_connections表失败: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if _, err := db.Exec(createWebshellConnectionStatesTable); err != nil {
|
||||||
|
return fmt.Errorf("创建webshell_connection_states表失败: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
// 为已有表添加新字段(如果不存在)- 必须在创建索引之前
|
// 为已有表添加新字段(如果不存在)- 必须在创建索引之前
|
||||||
if err := db.migrateConversationsTable(); err != nil {
|
if err := db.migrateConversationsTable(); err != nil {
|
||||||
db.logger.Warn("迁移conversations表失败", zap.Error(err))
|
db.logger.Warn("迁移conversations表失败", zap.Error(err))
|
||||||
|
|||||||
@@ -19,6 +19,42 @@ type WebShellConnection struct {
|
|||||||
CreatedAt time.Time `json:"createdAt"`
|
CreatedAt time.Time `json:"createdAt"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetWebshellConnectionState 获取连接关联的持久化状态 JSON,不存在时返回 "{}"
|
||||||
|
func (db *DB) GetWebshellConnectionState(connectionID string) (string, error) {
|
||||||
|
var stateJSON string
|
||||||
|
err := db.QueryRow(`SELECT state_json FROM webshell_connection_states WHERE connection_id = ?`, connectionID).Scan(&stateJSON)
|
||||||
|
if err == sql.ErrNoRows {
|
||||||
|
return "{}", nil
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
db.logger.Error("查询 WebShell 连接状态失败", zap.Error(err), zap.String("connectionID", connectionID))
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
if stateJSON == "" {
|
||||||
|
stateJSON = "{}"
|
||||||
|
}
|
||||||
|
return stateJSON, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpsertWebshellConnectionState 保存连接关联的持久化状态 JSON
|
||||||
|
func (db *DB) UpsertWebshellConnectionState(connectionID, stateJSON string) error {
|
||||||
|
if stateJSON == "" {
|
||||||
|
stateJSON = "{}"
|
||||||
|
}
|
||||||
|
query := `
|
||||||
|
INSERT INTO webshell_connection_states (connection_id, state_json, updated_at)
|
||||||
|
VALUES (?, ?, ?)
|
||||||
|
ON CONFLICT(connection_id) DO UPDATE SET
|
||||||
|
state_json = excluded.state_json,
|
||||||
|
updated_at = excluded.updated_at
|
||||||
|
`
|
||||||
|
if _, err := db.Exec(query, connectionID, stateJSON, time.Now()); err != nil {
|
||||||
|
db.logger.Error("保存 WebShell 连接状态失败", zap.Error(err), zap.String("connectionID", connectionID))
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// ListWebshellConnections 列出所有 WebShell 连接,按创建时间倒序
|
// ListWebshellConnections 列出所有 WebShell 连接,按创建时间倒序
|
||||||
func (db *DB) ListWebshellConnections() ([]WebShellConnection, error) {
|
func (db *DB) ListWebshellConnections() ([]WebShellConnection, error) {
|
||||||
query := `
|
query := `
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ package handler
|
|||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"database/sql"
|
"database/sql"
|
||||||
|
"encoding/json"
|
||||||
"io"
|
"io"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
@@ -104,10 +105,10 @@ func (h *WebShellHandler) CreateConnection(c *gin.Context) {
|
|||||||
ID: "ws_" + strings.ReplaceAll(uuid.New().String(), "-", "")[:12],
|
ID: "ws_" + strings.ReplaceAll(uuid.New().String(), "-", "")[:12],
|
||||||
URL: req.URL,
|
URL: req.URL,
|
||||||
Password: strings.TrimSpace(req.Password),
|
Password: strings.TrimSpace(req.Password),
|
||||||
Type: shellType,
|
Type: shellType,
|
||||||
Method: method,
|
Method: method,
|
||||||
CmdParam: strings.TrimSpace(req.CmdParam),
|
CmdParam: strings.TrimSpace(req.CmdParam),
|
||||||
Remark: strings.TrimSpace(req.Remark),
|
Remark: strings.TrimSpace(req.Remark),
|
||||||
CreatedAt: time.Now(),
|
CreatedAt: time.Now(),
|
||||||
}
|
}
|
||||||
if err := h.db.CreateWebshellConnection(conn); err != nil {
|
if err := h.db.CreateWebshellConnection(conn); err != nil {
|
||||||
@@ -197,6 +198,85 @@ func (h *WebShellHandler) DeleteConnection(c *gin.Context) {
|
|||||||
c.JSON(http.StatusOK, gin.H{"ok": true})
|
c.JSON(http.StatusOK, gin.H{"ok": true})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetConnectionState 获取 WebShell 连接关联的前端持久化状态(GET /api/webshell/connections/:id/state)
|
||||||
|
func (h *WebShellHandler) GetConnectionState(c *gin.Context) {
|
||||||
|
if h.db == nil {
|
||||||
|
c.JSON(http.StatusServiceUnavailable, gin.H{"error": "database not available"})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
id := strings.TrimSpace(c.Param("id"))
|
||||||
|
if id == "" {
|
||||||
|
c.JSON(http.StatusBadRequest, gin.H{"error": "id is required"})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
conn, err := h.db.GetWebshellConnection(id)
|
||||||
|
if err != nil {
|
||||||
|
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if conn == nil {
|
||||||
|
c.JSON(http.StatusNotFound, gin.H{"error": "connection not found"})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
stateJSON, err := h.db.GetWebshellConnectionState(id)
|
||||||
|
if err != nil {
|
||||||
|
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
var state interface{}
|
||||||
|
if err := json.Unmarshal([]byte(stateJSON), &state); err != nil {
|
||||||
|
state = map[string]interface{}{}
|
||||||
|
}
|
||||||
|
c.JSON(http.StatusOK, gin.H{"state": state})
|
||||||
|
}
|
||||||
|
|
||||||
|
// SaveConnectionState 保存 WebShell 连接关联的前端持久化状态(PUT /api/webshell/connections/:id/state)
|
||||||
|
func (h *WebShellHandler) SaveConnectionState(c *gin.Context) {
|
||||||
|
if h.db == nil {
|
||||||
|
c.JSON(http.StatusServiceUnavailable, gin.H{"error": "database not available"})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
id := strings.TrimSpace(c.Param("id"))
|
||||||
|
if id == "" {
|
||||||
|
c.JSON(http.StatusBadRequest, gin.H{"error": "id is required"})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
conn, err := h.db.GetWebshellConnection(id)
|
||||||
|
if err != nil {
|
||||||
|
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if conn == nil {
|
||||||
|
c.JSON(http.StatusNotFound, gin.H{"error": "connection not found"})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
var req struct {
|
||||||
|
State json.RawMessage `json:"state"`
|
||||||
|
}
|
||||||
|
if err := c.ShouldBindJSON(&req); err != nil {
|
||||||
|
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
raw := req.State
|
||||||
|
if len(raw) == 0 {
|
||||||
|
raw = json.RawMessage(`{}`)
|
||||||
|
}
|
||||||
|
if len(raw) > 2*1024*1024 {
|
||||||
|
c.JSON(http.StatusBadRequest, gin.H{"error": "state payload too large (max 2MB)"})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
var anyJSON interface{}
|
||||||
|
if err := json.Unmarshal(raw, &anyJSON); err != nil {
|
||||||
|
c.JSON(http.StatusBadRequest, gin.H{"error": "state must be valid json"})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if err := h.db.UpsertWebshellConnectionState(id, string(raw)); err != nil {
|
||||||
|
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
c.JSON(http.StatusOK, gin.H{"ok": true})
|
||||||
|
}
|
||||||
|
|
||||||
// GetAIHistory 获取指定 WebShell 连接的 AI 助手对话历史(GET /api/webshell/connections/:id/ai-history)
|
// GetAIHistory 获取指定 WebShell 连接的 AI 助手对话历史(GET /api/webshell/connections/:id/ai-history)
|
||||||
func (h *WebShellHandler) GetAIHistory(c *gin.Context) {
|
func (h *WebShellHandler) GetAIHistory(c *gin.Context) {
|
||||||
if h.db == nil {
|
if h.db == nil {
|
||||||
@@ -267,8 +347,8 @@ type FileOpRequest struct {
|
|||||||
URL string `json:"url" binding:"required"`
|
URL string `json:"url" binding:"required"`
|
||||||
Password string `json:"password"`
|
Password string `json:"password"`
|
||||||
Type string `json:"type"`
|
Type string `json:"type"`
|
||||||
Method string `json:"method"` // GET 或 POST,空则默认 POST
|
Method string `json:"method"` // GET 或 POST,空则默认 POST
|
||||||
CmdParam string `json:"cmd_param"` // 命令参数名,如 cmd/xxx,空则默认 cmd
|
CmdParam string `json:"cmd_param"` // 命令参数名,如 cmd/xxx,空则默认 cmd
|
||||||
Action string `json:"action" binding:"required"` // list, read, delete, write, mkdir, rename, upload, upload_chunk
|
Action string `json:"action" binding:"required"` // list, read, delete, write, mkdir, rename, upload, upload_chunk
|
||||||
Path string `json:"path"`
|
Path string `json:"path"`
|
||||||
TargetPath string `json:"target_path"` // rename 时目标路径
|
TargetPath string `json:"target_path"` // rename 时目标路径
|
||||||
|
|||||||
+617
-3
@@ -8860,6 +8860,71 @@ header {
|
|||||||
border-radius: 10px;
|
border-radius: 10px;
|
||||||
border: 1px solid var(--border-color);
|
border: 1px solid var(--border-color);
|
||||||
}
|
}
|
||||||
|
.webshell-terminal-sessions {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 2px;
|
||||||
|
padding: 0 8px;
|
||||||
|
height: 34px;
|
||||||
|
background: #0b0f14;
|
||||||
|
border-bottom: 1px solid rgba(255, 255, 255, 0.08);
|
||||||
|
overflow-x: auto;
|
||||||
|
}
|
||||||
|
.webshell-terminal-session {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
background: rgba(255, 255, 255, 0.04);
|
||||||
|
border: 1px solid rgba(255, 255, 255, 0.12);
|
||||||
|
border-bottom: none;
|
||||||
|
border-radius: 6px 6px 0 0;
|
||||||
|
height: 30px;
|
||||||
|
color: #c9d1d9;
|
||||||
|
}
|
||||||
|
.webshell-terminal-session.active {
|
||||||
|
background: #0d1117;
|
||||||
|
border-color: rgba(88, 166, 255, 0.45);
|
||||||
|
color: #e6edf3;
|
||||||
|
}
|
||||||
|
.webshell-terminal-session-main {
|
||||||
|
border: 0;
|
||||||
|
background: transparent;
|
||||||
|
color: inherit;
|
||||||
|
font-size: 12px;
|
||||||
|
height: 100%;
|
||||||
|
padding: 0 10px;
|
||||||
|
cursor: pointer;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
.webshell-terminal-session-close {
|
||||||
|
border: 0;
|
||||||
|
background: transparent;
|
||||||
|
color: #8b949e;
|
||||||
|
width: 20px;
|
||||||
|
height: 100%;
|
||||||
|
cursor: pointer;
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
.webshell-terminal-session-close:hover {
|
||||||
|
color: #f85149;
|
||||||
|
background: rgba(248, 81, 73, 0.08);
|
||||||
|
}
|
||||||
|
.webshell-terminal-session-add {
|
||||||
|
border: 1px solid rgba(255, 255, 255, 0.16);
|
||||||
|
border-bottom: none;
|
||||||
|
background: rgba(255, 255, 255, 0.03);
|
||||||
|
color: #8b949e;
|
||||||
|
height: 30px;
|
||||||
|
width: 28px;
|
||||||
|
border-radius: 6px 6px 0 0;
|
||||||
|
cursor: pointer;
|
||||||
|
font-size: 16px;
|
||||||
|
line-height: 1;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
.webshell-terminal-session-add:hover {
|
||||||
|
color: #e6edf3;
|
||||||
|
background: rgba(255, 255, 255, 0.08);
|
||||||
|
}
|
||||||
.webshell-quick-label {
|
.webshell-quick-label {
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
@@ -8869,20 +8934,48 @@ header {
|
|||||||
.webshell-terminal-toolbar .btn-ghost {
|
.webshell-terminal-toolbar .btn-ghost {
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
}
|
}
|
||||||
|
.webshell-terminal-status {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
height: 24px;
|
||||||
|
padding: 0 10px;
|
||||||
|
border-radius: 999px;
|
||||||
|
font-size: 12px;
|
||||||
|
font-weight: 600;
|
||||||
|
border: 1px solid transparent;
|
||||||
|
}
|
||||||
|
.webshell-terminal-status.idle {
|
||||||
|
color: #166534;
|
||||||
|
background: rgba(34, 197, 94, 0.1);
|
||||||
|
border-color: rgba(34, 197, 94, 0.25);
|
||||||
|
}
|
||||||
|
.webshell-terminal-status.running {
|
||||||
|
color: #9a3412;
|
||||||
|
background: rgba(251, 146, 60, 0.14);
|
||||||
|
border-color: rgba(251, 146, 60, 0.28);
|
||||||
|
}
|
||||||
|
|
||||||
#webshell-pane-terminal {
|
#webshell-pane-terminal {
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 仅外框圆角,内部不做额外装饰,避免挡住文字 */
|
/* 仅外框圆角,内部不做额外装饰,避免挡住文字 */
|
||||||
.webshell-terminal-container {
|
.webshell-terminal-shell {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
min-height: 360px;
|
min-height: 360px;
|
||||||
padding: 0;
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
background: #0d1117;
|
background: #0d1117;
|
||||||
border-radius: 12px;
|
border-radius: 12px;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
box-shadow: 0 4px 12px rgba(0,0,0,0.15);
|
box-shadow: 0 4px 12px rgba(0,0,0,0.15);
|
||||||
|
}
|
||||||
|
.webshell-terminal-container {
|
||||||
|
flex: 1;
|
||||||
|
min-height: 0;
|
||||||
|
padding: 0;
|
||||||
|
background: transparent;
|
||||||
|
overflow: hidden;
|
||||||
-webkit-font-smoothing: antialiased;
|
-webkit-font-smoothing: antialiased;
|
||||||
-moz-osx-font-smoothing: grayscale;
|
-moz-osx-font-smoothing: grayscale;
|
||||||
transform: translateZ(0);
|
transform: translateZ(0);
|
||||||
@@ -8941,6 +9034,128 @@ header {
|
|||||||
background: rgba(139, 148, 158, 0.7);
|
background: rgba(139, 148, 158, 0.7);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.webshell-file-layout {
|
||||||
|
display: flex;
|
||||||
|
gap: 12px;
|
||||||
|
min-height: 0;
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.webshell-file-sidebar {
|
||||||
|
width: 280px;
|
||||||
|
min-width: 260px;
|
||||||
|
max-width: 320px;
|
||||||
|
border: 1px solid var(--border-color);
|
||||||
|
border-radius: 10px;
|
||||||
|
background: linear-gradient(180deg, var(--bg-secondary) 0%, var(--bg-primary) 100%);
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
min-height: 0;
|
||||||
|
box-shadow: var(--shadow-sm);
|
||||||
|
}
|
||||||
|
|
||||||
|
.webshell-file-sidebar-title {
|
||||||
|
padding: 12px 14px;
|
||||||
|
border-bottom: 1px solid var(--border-color);
|
||||||
|
font-weight: 600;
|
||||||
|
color: var(--text-primary);
|
||||||
|
letter-spacing: 0.2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.webshell-dir-tree {
|
||||||
|
padding: 10px 8px;
|
||||||
|
overflow: auto;
|
||||||
|
min-height: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.webshell-tree-node {
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.webshell-tree-row {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
border-radius: 8px;
|
||||||
|
margin: 2px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.webshell-tree-row.active {
|
||||||
|
background: rgba(0, 102, 255, 0.11);
|
||||||
|
}
|
||||||
|
|
||||||
|
.webshell-tree-toggle {
|
||||||
|
width: 18px;
|
||||||
|
min-width: 18px;
|
||||||
|
height: 24px;
|
||||||
|
margin-left: 2px;
|
||||||
|
border: none;
|
||||||
|
background: transparent;
|
||||||
|
color: var(--text-secondary);
|
||||||
|
cursor: pointer;
|
||||||
|
padding: 0;
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.webshell-tree-toggle.empty {
|
||||||
|
cursor: default;
|
||||||
|
opacity: 0.6;
|
||||||
|
}
|
||||||
|
|
||||||
|
.webshell-tree-children {
|
||||||
|
margin-left: 14px;
|
||||||
|
border-left: 1px dashed rgba(128, 128, 128, 0.28);
|
||||||
|
padding-left: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.webshell-dir-item {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 6px;
|
||||||
|
flex: 1;
|
||||||
|
text-align: left;
|
||||||
|
border: none;
|
||||||
|
background: transparent;
|
||||||
|
color: var(--text-primary);
|
||||||
|
border-radius: 6px;
|
||||||
|
padding: 4px 8px;
|
||||||
|
margin: 0;
|
||||||
|
cursor: pointer;
|
||||||
|
min-width: 0;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.webshell-tree-icon {
|
||||||
|
flex: 0 0 auto;
|
||||||
|
font-size: 0.92rem;
|
||||||
|
opacity: 0.92;
|
||||||
|
}
|
||||||
|
|
||||||
|
.webshell-tree-name {
|
||||||
|
min-width: 0;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.webshell-dir-item:hover {
|
||||||
|
background: rgba(0, 102, 255, 0.08);
|
||||||
|
}
|
||||||
|
|
||||||
|
.webshell-tree-row.active .webshell-dir-item {
|
||||||
|
color: var(--accent-color);
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.webshell-file-main {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
min-width: 0;
|
||||||
|
min-height: 0;
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
.webshell-file-toolbar {
|
.webshell-file-toolbar {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
@@ -8948,7 +9163,7 @@ header {
|
|||||||
margin-bottom: 12px;
|
margin-bottom: 12px;
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
padding: 12px 14px;
|
padding: 12px 14px;
|
||||||
background: var(--bg-secondary);
|
background: linear-gradient(180deg, var(--bg-secondary) 0%, var(--bg-primary) 100%);
|
||||||
border-radius: 10px;
|
border-radius: 10px;
|
||||||
border: 1px solid var(--border-color);
|
border: 1px solid var(--border-color);
|
||||||
width: 100%;
|
width: 100%;
|
||||||
@@ -9093,6 +9308,19 @@ header {
|
|||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@media (max-width: 1200px) {
|
||||||
|
.webshell-file-layout {
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
.webshell-file-sidebar {
|
||||||
|
width: 100%;
|
||||||
|
min-width: 0;
|
||||||
|
max-width: none;
|
||||||
|
max-height: 220px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.webshell-file-table {
|
.webshell-file-table {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
border-collapse: collapse;
|
border-collapse: collapse;
|
||||||
@@ -9154,6 +9382,22 @@ header {
|
|||||||
color: var(--accent-hover);
|
color: var(--accent-hover);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.webshell-file-link.is-dir::before,
|
||||||
|
.webshell-file-link.is-file::before {
|
||||||
|
display: inline-block;
|
||||||
|
margin-right: 6px;
|
||||||
|
font-size: 0.95rem;
|
||||||
|
opacity: 0.95;
|
||||||
|
}
|
||||||
|
|
||||||
|
.webshell-file-link.is-dir::before {
|
||||||
|
content: "📁";
|
||||||
|
}
|
||||||
|
|
||||||
|
.webshell-file-link.is-file::before {
|
||||||
|
content: "📄";
|
||||||
|
}
|
||||||
|
|
||||||
.webshell-file-table .webshell-file-read {
|
.webshell-file-table .webshell-file-read {
|
||||||
color: var(--accent-color);
|
color: var(--accent-color);
|
||||||
margin-right: 8px;
|
margin-right: 8px;
|
||||||
@@ -9426,6 +9670,112 @@ header {
|
|||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
min-height: 0;
|
min-height: 0;
|
||||||
}
|
}
|
||||||
|
.webshell-pane-memo {
|
||||||
|
padding: 14px;
|
||||||
|
background: linear-gradient(180deg, rgba(248, 250, 252, 0.55) 0%, rgba(241, 245, 249, 0.28) 100%);
|
||||||
|
}
|
||||||
|
.webshell-memo-layout {
|
||||||
|
flex: 1;
|
||||||
|
min-height: 0;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
border: 1px solid rgba(15, 23, 42, 0.08);
|
||||||
|
border-radius: 14px;
|
||||||
|
background: linear-gradient(180deg, rgba(255, 255, 255, 0.96) 0%, rgba(248, 250, 252, 0.9) 100%);
|
||||||
|
box-shadow: 0 10px 24px rgba(15, 23, 42, 0.06), inset 0 1px 0 rgba(255, 255, 255, 0.85);
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
.webshell-memo-head {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
gap: 8px;
|
||||||
|
padding: 10px 14px;
|
||||||
|
border-bottom: 1px solid rgba(15, 23, 42, 0.08);
|
||||||
|
font-size: 0.92rem;
|
||||||
|
font-weight: 600;
|
||||||
|
color: var(--text-primary);
|
||||||
|
background: rgba(255, 255, 255, 0.75);
|
||||||
|
backdrop-filter: blur(6px);
|
||||||
|
}
|
||||||
|
.webshell-memo-input {
|
||||||
|
flex: 1;
|
||||||
|
min-height: 0;
|
||||||
|
margin: 12px 14px 8px;
|
||||||
|
padding: 12px 13px;
|
||||||
|
border-radius: 10px;
|
||||||
|
border: 1px solid rgba(15, 23, 42, 0.14);
|
||||||
|
background: #fff;
|
||||||
|
font-size: 0.9rem;
|
||||||
|
line-height: 1.55;
|
||||||
|
font-family: "JetBrains Mono", "Fira Code", "SFMono-Regular", Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
|
||||||
|
color: var(--text-primary);
|
||||||
|
resize: none;
|
||||||
|
transition: border-color 0.2s ease, box-shadow 0.2s ease;
|
||||||
|
}
|
||||||
|
.webshell-memo-input:focus {
|
||||||
|
border-color: rgba(37, 99, 235, 0.5);
|
||||||
|
box-shadow: 0 0 0 3px rgba(37, 99, 235, 0.15);
|
||||||
|
outline: none;
|
||||||
|
}
|
||||||
|
.webshell-memo-status {
|
||||||
|
margin: 0 14px 12px auto;
|
||||||
|
padding: 3px 10px;
|
||||||
|
border-radius: 999px;
|
||||||
|
font-size: 0.74rem;
|
||||||
|
color: var(--text-secondary);
|
||||||
|
background: rgba(148, 163, 184, 0.14);
|
||||||
|
border: 1px solid rgba(148, 163, 184, 0.25);
|
||||||
|
}
|
||||||
|
.webshell-memo-status.error {
|
||||||
|
color: #b91c1c;
|
||||||
|
background: rgba(239, 68, 68, 0.14);
|
||||||
|
border-color: rgba(239, 68, 68, 0.28);
|
||||||
|
}
|
||||||
|
.webshell-ai-memo {
|
||||||
|
flex-shrink: 0;
|
||||||
|
width: 300px;
|
||||||
|
min-width: 220px;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
border-left: 1px solid var(--border-color);
|
||||||
|
background: var(--bg-secondary);
|
||||||
|
min-height: 0;
|
||||||
|
}
|
||||||
|
.webshell-ai-memo-head {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
gap: 8px;
|
||||||
|
padding: 10px 12px;
|
||||||
|
border-bottom: 1px solid var(--border-color);
|
||||||
|
font-size: 0.9rem;
|
||||||
|
color: var(--text-primary);
|
||||||
|
}
|
||||||
|
.webshell-ai-memo-input {
|
||||||
|
flex: 1;
|
||||||
|
min-height: 0;
|
||||||
|
margin: 10px 12px 8px;
|
||||||
|
resize: none;
|
||||||
|
}
|
||||||
|
.webshell-ai-memo-status {
|
||||||
|
padding: 0 12px 10px;
|
||||||
|
font-size: 0.75rem;
|
||||||
|
color: var(--text-secondary);
|
||||||
|
}
|
||||||
|
.webshell-ai-memo-status.error {
|
||||||
|
color: #dc2626;
|
||||||
|
}
|
||||||
|
@media (max-width: 1280px) {
|
||||||
|
.webshell-ai-memo {
|
||||||
|
width: 260px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@media (max-width: 980px) {
|
||||||
|
.webshell-ai-memo {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
.webshell-ai-hint {
|
.webshell-ai-hint {
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
padding: 10px 14px;
|
padding: 10px 14px;
|
||||||
@@ -9793,6 +10143,222 @@ header {
|
|||||||
background: linear-gradient(180deg, rgba(2, 6, 23, 0.015) 0%, rgba(2, 6, 23, 0.03) 100%);
|
background: linear-gradient(180deg, rgba(2, 6, 23, 0.015) 0%, rgba(2, 6, 23, 0.03) 100%);
|
||||||
border-radius: 10px;
|
border-radius: 10px;
|
||||||
}
|
}
|
||||||
|
.webshell-db-profiles-bar {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
gap: 8px;
|
||||||
|
border: 1px solid rgba(15, 23, 42, 0.08);
|
||||||
|
border-radius: 10px;
|
||||||
|
padding: 6px;
|
||||||
|
background: linear-gradient(180deg, rgba(255, 255, 255, 0.96) 0%, rgba(248, 250, 252, 0.92) 100%);
|
||||||
|
}
|
||||||
|
.webshell-db-profiles {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 6px;
|
||||||
|
overflow-x: auto;
|
||||||
|
min-width: 0;
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
.webshell-db-profile-actions {
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
.webshell-db-profile-tab {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
border: 1px solid rgba(15, 23, 42, 0.12);
|
||||||
|
border-radius: 8px;
|
||||||
|
overflow: hidden;
|
||||||
|
background: #fff;
|
||||||
|
}
|
||||||
|
.webshell-db-profile-tab.active {
|
||||||
|
border-color: rgba(0, 102, 255, 0.36);
|
||||||
|
box-shadow: 0 0 0 1px rgba(0, 102, 255, 0.12);
|
||||||
|
}
|
||||||
|
.webshell-db-profile-main,
|
||||||
|
.webshell-db-profile-menu {
|
||||||
|
border: 0;
|
||||||
|
background: transparent;
|
||||||
|
color: var(--text-secondary);
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
.webshell-db-profile-main {
|
||||||
|
padding: 5px 10px;
|
||||||
|
max-width: 200px;
|
||||||
|
white-space: nowrap;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
font-size: 0.8rem;
|
||||||
|
}
|
||||||
|
.webshell-db-profile-tab.active .webshell-db-profile-main {
|
||||||
|
color: var(--text-primary);
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
.webshell-db-profile-menu {
|
||||||
|
padding: 5px 7px;
|
||||||
|
border-left: 1px solid rgba(15, 23, 42, 0.1);
|
||||||
|
font-size: 0.78rem;
|
||||||
|
}
|
||||||
|
.webshell-db-profile-menu:hover {
|
||||||
|
background: rgba(15, 23, 42, 0.06);
|
||||||
|
}
|
||||||
|
.webshell-db-layout {
|
||||||
|
flex: 1;
|
||||||
|
min-height: 0;
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 300px minmax(0, 1fr);
|
||||||
|
gap: 12px;
|
||||||
|
}
|
||||||
|
.webshell-db-sidebar {
|
||||||
|
border: 1px solid rgba(15, 23, 42, 0.08);
|
||||||
|
border-radius: 12px;
|
||||||
|
background: linear-gradient(180deg, rgba(255, 255, 255, 0.98) 0%, rgba(248, 250, 252, 0.96) 100%);
|
||||||
|
box-shadow: 0 6px 20px rgba(15, 23, 42, 0.06), inset 0 1px 0 rgba(255, 255, 255, 0.9);
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
min-height: 0;
|
||||||
|
}
|
||||||
|
.webshell-db-sidebar-head {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
gap: 8px;
|
||||||
|
padding: 10px 12px;
|
||||||
|
border-bottom: 1px solid var(--border-color);
|
||||||
|
}
|
||||||
|
.webshell-db-sidebar-head span {
|
||||||
|
font-size: 0.82rem;
|
||||||
|
font-weight: 700;
|
||||||
|
color: var(--text-primary);
|
||||||
|
}
|
||||||
|
.webshell-db-schema-tree {
|
||||||
|
flex: 1;
|
||||||
|
min-height: 0;
|
||||||
|
overflow-y: auto;
|
||||||
|
overflow-x: hidden;
|
||||||
|
padding: 10px;
|
||||||
|
}
|
||||||
|
.webshell-db-sidebar-hint {
|
||||||
|
border-top: 1px solid var(--border-color);
|
||||||
|
padding: 8px 12px;
|
||||||
|
font-size: 0.76rem;
|
||||||
|
color: var(--text-secondary);
|
||||||
|
background: rgba(2, 6, 23, 0.02);
|
||||||
|
}
|
||||||
|
.webshell-db-group {
|
||||||
|
border: 1px solid rgba(15, 23, 42, 0.08);
|
||||||
|
border-radius: 8px;
|
||||||
|
background: #fff;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
}
|
||||||
|
.webshell-db-group-title {
|
||||||
|
cursor: pointer;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 6px;
|
||||||
|
list-style: none;
|
||||||
|
padding: 8px 10px;
|
||||||
|
font-size: 0.82rem;
|
||||||
|
color: var(--text-primary);
|
||||||
|
min-width: 0;
|
||||||
|
white-space: nowrap;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
.webshell-db-group-title::-webkit-details-marker {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
.webshell-db-count {
|
||||||
|
margin-left: auto;
|
||||||
|
font-size: 0.74rem;
|
||||||
|
color: var(--text-secondary);
|
||||||
|
flex: 0 0 auto;
|
||||||
|
}
|
||||||
|
.webshell-db-group-items {
|
||||||
|
border-top: 1px solid var(--border-color);
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
max-height: 260px;
|
||||||
|
overflow-y: auto;
|
||||||
|
overflow-x: hidden;
|
||||||
|
}
|
||||||
|
.webshell-db-table-node {
|
||||||
|
border-bottom: 1px solid rgba(15, 23, 42, 0.06);
|
||||||
|
min-width: 0;
|
||||||
|
}
|
||||||
|
.webshell-db-table-node:last-child {
|
||||||
|
border-bottom: 0;
|
||||||
|
}
|
||||||
|
.webshell-db-table-item {
|
||||||
|
border: 0;
|
||||||
|
background: transparent;
|
||||||
|
text-align: left;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 6px;
|
||||||
|
padding: 7px 10px;
|
||||||
|
cursor: pointer;
|
||||||
|
color: var(--text-secondary);
|
||||||
|
font-size: 0.8rem;
|
||||||
|
list-style: none;
|
||||||
|
min-width: 0;
|
||||||
|
white-space: nowrap;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
.webshell-db-table-item::-webkit-details-marker {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
.webshell-db-table-item:hover {
|
||||||
|
background: rgba(0, 102, 255, 0.06);
|
||||||
|
color: var(--text-primary);
|
||||||
|
}
|
||||||
|
.webshell-db-column-list {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
margin: 0 0 4px;
|
||||||
|
}
|
||||||
|
.webshell-db-column-item {
|
||||||
|
border: 0;
|
||||||
|
background: transparent;
|
||||||
|
text-align: left;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 6px;
|
||||||
|
padding: 6px 10px 6px 24px;
|
||||||
|
cursor: pointer;
|
||||||
|
color: var(--text-secondary);
|
||||||
|
font-size: 0.78rem;
|
||||||
|
min-width: 0;
|
||||||
|
white-space: nowrap;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
.webshell-db-column-item:hover {
|
||||||
|
background: rgba(0, 102, 255, 0.06);
|
||||||
|
color: var(--text-primary);
|
||||||
|
}
|
||||||
|
.webshell-db-column-empty {
|
||||||
|
padding: 4px 10px 8px 24px;
|
||||||
|
font-size: 0.74rem;
|
||||||
|
color: var(--text-secondary);
|
||||||
|
}
|
||||||
|
.webshell-db-icon {
|
||||||
|
opacity: 0.85;
|
||||||
|
flex: 0 0 auto;
|
||||||
|
}
|
||||||
|
.webshell-db-label {
|
||||||
|
min-width: 0;
|
||||||
|
flex: 1 1 auto;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
.webshell-db-main {
|
||||||
|
min-width: 0;
|
||||||
|
min-height: 0;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 10px;
|
||||||
|
}
|
||||||
.webshell-db-toolbar {
|
.webshell-db-toolbar {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: repeat(4, minmax(160px, 1fr));
|
grid-template-columns: repeat(4, minmax(160px, 1fr));
|
||||||
@@ -9844,6 +10410,10 @@ header {
|
|||||||
#webshell-db-sqlite-row {
|
#webshell-db-sqlite-row {
|
||||||
grid-column: 1 / -1;
|
grid-column: 1 / -1;
|
||||||
}
|
}
|
||||||
|
.webshell-db-sql-tools {
|
||||||
|
display: flex;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
.webshell-db-sql {
|
.webshell-db-sql {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
min-height: 140px;
|
min-height: 140px;
|
||||||
@@ -9905,6 +10475,41 @@ header {
|
|||||||
.webshell-db-output.error {
|
.webshell-db-output.error {
|
||||||
color: var(--error-color);
|
color: var(--error-color);
|
||||||
}
|
}
|
||||||
|
.webshell-db-result-table {
|
||||||
|
border-bottom: 1px solid var(--border-color);
|
||||||
|
overflow: auto;
|
||||||
|
max-height: 46%;
|
||||||
|
}
|
||||||
|
.webshell-db-table {
|
||||||
|
width: 100%;
|
||||||
|
border-collapse: collapse;
|
||||||
|
font-size: 0.8rem;
|
||||||
|
}
|
||||||
|
.webshell-db-table th,
|
||||||
|
.webshell-db-table td {
|
||||||
|
padding: 7px 8px;
|
||||||
|
border-bottom: 1px solid rgba(148, 163, 184, 0.24);
|
||||||
|
border-right: 1px solid rgba(148, 163, 184, 0.24);
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
.webshell-db-table th:last-child,
|
||||||
|
.webshell-db-table td:last-child {
|
||||||
|
border-right: none;
|
||||||
|
}
|
||||||
|
.webshell-db-table thead th {
|
||||||
|
position: sticky;
|
||||||
|
top: 0;
|
||||||
|
z-index: 1;
|
||||||
|
background: rgba(248, 250, 252, 0.98);
|
||||||
|
font-weight: 700;
|
||||||
|
}
|
||||||
|
.webshell-db-table-meta {
|
||||||
|
padding: 6px 8px;
|
||||||
|
font-size: 0.74rem;
|
||||||
|
color: var(--text-secondary);
|
||||||
|
border-top: 1px solid var(--border-color);
|
||||||
|
background: rgba(248, 250, 252, 0.9);
|
||||||
|
}
|
||||||
.webshell-db-hint {
|
.webshell-db-hint {
|
||||||
border-top: 1px solid var(--border-color);
|
border-top: 1px solid var(--border-color);
|
||||||
font-size: 0.76rem;
|
font-size: 0.76rem;
|
||||||
@@ -9913,11 +10518,20 @@ header {
|
|||||||
background: rgba(2, 6, 23, 0.02);
|
background: rgba(2, 6, 23, 0.02);
|
||||||
}
|
}
|
||||||
@media (max-width: 1280px) {
|
@media (max-width: 1280px) {
|
||||||
|
.webshell-db-layout {
|
||||||
|
grid-template-columns: 240px minmax(0, 1fr);
|
||||||
|
}
|
||||||
.webshell-db-toolbar {
|
.webshell-db-toolbar {
|
||||||
grid-template-columns: repeat(3, minmax(140px, 1fr));
|
grid-template-columns: repeat(3, minmax(140px, 1fr));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@media (max-width: 980px) {
|
@media (max-width: 980px) {
|
||||||
|
.webshell-db-layout {
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
}
|
||||||
|
.webshell-db-sidebar {
|
||||||
|
min-height: 200px;
|
||||||
|
}
|
||||||
.webshell-db-toolbar {
|
.webshell-db-toolbar {
|
||||||
grid-template-columns: repeat(2, minmax(140px, 1fr));
|
grid-template-columns: repeat(2, minmax(140px, 1fr));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -374,6 +374,7 @@
|
|||||||
"tabFileManager": "File manager",
|
"tabFileManager": "File manager",
|
||||||
"tabAiAssistant": "AI Assistant",
|
"tabAiAssistant": "AI Assistant",
|
||||||
"tabDbManager": "Database Manager",
|
"tabDbManager": "Database Manager",
|
||||||
|
"tabMemo": "Memo",
|
||||||
"dbType": "Database type",
|
"dbType": "Database type",
|
||||||
"dbHost": "Host",
|
"dbHost": "Host",
|
||||||
"dbPort": "Port",
|
"dbPort": "Port",
|
||||||
@@ -390,6 +391,23 @@
|
|||||||
"dbRunning": "Database command is running, please wait",
|
"dbRunning": "Database command is running, please wait",
|
||||||
"dbCliHint": "If command not found appears, install mysql/psql/sqlite3/sqlcmd on the target host first",
|
"dbCliHint": "If command not found appears, install mysql/psql/sqlite3/sqlcmd on the target host first",
|
||||||
"dbExecFailed": "Database execution failed",
|
"dbExecFailed": "Database execution failed",
|
||||||
|
"dbSchema": "Database Schema",
|
||||||
|
"dbLoadSchema": "Load Schema",
|
||||||
|
"dbNoSchema": "No schema yet, click Load Schema",
|
||||||
|
"dbSelectTableHint": "Click a table to expand columns and generate SQL",
|
||||||
|
"dbNoColumns": "No column details",
|
||||||
|
"dbResultTable": "Result Table",
|
||||||
|
"dbClearSql": "Clear SQL",
|
||||||
|
"dbTemplateSql": "SQL Template",
|
||||||
|
"dbRows": "rows",
|
||||||
|
"dbColumns": "columns",
|
||||||
|
"dbSchemaFailed": "Failed to load schema",
|
||||||
|
"dbAddProfile": "Add connection",
|
||||||
|
"dbRenameProfile": "Rename",
|
||||||
|
"dbDeleteProfile": "Delete connection",
|
||||||
|
"dbDeleteProfileConfirm": "Delete this database connection profile?",
|
||||||
|
"dbProfileNamePrompt": "Enter profile name",
|
||||||
|
"dbProfiles": "Database connections",
|
||||||
"aiSystemReadyMessage": "System is ready. Please enter your test requirements, and the system will automatically perform the corresponding security tests.",
|
"aiSystemReadyMessage": "System is ready. Please enter your test requirements, and the system will automatically perform the corresponding security tests.",
|
||||||
"aiNewConversation": "New conversation",
|
"aiNewConversation": "New conversation",
|
||||||
"aiPreviousConversation": "Previous conversation",
|
"aiPreviousConversation": "Previous conversation",
|
||||||
@@ -397,6 +415,11 @@
|
|||||||
"aiDeleteConversationConfirm": "Delete this conversation?",
|
"aiDeleteConversationConfirm": "Delete this conversation?",
|
||||||
"aiPlaceholder": "e.g. List files in the current directory",
|
"aiPlaceholder": "e.g. List files in the current directory",
|
||||||
"aiSend": "Send",
|
"aiSend": "Send",
|
||||||
|
"aiMemo": "Memo",
|
||||||
|
"aiMemoPlaceholder": "Save key commands, testing ideas, and repro steps...",
|
||||||
|
"aiMemoClear": "Clear",
|
||||||
|
"aiMemoSaving": "Saving...",
|
||||||
|
"aiMemoSaved": "Saved locally",
|
||||||
"quickCommands": "Quick commands",
|
"quickCommands": "Quick commands",
|
||||||
"downloadFile": "Download",
|
"downloadFile": "Download",
|
||||||
"terminalWelcome": "WebShell virtual terminal — type a command and press Enter (Ctrl+L clear)",
|
"terminalWelcome": "WebShell virtual terminal — type a command and press Enter (Ctrl+L clear)",
|
||||||
@@ -414,6 +437,13 @@
|
|||||||
"testFailed": "Connectivity test failed",
|
"testFailed": "Connectivity test failed",
|
||||||
"testNoExpectedOutput": "Shell responded but expected output was not found. Check password and command parameter name.",
|
"testNoExpectedOutput": "Shell responded but expected output was not found. Check password and command parameter name.",
|
||||||
"clearScreen": "Clear",
|
"clearScreen": "Clear",
|
||||||
|
"copyTerminalLog": "Copy log",
|
||||||
|
"terminalIdle": "Idle",
|
||||||
|
"terminalRunning": "Running",
|
||||||
|
"terminalCopyOk": "Log copied",
|
||||||
|
"terminalCopyFail": "Copy failed",
|
||||||
|
"terminalNewWindow": "New terminal",
|
||||||
|
"terminalWindowPrefix": "Terminal",
|
||||||
"running": "Running…",
|
"running": "Running…",
|
||||||
"waitFinish": "Please wait for the current command to finish",
|
"waitFinish": "Please wait for the current command to finish",
|
||||||
"newDir": "New directory",
|
"newDir": "New directory",
|
||||||
|
|||||||
@@ -374,6 +374,7 @@
|
|||||||
"tabFileManager": "文件管理",
|
"tabFileManager": "文件管理",
|
||||||
"tabAiAssistant": "AI 助手",
|
"tabAiAssistant": "AI 助手",
|
||||||
"tabDbManager": "数据库管理",
|
"tabDbManager": "数据库管理",
|
||||||
|
"tabMemo": "备忘录",
|
||||||
"dbType": "数据库类型",
|
"dbType": "数据库类型",
|
||||||
"dbHost": "主机",
|
"dbHost": "主机",
|
||||||
"dbPort": "端口",
|
"dbPort": "端口",
|
||||||
@@ -390,6 +391,23 @@
|
|||||||
"dbRunning": "数据库命令执行中,请稍候",
|
"dbRunning": "数据库命令执行中,请稍候",
|
||||||
"dbCliHint": "如果提示命令不存在,请先在目标主机安装对应客户端(mysql/psql/sqlite3/sqlcmd)",
|
"dbCliHint": "如果提示命令不存在,请先在目标主机安装对应客户端(mysql/psql/sqlite3/sqlcmd)",
|
||||||
"dbExecFailed": "数据库执行失败",
|
"dbExecFailed": "数据库执行失败",
|
||||||
|
"dbSchema": "数据库结构",
|
||||||
|
"dbLoadSchema": "加载结构",
|
||||||
|
"dbNoSchema": "暂无数据库结构,请先加载",
|
||||||
|
"dbSelectTableHint": "点击表名可展开列信息并生成查询 SQL",
|
||||||
|
"dbNoColumns": "暂无列信息",
|
||||||
|
"dbResultTable": "结果表格",
|
||||||
|
"dbClearSql": "清空 SQL",
|
||||||
|
"dbTemplateSql": "示例 SQL",
|
||||||
|
"dbRows": "行",
|
||||||
|
"dbColumns": "列",
|
||||||
|
"dbSchemaFailed": "加载数据库结构失败",
|
||||||
|
"dbAddProfile": "新增连接",
|
||||||
|
"dbRenameProfile": "重命名",
|
||||||
|
"dbDeleteProfile": "删除连接",
|
||||||
|
"dbDeleteProfileConfirm": "确定删除该数据库连接配置吗?",
|
||||||
|
"dbProfileNamePrompt": "请输入连接名称",
|
||||||
|
"dbProfiles": "数据库连接",
|
||||||
"aiSystemReadyMessage": "系统已就绪。请输入您的测试需求,系统将自动执行相应的安全测试。",
|
"aiSystemReadyMessage": "系统已就绪。请输入您的测试需求,系统将自动执行相应的安全测试。",
|
||||||
"aiNewConversation": "新对话",
|
"aiNewConversation": "新对话",
|
||||||
"aiPreviousConversation": "之前的对话",
|
"aiPreviousConversation": "之前的对话",
|
||||||
@@ -397,6 +415,11 @@
|
|||||||
"aiDeleteConversationConfirm": "确定删除当前对话记录?",
|
"aiDeleteConversationConfirm": "确定删除当前对话记录?",
|
||||||
"aiPlaceholder": "例如:列出当前目录下的文件",
|
"aiPlaceholder": "例如:列出当前目录下的文件",
|
||||||
"aiSend": "发送",
|
"aiSend": "发送",
|
||||||
|
"aiMemo": "备忘录",
|
||||||
|
"aiMemoPlaceholder": "记录关键命令、测试思路、复现步骤...",
|
||||||
|
"aiMemoClear": "清空",
|
||||||
|
"aiMemoSaving": "保存中...",
|
||||||
|
"aiMemoSaved": "已保存到本地",
|
||||||
"quickCommands": "快捷命令",
|
"quickCommands": "快捷命令",
|
||||||
"downloadFile": "下载",
|
"downloadFile": "下载",
|
||||||
"terminalWelcome": "WebShell 虚拟终端 — 输入命令后按回车执行(Ctrl+L 清屏)",
|
"terminalWelcome": "WebShell 虚拟终端 — 输入命令后按回车执行(Ctrl+L 清屏)",
|
||||||
@@ -414,6 +437,13 @@
|
|||||||
"testFailed": "连通性测试失败",
|
"testFailed": "连通性测试失败",
|
||||||
"testNoExpectedOutput": "Shell 返回了响应但未得到预期输出,请检查连接密码与命令参数名",
|
"testNoExpectedOutput": "Shell 返回了响应但未得到预期输出,请检查连接密码与命令参数名",
|
||||||
"clearScreen": "清屏",
|
"clearScreen": "清屏",
|
||||||
|
"copyTerminalLog": "复制日志",
|
||||||
|
"terminalIdle": "空闲",
|
||||||
|
"terminalRunning": "执行中",
|
||||||
|
"terminalCopyOk": "日志已复制",
|
||||||
|
"terminalCopyFail": "复制失败",
|
||||||
|
"terminalNewWindow": "新终端",
|
||||||
|
"terminalWindowPrefix": "终端",
|
||||||
"running": "执行中…",
|
"running": "执行中…",
|
||||||
"waitFinish": "请等待当前命令执行完成",
|
"waitFinish": "请等待当前命令执行完成",
|
||||||
"newDir": "新建目录",
|
"newDir": "新建目录",
|
||||||
|
|||||||
+1483
-120
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user