Skip to content

Commit 9f4d227

Browse files
authored
feat: UI/UX enhancements (#6)
1 parent 61ee24c commit 9f4d227

File tree

5 files changed

+211
-78
lines changed

5 files changed

+211
-78
lines changed

cmd/server.go

Lines changed: 54 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -10,14 +10,14 @@ import (
1010
"strconv"
1111
"strings"
1212
"time"
13+
14+
"go.szostok.io/version"
15+
"gopkg.in/yaml.v3"
1316
)
1417

1518
//go:embed template.html
1619
var htmlTemplate string
1720

18-
//go:embed swagger_ui_template.js
19-
var swaggerUITemplate string
20-
2121
// Microservice represents configuration for each microservice
2222
type Microservice struct {
2323
Endpoint string `mapstructure:"endpoint"`
@@ -81,36 +81,62 @@ func GetOASSpec(url string) ([]byte, string, string, error) {
8181

8282
// GenerateHTML generates the HTML for viewing the OpenAPI spec using Swagger UI
8383
func GenerateHTML(spec []byte, microserviceList []MicroserviceList, serviceURL, selectedService, serviceSummary, message string) (string, error) {
84-
type SwaggerUIParams struct {
85-
Spec string
86-
Host string
87-
ProxyAddress string
88-
Headers map[string]string
89-
MicroserviceList []MicroserviceList
90-
SelectedService string
91-
ServiceSummary string
92-
}
9384

94-
params := SwaggerUIParams{
95-
Spec: string(spec),
96-
Host: serviceURL,
97-
ProxyAddress: proxyAddress + "/",
98-
Headers: headers,
99-
MicroserviceList: microserviceList,
100-
SelectedService: selectedService,
101-
ServiceSummary: serviceSummary,
102-
}
85+
servicesYaml, err := yaml.Marshal(&services)
86+
checkError(err)
10387

104-
tmpl := htmlTemplate
88+
headersYaml, err := yaml.Marshal(&headers)
89+
checkError(err)
10590

106-
// Only include the SwaggerUIBundle if a service is selected from drop-down list
107-
// and the OAS specs have been successfully retrieved from the service.
108-
if message == "" {
109-
tmpl += swaggerUITemplate
91+
oasBinderConfiguration := `
92+
config = "` + cfgFile + `"
93+
proxyAddress = "` + proxyAddress + `"
94+
listenPort = ` + strconv.Itoa(listenPort) + `
95+
listenAddress = "` + listenAddress + `"
96+
apiSpecsPath = "` + apiSpecsPath + `"
97+
services =
98+
` + string(servicesYaml) + `headers = ` + string(headersYaml)
99+
100+
versionInfo := version.Get()
101+
102+
aboutOasBinder := `
103+
Version: ` + versionInfo.Version + `
104+
Git Commit: ` + versionInfo.GitCommit + `
105+
Build Date: ` + versionInfo.BuildDate + `
106+
Commit Date: ` + versionInfo.CommitDate + `
107+
Go Version: ` + versionInfo.GoVersion + `
108+
Compiler: ` + versionInfo.Compiler + `
109+
Platform: ` + versionInfo.Platform
110+
111+
type SwaggerUIParams struct {
112+
Spec string
113+
Host string
114+
ProxyAddress string
115+
Headers map[string]string
116+
MicroserviceList []MicroserviceList
117+
SelectedService string
118+
ServiceSummary string
119+
DisplaySwaggerUI bool
120+
Message string
121+
OASBinderConfiguration string
122+
AboutOASBinder string
110123
}
111-
tmpl += `</script><br />` + message + `</body></html>`
112124

113-
t, err := template.New("swaggerui").Parse(tmpl)
125+
params := SwaggerUIParams{
126+
Spec: string(spec),
127+
Host: serviceURL,
128+
ProxyAddress: proxyAddress + "/",
129+
Headers: headers,
130+
MicroserviceList: microserviceList,
131+
SelectedService: selectedService,
132+
ServiceSummary: serviceSummary,
133+
DisplaySwaggerUI: message == "",
134+
Message: message,
135+
OASBinderConfiguration: oasBinderConfiguration,
136+
AboutOASBinder: aboutOasBinder,
137+
}
138+
139+
t, err := template.New("swaggerui").Parse(htmlTemplate)
114140
if err != nil {
115141
return "", err
116142
}

cmd/swagger_ui_template.js

Lines changed: 0 additions & 13 deletions
This file was deleted.

cmd/template.html

Lines changed: 146 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,38 +1,159 @@
11
<!DOCTYPE html>
2-
<html>
2+
<html lang="en">
33
<head>
4-
<meta charset="utf-8">
5-
<title>Swagger UI</title>
6-
<!-- Load the latest Swagger UI code and style from npm using unpkg.com -->
7-
<script src="https://cdnjs.cloudflare.com/ajax/libs/swagger-ui/5.17.14/swagger-ui-bundle.min.js" integrity="sha512-7ihPQv5ibiTr0DW6onbl2MIKegdT6vjpPySyIb4Ftp68kER6Z7Yiub0tFoMmCHzZfQE9+M+KSjQndv6NhYxDgg==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
8-
<script src="https://cdnjs.cloudflare.com/ajax/libs/swagger-ui/5.17.14/swagger-ui-standalone-preset.min.js" integrity="sha512-UrYi+60Ci3WWWcoDXbMmzpoi1xpERbwjPGij6wTh8fXl81qNdioNNHExr9ttnBebKF0ZbVnPlTPlw+zECUK1Xw==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
9-
<link rel="stylesheet" type="text/css" href="https://cdnjs.cloudflare.com/ajax/libs/swagger-ui/5.17.14/swagger-ui.min.css" integrity="sha512-+9UD8YSD9GF7FzOH38L9S6y56aYNx3R4dYbOCgvTJ2ZHpJScsahNdaMQJU/8osUiz9FPu0YZ8wdKf4evUbsGSg==" crossorigin="anonymous" referrerpolicy="no-referrer" />
4+
<meta charset="UTF-8">
5+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
6+
<title>OAS Binder</title>
7+
<!-- Load the latest Swagger UI code and style -->
8+
<script src="https://cdnjs.cloudflare.com/ajax/libs/swagger-ui/5.18.2/swagger-ui-bundle.min.js" integrity="sha512-9tBcCofqWq+PelL6USpUB7OJrCaObfefi9ht9nVZuKt1XP7eHDs7NwVljLSLVtSsErax1Tz3pG3O82eeq546Rg==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
9+
<script src="https://cdnjs.cloudflare.com/ajax/libs/swagger-ui/5.18.2/swagger-ui-standalone-preset.min.js" integrity="sha512-RYT3vTu8lWSgdoB5zNck/WogIqUb/ap/ivTr6t2LeS+MwqxRQsnSHkHpJRKjnC4T2fH7OMTxxQoC3jh7KGd3HA==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
10+
<script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/2.11.8/umd/popper.min.js" integrity="sha512-TPh2Oxlg1zp+kz3nFA0C5vVC6leG/6mm1z9+mA81MI5eaUVqasPLO8Cuk4gMF4gUfP5etR73rgU/8PNMsSesoQ==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
11+
<script src="https://cdnjs.cloudflare.com/ajax/libs/bootstrap/5.3.3/js/bootstrap.min.js" integrity="sha512-ykZ1QQr0Jy/4ZkvKuqWn4iF3lqPZyij9iRv6sGqLRdTPkY69YX6+7wvVGmsdBbiIfN/8OdsI7HABjvEok6ZopQ==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
12+
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/swagger-ui/5.18.2/swagger-ui.min.css" integrity="sha512-xRGj65XGEcpPTE7Cn6ujJWokpXVLxqLxdtNZ/n1w52+76XaCRO7UWKZl9yJHvzpk99A0EP6EW+opPcRwPDxwkA==" crossorigin="anonymous" referrerpolicy="no-referrer" />
1013
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/bootstrap/5.3.3/css/bootstrap.min.css" integrity="sha512-jnSuA4Ss2PkkikSOLtYs8BlYIeeIK1h99ty4YfvRPAlzr377vr3CXDb7sb7eEEBYjDtcYj+AjBH3FLv5uSJuXg==" crossorigin="anonymous" referrerpolicy="no-referrer" />
1114

1215
<style>
13-
.toolbar {
14-
margin: 20px 0;
16+
body {
17+
display: flex;
18+
height: 100vh;
19+
margin-top: 56px;
20+
}
21+
.sidebar {
22+
height: 100vh;
23+
max-width: 30%;
24+
flex: 0 0 400px;
25+
}
26+
.content {
27+
flex-grow: 1;
28+
padding: 15px;
29+
}
30+
.sidebar img {
31+
max-width: 100%;
32+
margin-bottom: 15px;
33+
}
34+
.navbar {
35+
position: fixed;
36+
top: 0;
37+
width: 100%;
38+
z-index: 1000;
39+
height: 56px;
40+
}
41+
.navbar-brand {
42+
position: absolute;
43+
left: 50%;
44+
transform: translateX(-50%);
45+
font-size: 1.5rem;
46+
font-weight: bold;
47+
}
48+
.navbar-nav {
49+
flex-grow: 0;
50+
}
51+
.sidebar-title {
52+
text-align: center;
53+
font-weight: bold;
54+
margin-bottom: 15px;
55+
font-size: 1.25rem;
1556
}
1657
</style>
1758
</head>
1859
<body>
19-
<div class="container">
20-
<div class="toolbar">
21-
<div class="form-group">
22-
<label for="microservice-select" class="font-weight-bold">Select Microservice:</label>
23-
<select id="microservice-select" class="form-control">
24-
<option value=""></option>
25-
{{range .MicroserviceList}}
26-
<option value="{{.Endpoint}}" {{if .Selected}}selected{{end}}>{{.Name}}</option>
27-
{{end}}
28-
</select>
60+
<nav class="navbar navbar-expand-lg navbar-light bg-light">
61+
<a class="navbar-brand" href="#">OAS Binder</a>
62+
<div class="collapse navbar-collapse" id="navbarNav">
63+
<ul class="navbar-nav ms-auto">
64+
<li class="nav-item">
65+
<button class="btn btn-warning" data-bs-toggle="modal" data-bs-target="#modal1"></button>
66+
</li>
67+
<li class="nav-item">
68+
<button class="btn btn-warning ms-2" data-bs-toggle="modal" data-bs-target="#modal2"></button>
69+
</li>
70+
</ul>
71+
</div>
72+
</nav>
73+
<div class="sidebar bg-light p-3">
74+
<img src="https://cdnlogo.com/logos/o/44/openapi-wordmark.svg" alt="">
75+
<div class="sidebar-title">Select Service</div>
76+
<ul class="nav flex-column">
77+
{{range .MicroserviceList}}
78+
<li class="nav-item">
79+
<button data-target="{{.Endpoint}}" class="btn {{if .Selected}}btn-primary {{else}} btn-outline-primary{{end}} mb-2">{{.Name}}</button>
80+
</li>
81+
{{end}}
82+
</ul>
83+
</div>
84+
<div class="content">
85+
<div id="mainContent">
86+
<div class="sidebar-title">{{ .Message }}</div>
87+
<div id="swagger-ui"></div>
88+
</div>
89+
</div>
90+
91+
<!-- Modal structures -->
92+
<div class="modal fade" id="modal1" tabindex="-1" aria-labelledby="modal1Label" aria-hidden="true">
93+
<div class="modal-dialog" role="document">
94+
<div class="modal-content">
95+
<div class="modal-header">
96+
<h5 class="modal-title" id="modal1Label">OAS Binder configuration</h5>
97+
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
98+
</div>
99+
<div class="modal-body">
100+
<pre>
101+
{{ .OASBinderConfiguration }}
102+
</pre>
103+
</div>
104+
<div class="modal-footer">
105+
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Close</button>
106+
</div>
29107
</div>
30-
<div id="swagger-ui"></div>
108+
</div>
31109
</div>
110+
111+
<div class="modal fade" id="modal2" tabindex="-1" aria-labelledby="modal2Label" aria-hidden="true">
112+
<div class="modal-dialog" role="document">
113+
<div class="modal-content">
114+
<div class="modal-header">
115+
<h5 class="modal-title" id="modal2Label">About OAS Binder</h5>
116+
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
117+
</div>
118+
<div class="modal-body">
119+
A tool to interact with OAS docs for multiple microservices
120+
<br />
121+
URL: <a href="https://github.com/insightsengineering/oasbinder">https://github.com/insightsengineering/oasbinder</a>
122+
<br />
123+
<pre>
124+
{{ .AboutOASBinder }}
125+
</pre>
126+
</div>
127+
<div class="modal-footer">
128+
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Close</button>
129+
</div>
130+
</div>
131+
</div>
132+
</div>
133+
32134
<script>
33-
document.getElementById('microservice-select').addEventListener('change', function() {
34-
var newEndpoint = this.value;
35-
if (newEndpoint !== "{{.SelectedService}}") {
36-
window.location.href = newEndpoint;
37-
}
135+
document.querySelectorAll('button[data-target]').forEach(button => {
136+
button.addEventListener('click', function () {
137+
const target = button.getAttribute('data-target');
138+
window.location.href = `${window.location.origin}/${target}`;
139+
});
38140
});
141+
142+
window.onload = function() {
143+
if ({{ .DisplaySwaggerUI }}) {
144+
const ui = SwaggerUIBundle({
145+
spec: JSON.parse({{.Spec}}),
146+
dom_id: "#swagger-ui",
147+
requestInterceptor: (req) => {
148+
req.url = req.url.replace({{.ProxyAddress}}, {{.Host}});
149+
{{range $key, $value := .Headers}}
150+
req.headers["{{$key}}"] = "{{$value}}";
151+
{{end}}
152+
return req;
153+
}
154+
});
155+
}
156+
}
157+
</script>
158+
</body>
159+
</html>

go.mod

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,10 @@ toolchain go1.23.6
77
require (
88
github.com/jamiealquiza/envy v1.1.0
99
github.com/sirupsen/logrus v1.9.3
10-
github.com/spf13/cobra v1.8.1
10+
github.com/spf13/cobra v1.9.1
1111
github.com/spf13/viper v1.19.0
1212
go.szostok.io/version v1.2.0
13+
gopkg.in/yaml.v3 v3.0.1
1314
)
1415

1516
require (
@@ -22,7 +23,7 @@ require (
2223
github.com/dustin/go-humanize v1.0.1 // indirect
2324
github.com/fatih/color v1.18.0 // indirect
2425
github.com/fsnotify/fsnotify v1.8.0 // indirect
25-
github.com/goccy/go-yaml v1.15.22 // indirect
26+
github.com/goccy/go-yaml v1.15.23 // indirect
2627
github.com/google/uuid v1.6.0 // indirect
2728
github.com/hashicorp/go-version v1.7.0 // indirect
2829
github.com/hashicorp/hcl v1.0.0 // indirect
@@ -50,11 +51,10 @@ require (
5051
github.com/subosito/gotenv v1.6.0 // indirect
5152
go.uber.org/multierr v1.11.0 // indirect
5253
golang.org/x/crypto v0.33.0 // indirect
53-
golang.org/x/exp v0.0.0-20250210185358-939b2ce775ac // indirect
54+
golang.org/x/exp v0.0.0-20250218142911-aa4b98e5adaa // indirect
5455
golang.org/x/sys v0.30.0 // indirect
5556
golang.org/x/text v0.22.0 // indirect
5657
gopkg.in/ini.v1 v1.67.0 // indirect
57-
gopkg.in/yaml.v3 v3.0.1 // indirect
5858
)
5959

6060
replace github.com/imdario/mergo => github.com/imdario/mergo v0.3.16

go.sum

Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ github.com/araddon/dateparse v0.0.0-20210429162001-6b43995a97de h1:FxWPpzIjnTlhP
1010
github.com/araddon/dateparse v0.0.0-20210429162001-6b43995a97de/go.mod h1:DCaWoUhZrYW9p1lxo/cm8EmUOOzAPSEZNGF2DK1dJgw=
1111
github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k=
1212
github.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8=
13-
github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
13+
github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g=
1414
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
1515
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
1616
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
@@ -23,8 +23,8 @@ github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHk
2323
github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0=
2424
github.com/fsnotify/fsnotify v1.8.0 h1:dAwr6QBTBZIkG8roQaJjGof0pp0EeF+tNV7YBP3F/8M=
2525
github.com/fsnotify/fsnotify v1.8.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0=
26-
github.com/goccy/go-yaml v1.15.22 h1:iQI1hvCoiYYiVFq76P4AI8ImgDOfgiyKnl/AWjK8/gA=
27-
github.com/goccy/go-yaml v1.15.22/go.mod h1:XBurs7gK8ATbW4ZPGKgcbrY1Br56PdM69F7LkFRi1kA=
26+
github.com/goccy/go-yaml v1.15.23 h1:WS0GAX1uNPDLUvLkNU2vXq6oTnsmfVFocjQ/4qA48qo=
27+
github.com/goccy/go-yaml v1.15.23/go.mod h1:XBurs7gK8ATbW4ZPGKgcbrY1Br56PdM69F7LkFRi1kA=
2828
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
2929
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
3030
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
@@ -91,9 +91,8 @@ github.com/spf13/afero v1.12.0 h1:UcOPyRBYczmFn6yvphxkn9ZEOY65cpwGKb5mL36mrqs=
9191
github.com/spf13/afero v1.12.0/go.mod h1:ZTlWwG4/ahT8W7T0WQ5uYmjI9duaLQGy3Q2OAl4sk/4=
9292
github.com/spf13/cast v1.7.1 h1:cuNEagBQEHWN1FnbGEjCXL2szYEXqfJPbP2HNUaca9Y=
9393
github.com/spf13/cast v1.7.1/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo=
94-
github.com/spf13/cobra v1.8.1 h1:e5/vxKd/rZsfSJMUX1agtjeTDf+qv1/JdBF8gg5k9ZM=
95-
github.com/spf13/cobra v1.8.1/go.mod h1:wHxEcudfqmLYa8iTfL+OuZPbBZkmvliBWKIezN3kD9Y=
96-
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
94+
github.com/spf13/cobra v1.9.1 h1:CXSaggrXdbHK9CF+8ywj8Amf7PBRmPCOJugH954Nnlo=
95+
github.com/spf13/cobra v1.9.1/go.mod h1:nDyEzZ8ogv936Cinf6g1RU9MRY64Ir93oCnqb9wxYW0=
9796
github.com/spf13/pflag v1.0.6 h1:jFzHGLGAlb3ruxLB8MhbI6A8+AQX/2eW4qeyNZXNp2o=
9897
github.com/spf13/pflag v1.0.6/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
9998
github.com/spf13/viper v1.19.0 h1:RWq5SEjt8o25SROyN3z2OrDB9l7RPd3lwTWU8EcEdcI=
@@ -110,8 +109,8 @@ go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
110109
go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
111110
golang.org/x/crypto v0.33.0 h1:IOBPskki6Lysi0lo9qQvbxiQ+FvsCC/YWOecCHAixus=
112111
golang.org/x/crypto v0.33.0/go.mod h1:bVdXmD7IV/4GdElGPozy6U7lWdRXA4qyRVGJV57uQ5M=
113-
golang.org/x/exp v0.0.0-20250210185358-939b2ce775ac h1:l5+whBCLH3iH2ZNHYLbAe58bo7yrN4mVcnkHDYz5vvs=
114-
golang.org/x/exp v0.0.0-20250210185358-939b2ce775ac/go.mod h1:hH+7mtFmImwwcMvScyxUhjuVHR3HGaDPMn9rMSUUbxo=
112+
golang.org/x/exp v0.0.0-20250218142911-aa4b98e5adaa h1:t2QcU6V556bFjYgu4L6C+6VrCPyJZ+eyRsABUPs1mz4=
113+
golang.org/x/exp v0.0.0-20250218142911-aa4b98e5adaa/go.mod h1:BHOTPb3L19zxehTsLoJXVaTktb06DFgmdW6Wb9s8jqk=
115114
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
116115
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
117116
golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc=

0 commit comments

Comments
 (0)