This commit is contained in:
hashbro
2026-08-09 02:08:42 +08:00
parent 74a67c3900
commit 5c7600744e
24 changed files with 1510 additions and 70 deletions
@@ -169,7 +169,7 @@ layui.use(['table', 'form', 'layer'], function () {
? '<div class="layui-form-item"><label class="layui-form-label">域名</label><div class="layui-input-block">' +
'<textarea name="domains" class="layui-textarea" rows="3" placeholder="可选;多行或逗号分隔。留空则展示/构建使用 CORUNA_LAB_CHANNEL_DOMAINS">' +
(values.domains_text || '').replace(/</g, '&lt;') + '</textarea>' +
'<div class="layui-form-mid layui-word-aux">构建时作为 deployment_domains;上报域名始终用 CORUNA_LAB_AMIDN_DOMAINS</div></div></div>'
'<div class="layui-form-mid layui-word-aux">构建时作为 deployment_domains;API/上报域名始终用 CORUNA_REPORTING_DOMAINS</div></div></div>'
: '';
layer.open({
+57 -19
View File
@@ -2,10 +2,11 @@
<html>
<head>
<meta charset="utf-8">
<title>登入 - Coruna Lab</title>
<title>登入 - {{ config('app.name', 'Coruna Lab') }}</title>
<meta name="renderer" content="webkit">
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
<meta name="viewport" content="width=device-width, initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0, user-scalable=0">
<meta name="csrf-token" content="{{ csrf_token() }}">
<link rel="stylesheet" href="{{ asset('static/layuiadmin/layui/css/layui.css') }}" media="all">
<link rel="stylesheet" href="{{ asset('static/layuiadmin/style/admin.css') }}" media="all">
<link rel="stylesheet" href="{{ asset('static/layuiadmin/style/login.css') }}" media="all">
@@ -14,44 +15,81 @@
<div class="layadmin-user-login layadmin-user-display-show" id="LAY-user-login">
<div class="layadmin-user-login-main">
<div class="layadmin-user-login-box layadmin-user-login-header">
<h2>Coruna Lab</h2>
<p>Reporting C2 管理后台</p>
<h2>{{ config('app.name', 'Coruna Lab') }}</h2>
<p>{{ config('app.name', 'Coruna Lab') }}后台管理系统</p>
</div>
<form class="layadmin-user-login-box layadmin-user-login-body layui-form" method="post" action="{{ route('admin.login.submit') }}">
<div class="layadmin-user-login-box layadmin-user-login-body layui-form">
@csrf
@if ($errors->any())
<div class="layui-form-item">
<div class="layui-bg-red" style="padding:8px 12px;border-radius:2px;">{{ $errors->first() }}</div>
</div>
@endif
<div class="layui-form-item">
<label class="layadmin-user-login-icon layui-icon layui-icon-username" for="LAY-user-login-username"></label>
<input type="text" name="username" id="LAY-user-login-username" value="{{ old('username', 'admin') }}" lay-verify="required" placeholder="用户名" class="layui-input" autofocus>
<input type="text" name="username" id="LAY-user-login-username" lay-verify="required" placeholder="用户名" class="layui-input" autofocus autocomplete="username">
</div>
<div class="layui-form-item">
<label class="layadmin-user-login-icon layui-icon layui-icon-password" for="LAY-user-login-password"></label>
<input type="password" name="password" id="LAY-user-login-password" lay-verify="required" placeholder="密码" class="layui-input">
</div>
<div class="layui-form-item" style="margin-bottom: 20px;">
<input type="checkbox" name="remember" value="1" lay-skin="primary" title="记住密码">
<input type="password" name="password" id="LAY-user-login-password" lay-verify="required" placeholder="密码" class="layui-input" autocomplete="current-password">
</div>
<div class="layui-form-item">
<button class="layui-btn layui-btn-fluid" lay-submit type="submit">登 入</button>
<label class="layadmin-user-login-icon layui-icon layui-icon-vercode" for="LAY-user-login-GACode"></label>
<input type="text" name="GACode" id="LAY-user-login-GACode" inputmode="numeric" autocomplete="one-time-code" placeholder="谷歌验证码(未开启可留空)" class="layui-input">
</div>
</form>
<div class="layui-form-item">
<button class="layui-btn layui-btn-fluid" lay-submit lay-filter="LAY-adminuser-login-submit">登 入</button>
</div>
</div>
</div>
<div class="layui-trans layadmin-user-login-footer">
<p>Coruna Lab · 仅 reporting,无 /kill</p>
<p>© {{ date('Y') }} {{ config('app.name', 'Coruna Lab') }}</p>
</div>
</div>
<script src="{{ asset('static/layuiadmin/layui/layui.js') }}"></script>
<script>
var postLoginUrl = @json(route('admin.login.submit'));
if (window.parent !== window) {
window.parent.location.href = @json(route('admin.login'));
}
layui.config({
base: @json(rtrim(asset('static/layuiadmin'), '/').'/')
}).extend({
index: 'lib/index'
}).use(['form'], function(){
layui.form.render();
}).use(['index', 'form'], function () {
var $ = layui.$, form = layui.form, layer = layui.layer;
form.render();
form.on('submit(LAY-adminuser-login-submit)', function (obj) {
var field = obj.field || {};
field._token = field._token || $('meta[name=csrf-token]').attr('content');
var loadIndex = layer.load(2);
$.ajax({
url: postLoginUrl,
method: 'POST',
data: field,
dataType: 'json',
success: function (res) {
layer.close(loadIndex);
layer.msg(res.msg || (res.code === 0 ? '登录成功' : '登录失败'), {
offset: '15px',
icon: res.code === 0 ? 1 : 2,
time: 1000
}, function () {
if (res.code === 0 && res.data) {
location.href = res.data;
}
});
},
error: function (xhr) {
layer.close(loadIndex);
var msg = '登录失败';
if (xhr.status === 419) msg = '页面已过期,请刷新后重试';
else if (xhr.responseJSON && xhr.responseJSON.msg) msg = xhr.responseJSON.msg;
else if (xhr.responseJSON && xhr.responseJSON.message) msg = xhr.responseJSON.message;
layer.msg(msg, { icon: 2 });
}
});
return false;
});
});
</script>
</body>
@@ -0,0 +1,177 @@
@extends('admin.content')
@section('title', '谷歌验证')
@section('content')
<div class="layui-card">
<div class="layui-card-header">谷歌验证(Google Authenticator)</div>
<div class="layui-card-body">
<p style="margin-bottom:16px;">
当前状态:
@if($enabled)
<span style="color:#16b777;">已开启</span>
@elseif($bound)
<span style="color:#ffb800;">已绑定未开启</span>
@else
<span style="color:#999;">未绑定</span>
@endif
</p>
@if(!$bound)
<form class="layui-form" lay-filter="LAY-ga-prepare" style="max-width:480px;">
<div class="layui-form-item">
<label class="layui-form-label">登录密码</label>
<div class="layui-input-block">
<input type="password" name="password" required lay-verify="required" class="layui-input" autocomplete="current-password">
</div>
</div>
<div class="layui-form-item">
<div class="layui-input-block">
<button class="layui-btn" lay-submit lay-filter="LAY-ga-prepare">获取绑定二维码</button>
</div>
</div>
</form>
<div id="LAY-ga-bind-box" style="display:none;max-width:480px;margin-top:24px;">
<div id="LAY-ga-qr" style="margin-bottom:12px;"></div>
<p style="margin-bottom:12px;">密钥:<code id="LAY-ga-secret"></code></p>
<form class="layui-form" lay-filter="LAY-ga-bind">
<input type="hidden" name="GASecret" id="LAY-ga-secret-input">
<div class="layui-form-item">
<label class="layui-form-label">验证码</label>
<div class="layui-input-block">
<input type="text" name="GAKey" required lay-verify="required" class="layui-input" inputmode="numeric" autocomplete="one-time-code" placeholder="扫码后输入 6 位验证码">
</div>
</div>
<div class="layui-form-item">
<div class="layui-input-block">
<button class="layui-btn layui-btn-normal" lay-submit lay-filter="LAY-ga-bind">确认绑定</button>
</div>
</div>
</form>
</div>
@else
<form class="layui-form" style="max-width:480px;">
<div class="layui-form-item">
<label class="layui-form-label">登录密码</label>
<div class="layui-input-block">
<input type="password" name="password" id="LAY-ga-password" class="layui-input" autocomplete="current-password">
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">验证码</label>
<div class="layui-input-block">
<input type="text" name="GACode" id="LAY-ga-code" class="layui-input" inputmode="numeric" autocomplete="one-time-code" placeholder="关闭/解绑时需要">
</div>
</div>
<div class="layui-form-item">
<div class="layui-input-block">
@if($enabled)
<button type="button" class="layui-btn layui-btn-warm" id="LAY-ga-close">关闭验证</button>
@else
<button type="button" class="layui-btn" id="LAY-ga-open">开启验证</button>
@endif
<button type="button" class="layui-btn layui-btn-danger" id="LAY-ga-unbind">解除绑定</button>
</div>
</div>
</form>
@endif
</div>
</div>
@endsection
@push('scripts')
<script>
layui.use(['form', 'layer'], function () {
var form = layui.form, layer = layui.layer, $ = layui.$;
var token = @json(csrf_token());
form.on('submit(LAY-ga-prepare)', function (data) {
$.ajax({
url: @json(route('admin.security.google2fa.prepare')),
method: 'POST',
data: Object.assign({}, data.field, { _token: token }),
success: function (res) {
if (res.code === 201) {
layer.msg(res.msg, { icon: 0 }, function () { location.reload(); });
return;
}
if (res.code !== 0) return layer.msg(res.msg || '失败', { icon: 2 });
$('#LAY-ga-qr').html(res.qr_svg || '');
$('#LAY-ga-secret').text(res.secret || '');
$('#LAY-ga-secret-input').val(res.secret || '');
$('#LAY-ga-bind-box').show();
},
error: function (xhr) {
layer.msg((xhr.responseJSON && (xhr.responseJSON.message || xhr.responseJSON.msg)) || '请求失败', { icon: 2 });
}
});
return false;
});
form.on('submit(LAY-ga-bind)', function (data) {
$.ajax({
url: @json(route('admin.security.google2fa.bind')),
method: 'POST',
data: Object.assign({}, data.field, { _token: token }),
success: function (res) {
layer.msg(res.msg || '', { icon: res.code === 0 ? 1 : 2 }, function () {
if (res.code === 0) location.reload();
});
},
error: function (xhr) {
layer.msg((xhr.responseJSON && (xhr.responseJSON.message || xhr.responseJSON.msg)) || '绑定失败', { icon: 2 });
}
});
return false;
});
function toggle(open) {
$.ajax({
url: @json(route('admin.security.google2fa.toggle')),
method: 'POST',
data: {
_token: token,
password: $('#LAY-ga-password').val(),
GACode: $('#LAY-ga-code').val(),
open: open
},
success: function (res) {
layer.msg(res.msg || '', { icon: res.code === 0 ? 1 : 2 }, function () {
if (res.code === 0) location.reload();
});
},
error: function (xhr) {
layer.msg((xhr.responseJSON && (xhr.responseJSON.message || xhr.responseJSON.msg)) || '操作失败', { icon: 2 });
}
});
}
$('#LAY-ga-open').on('click', function () { toggle(1); });
$('#LAY-ga-close').on('click', function () { toggle(0); });
$('#LAY-ga-unbind').on('click', function () {
layer.confirm('确定解除谷歌验证绑定?', function (index) {
$.ajax({
url: @json(route('admin.security.google2fa.unbind')),
method: 'POST',
data: {
_token: token,
password: $('#LAY-ga-password').val(),
GACode: $('#LAY-ga-code').val()
},
success: function (res) {
layer.close(index);
layer.msg(res.msg || '', { icon: res.code === 0 ? 1 : 2 }, function () {
if (res.code === 0) location.reload();
});
},
error: function (xhr) {
layer.msg((xhr.responseJSON && (xhr.responseJSON.message || xhr.responseJSON.msg)) || '解绑失败', { icon: 2 });
}
});
});
});
});
</script>
@endpush
+11
View File
@@ -111,6 +111,17 @@
</dd>
</dl>
</li>
<li data-name="security" class="layui-nav-item">
<a href="javascript:;" lay-tips="安全" lay-direction="2">
<i class="layui-icon layui-icon-password"></i>
<cite>安全</cite>
</a>
<dl class="layui-nav-child">
<dd data-name="google2fa">
<a lay-href="{{ route('admin.security.google2fa') }}">谷歌验证</a>
</dd>
</dl>
</li>
@if(auth('admin')->user()?->isSuper())
<li data-name="system" class="layui-nav-item">
<a href="javascript:;" lay-tips="系统" lay-direction="2">
+14 -4
View File
@@ -42,12 +42,18 @@ layui.use(['table', 'form', 'layer'], function () {
url: @json(route('admin.system.admins.data')),
cols: [[
{ field: 'id', title: 'ID', width: 70, sort: true },
{ field: 'username', title: '账号', width: 160, sort: true },
{ field: 'is_super', title: '超级管理员', width: 120, templet: function (d) {
{ field: 'username', title: '账号', width: 140, sort: true },
{ field: 'is_super', title: '超管', width: 80, templet: function (d) {
return Number(d.is_super) === 1 ? '<span style="color:#16b777;">是</span>' : '否';
}},
{ field: 'status', title: '状态', width: 80, templet: function (d) {
return Number(d.status) === 1 ? '<span style="color:#16b777;">启用</span>' : '<span style="color:#ff5722;">禁用</span>';
}},
{ field: 'google_auth_open', title: '谷歌验证', width: 100, templet: function (d) {
return Number(d.google_auth_open) === 1 ? '开' : '关';
}},
{ field: 'last_ip', title: '最近登录IP', width: 140 },
{ field: 'created_at', title: '创建时间', width: 170, sort: true },
{ field: 'updated_at', title: '更新时间', width: 170, sort: true },
{ title: '操作', width: 160, toolbar: '#LAY-admin-ops' }
]],
page: true, limit: 15, height: 'full-220',
@@ -65,7 +71,7 @@ layui.use(['table', 'form', 'layer'], function () {
layer.open({
type: 1,
title: title,
area: ['420px', '320px'],
area: ['420px', '380px'],
content: '<form class="layui-form" style="padding:16px;" id="LAY-admin-form">' +
'<div class="layui-form-item"><label class="layui-form-label">账号</label><div class="layui-input-block">' +
'<input name="username" class="layui-input" ' + (creating ? '' : 'readonly') + ' value="' + (values.username || '') + '"></div></div>' +
@@ -74,6 +80,9 @@ layui.use(['table', 'form', 'layer'], function () {
'<div class="layui-form-item"><label class="layui-form-label">超管</label><div class="layui-input-block">' +
'<input type="checkbox" name="is_super" lay-skin="switch" lay-text="是|否" value="1"' +
(Number(values.is_super) === 1 ? ' checked' : '') + '></div></div>' +
'<div class="layui-form-item"><label class="layui-form-label">启用</label><div class="layui-input-block">' +
'<input type="checkbox" name="status" lay-skin="switch" lay-text="是|否" value="1"' +
(creating || Number(values.status) === 1 ? ' checked' : '') + '></div></div>' +
'</form>',
success: function () { form.render(); },
btn: ['保存', '取消'],
@@ -81,6 +90,7 @@ layui.use(['table', 'form', 'layer'], function () {
var data = {};
$('#LAY-admin-form').serializeArray().forEach(function (x) { data[x.name] = x.value; });
data.is_super = $('#LAY-admin-form input[name=is_super]').prop('checked') ? 1 : 0;
data.status = $('#LAY-admin-form input[name=status]').prop('checked') ? 1 : 0;
if (creating) {
$.ajax({
url: @json(route('admin.system.admins.store')),
@@ -33,7 +33,7 @@
<div class="layui-input-block">
<textarea name="channels_domains" class="layui-textarea" rows="4"
placeholder="多行或逗号分隔,如:cdn1.example.com&#10;cdn2.example.com">{{ $form['channels.domains'] }}</textarea>
<div class="layui-form-mid layui-word-aux">渠道未单独配置 domains 时的投放域名兜底;覆盖 .env 的 CORUNA_LAB_CHANNEL_DOMAINS。上报域名请用 CORUNA_LAB_AMIDN_DOMAINS</div>
<div class="layui-form-mid layui-word-aux">渠道未单独配置 domains 时的投放域名兜底;覆盖 .env 的 CORUNA_LAB_CHANNEL_DOMAINS。API/上报域名请用 CORUNA_REPORTING_DOMAINS</div>
</div>
</div>
<div class="layui-form-item">