├── VERSION ├── agent ├── config.go ├── context.go ├── agent_test.go ├── saver.go ├── rpc_client_test.go ├── msg_queue.go ├── options.go ├── monitor.go ├── loader.go ├── http.go ├── rpc_client.go └── agent.go ├── dashboard ├── template │ ├── public_html │ │ ├── system.html │ │ ├── common │ │ │ ├── layout.html │ │ │ ├── footer.html │ │ │ ├── header.html │ │ │ └── left.html │ │ ├── index.html │ │ ├── var.html │ │ ├── heartbeat.html │ │ └── agent.html │ └── asset │ │ ├── css │ │ ├── .DS_Store │ │ ├── sb-admin-rtl.css │ │ ├── morris.css │ │ └── sb-admin.css │ │ ├── js │ │ ├── .DS_Store │ │ ├── jquery.flot.resize.js │ │ ├── jquery.flot.tooltip.min.js │ │ ├── morris-data.js │ │ └── excanvas.min.js │ │ └── font-awesome │ │ ├── fonts │ │ ├── FontAwesome.otf │ │ ├── fontawesome-webfont.eot │ │ ├── fontawesome-webfont.ttf │ │ └── fontawesome-webfont.woff │ │ ├── less │ │ ├── fixed-width.less │ │ ├── bordered-pulled.less │ │ ├── larger.less │ │ ├── core.less │ │ ├── list.less │ │ ├── font-awesome.less │ │ ├── stacked.less │ │ ├── rotated-flipped.less │ │ ├── spinning.less │ │ ├── path.less │ │ ├── mixins.less │ │ └── variables.less │ │ └── scss │ │ ├── _fixed-width.scss │ │ ├── _bordered-pulled.scss │ │ ├── _larger.scss │ │ ├── _core.scss │ │ ├── _list.scss │ │ ├── font-awesome.scss │ │ ├── _stacked.scss │ │ ├── _spinning.scss │ │ ├── _path.scss │ │ ├── _rotated-flipped.scss │ │ ├── _mixins.scss │ │ └── _variables.scss ├── context.go ├── options.go ├── dashboard_test.go ├── dashboard.go └── http.go ├── registry ├── frontend │ ├── leveldb.go │ ├── empty.go │ ├── file_test.go │ ├── shm.go │ └── file.go ├── .DS_Store ├── frontend.go ├── backend.go └── backend │ ├── etcd_test.go │ └── etcd.go ├── cmd ├── .DS_Store ├── monitor │ ├── monitor.toml │ └── main.go ├── dashboard │ ├── dashboard.toml │ └── main.go └── agent │ ├── agent.toml │ └── main.go ├── sdk ├── .DS_Store ├── goconfd_test.go └── goconfd.go ├── store ├── .DS_Store ├── store.go ├── db │ ├── adapter.go │ └── mongo │ │ ├── mongo_test.go │ │ └── mongo.go └── types │ └── types.go ├── documents └── img0.png ├── monitor ├── .DS_Store ├── context.go ├── options.go ├── rpc.go ├── monitor.go └── service.go ├── libs ├── util │ ├── host.go │ ├── wait_group_wrapper.go │ ├── string_array.go │ ├── key_test.go │ ├── file_test.go │ ├── key.go │ └── file.go ├── version │ └── binary.go ├── shm │ ├── common_test.go │ ├── shm_test.go │ ├── common.go │ └── shm.go ├── reflect │ ├── tag.go │ └── tag_test.go ├── kv │ ├── json.go │ ├── kv.go │ ├── kv_test.go │ └── php.go ├── node │ └── node.go ├── client │ ├── selector_test.go │ ├── selector.go │ └── rpc.go ├── try │ └── try.go ├── protocol │ └── protocol.go ├── net2 │ ├── net_test.go │ └── net.go └── ringhash │ └── ringhash.go ├── run.sh ├── .vscode └── launch.json ├── Makefile └── README.md /VERSION: -------------------------------------------------------------------------------- 1 | v0.1.1 -------------------------------------------------------------------------------- /agent/config.go: -------------------------------------------------------------------------------- 1 | package agent 2 | -------------------------------------------------------------------------------- /dashboard/template/public_html/system.html: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /registry/frontend/leveldb.go: -------------------------------------------------------------------------------- 1 | package frontend 2 | -------------------------------------------------------------------------------- /cmd/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Leon2012/goconfd/HEAD/cmd/.DS_Store -------------------------------------------------------------------------------- /sdk/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Leon2012/goconfd/HEAD/sdk/.DS_Store -------------------------------------------------------------------------------- /store/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Leon2012/goconfd/HEAD/store/.DS_Store -------------------------------------------------------------------------------- /documents/img0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Leon2012/goconfd/HEAD/documents/img0.png -------------------------------------------------------------------------------- /monitor/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Leon2012/goconfd/HEAD/monitor/.DS_Store -------------------------------------------------------------------------------- /registry/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Leon2012/goconfd/HEAD/registry/.DS_Store -------------------------------------------------------------------------------- /monitor/context.go: -------------------------------------------------------------------------------- 1 | package monitor 2 | 3 | type Context struct { 4 | Monitor *Monitor 5 | } 6 | -------------------------------------------------------------------------------- /dashboard/context.go: -------------------------------------------------------------------------------- 1 | package dashboard 2 | 3 | type Context struct { 4 | Dashboard *Dashboard 5 | } 6 | -------------------------------------------------------------------------------- /agent/context.go: -------------------------------------------------------------------------------- 1 | package agent 2 | 3 | import () 4 | 5 | type Context struct { 6 | Agent *Agent 7 | } 8 | -------------------------------------------------------------------------------- /dashboard/template/asset/css/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Leon2012/goconfd/HEAD/dashboard/template/asset/css/.DS_Store -------------------------------------------------------------------------------- /dashboard/template/asset/js/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Leon2012/goconfd/HEAD/dashboard/template/asset/js/.DS_Store -------------------------------------------------------------------------------- /cmd/monitor/monitor.toml: -------------------------------------------------------------------------------- 1 | db_url="127.0.0.1:27017" 2 | db_name="goconfd" 3 | hosts = "127.0.0.1:2379" #etcd hosts 4 | heartbeat_interval=30 -------------------------------------------------------------------------------- /libs/util/host.go: -------------------------------------------------------------------------------- 1 | package util 2 | 3 | import ( 4 | "os" 5 | ) 6 | 7 | func GetHostName() (string, error) { 8 | return os.Hostname() 9 | } 10 | -------------------------------------------------------------------------------- /dashboard/template/asset/font-awesome/fonts/FontAwesome.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Leon2012/goconfd/HEAD/dashboard/template/asset/font-awesome/fonts/FontAwesome.otf -------------------------------------------------------------------------------- /dashboard/template/asset/css/sb-admin-rtl.css: -------------------------------------------------------------------------------- 1 | 2 | @media (min-width: 768px){ 3 | #wrapper {padding-right: 225px; padding-left: 0;} 4 | .side-nav{right: 0;left: auto;} 5 | } -------------------------------------------------------------------------------- /cmd/dashboard/dashboard.toml: -------------------------------------------------------------------------------- 1 | etcd_hosts = "localhost:2379" 2 | db_url="127.0.0.1:27017" 3 | db_name="goconfd" 4 | template_path="/Users/pengleon/gopath/goconfd/dashboard/template/" 5 | -------------------------------------------------------------------------------- /dashboard/template/asset/font-awesome/fonts/fontawesome-webfont.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Leon2012/goconfd/HEAD/dashboard/template/asset/font-awesome/fonts/fontawesome-webfont.eot -------------------------------------------------------------------------------- /dashboard/template/asset/font-awesome/fonts/fontawesome-webfont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Leon2012/goconfd/HEAD/dashboard/template/asset/font-awesome/fonts/fontawesome-webfont.ttf -------------------------------------------------------------------------------- /dashboard/template/asset/font-awesome/fonts/fontawesome-webfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Leon2012/goconfd/HEAD/dashboard/template/asset/font-awesome/fonts/fontawesome-webfont.woff -------------------------------------------------------------------------------- /dashboard/template/asset/font-awesome/less/fixed-width.less: -------------------------------------------------------------------------------- 1 | // Fixed Width Icons 2 | // ------------------------- 3 | .@{fa-css-prefix}-fw { 4 | width: (18em / 14); 5 | text-align: center; 6 | } 7 | -------------------------------------------------------------------------------- /dashboard/template/asset/font-awesome/scss/_fixed-width.scss: -------------------------------------------------------------------------------- 1 | // Fixed Width Icons 2 | // ------------------------- 3 | .#{$fa-css-prefix}-fw { 4 | width: (18em / 14); 5 | text-align: center; 6 | } 7 | -------------------------------------------------------------------------------- /run.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e 3 | rm -rf ./nohup.out 4 | nohup ./build/monitor --config ./build/monitor.toml & 5 | nohup ./build/agent --config ./build/agent.toml & 6 | nohup ./build/dashboard --config ./build/dashboard.toml & -------------------------------------------------------------------------------- /dashboard/template/public_html/common/layout.html: -------------------------------------------------------------------------------- 1 | {{define "layout"}} 2 | {{template "header"}} 3 | {{template "left"}} 4 |
5 | {{template "content" .}} 6 |
7 | 8 | {{template "footer"}} 9 | {{end}} -------------------------------------------------------------------------------- /libs/version/binary.go: -------------------------------------------------------------------------------- 1 | package version 2 | 3 | import ( 4 | "fmt" 5 | "runtime" 6 | ) 7 | 8 | const Binary = "0.0.1" 9 | 10 | func String(app string) string { 11 | return fmt.Sprintf("%s v%s (built w/%s)", app, Binary, runtime.Version()) 12 | } 13 | -------------------------------------------------------------------------------- /cmd/agent/agent.toml: -------------------------------------------------------------------------------- 1 | hosts = "127.0.0.1:2379" #etcd hosts 2 | save_type=2 #1=file, 2=share memory 3 | save_path = "/tmp/goconfd" #file or share memory save path 4 | file_ext = "php" 5 | key_prefix= "develop.activity" #monitor key prefix 6 | heartbeat_interval=30 -------------------------------------------------------------------------------- /registry/frontend.go: -------------------------------------------------------------------------------- 1 | package registry 2 | 3 | import ( 4 | _ "time" 5 | 6 | "github.com/Leon2012/goconfd/libs/kv" 7 | ) 8 | 9 | type Frontend interface { 10 | Save(k *kv.Kv) error 11 | Get(k string) (*kv.Kv, error) 12 | Keys() []string 13 | } 14 | 15 | var DefaultFrontend Frontend 16 | -------------------------------------------------------------------------------- /libs/util/wait_group_wrapper.go: -------------------------------------------------------------------------------- 1 | package util 2 | 3 | import ( 4 | "sync" 5 | ) 6 | 7 | type WaitGroupWrapper struct { 8 | sync.WaitGroup 9 | } 10 | 11 | func (w *WaitGroupWrapper) Wrap(cb func()) { 12 | w.Add(1) 13 | go func() { 14 | cb() 15 | w.Done() 16 | }() 17 | } 18 | -------------------------------------------------------------------------------- /libs/shm/common_test.go: -------------------------------------------------------------------------------- 1 | package shm 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | func TestFtok(t *testing.T) { 8 | pathname := "/tmp/queue1" 9 | projID := uint8(0x01) 10 | key, err := Ftok(pathname, projID) 11 | if err != nil { 12 | t.Error(err) 13 | } else { 14 | t.Log(key) 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /libs/util/string_array.go: -------------------------------------------------------------------------------- 1 | package util 2 | 3 | import ( 4 | "strings" 5 | ) 6 | 7 | type StringArray []string 8 | 9 | func (a *StringArray) Set(s string) error { 10 | *a = append(*a, s) 11 | return nil 12 | } 13 | 14 | func (a *StringArray) String() string { 15 | return strings.Join(*a, ";") 16 | } 17 | -------------------------------------------------------------------------------- /libs/reflect/tag.go: -------------------------------------------------------------------------------- 1 | package reflect 2 | 3 | import ( 4 | "reflect" 5 | ) 6 | 7 | func GetTag(s interface{}, fieldName, tagName string) string { 8 | t := reflect.TypeOf(s) 9 | field, b := t.Elem().FieldByName(fieldName) 10 | if !b { 11 | return "" 12 | } 13 | tag := field.Tag.Get(tagName) 14 | return tag 15 | } 16 | -------------------------------------------------------------------------------- /agent/agent_test.go: -------------------------------------------------------------------------------- 1 | package agent 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | func TestAgent(t *testing.T) { 8 | opts := NewOptions() 9 | opts.Hosts = "localhost:2379" 10 | opts.KeyPrefix = "/develop/activity/" 11 | opts.SavePath = "/home/vagrant" 12 | 13 | agent := NewAgent(opts) 14 | defer agent.Exit() 15 | 16 | agent.Main() 17 | 18 | } 19 | -------------------------------------------------------------------------------- /libs/kv/json.go: -------------------------------------------------------------------------------- 1 | package kv 2 | 3 | import "encoding/json" 4 | 5 | func JsonEncode(kv *Kv) ([]byte, error) { 6 | b, err := json.Marshal(kv) 7 | return b, err 8 | } 9 | 10 | func JsonDecode(data []byte) (*Kv, error) { 11 | var k Kv 12 | err := json.Unmarshal(data, &k) 13 | if err != nil { 14 | return nil, err 15 | } else { 16 | return &k, nil 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /store/store.go: -------------------------------------------------------------------------------- 1 | package store 2 | 3 | import ( 4 | "github.com/Leon2012/goconfd/store/db" 5 | ) 6 | 7 | var adaptr db.Adapter 8 | 9 | func Register(name string, adapter db.Adapter) { 10 | if adapter == nil { 11 | panic("store: Register adapter is nil") 12 | } 13 | if adaptr != nil { 14 | panic("store: Adapter already registered") 15 | } 16 | adaptr = adapter 17 | } 18 | -------------------------------------------------------------------------------- /registry/frontend/empty.go: -------------------------------------------------------------------------------- 1 | package frontend 2 | 3 | import ( 4 | "fmt" 5 | "github.com/Leon2012/goconfd/libs/kv" 6 | ) 7 | 8 | type Empty struct{} 9 | 10 | func (e *Empty) Save(k *kv.Kv) error { 11 | //fmt.Printf("key : %s, value : %s \n", k.Key, k.Value) 12 | fmt.Println(k) 13 | return nil 14 | } 15 | 16 | func (e *Empty) Get(k string) (*kv.Kv, error) { 17 | fmt.Printf("get key : %s \n", k) 18 | return nil, nil 19 | } 20 | -------------------------------------------------------------------------------- /dashboard/template/asset/font-awesome/less/bordered-pulled.less: -------------------------------------------------------------------------------- 1 | // Bordered & Pulled 2 | // ------------------------- 3 | 4 | .@{fa-css-prefix}-border { 5 | padding: .2em .25em .15em; 6 | border: solid .08em @fa-border-color; 7 | border-radius: .1em; 8 | } 9 | 10 | .pull-right { float: right; } 11 | .pull-left { float: left; } 12 | 13 | .@{fa-css-prefix} { 14 | &.pull-left { margin-right: .3em; } 15 | &.pull-right { margin-left: .3em; } 16 | } 17 | -------------------------------------------------------------------------------- /dashboard/template/asset/font-awesome/scss/_bordered-pulled.scss: -------------------------------------------------------------------------------- 1 | // Bordered & Pulled 2 | // ------------------------- 3 | 4 | .#{$fa-css-prefix}-border { 5 | padding: .2em .25em .15em; 6 | border: solid .08em $fa-border-color; 7 | border-radius: .1em; 8 | } 9 | 10 | .pull-right { float: right; } 11 | .pull-left { float: left; } 12 | 13 | .#{$fa-css-prefix} { 14 | &.pull-left { margin-right: .3em; } 15 | &.pull-right { margin-left: .3em; } 16 | } 17 | -------------------------------------------------------------------------------- /dashboard/template/asset/css/morris.css: -------------------------------------------------------------------------------- 1 | .morris-hover{position:absolute;z-index:1000}.morris-hover.morris-default-style{border-radius:10px;padding:6px;color:#666;background:rgba(255,255,255,0.8);border:solid 2px rgba(230,230,230,0.8);font-family:sans-serif;font-size:12px;text-align:center}.morris-hover.morris-default-style .morris-hover-row-label{font-weight:bold;margin:0.25em 0} 2 | .morris-hover.morris-default-style .morris-hover-point{white-space:nowrap;margin:0.1em 0} 3 | -------------------------------------------------------------------------------- /dashboard/template/asset/font-awesome/less/larger.less: -------------------------------------------------------------------------------- 1 | // Icon Sizes 2 | // ------------------------- 3 | 4 | /* makes the font 33% larger relative to the icon container */ 5 | .@{fa-css-prefix}-lg { 6 | font-size: (4em / 3); 7 | line-height: (3em / 4); 8 | vertical-align: -15%; 9 | } 10 | .@{fa-css-prefix}-2x { font-size: 2em; } 11 | .@{fa-css-prefix}-3x { font-size: 3em; } 12 | .@{fa-css-prefix}-4x { font-size: 4em; } 13 | .@{fa-css-prefix}-5x { font-size: 5em; } 14 | -------------------------------------------------------------------------------- /dashboard/template/asset/font-awesome/scss/_larger.scss: -------------------------------------------------------------------------------- 1 | // Icon Sizes 2 | // ------------------------- 3 | 4 | /* makes the font 33% larger relative to the icon container */ 5 | .#{$fa-css-prefix}-lg { 6 | font-size: (4em / 3); 7 | line-height: (3em / 4); 8 | vertical-align: -15%; 9 | } 10 | .#{$fa-css-prefix}-2x { font-size: 2em; } 11 | .#{$fa-css-prefix}-3x { font-size: 3em; } 12 | .#{$fa-css-prefix}-4x { font-size: 4em; } 13 | .#{$fa-css-prefix}-5x { font-size: 5em; } 14 | -------------------------------------------------------------------------------- /libs/reflect/tag_test.go: -------------------------------------------------------------------------------- 1 | package reflect 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | type Kv struct { 8 | KeyPrefix string `json:"prefix" php:"prefix"` 9 | Revision int64 `json:"rev" php:"rev"` 10 | Event int32 `json:"type" php:"type"` 11 | Key string `json:"key" php:"key"` 12 | Value string `json:"value" php:"value"` 13 | } 14 | 15 | func TestGetTag(t *testing.T) { 16 | kv := &Kv{} 17 | tag := GetTag(kv, "Revision", "php") 18 | t.Log(tag) 19 | } 20 | -------------------------------------------------------------------------------- /agent/saver.go: -------------------------------------------------------------------------------- 1 | package agent 2 | 3 | import ( 4 | "github.com/Leon2012/goconfd/libs/kv" 5 | ) 6 | 7 | func SaveKV(ctx *Context) { 8 | var kv *kv.Kv 9 | for { 10 | select { 11 | case kv = <-ctx.Agent.watchKVChan: 12 | err := ctx.Agent.local.Save(kv) 13 | if err != nil { 14 | ctx.Agent.logf(err.Error()) 15 | } else { 16 | ctx.Agent.setLastHeartbeat(kv) 17 | ctx.Agent.logf("key:" + kv.Key + " save success") 18 | } 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /dashboard/template/asset/font-awesome/less/core.less: -------------------------------------------------------------------------------- 1 | // Base Class Definition 2 | // ------------------------- 3 | 4 | .@{fa-css-prefix} { 5 | display: inline-block; 6 | font: normal normal normal 14px/1 FontAwesome; // shortening font declaration 7 | font-size: inherit; // can't have font-size inherit on line above, so need to override 8 | text-rendering: auto; // optimizelegibility throws things off #1094 9 | -webkit-font-smoothing: antialiased; 10 | -moz-osx-font-smoothing: grayscale; 11 | } 12 | -------------------------------------------------------------------------------- /dashboard/template/asset/font-awesome/less/list.less: -------------------------------------------------------------------------------- 1 | // List Icons 2 | // ------------------------- 3 | 4 | .@{fa-css-prefix}-ul { 5 | padding-left: 0; 6 | margin-left: @fa-li-width; 7 | list-style-type: none; 8 | > li { position: relative; } 9 | } 10 | .@{fa-css-prefix}-li { 11 | position: absolute; 12 | left: -@fa-li-width; 13 | width: @fa-li-width; 14 | top: (2em / 14); 15 | text-align: center; 16 | &.@{fa-css-prefix}-lg { 17 | left: (-@fa-li-width + (4em / 14)); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /dashboard/template/asset/font-awesome/scss/_core.scss: -------------------------------------------------------------------------------- 1 | // Base Class Definition 2 | // ------------------------- 3 | 4 | .#{$fa-css-prefix} { 5 | display: inline-block; 6 | font: normal normal normal 14px/1 FontAwesome; // shortening font declaration 7 | font-size: inherit; // can't have font-size inherit on line above, so need to override 8 | text-rendering: auto; // optimizelegibility throws things off #1094 9 | -webkit-font-smoothing: antialiased; 10 | -moz-osx-font-smoothing: grayscale; 11 | } 12 | -------------------------------------------------------------------------------- /dashboard/template/asset/font-awesome/scss/_list.scss: -------------------------------------------------------------------------------- 1 | // List Icons 2 | // ------------------------- 3 | 4 | .#{$fa-css-prefix}-ul { 5 | padding-left: 0; 6 | margin-left: $fa-li-width; 7 | list-style-type: none; 8 | > li { position: relative; } 9 | } 10 | .#{$fa-css-prefix}-li { 11 | position: absolute; 12 | left: -$fa-li-width; 13 | width: $fa-li-width; 14 | top: (2em / 14); 15 | text-align: center; 16 | &.#{$fa-css-prefix}-lg { 17 | left: -$fa-li-width + (4em / 14); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.2.0", 3 | "configurations": [ 4 | 5 | { 6 | "name": "Launch", 7 | "type": "go", 8 | "request": "launch", 9 | "mode": "debug", 10 | "remotePath": "", 11 | "port": 2345, 12 | "host": "127.0.0.1", 13 | "program": "${workspaceRoot}/cmd/agent", 14 | "env": {}, 15 | "args": [], 16 | "showLog": true 17 | } 18 | ] 19 | } -------------------------------------------------------------------------------- /dashboard/template/asset/font-awesome/scss/font-awesome.scss: -------------------------------------------------------------------------------- 1 | /*! 2 | * Font Awesome 4.2.0 by @davegandy - http://fontawesome.io - @fontawesome 3 | * License - http://fontawesome.io/license (Font: SIL OFL 1.1, CSS: MIT License) 4 | */ 5 | 6 | @import "variables"; 7 | @import "mixins"; 8 | @import "path"; 9 | @import "core"; 10 | @import "larger"; 11 | @import "fixed-width"; 12 | @import "list"; 13 | @import "bordered-pulled"; 14 | @import "spinning"; 15 | @import "rotated-flipped"; 16 | @import "stacked"; 17 | @import "icons"; 18 | -------------------------------------------------------------------------------- /dashboard/template/public_html/common/footer.html: -------------------------------------------------------------------------------- 1 | {{define "footer"}} 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | {{end}} -------------------------------------------------------------------------------- /libs/node/node.go: -------------------------------------------------------------------------------- 1 | package node 2 | 3 | import "encoding/json" 4 | 5 | type Node struct { 6 | Name string `json:"name"` 7 | IP string `json:"ip"` 8 | CPU int `json:"cpu"` 9 | ServiceAddress string `json:"service_address"` 10 | } 11 | 12 | func (n *Node) String() string { 13 | value, _ := json.Marshal(n) 14 | return string(value) 15 | } 16 | 17 | func NewNode(data string) (*Node, error) { 18 | var node Node 19 | err := json.Unmarshal([]byte(data), &node) 20 | if err != nil { 21 | return nil, err 22 | } 23 | return &node, nil 24 | } 25 | -------------------------------------------------------------------------------- /dashboard/template/asset/font-awesome/less/font-awesome.less: -------------------------------------------------------------------------------- 1 | /*! 2 | * Font Awesome 4.2.0 by @davegandy - http://fontawesome.io - @fontawesome 3 | * License - http://fontawesome.io/license (Font: SIL OFL 1.1, CSS: MIT License) 4 | */ 5 | 6 | @import "variables.less"; 7 | @import "mixins.less"; 8 | @import "path.less"; 9 | @import "core.less"; 10 | @import "larger.less"; 11 | @import "fixed-width.less"; 12 | @import "list.less"; 13 | @import "bordered-pulled.less"; 14 | @import "spinning.less"; 15 | @import "rotated-flipped.less"; 16 | @import "stacked.less"; 17 | @import "icons.less"; 18 | -------------------------------------------------------------------------------- /sdk/goconfd_test.go: -------------------------------------------------------------------------------- 1 | package sdk 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | func TestGet(t *testing.T) { 8 | gconfd, err := NewGoconfd("/dev/shm") 9 | if err != nil { 10 | t.Error(err) 11 | } 12 | k, err := gconfd.Get("develop.activity.k6") 13 | if err != nil { 14 | t.Error(err) 15 | } 16 | t.Log(k.String()) 17 | } 18 | 19 | func TestGetFromAgent(t *testing.T) { 20 | gconfd, err := NewGoconfd("/dev/shm") 21 | if err != nil { 22 | t.Error(err) 23 | } 24 | k, err := gconfd.Get("develop.activity.k7") 25 | if err != nil { 26 | t.Error(err) 27 | } 28 | t.Log(k.Value) 29 | } 30 | -------------------------------------------------------------------------------- /store/db/adapter.go: -------------------------------------------------------------------------------- 1 | package db 2 | 3 | import ( 4 | "time" 5 | 6 | "github.com/Leon2012/goconfd/store/types" 7 | ) 8 | 9 | type Adapter interface { 10 | Open(c interface{}) error 11 | Close() error 12 | IsOpen() bool 13 | Online(agent *types.Agent) error 14 | Offline(agent *types.Agent) error 15 | Heartbeat(pack *types.Heartbeat) error 16 | GetAgents() ([]*types.Agent, error) 17 | GetHeartbeats() ([]*types.Heartbeat, error) 18 | GetHeartbeatsByAgent(agent *types.Agent) ([]*types.Heartbeat, error) 19 | GetHeartbeatsByTime(time time.Time) ([]*types.Heartbeat, error) 20 | } 21 | -------------------------------------------------------------------------------- /dashboard/template/asset/font-awesome/less/stacked.less: -------------------------------------------------------------------------------- 1 | // Stacked Icons 2 | // ------------------------- 3 | 4 | .@{fa-css-prefix}-stack { 5 | position: relative; 6 | display: inline-block; 7 | width: 2em; 8 | height: 2em; 9 | line-height: 2em; 10 | vertical-align: middle; 11 | } 12 | .@{fa-css-prefix}-stack-1x, .@{fa-css-prefix}-stack-2x { 13 | position: absolute; 14 | left: 0; 15 | width: 100%; 16 | text-align: center; 17 | } 18 | .@{fa-css-prefix}-stack-1x { line-height: inherit; } 19 | .@{fa-css-prefix}-stack-2x { font-size: 2em; } 20 | .@{fa-css-prefix}-inverse { color: @fa-inverse; } 21 | -------------------------------------------------------------------------------- /libs/client/selector_test.go: -------------------------------------------------------------------------------- 1 | package client 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | var addrs []string 8 | 9 | func init() { 10 | addrs = []string{"127.0.0.1:1000", "127.0.0.1:1001", "127.0.0.1:1002", "127.0.0.1:1003", "127.0.0.1:1004", "127.0.0.1:1005"} 11 | } 12 | 13 | func TestDefaultSelector(t *testing.T) { 14 | selector := &DefaultSelector{} 15 | addr := selector.Select(addrs) 16 | t.Log(addr) 17 | } 18 | 19 | func TestRingSelector(t *testing.T) { 20 | selector := &RingSelector{} 21 | selector.Key = "1" 22 | addr := selector.Select(addrs) 23 | t.Log(addr) 24 | } 25 | -------------------------------------------------------------------------------- /dashboard/template/asset/font-awesome/scss/_stacked.scss: -------------------------------------------------------------------------------- 1 | // Stacked Icons 2 | // ------------------------- 3 | 4 | .#{$fa-css-prefix}-stack { 5 | position: relative; 6 | display: inline-block; 7 | width: 2em; 8 | height: 2em; 9 | line-height: 2em; 10 | vertical-align: middle; 11 | } 12 | .#{$fa-css-prefix}-stack-1x, .#{$fa-css-prefix}-stack-2x { 13 | position: absolute; 14 | left: 0; 15 | width: 100%; 16 | text-align: center; 17 | } 18 | .#{$fa-css-prefix}-stack-1x { line-height: inherit; } 19 | .#{$fa-css-prefix}-stack-2x { font-size: 2em; } 20 | .#{$fa-css-prefix}-inverse { color: $fa-inverse; } 21 | -------------------------------------------------------------------------------- /libs/try/try.go: -------------------------------------------------------------------------------- 1 | package try 2 | 3 | import ( 4 | "errors" 5 | ) 6 | 7 | var MaxRetries = 10 8 | var errMaxRetriesReached = errors.New("exceeded retry limit") 9 | 10 | type Func func(attempt int) (retry bool, err error) 11 | 12 | func Do(fn Func) error { 13 | var err error 14 | var cont bool 15 | attempt := 1 16 | for { 17 | cont, err = fn(attempt) 18 | if !cont || err == nil { 19 | break 20 | } 21 | attempt++ 22 | if attempt > MaxRetries { 23 | return errMaxRetriesReached 24 | } 25 | } 26 | return err 27 | } 28 | 29 | func IsMaxRetries(err error) bool { 30 | return err == errMaxRetriesReached 31 | } 32 | -------------------------------------------------------------------------------- /registry/backend.go: -------------------------------------------------------------------------------- 1 | package registry 2 | 3 | import ( 4 | _ "time" 5 | 6 | "github.com/Leon2012/goconfd/libs/kv" 7 | ) 8 | 9 | type Backend interface { 10 | Del(k string) error 11 | Put(k, v string) error 12 | Get(k string) ([]*kv.Kv, error) 13 | BatchGetByPrefix(prefix string) ([]*kv.Kv, error) 14 | WatchWithPrefix(prefix string, f kv.KvFunc) 15 | WatchWithPrefixs(prefixs []string, f kv.KvFunc) 16 | WatchWithKey(key string, f kv.KvFunc) 17 | WatchWithKeys(keys []string, f kv.KvFunc) 18 | PutWithTTL(k, v string, ttl int64) error 19 | SetTTL(k string) error 20 | Close() 21 | } 22 | 23 | var DefaultBackend Backend 24 | -------------------------------------------------------------------------------- /libs/shm/shm_test.go: -------------------------------------------------------------------------------- 1 | package shm 2 | 3 | import ( 4 | "fmt" 5 | _ "os" 6 | "testing" 7 | ) 8 | 9 | var SHM_FILE = "/dev/shm/confd6" 10 | 11 | func TestWrite(t *testing.T) { 12 | id, err := Open(SHM_FILE) 13 | if err != nil { 14 | t.Error(err) 15 | } 16 | //defer Close(id) 17 | t.Log("shm id" + fmt.Sprintf("%d", id)) 18 | err = Write(id, "111111111hello world11111") 19 | if err != nil { 20 | t.Error(err) 21 | } 22 | } 23 | 24 | func TestDel(t *testing.T) { 25 | Del(SHM_FILE) 26 | 27 | } 28 | 29 | func TestRead(t *testing.T) { 30 | str := Read(SHM_FILE) 31 | t.Log("result:" + str) 32 | } 33 | -------------------------------------------------------------------------------- /libs/shm/common.go: -------------------------------------------------------------------------------- 1 | package shm 2 | 3 | /* 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | key_t ftok(const char *pathname, int proj_id); 10 | */ 11 | import "C" 12 | import ( 13 | "errors" 14 | "unsafe" 15 | ) 16 | 17 | func Ftok(pathname string, projID uint8) (int64, error) { 18 | if projID == 0 { 19 | return -1, errors.New("sysvipc: projID must be nonzero") 20 | } 21 | cpath := C.CString(pathname) 22 | defer C.free(unsafe.Pointer(cpath)) 23 | rckey, err := C.ftok(cpath, C.int(projID)) 24 | rc := int64(rckey) 25 | if rc == -1 { 26 | return -1, err 27 | } 28 | return rc, nil 29 | } 30 | -------------------------------------------------------------------------------- /libs/protocol/protocol.go: -------------------------------------------------------------------------------- 1 | package protocol 2 | 3 | import "time" 4 | 5 | type Ack struct { 6 | Code int 7 | Message string 8 | } 9 | 10 | type NoArg struct { 11 | } 12 | 13 | type NoReply struct { 14 | } 15 | 16 | type OnlineArg struct { 17 | HostName string 18 | KeyPrefix string 19 | IpAddress string 20 | } 21 | 22 | type OnlineReply struct { 23 | Status bool 24 | } 25 | 26 | type OfflineArg struct { 27 | HostName string 28 | KeyPrefix string 29 | } 30 | 31 | type OfflineReply struct { 32 | } 33 | 34 | type HeartbeatArg struct { 35 | HostName string 36 | KeyPrefix string 37 | Key string 38 | Value string 39 | Time time.Time 40 | } 41 | type HeartbeatReply struct { 42 | } 43 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | PREFIX=/usr/local 2 | DESTDIR= 3 | GOFLAGS= 4 | BINDIR=${PREFIX}/bin 5 | 6 | BLDDIR = build 7 | EXT= 8 | ifeq (${GOOS},windows) 9 | EXT=.exe 10 | endif 11 | 12 | APPS = agent monitor dashboard 13 | all: $(APPS) 14 | 15 | $(BLDDIR)/%: 16 | @mkdir -p $(dir $@) 17 | go build ${GOFLAGS} -o $@ ./cmd/$* 18 | @cp ./cmd/agent/agent.toml ./build/agent.toml 19 | @cp ./cmd/monitor/monitor.toml ./build/monitor.toml 20 | @cp ./cmd/dashboard/dashboard.toml ./build/dashboard.toml 21 | 22 | $(APPS): %: $(BLDDIR)/% 23 | 24 | clean: 25 | rm -fr $(BLDDIR) 26 | 27 | install: $(APPS) 28 | install -m 755 -d ${DESTDIR}${BINDIR} 29 | for APP in $^ ; do install -m 755 ${BLDDIR}/$$APP ${DESTDIR}${BINDIR}/$$APP${EXT} ; done 30 | -------------------------------------------------------------------------------- /store/types/types.go: -------------------------------------------------------------------------------- 1 | package types 2 | 3 | import ( 4 | "time" 5 | 6 | "gopkg.in/mgo.v2/bson" 7 | ) 8 | 9 | type ObjHeader struct { 10 | Id bson.ObjectId `bson:"_id,omitempty"` 11 | CreatedAt time.Time 12 | UpdatedAt time.Time 13 | } 14 | 15 | type Agent struct { 16 | ObjHeader `bson:",inline"` 17 | HostName string 18 | KeyPrefix string 19 | IpAddress string 20 | Port int 21 | Status int //0-离线, 1-上线, 22 | HeartbeatTime time.Time 23 | } 24 | 25 | type Heartbeat struct { 26 | ObjHeader `bson:",inline"` 27 | HostName string 28 | KeyPrefix string 29 | LatestKey string 30 | LatestValue string 31 | LatestTime time.Time 32 | } 33 | -------------------------------------------------------------------------------- /libs/util/key_test.go: -------------------------------------------------------------------------------- 1 | package util 2 | 3 | import ( 4 | "encoding/hex" 5 | "fmt" 6 | "hash/crc32" 7 | "testing" 8 | ) 9 | 10 | func TestEncryptKey(t *testing.T) { 11 | key := "/dachu/zhuanpan99" 12 | key1 := HashKey(key) 13 | t.Log("key:" + key1) 14 | } 15 | 16 | func TestCrc32Key(t *testing.T) { 17 | var castagnoliTable = crc32.MakeTable(crc32.Castagnoli) 18 | key := "/dachu/zhuanpan99" 19 | crc := crc32.New(castagnoliTable) 20 | crc.Write([]byte(key)) 21 | 22 | fmt.Printf("Sum32 : %x \n", crc.Sum32()) 23 | } 24 | 25 | func TestHexKey(t *testing.T) { 26 | key := "/dachu/zhuanpan99" 27 | src := []byte(key) 28 | dst := make([]byte, hex.EncodedLen(len(src))) 29 | hex.Encode(dst, src) 30 | fmt.Printf("%s\n", dst) 31 | } 32 | -------------------------------------------------------------------------------- /dashboard/template/asset/font-awesome/less/rotated-flipped.less: -------------------------------------------------------------------------------- 1 | // Rotated & Flipped Icons 2 | // ------------------------- 3 | 4 | .@{fa-css-prefix}-rotate-90 { .fa-icon-rotate(90deg, 1); } 5 | .@{fa-css-prefix}-rotate-180 { .fa-icon-rotate(180deg, 2); } 6 | .@{fa-css-prefix}-rotate-270 { .fa-icon-rotate(270deg, 3); } 7 | 8 | .@{fa-css-prefix}-flip-horizontal { .fa-icon-flip(-1, 1, 0); } 9 | .@{fa-css-prefix}-flip-vertical { .fa-icon-flip(1, -1, 2); } 10 | 11 | // Hook for IE8-9 12 | // ------------------------- 13 | 14 | :root .@{fa-css-prefix}-rotate-90, 15 | :root .@{fa-css-prefix}-rotate-180, 16 | :root .@{fa-css-prefix}-rotate-270, 17 | :root .@{fa-css-prefix}-flip-horizontal, 18 | :root .@{fa-css-prefix}-flip-vertical { 19 | filter: none; 20 | } 21 | -------------------------------------------------------------------------------- /dashboard/template/asset/font-awesome/less/spinning.less: -------------------------------------------------------------------------------- 1 | // Spinning Icons 2 | // -------------------------- 3 | 4 | .@{fa-css-prefix}-spin { 5 | -webkit-animation: fa-spin 2s infinite linear; 6 | animation: fa-spin 2s infinite linear; 7 | } 8 | 9 | @-webkit-keyframes fa-spin { 10 | 0% { 11 | -webkit-transform: rotate(0deg); 12 | transform: rotate(0deg); 13 | } 14 | 100% { 15 | -webkit-transform: rotate(359deg); 16 | transform: rotate(359deg); 17 | } 18 | } 19 | 20 | @keyframes fa-spin { 21 | 0% { 22 | -webkit-transform: rotate(0deg); 23 | transform: rotate(0deg); 24 | } 25 | 100% { 26 | -webkit-transform: rotate(359deg); 27 | transform: rotate(359deg); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /dashboard/template/asset/font-awesome/scss/_spinning.scss: -------------------------------------------------------------------------------- 1 | // Spinning Icons 2 | // -------------------------- 3 | 4 | .#{$fa-css-prefix}-spin { 5 | -webkit-animation: fa-spin 2s infinite linear; 6 | animation: fa-spin 2s infinite linear; 7 | } 8 | 9 | @-webkit-keyframes fa-spin { 10 | 0% { 11 | -webkit-transform: rotate(0deg); 12 | transform: rotate(0deg); 13 | } 14 | 100% { 15 | -webkit-transform: rotate(359deg); 16 | transform: rotate(359deg); 17 | } 18 | } 19 | 20 | @keyframes fa-spin { 21 | 0% { 22 | -webkit-transform: rotate(0deg); 23 | transform: rotate(0deg); 24 | } 25 | 100% { 26 | -webkit-transform: rotate(359deg); 27 | transform: rotate(359deg); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /dashboard/template/asset/font-awesome/less/path.less: -------------------------------------------------------------------------------- 1 | /* FONT PATH 2 | * -------------------------- */ 3 | 4 | @font-face { 5 | font-family: 'FontAwesome'; 6 | src: url('@{fa-font-path}/fontawesome-webfont.eot?v=@{fa-version}'); 7 | src: url('@{fa-font-path}/fontawesome-webfont.eot?#iefix&v=@{fa-version}') format('embedded-opentype'), 8 | url('@{fa-font-path}/fontawesome-webfont.woff?v=@{fa-version}') format('woff'), 9 | url('@{fa-font-path}/fontawesome-webfont.ttf?v=@{fa-version}') format('truetype'), 10 | url('@{fa-font-path}/fontawesome-webfont.svg?v=@{fa-version}#fontawesomeregular') format('svg'); 11 | // src: url('@{fa-font-path}/FontAwesome.otf') format('opentype'); // used when developing fonts 12 | font-weight: normal; 13 | font-style: normal; 14 | } 15 | -------------------------------------------------------------------------------- /dashboard/template/asset/font-awesome/scss/_path.scss: -------------------------------------------------------------------------------- 1 | /* FONT PATH 2 | * -------------------------- */ 3 | 4 | @font-face { 5 | font-family: 'FontAwesome'; 6 | src: url('#{$fa-font-path}/fontawesome-webfont.eot?v=#{$fa-version}'); 7 | src: url('#{$fa-font-path}/fontawesome-webfont.eot?#iefix&v=#{$fa-version}') format('embedded-opentype'), 8 | url('#{$fa-font-path}/fontawesome-webfont.woff?v=#{$fa-version}') format('woff'), 9 | url('#{$fa-font-path}/fontawesome-webfont.ttf?v=#{$fa-version}') format('truetype'), 10 | url('#{$fa-font-path}/fontawesome-webfont.svg?v=#{$fa-version}#fontawesomeregular') format('svg'); 11 | //src: url('#{$fa-font-path}/FontAwesome.otf') format('opentype'); // used when developing fonts 12 | font-weight: normal; 13 | font-style: normal; 14 | } 15 | -------------------------------------------------------------------------------- /libs/util/file_test.go: -------------------------------------------------------------------------------- 1 | package util 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | func TestIsExist(t *testing.T) { 8 | fileName := "/go/src/github.com/Leon2012/goconfd/README11.md" 9 | exist := IsExist(fileName) 10 | if exist { 11 | t.Log("exist") 12 | } else { 13 | t.Log("no exist") 14 | } 15 | } 16 | 17 | func TestGetFileList(t *testing.T) { 18 | dir := "/Users/pengleon/Downloads/goconfd" 19 | ext := "php" 20 | files, err := GetFileList(dir, ext) 21 | if err != nil { 22 | t.Error(err) 23 | } 24 | t.Log(files) 25 | } 26 | 27 | func TestGetName(t *testing.T) { 28 | fileName := "/Users/pengleon/Downloads/goconfd/646576656c6f702e61637469766974792e64616368752e61637439392e6964.php" 29 | newFileName := GetName(fileName) 30 | t.Log(newFileName) 31 | } 32 | -------------------------------------------------------------------------------- /dashboard/template/asset/font-awesome/scss/_rotated-flipped.scss: -------------------------------------------------------------------------------- 1 | // Rotated & Flipped Icons 2 | // ------------------------- 3 | 4 | .#{$fa-css-prefix}-rotate-90 { @include fa-icon-rotate(90deg, 1); } 5 | .#{$fa-css-prefix}-rotate-180 { @include fa-icon-rotate(180deg, 2); } 6 | .#{$fa-css-prefix}-rotate-270 { @include fa-icon-rotate(270deg, 3); } 7 | 8 | .#{$fa-css-prefix}-flip-horizontal { @include fa-icon-flip(-1, 1, 0); } 9 | .#{$fa-css-prefix}-flip-vertical { @include fa-icon-flip(1, -1, 2); } 10 | 11 | // Hook for IE8-9 12 | // ------------------------- 13 | 14 | :root .#{$fa-css-prefix}-rotate-90, 15 | :root .#{$fa-css-prefix}-rotate-180, 16 | :root .#{$fa-css-prefix}-rotate-270, 17 | :root .#{$fa-css-prefix}-flip-horizontal, 18 | :root .#{$fa-css-prefix}-flip-vertical { 19 | filter: none; 20 | } 21 | -------------------------------------------------------------------------------- /dashboard/template/public_html/index.html: -------------------------------------------------------------------------------- 1 | {{template "layout" .}} 2 | {{define "content"}} 3 |
4 | 5 |
6 |
7 |

8 | {{.title}} 9 | 10 |

11 | 12 | 20 | 21 | 22 |
23 |
24 | 25 | 26 |
27 | 28 | {{end}} 29 | -------------------------------------------------------------------------------- /libs/client/selector.go: -------------------------------------------------------------------------------- 1 | package client 2 | 3 | import ( 4 | "errors" 5 | "math/rand" 6 | 7 | "github.com/Leon2012/goconfd/libs/ringhash" 8 | ) 9 | 10 | type Selector interface { 11 | Select(values []string) (string, error) 12 | } 13 | 14 | type DefaultSelector struct { 15 | } 16 | 17 | func (s *DefaultSelector) Select(values []string) (string, error) { 18 | max := len(values) 19 | idx := rand.Intn(max) 20 | return values[idx], nil 21 | } 22 | 23 | type RingSelector struct { 24 | Key string 25 | } 26 | 27 | func NewRingSelector(k string) *RingSelector { 28 | return &RingSelector{ 29 | Key: k, 30 | } 31 | } 32 | 33 | func (s *RingSelector) Select(values []string) (string, error) { 34 | if s.Key == "" { 35 | return "", errors.New("no default key") 36 | } 37 | max := len(values) 38 | ring := ringhash.New(max, nil) 39 | ring.Add(values...) 40 | str := ring.Get(s.Key) 41 | return str, nil 42 | } 43 | -------------------------------------------------------------------------------- /registry/frontend/file_test.go: -------------------------------------------------------------------------------- 1 | package frontend 2 | 3 | import ( 4 | "github.com/Leon2012/goconfd/libs/kv" 5 | "strings" 6 | "testing" 7 | ) 8 | 9 | func TestSplitKey(t *testing.T) { 10 | key := "/develop/activity/dachu99/actid2" 11 | lastIndex := strings.LastIndex(key, "/") 12 | path := key[0:(lastIndex + 1)] 13 | file := key[(lastIndex + 1):len(key)] 14 | 15 | t.Log("file:" + file) 16 | t.Log("path:" + path) 17 | } 18 | 19 | func TestKeySave(t *testing.T) { 20 | k := kv.NewKv(1, "/develop/activity/dachu99/actid2", "40086", 1) 21 | s, err := NewFileSaver("/home/vagrant") 22 | if err != nil { 23 | t.Error(err) 24 | } 25 | err = s.Save(k) 26 | if err != nil { 27 | t.Error(err) 28 | } 29 | } 30 | 31 | func TestKeyGet(t *testing.T) { 32 | key := "/develop/activity/dachu99/actid2" 33 | s, err := NewFileSaver("/home/vagrant") 34 | if err != nil { 35 | t.Error(err) 36 | } 37 | kv, err := s.Get(key) 38 | if err != nil { 39 | t.Error(err) 40 | } 41 | t.Log(kv) 42 | } 43 | -------------------------------------------------------------------------------- /dashboard/template/asset/font-awesome/less/mixins.less: -------------------------------------------------------------------------------- 1 | // Mixins 2 | // -------------------------- 3 | 4 | .fa-icon() { 5 | display: inline-block; 6 | font: normal normal normal 14px/1 FontAwesome; // shortening font declaration 7 | font-size: inherit; // can't have font-size inherit on line above, so need to override 8 | text-rendering: auto; // optimizelegibility throws things off #1094 9 | -webkit-font-smoothing: antialiased; 10 | -moz-osx-font-smoothing: grayscale; 11 | } 12 | 13 | .fa-icon-rotate(@degrees, @rotation) { 14 | filter: progid:DXImageTransform.Microsoft.BasicImage(rotation=@rotation); 15 | -webkit-transform: rotate(@degrees); 16 | -ms-transform: rotate(@degrees); 17 | transform: rotate(@degrees); 18 | } 19 | 20 | .fa-icon-flip(@horiz, @vert, @rotation) { 21 | filter: progid:DXImageTransform.Microsoft.BasicImage(rotation=@rotation, mirror=1); 22 | -webkit-transform: scale(@horiz, @vert); 23 | -ms-transform: scale(@horiz, @vert); 24 | transform: scale(@horiz, @vert); 25 | } 26 | -------------------------------------------------------------------------------- /dashboard/template/asset/font-awesome/scss/_mixins.scss: -------------------------------------------------------------------------------- 1 | // Mixins 2 | // -------------------------- 3 | 4 | @mixin fa-icon() { 5 | display: inline-block; 6 | font: normal normal normal 14px/1 FontAwesome; // shortening font declaration 7 | font-size: inherit; // can't have font-size inherit on line above, so need to override 8 | text-rendering: auto; // optimizelegibility throws things off #1094 9 | -webkit-font-smoothing: antialiased; 10 | -moz-osx-font-smoothing: grayscale; 11 | } 12 | 13 | @mixin fa-icon-rotate($degrees, $rotation) { 14 | filter: progid:DXImageTransform.Microsoft.BasicImage(rotation=#{$rotation}); 15 | -webkit-transform: rotate($degrees); 16 | -ms-transform: rotate($degrees); 17 | transform: rotate($degrees); 18 | } 19 | 20 | @mixin fa-icon-flip($horiz, $vert, $rotation) { 21 | filter: progid:DXImageTransform.Microsoft.BasicImage(rotation=#{$rotation}); 22 | -webkit-transform: scale($horiz, $vert); 23 | -ms-transform: scale($horiz, $vert); 24 | transform: scale($horiz, $vert); 25 | } 26 | -------------------------------------------------------------------------------- /agent/rpc_client_test.go: -------------------------------------------------------------------------------- 1 | package agent 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/Leon2012/goconfd/libs/client" 7 | "github.com/Leon2012/goconfd/libs/protocol" 8 | ) 9 | 10 | var rpcAddress = "0.0.0.0:3002" 11 | var addrs []string 12 | var rpcClient *client.RPCClient 13 | 14 | func init() { 15 | addrs = []string{} 16 | addrs = append(addrs, rpcAddress) 17 | } 18 | 19 | func TestOnline(t *testing.T) { 20 | var err error 21 | getClient() 22 | err = rpcClient.Open() 23 | if err != nil { 24 | t.Error(err) 25 | return 26 | } 27 | args := &protocol.OnlineArg{} 28 | args.HostName = "hostname" 29 | args.IpAddress = "127.0.0.1" 30 | args.KeyPrefix = "developer.usergroup" 31 | var reply protocol.Ack 32 | 33 | err = rpcClient.Call("MonitorRpc.Online", args, &reply) 34 | if err != nil { 35 | t.Error(err) 36 | return 37 | } 38 | 39 | if reply.Code == 1000 { 40 | t.Log("online success") 41 | } else { 42 | t.Log(reply.Message) 43 | } 44 | 45 | rpcClient.Close() 46 | } 47 | 48 | func getClient() { 49 | s := &client.DefaultSelector{} 50 | c := client.NewRPCClient(s) 51 | c.SetAddrs(addrs) 52 | rpcClient = c 53 | } 54 | -------------------------------------------------------------------------------- /libs/net2/net_test.go: -------------------------------------------------------------------------------- 1 | package net2 2 | 3 | import ( 4 | "fmt" 5 | "net" 6 | "testing" 7 | ) 8 | 9 | func TestParseHosts(t *testing.T) { 10 | hosts := "127.0.0.1:2379;192.168.174.114:2379" 11 | arr := ParseHosts(hosts) 12 | t.Log(arr) 13 | } 14 | 15 | func TestCheckIP(t *testing.T) { 16 | ipStr := "192.168.174.114:2379" 17 | ip := CheckIp(ipStr) 18 | t.Log(ip) 19 | } 20 | 21 | func TestGetIp(t *testing.T) { 22 | ifaces, err := net.Interfaces() 23 | if err != nil { 24 | fmt.Print(err) 25 | return 26 | } 27 | for _, i := range ifaces { 28 | addrs, err := i.Addrs() 29 | if err != nil { 30 | fmt.Print(err) 31 | continue 32 | } 33 | for _, a := range addrs { 34 | if ipnet, ok := a.(*net.IPNet); ok && !ipnet.IP.IsLoopback() { 35 | if ipnet.IP.To4() != nil { 36 | fmt.Printf("%v : %s (%s)\n", i.Name, ipnet.IP.String(), ipnet.IP.DefaultMask()) 37 | } 38 | } 39 | // //fmt.Println(a) 40 | // switch v := a.(type) { 41 | // case *net.IPNet: 42 | // fmt.Printf("%v : %s (%s)\n", i.Name, v, v.IP.DefaultMask()) 43 | // } 44 | 45 | } 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /libs/kv/kv.go: -------------------------------------------------------------------------------- 1 | package kv 2 | 3 | import "fmt" 4 | 5 | const ( 6 | KV_EVENT_NONE = -1 7 | KV_EVENT_PUT = 0 8 | KV_EVENT_DELETE = 1 9 | ) 10 | 11 | type Kv struct { 12 | Revision int64 `json:"rev" php:"rev"` 13 | Event int32 `json:"type" php:"type"` 14 | Key string `json:"key" php:"key"` 15 | Value string `json:"value" php:"value"` 16 | } 17 | 18 | type KvFunc func(kv *Kv, prefix string) bool 19 | type EncodeFunc func(kv *Kv) ([]byte, error) 20 | type DecodeFunc func([]byte) (*Kv, error) 21 | 22 | func NewKv(event int32, key, value string, rev int64) *Kv { 23 | k := &Kv{ 24 | Event: event, 25 | Key: key, 26 | Value: value, 27 | Revision: rev, 28 | } 29 | return k 30 | } 31 | 32 | func (e *Kv) String() string { 33 | return fmt.Sprintf("key : %s, value : %s, event : %d , rev : %d \n", e.Key, e.Value, int(e.Event), int(e.Revision)) 34 | } 35 | 36 | func (e *Kv) Encode(f EncodeFunc) ([]byte, error) { 37 | b, err := f(e) 38 | return b, err 39 | } 40 | 41 | func Decode(data []byte, f DecodeFunc) (*Kv, error) { 42 | kv, err := f(data) 43 | return kv, err 44 | } 45 | -------------------------------------------------------------------------------- /sdk/goconfd.go: -------------------------------------------------------------------------------- 1 | package sdk 2 | 3 | import ( 4 | "fmt" 5 | "io/ioutil" 6 | "net/http" 7 | 8 | "github.com/Leon2012/goconfd/libs/kv" 9 | "github.com/Leon2012/goconfd/registry" 10 | "github.com/Leon2012/goconfd/registry/frontend" 11 | ) 12 | 13 | type Goconfd struct { 14 | local registry.Frontend 15 | agentUrl string 16 | } 17 | 18 | func NewGoconfd(shmPath string) (*Goconfd, error) { 19 | g := &Goconfd{} 20 | g.agentUrl = "http://127.0.0.1:3001/" 21 | local, err := frontend.NewShmSaver(shmPath) 22 | if err != nil { 23 | return nil, err 24 | } 25 | g.local = local 26 | return g, nil 27 | } 28 | 29 | func (g *Goconfd) SetAgentUrl(url string) { 30 | g.agentUrl = url 31 | } 32 | 33 | func (g *Goconfd) Get(key string) (*kv.Kv, error) { 34 | return g.local.Get(key) 35 | } 36 | 37 | func (g *Goconfd) GetFromAgent(key string) (*kv.Kv, error) { 38 | url := fmt.Sprintf("%s/get/%s", g.agentUrl, key) 39 | resp, err := http.Get(url) 40 | if err != nil { 41 | return nil, err 42 | } 43 | defer resp.Body.Close() 44 | data, err := ioutil.ReadAll(resp.Body) 45 | if err != nil { 46 | return nil, err 47 | } 48 | nkv, err := kv.Decode(data, kv.JsonDecode) 49 | if err != nil { 50 | return nil, err 51 | } 52 | return nkv, nil 53 | } 54 | -------------------------------------------------------------------------------- /dashboard/template/public_html/common/header.html: -------------------------------------------------------------------------------- 1 | {{define "header"}} 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | Goconfd Admin 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 33 | 34 | 35 | 36 | 37 | 38 |
39 | {{end}} -------------------------------------------------------------------------------- /libs/kv/kv_test.go: -------------------------------------------------------------------------------- 1 | package kv 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | func TestJsonEncode(t *testing.T) { 8 | kv := &Kv{ 9 | Revision: 0, 10 | Event: 0, 11 | Key: "dachu/dachu99_actid", 12 | Value: "48800", 13 | } 14 | b, err := kv.Encode(JsonEncode) 15 | if err != nil { 16 | t.Error(err) 17 | } 18 | t.Log(string(b)) 19 | } 20 | 21 | func TestJsonDecode(t *testing.T) { 22 | json := "{\"rev\":0,\"type\":0,\"key\":\"dachu/dachu99_actid\",\"value\":\"48800\"}" 23 | data := []byte(json) 24 | kv, err := Decode(data, JsonDecode) 25 | if err != nil { 26 | t.Error(err) 27 | } 28 | t.Log(kv.String()) 29 | } 30 | 31 | func TestPhpEncode(t *testing.T) { 32 | kv := &Kv{ 33 | Revision: 0, 34 | Event: 0, 35 | Key: "dachu/dachu99_actid", 36 | Value: "48800", 37 | } 38 | b, err := kv.Encode(PhpEncode) 39 | if err != nil { 40 | t.Error(err) 41 | } 42 | t.Log(string(b)) 43 | } 44 | 45 | func TestPhpDecode(t *testing.T) { 46 | php := ` 5 | #include 6 | char **EMPTY = NULL; 7 | */ 8 | import "C" //此行和上面的注释之间不能有空行,否则会报错 9 | import ( 10 | "crypto/md5" 11 | "encoding/hex" 12 | "fmt" 13 | "regexp" 14 | "unsafe" 15 | ) 16 | 17 | /** 18 | * @brief str_hash Hash algorithm of processing a md5 string. 19 | * 20 | * @param str The md5 string. 21 | * 22 | * @return The number less than 1024. 23 | */ 24 | func str_hash(str string) int { 25 | b := []byte(str) 26 | c := b[0:3] 27 | 28 | cc := C.CString(string(c)) 29 | defer C.free(unsafe.Pointer(cc)) 30 | 31 | d := C.strtol(cc, C.EMPTY, 16) 32 | d = d / 4 33 | return int(d) 34 | } 35 | 36 | func GenMd5Str(data []byte) string { 37 | fmt.Println("Begin to Caculate MD5...") 38 | m := md5.New() 39 | m.Write(data) 40 | return hex.EncodeToString(m.Sum(nil)) 41 | } 42 | 43 | func IsMd5(str string) bool { 44 | regular := `^([0-9a-zA-Z]){32}$` 45 | regx := regexp.MustCompile(regular) 46 | return regx.MatchString(str) 47 | } 48 | 49 | func HashKey(key string) string { 50 | data := []byte(key) 51 | md5Sum := GenMd5Str(data) 52 | lvl1 := str_hash(md5Sum) 53 | lvl2 := str_hash(string(md5Sum[3:])) 54 | return fmt.Sprintf("%d/%d/%s", lvl1, lvl2, md5Sum) 55 | } 56 | 57 | func HexKey(key string) string { 58 | src := []byte(key) 59 | dst := make([]byte, hex.EncodedLen(len(src))) 60 | hex.Encode(dst, src) 61 | return string(dst) 62 | } 63 | 64 | func UnHexKey(key string) (string, error) { 65 | decoded, err := hex.DecodeString(key) 66 | if err != nil { 67 | return "", err 68 | } 69 | return string(decoded), nil 70 | } 71 | -------------------------------------------------------------------------------- /libs/client/rpc.go: -------------------------------------------------------------------------------- 1 | package client 2 | 3 | import ( 4 | "errors" 5 | "net" 6 | "net/rpc" 7 | ) 8 | 9 | type RPCClient struct { 10 | client *rpc.Client 11 | conn *net.TCPConn 12 | addrs []string 13 | selector Selector 14 | } 15 | 16 | func NewRPCClient(sel Selector) *RPCClient { 17 | if sel == nil { 18 | sel = &DefaultSelector{} 19 | } 20 | return &RPCClient{ 21 | selector: sel, 22 | } 23 | } 24 | 25 | func (c *RPCClient) SetAddrs(addrs []string) { 26 | c.addrs = addrs 27 | } 28 | 29 | func (c *RPCClient) Select() string { 30 | s, err := c.selector.Select(c.addrs) 31 | if err != nil { 32 | return "" 33 | } 34 | return s 35 | } 36 | 37 | func (c *RPCClient) Open() error { 38 | addr := c.Select() 39 | if addr == "" { 40 | return errors.New("no addr select") 41 | } 42 | address, err := net.ResolveTCPAddr("tcp", addr) 43 | if err != nil { 44 | return err 45 | } 46 | conn, err := net.DialTCP("tcp", nil, address) 47 | if err != nil { 48 | return err 49 | } 50 | c.conn = conn 51 | client := rpc.NewClient(conn) 52 | c.client = client 53 | return nil 54 | } 55 | 56 | func (c *RPCClient) Call(method string, args interface{}, reply interface{}) error { 57 | err := c.Open() 58 | if err != nil { 59 | return err 60 | } 61 | defer c.Close() 62 | err = c.client.Call(method, args, reply) 63 | return err 64 | } 65 | 66 | func (c *RPCClient) Close() { 67 | if c.conn != nil { 68 | c.conn.Close() 69 | c.conn = nil 70 | } 71 | if c.client != nil { 72 | c.client.Close() 73 | 74 | c.client = nil 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /store/db/mongo/mongo_test.go: -------------------------------------------------------------------------------- 1 | package mongo 2 | 3 | import ( 4 | "os" 5 | "testing" 6 | "time" 7 | 8 | "github.com/Leon2012/goconfd/store/types" 9 | ) 10 | 11 | var config MongoConfig 12 | var adapter *MongoAdapter 13 | 14 | func init() { 15 | config = MongoConfig{ 16 | Url: "127.0.0.1:27017", 17 | DbName: "goconfd", 18 | Timeout: 1, 19 | Username: "", 20 | Password: "", 21 | } 22 | adapter = NewMongoAdapter() 23 | } 24 | 25 | func TestOnline(t *testing.T) { 26 | hostName, _ := os.Hostname() 27 | a := &types.Agent{ 28 | HostName: hostName, 29 | KeyPrefix: "developer.activity", 30 | IpAddress: "127.0.0.1", 31 | Port: 30, 32 | } 33 | adapter.Open(config) 34 | defer adapter.Close() 35 | err := adapter.Online(a) 36 | if err != nil { 37 | t.Error(err) 38 | } 39 | } 40 | 41 | func TestOffline(t *testing.T) { 42 | hostName, _ := os.Hostname() 43 | a := &types.Agent{ 44 | HostName: hostName, 45 | } 46 | adapter.Open(config) 47 | defer adapter.Close() 48 | err := adapter.Offline(a) 49 | if err != nil { 50 | t.Error(err) 51 | } 52 | } 53 | 54 | func TestHeartbeat(t *testing.T) { 55 | hostName, _ := os.Hostname() 56 | l := &types.Heartbeat{ 57 | HostName: hostName, 58 | KeyPrefix: "developer.activity", 59 | LatestKey: "a", 60 | LatestValue: "b", 61 | LatestTime: time.Now(), 62 | } 63 | adapter.Open(config) 64 | defer adapter.Close() 65 | err := adapter.Heartbeat(l) 66 | if err != nil { 67 | t.Error(err) 68 | } 69 | } 70 | 71 | func TestGetAgents(t *testing.T) { 72 | adapter.Open(config) 73 | defer adapter.Close() 74 | agents, err := adapter.GetAgents() 75 | if err != nil { 76 | t.Error(err) 77 | } else { 78 | t.Log(agents) 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /libs/util/file.go: -------------------------------------------------------------------------------- 1 | package util 2 | 3 | import ( 4 | "errors" 5 | "io/ioutil" 6 | "os" 7 | "path/filepath" 8 | "strings" 9 | ) 10 | 11 | func IsWritable(folder string) (err error) { 12 | fileInfo, err := os.Stat(folder) 13 | if err != nil { 14 | return err 15 | } 16 | if !fileInfo.IsDir() { 17 | return errors.New("Not a valid folder!") 18 | } 19 | perm := fileInfo.Mode().Perm() 20 | if 0200&perm != 0 { 21 | return nil 22 | } 23 | return errors.New("Not writable!") 24 | } 25 | 26 | func IsExist(name string) bool { 27 | _, err := os.Stat(name) 28 | //fmt.Println(err) 29 | if os.IsNotExist(err) { 30 | return false 31 | } 32 | return true 33 | } 34 | 35 | func GetFileList(dir string, ext string) ([]string, error) { 36 | files := []string{} 37 | fs, err := ioutil.ReadDir(dir) 38 | if err != nil { 39 | return nil, err 40 | } 41 | for _, f := range fs { 42 | if !f.IsDir() { 43 | fileName := f.Name() 44 | if strings.HasSuffix(fileName, ext) { 45 | fileName = filepath.Join(dir, fileName) 46 | files = append(files, fileName) 47 | } 48 | } 49 | } 50 | return files, nil 51 | } 52 | 53 | func GetFileContent(file string) ([]byte, error) { 54 | return ioutil.ReadFile(file) 55 | } 56 | 57 | func GetName(fileName string) string { 58 | pathSeparatorIdx := strings.LastIndex(fileName, "/") 59 | if pathSeparatorIdx == -1 { 60 | return fileName 61 | } 62 | baseFileName := fileName[(pathSeparatorIdx + 1):len(fileName)] 63 | pointSeparatorIdx := strings.LastIndex(baseFileName, ".") 64 | if pointSeparatorIdx == -1 { 65 | return baseFileName 66 | } 67 | name := baseFileName[0:pointSeparatorIdx] 68 | return name 69 | } 70 | -------------------------------------------------------------------------------- /monitor/options.go: -------------------------------------------------------------------------------- 1 | package monitor 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "log" 7 | "os" 8 | 9 | "github.com/Leon2012/goconfd/libs/net2" 10 | ) 11 | 12 | type Options struct { 13 | Logger *log.Logger 14 | RpcAddress string `flag:"rpc_address"` 15 | DBUrl string `flag:"db_url" cfg:"db_url"` 16 | DBName string `flag:"db_name" cfg:"db_name"` 17 | DBUser string `flag:"db_user" cfg:"db_user"` 18 | DBPass string `flag:"db_pass" cfg:"db_pass"` 19 | DBTimeout int `flag:"db_timeout" cfg:"db_timeout"` 20 | Hosts string `flag:"hosts" cfg:"hosts"` 21 | DialTimeout int `flag:"dial_timeout"` 22 | RequestTimeout int `flag:"request_timeout"` 23 | HeartbeatInterval int `flag:"heartbeat_interval" cfg:"heartbeat_interval"` 24 | } 25 | 26 | func NewOptions() *Options { 27 | return &Options{ 28 | RpcAddress: "0.0.0.0:3002", 29 | Logger: log.New(os.Stderr, "[monitor]", log.Ldate|log.Ltime|log.Lmicroseconds), 30 | DBUrl: "127.0.0.1:27017", 31 | DBName: "goconfd", 32 | DBTimeout: 5, 33 | DialTimeout: 5, 34 | RequestTimeout: 5, 35 | HeartbeatInterval: 5, 36 | } 37 | } 38 | 39 | func (o *Options) Valid() error { 40 | if o.DBUrl == "" { 41 | return errors.New("db url is require") 42 | } 43 | if o.DBName == "" { 44 | return errors.New("db name is require") 45 | } 46 | return nil 47 | } 48 | 49 | func (o *Options) String() string { 50 | return fmt.Sprintf("rpc_address:%s, db_url:%s, db_name:%s \n", o.RpcAddress, o.DBUrl, o.DBName) 51 | 52 | } 53 | 54 | func (o *Options) ParseHosts() []string { 55 | return net2.ParseHosts(o.Hosts) 56 | } 57 | -------------------------------------------------------------------------------- /dashboard/options.go: -------------------------------------------------------------------------------- 1 | package dashboard 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "log" 7 | "os" 8 | 9 | "github.com/Leon2012/goconfd/libs/net2" 10 | ) 11 | 12 | type Options struct { 13 | Logger *log.Logger 14 | HttpAddress string `flag:"http_address"` 15 | DBUrl string `flag:"db_url" cfg:"db_url"` 16 | DBName string `flag:"db_name" cfg:"db_name"` 17 | DBUser string `flag:"db_user" cfg:"db_user"` 18 | DBPass string `flag:"db_pass" cfg:"db_pass"` 19 | DBTimeout int `flag:"db_timeout" cfg:"db_timeout"` 20 | Hosts string `flag:"hosts" cfg:"hosts"` 21 | DialTimeout int `flag:"dial_timeout"` 22 | RequestTimeout int `flag:"request_timeout"` 23 | TemplatePath string `flag:"template_path" cfg:"template_path"` 24 | } 25 | 26 | func NewOptions() *Options { 27 | return &Options{ 28 | HttpAddress: "0.0.0.0:3003", 29 | Logger: log.New(os.Stderr, "[dashboard]", log.Ldate|log.Ltime|log.Lmicroseconds), 30 | DBUrl: "127.0.0.1:27017", 31 | DBName: "goconfd", 32 | DBTimeout: 5, 33 | DialTimeout: 5, 34 | RequestTimeout: 5, 35 | Hosts: "localhost:2379", 36 | TemplatePath: "./", 37 | } 38 | } 39 | 40 | func (o *Options) String() string { 41 | return fmt.Sprintf("http_address:%s, etcd_hosts:%s, db_url:%s \n", o.HttpAddress, o.Hosts, o.DBUrl) 42 | } 43 | 44 | func (o *Options) Valid() error { 45 | if o.DBUrl == "" { 46 | return errors.New("db url is require") 47 | } 48 | if o.DBName == "" { 49 | return errors.New("db name is require") 50 | } 51 | if o.Hosts == "" { 52 | return errors.New("etcd hosts is require") 53 | } 54 | return nil 55 | } 56 | 57 | func (o *Options) ParseHosts() []string { 58 | return net2.ParseHosts(o.Hosts) 59 | } 60 | -------------------------------------------------------------------------------- /dashboard/dashboard_test.go: -------------------------------------------------------------------------------- 1 | package dashboard 2 | 3 | import ( 4 | "html/template" 5 | "log" 6 | "mime" 7 | "net/http" 8 | "testing" 9 | 10 | "path" 11 | 12 | "io/ioutil" 13 | 14 | "github.com/julienschmidt/httprouter" 15 | ) 16 | 17 | var templatePath string 18 | var templateFiles []string 19 | 20 | func init() { 21 | templatePath = "template/asset" 22 | templateFiles = []string{ 23 | "template/public_html/index.html", 24 | "template/public_html/common/header.html", 25 | "template/public_html/common/footer.html", 26 | "template/public_html/common/left.html", 27 | } 28 | 29 | } 30 | 31 | func TestTemplate(t *testing.T) { 32 | router := httprouter.New() 33 | router.GET("/", IndexHandler) 34 | router.GET("/asset/:path/:asset", staticHandler) 35 | log.Fatal(http.ListenAndServe(":8080", router)) 36 | } 37 | 38 | func staticHandler(w http.ResponseWriter, r *http.Request, ps httprouter.Params) { 39 | assetName := ps.ByName("asset") 40 | pathName := ps.ByName("path") 41 | assetFile := path.Join(templatePath, pathName, assetName) 42 | log.Println(assetFile) 43 | data, _ := ioutil.ReadFile(assetFile) 44 | 45 | ext := path.Ext(assetName) 46 | ct := mime.TypeByExtension(ext) 47 | if ct != "" { 48 | w.Header().Set("Content-Type", ct) 49 | } 50 | 51 | w.Write(data) 52 | } 53 | 54 | func IndexHandler(w http.ResponseWriter, r *http.Request, _ httprouter.Params) { 55 | data := map[string]string{ 56 | "Name": "Mike", 57 | } 58 | 59 | tmpl := template.Must(template.ParseFiles("template/public_html/index.html", "template/public_html/common/header.html", "template/public_html/common/footer.html", "template/public_html/common/left.html")) 60 | w.Header().Set("Content-Type", "text/html") 61 | 62 | err := tmpl.Execute(w, data) 63 | if err != nil { 64 | panic(err) 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /dashboard/template/public_html/var.html: -------------------------------------------------------------------------------- 1 | {{template "layout" .}} 2 | {{define "content"}} 3 |
4 | 5 |
6 |
7 |

8 | 变量设置 9 |

10 | 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 | {{end}} -------------------------------------------------------------------------------- /agent/msg_queue.go: -------------------------------------------------------------------------------- 1 | package agent 2 | 3 | import ( 4 | "github.com/Leon2012/goconfd/libs/kv" 5 | "github.com/Shopify/sysv_mq" 6 | ) 7 | 8 | const ( 9 | defaultMsgQueueMaxSize = 1024 10 | ) 11 | 12 | type MsgQueue struct { 13 | mq *sysv_mq.MessageQueue 14 | ctx *Context 15 | } 16 | 17 | func NewMsgQueue(ctx *Context, path string, projId int) (*MsgQueue, error) { 18 | mq, err := sysv_mq.NewMessageQueue( 19 | &sysv_mq.QueueConfig{ 20 | Path: path, 21 | ProjId: projId, 22 | //Key: 0xDEADBEEF, 23 | MaxSize: defaultMsgQueueMaxSize, 24 | Mode: sysv_mq.IPC_CREAT | 0600, 25 | }, 26 | ) 27 | if err != nil { 28 | return nil, err 29 | } 30 | return &MsgQueue{ 31 | mq: mq, 32 | ctx: ctx, 33 | }, nil 34 | } 35 | 36 | func (q *MsgQueue) Receive() { 37 | for { 38 | select { 39 | case <-q.ctx.Agent.exitChan: 40 | return 41 | default: 42 | message, mtype, err := q.mq.ReceiveString(0, 0) 43 | q.ctx.Agent.logf("INFO: receive mq message - %s, type - %d", message, mtype) 44 | if err != nil { 45 | q.ctx.Agent.logf("ERROR: receive mq message (%s) error (%s)", message, err) 46 | continue 47 | } 48 | _, err = q.handle([]byte(message), mtype) 49 | if err != nil { 50 | q.ctx.Agent.logf("ERROR: handle mq message (%s) error (%s)", message, err) 51 | continue 52 | } 53 | } 54 | } 55 | } 56 | 57 | func (q *MsgQueue) SendString(message string) error { 58 | return q.mq.SendString(message, 1, 0) 59 | } 60 | 61 | func (q *MsgQueue) Send(message []byte) error { 62 | return q.mq.SendBytes(message, 1, 0) 63 | } 64 | 65 | func (q *MsgQueue) Close() { 66 | q.mq.Destroy() 67 | } 68 | 69 | func (q *MsgQueue) handle(message []byte, mtype int) ([]byte, error) { 70 | key := string(message) 71 | k, err := LoadValueByKey(q.ctx, key) 72 | if err != nil { 73 | return nil, err 74 | } 75 | data, err := kv.JsonEncode(k) 76 | if err != nil { 77 | return nil, err 78 | } else { 79 | return data, nil 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /agent/options.go: -------------------------------------------------------------------------------- 1 | package agent 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "log" 7 | "os" 8 | 9 | "github.com/Leon2012/goconfd/libs/net2" 10 | "github.com/Leon2012/goconfd/libs/util" 11 | ) 12 | 13 | type Options struct { 14 | Logger *log.Logger 15 | HttpAddress string `flag:"http_address"` 16 | KeyPrefix string `flag:"key_prefix" cfg:"key_prefix"` 17 | Hosts string `flag:"hosts" cfg:"hosts"` 18 | DialTimeout int `flag:"dial_timeout"` 19 | RequestTimeout int `flag:"request_timeout"` 20 | SavePath string `flag:"save_path" cfg:"save_path"` 21 | SaveType int `flag:"save_type" cfg:"save_type"` 22 | FileExt string `flag:"file_ext" cfg:"file_ext"` 23 | AutoLoad bool `flag:"auto_load"` 24 | HeartbeatInterval int `flag:"heartbeat_interval" cfg:"heartbeat_interval"` 25 | } 26 | 27 | func NewOptions() *Options { 28 | return &Options{ 29 | HttpAddress: "0.0.0.0:3001", 30 | Logger: log.New(os.Stderr, "[agent]", log.Ldate|log.Ltime|log.Lmicroseconds), 31 | DialTimeout: 5, 32 | RequestTimeout: 5, 33 | HeartbeatInterval: 5, 34 | } 35 | } 36 | 37 | func (o *Options) String() string { 38 | return fmt.Sprintf("http_address:%s, key_prefix:%s, etcd_hosts:%s, dial_timeout:%d, request_timeout:%d, save_path:%s \n", o.HttpAddress, o.KeyPrefix, o.Hosts, o.DialTimeout, o.RequestTimeout, o.SavePath) 39 | } 40 | 41 | func (o *Options) ParseHosts() []string { 42 | return net2.ParseHosts(o.Hosts) 43 | } 44 | 45 | func (o *Options) Valid() error { 46 | if o.KeyPrefix == "" { 47 | return errors.New("key prefix is require") 48 | } 49 | if o.Hosts == "" { 50 | return errors.New("hosts is require") 51 | } 52 | if o.SavePath == "" { 53 | return errors.New("save path is require") 54 | } 55 | exist := util.IsExist(o.SavePath) 56 | if !exist { 57 | return errors.New("save path is not exist") 58 | } 59 | return nil 60 | } 61 | -------------------------------------------------------------------------------- /dashboard/template/public_html/common/left.html: -------------------------------------------------------------------------------- 1 | {{define "left"}} 2 | 3 | 42 | 43 | {{end}} -------------------------------------------------------------------------------- /agent/monitor.go: -------------------------------------------------------------------------------- 1 | package agent 2 | 3 | import ( 4 | "net" 5 | 6 | "fmt" 7 | 8 | "sync" 9 | 10 | "github.com/Leon2012/goconfd/libs/kv" 11 | "github.com/Leon2012/goconfd/libs/node" 12 | ) 13 | 14 | const ( 15 | defaultSystemMonitorNodeKey = "/system/monitor/nodes/" 16 | ) 17 | 18 | type Monitor struct { 19 | nodes []*node.Node 20 | ctx *Context 21 | sync.Mutex 22 | } 23 | 24 | func NewMonitor(ctx *Context) *Monitor { 25 | return &Monitor{ 26 | ctx: ctx, 27 | nodes: []*node.Node{}, 28 | } 29 | } 30 | 31 | func (m *Monitor) WatchNodes() { 32 | m.ctx.Agent.idc.WatchWithPrefix(defaultSystemMonitorNodeKey, func(k *kv.Kv, prefix string) bool { 33 | nd, err := node.NewNode(k.Value) 34 | if err == nil { 35 | if k.Event == kv.KV_EVENT_PUT { 36 | m.AddNode(nd) 37 | } else if k.Event == kv.KV_EVENT_DELETE { 38 | m.DelNode(nd) 39 | } 40 | m.ctx.Agent.rpcClient.ReloadAddrs() 41 | return true 42 | } else { 43 | return false 44 | } 45 | }) 46 | } 47 | 48 | func (m *Monitor) AddNode(node *node.Node) { 49 | m.Lock() 50 | for idx, nd := range m.nodes { 51 | if nd.Name == node.Name { 52 | m.nodes = append(m.nodes[:idx], m.nodes[idx+1:]...) 53 | break 54 | } 55 | } 56 | m.nodes = append(m.nodes, node) 57 | m.Unlock() 58 | } 59 | 60 | func (m *Monitor) DelNode(node *node.Node) { 61 | m.Lock() 62 | for idx, nd := range m.nodes { 63 | if nd.Name == node.Name { 64 | m.nodes = append(m.nodes[:idx], m.nodes[idx+1:]...) 65 | break 66 | } 67 | } 68 | m.Unlock() 69 | } 70 | 71 | func (m *Monitor) UpdateNode(node *node.Node) { 72 | m.Lock() 73 | for _, nd := range m.nodes { 74 | if nd.Name == node.Name { 75 | nd.CPU = node.CPU 76 | nd.IP = node.IP 77 | nd.ServiceAddress = node.ServiceAddress 78 | break 79 | } 80 | } 81 | m.Unlock() 82 | } 83 | 84 | func (m *Monitor) GetNodesAddr() []string { 85 | addrs := []string{} 86 | for _, node := range m.nodes { 87 | host, port, err := net.SplitHostPort(node.ServiceAddress) 88 | if err == nil { 89 | if host == "0.0.0.0" { 90 | host = node.IP 91 | } 92 | addr := fmt.Sprintf("%s:%s", host, port) 93 | addrs = append(addrs, addr) 94 | } 95 | } 96 | return addrs 97 | } 98 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | #Gconfd 配置中心 2 | 3 | 4 | ---------- 5 | 6 | 7 | ##简介 8 | golang+etcd语言实现的配置中心功能,客户端目前支持php和golang 9 | 10 | 11 | ---------- 12 | 13 | 14 | ##架构图 15 | 16 | ![avatar](./documents/img0.png) 17 | 18 | 19 | ---------- 20 | 21 | ## 编译安装 22 | 23 | 1,源码编译 24 | 25 | git clone https://github.com/Leon2012/goconfd 26 | cd goconfd 27 | make 28 | 29 | 2,安装etcd 30 | 31 | DOWNLOAD_URL=https://github.com/coreos/etcd/releases/download 32 | curl -L ${DOWNLOAD_URL}/${ETCD_VER}/etcd-${ETCD_VER}-linux-amd64.tar.gz -o /tmp/etcd-${ETCD_VER}-linux-amd64.tar.gz 33 | mkdir -p /tmp/test-etcd && tar xzvf /tmp/etcd-${ETCD_VER}-linux-amd64.tar.gz -C /tmp/test-etcd --strip-components=1 34 | 35 | 36 | ##使用 37 | 38 | 1,启动etcd 39 | 40 | /tmp/etcd 41 | 42 | 2,编辑agent.toml 43 | 44 | hosts = "localhost:2379" #etcd监听地址 45 | save_type=1 #kv保存类型,1为文件,2为共享内存 46 | save_path = "/tmp/goconfd" #kv文件保存目录 47 | file_ext = "php" #kv文件保存类型,目前支持php或json 48 | key_prefix= "develop.activity" #监听kv前缀 49 | heartbeat_interval=30 #agent发送心跳包的间隔 50 | 51 | 3,启动 52 | 53 | cd build 54 | ./agent --config agent.toml 55 | ./monitor --config monitor.toml 56 | ./dashboard --config dashboard.toml 57 | 58 | 4, 打开dashboard 59 | [打开dashboard][1] 60 | 61 | 62 | ---------- 63 | 64 | 65 | ##SDK 66 | 67 | 1,php 68 | 69 | composer require leon2012/goconfd-php-sdk 70 | include_once("../vendor/autoload.php"); 71 | use goconfd\phpsdk\Goconfd; 72 | $config = [ 73 | 'save_path' => '/tmp/goconfd', 74 | 'key_prefix' => 'develop.activity', 75 | ]; 76 | $sdk = new Goconfd($config); 77 | $kv = $sdk->get("develop.activity.k1"); 78 | if ($kv) { 79 | echo $kv->getValue(); 80 | } 81 | 82 | 2, GO 83 | 84 | go get -u github.com/Leon2012/goconfd 85 | import( 86 | "github.com/Leon2012/goconfd/sdk" 87 | "fmt" 88 | ) 89 | gconfd, err := sdk.NewGoconfd() 90 | if err != nil { 91 | fmt.Println(err) 92 | } 93 | k, err := gconfd.Get("develop.activity.k1") 94 | if err != nil { 95 | fmt.Println(err) 96 | }else{ 97 | fmt.Println(k.Value) 98 | } -------------------------------------------------------------------------------- /agent/loader.go: -------------------------------------------------------------------------------- 1 | package agent 2 | 3 | import ( 4 | "errors" 5 | _ "time" 6 | 7 | "github.com/Leon2012/goconfd/libs/kv" 8 | "github.com/Leon2012/goconfd/libs/util" 9 | _ "golang.org/x/net/context" 10 | ) 11 | 12 | //更新本地数据 13 | func UpdateLocalVales(ctx *Context) error { 14 | files, err := util.GetFileList(ctx.Agent.opts.SavePath, "php") 15 | if err != nil { 16 | return err 17 | } 18 | for _, f := range files { 19 | encodeKey := util.GetName(f) 20 | decodeKey, err := util.UnHexKey(encodeKey) 21 | if err != nil { 22 | ctx.Agent.logf("decode hex key : %s faile", err.Error()) 23 | break 24 | } 25 | k, err := LoadValueByKey(ctx, decodeKey) 26 | if err != nil { 27 | ctx.Agent.logf("load local key :%s faile", encodeKey) 28 | break 29 | } else { 30 | //ctx.Agent.local.Save(k) 31 | ctx.Agent.watchKVChan <- k 32 | ctx.Agent.logf("load local key : %s success", encodeKey) 33 | } 34 | } 35 | return nil 36 | } 37 | 38 | //监控数据 39 | func WatchValuesByPrefix(ctx *Context) { 40 | //var err error 41 | keyPrefix := ctx.Agent.opts.KeyPrefix 42 | ctx.Agent.idc.WatchWithPrefix(keyPrefix, func(k *kv.Kv, prefix string) bool { 43 | // kp := &KvPair{ 44 | // KeyPrefix: keyPrefix, 45 | // Kv: k, 46 | // } 47 | ctx.Agent.watchKVChan <- k 48 | return true 49 | }) 50 | } 51 | 52 | //监控多前缀数据 53 | func WatchValuesByPrefixs(ctx *Context, prefixs []string) { 54 | ctx.Agent.idc.WatchWithPrefixs(prefixs, func(k *kv.Kv, prefix string) bool { 55 | ctx.Agent.watchKVChan <- k 56 | return true 57 | }) 58 | } 59 | 60 | //监控单key数据 61 | func WatchValuesByKey(ctx *Context, key string) { 62 | ctx.Agent.idc.WatchWithKey(key, func(k *kv.Kv, prefix string) bool { 63 | ctx.Agent.watchKVChan <- k 64 | return true 65 | }) 66 | } 67 | 68 | //加载远端数据 69 | func LoadValueByKey(ctx *Context, key string) (*kv.Kv, error) { 70 | var err error 71 | kvs, err := ctx.Agent.idc.Get(key) 72 | if err != nil { 73 | return nil, err 74 | } else { 75 | if len(kvs) > 0 { 76 | k := kvs[0] 77 | ctx.Agent.watchKVChan <- k 78 | return k, nil 79 | } else { 80 | return nil, errors.New("Not Found") 81 | } 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /agent/http.go: -------------------------------------------------------------------------------- 1 | package agent 2 | 3 | import ( 4 | "fmt" 5 | "net" 6 | "net/http" 7 | _ "strings" 8 | 9 | "github.com/Leon2012/goconfd/libs/kv" 10 | "github.com/julienschmidt/httprouter" 11 | ) 12 | 13 | type httpServer struct { 14 | ctx *Context 15 | router http.Handler 16 | } 17 | 18 | func newHttpServer(ctx *Context) *httpServer { 19 | router := httprouter.New() 20 | router.HandleMethodNotAllowed = true 21 | s := &httpServer{ 22 | ctx: ctx, 23 | router: router, 24 | } 25 | router.Handle("GET", "/ping", s.Ping) 26 | router.Handle("GET", "/get/:key", s.DoGet) 27 | router.Handle("GET", "/info", s.DoInfo) 28 | return s 29 | } 30 | 31 | func (h *httpServer) serve(listener net.Listener) { 32 | h.ctx.Agent.logf("http server listening on %s", listener.Addr()) 33 | server := &http.Server{ 34 | Handler: h, 35 | //ErrorLog: h.ctx.Agent.opts.Logger, 36 | } 37 | err := server.Serve(listener) 38 | if err != nil { 39 | h.ctx.Agent.logf("ERROR: http.Server() - %s", err) 40 | } 41 | h.ctx.Agent.logf("http server closing %s", listener.Addr()) 42 | } 43 | 44 | func (h *httpServer) ServeHTTP(w http.ResponseWriter, req *http.Request) { 45 | h.router.ServeHTTP(w, req) 46 | } 47 | 48 | func (h *httpServer) Ping(w http.ResponseWriter, r *http.Request, ps httprouter.Params) { 49 | fmt.Fprint(w, "Ok\n") 50 | } 51 | 52 | func (h *httpServer) DoInfo(w http.ResponseWriter, r *http.Request, ps httprouter.Params) { 53 | m := make(map[string]string) 54 | if h.ctx.Agent.lastHeartbeat != nil { 55 | m["lastUpdateKey"] = h.ctx.Agent.lastHeartbeat.Kv.Key 56 | m["lastUpdateValue"] = h.ctx.Agent.lastHeartbeat.Kv.Value 57 | m["lastUpdateTime"] = h.ctx.Agent.lastHeartbeat.UpdateTime.Format("2006-01-02 15:04:05") 58 | } 59 | fmt.Fprint(w, m) 60 | } 61 | 62 | func (h *httpServer) DoGet(w http.ResponseWriter, r *http.Request, ps httprouter.Params) { 63 | key := ps.ByName("key") 64 | k, err := LoadValueByKey(h.ctx, key) 65 | if err != nil { 66 | http.Error(w, err.Error(), 404) 67 | } else { 68 | data, err := kv.JsonEncode(k) 69 | if err != nil { 70 | http.Error(w, err.Error(), 404) 71 | } else { 72 | w.Header().Set("Content-Type", "application/json") 73 | fmt.Fprint(w, string(data)) 74 | } 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /libs/ringhash/ringhash.go: -------------------------------------------------------------------------------- 1 | package ringhash 2 | 3 | import ( 4 | "hash/crc32" 5 | "log" 6 | "sort" 7 | "strconv" 8 | ) 9 | 10 | type Hash func(data []byte) uint32 11 | 12 | type elem struct { 13 | key string 14 | hash int 15 | } 16 | 17 | type sortable []elem 18 | 19 | func (k sortable) Len() int { return len(k) } 20 | func (k sortable) Swap(i, j int) { k[i], k[j] = k[j], k[i] } 21 | func (k sortable) Less(i, j int) bool { 22 | // Weak hash function may cause collisions. 23 | if k[i].hash < k[j].hash { 24 | return true 25 | } else if k[i].hash == k[j].hash { 26 | return k[i].key < k[j].key 27 | } else { 28 | return false 29 | } 30 | } 31 | 32 | type Ring struct { 33 | keys []elem // Sorted list of keys. 34 | 35 | replicas int 36 | hashfunc Hash 37 | } 38 | 39 | func New(replicas int, fn Hash) *Ring { 40 | ring := &Ring{ 41 | replicas: replicas, 42 | hashfunc: fn, 43 | } 44 | if ring.hashfunc == nil { 45 | ring.hashfunc = crc32.ChecksumIEEE 46 | } 47 | return ring 48 | } 49 | 50 | // Returns the number of keys in the ring. 51 | func (ring *Ring) Len() int { 52 | return len(ring.keys) 53 | } 54 | 55 | // Adds keys to the ring. 56 | func (ring *Ring) Add(keys ...string) { 57 | for _, key := range keys { 58 | for i := 0; i < ring.replicas; i++ { 59 | ring.keys = append(ring.keys, elem{ 60 | hash: int(ring.hashfunc([]byte(strconv.Itoa(i) + key))), 61 | key: key}) 62 | } 63 | } 64 | sort.Sort(sortable(ring.keys)) 65 | } 66 | 67 | // Get returns the closest item in the ring to the provided key. 68 | func (ring *Ring) Get(key string) string { 69 | 70 | if ring.Len() == 0 { 71 | return "" 72 | } 73 | 74 | hash := int(ring.hashfunc([]byte(key))) 75 | 76 | // Binary search for appropriate replica. 77 | idx := sort.Search(len(ring.keys), func(i int) bool { 78 | el := ring.keys[i] 79 | return (el.hash > hash) || (el.hash == hash && el.key >= key) 80 | //return (ring.keys[i].hash > hash) || (ring.keys[i].hash == hash && ring.keys[i].key >= key) 81 | }) 82 | 83 | // Means we have cycled back to the first replica. 84 | if idx == len(ring.keys) { 85 | idx = 0 86 | } 87 | 88 | return ring.keys[idx].key 89 | } 90 | 91 | func (ring *Ring) dump() { 92 | for _, e := range ring.keys { 93 | log.Printf("key: '%s', hash=%d", e.key, e.hash) 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /libs/net2/net.go: -------------------------------------------------------------------------------- 1 | package net2 2 | 3 | import ( 4 | "log" 5 | "net" 6 | "regexp" 7 | "strings" 8 | ) 9 | 10 | func CheckIp(ip string) bool { 11 | if m, _ := regexp.MatchString("^[0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3}(\\:[0-9]{1,5})?$", ip); !m { 12 | return false 13 | } 14 | return true 15 | } 16 | 17 | func GetLocalIPv4() (string, error) { 18 | ips, err := GetLocalIPs() 19 | if err != nil || len(ips) == 0 { 20 | return "", err 21 | } 22 | ip := ips[0] 23 | return ip, nil 24 | } 25 | 26 | func GetLocalIPs() ([]string, error) { 27 | ifaces, err := net.Interfaces() 28 | if err != nil { 29 | return nil, err 30 | } 31 | ips := []string{} 32 | for _, i := range ifaces { 33 | addrs, _ := i.Addrs() 34 | for _, a := range addrs { 35 | if ipnet, ok := a.(*net.IPNet); ok && !ipnet.IP.IsLoopback() { 36 | if ipnet.IP.To4() != nil { 37 | ip := ipnet.IP.String() 38 | ips = append(ips, ip) 39 | //fmt.Printf("%v : %s (%s)\n", i.Name, ipnet.IP.String(), ipnet.IP.DefaultMask()) 40 | } 41 | } 42 | } 43 | } 44 | return ips, nil 45 | } 46 | 47 | func ParseHosts(hosts string) []string { 48 | results := []string{} 49 | idx := strings.Index(hosts, ";") 50 | if idx == -1 { 51 | results = append(results, hosts) 52 | } else { 53 | hs := strings.Split(hosts, ";") 54 | for _, h := range hs { 55 | h = strings.TrimSpace(h) 56 | if CheckIp(h) { 57 | results = append(results, h) 58 | } 59 | } 60 | } 61 | return results 62 | } 63 | 64 | // LocalIP tries to determine a non-loopback address for the local machine 65 | func LocalIP() (net.IP, error) { 66 | addrs, err := net.InterfaceAddrs() 67 | if err != nil { 68 | return nil, err 69 | } 70 | for _, addr := range addrs { 71 | if ipnet, ok := addr.(*net.IPNet); ok && ipnet.IP.IsGlobalUnicast() { 72 | if ipnet.IP.To4() != nil || ipnet.IP.To16() != nil { 73 | return ipnet.IP, nil 74 | } 75 | } 76 | } 77 | return nil, nil 78 | } 79 | 80 | func LocalIPString() string { 81 | ip, err := LocalIP() 82 | if err != nil { 83 | log.Print("[WARN] Error determining local ip address. ", err) 84 | return "" 85 | } 86 | if ip == nil { 87 | log.Print("[WARN] Could not determine local ip address") 88 | return "" 89 | } 90 | return ip.String() 91 | } 92 | -------------------------------------------------------------------------------- /cmd/dashboard/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "flag" 5 | "fmt" 6 | "log" 7 | "os" 8 | _ "path/filepath" 9 | "syscall" 10 | 11 | "github.com/BurntSushi/toml" 12 | "github.com/Leon2012/goconfd/dashboard" 13 | "github.com/Leon2012/goconfd/libs/version" 14 | "github.com/judwhite/go-svc/svc" 15 | "github.com/mreiferson/go-options" 16 | ) 17 | 18 | var ( 19 | flagSet = flag.NewFlagSet("dashboard", flag.ExitOnError) 20 | config = flagSet.String("config", "", "config file") 21 | showVersion = flagSet.Bool("version", false, "show version") 22 | httpAddress = flagSet.String("http_address", "0.0.0.0:3003", "http address") 23 | dbUrl = flagSet.String("db_url", "127.0.0.1:27017", "mongodb url") 24 | dbTimeout = flagSet.Int("db_timeout", 5, "mongodb timeout") 25 | dbName = flagSet.String("db_name", "goconfd", "mongodb name") 26 | dbUser = flagSet.String("db_user", "", "mongodb user ") 27 | dbPass = flagSet.String("db_pass", "", "mongodb pass") 28 | hosts = flagSet.String("hosts", "localhost:2379", "etcd hosts") 29 | dialTimeout = flagSet.Int("dial_timeout", 5, "dial timeout") 30 | requestTimeout = flagSet.Int("request_timeout", 5, "request timeout") 31 | templatePath = flagSet.String("template_path", "./template", "template path") 32 | ) 33 | 34 | type program struct { 35 | dashboard *dashboard.Dashboard 36 | } 37 | 38 | func main() { 39 | prg := &program{} 40 | if err := svc.Run(prg, syscall.SIGINT, syscall.SIGTERM); err != nil { 41 | log.Fatal(err) 42 | } 43 | } 44 | 45 | func (p *program) Init(env svc.Environment) error { 46 | return nil 47 | } 48 | 49 | func (p *program) Stop() error { 50 | p.dashboard.Exit() 51 | return nil 52 | } 53 | 54 | func (p *program) Start() error { 55 | flagSet.Parse(os.Args[1:]) 56 | if *showVersion { 57 | fmt.Println(version.String("dashboard")) 58 | os.Exit(0) 59 | } 60 | 61 | var cfg map[string]interface{} 62 | if *config != "" { 63 | _, err := toml.DecodeFile(*config, &cfg) 64 | if err != nil { 65 | log.Fatal("ERROR: failed to load config file %s - %s", *config, err.Error()) 66 | return err 67 | } 68 | } 69 | 70 | opts := dashboard.NewOptions() 71 | options.Resolve(opts, flagSet, cfg) 72 | log.Println(opts.String()) 73 | err := opts.Valid() 74 | if err != nil { 75 | return err 76 | } 77 | daemon := dashboard.NewDashboard(opts) 78 | daemon.Main() 79 | p.dashboard = daemon 80 | return nil 81 | } 82 | -------------------------------------------------------------------------------- /cmd/monitor/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "flag" 5 | "fmt" 6 | "log" 7 | "os" 8 | _ "path/filepath" 9 | "syscall" 10 | 11 | "github.com/BurntSushi/toml" 12 | "github.com/Leon2012/goconfd/libs/version" 13 | "github.com/Leon2012/goconfd/monitor" 14 | "github.com/judwhite/go-svc/svc" 15 | "github.com/mreiferson/go-options" 16 | ) 17 | 18 | var ( 19 | flagSet = flag.NewFlagSet("monitor", flag.ExitOnError) 20 | config = flagSet.String("config", "", "config file") 21 | showVersion = flagSet.Bool("version", false, "show version") 22 | rpcAddress = flagSet.String("rpc_address", "0.0.0.0:3002", "rpc address") 23 | dbUrl = flagSet.String("db_url", "127.0.0.1:27017", "mongodb url") 24 | dbTimeout = flagSet.Int("db_timeout", 5, "mongodb timeout") 25 | dbName = flagSet.String("db_name", "goconfd", "mongodb name") 26 | dbUser = flagSet.String("db_user", "", "mongodb user ") 27 | dbPass = flagSet.String("db_pass", "", "mongodb pass") 28 | dialTimeout = flagSet.Int("dial_timeout", 5, "dial timeout") 29 | requestTimeout = flagSet.Int("request_timeout", 5, "request timeout") 30 | hosts = flagSet.String("hosts", "localhost:2379", "etcd hosts") 31 | heartbeatInterval = flagSet.Int("heartbeat_interval", 5, "heartbeat interval") 32 | ) 33 | 34 | type program struct { 35 | monitor *monitor.Monitor 36 | } 37 | 38 | func main() { 39 | prg := &program{} 40 | if err := svc.Run(prg, syscall.SIGINT, syscall.SIGTERM); err != nil { 41 | log.Fatal(err) 42 | } 43 | } 44 | 45 | func (p *program) Init(env svc.Environment) error { 46 | return nil 47 | } 48 | 49 | func (p *program) Stop() error { 50 | p.monitor.Exit() 51 | return nil 52 | } 53 | 54 | func (p *program) Start() error { 55 | flagSet.Parse(os.Args[1:]) 56 | if *showVersion { 57 | fmt.Println(version.String("monitor")) 58 | os.Exit(0) 59 | } 60 | 61 | var cfg map[string]interface{} 62 | if *config != "" { 63 | _, err := toml.DecodeFile(*config, &cfg) 64 | if err != nil { 65 | log.Fatal("ERROR: failed to load config file %s - %s", *config, err.Error()) 66 | return err 67 | } 68 | } 69 | 70 | opts := monitor.NewOptions() 71 | options.Resolve(opts, flagSet, cfg) 72 | log.Println(opts.String()) 73 | err := opts.Valid() 74 | if err != nil { 75 | return err 76 | } 77 | daemon := monitor.NewMonitor(opts) 78 | daemon.Main() 79 | p.monitor = daemon 80 | return nil 81 | } 82 | -------------------------------------------------------------------------------- /dashboard/template/public_html/heartbeat.html: -------------------------------------------------------------------------------- 1 | {{template "layout" .}} 2 | {{define "content"}} 3 |
4 | 5 |
6 |
7 |

8 | 心跳列表 9 |

10 | 18 |
19 |
20 | 21 | 22 |
23 |
24 |

心跳列表

25 |
26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | {{range .}} 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | {{end}} 46 | 47 |
NameKey PrefixLatestKeyLatestValueLatestTime
{{.HostName}}{{.KeyPrefix}}{{.LatestKey}}{{.LatestValue}}{{.LatestTime}}
48 |
49 |
50 | 51 |
52 | 53 | 54 |
55 | 56 | {{end}} -------------------------------------------------------------------------------- /dashboard/template/public_html/agent.html: -------------------------------------------------------------------------------- 1 | {{template "layout" .}} 2 | {{define "content"}} 3 |
4 | 5 |
6 |
7 |

8 | Agents列表 9 |

10 | 18 |
19 |
20 | 21 | 22 |
23 |
24 |

Agents列表

25 |
26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | {{range .}} 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | {{end}} 46 | 47 |
NameKey PrefixIP最后心跳时间Status
{{.HostName}}{{.KeyPrefix}}{{.IpAddress}}{{.HeartbeatTime}}{{.Status}}
48 |
49 |
50 | 51 |
52 | 53 | 54 |
55 | 56 | {{end}} -------------------------------------------------------------------------------- /dashboard/dashboard.go: -------------------------------------------------------------------------------- 1 | package dashboard 2 | 3 | import ( 4 | "fmt" 5 | "net" 6 | "os" 7 | 8 | "sync" 9 | 10 | "github.com/Leon2012/goconfd/libs/util" 11 | "github.com/Leon2012/goconfd/libs/version" 12 | "github.com/Leon2012/goconfd/registry" 13 | "github.com/Leon2012/goconfd/registry/backend" 14 | "github.com/Leon2012/goconfd/store/db" 15 | "github.com/Leon2012/goconfd/store/db/mongo" 16 | ) 17 | 18 | type Dashboard struct { 19 | sync.RWMutex 20 | opts *Options 21 | httpListener net.Listener 22 | waitGroup util.WaitGroupWrapper 23 | dbConfig mongo.MongoConfig 24 | db db.Adapter 25 | idc registry.Backend 26 | } 27 | 28 | func NewDashboard(o *Options) *Dashboard { 29 | d := &Dashboard{ 30 | opts: o, 31 | } 32 | d.logf(version.String("dashboard")) 33 | return d 34 | } 35 | 36 | func (a *Dashboard) logf(f string, args ...interface{}) { 37 | if a.opts.Logger == nil { 38 | return 39 | } 40 | a.opts.Logger.Output(2, fmt.Sprintf(f, args...)) 41 | } 42 | 43 | func (a *Dashboard) Main() { 44 | ctx := &Context{ 45 | Dashboard: a, 46 | } 47 | a.Lock() 48 | a.dbConfig = mongo.MongoConfig{ 49 | Url: a.opts.DBUrl, 50 | DbName: a.opts.DBName, 51 | Timeout: a.opts.DBTimeout, 52 | Username: a.opts.DBUser, 53 | Password: a.opts.DBPass, 54 | } 55 | a.db = mongo.NewMongoAdapter() 56 | a.Unlock() 57 | err := a.db.Open(a.dbConfig) 58 | if err != nil { 59 | a.logf("FATAL: open db failed - %s", err) 60 | os.Exit(1) 61 | } 62 | 63 | //初始化etcd client 64 | if len(a.opts.Hosts) == 0 { 65 | a.logf("FATAL: etcd host empty") 66 | os.Exit(1) 67 | } 68 | cli, err := backend.NewEtcdAdpater(a.opts.ParseHosts(), a.opts.DialTimeout, a.opts.RequestTimeout) 69 | if err != nil { 70 | a.logf("FATAL: create etcd client failed - %s", err.Error()) 71 | os.Exit(1) 72 | } 73 | a.Lock() 74 | a.idc = cli 75 | a.Unlock() 76 | 77 | httpListener, err := net.Listen("tcp", a.opts.HttpAddress) 78 | if err != nil { 79 | a.logf("FATAL: listen (%s) failed - %s", a.opts.HttpAddress, err) 80 | os.Exit(1) 81 | } 82 | a.Lock() 83 | a.httpListener = httpListener 84 | a.Unlock() 85 | httpServer := newHttpServer(ctx, a.opts.TemplatePath) 86 | a.waitGroup.Wrap(func() { 87 | httpServer.serve(a.httpListener) 88 | }) 89 | } 90 | 91 | func (a *Dashboard) Exit() { 92 | if a.db != nil { 93 | a.db.Close() 94 | } 95 | if a.httpListener != nil { 96 | a.httpListener.Close() 97 | } 98 | a.waitGroup.Wait() 99 | } 100 | -------------------------------------------------------------------------------- /cmd/agent/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "flag" 5 | "fmt" 6 | "log" 7 | "os" 8 | _ "path/filepath" 9 | "syscall" 10 | 11 | "github.com/BurntSushi/toml" 12 | "github.com/Leon2012/goconfd/agent" 13 | "github.com/Leon2012/goconfd/libs/version" 14 | "github.com/judwhite/go-svc/svc" 15 | "github.com/mreiferson/go-options" 16 | ) 17 | 18 | var ( 19 | flagSet = flag.NewFlagSet("agent", flag.ExitOnError) 20 | config = flagSet.String("config", "", "config file") 21 | showVersion = flagSet.Bool("version", false, "show version") 22 | httpAddress = flagSet.String("http_address", "0.0.0.0:3001", "http address") 23 | dialTimeout = flagSet.Int("dial_timeout", 5, "dial timeout") 24 | requestTimeout = flagSet.Int("request_timeout", 5, "request timeout") 25 | keyPrefix = flagSet.String("key_prefix", "", "key prefix") 26 | savePath = flagSet.String("save_path", "/tmp/", "save path") 27 | saveType = flagSet.Int("save_type", 1, "1=file, 2=shm") 28 | hosts = flagSet.String("hosts", "localhost:2379", "etcd hosts") 29 | fileExt = flagSet.String("file_ext", "php", "file ext") 30 | autoLoad = flagSet.Bool("auto_load", false, "auto load local keys") 31 | heartbeatInterval = flagSet.Int("heartbeat_interval", 5, "heartbeat interval") 32 | ) 33 | 34 | type program struct { 35 | agent *agent.Agent 36 | } 37 | 38 | func main() { 39 | prg := &program{} 40 | if err := svc.Run(prg, syscall.SIGINT, syscall.SIGTERM); err != nil { 41 | log.Fatal(err) 42 | } 43 | } 44 | 45 | func (p *program) Init(env svc.Environment) error { 46 | //log.Printf("is win service? %v\n", env.IsWindowsService()) 47 | return nil 48 | } 49 | 50 | func (p *program) Stop() error { 51 | p.agent.Exit() 52 | return nil 53 | } 54 | 55 | func (p *program) Start() error { 56 | flagSet.Parse(os.Args[1:]) 57 | if *showVersion { 58 | fmt.Println(version.String("agent")) 59 | os.Exit(0) 60 | } 61 | 62 | var cfg map[string]interface{} 63 | if *config != "" { 64 | _, err := toml.DecodeFile(*config, &cfg) 65 | if err != nil { 66 | log.Fatal("ERROR: failed to load config file %s - %s", *config, err.Error()) 67 | return err 68 | } 69 | } 70 | 71 | opts := agent.NewOptions() 72 | options.Resolve(opts, flagSet, cfg) 73 | log.Println(opts.String()) 74 | err := opts.Valid() 75 | if err != nil { 76 | return err 77 | } 78 | daemon := agent.NewAgent(opts) 79 | daemon.Main() 80 | p.agent = daemon 81 | return nil 82 | } 83 | -------------------------------------------------------------------------------- /monitor/rpc.go: -------------------------------------------------------------------------------- 1 | package monitor 2 | 3 | import ( 4 | "net" 5 | "net/rpc" 6 | 7 | "github.com/Leon2012/goconfd/libs/protocol" 8 | "github.com/Leon2012/goconfd/store/types" 9 | ) 10 | 11 | type MonitorRpc struct { 12 | ctx *Context 13 | } 14 | 15 | func NewRpcServer(c *Context) *MonitorRpc { 16 | r := &MonitorRpc{ 17 | ctx: c, 18 | } 19 | return r 20 | } 21 | 22 | func (r *MonitorRpc) Ping(args *protocol.NoArg, reply *protocol.NoReply) error { 23 | return nil 24 | } 25 | 26 | func (r *MonitorRpc) Online(args *protocol.OnlineArg, reply *protocol.Ack) error { 27 | if args.HostName == "" || args.KeyPrefix == "" { 28 | reply.Code = 1001 29 | reply.Message = "hostname or keyprefix is empty" 30 | return nil 31 | } 32 | 33 | agent := &types.Agent{} 34 | agent.HostName = args.HostName 35 | agent.KeyPrefix = args.KeyPrefix 36 | agent.IpAddress = args.IpAddress 37 | r.ctx.Monitor.logf("INFO: call Online func, hostName-keyPrefix(%s-%s)", agent.HostName, agent.KeyPrefix) 38 | err := r.ctx.Monitor.db.Online(agent) 39 | if err != nil { 40 | return err 41 | } 42 | reply.Code = 1000 43 | reply.Message = "" 44 | return nil 45 | } 46 | 47 | func (r *MonitorRpc) Offline(args *protocol.OfflineArg, reply *protocol.Ack) error { 48 | if args.HostName == "" || args.KeyPrefix == "" { 49 | reply.Code = 1001 50 | reply.Message = "hostname or keyprefix is empty" 51 | return nil 52 | } 53 | agent := &types.Agent{} 54 | agent.HostName = args.HostName 55 | agent.KeyPrefix = args.KeyPrefix 56 | r.ctx.Monitor.logf("INFO: call Online func, hostName-keyPrefix(%s-%s)", agent.HostName, agent.KeyPrefix) 57 | err := r.ctx.Monitor.db.Offline(agent) 58 | if err != nil { 59 | return err 60 | } 61 | reply.Code = 1000 62 | reply.Message = "" 63 | return nil 64 | } 65 | 66 | func (r *MonitorRpc) Heartbeat(args *protocol.HeartbeatArg, reply *protocol.HeartbeatReply) error { 67 | r.ctx.Monitor.logf("INFO: call Heartbeat func, hostName-keyPrefix(%s-%s)", args.HostName, args.KeyPrefix) 68 | 69 | heartbeat := &types.Heartbeat{} 70 | heartbeat.HostName = args.HostName 71 | heartbeat.KeyPrefix = args.KeyPrefix 72 | heartbeat.LatestKey = args.Key 73 | heartbeat.LatestValue = args.Value 74 | heartbeat.LatestTime = args.Time 75 | err := r.ctx.Monitor.db.Heartbeat(heartbeat) 76 | if err != nil { 77 | r.ctx.Monitor.logf("ERROR: call heartbeat faile - %s", err) 78 | return err 79 | } 80 | return nil 81 | } 82 | 83 | func (r *MonitorRpc) serve(lis net.Listener) error { 84 | rpc.Register(r) 85 | go func() { 86 | for { 87 | conn, err := lis.Accept() 88 | if err != nil { 89 | continue 90 | } 91 | go rpc.ServeConn(conn) 92 | } 93 | }() 94 | 95 | return nil 96 | } 97 | -------------------------------------------------------------------------------- /monitor/monitor.go: -------------------------------------------------------------------------------- 1 | package monitor 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | "sync" 7 | 8 | "net" 9 | 10 | "github.com/Leon2012/goconfd/libs/util" 11 | "github.com/Leon2012/goconfd/libs/version" 12 | "github.com/Leon2012/goconfd/store/db" 13 | "github.com/Leon2012/goconfd/store/db/mongo" 14 | ) 15 | 16 | type Monitor struct { 17 | sync.RWMutex 18 | opts *Options 19 | tcpListener net.Listener 20 | waitGroup util.WaitGroupWrapper 21 | dbConfig mongo.MongoConfig 22 | db db.Adapter 23 | hostName string 24 | service *Service 25 | } 26 | 27 | func NewMonitor(o *Options) *Monitor { 28 | m := &Monitor{ 29 | opts: o, 30 | } 31 | m.logf(version.String("monitor")) 32 | return m 33 | } 34 | 35 | func (a *Monitor) logf(f string, args ...interface{}) { 36 | if a.opts.Logger == nil { 37 | return 38 | } 39 | a.opts.Logger.Output(2, fmt.Sprintf(f, args...)) 40 | } 41 | 42 | func (a *Monitor) Main() { 43 | ctx := &Context{a} 44 | 45 | hostName, err := util.GetHostName() 46 | if err != nil { 47 | a.logf("FATAL: get host name faile") 48 | os.Exit(1) 49 | } 50 | a.Lock() 51 | a.hostName = hostName 52 | a.Unlock() 53 | 54 | a.Lock() 55 | a.dbConfig = mongo.MongoConfig{ 56 | Url: a.opts.DBUrl, 57 | DbName: a.opts.DBName, 58 | Timeout: a.opts.DBTimeout, 59 | Username: a.opts.DBUser, 60 | Password: a.opts.DBPass, 61 | } 62 | a.db = mongo.NewMongoAdapter() 63 | a.Unlock() 64 | err = a.db.Open(a.dbConfig) 65 | if err != nil { 66 | a.logf("FATAL: open db failed - %s", err) 67 | os.Exit(1) 68 | } 69 | 70 | service, err := NewService(ctx) 71 | if err != nil { 72 | a.logf("FATAL: create service failed - %s", err.Error()) 73 | os.Exit(1) 74 | } 75 | err = service.Register() 76 | if err != nil { 77 | a.logf("FATAL: register service failed - %s", err.Error()) 78 | os.Exit(1) 79 | } 80 | a.Lock() 81 | a.service = service 82 | a.Unlock() 83 | a.waitGroup.Wrap(func() { 84 | a.service.Heartbeat() 85 | }) 86 | 87 | tcpListener, err := net.Listen("tcp", a.opts.RpcAddress) 88 | if err != nil { 89 | a.logf("FATAL: listen (%s) failed - %s", a.opts.RpcAddress, err) 90 | os.Exit(1) 91 | } 92 | a.Lock() 93 | a.tcpListener = tcpListener 94 | a.Unlock() 95 | rpcServer := NewRpcServer(ctx) 96 | a.waitGroup.Wrap(func() { 97 | rpcServer.serve(a.tcpListener) 98 | a.logf("INFO: rpc server listen(%s) success", a.opts.RpcAddress) 99 | }) 100 | } 101 | 102 | func (a *Monitor) Exit() { 103 | a.service.Deregister() 104 | if a.db != nil { 105 | a.db.Close() 106 | } 107 | if a.tcpListener != nil { 108 | a.tcpListener.Close() 109 | } 110 | a.waitGroup.Wait() 111 | } 112 | -------------------------------------------------------------------------------- /monitor/service.go: -------------------------------------------------------------------------------- 1 | package monitor 2 | 3 | import ( 4 | "fmt" 5 | "runtime" 6 | "time" 7 | 8 | "errors" 9 | 10 | "github.com/Leon2012/goconfd/libs/net2" 11 | "github.com/Leon2012/goconfd/libs/node" 12 | "github.com/Leon2012/goconfd/registry" 13 | "github.com/Leon2012/goconfd/registry/backend" 14 | ) 15 | 16 | const ( 17 | defaultSystemMonitorNodeKey = "/system/monitor/nodes/" 18 | ) 19 | 20 | type Service struct { 21 | ctx *Context 22 | info *node.Node 23 | heartbeatInterval int64 24 | ttlMax int 25 | ttlFailCnt int 26 | heartbeatTicker *time.Ticker 27 | backend registry.Backend 28 | exitChan chan int 29 | exit bool 30 | } 31 | 32 | func NewService(ctx *Context) (*Service, error) { 33 | s := &Service{ 34 | ctx: ctx, 35 | info: &node.Node{ 36 | ServiceAddress: ctx.Monitor.opts.RpcAddress, 37 | CPU: runtime.NumCPU(), 38 | IP: net2.LocalIPString(), 39 | }, 40 | heartbeatInterval: int64(ctx.Monitor.opts.HeartbeatInterval), 41 | ttlMax: 3, 42 | ttlFailCnt: 0, 43 | exitChan: make(chan int), 44 | exit: false, 45 | } 46 | hosts := ctx.Monitor.opts.ParseHosts() 47 | cli, err := backend.NewEtcdAdpater(hosts, ctx.Monitor.opts.DialTimeout, ctx.Monitor.opts.RequestTimeout) 48 | if err != nil { 49 | return nil, err 50 | } 51 | s.backend = cli 52 | s.info.Name = s.key() 53 | return s, nil 54 | } 55 | 56 | func (a *Service) key() string { 57 | return fmt.Sprintf("%s/%s/%s", defaultSystemMonitorNodeKey, a.ctx.Monitor.hostName, a.ctx.Monitor.opts.RpcAddress) 58 | } 59 | 60 | //心跳 61 | func (a *Service) doHeartbeat() { 62 | key := a.key() 63 | err := a.backend.SetTTL(key) 64 | if err != nil { 65 | a.ttlFailCnt++ 66 | } else { 67 | a.ttlFailCnt = 0 68 | } 69 | if a.ttlFailCnt >= a.ttlMax { 70 | a.Deregister() 71 | } 72 | } 73 | 74 | func (a *Service) Heartbeat() { 75 | d := time.Duration(a.heartbeatInterval) * time.Second 76 | a.heartbeatTicker = time.NewTicker(d) 77 | defer func() { 78 | a.heartbeatTicker.Stop() 79 | }() 80 | for { 81 | select { 82 | case <-a.exitChan: 83 | return 84 | case <-a.heartbeatTicker.C: 85 | a.doHeartbeat() 86 | } 87 | } 88 | } 89 | 90 | //服务注册 91 | func (a *Service) Register() error { 92 | key := a.key() 93 | err := a.backend.PutWithTTL(key, a.info.String(), (a.heartbeatInterval * int64(a.ttlMax))) 94 | return err 95 | } 96 | 97 | //服务注销 98 | func (a *Service) Deregister() error { 99 | if !a.exit { 100 | a.exit = true 101 | key := a.key() 102 | err := a.backend.Del(key) 103 | a.backend.Close() 104 | close(a.exitChan) 105 | return err 106 | } 107 | return errors.New("service is exit") 108 | } 109 | 110 | func (a *Service) WatchNodes() { 111 | 112 | } 113 | -------------------------------------------------------------------------------- /libs/kv/php.go: -------------------------------------------------------------------------------- 1 | package kv 2 | 3 | import ( 4 | "bytes" 5 | "crypto/md5" 6 | "encoding/hex" 7 | "errors" 8 | "regexp" 9 | "strconv" 10 | "strings" 11 | 12 | "github.com/Leon2012/goconfd/libs/reflect" 13 | "github.com/Leon2012/goconfd/libs/util" 14 | ) 15 | 16 | const PHP_CODE_PREFIX = " 4 | // #include 5 | // #include 6 | // #include 7 | /* 8 | int my_shm_open(char* filename, int open_flag){ 9 | int shm_id; 10 | key_t key; 11 | key = ftok(filename, 0x01); 12 | if(key == -1){ 13 | return -1; 14 | } 15 | if(open_flag) 16 | shm_id = shmget(key, 4096, IPC_CREAT|IPC_EXCL|0666); 17 | else 18 | shm_id = shmget(key, 0, 0); 19 | if(shm_id == -1){ 20 | return -1; 21 | } 22 | return shm_id; 23 | } 24 | 25 | int my_shm_update(int shm_id, char* content){ 26 | char* addr; 27 | addr = (char*)shmat(shm_id, NULL, 0); 28 | if(addr == (char*)-1){ 29 | return -1; 30 | } 31 | if(strlen(content) > 4095) 32 | return -1; 33 | strcpy(addr, content); 34 | shmdt(addr); 35 | return 0; 36 | } 37 | 38 | int my_shm_del(char* filename){ 39 | int shm_id; 40 | char* addr; 41 | char* s; 42 | shm_id = my_shm_open(filename, 0); 43 | if(shm_id == -1){ 44 | return -1; 45 | } 46 | shmctl(shm_id, IPC_RMID, NULL); 47 | return 0; 48 | } 49 | 50 | char* my_shm_read(char* filename){ 51 | int shm_id; 52 | char* addr; 53 | char* s; 54 | shm_id = my_shm_open(filename, 0); 55 | if(shm_id == -1) 56 | return NULL; 57 | addr = (char*)shmat(shm_id, NULL, 0); 58 | if(addr == (char*)-1){ 59 | return NULL; 60 | } 61 | s = (char*)malloc(strlen(addr) + 1); 62 | strcpy(s, addr); 63 | shmdt(addr); 64 | return s; 65 | } 66 | */ 67 | import "C" 68 | 69 | import "unsafe" 70 | 71 | import ( 72 | _ "log" 73 | "os" 74 | _ "time" 75 | 76 | "github.com/Leon2012/goconfd/libs/util" 77 | ) 78 | 79 | type errorString struct { 80 | s string 81 | } 82 | 83 | func (e *errorString) Error() string { 84 | return e.s 85 | } 86 | 87 | func Open(filename string) (int, error) { 88 | if util.IsExist(filename) { 89 | Del(filename) 90 | } 91 | //filename := filepath.Join("/tmp", file) 92 | fp, err := os.OpenFile(filename, os.O_RDWR|os.O_CREATE, 0666) 93 | if err != nil { 94 | return 0, err 95 | } 96 | defer fp.Close() 97 | f := C.CString(filename) 98 | defer C.free(unsafe.Pointer(f)) 99 | r := int(C.my_shm_open(f, C.int(1))) 100 | if r == -1 { 101 | return 0, &errorString{"Open shm error"} 102 | } 103 | return r, nil 104 | } 105 | 106 | func Write(shm_id int, content string) error { 107 | c := C.CString(content) 108 | defer C.free(unsafe.Pointer(c)) 109 | r := int(C.my_shm_update(C.int(shm_id), c)) 110 | if r == -1 { 111 | return &errorString{"Write error"} 112 | } 113 | return nil 114 | } 115 | 116 | func Read(filename string) string { 117 | //filename := filepath.Join("/tmp", file) 118 | f := C.CString(filename) 119 | defer C.free(unsafe.Pointer(f)) 120 | s := C.my_shm_read(f) 121 | defer C.free(unsafe.Pointer(s)) 122 | return C.GoString(s) 123 | } 124 | 125 | func Del(filename string) error { 126 | //C.my_shm_del(C.int(shm_id)) 127 | //filename := filepath.Join("/tmp", file) 128 | f := C.CString(filename) 129 | defer C.free(unsafe.Pointer(f)) 130 | C.my_shm_del(f) 131 | err := os.Remove(filename) 132 | return err 133 | } 134 | -------------------------------------------------------------------------------- /agent/rpc_client.go: -------------------------------------------------------------------------------- 1 | package agent 2 | 3 | import ( 4 | "time" 5 | 6 | "errors" 7 | 8 | "github.com/Leon2012/goconfd/libs/client" 9 | "github.com/Leon2012/goconfd/libs/protocol" 10 | ) 11 | 12 | type RpcClient struct { 13 | client *client.RPCClient 14 | ctx *Context 15 | maxErrCnt, errCnt, maxRetries int 16 | } 17 | 18 | func NewRpcClient(c *Context) *RpcClient { 19 | key := c.Agent.hostName + "_" + c.Agent.opts.KeyPrefix 20 | selector := client.NewRingSelector(key) 21 | cl := &RpcClient{ 22 | ctx: c, 23 | maxErrCnt: 10, 24 | errCnt: 0, 25 | maxRetries: 5, 26 | } 27 | cl.client = client.NewRPCClient(selector) 28 | return cl 29 | } 30 | 31 | func (r *RpcClient) Online() error { 32 | var err error 33 | args := &protocol.OnlineArg{} 34 | args.HostName = r.ctx.Agent.hostName 35 | args.IpAddress = r.ctx.Agent.localIP 36 | args.KeyPrefix = r.ctx.Agent.opts.KeyPrefix 37 | var reply protocol.Ack 38 | err = r.doCall("MonitorRpc.Online", args, reply) 39 | if err != nil { 40 | return err 41 | } 42 | if reply.Code != 1000 { 43 | return errors.New(reply.Message) 44 | } 45 | return nil 46 | } 47 | 48 | func (r *RpcClient) Offline() error { 49 | var err error 50 | args := &protocol.OfflineArg{} 51 | args.HostName = r.ctx.Agent.hostName 52 | args.KeyPrefix = r.ctx.Agent.opts.KeyPrefix 53 | var reply protocol.Ack 54 | err = r.doCall("MonitorRpc.Offline", args, reply) 55 | if err != nil { 56 | return err 57 | } 58 | if reply.Code != 1000 { 59 | return errors.New(reply.Message) 60 | } 61 | return nil 62 | } 63 | 64 | func (r *RpcClient) Heartbeat(interval int) { 65 | var pp time.Duration 66 | pp = ((time.Duration(interval) * time.Second) * 9) / 10 67 | ticker := time.NewTicker(pp) //心跳包 68 | defer func() { 69 | ticker.Stop() 70 | }() 71 | for { 72 | select { 73 | case <-r.ctx.Agent.exitChan: 74 | r.ctx.Agent.logf("agent exit!!!!!!") 75 | return 76 | case <-ticker.C: //心跳 77 | r.doHeartbeat() 78 | } 79 | } 80 | } 81 | 82 | func (r *RpcClient) ReloadAddrs() bool { 83 | addrs := r.ctx.Agent.monitor.GetNodesAddr() 84 | r.client.SetAddrs(addrs) 85 | r.ctx.Agent.logf("set monitor rpc address : %s", addrs) 86 | return true 87 | } 88 | 89 | func (r *RpcClient) doHeartbeat() { 90 | r.ctx.Agent.logf("call doHeartbeat func!") 91 | args := &protocol.HeartbeatArg{} 92 | args.HostName = r.ctx.Agent.hostName 93 | args.KeyPrefix = r.ctx.Agent.opts.KeyPrefix 94 | if r.ctx.Agent.lastHeartbeat != nil { 95 | if r.ctx.Agent.lastHeartbeat.Kv != nil { 96 | args.Key = r.ctx.Agent.lastHeartbeat.Kv.Key 97 | args.Value = r.ctx.Agent.lastHeartbeat.Kv.Value 98 | } 99 | args.Time = r.ctx.Agent.lastHeartbeat.UpdateTime 100 | } else { 101 | args.Time = time.Now() 102 | } 103 | var reply protocol.HeartbeatReply 104 | err := r.doCall("MonitorRpc.Heartbeat", args, reply) 105 | if err != nil { 106 | r.ctx.Agent.logf("doHeartbeat error : %s", err.Error()) 107 | return 108 | } 109 | } 110 | 111 | func (r *RpcClient) doCall(method string, args, reply interface{}) error { 112 | if r.errCnt > r.maxErrCnt { 113 | return errors.New("error cnt more than the max error cnt, require ignore!") 114 | } 115 | var err error 116 | err = r.client.Call(method, args, reply) 117 | if err != nil { 118 | r.errCnt++ 119 | return err 120 | } 121 | return nil 122 | } 123 | -------------------------------------------------------------------------------- /dashboard/template/asset/js/jquery.flot.resize.js: -------------------------------------------------------------------------------- 1 | /* Flot plugin for automatically redrawing plots as the placeholder resizes. 2 | 3 | Copyright (c) 2007-2014 IOLA and Ole Laursen. 4 | Licensed under the MIT license. 5 | 6 | It works by listening for changes on the placeholder div (through the jQuery 7 | resize event plugin) - if the size changes, it will redraw the plot. 8 | 9 | There are no options. If you need to disable the plugin for some plots, you 10 | can just fix the size of their placeholders. 11 | 12 | */ 13 | 14 | /* Inline dependency: 15 | * jQuery resize event - v1.1 - 3/14/2010 16 | * http://benalman.com/projects/jquery-resize-plugin/ 17 | * 18 | * Copyright (c) 2010 "Cowboy" Ben Alman 19 | * Dual licensed under the MIT and GPL licenses. 20 | * http://benalman.com/about/license/ 21 | */ 22 | (function($,e,t){"$:nomunge";var i=[],n=$.resize=$.extend($.resize,{}),a,r=false,s="setTimeout",u="resize",m=u+"-special-event",o="pendingDelay",l="activeDelay",f="throttleWindow";n[o]=200;n[l]=20;n[f]=true;$.event.special[u]={setup:function(){if(!n[f]&&this[s]){return false}var e=$(this);i.push(this);e.data(m,{w:e.width(),h:e.height()});if(i.length===1){a=t;h()}},teardown:function(){if(!n[f]&&this[s]){return false}var e=$(this);for(var t=i.length-1;t>=0;t--){if(i[t]==this){i.splice(t,1);break}}e.removeData(m);if(!i.length){if(r){cancelAnimationFrame(a)}else{clearTimeout(a)}a=null}},add:function(e){if(!n[f]&&this[s]){return false}var i;function a(e,n,a){var r=$(this),s=r.data(m)||{};s.w=n!==t?n:r.width();s.h=a!==t?a:r.height();i.apply(this,arguments)}if($.isFunction(e)){i=e;return a}else{i=e.handler;e.handler=a}}};function h(t){if(r===true){r=t||1}for(var s=i.length-1;s>=0;s--){var l=$(i[s]);if(l[0]==e||l.is(":visible")){var f=l.width(),c=l.height(),d=l.data(m);if(d&&(f!==d.w||c!==d.h)){l.trigger(u,[d.w=f,d.h=c]);r=t||true}}else{d=l.data(m);d.w=0;d.h=0}}if(a!==null){if(r&&(t==null||t-r<1e3)){a=e.requestAnimationFrame(h)}else{a=setTimeout(h,n[o]);r=false}}}if(!e.requestAnimationFrame){e.requestAnimationFrame=function(){return e.webkitRequestAnimationFrame||e.mozRequestAnimationFrame||e.oRequestAnimationFrame||e.msRequestAnimationFrame||function(t,i){return e.setTimeout(function(){t((new Date).getTime())},n[l])}}()}if(!e.cancelAnimationFrame){e.cancelAnimationFrame=function(){return e.webkitCancelRequestAnimationFrame||e.mozCancelRequestAnimationFrame||e.oCancelRequestAnimationFrame||e.msCancelRequestAnimationFrame||clearTimeout}()}})(jQuery,this); 23 | 24 | (function ($) { 25 | var options = { }; // no options 26 | 27 | function init(plot) { 28 | function onResize() { 29 | var placeholder = plot.getPlaceholder(); 30 | 31 | // somebody might have hidden us and we can't plot 32 | // when we don't have the dimensions 33 | if (placeholder.width() == 0 || placeholder.height() == 0) 34 | return; 35 | 36 | plot.resize(); 37 | plot.setupGrid(); 38 | plot.draw(); 39 | } 40 | 41 | function bindEvents(plot, eventHolder) { 42 | plot.getPlaceholder().resize(onResize); 43 | } 44 | 45 | function shutdown(plot, eventHolder) { 46 | plot.getPlaceholder().unbind("resize", onResize); 47 | } 48 | 49 | plot.hooks.bindEvents.push(bindEvents); 50 | plot.hooks.shutdown.push(shutdown); 51 | } 52 | 53 | $.plot.plugins.push({ 54 | init: init, 55 | options: options, 56 | name: 'resize', 57 | version: '1.0' 58 | }); 59 | })(jQuery); 60 | -------------------------------------------------------------------------------- /registry/frontend/file.go: -------------------------------------------------------------------------------- 1 | package frontend 2 | 3 | import ( 4 | "errors" 5 | "os" 6 | "path/filepath" 7 | "strings" 8 | "sync" 9 | 10 | "github.com/Leon2012/goconfd/libs/kv" 11 | "github.com/Leon2012/goconfd/libs/util" 12 | ) 13 | 14 | const ( 15 | FILE_SAVE_TYPE_JSON = iota 16 | FILE_SAVE_TYPE_PHP 17 | ) 18 | 19 | type FileSaver struct { 20 | sync.RWMutex 21 | Path string 22 | Ext string 23 | EncodeFunc kv.EncodeFunc 24 | DecodeFunc kv.DecodeFunc 25 | } 26 | 27 | func NewFileSaver(path string, ext string) (*FileSaver, error) { 28 | err := util.IsWritable(path) 29 | if err != nil { 30 | return nil, err 31 | } 32 | s := &FileSaver{ 33 | Path: path, 34 | } 35 | if !strings.HasPrefix(".", ext) { 36 | s.Ext = "." + ext 37 | } 38 | if ext == "json" { 39 | s.EncodeFunc = kv.JsonEncode 40 | s.DecodeFunc = kv.JsonDecode 41 | } else if ext == "php" { 42 | s.EncodeFunc = kv.PhpEncode 43 | s.DecodeFunc = kv.PhpDecode 44 | } 45 | return s, nil 46 | } 47 | 48 | func (s *FileSaver) Save(k *kv.Kv) error { 49 | s.Lock() 50 | defer s.Unlock() 51 | var err error 52 | key := k.Key 53 | newKey := s.safeKey(key) 54 | var fullFilePath string 55 | lastIndex := strings.LastIndex(newKey, "/") 56 | if lastIndex != -1 { 57 | path := newKey[0:(lastIndex + 1)] 58 | file := newKey[(lastIndex + 1):len(newKey)] 59 | fullPath := filepath.Join(s.Path, path) 60 | if _, err = os.Stat(fullPath); os.IsNotExist(err) { 61 | err = os.MkdirAll(fullPath, 0777) 62 | if err != nil { 63 | return err 64 | } 65 | } 66 | fullFilePath = filepath.Join(fullPath, file) 67 | } else { 68 | fullFilePath = filepath.Join(s.Path, newKey) 69 | } 70 | fullFilePath += s.Ext 71 | //fmt.Println(fullFilePath) 72 | if k.Event == 0 { //KV_EVENT_PUT 73 | err = s.save(fullFilePath, k) 74 | } else if k.Event == 1 { //KV_EVENT_DELETE 75 | err = s.delete(fullFilePath, k) 76 | } else { //KV_EVENT_NONE 77 | err = s.save(fullFilePath, k) 78 | } 79 | return err 80 | } 81 | 82 | func (s *FileSaver) Get(k string) (*kv.Kv, error) { 83 | s.Lock() 84 | defer s.Unlock() 85 | return s.get(k) 86 | } 87 | 88 | func (s *FileSaver) Keys() []string { 89 | return nil 90 | } 91 | 92 | func (s *FileSaver) save(fullFilePath string, k *kv.Kv) error { 93 | data, err := k.Encode(s.EncodeFunc) 94 | if err != nil { 95 | return err 96 | } 97 | f, err := os.Create(fullFilePath) 98 | if err != nil { 99 | return err 100 | } 101 | defer f.Close() 102 | _, err = f.Write(data) 103 | if err != nil { 104 | return err 105 | } 106 | return nil 107 | } 108 | 109 | func (s *FileSaver) delete(fullFilePath string, k *kv.Kv) error { 110 | if util.IsExist(fullFilePath) { 111 | return os.Remove(fullFilePath) 112 | } else { 113 | return errors.New("key: " + k.Key + " not exist in local") 114 | } 115 | } 116 | 117 | func (s *FileSaver) get(k string) (*kv.Kv, error) { 118 | fullFilePath := filepath.Join(s.Path, k) 119 | f, err := os.Open(fullFilePath) 120 | if err != nil { 121 | return nil, err 122 | } 123 | defer f.Close() 124 | fi, err := f.Stat() 125 | if err != nil { 126 | return nil, err 127 | } 128 | fileSize := fi.Size() 129 | buf := make([]byte, fileSize) 130 | _, err = f.Read(buf) 131 | if err != nil { 132 | return nil, err 133 | } 134 | nkv, err := kv.Decode(buf, s.DecodeFunc) 135 | if err != nil { 136 | return nil, err 137 | } 138 | return nkv, nil 139 | } 140 | 141 | func (s *FileSaver) safeKey(key string) string { 142 | var newKey string 143 | newKey = util.HexKey(key) 144 | return newKey 145 | } 146 | -------------------------------------------------------------------------------- /dashboard/template/asset/js/jquery.flot.tooltip.min.js: -------------------------------------------------------------------------------- 1 | /* 2 | * jquery.flot.tooltip 3 | * 4 | * description: easy-to-use tooltips for Flot charts 5 | * version: 0.6.2 6 | * author: Krzysztof Urbas @krzysu [myviews.pl] 7 | * website: https://github.com/krzysu/flot.tooltip 8 | * 9 | * build on 2013-09-30 10 | * released under MIT License, 2012 11 | */ 12 | (function(t){var o={tooltip:!1,tooltipOpts:{content:"%s | X: %x | Y: %y",xDateFormat:null,yDateFormat:null,shifts:{x:10,y:20},defaultTheme:!0,onHover:function(){}}},i=function(t){this.tipPosition={x:0,y:0},this.init(t)};i.prototype.init=function(o){function i(t){var o={};o.x=t.pageX,o.y=t.pageY,s.updateTooltipPosition(o)}function e(t,o,i){var e=s.getDomElement();if(i){var n;n=s.stringFormat(s.tooltipOptions.content,i),e.html(n),s.updateTooltipPosition({x:o.pageX,y:o.pageY}),e.css({left:s.tipPosition.x+s.tooltipOptions.shifts.x,top:s.tipPosition.y+s.tooltipOptions.shifts.y}).show(),"function"==typeof s.tooltipOptions.onHover&&s.tooltipOptions.onHover(i,e)}else e.hide().html("")}var s=this;o.hooks.bindEvents.push(function(o,n){s.plotOptions=o.getOptions(),s.plotOptions.tooltip!==!1&&void 0!==s.plotOptions.tooltip&&(s.tooltipOptions=s.plotOptions.tooltipOpts,s.getDomElement(),t(o.getPlaceholder()).bind("plothover",e),t(n).bind("mousemove",i))}),o.hooks.shutdown.push(function(o,s){t(o.getPlaceholder()).unbind("plothover",e),t(s).unbind("mousemove",i)})},i.prototype.getDomElement=function(){var o;return t("#flotTip").length>0?o=t("#flotTip"):(o=t("
").attr("id","flotTip"),o.appendTo("body").hide().css({position:"absolute"}),this.tooltipOptions.defaultTheme&&o.css({background:"#fff","z-index":"100",padding:"0.4em 0.6em","border-radius":"0.5em","font-size":"0.8em",border:"1px solid #111",display:"none","white-space":"nowrap"})),o},i.prototype.updateTooltipPosition=function(o){var i=t("#flotTip").outerWidth()+this.tooltipOptions.shifts.x,e=t("#flotTip").outerHeight()+this.tooltipOptions.shifts.y;o.x-t(window).scrollLeft()>t(window).innerWidth()-i&&(o.x-=i),o.y-t(window).scrollTop()>t(window).innerHeight()-e&&(o.y-=e),this.tipPosition.x=o.x,this.tipPosition.y=o.y},i.prototype.stringFormat=function(t,o){var i=/%p\.{0,1}(\d{0,})/,e=/%s/,s=/%x\.{0,1}(?:\d{0,})/,n=/%y\.{0,1}(?:\d{0,})/;return"function"==typeof t&&(t=t(o.series.label,o.series.data[o.dataIndex][0],o.series.data[o.dataIndex][1],o)),o.series.percent!==void 0&&(t=this.adjustValPrecision(i,t,o.series.percent)),o.series.label!==void 0&&(t=t.replace(e,o.series.label)),this.isTimeMode("xaxis",o)&&this.isXDateFormat(o)&&(t=t.replace(s,this.timestampToDate(o.series.data[o.dataIndex][0],this.tooltipOptions.xDateFormat))),this.isTimeMode("yaxis",o)&&this.isYDateFormat(o)&&(t=t.replace(n,this.timestampToDate(o.series.data[o.dataIndex][1],this.tooltipOptions.yDateFormat))),"number"==typeof o.series.data[o.dataIndex][0]&&(t=this.adjustValPrecision(s,t,o.series.data[o.dataIndex][0])),"number"==typeof o.series.data[o.dataIndex][1]&&(t=this.adjustValPrecision(n,t,o.series.data[o.dataIndex][1])),o.series.xaxis.tickFormatter!==void 0&&(t=t.replace(s,o.series.xaxis.tickFormatter(o.series.data[o.dataIndex][0],o.series.xaxis))),o.series.yaxis.tickFormatter!==void 0&&(t=t.replace(n,o.series.yaxis.tickFormatter(o.series.data[o.dataIndex][1],o.series.yaxis))),t},i.prototype.isTimeMode=function(t,o){return o.series[t].options.mode!==void 0&&"time"===o.series[t].options.mode},i.prototype.isXDateFormat=function(){return this.tooltipOptions.xDateFormat!==void 0&&null!==this.tooltipOptions.xDateFormat},i.prototype.isYDateFormat=function(){return this.tooltipOptions.yDateFormat!==void 0&&null!==this.tooltipOptions.yDateFormat},i.prototype.timestampToDate=function(o,i){var e=new Date(o);return t.plot.formatDate(e,i)},i.prototype.adjustValPrecision=function(t,o,i){var e,s=o.match(t);return null!==s&&""!==RegExp.$1&&(e=RegExp.$1,i=i.toFixed(e),o=o.replace(t,i)),o};var e=function(t){new i(t)};t.plot.plugins.push({init:e,options:o,name:"tooltip",version:"0.6.1"})})(jQuery); -------------------------------------------------------------------------------- /registry/backend/etcd_test.go: -------------------------------------------------------------------------------- 1 | package backend 2 | 3 | import ( 4 | "fmt" 5 | "testing" 6 | "time" 7 | 8 | "github.com/Leon2012/goconfd/libs/kv" 9 | "github.com/coreos/etcd/clientv3" 10 | "golang.org/x/net/context" 11 | ) 12 | 13 | const ETCD_HOST = "localhost:2379" 14 | const ETCD_REQUEST_TIMEOUT = 5 * time.Second 15 | const ETCD_DIAL_TIMEOUT = 5 * time.Second 16 | 17 | func getEtcdClient() (*clientv3.Client, error) { 18 | cli, err := clientv3.New(clientv3.Config{ 19 | Endpoints: []string{ETCD_HOST}, 20 | DialTimeout: ETCD_DIAL_TIMEOUT, 21 | }) 22 | return cli, err 23 | } 24 | 25 | func TestGet(t *testing.T) { 26 | cli, err := getEtcdClient() 27 | if err != nil { 28 | t.Error(err) 29 | } 30 | defer cli.Close() 31 | 32 | ctx, cancel := context.WithTimeout(context.Background(), ETCD_REQUEST_TIMEOUT) 33 | cancel() 34 | 35 | k := "test1" 36 | resp, err := cli.Get(ctx, k) 37 | if err != nil { 38 | t.Error(err) 39 | } 40 | 41 | t.Log(resp) 42 | } 43 | 44 | func TestPut(t *testing.T) { 45 | cli, err := getEtcdClient() 46 | if err != nil { 47 | t.Error(err) 48 | } 49 | defer cli.Close() 50 | 51 | ctx, cancel := context.WithTimeout(context.Background(), ETCD_REQUEST_TIMEOUT) 52 | defer cancel() 53 | 54 | resp, err := cli.Put(ctx, "foo1", "bar1") 55 | if err != nil { 56 | t.Error(err) 57 | } 58 | 59 | fmt.Println(resp) 60 | } 61 | 62 | func TestWatch(t *testing.T) { 63 | cli, err := getEtcdClient() 64 | if err != nil { 65 | t.Error(err) 66 | } 67 | defer cli.Close() 68 | rch := cli.Watch(context.Background(), "/develop/activity/", clientv3.WithPrefix()) 69 | for wresp := range rch { 70 | for _, ev := range wresp.Events { 71 | fmt.Printf("%s %q : %q\n", ev.Type, ev.Kv.Key, ev.Kv.Value) 72 | } 73 | } 74 | } 75 | 76 | func TestBatchGet(t *testing.T) { 77 | cli, err := getEtcdClient() 78 | if err != nil { 79 | t.Error(err) 80 | } 81 | defer cli.Close() 82 | ctx, cancel := context.WithTimeout(context.Background(), ETCD_REQUEST_TIMEOUT) 83 | resp, err := cli.Get(ctx, "/develop/activity/", clientv3.WithPrefix(), clientv3.WithSort(clientv3.SortByKey, clientv3.SortAscend)) 84 | cancel() 85 | if err != nil { 86 | t.Error(err) 87 | } 88 | for _, ev := range resp.Kvs { 89 | fmt.Printf("%s : %s\n", ev.Key, ev.Value) 90 | } 91 | } 92 | 93 | func TestEtcdPut(t *testing.T) { 94 | hosts := []string{"localhost:2379"} 95 | etcd, err := NewEtcdAdpater(hosts, 5, 5) 96 | if err != nil { 97 | t.Error(err) 98 | } 99 | defer etcd.Close() 100 | 101 | err = etcd.Put("foo3", "bar3") 102 | if err != nil { 103 | t.Error(err) 104 | } 105 | } 106 | 107 | func TestWatchWithPrefixs(t *testing.T) { 108 | //hosts := []string{"127.0.0.1:2379", "192.168.174.114:2379"} 109 | hosts := []string{"localhost:2379"} 110 | prefixs := []string{"develop.activity", "develop.user"} 111 | etcd, err := NewEtcdAdpater(hosts, 5, 5) 112 | if err != nil { 113 | t.Error(err) 114 | } 115 | defer etcd.Close() 116 | var f kv.KvFunc 117 | f = func(k *kv.Kv, prefix string) bool { 118 | str := fmt.Sprintf("watch prefix %s change", prefix) 119 | fmt.Println(str) 120 | return true 121 | } 122 | fmt.Println(f) 123 | etcd.WatchWithPrefixs(prefixs, f) 124 | for { 125 | 126 | } 127 | } 128 | 129 | func TestWatchPrefix(t *testing.T) { 130 | //hosts := []string{"127.0.0.1:2379", "192.168.174.114:2379"} 131 | hosts := []string{"localhost:2379"} 132 | prefix := "develop.activity" 133 | etcd, err := NewEtcdAdpater(hosts, 5, 5) 134 | if err != nil { 135 | t.Error(err) 136 | } 137 | defer etcd.Close() 138 | var f kv.KvFunc 139 | f = func(k *kv.Kv, prefix string) bool { 140 | str := fmt.Sprintf("watch prefix %s change", prefix) 141 | fmt.Println(str) 142 | return true 143 | } 144 | fmt.Println(f) 145 | etcd.WatchWithPrefix(prefix, f) 146 | } 147 | -------------------------------------------------------------------------------- /dashboard/template/asset/css/sb-admin.css: -------------------------------------------------------------------------------- 1 | /*! 2 | * Start Bootstrap - SB Admin (http://startbootstrap.com/) 3 | * Copyright 2013-2016 Start Bootstrap 4 | * Licensed under MIT (https://github.com/BlackrockDigital/startbootstrap/blob/gh-pages/LICENSE) 5 | */ 6 | 7 | /* Global Styles */ 8 | 9 | body { 10 | margin-top: 100px; 11 | background-color: #222; 12 | } 13 | 14 | @media(min-width:768px) { 15 | body { 16 | margin-top: 50px; 17 | } 18 | } 19 | 20 | #wrapper { 21 | padding-left: 0; 22 | } 23 | 24 | #page-wrapper { 25 | width: 100%; 26 | padding: 0; 27 | background-color: #fff; 28 | } 29 | 30 | .huge { 31 | font-size: 50px; 32 | line-height: normal; 33 | } 34 | 35 | @media(min-width:768px) { 36 | #wrapper { 37 | padding-left: 225px; 38 | } 39 | 40 | #page-wrapper { 41 | padding: 10px; 42 | } 43 | } 44 | 45 | /* Top Navigation */ 46 | 47 | .top-nav { 48 | padding: 0 15px; 49 | } 50 | 51 | .top-nav>li { 52 | display: inline-block; 53 | float: left; 54 | } 55 | 56 | .top-nav>li>a { 57 | padding-top: 15px; 58 | padding-bottom: 15px; 59 | line-height: 20px; 60 | color: #999; 61 | } 62 | 63 | .top-nav>li>a:hover, 64 | .top-nav>li>a:focus, 65 | .top-nav>.open>a, 66 | .top-nav>.open>a:hover, 67 | .top-nav>.open>a:focus { 68 | color: #fff; 69 | background-color: #000; 70 | } 71 | 72 | .top-nav>.open>.dropdown-menu { 73 | float: left; 74 | position: absolute; 75 | margin-top: 0; 76 | border: 1px solid rgba(0,0,0,.15); 77 | border-top-left-radius: 0; 78 | border-top-right-radius: 0; 79 | background-color: #fff; 80 | -webkit-box-shadow: 0 6px 12px rgba(0,0,0,.175); 81 | box-shadow: 0 6px 12px rgba(0,0,0,.175); 82 | } 83 | 84 | .top-nav>.open>.dropdown-menu>li>a { 85 | white-space: normal; 86 | } 87 | 88 | ul.message-dropdown { 89 | padding: 0; 90 | max-height: 250px; 91 | overflow-x: hidden; 92 | overflow-y: auto; 93 | } 94 | 95 | li.message-preview { 96 | width: 275px; 97 | border-bottom: 1px solid rgba(0,0,0,.15); 98 | } 99 | 100 | li.message-preview>a { 101 | padding-top: 15px; 102 | padding-bottom: 15px; 103 | } 104 | 105 | li.message-footer { 106 | margin: 5px 0; 107 | } 108 | 109 | ul.alert-dropdown { 110 | width: 200px; 111 | } 112 | 113 | /* Side Navigation */ 114 | 115 | @media(min-width:768px) { 116 | .side-nav { 117 | position: fixed; 118 | top: 51px; 119 | left: 225px; 120 | width: 225px; 121 | margin-left: -225px; 122 | border: none; 123 | border-radius: 0; 124 | overflow-y: auto; 125 | background-color: #222; 126 | bottom: 0; 127 | overflow-x: hidden; 128 | padding-bottom: 40px; 129 | } 130 | 131 | .side-nav>li>a { 132 | width: 225px; 133 | } 134 | 135 | .side-nav li a:hover, 136 | .side-nav li a:focus { 137 | outline: none; 138 | background-color: #000 !important; 139 | } 140 | } 141 | 142 | .side-nav>li>ul { 143 | padding: 0; 144 | } 145 | 146 | .side-nav>li>ul>li>a { 147 | display: block; 148 | padding: 10px 15px 10px 38px; 149 | text-decoration: none; 150 | color: #999; 151 | } 152 | 153 | .side-nav>li>ul>li>a:hover { 154 | color: #fff; 155 | } 156 | 157 | /* Flot Chart Containers */ 158 | 159 | .flot-chart { 160 | display: block; 161 | height: 400px; 162 | } 163 | 164 | .flot-chart-content { 165 | width: 100%; 166 | height: 100%; 167 | } 168 | 169 | /* Custom Colored Panels */ 170 | 171 | .huge { 172 | font-size: 40px; 173 | } 174 | 175 | .panel-green { 176 | border-color: #5cb85c; 177 | } 178 | 179 | .panel-green > .panel-heading { 180 | border-color: #5cb85c; 181 | color: #fff; 182 | background-color: #5cb85c; 183 | } 184 | 185 | .panel-green > a { 186 | color: #5cb85c; 187 | } 188 | 189 | .panel-green > a:hover { 190 | color: #3d8b3d; 191 | } 192 | 193 | .panel-red { 194 | border-color: #d9534f; 195 | } 196 | 197 | .panel-red > .panel-heading { 198 | border-color: #d9534f; 199 | color: #fff; 200 | background-color: #d9534f; 201 | } 202 | 203 | .panel-red > a { 204 | color: #d9534f; 205 | } 206 | 207 | .panel-red > a:hover { 208 | color: #b52b27; 209 | } 210 | 211 | .panel-yellow { 212 | border-color: #f0ad4e; 213 | } 214 | 215 | .panel-yellow > .panel-heading { 216 | border-color: #f0ad4e; 217 | color: #fff; 218 | background-color: #f0ad4e; 219 | } 220 | 221 | .panel-yellow > a { 222 | color: #f0ad4e; 223 | } 224 | 225 | .panel-yellow > a:hover { 226 | color: #df8a13; 227 | } -------------------------------------------------------------------------------- /agent/agent.go: -------------------------------------------------------------------------------- 1 | package agent 2 | 3 | import ( 4 | "fmt" 5 | "net" 6 | "os" 7 | "sync" 8 | "time" 9 | 10 | "github.com/Leon2012/goconfd/libs/kv" 11 | "github.com/Leon2012/goconfd/libs/net2" 12 | "github.com/Leon2012/goconfd/libs/util" 13 | "github.com/Leon2012/goconfd/libs/version" 14 | "github.com/Leon2012/goconfd/registry" 15 | "github.com/Leon2012/goconfd/registry/backend" 16 | "github.com/Leon2012/goconfd/registry/frontend" 17 | _ "github.com/coreos/etcd/clientv3" 18 | ) 19 | 20 | type KvPair struct { 21 | KeyPrefix string 22 | Kv *kv.Kv 23 | } 24 | 25 | type Heartbeat struct { 26 | Kv *kv.Kv 27 | UpdateTime time.Time 28 | } 29 | 30 | type Agent struct { 31 | sync.RWMutex 32 | opts *Options 33 | httpListener net.Listener 34 | waitGroup util.WaitGroupWrapper 35 | idc registry.Backend 36 | local registry.Frontend 37 | keyCache map[string]string 38 | lastHeartbeat *Heartbeat 39 | watchKVChan chan *kv.Kv 40 | hostName string 41 | localIP string 42 | rpcClient *RpcClient 43 | exitChan chan bool 44 | mq *MsgQueue 45 | monitor *Monitor 46 | } 47 | 48 | func NewAgent(opts *Options) *Agent { 49 | a := &Agent{ 50 | opts: opts, 51 | keyCache: make(map[string]string), 52 | watchKVChan: make(chan *kv.Kv), 53 | lastHeartbeat: nil, 54 | rpcClient: nil, 55 | exitChan: make(chan bool), 56 | } 57 | a.logf(version.String("agent")) 58 | return a 59 | } 60 | 61 | func (a *Agent) setLastHeartbeat(kv *kv.Kv) { 62 | if a.lastHeartbeat == nil { 63 | a.lastHeartbeat = &Heartbeat{} 64 | } 65 | a.lastHeartbeat.Kv = kv 66 | a.lastHeartbeat.UpdateTime = time.Now() 67 | } 68 | 69 | func (a *Agent) logf(f string, args ...interface{}) { 70 | if a.opts.Logger == nil { 71 | return 72 | } 73 | a.opts.Logger.Output(2, fmt.Sprintf(f, args...)) 74 | } 75 | 76 | func (a *Agent) Main() { 77 | ctx := &Context{a} 78 | 79 | hostName, err := util.GetHostName() 80 | if err != nil { 81 | a.logf("FATAL: get host name faile") 82 | os.Exit(1) 83 | } 84 | 85 | localIP, err := net2.GetLocalIPv4() 86 | if err != nil { 87 | a.logf("FATAL: get local ipv4 faile") 88 | os.Exit(1) 89 | } 90 | 91 | a.Lock() 92 | a.hostName = hostName 93 | a.localIP = localIP 94 | a.Unlock() 95 | 96 | //初始化etcd client 97 | if len(a.opts.Hosts) == 0 { 98 | a.logf("FATAL: etcd host empty") 99 | os.Exit(1) 100 | } 101 | hosts := a.opts.ParseHosts() 102 | cli, err := backend.NewEtcdAdpater(hosts, a.opts.DialTimeout, a.opts.RequestTimeout) 103 | if err != nil { 104 | a.logf("FATAL: create etcd client failed - %s, hosts : %s", err.Error(), hosts) 105 | os.Exit(1) 106 | } 107 | a.Lock() 108 | a.idc = cli 109 | a.Unlock() 110 | 111 | var l registry.Frontend 112 | if a.opts.SaveType == 1 { 113 | l, err = frontend.NewFileSaver(a.opts.SavePath, a.opts.FileExt) 114 | } else if a.opts.SaveType == 2 { 115 | l, err = frontend.NewShmSaver(a.opts.SavePath) 116 | } 117 | if err != nil { 118 | a.logf("FATAL: create local save failed - %s", err.Error()) 119 | os.Exit(1) 120 | } 121 | a.Lock() 122 | a.local = l 123 | a.Unlock() 124 | 125 | httpListener, err := net.Listen("tcp", a.opts.HttpAddress) 126 | if err != nil { 127 | a.logf("FATAL: listen (%s) failed - %s", a.opts.HttpAddress, err) 128 | os.Exit(1) 129 | } 130 | a.Lock() 131 | a.httpListener = httpListener 132 | a.Unlock() 133 | httpServer := newHttpServer(ctx) 134 | a.waitGroup.Wrap(func() { 135 | httpServer.serve(a.httpListener) 136 | }) 137 | 138 | mq, err := NewMsgQueue(ctx, "/tmp/queue1", 0x01) 139 | if err != nil { 140 | a.logf("FATAL: init Msg queue failed - %s", err) 141 | os.Exit(1) 142 | } 143 | a.Lock() 144 | a.mq = mq 145 | a.Unlock() 146 | a.waitGroup.Wrap(func() { 147 | mq.Receive() 148 | }) 149 | 150 | go SaveKV(ctx) 151 | if a.opts.AutoLoad { 152 | UpdateLocalVales(ctx) 153 | } 154 | monitor := NewMonitor(ctx) 155 | a.Lock() 156 | a.monitor = monitor 157 | a.Unlock() 158 | a.waitGroup.Wrap(func() { 159 | a.monitor.WatchNodes() 160 | }) 161 | 162 | rpcClient := NewRpcClient(ctx) 163 | a.Lock() 164 | a.rpcClient = rpcClient 165 | a.Unlock() 166 | a.rpcClient.ReloadAddrs() 167 | err = a.rpcClient.Online() 168 | if err != nil { 169 | a.logf("agent call online faile : %s", err.Error()) 170 | } 171 | 172 | go WatchValuesByPrefix(ctx) 173 | go rpcClient.Heartbeat(a.opts.HeartbeatInterval) 174 | } 175 | 176 | func (a *Agent) Exit() { 177 | close(a.exitChan) 178 | if a.httpListener != nil { 179 | a.httpListener.Close() 180 | } 181 | if a.idc != nil { 182 | a.idc.Close() 183 | } 184 | err := a.rpcClient.Offline() 185 | if err != nil { 186 | a.logf("agent call offline faile : %s", err.Error()) 187 | } 188 | 189 | if a.mq != nil { 190 | a.mq.Close() 191 | } 192 | a.waitGroup.Wait() 193 | } 194 | -------------------------------------------------------------------------------- /dashboard/http.go: -------------------------------------------------------------------------------- 1 | package dashboard 2 | 3 | import ( 4 | "html/template" 5 | "io/ioutil" 6 | "mime" 7 | "net" 8 | "net/http" 9 | "path" 10 | "path/filepath" 11 | 12 | "strings" 13 | 14 | "github.com/Leon2012/goconfd/store/types" 15 | "github.com/julienschmidt/httprouter" 16 | ) 17 | 18 | type httpServer struct { 19 | Path string 20 | HtmlPath string 21 | AssetPath string 22 | HtmlCommonFiles []string 23 | ctx *Context 24 | router http.Handler 25 | } 26 | 27 | func newHttpServer(c *Context, path string) *httpServer { 28 | router := httprouter.New() 29 | router.HandleMethodNotAllowed = false 30 | h := &httpServer{} 31 | h.Path = path 32 | h.AssetPath = filepath.Join(h.Path, "asset") 33 | h.HtmlPath = filepath.Join(h.Path, "public_html") 34 | h.HtmlCommonFiles = []string{ 35 | filepath.Join(h.HtmlPath, "common/header.html"), 36 | filepath.Join(h.HtmlPath, "common/footer.html"), 37 | filepath.Join(h.HtmlPath, "common/left.html"), 38 | filepath.Join(h.HtmlPath, "common/layout.html"), 39 | } 40 | h.ctx = c 41 | h.router = router 42 | router.Handle("GET", "/", h.Index) 43 | router.Handle("GET", "/agent", h.Agent) 44 | router.Handle("GET", "/heartbeat/:hostname/:keyprefix", h.Heartbeat) 45 | router.Handle("GET", "/var", h.Var) 46 | router.Handle("GET", "/add/:key/:value", h.Add) 47 | router.Handle("GET", "/system", h.System) 48 | router.GET("/asset/:path/:asset", h.Static) 49 | return h 50 | } 51 | 52 | func (h *httpServer) serve(listener net.Listener) { 53 | h.ctx.Dashboard.logf("http server listening on %s", listener.Addr()) 54 | server := &http.Server{ 55 | Handler: h, 56 | //ErrorLog: h.ctx.Agent.opts.Logger, 57 | } 58 | err := server.Serve(listener) 59 | if err != nil { 60 | h.ctx.Dashboard.logf("ERROR: http.Server() - %s", err) 61 | } 62 | h.ctx.Dashboard.logf("http server closing %s", listener.Addr()) 63 | } 64 | 65 | func (h *httpServer) ServeHTTP(w http.ResponseWriter, req *http.Request) { 66 | req.ParseForm() 67 | if req.Method == "POST" { 68 | h.DoAdd(w, req) 69 | } else { 70 | h.router.ServeHTTP(w, req) 71 | } 72 | } 73 | 74 | func (h *httpServer) Render(templateFile string, data interface{}, w http.ResponseWriter, r *http.Request, ps httprouter.Params) { 75 | w.Header().Set("Content-Type", "text/html") 76 | templateFile += ".html" 77 | templateFile = filepath.Join(h.HtmlPath, templateFile) 78 | templateFiles := []string{} 79 | templateFiles = append(templateFiles, templateFile) 80 | for _, htmlCommonFile := range h.HtmlCommonFiles { 81 | templateFiles = append(templateFiles, htmlCommonFile) 82 | } 83 | tmpl := template.Must(template.ParseFiles(templateFiles...)) 84 | err := tmpl.Execute(w, data) 85 | if err != nil { 86 | h.ctx.Dashboard.logf("FATAL: execute template %s", err.Error()) 87 | } 88 | } 89 | 90 | func (h *httpServer) DoAdd(w http.ResponseWriter, r *http.Request) { 91 | key := strings.TrimSpace(r.FormValue("key")) 92 | value := strings.TrimSpace(r.FormValue("value")) 93 | if key != "" && value != "" { 94 | err := h.ctx.Dashboard.idc.Put(key, value) 95 | if err != nil { 96 | http.Error(w, err.Error(), 500) 97 | return 98 | } 99 | } 100 | http.Redirect(w, r, "/var", 302) 101 | } 102 | 103 | func (h *httpServer) Add(w http.ResponseWriter, r *http.Request, ps httprouter.Params) { 104 | key := strings.TrimSpace(ps.ByName("key")) 105 | value := strings.TrimSpace(ps.ByName("value")) 106 | if key != "" && value != "" { 107 | err := h.ctx.Dashboard.idc.Put(key, value) 108 | if err != nil { 109 | http.Error(w, err.Error(), 500) 110 | return 111 | } 112 | } else { 113 | w.Write([]byte("OK\n")) 114 | } 115 | } 116 | 117 | func (h *httpServer) Var(w http.ResponseWriter, r *http.Request, ps httprouter.Params) { 118 | templateFile := "var" 119 | h.Render(templateFile, nil, w, r, ps) 120 | } 121 | 122 | func (h *httpServer) System(w http.ResponseWriter, r *http.Request, ps httprouter.Params) { 123 | 124 | } 125 | 126 | func (h *httpServer) Index(w http.ResponseWriter, r *http.Request, ps httprouter.Params) { 127 | templateFile := "index" 128 | data := make(map[string]string) 129 | data["title"] = "goconfd dashboard" 130 | h.Render(templateFile, data, w, r, ps) 131 | } 132 | 133 | func (h *httpServer) Agent(w http.ResponseWriter, r *http.Request, ps httprouter.Params) { 134 | templateFile := "agent" 135 | data, err := h.ctx.Dashboard.db.GetAgents() 136 | if err != nil { 137 | http.Error(w, err.Error(), 500) 138 | } else { 139 | h.Render(templateFile, data, w, r, ps) 140 | } 141 | } 142 | 143 | func (h *httpServer) Heartbeat(w http.ResponseWriter, r *http.Request, ps httprouter.Params) { 144 | var data []*types.Heartbeat 145 | var err error 146 | templateFile := "heartbeat" 147 | hostName := ps.ByName("hostname") 148 | keyPrefix := ps.ByName("keyprefix") 149 | if hostName != "" && keyPrefix != "" { 150 | agent := &types.Agent{} 151 | agent.HostName = hostName 152 | agent.KeyPrefix = keyPrefix 153 | data, err = h.ctx.Dashboard.db.GetHeartbeatsByAgent(agent) 154 | } else { 155 | data, err = h.ctx.Dashboard.db.GetHeartbeats() 156 | } 157 | if err != nil { 158 | http.Error(w, err.Error(), 500) 159 | } else { 160 | h.Render(templateFile, data, w, r, ps) 161 | } 162 | } 163 | 164 | func (h *httpServer) Static(w http.ResponseWriter, r *http.Request, ps httprouter.Params) { 165 | assetName := ps.ByName("asset") 166 | pathName := ps.ByName("path") 167 | assetFile := filepath.Join(h.AssetPath, pathName, assetName) 168 | //log.Println(assetFile) 169 | data, err := ioutil.ReadFile(assetFile) 170 | if err != nil { 171 | http.Error(w, err.Error(), 404) 172 | } else { 173 | ext := path.Ext(assetName) 174 | ct := mime.TypeByExtension(ext) 175 | if ct != "" { 176 | w.Header().Set("Content-Type", ct) 177 | } 178 | w.Write(data) 179 | } 180 | } 181 | -------------------------------------------------------------------------------- /store/db/mongo/mongo.go: -------------------------------------------------------------------------------- 1 | package mongo 2 | 3 | import ( 4 | "errors" 5 | "log" 6 | "sync" 7 | "time" 8 | 9 | "github.com/Leon2012/goconfd/store" 10 | "github.com/Leon2012/goconfd/store/types" 11 | mgo "gopkg.in/mgo.v2" 12 | "gopkg.in/mgo.v2/bson" 13 | ) 14 | 15 | func init() { 16 | store.Register("mongo", NewMongoAdapter()) 17 | } 18 | 19 | type MongoAdapter struct { 20 | Conf MongoConfig 21 | Session *mgo.Session 22 | isOpen bool 23 | m sync.Mutex 24 | } 25 | 26 | type MongoConfig struct { 27 | Url string 28 | DbName string 29 | Timeout int 30 | Username string 31 | Password string 32 | } 33 | 34 | func NewMongoAdapter() *MongoAdapter { 35 | return &MongoAdapter{} 36 | } 37 | 38 | func (a *MongoAdapter) Open(c interface{}) error { 39 | config, ok := c.(MongoConfig) 40 | if !ok { 41 | return errors.New("Invaid Config") 42 | } 43 | timeout := time.Duration(config.Timeout) * time.Second 44 | session, err := mgo.DialWithTimeout(config.Url, timeout) 45 | if err != nil { 46 | return err 47 | } 48 | session.SetMode(mgo.Monotonic, true) 49 | 50 | a.Session = session 51 | a.Conf = config 52 | a.isOpen = true 53 | return nil 54 | } 55 | 56 | func (a *MongoAdapter) Close() error { 57 | a.Session.Close() 58 | a.isOpen = false 59 | return nil 60 | } 61 | 62 | func (a *MongoAdapter) IsOpen() bool { 63 | return a.isOpen 64 | } 65 | 66 | func (a *MongoAdapter) Online(agent *types.Agent) error { 67 | var err error 68 | now := time.Now() 69 | selector := types.Agent{} 70 | agent.Status = 1 71 | a.run("agents", func(c *mgo.Collection) { 72 | err = c.Find(bson.M{"hostname": agent.HostName, "keyprefix": agent.KeyPrefix}).One(&selector) 73 | }) 74 | if err != nil { //未查到记录 75 | agent.CreatedAt = now 76 | a.run("agents", func(c *mgo.Collection) { 77 | err = c.Insert(agent) 78 | }) 79 | } else { 80 | data := bson.M{"$set": bson.M{"status": 1, "updatedat": now}} 81 | a.run("agents", func(c *mgo.Collection) { 82 | err = c.Update(selector, data) 83 | }) 84 | } 85 | return err 86 | } 87 | 88 | func (a *MongoAdapter) Offline(agent *types.Agent) error { 89 | var err error 90 | now := time.Now() 91 | selector := bson.M{"hostname": agent.HostName, "keyprefix": agent.KeyPrefix} 92 | data := bson.M{"$set": bson.M{"status": 0, "updatedat": now}} 93 | a.run("agents", func(c *mgo.Collection) { 94 | err = c.Update(selector, data) 95 | }) 96 | return err 97 | } 98 | 99 | func (a *MongoAdapter) Heartbeat(log *types.Heartbeat) error { 100 | var err error 101 | now := time.Now() 102 | selector := bson.M{"hostname": log.HostName, "keyprefix": log.KeyPrefix} 103 | data := bson.M{"$set": bson.M{"heartbeattime": now}} 104 | a.run("agents", func(c *mgo.Collection) { 105 | err = c.Update(selector, data) 106 | }) 107 | if err != nil { 108 | return err 109 | } 110 | log.CreatedAt = now 111 | a.run("heartbeats", func(c *mgo.Collection) { 112 | err = c.Insert(log) 113 | }) 114 | return err 115 | } 116 | 117 | func (a *MongoAdapter) GetAgents() ([]*types.Agent, error) { 118 | var results []*types.Agent 119 | var err error 120 | a.run("agents", func(c *mgo.Collection) { 121 | err = c.Find(nil).All(&results) 122 | }) 123 | if err != nil { 124 | return nil, err 125 | } else { 126 | return results, nil 127 | } 128 | } 129 | 130 | func (a *MongoAdapter) GetHeartbeats() ([]*types.Heartbeat, error) { 131 | var results []*types.Heartbeat 132 | var err error 133 | a.run("heartbeats", func(c *mgo.Collection) { 134 | err = c.Find(nil).Sort("-createdat").Limit(50).All(&results) 135 | }) 136 | if err != nil { 137 | return nil, err 138 | } else { 139 | return results, nil 140 | } 141 | } 142 | 143 | func (a *MongoAdapter) GetHeartbeatsByAgent(agent *types.Agent) ([]*types.Heartbeat, error) { 144 | var results []*types.Heartbeat 145 | var err error 146 | selector := bson.M{"hostname": agent.HostName, "keyprefix": agent.KeyPrefix} 147 | a.run("heartbeats", func(c *mgo.Collection) { 148 | err = c.Find(selector).Sort("-createdat").Limit(50).All(&results) 149 | }) 150 | if err != nil { 151 | return nil, err 152 | } else { 153 | return results, nil 154 | } 155 | } 156 | 157 | func (a *MongoAdapter) GetHeartbeatsByTime(time time.Time) ([]*types.Heartbeat, error) { 158 | var results []*types.Heartbeat 159 | var err error 160 | selector := bson.M{"CreatedAt": time} 161 | a.run("heartbeats", func(c *mgo.Collection) { 162 | err = c.Find(selector).All(&results) 163 | }) 164 | if err != nil { 165 | return nil, err 166 | } else { 167 | return results, nil 168 | } 169 | } 170 | 171 | func (a *MongoAdapter) clone() *mgo.Session { 172 | a.m.Lock() 173 | defer a.m.Unlock() 174 | newSession := a.Session.Clone() 175 | newSession.Refresh() 176 | return newSession 177 | } 178 | 179 | func (a *MongoAdapter) run(collection string, f func(*mgo.Collection)) { 180 | session := a.clone() 181 | defer func() { 182 | session.Clone() 183 | if err := recover(); err != nil { 184 | log.Fatal(err) 185 | } 186 | }() 187 | 188 | db := session.DB(a.Conf.DbName) 189 | if a.Conf.Username != "" { 190 | err := db.Login(a.Conf.Username, a.Conf.Password) 191 | if err != nil { 192 | log.Fatal(err) 193 | } 194 | } 195 | c := db.C(collection) 196 | f(c) 197 | } 198 | 199 | func (a *MongoAdapter) search(collection string, q interface{}, skip, limit int, sorts []string) (searchResults []interface{}, err error) { 200 | var query *mgo.Query 201 | a.run(collection, func(c *mgo.Collection) { 202 | if q != nil { 203 | query = c.Find(q) 204 | } else { 205 | query = c.Find(nil) 206 | } 207 | if skip > 0 { 208 | query = query.Skip(skip) 209 | } 210 | if limit > 0 { 211 | query = query.Limit(limit) 212 | } 213 | if sorts != nil && len(sorts) > 0 { 214 | query = query.Sort(sorts...) 215 | } 216 | err = query.All(&searchResults) 217 | }) 218 | return 219 | } 220 | -------------------------------------------------------------------------------- /registry/backend/etcd.go: -------------------------------------------------------------------------------- 1 | package backend 2 | 3 | import ( 4 | "fmt" 5 | "time" 6 | 7 | "sync" 8 | 9 | "errors" 10 | 11 | "github.com/Leon2012/goconfd/libs/kv" 12 | "github.com/coreos/etcd/clientv3" 13 | "golang.org/x/net/context" 14 | ) 15 | 16 | type prefixChan struct { 17 | p string 18 | c clientv3.WatchChan 19 | } 20 | 21 | type EtcdAdpater struct { 22 | client *clientv3.Client 23 | hosts []string 24 | requestTimeout time.Duration 25 | dialTimeout time.Duration 26 | wg sync.WaitGroup 27 | ttlMap map[string]*clientv3.LeaseGrantResponse 28 | } 29 | 30 | func NewEtcdAdpater(hosts []string, request, dial int) (*EtcdAdpater, error) { 31 | etcd := &EtcdAdpater{ 32 | hosts: hosts, 33 | requestTimeout: time.Duration(request) * time.Second, 34 | dialTimeout: time.Duration(dial) * time.Second, 35 | ttlMap: make(map[string]*clientv3.LeaseGrantResponse), 36 | } 37 | err := etcd.connect() 38 | if err != nil { 39 | return nil, err 40 | } 41 | return etcd, nil 42 | } 43 | 44 | func (e *EtcdAdpater) connect() error { 45 | cli, err := clientv3.New(clientv3.Config{ 46 | Endpoints: e.hosts, 47 | DialTimeout: e.dialTimeout, 48 | }) 49 | if err != nil { 50 | return err 51 | } 52 | e.client = cli 53 | return nil 54 | } 55 | 56 | func (e *EtcdAdpater) Del(k string) error { 57 | ctx, cancel := context.WithTimeout(context.Background(), e.requestTimeout) 58 | _, err := e.client.Delete(ctx, k) 59 | cancel() 60 | if err != nil { 61 | return err 62 | } 63 | delete(e.ttlMap, k) 64 | return nil 65 | } 66 | 67 | func (e *EtcdAdpater) Put(k, v string) error { 68 | ctx, cancel := context.WithTimeout(context.Background(), e.requestTimeout) 69 | _, err := e.client.Put(ctx, k, v) 70 | cancel() 71 | if err != nil { 72 | return err 73 | } 74 | return nil 75 | } 76 | 77 | func (e *EtcdAdpater) PutWithTTL(k, v string, ttl int64) error { 78 | leaseResp, err := e.client.Grant(context.TODO(), ttl) 79 | if err != nil { 80 | return err 81 | } 82 | ctx, cancel := context.WithTimeout(context.Background(), e.requestTimeout) 83 | _, err = e.client.Put(ctx, k, v, clientv3.WithLease(leaseResp.ID)) 84 | cancel() 85 | if err != nil { 86 | return err 87 | } 88 | e.ttlMap[k] = leaseResp 89 | return nil 90 | } 91 | 92 | func (e *EtcdAdpater) Get(k string) ([]*kv.Kv, error) { 93 | fmt.Println("get key:" + k) 94 | kvs := []*kv.Kv{} 95 | ctx, cancel := context.WithTimeout(context.Background(), e.requestTimeout) 96 | resp, err := e.client.Get(ctx, k) 97 | cancel() 98 | if err != nil { 99 | return kvs, err 100 | } 101 | revision := resp.Header.Revision 102 | for _, ev := range resp.Kvs { 103 | kv := kv.NewKv(0, string(ev.Key), string(ev.Value), revision) 104 | kvs = append(kvs, kv) 105 | } 106 | return kvs, nil 107 | } 108 | 109 | func (e *EtcdAdpater) SetTTL(k string) error { 110 | leaseResp, ok := e.ttlMap[k] 111 | if !ok { 112 | return errors.New("key not found TTL") 113 | } 114 | _, err := e.client.KeepAliveOnce(context.TODO(), leaseResp.ID) 115 | if err != nil { 116 | return err 117 | } 118 | return nil 119 | } 120 | 121 | func (e *EtcdAdpater) BatchGetByPrefix(prefix string) ([]*kv.Kv, error) { 122 | kvs := []*kv.Kv{} 123 | ctx, cancel := context.WithTimeout(context.Background(), e.requestTimeout) 124 | resp, err := e.client.Get(ctx, prefix, clientv3.WithPrefix(), clientv3.WithSort(clientv3.SortByKey, clientv3.SortAscend)) 125 | cancel() 126 | if err != nil { 127 | return kvs, err 128 | } 129 | revision := resp.Header.Revision 130 | for _, ev := range resp.Kvs { 131 | kv := kv.NewKv(0, string(ev.Key), string(ev.Value), revision) 132 | kvs = append(kvs, kv) 133 | } 134 | return kvs, nil 135 | } 136 | 137 | func (e *EtcdAdpater) WatchWithKey(key string, f kv.KvFunc) { 138 | rch := e.client.Watch(context.Background(), key) 139 | for wresp := range rch { 140 | revision := wresp.Header.Revision 141 | for _, ev := range wresp.Events { 142 | kv := kv.NewKv(int32(ev.Type), string(ev.Kv.Key), string(ev.Kv.Value), revision) 143 | f(kv, key) 144 | } 145 | } 146 | } 147 | 148 | func (e *EtcdAdpater) WatchWithPrefix(prefix string, f kv.KvFunc) { 149 | rch := e.client.Watch(context.Background(), prefix, clientv3.WithPrefix()) 150 | for wresp := range rch { 151 | revision := wresp.Header.Revision 152 | for _, ev := range wresp.Events { 153 | kv := kv.NewKv(int32(ev.Type), string(ev.Kv.Key), string(ev.Kv.Value), revision) 154 | f(kv, prefix) 155 | } 156 | } 157 | } 158 | 159 | func (e *EtcdAdpater) WatchWithKeys(keys []string, f kv.KvFunc) { 160 | pcs := []*prefixChan{} 161 | for _, key := range keys { 162 | rch := e.client.Watch(context.Background(), key) 163 | pc := &prefixChan{ 164 | p: key, 165 | c: rch, 166 | } 167 | pcs = append(pcs, pc) 168 | } 169 | e.processChans(pcs, f) 170 | } 171 | 172 | func (e *EtcdAdpater) WatchWithPrefixs(prefixs []string, f kv.KvFunc) { 173 | pcs := []*prefixChan{} 174 | for _, prefix := range prefixs { 175 | rch := e.client.Watch(context.Background(), prefix, clientv3.WithPrefix()) 176 | pc := &prefixChan{ 177 | p: prefix, 178 | c: rch, 179 | } 180 | pcs = append(pcs, pc) 181 | } 182 | e.processChans(pcs, f) 183 | } 184 | 185 | func (e *EtcdAdpater) processChans(pcs []*prefixChan, f kv.KvFunc) { 186 | n := len(pcs) 187 | e.wg.Add(n) 188 | for i, pc := range pcs { 189 | p := pc.p 190 | c := pc.c 191 | go e.processChan(i, c, p, f) 192 | } 193 | } 194 | 195 | func (e *EtcdAdpater) processChan(i int, c clientv3.WatchChan, p string, f kv.KvFunc) { 196 | for wresp := range c { 197 | revision := wresp.Header.Revision 198 | for _, ev := range wresp.Events { 199 | kv := kv.NewKv(int32(ev.Type), string(ev.Kv.Key), string(ev.Kv.Value), revision) 200 | f(kv, p) 201 | } 202 | } 203 | e.wg.Done() 204 | } 205 | 206 | func (e *EtcdAdpater) Close() { 207 | if e.client != nil { 208 | e.client.Close() 209 | } 210 | e.wg.Wait() 211 | } 212 | -------------------------------------------------------------------------------- /dashboard/template/asset/js/morris-data.js: -------------------------------------------------------------------------------- 1 | // Morris.js Charts sample data for SB Admin template 2 | 3 | $(function() { 4 | 5 | // Area Chart 6 | Morris.Area({ 7 | element: 'morris-area-chart', 8 | data: [{ 9 | period: '2010 Q1', 10 | iphone: 2666, 11 | ipad: null, 12 | itouch: 2647 13 | }, { 14 | period: '2010 Q2', 15 | iphone: 2778, 16 | ipad: 2294, 17 | itouch: 2441 18 | }, { 19 | period: '2010 Q3', 20 | iphone: 4912, 21 | ipad: 1969, 22 | itouch: 2501 23 | }, { 24 | period: '2010 Q4', 25 | iphone: 3767, 26 | ipad: 3597, 27 | itouch: 5689 28 | }, { 29 | period: '2011 Q1', 30 | iphone: 6810, 31 | ipad: 1914, 32 | itouch: 2293 33 | }, { 34 | period: '2011 Q2', 35 | iphone: 5670, 36 | ipad: 4293, 37 | itouch: 1881 38 | }, { 39 | period: '2011 Q3', 40 | iphone: 4820, 41 | ipad: 3795, 42 | itouch: 1588 43 | }, { 44 | period: '2011 Q4', 45 | iphone: 15073, 46 | ipad: 5967, 47 | itouch: 5175 48 | }, { 49 | period: '2012 Q1', 50 | iphone: 10687, 51 | ipad: 4460, 52 | itouch: 2028 53 | }, { 54 | period: '2012 Q2', 55 | iphone: 8432, 56 | ipad: 5713, 57 | itouch: 1791 58 | }], 59 | xkey: 'period', 60 | ykeys: ['iphone', 'ipad', 'itouch'], 61 | labels: ['iPhone', 'iPad', 'iPod Touch'], 62 | pointSize: 2, 63 | hideHover: 'auto', 64 | resize: true 65 | }); 66 | 67 | // Donut Chart 68 | Morris.Donut({ 69 | element: 'morris-donut-chart', 70 | data: [{ 71 | label: "Download Sales", 72 | value: 12 73 | }, { 74 | label: "In-Store Sales", 75 | value: 30 76 | }, { 77 | label: "Mail-Order Sales", 78 | value: 20 79 | }], 80 | resize: true 81 | }); 82 | 83 | // Line Chart 84 | Morris.Line({ 85 | // ID of the element in which to draw the chart. 86 | element: 'morris-line-chart', 87 | // Chart data records -- each entry in this array corresponds to a point on 88 | // the chart. 89 | data: [{ 90 | d: '2012-10-01', 91 | visits: 802 92 | }, { 93 | d: '2012-10-02', 94 | visits: 783 95 | }, { 96 | d: '2012-10-03', 97 | visits: 820 98 | }, { 99 | d: '2012-10-04', 100 | visits: 839 101 | }, { 102 | d: '2012-10-05', 103 | visits: 792 104 | }, { 105 | d: '2012-10-06', 106 | visits: 859 107 | }, { 108 | d: '2012-10-07', 109 | visits: 790 110 | }, { 111 | d: '2012-10-08', 112 | visits: 1680 113 | }, { 114 | d: '2012-10-09', 115 | visits: 1592 116 | }, { 117 | d: '2012-10-10', 118 | visits: 1420 119 | }, { 120 | d: '2012-10-11', 121 | visits: 882 122 | }, { 123 | d: '2012-10-12', 124 | visits: 889 125 | }, { 126 | d: '2012-10-13', 127 | visits: 819 128 | }, { 129 | d: '2012-10-14', 130 | visits: 849 131 | }, { 132 | d: '2012-10-15', 133 | visits: 870 134 | }, { 135 | d: '2012-10-16', 136 | visits: 1063 137 | }, { 138 | d: '2012-10-17', 139 | visits: 1192 140 | }, { 141 | d: '2012-10-18', 142 | visits: 1224 143 | }, { 144 | d: '2012-10-19', 145 | visits: 1329 146 | }, { 147 | d: '2012-10-20', 148 | visits: 1329 149 | }, { 150 | d: '2012-10-21', 151 | visits: 1239 152 | }, { 153 | d: '2012-10-22', 154 | visits: 1190 155 | }, { 156 | d: '2012-10-23', 157 | visits: 1312 158 | }, { 159 | d: '2012-10-24', 160 | visits: 1293 161 | }, { 162 | d: '2012-10-25', 163 | visits: 1283 164 | }, { 165 | d: '2012-10-26', 166 | visits: 1248 167 | }, { 168 | d: '2012-10-27', 169 | visits: 1323 170 | }, { 171 | d: '2012-10-28', 172 | visits: 1390 173 | }, { 174 | d: '2012-10-29', 175 | visits: 1420 176 | }, { 177 | d: '2012-10-30', 178 | visits: 1529 179 | }, { 180 | d: '2012-10-31', 181 | visits: 1892 182 | }, ], 183 | // The name of the data record attribute that contains x-visitss. 184 | xkey: 'd', 185 | // A list of names of data record attributes that contain y-visitss. 186 | ykeys: ['visits'], 187 | // Labels for the ykeys -- will be displayed when you hover over the 188 | // chart. 189 | labels: ['Visits'], 190 | // Disables line smoothing 191 | smooth: false, 192 | resize: true 193 | }); 194 | 195 | // Bar Chart 196 | Morris.Bar({ 197 | element: 'morris-bar-chart', 198 | data: [{ 199 | device: 'iPhone', 200 | geekbench: 136 201 | }, { 202 | device: 'iPhone 3G', 203 | geekbench: 137 204 | }, { 205 | device: 'iPhone 3GS', 206 | geekbench: 275 207 | }, { 208 | device: 'iPhone 4', 209 | geekbench: 380 210 | }, { 211 | device: 'iPhone 4S', 212 | geekbench: 655 213 | }, { 214 | device: 'iPhone 5', 215 | geekbench: 1571 216 | }], 217 | xkey: 'device', 218 | ykeys: ['geekbench'], 219 | labels: ['Geekbench'], 220 | barRatio: 0.4, 221 | xLabelAngle: 35, 222 | hideHover: 'auto', 223 | resize: true 224 | }); 225 | 226 | 227 | }); 228 | -------------------------------------------------------------------------------- /dashboard/template/asset/font-awesome/less/variables.less: -------------------------------------------------------------------------------- 1 | // Variables 2 | // -------------------------- 3 | 4 | @fa-font-path: "../fonts"; 5 | //@fa-font-path: "//netdna.bootstrapcdn.com/font-awesome/4.2.0/fonts"; // for referencing Bootstrap CDN font files directly 6 | @fa-css-prefix: fa; 7 | @fa-version: "4.2.0"; 8 | @fa-border-color: #eee; 9 | @fa-inverse: #fff; 10 | @fa-li-width: (30em / 14); 11 | 12 | @fa-var-adjust: "\f042"; 13 | @fa-var-adn: "\f170"; 14 | @fa-var-align-center: "\f037"; 15 | @fa-var-align-justify: "\f039"; 16 | @fa-var-align-left: "\f036"; 17 | @fa-var-align-right: "\f038"; 18 | @fa-var-ambulance: "\f0f9"; 19 | @fa-var-anchor: "\f13d"; 20 | @fa-var-android: "\f17b"; 21 | @fa-var-angellist: "\f209"; 22 | @fa-var-angle-double-down: "\f103"; 23 | @fa-var-angle-double-left: "\f100"; 24 | @fa-var-angle-double-right: "\f101"; 25 | @fa-var-angle-double-up: "\f102"; 26 | @fa-var-angle-down: "\f107"; 27 | @fa-var-angle-left: "\f104"; 28 | @fa-var-angle-right: "\f105"; 29 | @fa-var-angle-up: "\f106"; 30 | @fa-var-apple: "\f179"; 31 | @fa-var-archive: "\f187"; 32 | @fa-var-area-chart: "\f1fe"; 33 | @fa-var-arrow-circle-down: "\f0ab"; 34 | @fa-var-arrow-circle-left: "\f0a8"; 35 | @fa-var-arrow-circle-o-down: "\f01a"; 36 | @fa-var-arrow-circle-o-left: "\f190"; 37 | @fa-var-arrow-circle-o-right: "\f18e"; 38 | @fa-var-arrow-circle-o-up: "\f01b"; 39 | @fa-var-arrow-circle-right: "\f0a9"; 40 | @fa-var-arrow-circle-up: "\f0aa"; 41 | @fa-var-arrow-down: "\f063"; 42 | @fa-var-arrow-left: "\f060"; 43 | @fa-var-arrow-right: "\f061"; 44 | @fa-var-arrow-up: "\f062"; 45 | @fa-var-arrows: "\f047"; 46 | @fa-var-arrows-alt: "\f0b2"; 47 | @fa-var-arrows-h: "\f07e"; 48 | @fa-var-arrows-v: "\f07d"; 49 | @fa-var-asterisk: "\f069"; 50 | @fa-var-at: "\f1fa"; 51 | @fa-var-automobile: "\f1b9"; 52 | @fa-var-backward: "\f04a"; 53 | @fa-var-ban: "\f05e"; 54 | @fa-var-bank: "\f19c"; 55 | @fa-var-bar-chart: "\f080"; 56 | @fa-var-bar-chart-o: "\f080"; 57 | @fa-var-barcode: "\f02a"; 58 | @fa-var-bars: "\f0c9"; 59 | @fa-var-beer: "\f0fc"; 60 | @fa-var-behance: "\f1b4"; 61 | @fa-var-behance-square: "\f1b5"; 62 | @fa-var-bell: "\f0f3"; 63 | @fa-var-bell-o: "\f0a2"; 64 | @fa-var-bell-slash: "\f1f6"; 65 | @fa-var-bell-slash-o: "\f1f7"; 66 | @fa-var-bicycle: "\f206"; 67 | @fa-var-binoculars: "\f1e5"; 68 | @fa-var-birthday-cake: "\f1fd"; 69 | @fa-var-bitbucket: "\f171"; 70 | @fa-var-bitbucket-square: "\f172"; 71 | @fa-var-bitcoin: "\f15a"; 72 | @fa-var-bold: "\f032"; 73 | @fa-var-bolt: "\f0e7"; 74 | @fa-var-bomb: "\f1e2"; 75 | @fa-var-book: "\f02d"; 76 | @fa-var-bookmark: "\f02e"; 77 | @fa-var-bookmark-o: "\f097"; 78 | @fa-var-briefcase: "\f0b1"; 79 | @fa-var-btc: "\f15a"; 80 | @fa-var-bug: "\f188"; 81 | @fa-var-building: "\f1ad"; 82 | @fa-var-building-o: "\f0f7"; 83 | @fa-var-bullhorn: "\f0a1"; 84 | @fa-var-bullseye: "\f140"; 85 | @fa-var-bus: "\f207"; 86 | @fa-var-cab: "\f1ba"; 87 | @fa-var-calculator: "\f1ec"; 88 | @fa-var-calendar: "\f073"; 89 | @fa-var-calendar-o: "\f133"; 90 | @fa-var-camera: "\f030"; 91 | @fa-var-camera-retro: "\f083"; 92 | @fa-var-car: "\f1b9"; 93 | @fa-var-caret-down: "\f0d7"; 94 | @fa-var-caret-left: "\f0d9"; 95 | @fa-var-caret-right: "\f0da"; 96 | @fa-var-caret-square-o-down: "\f150"; 97 | @fa-var-caret-square-o-left: "\f191"; 98 | @fa-var-caret-square-o-right: "\f152"; 99 | @fa-var-caret-square-o-up: "\f151"; 100 | @fa-var-caret-up: "\f0d8"; 101 | @fa-var-cc: "\f20a"; 102 | @fa-var-cc-amex: "\f1f3"; 103 | @fa-var-cc-discover: "\f1f2"; 104 | @fa-var-cc-mastercard: "\f1f1"; 105 | @fa-var-cc-paypal: "\f1f4"; 106 | @fa-var-cc-stripe: "\f1f5"; 107 | @fa-var-cc-visa: "\f1f0"; 108 | @fa-var-certificate: "\f0a3"; 109 | @fa-var-chain: "\f0c1"; 110 | @fa-var-chain-broken: "\f127"; 111 | @fa-var-check: "\f00c"; 112 | @fa-var-check-circle: "\f058"; 113 | @fa-var-check-circle-o: "\f05d"; 114 | @fa-var-check-square: "\f14a"; 115 | @fa-var-check-square-o: "\f046"; 116 | @fa-var-chevron-circle-down: "\f13a"; 117 | @fa-var-chevron-circle-left: "\f137"; 118 | @fa-var-chevron-circle-right: "\f138"; 119 | @fa-var-chevron-circle-up: "\f139"; 120 | @fa-var-chevron-down: "\f078"; 121 | @fa-var-chevron-left: "\f053"; 122 | @fa-var-chevron-right: "\f054"; 123 | @fa-var-chevron-up: "\f077"; 124 | @fa-var-child: "\f1ae"; 125 | @fa-var-circle: "\f111"; 126 | @fa-var-circle-o: "\f10c"; 127 | @fa-var-circle-o-notch: "\f1ce"; 128 | @fa-var-circle-thin: "\f1db"; 129 | @fa-var-clipboard: "\f0ea"; 130 | @fa-var-clock-o: "\f017"; 131 | @fa-var-close: "\f00d"; 132 | @fa-var-cloud: "\f0c2"; 133 | @fa-var-cloud-download: "\f0ed"; 134 | @fa-var-cloud-upload: "\f0ee"; 135 | @fa-var-cny: "\f157"; 136 | @fa-var-code: "\f121"; 137 | @fa-var-code-fork: "\f126"; 138 | @fa-var-codepen: "\f1cb"; 139 | @fa-var-coffee: "\f0f4"; 140 | @fa-var-cog: "\f013"; 141 | @fa-var-cogs: "\f085"; 142 | @fa-var-columns: "\f0db"; 143 | @fa-var-comment: "\f075"; 144 | @fa-var-comment-o: "\f0e5"; 145 | @fa-var-comments: "\f086"; 146 | @fa-var-comments-o: "\f0e6"; 147 | @fa-var-compass: "\f14e"; 148 | @fa-var-compress: "\f066"; 149 | @fa-var-copy: "\f0c5"; 150 | @fa-var-copyright: "\f1f9"; 151 | @fa-var-credit-card: "\f09d"; 152 | @fa-var-crop: "\f125"; 153 | @fa-var-crosshairs: "\f05b"; 154 | @fa-var-css3: "\f13c"; 155 | @fa-var-cube: "\f1b2"; 156 | @fa-var-cubes: "\f1b3"; 157 | @fa-var-cut: "\f0c4"; 158 | @fa-var-cutlery: "\f0f5"; 159 | @fa-var-dashboard: "\f0e4"; 160 | @fa-var-database: "\f1c0"; 161 | @fa-var-dedent: "\f03b"; 162 | @fa-var-delicious: "\f1a5"; 163 | @fa-var-desktop: "\f108"; 164 | @fa-var-deviantart: "\f1bd"; 165 | @fa-var-digg: "\f1a6"; 166 | @fa-var-dollar: "\f155"; 167 | @fa-var-dot-circle-o: "\f192"; 168 | @fa-var-download: "\f019"; 169 | @fa-var-dribbble: "\f17d"; 170 | @fa-var-dropbox: "\f16b"; 171 | @fa-var-drupal: "\f1a9"; 172 | @fa-var-edit: "\f044"; 173 | @fa-var-eject: "\f052"; 174 | @fa-var-ellipsis-h: "\f141"; 175 | @fa-var-ellipsis-v: "\f142"; 176 | @fa-var-empire: "\f1d1"; 177 | @fa-var-envelope: "\f0e0"; 178 | @fa-var-envelope-o: "\f003"; 179 | @fa-var-envelope-square: "\f199"; 180 | @fa-var-eraser: "\f12d"; 181 | @fa-var-eur: "\f153"; 182 | @fa-var-euro: "\f153"; 183 | @fa-var-exchange: "\f0ec"; 184 | @fa-var-exclamation: "\f12a"; 185 | @fa-var-exclamation-circle: "\f06a"; 186 | @fa-var-exclamation-triangle: "\f071"; 187 | @fa-var-expand: "\f065"; 188 | @fa-var-external-link: "\f08e"; 189 | @fa-var-external-link-square: "\f14c"; 190 | @fa-var-eye: "\f06e"; 191 | @fa-var-eye-slash: "\f070"; 192 | @fa-var-eyedropper: "\f1fb"; 193 | @fa-var-facebook: "\f09a"; 194 | @fa-var-facebook-square: "\f082"; 195 | @fa-var-fast-backward: "\f049"; 196 | @fa-var-fast-forward: "\f050"; 197 | @fa-var-fax: "\f1ac"; 198 | @fa-var-female: "\f182"; 199 | @fa-var-fighter-jet: "\f0fb"; 200 | @fa-var-file: "\f15b"; 201 | @fa-var-file-archive-o: "\f1c6"; 202 | @fa-var-file-audio-o: "\f1c7"; 203 | @fa-var-file-code-o: "\f1c9"; 204 | @fa-var-file-excel-o: "\f1c3"; 205 | @fa-var-file-image-o: "\f1c5"; 206 | @fa-var-file-movie-o: "\f1c8"; 207 | @fa-var-file-o: "\f016"; 208 | @fa-var-file-pdf-o: "\f1c1"; 209 | @fa-var-file-photo-o: "\f1c5"; 210 | @fa-var-file-picture-o: "\f1c5"; 211 | @fa-var-file-powerpoint-o: "\f1c4"; 212 | @fa-var-file-sound-o: "\f1c7"; 213 | @fa-var-file-text: "\f15c"; 214 | @fa-var-file-text-o: "\f0f6"; 215 | @fa-var-file-video-o: "\f1c8"; 216 | @fa-var-file-word-o: "\f1c2"; 217 | @fa-var-file-zip-o: "\f1c6"; 218 | @fa-var-files-o: "\f0c5"; 219 | @fa-var-film: "\f008"; 220 | @fa-var-filter: "\f0b0"; 221 | @fa-var-fire: "\f06d"; 222 | @fa-var-fire-extinguisher: "\f134"; 223 | @fa-var-flag: "\f024"; 224 | @fa-var-flag-checkered: "\f11e"; 225 | @fa-var-flag-o: "\f11d"; 226 | @fa-var-flash: "\f0e7"; 227 | @fa-var-flask: "\f0c3"; 228 | @fa-var-flickr: "\f16e"; 229 | @fa-var-floppy-o: "\f0c7"; 230 | @fa-var-folder: "\f07b"; 231 | @fa-var-folder-o: "\f114"; 232 | @fa-var-folder-open: "\f07c"; 233 | @fa-var-folder-open-o: "\f115"; 234 | @fa-var-font: "\f031"; 235 | @fa-var-forward: "\f04e"; 236 | @fa-var-foursquare: "\f180"; 237 | @fa-var-frown-o: "\f119"; 238 | @fa-var-futbol-o: "\f1e3"; 239 | @fa-var-gamepad: "\f11b"; 240 | @fa-var-gavel: "\f0e3"; 241 | @fa-var-gbp: "\f154"; 242 | @fa-var-ge: "\f1d1"; 243 | @fa-var-gear: "\f013"; 244 | @fa-var-gears: "\f085"; 245 | @fa-var-gift: "\f06b"; 246 | @fa-var-git: "\f1d3"; 247 | @fa-var-git-square: "\f1d2"; 248 | @fa-var-github: "\f09b"; 249 | @fa-var-github-alt: "\f113"; 250 | @fa-var-github-square: "\f092"; 251 | @fa-var-gittip: "\f184"; 252 | @fa-var-glass: "\f000"; 253 | @fa-var-globe: "\f0ac"; 254 | @fa-var-google: "\f1a0"; 255 | @fa-var-google-plus: "\f0d5"; 256 | @fa-var-google-plus-square: "\f0d4"; 257 | @fa-var-google-wallet: "\f1ee"; 258 | @fa-var-graduation-cap: "\f19d"; 259 | @fa-var-group: "\f0c0"; 260 | @fa-var-h-square: "\f0fd"; 261 | @fa-var-hacker-news: "\f1d4"; 262 | @fa-var-hand-o-down: "\f0a7"; 263 | @fa-var-hand-o-left: "\f0a5"; 264 | @fa-var-hand-o-right: "\f0a4"; 265 | @fa-var-hand-o-up: "\f0a6"; 266 | @fa-var-hdd-o: "\f0a0"; 267 | @fa-var-header: "\f1dc"; 268 | @fa-var-headphones: "\f025"; 269 | @fa-var-heart: "\f004"; 270 | @fa-var-heart-o: "\f08a"; 271 | @fa-var-history: "\f1da"; 272 | @fa-var-home: "\f015"; 273 | @fa-var-hospital-o: "\f0f8"; 274 | @fa-var-html5: "\f13b"; 275 | @fa-var-ils: "\f20b"; 276 | @fa-var-image: "\f03e"; 277 | @fa-var-inbox: "\f01c"; 278 | @fa-var-indent: "\f03c"; 279 | @fa-var-info: "\f129"; 280 | @fa-var-info-circle: "\f05a"; 281 | @fa-var-inr: "\f156"; 282 | @fa-var-instagram: "\f16d"; 283 | @fa-var-institution: "\f19c"; 284 | @fa-var-ioxhost: "\f208"; 285 | @fa-var-italic: "\f033"; 286 | @fa-var-joomla: "\f1aa"; 287 | @fa-var-jpy: "\f157"; 288 | @fa-var-jsfiddle: "\f1cc"; 289 | @fa-var-key: "\f084"; 290 | @fa-var-keyboard-o: "\f11c"; 291 | @fa-var-krw: "\f159"; 292 | @fa-var-language: "\f1ab"; 293 | @fa-var-laptop: "\f109"; 294 | @fa-var-lastfm: "\f202"; 295 | @fa-var-lastfm-square: "\f203"; 296 | @fa-var-leaf: "\f06c"; 297 | @fa-var-legal: "\f0e3"; 298 | @fa-var-lemon-o: "\f094"; 299 | @fa-var-level-down: "\f149"; 300 | @fa-var-level-up: "\f148"; 301 | @fa-var-life-bouy: "\f1cd"; 302 | @fa-var-life-buoy: "\f1cd"; 303 | @fa-var-life-ring: "\f1cd"; 304 | @fa-var-life-saver: "\f1cd"; 305 | @fa-var-lightbulb-o: "\f0eb"; 306 | @fa-var-line-chart: "\f201"; 307 | @fa-var-link: "\f0c1"; 308 | @fa-var-linkedin: "\f0e1"; 309 | @fa-var-linkedin-square: "\f08c"; 310 | @fa-var-linux: "\f17c"; 311 | @fa-var-list: "\f03a"; 312 | @fa-var-list-alt: "\f022"; 313 | @fa-var-list-ol: "\f0cb"; 314 | @fa-var-list-ul: "\f0ca"; 315 | @fa-var-location-arrow: "\f124"; 316 | @fa-var-lock: "\f023"; 317 | @fa-var-long-arrow-down: "\f175"; 318 | @fa-var-long-arrow-left: "\f177"; 319 | @fa-var-long-arrow-right: "\f178"; 320 | @fa-var-long-arrow-up: "\f176"; 321 | @fa-var-magic: "\f0d0"; 322 | @fa-var-magnet: "\f076"; 323 | @fa-var-mail-forward: "\f064"; 324 | @fa-var-mail-reply: "\f112"; 325 | @fa-var-mail-reply-all: "\f122"; 326 | @fa-var-male: "\f183"; 327 | @fa-var-map-marker: "\f041"; 328 | @fa-var-maxcdn: "\f136"; 329 | @fa-var-meanpath: "\f20c"; 330 | @fa-var-medkit: "\f0fa"; 331 | @fa-var-meh-o: "\f11a"; 332 | @fa-var-microphone: "\f130"; 333 | @fa-var-microphone-slash: "\f131"; 334 | @fa-var-minus: "\f068"; 335 | @fa-var-minus-circle: "\f056"; 336 | @fa-var-minus-square: "\f146"; 337 | @fa-var-minus-square-o: "\f147"; 338 | @fa-var-mobile: "\f10b"; 339 | @fa-var-mobile-phone: "\f10b"; 340 | @fa-var-money: "\f0d6"; 341 | @fa-var-moon-o: "\f186"; 342 | @fa-var-mortar-board: "\f19d"; 343 | @fa-var-music: "\f001"; 344 | @fa-var-navicon: "\f0c9"; 345 | @fa-var-newspaper-o: "\f1ea"; 346 | @fa-var-openid: "\f19b"; 347 | @fa-var-outdent: "\f03b"; 348 | @fa-var-pagelines: "\f18c"; 349 | @fa-var-paint-brush: "\f1fc"; 350 | @fa-var-paper-plane: "\f1d8"; 351 | @fa-var-paper-plane-o: "\f1d9"; 352 | @fa-var-paperclip: "\f0c6"; 353 | @fa-var-paragraph: "\f1dd"; 354 | @fa-var-paste: "\f0ea"; 355 | @fa-var-pause: "\f04c"; 356 | @fa-var-paw: "\f1b0"; 357 | @fa-var-paypal: "\f1ed"; 358 | @fa-var-pencil: "\f040"; 359 | @fa-var-pencil-square: "\f14b"; 360 | @fa-var-pencil-square-o: "\f044"; 361 | @fa-var-phone: "\f095"; 362 | @fa-var-phone-square: "\f098"; 363 | @fa-var-photo: "\f03e"; 364 | @fa-var-picture-o: "\f03e"; 365 | @fa-var-pie-chart: "\f200"; 366 | @fa-var-pied-piper: "\f1a7"; 367 | @fa-var-pied-piper-alt: "\f1a8"; 368 | @fa-var-pinterest: "\f0d2"; 369 | @fa-var-pinterest-square: "\f0d3"; 370 | @fa-var-plane: "\f072"; 371 | @fa-var-play: "\f04b"; 372 | @fa-var-play-circle: "\f144"; 373 | @fa-var-play-circle-o: "\f01d"; 374 | @fa-var-plug: "\f1e6"; 375 | @fa-var-plus: "\f067"; 376 | @fa-var-plus-circle: "\f055"; 377 | @fa-var-plus-square: "\f0fe"; 378 | @fa-var-plus-square-o: "\f196"; 379 | @fa-var-power-off: "\f011"; 380 | @fa-var-print: "\f02f"; 381 | @fa-var-puzzle-piece: "\f12e"; 382 | @fa-var-qq: "\f1d6"; 383 | @fa-var-qrcode: "\f029"; 384 | @fa-var-question: "\f128"; 385 | @fa-var-question-circle: "\f059"; 386 | @fa-var-quote-left: "\f10d"; 387 | @fa-var-quote-right: "\f10e"; 388 | @fa-var-ra: "\f1d0"; 389 | @fa-var-random: "\f074"; 390 | @fa-var-rebel: "\f1d0"; 391 | @fa-var-recycle: "\f1b8"; 392 | @fa-var-reddit: "\f1a1"; 393 | @fa-var-reddit-square: "\f1a2"; 394 | @fa-var-refresh: "\f021"; 395 | @fa-var-remove: "\f00d"; 396 | @fa-var-renren: "\f18b"; 397 | @fa-var-reorder: "\f0c9"; 398 | @fa-var-repeat: "\f01e"; 399 | @fa-var-reply: "\f112"; 400 | @fa-var-reply-all: "\f122"; 401 | @fa-var-retweet: "\f079"; 402 | @fa-var-rmb: "\f157"; 403 | @fa-var-road: "\f018"; 404 | @fa-var-rocket: "\f135"; 405 | @fa-var-rotate-left: "\f0e2"; 406 | @fa-var-rotate-right: "\f01e"; 407 | @fa-var-rouble: "\f158"; 408 | @fa-var-rss: "\f09e"; 409 | @fa-var-rss-square: "\f143"; 410 | @fa-var-rub: "\f158"; 411 | @fa-var-ruble: "\f158"; 412 | @fa-var-rupee: "\f156"; 413 | @fa-var-save: "\f0c7"; 414 | @fa-var-scissors: "\f0c4"; 415 | @fa-var-search: "\f002"; 416 | @fa-var-search-minus: "\f010"; 417 | @fa-var-search-plus: "\f00e"; 418 | @fa-var-send: "\f1d8"; 419 | @fa-var-send-o: "\f1d9"; 420 | @fa-var-share: "\f064"; 421 | @fa-var-share-alt: "\f1e0"; 422 | @fa-var-share-alt-square: "\f1e1"; 423 | @fa-var-share-square: "\f14d"; 424 | @fa-var-share-square-o: "\f045"; 425 | @fa-var-shekel: "\f20b"; 426 | @fa-var-sheqel: "\f20b"; 427 | @fa-var-shield: "\f132"; 428 | @fa-var-shopping-cart: "\f07a"; 429 | @fa-var-sign-in: "\f090"; 430 | @fa-var-sign-out: "\f08b"; 431 | @fa-var-signal: "\f012"; 432 | @fa-var-sitemap: "\f0e8"; 433 | @fa-var-skype: "\f17e"; 434 | @fa-var-slack: "\f198"; 435 | @fa-var-sliders: "\f1de"; 436 | @fa-var-slideshare: "\f1e7"; 437 | @fa-var-smile-o: "\f118"; 438 | @fa-var-soccer-ball-o: "\f1e3"; 439 | @fa-var-sort: "\f0dc"; 440 | @fa-var-sort-alpha-asc: "\f15d"; 441 | @fa-var-sort-alpha-desc: "\f15e"; 442 | @fa-var-sort-amount-asc: "\f160"; 443 | @fa-var-sort-amount-desc: "\f161"; 444 | @fa-var-sort-asc: "\f0de"; 445 | @fa-var-sort-desc: "\f0dd"; 446 | @fa-var-sort-down: "\f0dd"; 447 | @fa-var-sort-numeric-asc: "\f162"; 448 | @fa-var-sort-numeric-desc: "\f163"; 449 | @fa-var-sort-up: "\f0de"; 450 | @fa-var-soundcloud: "\f1be"; 451 | @fa-var-space-shuttle: "\f197"; 452 | @fa-var-spinner: "\f110"; 453 | @fa-var-spoon: "\f1b1"; 454 | @fa-var-spotify: "\f1bc"; 455 | @fa-var-square: "\f0c8"; 456 | @fa-var-square-o: "\f096"; 457 | @fa-var-stack-exchange: "\f18d"; 458 | @fa-var-stack-overflow: "\f16c"; 459 | @fa-var-star: "\f005"; 460 | @fa-var-star-half: "\f089"; 461 | @fa-var-star-half-empty: "\f123"; 462 | @fa-var-star-half-full: "\f123"; 463 | @fa-var-star-half-o: "\f123"; 464 | @fa-var-star-o: "\f006"; 465 | @fa-var-steam: "\f1b6"; 466 | @fa-var-steam-square: "\f1b7"; 467 | @fa-var-step-backward: "\f048"; 468 | @fa-var-step-forward: "\f051"; 469 | @fa-var-stethoscope: "\f0f1"; 470 | @fa-var-stop: "\f04d"; 471 | @fa-var-strikethrough: "\f0cc"; 472 | @fa-var-stumbleupon: "\f1a4"; 473 | @fa-var-stumbleupon-circle: "\f1a3"; 474 | @fa-var-subscript: "\f12c"; 475 | @fa-var-suitcase: "\f0f2"; 476 | @fa-var-sun-o: "\f185"; 477 | @fa-var-superscript: "\f12b"; 478 | @fa-var-support: "\f1cd"; 479 | @fa-var-table: "\f0ce"; 480 | @fa-var-tablet: "\f10a"; 481 | @fa-var-tachometer: "\f0e4"; 482 | @fa-var-tag: "\f02b"; 483 | @fa-var-tags: "\f02c"; 484 | @fa-var-tasks: "\f0ae"; 485 | @fa-var-taxi: "\f1ba"; 486 | @fa-var-tencent-weibo: "\f1d5"; 487 | @fa-var-terminal: "\f120"; 488 | @fa-var-text-height: "\f034"; 489 | @fa-var-text-width: "\f035"; 490 | @fa-var-th: "\f00a"; 491 | @fa-var-th-large: "\f009"; 492 | @fa-var-th-list: "\f00b"; 493 | @fa-var-thumb-tack: "\f08d"; 494 | @fa-var-thumbs-down: "\f165"; 495 | @fa-var-thumbs-o-down: "\f088"; 496 | @fa-var-thumbs-o-up: "\f087"; 497 | @fa-var-thumbs-up: "\f164"; 498 | @fa-var-ticket: "\f145"; 499 | @fa-var-times: "\f00d"; 500 | @fa-var-times-circle: "\f057"; 501 | @fa-var-times-circle-o: "\f05c"; 502 | @fa-var-tint: "\f043"; 503 | @fa-var-toggle-down: "\f150"; 504 | @fa-var-toggle-left: "\f191"; 505 | @fa-var-toggle-off: "\f204"; 506 | @fa-var-toggle-on: "\f205"; 507 | @fa-var-toggle-right: "\f152"; 508 | @fa-var-toggle-up: "\f151"; 509 | @fa-var-trash: "\f1f8"; 510 | @fa-var-trash-o: "\f014"; 511 | @fa-var-tree: "\f1bb"; 512 | @fa-var-trello: "\f181"; 513 | @fa-var-trophy: "\f091"; 514 | @fa-var-truck: "\f0d1"; 515 | @fa-var-try: "\f195"; 516 | @fa-var-tty: "\f1e4"; 517 | @fa-var-tumblr: "\f173"; 518 | @fa-var-tumblr-square: "\f174"; 519 | @fa-var-turkish-lira: "\f195"; 520 | @fa-var-twitch: "\f1e8"; 521 | @fa-var-twitter: "\f099"; 522 | @fa-var-twitter-square: "\f081"; 523 | @fa-var-umbrella: "\f0e9"; 524 | @fa-var-underline: "\f0cd"; 525 | @fa-var-undo: "\f0e2"; 526 | @fa-var-university: "\f19c"; 527 | @fa-var-unlink: "\f127"; 528 | @fa-var-unlock: "\f09c"; 529 | @fa-var-unlock-alt: "\f13e"; 530 | @fa-var-unsorted: "\f0dc"; 531 | @fa-var-upload: "\f093"; 532 | @fa-var-usd: "\f155"; 533 | @fa-var-user: "\f007"; 534 | @fa-var-user-md: "\f0f0"; 535 | @fa-var-users: "\f0c0"; 536 | @fa-var-video-camera: "\f03d"; 537 | @fa-var-vimeo-square: "\f194"; 538 | @fa-var-vine: "\f1ca"; 539 | @fa-var-vk: "\f189"; 540 | @fa-var-volume-down: "\f027"; 541 | @fa-var-volume-off: "\f026"; 542 | @fa-var-volume-up: "\f028"; 543 | @fa-var-warning: "\f071"; 544 | @fa-var-wechat: "\f1d7"; 545 | @fa-var-weibo: "\f18a"; 546 | @fa-var-weixin: "\f1d7"; 547 | @fa-var-wheelchair: "\f193"; 548 | @fa-var-wifi: "\f1eb"; 549 | @fa-var-windows: "\f17a"; 550 | @fa-var-won: "\f159"; 551 | @fa-var-wordpress: "\f19a"; 552 | @fa-var-wrench: "\f0ad"; 553 | @fa-var-xing: "\f168"; 554 | @fa-var-xing-square: "\f169"; 555 | @fa-var-yahoo: "\f19e"; 556 | @fa-var-yelp: "\f1e9"; 557 | @fa-var-yen: "\f157"; 558 | @fa-var-youtube: "\f167"; 559 | @fa-var-youtube-play: "\f16a"; 560 | @fa-var-youtube-square: "\f166"; 561 | 562 | -------------------------------------------------------------------------------- /dashboard/template/asset/font-awesome/scss/_variables.scss: -------------------------------------------------------------------------------- 1 | // Variables 2 | // -------------------------- 3 | 4 | $fa-font-path: "../fonts" !default; 5 | //$fa-font-path: "//netdna.bootstrapcdn.com/font-awesome/4.2.0/fonts" !default; // for referencing Bootstrap CDN font files directly 6 | $fa-css-prefix: fa !default; 7 | $fa-version: "4.2.0" !default; 8 | $fa-border-color: #eee !default; 9 | $fa-inverse: #fff !default; 10 | $fa-li-width: (30em / 14) !default; 11 | 12 | $fa-var-adjust: "\f042"; 13 | $fa-var-adn: "\f170"; 14 | $fa-var-align-center: "\f037"; 15 | $fa-var-align-justify: "\f039"; 16 | $fa-var-align-left: "\f036"; 17 | $fa-var-align-right: "\f038"; 18 | $fa-var-ambulance: "\f0f9"; 19 | $fa-var-anchor: "\f13d"; 20 | $fa-var-android: "\f17b"; 21 | $fa-var-angellist: "\f209"; 22 | $fa-var-angle-double-down: "\f103"; 23 | $fa-var-angle-double-left: "\f100"; 24 | $fa-var-angle-double-right: "\f101"; 25 | $fa-var-angle-double-up: "\f102"; 26 | $fa-var-angle-down: "\f107"; 27 | $fa-var-angle-left: "\f104"; 28 | $fa-var-angle-right: "\f105"; 29 | $fa-var-angle-up: "\f106"; 30 | $fa-var-apple: "\f179"; 31 | $fa-var-archive: "\f187"; 32 | $fa-var-area-chart: "\f1fe"; 33 | $fa-var-arrow-circle-down: "\f0ab"; 34 | $fa-var-arrow-circle-left: "\f0a8"; 35 | $fa-var-arrow-circle-o-down: "\f01a"; 36 | $fa-var-arrow-circle-o-left: "\f190"; 37 | $fa-var-arrow-circle-o-right: "\f18e"; 38 | $fa-var-arrow-circle-o-up: "\f01b"; 39 | $fa-var-arrow-circle-right: "\f0a9"; 40 | $fa-var-arrow-circle-up: "\f0aa"; 41 | $fa-var-arrow-down: "\f063"; 42 | $fa-var-arrow-left: "\f060"; 43 | $fa-var-arrow-right: "\f061"; 44 | $fa-var-arrow-up: "\f062"; 45 | $fa-var-arrows: "\f047"; 46 | $fa-var-arrows-alt: "\f0b2"; 47 | $fa-var-arrows-h: "\f07e"; 48 | $fa-var-arrows-v: "\f07d"; 49 | $fa-var-asterisk: "\f069"; 50 | $fa-var-at: "\f1fa"; 51 | $fa-var-automobile: "\f1b9"; 52 | $fa-var-backward: "\f04a"; 53 | $fa-var-ban: "\f05e"; 54 | $fa-var-bank: "\f19c"; 55 | $fa-var-bar-chart: "\f080"; 56 | $fa-var-bar-chart-o: "\f080"; 57 | $fa-var-barcode: "\f02a"; 58 | $fa-var-bars: "\f0c9"; 59 | $fa-var-beer: "\f0fc"; 60 | $fa-var-behance: "\f1b4"; 61 | $fa-var-behance-square: "\f1b5"; 62 | $fa-var-bell: "\f0f3"; 63 | $fa-var-bell-o: "\f0a2"; 64 | $fa-var-bell-slash: "\f1f6"; 65 | $fa-var-bell-slash-o: "\f1f7"; 66 | $fa-var-bicycle: "\f206"; 67 | $fa-var-binoculars: "\f1e5"; 68 | $fa-var-birthday-cake: "\f1fd"; 69 | $fa-var-bitbucket: "\f171"; 70 | $fa-var-bitbucket-square: "\f172"; 71 | $fa-var-bitcoin: "\f15a"; 72 | $fa-var-bold: "\f032"; 73 | $fa-var-bolt: "\f0e7"; 74 | $fa-var-bomb: "\f1e2"; 75 | $fa-var-book: "\f02d"; 76 | $fa-var-bookmark: "\f02e"; 77 | $fa-var-bookmark-o: "\f097"; 78 | $fa-var-briefcase: "\f0b1"; 79 | $fa-var-btc: "\f15a"; 80 | $fa-var-bug: "\f188"; 81 | $fa-var-building: "\f1ad"; 82 | $fa-var-building-o: "\f0f7"; 83 | $fa-var-bullhorn: "\f0a1"; 84 | $fa-var-bullseye: "\f140"; 85 | $fa-var-bus: "\f207"; 86 | $fa-var-cab: "\f1ba"; 87 | $fa-var-calculator: "\f1ec"; 88 | $fa-var-calendar: "\f073"; 89 | $fa-var-calendar-o: "\f133"; 90 | $fa-var-camera: "\f030"; 91 | $fa-var-camera-retro: "\f083"; 92 | $fa-var-car: "\f1b9"; 93 | $fa-var-caret-down: "\f0d7"; 94 | $fa-var-caret-left: "\f0d9"; 95 | $fa-var-caret-right: "\f0da"; 96 | $fa-var-caret-square-o-down: "\f150"; 97 | $fa-var-caret-square-o-left: "\f191"; 98 | $fa-var-caret-square-o-right: "\f152"; 99 | $fa-var-caret-square-o-up: "\f151"; 100 | $fa-var-caret-up: "\f0d8"; 101 | $fa-var-cc: "\f20a"; 102 | $fa-var-cc-amex: "\f1f3"; 103 | $fa-var-cc-discover: "\f1f2"; 104 | $fa-var-cc-mastercard: "\f1f1"; 105 | $fa-var-cc-paypal: "\f1f4"; 106 | $fa-var-cc-stripe: "\f1f5"; 107 | $fa-var-cc-visa: "\f1f0"; 108 | $fa-var-certificate: "\f0a3"; 109 | $fa-var-chain: "\f0c1"; 110 | $fa-var-chain-broken: "\f127"; 111 | $fa-var-check: "\f00c"; 112 | $fa-var-check-circle: "\f058"; 113 | $fa-var-check-circle-o: "\f05d"; 114 | $fa-var-check-square: "\f14a"; 115 | $fa-var-check-square-o: "\f046"; 116 | $fa-var-chevron-circle-down: "\f13a"; 117 | $fa-var-chevron-circle-left: "\f137"; 118 | $fa-var-chevron-circle-right: "\f138"; 119 | $fa-var-chevron-circle-up: "\f139"; 120 | $fa-var-chevron-down: "\f078"; 121 | $fa-var-chevron-left: "\f053"; 122 | $fa-var-chevron-right: "\f054"; 123 | $fa-var-chevron-up: "\f077"; 124 | $fa-var-child: "\f1ae"; 125 | $fa-var-circle: "\f111"; 126 | $fa-var-circle-o: "\f10c"; 127 | $fa-var-circle-o-notch: "\f1ce"; 128 | $fa-var-circle-thin: "\f1db"; 129 | $fa-var-clipboard: "\f0ea"; 130 | $fa-var-clock-o: "\f017"; 131 | $fa-var-close: "\f00d"; 132 | $fa-var-cloud: "\f0c2"; 133 | $fa-var-cloud-download: "\f0ed"; 134 | $fa-var-cloud-upload: "\f0ee"; 135 | $fa-var-cny: "\f157"; 136 | $fa-var-code: "\f121"; 137 | $fa-var-code-fork: "\f126"; 138 | $fa-var-codepen: "\f1cb"; 139 | $fa-var-coffee: "\f0f4"; 140 | $fa-var-cog: "\f013"; 141 | $fa-var-cogs: "\f085"; 142 | $fa-var-columns: "\f0db"; 143 | $fa-var-comment: "\f075"; 144 | $fa-var-comment-o: "\f0e5"; 145 | $fa-var-comments: "\f086"; 146 | $fa-var-comments-o: "\f0e6"; 147 | $fa-var-compass: "\f14e"; 148 | $fa-var-compress: "\f066"; 149 | $fa-var-copy: "\f0c5"; 150 | $fa-var-copyright: "\f1f9"; 151 | $fa-var-credit-card: "\f09d"; 152 | $fa-var-crop: "\f125"; 153 | $fa-var-crosshairs: "\f05b"; 154 | $fa-var-css3: "\f13c"; 155 | $fa-var-cube: "\f1b2"; 156 | $fa-var-cubes: "\f1b3"; 157 | $fa-var-cut: "\f0c4"; 158 | $fa-var-cutlery: "\f0f5"; 159 | $fa-var-dashboard: "\f0e4"; 160 | $fa-var-database: "\f1c0"; 161 | $fa-var-dedent: "\f03b"; 162 | $fa-var-delicious: "\f1a5"; 163 | $fa-var-desktop: "\f108"; 164 | $fa-var-deviantart: "\f1bd"; 165 | $fa-var-digg: "\f1a6"; 166 | $fa-var-dollar: "\f155"; 167 | $fa-var-dot-circle-o: "\f192"; 168 | $fa-var-download: "\f019"; 169 | $fa-var-dribbble: "\f17d"; 170 | $fa-var-dropbox: "\f16b"; 171 | $fa-var-drupal: "\f1a9"; 172 | $fa-var-edit: "\f044"; 173 | $fa-var-eject: "\f052"; 174 | $fa-var-ellipsis-h: "\f141"; 175 | $fa-var-ellipsis-v: "\f142"; 176 | $fa-var-empire: "\f1d1"; 177 | $fa-var-envelope: "\f0e0"; 178 | $fa-var-envelope-o: "\f003"; 179 | $fa-var-envelope-square: "\f199"; 180 | $fa-var-eraser: "\f12d"; 181 | $fa-var-eur: "\f153"; 182 | $fa-var-euro: "\f153"; 183 | $fa-var-exchange: "\f0ec"; 184 | $fa-var-exclamation: "\f12a"; 185 | $fa-var-exclamation-circle: "\f06a"; 186 | $fa-var-exclamation-triangle: "\f071"; 187 | $fa-var-expand: "\f065"; 188 | $fa-var-external-link: "\f08e"; 189 | $fa-var-external-link-square: "\f14c"; 190 | $fa-var-eye: "\f06e"; 191 | $fa-var-eye-slash: "\f070"; 192 | $fa-var-eyedropper: "\f1fb"; 193 | $fa-var-facebook: "\f09a"; 194 | $fa-var-facebook-square: "\f082"; 195 | $fa-var-fast-backward: "\f049"; 196 | $fa-var-fast-forward: "\f050"; 197 | $fa-var-fax: "\f1ac"; 198 | $fa-var-female: "\f182"; 199 | $fa-var-fighter-jet: "\f0fb"; 200 | $fa-var-file: "\f15b"; 201 | $fa-var-file-archive-o: "\f1c6"; 202 | $fa-var-file-audio-o: "\f1c7"; 203 | $fa-var-file-code-o: "\f1c9"; 204 | $fa-var-file-excel-o: "\f1c3"; 205 | $fa-var-file-image-o: "\f1c5"; 206 | $fa-var-file-movie-o: "\f1c8"; 207 | $fa-var-file-o: "\f016"; 208 | $fa-var-file-pdf-o: "\f1c1"; 209 | $fa-var-file-photo-o: "\f1c5"; 210 | $fa-var-file-picture-o: "\f1c5"; 211 | $fa-var-file-powerpoint-o: "\f1c4"; 212 | $fa-var-file-sound-o: "\f1c7"; 213 | $fa-var-file-text: "\f15c"; 214 | $fa-var-file-text-o: "\f0f6"; 215 | $fa-var-file-video-o: "\f1c8"; 216 | $fa-var-file-word-o: "\f1c2"; 217 | $fa-var-file-zip-o: "\f1c6"; 218 | $fa-var-files-o: "\f0c5"; 219 | $fa-var-film: "\f008"; 220 | $fa-var-filter: "\f0b0"; 221 | $fa-var-fire: "\f06d"; 222 | $fa-var-fire-extinguisher: "\f134"; 223 | $fa-var-flag: "\f024"; 224 | $fa-var-flag-checkered: "\f11e"; 225 | $fa-var-flag-o: "\f11d"; 226 | $fa-var-flash: "\f0e7"; 227 | $fa-var-flask: "\f0c3"; 228 | $fa-var-flickr: "\f16e"; 229 | $fa-var-floppy-o: "\f0c7"; 230 | $fa-var-folder: "\f07b"; 231 | $fa-var-folder-o: "\f114"; 232 | $fa-var-folder-open: "\f07c"; 233 | $fa-var-folder-open-o: "\f115"; 234 | $fa-var-font: "\f031"; 235 | $fa-var-forward: "\f04e"; 236 | $fa-var-foursquare: "\f180"; 237 | $fa-var-frown-o: "\f119"; 238 | $fa-var-futbol-o: "\f1e3"; 239 | $fa-var-gamepad: "\f11b"; 240 | $fa-var-gavel: "\f0e3"; 241 | $fa-var-gbp: "\f154"; 242 | $fa-var-ge: "\f1d1"; 243 | $fa-var-gear: "\f013"; 244 | $fa-var-gears: "\f085"; 245 | $fa-var-gift: "\f06b"; 246 | $fa-var-git: "\f1d3"; 247 | $fa-var-git-square: "\f1d2"; 248 | $fa-var-github: "\f09b"; 249 | $fa-var-github-alt: "\f113"; 250 | $fa-var-github-square: "\f092"; 251 | $fa-var-gittip: "\f184"; 252 | $fa-var-glass: "\f000"; 253 | $fa-var-globe: "\f0ac"; 254 | $fa-var-google: "\f1a0"; 255 | $fa-var-google-plus: "\f0d5"; 256 | $fa-var-google-plus-square: "\f0d4"; 257 | $fa-var-google-wallet: "\f1ee"; 258 | $fa-var-graduation-cap: "\f19d"; 259 | $fa-var-group: "\f0c0"; 260 | $fa-var-h-square: "\f0fd"; 261 | $fa-var-hacker-news: "\f1d4"; 262 | $fa-var-hand-o-down: "\f0a7"; 263 | $fa-var-hand-o-left: "\f0a5"; 264 | $fa-var-hand-o-right: "\f0a4"; 265 | $fa-var-hand-o-up: "\f0a6"; 266 | $fa-var-hdd-o: "\f0a0"; 267 | $fa-var-header: "\f1dc"; 268 | $fa-var-headphones: "\f025"; 269 | $fa-var-heart: "\f004"; 270 | $fa-var-heart-o: "\f08a"; 271 | $fa-var-history: "\f1da"; 272 | $fa-var-home: "\f015"; 273 | $fa-var-hospital-o: "\f0f8"; 274 | $fa-var-html5: "\f13b"; 275 | $fa-var-ils: "\f20b"; 276 | $fa-var-image: "\f03e"; 277 | $fa-var-inbox: "\f01c"; 278 | $fa-var-indent: "\f03c"; 279 | $fa-var-info: "\f129"; 280 | $fa-var-info-circle: "\f05a"; 281 | $fa-var-inr: "\f156"; 282 | $fa-var-instagram: "\f16d"; 283 | $fa-var-institution: "\f19c"; 284 | $fa-var-ioxhost: "\f208"; 285 | $fa-var-italic: "\f033"; 286 | $fa-var-joomla: "\f1aa"; 287 | $fa-var-jpy: "\f157"; 288 | $fa-var-jsfiddle: "\f1cc"; 289 | $fa-var-key: "\f084"; 290 | $fa-var-keyboard-o: "\f11c"; 291 | $fa-var-krw: "\f159"; 292 | $fa-var-language: "\f1ab"; 293 | $fa-var-laptop: "\f109"; 294 | $fa-var-lastfm: "\f202"; 295 | $fa-var-lastfm-square: "\f203"; 296 | $fa-var-leaf: "\f06c"; 297 | $fa-var-legal: "\f0e3"; 298 | $fa-var-lemon-o: "\f094"; 299 | $fa-var-level-down: "\f149"; 300 | $fa-var-level-up: "\f148"; 301 | $fa-var-life-bouy: "\f1cd"; 302 | $fa-var-life-buoy: "\f1cd"; 303 | $fa-var-life-ring: "\f1cd"; 304 | $fa-var-life-saver: "\f1cd"; 305 | $fa-var-lightbulb-o: "\f0eb"; 306 | $fa-var-line-chart: "\f201"; 307 | $fa-var-link: "\f0c1"; 308 | $fa-var-linkedin: "\f0e1"; 309 | $fa-var-linkedin-square: "\f08c"; 310 | $fa-var-linux: "\f17c"; 311 | $fa-var-list: "\f03a"; 312 | $fa-var-list-alt: "\f022"; 313 | $fa-var-list-ol: "\f0cb"; 314 | $fa-var-list-ul: "\f0ca"; 315 | $fa-var-location-arrow: "\f124"; 316 | $fa-var-lock: "\f023"; 317 | $fa-var-long-arrow-down: "\f175"; 318 | $fa-var-long-arrow-left: "\f177"; 319 | $fa-var-long-arrow-right: "\f178"; 320 | $fa-var-long-arrow-up: "\f176"; 321 | $fa-var-magic: "\f0d0"; 322 | $fa-var-magnet: "\f076"; 323 | $fa-var-mail-forward: "\f064"; 324 | $fa-var-mail-reply: "\f112"; 325 | $fa-var-mail-reply-all: "\f122"; 326 | $fa-var-male: "\f183"; 327 | $fa-var-map-marker: "\f041"; 328 | $fa-var-maxcdn: "\f136"; 329 | $fa-var-meanpath: "\f20c"; 330 | $fa-var-medkit: "\f0fa"; 331 | $fa-var-meh-o: "\f11a"; 332 | $fa-var-microphone: "\f130"; 333 | $fa-var-microphone-slash: "\f131"; 334 | $fa-var-minus: "\f068"; 335 | $fa-var-minus-circle: "\f056"; 336 | $fa-var-minus-square: "\f146"; 337 | $fa-var-minus-square-o: "\f147"; 338 | $fa-var-mobile: "\f10b"; 339 | $fa-var-mobile-phone: "\f10b"; 340 | $fa-var-money: "\f0d6"; 341 | $fa-var-moon-o: "\f186"; 342 | $fa-var-mortar-board: "\f19d"; 343 | $fa-var-music: "\f001"; 344 | $fa-var-navicon: "\f0c9"; 345 | $fa-var-newspaper-o: "\f1ea"; 346 | $fa-var-openid: "\f19b"; 347 | $fa-var-outdent: "\f03b"; 348 | $fa-var-pagelines: "\f18c"; 349 | $fa-var-paint-brush: "\f1fc"; 350 | $fa-var-paper-plane: "\f1d8"; 351 | $fa-var-paper-plane-o: "\f1d9"; 352 | $fa-var-paperclip: "\f0c6"; 353 | $fa-var-paragraph: "\f1dd"; 354 | $fa-var-paste: "\f0ea"; 355 | $fa-var-pause: "\f04c"; 356 | $fa-var-paw: "\f1b0"; 357 | $fa-var-paypal: "\f1ed"; 358 | $fa-var-pencil: "\f040"; 359 | $fa-var-pencil-square: "\f14b"; 360 | $fa-var-pencil-square-o: "\f044"; 361 | $fa-var-phone: "\f095"; 362 | $fa-var-phone-square: "\f098"; 363 | $fa-var-photo: "\f03e"; 364 | $fa-var-picture-o: "\f03e"; 365 | $fa-var-pie-chart: "\f200"; 366 | $fa-var-pied-piper: "\f1a7"; 367 | $fa-var-pied-piper-alt: "\f1a8"; 368 | $fa-var-pinterest: "\f0d2"; 369 | $fa-var-pinterest-square: "\f0d3"; 370 | $fa-var-plane: "\f072"; 371 | $fa-var-play: "\f04b"; 372 | $fa-var-play-circle: "\f144"; 373 | $fa-var-play-circle-o: "\f01d"; 374 | $fa-var-plug: "\f1e6"; 375 | $fa-var-plus: "\f067"; 376 | $fa-var-plus-circle: "\f055"; 377 | $fa-var-plus-square: "\f0fe"; 378 | $fa-var-plus-square-o: "\f196"; 379 | $fa-var-power-off: "\f011"; 380 | $fa-var-print: "\f02f"; 381 | $fa-var-puzzle-piece: "\f12e"; 382 | $fa-var-qq: "\f1d6"; 383 | $fa-var-qrcode: "\f029"; 384 | $fa-var-question: "\f128"; 385 | $fa-var-question-circle: "\f059"; 386 | $fa-var-quote-left: "\f10d"; 387 | $fa-var-quote-right: "\f10e"; 388 | $fa-var-ra: "\f1d0"; 389 | $fa-var-random: "\f074"; 390 | $fa-var-rebel: "\f1d0"; 391 | $fa-var-recycle: "\f1b8"; 392 | $fa-var-reddit: "\f1a1"; 393 | $fa-var-reddit-square: "\f1a2"; 394 | $fa-var-refresh: "\f021"; 395 | $fa-var-remove: "\f00d"; 396 | $fa-var-renren: "\f18b"; 397 | $fa-var-reorder: "\f0c9"; 398 | $fa-var-repeat: "\f01e"; 399 | $fa-var-reply: "\f112"; 400 | $fa-var-reply-all: "\f122"; 401 | $fa-var-retweet: "\f079"; 402 | $fa-var-rmb: "\f157"; 403 | $fa-var-road: "\f018"; 404 | $fa-var-rocket: "\f135"; 405 | $fa-var-rotate-left: "\f0e2"; 406 | $fa-var-rotate-right: "\f01e"; 407 | $fa-var-rouble: "\f158"; 408 | $fa-var-rss: "\f09e"; 409 | $fa-var-rss-square: "\f143"; 410 | $fa-var-rub: "\f158"; 411 | $fa-var-ruble: "\f158"; 412 | $fa-var-rupee: "\f156"; 413 | $fa-var-save: "\f0c7"; 414 | $fa-var-scissors: "\f0c4"; 415 | $fa-var-search: "\f002"; 416 | $fa-var-search-minus: "\f010"; 417 | $fa-var-search-plus: "\f00e"; 418 | $fa-var-send: "\f1d8"; 419 | $fa-var-send-o: "\f1d9"; 420 | $fa-var-share: "\f064"; 421 | $fa-var-share-alt: "\f1e0"; 422 | $fa-var-share-alt-square: "\f1e1"; 423 | $fa-var-share-square: "\f14d"; 424 | $fa-var-share-square-o: "\f045"; 425 | $fa-var-shekel: "\f20b"; 426 | $fa-var-sheqel: "\f20b"; 427 | $fa-var-shield: "\f132"; 428 | $fa-var-shopping-cart: "\f07a"; 429 | $fa-var-sign-in: "\f090"; 430 | $fa-var-sign-out: "\f08b"; 431 | $fa-var-signal: "\f012"; 432 | $fa-var-sitemap: "\f0e8"; 433 | $fa-var-skype: "\f17e"; 434 | $fa-var-slack: "\f198"; 435 | $fa-var-sliders: "\f1de"; 436 | $fa-var-slideshare: "\f1e7"; 437 | $fa-var-smile-o: "\f118"; 438 | $fa-var-soccer-ball-o: "\f1e3"; 439 | $fa-var-sort: "\f0dc"; 440 | $fa-var-sort-alpha-asc: "\f15d"; 441 | $fa-var-sort-alpha-desc: "\f15e"; 442 | $fa-var-sort-amount-asc: "\f160"; 443 | $fa-var-sort-amount-desc: "\f161"; 444 | $fa-var-sort-asc: "\f0de"; 445 | $fa-var-sort-desc: "\f0dd"; 446 | $fa-var-sort-down: "\f0dd"; 447 | $fa-var-sort-numeric-asc: "\f162"; 448 | $fa-var-sort-numeric-desc: "\f163"; 449 | $fa-var-sort-up: "\f0de"; 450 | $fa-var-soundcloud: "\f1be"; 451 | $fa-var-space-shuttle: "\f197"; 452 | $fa-var-spinner: "\f110"; 453 | $fa-var-spoon: "\f1b1"; 454 | $fa-var-spotify: "\f1bc"; 455 | $fa-var-square: "\f0c8"; 456 | $fa-var-square-o: "\f096"; 457 | $fa-var-stack-exchange: "\f18d"; 458 | $fa-var-stack-overflow: "\f16c"; 459 | $fa-var-star: "\f005"; 460 | $fa-var-star-half: "\f089"; 461 | $fa-var-star-half-empty: "\f123"; 462 | $fa-var-star-half-full: "\f123"; 463 | $fa-var-star-half-o: "\f123"; 464 | $fa-var-star-o: "\f006"; 465 | $fa-var-steam: "\f1b6"; 466 | $fa-var-steam-square: "\f1b7"; 467 | $fa-var-step-backward: "\f048"; 468 | $fa-var-step-forward: "\f051"; 469 | $fa-var-stethoscope: "\f0f1"; 470 | $fa-var-stop: "\f04d"; 471 | $fa-var-strikethrough: "\f0cc"; 472 | $fa-var-stumbleupon: "\f1a4"; 473 | $fa-var-stumbleupon-circle: "\f1a3"; 474 | $fa-var-subscript: "\f12c"; 475 | $fa-var-suitcase: "\f0f2"; 476 | $fa-var-sun-o: "\f185"; 477 | $fa-var-superscript: "\f12b"; 478 | $fa-var-support: "\f1cd"; 479 | $fa-var-table: "\f0ce"; 480 | $fa-var-tablet: "\f10a"; 481 | $fa-var-tachometer: "\f0e4"; 482 | $fa-var-tag: "\f02b"; 483 | $fa-var-tags: "\f02c"; 484 | $fa-var-tasks: "\f0ae"; 485 | $fa-var-taxi: "\f1ba"; 486 | $fa-var-tencent-weibo: "\f1d5"; 487 | $fa-var-terminal: "\f120"; 488 | $fa-var-text-height: "\f034"; 489 | $fa-var-text-width: "\f035"; 490 | $fa-var-th: "\f00a"; 491 | $fa-var-th-large: "\f009"; 492 | $fa-var-th-list: "\f00b"; 493 | $fa-var-thumb-tack: "\f08d"; 494 | $fa-var-thumbs-down: "\f165"; 495 | $fa-var-thumbs-o-down: "\f088"; 496 | $fa-var-thumbs-o-up: "\f087"; 497 | $fa-var-thumbs-up: "\f164"; 498 | $fa-var-ticket: "\f145"; 499 | $fa-var-times: "\f00d"; 500 | $fa-var-times-circle: "\f057"; 501 | $fa-var-times-circle-o: "\f05c"; 502 | $fa-var-tint: "\f043"; 503 | $fa-var-toggle-down: "\f150"; 504 | $fa-var-toggle-left: "\f191"; 505 | $fa-var-toggle-off: "\f204"; 506 | $fa-var-toggle-on: "\f205"; 507 | $fa-var-toggle-right: "\f152"; 508 | $fa-var-toggle-up: "\f151"; 509 | $fa-var-trash: "\f1f8"; 510 | $fa-var-trash-o: "\f014"; 511 | $fa-var-tree: "\f1bb"; 512 | $fa-var-trello: "\f181"; 513 | $fa-var-trophy: "\f091"; 514 | $fa-var-truck: "\f0d1"; 515 | $fa-var-try: "\f195"; 516 | $fa-var-tty: "\f1e4"; 517 | $fa-var-tumblr: "\f173"; 518 | $fa-var-tumblr-square: "\f174"; 519 | $fa-var-turkish-lira: "\f195"; 520 | $fa-var-twitch: "\f1e8"; 521 | $fa-var-twitter: "\f099"; 522 | $fa-var-twitter-square: "\f081"; 523 | $fa-var-umbrella: "\f0e9"; 524 | $fa-var-underline: "\f0cd"; 525 | $fa-var-undo: "\f0e2"; 526 | $fa-var-university: "\f19c"; 527 | $fa-var-unlink: "\f127"; 528 | $fa-var-unlock: "\f09c"; 529 | $fa-var-unlock-alt: "\f13e"; 530 | $fa-var-unsorted: "\f0dc"; 531 | $fa-var-upload: "\f093"; 532 | $fa-var-usd: "\f155"; 533 | $fa-var-user: "\f007"; 534 | $fa-var-user-md: "\f0f0"; 535 | $fa-var-users: "\f0c0"; 536 | $fa-var-video-camera: "\f03d"; 537 | $fa-var-vimeo-square: "\f194"; 538 | $fa-var-vine: "\f1ca"; 539 | $fa-var-vk: "\f189"; 540 | $fa-var-volume-down: "\f027"; 541 | $fa-var-volume-off: "\f026"; 542 | $fa-var-volume-up: "\f028"; 543 | $fa-var-warning: "\f071"; 544 | $fa-var-wechat: "\f1d7"; 545 | $fa-var-weibo: "\f18a"; 546 | $fa-var-weixin: "\f1d7"; 547 | $fa-var-wheelchair: "\f193"; 548 | $fa-var-wifi: "\f1eb"; 549 | $fa-var-windows: "\f17a"; 550 | $fa-var-won: "\f159"; 551 | $fa-var-wordpress: "\f19a"; 552 | $fa-var-wrench: "\f0ad"; 553 | $fa-var-xing: "\f168"; 554 | $fa-var-xing-square: "\f169"; 555 | $fa-var-yahoo: "\f19e"; 556 | $fa-var-yelp: "\f1e9"; 557 | $fa-var-yen: "\f157"; 558 | $fa-var-youtube: "\f167"; 559 | $fa-var-youtube-play: "\f16a"; 560 | $fa-var-youtube-square: "\f166"; 561 | 562 | -------------------------------------------------------------------------------- /dashboard/template/asset/js/excanvas.min.js: -------------------------------------------------------------------------------- 1 | if(!document.createElement("canvas").getContext){(function(){var z=Math;var K=z.round;var J=z.sin;var U=z.cos;var b=z.abs;var k=z.sqrt;var D=10;var F=D/2;function T(){return this.context_||(this.context_=new W(this))}var O=Array.prototype.slice;function G(i,j,m){var Z=O.call(arguments,2);return function(){return i.apply(j,Z.concat(O.call(arguments)))}}function AD(Z){return String(Z).replace(/&/g,"&").replace(/"/g,""")}function r(i){if(!i.namespaces.g_vml_){i.namespaces.add("g_vml_","urn:schemas-microsoft-com:vml","#default#VML")}if(!i.namespaces.g_o_){i.namespaces.add("g_o_","urn:schemas-microsoft-com:office:office","#default#VML")}if(!i.styleSheets.ex_canvas_){var Z=i.createStyleSheet();Z.owningElement.id="ex_canvas_";Z.cssText="canvas{display:inline-block;overflow:hidden;text-align:left;width:300px;height:150px}"}}r(document);var E={init:function(Z){if(/MSIE/.test(navigator.userAgent)&&!window.opera){var i=Z||document;i.createElement("canvas");i.attachEvent("onreadystatechange",G(this.init_,this,i))}},init_:function(m){var j=m.getElementsByTagName("canvas");for(var Z=0;Z1){j--}if(6*j<1){return i+(Z-i)*6*j}else{if(2*j<1){return Z}else{if(3*j<2){return i+(Z-i)*(2/3-j)*6}else{return i}}}}function Y(Z){var AE,p=1;Z=String(Z);if(Z.charAt(0)=="#"){AE=Z}else{if(/^rgb/.test(Z)){var m=g(Z);var AE="#",AF;for(var j=0;j<3;j++){if(m[j].indexOf("%")!=-1){AF=Math.floor(C(m[j])*255)}else{AF=Number(m[j])}AE+=I[N(AF,0,255)]}p=m[3]}else{if(/^hsl/.test(Z)){var m=g(Z);AE=c(m);p=m[3]}else{AE=B[Z]||Z}}}return{color:AE,alpha:p}}var L={style:"normal",variant:"normal",weight:"normal",size:10,family:"sans-serif"};var f={};function X(Z){if(f[Z]){return f[Z]}var m=document.createElement("div");var j=m.style;try{j.font=Z}catch(i){}return f[Z]={style:j.fontStyle||L.style,variant:j.fontVariant||L.variant,weight:j.fontWeight||L.weight,size:j.fontSize||L.size,family:j.fontFamily||L.family}}function P(j,i){var Z={};for(var AF in j){Z[AF]=j[AF]}var AE=parseFloat(i.currentStyle.fontSize),m=parseFloat(j.size);if(typeof j.size=="number"){Z.size=j.size}else{if(j.size.indexOf("px")!=-1){Z.size=m}else{if(j.size.indexOf("em")!=-1){Z.size=AE*m}else{if(j.size.indexOf("%")!=-1){Z.size=(AE/100)*m}else{if(j.size.indexOf("pt")!=-1){Z.size=m/0.75}else{Z.size=AE}}}}}Z.size*=0.981;return Z}function AA(Z){return Z.style+" "+Z.variant+" "+Z.weight+" "+Z.size+"px "+Z.family}function t(Z){switch(Z){case"butt":return"flat";case"round":return"round";case"square":default:return"square"}}function W(i){this.m_=V();this.mStack_=[];this.aStack_=[];this.currentPath_=[];this.strokeStyle="#000";this.fillStyle="#000";this.lineWidth=1;this.lineJoin="miter";this.lineCap="butt";this.miterLimit=D*1;this.globalAlpha=1;this.font="10px sans-serif";this.textAlign="left";this.textBaseline="alphabetic";this.canvas=i;var Z=i.ownerDocument.createElement("div");Z.style.width=i.clientWidth+"px";Z.style.height=i.clientHeight+"px";Z.style.overflow="hidden";Z.style.position="absolute";i.appendChild(Z);this.element_=Z;this.arcScaleX_=1;this.arcScaleY_=1;this.lineScale_=1}var M=W.prototype;M.clearRect=function(){if(this.textMeasureEl_){this.textMeasureEl_.removeNode(true);this.textMeasureEl_=null}this.element_.innerHTML=""};M.beginPath=function(){this.currentPath_=[]};M.moveTo=function(i,Z){var j=this.getCoords_(i,Z);this.currentPath_.push({type:"moveTo",x:j.x,y:j.y});this.currentX_=j.x;this.currentY_=j.y};M.lineTo=function(i,Z){var j=this.getCoords_(i,Z);this.currentPath_.push({type:"lineTo",x:j.x,y:j.y});this.currentX_=j.x;this.currentY_=j.y};M.bezierCurveTo=function(j,i,AI,AH,AG,AE){var Z=this.getCoords_(AG,AE);var AF=this.getCoords_(j,i);var m=this.getCoords_(AI,AH);e(this,AF,m,Z)};function e(Z,m,j,i){Z.currentPath_.push({type:"bezierCurveTo",cp1x:m.x,cp1y:m.y,cp2x:j.x,cp2y:j.y,x:i.x,y:i.y});Z.currentX_=i.x;Z.currentY_=i.y}M.quadraticCurveTo=function(AG,j,i,Z){var AF=this.getCoords_(AG,j);var AE=this.getCoords_(i,Z);var AH={x:this.currentX_+2/3*(AF.x-this.currentX_),y:this.currentY_+2/3*(AF.y-this.currentY_)};var m={x:AH.x+(AE.x-this.currentX_)/3,y:AH.y+(AE.y-this.currentY_)/3};e(this,AH,m,AE)};M.arc=function(AJ,AH,AI,AE,i,j){AI*=D;var AN=j?"at":"wa";var AK=AJ+U(AE)*AI-F;var AM=AH+J(AE)*AI-F;var Z=AJ+U(i)*AI-F;var AL=AH+J(i)*AI-F;if(AK==Z&&!j){AK+=0.125}var m=this.getCoords_(AJ,AH);var AG=this.getCoords_(AK,AM);var AF=this.getCoords_(Z,AL);this.currentPath_.push({type:AN,x:m.x,y:m.y,radius:AI,xStart:AG.x,yStart:AG.y,xEnd:AF.x,yEnd:AF.y})};M.rect=function(j,i,Z,m){this.moveTo(j,i);this.lineTo(j+Z,i);this.lineTo(j+Z,i+m);this.lineTo(j,i+m);this.closePath()};M.strokeRect=function(j,i,Z,m){var p=this.currentPath_;this.beginPath();this.moveTo(j,i);this.lineTo(j+Z,i);this.lineTo(j+Z,i+m);this.lineTo(j,i+m);this.closePath();this.stroke();this.currentPath_=p};M.fillRect=function(j,i,Z,m){var p=this.currentPath_;this.beginPath();this.moveTo(j,i);this.lineTo(j+Z,i);this.lineTo(j+Z,i+m);this.lineTo(j,i+m);this.closePath();this.fill();this.currentPath_=p};M.createLinearGradient=function(i,m,Z,j){var p=new v("gradient");p.x0_=i;p.y0_=m;p.x1_=Z;p.y1_=j;return p};M.createRadialGradient=function(m,AE,j,i,p,Z){var AF=new v("gradientradial");AF.x0_=m;AF.y0_=AE;AF.r0_=j;AF.x1_=i;AF.y1_=p;AF.r1_=Z;return AF};M.drawImage=function(AO,j){var AH,AF,AJ,AV,AM,AK,AQ,AX;var AI=AO.runtimeStyle.width;var AN=AO.runtimeStyle.height;AO.runtimeStyle.width="auto";AO.runtimeStyle.height="auto";var AG=AO.width;var AT=AO.height;AO.runtimeStyle.width=AI;AO.runtimeStyle.height=AN;if(arguments.length==3){AH=arguments[1];AF=arguments[2];AM=AK=0;AQ=AJ=AG;AX=AV=AT}else{if(arguments.length==5){AH=arguments[1];AF=arguments[2];AJ=arguments[3];AV=arguments[4];AM=AK=0;AQ=AG;AX=AT}else{if(arguments.length==9){AM=arguments[1];AK=arguments[2];AQ=arguments[3];AX=arguments[4];AH=arguments[5];AF=arguments[6];AJ=arguments[7];AV=arguments[8]}else{throw Error("Invalid number of arguments")}}}var AW=this.getCoords_(AH,AF);var m=AQ/2;var i=AX/2;var AU=[];var Z=10;var AE=10;AU.push(" ','","");this.element_.insertAdjacentHTML("BeforeEnd",AU.join(""))};M.stroke=function(AM){var m=10;var AN=10;var AE=5000;var AG={x:null,y:null};var AL={x:null,y:null};for(var AH=0;AHAL.x){AL.x=Z.x}if(AG.y==null||Z.yAL.y){AL.y=Z.y}}}AK.push(' ">');if(!AM){R(this,AK)}else{a(this,AK,AG,AL)}AK.push("");this.element_.insertAdjacentHTML("beforeEnd",AK.join(""))}};function R(j,AE){var i=Y(j.strokeStyle);var m=i.color;var p=i.alpha*j.globalAlpha;var Z=j.lineScale_*j.lineWidth;if(Z<1){p*=Z}AE.push("')}function a(AO,AG,Ah,AP){var AH=AO.fillStyle;var AY=AO.arcScaleX_;var AX=AO.arcScaleY_;var Z=AP.x-Ah.x;var m=AP.y-Ah.y;if(AH instanceof v){var AL=0;var Ac={x:0,y:0};var AU=0;var AK=1;if(AH.type_=="gradient"){var AJ=AH.x0_/AY;var j=AH.y0_/AX;var AI=AH.x1_/AY;var Aj=AH.y1_/AX;var Ag=AO.getCoords_(AJ,j);var Af=AO.getCoords_(AI,Aj);var AE=Af.x-Ag.x;var p=Af.y-Ag.y;AL=Math.atan2(AE,p)*180/Math.PI;if(AL<0){AL+=360}if(AL<0.000001){AL=0}}else{var Ag=AO.getCoords_(AH.x0_,AH.y0_);Ac={x:(Ag.x-Ah.x)/Z,y:(Ag.y-Ah.y)/m};Z/=AY*D;m/=AX*D;var Aa=z.max(Z,m);AU=2*AH.r0_/Aa;AK=2*AH.r1_/Aa-AU}var AS=AH.colors_;AS.sort(function(Ak,i){return Ak.offset-i.offset});var AN=AS.length;var AR=AS[0].color;var AQ=AS[AN-1].color;var AW=AS[0].alpha*AO.globalAlpha;var AV=AS[AN-1].alpha*AO.globalAlpha;var Ab=[];for(var Ae=0;Ae')}else{if(AH instanceof u){if(Z&&m){var AF=-Ah.x;var AZ=-Ah.y;AG.push("')}}else{var Ai=Y(AO.fillStyle);var AT=Ai.color;var Ad=Ai.alpha*AO.globalAlpha;AG.push('')}}}M.fill=function(){this.stroke(true)};M.closePath=function(){this.currentPath_.push({type:"close"})};M.getCoords_=function(j,i){var Z=this.m_;return{x:D*(j*Z[0][0]+i*Z[1][0]+Z[2][0])-F,y:D*(j*Z[0][1]+i*Z[1][1]+Z[2][1])-F}};M.save=function(){var Z={};Q(this,Z);this.aStack_.push(Z);this.mStack_.push(this.m_);this.m_=d(V(),this.m_)};M.restore=function(){if(this.aStack_.length){Q(this.aStack_.pop(),this);this.m_=this.mStack_.pop()}};function H(Z){return isFinite(Z[0][0])&&isFinite(Z[0][1])&&isFinite(Z[1][0])&&isFinite(Z[1][1])&&isFinite(Z[2][0])&&isFinite(Z[2][1])}function y(i,Z,j){if(!H(Z)){return }i.m_=Z;if(j){var p=Z[0][0]*Z[1][1]-Z[0][1]*Z[1][0];i.lineScale_=k(b(p))}}M.translate=function(j,i){var Z=[[1,0,0],[0,1,0],[j,i,1]];y(this,d(Z,this.m_),false)};M.rotate=function(i){var m=U(i);var j=J(i);var Z=[[m,j,0],[-j,m,0],[0,0,1]];y(this,d(Z,this.m_),false)};M.scale=function(j,i){this.arcScaleX_*=j;this.arcScaleY_*=i;var Z=[[j,0,0],[0,i,0],[0,0,1]];y(this,d(Z,this.m_),true)};M.transform=function(p,m,AF,AE,i,Z){var j=[[p,m,0],[AF,AE,0],[i,Z,1]];y(this,d(j,this.m_),true)};M.setTransform=function(AE,p,AG,AF,j,i){var Z=[[AE,p,0],[AG,AF,0],[j,i,1]];y(this,Z,true)};M.drawText_=function(AK,AI,AH,AN,AG){var AM=this.m_,AQ=1000,i=0,AP=AQ,AF={x:0,y:0},AE=[];var Z=P(X(this.font),this.element_);var j=AA(Z);var AR=this.element_.currentStyle;var p=this.textAlign.toLowerCase();switch(p){case"left":case"center":case"right":break;case"end":p=AR.direction=="ltr"?"right":"left";break;case"start":p=AR.direction=="rtl"?"right":"left";break;default:p="left"}switch(this.textBaseline){case"hanging":case"top":AF.y=Z.size/1.75;break;case"middle":break;default:case null:case"alphabetic":case"ideographic":case"bottom":AF.y=-Z.size/2.25;break}switch(p){case"right":i=AQ;AP=0.05;break;case"center":i=AP=AQ/2;break}var AO=this.getCoords_(AI+AF.x,AH+AF.y);AE.push('');if(AG){R(this,AE)}else{a(this,AE,{x:-i,y:0},{x:AP,y:Z.size})}var AL=AM[0][0].toFixed(3)+","+AM[1][0].toFixed(3)+","+AM[0][1].toFixed(3)+","+AM[1][1].toFixed(3)+",0,0";var AJ=K(AO.x/D)+","+K(AO.y/D);AE.push('','','');this.element_.insertAdjacentHTML("beforeEnd",AE.join(""))};M.fillText=function(j,Z,m,i){this.drawText_(j,Z,m,i,false)};M.strokeText=function(j,Z,m,i){this.drawText_(j,Z,m,i,true)};M.measureText=function(j){if(!this.textMeasureEl_){var Z='';this.element_.insertAdjacentHTML("beforeEnd",Z);this.textMeasureEl_=this.element_.lastChild}var i=this.element_.ownerDocument;this.textMeasureEl_.innerHTML="";this.textMeasureEl_.style.font=this.font;this.textMeasureEl_.appendChild(i.createTextNode(j));return{width:this.textMeasureEl_.offsetWidth}};M.clip=function(){};M.arcTo=function(){};M.createPattern=function(i,Z){return new u(i,Z)};function v(Z){this.type_=Z;this.x0_=0;this.y0_=0;this.r0_=0;this.x1_=0;this.y1_=0;this.r1_=0;this.colors_=[]}v.prototype.addColorStop=function(i,Z){Z=Y(Z);this.colors_.push({offset:i,color:Z.color,alpha:Z.alpha})};function u(i,Z){q(i);switch(Z){case"repeat":case null:case"":this.repetition_="repeat";break;case"repeat-x":case"repeat-y":case"no-repeat":this.repetition_=Z;break;default:n("SYNTAX_ERR")}this.src_=i.src;this.width_=i.width;this.height_=i.height}function n(Z){throw new o(Z)}function q(Z){if(!Z||Z.nodeType!=1||Z.tagName!="IMG"){n("TYPE_MISMATCH_ERR")}if(Z.readyState!="complete"){n("INVALID_STATE_ERR")}}function o(Z){this.code=this[Z];this.message=Z+": DOM Exception "+this.code}var x=o.prototype=new Error;x.INDEX_SIZE_ERR=1;x.DOMSTRING_SIZE_ERR=2;x.HIERARCHY_REQUEST_ERR=3;x.WRONG_DOCUMENT_ERR=4;x.INVALID_CHARACTER_ERR=5;x.NO_DATA_ALLOWED_ERR=6;x.NO_MODIFICATION_ALLOWED_ERR=7;x.NOT_FOUND_ERR=8;x.NOT_SUPPORTED_ERR=9;x.INUSE_ATTRIBUTE_ERR=10;x.INVALID_STATE_ERR=11;x.SYNTAX_ERR=12;x.INVALID_MODIFICATION_ERR=13;x.NAMESPACE_ERR=14;x.INVALID_ACCESS_ERR=15;x.VALIDATION_ERR=16;x.TYPE_MISMATCH_ERR=17;G_vmlCanvasManager=E;CanvasRenderingContext2D=W;CanvasGradient=v;CanvasPattern=u;DOMException=o})()}; --------------------------------------------------------------------------------