launcher: application launcher
Change-Id: I81d49a0651702dc821d683d6a4b3bbff6af3c753
diff --git a/core/installer/.gitignore b/core/installer/.gitignore
index ef7fc43..04fadb1 100644
--- a/core/installer/.gitignore
+++ b/core/installer/.gitignore
@@ -1 +1 @@
-pcloud
\ No newline at end of file
+pcloud
diff --git a/core/installer/Makefile b/core/installer/Makefile
index 89a5715..5280a89 100644
--- a/core/installer/Makefile
+++ b/core/installer/Makefile
@@ -46,6 +46,9 @@
rewrite:
./pcloud rewrite --ssh-key=/Users/lekva/.ssh/id_ed25519 --repo-addr=ssh://localhost:2222/config
+launcher:
+ ./pcloud launcher --port=9090 --logout-url=http://localhost:8080
+
## installer image
build_arm64: export CGO_ENABLED=0
build_arm64: export GO111MODULE=on
diff --git a/core/installer/cmd/launcher.go b/core/installer/cmd/launcher.go
new file mode 100644
index 0000000..d86196b
--- /dev/null
+++ b/core/installer/cmd/launcher.go
@@ -0,0 +1,46 @@
+package main
+
+import (
+ "fmt"
+
+ "github.com/giolekva/pcloud/core/installer/welcome"
+ "github.com/spf13/cobra"
+)
+
+var launcherFlags struct {
+ logoutUrl string
+ port int
+}
+
+func launcherCmd() *cobra.Command {
+ cmd := &cobra.Command{
+ Use: "launcher",
+ RunE: launcherCmdRun,
+ }
+ cmd.Flags().IntVar(
+ &launcherFlags.port,
+ "port",
+ 8080,
+ "",
+ )
+ cmd.Flags().StringVar(
+ &launcherFlags.logoutUrl,
+ "logout-url",
+ "",
+ "",
+ )
+ return cmd
+}
+
+func launcherCmdRun(cmd *cobra.Command, args []string) error {
+ s, err := welcome.NewLauncherServer(
+ launcherFlags.port,
+ launcherFlags.logoutUrl,
+ &welcome.FakeAppDirectory{},
+ )
+ if err != nil {
+ return fmt.Errorf("failed to create LauncherServer: %v", err)
+ }
+ s.Start()
+ return nil
+}
diff --git a/core/installer/cmd/main.go b/core/installer/cmd/main.go
index 1e4ec1d..5b05381 100644
--- a/core/installer/cmd/main.go
+++ b/core/installer/cmd/main.go
@@ -27,6 +27,7 @@
rootCmd.AddCommand(envManagerCmd())
rootCmd.AddCommand(welcomeCmd())
rootCmd.AddCommand(rewriteCmd())
+ rootCmd.AddCommand(launcherCmd())
}
func main() {
diff --git a/core/installer/go.mod b/core/installer/go.mod
index dc4d4e4..8e173f2 100644
--- a/core/installer/go.mod
+++ b/core/installer/go.mod
@@ -21,7 +21,7 @@
github.com/libdns/libdns v0.2.2
github.com/miekg/dns v1.1.58
github.com/spf13/cobra v1.8.0
- golang.org/x/crypto v0.21.0
+ golang.org/x/crypto v0.22.0
golang.org/x/exp v0.0.0-20240325151524-a685a6edb6d8
helm.sh/helm/v3 v3.14.3
k8s.io/api v0.29.3
@@ -134,7 +134,7 @@
github.com/rubenv/sql-migrate v1.6.1 // indirect
github.com/russross/blackfriday/v2 v2.1.0 // indirect
github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 // indirect
- github.com/shopspring/decimal v1.3.1 // indirect
+ github.com/shopspring/decimal v1.4.0 // indirect
github.com/sirupsen/logrus v1.9.3 // indirect
github.com/skeema/knownhosts v1.2.2 // indirect
github.com/spf13/cast v1.6.0 // indirect
@@ -155,8 +155,8 @@
golang.org/x/net v0.23.0 // indirect
golang.org/x/oauth2 v0.18.0 // indirect
golang.org/x/sync v0.6.0 // indirect
- golang.org/x/sys v0.18.0 // indirect
- golang.org/x/term v0.18.0 // indirect
+ golang.org/x/sys v0.19.0 // indirect
+ golang.org/x/term v0.19.0 // indirect
golang.org/x/text v0.14.0 // indirect
golang.org/x/time v0.5.0 // indirect
golang.org/x/tools v0.19.0 // indirect
diff --git a/core/installer/go.sum b/core/installer/go.sum
index 49b5466..097b1ca 100644
--- a/core/installer/go.sum
+++ b/core/installer/go.sum
@@ -370,8 +370,8 @@
github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 h1:n661drycOFuPLCN3Uc8sB6B/s6Z4t2xvBgU1htSHuq8=
github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3/go.mod h1:A0bzQcvG0E7Rwjx0REVgAGH58e96+X0MeOfepqsbeW4=
github.com/shopspring/decimal v1.2.0/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o=
-github.com/shopspring/decimal v1.3.1 h1:2Usl1nmF/WZucqkFZhnfFYxxxu8LG21F6nPQBE5gKV8=
-github.com/shopspring/decimal v1.3.1/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o=
+github.com/shopspring/decimal v1.4.0 h1:bxl37RwXBklmTi0C79JfXCEBD1cqqHt0bbgBAGFp81k=
+github.com/shopspring/decimal v1.4.0/go.mod h1:gawqmDU56v4yIKSwfBSFip1HdCCXN8/+DMd9qYNcwME=
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
@@ -444,8 +444,8 @@
golang.org/x/crypto v0.3.0/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4=
golang.org/x/crypto v0.3.1-0.20221117191849-2c476679df9a/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4=
golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU=
-golang.org/x/crypto v0.21.0 h1:X31++rzVUdKhX5sWmSOFZxx8UW/ldWx55cbf08iNAMA=
-golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs=
+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/exp v0.0.0-20240325151524-a685a6edb6d8 h1:aAcj0Da7eBAtrTp03QXWvm88pSyOt+UgdZw2BFZ+lEw=
golang.org/x/exp v0.0.0-20240325151524-a685a6edb6d8/go.mod h1:CQ1k9gNrJ50XIzaKCRR2hssIjF07kZFEiieALBM/ARQ=
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
@@ -499,15 +499,15 @@
golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4=
-golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
+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/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc=
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U=
-golang.org/x/term v0.18.0 h1:FcHjZXDMxI8mM3nwhX9HlKop4C0YQvCVCdwYl2wOtE8=
-golang.org/x/term v0.18.0/go.mod h1:ILwASektA3OnRv7amZ1xhE/KTR+u50pbXfZ03+6Nx58=
+golang.org/x/term v0.19.0 h1:+ThwsDv+tYfnJFhF4L8jITxu1tdTWRTZpdsWgEgjL6Q=
+golang.org/x/term v0.19.0/go.mod h1:2CuTdWZ7KHSQwUzKva0cbMg6q2DMI3Mmxp+gKJbskEk=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
diff --git a/core/installer/values-tmpl/launcher.cue b/core/installer/values-tmpl/launcher.cue
new file mode 100644
index 0000000..39328ba
--- /dev/null
+++ b/core/installer/values-tmpl/launcher.cue
@@ -0,0 +1,64 @@
+input: {
+ authGroups: string
+}
+
+_subdomain: "launcher"
+_domain: "\(_subdomain).\(global.privateDomain)"
+
+name: "launcher"
+namespace: "core-installer-welcome-launcher"
+readme: "App Launcher application will be installed on Private or Public network and be accessible at https://\(_domain)"
+description: "The application is a App launcher, designed to run all accessible applications. Can be configured to be reachable only from private network or publicly."
+icon: "<svg xmlns='http://www.w3.org/2000/svg' width='50' height='50' viewBox='0 0 24 24'><path fill='currentColor' d='M15.43 15.48c-1.1-.49-2.26-.73-3.43-.73c-1.18 0-2.33.25-3.43.73c-.23.1-.4.29-.49.52h7.85a.978.978 0 0 0-.5-.52m-2.49-6.69C12.86 8.33 12.47 8 12 8s-.86.33-.94.79l-.2 1.21h2.28z' opacity='0.3'/><path fill='currentColor' d='M10.27 12h3.46a1.5 1.5 0 0 0 1.48-1.75l-.3-1.79a2.951 2.951 0 0 0-5.82.01l-.3 1.79c-.15.91.55 1.74 1.48 1.74m.79-3.21c.08-.46.47-.79.94-.79s.86.33.94.79l.2 1.21h-2.28zm-9.4 2.32c-.13.26-.18.57-.1.88c.16.69.76 1.03 1.53 1h1.95c.83 0 1.51-.58 1.51-1.29c0-.14-.03-.27-.07-.4c-.01-.03-.01-.05.01-.08c.09-.16.14-.34.14-.53c0-.31-.14-.6-.36-.82c-.03-.03-.03-.06-.02-.1c.07-.2.07-.43.01-.65a1.12 1.12 0 0 0-.99-.74a.09.09 0 0 1-.07-.03C5.03 8.14 4.72 8 4.37 8c-.3 0-.57.1-.75.26c-.03.03-.06.03-.09.02a1.24 1.24 0 0 0-1.7 1.03c0 .02-.01.04-.03.06c-.29.26-.46.65-.41 1.05c.03.22.12.43.25.6c.03.02.03.06.02.09m14.58 2.54c-1.17-.52-2.61-.9-4.24-.9c-1.63 0-3.07.39-4.24.9A2.988 2.988 0 0 0 6 16.39V18h12v-1.61c0-1.18-.68-2.26-1.76-2.74M8.07 16a.96.96 0 0 1 .49-.52c1.1-.49 2.26-.73 3.43-.73c1.18 0 2.33.25 3.43.73c.23.1.4.29.49.52zm-6.85-1.42A2.01 2.01 0 0 0 0 16.43V18h4.5v-1.61c0-.83.23-1.61.63-2.29c-.37-.06-.74-.1-1.13-.1c-.99 0-1.93.21-2.78.58m21.56 0A6.95 6.95 0 0 0 20 14c-.39 0-.76.04-1.13.1c.4.68.63 1.46.63 2.29V18H24v-1.57c0-.81-.48-1.53-1.22-1.85M22 11v-.5c0-1.1-.9-2-2-2h-2c-.42 0-.65.48-.39.81l.7.63c-.19.31-.31.67-.31 1.06c0 1.1.9 2 2 2s2-.9 2-2'/></svg>"
+
+_httpPortName: "http"
+
+ingress: {
+ launcher: {
+ auth: {
+ enabled: true
+ groups: input.authGroups
+ }
+ network: networks.private
+ subdomain: _subdomain
+ service: {
+ name: "launcher"
+ port: name: _httpPortName
+ }
+ }
+}
+
+images: {
+ launcher: {
+ repository: "giolekva"
+ name: "launcher"
+ tag: "latest"
+ pullPolicy: "Always"
+ }
+}
+
+charts: {
+ launcher: {
+ chart: "charts/launcher"
+ sourceRef: {
+ kind: "GitRepository"
+ name: "pcloud"
+ namespace: global.id
+ }
+ }
+}
+
+helm: {
+ launcher: {
+ chart: charts.launcher
+ values: {
+ image: {
+ repository: images.launcher.fullName
+ tag: images.launcher.tag
+ pullPolicy: images.launcher.pullPolicy
+ }
+ portName: _httpPortName
+ logoutUrl: "https://accounts-ui.\(global.domain)/logout"
+ }
+ }
+}
diff --git a/core/installer/welcome/fake_app_directory.go b/core/installer/welcome/fake_app_directory.go
new file mode 100644
index 0000000..a8470d5
--- /dev/null
+++ b/core/installer/welcome/fake_app_directory.go
@@ -0,0 +1,112 @@
+package welcome
+
+type FakeAppDirectory struct {
+}
+
+func (s *FakeAppDirectory) GetAllApps() ([]AppLauncherInfo, error) {
+ // TODO(dtabidze): use repoIO to get actual info
+ googleInfo := AppLauncherInfo{
+ Name: "Memberships",
+ Description: "Memberships Description",
+ Icon: "<svg xmlns='http://www.w3.org/2000/svg' width='50' height='50' viewBox='0 0 24 24'><path fill='currentColor' d='M15.43 15.48c-1.1-.49-2.26-.73-3.43-.73c-1.18 0-2.33.25-3.43.73c-.23.1-.4.29-.49.52h7.85a.978.978 0 0 0-.5-.52m-2.49-6.69C12.86 8.33 12.47 8 12 8s-.86.33-.94.79l-.2 1.21h2.28z' opacity='0.3'/><path fill='currentColor' d='M10.27 12h3.46a1.5 1.5 0 0 0 1.48-1.75l-.3-1.79a2.951 2.951 0 0 0-5.82.01l-.3 1.79c-.15.91.55 1.74 1.48 1.74m.79-3.21c.08-.46.47-.79.94-.79s.86.33.94.79l.2 1.21h-2.28zm-9.4 2.32c-.13.26-.18.57-.1.88c.16.69.76 1.03 1.53 1h1.95c.83 0 1.51-.58 1.51-1.29c0-.14-.03-.27-.07-.4c-.01-.03-.01-.05.01-.08c.09-.16.14-.34.14-.53c0-.31-.14-.6-.36-.82c-.03-.03-.03-.06-.02-.1c.07-.2.07-.43.01-.65a1.12 1.12 0 0 0-.99-.74a.09.09 0 0 1-.07-.03C5.03 8.14 4.72 8 4.37 8c-.3 0-.57.1-.75.26c-.03.03-.06.03-.09.02a1.24 1.24 0 0 0-1.7 1.03c0 .02-.01.04-.03.06c-.29.26-.46.65-.41 1.05c.03.22.12.43.25.6c.03.02.03.06.02.09m14.58 2.54c-1.17-.52-2.61-.9-4.24-.9c-1.63 0-3.07.39-4.24.9A2.988 2.988 0 0 0 6 16.39V18h12v-1.61c0-1.18-.68-2.26-1.76-2.74M8.07 16a.96.96 0 0 1 .49-.52c1.1-.49 2.26-.73 3.43-.73c1.18 0 2.33.25 3.43.73c.23.1.4.29.49.52zm-6.85-1.42A2.01 2.01 0 0 0 0 16.43V18h4.5v-1.61c0-.83.23-1.61.63-2.29c-.37-.06-.74-.1-1.13-.1c-.99 0-1.93.21-2.78.58m21.56 0A6.95 6.95 0 0 0 20 14c-.39 0-.76.04-1.13.1c.4.68.63 1.46.63 2.29V18H24v-1.57c0-.81-.48-1.53-1.22-1.85M22 11v-.5c0-1.1-.9-2-2-2h-2c-.42 0-.65.48-.39.81l.7.63c-.19.31-.31.67-.31 1.06c0 1.1.9 2 2 2s2-.9 2-2'/></svg>",
+ Help: []HelpDocument{
+ {
+ Title: "Memberships search",
+ Contents: `In the realm of coding, every keystroke is a brushstroke on the canvas of innovation. It's a domain where strings of characters dance to the rhythm of logic, constructing intricate digital symphonies. From the crisp lines of Python to the curly braces of JavaScript, each language tells a unique story, weaving together the fabric of our digital world.
+
+ In this labyrinth of code, programmers are modern-day alchemists, transforming abstract ideas into tangible realities. They navigate through the maze of syntax, debugging their creations with the precision of a surgeon. Errors are not failures but rather stepping stones on the path to mastery, each one offering a lesson in resilience and problem-solving.
+
+ In the realm of algorithms, complexity is the name of the game. Programmers orchestrate elegant solutions to complex problems, employing a myriad of data structures and algorithms to optimize performance and efficiency. From sorting algorithms that arrange data in perfect harmony to search algorithms that uncover hidden treasures within vast datasets, the possibilities are as boundless as the imagination itself.
+
+ But coding is not just about logic and structure; it's also a form of expression. Just as a poet wields words to evoke emotion, a coder crafts lines of code to breathe life into their creations. Whether it's a sleek user interface or a powerful backend system, every project is an opportunity to leave a mark on the digital landscape.
+
+ In the collaborative ecosystem of coding, communities flourish and ideas thrive. Open-source repositories serve as the collective knowledge base of humanity, where programmers from around the globe collaborate to build upon each other's work. It's a testament to the power of collective intelligence, where the sum is truly greater than its parts.
+
+ As we journey deeper into the digital age, the importance of coding becomes increasingly evident. It's not just a technical skill but a language of the future, a gateway to unlocking the full potential of technology. So let us embrace the beauty of code, for in its intricate tapestry lies the blueprint for tomorrow's innovations.`,
+ Children: []HelpDocument{
+ {
+ Title: "Nested Child 1",
+ Contents: "Nested Child 1 Content",
+ Children: nil,
+ },
+ {
+ Title: "Nested Child 2",
+ Contents: "Nested Child 2 Content",
+ Children: []HelpDocument{
+ {
+ Title: "Nested Child 2.1",
+ Contents: "Nested Child 2.1 Content",
+ Children: []HelpDocument{
+ {
+ Title: "Nested Child 2.1.1",
+ Contents: "Nested Child 2.1.1 Content",
+ Children: nil,
+ },
+ {
+ Title: "Nested Child 2.1.2",
+ Contents: "Nested Child 2.1.2 Content",
+ Children: nil,
+ },
+ },
+ },
+ {
+ Title: "Nested Child 2.2",
+ Contents: "Nested Child 2.2 Content",
+ Children: nil,
+ },
+ },
+ },
+ {
+ Title: "Nested Child 3",
+ Contents: "Nested Child 3 Content",
+ Children: nil,
+ },
+ },
+ },
+ {
+ Title: "Another Memberships search",
+ Contents: "Another Memberships Content",
+ Children: []HelpDocument{
+ {
+ Title: "Another Nested Child 1",
+ Contents: "Another Nested Child 1 Content",
+ Children: nil,
+ },
+ {
+ Title: "Another Nested Child 2",
+ Contents: "Another Nested Child 2 Content",
+ Children: nil,
+ },
+ },
+ },
+ },
+ Url: "https://memberships.p.v1.dodo.cloud",
+ }
+ gitInfo := AppLauncherInfo{
+ Name: "Gerrit",
+ Description: "Gerrit Description",
+ Icon: "<svg fill='#000000' width='50px' height='50px' viewBox='0 0 24 24' role='img' xmlns='http://www.w3.org/2000/svg'><g id='SVGRepo_bgCarrier' stroke-width='0'></g><g id='SVGRepo_tracerCarrier' stroke-linecap='round' stroke-linejoin='round'></g><g id='SVGRepo_iconCarrier'><title>Gerrit icon</title><path d='M12.648 2.678l-.245-.266c.004-.004.29-.268.413-.41.121-.146.342-.484.346-.486l.301.195c-.014.016-.234.359-.375.522-.137.165-.428.432-.44.445zm1.577 10.597c-.012-.004-.24-.154-.365-.221-.117-.059-.32-.146-.422-.191l.213-.612-.898-.444-.286.871c-.174.004-.713.053-1.51.389-.959.4-1.688 1.025-1.695 1.029l-.143.125.641.025.02-.016c.006-.006.721-.535 1.119-.705.088-.037.207-.074.33-.105-.209.105-.439.227-.6.32-.199.119-.57.381-.586.393l-.186.129.682.016.018-.01c.012-.008 1.164-.623 1.789-.76l.196-.047c.145-.037.246-.064.422-.064.156 0 .369.021.688.07.398.059.66.158.664.16l.24.094-.322-.436-.012-.008.003-.002zm-8.98-7.298h-.029l-1.006.916v.03c-.016.266.078.52.258.716.182.196.42.309.686.319h.043c.531 0 .965-.413.992-.94.023-.545-.399-1.01-.944-1.041zM5.2 7.808h-.041c-.117-.005-.23-.032-.33-.085.045.008.098.01.148.002.284-.035.481-.291.448-.575-.035-.279-.293-.48-.576-.442-.264.034-.457.267-.446.531-.031-.09-.045-.183-.045-.28l.912-.833c.446.042.79.431.768.882s-.391.8-.842.8H5.2zm9.329-3.725l-.293-.195-.41.265-.395-.285-.301.18.404.291-.416.27.297.18.4-.254.387.28.309-.169-.407-.296M24 19.525c-.213-.209-.418-.416-.629-.627-.48-.488-.957-.984-1.418-1.486-.547-.598-1.082-1.207-1.582-1.844-.225-.283-.441-.58-.646-.881-.254-.387-.469-.795-.668-1.215-.211-.445-.398-.9-.576-1.356.24.21.463.444.664.692.021-.215.041-.43.059-.648l.023-.322c0-.033.012-.066-.008-.096-.016-.029-.033-.051-.053-.075-.121-.149-.264-.282-.406-.413-.189-.181-.387-.36-.584-.533l-.18-.156c-.027-.027-.057-.051-.086-.074-.031-.03-.041-.096-.055-.136l-.154-.479c.607.285 1.109.741 1.578 1.215.004-.219.004-.436 0-.652 0-.114-.002-.229-.008-.346 0-.029.006-.1-.016-.127-.012-.02-.031-.036-.043-.055-.109-.117-.234-.217-.357-.314-.172-.143-.35-.277-.527-.408-.156-.117-.318-.232-.477-.345-.018-.016-.076-.039-.064-.06l.039-.08c.02-.035.014-.045.053-.029l.17.059c.406.156.779.368 1.143.608-.123-.45-.311-.885-.525-1.296-.389-.762-.893-1.468-1.463-2.105-.379-.426-.785-.824-1.219-1.193-.223-.19-.457-.369-.699-.533-.113-.074-.225-.149-.346-.217-.049-.03-.105-.056-.15-.094-.18-.144-.365-.284-.549-.429.238.098.469.21.689.338-.127-.194-.342-.331-.545-.436-.307-.159-.637-.276-.967-.378.113-.12.234-.228.346-.348.113-.12.223-.246.33-.372.127-.15.252-.3.375-.455l-1.156-.726-.48-.303c-.123.246-.277.476-.458.68-.18.203-.391.369-.6.539-.191.156-.379.316-.566.472-.047.04-.092.085-.145.12-.025.021-.096.004-.127.004H10.7c-.297.006-.596.029-.891.068-.058.007-.113.013-.175.008l-.178-.01c-.137-.008-.271-.016-.408-.016-.289-.004-.58 0-.871.025-.244.022-.489.055-.729.112-.238.056-.461.172-.66.312-.193.141-.387.32-.504.53s-.17.456-.213.689c-.135.013-.268.01-.4.016-.141.008-.277.021-.416.039-.27.037-.535.096-.795.18-.496.154-.945.405-1.336.75-.195.181-.377.38-.539.596-.02.025-.037.051-.063.068-.01.009-.027.016-.033.027-.015.027-.033.052-.051.076l-.134.212c-.168.285-.276.595-.383.906l-.045.123.033-.021c-.01.067-.02.135-.025.202l-.008.105v.053l-.066.013c-.135.026-.271.06-.4.101-.12.039-.233.086-.337.15-.105.066-.195.153-.285.239-.318.31-.562.698-.687 1.128-.1.348-.137.735-.059 1.092.021.09.049.188.107.262.074.09.199.126.313.095.131-.036.248-.124.371-.187l.314-.157c.455-.226.93-.446 1.438-.525.035-.005.1.067.131.091.049.037.1.074.148.104.109.069.221.129.334.181.262.114.541.174.818.231.502.105 1.014.171 1.529.18.207.004.416 0 .625-.018.225-.02.451-.049.678-.051.25-.005.496.025.74.055.254.031.51.068.764.105.518.079 1.031.169 1.543.279-.115.18-.227.362-.338.545-.008.013-.072 0-.088 0-.041-.002-.086-.002-.127 0-.074 0-.15.008-.225.019-.211.03-.416.083-.615.156-.49.181-.938.483-1.326.833-.221.195-.43.408-.609.641-.049.064-.094.129-.139.193.105-.023.211-.045.318-.07.061-.016.117-.027.176-.039.021-.008.029-.016.049-.027.219-.203.451-.393.695-.563.111-.08.225-.154.348-.215.166-.083.354-.131.533-.174-.33.18-.678.35-.977.584-.172.139-.328.291-.49.439l.654-.104c.01 0 .014-.004.023-.01l.17-.094c.113-.064.23-.125.346-.186.24-.123.482-.24.732-.34.236-.094.48-.178.732-.225.221-.046.428-.052.648 0 .342.074.67.232.969.414.021.014.035.033.055.014.016-.014.098-.074.09-.092l-.18-.334c-.016-.029-.023-.049-.051-.066l-.131-.077c-.176-.104-.348-.21-.51-.331-.037-.023-.078-.039-.082-.089-.004-.046.014-.093.029-.136.033-.09.08-.169.131-.248.033-.058.072-.111.109-.166.014-.018.01-.021.031-.014l.119.034c.236.077.469.159.711.22.27.068.545.123.82.176l.048.01c-.056-.03-.091-.09-.132-.136-.023-.03-.035-.05-.074-.06l-.105-.02c-.074-.016-.148-.03-.225-.049-.262-.057-.525-.119-.777-.209-.375-.13-.758-.232-1.145-.322-.486-.107-.977-.194-1.465-.275-.25-.041-.498-.074-.75-.109-.246-.034-.496-.07-.746-.087-.539-.034-1.074.087-1.615.081-.365-.004-.734-.055-1.096-.105l-.068-.009c.252-.093.5-.188.75-.285.236-.095.471-.192.705-.289.127-.052.25-.105.373-.157.113-.051.225-.1.328-.17.408-.279.676-.727.975-1.109.273-.361.586-.7.99-.908.129-.066.262-.117.4-.156-.283-.439-.549-.93-.623-1.455.029.012.061.025.094.037.035.012.064.016.072.045l.029.135c.026.091.053.176.086.262.072.191.16.375.26.551.219.396.484.766.766 1.12.539.686 1.145 1.305 1.736 1.941.145.158.275.324.41.492.15.187.303.375.457.561.121.141.234.285.356.426l.094.112c.016.018.037.026.061.036.604.3 1.213.6 1.822.895.434.209.869.42 1.309.623.236.109.471.219.711.32.029.014.066.029.098.039.016.01.029.016.047.023l.016.049c.027.072.051.145.076.217.049.141.098.279.148.416.33.912.695 1.814 1.145 2.676.346.656.715 1.301 1.09 1.939.4.68.811 1.354 1.225 2.025.332.535.666 1.072 1.008 1.605.146.227.289.459.439.688l.063.094.775-1.141.191-.283c.008-.006.082-.102.076-.109l-.156-.24c-.291-.451-.584-.898-.871-1.35l-.828-1.283-.105-.166.656.799 1.115 1.35c.121.146.236.289.354.438.219-.402.439-.801.662-1.201.086-.164.176-.33.266-.492M12.13 1.915c.309-.246.563-.563.757-.906l1.178.743.18.112c-.246.3-.496.604-.775.873-.035.034-.074.069-.107.104-.021.025-.047.051-.07.075l-.033.038c-.023-.007-.045-.016-.068-.02-.146-.041-.289-.08-.436-.111-.292-.066-.589-.117-.886-.152-.191-.021-.385-.04-.578-.058.275-.234.557-.467.838-.698m-1.754 1.37c.232-.096.482-.15.73-.191.49-.082.994-.094 1.489-.04.354.038.701.108 1.041.21l-.141.115-.225-.061c-.141-.029-.281-.06-.422-.082-.246-.037-.497-.06-.749-.065-.461-.008-.926.042-1.371.159-.301.078-.604.188-.879.332-.286.154-.555.346-.78.582-.134.137-.257.289-.359.455-.057.09-.105.18-.15.275-.024.047-.043.096-.061.144l-.029.075c-.004.015-.008.026-.012.038L8.39 5.22l-.082-.011c.234-.719.763-1.286 1.418-1.649.207-.114.426-.218.65-.279m-3.93.374c.266-.381.686-.649 1.139-.743.502-.101 1.016-.119 1.525-.107-.525.131-1.05.326-1.488.652-.34.075-.664.24-.934.459-.09.071-.17.15-.246.236-.035.037-.066.075-.097.116-.014.015-.027.03-.037.049h-.084c.023-.135.055-.27.098-.4.029-.09.064-.18.119-.26m.882.056c-.145.143-.27.303-.391.469-.047.069-.098.153-.18.191-.085.039-.194-.005-.28-.031.224-.279.521-.494.851-.629M2.308 6.585c.08-.16.182-.315.275-.465.016-.027.053-.046.074-.063.049-.034.096-.069.143-.106.309-.225.621-.446.939-.656.166-.107.33-.213.502-.307.182-.101.369-.18.565-.244.347-.112.707-.189 1.068-.244.174-.025.361-.057.531-.015-.33.188-.658.375-.99.558-.342.191-.689.367-1.035.552-.332.18-.66.375-.981.577-.336.206-.67.419-1.002.629-.08.051-.16.105-.24.155.045-.125.09-.251.151-.371m1.009 3.056c-.029.056-.134.042-.187.042-.094 0-.186 0-.279.006-.258.015-.51.06-.758.138-.475.147-.922.375-1.365.604-.1.055-.203.135-.316.162-.061.015-.131-.008-.166-.063-.035-.061-.053-.131-.064-.198-.027-.12-.035-.245-.033-.368 0-.33.076-.66.215-.96.096-.21.221-.401.371-.574.15-.175.326-.351.539-.45.355-.162.775-.213 1.164-.235.072-.005.145-.007.217-.007.1 0 .217-.016.311.019.074.027.121.105.15.173.045.105.074.225.1.336.061.26.123.525.145.791.01.119.016.239.004.359-.009.073-.014.156-.05.223m2.829-1.973c-.194.123-.403.218-.616.298-.215.077-.436.144-.66.181-.097.013-.195.025-.293.021-.101-.003-.193-.036-.293-.067-.195-.063-.393-.13-.57-.233-.064-.039-.135-.084-.174-.15-.029-.052-.029-.116-.023-.174.012-.231.094-.458.203-.66.187-.343.482-.612.84-.775.879-.396 1.865-.029 2.611.49l.08.061c-.154.165-.311.321-.473.477-.199.189-.404.381-.634.531M8.31 5.739c-.475-.203-.926-.458-1.356-.738.449.188.934.3 1.414.37.258.037.521.072.781.08.281.008.564-.021.84-.075.545-.103 1.068-.305 1.566-.551.494-.245.964-.537 1.413-.859.217-.155.43-.315.633-.487.021-.016.174-.161.184-.154l.041.031.537.416c.328.254.658.51.988.762-.906.326-1.826.629-2.752.904-.519.156-1.038.301-1.565.42-.412.098-.834.189-1.256.21-.507.022-1.006-.135-1.47-.33m8.85 3.942c.076.021.145.045.215.067l.094.033c.016.006.031.015.045.02l.021.06c.045.146.09.289.139.432-.15-.127-.301-.254-.451-.379l-.09-.074c-.021-.016-.045-.021-.029-.046l.059-.114m-.671 1.444l.035-.063.027-.046c.012-.018.008-.022.029-.012.129.054.258.111.385.17.24.11.475.23.703.364.107.065.217.135.322.205l.15.105.074.057c.033.027.041.063.057.102.104.282.219.564.338.844.078.189.162.379.248.566-.293-.371-.621-.715-.957-1.045-.346-.346-.705-.671-1.078-.981l-.323-.264m3.746 6.42l-.121.09.008.016.063.094.271.42.904 1.402c.311.48.621.963.932 1.445l.309.48.084.133c.004.004.029.041.029.045-.26.385-.523.77-.783 1.154-.027.037-.051.076-.074.111-.24-.373-.479-.744-.715-1.117-.4-.635-.795-1.277-1.184-1.916-.434-.709-.855-1.418-1.264-2.141-.383-.674-.75-1.361-1.059-2.076-.301-.697-.563-1.408-.811-2.121.486.193.98.367 1.48.521.145.045.289.09.436.127l.063.018c.008 0 .014-.039.018-.049.018-.064.031-.129.045-.195.031-.125.051-.254.074-.381.205.428.436.844.701 1.236.215.314.445.621.686.92.521.656 1.074 1.283 1.643 1.898.463.494.934.984 1.408 1.465l.389.389c.006.006.039.031.037.041l-.031.053-.148.275-.588 1.068c-.18-.219-.361-.436-.541-.658l-1.125-1.361c-.314-.387-.637-.773-.953-1.16l-.186-.225'></path></g></svg>",
+ Help: []HelpDocument{
+ {
+ Title: "Gerrit Doc",
+ Contents: "Some random Gerrit help.",
+ Children: nil,
+ },
+ },
+ Url: "https://code.v1.dodo.cloud/dashboard/self",
+ }
+ forumge := AppLauncherInfo{
+ Name: "Forum",
+ Description: "Forum Description",
+ Icon: "<svg xmlns='http://www.w3.org/2000/svg' width='50' height='50' viewBox='0 0 256 256'><path fill='currentColor' d='M100 140a8 8 0 1 1-8-8a8 8 0 0 1 8 8Zm64 8a8 8 0 1 0-8-8a8 8 0 0 0 8 8Zm64.94-9.11a12.12 12.12 0 0 1-5 1.11a11.83 11.83 0 0 1-9.35-4.62l-2.59-3.29V184a36 36 0 0 1-36 36H80a36 36 0 0 1-36-36v-51.91l-2.53 3.27A11.88 11.88 0 0 1 32.1 140a12.08 12.08 0 0 1-5-1.11a11.82 11.82 0 0 1-6.84-13.14l16.42-88a12 12 0 0 1 14.7-9.43h.16L104.58 44h46.84l53.08-15.6h.16a12 12 0 0 1 14.7 9.43l16.42 88a11.81 11.81 0 0 1-6.84 13.06ZM97.25 50.18L49.34 36.1a4.18 4.18 0 0 0-.92-.1a4 4 0 0 0-3.92 3.26l-16.42 88a4 4 0 0 0 7.08 3.22ZM204 121.75L150 52h-44l-54 69.75V184a28 28 0 0 0 28 28h44v-18.34l-14.83-14.83a4 4 0 0 1 5.66-5.66L128 186.34l13.17-13.17a4 4 0 0 1 5.66 5.66L132 193.66V212h44a28 28 0 0 0 28-28Zm23.92 5.48l-16.42-88a4 4 0 0 0-4.84-3.16l-47.91 14.11l62.11 80.28a4 4 0 0 0 7.06-3.23Z'/></svg>",
+ Help: []HelpDocument{
+ {
+ Title: "Forum Doc",
+ Contents: "Some random Forum help.",
+ Children: nil,
+ },
+ },
+ Url: "https://forum.ge",
+ }
+ return []AppLauncherInfo{googleInfo, gitInfo, forumge}, nil
+}
diff --git a/core/installer/welcome/launcher-tmpl/launcher.html b/core/installer/welcome/launcher-tmpl/launcher.html
new file mode 100644
index 0000000..3511064
--- /dev/null
+++ b/core/installer/welcome/launcher-tmpl/launcher.html
@@ -0,0 +1,75 @@
+<!DOCTYPE html>
+<html lang="en" data-theme="light">
+<head>
+ <meta charset="UTF-8">
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
+ <title>App Launcher</title>
+ <link rel="stylesheet" type="text/css" href="/static/pico.2.0.6.min.css">
+ <link rel="stylesheet" type="text/css" href="/static/launcher.css">
+</head>
+<body class="container-fluid">
+ <div id="left-panel">
+ <div class="user-circle">
+ <div class="circle">
+ <p>{{ GetUserInitials .LoggedInUsername }}</p>
+ <div class="tooltip-user" id="tooltip-user">
+ <p>{{ .LoggedInUsername }}</p>
+ <button id="logout-button">Log out</button>
+ </div>
+ </div>
+ </div>
+ <hr class="separator">
+ <div class="app-list">
+ {{range .AllAppsInfo}}
+ <div class="app-icon-tooltip" data-app-url="{{ .Url }}">
+ <div class="icon">
+ {{.Icon}}
+ </div>
+ <div class="tooltip">
+ <p>{{ .Name }}</p>
+ <button class="help-button" id="help-button-{{ CleanAppName .Name }}">Help</button>
+ </div>
+ </div>
+ <dialog class="app-help-modal" id="modal-{{ CleanAppName .Name }}" close>
+ <article class="modal-article">
+ <header>
+ <h4>{{ .Name }}</h4>
+ <button class="close-button" id="close-help-{{ CleanAppName .Name }}">
+ <svg xmlns="http://www.w3.org/2000/svg" width="1.5em" height="1.5em" viewBox="0 0 32 32"><path fill="black" d="M16 2C8.2 2 2 8.2 2 16s6.2 14 14 14s14-6.2 14-14S23.8 2 16 2m5.4 21L16 17.6L10.6 23L9 21.4l5.4-5.4L9 10.6L10.6 9l5.4 5.4L21.4 9l1.6 1.6l-5.4 5.4l5.4 5.4z"/></svg>
+ </button>
+ </header>
+ <div class="app-help-modal-article">
+ <div class="modal-left">
+ {{ template "help-menu-template" .Help }}
+ </div>
+ <div class="modal-right" id="modal-right-help-content-{{ CleanAppName .Name }}">
+ {{ template "help-content-template" .Help }}
+ </div>
+ </div>
+ </article>
+ </dialog>
+ {{end}}
+ </div>
+ </div>
+ <div id="right-panel">
+ <iframe id="appFrame" width="100%" height="100%" frameborder="0"></iframe>
+ </div>
+ {{ define "help-menu-template" }}
+ <ul class="ul">
+ {{ range .}}
+ <li>
+ <a class="title-menu" id="title-{{ CleanAppName .Title }}">{{ .Title }}</a>
+ {{ template "help-menu-template" .Children }}
+ </li>
+ {{ end }}
+ </ul>
+ {{ end }}
+ {{ define "help-content-template" }}
+ {{ range . }}
+ <p class="help-content" id="help-content-{{ CleanAppName .Title }}"> {{ .Contents }}</p>
+ {{ template "help-content-template" .Children }}
+ {{ end }}
+ {{ end }}
+ <script src="/static/launcher.js"></script>
+</body>
+</html>
diff --git a/core/installer/welcome/launcher.go b/core/installer/welcome/launcher.go
new file mode 100644
index 0000000..6f40714
--- /dev/null
+++ b/core/installer/welcome/launcher.go
@@ -0,0 +1,107 @@
+package welcome
+
+import (
+ "embed"
+ "fmt"
+ "html/template"
+ "log"
+ "net/http"
+ "strings"
+)
+
+//go:embed launcher-tmpl/launcher.html
+var indexHTML embed.FS
+
+//go:embed static/*
+var files embed.FS
+
+type AppLauncherInfo struct {
+ Name string
+ Description string
+ Icon template.HTML
+ Help []HelpDocument
+ Url string
+}
+
+type HelpDocument struct {
+ Title string
+ Contents string
+ Children []HelpDocument
+}
+
+type AppDirectory interface {
+ GetAllApps() ([]AppLauncherInfo, error)
+}
+
+type LauncherServer struct {
+ port int
+ logoutUrl string
+ appDirectory AppDirectory
+ homeTmpl *template.Template
+}
+
+func NewLauncherServer(
+ port int,
+ logoutUrl string,
+ appDirectory AppDirectory,
+) (*LauncherServer, error) {
+ tmpl, err := indexHTML.ReadFile("launcher-tmpl/launcher.html")
+ if err != nil {
+ return nil, fmt.Errorf("failed to parse template: %v", err)
+ }
+ t := template.New("index").Funcs(template.FuncMap{
+ "GetUserInitials": getUserInitials,
+ "CleanAppName": cleanAppName,
+ })
+ t, err = t.Parse(string(tmpl))
+ if err != nil {
+ return nil, fmt.Errorf("failed to parse template: %v", err)
+ }
+ return &LauncherServer{
+ port,
+ logoutUrl,
+ appDirectory,
+ t,
+ }, nil
+}
+
+func getUserInitials(username string) string {
+ if username == "" {
+ return ""
+ }
+ return strings.ToUpper(username[:1])
+}
+
+func cleanAppName(name string) string {
+ cleanName := strings.ToLower(name)
+ cleanName = strings.ReplaceAll(cleanName, " ", "-")
+ return cleanName
+}
+
+func (s *LauncherServer) Start() {
+ http.Handle("/static/", http.FileServer(http.FS(files)))
+ http.HandleFunc("/", s.homeHandler)
+ log.Fatal(http.ListenAndServe(fmt.Sprintf(":%d", s.port), nil))
+}
+
+type homeHandlerData struct {
+ LoggedInUsername string
+ AllAppsInfo []AppLauncherInfo
+}
+
+func (s *LauncherServer) homeHandler(w http.ResponseWriter, r *http.Request) {
+ loggedInUsername := "longusername"
+ allAppsInfo, err := s.appDirectory.GetAllApps()
+ if err != nil {
+ http.Error(w, err.Error(), http.StatusBadRequest)
+ return
+ }
+ data := homeHandlerData{
+ LoggedInUsername: loggedInUsername,
+ AllAppsInfo: allAppsInfo,
+ }
+ if err := s.homeTmpl.Execute(w, data); err != nil {
+ http.Error(w, err.Error(), http.StatusInternalServerError)
+ return
+ }
+}
diff --git a/core/installer/welcome/static/launcher.css b/core/installer/welcome/static/launcher.css
new file mode 100644
index 0000000..6802bf2
--- /dev/null
+++ b/core/installer/welcome/static/launcher.css
@@ -0,0 +1,290 @@
+:root:not([data-theme]) {
+ --pico-background-color: unset;
+ --pico-color: unset;
+}
+
+body {
+ margin: 0;
+ padding: 0;
+ font-family: Arial, sans-serif;
+ display: flex;
+ height: 100vh;
+ padding-left: 10px !important;
+ padding-right: 10px !important;
+ background-color: black;
+}
+
+#left-panel {
+ width: 80px;
+ background-color: #f0f0f0;
+ border-radius: 10px;
+ border-width: 1px;
+ border-color: black;
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ margin-top: 5px;
+ margin-bottom: 5px;
+}
+
+.app-list {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+}
+
+#right-panel {
+ flex: 1;
+ background-color: #f0f0f0;
+ margin: 5px;
+ padding: 2px;
+ border-radius: 10px;
+ border-color: black;
+}
+
+#appFrame {
+ border-radius: 10px;
+}
+
+.app-icon-tooltip {
+ position: relative;
+ display: inline-block;
+ align-items: flex-start;
+ justify-content: center;
+ cursor: initial;
+ width: 80px !important;
+ height: 50px !important;
+ margin-bottom: 10px !important;
+ cursor: pointer !important;
+ --pico-background-color: unset !important;
+ --pico-color: unset !important;
+}
+
+.tooltip {
+ position: absolute;
+ width: 200px;
+ border-radius: 0 10px 10px 0;
+ top: 70%;
+ left: 98%;
+ transform: translateY(-50%);
+ background-color: black;
+ color: white;
+ box-shadow: 0 0 5px rgba(0, 0, 0, 0.2);
+ padding: 5px;
+ z-index: 1;
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ visibility: hidden;
+ opacity: 0;
+ cursor: auto;
+ font-size: 16px;
+}
+
+.help-button {
+ margin-top: 5px !important;
+ padding: 0 !important;
+ border: 0 !important;
+ margin-bottom: 5px !important;
+ width: 100% !important;
+ color: white !important;
+ cursor: pointer !important;
+ font-size: 16px !important;
+}
+
+.icon {
+ display: flex;
+ justify-content: center;
+ align-items: center !important;
+}
+
+.tooltip p {
+ color: white;
+ margin: 0;
+ cursor: auto;
+}
+
+.app-icon-tooltip:hover {
+ transform: scale(1.15);
+}
+
+.app-icon-tooltip .background-glow {
+ position: absolute;
+ top: 0;
+ left: 4px;
+ right: 4px;
+ bottom: 0;
+ background: rgba(0, 0, 0, 0);
+ pointer-events: none;
+ border-radius: 5px;
+ box-shadow: 0px 0px 7px 7px black;
+}
+
+.modal-left {
+ width: 30%;
+ overflow-y: auto;
+ float: left;
+ margin-left: 0px;
+ background-color: #fbfcfc;
+ border-radius: 2px;
+}
+
+.modal-right {
+ /* flex: 1; */
+ width: 70%;
+ overflow-y: auto;
+ float: right;
+ margin-left: 2px;
+}
+
+.app-help-modal {
+ position: fixed;
+ top: 0;
+ left: 0;
+ width: 100%;
+ height: 100%;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ flex-direction: column;
+ overflow: hidden;
+}
+
+.app-help-modal-article {
+ display: flex;
+ flex-direction: row;
+ width: 100%;
+ max-width: 100%;
+ min-height: 97%;
+ max-height: 97%;
+ overflow: hidden;
+}
+
+.app-info-modal-article header {
+ flex: 0 0 auto;
+}
+
+header {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ position: relative;
+ margin-bottom: 2px !important;
+}
+
+.close-button {
+ padding: 0;
+ border: none;
+ background: none;
+ cursor: pointer;
+ outline: none;
+ width: 1.5em;
+ height: 1.5em;
+ position: absolute;
+ top: 11px;
+ right: 5px;
+}
+
+.modal-article {
+ min-width: 80% !important;
+ max-width: 80% !important;
+ min-height: 90% !important;
+ max-height: 90% !important;
+ overflow: hidden;
+}
+
+.help-content {
+ display: none;
+}
+
+.circle {
+ width: 50px;
+ height: 50px;
+ border-radius: 50%;
+ background-color: #ccc;
+ display: flex;
+ justify-content: center;
+ align-items: center;
+}
+
+.circle p {
+ font-size: 24px;
+ text-align: center;
+ line-height: 50px;
+ margin: 0;
+ position: relative;
+ display: inline-block;
+}
+
+.user-circle {
+ min-width: 80px !important;
+ max-width: 80px !important;
+ cursor: pointer;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+}
+
+.separator {
+ margin-top: 2px !important;
+ margin-bottom: 4px !important;
+ border-width: 2px !important;
+ border-color: black !important;
+ width: 100% !important;
+}
+
+ul {
+ padding-inline-start: 0px !important;
+ margin-bottom: 0px;
+ list-style: none;
+ font-size: 14px;
+}
+
+ul ul {
+ padding-inline-start: 19px;
+}
+
+ul li {
+ list-style: none !important;
+ padding-inline-start: 19px !important;
+ margin-bottom: 0px;
+ font-size: 16px !important;
+}
+
+.tooltip-user {
+ position: absolute;
+ top: 54px;
+ left: 90px;
+ transform: translateY(-50%);
+ width: 234px;
+ background-color: black;
+ box-shadow: 0 0 5px rgba(0, 0, 0, 0.2);
+ padding: 5px;
+ z-index: 1;
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ visibility: hidden;
+ opacity: 0;
+ border-radius: 0 0 10px 0;
+ cursor: auto;
+}
+
+.tooltip-user button {
+ margin-top: 5px !important;
+ padding: 0 !important;
+ border: 0 !important;
+ margin-bottom: 5px !important;
+ width: 100% !important;
+ color: white !important;
+ cursor: pointer !important;
+ font-size: 19px !important;
+}
+
+.tooltip-user p {
+ color: white;
+ margin: 0;
+ cursor: auto;
+ font-size: 19px;
+ /* align-self: flex-start; */
+}
diff --git a/core/installer/welcome/static/launcher.js b/core/installer/welcome/static/launcher.js
new file mode 100644
index 0000000..b8c2ae5
--- /dev/null
+++ b/core/installer/welcome/static/launcher.js
@@ -0,0 +1,78 @@
+document.addEventListener("DOMContentLoaded", function () {
+ function showTooltip(obj) {
+ // obj.style.display = 'flex';
+ obj.style.visibility = 'visible';
+ obj.style.opacity = '1';
+ }
+ function hideTooltip(obj) {
+ obj.style.visibility = 'hidden';
+ obj.style.opacity = '0';
+ // obj.style.display = '';
+ }
+ const circle = document.querySelector(".user-circle");
+ const tooltipUser = document.querySelector("#tooltip-user");
+ [
+ ['mouseenter', () => showTooltip(tooltipUser)],
+ ['mouseleave', () => hideTooltip(tooltipUser)],
+ ].forEach(([event, listener]) => {
+ circle.addEventListener(event, listener);
+ });
+ const icons = document.querySelectorAll(".app-icon-tooltip");
+ icons.forEach(function (icon) {
+ icon.addEventListener("click", function (event) {
+ event.stopPropagation();
+ const appUrl = this.getAttribute("data-app-url");
+ document.getElementById('appFrame').src = 'about:blank';
+ document.getElementById('appFrame').src = appUrl;
+ document.querySelectorAll(".app-icon-tooltip .background-glow").forEach((e) => e.remove());
+ const glow = document.createElement('div');
+ glow.classList.add("background-glow");
+ glow.setAttribute("style", "transform: none; transform-origin: 50% 50% 0px;")
+ this.appendChild(glow);
+ });
+ const tooltip = icon.querySelector('.tooltip');
+ tooltip.addEventListener("click", function (event) {
+ event.stopPropagation();
+ });
+ [
+ ['mouseenter', () => showTooltip(tooltip)],
+ ['mouseleave', () => hideTooltip(tooltip)],
+ ['focus', () => showTooltip(tooltip)],
+ ['blur', () => hideTooltip(tooltip)],
+ ].forEach(([event, listener]) => {
+ icon.addEventListener(event, listener);
+ });
+ });
+ const helpButtons = document.querySelectorAll('.help-button');
+ helpButtons.forEach(function (button) {
+ button.addEventListener('click', function (event) {
+ event.stopPropagation();
+ const buttonId = button.getAttribute('id');
+ const modalId = 'modal-' + buttonId.substring("help-button-".length);
+ const closeHelpId = "close-help-" + buttonId.substring("help-button-".length);
+ const modal = document.getElementById(modalId);
+ modal.removeAttribute("close");
+ modal.setAttribute("open", true);
+ const closeHelpButton = document.getElementById(closeHelpId);
+ closeHelpButton.addEventListener('click', function (event) {
+ event.stopPropagation();
+ modal.removeAttribute("open");
+ modal.setAttribute("close", true);
+ });
+ });
+ });
+ const modalHelpButtons = document.querySelectorAll('.title-menu');
+ modalHelpButtons.forEach(function (button) {
+ button.addEventListener('click', function (event) {
+ event.stopPropagation();
+ const helpTitle = button.getAttribute('id');
+ const helpTitleId = helpTitle.substring('title-'.length);
+ const helpContentId = 'help-content-' + helpTitleId;
+ const allContentElements = document.querySelectorAll('.help-content');
+ allContentElements.forEach(function (contentElement) {
+ contentElement.style.display = 'none';
+ });
+ document.getElementById(helpContentId).style.display = 'block';
+ });
+ });
+});