switch from websockets to sse
This commit is contained in:
		
							parent
							
								
									67335c97b4
								
							
						
					
					
						commit
						534077bad3
					
				
					 13 changed files with 384 additions and 233 deletions
				
			
		|  | @ -1,43 +0,0 @@ | |||
| name: Run tests | ||||
| 
 | ||||
| on: | ||||
|   push: | ||||
|   pull_request: | ||||
|   workflow_dispatch: | ||||
| 
 | ||||
| jobs: | ||||
|   nix: | ||||
|     runs-on: docker | ||||
|     container: | ||||
|       image: nix | ||||
|     steps: | ||||
|       - name: Checkout | ||||
|         uses: actions/checkout@v4 | ||||
|       - name: Nix doctor | ||||
|         run: nix doctor | ||||
|       - name: Check flake | ||||
|         run: nix flake check --all-systems | ||||
|       - name: Build flake | ||||
|         run: nix build --print-out-paths | ||||
|       - name: Flake info | ||||
|         run: nix flake info | ||||
|   tests: | ||||
|     runs-on: docker | ||||
|     container: | ||||
|       image: debian:stable | ||||
|     steps: | ||||
|       - name: Checkout | ||||
|         uses: actions/checkout@v4 | ||||
|       - name: Setup Go | ||||
|         uses: actions/setup-go@v4 | ||||
|         with: | ||||
|           #go-version: "stable" | ||||
|           go-version-file: "go.mod" | ||||
|           check-latest: true | ||||
|       - name: Run Linter | ||||
|         uses: dominikh/staticcheck-action@v1 | ||||
|         with: | ||||
|           version: "latest" | ||||
|           install-go: false | ||||
|       - name: Run tests | ||||
|         run: make test | ||||
							
								
								
									
										73
									
								
								.gitlab-ci.yml
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										73
									
								
								.gitlab-ci.yml
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,73 @@ | |||
| include: | ||||
|   - template: Security/Dependency-Scanning.gitlab-ci.yml | ||||
|   - template: Security/SAST.gitlab-ci.yml | ||||
|   - template: Security/Secret-Detection.gitlab-ci.yml | ||||
|   - template: Security/Container-Scanning.gitlab-ci.yml | ||||
| 
 | ||||
| variables: | ||||
|   # Use TLS https://docs.gitlab.com/ee/ci/docker/using_docker_build.html#tls-enabled | ||||
|   DOCKER_HOST: tcp://docker:2376 | ||||
|   DOCKER_TLS_CERTDIR: "/certs" | ||||
|   CONTAINER_TEST_IMAGE: $CI_REGISTRY_IMAGE:$CI_COMMIT_REF_SLUG | ||||
|   CONTAINER_RELEASE_IMAGE: $CI_REGISTRY_IMAGE:latest | ||||
| 
 | ||||
| container_scanning: | ||||
|   variables: | ||||
|     CS_DEFAULT_BRANCH_IMAGE: $CI_REGISTRY_IMAGE/$CI_DEFAULT_BRANCH:$CI_COMMIT_SHA | ||||
| 
 | ||||
| image: golang:latest | ||||
| 
 | ||||
| stages: | ||||
|   - test | ||||
|   - build | ||||
|   - release | ||||
| 
 | ||||
| format: | ||||
|   stage: test | ||||
|   script: | ||||
|     - go fmt $(go list ./...) | ||||
| 
 | ||||
| vet: | ||||
|   stage: test | ||||
|   script: | ||||
|     - go vet $(go list ./...) | ||||
| 
 | ||||
| test: | ||||
|   stage: test | ||||
|   script: | ||||
|     - go test -race $(go list ./...) | ||||
| 
 | ||||
| sast: | ||||
|   stage: test | ||||
| 
 | ||||
| compile: | ||||
|   stage: build | ||||
|   script: | ||||
|     - mkdir -p bin | ||||
|     - go build -v -o bin ./... | ||||
|   artifacts: | ||||
|     paths: | ||||
|       - bin | ||||
| 
 | ||||
| build: | ||||
|   stage: build | ||||
|   image: docker:stable | ||||
|   services: | ||||
|     - docker:stable-dind | ||||
|   script: | ||||
|     - docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY | ||||
|     - docker build --pull -t $CONTAINER_TEST_IMAGE . | ||||
|     - docker push $CONTAINER_TEST_IMAGE | ||||
| 
 | ||||
| release: | ||||
|   stage: release | ||||
|   image: docker:stable | ||||
|   services: | ||||
|     - docker:stable-dind | ||||
|   script: | ||||
|     - docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY | ||||
|     - docker pull $CONTAINER_TEST_IMAGE | ||||
|     - docker tag $CONTAINER_TEST_IMAGE $CONTAINER_RELEASE_IMAGE | ||||
|     - docker push $CONTAINER_RELEASE_IMAGE | ||||
|   only: | ||||
|     - main | ||||
							
								
								
									
										3
									
								
								Makefile
									
										
									
									
									
								
							
							
						
						
									
										3
									
								
								Makefile
									
										
									
									
									
								
							|  | @ -6,6 +6,7 @@ PROJECT := sanic | |||
| 
 | ||||
| mpd:  ## Run mpd test instance
 | ||||
| 	mkdir -p /tmp/${PROJECT}/{music,playlists} | ||||
| 	cp *.mp3 /tmp/${PROJECT}/music/ | ||||
| 	touch /tmp/${PROJECT}/mpd_db | ||||
| 	mpd --no-daemon ./mpd.conf | ||||
| 
 | ||||
|  | @ -17,7 +18,7 @@ build:  ## Compile project | |||
| 
 | ||||
| update:  ## Update go dependencies
 | ||||
| 	go get -u | ||||
| 	which gomod2nix && gomod2nix | ||||
| 	which gomod2nix && gomod2nix  # sync go deps with nix | ||||
| 
 | ||||
| tidy:  ## Add missing and remove unused modules
 | ||||
| 	go mod tidy | ||||
|  |  | |||
							
								
								
									
										12
									
								
								README.md
									
										
									
									
									
								
							
							
						
						
									
										12
									
								
								README.md
									
										
									
									
									
								
							|  | @ -28,7 +28,7 @@ Example flake setup (untested): | |||
|   inputs = { | ||||
|     nixpkgs.url = "github:NixOS/nixpkgs/nixpkgs-unstable"; | ||||
|     sanic = { | ||||
|       url = "git.berlin.ccc.de/cccb/sanic"; | ||||
|       url = "gitlab.com/XenGi/sanic"; | ||||
|       inputs.nixpkgs.follows = "nixpkgs"; | ||||
|     }; | ||||
|   }; | ||||
|  | @ -52,10 +52,18 @@ Example flake setup (untested): | |||
| 
 | ||||
| Install from the AUR: | ||||
| 
 | ||||
| ``` | ||||
| ```shell | ||||
| yay -S sanic | ||||
| ``` | ||||
| 
 | ||||
| ### Podman | ||||
| 
 | ||||
| Run as daemon: | ||||
| 
 | ||||
| ```shell | ||||
| podman run -d -v ./config.ini:/config.ini -p 8443:8443 registry.gitlab.com/XenGi/sanic:latest | ||||
| ``` | ||||
| 
 | ||||
| ## 🛠️ Development | ||||
| 
 | ||||
| sanic is developed using [Nix][nix], but you can also just use the usual Golang tooling. | ||||
|  |  | |||
|  | @ -6,7 +6,7 @@ port = 6600 | |||
| 
 | ||||
| [ui] | ||||
| hostname = [::1] | ||||
| port = 8080 | ||||
| tls = no | ||||
| port = 8443 | ||||
| tls = yes | ||||
| cert = cert.pem | ||||
| key = key.pem | ||||
|  |  | |||
							
								
								
									
										12
									
								
								flake.lock
									
										
									
										generated
									
									
									
								
							
							
						
						
									
										12
									
								
								flake.lock
									
										
									
										generated
									
									
									
								
							|  | @ -28,11 +28,11 @@ | |||
|         ] | ||||
|       }, | ||||
|       "locked": { | ||||
|         "lastModified": 1717050755, | ||||
|         "narHash": "sha256-C9IEHABulv2zEDFA+Bf0E1nmfN4y6MIUe5eM2RCrDC0=", | ||||
|         "lastModified": 1722589758, | ||||
|         "narHash": "sha256-sbbA8b6Q2vB/t/r1znHawoXLysCyD4L/6n6/RykiSnA=", | ||||
|         "owner": "tweag", | ||||
|         "repo": "gomod2nix", | ||||
|         "rev": "31b6d2e40b36456e792cd6cf50d5a8ddd2fa59a1", | ||||
|         "rev": "4e08ca09253ef996bd4c03afa383b23e35fe28a1", | ||||
|         "type": "github" | ||||
|       }, | ||||
|       "original": { | ||||
|  | @ -43,11 +43,11 @@ | |||
|     }, | ||||
|     "nixpkgs": { | ||||
|       "locked": { | ||||
|         "lastModified": 1720955038, | ||||
|         "narHash": "sha256-GaliJqfFwyYxReFywxAa8orCO+EnDq2NK2F+5aSc8vo=", | ||||
|         "lastModified": 1722957468, | ||||
|         "narHash": "sha256-SQ0TCC4aklOhN/OzcztrKqDLY8SjpIZcyvTulzhDXs0=", | ||||
|         "owner": "NixOS", | ||||
|         "repo": "nixpkgs", | ||||
|         "rev": "aa247c0c90ecf4ae7a032c54fdc21b91ca274062", | ||||
|         "rev": "2a13929e1f191b3690dd2f2db13098b04adb9043", | ||||
|         "type": "github" | ||||
|       }, | ||||
|       "original": { | ||||
|  |  | |||
							
								
								
									
										21
									
								
								go.mod
									
										
									
									
									
								
							
							
						
						
									
										21
									
								
								go.mod
									
										
									
									
									
								
							|  | @ -4,9 +4,9 @@ go 1.22 | |||
| 
 | ||||
| require ( | ||||
| 	github.com/fhs/gompd/v2 v2.3.0 | ||||
| 	github.com/labstack/echo-contrib v0.17.0 | ||||
| 	github.com/labstack/echo-contrib v0.17.1 | ||||
| 	github.com/labstack/echo/v4 v4.12.0 | ||||
| 	golang.org/x/net v0.24.0 | ||||
| 	golang.org/x/net v0.28.0 | ||||
| 	gopkg.in/ini.v1 v1.67.0 | ||||
| ) | ||||
| 
 | ||||
|  | @ -17,15 +17,16 @@ require ( | |||
| 	github.com/labstack/gommon v0.4.2 // indirect | ||||
| 	github.com/mattn/go-colorable v0.1.13 // indirect | ||||
| 	github.com/mattn/go-isatty v0.0.20 // indirect | ||||
| 	github.com/prometheus/client_golang v1.19.0 // indirect | ||||
| 	github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect | ||||
| 	github.com/prometheus/client_golang v1.19.1 // indirect | ||||
| 	github.com/prometheus/client_model v0.6.1 // indirect | ||||
| 	github.com/prometheus/common v0.52.3 // indirect | ||||
| 	github.com/prometheus/procfs v0.13.0 // indirect | ||||
| 	github.com/prometheus/common v0.55.0 // indirect | ||||
| 	github.com/prometheus/procfs v0.15.1 // indirect | ||||
| 	github.com/valyala/bytebufferpool v1.0.0 // indirect | ||||
| 	github.com/valyala/fasttemplate v1.2.2 // indirect | ||||
| 	golang.org/x/crypto v0.22.0 // indirect | ||||
| 	golang.org/x/sys v0.19.0 // indirect | ||||
| 	golang.org/x/text v0.14.0 // indirect | ||||
| 	golang.org/x/time v0.5.0 // indirect | ||||
| 	google.golang.org/protobuf v1.33.0 // indirect | ||||
| 	golang.org/x/crypto v0.26.0 // indirect | ||||
| 	golang.org/x/sys v0.23.0 // indirect | ||||
| 	golang.org/x/text v0.17.0 // indirect | ||||
| 	golang.org/x/time v0.6.0 // indirect | ||||
| 	google.golang.org/protobuf v1.34.2 // indirect | ||||
| ) | ||||
|  |  | |||
							
								
								
									
										22
									
								
								go.sum
									
										
									
									
									
								
							
							
						
						
									
										22
									
								
								go.sum
									
										
									
									
									
								
							|  | @ -12,6 +12,8 @@ github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= | |||
| github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= | ||||
| github.com/labstack/echo-contrib v0.17.0 h1:xam8wakZOsiQYM14Z0og1xF3w/heWNeDF5AtC5PlX8E= | ||||
| github.com/labstack/echo-contrib v0.17.0/go.mod h1:mjX5VB3OqJcroIEycptBOY9Hr7rK+unq79W8QFKGNV0= | ||||
| github.com/labstack/echo-contrib v0.17.1 h1:7I/he7ylVKsDUieaGRZ9XxxTYOjfQwVzHzUYrNykfCU= | ||||
| github.com/labstack/echo-contrib v0.17.1/go.mod h1:SnsCZtwHBAZm5uBSAtQtXQHI3wqEA73hvTn0bYMKnZA= | ||||
| github.com/labstack/echo/v4 v4.12.0 h1:IKpw49IMryVB2p1a4dzwlhP1O2Tf2E0Ir/450lH+kI0= | ||||
| github.com/labstack/echo/v4 v4.12.0/go.mod h1:UP9Cr2DJXbOK3Kr9ONYzNowSh7HP0aG0ShAyycHSJvM= | ||||
| github.com/labstack/gommon v0.4.2 h1:F8qTUNXgG1+6WQmqoUWnz8WiEU60mXVVw0P4ht1WRA0= | ||||
|  | @ -21,16 +23,24 @@ github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovk | |||
| github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= | ||||
| github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= | ||||
| github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= | ||||
| github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= | ||||
| github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= | ||||
| github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= | ||||
| github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= | ||||
| github.com/prometheus/client_golang v1.19.0 h1:ygXvpU1AoN1MhdzckN+PyD9QJOSD4x7kmXYlnfbA6JU= | ||||
| github.com/prometheus/client_golang v1.19.0/go.mod h1:ZRM9uEAypZakd+q/x7+gmsvXdURP+DABIEIjnmDdp+k= | ||||
| github.com/prometheus/client_golang v1.19.1 h1:wZWJDwK+NameRJuPGDhlnFgx8e8HN3XHQeLaYJFJBOE= | ||||
| github.com/prometheus/client_golang v1.19.1/go.mod h1:mP78NwGzrVks5S2H6ab8+ZZGJLZUq1hoULYBAYBw1Ho= | ||||
| github.com/prometheus/client_model v0.6.1 h1:ZKSh/rekM+n3CeS952MLRAdFwIKqeY8b62p8ais2e9E= | ||||
| github.com/prometheus/client_model v0.6.1/go.mod h1:OrxVMOVHjw3lKMa8+x6HeMGkHMQyHDk9E3jmP2AmGiY= | ||||
| github.com/prometheus/common v0.52.3 h1:5f8uj6ZwHSscOGNdIQg6OiZv/ybiK2CO2q2drVZAQSA= | ||||
| github.com/prometheus/common v0.52.3/go.mod h1:BrxBKv3FWBIGXw89Mg1AeBq7FSyRzXWI3l3e7W3RN5U= | ||||
| github.com/prometheus/common v0.55.0 h1:KEi6DK7lXW/m7Ig5i47x0vRzuBsHuvJdi5ee6Y3G1dc= | ||||
| github.com/prometheus/common v0.55.0/go.mod h1:2SECS4xJG1kd8XF9IcM1gMX6510RAEL65zxzNImwdc8= | ||||
| github.com/prometheus/procfs v0.13.0 h1:GqzLlQyfsPbaEHaQkO7tbDlriv/4o5Hudv6OXHGKX7o= | ||||
| github.com/prometheus/procfs v0.13.0/go.mod h1:cd4PFCR54QLnGKPaKGA6l+cfuNXtht43ZKY6tow0Y1g= | ||||
| github.com/prometheus/procfs v0.15.1 h1:YagwOFzUgYfKKHX6Dr+sHT7km/hxC76UB0learggepc= | ||||
| github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoGhij/e3PBqk= | ||||
| github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= | ||||
| github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= | ||||
| github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= | ||||
|  | @ -39,18 +49,30 @@ github.com/valyala/fasttemplate v1.2.2 h1:lxLXG0uE3Qnshl9QyaK6XJxMXlQZELvChBOCmQ | |||
| github.com/valyala/fasttemplate v1.2.2/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ= | ||||
| golang.org/x/crypto v0.22.0 h1:g1v0xeRhjcugydODzvb3mEM9SQ0HGp9s/nh3COQ/C30= | ||||
| golang.org/x/crypto v0.22.0/go.mod h1:vr6Su+7cTlO45qkww3VDJlzDn0ctJvRgYbC2NvXHt+M= | ||||
| golang.org/x/crypto v0.26.0 h1:RrRspgV4mU+YwB4FYnuBoKsUapNIL5cohGAmSH3azsw= | ||||
| golang.org/x/crypto v0.26.0/go.mod h1:GY7jblb9wI+FOo5y8/S2oY4zWP07AkOJ4+jxCqdqn54= | ||||
| golang.org/x/net v0.24.0 h1:1PcaxkF854Fu3+lvBIx5SYn9wRlBzzcnHZSiaFFAb0w= | ||||
| golang.org/x/net v0.24.0/go.mod h1:2Q7sJY5mzlzWjKtYUEXSlBWCdyaioyXzRB2RtU8KVE8= | ||||
| golang.org/x/net v0.28.0 h1:a9JDOJc5GMUJ0+UDqmLT86WiEy7iWyIhz8gz8E4e5hE= | ||||
| golang.org/x/net v0.28.0/go.mod h1:yqtgsTWOOnlGLG9GFRrK3++bGOUEkNBoHZc8MEDWPNg= | ||||
| golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= | ||||
| golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= | ||||
| golang.org/x/sys v0.19.0 h1:q5f1RH2jigJ1MoAWp2KTp3gm5zAGFUTarQZ5U386+4o= | ||||
| golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= | ||||
| golang.org/x/sys v0.23.0 h1:YfKFowiIMvtgl1UERQoTPPToxltDeZfbj4H7dVUCwmM= | ||||
| golang.org/x/sys v0.23.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= | ||||
| golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= | ||||
| golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= | ||||
| golang.org/x/text v0.17.0 h1:XtiM5bkSOt+ewxlOE/aE/AKEHibwj/6gvWMl9Rsh0Qc= | ||||
| golang.org/x/text v0.17.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= | ||||
| golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk= | ||||
| golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= | ||||
| golang.org/x/time v0.6.0 h1:eTDhh4ZXt5Qf0augr54TN6suAUudPcawVZeIAPU7D4U= | ||||
| golang.org/x/time v0.6.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= | ||||
| google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI= | ||||
| google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= | ||||
| google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg= | ||||
| google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw= | ||||
| gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA= | ||||
| gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= | ||||
| gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= | ||||
|  |  | |||
|  | @ -14,8 +14,8 @@ schema = 3 | |||
|     version = "v3.2.2+incompatible" | ||||
|     hash = "sha256-LOkpuXhWrFayvVf1GOaOmZI5YKEsgqVSb22aF8LnCEM=" | ||||
|   [mod."github.com/labstack/echo-contrib"] | ||||
|     version = "v0.17.0" | ||||
|     hash = "sha256-J5S8vO8Zg9uV9A3zbILswn/oPdwLKRXncsXdEjDOmU8=" | ||||
|     version = "v0.17.1" | ||||
|     hash = "sha256-OkO1gWz1xL8Rw2wy4RMJjsLGhABAqAsjpVkQ3b6G+L4=" | ||||
|   [mod."github.com/labstack/echo/v4"] | ||||
|     version = "v4.12.0" | ||||
|     hash = "sha256-TPXJv/6C53bnmcEYxa9g5Mft8u/rLT96q64tQ9+RtKU=" | ||||
|  | @ -28,18 +28,21 @@ schema = 3 | |||
|   [mod."github.com/mattn/go-isatty"] | ||||
|     version = "v0.0.20" | ||||
|     hash = "sha256-qhw9hWtU5wnyFyuMbKx+7RB8ckQaFQ8D+8GKPkN3HHQ=" | ||||
|   [mod."github.com/munnerz/goautoneg"] | ||||
|     version = "v0.0.0-20191010083416-a7dc8b61c822" | ||||
|     hash = "sha256-79URDDFenmGc9JZu+5AXHToMrtTREHb3BC84b/gym9Q=" | ||||
|   [mod."github.com/prometheus/client_golang"] | ||||
|     version = "v1.19.0" | ||||
|     hash = "sha256-YV8sxMPR+xorTUCriTfcFsaV2b7PZfPJDQmOgUYOZJo=" | ||||
|     version = "v1.19.1" | ||||
|     hash = "sha256-MSLsMDi89uQc7Pa2fhqeamyfPJpenGj3r+eB/UotK7w=" | ||||
|   [mod."github.com/prometheus/client_model"] | ||||
|     version = "v0.6.1" | ||||
|     hash = "sha256-rIDyUzNfxRA934PIoySR0EhuBbZVRK/25Jlc/r8WODw=" | ||||
|   [mod."github.com/prometheus/common"] | ||||
|     version = "v0.52.3" | ||||
|     hash = "sha256-JzNAt7pimXZMnPxv8droAHJq+5OKWi9BYkGtMvqpyd0=" | ||||
|     version = "v0.55.0" | ||||
|     hash = "sha256-qzvCnc+hnAB5dq2MYy8GlPxgyNnyn9kFVlN2CXZe9T0=" | ||||
|   [mod."github.com/prometheus/procfs"] | ||||
|     version = "v0.13.0" | ||||
|     hash = "sha256-J31K36TkIiQU2EGOcmqDa+dkoKXiVuxafPVT4rKbEsg=" | ||||
|     version = "v0.15.1" | ||||
|     hash = "sha256-H+WXJemFFwdoglmD6p7JRjrJJZmIVAmJwYmLbZ8Q9sw=" | ||||
|   [mod."github.com/valyala/bytebufferpool"] | ||||
|     version = "v1.0.0" | ||||
|     hash = "sha256-I9FPZ3kCNRB+o0dpMwBnwZ35Fj9+ThvITn8a3Jr8mAY=" | ||||
|  | @ -47,23 +50,23 @@ schema = 3 | |||
|     version = "v1.2.2" | ||||
|     hash = "sha256-gp+lNXE8zjO+qJDM/YbS6V43HFsYP6PKn4ux1qa5lZ0=" | ||||
|   [mod."golang.org/x/crypto"] | ||||
|     version = "v0.22.0" | ||||
|     hash = "sha256-2+u9nd32+Bi7EEv7QFc12CRTbfV7DApNv+yKIr7+lTw=" | ||||
|     version = "v0.26.0" | ||||
|     hash = "sha256-Iicrsb65fCmjfPILKoSLyBZMwe2VUcoTF5SpYTCQEuk=" | ||||
|   [mod."golang.org/x/net"] | ||||
|     version = "v0.24.0" | ||||
|     hash = "sha256-w1c21ljta5wNIyel9CSIn/crPzwOCRofNKhqmfs4aEQ=" | ||||
|     version = "v0.28.0" | ||||
|     hash = "sha256-WdH/mgsX/CB+CiYtXEwJAXHN8FgtW2YhFcWwrrHNBLo=" | ||||
|   [mod."golang.org/x/sys"] | ||||
|     version = "v0.19.0" | ||||
|     hash = "sha256-cmuL31TYLJmDm/fDnI2Sn0wB88cpdOHV1+urorsJWx4=" | ||||
|     version = "v0.23.0" | ||||
|     hash = "sha256-tC6QVLu72bADgINz26FUGdmYqKgsU45bHPg7sa0ZV7w=" | ||||
|   [mod."golang.org/x/text"] | ||||
|     version = "v0.14.0" | ||||
|     hash = "sha256-yh3B0tom1RfzQBf1RNmfdNWF1PtiqxV41jW1GVS6JAg=" | ||||
|     version = "v0.17.0" | ||||
|     hash = "sha256-R8JbsP7KX+KFTHH7SjRnUGCdvtagylVOfngWEnVSqBc=" | ||||
|   [mod."golang.org/x/time"] | ||||
|     version = "v0.5.0" | ||||
|     hash = "sha256-W6RgwgdYTO3byIPOFxrP2IpAZdgaGowAaVfYby7AULU=" | ||||
|     version = "v0.6.0" | ||||
|     hash = "sha256-gW9TVK9HjLk52lzfo5rBzSunc01gS0+SG2nk0X1w55M=" | ||||
|   [mod."google.golang.org/protobuf"] | ||||
|     version = "v1.33.0" | ||||
|     hash = "sha256-cWwQjtUwSIEkAlAadrlxK1PYZXTRrV4NKzt7xDpJgIU=" | ||||
|     version = "v1.34.2" | ||||
|     hash = "sha256-nMTlrDEE2dbpWz50eQMPBQXCyQh4IdjrTIccaU0F3m0=" | ||||
|   [mod."gopkg.in/ini.v1"] | ||||
|     version = "v1.67.0" | ||||
|     hash = "sha256-V10ahGNGT+NLRdKUyRg1dos5RxLBXBk1xutcnquc/+4=" | ||||
|  |  | |||
							
								
								
									
										106
									
								
								server.go
									
										
									
									
									
								
							
							
						
						
									
										106
									
								
								server.go
									
										
									
									
									
								
							|  | @ -1,19 +1,14 @@ | |||
| package main | ||||
| 
 | ||||
| import ( | ||||
| 	"encoding/json" | ||||
| 	"fmt" | ||||
| 	"github.com/fhs/gompd/v2/mpd" | ||||
| 	"github.com/labstack/echo-contrib/echoprometheus" | ||||
| 	"github.com/labstack/echo/v4" | ||||
| 	"github.com/labstack/echo/v4/middleware" | ||||
| 	"golang.org/x/net/websocket" | ||||
| 	"gopkg.in/ini.v1" | ||||
| 	"log" | ||||
| 	"net/http" | ||||
| 	"os" | ||||
| 	"os/exec" | ||||
| 	"strings" | ||||
| ) | ||||
| 
 | ||||
| // Config holds the configuration for the mpd connection and for the web server. | ||||
|  | @ -78,6 +73,21 @@ func main() { | |||
| 		return c.File("index.html") | ||||
| 	}) | ||||
| 
 | ||||
| 	// echo back request to check if HTTP/2 works etc | ||||
| 	e.GET("/echo", func(c echo.Context) error { | ||||
| 		req := c.Request() | ||||
| 		format := ` | ||||
| <code> | ||||
|     Protocol: %s<br> | ||||
|     Host: %s<br> | ||||
|     Remote Address: %s<br> | ||||
|     Method: %s<br> | ||||
|     Path: %s<br> | ||||
| </code> | ||||
| ` | ||||
| 		return c.HTML(http.StatusOK, fmt.Sprintf(format, req.Proto, req.Host, req.RemoteAddr, req.Method, req.URL.Path)) | ||||
| 	}) | ||||
| 
 | ||||
| 	g := e.Group("/api") | ||||
| 	g.GET("/update_db", updateDb) | ||||
| 	g.GET("/previous_track", previousTrack) | ||||
|  | @ -104,7 +114,7 @@ func main() { | |||
| 
 | ||||
| 	g.GET("/download", downloadTrack) | ||||
| 
 | ||||
| 	e.GET("/ws", wsServe) | ||||
| 	e.GET("/sse", serveSSE) | ||||
| 
 | ||||
| 	if config.UI.Tls { | ||||
| 		e.Logger.Fatal(e.StartTLS(fmt.Sprintf("%s:%d", config.UI.Hostname, config.UI.Port), config.UI.Certificate, config.UI.Key)) | ||||
|  | @ -113,90 +123,8 @@ func main() { | |||
| 	} | ||||
| } | ||||
| 
 | ||||
| // wsServe handles websocket connections. | ||||
| func wsServe(c echo.Context) error { | ||||
| 	websocket.Handler(func(ws *websocket.Conn) { | ||||
| 		defer ws.Close() | ||||
| 
 | ||||
| 		// Connect to MPD server | ||||
| 		mpdConn, err := mpd.Dial("tcp", "localhost:6600") | ||||
| 		if err != nil { | ||||
| 			//log.Fatalln(err) | ||||
| 			c.Logger().Error(err) | ||||
| 			err = websocket.Message.Send(ws, fmt.Sprintf("{\"mpd_error\":\"%s\"}", err.Error())) | ||||
| 			if err != nil { | ||||
| 				c.Logger().Error(err) | ||||
| 			} | ||||
| 		} | ||||
| 		defer mpdConn.Close() | ||||
| 
 | ||||
| 		for { | ||||
| 			// Read | ||||
| 			msg := "" | ||||
| 			err := websocket.Message.Receive(ws, &msg) | ||||
| 			if err != nil { | ||||
| 				c.Logger().Error(err) | ||||
| 				break | ||||
| 			} else { | ||||
| 				// log.Print(msg) | ||||
| 				if strings.ToLower(msg) == "#status" { | ||||
| 					status, err := mpdConn.Status() | ||||
| 					if err != nil { | ||||
| 						c.Logger().Error(err) | ||||
| 					} | ||||
| 					currentsong, err := mpdConn.CurrentSong() | ||||
| 					if err != nil { | ||||
| 						c.Logger().Error(err) | ||||
| 					} | ||||
| 					queue, err := mpdConn.PlaylistInfo(-1, -1) | ||||
| 					if err != nil { | ||||
| 						c.Logger().Error(err) | ||||
| 					} | ||||
| 					jsonStatus, err := json.Marshal(status) | ||||
| 					if err != nil { | ||||
| 						c.Logger().Error(err) | ||||
| 					} | ||||
| 					jsonCurrentSong, err := json.Marshal(currentsong) | ||||
| 					if err != nil { | ||||
| 						c.Logger().Error(err) | ||||
| 					} | ||||
| 					jsonQueue, err := json.Marshal(queue) | ||||
| 					if err != nil { | ||||
| 						c.Logger().Error(err) | ||||
| 					} | ||||
| 					err = websocket.Message.Send(ws, fmt.Sprintf("{\"mpd_status\":%s,\"mpd_current_song\":%s,\"mpd_queue\":%s}", string(jsonStatus), string(jsonCurrentSong), string(jsonQueue))) | ||||
| 					if err != nil { | ||||
| 						c.Logger().Error(err) | ||||
| 					} | ||||
| 
 | ||||
| 				} else if strings.HasPrefix(strings.ToLower(msg), "#download ") { | ||||
| 					// Download video link as audio file | ||||
| 					uri := strings.SplitN(msg, " ", 2)[1] | ||||
| 					// TODO: implement yt-dlp integration | ||||
| 					err := websocket.Message.Send(ws, fmt.Sprintf("Downloading %s", uri)) | ||||
| 					if err != nil { | ||||
| 						c.Logger().Error(err) | ||||
| 					} | ||||
| 				} | ||||
| 			} | ||||
| 		} | ||||
| 	}).ServeHTTP(c.Response(), c.Request()) | ||||
| 	return nil | ||||
| } | ||||
| 
 | ||||
| // downloadTrack tries to download a given URL and saves the song to the database. | ||||
| func downloadTrack(c echo.Context) error { | ||||
| 	// yt-dlp \ | ||||
| 	// --no-wait-for-video \ | ||||
| 	// --no-playlist \ | ||||
| 	// --windows-filenames \ | ||||
| 	// --newline \ | ||||
| 	// --extract-audio \ | ||||
| 	// --audio-format mp3 \ | ||||
| 	// --audio-quality 0 \ | ||||
| 	// -f bestaudio/best \ | ||||
| 	// ${video_url} | ||||
| 
 | ||||
| 	cmd := exec.Command( | ||||
| 		"yt-dlp", | ||||
| 		"--no-wait-for-video", | ||||
|  | @ -212,7 +140,7 @@ func downloadTrack(c echo.Context) error { | |||
| 	cmd.Stdout = os.Stdout | ||||
| 	cmd.Stderr = os.Stderr | ||||
| 	if err := cmd.Run(); err != nil { | ||||
| 		log.Fatalln(err) | ||||
| 		c.Logger().Fatal(err) | ||||
| 	} | ||||
| 
 | ||||
| 	return c.String(http.StatusAccepted, "") | ||||
|  |  | |||
							
								
								
									
										191
									
								
								sse.go
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										191
									
								
								sse.go
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,191 @@ | |||
| package main | ||||
| 
 | ||||
| import ( | ||||
| 	"bytes" | ||||
| 	"encoding/json" | ||||
| 	"fmt" | ||||
| 	"github.com/fhs/gompd/v2/mpd" | ||||
| 	"github.com/labstack/echo/v4" | ||||
| 	"io" | ||||
| 	"time" | ||||
| ) | ||||
| 
 | ||||
| // Event represents Server-Sent Event. | ||||
| // SSE explanation: https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events/Using_server-sent_events#event_stream_format | ||||
| type Event struct { | ||||
| 	// ID is used to set the EventSource object's last event ID value. | ||||
| 	ID []byte | ||||
| 	// Data field is for the message. When the EventSource receives multiple consecutive lines | ||||
| 	// that begin with data:, it concatenates them, inserting a newline character between each one. | ||||
| 	// Trailing newlines are removed. | ||||
| 	Data []byte | ||||
| 	// Event is a string identifying the type of event described. If this is specified, an event | ||||
| 	// will be dispatched on the browser to the listener for the specified event name; the website | ||||
| 	// source code should use addEventListener() to listen for named events. The onmessage handler | ||||
| 	// is called if no event name is specified for a message. | ||||
| 	Event []byte | ||||
| 	// Retry is the reconnection time. If the connection to the server is lost, the browser will | ||||
| 	// wait for the specified time before attempting to reconnect. This must be an integer, specifying | ||||
| 	// the reconnection time in milliseconds. If a non-integer value is specified, the field is ignored. | ||||
| 	Retry []byte | ||||
| 	// Comment line can be used to prevent connections from timing out; a server can send a comment | ||||
| 	// periodically to keep the connection alive. | ||||
| 	Comment []byte | ||||
| } | ||||
| 
 | ||||
| // MarshalTo marshals Event to given Writer | ||||
| func (ev *Event) MarshalTo(w io.Writer) error { | ||||
| 	// Marshalling part is taken from: https://github.com/r3labs/sse/blob/c6d5381ee3ca63828b321c16baa008fd6c0b4564/http.go#L16 | ||||
| 	if len(ev.Data) == 0 && len(ev.Comment) == 0 { | ||||
| 		return nil | ||||
| 	} | ||||
| 
 | ||||
| 	if len(ev.Data) > 0 { | ||||
| 		if _, err := fmt.Fprintf(w, "id: %s\n", ev.ID); err != nil { | ||||
| 			return err | ||||
| 		} | ||||
| 
 | ||||
| 		sd := bytes.Split(ev.Data, []byte("\n")) | ||||
| 		for i := range sd { | ||||
| 			if _, err := fmt.Fprintf(w, "data: %s\n", sd[i]); err != nil { | ||||
| 				return err | ||||
| 			} | ||||
| 		} | ||||
| 
 | ||||
| 		if len(ev.Event) > 0 { | ||||
| 			if _, err := fmt.Fprintf(w, "event: %s\n", ev.Event); err != nil { | ||||
| 				return err | ||||
| 			} | ||||
| 		} | ||||
| 
 | ||||
| 		if len(ev.Retry) > 0 { | ||||
| 			if _, err := fmt.Fprintf(w, "retry: %s\n", ev.Retry); err != nil { | ||||
| 				return err | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	if len(ev.Comment) > 0 { | ||||
| 		if _, err := fmt.Fprintf(w, ": %s\n", ev.Comment); err != nil { | ||||
| 			return err | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	if _, err := fmt.Fprint(w, "\n"); err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 
 | ||||
| 	return nil | ||||
| } | ||||
| 
 | ||||
| // serveSSE handles sending Server-Sent-Events. | ||||
| func serveSSE(c echo.Context) error { | ||||
| 	// TODO: figure out how to retrieve IP from Forwarded header behind proxy: https://echo.labstack.com/docs/ip-address | ||||
| 	c.Logger().Printf("SSE client connected, ip: %v", c.RealIP()) | ||||
| 
 | ||||
| 	w := c.Response() | ||||
| 	w.Header().Set("Content-Type", "text/event-stream") | ||||
| 	w.Header().Set("Cache-Control", "no-cache") | ||||
| 	w.Header().Set("Connection", "keep-alive") | ||||
| 
 | ||||
| 	// Connect to MPD server | ||||
| 	mpdConn, err := mpd.Dial("tcp", "localhost:6600") | ||||
| 	if err != nil { | ||||
| 		c.Logger().Error(err) | ||||
| 		event := Event{ | ||||
| 			Event: []byte("mpd"), | ||||
| 			Data:  []byte(fmt.Sprintf("connection error: %s", err.Error())), | ||||
| 		} | ||||
| 		if err := event.MarshalTo(w); err != nil { | ||||
| 			return err | ||||
| 		} | ||||
| 		w.Flush() | ||||
| 	} | ||||
| 	defer mpdConn.Close() | ||||
| 
 | ||||
| 	ticker := time.NewTicker(1 * time.Second) | ||||
| 	defer ticker.Stop() | ||||
| 
 | ||||
| 	var lastJsonStatus []byte | ||||
| 	var lastJsonCurrentSong []byte | ||||
| 	var lastJsonQueue []byte | ||||
| 	for { | ||||
| 		select { | ||||
| 		case <-c.Request().Context().Done(): | ||||
| 			c.Logger().Printf("SSE client disconnected, ip: %v", c.RealIP()) | ||||
| 			return nil | ||||
| 		case <-ticker.C: | ||||
| 			c.Logger().Printf("Getting MPD status for %v", c.RealIP()) | ||||
| 
 | ||||
| 			status, err := mpdConn.Status() | ||||
| 			if err != nil { | ||||
| 				c.Logger().Error(err) | ||||
| 			} | ||||
| 			jsonStatus, err := json.Marshal(status) | ||||
| 			if err != nil { | ||||
| 				c.Logger().Error(err) | ||||
| 			} | ||||
| 			// Only send new event if different from last time | ||||
| 			if !bytes.Equal(jsonStatus, lastJsonStatus) { | ||||
| 				statusEvent := Event{ | ||||
| 					Event: []byte("status"), | ||||
| 					Data:  []byte(string(jsonStatus)), | ||||
| 				} | ||||
| 				if err := statusEvent.MarshalTo(w); err != nil { | ||||
| 					return err | ||||
| 				} | ||||
| 				lastJsonStatus = jsonStatus | ||||
| 			} | ||||
| 
 | ||||
| 			currentsong, err := mpdConn.CurrentSong() | ||||
| 			if err != nil { | ||||
| 				c.Logger().Error(err) | ||||
| 			} | ||||
| 			jsonCurrentSong, err := json.Marshal(currentsong) | ||||
| 			if err != nil { | ||||
| 				c.Logger().Error(err) | ||||
| 			} | ||||
| 			// Only send new event if different from last time | ||||
| 			if !bytes.Equal(jsonCurrentSong, lastJsonCurrentSong) { | ||||
| 				currentSongEvent := Event{ | ||||
| 					Event: []byte("status"), | ||||
| 					Data:  []byte(string(jsonCurrentSong)), | ||||
| 				} | ||||
| 				if err := currentSongEvent.MarshalTo(w); err != nil { | ||||
| 					return err | ||||
| 				} | ||||
| 				lastJsonCurrentSong = jsonCurrentSong | ||||
| 			} | ||||
| 
 | ||||
| 			queue, err := mpdConn.PlaylistInfo(-1, -1) | ||||
| 			if err != nil { | ||||
| 				c.Logger().Error(err) | ||||
| 			} | ||||
| 			jsonQueue, err := json.Marshal(queue) | ||||
| 			if err != nil { | ||||
| 				c.Logger().Error(err) | ||||
| 			} | ||||
| 			// Only send new event if different from last time | ||||
| 			if !bytes.Equal(jsonQueue, lastJsonQueue) { | ||||
| 				queueEvent := Event{ | ||||
| 					Event: []byte("status"), | ||||
| 					Data:  []byte(string(jsonQueue)), | ||||
| 				} | ||||
| 				if err := queueEvent.MarshalTo(w); err != nil { | ||||
| 					return err | ||||
| 				} | ||||
| 				lastJsonQueue = jsonQueue | ||||
| 			} | ||||
| 
 | ||||
| 			// Ping to prevent timeout | ||||
| 			pingEvent := Event{ | ||||
| 				Comment: []byte("ping"), | ||||
| 			} | ||||
| 			if err := pingEvent.MarshalTo(w); err != nil { | ||||
| 				return err | ||||
| 			} | ||||
| 
 | ||||
| 			w.Flush() | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
|  | @ -197,7 +197,6 @@ | |||
|     <div id="playlist-browser" style="display: none"> | ||||
|       <label for="control-playlist-list"></label> | ||||
|       <select id="control-playlist-list" size="15"> | ||||
|         <option value="1">basedrive</option><!-- TODO: Remove this line --> | ||||
|       </select><!--/#control-playlist-list--> | ||||
|       <div> | ||||
|         <button id="control-refresh-playlists">🔄 Refresh</button><!-- 🔄 Counterclockwise Arrows Button --> | ||||
|  | @ -223,62 +222,9 @@ | |||
|     </table> | ||||
|   </div><!--/#result--> | ||||
|   <footer> | ||||
|     <a href="https://git.berlin.ccc.de/cccb/sanic"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 92 92"><defs><clipPath id="a"><path d="M0 .113h91.887V92H0Zm0 0"/></clipPath></defs><g clip-path="url(#a)"><path style="stroke:none;fill-rule:nonzero;fill:#ffffff;fill-opacity:1" d="M90.156 41.965 50.036 1.848a5.913 5.913 0 0 0-8.368 0l-8.332 8.332 10.566 10.566a7.03 7.03 0 0 1 7.23 1.684 7.043 7.043 0 0 1 1.673 7.277l10.183 10.184a7.026 7.026 0 0 1 7.278 1.672 7.04 7.04 0 0 1 0 9.957 7.045 7.045 0 0 1-9.961 0 7.038 7.038 0 0 1-1.532-7.66l-9.5-9.497V59.36a7.04 7.04 0 0 1 1.86 11.29 7.04 7.04 0 0 1-9.957 0 7.04 7.04 0 0 1 0-9.958 7.034 7.034 0 0 1 2.308-1.539V33.926a7.001 7.001 0 0 1-2.308-1.535 7.049 7.049 0 0 1-1.516-7.7L29.242 14.273 1.734 41.777a5.918 5.918 0 0 0 0 8.371L41.855 90.27a5.92 5.92 0 0 0 8.368 0l39.933-39.934a5.925 5.925 0 0 0 0-8.371"/></g></svg></a> Sanic MPD Web UI 0.1.0 - by XenGi and coon © 2023 | ||||
|     <a href="https://gitlab.com/XenGi/sanic"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 92 92"><defs><clipPath id="a"><path d="M0 .113h91.887V92H0Zm0 0"/></clipPath></defs><g clip-path="url(#a)"><path style="stroke:none;fill-rule:nonzero;fill:#ffffff;fill-opacity:1" d="M90.156 41.965 50.036 1.848a5.913 5.913 0 0 0-8.368 0l-8.332 8.332 10.566 10.566a7.03 7.03 0 0 1 7.23 1.684 7.043 7.043 0 0 1 1.673 7.277l10.183 10.184a7.026 7.026 0 0 1 7.278 1.672 7.04 7.04 0 0 1 0 9.957 7.045 7.045 0 0 1-9.961 0 7.038 7.038 0 0 1-1.532-7.66l-9.5-9.497V59.36a7.04 7.04 0 0 1 1.86 11.29 7.04 7.04 0 0 1-9.957 0 7.04 7.04 0 0 1 0-9.958 7.034 7.034 0 0 1 2.308-1.539V33.926a7.001 7.001 0 0 1-2.308-1.535 7.049 7.049 0 0 1-1.516-7.7L29.242 14.273 1.734 41.777a5.918 5.918 0 0 0 0 8.371L41.855 90.27a5.92 5.92 0 0 0 8.368 0l39.933-39.934a5.925 5.925 0 0 0 0-8.371"/></g></svg></a> Sanic MPD Web UI 0.1.0 - by XenGi and coon © 2023 | ||||
|   </footer> | ||||
| </main> | ||||
| <script src="index.js"></script> | ||||
| <script> | ||||
| const table = document.querySelector("#queue > table > tbody"); | ||||
| for (let i = 1; i <= 100; i++) { | ||||
|   const tr = document.createElement("tr"); | ||||
|   if (i === 1) { | ||||
|     tr.classList.add("playing"); | ||||
|     tr.dataset.song_id = i; | ||||
|   } | ||||
|   const pos = document.createElement("td"); | ||||
|   pos.innerText = i.toString(); | ||||
|   const artist = document.createElement("td"); | ||||
|   artist.innerText = `Artist ${i.toString()} with a super long name that should finally bring the length to a maximum`; | ||||
|   const track = document.createElement("td"); | ||||
|   track.innerText = `Track ${i.toString()} with super long name that could potentially expand the table by a lot!`; | ||||
|   const album = document.createElement("td"); | ||||
|   album.innerText = `Album ${i.toString()}`; | ||||
|   const length = document.createElement("td"); | ||||
|   length.innerText = "01:00:00"; | ||||
|   const actions = document.createElement("td"); | ||||
|   actions.classList.add("actions"); | ||||
|   // TODO: maybe use a instead of button? | ||||
|   const moveUp = document.createElement("button"); | ||||
|   moveUp.classList.add("borderless"); | ||||
|   moveUp.innerHTML = "🔺"; // 🔺 Red Triangle Pointed Down | ||||
|   moveUp.addEventListener("click", event => { | ||||
|     console.log(`DEBUG: move song ${i} up`); | ||||
|   }); | ||||
|   // TODO: maybe use a instead of button? | ||||
|   const moveDown = document.createElement("button"); | ||||
|   moveDown.classList.add("borderless"); | ||||
|   moveDown.innerHTML = "🔻"; // 🔻 Red Triangle Pointed Up | ||||
|   moveDown.addEventListener("click", event => { | ||||
|     console.log(`DEBUG: move song ${i} down`); | ||||
|   }); | ||||
|   // TODO: maybe use a instead of button? | ||||
|   const remove = document.createElement("button"); | ||||
|   remove.classList.add("borderless"); | ||||
|   remove.innerHTML = "❌"; // ❌ Cross mark | ||||
|   remove.addEventListener("click", event => { | ||||
|     console.log(`DEBUG: remove song ${i} from queue`); | ||||
|   }); | ||||
|   actions.appendChild(moveUp); | ||||
|   actions.appendChild(moveDown); | ||||
|   actions.appendChild(remove); | ||||
|   tr.appendChild(pos); | ||||
|   tr.appendChild(artist); | ||||
|   tr.appendChild(track); | ||||
|   tr.appendChild(album); | ||||
|   tr.appendChild(length); | ||||
|   tr.appendChild(actions); | ||||
|   table.appendChild(tr); | ||||
| } | ||||
| </script> | ||||
| </body> | ||||
| </html> | ||||
|  |  | |||
|  | @ -392,8 +392,28 @@ control_volume.addEventListener("change", event => { | |||
|   }); | ||||
| }); | ||||
| 
 | ||||
| // Server-Sent-Events
 | ||||
| 
 | ||||
| if (typeof (EventSource) !== "undefined") { | ||||
|   const sse = new EventSource("/sse"); | ||||
|   sse.addEventListener("status", (event) => { | ||||
|     const status = JSON.parse(event.data); | ||||
|     console.log("test: " + event.data); | ||||
| 
 | ||||
|     connection_state.innerHTML = "❌ Disconnected";  // ✅ Check Mark Button
 | ||||
|   }); | ||||
| 
 | ||||
|   sse.onmessage = function (event) { | ||||
|     console.log("sse message: " + event.data); | ||||
|   }; | ||||
| } else { | ||||
|   console.log("Sorry, your browser does not support server-sent events..."); | ||||
| } | ||||
| 
 | ||||
| // Websocket logic
 | ||||
| 
 | ||||
| /* | ||||
| 
 | ||||
| // Create WebSocket connection.
 | ||||
| const socket = new WebSocket(`${document.location.protocol === "https:" ? "wss" : "ws"}://${document.location.host}/ws`); | ||||
| 
 | ||||
|  | @ -409,9 +429,9 @@ socket.addEventListener("message", event => { | |||
| 
 | ||||
|   const msg = JSON.parse(event.data); | ||||
| 
 | ||||
|   if ("mpd_status" in msg) { | ||||
|   if ("status" in msg) { | ||||
|     if (msg.mpd_status == null) { | ||||
|       connection_state.innerHTML = "❌ Disconnected";  // ✅ Check Mark Button
 | ||||
| 
 | ||||
|     } else { | ||||
|       // print error if present
 | ||||
|       if ("error" in msg.mpd_status) { | ||||
|  | @ -480,7 +500,7 @@ socket.addEventListener("message", event => { | |||
|   } | ||||
| 
 | ||||
|   // update song info
 | ||||
|   if ("mpd_current_song" in msg && msg.mpd_current_song != null) { | ||||
|   if ("current_song" in msg && msg.mpd_current_song != null) { | ||||
|     let track; | ||||
|     if ("Artist" in msg.mpd_current_song && "Title" in msg.mpd_current_song) { | ||||
|       track = `${msg.mpd_current_song.Artist} - ${msg.mpd_current_song.Title}` | ||||
|  | @ -493,7 +513,7 @@ socket.addEventListener("message", event => { | |||
|   } | ||||
| 
 | ||||
|   // update queue
 | ||||
|   if ("mpd_queue" in msg && msg.mpd_queue != null) { | ||||
|   if ("queue" in msg && msg.mpd_queue != null) { | ||||
|     const tbody = document.createElement("tbody"); | ||||
|     msg.mpd_queue.forEach(song => { | ||||
|       const tr = document.createElement("tr"); | ||||
|  | @ -557,7 +577,7 @@ socket.addEventListener("message", event => { | |||
|     } | ||||
|   } | ||||
| 
 | ||||
|   if ("mpd_error" in msg) { | ||||
|   if ("error" in msg) { | ||||
|     console.error(`MPD Error: ${msg.mpd_error}`) | ||||
|   } | ||||
| }); | ||||
|  | @ -572,3 +592,4 @@ window.setInterval(() => { | |||
|   } | ||||
| }, 1000); | ||||
| 
 | ||||
| */ | ||||
|  |  | |||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue