This commit is contained in:
51
.gitea/workflows/registry.yml
Normal file
51
.gitea/workflows/registry.yml
Normal file
@@ -0,0 +1,51 @@
|
||||
name: release-tag
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- 'main'
|
||||
jobs:
|
||||
release-image:
|
||||
runs-on: ubuntu-fast
|
||||
env:
|
||||
DOCKER_ORG: ${{ vars.DOCKER_ORG }}
|
||||
DOCKER_LATEST: latest
|
||||
RUNNER_TOOL_CACHE: /toolcache
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v2
|
||||
|
||||
- name: Set up Docker BuildX
|
||||
uses: docker/setup-buildx-action@v2
|
||||
with: # replace it with your local IP
|
||||
config-inline: |
|
||||
[registry."${{ vars.DOCKER_REGISTRY }}"]
|
||||
http = true
|
||||
insecure = true
|
||||
|
||||
- name: Login to DockerHub
|
||||
uses: docker/login-action@v2
|
||||
with:
|
||||
registry: ${{ vars.DOCKER_REGISTRY }} # replace it with your local IP
|
||||
username: ${{ secrets.DOCKER_USERNAME }}
|
||||
password: ${{ secrets.DOCKER_PASSWORD }}
|
||||
|
||||
- name: Get Meta
|
||||
id: meta
|
||||
run: |
|
||||
echo REPO_NAME=$(echo ${GITHUB_REPOSITORY} | awk -F"/" '{print $2}') >> $GITHUB_OUTPUT
|
||||
echo REPO_VERSION=$(git describe --tags --always | sed 's/^v//') >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Build and push
|
||||
uses: docker/build-push-action@v4
|
||||
with:
|
||||
context: .
|
||||
file: ./Dockerfile
|
||||
platforms: |
|
||||
linux/amd64
|
||||
push: true
|
||||
tags: | # replace it with your local IP and tags
|
||||
${{ vars.DOCKER_REGISTRY }}/${{ env.DOCKER_ORG }}/${{ steps.meta.outputs.REPO_NAME }}:${{ steps.meta.outputs.REPO_VERSION }}
|
||||
${{ vars.DOCKER_REGISTRY }}/${{ env.DOCKER_ORG }}/${{ steps.meta.outputs.REPO_NAME }}:${{ env.DOCKER_LATEST }}
|
||||
29
Dockerfile
Normal file
29
Dockerfile
Normal file
@@ -0,0 +1,29 @@
|
||||
# --- Builder Stage ---
|
||||
FROM golang:1.24-alpine AS builder
|
||||
|
||||
# Arbeitsverzeichnis
|
||||
WORKDIR /app
|
||||
|
||||
# Go-Module laden
|
||||
COPY go.mod ./
|
||||
RUN go mod download
|
||||
|
||||
# Quellcode
|
||||
COPY . .
|
||||
|
||||
# Binary bauen (statisch, kleiner)
|
||||
RUN CGO_ENABLED=0 GOOS=linux go build -o subnetcalc .
|
||||
|
||||
# --- Runtime Stage ---
|
||||
FROM alpine:3.22
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
# Binary vom Builder kopieren
|
||||
COPY --from=builder /app/subnetcalc .
|
||||
|
||||
# Exponiere Port für Webinterface
|
||||
EXPOSE 8080
|
||||
|
||||
# Standardkommando: immer Web-Modus starten
|
||||
ENTRYPOINT ["./subnetcalc", "-web", ":8080"]
|
||||
3
go.mod
Normal file
3
go.mod
Normal file
@@ -0,0 +1,3 @@
|
||||
module git.send.nrw/sendnrw/go-ipv6-calculator
|
||||
|
||||
go 1.24.4
|
||||
500
main.go
Normal file
500
main.go
Normal file
@@ -0,0 +1,500 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"encoding/csv"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"flag"
|
||||
"fmt"
|
||||
"html/template"
|
||||
"net"
|
||||
"net/http"
|
||||
"os"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// ===== Datenmodelle =====
|
||||
type Vlan64 struct {
|
||||
VLAN int `json:"vlan,omitempty"` // nur gesetzt im gefilterten (VLAN) Modus
|
||||
Hextet string `json:"hextet,omitempty"` // 4. Hextet als 4-stelliger String, z.B. "1110"
|
||||
Prefix64 string `json:"prefix64"`
|
||||
// interne Sortierhilfe:
|
||||
hextetNum uint16 `json:"-"`
|
||||
}
|
||||
|
||||
type Sub58 struct {
|
||||
Prefix58 string `json:"prefix58"`
|
||||
Children []Vlan64 `json:"children"`
|
||||
// interne Sortierhilfe:
|
||||
num58 uint16 `json:"-"`
|
||||
}
|
||||
|
||||
type Sub56 struct {
|
||||
Prefix56 string `json:"prefix56"`
|
||||
Children []Sub58 `json:"children"`
|
||||
// interne Sortierhilfe:
|
||||
num56 uint16 `json:"-"`
|
||||
}
|
||||
|
||||
type Export struct {
|
||||
Input48 string `json:"input48"`
|
||||
Items []Sub56 `json:"items"`
|
||||
// Zusatz für UI
|
||||
Total56 int `json:"-"`
|
||||
Total58 int `json:"-"`
|
||||
Total64 int `json:"-"`
|
||||
}
|
||||
|
||||
// ===== Flags =====
|
||||
var (
|
||||
flagPrefix = flag.String("prefix", "", "ULA-/48 Präfix, z.B. fd12:3456:789a::/48")
|
||||
flagFormat = flag.String("format", "text", "Ausgabeformat: text|json|csv")
|
||||
flagOut = flag.String("out", "", "Dateipfad für Export (leer = stdout)")
|
||||
flagWebAddr = flag.String("web", "", "Webserver starten an Adresse, z.B. :8080 (leer = aus)")
|
||||
flagAll = flag.Bool("all", false, "Alle Netze (unabhängig von Filter) ausgeben (nur CLI). Im Web als Checkbox verfügbar.")
|
||||
)
|
||||
|
||||
// ===== main =====
|
||||
func main() {
|
||||
flag.Parse()
|
||||
|
||||
// Webmodus?
|
||||
if *flagWebAddr != "" {
|
||||
startWeb(*flagWebAddr)
|
||||
return
|
||||
}
|
||||
|
||||
// CLI-Modus
|
||||
if *flagPrefix == "" {
|
||||
fail("Fehler: bitte mit -prefix ein ULA-/48 angeben, z.B. -prefix fd12:3456:789a::/48 (oder -web :8080 für Webinterface)")
|
||||
}
|
||||
result, err := compute(*flagPrefix, *flagAll)
|
||||
if err != nil {
|
||||
fail(err.Error())
|
||||
}
|
||||
if len(result.Items) == 0 {
|
||||
fail("keine passenden Netze gefunden (Filter zu eng?)")
|
||||
}
|
||||
|
||||
var out *os.File = os.Stdout
|
||||
if *flagOut != "" {
|
||||
f, err := os.Create(*flagOut)
|
||||
if err != nil {
|
||||
fail("konnte Datei nicht erstellen: " + err.Error())
|
||||
}
|
||||
defer f.Close()
|
||||
out = f
|
||||
}
|
||||
|
||||
switch *flagFormat {
|
||||
case "json":
|
||||
enc := json.NewEncoder(out)
|
||||
enc.SetIndent("", " ")
|
||||
_ = enc.Encode(result)
|
||||
case "csv":
|
||||
w := csv.NewWriter(out)
|
||||
_ = w.Write([]string{"input48", "prefix56", "prefix58", "vlan", "hextet", "prefix64"})
|
||||
for _, s56 := range result.Items {
|
||||
for _, s58 := range s56.Children {
|
||||
for _, v := range s58.Children {
|
||||
_ = w.Write([]string{
|
||||
result.Input48, s56.Prefix56, s58.Prefix58,
|
||||
intOrEmpty(v.VLAN), v.Hextet, v.Prefix64,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
w.Flush()
|
||||
if err := w.Error(); err != nil {
|
||||
fail("CSV-Export fehlgeschlagen: " + err.Error())
|
||||
}
|
||||
case "text":
|
||||
fmt.Fprintf(out, "# Eingabe: %s\n", result.Input48)
|
||||
if !*flagAll {
|
||||
fmt.Fprintln(out, "# Sortierung: /56 numerisch → /58 numerisch (nur 0–9) → VLAN 1–4096 numerisch (4. Hextet = Dezimalzahl)")
|
||||
} else {
|
||||
fmt.Fprintln(out, "# Sortierung: /56 numerisch → /58 numerisch → /64 numerisch (alle Netze)")
|
||||
}
|
||||
fmt.Fprintln(out)
|
||||
for _, s56 := range result.Items {
|
||||
fmt.Fprintln(out, s56.Prefix56)
|
||||
for _, s58 := range s56.Children {
|
||||
fmt.Fprintf(out, " %s\n", s58.Prefix58)
|
||||
for _, v := range s58.Children {
|
||||
if !*flagAll {
|
||||
fmt.Fprintf(out, " - VLAN %-4d %s\n", v.VLAN, v.Prefix64)
|
||||
} else {
|
||||
fmt.Fprintf(out, " - %s\n", v.Prefix64)
|
||||
}
|
||||
}
|
||||
}
|
||||
fmt.Fprintln(out)
|
||||
}
|
||||
default:
|
||||
fail("unbekanntes -format (erlaubt: text|json|csv)")
|
||||
}
|
||||
}
|
||||
|
||||
// ===== Kernlogik =====
|
||||
|
||||
// compute erzeugt die Hierarchie. includeAll = true zeigt ALLE /58 und deren 64 /64-Kinder.
|
||||
// includeAll = false wendet deine Filter an: /58 nur wenn 4. Hextet ausschließlich 0–9;
|
||||
// /64 nur VLAN 1–4096, deren 4. Hextet exakt dezimal ist (nur 0–9).
|
||||
func compute(prefix48 string, includeAll bool) (Export, error) {
|
||||
baseIP, baseNet, err := net.ParseCIDR(prefix48)
|
||||
if err != nil {
|
||||
return Export{}, err
|
||||
}
|
||||
ones, bits := baseNet.Mask.Size()
|
||||
if bits != 128 || ones != 48 {
|
||||
return Export{}, errors.New("nur /48 Präfixe sind erlaubt")
|
||||
}
|
||||
if !isULA(baseIP) {
|
||||
return Export{}, errors.New("nur ULA (fc00::/7) sind erlaubt")
|
||||
}
|
||||
|
||||
// Basisadresse normalisieren (ab 4. Hextet = 0)
|
||||
base := baseIP.To16()
|
||||
for i := 6; i < 16; i++ {
|
||||
base[i] = 0x00
|
||||
}
|
||||
|
||||
var result Export
|
||||
result.Input48 = prefix48
|
||||
result.Items = make([]Sub56, 0, 256)
|
||||
|
||||
// VLAN-Liste (gefilterter Modus)
|
||||
type vlanItem struct {
|
||||
num int
|
||||
hextet uint16
|
||||
str string
|
||||
}
|
||||
var vlanList []vlanItem
|
||||
if !includeAll {
|
||||
vlanList = make([]vlanItem, 0, 4096)
|
||||
for vlan := 1; vlan <= 4096; vlan++ {
|
||||
s := strconv.Itoa(vlan) // "1".."4096" -> nur Ziffern
|
||||
val, err := strconv.ParseUint(s, 16, 16)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
h := uint16(val)
|
||||
if hextetAllDigits(h) {
|
||||
vlanList = append(vlanList, vlanItem{
|
||||
num: vlan,
|
||||
hextet: h,
|
||||
str: fmt.Sprintf("%04x", val),
|
||||
})
|
||||
}
|
||||
}
|
||||
sort.Slice(vlanList, func(i, j int) bool { return vlanList[i].num < vlanList[j].num })
|
||||
}
|
||||
|
||||
total56, total58, total64 := 0, 0, 0
|
||||
|
||||
// /48 → /56
|
||||
for hb := 0; hb <= 0xFF; hb++ {
|
||||
hextet56 := uint16(hb) << 8 // 0xXX00
|
||||
p56 := formatCIDR(withFourthHextet(base, hextet56), 56)
|
||||
|
||||
sub56 := Sub56{
|
||||
Prefix56: p56,
|
||||
num56: hextet56,
|
||||
}
|
||||
|
||||
// /56 → /58 (0xXX00, 0xXX40, 0xXX80, 0xXXC0)
|
||||
for two := 0; two < 4; two++ {
|
||||
lbTop := two << 6
|
||||
hextet58 := hextet56 | uint16(lbTop)
|
||||
|
||||
// Filter: im gefilterten Modus müssen /58 komplett nur aus 0–9 bestehen.
|
||||
if !includeAll && !hextetAllDigits(hextet58) {
|
||||
continue
|
||||
}
|
||||
|
||||
children := make([]Vlan64, 0, 64)
|
||||
|
||||
if includeAll {
|
||||
// ALLE 64 /64-Kinder (untere 6 Bits variieren)
|
||||
for child := 0; child < 64; child++ {
|
||||
h := hextet58 | uint16(child)
|
||||
children = append(children, Vlan64{
|
||||
Prefix64: formatCIDR(withFourthHextet(base, h), 64),
|
||||
hextetNum: h,
|
||||
})
|
||||
}
|
||||
// numerisch sortieren nach hextet
|
||||
sort.Slice(children, func(i, j int) bool { return children[i].hextetNum < children[j].hextetNum })
|
||||
} else {
|
||||
// Nur VLAN-/64, deren 4. Hextet = Dezimalzahl (1..4096) ist
|
||||
for _, v := range vlanList {
|
||||
if (v.hextet & 0xFFC0) == hextet58 {
|
||||
children = append(children, Vlan64{
|
||||
VLAN: v.num,
|
||||
Hextet: v.str,
|
||||
Prefix64: formatCIDR(withFourthHextet(base, v.hextet), 64),
|
||||
hextetNum: v.hextet,
|
||||
})
|
||||
}
|
||||
}
|
||||
// VLANs numerisch sortiert (v.num bereits aufsteigend)
|
||||
}
|
||||
|
||||
if len(children) > 0 {
|
||||
total64 += len(children)
|
||||
sub56.Children = append(sub56.Children, Sub58{
|
||||
Prefix58: formatCIDR(withFourthHextet(base, hextet58), 58),
|
||||
Children: children,
|
||||
num58: hextet58,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
if len(sub56.Children) > 0 {
|
||||
// /58 numerisch sortieren
|
||||
sort.Slice(sub56.Children, func(i, j int) bool { return sub56.Children[i].num58 < sub56.Children[j].num58 })
|
||||
result.Items = append(result.Items, sub56)
|
||||
total56++
|
||||
total58 += len(sub56.Children)
|
||||
}
|
||||
}
|
||||
|
||||
// /56 numerisch sortieren global
|
||||
sort.Slice(result.Items, func(i, j int) bool { return result.Items[i].num56 < result.Items[j].num56 })
|
||||
result.Total56, result.Total58, result.Total64 = total56, total58, total64
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// ===== Helpers =====
|
||||
func isULA(ip net.IP) bool {
|
||||
ip16 := ip.To16()
|
||||
if ip16 == nil {
|
||||
return false
|
||||
}
|
||||
return ip16[0]&0xFE == 0xFC // fc00::/7
|
||||
}
|
||||
|
||||
func withFourthHextet(base []byte, hextet uint16) net.IP {
|
||||
ip := make([]byte, 16)
|
||||
copy(ip, base)
|
||||
ip[6] = byte(hextet >> 8)
|
||||
ip[7] = byte(hextet)
|
||||
return net.IP(ip)
|
||||
}
|
||||
|
||||
func formatCIDR(ip net.IP, mask int) string {
|
||||
return fmt.Sprintf("%s/%d", ip.String(), mask)
|
||||
}
|
||||
|
||||
// true, wenn alle 4 Nibbles ∈ {0..9}
|
||||
func hextetAllDigits(h uint16) bool {
|
||||
for shift := 0; shift <= 12; shift += 4 {
|
||||
if ((h >> uint(shift)) & 0xF) > 9 {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func fail(msg string) {
|
||||
fmt.Fprintln(os.Stderr, "Fehler:", msg)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
func intOrEmpty(v int) string {
|
||||
if v == 0 {
|
||||
return ""
|
||||
}
|
||||
return strconv.Itoa(v)
|
||||
}
|
||||
|
||||
// ===== Webserver =====
|
||||
var pageTmpl = template.Must(template.New("page").Parse(`
|
||||
<!doctype html>
|
||||
<html lang="de">
|
||||
<head>
|
||||
<meta charset="utf-8"/>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1"/>
|
||||
<title>IPv6 Subnet-Calculator (/48 → /56 → /58 → /64)</title>
|
||||
<style>
|
||||
body{font-family:system-ui,-apple-system,Segoe UI,Roboto,Ubuntu,Arial,sans-serif;line-height:1.45;margin:2rem;max-width:1200px}
|
||||
header{margin-bottom:1rem}
|
||||
form{display:flex;gap:0.75rem;flex-wrap:wrap;align-items:center;margin-bottom:1rem}
|
||||
input[type=text]{padding:0.5rem;border:1px solid #ddd;border-radius:8px;min-width:320px}
|
||||
button{padding:0.6rem 0.9rem;border-radius:8px;border:1px solid #ccc;background:#f7f7f7;cursor:pointer}
|
||||
.card{border:1px solid #e5e5e5;border-radius:12px;padding:1rem;margin-bottom:1rem;background:#fff}
|
||||
.meta{color:#666;font-size:0.9rem;margin-top:0.25rem}
|
||||
details{margin:0.25rem 0 0.25rem 0.5rem}
|
||||
summary{cursor:pointer;font-weight:600}
|
||||
code{background:#f5f5f5;padding:0.1rem 0.3rem;border-radius:6px}
|
||||
.small{font-size:0.9rem;color:#666}
|
||||
.badge{display:inline-block;padding:0.15rem 0.4rem;border:1px solid #ddd;border-radius:6px;margin-left:0.25rem;background:#fafafa;font-size:0.85rem}
|
||||
.row64{margin-left:2rem}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<header>
|
||||
<h1>IPv6 Subnet-Calculator</h1>
|
||||
<div class="small">/48 → /56 → /58 → /64 · numerisch sortiert</div>
|
||||
</header>
|
||||
|
||||
<form method="GET" action="/">
|
||||
<label for="prefix">ULA-/48:</label>
|
||||
<input id="prefix" name="prefix" type="text" placeholder="z.B. fd12:3456:789a::/48" value="{{.Input48}}">
|
||||
<label><input type="checkbox" name="all" value="1" {{if .ShowAll}}checked{{end}}> Alle Netze anzeigen</label>
|
||||
<button type="submit">Berechnen</button>
|
||||
{{if .HasResult}}
|
||||
<a class="badge" href="/download?format=json&prefix={{.Input48URL}}&all={{.AllParam}}">JSON herunterladen</a>
|
||||
<a class="badge" href="/download?format=csv&prefix={{.Input48URL}}&all={{.AllParam}}">CSV herunterladen</a>
|
||||
{{end}}
|
||||
</form>
|
||||
|
||||
{{if .Error}}
|
||||
<div class="card" style="border-color:#f2c9c9;background:#fff7f7">
|
||||
<strong>Fehler:</strong> {{.Error}}
|
||||
</div>
|
||||
{{end}}
|
||||
|
||||
{{if .HasResult}}
|
||||
<div class="card">
|
||||
<div><strong>Eingabe:</strong> <code>{{.Result.Input48}}</code>
|
||||
{{if .ShowAll}}<span class="badge">Alle Netze</span>{{else}}<span class="badge">Gefiltert (Ziffern/VLAN)</span>{{end}}
|
||||
</div>
|
||||
<div class="meta">
|
||||
{{.Result.Total56}} × /56 · {{.Result.Total58}} × /58 · {{.Result.Total64}} × /64
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{range .Result.Items}}
|
||||
<details>
|
||||
<summary><code>{{.Prefix56}}</code> — {{len .Children}} × /58</summary>
|
||||
{{range .Children}}
|
||||
<details>
|
||||
<summary><code>{{.Prefix58}}</code> — {{len .Children}} × /64</summary>
|
||||
<div class="row64">
|
||||
{{range .Children}}
|
||||
{{if .VLAN}}
|
||||
<div>VLAN {{.VLAN}} — <code>{{.Prefix64}}</code></div>
|
||||
{{else}}
|
||||
<div><code>{{.Prefix64}}</code></div>
|
||||
{{end}}
|
||||
{{end}}
|
||||
</div>
|
||||
</details>
|
||||
{{end}}
|
||||
</details>
|
||||
{{end}}
|
||||
{{else}}
|
||||
<div class="card">Gib oben ein ULA-/48 ein und starte die Berechnung.</div>
|
||||
{{end}}
|
||||
|
||||
<footer class="small" style="margin-top:2rem">
|
||||
Hinweis: „Alle Netze“ zeigt sämtliche /58 und alle 64 /64-Kinder je /58 an. Ohne Häkchen wird gefiltert:
|
||||
/58 nur, wenn das gesamte 4. Hextet ausschließlich Ziffern (0–9) enthält; /64 nur VLAN 1–4096, wobei das 4. Hextet exakt die Dezimalzahl (nur Ziffern) ist.
|
||||
</footer>
|
||||
</body>
|
||||
</html>
|
||||
`))
|
||||
|
||||
type pageData struct {
|
||||
Input48 string
|
||||
Input48URL string
|
||||
ShowAll bool
|
||||
HasResult bool
|
||||
Error string
|
||||
Result Export
|
||||
AllParam string
|
||||
}
|
||||
|
||||
func startWeb(addr string) {
|
||||
http.HandleFunc("/", handleIndex)
|
||||
http.HandleFunc("/download", handleDownload)
|
||||
fmt.Println("Webinterface läuft auf", addr)
|
||||
if err := http.ListenAndServe(addr, nil); err != nil {
|
||||
fail(err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
func handleIndex(w http.ResponseWriter, r *http.Request) {
|
||||
q := r.URL.Query()
|
||||
prefix := strings.TrimSpace(q.Get("prefix"))
|
||||
showAll := q.Get("all") == "1" || strings.EqualFold(q.Get("all"), "true")
|
||||
if prefix == "" {
|
||||
prefix = "fd09:1:2::/48"
|
||||
}
|
||||
data := pageData{
|
||||
Input48: prefix,
|
||||
Input48URL: template.URLQueryEscaper(prefix),
|
||||
ShowAll: showAll,
|
||||
AllParam: boolTo01(showAll),
|
||||
}
|
||||
|
||||
if prefix != "" {
|
||||
res, err := compute(prefix, showAll)
|
||||
if err != nil {
|
||||
data.Error = err.Error()
|
||||
} else {
|
||||
data.Result = res
|
||||
data.HasResult = true
|
||||
}
|
||||
}
|
||||
|
||||
if err := pageTmpl.Execute(w, data); err != nil {
|
||||
http.Error(w, "Template-Fehler: "+err.Error(), 500)
|
||||
}
|
||||
}
|
||||
|
||||
func handleDownload(w http.ResponseWriter, r *http.Request) {
|
||||
q := r.URL.Query()
|
||||
prefix := strings.TrimSpace(q.Get("prefix"))
|
||||
showAll := q.Get("all") == "1" || strings.EqualFold(q.Get("all"), "true")
|
||||
format := strings.ToLower(q.Get("format"))
|
||||
if format == "" {
|
||||
format = "json"
|
||||
}
|
||||
|
||||
if prefix == "" {
|
||||
http.Error(w, "prefix fehlt", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
res, err := compute(prefix, showAll)
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
switch format {
|
||||
case "json":
|
||||
w.Header().Set("Content-Type", "application/json; charset=utf-8")
|
||||
enc := json.NewEncoder(w)
|
||||
enc.SetIndent("", " ")
|
||||
_ = enc.Encode(res)
|
||||
case "csv":
|
||||
w.Header().Set("Content-Type", "text/csv; charset=utf-8")
|
||||
w.Header().Set("Content-Disposition", "attachment; filename=\"subnets.csv\"")
|
||||
cw := csv.NewWriter(w)
|
||||
_ = cw.Write([]string{"input48", "prefix56", "prefix58", "vlan", "hextet", "prefix64"})
|
||||
for _, s56 := range res.Items {
|
||||
for _, s58 := range s56.Children {
|
||||
for _, v := range s58.Children {
|
||||
_ = cw.Write([]string{
|
||||
res.Input48, s56.Prefix56, s58.Prefix58,
|
||||
intOrEmpty(v.VLAN), v.Hextet, v.Prefix64,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
cw.Flush()
|
||||
default:
|
||||
http.Error(w, "format muss json oder csv sein", http.StatusBadRequest)
|
||||
}
|
||||
}
|
||||
|
||||
func boolTo01(b bool) string {
|
||||
if b {
|
||||
return "1"
|
||||
}
|
||||
return "0"
|
||||
}
|
||||
Reference in New Issue
Block a user