init
This commit is contained in:
commit
fb462f8233
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
||||
mist-connections
|
34
.goreleaser.yaml
Normal file
34
.goreleaser.yaml
Normal file
@ -0,0 +1,34 @@
|
||||
---
|
||||
|
||||
project_name: mist-connections
|
||||
|
||||
before:
|
||||
hooks:
|
||||
- go mod tidy
|
||||
- go generate ./...
|
||||
|
||||
builds:
|
||||
- env:
|
||||
- CGO_ENABLED=0
|
||||
goos:
|
||||
- linux
|
||||
- windows
|
||||
- darwin
|
||||
goarch:
|
||||
- 386
|
||||
- amd64
|
||||
- arm
|
||||
- arm64
|
||||
goarm:
|
||||
- 5
|
||||
- 6
|
||||
- 7
|
||||
|
||||
archives:
|
||||
- replacements:
|
||||
darwin: Darwin
|
||||
linux: Linux
|
||||
windows: Windows
|
||||
386: i386
|
||||
amd64: x86_64
|
||||
format: binary
|
40
README.md
Normal file
40
README.md
Normal file
@ -0,0 +1,40 @@
|
||||
# mist-connections
|
||||
|
||||
> ["Missed Connections"](https://en.m.wikipedia.org/wiki/Missed_connection)
|
||||
|
||||
[Moar](https://git.vvvvvvaria.org/decentral1se/unnamed-project) post-calibre prototypin' :tada:
|
||||
|
||||
## demo
|
||||
|
||||
[`msc.mp4`](https://vvvvvvaria.org/~decentral1se/msc/msc.mp4)
|
||||
|
||||
## install
|
||||
|
||||
Download the binary which matches your OS :point_right: [linux](https://git.vvvvvvaria.org/decentral1se/mist-connections/raw/branch/main/dist/mist-connections_linux_amd64/mist-connections) | [osx](https://git.vvvvvvaria.org/decentral1se/mist-connections/raw/branch/main/dist/mist-connections_darwin_amd64/mist-connections) | [windoze](https://git.vvvvvvaria.org/decentral1se/mist-connections/raw/branch/main/dist/mist-connections_windows_amd64/mist-connections.exe) | ["other"](./dist)
|
||||
|
||||
Then :point_right: `chmod +x mist-connections && ./mist-connections`
|
||||
|
||||
(PS. No Idea what you do on Windoze :shrug:)
|
||||
|
||||
## hack
|
||||
|
||||
`go install github.com/go-bindata/go-bindata/...`
|
||||
|
||||
`make`
|
||||
|
||||
Or, if you want to access the binary directly without make:
|
||||
|
||||
`go build .`
|
||||
|
||||
Then you have a freshly built `./mist-connections` ready.
|
||||
|
||||
## release
|
||||
|
||||
`go install github.com/goreleaser/goreleaser@latest`
|
||||
|
||||
`make release`
|
||||
|
||||
## ack
|
||||
|
||||
- [`@simoon`](https://git.vvvvvvaria.org/simoon)
|
||||
- [`unnamed project`](https://git.vvvvvvaria.org/decentral1se/unnamed-project)
|
1
assets/.gitignore
vendored
Normal file
1
assets/.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
||||
assets_gen.go
|
14
assets/assets.go
Normal file
14
assets/assets.go
Normal file
@ -0,0 +1,14 @@
|
||||
package assets
|
||||
|
||||
import (
|
||||
assetfs "github.com/elazarl/go-bindata-assetfs"
|
||||
)
|
||||
|
||||
func AssetFS() *assetfs.AssetFS {
|
||||
return &assetfs.AssetFS{
|
||||
Asset: Asset,
|
||||
AssetDir: AssetDir,
|
||||
AssetInfo: AssetInfo,
|
||||
Prefix: "public",
|
||||
}
|
||||
}
|
165
assets/static/style.css
Normal file
165
assets/static/style.css
Normal file
@ -0,0 +1,165 @@
|
||||
/* ---------- Fog ---------- */
|
||||
.fogwrapper {
|
||||
height: 100%;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
width: 100%;
|
||||
-webkit-filter: blur(1px) grayscale(0.2) saturate(1.2) sepia(0.2);
|
||||
filter: blur(1px) grayscale(0.2) saturate(1.2) sepia(0.2);
|
||||
}
|
||||
#foglayer_01, #foglayer_02, #foglayer_03 {
|
||||
height: 100%;
|
||||
position: absolute;
|
||||
width: 200%;
|
||||
}
|
||||
#foglayer_01 .image01, #foglayer_01 .image02,
|
||||
#foglayer_02 .image01, #foglayer_02 .image02,
|
||||
#foglayer_03 .image01, #foglayer_03 .image02 {
|
||||
float: left;
|
||||
height: 100%;
|
||||
width: 50%;
|
||||
}
|
||||
#foglayer_01 {
|
||||
-webkit-animation: foglayer_01_opacity 10s linear infinite, foglayer_moveme 15s linear infinite;
|
||||
-moz-animation: foglayer_01_opacity 10s linear infinite, foglayer_moveme 15s linear infinite;
|
||||
animation: foglayer_01_opacity 10s linear infinite, foglayer_moveme 15s linear infinite;
|
||||
}
|
||||
#foglayer_02, #foglayer_03 {
|
||||
-webkit-animation: foglayer_02_opacity 21s linear infinite, foglayer_moveme 13s linear infinite;
|
||||
-moz-animation: foglayer_02_opacity 21s linear infinite, foglayer_moveme 13s linear infinite;
|
||||
animation: foglayer_02_opacity 21s linear infinite, foglayer_moveme 13s linear infinite;
|
||||
}
|
||||
|
||||
/* ---------- Moving Fog ---------- */
|
||||
/*
|
||||
'size: cover' || 'size: 100%'; results remain the same
|
||||
'attachment: scroll' can be added or removed; results remain the same
|
||||
'attachment: fixed' causing unexpected results in Chrome
|
||||
'repeat-x' || 'no-repeat'; results remain the same
|
||||
*/
|
||||
#foglayer_01 .image01, #foglayer_01 .image02 {
|
||||
background: url("https://raw.githubusercontent.com/danielstuart14/CSS_FOG_ANIMATION/master/fog1.png") center center/cover no-repeat transparent;
|
||||
}
|
||||
#foglayer_02 .image01, #foglayer_02 .image02,
|
||||
#foglayer_03 .image01, #foglayer_03 .image02{
|
||||
background: url("https://raw.githubusercontent.com/danielstuart14/CSS_FOG_ANIMATION/master/fog2.png") center center/cover no-repeat transparent;
|
||||
}
|
||||
|
||||
/* ---------- Keyframe Layer 1 ---------- */
|
||||
@-webkit-keyframes foglayer_01_opacity {
|
||||
0% { opacity: .1; }
|
||||
22% { opacity: .5; }
|
||||
40% { opacity: .28; }
|
||||
58% { opacity: .4; }
|
||||
80% { opacity: .16; }
|
||||
100% { opacity: .1; }
|
||||
}
|
||||
@-moz-keyframes foglayer_01_opacity {
|
||||
0% { opacity: .1; }
|
||||
22% { opacity: .5; }
|
||||
40% { opacity: .28; }
|
||||
58% { opacity: .4; }
|
||||
80% { opacity: .16; }
|
||||
100% { opacity: .1; }
|
||||
}
|
||||
@-o-keyframes foglayer_01_opacity {
|
||||
0% { opacity: .1; }
|
||||
22% { opacity: .5; }
|
||||
40% { opacity: .28; }
|
||||
58% { opacity: .4; }
|
||||
80% { opacity: .16; }
|
||||
100% { opacity: .1; }
|
||||
}
|
||||
@keyframes foglayer_01_opacity {
|
||||
0% { opacity: .1; }
|
||||
22% { opacity: .5; }
|
||||
40% { opacity: .28; }
|
||||
58% { opacity: .4; }
|
||||
80% { opacity: .16; }
|
||||
100% { opacity: .1; }
|
||||
}
|
||||
/* ---------- Keyframe Layer 2 ---------- */
|
||||
@-webkit-keyframes foglayer_02_opacity {
|
||||
0% { opacity: .5; }
|
||||
25% { opacity: .2; }
|
||||
50% { opacity: .1; }
|
||||
80% { opacity: .3; }
|
||||
100% { opacity: .5; }
|
||||
}
|
||||
@-moz-keyframes foglayer_02_opacity {
|
||||
0% { opacity: .5; }
|
||||
25% { opacity: .2; }
|
||||
50% { opacity: .1; }
|
||||
80% { opacity: .3; }
|
||||
100% { opacity: .5; }
|
||||
}
|
||||
@-o-keyframes foglayer_02_opacity {
|
||||
0% { opacity: .5; }
|
||||
25% { opacity: .2; }
|
||||
50% { opacity: .1; }
|
||||
80% { opacity: .3; }
|
||||
100% { opacity: .5; }
|
||||
}
|
||||
@keyframes foglayer_02_opacity {
|
||||
0% { opacity: .5; }
|
||||
25% { opacity: .2; }
|
||||
50% { opacity: .1; }
|
||||
80% { opacity: .3; }
|
||||
100% { opacity: .5; }
|
||||
}
|
||||
/* ---------- Keyframe Layer 3 ---------- */
|
||||
@-webkit-keyframes foglayer_03_opacity {
|
||||
0% { opacity: .8 }
|
||||
27% { opacity: .2; }
|
||||
52% { opacity: .6; }
|
||||
68% { opacity: .3; }
|
||||
100% { opacity: .8; }
|
||||
}
|
||||
@-moz-keyframes foglayer_03_opacity {
|
||||
0% { opacity: .8 }
|
||||
27% { opacity: .2; }
|
||||
52% { opacity: .6; }
|
||||
68% { opacity: .3; }
|
||||
100% { opacity: .8; }
|
||||
}
|
||||
@-o-keyframes foglayer_03_opacity {
|
||||
0% { opacity: .8 }
|
||||
27% { opacity: .2; }
|
||||
52% { opacity: .6; }
|
||||
68% { opacity: .3; }
|
||||
100% { opacity: .8; }
|
||||
}
|
||||
@keyframes foglayer_03_opacity {
|
||||
0% { opacity: .8; }
|
||||
27% { opacity: .2; }
|
||||
52% { opacity: .6; }
|
||||
68% { opacity: .3; }
|
||||
100% { opacity: .8; }
|
||||
}
|
||||
/* ---------- Keyframe moveMe ---------- */
|
||||
@-webkit-keyframes foglayer_moveme {
|
||||
0% { left: 0; }
|
||||
100% { left: -100%; }
|
||||
}
|
||||
@-moz-keyframes foglayer_moveme {
|
||||
0% { left: 0; }
|
||||
100% { left: -100%; }
|
||||
}
|
||||
@-o-keyframes foglayer_moveme {
|
||||
0% { left: 0; }
|
||||
100% { left: -100%; }
|
||||
}
|
||||
@keyframes foglayer_moveme {
|
||||
0% { left: 0; }
|
||||
100% { left: -100%; }
|
||||
}
|
||||
|
||||
@media only screen
|
||||
and (min-width: 280px)
|
||||
and (max-width: 767px) {
|
||||
#foglayer_01 .image01, #foglayer_01 .image02,
|
||||
#foglayer_02 .image01, #foglayer_02 .image02,
|
||||
#foglayer_03 .image01, #foglayer_03 .image02 {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
10
go.mod
Normal file
10
go.mod
Normal file
@ -0,0 +1,10 @@
|
||||
module varia.zone/mist-connections
|
||||
|
||||
go 1.18
|
||||
|
||||
require (
|
||||
github.com/elazarl/go-bindata-assetfs v1.0.1
|
||||
github.com/gabriel-vasile/mimetype v1.4.0
|
||||
)
|
||||
|
||||
require golang.org/x/net v0.0.0-20210505024714-0287a6fb4125 // indirect
|
11
go.sum
Normal file
11
go.sum
Normal file
@ -0,0 +1,11 @@
|
||||
github.com/elazarl/go-bindata-assetfs v1.0.1 h1:m0kkaHRKEu7tUIUFVwhGGGYClXvyl4RE03qmvRTNfbw=
|
||||
github.com/elazarl/go-bindata-assetfs v1.0.1/go.mod h1:v+YaWX3bdea5J/mo8dSETolEo7R71Vk1u8bnjau5yw4=
|
||||
github.com/gabriel-vasile/mimetype v1.4.0 h1:Cn9dkdYsMIu56tGho+fqzh7XmvY2YyGU0FnbhiOsEro=
|
||||
github.com/gabriel-vasile/mimetype v1.4.0/go.mod h1:fA8fi6KUiG7MgQQ+mEWotXoEOvmxRtOJlERCzSmRvr8=
|
||||
golang.org/x/net v0.0.0-20210505024714-0287a6fb4125 h1:Ugb8sMTWuWRC3+sz5WeN/4kejDx9BvIwnPUiJBjJE+8=
|
||||
golang.org/x/net v0.0.0-20210505024714-0287a6fb4125/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
24
makefile
Normal file
24
makefile
Normal file
@ -0,0 +1,24 @@
|
||||
default: run
|
||||
|
||||
MAIN := mist-connections.go
|
||||
ASSETS := assets/assets_gen.go
|
||||
|
||||
generate:
|
||||
@rm -rf $(ASSETS) && go generate $(MAIN)
|
||||
|
||||
run: generate
|
||||
@go run $(MAIN)
|
||||
|
||||
bin: generate
|
||||
@go build .
|
||||
|
||||
release:
|
||||
@goreleaser release --snapshot --rm-dist
|
||||
|
||||
install:
|
||||
@go install .
|
||||
|
||||
build:
|
||||
@go build .
|
||||
|
||||
.PHONY: generate run bin release install build
|
343
mist-connections.go
Normal file
343
mist-connections.go
Normal file
@ -0,0 +1,343 @@
|
||||
package main
|
||||
|
||||
//go:generate go-bindata -o assets/assets_gen.go -pkg assets public/...
|
||||
|
||||
import (
|
||||
"container/list"
|
||||
"encoding/json"
|
||||
"flag"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"net"
|
||||
"net/http"
|
||||
"os"
|
||||
"os/user"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/gabriel-vasile/mimetype"
|
||||
"varia.zone/mist-connections/assets"
|
||||
)
|
||||
|
||||
func init() {
|
||||
// silence underlying http logger which complains about the awful hacks we
|
||||
// use to override the request/response handler. this keeps the command-line
|
||||
// free from endless spurious logger warnings
|
||||
log.SetOutput(ioutil.Discard)
|
||||
}
|
||||
|
||||
const help = `mist connections
|
||||
|
||||
A missed connection is a type of personal advertisement which arises after two
|
||||
people meet but are too shy or otherwise unable to exchange contact details.
|
||||
|
||||
Options:
|
||||
-n misty identifier (default: your system username)
|
||||
-p path to serve (default: current working directory)
|
||||
-h output help
|
||||
`
|
||||
|
||||
const indexPagePortFlag = 1312
|
||||
const homePagePortFlag = 5555
|
||||
|
||||
var helpFlag bool
|
||||
var mistyID string
|
||||
var sharePath string
|
||||
|
||||
func main() {
|
||||
systemUser, err := user.Current()
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
handleCliFlags(systemUser.Name)
|
||||
|
||||
sharePathAbs, err := filepath.Abs(sharePath)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
if helpFlag {
|
||||
fmt.Printf(help)
|
||||
os.Exit(0)
|
||||
}
|
||||
|
||||
conf := &config{
|
||||
MistyID: mistyID,
|
||||
IndexPagePort: indexPagePortFlag,
|
||||
HomePagePort: homePagePortFlag,
|
||||
SharePath: sharePathAbs,
|
||||
}
|
||||
|
||||
serve(conf)
|
||||
}
|
||||
|
||||
func handleCliFlags(defaultUsername string) {
|
||||
flag.StringVar(&mistyID, "n", defaultUsername, "misty identifier")
|
||||
flag.StringVar(&sharePath, "p", ".", "path to serve")
|
||||
flag.BoolVar(&helpFlag, "h", false, "output help")
|
||||
flag.Parse()
|
||||
}
|
||||
|
||||
type config struct {
|
||||
MistyID string `json:"mistyID"`
|
||||
IndexPagePort int `json:"webPort"`
|
||||
HomePagePort int `json:"filePort"`
|
||||
SharePath string `json:"sharePath"`
|
||||
}
|
||||
|
||||
type announcer struct {
|
||||
conf *config
|
||||
}
|
||||
|
||||
type nodeInfo struct {
|
||||
MistyID string `json:"mistyID"`
|
||||
Addr string `json:"addr"`
|
||||
IndexPagePort int `json:"webPort"`
|
||||
LastMulticast int64 `json:"lastMulticast"`
|
||||
}
|
||||
|
||||
var (
|
||||
nodeMutex sync.Mutex
|
||||
)
|
||||
|
||||
func newAnnouncePacket(n *nodeInfo) (string, error) {
|
||||
jsonMessage, err := json.Marshal(n)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
message := fmt.Sprintf("%s%s%s", header, nodeAnnounceCommand, jsonMessage)
|
||||
|
||||
return message, nil
|
||||
}
|
||||
|
||||
func announceNode(nodeInfo *nodeInfo) {
|
||||
address, err := net.ResolveUDPAddr("udp", multicastAddress)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
conn, err := net.DialUDP("udp", nil, address)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
for {
|
||||
message, err := newAnnouncePacket(nodeInfo)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
conn.Write([]byte(message))
|
||||
time.Sleep(announceIntervalSec * time.Second)
|
||||
}
|
||||
}
|
||||
|
||||
const (
|
||||
multicastAddress = "239.6.6.6:1337"
|
||||
multicastBufferSize = 4096
|
||||
nodeAnnounceCommand = "\x01"
|
||||
header = "\x60\x0D\xF0\x0D"
|
||||
minPackageSize = 6
|
||||
expireTimeoutSec = 15
|
||||
announceIntervalSec = 10
|
||||
)
|
||||
|
||||
func announcedNodeHandler(ninfo *nodeInfo, nodeList *list.List) {
|
||||
nodeMutex.Lock()
|
||||
updateNodeList(ninfo, nodeList)
|
||||
nodeMutex.Unlock()
|
||||
}
|
||||
|
||||
func updateNodeList(ninfo *nodeInfo, nodeList *list.List) {
|
||||
nodeExists := false
|
||||
for el := nodeList.Front(); el != nil; el = el.Next() {
|
||||
tmp := el.Value.(*nodeInfo)
|
||||
|
||||
if tmp.MistyID == ninfo.MistyID {
|
||||
tmp.LastMulticast = time.Now().Unix()
|
||||
nodeExists = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
for el := nodeList.Front(); el != nil; el = el.Next() {
|
||||
tmp := el.Value.(*nodeInfo)
|
||||
if isNodeExpired(tmp, expireTimeoutSec) {
|
||||
nodeList.Remove(el)
|
||||
}
|
||||
}
|
||||
|
||||
if !nodeExists {
|
||||
ninfo.LastMulticast = time.Now().Unix()
|
||||
nodeList.PushBack(ninfo)
|
||||
}
|
||||
}
|
||||
|
||||
func isNodeExpired(nodeInfo *nodeInfo, timeout int) bool {
|
||||
diff := time.Now().Unix() - nodeInfo.LastMulticast
|
||||
return diff > int64(timeout)
|
||||
}
|
||||
|
||||
func parseAnnouncePacket(size int, addr *net.UDPAddr, packet []byte) (*nodeInfo, error) {
|
||||
if size <= minPackageSize {
|
||||
return nil, fmt.Errorf("Invalid packet size")
|
||||
}
|
||||
|
||||
if strings.Compare(string(packet[0:len(header)]), header) != 0 {
|
||||
return nil, fmt.Errorf("Invalid packet header")
|
||||
}
|
||||
|
||||
if string(packet[len(header):len(header)+1]) != nodeAnnounceCommand[0:] {
|
||||
return nil, fmt.Errorf("Command different than nodeAnnounceCommand")
|
||||
}
|
||||
|
||||
payload := string(packet[len(header)+1:])
|
||||
payload = strings.Trim(payload, "\x00")
|
||||
|
||||
nodeInfo := &nodeInfo{}
|
||||
|
||||
err := json.Unmarshal([]byte(payload), nodeInfo)
|
||||
nodeInfo.Addr = addr.IP.String()
|
||||
nodeInfo.MistyID = fmt.Sprintf("%s", nodeInfo.MistyID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return nodeInfo, nil
|
||||
}
|
||||
|
||||
func listenForNodes(nodeList *list.List) {
|
||||
address, err := net.ResolveUDPAddr("udp", multicastAddress)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
conn, err := net.ListenMulticastUDP("udp", nil, address)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
conn.SetReadBuffer(multicastBufferSize)
|
||||
|
||||
for {
|
||||
packet := make([]byte, multicastBufferSize)
|
||||
size, udpAddr, err := conn.ReadFromUDP(packet)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
nodeInfo, err := parseAnnouncePacket(size, udpAddr, packet)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
go announcedNodeHandler(nodeInfo, nodeList)
|
||||
}
|
||||
}
|
||||
|
||||
func (a *announcer) Start(nodeList *list.List) {
|
||||
nodeInfo := &nodeInfo{
|
||||
MistyID: a.conf.MistyID,
|
||||
Addr: "",
|
||||
IndexPagePort: a.conf.IndexPagePort,
|
||||
LastMulticast: 0,
|
||||
}
|
||||
|
||||
go announceNode(nodeInfo)
|
||||
go listenForNodes(nodeList)
|
||||
}
|
||||
|
||||
func startAnnouncer(conf *config, nodeList *list.List) {
|
||||
announcer := &announcer{conf: conf}
|
||||
announcer.Start(nodeList)
|
||||
}
|
||||
|
||||
func serve(conf *config) {
|
||||
nodeList := list.New()
|
||||
|
||||
go startAnnouncer(conf, nodeList)
|
||||
go fileServe(conf)
|
||||
go dashboardServe(conf, nodeList)
|
||||
|
||||
fmt.Printf("getting misty @ http://localhost:%v :: %s\n", conf.HomePagePort, conf.SharePath)
|
||||
|
||||
select {} // hack to hang here forever until ctrl-c
|
||||
}
|
||||
|
||||
var topHTML = `
|
||||
<html>
|
||||
<head>
|
||||
<style>
|
||||
a { color: magenta; }
|
||||
</style>
|
||||
</head>
|
||||
<body>`
|
||||
|
||||
var bottomHTML = `
|
||||
<hr></hr>
|
||||
<a href="../">Back</a>
|
||||
<a href="http://localhost:5555">Home</a>
|
||||
</body>
|
||||
</html>`
|
||||
|
||||
func fileServe(conf *config) {
|
||||
fs := http.FileServer(http.Dir(conf.SharePath))
|
||||
|
||||
var handler http.HandlerFunc
|
||||
handler = func(w http.ResponseWriter, r *http.Request) {
|
||||
r.Header.Del("If-Modified-Since") // squash cache
|
||||
|
||||
fpath := filepath.Join(conf.SharePath, r.RequestURI)
|
||||
mtype, _ := mimetype.DetectFile(fpath)
|
||||
renderHTML := mtype.String() == "application/octet-stream"
|
||||
|
||||
if renderHTML {
|
||||
w.Write([]byte(topHTML))
|
||||
}
|
||||
|
||||
fs.ServeHTTP(w, r)
|
||||
|
||||
if renderHTML {
|
||||
w.Write([]byte(bottomHTML))
|
||||
}
|
||||
}
|
||||
|
||||
address := fmt.Sprintf("0.0.0.0:%v", conf.IndexPagePort)
|
||||
http.ListenAndServe(address, handler)
|
||||
}
|
||||
|
||||
func nodesHandler(nodeList *list.List) func(http.ResponseWriter, *http.Request) {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
nodes := make([]*nodeInfo, 0)
|
||||
for el := nodeList.Front(); el != nil; el = el.Next() {
|
||||
tmp := el.Value.(*nodeInfo)
|
||||
nodes = append(nodes, tmp)
|
||||
}
|
||||
|
||||
data, err := json.Marshal(nodes)
|
||||
if err != nil {
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
w.Header().Add("Content-Type", "application/json")
|
||||
w.Write(data)
|
||||
}
|
||||
}
|
||||
|
||||
func dashboardServe(conf *config, nodeList *list.List) {
|
||||
mux := http.NewServeMux()
|
||||
mux.Handle("/", http.FileServer(assets.AssetFS()))
|
||||
mux.HandleFunc("/api/nodes", nodesHandler(nodeList))
|
||||
|
||||
address := fmt.Sprintf("%s:%v", "0.0.0.0", conf.HomePagePort)
|
||||
err := http.ListenAndServe(address, mux)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
}
|
75
public/index.html
Normal file
75
public/index.html
Normal file
@ -0,0 +1,75 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
|
||||
<meta name="theme-color" content="#000000">
|
||||
<title>Mist Connections</title>
|
||||
<script src="https://unpkg.com/vue@3"></script>
|
||||
<script src="https://unpkg.com/axios@0.27.2"></script>
|
||||
<link rel="stylesheet" href="style.css">
|
||||
<style>
|
||||
ul li {
|
||||
list-style: none;
|
||||
}
|
||||
|
||||
.fade-enter-active,
|
||||
.fade-leave-active {
|
||||
transition: opacity 3.0s ease;
|
||||
}
|
||||
|
||||
.fade-enter-from,
|
||||
.fade-leave-to {
|
||||
opacity: 0;
|
||||
transform: translateX(30px);
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div id="app">
|
||||
<!-- <img class="fogwrapper" src="background.jpg"> -->
|
||||
<ul>
|
||||
<transition-group name="fade" tag="li">
|
||||
<li v-for="node in nodes" v-if="nodes.length">
|
||||
<a :href="'http://' + node.addr + ':1312'">{{ node.mistyID }}</a>
|
||||
</li>
|
||||
</transition-group>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="fog-background">
|
||||
<div id="foglayer_01" class="fog">
|
||||
<div class="image01"></div>
|
||||
<div class="image02"></div>
|
||||
</div>
|
||||
<div id="foglayer_02" class="fog">
|
||||
<div class="image01"></div>
|
||||
<div class="image02"></div>
|
||||
</div>
|
||||
<div id="foglayer_03" class="fog">
|
||||
<div class="image01"></div>
|
||||
<div class="image02"></div>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
<script>
|
||||
var app = Vue.createApp({
|
||||
data() {
|
||||
return {
|
||||
nodes: []
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
pollNodes() {
|
||||
axios
|
||||
.get('/api/nodes')
|
||||
.then(response => (this.nodes = response.data))
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.pollNodes()
|
||||
setInterval(() => { this.pollNodes(); }, 1000);
|
||||
},
|
||||
})
|
||||
app.mount("#app")
|
||||
</script>
|
||||
</html>
|
187
public/style.css
Normal file
187
public/style.css
Normal file
@ -0,0 +1,187 @@
|
||||
html, body {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
body {
|
||||
background: #000;
|
||||
background: rgba(200, 200, 200, 200);
|
||||
/* background: rgba(0, 0, 0, 1); */
|
||||
overflow-x: hidden;
|
||||
}
|
||||
div#app {
|
||||
z-index: 100;
|
||||
position: fixed;
|
||||
}
|
||||
|
||||
/* ---------- Fog ---------- */
|
||||
|
||||
.fog-background {
|
||||
/* z-index: -1000;
|
||||
position: sticky;*/
|
||||
}
|
||||
|
||||
.fogwrapper {
|
||||
height: 100%;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
width: 100%;
|
||||
-webkit-filter: blur(1px) grayscale(0.2) saturate(1.2) sepia(0.2);
|
||||
filter: blur(1px) grayscale(0.2) saturate(1.2) sepia(0.2);
|
||||
}
|
||||
#foglayer_01, #foglayer_02, #foglayer_03 {
|
||||
height: 100%;
|
||||
position: absolute;
|
||||
width: 200%;
|
||||
}
|
||||
#foglayer_01 .image01, #foglayer_01 .image02,
|
||||
#foglayer_02 .image01, #foglayer_02 .image02,
|
||||
#foglayer_03 .image01, #foglayer_03 .image02 {
|
||||
float: left;
|
||||
height: 100%;
|
||||
width: 50%;
|
||||
color: #0f0d2d8a;
|
||||
}
|
||||
#foglayer_01 {
|
||||
-webkit-animation: foglayer_01_opacity 10s linear infinite, foglayer_moveme 15s linear infinite;
|
||||
-moz-animation: foglayer_01_opacity 10s linear infinite, foglayer_moveme 15s linear infinite;
|
||||
animation: foglayer_01_opacity 10s linear infinite, foglayer_moveme 15s linear infinite;
|
||||
}
|
||||
#foglayer_02, #foglayer_03 {
|
||||
-webkit-animation: foglayer_02_opacity 21s linear infinite, foglayer_moveme 13s linear infinite;
|
||||
-moz-animation: foglayer_02_opacity 21s linear infinite, foglayer_moveme 13s linear infinite;
|
||||
animation: foglayer_02_opacity 21s linear infinite, foglayer_moveme 13s linear infinite;
|
||||
}
|
||||
|
||||
/* ---------- Moving Fog ---------- */
|
||||
/*
|
||||
'size: cover' || 'size: 100%'; results remain the same
|
||||
'attachment: scroll' can be added or removed; results remain the same
|
||||
'attachment: fixed' causing unexpected results in Chrome
|
||||
'repeat-x' || 'no-repeat'; results remain the same
|
||||
*/
|
||||
#foglayer_01 .image01, #foglayer_01 .image02 {
|
||||
background: url("https://raw.githubusercontent.com/danielstuart14/CSS_FOG_ANIMATION/master/fog1.png") center center/cover no-repeat transparent;
|
||||
}
|
||||
#foglayer_02 .image01, #foglayer_02 .image02,
|
||||
#foglayer_03 .image01, #foglayer_03 .image02{
|
||||
background: url("https://raw.githubusercontent.com/danielstuart14/CSS_FOG_ANIMATION/master/fog2.png") center center/cover no-repeat transparent;
|
||||
}
|
||||
|
||||
/* ---------- Keyframe Layer 1 ---------- */
|
||||
@-webkit-keyframes foglayer_01_opacity {
|
||||
0% { opacity: .1; }
|
||||
22% { opacity: .5; }
|
||||
40% { opacity: .28; }
|
||||
58% { opacity: .4; }
|
||||
80% { opacity: .16; }
|
||||
100% { opacity: .1; }
|
||||
}
|
||||
@-moz-keyframes foglayer_01_opacity {
|
||||
0% { opacity: .1; }
|
||||
22% { opacity: .5; }
|
||||
40% { opacity: .28; }
|
||||
58% { opacity: .4; }
|
||||
80% { opacity: .16; }
|
||||
100% { opacity: .1; }
|
||||
}
|
||||
@-o-keyframes foglayer_01_opacity {
|
||||
0% { opacity: .1; }
|
||||
22% { opacity: .5; }
|
||||
40% { opacity: .28; }
|
||||
58% { opacity: .4; }
|
||||
80% { opacity: .16; }
|
||||
100% { opacity: .1; }
|
||||
}
|
||||
@keyframes foglayer_01_opacity {
|
||||
0% { opacity: .1; }
|
||||
22% { opacity: .5; }
|
||||
40% { opacity: .28; }
|
||||
58% { opacity: .4; }
|
||||
80% { opacity: .16; }
|
||||
100% { opacity: .1; }
|
||||
}
|
||||
/* ---------- Keyframe Layer 2 ---------- */
|
||||
@-webkit-keyframes foglayer_02_opacity {
|
||||
0% { opacity: .5; }
|
||||
25% { opacity: .2; }
|
||||
50% { opacity: .1; }
|
||||
80% { opacity: .3; }
|
||||
100% { opacity: .5; }
|
||||
}
|
||||
@-moz-keyframes foglayer_02_opacity {
|
||||
0% { opacity: .5; }
|
||||
25% { opacity: .2; }
|
||||
50% { opacity: .1; }
|
||||
80% { opacity: .3; }
|
||||
100% { opacity: .5; }
|
||||
}
|
||||
@-o-keyframes foglayer_02_opacity {
|
||||
0% { opacity: .5; }
|
||||
25% { opacity: .2; }
|
||||
50% { opacity: .1; }
|
||||
80% { opacity: .3; }
|
||||
100% { opacity: .5; }
|
||||
}
|
||||
@keyframes foglayer_02_opacity {
|
||||
0% { opacity: .5; }
|
||||
25% { opacity: .2; }
|
||||
50% { opacity: .1; }
|
||||
80% { opacity: .3; }
|
||||
100% { opacity: .5; }
|
||||
}
|
||||
/* ---------- Keyframe Layer 3 ---------- */
|
||||
@-webkit-keyframes foglayer_03_opacity {
|
||||
0% { opacity: .8 }
|
||||
27% { opacity: .2; }
|
||||
52% { opacity: .6; }
|
||||
68% { opacity: .3; }
|
||||
100% { opacity: .8; }
|
||||
}
|
||||
@-moz-keyframes foglayer_03_opacity {
|
||||
0% { opacity: .8 }
|
||||
27% { opacity: .2; }
|
||||
52% { opacity: .6; }
|
||||
68% { opacity: .3; }
|
||||
100% { opacity: .8; }
|
||||
}
|
||||
@-o-keyframes foglayer_03_opacity {
|
||||
0% { opacity: .8 }
|
||||
27% { opacity: .2; }
|
||||
52% { opacity: .6; }
|
||||
68% { opacity: .3; }
|
||||
100% { opacity: .8; }
|
||||
}
|
||||
@keyframes foglayer_03_opacity {
|
||||
0% { opacity: .8; }
|
||||
27% { opacity: .2; }
|
||||
52% { opacity: .6; }
|
||||
68% { opacity: .3; }
|
||||
100% { opacity: .8; }
|
||||
}
|
||||
/* ---------- Keyframe moveMe ---------- */
|
||||
@-webkit-keyframes foglayer_moveme {
|
||||
0% { left: 0; }
|
||||
100% { left: -100%; }
|
||||
}
|
||||
@-moz-keyframes foglayer_moveme {
|
||||
0% { left: 0; }
|
||||
100% { left: -100%; }
|
||||
}
|
||||
@-o-keyframes foglayer_moveme {
|
||||
0% { left: 0; }
|
||||
100% { left: -100%; }
|
||||
}
|
||||
@keyframes foglayer_moveme {
|
||||
0% { left: 0; }
|
||||
100% { left: -100%; }
|
||||
}
|
||||
|
||||
@media only screen
|
||||
and (min-width: 280px)
|
||||
and (max-width: 767px) {
|
||||
#foglayer_01 .image01, #foglayer_01 .image02,
|
||||
#foglayer_02 .image01, #foglayer_02 .image02,
|
||||
#foglayer_03 .image01, #foglayer_03 .image02 {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user