Added favicons (iconset && auto-detection)

This commit is contained in:
Mike Schwörer 2023-12-23 23:52:53 +01:00
parent c38afc6ef3
commit 8eed2ca69f
Signed by: Mikescher
GPG Key ID: D3C7172E0A70F8CF
32 changed files with 576 additions and 36 deletions

2
.gitignore vendored
View File

@ -4,6 +4,8 @@ _build
.run-data
_run-data
_release
DOCKER_GIT_INFO
.swaggobin

6
.idea/golinter.xml Normal file
View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="GoLinterSettings">
<option name="checkGoLinterExe" value="false" />
</component>
</project>

View File

@ -14,6 +14,18 @@ build: enums ids swagger fmt
rm -f ./_build/bunny_backend
go build -v -buildvcs=false -o _build/bunny_backend ./cmd/server
release: build
mkdir -p _release
rm -f ./_release/bunny
go build -v -buildvcs=false -o _release/bunny ./cmd/server
restart:
mkdir -p _release
sudo rm -f ./_release/bunny
sudo systemctl stop localhostbunny
go build -v -buildvcs=false -o _release/bunny ./cmd/server
sudo systemctl start localhostbunny
enums:
go generate models/enums.go

View File

@ -1,6 +1,7 @@
package handler
import (
"gogs.mikescher.com/BlackForestBytes/goext/exerr"
"gogs.mikescher.com/BlackForestBytes/goext/ginext"
bunny "locbunny"
"locbunny/logic"
@ -45,3 +46,33 @@ func (h APIHandler) ListServer(pctx ginext.PreContext) ginext.HTTPResponse {
return ginext.JSON(http.StatusOK, response{Servers: srvs})
}
// GetIcon swaggerdoc
//
// @Summary Get Icon
//
// @Param cs path number true "Icon Checksum"
//
// @Router /icon/:cs [GET]
func (h APIHandler) GetIcon(pctx ginext.PreContext) ginext.HTTPResponse {
type uri struct {
Checksum string `uri:"cs"`
}
var u uri
ctx, _, errResp := pctx.URI(&u).Start()
if errResp != nil {
return *errResp
}
defer ctx.Cancel()
icn := h.app.GetIcon(ctx, u.Checksum)
if icn == nil {
return ginext.Error(exerr.New(bunny.ErrEntityNotFound, "Icon not found").Str("cs", u.Checksum).WithStatuscode(404).Build())
}
return ginext.Data(200, icn.ContentType, icn.Data).
WithHeader("X-BUNNY-ICONID", icn.IconID.String()).
WithHeader("X-BUNNY-CHECKSUM", icn.Checksum).
WithHeader("X-BUNNY-ICONDATE", icn.Time.String())
}

View File

@ -64,6 +64,7 @@ func (r *Router) Init(e *ginext.GinWrapper) {
// ================ API ================
api.GET("/server").Handle(r.apiHandler.ListServer)
api.GET("/icon/:cs").Handle(r.apiHandler.GetIcon)
// ================ ================

2
go.mod
View File

@ -10,6 +10,8 @@ require (
gogs.mikescher.com/BlackForestBytes/goext v0.0.288
)
require github.com/adampresley/gofavigrab v0.0.0-20150913222647-3e339572468d // indirect
require (
github.com/go-ole/go-ole v1.2.6 // indirect
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 // indirect

2
go.sum
View File

@ -1,3 +1,5 @@
github.com/adampresley/gofavigrab v0.0.0-20150913222647-3e339572468d h1:he2p51oHkafDBlMOjLoCzPd1zJSZuQGseI3WmbQoLaY=
github.com/adampresley/gofavigrab v0.0.0-20150913222647-3e339572468d/go.mod h1:383ML3lBZdTp1YUk6jGiN6qw7duOG6aF6Y6OVn2uM50=
github.com/bytedance/sonic v1.5.0/go.mod h1:ED5hyg4y6t3/9Ku1R6dU/4KyJ48DZ4jPhfY1O2AihPM=
github.com/bytedance/sonic v1.10.0-rc/go.mod h1:ElCzW+ufi8qKqNW0FY314xriJhyJhuoJ3gFZdAHF7NM=
github.com/bytedance/sonic v1.10.2 h1:GQebETVBxYB7JGWJtLBi07OVzWwt+8dWA00gEVW2ZFE=

5
icons/README.md Normal file
View File

@ -0,0 +1,5 @@
// source: https://github.com/PapirusDevelopmentTeam/papirus-icon-theme
https://www.iconarchive.com/show/papirus-apps-icons-by-papirus-team.html

16
icons/androidstudio.svg Normal file
View File

@ -0,0 +1,16 @@
<svg xmlns="http://www.w3.org/2000/svg" width="64" height="64" version="1.1" viewBox="0 0 64 64">
<g transform="translate(0,-1058.5197)">
<circle cx="32" cy="1091.52" r="29" style="opacity:0.2"/>
<circle style="fill:#88ba53" cx="32" cy="1090.52" r="29"/>
<path style="opacity:0.1" d="m 34.557948,1086.4207 -5.022948,2.9 15.95,27.626 5.411474,3.573 -0.388526,-6.473 z"/>
<path d="m 34.557948,1085.4207 -5.022948,2.9 15.95,27.626 5.411474,3.573 -0.388526,-6.473 z" style="fill:#e4e4e4"/>
<path style="opacity:0.2" d="m 29.488282,1086.4221 -15.94922,27.625 -0.390624,6.4726 5.414062,-3.5742 2.898438,-5.0196 c 3.246318,1.683 6.85494,2.5872 10.539062,2.5938 12.70255,0 23,-10.2974 23,-23 -0.0096,-0.3336 -0.0266,-0.667 -0.05078,-1 h -5.984374 c 0.02152,0.333 0.03326,0.6664 0.03516,1 0,9.3888 -7.61116,17 -17,17 -2.650842,0 -5.213098,-0.6554 -7.53125,-1.8086 l 10.042968,-17.3906 z"/>
<path style="fill:#ffffff" d="m 29.488526,1085.4207 5.022948,2.9 -15.95,27.626 -5.411474,3.573 0.388526,-6.473 z"/>
<path style="opacity:0.1" d="m 29,1072.5196 v 6 H 25.999998 C 23,1078.5196 23,1081.403 23,1081.403 v 11.9364 l 6,5.8946 c 1.99296,1.5824 4.151174,1.8412 6,0 l 6,-5.8946 v -11.9364 c 0,-2.9474 -2.999998,-2.8834 -2.999998,-2.8834 H 35 v -6 z"/>
<path d="m 29,1071.5197 v 6 H 25.999998 C 23,1077.5197 23,1080.4031 23,1080.4031 v 11.9364 l 6,5.8946 c 1.99296,1.5824 4.151174,1.8412 6,0 l 6,-5.8946 v -11.9364 c 0,-2.9474 -2.999998,-2.8834 -2.999998,-2.8834 H 35 v -6 z" style="fill:#4f4f4f"/>
<circle style="opacity:0.2" cx="32" cy="1087.52" r="3"/>
<circle cx="32" cy="1086.52" r="3" style="fill:#ffffff"/>
<path style="fill:#ffffff" d="m 48.964844,1089.5197 c 0.02152,0.3329 0.03326,0.6664 0.03516,1 0,9.3888 -7.61116,17 -17,17 -3.225652,0 -6.34409,-0.9251 -9.016804,-2.6033 -1.484598,-0.9322 -0.831668,-0.024 -1.9832,-1.3967 l -2,5.9453 c 3.824706,2.6328 8.356726,4.0464 13,4.0547 12.70255,0 23,-10.2974 23,-23 -0.0097,-0.3337 -0.0266,-0.6671 -0.05078,-1 z"/>
<path style="opacity:0.2;fill:#ffffff" d="M 32 3 A 29 29 0 0 0 3 32 A 29 29 0 0 0 3.0175781 32.488281 A 29 29 0 0 1 32 4 A 29 29 0 0 1 60.974609 32.494141 A 29 29 0 0 0 61 32 A 29 29 0 0 0 32 3 z" transform="translate(0,1058.5197)"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.2 KiB

16
icons/cups.svg Normal file
View File

@ -0,0 +1,16 @@
<svg xmlns="http://www.w3.org/2000/svg" width="64" height="64" version="1">
<path d="m 10,37 0,20.2 c 0,1.5512 1.2265,2.8 2.75,2.8 l 38.5,0 C 52.7735,60 54,58.7512 54,57.2 L 54,37 Z" style="opacity:0.2"/>
<rect width="24" height="56" x="-45" y="-60" rx="2.824" ry="2.8" transform="matrix(0,-1,-1,0,0,0)" style="opacity:0.2"/>
<path style="fill:#e4e4e4" d="M 12.75,4 C 11.2265,4 10,5.2742857 10,6.8571429 L 10,24 54,24 54,6.8571429 C 54,5.2742857 52.7735,4 51.25,4 l -38.5,0 z"/>
<rect style="fill:#4f4f4f" width="24" height="56" x="-44" y="-60" rx="2.824" ry="2.8" transform="matrix(0,-1,-1,0,0,0)"/>
<rect width="50" height="6" x="7" y="33" style="opacity:0.2"/>
<rect style="opacity:0.2" width="5" height="5" x="7" y="24" rx="1.875" ry="1.875"/>
<rect width="5" height="5" x="7" y="23" rx="1.875" ry="1.875" style="fill:#76ff03"/>
<path style="fill:#e4e4e4" d="m 10,36 0,20.2 c 0,1.5512 1.2265,2.8 2.75,2.8 l 38.5,0 C 52.7735,59 54,57.7512 54,56.2 L 54,36 Z"/>
<rect width="29" height="3" x="15" y="52" style="opacity:0.5;fill-rule:evenodd"/>
<rect width="24" height="3" x="15" y="46" style="opacity:0.5;fill-rule:evenodd"/>
<rect style="opacity:0.5;fill-rule:evenodd" width="34" height="3" x="15" y="40"/>
<path style="fill:#ffffff;opacity:0.2" d="M 12.75 4 C 11.2265 4 10 5.2745647 10 6.8574219 L 10 7.8574219 C 10 6.2745647 11.2265 5 12.75 5 L 51.25 5 C 52.7735 5 54 6.2745647 54 7.8574219 L 54 6.8574219 C 54 5.2745647 52.7735 4 51.25 4 L 12.75 4 z"/>
<path style="fill:#ffffff;opacity:0.1" d="M 6.8007812 20 C 5.2495813 20 4 21.259983 4 22.824219 L 4 23.824219 C 4 22.259983 5.2495813 21 6.8007812 21 L 57.199219 21 C 58.750419 21 60 22.259983 60 23.824219 L 60 22.824219 C 60 21.259983 58.750419 20 57.199219 20 L 6.8007812 20 z"/>
<path style="fill:#ffffff;opacity:0.2" d="M 8.875 23 C 7.83625 23 7 23.83625 7 24.875 L 7 25.875 C 7 24.83625 7.83625 24 8.875 24 L 10.125 24 C 11.16375 24 12 24.83625 12 25.875 L 12 24.875 C 12 23.83625 11.16375 23 10.125 23 L 8.875 23 z"/>
</svg>

After

Width:  |  Height:  |  Size: 2.0 KiB

1
icons/docker.svg Normal file
View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 512"><!--! Font Awesome Free 6.2.1 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) Copyright 2022 Fonticons, Inc. --><path fill="#086ad0" d="M349.9 236.3h-66.1v-59.4h66.1v59.4zm0-204.3h-66.1v60.7h66.1V32zm78.2 144.8H362v59.4h66.1v-59.4zm-156.3-72.1h-66.1v60.1h66.1v-60.1zm78.1 0h-66.1v60.1h66.1v-60.1zm276.8 100c-14.4-9.7-47.6-13.2-73.1-8.4-3.3-24-16.7-44.9-41.1-63.7l-14-9.3-9.3 14c-18.4 27.8-23.4 73.6-3.7 103.8-8.7 4.7-25.8 11.1-48.4 10.7H2.4c-8.7 50.8 5.8 116.8 44 162.1 37.1 43.9 92.7 66.2 165.4 66.2 157.4 0 273.9-72.5 328.4-204.2 21.4.4 67.6.1 91.3-45.2 1.5-2.5 6.6-13.2 8.5-17.1l-13.3-8.9zm-511.1-27.9h-66v59.4h66.1v-59.4zm78.1 0h-66.1v59.4h66.1v-59.4zm78.1 0h-66.1v59.4h66.1v-59.4zm-78.1-72.1h-66.1v60.1h66.1v-60.1z"/></svg>

After

Width:  |  Height:  |  Size: 889 B

15
icons/goland.svg Normal file
View File

@ -0,0 +1,15 @@
<svg xmlns="http://www.w3.org/2000/svg" width="64" height="64" version="1">
<path style="opacity:0.2" d="M 4,33 4,58.199218 C 4,59.750418 5.2495812,61 6.8007812,61 L 32,61 57.199218,61 C 58.750418,61 60,59.750418 60,58.199218 L 60,33 32,33 4,33 Z"/>
<path style="fill:#6655e4" d="M 32,4 6.8,4 C 5.2488,4 4,5.2488 4,6.8 L 4,32 32,32 Z"/>
<path style="fill:#21d789" d="M 32,4 57.2,4 C 58.7512,4 60,5.2488 60,6.8 L 60,32 32,32 Z"/>
<path style="fill:#2086e7" d="M 32,60 6.8,60 C 5.2488,60 4,58.7512 4,57.2 L 4,32 32,32 Z"/>
<path style="fill:#6655e4" d="m 32,60 25.2,0 C 58.7512,60 60,58.7512 60,57.2 L 60,32 32,32 Z"/>
<rect style="fill:#3f3f3f" width="40" height="40" x="12" y="12"/>
<path style="opacity:0.2;fill:none;stroke:#000000;stroke-width:4;stroke-linecap:round" d="m 18.028485,46.9998 15.999999,0"/>
<path style="fill:none;stroke:#ffffff;stroke-width:4;stroke-linecap:round" d="m 18.028485,46 15.999999,0"/>
<path style="opacity:0.2;fill:none;stroke:#000000;stroke-width:3.99999976;stroke-linecap:round;stroke-linejoin:bevel" d="m 24.000002,28.9998 3.999996,0 M 26,28.9998 c 0,0 0.981808,6 -4.000002,6 -3.999996,0 -3.999996,-4.000002 -3.999996,-8.000004 0,-3.999996 0,-7.999998 3.999996,-7.999998 4.000002,0 3.984756,1.999998 3.984756,1.999998"/>
<path style="fill:none;stroke:#ffffff;stroke-width:3.99999976;stroke-linecap:round;stroke-linejoin:bevel" d="m 24.000002,28 3.999996,0 M 26,28 c 0,0 0.981808,6 -4.000002,6 -3.999996,0 -3.999996,-4.000002 -3.999996,-8.000004 0,-3.999996 0,-7.999998 3.999996,-7.999998 4.000002,0 3.984756,1.999998 3.984756,1.999998"/>
<path d="m 39,17 c -3.324,0 -6,2.676 -6,6 l 0,8 c 0,3.324 2.676,6 6,6 3.324,0 6,-2.676 6,-6 l 0,-8 c 0,-3.324 -2.676,-6 -6,-6 z m 0,4 c 1.108,0 2,0 2,3.597656 l 0,4.804688 C 41,33.000002 40.108,33 39,33 c -1.108,0 -2,2e-6 -2,-3.597656 l 0,-4.804688 C 37,21 37.892,21 39,21 Z" style="opacity:0.2;fill-rule:evenodd"/>
<path style="fill:#ffffff;fill-rule:evenodd" d="m 39,16 c -3.324,0 -6,2.676 -6,6 l 0,8 c 0,3.324 2.676,6 6,6 3.324,0 6,-2.676 6,-6 l 0,-8 c 0,-3.324 -2.676,-6 -6,-6 z m 0,4 c 1.108,0 2,0 2,3.597656 l 0,4.804688 C 41,32.000002 40.108,32 39,32 c -1.108,0 -2,2e-6 -2,-3.597656 l 0,-4.804688 C 37,20 37.892,20 39,20 Z"/>
<path style="opacity:0.2;fill:#ffffff" d="M 6.8007812 4 C 5.2495812 4 4 5.2495812 4 6.8007812 L 4 7.8007812 C 4 6.2495813 5.2495812 5 6.8007812 5 L 32 5 L 57.199219 5 C 58.750419 5 60 6.2495812 60 7.8007812 L 60 6.8007812 C 60 5.2495813 58.750419 4 57.199219 4 L 32 4 L 6.8007812 4 z"/>
</svg>

After

Width:  |  Height:  |  Size: 2.5 KiB

39
icons/icons.go Normal file
View File

@ -0,0 +1,39 @@
package icons
import _ "embed"
//go:embed androidstudio.svg
var AndroidStudio []byte
//go:embed cups.svg
var CUPS []byte
//go:embed docker.svg
var Docker []byte
//go:embed goland.svg
var GoLand []byte
//go:embed idea.svg
var IntellijIDEA []byte
//go:embed java.svg
var Java []byte
//go:embed mongo.svg
var MongoDB []byte
//go:embed phpstorm.svg
var PHPStorm []byte
//go:embed rider.svg
var Rider []byte
//go:embed vlc.svg
var VLC []byte
//go:embed webstorm.svg
var WebStorm []byte
//go:embed pycharm.svg
var PyCharm []byte

15
icons/idea.svg Normal file
View File

@ -0,0 +1,15 @@
<svg xmlns="http://www.w3.org/2000/svg" width="64" height="64" version="1">
<path style="opacity:0.2" d="M 6.8007812,61 C 5.2495812,61 4,59.750418 4,58.199218 v -2 C 4,57.750418 5.2495812,59 6.8007812,59 H 32 57.199218 C 58.750418,59 60,57.750418 60,56.199218 v 2 C 60,59.750418 58.750418,61 57.199218,61 H 32 Z"/>
<path style="fill:#fe4b71" d="M 32,4 H 6.8 C 5.2488,4 4,5.2488 4,6.8 V 32 h 28 z"/>
<path style="fill:#2086e7" d="M 32,4 H 57.2 C 58.7512,4 60,5.2488 60,6.8 V 32 H 32 Z"/>
<path style="fill:#ff8c2a" d="M 32,60 H 6.8 C 5.2488,60 4,58.7512 4,57.2 V 32 h 28 z"/>
<path style="fill:#6655e4" d="M 32,60 H 57.2 C 58.7512,60 60,58.7512 60,57.2 V 32 H 32 Z"/>
<path style="opacity:0.2;fill:#ffffff" d="M 6.8007812 4 C 5.2495812 4 4 5.2495812 4 6.8007812 L 4 7.8007812 C 4 6.2495813 5.2495812 5 6.8007812 5 L 32 5 L 57.199219 5 C 58.750419 5 60 6.2495812 60 7.8007812 L 60 6.8007812 C 60 5.2495813 58.750419 4 57.199219 4 L 32 4 L 6.8007812 4 z"/>
<rect style="fill:#3f3f3f" width="40" height="40" x="12" y="12"/>
<path style="opacity:0.2;fill:none;stroke:#000000;stroke-width:4;stroke-linecap:round" d="M 18.000487,46.9998 H 34.000486"/>
<path style="fill:none;stroke:#ffffff;stroke-width:4;stroke-linecap:round" d="M 18.000487,46 H 34.000486"/>
<path style="opacity:0.2;fill:none;stroke:#000000;stroke-width:3.99999976;stroke-linecap:round" d="M 18.000485,34.999516 H 26.00048 M 18.000485,18.999511 H 26.00048 M 22.000486,34.999516 V 18.999511"/>
<path style="fill:none;stroke:#ffffff;stroke-width:3.99999976;stroke-linecap:round" d="M 18.000485,33.999716 H 26.00048 M 18.000485,17.999711 H 26.00048 M 22.000486,33.999716 V 17.999711"/>
<path style="opacity:0.2;fill:none;stroke:#000000;stroke-width:3.99999976;stroke-linecap:round;stroke-linejoin:round" d="m 34.000488,32.99951 v 2.000006 h 1.999998 c 7.831664,-0.230098 5.998382,-13.775978 6,-16.000005"/>
<path style="fill:none;stroke:#ffffff;stroke-width:3.99999976;stroke-linecap:round;stroke-linejoin:round" d="m 34.000488,31.99971 v 2.000006 h 1.999998 c 7.831664,-0.230098 5.998382,-13.775978 6,-16.000005"/>
</svg>

After

Width:  |  Height:  |  Size: 2.0 KiB

25
icons/java.svg Normal file
View File

@ -0,0 +1,25 @@
<svg xmlns="http://www.w3.org/2000/svg" width="64" height="64" version="1">
<path style="opacity:0.2" d="m 4.0545956,32.001976 c -0.011739,0.334698 -0.050778,0.662275 -0.050778,0.999932 C 4.0038171,48.512851 16.490966,61 32.001909,61 47.512852,61 60.000001,48.512851 60.000001,33.001908 c 0,-0.337657 -0.03904,-0.665234 -0.05078,-0.999932 -0.527964,15.040975 -12.775129,26.99816 -27.948095,26.99816 -15.173366,0 -27.4201313,-11.957185 -27.9480953,-26.99816 z"/>
<rect style="fill:#e74c3c" width="55.996" height="55.996" x="-60" y="-60" rx="27.998" ry="27.998" transform="matrix(0,-1,-1,0,0,0)"/>
<g style="clip-rule:evenodd;opacity:0.2;fill-rule:evenodd;enable-background:new" transform="matrix(0.6500557,0,0,0.68491332,83.36459,-4.0233684)">
<path d="m -86.682,70.021 c 0,0 -2.2303,1.2982 1.5891,1.7362 4.6278,0.52836 6.9927,0.45227 12.091,-0.51144 0,0 1.3431,0.84021 3.2156,1.5679 -11.433,4.8982 -25.875,-0.28382 -16.896,-2.7927 z"/>
<path d="m -88.08,63.628 c 0,0 -2.5019,1.8524 1.3204,2.2474 4.944,0.5104 8.8468,0.55226 15.604,-0.74823 0,0 0.93244,0.94717 2.4009,1.4648 -13.821,4.0426 -29.214,0.31805 -19.326,-2.964 z"/>
<path d="m -76.304,52.781 c 2.8185,3.2444 -0.73906,6.1616 -0.73906,6.1616 0,0 7.1525,-3.6917 3.8684,-8.3168 -3.0684,-4.3112 -5.4208,-6.4528 7.3147,-13.839 1.29e-4,0 -19.992,4.9918 -10.444,15.994 z"/>
<path d="m -61.183,74.75 c 0,0 1.6511,1.3611 -1.8185,2.4137 -6.5975,1.9983 -27.465,2.6011 -33.261,0.07996 -2.082,-0.90686 1.8242,-2.1638 3.0532,-2.4293 1.2815,-0.27723 2.0132,-0.22659 2.0132,-0.22659 -2.3172,-1.6317 -14.977,3.2046 -6.4297,4.5917 23.308,3.7781 42.488,-1.7017 36.442,-4.4294 z"/>
<path d="m -85.609,57.003 c 0,0 -10.613,2.5215 -3.7582,3.4379 2.8952,0.38742 8.6634,0.29828 14.04,-0.15282 4.393,-0.36869 8.8016,-1.1572 8.8016,-1.1572 0,0 -1.5479,0.664 -2.6685,1.4288 -10.78,2.8352 -31.6,1.5148 -25.605,-1.3838 5.0675,-2.4515 9.1898,-2.1729 9.1898,-2.1729 z"/>
<path d="m -66.569,67.644 c 10.957,-5.6922 5.8904,-11.163 2.3541,-10.426 -0.86475,0.18047 -1.2527,0.33678 -1.2527,0.33678 0,0 0.32218,-0.50472 0.93567,-0.72187 6.9959,-2.4584 12.375,7.2531 -2.256,11.099 -1.3e-4,0 0.1682,-0.15244 0.21896,-0.28808 z"/>
<path d="m -73.174,24.859 c 0,0 6.0668,6.0703 -5.756,15.402 -9.4811,7.4886 -2.1617,11.757 -0.0034,16.636 -5.5348,-4.9933 -9.5945,-9.3895 -6.8715,-13.48 3.9987,-6.0043 15.078,-8.9163 12.631,-18.558 z"/>
<path d="m -84.532,83.074 c 10.515,0.67227 26.667,-0.37411 27.049,-5.3501 0,0 -0.73479,1.8863 -8.6909,3.383 -8.9758,1.6898 -20.049,1.4931 -26.614,0.40912 0,0 1.3449,1.1132 8.2557,1.5579 z"/>
</g>
<g style="clip-rule:evenodd;fill:#ffffff;fill-rule:evenodd;enable-background:new" transform="matrix(0.6500557,0,0,0.68491332,83.36459,-5.0230321)">
<path d="m -86.682,70.021 c 0,0 -2.2303,1.2982 1.5891,1.7362 4.6278,0.52836 6.9927,0.45227 12.091,-0.51144 0,0 1.3431,0.84021 3.2156,1.5679 -11.433,4.8982 -25.875,-0.28382 -16.896,-2.7927 z"/>
<path d="m -88.08,63.628 c 0,0 -2.5019,1.8524 1.3204,2.2474 4.944,0.5104 8.8468,0.55226 15.604,-0.74823 0,0 0.93244,0.94717 2.4009,1.4648 -13.821,4.0426 -29.214,0.31805 -19.326,-2.964 z"/>
<path d="m -76.304,52.781 c 2.8185,3.2444 -0.73906,6.1616 -0.73906,6.1616 0,0 7.1525,-3.6917 3.8684,-8.3168 -3.0684,-4.3112 -5.4208,-6.4528 7.3147,-13.839 1.29e-4,0 -19.992,4.9918 -10.444,15.994 z"/>
<path d="m -61.183,74.75 c 0,0 1.6511,1.3611 -1.8185,2.4137 -6.5975,1.9983 -27.465,2.6011 -33.261,0.07996 -2.082,-0.90686 1.8242,-2.1638 3.0532,-2.4293 1.2815,-0.27723 2.0132,-0.22659 2.0132,-0.22659 -2.3172,-1.6317 -14.977,3.2046 -6.4297,4.5917 23.308,3.7781 42.488,-1.7017 36.442,-4.4294 z"/>
<path d="m -85.609,57.003 c 0,0 -10.613,2.5215 -3.7582,3.4379 2.8952,0.38742 8.6634,0.29828 14.04,-0.15282 4.393,-0.36869 8.8016,-1.1572 8.8016,-1.1572 0,0 -1.5479,0.664 -2.6685,1.4288 -10.78,2.8352 -31.6,1.5148 -25.605,-1.3838 5.0675,-2.4515 9.1898,-2.1729 9.1898,-2.1729 z"/>
<path d="m -66.569,67.644 c 10.957,-5.6922 5.8904,-11.163 2.3541,-10.426 -0.86475,0.18047 -1.2527,0.33678 -1.2527,0.33678 0,0 0.32218,-0.50472 0.93567,-0.72187 6.9959,-2.4584 12.375,7.2531 -2.256,11.099 -1.3e-4,0 0.1682,-0.15244 0.21896,-0.28808 z"/>
<path d="m -73.174,24.859 c 0,0 6.0668,6.0703 -5.756,15.402 -9.4811,7.4886 -2.1617,11.757 -0.0034,16.636 -5.5348,-4.9933 -9.5945,-9.3895 -6.8715,-13.48 3.9987,-6.0043 15.078,-8.9163 12.631,-18.558 z"/>
<path d="m -84.532,83.074 c 10.515,0.67227 26.667,-0.37411 27.049,-5.3501 0,0 -0.73479,1.8863 -8.6909,3.383 -8.9758,1.6898 -20.049,1.4931 -26.614,0.40912 0,0 1.3449,1.1132 8.2557,1.5579 z"/>
</g>
<path style="opacity:0.2;fill:#ffffff" d="M 31.998047 4.0039062 C 16.487104 4.0039062 4 16.491011 4 32.001953 C 4 32.115501 4.0112849 32.225993 4.0175781 32.337891 C 4.3690973 17.137171 16.709816 5.0039062 31.998047 5.0039062 C 47.284977 5.0039062 59.626938 17.13508 59.980469 32.333984 C 59.986725 32.223394 59.996094 32.114162 59.996094 32.001953 C 59.996094 16.491011 47.50899 4.0039062 31.998047 4.0039062 z"/>
</svg>

After

Width:  |  Height:  |  Size: 4.8 KiB

1
icons/mongo.svg Normal file
View File

@ -0,0 +1 @@
<svg role="img" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><title>MongoDB</title><path fill="#4da53f" d="M17.193 9.555c-1.264-5.58-4.252-7.414-4.573-8.115-.28-.394-.53-.954-.735-1.44-.036.495-.055.685-.523 1.184-.723.566-4.438 3.682-4.74 10.02-.282 5.912 4.27 9.435 4.888 9.884l.07.05A73.49 73.49 0 0111.91 24h.481c.114-1.032.284-2.056.51-3.07.417-.296.604-.463.85-.693a11.342 11.342 0 003.639-8.464c.01-.814-.103-1.662-.197-2.218zm-5.336 8.195s0-8.291.275-8.29c.213 0 .49 10.695.49 10.695-.381-.045-.765-1.76-.765-2.405z"/></svg>

After

Width:  |  Height:  |  Size: 542 B

15
icons/phpstorm.svg Normal file
View File

@ -0,0 +1,15 @@
<svg xmlns="http://www.w3.org/2000/svg" width="64" height="64" version="1">
<path style="opacity:0.2" d="M 6.8007812,61 C 5.2495812,61 4,59.750418 4,58.199218 v -2 C 4,57.750418 5.2495812,59 6.8007812,59 H 32 57.199218 C 58.750418,59 60,57.750418 60,56.199218 v 2 C 60,59.750418 58.750418,61 57.199218,61 H 32 Z"/>
<path style="fill:#cf4f8c" d="M 32,4 H 6.8 C 5.2488,4 4,5.2488 4,6.8 V 32 h 28 z"/>
<path style="fill:#6655e4" d="M 32,4 H 57.2 C 58.7512,4 60,5.2488 60,6.8 V 32 H 32 Z"/>
<path style="fill:#6655e4" d="M 32,60 H 6.8 C 5.2488,60 4,58.7512 4,57.2 V 32 h 28 z"/>
<path style="fill:#cf4f8c" d="M 32,60 H 57.2 C 58.7512,60 60,58.7512 60,57.2 V 32 H 32 Z"/>
<path style="opacity:0.2;fill:#ffffff" d="M 6.8007812 4 C 5.2495812 4 4 5.2495812 4 6.8007812 L 4 7.8007812 C 4 6.2495813 5.2495812 5 6.8007812 5 L 32 5 L 57.199219 5 C 58.750419 5 60 6.2495812 60 7.8007812 L 60 6.8007812 C 60 5.2495813 58.750419 4 57.199219 4 L 32 4 L 6.8007812 4 z"/>
<rect style="fill:#3f3f3f" width="40" height="40" x="12" y="12"/>
<path style="opacity:0.2;fill:none;stroke:#000000;stroke-width:4;stroke-linecap:round" d="M 18.000485,46.9998 H 34.000484"/>
<path style="fill:none;stroke:#ffffff;stroke-width:4;stroke-linecap:round" d="M 18.000485,46 H 34.000484"/>
<path style="opacity:0.2;fill:none;stroke:#000000;stroke-width:3.99999976;stroke-linecap:round;stroke-linejoin:bevel" d="m 18.000485,26.999314 c 0,0 8.000005,0.929002 8.000005,-3.999994 0,-4.929003 -8.000005,-4.000003 -8.000005,-4.000003 m 1.98e-4,16.000199 V 18.999513"/>
<path style="fill:none;stroke:#ffffff;stroke-width:3.99999976;stroke-linecap:round;stroke-linejoin:bevel" d="m 18.000485,25.999514 c 0,0 8.000005,0.929002 8.000005,-3.999994 0,-4.929003 -8.000005,-4.000003 -8.000005,-4.000003 m 1.98e-4,16.000199 V 17.999713"/>
<path style="opacity:0.2;fill:none;stroke:#000000;stroke-width:3.99999976;stroke-linecap:round;stroke-linejoin:round" d="m 34.000484,32.999314 c 0,0 1.00877,2.001934 4.000002,2.000966 2.991224,-9.6e-4 3.999994,-2.000966 3.999994,-4.000962 0,-8.42877 -7.999996,0.422324 -7.976034,-7.98141 -0.02418,-2.018594 0.943732,-4.018591 3.97604,-4.018591 3.0323,0 3.999768,1.999997 3.999768,1.999997"/>
<path style="fill:none;stroke:#ffffff;stroke-width:3.99999976;stroke-linecap:round;stroke-linejoin:round" d="m 34.000484,31.999514 c 0,0 1.00877,2.001934 4.000002,2.000966 2.991224,-9.6e-4 3.999994,-2.000966 3.999994,-4.000962 0,-8.42877 -7.999996,0.422324 -7.976034,-7.98141 -0.02418,-2.018595 0.943732,-4.018591 3.97604,-4.018591 3.0323,0 3.999768,1.999996 3.999768,1.999996"/>
</svg>

After

Width:  |  Height:  |  Size: 2.5 KiB

15
icons/pycharm.svg Normal file
View File

@ -0,0 +1,15 @@
<svg xmlns="http://www.w3.org/2000/svg" width="64" height="64" version="1">
<path style="opacity:0.2" d="M 6.8007812,61 C 5.2495812,61 4,59.750418 4,58.199218 v -2 C 4,57.750418 5.2495812,59 6.8007812,59 H 32 57.199218 C 58.750418,59 60,57.750418 60,56.199218 v 2 C 60,59.750418 58.750418,61 57.199218,61 H 32 Z"/>
<path style="fill:#a2e36e" d="M 32,4 H 6.8 C 5.2488,4 4,5.2488 4,6.8 V 32 h 28 z"/>
<path style="fill:#2cbfeb" d="M 32,4 H 57.2 C 58.7512,4 60,5.2488 60,6.8 V 32 H 32 Z"/>
<path style="fill:#21d789" d="M 32,60 H 6.8 C 5.2488,60 4,58.7512 4,57.2 V 32 h 28 z"/>
<path style="fill:#f7c73c" d="M 32,60 H 57.2 C 58.7512,60 60,58.7512 60,57.2 V 32 H 32 Z"/>
<path style="opacity:0.2;fill:#ffffff" d="M 6.8007812 4 C 5.2495812 4 4 5.2495812 4 6.8007812 L 4 7.8007812 C 4 6.2495813 5.2495812 5 6.8007812 5 L 32 5 L 57.199219 5 C 58.750419 5 60 6.2495812 60 7.8007812 L 60 6.8007812 C 60 5.2495813 58.750419 4 57.199219 4 L 32 4 L 6.8007812 4 z"/>
<rect style="fill:#3f3f3f" width="40" height="40" x="12" y="12"/>
<path style="opacity:0.2;fill:none;stroke:#000000;stroke-width:4;stroke-linecap:round" d="M 18.000485,46.9998 H 34.000484"/>
<path style="fill:none;stroke:#ffffff;stroke-width:4;stroke-linecap:round" d="M 18.000485,46 H 34.000484"/>
<path style="opacity:0.2;fill:none;stroke:#000000;stroke-width:3.99999976;stroke-linecap:round;stroke-linejoin:bevel" d="m 18.000485,26.999314 c 0,0 8.000005,0.929002 8.000005,-3.999994 0,-4.929003 -8.000005,-4.000003 -8.000005,-4.000003 m 1.96e-4,16.000199 V 18.999513"/>
<path style="fill:none;stroke:#ffffff;stroke-width:3.99999976;stroke-linecap:round;stroke-linejoin:bevel" d="m 18.000485,25.999514 c 0,0 8.000005,0.929002 8.000005,-3.999994 0,-4.929003 -8.000005,-4.000003 -8.000005,-4.000003 m 1.96e-4,16.000199 V 17.999713"/>
<path style="opacity:0.2;fill:none;stroke:#000000;stroke-width:3.99999976;stroke-linecap:round;stroke-linejoin:bevel" d="m 42.000484,32.999314 c 0,0 0,2.000004 -4.000002,2.000004 -3.999998,0 -3.999998,-4.000002 -3.999998,-8.000004 0,-3.999996 0,-7.999999 3.999998,-7.999999 4.000002,0 4.000002,1.999999 4.000002,1.999999"/>
<path style="fill:none;stroke:#ffffff;stroke-width:3.99999976;stroke-linecap:round;stroke-linejoin:bevel" d="m 42.000484,31.999514 c 0,0 0,2.000004 -4.000002,2.000004 -3.999998,0 -3.999998,-4.000002 -3.999998,-8.000004 0,-3.999996 0,-7.999999 3.999998,-7.999999 4.000002,0 4.000002,1.999998 4.000002,1.999998"/>
</svg>

After

Width:  |  Height:  |  Size: 2.4 KiB

15
icons/rider.svg Normal file
View File

@ -0,0 +1,15 @@
<svg xmlns="http://www.w3.org/2000/svg" width="64" height="64" version="1">
<rect style="opacity:0.2" width="56" height="56" x="4" y="5" rx="2.6" ry="2.6"/>
<path style="fill:#c72566" d="M 32,4 H 6.8 C 5.2488,4 4,5.2488 4,6.8 V 32 H 32 Z"/>
<path style="fill:#6655e4" d="M 32,4 H 57.2 C 58.751,4 60,5.2488 60,6.8 V 32 H 32 Z"/>
<path style="fill:#ff8c2a" d="M 32,60 H 6.8 C 5.2488,60 4,58.751 4,57.2 V 32 H 32 Z"/>
<path style="fill:#c72566" d="M 32,60 H 57.2 C 58.751,60 60,58.751 60,57.2 V 32 H 32 Z"/>
<rect style="fill:#3f3f3f" width="40" height="40" x="12" y="12"/>
<path style="opacity:0.2;fill:none;stroke:#000000;stroke-width:4;stroke-linecap:round" d="M 18,47 H 34"/>
<path style="fill:none;stroke:#ffffff;stroke-width:4;stroke-linecap:round" d="M 18,46 H 34"/>
<path style="fill:none;stroke:#000000;stroke-width:4;stroke-linecap:round;stroke-linejoin:bevel;opacity:0.2" d="M 34,35 C 34,35 42.088,35.7 42,27 41.913,18.299 34,19 34,19 M 34,35 V 19"/>
<path style="fill:none;stroke:#ffffff;stroke-width:4;stroke-linecap:round;stroke-linejoin:bevel" d="M 34,34 C 34,34 42.088,34.7 42,26 41.913,17.299 34,18 34,18 M 34,34 V 18"/>
<path style="opacity:0.2;fill:none;stroke:#000000;stroke-width:4;stroke-linecap:round" d="M 26,35 22,29 M 18,27 C 18,27 26,27.929 26,23 26,18.071 18,19 18,19 M 18,35 V 19"/>
<path style="fill:none;stroke:#ffffff;stroke-width:4;stroke-linecap:round" d="M 26,34 22,28 M 18,26 C 18,26 26,26.929 26,22 26,17.071 18,18 18,18 M 18,34 V 18"/>
<path style="opacity:0.2;paint-order:markers stroke fill;fill:#ffffff" d="M 6.5996 4 C 5.1592 4 4 5.1592 4 6.5996 L 4 7.5996 C 4 6.1592 5.1592 5 6.5996 5 L 57.4 5 C 58.841 5 60 6.1592 60 7.5996 L 60 6.5996 C 60 5.1592 58.841 4 57.4 4 L 6.5996 4 z"/>
</svg>

After

Width:  |  Height:  |  Size: 1.7 KiB

8
icons/vlc.svg Normal file
View File

@ -0,0 +1,8 @@
<svg xmlns="http://www.w3.org/2000/svg" width="64" height="64" version="1">
<path style="fill:#ff8800;fill-rule:evenodd" d="m 13.801454,38.00012 c -1.129914,2.8e-4 -2.148988,0.678902 -2.583786,1.720703 L 4.2183064,56.127091 C 3.4499506,57.969165 4.8043832,59.9994 6.8020924,60 L 57.197819,60 C 59.195508,59.99938 60.55,57.969145 59.781605,56.127091 L 52.782243,39.720823 C 52.347425,38.679022 51.328411,38.0004 50.198457,38.00012 Z"/>
<path style="opacity:0.2;fill:#ffffff;fill-rule:evenodd" d="m 13.797406,38 c -1.130087,2.8e-4 -2.147273,0.679405 -2.582137,1.720771 L 4.2149822,56.117906 c -0.2195779,0.52612 -0.2619132,1.06724 -0.1679757,1.572328 0.035224,-0.19104 0.088891,-0.382798 0.1679757,-0.572288 L 11.215269,40.720811 c 0.434864,-1.041366 1.45205,-1.720491 2.582137,-1.720771 l 36.403832,0 c 1.130066,2.8e-4 2.147253,0.679405 2.582137,1.720771 l 7.000286,16.397135 c 0.08042,0.19272 0.132885,0.387783 0.167976,0.582054 0.09631,-0.507825 0.05299,-1.052568 -0.167976,-1.582094 L 52.783375,39.720771 C 52.348491,38.679405 51.331304,38.00028 50.201238,38 l -36.403832,0 z"/>
<path style="fill:#ff8800;fill-rule:evenodd" d="m 32.001848,4.0009564 c -1.271753,-0.014 -2.568586,0.2017389 -3.849579,0.7164301 -0.793816,0.3192903 -1.398092,0.9840546 -1.640491,1.8047142 l -2.575586,8.7064403 c 4.073378,8.760846 12.262333,2.870677 16.120512,0 L 37.481118,6.5221007 C 37.22426,5.6534554 36.563163,4.9627492 35.706668,4.6681608 34.520194,4.259641 33.273681,4.0151763 32.001888,4.0009564 Z M 20.62391,26.429122 17.516727,36.930135 c 1.005834,8.913263 16.661109,7.050882 28.855843,-0.357494 L 43.371786,26.430262 c -7.699358,3.062497 -10.822141,-5.870968 -22.747876,0 z"/>
<path style="opacity:0.2;fill-rule:evenodd" d="M 4.0444692,57.72 C 3.7449348,59.378608 4.9920126,61 6.7784617,61 l 50.4430393,0 c 1.786576,-5.61e-4 3.033667,-1.620273 2.733992,-3.28 -0.236418,1.253896 -1.313542,2.281717 -2.733992,2.282163 l -50.4430393,0 c -1.4203064,0 -2.4974809,-1.028614 -2.7339925,-2.282163 z"/>
<path style="fill:#ffffff;fill-rule:evenodd" d="m 23.93712,15.400254 -6.42021,21.579825 3.107998,-10.441787 c 8.574749,3.828987 15.048764,3.045803 22.749458,0 l 3.001192,10.087493 c -12.18678,7.342472 -25.841655,2.112626 -28.857848,0.354094 l -2.202741,7.400359 c -0.341482,1.149354 0.09473,2.387349 1.082869,3.073256 4.315276,2.992689 9.919435,4.198001 15.497993,4.144887 5.578557,-0.05312 11.174315,-1.372015 15.569197,-4.05517 1.078488,-0.658972 1.578541,-1.955811 1.219597,-3.162973 L 40.060074,15.400254 c -3.860849,2.784807 -12.261985,2.784807 -16.122834,0 z"/>
<path style="opacity:0.2;fill:#ffffff" d="M 31.767578 4 C 28.795544 4 26.820312 4.9030976 26.820312 6.0957031 C 26.820312 6.2564108 26.871956 6.4205143 26.964844 6.5839844 C 27.45082 5.656355 29.242274 5 31.767578 5 C 34.27554 5 36.540735 5.8209478 37.193359 6.7929688 C 37.322024 6.6335481 37.400391 6.4815515 37.400391 6.3417969 C 37.400351 5.1492334 34.73961 4 31.767578 4 z"/>
</svg>

After

Width:  |  Height:  |  Size: 2.9 KiB

15
icons/webstorm.svg Normal file
View File

@ -0,0 +1,15 @@
<svg xmlns="http://www.w3.org/2000/svg" width="64" height="64" version="1">
<path style="opacity:0.2" d="M 6.8007812,61 C 5.2495812,61 4,59.750418 4,58.199218 v -2 C 4,57.750418 5.2495812,59 6.8007812,59 H 32 57.199218 C 58.750418,59 60,57.750418 60,56.199218 v 2 C 60,59.750418 58.750418,61 57.199218,61 H 32 Z"/>
<path style="fill:#14ddb1" d="M 32,4 H 6.8 C 5.2488,4 4,5.2488 4,6.8 V 32 h 28 z"/>
<path style="fill:#f7c73c" d="M 32,4 H 57.2 C 58.7512,4 60,5.2488 60,6.8 V 32 H 32 Z"/>
<path style="fill:#2086e7" d="M 32,60 H 6.8 C 5.2488,60 4,58.7512 4,57.2 V 32 h 28 z"/>
<path style="fill:#14ddb1" d="M 32,60 H 57.2 C 58.7512,60 60,58.7512 60,57.2 V 32 H 32 Z"/>
<path style="opacity:0.2;fill:#ffffff" d="M 6.8007812 4 C 5.2495812 4 4 5.2495812 4 6.8007812 L 4 7.8007812 C 4 6.2495813 5.2495812 5 6.8007812 5 L 32 5 L 57.199219 5 C 58.750419 5 60 6.2495812 60 7.8007812 L 60 6.8007812 C 60 5.2495813 58.750419 4 57.199219 4 L 32 4 L 6.8007812 4 z"/>
<rect style="fill:#3f3f3f" width="40" height="40" x="12" y="12"/>
<path style="opacity:0.2;fill:none;stroke:#000000;stroke-width:4;stroke-linecap:round" d="M 16,46.9998 H 32"/>
<path style="fill:none;stroke:#ffffff;stroke-width:4;stroke-linecap:round" d="M 16,46 H 32"/>
<path style="opacity:0.2;fill:none;stroke:#000000;stroke-width:3.99999976;stroke-linecap:round;stroke-linejoin:bevel" d="m 31.999996,19.000204 -6e-6,12.000002 c 0,3.029358 -2.087896,4.049596 -3.999996,4.000002 -1.79192,0.0072 -4.000002,-0.932524 -4.000002,-4.000002 L 24,19.000204 m -8e-6,0 -8e-6,12.000002 c 0,3.029358 -2.087894,4.049596 -3.999994,4.000002 -1.79192,0.0072 -4.000004,-0.932524 -4.000004,-4.000002 l 8e-6,-12.000002"/>
<path style="fill:none;stroke:#ffffff;stroke-width:3.99999976;stroke-linecap:round;stroke-linejoin:bevel" d="m 31.999996,18.000404 -6e-6,12.000002 c 0,3.029358 -2.087896,4.049596 -3.999996,4.000002 -1.79192,0.0072 -4.000002,-0.932524 -4.000002,-4.000002 L 24,18.000404 m -8e-6,0 -8e-6,12.000002 c 0,3.029358 -2.087894,4.049596 -3.999994,4.000002 -1.79192,0.0072 -4.000004,-0.932524 -4.000004,-4.000002 l 8e-6,-12.000002"/>
<path style="opacity:0.2;fill:none;stroke:#000000;stroke-width:3.99999976;stroke-linecap:round;stroke-linejoin:round" d="m 40.00023,33.000204 c 0,0 1.008772,2.001932 4.000002,2.000964 2.991224,-9.6e-4 3.999996,-2.000964 3.999996,-4.000962 0,-8.42877 -7.999998,0.422324 -7.976036,-7.98141 -0.0242,-2.018592 0.943734,-4.01859 3.97604,-4.01859 3.0323,0 3.999768,1.999998 3.999768,1.999998"/>
<path style="fill:none;stroke:#ffffff;stroke-width:3.99999976;stroke-linecap:round;stroke-linejoin:round" d="m 40.00023,32.000404 c 0,0 1.008772,2.001932 4.000002,2.000964 2.991224,-9.6e-4 3.999996,-2.000964 3.999996,-4.000962 0,-8.42877 -7.999998,0.422324 -7.976036,-7.98141 -0.0242,-2.018592 0.943734,-4.01859 3.97604,-4.01859 3.0323,0 3.999768,1.999998 3.999768,1.999998"/>
</svg>

After

Width:  |  Height:  |  Size: 2.8 KiB

View File

@ -4,19 +4,24 @@ import (
"context"
"errors"
"fmt"
"github.com/adampresley/gofavigrab/parser"
"github.com/cakturk/go-netstat/netstat"
"github.com/rs/zerolog/log"
"github.com/shirou/gopsutil/v3/process"
"gogs.mikescher.com/BlackForestBytes/goext/cryptext"
"gogs.mikescher.com/BlackForestBytes/goext/ginext"
"gogs.mikescher.com/BlackForestBytes/goext/langext"
"gogs.mikescher.com/BlackForestBytes/goext/rext"
"gogs.mikescher.com/BlackForestBytes/goext/rfctime"
"gogs.mikescher.com/BlackForestBytes/goext/syncext"
"io"
bunny "locbunny"
"locbunny/icons"
"locbunny/models"
"locbunny/webassets"
"net"
"net/http"
"net/url"
"os"
"os/signal"
"regexp"
@ -43,6 +48,9 @@ type Application struct {
cacheLock sync.Mutex
serverCacheValue []models.Server
serverCacheTime *time.Time
iconCache map[string]models.Icon
iconCacheLock sync.Mutex
}
func NewApp(ass *webassets.Assets) *Application {
@ -51,6 +59,7 @@ func NewApp(ass *webassets.Assets) *Application {
Assets: ass,
stopChan: make(chan bool),
IsRunning: syncext.NewAtomicBool(false),
iconCache: make(map[string]models.Icon, 1024),
}
}
@ -258,29 +267,13 @@ func (app *Application) verifyHTTPConn(sock netstat.SockTabEntry, proto string,
return models.Server{}, errors.New("skip self")
}
c := http.Client{}
url := fmt.Sprintf("%s://localhost:%d", strings.ToLower(proto), port)
req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)
resbody, header, statuscode, err := app.doRequest(ctx, proto, port, "")
if err != nil {
log.Debug().Msg(fmt.Sprintf("Failed to create [%s|%s|%d] request to %d (-> %s)", strings.ToUpper(proto), ipversion, port, port, err.Error()))
log.Debug().Msg(fmt.Sprintf("Failed to [%s|%s|%d] request to %d (-> %s)", strings.ToUpper(proto), ipversion, port, port, err.Error()))
return models.Server{}, err
}
resp1, err := c.Do(req)
if err != nil {
log.Debug().Msg(fmt.Sprintf("Failed to send [%s|%s|%d] request to %s (-> %s)", strings.ToUpper(proto), ipversion, port, url, err.Error()))
return models.Server{}, err
}
defer func() { _ = resp1.Body.Close() }()
resbody, err := io.ReadAll(resp1.Body)
if err != nil {
log.Debug().Msg(fmt.Sprintf("Failed to read [%s|%s|%d] response from %s (-> %s)", strings.ToUpper(proto), ipversion, port, url, err.Error()))
return models.Server{}, err
}
ct := resp1.Header.Get("Content-Type")
ct := header.Get("Content-Type")
if ct != "" {
var pnm *string = nil
@ -292,12 +285,21 @@ func (app *Application) verifyHTTPConn(sock netstat.SockTabEntry, proto string,
name := app.DetectName(sock, ct, string(resbody))
var iconRef *string = nil
iconData, iconCT := app.DetectIcon(sock, proto, port, name, string(resbody))
if iconData != nil && iconCT != "" {
cs := cryptext.StrSha256(cryptext.BytesSha256(iconData) + iconCT)
_, _ = app.StoreIcon(cs, iconData, iconCT)
iconRef = &cs
}
return models.Server{
Port: port,
IP: sock.LocalAddr.IP.String(),
Name: name,
Icon: iconRef,
Protocol: proto,
StatusCode: resp1.StatusCode,
StatusCode: statuscode,
Response: string(resbody),
ContentType: ct,
Process: pnm,
@ -307,11 +309,33 @@ func (app *Application) verifyHTTPConn(sock netstat.SockTabEntry, proto string,
}, nil
}
log.Debug().Msg(fmt.Sprintf("Failed to categorize [%s|%s|%d] response from %s (Content-Type: '%s')", strings.ToUpper(proto), ipversion, port, url, ct))
log.Debug().Msg(fmt.Sprintf("Failed to categorize [%s|%s|%d] response (Content-Type: '%s')", strings.ToUpper(proto), ipversion, port, ct))
return models.Server{}, errors.New("invalid response-type")
}
func (app *Application) doRequest(ctx context.Context, proto string, port int, path string) ([]byte, http.Header, int, error) {
c := http.Client{}
url := fmt.Sprintf("%s://localhost:%d"+path, strings.ToLower(proto), port)
req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)
if err != nil {
return nil, nil, 0, err
}
resp1, err := c.Do(req)
if err != nil {
return nil, nil, 0, err
}
defer func() { _ = resp1.Body.Close() }()
resbody, err := io.ReadAll(resp1.Body)
if err != nil {
return nil, nil, 0, err
}
return resbody, resp1.Header, resp1.StatusCode, nil
}
func (app *Application) DetectName(sock netstat.SockTabEntry, ct string, body string) string {
if strings.Contains(strings.ToLower(ct), "html") {
@ -353,6 +377,103 @@ func (app *Application) DetectName(sock netstat.SockTabEntry, ct string, body st
return "unknown"
}
func (app *Application) DetectIcon(sock netstat.SockTabEntry, proto string, port int, name string, body string) ([]byte, string) {
if strings.Contains(strings.ToLower(body), "it looks like you are trying to access mongodb over http on the native driver port.") {
return icons.MongoDB, "image/svg+xml"
}
if sock.Process != nil {
pname := strings.ToLower(strings.TrimSpace(sock.Process.Name))
if pname == "vlc" {
return icons.VLC, "image/svg+xml"
}
if pname == "cupsd" {
return icons.CUPS, "image/svg+xml"
}
if pname == "containerd" {
return icons.Docker, "image/svg+xml"
}
}
name = strings.ToLower(name)
if strings.HasPrefix(name, "goland") {
return icons.GoLand, "image/svg+xml"
}
if strings.HasPrefix(name, "phpstorm") {
return icons.PHPStorm, "image/svg+xml"
}
if strings.HasPrefix(name, "pycharm") {
return icons.PyCharm, "image/svg+xml"
}
if strings.HasPrefix(name, "webstorm") {
return icons.WebStorm, "image/svg+xml"
}
if strings.HasPrefix(name, "intellijidea") {
return icons.IntellijIDEA, "image/svg+xml"
}
if strings.HasPrefix(name, "rider") {
return icons.Rider, "image/svg+xml"
}
if strings.HasPrefix(name, "androidstudio") {
return icons.AndroidStudio, "image/svg+xml"
}
if favurlAbs, err := parser.NewHTMLParser(body).GetFaviconURL(); err == nil {
if parsedURL, err := url.Parse(favurlAbs); err == nil {
ctx, cancel := context.WithTimeout(context.Background(), 500*time.Millisecond)
defer cancel()
resbody, hdr, sc, err := app.doRequest(ctx, proto, port, parsedURL.EscapedPath())
if err == nil && sc >= 200 && sc < 300 && hdr.Get("Content-Type") != "" {
return resbody, hdr.Get("Content-Type")
}
}
}
{
ctx, cancel := context.WithTimeout(context.Background(), 500*time.Millisecond)
defer cancel()
resbody, _, sc, err := app.doRequest(ctx, proto, port, "/favicon.ico")
if err == nil && sc >= 200 && sc < 300 {
return resbody, "image/vnd.microsoft.icon"
}
}
{
ctx, cancel := context.WithTimeout(context.Background(), 500*time.Millisecond)
defer cancel()
resbody, _, sc, err := app.doRequest(ctx, proto, port, "/favicon.png")
if err == nil && sc >= 200 && sc < 300 {
return resbody, "image/png"
}
}
{
ctx, cancel := context.WithTimeout(context.Background(), 500*time.Millisecond)
defer cancel()
resbody, _, sc, err := app.doRequest(ctx, proto, port, "/favicon.jpeg")
if err == nil && sc >= 200 && sc < 300 {
return resbody, "image/jpeg"
}
}
{
ctx, cancel := context.WithTimeout(context.Background(), 500*time.Millisecond)
defer cancel()
resbody, _, sc, err := app.doRequest(ctx, proto, port, "/favicon.jpg")
if err == nil && sc >= 200 && sc < 300 {
return resbody, "image/jpeg"
}
}
if sock.Process != nil && sock.Process.Name == "java" {
return icons.Java, "image/svg+xml"
}
return nil, ""
}
func (app *Application) isInvalidHTMLTitle(title string) bool {
title = strings.ToLower(title)
title = strings.TrimSpace(title)
@ -423,3 +544,35 @@ func (app *Application) extractNameFromJava(cmdl []string) (string, bool) {
return "", false
}
func (app *Application) StoreIcon(cs string, data []byte, ct string) (models.Icon, bool) {
app.iconCacheLock.Lock()
defer app.iconCacheLock.Unlock()
if v, ok := app.iconCache[cs]; ok {
return v, false
}
v := models.Icon{
IconID: models.NewIconID(),
Checksum: cs,
Data: data,
ContentType: ct,
Time: rfctime.NowRFC3339Nano(),
}
app.iconCache[cs] = v
return v, true
}
func (app *Application) GetIcon(ctx *ginext.AppContext, cs string) *models.Icon {
app.iconCacheLock.Lock()
defer app.iconCacheLock.Unlock()
if v, ok := app.iconCache[cs]; ok {
return langext.Ptr(v)
}
return nil
}

13
models/icon.go Normal file
View File

@ -0,0 +1,13 @@
package models
import (
"gogs.mikescher.com/BlackForestBytes/goext/rfctime"
)
type Icon struct {
IconID IconID `bson:"_id,omitempty" json:"id"`
Checksum string `bson:"checksum" json:"checksum"`
Data []byte `bson:"data" json:"data"`
ContentType string `bson:"contentType" json:"contentType"`
Time rfctime.RFC3339NanoTime `bson:"time" json:"time"`
}

View File

@ -19,3 +19,5 @@ type AnyID string //@id:type
type JobLogID string //@id:type
type JobExecutionID string //@id:type
type IconID string //@id:type

View File

@ -7,7 +7,7 @@ import "go.mongodb.org/mongo-driver/bson/bsontype"
import "go.mongodb.org/mongo-driver/bson/primitive"
import "gogs.mikescher.com/BlackForestBytes/goext/exerr"
const ChecksumIDGenerator = "c6ecd0c3665e4ed6d1316ccf2899ba29a28d9d06b6c6b97a86adc19c1343787e" // GoExtVersion: 0.0.288
const ChecksumIDGenerator = "ce868e40b2314fd3d5484f0c1366d2580d188535e6d7400b16977f76c4c159a6" // GoExtVersion: 0.0.288
// ================================ AnyID (ids.go) ================================
@ -101,3 +101,34 @@ func (i JobExecutionID) AsAny() AnyID {
func NewJobExecutionID() JobExecutionID {
return JobExecutionID(primitive.NewObjectID().Hex())
}
// ================================ IconID (ids.go) ================================
func (i IconID) MarshalBSONValue() (bsontype.Type, []byte, error) {
if objId, err := primitive.ObjectIDFromHex(string(i)); err == nil {
return bson.MarshalValue(objId)
} else {
return 0, nil, exerr.New(exerr.TypeMarshalEntityID, "Failed to marshal IconID("+i.String()+") to ObjectId").Str("value", string(i)).Type("type", i).Build()
}
}
func (i IconID) String() string {
return string(i)
}
func (i IconID) ObjID() (primitive.ObjectID, error) {
return primitive.ObjectIDFromHex(string(i))
}
func (i IconID) Valid() bool {
_, err := primitive.ObjectIDFromHex(string(i))
return err == nil
}
func (i IconID) AsAny() AnyID {
return AnyID(i)
}
func NewIconID() IconID {
return IconID(primitive.NewObjectID().Hex())
}

View File

@ -12,4 +12,5 @@ type Server struct {
UID uint32 `json:"uid"`
SockState string `json:"sockState"`
Name string `json:"name"`
Icon *string `json:"icon"`
}

View File

@ -175,6 +175,21 @@
}
}
},
"/icon/:cs": {
"get": {
"summary": "Get Icon",
"parameters": [
{
"type": "number",
"description": "Icon Checksum",
"name": "cs",
"in": "path",
"required": true
}
],
"responses": {}
}
},
"/index.html": {
"get": {
"summary": "(Website)",
@ -304,6 +319,9 @@
"contentType": {
"type": "string"
},
"icon": {
"type": "string"
},
"ip": {
"type": "string"
},

View File

@ -58,6 +58,8 @@ definitions:
properties:
contentType:
type: string
icon:
type: string
ip:
type: string
name:
@ -193,6 +195,16 @@ paths:
summary: Return 200 after x seconds
tags:
- Common
/icon/:cs:
get:
parameters:
- description: Icon Checksum
in: path
name: cs
required: true
type: number
responses: {}
summary: Get Icon
/index.html:
get:
responses: {}

View File

@ -18,7 +18,7 @@ body {
main {
display: grid;
grid-template-columns: auto 1fr auto;
grid-template-columns: 20px 50px 1fr 50px 20px;
grid-column-gap: 1rem;
grid-row-gap: 1rem;
@ -30,6 +30,14 @@ h1 {
text-shadow: 0 0 8px #888;
}
.header {
grid-column: 2/5;
display: grid;
grid-template-columns: auto 1fr auto;
grid-column-gap: 1rem;
}
.loader_left,
.loader_right{
display: flex;
@ -38,14 +46,39 @@ h1 {
align-items: center;
}
.loader_left img {
width: 24px;
height: 24px;
margin-bottom: 4px;
cursor: pointer;
transition: transform 0.15s ease-in-out,
opacity 0.10s ease-in-out;
opacity: 0;
}
.loader_left img:hover {
transform: scale(1.2, 1.2);
opacity: 1.0;
}
.header:hover .loader_left img {
opacity: 0.5;
}
.header:hover .loader_left img:hover {
opacity: 1.0;
}
#maincontent {
display: flex;
flex-direction: column;
gap: 0.5rem;
padding: 0 2rem;
grid-column: 2;
grid-column: 1/-1;
}
.server {
@ -71,10 +104,18 @@ h1 {
display: flex;
justify-content: center;
align-items: center;
text-align: left;
}
.server .txt_icon {
text-align: left;
.server .txt_icon img {
width: 16px;
height: 16px;
object-fit: contain;
}
.server:not(:hover) .txt_icon img {
filter: grayscale(1)
}
.server .txt_port {

View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" height="16" width="16" viewBox="0 0 512 512"><!--!Font Awesome Free 6.5.1 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2023 Fonticons, Inc.--><path d="M105.1 202.6c7.7-21.8 20.2-42.3 37.8-59.8c62.5-62.5 163.8-62.5 226.3 0L386.3 160H352c-17.7 0-32 14.3-32 32s14.3 32 32 32H463.5c0 0 0 0 0 0h.4c17.7 0 32-14.3 32-32V80c0-17.7-14.3-32-32-32s-32 14.3-32 32v35.2L414.4 97.6c-87.5-87.5-229.3-87.5-316.8 0C73.2 122 55.6 150.7 44.8 181.4c-5.9 16.7 2.9 34.9 19.5 40.8s34.9-2.9 40.8-19.5zM39 289.3c-5 1.5-9.8 4.2-13.7 8.2c-4 4-6.7 8.8-8.1 14c-.3 1.2-.6 2.5-.8 3.8c-.3 1.7-.4 3.4-.4 5.1V432c0 17.7 14.3 32 32 32s32-14.3 32-32V396.9l17.6 17.5 0 0c87.5 87.4 229.3 87.4 316.7 0c24.4-24.4 42.1-53.1 52.9-83.7c5.9-16.7-2.9-34.9-19.5-40.8s-34.9 2.9-40.8 19.5c-7.7 21.8-20.2 42.3-37.8 59.8c-62.5 62.5-163.8 62.5-226.3 0l-.1-.1L125.6 352H160c17.7 0 32-14.3 32-32s-14.3-32-32-32H48.4c-1.6 0-3.2 .1-4.8 .3s-3.1 .5-4.6 1z"/></svg>

After

Width:  |  Height:  |  Size: 1002 B

View File

@ -30,11 +30,11 @@
<main>
<span class="loader_left"></span>
<h1>LocalHostBunny</h1>
<span class="loader_right">
<span id="loader" class="loader hidden"></span>
</span>
<div class="header">
<span class="loader_left" onclick="autoReload()"><img alt="reload" src="/icons/reload.svg"></span>
<h1>LocalHostBunny</h1>
<span class="loader_right"><span id="loader" class="loader hidden"></span></span>
</div>
<div id="maincontent"></div>

View File

@ -7,6 +7,8 @@ function now() { return (new Date()).getTime(); }
function enc(v) { return `${v}`.replace(/[\u00A0-\u9999<>&]/g, i => '&#'+i.charCodeAt(0)+';') }
function sleep(ms) { return new Promise(resolve => setTimeout(resolve, ms)); }
function updateHTML(servers) {
const main = document.getElementById('maincontent')
@ -16,21 +18,30 @@ function updateHTML(servers) {
for (const srv of servers) {
html += `<a class="server" href="${srv['protocol'].toLowerCase()}://localhost:${srv['port']}" target="_blank">`;
html += `<span class="txt_icon"><img src="/icons/microchip-sharp-solid.svg" alt="process"></span>`
if (srv['icon'] === null) {
html += `<span class="txt_icon"><img src="/icons/microchip-sharp-solid.svg" alt="icon"></span>`
} else {
html += `<span class="txt_icon"><img src="/api/v1/icon/${srv['icon']}" alt="icon"></span>`
}
html += `<span class="txt_name">${enc(srv['name'])}</span>`
html += `<span class="txt_port">${enc(srv['port'])}</span>`
html += `</a>`;
}
main.innerHTML = html;
}
function onVisibilityChange() {
if (!document.hidden && (now() - last_refresh) > refresh_delay) autoReload();
console.log('[I] Visibility changed to ' + document.hidden)
if (!document.hidden && (now() - last_refresh) > refresh_delay) {
sleep(300).then(async () => await autoReload());
}
}
async function autoReload() {
console.log('[I] AutoReload')
try {
document.getElementById('loader').classList.remove('hidden');