首先就是后台对应api的实现,直接用Whale插件处理好的dockerclient方便写代码,新增后端api如下
1 |
|
然后就是前端实现上
{% extends "whale_base.html" %}
{% block menu %}
<li class="nav-item">
<a class="nav-link" href="/plugins/ctfd-whale/admin/settings">🔗{{"Settings" if en else "设置"}}</a>
</li>
<li class="nav-item">
<a class="nav-link" href="/plugins/ctfd-whale/admin/containers">🔗{{"Instances" if en else "实例"}}</a>
</li>
<li class="nav-item">
<a class="nav-link" href="/plugins/ctfd-whale/admin/upload">🔗{{"Upload" if en else "上传"}}</a>
</li>
<li class="nav-item">
<a class="nav-link active" href="#">{{"Containers" if en else "容器"}}</a>
</li>
{% endblock %}
{% block panel %}
<svg hidden>
<symbol id="copy" viewBox="0 0 24 24">
<path d="M16 1H4c-1.1 0-2 .9-2 2v14h2V3h12V1zm3 4H8c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h11c1.1 0 2-.9 2-2V7c0-1.1-.9-2-2-2zm0 16H8V7h11v14z"></path>
</symbol>
</svg>
<div class="row">
<div class="col-md-12">
<table class="table table-striped border">
<thead>
<tr>
<th class="sort-col text-center"><b>ID</b></td>
<th class="text-center"><b>{{"Image Name" if en else "镜像名称"}}</b></td>
<th class="sort-col text-center"><b>{{"Container Name" if en else "容器名称"}}</b></td>
<th class="sort-col text-center"><b>{{"Container Satus" if en else "运行状态"}}</b></td>
<th class="text-center"><b>{{"Container Logs" if en else "容器日志"}}</b></td>
</tr>
</thead>
<tbody>
{% for container in containers %}
<tr>
<td class="text-center">
{{ container.id | truncate(12)}}
<svg class="click-copy" data-copy="{{ container.id }}"
height="24" width="24" style="cursor: pointer;">
<use xlink:href="#copy" />
</svg>
</td>
<td class="text-center">
{{ container.image.tags[0] }}
<svg class="click-copy" data-copy="{{ container.image.tags[0] }}"
height="24" width="24" style="cursor: pointer;">
<use xlink:href="#copy" />
</svg>
</td>
<td class="text-center">
{{ container.name }}
</td>
<td class="text-center">
{{ container.status }}
</td>
<td class="text-center">
<a class="view-log" container-id="{{ container.id }}" data-toggle="tooltip" data-placement="top"
title="{{'Check Logs' if en else '查看日志'}}">
<i class="fas fa-file"></i>
</a>
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
{% endblock %}
{% block scripts %}
<script defer src="{{ url_for('plugins.ctfd-whale.assets', path='docker.js') }}"></script>
{% endblock %}
docker.js
1 |
|
然后就是查看日志的页面,为了看起来好看用xterm.js渲染日志,并且允许关闭自动刷新和设置日志显示行数
{% extends "admin/base.html" %}
{% if request.cookies.get('Scr1wCTFdLanguage') == 'en' %}
{% set en = true %}
{% else %}
{% set en = false %}
{% endif %}
{% block content %}
<link rel="stylesheet" href="{{ url_for('plugins.ctfd-whale.assets', path='log.css') }}">
<link rel="stylesheet" href="{{ url_for('plugins.ctfd-whale.assets', path='xterm.css') }}">
<div class="container mt-4">
<h1>容器日志</h1>
<div class="row mb-2">
<div class="col-md-6">
<label for="logLines">日志行数:</label>
<input type="number" id="logLines" class="form-control" value="500">
</div>
<div class="col-md-6">
<label for="switch">自动刷新:</label>
<br>
<div id="switch" class="switch">
<input type="checkbox" id="autoRefresh" checked>
<span class="slider round"></span>
</div>
</div>
</div>
<div id="terminal"></div>
</div>
{% endblock %}
{% block scripts %}
<script defer src="{{ url_for('plugins.ctfd-whale.assets', path='xterm.js') }}"></script>
<script defer src="{{ url_for('plugins.ctfd-whale.assets', path='xterm-addon-fit.js') }}"></script>
<script defer src="{{ url_for('plugins.ctfd-whale.assets', path='bundle.js') }}"></script>
<script>
// bundle的源码
// import {Terminal} from 'xterm'
// import {FitAddon} from 'xterm-addon-fit';
//
// window.addEventListener('DOMContentLoaded', (event) => {
// function getCookieForLanguage(name) {
// const cookies = document.cookie.split('; ');
// for (const cookie of cookies) {
// const [cookieName, cookieValue] = cookie.split('=');
// if (cookieName === name) {
// return decodeURIComponent(cookieValue);
// }
// }
// return null;
// }
// const terminal = new Terminal();
// const fitAddon = new FitAddon();
// terminal.loadAddon(fitAddon);
// terminal.open(document.getElementById('terminal'));
// fitAddon.fit();
//
// // 获取当前页面地址中的id参数
// const urlParams = new URLSearchParams(window.location.search);
// const id = urlParams.get('id');
//
// const logContent = document.getElementById('logContent');
// const logLines = document.getElementById('logLines');
// const autoRefresh = document.getElementById('autoRefresh');
//
// // 获取日志内容的函数
// function getLog() {
// const tail = logLines.value;
// const url = `/plugins/ctfd-whale/admin/getLog?id=${id}&tail=${tail}`;
//
// window.CTFd.fetch(url, {
// method: 'GET',
// credentials: 'same-origin',
// headers: {
// 'Accept': 'application/json',
// }
// }).then(function (response) {
// if (response.status === 429) {
// // User was ratelimited but process response
// return response.json();
// }
// if (response.status === 403) {
// // User is not logged in or CTF is paused.
// return response.json();
// }
// return response.json();
// }).then(function (response) {
// if (response.success) {
// terminal.clear();
// const messages = response.message.split('\n');
// for (const message of messages) {
// terminal.writeln(message);
// }
// } else {
// var e = new Object;
// e.title = (getCookieForLanguage("Scr1wCTFdLanguage") === "en" ? "Log Auto Refresh Fail!" : "日志自动刷新失败!");
// e.body = response.message;
// CTFd.ui.ezq.ezToast(e)
// }
// });
// }
//
// // 自动刷新日志内容
// let refreshInterval;
//
// function startAutoRefresh() {
// refreshInterval = setInterval(getLog, 10000); // 10 seconds
// }
//
// function stopAutoRefresh() {
// clearInterval(refreshInterval);
// }
//
// // 切换自动刷新状态
// autoRefresh.addEventListener('change', () => {
// if (autoRefresh.checked) {
// startAutoRefresh();
// } else {
// stopAutoRefresh();
// }
// });
// getLog()
// startAutoRefresh()
// })
</script>
{% endblock %}
bundle的源码就是上面注释里的内容,引入xterm和xterm-fit-addon使用webpack打包。
效果如下:
完活下机!