fix:path rewriting & model list
This commit is contained in:
@@ -1135,6 +1135,8 @@ class ApiKeyList {
|
|||||||
const actionConfig = this._getQuickActionConfig(action);
|
const actionConfig = this._getQuickActionConfig(action);
|
||||||
if (actionConfig && actionConfig.requiresConfirm) {
|
if (actionConfig && actionConfig.requiresConfirm) {
|
||||||
const result = await Swal.fire({
|
const result = await Swal.fire({
|
||||||
|
backdrop: `rgba(0,0,0,0.5)`,
|
||||||
|
heightAuto: false,
|
||||||
target: '#main-content-wrapper',
|
target: '#main-content-wrapper',
|
||||||
title: '请确认操作',
|
title: '请确认操作',
|
||||||
html: actionConfig.confirmText,
|
html: actionConfig.confirmText,
|
||||||
|
|||||||
@@ -113,12 +113,22 @@ class LogsPage {
|
|||||||
this.elements.systemControls
|
this.elements.systemControls
|
||||||
);
|
);
|
||||||
Swal.fire({
|
Swal.fire({
|
||||||
title: '实时系统日志',
|
width: '20rem',
|
||||||
text: '您即将连接到实时日志流。这会与服务器建立一个持续的连接。',
|
backdrop: `rgba(0,0,0,0.5)`,
|
||||||
icon: 'info',
|
heightAuto: false,
|
||||||
confirmButtonText: '我明白了,开始连接',
|
customClass: {
|
||||||
|
popup: `swal2-custom-style ${document.documentElement.classList.contains('dark') ? 'swal2-dark' : ''}`
|
||||||
|
},
|
||||||
|
title: '系统终端日志',
|
||||||
|
text: '您即将连接到实时系统日志流窗口。',
|
||||||
showCancelButton: true,
|
showCancelButton: true,
|
||||||
|
confirmButtonText: '确认',
|
||||||
cancelButtonText: '取消',
|
cancelButtonText: '取消',
|
||||||
|
reverseButtons: false,
|
||||||
|
confirmButtonColor: 'rgba(31, 102, 255, 0.8)',
|
||||||
|
cancelButtonColor: '#6b7280',
|
||||||
|
focusConfirm: false,
|
||||||
|
focusCancel: false,
|
||||||
target: '#main-content-wrapper',
|
target: '#main-content-wrapper',
|
||||||
}).then((result) => {
|
}).then((result) => {
|
||||||
if (result.isConfirmed) {
|
if (result.isConfirmed) {
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ export default class SystemLogTerminal {
|
|||||||
this.shouldAutoScroll = true;
|
this.shouldAutoScroll = true;
|
||||||
this.reconnectAttempts = 0;
|
this.reconnectAttempts = 0;
|
||||||
this.maxReconnectAttempts = 5;
|
this.maxReconnectAttempts = 5;
|
||||||
|
this.isConnected = false;
|
||||||
|
|
||||||
this.elements = {
|
this.elements = {
|
||||||
output: this.container.querySelector('#log-terminal-output'),
|
output: this.container.querySelector('#log-terminal-output'),
|
||||||
@@ -16,7 +17,8 @@ export default class SystemLogTerminal {
|
|||||||
clearBtn: this.controlsContainer.querySelector('[data-action="clear-terminal"]'),
|
clearBtn: this.controlsContainer.querySelector('[data-action="clear-terminal"]'),
|
||||||
pauseBtn: this.controlsContainer.querySelector('[data-action="toggle-pause-terminal"]'),
|
pauseBtn: this.controlsContainer.querySelector('[data-action="toggle-pause-terminal"]'),
|
||||||
scrollBtn: this.controlsContainer.querySelector('[data-action="toggle-scroll-terminal"]'),
|
scrollBtn: this.controlsContainer.querySelector('[data-action="toggle-scroll-terminal"]'),
|
||||||
disconnectBtn: this.controlsContainer.querySelector('[data-action="disconnect-terminal"]'),
|
connectBtn: this.controlsContainer.querySelector('[data-action="toggle-connect-terminal"]'),
|
||||||
|
settingsBtn: this.controlsContainer.querySelector('[data-action="terminal-settings"]'),
|
||||||
};
|
};
|
||||||
|
|
||||||
this._initEventListeners();
|
this._initEventListeners();
|
||||||
@@ -26,7 +28,16 @@ export default class SystemLogTerminal {
|
|||||||
this.elements.clearBtn.addEventListener('click', () => this.clear());
|
this.elements.clearBtn.addEventListener('click', () => this.clear());
|
||||||
this.elements.pauseBtn.addEventListener('click', () => this.togglePause());
|
this.elements.pauseBtn.addEventListener('click', () => this.togglePause());
|
||||||
this.elements.scrollBtn.addEventListener('click', () => this.toggleAutoScroll());
|
this.elements.scrollBtn.addEventListener('click', () => this.toggleAutoScroll());
|
||||||
this.elements.disconnectBtn.addEventListener('click', () => this.disconnect());
|
this.elements.connectBtn.addEventListener('click', () => this.toggleConnect());
|
||||||
|
this.elements.settingsBtn.addEventListener('click', () => this.openSettings());
|
||||||
|
}
|
||||||
|
|
||||||
|
toggleConnect() {
|
||||||
|
if (this.isConnected) {
|
||||||
|
this.disconnect();
|
||||||
|
} else {
|
||||||
|
this.connect();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
connect() {
|
connect() {
|
||||||
@@ -43,6 +54,9 @@ export default class SystemLogTerminal {
|
|||||||
this._appendMessage('info', '✓ 已连接到系统日志流');
|
this._appendMessage('info', '✓ 已连接到系统日志流');
|
||||||
this._updateStatus('connected', '已连接');
|
this._updateStatus('connected', '已连接');
|
||||||
this.reconnectAttempts = 0;
|
this.reconnectAttempts = 0;
|
||||||
|
this.isConnected = true;
|
||||||
|
this.elements.connectBtn.title = '断开';
|
||||||
|
this.elements.connectBtn.querySelector('i').classList.replace('fa-plug', 'fa-minus-circle');
|
||||||
};
|
};
|
||||||
|
|
||||||
this.ws.onmessage = (event) => {
|
this.ws.onmessage = (event) => {
|
||||||
@@ -53,7 +67,7 @@ export default class SystemLogTerminal {
|
|||||||
const levelColors = {
|
const levelColors = {
|
||||||
'error': 'text-red-500',
|
'error': 'text-red-500',
|
||||||
'warning': 'text-yellow-400',
|
'warning': 'text-yellow-400',
|
||||||
'info': 'text-blue-400',
|
'info': 'text-green-400',
|
||||||
'debug': 'text-zinc-400'
|
'debug': 'text-zinc-400'
|
||||||
};
|
};
|
||||||
const color = levelColors[data.level] || 'text-zinc-200';
|
const color = levelColors[data.level] || 'text-zinc-200';
|
||||||
@@ -73,6 +87,9 @@ export default class SystemLogTerminal {
|
|||||||
this.ws.onclose = () => {
|
this.ws.onclose = () => {
|
||||||
this._appendMessage('error', '✗ 连接已断开');
|
this._appendMessage('error', '✗ 连接已断开');
|
||||||
this._updateStatus('disconnected', '未连接');
|
this._updateStatus('disconnected', '未连接');
|
||||||
|
this.isConnected = false;
|
||||||
|
this.elements.connectBtn.title = '连接';
|
||||||
|
this.elements.connectBtn.querySelector('i').classList.replace('fa-minus-circle', 'fa-plug');
|
||||||
|
|
||||||
if (this.reconnectAttempts < this.maxReconnectAttempts) {
|
if (this.reconnectAttempts < this.maxReconnectAttempts) {
|
||||||
this.reconnectAttempts++;
|
this.reconnectAttempts++;
|
||||||
@@ -90,7 +107,10 @@ export default class SystemLogTerminal {
|
|||||||
this.ws = null;
|
this.ws = null;
|
||||||
}
|
}
|
||||||
this.reconnectAttempts = this.maxReconnectAttempts;
|
this.reconnectAttempts = this.maxReconnectAttempts;
|
||||||
|
this.isConnected = false;
|
||||||
this._updateStatus('disconnected', '未连接');
|
this._updateStatus('disconnected', '未连接');
|
||||||
|
this.elements.connectBtn.title = '连接';
|
||||||
|
this.elements.connectBtn.querySelector('i').classList.replace('fa-minus-circle', 'fa-plug');
|
||||||
}
|
}
|
||||||
|
|
||||||
clear() {
|
clear() {
|
||||||
@@ -101,25 +121,24 @@ export default class SystemLogTerminal {
|
|||||||
|
|
||||||
togglePause() {
|
togglePause() {
|
||||||
this.isPaused = !this.isPaused;
|
this.isPaused = !this.isPaused;
|
||||||
const span = this.elements.pauseBtn.querySelector('span');
|
|
||||||
const icon = this.elements.pauseBtn.querySelector('i');
|
const icon = this.elements.pauseBtn.querySelector('i');
|
||||||
if (this.isPaused) {
|
if (this.isPaused) {
|
||||||
span.textContent = '继续';
|
this.elements.pauseBtn.title = '继续';
|
||||||
icon.classList.replace('fa-pause', 'fa-play');
|
icon.classList.replace('fa-pause', 'fa-play');
|
||||||
} else {
|
} else {
|
||||||
span.textContent = '暂停';
|
this.elements.pauseBtn.title = '暂停';
|
||||||
icon.classList.replace('fa-play', 'fa-pause');
|
icon.classList.replace('fa-play', 'fa-pause');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
toggleAutoScroll() {
|
toggleAutoScroll() {
|
||||||
this.shouldAutoScroll = !this.shouldAutoScroll;
|
this.shouldAutoScroll = !this.shouldAutoScroll;
|
||||||
const span = this.elements.scrollBtn.querySelector('span');
|
this.elements.scrollBtn.title = this.shouldAutoScroll ? '自动滚动' : '手动滚动';
|
||||||
if (this.shouldAutoScroll) {
|
}
|
||||||
span.textContent = '自动滚动';
|
|
||||||
} else {
|
openSettings() {
|
||||||
span.textContent = '手动滚动';
|
// 实现设置功能
|
||||||
}
|
console.log('打开设置');
|
||||||
}
|
}
|
||||||
|
|
||||||
_appendMessage(colorClass, text) {
|
_appendMessage(colorClass, text) {
|
||||||
|
|||||||
@@ -62,10 +62,13 @@ func (ch *GeminiChannel) extractModelFromRequest(c *gin.Context, bodyBytes []byt
|
|||||||
var p struct {
|
var p struct {
|
||||||
Model string `json:"model"`
|
Model string `json:"model"`
|
||||||
}
|
}
|
||||||
if json.Unmarshal(bodyBytes, &p) == nil && p.Model != "" {
|
_ = json.Unmarshal(bodyBytes, &p)
|
||||||
return strings.TrimPrefix(p.Model, "models/")
|
modelName := strings.TrimPrefix(p.Model, "models/")
|
||||||
|
|
||||||
|
if modelName == "" {
|
||||||
|
modelName = ch.extractModelFromPath(c.Request.URL.Path)
|
||||||
}
|
}
|
||||||
return ch.extractModelFromPath(c.Request.URL.Path)
|
return modelName
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ch *GeminiChannel) extractModelFromPath(path string) string {
|
func (ch *GeminiChannel) extractModelFromPath(path string) string {
|
||||||
@@ -85,7 +88,11 @@ func (ch *GeminiChannel) IsOpenAICompatibleRequest(c *gin.Context) bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (ch *GeminiChannel) isOpenAIPath(path string) bool {
|
func (ch *GeminiChannel) isOpenAIPath(path string) bool {
|
||||||
return strings.Contains(path, "/v1/chat/completions") || strings.Contains(path, "/v1/embeddings")
|
return strings.Contains(path, "/v1/chat/completions") ||
|
||||||
|
strings.Contains(path, "/v1/completions") ||
|
||||||
|
strings.Contains(path, "/v1/embeddings") ||
|
||||||
|
strings.Contains(path, "/v1/models") ||
|
||||||
|
strings.Contains(path, "/v1/audio/")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ch *GeminiChannel) ValidateKey(
|
func (ch *GeminiChannel) ValidateKey(
|
||||||
|
|||||||
@@ -986,6 +986,7 @@ func (h *ProxyHandler) getMaxRetries(isPreciseRouting bool, finalOpConfig *model
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (h *ProxyHandler) handleListModelsRequest(c *gin.Context) {
|
func (h *ProxyHandler) handleListModelsRequest(c *gin.Context) {
|
||||||
|
|
||||||
authTokenValue, exists := c.Get("authToken")
|
authTokenValue, exists := c.Get("authToken")
|
||||||
if !exists {
|
if !exists {
|
||||||
errToJSON(c, uuid.New().String(), errors.NewAPIError(errors.ErrUnauthorized, "Auth token not found in context"))
|
errToJSON(c, uuid.New().String(), errors.NewAPIError(errors.ErrUnauthorized, "Auth token not found in context"))
|
||||||
@@ -996,7 +997,61 @@ func (h *ProxyHandler) handleListModelsRequest(c *gin.Context) {
|
|||||||
errToJSON(c, uuid.New().String(), errors.NewAPIError(errors.ErrInternalServer, "Invalid auth token type in context"))
|
errToJSON(c, uuid.New().String(), errors.NewAPIError(errors.ErrInternalServer, "Invalid auth token type in context"))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
modelNames := h.resourceService.GetAllowedModelsForToken(authToken)
|
|
||||||
|
groupName := c.Param("group_name")
|
||||||
|
h.logger.Infof("List models request: path=%s, groupName=%s", c.Request.URL.Path, groupName)
|
||||||
|
isPreciseRouting := groupName != ""
|
||||||
|
|
||||||
|
var modelNames []string
|
||||||
|
|
||||||
|
if isPreciseRouting {
|
||||||
|
group, ok := h.groupManager.GetGroupByName(groupName)
|
||||||
|
if !ok {
|
||||||
|
errToJSON(c, uuid.New().String(), errors.NewAPIError(errors.ErrNotFound, "Group not found"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, modelMapping := range group.AllowedModels {
|
||||||
|
modelNames = append(modelNames, modelMapping.ModelName)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(modelNames) == 0 {
|
||||||
|
h.logger.Infof("Triggering passthrough for model list")
|
||||||
|
initialResources, err := h.resourceService.GetResourceFromGroup(c.Request.Context(), authToken, groupName)
|
||||||
|
if err != nil {
|
||||||
|
errToJSON(c, uuid.New().String(), errors.NewAPIError(errors.ErrInternalServer, "Failed to get resources"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
targetURL, _ := url.Parse(initialResources.UpstreamEndpoint.URL)
|
||||||
|
apiPath := strings.TrimPrefix(c.Request.URL.Path, "/proxy/"+groupName)
|
||||||
|
targetURL.Path = h.channel.RewritePath(targetURL.Path, apiPath)
|
||||||
|
h.logger.Infof("Final upstream path: %s", targetURL.String())
|
||||||
|
targetURL.RawQuery = c.Request.URL.RawQuery
|
||||||
|
|
||||||
|
req, _ := http.NewRequestWithContext(c.Request.Context(), "GET", targetURL.String(), nil)
|
||||||
|
h.channel.ModifyRequest(req, initialResources.APIKey)
|
||||||
|
|
||||||
|
client := &http.Client{Transport: h.transparentProxy.Transport}
|
||||||
|
resp, err := client.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
errToJSON(c, uuid.New().String(), errors.NewAPIError(errors.ErrBadGateway, "Failed to fetch models"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
c.Writer.WriteHeader(resp.StatusCode)
|
||||||
|
for k, v := range resp.Header {
|
||||||
|
c.Writer.Header()[k] = v
|
||||||
|
}
|
||||||
|
io.Copy(c.Writer, resp.Body)
|
||||||
|
h.logger.Infof("Passthrough response sent")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
modelNames = h.resourceService.GetAllowedModelsForToken(authToken)
|
||||||
|
}
|
||||||
|
|
||||||
if strings.Contains(c.Request.URL.Path, "/v1beta/") {
|
if strings.Contains(c.Request.URL.Path, "/v1beta/") {
|
||||||
h.respondWithGeminiFormat(c, modelNames)
|
h.respondWithGeminiFormat(c, modelNames)
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -496,6 +496,9 @@
|
|||||||
.my-1\.5 {
|
.my-1\.5 {
|
||||||
margin-block: calc(var(--spacing) * 1.5);
|
margin-block: calc(var(--spacing) * 1.5);
|
||||||
}
|
}
|
||||||
|
.mt-0 {
|
||||||
|
margin-top: calc(var(--spacing) * 0);
|
||||||
|
}
|
||||||
.mt-0\.5 {
|
.mt-0\.5 {
|
||||||
margin-top: calc(var(--spacing) * 0.5);
|
margin-top: calc(var(--spacing) * 0.5);
|
||||||
}
|
}
|
||||||
@@ -614,6 +617,9 @@
|
|||||||
width: calc(var(--spacing) * 6);
|
width: calc(var(--spacing) * 6);
|
||||||
height: calc(var(--spacing) * 6);
|
height: calc(var(--spacing) * 6);
|
||||||
}
|
}
|
||||||
|
.h-0 {
|
||||||
|
height: calc(var(--spacing) * 0);
|
||||||
|
}
|
||||||
.h-0\.5 {
|
.h-0\.5 {
|
||||||
height: calc(var(--spacing) * 0.5);
|
height: calc(var(--spacing) * 0.5);
|
||||||
}
|
}
|
||||||
@@ -698,6 +704,9 @@
|
|||||||
.w-0 {
|
.w-0 {
|
||||||
width: calc(var(--spacing) * 0);
|
width: calc(var(--spacing) * 0);
|
||||||
}
|
}
|
||||||
|
.w-1 {
|
||||||
|
width: calc(var(--spacing) * 1);
|
||||||
|
}
|
||||||
.w-1\/4 {
|
.w-1\/4 {
|
||||||
width: calc(1/4 * 100%);
|
width: calc(1/4 * 100%);
|
||||||
}
|
}
|
||||||
@@ -806,6 +815,9 @@
|
|||||||
.flex-1 {
|
.flex-1 {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
}
|
}
|
||||||
|
.flex-shrink {
|
||||||
|
flex-shrink: 1;
|
||||||
|
}
|
||||||
.shrink-0 {
|
.shrink-0 {
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
}
|
}
|
||||||
@@ -818,6 +830,9 @@
|
|||||||
.caption-bottom {
|
.caption-bottom {
|
||||||
caption-side: bottom;
|
caption-side: bottom;
|
||||||
}
|
}
|
||||||
|
.border-collapse {
|
||||||
|
border-collapse: collapse;
|
||||||
|
}
|
||||||
.origin-center {
|
.origin-center {
|
||||||
transform-origin: center;
|
transform-origin: center;
|
||||||
}
|
}
|
||||||
@@ -844,6 +859,10 @@
|
|||||||
--tw-translate-x: 100%;
|
--tw-translate-x: 100%;
|
||||||
translate: var(--tw-translate-x) var(--tw-translate-y);
|
translate: var(--tw-translate-x) var(--tw-translate-y);
|
||||||
}
|
}
|
||||||
|
.-translate-y-1 {
|
||||||
|
--tw-translate-y: calc(var(--spacing) * -1);
|
||||||
|
translate: var(--tw-translate-x) var(--tw-translate-y);
|
||||||
|
}
|
||||||
.-translate-y-1\/2 {
|
.-translate-y-1\/2 {
|
||||||
--tw-translate-y: calc(calc(1/2 * 100%) * -1);
|
--tw-translate-y: calc(calc(1/2 * 100%) * -1);
|
||||||
translate: var(--tw-translate-x) var(--tw-translate-y);
|
translate: var(--tw-translate-x) var(--tw-translate-y);
|
||||||
@@ -1000,6 +1019,9 @@
|
|||||||
margin-block-end: calc(calc(var(--spacing) * 8) * calc(1 - var(--tw-space-y-reverse)));
|
margin-block-end: calc(calc(var(--spacing) * 8) * calc(1 - var(--tw-space-y-reverse)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
.gap-x-1 {
|
||||||
|
column-gap: calc(var(--spacing) * 1);
|
||||||
|
}
|
||||||
.gap-x-1\.5 {
|
.gap-x-1\.5 {
|
||||||
column-gap: calc(var(--spacing) * 1.5);
|
column-gap: calc(var(--spacing) * 1.5);
|
||||||
}
|
}
|
||||||
@@ -1145,6 +1167,9 @@
|
|||||||
--tw-border-style: none;
|
--tw-border-style: none;
|
||||||
border-style: none;
|
border-style: none;
|
||||||
}
|
}
|
||||||
|
.border-black {
|
||||||
|
border-color: var(--color-black);
|
||||||
|
}
|
||||||
.border-black\/10 {
|
.border-black\/10 {
|
||||||
border-color: color-mix(in srgb, #000 10%, transparent);
|
border-color: color-mix(in srgb, #000 10%, transparent);
|
||||||
@supports (color: color-mix(in lab, red, red)) {
|
@supports (color: color-mix(in lab, red, red)) {
|
||||||
@@ -1172,6 +1197,9 @@
|
|||||||
.border-green-200 {
|
.border-green-200 {
|
||||||
border-color: var(--color-green-200);
|
border-color: var(--color-green-200);
|
||||||
}
|
}
|
||||||
|
.border-primary {
|
||||||
|
border-color: var(--color-primary);
|
||||||
|
}
|
||||||
.border-primary\/20 {
|
.border-primary\/20 {
|
||||||
border-color: var(--color-primary);
|
border-color: var(--color-primary);
|
||||||
@supports (color: color-mix(in lab, red, red)) {
|
@supports (color: color-mix(in lab, red, red)) {
|
||||||
@@ -1208,6 +1236,9 @@
|
|||||||
.border-zinc-300 {
|
.border-zinc-300 {
|
||||||
border-color: var(--color-zinc-300);
|
border-color: var(--color-zinc-300);
|
||||||
}
|
}
|
||||||
|
.border-zinc-700 {
|
||||||
|
border-color: var(--color-zinc-700);
|
||||||
|
}
|
||||||
.border-zinc-700\/50 {
|
.border-zinc-700\/50 {
|
||||||
border-color: color-mix(in srgb, oklch(37% 0.013 285.805) 50%, transparent);
|
border-color: color-mix(in srgb, oklch(37% 0.013 285.805) 50%, transparent);
|
||||||
@supports (color: color-mix(in lab, red, red)) {
|
@supports (color: color-mix(in lab, red, red)) {
|
||||||
@@ -1280,6 +1311,9 @@
|
|||||||
.bg-gray-500 {
|
.bg-gray-500 {
|
||||||
background-color: var(--color-gray-500);
|
background-color: var(--color-gray-500);
|
||||||
}
|
}
|
||||||
|
.bg-gray-950 {
|
||||||
|
background-color: var(--color-gray-950);
|
||||||
|
}
|
||||||
.bg-gray-950\/5 {
|
.bg-gray-950\/5 {
|
||||||
background-color: color-mix(in srgb, oklch(13% 0.028 261.692) 5%, transparent);
|
background-color: color-mix(in srgb, oklch(13% 0.028 261.692) 5%, transparent);
|
||||||
@supports (color: color-mix(in lab, red, red)) {
|
@supports (color: color-mix(in lab, red, red)) {
|
||||||
@@ -1494,6 +1528,10 @@
|
|||||||
--tw-gradient-position: to right in oklab;
|
--tw-gradient-position: to right in oklab;
|
||||||
background-image: linear-gradient(var(--tw-gradient-stops));
|
background-image: linear-gradient(var(--tw-gradient-stops));
|
||||||
}
|
}
|
||||||
|
.from-blue-500 {
|
||||||
|
--tw-gradient-from: var(--color-blue-500);
|
||||||
|
--tw-gradient-stops: var(--tw-gradient-via-stops, var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position));
|
||||||
|
}
|
||||||
.from-blue-500\/30 {
|
.from-blue-500\/30 {
|
||||||
--tw-gradient-from: color-mix(in srgb, oklch(62.3% 0.214 259.815) 30%, transparent);
|
--tw-gradient-from: color-mix(in srgb, oklch(62.3% 0.214 259.815) 30%, transparent);
|
||||||
@supports (color: color-mix(in lab, red, red)) {
|
@supports (color: color-mix(in lab, red, red)) {
|
||||||
@@ -1564,6 +1602,9 @@
|
|||||||
.px-8 {
|
.px-8 {
|
||||||
padding-inline: calc(var(--spacing) * 8);
|
padding-inline: calc(var(--spacing) * 8);
|
||||||
}
|
}
|
||||||
|
.py-0 {
|
||||||
|
padding-block: calc(var(--spacing) * 0);
|
||||||
|
}
|
||||||
.py-0\.5 {
|
.py-0\.5 {
|
||||||
padding-block: calc(var(--spacing) * 0.5);
|
padding-block: calc(var(--spacing) * 0.5);
|
||||||
}
|
}
|
||||||
@@ -1612,6 +1653,9 @@
|
|||||||
.pr-20 {
|
.pr-20 {
|
||||||
padding-right: calc(var(--spacing) * 20);
|
padding-right: calc(var(--spacing) * 20);
|
||||||
}
|
}
|
||||||
|
.pb-1 {
|
||||||
|
padding-bottom: calc(var(--spacing) * 1);
|
||||||
|
}
|
||||||
.pb-1\.5 {
|
.pb-1\.5 {
|
||||||
padding-bottom: calc(var(--spacing) * 1.5);
|
padding-bottom: calc(var(--spacing) * 1.5);
|
||||||
}
|
}
|
||||||
@@ -1787,6 +1831,9 @@
|
|||||||
.text-gray-950 {
|
.text-gray-950 {
|
||||||
color: var(--color-gray-950);
|
color: var(--color-gray-950);
|
||||||
}
|
}
|
||||||
|
.text-green-400 {
|
||||||
|
color: var(--color-green-400);
|
||||||
|
}
|
||||||
.text-green-500 {
|
.text-green-500 {
|
||||||
color: var(--color-green-500);
|
color: var(--color-green-500);
|
||||||
}
|
}
|
||||||
@@ -1895,6 +1942,9 @@
|
|||||||
.italic {
|
.italic {
|
||||||
font-style: italic;
|
font-style: italic;
|
||||||
}
|
}
|
||||||
|
.underline {
|
||||||
|
text-decoration-line: underline;
|
||||||
|
}
|
||||||
.opacity-0 {
|
.opacity-0 {
|
||||||
opacity: 0%;
|
opacity: 0%;
|
||||||
}
|
}
|
||||||
@@ -1952,6 +2002,10 @@
|
|||||||
--tw-inset-shadow: inset 0 2px 4px var(--tw-inset-shadow-color, oklab(from rgb(0 0 0 / 0.05) l a b / 25%));
|
--tw-inset-shadow: inset 0 2px 4px var(--tw-inset-shadow-color, oklab(from rgb(0 0 0 / 0.05) l a b / 25%));
|
||||||
box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow);
|
box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow);
|
||||||
}
|
}
|
||||||
|
.inset-shadow-sm {
|
||||||
|
--tw-inset-shadow: inset 0 2px 4px var(--tw-inset-shadow-color, rgb(0 0 0 / 0.05));
|
||||||
|
box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow);
|
||||||
|
}
|
||||||
.ring-black {
|
.ring-black {
|
||||||
--tw-ring-color: var(--color-black);
|
--tw-ring-color: var(--color-black);
|
||||||
}
|
}
|
||||||
@@ -1973,6 +2027,10 @@
|
|||||||
--tw-ring-color: color-mix(in oklab, var(--color-black) 15%, transparent);
|
--tw-ring-color: color-mix(in oklab, var(--color-black) 15%, transparent);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
.outline {
|
||||||
|
outline-style: var(--tw-outline-style);
|
||||||
|
outline-width: 1px;
|
||||||
|
}
|
||||||
.blur {
|
.blur {
|
||||||
--tw-blur: blur(8px);
|
--tw-blur: blur(8px);
|
||||||
filter: var(--tw-blur,) var(--tw-brightness,) var(--tw-contrast,) var(--tw-grayscale,) var(--tw-hue-rotate,) var(--tw-invert,) var(--tw-saturate,) var(--tw-sepia,) var(--tw-drop-shadow,);
|
filter: var(--tw-blur,) var(--tw-brightness,) var(--tw-contrast,) var(--tw-grayscale,) var(--tw-hue-rotate,) var(--tw-invert,) var(--tw-saturate,) var(--tw-sepia,) var(--tw-drop-shadow,);
|
||||||
@@ -2360,6 +2418,13 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
.hover\:text-zinc-700 {
|
||||||
|
&:hover {
|
||||||
|
@media (hover: hover) {
|
||||||
|
color: var(--color-zinc-700);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
.hover\:shadow-lg {
|
.hover\:shadow-lg {
|
||||||
&:hover {
|
&:hover {
|
||||||
@media (hover: hover) {
|
@media (hover: hover) {
|
||||||
@@ -3079,6 +3144,15 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
.dark\:hover\:text-zinc-200 {
|
||||||
|
&:where(.dark, .dark *) {
|
||||||
|
&:hover {
|
||||||
|
@media (hover: hover) {
|
||||||
|
color: var(--color-zinc-200);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
.dark\:focus\:border-blue-500 {
|
.dark\:focus\:border-blue-500 {
|
||||||
&:where(.dark, .dark *) {
|
&:where(.dark, .dark *) {
|
||||||
&:focus {
|
&:focus {
|
||||||
@@ -5062,6 +5136,11 @@
|
|||||||
inherits: false;
|
inherits: false;
|
||||||
initial-value: 0 0 #0000;
|
initial-value: 0 0 #0000;
|
||||||
}
|
}
|
||||||
|
@property --tw-outline-style {
|
||||||
|
syntax: "*";
|
||||||
|
inherits: false;
|
||||||
|
initial-value: solid;
|
||||||
|
}
|
||||||
@property --tw-blur {
|
@property --tw-blur {
|
||||||
syntax: "*";
|
syntax: "*";
|
||||||
inherits: false;
|
inherits: false;
|
||||||
@@ -5174,11 +5253,6 @@
|
|||||||
inherits: false;
|
inherits: false;
|
||||||
initial-value: 1;
|
initial-value: 1;
|
||||||
}
|
}
|
||||||
@property --tw-outline-style {
|
|
||||||
syntax: "*";
|
|
||||||
inherits: false;
|
|
||||||
initial-value: solid;
|
|
||||||
}
|
|
||||||
@keyframes spin {
|
@keyframes spin {
|
||||||
to {
|
to {
|
||||||
transform: rotate(360deg);
|
transform: rotate(360deg);
|
||||||
@@ -5236,6 +5310,7 @@
|
|||||||
--tw-ring-offset-width: 0px;
|
--tw-ring-offset-width: 0px;
|
||||||
--tw-ring-offset-color: #fff;
|
--tw-ring-offset-color: #fff;
|
||||||
--tw-ring-offset-shadow: 0 0 #0000;
|
--tw-ring-offset-shadow: 0 0 #0000;
|
||||||
|
--tw-outline-style: solid;
|
||||||
--tw-blur: initial;
|
--tw-blur: initial;
|
||||||
--tw-brightness: initial;
|
--tw-brightness: initial;
|
||||||
--tw-contrast: initial;
|
--tw-contrast: initial;
|
||||||
@@ -5263,7 +5338,6 @@
|
|||||||
--tw-scale-x: 1;
|
--tw-scale-x: 1;
|
||||||
--tw-scale-y: 1;
|
--tw-scale-y: 1;
|
||||||
--tw-scale-z: 1;
|
--tw-scale-z: 1;
|
||||||
--tw-outline-style: solid;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2328,6 +2328,8 @@ var ApiKeyList = class {
|
|||||||
const actionConfig = this._getQuickActionConfig(action);
|
const actionConfig = this._getQuickActionConfig(action);
|
||||||
if (actionConfig && actionConfig.requiresConfirm) {
|
if (actionConfig && actionConfig.requiresConfirm) {
|
||||||
const result = await Swal.fire({
|
const result = await Swal.fire({
|
||||||
|
backdrop: `rgba(0,0,0,0.5)`,
|
||||||
|
heightAuto: false,
|
||||||
target: "#main-content-wrapper",
|
target: "#main-content-wrapper",
|
||||||
title: "\u8BF7\u786E\u8BA4\u64CD\u4F5C",
|
title: "\u8BF7\u786E\u8BA4\u64CD\u4F5C",
|
||||||
html: actionConfig.confirmText,
|
html: actionConfig.confirmText,
|
||||||
@@ -853,13 +853,15 @@ var SystemLogTerminal = class {
|
|||||||
this.shouldAutoScroll = true;
|
this.shouldAutoScroll = true;
|
||||||
this.reconnectAttempts = 0;
|
this.reconnectAttempts = 0;
|
||||||
this.maxReconnectAttempts = 5;
|
this.maxReconnectAttempts = 5;
|
||||||
|
this.isConnected = false;
|
||||||
this.elements = {
|
this.elements = {
|
||||||
output: this.container.querySelector("#log-terminal-output"),
|
output: this.container.querySelector("#log-terminal-output"),
|
||||||
statusIndicator: this.controlsContainer.querySelector("#terminal-status-indicator"),
|
statusIndicator: this.controlsContainer.querySelector("#terminal-status-indicator"),
|
||||||
clearBtn: this.controlsContainer.querySelector('[data-action="clear-terminal"]'),
|
clearBtn: this.controlsContainer.querySelector('[data-action="clear-terminal"]'),
|
||||||
pauseBtn: this.controlsContainer.querySelector('[data-action="toggle-pause-terminal"]'),
|
pauseBtn: this.controlsContainer.querySelector('[data-action="toggle-pause-terminal"]'),
|
||||||
scrollBtn: this.controlsContainer.querySelector('[data-action="toggle-scroll-terminal"]'),
|
scrollBtn: this.controlsContainer.querySelector('[data-action="toggle-scroll-terminal"]'),
|
||||||
disconnectBtn: this.controlsContainer.querySelector('[data-action="disconnect-terminal"]')
|
connectBtn: this.controlsContainer.querySelector('[data-action="toggle-connect-terminal"]'),
|
||||||
|
settingsBtn: this.controlsContainer.querySelector('[data-action="terminal-settings"]')
|
||||||
};
|
};
|
||||||
this._initEventListeners();
|
this._initEventListeners();
|
||||||
}
|
}
|
||||||
@@ -867,7 +869,15 @@ var SystemLogTerminal = class {
|
|||||||
this.elements.clearBtn.addEventListener("click", () => this.clear());
|
this.elements.clearBtn.addEventListener("click", () => this.clear());
|
||||||
this.elements.pauseBtn.addEventListener("click", () => this.togglePause());
|
this.elements.pauseBtn.addEventListener("click", () => this.togglePause());
|
||||||
this.elements.scrollBtn.addEventListener("click", () => this.toggleAutoScroll());
|
this.elements.scrollBtn.addEventListener("click", () => this.toggleAutoScroll());
|
||||||
this.elements.disconnectBtn.addEventListener("click", () => this.disconnect());
|
this.elements.connectBtn.addEventListener("click", () => this.toggleConnect());
|
||||||
|
this.elements.settingsBtn.addEventListener("click", () => this.openSettings());
|
||||||
|
}
|
||||||
|
toggleConnect() {
|
||||||
|
if (this.isConnected) {
|
||||||
|
this.disconnect();
|
||||||
|
} else {
|
||||||
|
this.connect();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
connect() {
|
connect() {
|
||||||
this.clear();
|
this.clear();
|
||||||
@@ -880,6 +890,9 @@ var SystemLogTerminal = class {
|
|||||||
this._appendMessage("info", "\u2713 \u5DF2\u8FDE\u63A5\u5230\u7CFB\u7EDF\u65E5\u5FD7\u6D41");
|
this._appendMessage("info", "\u2713 \u5DF2\u8FDE\u63A5\u5230\u7CFB\u7EDF\u65E5\u5FD7\u6D41");
|
||||||
this._updateStatus("connected", "\u5DF2\u8FDE\u63A5");
|
this._updateStatus("connected", "\u5DF2\u8FDE\u63A5");
|
||||||
this.reconnectAttempts = 0;
|
this.reconnectAttempts = 0;
|
||||||
|
this.isConnected = true;
|
||||||
|
this.elements.connectBtn.title = "\u65AD\u5F00";
|
||||||
|
this.elements.connectBtn.querySelector("i").classList.replace("fa-plug", "fa-minus-circle");
|
||||||
};
|
};
|
||||||
this.ws.onmessage = (event) => {
|
this.ws.onmessage = (event) => {
|
||||||
if (this.isPaused) return;
|
if (this.isPaused) return;
|
||||||
@@ -888,7 +901,7 @@ var SystemLogTerminal = class {
|
|||||||
const levelColors = {
|
const levelColors = {
|
||||||
"error": "text-red-500",
|
"error": "text-red-500",
|
||||||
"warning": "text-yellow-400",
|
"warning": "text-yellow-400",
|
||||||
"info": "text-blue-400",
|
"info": "text-green-400",
|
||||||
"debug": "text-zinc-400"
|
"debug": "text-zinc-400"
|
||||||
};
|
};
|
||||||
const color = levelColors[data.level] || "text-zinc-200";
|
const color = levelColors[data.level] || "text-zinc-200";
|
||||||
@@ -906,6 +919,9 @@ var SystemLogTerminal = class {
|
|||||||
this.ws.onclose = () => {
|
this.ws.onclose = () => {
|
||||||
this._appendMessage("error", "\u2717 \u8FDE\u63A5\u5DF2\u65AD\u5F00");
|
this._appendMessage("error", "\u2717 \u8FDE\u63A5\u5DF2\u65AD\u5F00");
|
||||||
this._updateStatus("disconnected", "\u672A\u8FDE\u63A5");
|
this._updateStatus("disconnected", "\u672A\u8FDE\u63A5");
|
||||||
|
this.isConnected = false;
|
||||||
|
this.elements.connectBtn.title = "\u8FDE\u63A5";
|
||||||
|
this.elements.connectBtn.querySelector("i").classList.replace("fa-minus-circle", "fa-plug");
|
||||||
if (this.reconnectAttempts < this.maxReconnectAttempts) {
|
if (this.reconnectAttempts < this.maxReconnectAttempts) {
|
||||||
this.reconnectAttempts++;
|
this.reconnectAttempts++;
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
@@ -921,7 +937,10 @@ var SystemLogTerminal = class {
|
|||||||
this.ws = null;
|
this.ws = null;
|
||||||
}
|
}
|
||||||
this.reconnectAttempts = this.maxReconnectAttempts;
|
this.reconnectAttempts = this.maxReconnectAttempts;
|
||||||
|
this.isConnected = false;
|
||||||
this._updateStatus("disconnected", "\u672A\u8FDE\u63A5");
|
this._updateStatus("disconnected", "\u672A\u8FDE\u63A5");
|
||||||
|
this.elements.connectBtn.title = "\u8FDE\u63A5";
|
||||||
|
this.elements.connectBtn.querySelector("i").classList.replace("fa-minus-circle", "fa-plug");
|
||||||
}
|
}
|
||||||
clear() {
|
clear() {
|
||||||
if (this.elements.output) {
|
if (this.elements.output) {
|
||||||
@@ -930,24 +949,21 @@ var SystemLogTerminal = class {
|
|||||||
}
|
}
|
||||||
togglePause() {
|
togglePause() {
|
||||||
this.isPaused = !this.isPaused;
|
this.isPaused = !this.isPaused;
|
||||||
const span = this.elements.pauseBtn.querySelector("span");
|
|
||||||
const icon = this.elements.pauseBtn.querySelector("i");
|
const icon = this.elements.pauseBtn.querySelector("i");
|
||||||
if (this.isPaused) {
|
if (this.isPaused) {
|
||||||
span.textContent = "\u7EE7\u7EED";
|
this.elements.pauseBtn.title = "\u7EE7\u7EED";
|
||||||
icon.classList.replace("fa-pause", "fa-play");
|
icon.classList.replace("fa-pause", "fa-play");
|
||||||
} else {
|
} else {
|
||||||
span.textContent = "\u6682\u505C";
|
this.elements.pauseBtn.title = "\u6682\u505C";
|
||||||
icon.classList.replace("fa-play", "fa-pause");
|
icon.classList.replace("fa-play", "fa-pause");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
toggleAutoScroll() {
|
toggleAutoScroll() {
|
||||||
this.shouldAutoScroll = !this.shouldAutoScroll;
|
this.shouldAutoScroll = !this.shouldAutoScroll;
|
||||||
const span = this.elements.scrollBtn.querySelector("span");
|
this.elements.scrollBtn.title = this.shouldAutoScroll ? "\u81EA\u52A8\u6EDA\u52A8" : "\u624B\u52A8\u6EDA\u52A8";
|
||||||
if (this.shouldAutoScroll) {
|
}
|
||||||
span.textContent = "\u81EA\u52A8\u6EDA\u52A8";
|
openSettings() {
|
||||||
} else {
|
console.log("\u6253\u5F00\u8BBE\u7F6E");
|
||||||
span.textContent = "\u624B\u52A8\u6EDA\u52A8";
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
_appendMessage(colorClass, text) {
|
_appendMessage(colorClass, text) {
|
||||||
if (!this.elements.output) return;
|
if (!this.elements.output) return;
|
||||||
@@ -1085,12 +1101,22 @@ var LogsPage = class {
|
|||||||
this.elements.systemControls
|
this.elements.systemControls
|
||||||
);
|
);
|
||||||
Swal.fire({
|
Swal.fire({
|
||||||
title: "\u5B9E\u65F6\u7CFB\u7EDF\u65E5\u5FD7",
|
width: "20rem",
|
||||||
text: "\u60A8\u5373\u5C06\u8FDE\u63A5\u5230\u5B9E\u65F6\u65E5\u5FD7\u6D41\u3002\u8FD9\u4F1A\u4E0E\u670D\u52A1\u5668\u5EFA\u7ACB\u4E00\u4E2A\u6301\u7EED\u7684\u8FDE\u63A5\u3002",
|
backdrop: `rgba(0,0,0,0.5)`,
|
||||||
icon: "info",
|
heightAuto: false,
|
||||||
confirmButtonText: "\u6211\u660E\u767D\u4E86\uFF0C\u5F00\u59CB\u8FDE\u63A5",
|
customClass: {
|
||||||
|
popup: `swal2-custom-style ${document.documentElement.classList.contains("dark") ? "swal2-dark" : ""}`
|
||||||
|
},
|
||||||
|
title: "\u7CFB\u7EDF\u7EC8\u7AEF\u65E5\u5FD7",
|
||||||
|
text: "\u60A8\u5373\u5C06\u8FDE\u63A5\u5230\u5B9E\u65F6\u7CFB\u7EDF\u65E5\u5FD7\u6D41\u7A97\u53E3\u3002",
|
||||||
showCancelButton: true,
|
showCancelButton: true,
|
||||||
|
confirmButtonText: "\u786E\u8BA4",
|
||||||
cancelButtonText: "\u53D6\u6D88",
|
cancelButtonText: "\u53D6\u6D88",
|
||||||
|
reverseButtons: false,
|
||||||
|
confirmButtonColor: "rgba(31, 102, 255, 0.8)",
|
||||||
|
cancelButtonColor: "#6b7280",
|
||||||
|
focusConfirm: false,
|
||||||
|
focusCancel: false,
|
||||||
target: "#main-content-wrapper"
|
target: "#main-content-wrapper"
|
||||||
}).then((result) => {
|
}).then((result) => {
|
||||||
if (result.isConfirmed) {
|
if (result.isConfirmed) {
|
||||||
@@ -181,8 +181,8 @@ var pageModules = {
|
|||||||
// 键 'dashboard' 对应一个函数,该函数调用 import() 返回一个 Promise
|
// 键 'dashboard' 对应一个函数,该函数调用 import() 返回一个 Promise
|
||||||
// esbuild 看到这个 import() 语法,就会自动将 dashboard.js 及其依赖打包成一个独立的 chunk 文件
|
// esbuild 看到这个 import() 语法,就会自动将 dashboard.js 及其依赖打包成一个独立的 chunk 文件
|
||||||
"dashboard": () => import("./dashboard-XFUWX3IN.js"),
|
"dashboard": () => import("./dashboard-XFUWX3IN.js"),
|
||||||
"keys": () => import("./keys-HRP4JR7B.js"),
|
"keys": () => import("./keys-2IUHJHHE.js"),
|
||||||
"logs": () => import("./logs-43KF5HY3.js")
|
"logs": () => import("./logs-MNVRT6ND.js")
|
||||||
// 'settings': () => import('./pages/settings.js'), // 未来启用 settings 页面
|
// 'settings': () => import('./pages/settings.js'), // 未来启用 settings 页面
|
||||||
// 未来新增的页面,只需在这里添加一行映射,esbuild会自动处理
|
// 未来新增的页面,只需在这里添加一行映射,esbuild会自动处理
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -28,8 +28,6 @@
|
|||||||
<div class="absolute left-0 h-[calc(100%-0.5rem)] rounded-md bg-white dark:bg-zinc-700 shadow-sm" data-tab-indicator style="transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);"></div>
|
<div class="absolute left-0 h-[calc(100%-0.5rem)] rounded-md bg-white dark:bg-zinc-700 shadow-sm" data-tab-indicator style="transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);"></div>
|
||||||
<a href="#" role="tab" class="tab-item tab-active" data-tab-item data-tab-target="error">错误日志</a>
|
<a href="#" role="tab" class="tab-item tab-active" data-tab-item data-tab-target="error">错误日志</a>
|
||||||
<a href="#" role="tab" class="tab-item" data-tab-item data-tab-target="system">系统日志</a>
|
<a href="#" role="tab" class="tab-item" data-tab-item data-tab-target="system">系统日志</a>
|
||||||
<a href="#" role="tab" class="tab-item" data-tab-item>保留标签</a>
|
|
||||||
<a href="#" role="tab" class="tab-item" data-tab-item>保留标签</a>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -72,34 +70,29 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- [新增] 3.2 系统日志的快捷操作栏 (默认隐藏) -->
|
<!-- [新增] 3.2 系统日志的快捷操作栏 (默认隐藏) -->
|
||||||
<div id="system-logs-controls" class="hidden flex items-center justify-between shrink-0 py-4">
|
<div id="system-logs-controls" class="hidden flex items-center justify-end shrink-0 py-4 space-x-4">
|
||||||
<div class="flex flex-1 items-center space-x-2">
|
<div id="terminal-status-indicator" class="flex items-center text-xs text-zinc-500 dark:text-zinc-400">
|
||||||
<button data-action="clear-terminal" class="btn btn-outline h-8 px-3 text-xs">
|
<span class="relative flex h-2 w-2 mr-2">
|
||||||
<i class="fas fa-trash-alt mr-2 h-4 w-4"></i>
|
<span class="animate-ping absolute inline-flex h-full w-full rounded-full bg-zinc-400 dark:bg-zinc-500 opacity-75"></span>
|
||||||
清屏
|
<span class="relative inline-flex rounded-full h-2 w-2 bg-zinc-500 dark:bg-zinc-600"></span>
|
||||||
</button>
|
</span>
|
||||||
<button data-action="toggle-pause-terminal" class="btn btn-outline h-8 px-3 text-xs">
|
未连接
|
||||||
<i class="fas fa-pause mr-2 h-4 w-4"></i>
|
|
||||||
<span>暂停</span>
|
|
||||||
</button>
|
|
||||||
<button data-action="toggle-scroll-terminal" class="btn btn-outline h-8 px-3 text-xs">
|
|
||||||
<i class="fas fa-arrow-down mr-2 h-4 w-4"></i>
|
|
||||||
<span>自动滚动</span>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
<div class="flex items-center space-x-3">
|
|
||||||
<div id="terminal-status-indicator" class="flex items-center text-xs text-zinc-500 dark:text-zinc-400">
|
|
||||||
<span class="relative flex h-2 w-2 mr-2">
|
|
||||||
<span class="animate-ping absolute inline-flex h-full w-full rounded-full bg-zinc-400 dark:bg-zinc-500 opacity-75"></span>
|
|
||||||
<span class="relative inline-flex rounded-full h-2 w-2 bg-zinc-500 dark:bg-zinc-600"></span>
|
|
||||||
</span>
|
|
||||||
未连接
|
|
||||||
</div>
|
|
||||||
<button data-action="disconnect-terminal" class="btn btn-danger h-8 px-3 text-xs">
|
|
||||||
<i class="fas fa-times-circle mr-2 h-4 w-4"></i>
|
|
||||||
关闭
|
|
||||||
</button>
|
|
||||||
</div>
|
</div>
|
||||||
|
<button data-action="toggle-connect-terminal" class="text-zinc-500 hover:text-zinc-700 dark:text-zinc-400 dark:hover:text-zinc-200" title="连接">
|
||||||
|
<i class="fas fa-plug h-4 w-4"></i>
|
||||||
|
</button>
|
||||||
|
<button data-action="clear-terminal" class="text-zinc-500 hover:text-zinc-700 dark:text-zinc-400 dark:hover:text-zinc-200" title="清屏">
|
||||||
|
<i class="fas fa-refresh h-4 w-4"></i>
|
||||||
|
</button>
|
||||||
|
<button data-action="toggle-pause-terminal" class="text-zinc-500 hover:text-zinc-700 dark:text-zinc-400 dark:hover:text-zinc-200" title="暂停">
|
||||||
|
<i class="fas fa-pause h-4 w-4"></i>
|
||||||
|
</button>
|
||||||
|
<button data-action="toggle-scroll-terminal" class="text-zinc-500 hover:text-zinc-700 dark:text-zinc-400 dark:hover:text-zinc-200" title="自动滚动">
|
||||||
|
<i class="fas fa-arrow-down h-4 w-4"></i>
|
||||||
|
</button>
|
||||||
|
<button data-action="terminal-settings" class="text-zinc-500 hover:text-zinc-700 dark:text-zinc-400 dark:hover:text-zinc-200" title="设置">
|
||||||
|
<i class="fas fa-sliders-h h-4 w-4"></i>
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- [修改] 3.3 核心内容容器,它的 *内部* 将被动态替换 -->
|
<!-- [修改] 3.3 核心内容容器,它的 *内部* 将被动态替换 -->
|
||||||
|
|||||||
Reference in New Issue
Block a user