Merge remote-tracking branch 'origin/master'

# Conflicts:
#	package-lock.json
This commit is contained in:
Van 2019-05-17 09:48:47 +08:00
commit 7167b83c9e
No known key found for this signature in database
GPG Key ID: 7059B8783A78F16C
125 changed files with 1657 additions and 2924 deletions

1
.gitattributes vendored Normal file
View File

@ -0,0 +1 @@
static/js/lib/* linguist-vendored

46
.github/ISSUE_TEMPLATE/bug_report.md vendored Normal file
View File

@ -0,0 +1,46 @@
---
name: 报告缺陷
about: 报告缺陷以帮助我们改进
---
**请先看[《提问的智慧》](https://hacpai.com/article/1536377163156)**,并尝试到[讨论区](https://hacpai.com/tag/wide)搜寻资料解决问题。
----
### 描述问题
请尽量清晰精准地描述你碰到的问题。
### 重现步骤
请描述如何重现这个问题:
1. Go to '...'
2. Click on '...'
3. Scroll down to '...'
4. See error
### 期待的结果
请尽量清晰精准地描述你所期待的结果。
### 截屏或录像
如果可能,请尽量附加截图或录像来描述你遇到的问题。
### 桌面端环境
- OS: [e.g. windows, mac, linux]
- Browser [e.g. chrome, safari]
- Version [e.g. 22]
### 移动端环境
- Device: [e.g. iPhone8]
- OS: [e.g. iOS12]
- Browser [e.g. stock browser, safari]
- Version [e.g. 22]
### 其他信息
请提供其他附加信息帮助我们诊断问题。

View File

@ -0,0 +1,20 @@
---
name: 请求新功能
about: 提出你期待的功能特性
---
### 你在什么场景下需要该功能?
请尽量清晰精准地描述你碰到的问题。
### 描述可能的解决方案
请尽量清晰精准地描述你期待我们要做的,描述你想到的实现方案。
### 描述你认为的候选方案
请尽量清晰精准地描述你能接受的候选解决方案。
### 其他信息
请提供关于该功能建议的其他附加信息。

2
.gitignore vendored
View File

@ -1,8 +1,6 @@
/wide.exe /wide.exe
/wide /wide
/static/user/admin/style.css
/header /header
/header.exe /header.exe

View File

@ -10,11 +10,12 @@
], ],
"Excludes": [ "Excludes": [
"static/js/lib/*", "static/js/lib/*",
"static/user/*" "static/user/*",
"vendor/**"
], ],
"UseDefaultExcludes": true, "UseDefaultExcludes": true,
"Properties": { "Properties": {
"Year": "2014-2017", "Year": "2014-2019",
"Owner": "b3log.org & hacpai.com" "Owner": "b3log.org & hacpai.com"
} }
} }

View File

@ -4,7 +4,7 @@ Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License. you may not use this file except in compliance with the License.
You may obtain a copy of the License at You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0 https://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS, distributed under the License is distributed on an "AS IS" BASIS,

View File

@ -1,7 +1,7 @@
language: go language: go
go: go:
- 1.4 - 1.12
before_install: before_install:
- go get github.com/axw/gocov/gocov - go get github.com/axw/gocov/gocov
- go get github.com/mattn/goveralls - go get github.com/mattn/goveralls

View File

@ -1,32 +0,0 @@
FROM golang:1.7.4
MAINTAINER Liang Ding <dl88250@gmail.com>
RUN apt-get update && apt-get install bzip2 zip unzip
ENV GOROOT /usr/local/go
RUN cp -r /usr/local/go /usr/local/gobt
ENV GOROOT_BOOTSTRAP=/usr/local/gobt
RUN cd /usr/local/go/src && export GOOS=darwin && export GOARCH=amd64 && ./make.bash --no-clean
RUN cd /usr/local/go/src && export GOOS=linux && export GOARCH=arm && ./make.bash --no-clean
RUN cd /usr/local/go/src && export GOOS=windows && export GOARCH=amd64 && ./make.bash --no-clean
ADD . /wide/gogogo/src/github.com/b3log/wide
RUN unzip /wide/gogogo/src/github.com/b3log/wide/deps/golang.org.zip -d /wide/gogogo/src/
RUN unzip /wide/gogogo/src/github.com/b3log/wide/deps/github.com.zip -d /wide/gogogo/src/
RUN useradd wide && useradd runner
ENV GOPATH /wide/gogogo
RUN go build github.com/go-fsnotify/fsnotify
RUN go build github.com/gorilla/sessions
RUN go build github.com/gorilla/websocket
RUN go install github.com/visualfc/gotools github.com/nsf/gocode github.com/bradfitz/goimports
WORKDIR /wide/gogogo/src/github.com/b3log/wide
RUN go build -v
EXPOSE 7070

View File

@ -192,7 +192,7 @@
you may not use this file except in compliance with the License. you may not use this file except in compliance with the License.
You may obtain a copy of the License at You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0 https://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS, distributed under the License is distributed on an "AS IS" BASIS,

212
README.md
View File

@ -1,173 +1,129 @@
# [Wide](https://github.com/b3log/wide) [![Build Status](https://img.shields.io/travis/b3log/wide.svg?style=flat)](https://travis-ci.org/b3log/wide) [![Coverage Status](https://img.shields.io/coveralls/b3log/wide.svg?style=flat)](https://coveralls.io/r/b3log/wide) [![Apache License](http://img.shields.io/badge/license-apache2-orange.svg?style=flat)](http://www.apache.org/licenses/LICENSE-2.0) [![API Documentation](http://img.shields.io/badge/godoc-reference-blue.svg?style=flat)](http://godoc.org/github.com/b3log/wide) [![Download](http://img.shields.io/badge/download-~4.3K-red.svg?style=flat)](http://pan.baidu.com/s/1dD3XwOT) # [Wide](https://github.com/b3log/wide) [![Build Status](https://img.shields.io/travis/b3log/wide.svg?style=flat)](https://travis-ci.org/b3log/wide) [![Go Report Card](https://goreportcard.com/badge/github.com/b3log/wide)](https://goreportcard.com/report/github.com/b3log/wide) [![Coverage Status](https://img.shields.io/coveralls/b3log/wide.svg?style=flat)](https://coveralls.io/r/b3log/wide) [![Apache License](https://img.shields.io/badge/license-apache2-orange.svg?style=flat)](https://www.apache.org/licenses/LICENSE-2.0) [![API Documentation](https://img.shields.io/badge/godoc-reference-blue.svg?style=flat)](https://godoc.org/github.com/b3log/wide) [![Download](https://img.shields.io/badge/download-~4.3K-red.svg?style=flat)](https://pan.baidu.com/s/1dD3XwOT)
_Have a [try](http://wide.b3log.org/signup) first, then [download](http://pan.baidu.com/s/1dD3XwOT) and setup it on your local area network, enjoy yourself!_ 先试试我们搭建好的[在线服务](https://wide.b3log.org/signup),你可以在这里[下载](https://pan.baidu.com/s/1dD3XwOT)并在本地环境运行,然后邀请小伙伴们来玩吧!
先试试我们搭建好的[在线服务](http://wide.b3log.org/signup),你可以在这里[下载](http://pan.baidu.com/s/1dD3XwOT)并在本地环境运行,然后邀请小伙伴们来玩吧! ## 简介
> * 关于 Wide 的产品定位,请看[这里](http://hacpai.com/article/1438407961481),并欢迎参与讨论~ Wide 是一个基于 **W**eb 的 Go 语言 **IDE**
> * 加入[**黑客派**](http://hacpai.com/register),与其他程序员、设计师共同成长!
## Introduction ![](https://cloud.githubusercontent.com/assets/873584/4606377/d0ca3c2a-521b-11e4-912c-d955ab05850b.png)
A <b>W</b>eb-based <b>IDE</b> for Teams using Go programming language/Golang. ## 动机
![Hello, 世界](https://cloud.githubusercontent.com/assets/873584/4606377/d0ca3c2a-521b-11e4-912c-d955ab05850b.png) 目前较为流行的 Go IDE 都有一些缺陷或遗憾:
* 文本编辑器类vim/emacs/sublime/Atom 等):对于新手门槛太高,搭建复杂
* 插件类goclipse、IDEA 等):需要原 IDE 支持,不够专业
* LiteIDE 界面不够 modern、goland 收费
* **缺少网络分享、嵌入网站可运行功能**
## Authors 另外Go IDE 很少,用 Go 本身开发的 IDE 更是没有,这是一个很好的尝试。关于产品定位的讨论请看[这里](https://hacpai.com/article/1438407961481)。
[Daniel](https://github.com/88250) and [Vanessa](https://github.com/Vanessa219) are the main authors of Wide, [here](https://github.com/b3log/wide/graphs/contributors) are all contributors. ## 特性
Wide 的主要作者是 [Daniel](https://github.com/88250) 与 [Vanessa](https://github.com/Vanessa219),所有贡献者可以在[这里](https://github.com/b3log/wide/graphs/contributors)看到。 基于 Web 的 IDE
## Motivation * 只需要浏览器就能进行开发、运行
* 跨平台,甚至在移动设备上
* 易进行功能扩展
* 易与其他系统集成
* 极客体验
* **Team** IDE: 核心功能:
* _Safe and reliable_: the project source code stored on the server in real time, the developer's machine crashes without losing any source code
* _Unified environment_: server unified development environment configuration, the developer machine without any additional configuration
* _Out of the box_: 5 minutes to setup a server then open browser to develop, debug
* _Version Control_: each developer has its own source code repository, easy sync with the trunk
* **Web-based** IDE:
* Developer needs a browser only
* Cross-platform, even on mobile devices
* Easy to extend
* Easy to integrate with other systems
* For the geeks
* A try for commercial-open source: versions customized for enterprises, close to their development work flows respectively
* Currently more popular Go IDE has some defects or regrets:
* Text editor (vim/emacs/sublime/Atom, etc.): For the Go newbie is too complex
* Plug-in (goclipse, etc.): the need for the original IDE support, not professional
* LiteIDE: no modern user interface :p
* No team development experience
* There are a few of GO IDEs, and no one developed by Go itself, this is a nice try
## Features * 代码高亮、折叠Go/HTML/JavaScript/Markdown 等
* 自动完成Go/HTML 等
* 编译检查:编辑器提示编译错误
* 格式化Go/HTML/JSON 等
* 运行:支持同时运行多个程序
* 代码导航:跳转到声明,查找使用,文件搜索等
* Web 开发前端HTML/JS/CSS开发支持
* go toolgo get/install/fmt 等
* 项目文件导出
* UI/编辑器多主题
* 支持交叉编译
* [X] Code Highlight, Folding: Go/HTML/JavaScript/Markdown etc. ## 界面
* [X] Autocomplete: Go/HTML etc.
* [X] Format: Go/HTML/JSON etc.
* [X] Build & Run
* [X] Multiplayer: a real team development experience
* [X] Navigation, Jump to declaration, Find usages, File search etc.
* [X] Shell: run command on the server
* [X] Web development: HTML/JS/CSS editor with [Emmet](http://emmet.io) integrated
* [X] Go tool: go get/install/fmt etc.
* [X] File Import & Export
* [X] Themes: editor and UI adjust, respectively
* [X] Cross-Compilation
* [ ] Debug
* [ ] Git integration: git command on the web
## Screenshots ### 主界面
* **Overview** ![Overview](https://cloud.githubusercontent.com/assets/873584/5450620/1d51831e-8543-11e4-930b-670871902425.png)
![Overview](https://cloud.githubusercontent.com/assets/873584/5450620/1d51831e-8543-11e4-930b-670871902425.png) ### 跳转到文件
* **Goto File**
![Goto File](https://cloud.githubusercontent.com/assets/873584/5450616/1d495da6-8543-11e4-9285-f9d9c60779ac.png) ![Goto File](https://cloud.githubusercontent.com/assets/873584/5450616/1d495da6-8543-11e4-9285-f9d9c60779ac.png)
* **Autocomplete**
![Autocomplete](https://cloud.githubusercontent.com/assets/873584/5450619/1d4d5712-8543-11e4-8fe4-35dbc8348a6e.png) ### 自动完成
* **Theme**
![4](https://cloud.githubusercontent.com/assets/873584/5450617/1d4c0826-8543-11e4-8b86-f79a4e41550a.png) ![Autocomplete](https://cloud.githubusercontent.com/assets/873584/5450619/1d4d5712-8543-11e4-8fe4-35dbc8348a6e.png)
* **Show Expression Info**
![Show Expression Info](https://cloud.githubusercontent.com/assets/873584/5450618/1d4cd9f4-8543-11e4-950f-121bd3ff4a39.png) ### 主题
* **Build Error Info**
![Build Error Info](https://cloud.githubusercontent.com/assets/873584/5450632/3e51cccc-8543-11e4-8ca8-8d2427aa16b8.png) ![Theme](https://cloud.githubusercontent.com/assets/873584/5450617/1d4c0826-8543-11e4-8b86-f79a4e41550a.png)
* **Git Clone**
![Git Clone](https://cloud.githubusercontent.com/assets/873584/6545235/2284f230-c5b7-11e4-985e-7e04367921b1.png) ### 查看表达式
* **Cross-Compilation**
![Cross-Compilation](https://cloud.githubusercontent.com/assets/873584/10130037/226d75fc-65f7-11e5-94e4-25ee579ca175.png) ![Show Expression Info](https://cloud.githubusercontent.com/assets/873584/5450618/1d4cd9f4-8543-11e4-950f-121bd3ff4a39.png)
* **Playground** ### 构建报错提示
![Playground](https://cloud.githubusercontent.com/assets/873584/21209772/449ecfd2-c2b1-11e6-9aa6-a83477d9f269.gif)
## Architecture ![Build Error Info](https://cloud.githubusercontent.com/assets/873584/5450632/3e51cccc-8543-11e4-8ca8-8d2427aa16b8.png)
### Build & Run ### 交叉编译
![Cross-Compilation](https://cloud.githubusercontent.com/assets/873584/10130037/226d75fc-65f7-11e5-94e4-25ee579ca175.png)
### Playground
![Playground](https://cloud.githubusercontent.com/assets/873584/21209772/449ecfd2-c2b1-11e6-9aa6-a83477d9f269.gif)
## 架构
### 构建与运行
![Build & Run](https://cloud.githubusercontent.com/assets/873584/4389219/3642bc62-43f3-11e4-8d1f-06d7aaf22784.png) ![Build & Run](https://cloud.githubusercontent.com/assets/873584/4389219/3642bc62-43f3-11e4-8d1f-06d7aaf22784.png)
* A browser tab corresponds to a Wide session * 一个浏览器 tab 对应一个 Wide 会话
* Execution output push via WebSocket * 通过 WebSocket 进行程序执行输出推送
Flow: 1. 客户端浏览器发送 ````Build```` 请求
1. Browser sends ````Build```` request 2. 服务器使用 ````os/exec```` 执行 ````go build```` 命令<br/>
2. Server executes ````go build```` command via ````os/exec````<br/> 2.1. 生成可执行文件
2.1. Generates a executable file 3. 客户端浏览器发送 ````Run```` 请求
3. Browser sends ````Run```` request 4. 服务器使用 ````os/exec```` 执行文件<br/>
4. Server executes the file via ````os/exec````<br/> 4.1. 生成进程<br/>
4.1. A running process<br/> 4.2. 运行结果输出到 WebSocket 通道
4.2. Execution output push via WebSocket channel 5. 客户端浏览器监听 ````ws.onmessage```` 到消息后做展现
5. Browser renders with callback function ````ws.onmessage````
### Code Assist ### 代码辅助
![Code Assist](https://cloud.githubusercontent.com/assets/873584/4399135/3b80c21c-4463-11e4-8e94-7f7e8d12a4df.png) ![](https://cloud.githubusercontent.com/assets/873584/4399135/3b80c21c-4463-11e4-8e94-7f7e8d12a4df.png)
* Autocompletion * 自动完成
* Find Usages/Jump To Declaration/etc. * 查找使用
Flow: 1. 浏览器客户端发送代码辅助请求
1. Browser sends code assist request 2. Handler 根据请求对应的 HTTP 会话获取用户工作空间
2. Handler gets user workspace of the request with HTTP session 3. 执行 `gocode`/`ide_stub(gotools)` 命令<br/>
3. Server executes ````gocode````/````ide_stub(gotools)````<br/> 3.1 设置环境变量(${GOPATH} 为用户工作空间路径)<br/>
3.1 Sets environment variables (e.g. ${GOPATH})<br/> 3.2 `gocode` 命令需要设置参数 `lib-path`
3.2 ````gocode```` with ````lib-path```` parameter
## Documents ## 文档
* [用户指南](https://www.gitbook.com/book/88250/wide-user-guide) * [用户指南](https://hacpai.com/article/1538873544275)
* [开发指南](https://www.gitbook.com/book/88250/wide-dev-guide) * [开发指南](https://hacpai.com/article/1538876422995)
## Setup ## 社区
### Download Binary * [讨论区](https://hacpai.com/tag/wide)
* [报告问题](https://github.com/b3log/wide/issues/new/choose)
We have provided OS-specific executable binary as follows: ## 授权
* linux-amd64/386 Wide 使用 [Apache License, Version 2](https://www.apache.org/licenses/LICENSE-2.0) 作为开源协议,请务必遵循该开源协议相关约定。
* windows-amd64/386
* darwin-amd64/386
Download [HERE](http://pan.baidu.com/s/1dD3XwOT)! ## 鸣谢
### Build Wide for yourself * [golang](https://golang.org)
1. [Download](https://github.com/b3log/wide/archive/master.zip) source or by `git clone https://github.com/b3log/wide`
2. Get dependencies with
* `go get`
* `go get github.com/visualfc/gotools github.com/nsf/gocode github.com/bradfitz/goimports`
3. Compile wide with `go build`
### Docker
1. Get image: `sudo docker pull 88250/wide:latest`
2. Run: `sudo docker run -p 127.0.0.1:7070:7070 88250/wide:latest ./wide -docker=true -channel=ws://127.0.0.1:7070`
3. Open browser: http://127.0.0.1:7070
## Known Issues
* [Shell is not available on Windows](https://github.com/b3log/wide/issues/32)
* [Rename directory](https://github.com/b3log/wide/issues/251)
## Terms
* This software is open sourced under the Apache License 2.0
* You can not get rid of the "Powered by [B3log](http://b3log.org)" from any page, even which you made
* If you want to use this software for commercial purpose, please mail to support@liuyun.io for a commercial license request
* Copyright &copy; b3log.org, all rights reserved
## Credits
Wide is made possible by the following open source projects.
* [golang](http://golang.org)
* [CodeMirror](https://github.com/marijnh/CodeMirror) * [CodeMirror](https://github.com/marijnh/CodeMirror)
* [zTree](https://github.com/zTree/zTree_v3) * [zTree](https://github.com/zTree/zTree_v3)
* [LiteIDE](https://github.com/visualfc/liteide) * [LiteIDE](https://github.com/visualfc/liteide)

View File

@ -1,4 +1,4 @@
* This software is open sourced under the Apache License 2.0 * This software is open sourced under the Apache License 2.0
* You can not get rid of the "Powered by [B3log](http://b3log.org)" from any pages, even the pages are developed by you * You can not get rid of the "Powered by [B3log](https://b3log.org)" from any pages, even the pages are developed by you
* If you want to use this software for commercial purpose, please mail to support@liuyun.io for request a commercial license * If you want to use this software for commercial purpose, please mail to os@b3log.org for request a commercial license
* Copyright (c) b3log.org, all rights reserved * Copyright (c) b3log.org, all rights reserved

View File

@ -1,10 +1,10 @@
// Copyright (c) 2014-2017, b3log.org & hacpai.com // Copyright (c) 2014-2019, b3log.org & hacpai.com
// //
// Licensed under the Apache License, Version 2.0 (the "License"); // Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License. // you may not use this file except in compliance with the License.
// You may obtain a copy of the License at // You may obtain a copy of the License at
// //
// http://www.apache.org/licenses/LICENSE-2.0 // https://www.apache.org/licenses/LICENSE-2.0
// //
// Unless required by applicable law or agreed to in writing, software // Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS, // distributed under the License is distributed on an "AS IS" BASIS,
@ -15,9 +15,6 @@
package conf package conf
import ( import (
"crypto/md5"
"crypto/sha1"
"encoding/hex"
"encoding/json" "encoding/json"
"io/ioutil" "io/ioutil"
"os" "os"
@ -25,8 +22,6 @@ import (
"regexp" "regexp"
"strings" "strings"
"time" "time"
"github.com/b3log/wide/util"
) )
// Panel represents a UI panel. // Panel represents a UI panel.
@ -52,11 +47,9 @@ type LatestSessionContent struct {
// User configuration. // User configuration.
type User struct { type User struct {
Id string
Name string Name string
Password string Avatar string
Salt string
Email string
Gravatar string // see http://gravatar.com
Workspace string // the GOPATH of this user (maybe contain several paths splitted by os.PathListSeparator) Workspace string // the GOPATH of this user (maybe contain several paths splitted by os.PathListSeparator)
Locale string Locale string
GoFormat string GoFormat string
@ -83,28 +76,7 @@ type editor struct {
TabSize string TabSize string
} }
// NewUser creates a user with the specified username, password, email and workspace. // Save saves the user's configurations in conf/users/{userId}.json.
func NewUser(username, password, email, workspace string) *User {
md5hash := md5.New()
md5hash.Write([]byte(email))
gravatar := hex.EncodeToString(md5hash.Sum(nil))
salt := util.Rand.String(16)
password = Salt(password, salt)
now := time.Now().UnixNano()
return &User{Name: username, Password: password, Salt: salt, Email: email, Gravatar: gravatar, Workspace: workspace,
Locale: Wide.Locale, GoFormat: "gofmt",
GoBuildArgsForLinux: "-i", GoBuildArgsForWindows: "-i", GoBuildArgsForDarwin: "-i",
FontFamily: "Helvetica", FontSize: "13px", Theme: "default",
Keymap: "wide",
Created: now, Updated: now, Lived: now,
Editor: &editor{FontFamily: "Consolas, 'Courier New', monospace", FontSize: "inherit", LineHeight: "17px",
Theme: "wide", TabSize: "4"}}
}
// Save saves the user's configurations in conf/users/{username}.json.
func (u *User) Save() bool { func (u *User) Save() bool {
bytes, err := json.MarshalIndent(u, "", " ") bytes, err := json.MarshalIndent(u, "", " ")
@ -115,12 +87,12 @@ func (u *User) Save() bool {
} }
if "" == string(bytes) { if "" == string(bytes) {
logger.Error("Truncated user [" + u.Name + "]") logger.Error("Truncated user [" + u.Id + "]")
return false return false
} }
if err = ioutil.WriteFile("conf/users/"+u.Name+".json", bytes, 0644); nil != err { if err = ioutil.WriteFile(filepath.Join(Wide.Data, "users", u.Id+".json"), bytes, 0644); nil != err {
logger.Error(err) logger.Error(err)
return false return false
@ -129,6 +101,20 @@ func (u *User) Save() bool {
return true return true
} }
// NewUser creates a user with the specified username and workspace.
func NewUser(id, name, avatar, workspace string) *User {
now := time.Now().UnixNano()
return &User{Id: id, Name: name, Avatar: avatar, Workspace: workspace,
Locale: Wide.Locale, GoFormat: "gofmt",
GoBuildArgsForLinux: "-i", GoBuildArgsForWindows: "-i", GoBuildArgsForDarwin: "-i",
FontFamily: "Helvetica", FontSize: "13px", Theme: "default",
Keymap: "wide",
Created: now, Updated: now, Lived: now,
Editor: &editor{FontFamily: "Consolas, 'Courier New', monospace", FontSize: "inherit", LineHeight: "17px",
Theme: "wide", TabSize: "4"}}
}
// WorkspacePath gets workspace path of the user. // WorkspacePath gets workspace path of the user.
// //
// Compared to the use of Wide.Workspace, this function will be processed as follows: // Compared to the use of Wide.Workspace, this function will be processed as follows:
@ -136,7 +122,7 @@ func (u *User) Save() bool {
// 2. Replace ${GOPATH} with enviorment variable GOPATH // 2. Replace ${GOPATH} with enviorment variable GOPATH
// 3. Replace "/" with "\\" (Windows) // 3. Replace "/" with "\\" (Windows)
func (u *User) WorkspacePath() string { func (u *User) WorkspacePath() string {
w := strings.Replace(u.Workspace, "{WD}", Wide.WD, 1) w := u.Workspace
w = strings.Replace(w, "${GOPATH}", os.Getenv("GOPATH"), 1) w = strings.Replace(w, "${GOPATH}", os.Getenv("GOPATH"), 1)
return filepath.FromSlash(w) return filepath.FromSlash(w)
@ -168,17 +154,9 @@ func (u *User) BuildArgs(os string) []string {
func GetOwner(path string) string { func GetOwner(path string) string {
for _, user := range Users { for _, user := range Users {
if strings.HasPrefix(path, user.WorkspacePath()) { if strings.HasPrefix(path, user.WorkspacePath()) {
return user.Name return user.Id
} }
} }
return "" return ""
} }
// Salt salts the specified password with the specified salt.
func Salt(password, salt string) string {
sha1hash := sha1.New()
sha1hash.Write([]byte(password + salt))
return hex.EncodeToString(sha1hash.Sum(nil))
}

View File

@ -1,55 +0,0 @@
{
"Name": "admin",
"Password": "d1bfca21893c908e64fabda01d71294b1ccdcaa7",
"Salt": "dnoyeb",
"Email": "",
"Gravatar": "d41d8cd98f00b204e9800998ecf8427e",
"Workspace": "${GOPATH}",
"Locale": "en_US",
"GoFormat": "gofmt",
"GoBuildArgsForLinux": "-i",
"GoBuildArgsForWindows": "-i",
"GoBuildArgsForDarwin": "-i",
"FontFamily": "Helvetica",
"FontSize": "13px",
"Theme": "default",
"Keymap": "wide",
"Created": 1414080000000000000,
"Updated": 1492963306222604966,
"Lived": 1492963306222604966,
"Editor": {
"FontFamily": "Consolas, 'Courier New', monospace",
"FontSize": "13px",
"LineHeight": "17px",
"Theme": "wide",
"TabSize": "4"
},
"LatestSessionContent": {
"fileTree": [
"/Users/Vanessa/Work/Code/GoGoGo/src",
"/Users/Vanessa/Work/Code/GoGoGo/src/github.com",
"/Users/Vanessa/Work/Code/GoGoGo/src/github.com/b3log",
"/Users/Vanessa/Work/Code/GoGoGo/src/github.com/b3log/wide",
"/Users/Vanessa/Work/Code/GoGoGo/src/github.com/b3log/wide/.idea",
"/Users/Vanessa/Work/Code/GoGoGo/src/github.com/b3log/wide/static",
"/Users/Vanessa/Work/Code/GoGoGo/src/github.com/b3log/wide/static/css",
"/Users/Vanessa/Work/Code/GoGoGo/src/github.com/b3log/wide/static/js"
],
"files": [],
"currentFile": "",
"layout": {
"side": {
"state": "normal",
"size": 200
},
"sideRight": {
"state": "normal",
"size": 200
},
"bottom": {
"state": "normal",
"size": 100
}
}
}
}

View File

@ -1,10 +1,10 @@
// Copyright (c) 2014-2017, b3log.org & hacpai.com // Copyright (c) 2014-2019, b3log.org & hacpai.com
// //
// Licensed under the Apache License, Version 2.0 (the "License"); // Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License. // you may not use this file except in compliance with the License.
// You may obtain a copy of the License at // You may obtain a copy of the License at
// //
// http://www.apache.org/licenses/LICENSE-2.0 // https://www.apache.org/licenses/LICENSE-2.0
// //
// Unless required by applicable law or agreed to in writing, software // Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS, // distributed under the License is distributed on an "AS IS" BASIS,
@ -39,9 +39,11 @@ const (
PathListSeparator = string(os.PathListSeparator) PathListSeparator = string(os.PathListSeparator)
// WideVersion holds the current Wide's version. // WideVersion holds the current Wide's version.
WideVersion = "1.5.2" WideVersion = "1.5.3"
// CodeMirrorVer holds the current editor version. // CodeMirrorVer holds the current editor version.
CodeMirrorVer = "5.1" CodeMirrorVer = "5.1"
// UserAgent represents HTTP client user agent.
UserAgent = "Wide/" + WideVersion + "; +https://github.com/b3log/wide"
HelloWorld = `package main HelloWorld = `package main
@ -55,22 +57,14 @@ func main() {
// Configuration. // Configuration.
type conf struct { type conf struct {
IP string // server ip, ${ip} Server string // server
Port string // server port
Context string // server context
Server string // server host and port ({IP}:{Port})
StaticServer string // static resources server scheme, host and port (http://{IP}:{Port})
LogLevel string // logging level: trace/debug/info/warn/error LogLevel string // logging level: trace/debug/info/warn/error
Channel string // channel (ws://{IP}:{Port}) Data string // data directory
RuntimeMode string // runtime mode (dev/prod)
HTTPSessionMaxAge int // HTTP session max age (in seciond) HTTPSessionMaxAge int // HTTP session max age (in seciond)
StaticResourceVersion string // version of static resources StaticResourceVersion string // version of static resources
MaxProcs int // Go max procs MaxProcs int // Go max procs
RuntimeMode string // runtime mode (dev/prod)
WD string // current working direcitory, ${pwd}
Locale string // default locale Locale string // default locale
Playground string // playground directory
UsersWorkspaces string // users' workspaces directory (admin defaults to ${GOPATH}, others using this)
AllowRegister bool // allow register or not
Autocomplete bool // default autocomplete Autocomplete bool // default autocomplete
} }
@ -83,21 +77,34 @@ var Wide *conf
// configurations of users. // configurations of users.
var Users []*User var Users []*User
// Indicates whether runs via Docker. // Indicates whether Docker is available.
var Docker bool var Docker bool
// Load loads the Wide configurations from wide.json and users' configurations from users/{username}.json. // Docker image to run user's program
func Load(confPath, confIP, confPort, confServer, confLogLevel, confStaticServer, confContext, confChannel, const DockerImageGo = "golang"
confPlayground string, confDocker bool, confUsersWorkspaces string) {
// XXX: ugly args list....
initWide(confPath, confIP, confPort, confServer, confLogLevel, confStaticServer, confContext, confChannel, // Load loads the Wide configurations from wide.json and users' configurations from users/{userId}.json.
confPlayground, confDocker, confUsersWorkspaces) func Load(confPath, confData, confServer, confLogLevel string) {
initWide(confPath, confData, confServer, confLogLevel)
initUsers() initUsers()
cmd := exec.Command("docker", "version")
_, err := cmd.CombinedOutput()
if nil != err {
if !util.OS.IsWindows() {
logger.Errorf("Not found 'docker' installed, running user's code will cause security problem")
os.Exit(-1)
}
} else {
Docker = true
}
} }
func initUsers() { func initUsers() {
f, err := os.Open("conf/users") os.MkdirAll(Wide.Data+PathSeparator+"users", 0755)
f, err := os.Open(Wide.Data + PathSeparator + "users")
if nil != err { if nil != err {
logger.Error(err) logger.Error(err)
@ -123,8 +130,7 @@ func initUsers() {
user := &User{} user := &User{}
bytes, _ := ioutil.ReadFile("conf/users/" + name) bytes, _ := ioutil.ReadFile(filepath.Join(Wide.Data, "users", name))
err := json.Unmarshal(bytes, user) err := json.Unmarshal(bytes, user)
if err != nil { if err != nil {
logger.Errorf("Parses [%s] error: %v, skip loading this user", name, err) logger.Errorf("Parses [%s] error: %v, skip loading this user", name, err)
@ -155,8 +161,7 @@ func initUsers() {
initCustomizedConfs() initCustomizedConfs()
} }
func initWide(confPath, confIP, confPort, confServer, confLogLevel, confStaticServer, confContext, confChannel, func initWide(confPath, confData, confServer, confLogLevel string) {
confPlayground string, confDocker bool, confUsersWorkspaces string) {
bytes, err := ioutil.ReadFile(confPath) bytes, err := ioutil.ReadFile(confPath)
if nil != err { if nil != err {
logger.Error(err) logger.Error(err)
@ -182,10 +187,6 @@ func initWide(confPath, confIP, confPort, confServer, confLogLevel, confStaticSe
logger.Debug("Conf: \n" + string(bytes)) logger.Debug("Conf: \n" + string(bytes))
// Working Directory
Wide.WD = util.OS.Pwd()
logger.Debugf("${pwd} [%s]", Wide.WD)
// User Home // User Home
home, err := util.OS.Home() home, err := util.OS.Home()
if nil != err { if nil != err {
@ -193,82 +194,30 @@ func initWide(confPath, confIP, confPort, confServer, confLogLevel, confStaticSe
os.Exit(-1) os.Exit(-1)
} }
logger.Debugf("${user.home} [%s]", home) logger.Debugf("${user.home} [%s]", home)
// Playground Directory // Data directory
Wide.Playground = strings.Replace(Wide.Playground, "${home}", home, 1) if "" != confData {
if "" != confPlayground { Wide.Data = confData
Wide.Playground = confPlayground
} }
Wide.Data = strings.Replace(Wide.Data, "${home}", home, -1)
// Users' workspaces Directory Wide.Data = filepath.Clean(Wide.Data)
Wide.UsersWorkspaces = strings.Replace(Wide.UsersWorkspaces, "${WD}", Wide.WD, 1) if !util.File.IsExist(Wide.Data) {
Wide.UsersWorkspaces = strings.Replace(Wide.UsersWorkspaces, "${home}", home, 1) if err := os.MkdirAll(Wide.Data, 0775); nil != err {
if "" != confUsersWorkspaces { logger.Errorf("Create data directory [%s] error", err)
Wide.UsersWorkspaces = confUsersWorkspaces
}
Wide.UsersWorkspaces = filepath.Clean(Wide.UsersWorkspaces)
if !util.File.IsExist(Wide.Playground) {
if err := os.Mkdir(Wide.Playground, 0775); nil != err {
logger.Errorf("Create Playground [%s] error", err)
os.Exit(-1) os.Exit(-1)
} }
} }
// IP
if "" != confIP {
Wide.IP = confIP
} else {
ip, err := util.Net.LocalIP()
if nil != err {
logger.Error(err)
os.Exit(-1)
}
logger.Debugf("${ip} [%s]", ip)
Wide.IP = strings.Replace(Wide.IP, "${ip}", ip, 1)
}
if "" != confPort {
Wide.Port = confPort
}
// Docker flag
Docker = confDocker
// Server // Server
Wide.Server = strings.Replace(Wide.Server, "{IP}", Wide.IP, 1)
Wide.Server = strings.Replace(Wide.Server, "{Port}", Wide.Port, 1)
if "" != confServer { if "" != confServer {
Wide.Server = confServer Wide.Server = confServer
} }
// Static Server
Wide.StaticServer = strings.Replace(Wide.StaticServer, "{IP}", Wide.IP, 1)
Wide.StaticServer = strings.Replace(Wide.StaticServer, "{Port}", Wide.Port, 1)
if "" != confStaticServer {
Wide.StaticServer = confStaticServer
}
// Context
if "" != confContext {
Wide.Context = confContext
}
time := strconv.FormatInt(time.Now().UnixNano(), 10) time := strconv.FormatInt(time.Now().UnixNano(), 10)
logger.Debugf("${time} [%s]", time) logger.Debugf("${time} [%s]", time)
Wide.StaticResourceVersion = strings.Replace(Wide.StaticResourceVersion, "${time}", time, 1) Wide.StaticResourceVersion = strings.Replace(Wide.StaticResourceVersion, "${time}", time, 1)
// Channel
Wide.Channel = strings.Replace(Wide.Channel, "{IP}", Wide.IP, 1)
Wide.Channel = strings.Replace(Wide.Channel, "{Port}", Wide.Port, 1)
if "" != confChannel {
Wide.Channel = confChannel
}
} }
// FixedTimeCheckEnv checks Wide runtime enviorment periodically (7 minutes). // FixedTimeCheckEnv checks Wide runtime enviorment periodically (7 minutes).
@ -279,7 +228,7 @@ func FixedTimeCheckEnv() {
checkEnv() // check immediately checkEnv() // check immediately
go func() { go func() {
for _ = range time.Tick(time.Minute * 7) { for _ = range time.Tick(time.Minute*7) {
checkEnv() checkEnv()
} }
}() }()
@ -322,10 +271,10 @@ func checkEnv() {
} }
} }
// GetUserWorkspace gets workspace path with the specified username, returns "" if not found. // GetUserWorkspace gets workspace path with the specified user id, returns "" if not found.
func GetUserWorkspace(username string) string { func GetUserWorkspace(userId string) string {
for _, user := range Users { for _, user := range Users {
if user.Name == username { if user.Id == userId {
return user.WorkspacePath() return user.WorkspacePath()
} }
} }
@ -334,9 +283,9 @@ func GetUserWorkspace(username string) string {
} }
// GetGoFmt gets the path of Go format tool, returns "gofmt" if not found "goimports". // GetGoFmt gets the path of Go format tool, returns "gofmt" if not found "goimports".
func GetGoFmt(username string) string { func GetGoFmt(userId string) string {
for _, user := range Users { for _, user := range Users {
if user.Name == username { if user.Id == userId {
switch user.GoFormat { switch user.GoFormat {
case "gofmt": case "gofmt":
return "gofmt" return "gofmt"
@ -352,15 +301,14 @@ func GetGoFmt(username string) string {
return "gofmt" return "gofmt"
} }
// GetUser gets configuration of the user specified by the given username, returns nil if not found. // GetUser gets configuration of the user specified by the given user id, returns nil if not found.
func GetUser(username string) *User { func GetUser(id string) *User {
if "playground" == username { // reserved user for Playground if "playground" == id { // reserved user for Playground
// mock it return NewUser("playground", "playground", "", "")
return NewUser("playground", "", "", "")
} }
for _, user := range Users { for _, user := range Users {
if user.Name == username { if user.Id == id {
return user return user
} }
} }
@ -371,17 +319,17 @@ func GetUser(username string) *User {
// initCustomizedConfs initializes the user customized configurations. // initCustomizedConfs initializes the user customized configurations.
func initCustomizedConfs() { func initCustomizedConfs() {
for _, user := range Users { for _, user := range Users {
UpdateCustomizedConf(user.Name) UpdateCustomizedConf(user.Id)
} }
} }
// UpdateCustomizedConf creates (if not exists) or updates user customized configuration files. // UpdateCustomizedConf creates (if not exists) or updates user customized configuration files.
// //
// 1. /static/user/{username}/style.css // 1. /static/users/{userId}/style.css
func UpdateCustomizedConf(username string) { func UpdateCustomizedConf(userId string) {
var u *User var u *User
for _, user := range Users { // maybe it is a beauty of the trade-off of the another world between design and implementation for _, user := range Users { // maybe it is a beauty of the trade-off of the another world between design and implementation
if user.Name == username { if user.Id == userId {
u = user u = user
} }
} }
@ -399,8 +347,7 @@ func UpdateCustomizedConf(username string) {
os.Exit(-1) os.Exit(-1)
} }
wd := util.OS.Pwd() dir := filepath.Clean(Wide.Data + "/static/users/" + userId)
dir := filepath.Clean(wd + "/static/user/" + u.Name)
if err := os.MkdirAll(dir, 0755); nil != err { if err := os.MkdirAll(dir, 0755); nil != err {
logger.Error(err) logger.Error(err)

View File

@ -1,19 +1,11 @@
{ {
"IP": "${ip}", "Server": "http://127.0.0.1:7070",
"Port": "7070",
"Context": "",
"Server": "{IP}:{Port}",
"StaticServer": "",
"LogLevel": "debug", "LogLevel": "debug",
"Channel": "ws://{IP}:{Port}", "Data": "${home}/wide",
"RuntimeMode": "dev",
"HTTPSessionMaxAge": 86400, "HTTPSessionMaxAge": 86400,
"StaticResourceVersion": "${time}", "StaticResourceVersion": "${time}",
"MaxProcs": 4, "MaxProcs": 4,
"RuntimeMode": "dev",
"WD": "${pwd}",
"Locale": "en_US", "Locale": "en_US",
"Playground": "${home}/playground",
"UsersWorkspaces": "${WD}/workspaces",
"AllowRegister": true,
"Autocomplete": true "Autocomplete": true
} }

View File

@ -8,7 +8,7 @@ set -e
echo "mode: count" > profile.cov echo "mode: count" > profile.cov
# Standard go tooling behavior is to ignore dirs with leading underscors # Standard go tooling behavior is to ignore dirs with leading underscors
for dir in $(find . -maxdepth 10 -not -path './.git*' -not -path '*/_*' -type d); for dir in $(find . -maxdepth 10 -not -path './.git*' -not -path '*/_*' -not -path './vendor*' -type d);
do do
if ls $dir/*.go &> /dev/null; then if ls $dir/*.go &> /dev/null; then
go test -covermode=count -coverprofile=$dir/profile.tmp $dir go test -covermode=count -coverprofile=$dir/profile.tmp $dir

BIN
deps/github.com.zip vendored

Binary file not shown.

BIN
deps/golang.org.zip vendored

Binary file not shown.

View File

@ -1,2 +0,0 @@
* [User Guide](https://www.gitbook.com/book/88250/wide-user-guide)
* [Developer Guide](https://www.gitbook.com/book/88250/wide-dev-guide)

View File

@ -1,10 +1,10 @@
// Copyright (c) 2014-2017, b3log.org & hacpai.com // Copyright (c) 2014-2019, b3log.org & hacpai.com
// //
// Licensed under the Apache License, Version 2.0 (the "License"); // Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License. // you may not use this file except in compliance with the License.
// You may obtain a copy of the License at // You may obtain a copy of the License at
// //
// http://www.apache.org/licenses/LICENSE-2.0 // https://www.apache.org/licenses/LICENSE-2.0
// //
// Unless required by applicable law or agreed to in writing, software // Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS, // distributed under the License is distributed on an "AS IS" BASIS,
@ -41,7 +41,7 @@ var logger = log.NewLogger(os.Stdout)
// WSHandler handles request of creating editor channel. // WSHandler handles request of creating editor channel.
// XXX: NOT used at present // XXX: NOT used at present
func WSHandler(w http.ResponseWriter, r *http.Request) { func WSHandler(w http.ResponseWriter, r *http.Request) {
httpSession, _ := session.HTTPSession.Get(r, "wide-session") httpSession, _ := session.HTTPSession.Get(r, session.CookieName)
if httpSession.IsNew { if httpSession.IsNew {
http.Error(w, "Forbidden", http.StatusForbidden) http.Error(w, "Forbidden", http.StatusForbidden)
@ -111,13 +111,13 @@ func AutocompleteHandler(w http.ResponseWriter, r *http.Request) {
return return
} }
session, _ := session.HTTPSession.Get(r, "wide-session") session, _ := session.HTTPSession.Get(r, session.CookieName)
if session.IsNew { if session.IsNew {
http.Error(w, "Forbidden", http.StatusForbidden) http.Error(w, "Forbidden", http.StatusForbidden)
return return
} }
username := session.Values["username"].(string) uid := session.Values["uid"].(string)
path := args["path"].(string) path := args["path"].(string)
@ -147,7 +147,7 @@ func AutocompleteHandler(w http.ResponseWriter, r *http.Request) {
logger.Tracef("offset: %d", offset) logger.Tracef("offset: %d", offset)
userWorkspace := conf.GetUserWorkspace(username) userWorkspace := conf.GetUserWorkspace(uid)
workspaces := filepath.SplitList(userWorkspace) workspaces := filepath.SplitList(userWorkspace)
libPath := "" libPath := ""
for _, workspace := range workspaces { for _, workspace := range workspaces {
@ -182,8 +182,8 @@ func GetExprInfoHandler(w http.ResponseWriter, r *http.Request) {
result := util.NewResult() result := util.NewResult()
defer util.RetResult(w, r, result) defer util.RetResult(w, r, result)
session, _ := session.HTTPSession.Get(r, "wide-session") session, _ := session.HTTPSession.Get(r, session.CookieName)
username := session.Values["username"].(string) uid := session.Values["uid"].(string)
var args map[string]interface{} var args map[string]interface{}
if err := json.NewDecoder(r.Body).Decode(&args); err != nil { if err := json.NewDecoder(r.Body).Decode(&args); err != nil {
@ -228,7 +228,7 @@ func GetExprInfoHandler(w http.ResponseWriter, r *http.Request) {
cmd := exec.Command(ideStub, argv...) cmd := exec.Command(ideStub, argv...)
cmd.Dir = curDir cmd.Dir = curDir
setCmdEnv(cmd, username) setCmdEnv(cmd, uid)
output, err := cmd.CombinedOutput() output, err := cmd.CombinedOutput()
if nil != err { if nil != err {
@ -253,13 +253,13 @@ func FindDeclarationHandler(w http.ResponseWriter, r *http.Request) {
result := util.NewResult() result := util.NewResult()
defer util.RetResult(w, r, result) defer util.RetResult(w, r, result)
session, _ := session.HTTPSession.Get(r, "wide-session") session, _ := session.HTTPSession.Get(r, session.CookieName)
if session.IsNew { if session.IsNew {
http.Error(w, "Forbidden", http.StatusForbidden) http.Error(w, "Forbidden", http.StatusForbidden)
return return
} }
username := session.Values["username"].(string) uid := session.Values["uid"].(string)
var args map[string]interface{} var args map[string]interface{}
if err := json.NewDecoder(r.Body).Decode(&args); err != nil { if err := json.NewDecoder(r.Body).Decode(&args); err != nil {
@ -304,7 +304,7 @@ func FindDeclarationHandler(w http.ResponseWriter, r *http.Request) {
cmd := exec.Command(ideStub, argv...) cmd := exec.Command(ideStub, argv...)
cmd.Dir = curDir cmd.Dir = curDir
setCmdEnv(cmd, username) setCmdEnv(cmd, uid)
output, err := cmd.CombinedOutput() output, err := cmd.CombinedOutput()
if nil != err { if nil != err {
@ -341,13 +341,13 @@ func FindUsagesHandler(w http.ResponseWriter, r *http.Request) {
result := util.NewResult() result := util.NewResult()
defer util.RetResult(w, r, result) defer util.RetResult(w, r, result)
session, _ := session.HTTPSession.Get(r, "wide-session") session, _ := session.HTTPSession.Get(r, session.CookieName)
if session.IsNew { if session.IsNew {
http.Error(w, "Forbidden", http.StatusForbidden) http.Error(w, "Forbidden", http.StatusForbidden)
return return
} }
username := session.Values["username"].(string) uid := session.Values["uid"].(string)
var args map[string]interface{} var args map[string]interface{}
@ -392,7 +392,7 @@ func FindUsagesHandler(w http.ResponseWriter, r *http.Request) {
cmd := exec.Command(ideStub, argv...) cmd := exec.Command(ideStub, argv...)
cmd.Dir = curDir cmd.Dir = curDir
setCmdEnv(cmd, username) setCmdEnv(cmd, uid)
output, err := cmd.CombinedOutput() output, err := cmd.CombinedOutput()
if nil != err { if nil != err {
@ -453,8 +453,8 @@ func getCursorOffset(code string, line, ch int) (offset int) {
return offset return offset
} }
func setCmdEnv(cmd *exec.Cmd, username string) { func setCmdEnv(cmd *exec.Cmd, userId string) {
userWorkspace := conf.GetUserWorkspace(username) userWorkspace := conf.GetUserWorkspace(userId)
cmd.Env = append(cmd.Env, cmd.Env = append(cmd.Env,
"GOPATH="+userWorkspace, "GOPATH="+userWorkspace,

View File

@ -1,10 +1,10 @@
// Copyright (c) 2014-2017, b3log.org & hacpai.com // Copyright (c) 2014-2019, b3log.org & hacpai.com
// //
// Licensed under the Apache License, Version 2.0 (the "License"); // Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License. // you may not use this file except in compliance with the License.
// You may obtain a copy of the License at // You may obtain a copy of the License at
// //
// http://www.apache.org/licenses/LICENSE-2.0 // https://www.apache.org/licenses/LICENSE-2.0
// //
// Unless required by applicable law or agreed to in writing, software // Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS, // distributed under the License is distributed on an "AS IS" BASIS,
@ -34,13 +34,13 @@ func GoFmtHandler(w http.ResponseWriter, r *http.Request) {
result := util.NewResult() result := util.NewResult()
defer util.RetResult(w, r, result) defer util.RetResult(w, r, result)
session, _ := session.HTTPSession.Get(r, "wide-session") session, _ := session.HTTPSession.Get(r, session.CookieName)
if session.IsNew { if session.IsNew {
http.Error(w, "Forbidden", http.StatusForbidden) http.Error(w, "Forbidden", http.StatusForbidden)
return return
} }
username := session.Values["username"].(string) uid := session.Values["uid"].(string)
var args map[string]interface{} var args map[string]interface{}
@ -85,7 +85,7 @@ func GoFmtHandler(w http.ResponseWriter, r *http.Request) {
result.Data = data result.Data = data
fmt := conf.GetGoFmt(username) fmt := conf.GetGoFmt(uid)
argv := []string{filePath} argv := []string{filePath}
cmd := exec.Command(fmt, argv...) cmd := exec.Command(fmt, argv...)

View File

@ -1,10 +1,10 @@
// Copyright (c) 2014-2017, b3log.org & hacpai.com // Copyright (c) 2014-2019, b3log.org & hacpai.com
// //
// Licensed under the Apache License, Version 2.0 (the "License"); // Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License. // you may not use this file except in compliance with the License.
// You may obtain a copy of the License at // You may obtain a copy of the License at
// //
// http://www.apache.org/licenses/LICENSE-2.0 // https://www.apache.org/licenses/LICENSE-2.0
// //
// Unless required by applicable law or agreed to in writing, software // Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS, // distributed under the License is distributed on an "AS IS" BASIS,

View File

@ -1,10 +1,10 @@
// Copyright (c) 2014-2017, b3log.org & hacpai.com // Copyright (c) 2014-2019, b3log.org & hacpai.com
// //
// Licensed under the Apache License, Version 2.0 (the "License"); // Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License. // you may not use this file except in compliance with the License.
// You may obtain a copy of the License at // You may obtain a copy of the License at
// //
// http://www.apache.org/licenses/LICENSE-2.0 // https://www.apache.org/licenses/LICENSE-2.0
// //
// Unless required by applicable law or agreed to in writing, software // Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS, // distributed under the License is distributed on an "AS IS" BASIS,

View File

@ -1,10 +1,10 @@
// Copyright (c) 2014-2017, b3log.org & hacpai.com // Copyright (c) 2014-2019, b3log.org & hacpai.com
// //
// Licensed under the Apache License, Version 2.0 (the "License"); // Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License. // you may not use this file except in compliance with the License.
// You may obtain a copy of the License at // You may obtain a copy of the License at
// //
// http://www.apache.org/licenses/LICENSE-2.0 // https://www.apache.org/licenses/LICENSE-2.0
// //
// Unless required by applicable law or agreed to in writing, software // Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS, // distributed under the License is distributed on an "AS IS" BASIS,

View File

@ -1,10 +1,10 @@
// Copyright (c) 2014-2017, b3log.org & hacpai.com // Copyright (c) 2014-2019, b3log.org & hacpai.com
// //
// Licensed under the Apache License, Version 2.0 (the "License"); // Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License. // you may not use this file except in compliance with the License.
// You may obtain a copy of the License at // You may obtain a copy of the License at
// //
// http://www.apache.org/licenses/LICENSE-2.0 // https://www.apache.org/licenses/LICENSE-2.0
// //
// Unless required by applicable law or agreed to in writing, software // Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS, // distributed under the License is distributed on an "AS IS" BASIS,
@ -74,18 +74,18 @@ func initAPINode() {
// The Go API source code package also as a child node, // The Go API source code package also as a child node,
// so that users can easily view the Go API source code in file tree. // so that users can easily view the Go API source code in file tree.
func GetFilesHandler(w http.ResponseWriter, r *http.Request) { func GetFilesHandler(w http.ResponseWriter, r *http.Request) {
httpSession, _ := session.HTTPSession.Get(r, "wide-session") httpSession, _ := session.HTTPSession.Get(r, session.CookieName)
if httpSession.IsNew { if httpSession.IsNew {
http.Error(w, "Forbidden", http.StatusForbidden) http.Error(w, "Forbidden", http.StatusForbidden)
return return
} }
username := httpSession.Values["username"].(string) uid := httpSession.Values["uid"].(string)
result := util.NewResult() result := util.NewResult()
defer util.RetGzResult(w, r, result) defer util.RetGzResult(w, r, result)
userWorkspace := conf.GetUserWorkspace(username) userWorkspace := conf.GetUserWorkspace(uid)
workspaces := filepath.SplitList(userWorkspace) workspaces := filepath.SplitList(userWorkspace)
root := Node{Name: "root", Path: "", IconSkin: "ico-ztree-dir ", Type: "d", IsParent: true, Children: []*Node{}} root := Node{Name: "root", Path: "", IconSkin: "ico-ztree-dir ", Type: "d", IsParent: true, Children: []*Node{}}
@ -123,18 +123,18 @@ func GetFilesHandler(w http.ResponseWriter, r *http.Request) {
// RefreshDirectoryHandler handles request of refresh a directory of file tree. // RefreshDirectoryHandler handles request of refresh a directory of file tree.
func RefreshDirectoryHandler(w http.ResponseWriter, r *http.Request) { func RefreshDirectoryHandler(w http.ResponseWriter, r *http.Request) {
httpSession, _ := session.HTTPSession.Get(r, "wide-session") httpSession, _ := session.HTTPSession.Get(r, session.CookieName)
if httpSession.IsNew { if httpSession.IsNew {
http.Error(w, "Forbidden", http.StatusForbidden) http.Error(w, "Forbidden", http.StatusForbidden)
return return
} }
username := httpSession.Values["username"].(string) uid := httpSession.Values["uid"].(string)
r.ParseForm() r.ParseForm()
path := r.FormValue("path") path := r.FormValue("path")
if !util.Go.IsAPI(path) && !session.CanAccess(username, path) { if !util.Go.IsAPI(path) && !session.CanAccess(uid, path) {
http.Error(w, "Forbidden", http.StatusForbidden) http.Error(w, "Forbidden", http.StatusForbidden)
return return
@ -156,13 +156,13 @@ func RefreshDirectoryHandler(w http.ResponseWriter, r *http.Request) {
// GetFileHandler handles request of opening file by editor. // GetFileHandler handles request of opening file by editor.
func GetFileHandler(w http.ResponseWriter, r *http.Request) { func GetFileHandler(w http.ResponseWriter, r *http.Request) {
httpSession, _ := session.HTTPSession.Get(r, "wide-session") httpSession, _ := session.HTTPSession.Get(r, session.CookieName)
if httpSession.IsNew { if httpSession.IsNew {
http.Error(w, "Forbidden", http.StatusForbidden) http.Error(w, "Forbidden", http.StatusForbidden)
return return
} }
username := httpSession.Values["username"].(string) uid := httpSession.Values["uid"].(string)
result := util.NewResult() result := util.NewResult()
defer util.RetResult(w, r, result) defer util.RetResult(w, r, result)
@ -178,7 +178,7 @@ func GetFileHandler(w http.ResponseWriter, r *http.Request) {
path := args["path"].(string) path := args["path"].(string)
if !util.Go.IsAPI(path) && !session.CanAccess(username, path) { if !util.Go.IsAPI(path) && !session.CanAccess(uid, path) {
http.Error(w, "Forbidden", http.StatusForbidden) http.Error(w, "Forbidden", http.StatusForbidden)
return return
@ -204,15 +204,15 @@ func GetFileHandler(w http.ResponseWriter, r *http.Request) {
data["mode"] = "img" data["mode"] = "img"
username := conf.GetOwner(path) userId := conf.GetOwner(path)
if "" == username { if "" == userId {
logger.Warnf("The path [%s] has no owner") logger.Warnf("The path [%s] has no owner", path)
data["path"] = "" data["path"] = ""
return return
} }
user := conf.GetUser(username) user := conf.GetUser(uid)
data["path"] = "/workspace/" + user.Name + "/" + strings.Replace(path, user.WorkspacePath(), "", 1) data["path"] = "/workspace/" + user.Name + "/" + strings.Replace(path, user.WorkspacePath(), "", 1)
@ -232,13 +232,13 @@ func GetFileHandler(w http.ResponseWriter, r *http.Request) {
// SaveFileHandler handles request of saving file. // SaveFileHandler handles request of saving file.
func SaveFileHandler(w http.ResponseWriter, r *http.Request) { func SaveFileHandler(w http.ResponseWriter, r *http.Request) {
httpSession, _ := session.HTTPSession.Get(r, "wide-session") httpSession, _ := session.HTTPSession.Get(r, session.CookieName)
if httpSession.IsNew { if httpSession.IsNew {
http.Error(w, "Forbidden", http.StatusForbidden) http.Error(w, "Forbidden", http.StatusForbidden)
return return
} }
username := httpSession.Values["username"].(string) uid := httpSession.Values["uid"].(string)
result := util.NewResult() result := util.NewResult()
defer util.RetResult(w, r, result) defer util.RetResult(w, r, result)
@ -255,7 +255,7 @@ func SaveFileHandler(w http.ResponseWriter, r *http.Request) {
filePath := args["file"].(string) filePath := args["file"].(string)
sid := args["sid"].(string) sid := args["sid"].(string)
if util.Go.IsAPI(filePath) || !session.CanAccess(username, filePath) { if util.Go.IsAPI(filePath) || !session.CanAccess(uid, filePath) {
http.Error(w, "Forbidden", http.StatusForbidden) http.Error(w, "Forbidden", http.StatusForbidden)
return return
@ -288,13 +288,13 @@ func SaveFileHandler(w http.ResponseWriter, r *http.Request) {
// NewFileHandler handles request of creating file or directory. // NewFileHandler handles request of creating file or directory.
func NewFileHandler(w http.ResponseWriter, r *http.Request) { func NewFileHandler(w http.ResponseWriter, r *http.Request) {
httpSession, _ := session.HTTPSession.Get(r, "wide-session") httpSession, _ := session.HTTPSession.Get(r, session.CookieName)
if httpSession.IsNew { if httpSession.IsNew {
http.Error(w, "Forbidden", http.StatusForbidden) http.Error(w, "Forbidden", http.StatusForbidden)
return return
} }
username := httpSession.Values["username"].(string) uid := httpSession.Values["uid"].(string)
result := util.NewResult() result := util.NewResult()
defer util.RetResult(w, r, result) defer util.RetResult(w, r, result)
@ -310,7 +310,7 @@ func NewFileHandler(w http.ResponseWriter, r *http.Request) {
path := args["path"].(string) path := args["path"].(string)
if util.Go.IsAPI(path) || !session.CanAccess(username, path) { if util.Go.IsAPI(path) || !session.CanAccess(uid, path) {
http.Error(w, "Forbidden", http.StatusForbidden) http.Error(w, "Forbidden", http.StatusForbidden)
return return
@ -331,22 +331,22 @@ func NewFileHandler(w http.ResponseWriter, r *http.Request) {
} }
if "f" == fileType { if "f" == fileType {
logger.Debugf("Created a file [%s] by user [%s]", path, wSession.Username) logger.Debugf("Created a file [%s] by user [%s]", path, wSession.UserId)
} else { } else {
logger.Debugf("Created a dir [%s] by user [%s]", path, wSession.Username) logger.Debugf("Created a dir [%s] by user [%s]", path, wSession.UserId)
} }
} }
// RemoveFileHandler handles request of removing file or directory. // RemoveFileHandler handles request of removing file or directory.
func RemoveFileHandler(w http.ResponseWriter, r *http.Request) { func RemoveFileHandler(w http.ResponseWriter, r *http.Request) {
httpSession, _ := session.HTTPSession.Get(r, "wide-session") httpSession, _ := session.HTTPSession.Get(r, session.CookieName)
if httpSession.IsNew { if httpSession.IsNew {
http.Error(w, "Forbidden", http.StatusForbidden) http.Error(w, "Forbidden", http.StatusForbidden)
return return
} }
username := httpSession.Values["username"].(string) uid := httpSession.Values["uid"].(string)
result := util.NewResult() result := util.NewResult()
defer util.RetResult(w, r, result) defer util.RetResult(w, r, result)
@ -362,7 +362,7 @@ func RemoveFileHandler(w http.ResponseWriter, r *http.Request) {
path := args["path"].(string) path := args["path"].(string)
if util.Go.IsAPI(path) || !session.CanAccess(username, path) { if util.Go.IsAPI(path) || !session.CanAccess(uid, path) {
http.Error(w, "Forbidden", http.StatusForbidden) http.Error(w, "Forbidden", http.StatusForbidden)
return return
@ -381,18 +381,18 @@ func RemoveFileHandler(w http.ResponseWriter, r *http.Request) {
return return
} }
logger.Debugf("Removed a file [%s] by user [%s]", path, wSession.Username) logger.Debugf("Removed a file [%s] by user [%s]", path, wSession.UserId)
} }
// RenameFileHandler handles request of renaming file or directory. // RenameFileHandler handles request of renaming file or directory.
func RenameFileHandler(w http.ResponseWriter, r *http.Request) { func RenameFileHandler(w http.ResponseWriter, r *http.Request) {
httpSession, _ := session.HTTPSession.Get(r, "wide-session") httpSession, _ := session.HTTPSession.Get(r, session.CookieName)
if httpSession.IsNew { if httpSession.IsNew {
http.Error(w, "Forbidden", http.StatusForbidden) http.Error(w, "Forbidden", http.StatusForbidden)
return return
} }
username := httpSession.Values["username"].(string) uid := httpSession.Values["uid"].(string)
result := util.NewResult() result := util.NewResult()
defer util.RetResult(w, r, result) defer util.RetResult(w, r, result)
@ -408,14 +408,14 @@ func RenameFileHandler(w http.ResponseWriter, r *http.Request) {
oldPath := args["oldPath"].(string) oldPath := args["oldPath"].(string)
if util.Go.IsAPI(oldPath) || if util.Go.IsAPI(oldPath) ||
!session.CanAccess(username, oldPath) { !session.CanAccess(uid, oldPath) {
http.Error(w, "Forbidden", http.StatusForbidden) http.Error(w, "Forbidden", http.StatusForbidden)
return return
} }
newPath := args["newPath"].(string) newPath := args["newPath"].(string)
if util.Go.IsAPI(newPath) || !session.CanAccess(username, newPath) { if util.Go.IsAPI(newPath) || !session.CanAccess(uid, newPath) {
http.Error(w, "Forbidden", http.StatusForbidden) http.Error(w, "Forbidden", http.StatusForbidden)
return return
@ -434,7 +434,7 @@ func RenameFileHandler(w http.ResponseWriter, r *http.Request) {
return return
} }
logger.Debugf("Renamed a file [%s] to [%s] by user [%s]", oldPath, newPath, wSession.Username) logger.Debugf("Renamed a file [%s] to [%s] by user [%s]", oldPath, newPath, wSession.UserId)
} }
// Use to find results sorting. // Use to find results sorting.
@ -451,13 +451,13 @@ func (f foundPaths) Less(i, j int) bool { return f[i].score > f[j].score }
// FindHandler handles request of find files under the specified directory with the specified filename pattern. // FindHandler handles request of find files under the specified directory with the specified filename pattern.
func FindHandler(w http.ResponseWriter, r *http.Request) { func FindHandler(w http.ResponseWriter, r *http.Request) {
httpSession, _ := session.HTTPSession.Get(r, "wide-session") httpSession, _ := session.HTTPSession.Get(r, session.CookieName)
if httpSession.IsNew { if httpSession.IsNew {
http.Error(w, "Forbidden", http.StatusForbidden) http.Error(w, "Forbidden", http.StatusForbidden)
return return
} }
username := httpSession.Values["username"].(string) uid := httpSession.Values["uid"].(string)
result := util.NewResult() result := util.NewResult()
defer util.RetResult(w, r, result) defer util.RetResult(w, r, result)
@ -471,7 +471,7 @@ func FindHandler(w http.ResponseWriter, r *http.Request) {
} }
path := args["path"].(string) // path of selected file in file tree path := args["path"].(string) // path of selected file in file tree
if !util.Go.IsAPI(path) && !session.CanAccess(username, path) { if !util.Go.IsAPI(path) && !session.CanAccess(uid, path) {
http.Error(w, "Forbidden", http.StatusForbidden) http.Error(w, "Forbidden", http.StatusForbidden)
return return
@ -479,7 +479,7 @@ func FindHandler(w http.ResponseWriter, r *http.Request) {
name := args["name"].(string) name := args["name"].(string)
userWorkspace := conf.GetUserWorkspace(username) userWorkspace := conf.GetUserWorkspace(uid)
workspaces := filepath.SplitList(userWorkspace) workspaces := filepath.SplitList(userWorkspace)
if "" != path && !util.File.IsDir(path) { if "" != path && !util.File.IsDir(path) {
@ -505,7 +505,7 @@ func FindHandler(w http.ResponseWriter, r *http.Request) {
// SearchTextHandler handles request of searching files under the specified directory with the specified keyword. // SearchTextHandler handles request of searching files under the specified directory with the specified keyword.
func SearchTextHandler(w http.ResponseWriter, r *http.Request) { func SearchTextHandler(w http.ResponseWriter, r *http.Request) {
httpSession, _ := session.HTTPSession.Get(r, "wide-session") httpSession, _ := session.HTTPSession.Get(r, session.CookieName)
if httpSession.IsNew { if httpSession.IsNew {
http.Error(w, "Forbidden", http.StatusForbidden) http.Error(w, "Forbidden", http.StatusForbidden)
@ -536,7 +536,7 @@ func SearchTextHandler(w http.ResponseWriter, r *http.Request) {
dir := args["dir"].(string) dir := args["dir"].(string)
if "" == dir { if "" == dir {
userWorkspace := conf.GetUserWorkspace(wSession.Username) userWorkspace := conf.GetUserWorkspace(wSession.UserId)
workspaces := filepath.SplitList(userWorkspace) workspaces := filepath.SplitList(userWorkspace)
dir = workspaces[0] dir = workspaces[0]
} }

View File

@ -1,77 +0,0 @@
// Copyright (c) 2014-2017, b3log.org & hacpai.com
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package file
import (
"io"
"mime/multipart"
"net/http"
"os"
"path/filepath"
"github.com/b3log/wide/util"
)
type fileInfo struct {
Name string `json:"name"`
Type string `json:"type"`
Error string `json:"error,omitempty"`
}
func handleUpload(p *multipart.Part, dir string) (fi *fileInfo) {
fi = &fileInfo{
Name: p.FileName(),
Type: p.Header.Get("Content-Type"),
}
path := filepath.Clean(dir + "/" + fi.Name)
f, _ := os.Create(path)
io.Copy(f, p)
f.Close()
return
}
func handleUploads(r *http.Request, dir string) (fileInfos []*fileInfo) {
fileInfos = make([]*fileInfo, 0)
mr, err := r.MultipartReader()
part, err := mr.NextPart()
for err == nil {
if name := part.FormName(); name != "" {
if part.FileName() != "" {
fileInfos = append(fileInfos, handleUpload(part, dir))
}
}
part, err = mr.NextPart()
}
return
}
// UploadHandler handles request of file upload.
func UploadHandler(w http.ResponseWriter, r *http.Request) {
result := util.NewResult()
defer util.RetResult(w, r, result)
q := r.URL.Query()
dir := q["path"][0]
result.Data = handleUploads(r, dir)
}

View File

@ -1,10 +1,10 @@
// Copyright (c) 2014-2017, b3log.org & hacpai.com // Copyright (c) 2014-2019, b3log.org & hacpai.com
// //
// Licensed under the Apache License, Version 2.0 (the "License"); // Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License. // you may not use this file except in compliance with the License.
// You may obtain a copy of the License at // You may obtain a copy of the License at
// //
// http://www.apache.org/licenses/LICENSE-2.0 // https://www.apache.org/licenses/LICENSE-2.0
// //
// Unless required by applicable law or agreed to in writing, software // Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS, // distributed under the License is distributed on an "AS IS" BASIS,

21
go.mod Normal file
View File

@ -0,0 +1,21 @@
module github.com/b3log/wide
go 1.12
require (
github.com/elazarl/goproxy v0.0.0-20190421051319-9d40249d3c2f // indirect
github.com/elazarl/goproxy/ext v0.0.0-20190421051319-9d40249d3c2f // indirect
github.com/fsnotify/fsnotify v1.4.7
github.com/gorilla/context v0.0.0-20141217160251-215affda49ad // indirect
github.com/gorilla/securecookie v0.0.0-20150327155805-8e98dd730fc4 // indirect
github.com/gorilla/sessions v0.0.0-20150417174705-f61c3ec2cf65
github.com/gorilla/websocket v0.0.0-20150530030352-a3ec486e6a7a
github.com/hashicorp/go-version v1.2.0
github.com/moul/http2curl v1.0.0 // indirect
github.com/parnurzeal/gorequest v0.2.15
github.com/pkg/errors v0.8.1 // indirect
github.com/smartystreets/goconvey v0.0.0-20190330032615-68dc04aab96a // indirect
golang.org/x/net v0.0.0-20190514140710-3ec191127204 // indirect
golang.org/x/sys v0.0.0-20190515190549-87c872767d25 // indirect
golang.org/x/text v0.3.2
)

43
go.sum Normal file
View File

@ -0,0 +1,43 @@
github.com/elazarl/goproxy v0.0.0-20190421051319-9d40249d3c2f h1:8GDPb0tCY8LQ+OJ3dbHb5sA6YZWXFORQYZx5sdsTlMs=
github.com/elazarl/goproxy v0.0.0-20190421051319-9d40249d3c2f/go.mod h1:/Zj4wYkgs4iZTTu3o/KG3Itv/qCCa8VVMlb3i9OVuzc=
github.com/elazarl/goproxy/ext v0.0.0-20190421051319-9d40249d3c2f h1:AUj1VoZUfhPhOPHULCQQDnGhRelpFWHMLhQVWDsS0v4=
github.com/elazarl/goproxy/ext v0.0.0-20190421051319-9d40249d3c2f/go.mod h1:gNh8nYJoAm43RfaxurUnxr+N1PwuFV3ZMl/efxlIlY8=
github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I=
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8=
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
github.com/gorilla/context v0.0.0-20141217160251-215affda49ad h1:wJwKN6X6iRRVnjdBgrkWjhBOvYm7yw5boqXwFUnBtbE=
github.com/gorilla/context v0.0.0-20141217160251-215affda49ad/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg=
github.com/gorilla/securecookie v0.0.0-20150327155805-8e98dd730fc4 h1:X0DbEdoaUJT+NZ8mLHRNMSLmogzqLhsA1Eh6gs7Y7Zg=
github.com/gorilla/securecookie v0.0.0-20150327155805-8e98dd730fc4/go.mod h1:ra0sb63/xPlUeL+yeDciTfxMRAA+MP+HVt/4epWDjd4=
github.com/gorilla/sessions v0.0.0-20150417174705-f61c3ec2cf65 h1:bDPV+Nh80WIp0U0a/o9wKilWRxH+l8jIySjElU91LeA=
github.com/gorilla/sessions v0.0.0-20150417174705-f61c3ec2cf65/go.mod h1:+WVp8kdw6VhyKExm03PAMRn2ZxnPtm58pV0dBVPdhHE=
github.com/gorilla/websocket v0.0.0-20150530030352-a3ec486e6a7a h1:p/PGT+3UGSK7eULOGHUMCUxI5U976R34HWuKHbFpK3Q=
github.com/gorilla/websocket v0.0.0-20150530030352-a3ec486e6a7a/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ=
github.com/hashicorp/go-version v1.2.0 h1:3vNe/fWF5CBgRIguda1meWhsZHy3m8gCJ5wx+dIzX/E=
github.com/hashicorp/go-version v1.2.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo=
github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
github.com/moul/http2curl v1.0.0 h1:dRMWoAtb+ePxMlLkrCbAqh4TlPHXvoGUSQ323/9Zahs=
github.com/moul/http2curl v1.0.0/go.mod h1:8UbvGypXm98wA/IqH45anm5Y2Z6ep6O31QGOAZ3H0fQ=
github.com/parnurzeal/gorequest v0.2.15 h1:oPjDCsF5IkD4gUk6vIgsxYNaSgvAnIh1EJeROn3HdJU=
github.com/parnurzeal/gorequest v0.2.15/go.mod h1:3Kh2QUMJoqw3icWAecsyzkpY7UzRfDhbRdTjtNwNiUE=
github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/rogpeppe/go-charset v0.0.0-20180617210344-2471d30d28b4/go.mod h1:qgYeAmZ5ZIpBWTGllZSQnw97Dj+woV0toclVaRGI8pc=
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d h1:zE9ykElWQ6/NYmHa3jpm/yHnI4xSofP+UP6SpjHcSeM=
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
github.com/smartystreets/goconvey v0.0.0-20190330032615-68dc04aab96a h1:pa8hGb/2YqsZKovtsgrwcDH1RZhVbTKCjLp47XpqCDs=
github.com/smartystreets/goconvey v0.0.0-20190330032615-68dc04aab96a/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190514140710-3ec191127204 h1:4yG6GqBtw9C+UrLp6s2wtSniayy/Vd/3F7ffLE427XI=
golang.org/x/net v0.0.0-20190514140710-3ec191127204/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190515190549-87c872767d25 h1:SSKQq5sjDoW0L0NaDoVl7d7HmtTxM0ezm0Ef9azs4uQ=
golang.org/x/sys v0.0.0-20190515190549-87c872767d25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=

View File

@ -1,11 +1,11 @@
/* /*
* Copyright (c) 2014-2015, b3log.org * Copyright (c) 2014-2018, b3log.org
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
* You may obtain a copy of the License at * You may obtain a copy of the License at
* *
* http://www.apache.org/licenses/LICENSE-2.0 * https://www.apache.org/licenses/LICENSE-2.0
* *
* Unless required by applicable law or agreed to in writing, software * Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, * distributed under the License is distributed on an "AS IS" BASIS,
@ -18,118 +18,133 @@
* @file frontend tool. * @file frontend tool.
* *
* @author <a href="mailto:liliyuan@fangstar.net">Liyuan Li</a> * @author <a href="mailto:liliyuan@fangstar.net">Liyuan Li</a>
* @version 0.1.0.0, Dec 15, 2015 * @version 0.2.0.0, Oct 5, 2018
*/ */
var gulp = require("gulp"); var gulp = require('gulp')
var concat = require('gulp-concat'); var concat = require('gulp-concat')
var minifyCSS = require('gulp-minify-css'); var minifyCSS = require('gulp-minify-css')
var uglify = require('gulp-uglify'); var uglify = require('gulp-uglify')
var sourcemaps = require("gulp-sourcemaps"); var sourcemaps = require('gulp-sourcemaps')
gulp.task('cc', function () { function minLibCSS () {
// css // css
var cssLibs = ['./static/js/lib/jquery-layout/layout-default-latest.css', var cssLibs = [
'./static/js/lib/codemirror-5.1/codemirror.css', './static/js/lib/jquery-layout/layout-default-latest.css',
'./static/js/lib/codemirror-5.1/addon/hint/show-hint.css', './static/js/lib/codemirror-5.1/codemirror.css',
'./static/js/lib/codemirror-5.1/addon/lint/lint.css', './static/js/lib/codemirror-5.1/addon/hint/show-hint.css',
'./static/js/lib/codemirror-5.1/addon/fold/foldgutter.css', './static/js/lib/codemirror-5.1/addon/lint/lint.css',
'./static/js/lib/codemirror-5.1/addon/dialog/dialog.css', './static/js/lib/codemirror-5.1/addon/fold/foldgutter.css',
'./static/js/overwrite/codemirror/theme/*.css']; './static/js/lib/codemirror-5.1/addon/dialog/dialog.css',
gulp.src(cssLibs) './static/js/overwrite/codemirror/theme/*.css']
.pipe(minifyCSS()) return gulp.src(cssLibs).
.pipe(concat('lib.min.css')) pipe(minifyCSS()).
.pipe(gulp.dest('./static/css/')); pipe(concat('lib.min.css')).
pipe(gulp.dest('./static/css/'))
}
gulp.src('./static/js/lib/ztree/zTreeStyle.css') function minZTreeStyleCSS () {
.pipe(minifyCSS()) return gulp.src('./static/js/lib/ztree/zTreeStyle.css').
.pipe(concat('zTreeStyle.min.css')) pipe(minifyCSS()).
.pipe(gulp.dest('./static/js/lib/ztree/')); pipe(concat('zTreeStyle.min.css')).
pipe(gulp.dest('./static/js/lib/ztree/'))
}
var cssWide = ['./static/css/dialog.css', function minWideCSS () {
'./static/css/base.css', var cssWide = [
'./static/css/wide.css', './static/css/dialog.css',
'./static/css/side.css', './static/css/base.css',
'./static/css/start.css', './static/css/wide.css',
'./static/css/about.css' './static/css/side.css',
]; './static/css/start.css',
'./static/css/about.css',
]
gulp.src(cssWide) return gulp.src(cssWide).
.pipe(minifyCSS()) pipe(minifyCSS()).
.pipe(concat('wide.min.css')) pipe(concat('wide.min.css')).
.pipe(gulp.dest('./static/css/')); pipe(gulp.dest('./static/css/'))
}
function minLibJS () {
// js
var jsLibs = [
'./static/js/lib/jquery-2.1.1.min.js',
'./static/js/lib/jquery-ui.min.js',
'./static/js/lib/jquery-layout/jquery.layout-latest.js',
'./static/js/lib/reconnecting-websocket.js',
'./static/js/lib/Autolinker.min.js',
'./static/js/lib/emmet.js',
'./static/js/lib/js-beautify-1.5.4/beautify.js',
'./static/js/lib/js-beautify-1.5.4/beautify-html.js',
'./static/js/lib/js-beautify-1.5.4/beautify-css.js',
'./static/js/lib/jquery-file-upload-9.8.0/vendor/jquery.ui.widget.js',
'./static/js/lib/jquery-file-upload-9.8.0/jquery.iframe-transport.js',
'./static/js/lib/jquery-file-upload-9.8.0/jquery.fileupload.js',
'./static/js/lib/codemirror-5.1/codemirror.min.js',
'./static/js/lib/codemirror-5.1/addon/lint/lint.js',
'./static/js/lib/codemirror-5.1/addon/lint/json-lint.js',
'./static/js/lib/codemirror-5.1/addon/selection/active-line.js',
'./static/js/lib/codemirror-5.1/addon/selection/active-line.js',
'./static/js/overwrite/codemirror/addon/hint/show-hint.js',
'./static/js/lib/codemirror-5.1/addon/hint/anyword-hint.js',
'./static/js/lib/codemirror-5.1/addon/display/rulers.js',
'./static/js/lib/codemirror-5.1/addon/edit/closebrackets.js',
'./static/js/lib/codemirror-5.1/addon/edit/matchbrackets.js',
'./static/js/lib/codemirror-5.1/addon/edit/closetag.js',
'./static/js/lib/codemirror-5.1/addon/search/searchcursor.js',
'./static/js/lib/codemirror-5.1/addon/search/search.js',
'./static/js/lib/codemirror-5.1/addon/dialog/dialog.js',
'./static/js/lib/codemirror-5.1/addon/search/match-highlighter.js',
'./static/js/lib/codemirror-5.1/addon/fold/foldcode.js',
'./static/js/lib/codemirror-5.1/addon/fold/foldgutter.js',
'./static/js/lib/codemirror-5.1/addon/fold/brace-fold.js',
'./static/js/lib/codemirror-5.1/addon/fold/xml-fold.js',
'./static/js/lib/codemirror-5.1/addon/fold/markdown-fold.js',
'./static/js/lib/codemirror-5.1/addon/fold/comment-fold.js',
'./static/js/lib/codemirror-5.1/addon/mode/loadmode.js',
'./static/js/lib/codemirror-5.1/addon/comment/comment.js',
'./static/js/lib/codemirror-5.1/mode/meta.js',
'./static/js/lib/codemirror-5.1/mode/go/go.js',
'./static/js/lib/codemirror-5.1/mode/clike/clike.js',
'./static/js/lib/codemirror-5.1/mode/xml/xml.js',
'./static/js/lib/codemirror-5.1/mode/htmlmixed/htmlmixed.js',
'./static/js/lib/codemirror-5.1/mode/javascript/javascript.js',
'./static/js/lib/codemirror-5.1/mode/markdown/markdown.js',
'./static/js/lib/codemirror-5.1/mode/css/css.js',
'./static/js/lib/codemirror-5.1/mode/shell/shell.js',
'./static/js/lib/codemirror-5.1/mode/sql/sql.js',
'./static/js/lib/codemirror-5.1/keymap/vim.js',
'./static/js/lib/lint/json-lint.js',
'./static/js/lib/lint/go-lint.js']
return gulp.src(jsLibs).
pipe(uglify()).
pipe(concat('lib.min.js')).
pipe(gulp.dest('./static/js/'))
}
// js function minWideJS () {
var jsLibs = ['./static/js/lib/jquery-2.1.1.min.js', var jsWide = [
'./static/js/lib/jquery-ui.min.js', './static/js/tabs.js',
'./static/js/lib/jquery-layout/jquery.layout-latest.js', './static/js/tabs.js',
'./static/js/lib/reconnecting-websocket.js', './static/js/dialog.js',
'./static/js/lib/Autolinker.min.js', './static/js/editors.js',
'./static/js/lib/emmet.js', './static/js/notification.js',
'./static/js/lib/js-beautify-1.5.4/beautify.js', './static/js/tree.js',
'./static/js/lib/js-beautify-1.5.4/beautify-html.js', './static/js/wide.js',
'./static/js/lib/js-beautify-1.5.4/beautify-css.js', './static/js/session.js',
'./static/js/lib/jquery-file-upload-9.8.0/vendor/jquery.ui.widget.js', './static/js/menu.js',
'./static/js/lib/jquery-file-upload-9.8.0/jquery.iframe-transport.js', './static/js/windows.js',
'./static/js/lib/jquery-file-upload-9.8.0/jquery.fileupload.js', './static/js/hotkeys.js',
'./static/js/lib/codemirror-5.1/codemirror.min.js', './static/js/bottomGroup.js',
'./static/js/lib/codemirror-5.1/addon/lint/lint.js', ]
'./static/js/lib/codemirror-5.1/addon/lint/json-lint.js', return gulp.src(jsWide).
'./static/js/lib/codemirror-5.1/addon/selection/active-line.js', pipe(sourcemaps.init()).
'./static/js/lib/codemirror-5.1/addon/selection/active-line.js', pipe(uglify()).
'./static/js/overwrite/codemirror/addon/hint/show-hint.js', pipe(concat('wide.min.js')).
'./static/js/lib/codemirror-5.1/addon/hint/anyword-hint.js', pipe(sourcemaps.write('.')).
'./static/js/lib/codemirror-5.1/addon/display/rulers.js', pipe(gulp.dest('./static/js/'))
'./static/js/lib/codemirror-5.1/addon/edit/closebrackets.js', }
'./static/js/lib/codemirror-5.1/addon/edit/matchbrackets.js',
'./static/js/lib/codemirror-5.1/addon/edit/closetag.js',
'./static/js/lib/codemirror-5.1/addon/search/searchcursor.js',
'./static/js/lib/codemirror-5.1/addon/search/search.js',
'./static/js/lib/codemirror-5.1/addon/dialog/dialog.js',
'./static/js/lib/codemirror-5.1/addon/search/match-highlighter.js',
'./static/js/lib/codemirror-5.1/addon/fold/foldcode.js',
'./static/js/lib/codemirror-5.1/addon/fold/foldgutter.js',
'./static/js/lib/codemirror-5.1/addon/fold/brace-fold.js',
'./static/js/lib/codemirror-5.1/addon/fold/xml-fold.js',
'./static/js/lib/codemirror-5.1/addon/fold/markdown-fold.js',
'./static/js/lib/codemirror-5.1/addon/fold/comment-fold.js',
'./static/js/lib/codemirror-5.1/addon/fold/mode/loadmode.js',
'./static/js/lib/codemirror-5.1/addon/fold/comment/comment.js',
'./static/js/lib/codemirror-5.1/mode/meta.js',
'./static/js/lib/codemirror-5.1/mode/go/go.js',
'./static/js/lib/codemirror-5.1/mode/clike/clike.js',
'./static/js/lib/codemirror-5.1/mode/xml/xml.js',
'./static/js/lib/codemirror-5.1/mode/htmlmixed/htmlmixed.js',
'./static/js/lib/codemirror-5.1/mode/javascript/javascript.js',
'./static/js/lib/codemirror-5.1/mode/markdown/markdown.js',
'./static/js/lib/codemirror-5.1/mode/css/css.js',
'./static/js/lib/codemirror-5.1/mode/shell/shell.js',
'./static/js/lib/codemirror-5.1/mode/sql/sql.js',
'./static/js/lib/codemirror-5.1/keymap/vim.js',
'./static/js/lib/lint/json-lint.js',
'./static/js/lib/lint/go-lint.js'];
gulp.src(jsLibs)
.pipe(uglify())
.pipe(concat('lib.min.js'))
.pipe(gulp.dest('./static/js/'));
var jsWide = ['./static/js/tabs.js', gulp.task('default',
'./static/js/tabs.js', gulp.series(
'./static/js/dialog.js', gulp.parallel(minLibCSS, minZTreeStyleCSS, minWideCSS, minLibJS, minWideJS)))
'./static/js/editors.js',
'./static/js/notification.js',
'./static/js/tree.js',
'./static/js/wide.js',
'./static/js/session.js',
'./static/js/menu.js',
'./static/js/windows.js',
'./static/js/hotkeys.js',
'./static/js/bottomGroup.js'
];
gulp.src(jsWide)
.pipe(sourcemaps.init())
.pipe(uglify())
.pipe(concat('wide.min.js'))
.pipe(sourcemaps.write("."))
.pipe(gulp.dest('./static/js/'));
});

View File

@ -105,8 +105,6 @@
"start-get": "START [go get]", "start-get": "START [go get]",
"get-succ": "[go get] SUCCESS", "get-succ": "[go get] SUCCESS",
"get-error": "[go get] ERROR", "get-error": "[go get] ERROR",
"start-git_clone": "START [git clone]",
"git_clone-done": "[git clone] DONE",
"check_version": "Checking update", "check_version": "Checking update",
"new_version_available": "new version available", "new_version_available": "new version available",
"go_env": "Go", "go_env": "Go",
@ -139,7 +137,6 @@
"clearOutput": "Clear Output", "clearOutput": "Clear Output",
"export": "Export", "export": "Export",
"refresh": "Refresh", "refresh": "Refresh",
"import": "Import",
"theme": "Theme", "theme": "Theme",
"tab_size": "Tab Size", "tab_size": "Tab Size",
"copy_file_path": "Copy File Path", "copy_file_path": "Copy File Path",
@ -162,7 +159,6 @@
"no_empty": "Can not Empty!", "no_empty": "Can not Empty!",
"change_avatar": "Avatar modify go", "change_avatar": "Avatar modify go",
"open": "Open", "open": "Open",
"pricing": "Pricing",
"search_no_match": "No matching files were found.", "search_no_match": "No matching files were found.",
"outline": "Outline", "outline": "Outline",
"govet": "go vet", "govet": "go vet",
@ -174,7 +170,6 @@
"url": "URL", "url": "URL",
"short_url": "Short URL", "short_url": "Short URL",
"embeded": "Embeded", "embeded": "Embeded",
"git_clone": "Git Clone",
"terms": "Terms", "terms": "Terms",
"download": "Download", "download": "Download",
"decompress": "Decompress", "decompress": "Decompress",

View File

@ -105,8 +105,6 @@
"start-get": "[go get] 開始", "start-get": "[go get] 開始",
"get-succ": "[go get] 成功", "get-succ": "[go get] 成功",
"get-error": "[go get] 失敗", "get-error": "[go get] 失敗",
"start-git_clone": "[git clone] 開始",
"git_clone-done": "[git clone] 終わった",
"check_version": "更新をチェック中", "check_version": "更新をチェック中",
"new_version_available": "新しいバージョンがあります", "new_version_available": "新しいバージョンがあります",
"go_env": "Go", "go_env": "Go",
@ -139,7 +137,6 @@
"clearOutput": "空の出力", "clearOutput": "空の出力",
"export": "輸出", "export": "輸出",
"refresh": "リフレッシュ", "refresh": "リフレッシュ",
"import": "インポート",
"theme": "テーマ", "theme": "テーマ",
"tab_size": "Tab サイズ", "tab_size": "Tab サイズ",
"copy_file_path": "ファイルパスをコピー", "copy_file_path": "ファイルパスをコピー",
@ -162,7 +159,6 @@
"no_empty": "空ではありません", "no_empty": "空ではありません",
"change_avatar": "アバターの変更は行く", "change_avatar": "アバターの変更は行く",
"open": "オープン", "open": "オープン",
"pricing": "价格",
"search_no_match": "一致するファイルが見つかりませんでした。", "search_no_match": "一致するファイルが見つかりませんでした。",
"outline": "アウトライン", "outline": "アウトライン",
"govet": "go vet", "govet": "go vet",
@ -174,7 +170,6 @@
"url": "リンク", "url": "リンク",
"short_url": "ショートリンク", "short_url": "ショートリンク",
"embeded": "埋め込む", "embeded": "埋め込む",
"git_clone": "Git クローン",
"terms": "利用規約", "terms": "利用規約",
"download": "ダウンロード", "download": "ダウンロード",
"decompress": "解凍する", "decompress": "解凍する",

View File

@ -105,8 +105,6 @@
"start-get": "시작 [go get]", "start-get": "시작 [go get]",
"get-succ": "[go get] 성공", "get-succ": "[go get] 성공",
"get-error": "[go get] 실패", "get-error": "[go get] 실패",
"start-git_clone": "시작 [git clone]",
"git_clone-done": "[git clone] 완성",
"check_version": "최신버전검색중", "check_version": "최신버전검색중",
"new_version_available": "최신업데이트 사용 가능", "new_version_available": "최신업데이트 사용 가능",
"go_env": "Go 환경", "go_env": "Go 환경",
@ -139,7 +137,6 @@
"clearOutput": "ouput 클리어", "clearOutput": "ouput 클리어",
"export": "내보내기", "export": "내보내기",
"refresh": "새로고침", "refresh": "새로고침",
"import": "가져오기",
"theme": "주제", "theme": "주제",
"tab_size": "Tab 크기", "tab_size": "Tab 크기",
"copy_file_path": "경로복사", "copy_file_path": "경로복사",
@ -162,7 +159,6 @@
"no_empty": "값을 입력해 주세요.", "no_empty": "값을 입력해 주세요.",
"change_avatar": "아이콘변경은 여기로.", "change_avatar": "아이콘변경은 여기로.",
"open": "열기", "open": "열기",
"pricing": "가격",
"search_no_match": "해당 문서를 찾지 못하였습니다.", "search_no_match": "해당 문서를 찾지 못하였습니다.",
"outline": "주제", "outline": "주제",
"govet": "go vet", "govet": "go vet",
@ -174,7 +170,6 @@
"url": "하이퍼링크", "url": "하이퍼링크",
"short_url": "짧은 링크", "short_url": "짧은 링크",
"embeded": "삽입", "embeded": "삽입",
"git_clone": "Git clone",
"terms": "사용계약", "terms": "사용계약",
"download": "다운로드", "download": "다운로드",
"decompress": "압축풀기", "decompress": "압축풀기",

View File

@ -1,10 +1,10 @@
// Copyright (c) 2014-2017, b3log.org & hacpai.com // Copyright (c) 2014-2019, b3log.org & hacpai.com
// //
// Licensed under the Apache License, Version 2.0 (the "License"); // Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License. // you may not use this file except in compliance with the License.
// You may obtain a copy of the License at // You may obtain a copy of the License at
// //
// http://www.apache.org/licenses/LICENSE-2.0 // https://www.apache.org/licenses/LICENSE-2.0
// //
// Unless required by applicable law or agreed to in writing, software // Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS, // distributed under the License is distributed on an "AS IS" BASIS,

View File

@ -105,8 +105,6 @@
"start-get": "开始 [go get]", "start-get": "开始 [go get]",
"get-succ": "[go get] 成功", "get-succ": "[go get] 成功",
"get-error": "[go get] 失败", "get-error": "[go get] 失败",
"start-git_clone": "开始 [git clone]",
"git_clone-done": "[git clone] 完成",
"check_version": "正在检查更新", "check_version": "正在检查更新",
"new_version_available": "新版本可用", "new_version_available": "新版本可用",
"go_env": "Go 环境", "go_env": "Go 环境",
@ -139,7 +137,6 @@
"clearOutput": "清空输出", "clearOutput": "清空输出",
"export": "导出", "export": "导出",
"refresh": "刷新", "refresh": "刷新",
"import": "导入",
"theme": "主题", "theme": "主题",
"tab_size": "Tab 大小", "tab_size": "Tab 大小",
"copy_file_path": "复制文件路径", "copy_file_path": "复制文件路径",
@ -162,7 +159,6 @@
"no_empty": "不能为空", "no_empty": "不能为空",
"change_avatar": "头像修改请到", "change_avatar": "头像修改请到",
"open": "打开", "open": "打开",
"pricing": "价格",
"search_no_match": "没有发现匹配的文件。", "search_no_match": "没有发现匹配的文件。",
"outline": "大纲", "outline": "大纲",
"govet": "go vet", "govet": "go vet",
@ -174,7 +170,6 @@
"url": "链接", "url": "链接",
"short_url": "短链接", "short_url": "短链接",
"embeded": "嵌入", "embeded": "嵌入",
"git_clone": "Git 克隆",
"terms": "使用条款", "terms": "使用条款",
"download": "下载", "download": "下载",
"decompress": "解压缩", "decompress": "解压缩",

View File

@ -105,8 +105,6 @@
"start-get": "開始 [go get]", "start-get": "開始 [go get]",
"get-succ": "[go get] 成功", "get-succ": "[go get] 成功",
"get-error": "[go get] 失敗", "get-error": "[go get] 失敗",
"start-git_clone": "開始 [git clone]",
"git_clone-done": "[git clone] 完成",
"check_version": "正在檢查更新", "check_version": "正在檢查更新",
"new_version_available": "可用新版本", "new_version_available": "可用新版本",
"go_env": "Go 環境", "go_env": "Go 環境",
@ -139,7 +137,6 @@
"clearOutput": "清空輸出", "clearOutput": "清空輸出",
"export": "導出", "export": "導出",
"refresh": "刷新", "refresh": "刷新",
"import": "導入",
"theme": "主題", "theme": "主題",
"tab_size": "Tab 大小", "tab_size": "Tab 大小",
"copy_file_path": "複製檔案位置", "copy_file_path": "複製檔案位置",
@ -162,7 +159,6 @@
"no_empty": "不能為空", "no_empty": "不能為空",
"change_avatar": "修改頭像請到", "change_avatar": "修改頭像請到",
"open": "開啟", "open": "開啟",
"pricing": "價格",
"search_no_match": "沒有發現匹配的文件。", "search_no_match": "沒有發現匹配的文件。",
"outline": "大綱", "outline": "大綱",
"govet": "go vet", "govet": "go vet",
@ -174,7 +170,6 @@
"url": "連結", "url": "連結",
"short_url": "短網址", "short_url": "短網址",
"embeded": "嵌入", "embeded": "嵌入",
"git_clone": "Git Clone",
"terms": "使用條款", "terms": "使用條款",
"download": "下載", "download": "下載",
"decompress": "解壓縮", "decompress": "解壓縮",

View File

@ -1,10 +1,10 @@
// Copyright (c) 2014-2017, b3log.org & hacpai.com // Copyright (c) 2014-2019, b3log.org & hacpai.com
// //
// Licensed under the Apache License, Version 2.0 (the "License"); // Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License. // you may not use this file except in compliance with the License.
// You may obtain a copy of the License at // You may obtain a copy of the License at
// //
// http://www.apache.org/licenses/LICENSE-2.0 // https://www.apache.org/licenses/LICENSE-2.0
// //
// Unless required by applicable law or agreed to in writing, software // Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS, // distributed under the License is distributed on an "AS IS" BASIS,

View File

@ -1,10 +1,10 @@
// Copyright (c) 2014-2017, b3log.org & hacpai.com // Copyright (c) 2014-2019, b3log.org & hacpai.com
// //
// Licensed under the Apache License, Version 2.0 (the "License"); // Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License. // you may not use this file except in compliance with the License.
// You may obtain a copy of the License at // You may obtain a copy of the License at
// //
// http://www.apache.org/licenses/LICENSE-2.0 // https://www.apache.org/licenses/LICENSE-2.0
// //
// Unless required by applicable law or agreed to in writing, software // Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS, // distributed under the License is distributed on an "AS IS" BASIS,

216
main.go
View File

@ -1,10 +1,10 @@
// Copyright (c) 2014-2017, b3log.org & hacpai.com // Copyright (c) 2014-2019, b3log.org & hacpai.com
// //
// Licensed under the Apache License, Version 2.0 (the "License"); // Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License. // you may not use this file except in compliance with the License.
// You may obtain a copy of the License at // You may obtain a copy of the License at
// //
// http://www.apache.org/licenses/LICENSE-2.0 // https://www.apache.org/licenses/LICENSE-2.0
// //
// Unless required by applicable law or agreed to in writing, software // Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS, // distributed under the License is distributed on an "AS IS" BASIS,
@ -38,7 +38,6 @@ import (
"github.com/b3log/wide/notification" "github.com/b3log/wide/notification"
"github.com/b3log/wide/output" "github.com/b3log/wide/output"
"github.com/b3log/wide/playground" "github.com/b3log/wide/playground"
"github.com/b3log/wide/scm/git"
"github.com/b3log/wide/session" "github.com/b3log/wide/session"
"github.com/b3log/wide/util" "github.com/b3log/wide/util"
) )
@ -49,34 +48,26 @@ var logger *log.Logger
// The only one init function in Wide. // The only one init function in Wide.
func init() { func init() {
confPath := flag.String("conf", "conf/wide.json", "path of wide.json") confPath := flag.String("conf", "conf/wide.json", "path of wide.json")
confIP := flag.String("ip", "", "this will overwrite Wide.IP if specified") confData := flag.String("data", "", "path of data dir")
confPort := flag.String("port", "", "this will overwrite Wide.Port if specified")
confServer := flag.String("server", "", "this will overwrite Wide.Server if specified") confServer := flag.String("server", "", "this will overwrite Wide.Server if specified")
confLogLevel := flag.String("log_level", "", "this will overwrite Wide.LogLevel if specified") confLogLevel := flag.String("log_level", "", "this will overwrite Wide.LogLevel if specified")
confStaticServer := flag.String("static_server", "", "this will overwrite Wide.StaticServer if specified")
confContext := flag.String("context", "", "this will overwrite Wide.Context if specified")
confChannel := flag.String("channel", "", "this will overwrite Wide.Channel if specified")
confStat := flag.Bool("stat", false, "whether report statistics periodically") confStat := flag.Bool("stat", false, "whether report statistics periodically")
confDocker := flag.Bool("docker", false, "whether run in a docker container")
confPlayground := flag.String("playground", "", "this will overwrite Wide.Playground if specified")
confUsersWorkspaces := flag.String("users_workspaces", "", "this will overwrite Wide.UsersWorkspaces if specified")
flag.Parse() flag.Parse()
log.SetLevel("warn") log.SetLevel("warn")
logger = log.NewLogger(os.Stdout) logger = log.NewLogger(os.Stdout)
wd := util.OS.Pwd() //wd := util.OS.Pwd()
if strings.HasPrefix(wd, os.TempDir()) { //if strings.HasPrefix(wd, os.TempDir()) {
logger.Error("Don't run Wide in OS' temp directory or with `go run`") // logger.Error("Don't run Wide in OS' temp directory or with `go run`")
//
os.Exit(-1) // os.Exit(-1)
} //}
i18n.Load() i18n.Load()
event.Load() event.Load()
conf.Load(*confPath, *confIP, *confPort, *confServer, *confLogLevel, *confStaticServer, *confContext, *confChannel, conf.Load(*confPath, *confData, *confServer, *confLogLevel)
*confPlayground, *confDocker, *confUsersWorkspaces)
conf.FixedTimeCheckEnv() conf.FixedTimeCheckEnv()
session.FixedTimeSave() session.FixedTimeSave()
@ -86,8 +77,7 @@ func init() {
session.FixedTimeReport() session.FixedTimeReport()
} }
logger.Debug("host ["+runtime.Version()+", "+runtime.GOOS+"_"+runtime.GOARCH+"], cross-compilation ", logger.Debug("host ["+runtime.Version()+", "+runtime.GOOS+"_"+runtime.GOARCH+"], cross-compilation ", util.Go.GetCrossPlatforms())
util.Go.GetCrossPlatforms())
} }
// Main. // Main.
@ -98,96 +88,86 @@ func main() {
handleSignal() handleSignal()
// IDE // IDE
http.HandleFunc(conf.Wide.Context+"/", handlerGzWrapper(indexHandler)) http.HandleFunc("/", handlerGzWrapper(indexHandler))
http.HandleFunc(conf.Wide.Context+"/start", handlerWrapper(startHandler)) http.HandleFunc("/start", handlerWrapper(startHandler))
http.HandleFunc(conf.Wide.Context+"/about", handlerWrapper(aboutHandler)) http.HandleFunc("/about", handlerWrapper(aboutHandler))
http.HandleFunc(conf.Wide.Context+"/keyboard_shortcuts", handlerWrapper(keyboardShortcutsHandler)) http.HandleFunc("/keyboard_shortcuts", handlerWrapper(keyboardShortcutsHandler))
// static resources // static resources
http.Handle(conf.Wide.Context+"/static/", http.StripPrefix(conf.Wide.Context+"/static/", http.FileServer(http.Dir("static")))) http.Handle("/static/", http.StripPrefix("/static/", http.FileServer(http.Dir("static"))))
http.Handle("/static/users/", http.StripPrefix("/static/", http.FileServer(http.Dir("C:\\Users\\DL882\\wide\\static\\"))))
serveSingle("/favicon.ico", "./static/favicon.ico") serveSingle("/favicon.ico", "./static/favicon.ico")
// workspaces // oauth
for _, user := range conf.Users { http.HandleFunc("/oauth/github", session.RedirectGitHubHandler)
http.Handle(conf.Wide.Context+"/workspace/"+user.Name+"/", http.HandleFunc("/oauth/github/callback", session.GithubCallbackHandler)
http.StripPrefix(conf.Wide.Context+"/workspace/"+user.Name+"/", http.FileServer(http.Dir(user.WorkspacePath()))))
}
// session // session
http.HandleFunc(conf.Wide.Context+"/session/ws", handlerWrapper(session.WSHandler)) http.HandleFunc("/session/ws", handlerWrapper(session.WSHandler))
http.HandleFunc(conf.Wide.Context+"/session/save", handlerWrapper(session.SaveContentHandler)) http.HandleFunc("/session/save", handlerWrapper(session.SaveContentHandler))
// run // run
http.HandleFunc(conf.Wide.Context+"/build", handlerWrapper(output.BuildHandler)) http.HandleFunc("/build", handlerWrapper(output.BuildHandler))
http.HandleFunc(conf.Wide.Context+"/run", handlerWrapper(output.RunHandler)) http.HandleFunc("/run", handlerWrapper(output.RunHandler))
http.HandleFunc(conf.Wide.Context+"/stop", handlerWrapper(output.StopHandler)) http.HandleFunc("/stop", handlerWrapper(output.StopHandler))
http.HandleFunc(conf.Wide.Context+"/go/test", handlerWrapper(output.GoTestHandler)) http.HandleFunc("/go/test", handlerWrapper(output.GoTestHandler))
http.HandleFunc(conf.Wide.Context+"/go/vet", handlerWrapper(output.GoVetHandler)) http.HandleFunc("/go/vet", handlerWrapper(output.GoVetHandler))
http.HandleFunc(conf.Wide.Context+"/go/get", handlerWrapper(output.GoGetHandler)) http.HandleFunc("/go/get", handlerWrapper(output.GoGetHandler))
http.HandleFunc(conf.Wide.Context+"/go/install", handlerWrapper(output.GoInstallHandler)) http.HandleFunc("/go/install", handlerWrapper(output.GoInstallHandler))
http.HandleFunc(conf.Wide.Context+"/output/ws", handlerWrapper(output.WSHandler)) http.HandleFunc("/output/ws", handlerWrapper(output.WSHandler))
// cross-compilation // cross-compilation
http.HandleFunc(conf.Wide.Context+"/cross", handlerWrapper(output.CrossCompilationHandler)) http.HandleFunc("/cross", handlerWrapper(output.CrossCompilationHandler))
// file tree // file tree
http.HandleFunc(conf.Wide.Context+"/files", handlerWrapper(file.GetFilesHandler)) http.HandleFunc("/files", handlerWrapper(file.GetFilesHandler))
http.HandleFunc(conf.Wide.Context+"/file/refresh", handlerWrapper(file.RefreshDirectoryHandler)) http.HandleFunc("/file/refresh", handlerWrapper(file.RefreshDirectoryHandler))
http.HandleFunc(conf.Wide.Context+"/file", handlerWrapper(file.GetFileHandler)) http.HandleFunc("/file", handlerWrapper(file.GetFileHandler))
http.HandleFunc(conf.Wide.Context+"/file/save", handlerWrapper(file.SaveFileHandler)) http.HandleFunc("/file/save", handlerWrapper(file.SaveFileHandler))
http.HandleFunc(conf.Wide.Context+"/file/new", handlerWrapper(file.NewFileHandler)) http.HandleFunc("/file/new", handlerWrapper(file.NewFileHandler))
http.HandleFunc(conf.Wide.Context+"/file/remove", handlerWrapper(file.RemoveFileHandler)) http.HandleFunc("/file/remove", handlerWrapper(file.RemoveFileHandler))
http.HandleFunc(conf.Wide.Context+"/file/rename", handlerWrapper(file.RenameFileHandler)) http.HandleFunc("/file/rename", handlerWrapper(file.RenameFileHandler))
http.HandleFunc(conf.Wide.Context+"/file/search/text", handlerWrapper(file.SearchTextHandler)) http.HandleFunc("/file/search/text", handlerWrapper(file.SearchTextHandler))
http.HandleFunc(conf.Wide.Context+"/file/find/name", handlerWrapper(file.FindHandler)) http.HandleFunc("/file/find/name", handlerWrapper(file.FindHandler))
// outline // outline
http.HandleFunc(conf.Wide.Context+"/outline", handlerWrapper(file.GetOutlineHandler)) http.HandleFunc("/outline", handlerWrapper(file.GetOutlineHandler))
// file export/import // file export/import
http.HandleFunc(conf.Wide.Context+"/file/zip/new", handlerWrapper(file.CreateZipHandler)) http.HandleFunc("/file/zip/new", handlerWrapper(file.CreateZipHandler))
http.HandleFunc(conf.Wide.Context+"/file/zip", handlerWrapper(file.GetZipHandler)) http.HandleFunc("/file/zip", handlerWrapper(file.GetZipHandler))
http.HandleFunc(conf.Wide.Context+"/file/upload", handlerWrapper(file.UploadHandler)) http.HandleFunc("/file/decompress", handlerWrapper(file.DecompressHandler))
http.HandleFunc(conf.Wide.Context+"/file/decompress", handlerWrapper(file.DecompressHandler))
// editor // editor
http.HandleFunc(conf.Wide.Context+"/editor/ws", handlerWrapper(editor.WSHandler)) http.HandleFunc("/editor/ws", handlerWrapper(editor.WSHandler))
http.HandleFunc(conf.Wide.Context+"/go/fmt", handlerWrapper(editor.GoFmtHandler)) http.HandleFunc("/go/fmt", handlerWrapper(editor.GoFmtHandler))
http.HandleFunc(conf.Wide.Context+"/autocomplete", handlerWrapper(editor.AutocompleteHandler)) http.HandleFunc("/autocomplete", handlerWrapper(editor.AutocompleteHandler))
http.HandleFunc(conf.Wide.Context+"/exprinfo", handlerWrapper(editor.GetExprInfoHandler)) http.HandleFunc("/exprinfo", handlerWrapper(editor.GetExprInfoHandler))
http.HandleFunc(conf.Wide.Context+"/find/decl", handlerWrapper(editor.FindDeclarationHandler)) http.HandleFunc("/find/decl", handlerWrapper(editor.FindDeclarationHandler))
http.HandleFunc(conf.Wide.Context+"/find/usages", handlerWrapper(editor.FindUsagesHandler)) http.HandleFunc("/find/usages", handlerWrapper(editor.FindUsagesHandler))
// shell
// http.HandleFunc(conf.Wide.Context+"/shell/ws", handlerWrapper(shell.WSHandler))
// http.HandleFunc(conf.Wide.Context+"/shell", handlerWrapper(shell.IndexHandler))
// notification // notification
http.HandleFunc(conf.Wide.Context+"/notification/ws", handlerWrapper(notification.WSHandler)) http.HandleFunc("/notification/ws", handlerWrapper(notification.WSHandler))
// user // user
http.HandleFunc(conf.Wide.Context+"/login", handlerWrapper(session.LoginHandler)) http.HandleFunc("/logout", handlerWrapper(session.LogoutHandler))
http.HandleFunc(conf.Wide.Context+"/logout", handlerWrapper(session.LogoutHandler)) http.HandleFunc("/preference", handlerWrapper(session.PreferenceHandler))
http.HandleFunc(conf.Wide.Context+"/signup", handlerWrapper(session.SignUpUserHandler))
http.HandleFunc(conf.Wide.Context+"/preference", handlerWrapper(session.PreferenceHandler))
// playground // playground
http.HandleFunc(conf.Wide.Context+"/playground", handlerWrapper(playground.IndexHandler)) http.HandleFunc("/playground", handlerWrapper(playground.IndexHandler))
http.HandleFunc(conf.Wide.Context+"/playground/", handlerWrapper(playground.IndexHandler)) http.HandleFunc("/playground/", handlerWrapper(playground.IndexHandler))
http.HandleFunc(conf.Wide.Context+"/playground/ws", handlerWrapper(playground.WSHandler)) http.HandleFunc("/playground/ws", handlerWrapper(playground.WSHandler))
http.HandleFunc(conf.Wide.Context+"/playground/save", handlerWrapper(playground.SaveHandler)) http.HandleFunc("/playground/save", handlerWrapper(playground.SaveHandler))
http.HandleFunc(conf.Wide.Context+"/playground/short-url", handlerWrapper(playground.ShortURLHandler)) http.HandleFunc("/playground/short-url", handlerWrapper(playground.ShortURLHandler))
http.HandleFunc(conf.Wide.Context+"/playground/build", handlerWrapper(playground.BuildHandler)) http.HandleFunc("/playground/build", handlerWrapper(playground.BuildHandler))
http.HandleFunc(conf.Wide.Context+"/playground/run", handlerWrapper(playground.RunHandler)) http.HandleFunc("/playground/run", handlerWrapper(playground.RunHandler))
http.HandleFunc(conf.Wide.Context+"/playground/stop", handlerWrapper(playground.StopHandler)) http.HandleFunc("/playground/stop", handlerWrapper(playground.StopHandler))
http.HandleFunc(conf.Wide.Context+"/playground/autocomplete", handlerWrapper(playground.AutocompleteHandler)) http.HandleFunc("/playground/autocomplete", handlerWrapper(playground.AutocompleteHandler))
// git logger.Infof("Wide is running [%s]", conf.Wide.Server)
http.HandleFunc(conf.Wide.Context+"/git/clone", handlerWrapper(git.CloneHandler))
logger.Infof("Wide is running [%s]", conf.Wide.Server+conf.Wide.Context) listen := conf.Wide.Server[strings.Index(conf.Wide.Server, "://")+3:]
err := http.ListenAndServe(listen, nil)
err := http.ListenAndServe(conf.Wide.Server, nil)
if err != nil { if err != nil {
logger.Error(err) logger.Error(err)
} }
@ -195,51 +175,46 @@ func main() {
// indexHandler handles request of Wide index. // indexHandler handles request of Wide index.
func indexHandler(w http.ResponseWriter, r *http.Request) { func indexHandler(w http.ResponseWriter, r *http.Request) {
if conf.Wide.Context+"/" != r.RequestURI { if "/" != r.RequestURI {
http.Redirect(w, r, conf.Wide.Context+"/", http.StatusFound) http.Redirect(w, r, "/", http.StatusFound)
return return
} }
httpSession, _ := session.HTTPSession.Get(r, "wide-session") httpSession, _ := session.HTTPSession.Get(r, session.CookieName)
if httpSession.IsNew { if httpSession.IsNew {
http.Redirect(w, r, conf.Wide.Context+"/login", http.StatusFound) http.Redirect(w, r, "/login", http.StatusFound)
return return
} }
username := httpSession.Values["username"].(string) uid := httpSession.Values["uid"].(string)
if "playground" == username { // reserved user for Playground if "playground" == uid { // reserved user for Playground
http.Redirect(w, r, conf.Wide.Context+"/login", http.StatusFound) http.Redirect(w, r, "/login", http.StatusFound)
return return
} }
httpSession.Options.MaxAge = conf.Wide.HTTPSessionMaxAge httpSession.Options.MaxAge = conf.Wide.HTTPSessionMaxAge
if "" != conf.Wide.Context {
httpSession.Options.Path = conf.Wide.Context
}
httpSession.Save(r, w) httpSession.Save(r, w)
user := conf.GetUser(username) user := conf.GetUser(uid)
if nil == user { if nil == user {
logger.Warnf("Not found user [%s]", username) http.Redirect(w, r, "/login", http.StatusFound)
http.Redirect(w, r, conf.Wide.Context+"/login", http.StatusFound)
return return
} }
locale := user.Locale locale := user.Locale
wideSessions := session.WideSessions.GetByUsername(username) wideSessions := session.WideSessions.GetByUserId(uid)
model := map[string]interface{}{"conf": conf.Wide, "i18n": i18n.GetAll(locale), "locale": locale, model := map[string]interface{}{"conf": conf.Wide, "i18n": i18n.GetAll(locale), "locale": locale,
"username": username, "sid": session.WideSessions.GenId(), "latestSessionContent": user.LatestSessionContent, "uid": uid, "sid": session.WideSessions.GenId(), "latestSessionContent": user.LatestSessionContent,
"pathSeparator": conf.PathSeparator, "codeMirrorVer": conf.CodeMirrorVer, "pathSeparator": conf.PathSeparator, "codeMirrorVer": conf.CodeMirrorVer,
"user": user, "editorThemes": conf.GetEditorThemes(), "crossPlatforms": util.Go.GetCrossPlatforms()} "user": user, "editorThemes": conf.GetEditorThemes(), "crossPlatforms": util.Go.GetCrossPlatforms()}
logger.Debugf("User [%s] has [%d] sessions", username, len(wideSessions)) logger.Debugf("User [%s] has [%d] sessions", uid, len(wideSessions))
t, err := template.ParseFiles("views/index.html") t, err := template.ParseFiles("views/index.html")
if nil != err { if nil != err {
@ -277,22 +252,19 @@ func serveSingle(pattern string, filename string) {
// startHandler handles request of start page. // startHandler handles request of start page.
func startHandler(w http.ResponseWriter, r *http.Request) { func startHandler(w http.ResponseWriter, r *http.Request) {
httpSession, _ := session.HTTPSession.Get(r, "wide-session") httpSession, _ := session.HTTPSession.Get(r, session.CookieName)
if httpSession.IsNew { if httpSession.IsNew {
http.Redirect(w, r, conf.Wide.Context+"/login", http.StatusFound) http.Redirect(w, r, "/s", http.StatusFound)
return return
} }
httpSession.Options.MaxAge = conf.Wide.HTTPSessionMaxAge httpSession.Options.MaxAge = conf.Wide.HTTPSessionMaxAge
if "" != conf.Wide.Context {
httpSession.Options.Path = conf.Wide.Context
}
httpSession.Save(r, w) httpSession.Save(r, w)
username := httpSession.Values["username"].(string) uid := httpSession.Values["uid"].(string)
locale := conf.GetUser(username).Locale locale := conf.GetUser(uid).Locale
userWorkspace := conf.GetUserWorkspace(username) userWorkspace := conf.GetUserWorkspace(uid)
sid := r.URL.Query()["sid"][0] sid := r.URL.Query()["sid"][0]
wSession := session.WideSessions.Get(sid) wSession := session.WideSessions.Get(sid)
@ -301,7 +273,7 @@ func startHandler(w http.ResponseWriter, r *http.Request) {
} }
model := map[string]interface{}{"conf": conf.Wide, "i18n": i18n.GetAll(locale), "locale": locale, model := map[string]interface{}{"conf": conf.Wide, "i18n": i18n.GetAll(locale), "locale": locale,
"username": username, "workspace": userWorkspace, "ver": conf.WideVersion, "sid": sid} "uid": uid, "workspace": userWorkspace, "ver": conf.WideVersion, "sid": sid}
t, err := template.ParseFiles("views/start.html") t, err := template.ParseFiles("views/start.html")
@ -317,21 +289,18 @@ func startHandler(w http.ResponseWriter, r *http.Request) {
// keyboardShortcutsHandler handles request of keyboard shortcuts page. // keyboardShortcutsHandler handles request of keyboard shortcuts page.
func keyboardShortcutsHandler(w http.ResponseWriter, r *http.Request) { func keyboardShortcutsHandler(w http.ResponseWriter, r *http.Request) {
httpSession, _ := session.HTTPSession.Get(r, "wide-session") httpSession, _ := session.HTTPSession.Get(r, session.CookieName)
if httpSession.IsNew { if httpSession.IsNew {
http.Redirect(w, r, conf.Wide.Context+"/login", http.StatusFound) http.Redirect(w, r, "/login", http.StatusFound)
return return
} }
httpSession.Options.MaxAge = conf.Wide.HTTPSessionMaxAge httpSession.Options.MaxAge = conf.Wide.HTTPSessionMaxAge
if "" != conf.Wide.Context {
httpSession.Options.Path = conf.Wide.Context
}
httpSession.Save(r, w) httpSession.Save(r, w)
username := httpSession.Values["username"].(string) uid := httpSession.Values["uid"].(string)
locale := conf.GetUser(username).Locale locale := conf.GetUser(uid).Locale
model := map[string]interface{}{"conf": conf.Wide, "i18n": i18n.GetAll(locale), "locale": locale} model := map[string]interface{}{"conf": conf.Wide, "i18n": i18n.GetAll(locale), "locale": locale}
@ -349,21 +318,18 @@ func keyboardShortcutsHandler(w http.ResponseWriter, r *http.Request) {
// aboutHandle handles request of about page. // aboutHandle handles request of about page.
func aboutHandler(w http.ResponseWriter, r *http.Request) { func aboutHandler(w http.ResponseWriter, r *http.Request) {
httpSession, _ := session.HTTPSession.Get(r, "wide-session") httpSession, _ := session.HTTPSession.Get(r, session.CookieName)
if httpSession.IsNew { if httpSession.IsNew {
http.Redirect(w, r, conf.Wide.Context+"/login", http.StatusFound) http.Redirect(w, r, "/login", http.StatusFound)
return return
} }
httpSession.Options.MaxAge = conf.Wide.HTTPSessionMaxAge httpSession.Options.MaxAge = conf.Wide.HTTPSessionMaxAge
if "" != conf.Wide.Context {
httpSession.Options.Path = conf.Wide.Context
}
httpSession.Save(r, w) httpSession.Save(r, w)
username := httpSession.Values["username"].(string) uid := httpSession.Values["uid"].(string)
locale := conf.GetUser(username).Locale locale := conf.GetUser(uid).Locale
model := map[string]interface{}{"conf": conf.Wide, "i18n": i18n.GetAll(locale), "locale": locale, model := map[string]interface{}{"conf": conf.Wide, "i18n": i18n.GetAll(locale), "locale": locale,
"ver": conf.WideVersion, "goos": runtime.GOOS, "goarch": runtime.GOARCH, "gover": runtime.Version()} "ver": conf.WideVersion, "goos": runtime.GOOS, "goarch": runtime.GOARCH, "gover": runtime.Version()}

View File

@ -1,10 +1,10 @@
// Copyright (c) 2014-2017, b3log.org & hacpai.com // Copyright (c) 2014-2019, b3log.org & hacpai.com
// //
// Licensed under the Apache License, Version 2.0 (the "License"); // Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License. // you may not use this file except in compliance with the License.
// You may obtain a copy of the License at // You may obtain a copy of the License at
// //
// http://www.apache.org/licenses/LICENSE-2.0 // https://www.apache.org/licenses/LICENSE-2.0
// //
// Unless required by applicable law or agreed to in writing, software // Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS, // distributed under the License is distributed on an "AS IS" BASIS,
@ -62,9 +62,9 @@ func event2Notification(e *event.Event) {
return return
} }
httpSession, _ := session.HTTPSession.Get(wsChannel.Request, "wide-session") httpSession, _ := session.HTTPSession.Get(wsChannel.Request, session.CookieName)
username := httpSession.Values["username"].(string) uid := httpSession.Values["uid"].(string)
locale := conf.GetUser(username).Locale locale := conf.GetUser(uid).Locale
var notification *Notification var notification *Notification

View File

@ -1,10 +1,10 @@
// Copyright (c) 2014-2017, b3log.org & hacpai.com // Copyright (c) 2014-2019, b3log.org & hacpai.com
// //
// Licensed under the Apache License, Version 2.0 (the "License"); // Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License. // you may not use this file except in compliance with the License.
// You may obtain a copy of the License at // You may obtain a copy of the License at
// //
// http://www.apache.org/licenses/LICENSE-2.0 // https://www.apache.org/licenses/LICENSE-2.0
// //
// Unless required by applicable law or agreed to in writing, software // Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS, // distributed under the License is distributed on an "AS IS" BASIS,
@ -38,14 +38,14 @@ func BuildHandler(w http.ResponseWriter, r *http.Request) {
result := util.NewResult() result := util.NewResult()
defer util.RetResult(w, r, result) defer util.RetResult(w, r, result)
httpSession, _ := session.HTTPSession.Get(r, "wide-session") httpSession, _ := session.HTTPSession.Get(r, session.CookieName)
if httpSession.IsNew { if httpSession.IsNew {
http.Error(w, "Forbidden", http.StatusForbidden) http.Error(w, "Forbidden", http.StatusForbidden)
return return
} }
username := httpSession.Values["username"].(string) uid := httpSession.Values["uid"].(string)
user := conf.GetUser(username) user := conf.GetUser(uid)
locale := user.Locale locale := user.Locale
var args map[string]interface{} var args map[string]interface{}
@ -61,7 +61,7 @@ func BuildHandler(w http.ResponseWriter, r *http.Request) {
filePath := args["file"].(string) filePath := args["file"].(string)
if util.Go.IsAPI(filePath) || !session.CanAccess(username, filePath) { if util.Go.IsAPI(filePath) || !session.CanAccess(uid, filePath) {
http.Error(w, "Forbidden", http.StatusForbidden) http.Error(w, "Forbidden", http.StatusForbidden)
return return
@ -95,13 +95,13 @@ func BuildHandler(w http.ResponseWriter, r *http.Request) {
} }
goBuildArgs := []string{} goBuildArgs := []string{}
goBuildArgs = append(goBuildArgs, "build") goBuildArgs = append(goBuildArgs, "build", "-i")
goBuildArgs = append(goBuildArgs, user.BuildArgs(runtime.GOOS)...) goBuildArgs = append(goBuildArgs, user.BuildArgs(runtime.GOOS)...)
cmd := exec.Command("go", goBuildArgs...) cmd := exec.Command("go", goBuildArgs...)
cmd.Dir = curDir cmd.Dir = curDir
setCmdEnv(cmd, username) setCmdEnv(cmd, uid)
executable := filepath.Base(curDir) + suffix executable := filepath.Base(curDir) + suffix
executable = filepath.Join(curDir, executable) executable = filepath.Join(curDir, executable)
@ -155,8 +155,6 @@ func BuildHandler(w http.ResponseWriter, r *http.Request) {
return return
} }
// logger.Debugf("User [%s, %s] is building [id=%d, dir=%s]", username, sid, runningId, curDir)
channelRet["cmd"] = "build" channelRet["cmd"] = "build"
channelRet["executable"] = executable channelRet["executable"] = executable
@ -188,6 +186,7 @@ func BuildHandler(w http.ResponseWriter, r *http.Request) {
err = wsChannel.WriteJSON(&channelRet) err = wsChannel.WriteJSON(&channelRet)
if nil != err { if nil != err {
logger.Warn(err) logger.Warn(err)
break break
} }
@ -196,7 +195,7 @@ func BuildHandler(w http.ResponseWriter, r *http.Request) {
}() }()
errReader := bufio.NewReader(stderr) errReader := bufio.NewReader(stderr)
lines := []string{} var lines []string
for { for {
wsChannel := session.OutputWS[sid] wsChannel := session.OutputWS[sid]
if nil == wsChannel { if nil == wsChannel {
@ -232,20 +231,6 @@ func BuildHandler(w http.ResponseWriter, r *http.Request) {
if nil == cmd.Wait() { if nil == cmd.Wait() {
channelRet["nextCmd"] = args["nextCmd"] channelRet["nextCmd"] = args["nextCmd"]
channelRet["output"] = "<span class='build-succ'>" + i18n.Get(locale, "build-succ").(string) + "</span>\n" channelRet["output"] = "<span class='build-succ'>" + i18n.Get(locale, "build-succ").(string) + "</span>\n"
go func() { // go install, for subsequent gocode lib-path
defer util.Recover()
cmd := exec.Command("go", "install")
cmd.Dir = curDir
setCmdEnv(cmd, username)
out, _ := cmd.CombinedOutput()
if len(out) > 0 {
logger.Warn(string(out))
}
}()
} else { } else {
channelRet["output"] = "<span class='build-error'>" + i18n.Get(locale, "build-error").(string) + "</span>\n" channelRet["output"] = "<span class='build-error'>" + i18n.Get(locale, "build-error").(string) + "</span>\n"

View File

@ -1,10 +1,10 @@
// Copyright (c) 2014-2017, b3log.org & hacpai.com // Copyright (c) 2014-2019, b3log.org & hacpai.com
// //
// Licensed under the Apache License, Version 2.0 (the "License"); // Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License. // you may not use this file except in compliance with the License.
// You may obtain a copy of the License at // You may obtain a copy of the License at
// //
// http://www.apache.org/licenses/LICENSE-2.0 // https://www.apache.org/licenses/LICENSE-2.0
// //
// Unless required by applicable law or agreed to in writing, software // Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS, // distributed under the License is distributed on an "AS IS" BASIS,
@ -37,14 +37,14 @@ func CrossCompilationHandler(w http.ResponseWriter, r *http.Request) {
result := util.NewResult() result := util.NewResult()
defer util.RetResult(w, r, result) defer util.RetResult(w, r, result)
httpSession, _ := session.HTTPSession.Get(r, "wide-session") httpSession, _ := session.HTTPSession.Get(r, session.CookieName)
if httpSession.IsNew { if httpSession.IsNew {
http.Error(w, "Forbidden", http.StatusForbidden) http.Error(w, "Forbidden", http.StatusForbidden)
return return
} }
username := httpSession.Values["username"].(string) uid := httpSession.Values["uid"].(string)
locale := conf.GetUser(username).Locale locale := conf.GetUser(uid).Locale
var args map[string]interface{} var args map[string]interface{}
@ -58,7 +58,7 @@ func CrossCompilationHandler(w http.ResponseWriter, r *http.Request) {
sid := args["sid"].(string) sid := args["sid"].(string)
filePath := args["path"].(string) filePath := args["path"].(string)
if util.Go.IsAPI(filePath) || !session.CanAccess(username, filePath) { if util.Go.IsAPI(filePath) || !session.CanAccess(uid, filePath) {
http.Error(w, "Forbidden", http.StatusForbidden) http.Error(w, "Forbidden", http.StatusForbidden)
return return
@ -75,7 +75,7 @@ func CrossCompilationHandler(w http.ResponseWriter, r *http.Request) {
suffix = ".exe" suffix = ".exe"
} }
user := conf.GetUser(username) user := conf.GetUser(uid)
goBuildArgs := []string{} goBuildArgs := []string{}
goBuildArgs = append(goBuildArgs, "build") goBuildArgs = append(goBuildArgs, "build")
goBuildArgs = append(goBuildArgs, user.BuildArgs(goos)...) goBuildArgs = append(goBuildArgs, user.BuildArgs(goos)...)
@ -83,7 +83,7 @@ func CrossCompilationHandler(w http.ResponseWriter, r *http.Request) {
cmd := exec.Command("go", goBuildArgs...) cmd := exec.Command("go", goBuildArgs...)
cmd.Dir = curDir cmd.Dir = curDir
setCmdEnv(cmd, username) setCmdEnv(cmd, uid)
for i, env := range cmd.Env { for i, env := range cmd.Env {
if strings.HasPrefix(env, "GOOS=") { if strings.HasPrefix(env, "GOOS=") {
@ -155,8 +155,6 @@ func CrossCompilationHandler(w http.ResponseWriter, r *http.Request) {
defer util.Recover() defer util.Recover()
defer cmd.Wait() defer cmd.Wait()
// logger.Debugf("User [%s, %s] is building [id=%d, dir=%s]", username, sid, runningId, curDir)
// read all // read all
buf, _ := ioutil.ReadAll(reader) buf, _ := ioutil.ReadAll(reader)
@ -235,8 +233,6 @@ func CrossCompilationHandler(w http.ResponseWriter, r *http.Request) {
} }
if nil != session.OutputWS[sid] { if nil != session.OutputWS[sid] {
// logger.Debugf("User [%s, %s] 's build [id=%d, dir=%s] has done", username, sid, runningId, curDir)
wsChannel := session.OutputWS[sid] wsChannel := session.OutputWS[sid]
err := wsChannel.WriteJSON(&channelRet) err := wsChannel.WriteJSON(&channelRet)
if nil != err { if nil != err {

View File

@ -1,10 +1,10 @@
// Copyright (c) 2014-2017, b3log.org & hacpai.com // Copyright (c) 2014-2019, b3log.org & hacpai.com
// //
// Licensed under the Apache License, Version 2.0 (the "License"); // Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License. // you may not use this file except in compliance with the License.
// You may obtain a copy of the License at // You may obtain a copy of the License at
// //
// http://www.apache.org/licenses/LICENSE-2.0 // https://www.apache.org/licenses/LICENSE-2.0
// //
// Unless required by applicable law or agreed to in writing, software // Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS, // distributed under the License is distributed on an "AS IS" BASIS,
@ -35,14 +35,14 @@ func GoGetHandler(w http.ResponseWriter, r *http.Request) {
result := util.NewResult() result := util.NewResult()
defer util.RetResult(w, r, result) defer util.RetResult(w, r, result)
httpSession, _ := session.HTTPSession.Get(r, "wide-session") httpSession, _ := session.HTTPSession.Get(r, session.CookieName)
if httpSession.IsNew { if httpSession.IsNew {
http.Error(w, "Forbidden", http.StatusForbidden) http.Error(w, "Forbidden", http.StatusForbidden)
return return
} }
username := httpSession.Values["username"].(string) uid := httpSession.Values["uid"].(string)
locale := conf.GetUser(username).Locale locale := conf.GetUser(uid).Locale
var args map[string]interface{} var args map[string]interface{}
if err := json.NewDecoder(r.Body).Decode(&args); err != nil { if err := json.NewDecoder(r.Body).Decode(&args); err != nil {
@ -60,7 +60,7 @@ func GoGetHandler(w http.ResponseWriter, r *http.Request) {
cmd := exec.Command("go", "get") cmd := exec.Command("go", "get")
cmd.Dir = curDir cmd.Dir = curDir
setCmdEnv(cmd, username) setCmdEnv(cmd, uid)
stdout, err := cmd.StdoutPipe() stdout, err := cmd.StdoutPipe()
if nil != err { if nil != err {
@ -114,7 +114,7 @@ func GoGetHandler(w http.ResponseWriter, r *http.Request) {
defer util.Recover() defer util.Recover()
defer cmd.Wait() defer cmd.Wait()
logger.Debugf("User [%s, %s] is running [go get] [runningId=%d]", username, sid, runningId) logger.Debugf("User [%s, %s] is running [go get] [runningId=%d]", uid, sid, runningId)
channelRet := map[string]interface{}{} channelRet := map[string]interface{}{}
channelRet["cmd"] = "go get" channelRet["cmd"] = "go get"
@ -123,11 +123,11 @@ func GoGetHandler(w http.ResponseWriter, r *http.Request) {
buf, _ := ioutil.ReadAll(reader) buf, _ := ioutil.ReadAll(reader)
if 0 != len(buf) { if 0 != len(buf) {
logger.Debugf("User [%s, %s] 's [go get] [runningId=%d] has done (with error)", username, sid, runningId) logger.Debugf("User [%s, %s] 's [go get] [runningId=%d] has done (with error)", uid, sid, runningId)
channelRet["output"] = "<span class='get-error'>" + i18n.Get(locale, "get-error").(string) + "</span>\n" + string(buf) channelRet["output"] = "<span class='get-error'>" + i18n.Get(locale, "get-error").(string) + "</span>\n" + string(buf)
} else { } else {
logger.Debugf("User [%s, %s] 's running [go get] [runningId=%d] has done", username, sid, runningId) logger.Debugf("User [%s, %s] 's running [go get] [runningId=%d] has done", uid, sid, runningId)
channelRet["output"] = "<span class='get-succ'>" + i18n.Get(locale, "get-succ").(string) + "</span>\n" channelRet["output"] = "<span class='get-succ'>" + i18n.Get(locale, "get-succ").(string) + "</span>\n"
} }

View File

@ -1,10 +1,10 @@
// Copyright (c) 2014-2017, b3log.org & hacpai.com // Copyright (c) 2014-2019, b3log.org & hacpai.com
// //
// Licensed under the Apache License, Version 2.0 (the "License"); // Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License. // you may not use this file except in compliance with the License.
// You may obtain a copy of the License at // You may obtain a copy of the License at
// //
// http://www.apache.org/licenses/LICENSE-2.0 // https://www.apache.org/licenses/LICENSE-2.0
// //
// Unless required by applicable law or agreed to in writing, software // Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS, // distributed under the License is distributed on an "AS IS" BASIS,
@ -37,14 +37,14 @@ func GoInstallHandler(w http.ResponseWriter, r *http.Request) {
result := util.NewResult() result := util.NewResult()
defer util.RetResult(w, r, result) defer util.RetResult(w, r, result)
httpSession, _ := session.HTTPSession.Get(r, "wide-session") httpSession, _ := session.HTTPSession.Get(r, session.CookieName)
if httpSession.IsNew { if httpSession.IsNew {
http.Error(w, "Forbidden", http.StatusForbidden) http.Error(w, "Forbidden", http.StatusForbidden)
return return
} }
username := httpSession.Values["username"].(string) uid := httpSession.Values["uid"].(string)
locale := conf.GetUser(username).Locale locale := conf.GetUser(uid).Locale
var args map[string]interface{} var args map[string]interface{}
@ -63,7 +63,7 @@ func GoInstallHandler(w http.ResponseWriter, r *http.Request) {
cmd := exec.Command("go", "install") cmd := exec.Command("go", "install")
cmd.Dir = curDir cmd.Dir = curDir
setCmdEnv(cmd, username) setCmdEnv(cmd, uid)
logger.Debugf("go install %s", curDir) logger.Debugf("go install %s", curDir)
@ -119,7 +119,7 @@ func GoInstallHandler(w http.ResponseWriter, r *http.Request) {
defer util.Recover() defer util.Recover()
defer cmd.Wait() defer cmd.Wait()
logger.Debugf("User [%s, %s] is running [go install] [id=%d, dir=%s]", username, sid, runningId, curDir) logger.Debugf("User [%s, %s] is running [go install] [id=%d, dir=%s]", uid, sid, runningId, curDir)
// read all // read all
buf, _ := ioutil.ReadAll(reader) buf, _ := ioutil.ReadAll(reader)
@ -183,7 +183,7 @@ func GoInstallHandler(w http.ResponseWriter, r *http.Request) {
} }
if nil != session.OutputWS[sid] { if nil != session.OutputWS[sid] {
logger.Debugf("User [%s, %s] 's running [go install] [id=%d, dir=%s] has done", username, sid, runningId, curDir) logger.Debugf("User [%s, %s] 's running [go install] [id=%d, dir=%s] has done", uid, sid, runningId, curDir)
wsChannel := session.OutputWS[sid] wsChannel := session.OutputWS[sid]
err := wsChannel.WriteJSON(&channelRet) err := wsChannel.WriteJSON(&channelRet)

View File

@ -1,25 +0,0 @@
// Copyright (c) 2014-2017, b3log.org & hacpai.com
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// +build !linux
package output
import (
"os/exec"
)
func SetNamespace(cmd *exec.Cmd) {
// do nothing
}

View File

@ -1,35 +0,0 @@
// Copyright (c) 2014-2017, b3log.org & hacpai.com
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package output
import (
"os/exec"
"syscall"
)
func SetNamespace(cmd *exec.Cmd) {
// XXX: keep move with Go 1.4 and later's
cmd.SysProcAttr = &syscall.SysProcAttr{}
//cmd.SysProcAttr.Cloneflags = syscall.CLONE_NEWUSER | syscall.CLONE_NEWNS | syscall.CLONE_NEWUTS | syscall.CLONE_NEWPID | syscall.CLONE_NEWIPC | syscall.CLONE_NEWNET
cmd.SysProcAttr.Cloneflags = syscall.CLONE_NEWUSER /*| syscall.CLONE_NEWNS*/ | syscall.CLONE_NEWUTS | syscall.CLONE_NEWPID | syscall.CLONE_NEWIPC /*| syscall.CLONE_NEWNET*/
cmd.SysProcAttr.Credential = &syscall.Credential{
Uid: 0,
Gid: 0,
}
cmd.SysProcAttr.UidMappings = []syscall.SysProcIDMap{{ContainerID: 0, HostID: 1001, Size: 1}}
cmd.SysProcAttr.GidMappings = []syscall.SysProcIDMap{{ContainerID: 0, HostID: 1001, Size: 1}}
}

View File

@ -1,10 +1,10 @@
// Copyright (c) 2014-2017, b3log.org & hacpai.com // Copyright (c) 2014-2019, b3log.org & hacpai.com
// //
// Licensed under the Apache License, Version 2.0 (the "License"); // Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License. // you may not use this file except in compliance with the License.
// You may obtain a copy of the License at // You may obtain a copy of the License at
// //
// http://www.apache.org/licenses/LICENSE-2.0 // https://www.apache.org/licenses/LICENSE-2.0
// //
// Unless required by applicable law or agreed to in writing, software // Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS, // distributed under the License is distributed on an "AS IS" BASIS,
@ -104,18 +104,27 @@ func parsePath(curDir, outputLine string) string {
return tagStart + text + tagEnd + msgPart return tagStart + text + tagEnd + msgPart
} }
func setCmdEnv(cmd *exec.Cmd, username string) { func setCmdEnv(cmd *exec.Cmd, uid string) {
userWorkspace := conf.GetUserWorkspace(username) userWorkspace := conf.GetUserWorkspace(uid)
cache, err := os.UserCacheDir()
if nil != err {
logger.Warnf("Get user cache dir failed [" + err.Error() + "]")
cache = os.TempDir()
}
cmd.Env = append(cmd.Env, cmd.Env = append(cmd.Env,
"GOPATH="+userWorkspace, "GOPATH="+userWorkspace,
"GOOS="+runtime.GOOS, "GOOS="+runtime.GOOS,
"GOARCH="+runtime.GOARCH, "GOARCH="+runtime.GOARCH,
"GOROOT="+runtime.GOROOT(), "GOROOT="+runtime.GOROOT(),
"GOCACHE="+cache,
"PATH="+os.Getenv("PATH")) "PATH="+os.Getenv("PATH"))
if util.OS.IsWindows() { if util.OS.IsWindows() {
// FIXME: for some weird issues on Windows, such as: The requested service provider could not be loaded or initialized. // FIXME: for some weird issues on Windows, such as: The requested service provider could not be loaded or initialized.
cmd.Env = append(cmd.Env, os.Environ()...) cmd.Env = append(cmd.Env, os.Environ()...)
} else {
// 编译链接时找不到依赖的动态库 https://github.com/b3log/wide/issues/352
cmd.Env = append(cmd.Env, "LD_LIBRARY_PATH="+os.Getenv("LD_LIBRARY_PATH"))
} }
} }

View File

@ -1,10 +1,10 @@
// Copyright (c) 2014-2017, b3log.org & hacpai.com // Copyright (c) 2014-2019, b3log.org & hacpai.com
// //
// Licensed under the Apache License, Version 2.0 (the "License"); // Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License. // you may not use this file except in compliance with the License.
// You may obtain a copy of the License at // You may obtain a copy of the License at
// //
// http://www.apache.org/licenses/LICENSE-2.0 // https://www.apache.org/licenses/LICENSE-2.0
// //
// Unless required by applicable law or agreed to in writing, software // Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS, // distributed under the License is distributed on an "AS IS" BASIS,
@ -86,7 +86,7 @@ func (procs *procs) Kill(wSession *session.WideSession, pid int) {
for i, p := range userProcesses { for i, p := range userProcesses {
if p.Pid == pid { if p.Pid == pid {
if err := p.Kill(); nil != err { if err := p.Kill(); nil != err {
logger.Errorf("Kill a process [pid=%d] of user [%s, %s] failed [error=%v]", pid, wSession.Username, sid, err) logger.Errorf("Kill a process [pid=%d] of user [%s, %s] failed [error=%v]", pid, wSession.UserId, sid, err)
} else { } else {
var newProcesses []*os.Process var newProcesses []*os.Process
@ -96,7 +96,7 @@ func (procs *procs) Kill(wSession *session.WideSession, pid int) {
// bind process with wide session // bind process with wide session
wSession.SetProcesses(newProcesses) wSession.SetProcesses(newProcesses)
logger.Debugf("Killed a process [pid=%d] of user [%s, %s]", pid, wSession.Username, sid) logger.Debugf("Killed a process [pid=%d] of user [%s, %s]", pid, wSession.UserId, sid)
} }
return return

View File

@ -1,10 +1,10 @@
// Copyright (c) 2014-2017, b3log.org & hacpai.com // Copyright (c) 2014-2019, b3log.org & hacpai.com
// //
// Licensed under the Apache License, Version 2.0 (the "License"); // Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License. // you may not use this file except in compliance with the License.
// You may obtain a copy of the License at // You may obtain a copy of the License at
// //
// http://www.apache.org/licenses/LICENSE-2.0 // https://www.apache.org/licenses/LICENSE-2.0
// //
// Unless required by applicable law or agreed to in writing, software // Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS, // distributed under the License is distributed on an "AS IS" BASIS,
@ -22,24 +22,12 @@ import (
"os/exec" "os/exec"
"path/filepath" "path/filepath"
"strings" "strings"
"time"
"github.com/b3log/wide/conf" "github.com/b3log/wide/conf"
"github.com/b3log/wide/session" "github.com/b3log/wide/session"
"github.com/b3log/wide/util" "github.com/b3log/wide/util"
) )
const (
outputBufMax = 1024 // 1024 string(rune)
outputTimeout = 100 // 100ms
outputCountMax = 30 // 30 reads
)
type outputBuf struct {
content string
millisecond int64
}
// RunHandler handles request of executing a binary file. // RunHandler handles request of executing a binary file.
func RunHandler(w http.ResponseWriter, r *http.Request) { func RunHandler(w http.ResponseWriter, r *http.Request) {
result := util.NewResult() result := util.NewResult()
@ -59,13 +47,15 @@ func RunHandler(w http.ResponseWriter, r *http.Request) {
} }
filePath := args["executable"].(string) filePath := args["executable"].(string)
curDir := filepath.Dir(filePath)
cmd := exec.Command(filePath)
cmd.Dir = curDir
var cmd *exec.Cmd
if conf.Docker { if conf.Docker {
SetNamespace(cmd) fileName := filepath.Base(filePath)
cmd = exec.Command("timeout", "5", "docker", "run", "--rm", "-v", filePath+":/"+fileName, conf.DockerImageGo, "/"+fileName)
} else {
cmd = exec.Command(filePath)
curDir := filepath.Dir(filePath)
cmd.Dir = curDir
} }
stdout, err := cmd.StdoutPipe() stdout, err := cmd.StdoutPipe()
@ -87,24 +77,13 @@ func RunHandler(w http.ResponseWriter, r *http.Request) {
logger.Error(err) logger.Error(err)
result.Succ = false result.Succ = false
} }
wsChannel := session.OutputWS[sid] wsChannel := session.OutputWS[sid]
channelRet := map[string]interface{}{} channelRet := map[string]interface{}{}
if !result.Succ { if !result.Succ {
if nil != wsChannel { channelRet["cmd"] = "run-done"
channelRet["cmd"] = "run-done" channelRet["output"] = ""
channelRet["output"] = "" wsChannel.WriteJSON(&channelRet)
wsChannel.Refresh()
err := wsChannel.WriteJSON(&channelRet)
if nil != err {
logger.Warn(err)
return
}
wsChannel.Refresh()
}
return return
} }
@ -114,148 +93,76 @@ func RunHandler(w http.ResponseWriter, r *http.Request) {
// add the process to user's process set // add the process to user's process set
Processes.Add(wSession, cmd.Process) Processes.Add(wSession, cmd.Process)
go func(runningId int) { // push once for front-end to get the 'run' state and pid
defer util.Recover() if nil != wsChannel {
defer cmd.Wait() channelRet["cmd"] = "run"
channelRet["output"] = ""
logger.Debugf("User [%s, %s] is running [id=%d, file=%s]", wSession.Username, sid, runningId, filePath)
// push once for front-end to get the 'run' state and pid
if nil != wsChannel { if nil != wsChannel {
channelRet["cmd"] = "run" wsChannel.WriteJSON(&channelRet)
channelRet["output"] = ""
err := wsChannel.WriteJSON(&channelRet)
if nil != err {
logger.Warn(err)
return
}
wsChannel.Refresh() wsChannel.Refresh()
} }
}
go func(runningId int) {
defer util.Recover()
logger.Debugf("User [%s, %s] is running [id=%d, file=%s]", wSession.UserId, sid, runningId, filePath)
go func() { go func() {
defer util.Recover() defer util.Recover()
buf := outputBuf{}
count := 0
for { for {
wsChannel := session.OutputWS[sid]
if nil == wsChannel {
break
}
r, _, err := outReader.ReadRune() r, _, err := outReader.ReadRune()
count++
if nil != err { if nil != err {
// remove the exited process from user's process set
Processes.Remove(wSession, cmd.Process)
logger.Debugf("User [%s, %s] 's running [id=%d, file=%s] has done [stdout %v], ",
wSession.Username, sid, runningId, filePath, err)
channelRet["cmd"] = "run-done"
channelRet["output"] = buf.content
err := wsChannel.WriteJSON(&channelRet)
if nil != err {
logger.Warn(err)
break
}
wsChannel.Refresh()
break break
} }
oneRuneStr := string(r) oneRuneStr := string(r)
oneRuneStr = strings.Replace(oneRuneStr, "<", "&lt;", -1) oneRuneStr = strings.Replace(oneRuneStr, "<", "&lt;", -1)
oneRuneStr = strings.Replace(oneRuneStr, ">", "&gt;", -1) oneRuneStr = strings.Replace(oneRuneStr, ">", "&gt;", -1)
channelRet["cmd"] = "run"
buf.content += oneRuneStr channelRet["output"] = oneRuneStr
wsChannel := session.OutputWS[sid]
now := time.Now().UnixNano() / int64(time.Millisecond) if nil != wsChannel {
wsChannel.WriteJSON(&channelRet)
if 0 == buf.millisecond {
buf.millisecond = now
}
flood := count > outputCountMax
if "\n" == oneRuneStr && !flood {
channelRet["cmd"] = "run"
channelRet["output"] = buf.content
buf = outputBuf{} // a new buffer
count = 0 // clear count
err = wsChannel.WriteJSON(&channelRet)
if nil != err {
logger.Warn(err)
break
}
wsChannel.Refresh() wsChannel.Refresh()
continue
}
if now-outputTimeout >= buf.millisecond || len(buf.content) > outputBufMax {
channelRet["cmd"] = "run"
channelRet["output"] = buf.content
buf = outputBuf{} // a new buffer
count = 0 // clear count
err = wsChannel.WriteJSON(&channelRet)
if nil != err {
logger.Warn(err)
break
}
wsChannel.Refresh()
continue
} }
} }
}() }()
buf := outputBuf{}
for { for {
r, _, err := errReader.ReadRune() r, _, err := errReader.ReadRune()
if nil != err {
wsChannel := session.OutputWS[sid]
if nil != err || nil == wsChannel {
break break
} }
oneRuneStr := string(r) oneRuneStr := string(r)
oneRuneStr = strings.Replace(oneRuneStr, "<", "&lt;", -1) oneRuneStr = strings.Replace(oneRuneStr, "<", "&lt;", -1)
oneRuneStr = strings.Replace(oneRuneStr, ">", "&gt;", -1) oneRuneStr = strings.Replace(oneRuneStr, ">", "&gt;", -1)
channelRet["cmd"] = "run"
buf.content += oneRuneStr channelRet["output"] = "<span class='stderr'>" + oneRuneStr + "</span>"
wsChannel := session.OutputWS[sid]
now := time.Now().UnixNano() / int64(time.Millisecond) if nil != wsChannel {
wsChannel.WriteJSON(&channelRet)
if 0 == buf.millisecond {
buf.millisecond = now
}
if now-outputTimeout >= buf.millisecond || len(buf.content) > outputBufMax || oneRuneStr == "\n" {
channelRet["cmd"] = "run"
channelRet["output"] = "<span class='stderr'>" + buf.content + "</span>"
buf = outputBuf{} // a new buffer
err = wsChannel.WriteJSON(&channelRet)
if nil != err {
logger.Warn(err)
break
}
wsChannel.Refresh() wsChannel.Refresh()
} }
} }
cmd.Wait()
// remove the exited process from user's process set
Processes.Remove(wSession, cmd.Process)
channelRet["cmd"] = "run-done"
if 124 == cmd.ProcessState.ExitCode() {
channelRet["output"] = "<span class='stderr'>run program timeout in 5s</span>\n"
} else {
channelRet["output"] = "\n<span class='stderr'>run program complete</span>\n"
}
if nil != wsChannel {
wsChannel.WriteJSON(&channelRet)
wsChannel.Refresh()
}
}(rand.Int()) }(rand.Int())
} }

View File

@ -1,10 +1,10 @@
// Copyright (c) 2014-2017, b3log.org & hacpai.com // Copyright (c) 2014-2019, b3log.org & hacpai.com
// //
// Licensed under the Apache License, Version 2.0 (the "License"); // Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License. // you may not use this file except in compliance with the License.
// You may obtain a copy of the License at // You may obtain a copy of the License at
// //
// http://www.apache.org/licenses/LICENSE-2.0 // https://www.apache.org/licenses/LICENSE-2.0
// //
// Unless required by applicable law or agreed to in writing, software // Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS, // distributed under the License is distributed on an "AS IS" BASIS,
@ -35,14 +35,14 @@ func GoTestHandler(w http.ResponseWriter, r *http.Request) {
result := util.NewResult() result := util.NewResult()
defer util.RetResult(w, r, result) defer util.RetResult(w, r, result)
httpSession, _ := session.HTTPSession.Get(r, "wide-session") httpSession, _ := session.HTTPSession.Get(r, session.CookieName)
if httpSession.IsNew { if httpSession.IsNew {
http.Error(w, "Forbidden", http.StatusForbidden) http.Error(w, "Forbidden", http.StatusForbidden)
return return
} }
username := httpSession.Values["username"].(string) uid := httpSession.Values["uid"].(string)
locale := conf.GetUser(username).Locale locale := conf.GetUser(uid).Locale
var args map[string]interface{} var args map[string]interface{}
@ -61,7 +61,7 @@ func GoTestHandler(w http.ResponseWriter, r *http.Request) {
cmd := exec.Command("go", "test", "-v") cmd := exec.Command("go", "test", "-v")
cmd.Dir = curDir cmd.Dir = curDir
setCmdEnv(cmd, username) setCmdEnv(cmd, uid)
stdout, err := cmd.StdoutPipe() stdout, err := cmd.StdoutPipe()
if nil != err { if nil != err {
@ -114,7 +114,7 @@ func GoTestHandler(w http.ResponseWriter, r *http.Request) {
go func(runningId int) { go func(runningId int) {
defer util.Recover() defer util.Recover()
logger.Debugf("User [%s, %s] is running [go test] [runningId=%d]", username, sid, runningId) logger.Debugf("User [%s, %s] is running [go test] [runningId=%d]", uid, sid, runningId)
channelRet := map[string]interface{}{} channelRet := map[string]interface{}{}
channelRet["cmd"] = "go test" channelRet["cmd"] = "go test"
@ -126,11 +126,11 @@ func GoTestHandler(w http.ResponseWriter, r *http.Request) {
cmd.Wait() cmd.Wait()
if !cmd.ProcessState.Success() { if !cmd.ProcessState.Success() {
logger.Debugf("User [%s, %s] 's running [go test] [runningId=%d] has done (with error)", username, sid, runningId) logger.Debugf("User [%s, %s] 's running [go test] [runningId=%d] has done (with error)", uid, sid, runningId)
channelRet["output"] = "<span class='test-error'>" + i18n.Get(locale, "test-error").(string) + "</span>\n" + string(buf) channelRet["output"] = "<span class='test-error'>" + i18n.Get(locale, "test-error").(string) + "</span>\n" + string(buf)
} else { } else {
logger.Debugf("User [%s, %s] 's running [go test] [runningId=%d] has done", username, sid, runningId) logger.Debugf("User [%s, %s] 's running [go test] [runningId=%d] has done", uid, sid, runningId)
channelRet["output"] = "<span class='test-succ'>" + i18n.Get(locale, "test-succ").(string) + "</span>\n" + string(buf) channelRet["output"] = "<span class='test-succ'>" + i18n.Get(locale, "test-succ").(string) + "</span>\n" + string(buf)
} }

View File

@ -1,10 +1,10 @@
// Copyright (c) 2014-2017, b3log.org & hacpai.com // Copyright (c) 2014-2019, b3log.org & hacpai.com
// //
// Licensed under the Apache License, Version 2.0 (the "License"); // Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License. // you may not use this file except in compliance with the License.
// You may obtain a copy of the License at // You may obtain a copy of the License at
// //
// http://www.apache.org/licenses/LICENSE-2.0 // https://www.apache.org/licenses/LICENSE-2.0
// //
// Unless required by applicable law or agreed to in writing, software // Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS, // distributed under the License is distributed on an "AS IS" BASIS,
@ -35,14 +35,14 @@ func GoVetHandler(w http.ResponseWriter, r *http.Request) {
result := util.NewResult() result := util.NewResult()
defer util.RetResult(w, r, result) defer util.RetResult(w, r, result)
httpSession, _ := session.HTTPSession.Get(r, "wide-session") httpSession, _ := session.HTTPSession.Get(r, session.CookieName)
if httpSession.IsNew { if httpSession.IsNew {
http.Error(w, "Forbidden", http.StatusForbidden) http.Error(w, "Forbidden", http.StatusForbidden)
return return
} }
username := httpSession.Values["username"].(string) uid := httpSession.Values["uid"].(string)
locale := conf.GetUser(username).Locale locale := conf.GetUser(uid).Locale
var args map[string]interface{} var args map[string]interface{}
@ -61,7 +61,7 @@ func GoVetHandler(w http.ResponseWriter, r *http.Request) {
cmd := exec.Command("go", "vet", ".") cmd := exec.Command("go", "vet", ".")
cmd.Dir = curDir cmd.Dir = curDir
setCmdEnv(cmd, username) setCmdEnv(cmd, uid)
stdout, err := cmd.StdoutPipe() stdout, err := cmd.StdoutPipe()
if nil != err { if nil != err {
@ -114,7 +114,7 @@ func GoVetHandler(w http.ResponseWriter, r *http.Request) {
go func(runningId int) { go func(runningId int) {
defer util.Recover() defer util.Recover()
logger.Debugf("User [%s, %s] is running [go vet] [runningId=%d]", username, sid, runningId) logger.Debugf("User [%s, %s] is running [go vet] [runningId=%d]", uid, sid, runningId)
channelRet := map[string]interface{}{} channelRet := map[string]interface{}{}
channelRet["cmd"] = "go vet" channelRet["cmd"] = "go vet"
@ -126,11 +126,11 @@ func GoVetHandler(w http.ResponseWriter, r *http.Request) {
cmd.Wait() cmd.Wait()
if !cmd.ProcessState.Success() { if !cmd.ProcessState.Success() {
logger.Debugf("User [%s, %s] 's running [go vet] [runningId=%d] has done (with error)", username, sid, runningId) logger.Debugf("User [%s, %s] 's running [go vet] [runningId=%d] has done (with error)", uid, sid, runningId)
channelRet["output"] = "<span class='vet-error'>" + i18n.Get(locale, "vet-error").(string) + "</span>\n" + string(buf) channelRet["output"] = "<span class='vet-error'>" + i18n.Get(locale, "vet-error").(string) + "</span>\n" + string(buf)
} else { } else {
logger.Debugf("User [%s, %s] 's running [go vet] [runningId=%d] has done", username, sid, runningId) logger.Debugf("User [%s, %s] 's running [go vet] [runningId=%d] has done", uid, sid, runningId)
channelRet["output"] = "<span class='vet-succ'>" + i18n.Get(locale, "vet-succ").(string) + "</span>\n" + string(buf) channelRet["output"] = "<span class='vet-succ'>" + i18n.Get(locale, "vet-succ").(string) + "</span>\n" + string(buf)
} }

View File

@ -12,22 +12,25 @@
}, },
"license": "Apache License", "license": "Apache License",
"private": true, "private": true,
"author": "Daniel <dl882509@gmail.com> (http://88250.b3log.org) & Vanessa <lly219@gmail.com> (http://vanessa.b3log.org)", "author": "Daniel <d@b3log.org> (http://88250.b3log.org) & Vanessa <v@b3log.org> (http://vanessa.b3log.org)",
"maintainers": [ "maintainers": [
{ {
"name": "Daniel", "name": "Daniel",
"email": "dl88250@gmail.com" "email": "d@b3log.org"
}, },
{ {
"name": "Vanessa", "name": "Vanessa",
"email": "lly219@gmail.com" "email": "v@b3log.org"
} }
], ],
"scripts": {
"build": "gulp"
},
"devDependencies": { "devDependencies": {
"gulp": "^3.9.1", "gulp": "^4.0.2",
"gulp-concat": "^2.6.1", "gulp-concat": "^2.6.1",
"gulp-minify-css": "^1.2.4", "gulp-minify-css": "^1.2.4",
"gulp-sourcemaps": "^2.6.0", "gulp-sourcemaps": "^2.6.0",
"gulp-uglify": "^2.1.2" "gulp-uglify": "^3.0.1"
} }
} }

View File

@ -1,10 +1,10 @@
// Copyright (c) 2014-2017, b3log.org & hacpai.com // Copyright (c) 2014-2019, b3log.org & hacpai.com
// //
// Licensed under the Apache License, Version 2.0 (the "License"); // Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License. // you may not use this file except in compliance with the License.
// You may obtain a copy of the License at // You may obtain a copy of the License at
// //
// http://www.apache.org/licenses/LICENSE-2.0 // https://www.apache.org/licenses/LICENSE-2.0
// //
// Unless required by applicable law or agreed to in writing, software // Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS, // distributed under the License is distributed on an "AS IS" BASIS,
@ -37,7 +37,7 @@ func AutocompleteHandler(w http.ResponseWriter, r *http.Request) {
return return
} }
session, _ := session.HTTPSession.Get(r, "wide-session") session, _ := session.HTTPSession.Get(r, session.CookieName)
if session.IsNew { if session.IsNew {
http.Error(w, "Forbidden", http.StatusForbidden) http.Error(w, "Forbidden", http.StatusForbidden)

View File

@ -1,10 +1,10 @@
// Copyright (c) 2014-2017, b3log.org & hacpai.com // Copyright (c) 2014-2019, b3log.org & hacpai.com
// //
// Licensed under the Apache License, Version 2.0 (the "License"); // Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License. // you may not use this file except in compliance with the License.
// You may obtain a copy of the License at // You may obtain a copy of the License at
// //
// http://www.apache.org/licenses/LICENSE-2.0 // https://www.apache.org/licenses/LICENSE-2.0
// //
// Unless required by applicable law or agreed to in writing, software // Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS, // distributed under the License is distributed on an "AS IS" BASIS,
@ -32,7 +32,7 @@ func BuildHandler(w http.ResponseWriter, r *http.Request) {
result := util.NewResult() result := util.NewResult()
defer util.RetResult(w, r, result) defer util.RetResult(w, r, result)
httpSession, _ := session.HTTPSession.Get(r, "wide-session") httpSession, _ := session.HTTPSession.Get(r, session.CookieName)
if httpSession.IsNew { if httpSession.IsNew {
http.Error(w, "Forbidden", http.StatusForbidden) http.Error(w, "Forbidden", http.StatusForbidden)
@ -48,7 +48,7 @@ func BuildHandler(w http.ResponseWriter, r *http.Request) {
} }
fileName := args["fileName"].(string) fileName := args["fileName"].(string)
filePath := filepath.Clean(conf.Wide.Playground + "/" + fileName) filePath := filepath.Clean(conf.Wide.Data + "/playground/" + fileName)
suffix := "" suffix := ""
if util.OS.IsWindows() { if util.OS.IsWindows() {
@ -58,7 +58,7 @@ func BuildHandler(w http.ResponseWriter, r *http.Request) {
data := map[string]interface{}{} data := map[string]interface{}{}
result.Data = &data result.Data = &data
executable := filepath.Clean(conf.Wide.Playground + "/" + strings.Replace(fileName, ".go", suffix, -1)) executable := filepath.Clean(conf.Wide.Data + "/playground/" + strings.Replace(fileName, ".go", suffix, -1))
cmd := exec.Command("go", "build", "-o", executable, filePath) cmd := exec.Command("go", "build", "-o", executable, filePath)
out, err := cmd.CombinedOutput() out, err := cmd.CombinedOutput()

View File

@ -1,10 +1,10 @@
// Copyright (c) 2014-2017, b3log.org & hacpai.com // Copyright (c) 2014-2019, b3log.org & hacpai.com
// //
// Licensed under the Apache License, Version 2.0 (the "License"); // Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License. // you may not use this file except in compliance with the License.
// You may obtain a copy of the License at // You may obtain a copy of the License at
// //
// http://www.apache.org/licenses/LICENSE-2.0 // https://www.apache.org/licenses/LICENSE-2.0
// //
// Unless required by applicable law or agreed to in writing, software // Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS, // distributed under the License is distributed on an "AS IS" BASIS,
@ -35,7 +35,7 @@ func SaveHandler(w http.ResponseWriter, r *http.Request) {
result := util.NewResult() result := util.NewResult()
defer util.RetResult(w, r, result) defer util.RetResult(w, r, result)
session, _ := session.HTTPSession.Get(r, "wide-session") session, _ := session.HTTPSession.Get(r, session.CookieName)
if session.IsNew { if session.IsNew {
http.Error(w, "Forbidden", http.StatusForbidden) http.Error(w, "Forbidden", http.StatusForbidden)
@ -85,7 +85,7 @@ func SaveHandler(w http.ResponseWriter, r *http.Request) {
data["fileName"] = fileName data["fileName"] = fileName
// Step3. write file // Step3. write file
filePath := filepath.Clean(conf.Wide.Playground + "/" + fileName) filePath := filepath.Clean(conf.Wide.Data + "/playground" + fileName)
fout, err := os.Create(filePath) fout, err := os.Create(filePath)
fout.WriteString(code) fout.WriteString(code)
if err := fout.Close(); nil != err { if err := fout.Close(); nil != err {
@ -101,7 +101,7 @@ func ShortURLHandler(w http.ResponseWriter, r *http.Request) {
result := util.NewResult() result := util.NewResult()
defer util.RetResult(w, r, result) defer util.RetResult(w, r, result)
session, _ := session.HTTPSession.Get(r, "wide-session") session, _ := session.HTTPSession.Get(r, session.CookieName)
if session.IsNew { if session.IsNew {
http.Error(w, "Forbidden", http.StatusForbidden) http.Error(w, "Forbidden", http.StatusForbidden)

View File

@ -1,10 +1,10 @@
// Copyright (c) 2014-2017, b3log.org & hacpai.com // Copyright (c) 2014-2019, b3log.org & hacpai.com
// //
// Licensed under the Apache License, Version 2.0 (the "License"); // Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License. // you may not use this file except in compliance with the License.
// You may obtain a copy of the License at // You may obtain a copy of the License at
// //
// http://www.apache.org/licenses/LICENSE-2.0 // https://www.apache.org/licenses/LICENSE-2.0
// //
// Unless required by applicable law or agreed to in writing, software // Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS, // distributed under the License is distributed on an "AS IS" BASIS,
@ -40,19 +40,16 @@ var logger = log.NewLogger(os.Stdout)
// IndexHandler handles request of Playground index. // IndexHandler handles request of Playground index.
func IndexHandler(w http.ResponseWriter, r *http.Request) { func IndexHandler(w http.ResponseWriter, r *http.Request) {
// create a HTTP session // create a HTTP session
httpSession, _ := session.HTTPSession.Get(r, "wide-session") httpSession, _ := session.HTTPSession.Get(r, session.CookieName)
if httpSession.IsNew { if httpSession.IsNew {
httpSession.Values["id"] = strconv.Itoa(rand.Int()) httpSession.Values["id"] = strconv.Itoa(rand.Int())
httpSession.Values["username"] = "playground" httpSession.Values["uid"] = "playground"
} }
httpSession.Options.MaxAge = conf.Wide.HTTPSessionMaxAge httpSession.Options.MaxAge = conf.Wide.HTTPSessionMaxAge
if "" != conf.Wide.Context {
httpSession.Options.Path = conf.Wide.Context
}
httpSession.Save(r, w) httpSession.Save(r, w)
username := httpSession.Values["username"].(string) uid := httpSession.Values["uid"].(string)
locale := conf.Wide.Locale locale := conf.Wide.Locale
@ -62,7 +59,7 @@ func IndexHandler(w http.ResponseWriter, r *http.Request) {
if strings.HasSuffix(r.URL.Path, ".go") { if strings.HasSuffix(r.URL.Path, ".go") {
fileNameArg := r.URL.Path[len("/playground/"):] fileNameArg := r.URL.Path[len("/playground/"):]
filePath := filepath.Clean(conf.Wide.Playground + "/" + fileNameArg) filePath := filepath.Clean(conf.Wide.Data+ "/playground" + fileNameArg)
bytes, err := ioutil.ReadFile(filePath) bytes, err := ioutil.ReadFile(filePath)
if nil != err { if nil != err {
@ -92,9 +89,9 @@ func IndexHandler(w http.ResponseWriter, r *http.Request) {
"code": template.HTML(code), "ver": conf.WideVersion, "year": time.Now().Year(), "code": template.HTML(code), "ver": conf.WideVersion, "year": time.Now().Year(),
"embed": embed, "disqus": disqus, "fileName": fileName} "embed": embed, "disqus": disqus, "fileName": fileName}
wideSessions := session.WideSessions.GetByUsername(username) wideSessions := session.WideSessions.GetByUserId(uid)
logger.Debugf("User [%s] has [%d] sessions", username, len(wideSessions)) logger.Debugf("User [%s] has [%d] sessions", uid, len(wideSessions))
t, err := template.ParseFiles("views/playground/index.html") t, err := template.ParseFiles("views/playground/index.html")

View File

@ -1,10 +1,10 @@
// Copyright (c) 2014-2017, b3log.org & hacpai.com // Copyright (c) 2014-2019, b3log.org & hacpai.com
// //
// Licensed under the Apache License, Version 2.0 (the "License"); // Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License. // you may not use this file except in compliance with the License.
// You may obtain a copy of the License at // You may obtain a copy of the License at
// //
// http://www.apache.org/licenses/LICENSE-2.0 // https://www.apache.org/licenses/LICENSE-2.0
// //
// Unless required by applicable law or agreed to in writing, software // Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS, // distributed under the License is distributed on an "AS IS" BASIS,
@ -20,7 +20,8 @@ import (
"math/rand" "math/rand"
"net/http" "net/http"
"os/exec" "os/exec"
"time" "path/filepath"
"strings"
"github.com/b3log/wide/conf" "github.com/b3log/wide/conf"
"github.com/b3log/wide/output" "github.com/b3log/wide/output"
@ -28,17 +29,6 @@ import (
"github.com/b3log/wide/util" "github.com/b3log/wide/util"
) )
const (
outputBufMax = 1024 // 1024 string(rune)
outputTimeout = 100 // 100ms
outputCountMax = 30 // 30 reads
)
type outputBuf struct {
content string
millisecond int64
}
// RunHandler handles request of executing a binary file. // RunHandler handles request of executing a binary file.
func RunHandler(w http.ResponseWriter, r *http.Request) { func RunHandler(w http.ResponseWriter, r *http.Request) {
result := util.NewResult() result := util.NewResult()
@ -59,10 +49,14 @@ func RunHandler(w http.ResponseWriter, r *http.Request) {
filePath := args["executable"].(string) filePath := args["executable"].(string)
cmd := exec.Command(filePath) var cmd *exec.Cmd
if conf.Docker { if conf.Docker {
output.SetNamespace(cmd) fileName := filepath.Base(filePath)
cmd = exec.Command("timeout", "5", "docker", "run", "--rm", "-v", filePath+":/"+fileName, conf.DockerImageGo, "/"+fileName)
} else {
cmd = exec.Command(filePath)
curDir := filepath.Dir(filePath)
cmd.Dir = curDir
} }
stdout, err := cmd.StdoutPipe() stdout, err := cmd.StdoutPipe()
@ -86,22 +80,12 @@ func RunHandler(w http.ResponseWriter, r *http.Request) {
} }
wsChannel := session.PlaygroundWS[sid] wsChannel := session.PlaygroundWS[sid]
channelRet := map[string]interface{}{} channelRet := map[string]interface{}{}
if !result.Succ { if !result.Succ {
if nil != wsChannel { channelRet["cmd"] = "run-done"
channelRet["cmd"] = "run-done" channelRet["output"] = ""
channelRet["output"] = "" wsChannel.WriteJSON(&channelRet)
wsChannel.Refresh()
err := wsChannel.WriteJSON(&channelRet)
if nil != err {
logger.Warn(err)
return
}
wsChannel.Refresh()
}
return return
} }
@ -111,143 +95,74 @@ func RunHandler(w http.ResponseWriter, r *http.Request) {
// add the process to user's process set // add the process to user's process set
output.Processes.Add(wSession, cmd.Process) output.Processes.Add(wSession, cmd.Process)
go func(runningId int) { // push once for front-end to get the 'run' state and pid
defer util.Recover() if nil != wsChannel {
defer cmd.Wait() channelRet["cmd"] = "run"
channelRet["output"] = ""
logger.Debugf("User [%s, %s] is running [id=%d, file=%s]", wSession.Username, sid, runningId, filePath)
// push once for front-end to get the 'run' state and pid
if nil != wsChannel { if nil != wsChannel {
channelRet["cmd"] = "run" wsChannel.WriteJSON(&channelRet)
channelRet["output"] = ""
err := wsChannel.WriteJSON(&channelRet)
if nil != err {
logger.Warn(err)
return
}
wsChannel.Refresh() wsChannel.Refresh()
} }
}
go func(runningId int) {
defer util.Recover()
logger.Debugf("User [%s, %s] is running [id=%d, file=%s]", wSession.UserId, sid, runningId, filePath)
go func() { go func() {
defer util.Recover() defer util.Recover()
buf := outputBuf{}
count := 0
for { for {
wsChannel := session.PlaygroundWS[sid]
if nil == wsChannel {
break
}
r, _, err := outReader.ReadRune() r, _, err := outReader.ReadRune()
count++
if nil != err { if nil != err {
// remove the exited process from user process set
output.Processes.Remove(wSession, cmd.Process)
logger.Debugf("User [%s, %s] 's running [id=%d, file=%s] has done [stdout %v], ", wSession.Username, sid, runningId, filePath, err)
channelRet["cmd"] = "run-done"
channelRet["output"] = buf.content
err := wsChannel.WriteJSON(&channelRet)
if nil != err {
logger.Warn(err)
break
}
wsChannel.Refresh()
break break
} }
oneRuneStr := string(r) oneRuneStr := string(r)
oneRuneStr = strings.Replace(oneRuneStr, "<", "&lt;", -1)
buf.content += oneRuneStr oneRuneStr = strings.Replace(oneRuneStr, ">", "&gt;", -1)
channelRet["cmd"] = "run"
now := time.Now().UnixNano() / int64(time.Millisecond) channelRet["output"] = oneRuneStr
wsChannel := session.PlaygroundWS[sid]
if 0 == buf.millisecond { if nil != wsChannel {
buf.millisecond = now wsChannel.WriteJSON(&channelRet)
}
flood := count > outputCountMax
if "\n" == oneRuneStr && !flood {
channelRet["cmd"] = "run"
channelRet["output"] = buf.content
buf = outputBuf{} // a new buffer
count = 0 // clear count
err = wsChannel.WriteJSON(&channelRet)
if nil != err {
logger.Warn(err)
break
}
wsChannel.Refresh() wsChannel.Refresh()
continue
}
if now-outputTimeout >= buf.millisecond || len(buf.content) > outputBufMax {
channelRet["cmd"] = "run"
channelRet["output"] = buf.content
buf = outputBuf{} // a new buffer
count = 0 // clear count
err = wsChannel.WriteJSON(&channelRet)
if nil != err {
logger.Warn(err)
break
}
wsChannel.Refresh()
continue
} }
} }
}() }()
buf := outputBuf{}
for { for {
r, _, err := errReader.ReadRune() r, _, err := errReader.ReadRune()
if nil != err {
wsChannel := session.PlaygroundWS[sid]
if nil != err || nil == wsChannel {
break break
} }
oneRuneStr := string(r) oneRuneStr := string(r)
oneRuneStr = strings.Replace(oneRuneStr, "<", "&lt;", -1)
buf.content += oneRuneStr oneRuneStr = strings.Replace(oneRuneStr, ">", "&gt;", -1)
channelRet["cmd"] = "run"
now := time.Now().UnixNano() / int64(time.Millisecond) channelRet["output"] = oneRuneStr
wsChannel := session.PlaygroundWS[sid]
if 0 == buf.millisecond { if nil != wsChannel {
buf.millisecond = now wsChannel.WriteJSON(&channelRet)
}
if now-outputTimeout >= buf.millisecond || len(buf.content) > outputBufMax || oneRuneStr == "\n" {
channelRet["cmd"] = "run"
channelRet["output"] = buf.content
buf = outputBuf{} // a new buffer
err = wsChannel.WriteJSON(&channelRet)
if nil != err {
logger.Warn(err)
break
}
wsChannel.Refresh() wsChannel.Refresh()
} }
} }
cmd.Wait()
// remove the exited process from user's process set
output.Processes.Remove(wSession, cmd.Process)
channelRet["cmd"] = "run-done"
if 124 == cmd.ProcessState.ExitCode() {
channelRet["output"] = "\nrun program timeout in 5s\n"
}
if nil != wsChannel {
wsChannel.WriteJSON(&channelRet)
wsChannel.Refresh()
}
}(rand.Int()) }(rand.Int())
} }

View File

@ -1,146 +0,0 @@
// Copyright (c) 2014-2017, b3log.org & hacpai.com
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package git
import (
"bufio"
"encoding/json"
"io"
"io/ioutil"
"math/rand"
"net/http"
"os"
"os/exec"
"github.com/b3log/wide/conf"
"github.com/b3log/wide/i18n"
"github.com/b3log/wide/log"
"github.com/b3log/wide/session"
"github.com/b3log/wide/util"
)
// Logger.
var logger = log.NewLogger(os.Stdout)
// Clone handles request of git clone.
func CloneHandler(w http.ResponseWriter, r *http.Request) {
result := util.NewResult()
defer util.RetResult(w, r, result)
httpSession, _ := session.HTTPSession.Get(r, "wide-session")
if httpSession.IsNew {
http.Error(w, "Forbidden", http.StatusForbidden)
return
}
username := httpSession.Values["username"].(string)
locale := conf.GetUser(username).Locale
var args map[string]interface{}
if err := json.NewDecoder(r.Body).Decode(&args); err != nil {
logger.Error(err)
result.Succ = false
return
}
sid := args["sid"].(string)
path := args["path"].(string)
repository := args["repository"].(string)
cmd := exec.Command("git", "clone", repository)
cmd.Dir = path
stdout, err := cmd.StdoutPipe()
if nil != err {
logger.Error(err)
result.Succ = false
return
}
stderr, err := cmd.StderrPipe()
if nil != err {
logger.Error(err)
result.Succ = false
return
}
if !result.Succ {
return
}
channelRet := map[string]interface{}{}
if nil != session.OutputWS[sid] {
// display "START [git clone]" in front-end browser
channelRet["output"] = "<span class='start-get'>" + i18n.Get(locale, "start-git_clone").(string) + "</span>\n"
channelRet["cmd"] = "start-git_clone"
wsChannel := session.OutputWS[sid]
err := wsChannel.WriteJSON(&channelRet)
if nil != err {
logger.Warn(err)
return
}
wsChannel.Refresh()
}
reader := bufio.NewReader(io.MultiReader(stdout, stderr))
if err := cmd.Start(); nil != err {
logger.Error(err)
result.Succ = false
return
}
go func(runningId int) {
defer util.Recover()
defer cmd.Wait()
logger.Debugf("User [%s, %s] is running [git clone] [runningId=%d]", username, sid, runningId)
channelRet := map[string]interface{}{}
channelRet["cmd"] = "git clone"
// read all
buf, err := ioutil.ReadAll(reader)
if nil != err {
logger.Warn(err)
// TODO: handle clone error
}
logger.Debugf("User [%s, %s] 's running [git clone] [runningId=%d] has done: %s", username, sid, runningId, string(buf))
channelRet["output"] = "<span class='get-succ'>" + i18n.Get(locale, "git_clone-done").(string) + "</span>\n"
if nil != session.OutputWS[sid] {
wsChannel := session.OutputWS[sid]
err := wsChannel.WriteJSON(&channelRet)
if nil != err {
logger.Warn(err)
}
wsChannel.Refresh()
}
}(rand.Int())
}

272
session/oauthctl.go Normal file
View File

@ -0,0 +1,272 @@
// Copyright (c) 2014-2019, b3log.org & hacpai.com
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package session
import (
"crypto/tls"
"math/rand"
"net/http"
"os"
"path/filepath"
"strconv"
"strings"
"time"
"github.com/b3log/wide/conf"
"github.com/b3log/wide/util"
"github.com/parnurzeal/gorequest"
)
var states = map[string]string{}
// RedirectGitHubHandler redirects to GitHub auth page.
func RedirectGitHubHandler(w http.ResponseWriter, r *http.Request) {
requestResult := util.NewResult()
_, _, errs := gorequest.New().TLSClientConfig(&tls.Config{InsecureSkipVerify: true}).
Get("https://hacpai.com/oauth/wide/client").
Set("user-agent", conf.UserAgent).Timeout(10 * time.Second).EndStruct(requestResult)
if nil != errs {
logger.Errorf("Get oauth client id failed: %+v", errs)
http.Error(w, "Get oauth info failed", http.StatusInternalServerError)
return
}
if 0 != requestResult.Code {
logger.Errorf("get oauth client id failed [code=%d, msg=%s]", requestResult.Code, requestResult.Msg)
http.Error(w, "Get oauth info failed", http.StatusNotFound)
return
}
data := requestResult.Data.(map[string]interface{})
clientId := data["clientId"].(string)
loginAuthURL := data["loginAuthURL"].(string)
referer := r.URL.Query().Get("referer")
if "" == referer || !strings.Contains(referer, "://") {
referer = conf.Wide.Server + referer
}
if strings.HasSuffix(referer, "/") {
referer = referer[:len(referer)-1]
}
referer += "__1"
state := util.Rand.String(16) + referer
states[state] = state
path := loginAuthURL + "?client_id=" + clientId + "&state=" + state + "&scope=public_repo,read:user,user:follow"
logger.Infof("redirect to github [" + path + "]")
http.Redirect(w, r, path, http.StatusSeeOther)
}
func GithubCallbackHandler(w http.ResponseWriter, r *http.Request) {
logger.Infof("Github callback [" + r.URL.String() + "]")
state := r.URL.Query().Get("state")
if _, exist := states[state]; !exist {
http.Error(w, "Get state param failed", http.StatusBadRequest)
return
}
delete(states, state)
referer := state[16:]
if strings.Contains(referer, "__0") || strings.Contains(referer, "__1") {
referer = referer[:len(referer)-len("__0")]
}
accessToken := r.URL.Query().Get("ak")
githubUser := GitHubUserInfo(accessToken)
if nil == githubUser {
logger.Warnf("Can not get user info with token [" + accessToken + "]")
http.Error(w, "Get user info failed", http.StatusUnauthorized)
return
}
githubId := githubUser["userId"].(string)
userName := githubUser["userName"].(string)
avatar := githubUser["userAvatarURL"].(string)
result := util.NewResult()
defer util.RetResult(w, r, result)
user := conf.GetUser(githubId)
if nil == user {
msg := addUser(githubId, userName, avatar)
if userCreated != msg {
result.Succ = false
result.Msg = msg
return
}
}
// create a HTTP session
httpSession, _ := HTTPSession.Get(r, CookieName)
httpSession.Values["uid"] = githubId
httpSession.Values["id"] = strconv.Itoa(rand.Int())
httpSession.Options.MaxAge = conf.Wide.HTTPSessionMaxAge
httpSession.Save(r, w)
logger.Debugf("Created a HTTP session [%s] for user [%s]", httpSession.Values["id"].(string), githubId)
}
// GitHubUserInfo returns GitHub user info specified by the given access token.
func GitHubUserInfo(accessToken string) (ret map[string]interface{}) {
result := map[string]interface{}{}
response, data, errors := gorequest.New().TLSClientConfig(&tls.Config{InsecureSkipVerify: true}).
Get("https://hacpai.com/github/user?ak="+accessToken).Timeout(7*time.Second).
Set("User-Agent", conf.UserAgent).EndStruct(&result)
if nil != errors || http.StatusOK != response.StatusCode {
logger.Errorf("Get github user info failed: %+v, %s", errors, data)
return nil
}
if 0 != result["sc"].(float64) {
return nil
}
return result["data"].(map[string]interface{})
}
// LogoutHandler handles request of user logout (exit).
func LogoutHandler(w http.ResponseWriter, r *http.Request) {
result := util.NewResult()
defer util.RetResult(w, r, result)
httpSession, _ := HTTPSession.Get(r, CookieName)
httpSession.Options.MaxAge = -1
httpSession.Save(r, w)
}
// addUser add a user with the specified user id, username and avatar.
//
// 1. create the user's workspace
// 2. generate 'Hello, 世界' demo code in the workspace (a console version and a HTTP version)
// 3. update the user customized configurations, such as style.css
// 4. serve files of the user's workspace via HTTP
//
// Note: user [playground] is a reserved mock user
func addUser(userId, userName, userAvatar string) string {
if "playground" == userId {
return userExists
}
addUserMutex.Lock()
defer addUserMutex.Unlock()
for _, user := range conf.Users {
if strings.ToLower(user.Id) == strings.ToLower(userId) {
return userExists
}
}
workspace := filepath.Join(conf.Wide.Data, "workspaces", userId)
newUser := conf.NewUser(userId, userName, userAvatar, workspace)
conf.Users = append(conf.Users, newUser)
if !newUser.Save() {
return userCreateError
}
conf.CreateWorkspaceDir(workspace)
helloWorld(workspace)
conf.UpdateCustomizedConf(userId)
logger.Infof("Created a user [%s]", userId)
return userCreated
}
// helloWorld generates the 'Hello, 世界' source code.
// 1. src/hello/main.go
// 2. src/web/main.go
func helloWorld(workspace string) {
consoleHello(workspace)
webHello(workspace)
}
func consoleHello(workspace string) {
dir := workspace + conf.PathSeparator + "src" + conf.PathSeparator + "hello"
if err := os.MkdirAll(dir, 0755); nil != err {
logger.Error(err)
return
}
fout, err := os.Create(dir + conf.PathSeparator + "main.go")
if nil != err {
logger.Error(err)
return
}
fout.WriteString(conf.HelloWorld)
fout.Close()
}
func webHello(workspace string) {
dir := workspace + conf.PathSeparator + "src" + conf.PathSeparator + "web"
if err := os.MkdirAll(dir, 0755); nil != err {
logger.Error(err)
return
}
fout, err := os.Create(dir + conf.PathSeparator + "main.go")
if nil != err {
logger.Error(err)
return
}
code := `package main
import (
"fmt"
"math/rand"
"net/http"
"strconv"
"time"
)
func main() {
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("Hello, 世界"))
})
port := getPort()
// you may need to change the address
fmt.Println("Open https://wide.b3log.org:" + port + " in your browser to see the result")
if err := http.ListenAndServe(":"+port, nil); nil != err {
fmt.Println(err)
}
}
func getPort() string {
rand.Seed(time.Now().UnixNano())
return strconv.Itoa(7000 + rand.Intn(8000-7000))
}
`
fout.WriteString(code)
fout.Close()
}

View File

@ -1,10 +1,10 @@
// Copyright (c) 2014-2017, b3log.org & hacpai.com // Copyright (c) 2014-2019, b3log.org & hacpai.com
// //
// Licensed under the Apache License, Version 2.0 (the "License"); // Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License. // you may not use this file except in compliance with the License.
// You may obtain a copy of the License at // You may obtain a copy of the License at
// //
// http://www.apache.org/licenses/LICENSE-2.0 // https://www.apache.org/licenses/LICENSE-2.0
// //
// Unless required by applicable law or agreed to in writing, software // Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS, // distributed under the License is distributed on an "AS IS" BASIS,
@ -31,6 +31,7 @@ import (
"path/filepath" "path/filepath"
"sort" "sort"
"strconv" "strconv"
"strings"
"sync" "sync"
"time" "time"
@ -38,14 +39,16 @@ import (
"github.com/b3log/wide/event" "github.com/b3log/wide/event"
"github.com/b3log/wide/log" "github.com/b3log/wide/log"
"github.com/b3log/wide/util" "github.com/b3log/wide/util"
"github.com/go-fsnotify/fsnotify" "github.com/fsnotify/fsnotify"
"github.com/gorilla/sessions" "github.com/gorilla/sessions"
"github.com/gorilla/websocket" "github.com/gorilla/websocket"
) )
const ( const (
sessionStateActive = iota sessionStateActive = iota
sessionStateClosed // (not used so far) sessionStateClosed // (not used so far)
CookieName = "wide-sess"
) )
// Logger. // Logger.
@ -74,7 +77,7 @@ var HTTPSession = sessions.NewCookieStore([]byte("BEYOND"))
// WideSession represents a session associated with a browser tab. // WideSession represents a session associated with a browser tab.
type WideSession struct { type WideSession struct {
ID string // id ID string // id
Username string // username UserId string // user id
HTTPSession *sessions.Session // HTTP session related HTTPSession *sessions.Session // HTTP session related
Processes []*os.Process // process set Processes []*os.Process // process set
EventQueue *event.UserEventQueue // event queue EventQueue *event.UserEventQueue // event queue
@ -110,7 +113,7 @@ func FixedTimeRelease() {
for _, s := range WideSessions { for _, s := range WideSessions {
if s.Updated.Before(threshold) { if s.Updated.Before(threshold) {
logger.Debugf("Removes a invalid session [%s], user [%s]", s.ID, s.Username) logger.Debugf("Removes a invalid session [%s], user [%s]", s.ID, s.UserId)
WideSessions.Remove(s.ID) WideSessions.Remove(s.ID)
} }
@ -121,7 +124,7 @@ func FixedTimeRelease() {
// Online user statistic report. // Online user statistic report.
type userReport struct { type userReport struct {
username string userId string
sessionCnt int sessionCnt int
processCnt int processCnt int
updated time.Time updated time.Time
@ -129,7 +132,7 @@ type userReport struct {
// report returns a online user statistics in pretty format. // report returns a online user statistics in pretty format.
func (u *userReport) report() string { func (u *userReport) report() string {
return "[" + u.username + "] has [" + strconv.Itoa(u.sessionCnt) + "] sessions and [" + strconv.Itoa(u.processCnt) + return "[" + u.userId + "] has [" + strconv.Itoa(u.sessionCnt) + "] sessions and [" + strconv.Itoa(u.processCnt) +
"] running processes, latest activity [" + u.updated.Format("2006-01-02 15:04:05") + "]" "] running processes, latest activity [" + u.updated.Format("2006-01-02 15:04:05") + "]"
} }
@ -138,7 +141,7 @@ func FixedTimeReport() {
go func() { go func() {
defer util.Recover() defer util.Recover()
for _ = range time.Tick(10 * time.Minute) { for _ = range time.Tick(10*time.Minute) {
users := userReports{} users := userReports{}
processSum := 0 processSum := 0
@ -146,7 +149,7 @@ func FixedTimeReport() {
processCnt := len(s.Processes) processCnt := len(s.Processes)
processSum += processCnt processSum += processCnt
if report, exists := contains(users, s.Username); exists { if report, exists := contains(users, s.UserId); exists {
if s.Updated.After(report.updated) { if s.Updated.After(report.updated) {
report.updated = s.Updated report.updated = s.Updated
} }
@ -154,7 +157,7 @@ func FixedTimeReport() {
report.sessionCnt++ report.sessionCnt++
report.processCnt += processCnt report.processCnt += processCnt
} else { } else {
users = append(users, &userReport{username: s.Username, sessionCnt: 1, processCnt: processCnt, updated: s.Updated}) users = append(users, &userReport{userId: s.UserId, sessionCnt: 1, processCnt: processCnt, updated: s.Updated})
} }
} }
@ -173,9 +176,9 @@ func FixedTimeReport() {
}() }()
} }
func contains(reports []*userReport, username string) (*userReport, bool) { func contains(reports []*userReport, userId string) (*userReport, bool) {
for _, ur := range reports { for _, ur := range reports {
if username == ur.username { if userId == ur.userId {
return ur, true return ur, true
} }
} }
@ -219,7 +222,7 @@ func WSHandler(w http.ResponseWriter, r *http.Request) {
wSession := WideSessions.Get(sid) wSession := WideSessions.Get(sid)
if nil == wSession { if nil == wSession {
httpSession, _ := HTTPSession.Get(r, "wide-session") httpSession, _ := HTTPSession.Get(r, CookieName)
if httpSession.IsNew { if httpSession.IsNew {
return return
@ -230,7 +233,7 @@ func WSHandler(w http.ResponseWriter, r *http.Request) {
wSession = WideSessions.new(httpSession, sid) wSession = WideSessions.new(httpSession, sid)
logger.Tracef("Created a wide session [%s] for websocket reconnecting, user [%s]", sid, wSession.Username) logger.Tracef("Created a wide session [%s] for websocket reconnecting, user [%s]", sid, wSession.UserId)
} }
logger.Tracef("Open a new [Session Channel] with session [%s], %d", sid, len(SessionWS)) logger.Tracef("Open a new [Session Channel] with session [%s], %d", sid, len(SessionWS))
@ -262,7 +265,7 @@ func WSHandler(w http.ResponseWriter, r *http.Request) {
for { for {
if err := wsChan.ReadJSON(&input); err != nil { if err := wsChan.ReadJSON(&input); err != nil {
logger.Tracef("[Session Channel] of session [%s] disconnected, releases all resources with it, user [%s]", sid, wSession.Username) logger.Tracef("[Session Channel] of session [%s] disconnected, releases all resources with it, user [%s]", sid, wSession.UserId)
return return
} }
@ -306,7 +309,7 @@ func SaveContentHandler(w http.ResponseWriter, r *http.Request) {
wSession.Content = args.LatestSessionContent wSession.Content = args.LatestSessionContent
for _, user := range conf.Users { for _, user := range conf.Users {
if user.Name == wSession.Username { if user.Id == wSession.UserId {
// update the variable in-memory, session.FixedTimeSave() function will persist it periodically // update the variable in-memory, session.FixedTimeSave() function will persist it periodically
user.LatestSessionContent = wSession.Content user.LatestSessionContent = wSession.Content
@ -375,9 +378,9 @@ func (sessions *wSessions) Remove(sid string) {
// kill processes // kill processes
for _, p := range s.Processes { for _, p := range s.Processes {
if err := p.Kill(); nil != err { if err := p.Kill(); nil != err {
logger.Errorf("Can't kill process [%d] of session [%s], user [%s]", p.Pid, sid, s.Username) logger.Errorf("Can't kill process [%d] of session [%s], user [%s]", p.Pid, sid, s.UserId)
} else { } else {
logger.Debugf("Killed a process [%d] of session [%s], user [%s]", p.Pid, sid, s.Username) logger.Debugf("Killed a process [%d] of session [%s], user [%s]", p.Pid, sid, s.UserId)
} }
} }
@ -409,12 +412,12 @@ func (sessions *wSessions) Remove(sid string) {
cnt := 0 // count wide sessions associated with HTTP session cnt := 0 // count wide sessions associated with HTTP session
for _, ses := range *sessions { for _, ses := range *sessions {
if ses.Username == s.Username { if ses.UserId == s.UserId {
cnt++ cnt++
} }
} }
logger.Debugf("Removed a session [%s] of user [%s], it has [%d] sessions currently", sid, s.Username, cnt) logger.Debugf("Removed a session [%s] of user [%s], it has [%d] sessions currently", sid, s.UserId, cnt)
return return
} }
@ -422,14 +425,14 @@ func (sessions *wSessions) Remove(sid string) {
} }
// GetByUsername gets wide sessions. // GetByUsername gets wide sessions.
func (sessions *wSessions) GetByUsername(username string) []*WideSession { func (sessions *wSessions) GetByUserId(userId string) []*WideSession {
mutex.Lock() mutex.Lock()
defer mutex.Unlock() defer mutex.Unlock()
ret := []*WideSession{} ret := []*WideSession{}
for _, s := range *sessions { for _, s := range *sessions {
if s.Username == username { if s.UserId == userId {
ret = append(ret, s) ret = append(ret, s)
} }
} }
@ -442,12 +445,12 @@ func (sessions *wSessions) new(httpSession *sessions.Session, sid string) *WideS
mutex.Lock() mutex.Lock()
defer mutex.Unlock() defer mutex.Unlock()
username := httpSession.Values["username"].(string) uid := httpSession.Values["uid"].(string)
now := time.Now() now := time.Now()
ret := &WideSession{ ret := &WideSession{
ID: sid, ID: sid,
Username: username, UserId: uid,
HTTPSession: httpSession, HTTPSession: httpSession,
EventQueue: nil, EventQueue: nil,
State: sessionStateActive, State: sessionStateActive,
@ -458,7 +461,7 @@ func (sessions *wSessions) new(httpSession *sessions.Session, sid string) *WideS
*sessions = append(*sessions, ret) *sessions = append(*sessions, ret)
if "playground" == username { if "playground" == uid {
return ret return ret
} }
@ -479,7 +482,7 @@ func (sessions *wSessions) new(httpSession *sessions.Session, sid string) *WideS
for { for {
ch := SessionWS[sid] ch := SessionWS[sid]
if nil == ch { if nil == ch {
return // release this gorutine return // release this goroutine
} }
select { select {
@ -505,17 +508,13 @@ func (sessions *wSessions) new(httpSession *sessions.Session, sid string) *WideS
} }
} }
cmd := map[string]interface{}{"path": path, "dir": dir, cmd := map[string]interface{}{"path": path, "dir": dir, "cmd": "create-file", "type": fileType}
"cmd": "create-file", "type": fileType}
ch.WriteJSON(&cmd) ch.WriteJSON(&cmd)
} else if event.Op&fsnotify.Remove == fsnotify.Remove { } else if event.Op&fsnotify.Remove == fsnotify.Remove {
cmd := map[string]interface{}{"path": path, "dir": dir, cmd := map[string]interface{}{"path": path, "dir": dir, "cmd": "remove-file", "type": ""}
"cmd": "remove-file", "type": ""}
ch.WriteJSON(&cmd) ch.WriteJSON(&cmd)
} else if event.Op&fsnotify.Rename == fsnotify.Rename { } else if event.Op&fsnotify.Rename == fsnotify.Rename {
cmd := map[string]interface{}{"path": path, "dir": dir, cmd := map[string]interface{}{"path": path, "dir": dir, "cmd": "rename-file", "type": ""}
"cmd": "rename-file", "type": ""}
ch.WriteJSON(&cmd) ch.WriteJSON(&cmd)
} }
case err := <-watcher.Errors: case err := <-watcher.Errors:
@ -529,10 +528,10 @@ func (sessions *wSessions) new(httpSession *sessions.Session, sid string) *WideS
go func() { go func() {
defer util.Recover() defer util.Recover()
workspaces := filepath.SplitList(conf.GetUserWorkspace(username)) workspaces := filepath.SplitList(conf.GetUserWorkspace(uid))
for _, workspace := range workspaces { for _, workspace := range workspaces {
filepath.Walk(filepath.Join(workspace, "src"), func(dirPath string, f os.FileInfo, err error) error { filepath.Walk(filepath.Join(workspace, "src"), func(dirPath string, f os.FileInfo, err error) error {
if ".git" == f.Name() { // XXX: discard other unconcered dirs if strings.HasPrefix(f.Name(), ".") || "node_modules" == f.Name() || "vendor" == f.Name() {
return filepath.SkipDir return filepath.SkipDir
} }

View File

@ -1,10 +1,10 @@
// Copyright (c) 2014-2017, b3log.org & hacpai.com // Copyright (c) 2014-2019, b3log.org & hacpai.com
// //
// Licensed under the Apache License, Version 2.0 (the "License"); // Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License. // you may not use this file except in compliance with the License.
// You may obtain a copy of the License at // You may obtain a copy of the License at
// //
// http://www.apache.org/licenses/LICENSE-2.0 // https://www.apache.org/licenses/LICENSE-2.0
// //
// Unless required by applicable law or agreed to in writing, software // Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS, // distributed under the License is distributed on an "AS IS" BASIS,
@ -15,15 +15,10 @@
package session package session
import ( import (
"crypto/md5"
"encoding/hex"
"encoding/json" "encoding/json"
"math/rand"
"net/http" "net/http"
"os"
"path/filepath" "path/filepath"
"runtime" "runtime"
"strconv"
"strings" "strings"
"sync" "sync"
"text/template" "text/template"
@ -38,10 +33,8 @@ const (
// TODO: i18n // TODO: i18n
userExists = "user exists" userExists = "user exists"
emailExists = "email exists"
userCreated = "user created" userCreated = "user created"
userCreateError = "user create error" userCreateError = "user create error"
notAllowRegister = "not allow register"
) )
// Exclusive lock for adding user. // Exclusive lock for adding user.
@ -49,22 +42,19 @@ var addUserMutex sync.Mutex
// PreferenceHandler handles request of preference page. // PreferenceHandler handles request of preference page.
func PreferenceHandler(w http.ResponseWriter, r *http.Request) { func PreferenceHandler(w http.ResponseWriter, r *http.Request) {
httpSession, _ := HTTPSession.Get(r, "wide-session") httpSession, _ := HTTPSession.Get(r, CookieName)
if httpSession.IsNew { if httpSession.IsNew {
http.Redirect(w, r, conf.Wide.Context+"/login", http.StatusFound) http.Redirect(w, r, "/login", http.StatusFound)
return return
} }
httpSession.Options.MaxAge = conf.Wide.HTTPSessionMaxAge httpSession.Options.MaxAge = conf.Wide.HTTPSessionMaxAge
if "" != conf.Wide.Context {
httpSession.Options.Path = conf.Wide.Context
}
httpSession.Save(r, w) httpSession.Save(r, w)
username := httpSession.Values["username"].(string) uid := httpSession.Values["uid"].(string)
user := conf.GetUser(username) user := conf.GetUser(uid)
if "GET" == r.Method { if "GET" == r.Method {
tmpLinux := user.GoBuildArgsForLinux tmpLinux := user.GoBuildArgsForLinux
@ -116,8 +106,6 @@ func PreferenceHandler(w http.ResponseWriter, r *http.Request) {
Keymap string Keymap string
Workspace string Workspace string
Username string Username string
Password string
Email string
Locale string Locale string
Theme string Theme string
EditorFontFamily string EditorFontFamily string
@ -143,14 +131,6 @@ func PreferenceHandler(w http.ResponseWriter, r *http.Request) {
user.Keymap = args.Keymap user.Keymap = args.Keymap
// XXX: disallow change workspace at present // XXX: disallow change workspace at present
// user.Workspace = args.Workspace // user.Workspace = args.Workspace
if user.Password != args.Password {
user.Password = conf.Salt(args.Password, user.Salt)
}
user.Email = args.Email
hash := md5.New()
hash.Write([]byte(user.Email))
user.Gravatar = hex.EncodeToString(hash.Sum(nil))
user.Locale = args.Locale user.Locale = args.Locale
user.Theme = args.Theme user.Theme = args.Theme
@ -160,7 +140,7 @@ func PreferenceHandler(w http.ResponseWriter, r *http.Request) {
user.Editor.Theme = args.EditorTheme user.Editor.Theme = args.EditorTheme
user.Editor.TabSize = args.EditorTabSize user.Editor.TabSize = args.EditorTabSize
conf.UpdateCustomizedConf(username) conf.UpdateCustomizedConf(uid)
now := time.Now().UnixNano() now := time.Now().UnixNano()
user.Lived = now user.Lived = now
@ -169,137 +149,6 @@ func PreferenceHandler(w http.ResponseWriter, r *http.Request) {
result.Succ = user.Save() result.Succ = user.Save()
} }
// LoginHandler handles request of user login.
func LoginHandler(w http.ResponseWriter, r *http.Request) {
if "GET" == r.Method {
// show the login page
model := map[string]interface{}{"conf": conf.Wide, "i18n": i18n.GetAll(conf.Wide.Locale),
"locale": conf.Wide.Locale, "ver": conf.WideVersion, "year": time.Now().Year()}
t, err := template.ParseFiles("views/login.html")
if nil != err {
logger.Error(err)
http.Error(w, err.Error(), 500)
return
}
t.Execute(w, model)
return
}
// non-GET request as login request
result := util.NewResult()
defer util.RetResult(w, r, result)
args := struct {
Username string
Password string
}{}
args.Username = r.FormValue("username")
args.Password = r.FormValue("password")
result.Succ = false
for _, user := range conf.Users {
if user.Name == args.Username && user.Password == conf.Salt(args.Password, user.Salt) {
result.Succ = true
break
}
}
if !result.Succ {
return
}
// create a HTTP session
httpSession, _ := HTTPSession.Get(r, "wide-session")
httpSession.Values["username"] = args.Username
httpSession.Values["id"] = strconv.Itoa(rand.Int())
httpSession.Options.MaxAge = conf.Wide.HTTPSessionMaxAge
if "" != conf.Wide.Context {
httpSession.Options.Path = conf.Wide.Context
}
httpSession.Save(r, w)
logger.Debugf("Created a HTTP session [%s] for user [%s]", httpSession.Values["id"].(string), args.Username)
}
// LogoutHandler handles request of user logout (exit).
func LogoutHandler(w http.ResponseWriter, r *http.Request) {
result := util.NewResult()
defer util.RetResult(w, r, result)
httpSession, _ := HTTPSession.Get(r, "wide-session")
httpSession.Options.MaxAge = -1
httpSession.Save(r, w)
}
// SignUpUserHandler handles request of registering user.
func SignUpUserHandler(w http.ResponseWriter, r *http.Request) {
if "GET" == r.Method {
// show the user sign up page
model := map[string]interface{}{"conf": conf.Wide, "i18n": i18n.GetAll(conf.Wide.Locale),
"locale": conf.Wide.Locale, "ver": conf.WideVersion, "dir": conf.Wide.UsersWorkspaces,
"pathSeparator": conf.PathSeparator, "year": time.Now().Year()}
t, err := template.ParseFiles("views/sign_up.html")
if nil != err {
logger.Error(err)
http.Error(w, err.Error(), 500)
return
}
t.Execute(w, model)
return
}
// non-GET request as add user request
result := util.NewResult()
defer util.RetResult(w, r, result)
var args map[string]interface{}
if err := json.NewDecoder(r.Body).Decode(&args); err != nil {
logger.Error(err)
result.Succ = false
return
}
username := args["username"].(string)
password := args["password"].(string)
email := args["email"].(string)
msg := addUser(username, password, email)
if userCreated != msg {
result.Succ = false
result.Msg = msg
return
}
// create a HTTP session
httpSession, _ := HTTPSession.Get(r, "wide-session")
httpSession.Values["username"] = username
httpSession.Values["id"] = strconv.Itoa(rand.Int())
httpSession.Options.MaxAge = conf.Wide.HTTPSessionMaxAge
if "" != conf.Wide.Context {
httpSession.Options.Path = conf.Wide.Context
}
httpSession.Save(r, w)
}
// FixedTimeSave saves online users' configurations periodically (1 minute). // FixedTimeSave saves online users' configurations periodically (1 minute).
// //
// Main goal of this function is to save user session content, for restoring session content while user open Wide next time. // Main goal of this function is to save user session content, for restoring session content while user open Wide next time.
@ -313,11 +162,11 @@ func FixedTimeSave() {
}() }()
} }
// CanAccess determines whether the user specified by the given username can access the specified path. // CanAccess determines whether the user specified by the given user id can access the specified path.
func CanAccess(username, path string) bool { func CanAccess(userId, path string) bool {
path = filepath.FromSlash(path) path = filepath.FromSlash(path)
userWorkspace := conf.GetUserWorkspace(username) userWorkspace := conf.GetUserWorkspace(userId)
workspaces := filepath.SplitList(userWorkspace) workspaces := filepath.SplitList(userWorkspace)
for _, workspace := range workspaces { for _, workspace := range workspaces {
@ -342,20 +191,20 @@ func SaveOnlineUsers() {
func getOnlineUsers() []*conf.User { func getOnlineUsers() []*conf.User {
ret := []*conf.User{} ret := []*conf.User{}
usernames := map[string]string{} // distinct username uids := map[string]string{} // distinct uid
for _, s := range WideSessions { for _, s := range WideSessions {
usernames[s.Username] = s.Username uids[s.UserId] = s.UserId
} }
for _, username := range usernames { for _, uid := range uids {
u := conf.GetUser(username) u := conf.GetUser(uid)
if "playground" == username { // user [playground] is a reserved mock user if "playground" == uid { // user [playground] is a reserved mock user
continue continue
} }
if nil == u { if nil == u {
logger.Warnf("Not found user [%s]", username) logger.Warnf("Not found user [%s]", uid)
continue continue
} }
@ -365,135 +214,3 @@ func getOnlineUsers() []*conf.User {
return ret return ret
} }
// addUser add a user with the specified username, password and email.
//
// 1. create the user's workspace
// 2. generate 'Hello, 世界' demo code in the workspace (a console version and a HTTP version)
// 3. update the user customized configurations, such as style.css
// 4. serve files of the user's workspace via HTTP
//
// Note: user [playground] is a reserved mock user
func addUser(username, password, email string) string {
if !conf.Wide.AllowRegister {
return notAllowRegister
}
if "playground" == username {
return userExists
}
addUserMutex.Lock()
defer addUserMutex.Unlock()
for _, user := range conf.Users {
if strings.ToLower(user.Name) == strings.ToLower(username) {
return userExists
}
if strings.ToLower(user.Email) == strings.ToLower(email) {
return emailExists
}
}
workspace := filepath.Join(conf.Wide.UsersWorkspaces, username)
newUser := conf.NewUser(username, password, email, workspace)
conf.Users = append(conf.Users, newUser)
if !newUser.Save() {
return userCreateError
}
conf.CreateWorkspaceDir(workspace)
helloWorld(workspace)
conf.UpdateCustomizedConf(username)
http.Handle("/workspace/"+username+"/",
http.StripPrefix("/workspace/"+username+"/", http.FileServer(http.Dir(newUser.WorkspacePath()))))
logger.Infof("Created a user [%s]", username)
return userCreated
}
// helloWorld generates the 'Hello, 世界' source code.
// 1. src/hello/main.go
// 2. src/web/main.go
func helloWorld(workspace string) {
consoleHello(workspace)
webHello(workspace)
}
func consoleHello(workspace string) {
dir := workspace + conf.PathSeparator + "src" + conf.PathSeparator + "hello"
if err := os.MkdirAll(dir, 0755); nil != err {
logger.Error(err)
return
}
fout, err := os.Create(dir + conf.PathSeparator + "main.go")
if nil != err {
logger.Error(err)
return
}
fout.WriteString(conf.HelloWorld)
fout.Close()
}
func webHello(workspace string) {
dir := workspace + conf.PathSeparator + "src" + conf.PathSeparator + "web"
if err := os.MkdirAll(dir, 0755); nil != err {
logger.Error(err)
return
}
fout, err := os.Create(dir + conf.PathSeparator + "main.go")
if nil != err {
logger.Error(err)
return
}
code := `package main
import (
"fmt"
"math/rand"
"net/http"
"strconv"
"time"
)
func main() {
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("Hello, 世界"))
})
port := getPort()
// you may need to change the address
fmt.Println("Open http://wide.b3log.org:" + port + " in your browser to see the result")
if err := http.ListenAndServe(":"+port, nil); nil != err {
fmt.Println(err)
}
}
func getPort() string {
rand.Seed(time.Now().UnixNano())
return strconv.Itoa(7000 + rand.Intn(8000-7000))
}
`
fout.WriteString(code)
fout.Close()
}

View File

@ -1,189 +0,0 @@
// Copyright (c) 2014-2017, b3log.org & hacpai.com
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// Package shell include shell related mainipulations.
package shell
import (
"html/template"
"net/http"
"os"
"os/exec"
"runtime"
"strings"
"time"
"github.com/b3log/wide/conf"
"github.com/b3log/wide/i18n"
"github.com/b3log/wide/log"
"github.com/b3log/wide/session"
"github.com/b3log/wide/util"
"github.com/gorilla/websocket"
)
// Shell channel.
//
// <sid, *util.WSChannel>>
var ShellWS = map[string]*util.WSChannel{}
// Logger.
var logger = log.NewLogger(os.Stdout)
// IndexHandler handles request of Shell index.
func IndexHandler(w http.ResponseWriter, r *http.Request) {
httpSession, _ := session.HTTPSession.Get(r, "wide-session")
if httpSession.IsNew {
http.Redirect(w, r, conf.Wide.Context+"/login", http.StatusFound)
return
}
httpSession.Options.MaxAge = conf.Wide.HTTPSessionMaxAge
if "" != conf.Wide.Context {
httpSession.Options.Path = conf.Wide.Context
}
httpSession.Save(r, w)
username := httpSession.Values["username"].(string)
locale := conf.GetUser(username).Locale
model := map[string]interface{}{"conf": conf.Wide, "i18n": i18n.GetAll(locale), "locale": locale,
"sid": session.WideSessions.GenId()}
wideSessions := session.WideSessions.GetByUsername(username)
logger.Tracef("User [%s] has [%d] sessions", username, len(wideSessions))
t, err := template.ParseFiles("views/shell.html")
if nil != err {
logger.Error(err)
http.Error(w, err.Error(), 500)
return
}
t.Execute(w, model)
}
// WSHandler handles request of creating Shell channel.
func WSHandler(w http.ResponseWriter, r *http.Request) {
httpSession, _ := session.HTTPSession.Get(r, "wide-session")
if httpSession.IsNew {
http.Error(w, "Forbidden", http.StatusForbidden)
return
}
username := httpSession.Values["username"].(string)
sid := r.URL.Query()["sid"][0]
conn, _ := websocket.Upgrade(w, r, nil, 1024, 1024)
wsChan := util.WSChannel{Sid: sid, Conn: conn, Request: r, Time: time.Now()}
ret := map[string]interface{}{"output": "Shell initialized", "cmd": "init-shell"}
err := wsChan.WriteJSON(&ret)
if nil != err {
return
}
ShellWS[sid] = &wsChan
logger.Debugf("Open a new [Shell] with session [%s], %d", sid, len(ShellWS))
input := map[string]interface{}{}
for {
if err := wsChan.ReadJSON(&input); err != nil {
logger.Error("Shell WS ERROR: " + err.Error())
return
}
inputCmd := input["cmd"].(string)
cmds := strings.Split(inputCmd, "|")
commands := []*exec.Cmd{}
for _, cmdWithArgs := range cmds {
cmdWithArgs = strings.TrimSpace(cmdWithArgs)
cmdWithArgs := strings.Split(cmdWithArgs, " ")
args := []string{}
if len(cmdWithArgs) > 1 {
args = cmdWithArgs[1:]
}
cmd := exec.Command(cmdWithArgs[0], args...)
commands = append(commands, cmd)
}
output := ""
if !strings.Contains(inputCmd, "clear") {
output = pipeCommands(username, commands...)
}
ret = map[string]interface{}{"output": output, "cmd": "shell-output"}
if err := wsChan.WriteJSON(&ret); err != nil {
logger.Error("Shell WS ERROR: " + err.Error())
return
}
wsChan.Refresh()
}
}
func pipeCommands(username string, commands ...*exec.Cmd) string {
for i, command := range commands[:len(commands)-1] {
setCmdEnv(command, username)
stdout, err := command.StdoutPipe()
if nil != err {
return err.Error()
}
command.Start()
commands[i+1].Stdin = stdout
}
last := commands[len(commands)-1]
setCmdEnv(last, username)
out, err := last.CombinedOutput()
// release resources
for _, command := range commands[:len(commands)-1] {
command.Wait()
}
if err != nil {
return err.Error()
}
return string(out)
}
func setCmdEnv(cmd *exec.Cmd, username string) {
userWorkspace := conf.GetUserWorkspace(username)
cmd.Env = append(cmd.Env,
"TERM="+os.Getenv("TERM"),
"GOPATH="+userWorkspace,
"GOOS="+runtime.GOOS,
"GOARCH="+runtime.GOARCH,
"GOROOT="+runtime.GOROOT(),
"PATH="+os.Getenv("PATH"))
cmd.Dir = userWorkspace
}

View File

@ -1,11 +1,11 @@
/* /*
* Copyright (c) 2014-2017, b3log.org & hacpai.com * Copyright (c) 2014-2019, b3log.org & hacpai.com
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
* You may obtain a copy of the License at * You may obtain a copy of the License at
* *
* http://www.apache.org/licenses/LICENSE-2.0 * https://www.apache.org/licenses/LICENSE-2.0
* *
* Unless required by applicable law or agreed to in writing, software * Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, * distributed under the License is distributed on an "AS IS" BASIS,

View File

@ -1,11 +1,11 @@
/* /*
* Copyright (c) 2014-2017, b3log.org & hacpai.com * Copyright (c) 2014-2019, b3log.org & hacpai.com
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
* You may obtain a copy of the License at * You may obtain a copy of the License at
* *
* http://www.apache.org/licenses/LICENSE-2.0 * https://www.apache.org/licenses/LICENSE-2.0
* *
* Unless required by applicable law or agreed to in writing, software * Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, * distributed under the License is distributed on an "AS IS" BASIS,
@ -18,7 +18,7 @@
* themes for base. * themes for base.
* *
* @author <a href="http://vanessa.b3log.org">Liyuan Li</a> * @author <a href="http://vanessa.b3log.org">Liyuan Li</a>
* @version 0.1.0.0, Dec 6, 2015 * @version 0.2.0.0, Oct 5, 2018
*/ */
/* start reset & function */ /* start reset & function */
::-webkit-scrollbar { ::-webkit-scrollbar {
@ -140,18 +140,24 @@ button {
/* start icon */ /* start icon */
@font-face { @font-face {
font-family: 'icomoon'; font-family: 'icomoon';
src:url('fonts/icomoon.eot?35cb2z'); src: url('fonts/icomoon.eot?lqk80d');
src:url('fonts/icomoon.eot?#iefix35cb2z') format('embedded-opentype'), src: url('fonts/icomoon.eot?lqk80d#iefix') format('embedded-opentype'),
url('fonts/icomoon.woff?35cb2z') format('woff'), url('fonts/icomoon.ttf?lqk80d') format('truetype'),
url('fonts/icomoon.ttf?35cb2z') format('truetype'), url('fonts/icomoon.woff?lqk80d') format('woff'),
url('fonts/icomoon.svg?35cb2z#icomoon') format('svg'); url('fonts/icomoon.svg?lqk80d#icomoon') format('svg');
font-weight: normal; font-weight: normal;
font-style: normal; font-style: normal;
} }
.font-ico, [class^="ico-"], [class*=" ico-"] {
[class^="ico-"] { /* use !important to prevent issues with browser extensions that change fonts */
font-family: 'icomoon'; font-family: 'icomoon' !important;
speak: none;
font-style: normal;
font-weight: normal;
font-variant: normal;
text-transform: none;
/* Better Font Rendering =========== */ /* Better Font Rendering =========== */
-webkit-font-smoothing: antialiased; -webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale; -moz-osx-font-smoothing: grayscale;
@ -160,177 +166,143 @@ button {
line-height: 20px; line-height: 20px;
} }
.ico-book:before { .ico-qqz:before {
content: "\e623"; content: "\e900";
} }
.ico-price:before {
content: "\e616";
}
.ico-start:before {
content: "\e9d7";
}
.ico-share:before {
content: "\e61f";
}
.ico-github:before {
content: "\f00a";
}
.ico-git:before {
content: "\e624";
}
.ico-tencent:before {
content: "\e622";
}
.ico-weibo:before {
content: "\e621";
}
.ico-googleplus:before {
content: "\e61a";
}
.ico-twitter:before {
content: "\e61c";
}
.ico-email:before {
content: "\e619";
}
.ico-facebook:before {
content: "\e61b";
}
.ico-moveup:before {
content: "\f148";
}
.ico-movedown:before {
content: "\f149";
}
.ico-keyboard:before {
content: "\f11c";
}
.ico-findfiles:before {
content: "\e603";
}
.ico-find:before { .ico-find:before {
content: "\e602"; content: "\e602";
} }
.ico-findfiles:before {
content: "\e603";
}
.ico-editor:before { .ico-editor:before {
content: "\e604"; content: "\e604";
} }
.ico-tree:before {
content: "\e600";
}
.ico-build:before {
content: "\e601";
}
.ico-notification:before { .ico-notification:before {
content: "\e607"; content: "\e607";
} }
.ico-price:before {
content: "\e616";
}
.ico-report:before { .ico-report:before {
content: "\e605"; content: "\e605";
} }
.ico-comment:before {
content: "\e620";
}
.ico-goline:before {
content: "\e61e";
}
.ico-info:before {
content: "\e61d";
}
.ico-signup:before { .ico-signup:before {
content: "\e606"; content: "\e606";
} }
.ico-git:before {
.ico-signout:before { content: "\e624";
content: "\e618";
} }
.ico-book:before {
.ico-redo:before { content: "\e623";
content: "\e615";
} }
.ico-start:before {
.ico-undo:before { content: "\e9d7";
content: "\e60e"; text-shadow: 0 0 rgba(0, 0, 0, 0.4);
} }
.ico-tree:before {
.ico-about:before { content: "\e600";
content: "\e60d";
} }
.ico-build:before {
.ico-import:before { content: "\e601";
content: "\f0ee";
} }
.ico-export:before { .ico-export:before {
content: "\f0ed"; content: "\f0ed";
} }
.ico-import:before {
.ico-refresh:before { content: "\f0ee";
content: "\f021";
} }
.ico-keyboard:before {
.ico-remove:before { content: "\f11c";
content: "\e60b";
} }
.ico-moveup:before {
.ico-save:before { content: "\f148";
content: "\f0c7"; }
.ico-movedown:before {
content: "\f149";
}
.ico-weibo:before {
content: "\e621";
}
.ico-uniE608:before {
content: "\e608";
} }
.ico-max:before { .ico-max:before {
content: "\e609"; content: "\e609";
} }
.ico-remove:before {
.ico-format:before { content: "\e60b";
content: "\e612";
} }
.ico-buildrun:before { .ico-buildrun:before {
content: "\e60c"; content: "\e60c";
} }
.ico-about:before {
content: "\e60d";
}
.ico-undo:before {
content: "\e60e";
}
.ico-stop:before { .ico-stop:before {
content: "\e60f"; content: "\e60f";
} }
.ico-close:before {
content: "\e611";
text-shadow: 0 0 rgba(0, 0, 0, 0.4);
}
.ico-format:before {
content: "\e612";
}
.ico-restore:before { .ico-restore:before {
content: "\e613"; content: "\e613";
} }
.toolbars .ico-restore:before { .toolbars .ico-restore:before {
content: "\e60a"; content: "\e60a";
} }
.ico-min:before { .ico-min:before {
content: "\e614"; content: "\e614";
position: absolute; position: absolute;
right: 5px; right: 5px;
} }
.ico-redo:before {
.ico-close:before { content: "\e615";
content: "\e611"; }
.ico-uniE617:before {
content: "\e617";
}
.ico-signout:before {
content: "\e618";
}
.ico-email:before {
content: "\e619";
}
.ico-googleplus:before {
content: "\e61a";
}
.ico-facebook:before {
content: "\e61b";
}
.ico-twitter:before {
content: "\e61c";
}
.ico-info:before {
content: "\e61d";
}
.ico-goline:before {
content: "\e61e";
}
.ico-share:before {
content: "\e61f";
}
.ico-comment:before {
content: "\e620";
}
.ico-github:before {
content: "\f00a";
}
.ico-refresh:before {
content: "\f021";
}
.ico-save:before {
content: "\f0c7";
} }
/* end ico */ /* end ico */

View File

@ -1,11 +1,11 @@
/* /*
* Copyright (c) 2014-2017, b3log.org & hacpai.com * Copyright (c) 2014-2019, b3log.org & hacpai.com
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
* You may obtain a copy of the License at * You may obtain a copy of the License at
* *
* http://www.apache.org/licenses/LICENSE-2.0 * https://www.apache.org/licenses/LICENSE-2.0
* *
* Unless required by applicable law or agreed to in writing, software * Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, * distributed under the License is distributed on an "AS IS" BASIS,
@ -17,7 +17,7 @@
/** /**
* dialig style * dialig style
* *
* @author <a href="mailto:LLY219@gmail.com">Liyuan Li</a> * @author <a href="http://vanessa.b3log.org">Liyuan Li</a>
* @version 0.0.0.6, Jun 3, 2012 * @version 0.0.0.6, Jun 3, 2012
*/ */

BIN
static/css/fonts/icomoon.eot Normal file → Executable file

Binary file not shown.

94
static/css/fonts/icomoon.svg Normal file → Executable file
View File

@ -6,52 +6,50 @@
<font id="icomoon" horiz-adv-x="1024"> <font id="icomoon" horiz-adv-x="1024">
<font-face units-per-em="1024" ascent="960" descent="-64" /> <font-face units-per-em="1024" ascent="960" descent="-64" />
<missing-glyph horiz-adv-x="1024" /> <missing-glyph horiz-adv-x="1024" />
<glyph unicode="&#x20;" d="" horiz-adv-x="512" /> <glyph unicode="&#x20;" horiz-adv-x="512" d="" />
<glyph unicode="&#xe600;" d="M0 886.858c0 40.396 32.747 73.143 73.143 73.143s73.143-32.747 73.143-73.143c0-40.396-32.747-73.143-73.143-73.143s-73.143 32.747-73.143 73.143zM292.572 960h877.714v-146.286h-877.714zM292.572 594.286c0 40.396 32.747 73.143 73.143 73.143s73.143-32.747 73.143-73.143c0-40.396-32.747-73.143-73.143-73.143s-73.143 32.747-73.143 73.143zM585.142 667.428h585.144v-146.286h-585.144zM292.572 9.142c0 40.396 32.747 73.143 73.143 73.143s73.143-32.747 73.143-73.143c0-40.396-32.747-73.143-73.143-73.143s-73.143 32.747-73.143 73.143zM585.142 82.286h585.144v-146.286h-585.144zM585.142 301.714c0 40.396 32.747 73.143 73.143 73.143s73.143-32.747 73.143-73.143c0-40.396-32.747-73.143-73.143-73.143s-73.143 32.747-73.143 73.143zM877.714 374.858h292.572v-146.286h-292.572z" horiz-adv-x="1170" /> <glyph unicode="&#xe600;" glyph-name="tree" horiz-adv-x="1170" d="M0 886.858c0 40.396 32.747 73.143 73.143 73.143s73.143-32.747 73.143-73.143c0-40.396-32.747-73.143-73.143-73.143s-73.143 32.747-73.143 73.143zM292.572 960h877.714v-146.286h-877.714zM292.572 594.286c0 40.396 32.747 73.143 73.143 73.143s73.143-32.747 73.143-73.143c0-40.396-32.747-73.143-73.143-73.143s-73.143 32.747-73.143 73.143zM585.142 667.428h585.144v-146.286h-585.144zM292.572 9.142c0 40.396 32.747 73.143 73.143 73.143s73.143-32.747 73.143-73.143c0-40.396-32.747-73.143-73.143-73.143s-73.143 32.747-73.143 73.143zM585.142 82.286h585.144v-146.286h-585.144zM585.142 301.714c0 40.396 32.747 73.143 73.143 73.143s73.143-32.747 73.143-73.143c0-40.396-32.747-73.143-73.143-73.143s-73.143 32.747-73.143 73.143zM877.714 374.858h292.572v-146.286h-292.572z" />
<glyph unicode="&#xe601;" d="M1009.996 131.024l-301.544 301.544c-18.668 18.668-49.214 18.668-67.882 0l-22.626-22.626-184 184 302.056 302.058h-320l-142.058-142.058-14.060 14.058h-67.882v-67.882l14.058-14.058-206.058-206.060 160-160 206.058 206.058 184-184-22.626-22.626c-18.668-18.668-18.668-49.214 0-67.882l301.544-301.544c18.668-18.668 49.214-18.668 67.882 0l113.136 113.136c18.67 18.666 18.67 49.214 0.002 67.882z" /> <glyph unicode="&#xe601;" glyph-name="build" d="M1009.996 131.024l-301.544 301.544c-18.668 18.668-49.214 18.668-67.882 0l-22.626-22.626-184 184 302.056 302.058h-320l-142.058-142.058-14.060 14.058h-67.882v-67.882l14.058-14.058-206.058-206.060 160-160 206.058 206.058 184-184-22.626-22.626c-18.668-18.668-18.668-49.214 0-67.882l301.544-301.544c18.668-18.668 49.214-18.668 67.882 0l113.136 113.136c18.67 18.666 18.67 49.214 0.002 67.882z" />
<glyph unicode="&#xe602;" d="M632.576 661.376c46.080-10.666 82.752-49.92 93.418-92.8 3.648-20.054 21.12-35.2 42.048-35.2 23.466 0 42.666 19.2 42.666 42.666 0 30.72-25.814 77.44-61.014 112.426-34.56 34.134-75.734 58.24-109.654 58.24-23.466 0-42.666-19.84-42.666-43.306 0.002-20.884 15.148-38.4 35.202-42.026zM46.122 102.016c-33.066-33.046-33.066-86.826 0-119.872 33.066-33.088 86.826-33.088 119.68 0l245.098 244.906c57.194-35.67 124.586-56.342 197.12-56.342 206.294 0 373.334 167.040 373.334 373.334s-167.040 373.334-373.334 373.334-373.334-167.040-373.334-373.334c0-72.32 20.714-139.946 56.342-197.12l-244.906-244.906zM341.376 544.042c0 147.2 119.488 266.666 266.666 266.666 147.2 0 266.666-119.466 266.666-266.666s-119.466-266.666-266.666-266.666c-147.178 0.022-266.666 119.488-266.666 266.666z" /> <glyph unicode="&#xe602;" glyph-name="find" d="M632.576 661.376c46.080-10.666 82.752-49.92 93.418-92.8 3.648-20.054 21.12-35.2 42.048-35.2 23.466 0 42.666 19.2 42.666 42.666 0 30.72-25.814 77.44-61.014 112.426-34.56 34.134-75.734 58.24-109.654 58.24-23.466 0-42.666-19.84-42.666-43.306 0.002-20.884 15.148-38.4 35.202-42.026zM46.122 102.016c-33.066-33.046-33.066-86.826 0-119.872 33.066-33.088 86.826-33.088 119.68 0l245.098 244.906c57.194-35.67 124.586-56.342 197.12-56.342 206.294 0 373.334 167.040 373.334 373.334s-167.040 373.334-373.334 373.334-373.334-167.040-373.334-373.334c0-72.32 20.714-139.946 56.342-197.12l-244.906-244.906zM341.376 544.042c0 147.2 119.488 266.666 266.666 266.666 147.2 0 266.666-119.466 266.666-266.666s-119.466-266.666-266.666-266.666c-147.178 0.022-266.666 119.488-266.666 266.666z" />
<glyph unicode="&#xe603;" d="M0 21.334c0-47.146 38.186-85.334 85.334-85.334h277.334c47.146 0 85.334 38.186 85.334 85.334l-0.002 341.332h128v-341.334c0-47.146 38.186-85.334 85.334-85.334h277.334c47.146 0 85.334 38.186 85.334 85.334l-0.002 554.668c0 47.146-38.186 85.334-85.334 85.334h-42.666v192h10.666c29.44 0 53.334 23.892 53.334 53.332s-23.894 53.334-53.334 53.334h-277.334c-29.438 0-53.332-23.894-53.332-53.334s23.894-53.332 53.334-53.332h10.666v-192h-256v192h10.666c29.44 0 53.334 23.892 53.334 53.332s-23.894 53.334-53.334 53.334h-277.334c-29.438 0-53.332-23.894-53.332-53.334s23.894-53.332 53.334-53.332h10.666v-192h-42.666c-47.146 0-85.334-38.186-85.334-85.334v-554.666zM554.666 469.334h-85.334c-23.466 0-42.666 19.2-42.666 42.666s19.2 42.666 42.666 42.666h85.334c23.466 0 42.666-19.2 42.666-42.666s-19.198-42.666-42.666-42.666z" /> <glyph unicode="&#xe603;" glyph-name="findfiles" d="M0 21.334c0-47.146 38.186-85.334 85.334-85.334h277.334c47.146 0 85.334 38.186 85.334 85.334l-0.002 341.332h128v-341.334c0-47.146 38.186-85.334 85.334-85.334h277.334c47.146 0 85.334 38.186 85.334 85.334l-0.002 554.668c0 47.146-38.186 85.334-85.334 85.334h-42.666v192h10.666c29.44 0 53.334 23.892 53.334 53.332s-23.894 53.334-53.334 53.334h-277.334c-29.438 0-53.332-23.894-53.332-53.334s23.894-53.332 53.334-53.332h10.666v-192h-256v192h10.666c29.44 0 53.334 23.892 53.334 53.332s-23.894 53.334-53.334 53.334h-277.334c-29.438 0-53.332-23.894-53.332-53.334s23.894-53.332 53.334-53.332h10.666v-192h-42.666c-47.146 0-85.334-38.186-85.334-85.334v-554.666zM554.666 469.334h-85.334c-23.466 0-42.666 19.2-42.666 42.666s19.2 42.666 42.666 42.666h85.334c23.466 0 42.666-19.2 42.666-42.666s-19.198-42.666-42.666-42.666z" />
<glyph unicode="&#xe604;" d="M118.634 528.918c-20.886-20.886-30.934-50.774-36.032-81.066l-81.322-384.428c-11.52-68.288 56.96-136.982 126.102-126.314l384.874 80.234c28.16 5.098 58.666 13.866 79.574 34.774l407.68 408.128c32.64 32.832 32.64 85.952 0 118.806l-356.054 356.48c-32.854 32.64-85.974 32.64-118.634 0l-406.188-406.614zM509.888 118.678l-199.254-38.208-165.12 165.334 37.952 199.488 296.768 297.174c16.426 16.448 42.88 16.448 59.306 0 16.448-16.426 16.448-43.094 0-59.52l-274.368-274.774c-20.48-20.48-20.48-53.782 0-74.218 20.48-20.502 53.546-20.502 74.026 0l274.582 274.774c16.448 16.426 42.88 16.426 59.306 0 16.448-16.448 16.448-42.902 0-59.306l-274.368-274.774c-20.48-20.48-20.48-53.782 0-74.24 20.48-20.48 53.568-20.48 74.026 0l274.582 274.774c16.234 16.234 42.902 16.234 59.328 0 16.426-16.426 16.426-43.094 0-59.498l-296.766-297.006z" /> <glyph unicode="&#xe604;" glyph-name="editor" d="M118.634 528.918c-20.886-20.886-30.934-50.774-36.032-81.066l-81.322-384.428c-11.52-68.288 56.96-136.982 126.102-126.314l384.874 80.234c28.16 5.098 58.666 13.866 79.574 34.774l407.68 408.128c32.64 32.832 32.64 85.952 0 118.806l-356.054 356.48c-32.854 32.64-85.974 32.64-118.634 0l-406.188-406.614zM509.888 118.678l-199.254-38.208-165.12 165.334 37.952 199.488 296.768 297.174c16.426 16.448 42.88 16.448 59.306 0 16.448-16.426 16.448-43.094 0-59.52l-274.368-274.774c-20.48-20.48-20.48-53.782 0-74.218 20.48-20.502 53.546-20.502 74.026 0l274.582 274.774c16.448 16.426 42.88 16.426 59.306 0 16.448-16.448 16.448-42.902 0-59.306l-274.368-274.774c-20.48-20.48-20.48-53.782 0-74.24 20.48-20.48 53.568-20.48 74.026 0l274.582 274.774c16.234 16.234 42.902 16.234 59.328 0 16.426-16.426 16.426-43.094 0-59.498l-296.766-297.006z" />
<glyph unicode="&#xe605;" d="M622.858 778.475l-22.032 110.158h-495.715v-936.349h110.158v385.556h308.443l22.032-110.158h385.556v550.795z" /> <glyph unicode="&#xe605;" glyph-name="report" d="M622.858 799.808l-22.032 110.158h-495.715v-936.349h110.158v385.556h308.443l22.032-110.158h385.556v550.795z" />
<glyph unicode="&#xe606;" d="M697.83 473.125c76.887 0 138.908 62.486 138.908 139.372s-62.021 139.372-138.908 139.372c-76.887 0-139.372-62.486-139.372-139.372s62.486-139.372 139.372-139.372zM326.17 473.125c76.887 0 138.908 62.486 138.908 139.372s-62.021 139.372-138.908 139.372c-76.887 0-139.372-62.486-139.372-139.372s62.486-139.372 139.372-139.372zM326.17 380.209c-108.479 0-325.203-54.355-325.203-162.601v-116.144h650.404v116.144c0 108.245-216.724 162.601-325.203 162.601zM697.83 380.209c-13.472 0-28.571-0.929-44.831-2.555 53.89-38.792 91.289-91.057 91.289-160.047v-116.144h278.744v116.144c0 108.245-216.724 162.601-325.203 162.601z" /> <glyph unicode="&#xe606;" glyph-name="signup" d="M697.83 494.458c76.887 0 138.908 62.486 138.908 139.372s-62.021 139.372-138.908 139.372c-76.887 0-139.372-62.486-139.372-139.372s62.486-139.372 139.372-139.372zM326.17 494.458c76.887 0 138.908 62.486 138.908 139.372s-62.021 139.372-138.908 139.372c-76.887 0-139.372-62.486-139.372-139.372s62.486-139.372 139.372-139.372zM326.17 401.542c-108.479 0-325.203-54.355-325.203-162.601v-116.144h650.404v116.144c0 108.245-216.724 162.601-325.203 162.601zM697.83 401.542c-13.472 0-28.571-0.929-44.831-2.555 53.89-38.792 91.289-91.057 91.289-160.047v-116.144h278.744v116.144c0 108.245-216.724 162.601-325.203 162.601z" />
<glyph unicode="&#xe607;" d="M977.76 152.992l-400.608 685.952c-33.408 33.408-87.616 33.408-121.024 0l-400.64-685.952c-33.408-33.376-33.408-87.552 0-120.992h922.24c33.472 33.44 33.472 87.616 0.032 120.992zM479.744 592.704c0 26.528 21.504 48 48 48s48-21.472 48-48v-224c0-26.496-21.504-48-48-48s-48 21.504-48 48v224zM528.032 160.448c-26.496 0-48 21.44-48 48 0 26.496 21.504 48 48 48s48-21.504 48-48c0-26.56-21.504-48-48-48z" /> <glyph unicode="&#xe607;" glyph-name="notification" d="M977.76 152.992l-400.608 685.952c-33.408 33.408-87.616 33.408-121.024 0l-400.64-685.952c-33.408-33.376-33.408-87.552 0-120.992h922.24c33.472 33.44 33.472 87.616 0.032 120.992zM479.744 592.704c0 26.528 21.504 48 48 48s48-21.472 48-48v-224c0-26.496-21.504-48-48-48s-48 21.504-48 48v224zM528.032 160.448c-26.496 0-48 21.44-48 48 0 26.496 21.504 48 48 48s48-21.504 48-48c0-26.56-21.504-48-48-48z" />
<glyph unicode="&#xe608;" d="M438.928 896.072q14.856 0 25.714-10.856t10.858-25.714v-128h168l98.856 98.856q10.856 10.856 25.714 10.856t25.714-10.856 10.856-25.714-10.856-25.714l-98.856-98.856v-482.286l98.856-98.856q10.856-10.856 10.856-25.714t-10.856-25.714-25.714-10.856-25.714 10.856l-98.856 98.856h-168v-128q0-14.856-10.856-25.714t-25.714-10.856-25.714 10.856-10.856 25.714v128q-97.714 0-165.714 38.286l-119.43-118.856q-10.856-10.856-25.714-10.856t-25.714 10.856q-10.856 10.286-10.856 25.714t10.856 25.714l112.57 113.144q-2.856 2.856-7.43 8.572t-16.286 24-20.856 37.144-16.57 46.856-7.43 55.428h512v73.144h-512q0 29.144 7.714 58t18.856 49.714 22.286 37.714 18.57 24.856l8 8.572-118.286 104.57q-12 11.43-12 27.43 0 13.714 9.144 24.57 10.286 10.858 25.43 11.714t26.57-8.856l129.714-115.428q65.144 33.142 156.57 33.142v128q0 14.858 10.858 25.714t25.714 10.856zM768.072 621.786q76 0 129.428-53.428t53.428-129.428-53.428-129.428-129.428-53.428v365.714z" horiz-adv-x="952" /> <glyph unicode="&#xe608;" glyph-name="uniE608" horiz-adv-x="952" d="M438.928 896.072q14.856 0 25.714-10.856t10.858-25.714v-128h168l98.856 98.856q10.856 10.856 25.714 10.856t25.714-10.856 10.856-25.714-10.856-25.714l-98.856-98.856v-482.286l98.856-98.856q10.856-10.856 10.856-25.714t-10.856-25.714-25.714-10.856-25.714 10.856l-98.856 98.856h-168v-128q0-14.856-10.856-25.714t-25.714-10.856-25.714 10.856-10.856 25.714v128q-97.714 0-165.714 38.286l-119.43-118.856q-10.856-10.856-25.714-10.856t-25.714 10.856q-10.856 10.286-10.856 25.714t10.856 25.714l112.57 113.144q-2.856 2.856-7.43 8.572t-16.286 24-20.856 37.144-16.57 46.856-7.43 55.428h512v73.144h-512q0 29.144 7.714 58t18.856 49.714 22.286 37.714 18.57 24.856l8 8.572-118.286 104.57q-12 11.43-12 27.43 0 13.714 9.144 24.57 10.286 10.858 25.43 11.714t26.57-8.856l129.714-115.428q65.144 33.142 156.57 33.142v128q0 14.858 10.858 25.714t25.714 10.856zM768.072 621.786q76 0 129.428-53.428t53.428-129.428-53.428-129.428-129.428-53.428v365.714z" />
<glyph unicode="&#xe609;" d="M1024 960v-384l-138.26 138.26-212-212-107.48 107.48 212 212-138.26 138.26zM245.74 821.74l212-212-107.48-107.48-212 212-138.26-138.26v384h384zM885.74 181.74l138.26 138.26v-384h-384l138.26 138.26-212 212 107.48 107.48zM457.74 286.26l-212-212 138.26-138.26h-384v384l138.26-138.26 212 212z" /> <glyph unicode="&#xe609;" glyph-name="max" d="M1024 960v-384l-138.26 138.26-212-212-107.48 107.48 212 212-138.26 138.26zM245.74 821.74l212-212-107.48-107.48-212 212-138.26-138.26v384h384zM885.74 181.74l138.26 138.26v-384h-384l138.26 138.26-212 212 107.48 107.48zM457.74 286.26l-212-212 138.26-138.26h-384v384l138.26-138.26 212 212z" />
<glyph unicode="&#xe60a;" d="M64 384h384v-384l-138.26 138.26-202-202-107.48 107.48 202 202zM821.74 245.74l202-202-107.48-107.48-202 202-138.26-138.26v384h384zM960 512h-384v384l138.26-138.26 202 202 107.48-107.48-202-202zM309.74 757.74l138.26 138.26v-384h-384l138.26 138.26-202 202 107.48 107.48z" /> <glyph unicode="&#xe60a;" glyph-name="uniE60A" d="M64 384h384v-384l-138.26 138.26-202-202-107.48 107.48 202 202zM821.74 245.74l202-202-107.48-107.48-202 202-138.26-138.26v384h384zM960 512h-384v384l138.26-138.26 202 202 107.48-107.48-202-202zM309.74 757.74l138.26 138.26v-384h-384l138.26 138.26-202 202 107.48 107.48z" />
<glyph unicode="&#xe60b;" d="M800 832h-576c-53.020 0-96-42.98-96-96v-32h768v32c0 53.020-42.98 96-96 96zM632.32 896l14.116-101h-268.872l14.114 101h240.642zM640 960h-256c-26.4 0-50.99-21.392-54.642-47.538l-18.714-133.924c-3.654-26.146 14.956-47.538 41.356-47.538h320c26.4 0 45.010 21.392 41.358 47.538l-18.714 133.924c-3.654 26.146-28.244 47.538-54.644 47.538v0zM816 640h-608c-35.2 0-61.392-28.682-58.206-63.738l52.412-576.526c3.186-35.054 34.594-63.736 69.794-63.736h480c35.2 0 66.608 28.682 69.794 63.736l52.41 576.526c3.188 35.056-23.004 63.738-58.204 63.738zM384 64h-96l-32 448h128v-448zM576 64h-128v448h128v-448zM736 64h-96v448h128l-32-448z" /> <glyph unicode="&#xe60b;" glyph-name="remove" d="M800 832h-576c-53.020 0-96-42.98-96-96v-32h768v32c0 53.020-42.98 96-96 96zM632.32 896l14.116-101h-268.872l14.114 101h240.642zM640 960h-256c-26.4 0-50.99-21.392-54.642-47.538l-18.714-133.924c-3.654-26.146 14.956-47.538 41.356-47.538h320c26.4 0 45.010 21.392 41.358 47.538l-18.714 133.924c-3.654 26.146-28.244 47.538-54.644 47.538v0zM816 640h-608c-35.2 0-61.392-28.682-58.206-63.738l52.412-576.526c3.186-35.054 34.594-63.736 69.794-63.736h480c35.2 0 66.608 28.682 69.794 63.736l52.41 576.526c3.188 35.056-23.004 63.738-58.204 63.738zM384 64h-96l-32 448h128v-448zM576 64h-128v448h128v-448zM736 64h-96v448h128l-32-448z" />
<glyph unicode="&#xe60c;" d="M192 832l640-384-640-384z" /> <glyph unicode="&#xe60c;" glyph-name="buildrun" d="M192 832l640-384-640-384z" />
<glyph unicode="&#xe60d;" d="M708.524 833.167c-54.823 39.545-123.584 59.295-206.423 59.295-63.041 0-116.185-13.935-159.382-41.708-68.567-43.534-104.989-117.434-109.41-221.702h158.852c0 30.367 8.841 59.629 26.572 87.787s47.81 42.236 90.238 42.236c43.101 0 72.844-11.436 89.084-34.26 16.289-22.92 24.41-48.242 24.41-76.015 0-24.169-12.108-46.272-26.714-66.405-8.024-11.726-18.644-22.488-31.759-32.387 0 0-86.154-55.258-124.016-99.657-21.959-25.755-23.929-64.291-25.851-119.598-0.144-3.94 1.346-12.059 15.136-12.059s111.332 0 123.584 0 14.8 9.083 14.991 13.069c0.865 20.133 3.123 30.416 6.823 42.044 6.967 21.959 25.803 41.129 47.042 57.614l43.726 30.176c39.447 30.752 70.97 55.978 84.855 75.775 23.738 32.578 40.409 72.651 40.409 120.171 0 77.601-27.437 136.126-82.166 175.623zM499.553 223.413c-54.777 1.633-99.944-36.231-101.672-95.619-1.73-59.342 41.227-98.549 96.004-100.184 57.18-1.682 101.145 34.981 102.875 94.322 1.682 59.39-40.026 99.8-97.205 101.481z" /> <glyph unicode="&#xe60d;" glyph-name="about" d="M708.524 833.167c-54.823 39.545-123.584 59.295-206.423 59.295-63.041 0-116.185-13.935-159.382-41.708-68.567-43.534-104.989-117.434-109.41-221.702h158.852c0 30.367 8.841 59.629 26.572 87.787s47.81 42.236 90.238 42.236c43.101 0 72.844-11.436 89.084-34.26 16.289-22.92 24.41-48.242 24.41-76.015 0-24.169-12.108-46.272-26.714-66.405-8.024-11.726-18.644-22.488-31.759-32.387 0 0-86.154-55.258-124.016-99.657-21.959-25.755-23.929-64.291-25.851-119.598-0.144-3.94 1.346-12.059 15.136-12.059s111.332 0 123.584 0 14.8 9.083 14.991 13.069c0.865 20.133 3.123 30.416 6.823 42.044 6.967 21.959 25.803 41.129 47.042 57.614l43.726 30.176c39.447 30.752 70.97 55.978 84.855 75.775 23.738 32.578 40.409 72.651 40.409 120.171 0 77.601-27.437 136.126-82.166 175.623zM499.553 223.413c-54.777 1.633-99.944-36.231-101.672-95.619-1.73-59.342 41.227-98.549 96.004-100.184 57.18-1.682 101.145 34.981 102.875 94.322 1.682 59.39-40.026 99.8-97.205 101.481z" />
<glyph unicode="&#xe60e;" d="M761.862-64c113.726 206.032 132.888 520.306-313.862 509.824v-253.824l-384 384 384 384v-248.372c534.962 13.942 594.57-472.214 313.862-775.628z" /> <glyph unicode="&#xe60e;" glyph-name="undo" d="M761.862-64c113.726 206.032 132.888 520.306-313.862 509.824v-253.824l-384 384 384 384v-248.372c534.962 13.942 594.57-472.214 313.862-775.628z" />
<glyph unicode="&#xe60f;" d="M128 832h768v-768h-768z" /> <glyph unicode="&#xe60f;" glyph-name="stop" d="M128 832h768v-768h-768z" />
<glyph unicode="&#xe610;" d="M363.722 237.948l41.298 57.816-45.254 45.256-57.818-41.296c-10.722 5.994-22.204 10.774-34.266 14.192l-11.682 70.084h-64l-11.68-70.086c-12.062-3.418-23.544-8.198-34.266-14.192l-57.818 41.298-45.256-45.256 41.298-57.816c-5.994-10.72-10.774-22.206-14.192-34.266l-70.086-11.682v-64l70.086-11.682c3.418-12.060 8.198-23.544 14.192-34.266l-41.298-57.816 45.254-45.256 57.818 41.296c10.722-5.994 22.204-10.774 34.266-14.192l11.682-70.084h64l11.68 70.086c12.062 3.418 23.544 8.198 34.266 14.192l57.818-41.296 45.254 45.256-41.298 57.816c5.994 10.72 10.774 22.206 14.192 34.266l70.088 11.68v64l-70.086 11.682c-3.418 12.060-8.198 23.544-14.192 34.266zM224 96c-35.348 0-64 28.654-64 64s28.652 64 64 64 64-28.654 64-64-28.652-64-64-64zM1024 576v64l-67.382 12.25c-1.242 8.046-2.832 15.978-4.724 23.79l57.558 37.1-24.492 59.128-66.944-14.468c-4.214 6.91-8.726 13.62-13.492 20.13l39.006 56.342-45.256 45.254-56.342-39.006c-6.512 4.766-13.22 9.276-20.13 13.494l14.468 66.944-59.128 24.494-37.1-57.558c-7.812 1.892-15.744 3.482-23.79 4.724l-12.252 67.382h-64l-12.252-67.382c-8.046-1.242-15.976-2.832-23.79-4.724l-37.098 57.558-59.128-24.492 14.468-66.944c-6.91-4.216-13.62-8.728-20.13-13.494l-56.342 39.006-45.254-45.254 39.006-56.342c-4.766-6.51-9.278-13.22-13.494-20.13l-66.944 14.468-24.492-59.128 57.558-37.1c-1.892-7.812-3.482-15.742-4.724-23.79l-67.384-12.252v-64l67.382-12.25c1.242-8.046 2.832-15.978 4.724-23.79l-57.558-37.1 24.492-59.128 66.944 14.468c4.216-6.91 8.728-13.618 13.494-20.13l-39.006-56.342 45.254-45.256 56.342 39.006c6.51-4.766 13.22-9.276 20.13-13.492l-14.468-66.944 59.128-24.492 37.102 57.558c7.81-1.892 15.742-3.482 23.788-4.724l12.252-67.384h64l12.252 67.382c8.044 1.242 15.976 2.832 23.79 4.724l37.1-57.558 59.128 24.492-14.468 66.944c6.91 4.216 13.62 8.726 20.13 13.492l56.342-39.006 45.256 45.256-39.006 56.342c4.766 6.512 9.276 13.22 13.492 20.13l66.944-14.468 24.492 59.13-57.558 37.1c1.892 7.812 3.482 15.742 4.724 23.79l67.382 12.25zM672 468.8c-76.878 0-139.2 62.322-139.2 139.2s62.32 139.2 139.2 139.2 139.2-62.322 139.2-139.2c0-76.878-62.32-139.2-139.2-139.2z" /> <glyph unicode="&#xe611;" glyph-name="close" d="M734.668 299.418l-141.21 161.382 141.21 161.382c24.014 24.014 24.014 62.926 0 86.886s-62.924 23.962-86.886 0l-135.782-155.186-135.73 155.136c-24.012 24.012-62.926 24.012-86.886 0s-23.962-62.926 0-86.886l141.158-161.332-141.21-161.382c-23.962-24.014-23.962-62.822 0-86.784 24.012-24.014 62.926-24.014 86.886 0l135.782 155.086 135.73-155.086c24.014-24.014 62.924-24.014 86.886 0s24.014 62.77 0.052 86.784z" />
<glyph unicode="&#xe611;" d="M734.668 299.418l-141.21 161.382 141.21 161.382c24.014 24.014 24.014 62.926 0 86.886s-62.924 23.962-86.886 0l-135.782-155.186-135.73 155.136c-24.012 24.012-62.926 24.012-86.886 0s-23.962-62.926 0-86.886l141.158-161.332-141.21-161.382c-23.962-24.014-23.962-62.822 0-86.784 24.012-24.014 62.926-24.014 86.886 0l135.782 155.086 135.73-155.086c24.014-24.014 62.924-24.014 86.886 0s24.014 62.77 0.052 86.784z" /> <glyph unicode="&#xe612;" glyph-name="format" d="M448 165.49l-205.254 237.254 58.508 58.51 146.746-114.744 274.744 242.744 58.512-58.508zM831.772 832c0.078-0.066 0.162-0.15 0.228-0.23v-767.542c-0.066-0.078-0.15-0.162-0.228-0.228h-639.544c-0.080 0.066-0.162 0.15-0.228 0.228v767.544c0.066 0.080 0.15 0.162 0.23 0.228h-128.23v-768c0-70.4 57.6-128 128-128h640c70.4 0 128 57.6 128 128v768h-128.228zM640 832v64c0 35.346-28.654 64-64 64h-128c-35.346 0-64-28.654-64-64v-64h-128v-128h512v128h-128zM576 832h-128v64h128v-64z" />
<glyph unicode="&#xe612;" d="M448 165.49l-205.254 237.254 58.508 58.51 146.746-114.744 274.744 242.744 58.512-58.508zM831.772 832c0.078-0.066 0.162-0.15 0.228-0.23v-767.542c-0.066-0.078-0.15-0.162-0.228-0.228h-639.544c-0.080 0.066-0.162 0.15-0.228 0.228v767.544c0.066 0.080 0.15 0.162 0.23 0.228h-128.23v-768c0-70.4 57.6-128 128-128h640c70.4 0 128 57.6 128 128v768h-128.228zM640 832v64c0 35.346-28.654 64-64 64h-128c-35.346 0-64-28.654-64-64v-64h-128v-128h512v128h-128zM576 832h-128v64h128v-64z" /> <glyph unicode="&#xe613;" glyph-name="restore" d="M128 682.666v128h896v-640h-128v-128h-896v640h128zM896 256h42.668v469.334h-725.332v-42.666h682.668v-426.668zM85.332 128h725.332v469.334h-725.332v-469.334z" />
<glyph unicode="&#xe613;" d="M128 682.666v128h896v-640h-128v-128h-896v640h128zM896 256h42.668v469.334h-725.332v-42.666h682.668v-426.668zM85.332 128h725.332v469.334h-725.332v-469.334z" /> <glyph unicode="&#xe614;" glyph-name="min" d="M0 544v-192c0-17.672 14.328-32 32-32h960c17.672 0 32 14.328 32 32v192c0 17.672-14.328 32-32 32h-960c-17.672 0-32-14.328-32-32z" />
<glyph unicode="&#xe614;" d="M0 544v-192c0-17.672 14.328-32 32-32h960c17.672 0 32 14.328 32 32v192c0 17.672-14.328 32-32 32h-960c-17.672 0-32-14.328-32-32z" /> <glyph unicode="&#xe615;" glyph-name="redo" d="M576 711.628v248.372l384-384-384-384v253.824c-446.75 10.482-427.588-303.792-313.86-509.824-280.712 303.414-221.1 789.57 313.86 775.628z" />
<glyph unicode="&#xe615;" d="M576 711.628v248.372l384-384-384-384v253.824c-446.75 10.482-427.588-303.792-313.86-509.824-280.712 303.414-221.1 789.57 313.86 775.628z" /> <glyph unicode="&#xe616;" glyph-name="price" d="M501.443 506.062c-119.819 31.143-158.353 63.078-158.353 113.223 0 57.535 53.047 97.916 142.518 97.916 93.957 0 128.794-44.867 131.961-110.848h116.654c-3.431 91.053-59.119 173.925-169.439 201.109v115.599h-158.353v-114.015c-102.402-22.433-184.746-88.413-184.746-190.553 0-121.933 101.082-182.636 248.086-217.999 132.225-31.671 158.353-77.856 158.353-127.474 0-36.159-25.601-94.219-142.518-94.219-108.736 0-151.755 48.826-157.297 110.848h-116.389c6.597-115.599 92.901-180.26 194.511-202.165v-114.544h158.353v113.487c102.667 19.794 184.746 79.178 184.746 187.65 0 149.38-128.266 200.581-248.086 231.987z" />
<glyph unicode="&#xe616;" d="M501.443 484.729c-119.819 31.143-158.353 63.078-158.353 113.223 0 57.535 53.047 97.916 142.518 97.916 93.957 0 128.794-44.867 131.961-110.848h116.654c-3.431 91.053-59.119 173.925-169.439 201.109v115.599h-158.353v-114.015c-102.402-22.433-184.746-88.413-184.746-190.553 0-121.933 101.082-182.636 248.086-217.999 132.225-31.671 158.353-77.856 158.353-127.474 0-36.159-25.601-94.219-142.518-94.219-108.736 0-151.755 48.826-157.297 110.848h-116.389c6.597-115.599 92.901-180.26 194.511-202.165v-114.544h158.353v113.487c102.667 19.794 184.746 79.178 184.746 187.65 0 149.38-128.266 200.581-248.086 231.987z" /> <glyph unicode="&#xe617;" glyph-name="uniE617" d="M384 448h-320v128h320v128l192-192-192-192zM1024 960v-832l-384-192v192h-384v256h64v-192h320v576l256 128h-576v-256h-64v320z" />
<glyph unicode="&#xe617;" d="M384 448h-320v128h320v128l192-192-192-192zM1024 960v-832l-384-192v192h-384v256h64v-192h320v576l256 128h-576v-256h-64v320z" /> <glyph unicode="&#xe618;" glyph-name="signout" d="M768 320v128h-320v128h320v128l192-192zM704 384v-256h-320v-192l-384 192v832h704v-320h-64v256h-512l256-128v-576h256v192z" />
<glyph unicode="&#xe618;" d="M768 320v128h-320v128h320v128l192-192zM704 384v-256h-320v-192l-384 192v832h704v-320h-64v256h-512l256-128v-576h256v192z" /> <glyph unicode="&#xe619;" glyph-name="email" d="M512 960c-282.77 0-512-229.23-512-512s229.23-512 512-512 512 229.23 512 512-229.23 512-512 512zM256 704h512c9.138 0 18.004-1.962 26.144-5.662l-282.144-329.168-282.144 329.17c8.14 3.696 17.006 5.66 26.144 5.66zM192 256v384c0 1.34 0.056 2.672 0.14 4l187.664-218.942-185.598-185.598c-1.444 5.336-2.206 10.886-2.206 16.54zM768 192h-512c-5.654 0-11.202 0.762-16.54 2.208l182.118 182.118 90.422-105.498 90.424 105.494 182.116-182.12c-5.34-1.44-10.886-2.202-16.54-2.202zM832 256c0-5.654-0.762-11.2-2.206-16.54l-185.6 185.598 187.666 218.942c0.084-1.328 0.14-2.66 0.14-4v-384z" />
<glyph unicode="&#xe619;" d="M512 960c-282.77 0-512-229.23-512-512s229.23-512 512-512 512 229.23 512 512-229.23 512-512 512zM256 704h512c9.138 0 18.004-1.962 26.144-5.662l-282.144-329.168-282.144 329.17c8.14 3.696 17.006 5.66 26.144 5.66zM192 256v384c0 1.34 0.056 2.672 0.14 4l187.664-218.942-185.598-185.598c-1.444 5.336-2.206 10.886-2.206 16.54zM768 192h-512c-5.654 0-11.202 0.762-16.54 2.208l182.118 182.118 90.422-105.498 90.424 105.494 182.116-182.12c-5.34-1.44-10.886-2.202-16.54-2.202zM832 256c0-5.654-0.762-11.2-2.206-16.54l-185.6 185.598 187.666 218.942c0.084-1.328 0.14-2.66 0.14-4v-384z" /> <glyph unicode="&#xe61a;" glyph-name="googleplus" d="M437.006 141.838c0-75.068-46.39-134.392-177.758-139.176-76.984 43.786-141.49 106.952-186.908 182.866 23.69 58.496 97.692 103.046 182.316 102.114 24.022-0.252 46.41-4.114 66.744-10.7 55.908-38.866 101-63.152 112.324-107.448 2.114-8.964 3.282-18.206 3.282-27.656zM512 960c-147.94 0-281.196-62.77-374.666-163.098 36.934 20.452 80.538 32.638 126.902 32.638 67.068 0 256.438 0 256.438 0l-57.304-60.14h-67.31c47.496-27.212 72.752-83.248 72.752-145.012 0-56.692-31.416-102.38-75.78-137.058-43.28-33.802-51.492-47.966-51.492-76.734 0-24.542 51.722-61.098 75.5-78.936 82.818-62.112 99.578-101.184 99.578-178.87 0-78.726-68.936-157.104-185.866-183.742 56.348-21.338 117.426-33.048 181.248-33.048 282.77 0 512 229.23 512 512s-229.23 512-512 512zM768 576v-128h-64v128h-128v64h128v128h64v-128h128v-64h-128zM365.768 620.528c11.922-90.776-27.846-149.19-96.934-147.134-69.126 2.082-134.806 65.492-146.74 156.242-11.928 90.788 34.418 160.254 103.53 158.196 69.090-2.074 128.22-76.542 140.144-167.304zM220.886 317.932c-74.68 0-138.128-25.768-182.842-63.864-24.502 59.82-38.044 125.29-38.044 193.932 0 56.766 9.256 111.368 26.312 162.396 7.374-99.442 77.352-176.192 192.97-176.192 8.514 0 16.764 0.442 24.874 1.022-7.95-15.23-13.622-32.19-13.622-49.982 0-29.97 16.488-47.070 36.868-66.894-15.402 0-30.27-0.418-46.516-0.418z" />
<glyph unicode="&#xe61a;" d="M437.006 141.838c0-75.068-46.39-134.392-177.758-139.176-76.984 43.786-141.49 106.952-186.908 182.866 23.69 58.496 97.692 103.046 182.316 102.114 24.022-0.252 46.41-4.114 66.744-10.7 55.908-38.866 101-63.152 112.324-107.448 2.114-8.964 3.282-18.206 3.282-27.656zM512 960c-147.94 0-281.196-62.77-374.666-163.098 36.934 20.452 80.538 32.638 126.902 32.638 67.068 0 256.438 0 256.438 0l-57.304-60.14h-67.31c47.496-27.212 72.752-83.248 72.752-145.012 0-56.692-31.416-102.38-75.78-137.058-43.28-33.802-51.492-47.966-51.492-76.734 0-24.542 51.722-61.098 75.5-78.936 82.818-62.112 99.578-101.184 99.578-178.87 0-78.726-68.936-157.104-185.866-183.742 56.348-21.338 117.426-33.048 181.248-33.048 282.77 0 512 229.23 512 512s-229.23 512-512 512zM768 576v-128h-64v128h-128v64h128v128h64v-128h128v-64h-128zM365.768 620.528c11.922-90.776-27.846-149.19-96.934-147.134-69.126 2.082-134.806 65.492-146.74 156.242-11.928 90.788 34.418 160.254 103.53 158.196 69.090-2.074 128.22-76.542 140.144-167.304zM220.886 317.932c-74.68 0-138.128-25.768-182.842-63.864-24.502 59.82-38.044 125.29-38.044 193.932 0 56.766 9.256 111.368 26.312 162.396 7.374-99.442 77.352-176.192 192.97-176.192 8.514 0 16.764 0.442 24.874 1.022-7.95-15.23-13.622-32.19-13.622-49.982 0-29.97 16.488-47.070 36.868-66.894-15.402 0-30.27-0.418-46.516-0.418z" /> <glyph unicode="&#xe61b;" glyph-name="facebook" d="M512 960c282.77 0 512-229.23 512-512 0-261.094-195.438-476.53-448-508.026v380.026h176l16 128h-192v64c0 35.346 28.654 64 64 64h128v128h-128c-106.040 0-192-85.96-192-192v-64h-96v-128h96v-380.026c-252.562 31.496-448 246.932-448 508.026 0 282.77 229.23 512 512 512z" />
<glyph unicode="&#xe61b;" d="M512 960c282.77 0 512-229.23 512-512 0-261.094-195.438-476.53-448-508.026v380.026h176l16 128h-192v64c0 35.346 28.654 64 64 64h128v128h-128c-106.040 0-192-85.96-192-192v-64h-96v-128h96v-380.026c-252.562 31.496-448 246.932-448 508.026 0 282.77 229.23 512 512 512z" /> <glyph unicode="&#xe61c;" glyph-name="twitter" d="M512 960c-282.77 0-512-229.23-512-512s229.23-512 512-512 512 229.23 512 512-229.23 512-512 512zM806.242 598.912c0.292-6.508 0.442-13.056 0.442-19.638 0-200.622-152.708-431.964-431.958-431.964-85.736 0-165.536 25.136-232.726 68.214 11.876-1.408 23.962-2.126 36.216-2.126 71.13 0 136.59 24.276 188.55 64.994-66.434 1.22-122.5 45.122-141.824 105.432 9.274-1.768 18.784-2.722 28.566-2.722 13.846 0 27.258 1.856 39.998 5.324-69.452 13.952-121.786 75.312-121.786 148.868 0 0.64 0 1.278 0.016 1.91 20.47-11.37 43.878-18.2 68.764-18.988-40.74 27.222-67.54 73.692-67.54 126.368 0 27.822 7.488 53.904 20.556 76.324 74.878-91.854 186.748-152.292 312.924-158.628-2.588 11.118-3.93 22.702-3.93 34.604 0 83.84 67.98 151.812 151.818 151.812 43.666 0 83.124-18.436 110.818-47.94 34.58 6.808 67.074 19.442 96.412 36.84-11.336-35.454-35.41-65.206-66.752-83.994 30.71 3.668 59.968 11.832 87.194 23.904-20.35-30.448-46.090-57.186-75.758-78.594z" />
<glyph unicode="&#xe61c;" d="M512 960c-282.77 0-512-229.23-512-512s229.23-512 512-512 512 229.23 512 512-229.23 512-512 512zM806.242 598.912c0.292-6.508 0.442-13.056 0.442-19.638 0-200.622-152.708-431.964-431.958-431.964-85.736 0-165.536 25.136-232.726 68.214 11.876-1.408 23.962-2.126 36.216-2.126 71.13 0 136.59 24.276 188.55 64.994-66.434 1.22-122.5 45.122-141.824 105.432 9.274-1.768 18.784-2.722 28.566-2.722 13.846 0 27.258 1.856 39.998 5.324-69.452 13.952-121.786 75.312-121.786 148.868 0 0.64 0 1.278 0.016 1.91 20.47-11.37 43.878-18.2 68.764-18.988-40.74 27.222-67.54 73.692-67.54 126.368 0 27.822 7.488 53.904 20.556 76.324 74.878-91.854 186.748-152.292 312.924-158.628-2.588 11.118-3.93 22.702-3.93 34.604 0 83.84 67.98 151.812 151.818 151.812 43.666 0 83.124-18.436 110.818-47.94 34.58 6.808 67.074 19.442 96.412 36.84-11.336-35.454-35.41-65.206-66.752-83.994 30.71 3.668 59.968 11.832 87.194 23.904-20.35-30.448-46.090-57.186-75.758-78.594z" /> <glyph unicode="&#xe61d;" glyph-name="info" d="M512 960c-282.77 0-512-229.23-512-512s229.23-512 512-512 512 229.23 512 512-229.23 512-512 512zM512 32c-229.75 0-416 186.25-416 416s186.25 416 416 416 416-186.25 416-416-186.25-416-416-416zM448 704h128v-128h-128zM640 192h-256v64h64v192h-64v64h192v-256h64z" />
<glyph unicode="&#xe61d;" d="M512 960c-282.77 0-512-229.23-512-512s229.23-512 512-512 512 229.23 512 512-229.23 512-512 512zM512 32c-229.75 0-416 186.25-416 416s186.25 416 416 416 416-186.25 416-416-186.25-416-416-416zM448 704h128v-128h-128zM640 192h-256v64h64v192h-64v64h192v-256h64z" /> <glyph unicode="&#xe61e;" glyph-name="goline" d="M384 128h640v-128h-640zM384 512h640v-128h-640zM384 896h640v-128h-640zM192 960v-256h-64v192h-64v64zM128 434v-50h128v-64h-192v146l128 60v50h-128v64h192v-146zM256 256v-320h-192v64h128v64h-128v64h128v64h-128v64z" />
<glyph unicode="&#xe61e;" d="M384 128h640v-128h-640zM384 512h640v-128h-640zM384 896h640v-128h-640zM192 960v-256h-64v192h-64v64zM128 434v-50h128v-64h-192v146l128 60v50h-128v64h192v-146zM256 256v-320h-192v64h128v64h-128v64h128v64h-128v64z" /> <glyph unicode="&#xe61f;" glyph-name="share" d="M864 256c-45.16 0-85.92-18.738-115.012-48.83l-431.004 215.502c1.314 8.252 2.016 16.706 2.016 25.328s-0.702 17.076-2.016 25.326l431.004 215.502c29.092-30.090 69.852-48.828 115.012-48.828 88.366 0 160 71.634 160 160s-71.634 160-160 160-160-71.634-160-160c0-8.622 0.704-17.076 2.016-25.326l-431.004-215.504c-29.092 30.090-69.852 48.83-115.012 48.83-88.366 0-160-71.636-160-160 0-88.368 71.634-160 160-160 45.16 0 85.92 18.738 115.012 48.828l431.004-215.502c-1.312-8.25-2.016-16.704-2.016-25.326 0-88.368 71.634-160 160-160s160 71.632 160 160c0 88.364-71.634 160-160 160z" />
<glyph unicode="&#xe61f;" d="M864 256c-45.16 0-85.92-18.738-115.012-48.83l-431.004 215.502c1.314 8.252 2.016 16.706 2.016 25.328s-0.702 17.076-2.016 25.326l431.004 215.502c29.092-30.090 69.852-48.828 115.012-48.828 88.366 0 160 71.634 160 160s-71.634 160-160 160-160-71.634-160-160c0-8.622 0.704-17.076 2.016-25.326l-431.004-215.504c-29.092 30.090-69.852 48.83-115.012 48.83-88.366 0-160-71.636-160-160 0-88.368 71.634-160 160-160 45.16 0 85.92 18.738 115.012 48.828l431.004-215.502c-1.312-8.25-2.016-16.704-2.016-25.326 0-88.368 71.634-160 160-160s160 71.632 160 160c0 88.364-71.634 160-160 160z" /> <glyph unicode="&#xe620;" glyph-name="comment" d="M0 343.808l321.344-206.624v149.152l-191.328 118.144 191.328 118.144v150.176l-321.344-207.68v-121.312zM389.312 64h110.592l162.048 768h-111.328l-161.312-768zM702.688 672.8v-150.176l191.264-118.144-191.264-118.112v-149.152l321.312 206.592v121.312l-321.312 207.68z" />
<glyph unicode="&#xe620;" d="M0 343.808l321.344-206.624v149.152l-191.328 118.144 191.328 118.144v150.176l-321.344-207.68v-121.312zM389.312 64h110.592l162.048 768h-111.328l-161.312-768zM702.688 672.8v-150.176l191.264-118.144-191.264-118.112v-149.152l321.312 206.592v121.312l-321.312 207.68z" /> <glyph unicode="&#xe621;" glyph-name="weibo" d="M428.222 493.19c-144.194-6.656-260.742-83.888-260.742-180.224 0-96.202 116.574-168.528 260.742-161.792 144.302 6.576 261.094 96.876 261.094 193.078 0 96.12-116.79 155.594-261.094 148.938zM526.472 250.88c-44.168-56.994-131.53-84.802-216.36-38.858-40.394 21.908-38.884 64.916-38.884 64.916s-16.76 135.842 128.27 152.818c145.192 16.816 171.116-121.856 126.976-178.876zM429.19 341.854c-9.298-6.736-11.182-19.618-6.144-27.62 4.85-8.22 16.142-9.162 25.276-2.318 8.974 7.086 12.45 19.428 7.572 27.62-4.798 7.976-15.952 10.294-26.704 2.318zM360.448 323.154c-27.108-2.802-46.484-26.408-46.484-48.99 0-22.636 21.826-38.264 48.882-35.086 26.974 3.072 48.908 23.928 48.908 46.484 0.054 22.636-20.184 40.582-51.308 37.592zM868.082 960h-712.22c-86.096 0-155.89-69.794-155.89-155.89v-712.22c0-86.096 69.794-155.89 155.89-155.89h712.22c86.096 0 155.89 69.794 155.89 155.89v712.22c0 86.096-69.768 155.89-155.89 155.89zM818.202 280.386c-59.446-126.274-255.462-187.716-400.734-176.344-138.050 10.86-315.526 56.724-333.878 223.798 0 0-9.7 75.668 63.65 173.568 0 0 105.498 147.322 228.378 189.36 122.988 41.85 137.35-28.968 137.35-70.818-6.548-35.49-18.782-56.374 27.38-42.038 0 0 120.886 56.076 170.658 6.332 40.126-40.152 6.63-95.42 6.63-95.42s-16.654-18.404 17.624-25.034c34.358-6.872 142.362-56.914 82.944-183.404zM698.988 629.274c-13.15 0-23.714 10.644-23.714 23.688 0 13.286 10.562 23.956 23.714 23.956 0 0 148.212 27.404 130.48-131.852 0-0.942-0.108-1.698-0.322-2.534-1.672-11.29-11.586-19.94-23.256-19.94-13.204 0-23.956 10.562-23.956 23.74 0-0.026 23.498 106.416-82.944 82.944zM949.518 501.894h-0.216c-3.908-26.948-17.274-29.104-33.198-29.104-19.052 0-34.438 11.964-34.438 31.044 0 16.52 6.846 33.308 6.846 33.308 2.020 6.952 18.136 50.176-10.644 114.796-52.708 88.522-158.856 89.816-171.384 84.776-12.638-4.958-31.286-7.436-31.286-7.436-19.188 0-34.548 15.602-34.548 34.572 0 15.926 10.644 29.4 25.196 33.524 0 0 0.322 0.538 0.808 0.62 1.052 0.216 2.13 1.268 3.26 1.374 14.794 2.83 67.476 13.178 118.702 1.186 91.648-21.396 217.52-110.026 160.904-298.658z" />
<glyph unicode="&#xe621;" d="M428.222 493.19c-144.194-6.656-260.742-83.888-260.742-180.224 0-96.202 116.574-168.528 260.742-161.792 144.302 6.576 261.094 96.876 261.094 193.078 0 96.12-116.79 155.594-261.094 148.938zM526.472 250.88c-44.168-56.994-131.53-84.802-216.36-38.858-40.394 21.908-38.884 64.916-38.884 64.916s-16.76 135.842 128.27 152.818c145.192 16.816 171.116-121.856 126.976-178.876zM429.19 341.854c-9.298-6.736-11.182-19.618-6.144-27.62 4.85-8.22 16.142-9.162 25.276-2.318 8.974 7.086 12.45 19.428 7.572 27.62-4.798 7.976-15.952 10.294-26.704 2.318zM360.448 323.154c-27.108-2.802-46.484-26.408-46.484-48.99 0-22.636 21.826-38.264 48.882-35.086 26.974 3.072 48.908 23.928 48.908 46.484 0.054 22.636-20.184 40.582-51.308 37.592zM868.082 960h-712.22c-86.096 0-155.89-69.794-155.89-155.89v-712.22c0-86.096 69.794-155.89 155.89-155.89h712.22c86.096 0 155.89 69.794 155.89 155.89v712.22c0 86.096-69.768 155.89-155.89 155.89zM818.202 280.386c-59.446-126.274-255.462-187.716-400.734-176.344-138.050 10.86-315.526 56.724-333.878 223.798 0 0-9.7 75.668 63.65 173.568 0 0 105.498 147.322 228.378 189.36 122.988 41.85 137.35-28.968 137.35-70.818-6.548-35.49-18.782-56.374 27.38-42.038 0 0 120.886 56.076 170.658 6.332 40.126-40.152 6.63-95.42 6.63-95.42s-16.654-18.404 17.624-25.034c34.358-6.872 142.362-56.914 82.944-183.404zM698.988 629.274c-13.15 0-23.714 10.644-23.714 23.688 0 13.286 10.562 23.956 23.714 23.956 0 0 148.212 27.404 130.48-131.852 0-0.942-0.108-1.698-0.322-2.534-1.672-11.29-11.586-19.94-23.256-19.94-13.204 0-23.956 10.562-23.956 23.74 0-0.026 23.498 106.416-82.944 82.944zM949.518 501.894h-0.216c-3.908-26.948-17.274-29.104-33.198-29.104-19.052 0-34.438 11.964-34.438 31.044 0 16.52 6.846 33.308 6.846 33.308 2.020 6.952 18.136 50.176-10.644 114.796-52.708 88.522-158.856 89.816-171.384 84.776-12.638-4.958-31.286-7.436-31.286-7.436-19.188 0-34.548 15.602-34.548 34.572 0 15.926 10.644 29.4 25.196 33.524 0 0 0.322 0.538 0.808 0.62 1.052 0.216 2.13 1.268 3.26 1.374 14.794 2.83 67.476 13.178 118.702 1.186 91.648-21.396 217.52-110.026 160.904-298.658z" /> <glyph unicode="&#xe623;" glyph-name="book" d="M512 768c0 0-128 128-512 128v-768c388 0 512-128 512-128s124 128 512 128v768c-384 0-512-128-512-128zM128 768c162.688-13.632 262.496-51.264 320-81.76v-515.488c-57.504 30.368-157.312 68-320 81.76v515.488zM896 252.512c-162.752-13.76-262.496-51.328-320-81.76v515.488c57.504 30.496 157.248 68.128 320 81.76v-515.488z" />
<glyph unicode="&#xe622;" d="M1023.972 804.11c0 86.096-69.794 155.89-155.89 155.89h-712.22c-86.096 0-155.89-69.794-155.89-155.89v-712.22c0-86.096 69.794-155.89 155.89-155.89h712.22c86.096 0 155.89 69.794 155.89 155.89v712.22zM133.524 60.794c1.024-12.316-8.218-43.952-20.562-43.952h-2.264c-11.426 0-21.18 28.24-22.284 39.962-17.058 190.14 42.982 344.172 96.66 423.692 19.32 28.916 39.128 52.708 57.074 71.626-4.474 10.24-6.98 21.558-6.98 33.524 0 46.216 37.646 83.592 83.86 83.592 46.484 0 83.7-37.376 83.7-83.592 0-46.51-37.24-83.86-83.7-83.86-17.838 0-34.276 5.632-47.94 15.092-15.872-16.95-33.064-47.884-49.746-73.054-71.114-106.442-100.892-241.986-87.82-383.030zM315.31 328.004c-19.080 0-38.4 2.102-56.832 6.198-12.18 2.884-19.43 15.010-16.924 26.894 2.938 12.26 14.794 19.832 26.892 16.976 15.226-3.504 31.016-5.202 46.86-5.202 114.878 0 208.194 93.32 208.194 207.926 0 114.634-93.32 207.952-208.194 207.952-114.606 0-208.088-93.318-208.088-207.952 0-33.254 7.6-65.268 22.824-94.856 5.848-10.94 1.482-24.522-9.862-30.1-11.076-5.902-24.414-1.536-30.154 9.566-18.296 35.382-27.756 75.508-27.756 115.362 0 139.534 113.474 253.036 253.036 253.036 139.642 0 253.196-113.502 253.196-253.036-0.028-139.292-113.582-252.766-253.198-252.766zM785.866 622.162c-22.42-0.486-45.218 4.204-65.698 13.932-71.464 34.008-101.996 119.726-67.988 191.272 33.9 71.49 119.726 101.996 191.272 67.988 71.49-33.874 102.024-119.836 67.852-191.112-4.958-9.836-10.428-19.212-17.14-27.674-4.23-5.47-12.45-6.466-17.948-2.102s-6.36 12.45-1.994 17.948c5.496 6.846 10.212 14.712 13.958 22.798 28.024 58.718 3.1 129.346-55.808 156.968-58.798 28.024-129.186 3.126-156.968-55.942-28.294-58.664-3.26-129.052 55.7-157.076 17.058-8.004 35.462-11.992 54.056-11.236 7.086 0.134 13.068-5.336 13.204-12.45 0.322-7.114-5.308-12.934-12.504-13.312zM1001.284 534.448c-0.162-0.648-0.432-0.862-0.432-1.24-2.938-5.9-9.674-8.73-16.062-6.466-101.538 37.484-155.972 101.026-183.646 147.86-9.944 16.95-17.164 33.038-22.42 46.7-6.576 0.162-12.854 1.886-18.782 4.5-24.064 11.454-34.062 39.614-22.69 63.408 11.21 23.688 39.478 33.792 63.272 22.42 23.688-11.318 33.658-39.64 22.582-63.272-4.366-9-11.346-16.194-19.482-20.696 5.012-12.476 11.48-26.302 20.318-40.986 37.16-62.41 94.316-108.168 169.58-135.924 6.71-2.614 9.972-9.702 7.76-16.302z" /> <glyph unicode="&#xe624;" glyph-name="git" d="M1004.692 493.606l-447.096 447.080c-25.738 25.754-67.496 25.754-93.268 0l-103.882-103.876 78.17-78.17c12.532 5.996 26.564 9.36 41.384 9.36 53.020 0 96-42.98 96-96 0-14.82-3.364-28.854-9.362-41.386l127.976-127.974c12.532 5.996 26.566 9.36 41.386 9.36 53.020 0 96-42.98 96-96s-42.98-96-96-96-96 42.98-96 96c0 14.82 3.364 28.854 9.362 41.386l-127.976 127.974c-3.042-1.456-6.176-2.742-9.384-3.876v-266.968c37.282-13.182 64-48.718 64-90.516 0-53.020-42.98-96-96-96s-96 42.98-96 96c0 41.796 26.718 77.334 64 90.516v266.968c-37.282 13.18-64 48.72-64 90.516 0 14.82 3.364 28.852 9.36 41.384l-78.17 78.17-295.892-295.876c-25.75-25.776-25.75-67.534 0-93.288l447.12-447.080c25.738-25.75 67.484-25.75 93.268 0l445.006 445.006c25.758 25.762 25.758 67.54-0.002 93.29z" />
<glyph unicode="&#xe623;" d="M512 768c0 0-128 128-512 128v-768c388 0 512-128 512-128s124 128 512 128v768c-384 0-512-128-512-128zM128 768c162.688-13.632 262.496-51.264 320-81.76v-515.488c-57.504 30.368-157.312 68-320 81.76v515.488zM896 252.512c-162.752-13.76-262.496-51.328-320-81.76v515.488c57.504 30.496 157.248 68.128 320 81.76v-515.488z" /> <glyph unicode="&#xe900;" glyph-name="qqz" d="M761.562 521.546l-305.314-229.928s122.534-19.085 307.335-16.875l-8.413 38.021 263.313 239.505c4.84 4.404 6.694 11.413 4.708 17.804-1.974 6.394-7.405 10.93-13.783 11.494l-347.126 31.289-135.62 336.932c-2.479 6.184-8.268 10.213-14.657 10.213-6.397 0-12.184-4.029-14.665-10.213l-135.621-336.932-347.128-31.289c-6.374-0.563-11.801-5.1-13.781-11.494-1.987-6.392-0.13-13.401 4.713-17.804l263.31-239.505-78.925-356.295c-1.445-6.521 0.962-13.347 6.142-17.317 5.196-3.9 12.107-4.202 17.608-0.752l298.345 188.885 298.347-188.885c2.554-1.655 5.395-2.401 8.235-2.401 3.299 0 6.583 1.049 9.366 3.146 5.182 3.98 7.593 10.803 6.149 17.325l-62.412 281.762c26.77 14.131 56.254 38.051 56.254 38.051s-116.838-59.919-536.289-30.65l303.994 231.311s-11.952 19.514-392.923 33.442c-25.494 0.938 310.6 66.909 558.84 11.159z" />
<glyph unicode="&#xe624;" d="M1004.692 493.606l-447.096 447.080c-25.738 25.754-67.496 25.754-93.268 0l-103.882-103.876 78.17-78.17c12.532 5.996 26.564 9.36 41.384 9.36 53.020 0 96-42.98 96-96 0-14.82-3.364-28.854-9.362-41.386l127.976-127.974c12.532 5.996 26.566 9.36 41.386 9.36 53.020 0 96-42.98 96-96s-42.98-96-96-96-96 42.98-96 96c0 14.82 3.364 28.854 9.362 41.386l-127.976 127.974c-3.042-1.456-6.176-2.742-9.384-3.876v-266.968c37.282-13.182 64-48.718 64-90.516 0-53.020-42.98-96-96-96s-96 42.98-96 96c0 41.796 26.718 77.334 64 90.516v266.968c-37.282 13.18-64 48.72-64 90.516 0 14.82 3.364 28.852 9.36 41.384l-78.17 78.17-295.892-295.876c-25.75-25.776-25.75-67.534 0-93.288l447.12-447.080c25.738-25.75 67.484-25.75 93.268 0l445.006 445.006c25.758 25.762 25.758 67.54-0.002 93.29z" /> <glyph unicode="&#xe9d7;" glyph-name="start" d="M1024 562.95l-353.78 51.408-158.22 320.582-158.216-320.582-353.784-51.408 256-249.538-60.432-352.352 316.432 166.358 316.432-166.358-60.434 352.352 256.002 249.538zM512 206.502l-223.462-117.48 42.676 248.83-180.786 176.222 249.84 36.304 111.732 226.396 111.736-226.396 249.836-36.304-180.788-176.222 42.678-248.83-223.462 117.48z" />
<glyph unicode="&#xe9d7;" d="M1024 562.95l-353.78 51.408-158.22 320.582-158.216-320.582-353.784-51.408 256-249.538-60.432-352.352 316.432 166.358 316.432-166.358-60.434 352.352 256.002 249.538zM512 206.502l-223.462-117.48 42.676 248.83-180.786 176.222 249.84 36.304 111.732 226.396 111.736-226.396 249.836-36.304-180.788-176.222 42.678-248.83-223.462 117.48z" /> <glyph unicode="&#xf00a;" glyph-name="github" d="M512 960c-282.75 0-512-229.25-512-512 0-226.25 146.688-418.126 350.156-485.812 25.594-4.688 34.938 11.126 34.938 24.626 0 12.188-0.468 52.562-0.718 95.312-142.376-30.938-172.468 60.376-172.468 60.376-23.312 59.126-56.844 74.876-56.844 74.876-46.532 31.75 3.53 31.126 3.53 31.126 51.406-3.562 78.47-52.75 78.47-52.75 45.688-78.25 119.876-55.626 149-42.5 4.654 33 17.904 55.626 32.5 68.376-113.656 12.938-233.218 56.876-233.218 253.062 0 55.938 19.968 101.562 52.656 137.406-5.218 13-22.844 65.094 5.062 135.562 0 0 42.938 13.75 140.812-52.5 40.812 11.406 84.594 17.032 128.126 17.22 43.5-0.188 87.312-5.876 128.188-17.28 97.688 66.312 140.688 52.5 140.688 52.5 28-70.532 10.376-122.562 5.126-135.5 32.812-35.844 52.626-81.47 52.626-137.406 0-196.688-119.75-240-233.812-252.688 18.438-15.876 34.75-47 34.75-94.75 0-68.438-0.688-123.626-0.688-140.5 0-13.626 9.312-29.562 35.25-24.562 203.312 67.812 349.876 259.688 349.876 485.812 0 282.75-229.25 512-512 512z" />
<glyph unicode="&#xf00a;" d="M512 960c-282.75 0-512-229.25-512-512 0-226.25 146.688-418.126 350.156-485.812 25.594-4.688 34.938 11.126 34.938 24.626 0 12.188-0.468 52.562-0.718 95.312-142.376-30.938-172.468 60.376-172.468 60.376-23.312 59.126-56.844 74.876-56.844 74.876-46.532 31.75 3.53 31.126 3.53 31.126 51.406-3.562 78.47-52.75 78.47-52.75 45.688-78.25 119.876-55.626 149-42.5 4.654 33 17.904 55.626 32.5 68.376-113.656 12.938-233.218 56.876-233.218 253.062 0 55.938 19.968 101.562 52.656 137.406-5.218 13-22.844 65.094 5.062 135.562 0 0 42.938 13.75 140.812-52.5 40.812 11.406 84.594 17.032 128.126 17.22 43.5-0.188 87.312-5.876 128.188-17.28 97.688 66.312 140.688 52.5 140.688 52.5 28-70.532 10.376-122.562 5.126-135.5 32.812-35.844 52.626-81.47 52.626-137.406 0-196.688-119.75-240-233.812-252.688 18.438-15.876 34.75-47 34.75-94.75 0-68.438-0.688-123.626-0.688-140.5 0-13.626 9.312-29.562 35.25-24.562 203.312 67.812 349.876 259.688 349.876 485.812 0 282.75-229.25 512-512 512z" /> <glyph unicode="&#xf021;" glyph-name="refresh" d="M863.428 356.572q0-2.856-0.572-4-36.572-153.144-153.144-248.286t-273.142-95.144q-83.43 0-161.43 31.428t-139.144 89.714l-73.714-73.714q-10.856-10.856-25.714-10.856t-25.714 10.856-10.856 25.714v256q0 14.856 10.856 25.714t25.714 10.856h256q14.856 0 25.714-10.856t10.858-25.714-10.856-25.714l-78.286-78.286q40.57-37.714 92-58.286t106.858-20.572q76.572 0 142.856 37.144t106.286 102.286q6.286 9.714 30.286 66.856 4.572 13.144 17.144 13.144h109.714q7.428 0 12.856-5.428t5.428-12.856zM877.714 813.714v-256q0-14.856-10.856-25.714t-25.714-10.856h-256q-14.856 0-25.714 10.856t-10.856 25.714 10.856 25.714l78.856 78.856q-84.572 78.286-199.428 78.286-76.572 0-142.856-37.142t-106.286-102.286q-6.286-9.714-30.286-66.858-4.57-13.144-17.144-13.144h-113.714q-7.43 0-12.856 5.428t-5.43 12.856v4q37.144 153.144 154.286 248.286t274.286 95.142q83.428 0 162.286-31.714t140-89.428l74.286 73.714q10.856 10.856 25.714 10.856t25.714-10.856 10.856-25.714z" />
<glyph unicode="&#xf021;" d="M863.428 356.572q0-2.856-0.572-4-36.572-153.144-153.144-248.286t-273.142-95.144q-83.43 0-161.43 31.428t-139.144 89.714l-73.714-73.714q-10.856-10.856-25.714-10.856t-25.714 10.856-10.856 25.714v256q0 14.856 10.856 25.714t25.714 10.856h256q14.856 0 25.714-10.856t10.858-25.714-10.856-25.714l-78.286-78.286q40.57-37.714 92-58.286t106.858-20.572q76.572 0 142.856 37.144t106.286 102.286q6.286 9.714 30.286 66.856 4.572 13.144 17.144 13.144h109.714q7.428 0 12.856-5.428t5.428-12.856zM877.714 813.714v-256q0-14.856-10.856-25.714t-25.714-10.856h-256q-14.856 0-25.714 10.856t-10.856 25.714 10.856 25.714l78.856 78.856q-84.572 78.286-199.428 78.286-76.572 0-142.856-37.142t-106.286-102.286q-6.286-9.714-30.286-66.858-4.57-13.144-17.144-13.144h-113.714q-7.43 0-12.856 5.428t-5.43 12.856v4q37.144 153.144 154.286 248.286t274.286 95.142q83.428 0 162.286-31.714t140-89.428l74.286 73.714q10.856 10.856 25.714 10.856t25.714-10.856 10.856-25.714z" /> <glyph unicode="&#xf0c7;" glyph-name="save" d="M219.43 82.286h438.858v219.428h-438.858v-219.428zM731.428 82.286h73.144v512q0 8-5.714 22t-11.428 19.714l-160.572 160.57q-5.714 5.714-19.428 11.43t-22.286 5.714v-237.714q0-22.858-16-38.858t-38.856-16h-329.142q-22.856 0-38.856 16t-16 38.856v237.714h-73.144v-731.43h73.144v237.714q0 22.856 16 38.856t38.856 16h475.43q22.856 0 38.856-16t16-38.856v-237.714zM512 612.572v182.856q0 7.428-5.428 12.858t-12.856 5.428h-109.714q-7.428 0-12.858-5.428t-5.428-12.858v-182.856q0-7.428 5.428-12.856t12.856-5.428h109.714q7.428 0 12.858 5.428t5.428 12.856zM877.714 594.286v-530.286q0-22.856-16-38.856t-38.856-16h-768q-22.856 0-38.856 16t-16 38.856v768q0 22.856 16 38.856t38.856 16h530.286q22.856 0 50.286-11.428t43.428-27.428l160-160q16-16 27.428-43.428t11.428-50.286z" />
<glyph unicode="&#xf05b;" d="M684 374.856h-62.286q-14.856 0-25.714 10.856t-10.856 25.714v73.144q0 14.856 10.856 25.714t25.714 10.858h62.286q-18.286 61.714-64.286 107.714t-107.714 64.286v-62.286q0-14.858-10.858-25.714t-25.714-10.856h-73.142q-14.856 0-25.714 10.856t-10.858 25.714v62.286q-61.714-18.286-107.714-64.286t-64.286-107.714h62.286q14.858 0 25.714-10.856t10.856-25.714v-73.142q0-14.856-10.856-25.714t-25.714-10.856h-62.286q18.286-61.714 64.286-107.714t107.714-64.286v62.286q0 14.856 10.856 25.714t25.714 10.856h73.142q14.856 0 25.714-10.856t10.858-25.714v-62.286q61.714 18.286 107.714 64.286t64.286 107.714zM877.714 484.572v-73.144q0-14.856-10.856-25.714t-25.714-10.856h-81.714q-21.144-92-88.286-159.144t-159.144-88.286v-81.714q0-14.856-10.856-25.714t-25.714-10.856h-73.142q-14.856 0-25.714 10.856t-10.858 25.714v81.714q-92 21.144-159.144 88.286t-88.286 159.144h-81.714q-14.856 0-25.714 10.856t-10.856 25.714v73.144q0 14.856 10.856 25.714t25.714 10.858h81.714q21.144 92 88.286 159.144t159.144 88.286v81.714q0 14.856 10.856 25.714t25.714 10.856h73.142q14.856 0 25.714-10.856t10.858-25.714v-81.714q92-21.142 159.144-88.286t88.286-159.144h81.714q14.856 0 25.714-10.856t10.856-25.714z" /> <glyph unicode="&#xf0ed;" glyph-name="export" horiz-adv-x="1097" d="M731.429 429.714q0 8-5.143 13.143t-13.143 5.143h-128v201.143q0 7.429-5.429 12.857t-12.857 5.429h-109.714q-7.429 0-12.857-5.429t-5.429-12.857v-201.143h-128q-7.429 0-12.857-5.429t-5.429-12.857q0-8 5.143-13.143l201.143-201.143q5.143-5.143 13.143-5.143t13.143 5.143l200.571 200.571q5.714 6.857 5.714 13.714zM1097.143 301.714q0-90.857-64.286-155.143t-155.143-64.286h-621.714q-105.714 0-180.857 75.143t-75.143 180.857q0 74.286 40 137.143t107.429 94.286q-1.143 17.143-1.143 24.571 0 121.143 85.714 206.857t206.857 85.714q89.143 0 163.143-49.714t107.714-132q40.571 35.429 94.857 35.429 60.571 0 103.429-42.857t42.857-103.429q0-43.429-23.429-78.857 74.286-17.714 122-77.429t47.714-136.286z" />
<glyph unicode="&#xf0c7;" d="M219.43 82.286h438.858v219.428h-438.858v-219.428zM731.428 82.286h73.144v512q0 8-5.714 22t-11.428 19.714l-160.572 160.57q-5.714 5.714-19.428 11.43t-22.286 5.714v-237.714q0-22.858-16-38.858t-38.856-16h-329.142q-22.856 0-38.856 16t-16 38.856v237.714h-73.144v-731.43h73.144v237.714q0 22.856 16 38.856t38.856 16h475.43q22.856 0 38.856-16t16-38.856v-237.714zM512 612.572v182.856q0 7.428-5.428 12.858t-12.856 5.428h-109.714q-7.428 0-12.858-5.428t-5.428-12.858v-182.856q0-7.428 5.428-12.856t12.856-5.428h109.714q7.428 0 12.858 5.428t5.428 12.856zM877.714 594.286v-530.286q0-22.856-16-38.856t-38.856-16h-768q-22.856 0-38.856 16t-16 38.856v768q0 22.856 16 38.856t38.856 16h530.286q22.856 0 50.286-11.428t43.428-27.428l160-160q16-16 27.428-43.428t11.428-50.286z" /> <glyph unicode="&#xf0ee;" glyph-name="import" horiz-adv-x="1097" d="M731.429 466.286q0 8-5.143 13.143l-201.143 201.143q-5.143 5.143-13.143 5.143t-13.143-5.143l-200.571-200.571q-5.714-6.857-5.714-13.714 0-8 5.143-13.143t13.143-5.143h128v-201.143q0-7.429 5.429-12.857t12.857-5.429h109.714q7.429 0 12.857 5.429t5.429 12.857v201.143h128q7.429 0 12.857 5.429t5.429 12.857zM1097.143 301.714q0-90.857-64.286-155.143t-155.143-64.286h-621.714q-105.714 0-180.857 75.143t-75.143 180.857q0 74.286 40 137.143t107.429 94.286q-1.143 17.143-1.143 24.571 0 121.143 85.714 206.857t206.857 85.714q89.143 0 163.143-49.714t107.714-132q40.571 35.429 94.857 35.429 60.571 0 103.429-42.857t42.857-103.429q0-43.429-23.429-78.857 74.286-17.714 122-77.429t47.714-136.286z" />
<glyph unicode="&#xf0ed;" d="M731.429 420.571q0 8-5.143 13.143t-13.143 5.143h-128v201.143q0 7.429-5.429 12.857t-12.857 5.429h-109.714q-7.429 0-12.857-5.429t-5.429-12.857v-201.143h-128q-7.429 0-12.857-5.429t-5.429-12.857q0-8 5.143-13.143l201.143-201.143q5.143-5.143 13.143-5.143t13.143 5.143l200.571 200.571q5.714 6.857 5.714 13.714zM1097.143 292.571q0-90.857-64.286-155.143t-155.143-64.286h-621.714q-105.714 0-180.857 75.143t-75.143 180.857q0 74.286 40 137.143t107.429 94.286q-1.143 17.143-1.143 24.571 0 121.143 85.714 206.857t206.857 85.714q89.143 0 163.143-49.714t107.714-132q40.571 35.429 94.857 35.429 60.571 0 103.429-42.857t42.857-103.429q0-43.429-23.429-78.857 74.286-17.714 122-77.429t47.714-136.286z" horiz-adv-x="1097" /> <glyph unicode="&#xf11c;" glyph-name="keyboard" horiz-adv-x="1097" d="M219.429 292.571v-54.857q0-9.143-9.143-9.143h-54.857q-9.143 0-9.143 9.143v54.857q0 9.143 9.143 9.143h54.857q9.143 0 9.143-9.143zM292.571 438.857v-54.857q0-9.143-9.143-9.143h-128q-9.143 0-9.143 9.143v54.857q0 9.143 9.143 9.143h128q9.143 0 9.143-9.143zM219.429 585.143v-54.857q0-9.143-9.143-9.143h-54.857q-9.143 0-9.143 9.143v54.857q0 9.143 9.143 9.143h54.857q9.143 0 9.143-9.143zM804.571 292.571v-54.857q0-9.143-9.143-9.143h-493.714q-9.143 0-9.143 9.143v54.857q0 9.143 9.143 9.143h493.714q9.143 0 9.143-9.143zM438.857 438.857v-54.857q0-9.143-9.143-9.143h-54.857q-9.143 0-9.143 9.143v54.857q0 9.143 9.143 9.143h54.857q9.143 0 9.143-9.143zM365.714 585.143v-54.857q0-9.143-9.143-9.143h-54.857q-9.143 0-9.143 9.143v54.857q0 9.143 9.143 9.143h54.857q9.143 0 9.143-9.143zM585.143 438.857v-54.857q0-9.143-9.143-9.143h-54.857q-9.143 0-9.143 9.143v54.857q0 9.143 9.143 9.143h54.857q9.143 0 9.143-9.143zM512 585.143v-54.857q0-9.143-9.143-9.143h-54.857q-9.143 0-9.143 9.143v54.857q0 9.143 9.143 9.143h54.857q9.143 0 9.143-9.143zM731.429 438.857v-54.857q0-9.143-9.143-9.143h-54.857q-9.143 0-9.143 9.143v54.857q0 9.143 9.143 9.143h54.857q9.143 0 9.143-9.143zM950.857 292.571v-54.857q0-9.143-9.143-9.143h-54.857q-9.143 0-9.143 9.143v54.857q0 9.143 9.143 9.143h54.857q9.143 0 9.143-9.143zM658.286 585.143v-54.857q0-9.143-9.143-9.143h-54.857q-9.143 0-9.143 9.143v54.857q0 9.143 9.143 9.143h54.857q9.143 0 9.143-9.143zM804.571 585.143v-54.857q0-9.143-9.143-9.143h-54.857q-9.143 0-9.143 9.143v54.857q0 9.143 9.143 9.143h54.857q9.143 0 9.143-9.143zM950.857 585.143v-201.143q0-9.143-9.143-9.143h-128q-9.143 0-9.143 9.143v54.857q0 9.143 9.143 9.143h64v137.143q0 9.143 9.143 9.143h54.857q9.143 0 9.143-9.143zM1024 155.429v512h-950.857v-512h950.857zM1097.143 667.429v-512q0-30.286-21.429-51.714t-51.714-21.429h-950.857q-30.286 0-51.714 21.429t-21.429 51.714v512q0 30.286 21.429 51.714t51.714 21.429h950.857q30.286 0 51.714-21.429t21.429-51.714z" />
<glyph unicode="&#xf0ee;" d="M731.429 457.143q0 8-5.143 13.143l-201.143 201.143q-5.143 5.143-13.143 5.143t-13.143-5.143l-200.571-200.571q-5.714-6.857-5.714-13.714 0-8 5.143-13.143t13.143-5.143h128v-201.143q0-7.429 5.429-12.857t12.857-5.429h109.714q7.429 0 12.857 5.429t5.429 12.857v201.143h128q7.429 0 12.857 5.429t5.429 12.857zM1097.143 292.571q0-90.857-64.286-155.143t-155.143-64.286h-621.714q-105.714 0-180.857 75.143t-75.143 180.857q0 74.286 40 137.143t107.429 94.286q-1.143 17.143-1.143 24.571 0 121.143 85.714 206.857t206.857 85.714q89.143 0 163.143-49.714t107.714-132q40.571 35.429 94.857 35.429 60.571 0 103.429-42.857t42.857-103.429q0-43.429-23.429-78.857 74.286-17.714 122-77.429t47.714-136.286z" horiz-adv-x="1097" /> <glyph unicode="&#xf148;" glyph-name="moveup" horiz-adv-x="585" d="M580.891 578.932q-10.269-21.109-33.090-21.109h-109.537v-492.917q0-7.987-5.135-13.122t-13.122-5.135h-401.637q-11.981 0-16.544 10.269-4.564 11.411 2.282 19.968l91.282 109.537q5.135 6.276 14.263 6.276h182.562v365.124h-109.537q-22.82 0-33.090 21.109-9.698 21.109 5.135 38.794l182.562 219.075q10.269 12.551 27.955 12.551t27.955-12.551l182.562-219.075q15.404-18.257 5.135-38.794z" />
<glyph unicode="&#xf11c;" d="M219.429 283.428v-54.857q0-9.143-9.143-9.143h-54.857q-9.143 0-9.143 9.143v54.857q0 9.143 9.143 9.143h54.857q9.143 0 9.143-9.143zM292.571 429.714v-54.857q0-9.143-9.143-9.143h-128q-9.143 0-9.143 9.143v54.857q0 9.143 9.143 9.143h128q9.143 0 9.143-9.143zM219.429 576v-54.857q0-9.143-9.143-9.143h-54.857q-9.143 0-9.143 9.143v54.857q0 9.143 9.143 9.143h54.857q9.143 0 9.143-9.143zM804.571 283.428v-54.857q0-9.143-9.143-9.143h-493.714q-9.143 0-9.143 9.143v54.857q0 9.143 9.143 9.143h493.714q9.143 0 9.143-9.143zM438.857 429.714v-54.857q0-9.143-9.143-9.143h-54.857q-9.143 0-9.143 9.143v54.857q0 9.143 9.143 9.143h54.857q9.143 0 9.143-9.143zM365.714 576v-54.857q0-9.143-9.143-9.143h-54.857q-9.143 0-9.143 9.143v54.857q0 9.143 9.143 9.143h54.857q9.143 0 9.143-9.143zM585.143 429.714v-54.857q0-9.143-9.143-9.143h-54.857q-9.143 0-9.143 9.143v54.857q0 9.143 9.143 9.143h54.857q9.143 0 9.143-9.143zM512 576v-54.857q0-9.143-9.143-9.143h-54.857q-9.143 0-9.143 9.143v54.857q0 9.143 9.143 9.143h54.857q9.143 0 9.143-9.143zM731.429 429.714v-54.857q0-9.143-9.143-9.143h-54.857q-9.143 0-9.143 9.143v54.857q0 9.143 9.143 9.143h54.857q9.143 0 9.143-9.143zM950.857 283.428v-54.857q0-9.143-9.143-9.143h-54.857q-9.143 0-9.143 9.143v54.857q0 9.143 9.143 9.143h54.857q9.143 0 9.143-9.143zM658.286 576v-54.857q0-9.143-9.143-9.143h-54.857q-9.143 0-9.143 9.143v54.857q0 9.143 9.143 9.143h54.857q9.143 0 9.143-9.143zM804.571 576v-54.857q0-9.143-9.143-9.143h-54.857q-9.143 0-9.143 9.143v54.857q0 9.143 9.143 9.143h54.857q9.143 0 9.143-9.143zM950.857 576v-201.143q0-9.143-9.143-9.143h-128q-9.143 0-9.143 9.143v54.857q0 9.143 9.143 9.143h64v137.143q0 9.143 9.143 9.143h54.857q9.143 0 9.143-9.143zM1024 146.286v512h-950.857v-512h950.857zM1097.143 658.286v-512q0-30.286-21.429-51.714t-51.714-21.429h-950.857q-30.286 0-51.714 21.429t-21.429 51.714v512q0 30.286 21.429 51.714t51.714 21.429h950.857q30.286 0 51.714-21.429t21.429-51.714z" horiz-adv-x="1097" /> <glyph unicode="&#xf149;" glyph-name="movedown" horiz-adv-x="585" d="M18.286 813.714h402.286q7.429 0 12.857-5.429t5.429-13.429v-493.143h109.714q22.857 0 33.143-21.143t-5.143-39.429l-182.857-219.429q-10.286-12.571-28-12.571t-28 12.571l-182.857 219.429q-14.857 17.714-5.143 39.429 10.286 21.143 33.143 21.143h109.714v365.714h-182.857q-8 0-14.286 6.286l-91.429 109.714q-7.429 8-2.286 19.429 5.143 10.857 16.571 10.857z" />
<glyph unicode="&#xf148;" d="M581.714 606.286q-10.286-21.143-33.143-21.143h-109.714v-493.714q0-8-5.143-13.143t-13.143-5.143h-402.286q-12 0-16.571 10.286-4.571 11.429 2.286 20l91.429 109.714q5.143 6.286 14.286 6.286h182.857v365.714h-109.714q-22.857 0-33.143 21.143-9.714 21.143 5.143 38.857l182.857 219.429q10.286 12.571 28 12.571t28-12.571l182.857-219.429q15.429-18.286 5.143-38.857z" horiz-adv-x="585" />
<glyph unicode="&#xf149;" d="M18.286 804.571h402.286q7.429 0 12.857-5.429t5.429-13.429v-493.143h109.714q22.857 0 33.143-21.143t-5.143-39.429l-182.857-219.429q-10.286-12.571-28-12.571t-28 12.571l-182.857 219.429q-14.857 17.714-5.143 39.429 10.286 21.143 33.143 21.143h109.714v365.714h-182.857q-8 0-14.286 6.286l-91.429 109.714q-7.429 8-2.286 19.429 5.143 10.857 16.571 10.857z" horiz-adv-x="585" />
</font></defs></svg> </font></defs></svg>

Before

Width:  |  Height:  |  Size: 31 KiB

After

Width:  |  Height:  |  Size: 28 KiB

BIN
static/css/fonts/icomoon.ttf Normal file → Executable file

Binary file not shown.

BIN
static/css/fonts/icomoon.woff Normal file → Executable file

Binary file not shown.

View File

@ -1,11 +1,11 @@
/* /*
* Copyright (c) 2014-2017, b3log.org & hacpai.com * Copyright (c) 2014-2019, b3log.org & hacpai.com
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
* You may obtain a copy of the License at * You may obtain a copy of the License at
* *
* http://www.apache.org/licenses/LICENSE-2.0 * https://www.apache.org/licenses/LICENSE-2.0
* *
* Unless required by applicable law or agreed to in writing, software * Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, * distributed under the License is distributed on an "AS IS" BASIS,

View File

@ -1,11 +1,11 @@
/* /*
* Copyright (c) 2014-2017, b3log.org & hacpai.com * Copyright (c) 2014-2019, b3log.org & hacpai.com
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
* You may obtain a copy of the License at * You may obtain a copy of the License at
* *
* http://www.apache.org/licenses/LICENSE-2.0 * https://www.apache.org/licenses/LICENSE-2.0
* *
* Unless required by applicable law or agreed to in writing, software * Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, * distributed under the License is distributed on an "AS IS" BASIS,

View File

@ -1,11 +1,11 @@
/* /*
* Copyright (c) 2014-2017, b3log.org & hacpai.com * Copyright (c) 2014-2019, b3log.org & hacpai.com
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
* You may obtain a copy of the License at * You may obtain a copy of the License at
* *
* http://www.apache.org/licenses/LICENSE-2.0 * https://www.apache.org/licenses/LICENSE-2.0
* *
* Unless required by applicable law or agreed to in writing, software * Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, * distributed under the License is distributed on an "AS IS" BASIS,

View File

@ -1,11 +1,11 @@
/* /*
* Copyright (c) 2014-2017, b3log.org & hacpai.com * Copyright (c) 2014-2019, b3log.org & hacpai.com
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
* You may obtain a copy of the License at * You may obtain a copy of the License at
* *
* http://www.apache.org/licenses/LICENSE-2.0 * https://www.apache.org/licenses/LICENSE-2.0
* *
* Unless required by applicable law or agreed to in writing, software * Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, * distributed under the License is distributed on an "AS IS" BASIS,

View File

@ -1,11 +1,11 @@
/* /*
* Copyright (c) 2014-2017, b3log.org & hacpai.com * Copyright (c) 2014-2019, b3log.org & hacpai.com
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
* You may obtain a copy of the License at * You may obtain a copy of the License at
* *
* http://www.apache.org/licenses/LICENSE-2.0 * https://www.apache.org/licenses/LICENSE-2.0
* *
* Unless required by applicable law or agreed to in writing, software * Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, * distributed under the License is distributed on an "AS IS" BASIS,

View File

@ -1,11 +1,11 @@
/* /*
* Copyright (c) 2014-2017, b3log.org & hacpai.com * Copyright (c) 2014-2019, b3log.org & hacpai.com
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
* You may obtain a copy of the License at * You may obtain a copy of the License at
* *
* http://www.apache.org/licenses/LICENSE-2.0 * https://www.apache.org/licenses/LICENSE-2.0
* *
* Unless required by applicable law or agreed to in writing, software * Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, * distributed under the License is distributed on an "AS IS" BASIS,

View File

@ -1,11 +1,11 @@
/* /*
* Copyright (c) 2014-2017, b3log.org & hacpai.com * Copyright (c) 2014-2019, b3log.org & hacpai.com
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
* You may obtain a copy of the License at * You may obtain a copy of the License at
* *
* http://www.apache.org/licenses/LICENSE-2.0 * https://www.apache.org/licenses/LICENSE-2.0
* *
* Unless required by applicable law or agreed to in writing, software * Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, * distributed under the License is distributed on an "AS IS" BASIS,

View File

@ -1,11 +1,11 @@
/* /*
* Copyright (c) 2014-2017, b3log.org & hacpai.com * Copyright (c) 2014-2019, b3log.org & hacpai.com
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
* You may obtain a copy of the License at * You may obtain a copy of the License at
* *
* http://www.apache.org/licenses/LICENSE-2.0 * https://www.apache.org/licenses/LICENSE-2.0
* *
* Unless required by applicable law or agreed to in writing, software * Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, * distributed under the License is distributed on an "AS IS" BASIS,

View File

@ -1,5 +1,5 @@
.dialog-close-icon,.dialog-close-icon:hover{text-decoration:none}.dialog-background{height:100%;left:0;opacity:.3;position:absolute;top:0;width:100%;filter:alpha(opacity=30);display:none;background-color:#000;z-index:99}.dialog-panel{position:absolute;z-index:100;display:none;-moz-user-select:none;user-select:none;box-shadow:0 2px 10px 1px #000}.dialog-title{float:left;line-height:22px;margin-left:3px;font-weight:700}.dialog-header-bg{height:23px;background-color:#bbb;cursor:move;width:100%}.dialog-close-icon{float:right;margin:3px}.dialog-main>div{width:100%}.dialog-footer{padding:10px;text-align:right}#dialogCloseEditor button,.dialog-footer button{margin:0 5px}#dialogAlert,#dialogRemoveConfirm,.dialog-form,.dialog-prompt{padding:10px 15px 0;overflow:hidden}.dialog-main input,.dialog-main select{width:100%;margin:2px auto}#dialogGoFilePrompt>ul{position:relative;height:260px;overflow:auto;margin-top:5px;background-color:#FFF;border:1px solid #919191}#dialogPreference{margin:10px}#dialogPreference .tabs-panel{padding:10px}#dialogPreference .preference{margin-bottom:10px}#dialogPreference img.gravatar{width:48px;height:48px} .dialog-close-icon,.dialog-close-icon:hover{text-decoration:none}.dialog-background{height:100%;left:0;opacity:.3;position:absolute;top:0;width:100%;filter:alpha(opacity=30);display:none;background-color:#000;z-index:99}.dialog-panel{position:absolute;z-index:100;display:none;-moz-user-select:none;user-select:none;box-shadow:0 2px 10px 1px #000}.dialog-title{float:left;line-height:22px;margin-left:3px;font-weight:700}.dialog-header-bg{height:23px;background-color:#bbb;cursor:move;width:100%}.dialog-close-icon{float:right;margin:3px}.dialog-main>div{width:100%}.dialog-footer{padding:10px;text-align:right}#dialogCloseEditor button,.dialog-footer button{margin:0 5px}#dialogAlert,#dialogRemoveConfirm,.dialog-form,.dialog-prompt{padding:10px 15px 0;overflow:hidden}.dialog-main input,.dialog-main select{width:100%;margin:2px auto}#dialogGoFilePrompt>ul{position:relative;height:260px;overflow:auto;margin-top:5px;background-color:#FFF;border:1px solid #919191}#dialogPreference{margin:10px}#dialogPreference .tabs-panel{padding:10px}#dialogPreference .preference{margin-bottom:10px}#dialogPreference img.gravatar{width:48px;height:48px}
body,ul{margin:0}body,button,input{font-family:Helvetica}.list li,body{overflow:hidden}::-webkit-scrollbar{background:0 0;width:16px;height:16px}::-webkit-scrollbar-corner{display:none;background-color:transparent}::-webkit-scrollbar-thumb{border:0 solid transparent;border-right-width:4px;border-left-width:4px;border-radius:9px;box-shadow:inset 0 0 0 1px rgba(128,128,128,.2),inset 0 0 0 4px rgba(128,128,128,.2)}::-webkit-scrollbar-thumb:horizontal{border-bottom-width:4px;border-top-width:4px}body{font-size:13px;color:#000}ul{padding:0;list-style:none}*{box-sizing:border-box}a{color:#4183c4;text-decoration:none}a:hover{text-decoration:underline}img{vertical-align:middle}.fn-left{float:left}.fn-right{float:right}.fn-clear:after,.fn-clear:before{display:table;content:""}.fn-clear:after{clear:both}.fn-none{display:none}.ft-small{color:#999;font-size:12px}.ft-red{color:#9d0000}.list li{cursor:pointer;line-height:20px;padding:0 3px;word-wrap:normal;word-break:normal;white-space:nowrap;text-overflow:ellipsis}.list li.selected,.list li:hover{background-color:#3875d7;color:#FFF}.list li.selected .ft-small,.list li:hover .ft-small{color:#FFF}@font-face{font-family:icomoon;src:url(fonts/icomoon.eot?35cb2z);src:url(fonts/icomoon.eot?#iefix35cb2z) format('embedded-opentype'),url(fonts/icomoon.woff?35cb2z) format('woff'),url(fonts/icomoon.ttf?35cb2z) format('truetype'),url(fonts/icomoon.svg?35cb2z#icomoon) format('svg');font-weight:400;font-style:normal}.font-ico,[class^=ico-]{font-family:icomoon;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;cursor:pointer;font-size:13px;line-height:20px}.ico-book:before{content:"\e623"}.ico-price:before{content:"\e616"}.ico-start:before{content:"\e9d7"}.ico-share:before{content:"\e61f"}.ico-github:before{content:"\f00a"}.ico-git:before{content:"\e624"}.ico-tencent:before{content:"\e622"}.ico-weibo:before{content:"\e621"}.ico-googleplus:before{content:"\e61a"}.ico-twitter:before{content:"\e61c"}.ico-email:before{content:"\e619"}.ico-facebook:before{content:"\e61b"}.ico-moveup:before{content:"\f148"}.ico-movedown:before{content:"\f149"}.ico-keyboard:before{content:"\f11c"}.ico-findfiles:before{content:"\e603"}.ico-find:before{content:"\e602"}.ico-editor:before{content:"\e604"}.ico-tree:before{content:"\e600"}.ico-build:before{content:"\e601"}.ico-notification:before{content:"\e607"}.ico-report:before{content:"\e605"}.ico-comment:before{content:"\e620"}.ico-goline:before{content:"\e61e"}.ico-info:before{content:"\e61d"}.ico-signup:before{content:"\e606"}.ico-signout:before{content:"\e618"}.ico-redo:before{content:"\e615"}.ico-undo:before{content:"\e60e"}.ico-about:before{content:"\e60d"}.ico-import:before{content:"\f0ee"}.ico-export:before{content:"\f0ed"}.ico-refresh:before{content:"\f021"}.ico-remove:before{content:"\e60b"}.ico-save:before{content:"\f0c7"}.ico-max:before{content:"\e609"}.ico-format:before{content:"\e612"}.ico-buildrun:before{content:"\e60c"}.ico-stop:before{content:"\e60f"}.ico-restore:before{content:"\e613"}.toolbars .ico-restore:before{content:"\e60a"}.ico-min:before{content:"\e614";position:absolute;right:5px}.ico-close:before{content:"\e611"} body,ul{margin:0}body,button,input{font-family:Helvetica}.list li,body{overflow:hidden}::-webkit-scrollbar{background:0 0;width:16px;height:16px}::-webkit-scrollbar-corner{display:none;background-color:transparent}::-webkit-scrollbar-thumb{border:0 solid transparent;border-right-width:4px;border-left-width:4px;border-radius:9px;box-shadow:inset 0 0 0 1px rgba(128,128,128,.2),inset 0 0 0 4px rgba(128,128,128,.2)}::-webkit-scrollbar-thumb:horizontal{border-bottom-width:4px;border-top-width:4px}body{font-size:13px;color:#000}ul{padding:0;list-style:none}*{box-sizing:border-box}a{color:#4183c4;text-decoration:none}a:hover{text-decoration:underline}img{vertical-align:middle}.fn-left{float:left}.fn-right{float:right}.fn-clear:after,.fn-clear:before{display:table;content:""}.fn-clear:after{clear:both}.fn-none{display:none}.ft-small{color:#999;font-size:12px}.ft-red{color:#9d0000}.list li{cursor:pointer;line-height:20px;padding:0 3px;word-wrap:normal;word-break:normal;white-space:nowrap;text-overflow:ellipsis}.list li.selected,.list li:hover{background-color:#3875d7;color:#FFF}.list li.selected .ft-small,.list li:hover .ft-small{color:#FFF}@font-face{font-family:icomoon;src:url(fonts/icomoon.eot?lqk80d);src:url(fonts/icomoon.eot?lqk80d#iefix) format('embedded-opentype'),url(fonts/icomoon.ttf?lqk80d) format('truetype'),url(fonts/icomoon.woff?lqk80d) format('woff'),url(fonts/icomoon.svg?lqk80d#icomoon) format('svg');font-weight:400;font-style:normal}[class*=" ico-"],[class^=ico-]{font-family:icomoon!important;speak:none;font-style:normal;font-weight:400;font-variant:normal;text-transform:none;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;cursor:pointer;font-size:13px;line-height:20px}.ico-qqz:before{content:"\e900"}.ico-find:before{content:"\e602"}.ico-findfiles:before{content:"\e603"}.ico-editor:before{content:"\e604"}.ico-notification:before{content:"\e607"}.ico-price:before{content:"\e616"}.ico-report:before{content:"\e605"}.ico-signup:before{content:"\e606"}.ico-git:before{content:"\e624"}.ico-book:before{content:"\e623"}.ico-start:before{content:"\e9d7";text-shadow:0 0 rgba(0,0,0,.4)}.ico-tree:before{content:"\e600"}.ico-build:before{content:"\e601"}.ico-export:before{content:"\f0ed"}.ico-import:before{content:"\f0ee"}.ico-keyboard:before{content:"\f11c"}.ico-moveup:before{content:"\f148"}.ico-movedown:before{content:"\f149"}.ico-weibo:before{content:"\e621"}.ico-uniE608:before{content:"\e608"}.ico-max:before{content:"\e609"}.ico-remove:before{content:"\e60b"}.ico-buildrun:before{content:"\e60c"}.ico-about:before{content:"\e60d"}.ico-undo:before{content:"\e60e"}.ico-stop:before{content:"\e60f"}.ico-close:before{content:"\e611";text-shadow:0 0 rgba(0,0,0,.4)}.ico-format:before{content:"\e612"}.ico-restore:before{content:"\e613"}.toolbars .ico-restore:before{content:"\e60a"}.ico-min:before{content:"\e614";position:absolute;right:5px}.ico-redo:before{content:"\e615"}.ico-uniE617:before{content:"\e617"}.ico-signout:before{content:"\e618"}.ico-email:before{content:"\e619"}.ico-googleplus:before{content:"\e61a"}.ico-facebook:before{content:"\e61b"}.ico-twitter:before{content:"\e61c"}.ico-info:before{content:"\e61d"}.ico-goline:before{content:"\e61e"}.ico-share:before{content:"\e61f"}.ico-comment:before{content:"\e620"}.ico-github:before{content:"\f00a"}.ico-refresh:before{content:"\f021"}.ico-save:before{content:"\f0c7"}
.frame li,.tabs>div{padding:0 5px;cursor:pointer}.footer .cursor,.frame li,.menu>ul>li>span,.notification-count,.tabs>div{cursor:pointer}.ico,.menu .split,.menu>ul>li,.tabs>div{float:left}.frame{position:absolute;width:320px;z-index:21;display:none}.frame li{line-height:25px}.frame li.disabled,.frame li.disabled .font-ico,.frame li.disabled:hover .font-ico{color:#999}.frame a{color:#000;text-decoration:none}.frame a:hover,.frame li:hover a{color:#FFF}.frame .space{display:inline-block;width:20px;height:15px}.frame .font-ico{margin-right:5px;width:15px;display:inline-block;text-align:center}.tabs{height:21px;overflow:hidden;width:100%}.tabs>div{line-height:20px;height:20px}.tabs>div>span.changed{font-weight:700}.tabs-panel{overflow:auto;flex:1;height:100%}.edit-exprinfo,.edit-panel{position:absolute;overflow:hidden}.menu{display:block!important}.menu>ul>li>span{font-size:12px;line-height:21px;padding:4px 7px}.menu .split{border-left:1px solid #919191;height:21px;margin:0 5px 0 0}.menu img.gravatar{float:left;margin:2px 8px;height:17px;width:17px;border-radius:9px}#buildRun{color:#6DB14C;font-size:19px}#buildRun.ico-stop{color:#9d0000;font-size:16px}.share-panel{position:absolute;z-index:20;width:226px;padding:5px 0;right:0;top:21px}.share-panel .font-ico{font-size:20px;transition:all .2s ease-out 0s;margin:0 5px;width:24px}.share-panel .font-ico:hover{transform:rotate(360deg)}.edit-panel{left:20%;width:60%;height:70%;flex-flow:column;display:flex}.toolbars{position:absolute;right:5px;top:1px}.ico{background-image:url(../images/ico-file.png);height:16px;margin:2px 0 0 -2px;width:16px}.edit-exprinfo{z-index:10;list-style:none;margin:0;padding:2px;-webkit-box-shadow:2px 3px 5px rgba(0,0,0,.2);-moz-box-shadow:2px 3px 5px rgba(0,0,0,.2);box-shadow:2px 3px 5px rgba(0,0,0,.2);border-radius:3px;border:1px solid silver;background:#fff;font-size:90%;max-height:20em;overflow-y:auto}.CodeMirror,.CodeMirror-hints{font-family:Consolas,'Courier New',monospace}.CodeMirror-hints .ico{margin:-1px 2px 0 -1px}.CodeMirror-focused .cm-matchhighlight{background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAIAAAACCAYAAABytg0kAAAAFklEQVQI12NgYGBgkKzc8x9CMDAwAAAmhwSbidEoSQAAAABJRU5ErkJggg==);background-position:bottom;background-repeat:repeat-x}.CodeMirror-hint{padding-right:18px;max-width:none}.CodeMirror-hint:hover{background:#08f;color:#fff}.CodeMirror div.CodeMirror-cursor{border-left:2px solid #333}.CodeMirror-gutter-filler,.CodeMirror-scrollbar-filler{background-color:transparent}.bottom-window-group{background-color:#fff;flex-flow:column}.bottom-window-group .output{font-family:Consolas,Courier New,monospace;padding:0 5px;line-height:16px;font-size:12px;overflow-x:scroll;outline:0}.bottom-window-group .output pre{margin:0;font-family:Consolas,'Courier New',monospace}.bottom-window-group .output .start-build,.bottom-window-group .output .start-get,.bottom-window-group .output .start-install,.bottom-window-group .output .start-test,.start-vet{color:#999}.bottom-window-group .output .build-succ,.bottom-window-group .output .get-succ,.bottom-window-group .output .install-succ,.bottom-window-group .output .test-succ,.vet-succ{color:#090}.bottom-window-group .output .build-error,.bottom-window-group .output .get-error,.bottom-window-group .output .install-error,.bottom-window-group .output .test-error,.vet-error{color:#9d0000}.bottom-window-group .output .stderr{color:gray;font-style:italic}.bottom-window-group .output .path{text-decoration:underline;cursor:pointer}.bottom-window-group table{width:100%}.bottom-window-group td{border-bottom:1px solid #919191;font-size:12px;line-height:19px}.bottom-window-group .notification{outline:0}.bottom-window-group .notification .severity,.bottom-window-group .notification .type{width:50px;padding:0 5px}.bottom-window-group .search{display:flex;flex-flow:column;outline:0}.footer{box-shadow:0 1px 0 0 rgba(255,255,255,.06) inset;padding-left:5px;line-height:18px;display:block!important}.notification-count{float:right;display:none;background-color:#9d0000;color:#FFF;margin:1px 5px;padding:0 2px;border-radius:3px;line-height:16px} .frame li,.tabs>div{padding:0 5px;cursor:pointer}.footer .cursor,.frame li,.menu>ul>li>span,.notification-count,.tabs>div{cursor:pointer}.ico,.menu .split,.menu>ul>li,.tabs>div{float:left}.frame{position:absolute;width:320px;z-index:21;display:none}.frame li{line-height:25px}.frame li.disabled,.frame li.disabled .font-ico,.frame li.disabled:hover .font-ico{color:#999}.frame a{color:#000;text-decoration:none}.frame a:hover,.frame li:hover a{color:#FFF}.frame .space{display:inline-block;width:20px;height:15px}.frame .font-ico{margin-right:5px;width:15px;display:inline-block;text-align:center}.tabs{height:21px;overflow:hidden;width:100%}.tabs>div{line-height:20px;height:20px}.tabs>div>span.changed{font-weight:700}.tabs-panel{overflow:auto;flex:1;height:100%}.edit-exprinfo,.edit-panel{position:absolute;overflow:hidden}.menu{display:block!important}.menu>ul>li>span{font-size:12px;line-height:21px;padding:4px 7px}.menu .split{border-left:1px solid #919191;height:21px;margin:0 5px 0 0}.menu img.gravatar{float:left;margin:2px 8px;height:17px;width:17px;border-radius:9px}#buildRun{color:#6DB14C;font-size:19px}#buildRun.ico-stop{color:#9d0000;font-size:16px}.share-panel{position:absolute;z-index:20;width:226px;padding:5px 0;right:0;top:21px}.share-panel .font-ico{font-size:20px;transition:all .2s ease-out 0s;margin:0 5px;width:24px}.share-panel .font-ico:hover{transform:rotate(360deg)}.edit-panel{left:20%;width:60%;height:70%;flex-flow:column;display:flex}.toolbars{position:absolute;right:5px;top:1px}.ico{background-image:url(../images/ico-file.png);height:16px;margin:2px 0 0 -2px;width:16px}.edit-exprinfo{z-index:10;list-style:none;margin:0;padding:2px;-webkit-box-shadow:2px 3px 5px rgba(0,0,0,.2);-moz-box-shadow:2px 3px 5px rgba(0,0,0,.2);box-shadow:2px 3px 5px rgba(0,0,0,.2);border-radius:3px;border:1px solid silver;background:#fff;font-size:90%;max-height:20em;overflow-y:auto}.CodeMirror,.CodeMirror-hints{font-family:Consolas,'Courier New',monospace}.CodeMirror-hints .ico{margin:-1px 2px 0 -1px}.CodeMirror-focused .cm-matchhighlight{background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAIAAAACCAYAAABytg0kAAAAFklEQVQI12NgYGBgkKzc8x9CMDAwAAAmhwSbidEoSQAAAABJRU5ErkJggg==);background-position:bottom;background-repeat:repeat-x}.CodeMirror-hint{padding-right:18px;max-width:none}.CodeMirror-hint:hover{background:#08f;color:#fff}.CodeMirror div.CodeMirror-cursor{border-left:2px solid #333}.CodeMirror-gutter-filler,.CodeMirror-scrollbar-filler{background-color:transparent}.bottom-window-group{background-color:#fff;flex-flow:column}.bottom-window-group .output{font-family:Consolas,Courier New,monospace;padding:0 5px;line-height:16px;font-size:12px;overflow-x:scroll;outline:0}.bottom-window-group .output pre{margin:0;font-family:Consolas,'Courier New',monospace}.bottom-window-group .output .start-build,.bottom-window-group .output .start-get,.bottom-window-group .output .start-install,.bottom-window-group .output .start-test,.start-vet{color:#999}.bottom-window-group .output .build-succ,.bottom-window-group .output .get-succ,.bottom-window-group .output .install-succ,.bottom-window-group .output .test-succ,.vet-succ{color:#090}.bottom-window-group .output .build-error,.bottom-window-group .output .get-error,.bottom-window-group .output .install-error,.bottom-window-group .output .test-error,.vet-error{color:#9d0000}.bottom-window-group .output .stderr{color:gray;font-style:italic}.bottom-window-group .output .path{text-decoration:underline;cursor:pointer}.bottom-window-group table{width:100%}.bottom-window-group td{border-bottom:1px solid #919191;font-size:12px;line-height:19px}.bottom-window-group .notification{outline:0}.bottom-window-group .notification .severity,.bottom-window-group .notification .type{width:50px;padding:0 5px}.bottom-window-group .search{display:flex;flex-flow:column;outline:0}.footer{box-shadow:0 1px 0 0 rgba(255,255,255,.06) inset;padding-left:5px;line-height:18px;display:block!important}.notification-count{float:right;display:none;background-color:#9d0000;color:#FFF;margin:1px 5px;padding:0 2px;border-radius:3px;line-height:16px}
.side{width:20%;position:absolute;height:100%;z-index:8;flex-flow:column;display:flex}.side-max{width:100%;z-index:11}.side-right .tabs-panel>div{overflow:auto}.side-right{flex-flow:column}#outline .ico{margin:1px 5px 0}.ico-func{background-position:-123px -21px}.ico-interface{background-position:-143px -21px}.ico-const{background-position:-103px -21px}.ico-var{background-position:-63px -21px}.ico-struct{background-position:-83px -21px}.ico-type{background-position:-163px -21px}.ico-package{background-position:-183px -21px}.ztree{width:100%;padding:0;outline:0;border:0}.ztree li a.curSelectedNode{background-color:#3875d7;border-width:0;color:#fff;height:18px;opacity:1}.ztree li a:hover{text-decoration:none}.ztree li>a>span.button,.ztree li>a>span.button.ico-ztree-dir,.ztree li>a>span.button.ico-ztree-dir-api,.ztree li>a>span.button.ico-ztree-dir-workspace{margin-right:2px}.ztree li>a>span.button{background-image:url(../images/ico-file.png);margin-right:0}.ico-ztree-dir{background-position:-2px -23px}.ico-ztree-dir-api{background-position:-22px -23px}.ico-ztree-dir-workspace{background-position:-42px -23px}.ico-ztree-html{background-position:-4px -2px}.ico-ztree-go{background-position:-22px -2px}.ico-ztree-css{background-position:-42px -2px}.ico-ztree-img{background-position:-63px -2px}.ico-ztree-other{background-position:-83px -2px}.ico-ztree-text{background-position:-103px -2px}.ico-ztree-sql{background-position:-123px -2px}.ico-ztree-pro{background-position:-142px -2px}.ico-ztree-md{background-position:-162px -2px}.ico-ztree-js{background-position:-182px -2px}.ico-ztree-xml{background-position:-202px -2px} .side{width:20%;position:absolute;height:100%;z-index:8;flex-flow:column;display:flex}.side-max{width:100%;z-index:11}.side-right .tabs-panel>div{overflow:auto}.side-right{flex-flow:column}#outline .ico{margin:1px 5px 0}.ico-func{background-position:-123px -21px}.ico-interface{background-position:-143px -21px}.ico-const{background-position:-103px -21px}.ico-var{background-position:-63px -21px}.ico-struct{background-position:-83px -21px}.ico-type{background-position:-163px -21px}.ico-package{background-position:-183px -21px}.ztree{width:100%;padding:0;outline:0;border:0}.ztree li a.curSelectedNode{background-color:#3875d7;border-width:0;color:#fff;height:18px;opacity:1}.ztree li a:hover{text-decoration:none}.ztree li>a>span.button,.ztree li>a>span.button.ico-ztree-dir,.ztree li>a>span.button.ico-ztree-dir-api,.ztree li>a>span.button.ico-ztree-dir-workspace{margin-right:2px}.ztree li>a>span.button{background-image:url(../images/ico-file.png);margin-right:0}.ico-ztree-dir{background-position:-2px -23px}.ico-ztree-dir-api{background-position:-22px -23px}.ico-ztree-dir-workspace{background-position:-42px -23px}.ico-ztree-html{background-position:-4px -2px}.ico-ztree-go{background-position:-22px -2px}.ico-ztree-css{background-position:-42px -2px}.ico-ztree-img{background-position:-63px -2px}.ico-ztree-other{background-position:-83px -2px}.ico-ztree-text{background-position:-103px -2px}.ico-ztree-sql{background-position:-123px -2px}.ico-ztree-pro{background-position:-142px -2px}.ico-ztree-md{background-position:-162px -2px}.ico-ztree-js{background-position:-182px -2px}.ico-ztree-xml{background-position:-202px -2px}
#startPage{padding:50px 70px;line-height:28px;white-space:normal;word-wrap:break-word;overflow:auto}#startPage a{color:#4183c4;text-decoration:none}#startPage a:hover{text-decoration:underline}#startPage .title{background-color:#BBB;border-bottom-width:0!important;border-radius:3px 3px 0 0;font-size:15px;margin-bottom:10px;padding:5px 10px;color:#FFF}#startPage .details li.border,#startPage .news li{border-bottom:1px solid #919191}#startPage .details{width:30%;float:left}#startPage .details label{color:#666}#startPage .details li.border{padding-bottom:5px;margin-bottom:5px}#startPage .details li.border.workspace{line-height:18px;padding-bottom:10px!important;word-wrap:break-word;white-space:normal;word-break:break-all}#startPage .news{width:60%;float:right;border-left:1px solid #f1f1f1;margin-left:10%;padding-left:10%;white-space:nowrap;overflow:hidden}#startPage .date{color:#bbb;font-size:13px;word-wrap:normal;white-space:nowrap} #startPage{padding:50px 70px;line-height:28px;white-space:normal;word-wrap:break-word;overflow:auto}#startPage a{color:#4183c4;text-decoration:none}#startPage a:hover{text-decoration:underline}#startPage .title{background-color:#BBB;border-bottom-width:0!important;border-radius:3px 3px 0 0;font-size:15px;margin-bottom:10px;padding:5px 10px;color:#FFF}#startPage .details li.border,#startPage .news li{border-bottom:1px solid #919191}#startPage .details{width:30%;float:left}#startPage .details label{color:#666}#startPage .details li.border{padding-bottom:5px;margin-bottom:5px}#startPage .details li.border.workspace{line-height:18px;padding-bottom:10px!important;word-wrap:break-word;white-space:normal;word-break:break-all}#startPage .news{width:60%;float:right;border-left:1px solid #f1f1f1;margin-left:10%;padding-left:10%;white-space:nowrap;overflow:hidden}#startPage .date{color:#bbb;font-size:13px;word-wrap:normal;white-space:nowrap}

View File

@ -1,11 +1,11 @@
/* /*
* Copyright (c) 2014-2017, b3log.org & hacpai.com * Copyright (c) 2014-2019, b3log.org & hacpai.com
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
* You may obtain a copy of the License at * You may obtain a copy of the License at
* *
* http://www.apache.org/licenses/LICENSE-2.0 * https://www.apache.org/licenses/LICENSE-2.0
* *
* Unless required by applicable law or agreed to in writing, software * Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, * distributed under the License is distributed on an "AS IS" BASIS,

View File

@ -1,11 +1,11 @@
/* /*
* Copyright (c) 2014-2017, b3log.org & hacpai.com * Copyright (c) 2014-2019, b3log.org & hacpai.com
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
* You may obtain a copy of the License at * You may obtain a copy of the License at
* *
* http://www.apache.org/licenses/LICENSE-2.0 * https://www.apache.org/licenses/LICENSE-2.0
* *
* Unless required by applicable law or agreed to in writing, software * Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, * distributed under the License is distributed on an "AS IS" BASIS,
@ -24,7 +24,7 @@
$.fn.extend({ $.fn.extend({
dialog: { dialog: {
version: "0.0.1.7", version: "0.0.1.7",
author: "lly219@gmail.com" author: "v@b3log.org"
} }
}); });

View File

@ -1,11 +1,11 @@
/* /*
* Copyright (c) 2014-2017, b3log.org & hacpai.com * Copyright (c) 2014-2019, b3log.org & hacpai.com
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
* You may obtain a copy of the License at * You may obtain a copy of the License at
* *
* http://www.apache.org/licenses/LICENSE-2.0 * https://www.apache.org/licenses/LICENSE-2.0
* *
* Unless required by applicable law or agreed to in writing, software * Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, * distributed under the License is distributed on an "AS IS" BASIS,
@ -246,7 +246,7 @@ var editors = {
+ '"><span class="ico-start font-ico"></span> ' + config.label.start_page + '</span>', + '"><span class="ico-start font-ico"></span> ' + config.label.start_page + '</span>',
content: '<div id="startPage"></div>', content: '<div id="startPage"></div>',
after: function () { after: function () {
$("#startPage").load(config.context + '/start?sid=' + config.wideSessionId); $("#startPage").load('/start?sid=' + config.wideSessionId);
$.ajax({ $.ajax({
url: "https://hacpai.com/apis/articles?tags=wide,golang&p=1&size=20", url: "https://hacpai.com/apis/articles?tags=wide,golang&p=1&size=20",
type: "GET", type: "GET",
@ -329,7 +329,7 @@ var editors = {
$.ajax({ $.ajax({
async: false, // 同步执行 async: false, // 同步执行
type: 'POST', type: 'POST',
url: config.context + '/autocomplete', url: '/autocomplete',
data: JSON.stringify(request), data: JSON.stringify(request),
dataType: "json", dataType: "json",
success: function (data) { success: function (data) {
@ -435,7 +435,7 @@ var editors = {
$.ajax({ $.ajax({
type: 'POST', type: 'POST',
url: config.context + '/exprinfo', url: '/exprinfo',
data: JSON.stringify(request), data: JSON.stringify(request),
dataType: "json", dataType: "json",
success: function (result) { success: function (result) {
@ -583,7 +583,7 @@ var editors = {
$.ajax({ $.ajax({
type: 'POST', type: 'POST',
url: config.context + '/find/decl', url: '/find/decl',
data: JSON.stringify(request), data: JSON.stringify(request),
dataType: "json", dataType: "json",
success: function (result) { success: function (result) {
@ -613,7 +613,7 @@ var editors = {
$.ajax({ $.ajax({
type: 'POST', type: 'POST',
url: config.context + '/find/usages', url: '/find/usages',
data: JSON.stringify(request), data: JSON.stringify(request),
dataType: "json", dataType: "json",
success: function (result) { success: function (result) {

View File

@ -1,11 +1,11 @@
/* /*
* Copyright (c) 2014-2017, b3log.org & hacpai.com * Copyright (c) 2014-2019, b3log.org & hacpai.com
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
* You may obtain a copy of the License at * You may obtain a copy of the License at
* *
* http://www.apache.org/licenses/LICENSE-2.0 * https://www.apache.org/licenses/LICENSE-2.0
* *
* Unless required by applicable law or agreed to in writing, software * Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, * distributed under the License is distributed on an "AS IS" BASIS,

124
static/js/lib.min.js vendored

File diff suppressed because one or more lines are too long

View File

@ -73,7 +73,7 @@ xquery version &quot;1.0-ml&quot;;
: you may not use this file except in compliance with the License. : you may not use this file except in compliance with the License.
: You may obtain a copy of the License at : You may obtain a copy of the License at
: :
: http://www.apache.org/licenses/LICENSE-2.0 : https://www.apache.org/licenses/LICENSE-2.0
: :
: Unless required by applicable law or agreed to in writing, software : Unless required by applicable law or agreed to in writing, software
: distributed under the License is distributed on an &quot;AS IS&quot; BASIS, : distributed under the License is distributed on an &quot;AS IS&quot; BASIS,

View File

@ -1,11 +1,11 @@
/* /*
* Copyright (c) 2014-2017, b3log.org & hacpai.com * Copyright (c) 2014-2019, b3log.org & hacpai.com
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
* You may obtain a copy of the License at * You may obtain a copy of the License at
* *
* http://www.apache.org/licenses/LICENSE-2.0 * https://www.apache.org/licenses/LICENSE-2.0
* *
* Unless required by applicable law or agreed to in writing, software * Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, * distributed under the License is distributed on an "AS IS" BASIS,
@ -19,7 +19,7 @@
* *
* @author <a href="http://vanessa.b3log.org">Liyuan Li</a> * @author <a href="http://vanessa.b3log.org">Liyuan Li</a>
* @author <a href="http://88250.b3log.org">Liang Ding</a> * @author <a href="http://88250.b3log.org">Liang Ding</a>
* @version 1.0.1.2, Mar 27, 2017 * @version 1.0.1.3, Oct 5, 2018
*/ */
var menu = { var menu = {
init: function () { init: function () {
@ -55,14 +55,13 @@ var menu = {
var title = encodeURIComponent($('title').text() + '. \n' + $('meta[name=description]').attr('content') var title = encodeURIComponent($('title').text() + '. \n' + $('meta[name=description]').attr('content')
+ " #golang#"); + " #golang#");
urls.weibo = "http://v.t.sina.com.cn/share/share.php?title=" + title + "&url=" + url + "&pic=" + pic; urls.weibo = "http://v.t.sina.com.cn/share/share.php?title=" + title + "&url=" + url + "&pic=" + pic;
urls.tencent = "http://share.v.t.qq.com/index.php?c=share&a=index&title=" + title + urls.qqz = "https://sns.qzone.qq.com/cgi-bin/qzshare/cgi_qzshare_onekey?url=" + url + "&sharesource=qzone&title=" + title+ "&pics=" + pic;
"&url=" + url + "&pic=" + pic;
window.open(urls[key], "_blank", "top=100,left=200,width=648,height=618"); window.open(urls[key], "_blank", "top=100,left=200,width=648,height=618");
}); });
}, },
_initAbout: function () { _initAbout: function () {
$("#dialogAbout").load(config.context + '/about', function () { $("#dialogAbout").load('/about', function () {
$("#dialogAbout").dialog({ $("#dialogAbout").dialog({
"modal": true, "modal": true,
"title": config.label.about, "title": config.label.about,
@ -160,12 +159,12 @@ var menu = {
$.ajax({ $.ajax({
type: 'POST', type: 'POST',
url: config.context + '/logout', url: '/logout',
data: JSON.stringify(request), data: JSON.stringify(request),
dataType: "json", dataType: "json",
success: function (result) { success: function (result) {
if (result.succ) { if (result.succ) {
window.location.href = config.context + "/login"; window.location.href = "/login";
} }
} }
}); });
@ -190,7 +189,7 @@ var menu = {
$.ajax({ $.ajax({
type: 'POST', type: 'POST',
url: config.context + '/go/get', url: '/go/get',
data: JSON.stringify(request), data: JSON.stringify(request),
dataType: "json", dataType: "json",
beforeSend: function () { beforeSend: function () {
@ -217,7 +216,7 @@ var menu = {
$.ajax({ $.ajax({
type: 'POST', type: 'POST',
url: config.context + '/go/install', url: '/go/install',
data: JSON.stringify(request), data: JSON.stringify(request),
dataType: "json", dataType: "json",
beforeSend: function () { beforeSend: function () {
@ -245,7 +244,7 @@ var menu = {
$.ajax({ $.ajax({
type: 'POST', type: 'POST',
url: config.context + '/go/test', url: '/go/test',
data: JSON.stringify(request), data: JSON.stringify(request),
dataType: "json", dataType: "json",
beforeSend: function () { beforeSend: function () {
@ -273,7 +272,7 @@ var menu = {
$.ajax({ $.ajax({
type: 'POST', type: 'POST',
url: config.context + '/go/vet', url: '/go/vet',
data: JSON.stringify(request), data: JSON.stringify(request),
dataType: "json", dataType: "json",
beforeSend: function () { beforeSend: function () {
@ -308,7 +307,7 @@ var menu = {
$.ajax({ $.ajax({
type: 'POST', type: 'POST',
url: config.context + '/build', url: '/build',
data: JSON.stringify(request), data: JSON.stringify(request),
dataType: "json", dataType: "json",
beforeSend: function () { beforeSend: function () {
@ -341,7 +340,7 @@ var menu = {
$.ajax({ $.ajax({
type: 'POST', type: 'POST',
url: config.context + '/build', url: '/build',
data: JSON.stringify(request), data: JSON.stringify(request),
dataType: "json", dataType: "json",
beforeSend: function () { beforeSend: function () {
@ -352,7 +351,7 @@ var menu = {
}); });
}, },
_initPreference: function () { _initPreference: function () {
$("#dialogPreference").load(config.context + '/preference', function () { $("#dialogPreference").load('/preference', function () {
$("#dialogPreference input").keyup(function () { $("#dialogPreference input").keyup(function () {
var isChange = false, var isChange = false,
emptys = [], emptys = [],
@ -466,7 +465,7 @@ var menu = {
$.ajax({ $.ajax({
type: 'POST', type: 'POST',
url: config.context + '/preference', url: '/preference',
data: JSON.stringify(request), data: JSON.stringify(request),
success: function (result, textStatus, jqXHR) { success: function (result, textStatus, jqXHR) {
if (!result.succ) { if (!result.succ) {
@ -497,7 +496,7 @@ var menu = {
var $okBtn = $("#dialogPreference").closest(".dialog-main").find(".dialog-footer > button:eq(0)"); var $okBtn = $("#dialogPreference").closest(".dialog-main").find(".dialog-footer > button:eq(0)");
$okBtn.prop("disabled", true); $okBtn.prop("disabled", true);
$("#themesLink").attr("href", config.staticServer + '/static/css/themes/' + $theme.val() + '.css'); $("#themesLink").attr("href", '/static/css/themes/' + $theme.val() + '.css');
config.editorTheme = $editorTheme.val(); config.editorTheme = $editorTheme.val();
for (var i = 0, ii = editors.data.length; i < ii; i++) { for (var i = 0, ii = editors.data.length; i < ii; i++) {

View File

@ -1,11 +1,11 @@
/* /*
* Copyright (c) 2014-2017, b3log.org & hacpai.com * Copyright (c) 2014-2019, b3log.org & hacpai.com
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
* You may obtain a copy of the License at * You may obtain a copy of the License at
* *
* http://www.apache.org/licenses/LICENSE-2.0 * https://www.apache.org/licenses/LICENSE-2.0
* *
* Unless required by applicable law or agreed to in writing, software * Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, * distributed under the License is distributed on an "AS IS" BASIS,

View File

@ -1,11 +1,11 @@
/* /*
* Copyright (c) 2014-2017, b3log.org & hacpai.com * Copyright (c) 2014-2019, b3log.org & hacpai.com
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
* You may obtain a copy of the License at * You may obtain a copy of the License at
* *
* http://www.apache.org/licenses/LICENSE-2.0 * https://www.apache.org/licenses/LICENSE-2.0
* *
* Unless required by applicable law or agreed to in writing, software * Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, * distributed under the License is distributed on an "AS IS" BASIS,

View File

@ -5,7 +5,7 @@
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
* You may obtain a copy of the License at * You may obtain a copy of the License at
* *
* http://www.apache.org/licenses/LICENSE-2.0 * https://www.apache.org/licenses/LICENSE-2.0
* *
* Unless required by applicable law or agreed to in writing, software * Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, * distributed under the License is distributed on an "AS IS" BASIS,

View File

@ -1,11 +1,11 @@
/* /*
* Copyright (c) 2014-2017, b3log.org & hacpai.com * Copyright (c) 2014-2019, b3log.org & hacpai.com
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
* You may obtain a copy of the License at * You may obtain a copy of the License at
* *
* http://www.apache.org/licenses/LICENSE-2.0 * https://www.apache.org/licenses/LICENSE-2.0
* *
* Unless required by applicable law or agreed to in writing, software * Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, * distributed under the License is distributed on an "AS IS" BASIS,
@ -19,7 +19,7 @@
* *
* @author <a href="http://vanessa.b3log.org">Liyuan Li</a> * @author <a href="http://vanessa.b3log.org">Liyuan Li</a>
* @author <a href="http://88250.b3log.org">Liang Ding</a> * @author <a href="http://88250.b3log.org">Liang Ding</a>
* @version 1.0.0.1, Dec 8, 2015 * @version 1.0.0.2, Oct 5, 2018
*/ */
var playground = { var playground = {
autocompleteMutex: false, autocompleteMutex: false,
@ -53,10 +53,9 @@ var playground = {
var title = encodeURIComponent($('title').text() + '. \n' + $('meta[name=description]').attr('content') var title = encodeURIComponent($('title').text() + '. \n' + $('meta[name=description]').attr('content')
+ " #golang#"); + " #golang#");
urls.weibo = "http://v.t.sina.com.cn/share/share.php?title=" + title + "&url=" + url + "&pic=" + pic; urls.weibo = "http://v.t.sina.com.cn/share/share.php?title=" + title + "&url=" + url + "&pic=" + pic;
urls.tencent = "http://share.v.t.qq.com/index.php?c=share&a=index&title=" + title + urls.qqz = "https://sns.qzone.qq.com/cgi-bin/qzshare/cgi_qzshare_onekey?url=" + url + "&sharesource=qzone&title=" + title+ "&pics=" + pic;
"&url=" + url + "&pic=" + pic;
window.open(urls[key], "_blank", "top=100,left=200,width=648,height=618"); window.open(urls[key], "_blank", "top=100,left=200,width=648,height=618");
$(".menu .share-panel").hide(); $(".menu .share-panel").hide();
}); });
@ -92,7 +91,7 @@ var playground = {
$.ajax({ $.ajax({
async: false, // 同步执行 async: false, // 同步执行
type: 'POST', type: 'POST',
url: config.context + '/playground/autocomplete', url: '/playground/autocomplete',
data: JSON.stringify(request), data: JSON.stringify(request),
dataType: "json", dataType: "json",
success: function (data) { success: function (data) {
@ -357,7 +356,7 @@ var playground = {
$.ajax({ $.ajax({
type: 'POST', type: 'POST',
url: config.context + '/playground/save', url: '/playground/save',
data: JSON.stringify(request), data: JSON.stringify(request),
dataType: "json", dataType: "json",
success: function (result) { success: function (result) {
@ -376,7 +375,7 @@ var playground = {
request.url = url; request.url = url;
$.ajax({ $.ajax({
type: 'POST', type: 'POST',
url: config.context + '/playground/short-url', url: '/playground/short-url',
data: JSON.stringify(request), data: JSON.stringify(request),
dataType: "json", dataType: "json",
success: function (result) { success: function (result) {
@ -421,7 +420,7 @@ var playground = {
$.ajax({ $.ajax({
type: 'POST', type: 'POST',
url: config.context + '/playground/stop', url: '/playground/stop',
data: JSON.stringify(request), data: JSON.stringify(request),
dataType: "json" dataType: "json"
}); });
@ -444,7 +443,7 @@ var playground = {
$.ajax({ $.ajax({
type: 'POST', type: 'POST',
url: config.context + '/playground/save', url: '/playground/save',
data: JSON.stringify(request), data: JSON.stringify(request),
dataType: "json", dataType: "json",
success: function (result) { success: function (result) {
@ -463,7 +462,7 @@ var playground = {
$.ajax({ $.ajax({
type: 'POST', type: 'POST',
url: config.context + '/playground/build', url: '/playground/build',
data: JSON.stringify(request), data: JSON.stringify(request),
dataType: "json", dataType: "json",
success: function (result) { success: function (result) {
@ -483,7 +482,7 @@ var playground = {
$.ajax({ $.ajax({
type: 'POST', type: 'POST',
url: config.context + '/playground/run', url: '/playground/run',
data: JSON.stringify(request), data: JSON.stringify(request),
dataType: "json", dataType: "json",
success: function (result) { success: function (result) {
@ -510,7 +509,7 @@ var playground = {
$.ajax({ $.ajax({
type: 'POST', type: 'POST',
url: config.context + '/playground/save', url: '/playground/save',
data: JSON.stringify(request), data: JSON.stringify(request),
dataType: "json", dataType: "json",
success: function (result) { success: function (result) {

View File

@ -1,11 +1,11 @@
/* /*
* Copyright (c) 2014-2017, b3log.org & hacpai.com * Copyright (c) 2014-2019, b3log.org & hacpai.com
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
* You may obtain a copy of the License at * You may obtain a copy of the License at
* *
* http://www.apache.org/licenses/LICENSE-2.0 * https://www.apache.org/licenses/LICENSE-2.0
* *
* Unless required by applicable law or agreed to in writing, software * Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, * distributed under the License is distributed on an "AS IS" BASIS,
@ -74,7 +74,7 @@ var session = {
$.ajax({ $.ajax({
type: 'POST', type: 'POST',
url: config.context + '/session/save', url: '/session/save',
data: JSON.stringify(request), data: JSON.stringify(request),
dataType: "json", dataType: "json",
success: function (result) { success: function (result) {

View File

@ -1,61 +0,0 @@
/*
* Copyright (c) 2014-2017, b3log.org & hacpai.com
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/*
* @file shell.js
*
* @author <a href="http://88250.b3log.org">Liang Ding</a>
* @version 1.0.0.1, Dec 8, 2015
*/
var shell = {
_shellWS: undefined,
_initWS: function () {
shell.shellWS = new ReconnectingWebSocket(config.channel + '/shell/ws?sid=' + config.wideSessionId);
shell.shellWS.onopen = function () {
console.log('[shell onopen] connected');
};
shell.shellWS.onmessage = function (e) {
console.log('[shell onmessage]' + e.data);
var data = JSON.parse(e.data);
if ('init-shell' !== data.cmd) {
$('#shellOutput').val(data.output);
}
};
shell.shellWS.onclose = function (e) {
console.log('[shell onclose] disconnected (' + e.code + ')');
};
shell.shellWS.onerror = function (e) {
console.log('[shell onerror] ' + e);
};
},
init: function () {
this._initWS();
$('#shellInput').keydown(function (event) {
if (13 === event.which) {
var input = {
cmd: $('#shellInput').val()
};
shell.shellWS.send(JSON.stringify(input));
$('#shellInput').val('');
}
});
}
};
$(document).ready(function () {
shell.init();
});

View File

@ -1,11 +1,11 @@
/* /*
* Copyright (c) 2014-2017, b3log.org & hacpai.com * Copyright (c) 2014-2019, b3log.org & hacpai.com
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
* You may obtain a copy of the License at * You may obtain a copy of the License at
* *
* http://www.apache.org/licenses/LICENSE-2.0 * https://www.apache.org/licenses/LICENSE-2.0
* *
* Unless required by applicable law or agreed to in writing, software * Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, * distributed under the License is distributed on an "AS IS" BASIS,

View File

@ -1,11 +1,11 @@
/* /*
* Copyright (c) 2014-2017, b3log.org & hacpai.com * Copyright (c) 2014-2019, b3log.org & hacpai.com
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
* You may obtain a copy of the License at * You may obtain a copy of the License at
* *
* http://www.apache.org/licenses/LICENSE-2.0 * https://www.apache.org/licenses/LICENSE-2.0
* *
* Unless required by applicable law or agreed to in writing, software * Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, * distributed under the License is distributed on an "AS IS" BASIS,
@ -160,7 +160,7 @@ var tree = {
$.ajax({ $.ajax({
async: false, async: false,
type: 'POST', type: 'POST',
url: config.context + '/file/zip/new', url: '/file/zip/new',
data: JSON.stringify(request), data: JSON.stringify(request),
dataType: "json", dataType: "json",
success: function (result) { success: function (result) {
@ -175,7 +175,7 @@ var tree = {
}); });
if (isSucc) { if (isSucc) {
window.open(config.context + '/file/zip?path=' + wide.curNode.path + ".zip"); window.open('/file/zip?path=' + wide.curNode.path + ".zip");
} }
}, },
crossCompile: function (platform) { crossCompile: function (platform) {
@ -186,7 +186,7 @@ var tree = {
$.ajax({ $.ajax({
async: false, async: false,
type: 'POST', type: 'POST',
url: config.context + '/cross', url: '/cross',
data: JSON.stringify(request), data: JSON.stringify(request),
dataType: "json", dataType: "json",
success: function (result) { success: function (result) {
@ -198,28 +198,6 @@ var tree = {
} }
}); });
}, },
decompress: function () {
var request = newWideRequest();
request.path = wide.curNode.path;
$.ajax({
async: false,
type: 'POST',
url: config.context + '/file/decompress',
data: JSON.stringify(request),
dataType: "json",
success: function (result) {
if (!result.succ) {
$("#dialogAlert").dialog("open", result.msg);
return false;
}
var dir = wide.curNode.getParentNode();
tree.fileTree.reAsyncChildNodes(dir, "refresh");
}
});
},
refresh: function (it) { refresh: function (it) {
if (it) { if (it) {
if ($(it).hasClass("disabled")) { if ($(it).hasClass("disabled")) {
@ -229,31 +207,6 @@ var tree = {
tree.fileTree.reAsyncChildNodes(wide.curNode, "refresh", true); tree.fileTree.reAsyncChildNodes(wide.curNode, "refresh", true);
}, },
gitClone: function (it) {
if (it) {
if ($(it).hasClass("disabled")) {
return false;
}
}
$("#dialogGitClonePrompt").dialog('open');
},
import: function () {
var request = newWideRequest();
request.path = wide.curNode.path;
$('#importFileupload').fileupload({
url: "/file/upload?path=" + request.path,
dataType: 'json',
formData: request,
done: function (e, result) {
tree.fileTree.reAsyncChildNodes(wide.curNode, "refresh");
},
fail: function () {
console.log(arguments);
}
});
},
init: function () { init: function () {
$("#file").click(function () { $("#file").click(function () {
$(this).focus(); $(this).focus();
@ -263,7 +216,7 @@ var tree = {
$.ajax({ $.ajax({
type: 'POST', type: 'POST',
url: config.context + '/files', url: '/files',
data: JSON.stringify(request), data: JSON.stringify(request),
dataType: "json", dataType: "json",
success: function (result) { success: function (result) {
@ -282,7 +235,7 @@ var tree = {
}, },
async: { async: {
enable: true, enable: true,
url: config.context + "/file/refresh", url: "/file/refresh",
autoParam: ["path"] autoParam: ["path"]
}, },
callback: { callback: {
@ -417,7 +370,7 @@ var tree = {
$.ajax({ $.ajax({
async: false, async: false,
type: 'POST', type: 'POST',
url: config.context + '/file', url: '/file',
data: JSON.stringify(request), data: JSON.stringify(request),
dataType: "json", dataType: "json",
success: function (result) { success: function (result) {
@ -444,7 +397,7 @@ var tree = {
if ("img" === data.mode) { // 是图片文件的话新建 tab 打开 if ("img" === data.mode) { // 是图片文件的话新建 tab 打开
// 最好是开 tab但这个最终取决于浏览器设置 // 最好是开 tab但这个最终取决于浏览器设置
var w = window.open(config.context + data.path); var w = window.open(data.path);
return false; return false;
} }
@ -506,7 +459,7 @@ var tree = {
$.ajax({ $.ajax({
type: 'POST', type: 'POST',
url: config.context + '/file/search/text', url: '/file/search/text',
data: JSON.stringify(request), data: JSON.stringify(request),
dataType: "json", dataType: "json",
success: function (result) { success: function (result) {
@ -542,7 +495,7 @@ var tree = {
$.ajax({ $.ajax({
type: 'POST', type: 'POST',
url: config.context + '/file/rename', url: '/file/rename',
data: JSON.stringify(request), data: JSON.stringify(request),
dataType: "json", dataType: "json",
success: function (result) { success: function (result) {

View File

@ -1,11 +1,11 @@
/* /*
* Copyright (c) 2014-2017, b3log.org & hacpai.com * Copyright (c) 2014-2019, b3log.org & hacpai.com
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
* You may obtain a copy of the License at * You may obtain a copy of the License at
* *
* http://www.apache.org/licenses/LICENSE-2.0 * https://www.apache.org/licenses/LICENSE-2.0
* *
* Unless required by applicable law or agreed to in writing, software * Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, * distributed under the License is distributed on an "AS IS" BASIS,
@ -38,7 +38,7 @@ var wide = {
$.ajax({ $.ajax({
type: 'POST', type: 'POST',
async: false, async: false,
url: config.context + '/outline', url: '/outline',
data: JSON.stringify(request), data: JSON.stringify(request),
dataType: "json", dataType: "json",
success: function (result) { success: function (result) {
@ -121,7 +121,7 @@ var wide = {
$.ajax({ $.ajax({
type: 'POST', type: 'POST',
url: config.context + '/file/remove', url: '/file/remove',
data: JSON.stringify(request), data: JSON.stringify(request),
dataType: "json", dataType: "json",
success: function (result) { success: function (result) {
@ -159,7 +159,7 @@ var wide = {
$.ajax({ $.ajax({
type: 'POST', type: 'POST',
url: config.context + '/file/new', url: '/file/new',
data: JSON.stringify(request), data: JSON.stringify(request),
dataType: "json", dataType: "json",
success: function (result) { success: function (result) {
@ -203,7 +203,7 @@ var wide = {
$.ajax({ $.ajax({
type: 'POST', type: 'POST',
url: config.context + '/file/new', url: '/file/new',
data: JSON.stringify(request), data: JSON.stringify(request),
dataType: "json", dataType: "json",
success: function (result) { success: function (result) {
@ -264,7 +264,7 @@ var wide = {
$.ajax({ $.ajax({
type: 'POST', type: 'POST',
url: config.context + '/file/find/name', url: '/file/find/name',
data: JSON.stringify(request), data: JSON.stringify(request),
dataType: "json", dataType: "json",
success: function (result) { success: function (result) {
@ -339,35 +339,6 @@ var wide = {
editor.focus(); editor.focus();
} }
}); });
$("#dialogGitClonePrompt").dialog({
"modal": true,
"height": 52,
"width": 360,
"title": config.label.git_clone,
"okText": config.label.confirm,
"cancelText": config.label.cancel,
"afterOpen": function () {
$("#dialogGitClonePrompt > input").val('').focus();
$("#dialogGitClonePrompt").closest(".dialog-main").find(".dialog-footer > button:eq(0)").prop("disabled", true);
},
"ok": function () {
$("#dialogGitClonePrompt").dialog("close");
var request = newWideRequest();
request.path = wide.curNode.path;
request.repository = $("#dialogGitClonePrompt > input").val();
$.ajax({
type: 'POST',
url: config.context + '/git/clone',
data: JSON.stringify(request),
dataType: "json",
success: function (result) {
}
});
}
});
}, },
_initWS: function () { _initWS: function () {
var outputWS = new ReconnectingWebSocket(config.channel + '/output/ws?sid=' + config.wideSessionId); var outputWS = new ReconnectingWebSocket(config.channel + '/output/ws?sid=' + config.wideSessionId);
@ -389,7 +360,7 @@ var wide = {
$.ajax({ $.ajax({
type: 'POST', type: 'POST',
url: config.context + '/run', url: '/run',
data: JSON.stringify(request), data: JSON.stringify(request),
dataType: "json" dataType: "json"
}); });
@ -420,7 +391,6 @@ var wide = {
case 'start-vet': case 'start-vet':
case 'start-install': case 'start-install':
case 'start-get': case 'start-get':
case 'start-git_clone':
bottomGroup.fillOutput(data.output); bottomGroup.fillOutput(data.output);
break; break;
@ -471,7 +441,7 @@ var wide = {
$.ajax({ $.ajax({
async: false, async: false,
type: 'POST', type: 'POST',
url: config.context + '/file/zip/new', url: '/file/zip/new',
data: JSON.stringify(request), data: JSON.stringify(request),
dataType: "json", dataType: "json",
success: function (result) { success: function (result) {
@ -486,7 +456,7 @@ var wide = {
}); });
if (path) { if (path) {
window.open(config.context + '/file/zip?path=' + path + ".zip"); window.open('/file/zip?path=' + path + ".zip");
} }
} }
} }
@ -551,7 +521,7 @@ var wide = {
$.ajax({ $.ajax({
type: 'POST', type: 'POST',
url: config.context + '/file/save', url: '/file/save',
data: JSON.stringify(request), data: JSON.stringify(request),
dataType: "json", dataType: "json",
success: function (result) { success: function (result) {
@ -587,7 +557,7 @@ var wide = {
request.nextCmd = ""; // build only, no following operation request.nextCmd = ""; // build only, no following operation
$.ajax({ $.ajax({
type: 'POST', type: 'POST',
url: config.context + '/build', url: '/build',
data: JSON.stringify(request), data: JSON.stringify(request),
dataType: "json", dataType: "json",
beforeSend: function () { beforeSend: function () {
@ -620,7 +590,7 @@ var wide = {
$.ajax({ $.ajax({
type: 'POST', type: 'POST',
url: config.context + '/stop', url: '/stop',
data: JSON.stringify(request), data: JSON.stringify(request),
dataType: "json", dataType: "json",
success: function (result) { success: function (result) {
@ -642,7 +612,7 @@ var wide = {
$.ajax({ $.ajax({
async: false, // sync async: false, // sync
type: 'POST', type: 'POST',
url: config.context + '/go/fmt', url: '/go/fmt',
data: JSON.stringify(request), data: JSON.stringify(request),
dataType: "json", dataType: "json",
success: function (result) { success: function (result) {
@ -675,7 +645,7 @@ var wide = {
$.ajax({ $.ajax({
async: false, // sync async: false, // sync
type: 'POST', type: 'POST',
url: config.context + '/go/fmt', url: '/go/fmt',
data: JSON.stringify(request), data: JSON.stringify(request),
dataType: "json", dataType: "json",
success: function (result) { success: function (result) {

22
static/js/wide.min.js vendored

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -1,11 +1,11 @@
/* /*
* Copyright (c) 2014-2017, b3log.org & hacpai.com * Copyright (c) 2014-2019, b3log.org & hacpai.com
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
* You may obtain a copy of the License at * You may obtain a copy of the License at
* *
* http://www.apache.org/licenses/LICENSE-2.0 * https://www.apache.org/licenses/LICENSE-2.0
* *
* Unless required by applicable law or agreed to in writing, software * Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, * distributed under the License is distributed on an "AS IS" BASIS,

View File

@ -1 +0,0 @@
This directory is used to hold user _admin_ customized style file `style.css`, it will be generated when wide startup automatically.

View File

@ -1,10 +1,10 @@
// Copyright (c) 2014-2017, b3log.org & hacpai.com // Copyright (c) 2014-2019, b3log.org & hacpai.com
// //
// Licensed under the Apache License, Version 2.0 (the "License"); // Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License. // you may not use this file except in compliance with the License.
// You may obtain a copy of the License at // You may obtain a copy of the License at
// //
// http://www.apache.org/licenses/LICENSE-2.0 // https://www.apache.org/licenses/LICENSE-2.0
// //
// Unless required by applicable law or agreed to in writing, software // Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS, // distributed under the License is distributed on an "AS IS" BASIS,

View File

@ -1,10 +1,10 @@
// Copyright (c) 2014-2017, b3log.org & hacpai.com // Copyright (c) 2014-2019, b3log.org & hacpai.com
// //
// Licensed under the Apache License, Version 2.0 (the "License"); // Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License. // you may not use this file except in compliance with the License.
// You may obtain a copy of the License at // You may obtain a copy of the License at
// //
// http://www.apache.org/licenses/LICENSE-2.0 // https://www.apache.org/licenses/LICENSE-2.0
// //
// Unless required by applicable law or agreed to in writing, software // Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS, // distributed under the License is distributed on an "AS IS" BASIS,

Some files were not shown because too many files have changed in this diff Show More