added periodic actions (licence verification, statistics report, start with nag window)
This commit is contained in:
parent
72665f55a4
commit
988ef7de8e
|
@ -15,6 +15,7 @@ const (
|
||||||
statisticsPath = "/telemetry/statistics"
|
statisticsPath = "/telemetry/statistics"
|
||||||
crashPath = "/telemetry/crash"
|
crashPath = "/telemetry/crash"
|
||||||
registrationPath = "/license/registration"
|
registrationPath = "/license/registration"
|
||||||
|
verificationPath = "/license/verification"
|
||||||
jsonContentType = "application/json"
|
jsonContentType = "application/json"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -44,8 +45,18 @@ func (c *BackendClient) ReportInstallation(statistics *metadata.Statistics) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *BackendClient) ReportUsageStatistics(error string, statistics *metadata.Statistics) {
|
func (c *BackendClient) ReportStatistics(statistics *metadata.Statistics) {
|
||||||
// TODO
|
|
||||||
|
buf := new(bytes.Buffer)
|
||||||
|
err := json.NewEncoder(buf).Encode(statistics)
|
||||||
|
if err != nil {
|
||||||
|
c.ReportCrash(err.Error(), statistics)
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = http.Post(backendUrl+statisticsPath, jsonContentType, buf)
|
||||||
|
if err != nil {
|
||||||
|
c.ReportCrash(err.Error(), statistics)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *BackendClient) ReportCrash(error string, statistics *metadata.Statistics) {
|
func (c *BackendClient) ReportCrash(error string, statistics *metadata.Statistics) {
|
||||||
|
@ -100,3 +111,35 @@ func (c *BackendClient) RegisterLicenseKey(licenseKey string, statistics *metada
|
||||||
|
|
||||||
return &license, nil
|
return &license, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *BackendClient) VerifyLicenseKey(licenseKey string) (*metadata.License, error) {
|
||||||
|
|
||||||
|
req := struct {
|
||||||
|
LicenseKey string
|
||||||
|
}{
|
||||||
|
licenseKey,
|
||||||
|
}
|
||||||
|
|
||||||
|
buf := new(bytes.Buffer)
|
||||||
|
err := json.NewEncoder(buf).Encode(req)
|
||||||
|
if err != nil {
|
||||||
|
c.ReportCrash(err.Error(), nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
response, err := http.Post(
|
||||||
|
backendUrl+verificationPath, jsonContentType, buf)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if response.StatusCode != 200 {
|
||||||
|
body, _ := ioutil.ReadAll(response.Body)
|
||||||
|
return nil, errors.New(string(body))
|
||||||
|
}
|
||||||
|
|
||||||
|
var license metadata.License
|
||||||
|
json.NewDecoder(response.Body).Decode(&license)
|
||||||
|
|
||||||
|
return &license, nil
|
||||||
|
}
|
||||||
|
|
|
@ -63,30 +63,21 @@ func NewIntro(palette console.Palette) *Intro {
|
||||||
|
|
||||||
func (intro *Intro) Draw(buffer *ui.Buffer) {
|
func (intro *Intro) Draw(buffer *ui.Buffer) {
|
||||||
|
|
||||||
logo := []string{
|
introText := append(util.AsciiLogo, []string{
|
||||||
" __ ",
|
|
||||||
" _________ ____ ___ ____ / /__ _____",
|
|
||||||
" / ___/ __ `/ __ `__ \\/ __ \\/ / _ \\/ ___/",
|
|
||||||
" (__ ) /_/ / / / / / / /_/ / / __/ / ",
|
|
||||||
"/____/\\__,_/_/ /_/ /_/ .___/_/\\___/_/ ",
|
|
||||||
" /_/ ",
|
|
||||||
}
|
|
||||||
|
|
||||||
introText := append(logo, []string{
|
|
||||||
"", "", "",
|
"", "", "",
|
||||||
"Welcome.",
|
"Welcome.",
|
||||||
"Sampler is free of charge for personal use, but license must be purchased to use it for business purposes.",
|
"Sampler is free of charge for personal use, but license must be purchased to use it for business purposes.",
|
||||||
"Clicking below indicates you agree to the terms of the license agreement and privacy policy: www.sampler.dev/license",
|
"By proceeding, you agree to the terms of the license agreement and privacy policy: www.sampler.dev/license",
|
||||||
"", "", "",
|
"", "", "",
|
||||||
"How do you plan to use Sampler?",
|
"How do you plan to use Sampler?",
|
||||||
}...)
|
}...)
|
||||||
|
|
||||||
commericalText := append(logo, []string{
|
commericalText := append(util.AsciiLogo, []string{
|
||||||
"", "", "", "",
|
"", "", "", "",
|
||||||
"Please visit www.sampler.dev to purchase a license and then start Sampler with --license flag",
|
"Please visit www.sampler.dev to purchase a license and then start Sampler with --license flag",
|
||||||
}...)
|
}...)
|
||||||
|
|
||||||
personalText := append(logo, []string{
|
personalText := append(util.AsciiLogo, []string{
|
||||||
"", "", "", "",
|
"", "", "", "",
|
||||||
"Sampler is always free for non-commercial use, but you can support the project and buy a personal license:",
|
"Sampler is always free for non-commercial use, but you can support the project and buy a personal license:",
|
||||||
"www.sampler.dev",
|
"www.sampler.dev",
|
||||||
|
|
|
@ -10,6 +10,7 @@ import (
|
||||||
"github.com/sqshq/sampler/data"
|
"github.com/sqshq/sampler/data"
|
||||||
"image"
|
"image"
|
||||||
"math"
|
"math"
|
||||||
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Layout struct {
|
type Layout struct {
|
||||||
|
@ -18,10 +19,12 @@ type Layout struct {
|
||||||
statusbar *component.StatusBar
|
statusbar *component.StatusBar
|
||||||
menu *component.Menu
|
menu *component.Menu
|
||||||
intro *component.Intro
|
intro *component.Intro
|
||||||
|
nag *component.NagWindow
|
||||||
ChangeModeEvents chan Mode
|
ChangeModeEvents chan Mode
|
||||||
mode Mode
|
mode Mode
|
||||||
selection int
|
selection int
|
||||||
positionsChanged bool
|
positionsChanged bool
|
||||||
|
startupTime time.Time
|
||||||
}
|
}
|
||||||
|
|
||||||
type Mode rune
|
type Mode rune
|
||||||
|
@ -29,25 +32,28 @@ type Mode rune
|
||||||
const (
|
const (
|
||||||
ModeDefault Mode = 0
|
ModeDefault Mode = 0
|
||||||
ModeIntro Mode = 1
|
ModeIntro Mode = 1
|
||||||
ModePause Mode = 2
|
ModeNag Mode = 2
|
||||||
ModeComponentSelect Mode = 3
|
ModePause Mode = 3
|
||||||
ModeMenuOptionSelect Mode = 4
|
ModeComponentSelect Mode = 4
|
||||||
ModeComponentMove Mode = 5
|
ModeMenuOptionSelect Mode = 5
|
||||||
ModeComponentResize Mode = 6
|
ModeComponentMove Mode = 6
|
||||||
ModeChartPinpoint Mode = 7
|
ModeComponentResize Mode = 7
|
||||||
|
ModeChartPinpoint Mode = 8
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
minDimension = 3
|
minDimension = 3
|
||||||
statusbarHeight = 1
|
statusbarHeight = 1
|
||||||
|
nagWindowDurationSec = 5
|
||||||
)
|
)
|
||||||
|
|
||||||
func NewLayout(statusline *component.StatusBar, menu *component.Menu, intro *component.Intro) *Layout {
|
func NewLayout(statusline *component.StatusBar, menu *component.Menu, intro *component.Intro, nag *component.NagWindow) *Layout {
|
||||||
|
|
||||||
width, height := ui.TerminalDimensions()
|
width, height := ui.TerminalDimensions()
|
||||||
block := *ui.NewBlock()
|
block := *ui.NewBlock()
|
||||||
block.SetRect(0, 0, width, height)
|
block.SetRect(0, 0, width, height)
|
||||||
intro.SetRect(0, 0, width, height)
|
intro.SetRect(0, 0, width, height)
|
||||||
|
nag.SetRect(0, 0, width, height)
|
||||||
statusline.SetRect(0, height-statusbarHeight, width, height)
|
statusline.SetRect(0, height-statusbarHeight, width, height)
|
||||||
|
|
||||||
return &Layout{
|
return &Layout{
|
||||||
|
@ -56,9 +62,11 @@ func NewLayout(statusline *component.StatusBar, menu *component.Menu, intro *com
|
||||||
statusbar: statusline,
|
statusbar: statusline,
|
||||||
menu: menu,
|
menu: menu,
|
||||||
intro: intro,
|
intro: intro,
|
||||||
|
nag: nag,
|
||||||
mode: ModeDefault,
|
mode: ModeDefault,
|
||||||
selection: 0,
|
selection: 0,
|
||||||
ChangeModeEvents: make(chan Mode, 10),
|
ChangeModeEvents: make(chan Mode, 10),
|
||||||
|
startupTime: time.Now(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -66,10 +74,14 @@ func (l *Layout) AddComponent(cpt *component.Component) {
|
||||||
l.Components = append(l.Components, cpt)
|
l.Components = append(l.Components, cpt)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (l *Layout) RunIntro() {
|
func (l *Layout) StartWithIntro() {
|
||||||
l.mode = ModeIntro
|
l.mode = ModeIntro
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (l *Layout) StartWithNagWindow() {
|
||||||
|
l.mode = ModeNag
|
||||||
|
}
|
||||||
|
|
||||||
func (l *Layout) changeMode(m Mode) {
|
func (l *Layout) changeMode(m Mode) {
|
||||||
if m == ModeComponentResize || m == ModeComponentMove {
|
if m == ModeComponentResize || m == ModeComponentMove {
|
||||||
l.positionsChanged = true
|
l.positionsChanged = true
|
||||||
|
@ -79,7 +91,7 @@ func (l *Layout) changeMode(m Mode) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (l *Layout) HandleMouseClick(x int, y int) {
|
func (l *Layout) HandleMouseClick(x int, y int) {
|
||||||
if l.mode == ModeIntro {
|
if l.mode == ModeIntro || l.mode == ModeNag {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
l.menu.Idle()
|
l.menu.Idle()
|
||||||
|
@ -145,6 +157,8 @@ func (l *Layout) HandleKeyboardEvent(e string) {
|
||||||
} else {
|
} else {
|
||||||
l.changeMode(ModeDefault)
|
l.changeMode(ModeDefault)
|
||||||
}
|
}
|
||||||
|
case ModeNag:
|
||||||
|
l.nag.Accept()
|
||||||
}
|
}
|
||||||
case console.KeyEsc:
|
case console.KeyEsc:
|
||||||
l.resetAlerts()
|
l.resetAlerts()
|
||||||
|
@ -310,15 +324,28 @@ func (l *Layout) Draw(buffer *ui.Buffer) {
|
||||||
columnWidth := float64(l.GetRect().Dx()) / float64(console.ColumnsCount)
|
columnWidth := float64(l.GetRect().Dx()) / float64(console.ColumnsCount)
|
||||||
rowHeight := float64(l.GetRect().Dy()-statusbarHeight) / float64(console.RowsCount)
|
rowHeight := float64(l.GetRect().Dy()-statusbarHeight) / float64(console.RowsCount)
|
||||||
|
|
||||||
|
for _, c := range l.Components {
|
||||||
|
rectangle := calculateComponentCoordinates(c, columnWidth, rowHeight)
|
||||||
|
c.SetRect(rectangle.Min.X, rectangle.Min.Y, rectangle.Max.X, rectangle.Max.Y)
|
||||||
|
}
|
||||||
|
|
||||||
if l.mode == ModeIntro {
|
if l.mode == ModeIntro {
|
||||||
l.intro.SetRect(l.Min.X, l.Min.Y, l.Max.X, l.Max.Y)
|
l.intro.SetRect(l.Min.X, l.Min.Y, l.Max.X, l.Max.Y)
|
||||||
l.intro.Draw(buffer)
|
l.intro.Draw(buffer)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if l.mode == ModeNag {
|
||||||
|
if l.nag.IsAccepted() && time.Since(l.startupTime).Seconds() > nagWindowDurationSec {
|
||||||
|
l.mode = ModeDefault
|
||||||
|
} else {
|
||||||
|
l.nag.SetRect(l.Min.X, l.Min.Y, l.Max.X, l.Max.Y)
|
||||||
|
l.nag.Draw(buffer)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
for _, c := range l.Components {
|
for _, c := range l.Components {
|
||||||
rectangle := calculateComponentCoordinates(c, columnWidth, rowHeight)
|
|
||||||
c.SetRect(rectangle.Min.X, rectangle.Min.Y, rectangle.Max.X, rectangle.Max.Y)
|
|
||||||
c.Draw(buffer)
|
c.Draw(buffer)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,53 @@
|
||||||
|
package component
|
||||||
|
|
||||||
|
import (
|
||||||
|
ui "github.com/gizak/termui/v3"
|
||||||
|
"github.com/sqshq/sampler/component/util"
|
||||||
|
"github.com/sqshq/sampler/console"
|
||||||
|
)
|
||||||
|
|
||||||
|
type NagWindow struct {
|
||||||
|
*ui.Block
|
||||||
|
palette console.Palette
|
||||||
|
accepted bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewNagWindow(palette console.Palette) *NagWindow {
|
||||||
|
return &NagWindow{
|
||||||
|
Block: NewBlock("", false, palette),
|
||||||
|
palette: palette,
|
||||||
|
accepted: false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n *NagWindow) Accept() {
|
||||||
|
n.accepted = true
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n *NagWindow) IsAccepted() bool {
|
||||||
|
return n.accepted
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n *NagWindow) Draw(buffer *ui.Buffer) {
|
||||||
|
|
||||||
|
text := append(util.AsciiLogo, []string{
|
||||||
|
"", "", "",
|
||||||
|
"Thank you for using Sampler.",
|
||||||
|
"It is always free for non-commercial use, but you can support the project and buy a personal license.",
|
||||||
|
"",
|
||||||
|
"Please visit www.sampler.dev",
|
||||||
|
}...)
|
||||||
|
|
||||||
|
for i, a := range text {
|
||||||
|
util.PrintString(
|
||||||
|
a,
|
||||||
|
ui.NewStyle(n.palette.BaseColor),
|
||||||
|
util.GetMiddlePoint(n.Block.Rectangle, a, i-15),
|
||||||
|
buffer)
|
||||||
|
}
|
||||||
|
|
||||||
|
buffer.SetString(string(buttonOk), ui.NewStyle(n.palette.ReverseColor, n.palette.BaseColor),
|
||||||
|
util.GetMiddlePoint(n.Block.Rectangle, string(buttonOk), 4))
|
||||||
|
|
||||||
|
n.Block.Draw(buffer)
|
||||||
|
}
|
|
@ -9,6 +9,15 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var AsciiLogo = []string{
|
||||||
|
" __ ",
|
||||||
|
" _________ ____ ___ ____ / /__ _____",
|
||||||
|
" / ___/ __ `/ __ `__ \\/ __ \\/ / _ \\/ ___/",
|
||||||
|
" (__ ) /_/ / / / / / / /_/ / / __/ / ",
|
||||||
|
"/____/\\__,_/_/ /_/ /_/ .___/_/\\___/_/ ",
|
||||||
|
" /_/ ",
|
||||||
|
}
|
||||||
|
|
||||||
func FormatValue(value float64, scale int) string {
|
func FormatValue(value float64, scale int) string {
|
||||||
if math.Abs(value) == math.MaxFloat64 {
|
if math.Abs(value) == math.MaxFloat64 {
|
||||||
return "Inf"
|
return "Inf"
|
||||||
|
|
|
@ -1,33 +0,0 @@
|
||||||
variables:
|
|
||||||
PGPASSWORD: fred
|
|
||||||
mongoconnection: mongo --quiet --host=localhost blog
|
|
||||||
mysqlconnection: mysql -u root -s --database mysql --skip-column-names
|
|
||||||
neo4jconnection: cypher-shell -u neo4j -p 121314 --format plain
|
|
||||||
postgresconnection: psql -h localhost -U postgres --no-align --tuples-only
|
|
||||||
sshconnection: ssh -i ~/sqshq.pem ec2-user@3.215.108.82
|
|
||||||
textboxes:
|
|
||||||
- title: Neo4j
|
|
||||||
position: [[0, 0], [10, 40]]
|
|
||||||
pty: true
|
|
||||||
init: $neo4jconnection
|
|
||||||
sample: RETURN rand();
|
|
||||||
transform: echo "$sample" | tail -n 1
|
|
||||||
- title: Postgres
|
|
||||||
position: [[10, 0], [9, 40]]
|
|
||||||
init: $postgresconnection
|
|
||||||
sample: select random();
|
|
||||||
- title: MySQL
|
|
||||||
position: [[19, 0], [10, 40]]
|
|
||||||
pty: true
|
|
||||||
init: $mysqlconnection
|
|
||||||
sample: select rand();
|
|
||||||
- title: MongoDB
|
|
||||||
position: [[29, 0], [10, 40]]
|
|
||||||
rate-ms: 500
|
|
||||||
init: $mongoconnection
|
|
||||||
sample: sleep(3000);Date.now();
|
|
||||||
- title: SSH
|
|
||||||
position: [[39, 0], [41, 40]]
|
|
||||||
pty: true
|
|
||||||
init: $sshconnection
|
|
||||||
sample: top
|
|
26
main.go
26
main.go
|
@ -89,18 +89,23 @@ func main() {
|
||||||
defer player.Close()
|
defer player.Close()
|
||||||
|
|
||||||
palette := console.GetPalette(*cfg.Theme)
|
palette := console.GetPalette(*cfg.Theme)
|
||||||
lout := layout.NewLayout(
|
lout := layout.NewLayout(component.NewStatusLine(*opt.ConfigFile, palette, license),
|
||||||
component.NewStatusLine(*opt.ConfigFile, palette, license), component.NewMenu(palette), component.NewIntro(palette))
|
component.NewMenu(palette), component.NewIntro(palette), component.NewNagWindow(palette))
|
||||||
|
|
||||||
if statistics.LaunchCount == 0 {
|
if statistics.LaunchCount == 0 {
|
||||||
if !opt.DisableTelemetry {
|
if !opt.DisableTelemetry {
|
||||||
go bc.ReportInstallation(statistics)
|
go bc.ReportInstallation(statistics)
|
||||||
}
|
}
|
||||||
lout.RunIntro()
|
lout.StartWithIntro()
|
||||||
} else /* with random */ {
|
} else if statistics.LaunchCount%20 == 0 { // once in a while
|
||||||
// TODO if license == nil lout.showNagWindow() with timeout and OK button
|
if license == nil || !license.Valid {
|
||||||
// TODO if license != nil, verify license
|
lout.StartWithNagWindow()
|
||||||
// TODO report statistics
|
} else {
|
||||||
|
go verifyLicense(license, bc)
|
||||||
|
}
|
||||||
|
if !opt.DisableTelemetry {
|
||||||
|
go bc.ReportStatistics(statistics)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
metadata.PersistStatistics(cfg)
|
metadata.PersistStatistics(cfg)
|
||||||
|
@ -130,3 +135,10 @@ func registerLicense(statistics *metadata.Statistics, opt config.Options, bc *cl
|
||||||
console.Exit("License successfully verified, Sampler can be restarted without --license flag now. Thank you.")
|
console.Exit("License successfully verified, Sampler can be restarted without --license flag now. Thank you.")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func verifyLicense(license *metadata.License, bc *client.BackendClient) {
|
||||||
|
verifiedLicense, _ := bc.VerifyLicenseKey(*license.Key)
|
||||||
|
if verifiedLicense != nil {
|
||||||
|
metadata.SaveLicense(*verifiedLicense)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue