Self-Hosted Servers Experiment
the build was successful
Details
the build was successful
Details
This commit is contained in:
parent
fd45f72a09
commit
a276d5732a
2
go.mod
2
go.mod
|
@ -3,7 +3,7 @@ module cwtch.im/ui
|
||||||
go 1.12
|
go 1.12
|
||||||
|
|
||||||
require (
|
require (
|
||||||
cwtch.im/cwtch v0.4.6
|
cwtch.im/cwtch v0.4.7
|
||||||
git.openprivacy.ca/openprivacy/connectivity v1.3.1
|
git.openprivacy.ca/openprivacy/connectivity v1.3.1
|
||||||
git.openprivacy.ca/openprivacy/log v1.0.1
|
git.openprivacy.ca/openprivacy/log v1.0.1
|
||||||
github.com/c-bata/go-prompt v0.2.3 // indirect
|
github.com/c-bata/go-prompt v0.2.3 // indirect
|
||||||
|
|
4
go.sum
4
go.sum
|
@ -9,8 +9,8 @@ cwtch.im/cwtch v0.4.4 h1:LC9SngDx+iLXFiDK8GUkSxaWqloXQcPijvMPmwt9h80=
|
||||||
cwtch.im/cwtch v0.4.4/go.mod h1:10gBkMSqAH95Pz4jTx5mpIHE+dkn+4kRC4BFTxWuQK8=
|
cwtch.im/cwtch v0.4.4/go.mod h1:10gBkMSqAH95Pz4jTx5mpIHE+dkn+4kRC4BFTxWuQK8=
|
||||||
cwtch.im/cwtch v0.4.5 h1:BK4IMqCMf9xNmeLzaVDUbl2bnXdw5fOWXvEGBMTOjXM=
|
cwtch.im/cwtch v0.4.5 h1:BK4IMqCMf9xNmeLzaVDUbl2bnXdw5fOWXvEGBMTOjXM=
|
||||||
cwtch.im/cwtch v0.4.5/go.mod h1:Mh7vQQ3z55+prpX6EuUkg4QNQkBACMoDcgCNBeAH2EY=
|
cwtch.im/cwtch v0.4.5/go.mod h1:Mh7vQQ3z55+prpX6EuUkg4QNQkBACMoDcgCNBeAH2EY=
|
||||||
cwtch.im/cwtch v0.4.6 h1:jQT0WZY0BGS/EKZrtvL48kMYoed00/q1ycvI0u7Dez4=
|
cwtch.im/cwtch v0.4.7 h1:y8Roq1L1PAs0FkBDdk+7EUVLCHwyzl+dOEfVu4VX0Ic=
|
||||||
cwtch.im/cwtch v0.4.6/go.mod h1:Mh7vQQ3z55+prpX6EuUkg4QNQkBACMoDcgCNBeAH2EY=
|
cwtch.im/cwtch v0.4.7/go.mod h1:Mh7vQQ3z55+prpX6EuUkg4QNQkBACMoDcgCNBeAH2EY=
|
||||||
cwtch.im/tapir v0.2.0 h1:7MkoR5+uEuPW34/O0GZRidnIjq/01Cfm8nl5IRuqpGc=
|
cwtch.im/tapir v0.2.0 h1:7MkoR5+uEuPW34/O0GZRidnIjq/01Cfm8nl5IRuqpGc=
|
||||||
cwtch.im/tapir v0.2.0/go.mod h1:xzzZ28adyUXNkYL1YodcHsAiTt3IJ8Loc29YVn9mIEQ=
|
cwtch.im/tapir v0.2.0/go.mod h1:xzzZ28adyUXNkYL1YodcHsAiTt3IJ8Loc29YVn9mIEQ=
|
||||||
cwtch.im/tapir v0.2.1 h1:t1YJB9q5sV1A9xwiiwL6WVfw3dwQWLoecunuzT1PQtw=
|
cwtch.im/tapir v0.2.1 h1:t1YJB9q5sV1A9xwiiwL6WVfw3dwQWLoecunuzT1PQtw=
|
||||||
|
|
|
@ -2,7 +2,25 @@ package constants
|
||||||
|
|
||||||
import "cwtch.im/cwtch/event"
|
import "cwtch.im/cwtch/event"
|
||||||
|
|
||||||
// The server manage defines its own events...
|
// The server manager defines its own events, most should be self-explanatory:
|
||||||
const (
|
const (
|
||||||
|
NewServer = event.Type("NewServer")
|
||||||
|
|
||||||
|
// Force a UI update
|
||||||
ListServers = event.Type("ListServers")
|
ListServers = event.Type("ListServers")
|
||||||
|
|
||||||
|
// Takes an Onion, used to toggle off/on Server availability
|
||||||
|
StartServer = event.Type("StartServer")
|
||||||
|
StopServer = event.Type("StopServer")
|
||||||
|
|
||||||
|
// Takes an Onion and a AutoStartEnabled boolean
|
||||||
|
AutoStart = event.Type("AutoStart")
|
||||||
|
|
||||||
|
// Get the status of a particular server (takes an Onion)
|
||||||
|
CheckServerStatus = event.Type("CheckServerStatus")
|
||||||
|
ServerStatusUpdate = event.Type("ServerStatusUpdate")
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
AutoStartEnabled = event.Field("AutoStartEnabled")
|
||||||
)
|
)
|
|
@ -1,57 +1,267 @@
|
||||||
package servers
|
package servers
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"crypto/rand"
|
||||||
"cwtch.im/cwtch/event"
|
"cwtch.im/cwtch/event"
|
||||||
|
"cwtch.im/cwtch/model"
|
||||||
"cwtch.im/cwtch/server"
|
"cwtch.im/cwtch/server"
|
||||||
"cwtch.im/ui/go/constants"
|
"cwtch.im/ui/go/constants"
|
||||||
"cwtch.im/ui/go/the"
|
"cwtch.im/ui/go/the"
|
||||||
"cwtch.im/ui/go/ui"
|
"cwtch.im/ui/go/ui"
|
||||||
|
"encoding/base64"
|
||||||
|
"fmt"
|
||||||
|
"git.openprivacy.ca/openprivacy/connectivity"
|
||||||
|
"git.openprivacy.ca/openprivacy/connectivity/tor"
|
||||||
"git.openprivacy.ca/openprivacy/log"
|
"git.openprivacy.ca/openprivacy/log"
|
||||||
|
"io/ioutil"
|
||||||
|
"math"
|
||||||
|
"math/big"
|
||||||
|
"os"
|
||||||
|
"path"
|
||||||
|
"sync"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// ServerManager is responsible for managing user operated servers
|
// ServerManager is responsible for managing user operated servers
|
||||||
type ServerManager struct {
|
type ServerManager struct {
|
||||||
servers map[string]server.Server
|
servers sync.Map
|
||||||
|
configDir string
|
||||||
|
acn connectivity.ACN
|
||||||
}
|
}
|
||||||
|
|
||||||
func LaunchServiceManager(gcd *ui.GrandCentralDispatcher) {
|
type serverStatusCache struct {
|
||||||
|
online bool
|
||||||
|
autostart bool
|
||||||
|
messages uint64
|
||||||
|
bundle []byte
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// LaunchServiceManager is responsible for setting up everything relating to managing servers in the UI.
|
||||||
|
func LaunchServiceManager(gcd *ui.GrandCentralDispatcher, acn connectivity.ACN, configDir string) {
|
||||||
sm := new(ServerManager)
|
sm := new(ServerManager)
|
||||||
|
sm.configDir = configDir
|
||||||
|
sm.acn = acn
|
||||||
sm.Init(gcd)
|
sm.Init(gcd)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// initializeServerCache sets up a new cache based on the config. Notably it stores a newly signed keybundle that
|
||||||
|
// the ui uses to allow people to share server key bundles.
|
||||||
|
func initializeServerCache(config server.Config) (*server.Server,serverStatusCache) {
|
||||||
|
newServer := new(server.Server)
|
||||||
|
newServer.Setup(config)
|
||||||
|
newServer.KeyBundle()
|
||||||
|
return newServer, serverStatusCache{
|
||||||
|
false,
|
||||||
|
config.AutoStart,
|
||||||
|
0,
|
||||||
|
newServer.KeyBundle().Serialize(),
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
func (sm *ServerManager) Init(gcd *ui.GrandCentralDispatcher) {
|
func (sm *ServerManager) Init(gcd *ui.GrandCentralDispatcher) {
|
||||||
sm.servers = make(map[string]server.Server)
|
|
||||||
|
|
||||||
q := event.NewQueue()
|
q := event.NewQueue()
|
||||||
|
the.AppBus.Subscribe(constants.NewServer, q)
|
||||||
the.AppBus.Subscribe(constants.ListServers, q)
|
the.AppBus.Subscribe(constants.ListServers, q)
|
||||||
|
the.AppBus.Subscribe(constants.ServerStatusUpdate, q)
|
||||||
|
the.AppBus.Subscribe(event.Shutdown, q)
|
||||||
|
|
||||||
|
// NOTE: Servers don't (yet?) benefit from any kind of encryption of their config (unlike peer profiles)
|
||||||
|
// FIXME: We assume servers can be malicious, a compromised server will not be able to derive any additional
|
||||||
|
// metadata from a hosted group (assuming that the server isn't also a group participant) - this logic doesn't
|
||||||
|
// really hold if the server being hosted as part of the UI - however a large pool of self hosted servers also mitigates
|
||||||
|
// many of risks e.g lack of server diversification / availability of servers.
|
||||||
|
// Like many parts of the metadata resistant risk model, it is a compromise
|
||||||
|
log.Debugf("Reading server directory: %v", sm.configDir)
|
||||||
|
os.MkdirAll(sm.configDir,0700)
|
||||||
|
items, _ := ioutil.ReadDir(sm.configDir)
|
||||||
|
for _, item := range items {
|
||||||
|
if item.IsDir() {
|
||||||
|
log.Debugf("Found Server Directory %v / %v", sm.configDir, item.Name())
|
||||||
|
config := server.LoadConfig(path.Join(sm.configDir, item.Name()), "serverconfig")
|
||||||
|
identity := config.Identity()
|
||||||
|
log.Debugf("Launching Server goroutine for %v", identity.Hostname())
|
||||||
|
s,cache := initializeServerCache(config)
|
||||||
|
sm.servers.Store(identity.Hostname(), cache)
|
||||||
|
go sm.runServer(s)
|
||||||
|
} else {
|
||||||
|
log.Debugf("Found non server directory: %v", item.Name())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update the UI with all know servers
|
||||||
|
sm.ListServers(gcd)
|
||||||
|
|
||||||
for {
|
for {
|
||||||
e := q.Next()
|
e := q.Next()
|
||||||
|
|
||||||
switch e.EventType {
|
switch e.EventType {
|
||||||
case constants.ListServers: {
|
case constants.NewServer:
|
||||||
sm.ListServers(gcd)
|
sm.NewServer()
|
||||||
}
|
sm.ListServers(gcd)
|
||||||
|
case constants.ListServers:
|
||||||
|
sm.ListServers(gcd)
|
||||||
|
case constants.ServerStatusUpdate:
|
||||||
|
sm.ListServers(gcd)
|
||||||
|
case event.Shutdown:
|
||||||
|
// we don't need to do anything else here, all the server goroutines will also subscribe to the
|
||||||
|
// shutdown event and return nicely.
|
||||||
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO Replace with details from actual hosted servers. Right now these values are used to sketch / test out the
|
|
||||||
// UI QML
|
// NewServer createa a new server
|
||||||
|
func (sm *ServerManager) NewServer() {
|
||||||
|
log.Debugf("Adding a new Server")
|
||||||
|
num, err := rand.Int(rand.Reader, big.NewInt(math.MaxUint32))
|
||||||
|
if err == nil {
|
||||||
|
serverDir := path.Join(sm.configDir, num.String());
|
||||||
|
os.MkdirAll(serverDir,0700)
|
||||||
|
config := server.LoadConfig(serverDir, "serverconfig")
|
||||||
|
identity := config.Identity()
|
||||||
|
s, cache := initializeServerCache(config)
|
||||||
|
sm.servers.Store(identity.Hostname(),cache)
|
||||||
|
go sm.runServer(s)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// runServer sets up an event queue per server to allow them to manage their own state.
|
||||||
|
func (sm *ServerManager) runServer(s * server.Server) {
|
||||||
|
q := event.NewQueue()
|
||||||
|
the.AppBus.Subscribe(constants.StartServer, q)
|
||||||
|
the.AppBus.Subscribe(constants.StopServer, q)
|
||||||
|
the.AppBus.Subscribe(constants.CheckServerStatus, q)
|
||||||
|
the.AppBus.Subscribe(constants.AutoStart, q)
|
||||||
|
the.AppBus.Subscribe(event.Shutdown, q)
|
||||||
|
|
||||||
|
|
||||||
|
identity := s.Identity()
|
||||||
|
|
||||||
|
cache,ok := sm.servers.Load(identity.Hostname())
|
||||||
|
if ok {
|
||||||
|
serverStatusCache := cache.(serverStatusCache)
|
||||||
|
if serverStatusCache.autostart {
|
||||||
|
the.AppBus.Publish(event.NewEvent(constants.StartServer, map[event.Field]string{event.Onion: identity.Hostname()}))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
log.Debugf("Launching Server %v", identity.Hostname())
|
||||||
|
log.Debugf("Launching Event Bus for Server %v", identity.Hostname())
|
||||||
|
for {
|
||||||
|
e := q.Next()
|
||||||
|
|
||||||
|
switch e.EventType {
|
||||||
|
case constants.StartServer:
|
||||||
|
onion := e.Data[event.Onion]
|
||||||
|
if onion == identity.Hostname() {
|
||||||
|
if running,_ := s.CheckStatus(); running {
|
||||||
|
// we are already running
|
||||||
|
log.Debugf("Server %v Already Running", onion)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
log.Debugf("Running Server %v", onion)
|
||||||
|
s.Run(sm.acn)
|
||||||
|
|
||||||
|
// TODO Remove this whole blob once we can actually create groups in the UI
|
||||||
|
// This is for developers who want to test their newly created server with a group.
|
||||||
|
group, _ := model.NewGroup(tor.GetTorV3Hostname(identity.PublicKey()))
|
||||||
|
group.SignGroup([]byte{})
|
||||||
|
invite, err := group.Invite([]byte{})
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
fmt.Printf("Secret Debugging Group for Testing: %v\n", "torv3"+base64.StdEncoding.EncodeToString(invite))
|
||||||
|
cache,ok := sm.servers.Load(onion)
|
||||||
|
if ok {
|
||||||
|
serverStatusCache := cache.(serverStatusCache)
|
||||||
|
serverStatusCache.online = true
|
||||||
|
sm.servers.Store(onion, serverStatusCache)
|
||||||
|
the.AppBus.Publish(event.NewEvent(constants.ServerStatusUpdate, map[event.Field]string{}))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case constants.StopServer:
|
||||||
|
onion := e.Data[event.Onion]
|
||||||
|
if onion == identity.Hostname() {
|
||||||
|
s.Shutdown()
|
||||||
|
cache,ok := sm.servers.Load(onion)
|
||||||
|
if ok {
|
||||||
|
serverStatusCache := cache.(serverStatusCache)
|
||||||
|
serverStatusCache.online = false
|
||||||
|
sm.servers.Store(onion, serverStatusCache)
|
||||||
|
the.AppBus.Publish(event.NewEvent(constants.ServerStatusUpdate, map[event.Field]string{}))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case constants.CheckServerStatus:
|
||||||
|
onion := e.Data[event.Onion]
|
||||||
|
if onion == identity.Hostname() {
|
||||||
|
// Kick off a restart
|
||||||
|
if _, err := s.CheckStatus(); err != nil {
|
||||||
|
s.Shutdown()
|
||||||
|
s.Run(sm.acn)
|
||||||
|
}
|
||||||
|
cache, ok := sm.servers.Load(onion)
|
||||||
|
if ok {
|
||||||
|
serverStatusCache := cache.(serverStatusCache)
|
||||||
|
serverStatusCache.messages = uint64(s.GetStatistics().TotalMessages)
|
||||||
|
log.Debugf("Server Statistics %v %v", onion, serverStatusCache.messages)
|
||||||
|
sm.servers.Store(onion, serverStatusCache)
|
||||||
|
the.AppBus.Publish(event.NewEvent(constants.ServerStatusUpdate, map[event.Field]string{}))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case constants.AutoStart:
|
||||||
|
onion := e.Data[event.Onion]
|
||||||
|
if onion == identity.Hostname() {
|
||||||
|
autostart := e.Data[constants.AutoStartEnabled] == event.True
|
||||||
|
s.ConfigureAutostart(autostart)
|
||||||
|
cache,ok := sm.servers.Load(onion)
|
||||||
|
if ok {
|
||||||
|
serverStatusCache := cache.(serverStatusCache)
|
||||||
|
serverStatusCache.autostart = autostart
|
||||||
|
sm.servers.Store(onion, serverStatusCache)
|
||||||
|
the.AppBus.Publish(event.NewEvent(constants.ServerStatusUpdate, map[event.Field]string{}))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case event.Shutdown:
|
||||||
|
s.Shutdown()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListServers iterates through the current collection of servers and their associated
|
||||||
|
// cache and sends an update to the UI.
|
||||||
func (sm *ServerManager) ListServers(gcd *ui.GrandCentralDispatcher) {
|
func (sm *ServerManager) ListServers(gcd *ui.GrandCentralDispatcher) {
|
||||||
log.Debugf("Listing Servers...")
|
log.Debugf("Listing Servers...")
|
||||||
gcd.AddServer("Server 1","Server 1","server",0)
|
sm.servers.Range(func(k interface{},v interface{}) bool {
|
||||||
gcd.AddServer("Server 2","Server 2","server",4)
|
serverOnion := k.(string)
|
||||||
gcd.AddServer("Server 3","Server 3","server",4)
|
statusCache := v.(serverStatusCache)
|
||||||
}
|
status := 0
|
||||||
|
if statusCache.online {
|
||||||
|
status = 1
|
||||||
|
}
|
||||||
|
|
||||||
func (sm *ServerManager) StartServer(handle string) {
|
// TODO this doesn't allow for an expansion of key types, this whole flow needs rethinking...
|
||||||
// TODO Start the server with the given handle config
|
key_types := []model.KeyType{model.KeyTypeServerOnion, model.KeyTypeTokenOnion, model.KeyTypePrivacyPass}
|
||||||
}
|
var keyNames []string
|
||||||
|
var keys []string
|
||||||
|
|
||||||
func (sm *ServerManager) StopServer(handle string) {
|
keybundle,_ := model.DeserializeAndVerify(statusCache.bundle)
|
||||||
// TODO Stop the given server
|
|
||||||
|
for _, key_type := range key_types {
|
||||||
|
log.Debugf("Looking up %v %v", key_type, keyNames)
|
||||||
|
if keybundle.HasKeyType(key_type) {
|
||||||
|
key,_ := keybundle.GetKey(key_type)
|
||||||
|
keyNames = append(keyNames, string(key_type))
|
||||||
|
keys = append(keys, string(key))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Debugf("Updating Server %v %v %v", serverOnion, status, statusCache.messages)
|
||||||
|
gcd.AddServer(serverOnion,serverOnion,serverOnion, status, statusCache.autostart, "server:"+base64.StdEncoding.EncodeToString(statusCache.bundle), int(statusCache.messages), keyNames, keys)
|
||||||
|
return true
|
||||||
|
})
|
||||||
}
|
}
|
|
@ -131,8 +131,6 @@ func PeerHandler(onion string, uiManager ui.Manager, subscribed chan bool) {
|
||||||
}
|
}
|
||||||
uiManager.UpdateContactStatus(groupID, int(state), loading)
|
uiManager.UpdateContactStatus(groupID, int(state), loading)
|
||||||
uiManager.UpdateContactStatus(serverOnion, int(state), loading)
|
uiManager.UpdateContactStatus(serverOnion, int(state), loading)
|
||||||
} else {
|
|
||||||
log.Errorf("found group that is nil :/")
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
case event.DeletePeer:
|
case event.DeletePeer:
|
||||||
|
|
46
go/ui/gcd.go
46
go/ui/gcd.go
|
@ -57,8 +57,13 @@ type GrandCentralDispatcher struct {
|
||||||
_ func(failed bool) `signal:"ChangePasswordResponse"`
|
_ func(failed bool) `signal:"ChangePasswordResponse"`
|
||||||
|
|
||||||
// server management
|
// server management
|
||||||
_ func(handle, displayname, image string, status int) `signal:"AddServer"`
|
_ func(handle, displayname, image string, status int, autostart bool, bundle string, messages int, key_types []string, keys []string) `signal:"AddServer"`
|
||||||
_ func() `signal:"requestServers,auto"`
|
_ func() `signal:"requestServers,auto"`
|
||||||
|
_ func() `signal:"newServer,auto"`
|
||||||
|
_ func(server string) `signal:"startServer,auto"`
|
||||||
|
_ func(server string) `signal:"stopServer,auto"`
|
||||||
|
_ func(server string) `signal:"checkServer,auto"`
|
||||||
|
_ func(server string, enabled bool) `signal:"autostartServer",auto`
|
||||||
|
|
||||||
// contact list stuff
|
// contact list stuff
|
||||||
_ func(handle, displayName, image string, badge, status int, authorization string, loading bool, lastMsgTime int) `signal:"AddContact"`
|
_ func(handle, displayName, image string, badge, status int, authorization string, loading bool, lastMsgTime int) `signal:"AddContact"`
|
||||||
|
@ -388,6 +393,45 @@ func (this *GrandCentralDispatcher) requestServers() {
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (this *GrandCentralDispatcher) newServer() {
|
||||||
|
the.AppBus.Publish(event.NewEvent(constants.NewServer, map[event.Field]string{
|
||||||
|
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (this *GrandCentralDispatcher) startServer(onion string) {
|
||||||
|
log.Debugf("Requesting Start Server: %v", onion)
|
||||||
|
the.AppBus.Publish(event.NewEvent(constants.StartServer, map[event.Field]string{
|
||||||
|
event.Onion: onion,
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (this *GrandCentralDispatcher) stopServer(onion string) {
|
||||||
|
log.Debugf("Requesting Stop Server: %v", onion)
|
||||||
|
the.AppBus.Publish(event.NewEvent(constants.StopServer, map[event.Field]string{
|
||||||
|
event.Onion: onion,
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (this *GrandCentralDispatcher) checkServer(onion string) {
|
||||||
|
log.Debugf("Requesting Stop Server: %v", onion)
|
||||||
|
the.AppBus.Publish(event.NewEvent(constants.CheckServerStatus, map[event.Field]string{
|
||||||
|
event.Onion: onion,
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (this *GrandCentralDispatcher) autostartServer(onion string, enabled bool) {
|
||||||
|
log.Debugf("Requesting Autostart Toggle: %v %v", onion, enabled)
|
||||||
|
value := event.False
|
||||||
|
if enabled {
|
||||||
|
value = event.True
|
||||||
|
}
|
||||||
|
the.AppBus.Publish(event.NewEvent(constants.AutoStart, map[event.Field]string{
|
||||||
|
event.Onion: onion,
|
||||||
|
constants.AutoStartEnabled: value,
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
func (this *GrandCentralDispatcher) requestGroupSettings(groupID string) {
|
func (this *GrandCentralDispatcher) requestGroupSettings(groupID string) {
|
||||||
group := the.Peer.GetGroup(groupID)
|
group := the.Peer.GetGroup(groupID)
|
||||||
|
|
||||||
|
|
2
main.go
2
main.go
|
@ -323,7 +323,7 @@ func loadNetworkingAndFiles(gcd *ui.GrandCentralDispatcher, service bool, client
|
||||||
the.AppBus = the.CwtchApp.GetPrimaryBus()
|
the.AppBus = the.CwtchApp.GetPrimaryBus()
|
||||||
subscribed := make(chan bool)
|
subscribed := make(chan bool)
|
||||||
go handlers.App(gcd, subscribed, clientUI)
|
go handlers.App(gcd, subscribed, clientUI)
|
||||||
go servers.LaunchServiceManager(gcd)
|
go servers.LaunchServiceManager(gcd, the.ACN, path.Join(the.CwtchDir, "servers"))
|
||||||
<-subscribed
|
<-subscribed
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -8,9 +8,6 @@ import QtQuick.Window 2.11
|
||||||
|
|
||||||
import "../opaque" as Opaque
|
import "../opaque" as Opaque
|
||||||
import "../opaque/theme"
|
import "../opaque/theme"
|
||||||
// import "../styles"
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
Opaque.SettingsList { // Add Profile Pane
|
Opaque.SettingsList { // Add Profile Pane
|
||||||
id: serverAddEditPane
|
id: serverAddEditPane
|
||||||
|
@ -19,15 +16,22 @@ Opaque.SettingsList { // Add Profile Pane
|
||||||
function reset() {
|
function reset() {
|
||||||
serverAddEditPane.server_name = "";
|
serverAddEditPane.server_name = "";
|
||||||
serverAddEditPane.server_available = false;
|
serverAddEditPane.server_available = false;
|
||||||
|
serverAddEditPane.server_bundle = ""
|
||||||
}
|
}
|
||||||
|
|
||||||
property string server_name;
|
property string server_name;
|
||||||
property bool server_available;
|
property bool server_available;
|
||||||
|
property string server_bundle;
|
||||||
|
property bool autostart_server;
|
||||||
|
property int server_messages;
|
||||||
|
|
||||||
function load(server_onion, server_name, server_available) {
|
function load(server_onion, server_name, server_available, autostart_server, server_messages, server_bundle) {
|
||||||
reset();
|
reset();
|
||||||
serverAddEditPane.server_name = server_name;
|
serverAddEditPane.server_name = server_name;
|
||||||
serverAddEditPane.server_available = server_available;
|
serverAddEditPane.server_available = server_available;
|
||||||
|
serverAddEditPane.server_bundle = server_bundle;
|
||||||
|
serverAddEditPane.autostart_server = autostart_server;
|
||||||
|
serverAddEditPane.server_messages = server_messages;
|
||||||
}
|
}
|
||||||
|
|
||||||
settings: Column {
|
settings: Column {
|
||||||
|
@ -36,7 +40,7 @@ Opaque.SettingsList { // Add Profile Pane
|
||||||
|
|
||||||
Opaque.ScalingLabel {
|
Opaque.ScalingLabel {
|
||||||
text: server_name
|
text: server_name
|
||||||
size: 48
|
size: 16
|
||||||
}
|
}
|
||||||
|
|
||||||
Opaque.Setting {
|
Opaque.Setting {
|
||||||
|
@ -48,16 +52,64 @@ Opaque.SettingsList { // Add Profile Pane
|
||||||
|
|
||||||
isToggled: serverAddEditPane.server_available
|
isToggled: serverAddEditPane.server_available
|
||||||
onToggled: function() {
|
onToggled: function() {
|
||||||
serverAddEditPane.serverAddEditPane = !serverAddEditPane
|
serverAddEditPane.server_available = !serverAddEditPane.server_available
|
||||||
|
if (serverAddEditPane.server_available) {
|
||||||
|
gcd.startServer(serverAddEditPane.server_name)
|
||||||
|
} else {
|
||||||
|
gcd.stopServer(serverAddEditPane.server_name)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Opaque.Setting {
|
||||||
|
label: qsTr("server-autostart")
|
||||||
|
|
||||||
|
field: Opaque.ToggleSwitch {
|
||||||
|
anchors.right: parent.right
|
||||||
|
|
||||||
|
isToggled: serverAddEditPane.autostart_server
|
||||||
|
onToggled: function() {
|
||||||
|
serverAddEditPane.autostart_server = !serverAddEditPane.autostart_server
|
||||||
|
gcd.autostartServer(serverAddEditPane.server_name, serverAddEditPane.autostart_server)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Opaque.Setting {
|
||||||
|
inline: false
|
||||||
|
label: qsTr("server-num-messages")
|
||||||
|
|
||||||
|
field: Opaque.TextField {
|
||||||
|
id: numMessages
|
||||||
|
readOnly: true
|
||||||
|
text: serverAddEditPane.server_messages;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Opaque.Setting {
|
||||||
|
inline: false
|
||||||
|
label: qsTr("server-key-bundle")
|
||||||
|
|
||||||
|
field: Opaque.ButtonTextField {
|
||||||
|
id: txtServerBundle
|
||||||
|
readOnly: true
|
||||||
|
text: serverAddEditPane.server_bundle;
|
||||||
|
button_text: qsTr("copy-btn")
|
||||||
|
dropShadowColor: Theme.dropShadowPaneColor
|
||||||
|
onClicked: {
|
||||||
|
//: notification: copied to clipboard
|
||||||
|
gcd.popup(qsTr("copied-to-clipboard-notification"))
|
||||||
|
txtServerBundle.selectAll()
|
||||||
|
txtServerBundle.copy()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
Connections { // UPDATE UNREAD MESSAGES COUNTER
|
Connections {
|
||||||
target: gcd
|
target: gcd
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -40,11 +40,13 @@ ColumnLayout {
|
||||||
gcd.requestServers();
|
gcd.requestServers();
|
||||||
}
|
}
|
||||||
|
|
||||||
onAddServer: function(handle, displayName, image, status) {
|
onAddServer: function(handle, displayName, image, status, autostart, bundle, messages, key_types, keys) {
|
||||||
|
|
||||||
// don't add duplicates
|
// don't add duplicates
|
||||||
for (var i = 0; i < serversModel.count; i++) {
|
for (var i = 0; i < serversModel.count; i++) {
|
||||||
if (serversModel.get(i)["_handle"] == handle) {
|
if (serversModel.get(i)["_handle"] == handle) {
|
||||||
|
serversModel.get(i)["_status"] = status
|
||||||
|
serversModel.get(i)["_messages"] = messages
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -64,6 +66,9 @@ ColumnLayout {
|
||||||
_displayName: displayName,
|
_displayName: displayName,
|
||||||
_image: image,
|
_image: image,
|
||||||
_status: status,
|
_status: status,
|
||||||
|
_bundle: bundle,
|
||||||
|
_autostart: autostart,
|
||||||
|
_messages: messages
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -84,6 +89,9 @@ ColumnLayout {
|
||||||
displayName: _displayName
|
displayName: _displayName
|
||||||
image: _image
|
image: _image
|
||||||
status: _status
|
status: _status
|
||||||
|
bundle: _bundle
|
||||||
|
autostart: _autostart
|
||||||
|
messages: _messages
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -105,7 +113,7 @@ ColumnLayout {
|
||||||
}
|
}
|
||||||
badgeColor: Theme.defaultButtonColor
|
badgeColor: Theme.defaultButtonColor
|
||||||
|
|
||||||
onClicked: function(handle) { serverAddEditPane.reset(); parentStack.pane = parentStack.addEditServerPane }
|
onClicked: function(handle) { gcd.newServer() }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,13 +14,16 @@ import "../opaque/theme"
|
||||||
Opaque.PortraitRow {
|
Opaque.PortraitRow {
|
||||||
id: root
|
id: root
|
||||||
property int status;
|
property int status;
|
||||||
|
property string bundle;
|
||||||
|
property bool autostart;
|
||||||
|
property int messages;
|
||||||
|
|
||||||
portraitBorderColor: Theme.portraitOnlineBorderColor
|
portraitBorderColor: Theme.portraitOnlineBorderColor
|
||||||
portraitColor: Theme.portraitOnlineBackgroundColor
|
portraitColor: Theme.portraitOnlineBackgroundColor
|
||||||
nameColor: Theme.portraitOnlineTextColor
|
nameColor: Theme.portraitOnlineTextColor
|
||||||
onionColor: Theme.portraitOnlineTextColor
|
onionColor: Theme.portraitOnlineTextColor
|
||||||
|
|
||||||
badgeColor: status == 4 ? Theme.portraitOnlineBadgeColor : Theme.portraitOfflineBadgeColor
|
badgeColor: status == 1 ? Theme.portraitOnlineBadgeColor : Theme.portraitOfflineBadgeColor
|
||||||
badgeVisible: true
|
badgeVisible: true
|
||||||
|
|
||||||
Opaque.Icon {// Edit BUTTON
|
Opaque.Icon {// Edit BUTTON
|
||||||
|
@ -42,12 +45,14 @@ Opaque.PortraitRow {
|
||||||
size: parent.height * 0.5
|
size: parent.height * 0.5
|
||||||
|
|
||||||
onClicked: {
|
onClicked: {
|
||||||
serverAddEditPane.load(handle, displayName, status)
|
gcd.checkServer(handle)
|
||||||
|
serverAddEditPane.load(handle, displayName, status, autostart, messages, bundle)
|
||||||
parentStack.pane = parentStack.addEditServerPane
|
parentStack.pane = parentStack.addEditServerPane
|
||||||
}
|
}
|
||||||
|
|
||||||
onHover: function (hover) {
|
onHover: function (hover) {
|
||||||
root.isHover = hover
|
root.isHover = hover
|
||||||
|
gcd.checkServer(handle)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
Reference in New Issue