feat(mcp): MCP 调用日志只读列表页

This commit is contained in:
augushong
2026-08-16 20:55:03 +08:00
parent 918d1d4dd5
commit 25c98b6ed6
6 changed files with 187 additions and 0 deletions

View File

@@ -0,0 +1,86 @@
<?php
namespace base\admin\controller\system;
use app\admin\model\SystemMcpKey;
use app\admin\model\SystemMcpLog;
use app\admin\service\annotation\ControllerAnnotation;
use app\admin\service\annotation\NodeAnotation;
use app\common\controller\AdminController;
use think\App;
/**
* MCP调用日志只读列表.
* 日志仅由系统写入,不提供 add/edit/read/modify
* 保留 delete 供清理旧日志。
*
* @ControllerAnnotation(title="MCP调用日志")
*/
class McpLogBase extends AdminController
{
protected $relationSearch = true;
protected $sort = [
'id' => 'desc',
];
public function __construct(App $app)
{
parent::__construct($app);
$this->model = new SystemMcpLog();
// 密钥下拉搜索用SystemMcpKey 软删自动排除
$this->assign('select_list_mcp_key', SystemMcpKey::select(), true);
}
/**
* @NodeAnotation(title="列表")
*/
public function index()
{
if ($this->request->isAjax()) {
if (input('selectFields')) {
return $this->selectList();
}
list($page, $limit, $where) = $this->buildTableParames();
$count = $this->model
->withJoin('mcpKey', 'LEFT')
->where($where)
->count();
$list = $this->model
->withJoin('mcpKey', 'LEFT')
->where($where)
->page($page, $limit)
->order($this->sort)
->select();
$data = [
'code' => 0,
'msg' => '',
'count' => $count,
'data' => $list,
];
return json($data);
}
return $this->fetch();
}
/**
* @NodeAnotation(title="删除")
*/
public function delete($id)
{
$this->checkPostRequest();
$row = $this->model->whereIn('id', $id)->select();
$row->isEmpty() && $this->error('数据不存在');
try {
// 仅删除日志记录,不级联影响密钥表
$save = $row->delete();
} catch (\Exception $e) {
$this->error('删除失败:' . $e->getMessage());
}
$save ? $this->success('删除成功') : $this->error('删除失败');
}
}

View File

@@ -4,9 +4,30 @@ namespace base\admin\model;
use app\common\model\TimeModel;
/**
* @property int $id
* @property int $key_id 密钥ID
* @property string $node 节点
* @property string $arguments 参数摘要JSON
* @property int $is_success 是否成功 0:失败,1:成功
* @property int $cost_ms 耗时毫秒
* @property int $create_time 创建时间
* @property \app\admin\model\SystemMcpKey|null $mcpKey 密钥
*/
class SystemMcpLogBase extends TimeModel
{
protected $updateTime = false;
protected $deleteTime = false;
/**
* 所属密钥.
* withJoin 为 LEFT JOIN 且不附加软删条件:
* 密钥不存在时关联字段为 null、软删密钥仍可 join 出delete_time > 0
* 前端据 mcp_key.title / mcp_key.delete_time 兜底显示"(已删除)#id"。
*/
public function mcpKey()
{
return $this->belongsTo('app\admin\model\SystemMcpKey', 'key_id', 'id');
}
}

View File

@@ -0,0 +1,11 @@
var init = {
tableElem: '#currentTable',
tableRenderId: 'currentTableRenderId',
indexUrl: 'system.mcp_log/index',
addUrl: '',
editUrl: '',
readUrl: '',
deleteUrl: 'system.mcp_log/delete',
exportUrl: '',
modifyUrl: '',
};

View File

@@ -0,0 +1,9 @@
<div class="layuimini-container">
<div class="layuimini-main">
<table id="currentTable" class="layui-table layui-hide"
data-auth-index="{:auth('system.mcp_log/index')}"
data-auth-delete="{:auth('system.mcp_log/delete')}"
lay-filter="currentTable">
</table>
</div>
</div>

View File

@@ -0,0 +1,46 @@
$(function(){
// 密钥名称LEFT JOIN 关联,密钥不存在或已软删时兜底显示
var keyNameTemplet = function(d) {
if (d.mcp_key && d.mcp_key.title && (!d.mcp_key.delete_time || d.mcp_key.delete_time === 0)) {
return d.mcp_key.title;
}
return '<span style="color:#999">(已删除)#' + d.key_id + '</span>';
};
// 成功/失败徽章
var successTemplet = function(d) {
if (d.is_success == 1) {
return '<span style="display:inline-block;padding:2px 10px;border-radius:3px;color:#5FB878;background:#e8f8ef;font-size:12px;">成功</span>';
}
return '<span style="display:inline-block;padding:2px 10px;border-radius:3px;color:#FF5722;background:#ffe8e2;font-size:12px;">失败</span>';
};
// 参数摘要:截断 200 字符title 悬浮全文
var argumentsTemplet = function(d) {
if (!d.arguments) return '<span style="color:#999">-</span>';
var text = String(d.arguments);
var safe = text.replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;').replace(/"/g,'&quot;').replace(/'/g,'&#39;');
if (text.length > 200) {
return '<span title="' + safe + '" style="cursor:pointer;">' + safe.substring(0, 200) + '...</span>';
}
return safe;
};
ua.table.render({
init: init,
toolbar: ['refresh', 'delete'],
defaultToolbar: ['filter'],
cols: [[
{type: 'checkbox'},
{field: 'id', title: 'ID', width: 80, sort: true},
{field: 'key_id', title: '密钥', minWidth: 140, templet: keyNameTemplet, search: 'select', selectList: ua.getDataBrage('select_list_mcp_key'), selectValue: 'id', selectLabel: 'title'},
{field: 'node', title: '节点', minWidth: 180},
{field: 'is_success', title: '状态', width: 90, templet: successTemplet, search: 'select', selectList: {1: '成功', 0: '失败'}},
{field: 'cost_ms', title: '耗时(ms)', width: 100, search: false},
{field: 'arguments', title: '参数摘要', minWidth: 220, templet: argumentsTemplet, search: false},
{field: 'create_time', title: '调用时间', width: 170, templet: ua.table.date, search: 'range'},
]],
});
ua.listen();
})