Commit fb2e780d authored by ale's avatar ale

Initial commit

parents
Pipeline #1131 failed with stages
in 12 seconds
image: docker:latest
stages:
- build
- docker_build
- release
services:
- docker:dind
variables:
IMAGE_TAG: $CI_REGISTRY_IMAGE:$CI_COMMIT_REF_NAME
RELEASE_TAG: $CI_REGISTRY_IMAGE:latest
GIT_SUBMODULE_STRATEGY: recursive
build:
stage: build
image: "ai/build:stretch"
script:
- env DEBIAN_FRONTEND=noninteractive apt-get -qy install make golang-go
- make
artifacts:
paths:
- build/
docker_build:
stage: docker_build
script:
- docker login -u gitlab-ci-token -p $CI_JOB_TOKEN registry.git.autistici.org
- docker build --build-arg ci_token=$CI_JOB_TOKEN --pull -t $IMAGE_TAG .
- docker push $IMAGE_TAG
dependencies:
- build
release:
stage: release
script:
- docker login -u gitlab-ci-token -p $CI_JOB_TOKEN registry.git.autistici.org
- docker pull $IMAGE_TAG
- docker tag $IMAGE_TAG $RELEASE_TAG
- docker push $RELEASE_TAG
only:
- master
FROM scratch
ADD build/float-dashboard /
CMD ["/float-dashboard"]
all: build/float-dashboard
build/float-dashboard: $(wildcard *.go templates/*.html)
-mkdir -p build
# Do not run 'go generate' on Docker builds.
#go generate
go build -o $@ -tags netgo -a -ldflags -w .
This diff is collapsed.
package main
//go:generate go-bindata --nocompress --pkg main static/... templates/...
//go:generate go run scripts/sri.go --package main --output sri_auto.go static
import (
"flag"
"html/template"
"io"
"io/ioutil"
"log"
"net/http"
"git.autistici.org/ai3/go-common/serverutil"
assetfs "github.com/elazarl/go-bindata-assetfs"
"github.com/gorilla/mux"
"gopkg.in/yaml.v2"
)
var (
addr = flag.String("addr", ":3000", "tcp `address` to listen on")
configPath = flag.String("config", "/etc/float/dashboard.yml", "dashboard config `file`")
floatServicesPath = flag.String("services", "/etc/float/services.json", "float services `file`")
tpl *template.Template
config *Config
services ServiceMap
)
// Config for the application.
type Config struct {
Domain string `yaml:"domain"`
PublicDomains []string `yaml:"public_domains"`
LogoImagePath string `yaml:"logo_image"`
HTTP *serverutil.ServerConfig `yaml:"http_server"`
}
func readYAML(path string, obj interface{}) error {
data, err := ioutil.ReadFile(path)
if err != nil {
return err
}
return yaml.Unmarshal(data, obj)
}
// Read the program's configuration file.
func mustReadConfig() *Config {
var c Config
if err := readYAML(*configPath, &c); err != nil {
log.Fatalf("error reading configuration: %v", err)
}
return &c
}
// Read the configured services.
func mustReadServices() ServiceMap {
m := make(map[string]*Service)
if err := readYAML(*floatServicesPath, &m); err != nil {
log.Fatalf("error reading services file: %v", err)
}
for name, s := range m {
s.Name = name
}
return m
}
// Parse the templates that are embedded with the binary (in bindata.go).
func mustParseEmbeddedTemplates() *template.Template {
root := template.New("").Funcs(template.FuncMap{
// Useful function to format something as YAML.
"yaml": func(obj interface{}) string {
if data, err := yaml.Marshal(obj); err == nil {
return string(data)
}
return ""
},
"sri_script": SRIScript,
"sri_stylesheet": SRIStylesheet,
})
files, err := AssetDir("templates")
if err != nil {
log.Fatalf("no asset dir for templates: %v", err)
}
for _, f := range files {
b, err := Asset("templates/" + f)
if err != nil {
log.Fatalf("could not read embedded template %s: %v", f, err)
}
if _, err := root.New(f).Parse(string(b)); err != nil {
log.Fatalf("error parsing template %s: %v", f, err)
}
}
return root
}
// A relatively strict CSP.
const contentSecurityPolicy = "default-src 'none'; img-src 'self' data:; script-src 'self'; style-src 'self'; font-src 'self';"
func withDynamicHeaders(h http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Pragma", "no-cache")
w.Header().Set("Cache-Control", "no-store")
w.Header().Set("Expires", "-1")
w.Header().Set("X-Frame-Options", "NONE")
w.Header().Set("X-XSS-Protection", "1; mode=block")
w.Header().Set("X-Content-Type-Options", "nosniff")
if w.Header().Get("Content-Security-Policy") == "" {
w.Header().Set("Content-Security-Policy", contentSecurityPolicy)
}
h.ServeHTTP(w, r)
})
}
func renderTemplate(w io.Writer, name string, ctx map[string]interface{}) {
if ctx == nil {
ctx = make(map[string]interface{})
}
ctx["Config"] = config
ctx["Services"] = services
if err := tpl.ExecuteTemplate(w, name, ctx); err != nil {
log.Printf("template error: %v", err)
}
}
func handleIndex(w http.ResponseWriter, r *http.Request) {
if r.URL.Path != "/" {
http.NotFound(w, r)
return
}
renderTemplate(w, "index.html", nil)
}
func handleServiceDetails(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
s, ok := services[vars["service"]]
if !ok {
http.NotFound(w, r)
return
}
renderTemplate(w, "service_details.html", map[string]interface{}{
"Service": s,
})
}
func handleContainerDetails(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
s, ok := services[vars["service"]]
if !ok {
http.NotFound(w, r)
return
}
var c *Container
for _, cc := range s.Containers {
if cc.Name == vars["container"] {
c = &cc
break
}
}
if c == nil {
http.NotFound(w, r)
return
}
renderTemplate(w, "container_details.html", map[string]interface{}{
"Service": s,
"Container": c,
})
}
// Create the HTTP server.
func makeServer() http.Handler {
root := mux.NewRouter()
root.PathPrefix("/static/").Handler(http.StripPrefix("/static/", http.FileServer(&assetfs.AssetFS{
Asset: Asset,
AssetDir: AssetDir,
AssetInfo: AssetInfo,
Prefix: "static",
})))
root.Handle("/service/{service}/details", withDynamicHeaders(http.HandlerFunc(handleServiceDetails)))
root.Handle("/service/{service}/container/{container}/details", withDynamicHeaders(http.HandlerFunc(handleContainerDetails)))
root.Handle("/", withDynamicHeaders(http.HandlerFunc(handleIndex)))
return root
}
func main() {
log.SetFlags(0)
flag.Parse()
tpl = mustParseEmbeddedTemplates()
config = mustReadConfig()
services = mustReadServices()
h := makeServer()
if err := serverutil.Serve(h, config.HTTP, *addr); err != nil {
log.Fatal(err)
}
}
type sortedStringList []string
func (l sortedStringList) Len() int { return len(l) }
func (l sortedStringList) Swap(i, j int) { l[i], l[j] = l[j], l[i] }
func (l sortedStringList) Less(i, j int) bool { return l[i] < l[j] }
package main
import (
"crypto/sha512"
"encoding/base64"
"flag"
"fmt"
"io"
"io/ioutil"
"log"
"os"
"path/filepath"
"strings"
)
var (
outputPath = flag.String("output", "", "output `file` name")
packageName = flag.String("package", "", "`name` of the package for generated code")
stripPrefix = flag.String("strip", "", "prefix to strip from `path`s to generate URLs")
)
func computeChecksum(path string) (string, error) {
data, err := ioutil.ReadFile(path)
if err != nil {
return "", err
}
sha := sha512.Sum384(data)
return "sha384-" + base64.StdEncoding.EncodeToString(sha[:]), nil
}
func urlForPath(path string) string {
path = strings.TrimPrefix(path, *stripPrefix)
if !strings.HasPrefix(path, "/") {
path = "/" + path
}
return path
}
func mkSRIMap(m map[string]string, dir string) error {
return filepath.Walk(dir, func(path string, info os.FileInfo, err error) error {
// Only match files with the desired extensions.
if info.IsDir() || !match(info.Name()) {
return nil
}
if cksum, err := computeChecksum(path); err == nil {
m[urlForPath(path)] = cksum
}
return nil
})
}
func match(name string) bool {
switch filepath.Ext(name) {
case ".js", ".json", ".css":
return true
default:
return false
}
}
func codegen(w io.Writer, m map[string]string) {
fmt.Fprintf(w, "package %s\n", *packageName)
io.WriteString(w, `
import (
"fmt"
"html/template"
)
var sriMap = map[string]string{
`)
for k, v := range m {
fmt.Fprintf(w, "\t%q: %q,\n", k, v)
}
io.WriteString(w, `}
func SRIScript(uri string) template.HTML {
s := fmt.Sprintf("<script src=\"%s\"", uri)
if sri, ok := sriMap[uri]; ok {
s += fmt.Sprintf(" crossorigin=\"\" integrity=\"%s\"", sri)
}
s += "></script>"
return template.HTML(s)
}
func SRIStylesheet(uri string) template.HTML {
s := fmt.Sprintf("<link rel=\"stylesheet\" type=\"text/css\" href=\"%s\"", uri)
if sri, ok := sriMap[uri]; ok {
s += fmt.Sprintf(" integrity=\"%s\"", sri)
}
s += ">"
return template.HTML(s)
}
`)
}
func main() {
log.SetFlags(0)
flag.Parse()
m := make(map[string]string)
for _, path := range flag.Args() {
if err := mkSRIMap(m, path); err != nil {
log.Println(err)
}
}
var w io.Writer = os.Stdout
if *outputPath != "" {
var err error
w, err = os.Create(*outputPath)
if err != nil {
log.Fatal(err)
}
}
codegen(w, m)
}
package main
import (
"fmt"
"sort"
)
type MonitoringEndpoint struct {
JobName string `yaml:"job_name"`
Port int `yaml:"port"`
Scheme string `yaml:"scheme,omitempty"`
}
func (m MonitoringEndpoint) TargetStatusURL() string {
return fmt.Sprintf("https://monitor.%s/graph?expr=ok{job=%s}", config.Domain, m.JobName)
}
func (m MonitoringEndpoint) ServiceDashboardURL() string {
return fmt.Sprintf("https://grafana.%s/service-dashboard?service=%s", config.Domain, m.JobName)
}
type PublicEndpoint struct {
Name string `yaml:"name"`
Domains []string `yaml:"domains,omitempty"`
Port int `yaml:"port"`
Scheme string `yaml:"scheme,omitempty"`
EnableSSOProxy bool `yaml:"enable_sso_proxy,omitempty"`
}
func (p PublicEndpoint) URL() string {
return fmt.Sprintf("https://%s.%s/", p.Name, config.PublicDomains[0])
}
func (p PublicEndpoint) LogsURL() string {
return fmt.Sprintf("https://logs.%s/kibana/bla/bla/?vhost=%s.%s", config.Domain, p.Name, config.PublicDomains[0])
}
// Boolean fields that default to true are problematic, so we use
// pointers to detect the case where the value is unset.
type ServiceCredentials struct {
Name string `yaml:"name"`
EnableClient *bool `yaml:"enable_client,omitempty"`
EnableServer *bool `yaml:"enable_server,omitempty"`
}
func (c *ServiceCredentials) HasClient() bool {
if c.EnableClient == nil {
return true
}
return *c.EnableClient
}
func (c *ServiceCredentials) HasServer() bool {
if c.EnableServer == nil {
return true
}
return *c.EnableServer
}
type Container struct {
Name string `yaml:"name"`
Image string `yaml:"image"`
Port int `yaml:"port"`
Volumes []map[string]string `yaml:"volumes,omitempty"`
Env map[string]string `yaml:"env,omitempty"`
}
type Service struct {
Name string
NumInstances int `yaml:"num_instances,omitempty"`
SchedulingGroup string `yaml:"scheduling_group"`
MasterElection bool `yaml:"master_election,omitempty"`
MasterSchedulingGroup string `yaml:"master_scheduling_group,omitempty"`
Ports []int `yaml:"ports,omitempty"`
ServiceCredentials []ServiceCredentials `yaml:"service_credentials,omitempty"`
LDAPCredentials []struct {
Name string `yaml:"name"`
} `yaml:"ldap_credentials,omitempty"`
MonitoringEndpoints []MonitoringEndpoint `yaml:"monitoring_endpoints,omitempty"`
PublicEndpoints []PublicEndpoint `yaml:"public_endpoints,omitempty"`
Containers []Container `yaml:"containers,omitempty"`
}
type ServiceMap map[string]*Service
func (m ServiceMap) Keys() []string {
var out []string
for k := range m {
out = append(out, k)
}
sort.Sort(sortedStringList(out))
return out
}
func (s *Service) HasClientServiceCredentials() bool {
for _, c := range s.ServiceCredentials {
if c.HasClient() {
return true
}
}
return false
}
func (s *Service) HasServerServiceCredentials() bool {
for _, c := range s.ServiceCredentials {
if c.HasServer() {
return true
}
}
return false
}
func (s *Service) HasPublicURLs() bool {
return len(s.PublicEndpoints) > 0
}
func (s *Service) HasMonitoringEndpoints() bool {
return len(s.MonitoringEndpoints) > 0
}
package main
import (
"fmt"
"html/template"
)
var sriMap = map[string]string{
"/static/css/style.css": "sha384-OLBgp1GsljhM2TJ+sbHjaiH9txEUvgdDTAzHv2P24donTt6/529l+9Ua0vFImLlb",
"/static/js/bootstrap.bundle.min.js": "sha384-CS0nxkpPy+xUkNGhObAISrkg/xjb3USVCwy+0/NMzd5VxgY4CMCyTkItmy5n0voC",
"/static/js/jquery.slim.min.js": "sha384-q8i/X+965DzO0rT7abK41JStQIAqVgRVzpbzo5smXKp4YfRvH+8abtTE1Pi6jizo",
"/static/css/bootstrap.min.css": "sha384-Smlep5jCw/wG7hdkwQ/Z5nLIefveQRIY9nfy6xoR1uRYBtpZgI6339F5dgvm/e9B",
"/static/css/open-iconic-bootstrap.min.css": "sha384-wWci3BOzr88l+HNsAtr3+e5bk9qh5KfjU6gl/rbzfTYdsAVHBEbxB33veLYmFg/a",
}
func SRIScript(uri string) template.HTML {
s := fmt.Sprintf("<script src=\"%s\"", uri)
if sri, ok := sriMap[uri]; ok {
s += fmt.Sprintf(" crossorigin=\"\" integrity=\"%s\"", sri)
}
s += "></script>"
return template.HTML(s)
}
func SRIStylesheet(uri string) template.HTML {
s := fmt.Sprintf("<link rel=\"stylesheet\" type=\"text/css\" href=\"%s\"", uri)
if sri, ok := sriMap[uri]; ok {
s += fmt.Sprintf(" integrity=\"%s\"", sri)
}
s += ">"
return template.HTML(s)
}
This source diff could not be displayed because it is too large. You can view the blob instead.
This diff is collapsed.
@font-face{font-family:Icons;src:url(../fonts/open-iconic.eot);src:url(../fonts/open-iconic.eot?#iconic-sm) format('embedded-opentype'),url(../fonts/open-iconic.woff) format('woff'),url(../fonts/open-iconic.ttf) format('truetype'),url(../fonts/open-iconic.otf) format('opentype'),url(../fonts/open-iconic.svg#iconic-sm) format('svg');font-weight:400;font-style:normal}.oi{position:relative;top:1px;display:inline-block;speak:none;font-family:Icons;font-style:normal;font-weight:400;line-height:1;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.oi:empty:before{width:1em;text-align:center;box-sizing:content-box}.oi.oi-align-center:before{text-align:center}.oi.oi-align-left:before{text-align:left}.oi.oi-align-right:before{text-align:right}.oi.oi-flip-horizontal:before{-webkit-transform:scale(-1,1);-ms-transform:scale(-1,1);transform:scale(-1,1)}.oi.oi-flip-vertical:before{-webkit-transform:scale(1,-1);-ms-transform:scale(-1,1);transform:scale(1,-1)}.oi.oi-flip-horizontal-vertical:before{-webkit-transform:scale(-1,-1);-ms-transform:scale(-1,1);transform:scale(-1,-1)}.oi-account-login:before{content:'\e000'}.oi-account-logout:before{content:'\e001'}.oi-action-redo:before{content:'\e002'}.oi-action-undo:before{content:'\e003'}.oi-align-center:before{content:'\e004'}.oi-align-left:before{content:'\e005'}.oi-align-right:before{content:'\e006'}.oi-aperture:before{content:'\e007'}.oi-arrow-bottom:before{content:'\e008'}.oi-arrow-circle-bottom:before{content:'\e009'}.oi-arrow-circle-left:before{content:'\e00a'}.oi-arrow-circle-right:before{content:'\e00b'}.oi-arrow-circle-top:before{content:'\e00c'}.oi-arrow-left:before{content:'\e00d'}.oi-arrow-right:before{content:'\e00e'}.oi-arrow-thick-bottom:before{content:'\e00f'}.oi-arrow-thick-left:before{content:'\e010'}.oi-arrow-thick-right:before{content:'\e011'}.oi-arrow-thick-top:before{content:'\e012'}.oi-arrow-top:before{content:'\e013'}.oi-audio-spectrum:before{content:'\e014'}.oi-audio:before{content:'\e015'}.oi-badge:before{content:'\e016'}.oi-ban:before{content:'\e017'}.oi-bar-chart:before{content:'\e018'}.oi-basket:before{content:'\e019'}.oi-battery-empty:before{content:'\e01a'}.oi-battery-full:before{content:'\e01b'}.oi-beaker:before{content:'\e01c'}.oi-bell:before{content:'\e01d'}.oi-bluetooth:before{content:'\e01e'}.oi-bold:before{content:'\e01f'}.oi-bolt:before{content:'\e020'}.oi-book:before{content:'\e021'}.oi-bookmark:before{content:'\e022'}.oi-box:before{content:'\e023'}.oi-briefcase:before{content:'\e024'}.oi-british-pound:before{content:'\e025'}.oi-browser:before{content:'\e026'}.oi-brush:before{content:'\e027'}.oi-bug:before{content:'\e028'}.oi-bullhorn:before{content:'\e029'}.oi-calculator:before{content:'\e02a'}.oi-calendar:before{content:'\e02b'}.oi-camera-slr:before{content:'\e02c'}.oi-caret-bottom:before{content:'\e02d'}.oi-caret-left:before{content:'\e02e'}.oi-caret-right:before{content:'\e02f'}.oi-caret-top:before{content:'\e030'}.oi-cart:before{content:'\e031'}.oi-chat:before{content:'\e032'}.oi-check:before{content:'\e033'}.oi-chevron-bottom:before{content:'\e034'}.oi-chevron-left:before{content:'\e035'}.oi-chevron-right:before{content:'\e036'}.oi-chevron-top:before{content:'\e037'}.oi-circle-check:before{content:'\e038'}.oi-circle-x:before{content:'\e039'}.oi-clipboard:before{content:'\e03a'}.oi-clock:before{content:'\e03b'}.oi-cloud-download:before{content:'\e03c'}.oi-cloud-upload:before{content:'\e03d'}.oi-cloud:before{content:'\e03e'}.oi-cloudy:before{content:'\e03f'}.oi-code:before{content:'\e040'}.oi-cog:before{content:'\e041'}.oi-collapse-down:before{content:'\e042'}.oi-collapse-left:before{content:'\e043'}.oi-collapse-right:before{content:'\e044'}.oi-collapse-up:before{content:'\e045'}.oi-command:before{content:'\e046'}.oi-comment-square:before{content:'\e047'}.oi-compass:before{content:'\e048'}.oi-contrast:before{content:'\e049'}.oi-copywriting:before{content:'\e04a'}.oi-credit-card:before{content:'\e04b'}.oi-crop:before{content:'\e04c'}.oi-dashboard:before{content:'\e04d'}.oi-data-transfer-download:before{content:'\e04e'}.oi-data-transfer-upload:before{content:'\e04f'}.oi-delete:before{content:'\e050'}.oi-dial:before{content:'\e051'}.oi-document:before{content:'\e052'}.oi-dollar:before{content:'\e053'}.oi-double-quote-sans-left:before{content:'\e054'}.oi-double-quote-sans-right:before{content:'\e055'}.oi-double-quote-serif-left:before{content:'\e056'}.oi-double-quote-serif-right:before{content:'\e057'}.oi-droplet:before{content:'\e058'}.oi-eject:before{content:'\e059'}.oi-elevator:before{content:'\e05a'}.oi-ellipses:before{content:'\e05b'}.oi-envelope-closed:before{content:'\e05c'}.oi-envelope-open:before{content:'\e05d'}.oi-euro:before{content:'\e05e'}.oi-excerpt:before{content:'\e05f'}.oi-expand-down:before{content:'\e060'}.oi-expand-left:before{content:'\e061'}.oi-expand-right:before{content:'\e062'}.oi-expand-up:before{content:'\e063'}.oi-external-link:before{content:'\e064'}.oi-eye:before{content:'\e065'}.oi-eyedropper:before{content:'\e066'}.oi-file:before{content:'\e067'}.oi-fire:before{content:'\e068'}.oi-flag:before{content:'\e069'}.oi-flash:before{content:'\e06a'}.oi-folder:before{content:'\e06b'}.oi-fork:before{content:'\e06c'}.oi-fullscreen-enter:before{content:'\e06d'}.oi-fullscreen-exit:before{content:'\e06e'}.oi-globe:before{content:'\e06f'}.oi-graph:before{content:'\e070'}.oi-grid-four-up:before{content:'\e071'}.oi-grid-three-up:before{content:'\e072'}.oi-grid-two-up:before{content:'\e073'}.oi-hard-drive:before{content:'\e074'}.oi-header:before{content:'\e075'}.oi-headphones:before{content:'\e076'}.oi-heart:before{content:'\e077'}.oi-home:before{content:'\e078'}.oi-image:before{content:'\e079'}.oi-inbox:before{content:'\e07a'}.oi-infinity:before{content:'\e07b'}.oi-info:before{content:'\e07c'}.oi-italic:before{content:'\e07d'}.oi-justify-center:before{content:'\e07e'}.oi-justify-left:before{content:'\e07f'}.oi-justify-right:before{content:'\e080'}.oi-key:before{content:'\e081'}.oi-laptop:before{content:'\e082'}.oi-layers:before{content:'\e083'}.oi-lightbulb:before{content:'\e084'}.oi-link-broken:before{content:'\e085'}.oi-link-intact:before{content:'\e086'}.oi-list-rich:before{content:'\e087'}.oi-list:before{content:'\e088'}.oi-location:before{content:'\e089'}.oi-lock-locked:before{content:'\e08a'}.oi-lock-unlocked:before{content:'\e08b'}.oi-loop-circular:before{content:'\e08c'}.oi-loop-square:before{content:'\e08d'}.oi-loop:before{content:'\e08e'}.oi-magnifying-glass:before{content:'\e08f'}.oi-map-marker:before{content:'\e090'}.oi-map:before{content:'\e091'}.oi-media-pause:before{content:'\e092'}.oi-media-play:before{content:'\e093'}.oi-media-record:before{content:'\e094'}.oi-media-skip-backward:before{content:'\e095'}.oi-media-skip-forward:before{content:'\e096'}.oi-media-step-backward:before{content:'\e097'}.oi-media-step-forward:before{content:'\e098'}.oi-media-stop:before{content:'\e099'}.oi-medical-cross:before{content:'\e09a'}.oi-menu:before{content:'\e09b'}.oi-microphone:before{content:'\e09c'}.oi-minus:before{content:'\e09d'}.oi-monitor:before{content:'\e09e'}.oi-moon:before{content:'\e09f'}.oi-move:before{content:'\e0a0'}.oi-musical-note:before{content:'\e0a1'}.oi-paperclip:before{content:'\e0a2'}.oi-pencil:before{content:'\e0a3'}.oi-people:before{content:'\e0a4'}.oi-person:before{content:'\e0a5'}.oi-phone:before{content:'\e0a6'}.oi-pie-chart:before{content:'\e0a7'}.oi-pin:before{content:'\e0a8'}.oi-play-circle:before{content:'\e0a9'}.oi-plus:before{content:'\e0aa'}.oi-power-standby:before{content:'\e0ab'}.oi-print:before{content:'\e0ac'}.oi-project:before{content:'\e0ad'}.oi-pulse:before{content:'\e0ae'}.oi-puzzle-piece:before{content:'\e0af'}.oi-question-mark:before{content:'\e0b0'}.oi-rain:before{content:'\e0b1'}.oi-random:before{content:'\e0b2'}.oi-reload:before{content:'\e0b3'}.oi-resize-both:before{content:'\e0b4'}.oi-resize-height:before{content:'\e0b5'}.oi-resize-width:before{content:'\e0b6'}.oi-rss-alt:before{content:'\e0b7'}.oi-rss:before{content:'\e0b8'}.oi-script:before{content:'\e0b9'}.oi-share-boxed:before{content:'\e0ba'}.oi-share:before{content:'\e0bb'}.oi-shield:before{content:'\e0bc'}.oi-signal:before{content:'\e0bd'}.oi-signpost:before{content:'\e0be'}.oi-sort-ascending:before{content:'\e0bf'}.oi-sort-descending:before{content:'\e0c0'}.oi-spreadsheet:before{content:'\e0c1'}.oi-star:before{content:'\e0c2'}.oi-sun:before{content:'\e0c3'}.oi-tablet:before{content:'\e0c4'}.oi-tag:before{content:'\e0c5'}.oi-tags:before{content:'\e0c6'}.oi-target:before{content:'\e0c7'}.oi-task:before{content:'\e0c8'}.oi-terminal:before{content:'\e0c9'}.oi-text:before{content:'\e0ca'}.oi-thumb-down:before{content:'\e0cb'}.oi-thumb-up:before{content:'\e0cc'}.oi-timer:before{content:'\e0cd'}.oi-transfer:before{content:'\e0ce'}.oi-trash:before{content:'\e0cf'}.oi-underline:before{content:'\e0d0'}.oi-vertical-align-bottom:before{content:'\e0d1'}.oi-vertical-align-center:before{content:'\e0d2'}.oi-vertical-align-top:before{content:'\e0d3'}.oi-video:before{content:'\e0d4'}.oi-volume-high:before{content:'\e0d5'}.oi-volume-low:before{content:'\e0d6'}.oi-volume-off:before{content:'\e0d7'}.oi-warning:before{content:'\e0d8'}.oi-wifi:before{content:'\e0d9'}.oi-wrench:before{content:'\e0da'}.oi-x:before{content:'\e0db'}.oi-yen:before{content:'\e0dc'}.oi-zoom-in:before{content:'\e0dd'}.oi-zoom-out:before{content:'\e0de'}
\ No newline at end of file
This diff is collapsed.
This diff is collapsed.
This source diff could not be displayed because it is too large. You can view the blob instead.
This diff is collapsed.
{{template "header" "container details"}}
{{$domain := .Config.Domain}}
<h1>{{$domain}} / service {{.Service.Name}} / container {{.Container.Name}}</h1>
<pre>{{.Container | yaml}}</pre>
{{template "footer"}}
{{define "footer"}}
<hr>
<div class="container-fluid">
<div class="float-right">
float
</div>
</div>
</div>
{{sri_script "/static/js/jquery.slim.min.js"}}
{{sri_script "/static/js/bootstrap.bundle.min.js"}}
</body>
</html>
{{end}}
{{define "header"}}
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<title>{{.}}</title>
{{sri_stylesheet "/static/css/bootstrap.min.css"}}
{{sri_stylesheet "/static/css/open-iconic-bootstrap.min.css"}}
{{sri_stylesheet "/static/css/style.css"}}
</head>
<body>
<div class="container" id="container-out">
{{end}}
{{template "header" "float services"}}
{{$domain := .Config.Domain}}
<h1>{{$domain}} services</h1>
<table class="table">
<thead>
<tr>
<th>Name</th>
<th>TLS</th>
<th>Ports</th>
<th>URLs</th>
</tr>
</thead>
<tbody>
{{with $services := .Services}}
{{range $services.Keys}}
{{$svc := index $services .}}
<tr>
<td>
{{.}}
<a href="/service/{{.}}/details">
<span class="oi oi-info" title="details"></span>
</a>
</td>
<td>
{{if $svc.HasServerServiceCredentials}}
<span class="oi oi-lock-locked"></span>
{{end}}
{{if $svc.HasClientServiceCredentials}}
<span class="oi oi-key"></span>
{{end}}
</td>
<td>{{if $svc.Ports}}{{$svc.Ports}}{{end}}</td>
<td>
{{range $i, $p := $svc.PublicEndpoints}}
{{if gt $i 0}}<br>{{end}}
{{$p.Name}}
<span class="muted">
<a href="{{$p.URL}}" target="_blank">
<span class="oi oi-globe" title="view site"></span>
</a>
<a href="{{$p.LogsURL}}">
<span class="oi oi-spreadsheet" title="http logs"></span>
</a>
</span>
{{end}}
</td>
<td>
{{range $i, $m := $svc.MonitoringEndpoints}}
{{if gt $i 0}}<br>{{end}}
{{$m.JobName}}
<a href="{{$m.TargetStatusURL}}">
<span class="oi oi-circle-check" title="health status"></span>
</a>
<a href="{{$m.ServiceDashboardURL}}">
<span class="oi oi-graph" title="health status"></span>
</a>
{{end}}
</td>
<td>
{{range $i, $c := $svc.Containers}}
{{if gt $i 0}}<br>{{end}}
{{$c.Name}}
<a href="/service/{{$svc.Name}}/container/{{$c.Name}}/details">
<span class="oi oi-excerpt" title="details"></span>
</a>
{{end}}
</td>
</tr>
{{end}}
{{end}}
</tbody>
</table>
<hr>
<h3>Configuration</h3>
<pre>{{.Services | yaml}}</pre>
{{template "footer"}}
{{template "header" "service details"}}
{{$domain := .Config.Domain}}
<h1>{{$domain}} / service {{.Service.Name}}</h1>
<pre>{{.Service | yaml}}</pre>
{{template "footer"}}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment