merge code from home.html
This commit is contained in:
repo.diff.committed_by
戴明辰
repo.diff.parent
52fbc5c556
repo.diff.commit
36a2f71dc7
@@ -1,51 +1,922 @@
|
||||
{{template "base/head" .}}
|
||||
<!DOCTYPE html>
|
||||
<html lang="{{ctx.Locale.Lang}}" data-theme="{{UserThemeName .SignedUser}}">
|
||||
<head>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>{{if .Title}}{{.Title}} - {{end}}{{if .Repository.Name}}{{.Repository.Name}} - {{end}}{{AppName}}</title>
|
||||
{{if .ManifestData}}<link rel="manifest" href="data:{{.ManifestData}}">{{end}}
|
||||
<meta name="author" content="{{if .Repository}}{{.Owner.Name}}{{else}}{{MetaAuthor}}{{end}}">
|
||||
<meta name="description" content="{{if .Repository}}{{.Repository.Name}}{{if .Repository.Description}} - {{.Repository.Description}}{{end}}{{else}}{{MetaDescription}}{{end}}">
|
||||
<meta name="keywords" content="{{MetaKeywords}}">
|
||||
<meta name="referrer" content="no-referrer">
|
||||
{{if .GoGetImport}}
|
||||
<meta name="go-import" content="{{.GoGetImport}} git {{.RepoCloneLink.HTTPS}}">
|
||||
<meta name="go-source" content="{{.GoGetImport}} _ {{.GoDocDirectory}} {{.GoDocFile}}">
|
||||
{{end}}
|
||||
{{if and .EnableFeed .FeedURL}}
|
||||
<link rel="alternate" type="application/atom+xml" title="" href="{{.FeedURL}}.atom">
|
||||
<link rel="alternate" type="application/rss+xml" title="" href="{{.FeedURL}}.rss">
|
||||
{{end}}
|
||||
<link rel="icon" href="{{AssetUrlPrefix}}/img/favicon.svg" type="image/svg+xml">
|
||||
<link rel="alternate icon" href="{{AssetUrlPrefix}}/img/favicon.png" type="image/png">
|
||||
{{template "base/head_script" .}}
|
||||
<noscript>
|
||||
<style>
|
||||
.dropdown:hover > .menu { display: block; }
|
||||
.ui.secondary.menu .dropdown.item > .menu { margin-top: 0; }
|
||||
</style>
|
||||
</noscript>
|
||||
{{template "base/head_opengraph" .}}
|
||||
{{template "base/head_style" .}}
|
||||
</head>
|
||||
|
||||
<body hx-headers='{"x-csrf-token": "{{.CsrfToken}}"}' hx-swap="outerHTML" hx-ext="morph" hx-push-url="false">
|
||||
|
||||
<div class="full height">
|
||||
<noscript>{{ctx.Locale.Tr "enable_javascript"}}</noscript>
|
||||
|
||||
{{template "custom/body_inner_pre" .}}
|
||||
|
||||
{{$notificationUnreadCount := 0}}
|
||||
{{if and .IsSigned .NotificationUnreadCount}}
|
||||
{{$notificationUnreadCount = call .NotificationUnreadCount}}
|
||||
{{end}}
|
||||
|
||||
<nav id="navbar" aria-label="{{ctx.Locale.Tr "aria.navbar"}}">
|
||||
<div class="navbar-left">
|
||||
<!-- the logo -->
|
||||
<a class="item" id="navbar-logo" href="{{AppSubUrl}}/" aria-label="{{if .IsSigned}}{{ctx.Locale.Tr "dashboard"}}{{else}}{{ctx.Locale.Tr "home"}}{{end}}">
|
||||
<img width="auto" height="30" src="{{AssetUrlPrefix}}/img/logo.svg" alt="{{ctx.Locale.Tr "logo"}}" aria-hidden="true">
|
||||
</a>
|
||||
|
||||
<!-- mobile right menu, it must be here because in mobile view, each item is a flex column, the first item is a full row column -->
|
||||
<div class="ui secondary menu item navbar-mobile-right only-mobile">
|
||||
<!-- {{if and .IsSigned EnableTimetracking .ActiveStopwatch}}-->
|
||||
<!-- <a id="mobile-stopwatch-icon" class="active-stopwatch item tw-mx-0" href="{{.ActiveStopwatch.IssueLink}}" title="{{ctx.Locale.Tr "active_stopwatch"}}" data-seconds="{{.ActiveStopwatch.Seconds}}">-->
|
||||
<!-- <div class="tw-relative">-->
|
||||
<!-- {{svg "octicon-stopwatch"}}-->
|
||||
<!-- <span class="header-stopwatch-dot"></span>-->
|
||||
<!-- </div>-->
|
||||
<!-- </a>-->
|
||||
<!-- {{end}}-->
|
||||
{{if .IsSigned}}
|
||||
<a id="mobile-notifications-icon" class="item tw-w-auto tw-p-2" href="{{AppSubUrl}}/notifications" data-tooltip-content="{{ctx.Locale.Tr "notifications"}}" aria-label="{{ctx.Locale.Tr "notifications"}}">
|
||||
<div class="tw-relative">
|
||||
{{svg "octicon-bell"}}
|
||||
<span class="notification_count{{if not $notificationUnreadCount}} tw-hidden{{end}}">{{$notificationUnreadCount}}</span>
|
||||
</div>
|
||||
</a>
|
||||
{{end}}
|
||||
<button class="item tw-w-auto ui icon mini button tw-p-2 tw-m-0" id="navbar-expand-toggle" aria-label="{{ctx.Locale.Tr "home.nav_menu"}}">{{svg "octicon-three-bars"}}</button>
|
||||
</div>
|
||||
|
||||
<!-- navbar links non-mobile -->
|
||||
{{if and .IsSigned .MustChangePassword}}
|
||||
{{/* No links */}}
|
||||
{{else if .IsSigned}}
|
||||
<!-- {{if not ctx.Consts.RepoUnitTypeIssues.UnitGlobalDisabled}}-->
|
||||
<!-- <a class="item{{if .PageIsIssues}} active{{end}}" href="{{AppSubUrl}}/issues">{{ctx.Locale.Tr "issues"}}</a>-->
|
||||
<!-- {{end}}-->
|
||||
<!-- {{if not ctx.Consts.RepoUnitTypePullRequests.UnitGlobalDisabled}}-->
|
||||
<!-- <a class="item{{if .PageIsPulls}} active{{end}}" href="{{AppSubUrl}}/pulls">{{ctx.Locale.Tr "pull_requests"}}</a>-->
|
||||
<!-- {{end}}-->
|
||||
<!-- {{if not (and ctx.Consts.RepoUnitTypeIssues.UnitGlobalDisabled ctx.Consts.RepoUnitTypePullRequests.UnitGlobalDisabled)}}-->
|
||||
<!-- {{if .ShowMilestonesDashboardPage}}-->
|
||||
<!-- <a class="item{{if .PageIsMilestonesDashboard}} active{{end}}" href="{{AppSubUrl}}/milestones">{{ctx.Locale.Tr "milestones"}}</a>-->
|
||||
<!-- {{end}}-->
|
||||
<!-- {{end}}-->
|
||||
|
||||
<!-- <a class="item{{if .PageIsExplore}} active{{end}}" href="{{AppSubUrl}}/explore/repos">{{ctx.Locale.Tr "explore"}}</a>-->
|
||||
{{else if .IsLandingPageOrganizations}}
|
||||
<!-- <a class="item{{if .PageIsExplore}} active{{end}}" href="{{AppSubUrl}}/explore/organizations">{{ctx.Locale.Tr "explore"}}</a>-->
|
||||
{{else}}
|
||||
<!-- <a class="item{{if .PageIsExplore}} active{{end}}" href="{{AppSubUrl}}/explore/repos">{{ctx.Locale.Tr "explore"}}</a>-->
|
||||
{{end}}
|
||||
|
||||
{{template "custom/extra_links" .}}
|
||||
|
||||
{{if not .IsSigned}}
|
||||
<!-- <a class="item" target="_blank" rel="noopener noreferrer" href="https://docs.gitea.com">{{ctx.Locale.Tr "help"}}</a>-->
|
||||
{{end}}
|
||||
|
||||
</div>
|
||||
|
||||
<!-- the full dropdown menus -->
|
||||
<div class="navbar-right">
|
||||
|
||||
<a id="signup-link" class="item active" href="{{AppSubUrl}}/user/sign_up">
|
||||
{{svg "octicon-person"}} {{ctx.Locale.Tr "register"}}
|
||||
</a>
|
||||
<a id="signin-link" class="item active" rel="nofollow" href="javascript:openLoginModal()">
|
||||
{{svg "octicon-sign-in"}} {{ctx.Locale.Tr "sign_in"}}
|
||||
</a>
|
||||
<a id="logout-link" class="item active" href="javascript:logout()">
|
||||
{{svg "octicon-sign-out"}}
|
||||
{{ctx.Locale.Tr "sign_out"}}
|
||||
</a>
|
||||
|
||||
{{if and .IsSigned .MustChangePassword}}
|
||||
{{else if .IsSigned}}
|
||||
{{else}}
|
||||
{{end}}
|
||||
</div><!-- end full right menu -->
|
||||
|
||||
</nav>
|
||||
|
||||
|
||||
<div role="main" aria-label="{{if .IsSigned}}{{ctx.Locale.Tr "dashboard"}}{{else}}{{ctx.Locale.Tr "home"}}{{end}}" class="page-content home">
|
||||
<div class="tw-mb-8 tw-px-8">
|
||||
<div class="center">
|
||||
<img class="logo" width="220" height="220" src="{{AssetUrlPrefix}}/img/logo.svg" alt="{{ctx.Locale.Tr "logo"}}">
|
||||
<div class="hero">
|
||||
<h1 class="ui icon header title">
|
||||
{{AppName}}
|
||||
</h1>
|
||||
<h2>{{ctx.Locale.Tr "startpage.app_desc"}}</h2>
|
||||
<!--<h1>DevStar Home</h1>-->
|
||||
<div class="ui middle very relaxed page">
|
||||
|
||||
<!-- login -->
|
||||
<div id="loginModal" class="ui modal" style=" width:50%; left: 25%; top: 20%">
|
||||
<div class="close right" onclick="closeLoginModal()">X</div>
|
||||
<div class="content">
|
||||
<div class="ui container column fluid">
|
||||
<h2 class="ui top attached header center">Login</h2>
|
||||
<div class="ui attached segment">
|
||||
<form id="loginForm" class="ui form" action="javascript:login()">
|
||||
<div class="required field">
|
||||
<label for="username">Username</label>
|
||||
<input type="text" id="username" name="username" required>
|
||||
</div>
|
||||
<div class="required field">
|
||||
<label for="password">Password</label>
|
||||
<input type="password" id="password" name="password" required>
|
||||
</div>
|
||||
<button class="ui button primary center">Login</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="ui stackable middle very relaxed page grid">
|
||||
<div class="eight wide center column">
|
||||
<h1 class="hero ui icon header">
|
||||
{{svg "octicon-flame"}} {{ctx.Locale.Tr "startpage.install"}}
|
||||
</h1>
|
||||
<p class="large">
|
||||
{{ctx.Locale.Tr "startpage.install_desc"}}
|
||||
</p>
|
||||
|
||||
|
||||
<div class="column">
|
||||
<div class="ui container">
|
||||
<button class="ui button primary" onclick="openModalForCreateRepository()">Create New Repository</button>
|
||||
</div>
|
||||
<div class="divider"></div>
|
||||
<div class="ui container">
|
||||
<h2 class="header">Repositories</h2>
|
||||
<table class="ui table" id="reposTable">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>repoName</th>
|
||||
<th>repoURL</th>
|
||||
<th>Operation</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<!-- 动态加载 -->
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
<div class="eight wide center column">
|
||||
<h1 class="hero ui icon header">
|
||||
{{svg "octicon-device-desktop"}} {{ctx.Locale.Tr "startpage.platform"}}
|
||||
</h1>
|
||||
<p class="large">
|
||||
{{ctx.Locale.Tr "startpage.platform_desc"}}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="ui stackable middle very relaxed page grid">
|
||||
<div class="eight wide center column">
|
||||
<h1 class="hero ui icon header">
|
||||
{{svg "octicon-rocket"}} {{ctx.Locale.Tr "startpage.lightweight"}}
|
||||
</h1>
|
||||
<p class="large">
|
||||
{{ctx.Locale.Tr "startpage.lightweight_desc"}}
|
||||
</p>
|
||||
</div>
|
||||
<div class="eight wide center column">
|
||||
<h1 class="hero ui icon header">
|
||||
{{svg "octicon-code"}} {{ctx.Locale.Tr "startpage.license"}}
|
||||
</h1>
|
||||
<p class="large">
|
||||
{{ctx.Locale.Tr "startpage.license_desc"}}
|
||||
</p>
|
||||
|
||||
<div id="modalForCreatingRepo" class="ui modal">
|
||||
|
||||
<!-- <div class="modal-content">-->
|
||||
<!-- <span class="close" onclick="closeModalForCreatingRepo()">×</span>-->
|
||||
<!-- <h2>Create New Project</h2>-->
|
||||
<!-- <form class="ui form" action="javascript:">-->
|
||||
<!-- <!– project settings –>-->
|
||||
<!-- <label class="required" for="projectName">Name</label>-->
|
||||
<!-- <input type="text" id="projectName" name="projectName"><br><br>-->
|
||||
<!-- <label for="projectDesc">Description</label>-->
|
||||
<!-- <textarea id="projectDesc" name="projectDesc" placeholder="(Optional)"></textarea><br><br>-->
|
||||
<!-- <label for="repoURL">Repo URL</label>-->
|
||||
<!-- <input type="text" id="repoURL" name="repoURL"-->
|
||||
<!-- placeholder="(Optional) Project repository you want to clone"><br><br>-->
|
||||
<!-- <hr>-->
|
||||
<!-- <!– repo settings–>-->
|
||||
<!-- <label for="template">-->
|
||||
<!-- As template?-->
|
||||
<!-- <input type="checkbox" id="template">-->
|
||||
<!-- </label>-->
|
||||
|
||||
<!-- <button type="button" onclick="submitRepo()">Create</button>-->
|
||||
<!-- </form>-->
|
||||
|
||||
</div>
|
||||
|
||||
<div id="alertBox" class="ui alert"></div>
|
||||
|
||||
<!-- ====================== Created Repository ==========================-->
|
||||
|
||||
<!-- <button onclick="loadRepositories()">Load Repositories</button>-->
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
|
||||
<!-- ======================================= Script ==================================-->
|
||||
<script>
|
||||
// ===================================== Initialization ===========================
|
||||
// Global variables
|
||||
DEVSTAR_HOME = "https://devstar.cn"
|
||||
var USERTOKEN = null
|
||||
var USERNAME = null
|
||||
var SIGNED = false
|
||||
|
||||
window.onload = async function () {
|
||||
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;
|
||||
loadPageModules()
|
||||
}
|
||||
|
||||
function loadPageModules() {
|
||||
if (SIGNED) {
|
||||
// head
|
||||
document.getElementById('signup-link').style.display = 'none'
|
||||
document.getElementById('signin-link').style.display = 'none'
|
||||
document.getElementById('logout-link').style.display = 'flex'
|
||||
// repo
|
||||
loadRepositories()
|
||||
} else {
|
||||
document.getElementById('signup-link').style.display = 'flex'
|
||||
document.getElementById('signin-link').style.display = 'flex'
|
||||
document.getElementById('logout-link').style.display = 'none'
|
||||
// repo
|
||||
loadRepositories()
|
||||
}
|
||||
}
|
||||
|
||||
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_HOME + '/api/devcontainer/user', {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Authorization': 'token ' + token
|
||||
},
|
||||
})
|
||||
.then(response => {
|
||||
if (response.ok) {
|
||||
resolve(response.status)
|
||||
} else {
|
||||
reject(new Error("Error code", response.status))
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
// ===================================== login and logout ===========================
|
||||
// login model
|
||||
function openLoginModal() {
|
||||
// check if user has logged in, only show login modal when user has not logged in
|
||||
if (SIGNED) {
|
||||
console.log('User has logged in')
|
||||
showAlert('已登录', 3000) // 消息显示3秒后消失
|
||||
} 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_HOME + `/api/v1/users/${username}/tokens`;
|
||||
|
||||
// Base64编码用户名和密码
|
||||
const base64Credentials = btoa(username + ':' + password);
|
||||
|
||||
const tokenName = generateTokenName(10);
|
||||
postData = {
|
||||
"name": tokenName,
|
||||
"scopes": ["write:user", "write:repository"]
|
||||
}
|
||||
|
||||
fetch(url, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Authorization': 'Basic ' + base64Credentials
|
||||
},
|
||||
body: JSON.stringify(postData)
|
||||
})
|
||||
.then(response => {
|
||||
if (!response.ok) {
|
||||
throw new Error('Error in logging ' + response.statusText);
|
||||
closeLoginModal()
|
||||
}
|
||||
return response.json();
|
||||
})
|
||||
.then(async data => {
|
||||
// store token in global variable and vscode global state
|
||||
USERTOKEN = data.sha1;
|
||||
USERNAME = username;
|
||||
setUserTokenToVSCode(USERTOKEN);
|
||||
setUsernameToVSCode(username);
|
||||
SIGNED = true
|
||||
loadPageModules()
|
||||
|
||||
// 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;
|
||||
|
||||
const keyTitle = `${USERNAME}-${machineName}`
|
||||
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)
|
||||
})
|
||||
|
||||
closeLoginModal()
|
||||
})
|
||||
.catch(error => {
|
||||
closeLoginModal()
|
||||
console.error('There has been a problem when logging', error);
|
||||
});
|
||||
}
|
||||
|
||||
function generateTokenName(length = 10) {
|
||||
// tokenName is random string and number and _ combination (10 characters)
|
||||
const characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789_';
|
||||
let name = '';
|
||||
const charactersLength = characters.length;
|
||||
for (let i = 0; i < length; i++) {
|
||||
name += characters.charAt(Math.floor(Math.random() * charactersLength));
|
||||
}
|
||||
return name
|
||||
}
|
||||
|
||||
async function logout() {
|
||||
// remove token and username from global variable and vscode global state
|
||||
USERTOKEN = null
|
||||
USERNAME = null
|
||||
await setUserTokenToVSCode('')
|
||||
await setUsernameToVSCode('')
|
||||
SIGNED = false
|
||||
|
||||
loadPageModules()
|
||||
|
||||
// location.reload()
|
||||
}
|
||||
|
||||
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_HOME + `/api/v1/user/keys`;
|
||||
|
||||
fetch(uploadUrl, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Authorization': 'token ' + USERTOKEN
|
||||
},
|
||||
body: JSON.stringify(postData)
|
||||
})
|
||||
.then(response => {
|
||||
if (!response.ok) {
|
||||
throw new Error('Error in logging ' + response.statusText);
|
||||
}
|
||||
return response.json();
|
||||
})
|
||||
.then(async data => {
|
||||
console.log("Successfully upload new created public key.\n", data)
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('There has been a problem with your fetch operation:', error);
|
||||
});
|
||||
}
|
||||
|
||||
// ===================================== Repo ===========================
|
||||
function loadRepositories() {
|
||||
// clear old data
|
||||
const tableBody = document.getElementById('reposTable').getElementsByTagName('tbody')[0];
|
||||
tableBody.innerHTML = '';
|
||||
|
||||
// load new data
|
||||
var url = DEVSTAR_HOME + "/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 => {
|
||||
if (!response.ok) {
|
||||
throw new Error('Network response was not ok when loading repos' + response.statusText);
|
||||
}
|
||||
return response.json();
|
||||
})
|
||||
.then(data => {
|
||||
var repos = data;
|
||||
repos.forEach((repo, index) => {
|
||||
var row = tableBody.insertRow(-1); // 在表格末尾插入一行
|
||||
var cell1 = row.insertCell(0);
|
||||
var cell2 = row.insertCell(1);
|
||||
var cell3 = row.insertCell(2);
|
||||
|
||||
const repoFullName = repo.full_name;
|
||||
const repoURL = repo.html_url;
|
||||
const repoID = repo.id;
|
||||
|
||||
cell1.textContent = repoFullName;
|
||||
cell2.textContent = repoURL;
|
||||
cell3.innerHTML = `<button class="ui button primary" onclick="openProject('${repoID}')">Open Project</button>
|
||||
<button class="ui red button" onclick="deleteDevContainer('${repoID}')">Delete Container</button>
|
||||
`;
|
||||
|
||||
});
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('There has been a problem with your fetch operation:', error);
|
||||
});
|
||||
}
|
||||
|
||||
// 打开弹窗
|
||||
function openModalForCreateRepository() {
|
||||
// make sure login first
|
||||
if (!USERTOKEN || !USERNAME) {
|
||||
showAlert('请先登录!', 3000)
|
||||
return
|
||||
}
|
||||
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 submitRepo() {
|
||||
// 这里添加实际的表单提交逻辑
|
||||
// 模拟表单处理
|
||||
var projectName = document.getElementById('projectName').value;
|
||||
var projectDesc = document.getElementById('projectDesc').value;
|
||||
var projectTemplate = document.getElementById('template').checked;
|
||||
|
||||
// check required fields
|
||||
if (projectName == '') {
|
||||
showAlert('请填写必要的项目信息!', 3000) // 消息显示3秒后消失
|
||||
} else {
|
||||
}
|
||||
|
||||
const url = DEVSTAR_HOME + "/api/v1/user/repos"
|
||||
var token = USERTOKEN
|
||||
|
||||
const postData = {
|
||||
"name": projectName,
|
||||
"description": projectDesc,
|
||||
"template": projectTemplate,
|
||||
}
|
||||
|
||||
fetch(url, {
|
||||
method: 'POST', // 或者 'POST', 根据API要求
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Authorization': 'token ' + token
|
||||
},
|
||||
body: JSON.stringify(postData)
|
||||
})
|
||||
.then(response => {
|
||||
console.log(response)
|
||||
if (!response.ok) {
|
||||
throw new Error('Network response was not ok when creating project' + response.statusText);
|
||||
}
|
||||
return response.json();
|
||||
})
|
||||
.then(data => {
|
||||
console.log(data);
|
||||
showAlert('项目创建成功!', 1500) // 消息显示1.5秒后消失
|
||||
loadRepositories()
|
||||
closeModalForCreatingRepo(); // 关闭创建项目弹窗
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('There has been a problem with your fetch operation:', error);
|
||||
});
|
||||
}
|
||||
|
||||
// ===================================== Projects ===========================
|
||||
|
||||
async function openProject(repoId) {
|
||||
var newCreated = false;
|
||||
// check if container exist
|
||||
await hasDevContainer(repoId)
|
||||
.then(async hasDevContainer => {
|
||||
if (!hasDevContainer) {
|
||||
showAlert("正在创建开发容器...", 1500)
|
||||
await createDevContainer(repoId)
|
||||
.then(res => {
|
||||
newCreated = true;
|
||||
showAlert("创建容器成功!", 1500)
|
||||
console.log(`Succeed to create dev container for repo ${repoId}`)
|
||||
})
|
||||
.catch(error => {
|
||||
showAlert("创建容器失败!", 1500)
|
||||
console.log(`Fail to create dev container for repo ${repoId}: `, error)
|
||||
return;
|
||||
})
|
||||
}
|
||||
}).catch(error => {
|
||||
console.log("There has a problem when check if the repo has devContainer:", error)
|
||||
return;
|
||||
})
|
||||
|
||||
console.log("opening project")
|
||||
|
||||
// open devcontainer through repoId
|
||||
var url = DEVSTAR_HOME + "/api/devcontainer"
|
||||
var token = USERTOKEN
|
||||
|
||||
const queryParams = new URLSearchParams({
|
||||
repoId: repoId,
|
||||
wait: true
|
||||
}).toString();
|
||||
const urlWithParams = `${url}?${queryParams}`;
|
||||
|
||||
fetch(urlWithParams, {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Authorization': 'token ' + token
|
||||
},
|
||||
})
|
||||
.then(response => {
|
||||
if (!response.ok) {
|
||||
throw new Error('Network response was not ok when querying devContainer by repoId' + response.statusText);
|
||||
}
|
||||
return response.json();
|
||||
})
|
||||
.then(data => {
|
||||
const responseCode = data.code
|
||||
const reponseMsg = data.msg
|
||||
|
||||
if (responseCode == 0) {
|
||||
// container start successfully
|
||||
// get devContainer ssh connection information
|
||||
const devContainerHost = data.data.devContainerHost
|
||||
const devContainerUsername = data.data.devContainerUsername
|
||||
const devContainerPort = data.data.devContainerPort
|
||||
const devContainerWorkDir = data.data.devContainerWorkDir
|
||||
|
||||
// default: open with key
|
||||
communicateVSCodeByWebview('firstOpenRemoteFolder', {
|
||||
host: `${devContainerHost}`,
|
||||
username: `${devContainerUsername}`,
|
||||
port: `${devContainerPort}`,
|
||||
path: `${devContainerWorkDir}`
|
||||
})
|
||||
} else {
|
||||
// show Error to User
|
||||
showAlert("打开容器失败!", 1500)
|
||||
const responseErrorMsg = data.data.ErrorMsg
|
||||
console.error("Error happen when starting dev container", responseErrorMsg)
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('There has been a problem with your fetch operation:', error);
|
||||
});
|
||||
}
|
||||
|
||||
async function hasDevContainer(repoId) {
|
||||
return new Promise((resolve, reject) => {
|
||||
url = DEVSTAR_HOME + "/api/devcontainer/user"
|
||||
token = USERTOKEN
|
||||
|
||||
fetch(url, {
|
||||
method: 'GET', // 或者 'POST', 根据API要求
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Authorization': 'token ' + token
|
||||
},
|
||||
})
|
||||
.then(response => {
|
||||
if (!response.ok) {
|
||||
throw new Error('Network response was not ok when querying devContainer list' + response.statusText);
|
||||
}
|
||||
return response.json();
|
||||
})
|
||||
.then(data => {
|
||||
const devContainers = data.data.devContainers;
|
||||
devContainers.forEach((c, index) => {
|
||||
if (c.repoId == repoId) {
|
||||
// has devContainer
|
||||
resolve(true)
|
||||
return
|
||||
}
|
||||
});
|
||||
resolve(false)
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('There has been a problem with your fetch operation:', error);
|
||||
reject(error)
|
||||
});
|
||||
})
|
||||
}
|
||||
|
||||
async function createDevContainer(repoId) {
|
||||
return new Promise(async (resolve, reject) => {
|
||||
// request creating container
|
||||
const url = DEVSTAR_HOME + "/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 => {
|
||||
if (!response.ok) {
|
||||
throw new Error(`Network response was not ok when creating devContainer ${repoId}` + response.statusText);
|
||||
}
|
||||
return response.json();
|
||||
})
|
||||
.then(data => {
|
||||
const responseCode = data.code
|
||||
|
||||
if (responseCode == 0) {
|
||||
resolve("success")
|
||||
} else {
|
||||
reject(data.data.ErrorMsg)
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('There has been a problem with your fetch operation:', error);
|
||||
reject(error)
|
||||
});
|
||||
|
||||
})
|
||||
}
|
||||
|
||||
async function getDefaultPublicKeyFromVSCode() {
|
||||
try {
|
||||
const data = await communicateVSCodeByWebview('getDefaultPublicKey', null);
|
||||
const defaultPublicKey = data.defaultPublicKey;
|
||||
|
||||
return defaultPublicKey;
|
||||
} catch (error) {
|
||||
console.log("Failed to get default public key: ", error)
|
||||
}
|
||||
}
|
||||
|
||||
function deleteDevContainer(repoId) {
|
||||
var url = DEVSTAR_HOME + "/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 => {
|
||||
if (!response.ok) {
|
||||
throw new Error('Network response was not ok when DELETEING devContainer by repoId' + response.statusText);
|
||||
}
|
||||
return response.json();
|
||||
})
|
||||
.then(data => {
|
||||
const respCode = data.code;
|
||||
if (0 == respCode) {
|
||||
console.log('Successfully delete dev container belong to repoId:', repoId, data)
|
||||
showAlert("删除容器成功!", 1500)
|
||||
} else {
|
||||
const errorMsg = data.data.ErrorMsg
|
||||
throw new Error(errorMsg)
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
showAlert(`删除容器失败\n${error}`, 3000)
|
||||
console.error(`Failed to delete dev container belong to repoId: ${repoId}\n`, 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 showAlert(alertText, duration) {
|
||||
document.getElementById('alertBox').innerHTML = alertText;
|
||||
document.getElementById('alertBox').style.display = 'block';
|
||||
setTimeout(function () {
|
||||
document.getElementById('alertBox').style.display = 'none';
|
||||
}, duration); // 消息显示 duration/1000 秒后消失
|
||||
}
|
||||
|
||||
async function 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 "base/footer" .}}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user