├── .gitignore ├── README.md ├── beewatch.go ├── beewatch.json ├── beewatch_test.go ├── http.go ├── monitor.go ├── static ├── css │ ├── furatto.css │ └── normalize.css ├── fonts │ ├── fontawesome │ │ ├── FontAwesome.otf │ │ ├── fontawesome-webfont.eot │ │ ├── fontawesome-webfont.svg │ │ ├── fontawesome-webfont.ttf │ │ └── fontawesome-webfont.woff │ ├── meteocons-webfont.eot │ ├── meteocons-webfont.svg │ ├── meteocons-webfont.ttf │ └── meteocons-webfont.woff ├── img │ ├── icheck │ │ ├── aero.png │ │ ├── aero@2x.png │ │ ├── blue.png │ │ ├── blue@2x.png │ │ ├── flat.png │ │ ├── flat@2x.png │ │ ├── green.png │ │ ├── green@2x.png │ │ ├── grey.png │ │ ├── grey@2x.png │ │ ├── orange.png │ │ ├── orange@2x.png │ │ ├── pink.png │ │ ├── pink@2x.png │ │ ├── purple.png │ │ ├── purple@2x.png │ │ ├── red.png │ │ ├── red@2x.png │ │ ├── yellow.png │ │ └── yellow@2x.png │ ├── icons │ │ ├── customize-icon150.png │ │ ├── flexible-icon.png │ │ ├── github-128-black.png │ │ ├── iphone-icon150.png │ │ ├── lock-icon128.png │ │ ├── rocket-icon128.png │ │ ├── rocket-icon150.png │ │ ├── screen-icon.png │ │ ├── screens-icon.png │ │ └── screens2-icon.png │ ├── next.png │ ├── previous.png │ ├── themes.gif │ └── toggle.png └── js │ ├── beewatch.js │ ├── furatto.min.js │ └── jquery-1.10.2.min.js ├── tests ├── demo │ ├── beewatch.go │ └── beewatch.json └── images │ └── demo_beewatch.png ├── utils.go ├── views └── home.html └── watchpoint.go /.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled Object files, Static and Dynamic libs (Shared Objects) 2 | *.o 3 | *.a 4 | *.so 5 | 6 | # Folders 7 | _obj 8 | _test 9 | .idea/ 10 | 11 | # Architecture specific extensions/prefixes 12 | *.[568vq] 13 | [568vq].out 14 | 15 | *.cgo1.go 16 | *.cgo2.c 17 | _cgo_defun.c 18 | _cgo_gotypes.go 19 | _cgo_export.* 20 | 21 | _testmain.go 22 | 23 | *.exe 24 | .DS_Store 25 | beebbs 26 | bee 27 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Bee Watch 2 | ======== 3 | 4 | [![Build Status](https://drone.io/github.com/beego/beewatch/status.png)](https://drone.io/github.com/beego/beewatch/latest) 5 | 6 | Bee Watch is an interactive debugger for the Go programming language. 7 | 8 | ## Features 9 | 10 | - Use `Critical`, `Info` and `Trace` three levels to change debugger behavior. 11 | - `Display()` variable values or `Printf()` with customized format. 12 | - User-friendly Web UI for **WebSocket** mode or easy-control **command-line** mode. 13 | - Call `AddWatchVars()` to monitor variables and show their information when the program calls `Break()`. 14 | - Configuration file with your customized settings(`beewatch.json`). 15 | 16 | ## Installation 17 | 18 | Bee Watch is a "go get" able Go project, you can execute the following command to auto-install: 19 | 20 | go get github.com/beego/beewatch 21 | 22 | **Attention** This project can only be installed by source code now. 23 | 24 | ## Quick start 25 | 26 | [beego.me/docs/Reference_BeeWatch](http://beego.me/docs/Reference_BeeWatch) 27 | 28 | ## Credits 29 | 30 | - [emicklei/hopwatch](https://github.com/emicklei/hopwatch) 31 | - [realint/dbgutil](https://github.com/realint/dbgutil) 32 | 33 | ## Examples and API documentation 34 | 35 | [Go Walker](http://gowalker.org/github.com/beego/beewatch) 36 | 37 | 38 | ## License 39 | 40 | [Apache License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0.html). -------------------------------------------------------------------------------- /beewatch.go: -------------------------------------------------------------------------------- 1 | // Copyright 2013 Unknown 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"): you may 4 | // not use this file except in compliance with the License. You may obtain 5 | // a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 11 | // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 12 | // License for the specific language governing permissions and limitations 13 | // under the License. 14 | 15 | // Bee Watch is an interactive debugger for the Go programming language. 16 | package beewatch 17 | 18 | import ( 19 | "encoding/json" 20 | "os" 21 | ) 22 | 23 | const ( 24 | APP_VER = "0.5.2.0830" 25 | ) 26 | 27 | type debugLevel int 28 | 29 | const ( 30 | LevelTrace debugLevel = iota 31 | LevelInfo 32 | LevelCritical 33 | ) 34 | 35 | var ( 36 | watchLevel debugLevel 37 | isStarted bool 38 | ) 39 | 40 | var App struct { 41 | Name string `json:"app_name"` 42 | HttpPort int `json:"http_port"` 43 | WatchEnabled bool `json:"watch_enabled"` 44 | CmdMode bool `json:"cmd_mode"` 45 | SkipSuspend bool `json:"skip_suspend"` 46 | PrintStack bool `json:"print_stack"` 47 | PrintSource bool `json:"print_source"` 48 | } 49 | 50 | func loadJSON() { 51 | wd, err := os.Getwd() 52 | if err != nil { 53 | colorLog("[ERRO] BW: Fail to get work directory[ %s ]\n", err) 54 | return 55 | } 56 | 57 | f, err := os.Open(wd + "/beewatch.json") 58 | if err != nil { 59 | colorLog("[WARN] BW: Fail to load beewatch.json[ %s ]\n", err) 60 | return 61 | } 62 | defer f.Close() 63 | 64 | d := json.NewDecoder(f) 65 | err = d.Decode(&App) 66 | if err != nil { 67 | colorLog("[WARN] BW: Fail to parse beewatch.json[ %s ]\n", err) 68 | os.Exit(2) 69 | } 70 | } 71 | 72 | // Start initialize debugger data. 73 | func Start(wl ...debugLevel) { 74 | if isStarted { 75 | colorLog("[ERRO] Fail to start Bee Watch[ %s ]", 76 | "cannot start Bee Watch twice") 77 | return 78 | } 79 | 80 | isStarted = true 81 | colorLog("[INIT] BW: Bee Watch v%s.\n", APP_VER) 82 | 83 | watchLevel = LevelTrace 84 | if len(wl) > 0 { 85 | watchLevel = wl[0] 86 | } 87 | 88 | App.Name = "Bee Watch" 89 | App.HttpPort = 23456 90 | App.WatchEnabled = true 91 | App.PrintStack = true 92 | App.PrintSource = true 93 | 94 | loadJSON() 95 | 96 | if App.WatchEnabled { 97 | if App.CmdMode { 98 | 99 | } else { 100 | initHTTP() 101 | } 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /beewatch.json: -------------------------------------------------------------------------------- 1 | { 2 | "app_name": "Bee Watch Demo", 3 | "http_port": 23456, 4 | "watch_enabled": true, 5 | "cmd_mode": false, 6 | "skip_suspend": false, 7 | "print_stack": true, 8 | "print_source": true 9 | } 10 | -------------------------------------------------------------------------------- /beewatch_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2013 Unknown 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"): you may 4 | // not use this file except in compliance with the License. You may obtain 5 | // a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 11 | // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 12 | // License for the specific language governing permissions and limitations 13 | // under the License. 14 | 15 | package beewatch 16 | 17 | import ( 18 | "testing" 19 | ) 20 | 21 | func Test_BeeWatch(t *testing.T) { 22 | 23 | } 24 | -------------------------------------------------------------------------------- /http.go: -------------------------------------------------------------------------------- 1 | // Copyright 2013 Unknown 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"): you may 4 | // not use this file except in compliance with the License. You may obtain 5 | // a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 11 | // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 12 | // License for the specific language governing permissions and limitations 13 | // under the License. 14 | 15 | package beewatch 16 | 17 | import ( 18 | "fmt" 19 | "html/template" 20 | "io" 21 | "net/http" 22 | "os" 23 | "strings" 24 | 25 | "code.google.com/p/go.net/websocket" 26 | ) 27 | 28 | var ( 29 | viewPath string 30 | data map[interface{}]interface{} 31 | ) 32 | 33 | func initHTTP() { 34 | // Static path. 35 | sp, err := getStaticPath() 36 | if err != nil { 37 | colorLog("[ERRO] BW: Fail to get static path[ %s ]\n", err) 38 | os.Exit(2) 39 | } 40 | 41 | http.Handle("/static/", http.StripPrefix("/static/", 42 | http.FileServer(http.Dir(sp)))) 43 | colorLog("[INIT] BW: File server( %s )\n", 44 | sp[:strings.LastIndex(sp, "/")]) 45 | 46 | // View path. 47 | viewPath = strings.Replace(sp, "static", "views", 1) 48 | 49 | http.HandleFunc("/", mainPage) 50 | http.HandleFunc("/gosource", gosource) 51 | http.Handle("/beewatch", websocket.Handler(connectHandler)) 52 | 53 | data = make(map[interface{}]interface{}) 54 | data["AppVer"] = "v" + APP_VER 55 | data["AppName"] = App.Name 56 | 57 | go sendLoop() 58 | go listen() 59 | } 60 | 61 | func listen() { 62 | err := http.ListenAndServe(":"+fmt.Sprintf("%d", App.HttpPort), nil) 63 | if err != nil { 64 | colorLog("[ERRO] BW: Server crashed[ %s ]\n", err) 65 | os.Exit(2) 66 | } 67 | } 68 | 69 | // Close sends disconnect signal to the browser. 70 | func Close() { 71 | if App.WatchEnabled && !App.CmdMode { 72 | channelExchangeCommands(LevelCritical, command{Action: "DONE"}) 73 | } 74 | } 75 | 76 | func mainPage(w http.ResponseWriter, r *http.Request) { 77 | b, err := loadFile(viewPath + "/home.html") 78 | if err != nil { 79 | colorLog("[ERRO] BW: Fail to load template[ %s ]\n", err) 80 | os.Exit(2) 81 | } 82 | t := template.New("home.html") 83 | t.Parse(string(b)) 84 | t.Execute(w, data) 85 | } 86 | 87 | // serve a (source) file for displaying in the debugger 88 | func gosource(w http.ResponseWriter, r *http.Request) { 89 | if App.PrintSource { 90 | fileName := r.FormValue("file") 91 | // should check for permission? 92 | w.Header().Set("Cache-control", "no-store, no-cache, must-revalidate") 93 | http.ServeFile(w, r, fileName) 94 | } else { 95 | io.WriteString(w, "'print_source' disenabled.") 96 | } 97 | } 98 | 99 | var ( 100 | currentWebsocket *websocket.Conn 101 | connectChannel = make(chan command) 102 | toBrowserChannel = make(chan command) 103 | fromBrowserChannel = make(chan command) 104 | ) 105 | 106 | // command is used to transport message to and from the debugger. 107 | type command struct { 108 | Action string 109 | Level string 110 | Parameters map[string]string 111 | WatchVars map[string]*watchVar 112 | } 113 | 114 | // addParam adds a key,value string pair to the command ; no check on overwrites. 115 | func (c *command) addParam(key, value string) { 116 | if c.Parameters == nil { 117 | c.Parameters = map[string]string{} 118 | } 119 | c.Parameters[key] = value 120 | } 121 | 122 | func connectHandler(ws *websocket.Conn) { 123 | if currentWebsocket != nil { 124 | colorLog("[INFO] BW: Connection has already been established, ignore.\n") 125 | return 126 | } 127 | 128 | var cmd command 129 | if err := websocket.JSON.Receive(ws, &cmd); err != nil { 130 | colorLog("[ERRO] BW: Fail to establish connection[ %s ]\n", err) 131 | } else { 132 | currentWebsocket = ws 133 | connectChannel <- cmd 134 | App.WatchEnabled = true 135 | colorLog("[SUCC] BW: Connected to browser, ready to watch.\n") 136 | receiveLoop() 137 | } 138 | } 139 | 140 | // receiveLoop reads commands from the websocket and puts them onto a channel. 141 | func receiveLoop() { 142 | for { 143 | var cmd command 144 | if err := websocket.JSON.Receive(currentWebsocket, &cmd); err != nil { 145 | colorLog("[ERRO] BW: connectHandler.JSON.Receive failed[ %s ]\n", err) 146 | cmd = command{Action: "QUIT"} 147 | } 148 | 149 | colorLog("[SUCC] BW: Received %v.\n", cmd) 150 | if "QUIT" == cmd.Action { 151 | App.WatchEnabled = false 152 | colorLog("[INFO] BW: Browser requests disconnect.\n") 153 | currentWebsocket.Close() 154 | currentWebsocket = nil 155 | //toBrowserChannel <- cmd 156 | if cmd.Parameters["PASSIVE"] == "1" { 157 | fromBrowserChannel <- cmd 158 | } 159 | close(toBrowserChannel) 160 | close(fromBrowserChannel) 161 | App.WatchEnabled = false 162 | colorLog("[WARN] BW: Disconnected.\n") 163 | break 164 | } else { 165 | fromBrowserChannel <- cmd 166 | } 167 | } 168 | 169 | colorLog("[WARN] BW: Exit receive loop.\n") 170 | //go sendLoop() 171 | } 172 | 173 | // sendLoop takes commands from a channel to send to the browser (debugger). 174 | // If no connection is available then wait for it. 175 | // If the command action is quit then abort the loop. 176 | func sendLoop() { 177 | if currentWebsocket == nil { 178 | colorLog("[INFO] BW: No connection, wait for it.\n") 179 | cmd := <-connectChannel 180 | if "QUIT" == cmd.Action { 181 | return 182 | } 183 | } 184 | 185 | for { 186 | next, ok := <-toBrowserChannel 187 | if !ok { 188 | colorLog("[WARN] BW: Send channel was closed.\n") 189 | break 190 | } 191 | 192 | if "QUIT" == next.Action { 193 | break 194 | } 195 | 196 | if currentWebsocket == nil { 197 | colorLog("[INFO] BW: No connection, wait for it.\n") 198 | cmd := <-connectChannel 199 | if "QUIT" == cmd.Action { 200 | break 201 | } 202 | } 203 | websocket.JSON.Send(currentWebsocket, &next) 204 | colorLog("[SUCC] BW: Sent %v.\n", next) 205 | } 206 | 207 | colorLog("[WARN] BW: Exit send loop.\n") 208 | } 209 | -------------------------------------------------------------------------------- /monitor.go: -------------------------------------------------------------------------------- 1 | // Copyright 2013 Unknown 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"): you may 4 | // not use this file except in compliance with the License. You may obtain 5 | // a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 11 | // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 12 | // License for the specific language governing permissions and limitations 13 | // under the License. 14 | 15 | package beewatch 16 | 17 | import ( 18 | //"bytes" 19 | "fmt" 20 | "reflect" 21 | "sync" 22 | ) 23 | 24 | type watchValue struct { 25 | Value reflect.Value 26 | Note string 27 | } 28 | 29 | var ( 30 | watchLock *sync.RWMutex 31 | watchMap map[string]*watchValue 32 | ) 33 | 34 | // AddWatchVars adds variables to be watched, 35 | // so that when the program calls 'Break()' 36 | // these variables' infomation will be showed on monitor panel. 37 | // The parameter 'nameValuePairs' must be even sized. 38 | // Note that you have to pass pointers in order to have correct results. 39 | func AddWatchVars(nameValuePairs ...interface{}) { 40 | // if !App.WatchEnabled { 41 | // return 42 | // } 43 | 44 | if watchLock == nil { 45 | watchLock = new(sync.RWMutex) 46 | } 47 | watchLock.Lock() 48 | defer watchLock.Unlock() 49 | 50 | if watchMap == nil { 51 | watchMap = make(map[string]*watchValue) 52 | } 53 | 54 | l := len(nameValuePairs) 55 | if l%2 != 0 { 56 | colorLog("[ERRO] cannot add watch variables without even sized.\n") 57 | return 58 | } 59 | 60 | for i := 0; i < l; i += 2 { 61 | k := nameValuePairs[i] 62 | v := nameValuePairs[i+1] 63 | rv := reflect.ValueOf(v) 64 | 65 | if rv.Kind() != reflect.Ptr { 66 | colorLog("[WARN] cannot watch variable( %s ) because [ %s ]\n", 67 | k, "it's not passed by pointer") 68 | continue 69 | } 70 | 71 | watchMap[fmt.Sprint(k)] = &watchValue{ 72 | Value: rv, 73 | Note: "", 74 | } 75 | } 76 | } 77 | 78 | // func pirntWatchValues() { 79 | // buf := new(bytes.Buffer) 80 | // for k, v := range watchMap { 81 | // printWatchValue(buf, k, "", v) 82 | // } 83 | // fmt.Print(buf.String()) 84 | // } 85 | 86 | // func printWatchValue(buf *bytes.Buffer, name, prefix string, wv *watchValue) { 87 | // switch wv.Type { 88 | // case reflect.String: 89 | // fmt.Fprintf(buf, "%s:string:%s\"%v\"\n", name, prefix, wv.Value) 90 | // case reflect.Ptr: 91 | // if wv.Value.IsNil() { 92 | // fmt.Fprintf(buf, "%s:pointer:null\n", name) 93 | // return 94 | // } 95 | 96 | // printWatchValue(buf, name, "&", &watchValue{ 97 | // Type: wv.Value.Elem().Kind(), 98 | // Value: wv.Value.Elem(), 99 | // }) 100 | // } 101 | // } 102 | 103 | type watchVar struct { 104 | Kind, Value, Note string 105 | } 106 | 107 | func formatWatchVars() map[string]*watchVar { 108 | if watchLock == nil { 109 | return nil 110 | } 111 | 112 | watchLock.RLock() 113 | defer watchLock.RUnlock() 114 | 115 | watchVars := make(map[string]*watchVar) 116 | for k, v := range watchMap { 117 | watchVars[k] = &watchVar{ 118 | Kind: fmt.Sprint(v.Value.Elem().Kind()), 119 | Value: reflectToStr(v.Value.Elem()), 120 | Note: v.Note, 121 | } 122 | } 123 | return watchVars 124 | } 125 | 126 | func reflectToStr(rv reflect.Value) string { 127 | k := rv.Kind() 128 | switch k { 129 | case reflect.Bool: 130 | return fmt.Sprintf("%v", rv.Bool()) 131 | case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: 132 | return fmt.Sprintf("%v", rv.Int()) 133 | case reflect.Uint8, reflect.Uint16, reflect.Uint, reflect.Uint32, reflect.Uint64: 134 | return fmt.Sprintf("%v", rv.Uint()) 135 | case reflect.Float32, reflect.Float64: 136 | return fmt.Sprintf("%v", rv.Float()) 137 | case reflect.Complex64, reflect.Complex128: 138 | return fmt.Sprintf("%v", rv.Complex()) 139 | case reflect.String: 140 | return fmt.Sprintf("\"%s\"", rv.String()) 141 | case reflect.Invalid: 142 | return "Invalid" 143 | default: 144 | return "Unknown" 145 | } 146 | } 147 | -------------------------------------------------------------------------------- /static/css/normalize.css: -------------------------------------------------------------------------------- 1 | /* normalize.css v2.1.1 | MIT License | git.io/normalize */article,aside,details,figcaption,figure,footer,header,hgroup,main,nav,section,summary{display:block}audio,canvas,video{display:inline-block}audio:not([controls]){display:none;height:0}[hidden]{display:none}html{background:#fff;color:#000;font-family:sans-serif;-ms-text-size-adjust:100%;-webkit-text-size-adjust:100%}body{margin:0}a:focus{outline:thin dotted}a:active,a:hover{outline:0}h1{font-size:2em;margin:0.67em 0}abbr[title]{border-bottom:1px dotted}b,strong{font-weight:bold}dfn{font-style:italic}hr{-moz-box-sizing:content-box;box-sizing:content-box;height:0}mark{background:#ff0;color:#000}code,kbd,pre,samp{font-family:monospace, serif;font-size:1em}pre{white-space:pre;white-space:pre-wrap;word-wrap:break-word}q{quotes:"\201C" "\201D" "\2018" "\2019"}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sup{top:-0.5em}sub{bottom:-0.25em}img{border:0}svg:not(:root){overflow:hidden}figure{margin:0}fieldset{border:1px solid #c0c0c0;margin:0 2px;padding:0.35em 0.625em 0.75em}legend{border:0;padding:0}button,input,select,textarea{font-family:inherit;font-size:100%;margin:0}button,input{line-height:normal}button,select{text-transform:none}button,html input[type="button"],input[type="reset"],input[type="submit"]{-webkit-appearance:button;cursor:pointer}button[disabled]{cursor:default}input[type="checkbox"],input[type="radio"]{box-sizing:border-box;padding:0}input[type="search"]{-webkit-appearance:textfield;-moz-box-sizing:content-box;-webkit-box-sizing:content-box;box-sizing:content-box}input[type="search"]::-webkit-search-cancel-button,input[type="search"]::-webkit-search-decoration{-webkit-appearance:none}button::-moz-focus-inner,input::-moz-focus-inner{border:0;padding:0}textarea{overflow:auto;vertical-align:top}table{border-collapse:collapse;border-spacing:0} 2 | 3 | .version { 4 | font-size: 50%; 5 | color: rgb(204, 154, 204); 6 | } 7 | 8 | .mono { 9 | font-family: "Lucida Console", Monaco, monospace; 10 | font-size: 13px; 11 | } 12 | 13 | .break { 14 | background-color: #375EAB; 15 | color: #FFF; 16 | } 17 | 18 | #gosource { 19 | background: #FFD; 20 | white-space: pre; 21 | } 22 | -------------------------------------------------------------------------------- /static/fonts/fontawesome/FontAwesome.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/beego/beewatch/ac58d834487e7ddda1e154ecc5eee66e9e63726f/static/fonts/fontawesome/FontAwesome.otf -------------------------------------------------------------------------------- /static/fonts/fontawesome/fontawesome-webfont.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/beego/beewatch/ac58d834487e7ddda1e154ecc5eee66e9e63726f/static/fonts/fontawesome/fontawesome-webfont.eot -------------------------------------------------------------------------------- /static/fonts/fontawesome/fontawesome-webfont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/beego/beewatch/ac58d834487e7ddda1e154ecc5eee66e9e63726f/static/fonts/fontawesome/fontawesome-webfont.ttf -------------------------------------------------------------------------------- /static/fonts/fontawesome/fontawesome-webfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/beego/beewatch/ac58d834487e7ddda1e154ecc5eee66e9e63726f/static/fonts/fontawesome/fontawesome-webfont.woff -------------------------------------------------------------------------------- /static/fonts/meteocons-webfont.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/beego/beewatch/ac58d834487e7ddda1e154ecc5eee66e9e63726f/static/fonts/meteocons-webfont.eot -------------------------------------------------------------------------------- /static/fonts/meteocons-webfont.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | This is a custom SVG webfont generated by Font Squirrel. 6 | Designer : Alessio Atzeni 7 | Foundry : Alessio Atzeni 8 | Foundry URL : http://www.alessioatzeni.com/meteocons/ 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | -------------------------------------------------------------------------------- /static/fonts/meteocons-webfont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/beego/beewatch/ac58d834487e7ddda1e154ecc5eee66e9e63726f/static/fonts/meteocons-webfont.ttf -------------------------------------------------------------------------------- /static/fonts/meteocons-webfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/beego/beewatch/ac58d834487e7ddda1e154ecc5eee66e9e63726f/static/fonts/meteocons-webfont.woff -------------------------------------------------------------------------------- /static/img/icheck/aero.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/beego/beewatch/ac58d834487e7ddda1e154ecc5eee66e9e63726f/static/img/icheck/aero.png -------------------------------------------------------------------------------- /static/img/icheck/aero@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/beego/beewatch/ac58d834487e7ddda1e154ecc5eee66e9e63726f/static/img/icheck/aero@2x.png -------------------------------------------------------------------------------- /static/img/icheck/blue.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/beego/beewatch/ac58d834487e7ddda1e154ecc5eee66e9e63726f/static/img/icheck/blue.png -------------------------------------------------------------------------------- /static/img/icheck/blue@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/beego/beewatch/ac58d834487e7ddda1e154ecc5eee66e9e63726f/static/img/icheck/blue@2x.png -------------------------------------------------------------------------------- /static/img/icheck/flat.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/beego/beewatch/ac58d834487e7ddda1e154ecc5eee66e9e63726f/static/img/icheck/flat.png -------------------------------------------------------------------------------- /static/img/icheck/flat@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/beego/beewatch/ac58d834487e7ddda1e154ecc5eee66e9e63726f/static/img/icheck/flat@2x.png -------------------------------------------------------------------------------- /static/img/icheck/green.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/beego/beewatch/ac58d834487e7ddda1e154ecc5eee66e9e63726f/static/img/icheck/green.png -------------------------------------------------------------------------------- /static/img/icheck/green@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/beego/beewatch/ac58d834487e7ddda1e154ecc5eee66e9e63726f/static/img/icheck/green@2x.png -------------------------------------------------------------------------------- /static/img/icheck/grey.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/beego/beewatch/ac58d834487e7ddda1e154ecc5eee66e9e63726f/static/img/icheck/grey.png -------------------------------------------------------------------------------- /static/img/icheck/grey@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/beego/beewatch/ac58d834487e7ddda1e154ecc5eee66e9e63726f/static/img/icheck/grey@2x.png -------------------------------------------------------------------------------- /static/img/icheck/orange.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/beego/beewatch/ac58d834487e7ddda1e154ecc5eee66e9e63726f/static/img/icheck/orange.png -------------------------------------------------------------------------------- /static/img/icheck/orange@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/beego/beewatch/ac58d834487e7ddda1e154ecc5eee66e9e63726f/static/img/icheck/orange@2x.png -------------------------------------------------------------------------------- /static/img/icheck/pink.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/beego/beewatch/ac58d834487e7ddda1e154ecc5eee66e9e63726f/static/img/icheck/pink.png -------------------------------------------------------------------------------- /static/img/icheck/pink@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/beego/beewatch/ac58d834487e7ddda1e154ecc5eee66e9e63726f/static/img/icheck/pink@2x.png -------------------------------------------------------------------------------- /static/img/icheck/purple.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/beego/beewatch/ac58d834487e7ddda1e154ecc5eee66e9e63726f/static/img/icheck/purple.png -------------------------------------------------------------------------------- /static/img/icheck/purple@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/beego/beewatch/ac58d834487e7ddda1e154ecc5eee66e9e63726f/static/img/icheck/purple@2x.png -------------------------------------------------------------------------------- /static/img/icheck/red.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/beego/beewatch/ac58d834487e7ddda1e154ecc5eee66e9e63726f/static/img/icheck/red.png -------------------------------------------------------------------------------- /static/img/icheck/red@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/beego/beewatch/ac58d834487e7ddda1e154ecc5eee66e9e63726f/static/img/icheck/red@2x.png -------------------------------------------------------------------------------- /static/img/icheck/yellow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/beego/beewatch/ac58d834487e7ddda1e154ecc5eee66e9e63726f/static/img/icheck/yellow.png -------------------------------------------------------------------------------- /static/img/icheck/yellow@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/beego/beewatch/ac58d834487e7ddda1e154ecc5eee66e9e63726f/static/img/icheck/yellow@2x.png -------------------------------------------------------------------------------- /static/img/icons/customize-icon150.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/beego/beewatch/ac58d834487e7ddda1e154ecc5eee66e9e63726f/static/img/icons/customize-icon150.png -------------------------------------------------------------------------------- /static/img/icons/flexible-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/beego/beewatch/ac58d834487e7ddda1e154ecc5eee66e9e63726f/static/img/icons/flexible-icon.png -------------------------------------------------------------------------------- /static/img/icons/github-128-black.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/beego/beewatch/ac58d834487e7ddda1e154ecc5eee66e9e63726f/static/img/icons/github-128-black.png -------------------------------------------------------------------------------- /static/img/icons/iphone-icon150.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/beego/beewatch/ac58d834487e7ddda1e154ecc5eee66e9e63726f/static/img/icons/iphone-icon150.png -------------------------------------------------------------------------------- /static/img/icons/lock-icon128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/beego/beewatch/ac58d834487e7ddda1e154ecc5eee66e9e63726f/static/img/icons/lock-icon128.png -------------------------------------------------------------------------------- /static/img/icons/rocket-icon128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/beego/beewatch/ac58d834487e7ddda1e154ecc5eee66e9e63726f/static/img/icons/rocket-icon128.png -------------------------------------------------------------------------------- /static/img/icons/rocket-icon150.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/beego/beewatch/ac58d834487e7ddda1e154ecc5eee66e9e63726f/static/img/icons/rocket-icon150.png -------------------------------------------------------------------------------- /static/img/icons/screen-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/beego/beewatch/ac58d834487e7ddda1e154ecc5eee66e9e63726f/static/img/icons/screen-icon.png -------------------------------------------------------------------------------- /static/img/icons/screens-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/beego/beewatch/ac58d834487e7ddda1e154ecc5eee66e9e63726f/static/img/icons/screens-icon.png -------------------------------------------------------------------------------- /static/img/icons/screens2-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/beego/beewatch/ac58d834487e7ddda1e154ecc5eee66e9e63726f/static/img/icons/screens2-icon.png -------------------------------------------------------------------------------- /static/img/next.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/beego/beewatch/ac58d834487e7ddda1e154ecc5eee66e9e63726f/static/img/next.png -------------------------------------------------------------------------------- /static/img/previous.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/beego/beewatch/ac58d834487e7ddda1e154ecc5eee66e9e63726f/static/img/previous.png -------------------------------------------------------------------------------- /static/img/themes.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/beego/beewatch/ac58d834487e7ddda1e154ecc5eee66e9e63726f/static/img/themes.gif -------------------------------------------------------------------------------- /static/img/toggle.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/beego/beewatch/ac58d834487e7ddda1e154ecc5eee66e9e63726f/static/img/toggle.png -------------------------------------------------------------------------------- /static/js/beewatch.js: -------------------------------------------------------------------------------- 1 | var wsUri = "ws://" + window.location.hostname + ":" + window.location.port + "/beewatch"; 2 | var output; 3 | var ctrlPanel; 4 | var varMonitor; 5 | var varTable; 6 | var connected = false; 7 | var suspended = false; 8 | var websocket = new WebSocket(wsUri); 9 | 10 | function init() { 11 | output = document.getElementById("output"); 12 | ctrlPanel = document.getElementById("control_panel"); 13 | ctrlPanel.style.width = (document.body.clientWidth - 820) + "px"; 14 | varMonitor = document.getElementById("variable_monitor"); 15 | output.style.minHeight = (window.innerHeight - 200) + "px"; 16 | varMonitor.style.height = (window.innerHeight - 220) + "px"; 17 | varTable = document.getElementById("var_table"); 18 | 19 | setupWebSocket(); 20 | } 21 | 22 | function setupWebSocket() { 23 | websocket.onopen = function (evt) { 24 | onOpen(evt) 25 | }; 26 | websocket.onclose = function (evt) { 27 | onClose(evt) 28 | }; 29 | websocket.onmessage = function (evt) { 30 | onMessage(evt) 31 | }; 32 | websocket.onerror = function (evt) { 33 | onError(evt) 34 | }; 35 | } 36 | 37 | function onOpen(evt) { 38 | connected = true; 39 | //document.getElementById("disconnect").className = "buttonEnabled"; 40 | writeToScreen("Connection has established.", "label label-funky", "INFO", ""); 41 | sendConnected(); 42 | } 43 | 44 | function onClose(evt) { 45 | //handleDisconnected(); 46 | } 47 | 48 | function handleDisconnected() { 49 | //connected = false; 50 | //document.getElementById("resume").className = "buttonDisabled"; 51 | //document.getElementById("disconnect").className = "buttonDisabled"; 52 | //writeToScreen("Disconnected.", "label label-funky", "INFO", ""); 53 | } 54 | 55 | function onMessage(evt) { 56 | try { 57 | var cmd = JSON.parse(evt.data); 58 | } catch (e) { 59 | writeToScreen("Failed to read valid JSON", "label label-funky", "ERRO", e.message.data); 60 | return; 61 | } 62 | 63 | switch (cmd.Action) { 64 | case "PRINT": 65 | case "DISPLAY": 66 | writeToScreen(getTitle(cmd), getLevelCls(cmd.Level), cmd.Level, watchParametersToHtml(cmd.Parameters)); 67 | sendResume(); 68 | return; 69 | case "DONE": 70 | actionDisconnect(true); 71 | return; 72 | case "BREAK": 73 | var title; 74 | if (cmd.Parameters["go.SKIP_SUSPEND"] == "true") { 75 | title = "program suspend skiped <->"; 76 | } else { 77 | suspended = true; 78 | title = "program suspended -->"; 79 | } 80 | var logdiv = writeToScreen(title, getLevelCls(cmd.Level), cmd.Level, ""); 81 | 82 | addStack(logdiv, cmd); 83 | handleSourceUpdate(logdiv, cmd); 84 | updateVarInfo(cmd); 85 | return; 86 | } 87 | } 88 | 89 | function getTitle(cmd) { 90 | var filePath = cmd.Parameters["go.file"]; 91 | var i = filePath.lastIndexOf("/") + 1; 92 | return filePath.substring(i, filePath.length) + ":" + cmd.Parameters["go.line"]; 93 | } 94 | 95 | function onError(evt) { 96 | writeToScreen("WebScoket error", "label label-funky", "ERRO", evt.data); 97 | } 98 | 99 | function writeToScreen(title, cls, level, msg) { 100 | var logdiv = document.createElement("div"); 101 | addTime(logdiv, cls, level); 102 | addTitle(logdiv, title); 103 | addMessage(logdiv, msg, level); 104 | output.appendChild(logdiv); 105 | logdiv.scrollIntoView(); 106 | return logdiv; 107 | } 108 | 109 | function addTime(logdiv, cls, level) { 110 | var stamp = document.createElement("span"); 111 | stamp.innerHTML = timeHHMMSS() + " " + level; 112 | stamp.className = cls; 113 | logdiv.appendChild(stamp); 114 | } 115 | 116 | function timeHHMMSS() { 117 | return new Date().toTimeString().replace(/.*(\d{2}:\d{2}:\d{2}).*/, "$1"); 118 | } 119 | 120 | function addTitle(logdiv, title) { 121 | var name = document.createElement("span"); 122 | name.innerHTML = " " + title; 123 | logdiv.appendChild(name); 124 | } 125 | 126 | function addMessage(logdiv, msg, level) { 127 | var txt = document.createElement("span"); 128 | 129 | if (msg.substr(0, 1) == "[") { 130 | // Debugger messages. 131 | txt.className = getMsgClass(msg.substr(1, 4)); 132 | } else { 133 | // App messages. 134 | txt.className = getMsgClass(level); 135 | } 136 | 137 | txt.innerHTML = "      " + msg; 138 | logdiv.appendChild(txt); 139 | } 140 | 141 | function addStack(logdiv, cmd) { 142 | var stack; 143 | if (cmd.Parameters["go.PRINT_STACK"] != "true") { 144 | stack = "'print_stack' disenabled."; 145 | } else { 146 | stack = cmd.Parameters["go.stack"]; 147 | } 148 | if (stack != null && stack.length > 0) { 149 | addNonEmptyStackTo(stack, logdiv); 150 | } 151 | } 152 | 153 | function addNonEmptyStackTo(stack, logdiv) { 154 | var toggle = document.createElement("a"); 155 | toggle.className = "label label-primary"; 156 | toggle.onclick = function () { 157 | toggleStack(toggle); 158 | }; 159 | toggle.innerHTML = "Stack & Source ▶"; 160 | logdiv.appendChild(toggle); 161 | 162 | var panel = document.createElement("div"); 163 | panel.style.display = "none"; 164 | panel.id = "panel"; 165 | var stk = document.createElement("div"); 166 | var lines = document.createElement("pre"); 167 | lines.innerHTML = stack; 168 | stk.appendChild(lines); 169 | panel.appendChild(stk); 170 | logdiv.appendChild(panel); 171 | } 172 | 173 | function toggleStack(link) { 174 | var stack = link.nextSibling; 175 | if (stack.style.display == "none") { 176 | link.innerHTML = "Stack & Source ▼"; 177 | stack.style.display = "block"; 178 | } else { 179 | link.innerHTML = "Stack & Source ▶"; 180 | stack.style.display = "none"; 181 | } 182 | } 183 | 184 | function getMsgClass(level) { 185 | switch (level) { 186 | case "INIT", "INFO": 187 | return "text-success"; 188 | } 189 | } 190 | 191 | function getLevelCls(level) { 192 | switch (level) { 193 | case "TRACE": 194 | return "label"; 195 | case "INFO": 196 | return "label label-info"; 197 | case "CRITICAL": 198 | return "label label-danger"; 199 | default: 200 | return "lable"; 201 | } 202 | } 203 | 204 | function watchParametersToHtml(parameters) { 205 | var line = ""; 206 | var multiline = false; 207 | for (var prop in parameters) { 208 | if (prop.slice(0, 3) != "go.") { 209 | if (multiline) { 210 | line = line + ", "; 211 | } 212 | line = line + prop + " => " + parameters[prop]; 213 | multiline = true; 214 | } 215 | } 216 | return line 217 | } 218 | 219 | function handleSourceUpdate(logdiv, cmd) { 220 | loadSource(logdiv, cmd.Parameters["go.file"], cmd.Parameters["go.line"]); 221 | } 222 | 223 | function loadSource(logdiv, fileName, nr) { 224 | $("#gofile").html(shortenFileName(fileName)); 225 | $("#source_panel").show(); 226 | $.ajax({ 227 | url:"/gosource?file=" + fileName 228 | } 229 | ). 230 | done( 231 | function (responseText, status, xhr) { 232 | handleSourceLoaded(logdiv, fileName, responseText, parseInt(nr)); 233 | } 234 | ); 235 | } 236 | 237 | function handleSourceLoaded(logdiv, fileName, responseText, line) { 238 | var srcPanel = document.createElement("div"); 239 | var gosrc = document.createElement("div") 240 | gosrc.className = "mono"; 241 | gosrc.id = "gosource"; 242 | 243 | if (responseText.indexOf("\n") > -1) { 244 | var breakElm; 245 | 246 | var elm = document.createElement("div"); 247 | elm.innerHTML = shortenFileName(fileName); 248 | gosrc.appendChild(elm); 249 | 250 | // Insert line numbers 251 | var arr = responseText.split('\n'); 252 | for (var i = 0; i < arr.length; i++) { 253 | if ((i + 1 <= line - 10)) { 254 | continue; 255 | } else if (i + 1 >= line + 10) { 256 | break; 257 | } 258 | 259 | var nr = i + 1 260 | var buf = space_padded(nr) + arr[i]; 261 | var elm = document.createElement("div"); 262 | elm.innerHTML = buf; 263 | if (line == nr) { 264 | elm.className = "break"; 265 | breakElm = elm 266 | } 267 | gosrc.appendChild(elm); 268 | } 269 | } else { 270 | var elm = document.createElement("div"); 271 | elm.innerHTML = responseText; 272 | gosrc.appendChild(elm); 273 | } 274 | 275 | srcPanel.appendChild(gosrc); 276 | logdiv.childNodes[4].appendChild(srcPanel); 277 | } 278 | 279 | function space_padded(i) { 280 | var buf = "" + i 281 | if (i < 1000) { 282 | buf += " " 283 | } 284 | if (i < 100) { 285 | buf += " " 286 | } 287 | if (i < 10) { 288 | buf += " " 289 | } 290 | return buf 291 | } 292 | 293 | function shortenFileName(fileName) { 294 | return fileName.length > 60 ? "..." + fileName.substring(fileName.length - 60) : fileName; 295 | } 296 | 297 | function updateVarInfo(cmd) { 298 | for (var wv in cmd.WatchVars) { 299 | var tr = findWatchVar(wv); 300 | if (tr == null) { 301 | tr = document.createElement("tr"); 302 | var td = document.createElement("td"); 303 | td.innerHTML = wv; 304 | tr.appendChild(td); 305 | 306 | td = document.createElement("td"); 307 | td.innerHTML = cmd.WatchVars[wv].Kind; 308 | tr.appendChild(td); 309 | 310 | td = document.createElement("td"); 311 | td.innerHTML = cmd.WatchVars[wv].Value; 312 | tr.appendChild(td); 313 | 314 | varTable.appendChild(tr); 315 | } else { 316 | var v = cmd.WatchVars[wv].Value; 317 | if (v != tr.childNodes[2].innerHTML) { 318 | tr.childNodes[2].innerHTML = v; 319 | tr.className="text-error"; 320 | } 321 | } 322 | } 323 | } 324 | 325 | function findWatchVar(name) { 326 | var trs = varTable.childNodes; 327 | for (var i = 0; i < trs.length; i++) { 328 | if (name == trs[i].childNodes[0].innerHTML) { 329 | return trs[i]; 330 | } 331 | } 332 | return null; 333 | } 334 | 335 | function actionResume() { 336 | if (!connected) return; 337 | if (!suspended) return; 338 | suspended = false; 339 | //document.getElementById("resume").className = "buttonDisabled"; 340 | writeToScreen("<-- resume program.", "label label-info", "INFO", ""); 341 | sendResume(); 342 | } 343 | 344 | function actionDisconnect(passive) { 345 | if (!connected) return; 346 | connected = false; 347 | //document.getElementById("disconnect").className = "buttonDisabled"; 348 | sendQuit(passive); 349 | writeToScreen("Disconnected.", "label label-funky", "INFO", ""); 350 | websocket.close(); // seems not to trigger close on Go-side ; so handleDisconnected cannot be used here. 351 | } 352 | 353 | function sendConnected() { 354 | doSend('{"Action":"CONNECTED"}'); 355 | } 356 | 357 | function sendResume() { 358 | doSend('{"Action":"RESUME"}'); 359 | } 360 | 361 | function sendQuit(passive) { 362 | if (passive) { 363 | doSend('{"Action":"QUIT","Parameters":{"PASSIVE":"1"}}'); 364 | } else { 365 | doSend('{"Action":"QUIT","Parameters":{"PASSIVE":"0"}}'); 366 | } 367 | } 368 | 369 | function doSend(message) { 370 | // console.log("[hopwatch] send: " + message); 371 | websocket.send(message); 372 | } 373 | 374 | function handleKeyDown(event) { 375 | switch (event.keyCode) { 376 | case 119: // F8. 377 | actionResume(); 378 | case 120: // F9. 379 | } 380 | } 381 | 382 | function resizeWindow() { 383 | output.style.minHeight = (window.innerHeight - 200) + "px"; 384 | varMonitor.style.height = (window.innerHeight - 220) + "px"; 385 | } 386 | 387 | window.addEventListener("load", init, false); 388 | window.addEventListener("keydown", handleKeyDown, true); 389 | window.addEventListener("resize", resizeWindow, false) -------------------------------------------------------------------------------- /static/js/furatto.min.js: -------------------------------------------------------------------------------- 1 | !function(e){"use strict";function r(){e(".dropdown-backdrop").remove(),e(t).each(function(){i(e(this)).removeClass("open")})}function i(t){var n=t.attr("data-target"),r;n||(n=t.attr("href"),n=n&&/#/.test(n)&&n.replace(/.*(?=#[^\s]*$)/,"")),r=n&&e(n);if(!r||!r.length)r=t.parent();return r}var t="[data-toggle=dropdown]",n=function(t){var n=e(t).on("click.dropdown.data-api",this.toggle);e("html").on("click.dropdown.data-api",function(){n.parent().removeClass("open")})};n.prototype={constructor:n,toggle:function(t){var n=e(this),s,o;if(n.is(".disabled, :disabled"))return;return s=i(n),o=s.hasClass("open"),r(),o||("ontouchstart"in document.documentElement&&e('