Files
devstar/templates/repo/devcontainer/details.tmpl
xinitx 9071a754f4 !108 给devcontainer增加变量和脚本功能
给devcontainer增加变量和脚本功能

- 能从devstar.cn上获取预定义的DEVSTAR_开头的变量或脚本
- 添加到脚本管理中的变量名,在devcontainer启动时会自动执行,然后才执行devcontainer.json中用户自定义脚本,其中可以调用设置的变量或脚本
- 变量或脚本在用户设置、项目设置和后台管理中都可以添加,如有重名优先级为:用户设置 > 项目设置 > 后台管理
2025-10-18 08:53:50 +00:00

428 lines
15 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>
<iframe id="webTerminalContainer" src="{{.WebSSHUrl}}" width="100%" style="height: 100vh; display: none;" frameborder="0">您的浏览器不支持iframe</iframe>
</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 .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"}} Create Dev Container</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>
<!-- 确认删除 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="inline field">
<div class="ui checkbox">
{{if not .HasDevContainerDockerfile}}
<input type="checkbox" id="SaveMethod" name="SaveMethod" disabled>
{{else}}
<input type="checkbox" id="SaveMethod" name="SaveMethod" value="on">
{{end}}
<label for="SaveMethod">Build From Dockerfile</label>
</div>
</div>
<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>
<input style="border: 1px solid black;" type="text" id="RepositoryPassword" name="RepositoryPassword" required>
</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="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>
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();
}
if(status !== '-1' && data.status == '-1'){
window.location.reload();
}
if(status !== '4' && status !== '-1' && data.status == '4'){
window.location.reload();
}
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, 3000);
if (restartContainer) {
restartContainer.addEventListener('click', function(event) {
// 处理点击逻辑
concealElement();
if (loadingElement) {
loadingElement.style.display = 'block';
}
fetch('{{.Repository.Link}}' + '/devcontainer/restart')
.then(response => {intervalID = setInterval(getStatus, 3000);})
});
}
if (stopContainer) {
stopContainer.addEventListener('click', function(event) {
concealElement();
if (loadingElement) {
loadingElement.style.display = 'block';
}
// 处理点击逻辑
fetch('{{.Repository.Link}}' + '/devcontainer/stop')
.then(response => {intervalID = setInterval(getStatus, 3000);})
});
}
if (deleteContainer) {
deleteContainer.addEventListener('click', function(event) {
setInterval(getStatus, 3000);
});
}
function submitForm(event) {
event.preventDefault(); // 阻止默认的表单提交行为
const {csrfToken} = window.config;
const {appSubUrl} = window.config;
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;
alert(data.message);
if(data.redirect){
closeButton.click()
}
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)}
}
</style>
{{template "base/footer" .}}