添加敏感数据清理和免责声明

This commit is contained in:
2025-12-22 23:17:34 +08:00
committed by mengning
parent 53ff8f0112
commit 4a82821e8e
4 changed files with 454 additions and 6 deletions

View File

@@ -3964,6 +3964,28 @@ manage = Container Management
registry_username = Registry Username
registry_password = Registry Password
save_tip = 💡 Tip: It is recommended to prioritize using <strong>Build From Dockerfile</strong> to save images. Saving images directly from containers is opaque and storage-bloated, while building through Dockerfile ensures image reproducibility and minimization.
security_disclaimer_title = [Important Notice] Data Security Disclaimer
security_disclaimer_warning_title = ⚠️ You have selected direct container commit mode
security_disclaimer_warning_content = This mode will save all runtime data of the container. Before saving, please ensure that you have manually cleaned all sensitive data, including but not limited to:
security_disclaimer_key_credentials = Keys and credentials: SSH keys, API keys, access tokens, passwords, etc.
security_disclaimer_personal_docs = Personal documents: Personal files, business secrets, unpublished code, etc.
security_disclaimer_connection_info = Connection information: Database connection strings, service endpoints, authentication information, etc.
security_disclaimer_history = History records: Command history, log files, etc.
security_disclaimer_config_files = Configuration files: API keys and tokens in code, passwords and credentials in configuration files
security_disclaimer_env_vars = Environment variables: Sensitive information in environment variables
security_disclaimer_temp_files = Temporary files: Temporary files and cache data
security_disclaimer_auto_clean = System will automatically clean: SSH keys (authorized_keys), SSH host keys, command history, Git configuration, temporary files and other system-generated data.
security_disclaimer_manual_clean = ⚠️ However, the system cannot clean data you have added manually (such as keys in code, personal documents, etc.), which requires manual cleanup!
security_disclaimer_risk_warning = If not cleaned properly, others using your saved container image may cause data leakage. The risk of data leakage and related responsibilities arising from this shall be borne by the user!
security_disclaimer_view_link = View Disclaimer
security_disclaimer_accept_label = I have read and understood the risks above and confirm that all sensitive data has been cleaned
security_disclaimer_cancel_button = Cancel
security_disclaimer_accept_button = I understand the risks and confirm
security_disclaimer_validation_error = Please read and confirm the data security disclaimer first
save_image_modal_title = Save as Container Image
save_image_submit_button = Submit
save_image_cancel_button = Cancel
save_image_required_fields_error = Please fill in all required fields
scripts = ShellScripts
scripts.management = ShellScripts Management
scripts.creation = Add ShellScript

View File

@@ -3953,6 +3953,28 @@ manage=容器管理
registry_username = 镜像仓库用户名
registry_password = 镜像仓库密码
save_tip = 💡 提示:建议优先使用 <strong>Build From Dockerfile</strong> 方式保存镜像。从容器直接保存镜像不透明且存储臃肿而通过Dockerfile构建可以确保镜像的可重现性和最小化。
security_disclaimer_title = 【重要提示】数据安全免责声明
security_disclaimer_warning_title = ⚠️ 您选择了直接提交容器模式
security_disclaimer_warning_content = 此模式会保存容器的所有运行时数据,在保存前请务必自行清理所有敏感数据,包括但不限于:
security_disclaimer_key_credentials = 密钥和凭证SSH密钥、API密钥、访问令牌、密码等
security_disclaimer_personal_docs = 个人文档:个人文件、商业机密、未公开代码等
security_disclaimer_connection_info = 连接信息:数据库连接字符串、服务端点、认证信息等
security_disclaimer_history = 历史记录:命令历史、日志文件等
security_disclaimer_config_files = 配置文件代码中的API密钥和令牌、配置文件中的密码和凭证
security_disclaimer_env_vars = 环境变量:环境变量中的敏感信息
security_disclaimer_temp_files = 临时文件:临时文件和缓存数据
security_disclaimer_auto_clean = 系统将自动清理SSH密钥authorized_keys、SSH主机密钥、命令历史记录、Git配置、临时文件等系统生成的数据。
security_disclaimer_manual_clean = ⚠️ 但系统无法清理您自行添加的数据(如代码中的密钥、个人文档等),需要您手动清理!
security_disclaimer_risk_warning = 如未清理干净,他人使用您保存的容器镜像时可能造成数据泄露,由此带来的数据泄露风险及相关责任由用户自行承担!
security_disclaimer_view_link = 查看免责声明
security_disclaimer_accept_label = 我已阅读并理解上述风险,确认已清理所有敏感数据
security_disclaimer_cancel_button = 取消
security_disclaimer_accept_button = 我已了解风险并确认
security_disclaimer_validation_error = 请先阅读并确认数据安全免责声明
save_image_modal_title = 保存为容器镜像
save_image_submit_button = 提交
save_image_cancel_button = 取消
save_image_required_fields_error = 请填写所有必填字段
scripts=脚本管理
scripts.management=脚本管理
scripts.creation=添加脚本

View File

@@ -392,6 +392,95 @@ func StopDevContainerByDocker(ctx context.Context, devContainerName string) erro
}
return nil
}
// CleanSensitiveDataBeforeCommit 在提交容器前清理敏感数据
// 清理系统自动生成的敏感数据如SSH密钥、历史记录等
func CleanSensitiveDataBeforeCommit(ctx context.Context, cli *client.Client, containerName string) error {
// 构建清理命令
// 清理SSH相关数据、历史记录、Git配置和临时文件
cleanupCommand := `
# 清理SSH密钥authorized_keys
rm -f ~/.ssh/authorized_keys 2>/dev/null || true
rm -f /root/.ssh/authorized_keys 2>/dev/null || true
# 清理SSH主机密钥容器重启时会重新生成
rm -f /etc/ssh/ssh_host_* 2>/dev/null || true
# 清理SSH known_hosts可能包含服务器指纹信息
rm -f ~/.ssh/known_hosts 2>/dev/null || true
rm -f ~/.ssh/known_hosts.old 2>/dev/null || true
rm -f /root/.ssh/known_hosts 2>/dev/null || true
rm -f /root/.ssh/known_hosts.old 2>/dev/null || true
# 清理命令历史记录
rm -f ~/.bash_history ~/.zsh_history ~/.history ~/.fish_history 2>/dev/null || true
rm -f /root/.bash_history /root/.zsh_history /root/.history /root/.fish_history 2>/dev/null || true
# 同时清空当前会话的历史
history -c 2>/dev/null || true
# 清理Git配置和凭证可能包含用户信息和token
rm -f ~/.gitconfig 2>/dev/null || true
rm -f /root/.gitconfig 2>/dev/null || true
rm -f ~/.git-credentials 2>/dev/null || true
rm -f /root/.git-credentials 2>/dev/null || true
# 清理Docker凭证可能包含registry认证信息
rm -f ~/.docker/config.json 2>/dev/null || true
rm -f /root/.docker/config.json 2>/dev/null || true
# 清理AWS凭证和配置
rm -f ~/.aws/credentials 2>/dev/null || true
rm -f ~/.aws/config 2>/dev/null || true
rm -f /root/.aws/credentials 2>/dev/null || true
rm -f /root/.aws/config 2>/dev/null || true
# 清理npm/yarn凭证可能包含registry token
rm -f ~/.npmrc 2>/dev/null || true
rm -f ~/.yarnrc 2>/dev/null || true
rm -f /root/.npmrc 2>/dev/null || true
rm -f /root/.yarnrc 2>/dev/null || true
# 清理pip凭证Python包管理器
rm -f ~/.pip/pip.conf 2>/dev/null || true
rm -f ~/.pypirc 2>/dev/null || true
rm -f /root/.pip/pip.conf 2>/dev/null || true
rm -f /root/.pypirc 2>/dev/null || true
# 清理其他包管理器凭证
rm -f ~/.composer/auth.json 2>/dev/null || true
rm -f /root/.composer/auth.json 2>/dev/null || true
rm -f ~/.m2/settings.xml 2>/dev/null || true
rm -f /root/.m2/settings.xml 2>/dev/null || true
rm -f ~/.gradle/gradle.properties 2>/dev/null || true
rm -f /root/.gradle/gradle.properties 2>/dev/null || true
# 清理SSH私钥如果存在
rm -f ~/.ssh/id_rsa ~/.ssh/id_ed25519 ~/.ssh/id_ecdsa ~/.ssh/id_dsa 2>/dev/null || true
rm -f /root/.ssh/id_rsa /root/.ssh/id_ed25519 /root/.ssh/id_ecdsa /root/.ssh/id_dsa 2>/dev/null || true
rm -f ~/.ssh/id_rsa.pub ~/.ssh/id_ed25519.pub ~/.ssh/id_ecdsa.pub ~/.ssh/id_dsa.pub 2>/dev/null || true
rm -f /root/.ssh/id_rsa.pub /root/.ssh/id_ed25519.pub /root/.ssh/id_ecdsa.pub /root/.ssh/id_dsa.pub 2>/dev/null || true
# 清理临时文件和缓存
rm -rf /tmp/* /var/tmp/* 2>/dev/null || true
rm -rf ~/.cache/* /root/.cache/* 2>/dev/null || true
# 清理系统日志(用户可访问的部分)
rm -rf /var/log/*.log 2>/dev/null || true
echo "敏感数据清理完成"
`
// 执行清理命令
output, err := docker_module.ExecCommandInContainer(ctx, cli, containerName, cleanupCommand)
if err != nil {
log.Warn("清理敏感数据时出现错误: %v, 输出: %s", err, output)
// 清理失败不阻止提交,但记录警告日志
return fmt.Errorf("清理敏感数据时出错: %v (输出: %s)", err, output)
}
log.Info("容器 %s 敏感数据清理完成,输出: %s", containerName, output)
return nil
}
func UpdateDevContainerByDocker(ctx context.Context, devContainerInfo *devcontainer_models.Devcontainer, updateInfo *UpdateInfo, repo *gitea_context.Repository, doer *user.User) error {
// 创建docker client
cli, err := docker_module.CreateDockerClient(ctx)
@@ -497,6 +586,14 @@ func UpdateDevContainerByDocker(ctx context.Context, devContainerInfo *devcontai
}
} else {
// 非Dockerfile模式直接提交容器
// 在提交前清理敏感数据
log.Info("开始清理容器 %s 的敏感数据...", devContainerInfo.Name)
if err := CleanSensitiveDataBeforeCommit(ctx, cli, devContainerInfo.Name); err != nil {
log.Warn("清理敏感数据时出现警告: %v继续执行提交操作", err)
// 清理失败不阻止提交,但已记录警告
}
// 获取容器 ID
containerID, err := docker_module.GetContainerID(cli, devContainerInfo.Name)
if err != nil {

View File

@@ -1,6 +1,6 @@
<div class="ui g-modal-confirm delete modal" style="width: 35%" id="updatemodal">
<div class="header">
保存为容器镜像
{{ctx.Locale.Tr "devcontainer.save_image_modal_title"}}
</div>
<div class="content" style="position: relative;">
<form class="ui form tw-max-w-2xl tw-m-auto" id="updateForm">
@@ -35,21 +35,35 @@
<div class="inline field">
<div class="ui checkbox">
{{if .HasDevContainerDockerfile}}
<input type="checkbox" id="SaveMethod" name="SaveMethod" value="on" checked>
<input type="checkbox" id="SaveMethod" name="SaveMethod" value="on" checked onchange="handleSaveMethodChange(); checkSubmitButtonState();">
<label for="SaveMethod">Build From Dockerfile{{if .DockerfilePath}}: <a id="dockerfileLinkStatic" href="{{.Repository.Link}}/_edit/{{.Repository.DefaultBranch | PathEscapeSegments}}/{{.DockerfilePath | PathEscapeSegments}}" target="_blank" style="color: #2185d0; text-decoration: underline;">{{.DockerfilePath}}</a>{{end}}<span id="dockerfilePathSpan" style="display: none;">: <a id="dockerfileLink" href="#" target="_blank" style="color: #2185d0; text-decoration: underline;"></a></span></label>
{{else}}
<input type="checkbox" id="SaveMethod" name="SaveMethod" value="on" checked>
<input type="checkbox" id="SaveMethod" name="SaveMethod" value="on" checked onchange="handleSaveMethodChange(); checkSubmitButtonState();">
<label for="SaveMethod">Build From Dockerfile<span id="dockerfilePathSpan" style="display: none;">: <a id="dockerfileLink" href="#" target="_blank" style="color: #2185d0; text-decoration: underline;"></a></span></label>
{{end}}
</div>
<div style="color: #2185d0; font-size: 13px; margin-top: 12px; padding: 10px; background-color: #f0f8ff; border-left: 3px solid #2185d0; border-radius: 3px;">
{{ctx.Locale.TrHTML "devcontainer.save_tip"}}
</div>
<!-- 用户确认复选框仅在未勾选Build From Dockerfile时显示 -->
<div class="field" id="securityDisclaimerCheckbox" style="display: none; margin-top: 15px;">
<div style="margin-bottom: 8px;">
<a href="javascript:void(0);" id="showDisclaimerLink" style="color: #2185d0; text-decoration: underline; cursor: pointer;">
<i class="warning circle icon" style="margin-right: 4px;"></i>{{ctx.Locale.Tr "devcontainer.security_disclaimer_view_link"}}
</a>
</div>
<div class="ui checkbox">
<input type="checkbox" id="SecurityDisclaimerAccepted" name="SecurityDisclaimerAccepted" onchange="checkSubmitButtonState()">
<label for="SecurityDisclaimerAccepted" style="font-weight: bold;">
{{ctx.Locale.Tr "devcontainer.security_disclaimer_accept_label"}}
</label>
</div>
</div>
</div>
<div class="actions">
<button class="ui primary button" type="button" id="updateSubmitButton">提交</button>
<button class="ui cancel button" type="button" id="updateCloseButton">取消</button>
<button class="ui primary button" type="button" id="updateSubmitButton">{{ctx.Locale.Tr "devcontainer.save_image_submit_button"}}</button>
<button class="ui cancel button" type="button" id="updateCloseButton">{{ctx.Locale.Tr "devcontainer.save_image_cancel_button"}}</button>
</div>
</form>
@@ -61,6 +75,56 @@
</div>
<!-- 免责声明遮罩层和弹窗不使用语义UI的modal避免关闭保存模态框 -->
<div id="securityDisclaimerOverlay" class="security-disclaimer-overlay" style="display: none;">
<div class="security-disclaimer-modal">
<div class="security-disclaimer-header">
<div style="display: flex; align-items: center;">
<i class="warning circle icon" style="color: #d01919; margin-right: 10px;"></i>
{{ctx.Locale.Tr "devcontainer.security_disclaimer_title"}}
</div>
</div>
<div class="security-disclaimer-body">
<div class="ui negative message">
<div class="header">{{ctx.Locale.Tr "devcontainer.security_disclaimer_warning_title"}}</div>
<p style="margin: 10px 0; font-weight: bold;">{{ctx.Locale.Tr "devcontainer.security_disclaimer_warning_content"}}</p>
</div>
<div class="security-disclaimer-content" style="margin-top: 15px;">
<ul style="margin: 10px 0; padding-left: 20px; line-height: 1.8;">
<li><strong>{{ctx.Locale.Tr "devcontainer.security_disclaimer_key_credentials"}}</strong></li>
<li><strong>{{ctx.Locale.Tr "devcontainer.security_disclaimer_personal_docs"}}</strong></li>
<li><strong>{{ctx.Locale.Tr "devcontainer.security_disclaimer_connection_info"}}</strong></li>
<li><strong>{{ctx.Locale.Tr "devcontainer.security_disclaimer_history"}}</strong></li>
<li><strong>{{ctx.Locale.Tr "devcontainer.security_disclaimer_config_files"}}</strong></li>
<li><strong>{{ctx.Locale.Tr "devcontainer.security_disclaimer_env_vars"}}</strong></li>
<li><strong>{{ctx.Locale.Tr "devcontainer.security_disclaimer_temp_files"}}</strong></li>
</ul>
<div class="ui info message" style="margin-top: 15px;">
<p style="margin: 8px 0;">
<strong>{{ctx.Locale.Tr "devcontainer.security_disclaimer_auto_clean"}}</strong>
</p>
<p style="margin: 8px 0; color: #d01919; font-weight: bold;">
{{ctx.Locale.Tr "devcontainer.security_disclaimer_manual_clean"}}
</p>
</div>
<div class="ui negative message" style="margin-top: 15px;">
<p style="margin: 10px 0; color: #d01919; font-weight: bold; font-size: 16px;">
{{ctx.Locale.Tr "devcontainer.security_disclaimer_risk_warning"}}
</p>
</div>
</div>
</div>
<div class="security-disclaimer-actions">
<button class="ui cancel button" type="button" id="disclaimerCancelButton">{{ctx.Locale.Tr "devcontainer.security_disclaimer_cancel_button"}}</button>
<button class="ui primary button" type="button" id="disclaimerAcceptButton">{{ctx.Locale.Tr "devcontainer.security_disclaimer_accept_button"}}</button>
</div>
</div>
</div>
<script>
// 保存模态框相关功能 - 可复用组件
(function() {
@@ -79,6 +143,73 @@
}
};
// 检查提交按钮状态
window.checkSubmitButtonState = function() {
const saveMethodCheckbox = document.getElementById('SaveMethod');
const disclaimerAcceptedCheckbox = document.getElementById('SecurityDisclaimerAccepted');
const submitButton = document.getElementById('updateSubmitButton');
if (!submitButton) return;
// 如果Build From Dockerfile已勾选允许提交
if (saveMethodCheckbox && saveMethodCheckbox.checked) {
submitButton.disabled = false;
return;
}
// 如果Build From Dockerfile未勾选必须勾选免责声明才能提交
if (disclaimerAcceptedCheckbox && disclaimerAcceptedCheckbox.checked) {
submitButton.disabled = false;
} else {
submitButton.disabled = true;
}
};
// 显示免责声明遮罩层
window.showDisclaimerOverlay = function() {
const disclaimerOverlay = document.getElementById('securityDisclaimerOverlay');
if (disclaimerOverlay) {
disclaimerOverlay.style.display = 'flex';
}
};
// 处理SaveMethod变更显示/隐藏警告和确认框
window.handleSaveMethodChange = function() {
const saveMethodCheckbox = document.getElementById('SaveMethod');
const securityDisclaimerCheckbox = document.getElementById('securityDisclaimerCheckbox');
const disclaimerAcceptedCheckbox = document.getElementById('SecurityDisclaimerAccepted');
const disclaimerOverlay = document.getElementById('securityDisclaimerOverlay');
if (!saveMethodCheckbox.checked) {
// 非Dockerfile模式直接提交容器- 显示免责声明遮罩层
// 先重置确认框状态
if (disclaimerAcceptedCheckbox) {
disclaimerAcceptedCheckbox.checked = false;
}
// 隐藏确认框(等待用户确认免责声明后再显示)
if (securityDisclaimerCheckbox) {
securityDisclaimerCheckbox.style.display = 'none';
}
// 显示免责声明遮罩层(在保存模态框之上,但不关闭保存模态框)
if (disclaimerOverlay) {
disclaimerOverlay.style.display = 'flex';
}
} else {
// Dockerfile模式 - 隐藏确认框和免责声明遮罩层
if (securityDisclaimerCheckbox) {
securityDisclaimerCheckbox.style.display = 'none';
}
if (disclaimerAcceptedCheckbox) {
disclaimerAcceptedCheckbox.checked = false;
}
if (disclaimerOverlay) {
disclaimerOverlay.style.display = 'none';
}
}
// 检查提交按钮状态
checkSubmitButtonState();
};
// 提交保存表单
// targetUrl: 保存请求的URL
// statusUrl: 状态检查URL用于轮询
@@ -95,10 +226,29 @@
const RepositoryPassword = formData.get('RepositoryPassword');
const SaveMethod = formData.get('SaveMethod');
const ImageName = formData.get('ImageName');
const SecurityDisclaimerAccepted = formData.get('SecurityDisclaimerAccepted');
// 验证必填字段
if (!ImageName || !RepositoryUsername || !RepositoryPassword) {
alert('请填写所有必填字段');
const requiredFieldsError = '{{ctx.Locale.Tr "devcontainer.save_image_required_fields_error"}}';
alert(requiredFieldsError);
return;
}
// 验证用户已接受免责声明仅在非Dockerfile模式时需要
// 注意:这里再次检查是因为按钮可能被手动启用,需要双重验证
if (!SaveMethod && !SecurityDisclaimerAccepted) {
const errorMsg = '{{ctx.Locale.Tr "devcontainer.security_disclaimer_validation_error"}}';
alert(errorMsg);
const disclaimerCheckbox = document.getElementById('SecurityDisclaimerAccepted');
if (disclaimerCheckbox) {
disclaimerCheckbox.focus();
disclaimerCheckbox.scrollIntoView({ behavior: 'smooth', block: 'center' });
}
// 禁用提交按钮
if (submitButton) {
submitButton.disabled = true;
}
return;
}
@@ -239,6 +389,28 @@
// 打开模态框
window.openSaveModal = function(repoLink, dockerfilePath) {
// 重置状态
const securityDisclaimerCheckbox = document.getElementById('securityDisclaimerCheckbox');
const saveMethodCheckbox = document.getElementById('SaveMethod');
const disclaimerOverlay = document.getElementById('securityDisclaimerOverlay');
const disclaimerAcceptedCheckbox = document.getElementById('SecurityDisclaimerAccepted');
if (securityDisclaimerCheckbox) {
securityDisclaimerCheckbox.style.display = 'none';
}
if (saveMethodCheckbox) {
saveMethodCheckbox.checked = true;
}
if (disclaimerOverlay) {
disclaimerOverlay.style.display = 'none';
}
if (disclaimerAcceptedCheckbox) {
disclaimerAcceptedCheckbox.checked = false;
}
// 检查提交按钮状态
setTimeout(function() {
checkSubmitButtonState();
}, 100);
// 如果提供了repoLink尝试检查Dockerfile是否存在管理页面场景
if (repoLink) {
const dockerfileLink = document.getElementById('dockerfileLink');
@@ -289,10 +461,24 @@
$('#updatemodal').modal('hide');
const form = document.getElementById('updateForm');
const loadingOverlay = document.getElementById('saveLoadingOverlay');
const securityDisclaimerCheckbox = document.getElementById('securityDisclaimerCheckbox');
const saveMethodCheckbox = document.getElementById('SaveMethod');
const disclaimerOverlay = document.getElementById('securityDisclaimerOverlay');
form.reset();
if (loadingOverlay) {
loadingOverlay.style.display = 'none';
}
// 重置状态
if (securityDisclaimerCheckbox) {
securityDisclaimerCheckbox.style.display = 'none';
}
if (saveMethodCheckbox) {
saveMethodCheckbox.checked = true;
}
if (disclaimerOverlay) {
disclaimerOverlay.style.display = 'none';
}
};
// 关闭按钮事件
@@ -303,6 +489,66 @@
closeSaveModal();
});
}
// 免责声明遮罩层按钮事件
const disclaimerCancelButton = document.getElementById('disclaimerCancelButton');
if (disclaimerCancelButton) {
disclaimerCancelButton.addEventListener('click', function(e) {
e.preventDefault();
e.stopPropagation();
// 隐藏免责声明遮罩层
const disclaimerOverlay = document.getElementById('securityDisclaimerOverlay');
if (disclaimerOverlay) {
disclaimerOverlay.style.display = 'none';
}
// 恢复勾选Build From Dockerfile
const saveMethodCheckbox = document.getElementById('SaveMethod');
if (saveMethodCheckbox) {
saveMethodCheckbox.checked = true;
}
// 隐藏确认框
const securityDisclaimerCheckbox = document.getElementById('securityDisclaimerCheckbox');
if (securityDisclaimerCheckbox) {
securityDisclaimerCheckbox.style.display = 'none';
}
});
}
const disclaimerAcceptButton = document.getElementById('disclaimerAcceptButton');
if (disclaimerAcceptButton) {
disclaimerAcceptButton.addEventListener('click', function(e) {
e.preventDefault();
e.stopPropagation();
// 隐藏免责声明遮罩层
const disclaimerOverlay = document.getElementById('securityDisclaimerOverlay');
if (disclaimerOverlay) {
disclaimerOverlay.style.display = 'none';
}
// 显示确认框并自动勾选
const securityDisclaimerCheckbox = document.getElementById('securityDisclaimerCheckbox');
const disclaimerAcceptedCheckbox = document.getElementById('SecurityDisclaimerAccepted');
if (securityDisclaimerCheckbox) {
securityDisclaimerCheckbox.style.display = 'block';
}
if (disclaimerAcceptedCheckbox) {
disclaimerAcceptedCheckbox.checked = true;
}
// 检查提交按钮状态
checkSubmitButtonState();
});
}
// 查看免责声明链接事件
const showDisclaimerLink = document.getElementById('showDisclaimerLink');
if (showDisclaimerLink) {
showDisclaimerLink.addEventListener('click', function(e) {
e.preventDefault();
showDisclaimerOverlay();
});
}
// 初始化时检查提交按钮状态
checkSubmitButtonState();
})();
</script>
@@ -337,4 +583,65 @@
0%{-webkit-transform:rotate(0deg)}
100%{-webkit-transform:rotate(360deg)}
}
.security-disclaimer-content {
max-height: 300px;
overflow-y: auto;
padding: 5px 0;
}
.security-disclaimer-content ul {
margin: 10px 0;
padding-left: 20px;
}
.security-disclaimer-content li {
margin: 5px 0;
}
/* 免责声明遮罩层样式 */
.security-disclaimer-overlay {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: rgba(0, 0, 0, 0.5);
display: flex;
align-items: center;
justify-content: center;
z-index: 1002;
}
.security-disclaimer-modal {
background: white;
border-radius: 4px;
width: 50%;
max-width: 700px;
max-height: 80vh;
display: flex;
flex-direction: column;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.3);
}
.security-disclaimer-header {
padding: 20px;
border-bottom: 1px solid #d4d4d5;
font-size: 18px;
font-weight: bold;
display: flex;
align-items: center;
}
.security-disclaimer-body {
padding: 20px;
overflow-y: auto;
flex: 1;
}
.security-disclaimer-actions {
padding: 15px 20px;
border-top: 1px solid #d4d4d5;
text-align: right;
}
.security-disclaimer-actions .button {
margin-left: 10px;
}
</style>