1.0.0
This commit is contained in:
commit
36b92f19e0
18
.babelrc
Normal file
18
.babelrc
Normal file
@ -0,0 +1,18 @@
|
||||
{
|
||||
"presets": [
|
||||
["@babel/preset-env", {
|
||||
"targets": {
|
||||
"node": "current"
|
||||
}
|
||||
}]
|
||||
],
|
||||
"plugins": [
|
||||
["@babel/plugin-proposal-decorators", {
|
||||
"legacy": true
|
||||
}],
|
||||
["@babel/plugin-proposal-class-properties", {
|
||||
"loose": true
|
||||
}],
|
||||
"@babel/plugin-proposal-optional-chaining"
|
||||
]
|
||||
}
|
15
.gitignore
vendored
Normal file
15
.gitignore
vendored
Normal file
@ -0,0 +1,15 @@
|
||||
.DS_Store
|
||||
node_modules
|
||||
dist/
|
||||
bin/
|
||||
npm-debug.log
|
||||
.idea/
|
||||
|
||||
# Thumbnails
|
||||
._*
|
||||
|
||||
# Files that might appear on external disk
|
||||
.Spotlight-V100
|
||||
.Trashes
|
||||
|
||||
yarn-error.log
|
22
Dockerfile
Normal file
22
Dockerfile
Normal file
@ -0,0 +1,22 @@
|
||||
FROM node:12.4.0-alpine
|
||||
LABEL AUTHOR="yi-ge"
|
||||
LABEL maintainer="a@wyr.me"
|
||||
|
||||
RUN apk add --no-cache \
|
||||
libstdc++ \
|
||||
libgcc \
|
||||
rsync \
|
||||
openssh-client \
|
||||
bash \
|
||||
ca-certificates \
|
||||
git
|
||||
|
||||
RUN mkdir /project
|
||||
|
||||
ADD . /project
|
||||
|
||||
WORKDIR /project
|
||||
|
||||
EXPOSE 80
|
||||
|
||||
CMD ["npm", "start"]
|
21
LICENSE
Normal file
21
LICENSE
Normal file
@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2019 Yige
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
176
README.md
Normal file
176
README.md
Normal file
@ -0,0 +1,176 @@
|
||||
# Electron Distribution
|
||||
|
||||
[](https://github.com/yi-ge/electron-distribution/blob/master/LICENSE)
|
||||
[](https://github.com/yi-ge/electron-distribution)
|
||||
|
||||
[](https://github.com/standard/standard)
|
||||
|
||||
[简体中文](README.zh-CN.md)
|
||||
|
||||
Electron build and auto update service, application distribution. Making application distribution easier.
|
||||
|
||||
A git repository corresponds to an electron app and an distributed system.
|
||||
|
||||
Electron Distribution server-side work in 64 bit Linux OS (required) and MacOS (optional), and build for x64 platfrom, other platfrom need to modify the code (It is easy).
|
||||
|
||||
## Quick Setup Guide
|
||||
|
||||
### In your Linux Server (x64)
|
||||
|
||||
#### Example
|
||||
|
||||
```bash
|
||||
docker run -itd --name electron-distribution --restart always \
|
||||
-e NAME=your-app-name \
|
||||
-e SCHEME=https \
|
||||
-e DOMAIN=www.example.com \
|
||||
-e TOKEN=123456 \
|
||||
-e REPOPATH=git@github.com:abc/def.git \
|
||||
-e BUILD_TYPE=win,linux,mac \
|
||||
-e WORKPATH=/data \
|
||||
-e OBJECT_STORAGE_TYPE=cos
|
||||
-v /data:/data \
|
||||
-p 80:80 \
|
||||
-v /var/run/docker.sock:/var/run/docker.sock \
|
||||
-v /root/.ssh:/root/.ssh \
|
||||
-v /data/icon.ico:/project/public/icon.ico \
|
||||
wy373226722/electron-distribution:latest
|
||||
```
|
||||
|
||||
China user: `docker pull registry.cn-shenzhen.aliyuncs.com/yi-ge/electron-distribution:latest` or `docker pull ccr.ccs.tencentyun.com/yi-ge/electron-distribution:latest`
|
||||
|
||||
You need to configure nginx and SSL by yourself. SCHEME only a tip.
|
||||
|
||||
#### Environment
|
||||
|
||||
| ENV Var | Default | Description |
|
||||
|---|---|---|
|
||||
| `NAME` | `"APP"` | `Your app name. Numbers, letters and "-".` |
|
||||
| `SCHEME` | `"https"` | `Production environment only work with SSL.` |
|
||||
| `DOMAIN` | `"www.example.com"` | `Your Electron Distribution server-side domain.` |
|
||||
| `TOKEN` | `"1jH27dJf9s852"` | `Your Electron Distribution API TOKEN.` |
|
||||
| `REPOPATH` | `"git@github.com:yi-ge/electron-distribution.git"` | `Your electron app repository.` |
|
||||
| `BUILD_TYPE` | `"win,linux"` | `win,linux,mac` |
|
||||
| `WORKPATH` | `"/data"` | `-v /data:/data, The two path must be consistent.` |
|
||||
| `DOCKER_SOCKET` | `"/var/run/docker.sock"` | `Docker socket path.` |
|
||||
| `MAC_SERVER_HOST` | `"127.0.0.1"` | `Your macOS server host.` |
|
||||
| `MAC_SERVER_PORT` | `"22"` | `Your macOS server ssh port.` |
|
||||
| `MAC_SERVER_USERNAME` | `"guest"` | `Your macOS server ssh username.` |
|
||||
| `LINUX_SERVER_HOST` | `"127.0.0.1"` | `Only require build mac application. Your linux server host.` |
|
||||
| `LINUX_SERVER_PORT` | `"22"` | `Only require build mac application. Your linux server ssh port.` |
|
||||
| `LINUX_SERVER_USERNAME` | `"guest"` | `Only require build mac application. Your linux server ssh username.` |
|
||||
| `GH_TOKEN` | `""` | `If you set publish option.` |
|
||||
| `CSC_LINK` | `""` | `https://www.electron.build/code-signing` |
|
||||
| `CSC_KEY_PASSWORD` | `""` | `https://www.electron.build/code-signing` |
|
||||
| `CSC_NAME`, | `""` | `https://www.electron.build/code-signing` |
|
||||
| `BUILD_CPU_LIMIT` | `"0"` | `Linux and Windows build cpu limit. CPUs in which to allow execution (e.g., 0-3, 0,1)` |
|
||||
| `BUILD_MEMORY_LIMIT` | `0` | `Linux and Windows memory limit in bytes. 1024 * 1024 * 1024 bytes = 1073741824 bytes = 1GB` |
|
||||
| `OBJECT_STORAGE_TYPE` | `"cos"` | `cos: Tencent Cloud Object Storage; oss: Aliyun Object Storage; qiniu: Qiniu Object Storage.` |
|
||||
| `QINIU_ACCESS_KEY` | `""` | `Qiniu Object Storage, accessKey.` |
|
||||
| `QINIU_SECRET_KEY` | `""` | `Qiniu Object Storage, secretKey.` |
|
||||
| `QINIU_BUCKET_KEY` | `""` | `Qiniu Object Storage, bucket.` |
|
||||
| `QINIU_ZONE` | `"Zone_z0"` | `华东 Zone_z0、华北 Zone_z1、华南 Zone_z2、北美 Zone_na0` |
|
||||
| `QINIU_URL` | `"https://cdn.xxx.com"` | `Qiniu Object Storage CDN url.` |
|
||||
| `OSS_ACCESS_KEY_ID` | `"id"` | `Aliyun accessKeyId.` |
|
||||
| `OSS_ACCESS_SECRET` | `"secret"` | `Aliyun accessKeySecret.` |
|
||||
| `OSS_REGION` | `"oss-cn-qingdao"` | `Aliyun Object Storage, Region.` |
|
||||
| `OSS_BUCKET` | `"bucket"` | `Aliyun Object Storage, Bucket.` |
|
||||
| `OSS_URL` | `"https://cdn.xxx.com"` | `Aliyun Object Storage CDN url.` |
|
||||
| `OSS_INTERNAL` | `false` | `Access aliyun OSS with aliyun internal network or not, default is false. If your servers are running on aliyun too, you can set "true" to save lot of money.` |
|
||||
| `COS_SECRE_ID` | `""` | `Tencent Cloud Object Storage SecretId.` |
|
||||
| `COS_SECRE_KEY` | `""` | `SecretKey.` |
|
||||
| `COS_BUCKET` | `"bucketname-12345678"` | `Bucket.` |
|
||||
| `COS_REGION` | `"ap-chengdu"` | `Region.` |
|
||||
| `COS_URL` | `"https://cdn.xxx.com"` | `Object Storage CDN url.` |
|
||||
|
||||
Qiniu Object Storage: [https://developer.qiniu.com/kodo/sdk/1289/nodejs](https://developer.qiniu.com/kodo/sdk/1289/nodejs)
|
||||
Aliyun Object Storage: [https://github.com/ali-sdk/ali-oss](https://github.com/ali-sdk/ali-oss)
|
||||
Tencent Cloud Object Storage: [https://github.com/tencentyun/cos-nodejs-sdk-v5](https://github.com/tencentyun/cos-nodejs-sdk-v5)
|
||||
|
||||
#### API Document
|
||||
|
||||
**Swagger:** https://yourdomain/documentation
|
||||
|
||||
API token require `SHA-512` encrypt.
|
||||
|
||||
**Github webhooks:** https://yourdomain/build/webhooks
|
||||
|
||||
Content type: `application/json`
|
||||
Secret: `your Token`
|
||||
|
||||
### In your Electron App
|
||||
|
||||
```bash
|
||||
yarn add electron-builder electron-simple-updater -D
|
||||
```
|
||||
|
||||
More about: [electron-builder](https://github.com/electron-userland/electron-builder) [electron-simple-updater](https://github.com/megahertz/electron-simple-updater)
|
||||
|
||||
Insert `build` configuration in your `package.json` ([https://www.electron.build](https://www.electron.build)):
|
||||
|
||||
```json
|
||||
"scripts": {
|
||||
"build": "node config/build.js && electron-builder",
|
||||
"build:dir": "node config/build.js && electron-builder --dir",
|
||||
...
|
||||
},
|
||||
"build": {
|
||||
"productName": "Your App Name",
|
||||
"appId": "com.appid.abc",
|
||||
"directories": {
|
||||
"output": "build"
|
||||
},
|
||||
"files": [
|
||||
"dist/electron/**/*"
|
||||
],
|
||||
"dmg": {
|
||||
"contents": [
|
||||
{
|
||||
"x": 410,
|
||||
"y": 150,
|
||||
"type": "link",
|
||||
"path": "/Applications"
|
||||
},
|
||||
{
|
||||
"x": 130,
|
||||
"y": 150,
|
||||
"type": "file"
|
||||
}
|
||||
]
|
||||
},
|
||||
"mac": {
|
||||
"icon": "build/icons/icon.icns"
|
||||
},
|
||||
"win": {
|
||||
"icon": "build/icons/icon.ico",
|
||||
"target": "squirrel"
|
||||
},
|
||||
"linux": {
|
||||
"artifactName": "${productName}-${version}-${arch}.${ext}",
|
||||
"icon": "build/icons"
|
||||
},
|
||||
"squirrelWindows": {
|
||||
"iconUrl": "https://yourServer/app/icon.ico"
|
||||
}
|
||||
},
|
||||
```
|
||||
|
||||
## Mac APP Build And Code Signing
|
||||
|
||||
Install `Xcode 10.2` \ `brew (yarn 1.15.2)` \ `nvm (node 11.13.0)` in the macOS Majave (10.14.4), run the Xcode at least once.
|
||||
|
||||
Git and rsync are installed by default.
|
||||
|
||||
Start sshd:
|
||||
|
||||
```bash
|
||||
sudo launchctl load -w /System/Library/LaunchDaemons/ssh.plist
|
||||
```
|
||||
|
||||
Adding your server public key to mac `~/.ssh/authorized_keys`.
|
||||
Adding your mac public key to server `~/.ssh/authorized_keys`.
|
||||
|
||||
Run the `ssh macName@macIp` in the linux server at least once .
|
||||
Run the `ssh linux@linuxIp` in the mac server at least once.
|
||||
|
||||
Install `rsync` and enable sshd in your Linux Server.
|
173
README.zh-CN.md
Normal file
173
README.zh-CN.md
Normal file
@ -0,0 +1,173 @@
|
||||
# Electron 应用分发系统
|
||||
|
||||
[](https://github.com/yi-ge/electron-distribution/blob/master/LICENSE)
|
||||
[](https://github.com/yi-ge/electron-distribution)
|
||||
|
||||
[](https://github.com/standard/standard)
|
||||
|
||||
[简体中文](README.zh-CN.md)
|
||||
|
||||
`Electron 应用分发系统`提供应用程序编译、自动升级、分发服务。让`Electron`应用分发变得非常容易。
|
||||
|
||||
一个`git`仓库对应一个`Electron`应用程序,对应使用一套应用分发系统。
|
||||
|
||||
`Electron 应用分发系统`服务器端工作在64位Linux操作系统(必须)和MacOS(可选),默认编译x64应用程序, 其它平台及架构需要修改相应代码(不过这很容易实现).
|
||||
|
||||
## 快速开始
|
||||
|
||||
### Linux Server (x64) 服务器部署指南
|
||||
|
||||
#### 命令运行例子
|
||||
|
||||
```bash
|
||||
docker run -itd --name electron-distribution --restart always \
|
||||
-e NAME=your-app-name \
|
||||
-e SCHEME=https \
|
||||
-e DOMAIN=www.example.com \
|
||||
-e TOKEN=123456 \
|
||||
-e REPOPATH=git@github.com:abc/def.git \
|
||||
-e BUILD_TYPE=win,linux,mac \
|
||||
-e WORKPATH=/data \
|
||||
-e OBJECT_STORAGE_TYPE=cos
|
||||
-v /data:/data \
|
||||
-p 80:80 \
|
||||
-v /var/run/docker.sock:/var/run/docker.sock \
|
||||
-v /root/.ssh:/root/.ssh \
|
||||
-v /data/icon.ico:/project/public/icon.ico \
|
||||
wy373226722/electron-distribution
|
||||
```
|
||||
|
||||
国内用户: `docker pull registry.cn-shenzhen.aliyuncs.com/yi-ge/electron-distribution:latest` or `docker pull ccr.ccs.tencentyun.com/yi-ge/electron-distribution:latest`
|
||||
|
||||
你需要自行安装Nginx并配置使其支持SSL,这是必须的,否则应用程序自动更新可能会失效。`SCHEME`只是一个标识。
|
||||
|
||||
#### 环境变量
|
||||
|
||||
| 变量名 | 默认值 | 描述 |
|
||||
|---|---|---|
|
||||
| `NAME` | `"APP"` | `Your app name. Numbers, letters and "-".` |
|
||||
| `SCHEME` | `"https"` | `Production environment only work with SSL.` |
|
||||
| `DOMAIN` | `"www.example.com"` | `Your Electron Distribution server-side domain.` |
|
||||
| `TOKEN` | `"1jH27dJf9s852"` | `Your Electron Distribution API TOKEN.` |
|
||||
| `REPOPATH` | `"git@github.com:yi-ge/electron-distribution.git"` | `Your electron app repository.` |
|
||||
| `BUILD_TYPE` | `"win,linux"` | `win,linux,mac` |
|
||||
| `WORKPATH` | `"/data"` | `-v /data:/data, The two path must be consistent.` |
|
||||
| `DOCKER_SOCKET` | `"/var/run/docker.sock"` | `Docker socket path.` |
|
||||
| `MAC_SERVER_HOST` | `"127.0.0.1"` | `Your macOS server host.` |
|
||||
| `MAC_SERVER_PORT` | `"22"` | `Your macOS server ssh port.` |
|
||||
| `MAC_SERVER_USERNAME` | `"guest"` | `Your macOS server ssh username.` |
|
||||
| `LINUX_SERVER_HOST` | `"127.0.0.1"` | `Only require build mac application. Your linux server host.` |
|
||||
| `LINUX_SERVER_PORT` | `"22"` | `Only require build mac application. Your linux server ssh port.` |
|
||||
| `LINUX_SERVER_USERNAME` | `"root"` | `Only require build mac application. Your linux server ssh username.` |
|
||||
| `GH_TOKEN` | `""` | `If you set publish option.` |
|
||||
| `CSC_LINK` | `""` | `https://www.electron.build/code-signing` |
|
||||
| `CSC_KEY_PASSWORD` | `""` | `https://www.electron.build/code-signing` |
|
||||
| `CSC_NAME`, | `""` | `https://www.electron.build/code-signing` |
|
||||
| `BUILD_CPU_LIMIT` | `"0"` | `Linux and Windows build cpu limit. CPUs in which to allow execution (e.g., 0-3, 0,1)` |
|
||||
| `BUILD_MEMORY_LIMIT` | `0` | `Linux and Windows memory limit in bytes. 1024 * 1024 * 1024 bytes = 1073741824 bytes = 1GB` |
|
||||
| `OBJECT_STORAGE_TYPE` | `"cos"` | `cos: Tencent Cloud Object Storage; oss: Aliyun Object Storage; qiniu: Qiniu Object Storage.` |
|
||||
| `QINIU_ACCESS_KEY` | `""` | `Qiniu Object Storage, accessKey.` |
|
||||
| `QINIU_SECRET_KEY` | `""` | `Qiniu Object Storage, secretKey.` |
|
||||
| `QINIU_BUCKET_KEY` | `""` | `Qiniu Object Storage, bucket.` |
|
||||
| `QINIU_ZONE` | `"Zone_z0"` | `华东 Zone_z0、华北 Zone_z1、华南 Zone_z2、北美 Zone_na0` |
|
||||
| `QINIU_URL` | `"https://cdn.xxx.com"` | `Qiniu Object Storage CDN url.` |
|
||||
| `OSS_ACCESS_KEY_ID` | `"id"` | `Aliyun accessKeyId.` |
|
||||
| `OSS_ACCESS_SECRET` | `"secret"` | `Aliyun accessKeySecret.` |
|
||||
| `OSS_REGION` | `"oss-cn-qingdao"` | `Aliyun Object Storage, Region.` |
|
||||
| `OSS_BUCKET` | `"bucket"` | `Aliyun Object Storage, Bucket.` |
|
||||
| `OSS_URL` | `"https://cdn.xxx.com"` | `Aliyun Object Storage CDN url.` |
|
||||
| `OSS_INTERNAL` | `false` | `Access aliyun OSS with aliyun internal network or not, default is false. If your servers are running on aliyun too, you can set "true" to save lot of money.` |
|
||||
| `COS_SECRE_ID` | `""` | `Tencent Cloud Object Storage SecretId.` |
|
||||
| `COS_SECRE_KEY` | `""` | `SecretKey.` |
|
||||
| `COS_BUCKET` | `"bucketname-12345678"` | `Bucket.` |
|
||||
| `COS_REGION` | `"ap-chengdu"` | `Region.` |
|
||||
| `COS_URL` | `"https://cdn.xxx.com"` | `Object Storage CDN url.` |
|
||||
|
||||
七牛对象存储: [https://developer.qiniu.com/kodo/sdk/1289/nodejs](https://developer.qiniu.com/kodo/sdk/1289/nodejs)
|
||||
阿里云对象存储: [https://github.com/ali-sdk/ali-oss](https://github.com/ali-sdk/ali-oss)
|
||||
腾讯云对象存储: [https://github.com/tencentyun/cos-nodejs-sdk-v5](https://github.com/tencentyun/cos-nodejs-sdk-v5)
|
||||
|
||||
#### API 文档
|
||||
|
||||
**Swagger:** https://yourdomain/documentation
|
||||
|
||||
API中的token需要进行`SHA-512`加密。
|
||||
|
||||
**Github webhooks:** https://yourdomain/build/webhooks
|
||||
|
||||
Content type: `application/json`
|
||||
Secret: `your Token`
|
||||
|
||||
### Electron 应用程序配置指南
|
||||
|
||||
```bash
|
||||
yarn add electron-builder electron-simple-updater -D
|
||||
```
|
||||
|
||||
关于 [electron-builder](https://github.com/electron-userland/electron-builder) [electron-simple-updater](https://github.com/megahertz/electron-simple-updater)
|
||||
|
||||
在你的`package.json`文件中加入`build`配置信息([https://www.electron.build](https://www.electron.build)):
|
||||
|
||||
```json
|
||||
"scripts": {
|
||||
"build": "node config/build.js && electron-builder",
|
||||
"build:dir": "node config/build.js && electron-builder --dir",
|
||||
...
|
||||
},
|
||||
"build": {
|
||||
"productName": "Your App Name",
|
||||
"appId": "com.appid.abc",
|
||||
"directories": {
|
||||
"output": "build"
|
||||
},
|
||||
"files": [
|
||||
"dist/electron/**/*"
|
||||
],
|
||||
"dmg": {
|
||||
"contents": [
|
||||
{
|
||||
"x": 410,
|
||||
"y": 150,
|
||||
"type": "link",
|
||||
"path": "/Applications"
|
||||
},
|
||||
{
|
||||
"x": 130,
|
||||
"y": 150,
|
||||
"type": "file"
|
||||
}
|
||||
]
|
||||
},
|
||||
"mac": {
|
||||
"icon": "build/icons/icon.icns"
|
||||
},
|
||||
"win": {
|
||||
"icon": "build/icons/icon.ico",
|
||||
"target": "squirrel"
|
||||
},
|
||||
"linux": {
|
||||
"artifactName": "${productName}-${version}-${arch}.${ext}",
|
||||
"icon": "build/icons"
|
||||
},
|
||||
"squirrelWindows": {
|
||||
"iconUrl": "https://yourServer/app/icon.ico"
|
||||
}
|
||||
},
|
||||
```
|
||||
|
||||
## Mac APP 编译及代码签名
|
||||
|
||||
在macOS Majave (10.14.4)中安装 `Xcode 10.2` \ `brew (yarn 1.15.2)` \ `nvm (node 11.13.0)`, 至少运行一次`Xcode`。
|
||||
|
||||
操作系统默认安装了 Git 和 rsync。
|
||||
|
||||
开启 sshd:
|
||||
|
||||
```bash
|
||||
sudo launchctl load -w /System/Library/LaunchDaemons/ssh.plist
|
||||
```
|
||||
|
||||
添加你的服务器公钥到Mac `~/.ssh/authorized_keys`。
|
||||
添加你的Mac公钥到服务器 `~/.ssh/authorized_keys`。
|
||||
|
||||
在你的Linux服务器中安装`rsync`,并开启SSH服务。
|
65
package.json
Normal file
65
package.json
Normal file
@ -0,0 +1,65 @@
|
||||
{
|
||||
"name": "electron-distribution",
|
||||
"version": "1.0.0",
|
||||
"description": "Electron Distribution",
|
||||
"author": "yi-ge <a@wyr.me>",
|
||||
"scripts": {
|
||||
"dev": "cross-env NODE_ENV=development nodemon src/dev.js",
|
||||
"build": "babel src -d dist",
|
||||
"start": "node dist/app.js"
|
||||
},
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"ali-oss": "^6.1.1",
|
||||
"axios": "^0.18.0",
|
||||
"chalk": "^2.4.2",
|
||||
"chance": "^1.0.18",
|
||||
"cos-nodejs-sdk-v5": "^2.5.7",
|
||||
"cross-env": "^5.2.0",
|
||||
"dockerode": "^2.5.8",
|
||||
"hapi": "^18.1.0",
|
||||
"hapi-swagger": "^9.4.2",
|
||||
"inert": "^5.1.2",
|
||||
"joi": "^13.7.0",
|
||||
"jssha": "^2.3.1",
|
||||
"lodash": "^4.17.11",
|
||||
"lowdb": "^1.0.0",
|
||||
"moment-timezone": "^0.5.23",
|
||||
"qiniu": "^7.2.1",
|
||||
"simple-git": "^1.110.0",
|
||||
"socket.io": "^2.2.0",
|
||||
"ssh2": "^0.8.2",
|
||||
"vision": "^5.4.4"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/cli": "^7.2.3",
|
||||
"@babel/core": "^7.4.0",
|
||||
"@babel/plugin-proposal-class-properties": "^7.4.0",
|
||||
"@babel/plugin-proposal-decorators": "^7.4.0",
|
||||
"@babel/plugin-proposal-optional-chaining": "^7.2.0",
|
||||
"@babel/preset-env": "^7.4.2",
|
||||
"@babel/register": "^7.4.0",
|
||||
"nodemon": "^1.18.10"
|
||||
},
|
||||
"nodemonConfig": {
|
||||
"ignore": [
|
||||
".git",
|
||||
"node_modules/**/node_modules"
|
||||
],
|
||||
"delay": "2500",
|
||||
"env": {
|
||||
"NODE_ENV": "development"
|
||||
},
|
||||
"verbose": true,
|
||||
"execMap": {
|
||||
"js": "node --harmony"
|
||||
},
|
||||
"watch": [
|
||||
"src/"
|
||||
],
|
||||
"ext": "js,json"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 10"
|
||||
}
|
||||
}
|
7
public/css/bootstrap.min.css
vendored
Normal file
7
public/css/bootstrap.min.css
vendored
Normal file
File diff suppressed because one or more lines are too long
163
public/css/xterm.css
Normal file
163
public/css/xterm.css
Normal file
@ -0,0 +1,163 @@
|
||||
/**
|
||||
* Copyright (c) 2014 The xterm.js authors. All rights reserved.
|
||||
* Copyright (c) 2012-2013, Christopher Jeffrey (MIT License)
|
||||
* https://github.com/chjj/term.js
|
||||
* @license MIT
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*
|
||||
* Originally forked from (with the author's permission):
|
||||
* Fabrice Bellard's javascript vt100 for jslinux:
|
||||
* http://bellard.org/jslinux/
|
||||
* Copyright (c) 2011 Fabrice Bellard
|
||||
* The original design remains. The terminal itself
|
||||
* has been extended to include xterm CSI codes, among
|
||||
* other features.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Default styles for xterm.js
|
||||
*/
|
||||
|
||||
.xterm {
|
||||
font-feature-settings: "liga" 0;
|
||||
position: relative;
|
||||
user-select: none;
|
||||
-ms-user-select: none;
|
||||
-webkit-user-select: none;
|
||||
}
|
||||
|
||||
.xterm.focus,
|
||||
.xterm:focus {
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.xterm .xterm-helpers {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
/**
|
||||
* The z-index of the helpers must be higher than the canvases in order for
|
||||
* IMEs to appear on top.
|
||||
*/
|
||||
z-index: 10;
|
||||
}
|
||||
|
||||
.xterm .xterm-helper-textarea {
|
||||
/*
|
||||
* HACK: to fix IE's blinking cursor
|
||||
* Move textarea out of the screen to the far left, so that the cursor is not visible.
|
||||
*/
|
||||
position: absolute;
|
||||
opacity: 0;
|
||||
left: -9999em;
|
||||
top: 0;
|
||||
width: 0;
|
||||
height: 0;
|
||||
z-index: -10;
|
||||
/** Prevent wrapping so the IME appears against the textarea at the correct position */
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
resize: none;
|
||||
}
|
||||
|
||||
.xterm .composition-view {
|
||||
/* TODO: Composition position got messed up somewhere */
|
||||
background: #000;
|
||||
color: #FFF;
|
||||
display: none;
|
||||
position: absolute;
|
||||
white-space: nowrap;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.xterm .composition-view.active {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.xterm .xterm-viewport {
|
||||
/* On OS X this is required in order for the scroll bar to appear fully opaque */
|
||||
background-color: #000;
|
||||
overflow-y: scroll;
|
||||
cursor: default;
|
||||
position: absolute;
|
||||
right: 0;
|
||||
left: 0;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
}
|
||||
|
||||
.xterm .xterm-screen {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.xterm .xterm-screen canvas {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
}
|
||||
|
||||
.xterm .xterm-scroll-area {
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
.xterm-char-measure-element {
|
||||
display: inline-block;
|
||||
visibility: hidden;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: -9999em;
|
||||
line-height: normal;
|
||||
}
|
||||
|
||||
.xterm {
|
||||
cursor: text;
|
||||
}
|
||||
|
||||
.xterm.enable-mouse-events {
|
||||
/* When mouse events are enabled (eg. tmux), revert to the standard pointer cursor */
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
.xterm.xterm-cursor-pointer {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.xterm.column-select.focus {
|
||||
/* Column selection mode */
|
||||
cursor: crosshair;
|
||||
}
|
||||
|
||||
.xterm .xterm-accessibility,
|
||||
.xterm .xterm-message {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
right: 0;
|
||||
z-index: 100;
|
||||
color: transparent;
|
||||
}
|
||||
|
||||
.xterm .live-region {
|
||||
position: absolute;
|
||||
left: -9999px;
|
||||
width: 1px;
|
||||
height: 1px;
|
||||
overflow: hidden;
|
||||
}
|
BIN
public/icon.ico
Normal file
BIN
public/icon.ico
Normal file
Binary file not shown.
After Width: | Height: | Size: 264 KiB |
78
public/index.html
Normal file
78
public/index.html
Normal file
@ -0,0 +1,78 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<meta http-equiv="X-UA-Compatible" content="ie=edge">
|
||||
<title>Electron Distribution</title>
|
||||
<link rel="stylesheet" href="css/bootstrap.min.css">
|
||||
<link rel="stylesheet" href="css/xterm.css">
|
||||
<script>
|
||||
if (!document.querySelectorAll || !window.localStorage || !self.fetch) {
|
||||
alert('Upgrade your browser, Thank you.')
|
||||
}
|
||||
</script>
|
||||
<style>
|
||||
body {
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
.out {
|
||||
margin-top: 10px;
|
||||
background-color: black;
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
#no-auth, #auth {
|
||||
display: none;
|
||||
}
|
||||
|
||||
#terminal {
|
||||
width: 100%;
|
||||
min-height: 428px;
|
||||
}
|
||||
|
||||
#table {
|
||||
max-height: 300px;
|
||||
overflow: auto;
|
||||
margin: 5px 0;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div id="no-auth">
|
||||
Just for developers debugging, this page is very simple, sorry.
|
||||
<input type="password" size="128" placeholder="Please input the token and press enter key" id="tokenInput">
|
||||
</div>
|
||||
|
||||
<div id="auth">
|
||||
<div class="menu">
|
||||
<input type="button" value="Docker Initialize" id="dockerInitBtn">
|
||||
<input type="button" value="Release Log" id="listReleaseBtn">
|
||||
<input type="button" value="Build Log" id="listBuildBtn">
|
||||
<input type="button" value="Git Pull" id="gitPullBtn">
|
||||
<input type="button" value="Build Linux" id="buildLinuxBtn">
|
||||
<input type="button" value="Build Windows" id="buildWindowsBtn">
|
||||
<input type="button" value="Build Mac" id="buildMacBtn">
|
||||
<input type="button" value="Logout" id="logoutBtn">
|
||||
</div>
|
||||
|
||||
<div id="table"></div>
|
||||
|
||||
<div class="out">
|
||||
<div id="terminal"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script src="js/sha.js"></script>
|
||||
<script src="js/xterm.js"></script>
|
||||
<script src="js/addons/fit/fit.js"></script>
|
||||
<script src="js/addons/attach/attach.js"></script>
|
||||
<script src="js/socket.io.js"></script>
|
||||
<script src="js/jsonToTable.js"></script>
|
||||
<script src="js/main.js"></script>
|
||||
</body>
|
||||
|
||||
</html>
|
104
public/js/addons/attach/attach.js
Normal file
104
public/js/addons/attach/attach.js
Normal file
@ -0,0 +1,104 @@
|
||||
(function(f){if(typeof exports==="object"&&typeof module!=="undefined"){module.exports=f()}else if(typeof define==="function"&&define.amd){define([],f)}else{var g;if(typeof window!=="undefined"){g=window}else if(typeof global!=="undefined"){g=global}else if(typeof self!=="undefined"){g=self}else{g=this}g.attach = f()}})(function(){var define,module,exports;return (function(){function r(e,n,t){function o(i,f){if(!n[i]){if(!e[i]){var c="function"==typeof require&&require;if(!f&&c)return c(i,!0);if(u)return u(i,!0);var a=new Error("Cannot find module '"+i+"'");throw a.code="MODULE_NOT_FOUND",a}var p=n[i]={exports:{}};e[i][0].call(p.exports,function(r){var n=e[i][1][r];return o(n||r)},p,p.exports,r,e,n,t)}return n[i].exports}for(var u="function"==typeof require&&require,i=0;i<t.length;i++)o(t[i]);return o}return r})()({1:[function(require,module,exports){
|
||||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
function attach(term, socket, bidirectional, buffered) {
|
||||
var addonTerminal = term;
|
||||
bidirectional = (typeof bidirectional === 'undefined') ? true : bidirectional;
|
||||
addonTerminal.__socket = socket;
|
||||
addonTerminal.__flushBuffer = function () {
|
||||
addonTerminal.write(addonTerminal.__attachSocketBuffer);
|
||||
addonTerminal.__attachSocketBuffer = null;
|
||||
};
|
||||
addonTerminal.__pushToBuffer = function (data) {
|
||||
if (addonTerminal.__attachSocketBuffer) {
|
||||
addonTerminal.__attachSocketBuffer += data;
|
||||
}
|
||||
else {
|
||||
addonTerminal.__attachSocketBuffer = data;
|
||||
setTimeout(addonTerminal.__flushBuffer, 10);
|
||||
}
|
||||
};
|
||||
var myTextDecoder;
|
||||
addonTerminal.__getMessage = function (ev) {
|
||||
var str;
|
||||
if (typeof ev.data === 'object') {
|
||||
if (!myTextDecoder) {
|
||||
myTextDecoder = new TextDecoder();
|
||||
}
|
||||
if (ev.data instanceof ArrayBuffer) {
|
||||
str = myTextDecoder.decode(ev.data);
|
||||
displayData(str);
|
||||
}
|
||||
else {
|
||||
var fileReader_1 = new FileReader();
|
||||
fileReader_1.addEventListener('load', function () {
|
||||
str = myTextDecoder.decode(fileReader_1.result);
|
||||
displayData(str);
|
||||
});
|
||||
fileReader_1.readAsArrayBuffer(ev.data);
|
||||
}
|
||||
}
|
||||
else if (typeof ev.data === 'string') {
|
||||
displayData(ev.data);
|
||||
}
|
||||
else {
|
||||
throw Error("Cannot handle \"" + typeof ev.data + "\" websocket message.");
|
||||
}
|
||||
};
|
||||
function displayData(str, data) {
|
||||
if (buffered) {
|
||||
addonTerminal.__pushToBuffer(str || data);
|
||||
}
|
||||
else {
|
||||
addonTerminal.write(str || data);
|
||||
}
|
||||
}
|
||||
addonTerminal.__sendData = function (data) {
|
||||
if (socket.readyState !== 1) {
|
||||
return;
|
||||
}
|
||||
socket.send(data);
|
||||
};
|
||||
addonTerminal._core.register(addSocketListener(socket, 'message', addonTerminal.__getMessage));
|
||||
if (bidirectional) {
|
||||
addonTerminal._core.register(addonTerminal.addDisposableListener('data', addonTerminal.__sendData));
|
||||
}
|
||||
addonTerminal._core.register(addSocketListener(socket, 'close', function () { return detach(addonTerminal, socket); }));
|
||||
addonTerminal._core.register(addSocketListener(socket, 'error', function () { return detach(addonTerminal, socket); }));
|
||||
}
|
||||
exports.attach = attach;
|
||||
function addSocketListener(socket, type, handler) {
|
||||
socket.addEventListener(type, handler);
|
||||
return {
|
||||
dispose: function () {
|
||||
if (!handler) {
|
||||
return;
|
||||
}
|
||||
socket.removeEventListener(type, handler);
|
||||
handler = null;
|
||||
}
|
||||
};
|
||||
}
|
||||
function detach(term, socket) {
|
||||
var addonTerminal = term;
|
||||
addonTerminal.off('data', addonTerminal.__sendData);
|
||||
socket = (typeof socket === 'undefined') ? addonTerminal.__socket : socket;
|
||||
if (socket) {
|
||||
socket.removeEventListener('message', addonTerminal.__getMessage);
|
||||
}
|
||||
delete addonTerminal.__socket;
|
||||
}
|
||||
exports.detach = detach;
|
||||
function apply(terminalConstructor) {
|
||||
terminalConstructor.prototype.attach = function (socket, bidirectional, buffered) {
|
||||
attach(this, socket, bidirectional, buffered);
|
||||
};
|
||||
terminalConstructor.prototype.detach = function (socket) {
|
||||
detach(this, socket);
|
||||
};
|
||||
}
|
||||
exports.apply = apply;
|
||||
|
||||
},{}]},{},[1])(1)
|
||||
});
|
||||
//# sourceMappingURL=attach.js.map
|
1
public/js/addons/attach/attach.js.map
Normal file
1
public/js/addons/attach/attach.js.map
Normal file
File diff suppressed because one or more lines are too long
51
public/js/addons/fit/fit.js
Normal file
51
public/js/addons/fit/fit.js
Normal file
@ -0,0 +1,51 @@
|
||||
(function(f){if(typeof exports==="object"&&typeof module!=="undefined"){module.exports=f()}else if(typeof define==="function"&&define.amd){define([],f)}else{var g;if(typeof window!=="undefined"){g=window}else if(typeof global!=="undefined"){g=global}else if(typeof self!=="undefined"){g=self}else{g=this}g.fit = f()}})(function(){var define,module,exports;return (function(){function r(e,n,t){function o(i,f){if(!n[i]){if(!e[i]){var c="function"==typeof require&&require;if(!f&&c)return c(i,!0);if(u)return u(i,!0);var a=new Error("Cannot find module '"+i+"'");throw a.code="MODULE_NOT_FOUND",a}var p=n[i]={exports:{}};e[i][0].call(p.exports,function(r){var n=e[i][1][r];return o(n||r)},p,p.exports,r,e,n,t)}return n[i].exports}for(var u="function"==typeof require&&require,i=0;i<t.length;i++)o(t[i]);return o}return r})()({1:[function(require,module,exports){
|
||||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
function proposeGeometry(term) {
|
||||
if (!term.element.parentElement) {
|
||||
return null;
|
||||
}
|
||||
var parentElementStyle = window.getComputedStyle(term.element.parentElement);
|
||||
var parentElementHeight = parseInt(parentElementStyle.getPropertyValue('height'));
|
||||
var parentElementWidth = Math.max(0, parseInt(parentElementStyle.getPropertyValue('width')));
|
||||
var elementStyle = window.getComputedStyle(term.element);
|
||||
var elementPadding = {
|
||||
top: parseInt(elementStyle.getPropertyValue('padding-top')),
|
||||
bottom: parseInt(elementStyle.getPropertyValue('padding-bottom')),
|
||||
right: parseInt(elementStyle.getPropertyValue('padding-right')),
|
||||
left: parseInt(elementStyle.getPropertyValue('padding-left'))
|
||||
};
|
||||
var elementPaddingVer = elementPadding.top + elementPadding.bottom;
|
||||
var elementPaddingHor = elementPadding.right + elementPadding.left;
|
||||
var availableHeight = parentElementHeight - elementPaddingVer;
|
||||
var availableWidth = parentElementWidth - elementPaddingHor - term._core.viewport.scrollBarWidth;
|
||||
var geometry = {
|
||||
cols: Math.floor(availableWidth / term._core.renderer.dimensions.actualCellWidth),
|
||||
rows: Math.floor(availableHeight / term._core.renderer.dimensions.actualCellHeight)
|
||||
};
|
||||
return geometry;
|
||||
}
|
||||
exports.proposeGeometry = proposeGeometry;
|
||||
function fit(term) {
|
||||
var geometry = proposeGeometry(term);
|
||||
if (geometry) {
|
||||
if (term.rows !== geometry.rows || term.cols !== geometry.cols) {
|
||||
term._core.renderer.clear();
|
||||
term.resize(geometry.cols, geometry.rows);
|
||||
}
|
||||
}
|
||||
}
|
||||
exports.fit = fit;
|
||||
function apply(terminalConstructor) {
|
||||
terminalConstructor.prototype.proposeGeometry = function () {
|
||||
return proposeGeometry(this);
|
||||
};
|
||||
terminalConstructor.prototype.fit = function () {
|
||||
fit(this);
|
||||
};
|
||||
}
|
||||
exports.apply = apply;
|
||||
|
||||
},{}]},{},[1])(1)
|
||||
});
|
||||
//# sourceMappingURL=fit.js.map
|
1
public/js/addons/fit/fit.js.map
Normal file
1
public/js/addons/fit/fit.js.map
Normal file
@ -0,0 +1 @@
|
||||
{"version":3,"file":"fit.js","sources":["../../../src/addons/fit/fit.ts","../../../node_modules/browser-pack/_prelude.js"],"sourcesContent":["/**\n * Copyright (c) 2014 The xterm.js authors. All rights reserved.\n * @license MIT\n *\n * Fit terminal columns and rows to the dimensions of its DOM element.\n *\n * ## Approach\n *\n * Rows: Truncate the division of the terminal parent element height by the\n * terminal row height.\n * Columns: Truncate the division of the terminal parent element width by the\n * terminal character width (apply display: inline at the terminal\n * row and truncate its width with the current number of columns).\n */\n\nimport { Terminal } from 'xterm';\n\nexport interface IGeometry {\n rows: number;\n cols: number;\n}\n\nexport function proposeGeometry(term: Terminal): IGeometry {\n if (!term.element.parentElement) {\n return null;\n }\n const parentElementStyle = window.getComputedStyle(term.element.parentElement);\n const parentElementHeight = parseInt(parentElementStyle.getPropertyValue('height'));\n const parentElementWidth = Math.max(0, parseInt(parentElementStyle.getPropertyValue('width')));\n const elementStyle = window.getComputedStyle(term.element);\n const elementPadding = {\n top: parseInt(elementStyle.getPropertyValue('padding-top')),\n bottom: parseInt(elementStyle.getPropertyValue('padding-bottom')),\n right: parseInt(elementStyle.getPropertyValue('padding-right')),\n left: parseInt(elementStyle.getPropertyValue('padding-left'))\n };\n const elementPaddingVer = elementPadding.top + elementPadding.bottom;\n const elementPaddingHor = elementPadding.right + elementPadding.left;\n const availableHeight = parentElementHeight - elementPaddingVer;\n const availableWidth = parentElementWidth - elementPaddingHor - (<any>term)._core.viewport.scrollBarWidth;\n const geometry = {\n cols: Math.floor(availableWidth / (<any>term)._core.renderer.dimensions.actualCellWidth),\n rows: Math.floor(availableHeight / (<any>term)._core.renderer.dimensions.actualCellHeight)\n };\n return geometry;\n}\n\nexport function fit(term: Terminal): void {\n const geometry = proposeGeometry(term);\n if (geometry) {\n // Force a full render\n if (term.rows !== geometry.rows || term.cols !== geometry.cols) {\n (<any>term)._core.renderer.clear();\n term.resize(geometry.cols, geometry.rows);\n }\n }\n}\n\nexport function apply(terminalConstructor: typeof Terminal): void {\n (<any>terminalConstructor.prototype).proposeGeometry = function (): IGeometry {\n return proposeGeometry(this);\n };\n\n (<any>terminalConstructor.prototype).fit = function (): void {\n fit(this);\n };\n}\n",null],"names":[],"mappings":"ACAA;;;ADsBA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAvBA;AAyBA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AATA;AAWA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AARA;"}
|
10
public/js/addons/fullscreen/fullscreen.css
Normal file
10
public/js/addons/fullscreen/fullscreen.css
Normal file
@ -0,0 +1,10 @@
|
||||
.xterm.fullscreen {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
width: auto;
|
||||
height: auto;
|
||||
z-index: 255;
|
||||
}
|
29
public/js/addons/fullscreen/fullscreen.js
Normal file
29
public/js/addons/fullscreen/fullscreen.js
Normal file
@ -0,0 +1,29 @@
|
||||
(function(f){if(typeof exports==="object"&&typeof module!=="undefined"){module.exports=f()}else if(typeof define==="function"&&define.amd){define([],f)}else{var g;if(typeof window!=="undefined"){g=window}else if(typeof global!=="undefined"){g=global}else if(typeof self!=="undefined"){g=self}else{g=this}g.fullscreen = f()}})(function(){var define,module,exports;return (function(){function r(e,n,t){function o(i,f){if(!n[i]){if(!e[i]){var c="function"==typeof require&&require;if(!f&&c)return c(i,!0);if(u)return u(i,!0);var a=new Error("Cannot find module '"+i+"'");throw a.code="MODULE_NOT_FOUND",a}var p=n[i]={exports:{}};e[i][0].call(p.exports,function(r){var n=e[i][1][r];return o(n||r)},p,p.exports,r,e,n,t)}return n[i].exports}for(var u="function"==typeof require&&require,i=0;i<t.length;i++)o(t[i]);return o}return r})()({1:[function(require,module,exports){
|
||||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
function toggleFullScreen(term, fullscreen) {
|
||||
var fn;
|
||||
if (typeof fullscreen === 'undefined') {
|
||||
fn = (term.element.classList.contains('fullscreen')) ?
|
||||
term.element.classList.remove : term.element.classList.add;
|
||||
}
|
||||
else if (!fullscreen) {
|
||||
fn = term.element.classList.remove;
|
||||
}
|
||||
else {
|
||||
fn = term.element.classList.add;
|
||||
}
|
||||
fn = fn.bind(term.element.classList);
|
||||
fn('fullscreen');
|
||||
}
|
||||
exports.toggleFullScreen = toggleFullScreen;
|
||||
function apply(terminalConstructor) {
|
||||
terminalConstructor.prototype.toggleFullScreen = function (fullscreen) {
|
||||
toggleFullScreen(this, fullscreen);
|
||||
};
|
||||
}
|
||||
exports.apply = apply;
|
||||
|
||||
},{}]},{},[1])(1)
|
||||
});
|
||||
//# sourceMappingURL=fullscreen.js.map
|
1
public/js/addons/fullscreen/fullscreen.js.map
Normal file
1
public/js/addons/fullscreen/fullscreen.js.map
Normal file
@ -0,0 +1 @@
|
||||
{"version":3,"file":"fullscreen.js","sources":["../../../src/addons/fullscreen/fullscreen.ts","../../../node_modules/browser-pack/_prelude.js"],"sourcesContent":["/**\n * Copyright (c) 2014 The xterm.js authors. All rights reserved.\n * @license MIT\n */\n\nimport { Terminal } from 'xterm';\n\n/**\n * Toggle the given terminal's fullscreen mode.\n * @param term The terminal to toggle full screen mode\n * @param fullscreen Toggle fullscreen on (true) or off (false)\n */\nexport function toggleFullScreen(term: Terminal, fullscreen: boolean): void {\n let fn: (...tokens: string[]) => void;\n\n if (typeof fullscreen === 'undefined') {\n fn = (term.element.classList.contains('fullscreen')) ?\n term.element.classList.remove : term.element.classList.add;\n } else if (!fullscreen) {\n fn = term.element.classList.remove;\n } else {\n fn = term.element.classList.add;\n }\n\n fn = fn.bind(term.element.classList);\n fn('fullscreen');\n}\n\nexport function apply(terminalConstructor: typeof Terminal): void {\n (<any>terminalConstructor.prototype).toggleFullScreen = function (fullscreen: boolean): void {\n toggleFullScreen(this, fullscreen);\n };\n}\n",null],"names":[],"mappings":"ACAA;;;ADYA;AACA;AAEA;AACA;AACA;AACA;AAAA;AACA;AACA;AAAA;AACA;AACA;AAEA;AACA;AACA;AAdA;AAgBA;AACA;AACA;AACA;AACA;AAJA;"}
|
235
public/js/addons/search/search.js
Normal file
235
public/js/addons/search/search.js
Normal file
@ -0,0 +1,235 @@
|
||||
(function(f){if(typeof exports==="object"&&typeof module!=="undefined"){module.exports=f()}else if(typeof define==="function"&&define.amd){define([],f)}else{var g;if(typeof window!=="undefined"){g=window}else if(typeof global!=="undefined"){g=global}else if(typeof self!=="undefined"){g=self}else{g=this}g.search = f()}})(function(){var define,module,exports;return (function(){function r(e,n,t){function o(i,f){if(!n[i]){if(!e[i]){var c="function"==typeof require&&require;if(!f&&c)return c(i,!0);if(u)return u(i,!0);var a=new Error("Cannot find module '"+i+"'");throw a.code="MODULE_NOT_FOUND",a}var p=n[i]={exports:{}};e[i][0].call(p.exports,function(r){var n=e[i][1][r];return o(n||r)},p,p.exports,r,e,n,t)}return n[i].exports}for(var u="function"==typeof require&&require,i=0;i<t.length;i++)o(t[i]);return o}return r})()({1:[function(require,module,exports){
|
||||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
var NON_WORD_CHARACTERS = ' ~!@#$%^&*()+`-=[]{}|\;:"\',./<>?';
|
||||
var LINES_CACHE_TIME_TO_LIVE = 15 * 1000;
|
||||
var SearchHelper = (function () {
|
||||
function SearchHelper(_terminal) {
|
||||
this._terminal = _terminal;
|
||||
this._linesCache = null;
|
||||
this._linesCacheTimeoutId = 0;
|
||||
this._destroyLinesCache = this._destroyLinesCache.bind(this);
|
||||
}
|
||||
SearchHelper.prototype.findNext = function (term, searchOptions) {
|
||||
var selectionManager = this._terminal._core.selectionManager;
|
||||
var incremental = searchOptions.incremental;
|
||||
var result;
|
||||
if (!term || term.length === 0) {
|
||||
selectionManager.clearSelection();
|
||||
return false;
|
||||
}
|
||||
var startCol = 0;
|
||||
var startRow = this._terminal._core.buffer.ydisp;
|
||||
if (selectionManager.selectionEnd) {
|
||||
if (this._terminal.getSelection().length !== 0) {
|
||||
startRow = incremental ? selectionManager.selectionStart[1] : selectionManager.selectionEnd[1];
|
||||
startCol = incremental ? selectionManager.selectionStart[0] : selectionManager.selectionEnd[0];
|
||||
}
|
||||
}
|
||||
this._initLinesCache();
|
||||
result = this._findInLine(term, startRow, startCol, searchOptions);
|
||||
if (!result) {
|
||||
for (var y = startRow + 1; y < this._terminal._core.buffer.ybase + this._terminal.rows; y++) {
|
||||
result = this._findInLine(term, y, 0, searchOptions);
|
||||
if (result) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!result) {
|
||||
for (var y = 0; y <= startRow; y++) {
|
||||
result = this._findInLine(term, y, 0, searchOptions);
|
||||
if (result) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return this._selectResult(result);
|
||||
};
|
||||
SearchHelper.prototype.findPrevious = function (term, searchOptions) {
|
||||
var selectionManager = this._terminal._core.selectionManager;
|
||||
var result;
|
||||
if (!term || term.length === 0) {
|
||||
selectionManager.clearSelection();
|
||||
return false;
|
||||
}
|
||||
var isReverseSearch = true;
|
||||
var startRow = this._terminal._core.buffer.ydisp;
|
||||
var startCol = this._terminal._core.buffer.lines.get(startRow).length;
|
||||
if (selectionManager.selectionStart) {
|
||||
if (this._terminal.getSelection().length !== 0) {
|
||||
startRow = selectionManager.selectionStart[1];
|
||||
startCol = selectionManager.selectionStart[0];
|
||||
}
|
||||
}
|
||||
this._initLinesCache();
|
||||
result = this._findInLine(term, startRow, startCol, searchOptions, isReverseSearch);
|
||||
if (!result) {
|
||||
for (var y = startRow - 1; y >= 0; y--) {
|
||||
result = this._findInLine(term, y, this._terminal._core.buffer.lines.get(y).length, searchOptions, isReverseSearch);
|
||||
if (result) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!result) {
|
||||
var searchFrom = this._terminal._core.buffer.ybase + this._terminal.rows - 1;
|
||||
for (var y = searchFrom; y >= startRow; y--) {
|
||||
result = this._findInLine(term, y, this._terminal._core.buffer.lines.get(y).length, searchOptions, isReverseSearch);
|
||||
if (result) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return this._selectResult(result);
|
||||
};
|
||||
SearchHelper.prototype._initLinesCache = function () {
|
||||
var _this = this;
|
||||
if (!this._linesCache) {
|
||||
this._linesCache = new Array(this._terminal._core.buffer.length);
|
||||
this._terminal.on('cursormove', this._destroyLinesCache);
|
||||
}
|
||||
window.clearTimeout(this._linesCacheTimeoutId);
|
||||
this._linesCacheTimeoutId = window.setTimeout(function () { return _this._destroyLinesCache(); }, LINES_CACHE_TIME_TO_LIVE);
|
||||
};
|
||||
SearchHelper.prototype._destroyLinesCache = function () {
|
||||
this._linesCache = null;
|
||||
this._terminal.off('cursormove', this._destroyLinesCache);
|
||||
if (this._linesCacheTimeoutId) {
|
||||
window.clearTimeout(this._linesCacheTimeoutId);
|
||||
this._linesCacheTimeoutId = 0;
|
||||
}
|
||||
};
|
||||
SearchHelper.prototype._isWholeWord = function (searchIndex, line, term) {
|
||||
return (((searchIndex === 0) || (NON_WORD_CHARACTERS.indexOf(line[searchIndex - 1]) !== -1)) &&
|
||||
(((searchIndex + term.length) === line.length) || (NON_WORD_CHARACTERS.indexOf(line[searchIndex + term.length]) !== -1)));
|
||||
};
|
||||
SearchHelper.prototype._findInLine = function (term, row, col, searchOptions, isReverseSearch) {
|
||||
if (searchOptions === void 0) { searchOptions = {}; }
|
||||
if (isReverseSearch === void 0) { isReverseSearch = false; }
|
||||
if (this._terminal._core.buffer.lines.get(row).isWrapped) {
|
||||
return;
|
||||
}
|
||||
var stringLine = this._linesCache ? this._linesCache[row] : void 0;
|
||||
if (stringLine === void 0) {
|
||||
stringLine = this.translateBufferLineToStringWithWrap(row, true);
|
||||
if (this._linesCache) {
|
||||
this._linesCache[row] = stringLine;
|
||||
}
|
||||
}
|
||||
var searchTerm = searchOptions.caseSensitive ? term : term.toLowerCase();
|
||||
var searchStringLine = searchOptions.caseSensitive ? stringLine : stringLine.toLowerCase();
|
||||
var resultIndex = -1;
|
||||
if (searchOptions.regex) {
|
||||
var searchRegex = RegExp(searchTerm, 'g');
|
||||
var foundTerm = void 0;
|
||||
if (isReverseSearch) {
|
||||
while (foundTerm = searchRegex.exec(searchStringLine.slice(0, col))) {
|
||||
resultIndex = searchRegex.lastIndex - foundTerm[0].length;
|
||||
term = foundTerm[0];
|
||||
searchRegex.lastIndex -= (term.length - 1);
|
||||
}
|
||||
}
|
||||
else {
|
||||
foundTerm = searchRegex.exec(searchStringLine.slice(col));
|
||||
if (foundTerm && foundTerm[0].length > 0) {
|
||||
resultIndex = col + (searchRegex.lastIndex - foundTerm[0].length);
|
||||
term = foundTerm[0];
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (isReverseSearch) {
|
||||
if (col - searchTerm.length >= 0) {
|
||||
resultIndex = searchStringLine.lastIndexOf(searchTerm, col - searchTerm.length);
|
||||
}
|
||||
}
|
||||
else {
|
||||
resultIndex = searchStringLine.indexOf(searchTerm, col);
|
||||
}
|
||||
}
|
||||
if (resultIndex >= 0) {
|
||||
if (resultIndex >= this._terminal.cols) {
|
||||
row += Math.floor(resultIndex / this._terminal.cols);
|
||||
resultIndex = resultIndex % this._terminal.cols;
|
||||
}
|
||||
if (searchOptions.wholeWord && !this._isWholeWord(resultIndex, searchStringLine, term)) {
|
||||
return;
|
||||
}
|
||||
var line = this._terminal._core.buffer.lines.get(row);
|
||||
for (var i = 0; i < resultIndex; i++) {
|
||||
var charData = line.get(i);
|
||||
var char = charData[1];
|
||||
if (char.length > 1) {
|
||||
resultIndex -= char.length - 1;
|
||||
}
|
||||
var charWidth = charData[2];
|
||||
if (charWidth === 0) {
|
||||
resultIndex++;
|
||||
}
|
||||
}
|
||||
return {
|
||||
term: term,
|
||||
col: resultIndex,
|
||||
row: row
|
||||
};
|
||||
}
|
||||
};
|
||||
SearchHelper.prototype.translateBufferLineToStringWithWrap = function (lineIndex, trimRight) {
|
||||
var lineString = '';
|
||||
var lineWrapsToNext;
|
||||
do {
|
||||
var nextLine = this._terminal._core.buffer.lines.get(lineIndex + 1);
|
||||
lineWrapsToNext = nextLine ? nextLine.isWrapped : false;
|
||||
lineString += this._terminal._core.buffer.translateBufferLineToString(lineIndex, !lineWrapsToNext && trimRight).substring(0, this._terminal.cols);
|
||||
lineIndex++;
|
||||
} while (lineWrapsToNext);
|
||||
return lineString;
|
||||
};
|
||||
SearchHelper.prototype._selectResult = function (result) {
|
||||
if (!result) {
|
||||
this._terminal.clearSelection();
|
||||
return false;
|
||||
}
|
||||
this._terminal._core.selectionManager.setSelection(result.col, result.row, result.term.length);
|
||||
this._terminal.scrollLines(result.row - this._terminal._core.buffer.ydisp);
|
||||
return true;
|
||||
};
|
||||
return SearchHelper;
|
||||
}());
|
||||
exports.SearchHelper = SearchHelper;
|
||||
|
||||
},{}],2:[function(require,module,exports){
|
||||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
var SearchHelper_1 = require("./SearchHelper");
|
||||
function findNext(terminal, term, searchOptions) {
|
||||
if (searchOptions === void 0) { searchOptions = {}; }
|
||||
var addonTerminal = terminal;
|
||||
if (!addonTerminal.__searchHelper) {
|
||||
addonTerminal.__searchHelper = new SearchHelper_1.SearchHelper(addonTerminal);
|
||||
}
|
||||
return addonTerminal.__searchHelper.findNext(term, searchOptions);
|
||||
}
|
||||
exports.findNext = findNext;
|
||||
function findPrevious(terminal, term, searchOptions) {
|
||||
var addonTerminal = terminal;
|
||||
if (!addonTerminal.__searchHelper) {
|
||||
addonTerminal.__searchHelper = new SearchHelper_1.SearchHelper(addonTerminal);
|
||||
}
|
||||
return addonTerminal.__searchHelper.findPrevious(term, searchOptions);
|
||||
}
|
||||
exports.findPrevious = findPrevious;
|
||||
function apply(terminalConstructor) {
|
||||
terminalConstructor.prototype.findNext = function (term, searchOptions) {
|
||||
return findNext(this, term, searchOptions);
|
||||
};
|
||||
terminalConstructor.prototype.findPrevious = function (term, searchOptions) {
|
||||
return findPrevious(this, term, searchOptions);
|
||||
};
|
||||
}
|
||||
exports.apply = apply;
|
||||
|
||||
},{"./SearchHelper":1}]},{},[2])(2)
|
||||
});
|
||||
//# sourceMappingURL=search.js.map
|
1
public/js/addons/search/search.js.map
Normal file
1
public/js/addons/search/search.js.map
Normal file
File diff suppressed because one or more lines are too long
69
public/js/addons/terminado/terminado.js
Normal file
69
public/js/addons/terminado/terminado.js
Normal file
@ -0,0 +1,69 @@
|
||||
(function(f){if(typeof exports==="object"&&typeof module!=="undefined"){module.exports=f()}else if(typeof define==="function"&&define.amd){define([],f)}else{var g;if(typeof window!=="undefined"){g=window}else if(typeof global!=="undefined"){g=global}else if(typeof self!=="undefined"){g=self}else{g=this}g.terminado = f()}})(function(){var define,module,exports;return (function(){function r(e,n,t){function o(i,f){if(!n[i]){if(!e[i]){var c="function"==typeof require&&require;if(!f&&c)return c(i,!0);if(u)return u(i,!0);var a=new Error("Cannot find module '"+i+"'");throw a.code="MODULE_NOT_FOUND",a}var p=n[i]={exports:{}};e[i][0].call(p.exports,function(r){var n=e[i][1][r];return o(n||r)},p,p.exports,r,e,n,t)}return n[i].exports}for(var u="function"==typeof require&&require,i=0;i<t.length;i++)o(t[i]);return o}return r})()({1:[function(require,module,exports){
|
||||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
function terminadoAttach(term, socket, bidirectional, buffered) {
|
||||
var addonTerminal = term;
|
||||
bidirectional = (typeof bidirectional === 'undefined') ? true : bidirectional;
|
||||
addonTerminal.__socket = socket;
|
||||
addonTerminal.__flushBuffer = function () {
|
||||
addonTerminal.write(addonTerminal.__attachSocketBuffer);
|
||||
addonTerminal.__attachSocketBuffer = null;
|
||||
};
|
||||
addonTerminal.__pushToBuffer = function (data) {
|
||||
if (addonTerminal.__attachSocketBuffer) {
|
||||
addonTerminal.__attachSocketBuffer += data;
|
||||
}
|
||||
else {
|
||||
addonTerminal.__attachSocketBuffer = data;
|
||||
setTimeout(addonTerminal.__flushBuffer, 10);
|
||||
}
|
||||
};
|
||||
addonTerminal.__getMessage = function (ev) {
|
||||
var data = JSON.parse(ev.data);
|
||||
if (data[0] === 'stdout') {
|
||||
if (buffered) {
|
||||
addonTerminal.__pushToBuffer(data[1]);
|
||||
}
|
||||
else {
|
||||
addonTerminal.write(data[1]);
|
||||
}
|
||||
}
|
||||
};
|
||||
addonTerminal.__sendData = function (data) {
|
||||
socket.send(JSON.stringify(['stdin', data]));
|
||||
};
|
||||
addonTerminal.__setSize = function (size) {
|
||||
socket.send(JSON.stringify(['set_size', size.rows, size.cols]));
|
||||
};
|
||||
socket.addEventListener('message', addonTerminal.__getMessage);
|
||||
if (bidirectional) {
|
||||
addonTerminal.on('data', addonTerminal.__sendData);
|
||||
}
|
||||
addonTerminal.on('resize', addonTerminal.__setSize);
|
||||
socket.addEventListener('close', function () { return terminadoDetach(addonTerminal, socket); });
|
||||
socket.addEventListener('error', function () { return terminadoDetach(addonTerminal, socket); });
|
||||
}
|
||||
exports.terminadoAttach = terminadoAttach;
|
||||
function terminadoDetach(term, socket) {
|
||||
var addonTerminal = term;
|
||||
addonTerminal.off('data', addonTerminal.__sendData);
|
||||
socket = (typeof socket === 'undefined') ? addonTerminal.__socket : socket;
|
||||
if (socket) {
|
||||
socket.removeEventListener('message', addonTerminal.__getMessage);
|
||||
}
|
||||
delete addonTerminal.__socket;
|
||||
}
|
||||
exports.terminadoDetach = terminadoDetach;
|
||||
function apply(terminalConstructor) {
|
||||
terminalConstructor.prototype.terminadoAttach = function (socket, bidirectional, buffered) {
|
||||
return terminadoAttach(this, socket, bidirectional, buffered);
|
||||
};
|
||||
terminalConstructor.prototype.terminadoDetach = function (socket) {
|
||||
return terminadoDetach(this, socket);
|
||||
};
|
||||
}
|
||||
exports.apply = apply;
|
||||
|
||||
},{}]},{},[1])(1)
|
||||
});
|
||||
//# sourceMappingURL=terminado.js.map
|
1
public/js/addons/terminado/terminado.js.map
Normal file
1
public/js/addons/terminado/terminado.js.map
Normal file
@ -0,0 +1 @@
|
||||
{"version":3,"file":"terminado.js","sources":["../../../src/addons/terminado/terminado.ts","../../../node_modules/browser-pack/_prelude.js"],"sourcesContent":["/**\n * Copyright (c) 2016 The xterm.js authors. All rights reserved.\n * @license MIT\n *\n * This module provides methods for attaching a terminal to a terminado\n * WebSocket stream.\n */\n\nimport { Terminal } from 'xterm';\nimport { ITerminadoAddonTerminal } from './Interfaces';\n\n/**\n * Attaches the given terminal to the given socket.\n *\n * @param term The terminal to be attached to the given socket.\n * @param socket The socket to attach the current terminal.\n * @param bidirectional Whether the terminal should send data to the socket as well.\n * @param buffered Whether the rendering of incoming data should happen instantly or at a maximum\n * frequency of 1 rendering per 10ms.\n */\nexport function terminadoAttach(term: Terminal, socket: WebSocket, bidirectional: boolean, buffered: boolean): void {\n const addonTerminal = <ITerminadoAddonTerminal>term;\n bidirectional = (typeof bidirectional === 'undefined') ? true : bidirectional;\n addonTerminal.__socket = socket;\n\n addonTerminal.__flushBuffer = () => {\n addonTerminal.write(addonTerminal.__attachSocketBuffer);\n addonTerminal.__attachSocketBuffer = null;\n };\n\n addonTerminal.__pushToBuffer = (data: string) => {\n if (addonTerminal.__attachSocketBuffer) {\n addonTerminal.__attachSocketBuffer += data;\n } else {\n addonTerminal.__attachSocketBuffer = data;\n setTimeout(addonTerminal.__flushBuffer, 10);\n }\n };\n\n addonTerminal.__getMessage = (ev: MessageEvent) => {\n const data = JSON.parse(ev.data);\n if (data[0] === 'stdout') {\n if (buffered) {\n addonTerminal.__pushToBuffer(data[1]);\n } else {\n addonTerminal.write(data[1]);\n }\n }\n };\n\n addonTerminal.__sendData = (data: string) => {\n socket.send(JSON.stringify(['stdin', data]));\n };\n\n addonTerminal.__setSize = (size: {rows: number, cols: number}) => {\n socket.send(JSON.stringify(['set_size', size.rows, size.cols]));\n };\n\n socket.addEventListener('message', addonTerminal.__getMessage);\n\n if (bidirectional) {\n addonTerminal.on('data', addonTerminal.__sendData);\n }\n addonTerminal.on('resize', addonTerminal.__setSize);\n\n socket.addEventListener('close', () => terminadoDetach(addonTerminal, socket));\n socket.addEventListener('error', () => terminadoDetach(addonTerminal, socket));\n}\n\n/**\n * Detaches the given terminal from the given socket\n *\n * @param term The terminal to be detached from the given socket.\n * @param socket The socket from which to detach the current terminal.\n */\nexport function terminadoDetach(term: Terminal, socket: WebSocket): void {\n const addonTerminal = <ITerminadoAddonTerminal>term;\n addonTerminal.off('data', addonTerminal.__sendData);\n\n socket = (typeof socket === 'undefined') ? addonTerminal.__socket : socket;\n\n if (socket) {\n socket.removeEventListener('message', addonTerminal.__getMessage);\n }\n\n delete addonTerminal.__socket;\n}\n\nexport function apply(terminalConstructor: typeof Terminal): void {\n /**\n * Attaches the current terminal to the given socket\n *\n * @param socket - The socket to attach the current terminal.\n * @param bidirectional - Whether the terminal should send data to the socket as well.\n * @param buffered - Whether the rendering of incoming data should happen instantly or at a\n * maximum frequency of 1 rendering per 10ms.\n */\n (<any>terminalConstructor.prototype).terminadoAttach = function (socket: WebSocket, bidirectional: boolean, buffered: boolean): void {\n return terminadoAttach(this, socket, bidirectional, buffered);\n };\n\n /**\n * Detaches the current terminal from the given socket.\n *\n * @param socket The socket from which to detach the current terminal.\n */\n (<any>terminalConstructor.prototype).terminadoDetach = function (socket: WebSocket): void {\n return terminadoDetach(this, socket);\n };\n}\n",null],"names":[],"mappings":"ACAA;;;ADoBA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAAA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAAA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AAEA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AA/CA;AAuDA;AACA;AACA;AAEA;AAEA;AACA;AACA;AAEA;AACA;AAXA;AAaA;AASA;AACA;AACA;AAOA;AACA;AACA;AACA;AArBA;"}
|
41
public/js/addons/webLinks/webLinks.js
Normal file
41
public/js/addons/webLinks/webLinks.js
Normal file
@ -0,0 +1,41 @@
|
||||
(function(f){if(typeof exports==="object"&&typeof module!=="undefined"){module.exports=f()}else if(typeof define==="function"&&define.amd){define([],f)}else{var g;if(typeof window!=="undefined"){g=window}else if(typeof global!=="undefined"){g=global}else if(typeof self!=="undefined"){g=self}else{g=this}g.webLinks = f()}})(function(){var define,module,exports;return (function(){function r(e,n,t){function o(i,f){if(!n[i]){if(!e[i]){var c="function"==typeof require&&require;if(!f&&c)return c(i,!0);if(u)return u(i,!0);var a=new Error("Cannot find module '"+i+"'");throw a.code="MODULE_NOT_FOUND",a}var p=n[i]={exports:{}};e[i][0].call(p.exports,function(r){var n=e[i][1][r];return o(n||r)},p,p.exports,r,e,n,t)}return n[i].exports}for(var u="function"==typeof require&&require,i=0;i<t.length;i++)o(t[i]);return o}return r})()({1:[function(require,module,exports){
|
||||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
var protocolClause = '(https?:\\/\\/)';
|
||||
var domainCharacterSet = '[\\da-z\\.-]+';
|
||||
var negatedDomainCharacterSet = '[^\\da-z\\.-]+';
|
||||
var domainBodyClause = '(' + domainCharacterSet + ')';
|
||||
var tldClause = '([a-z\\.]{2,6})';
|
||||
var ipClause = '((\\d{1,3}\\.){3}\\d{1,3})';
|
||||
var localHostClause = '(localhost)';
|
||||
var portClause = '(:\\d{1,5})';
|
||||
var hostClause = '((' + domainBodyClause + '\\.' + tldClause + ')|' + ipClause + '|' + localHostClause + ')' + portClause + '?';
|
||||
var pathClause = '(\\/[\\/\\w\\.\\-%~:]*)*([^:"\'\\s])';
|
||||
var queryStringHashFragmentCharacterSet = '[0-9\\w\\[\\]\\(\\)\\/\\?\\!#@$%&\'*+,:;~\\=\\.\\-]*';
|
||||
var queryStringClause = '(\\?' + queryStringHashFragmentCharacterSet + ')?';
|
||||
var hashFragmentClause = '(#' + queryStringHashFragmentCharacterSet + ')?';
|
||||
var negatedPathCharacterSet = '[^\\/\\w\\.\\-%]+';
|
||||
var bodyClause = hostClause + pathClause + queryStringClause + hashFragmentClause;
|
||||
var start = '(?:^|' + negatedDomainCharacterSet + ')(';
|
||||
var end = ')($|' + negatedPathCharacterSet + ')';
|
||||
var strictUrlRegex = new RegExp(start + protocolClause + bodyClause + end);
|
||||
function handleLink(event, uri) {
|
||||
window.open(uri, '_blank');
|
||||
}
|
||||
function webLinksInit(term, handler, options) {
|
||||
if (handler === void 0) { handler = handleLink; }
|
||||
if (options === void 0) { options = {}; }
|
||||
options.matchIndex = 1;
|
||||
term.registerLinkMatcher(strictUrlRegex, handler, options);
|
||||
}
|
||||
exports.webLinksInit = webLinksInit;
|
||||
function apply(terminalConstructor) {
|
||||
terminalConstructor.prototype.webLinksInit = function (handler, options) {
|
||||
webLinksInit(this, handler, options);
|
||||
};
|
||||
}
|
||||
exports.apply = apply;
|
||||
|
||||
},{}]},{},[1])(1)
|
||||
});
|
||||
//# sourceMappingURL=webLinks.js.map
|
1
public/js/addons/webLinks/webLinks.js.map
Normal file
1
public/js/addons/webLinks/webLinks.js.map
Normal file
@ -0,0 +1 @@
|
||||
{"version":3,"file":"webLinks.js","sources":["../../../src/addons/webLinks/webLinks.ts","../../../node_modules/browser-pack/_prelude.js"],"sourcesContent":["/**\n * Copyright (c) 2017 The xterm.js authors. All rights reserved.\n * @license MIT\n */\n\nimport { Terminal, ILinkMatcherOptions } from 'xterm';\n\nconst protocolClause = '(https?:\\\\/\\\\/)';\nconst domainCharacterSet = '[\\\\da-z\\\\.-]+';\nconst negatedDomainCharacterSet = '[^\\\\da-z\\\\.-]+';\nconst domainBodyClause = '(' + domainCharacterSet + ')';\nconst tldClause = '([a-z\\\\.]{2,6})';\nconst ipClause = '((\\\\d{1,3}\\\\.){3}\\\\d{1,3})';\nconst localHostClause = '(localhost)';\nconst portClause = '(:\\\\d{1,5})';\nconst hostClause = '((' + domainBodyClause + '\\\\.' + tldClause + ')|' + ipClause + '|' + localHostClause + ')' + portClause + '?';\nconst pathClause = '(\\\\/[\\\\/\\\\w\\\\.\\\\-%~:]*)*([^:\"\\'\\\\s])';\nconst queryStringHashFragmentCharacterSet = '[0-9\\\\w\\\\[\\\\]\\\\(\\\\)\\\\/\\\\?\\\\!#@$%&\\'*+,:;~\\\\=\\\\.\\\\-]*';\nconst queryStringClause = '(\\\\?' + queryStringHashFragmentCharacterSet + ')?';\nconst hashFragmentClause = '(#' + queryStringHashFragmentCharacterSet + ')?';\nconst negatedPathCharacterSet = '[^\\\\/\\\\w\\\\.\\\\-%]+';\nconst bodyClause = hostClause + pathClause + queryStringClause + hashFragmentClause;\nconst start = '(?:^|' + negatedDomainCharacterSet + ')(';\nconst end = ')($|' + negatedPathCharacterSet + ')';\nconst strictUrlRegex = new RegExp(start + protocolClause + bodyClause + end);\n\nfunction handleLink(event: MouseEvent, uri: string): void {\n window.open(uri, '_blank');\n}\n\n/**\n * Initialize the web links addon, registering the link matcher.\n * @param term The terminal to use web links within.\n * @param handler A custom handler to use.\n * @param options Custom options to use, matchIndex will always be ignored.\n */\nexport function webLinksInit(term: Terminal, handler: (event: MouseEvent, uri: string) => void = handleLink, options: ILinkMatcherOptions = {}): void {\n options.matchIndex = 1;\n term.registerLinkMatcher(strictUrlRegex, handler, options);\n}\n\nexport function apply(terminalConstructor: typeof Terminal): void {\n (<any>terminalConstructor.prototype).webLinksInit = function (handler?: (event: MouseEvent, uri: string) => void, options?: ILinkMatcherOptions): void {\n webLinksInit(this, handler, options);\n };\n}\n",null],"names":[],"mappings":"ACAA;;;ADOA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAQA;AAAA;AAAA;AACA;AACA;AACA;AAHA;AAKA;AACA;AACA;AACA;AACA;AAJA;"}
|
33
public/js/addons/winptyCompat/winptyCompat.js
Normal file
33
public/js/addons/winptyCompat/winptyCompat.js
Normal file
@ -0,0 +1,33 @@
|
||||
(function(f){if(typeof exports==="object"&&typeof module!=="undefined"){module.exports=f()}else if(typeof define==="function"&&define.amd){define([],f)}else{var g;if(typeof window!=="undefined"){g=window}else if(typeof global!=="undefined"){g=global}else if(typeof self!=="undefined"){g=self}else{g=this}g.winptyCompat = f()}})(function(){var define,module,exports;return (function(){function r(e,n,t){function o(i,f){if(!n[i]){if(!e[i]){var c="function"==typeof require&&require;if(!f&&c)return c(i,!0);if(u)return u(i,!0);var a=new Error("Cannot find module '"+i+"'");throw a.code="MODULE_NOT_FOUND",a}var p=n[i]={exports:{}};e[i][0].call(p.exports,function(r){var n=e[i][1][r];return o(n||r)},p,p.exports,r,e,n,t)}return n[i].exports}for(var u="function"==typeof require&&require,i=0;i<t.length;i++)o(t[i]);return o}return r})()({1:[function(require,module,exports){
|
||||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
var CHAR_DATA_CODE_INDEX = 3;
|
||||
var NULL_CELL_CODE = 0;
|
||||
var WHITESPACE_CELL_CODE = 32;
|
||||
function winptyCompatInit(terminal) {
|
||||
var addonTerminal = terminal;
|
||||
var isWindows = ['Windows', 'Win16', 'Win32', 'WinCE'].indexOf(navigator.platform) >= 0;
|
||||
if (!isWindows) {
|
||||
return;
|
||||
}
|
||||
addonTerminal._core.isWinptyCompatEnabled = true;
|
||||
addonTerminal.on('linefeed', function () {
|
||||
var line = addonTerminal._core.buffer.lines.get(addonTerminal._core.buffer.ybase + addonTerminal._core.buffer.y - 1);
|
||||
var lastChar = line.get(addonTerminal.cols - 1);
|
||||
if (lastChar[CHAR_DATA_CODE_INDEX] !== NULL_CELL_CODE && lastChar[CHAR_DATA_CODE_INDEX] !== WHITESPACE_CELL_CODE) {
|
||||
var nextLine = addonTerminal._core.buffer.lines.get(addonTerminal._core.buffer.ybase + addonTerminal._core.buffer.y);
|
||||
nextLine.isWrapped = true;
|
||||
}
|
||||
});
|
||||
}
|
||||
exports.winptyCompatInit = winptyCompatInit;
|
||||
function apply(terminalConstructor) {
|
||||
terminalConstructor.prototype.winptyCompatInit = function () {
|
||||
winptyCompatInit(this);
|
||||
};
|
||||
}
|
||||
exports.apply = apply;
|
||||
|
||||
},{}]},{},[1])(1)
|
||||
});
|
||||
//# sourceMappingURL=winptyCompat.js.map
|
1
public/js/addons/winptyCompat/winptyCompat.js.map
Normal file
1
public/js/addons/winptyCompat/winptyCompat.js.map
Normal file
@ -0,0 +1 @@
|
||||
{"version":3,"file":"winptyCompat.js","sources":["../../../src/addons/winptyCompat/winptyCompat.ts","../../../node_modules/browser-pack/_prelude.js"],"sourcesContent":["/**\n * Copyright (c) 2017 The xterm.js authors. All rights reserved.\n * @license MIT\n */\n\nimport { Terminal } from 'xterm';\nimport { IWinptyCompatAddonTerminal } from './Interfaces';\n\nconst CHAR_DATA_CODE_INDEX = 3;\nconst NULL_CELL_CODE = 0;\nconst WHITESPACE_CELL_CODE = 32;\n\nexport function winptyCompatInit(terminal: Terminal): void {\n const addonTerminal = <IWinptyCompatAddonTerminal>terminal;\n\n // Don't do anything when the platform is not Windows\n const isWindows = ['Windows', 'Win16', 'Win32', 'WinCE'].indexOf(navigator.platform) >= 0;\n if (!isWindows) {\n return;\n }\n\n (addonTerminal._core as any).isWinptyCompatEnabled = true;\n\n // Winpty does not support wraparound mode which means that lines will never\n // be marked as wrapped. This causes issues for things like copying a line\n // retaining the wrapped new line characters or if consumers are listening\n // in on the data stream.\n //\n // The workaround for this is to listen to every incoming line feed and mark\n // the line as wrapped if the last character in the previous line is not a\n // space. This is certainly not without its problems, but generally on\n // Windows when text reaches the end of the terminal it's likely going to be\n // wrapped.\n addonTerminal.on('linefeed', () => {\n const line = addonTerminal._core.buffer.lines.get(addonTerminal._core.buffer.ybase + addonTerminal._core.buffer.y - 1);\n const lastChar = line.get(addonTerminal.cols - 1);\n\n if (lastChar[CHAR_DATA_CODE_INDEX] !== NULL_CELL_CODE && lastChar[CHAR_DATA_CODE_INDEX] !== WHITESPACE_CELL_CODE) {\n const nextLine = addonTerminal._core.buffer.lines.get(addonTerminal._core.buffer.ybase + addonTerminal._core.buffer.y);\n nextLine.isWrapped = true;\n }\n });\n}\n\nexport function apply(terminalConstructor: typeof Terminal): void {\n (<any>terminalConstructor.prototype).winptyCompatInit = function (): void {\n winptyCompatInit(this);\n };\n}\n",null],"names":[],"mappings":"ACAA;;;ADQA;AACA;AACA;AAEA;AACA;AAGA;AACA;AACA;AACA;AAEA;AAYA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AA9BA;AAgCA;AACA;AACA;AACA;AACA;AAJA;"}
|
45
public/js/addons/zmodem/zmodem.js
Normal file
45
public/js/addons/zmodem/zmodem.js
Normal file
@ -0,0 +1,45 @@
|
||||
(function(f){if(typeof exports==="object"&&typeof module!=="undefined"){module.exports=f()}else if(typeof define==="function"&&define.amd){define([],f)}else{var g;if(typeof window!=="undefined"){g=window}else if(typeof global!=="undefined"){g=global}else if(typeof self!=="undefined"){g=self}else{g=this}g.zmodem = f()}})(function(){var define,module,exports;return (function(){function r(e,n,t){function o(i,f){if(!n[i]){if(!e[i]){var c="function"==typeof require&&require;if(!f&&c)return c(i,!0);if(u)return u(i,!0);var a=new Error("Cannot find module '"+i+"'");throw a.code="MODULE_NOT_FOUND",a}var p=n[i]={exports:{}};e[i][0].call(p.exports,function(r){var n=e[i][1][r];return o(n||r)},p,p.exports,r,e,n,t)}return n[i].exports}for(var u="function"==typeof require&&require,i=0;i<t.length;i++)o(t[i]);return o}return r})()({1:[function(require,module,exports){
|
||||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
var zmodem;
|
||||
function zmodemAttach(ws, opts) {
|
||||
if (opts === void 0) { opts = {}; }
|
||||
var term = this;
|
||||
var senderFunc = function (octets) { return ws.send(new Uint8Array(octets)); };
|
||||
var zsentry;
|
||||
function shouldWrite() {
|
||||
return !!zsentry.get_confirmed_session() || !opts.noTerminalWriteOutsideSession;
|
||||
}
|
||||
zsentry = new zmodem.Sentry({
|
||||
to_terminal: function (octets) {
|
||||
if (shouldWrite()) {
|
||||
term.write(String.fromCharCode.apply(String, octets));
|
||||
}
|
||||
},
|
||||
sender: senderFunc,
|
||||
on_retract: function () { return term.emit('zmodemRetract'); },
|
||||
on_detect: function (detection) { return term.emit('zmodemDetect', detection); }
|
||||
});
|
||||
function handleWSMessage(evt) {
|
||||
if (typeof evt.data === 'string') {
|
||||
if (shouldWrite()) {
|
||||
term.write(evt.data);
|
||||
}
|
||||
}
|
||||
else {
|
||||
zsentry.consume(evt.data);
|
||||
}
|
||||
}
|
||||
ws.binaryType = 'arraybuffer';
|
||||
ws.addEventListener('message', handleWSMessage);
|
||||
}
|
||||
function apply(terminalConstructor) {
|
||||
zmodem = (typeof window === 'object') ? window.Zmodem : { Browser: null };
|
||||
terminalConstructor.prototype.zmodemAttach = zmodemAttach;
|
||||
terminalConstructor.prototype.zmodemBrowser = zmodem.Browser;
|
||||
}
|
||||
exports.apply = apply;
|
||||
|
||||
},{}]},{},[1])(1)
|
||||
});
|
||||
//# sourceMappingURL=zmodem.js.map
|
1
public/js/addons/zmodem/zmodem.js.map
Normal file
1
public/js/addons/zmodem/zmodem.js.map
Normal file
@ -0,0 +1 @@
|
||||
{"version":3,"file":"zmodem.js","sources":["../../../src/addons/zmodem/zmodem.ts","../../../node_modules/browser-pack/_prelude.js"],"sourcesContent":["/**\n * Copyright (c) 2017 The xterm.js authors. All rights reserved.\n * @license MIT\n */\n\nimport { Terminal } from 'xterm';\n\n/**\n *\n * Allow xterm.js to handle ZMODEM uploads and downloads.\n *\n * This addon is a wrapper around zmodem.js. It adds the following to the\n * Terminal class:\n *\n * - function `zmodemAttach(<WebSocket>, <Object>)` - creates a Zmodem.Sentry\n * on the passed WebSocket object. The Object passed is optional and\n * can contain:\n * - noTerminalWriteOutsideSession: Suppress writes from the Sentry\n * object to the Terminal while there is no active Session. This\n * is necessary for compatibility with, for example, the\n * `attach.js` addon.\n *\n * - event `zmodemDetect` - fired on Zmodem.Sentry’s `on_detect` callback.\n * Passes the zmodem.js Detection object.\n *\n * - event `zmodemRetract` - fired on Zmodem.Sentry’s `on_retract` callback.\n *\n * You’ll need to provide logic to handle uploads and downloads.\n * See zmodem.js’s documentation for more details.\n *\n * **IMPORTANT:** After you confirm() a zmodem.js Detection, if you have\n * used the `attach` or `terminado` addons, you’ll need to suspend their\n * operation for the duration of the ZMODEM session. (The demo does this\n * via `detach()` and a re-`attach()`.)\n */\n\nlet zmodem: any;\n\nexport interface IZmodemOptions {\n noTerminalWriteOutsideSession?: boolean;\n}\n\nfunction zmodemAttach(ws: WebSocket, opts: IZmodemOptions = {}): void {\n const term = this;\n const senderFunc = (octets: ArrayLike<number>) => ws.send(new Uint8Array(octets));\n\n let zsentry: any;\n\n function shouldWrite(): boolean {\n return !!zsentry.get_confirmed_session() || !opts.noTerminalWriteOutsideSession;\n }\n\n zsentry = new zmodem.Sentry({\n to_terminal: (octets: ArrayLike<number>) => {\n if (shouldWrite()) {\n term.write(\n String.fromCharCode.apply(String, octets)\n );\n }\n },\n sender: senderFunc,\n on_retract: () => (<any>term).emit('zmodemRetract'),\n on_detect: (detection: any) => (<any>term).emit('zmodemDetect', detection)\n });\n\n function handleWSMessage(evt: MessageEvent): void {\n\n // In testing with xterm.js’s demo the first message was\n // always text even if the rest were binary. While that\n // may be specific to xterm.js’s demo, ultimately we\n // should reject anything that isn’t binary.\n if (typeof evt.data === 'string') {\n if (shouldWrite()) {\n term.write(evt.data);\n }\n }\n else {\n zsentry.consume(evt.data);\n }\n }\n\n ws.binaryType = 'arraybuffer';\n ws.addEventListener('message', handleWSMessage);\n}\n\nexport function apply(terminalConstructor: typeof Terminal): void {\n zmodem = (typeof window === 'object') ? (<any>window).Zmodem : {Browser: null}; // Nullify browser for tests\n\n (<any>terminalConstructor.prototype).zmodemAttach = zmodemAttach;\n (<any>terminalConstructor.prototype).zmodemBrowser = zmodem.Browser;\n}\n",null],"names":[],"mappings":"ACAA;;;ADoCA;AAMA;AAAA;AACA;AACA;AAEA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AAGA;AACA;AACA;AACA;AACA;AACA;AAEA;AAMA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AALA;"}
|
187
public/js/jsonToTable.js
Normal file
187
public/js/jsonToTable.js
Normal file
@ -0,0 +1,187 @@
|
||||
/* eslint-disable no-extend-native */
|
||||
// https://github.com/afshinm/Json-to-HTML-Table/blob/master/json-to-table.js
|
||||
/**
|
||||
* JavaScript format string function
|
||||
*
|
||||
*/
|
||||
String.prototype.format = function () {
|
||||
var args = arguments
|
||||
return this.replace(/{(\d+)}/g, function (match, number) {
|
||||
return typeof args[number] !== 'undefined' ? args[number]
|
||||
: '{' + number + '}'
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Return just the keys from the input array, optionally only for the specified search_value
|
||||
* version: 1109.2015
|
||||
* discuss at: http://phpjs.org/functions/arrayKeys
|
||||
* + original by: Kevin van Zonneveld (http://kevin.vanzonneveld.net)
|
||||
* + input by: Brett Zamir (http://brett-zamir.me)
|
||||
* + bugfixed by: Kevin van Zonneveld (http://kevin.vanzonneveld.net)
|
||||
* + improved by: jd
|
||||
* + improved by: Brett Zamir (http://brett-zamir.me)
|
||||
* + input by: P
|
||||
* + bugfixed by: Brett Zamir (http://brett-zamir.me)
|
||||
* * example 1: arrayKeys( {firstname: 'Kevin', surname: 'van Zonneveld'} );
|
||||
* * returns 1: {0: 'firstname', 1: 'surname'}
|
||||
*/
|
||||
const arrayKeys = (input, searchValue, argStrict) => {
|
||||
var search = typeof searchValue !== 'undefined'
|
||||
var tmpArr = []
|
||||
var strict = !!argStrict
|
||||
var include = true
|
||||
var key = ''
|
||||
|
||||
if (input && typeof input === 'object' && input.change_key_case) { // Duck-type check for our own array()-created PHPJS_Array
|
||||
return input.keys(searchValue, argStrict)
|
||||
}
|
||||
|
||||
for (key in input) {
|
||||
if (input.hasOwnProperty(key)) {
|
||||
include = true
|
||||
if (search) {
|
||||
if (strict && input[key] !== searchValue) {
|
||||
include = false
|
||||
} else if (input[key] !== searchValue) {
|
||||
include = false
|
||||
}
|
||||
}
|
||||
if (include) {
|
||||
tmpArr[tmpArr.length] = key
|
||||
}
|
||||
}
|
||||
}
|
||||
return tmpArr
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert a Javascript Oject array or String array to an HTML table
|
||||
* JSON parsing has to be made before function call
|
||||
* It allows use of other JSON parsing methods like jQuery.parseJSON
|
||||
* http(s)://, ftp://, file:// and javascript:; links are automatically computed
|
||||
*
|
||||
* JSON data samples that should be parsed and then can be converted to an HTML table
|
||||
* var objectArray = '[{"Total":"34","Version":"1.0.4","Office":"New York"},{"Total":"67","Version":"1.1.0","Office":"Paris"}]';
|
||||
* var stringArray = '["New York","Berlin","Paris","Marrakech","Moscow"]';
|
||||
* var nestedTable = '[{ key1: "val1", key2: "val2", key3: { tableId: "tblIdNested1", tableClassName: "clsNested", linkText: "Download", data: [{ subkey1: "subval1", subkey2: "subval2", subkey3: "subval3" }] } }]';
|
||||
*
|
||||
* Code sample to create a HTML table Javascript String
|
||||
* var jsonHtmlTable = ConvertJsonToTable(eval(dataString), 'jsonTable', null, 'Download');
|
||||
*
|
||||
* Code sample explaned
|
||||
* - eval is used to parse a JSON dataString
|
||||
* - table HTML id attribute will be 'jsonTable'
|
||||
* - table HTML class attribute will not be added
|
||||
* - 'Download' text will be displayed instead of the link itself
|
||||
*
|
||||
* @author Afshin Mehrabani <afshin dot meh at gmail dot com>
|
||||
*
|
||||
* @class ConvertJsonToTable
|
||||
*
|
||||
* @method ConvertJsonToTable
|
||||
*
|
||||
* @param parsedJson object Parsed JSON data
|
||||
* @param tableId string Optional table id
|
||||
* @param tableClassName string Optional table css class name
|
||||
* @param linkText string Optional text replacement for link pattern
|
||||
*
|
||||
* @return string Converted JSON to HTML table
|
||||
*/
|
||||
|
||||
const convertJsonToTable = (parsedJson, tableId, tableClassName, linkText) => {
|
||||
// Patterns for links and NULL value
|
||||
let italic = '<i>{0}</i>'
|
||||
let link = linkText ? '<a href="{0}">' + linkText + '</a>'
|
||||
: '<a href="{0}">{0}</a>'
|
||||
let log = '<a href="javascript:showLog(\'{0}\')">{0}</a>'
|
||||
|
||||
// Pattern for table
|
||||
let idMarkup = tableId ? ' id="' + tableId + '"'
|
||||
: ''
|
||||
|
||||
let classMarkup = tableClassName ? ' class="' + tableClassName + '"'
|
||||
: ''
|
||||
|
||||
let tbl = '<table border="1" cellpadding="1" cellspacing="1"' + idMarkup + classMarkup + '>{0}{1}</table>'
|
||||
|
||||
// Patterns for table content
|
||||
let th = '<thead>{0}</thead>'
|
||||
let tb = '<tbody>{0}</tbody>'
|
||||
let tr = '<tr>{0}</tr>'
|
||||
let thRow = '<th>{0}</th>'
|
||||
let tdRow = '<td>{0}</td>'
|
||||
let thCon = ''
|
||||
let tbCon = ''
|
||||
let trCon = ''
|
||||
|
||||
if (parsedJson) {
|
||||
let isStringArray = typeof (parsedJson[0]) === 'string'
|
||||
let headers
|
||||
|
||||
// Create table headers from JSON data
|
||||
// If JSON data is a simple string array we create a single table header
|
||||
if (isStringArray) {
|
||||
thCon += thRow.format('value')
|
||||
} else {
|
||||
// If JSON data is an object array, headers are automatically computed
|
||||
if (typeof (parsedJson[0]) === 'object') {
|
||||
headers = arrayKeys(parsedJson[0])
|
||||
|
||||
for (let i = 0; i < headers.length; i++) {
|
||||
thCon += thRow.format(headers[i])
|
||||
}
|
||||
}
|
||||
}
|
||||
th = th.format(tr.format(thCon))
|
||||
|
||||
// Create table rows from Json data
|
||||
if (isStringArray) {
|
||||
for (let i = 0; i < parsedJson.length; i++) {
|
||||
tbCon += tdRow.format(parsedJson[i])
|
||||
trCon += tr.format(tbCon)
|
||||
tbCon = ''
|
||||
}
|
||||
} else {
|
||||
if (headers) {
|
||||
let urlRegExp = new RegExp(/(\b(https?|ftp|file):\/\/[-A-Z0-9+&@#\/%?=~_|!:,.;]*[-A-Z0-9+&@#\/%=~_|])/ig) // eslint-disable-line
|
||||
let javascriptRegExp = new RegExp(/(^javascript:[\s\S]*;$)/ig)
|
||||
let logRegExp = new RegExp(/(.log$)/ig)
|
||||
|
||||
for (let i = 0; i < parsedJson.length; i++) {
|
||||
for (let j = 0; j < headers.length; j++) {
|
||||
let value = parsedJson[i][headers[j]]
|
||||
let isUrl = urlRegExp.test(value) || javascriptRegExp.test(value)
|
||||
let isLog = logRegExp.test(value)
|
||||
|
||||
if (isUrl) { // If value is URL we auto-create a link
|
||||
tbCon += tdRow.format(link.format(value))
|
||||
} else if (isLog) {
|
||||
tbCon += tdRow.format(log.format(value))
|
||||
} else {
|
||||
if (value) {
|
||||
if (typeof (value) === 'object') {
|
||||
// for supporting nested tables
|
||||
tbCon += tdRow.format(convertJsonToTable(eval(value.data), value.tableId, value.tableClassName, value.linkText)) // eslint-disable-line
|
||||
} else {
|
||||
tbCon += tdRow.format(value)
|
||||
}
|
||||
} else { // If value == null we format it like PhpMyAdmin NULL values
|
||||
tbCon += tdRow.format(italic.format(value).toUpperCase())
|
||||
}
|
||||
}
|
||||
}
|
||||
trCon += tr.format(tbCon)
|
||||
tbCon = ''
|
||||
}
|
||||
}
|
||||
}
|
||||
tb = tb.format(trCon)
|
||||
tbl = tbl.format(th, tb)
|
||||
|
||||
return tbl
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
window.convertJsonToTable = convertJsonToTable
|
252
public/js/main.js
Normal file
252
public/js/main.js
Normal file
@ -0,0 +1,252 @@
|
||||
window.onload = function () {
|
||||
let buildType = null;
|
||||
Terminal.applyAddon(attach);
|
||||
Terminal.applyAddon(fit);
|
||||
const term = new Terminal({
|
||||
useStyle: true,
|
||||
convertEol: true,
|
||||
screenKeys: true,
|
||||
cursorBlink: false,
|
||||
visualBell: true,
|
||||
colors: Terminal.xtermColors
|
||||
});
|
||||
window.term = term;
|
||||
const terminalDom = document.getElementById('terminal');
|
||||
const socket = io();
|
||||
|
||||
// 对Date的扩展,将 Date 转化为指定格式的String
|
||||
// 月(M)、日(d)、小时(h)、分(m)、秒(s)、季度(q) 可以用 1-2 个占位符,
|
||||
// 年(y)可以用 1-4 个占位符,毫秒(S)只能用 1 个占位符(是 1-3 位的数字)
|
||||
// 例子:
|
||||
// (new Date()).Format("yyyy-MM-dd hh:mm:ss.S") ==> 2006-07-02 08:09:04.423
|
||||
// (new Date()).Format("yyyy-M-d h:m:s.S") ==> 2006-7-2 8:9:4.18
|
||||
Date.prototype.Format = function (fmt) { //author: meizz
|
||||
var o = {
|
||||
"M+" : this.getMonth()+1, //月份
|
||||
"d+" : this.getDate(), //日
|
||||
"h+" : this.getHours(), //小时
|
||||
"m+" : this.getMinutes(), //分
|
||||
"s+" : this.getSeconds(), //秒
|
||||
"q+" : Math.floor((this.getMonth()+3)/3), //季度
|
||||
"S" : this.getMilliseconds() //毫秒
|
||||
};
|
||||
if(/(y+)/.test(fmt))
|
||||
fmt=fmt.replace(RegExp.$1, (this.getFullYear()+"").substr(4 - RegExp.$1.length));
|
||||
for(var k in o)
|
||||
if(new RegExp("("+ k +")").test(fmt))
|
||||
fmt = fmt.replace(RegExp.$1, (RegExp.$1.length==1) ? (o[k]) : (("00"+ o[k]).substr((""+ o[k]).length)));
|
||||
return fmt;
|
||||
}
|
||||
|
||||
const checkToken = () => {
|
||||
if (window.localStorage.token) {
|
||||
document.querySelector('#no-auth').style.display = 'none';
|
||||
document.querySelector('#auth').style.display = 'block';
|
||||
return true;
|
||||
} else {
|
||||
document.querySelector('#no-auth').style.display = 'block';
|
||||
document.querySelector('#auth').style.display = 'none';
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
const dockerInitailize = () => {
|
||||
socket.emit('pull', 'win');
|
||||
socket.emit('pull', 'linux');
|
||||
term.on('data', (data) => {
|
||||
socket.emit('cmd', data);
|
||||
})
|
||||
}
|
||||
|
||||
const showLog = (path) => {
|
||||
term.clear();
|
||||
term.writeln('Loading...');
|
||||
socket.emit('log', encodeURI(path));
|
||||
}
|
||||
|
||||
const getHash = () => {
|
||||
try {
|
||||
const shaObj = new jsSHA("SHA-512", "TEXT");
|
||||
shaObj.update(window.localStorage.token);
|
||||
return shaObj.getHash("HEX");
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
alert(err.toString());
|
||||
}
|
||||
}
|
||||
|
||||
const checkAuth = async () => {
|
||||
try {
|
||||
const hash = getHash();
|
||||
const response = await fetch('/app/auth?token=' + hash);
|
||||
const data = await response.json();
|
||||
if (data.status === 1) {
|
||||
buildType = data.result.buildType;
|
||||
if (!buildType.includes('linux')) document.getElementById('buildLinuxBtn').style.display = 'none';
|
||||
if (!buildType.includes('win')) document.getElementById('buildWindowsBtn').style.display = 'none';
|
||||
if (!buildType.includes('mac')) document.getElementById('buildMacBtn').style.display = 'none';
|
||||
} else {
|
||||
alert(data.msg);
|
||||
window.localStorage.removeItem('token');
|
||||
window.location.reload();
|
||||
return false;
|
||||
}
|
||||
|
||||
socket.on('requireAuth', (data) => {
|
||||
if (data === 'distribution') {
|
||||
socket.emit('auth', hash);
|
||||
}
|
||||
})
|
||||
|
||||
socket.on('auth', (data) => {
|
||||
if (data === 'success') {
|
||||
term.clear();
|
||||
term.write('Welcome \x1B[1;3;31mElectron Distribution\x1B[0m ! \n');
|
||||
} else {
|
||||
term.clear();
|
||||
term.write('Auth fail. \n');
|
||||
}
|
||||
})
|
||||
|
||||
return true;
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
alert('Network Error.');
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
const gitPull = () => {
|
||||
socket.emit('gitPull', '');
|
||||
}
|
||||
|
||||
const buildApp = async (type) => {
|
||||
document.getElementById('table').innerHTML = '';
|
||||
term.clear();
|
||||
term.writeln('Loading...');
|
||||
|
||||
try {
|
||||
const hash = getHash();
|
||||
const response = await fetch('/build/' + type + '?token=' + hash);
|
||||
const data = await response.json();
|
||||
if (data.status === 1) {
|
||||
term.clear();
|
||||
term.write('Runing yarn... \n');
|
||||
socket.emit('logs', data.result);
|
||||
} else {
|
||||
alert(data.msg);
|
||||
}
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
alert('Network Error.');
|
||||
}
|
||||
}
|
||||
|
||||
const getLocalTime = (nS) => {
|
||||
return new Date(Number(nS)).Format("yyyy-MM-dd hh:mm:ss");
|
||||
}
|
||||
|
||||
const getList = async (type) => {
|
||||
try {
|
||||
const hash = getHash();
|
||||
const response = await fetch('/app/list/' + type + '?token=' + hash);
|
||||
const data = await response.json();
|
||||
if (data.status === 1) {
|
||||
if (data.result.list && data.result.list.length) {
|
||||
for (const n in data.result.list) {
|
||||
if (data.result.list[n].startDate) {
|
||||
data.result.list[n].startDate = getLocalTime(data.result.list[n].startDate)
|
||||
} else if (data.result.list[n].releaseDate) {
|
||||
data.result.list[n].releaseDate = getLocalTime(data.result.list[n].releaseDate)
|
||||
}
|
||||
}
|
||||
document.getElementById('table').innerHTML = convertJsonToTable(data.result.list);
|
||||
} else {
|
||||
document.getElementById('table').innerHTML = 'No data.';
|
||||
}
|
||||
} else {
|
||||
alert(data.msg);
|
||||
}
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
alert('Network Error.');
|
||||
}
|
||||
}
|
||||
|
||||
const init = async () => {
|
||||
if (!checkToken()) return;
|
||||
|
||||
if (!(await checkAuth())) return;
|
||||
|
||||
window.showLog = showLog;
|
||||
|
||||
socket.on('show', (data) => {
|
||||
term.write(data);
|
||||
})
|
||||
|
||||
socket.on('progress', (data) => {
|
||||
term.clear();
|
||||
term.write(data);
|
||||
})
|
||||
|
||||
socket.on('end', (data) => {
|
||||
term.write(data);
|
||||
})
|
||||
|
||||
socket.on('err', (data) => {
|
||||
term.write(data);
|
||||
})
|
||||
|
||||
term.open(terminalDom);
|
||||
term.fit();
|
||||
term.write('Verifying... Please wait ...\n');
|
||||
}
|
||||
|
||||
document.querySelector('#tokenInput').onkeyup = function (e) {
|
||||
if (e && e.keyCode == 13) {
|
||||
window.localStorage.token = document.querySelector('#tokenInput').value;
|
||||
window.location.reload();
|
||||
}
|
||||
}
|
||||
|
||||
document.querySelector('#logoutBtn').onclick = function () {
|
||||
window.localStorage.removeItem('token');
|
||||
window.location.reload();
|
||||
}
|
||||
|
||||
document.querySelector('#listReleaseBtn').onclick = function () {
|
||||
getList('release')
|
||||
}
|
||||
|
||||
document.querySelector('#listBuildBtn').onclick = function () {
|
||||
getList('build')
|
||||
}
|
||||
|
||||
document.querySelector('#dockerInitBtn').onclick = function () {
|
||||
document.getElementById('table').innerHTML = '';
|
||||
term.clear();
|
||||
term.writeln('Loading...');
|
||||
dockerInitailize();
|
||||
}
|
||||
|
||||
document.querySelector('#gitPullBtn').onclick = function () {
|
||||
document.getElementById('table').innerHTML = '';
|
||||
term.clear();
|
||||
term.writeln('Loading...');
|
||||
gitPull();
|
||||
}
|
||||
|
||||
document.querySelector('#buildLinuxBtn').onclick = function () {
|
||||
buildApp('linux');
|
||||
}
|
||||
|
||||
document.querySelector('#buildWindowsBtn').onclick = function () {
|
||||
buildApp('win');
|
||||
}
|
||||
|
||||
document.querySelector('#buildMacBtn').onclick = function () {
|
||||
buildApp('mac');
|
||||
}
|
||||
|
||||
init();
|
||||
}
|
42
public/js/sha.js
Normal file
42
public/js/sha.js
Normal file
@ -0,0 +1,42 @@
|
||||
/*
|
||||
A JavaScript implementation of the SHA family of hashes, as
|
||||
defined in FIPS PUB 180-4 and FIPS PUB 202, as well as the corresponding
|
||||
HMAC implementation as defined in FIPS PUB 198a
|
||||
Copyright 2008-2018 Brian Turek, 1998-2009 Paul Johnston & Contributors
|
||||
Distributed under the BSD License
|
||||
See http://caligatio.github.com/jsSHA/ for more information
|
||||
*/
|
||||
'use strict';(function(Y){function C(c,a,b){var e=0,h=[],n=0,g,l,d,f,m,q,u,r,I=!1,v=[],w=[],t,y=!1,z=!1,x=-1;b=b||{};g=b.encoding||"UTF8";t=b.numRounds||1;if(t!==parseInt(t,10)||1>t)throw Error("numRounds must a integer >= 1");if("SHA-1"===c)m=512,q=K,u=Z,f=160,r=function(a){return a.slice()};else if(0===c.lastIndexOf("SHA-",0))if(q=function(a,b){return L(a,b,c)},u=function(a,b,h,e){var k,f;if("SHA-224"===c||"SHA-256"===c)k=(b+65>>>9<<4)+15,f=16;else if("SHA-384"===c||"SHA-512"===c)k=(b+129>>>10<<
|
||||
5)+31,f=32;else throw Error("Unexpected error in SHA-2 implementation");for(;a.length<=k;)a.push(0);a[b>>>5]|=128<<24-b%32;b=b+h;a[k]=b&4294967295;a[k-1]=b/4294967296|0;h=a.length;for(b=0;b<h;b+=f)e=L(a.slice(b,b+f),e,c);if("SHA-224"===c)a=[e[0],e[1],e[2],e[3],e[4],e[5],e[6]];else if("SHA-256"===c)a=e;else if("SHA-384"===c)a=[e[0].a,e[0].b,e[1].a,e[1].b,e[2].a,e[2].b,e[3].a,e[3].b,e[4].a,e[4].b,e[5].a,e[5].b];else if("SHA-512"===c)a=[e[0].a,e[0].b,e[1].a,e[1].b,e[2].a,e[2].b,e[3].a,e[3].b,e[4].a,
|
||||
e[4].b,e[5].a,e[5].b,e[6].a,e[6].b,e[7].a,e[7].b];else throw Error("Unexpected error in SHA-2 implementation");return a},r=function(a){return a.slice()},"SHA-224"===c)m=512,f=224;else if("SHA-256"===c)m=512,f=256;else if("SHA-384"===c)m=1024,f=384;else if("SHA-512"===c)m=1024,f=512;else throw Error("Chosen SHA variant is not supported");else if(0===c.lastIndexOf("SHA3-",0)||0===c.lastIndexOf("SHAKE",0)){var F=6;q=D;r=function(a){var c=[],e;for(e=0;5>e;e+=1)c[e]=a[e].slice();return c};x=1;if("SHA3-224"===
|
||||
c)m=1152,f=224;else if("SHA3-256"===c)m=1088,f=256;else if("SHA3-384"===c)m=832,f=384;else if("SHA3-512"===c)m=576,f=512;else if("SHAKE128"===c)m=1344,f=-1,F=31,z=!0;else if("SHAKE256"===c)m=1088,f=-1,F=31,z=!0;else throw Error("Chosen SHA variant is not supported");u=function(a,c,e,b,h){e=m;var k=F,f,g=[],n=e>>>5,l=0,d=c>>>5;for(f=0;f<d&&c>=e;f+=n)b=D(a.slice(f,f+n),b),c-=e;a=a.slice(f);for(c%=e;a.length<n;)a.push(0);f=c>>>3;a[f>>2]^=k<<f%4*8;a[n-1]^=2147483648;for(b=D(a,b);32*g.length<h;){a=b[l%
|
||||
5][l/5|0];g.push(a.b);if(32*g.length>=h)break;g.push(a.a);l+=1;0===64*l%e&&D(null,b)}return g}}else throw Error("Chosen SHA variant is not supported");d=M(a,g,x);l=A(c);this.setHMACKey=function(a,b,h){var k;if(!0===I)throw Error("HMAC key already set");if(!0===y)throw Error("Cannot set HMAC key after calling update");if(!0===z)throw Error("SHAKE is not supported for HMAC");g=(h||{}).encoding||"UTF8";b=M(b,g,x)(a);a=b.binLen;b=b.value;k=m>>>3;h=k/4-1;if(k<a/8){for(b=u(b,a,0,A(c),f);b.length<=h;)b.push(0);
|
||||
b[h]&=4294967040}else if(k>a/8){for(;b.length<=h;)b.push(0);b[h]&=4294967040}for(a=0;a<=h;a+=1)v[a]=b[a]^909522486,w[a]=b[a]^1549556828;l=q(v,l);e=m;I=!0};this.update=function(a){var c,b,k,f=0,g=m>>>5;c=d(a,h,n);a=c.binLen;b=c.value;c=a>>>5;for(k=0;k<c;k+=g)f+m<=a&&(l=q(b.slice(k,k+g),l),f+=m);e+=f;h=b.slice(f>>>5);n=a%m;y=!0};this.getHash=function(a,b){var k,g,d,m;if(!0===I)throw Error("Cannot call getHash after setting HMAC key");d=N(b);if(!0===z){if(-1===d.shakeLen)throw Error("shakeLen must be specified in options");
|
||||
f=d.shakeLen}switch(a){case "HEX":k=function(a){return O(a,f,x,d)};break;case "B64":k=function(a){return P(a,f,x,d)};break;case "BYTES":k=function(a){return Q(a,f,x)};break;case "ARRAYBUFFER":try{g=new ArrayBuffer(0)}catch(p){throw Error("ARRAYBUFFER not supported by this environment");}k=function(a){return R(a,f,x)};break;default:throw Error("format must be HEX, B64, BYTES, or ARRAYBUFFER");}m=u(h.slice(),n,e,r(l),f);for(g=1;g<t;g+=1)!0===z&&0!==f%32&&(m[m.length-1]&=16777215>>>24-f%32),m=u(m,f,
|
||||
0,A(c),f);return k(m)};this.getHMAC=function(a,b){var k,g,d,p;if(!1===I)throw Error("Cannot call getHMAC without first setting HMAC key");d=N(b);switch(a){case "HEX":k=function(a){return O(a,f,x,d)};break;case "B64":k=function(a){return P(a,f,x,d)};break;case "BYTES":k=function(a){return Q(a,f,x)};break;case "ARRAYBUFFER":try{k=new ArrayBuffer(0)}catch(v){throw Error("ARRAYBUFFER not supported by this environment");}k=function(a){return R(a,f,x)};break;default:throw Error("outputFormat must be HEX, B64, BYTES, or ARRAYBUFFER");
|
||||
}g=u(h.slice(),n,e,r(l),f);p=q(w,A(c));p=u(g,f,m,p,f);return k(p)}}function b(c,a){this.a=c;this.b=a}function O(c,a,b,e){var h="";a/=8;var n,g,d;d=-1===b?3:0;for(n=0;n<a;n+=1)g=c[n>>>2]>>>8*(d+n%4*b),h+="0123456789abcdef".charAt(g>>>4&15)+"0123456789abcdef".charAt(g&15);return e.outputUpper?h.toUpperCase():h}function P(c,a,b,e){var h="",n=a/8,g,d,p,f;f=-1===b?3:0;for(g=0;g<n;g+=3)for(d=g+1<n?c[g+1>>>2]:0,p=g+2<n?c[g+2>>>2]:0,p=(c[g>>>2]>>>8*(f+g%4*b)&255)<<16|(d>>>8*(f+(g+1)%4*b)&255)<<8|p>>>8*(f+
|
||||
(g+2)%4*b)&255,d=0;4>d;d+=1)8*g+6*d<=a?h+="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/".charAt(p>>>6*(3-d)&63):h+=e.b64Pad;return h}function Q(c,a,b){var e="";a/=8;var h,d,g;g=-1===b?3:0;for(h=0;h<a;h+=1)d=c[h>>>2]>>>8*(g+h%4*b)&255,e+=String.fromCharCode(d);return e}function R(c,a,b){a/=8;var e,h=new ArrayBuffer(a),d,g;g=new Uint8Array(h);d=-1===b?3:0;for(e=0;e<a;e+=1)g[e]=c[e>>>2]>>>8*(d+e%4*b)&255;return h}function N(c){var a={outputUpper:!1,b64Pad:"=",shakeLen:-1};c=c||{};
|
||||
a.outputUpper=c.outputUpper||!1;!0===c.hasOwnProperty("b64Pad")&&(a.b64Pad=c.b64Pad);if(!0===c.hasOwnProperty("shakeLen")){if(0!==c.shakeLen%8)throw Error("shakeLen must be a multiple of 8");a.shakeLen=c.shakeLen}if("boolean"!==typeof a.outputUpper)throw Error("Invalid outputUpper formatting option");if("string"!==typeof a.b64Pad)throw Error("Invalid b64Pad formatting option");return a}function M(c,a,b){switch(a){case "UTF8":case "UTF16BE":case "UTF16LE":break;default:throw Error("encoding must be UTF8, UTF16BE, or UTF16LE");
|
||||
}switch(c){case "HEX":c=function(a,c,d){var g=a.length,l,p,f,m,q,u;if(0!==g%2)throw Error("String of HEX type must be in byte increments");c=c||[0];d=d||0;q=d>>>3;u=-1===b?3:0;for(l=0;l<g;l+=2){p=parseInt(a.substr(l,2),16);if(isNaN(p))throw Error("String of HEX type contains invalid characters");m=(l>>>1)+q;for(f=m>>>2;c.length<=f;)c.push(0);c[f]|=p<<8*(u+m%4*b)}return{value:c,binLen:4*g+d}};break;case "TEXT":c=function(c,h,d){var g,l,p=0,f,m,q,u,r,t;h=h||[0];d=d||0;q=d>>>3;if("UTF8"===a)for(t=-1===
|
||||
b?3:0,f=0;f<c.length;f+=1)for(g=c.charCodeAt(f),l=[],128>g?l.push(g):2048>g?(l.push(192|g>>>6),l.push(128|g&63)):55296>g||57344<=g?l.push(224|g>>>12,128|g>>>6&63,128|g&63):(f+=1,g=65536+((g&1023)<<10|c.charCodeAt(f)&1023),l.push(240|g>>>18,128|g>>>12&63,128|g>>>6&63,128|g&63)),m=0;m<l.length;m+=1){r=p+q;for(u=r>>>2;h.length<=u;)h.push(0);h[u]|=l[m]<<8*(t+r%4*b);p+=1}else if("UTF16BE"===a||"UTF16LE"===a)for(t=-1===b?2:0,l="UTF16LE"===a&&1!==b||"UTF16LE"!==a&&1===b,f=0;f<c.length;f+=1){g=c.charCodeAt(f);
|
||||
!0===l&&(m=g&255,g=m<<8|g>>>8);r=p+q;for(u=r>>>2;h.length<=u;)h.push(0);h[u]|=g<<8*(t+r%4*b);p+=2}return{value:h,binLen:8*p+d}};break;case "B64":c=function(a,c,d){var g=0,l,p,f,m,q,u,r,t;if(-1===a.search(/^[a-zA-Z0-9=+\/]+$/))throw Error("Invalid character in base-64 string");p=a.indexOf("=");a=a.replace(/\=/g,"");if(-1!==p&&p<a.length)throw Error("Invalid '=' found in base-64 string");c=c||[0];d=d||0;u=d>>>3;t=-1===b?3:0;for(p=0;p<a.length;p+=4){q=a.substr(p,4);for(f=m=0;f<q.length;f+=1)l="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/".indexOf(q[f]),
|
||||
m|=l<<18-6*f;for(f=0;f<q.length-1;f+=1){r=g+u;for(l=r>>>2;c.length<=l;)c.push(0);c[l]|=(m>>>16-8*f&255)<<8*(t+r%4*b);g+=1}}return{value:c,binLen:8*g+d}};break;case "BYTES":c=function(a,c,d){var g,l,p,f,m,q;c=c||[0];d=d||0;p=d>>>3;q=-1===b?3:0;for(l=0;l<a.length;l+=1)g=a.charCodeAt(l),m=l+p,f=m>>>2,c.length<=f&&c.push(0),c[f]|=g<<8*(q+m%4*b);return{value:c,binLen:8*a.length+d}};break;case "ARRAYBUFFER":try{c=new ArrayBuffer(0)}catch(e){throw Error("ARRAYBUFFER not supported by this environment");}c=
|
||||
function(a,c,d){var g,l,p,f,m,q;c=c||[0];d=d||0;l=d>>>3;m=-1===b?3:0;q=new Uint8Array(a);for(g=0;g<a.byteLength;g+=1)f=g+l,p=f>>>2,c.length<=p&&c.push(0),c[p]|=q[g]<<8*(m+f%4*b);return{value:c,binLen:8*a.byteLength+d}};break;default:throw Error("format must be HEX, TEXT, B64, BYTES, or ARRAYBUFFER");}return c}function y(c,a){return c<<a|c>>>32-a}function S(c,a){return 32<a?(a-=32,new b(c.b<<a|c.a>>>32-a,c.a<<a|c.b>>>32-a)):0!==a?new b(c.a<<a|c.b>>>32-a,c.b<<a|c.a>>>32-a):c}function w(c,a){return c>>>
|
||||
a|c<<32-a}function t(c,a){var k=null,k=new b(c.a,c.b);return k=32>=a?new b(k.a>>>a|k.b<<32-a&4294967295,k.b>>>a|k.a<<32-a&4294967295):new b(k.b>>>a-32|k.a<<64-a&4294967295,k.a>>>a-32|k.b<<64-a&4294967295)}function T(c,a){var k=null;return k=32>=a?new b(c.a>>>a,c.b>>>a|c.a<<32-a&4294967295):new b(0,c.a>>>a-32)}function aa(c,a,b){return c&a^~c&b}function ba(c,a,k){return new b(c.a&a.a^~c.a&k.a,c.b&a.b^~c.b&k.b)}function U(c,a,b){return c&a^c&b^a&b}function ca(c,a,k){return new b(c.a&a.a^c.a&k.a^a.a&
|
||||
k.a,c.b&a.b^c.b&k.b^a.b&k.b)}function da(c){return w(c,2)^w(c,13)^w(c,22)}function ea(c){var a=t(c,28),k=t(c,34);c=t(c,39);return new b(a.a^k.a^c.a,a.b^k.b^c.b)}function fa(c){return w(c,6)^w(c,11)^w(c,25)}function ga(c){var a=t(c,14),k=t(c,18);c=t(c,41);return new b(a.a^k.a^c.a,a.b^k.b^c.b)}function ha(c){return w(c,7)^w(c,18)^c>>>3}function ia(c){var a=t(c,1),k=t(c,8);c=T(c,7);return new b(a.a^k.a^c.a,a.b^k.b^c.b)}function ja(c){return w(c,17)^w(c,19)^c>>>10}function ka(c){var a=t(c,19),k=t(c,61);
|
||||
c=T(c,6);return new b(a.a^k.a^c.a,a.b^k.b^c.b)}function G(c,a){var b=(c&65535)+(a&65535);return((c>>>16)+(a>>>16)+(b>>>16)&65535)<<16|b&65535}function la(c,a,b,e){var h=(c&65535)+(a&65535)+(b&65535)+(e&65535);return((c>>>16)+(a>>>16)+(b>>>16)+(e>>>16)+(h>>>16)&65535)<<16|h&65535}function H(c,a,b,e,h){var d=(c&65535)+(a&65535)+(b&65535)+(e&65535)+(h&65535);return((c>>>16)+(a>>>16)+(b>>>16)+(e>>>16)+(h>>>16)+(d>>>16)&65535)<<16|d&65535}function ma(c,a){var d,e,h;d=(c.b&65535)+(a.b&65535);e=(c.b>>>16)+
|
||||
(a.b>>>16)+(d>>>16);h=(e&65535)<<16|d&65535;d=(c.a&65535)+(a.a&65535)+(e>>>16);e=(c.a>>>16)+(a.a>>>16)+(d>>>16);return new b((e&65535)<<16|d&65535,h)}function na(c,a,d,e){var h,n,g;h=(c.b&65535)+(a.b&65535)+(d.b&65535)+(e.b&65535);n=(c.b>>>16)+(a.b>>>16)+(d.b>>>16)+(e.b>>>16)+(h>>>16);g=(n&65535)<<16|h&65535;h=(c.a&65535)+(a.a&65535)+(d.a&65535)+(e.a&65535)+(n>>>16);n=(c.a>>>16)+(a.a>>>16)+(d.a>>>16)+(e.a>>>16)+(h>>>16);return new b((n&65535)<<16|h&65535,g)}function oa(c,a,d,e,h){var n,g,l;n=(c.b&
|
||||
65535)+(a.b&65535)+(d.b&65535)+(e.b&65535)+(h.b&65535);g=(c.b>>>16)+(a.b>>>16)+(d.b>>>16)+(e.b>>>16)+(h.b>>>16)+(n>>>16);l=(g&65535)<<16|n&65535;n=(c.a&65535)+(a.a&65535)+(d.a&65535)+(e.a&65535)+(h.a&65535)+(g>>>16);g=(c.a>>>16)+(a.a>>>16)+(d.a>>>16)+(e.a>>>16)+(h.a>>>16)+(n>>>16);return new b((g&65535)<<16|n&65535,l)}function B(c,a){return new b(c.a^a.a,c.b^a.b)}function A(c){var a=[],d;if("SHA-1"===c)a=[1732584193,4023233417,2562383102,271733878,3285377520];else if(0===c.lastIndexOf("SHA-",0))switch(a=
|
||||
[3238371032,914150663,812702999,4144912697,4290775857,1750603025,1694076839,3204075428],d=[1779033703,3144134277,1013904242,2773480762,1359893119,2600822924,528734635,1541459225],c){case "SHA-224":break;case "SHA-256":a=d;break;case "SHA-384":a=[new b(3418070365,a[0]),new b(1654270250,a[1]),new b(2438529370,a[2]),new b(355462360,a[3]),new b(1731405415,a[4]),new b(41048885895,a[5]),new b(3675008525,a[6]),new b(1203062813,a[7])];break;case "SHA-512":a=[new b(d[0],4089235720),new b(d[1],2227873595),
|
||||
new b(d[2],4271175723),new b(d[3],1595750129),new b(d[4],2917565137),new b(d[5],725511199),new b(d[6],4215389547),new b(d[7],327033209)];break;default:throw Error("Unknown SHA variant");}else if(0===c.lastIndexOf("SHA3-",0)||0===c.lastIndexOf("SHAKE",0))for(c=0;5>c;c+=1)a[c]=[new b(0,0),new b(0,0),new b(0,0),new b(0,0),new b(0,0)];else throw Error("No SHA variants supported");return a}function K(c,a){var b=[],e,d,n,g,l,p,f;e=a[0];d=a[1];n=a[2];g=a[3];l=a[4];for(f=0;80>f;f+=1)b[f]=16>f?c[f]:y(b[f-
|
||||
3]^b[f-8]^b[f-14]^b[f-16],1),p=20>f?H(y(e,5),d&n^~d&g,l,1518500249,b[f]):40>f?H(y(e,5),d^n^g,l,1859775393,b[f]):60>f?H(y(e,5),U(d,n,g),l,2400959708,b[f]):H(y(e,5),d^n^g,l,3395469782,b[f]),l=g,g=n,n=y(d,30),d=e,e=p;a[0]=G(e,a[0]);a[1]=G(d,a[1]);a[2]=G(n,a[2]);a[3]=G(g,a[3]);a[4]=G(l,a[4]);return a}function Z(c,a,b,e){var d;for(d=(a+65>>>9<<4)+15;c.length<=d;)c.push(0);c[a>>>5]|=128<<24-a%32;a+=b;c[d]=a&4294967295;c[d-1]=a/4294967296|0;a=c.length;for(d=0;d<a;d+=16)e=K(c.slice(d,d+16),e);return e}function L(c,
|
||||
a,k){var e,h,n,g,l,p,f,m,q,u,r,t,v,w,y,A,z,x,F,B,C,D,E=[],J;if("SHA-224"===k||"SHA-256"===k)u=64,t=1,D=Number,v=G,w=la,y=H,A=ha,z=ja,x=da,F=fa,C=U,B=aa,J=d;else if("SHA-384"===k||"SHA-512"===k)u=80,t=2,D=b,v=ma,w=na,y=oa,A=ia,z=ka,x=ea,F=ga,C=ca,B=ba,J=V;else throw Error("Unexpected error in SHA-2 implementation");k=a[0];e=a[1];h=a[2];n=a[3];g=a[4];l=a[5];p=a[6];f=a[7];for(r=0;r<u;r+=1)16>r?(q=r*t,m=c.length<=q?0:c[q],q=c.length<=q+1?0:c[q+1],E[r]=new D(m,q)):E[r]=w(z(E[r-2]),E[r-7],A(E[r-15]),E[r-
|
||||
16]),m=y(f,F(g),B(g,l,p),J[r],E[r]),q=v(x(k),C(k,e,h)),f=p,p=l,l=g,g=v(n,m),n=h,h=e,e=k,k=v(m,q);a[0]=v(k,a[0]);a[1]=v(e,a[1]);a[2]=v(h,a[2]);a[3]=v(n,a[3]);a[4]=v(g,a[4]);a[5]=v(l,a[5]);a[6]=v(p,a[6]);a[7]=v(f,a[7]);return a}function D(c,a){var d,e,h,n,g=[],l=[];if(null!==c)for(e=0;e<c.length;e+=2)a[(e>>>1)%5][(e>>>1)/5|0]=B(a[(e>>>1)%5][(e>>>1)/5|0],new b(c[e+1],c[e]));for(d=0;24>d;d+=1){n=A("SHA3-");for(e=0;5>e;e+=1){h=a[e][0];var p=a[e][1],f=a[e][2],m=a[e][3],q=a[e][4];g[e]=new b(h.a^p.a^f.a^
|
||||
m.a^q.a,h.b^p.b^f.b^m.b^q.b)}for(e=0;5>e;e+=1)l[e]=B(g[(e+4)%5],S(g[(e+1)%5],1));for(e=0;5>e;e+=1)for(h=0;5>h;h+=1)a[e][h]=B(a[e][h],l[e]);for(e=0;5>e;e+=1)for(h=0;5>h;h+=1)n[h][(2*e+3*h)%5]=S(a[e][h],W[e][h]);for(e=0;5>e;e+=1)for(h=0;5>h;h+=1)a[e][h]=B(n[e][h],new b(~n[(e+1)%5][h].a&n[(e+2)%5][h].a,~n[(e+1)%5][h].b&n[(e+2)%5][h].b));a[0][0]=B(a[0][0],X[d])}return a}var d,V,W,X;d=[1116352408,1899447441,3049323471,3921009573,961987163,1508970993,2453635748,2870763221,3624381080,310598401,607225278,
|
||||
1426881987,1925078388,2162078206,2614888103,3248222580,3835390401,4022224774,264347078,604807628,770255983,1249150122,1555081692,1996064986,2554220882,2821834349,2952996808,3210313671,3336571891,3584528711,113926993,338241895,666307205,773529912,1294757372,1396182291,1695183700,1986661051,2177026350,2456956037,2730485921,2820302411,3259730800,3345764771,3516065817,3600352804,4094571909,275423344,430227734,506948616,659060556,883997877,958139571,1322822218,1537002063,1747873779,1955562222,2024104815,
|
||||
2227730452,2361852424,2428436474,2756734187,3204031479,3329325298];V=[new b(d[0],3609767458),new b(d[1],602891725),new b(d[2],3964484399),new b(d[3],2173295548),new b(d[4],4081628472),new b(d[5],3053834265),new b(d[6],2937671579),new b(d[7],3664609560),new b(d[8],2734883394),new b(d[9],1164996542),new b(d[10],1323610764),new b(d[11],3590304994),new b(d[12],4068182383),new b(d[13],991336113),new b(d[14],633803317),new b(d[15],3479774868),new b(d[16],2666613458),new b(d[17],944711139),new b(d[18],2341262773),
|
||||
new b(d[19],2007800933),new b(d[20],1495990901),new b(d[21],1856431235),new b(d[22],3175218132),new b(d[23],2198950837),new b(d[24],3999719339),new b(d[25],766784016),new b(d[26],2566594879),new b(d[27],3203337956),new b(d[28],1034457026),new b(d[29],2466948901),new b(d[30],3758326383),new b(d[31],168717936),new b(d[32],1188179964),new b(d[33],1546045734),new b(d[34],1522805485),new b(d[35],2643833823),new b(d[36],2343527390),new b(d[37],1014477480),new b(d[38],1206759142),new b(d[39],344077627),
|
||||
new b(d[40],1290863460),new b(d[41],3158454273),new b(d[42],3505952657),new b(d[43],106217008),new b(d[44],3606008344),new b(d[45],1432725776),new b(d[46],1467031594),new b(d[47],851169720),new b(d[48],3100823752),new b(d[49],1363258195),new b(d[50],3750685593),new b(d[51],3785050280),new b(d[52],3318307427),new b(d[53],3812723403),new b(d[54],2003034995),new b(d[55],3602036899),new b(d[56],1575990012),new b(d[57],1125592928),new b(d[58],2716904306),new b(d[59],442776044),new b(d[60],593698344),new b(d[61],
|
||||
3733110249),new b(d[62],2999351573),new b(d[63],3815920427),new b(3391569614,3928383900),new b(3515267271,566280711),new b(3940187606,3454069534),new b(4118630271,4000239992),new b(116418474,1914138554),new b(174292421,2731055270),new b(289380356,3203993006),new b(460393269,320620315),new b(685471733,587496836),new b(852142971,1086792851),new b(1017036298,365543100),new b(1126000580,2618297676),new b(1288033470,3409855158),new b(1501505948,4234509866),new b(1607167915,987167468),new b(1816402316,
|
||||
1246189591)];X=[new b(0,1),new b(0,32898),new b(2147483648,32906),new b(2147483648,2147516416),new b(0,32907),new b(0,2147483649),new b(2147483648,2147516545),new b(2147483648,32777),new b(0,138),new b(0,136),new b(0,2147516425),new b(0,2147483658),new b(0,2147516555),new b(2147483648,139),new b(2147483648,32905),new b(2147483648,32771),new b(2147483648,32770),new b(2147483648,128),new b(0,32778),new b(2147483648,2147483658),new b(2147483648,2147516545),new b(2147483648,32896),new b(0,2147483649),
|
||||
new b(2147483648,2147516424)];W=[[0,36,3,41,18],[1,44,10,45,2],[62,6,43,15,61],[28,55,25,21,56],[27,20,39,8,14]];"function"===typeof define&&define.amd?define(function(){return C}):"undefined"!==typeof exports?("undefined"!==typeof module&&module.exports&&(module.exports=C),exports=C):Y.jsSHA=C})(this);
|
9
public/js/socket.io.js
Normal file
9
public/js/socket.io.js
Normal file
File diff suppressed because one or more lines are too long
9640
public/js/xterm.js
Normal file
9640
public/js/xterm.js
Normal file
File diff suppressed because it is too large
Load Diff
19
release.sh
Executable file
19
release.sh
Executable file
@ -0,0 +1,19 @@
|
||||
#!/bin/bash
|
||||
PACKAGE_VERSION=$(grep 'version' package.json | cut -d '"' -f4)
|
||||
|
||||
yarn build
|
||||
|
||||
docker build -t wy373226722/electron-distribution:$PACKAGE_VERSION .
|
||||
|
||||
docker tag wy373226722/electron-distribution:$PACKAGE_VERSION registry.cn-shenzhen.aliyuncs.com/yi-ge/electron-distribution:$PACKAGE_VERSION
|
||||
docker tag wy373226722/electron-distribution:$PACKAGE_VERSION registry.cn-shenzhen.aliyuncs.com/yi-ge/electron-distribution:latest
|
||||
docker tag wy373226722/electron-distribution:$PACKAGE_VERSION ccr.ccs.tencentyun.com/yi-ge/electron-distribution:$PACKAGE_VERSION
|
||||
docker tag wy373226722/electron-distribution:$PACKAGE_VERSION ccr.ccs.tencentyun.com/yi-ge/electron-distribution:latest
|
||||
docker tag wy373226722/electron-distribution:$PACKAGE_VERSION wy373226722/electron-distribution:latest
|
||||
|
||||
docker push ccr.ccs.tencentyun.com/yi-ge/electron-distribution:$PACKAGE_VERSION
|
||||
docker push ccr.ccs.tencentyun.com/yi-ge/electron-distribution:latest
|
||||
docker push registry.cn-shenzhen.aliyuncs.com/yi-ge/electron-distribution:$PACKAGE_VERSION
|
||||
docker push registry.cn-shenzhen.aliyuncs.com/yi-ge/electron-distribution:latest
|
||||
docker push wy373226722/electron-distribution:$PACKAGE_VERSION
|
||||
docker push wy373226722/electron-distribution:latest
|
99
src/app.js
Normal file
99
src/app.js
Normal file
@ -0,0 +1,99 @@
|
||||
import hapi from 'hapi'
|
||||
import swagger from './lib/swagger'
|
||||
import {
|
||||
SERVER,
|
||||
SYSTEM
|
||||
} from './config'
|
||||
import routes from './routes'
|
||||
import db from './lib/db'
|
||||
import moment from 'moment-timezone'
|
||||
import SocketIO from 'socket.io'
|
||||
import websocket from './websocket'
|
||||
import DockerOde from 'dockerode'
|
||||
import fs from 'fs'
|
||||
|
||||
;(async () => {
|
||||
const socketPath = SYSTEM.DOCKER_SOCKET
|
||||
const stats = fs.statSync(socketPath)
|
||||
|
||||
if (!stats.isSocket()) {
|
||||
console.log('Docker can\'t connect.')
|
||||
}
|
||||
|
||||
const docker = new DockerOde({
|
||||
socketPath: socketPath
|
||||
})
|
||||
|
||||
const checkDockerEnv = async () => {
|
||||
try {
|
||||
const r = await docker.info()
|
||||
if (r.Architecture !== 'x86_64') {
|
||||
console.log('Require x86-64 system.')
|
||||
}
|
||||
} catch (err) {
|
||||
console.log('Please make sure Docker is working.')
|
||||
}
|
||||
|
||||
try {
|
||||
const r = await docker.version()
|
||||
if (r.Version.split('.')[0] < 18) {
|
||||
console.log('Require Docker 18+ .')
|
||||
}
|
||||
} catch (err) {
|
||||
console.log('Error: ' + err.toString())
|
||||
}
|
||||
|
||||
console.log('Docker Runding...')
|
||||
}
|
||||
|
||||
checkDockerEnv()
|
||||
|
||||
const server = hapi.server(SERVER)
|
||||
|
||||
await server.register([
|
||||
...swagger
|
||||
])
|
||||
|
||||
try {
|
||||
server.bind({
|
||||
docker,
|
||||
$db: db,
|
||||
$moment: moment,
|
||||
/**
|
||||
* send success data
|
||||
*/
|
||||
success (data, status = 1, msg) {
|
||||
return {
|
||||
status,
|
||||
msg,
|
||||
result: data
|
||||
}
|
||||
},
|
||||
/**
|
||||
* send fail data
|
||||
*/
|
||||
fail (data, status = 10000, msg) {
|
||||
return {
|
||||
status,
|
||||
msg,
|
||||
result: data
|
||||
}
|
||||
}
|
||||
})
|
||||
server.route(routes)
|
||||
await server.start()
|
||||
|
||||
const io = SocketIO.listen(server.listener)
|
||||
|
||||
server.bind({
|
||||
io
|
||||
})
|
||||
|
||||
websocket(io, docker)
|
||||
|
||||
console.log('Server running at:', server.info.uri)
|
||||
} catch (err) {
|
||||
console.log(err)
|
||||
process.exit(1)
|
||||
}
|
||||
})()
|
78
src/config.js
Normal file
78
src/config.js
Normal file
@ -0,0 +1,78 @@
|
||||
const isDev = process.env.NODE_ENV ? process.env.NODE_ENV === 'development' : false
|
||||
|
||||
export const SYSTEM = {
|
||||
NAME: process.env.NAME || 'APP',
|
||||
TOKEN: process.env.TOKEN || '1jH27dJf9s852',
|
||||
SCHEME: [isDev ? 'http' : (process.env.SCHEME || 'http')],
|
||||
DOMAIN: process.env.DOMAIN || 'www.example.com',
|
||||
REPOPATH: process.env.REPOPATH || 'git@github.com:yi-ge/electron-distribution.git',
|
||||
WORKPATH: process.env.WORKPATH || '/data',
|
||||
BUILD_TYPE: process.env.BUILD_TYPE ? process.env.BUILD_TYPE.split(',') : ['win', 'linux'], // ['linux', 'win', 'mac']
|
||||
GH_TOKEN: process.env.GH_TOKEN || '', // If you set publish option
|
||||
CSC_LINK: process.env.CSC_LINK || '', // https://www.electron.build/code-signing
|
||||
CSC_KEY_PASSWORD: process.env.CSC_KEY_PASSWORD || '',
|
||||
CSC_NAME: process.env.CSC_NAME || '',
|
||||
CSC_IDENTITY_AUTO_DISCOVERY: 'false',
|
||||
DOCKER_SOCKET: process.env.DOCKER_SOCKET || '/var/run/docker.sock',
|
||||
MAC_SERVER_HOST: process.env.MAC_SERVER_HOST || '127.0.0.1',
|
||||
MAC_SERVER_PORT: process.env.MAC_SERVER_PORT || '22',
|
||||
MAC_SERVER_USERNAME: process.env.MAC_SERVER_USERNAME || 'guest',
|
||||
LINUX_SERVER_HOST: process.env.LINUX_SERVER_HOST || '127.0.0.1',
|
||||
LINUX_SERVER_PORT: process.env.LINUX_SERVER_PORT || '22',
|
||||
LINUX_SERVER_USERNAME: process.env.LINUX_SERVER_USERNAME || 'root',
|
||||
OBJECT_STORAGE_TYPE: process.env.OBJECT_STORAGE_TYPE || 'cos'
|
||||
}
|
||||
|
||||
export const SERVER = {
|
||||
port: isDev ? '65533' : (process.env.PORT || '80'),
|
||||
host: isDev ? '0.0.0.0' : (process.env.HOST || '0.0.0.0'),
|
||||
routes: {
|
||||
cors: {
|
||||
origin: ['*'],
|
||||
additionalHeaders: ['Expect', 'X-GitHub-Delivery', 'X-GitHub-Event', 'X-Hub-Signature']
|
||||
},
|
||||
state: {
|
||||
parse: false, // parse and store in request.state
|
||||
failAction: 'ignore' // may also be 'ignore' or 'log'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 腾讯云
|
||||
export const qcloudAccessKey = {
|
||||
SecretId: process.env.COS_SECRE_ID || '',
|
||||
SecretKey: process.env.COS_SECRE_KEY || ''
|
||||
}
|
||||
|
||||
export const COS = {
|
||||
bucket: process.env.COS_BUCKET || 'bucketname-12345678',
|
||||
region: process.env.COS_REGION || 'ap-chengdu',
|
||||
url: process.env.COS_URL || 'https://cdn.xxx.com'
|
||||
}
|
||||
|
||||
// 阿里云OSS
|
||||
const AliyunAccessKey = {
|
||||
accessKeyId: process.env.OSS_ACCESS_KEY_ID || 'id',
|
||||
accessKeySecret: process.env.OSS_ACCESS_SECRET || 'key'
|
||||
}
|
||||
|
||||
export const OSS = {
|
||||
config: {
|
||||
region: process.env.OSS_REGION || 'oss-cn-qingdao',
|
||||
accessKeyId: AliyunAccessKey.accessKeyId,
|
||||
accessKeySecret: AliyunAccessKey.accessKeySecret,
|
||||
bucket: process.env.OSS_BUCKET || 'bucket',
|
||||
internal: process.env.OSS_INTERNAL === 'true',
|
||||
secure: true,
|
||||
timeout: 1200000 // 20min
|
||||
},
|
||||
url: process.env.OSS_URL || 'https://cdn.xxx.com'
|
||||
}
|
||||
|
||||
export const QINIU = {
|
||||
accessKey: process.env.QINIU_ACCESS_KEY || '',
|
||||
secretKey: process.env.QINIU_SECRET_KEY || '',
|
||||
bucket: process.env.QINIU_BUCKET_KEY || '',
|
||||
url: process.env.QINIU_URL || 'https://cdn.xxx.com',
|
||||
zone: process.env.QINIU_ZONE || 'Zone_z0'
|
||||
}
|
2
src/dev.js
Normal file
2
src/dev.js
Normal file
@ -0,0 +1,2 @@
|
||||
require('@babel/register')
|
||||
require('./app')
|
135
src/lib/ali-oss.js
Normal file
135
src/lib/ali-oss.js
Normal file
@ -0,0 +1,135 @@
|
||||
import fs from 'fs'
|
||||
import { OSS as OSSConfig } from '../config'
|
||||
import OSS from 'ali-oss'
|
||||
|
||||
// 阿里云OSS接口,返回Promise,使用.then()获取结果,.catch()抓取错误。
|
||||
|
||||
// web访问地址:OSSConfig.url + object_key
|
||||
const client = OSSConfig.config.accessKeyId !== 'id' ? new OSS(OSSConfig.config) : { list () {} }
|
||||
|
||||
/***
|
||||
* 查看所有文件
|
||||
* 通过list来列出当前Bucket下的所有文件。主要的参数如下:
|
||||
|
||||
prefix 指定只列出符合特定前缀的文件
|
||||
marker 指定只列出文件名大于marker之后的文件
|
||||
delimiter 用于获取文件的公共前缀
|
||||
max-keys 用于指定最多返回的文件个数
|
||||
* @type {*}
|
||||
*/
|
||||
// 不带任何参数,默认最多返回1000个文件
|
||||
export const listFiles = client.list()
|
||||
|
||||
/***
|
||||
* 上传本地文件
|
||||
* @param object_key 文件名
|
||||
* @param local_file 本地文件路径
|
||||
*
|
||||
* join
|
||||
*该方法将多个参数值字符串结合成一个路径字符串,使用方式如下:
|
||||
*path.([path1], [path2], [...])
|
||||
*在该方法中,可以使用一个或多个字符串值参数,该参数返回将这些字符串值参数结合而成的路径。
|
||||
*var joinPath = path.join(__dirname, 'a', 'b', 'c');
|
||||
*console.log(joinPath); // D:\nodePro\fileTest\a\b\c
|
||||
*__dirname变量值代表程序运行的根目录。
|
||||
*
|
||||
* 用法示例:
|
||||
import path from 'path';
|
||||
return upload_local_file("test.js", path.join(__dirname, "address.js")).then(function (result) {
|
||||
console.log(result);
|
||||
}).catch(function (err) {
|
||||
console.log(err);
|
||||
});
|
||||
return list_files.then(function (result) {
|
||||
console.log(result.objects);
|
||||
}).catch(function (err) {
|
||||
console.error(err);
|
||||
});
|
||||
*/
|
||||
export const uploadLocalFile = (objectKey, localFile) => {
|
||||
return client.put(objectKey, localFile)
|
||||
}
|
||||
|
||||
/***
|
||||
* 流式上传
|
||||
* 通过putStream接口来上传一个Stream中的内容,stream参数可以是任何实现了Readable Stream的对象,包含文件流,网络流等。当使用putStream接口时,SDK默认会发起一个chunked encoding的HTTP PUT请求。如果在options指定了contentLength参数,则不会使用chunked encoding。
|
||||
* @param objectKey
|
||||
* @param localFile
|
||||
* @param chunked 是否使用chunked encoding 默认不使用
|
||||
* @returns {Object}
|
||||
*/
|
||||
export const uploadStream = (objectKey, localFile, chunked = false) => {
|
||||
// // use 'chunked encoding'
|
||||
const stream = fs.createReadStream(localFile)
|
||||
if (chunked) {
|
||||
return client.putStream(objectKey, stream)
|
||||
} else {
|
||||
const size = fs.statSync(localFile).size // don't use 'chunked encoding'
|
||||
return client.putStream(objectKey, stream, {
|
||||
contentLength: size
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/***
|
||||
* 上传Buffer内容
|
||||
* @param objectKey
|
||||
* @param buffer Buffer对象,例如new Buffer('hello world')
|
||||
*/
|
||||
export const uploadBuffer = (objectKey, buffer) => {
|
||||
return client.put(objectKey, buffer)
|
||||
}
|
||||
|
||||
/***
|
||||
* 分片上传
|
||||
* 在需要上传的文件较大时,可以通过multipartUpload接口进行分片上传。分片上传的好处是将一个大请求分成多个小请求来执行,这样当其中一些请求失败后,不需要重新上传整个文件,而只需要上传失败的分片就可以了。一般对于大于100MB的文件,建议采用分片上传的方法。
|
||||
* @param objectKey
|
||||
* @param localFile
|
||||
*/
|
||||
export const uploadMultipart = (objectKey, localFile) => {
|
||||
return client.multipartUpload(objectKey, localFile, {
|
||||
progress: function * (p) {
|
||||
console.log('Progress: ' + p)
|
||||
}
|
||||
})
|
||||
// 上面的progress参数是一个进度回调函数,用于获取上传进度。progress可以是一个generator function(function*),也可以是一个”thunk”:
|
||||
// const progress = function (p) {
|
||||
// return function (done) {
|
||||
// console.log(p);
|
||||
// done();
|
||||
// };
|
||||
// };
|
||||
}
|
||||
|
||||
/***
|
||||
* 断点上传(需要循环调用)
|
||||
* 分片上传提供progress参数允许用户传递一个进度回调,在回调中SDK将当前已经上传成功的比例和断点信息作为参数。为了实现断点上传,可以在上传过程中保存断点信息(checkpoint),发生错误后,再将已保存的checkpoint作为参数传递给multipartUpload,此时将从上次失败的地方继续上传。
|
||||
* @param objectKey
|
||||
* @param localFile
|
||||
*/
|
||||
export const uploadMultiparts = (objectKey, localFile) => {
|
||||
let checkpoint
|
||||
return client.multipartUpload(objectKey, localFile, {
|
||||
checkpoint: checkpoint,
|
||||
progress: function * (percentage, cpt) {
|
||||
checkpoint = cpt
|
||||
}
|
||||
})
|
||||
// 上面的代码只是将checkpoint保存在变量中,如果程序崩溃的话就丢失了,用户也可以将它保存在文件中,然后在程序重启后将checkpoint信息从文件中读取出来。
|
||||
}
|
||||
|
||||
/***
|
||||
* 下载文件到本地
|
||||
* @param objectKey
|
||||
* @param localFile 本地路径
|
||||
*/
|
||||
export const downloadLocalFile = (objectKey, localFile) => {
|
||||
return client.get(objectKey, localFile)
|
||||
}
|
||||
|
||||
// export const download_stream = (object_key, local_file) => {
|
||||
// var result = yield client.getStream(object_key)
|
||||
// console.log(result)
|
||||
// var writeStream = fs.createWriteStream(local_file)
|
||||
// result.stream.pipe(writeStream)
|
||||
// }
|
10
src/lib/auth.js
Normal file
10
src/lib/auth.js
Normal file
@ -0,0 +1,10 @@
|
||||
import JsSHA from 'jssha'
|
||||
import { SYSTEM } from '../config'
|
||||
|
||||
export default (token) => {
|
||||
const shaObj = new JsSHA('SHA-512', 'TEXT')
|
||||
shaObj.update(SYSTEM.TOKEN)
|
||||
const hash = shaObj.getHash('HEX')
|
||||
if (token === hash) return true
|
||||
return false
|
||||
}
|
14
src/lib/db.js
Normal file
14
src/lib/db.js
Normal file
@ -0,0 +1,14 @@
|
||||
import low from 'lowdb'
|
||||
import path from 'path'
|
||||
import FileSync from 'lowdb/adapters/FileSync'
|
||||
import { SYSTEM } from '../config'
|
||||
|
||||
const isDev = process.env.NODE_ENV ? process.env.NODE_ENV === 'development' : false
|
||||
const adapter = isDev ? new FileSync(path.join(SYSTEM.NAME + '-distribution-db.json')) : new FileSync(path.join(SYSTEM.WORKPATH, SYSTEM.NAME + '-distribution-db.json'))
|
||||
const db = low(adapter)
|
||||
|
||||
if (!db.get('appLog').value()) {
|
||||
db.defaults({ appLog: [], buildLog: [] }).write()
|
||||
}
|
||||
|
||||
export default db
|
41
src/lib/qiniu.js
Normal file
41
src/lib/qiniu.js
Normal file
@ -0,0 +1,41 @@
|
||||
|
||||
import qiniu from 'qiniu'
|
||||
import { QINIU } from '../config'
|
||||
|
||||
const mac = new qiniu.auth.digest.Mac(QINIU.accessKey, QINIU.secretKey)
|
||||
const putPolicy = new qiniu.rs.PutPolicy({
|
||||
scope: QINIU.bucket
|
||||
})
|
||||
const uploadToken = putPolicy.uploadToken(mac)
|
||||
|
||||
const config = new qiniu.conf.Config()
|
||||
// 空间对应的机房
|
||||
config.zone = qiniu.zone[QINIU.zone]
|
||||
// 是否使用https域名
|
||||
config.useHttpsDomain = true
|
||||
// 上传是否使用cdn加速
|
||||
// config.useCdnDomain = true;
|
||||
|
||||
export const uploadLocalFileToQiniu = (key, localFile) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
var resumeUploader = new qiniu.resume_up.ResumeUploader(config)
|
||||
var putExtra = new qiniu.resume_up.PutExtra()
|
||||
// 如果指定了断点记录文件,那么下次会从指定的该文件尝试读取上次上传的进度,以实现断点续传
|
||||
// putExtra.resumeRecordFile = 'progress.log';
|
||||
// 文件分片上传
|
||||
const remoteKey = key.substr(1) // 删除开头的/
|
||||
resumeUploader.putFile(uploadToken, remoteKey, localFile, putExtra, function (respErr,
|
||||
respBody, respInfo) {
|
||||
if (respErr) {
|
||||
return reject(respErr)
|
||||
}
|
||||
if (respInfo.statusCode === 200) {
|
||||
resolve(QINIU.url + key)
|
||||
} else {
|
||||
console.log(respInfo.statusCode)
|
||||
console.log(respBody)
|
||||
return reject(respInfo)
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
33
src/lib/swagger.js
Normal file
33
src/lib/swagger.js
Normal file
@ -0,0 +1,33 @@
|
||||
import inert from 'inert'
|
||||
import vision from 'vision'
|
||||
import hapiSwagger from 'hapi-swagger'
|
||||
import { SYSTEM } from '../config'
|
||||
import pack from '../../package'
|
||||
|
||||
const swaggerOptions = {
|
||||
schemes: SYSTEM.SCHEME,
|
||||
info: {
|
||||
title: 'Electron Distribution',
|
||||
version: pack.version
|
||||
},
|
||||
grouping: 'tags',
|
||||
tags: [
|
||||
{
|
||||
name: 'app',
|
||||
description: 'App Distribution'
|
||||
},
|
||||
{
|
||||
name: 'build',
|
||||
description: 'APP Buils'
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
export default [
|
||||
inert,
|
||||
vision,
|
||||
{
|
||||
plugin: hapiSwagger,
|
||||
options: swaggerOptions
|
||||
}
|
||||
]
|
26
src/lib/tencent-cos.js
Normal file
26
src/lib/tencent-cos.js
Normal file
@ -0,0 +1,26 @@
|
||||
import COSSDK from 'cos-nodejs-sdk-v5'
|
||||
import { qcloudAccessKey, COS } from '../config'
|
||||
|
||||
const cos = new COSSDK(qcloudAccessKey)
|
||||
|
||||
export const uploadToCOS = (key, filePath) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
// 分片上传
|
||||
cos.sliceUploadFile(
|
||||
{
|
||||
Bucket: COS.bucket,
|
||||
Region: COS.region,
|
||||
Key: key,
|
||||
FilePath: filePath
|
||||
},
|
||||
function (err, data) {
|
||||
if (err || !data) {
|
||||
reject(err)
|
||||
} else {
|
||||
data.realPath = COS.url + key
|
||||
resolve(data)
|
||||
}
|
||||
}
|
||||
)
|
||||
})
|
||||
}
|
23
src/lib/upload.js
Normal file
23
src/lib/upload.js
Normal file
@ -0,0 +1,23 @@
|
||||
import { uploadToCOS } from './tencent-cos'
|
||||
import { uploadStream as uploadLocalFileToOSS } from './ali-oss'
|
||||
import { uploadLocalFileToQiniu } from './qiniu'
|
||||
import { OSS as OSSConfig, SYSTEM } from '../config'
|
||||
|
||||
export default async (key, localFilePath) => {
|
||||
switch (SYSTEM.OBJECT_STORAGE_TYPE) {
|
||||
case 'cos':
|
||||
const cos = await uploadToCOS(key, localFilePath)
|
||||
return cos.realPath
|
||||
case 'oss':
|
||||
const oss = await uploadLocalFileToOSS(key, localFilePath)
|
||||
// console.log(oss.url)
|
||||
if (oss) {
|
||||
return OSSConfig.url + key
|
||||
} else {
|
||||
return null
|
||||
}
|
||||
case 'qiniu':
|
||||
const qiniu = await uploadLocalFileToQiniu(key, localFilePath)
|
||||
return qiniu
|
||||
}
|
||||
}
|
26
src/routes/auth.js
Normal file
26
src/routes/auth.js
Normal file
@ -0,0 +1,26 @@
|
||||
import Joi from 'joi'
|
||||
import { SYSTEM } from '../config'
|
||||
import auth from '../lib/auth'
|
||||
|
||||
export default [{
|
||||
method: 'GET',
|
||||
path: `/app/auth`,
|
||||
config: {
|
||||
auth: false,
|
||||
tags: ['api', 'app'],
|
||||
description: 'Check token.',
|
||||
validate: {
|
||||
query: {
|
||||
token: Joi.string().required().description('Encrypted-Token')
|
||||
}
|
||||
}
|
||||
},
|
||||
async handler (request) {
|
||||
if (auth(request.query.token)) {
|
||||
return this.success({
|
||||
buildType: SYSTEM.BUILD_TYPE
|
||||
})
|
||||
}
|
||||
return this.fail(null, 403, 'Token Error.')
|
||||
}
|
||||
}]
|
190
src/routes/build.js
Normal file
190
src/routes/build.js
Normal file
@ -0,0 +1,190 @@
|
||||
import Joi from 'joi'
|
||||
import fs from 'fs'
|
||||
import path from 'path'
|
||||
import { SYSTEM } from '../config'
|
||||
import { Client } from 'ssh2'
|
||||
import { spawn } from 'child_process'
|
||||
import auth from '../lib/auth'
|
||||
import JsSHA from 'jssha'
|
||||
|
||||
const WIN_IMAGE_NAME = 'electronuserland/builder:wine-mono'
|
||||
const LINUX_IMAGE_NAME = 'electronuserland/builder:10'
|
||||
|
||||
const mkdirsSync = (dirname) => {
|
||||
if (fs.existsSync(dirname)) {
|
||||
return true
|
||||
} else {
|
||||
if (mkdirsSync(path.dirname(dirname))) {
|
||||
fs.mkdirSync(dirname)
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const getHashToken = () => {
|
||||
const shaObj = new JsSHA('SHA-512', 'TEXT')
|
||||
shaObj.update(SYSTEM.TOKEN)
|
||||
return shaObj.getHash('HEX')
|
||||
}
|
||||
|
||||
export default [
|
||||
{
|
||||
method: 'GET',
|
||||
path: `/build/{type}`,
|
||||
config: {
|
||||
auth: false,
|
||||
tags: ['api', 'build'],
|
||||
description: 'App Build',
|
||||
validate: {
|
||||
params: {
|
||||
type: Joi.string().required().description('Type')
|
||||
},
|
||||
query: {
|
||||
token: Joi.string().required().description('Encrypted-Token')
|
||||
}
|
||||
}
|
||||
},
|
||||
async handler (request) {
|
||||
if (!auth(request.query.token)) {
|
||||
return this.fail(null, 403, 'Token Error.')
|
||||
}
|
||||
const shaObj = new JsSHA('SHA-512', 'TEXT')
|
||||
shaObj.update(SYSTEM.TOKEN)
|
||||
const hashToken = shaObj.getHash('HEX')
|
||||
|
||||
const publishOpt = SYSTEM.GH_TOKEN ? 'always' : 'never'
|
||||
|
||||
let containerCmd = 'yarn --ignore-engines'
|
||||
let imageName = null
|
||||
let workPath = SYSTEM.WORKPATH
|
||||
let type = request.params.type
|
||||
const sourcePath = path.join(workPath, '/source')
|
||||
switch (type) {
|
||||
case 'win':
|
||||
workPath += '/win'
|
||||
imageName = WIN_IMAGE_NAME
|
||||
containerCmd += ' && yarn run build --' + type + ' --publish ' + publishOpt + ' && curl -X GET "' + SYSTEM.SCHEME + '://' + SYSTEM.DOMAIN + '/app/upload?platform=' + type + '&extended=x86-64&token=' + hashToken + '&logPath=$LOG_PATH" -H "cache-control: no-cache"'
|
||||
break
|
||||
case 'linux':
|
||||
workPath += '/linux'
|
||||
imageName = LINUX_IMAGE_NAME
|
||||
containerCmd += ' && yarn run build --' + type + ' --publish ' + publishOpt + ' && curl -X GET "' + SYSTEM.SCHEME + '://' + SYSTEM.DOMAIN + '/app/upload?platform=' + type + '&extended=x86-64&token=' + hashToken + '&logPath=$LOG_PATH" -H "cache-control: no-cache"'
|
||||
break
|
||||
}
|
||||
|
||||
if (!fs.existsSync(SYSTEM.WORKPATH + '/logs/' + type)) mkdirsSync(SYSTEM.WORKPATH + '/logs/' + type)
|
||||
const LogPath = SYSTEM.WORKPATH + '/logs/' + type + '/' + (new Date()).getTime() + '.log'
|
||||
|
||||
if (type === 'mac') {
|
||||
// 1. rsync server -> mac
|
||||
const writerStream = fs.createWriteStream(LogPath, {flags: 'a'})
|
||||
const rsync = spawn('/usr/bin/rsync', ['-avrz', '-e', `'/usr/bin/ssh -p ${SYSTEM.MAC_SERVER_PORT}'`, '--delete-after', '--exclude', '"node_modules"', sourcePath + '/', SYSTEM.MAC_SERVER_USERNAME + '@' + SYSTEM.MAC_SERVER_HOST + ':/tmp/' + SYSTEM.NAME])
|
||||
|
||||
rsync.stdout.pipe(writerStream)
|
||||
rsync.stderr.pipe(writerStream)
|
||||
|
||||
rsync.on('close', (code) => {
|
||||
const writerStream = fs.createWriteStream(LogPath, {flags: 'a'})
|
||||
writerStream.write(`\nChild process exited with code ${code} \n`)
|
||||
|
||||
// 2. build app and rsync mac build dir -> server build dir
|
||||
let bashContent = ``
|
||||
if (SYSTEM.CSC_LINK) bashContent += 'export CSC_LINK=' + SYSTEM.CSC_LINK + '\n'
|
||||
if (SYSTEM.CSC_KEY_PASSWORD) bashContent += 'export CSC_KEY_PASSWORD=' + SYSTEM.CSC_KEY_PASSWORD + '\n'
|
||||
if (SYSTEM.GH_TOKEN) bashContent += 'export GH_TOKEN=' + SYSTEM.GH_TOKEN + '\n'
|
||||
bashContent += 'export LOG_PATH=' + LogPath + '\n'
|
||||
bashContent += 'cd /tmp/' + SYSTEM.NAME + '\n'
|
||||
bashContent += `yarn --ignore-engines` + ' && yarn run build --' + type + ' --publish ' + publishOpt + '\n'
|
||||
// bashContent += `echo -e "Host ${SYSTEM.LINUX_SERVER_HOST}\n\tStrictHostKeyChecking no\n" >> ~/.ssh/config\n`
|
||||
bashContent += `rsync -avrz -e 'ssh -p ${SYSTEM.LINUX_SERVER_PORT}' --exclude "node_modules" /tmp/` + SYSTEM.NAME + '/build/ ' + SYSTEM.LINUX_SERVER_USERNAME + '@' + SYSTEM.LINUX_SERVER_HOST + ':' + sourcePath + '/build \n'
|
||||
bashContent += 'curl -X GET "' + SYSTEM.SCHEME + '://' + SYSTEM.DOMAIN + '/app/upload?platform=' + type + '&extended=x86-64&token=' + getHashToken() + '&logPath=' + LogPath + '" -H "cache-control: no-cache"\n'
|
||||
writerStream.write('Run command: \n')
|
||||
writerStream.write(bashContent)
|
||||
|
||||
const conn = new Client()
|
||||
conn.on('ready', function () {
|
||||
const writerStream = fs.createWriteStream(LogPath, {flags: 'a'})
|
||||
writerStream.write('Client :: ready\n')
|
||||
conn.shell(function (err, stream) {
|
||||
if (err) throw err
|
||||
stream.pipe(writerStream)
|
||||
stream.on('close', function () {
|
||||
const writerStream = fs.createWriteStream(LogPath, {flags: 'a'})
|
||||
writerStream.write('Stream :: close')
|
||||
conn.end()
|
||||
})
|
||||
stream.end(bashContent)
|
||||
})
|
||||
}).connect({
|
||||
host: SYSTEM.MAC_SERVER_HOST,
|
||||
port: Number(SYSTEM.MAC_SERVER_PORT),
|
||||
username: SYSTEM.MAC_SERVER_USERNAME,
|
||||
privateKey: require('fs').readFileSync('/root/.ssh/id_rsa')
|
||||
})
|
||||
})
|
||||
} else {
|
||||
const Env = [
|
||||
'LOG_PATH=' + LogPath
|
||||
]
|
||||
|
||||
if (SYSTEM.CSC_LINK) Env.push('CSC_LINK=' + SYSTEM.CSC_LINK)
|
||||
if (SYSTEM.CSC_KEY_PASSWORD) Env.push('CSC_KEY_PASSWORD=' + SYSTEM.CSC_KEY_PASSWORD)
|
||||
if (SYSTEM.GH_TOKEN) Env.push('GH_TOKEN=' + SYSTEM.GH_TOKEN)
|
||||
|
||||
const optsc = {
|
||||
'AttachStdin': true,
|
||||
'AttachStdout': true,
|
||||
'AttachStderr': true,
|
||||
'Tty': true,
|
||||
'OpenStdin': true,
|
||||
'StdinOnce': false,
|
||||
'Env': Env,
|
||||
'Cmd': ['/bin/bash', '-c', containerCmd],
|
||||
'Image': imageName,
|
||||
'WorkingDir': '/project',
|
||||
'Volumes': {},
|
||||
'VolumesFrom': [],
|
||||
'HostConfig': {
|
||||
Binds: [
|
||||
workPath + ':/project:rw',
|
||||
'/etc/localtime:/etc/localtime:ro',
|
||||
workPath + '/.cache/electron:/root/.cache/electron',
|
||||
workPath + '/.cache/electron-builder:/root/.cache/electron-builder'
|
||||
],
|
||||
CpusetCpus: SYSTEM.BUILD_CPU_LIMIT || '0',
|
||||
Memory: Number(SYSTEM.BUILD_MEMORY_LIMIT) || 0,
|
||||
AutoRemove: true
|
||||
}
|
||||
}
|
||||
|
||||
const runDocker = () => {
|
||||
return new Promise((resolve, reject) => {
|
||||
this.docker.createContainer(optsc, (err, container) => {
|
||||
if (err || !container) return reject(err || 'container is null')
|
||||
|
||||
container.attach({stream: true, stdout: true, stderr: true}, (err, stream) => {
|
||||
if (err) return reject(err)
|
||||
const writerStream = fs.createWriteStream(LogPath)
|
||||
stream.pipe(writerStream)
|
||||
})
|
||||
|
||||
container.start((err, data) => {
|
||||
if (err) return reject(err)
|
||||
console.log(data)
|
||||
resolve(container.id)
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
try {
|
||||
const res = await runDocker()
|
||||
return this.success(res)
|
||||
} catch (err) {
|
||||
console.log(err)
|
||||
return this.fail(null, 10001, err.toString())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
22
src/routes/encrypt.js
Normal file
22
src/routes/encrypt.js
Normal file
@ -0,0 +1,22 @@
|
||||
import Joi from 'joi'
|
||||
import JsSHA from 'jssha'
|
||||
|
||||
export default [{
|
||||
method: 'GET',
|
||||
path: `/app/encrypt`,
|
||||
config: {
|
||||
auth: false,
|
||||
tags: ['api', 'app'],
|
||||
description: 'Token encrypt service.',
|
||||
validate: {
|
||||
query: {
|
||||
token: Joi.string().required().description('Token')
|
||||
}
|
||||
}
|
||||
},
|
||||
async handler (request) {
|
||||
const shaObj = new JsSHA('SHA-512', 'TEXT')
|
||||
shaObj.update(request.query.token)
|
||||
return this.success(shaObj.getHash('HEX'))
|
||||
}
|
||||
}]
|
12
src/routes/icon.ico.js
Normal file
12
src/routes/icon.ico.js
Normal file
@ -0,0 +1,12 @@
|
||||
export default [{
|
||||
method: 'GET',
|
||||
path: `/app/icon.ico`,
|
||||
config: {
|
||||
auth: false,
|
||||
tags: ['api', 'app'],
|
||||
description: 'Squirrel windows icon.'
|
||||
},
|
||||
handler (request, h) {
|
||||
return h.file('public/icon.ico')
|
||||
}
|
||||
}]
|
19
src/routes/index.js
Normal file
19
src/routes/index.js
Normal file
@ -0,0 +1,19 @@
|
||||
import fs from 'fs'
|
||||
import path from 'path'
|
||||
|
||||
const modules = []
|
||||
|
||||
const files = fs.readdirSync(__dirname).filter((file) => {
|
||||
return file.match(/\.(json|js)$/)
|
||||
})
|
||||
|
||||
files.forEach(key => {
|
||||
if (key === 'index.js') return
|
||||
|
||||
// const content = require(path.join(__dirname, key)).default
|
||||
const content = require(path.join(__dirname, key)).default
|
||||
|
||||
if (Array.isArray(content)) { modules.push(...content) } else { modules.push(content) }
|
||||
})
|
||||
|
||||
export default modules
|
54
src/routes/list.js
Normal file
54
src/routes/list.js
Normal file
@ -0,0 +1,54 @@
|
||||
import Joi from 'joi'
|
||||
import { SYSTEM } from '../config'
|
||||
import auth from '../lib/auth'
|
||||
|
||||
export default [{
|
||||
method: 'GET',
|
||||
path: `/app/list/release`,
|
||||
config: {
|
||||
auth: false,
|
||||
tags: ['api', 'app'],
|
||||
description: 'App release log list.',
|
||||
validate: {
|
||||
query: {
|
||||
token: Joi.string().required().description('Encrypted-Token')
|
||||
}
|
||||
}
|
||||
},
|
||||
async handler (request) {
|
||||
if (!auth(request.query.token)) {
|
||||
return this.fail(null, 403, 'Token Error.')
|
||||
}
|
||||
return this.success({
|
||||
name: SYSTEM.NAME,
|
||||
list: this.$db.get('appLog') // .filter(o => o.type !== 'maczip')
|
||||
.sortBy((item) => -item.releaseDate)
|
||||
.value()
|
||||
})
|
||||
}
|
||||
},
|
||||
{
|
||||
method: 'GET',
|
||||
path: `/app/list/build`,
|
||||
config: {
|
||||
auth: false,
|
||||
tags: ['api', 'app'],
|
||||
description: 'App build log list.',
|
||||
validate: {
|
||||
query: {
|
||||
token: Joi.string().required().description('Encrypted-Token')
|
||||
}
|
||||
}
|
||||
},
|
||||
async handler (request) {
|
||||
if (!auth(request.query.token)) {
|
||||
return this.fail(null, 403, 'Token Error.')
|
||||
}
|
||||
return this.success({
|
||||
name: SYSTEM.NAME,
|
||||
list: this.$db.get('buildLog')
|
||||
.sortBy((item) => -item.startDate)
|
||||
.value()
|
||||
})
|
||||
}
|
||||
}]
|
39
src/routes/log.log.js
Normal file
39
src/routes/log.log.js
Normal file
@ -0,0 +1,39 @@
|
||||
import fs from 'fs'
|
||||
import Joi from 'joi'
|
||||
|
||||
export default [
|
||||
{
|
||||
method: 'GET',
|
||||
path: `/build/log.log`,
|
||||
config: {
|
||||
auth: false,
|
||||
tags: ['api', 'build'],
|
||||
description: 'Get build log.',
|
||||
validate: {
|
||||
query: {
|
||||
path: Joi.string().required().description('Log path.')
|
||||
}
|
||||
}
|
||||
},
|
||||
async handler (request) {
|
||||
const path = decodeURI(request.query.path)
|
||||
|
||||
const logLast = this.$db.get('appLog')
|
||||
.filter({logPath: path})
|
||||
.sortBy((item) => -item.releaseDate)
|
||||
.take()
|
||||
.first()
|
||||
.value()
|
||||
|
||||
if (logLast && logLast.logPath) {
|
||||
try {
|
||||
return fs.readFileSync(logLast.logPath)
|
||||
} catch (err) {
|
||||
return this.fail(err)
|
||||
}
|
||||
} else {
|
||||
return this.fail(null, 10001, 'No file.')
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
78
src/routes/nupkg.js
Normal file
78
src/routes/nupkg.js
Normal file
@ -0,0 +1,78 @@
|
||||
import Joi from 'joi'
|
||||
import path from 'path'
|
||||
import { SYSTEM, COS, OSS, QINIU } from '../config'
|
||||
import axios from 'axios'
|
||||
|
||||
export default [
|
||||
{
|
||||
method: 'GET',
|
||||
path: `/app/nupkg/{version}/{releases}`,
|
||||
config: {
|
||||
auth: false,
|
||||
tags: ['api', 'app'],
|
||||
description: 'RELEASES file or download pukge.',
|
||||
validate: {
|
||||
params: {
|
||||
version: Joi.string().required().description('Version'),
|
||||
releases: Joi.string().required().description('RELEASES file or File name')
|
||||
}
|
||||
}
|
||||
},
|
||||
async handler (request, h) {
|
||||
const version = request.params.version
|
||||
const releases = request.params.releases
|
||||
|
||||
let objectStorageUrl = ''
|
||||
switch (SYSTEM.OBJECT_STORAGE_TYPE) {
|
||||
case 'cos':
|
||||
objectStorageUrl = COS.url
|
||||
break
|
||||
case 'oss':
|
||||
objectStorageUrl = OSS.url
|
||||
break
|
||||
case 'qiniu':
|
||||
objectStorageUrl = QINIU.url
|
||||
break
|
||||
}
|
||||
|
||||
if (releases === 'RELEASES' || releases === 'releases') {
|
||||
// TODO: ?id=name&localVersion=4.7.2&arch=amd64
|
||||
const nupkgLast = this.$db.get('appLog')
|
||||
.filter({type: 'RELEASES', version})
|
||||
.sortBy((item) => -item.releaseDate)
|
||||
.take()
|
||||
.first()
|
||||
.value()
|
||||
|
||||
if (nupkgLast) {
|
||||
const RELEASESname = path.join('RELEASES-' + version, 'RELEASES')
|
||||
const url = objectStorageUrl + '/app/' + nupkgLast.name + '/' + version + '/' + RELEASESname
|
||||
|
||||
try {
|
||||
const { data } = await axios.get(url)
|
||||
return data
|
||||
} catch (err) {
|
||||
console.log(err)
|
||||
return ''
|
||||
}
|
||||
}
|
||||
} else {
|
||||
const fileName = releases
|
||||
const nupkgLast = this.$db.get('appLog')
|
||||
.filter({type: 'nupkg', version})
|
||||
.sortBy((item) => -item.releaseDate)
|
||||
.take()
|
||||
.first()
|
||||
.value()
|
||||
|
||||
if (nupkgLast) {
|
||||
return h.redirect(objectStorageUrl + '/app/' + nupkgLast.name + '/' + version + '/' + fileName)
|
||||
} else {
|
||||
return ''
|
||||
}
|
||||
}
|
||||
|
||||
return this.fail()
|
||||
}
|
||||
}
|
||||
]
|
11
src/routes/public.js
Normal file
11
src/routes/public.js
Normal file
@ -0,0 +1,11 @@
|
||||
export default [
|
||||
{
|
||||
method: 'GET',
|
||||
path: '/{param*}',
|
||||
handler: {
|
||||
directory: {
|
||||
path: 'public'
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
40
src/routes/release.js
Normal file
40
src/routes/release.js
Normal file
@ -0,0 +1,40 @@
|
||||
import Joi from 'joi'
|
||||
import auth from '../lib/auth'
|
||||
|
||||
export default [
|
||||
{
|
||||
method: 'POST',
|
||||
path: `/app/release`,
|
||||
config: {
|
||||
auth: false,
|
||||
tags: ['api', 'app'],
|
||||
description: 'The app release.',
|
||||
validate: {
|
||||
payload: {
|
||||
token: Joi.string().required().description('Encrypted-Token'),
|
||||
name: Joi.string().required().description('The package.json name'),
|
||||
downloadUrl: Joi.string().required().description('Download URL'),
|
||||
version: Joi.string().required().description('APP version'),
|
||||
platform: Joi.string().required().description('Platform'),
|
||||
extended: Joi.string().required().description('Extended'),
|
||||
type: Joi.string().required().description('Type'),
|
||||
logPath: Joi.string().required().description('Log path'),
|
||||
author: Joi.string().description('Author'),
|
||||
authorEmail: Joi.string().description('Author Email'),
|
||||
message: Joi.string().description('Message')
|
||||
}
|
||||
}
|
||||
},
|
||||
async handler (request) {
|
||||
if (!auth(request.payload.token)) {
|
||||
return this.fail(null, 403, 'Token Error.')
|
||||
}
|
||||
|
||||
const data = request.payload
|
||||
data.releaseDate = new Date().getTime().toString()
|
||||
const result = this.$db.get('appLog').push(data).write()
|
||||
|
||||
return this.success(result)
|
||||
}
|
||||
}
|
||||
]
|
26
src/routes/release.json.js
Normal file
26
src/routes/release.json.js
Normal file
@ -0,0 +1,26 @@
|
||||
export default [
|
||||
{
|
||||
method: 'GET',
|
||||
path: `/app/release.json`,
|
||||
config: {
|
||||
auth: false,
|
||||
tags: ['api', 'app'],
|
||||
description: 'Get new JSON about MAC update.'
|
||||
},
|
||||
async handler () {
|
||||
const maczipLast = this.$db.get('appLog')
|
||||
.filter({type: 'maczip'})
|
||||
.sortBy((item) => -item.releaseDate)
|
||||
.take()
|
||||
.first()
|
||||
.value()
|
||||
|
||||
return maczipLast ? {
|
||||
'url': maczipLast.downloadUrl,
|
||||
'name': maczipLast.name,
|
||||
'notes': maczipLast.message,
|
||||
'pub_date': this.$moment(maczipLast.releaseDate).tz('Asia/Shanghai').format()
|
||||
} : {}
|
||||
}
|
||||
}
|
||||
]
|
62
src/routes/updates.json.js
Normal file
62
src/routes/updates.json.js
Normal file
@ -0,0 +1,62 @@
|
||||
import { SYSTEM } from '../config'
|
||||
|
||||
export default [{
|
||||
method: 'GET',
|
||||
path: `/app/updates.json`,
|
||||
config: {
|
||||
auth: false,
|
||||
tags: ['api', 'app'],
|
||||
description: 'Update check JSON.'
|
||||
},
|
||||
async handler () {
|
||||
const macLast = this.$db.get('appLog')
|
||||
.filter({
|
||||
platform: 'mac',
|
||||
type: 'install'
|
||||
})
|
||||
.sortBy((item) => -item.releaseDate)
|
||||
.take()
|
||||
.first()
|
||||
.value()
|
||||
|
||||
const winLast = this.$db.get('appLog')
|
||||
.filter({
|
||||
platform: 'win',
|
||||
type: 'install'
|
||||
})
|
||||
.sortBy((item) => -item.releaseDate)
|
||||
.take()
|
||||
.first()
|
||||
.value()
|
||||
|
||||
const linuxLast = this.$db.get('appLog')
|
||||
.filter({
|
||||
platform: 'linux',
|
||||
type: 'install'
|
||||
})
|
||||
.sortBy((item) => -item.releaseDate)
|
||||
.take()
|
||||
.first()
|
||||
.value()
|
||||
|
||||
return {
|
||||
'win32-x64-prod': winLast ? {
|
||||
'readme': winLast.name,
|
||||
'update': SYSTEM.SCHEME + '://' + SYSTEM.DOMAIN + '/app/nupkg/' + winLast.version,
|
||||
'install': winLast.downloadUrl,
|
||||
'version': winLast.version
|
||||
} : {},
|
||||
'darwin-x64-prod': macLast ? {
|
||||
'readme': macLast.name,
|
||||
'update': SYSTEM.SCHEME + '://' + SYSTEM.DOMAIN + '/app/release.json',
|
||||
'install': macLast.downloadUrl,
|
||||
'version': macLast.version
|
||||
} : {},
|
||||
'linux-x64-prod': linuxLast ? {
|
||||
'update': linuxLast.downloadUrl,
|
||||
'install': linuxLast.downloadUrl,
|
||||
'version': linuxLast.version
|
||||
} : {}
|
||||
}
|
||||
}
|
||||
}]
|
259
src/routes/upload.js
Normal file
259
src/routes/upload.js
Normal file
@ -0,0 +1,259 @@
|
||||
import fs from 'fs'
|
||||
import Joi from 'joi'
|
||||
import git from 'simple-git'
|
||||
import path from 'path'
|
||||
import uploadToObjectStorage from '../lib/upload'
|
||||
import auth from '../lib/auth'
|
||||
import {
|
||||
SYSTEM
|
||||
} from '../config'
|
||||
|
||||
const GIT_SSH_COMMAND = 'ssh -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no'
|
||||
|
||||
export default [{
|
||||
method: 'GET',
|
||||
path: `/app/upload`,
|
||||
config: {
|
||||
auth: false,
|
||||
tags: ['api', 'app'],
|
||||
description: 'Upload app to object storage.',
|
||||
validate: {
|
||||
query: {
|
||||
platform: Joi.string().required().description('System platform'),
|
||||
extended: Joi.string().default('x86-64').description('System extended'),
|
||||
startDate: Joi.string().required().description('Build startDate'),
|
||||
logPath: Joi.string().required().description('Log file path.'),
|
||||
token: Joi.string().required().description('Encrypted-Token')
|
||||
}
|
||||
}
|
||||
},
|
||||
async handler (request) {
|
||||
if (!auth(request.query.token)) {
|
||||
return this.fail(null, 403, 'Token Error.')
|
||||
}
|
||||
|
||||
const platform = request.query.platform
|
||||
const extended = request.query.extended
|
||||
const logPath = request.query.logPath
|
||||
const startDate = request.query.startDate
|
||||
const workPath = SYSTEM.WORKPATH
|
||||
const sourcePath = path.join(workPath, '/source')
|
||||
|
||||
const gitLog = (workPath) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
git(workPath).env({
|
||||
...process.env,
|
||||
GIT_SSH_COMMAND
|
||||
}).log({
|
||||
n: 1
|
||||
}, (err, status) => {
|
||||
if (err) {
|
||||
return reject(err)
|
||||
}
|
||||
resolve(status)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
try {
|
||||
const log = await gitLog(sourcePath)
|
||||
if (log && log.all && log.all.length === 1) {
|
||||
const gitInfo = log.all[0]
|
||||
let packageJson = null
|
||||
let linuxPath = null
|
||||
let winPath = null
|
||||
let filePath = null
|
||||
let filePath2 = null
|
||||
let filename = null
|
||||
let filename2 = null
|
||||
let nupkg = null
|
||||
let RELEASES = null
|
||||
let RELEASESname = null
|
||||
let nupkgname = null
|
||||
|
||||
switch (platform) {
|
||||
case 'mac':
|
||||
packageJson = JSON.parse(fs.readFileSync(path.join(sourcePath, 'package.json'), 'utf-8'))
|
||||
filePath = path.join(sourcePath, 'build', packageJson.build.productName + '-' + packageJson.version + '.dmg')
|
||||
filePath2 = path.join(sourcePath, 'build', packageJson.build.productName + '-' + packageJson.version + '-mac.zip')
|
||||
filename = packageJson.name + '-' + packageJson.version + '.dmg'
|
||||
filename2 = packageJson.name + '-' + packageJson.version + '-mac.zip'
|
||||
break
|
||||
case 'linux':
|
||||
linuxPath = path.join(workPath, '/linux')
|
||||
packageJson = JSON.parse(fs.readFileSync(path.join(linuxPath, 'package.json'), 'utf-8'))
|
||||
if (extended === 'armv7l') {
|
||||
filePath = path.join(linuxPath, 'build', packageJson.name + '-' + packageJson.version + '-armv7l.AppImage')
|
||||
filename = packageJson.name + '-' + packageJson.version + '-armv7l.AppImage'
|
||||
} else {
|
||||
filePath = path.join(linuxPath, 'build', packageJson.name + '-' + packageJson.version + '-x86_64.AppImage')
|
||||
filename = packageJson.name + '-' + packageJson.version + '-x86_64.AppImage'
|
||||
}
|
||||
break
|
||||
case 'win':
|
||||
winPath = path.join(workPath, '/win')
|
||||
packageJson = JSON.parse(fs.readFileSync(path.join(winPath, 'package.json'), 'utf-8'))
|
||||
filePath = path.join(winPath, 'build', 'squirrel-windows', packageJson.build.productName + ' Setup ' + packageJson.version + '.exe')
|
||||
filename = packageJson.name + '-' + packageJson.version + '.exe'
|
||||
nupkg = path.join(winPath, 'build', 'squirrel-windows', packageJson.name + '-' + packageJson.version + '-full.nupkg')
|
||||
RELEASES = path.join(winPath, 'build', 'squirrel-windows', 'RELEASES')
|
||||
RELEASESname = path.join('RELEASES-' + packageJson.version, 'RELEASES')
|
||||
nupkgname = packageJson.name + '-' + packageJson.version + '-full.nupkg'
|
||||
break
|
||||
}
|
||||
|
||||
this.$db.get('buildLog').find({
|
||||
startDate
|
||||
}).assign({
|
||||
status: 'uploading'
|
||||
}).write()
|
||||
|
||||
const fileFullPath = path.join('/app/', packageJson.name, packageJson.version, filename)
|
||||
|
||||
try {
|
||||
var upload = () => {
|
||||
uploadToObjectStorage(fileFullPath, filePath).then(res => {
|
||||
this.$db.get('appLog').push({
|
||||
name: packageJson.name,
|
||||
downloadUrl: res,
|
||||
version: packageJson.version,
|
||||
platform,
|
||||
extended,
|
||||
action: 'release',
|
||||
type: 'install',
|
||||
logPath,
|
||||
author: gitInfo.author_name,
|
||||
authorEmail: gitInfo.author_email,
|
||||
message: gitInfo.message,
|
||||
releaseDate: new Date().getTime().toString()
|
||||
}).write()
|
||||
|
||||
if (platform === 'linux') {
|
||||
this.$db.get('buildLog').find({
|
||||
startDate
|
||||
}).assign({
|
||||
status: 'finish'
|
||||
}).write()
|
||||
}
|
||||
}).catch(err => {
|
||||
console.log(err)
|
||||
})
|
||||
}
|
||||
upload()
|
||||
} catch (err) {
|
||||
console.log(err)
|
||||
upload()
|
||||
}
|
||||
|
||||
if (filePath2) {
|
||||
const fileFullPath2 = path.join('/app/', packageJson.name, packageJson.version, filename2)
|
||||
|
||||
try {
|
||||
var upload2 = () => {
|
||||
uploadToObjectStorage(fileFullPath2, filePath2).then(res => {
|
||||
this.$db.get('appLog').push({
|
||||
name: packageJson.name,
|
||||
downloadUrl: res,
|
||||
version: packageJson.version,
|
||||
platform,
|
||||
extended,
|
||||
action: 'release',
|
||||
type: 'maczip',
|
||||
logPath,
|
||||
author: gitInfo.author_name,
|
||||
authorEmail: gitInfo.author_email,
|
||||
message: gitInfo.message,
|
||||
releaseDate: new Date().getTime().toString()
|
||||
}).write()
|
||||
|
||||
this.$db.get('buildLog').find({
|
||||
startDate
|
||||
}).assign({
|
||||
status: 'finish'
|
||||
}).write()
|
||||
}).catch(err => {
|
||||
console.log(err)
|
||||
})
|
||||
}
|
||||
upload2()
|
||||
} catch (err) {
|
||||
console.log(err)
|
||||
upload2()
|
||||
}
|
||||
}
|
||||
|
||||
if (nupkg) {
|
||||
const fileFullPath3 = path.join('/app/', packageJson.name, packageJson.version, nupkgname)
|
||||
|
||||
try {
|
||||
var upload3 = () => {
|
||||
uploadToObjectStorage(fileFullPath3, nupkg).then(res => {
|
||||
this.$db.get('appLog').push({
|
||||
name: packageJson.name,
|
||||
downloadUrl: res,
|
||||
version: packageJson.version,
|
||||
platform,
|
||||
extended,
|
||||
action: 'release',
|
||||
type: 'nupkg',
|
||||
logPath,
|
||||
author: gitInfo.author_name,
|
||||
authorEmail: gitInfo.author_email,
|
||||
message: gitInfo.message,
|
||||
releaseDate: new Date().getTime().toString()
|
||||
}).write()
|
||||
|
||||
this.$db.get('buildLog').find({
|
||||
startDate
|
||||
}).assign({
|
||||
status: 'finish'
|
||||
}).write()
|
||||
}).catch(err => {
|
||||
console.log(err)
|
||||
})
|
||||
}
|
||||
upload3()
|
||||
} catch (err) {
|
||||
console.log(err)
|
||||
upload3()
|
||||
}
|
||||
|
||||
const fileFullPath4 = path.join('/app/', packageJson.name, packageJson.version, RELEASESname)
|
||||
try {
|
||||
var upload4 = () => {
|
||||
uploadToObjectStorage(fileFullPath4, RELEASES).then(res => {
|
||||
this.$db.get('appLog').push({
|
||||
name: packageJson.name,
|
||||
downloadUrl: res,
|
||||
version: packageJson.version,
|
||||
platform,
|
||||
extended,
|
||||
action: 'release',
|
||||
type: 'RELEASES',
|
||||
logPath,
|
||||
author: gitInfo.author_name,
|
||||
authorEmail: gitInfo.author_email,
|
||||
message: gitInfo.message,
|
||||
releaseDate: new Date().getTime().toString()
|
||||
}).write()
|
||||
}).catch(err => {
|
||||
console.log(err)
|
||||
})
|
||||
}
|
||||
upload4()
|
||||
} catch (err) {
|
||||
console.log(err)
|
||||
upload4()
|
||||
}
|
||||
}
|
||||
|
||||
return this.success('ok')
|
||||
} else {
|
||||
return this.fail(null, 10003, 'Get git log content error.')
|
||||
}
|
||||
} catch (err) {
|
||||
console.log(err)
|
||||
return this.fail(err.toString(), 10001, 'Get git log error.')
|
||||
}
|
||||
}
|
||||
}]
|
363
src/routes/webhooks.js
Normal file
363
src/routes/webhooks.js
Normal file
@ -0,0 +1,363 @@
|
||||
import fs from 'fs'
|
||||
import path from 'path'
|
||||
import git from 'simple-git'
|
||||
import { SYSTEM } from '../config'
|
||||
import { Client } from 'ssh2'
|
||||
import { spawn } from 'child_process'
|
||||
import JsSHA from 'jssha'
|
||||
import Joi from 'joi'
|
||||
import db from '../lib/db'
|
||||
|
||||
const repoPath = SYSTEM.REPOPATH
|
||||
const workPath = SYSTEM.WORKPATH
|
||||
const sourcePath = path.join(workPath, '/source')
|
||||
const linuxPath = path.join(workPath, '/linux')
|
||||
const winPath = path.join(workPath, '/win')
|
||||
|
||||
const WIN_IMAGE_NAME = 'electronuserland/builder:wine-mono'
|
||||
const LINUX_IMAGE_NAME = 'electronuserland/builder:10'
|
||||
const GIT_SSH_COMMAND = 'ssh -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no'
|
||||
|
||||
const getHashToken = () => {
|
||||
const shaObj = new JsSHA('SHA-512', 'TEXT')
|
||||
shaObj.update(SYSTEM.TOKEN)
|
||||
return shaObj.getHash('HEX')
|
||||
}
|
||||
|
||||
const gitCodeUpdate = async (buidType) => {
|
||||
const gitClone = (repoPath, workPath, type) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
git().env({
|
||||
...process.env,
|
||||
GIT_SSH_COMMAND
|
||||
})
|
||||
.clone(repoPath, workPath, (err) => {
|
||||
if (err) {
|
||||
reject(err)
|
||||
return
|
||||
}
|
||||
resolve({
|
||||
code: 1,
|
||||
type: 'clone',
|
||||
change: true
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
const gitPull = (workPath, type) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
git(workPath).env({
|
||||
...process.env,
|
||||
GIT_SSH_COMMAND
|
||||
})
|
||||
.pull((err, update) => {
|
||||
if (err) {
|
||||
reject(err)
|
||||
return
|
||||
}
|
||||
if (update && update.summary.changes) {
|
||||
resolve({
|
||||
code: 1,
|
||||
type: 'pull',
|
||||
change: true
|
||||
})
|
||||
} else {
|
||||
resolve({
|
||||
code: 1,
|
||||
type: 'clone',
|
||||
change: false
|
||||
})
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
const promiseList = []
|
||||
|
||||
if (buidType.includes('mac')) {
|
||||
if (!fs.existsSync(sourcePath)) {
|
||||
mkdirsSync(sourcePath)
|
||||
promiseList.push(gitClone(repoPath, sourcePath, 'Source'))
|
||||
} else {
|
||||
if (fs.readdirSync(sourcePath).includes('.git')) {
|
||||
promiseList.push(gitPull(sourcePath, 'Source'))
|
||||
} else {
|
||||
promiseList.push(gitClone(repoPath, sourcePath, 'Source'))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (buidType.includes('linux')) {
|
||||
if (!fs.existsSync(linuxPath)) {
|
||||
mkdirsSync(linuxPath)
|
||||
promiseList.push(gitClone(repoPath, linuxPath, 'Linux'))
|
||||
} else {
|
||||
if (fs.readdirSync(linuxPath).includes('.git')) {
|
||||
promiseList.push(gitPull(linuxPath, 'Linux'))
|
||||
} else {
|
||||
promiseList.push(gitClone(repoPath, linuxPath, 'Linux'))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (buidType.includes('win')) {
|
||||
if (!fs.existsSync(winPath)) {
|
||||
mkdirsSync(winPath)
|
||||
promiseList.push(gitClone(repoPath, winPath, 'Win'))
|
||||
} else {
|
||||
if (fs.readdirSync(winPath).includes('.git')) {
|
||||
promiseList.push(gitPull(winPath, 'Win'))
|
||||
} else {
|
||||
promiseList.push(gitClone(repoPath, winPath, 'Win'))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const res = await Promise.all(promiseList)
|
||||
|
||||
return res.every(a => a.code === 1)
|
||||
}
|
||||
|
||||
const mkdirsSync = (dirname) => {
|
||||
if (fs.existsSync(dirname)) {
|
||||
return true
|
||||
} else {
|
||||
if (mkdirsSync(path.dirname(dirname))) {
|
||||
fs.mkdirSync(dirname)
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const startDockerToBuild = async (name, version, type, docker) => {
|
||||
const publishOpt = SYSTEM.GH_TOKEN ? 'always' : 'never'
|
||||
const startDate = new Date().getTime().toString()
|
||||
|
||||
let containerCmd = 'yarn --ignore-engines'
|
||||
let imageName = null
|
||||
let workPath = SYSTEM.WORKPATH
|
||||
switch (type) {
|
||||
case 'win':
|
||||
workPath += '/win'
|
||||
imageName = WIN_IMAGE_NAME
|
||||
containerCmd += ' && yarn run build --' + type + ' --publish ' + publishOpt + ' && curl -X GET "' + SYSTEM.SCHEME + '://' + SYSTEM.DOMAIN + '/app/upload?platform=' + type + '&extended=x86-64&token=' + getHashToken() + '&startDate=' + startDate + '&logPath=$LOG_PATH" -H "cache-control: no-cache"'
|
||||
break
|
||||
case 'linux':
|
||||
workPath += '/linux'
|
||||
imageName = LINUX_IMAGE_NAME
|
||||
containerCmd += ' && yarn run build --' + type + ' --publish ' + publishOpt + ' && curl -X GET "' + SYSTEM.SCHEME + '://' + SYSTEM.DOMAIN + '/app/upload?platform=' + type + '&extended=x86-64&token=' + getHashToken() + '&startDate=' + startDate + '&logPath=$LOG_PATH" -H "cache-control: no-cache"'
|
||||
break
|
||||
}
|
||||
|
||||
if (!fs.existsSync(SYSTEM.WORKPATH + '/logs/' + type)) mkdirsSync(SYSTEM.WORKPATH + '/logs/' + type)
|
||||
const logPath = SYSTEM.WORKPATH + '/logs/' + type + '/' + (new Date()).getTime() + '.log'
|
||||
|
||||
db.get('buildLog').push({
|
||||
name,
|
||||
version,
|
||||
platform: type,
|
||||
extended: 'x86-64',
|
||||
action: 'build',
|
||||
status: 'buiding',
|
||||
logPath,
|
||||
startDate
|
||||
}).write()
|
||||
|
||||
const Env = [
|
||||
'LOG_PATH=' + logPath
|
||||
]
|
||||
|
||||
if (SYSTEM.CSC_LINK) Env.push('CSC_LINK=' + SYSTEM.CSC_LINK)
|
||||
if (SYSTEM.CSC_KEY_PASSWORD) Env.push('CSC_KEY_PASSWORD=' + SYSTEM.CSC_KEY_PASSWORD)
|
||||
if (SYSTEM.GH_TOKEN) Env.push('GH_TOKEN=' + SYSTEM.GH_TOKEN)
|
||||
|
||||
const optsc = {
|
||||
'AttachStdin': true,
|
||||
'AttachStdout': true,
|
||||
'AttachStderr': true,
|
||||
'Tty': true,
|
||||
'OpenStdin': true,
|
||||
'StdinOnce': false,
|
||||
'Env': Env,
|
||||
'Cmd': ['/bin/bash', '-c', containerCmd],
|
||||
'Image': imageName,
|
||||
'WorkingDir': '/project',
|
||||
'Volumes': {},
|
||||
'VolumesFrom': [],
|
||||
'HostConfig': {
|
||||
Binds: [
|
||||
workPath + ':/project:rw',
|
||||
'/etc/localtime:/etc/localtime:ro',
|
||||
workPath + '/.cache/electron:/root/.cache/electron',
|
||||
workPath + '/.cache/electron-builder:/root/.cache/electron-builder'
|
||||
],
|
||||
CpusetCpus: SYSTEM.BUILD_CPU_LIMIT || '0',
|
||||
Memory: Number(SYSTEM.BUILD_MEMORY_LIMIT) || 0,
|
||||
AutoRemove: true
|
||||
}
|
||||
}
|
||||
|
||||
const runDocker = (docker) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
docker.createContainer(optsc, (err, container) => {
|
||||
if (err || !container) return reject(err || 'container is null')
|
||||
|
||||
container.attach({stream: true, stdout: true, stderr: true}, (err, stream) => {
|
||||
const writerStream = fs.createWriteStream(logPath)
|
||||
if (err) return writerStream.write(err.toString())
|
||||
stream.pipe(writerStream)
|
||||
})
|
||||
|
||||
container.start((err, data) => {
|
||||
if (err) return reject(err)
|
||||
console.log(data)
|
||||
resolve(container.id)
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
return runDocker(docker)
|
||||
}
|
||||
|
||||
const macBuild = async (name, version) => {
|
||||
const type = 'mac'
|
||||
const publishOpt = SYSTEM.GH_TOKEN ? 'always' : 'never'
|
||||
|
||||
if (!fs.existsSync(SYSTEM.WORKPATH + '/logs/' + type)) mkdirsSync(SYSTEM.WORKPATH + '/logs/' + type)
|
||||
const logPath = SYSTEM.WORKPATH + '/logs/' + type + '/' + (new Date()).getTime() + '.log'
|
||||
|
||||
const startDate = new Date().getTime().toString()
|
||||
db.get('buildLog').push({
|
||||
name,
|
||||
version,
|
||||
platform: type,
|
||||
extended: 'x86-64',
|
||||
action: 'build',
|
||||
status: 'buiding',
|
||||
logPath: logPath,
|
||||
startDate
|
||||
}).write()
|
||||
|
||||
// 1. rsync server -> mac
|
||||
const writerStream = fs.createWriteStream(logPath, {flags: 'a'})
|
||||
const cmd = `rsync -avrz -e 'ssh -p ${SYSTEM.MAC_SERVER_PORT}' --delete-after --exclude "node_modules" ${sourcePath}/ ${SYSTEM.MAC_SERVER_USERNAME}@${SYSTEM.MAC_SERVER_HOST}:/tmp/${SYSTEM.NAME}`
|
||||
writerStream.write(cmd)
|
||||
const rsync = spawn('/bin/sh', ['-c', cmd])
|
||||
rsync.stdout.pipe(writerStream)
|
||||
rsync.stderr.pipe(writerStream)
|
||||
|
||||
rsync.on('close', (code) => {
|
||||
const writerStream = fs.createWriteStream(logPath, {flags: 'a'})
|
||||
writerStream.write(`\nChild process exited with code ${code} \n`)
|
||||
|
||||
// 2. build app and rsync mac build dir -> server build dir
|
||||
let bashContent = ''
|
||||
if (SYSTEM.CSC_LINK) bashContent += 'export CSC_LINK=' + SYSTEM.CSC_LINK + '\n'
|
||||
if (SYSTEM.CSC_KEY_PASSWORD) bashContent += 'export CSC_KEY_PASSWORD=' + SYSTEM.CSC_KEY_PASSWORD + '\n'
|
||||
if (SYSTEM.GH_TOKEN) bashContent += 'export GH_TOKEN=' + SYSTEM.GH_TOKEN + '\n'
|
||||
bashContent += 'export LOG_PATH=' + logPath + '\n'
|
||||
bashContent += 'cd /tmp/' + SYSTEM.NAME + '\n'
|
||||
bashContent += `yarn --ignore-engines` + ' && yarn run build --' + type + ' --publish ' + publishOpt + '\n'
|
||||
// bashContent += `echo -e "Host ${SYSTEM.LINUX_SERVER_HOST}\n\tStrictHostKeyChecking no\n" >> ~/.ssh/config\n`
|
||||
bashContent += `rsync -avrz -e 'ssh -p ${SYSTEM.LINUX_SERVER_PORT}' --exclude "node_modules" /tmp/` + SYSTEM.NAME + '/build/ ' + SYSTEM.LINUX_SERVER_USERNAME + '@' + SYSTEM.LINUX_SERVER_HOST + ':' + sourcePath + '/build \n'
|
||||
bashContent += 'curl -X GET "' + SYSTEM.SCHEME + '://' + SYSTEM.DOMAIN + '/app/upload?platform=' + type + '&extended=x86-64&token=' + getHashToken() + '&startDate=' + startDate + '&logPath=' + logPath + '" -H "cache-control: no-cache"\n'
|
||||
writerStream.write('Run command: \n')
|
||||
writerStream.write(bashContent)
|
||||
|
||||
const conn = new Client()
|
||||
conn.on('ready', function () {
|
||||
const writerStream = fs.createWriteStream(logPath, {flags: 'a'})
|
||||
writerStream.write('Client :: ready\n')
|
||||
conn.shell(function (err, stream) {
|
||||
if (err) throw err
|
||||
const writerStream = fs.createWriteStream(logPath, {flags: 'a'})
|
||||
stream.pipe(writerStream)
|
||||
stream.on('close', function () {
|
||||
const writerStream = fs.createWriteStream(logPath, {flags: 'a'})
|
||||
writerStream.write('\nStream :: close\n')
|
||||
conn.end()
|
||||
})
|
||||
stream.end(bashContent)
|
||||
})
|
||||
}).connect({
|
||||
host: SYSTEM.MAC_SERVER_HOST,
|
||||
port: Number(SYSTEM.MAC_SERVER_PORT),
|
||||
username: SYSTEM.MAC_SERVER_USERNAME,
|
||||
privateKey: require('fs').readFileSync('/root/.ssh/id_rsa')
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
const linuxBuild = async (name, version, docker) => {
|
||||
await startDockerToBuild(name, version, 'linux', docker)
|
||||
}
|
||||
|
||||
const winBuild = async (name, version, docker) => {
|
||||
await startDockerToBuild(name, version, 'win', docker)
|
||||
}
|
||||
|
||||
const sleep = (s) => new Promise(resolve => setTimeout(resolve, s))
|
||||
|
||||
export default [
|
||||
{
|
||||
method: 'POST',
|
||||
path: `/build/webhooks`,
|
||||
config: {
|
||||
auth: false,
|
||||
tags: ['api', 'build'],
|
||||
description: 'Github webhook',
|
||||
validate: {
|
||||
headers: {
|
||||
'x-hub-signature': Joi.string().required().description('Github Secret.')
|
||||
},
|
||||
options: {
|
||||
allowUnknown: true
|
||||
}
|
||||
}
|
||||
},
|
||||
async handler (request) {
|
||||
try {
|
||||
const shaObj = new JsSHA('SHA-1', 'TEXT')
|
||||
shaObj.setHMACKey(SYSTEM.TOKEN, 'TEXT')
|
||||
shaObj.update(JSON.stringify(request.payload))
|
||||
const hash = shaObj.getHMAC('HEX')
|
||||
if (request.headers && request.headers['x-hub-signature'] === 'sha1=' + hash) {
|
||||
const updateCodeRes = await gitCodeUpdate(SYSTEM.BUILD_TYPE)
|
||||
if (updateCodeRes) {
|
||||
const packageJson = JSON.parse(fs.readFileSync(path.join(sourcePath, 'package.json'), 'utf-8'))
|
||||
|
||||
if (packageJson && packageJson.name && packageJson.version) {
|
||||
const name = packageJson.name
|
||||
const version = packageJson.version
|
||||
if (SYSTEM.BUILD_TYPE.includes('mac')) {
|
||||
macBuild(name, version) // async
|
||||
await sleep(500)
|
||||
}
|
||||
|
||||
if (SYSTEM.BUILD_TYPE.includes('linux')) {
|
||||
linuxBuild(name, version, this.docker) // async
|
||||
await sleep(500)
|
||||
}
|
||||
|
||||
if (SYSTEM.BUILD_TYPE.includes('win')) {
|
||||
winBuild(name, version, this.docker) // async
|
||||
}
|
||||
} else {
|
||||
return this.fail(null, 10010, 'package read failed.')
|
||||
}
|
||||
} else {
|
||||
return this.fail(null, 10009, 'code update failed.')
|
||||
}
|
||||
|
||||
return this.success('ok')
|
||||
} else {
|
||||
return this.fail()
|
||||
}
|
||||
} catch (err) {
|
||||
return this.fail(null, 10001, err.toString())
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
5
src/tool/mac.js
Normal file
5
src/tool/mac.js
Normal file
@ -0,0 +1,5 @@
|
||||
// import { exec } from 'child_process'
|
||||
|
||||
// const out = spawn(this.getJavaPath() + '\\java.exe', ['-jar', execPath], {
|
||||
// cwd: path.join(__dirname.replace('app.asar', 'app.asar.unpacked'))
|
||||
// })
|
23
src/util.js
Normal file
23
src/util.js
Normal file
@ -0,0 +1,23 @@
|
||||
import Chance from 'chance'
|
||||
|
||||
const chance = new Chance()
|
||||
|
||||
// 给数字字符串补零
|
||||
function preZeroFill (num, size) {
|
||||
if (num >= Math.pow(10, size)) { // 如果num本身位数不小于size位
|
||||
return num.toString()
|
||||
} else {
|
||||
var _str = Array(size + 1).join('0') + num
|
||||
return _str.slice(_str.length - size)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取指定位数的整数随机数
|
||||
* @param {int} size 位数
|
||||
* @return {string} 定位数的整数随机数字符串
|
||||
*/
|
||||
export let getIntRandom = (size) => preZeroFill(chance.integer({
|
||||
min: 0,
|
||||
max: Array(size + 1).join(9)
|
||||
}), size)
|
168
src/websocket/container.js
Normal file
168
src/websocket/container.js
Normal file
@ -0,0 +1,168 @@
|
||||
import { Transform } from 'stream'
|
||||
import chalk from 'chalk'
|
||||
|
||||
const WIN_IMAGE_NAME = 'electronuserland/builder:wine-mono'
|
||||
const LINUX_IMAGE_NAME = 'electronuserland/builder:10'
|
||||
|
||||
export default (io, socket, docker) => {
|
||||
socket.on('exec', function (id, w, h) {
|
||||
var container = docker.getContainer(id)
|
||||
var cmd = {
|
||||
'AttachStdout': true,
|
||||
'AttachStderr': true,
|
||||
'AttachStdin': true,
|
||||
'Tty': true,
|
||||
Cmd: ['/bin/bash']
|
||||
}
|
||||
container.exec(cmd, (err, exec) => {
|
||||
var options = {
|
||||
'Tty': true,
|
||||
stream: true,
|
||||
stdin: true,
|
||||
stdout: true,
|
||||
stderr: true,
|
||||
// fix vim
|
||||
hijack: true
|
||||
}
|
||||
|
||||
container.wait((err, data) => {
|
||||
console.log(err)
|
||||
socket.emit('end', 'ended')
|
||||
})
|
||||
|
||||
if (err) {
|
||||
return
|
||||
}
|
||||
|
||||
exec.start(options, (err, stream) => {
|
||||
console.log(err)
|
||||
var dimensions = {
|
||||
h,
|
||||
w
|
||||
}
|
||||
if (dimensions.h !== 0 && dimensions.w !== 0) {
|
||||
exec.resize(dimensions, () => {})
|
||||
}
|
||||
|
||||
stream.on('data', (chunk) => {
|
||||
socket.emit('show', chunk.toString())
|
||||
})
|
||||
|
||||
socket.on('cmd', (data) => {
|
||||
stream.write(data)
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
socket.on('logs', function (id) {
|
||||
const container = docker.getContainer(id)
|
||||
|
||||
const logsOpts = {
|
||||
follow: true,
|
||||
stdout: true,
|
||||
stderr: true,
|
||||
timestamps: false
|
||||
}
|
||||
|
||||
container.logs(logsOpts, (err, stream) => {
|
||||
if (err) {
|
||||
console.log(err)
|
||||
socket.emit('err', chalk.red('Error:\n') + err + '.\n')
|
||||
return
|
||||
}
|
||||
|
||||
stream.on('data', (data) => { socket.emit('show', data.toString('utf-8')) })
|
||||
stream.on('end', function () {
|
||||
socket.emit('show', '\n===Stream finished===\n')
|
||||
stream.destroy()
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
socket.on('pull', function (type) {
|
||||
let imageName = null
|
||||
switch (type) {
|
||||
case 'win':
|
||||
imageName = WIN_IMAGE_NAME
|
||||
break
|
||||
case 'linux':
|
||||
imageName = LINUX_IMAGE_NAME
|
||||
break
|
||||
}
|
||||
|
||||
docker.pull(imageName, function (err, stream) {
|
||||
if (err) {
|
||||
console.log(err)
|
||||
socket.emit('err', chalk.red('Error:\n') + err + '.\n')
|
||||
return
|
||||
}
|
||||
|
||||
const bytesToSize = (bytes) => {
|
||||
if (bytes === 0) return '0 B'
|
||||
const k = 1000 // or 1024
|
||||
const sizes = ['B', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB']
|
||||
const i = Math.floor(Math.log(bytes) / Math.log(k))
|
||||
return (bytes / Math.pow(k, i)).toPrecision(3) + ' ' + sizes[i]
|
||||
}
|
||||
|
||||
let downTmp = []
|
||||
let downTmpId = []
|
||||
const commaSplitter = new Transform({
|
||||
readableObjectMode: true,
|
||||
transform (chunk, encoding, callback) {
|
||||
let tmp = ''
|
||||
try {
|
||||
var result = chunk.toString().match(/{(.*)}/)
|
||||
result = result ? result[0] : null
|
||||
if (!result) callback()
|
||||
tmp = JSON.parse(result)
|
||||
if (tmp.id) {
|
||||
if (downTmpId.includes(tmp.id)) {
|
||||
for (const n in downTmp) {
|
||||
if (downTmp[n].id === tmp.id) {
|
||||
if (tmp.progressDetail && tmp.progressDetail.current && tmp.progressDetail.total) {
|
||||
const percentage = Math.floor(100 * tmp.progressDetail.current / tmp.progressDetail.total)
|
||||
downTmp[n].val = ': [' + percentage + '%] Total ' + bytesToSize(tmp.progressDetail.total)
|
||||
} else if (tmp.status) {
|
||||
downTmp[n].val = ': ' + tmp.status
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
downTmpId.push(tmp.id)
|
||||
const temp = {}
|
||||
temp.id = tmp.id
|
||||
if (tmp.progressDetail && tmp.progressDetail.current && tmp.progressDetail.total) {
|
||||
const percentage = Math.floor(100 * tmp.progressDetail.current / tmp.progressDetail.total)
|
||||
temp.val = ': [' + percentage + '%] Total ' + bytesToSize(tmp.progressDetail.total)
|
||||
} else if (tmp.status) {
|
||||
temp.val = ': ' + tmp.status
|
||||
}
|
||||
downTmp.push(temp)
|
||||
}
|
||||
|
||||
let str = ''
|
||||
for (const n in downTmp) {
|
||||
str += downTmp[n].id + downTmp[n].val + '\n'
|
||||
}
|
||||
socket.emit('progress', str)
|
||||
}
|
||||
} catch (err) {
|
||||
// console.log(err)
|
||||
}
|
||||
|
||||
callback()
|
||||
}
|
||||
})
|
||||
|
||||
stream.pipe(commaSplitter)
|
||||
stream.once('end', () => {
|
||||
socket.emit('progress', chalk.green('All: [100%] Finish。\n'))
|
||||
// socket.emit('end', imageName + ' install ' + chalk.green('success') + '.\n')
|
||||
downTmp = []
|
||||
downTmpId = []
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
93
src/websocket/git.js
Normal file
93
src/websocket/git.js
Normal file
@ -0,0 +1,93 @@
|
||||
import git from 'simple-git'
|
||||
import chalk from 'chalk'
|
||||
import fs from 'fs'
|
||||
import path from 'path'
|
||||
import { SYSTEM } from '../config'
|
||||
|
||||
const GIT_SSH_COMMAND = 'ssh -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no'
|
||||
|
||||
const mkdirsSync = (dirname) => {
|
||||
if (fs.existsSync(dirname)) {
|
||||
return true
|
||||
} else {
|
||||
if (mkdirsSync(path.dirname(dirname))) {
|
||||
fs.mkdirSync(dirname)
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default (io, socket, docker) => {
|
||||
socket.on('gitPull', (data) => {
|
||||
const repoPath = SYSTEM.REPOPATH
|
||||
const workPath = SYSTEM.WORKPATH
|
||||
const sourcePath = path.join(workPath, '/source')
|
||||
const linuxPath = path.join(workPath, '/linux')
|
||||
const winPath = path.join(workPath, '/win')
|
||||
|
||||
const gitClone = (repoPath, workPath, type) => {
|
||||
git().env({
|
||||
...process.env,
|
||||
GIT_SSH_COMMAND
|
||||
})
|
||||
.clone(repoPath, workPath, (err) => {
|
||||
if (err) {
|
||||
socket.emit('err', chalk.red(type + ' clone error:\n') + err + '\n')
|
||||
return
|
||||
}
|
||||
socket.emit('show', chalk.green(type + ' clone is finished!\n'))
|
||||
})
|
||||
}
|
||||
|
||||
const gitPull = (workPath, type) => {
|
||||
git(workPath).env({
|
||||
...process.env,
|
||||
GIT_SSH_COMMAND
|
||||
})
|
||||
.pull((err, update) => {
|
||||
if (err) {
|
||||
socket.emit('err', chalk.red(type + ' pull error:\n') + err + '\n')
|
||||
return
|
||||
}
|
||||
if (update && update.summary.changes) {
|
||||
socket.emit('show', chalk.green(type + ' update success.\n'))
|
||||
} else {
|
||||
socket.emit('show', chalk.green(type + ' update success, no change.\n'))
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
if (!fs.existsSync(sourcePath)) {
|
||||
mkdirsSync(sourcePath)
|
||||
gitClone(repoPath, sourcePath, 'Source')
|
||||
} else {
|
||||
if (fs.readdirSync(sourcePath).includes('.git')) {
|
||||
gitPull(sourcePath, 'Source')
|
||||
} else {
|
||||
gitClone(repoPath, sourcePath, 'Source')
|
||||
}
|
||||
}
|
||||
|
||||
if (!fs.existsSync(linuxPath)) {
|
||||
mkdirsSync(linuxPath)
|
||||
gitClone(repoPath, linuxPath, 'Linux')
|
||||
} else {
|
||||
if (fs.readdirSync(linuxPath).includes('.git')) {
|
||||
gitPull(linuxPath, 'Linux')
|
||||
} else {
|
||||
gitClone(repoPath, linuxPath, 'Linux')
|
||||
}
|
||||
}
|
||||
|
||||
if (!fs.existsSync(winPath)) {
|
||||
mkdirsSync(winPath)
|
||||
gitClone(repoPath, winPath, 'Win')
|
||||
} else {
|
||||
if (fs.readdirSync(winPath).includes('.git')) {
|
||||
gitPull(winPath, 'Win')
|
||||
} else {
|
||||
gitClone(repoPath, winPath, 'Win')
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
30
src/websocket/index.js
Normal file
30
src/websocket/index.js
Normal file
@ -0,0 +1,30 @@
|
||||
import container from './container'
|
||||
import git from './git'
|
||||
import log from './log'
|
||||
import JsSHA from 'jssha'
|
||||
import {
|
||||
SYSTEM
|
||||
} from '../config'
|
||||
|
||||
export default (io, docker) => {
|
||||
console.log('Websocket Runing...')
|
||||
io.on('connection', function (socket) {
|
||||
console.log('One user connected - ' + socket.id)
|
||||
socket.emit('requireAuth', 'distribution')
|
||||
socket.emit('opend', new Date())
|
||||
|
||||
socket.on('auth', function (token) {
|
||||
const shaObj = new JsSHA('SHA-512', 'TEXT')
|
||||
shaObj.update(SYSTEM.TOKEN)
|
||||
const hash = shaObj.getHash('HEX')
|
||||
if (token === hash) {
|
||||
container(io, socket, docker)
|
||||
git(io, socket, docker)
|
||||
log(io, socket, docker)
|
||||
socket.emit('auth', 'success')
|
||||
} else {
|
||||
socket.emit('auth', 'fail')
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
25
src/websocket/log.js
Normal file
25
src/websocket/log.js
Normal file
@ -0,0 +1,25 @@
|
||||
import fs from 'fs'
|
||||
import db from '../lib/db'
|
||||
|
||||
export default (io, socket, docker) => {
|
||||
socket.on('log', function (path) {
|
||||
path = decodeURI(path)
|
||||
|
||||
const logLast = db.get('buildLog')
|
||||
.filter({logPath: path})
|
||||
.sortBy((item) => -item.startDate)
|
||||
.take()
|
||||
.first()
|
||||
.value()
|
||||
|
||||
if (logLast && logLast.logPath) {
|
||||
try {
|
||||
socket.emit('show', fs.readFileSync(logLast.logPath) + '\n')
|
||||
} catch (err) {
|
||||
socket.emit('show', err.toString() + '\n')
|
||||
}
|
||||
} else {
|
||||
socket.emit('show', 'No file.')
|
||||
}
|
||||
})
|
||||
}
|
15
test/spawn.js
Normal file
15
test/spawn.js
Normal file
@ -0,0 +1,15 @@
|
||||
const { spawn } = require('child_process')
|
||||
const fs = require('fs')
|
||||
|
||||
const writerStream = fs.createWriteStream('test/test.log', {flags: 'a'})
|
||||
|
||||
const cmd = `rsync -avrz -e 'ssh -p 22' --delete-after --exclude "node_modules" /data/ykfz/source/ root@xxxxxxx:/tmp/ykfz`
|
||||
writerStream.write(cmd)
|
||||
const rsync = spawn('/bin/sh', ['-c', cmd])
|
||||
rsync.stdout.pipe(writerStream)
|
||||
rsync.stderr.pipe(writerStream)
|
||||
|
||||
rsync.on('close', (code) => {
|
||||
const writerStream = fs.createWriteStream('test/test.log', {flags: 'a'})
|
||||
writerStream.write(`\nChild process exited with code ${code} \n`)
|
||||
})
|
1
test/test.log
Normal file
1
test/test.log
Normal file
@ -0,0 +1 @@
|
||||
Child process exited with code 23
|
Loading…
Reference in New Issue
Block a user