This commit is contained in:
parent
7521c8b6d4
commit
36a8bb4d50
47
conf/wide.go
47
conf/wide.go
|
@ -20,6 +20,7 @@ import (
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
|
"os/user"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"sort"
|
"sort"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
@ -42,6 +43,15 @@ const (
|
||||||
WideVersion = "1.1.0"
|
WideVersion = "1.1.0"
|
||||||
// CodeMirrorVer holds the current editor version.
|
// CodeMirrorVer holds the current editor version.
|
||||||
CodeMirrorVer = "4.10"
|
CodeMirrorVer = "4.10"
|
||||||
|
|
||||||
|
HelloWorld = `package main
|
||||||
|
|
||||||
|
import "fmt"
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
fmt.Println("Hello, 世界")
|
||||||
|
}
|
||||||
|
`
|
||||||
)
|
)
|
||||||
|
|
||||||
// Configuration.
|
// Configuration.
|
||||||
|
@ -59,6 +69,7 @@ type conf struct {
|
||||||
RuntimeMode string // runtime mode (dev/prod)
|
RuntimeMode string // runtime mode (dev/prod)
|
||||||
WD string // current working direcitory, ${pwd}
|
WD string // current working direcitory, ${pwd}
|
||||||
Locale string // default locale
|
Locale string // default locale
|
||||||
|
Playground string // playground directory
|
||||||
}
|
}
|
||||||
|
|
||||||
// Logger.
|
// Logger.
|
||||||
|
@ -74,8 +85,12 @@ var Users []*User
|
||||||
var Docker bool
|
var Docker bool
|
||||||
|
|
||||||
// Load loads the Wide configurations from wide.json and users' configurations from users/{username}.json.
|
// Load loads the Wide configurations from wide.json and users' configurations from users/{username}.json.
|
||||||
func Load(confPath, confIP, confPort, confServer, confLogLevel, confStaticServer, confContext, confChannel string, confDocker bool) {
|
func Load(confPath, confIP, confPort, confServer, confLogLevel, confStaticServer, confContext, confChannel,
|
||||||
initWide(confPath, confIP, confPort, confServer, confLogLevel, confStaticServer, confContext, confChannel, confDocker)
|
confPlayground string, confDocker bool) {
|
||||||
|
// XXX: ugly args list....
|
||||||
|
|
||||||
|
initWide(confPath, confIP, confPort, confServer, confLogLevel, confStaticServer, confContext, confChannel,
|
||||||
|
confPlayground, confDocker)
|
||||||
initUsers()
|
initUsers()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -114,7 +129,8 @@ func initUsers() {
|
||||||
initCustomizedConfs()
|
initCustomizedConfs()
|
||||||
}
|
}
|
||||||
|
|
||||||
func initWide(confPath, confIP, confPort, confServer, confLogLevel, confStaticServer, confContext, confChannel string, confDocker bool) {
|
func initWide(confPath, confIP, confPort, confServer, confLogLevel, confStaticServer, confContext, confChannel,
|
||||||
|
confPlayground string, confDocker bool) {
|
||||||
bytes, err := ioutil.ReadFile(confPath)
|
bytes, err := ioutil.ReadFile(confPath)
|
||||||
if nil != err {
|
if nil != err {
|
||||||
logger.Error(err)
|
logger.Error(err)
|
||||||
|
@ -144,6 +160,31 @@ func initWide(confPath, confIP, confPort, confServer, confLogLevel, confStaticSe
|
||||||
Wide.WD = util.OS.Pwd()
|
Wide.WD = util.OS.Pwd()
|
||||||
logger.Debugf("${pwd} [%s]", Wide.WD)
|
logger.Debugf("${pwd} [%s]", Wide.WD)
|
||||||
|
|
||||||
|
// User Home
|
||||||
|
user, err := user.Current()
|
||||||
|
if nil != err {
|
||||||
|
logger.Error("Can't get user's home, please report this issue to developer")
|
||||||
|
|
||||||
|
os.Exit(-1)
|
||||||
|
}
|
||||||
|
|
||||||
|
userHome := user.HomeDir
|
||||||
|
logger.Debugf("${user.home} [%s]", userHome)
|
||||||
|
|
||||||
|
// Playground Directory
|
||||||
|
Wide.Playground = strings.Replace(Wide.Playground, "${home}", userHome, 1)
|
||||||
|
if "" != confPlayground {
|
||||||
|
Wide.Playground = confPlayground
|
||||||
|
}
|
||||||
|
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// IP
|
// IP
|
||||||
ip, err := util.Net.LocalIP()
|
ip, err := util.Net.LocalIP()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
@ -11,5 +11,6 @@
|
||||||
"MaxProcs": 4,
|
"MaxProcs": 4,
|
||||||
"RuntimeMode": "dev",
|
"RuntimeMode": "dev",
|
||||||
"WD": "${pwd}",
|
"WD": "${pwd}",
|
||||||
"Locale": "en_US"
|
"Locale": "en_US",
|
||||||
|
"Playground": "${home}/playground"
|
||||||
}
|
}
|
26
main.go
26
main.go
|
@ -37,8 +37,8 @@ import (
|
||||||
"github.com/b3log/wide/log"
|
"github.com/b3log/wide/log"
|
||||||
"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/session"
|
"github.com/b3log/wide/session"
|
||||||
"github.com/b3log/wide/shell"
|
|
||||||
"github.com/b3log/wide/util"
|
"github.com/b3log/wide/util"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -57,6 +57,7 @@ func init() {
|
||||||
confChannel := flag.String("channel", "", "this will overwrite Wide.Channel 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")
|
confDocker := flag.Bool("docker", false, "whether run in a docker container")
|
||||||
|
confPlayground := flag.String("playground", "", "this will overwrite Wide.Playground if specified")
|
||||||
|
|
||||||
flag.Parse()
|
flag.Parse()
|
||||||
|
|
||||||
|
@ -75,7 +76,7 @@ func init() {
|
||||||
event.Load()
|
event.Load()
|
||||||
|
|
||||||
conf.Load(*confPath, *confIP, *confPort, *confServer, *confLogLevel, *confStaticServer, *confContext, *confChannel,
|
conf.Load(*confPath, *confIP, *confPort, *confServer, *confLogLevel, *confStaticServer, *confContext, *confChannel,
|
||||||
*confDocker)
|
*confPlayground, *confDocker)
|
||||||
|
|
||||||
conf.FixedTimeCheckEnv()
|
conf.FixedTimeCheckEnv()
|
||||||
|
|
||||||
|
@ -151,8 +152,8 @@ func main() {
|
||||||
http.HandleFunc(conf.Wide.Context+"/find/usages", handlerWrapper(editor.FindUsagesHandler))
|
http.HandleFunc(conf.Wide.Context+"/find/usages", handlerWrapper(editor.FindUsagesHandler))
|
||||||
|
|
||||||
// shell
|
// shell
|
||||||
http.HandleFunc(conf.Wide.Context+"/shell/ws", handlerWrapper(shell.WSHandler))
|
// http.HandleFunc(conf.Wide.Context+"/shell/ws", handlerWrapper(shell.WSHandler))
|
||||||
http.HandleFunc(conf.Wide.Context+"/shell", handlerWrapper(shell.IndexHandler))
|
// http.HandleFunc(conf.Wide.Context+"/shell", handlerWrapper(shell.IndexHandler))
|
||||||
|
|
||||||
// notification
|
// notification
|
||||||
http.HandleFunc(conf.Wide.Context+"/notification/ws", handlerWrapper(notification.WSHandler))
|
http.HandleFunc(conf.Wide.Context+"/notification/ws", handlerWrapper(notification.WSHandler))
|
||||||
|
@ -163,6 +164,14 @@ func main() {
|
||||||
http.HandleFunc(conf.Wide.Context+"/signup", handlerWrapper(session.SignUpUser))
|
http.HandleFunc(conf.Wide.Context+"/signup", handlerWrapper(session.SignUpUser))
|
||||||
http.HandleFunc(conf.Wide.Context+"/preference", handlerWrapper(session.PreferenceHandler))
|
http.HandleFunc(conf.Wide.Context+"/preference", handlerWrapper(session.PreferenceHandler))
|
||||||
|
|
||||||
|
// playground
|
||||||
|
http.HandleFunc(conf.Wide.Context+"/playground", handlerWrapper(playground.IndexHandler))
|
||||||
|
http.HandleFunc(conf.Wide.Context+"/playground/ws", handlerWrapper(playground.WSHandler))
|
||||||
|
http.HandleFunc(conf.Wide.Context+"/playground/save", handlerWrapper(playground.SaveHandler))
|
||||||
|
http.HandleFunc(conf.Wide.Context+"/playground/build", handlerWrapper(playground.BuildHandler))
|
||||||
|
http.HandleFunc(conf.Wide.Context+"/playground/run", handlerWrapper(playground.RunHandler))
|
||||||
|
http.HandleFunc(conf.Wide.Context+"/playground/stop", handlerWrapper(playground.StopHandler))
|
||||||
|
|
||||||
logger.Infof("Wide is running [%s]", conf.Wide.Server+conf.Wide.Context)
|
logger.Infof("Wide is running [%s]", conf.Wide.Server+conf.Wide.Context)
|
||||||
|
|
||||||
err := http.ListenAndServe(conf.Wide.Server, nil)
|
err := http.ListenAndServe(conf.Wide.Server, nil)
|
||||||
|
@ -186,6 +195,14 @@ func indexHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
username := httpSession.Values["username"].(string)
|
||||||
|
|
||||||
|
if "playground" == username { // reserved user for Playground
|
||||||
|
http.Redirect(w, r, conf.Wide.Context+"login", http.StatusFound)
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
httpSession.Options.MaxAge = conf.Wide.HTTPSessionMaxAge
|
httpSession.Options.MaxAge = conf.Wide.HTTPSessionMaxAge
|
||||||
if "" != conf.Wide.Context {
|
if "" != conf.Wide.Context {
|
||||||
httpSession.Options.Path = conf.Wide.Context
|
httpSession.Options.Path = conf.Wide.Context
|
||||||
|
@ -197,7 +214,6 @@ func indexHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
sid := strconv.Itoa(rand.Int())
|
sid := strconv.Itoa(rand.Int())
|
||||||
wideSession := session.WideSessions.New(httpSession, sid)
|
wideSession := session.WideSessions.New(httpSession, sid)
|
||||||
|
|
||||||
username := httpSession.Values["username"].(string)
|
|
||||||
user := conf.GetUser(username)
|
user := conf.GetUser(username)
|
||||||
if nil == user {
|
if nil == user {
|
||||||
logger.Warnf("Not found user [%s]", username)
|
logger.Warnf("Not found user [%s]", username)
|
||||||
|
|
|
@ -20,6 +20,6 @@ import (
|
||||||
"os/exec"
|
"os/exec"
|
||||||
)
|
)
|
||||||
|
|
||||||
func setNamespace(cmd *exec.Cmd) {
|
func SetNamespace(cmd *exec.Cmd) {
|
||||||
// do nothing
|
// do nothing
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,7 +19,7 @@ import (
|
||||||
"syscall"
|
"syscall"
|
||||||
)
|
)
|
||||||
|
|
||||||
func setNamespace(cmd *exec.Cmd) {
|
func SetNamespace(cmd *exec.Cmd) {
|
||||||
// XXX: keep move with Go 1.4 and later's
|
// XXX: keep move with Go 1.4 and later's
|
||||||
|
|
||||||
cmd.SysProcAttr = &syscall.SysProcAttr{}
|
cmd.SysProcAttr = &syscall.SysProcAttr{}
|
||||||
|
|
|
@ -27,13 +27,13 @@ type procs map[string][]*os.Process
|
||||||
// Processse of all users.
|
// Processse of all users.
|
||||||
//
|
//
|
||||||
// <sid, []*os.Process>
|
// <sid, []*os.Process>
|
||||||
var processes = procs{}
|
var Processes = procs{}
|
||||||
|
|
||||||
// Exclusive lock.
|
// Exclusive lock.
|
||||||
var mutex sync.Mutex
|
var mutex sync.Mutex
|
||||||
|
|
||||||
// add adds the specified process to the user process set.
|
// add adds the specified process to the user process set.
|
||||||
func (procs *procs) add(wSession *session.WideSession, proc *os.Process) {
|
func (procs *procs) Add(wSession *session.WideSession, proc *os.Process) {
|
||||||
mutex.Lock()
|
mutex.Lock()
|
||||||
defer mutex.Unlock()
|
defer mutex.Unlock()
|
||||||
|
|
||||||
|
@ -50,7 +50,7 @@ func (procs *procs) add(wSession *session.WideSession, proc *os.Process) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// remove removes the specified process from the user process set.
|
// remove removes the specified process from the user process set.
|
||||||
func (procs *procs) remove(wSession *session.WideSession, proc *os.Process) {
|
func (procs *procs) Remove(wSession *session.WideSession, proc *os.Process) {
|
||||||
mutex.Lock()
|
mutex.Lock()
|
||||||
defer mutex.Unlock()
|
defer mutex.Unlock()
|
||||||
|
|
||||||
|
@ -75,7 +75,7 @@ func (procs *procs) remove(wSession *session.WideSession, proc *os.Process) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// kill kills a process specified by the given pid.
|
// kill kills a process specified by the given pid.
|
||||||
func (procs *procs) kill(wSession *session.WideSession, pid int) {
|
func (procs *procs) Kill(wSession *session.WideSession, pid int) {
|
||||||
mutex.Lock()
|
mutex.Lock()
|
||||||
defer mutex.Unlock()
|
defer mutex.Unlock()
|
||||||
|
|
||||||
|
|
|
@ -64,7 +64,7 @@ func RunHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
cmd.Dir = curDir
|
cmd.Dir = curDir
|
||||||
|
|
||||||
if conf.Docker {
|
if conf.Docker {
|
||||||
setNamespace(cmd)
|
SetNamespace(cmd)
|
||||||
}
|
}
|
||||||
|
|
||||||
stdout, err := cmd.StdoutPipe()
|
stdout, err := cmd.StdoutPipe()
|
||||||
|
@ -111,7 +111,7 @@ func RunHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
channelRet["pid"] = cmd.Process.Pid
|
channelRet["pid"] = cmd.Process.Pid
|
||||||
|
|
||||||
// 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) {
|
go func(runningId int) {
|
||||||
defer util.Recover()
|
defer util.Recover()
|
||||||
|
@ -151,7 +151,7 @@ func RunHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
|
|
||||||
if nil != err {
|
if nil != err {
|
||||||
// remove the exited process from user process set
|
// remove the exited process from user process set
|
||||||
processes.remove(wSession, cmd.Process)
|
Processes.Remove(wSession, cmd.Process)
|
||||||
|
|
||||||
logger.Tracef("User [%s, %s] 's running [id=%d, file=%s] has done [stdout %v], ", wSession.Username, sid, runningId, filePath, err)
|
logger.Tracef("User [%s, %s] 's running [id=%d, file=%s] has done [stdout %v], ", wSession.Username, sid, runningId, filePath, err)
|
||||||
|
|
||||||
|
@ -253,5 +253,5 @@ func StopHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
processes.kill(wSession, pid)
|
Processes.Kill(wSession, pid)
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,73 @@
|
||||||
|
// Copyright (c) 2014-2015, b3log.org
|
||||||
|
//
|
||||||
|
// 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 playground
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"html/template"
|
||||||
|
"net/http"
|
||||||
|
"os/exec"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/b3log/wide/conf"
|
||||||
|
"github.com/b3log/wide/session"
|
||||||
|
"github.com/b3log/wide/util"
|
||||||
|
)
|
||||||
|
|
||||||
|
// BuildHandler handles request of Playground building.
|
||||||
|
func BuildHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
|
data := map[string]interface{}{"succ": true}
|
||||||
|
defer util.RetJSON(w, r, data)
|
||||||
|
|
||||||
|
httpSession, _ := session.HTTPSession.Get(r, "wide-session")
|
||||||
|
if httpSession.IsNew {
|
||||||
|
http.Error(w, "Forbidden", http.StatusForbidden)
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var args map[string]interface{}
|
||||||
|
if err := json.NewDecoder(r.Body).Decode(&args); err != nil {
|
||||||
|
logger.Error(err)
|
||||||
|
data["succ"] = false
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
filePath := args["filePath"].(string)
|
||||||
|
|
||||||
|
suffix := ""
|
||||||
|
if util.OS.IsWindows() {
|
||||||
|
suffix = ".exe"
|
||||||
|
}
|
||||||
|
|
||||||
|
fileName := filepath.Base(filePath)
|
||||||
|
executable := filepath.Clean(conf.Wide.Playground + "/" + strings.Replace(fileName, ".go", suffix, -1))
|
||||||
|
|
||||||
|
cmd := exec.Command("go", "build", "-o", executable, filePath)
|
||||||
|
|
||||||
|
out, err := cmd.CombinedOutput()
|
||||||
|
data["output"] = template.HTML(string(out))
|
||||||
|
|
||||||
|
if nil != err {
|
||||||
|
logger.Error(err)
|
||||||
|
data["succ"] = false
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
data["executable"] = executable
|
||||||
|
}
|
|
@ -0,0 +1,113 @@
|
||||||
|
// Copyright (c) 2014-2015, b3log.org
|
||||||
|
//
|
||||||
|
// 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 playground
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/md5"
|
||||||
|
"encoding/hex"
|
||||||
|
"encoding/json"
|
||||||
|
"net/http"
|
||||||
|
"os"
|
||||||
|
"os/exec"
|
||||||
|
"path/filepath"
|
||||||
|
|
||||||
|
"github.com/b3log/wide/session"
|
||||||
|
"github.com/b3log/wide/util"
|
||||||
|
"github.com/b3log/wide/conf"
|
||||||
|
)
|
||||||
|
|
||||||
|
// SaveHandler handles request of Playground code save.
|
||||||
|
func SaveHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
|
data := map[string]interface{}{"succ": true}
|
||||||
|
defer util.RetJSON(w, r, data)
|
||||||
|
|
||||||
|
session, _ := session.HTTPSession.Get(r, "wide-session")
|
||||||
|
if session.IsNew {
|
||||||
|
http.Error(w, "Forbidden", http.StatusForbidden)
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var args map[string]interface{}
|
||||||
|
if err := json.NewDecoder(r.Body).Decode(&args); err != nil {
|
||||||
|
logger.Error(err)
|
||||||
|
data["succ"] = false
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
code := args["code"].(string)
|
||||||
|
|
||||||
|
// generate file name
|
||||||
|
hasher := md5.New()
|
||||||
|
hasher.Write([]byte(code))
|
||||||
|
fileName := hex.EncodeToString(hasher.Sum(nil))
|
||||||
|
fileName += ".go"
|
||||||
|
filePath := filepath.Clean(conf.Wide.Playground + "/" + fileName)
|
||||||
|
|
||||||
|
fout, err := os.Create(filePath)
|
||||||
|
if nil != err {
|
||||||
|
logger.Error(err)
|
||||||
|
data["succ"] = false
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
fout.WriteString(code)
|
||||||
|
if err := fout.Close(); nil != err {
|
||||||
|
logger.Error(err)
|
||||||
|
data["succ"] = false
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
data["filePath"] = filePath
|
||||||
|
data["url"] = filepath.ToSlash(filePath)
|
||||||
|
|
||||||
|
argv := []string{filePath}
|
||||||
|
cmd := exec.Command("gofmt", argv...)
|
||||||
|
|
||||||
|
bytes, _ := cmd.Output()
|
||||||
|
output := string(bytes)
|
||||||
|
if "" == output {
|
||||||
|
// format error, returns the original content
|
||||||
|
data["succ"] = true
|
||||||
|
data["code"] = code
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
code = string(output)
|
||||||
|
data["code"] = code
|
||||||
|
|
||||||
|
// generate file name
|
||||||
|
hasher = md5.New()
|
||||||
|
hasher.Write([]byte(code))
|
||||||
|
fileName = hex.EncodeToString(hasher.Sum(nil))
|
||||||
|
fileName += ".go"
|
||||||
|
filePath = filepath.Clean(conf.Wide.Playground + "/" + fileName)
|
||||||
|
data["filePath"] = filePath
|
||||||
|
data["url"] = filepath.ToSlash(filePath)
|
||||||
|
|
||||||
|
fout, err = os.Create(filePath)
|
||||||
|
fout.WriteString(code)
|
||||||
|
if err := fout.Close(); nil != err {
|
||||||
|
logger.Error(err)
|
||||||
|
data["succ"] = false
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,99 @@
|
||||||
|
// Copyright (c) 2014-2015, b3log.org
|
||||||
|
//
|
||||||
|
// 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 playground related mainipulations.
|
||||||
|
package playground
|
||||||
|
|
||||||
|
import (
|
||||||
|
"html/template"
|
||||||
|
"math/rand"
|
||||||
|
"net/http"
|
||||||
|
"os"
|
||||||
|
"strconv"
|
||||||
|
"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"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Logger.
|
||||||
|
var logger = log.NewLogger(os.Stdout)
|
||||||
|
|
||||||
|
// IndexHandler handles request of Playground index.
|
||||||
|
func IndexHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
|
username := "playground"
|
||||||
|
|
||||||
|
// create a HTTP session
|
||||||
|
httpSession, _ := session.HTTPSession.Get(r, "wide-session")
|
||||||
|
httpSession.Values["username"] = username
|
||||||
|
if httpSession.IsNew {
|
||||||
|
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)
|
||||||
|
|
||||||
|
// create a wide session
|
||||||
|
rand.Seed(time.Now().UnixNano())
|
||||||
|
sid := strconv.Itoa(rand.Int())
|
||||||
|
wideSession := session.WideSessions.New(httpSession, sid)
|
||||||
|
|
||||||
|
locale := conf.Wide.Locale
|
||||||
|
|
||||||
|
code := conf.HelloWorld
|
||||||
|
|
||||||
|
model := map[string]interface{}{"conf": conf.Wide, "i18n": i18n.GetAll(locale), "locale": locale,
|
||||||
|
"session": wideSession, "pathSeparator": conf.PathSeparator, "codeMirrorVer": conf.CodeMirrorVer,
|
||||||
|
"code": template.HTML(code)}
|
||||||
|
|
||||||
|
wideSessions := session.WideSessions.GetByUsername(username)
|
||||||
|
|
||||||
|
logger.Tracef("User [%s] has [%d] sessions", username, len(wideSessions))
|
||||||
|
|
||||||
|
t, err := template.ParseFiles("views/playground/index.html")
|
||||||
|
|
||||||
|
if nil != err {
|
||||||
|
logger.Error(err)
|
||||||
|
http.Error(w, err.Error(), 500)
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Execute(w, model)
|
||||||
|
}
|
||||||
|
|
||||||
|
// WSHandler handles request of creating Playground channel.
|
||||||
|
func WSHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
|
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": "Playground initialized", "cmd": "init-playground"}
|
||||||
|
err := wsChan.WriteJSON(&ret)
|
||||||
|
if nil != err {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
session.PlaygroundWS[sid] = &wsChan
|
||||||
|
|
||||||
|
logger.Tracef("Open a new [PlaygroundWS] with session [%s], %d", sid, len(session.PlaygroundWS))
|
||||||
|
}
|
|
@ -0,0 +1,253 @@
|
||||||
|
// Copyright (c) 2014-2015, b3log.org
|
||||||
|
//
|
||||||
|
// 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 playground
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"encoding/json"
|
||||||
|
"math/rand"
|
||||||
|
"net/http"
|
||||||
|
"os/exec"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/b3log/wide/conf"
|
||||||
|
"github.com/b3log/wide/output"
|
||||||
|
"github.com/b3log/wide/session"
|
||||||
|
"github.com/b3log/wide/util"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
outputBufMax = 128 // 128 string(rune)
|
||||||
|
outputTimeout = 100 // 100ms
|
||||||
|
)
|
||||||
|
|
||||||
|
type outputBuf struct {
|
||||||
|
content string
|
||||||
|
millisecond int64
|
||||||
|
}
|
||||||
|
|
||||||
|
// RunHandler handles request of executing a binary file.
|
||||||
|
func RunHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
|
data := map[string]interface{}{"succ": true}
|
||||||
|
defer util.RetJSON(w, r, data)
|
||||||
|
|
||||||
|
var args map[string]interface{}
|
||||||
|
|
||||||
|
if err := json.NewDecoder(r.Body).Decode(&args); err != nil {
|
||||||
|
logger.Error(err)
|
||||||
|
data["succ"] = false
|
||||||
|
}
|
||||||
|
|
||||||
|
sid := args["sid"].(string)
|
||||||
|
wSession := session.WideSessions.Get(sid)
|
||||||
|
if nil == wSession {
|
||||||
|
data["succ"] = false
|
||||||
|
}
|
||||||
|
|
||||||
|
filePath := args["executable"].(string)
|
||||||
|
|
||||||
|
cmd := exec.Command(filePath)
|
||||||
|
|
||||||
|
if conf.Docker {
|
||||||
|
output.SetNamespace(cmd)
|
||||||
|
}
|
||||||
|
|
||||||
|
stdout, err := cmd.StdoutPipe()
|
||||||
|
if nil != err {
|
||||||
|
logger.Error(err)
|
||||||
|
data["succ"] = false
|
||||||
|
}
|
||||||
|
|
||||||
|
stderr, err := cmd.StderrPipe()
|
||||||
|
if nil != err {
|
||||||
|
logger.Error(err)
|
||||||
|
data["succ"] = false
|
||||||
|
}
|
||||||
|
|
||||||
|
outReader := bufio.NewReader(stdout)
|
||||||
|
errReader := bufio.NewReader(stderr)
|
||||||
|
|
||||||
|
if err := cmd.Start(); nil != err {
|
||||||
|
logger.Error(err)
|
||||||
|
data["succ"] = false
|
||||||
|
}
|
||||||
|
|
||||||
|
wsChannel := session.PlaygroundWS[sid]
|
||||||
|
|
||||||
|
channelRet := map[string]interface{}{}
|
||||||
|
|
||||||
|
if !data["succ"].(bool) {
|
||||||
|
if nil != wsChannel {
|
||||||
|
channelRet["cmd"] = "run-done"
|
||||||
|
channelRet["output"] = ""
|
||||||
|
|
||||||
|
err := wsChannel.WriteJSON(&channelRet)
|
||||||
|
if nil != err {
|
||||||
|
logger.Error(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
wsChannel.Refresh()
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
channelRet["pid"] = cmd.Process.Pid
|
||||||
|
|
||||||
|
// add the process to user's process set
|
||||||
|
output.Processes.Add(wSession, cmd.Process)
|
||||||
|
|
||||||
|
go func(runningId int) {
|
||||||
|
defer util.Recover()
|
||||||
|
defer cmd.Wait()
|
||||||
|
|
||||||
|
// push once for front-end to get the 'run' state and pid
|
||||||
|
if nil != wsChannel {
|
||||||
|
channelRet["cmd"] = "run"
|
||||||
|
channelRet["output"] = ""
|
||||||
|
err := wsChannel.WriteJSON(&channelRet)
|
||||||
|
if nil != err {
|
||||||
|
logger.Error(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
wsChannel.Refresh()
|
||||||
|
}
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
buf := outputBuf{}
|
||||||
|
|
||||||
|
for {
|
||||||
|
wsChannel := session.PlaygroundWS[sid]
|
||||||
|
if nil == wsChannel {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
r, _, err := outReader.ReadRune()
|
||||||
|
|
||||||
|
oneRuneStr := string(r)
|
||||||
|
oneRuneStr = strings.Replace(oneRuneStr, "<", "<", -1)
|
||||||
|
oneRuneStr = strings.Replace(oneRuneStr, ">", ">", -1)
|
||||||
|
|
||||||
|
buf.content += oneRuneStr
|
||||||
|
|
||||||
|
if nil != err {
|
||||||
|
// remove the exited process from user process set
|
||||||
|
output.Processes.Remove(wSession, cmd.Process)
|
||||||
|
|
||||||
|
logger.Tracef("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.Error(err)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
wsChannel.Refresh()
|
||||||
|
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
now := time.Now().UnixNano() / int64(time.Millisecond)
|
||||||
|
|
||||||
|
if 0 == buf.millisecond {
|
||||||
|
buf.millisecond = now
|
||||||
|
}
|
||||||
|
|
||||||
|
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.Error(err)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
wsChannel.Refresh()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
buf := outputBuf{}
|
||||||
|
for {
|
||||||
|
r, _, err := errReader.ReadRune()
|
||||||
|
|
||||||
|
wsChannel := session.PlaygroundWS[sid]
|
||||||
|
if nil != err || nil == wsChannel {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
oneRuneStr := string(r)
|
||||||
|
oneRuneStr = strings.Replace(oneRuneStr, "<", "<", -1)
|
||||||
|
oneRuneStr = strings.Replace(oneRuneStr, ">", ">", -1)
|
||||||
|
|
||||||
|
buf.content += oneRuneStr
|
||||||
|
|
||||||
|
now := time.Now().UnixNano() / int64(time.Millisecond)
|
||||||
|
|
||||||
|
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.Error(err)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
wsChannel.Refresh()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}(rand.Int())
|
||||||
|
}
|
||||||
|
|
||||||
|
// StopHandler handles request of stoping a running process.
|
||||||
|
func StopHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
|
data := map[string]interface{}{"succ": true}
|
||||||
|
defer util.RetJSON(w, r, data)
|
||||||
|
|
||||||
|
var args map[string]interface{}
|
||||||
|
if err := json.NewDecoder(r.Body).Decode(&args); err != nil {
|
||||||
|
logger.Error(err)
|
||||||
|
data["succ"] = false
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
sid := args["sid"].(string)
|
||||||
|
pid := int(args["pid"].(float64))
|
||||||
|
|
||||||
|
wSession := session.WideSessions.Get(sid)
|
||||||
|
if nil == wSession {
|
||||||
|
data["succ"] = false
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
output.Processes.Kill(wSession, pid)
|
||||||
|
}
|
|
@ -60,6 +60,9 @@ var (
|
||||||
|
|
||||||
// NotificationWS holds all notification channels. <sid, *util.WSChannel>
|
// NotificationWS holds all notification channels. <sid, *util.WSChannel>
|
||||||
NotificationWS = map[string]*util.WSChannel{}
|
NotificationWS = map[string]*util.WSChannel{}
|
||||||
|
|
||||||
|
// PlaygroundWS holds all playground channels. <sid, *util.WSChannel>
|
||||||
|
PlaygroundWS = map[string]*util.WSChannel{}
|
||||||
)
|
)
|
||||||
|
|
||||||
// HTTP session store.
|
// HTTP session store.
|
||||||
|
@ -371,6 +374,11 @@ func (sessions *wSessions) Remove(sid string) {
|
||||||
delete(SessionWS, sid)
|
delete(SessionWS, sid)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if ws, ok := PlaygroundWS[sid]; ok {
|
||||||
|
ws.Close()
|
||||||
|
delete(PlaygroundWS, sid)
|
||||||
|
}
|
||||||
|
|
||||||
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.HTTPSession.Values["id"] == s.HTTPSession.Values["id"] {
|
if ses.HTTPSession.Values["id"] == s.HTTPSession.Values["id"] {
|
||||||
|
|
|
@ -315,6 +315,10 @@ func getOnlineUsers() []*conf.User {
|
||||||
for _, username := range usernames {
|
for _, username := range usernames {
|
||||||
u := conf.GetUser(username)
|
u := conf.GetUser(username)
|
||||||
|
|
||||||
|
if "playground" == username { // user [playground] is a reserved mock user
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
if nil == u {
|
if nil == u {
|
||||||
logger.Warnf("Not found user [%s]", username)
|
logger.Warnf("Not found user [%s]", username)
|
||||||
|
|
||||||
|
@ -333,7 +337,13 @@ func getOnlineUsers() []*conf.User {
|
||||||
// 2. generate 'Hello, 世界' demo code in the workspace (a console version and a http version)
|
// 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
|
// 3. update the user customized configurations, such as style.css
|
||||||
// 4. serve files of the user's workspace via HTTP
|
// 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 {
|
func addUser(username, password, email string) string {
|
||||||
|
if "playground" == username {
|
||||||
|
return userExists
|
||||||
|
}
|
||||||
|
|
||||||
addUserMutex.Lock()
|
addUserMutex.Lock()
|
||||||
defer addUserMutex.Unlock()
|
defer addUserMutex.Unlock()
|
||||||
|
|
||||||
|
@ -393,14 +403,7 @@ func consoleHello(workspace string) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
fout.WriteString(`package main
|
fout.WriteString(conf.HelloWorld)
|
||||||
|
|
||||||
import "fmt"
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
fmt.Println("Hello, 世界")
|
|
||||||
}
|
|
||||||
`)
|
|
||||||
|
|
||||||
fout.Close()
|
fout.Close()
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,196 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2014-2015, b3log.org
|
||||||
|
*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
var playground = {
|
||||||
|
editor: undefined,
|
||||||
|
pid: undefined,
|
||||||
|
init: function () {
|
||||||
|
$("#editorDiv").append("<textarea id='editor'></textarea>");
|
||||||
|
var textArea = document.getElementById("editor");
|
||||||
|
textArea.value = code;
|
||||||
|
playground.editor = CodeMirror.fromTextArea(textArea, {
|
||||||
|
lineNumbers: true,
|
||||||
|
autofocus: true,
|
||||||
|
autoCloseBrackets: true,
|
||||||
|
matchBrackets: true,
|
||||||
|
highlightSelectionMatches: {showToken: /\w/},
|
||||||
|
rulers: [{color: "#ccc", column: 120, lineStyle: "dashed"}],
|
||||||
|
styleActiveLine: true,
|
||||||
|
theme: "wide",
|
||||||
|
tabSize: 4,
|
||||||
|
indentUnit: 4,
|
||||||
|
foldGutter: true,
|
||||||
|
cursorHeight: 1,
|
||||||
|
});
|
||||||
|
|
||||||
|
this._initWS();
|
||||||
|
},
|
||||||
|
_initWS: function () {
|
||||||
|
// Used for session retention, server will release all resources of the session if this channel closed
|
||||||
|
var sessionWS = new ReconnectingWebSocket(config.channel + '/session/ws?sid=' + config.wideSessionId);
|
||||||
|
|
||||||
|
sessionWS.onopen = function () {
|
||||||
|
console.log('[session onopen] connected');
|
||||||
|
};
|
||||||
|
|
||||||
|
sessionWS.onmessage = function (e) {
|
||||||
|
console.log('[session onmessage]' + e.data);
|
||||||
|
};
|
||||||
|
sessionWS.onclose = function (e) {
|
||||||
|
console.log('[session onclose] disconnected (' + e.code + ')');
|
||||||
|
};
|
||||||
|
sessionWS.onerror = function (e) {
|
||||||
|
console.log('[session onerror] ' + JSON.parse(e));
|
||||||
|
};
|
||||||
|
|
||||||
|
var playgroundWS = new ReconnectingWebSocket(config.channel + '/playground/ws?sid=' + config.wideSessionId);
|
||||||
|
|
||||||
|
playgroundWS.onopen = function () {
|
||||||
|
console.log('[playground onopen] connected');
|
||||||
|
};
|
||||||
|
|
||||||
|
playgroundWS.onmessage = function (e) {
|
||||||
|
console.log('[playground onmessage]' + e.data);
|
||||||
|
|
||||||
|
var data = JSON.parse(e.data);
|
||||||
|
|
||||||
|
playground.pid = data.pid;
|
||||||
|
|
||||||
|
var val = $("#output").val();
|
||||||
|
$("#output").val(val + data.output);
|
||||||
|
};
|
||||||
|
playgroundWS.onclose = function (e) {
|
||||||
|
console.log('[playground onclose] disconnected (' + e.code + ')');
|
||||||
|
};
|
||||||
|
playgroundWS.onerror = function (e) {
|
||||||
|
console.log('[playground onerror] ' + JSON.parse(e));
|
||||||
|
};
|
||||||
|
},
|
||||||
|
share: function () {
|
||||||
|
if (!playground.editor) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var request = newWideRequest();
|
||||||
|
request.pid = playground.pid;
|
||||||
|
|
||||||
|
var code = playground.editor.getValue();
|
||||||
|
|
||||||
|
var request = newWideRequest();
|
||||||
|
request.code = code;
|
||||||
|
|
||||||
|
$.ajax({
|
||||||
|
type: 'POST',
|
||||||
|
url: config.context + '/playground/save',
|
||||||
|
data: JSON.stringify(request),
|
||||||
|
dataType: "json",
|
||||||
|
success: function (data) {
|
||||||
|
playground.editor.setValue(data.code);
|
||||||
|
|
||||||
|
if (!data.succ) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var url = window.location.protocol + "//" + window.location.host + '/' + data.url;
|
||||||
|
var html = '<a href="' + url + '" target="_blank">'
|
||||||
|
+ url + "</a>";
|
||||||
|
$("#url").html(html);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
stop: function () {
|
||||||
|
if (!playground.editor || !playground.pid) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var request = newWideRequest();
|
||||||
|
request.pid = playground.pid;
|
||||||
|
|
||||||
|
$.ajax({
|
||||||
|
type: 'POST',
|
||||||
|
url: config.context + '/playground/stop',
|
||||||
|
data: JSON.stringify(request),
|
||||||
|
dataType: "json"
|
||||||
|
});
|
||||||
|
},
|
||||||
|
run: function () {
|
||||||
|
if (!playground.editor) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var code = playground.editor.getValue();
|
||||||
|
|
||||||
|
// Step 1. save & format code
|
||||||
|
var request = newWideRequest();
|
||||||
|
request.code = code;
|
||||||
|
|
||||||
|
$("#output").val("");
|
||||||
|
|
||||||
|
$.ajax({
|
||||||
|
type: 'POST',
|
||||||
|
url: config.context + '/playground/save',
|
||||||
|
data: JSON.stringify(request),
|
||||||
|
dataType: "json",
|
||||||
|
success: function (data) {
|
||||||
|
// console.log(data);
|
||||||
|
playground.editor.setValue(data.code);
|
||||||
|
|
||||||
|
if (!data.succ) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Step 2. compile code
|
||||||
|
var request = newWideRequest();
|
||||||
|
request.filePath = data.filePath;
|
||||||
|
|
||||||
|
$.ajax({
|
||||||
|
type: 'POST',
|
||||||
|
url: config.context + '/playground/build',
|
||||||
|
data: JSON.stringify(request),
|
||||||
|
dataType: "json",
|
||||||
|
success: function (data) {
|
||||||
|
// console.log(data);
|
||||||
|
|
||||||
|
$("#output").val(data.output);
|
||||||
|
|
||||||
|
if (!data.succ) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Step 3. run the executable binary and handle its output
|
||||||
|
var request = newWideRequest();
|
||||||
|
request.executable = data.executable;
|
||||||
|
|
||||||
|
$.ajax({
|
||||||
|
type: 'POST',
|
||||||
|
url: config.context + '/playground/run',
|
||||||
|
data: JSON.stringify(request),
|
||||||
|
dataType: "json",
|
||||||
|
success: function (data) {
|
||||||
|
// console.log(data);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
$(document).ready(function () {
|
||||||
|
playground.init();
|
||||||
|
});
|
||||||
|
|
|
@ -121,7 +121,7 @@ var session = {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
_initWS: function () {
|
_initWS: function () {
|
||||||
// 用于保持会话,如果该通道断开,则服务器端会销毁会话状态,回收相关资源.
|
// Used for session retention, server will release all resources of the session if this channel closed
|
||||||
var sessionWS = new ReconnectingWebSocket(config.channel + '/session/ws?sid=' + config.wideSessionId);
|
var sessionWS = new ReconnectingWebSocket(config.channel + '/session/ws?sid=' + config.wideSessionId);
|
||||||
|
|
||||||
sessionWS.onopen = function () {
|
sessionWS.onopen = function () {
|
||||||
|
|
|
@ -0,0 +1,76 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<title>{{.i18n.wide}} - {{.i18n.wide_title}}</title>
|
||||||
|
|
||||||
|
<meta name="keywords" content="Wide, Golang, IDE, Team, Cloud, B3log, Playground"/>
|
||||||
|
<meta name="description" content="A Web-based IDE for Teams using Golang, do your development anytime, anywhere."/>
|
||||||
|
<meta name="author" content="B3log">
|
||||||
|
|
||||||
|
<link rel="stylesheet" href="{{.conf.StaticServer}}/static/css/base.css?{{.conf.StaticResourceVersion}}">
|
||||||
|
|
||||||
|
<link rel="stylesheet" href="{{.conf.StaticServer}}/static/js/lib/codemirror-{{.codeMirrorVer}}/codemirror.css">
|
||||||
|
<link rel="stylesheet" href="{{$.conf.StaticServer}}/static/js/overwrite/codemirror/theme/wide.css">
|
||||||
|
|
||||||
|
<link rel="icon" type="image/x-icon" href="/favicon.ico" />
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div>
|
||||||
|
<button id="run" onclick="playground.run();">Run</button>
|
||||||
|
<button id="stop" onclick="playground.stop();">Stop</button>
|
||||||
|
<button id="share" onclick="playground.share();">Share</button>
|
||||||
|
<span id="url"></span>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<div id="editorDiv">
|
||||||
|
</div>
|
||||||
|
<textarea id="output" rows="20" readonly="readonly" style="width: 100%;" ></textarea>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
var config = {
|
||||||
|
"context": "{{.conf.Context}}",
|
||||||
|
"staticServer": "{{.conf.StaticServer}}",
|
||||||
|
"channel": "{{.conf.Channel}}",
|
||||||
|
"wideSessionId": "{{.session.ID}}"
|
||||||
|
};
|
||||||
|
var code = "{{.code}}";
|
||||||
|
|
||||||
|
function newWideRequest() {
|
||||||
|
var ret = {
|
||||||
|
sid: config.wideSessionId
|
||||||
|
};
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<script type="text/javascript" src="{{.conf.StaticServer}}/static/js/lib/jquery-2.1.1.min.js"></script>
|
||||||
|
<script type="text/javascript" src="{{.conf.StaticServer}}/static/js/lib/reconnecting-websocket.js"></script>
|
||||||
|
<script type="text/javascript" src="{{.conf.StaticServer}}/static/js/lib/ztree/jquery.ztree.all-3.5.min.js"></script>
|
||||||
|
|
||||||
|
<script type="text/javascript" src="{{.conf.StaticServer}}/static/js/lib/codemirror-{{.codeMirrorVer}}/codemirror.js"></script>
|
||||||
|
<script type="text/javascript" src="{{.conf.StaticServer}}/static/js/lib/codemirror-{{.codeMirrorVer}}/addon/selection/active-line.js"></script>
|
||||||
|
<script type="text/javascript" src="{{.conf.StaticServer}}/static/js/overwrite/codemirror/addon/hint/show-hint.js"></script>
|
||||||
|
<script type="text/javascript" src="{{.conf.StaticServer}}/static/js/lib/codemirror-{{.codeMirrorVer}}/addon/hint/anyword-hint.js"></script>
|
||||||
|
<script type="text/javascript" src="{{.conf.StaticServer}}/static/js/lib/codemirror-{{.codeMirrorVer}}/addon/display/rulers.js"></script>
|
||||||
|
<script type="text/javascript" src="{{.conf.StaticServer}}/static/js/lib/codemirror-{{.codeMirrorVer}}/addon/edit/closebrackets.js"></script>
|
||||||
|
<script type="text/javascript" src="{{.conf.StaticServer}}/static/js/lib/codemirror-{{.codeMirrorVer}}/addon/edit/matchbrackets.js"></script>
|
||||||
|
<script type="text/javascript" src="{{.conf.StaticServer}}/static/js/lib/codemirror-{{.codeMirrorVer}}/addon/edit/closetag.js"></script>
|
||||||
|
<script type="text/javascript" src="{{.conf.StaticServer}}/static/js/lib/codemirror-{{.codeMirrorVer}}/addon/search/searchcursor.js"></script>
|
||||||
|
<script type="text/javascript" src="{{.conf.StaticServer}}/static/js/lib/codemirror-{{.codeMirrorVer}}/addon/search/search.js"></script>
|
||||||
|
<script type="text/javascript" src="{{.conf.StaticServer}}/static/js/lib/codemirror-{{.codeMirrorVer}}/addon/dialog/dialog.js"></script>
|
||||||
|
<script type="text/javascript" src="{{.conf.StaticServer}}/static/js/lib/codemirror-{{.codeMirrorVer}}/addon/search/match-highlighter.js"></script>
|
||||||
|
<script type="text/javascript" src="{{.conf.StaticServer}}/static/js/lib/codemirror-{{.codeMirrorVer}}/addon/fold/foldcode.js"></script>
|
||||||
|
<script type="text/javascript" src="{{.conf.StaticServer}}/static/js/lib/codemirror-{{.codeMirrorVer}}/addon/fold/foldgutter.js"></script>
|
||||||
|
<script type="text/javascript" src="{{.conf.StaticServer}}/static/js/lib/codemirror-{{.codeMirrorVer}}/addon/fold/brace-fold.js"></script>
|
||||||
|
<script type="text/javascript" src="{{.conf.StaticServer}}/static/js/lib/codemirror-{{.codeMirrorVer}}/addon/fold/comment-fold.js"></script>
|
||||||
|
<script type="text/javascript" src="{{.conf.StaticServer}}/static/js/lib/codemirror-{{.codeMirrorVer}}/addon/mode/loadmode.js"></script>
|
||||||
|
<script type="text/javascript" src="{{.conf.StaticServer}}/static/js/lib/codemirror-{{.codeMirrorVer}}/addon/comment/comment.js"></script>
|
||||||
|
<script type="text/javascript" src="{{.conf.StaticServer}}/static/js/lib/codemirror-{{.codeMirrorVer}}/mode/meta.js"></script>
|
||||||
|
<script type="text/javascript" src="{{.conf.StaticServer}}/static/js/lib/codemirror-{{.codeMirrorVer}}/mode/go/go.js"></script>
|
||||||
|
|
||||||
|
<script type="text/javascript" src="{{.conf.StaticServer}}/static/js/playground.js?{{.conf.StaticResourceVersion}}"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
Loading…
Reference in New Issue