1097 lines
42 KiB
Handlebars
1097 lines
42 KiB
Handlebars
<!-- ======================================= Script ==================================-->
|
||
<script>
|
||
// ===================================== Initialization ===========================
|
||
// Global variables
|
||
DEVSTAR_DOMAIN = ""
|
||
DEVSTAR_DOMAIN_TEST_URL = "" // It is empty by default in the production environment
|
||
var LANGUAGE = "zh-cn" // support display language: zh-cn or en
|
||
var USERTOKEN = null
|
||
var USERNAME = null
|
||
var SIGNED = false
|
||
|
||
// i18n
|
||
const i18n = {
|
||
languages: {
|
||
'en': {
|
||
login_reminder: "Please login!",
|
||
login_modal: "Login",
|
||
username: "Username",
|
||
password: "Password",
|
||
login_button: "Login",
|
||
createNewRepositoryModal: "Create New Repository",
|
||
createNewRepositoryButton: "Create New Repository"
|
||
},
|
||
'zh-cn': {
|
||
login_reminder: "请登录!",
|
||
login_modal: "登录",
|
||
username: "用户名",
|
||
password: "密码",
|
||
login_button: "确认",
|
||
createNewRepositoryModal: "创建新仓库",
|
||
createNewRepositoryButton: "创建新仓库",
|
||
}
|
||
},
|
||
|
||
init(lang = LANGUAGE) {
|
||
this.currentLang = this.languages[lang] ? lang : 'zh-cn';
|
||
this.updateAll();
|
||
},
|
||
|
||
// 更新页面的所有翻译
|
||
updateAll() {
|
||
document.querySelectorAll('.i18n').forEach(el => {
|
||
const key = el.dataset.key;
|
||
el.textContent = this.getString(key);
|
||
});
|
||
},
|
||
// 安全获取翻译文本(如果没有设置,则显示key的名称)
|
||
getString(key) {
|
||
return this.languages[this.currentLang]?.[key] || `[${key}]`
|
||
}
|
||
};
|
||
|
||
window.onload = async function () {
|
||
if ("" === DEVSTAR_DOMAIN_TEST_URL) {
|
||
await getDevstarDomainFromVSCode()
|
||
.then(async devstarDomain => {
|
||
DEVSTAR_DOMAIN = devstarDomain.endsWith("/") ? devstarDomain.substring(0, devstarDomain.length - 1) : devstarDomain
|
||
})
|
||
.catch(error => {
|
||
DEVSTAR_DOMAIN = "https://devstar.cn"
|
||
console.error('Failed to get devstar domain: ', error, '. Use default domain: ', DEVSTAR_DOMAIN)
|
||
})
|
||
} else {
|
||
DEVSTAR_DOMAIN = DEVSTAR_DOMAIN_TEST_URL
|
||
}
|
||
|
||
// initiate home
|
||
// display language
|
||
await getHomeConfigFromVSCode()
|
||
.then(async homeConfig => {
|
||
LANGUAGE = homeConfig.language
|
||
i18n.init()
|
||
})
|
||
.catch(error => {
|
||
console.error("Failed to get home config from vscode ", error)
|
||
})
|
||
|
||
// related to login status
|
||
await getUserTokenFromVSCode()
|
||
.then(async userToken => {
|
||
// verify user token
|
||
await verifyToken(userToken)
|
||
.then(result => {
|
||
// initialize user token
|
||
USERTOKEN = userToken
|
||
})
|
||
.catch(error => {
|
||
console.error('Error in verifying token:', error)
|
||
})
|
||
})
|
||
.catch(error => {
|
||
console.error("Failed to get user token from vscode: ", error)
|
||
})
|
||
|
||
await getUsernameFromVSCode()
|
||
.then(async username => {
|
||
USERNAME = username
|
||
})
|
||
.catch(error => {
|
||
console.error('Failed to get user name from vscode: ', error)
|
||
})
|
||
|
||
SIGNED = true ? USERTOKEN && USERNAME : false;
|
||
|
||
await reloadPageModules()
|
||
}
|
||
|
||
async function getHomeConfigFromVSCode() {
|
||
return new Promise(async (resolve, reject) => {
|
||
await communicateVSCodeByWebview('getHomeConfig', null)
|
||
.then(async data => {
|
||
console.log(data)
|
||
const config = data.homeConfig
|
||
|
||
if (config === undefined) {
|
||
reject("homeConfig is undefined")
|
||
}
|
||
resolve(config)
|
||
})
|
||
.catch(error => {
|
||
reject(error)
|
||
})
|
||
})
|
||
}
|
||
|
||
async function loadCreatingRepoForm() {
|
||
// fetch template data
|
||
const issueLabels = await fetchTemplateData(DEVSTAR_DOMAIN + `/api/v1/label/templates2`)
|
||
const gitignores = await fetchTemplateData(DEVSTAR_DOMAIN + `/api/v1/gitignore/templates`)
|
||
const licenses = await fetchTemplateData(DEVSTAR_DOMAIN + `/api/v1/licenses`)
|
||
const readmes = await fetchTemplateData(DEVSTAR_DOMAIN + `/api/v1/readme/templates`).then(data => { return data.data })
|
||
const object_formats = await fetchTemplateData(DEVSTAR_DOMAIN + `/api/v1/object_formats`).then(data => { return data.data })
|
||
// console.log('issueLabels, gitignores, licenses, readmes, object_formats', issueLabels, gitignores, licenses, readmes, object_formats)
|
||
|
||
// generate menus
|
||
const issue_label_menu = document.getElementById('issue_label_menu');
|
||
issueLabels.forEach(label => {
|
||
const newItem = document.createElement('div');
|
||
newItem.classList.add('item');
|
||
newItem.setAttribute('data-value', label.DisplayName);
|
||
newItem.innerHTML = `${label.DisplayName}<br><i>(${label.Description})</i>`;
|
||
issue_label_menu.appendChild(newItem);
|
||
});
|
||
|
||
const gitignore_menu = document.getElementById('gitignore_menu');
|
||
gitignores.forEach(gitignore => {
|
||
const newItem = document.createElement('div');
|
||
newItem.classList.add('item');
|
||
newItem.setAttribute('data-value', gitignore)
|
||
newItem.innerHTML = `${gitignore}`;
|
||
gitignore_menu.appendChild(newItem);
|
||
})
|
||
|
||
const license_menu = document.getElementById('license_menu');
|
||
licenses.forEach(license => {
|
||
const newItem = document.createElement('div');
|
||
newItem.classList.add('item');
|
||
newItem.setAttribute('data-value', license.name)
|
||
newItem.innerHTML = `${license.name}`;
|
||
license_menu.appendChild(newItem);
|
||
})
|
||
|
||
const readme_menu = document.getElementById('readme_menu');
|
||
readmes.forEach(readme => {
|
||
const newItem = document.createElement('div');
|
||
newItem.classList.add('item');
|
||
newItem.setAttribute('data-value', readme.name)
|
||
newItem.innerHTML = `${readme.name}`;
|
||
readme_menu.appendChild(newItem);
|
||
})
|
||
|
||
const defaultBranchElement = document.getElementById('default_branch');
|
||
if (defaultBranchElement) {
|
||
defaultBranchElement.value = 'main'; // Set the value of the input field to "main"
|
||
defaultBranchElement.placeholder = 'main'; // Optionally update the placeholder as well
|
||
}
|
||
|
||
const defaultObjectFormatElement = document.getElementById('object_format_name');
|
||
if (defaultObjectFormatElement) {
|
||
defaultObjectFormatElement.value = object_formats[0].name
|
||
}
|
||
const default_object_format_name = document.getElementById('default_object_format_name')
|
||
if (default_object_format_name) {
|
||
default_object_format_name.innerHTML = object_formats[0].name
|
||
}
|
||
|
||
const object_format_menu = document.getElementById('object_format_menu')
|
||
object_formats.forEach(object_format => {
|
||
const newItem = document.createElement('div');
|
||
newItem.classList.add('item');
|
||
newItem.setAttribute('data-value', object_format.name)
|
||
newItem.innerHTML = `${object_format.name}`;
|
||
object_format_menu.appendChild(newItem);
|
||
})
|
||
}
|
||
|
||
async function fetchTemplateData(url) {
|
||
return new Promise((resolve, reject) => {
|
||
try {
|
||
fetch(url, {
|
||
method: 'GET',
|
||
})
|
||
.then(response => {
|
||
response.json().then(data => {
|
||
if (response.ok) {
|
||
resolve(data)
|
||
} else {
|
||
throw new Error(`Failed to fetch template data from url: ${url} Error: ${data.message}`)
|
||
}
|
||
})
|
||
})
|
||
} catch (error) {
|
||
reject(error)
|
||
console.error(error)
|
||
}
|
||
})
|
||
}
|
||
|
||
async function getDevstarDomainFromVSCode() {
|
||
return new Promise(async (resolve, reject) => {
|
||
await communicateVSCodeByWebview('getDevstarDomain', null)
|
||
.then(async data => {
|
||
const devstarDomain = data.devstarDomain
|
||
|
||
if (undefined === devstarDomain) {
|
||
reject("devstar domain is undefined")
|
||
}
|
||
resolve(devstarDomain)
|
||
})
|
||
.catch(error => {
|
||
reject(error)
|
||
})
|
||
})
|
||
}
|
||
|
||
async function getUserTokenFromVSCode() {
|
||
return new Promise(async (resolve, reject) => {
|
||
await communicateVSCodeByWebview('getUserToken', null)
|
||
.then(async data => {
|
||
const userToken = data.userToken
|
||
|
||
if (userToken === undefined) {
|
||
reject("userToken is undefined")
|
||
}
|
||
resolve(userToken)
|
||
})
|
||
.catch(error => {
|
||
reject(error)
|
||
})
|
||
})
|
||
}
|
||
|
||
async function getUsernameFromVSCode() {
|
||
return new Promise(async (resolve, reject) => {
|
||
await communicateVSCodeByWebview('getUsername', null)
|
||
.then(async data => {
|
||
const username = data.username
|
||
if (username === undefined) {
|
||
reject('username is undefined')
|
||
}
|
||
|
||
resolve(username)
|
||
})
|
||
.catch(error => {
|
||
reject(error)
|
||
})
|
||
})
|
||
}
|
||
|
||
function verifyToken(token) {
|
||
return new Promise((resolve, reject) => {
|
||
fetch(DEVSTAR_DOMAIN + '/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))
|
||
}
|
||
})
|
||
})
|
||
}
|
||
|
||
async function reloadPageModules() {
|
||
// everytime login status change, call this function
|
||
if (SIGNED) {
|
||
// head
|
||
// document.getElementById('signup-link').style.display = 'none'
|
||
document.getElementById('signin-link').style.display = 'none'
|
||
document.getElementById('logout-link').style.display = 'flex'
|
||
// html content
|
||
document.getElementById('contentBeforeSigned').style.display = 'none'
|
||
document.getElementById('contentAfterSigned').style.display = 'block'
|
||
// load data for page
|
||
loadRepositories() // repo list
|
||
await loadCreatingRepoForm() // form for creating repo
|
||
} else {
|
||
// document.getElementById('signup-link').style.display = 'flex'
|
||
document.getElementById('signin-link').style.display = 'flex'
|
||
document.getElementById('logout-link').style.display = 'none'
|
||
// html content
|
||
document.getElementById('contentBeforeSigned').style.display = 'block'
|
||
document.getElementById('contentAfterSigned').style.display = 'none'
|
||
}
|
||
}
|
||
|
||
// ===================================== login and logout ===========================
|
||
// login model
|
||
function openLoginModal() {
|
||
// check if user has logged in, only show login modal when user has not logged in
|
||
if (SIGNED) {
|
||
console.log('User has logged in')
|
||
showInformationNotification('已登录')
|
||
} else {
|
||
document.getElementById('loginModal').style.display = 'block';
|
||
}
|
||
}
|
||
|
||
function closeLoginModal() {
|
||
document.getElementById('loginModal').style.display = 'none';
|
||
}
|
||
|
||
async function login() {
|
||
var username = document.getElementById('username').value;
|
||
var password = document.getElementById('password').value;
|
||
const url = DEVSTAR_DOMAIN + `/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 => {
|
||
response.json().then(async data => {
|
||
if (response.ok) {
|
||
// store token in global variable and vscode global state
|
||
USERTOKEN = data.sha1;
|
||
USERNAME = username;
|
||
setUserTokenToVSCode(USERTOKEN);
|
||
setUsernameToVSCode(username);
|
||
SIGNED = true
|
||
await reloadPageModules()
|
||
closeLoginModal()
|
||
|
||
// if user public key exist, meaning that public key has been uploaded
|
||
await getUserPublicKeyFromVSCode()
|
||
.then(async userPublicKey => {
|
||
if (userPublicKey === '') {
|
||
await createUserPublicKeyByVSCode()
|
||
.then(async () => {
|
||
// only upload new created public key
|
||
await getUserPublicKeyFromVSCode()
|
||
.then(async userPublicKey => {
|
||
const dataFromResp = await communicateVSCodeByWebview('getMachineName', {});
|
||
const machineName = dataFromResp.machineName;
|
||
|
||
// key title: username-machine-timestamp
|
||
const timestamp = Date.now();
|
||
const keyTitle = `${USERNAME}-${machineName}-${timestamp}`
|
||
await uploadNewCreatedPublicKey(userPublicKey, keyTitle);
|
||
})
|
||
.catch(error => {
|
||
console.error("Failed to get NEW CREATED user public key from vscode: ", error)
|
||
})
|
||
})
|
||
}
|
||
})
|
||
.catch(error => {
|
||
console.error("Failed to get user public key from vscode: ", error)
|
||
})
|
||
} else {
|
||
showErrorNotification(`登录失败!\nError: ${data.message}`)
|
||
throw new Error(`登录失败!\nError: ${data.message}`);
|
||
}
|
||
})
|
||
})
|
||
.catch(error => {
|
||
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 = 'vscode_login_token_';
|
||
const charactersLength = characters.length;
|
||
for (let i = 0; i < length; i++) {
|
||
name += characters.charAt(Math.floor(Math.random() * charactersLength));
|
||
}
|
||
return name
|
||
}
|
||
|
||
async function logout() {
|
||
// remove token and username from global variable and vscode global state
|
||
USERTOKEN = null
|
||
USERNAME = null
|
||
await setUserTokenToVSCode('')
|
||
await setUsernameToVSCode('')
|
||
SIGNED = false
|
||
|
||
await reloadPageModules()
|
||
}
|
||
|
||
async function setUserTokenToVSCode(userToken) {
|
||
var removeUserToken = false;
|
||
if ('' === userToken) {
|
||
removeUserToken = true
|
||
}
|
||
|
||
communicateVSCodeByWebview('setUserToken', { userToken: userToken })
|
||
.then(result => {
|
||
if (result.ok) {
|
||
console.log(removeUserToken ? 'User token has been removed from vscode' : 'User token has been stored in vscode')
|
||
} else {
|
||
console.error(removeUserToken ? 'Failed to remove user token from vscode' : 'Failed to store user token into vscode')
|
||
}
|
||
})
|
||
.catch(error => {
|
||
console.error('Failed to set user token into vscode:', error)
|
||
})
|
||
}
|
||
|
||
async function setUsernameToVSCode(username) {
|
||
var removeUsername = false;
|
||
if ('' === username) {
|
||
removeUsername = true;
|
||
}
|
||
|
||
await communicateVSCodeByWebview('setUsername', { username: username })
|
||
.then(result => {
|
||
if (result.ok) {
|
||
console.log(removeUsername ? 'User name has been removed from vscode' : 'User name has been stored in vscode')
|
||
} else {
|
||
console.error(removeUsername ? 'Failed to removing user name from vscode' : 'Failed to store user name in vscode')
|
||
}
|
||
})
|
||
.catch(error => {
|
||
console.error("Error happened when setting user name: ", error)
|
||
})
|
||
}
|
||
|
||
async function getUserPublicKeyFromVSCode() {
|
||
return new Promise(async (resolve, reject) => {
|
||
await communicateVSCodeByWebview('getUserPublicKey', {})
|
||
.then(data => {
|
||
const publicKey = data.userPublicKey;
|
||
resolve(publicKey)
|
||
})
|
||
.catch(error => {
|
||
reject(error)
|
||
})
|
||
})
|
||
}
|
||
|
||
async function createUserPublicKeyByVSCode() {
|
||
await communicateVSCodeByWebview('createUserPublicKey', {})
|
||
.then(res => {
|
||
if (res.ok) {
|
||
console.log('User public key has been created')
|
||
} else {
|
||
console.error('Failed to create user public key')
|
||
}
|
||
})
|
||
.catch(error => {
|
||
console.error('Failed to request to create user public key: ', erro)
|
||
})
|
||
}
|
||
|
||
async function uploadNewCreatedPublicKey(userPublicKey, keyTitle) {
|
||
const postData = {
|
||
"key": userPublicKey,
|
||
"title": keyTitle
|
||
}
|
||
|
||
const uploadUrl = DEVSTAR_DOMAIN + `/api/v1/user/keys`;
|
||
|
||
fetch(uploadUrl, {
|
||
method: 'POST',
|
||
headers: {
|
||
'Content-Type': 'application/json',
|
||
'Authorization': 'token ' + USERTOKEN
|
||
},
|
||
body: JSON.stringify(postData)
|
||
})
|
||
.then(response => {
|
||
response.json().then(data => {
|
||
if (response.ok) {
|
||
console.log("Successfully upload new created public key.\n", data)
|
||
} else {
|
||
throw new Error(`Failed to upload new created public key!\nError: ${data.message}`)
|
||
}
|
||
})
|
||
})
|
||
.catch(error => {
|
||
console.error(error);
|
||
});
|
||
}
|
||
|
||
// ===================================== Repo ===========================
|
||
function loadRepositories() {
|
||
// clear old data
|
||
const repoList = document.getElementById('repo_list')
|
||
repoList.innerHTML = '';
|
||
|
||
// load new data
|
||
var url = DEVSTAR_DOMAIN + "/api/v1/user/repos?page=1&limit=20"
|
||
var token = USERTOKEN
|
||
fetch(url, {
|
||
method: 'GET',
|
||
headers: {
|
||
'Content-Type': 'application/json',
|
||
'Authorization': 'token ' + token
|
||
},
|
||
})
|
||
.then(response => {
|
||
response.json().then(data => {
|
||
if (response.ok) {
|
||
let repos = data;
|
||
repos.forEach((repo, index) => {
|
||
const repoFullName = repo.full_name;
|
||
const repoURL = repo.html_url;
|
||
const repoID = repo.id;
|
||
|
||
hasDevContainer(repoID)
|
||
.then(found => {
|
||
addItemForRepoList(repoFullName, repoURL, repoID, found)
|
||
})
|
||
})
|
||
} else {
|
||
throw new Error(`Failed to load repos, Error: ${data.message}`);
|
||
}
|
||
})
|
||
})
|
||
.catch(error => {
|
||
console.error(error);
|
||
});
|
||
}
|
||
|
||
/**
|
||
* isOpen:表示项目是否已经被打开
|
||
*/
|
||
function addItemForRepoList(repoFullName, repoURL, repoID, hasDevContainer) {
|
||
const repoList = document.getElementById('repo_list');
|
||
|
||
// 创建新的 flex-item 元素
|
||
const flexItem = document.createElement('div');
|
||
flexItem.classList.add('flex-item');
|
||
|
||
// 创建 flex-item-main
|
||
const flexItemMain = document.createElement('div');
|
||
flexItemMain.classList.add('flex-item-main');
|
||
|
||
// 创建 flex-item-header
|
||
const flexItemHeader = document.createElement('div');
|
||
flexItemHeader.classList.add('flex-item-header');
|
||
|
||
// 创建 flex-item-title
|
||
const flexItemTitle = document.createElement('div');
|
||
flexItemTitle.classList.add('flex-item-title');
|
||
|
||
// 创建名称部分
|
||
const repoNameDiv = document.createElement('div');
|
||
repoNameDiv.classList.add('text', 'name');
|
||
repoNameDiv.textContent = repoFullName;
|
||
|
||
// 创建 flex-item-trailing
|
||
const flexItemTrailing = document.createElement('div');
|
||
flexItemTrailing.classList.add('flex-item-trailing');
|
||
|
||
// 创建按钮
|
||
const createContainerBtn = document.createElement('button');
|
||
createContainerBtn.id = `${repoID}_createContainerBtn`
|
||
createContainerBtn.classList.add('ui', 'green', 'button');
|
||
createContainerBtn.onclick = function () {
|
||
createDevContainer(repoID)
|
||
}
|
||
|
||
const openProjectBtn = document.createElement('button');
|
||
openProjectBtn.id = `${repoID}_openProjectBtn`
|
||
openProjectBtn.classList.add('ui', 'primary', 'button');
|
||
openProjectBtn.onclick = function () {
|
||
openDevContainer(repoID)
|
||
}
|
||
|
||
const deleteContainerBtn = document.createElement('button');
|
||
deleteContainerBtn.id = `${repoID}_deleteContainerBtn`
|
||
deleteContainerBtn.classList.add('ui', 'red', 'button');
|
||
deleteContainerBtn.onclick = function () {
|
||
deleteDevContainer(repoID)
|
||
}
|
||
|
||
// 按钮名称根据语言配置
|
||
switch (LANGUAGE) {
|
||
case 'zh-cn':
|
||
createContainerBtn.textContent = '创建容器';
|
||
openProjectBtn.textContent = '打开项目';
|
||
deleteContainerBtn.textContent = '删除容器'
|
||
break
|
||
case 'en':
|
||
createContainerBtn.textContent = 'Create Container'
|
||
openProjectBtn.textContent = 'Open Project'
|
||
deleteContainerBtn.textContent = 'Delete Container'
|
||
break;
|
||
}
|
||
|
||
// 创建 flex-item-body
|
||
const flexItemBody = document.createElement('div');
|
||
flexItemBody.classList.add('flex-item-body');
|
||
flexItemBody.textContent = repoURL;
|
||
|
||
// 组合所有部分
|
||
flexItemTitle.appendChild(repoNameDiv);
|
||
|
||
flexItemTrailing.appendChild(createContainerBtn);
|
||
flexItemTrailing.appendChild(openProjectBtn);
|
||
flexItemTrailing.appendChild(deleteContainerBtn);
|
||
|
||
flexItemHeader.appendChild(flexItemTitle);
|
||
flexItemHeader.appendChild(flexItemTrailing);
|
||
|
||
flexItemMain.appendChild(flexItemHeader);
|
||
flexItemMain.appendChild(flexItemBody);
|
||
|
||
flexItem.appendChild(flexItemMain);
|
||
|
||
// 将新创建的 flex-item 添加到 repo_list 中
|
||
repoList.appendChild(flexItem);
|
||
|
||
// 调整按钮显示
|
||
if (hasDevContainer) {
|
||
adjustBtnForRepoItem(repoID, 'created')
|
||
} else {
|
||
adjustBtnForRepoItem(repoID, 'not create')
|
||
}
|
||
}
|
||
|
||
function adjustBtnForRepoItem(repoId, status) {
|
||
// status: not create, createing, created
|
||
const createContainerBtn = document.getElementById(`${repoId}_createContainerBtn`);
|
||
const openProjectBtn = document.getElementById(`${repoId}_openProjectBtn`);
|
||
const deleteContainerBtn = document.getElementById(`${repoId}_deleteContainerBtn`);
|
||
if (status === 'not create') {
|
||
createContainerBtn.style.display = 'block';
|
||
openProjectBtn.style.display = 'none';
|
||
deleteContainerBtn.style.display = 'none';
|
||
|
||
} else if (status === 'creating') {
|
||
createContainerBtn.classList.toggle('disabled')
|
||
openProjectBtn.style.display = 'none';
|
||
deleteContainerBtn.style.display = 'none';
|
||
} else {
|
||
createContainerBtn.style.display = 'none';
|
||
openProjectBtn.style.display = 'block';
|
||
deleteContainerBtn.style.display = 'block';
|
||
}
|
||
}
|
||
|
||
// 创建仓库modal
|
||
function openModalForCreateRepository() {
|
||
document.getElementById('modalForCreatingRepo').style.display = "block";
|
||
}
|
||
|
||
function closeModalForCreatingRepo() {
|
||
document.getElementById('modalForCreatingRepo').style.display = "none";
|
||
}
|
||
|
||
// 点击窗外关闭弹窗
|
||
window.onclick = function (event) {
|
||
if (event.target == document.getElementById('modalForCreatingRepo')) {
|
||
closeModalForCreatingRepo();
|
||
}
|
||
if (event.target == document.getElementById('loginModal')) {
|
||
closeLoginModal();
|
||
}
|
||
}
|
||
|
||
function createRepo() {
|
||
// 这里添加实际的表单提交逻辑
|
||
// 模拟表单处理
|
||
var repo_name = document.getElementById('repo_name').value;
|
||
// 暂时不支持private
|
||
// var is_private = document.getElementById('is_private').checked;
|
||
var description = document.getElementById('description').value;
|
||
var template_url = document.getElementById('devstar_template').value;
|
||
var issue_labels = document.getElementById('issue_label').value
|
||
var gitignore = document.getElementById('gitignore').value
|
||
var license = document.getElementById('license').value
|
||
var readme = document.getElementById('readme').value
|
||
var default_branch = document.getElementById('default_branch').value;
|
||
var object_format_name = document.getElementById('object_format_name').value;
|
||
var is_template = document.getElementById('is_template').checked
|
||
|
||
const url = DEVSTAR_DOMAIN + "/api/v1/user/repos"
|
||
var token = USERTOKEN
|
||
|
||
const postData = {
|
||
'name': repo_name,
|
||
// 'private': is_private,
|
||
'trust_model': 'default',
|
||
"auto_init": true
|
||
}
|
||
|
||
if (description) {
|
||
postData.description = description
|
||
}
|
||
if (issue_labels) {
|
||
postData.issue_labels = issue_labels
|
||
}
|
||
if (gitignore) {
|
||
postData.gitignore = gitignore
|
||
}
|
||
if (license) {
|
||
postData.license = license
|
||
}
|
||
if (readme) {
|
||
postData.readme = readme
|
||
}
|
||
if (default_branch) {
|
||
postData.default_branch = default_branch
|
||
}
|
||
if (object_format_name) {
|
||
postData.object_format_name = object_format_name
|
||
}
|
||
if (is_template) {
|
||
postData.template = is_template
|
||
}
|
||
|
||
fetch(url, {
|
||
method: 'POST',
|
||
headers: {
|
||
'Content-Type': 'application/json',
|
||
'Authorization': 'token ' + token
|
||
},
|
||
body: JSON.stringify(postData)
|
||
})
|
||
.then(response => {
|
||
response.json().then(data => {
|
||
if (response.ok) {
|
||
showInformationNotification('项目创建成功!')
|
||
console.log('项目创建成功!')
|
||
loadRepositories()
|
||
closeModalForCreatingRepo(); // 关闭创建项目弹窗
|
||
} else {
|
||
showErrorNotification(`项目创建失败!\nError: ${data.message}`)
|
||
throw new Error(`项目创建失败!\nError: ${data.message}`)
|
||
}
|
||
})
|
||
})
|
||
.catch(error => {
|
||
console.error(error);
|
||
});
|
||
return false;
|
||
}
|
||
|
||
// ===================================== Projects ===========================
|
||
async function openDevContainer(repoId) {
|
||
console.log("opening project")
|
||
|
||
// open devcontainer through repoId
|
||
var url = DEVSTAR_DOMAIN + "/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 => {
|
||
response.json().then(data => {
|
||
const responseCode = data.code
|
||
if (response.ok && responseCode === 0) {
|
||
// container start successfully
|
||
// get devContainer ssh connection information
|
||
const projectName = data.data.repoName
|
||
const devContainerHost = data.data.devContainerHost
|
||
const devContainerUsername = data.data.devContainerUsername
|
||
const devContainerPort = data.data.devContainerPort
|
||
const devContainerWorkDir = data.data.devContainerWorkDir
|
||
|
||
// default: open with key
|
||
communicateVSCodeByWebview('firstOpenRemoteFolder', {
|
||
host: `${projectName}`,
|
||
hostname: `${devContainerHost}`,
|
||
username: `${devContainerUsername}`,
|
||
port: `${devContainerPort}`,
|
||
path: `${devContainerWorkDir}`,
|
||
})
|
||
} else if (response.ok) {
|
||
// TODO: 等待的code
|
||
console.warn(`打开容器失败!code: ${responseCode}\nError: ${data.data.ErrorMsg}`)
|
||
showErrorNotification(`打开容器失败!\nError: ${data.data.ErrorMsg}`)
|
||
throw new Error(`打开容器失败!\nError: ${data.data.ErrorMsg}`)
|
||
} else {
|
||
showErrorNotification(`打开容器失败!\nError: ${data.message}`)
|
||
throw new Error(`打开容器失败!\nError: ${data.message}`)
|
||
}
|
||
})
|
||
})
|
||
.catch(error => {
|
||
console.error(error);
|
||
});
|
||
}
|
||
|
||
async function hasDevContainer(repoId) {
|
||
return new Promise(async (resolve) => {
|
||
url = DEVSTAR_DOMAIN + "/api/devcontainer/user"
|
||
token = USERTOKEN
|
||
|
||
fetch(url, {
|
||
method: 'GET',
|
||
headers: {
|
||
'Content-Type': 'application/json',
|
||
'Authorization': 'token ' + token
|
||
},
|
||
})
|
||
.then(response => {
|
||
response.json().then(data => {
|
||
if (response.ok) {
|
||
let found = false
|
||
const devContainers = data.data.devContainers;
|
||
devContainers.forEach((c, index) => {
|
||
if (String(c.repoId) === String(repoId)) {
|
||
// has devContainer
|
||
found = true
|
||
resolve(true)
|
||
}
|
||
});
|
||
|
||
if (!found) {
|
||
resolve(false)
|
||
}
|
||
} else {
|
||
throw new Error(`Failed to fetch devContainer list.\nError: ${data.message}`)
|
||
}
|
||
})
|
||
})
|
||
.catch(error => {
|
||
console.error(error);
|
||
});
|
||
})
|
||
}
|
||
|
||
async function createDevContainer(repoId) {
|
||
showInformationNotification("正在创建开发容器...")
|
||
|
||
// request creating container
|
||
const url = DEVSTAR_DOMAIN + "/api/devcontainer"
|
||
var token = USERTOKEN
|
||
|
||
const postData = {
|
||
"repoId": repoId.toString(),
|
||
// "sshPublicKeyList": [publicKey]
|
||
}
|
||
|
||
fetch(url, {
|
||
method: 'POST',
|
||
headers: {
|
||
'Content-Type': 'application/json',
|
||
'Authorization': 'token ' + token
|
||
},
|
||
body: JSON.stringify(postData)
|
||
})
|
||
.then(response => {
|
||
response.json().then(async data => {
|
||
const responseCode = data.code
|
||
if (response.ok && responseCode === 0) {
|
||
// TODO: 根据中英双语显示
|
||
showInformationNotification(`创建容器成功!项目初始化中,请等待...`)
|
||
adjustBtnForRepoItem(repoId, 'creating')
|
||
console.log('Successfully create dev container!')
|
||
|
||
// 此时等待容器准备完成
|
||
try {
|
||
await sleep(3000)
|
||
const projectStatus = await projectCompletionSign(repoId);
|
||
|
||
if (projectStatus === 'ok') {
|
||
console.log('Project prepared!')
|
||
adjustBtnForRepoItem(repoId, 'created')
|
||
showInformationNotification(`项目已准备完成!`)
|
||
} else {
|
||
// projectCompletionSign 抛出了错误或者返回了 'error'
|
||
console.warn("项目初始化失败或超时");
|
||
}
|
||
} catch (error) {
|
||
console.error("项目初始化失败:", error);
|
||
adjustBtnForRepoItem(repoId, 'created')
|
||
}
|
||
} else if (response.ok) {
|
||
console.warn(responseCode)
|
||
showErrorNotification(`创建容器失败!\nError: ${data.data.ErrorMsg}`)
|
||
throw new Error(`创建容器失败!\nError: ${data.data.ErrorMsg}`)
|
||
} else {
|
||
showErrorNotification(`创建容器失败!\nError: ${data.message}`)
|
||
throw new Error(`创建容器失败!\nError: ${data.message}`)
|
||
}
|
||
})
|
||
})
|
||
.catch(error => {
|
||
console.error(error);
|
||
});
|
||
}
|
||
|
||
function sleep(ms) {
|
||
return new Promise(resolve => setTimeout(resolve, ms));
|
||
}
|
||
|
||
async function projectCompletionSign(repoId) {
|
||
return new Promise((resolve, reject) => {
|
||
const maxRetries = 60; // 最大重试次数
|
||
let retryCount = 0;
|
||
const intervalId = setInterval(async () => {
|
||
var url = DEVSTAR_DOMAIN + "/api/devcontainer";
|
||
var token = USERTOKEN;
|
||
|
||
const queryParams = new URLSearchParams({
|
||
repoId: repoId,
|
||
wait: false
|
||
}).toString();
|
||
const urlWithParams = `${url}?${queryParams}`;
|
||
|
||
try {
|
||
const response = await fetch(urlWithParams, {
|
||
method: 'GET',
|
||
headers: {
|
||
'Content-Type': 'application/json',
|
||
'Authorization': 'token ' + token
|
||
},
|
||
});
|
||
|
||
const data = await response.json();
|
||
const responseCode = data.code;
|
||
const devcontainerStatus = data.data.devContainerStatus
|
||
|
||
console.log("容器状态: ", response.ok, responseCode, devcontainerStatus)
|
||
|
||
if (responseCode == 0 && devcontainerStatus == 4) {
|
||
// 项目准备完成
|
||
clearInterval(intervalId); // 清除定时器
|
||
resolve('ok');
|
||
return;
|
||
} else if (responseCode == 0) {
|
||
// 继续
|
||
} else {
|
||
showErrorNotification(`项目初始化失败!\nError: ${data.message}`);
|
||
clearInterval(intervalId);
|
||
reject(new Error(`项目初始化失败!\nError: ${data.message}`));
|
||
return;
|
||
}
|
||
} catch (error) {
|
||
console.error(error);
|
||
// 发生错误时,不清除定时器,继续重试
|
||
}
|
||
|
||
retryCount++;
|
||
if (retryCount >= maxRetries) {
|
||
clearInterval(intervalId); // 清除定时器
|
||
reject(new Error("达到最大重试次数,项目可能未准备好")); // Reject Promise
|
||
}
|
||
}, 1000); // 每秒检查一次
|
||
});
|
||
}
|
||
|
||
function deleteDevContainer(repoId) {
|
||
var url = DEVSTAR_DOMAIN + "/api/devcontainer"
|
||
var token = USERTOKEN
|
||
|
||
const queryParams = new URLSearchParams({
|
||
repoId: repoId,
|
||
}).toString();
|
||
const urlWithParams = `${url}?${queryParams}`;
|
||
|
||
fetch(urlWithParams, {
|
||
method: 'DELETE',
|
||
headers: {
|
||
'Content-Type': 'application/json',
|
||
'Authorization': 'token ' + token
|
||
},
|
||
})
|
||
.then(response => {
|
||
response.json().then(data => {
|
||
const responseCode = data.code
|
||
if (response.ok && responseCode === 0) {
|
||
showInformationNotification("删除容器成功!")
|
||
adjustBtnForRepoItem(repoId, 'not create')
|
||
console.log('Successfully delete dev container belong to repoId:', repoId, data)
|
||
} else if (response.ok) {
|
||
showErrorNotification(`删除容器失败!\nError: ${data.data.ErrorMsg}`)
|
||
throw new Error(`删除容器失败!\nError: ${data.data.ErrorMsg}`)
|
||
} else {
|
||
showErrorNotification(`删除容器失败!\nError: ${data.message}`)
|
||
throw new Error(`删除容器失败!\nError: ${data.message}`)
|
||
}
|
||
})
|
||
})
|
||
.catch(error => {
|
||
console.error(error)
|
||
})
|
||
}
|
||
|
||
function firstOpenRemoteFolder(host, hostname, username, password, port, path) {
|
||
const message = {
|
||
action: 'firstOpenRemoteFolder',
|
||
host: host,
|
||
hostname: hostname,
|
||
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 showInformationNotification(message) {
|
||
communicateVSCodeByWebviewNoReturn('showInformationNotification', { message: message })
|
||
}
|
||
|
||
function showWarningNotification(message) {
|
||
communicateVSCodeByWebviewNoReturn('showWarningNotification', { message: message })
|
||
}
|
||
|
||
function showErrorNotification(message) {
|
||
communicateVSCodeByWebviewNoReturn('showErrorNotification', { message: message })
|
||
}
|
||
|
||
async function communicateVSCodeByWebviewNoReturn(action, data) {
|
||
window.parent.postMessage({ target: "vscode_no_return", action: action, data: data }, "*");
|
||
}
|
||
|
||
async function communicateVSCodeByWebview(action, data) {
|
||
return new Promise((resolve, reject) => {
|
||
// request to webview
|
||
window.parent.postMessage({ target: "vscode", action: action, data: data }, '*');
|
||
|
||
// response from webview
|
||
function handleResponse(event) {
|
||
const jsonData = event.data
|
||
if (jsonData.action === action) {
|
||
// return webview response
|
||
console.log("dataFromVSCodeByWebview", jsonData.data)
|
||
|
||
window.removeEventListener('message', handleResponse) // 清理监听器
|
||
resolve(jsonData.data)
|
||
}
|
||
}
|
||
|
||
window.addEventListener('message', handleResponse)
|
||
|
||
setTimeout(() => {
|
||
window.removeEventListener('message', handleResponse)
|
||
reject('timeout')
|
||
}, 10000); // 10秒超时
|
||
|
||
})
|
||
}
|
||
|
||
|
||
</script> |