Files
devstar/templates/repo/devcontainer/details.tmpl
xinitx 28adf2541d
All checks were successful
DevStar Studio Auto Test Pipeline / unit-frontend-test (push) Successful in 34m23s
DevStar Studio Auto Test Pipeline / unit-backend-test (push) Successful in 19m32s
DevStar Studio CI/CD Pipeline / build-and-push-x86-64-docker-image (push) Successful in 18m42s
!110 完善了devcontainer相关功能细节
完善了devcontainer相关功能细节 见https://gitee.com/devstar/devstar/issues/ID2H25
2025-10-31 07:44:19 +00:00

571 lines
19 KiB
Handlebars
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
{{template "base/head" .}}
<div role="main" aria-label="{{.Title}}" class="page-content repository wiki pages">
{{template "repo/header" .}}
<div class="ui container">
{{template "base/alert" .}}
<!-- 开始Dev Container 正文 -->
<div class="issue-content">
<!-- 开始Dev Container 正文内容 - 左侧主展示区 -->
<div class="issue-content-left">
{{if not .HasDevContainerConfiguration}}
<div class="empty-placeholder">
{{svg "octicon-container" 48}}
<h2>{{ctx.Locale.Tr "repo.dev_container_empty"}}</h2>
{{if .isAdmin}}
<form method="get" action="{{.CreateDevcontainerSettingUrl}}" class="ui edit form">
<button class="ui primary button" type="submit">Create</button>
</form>
{{end}}
</div>
{{else}}
<div class="ui container">
<form class="ui edit form">
<div class="repo-editor-header">
<div class="ui breadcrumb field">
<a class="section" href="{{$.BranchLink}}">{{.Repository.Name}}</a>
{{range $i, $v := .TreeNames}}
<div class="breadcrumb-divider">/</div>
<span class="section"><a href="{{$.BranchLink}}/{{index $.TreePaths $i | PathEscapeSegments}}">{{$v}}</a></span>
{{end}}
</div>
<a href="{{.EditDevcontainerConfigurationUrl}}"><div class="ui primary button" style="margin-left: 10px;width: 4em;height: 1em;">Edit</div></a>
</div>
</form>
{{if and .ValidateDevContainerConfiguration .HasDevContainer}}
<iframe id="webTerminalContainer" src="{{.WebSSHUrl}}" width="100%" style="height: 100vh; display: none;" frameborder="0">您的浏览器不支持iframe</iframe>
{{end}}
</div>
{{end}}
</div>
<!-- 结束Dev Container 正文内容 - 左侧主展示区 -->
<!-- 开始Dev Container 正文内容 - 右侧展示区 -->
<div class="issue-content-right ui segment">
<strong>{{ctx.Locale.Tr "repo.dev_container_control"}}</strong>
<div class="ui relaxed list">
{{if and .ValidateDevContainerConfiguration .HasDevContainer}}
<div style=" display: none;" id="deleteContainer" class="item"><a class="delete-button flex-text-inline" data-modal="#delete-repo-devcontainer-of-user-modal" href="#" data-url="{{.Repository.Link}}/devcontainer/delete">{{svg "octicon-trash" 14}}{{ctx.Locale.Tr "repo.dev_container_control.delete"}}</a></div>
{{if .isAdmin}}
<div style=" display: none;" id="updateContainer" class="item"><a class="delete-button flex-text-inline" style="color:black; " data-modal-id="updatemodal" href="#">{{svg "octicon-database"}}{{ctx.Locale.Tr "repo.dev_container_control.update"}}</a></div>
{{end}}
<div style=" display: none;" id="restartContainer" class="item"><button class="flex-text-inline" style="color:black; " >{{svg "octicon-terminal" 14 "tw-mr-2"}}{{ctx.Locale.Tr "repo.dev_container_control.start"}}</button></div>
<div style=" display: none;" id="stopContainer" class="item"><button class="flex-text-inline" style="color:black; " >{{svg "octicon-terminal" 14 "tw-mr-2"}}{{ctx.Locale.Tr "repo.dev_container_control.stop"}} </button></div>
<div style=" display: none;" id="webTerminal" class="item"><a class="flex-text-inline" style="color:black; " href="{{.WebSSHUrl}}" target="_blank">{{svg "octicon-code" 14}}open with WebTerminal</a></div>
<div style=" display: none;" id="vsTerminal" class="item"><a class="flex-text-inline" style="color:black; " onclick="window.location.href = '{{.VSCodeUrl}}'">{{svg "octicon-code" 14}}open with VSCode</a ></div>
<div style=" display: none;" id="cursorTerminal" class="item"><a class="flex-text-inline" style="color:black; " onclick="window.location.href = '{{.CursorUrl}}'">{{svg "octicon-code" 14}}open with Cursor</a ></div>
<div style=" display: none;" id="windsurfTerminal" class="item"><a class="flex-text-inline" style="color:black;" onclick="window.location.href = '{{.WindsurfUrl}}'">{{svg "octicon-code" 14}}open with Windsurf</a ></div>
{{end}}
{{if .ValidateDevContainerConfiguration}}
<div style=" display: none;" id="createContainer" class="item">
<div>
<form method="get" action="{{.Repository.Link}}/devcontainer/create" class="ui edit form">
<button class="flex-text-inline" type="submit">{{svg "octicon-terminal" 14 "tw-mr-2"}} {{ctx.Locale.Tr "repo.dev_container_control.create"}}</button>
</form>
</div>
</div>
<div id="loading" class="loading"></div>
{{end}}
{{if not .ValidateDevContainerConfiguration}}
<div class="item">{{svg "octicon-alert" 16 "tw-mr-2"}} {{ctx.Locale.Tr "repo.dev_container_invalid_config_prompt"}} </div>
{{end}}
</div>
</div>
<!-- 结束Dev Container 正文内容 - 右侧展示区 -->
</div>
<!-- 结束Dev Container 正文内容 -->
</div>
</div>
<!-- 自定义警告框 -->
<div id="customAlert" class="custom-alert">
<div class="alert-content">
<div class="alert-header">
<strong>提示信息</strong>
<button class="alert-close" onclick="closeCustomAlert()">&times;</button>
</div>
<div id="alertText" class="alert-body"></div>
</div>
</div>
<!-- 确认删除 Dev Container 模态对话框 -->
<div class="ui g-modal-confirm delete modal" id="delete-repo-devcontainer-of-user-modal">
<div class="header">
{{svg "octicon-trash"}}
{{ctx.Locale.Tr "repo.dev_container_control.delete"}}
</div>
<div class="content">
<p>{{ctx.Locale.Tr "repo.dev_container_control.deletion_desc"}}</p>
</div>
{{template "base/modal_actions_confirm" .}}
</div>
<!-- 保存 Dev Container 模态对话框 -->
<div class="ui g-modal-confirm delete modal" style="width: 35%" id="updatemodal">
<div class="header">
{{ctx.Locale.Tr "repo.dev_container_control.update"}}
</div>
<div class="content">
<form class="ui form tw-max-w-2xl tw-m-auto" id="updateForm" onsubmit="submitForm(event)">
<div class="required field ">
<label for="RepositoryAddress">Registry:</label>
<input style="border: 1px solid black;" type="text" id="RepositoryAddress" name="RepositoryAddress" value="{{.RepositoryAddress}}">
</div>
<div class="required field ">
<label for="RepositoryUsername">Registry Username:</label>
<input style="border: 1px solid black;" type="text" id="RepositoryUsername" name="RepositoryUsername" value="{{.RepositoryUsername}}">
</div>
<div class="required field ">
<label for="RepositoryPassword">Registry Password:</label>
<div style="position: relative; display: inline-block; width: 100%;">
<input style="border: 1px solid black; width: 100%; padding-right: 80px;"
type="password"
id="RepositoryPassword"
name="RepositoryPassword"
required
autocomplete="current-password">
<button type="button"
style="position: absolute; right: 5px; top: 50%; transform: translateY(-50%);
background: none; border: none; cursor: pointer; color: #666;
font-size: 12px; padding: 5px 8px;"
onclick="togglePasswordVisibility('RepositoryPassword', this)">
显示密码
</button>
</div>
</div>
<div class="required field ">
<label for="ImageName">Image(name:tag):</label>
<input style="border: 1px solid black;" type="text" id="ImageName" name="ImageName" value="{{.ImageName}}">
</div>
<div class="inline field">
<div class="ui checkbox">
{{if not .HasDevContainerDockerfile}}
<input type="checkbox" id="SaveMethod" name="SaveMethod" disabled>
<label for="SaveMethod">There is no Dockerfile</label>
{{else}}
<input type="checkbox" id="SaveMethod" name="SaveMethod" value="on">
<label for="SaveMethod">Build From Dockerfile: {{.DockerfilePath}}</label>
{{end}}
</div>
</div>
<div class="actions">
<button class="ui primary button" type="submit" id="updateSubmitButton" >Submit</button>
<button class="ui cancel button" id="updateCloseButton">Close</button>
</div>
</form>
</div>
</div>
<script>
document.getElementById('updateSubmitButton').addEventListener('click', function() {
const form = document.getElementById('updateForm');
const formData = new FormData(form);
var RepositoryAddress = formData.get('RepositoryAddress');
var RepositoryUsername = formData.get('RepositoryUsername');
var RepositoryPassword = formData.get('RepositoryPassword');
var SaveMethod = formData.get('SaveMethod');
var ImageName = formData.get('ImageName');
if(ImageName != "" && SaveMethod != "" && RepositoryPassword != "" && RepositoryUsername != "" && RepositoryAddress != ""){
document.getElementById('updatemodal').classList.add('is-loading')
}
});
var status = '-1'
var intervalID
const createContainer = document.getElementById('createContainer');
const deleteContainer = document.getElementById('deleteContainer');
const updateContainer = document.getElementById('updateContainer');
const restartContainer = document.getElementById('restartContainer');
const stopContainer = document.getElementById('stopContainer');
const webTerminal = document.getElementById('webTerminal');
const vsTerminal = document.getElementById('vsTerminal');
const cursorTerminal = document.getElementById('cursorTerminal');
const windsurfTerminal = document.getElementById('windsurfTerminal');
const webTerminalContainer = document.getElementById('webTerminalContainer');
const loadingElement = document.getElementById('loading');
function concealElement(){
if (createContainer){
createContainer.style.display = 'none';
}
if (deleteContainer){
deleteContainer.style.display = 'none';
}
if (updateContainer) {
updateContainer.style.display = 'none';
}
if (restartContainer) {
restartContainer.style.display = 'none';
}
if (stopContainer) {
stopContainer.style.display = 'none';
}
if (webTerminal) {
webTerminal.style.display = 'none';
}
if (vsTerminal) {
vsTerminal.style.display = 'none';
}
if (cursorTerminal) {
cursorTerminal.style.display = 'none';
}
if (windsurfTerminal) {
windsurfTerminal.style.display = 'none';
}
if (webTerminalContainer) {
webTerminalContainer.style.display = 'none';
}
}
function displayElement(){
if (deleteContainer){
deleteContainer.style.display = 'block';
}
if (updateContainer) {
updateContainer.style.display = 'block';
}
if (restartContainer) {
restartContainer.style.display = 'block';
}
if (stopContainer) {
stopContainer.style.display = 'block';
}
if (webTerminal) {
webTerminal.style.display = 'block';
}
if (vsTerminal) {
vsTerminal.style.display = 'block';
}
if (cursorTerminal) {
cursorTerminal.style.display = 'block';
}
if (windsurfTerminal) {
windsurfTerminal.style.display = 'block';
}
if (webTerminalContainer) {
webTerminalContainer.style.display = 'block';
}
}
function getStatus() {
fetch(
'{{.Repository.Link}}'+'/devcontainer/status'
)
.then(response => response.json())
.then(data => {
if(status !== '9' && status !== '-1' && data.status == '9'){
window.location.reload();
}
else if(status !== '-1' && data.status == '-1'){
window.location.reload();
}
else if(status !== '4' && status !== '-1' && data.status == '4'){
//window.location.reload();
}
else if (data.status == '-1' || data.status == '') {
if (loadingElement) {
loadingElement.style.display = 'none';
}
if (createContainer){
createContainer.style.display = 'block';
}
clearInterval(intervalID);
} else if (data.status == '0' || data.status == '1' || data.status == '2') {
concealElement();
if (webTerminalContainer) {
webTerminalContainer.style.display = 'block';
}
if (deleteContainer){
deleteContainer.style.display = 'block';
}
if (loadingElement) {
loadingElement.style.display = 'block';
}
}else if (data.status == '3') {
concealElement();
if (deleteContainer){
deleteContainer.style.display = 'block';
}
if (webTerminalContainer) {
webTerminalContainer.style.display = 'block';
}
if (loadingElement) {
loadingElement.style.display = 'block';
}
}else if (data.status == '4') {
displayElement();
if (loadingElement) {
loadingElement.style.display = 'none';
}
if (restartContainer) {
restartContainer.style.display = 'none';
}
clearInterval(intervalID);
}else if (data.status == '5') {
concealElement();
if (loadingElement) {
loadingElement.style.display = 'block';
}
}else if (data.status == '6') {
concealElement();
if (deleteContainer){
deleteContainer.style.display = 'block';
}
if (updateContainer) {
updateContainer.style.display = 'block';
}
if (loadingElement) {
loadingElement.style.display = 'block';
}
}else if (data.status == '7') {
concealElement();
if (deleteContainer){
deleteContainer.style.display = 'block';
}
if (updateContainer) {
updateContainer.style.display = 'block';
}
if (loadingElement) {
loadingElement.style.display = 'block';
}
}else if (data.status == '8') {
concealElement();
if (deleteContainer){
deleteContainer.style.display = 'block';
}
if (updateContainer) {
updateContainer.style.display = 'block';
}
if (restartContainer) {
restartContainer.style.display = 'block';
}
if (loadingElement) {
loadingElement.style.display = 'none';
}
clearInterval(intervalID);
}else if (data.status == '9') {
concealElement();
if (loadingElement) {
loadingElement.style.display = 'block';
}
}
status = data.status
})
.catch(error => {
console.error('Error:', error);
});
}
intervalID = setInterval(getStatus, 5000);
if (restartContainer) {
restartContainer.addEventListener('click', function(event) {
// 处理点击逻辑
concealElement();
if (loadingElement) {
loadingElement.style.display = 'block';
}
fetch('{{.Repository.Link}}' + '/devcontainer/restart')
.then(response => {intervalID = setInterval(getStatus, 5000);})
});
}
if (stopContainer) {
stopContainer.addEventListener('click', function(event) {
concealElement();
if (loadingElement) {
loadingElement.style.display = 'block';
}
// 处理点击逻辑
fetch('{{.Repository.Link}}' + '/devcontainer/stop')
.then(response => {intervalID = setInterval(getStatus, 5000);})
});
}
if (deleteContainer) {
deleteContainer.addEventListener('click', function(event) {
setInterval(getStatus, 3000);
});
}
function togglePasswordVisibility(passwordFieldId, button) {
const passwordInput = document.getElementById(passwordFieldId);
if (passwordInput.type === 'password') {
passwordInput.type = 'text';
button.textContent = '隐藏密码';
button.style.color = '#2185d0'; // 主色调,表示激活状态
} else {
passwordInput.type = 'password';
button.textContent = '显示密码';
button.style.color = '#666'; // 恢复默认颜色
}
}
function showCustomAlert(message, title = "提示信息") {
const alertBox = document.getElementById('customAlert');
const alertText = document.getElementById('alertText');
const alertHeader = alertBox.querySelector('.alert-header strong');
alertHeader.textContent = title;
alertText.textContent = message;
alertBox.style.display = 'block';
}
function closeCustomAlert() {
document.getElementById('customAlert').style.display = 'none';
}
// 点击背景关闭
document.getElementById('customAlert').addEventListener('click', function(e) {
if (e.target === this) {
closeCustomAlert();
}
});
function submitForm(event) {
event.preventDefault(); // 阻止默认的表单提交行为
const {csrfToken} = window.config;
const {appSubUrl} = window.config;
const formModal = document.getElementById('updatemodal');
const form = document.getElementById('updateForm');
const submitButton = document.getElementById('updateSubmitButton');
const closeButton = document.getElementById('updateCloseButton');
submitButton.disabled = true;
const formData = new FormData(form);
fetch('{{.Repository.Link}}'+'/devcontainer/update', {
method: 'POST',
headers: {
'x-csrf-token': csrfToken, // 如果需要认证
'content-type' : 'application/json',
},
body: JSON.stringify({
RepositoryAddress: formData.get('RepositoryAddress'),
RepositoryUsername: formData.get('RepositoryUsername'),
RepositoryPassword: formData.get('RepositoryPassword'),
SaveMethod: formData.get('SaveMethod'),
ImageName: formData.get('ImageName'),
})
})
.then(response => response.json())
.then(data => {
submitButton.disabled = false;
formModal.classList.remove('is-loading')
showCustomAlert(data.message);
if(data.redirect){
closeCustomAlert()
}
intervalID = setInterval(getStatus, 3000);
})
.catch((error) => {
submitButton.disabled = false;
alert('提交失败,请重试。');
});
}
</script>
<style>
.loading{
width:60px;
height:60px;
border-radius:150px;
border:8px solid #fff;
border-top-color:rgba(0,0,0,0.3);
box-sizing:border-box;
margin-left:calc(50% - 30px);
animation:loading 1.2s linear infinite;
-webkit-animation:loading 1.2s linear infinite;
}
@keyframes loading{
0%{transform:rotate(0deg)}
100%{transform:rotate(360deg)}
}
@-webkit-keyframes loading{
0%{-webkit-transform:rotate(0deg)}
100%{-webkit-transform:rotate(360deg)}
}
.custom-alert {
display: none;
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(0,0,0,0.5);
z-index: 10000;
}
.alert-content {
color: black;
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
background: white;
padding: 0; /* 移除内边距,在内部元素中设置 */
border-radius: 8px;
width: 80%;
max-width: 600px;
max-height: 80%;
display: flex;
flex-direction: column;
box-shadow: 0 4px 6px rgba(0,0,0,0.1);
}
.alert-header {
padding: 15px 20px;
border-bottom: 1px solid #eee;
background: #f8f9fa;
border-radius: 8px 8px 0 0;
position: sticky;
top: 0;
z-index: 10;
display: flex;
justify-content: space-between;
align-items: center;
}
.alert-close {
cursor: pointer;
font-size: 24px;
font-weight: bold;
color: #666;
background: none;
border: none;
padding: 0;
width: 30px;
height: 30px;
display: flex;
align-items: center;
justify-content: center;
border-radius: 50%;
}
.alert-close:hover {
background: #e9ecef;
color: #000;
}
.alert-body {
padding: 20px;
overflow-y: auto;
max-height: calc(80vh - 100px); /* 减去头部高度 */
white-space: pre-wrap;
word-wrap: break-word;
}
</style>
{{template "base/footer" .}}