添加敏感数据清理和免责声明
This commit is contained in:
@@ -3964,6 +3964,28 @@ manage = Container Management
|
|||||||
registry_username = Registry Username
|
registry_username = Registry Username
|
||||||
registry_password = Registry Password
|
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.
|
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 = ShellScripts
|
||||||
scripts.management = ShellScripts Management
|
scripts.management = ShellScripts Management
|
||||||
scripts.creation = Add ShellScript
|
scripts.creation = Add ShellScript
|
||||||
|
|||||||
@@ -3953,6 +3953,28 @@ manage=容器管理
|
|||||||
registry_username = 镜像仓库用户名
|
registry_username = 镜像仓库用户名
|
||||||
registry_password = 镜像仓库密码
|
registry_password = 镜像仓库密码
|
||||||
save_tip = 💡 提示:建议优先使用 <strong>Build From Dockerfile</strong> 方式保存镜像。从容器直接保存镜像不透明且存储臃肿,而通过Dockerfile构建可以确保镜像的可重现性和最小化。
|
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=脚本管理
|
||||||
scripts.management=脚本管理
|
scripts.management=脚本管理
|
||||||
scripts.creation=添加脚本
|
scripts.creation=添加脚本
|
||||||
|
|||||||
@@ -392,6 +392,95 @@ func StopDevContainerByDocker(ctx context.Context, devContainerName string) erro
|
|||||||
}
|
}
|
||||||
return nil
|
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 {
|
func UpdateDevContainerByDocker(ctx context.Context, devContainerInfo *devcontainer_models.Devcontainer, updateInfo *UpdateInfo, repo *gitea_context.Repository, doer *user.User) error {
|
||||||
// 创建docker client
|
// 创建docker client
|
||||||
cli, err := docker_module.CreateDockerClient(ctx)
|
cli, err := docker_module.CreateDockerClient(ctx)
|
||||||
@@ -497,6 +586,14 @@ func UpdateDevContainerByDocker(ctx context.Context, devContainerInfo *devcontai
|
|||||||
}
|
}
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
|
// 非Dockerfile模式:直接提交容器
|
||||||
|
// 在提交前清理敏感数据
|
||||||
|
log.Info("开始清理容器 %s 的敏感数据...", devContainerInfo.Name)
|
||||||
|
if err := CleanSensitiveDataBeforeCommit(ctx, cli, devContainerInfo.Name); err != nil {
|
||||||
|
log.Warn("清理敏感数据时出现警告: %v,继续执行提交操作", err)
|
||||||
|
// 清理失败不阻止提交,但已记录警告
|
||||||
|
}
|
||||||
|
|
||||||
// 获取容器 ID
|
// 获取容器 ID
|
||||||
containerID, err := docker_module.GetContainerID(cli, devContainerInfo.Name)
|
containerID, err := docker_module.GetContainerID(cli, devContainerInfo.Name)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
<div class="ui g-modal-confirm delete modal" style="width: 35%" id="updatemodal">
|
<div class="ui g-modal-confirm delete modal" style="width: 35%" id="updatemodal">
|
||||||
<div class="header">
|
<div class="header">
|
||||||
保存为容器镜像
|
{{ctx.Locale.Tr "devcontainer.save_image_modal_title"}}
|
||||||
</div>
|
</div>
|
||||||
<div class="content" style="position: relative;">
|
<div class="content" style="position: relative;">
|
||||||
<form class="ui form tw-max-w-2xl tw-m-auto" id="updateForm">
|
<form class="ui form tw-max-w-2xl tw-m-auto" id="updateForm">
|
||||||
@@ -35,21 +35,35 @@
|
|||||||
<div class="inline field">
|
<div class="inline field">
|
||||||
<div class="ui checkbox">
|
<div class="ui checkbox">
|
||||||
{{if .HasDevContainerDockerfile}}
|
{{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>
|
<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}}
|
{{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>
|
<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}}
|
{{end}}
|
||||||
</div>
|
</div>
|
||||||
<div style="color: #2185d0; font-size: 13px; margin-top: 12px; padding: 10px; background-color: #f0f8ff; border-left: 3px solid #2185d0; border-radius: 3px;">
|
<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"}}
|
{{ctx.Locale.TrHTML "devcontainer.save_tip"}}
|
||||||
</div>
|
</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>
|
||||||
|
|
||||||
<div class="actions">
|
<div class="actions">
|
||||||
<button class="ui primary button" type="button" id="updateSubmitButton">提交</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">取消</button>
|
<button class="ui cancel button" type="button" id="updateCloseButton">{{ctx.Locale.Tr "devcontainer.save_image_cancel_button"}}</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</form>
|
</form>
|
||||||
@@ -61,6 +75,56 @@
|
|||||||
|
|
||||||
</div>
|
</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>
|
<script>
|
||||||
// 保存模态框相关功能 - 可复用组件
|
// 保存模态框相关功能 - 可复用组件
|
||||||
(function() {
|
(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
|
// targetUrl: 保存请求的URL
|
||||||
// statusUrl: 状态检查URL(用于轮询)
|
// statusUrl: 状态检查URL(用于轮询)
|
||||||
@@ -95,10 +226,29 @@
|
|||||||
const RepositoryPassword = formData.get('RepositoryPassword');
|
const RepositoryPassword = formData.get('RepositoryPassword');
|
||||||
const SaveMethod = formData.get('SaveMethod');
|
const SaveMethod = formData.get('SaveMethod');
|
||||||
const ImageName = formData.get('ImageName');
|
const ImageName = formData.get('ImageName');
|
||||||
|
const SecurityDisclaimerAccepted = formData.get('SecurityDisclaimerAccepted');
|
||||||
|
|
||||||
// 验证必填字段
|
// 验证必填字段
|
||||||
if (!ImageName || !RepositoryUsername || !RepositoryPassword) {
|
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;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -239,6 +389,28 @@
|
|||||||
|
|
||||||
// 打开模态框
|
// 打开模态框
|
||||||
window.openSaveModal = function(repoLink, dockerfilePath) {
|
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是否存在(管理页面场景)
|
// 如果提供了repoLink,尝试检查Dockerfile是否存在(管理页面场景)
|
||||||
if (repoLink) {
|
if (repoLink) {
|
||||||
const dockerfileLink = document.getElementById('dockerfileLink');
|
const dockerfileLink = document.getElementById('dockerfileLink');
|
||||||
@@ -289,10 +461,24 @@
|
|||||||
$('#updatemodal').modal('hide');
|
$('#updatemodal').modal('hide');
|
||||||
const form = document.getElementById('updateForm');
|
const form = document.getElementById('updateForm');
|
||||||
const loadingOverlay = document.getElementById('saveLoadingOverlay');
|
const loadingOverlay = document.getElementById('saveLoadingOverlay');
|
||||||
|
const securityDisclaimerCheckbox = document.getElementById('securityDisclaimerCheckbox');
|
||||||
|
const saveMethodCheckbox = document.getElementById('SaveMethod');
|
||||||
|
const disclaimerOverlay = document.getElementById('securityDisclaimerOverlay');
|
||||||
|
|
||||||
form.reset();
|
form.reset();
|
||||||
if (loadingOverlay) {
|
if (loadingOverlay) {
|
||||||
loadingOverlay.style.display = 'none';
|
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();
|
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>
|
</script>
|
||||||
|
|
||||||
@@ -337,4 +583,65 @@
|
|||||||
0%{-webkit-transform:rotate(0deg)}
|
0%{-webkit-transform:rotate(0deg)}
|
||||||
100%{-webkit-transform:rotate(360deg)}
|
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>
|
</style>
|
||||||
|
|||||||
Reference in New Issue
Block a user