851 lines
		
	
	
		
			30 KiB
		
	
	
	
		
			HTML
		
	
	
	
	
	
			
		
		
	
	
			851 lines
		
	
	
		
			30 KiB
		
	
	
	
		
			HTML
		
	
	
	
	
	
<?php
 | 
						|
header("Access-Control-Allow-Origin: *");
 | 
						|
header("Access-Control-Allow-Headers: X-API-KEY, Origin, X-Requested-With, Content-Type, Accept, Access-Control-Request-Method");
 | 
						|
header("Access-Control-Allow-Methods: GET, POST, OPTIONS, PUT, DELETE");
 | 
						|
header("Allow: GET, POST, OPTIONS, PUT, DELETE");
 | 
						|
?>
 | 
						|
<!DOCTYPE html>
 | 
						|
<html lang="en">
 | 
						|
<head>
 | 
						|
    <meta charset="UTF-8">
 | 
						|
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
 | 
						|
    <title>Embedded page</title>
 | 
						|
    <style>
 | 
						|
        /* 弹窗基本样式 */
 | 
						|
        .modal {
 | 
						|
            display: none; /* 默认隐藏 */
 | 
						|
            position: fixed; /* 固定定位 */
 | 
						|
            left: 0;
 | 
						|
            top: 0;
 | 
						|
            width: 100%; /* 全屏宽 */
 | 
						|
            height: 100%; /* 全屏高 */
 | 
						|
            background-color: rgba(0,0,0,0.5); /* 半透明黑色背景 */
 | 
						|
            z-index: 1; /* 确保在顶部 */
 | 
						|
        }
 | 
						|
 | 
						|
        /* 弹窗内容框样式 */
 | 
						|
        .modal-content {
 | 
						|
            background-color: #fefefe;
 | 
						|
            margin: 15% auto; /* 15% 从顶部开始,自动水平居中 */
 | 
						|
            padding: 20px;
 | 
						|
            border: 1px solid #888;
 | 
						|
            width: 30%; /* 弹窗宽度 */
 | 
						|
        }
 | 
						|
 | 
						|
        /* 关闭按钮样式 */
 | 
						|
        .close {
 | 
						|
            color: #aaa;
 | 
						|
            float: right;
 | 
						|
            font-size: 28px;
 | 
						|
            font-weight: bold;
 | 
						|
        }
 | 
						|
 | 
						|
        .close:hover,
 | 
						|
        .close:focus {
 | 
						|
            color: black;
 | 
						|
            text-decoration: none;
 | 
						|
            cursor: pointer;
 | 
						|
        }
 | 
						|
 | 
						|
        .alert {
 | 
						|
            position: fixed;
 | 
						|
            top: 20px; /* 距离顶部20px */
 | 
						|
            left: 50%; /* 水平居中 */
 | 
						|
            transform: translateX(-50%); /* 水平居中调整 */
 | 
						|
            background-color: green;
 | 
						|
            color: white;
 | 
						|
            text-align: center;
 | 
						|
            padding: 10px;
 | 
						|
            border-radius: 5px;
 | 
						|
            display: none;
 | 
						|
            z-index: 2;
 | 
						|
            width: auto; /* 自动宽度 */
 | 
						|
            max-width: 60%; /* 最大宽度60% */
 | 
						|
        }
 | 
						|
        /* table样式 */
 | 
						|
        table {
 | 
						|
            width: 100%;
 | 
						|
            border-collapse: collapse;
 | 
						|
        }
 | 
						|
        th, td {
 | 
						|
            border: 1px solid black;
 | 
						|
            padding: 8px;
 | 
						|
            text-align: left;
 | 
						|
        }
 | 
						|
        .required::before {
 | 
						|
            content: "*";
 | 
						|
            color: red;
 | 
						|
        }
 | 
						|
        form {
 | 
						|
            display: flex;
 | 
						|
            flex-direction: column;
 | 
						|
            align-items: flex-start;
 | 
						|
        }
 | 
						|
 | 
						|
        label, input, textarea {
 | 
						|
            margin-bottom: 10px;
 | 
						|
        }
 | 
						|
 | 
						|
        input, textarea {
 | 
						|
            padding: 8px;
 | 
						|
            border: 1px solid #ccc;
 | 
						|
            border-radius: 4px;
 | 
						|
        }
 | 
						|
        .button-container {
 | 
						|
            display: flex;
 | 
						|
            /* justify-content: space-between; */
 | 
						|
        }
 | 
						|
        ton {
 | 
						|
            width: 100px;
 | 
						|
            height: 30px;
 | 
						|
            background-color: #4CAF50;
 | 
						|
            color: white;
 | 
						|
            border: none;
 | 
						|
            border-radius: 5px;
 | 
						|
            cursor: pointer;
 | 
						|
        }
 | 
						|
        button:hover {
 | 
						|
            background-color: #45a049;
 | 
						|
        }
 | 
						|
    </style>
 | 
						|
</head>
 | 
						|
 | 
						|
<body>
 | 
						|
    <h1>DevStar Home</h1>
 | 
						|
    <!-- login -->
 | 
						|
    <div id="loginModal" class="modal">
 | 
						|
        <div class="modal-content">
 | 
						|
            <span class="close" onclick="closeLoginModal()">×</span>
 | 
						|
            <h2>Login</h2>
 | 
						|
            <form id="loginForm">
 | 
						|
                <label for="username">Username:</label>
 | 
						|
                <input type="text" id="username" name="username" required><br><br>
 | 
						|
                <label for="password">Password:</label>
 | 
						|
                <input type="password" id="password" name="password" required><br><br>
 | 
						|
                <button type="button" onclick="login()">Login</button>
 | 
						|
            </form>
 | 
						|
        </div>
 | 
						|
    </div>
 | 
						|
 | 
						|
    <button onclick="openLoginModal()">Login</button>
 | 
						|
    <button onclick="logout()">Logout</button>
 | 
						|
    <!-- create new project -->
 | 
						|
    <!-- 触发弹窗的按钮 -->
 | 
						|
    <button onclick="openModal()">Create New Project</button>
 | 
						|
    <!-- 弹窗本体 -->
 | 
						|
    <div id="myModal" class="modal">
 | 
						|
        <div class="modal-content">
 | 
						|
            <span class="close" onclick="closeModal()">×</span>
 | 
						|
            <h2>Create New Project</h2>
 | 
						|
            <form>
 | 
						|
                <!-- project settings -->
 | 
						|
                <label class="required" for="projectName">Name</label>
 | 
						|
                <input type="text" id="projectName" name="projectName" ><br><br>
 | 
						|
                <label for="projectDesc">Description</label>
 | 
						|
                <textarea id="projectDesc" name="projectDesc" placeholder="(Optional)"></textarea><br><br>
 | 
						|
                <label for="repoURL">Repo URL</label>
 | 
						|
                <input type="text" id="repoURL" name="repoURL" placeholder="(Optional) Project repository you want to clone"><br><br>
 | 
						|
                <hr>
 | 
						|
                <!-- repo settings-->
 | 
						|
                <label for="template">
 | 
						|
                    As template?
 | 
						|
                    <input type="checkbox" id="template"> 
 | 
						|
                </label>
 | 
						|
 | 
						|
                <button type="button" onclick="submitRepo()">Create</button>
 | 
						|
            </form>
 | 
						|
 | 
						|
        </div>
 | 
						|
    </div>
 | 
						|
    <div id="alertBox" class="alert"></div>
 | 
						|
 | 
						|
     <!-- ====================== Created Repository ==========================-->
 | 
						|
     <h2>Created Repositories</h2>
 | 
						|
    <button onclick="loadRepositories()">Load Repositories</button>
 | 
						|
     <table id="reposTable">
 | 
						|
        <thead>
 | 
						|
            <tr>
 | 
						|
                <th>repoName</th>
 | 
						|
                <th>repoURL</th>
 | 
						|
                <th>Operation</th>
 | 
						|
            </tr>
 | 
						|
        </thead>
 | 
						|
        <tbody>
 | 
						|
        <!-- 动态加载 -->
 | 
						|
        </tbody>
 | 
						|
     </table>
 | 
						|
 | 
						|
     <!-- ====================== open created project ==========================-->
 | 
						|
     <!-- <h2>Created Project</h2>
 | 
						|
    <button onclick="loadProjects()">Load Projects</button>
 | 
						|
     <table id="projectsTable">
 | 
						|
        <thead>
 | 
						|
        <tr>
 | 
						|
            <th>devContainerName</th>
 | 
						|
            <th>devContainerWorkDir</th>
 | 
						|
            <th>Operation</th>
 | 
						|
        </tr>
 | 
						|
        </thead>
 | 
						|
        <tbody>
 | 
						|
        </tbody>
 | 
						|
     </table> -->
 | 
						|
 | 
						|
     <!-- ======================================= Script ==================================-->`
 | 
						|
    <script>
 | 
						|
        // ===================================== Initialization ===========================
 | 
						|
        // Global variables
 | 
						|
        DEVSTAR_HOME = "https://devstar.cn"
 | 
						|
        var USERTOKEN = null
 | 
						|
        var USERNAME = null
 | 
						|
        var REPOLIST = []
 | 
						|
        var PROJECTLIST = []
 | 
						|
 | 
						|
        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)
 | 
						|
            })
 | 
						|
 | 
						|
            if (USERTOKEN && USERNAME) {
 | 
						|
                loadPageModules()
 | 
						|
            } else {
 | 
						|
                // TODO : do nothing or remind user to login
 | 
						|
            }
 | 
						|
        }
 | 
						|
 | 
						|
        function loadPageModules() {
 | 
						|
            loadRepositories()
 | 
						|
            // loadProjects()
 | 
						|
        }
 | 
						|
 | 
						|
        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 (USERTOKEN) {
 | 
						|
                verifyToken(USERTOKEN)
 | 
						|
                .then(result => {
 | 
						|
                    console.log('User has logged in')
 | 
						|
                    showAlert('已登录', 3000) // 消息显示3秒后消失 
 | 
						|
                    return
 | 
						|
                })
 | 
						|
                .catch(error => {
 | 
						|
                    // need to login
 | 
						|
                    document.getElementById('loginModal').style.display = 'block';
 | 
						|
                })
 | 
						|
            } else {
 | 
						|
                document.getElementById('loginModal').style.display = 'block';
 | 
						|
            }
 | 
						|
        }
 | 
						|
 | 
						|
        function closeLoginModal() {
 | 
						|
            document.getElementById('loginModal').style.display = 'none';
 | 
						|
        }
 | 
						|
 | 
						|
        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);
 | 
						|
                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('')
 | 
						|
 | 
						|
            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(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 onclick="openProject('${repoID}')">Open Project</button>`; 
 | 
						|
 | 
						|
                });
 | 
						|
            })
 | 
						|
            .catch(error => {
 | 
						|
                console.error('There has been a problem with your fetch operation:', error);
 | 
						|
            });
 | 
						|
        }
 | 
						|
 | 
						|
        // 打开弹窗
 | 
						|
        function openModal() {
 | 
						|
            // make sure login first
 | 
						|
            if (!USERTOKEN || !USERNAME) {
 | 
						|
                showAlert('请先登录!', 3000)
 | 
						|
                return
 | 
						|
            }
 | 
						|
            document.getElementById('myModal').style.display = "block";
 | 
						|
        }
 | 
						|
        // 关闭弹窗
 | 
						|
        function closeModal() {
 | 
						|
            document.getElementById('myModal').style.display = "none";
 | 
						|
        }
 | 
						|
        // 点击窗外关闭弹窗
 | 
						|
        window.onclick = function(event) {
 | 
						|
            if (event.target == document.getElementById('myModal')) {
 | 
						|
                closeModal();
 | 
						|
            }
 | 
						|
        }
 | 
						|
 | 
						|
        function submitRepo() {
 | 
						|
            // 这里添加实际的表单提交逻辑
 | 
						|
            // 模拟表单处理
 | 
						|
            var projectName = document.getElementById('projectName').value;
 | 
						|
            var projectDesc = document.getElementById('projectDesc').value;
 | 
						|
            var projectTemplate = document.getElementById('template').checked;
 | 
						|
 | 
						|
            // check required fields
 | 
						|
            if (projectName == '') {
 | 
						|
                showAlert('请填写必要的项目信息!', 3000) // 消息显示3秒后消失
 | 
						|
            } else {
 | 
						|
            }
 | 
						|
 | 
						|
            const url = 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()
 | 
						|
                closeModal(); // 关闭创建项目弹窗
 | 
						|
            })
 | 
						|
            .catch(error => {
 | 
						|
                console.error('There has been a problem with your fetch operation:', error);
 | 
						|
            });
 | 
						|
        }
 | 
						|
 | 
						|
        // ===================================== Projects ===========================
 | 
						|
 | 
						|
        async function openProject(repoId) {
 | 
						|
            // TODO: check if container exist
 | 
						|
            await hasDevContainer(repoId)
 | 
						|
            .then(async hasDevContainer => {
 | 
						|
                if (!hasDevContainer) {
 | 
						|
                    showAlert("正在创建开发容器...", 1500)
 | 
						|
                    await createDevContainer(repoId)
 | 
						|
                    .then(res => {
 | 
						|
                        console.log(`Succeed to create dev container for repo ${repoId}`)
 | 
						|
                    })
 | 
						|
                    .catch(error => {
 | 
						|
                        showAlert("创建容器失败!", 1500)
 | 
						|
                        console.log(`Fail to create dev container for repo ${repoId}: `, error)
 | 
						|
                    })
 | 
						|
                }
 | 
						|
            }).catch(error => {
 | 
						|
                console.log("There has a problem when check if the repo has devContainer:", error)
 | 
						|
            })
 | 
						|
 | 
						|
            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 devContainerPassword = data.data.devContainerPassword
 | 
						|
                    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 {
 | 
						|
                    // TODO: show Error to User
 | 
						|
                    showAlert("打开容器失败!", 1500)
 | 
						|
                    const responseErrorMsg = data.data.ErrorMsg
 | 
						|
                    console.log("Error happen when starting dev container", responseErrorMsg)
 | 
						|
                }
 | 
						|
            })
 | 
						|
            .catch(error => {
 | 
						|
                console.error('There has been a problem with your fetch operation:', error);
 | 
						|
            });
 | 
						|
        }
 | 
						|
 | 
						|
        async function hasDevContainer(repoId) {
 | 
						|
            return new Promise((resolve, reject) => {
 | 
						|
                url = 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)
 | 
						|
                });
 | 
						|
            })
 | 
						|
        }
 | 
						|
 | 
						|
        // TODO: create container
 | 
						|
        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)
 | 
						|
            }
 | 
						|
        }
 | 
						|
 | 
						|
        // TODO: delete container
 | 
						|
        function deleteDevContainer(repoId) {
 | 
						|
            return new Promise((resolve, reject) => {
 | 
						|
 | 
						|
            })
 | 
						|
        }
 | 
						|
 | 
						|
        function firstOpenRemoteFolder(host, username, password, port, path) {
 | 
						|
            const message = {
 | 
						|
                action: 'firstOpenRemoteFolder',
 | 
						|
                host : host,
 | 
						|
                username : username,
 | 
						|
                password : password,
 | 
						|
                port : port,
 | 
						|
                path : path,
 | 
						|
            }
 | 
						|
            // 向iframe父页面发送消息
 | 
						|
            window.parent.postMessage(message, '*');
 | 
						|
        }
 | 
						|
 | 
						|
        function openRemoteFolder(host, path) {
 | 
						|
            const message = {
 | 
						|
                action: 'openRemoteFolder',
 | 
						|
                host : host,
 | 
						|
                path : path,
 | 
						|
            }
 | 
						|
            // 向iframe父页面发送消息
 | 
						|
            window.parent.postMessage(message, '*');
 | 
						|
        }
 | 
						|
 | 
						|
        // ===================================== Utils ===========================
 | 
						|
 | 
						|
        // 消息提示
 | 
						|
        function showAlert(alertText, duration) {
 | 
						|
            document.getElementById('alertBox').innerHTML = alertText;
 | 
						|
            document.getElementById('alertBox').style.display = 'block';
 | 
						|
            setTimeout(function() {
 | 
						|
                document.getElementById('alertBox').style.display = 'none';
 | 
						|
            }, duration); // 消息显示 duration/1000 秒后消失
 | 
						|
        }
 | 
						|
 | 
						|
        async function 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>
 | 
						|
</body>
 | 
						|
 | 
						|
</html> |