refactor: separate js code from html
This commit is contained in:
942
templates/devstar-home-vscode-js.tmpl
Normal file
942
templates/devstar-home-vscode-js.tmpl
Normal file
@@ -0,0 +1,942 @@
|
||||
<!-- ======================================= 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
|
||||
|
||||
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
|
||||
const config = await getHomeConfigFromVSCode()
|
||||
console.log(config)
|
||||
|
||||
// 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);
|
||||
});
|
||||
}
|
||||
|
||||
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.textContent = 'Create Container';
|
||||
createContainerBtn.onclick = function () {
|
||||
createDevContainer(repoID)
|
||||
}
|
||||
|
||||
const openProjectBtn = document.createElement('button');
|
||||
openProjectBtn.id = `${repoID}_openProjectBtn`
|
||||
openProjectBtn.classList.add('ui', 'primary', 'button');
|
||||
openProjectBtn.textContent = 'Open Project';
|
||||
openProjectBtn.onclick = function () {
|
||||
openDevContainer(repoID)
|
||||
}
|
||||
|
||||
const deleteContainerBtn = document.createElement('button');
|
||||
deleteContainerBtn.id = `${repoID}_deleteContainerBtn`
|
||||
deleteContainerBtn.classList.add('ui', 'red', 'button');
|
||||
deleteContainerBtn.textContent = 'Delete Container';
|
||||
deleteContainerBtn.onclick = function () {
|
||||
deleteDevContainer(repoID)
|
||||
}
|
||||
|
||||
// 创建 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);
|
||||
|
||||
// 调整按钮显示
|
||||
adjustBtnForRepoItem(repoID, hasDevContainer)
|
||||
}
|
||||
|
||||
function adjustBtnForRepoItem(repoId, hasDevContainer) {
|
||||
const createContainerBtn = document.getElementById(`${repoId}_createContainerBtn`);
|
||||
const openProjectBtn = document.getElementById(`${repoId}_openProjectBtn`);
|
||||
const deleteContainerBtn = document.getElementById(`${repoId}_deleteContainerBtn`);
|
||||
if (hasDevContainer) {
|
||||
createContainerBtn.style.display = 'none';
|
||||
openProjectBtn.style.display = 'block';
|
||||
deleteContainerBtn.style.display = 'block';
|
||||
} else {
|
||||
createContainerBtn.style.display = 'block';
|
||||
openProjectBtn.style.display = 'none';
|
||||
deleteContainerBtn.style.display = 'none';
|
||||
}
|
||||
}
|
||||
|
||||
// 创建仓库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 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: `${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(data => {
|
||||
const responseCode = data.code
|
||||
if (response.ok && responseCode === 0) {
|
||||
showInformationNotification(`创建容器成功!`)
|
||||
adjustBtnForRepoItem(repoId, true)
|
||||
console.log('Successfully create dev container!')
|
||||
} 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 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, false)
|
||||
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, 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 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>
|
||||
@@ -373,923 +373,4 @@
|
||||
</body>
|
||||
</html>
|
||||
{{template "base/footer" .}}
|
||||
|
||||
<!-- ======================================= Script ==================================-->
|
||||
<script>
|
||||
// ===================================== Initialization ===========================
|
||||
// Global variables
|
||||
DEVSTAR_DOMAIN = ""
|
||||
DEVSTAR_DOMAIN_TEST_URL = "" // It is empty by default in the production environment
|
||||
var USERTOKEN = null
|
||||
var USERNAME = null
|
||||
var SIGNED = false
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
// 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 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);
|
||||
});
|
||||
}
|
||||
|
||||
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.textContent = 'Create Container';
|
||||
createContainerBtn.onclick = function () {
|
||||
createDevContainer(repoID)
|
||||
}
|
||||
|
||||
const openProjectBtn = document.createElement('button');
|
||||
openProjectBtn.id = `${repoID}_openProjectBtn`
|
||||
openProjectBtn.classList.add('ui', 'primary', 'button');
|
||||
openProjectBtn.textContent = 'Open Project';
|
||||
openProjectBtn.onclick = function () {
|
||||
openDevContainer(repoID)
|
||||
}
|
||||
|
||||
const deleteContainerBtn = document.createElement('button');
|
||||
deleteContainerBtn.id = `${repoID}_deleteContainerBtn`
|
||||
deleteContainerBtn.classList.add('ui', 'red', 'button');
|
||||
deleteContainerBtn.textContent = 'Delete Container';
|
||||
deleteContainerBtn.onclick = function () {
|
||||
deleteDevContainer(repoID)
|
||||
}
|
||||
|
||||
// 创建 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);
|
||||
|
||||
// 调整按钮显示
|
||||
adjustBtnForRepoItem(repoID, hasDevContainer)
|
||||
}
|
||||
|
||||
function adjustBtnForRepoItem(repoId, hasDevContainer) {
|
||||
const createContainerBtn = document.getElementById(`${repoId}_createContainerBtn`);
|
||||
const openProjectBtn = document.getElementById(`${repoId}_openProjectBtn`);
|
||||
const deleteContainerBtn = document.getElementById(`${repoId}_deleteContainerBtn`);
|
||||
if (hasDevContainer) {
|
||||
createContainerBtn.style.display = 'none';
|
||||
openProjectBtn.style.display = 'block';
|
||||
deleteContainerBtn.style.display = 'block';
|
||||
} else {
|
||||
createContainerBtn.style.display = 'block';
|
||||
openProjectBtn.style.display = 'none';
|
||||
deleteContainerBtn.style.display = 'none';
|
||||
}
|
||||
}
|
||||
|
||||
// 创建仓库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 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: `${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(data => {
|
||||
const responseCode = data.code
|
||||
if (response.ok && responseCode === 0) {
|
||||
showInformationNotification(`创建容器成功!`)
|
||||
adjustBtnForRepoItem(repoId, true)
|
||||
console.log('Successfully create dev container!')
|
||||
} 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 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, false)
|
||||
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, 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 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')
|
||||
}, 5000); // 5秒超时
|
||||
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
</script>
|
||||
|
||||
{{template "devstar-home-vscode-js" .}}
|
||||
Reference in New Issue
Block a user