Files
devstar_plugin/test/home.html
2024-10-12 11:38:28 +08:00

737 lines
27 KiB
HTML
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<?php
header("Access-Control-Allow-Origin: *");
header("Access-Control-Allow-Headers: X-API-KEY, Origin, X-Requested-With, Content-Type, Accept, Access-Control-Request-Method");
header("Access-Control-Allow-Methods: GET, POST, OPTIONS, PUT, DELETE");
header("Allow: GET, POST, OPTIONS, PUT, DELETE");
?>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Embedded page</title>
<style>
/* 弹窗基本样式 */
.modal {
display: none; /* 默认隐藏 */
position: fixed; /* 固定定位 */
left: 0;
top: 0;
width: 100%; /* 全屏宽 */
height: 100%; /* 全屏高 */
background-color: rgba(0,0,0,0.5); /* 半透明黑色背景 */
z-index: 1; /* 确保在顶部 */
}
/* 弹窗内容框样式 */
.modal-content {
background-color: #fefefe;
margin: 15% auto; /* 15% 从顶部开始,自动水平居中 */
padding: 20px;
border: 1px solid #888;
width: 30%; /* 弹窗宽度 */
}
/* 关闭按钮样式 */
.close {
color: #aaa;
float: right;
font-size: 28px;
font-weight: bold;
}
.close:hover,
.close:focus {
color: black;
text-decoration: none;
cursor: pointer;
}
.alert {
position: fixed;
top: 20px; /* 距离顶部20px */
left: 50%; /* 水平居中 */
transform: translateX(-50%); /* 水平居中调整 */
background-color: green;
color: white;
text-align: center;
padding: 10px;
border-radius: 5px;
display: none;
z-index: 2;
width: auto; /* 自动宽度 */
max-width: 60%; /* 最大宽度60% */
}
/* table样式 */
table {
width: 100%;
border-collapse: collapse;
}
th, td {
border: 1px solid black;
padding: 8px;
text-align: left;
}
.required::before {
content: "*";
color: red;
}
form {
display: flex;
flex-direction: column;
align-items: flex-start;
}
label, input, textarea {
margin-bottom: 10px;
}
input, textarea {
padding: 8px;
border: 1px solid #ccc;
border-radius: 4px;
}
.button-container {
display: flex;
/* justify-content: space-between; */
}
ton {
width: 100px;
height: 30px;
background-color: #4CAF50;
color: white;
border: none;
border-radius: 5px;
cursor: pointer;
}
button:hover {
background-color: #45a049;
}
</style>
</head>
<body>
<h1>DevStar Home</h1>
<!-- login -->
<div id="loginModal" class="modal">
<div class="modal-content">
<span class="close" onclick="closeLoginModal()">&times;</span>
<h2>Login</h2>
<form id="loginForm">
<label for="username">Username:</label>
<input type="text" id="username" name="username" required><br><br>
<label for="password">Password:</label>
<input type="password" id="password" name="password" required><br><br>
<button type="button" onclick="login()">Login</button>
</form>
</div>
</div>
<button onclick="openLoginModal()">Login</button>
<button onclick="logout()">Logout</button>
<!-- create new project -->
<!-- 触发弹窗的按钮 -->
<button onclick="openModal()">Create New Project</button>
<!-- 弹窗本体 -->
<div id="myModal" class="modal">
<div class="modal-content">
<span class="close" onclick="closeModal()">&times;</span>
<h2>Create New Project</h2>
<form>
<!-- project settings -->
<label class="required" for="projectName">Name</label>
<input type="text" id="projectName" name="projectName" ><br><br>
<label for="projectDesc">Description</label>
<textarea id="projectDesc" name="projectDesc" placeholder="(Optional)"></textarea><br><br>
<label for="repoURL">Repo URL</label>
<input type="text" id="repoURL" name="repoURL" placeholder="(Optional) Project repository you want to clone"><br><br>
<hr>
<!-- repo settings-->
<label for="template">
As template?
<input type="checkbox" id="template">
</label>
<button type="button" onclick="submitRepo()">Create</button>
</form>
</div>
</div>
<div id="alertBox" class="alert"></div>
<!-- ====================== Created Repository ==========================-->
<h2>Created Repositories</h2>
<button onclick="loadRepositories()">Load Repositories</button>
<table id="reposTable">
<thead>
<tr>
<th>repoName</th>
<th>repoURL</th>
<th>Operation</th>
</tr>
</thead>
<tbody>
<!-- 动态加载 -->
</tbody>
</table>
<!-- ====================== open created project ==========================-->
<!-- <h2>Created Project</h2>
<button onclick="loadProjects()">Load Projects</button>
<table id="projectsTable">
<thead>
<tr>
<th>devContainerName</th>
<th>devContainerWorkDir</th>
<th>Operation</th>
</tr>
</thead>
<tbody>
</tbody>
</table> -->
<!-- ======================================= Script ==================================-->`
<script>
// ===================================== Initialization ===========================
// Global variables
var USERTOKEN = null
var REPOLIST = []
var PROJECTLIST = []
window.onload = async function() {
await getUserTokenFromVscode()
if (USERTOKEN) {
loadPageModules()
} else {
// TODO : do nothing or remind user to login
}
}
function loadPageModules() {
loadRepositories()
// loadProjects()
}
async function getUserTokenFromVscode() {
// const userToken = 'ecd9ceda7904f1f980b90f22be87329910cc6fb1'
const data = await biCommunication2Webview('getUserToken', null)
const userToken = data.userToken
if (userToken === 'none') {
// do nothing
} else {
// verify user token
await verifyToken(userToken)
.then(result => {
USERTOKEN = userToken
})
.catch(error => {
console.error('Error in verifying token:', error)
})
}
}
function verifyToken(token) {
return new Promise((resolve, reject) => {
fetch('https://www.devstar.cn/api/devcontainer/user', {
method: 'GET',
headers: {
'Content-Type': 'application/json',
'Authorization': 'token ' + token
},
})
.then(response => {
if (response.ok) {
resolve(response.status)
} else {
reject(new Error("Error code", response.status))
}
})
})
}
// ===================================== login and logout ===========================
// login model
function openLoginModal() {
// check if user has logged in, only show login modal when user has not logged in
if (USERTOKEN) {
verifyToken(USERTOKEN)
.then(result => {
console.log('User has logged in')
showAlert('已登录', 3000) // 消息显示3秒后消失
return
})
.catch(error => {
// need to login
document.getElementById('loginModal').style.display = 'block';
})
} else {
document.getElementById('loginModal').style.display = 'block';
}
}
function closeLoginModal() {
document.getElementById('loginModal').style.display = 'none';
}

function login() {
var username = document.getElementById('username').value;
var password = document.getElementById('password').value;
const url = `https://www.devstar.cn/api/v1/users/${username}/tokens`;
// Base64编码用户名和密码
const base64Credentials = btoa(username + ':' + password);
const tokenName = generateTokenName(10);
postData = {
"name": tokenName,
"scopes": ["write:user", "write:repository"]
}
fetch(url, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': 'Basic ' + base64Credentials
},
body: JSON.stringify(postData)
})
.then(response => {
if (!response.ok) {
throw new Error('Error in logging ' + response.statusText);
closeLoginModal()
}
return response.json();
})
.then(data => {
// store token in global variable and vscode global state
USERTOKEN = data.sha1;
biCommunication2Webview('setUserToken', { userToken: USERTOKEN })
.then(result => {
if (result.ok) {
console.log('User token has been set to vscode global state')
loadPageModules()
} else {
throw new Error('Error in setting user token to vscode global state')
}
})
.catch(error => {
console.error('Error in setting user token to vscode global state:', error)
})
closeLoginModal()
})
.catch(error => {
closeLoginModal()
console.error('There has been a problem when logging', error);
});
}
function generateTokenName(length=10) {
// tokenName is random string and number and _ combination (10 characters)
const characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789_';
let name = '';
const charactersLength = characters.length;
for (let i = 0; i < length; i++) {
name += characters.charAt(Math.floor(Math.random() * charactersLength));
}
return name
}
function logout() {
// remove token from global variable and vscode global state
USERTOKEN = null
biCommunication2Webview('setUserToken', { userToken: 'none'})
.then(result => {
if (result.ok) {
console.log('User token has been removed from vscode global state')
} else {
throw new Error('Error in removing user token from vscode global state')
}
})
.catch(error => {
console.error('Error in removing user token from vscode global state:', error)
})
location.reload()
}
// ===================================== Repo ===========================
function loadRepositories() {
// clear old data
const tableBody = document.getElementById('reposTable').getElementsByTagName('tbody')[0];
tableBody.innerHTML = '';
// load new data
var url = "https://www.devstar.cn/api/v1/user/repos?page=1&limit=10"
var token = USERTOKEN
fetch(url, {
method: 'GET',
headers: {
'Content-Type': 'application/json',
'Authorization': 'token ' + token
},
})
.then(response => {
if (!response.ok) {
throw new Error('Network response was not ok when loading repos' + response.statusText);
}
return response.json();
})
.then(data => {
var repos = data;
repos.forEach((repo, index) => {
var row = tableBody.insertRow(-1); // 在表格末尾插入一行
var cell1 = row.insertCell(0);
var cell2 = row.insertCell(1);
var cell3 = row.insertCell(2);
const repoFullName = repo.full_name;
const repoURL = repo.html_url;
const repoID = repo.id;
cell1.textContent = repoFullName;
cell2.textContent = repoURL;
cell3.innerHTML = `<button onclick="openProject('${repoID}')">Open Project</button>`;
});
})
.catch(error => {
console.error('There has been a problem with your fetch operation:', error);
});
}
// 打开弹窗
function openModal() {
// make sure login first
if (!USERTOKEN) {
showAlert('请先登录!', 3000)
return
}
document.getElementById('myModal').style.display = "block";
}
// 关闭弹窗
function closeModal() {
document.getElementById('myModal').style.display = "none";
}
// 点击窗外关闭弹窗
window.onclick = function(event) {
if (event.target == document.getElementById('myModal')) {
closeModal();
}
}
function submitRepo() {
// 这里添加实际的表单提交逻辑
// 模拟表单处理
var projectName = document.getElementById('projectName').value;
var projectDesc = document.getElementById('projectDesc').value;
var projectTemplate = document.getElementById('template').checked;
// check required fields
if (projectName == '') {
showAlert('请填写必要的项目信息!', 3000) // 消息显示3秒后消失
} else {
}
const url = "https://www.devstar.cn/api/v1/user/repos"
var token = USERTOKEN
const postData = {
"name": projectName,
"description": projectDesc,
"template": projectTemplate,
}
fetch(url, {
method: 'POST', // 或者 'POST', 根据API要求
headers: {
'Content-Type': 'application/json',
'Authorization': 'token ' + token
},
body: JSON.stringify(postData)
})
.then(response => {
console.log(response)
if (!response.ok) {
throw new Error('Network response was not ok when creating project' + response.statusText);
}
return response.json();
})
.then(data => {
console.log(data);
showAlert('项目创建成功!', 1500) // 消息显示1.5秒后消失
loadRepositories()
closeModal(); // 关闭创建项目弹窗
})
.catch(error => {
console.error('There has been a problem with your fetch operation:', error);
});
}
// ===================================== Projects ===========================
// function loadProjects() {
// // clear old data
// const tableBody = document.getElementById('projectsTable').getElementsByTagName('tbody')[0];
// tableBody.innerHTML = '';
// // load new data
// var table = document.getElementById('projectsTable');
// url = "https://www.devstar.cn/api/devcontainer/user"
// token = USERTOKEN
// fetch(url, {
// method: 'GET', // 或者 'POST', 根据API要求
// headers: {
// 'Content-Type': 'application/json',
// 'Authorization': 'token ' + token
// },
// })
// .then(response => {
// if (!response.ok) {
// throw new Error('Network response was not ok when loading projects' + response.statusText);
// }
// return response.json();
// })
// .then(data => {
// var projects = data.data.devContainers;
// projects.forEach((project, index) => {
// var row = tableBody.insertRow(-1);
// var cell1 = row.insertCell(0);
// var cell2 = row.insertCell(1);
// var cell3 = row.insertCell(2);
// devContainerHost = project.devContainerHost;
// devContainerPort = project.devContainerPort;
// devContainerUsername = project.devContainerUsername;
// devContainerPassword = project.devContainerPassword;
// devContainerWorkDir = project.devContainerWorkDir;
// cell1.textContent = project.devContainerName;
// cell2.textContent = project.devContainerWorkDir;
// cell3.innerHTML = `<button onclick="openProject('${devContainerHost}',
// '${devContainerPort}', '${devContainerUsername}', '${devContainerPassword}', '${devContainerWorkDir}')">Open Project</button>`; // 动态创建按钮并附加事件
// });
// })
// .catch(error => {
// console.error('There has been a problem with your fetch operation:', error);
// });
// }
async function openProject(repoId) {
// TODO: check if container exist
await hasDevContainer(repoId)
.then(async hasDevContainer => {
if (!hasDevContainer) {
showAlert("正在创建开发容器...", 1500)
await createDevContainer(repoId)
.then(res => {
console.log(`Succeed to create dev container for repo ${repoId}`)
})
.catch(error => {
showAlert("创建容器失败!", 1500)
console.log(`Fail to create dev container for repo ${repoId}: `, error)
})
}
}).catch(error => {
console.log("There has a problem when check if the repo has devContainer:", error)
})
// open devcontainer through repoId
var url = "https://www.devstar.cn/api/devcontainer"
var token = USERTOKEN
const queryParams = new URLSearchParams({
repoId: repoId,
wait: true
}).toString();
const urlWithParams = `${url}?${queryParams}`;
fetch(urlWithParams, {
method: 'GET',
headers: {
'Content-Type': 'application/json',
'Authorization': 'token ' + token
},
})
.then(response => {
if (!response.ok) {
throw new Error('Network response was not ok when querying devContainer by repoId' + response.statusText);
}
return response.json();
})
.then(data => {
const responseCode = data.code
const reponseMsg = data.msg
if (responseCode == 0) {
// container start successfully
// get devContainer ssh connection information
const devContainerHost = data.data.devContainerHost
const devContainerUsername = data.data.devContainerUsername
const devContainerPassword = data.data.devContainerPassword
const devContainerPort = data.data.devContainerPort
const devContainerWorkDir = `${data.data.devContainerWorkDir}/${devContainerUsername}`
// default: open with password
firstOpenRemoteFolder(devContainerHost, devContainerUsername, devContainerPassword, devContainerPort, devContainerWorkDir)
} else {
// TODO: show Error to User
showAlert("打开容器失败!", 1500)
const responseErrorMsg = data.data.ErrorMsg
console.log("Error happen when starting dev container", responseErrorMsg)
}
})
.catch(error => {
console.error('There has been a problem with your fetch operation:', error);
});
}
async function hasDevContainer(repoId) {
return new Promise((resolve, reject) => {
url = "https://www.devstar.cn/api/devcontainer/user"
token = USERTOKEN
fetch(url, {
method: 'GET', // 或者 'POST', 根据API要求
headers: {
'Content-Type': 'application/json',
'Authorization': 'token ' + token
},
})
.then(response => {
if (!response.ok) {
throw new Error('Network response was not ok when querying devContainer list' + response.statusText);
}
return response.json();
})
.then(data => {
const devContainers = data.data.devContainers;
devContainers.forEach((c, index) => {
if (c.repoId == repoId) {
// has devContainer
resolve(true)
return
}
});
resolve(false)
})
.catch(error => {
console.error('There has been a problem with your fetch operation:', error);
reject(error)
});
})
}
// TODO: create container
function createDevContainer(repoId) {
return new Promise((resolve, reject) => {
const url = "https://www.devstar.cn/api/devcontainer"
var token = USERTOKEN
const postData = {
"repoId": repoId.toString(),
}
fetch(url, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': 'token ' + token
},
body: JSON.stringify(postData)
})
.then(response => {
if (!response.ok) {
throw new Error(`Network response was not ok when creating devContainer ${repoId}` + response.statusText);
}
return response.json();
})
.then(data => {
const responseCode = data.code
if (responseCode == 0) {
resolve("success")
} else {
reject(data.data.ErrorMsg)
}
})
.catch(error => {
console.error('There has been a problem with your fetch operation:', error);
reject(error)
});
})
}
// TODO: delete container
function deleteDevContainer(repoId) {
return new Promise((resolve, reject) => {
})
}
function firstOpenRemoteFolder(host, username, password, port, path) {
const message = {
action: 'firstOpenRemoteFolder',
host : host,
username : username,
password : password,
port : port,
path : path,
}
// 向iframe父页面发送消息
window.parent.postMessage(message, '*');
}
function openRemoteFolder(host, path) {
const message = {
action: 'openRemoteFolder',
host : host,
path : path,
}
// 向iframe父页面发送消息
window.parent.postMessage(message, '*');
}
// ===================================== Utils ===========================
// 消息提示
function showAlert(alertText, duration) {
document.getElementById('alertBox').innerHTML = alertText;
document.getElementById('alertBox').style.display = 'block';
setTimeout(function() {
document.getElementById('alertBox').style.display = 'none';
}, duration); // 消息显示 duration/1000 秒后消失
}
async function biCommunication2Webview(action, data) {
return new Promise((resolve, reject) => {
// request to webview
window.parent.postMessage({ action: action, data: data }, '*');
// response from webview
function handleResponse(event) {
const jsonData = event.data
if (jsonData.action === action) {
console.log("biCommunication2Webview", jsonData)
// return webview response
window.removeEventListener('message', handleResponse) // 清理监听器
resolve(jsonData.data)
}
}
window.addEventListener('message', handleResponse)
setTimeout(() => {
window.removeEventListener('message', handleResponse)
reject('timeout')
}, 5000); // 5秒超时
})
}
</script>
</body>
</html>