├── web
├── src
│ ├── assets
│ │ ├── .gitkeep
│ │ ├── logo.png
│ │ ├── angular.png
│ │ ├── github.png
│ │ ├── golang.png
│ │ ├── forkme_right_gray_6d6d6d.png
│ │ ├── filter.svg
│ │ └── filtera.svg
│ ├── app
│ │ ├── logout
│ │ │ ├── logout.component.html
│ │ │ └── logout.component.ts
│ │ ├── verify-email
│ │ │ ├── verify-email.component.html
│ │ │ └── verify-email.component.ts
│ │ ├── user
│ │ │ ├── user.component.html
│ │ │ └── user.component.ts
│ │ ├── utils
│ │ │ ├── color-percent.component.html
│ │ │ ├── date.ts
│ │ │ ├── color-percent.component.ts
│ │ │ ├── bytes.pipe.ts
│ │ │ └── mark-as-touched.directive.ts
│ │ ├── collection-tracking
│ │ │ ├── tracking.component.html
│ │ │ └── tracking.component.ts
│ │ ├── admin
│ │ │ ├── admin.component.ts
│ │ │ └── admin.component.html
│ │ ├── forms
│ │ │ ├── rvalidators.ts
│ │ │ └── invalid.component.ts
│ │ ├── home
│ │ │ ├── home.component.ts
│ │ │ └── home.component.html
│ │ ├── settings
│ │ │ ├── settings.component.ts
│ │ │ ├── settings.component.html
│ │ │ ├── profile
│ │ │ │ ├── profile.component.ts
│ │ │ │ └── profile.component.html
│ │ │ ├── delete-account
│ │ │ │ ├── delete-account.component.html
│ │ │ │ └── delete-account.component.ts
│ │ │ └── change-password
│ │ │ │ ├── change-password.component.html
│ │ │ │ └── change-password.component.ts
│ │ ├── admin-users
│ │ │ ├── admin-users.component.ts
│ │ │ └── admin-users.component.html
│ │ ├── toasty
│ │ │ ├── toasty.module.ts
│ │ │ ├── toasty.utils.ts
│ │ │ ├── toast.component.ts
│ │ │ └── toasty.component.ts
│ │ ├── admin-collections
│ │ │ ├── admin-collections.component.ts
│ │ │ └── admin-collections.component.html
│ │ ├── collection-create
│ │ │ ├── create.component.html
│ │ │ └── create.component.ts
│ │ ├── admin-backups
│ │ │ ├── admin-backups.component.html
│ │ │ └── admin-backups.component.ts
│ │ ├── reset-password
│ │ │ ├── reset-password.component.html
│ │ │ └── reset-password.component.ts
│ │ ├── collection-stat
│ │ │ ├── table-sum.component.ts
│ │ │ ├── table-sum.component.html
│ │ │ ├── stat.component.ts
│ │ │ └── stat.component.html
│ │ ├── forgot-password
│ │ │ ├── forgot-password.component.html
│ │ │ └── forgot-password.component.ts
│ │ ├── collection-settings
│ │ │ ├── teammates.component.html
│ │ │ ├── teammates.component.ts
│ │ │ ├── settings.component.html
│ │ │ └── settings.component.ts
│ │ ├── app.component.ts
│ │ ├── admin-users-create
│ │ │ ├── admin-users-create.component.html
│ │ │ └── admin-users-create.component.ts
│ │ ├── chart
│ │ │ └── chart.component.ts
│ │ ├── login
│ │ │ ├── login.component.html
│ │ │ └── login.component.ts
│ │ ├── registration
│ │ │ ├── registration.component.ts
│ │ │ └── registration.component.html
│ │ ├── app.component.html
│ │ ├── session
│ │ │ ├── session.component.ts
│ │ │ └── session.component.html
│ │ ├── collection-dashboard
│ │ │ └── dashboard.component.html
│ │ ├── admin-users-edit
│ │ │ ├── admin-users-edit.component.ts
│ │ │ └── admin-users-edit.component.html
│ │ ├── collection
│ │ │ ├── collection.component.html
│ │ │ └── collection.component.ts
│ │ └── app.module.ts
│ ├── favicon.ico
│ ├── environments
│ │ ├── environment.prod.ts
│ │ └── environment.ts
│ ├── typings.d.ts
│ ├── tsconfig.app.json
│ ├── main.ts
│ ├── tsconfig.spec.json
│ ├── index.html
│ ├── test.ts
│ ├── styles.scss
│ ├── polyfills.ts
│ └── tracker.js
├── proxy.conf.json
├── e2e
│ ├── app.po.ts
│ ├── tsconfig.e2e.json
│ └── app.e2e-spec.ts
├── .editorconfig
├── tsconfig.json
├── .gitignore
├── protractor.conf.js
├── README.md
├── karma.conf.js
├── package.json
├── tslint.json
└── angular.json
├── .gitignore
├── .travis.yml
├── internal
├── db
│ ├── models.go
│ ├── teammates.go
│ ├── models.proto
│ ├── shardbolt
│ │ ├── tx.go
│ │ ├── shard.go
│ │ ├── db_test.go
│ │ └── db.go
│ ├── db_test.go
│ ├── encode.go
│ └── seed.go
├── api
│ ├── backup.go
│ ├── config.go
│ ├── auth.go
│ ├── authtoken.go
│ ├── collect.go
│ ├── admin.go
│ ├── user.go
│ └── api.go
├── service
│ ├── backup.go
│ ├── authtoken.go
│ ├── error.go
│ ├── session.go
│ └── admin.go
├── mail
│ ├── templates_test.go
│ ├── mail.go
│ └── templates.go
├── geoip
│ └── geoip.go
└── config
│ └── read.go
├── go.mod
├── rightana.go
├── cmd.go
├── netseed.go
└── README.md
/web/src/assets/.gitkeep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/web/src/app/logout/logout.component.html:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/web/src/app/verify-email/verify-email.component.html:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/web/src/app/user/user.component.html:
--------------------------------------------------------------------------------
1 |
To start tracking, include the following JavaScript on your site:
3 |{{trackingCode}}
4 | | ID | 7 |Destination dir | 8 |Trigger URL | 9 |Run | 10 |
|---|---|---|---|
| {{b.id}} | 15 |{{b.dir}} | 16 |{{origin}}/api/backups/{{b.id}}/run | 17 |18 | |
| ID | 7 |Name | 8 |Owner | 9 |Created | 10 |Teammate count | 11 |
|---|---|---|---|---|
| {{c.id}} | 16 |{{c.name}} | 17 |{{c.owner_name}} | 18 |{{c.created / 1000000 | date:"yyyy.MM.dd HH:mm:ss"}} | 19 |{{c.teammate_count}} | 20 |
| # | 5 |{{name}} | 6 |Count | 7 |% | 8 |
|---|---|---|---|
| {{i+1}} | 13 |
14 | {{sum.name}}
15 | |
18 | {{sum.count}} | 19 |{{sum.percent | percent:"1.1-1"}} | 20 |
| 23 | show all 24 | | 25 ||||
| ID | 10 |Name | 11 |Created | 13 |Collections | 14 |Admin | 15 |Email Verified | 16 |Edit | 17 ||
|---|---|---|---|---|---|---|---|
| {{user.id}} | 22 |{{user.name}} | 23 |{{user.email}} | 24 |{{user.created / 1000000 | date:"yyyy.MM.dd HH:mm:ss"}} | 25 |26 | {{user.collection_count}} 27 | ✚ 28 | | 29 |✓ | 30 |✓ | 31 |edit | 32 |
5 |
6 |
9 | Carefree web analytics on your server!
14 |Hi {{.DisplayName}},
` 25 | footer := ` 26 |Not working? Try copying and pasting it to your browser.
27 |© {{.YearStr}} {{.AppName}}
28 | 29 | ` 30 | 31 | verifyEmail = template.Must(template.New("verifyEmail").Parse(` 32 | {{template "header" .}} 33 |Please click the following link to verify your email address:
34 |{{template "verifyEmailLink" .}}
35 | {{template "footer" .}} 36 | `)) 37 | verifyEmailLink := `{{.AppURL}}/{{.UserName}}/verify-email?verification_key={{.VerificationKey}}` 38 | template.Must(verifyEmail.New("header").Parse(header)) 39 | template.Must(verifyEmail.New("verifyEmailLink").Parse(verifyEmailLink)) 40 | template.Must(verifyEmail.New("footer").Parse(footer)) 41 | 42 | resetPassword = template.Must(template.New("resetPassword").Parse(` 43 | {{template "header" .}} 44 |Please click the following link to change your password within {{.ExpireMinutes}} minutes:
45 |{{template "resetPasswordLink" .}}
46 | {{template "footer" .}} 47 | `)) 48 | resetPasswordLink := `{{.AppURL}}/{{.UserName}}/reset-password?reset_key={{.ResetKey}}` 49 | template.Must(resetPassword.New("header").Parse(header)) 50 | template.Must(resetPassword.New("resetPasswordLink").Parse(resetPasswordLink)) 51 | template.Must(resetPassword.New("footer").Parse(footer)) 52 | } 53 | 54 | func getResetPasswordBody(userName, displayName, resetKey string, expireMinutes int) (string, error) { 55 | type params struct { 56 | UserName string 57 | DisplayName string 58 | ResetKey string 59 | ExpireMinutes int 60 | AppURL string 61 | AppName string 62 | YearStr string 63 | } 64 | body := &bytes.Buffer{} 65 | err := resetPassword.Execute(body, ¶ms{ 66 | userName, displayName, resetKey, expireMinutes, 67 | config.AppURL, config.AppName, getYearStr()}) 68 | if err != nil { 69 | return "", err 70 | } 71 | return body.String(), nil 72 | } 73 | 74 | func getVerifyEmailBody(userName, displayName, verificationKey string) (string, error) { 75 | type params struct { 76 | UserName string 77 | DisplayName string 78 | VerificationKey string 79 | AppURL string 80 | AppName string 81 | YearStr string 82 | } 83 | body := &bytes.Buffer{} 84 | err := verifyEmail.Execute(body, ¶ms{ 85 | userName, displayName, verificationKey, 86 | config.AppURL, config.AppName, getYearStr()}) 87 | if err != nil { 88 | return "", err 89 | } 90 | return body.String(), nil 91 | } 92 | 93 | func getYearStr() string { 94 | return strconv.Itoa(time.Now().Year()) 95 | } 96 | -------------------------------------------------------------------------------- /internal/db/shardbolt/db_test.go: -------------------------------------------------------------------------------- 1 | package shardbolt 2 | 3 | import ( 4 | "bytes" 5 | "errors" 6 | "log" 7 | "os" 8 | "testing" 9 | "time" 10 | ) 11 | 12 | var ( 13 | dir = "test" 14 | bucket = []byte("test") 15 | key = []byte("test") 16 | value = []byte("test") 17 | now = time.Now() 18 | mapFn = func(key []byte) string { 19 | t, err := unmarshalTime(key) 20 | if err != nil { 21 | panic(err) 22 | } 23 | return t.Format("2006-01") 24 | } 25 | ) 26 | 27 | func TestMain(m *testing.M) { 28 | err := os.RemoveAll(dir) 29 | if err != nil { 30 | log.Fatalln(err) 31 | } 32 | 33 | ret := m.Run() 34 | 35 | if ret == 0 { 36 | os.RemoveAll(dir) 37 | } 38 | 39 | os.Exit(ret) 40 | } 41 | 42 | func TestOpenNotExists(t *testing.T) { 43 | db, err := Open(dir, mapFn, 0666, nil) 44 | if err != nil { 45 | t.Error(err) 46 | } 47 | defer db.Close() 48 | } 49 | 50 | func TestPut(t *testing.T) { 51 | db, err := Open(dir, mapFn, 0666, nil) 52 | if err != nil { 53 | t.Error(err) 54 | } 55 | defer db.Close() 56 | 57 | err = db.Update(func(tx *MultiTx) error { 58 | return tx.Put(bucket, createKey(now, key), value) 59 | }) 60 | if err != nil { 61 | t.Error(err) 62 | } 63 | err = db.Update(func(tx *MultiTx) error { 64 | return tx.Put(bucket, createKey(now.Add(time.Duration(1)*time.Second), key), value) 65 | }) 66 | if err != nil { 67 | t.Error(err) 68 | } 69 | } 70 | 71 | func TestIterate(t *testing.T) { 72 | db, err := Open(dir, mapFn, 0666, nil) 73 | if err != nil { 74 | t.Error(err) 75 | } 76 | defer db.Close() 77 | 78 | count := 0 79 | db.Iterate(bucket, marshalTime(now), marshalTime(now.AddDate(0, 0, 1)), func(k []byte, v []byte) { 80 | count++ 81 | if !bytes.HasSuffix(k, key) { 82 | t.Error("bad key", k, key) 83 | } 84 | if bytes.Compare(v, value) != 0 { 85 | t.Error("bad value", v, value) 86 | } 87 | }) 88 | if count != 2 { 89 | t.Error(count) 90 | } 91 | 92 | } 93 | 94 | func TestGet(t *testing.T) { 95 | db, err := Open(dir, mapFn, 0666, nil) 96 | if err != nil { 97 | t.Error(err) 98 | } 99 | defer db.Close() 100 | 101 | v, err := db.Get(bucket, createKey(now, key)) 102 | if err != nil { 103 | t.Error(err) 104 | } 105 | if bytes.Compare(v, value) != 0 { 106 | t.Error("bad value", v, value) 107 | } 108 | 109 | } 110 | 111 | func marshalTime(t time.Time) []byte { 112 | nsec := t.UnixNano() 113 | enc := []byte{ 114 | byte(nsec >> 56), 115 | byte(nsec >> 48), 116 | byte(nsec >> 40), 117 | byte(nsec >> 32), 118 | byte(nsec >> 24), 119 | byte(nsec >> 16), 120 | byte(nsec >> 8), 121 | byte(nsec), 122 | } 123 | return enc 124 | } 125 | 126 | func unmarshalTime(data []byte) (time.Time, error) { 127 | if len(data) < 8 { 128 | return time.Time{}, errors.New("unmarshalTime: invalid length") 129 | } 130 | data = data[:8] 131 | nsec := int64(data[0])<<56 | 132 | int64(data[1])<<48 | 133 | int64(data[2])<<40 | 134 | int64(data[3])<<32 | 135 | int64(data[4])<<24 | 136 | int64(data[5])<<16 | 137 | int64(data[6])<<8 | 138 | int64(data[7]) 139 | return time.Unix(0, nsec), nil 140 | } 141 | 142 | func createKey(t time.Time, key []byte) []byte { 143 | return append(marshalTime(t), key...) 144 | } 145 | -------------------------------------------------------------------------------- /web/src/app/session/session.component.html: -------------------------------------------------------------------------------- 1 || Begin | 10 |Duration | 11 |Device | 12 |OS | 13 |Browser | 14 |Lang | 15 |Screen | 16 |Window | 17 |Hostname | 18 |Location | 19 |AS Name | 20 |Pageviews | 21 |||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| {{s.begin / 1000000 | date:"yyyy.MM.dd HH:mm:ss"}} | 27 |{{s.duration/60 | number:"1.0-0"}} m | 28 |{{s.device_type}} | 29 |{{s.device_os}} | 30 |{{s.browser_name}} {{s.browser_version}} | 31 |{{s.browser_language}} | 32 |{{s.screen_resolution}} | 33 |{{s.window_resolution}} | 34 |{{s.user_hostname}} | 35 |{{s.country_code}} {{s.city}} | 36 |{{s.as_name}} | 37 |{{s.pageview_count}} | 38 |||||||
|
41 | Begin: {{s.begin / 1000000 | date:"yyyy.MM.dd HH:mm:ss"}}
42 | End: {{s.begin / 1000000 + s.duration*1000 | date:"yyyy.MM.dd HH:mm:ss"}}
43 | User IP: {{s.user_ip}}
44 | User Hostname: {{s.user_hostname}}
45 | AS Number: {{s.as_number}}
46 | AS Name: {{s.as_name}}
47 | User Agent: {{s.user_agent}}
48 | Hostname: {{s.hostname}}
49 | Referrer: {{s.referrer}}
50 | |
51 | |||||||||||||||||
54 |
|
71 | |||||||||||||||||
| 75 | load more 76 | | 77 ||||||||||||||||||