916 lines
		
	
	
		
			27 KiB
		
	
	
	
		
			HTML
		
	
	
	
	
	
			
		
		
	
	
			916 lines
		
	
	
		
			27 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>
 | 
						|
  <link rel="stylesheet" href="web_src/css/themes/theme-gitea-auto.css">
 | 
						|
  <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>
 | 
						|
      <div>
 | 
						|
        <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>
 | 
						|
  </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("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 onclick="openProject('${repoID}')">Open Project</button>
 | 
						|
                    <button onclick="deleteDevContainer('${repoID}')">Delete 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) {
 | 
						|
      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>
 | 
						|
</body>
 | 
						|
 | 
						|
</html> |