From 4d842f7d3b1860b8656a4ce636ae158b5b840087 Mon Sep 17 00:00:00 2001 From: Jakob Borg Date: Sat, 21 Sep 2024 09:10:24 +0200 Subject: [PATCH] feat(ursrv): new metrics based approach --- cmd/infra/strelaypoolsrv/README.md | 1 - cmd/infra/ursrv/aggregate/aggregate.go | 226 --- cmd/infra/ursrv/main.go | 10 +- cmd/infra/ursrv/serve/analytics.go | 276 ---- cmd/infra/ursrv/serve/formatting.go | 131 -- cmd/infra/ursrv/serve/metrics.go | 38 +- cmd/infra/ursrv/serve/prometheus.go | 314 +++++ cmd/infra/ursrv/serve/serve.go | 1222 ++++------------- .../ursrv/serve/static/assets/img/favicon.png | Bin 4913 -> 0 bytes .../bootstrap/css/bootstrap-theme.min.css | 7 - .../static/bootstrap/css/bootstrap.min.css | 7 - .../static/bootstrap/js/bootstrap.min.js | 7 - .../fonts/glyphicons-halflings-regular.eot | Bin 20290 -> 0 bytes .../fonts/glyphicons-halflings-regular.svg | Bin 62850 -> 0 bytes .../fonts/glyphicons-halflings-regular.ttf | Bin 41236 -> 0 bytes .../fonts/glyphicons-halflings-regular.woff | Bin 23292 -> 0 bytes cmd/infra/ursrv/serve/static/index.html | 623 --------- cmd/stdiscosrv/database.go | 25 +- cmd/stdiscosrv/main.go | 10 +- cmd/stdiscosrv/s3.go | 97 -- go.mod | 1 - go.sum | 2 - .../syncthing/core/aboutModalView.html | 1 - lib/s3/s3.go | 101 ++ lib/ur/contract/contract.go | 422 ++---- lib/ur/usage_report.go | 18 + 26 files changed, 867 insertions(+), 2672 deletions(-) delete mode 100644 cmd/infra/ursrv/aggregate/aggregate.go delete mode 100644 cmd/infra/ursrv/serve/analytics.go delete mode 100644 cmd/infra/ursrv/serve/formatting.go create mode 100644 cmd/infra/ursrv/serve/prometheus.go delete mode 100644 cmd/infra/ursrv/serve/static/assets/img/favicon.png delete mode 100644 cmd/infra/ursrv/serve/static/bootstrap/css/bootstrap-theme.min.css delete mode 100644 cmd/infra/ursrv/serve/static/bootstrap/css/bootstrap.min.css delete mode 100644 cmd/infra/ursrv/serve/static/bootstrap/js/bootstrap.min.js delete mode 100644 cmd/infra/ursrv/serve/static/fonts/glyphicons-halflings-regular.eot delete mode 100644 cmd/infra/ursrv/serve/static/fonts/glyphicons-halflings-regular.svg delete mode 100644 cmd/infra/ursrv/serve/static/fonts/glyphicons-halflings-regular.ttf delete mode 100644 cmd/infra/ursrv/serve/static/fonts/glyphicons-halflings-regular.woff delete mode 100644 cmd/infra/ursrv/serve/static/index.html delete mode 100644 cmd/stdiscosrv/s3.go create mode 100644 lib/s3/s3.go diff --git a/cmd/infra/strelaypoolsrv/README.md b/cmd/infra/strelaypoolsrv/README.md index 7b5f332eb..a3cb3ce46 100644 --- a/cmd/infra/strelaypoolsrv/README.md +++ b/cmd/infra/strelaypoolsrv/README.md @@ -21,4 +21,3 @@ See `relaypoolsrv -help` for configuration options. [oschwald/geoip2-golang](https://github.com/oschwald/geoip2-golang), [oschwald/maxminddb-golang](https://github.com/oschwald/maxminddb-golang), Copyright (C) 2015 [Gregory J. Oschwald](mailto:oschwald@gmail.com). -[lib/pq](https://github.com/lib/pq), Copyright (C) 2011-2013 'pq' Contributors Portions Copyright (C) 2011 Blake Mizerany. diff --git a/cmd/infra/ursrv/aggregate/aggregate.go b/cmd/infra/ursrv/aggregate/aggregate.go deleted file mode 100644 index daf2e1c3c..000000000 --- a/cmd/infra/ursrv/aggregate/aggregate.go +++ /dev/null @@ -1,226 +0,0 @@ -// Copyright (C) 2018 The Syncthing Authors. -// -// This Source Code Form is subject to the terms of the Mozilla Public -// License, v. 2.0. If a copy of the MPL was not distributed with this file, -// You can obtain one at https://mozilla.org/MPL/2.0/. - -package aggregate - -import ( - "database/sql" - "fmt" - "log" - "os" - "time" - - _ "github.com/lib/pq" -) - -type CLI struct { - DBConn string `env:"UR_DB_URL" default:"postgres://user:password@localhost/ur?sslmode=disable"` -} - -func (cli *CLI) Run() error { - log.SetFlags(log.Ltime | log.Ldate) - log.SetOutput(os.Stdout) - - db, err := sql.Open("postgres", cli.DBConn) - if err != nil { - return fmt.Errorf("database: %w", err) - } - err = setupDB(db) - if err != nil { - return fmt.Errorf("database: %w", err) - } - - for { - runAggregation(db) - // Sleep until one minute past next midnight - sleepUntilNext(24*time.Hour, 1*time.Minute) - } -} - -func runAggregation(db *sql.DB) { - since := maxIndexedDay(db, "VersionSummary") - log.Println("Aggregating VersionSummary data since", since) - rows, err := aggregateVersionSummary(db, since.Add(24*time.Hour)) - if err != nil { - log.Println("aggregate:", err) - } - log.Println("Inserted", rows, "rows") - - since = maxIndexedDay(db, "Performance") - log.Println("Aggregating Performance data since", since) - rows, err = aggregatePerformance(db, since.Add(24*time.Hour)) - if err != nil { - log.Println("aggregate:", err) - } - log.Println("Inserted", rows, "rows") - - since = maxIndexedDay(db, "BlockStats") - log.Println("Aggregating BlockStats data since", since) - rows, err = aggregateBlockStats(db, since.Add(24*time.Hour)) - if err != nil { - log.Println("aggregate:", err) - } - log.Println("Inserted", rows, "rows") -} - -func sleepUntilNext(intv, margin time.Duration) { - now := time.Now().UTC() - next := now.Truncate(intv).Add(intv).Add(margin) - log.Println("Sleeping until", next) - time.Sleep(next.Sub(now)) -} - -func setupDB(db *sql.DB) error { - _, err := db.Exec(`CREATE TABLE IF NOT EXISTS VersionSummary ( - Day TIMESTAMP NOT NULL, - Version VARCHAR(8) NOT NULL, - Count INTEGER NOT NULL - )`) - if err != nil { - return err - } - - _, err = db.Exec(`CREATE TABLE IF NOT EXISTS Performance ( - Day TIMESTAMP NOT NULL, - TotFiles INTEGER NOT NULL, - TotMiB INTEGER NOT NULL, - SHA256Perf DOUBLE PRECISION NOT NULL, - MemorySize INTEGER NOT NULL, - MemoryUsageMiB INTEGER NOT NULL - )`) - if err != nil { - return err - } - - _, err = db.Exec(`CREATE TABLE IF NOT EXISTS BlockStats ( - Day TIMESTAMP NOT NULL, - Reports INTEGER NOT NULL, - Total BIGINT NOT NULL, - Renamed BIGINT NOT NULL, - Reused BIGINT NOT NULL, - Pulled BIGINT NOT NULL, - CopyOrigin BIGINT NOT NULL, - CopyOriginShifted BIGINT NOT NULL, - CopyElsewhere BIGINT NOT NULL - )`) - if err != nil { - return err - } - - var t string - - row := db.QueryRow(`SELECT 'UniqueDayVersionIndex'::regclass`) - if err := row.Scan(&t); err != nil { - _, _ = db.Exec(`CREATE UNIQUE INDEX UniqueDayVersionIndex ON VersionSummary (Day, Version)`) - } - - row = db.QueryRow(`SELECT 'VersionDayIndex'::regclass`) - if err := row.Scan(&t); err != nil { - _, _ = db.Exec(`CREATE INDEX VersionDayIndex ON VersionSummary (Day)`) - } - - row = db.QueryRow(`SELECT 'PerformanceDayIndex'::regclass`) - if err := row.Scan(&t); err != nil { - _, _ = db.Exec(`CREATE INDEX PerformanceDayIndex ON Performance (Day)`) - } - - row = db.QueryRow(`SELECT 'BlockStatsDayIndex'::regclass`) - if err := row.Scan(&t); err != nil { - _, _ = db.Exec(`CREATE INDEX BlockStatsDayIndex ON BlockStats (Day)`) - } - - return nil -} - -func maxIndexedDay(db *sql.DB, table string) time.Time { - var t time.Time - row := db.QueryRow("SELECT MAX(DATE_TRUNC('day', Day)) FROM " + table) - err := row.Scan(&t) - if err != nil { - return time.Time{} - } - return t -} - -func aggregateVersionSummary(db *sql.DB, since time.Time) (int64, error) { - res, err := db.Exec(`INSERT INTO VersionSummary ( - SELECT - DATE_TRUNC('day', Received) AS Day, - SUBSTRING(Report->>'version' FROM '^v\d.\d+') AS Ver, - COUNT(*) AS Count - FROM ReportsJson - WHERE - Received > $1 - AND Received < DATE_TRUNC('day', NOW()) - AND Report->>'version' like 'v_.%' - GROUP BY Day, Ver - ); - `, since) - if err != nil { - return 0, err - } - - return res.RowsAffected() -} - -func aggregatePerformance(db *sql.DB, since time.Time) (int64, error) { - res, err := db.Exec(`INSERT INTO Performance ( - SELECT - DATE_TRUNC('day', Received) AS Day, - AVG((Report->>'totFiles')::numeric) As TotFiles, - AVG((Report->>'totMiB')::numeric) As TotMiB, - AVG((Report->>'sha256Perf')::numeric) As SHA256Perf, - AVG((Report->>'memorySize')::numeric) As MemorySize, - AVG((Report->>'memoryUsageMiB')::numeric) As MemoryUsageMiB - FROM ReportsJson - WHERE - Received > $1 - AND Received < DATE_TRUNC('day', NOW()) - AND Report->>'version' like 'v_.%' - /* Some custom implementation reported bytes when we expect megabytes, cap at petabyte */ - AND (Report->>'memorySize')::numeric < 1073741824 - GROUP BY Day - ); - `, since) - if err != nil { - return 0, err - } - - return res.RowsAffected() -} - -func aggregateBlockStats(db *sql.DB, since time.Time) (int64, error) { - // Filter out anything prior 0.14.41 as that has sum aggregations which - // made no sense. - res, err := db.Exec(`INSERT INTO BlockStats ( - SELECT - DATE_TRUNC('day', Received) AS Day, - COUNT(1) As Reports, - SUM((Report->'blockStats'->>'total')::numeric)::bigint AS Total, - SUM((Report->'blockStats'->>'renamed')::numeric)::bigint AS Renamed, - SUM((Report->'blockStats'->>'reused')::numeric)::bigint AS Reused, - SUM((Report->'blockStats'->>'pulled')::numeric)::bigint AS Pulled, - SUM((Report->'blockStats'->>'copyOrigin')::numeric)::bigint AS CopyOrigin, - SUM((Report->'blockStats'->>'copyOriginShifted')::numeric)::bigint AS CopyOriginShifted, - SUM((Report->'blockStats'->>'copyElsewhere')::numeric)::bigint AS CopyElsewhere - FROM ReportsJson - WHERE - Received > $1 - AND Received < DATE_TRUNC('day', NOW()) - AND (Report->>'urVersion')::numeric >= 3 - AND Report->>'version' like 'v_.%' - AND Report->>'version' NOT LIKE 'v0.14.40%' - AND Report->>'version' NOT LIKE 'v0.14.39%' - AND Report->>'version' NOT LIKE 'v0.14.38%' - GROUP BY Day - ); - `, since) - if err != nil { - return 0, err - } - - return res.RowsAffected() -} diff --git a/cmd/infra/ursrv/main.go b/cmd/infra/ursrv/main.go index 78c4e23a7..b637d4d29 100644 --- a/cmd/infra/ursrv/main.go +++ b/cmd/infra/ursrv/main.go @@ -8,22 +8,22 @@ package main import ( "log" + "log/slog" "os" "github.com/alecthomas/kong" - "github.com/syncthing/syncthing/cmd/infra/ursrv/aggregate" "github.com/syncthing/syncthing/cmd/infra/ursrv/serve" _ "github.com/syncthing/syncthing/lib/automaxprocs" ) type CLI struct { - Serve serve.CLI `cmd:"" default:""` - Aggregate aggregate.CLI `cmd:""` + Serve serve.CLI `cmd:"" default:""` } func main() { - log.SetFlags(log.Ltime | log.Ldate | log.Lshortfile) - log.SetOutput(os.Stdout) + slog.SetDefault(slog.New(slog.NewTextHandler(os.Stdout, &slog.HandlerOptions{ + Level: slog.LevelInfo, + }))) var cli CLI ctx := kong.Parse(&cli) diff --git a/cmd/infra/ursrv/serve/analytics.go b/cmd/infra/ursrv/serve/analytics.go deleted file mode 100644 index b7c6ee189..000000000 --- a/cmd/infra/ursrv/serve/analytics.go +++ /dev/null @@ -1,276 +0,0 @@ -// Copyright (C) 2018 The Syncthing Authors. -// -// This Source Code Form is subject to the terms of the Mozilla Public -// License, v. 2.0. If a copy of the MPL was not distributed with this file, -// You can obtain one at https://mozilla.org/MPL/2.0/. - -package serve - -import ( - "regexp" - "sort" - "strconv" - "strings" -) - -type analytic struct { - Key string - Count int - Percentage float64 - Items []analytic `json:",omitempty"` -} - -type analyticList []analytic - -func (l analyticList) Less(a, b int) bool { - if l[a].Key == "Others" { - return false - } - if l[b].Key == "Others" { - return true - } - return l[b].Count < l[a].Count // inverse -} - -func (l analyticList) Swap(a, b int) { - l[a], l[b] = l[b], l[a] -} - -func (l analyticList) Len() int { - return len(l) -} - -// Returns a list of frequency analytics for a given list of strings. -func analyticsFor(ss []string, cutoff int) []analytic { - m := make(map[string]int) - t := 0 - for _, s := range ss { - m[s]++ - t++ - } - - l := make([]analytic, 0, len(m)) - for k, c := range m { - l = append(l, analytic{ - Key: k, - Count: c, - Percentage: 100 * float64(c) / float64(t), - }) - } - - sort.Sort(analyticList(l)) - - if cutoff > 0 && len(l) > cutoff { - c := 0 - for _, i := range l[cutoff:] { - c += i.Count - } - l = append(l[:cutoff], analytic{ - Key: "Others", - Count: c, - Percentage: 100 * float64(c) / float64(t), - }) - } - - return l -} - -// Find the points at which certain penetration levels are met -func penetrationLevels(as []analytic, points []float64) []analytic { - sort.Slice(as, func(a, b int) bool { - return versionLess(as[b].Key, as[a].Key) - }) - - var res []analytic - - idx := 0 - sum := 0.0 - for _, a := range as { - sum += a.Percentage - if sum >= points[idx] { - a.Count = int(points[idx]) - a.Percentage = sum - res = append(res, a) - idx++ - if idx == len(points) { - break - } - } - } - return res -} - -func statsForInts(data []int) [4]float64 { - var res [4]float64 - if len(data) == 0 { - return res - } - - sort.Ints(data) - res[0] = float64(data[int(float64(len(data))*0.05)]) - res[1] = float64(data[len(data)/2]) - res[2] = float64(data[int(float64(len(data))*0.95)]) - res[3] = float64(data[len(data)-1]) - return res -} - -func statsForInt64s(data []int64) [4]float64 { - var res [4]float64 - if len(data) == 0 { - return res - } - - sort.Slice(data, func(a, b int) bool { - return data[a] < data[b] - }) - - res[0] = float64(data[int(float64(len(data))*0.05)]) - res[1] = float64(data[len(data)/2]) - res[2] = float64(data[int(float64(len(data))*0.95)]) - res[3] = float64(data[len(data)-1]) - return res -} - -func statsForFloats(data []float64) [4]float64 { - var res [4]float64 - if len(data) == 0 { - return res - } - - sort.Float64s(data) - res[0] = data[int(float64(len(data))*0.05)] - res[1] = data[len(data)/2] - res[2] = data[int(float64(len(data))*0.95)] - res[3] = data[len(data)-1] - return res -} - -func group(by func(string) string, as []analytic, perGroup int, otherPct float64) []analytic { - var res []analytic - -next: - for _, a := range as { - group := by(a.Key) - for i := range res { - if res[i].Key == group { - res[i].Count += a.Count - res[i].Percentage += a.Percentage - if len(res[i].Items) < perGroup { - res[i].Items = append(res[i].Items, a) - } - continue next - } - } - res = append(res, analytic{ - Key: group, - Count: a.Count, - Percentage: a.Percentage, - Items: []analytic{a}, - }) - } - - sort.Sort(analyticList(res)) - - if otherPct > 0 { - // Groups with less than otherPCt go into "Other" - other := analytic{ - Key: "Other", - } - for i := 0; i < len(res); i++ { - if res[i].Percentage < otherPct || res[i].Key == "Other" { - other.Count += res[i].Count - other.Percentage += res[i].Percentage - res = append(res[:i], res[i+1:]...) - i-- - } - } - if other.Count > 0 { - res = append(res, other) - } - } - - return res -} - -func byVersion(s string) string { - parts := strings.Split(s, ".") - if len(parts) >= 2 { - return strings.Join(parts[:2], ".") - } - return s -} - -func byPlatform(s string) string { - parts := strings.Split(s, "-") - if len(parts) >= 2 { - return parts[0] - } - return s -} - -var numericGoVersion = regexp.MustCompile(`^go[0-9]\.[0-9]+`) - -func byCompiler(s string) string { - if m := numericGoVersion.FindString(s); m != "" { - return m - } - return "Other" -} - -func versionLess(a, b string) bool { - arel, apre := versionParts(a) - brel, bpre := versionParts(b) - - minlen := len(arel) - if l := len(brel); l < minlen { - minlen = l - } - - for i := 0; i < minlen; i++ { - if arel[i] != brel[i] { - return arel[i] < brel[i] - } - } - - // Longer version is newer, when the preceding parts are equal - if len(arel) != len(brel) { - return len(arel) < len(brel) - } - - if apre != bpre { - // "(+dev)" versions are ahead - if apre == plusStr { - return false - } - if bpre == plusStr { - return true - } - return apre < bpre - } - - // don't actually care how the prerelease stuff compares for our purposes - return false -} - -// Split a version as returned from transformVersion into parts. -// "1.2.3-beta.2" -> []int{1, 2, 3}, "beta.2"} -func versionParts(v string) ([]int, string) { - parts := strings.SplitN(v[1:], " ", 2) // " (+dev)" versions - if len(parts) == 1 { - parts = strings.SplitN(parts[0], "-", 2) // "-rc.1" type versions - } - fields := strings.Split(parts[0], ".") - - release := make([]int, len(fields)) - for i, s := range fields { - v, _ := strconv.Atoi(s) - release[i] = v - } - - var prerelease string - if len(parts) > 1 { - prerelease = parts[1] - } - - return release, prerelease -} diff --git a/cmd/infra/ursrv/serve/formatting.go b/cmd/infra/ursrv/serve/formatting.go deleted file mode 100644 index d8d914aa4..000000000 --- a/cmd/infra/ursrv/serve/formatting.go +++ /dev/null @@ -1,131 +0,0 @@ -// Copyright (C) 2018 The Syncthing Authors. -// -// This Source Code Form is subject to the terms of the Mozilla Public -// License, v. 2.0. If a copy of the MPL was not distributed with this file, -// You can obtain one at https://mozilla.org/MPL/2.0/. - -package serve - -import ( - "bytes" - "fmt" - "strings" -) - -type NumberType int - -const ( - NumberMetric NumberType = iota - NumberBinary - NumberDuration -) - -func number(ntype NumberType, v float64) string { - switch ntype { - case NumberMetric: - return metric(v) - case NumberDuration: - return duration(v) - case NumberBinary: - return binary(v) - default: - return metric(v) - } -} - -type suffix struct { - Suffix string - Multiplier float64 -} - -var metricSuffixes = []suffix{ - {"G", 1e9}, - {"M", 1e6}, - {"k", 1e3}, -} - -var binarySuffixes = []suffix{ - {"Gi", 1 << 30}, - {"Mi", 1 << 20}, - {"Ki", 1 << 10}, -} - -var durationSuffix = []suffix{ - {"year", 365 * 24 * 60 * 60}, - {"month", 30 * 24 * 60 * 60}, - {"day", 24 * 60 * 60}, - {"hour", 60 * 60}, - {"minute", 60}, - {"second", 1}, -} - -func metric(v float64) string { - return withSuffix(v, metricSuffixes, false) -} - -func binary(v float64) string { - return withSuffix(v, binarySuffixes, false) -} - -func duration(v float64) string { - return withSuffix(v, durationSuffix, true) -} - -func withSuffix(v float64, ps []suffix, pluralize bool) string { - for _, p := range ps { - if v >= p.Multiplier { - suffix := p.Suffix - if pluralize && v/p.Multiplier != 1.0 { - suffix += "s" - } - // If the number only has decimal zeroes, strip em off. - num := strings.TrimRight(strings.TrimRight(fmt.Sprintf("%.1f", v/p.Multiplier), "0"), ".") - return fmt.Sprintf("%s %s", num, suffix) - } - } - return strings.TrimRight(strings.TrimRight(fmt.Sprintf("%.1f", v), "0"), ".") -} - -// commatize returns a number with sep as thousands separators. Handles -// integers and plain floats. -func commatize(sep, s string) string { - // If no dot, don't do anything. - if !strings.ContainsRune(s, '.') { - return s - } - var b bytes.Buffer - fs := strings.SplitN(s, ".", 2) - - l := len(fs[0]) - for i := range fs[0] { - b.Write([]byte{s[i]}) - if i < l-1 && (l-i)%3 == 1 { - b.WriteString(sep) - } - } - - if len(fs) > 1 && len(fs[1]) > 0 { - b.WriteString(".") - b.WriteString(fs[1]) - } - - return b.String() -} - -func proportion(m map[string]int, count int) float64 { - total := 0 - isMax := true - for _, n := range m { - total += n - if n > count { - isMax = false - } - } - pct := (100 * float64(count)) / float64(total) - // To avoid rounding errors in the template, surpassing 100% and breaking - // the progress bars. - if isMax && len(m) > 1 && count != total { - pct -= 0.01 - } - return pct -} diff --git a/cmd/infra/ursrv/serve/metrics.go b/cmd/infra/ursrv/serve/metrics.go index 7c0075111..4fa8cb455 100644 --- a/cmd/infra/ursrv/serve/metrics.go +++ b/cmd/infra/ursrv/serve/metrics.go @@ -11,16 +11,36 @@ import ( "github.com/prometheus/client_golang/prometheus/promauto" ) -var metricReportsTotal = promauto.NewCounterVec(prometheus.CounterOpts{ - Namespace: "syncthing", - Subsystem: "ursrv", - Name: "reports_total", -}, []string{"version"}) +var ( + metricReportsTotal = promauto.NewCounterVec(prometheus.CounterOpts{ + Namespace: "syncthing", + Subsystem: "ursrv_v2", + Name: "incoming_reports_total", + }, []string{"result"}) + metricsCollectsTotal = promauto.NewCounter(prometheus.CounterOpts{ + Namespace: "syncthing", + Subsystem: "ursrv_v2", + Name: "collects_total", + }) + metricsCollectSecondsTotal = promauto.NewCounter(prometheus.CounterOpts{ + Namespace: "syncthing", + Subsystem: "ursrv_v2", + Name: "collect_seconds_total", + }) + metricsCollectSecondsLast = promauto.NewGauge(prometheus.GaugeOpts{ + Namespace: "syncthing", + Subsystem: "ursrv_v2", + Name: "collect_seconds_last", + }) + metricsWriteSecondsLast = promauto.NewGauge(prometheus.GaugeOpts{ + Namespace: "syncthing", + Subsystem: "ursrv_v2", + Name: "write_seconds_last", + }) +) func init() { metricReportsTotal.WithLabelValues("fail") - metricReportsTotal.WithLabelValues("duplicate") - metricReportsTotal.WithLabelValues("v1") - metricReportsTotal.WithLabelValues("v2") - metricReportsTotal.WithLabelValues("v3") + metricReportsTotal.WithLabelValues("replace") + metricReportsTotal.WithLabelValues("accept") } diff --git a/cmd/infra/ursrv/serve/prometheus.go b/cmd/infra/ursrv/serve/prometheus.go new file mode 100644 index 000000000..23014557f --- /dev/null +++ b/cmd/infra/ursrv/serve/prometheus.go @@ -0,0 +1,314 @@ +// Copyright (C) 2024 The Syncthing Authors. +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this file, +// You can obtain one at https://mozilla.org/MPL/2.0/. + +package serve + +import ( + "reflect" + "slices" + "strconv" + "strings" + "sync" + "time" + + "github.com/prometheus/client_golang/prometheus" + "github.com/syncthing/syncthing/lib/ur/contract" +) + +const namePrefix = "syncthing_usage_" + +type metricsSet struct { + srv *server + + gauges map[string]prometheus.Gauge + gaugeVecs map[string]*prometheus.GaugeVec + gaugeVecLabels map[string][]string + summaries map[string]*metricSummary + + collectMut sync.Mutex + collectCutoff time.Duration +} + +func newMetricsSet(srv *server) *metricsSet { + s := &metricsSet{ + srv: srv, + gauges: make(map[string]prometheus.Gauge), + gaugeVecs: make(map[string]*prometheus.GaugeVec), + gaugeVecLabels: make(map[string][]string), + summaries: make(map[string]*metricSummary), + collectCutoff: -24 * time.Hour, + } + + var initForType func(reflect.Type) + initForType = func(t reflect.Type) { + for i := 0; i < t.NumField(); i++ { + field := t.Field(i) + if field.Type.Kind() == reflect.Struct { + initForType(field.Type) + continue + } + name, typ, label := fieldNameTypeLabel(field) + sname, labels := nameConstLabels(name) + switch typ { + case "gauge": + s.gauges[name] = prometheus.NewGauge(prometheus.GaugeOpts{ + Name: namePrefix + sname, + ConstLabels: labels, + }) + case "summary": + s.summaries[name] = newMetricSummary(namePrefix+sname, nil, labels) + case "gaugeVec": + s.gaugeVecLabels[name] = append(s.gaugeVecLabels[name], label) + case "summaryVec": + s.summaries[name] = newMetricSummary(namePrefix+sname, []string{label}, labels) + } + } + } + initForType(reflect.ValueOf(contract.Report{}).Type()) + + for name, labels := range s.gaugeVecLabels { + s.gaugeVecs[name] = prometheus.NewGaugeVec(prometheus.GaugeOpts{ + Name: namePrefix + name, + }, labels) + } + + return s +} + +func fieldNameTypeLabel(rf reflect.StructField) (string, string, string) { + metric := rf.Tag.Get("metric") + name, typ, ok := strings.Cut(metric, ",") + if !ok { + return "", "", "" + } + gv, label, ok := strings.Cut(typ, ":") + if ok { + typ = gv + } + return name, typ, label +} + +func nameConstLabels(name string) (string, prometheus.Labels) { + if name == "-" { + return "", nil + } + name, labels, ok := strings.Cut(name, "{") + if !ok { + return name, nil + } + lls := strings.Split(labels[:len(labels)-1], ",") + m := make(map[string]string) + for _, l := range lls { + k, v, _ := strings.Cut(l, "=") + m[k] = v + } + return name, m +} + +func (s *metricsSet) addReport(r *contract.Report) { + gaugeVecs := make(map[string][]string) + s.addReportStruct(reflect.ValueOf(r).Elem(), gaugeVecs) + for name, lv := range gaugeVecs { + s.gaugeVecs[name].WithLabelValues(lv...).Add(1) + } +} + +func (s *metricsSet) addReportStruct(v reflect.Value, gaugeVecs map[string][]string) { + t := v.Type() + for i := 0; i < v.NumField(); i++ { + field := v.Field(i) + if field.Kind() == reflect.Struct { + s.addReportStruct(field, gaugeVecs) + continue + } + + name, typ, label := fieldNameTypeLabel(t.Field(i)) + switch typ { + case "gauge": + switch v := field.Interface().(type) { + case int: + s.gauges[name].Add(float64(v)) + case string: + s.gaugeVecs[name].WithLabelValues(v).Add(1) + case bool: + if v { + s.gauges[name].Add(1) + } + } + case "gaugeVec": + var labelValue string + switch v := field.Interface().(type) { + case string: + labelValue = v + case int: + labelValue = strconv.Itoa(v) + case map[string]int: + for k, v := range v { + labelValue = k + field.SetInt(int64(v)) + break + } + } + if _, ok := gaugeVecs[name]; !ok { + gaugeVecs[name] = make([]string, len(s.gaugeVecLabels[name])) + } + for i, l := range s.gaugeVecLabels[name] { + if l == label { + gaugeVecs[name][i] = labelValue + break + } + } + case "summary", "summaryVec": + switch v := field.Interface().(type) { + case int: + s.summaries[name].Observe("", float64(v)) + case float64: + s.summaries[name].Observe("", v) + case []int: + for _, v := range v { + s.summaries[name].Observe("", float64(v)) + } + case map[string]int: + for k, v := range v { + if k == "" { + // avoid empty string labels as those are the sign + // of a non-vec summary + k = "unknown" + } + s.summaries[name].Observe(k, float64(v)) + } + } + } + } +} + +func (s *metricsSet) Describe(c chan<- *prometheus.Desc) { + for _, g := range s.gauges { + g.Describe(c) + } + for _, g := range s.gaugeVecs { + g.Describe(c) + } + for _, g := range s.summaries { + g.Describe(c) + } +} + +func (s *metricsSet) Collect(c chan<- prometheus.Metric) { + s.collectMut.Lock() + defer s.collectMut.Unlock() + + t0 := time.Now() + defer func() { + dur := time.Since(t0).Seconds() + metricsCollectSecondsLast.Set(dur) + metricsCollectSecondsTotal.Add(dur) + metricsCollectsTotal.Inc() + }() + + for _, g := range s.gauges { + g.Set(0) + } + for _, g := range s.gaugeVecs { + g.Reset() + } + for _, g := range s.summaries { + g.Reset() + } + + cutoff := time.Now().Add(s.collectCutoff) + s.srv.reports.Range(func(key string, r *contract.Report) bool { + if s.collectCutoff < 0 && r.Received.Before(cutoff) { + s.srv.reports.Delete(key) + return true + } + s.addReport(r) + return true + }) + + for _, g := range s.gauges { + c <- g + } + for _, g := range s.gaugeVecs { + g.Collect(c) + } + for _, g := range s.summaries { + g.Collect(c) + } +} + +type metricSummary struct { + name string + values map[string][]float64 + zeroes map[string]int + + qDesc *prometheus.Desc + countDesc *prometheus.Desc + sumDesc *prometheus.Desc + zDesc *prometheus.Desc +} + +func newMetricSummary(name string, labels []string, constLabels prometheus.Labels) *metricSummary { + return &metricSummary{ + name: name, + values: make(map[string][]float64), + zeroes: make(map[string]int), + qDesc: prometheus.NewDesc(name, "", append(labels, "quantile"), constLabels), + countDesc: prometheus.NewDesc(name+"_nonzero_count", "", labels, constLabels), + sumDesc: prometheus.NewDesc(name+"_sum", "", labels, constLabels), + zDesc: prometheus.NewDesc(name+"_zero_count", "", labels, constLabels), + } +} + +func (q *metricSummary) Observe(labelValue string, v float64) { + if v == 0 { + q.zeroes[labelValue]++ + return + } + q.values[labelValue] = append(q.values[labelValue], v) +} + +func (q *metricSummary) Describe(c chan<- *prometheus.Desc) { + c <- q.qDesc + c <- q.countDesc + c <- q.sumDesc + c <- q.zDesc +} + +func (q *metricSummary) Collect(c chan<- prometheus.Metric) { + for lv, vs := range q.values { + var labelVals []string + if lv != "" { + labelVals = []string{lv} + } + + c <- prometheus.MustNewConstMetric(q.countDesc, prometheus.GaugeValue, float64(len(vs)), labelVals...) + c <- prometheus.MustNewConstMetric(q.zDesc, prometheus.GaugeValue, float64(q.zeroes[lv]), labelVals...) + + var sum float64 + for _, v := range vs { + sum += v + } + c <- prometheus.MustNewConstMetric(q.sumDesc, prometheus.GaugeValue, sum, labelVals...) + + if len(vs) == 0 { + return + } + + slices.Sort(vs) + c <- prometheus.MustNewConstMetric(q.qDesc, prometheus.GaugeValue, vs[0], append(labelVals, "0")...) + c <- prometheus.MustNewConstMetric(q.qDesc, prometheus.GaugeValue, vs[len(vs)*5/100], append(labelVals, "0.05")...) + c <- prometheus.MustNewConstMetric(q.qDesc, prometheus.GaugeValue, vs[len(vs)/2], append(labelVals, "0.5")...) + c <- prometheus.MustNewConstMetric(q.qDesc, prometheus.GaugeValue, vs[len(vs)*9/10], append(labelVals, "0.9")...) + c <- prometheus.MustNewConstMetric(q.qDesc, prometheus.GaugeValue, vs[len(vs)*95/100], append(labelVals, "0.95")...) + c <- prometheus.MustNewConstMetric(q.qDesc, prometheus.GaugeValue, vs[len(vs)-1], append(labelVals, "1")...) + } +} + +func (q *metricSummary) Reset() { + clear(q.values) + clear(q.zeroes) +} diff --git a/cmd/infra/ursrv/serve/serve.go b/cmd/infra/ursrv/serve/serve.go index 5e99ed14e..0986b4075 100644 --- a/cmd/infra/ursrv/serve/serve.go +++ b/cmd/infra/ursrv/serve/serve.go @@ -7,53 +7,50 @@ package serve import ( - "bytes" + "bufio" + "compress/gzip" "context" - "database/sql" - "embed" "encoding/json" + "errors" "fmt" - "html/template" "io" - "log" + "log/slog" "net" "net/http" "os" "regexp" - "sort" - "strconv" "strings" - "sync" "time" - "unicode" - _ "github.com/lib/pq" // PostgreSQL driver + _ "net/http/pprof" + + "github.com/prometheus/client_golang/prometheus" "github.com/prometheus/client_golang/prometheus/promhttp" - "golang.org/x/text/cases" - "golang.org/x/text/language" + "github.com/puzpuzpuz/xsync/v3" + "github.com/syncthing/syncthing/lib/build" "github.com/syncthing/syncthing/lib/geoip" - "github.com/syncthing/syncthing/lib/upgrade" + "github.com/syncthing/syncthing/lib/s3" "github.com/syncthing/syncthing/lib/ur/contract" ) type CLI struct { - Debug bool `env:"UR_DEBUG"` - DBConn string `env:"UR_DB_URL" default:"postgres://user:password@localhost/ur?sslmode=disable"` - Listen string `env:"UR_LISTEN" default:"0.0.0.0:8080"` - GeoIPLicenseKey string `env:"UR_GEOIP_LICENSE_KEY"` - GeoIPAccountID int `env:"UR_GEOIP_ACCOUNT_ID"` + Listen string `env:"UR_LISTEN" help:"Usage reporting & metrics endpoint listen address" default:"0.0.0.0:8080"` + ListenInternal string `env:"UR_LISTEN_INTERNAL" help:"Internal metrics endpoint listen address" default:"0.0.0.0:8082"` + GeoIPLicenseKey string `env:"UR_GEOIP_LICENSE_KEY"` + GeoIPAccountID int `env:"UR_GEOIP_ACCOUNT_ID"` + DumpFile string `env:"UR_DUMP_FILE" default:"reports.jsons.gz"` + DumpInterval time.Duration `env:"UR_DUMP_INTERVAL" default:"5m"` + + S3Endpoint string `name:"s3-endpoint" hidden:"true" env:"UR_S3_ENDPOINT"` + S3Region string `name:"s3-region" hidden:"true" env:"UR_S3_REGION"` + S3Bucket string `name:"s3-bucket" hidden:"true" env:"UR_S3_BUCKET"` + S3AccessKeyID string `name:"s3-access-key-id" hidden:"true" env:"UR_S3_ACCESS_KEY_ID"` + S3SecretKey string `name:"s3-secret-key" hidden:"true" env:"UR_S3_SECRET_KEY"` } -//go:embed static -var statics embed.FS - var ( - tpl *template.Template compilerRe = regexp.MustCompile(`\(([A-Za-z0-9()., -]+) \w+-\w+(?:| android| default)\) ([\w@.-]+)`) - progressBarClass = []string{"", "progress-bar-success", "progress-bar-info", "progress-bar-warning", "progress-bar-danger"} - featureOrder = []string{"Various", "Folder", "Device", "Connection", "GUI"} - knownVersions = []string{"v2", "v3"} knownDistributions = []distributionMatch{ // Maps well known builders to the official distribution method that // they represent @@ -71,9 +68,13 @@ var ( {regexp.MustCompile(`\sandroid-builder@github\.syncthing\.net`), "Google Play"}, {regexp.MustCompile(`\sandroid-.*teamcity@build\.syncthing\.net`), "Google Play"}, + {regexp.MustCompile(`\sandroid-.*vagrant@basebox-stretch64`), "F-Droid"}, - {regexp.MustCompile(`\svagrant@bookworm`), "F-Droid"}, {regexp.MustCompile(`\svagrant@bullseye`), "F-Droid"}, + {regexp.MustCompile(`\svagrant@bookworm`), "F-Droid"}, + + {regexp.MustCompile(`Anwender@NET2017`), "Syncthing-Fork (3rd party)"}, + {regexp.MustCompile(`\sbuilduser@(archlinux|svetlemodry)`), "Arch (3rd party)"}, {regexp.MustCompile(`\ssyncthing@archlinux`), "Arch (3rd party)"}, {regexp.MustCompile(`@debian`), "Debian (3rd party)"}, @@ -91,228 +92,188 @@ type distributionMatch struct { distribution string } -var funcs = map[string]interface{}{ - "commatize": commatize, - "number": number, - "proportion": proportion, - "counter": func() *counter { - return &counter{} - }, - "progressBarClassByIndex": func(a int) string { - return progressBarClass[a%len(progressBarClass)] - }, - "slice": func(numParts, whichPart int, input []feature) []feature { - var part []feature - perPart := (len(input) / numParts) + len(input)%2 - - parts := make([][]feature, 0, numParts) - for len(input) >= perPart { - part, input = input[:perPart], input[perPart:] - parts = append(parts, part) - } - if len(input) > 0 { - parts = append(parts, input) - } - return parts[whichPart-1] - }, -} - -func setupDB(db *sql.DB) error { - _, err := db.Exec(`CREATE TABLE IF NOT EXISTS ReportsJson ( - Received TIMESTAMP NOT NULL, - Report JSONB NOT NULL - )`) - if err != nil { - return err - } - - var t string - if err := db.QueryRow(`SELECT 'UniqueIDJsonIndex'::regclass`).Scan(&t); err != nil { - if _, err = db.Exec(`CREATE UNIQUE INDEX UniqueIDJsonIndex ON ReportsJson ((Report->>'date'), (Report->>'uniqueID'))`); err != nil { - return err - } - } - - if err := db.QueryRow(`SELECT 'ReceivedJsonIndex'::regclass`).Scan(&t); err != nil { - if _, err = db.Exec(`CREATE INDEX ReceivedJsonIndex ON ReportsJson (Received)`); err != nil { - return err - } - } - - if err := db.QueryRow(`SELECT 'ReportVersionJsonIndex'::regclass`).Scan(&t); err != nil { - if _, err = db.Exec(`CREATE INDEX ReportVersionJsonIndex ON ReportsJson (cast((Report->>'urVersion') as numeric))`); err != nil { - return err - } - } - - return nil -} - -func insertReport(db *sql.DB, r contract.Report) error { - _, err := db.Exec("INSERT INTO ReportsJson (Report, Received) VALUES ($1, $2)", r, time.Now().UTC()) - - return err -} - -type withDBFunc func(*sql.DB, http.ResponseWriter, *http.Request) - -func withDB(db *sql.DB, f withDBFunc) http.HandlerFunc { - return func(w http.ResponseWriter, r *http.Request) { - f(db, w, r) - } -} - func (cli *CLI) Run() error { - // Template - - fd, err := statics.Open("static/index.html") - if err != nil { - log.Fatalln("template:", err) - } - bs, err := io.ReadAll(fd) - if err != nil { - log.Fatalln("template:", err) - } - fd.Close() - tpl = template.Must(template.New("index.html").Funcs(funcs).Parse(string(bs))) - - // DB - - db, err := sql.Open("postgres", cli.DBConn) - if err != nil { - log.Fatalln("database:", err) - } - err = setupDB(db) - if err != nil { - log.Fatalln("database:", err) - } + slog.Info("Starting", "version", build.Version) // Listening - listener, err := net.Listen("tcp", cli.Listen) + urListener, err := net.Listen("tcp", cli.Listen) if err != nil { - log.Fatalln("listen:", err) + slog.Error("Failed to listen (usage reports)", "error", err) + return err + } + slog.Info("Listening (usage reports)", "address", urListener.Addr()) + + internalListener, err := net.Listen("tcp", cli.ListenInternal) + if err != nil { + slog.Error("Failed to listen (internal)", "error", err) + return err + } + slog.Info("Listening (internal)", "address", internalListener.Addr()) + + var geo *geoip.Provider + if cli.GeoIPAccountID != 0 && cli.GeoIPLicenseKey != "" { + geo, err = geoip.NewGeoLite2CityProvider(context.Background(), cli.GeoIPAccountID, cli.GeoIPLicenseKey, os.TempDir()) + if err != nil { + slog.Error("Failed to load GeoIP", "error", err) + return err + } + go geo.Serve(context.TODO()) } - geoip, err := geoip.NewGeoLite2CityProvider(context.Background(), cli.GeoIPAccountID, cli.GeoIPLicenseKey, os.TempDir()) - if err != nil { - log.Fatalln("geoip:", err) + // s3 + + var s3sess *s3.Session + if cli.S3Endpoint != "" { + s3sess, err = s3.NewSession(cli.S3Endpoint, cli.S3Region, cli.S3Bucket, cli.S3AccessKeyID, cli.S3SecretKey) + if err != nil { + slog.Error("Failed to create S3 session", "error", err) + return err + } } - go geoip.Serve(context.TODO()) + + if _, err := os.Stat(cli.DumpFile); err != nil && s3sess != nil { + if err := cli.downloadDumpFile(s3sess); err != nil { + slog.Error("Failed to download dump file", "error", err) + } + } + + // server srv := &server{ - db: db, - debug: cli.Debug, - geoip: geoip, + geo: geo, + reports: xsync.NewMapOf[string, *contract.Report](), } - http.HandleFunc("/", srv.rootHandler) - http.HandleFunc("/newdata", srv.newDataHandler) - http.HandleFunc("/summary.json", srv.summaryHandler) - http.HandleFunc("/performance.json", srv.performanceHandler) - http.HandleFunc("/blockstats.json", srv.blockStatsHandler) - http.HandleFunc("/locations.json", srv.locationsHandler) + + if fd, err := os.Open(cli.DumpFile); err == nil { + gr, err := gzip.NewReader(fd) + if err == nil { + srv.load(gr) + } + fd.Close() + } + + go func() { + for range time.Tick(cli.DumpInterval) { + if err := cli.saveDumpFile(srv, s3sess); err != nil { + slog.Error("Failed to write dump file", "error", err) + } + } + }() + + // The internal metrics endpoint just serves metrics about what the + // server is doing. + http.Handle("/metrics", promhttp.Handler()) - http.Handle("/static/", http.FileServer(http.FS(statics))) - go srv.cacheRefresher() - - httpSrv := http.Server{ + internalSrv := http.Server{ ReadTimeout: 5 * time.Second, WriteTimeout: 15 * time.Second, } - return httpSrv.Serve(listener) -} + go internalSrv.Serve(internalListener) -type server struct { - debug bool - db *sql.DB - geoip *geoip.Provider + // New external metrics endpoint accepts reports from clients and serves + // aggregated usage reporting metrics. - cacheMut sync.Mutex - cachedIndex []byte - cachedLocations []byte - cacheTime time.Time -} + ms := newMetricsSet(srv) + reg := prometheus.NewRegistry() + reg.MustRegister(ms) -const maxCacheTime = 15 * time.Minute + mux := http.NewServeMux() + mux.Handle("/metrics", promhttp.HandlerFor(reg, promhttp.HandlerOpts{})) + mux.HandleFunc("/newdata", srv.handleNewData) + mux.HandleFunc("/ping", srv.handlePing) -func (s *server) cacheRefresher() { - ticker := time.NewTicker(maxCacheTime - time.Minute) - defer ticker.Stop() - for ; true; <-ticker.C { - s.cacheMut.Lock() - if err := s.refreshCacheLocked(); err != nil { - log.Println(err) - } - s.cacheMut.Unlock() + metricsSrv := http.Server{ + ReadTimeout: 5 * time.Second, + WriteTimeout: 15 * time.Second, + Handler: mux, } + + slog.Info("Ready to serve") + return metricsSrv.Serve(urListener) } -func (s *server) refreshCacheLocked() error { - rep := getReport(s.db, s.geoip) - buf := new(bytes.Buffer) - err := tpl.Execute(buf, rep) +func (cli *CLI) downloadDumpFile(s3sess *s3.Session) error { + latestKey, err := s3sess.LatestKey() if err != nil { - return err + return fmt.Errorf("list latest S3 key: %w", err) } - s.cachedIndex = buf.Bytes() - s.cacheTime = time.Now() - - locs := rep["locations"].(map[location]int) - wlocs := make([]weightedLocation, 0, len(locs)) - for loc, w := range locs { - wlocs = append(wlocs, weightedLocation{loc, w}) + fd, err := os.Create(cli.DumpFile) + if err != nil { + return fmt.Errorf("create dump file: %w", err) } - s.cachedLocations, _ = json.Marshal(wlocs) + if err := s3sess.Download(fd, latestKey); err != nil { + _ = fd.Close() + return fmt.Errorf("download dump file: %w", err) + } + if err := fd.Close(); err != nil { + return fmt.Errorf("close dump file: %w", err) + } + slog.Info("Dump file downloaded", "key", latestKey) return nil } -func (s *server) rootHandler(w http.ResponseWriter, r *http.Request) { - if r.URL.Path == "/" || r.URL.Path == "/index.html" { - s.cacheMut.Lock() - defer s.cacheMut.Unlock() - - if time.Since(s.cacheTime) > maxCacheTime { - if err := s.refreshCacheLocked(); err != nil { - log.Println(err) - http.Error(w, "Template Error", http.StatusInternalServerError) - return - } - } - - w.Header().Set("Content-Type", "text/html; charset=utf-8") - w.Write(s.cachedIndex) - } else { - http.Error(w, "Not found", 404) - return +func (cli *CLI) saveDumpFile(srv *server, s3sess *s3.Session) error { + fd, err := os.Create(cli.DumpFile + ".tmp") + if err != nil { + return fmt.Errorf("creating dump file: %w", err) } -} + gw := gzip.NewWriter(fd) + if err := srv.save(gw); err != nil { + return fmt.Errorf("saving dump file: %w", err) + } + if err := gw.Close(); err != nil { + fd.Close() + return fmt.Errorf("closing gzip writer: %w", err) + } + if err := fd.Close(); err != nil { + return fmt.Errorf("closing dump file: %w", err) + } + if err := os.Rename(cli.DumpFile+".tmp", cli.DumpFile); err != nil { + return fmt.Errorf("renaming dump file: %w", err) + } + slog.Info("Dump file saved") -func (s *server) locationsHandler(w http.ResponseWriter, _ *http.Request) { - s.cacheMut.Lock() - defer s.cacheMut.Unlock() - - if time.Since(s.cacheTime) > maxCacheTime { - if err := s.refreshCacheLocked(); err != nil { - log.Println(err) - http.Error(w, "Template Error", http.StatusInternalServerError) - return + if s3sess != nil { + key := fmt.Sprintf("reports-%s.jsons.gz", time.Now().UTC().Format("2006-01-02")) + fd, err := os.Open(cli.DumpFile) + if err != nil { + return fmt.Errorf("opening dump file: %w", err) } + if err := s3sess.Upload(fd, key); err != nil { + return fmt.Errorf("uploading dump file: %w", err) + } + _ = fd.Close() + slog.Info("Dump file uploaded") } - w.Header().Set("Content-Type", "application/json; charset=utf-8") - w.Write(s.cachedLocations) + return nil } -func (s *server) newDataHandler(w http.ResponseWriter, r *http.Request) { - version := "fail" +type server struct { + geo *geoip.Provider + reports *xsync.MapOf[string, *contract.Report] +} + +func (s *server) handlePing(w http.ResponseWriter, r *http.Request) { +} + +func (s *server) handleNewData(w http.ResponseWriter, r *http.Request) { + result := "fail" defer func() { - // Version is "fail", "duplicate", "v2", "v3", ... - metricReportsTotal.WithLabelValues(version).Inc() + // result is "accept" (new report), "replace" (existing report) or + // "fail" + metricReportsTotal.WithLabelValues(result).Inc() }() defer r.Body.Close() + if r.Method != http.MethodPost { + http.Error(w, "Method Not Allowed", http.StatusMethodNotAllowed) + return + } + addr := r.Header.Get("X-Forwarded-For") if addr != "" { addr = strings.Split(addr, ", ")[0] @@ -324,576 +285,112 @@ func (s *server) newDataHandler(w http.ResponseWriter, r *http.Request) { addr = host } + log := slog.With("addr", addr) + if net.ParseIP(addr) == nil { addr = "" } var rep contract.Report - rep.Date = time.Now().UTC().Format("20060102") - rep.Address = addr lr := &io.LimitedReader{R: r.Body, N: 40 * 1024} bs, _ := io.ReadAll(lr) if err := json.Unmarshal(bs, &rep); err != nil { - log.Println("decode:", err) - if s.debug { - log.Printf("%s", bs) - } + log.Error("Failed to decode JSON", "error", err) http.Error(w, "JSON Decode Error", http.StatusInternalServerError) return } + rep.Received = time.Now() + rep.Date = rep.Received.UTC().Format("20060102") + rep.Address = addr + if err := rep.Validate(); err != nil { - log.Println("validate:", err) - if s.debug { - log.Printf("%#v", rep) - } + log.Error("Failed to validate report", "error", err) http.Error(w, "Validation Error", http.StatusInternalServerError) return } - if err := insertReport(s.db, rep); err != nil { - if err.Error() == `pq: duplicate key value violates unique constraint "uniqueidjsonindex"` { - // We already have a report today for the same unique ID; drop - // this one without complaining. - version = "duplicate" - return - } - log.Println("insert:", err) - if s.debug { - log.Printf("%#v", rep) - } - http.Error(w, "Database Error", http.StatusInternalServerError) - return + if s.addReport(&rep) { + result = "replace" + } else { + result = "accept" } - - version = fmt.Sprintf("v%d", rep.URVersion) } -func (s *server) summaryHandler(w http.ResponseWriter, r *http.Request) { - min, _ := strconv.Atoi(r.URL.Query().Get("min")) - sum, err := getSummary(s.db, min) +func (s *server) addReport(rep *contract.Report) bool { + if s.geo != nil { + if ip := net.ParseIP(rep.Address); ip != nil { + if city, err := s.geo.City(ip); err == nil { + rep.Country = city.Country.Names["en"] + rep.CountryCode = city.Country.IsoCode + } + } + } + if rep.Country == "" { + rep.Country = "Unknown" + } + if rep.CountryCode == "" { + rep.CountryCode = "ZZ" + } + + rep.Version = transformVersion(rep.Version) + if strings.Contains(rep.Version, ".") { + split := strings.SplitN(rep.Version, ".", 3) + if len(split) == 3 { + rep.MajorVersion = strings.Join(split[:2], ".") + } + } + rep.OS, rep.Arch, _ = strings.Cut(rep.Platform, "-") + + if m := compilerRe.FindStringSubmatch(rep.LongVersion); len(m) == 3 { + rep.Compiler = m[1] + rep.Builder = m[2] + } + for _, d := range knownDistributions { + if d.matcher.MatchString(rep.LongVersion) { + rep.Distribution = d.distribution + break + } + } + + _, loaded := s.reports.LoadAndStore(rep.UniqueID, rep) + return loaded +} + +func (s *server) save(w io.Writer) error { + bw := bufio.NewWriter(w) + enc := json.NewEncoder(bw) + var err error + s.reports.Range(func(k string, v *contract.Report) bool { + err = enc.Encode(v) + return err == nil + }) if err != nil { - log.Println("summaryHandler:", err) - http.Error(w, "Database Error", http.StatusInternalServerError) - return + return err } - - bs, err := sum.MarshalJSON() - if err != nil { - log.Println("summaryHandler:", err) - http.Error(w, "JSON Encode Error", http.StatusInternalServerError) - return - } - - w.Header().Set("Content-Type", "application/json") - w.Write(bs) + return bw.Flush() } -func (s *server) performanceHandler(w http.ResponseWriter, _ *http.Request) { - perf, err := getPerformance(s.db) - if err != nil { - log.Println("performanceHandler:", err) - http.Error(w, "Database Error", http.StatusInternalServerError) - return +func (s *server) load(r io.Reader) { + dec := json.NewDecoder(r) + s.reports.Clear() + for { + var rep contract.Report + if err := dec.Decode(&rep); errors.Is(err, io.EOF) { + break + } else if err != nil { + slog.Error("Failed to load record", "error", err) + break + } + s.addReport(&rep) } - - bs, err := json.Marshal(perf) - if err != nil { - log.Println("performanceHandler:", err) - http.Error(w, "JSON Encode Error", http.StatusInternalServerError) - return - } - - w.Header().Set("Content-Type", "application/json") - w.Write(bs) -} - -func (s *server) blockStatsHandler(w http.ResponseWriter, _ *http.Request) { - blocks, err := getBlockStats(s.db) - if err != nil { - log.Println("blockStatsHandler:", err) - http.Error(w, "Database Error", http.StatusInternalServerError) - return - } - - bs, err := json.Marshal(blocks) - if err != nil { - log.Println("blockStatsHandler:", err) - http.Error(w, "JSON Encode Error", http.StatusInternalServerError) - return - } - - w.Header().Set("Content-Type", "application/json") - w.Write(bs) -} - -type category struct { - Values [4]float64 - Key string - Descr string - Unit string - Type NumberType -} - -type feature struct { - Key string - Version string - Count int - Pct float64 -} - -type featureGroup struct { - Key string - Version string - Counts map[string]int -} - -// Used in the templates -type counter struct { - n int -} - -func (c *counter) Current() int { - return c.n -} - -func (c *counter) Increment() string { - c.n++ - return "" -} - -func (c *counter) DrawTwoDivider() bool { - return c.n != 0 && c.n%2 == 0 -} - -// add sets a key in a nested map, initializing things if needed as we go. -func add(storage map[string]map[string]int, parent, child string, value int) { - n, ok := storage[parent] - if !ok { - n = make(map[string]int) - storage[parent] = n - } - n[child] += value -} - -// inc makes sure that even for unused features, we initialize them in the -// feature map. Furthermore, this acts as a helper that accepts booleans -// to increment by one, or integers to increment by that integer. -func inc(storage map[string]int, key string, i interface{}) { - cv := storage[key] - switch v := i.(type) { - case bool: - if v { - cv++ - } - case int: - cv += v - } - storage[key] = cv -} - -type location struct { - Latitude float64 `json:"lat"` - Longitude float64 `json:"lon"` -} - -type weightedLocation struct { - location - Weight int `json:"weight"` -} - -func getReport(db *sql.DB, geoip *geoip.Provider) map[string]interface{} { - nodes := 0 - countriesTotal := 0 - var versions []string - var platforms []string - var numFolders []int - var numDevices []int - var totFiles []int - var maxFiles []int - var totMiB []int64 - var maxMiB []int64 - var memoryUsage []int64 - var sha256Perf []float64 - var memorySize []int64 - var uptime []int - var compilers []string - var builders []string - var distributions []string - locations := make(map[location]int) - countries := make(map[string]int) - - reports := make(map[string]int) - totals := make(map[string]int) - - // category -> version -> feature -> count - features := make(map[string]map[string]map[string]int) - // category -> version -> feature -> group -> count - featureGroups := make(map[string]map[string]map[string]map[string]int) - for _, category := range featureOrder { - features[category] = make(map[string]map[string]int) - featureGroups[category] = make(map[string]map[string]map[string]int) - for _, version := range knownVersions { - features[category][version] = make(map[string]int) - featureGroups[category][version] = make(map[string]map[string]int) - } - } - - // Initialize some features that hide behind if conditions, and might not - // be initialized. - add(featureGroups["Various"]["v2"], "Upgrades", "Pre-release", 0) - add(featureGroups["Various"]["v2"], "Upgrades", "Automatic", 0) - add(featureGroups["Various"]["v2"], "Upgrades", "Manual", 0) - add(featureGroups["Various"]["v2"], "Upgrades", "Disabled", 0) - add(featureGroups["Various"]["v3"], "Temporary Retention", "Disabled", 0) - add(featureGroups["Various"]["v3"], "Temporary Retention", "Custom", 0) - add(featureGroups["Various"]["v3"], "Temporary Retention", "Default", 0) - add(featureGroups["Connection"]["v3"], "IP version", "IPv4", 0) - add(featureGroups["Connection"]["v3"], "IP version", "IPv6", 0) - add(featureGroups["Connection"]["v3"], "IP version", "Unknown", 0) - - var numCPU []int - - var rep contract.Report - - rows, err := db.Query(`SELECT Received, Report FROM ReportsJson WHERE Received > now() - '1 day'::INTERVAL`) - if err != nil { - log.Println("sql:", err) - return nil - } - defer rows.Close() - - for rows.Next() { - err := rows.Scan(&rep.Received, &rep) - if err != nil { - log.Println("sql:", err) - return nil - } - - if geoip != nil && rep.Address != "" { - if addr, err := net.ResolveTCPAddr("tcp", net.JoinHostPort(rep.Address, "0")); err == nil { - city, err := geoip.City(addr.IP) - if err == nil { - loc := location{ - Latitude: city.Location.Latitude, - Longitude: city.Location.Longitude, - } - locations[loc]++ - countries[city.Country.Names["en"]]++ - countriesTotal++ - } - } - } - - nodes++ - versions = append(versions, transformVersion(rep.Version)) - platforms = append(platforms, rep.Platform) - - if m := compilerRe.FindStringSubmatch(rep.LongVersion); len(m) == 3 { - compilers = append(compilers, m[1]) - builders = append(builders, m[2]) - loop: - for _, d := range knownDistributions { - if d.matcher.MatchString(rep.LongVersion) { - distributions = append(distributions, d.distribution) - break loop - } - } - } - - if rep.NumFolders > 0 { - numFolders = append(numFolders, rep.NumFolders) - } - if rep.NumDevices > 0 { - numDevices = append(numDevices, rep.NumDevices) - } - if rep.TotFiles > 0 { - totFiles = append(totFiles, rep.TotFiles) - } - if rep.FolderMaxFiles > 0 { - maxFiles = append(maxFiles, rep.FolderMaxFiles) - } - if rep.TotMiB > 0 { - totMiB = append(totMiB, int64(rep.TotMiB)*(1<<20)) - } - if rep.FolderMaxMiB > 0 { - maxMiB = append(maxMiB, int64(rep.FolderMaxMiB)*(1<<20)) - } - if rep.MemoryUsageMiB > 0 { - memoryUsage = append(memoryUsage, int64(rep.MemoryUsageMiB)*(1<<20)) - } - if rep.SHA256Perf > 0 { - sha256Perf = append(sha256Perf, rep.SHA256Perf*(1<<20)) - } - if rep.MemorySize > 0 { - memorySize = append(memorySize, int64(rep.MemorySize)*(1<<20)) - } - if rep.Uptime > 0 { - uptime = append(uptime, rep.Uptime) - } - - totals["Device"] += rep.NumDevices - totals["Folder"] += rep.NumFolders - - if rep.URVersion >= 2 { - reports["v2"]++ - numCPU = append(numCPU, rep.NumCPU) - - // Various - inc(features["Various"]["v2"], "Rate limiting", rep.UsesRateLimit) - - if rep.UpgradeAllowedPre { - add(featureGroups["Various"]["v2"], "Upgrades", "Pre-release", 1) - } else if rep.UpgradeAllowedAuto { - add(featureGroups["Various"]["v2"], "Upgrades", "Automatic", 1) - } else if rep.UpgradeAllowedManual { - add(featureGroups["Various"]["v2"], "Upgrades", "Manual", 1) - } else { - add(featureGroups["Various"]["v2"], "Upgrades", "Disabled", 1) - } - - // Folders - inc(features["Folder"]["v2"], "Automatic normalization", rep.FolderUses.AutoNormalize) - inc(features["Folder"]["v2"], "Ignore deletes", rep.FolderUses.IgnoreDelete) - inc(features["Folder"]["v2"], "Ignore permissions", rep.FolderUses.IgnorePerms) - inc(features["Folder"]["v2"], "Mode, send only", rep.FolderUses.SendOnly) - inc(features["Folder"]["v2"], "Mode, receive only", rep.FolderUses.ReceiveOnly) - - add(featureGroups["Folder"]["v2"], "Versioning", "Simple", rep.FolderUses.SimpleVersioning) - add(featureGroups["Folder"]["v2"], "Versioning", "External", rep.FolderUses.ExternalVersioning) - add(featureGroups["Folder"]["v2"], "Versioning", "Staggered", rep.FolderUses.StaggeredVersioning) - add(featureGroups["Folder"]["v2"], "Versioning", "Trashcan", rep.FolderUses.TrashcanVersioning) - add(featureGroups["Folder"]["v2"], "Versioning", "Disabled", rep.NumFolders-rep.FolderUses.SimpleVersioning-rep.FolderUses.ExternalVersioning-rep.FolderUses.StaggeredVersioning-rep.FolderUses.TrashcanVersioning) - - // Device - inc(features["Device"]["v2"], "Custom certificate", rep.DeviceUses.CustomCertName) - inc(features["Device"]["v2"], "Introducer", rep.DeviceUses.Introducer) - - add(featureGroups["Device"]["v2"], "Compress", "Always", rep.DeviceUses.CompressAlways) - add(featureGroups["Device"]["v2"], "Compress", "Metadata", rep.DeviceUses.CompressMetadata) - add(featureGroups["Device"]["v2"], "Compress", "Nothing", rep.DeviceUses.CompressNever) - - add(featureGroups["Device"]["v2"], "Addresses", "Dynamic", rep.DeviceUses.DynamicAddr) - add(featureGroups["Device"]["v2"], "Addresses", "Static", rep.DeviceUses.StaticAddr) - - // Connections - inc(features["Connection"]["v2"], "Relaying, enabled", rep.Relays.Enabled) - inc(features["Connection"]["v2"], "Discovery, global enabled", rep.Announce.GlobalEnabled) - inc(features["Connection"]["v2"], "Discovery, local enabled", rep.Announce.LocalEnabled) - - add(featureGroups["Connection"]["v2"], "Discovery", "Default servers (using DNS)", rep.Announce.DefaultServersDNS) - add(featureGroups["Connection"]["v2"], "Discovery", "Default servers (using IP)", rep.Announce.DefaultServersIP) - add(featureGroups["Connection"]["v2"], "Discovery", "Other servers", rep.Announce.DefaultServersIP) - - add(featureGroups["Connection"]["v2"], "Relaying", "Default relays", rep.Relays.DefaultServers) - add(featureGroups["Connection"]["v2"], "Relaying", "Other relays", rep.Relays.OtherServers) - } - - if rep.URVersion >= 3 { - reports["v3"]++ - - inc(features["Various"]["v3"], "Custom LAN classification", rep.AlwaysLocalNets) - inc(features["Various"]["v3"], "Ignore caching", rep.CacheIgnoredFiles) - inc(features["Various"]["v3"], "Overwrite device names", rep.OverwriteRemoteDeviceNames) - inc(features["Various"]["v3"], "Download progress disabled", !rep.ProgressEmitterEnabled) - inc(features["Various"]["v3"], "Custom default path", rep.CustomDefaultFolderPath) - inc(features["Various"]["v3"], "Custom traffic class", rep.CustomTrafficClass) - inc(features["Various"]["v3"], "Custom temporary index threshold", rep.CustomTempIndexMinBlocks) - inc(features["Various"]["v3"], "Weak hash enabled", rep.WeakHashEnabled) - inc(features["Various"]["v3"], "LAN rate limiting", rep.LimitBandwidthInLan) - inc(features["Various"]["v3"], "Custom release server", rep.CustomReleaseURL) - inc(features["Various"]["v3"], "Restart after suspend", rep.RestartOnWakeup) - inc(features["Various"]["v3"], "Custom stun servers", rep.CustomStunServers) - inc(features["Various"]["v3"], "Ignore patterns", rep.IgnoreStats.Lines > 0) - - if rep.NATType != "" { - natType := rep.NATType - natType = strings.ReplaceAll(natType, "unknown", "Unknown") - natType = strings.ReplaceAll(natType, "Symetric", "Symmetric") - add(featureGroups["Various"]["v3"], "NAT Type", natType, 1) - } - - if rep.TemporariesDisabled { - add(featureGroups["Various"]["v3"], "Temporary Retention", "Disabled", 1) - } else if rep.TemporariesCustom { - add(featureGroups["Various"]["v3"], "Temporary Retention", "Custom", 1) - } else { - add(featureGroups["Various"]["v3"], "Temporary Retention", "Default", 1) - } - - inc(features["Folder"]["v3"], "Scan progress disabled", rep.FolderUsesV3.ScanProgressDisabled) - inc(features["Folder"]["v3"], "Disable sharing of partial files", rep.FolderUsesV3.DisableTempIndexes) - inc(features["Folder"]["v3"], "Disable sparse files", rep.FolderUsesV3.DisableSparseFiles) - inc(features["Folder"]["v3"], "Weak hash, always", rep.FolderUsesV3.AlwaysWeakHash) - inc(features["Folder"]["v3"], "Weak hash, custom threshold", rep.FolderUsesV3.CustomWeakHashThreshold) - inc(features["Folder"]["v3"], "Filesystem watcher", rep.FolderUsesV3.FsWatcherEnabled) - inc(features["Folder"]["v3"], "Case sensitive FS", rep.FolderUsesV3.CaseSensitiveFS) - inc(features["Folder"]["v3"], "Mode, receive encrypted", rep.FolderUsesV3.ReceiveEncrypted) - - add(featureGroups["Folder"]["v3"], "Conflicts", "Disabled", rep.FolderUsesV3.ConflictsDisabled) - add(featureGroups["Folder"]["v3"], "Conflicts", "Unlimited", rep.FolderUsesV3.ConflictsUnlimited) - add(featureGroups["Folder"]["v3"], "Conflicts", "Limited", rep.FolderUsesV3.ConflictsOther) - - for key, value := range rep.FolderUsesV3.PullOrder { - add(featureGroups["Folder"]["v3"], "Pull Order", prettyCase(key), value) - } - - for key, value := range rep.FolderUsesV3.CopyRangeMethod { - add(featureGroups["Folder"]["v3"], "Copy Range Method", prettyCase(key), value) - } - - inc(features["Device"]["v3"], "Untrusted", rep.DeviceUsesV3.Untrusted) - - totals["GUI"] += rep.GUIStats.Enabled - - inc(features["GUI"]["v3"], "Auth Enabled", rep.GUIStats.UseAuth) - inc(features["GUI"]["v3"], "TLS Enabled", rep.GUIStats.UseTLS) - inc(features["GUI"]["v3"], "Insecure Admin Access", rep.GUIStats.InsecureAdminAccess) - inc(features["GUI"]["v3"], "Skip Host check", rep.GUIStats.InsecureSkipHostCheck) - inc(features["GUI"]["v3"], "Allow Frame loading", rep.GUIStats.InsecureAllowFrameLoading) - - add(featureGroups["GUI"]["v3"], "Listen address", "Local", rep.GUIStats.ListenLocal) - add(featureGroups["GUI"]["v3"], "Listen address", "Unspecified", rep.GUIStats.ListenUnspecified) - add(featureGroups["GUI"]["v3"], "Listen address", "Other", rep.GUIStats.Enabled-rep.GUIStats.ListenLocal-rep.GUIStats.ListenUnspecified) - - for theme, count := range rep.GUIStats.Theme { - add(featureGroups["GUI"]["v3"], "Theme", prettyCase(theme), count) - } - - for transport, count := range rep.TransportStats { - add(featureGroups["Connection"]["v3"], "Transport", cases.Title(language.English).String(transport), count) - if strings.HasSuffix(transport, "4") { - add(featureGroups["Connection"]["v3"], "IP version", "IPv4", count) - } else if strings.HasSuffix(transport, "6") { - add(featureGroups["Connection"]["v3"], "IP version", "IPv6", count) - } else { - add(featureGroups["Connection"]["v3"], "IP version", "Unknown", count) - } - } - } - } - - categories := []category{ - { - Values: statsForInts(totFiles), - Descr: "Files Managed per Device", - }, { - Values: statsForInts(maxFiles), - Descr: "Files in Largest Folder", - }, { - Values: statsForInt64s(totMiB), - Descr: "Data Managed per Device", - Unit: "B", - Type: NumberBinary, - }, { - Values: statsForInt64s(maxMiB), - Descr: "Data in Largest Folder", - Unit: "B", - Type: NumberBinary, - }, { - Values: statsForInts(numDevices), - Descr: "Number of Devices in Cluster", - }, { - Values: statsForInts(numFolders), - Descr: "Number of Folders Configured", - }, { - Values: statsForInt64s(memoryUsage), - Descr: "Memory Usage", - Unit: "B", - Type: NumberBinary, - }, { - Values: statsForInt64s(memorySize), - Descr: "System Memory", - Unit: "B", - Type: NumberBinary, - }, { - Values: statsForFloats(sha256Perf), - Descr: "SHA-256 Hashing Performance", - Unit: "B/s", - Type: NumberBinary, - }, { - Values: statsForInts(numCPU), - Descr: "Number of CPU cores", - }, { - Values: statsForInts(uptime), - Descr: "Uptime (v3)", - Type: NumberDuration, - }, - } - - reportFeatures := make(map[string][]feature) - for featureType, versions := range features { - var featureList []feature - for version, featureMap := range versions { - // We count totals of the given feature type, for example number of - // folders or devices, if that doesn't exist, we work out percentage - // against the total of the version reports. Things like "Various" - // never have counts. - total, ok := totals[featureType] - if !ok { - total = reports[version] - } - for key, count := range featureMap { - featureList = append(featureList, feature{ - Key: key, - Version: version, - Count: count, - Pct: (100 * float64(count)) / float64(total), - }) - } - } - sort.Sort(sort.Reverse(sortableFeatureList(featureList))) - reportFeatures[featureType] = featureList - } - - reportFeatureGroups := make(map[string][]featureGroup) - for featureType, versions := range featureGroups { - var featureList []featureGroup - for version, featureMap := range versions { - for key, counts := range featureMap { - featureList = append(featureList, featureGroup{ - Key: key, - Version: version, - Counts: counts, - }) - } - } - reportFeatureGroups[featureType] = featureList - } - - var countryList []feature - for country, count := range countries { - countryList = append(countryList, feature{ - Key: country, - Count: count, - Pct: (100 * float64(count)) / float64(countriesTotal), - }) - sort.Sort(sort.Reverse(sortableFeatureList(countryList))) - } - - r := make(map[string]interface{}) - r["features"] = reportFeatures - r["featureGroups"] = reportFeatureGroups - r["nodes"] = nodes - r["versionNodes"] = reports - r["categories"] = categories - r["versions"] = group(byVersion, analyticsFor(versions, 2000), 5, 1.0) - r["versionPenetrations"] = penetrationLevels(analyticsFor(versions, 2000), []float64{50, 75, 90, 95}) - r["platforms"] = group(byPlatform, analyticsFor(platforms, 2000), 10, 0.0) - r["compilers"] = group(byCompiler, analyticsFor(compilers, 2000), 5, 1.0) - r["builders"] = analyticsFor(builders, 12) - r["distributions"] = analyticsFor(distributions, len(knownDistributions)) - r["featureOrder"] = featureOrder - r["locations"] = locations - r["countries"] = countryList - - return r + slog.Info("Loaded reports", "count", s.reports.Size()) } var ( - plusRe = regexp.MustCompile(`(\+.*|\.dev\..*)$`) - plusStr = "(+dev)" + plusRe = regexp.MustCompile(`(\+.*|[.-]dev\..*)$`) + plusStr = "-dev" ) // transformVersion returns a version number formatted correctly, with all @@ -905,234 +402,7 @@ func transformVersion(v string) string { if !strings.HasPrefix(v, "v") { v = "v" + v } - v = plusRe.ReplaceAllString(v, " "+plusStr) + v = plusRe.ReplaceAllString(v, plusStr) return v } - -type summary struct { - versions map[string]int // version string to count index - max map[string]int // version string to max users per day - rows map[string][]int // date to list of counts -} - -func newSummary() summary { - return summary{ - versions: make(map[string]int), - max: make(map[string]int), - rows: make(map[string][]int), - } -} - -func (s *summary) setCount(date, version string, count int) { - idx, ok := s.versions[version] - if !ok { - idx = len(s.versions) - s.versions[version] = idx - } - - if s.max[version] < count { - s.max[version] = count - } - - row := s.rows[date] - if len(row) <= idx { - old := row - row = make([]int, idx+1) - copy(row, old) - s.rows[date] = row - } - - row[idx] = count -} - -func (s *summary) MarshalJSON() ([]byte, error) { - var versions []string - for v := range s.versions { - versions = append(versions, v) - } - sort.Slice(versions, func(a, b int) bool { - return upgrade.CompareVersions(versions[a], versions[b]) < 0 - }) - - var filtered []string - for _, v := range versions { - if s.max[v] > 50 { - filtered = append(filtered, v) - } - } - versions = filtered - - headerRow := []interface{}{"Day"} - for _, v := range versions { - headerRow = append(headerRow, v) - } - - var table [][]interface{} - table = append(table, headerRow) - - var dates []string - for k := range s.rows { - dates = append(dates, k) - } - sort.Strings(dates) - - for _, date := range dates { - row := []interface{}{date} - for _, ver := range versions { - idx := s.versions[ver] - if len(s.rows[date]) > idx && s.rows[date][idx] > 0 { - row = append(row, s.rows[date][idx]) - } else { - row = append(row, nil) - } - } - table = append(table, row) - } - - return json.Marshal(table) -} - -// filter removes versions that never reach the specified min count. -func (s *summary) filter(min int) { - // We cheat and just remove the versions from the "index" and leave the - // data points alone. The version index is used to build the table when - // we do the serialization, so at that point the data points are - // filtered out as well. - for ver := range s.versions { - if s.max[ver] < min { - delete(s.versions, ver) - delete(s.max, ver) - } - } -} - -func getSummary(db *sql.DB, min int) (summary, error) { - s := newSummary() - - rows, err := db.Query(`SELECT Day, Version, Count FROM VersionSummary WHERE Day > now() - '3 year'::INTERVAL;`) - if err != nil { - return summary{}, err - } - defer rows.Close() - - for rows.Next() { - var day time.Time - var ver string - var num int - err := rows.Scan(&day, &ver, &num) - if err != nil { - return summary{}, err - } - - if ver == "v0.0" { - // ? - continue - } - - // SUPER UGLY HACK to avoid having to do sorting properly - if len(ver) == 4 && strings.HasPrefix(ver, "v0.") { // v0.x - ver = ver[:3] + "0" + ver[3:] // now v0.0x - } - - s.setCount(day.Format(time.DateOnly), ver, num) - } - - s.filter(min) - return s, nil -} - -func getPerformance(db *sql.DB) ([][]interface{}, error) { - rows, err := db.Query(`SELECT Day, TotFiles, TotMiB, SHA256Perf, MemorySize, MemoryUsageMiB FROM Performance WHERE Day > now() - '5 year'::INTERVAL ORDER BY Day`) - if err != nil { - return nil, err - } - defer rows.Close() - - res := [][]interface{}{ - {"Day", "TotFiles", "TotMiB", "SHA256Perf", "MemorySize", "MemoryUsageMiB"}, - } - - for rows.Next() { - var day time.Time - var sha256Perf float64 - var totFiles, totMiB, memorySize, memoryUsage int - err := rows.Scan(&day, &totFiles, &totMiB, &sha256Perf, &memorySize, &memoryUsage) - if err != nil { - return nil, err - } - - row := []interface{}{day.Format(time.DateOnly), totFiles, totMiB, float64(int(sha256Perf*10)) / 10, memorySize, memoryUsage} - res = append(res, row) - } - - return res, nil -} - -func getBlockStats(db *sql.DB) ([][]interface{}, error) { - rows, err := db.Query(`SELECT Day, Reports, Pulled, Renamed, Reused, CopyOrigin, CopyOriginShifted, CopyElsewhere FROM BlockStats WHERE Day > now() - '3 year'::INTERVAL ORDER BY Day`) - if err != nil { - return nil, err - } - defer rows.Close() - - res := [][]interface{}{ - {"Day", "Number of Reports", "Transferred (GiB)", "Saved by renaming files (GiB)", "Saved by resuming transfer (GiB)", "Saved by reusing data from old file (GiB)", "Saved by reusing shifted data from old file (GiB)", "Saved by reusing data from other files (GiB)"}, - } - blocksToGb := float64(8 * 1024) - for rows.Next() { - var day time.Time - var reports, pulled, renamed, reused, copyOrigin, copyOriginShifted, copyElsewhere float64 - err := rows.Scan(&day, &reports, &pulled, &renamed, &reused, ©Origin, ©OriginShifted, ©Elsewhere) - if err != nil { - return nil, err - } - // Legacy bad data on certain days - if reports <= 0 || pulled < 0 || renamed < 0 || reused < 0 || copyOrigin < 0 || copyOriginShifted < 0 || copyElsewhere < 0 { - continue - } - row := []interface{}{ - day.Format(time.DateOnly), - reports, - pulled / blocksToGb, - renamed / blocksToGb, - reused / blocksToGb, - copyOrigin / blocksToGb, - copyOriginShifted / blocksToGb, - copyElsewhere / blocksToGb, - } - res = append(res, row) - } - - return res, nil -} - -type sortableFeatureList []feature - -func (l sortableFeatureList) Len() int { - return len(l) -} - -func (l sortableFeatureList) Swap(a, b int) { - l[a], l[b] = l[b], l[a] -} - -func (l sortableFeatureList) Less(a, b int) bool { - if l[a].Pct != l[b].Pct { - return l[a].Pct < l[b].Pct - } - return l[a].Key > l[b].Key -} - -func prettyCase(input string) string { - output := "" - for i, runeValue := range input { - if i == 0 { - runeValue = unicode.ToUpper(runeValue) - } else if unicode.IsUpper(runeValue) { - output += " " - } - output += string(runeValue) - } - return output -} diff --git a/cmd/infra/ursrv/serve/static/assets/img/favicon.png b/cmd/infra/ursrv/serve/static/assets/img/favicon.png deleted file mode 100644 index 997278d6ddf8c5c98297deb2ad0b2c717c5f54c6..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4913 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 diff --git a/cmd/infra/ursrv/serve/static/bootstrap/css/bootstrap-theme.min.css b/cmd/infra/ursrv/serve/static/bootstrap/css/bootstrap-theme.min.css deleted file mode 100644 index f05c90c0a..000000000 --- a/cmd/infra/ursrv/serve/static/bootstrap/css/bootstrap-theme.min.css +++ /dev/null @@ -1,7 +0,0 @@ -/*! - * Bootstrap v3.1.0 (http://getbootstrap.com) - * Copyright 2011-2014 Twitter, Inc. - * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) - */ - -.btn-default,.btn-primary,.btn-success,.btn-info,.btn-warning,.btn-danger{text-shadow:0 -1px 0 rgba(0,0,0,0.2);-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,0.15),0 1px 1px rgba(0,0,0,0.075);box-shadow:inset 0 1px 0 rgba(255,255,255,0.15),0 1px 1px rgba(0,0,0,0.075)}.btn-default:active,.btn-primary:active,.btn-success:active,.btn-info:active,.btn-warning:active,.btn-danger:active,.btn-default.active,.btn-primary.active,.btn-success.active,.btn-info.active,.btn-warning.active,.btn-danger.active{-webkit-box-shadow:inset 0 3px 5px rgba(0,0,0,0.125);box-shadow:inset 0 3px 5px rgba(0,0,0,0.125)}.btn:active,.btn.active{background-image:none}.btn-default{background-image:-webkit-linear-gradient(top, #fff 0, #e0e0e0 100%);background-image:linear-gradient(to bottom, #fff 0, #e0e0e0 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffffff', endColorstr='#ffe0e0e0', GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled = false);background-repeat:repeat-x;border-color:#dbdbdb;text-shadow:0 1px 0 #fff;border-color:#ccc}.btn-default:hover,.btn-default:focus{background-color:#e0e0e0;background-position:0 -15px}.btn-default:active,.btn-default.active{background-color:#e0e0e0;border-color:#dbdbdb}.btn-primary{background-image:-webkit-linear-gradient(top, #428bca 0, #2d6ca2 100%);background-image:linear-gradient(to bottom, #428bca 0, #2d6ca2 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff428bca', endColorstr='#ff2d6ca2', GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled = false);background-repeat:repeat-x;border-color:#2b669a}.btn-primary:hover,.btn-primary:focus{background-color:#2d6ca2;background-position:0 -15px}.btn-primary:active,.btn-primary.active{background-color:#2d6ca2;border-color:#2b669a}.btn-success{background-image:-webkit-linear-gradient(top, #5cb85c 0, #419641 100%);background-image:linear-gradient(to bottom, #5cb85c 0, #419641 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5cb85c', endColorstr='#ff419641', GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled = false);background-repeat:repeat-x;border-color:#3e8f3e}.btn-success:hover,.btn-success:focus{background-color:#419641;background-position:0 -15px}.btn-success:active,.btn-success.active{background-color:#419641;border-color:#3e8f3e}.btn-info{background-image:-webkit-linear-gradient(top, #5bc0de 0, #2aabd2 100%);background-image:linear-gradient(to bottom, #5bc0de 0, #2aabd2 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5bc0de', endColorstr='#ff2aabd2', GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled = false);background-repeat:repeat-x;border-color:#28a4c9}.btn-info:hover,.btn-info:focus{background-color:#2aabd2;background-position:0 -15px}.btn-info:active,.btn-info.active{background-color:#2aabd2;border-color:#28a4c9}.btn-warning{background-image:-webkit-linear-gradient(top, #f0ad4e 0, #eb9316 100%);background-image:linear-gradient(to bottom, #f0ad4e 0, #eb9316 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff0ad4e', endColorstr='#ffeb9316', GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled = false);background-repeat:repeat-x;border-color:#e38d13}.btn-warning:hover,.btn-warning:focus{background-color:#eb9316;background-position:0 -15px}.btn-warning:active,.btn-warning.active{background-color:#eb9316;border-color:#e38d13}.btn-danger{background-image:-webkit-linear-gradient(top, #d9534f 0, #c12e2a 100%);background-image:linear-gradient(to bottom, #d9534f 0, #c12e2a 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9534f', endColorstr='#ffc12e2a', GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled = false);background-repeat:repeat-x;border-color:#b92c28}.btn-danger:hover,.btn-danger:focus{background-color:#c12e2a;background-position:0 -15px}.btn-danger:active,.btn-danger.active{background-color:#c12e2a;border-color:#b92c28}.thumbnail,.img-thumbnail{-webkit-box-shadow:0 1px 2px rgba(0,0,0,0.075);box-shadow:0 1px 2px rgba(0,0,0,0.075)}.dropdown-menu>li>a:hover,.dropdown-menu>li>a:focus{background-image:-webkit-linear-gradient(top, #f5f5f5 0, #e8e8e8 100%);background-image:linear-gradient(to bottom, #f5f5f5 0, #e8e8e8 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff5f5f5', endColorstr='#ffe8e8e8', GradientType=0);background-color:#e8e8e8}.dropdown-menu>.active>a,.dropdown-menu>.active>a:hover,.dropdown-menu>.active>a:focus{background-image:-webkit-linear-gradient(top, #428bca 0, #357ebd 100%);background-image:linear-gradient(to bottom, #428bca 0, #357ebd 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff428bca', endColorstr='#ff357ebd', GradientType=0);background-color:#357ebd}.navbar-default{background-image:-webkit-linear-gradient(top, #fff 0, #f8f8f8 100%);background-image:linear-gradient(to bottom, #fff 0, #f8f8f8 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffffff', endColorstr='#fff8f8f8', GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled = false);border-radius:4px;-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,0.15),0 1px 5px rgba(0,0,0,0.075);box-shadow:inset 0 1px 0 rgba(255,255,255,0.15),0 1px 5px rgba(0,0,0,0.075)}.navbar-default .navbar-nav>.active>a{background-image:-webkit-linear-gradient(top, #ebebeb 0, #f3f3f3 100%);background-image:linear-gradient(to bottom, #ebebeb 0, #f3f3f3 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffebebeb', endColorstr='#fff3f3f3', GradientType=0);-webkit-box-shadow:inset 0 3px 9px rgba(0,0,0,0.075);box-shadow:inset 0 3px 9px rgba(0,0,0,0.075)}.navbar-brand,.navbar-nav>li>a{text-shadow:0 1px 0 rgba(255,255,255,0.25)}.navbar-inverse{background-image:-webkit-linear-gradient(top, #3c3c3c 0, #222 100%);background-image:linear-gradient(to bottom, #3c3c3c 0, #222 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff3c3c3c', endColorstr='#ff222222', GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled = false)}.navbar-inverse .navbar-nav>.active>a{background-image:-webkit-linear-gradient(top, #222 0, #282828 100%);background-image:linear-gradient(to bottom, #222 0, #282828 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff222222', endColorstr='#ff282828', GradientType=0);-webkit-box-shadow:inset 0 3px 9px rgba(0,0,0,0.25);box-shadow:inset 0 3px 9px rgba(0,0,0,0.25)}.navbar-inverse .navbar-brand,.navbar-inverse .navbar-nav>li>a{text-shadow:0 -1px 0 rgba(0,0,0,0.25)}.navbar-static-top,.navbar-fixed-top,.navbar-fixed-bottom{border-radius:0}.alert{text-shadow:0 1px 0 rgba(255,255,255,0.2);-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,0.25),0 1px 2px rgba(0,0,0,0.05);box-shadow:inset 0 1px 0 rgba(255,255,255,0.25),0 1px 2px rgba(0,0,0,0.05)}.alert-success{background-image:-webkit-linear-gradient(top, #dff0d8 0, #c8e5bc 100%);background-image:linear-gradient(to bottom, #dff0d8 0, #c8e5bc 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffdff0d8', endColorstr='#ffc8e5bc', GradientType=0);border-color:#b2dba1}.alert-info{background-image:-webkit-linear-gradient(top, #d9edf7 0, #b9def0 100%);background-image:linear-gradient(to bottom, #d9edf7 0, #b9def0 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9edf7', endColorstr='#ffb9def0', GradientType=0);border-color:#9acfea}.alert-warning{background-image:-webkit-linear-gradient(top, #fcf8e3 0, #f8efc0 100%);background-image:linear-gradient(to bottom, #fcf8e3 0, #f8efc0 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fffcf8e3', endColorstr='#fff8efc0', GradientType=0);border-color:#f5e79e}.alert-danger{background-image:-webkit-linear-gradient(top, #f2dede 0, #e7c3c3 100%);background-image:linear-gradient(to bottom, #f2dede 0, #e7c3c3 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff2dede', endColorstr='#ffe7c3c3', GradientType=0);border-color:#dca7a7}.progress{background-image:-webkit-linear-gradient(top, #ebebeb 0, #f5f5f5 100%);background-image:linear-gradient(to bottom, #ebebeb 0, #f5f5f5 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffebebeb', endColorstr='#fff5f5f5', GradientType=0)}.progress-bar{background-image:-webkit-linear-gradient(top, #428bca 0, #3071a9 100%);background-image:linear-gradient(to bottom, #428bca 0, #3071a9 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff428bca', endColorstr='#ff3071a9', GradientType=0)}.progress-bar-success{background-image:-webkit-linear-gradient(top, #5cb85c 0, #449d44 100%);background-image:linear-gradient(to bottom, #5cb85c 0, #449d44 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5cb85c', endColorstr='#ff449d44', GradientType=0)}.progress-bar-info{background-image:-webkit-linear-gradient(top, #5bc0de 0, #31b0d5 100%);background-image:linear-gradient(to bottom, #5bc0de 0, #31b0d5 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5bc0de', endColorstr='#ff31b0d5', GradientType=0)}.progress-bar-warning{background-image:-webkit-linear-gradient(top, #f0ad4e 0, #ec971f 100%);background-image:linear-gradient(to bottom, #f0ad4e 0, #ec971f 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff0ad4e', endColorstr='#ffec971f', GradientType=0)}.progress-bar-danger{background-image:-webkit-linear-gradient(top, #d9534f 0, #c9302c 100%);background-image:linear-gradient(to bottom, #d9534f 0, #c9302c 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9534f', endColorstr='#ffc9302c', GradientType=0)}.list-group{border-radius:4px;-webkit-box-shadow:0 1px 2px rgba(0,0,0,0.075);box-shadow:0 1px 2px rgba(0,0,0,0.075)}.list-group-item.active,.list-group-item.active:hover,.list-group-item.active:focus{text-shadow:0 -1px 0 #3071a9;background-image:-webkit-linear-gradient(top, #428bca 0, #3278b3 100%);background-image:linear-gradient(to bottom, #428bca 0, #3278b3 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff428bca', endColorstr='#ff3278b3', GradientType=0);border-color:#3278b3}.panel{-webkit-box-shadow:0 1px 2px rgba(0,0,0,0.05);box-shadow:0 1px 2px rgba(0,0,0,0.05)}.panel-default>.panel-heading{background-image:-webkit-linear-gradient(top, #f5f5f5 0, #e8e8e8 100%);background-image:linear-gradient(to bottom, #f5f5f5 0, #e8e8e8 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff5f5f5', endColorstr='#ffe8e8e8', GradientType=0)}.panel-primary>.panel-heading{background-image:-webkit-linear-gradient(top, #428bca 0, #357ebd 100%);background-image:linear-gradient(to bottom, #428bca 0, #357ebd 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff428bca', endColorstr='#ff357ebd', GradientType=0)}.panel-success>.panel-heading{background-image:-webkit-linear-gradient(top, #dff0d8 0, #d0e9c6 100%);background-image:linear-gradient(to bottom, #dff0d8 0, #d0e9c6 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffdff0d8', endColorstr='#ffd0e9c6', GradientType=0)}.panel-info>.panel-heading{background-image:-webkit-linear-gradient(top, #d9edf7 0, #c4e3f3 100%);background-image:linear-gradient(to bottom, #d9edf7 0, #c4e3f3 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9edf7', endColorstr='#ffc4e3f3', GradientType=0)}.panel-warning>.panel-heading{background-image:-webkit-linear-gradient(top, #fcf8e3 0, #faf2cc 100%);background-image:linear-gradient(to bottom, #fcf8e3 0, #faf2cc 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fffcf8e3', endColorstr='#fffaf2cc', GradientType=0)}.panel-danger>.panel-heading{background-image:-webkit-linear-gradient(top, #f2dede 0, #ebcccc 100%);background-image:linear-gradient(to bottom, #f2dede 0, #ebcccc 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff2dede', endColorstr='#ffebcccc', GradientType=0)}.well{background-image:-webkit-linear-gradient(top, #e8e8e8 0, #f5f5f5 100%);background-image:linear-gradient(to bottom, #e8e8e8 0, #f5f5f5 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffe8e8e8', endColorstr='#fff5f5f5', GradientType=0);border-color:#dcdcdc;-webkit-box-shadow:inset 0 1px 3px rgba(0,0,0,0.05),0 1px 0 rgba(255,255,255,0.1);box-shadow:inset 0 1px 3px rgba(0,0,0,0.05),0 1px 0 rgba(255,255,255,0.1)} \ No newline at end of file diff --git a/cmd/infra/ursrv/serve/static/bootstrap/css/bootstrap.min.css b/cmd/infra/ursrv/serve/static/bootstrap/css/bootstrap.min.css deleted file mode 100644 index b8d388c19..000000000 --- a/cmd/infra/ursrv/serve/static/bootstrap/css/bootstrap.min.css +++ /dev/null @@ -1,7 +0,0 @@ -/*! - * Bootstrap v3.1.1 (http://getbootstrap.com) - * Copyright 2011-2014 Twitter, Inc. - * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) - */ - -/*! normalize.css v3.0.0 | MIT License | git.io/normalize */html{font-family:sans-serif;-ms-text-size-adjust:100%;-webkit-text-size-adjust:100%}body{margin:0}article,aside,details,figcaption,figure,footer,header,hgroup,main,nav,section,summary{display:block}audio,canvas,progress,video{display:inline-block;vertical-align:baseline}audio:not([controls]){display:none;height:0}[hidden],template{display:none}a{background:0 0}a:active,a:hover{outline:0}abbr[title]{border-bottom:1px dotted}b,strong{font-weight:700}dfn{font-style:italic}h1{font-size:2em;margin:.67em 0}mark{background:#ff0;color:#000}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sup{top:-.5em}sub{bottom:-.25em}img{border:0}svg:not(:root){overflow:hidden}figure{margin:1em 40px}hr{-moz-box-sizing:content-box;box-sizing:content-box;height:0}pre{overflow:auto}code,kbd,pre,samp{font-family:monospace,monospace;font-size:1em}button,input,optgroup,select,textarea{color:inherit;font:inherit;margin:0}button{overflow:visible}button,select{text-transform:none}button,html input[type=button],input[type=reset],input[type=submit]{-webkit-appearance:button;cursor:pointer}button[disabled],html input[disabled]{cursor:default}button::-moz-focus-inner,input::-moz-focus-inner{border:0;padding:0}input{line-height:normal}input[type=checkbox],input[type=radio]{box-sizing:border-box;padding:0}input[type=number]::-webkit-inner-spin-button,input[type=number]::-webkit-outer-spin-button{height:auto}input[type=search]{-webkit-appearance:textfield;-moz-box-sizing:content-box;-webkit-box-sizing:content-box;box-sizing:content-box}input[type=search]::-webkit-search-cancel-button,input[type=search]::-webkit-search-decoration{-webkit-appearance:none}fieldset{border:1px solid silver;margin:0 2px;padding:.35em .625em .75em}legend{border:0;padding:0}textarea{overflow:auto}optgroup{font-weight:700}table{border-collapse:collapse;border-spacing:0}td,th{padding:0}@media print{*{text-shadow:none!important;color:#000!important;background:transparent!important;box-shadow:none!important}a,a:visited{text-decoration:underline}a[href]:after{content:" (" attr(href) ")"}abbr[title]:after{content:" (" attr(title) ")"}a[href^="javascript:"]:after,a[href^="#"]:after{content:""}pre,blockquote{border:1px solid #999;page-break-inside:avoid}thead{display:table-header-group}tr,img{page-break-inside:avoid}img{max-width:100%!important}p,h2,h3{orphans:3;widows:3}h2,h3{page-break-after:avoid}select{background:#fff!important}.navbar{display:none}.table td,.table th{background-color:#fff!important}.btn>.caret,.dropup>.btn>.caret{border-top-color:#000!important}.label{border:1px solid #000}.table{border-collapse:collapse!important}.table-bordered th,.table-bordered td{border:1px solid #ddd!important}}*{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}:before,:after{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}html{font-size:62.5%;-webkit-tap-highlight-color:rgba(0,0,0,0)}body{font-family:"Helvetica Neue",Helvetica,Arial,sans-serif;font-size:16px;line-height:1.42857143;color:#333;background-color:#fff}input,button,select,textarea{font-family:inherit;font-size:inherit;line-height:inherit}a{color:#3498db;text-decoration:none}a:hover,a:focus{color:#1d6fa5;text-decoration:underline}a:focus{outline:thin dotted;outline:5px auto -webkit-focus-ring-color;outline-offset:-2px}figure{margin:0}img{vertical-align:middle}.img-responsive,.thumbnail>img,.thumbnail a>img,.carousel-inner>.item>img,.carousel-inner>.item>a>img{display:block;max-width:100%;height:auto}.img-rounded{border-radius:3px}.img-thumbnail{padding:4px;line-height:1.42857143;background-color:#fff;border:1px solid #ddd;border-radius:3px;-webkit-transition:all .2s ease-in-out;transition:all .2s ease-in-out;display:inline-block;max-width:100%;height:auto}.img-circle{border-radius:50%}hr{margin-top:22px;margin-bottom:22px;border:0;border-top:1px solid #eee}.sr-only{position:absolute;width:1px;height:1px;margin:-1px;padding:0;overflow:hidden;clip:rect(0,0,0,0);border:0}h1,h2,h3,h4,h5,h6,.h1,.h2,.h3,.h4,.h5,.h6{font-family:inherit;font-weight:400;line-height:1.1;color:inherit}h1 small,h2 small,h3 small,h4 small,h5 small,h6 small,.h1 small,.h2 small,.h3 small,.h4 small,.h5 small,.h6 small,h1 .small,h2 .small,h3 .small,h4 .small,h5 .small,h6 .small,.h1 .small,.h2 .small,.h3 .small,.h4 .small,.h5 .small,.h6 .small{font-weight:400;line-height:1;color:#999}h1,.h1,h2,.h2,h3,.h3{margin-top:22px;margin-bottom:11px}h1 small,.h1 small,h2 small,.h2 small,h3 small,.h3 small,h1 .small,.h1 .small,h2 .small,.h2 .small,h3 .small,.h3 .small{font-size:65%}h4,.h4,h5,.h5,h6,.h6{margin-top:11px;margin-bottom:11px}h4 small,.h4 small,h5 small,.h5 small,h6 small,.h6 small,h4 .small,.h4 .small,h5 .small,.h5 .small,h6 .small,.h6 .small{font-size:75%}h1,.h1{font-size:41px}h2,.h2{font-size:34px}h3,.h3{font-size:28px}h4,.h4{font-size:20px}h5,.h5{font-size:16px}h6,.h6{font-size:14px}p{margin:0 0 11px}.lead{margin-bottom:22px;font-size:18px;font-weight:200;line-height:1.4}@media (min-width:768px){.lead{font-size:24px}}small,.small{font-size:85%}cite{font-style:normal}.text-left{text-align:left}.text-right{text-align:right}.text-center{text-align:center}.text-justify{text-align:justify}.text-muted{color:#999}.text-primary{color:#3498db}a.text-primary:hover{color:#217dbb}.text-success{color:#29b74e}a.text-success:hover{color:#208e3c}.text-info{color:#7f4bab}a.text-info:hover{color:#653b87}.text-warning{color:#da8f0d}a.text-warning:hover{color:#aa6f0a}.text-danger{color:#e42533}a.text-danger:hover{color:#bf1824}.bg-primary{color:#fff;background-color:#3498db}a.bg-primary:hover{background-color:#217dbb}.bg-success{background-color:#2ecc71}a.bg-success:hover{background-color:#25a25a}.bg-info{background-color:#9b59b6}a.bg-info:hover{background-color:#804399}.bg-warning{background-color:#f1c40f}a.bg-warning:hover{background-color:#c29d0b}.bg-danger{background-color:#e74c3c}a.bg-danger:hover{background-color:#d62c1a}.page-header{padding-bottom:10px;margin:44px 0 22px;border-bottom:1px solid #eee}ul,ol{margin-top:0;margin-bottom:11px}ul ul,ol ul,ul ol,ol ol{margin-bottom:0}.list-unstyled{padding-left:0;list-style:none}.list-inline{padding-left:0;list-style:none;margin-left:-5px}.list-inline>li{display:inline-block;padding-left:5px;padding-right:5px}dl{margin-top:0;margin-bottom:22px}dt,dd{line-height:1.42857143}dt{font-weight:700}dd{margin-left:0}@media (min-width:480px){.dl-horizontal dt{float:left;width:160px;clear:left;text-align:right;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.dl-horizontal dd{margin-left:180px}}abbr[title],abbr[data-original-title]{cursor:help;border-bottom:1px dotted #999}.initialism{font-size:90%;text-transform:uppercase}blockquote{padding:11px 22px;margin:0 0 22px;font-size:20px;border-left:5px solid #eee}blockquote p:last-child,blockquote ul:last-child,blockquote ol:last-child{margin-bottom:0}blockquote footer,blockquote small,blockquote .small{display:block;font-size:80%;line-height:1.42857143;color:#999}blockquote footer:before,blockquote small:before,blockquote .small:before{content:'\2014 \00A0'}.blockquote-reverse,blockquote.pull-right{padding-right:15px;padding-left:0;border-right:5px solid #eee;border-left:0;text-align:right}.blockquote-reverse footer:before,blockquote.pull-right footer:before,.blockquote-reverse small:before,blockquote.pull-right small:before,.blockquote-reverse .small:before,blockquote.pull-right .small:before{content:''}.blockquote-reverse footer:after,blockquote.pull-right footer:after,.blockquote-reverse small:after,blockquote.pull-right small:after,.blockquote-reverse .small:after,blockquote.pull-right .small:after{content:'\00A0 \2014'}blockquote:before,blockquote:after{content:""}address{margin-bottom:22px;font-style:normal;line-height:1.42857143}code,kbd,pre,samp{font-family:Menlo,Monaco,Consolas,"Courier New",monospace}code{padding:2px 4px;font-size:90%;color:#c7254e;background-color:#f9f2f4;white-space:nowrap;border-radius:3px}kbd{padding:2px 4px;font-size:90%;color:#fff;background-color:#333;border-radius:0;box-shadow:inset 0 -1px 0 rgba(0,0,0,.25)}pre{display:block;padding:10.5px;margin:0 0 11px;font-size:15px;line-height:1.42857143;word-break:break-all;word-wrap:break-word;color:#333;background-color:#f5f5f5;border:1px solid #ccc;border-radius:3px}pre code{padding:0;font-size:inherit;color:inherit;white-space:pre-wrap;background-color:transparent;border-radius:0}.pre-scrollable{max-height:340px;overflow-y:scroll}.container{margin-right:auto;margin-left:auto;padding-left:15px;padding-right:15px}@media (min-width:768px){.container{width:750px}}@media (min-width:992px){.container{width:970px}}@media (min-width:1200px){.container{width:1170px}}.container-fluid{margin-right:auto;margin-left:auto;padding-left:15px;padding-right:15px}.row{margin-left:-15px;margin-right:-15px}.col-xs-1,.col-sm-1,.col-md-1,.col-lg-1,.col-xs-2,.col-sm-2,.col-md-2,.col-lg-2,.col-xs-3,.col-sm-3,.col-md-3,.col-lg-3,.col-xs-4,.col-sm-4,.col-md-4,.col-lg-4,.col-xs-5,.col-sm-5,.col-md-5,.col-lg-5,.col-xs-6,.col-sm-6,.col-md-6,.col-lg-6,.col-xs-7,.col-sm-7,.col-md-7,.col-lg-7,.col-xs-8,.col-sm-8,.col-md-8,.col-lg-8,.col-xs-9,.col-sm-9,.col-md-9,.col-lg-9,.col-xs-10,.col-sm-10,.col-md-10,.col-lg-10,.col-xs-11,.col-sm-11,.col-md-11,.col-lg-11,.col-xs-12,.col-sm-12,.col-md-12,.col-lg-12{position:relative;min-height:1px;padding-left:15px;padding-right:15px}.col-xs-1,.col-xs-2,.col-xs-3,.col-xs-4,.col-xs-5,.col-xs-6,.col-xs-7,.col-xs-8,.col-xs-9,.col-xs-10,.col-xs-11,.col-xs-12{float:left}.col-xs-12{width:100%}.col-xs-11{width:91.66666667%}.col-xs-10{width:83.33333333%}.col-xs-9{width:75%}.col-xs-8{width:66.66666667%}.col-xs-7{width:58.33333333%}.col-xs-6{width:50%}.col-xs-5{width:41.66666667%}.col-xs-4{width:33.33333333%}.col-xs-3{width:25%}.col-xs-2{width:16.66666667%}.col-xs-1{width:8.33333333%}.col-xs-pull-12{right:100%}.col-xs-pull-11{right:91.66666667%}.col-xs-pull-10{right:83.33333333%}.col-xs-pull-9{right:75%}.col-xs-pull-8{right:66.66666667%}.col-xs-pull-7{right:58.33333333%}.col-xs-pull-6{right:50%}.col-xs-pull-5{right:41.66666667%}.col-xs-pull-4{right:33.33333333%}.col-xs-pull-3{right:25%}.col-xs-pull-2{right:16.66666667%}.col-xs-pull-1{right:8.33333333%}.col-xs-pull-0{right:0}.col-xs-push-12{left:100%}.col-xs-push-11{left:91.66666667%}.col-xs-push-10{left:83.33333333%}.col-xs-push-9{left:75%}.col-xs-push-8{left:66.66666667%}.col-xs-push-7{left:58.33333333%}.col-xs-push-6{left:50%}.col-xs-push-5{left:41.66666667%}.col-xs-push-4{left:33.33333333%}.col-xs-push-3{left:25%}.col-xs-push-2{left:16.66666667%}.col-xs-push-1{left:8.33333333%}.col-xs-push-0{left:0}.col-xs-offset-12{margin-left:100%}.col-xs-offset-11{margin-left:91.66666667%}.col-xs-offset-10{margin-left:83.33333333%}.col-xs-offset-9{margin-left:75%}.col-xs-offset-8{margin-left:66.66666667%}.col-xs-offset-7{margin-left:58.33333333%}.col-xs-offset-6{margin-left:50%}.col-xs-offset-5{margin-left:41.66666667%}.col-xs-offset-4{margin-left:33.33333333%}.col-xs-offset-3{margin-left:25%}.col-xs-offset-2{margin-left:16.66666667%}.col-xs-offset-1{margin-left:8.33333333%}.col-xs-offset-0{margin-left:0}@media (min-width:768px){.col-sm-1,.col-sm-2,.col-sm-3,.col-sm-4,.col-sm-5,.col-sm-6,.col-sm-7,.col-sm-8,.col-sm-9,.col-sm-10,.col-sm-11,.col-sm-12{float:left}.col-sm-12{width:100%}.col-sm-11{width:91.66666667%}.col-sm-10{width:83.33333333%}.col-sm-9{width:75%}.col-sm-8{width:66.66666667%}.col-sm-7{width:58.33333333%}.col-sm-6{width:50%}.col-sm-5{width:41.66666667%}.col-sm-4{width:33.33333333%}.col-sm-3{width:25%}.col-sm-2{width:16.66666667%}.col-sm-1{width:8.33333333%}.col-sm-pull-12{right:100%}.col-sm-pull-11{right:91.66666667%}.col-sm-pull-10{right:83.33333333%}.col-sm-pull-9{right:75%}.col-sm-pull-8{right:66.66666667%}.col-sm-pull-7{right:58.33333333%}.col-sm-pull-6{right:50%}.col-sm-pull-5{right:41.66666667%}.col-sm-pull-4{right:33.33333333%}.col-sm-pull-3{right:25%}.col-sm-pull-2{right:16.66666667%}.col-sm-pull-1{right:8.33333333%}.col-sm-pull-0{right:0}.col-sm-push-12{left:100%}.col-sm-push-11{left:91.66666667%}.col-sm-push-10{left:83.33333333%}.col-sm-push-9{left:75%}.col-sm-push-8{left:66.66666667%}.col-sm-push-7{left:58.33333333%}.col-sm-push-6{left:50%}.col-sm-push-5{left:41.66666667%}.col-sm-push-4{left:33.33333333%}.col-sm-push-3{left:25%}.col-sm-push-2{left:16.66666667%}.col-sm-push-1{left:8.33333333%}.col-sm-push-0{left:0}.col-sm-offset-12{margin-left:100%}.col-sm-offset-11{margin-left:91.66666667%}.col-sm-offset-10{margin-left:83.33333333%}.col-sm-offset-9{margin-left:75%}.col-sm-offset-8{margin-left:66.66666667%}.col-sm-offset-7{margin-left:58.33333333%}.col-sm-offset-6{margin-left:50%}.col-sm-offset-5{margin-left:41.66666667%}.col-sm-offset-4{margin-left:33.33333333%}.col-sm-offset-3{margin-left:25%}.col-sm-offset-2{margin-left:16.66666667%}.col-sm-offset-1{margin-left:8.33333333%}.col-sm-offset-0{margin-left:0}}@media (min-width:992px){.col-md-1,.col-md-2,.col-md-3,.col-md-4,.col-md-5,.col-md-6,.col-md-7,.col-md-8,.col-md-9,.col-md-10,.col-md-11,.col-md-12{float:left}.col-md-12{width:100%}.col-md-11{width:91.66666667%}.col-md-10{width:83.33333333%}.col-md-9{width:75%}.col-md-8{width:66.66666667%}.col-md-7{width:58.33333333%}.col-md-6{width:50%}.col-md-5{width:41.66666667%}.col-md-4{width:33.33333333%}.col-md-3{width:25%}.col-md-2{width:16.66666667%}.col-md-1{width:8.33333333%}.col-md-pull-12{right:100%}.col-md-pull-11{right:91.66666667%}.col-md-pull-10{right:83.33333333%}.col-md-pull-9{right:75%}.col-md-pull-8{right:66.66666667%}.col-md-pull-7{right:58.33333333%}.col-md-pull-6{right:50%}.col-md-pull-5{right:41.66666667%}.col-md-pull-4{right:33.33333333%}.col-md-pull-3{right:25%}.col-md-pull-2{right:16.66666667%}.col-md-pull-1{right:8.33333333%}.col-md-pull-0{right:0}.col-md-push-12{left:100%}.col-md-push-11{left:91.66666667%}.col-md-push-10{left:83.33333333%}.col-md-push-9{left:75%}.col-md-push-8{left:66.66666667%}.col-md-push-7{left:58.33333333%}.col-md-push-6{left:50%}.col-md-push-5{left:41.66666667%}.col-md-push-4{left:33.33333333%}.col-md-push-3{left:25%}.col-md-push-2{left:16.66666667%}.col-md-push-1{left:8.33333333%}.col-md-push-0{left:0}.col-md-offset-12{margin-left:100%}.col-md-offset-11{margin-left:91.66666667%}.col-md-offset-10{margin-left:83.33333333%}.col-md-offset-9{margin-left:75%}.col-md-offset-8{margin-left:66.66666667%}.col-md-offset-7{margin-left:58.33333333%}.col-md-offset-6{margin-left:50%}.col-md-offset-5{margin-left:41.66666667%}.col-md-offset-4{margin-left:33.33333333%}.col-md-offset-3{margin-left:25%}.col-md-offset-2{margin-left:16.66666667%}.col-md-offset-1{margin-left:8.33333333%}.col-md-offset-0{margin-left:0}}@media (min-width:1200px){.col-lg-1,.col-lg-2,.col-lg-3,.col-lg-4,.col-lg-5,.col-lg-6,.col-lg-7,.col-lg-8,.col-lg-9,.col-lg-10,.col-lg-11,.col-lg-12{float:left}.col-lg-12{width:100%}.col-lg-11{width:91.66666667%}.col-lg-10{width:83.33333333%}.col-lg-9{width:75%}.col-lg-8{width:66.66666667%}.col-lg-7{width:58.33333333%}.col-lg-6{width:50%}.col-lg-5{width:41.66666667%}.col-lg-4{width:33.33333333%}.col-lg-3{width:25%}.col-lg-2{width:16.66666667%}.col-lg-1{width:8.33333333%}.col-lg-pull-12{right:100%}.col-lg-pull-11{right:91.66666667%}.col-lg-pull-10{right:83.33333333%}.col-lg-pull-9{right:75%}.col-lg-pull-8{right:66.66666667%}.col-lg-pull-7{right:58.33333333%}.col-lg-pull-6{right:50%}.col-lg-pull-5{right:41.66666667%}.col-lg-pull-4{right:33.33333333%}.col-lg-pull-3{right:25%}.col-lg-pull-2{right:16.66666667%}.col-lg-pull-1{right:8.33333333%}.col-lg-pull-0{right:0}.col-lg-push-12{left:100%}.col-lg-push-11{left:91.66666667%}.col-lg-push-10{left:83.33333333%}.col-lg-push-9{left:75%}.col-lg-push-8{left:66.66666667%}.col-lg-push-7{left:58.33333333%}.col-lg-push-6{left:50%}.col-lg-push-5{left:41.66666667%}.col-lg-push-4{left:33.33333333%}.col-lg-push-3{left:25%}.col-lg-push-2{left:16.66666667%}.col-lg-push-1{left:8.33333333%}.col-lg-push-0{left:0}.col-lg-offset-12{margin-left:100%}.col-lg-offset-11{margin-left:91.66666667%}.col-lg-offset-10{margin-left:83.33333333%}.col-lg-offset-9{margin-left:75%}.col-lg-offset-8{margin-left:66.66666667%}.col-lg-offset-7{margin-left:58.33333333%}.col-lg-offset-6{margin-left:50%}.col-lg-offset-5{margin-left:41.66666667%}.col-lg-offset-4{margin-left:33.33333333%}.col-lg-offset-3{margin-left:25%}.col-lg-offset-2{margin-left:16.66666667%}.col-lg-offset-1{margin-left:8.33333333%}.col-lg-offset-0{margin-left:0}}table{max-width:100%;background-color:transparent}th{text-align:left}.table{width:100%;margin-bottom:22px}.table>thead>tr>th,.table>tbody>tr>th,.table>tfoot>tr>th,.table>thead>tr>td,.table>tbody>tr>td,.table>tfoot>tr>td{padding:8px;line-height:1.42857143;vertical-align:top;border-top:1px solid #ddd}.table>thead>tr>th{vertical-align:bottom;border-bottom:2px solid #ddd}.table>caption+thead>tr:first-child>th,.table>colgroup+thead>tr:first-child>th,.table>thead:first-child>tr:first-child>th,.table>caption+thead>tr:first-child>td,.table>colgroup+thead>tr:first-child>td,.table>thead:first-child>tr:first-child>td{border-top:0}.table>tbody+tbody{border-top:2px solid #ddd}.table .table{background-color:#fff}.table-condensed>thead>tr>th,.table-condensed>tbody>tr>th,.table-condensed>tfoot>tr>th,.table-condensed>thead>tr>td,.table-condensed>tbody>tr>td,.table-condensed>tfoot>tr>td{padding:3px}.table-bordered{border:1px solid #ddd}.table-bordered>thead>tr>th,.table-bordered>tbody>tr>th,.table-bordered>tfoot>tr>th,.table-bordered>thead>tr>td,.table-bordered>tbody>tr>td,.table-bordered>tfoot>tr>td{border:1px solid #ddd}.table-bordered>thead>tr>th,.table-bordered>thead>tr>td{border-bottom-width:2px}.table-striped>tbody>tr:nth-child(odd)>td,.table-striped>tbody>tr:nth-child(odd)>th{background-color:#f9f9f9}.table-hover>tbody>tr:hover>td,.table-hover>tbody>tr:hover>th{background-color:#f5f5f5}table col[class*=col-]{position:static;float:none;display:table-column}table td[class*=col-],table th[class*=col-]{position:static;float:none;display:table-cell}.table>thead>tr>td.active,.table>tbody>tr>td.active,.table>tfoot>tr>td.active,.table>thead>tr>th.active,.table>tbody>tr>th.active,.table>tfoot>tr>th.active,.table>thead>tr.active>td,.table>tbody>tr.active>td,.table>tfoot>tr.active>td,.table>thead>tr.active>th,.table>tbody>tr.active>th,.table>tfoot>tr.active>th{background-color:#f5f5f5}.table-hover>tbody>tr>td.active:hover,.table-hover>tbody>tr>th.active:hover,.table-hover>tbody>tr.active:hover>td,.table-hover>tbody>tr.active:hover>th{background-color:#e8e8e8}.table>thead>tr>td.success,.table>tbody>tr>td.success,.table>tfoot>tr>td.success,.table>thead>tr>th.success,.table>tbody>tr>th.success,.table>tfoot>tr>th.success,.table>thead>tr.success>td,.table>tbody>tr.success>td,.table>tfoot>tr.success>td,.table>thead>tr.success>th,.table>tbody>tr.success>th,.table>tfoot>tr.success>th{background-color:#2ecc71}.table-hover>tbody>tr>td.success:hover,.table-hover>tbody>tr>th.success:hover,.table-hover>tbody>tr.success:hover>td,.table-hover>tbody>tr.success:hover>th{background-color:#29b765}.table>thead>tr>td.info,.table>tbody>tr>td.info,.table>tfoot>tr>td.info,.table>thead>tr>th.info,.table>tbody>tr>th.info,.table>tfoot>tr>th.info,.table>thead>tr.info>td,.table>tbody>tr.info>td,.table>tfoot>tr.info>td,.table>thead>tr.info>th,.table>tbody>tr.info>th,.table>tfoot>tr.info>th{background-color:#9b59b6}.table-hover>tbody>tr>td.info:hover,.table-hover>tbody>tr>th.info:hover,.table-hover>tbody>tr.info:hover>td,.table-hover>tbody>tr.info:hover>th{background-color:#8f4bab}.table>thead>tr>td.warning,.table>tbody>tr>td.warning,.table>tfoot>tr>td.warning,.table>thead>tr>th.warning,.table>tbody>tr>th.warning,.table>tfoot>tr>th.warning,.table>thead>tr.warning>td,.table>tbody>tr.warning>td,.table>tfoot>tr.warning>td,.table>thead>tr.warning>th,.table>tbody>tr.warning>th,.table>tfoot>tr.warning>th{background-color:#f1c40f}.table-hover>tbody>tr>td.warning:hover,.table-hover>tbody>tr>th.warning:hover,.table-hover>tbody>tr.warning:hover>td,.table-hover>tbody>tr.warning:hover>th{background-color:#dab10d}.table>thead>tr>td.danger,.table>tbody>tr>td.danger,.table>tfoot>tr>td.danger,.table>thead>tr>th.danger,.table>tbody>tr>th.danger,.table>tfoot>tr>th.danger,.table>thead>tr.danger>td,.table>tbody>tr.danger>td,.table>tfoot>tr.danger>td,.table>thead>tr.danger>th,.table>tbody>tr.danger>th,.table>tfoot>tr.danger>th{background-color:#e74c3c}.table-hover>tbody>tr>td.danger:hover,.table-hover>tbody>tr>th.danger:hover,.table-hover>tbody>tr.danger:hover>td,.table-hover>tbody>tr.danger:hover>th{background-color:#e43725}@media (max-width:767px){.table-responsive{width:100%;margin-bottom:16.5px;overflow-y:hidden;overflow-x:scroll;-ms-overflow-style:-ms-autohiding-scrollbar;border:1px solid #ddd;-webkit-overflow-scrolling:touch}.table-responsive>.table{margin-bottom:0}.table-responsive>.table>thead>tr>th,.table-responsive>.table>tbody>tr>th,.table-responsive>.table>tfoot>tr>th,.table-responsive>.table>thead>tr>td,.table-responsive>.table>tbody>tr>td,.table-responsive>.table>tfoot>tr>td{white-space:nowrap}.table-responsive>.table-bordered{border:0}.table-responsive>.table-bordered>thead>tr>th:first-child,.table-responsive>.table-bordered>tbody>tr>th:first-child,.table-responsive>.table-bordered>tfoot>tr>th:first-child,.table-responsive>.table-bordered>thead>tr>td:first-child,.table-responsive>.table-bordered>tbody>tr>td:first-child,.table-responsive>.table-bordered>tfoot>tr>td:first-child{border-left:0}.table-responsive>.table-bordered>thead>tr>th:last-child,.table-responsive>.table-bordered>tbody>tr>th:last-child,.table-responsive>.table-bordered>tfoot>tr>th:last-child,.table-responsive>.table-bordered>thead>tr>td:last-child,.table-responsive>.table-bordered>tbody>tr>td:last-child,.table-responsive>.table-bordered>tfoot>tr>td:last-child{border-right:0}.table-responsive>.table-bordered>tbody>tr:last-child>th,.table-responsive>.table-bordered>tfoot>tr:last-child>th,.table-responsive>.table-bordered>tbody>tr:last-child>td,.table-responsive>.table-bordered>tfoot>tr:last-child>td{border-bottom:0}}fieldset{padding:0;margin:0;border:0;min-width:0}legend{display:block;width:100%;padding:0;margin-bottom:22px;font-size:24px;line-height:inherit;color:#333;border:0;border-bottom:1px solid #e5e5e5}label{display:inline-block;margin-bottom:5px;font-weight:700}input[type=search]{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}input[type=radio],input[type=checkbox]{margin:4px 0 0;margin-top:1px \9;line-height:normal}input[type=file]{display:block}input[type=range]{display:block;width:100%}select[multiple],select[size]{height:auto}input[type=file]:focus,input[type=radio]:focus,input[type=checkbox]:focus{outline:thin dotted;outline:5px auto -webkit-focus-ring-color;outline-offset:-2px}output{display:block;padding-top:7px;font-size:16px;line-height:1.42857143;color:#555}.form-control{display:block;width:100%;height:36px;padding:6px 12px;font-size:16px;line-height:1.42857143;color:#555;background-color:#fff;background-image:none;border:1px solid #ccc;border-radius:3px;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075);box-shadow:inset 0 1px 1px rgba(0,0,0,.075);-webkit-transition:border-color ease-in-out .15s,box-shadow ease-in-out .15s;transition:border-color ease-in-out .15s,box-shadow ease-in-out .15s}.form-control:focus{border-color:#66afe9;outline:0;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 8px rgba(102,175,233,.6);box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 8px rgba(102,175,233,.6)}.form-control::-moz-placeholder{color:#999;opacity:1}.form-control:-ms-input-placeholder{color:#999}.form-control::-webkit-input-placeholder{color:#999}.form-control[disabled],.form-control[readonly],fieldset[disabled] .form-control{cursor:not-allowed;background-color:#eee;opacity:1}textarea.form-control{height:auto}input[type=search]{-webkit-appearance:none}input[type=date]{line-height:36px}.form-group{margin-bottom:15px}.radio,.checkbox{display:block;min-height:22px;margin-top:10px;margin-bottom:10px;padding-left:20px}.radio label,.checkbox label{display:inline;font-weight:400;cursor:pointer}.radio input[type=radio],.radio-inline input[type=radio],.checkbox input[type=checkbox],.checkbox-inline input[type=checkbox]{float:left;margin-left:-20px}.radio+.radio,.checkbox+.checkbox{margin-top:-5px}.radio-inline,.checkbox-inline{display:inline-block;padding-left:20px;margin-bottom:0;vertical-align:middle;font-weight:400;cursor:pointer}.radio-inline+.radio-inline,.checkbox-inline+.checkbox-inline{margin-top:0;margin-left:10px}input[type=radio][disabled],input[type=checkbox][disabled],.radio[disabled],.radio-inline[disabled],.checkbox[disabled],.checkbox-inline[disabled],fieldset[disabled] input[type=radio],fieldset[disabled] input[type=checkbox],fieldset[disabled] .radio,fieldset[disabled] .radio-inline,fieldset[disabled] .checkbox,fieldset[disabled] .checkbox-inline{cursor:not-allowed}.input-sm{height:33px;padding:5px 10px;font-size:14px;line-height:1.5;border-radius:0}select.input-sm{height:33px;line-height:33px}textarea.input-sm,select[multiple].input-sm{height:auto}.input-lg{height:49px;padding:10px 16px;font-size:20px;line-height:1.33;border-radius:3px}select.input-lg{height:49px;line-height:49px}textarea.input-lg,select[multiple].input-lg{height:auto}.has-feedback{position:relative}.has-feedback .form-control{padding-right:45px}.has-feedback .form-control-feedback{position:absolute;top:27px;right:0;display:block;width:36px;height:36px;line-height:36px;text-align:center}.has-success .help-block,.has-success .control-label,.has-success .radio,.has-success .checkbox,.has-success .radio-inline,.has-success .checkbox-inline{color:#29b74e}.has-success .form-control{border-color:#29b74e;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075);box-shadow:inset 0 1px 1px rgba(0,0,0,.075)}.has-success .form-control:focus{border-color:#208e3c;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 6px #69dd87;box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 6px #69dd87}.has-success .input-group-addon{color:#29b74e;border-color:#29b74e;background-color:#2ecc71}.has-success .form-control-feedback{color:#29b74e}.has-warning .help-block,.has-warning .control-label,.has-warning .radio,.has-warning .checkbox,.has-warning .radio-inline,.has-warning .checkbox-inline{color:#da8f0d}.has-warning .form-control{border-color:#da8f0d;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075);box-shadow:inset 0 1px 1px rgba(0,0,0,.075)}.has-warning .form-control:focus{border-color:#aa6f0a;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 6px #f5bb57;box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 6px #f5bb57}.has-warning .input-group-addon{color:#da8f0d;border-color:#da8f0d;background-color:#f1c40f}.has-warning .form-control-feedback{color:#da8f0d}.has-error .help-block,.has-error .control-label,.has-error .radio,.has-error .checkbox,.has-error .radio-inline,.has-error .checkbox-inline{color:#e42533}.has-error .form-control{border-color:#e42533;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075);box-shadow:inset 0 1px 1px rgba(0,0,0,.075)}.has-error .form-control:focus{border-color:#bf1824;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 6px #ef8088;box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 6px #ef8088}.has-error .input-group-addon{color:#e42533;border-color:#e42533;background-color:#e74c3c}.has-error .form-control-feedback{color:#e42533}.form-control-static{margin-bottom:0}.help-block{display:block;margin-top:5px;margin-bottom:10px;color:#737373}@media (min-width:768px){.form-inline .form-group{display:inline-block;margin-bottom:0;vertical-align:middle}.form-inline .form-control{display:inline-block;width:auto;vertical-align:middle}.form-inline .input-group>.form-control{width:100%}.form-inline .control-label{margin-bottom:0;vertical-align:middle}.form-inline .radio,.form-inline .checkbox{display:inline-block;margin-top:0;margin-bottom:0;padding-left:0;vertical-align:middle}.form-inline .radio input[type=radio],.form-inline .checkbox input[type=checkbox]{float:none;margin-left:0}.form-inline .has-feedback .form-control-feedback{top:0}}.form-horizontal .control-label,.form-horizontal .radio,.form-horizontal .checkbox,.form-horizontal .radio-inline,.form-horizontal .checkbox-inline{margin-top:0;margin-bottom:0;padding-top:7px}.form-horizontal .radio,.form-horizontal .checkbox{min-height:29px}.form-horizontal .form-group{margin-left:-15px;margin-right:-15px}.form-horizontal .form-control-static{padding-top:7px}@media (min-width:768px){.form-horizontal .control-label{text-align:right}}.form-horizontal .has-feedback .form-control-feedback{top:0;right:15px}.btn{display:inline-block;margin-bottom:0;font-weight:400;text-align:center;vertical-align:middle;cursor:pointer;background-image:none;border:1px solid transparent;white-space:nowrap;padding:6px 12px;font-size:16px;line-height:1.42857143;border-radius:3px;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.btn:focus,.btn:active:focus,.btn.active:focus{outline:thin dotted;outline:5px auto -webkit-focus-ring-color;outline-offset:-2px}.btn:hover,.btn:focus{color:#333;text-decoration:none}.btn:active,.btn.active{outline:0;background-image:none;-webkit-box-shadow:inset 0 3px 5px rgba(0,0,0,.125);box-shadow:inset 0 3px 5px rgba(0,0,0,.125)}.btn.disabled,.btn[disabled],fieldset[disabled] .btn{cursor:not-allowed;pointer-events:none;opacity:.65;filter:alpha(opacity=65);-webkit-box-shadow:none;box-shadow:none}.btn-default{color:#333;background-color:#fff;border-color:#ccc}.btn-default:hover,.btn-default:focus,.btn-default:active,.btn-default.active,.open .dropdown-toggle.btn-default{color:#333;background-color:#ebebeb;border-color:#adadad}.btn-default:active,.btn-default.active,.open .dropdown-toggle.btn-default{background-image:none}.btn-default.disabled,.btn-default[disabled],fieldset[disabled] .btn-default,.btn-default.disabled:hover,.btn-default[disabled]:hover,fieldset[disabled] .btn-default:hover,.btn-default.disabled:focus,.btn-default[disabled]:focus,fieldset[disabled] .btn-default:focus,.btn-default.disabled:active,.btn-default[disabled]:active,fieldset[disabled] .btn-default:active,.btn-default.disabled.active,.btn-default[disabled].active,fieldset[disabled] .btn-default.active{background-color:#fff;border-color:#ccc}.btn-default .badge{color:#fff;background-color:#333}.btn-primary{color:#fff;background-color:#3498db;border-color:#258cd1}.btn-primary:hover,.btn-primary:focus,.btn-primary:active,.btn-primary.active,.open .dropdown-toggle.btn-primary{color:#fff;background-color:#2383c4;border-color:#1c699d}.btn-primary:active,.btn-primary.active,.open .dropdown-toggle.btn-primary{background-image:none}.btn-primary.disabled,.btn-primary[disabled],fieldset[disabled] .btn-primary,.btn-primary.disabled:hover,.btn-primary[disabled]:hover,fieldset[disabled] .btn-primary:hover,.btn-primary.disabled:focus,.btn-primary[disabled]:focus,fieldset[disabled] .btn-primary:focus,.btn-primary.disabled:active,.btn-primary[disabled]:active,fieldset[disabled] .btn-primary:active,.btn-primary.disabled.active,.btn-primary[disabled].active,fieldset[disabled] .btn-primary.active{background-color:#3498db;border-color:#258cd1}.btn-primary .badge{color:#3498db;background-color:#fff}.btn-success{color:#fff;background-color:#2ecc71;border-color:#29b765}.btn-success:hover,.btn-success:focus,.btn-success:active,.btn-success.active,.open .dropdown-toggle.btn-success{color:#fff;background-color:#26ab5f;border-color:#1e854a}.btn-success:active,.btn-success.active,.open .dropdown-toggle.btn-success{background-image:none}.btn-success.disabled,.btn-success[disabled],fieldset[disabled] .btn-success,.btn-success.disabled:hover,.btn-success[disabled]:hover,fieldset[disabled] .btn-success:hover,.btn-success.disabled:focus,.btn-success[disabled]:focus,fieldset[disabled] .btn-success:focus,.btn-success.disabled:active,.btn-success[disabled]:active,fieldset[disabled] .btn-success:active,.btn-success.disabled.active,.btn-success[disabled].active,fieldset[disabled] .btn-success.active{background-color:#2ecc71;border-color:#29b765}.btn-success .badge{color:#2ecc71;background-color:#fff}.btn-info{color:#fff;background-color:#9b59b6;border-color:#8f4bab}.btn-info:hover,.btn-info:focus,.btn-info:active,.btn-info.active,.open .dropdown-toggle.btn-info{color:#fff;background-color:#8646a0;border-color:#6b3880}.btn-info:active,.btn-info.active,.open .dropdown-toggle.btn-info{background-image:none}.btn-info.disabled,.btn-info[disabled],fieldset[disabled] .btn-info,.btn-info.disabled:hover,.btn-info[disabled]:hover,fieldset[disabled] .btn-info:hover,.btn-info.disabled:focus,.btn-info[disabled]:focus,fieldset[disabled] .btn-info:focus,.btn-info.disabled:active,.btn-info[disabled]:active,fieldset[disabled] .btn-info:active,.btn-info.disabled.active,.btn-info[disabled].active,fieldset[disabled] .btn-info.active{background-color:#9b59b6;border-color:#8f4bab}.btn-info .badge{color:#9b59b6;background-color:#fff}.btn-warning{color:#fff;background-color:#f1c40f;border-color:#dab10d}.btn-warning:hover,.btn-warning:focus,.btn-warning:active,.btn-warning.active,.open .dropdown-toggle.btn-warning{color:#fff;background-color:#cba50c;border-color:#a08209}.btn-warning:active,.btn-warning.active,.open .dropdown-toggle.btn-warning{background-image:none}.btn-warning.disabled,.btn-warning[disabled],fieldset[disabled] .btn-warning,.btn-warning.disabled:hover,.btn-warning[disabled]:hover,fieldset[disabled] .btn-warning:hover,.btn-warning.disabled:focus,.btn-warning[disabled]:focus,fieldset[disabled] .btn-warning:focus,.btn-warning.disabled:active,.btn-warning[disabled]:active,fieldset[disabled] .btn-warning:active,.btn-warning.disabled.active,.btn-warning[disabled].active,fieldset[disabled] .btn-warning.active{background-color:#f1c40f;border-color:#dab10d}.btn-warning .badge{color:#f1c40f;background-color:#fff}.btn-danger{color:#fff;background-color:#e74c3c;border-color:#e43725}.btn-danger:hover,.btn-danger:focus,.btn-danger:active,.btn-danger.active,.open .dropdown-toggle.btn-danger{color:#fff;background-color:#df2e1b;border-color:#b62516}.btn-danger:active,.btn-danger.active,.open .dropdown-toggle.btn-danger{background-image:none}.btn-danger.disabled,.btn-danger[disabled],fieldset[disabled] .btn-danger,.btn-danger.disabled:hover,.btn-danger[disabled]:hover,fieldset[disabled] .btn-danger:hover,.btn-danger.disabled:focus,.btn-danger[disabled]:focus,fieldset[disabled] .btn-danger:focus,.btn-danger.disabled:active,.btn-danger[disabled]:active,fieldset[disabled] .btn-danger:active,.btn-danger.disabled.active,.btn-danger[disabled].active,fieldset[disabled] .btn-danger.active{background-color:#e74c3c;border-color:#e43725}.btn-danger .badge{color:#e74c3c;background-color:#fff}.btn-link{color:#3498db;font-weight:400;cursor:pointer;border-radius:0}.btn-link,.btn-link:active,.btn-link[disabled],fieldset[disabled] .btn-link{background-color:transparent;-webkit-box-shadow:none;box-shadow:none}.btn-link,.btn-link:hover,.btn-link:focus,.btn-link:active{border-color:transparent}.btn-link:hover,.btn-link:focus{color:#1d6fa5;text-decoration:underline;background-color:transparent}.btn-link[disabled]:hover,fieldset[disabled] .btn-link:hover,.btn-link[disabled]:focus,fieldset[disabled] .btn-link:focus{color:#999;text-decoration:none}.btn-lg,.btn-group-lg>.btn{padding:10px 16px;font-size:20px;line-height:1.33;border-radius:3px}.btn-sm,.btn-group-sm>.btn{padding:5px 10px;font-size:14px;line-height:1.5;border-radius:0}.btn-xs,.btn-group-xs>.btn{padding:1px 5px;font-size:14px;line-height:1.5;border-radius:0}.btn-block{display:block;width:100%;padding-left:0;padding-right:0}.btn-block+.btn-block{margin-top:5px}input[type=submit].btn-block,input[type=reset].btn-block,input[type=button].btn-block{width:100%}.fade{opacity:0;-webkit-transition:opacity .15s linear;transition:opacity .15s linear}.fade.in{opacity:1}.collapse{display:none}.collapse.in{display:block}.collapsing{position:relative;height:0;overflow:hidden;-webkit-transition:height .35s ease;transition:height .35s ease}@font-face{font-family:'Glyphicons Halflings';src:url(../fonts/glyphicons-halflings-regular.eot);src:url(../fonts/glyphicons-halflings-regular.eot?#iefix) format('embedded-opentype'),url(../fonts/glyphicons-halflings-regular.woff) format('woff'),url(../fonts/glyphicons-halflings-regular.ttf) format('truetype'),url(../fonts/glyphicons-halflings-regular.svg#glyphicons_halflingsregular) format('svg')}.glyphicon{position:relative;top:1px;display:inline-block;font-family:'Glyphicons Halflings';font-style:normal;font-weight:400;line-height:1;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.glyphicon-asterisk:before{content:"\2a"}.glyphicon-plus:before{content:"\2b"}.glyphicon-euro:before{content:"\20ac"}.glyphicon-minus:before{content:"\2212"}.glyphicon-cloud:before{content:"\2601"}.glyphicon-envelope:before{content:"\2709"}.glyphicon-pencil:before{content:"\270f"}.glyphicon-glass:before{content:"\e001"}.glyphicon-music:before{content:"\e002"}.glyphicon-search:before{content:"\e003"}.glyphicon-heart:before{content:"\e005"}.glyphicon-star:before{content:"\e006"}.glyphicon-star-empty:before{content:"\e007"}.glyphicon-user:before{content:"\e008"}.glyphicon-film:before{content:"\e009"}.glyphicon-th-large:before{content:"\e010"}.glyphicon-th:before{content:"\e011"}.glyphicon-th-list:before{content:"\e012"}.glyphicon-ok:before{content:"\e013"}.glyphicon-remove:before{content:"\e014"}.glyphicon-zoom-in:before{content:"\e015"}.glyphicon-zoom-out:before{content:"\e016"}.glyphicon-off:before{content:"\e017"}.glyphicon-signal:before{content:"\e018"}.glyphicon-cog:before{content:"\e019"}.glyphicon-trash:before{content:"\e020"}.glyphicon-home:before{content:"\e021"}.glyphicon-file:before{content:"\e022"}.glyphicon-time:before{content:"\e023"}.glyphicon-road:before{content:"\e024"}.glyphicon-download-alt:before{content:"\e025"}.glyphicon-download:before{content:"\e026"}.glyphicon-upload:before{content:"\e027"}.glyphicon-inbox:before{content:"\e028"}.glyphicon-play-circle:before{content:"\e029"}.glyphicon-repeat:before{content:"\e030"}.glyphicon-refresh:before{content:"\e031"}.glyphicon-list-alt:before{content:"\e032"}.glyphicon-lock:before{content:"\e033"}.glyphicon-flag:before{content:"\e034"}.glyphicon-headphones:before{content:"\e035"}.glyphicon-volume-off:before{content:"\e036"}.glyphicon-volume-down:before{content:"\e037"}.glyphicon-volume-up:before{content:"\e038"}.glyphicon-qrcode:before{content:"\e039"}.glyphicon-barcode:before{content:"\e040"}.glyphicon-tag:before{content:"\e041"}.glyphicon-tags:before{content:"\e042"}.glyphicon-book:before{content:"\e043"}.glyphicon-bookmark:before{content:"\e044"}.glyphicon-print:before{content:"\e045"}.glyphicon-camera:before{content:"\e046"}.glyphicon-font:before{content:"\e047"}.glyphicon-bold:before{content:"\e048"}.glyphicon-italic:before{content:"\e049"}.glyphicon-text-height:before{content:"\e050"}.glyphicon-text-width:before{content:"\e051"}.glyphicon-align-left:before{content:"\e052"}.glyphicon-align-center:before{content:"\e053"}.glyphicon-align-right:before{content:"\e054"}.glyphicon-align-justify:before{content:"\e055"}.glyphicon-list:before{content:"\e056"}.glyphicon-indent-left:before{content:"\e057"}.glyphicon-indent-right:before{content:"\e058"}.glyphicon-facetime-video:before{content:"\e059"}.glyphicon-picture:before{content:"\e060"}.glyphicon-map-marker:before{content:"\e062"}.glyphicon-adjust:before{content:"\e063"}.glyphicon-tint:before{content:"\e064"}.glyphicon-edit:before{content:"\e065"}.glyphicon-share:before{content:"\e066"}.glyphicon-check:before{content:"\e067"}.glyphicon-move:before{content:"\e068"}.glyphicon-step-backward:before{content:"\e069"}.glyphicon-fast-backward:before{content:"\e070"}.glyphicon-backward:before{content:"\e071"}.glyphicon-play:before{content:"\e072"}.glyphicon-pause:before{content:"\e073"}.glyphicon-stop:before{content:"\e074"}.glyphicon-forward:before{content:"\e075"}.glyphicon-fast-forward:before{content:"\e076"}.glyphicon-step-forward:before{content:"\e077"}.glyphicon-eject:before{content:"\e078"}.glyphicon-chevron-left:before{content:"\e079"}.glyphicon-chevron-right:before{content:"\e080"}.glyphicon-plus-sign:before{content:"\e081"}.glyphicon-minus-sign:before{content:"\e082"}.glyphicon-remove-sign:before{content:"\e083"}.glyphicon-ok-sign:before{content:"\e084"}.glyphicon-question-sign:before{content:"\e085"}.glyphicon-info-sign:before{content:"\e086"}.glyphicon-screenshot:before{content:"\e087"}.glyphicon-remove-circle:before{content:"\e088"}.glyphicon-ok-circle:before{content:"\e089"}.glyphicon-ban-circle:before{content:"\e090"}.glyphicon-arrow-left:before{content:"\e091"}.glyphicon-arrow-right:before{content:"\e092"}.glyphicon-arrow-up:before{content:"\e093"}.glyphicon-arrow-down:before{content:"\e094"}.glyphicon-share-alt:before{content:"\e095"}.glyphicon-resize-full:before{content:"\e096"}.glyphicon-resize-small:before{content:"\e097"}.glyphicon-exclamation-sign:before{content:"\e101"}.glyphicon-gift:before{content:"\e102"}.glyphicon-leaf:before{content:"\e103"}.glyphicon-fire:before{content:"\e104"}.glyphicon-eye-open:before{content:"\e105"}.glyphicon-eye-close:before{content:"\e106"}.glyphicon-warning-sign:before{content:"\e107"}.glyphicon-plane:before{content:"\e108"}.glyphicon-calendar:before{content:"\e109"}.glyphicon-random:before{content:"\e110"}.glyphicon-comment:before{content:"\e111"}.glyphicon-magnet:before{content:"\e112"}.glyphicon-chevron-up:before{content:"\e113"}.glyphicon-chevron-down:before{content:"\e114"}.glyphicon-retweet:before{content:"\e115"}.glyphicon-shopping-cart:before{content:"\e116"}.glyphicon-folder-close:before{content:"\e117"}.glyphicon-folder-open:before{content:"\e118"}.glyphicon-resize-vertical:before{content:"\e119"}.glyphicon-resize-horizontal:before{content:"\e120"}.glyphicon-hdd:before{content:"\e121"}.glyphicon-bullhorn:before{content:"\e122"}.glyphicon-bell:before{content:"\e123"}.glyphicon-certificate:before{content:"\e124"}.glyphicon-thumbs-up:before{content:"\e125"}.glyphicon-thumbs-down:before{content:"\e126"}.glyphicon-hand-right:before{content:"\e127"}.glyphicon-hand-left:before{content:"\e128"}.glyphicon-hand-up:before{content:"\e129"}.glyphicon-hand-down:before{content:"\e130"}.glyphicon-circle-arrow-right:before{content:"\e131"}.glyphicon-circle-arrow-left:before{content:"\e132"}.glyphicon-circle-arrow-up:before{content:"\e133"}.glyphicon-circle-arrow-down:before{content:"\e134"}.glyphicon-globe:before{content:"\e135"}.glyphicon-wrench:before{content:"\e136"}.glyphicon-tasks:before{content:"\e137"}.glyphicon-filter:before{content:"\e138"}.glyphicon-briefcase:before{content:"\e139"}.glyphicon-fullscreen:before{content:"\e140"}.glyphicon-dashboard:before{content:"\e141"}.glyphicon-paperclip:before{content:"\e142"}.glyphicon-heart-empty:before{content:"\e143"}.glyphicon-link:before{content:"\e144"}.glyphicon-phone:before{content:"\e145"}.glyphicon-pushpin:before{content:"\e146"}.glyphicon-usd:before{content:"\e148"}.glyphicon-gbp:before{content:"\e149"}.glyphicon-sort:before{content:"\e150"}.glyphicon-sort-by-alphabet:before{content:"\e151"}.glyphicon-sort-by-alphabet-alt:before{content:"\e152"}.glyphicon-sort-by-order:before{content:"\e153"}.glyphicon-sort-by-order-alt:before{content:"\e154"}.glyphicon-sort-by-attributes:before{content:"\e155"}.glyphicon-sort-by-attributes-alt:before{content:"\e156"}.glyphicon-unchecked:before{content:"\e157"}.glyphicon-expand:before{content:"\e158"}.glyphicon-collapse-down:before{content:"\e159"}.glyphicon-collapse-up:before{content:"\e160"}.glyphicon-log-in:before{content:"\e161"}.glyphicon-flash:before{content:"\e162"}.glyphicon-log-out:before{content:"\e163"}.glyphicon-new-window:before{content:"\e164"}.glyphicon-record:before{content:"\e165"}.glyphicon-save:before{content:"\e166"}.glyphicon-open:before{content:"\e167"}.glyphicon-saved:before{content:"\e168"}.glyphicon-import:before{content:"\e169"}.glyphicon-export:before{content:"\e170"}.glyphicon-send:before{content:"\e171"}.glyphicon-floppy-disk:before{content:"\e172"}.glyphicon-floppy-saved:before{content:"\e173"}.glyphicon-floppy-remove:before{content:"\e174"}.glyphicon-floppy-save:before{content:"\e175"}.glyphicon-floppy-open:before{content:"\e176"}.glyphicon-credit-card:before{content:"\e177"}.glyphicon-transfer:before{content:"\e178"}.glyphicon-cutlery:before{content:"\e179"}.glyphicon-header:before{content:"\e180"}.glyphicon-compressed:before{content:"\e181"}.glyphicon-earphone:before{content:"\e182"}.glyphicon-phone-alt:before{content:"\e183"}.glyphicon-tower:before{content:"\e184"}.glyphicon-stats:before{content:"\e185"}.glyphicon-sd-video:before{content:"\e186"}.glyphicon-hd-video:before{content:"\e187"}.glyphicon-subtitles:before{content:"\e188"}.glyphicon-sound-stereo:before{content:"\e189"}.glyphicon-sound-dolby:before{content:"\e190"}.glyphicon-sound-5-1:before{content:"\e191"}.glyphicon-sound-6-1:before{content:"\e192"}.glyphicon-sound-7-1:before{content:"\e193"}.glyphicon-copyright-mark:before{content:"\e194"}.glyphicon-registration-mark:before{content:"\e195"}.glyphicon-cloud-download:before{content:"\e197"}.glyphicon-cloud-upload:before{content:"\e198"}.glyphicon-tree-conifer:before{content:"\e199"}.glyphicon-tree-deciduous:before{content:"\e200"}.caret{display:inline-block;width:0;height:0;margin-left:2px;vertical-align:middle;border-top:4px solid;border-right:4px solid transparent;border-left:4px solid transparent}.dropdown{position:relative}.dropdown-toggle:focus{outline:0}.dropdown-menu{position:absolute;top:100%;left:0;z-index:1000;display:none;float:left;min-width:160px;padding:5px 0;margin:2px 0 0;list-style:none;font-size:16px;background-color:#fff;border:1px solid #ccc;border:1px solid rgba(0,0,0,.15);border-radius:3px;-webkit-box-shadow:0 6px 12px rgba(0,0,0,.175);box-shadow:0 6px 12px rgba(0,0,0,.175);background-clip:padding-box}.dropdown-menu.pull-right{right:0;left:auto}.dropdown-menu .divider{height:1px;margin:10px 0;overflow:hidden;background-color:#e5e5e5}.dropdown-menu>li>a{display:block;padding:3px 20px;clear:both;font-weight:400;line-height:1.42857143;color:#333;white-space:nowrap}.dropdown-menu>li>a:hover,.dropdown-menu>li>a:focus{text-decoration:none;color:#262626;background-color:#f5f5f5}.dropdown-menu>.active>a,.dropdown-menu>.active>a:hover,.dropdown-menu>.active>a:focus{color:#fff;text-decoration:none;outline:0;background-color:#3498db}.dropdown-menu>.disabled>a,.dropdown-menu>.disabled>a:hover,.dropdown-menu>.disabled>a:focus{color:#999}.dropdown-menu>.disabled>a:hover,.dropdown-menu>.disabled>a:focus{text-decoration:none;background-color:transparent;background-image:none;filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);cursor:not-allowed}.open>.dropdown-menu{display:block}.open>a{outline:0}.dropdown-menu-right{left:auto;right:0}.dropdown-menu-left{left:0;right:auto}.dropdown-header{display:block;padding:3px 20px;font-size:14px;line-height:1.42857143;color:#999}.dropdown-backdrop{position:fixed;left:0;right:0;bottom:0;top:0;z-index:990}.pull-right>.dropdown-menu{right:0;left:auto}.dropup .caret,.navbar-fixed-bottom .dropdown .caret{border-top:0;border-bottom:4px solid;content:""}.dropup .dropdown-menu,.navbar-fixed-bottom .dropdown .dropdown-menu{top:auto;bottom:100%;margin-bottom:1px}@media (min-width:480px){.navbar-right .dropdown-menu{left:auto;right:0}.navbar-right .dropdown-menu-left{left:0;right:auto}}.btn-group,.btn-group-vertical{position:relative;display:inline-block;vertical-align:middle}.btn-group>.btn,.btn-group-vertical>.btn{position:relative;float:left}.btn-group>.btn:hover,.btn-group-vertical>.btn:hover,.btn-group>.btn:focus,.btn-group-vertical>.btn:focus,.btn-group>.btn:active,.btn-group-vertical>.btn:active,.btn-group>.btn.active,.btn-group-vertical>.btn.active{z-index:2}.btn-group>.btn:focus,.btn-group-vertical>.btn:focus{outline:0}.btn-group .btn+.btn,.btn-group .btn+.btn-group,.btn-group .btn-group+.btn,.btn-group .btn-group+.btn-group{margin-left:-1px}.btn-toolbar{margin-left:-5px}.btn-toolbar .btn-group,.btn-toolbar .input-group{float:left}.btn-toolbar>.btn,.btn-toolbar>.btn-group,.btn-toolbar>.input-group{margin-left:5px}.btn-group>.btn:not(:first-child):not(:last-child):not(.dropdown-toggle){border-radius:0}.btn-group>.btn:first-child{margin-left:0}.btn-group>.btn:first-child:not(:last-child):not(.dropdown-toggle){border-bottom-right-radius:0;border-top-right-radius:0}.btn-group>.btn:last-child:not(:first-child),.btn-group>.dropdown-toggle:not(:first-child){border-bottom-left-radius:0;border-top-left-radius:0}.btn-group>.btn-group{float:left}.btn-group>.btn-group:not(:first-child):not(:last-child)>.btn{border-radius:0}.btn-group>.btn-group:first-child>.btn:last-child,.btn-group>.btn-group:first-child>.dropdown-toggle{border-bottom-right-radius:0;border-top-right-radius:0}.btn-group>.btn-group:last-child>.btn:first-child{border-bottom-left-radius:0;border-top-left-radius:0}.btn-group .dropdown-toggle:active,.btn-group.open .dropdown-toggle{outline:0}.btn-group>.btn+.dropdown-toggle{padding-left:8px;padding-right:8px}.btn-group>.btn-lg+.dropdown-toggle{padding-left:12px;padding-right:12px}.btn-group.open .dropdown-toggle{-webkit-box-shadow:inset 0 3px 5px rgba(0,0,0,.125);box-shadow:inset 0 3px 5px rgba(0,0,0,.125)}.btn-group.open .dropdown-toggle.btn-link{-webkit-box-shadow:none;box-shadow:none}.btn .caret{margin-left:0}.btn-lg .caret{border-width:5px 5px 0;border-bottom-width:0}.dropup .btn-lg .caret{border-width:0 5px 5px}.btn-group-vertical>.btn,.btn-group-vertical>.btn-group,.btn-group-vertical>.btn-group>.btn{display:block;float:none;width:100%;max-width:100%}.btn-group-vertical>.btn-group>.btn{float:none}.btn-group-vertical>.btn+.btn,.btn-group-vertical>.btn+.btn-group,.btn-group-vertical>.btn-group+.btn,.btn-group-vertical>.btn-group+.btn-group{margin-top:-1px;margin-left:0}.btn-group-vertical>.btn:not(:first-child):not(:last-child){border-radius:0}.btn-group-vertical>.btn:first-child:not(:last-child){border-top-right-radius:3px;border-bottom-right-radius:0;border-bottom-left-radius:0}.btn-group-vertical>.btn:last-child:not(:first-child){border-bottom-left-radius:3px;border-top-right-radius:0;border-top-left-radius:0}.btn-group-vertical>.btn-group:not(:first-child):not(:last-child)>.btn{border-radius:0}.btn-group-vertical>.btn-group:first-child:not(:last-child)>.btn:last-child,.btn-group-vertical>.btn-group:first-child:not(:last-child)>.dropdown-toggle{border-bottom-right-radius:0;border-bottom-left-radius:0}.btn-group-vertical>.btn-group:last-child:not(:first-child)>.btn:first-child{border-top-right-radius:0;border-top-left-radius:0}.btn-group-justified{display:table;width:100%;table-layout:fixed;border-collapse:separate}.btn-group-justified>.btn,.btn-group-justified>.btn-group{float:none;display:table-cell;width:1%}.btn-group-justified>.btn-group .btn{width:100%}[data-toggle=buttons]>.btn>input[type=radio],[data-toggle=buttons]>.btn>input[type=checkbox]{display:none}.input-group{position:relative;display:table;border-collapse:separate}.input-group[class*=col-]{float:none;padding-left:0;padding-right:0}.input-group .form-control{position:relative;z-index:2;float:left;width:100%;margin-bottom:0}.input-group-lg>.form-control,.input-group-lg>.input-group-addon,.input-group-lg>.input-group-btn>.btn{height:49px;padding:10px 16px;font-size:20px;line-height:1.33;border-radius:3px}select.input-group-lg>.form-control,select.input-group-lg>.input-group-addon,select.input-group-lg>.input-group-btn>.btn{height:49px;line-height:49px}textarea.input-group-lg>.form-control,textarea.input-group-lg>.input-group-addon,textarea.input-group-lg>.input-group-btn>.btn,select[multiple].input-group-lg>.form-control,select[multiple].input-group-lg>.input-group-addon,select[multiple].input-group-lg>.input-group-btn>.btn{height:auto}.input-group-sm>.form-control,.input-group-sm>.input-group-addon,.input-group-sm>.input-group-btn>.btn{height:33px;padding:5px 10px;font-size:14px;line-height:1.5;border-radius:0}select.input-group-sm>.form-control,select.input-group-sm>.input-group-addon,select.input-group-sm>.input-group-btn>.btn{height:33px;line-height:33px}textarea.input-group-sm>.form-control,textarea.input-group-sm>.input-group-addon,textarea.input-group-sm>.input-group-btn>.btn,select[multiple].input-group-sm>.form-control,select[multiple].input-group-sm>.input-group-addon,select[multiple].input-group-sm>.input-group-btn>.btn{height:auto}.input-group-addon,.input-group-btn,.input-group .form-control{display:table-cell}.input-group-addon:not(:first-child):not(:last-child),.input-group-btn:not(:first-child):not(:last-child),.input-group .form-control:not(:first-child):not(:last-child){border-radius:0}.input-group-addon,.input-group-btn{width:1%;white-space:nowrap;vertical-align:middle}.input-group-addon{padding:6px 12px;font-size:16px;font-weight:400;line-height:1;color:#555;text-align:center;background-color:#eee;border:1px solid #ccc;border-radius:3px}.input-group-addon.input-sm{padding:5px 10px;font-size:14px;border-radius:0}.input-group-addon.input-lg{padding:10px 16px;font-size:20px;border-radius:3px}.input-group-addon input[type=radio],.input-group-addon input[type=checkbox]{margin-top:0}.input-group .form-control:first-child,.input-group-addon:first-child,.input-group-btn:first-child>.btn,.input-group-btn:first-child>.btn-group>.btn,.input-group-btn:first-child>.dropdown-toggle,.input-group-btn:last-child>.btn:not(:last-child):not(.dropdown-toggle),.input-group-btn:last-child>.btn-group:not(:last-child)>.btn{border-bottom-right-radius:0;border-top-right-radius:0}.input-group-addon:first-child{border-right:0}.input-group .form-control:last-child,.input-group-addon:last-child,.input-group-btn:last-child>.btn,.input-group-btn:last-child>.btn-group>.btn,.input-group-btn:last-child>.dropdown-toggle,.input-group-btn:first-child>.btn:not(:first-child),.input-group-btn:first-child>.btn-group:not(:first-child)>.btn{border-bottom-left-radius:0;border-top-left-radius:0}.input-group-addon:last-child{border-left:0}.input-group-btn{position:relative;font-size:0;white-space:nowrap}.input-group-btn>.btn{position:relative}.input-group-btn>.btn+.btn{margin-left:-1px}.input-group-btn>.btn:hover,.input-group-btn>.btn:focus,.input-group-btn>.btn:active{z-index:2}.input-group-btn:first-child>.btn,.input-group-btn:first-child>.btn-group{margin-right:-1px}.input-group-btn:last-child>.btn,.input-group-btn:last-child>.btn-group{margin-left:-1px}.nav{margin-bottom:0;padding-left:0;list-style:none}.nav>li{position:relative;display:block}.nav>li>a{position:relative;display:block;padding:10px 15px}.nav>li>a:hover,.nav>li>a:focus{text-decoration:none;background-color:#eee}.nav>li.disabled>a{color:#999}.nav>li.disabled>a:hover,.nav>li.disabled>a:focus{color:#999;text-decoration:none;background-color:transparent;cursor:not-allowed}.nav .open>a,.nav .open>a:hover,.nav .open>a:focus{background-color:#eee;border-color:#3498db}.nav .nav-divider{height:1px;margin:10px 0;overflow:hidden;background-color:#e5e5e5}.nav>li>a>img{max-width:none}.nav-tabs{border-bottom:1px solid #ddd}.nav-tabs>li{float:left;margin-bottom:-1px}.nav-tabs>li>a{margin-right:2px;line-height:1.42857143;border:1px solid transparent;border-radius:3px 3px 0 0}.nav-tabs>li>a:hover{border-color:#eee #eee #ddd}.nav-tabs>li.active>a,.nav-tabs>li.active>a:hover,.nav-tabs>li.active>a:focus{color:#555;background-color:#fff;border:1px solid #ddd;border-bottom-color:transparent;cursor:default}.nav-tabs.nav-justified{width:100%;border-bottom:0}.nav-tabs.nav-justified>li{float:none}.nav-tabs.nav-justified>li>a{text-align:center;margin-bottom:5px}.nav-tabs.nav-justified>.dropdown .dropdown-menu{top:auto;left:auto}@media (min-width:768px){.nav-tabs.nav-justified>li{display:table-cell;width:1%}.nav-tabs.nav-justified>li>a{margin-bottom:0}}.nav-tabs.nav-justified>li>a{margin-right:0;border-radius:3px}.nav-tabs.nav-justified>.active>a,.nav-tabs.nav-justified>.active>a:hover,.nav-tabs.nav-justified>.active>a:focus{border:1px solid #ddd}@media (min-width:768px){.nav-tabs.nav-justified>li>a{border-bottom:1px solid #ddd;border-radius:3px 3px 0 0}.nav-tabs.nav-justified>.active>a,.nav-tabs.nav-justified>.active>a:hover,.nav-tabs.nav-justified>.active>a:focus{border-bottom-color:#fff}}.nav-pills>li{float:left}.nav-pills>li>a{border-radius:3px}.nav-pills>li+li{margin-left:2px}.nav-pills>li.active>a,.nav-pills>li.active>a:hover,.nav-pills>li.active>a:focus{color:#fff;background-color:#3498db}.nav-stacked>li{float:none}.nav-stacked>li+li{margin-top:2px;margin-left:0}.nav-justified{width:100%}.nav-justified>li{float:none}.nav-justified>li>a{text-align:center;margin-bottom:5px}.nav-justified>.dropdown .dropdown-menu{top:auto;left:auto}@media (min-width:768px){.nav-justified>li{display:table-cell;width:1%}.nav-justified>li>a{margin-bottom:0}}.nav-tabs-justified{border-bottom:0}.nav-tabs-justified>li>a{margin-right:0;border-radius:3px}.nav-tabs-justified>.active>a,.nav-tabs-justified>.active>a:hover,.nav-tabs-justified>.active>a:focus{border:1px solid #ddd}@media (min-width:768px){.nav-tabs-justified>li>a{border-bottom:1px solid #ddd;border-radius:3px 3px 0 0}.nav-tabs-justified>.active>a,.nav-tabs-justified>.active>a:hover,.nav-tabs-justified>.active>a:focus{border-bottom-color:#fff}}.tab-content>.tab-pane{display:none}.tab-content>.active{display:block}.nav-tabs .dropdown-menu{margin-top:-1px;border-top-right-radius:0;border-top-left-radius:0}.navbar{position:relative;min-height:50px;margin-bottom:22px;border:1px solid transparent}@media (min-width:480px){.navbar{border-radius:3px}}@media (min-width:480px){.navbar-header{float:left}}.navbar-collapse{max-height:340px;overflow-x:visible;padding-right:15px;padding-left:15px;border-top:1px solid transparent;box-shadow:inset 0 1px 0 rgba(255,255,255,.1);-webkit-overflow-scrolling:touch}.navbar-collapse.in{overflow-y:auto}@media (min-width:480px){.navbar-collapse{width:auto;border-top:0;box-shadow:none}.navbar-collapse.collapse{display:block!important;height:auto!important;padding-bottom:0;overflow:visible!important}.navbar-collapse.in{overflow-y:visible}.navbar-fixed-top .navbar-collapse,.navbar-static-top .navbar-collapse,.navbar-fixed-bottom .navbar-collapse{padding-left:0;padding-right:0}}.container>.navbar-header,.container-fluid>.navbar-header,.container>.navbar-collapse,.container-fluid>.navbar-collapse{margin-right:-15px;margin-left:-15px}@media (min-width:480px){.container>.navbar-header,.container-fluid>.navbar-header,.container>.navbar-collapse,.container-fluid>.navbar-collapse{margin-right:0;margin-left:0}}.navbar-static-top{z-index:1000;border-width:0 0 1px}@media (min-width:480px){.navbar-static-top{border-radius:0}}.navbar-fixed-top,.navbar-fixed-bottom{position:fixed;right:0;left:0;z-index:1030}@media (min-width:480px){.navbar-fixed-top,.navbar-fixed-bottom{border-radius:0}}.navbar-fixed-top{top:0;border-width:0 0 1px}.navbar-fixed-bottom{bottom:0;margin-bottom:0;border-width:1px 0 0}.navbar-brand{float:left;padding:14px 15px;font-size:20px;line-height:22px;height:50px}.navbar-brand:hover,.navbar-brand:focus{text-decoration:none}@media (min-width:480px){.navbar>.container .navbar-brand,.navbar>.container-fluid .navbar-brand{margin-left:-15px}}.navbar-toggle{position:relative;float:right;margin-right:15px;padding:9px 10px;margin-top:8px;margin-bottom:8px;background-color:transparent;background-image:none;border:1px solid transparent;border-radius:3px}.navbar-toggle:focus{outline:0}.navbar-toggle .icon-bar{display:block;width:22px;height:2px;border-radius:1px}.navbar-toggle .icon-bar+.icon-bar{margin-top:4px}@media (min-width:480px){.navbar-toggle{display:none}}.navbar-nav{margin:7px -15px}.navbar-nav>li>a{padding-top:10px;padding-bottom:10px;line-height:22px}@media (max-width:479px){.navbar-nav .open .dropdown-menu{position:static;float:none;width:auto;margin-top:0;background-color:transparent;border:0;box-shadow:none}.navbar-nav .open .dropdown-menu>li>a,.navbar-nav .open .dropdown-menu .dropdown-header{padding:5px 15px 5px 25px}.navbar-nav .open .dropdown-menu>li>a{line-height:22px}.navbar-nav .open .dropdown-menu>li>a:hover,.navbar-nav .open .dropdown-menu>li>a:focus{background-image:none}}@media (min-width:480px){.navbar-nav{float:left;margin:0}.navbar-nav>li{float:left}.navbar-nav>li>a{padding-top:14px;padding-bottom:14px}.navbar-nav.navbar-right:last-child{margin-right:-15px}}@media (min-width:480px){.navbar-left{float:left!important}.navbar-right{float:right!important}}.navbar-form{margin-left:-15px;margin-right:-15px;padding:10px 15px;border-top:1px solid transparent;border-bottom:1px solid transparent;-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,.1),0 1px 0 rgba(255,255,255,.1);box-shadow:inset 0 1px 0 rgba(255,255,255,.1),0 1px 0 rgba(255,255,255,.1);margin-top:7px;margin-bottom:7px}@media (min-width:768px){.navbar-form .form-group{display:inline-block;margin-bottom:0;vertical-align:middle}.navbar-form .form-control{display:inline-block;width:auto;vertical-align:middle}.navbar-form .input-group>.form-control{width:100%}.navbar-form .control-label{margin-bottom:0;vertical-align:middle}.navbar-form .radio,.navbar-form .checkbox{display:inline-block;margin-top:0;margin-bottom:0;padding-left:0;vertical-align:middle}.navbar-form .radio input[type=radio],.navbar-form .checkbox input[type=checkbox]{float:none;margin-left:0}.navbar-form .has-feedback .form-control-feedback{top:0}}@media (max-width:479px){.navbar-form .form-group{margin-bottom:5px}}@media (min-width:480px){.navbar-form{width:auto;border:0;margin-left:0;margin-right:0;padding-top:0;padding-bottom:0;-webkit-box-shadow:none;box-shadow:none}.navbar-form.navbar-right:last-child{margin-right:-15px}}.navbar-nav>li>.dropdown-menu{margin-top:0;border-top-right-radius:0;border-top-left-radius:0}.navbar-fixed-bottom .navbar-nav>li>.dropdown-menu{border-bottom-right-radius:0;border-bottom-left-radius:0}.navbar-btn{margin-top:7px;margin-bottom:7px}.navbar-btn.btn-sm{margin-top:8.5px;margin-bottom:8.5px}.navbar-btn.btn-xs{margin-top:14px;margin-bottom:14px}.navbar-text{margin-top:14px;margin-bottom:14px}@media (min-width:480px){.navbar-text{float:left;margin-left:15px;margin-right:15px}.navbar-text.navbar-right:last-child{margin-right:0}}.navbar-default{background-color:#f8f8f8;border-color:#e7e7e7}.navbar-default .navbar-brand{color:#555}.navbar-default .navbar-brand:hover,.navbar-default .navbar-brand:focus{color:#3b3b3b;background-color:transparent}.navbar-default .navbar-text{color:#555}.navbar-default .navbar-nav>li>a{color:#555}.navbar-default .navbar-nav>li>a:hover,.navbar-default .navbar-nav>li>a:focus{color:#333;background-color:transparent}.navbar-default .navbar-nav>.active>a,.navbar-default .navbar-nav>.active>a:hover,.navbar-default .navbar-nav>.active>a:focus{color:#555;background-color:#e7e7e7}.navbar-default .navbar-nav>.disabled>a,.navbar-default .navbar-nav>.disabled>a:hover,.navbar-default .navbar-nav>.disabled>a:focus{color:#ccc;background-color:transparent}.navbar-default .navbar-toggle{border-color:#ddd}.navbar-default .navbar-toggle:hover,.navbar-default .navbar-toggle:focus{background-color:#ddd}.navbar-default .navbar-toggle .icon-bar{background-color:#888}.navbar-default .navbar-collapse,.navbar-default .navbar-form{border-color:#e7e7e7}.navbar-default .navbar-nav>.open>a,.navbar-default .navbar-nav>.open>a:hover,.navbar-default .navbar-nav>.open>a:focus{background-color:#e7e7e7;color:#555}@media (max-width:479px){.navbar-default .navbar-nav .open .dropdown-menu>li>a{color:#555}.navbar-default .navbar-nav .open .dropdown-menu>li>a:hover,.navbar-default .navbar-nav .open .dropdown-menu>li>a:focus{color:#333;background-color:transparent}.navbar-default .navbar-nav .open .dropdown-menu>.active>a,.navbar-default .navbar-nav .open .dropdown-menu>.active>a:hover,.navbar-default .navbar-nav .open .dropdown-menu>.active>a:focus{color:#555;background-color:#e7e7e7}.navbar-default .navbar-nav .open .dropdown-menu>.disabled>a,.navbar-default .navbar-nav .open .dropdown-menu>.disabled>a:hover,.navbar-default .navbar-nav .open .dropdown-menu>.disabled>a:focus{color:#ccc;background-color:transparent}}.navbar-default .navbar-link{color:#555}.navbar-default .navbar-link:hover{color:#333}.navbar-inverse{background-color:#222;border-color:#080808}.navbar-inverse .navbar-brand{color:#999}.navbar-inverse .navbar-brand:hover,.navbar-inverse .navbar-brand:focus{color:#fff;background-color:transparent}.navbar-inverse .navbar-text{color:#999}.navbar-inverse .navbar-nav>li>a{color:#999}.navbar-inverse .navbar-nav>li>a:hover,.navbar-inverse .navbar-nav>li>a:focus{color:#fff;background-color:transparent}.navbar-inverse .navbar-nav>.active>a,.navbar-inverse .navbar-nav>.active>a:hover,.navbar-inverse .navbar-nav>.active>a:focus{color:#fff;background-color:#080808}.navbar-inverse .navbar-nav>.disabled>a,.navbar-inverse .navbar-nav>.disabled>a:hover,.navbar-inverse .navbar-nav>.disabled>a:focus{color:#444;background-color:transparent}.navbar-inverse .navbar-toggle{border-color:#333}.navbar-inverse .navbar-toggle:hover,.navbar-inverse .navbar-toggle:focus{background-color:#333}.navbar-inverse .navbar-toggle .icon-bar{background-color:#fff}.navbar-inverse .navbar-collapse,.navbar-inverse .navbar-form{border-color:#101010}.navbar-inverse .navbar-nav>.open>a,.navbar-inverse .navbar-nav>.open>a:hover,.navbar-inverse .navbar-nav>.open>a:focus{background-color:#080808;color:#fff}@media (max-width:479px){.navbar-inverse .navbar-nav .open .dropdown-menu>.dropdown-header{border-color:#080808}.navbar-inverse .navbar-nav .open .dropdown-menu .divider{background-color:#080808}.navbar-inverse .navbar-nav .open .dropdown-menu>li>a{color:#999}.navbar-inverse .navbar-nav .open .dropdown-menu>li>a:hover,.navbar-inverse .navbar-nav .open .dropdown-menu>li>a:focus{color:#fff;background-color:transparent}.navbar-inverse .navbar-nav .open .dropdown-menu>.active>a,.navbar-inverse .navbar-nav .open .dropdown-menu>.active>a:hover,.navbar-inverse .navbar-nav .open .dropdown-menu>.active>a:focus{color:#fff;background-color:#080808}.navbar-inverse .navbar-nav .open .dropdown-menu>.disabled>a,.navbar-inverse .navbar-nav .open .dropdown-menu>.disabled>a:hover,.navbar-inverse .navbar-nav .open .dropdown-menu>.disabled>a:focus{color:#444;background-color:transparent}}.navbar-inverse .navbar-link{color:#999}.navbar-inverse .navbar-link:hover{color:#fff}.breadcrumb{padding:8px 15px;margin-bottom:22px;list-style:none;background-color:#f5f5f5;border-radius:3px}.breadcrumb>li{display:inline-block}.breadcrumb>li+li:before{content:"/\00a0";padding:0 5px;color:#ccc}.breadcrumb>.active{color:#999}.pagination{display:inline-block;padding-left:0;margin:22px 0;border-radius:3px}.pagination>li{display:inline}.pagination>li>a,.pagination>li>span{position:relative;float:left;padding:6px 12px;line-height:1.42857143;text-decoration:none;color:#3498db;background-color:#fff;border:1px solid #ddd;margin-left:-1px}.pagination>li:first-child>a,.pagination>li:first-child>span{margin-left:0;border-bottom-left-radius:3px;border-top-left-radius:3px}.pagination>li:last-child>a,.pagination>li:last-child>span{border-bottom-right-radius:3px;border-top-right-radius:3px}.pagination>li>a:hover,.pagination>li>span:hover,.pagination>li>a:focus,.pagination>li>span:focus{color:#1d6fa5;background-color:#eee;border-color:#ddd}.pagination>.active>a,.pagination>.active>span,.pagination>.active>a:hover,.pagination>.active>span:hover,.pagination>.active>a:focus,.pagination>.active>span:focus{z-index:2;color:#fff;background-color:#3498db;border-color:#3498db;cursor:default}.pagination>.disabled>span,.pagination>.disabled>span:hover,.pagination>.disabled>span:focus,.pagination>.disabled>a,.pagination>.disabled>a:hover,.pagination>.disabled>a:focus{color:#999;background-color:#fff;border-color:#ddd;cursor:not-allowed}.pagination-lg>li>a,.pagination-lg>li>span{padding:10px 16px;font-size:20px}.pagination-lg>li:first-child>a,.pagination-lg>li:first-child>span{border-bottom-left-radius:3px;border-top-left-radius:3px}.pagination-lg>li:last-child>a,.pagination-lg>li:last-child>span{border-bottom-right-radius:3px;border-top-right-radius:3px}.pagination-sm>li>a,.pagination-sm>li>span{padding:5px 10px;font-size:14px}.pagination-sm>li:first-child>a,.pagination-sm>li:first-child>span{border-bottom-left-radius:0;border-top-left-radius:0}.pagination-sm>li:last-child>a,.pagination-sm>li:last-child>span{border-bottom-right-radius:0;border-top-right-radius:0}.pager{padding-left:0;margin:22px 0;list-style:none;text-align:center}.pager li{display:inline}.pager li>a,.pager li>span{display:inline-block;padding:5px 14px;background-color:#fff;border:1px solid #ddd;border-radius:15px}.pager li>a:hover,.pager li>a:focus{text-decoration:none;background-color:#eee}.pager .next>a,.pager .next>span{float:right}.pager .previous>a,.pager .previous>span{float:left}.pager .disabled>a,.pager .disabled>a:hover,.pager .disabled>a:focus,.pager .disabled>span{color:#999;background-color:#fff;cursor:not-allowed}.label{display:inline;padding:.2em .6em .3em;font-size:75%;font-weight:700;line-height:1;color:#fff;text-align:center;white-space:nowrap;vertical-align:baseline;border-radius:.25em}.label[href]:hover,.label[href]:focus{color:#fff;text-decoration:none;cursor:pointer}.label:empty{display:none}.btn .label{position:relative;top:-1px}.label-default{background-color:#999}.label-default[href]:hover,.label-default[href]:focus{background-color:gray}.label-primary{background-color:#3498db}.label-primary[href]:hover,.label-primary[href]:focus{background-color:#217dbb}.label-success{background-color:#2ecc71}.label-success[href]:hover,.label-success[href]:focus{background-color:#25a25a}.label-info{background-color:#9b59b6}.label-info[href]:hover,.label-info[href]:focus{background-color:#804399}.label-warning{background-color:#f1c40f}.label-warning[href]:hover,.label-warning[href]:focus{background-color:#c29d0b}.label-danger{background-color:#e74c3c}.label-danger[href]:hover,.label-danger[href]:focus{background-color:#d62c1a}.badge{display:inline-block;min-width:10px;padding:3px 7px;font-size:14px;font-weight:700;color:#fff;line-height:1;vertical-align:baseline;white-space:nowrap;text-align:center;background-color:#999;border-radius:10px}.badge:empty{display:none}.btn .badge{position:relative;top:-1px}.btn-xs .badge{top:0;padding:1px 5px}a.badge:hover,a.badge:focus{color:#fff;text-decoration:none;cursor:pointer}a.list-group-item.active>.badge,.nav-pills>.active>a>.badge{color:#3498db;background-color:#fff}.nav-pills>li>a>.badge{margin-left:3px}.jumbotron{padding:30px;margin-bottom:30px;color:inherit;background-color:#eee}.jumbotron h1,.jumbotron .h1{color:inherit}.jumbotron p{margin-bottom:15px;font-size:24px;font-weight:200}.container .jumbotron{border-radius:3px}.jumbotron .container{max-width:100%}@media screen and (min-width:768px){.jumbotron{padding-top:48px;padding-bottom:48px}.container .jumbotron{padding-left:60px;padding-right:60px}.jumbotron h1,.jumbotron .h1{font-size:72px}}.thumbnail{display:block;padding:4px;margin-bottom:22px;line-height:1.42857143;background-color:#fff;border:1px solid #ddd;border-radius:3px;-webkit-transition:all .2s ease-in-out;transition:all .2s ease-in-out}.thumbnail>img,.thumbnail a>img{margin-left:auto;margin-right:auto}a.thumbnail:hover,a.thumbnail:focus,a.thumbnail.active{border-color:#3498db}.thumbnail .caption{padding:9px;color:#333}.alert{padding:15px;margin-bottom:22px;border:1px solid transparent;border-radius:3px}.alert h4{margin-top:0;color:inherit}.alert .alert-link{font-weight:700}.alert>p,.alert>ul{margin-bottom:0}.alert>p+p{margin-top:5px}.alert-dismissable{padding-right:35px}.alert-dismissable .close{position:relative;top:-2px;right:-21px;color:inherit}.alert-success{background-color:#2ecc71;border-color:#29b74e;color:#fff}.alert-success hr{border-top-color:#25a245}.alert-success .alert-link{color:#e6e6e6}.alert-info{background-color:#9b59b6;border-color:#7f4bab;color:#fff}.alert-info hr{border-top-color:#724399}.alert-info .alert-link{color:#e6e6e6}.alert-warning{background-color:#f1c40f;border-color:#da8f0d;color:#fff}.alert-warning hr{border-top-color:#c27f0b}.alert-warning .alert-link{color:#e6e6e6}.alert-danger{background-color:#e74c3c;border-color:#e42533;color:#fff}.alert-danger hr{border-top-color:#d61a28}.alert-danger .alert-link{color:#e6e6e6}@-webkit-keyframes progress-bar-stripes{from{background-position:40px 0}to{background-position:0 0}}@keyframes progress-bar-stripes{from{background-position:40px 0}to{background-position:0 0}}.progress{overflow:hidden;height:22px;margin-bottom:22px;background-color:#f5f5f5;border-radius:3px;-webkit-box-shadow:inset 0 1px 2px rgba(0,0,0,.1);box-shadow:inset 0 1px 2px rgba(0,0,0,.1)}.progress-bar{float:left;width:0;height:100%;font-size:14px;line-height:22px;color:#fff;text-align:center;background-color:#3498db;-webkit-box-shadow:inset 0 -1px 0 rgba(0,0,0,.15);box-shadow:inset 0 -1px 0 rgba(0,0,0,.15);-webkit-transition:width .6s ease;transition:width .6s ease}.progress-striped .progress-bar{background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-size:40px 40px}.progress.active .progress-bar{-webkit-animation:progress-bar-stripes 2s linear infinite;animation:progress-bar-stripes 2s linear infinite}.progress-bar-success{background-color:#2ecc71}.progress-striped .progress-bar-success{background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent)}.progress-bar-info{background-color:#9b59b6}.progress-striped .progress-bar-info{background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent)}.progress-bar-warning{background-color:#f1c40f}.progress-striped .progress-bar-warning{background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent)}.progress-bar-danger{background-color:#e74c3c}.progress-striped .progress-bar-danger{background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent)}.media,.media-body{overflow:hidden;zoom:1}.media,.media .media{margin-top:15px}.media:first-child{margin-top:0}.media-object{display:block}.media-heading{margin:0 0 5px}.media>.pull-left{margin-right:10px}.media>.pull-right{margin-left:10px}.media-list{padding-left:0;list-style:none}.list-group{margin-bottom:20px;padding-left:0}.list-group-item{position:relative;display:block;padding:10px 15px;margin-bottom:-1px;background-color:#fff;border:1px solid #ddd}.list-group-item:first-child{border-top-right-radius:3px;border-top-left-radius:3px}.list-group-item:last-child{margin-bottom:0;border-bottom-right-radius:3px;border-bottom-left-radius:3px}.list-group-item>.badge{float:right}.list-group-item>.badge+.badge{margin-right:5px}a.list-group-item{color:#555}a.list-group-item .list-group-item-heading{color:#333}a.list-group-item:hover,a.list-group-item:focus{text-decoration:none;background-color:#f5f5f5}a.list-group-item.active,a.list-group-item.active:hover,a.list-group-item.active:focus{z-index:2;color:#fff;background-color:#3498db;border-color:#3498db}a.list-group-item.active .list-group-item-heading,a.list-group-item.active:hover .list-group-item-heading,a.list-group-item.active:focus .list-group-item-heading{color:inherit}a.list-group-item.active .list-group-item-text,a.list-group-item.active:hover .list-group-item-text,a.list-group-item.active:focus .list-group-item-text{color:#e1f0fa}.list-group-item-success{color:#29b74e;background-color:#2ecc71}a.list-group-item-success{color:#29b74e}a.list-group-item-success .list-group-item-heading{color:inherit}a.list-group-item-success:hover,a.list-group-item-success:focus{color:#29b74e;background-color:#29b765}a.list-group-item-success.active,a.list-group-item-success.active:hover,a.list-group-item-success.active:focus{color:#fff;background-color:#29b74e;border-color:#29b74e}.list-group-item-info{color:#7f4bab;background-color:#9b59b6}a.list-group-item-info{color:#7f4bab}a.list-group-item-info .list-group-item-heading{color:inherit}a.list-group-item-info:hover,a.list-group-item-info:focus{color:#7f4bab;background-color:#8f4bab}a.list-group-item-info.active,a.list-group-item-info.active:hover,a.list-group-item-info.active:focus{color:#fff;background-color:#7f4bab;border-color:#7f4bab}.list-group-item-warning{color:#da8f0d;background-color:#f1c40f}a.list-group-item-warning{color:#da8f0d}a.list-group-item-warning .list-group-item-heading{color:inherit}a.list-group-item-warning:hover,a.list-group-item-warning:focus{color:#da8f0d;background-color:#dab10d}a.list-group-item-warning.active,a.list-group-item-warning.active:hover,a.list-group-item-warning.active:focus{color:#fff;background-color:#da8f0d;border-color:#da8f0d}.list-group-item-danger{color:#e42533;background-color:#e74c3c}a.list-group-item-danger{color:#e42533}a.list-group-item-danger .list-group-item-heading{color:inherit}a.list-group-item-danger:hover,a.list-group-item-danger:focus{color:#e42533;background-color:#e43725}a.list-group-item-danger.active,a.list-group-item-danger.active:hover,a.list-group-item-danger.active:focus{color:#fff;background-color:#e42533;border-color:#e42533}.list-group-item-heading{margin-top:0;margin-bottom:5px}.list-group-item-text{margin-bottom:0;line-height:1.3}.panel{margin-bottom:22px;background-color:#fff;border:1px solid transparent;border-radius:3px;-webkit-box-shadow:0 1px 1px rgba(0,0,0,.05);box-shadow:0 1px 1px rgba(0,0,0,.05)}.panel-body{padding:15px}.panel-heading{padding:10px 15px;border-bottom:1px solid transparent;border-top-right-radius:2px;border-top-left-radius:2px}.panel-heading>.dropdown .dropdown-toggle{color:inherit}.panel-title{margin-top:0;margin-bottom:0;font-size:18px;color:inherit}.panel-title>a{color:inherit}.panel-footer{padding:10px 15px;background-color:#f5f5f5;border-top:1px solid #ddd;border-bottom-right-radius:2px;border-bottom-left-radius:2px}.panel>.list-group{margin-bottom:0}.panel>.list-group .list-group-item{border-width:1px 0;border-radius:0}.panel>.list-group:first-child .list-group-item:first-child{border-top:0;border-top-right-radius:2px;border-top-left-radius:2px}.panel>.list-group:last-child .list-group-item:last-child{border-bottom:0;border-bottom-right-radius:2px;border-bottom-left-radius:2px}.panel-heading+.list-group .list-group-item:first-child{border-top-width:0}.panel>.table,.panel>.table-responsive>.table{margin-bottom:0}.panel>.table:first-child,.panel>.table-responsive:first-child>.table:first-child{border-top-right-radius:2px;border-top-left-radius:2px}.panel>.table:first-child>thead:first-child>tr:first-child td:first-child,.panel>.table-responsive:first-child>.table:first-child>thead:first-child>tr:first-child td:first-child,.panel>.table:first-child>tbody:first-child>tr:first-child td:first-child,.panel>.table-responsive:first-child>.table:first-child>tbody:first-child>tr:first-child td:first-child,.panel>.table:first-child>thead:first-child>tr:first-child th:first-child,.panel>.table-responsive:first-child>.table:first-child>thead:first-child>tr:first-child th:first-child,.panel>.table:first-child>tbody:first-child>tr:first-child th:first-child,.panel>.table-responsive:first-child>.table:first-child>tbody:first-child>tr:first-child th:first-child{border-top-left-radius:2px}.panel>.table:first-child>thead:first-child>tr:first-child td:last-child,.panel>.table-responsive:first-child>.table:first-child>thead:first-child>tr:first-child td:last-child,.panel>.table:first-child>tbody:first-child>tr:first-child td:last-child,.panel>.table-responsive:first-child>.table:first-child>tbody:first-child>tr:first-child td:last-child,.panel>.table:first-child>thead:first-child>tr:first-child th:last-child,.panel>.table-responsive:first-child>.table:first-child>thead:first-child>tr:first-child th:last-child,.panel>.table:first-child>tbody:first-child>tr:first-child th:last-child,.panel>.table-responsive:first-child>.table:first-child>tbody:first-child>tr:first-child th:last-child{border-top-right-radius:2px}.panel>.table:last-child,.panel>.table-responsive:last-child>.table:last-child{border-bottom-right-radius:2px;border-bottom-left-radius:2px}.panel>.table:last-child>tbody:last-child>tr:last-child td:first-child,.panel>.table-responsive:last-child>.table:last-child>tbody:last-child>tr:last-child td:first-child,.panel>.table:last-child>tfoot:last-child>tr:last-child td:first-child,.panel>.table-responsive:last-child>.table:last-child>tfoot:last-child>tr:last-child td:first-child,.panel>.table:last-child>tbody:last-child>tr:last-child th:first-child,.panel>.table-responsive:last-child>.table:last-child>tbody:last-child>tr:last-child th:first-child,.panel>.table:last-child>tfoot:last-child>tr:last-child th:first-child,.panel>.table-responsive:last-child>.table:last-child>tfoot:last-child>tr:last-child th:first-child{border-bottom-left-radius:2px}.panel>.table:last-child>tbody:last-child>tr:last-child td:last-child,.panel>.table-responsive:last-child>.table:last-child>tbody:last-child>tr:last-child td:last-child,.panel>.table:last-child>tfoot:last-child>tr:last-child td:last-child,.panel>.table-responsive:last-child>.table:last-child>tfoot:last-child>tr:last-child td:last-child,.panel>.table:last-child>tbody:last-child>tr:last-child th:last-child,.panel>.table-responsive:last-child>.table:last-child>tbody:last-child>tr:last-child th:last-child,.panel>.table:last-child>tfoot:last-child>tr:last-child th:last-child,.panel>.table-responsive:last-child>.table:last-child>tfoot:last-child>tr:last-child th:last-child{border-bottom-right-radius:2px}.panel>.panel-body+.table,.panel>.panel-body+.table-responsive{border-top:1px solid #ddd}.panel>.table>tbody:first-child>tr:first-child th,.panel>.table>tbody:first-child>tr:first-child td{border-top:0}.panel>.table-bordered,.panel>.table-responsive>.table-bordered{border:0}.panel>.table-bordered>thead>tr>th:first-child,.panel>.table-responsive>.table-bordered>thead>tr>th:first-child,.panel>.table-bordered>tbody>tr>th:first-child,.panel>.table-responsive>.table-bordered>tbody>tr>th:first-child,.panel>.table-bordered>tfoot>tr>th:first-child,.panel>.table-responsive>.table-bordered>tfoot>tr>th:first-child,.panel>.table-bordered>thead>tr>td:first-child,.panel>.table-responsive>.table-bordered>thead>tr>td:first-child,.panel>.table-bordered>tbody>tr>td:first-child,.panel>.table-responsive>.table-bordered>tbody>tr>td:first-child,.panel>.table-bordered>tfoot>tr>td:first-child,.panel>.table-responsive>.table-bordered>tfoot>tr>td:first-child{border-left:0}.panel>.table-bordered>thead>tr>th:last-child,.panel>.table-responsive>.table-bordered>thead>tr>th:last-child,.panel>.table-bordered>tbody>tr>th:last-child,.panel>.table-responsive>.table-bordered>tbody>tr>th:last-child,.panel>.table-bordered>tfoot>tr>th:last-child,.panel>.table-responsive>.table-bordered>tfoot>tr>th:last-child,.panel>.table-bordered>thead>tr>td:last-child,.panel>.table-responsive>.table-bordered>thead>tr>td:last-child,.panel>.table-bordered>tbody>tr>td:last-child,.panel>.table-responsive>.table-bordered>tbody>tr>td:last-child,.panel>.table-bordered>tfoot>tr>td:last-child,.panel>.table-responsive>.table-bordered>tfoot>tr>td:last-child{border-right:0}.panel>.table-bordered>thead>tr:first-child>td,.panel>.table-responsive>.table-bordered>thead>tr:first-child>td,.panel>.table-bordered>tbody>tr:first-child>td,.panel>.table-responsive>.table-bordered>tbody>tr:first-child>td,.panel>.table-bordered>thead>tr:first-child>th,.panel>.table-responsive>.table-bordered>thead>tr:first-child>th,.panel>.table-bordered>tbody>tr:first-child>th,.panel>.table-responsive>.table-bordered>tbody>tr:first-child>th{border-bottom:0}.panel>.table-bordered>tbody>tr:last-child>td,.panel>.table-responsive>.table-bordered>tbody>tr:last-child>td,.panel>.table-bordered>tfoot>tr:last-child>td,.panel>.table-responsive>.table-bordered>tfoot>tr:last-child>td,.panel>.table-bordered>tbody>tr:last-child>th,.panel>.table-responsive>.table-bordered>tbody>tr:last-child>th,.panel>.table-bordered>tfoot>tr:last-child>th,.panel>.table-responsive>.table-bordered>tfoot>tr:last-child>th{border-bottom:0}.panel>.table-responsive{border:0;margin-bottom:0}.panel-group{margin-bottom:22px}.panel-group .panel{margin-bottom:0;border-radius:3px;overflow:hidden}.panel-group .panel+.panel{margin-top:5px}.panel-group .panel-heading{border-bottom:0}.panel-group .panel-heading+.panel-collapse .panel-body{border-top:1px solid #ddd}.panel-group .panel-footer{border-top:0}.panel-group .panel-footer+.panel-collapse .panel-body{border-bottom:1px solid #ddd}.panel-default{border-color:#ddd}.panel-default>.panel-heading{color:#333;background-color:#f5f5f5;border-color:#ddd}.panel-default>.panel-heading+.panel-collapse .panel-body{border-top-color:#ddd}.panel-default>.panel-footer+.panel-collapse .panel-body{border-bottom-color:#ddd}.panel-primary{border-color:#3498db}.panel-primary>.panel-heading{color:#fff;background-color:#3498db;border-color:#3498db}.panel-primary>.panel-heading+.panel-collapse .panel-body{border-top-color:#3498db}.panel-primary>.panel-footer+.panel-collapse .panel-body{border-bottom-color:#3498db}.panel-success{border-color:#29b74e}.panel-success>.panel-heading{color:#fff;background-color:#2ecc71;border-color:#29b74e}.panel-success>.panel-heading+.panel-collapse .panel-body{border-top-color:#29b74e}.panel-success>.panel-footer+.panel-collapse .panel-body{border-bottom-color:#29b74e}.panel-info{border-color:#7f4bab}.panel-info>.panel-heading{color:#fff;background-color:#9b59b6;border-color:#7f4bab}.panel-info>.panel-heading+.panel-collapse .panel-body{border-top-color:#7f4bab}.panel-info>.panel-footer+.panel-collapse .panel-body{border-bottom-color:#7f4bab}.panel-warning{border-color:#da8f0d}.panel-warning>.panel-heading{color:#fff;background-color:#f1c40f;border-color:#da8f0d}.panel-warning>.panel-heading+.panel-collapse .panel-body{border-top-color:#da8f0d}.panel-warning>.panel-footer+.panel-collapse .panel-body{border-bottom-color:#da8f0d}.panel-danger{border-color:#e42533}.panel-danger>.panel-heading{color:#fff;background-color:#e74c3c;border-color:#e42533}.panel-danger>.panel-heading+.panel-collapse .panel-body{border-top-color:#e42533}.panel-danger>.panel-footer+.panel-collapse .panel-body{border-bottom-color:#e42533}.well{min-height:20px;padding:19px;margin-bottom:20px;background-color:#f5f5f5;border:1px solid #e3e3e3;border-radius:3px;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.05);box-shadow:inset 0 1px 1px rgba(0,0,0,.05)}.well blockquote{border-color:#ddd;border-color:rgba(0,0,0,.15)}.well-lg{padding:24px;border-radius:3px}.well-sm{padding:9px;border-radius:0}.close{float:right;font-size:24px;font-weight:700;line-height:1;color:#000;text-shadow:0 1px 0 #fff;opacity:.2;filter:alpha(opacity=20)}.close:hover,.close:focus{color:#000;text-decoration:none;cursor:pointer;opacity:.5;filter:alpha(opacity=50)}button.close{padding:0;cursor:pointer;background:0 0;border:0;-webkit-appearance:none}.modal-open{overflow:hidden}.modal{display:none;overflow:auto;overflow-y:scroll;position:fixed;top:0;right:0;bottom:0;left:0;z-index:1050;-webkit-overflow-scrolling:touch;outline:0}.modal.fade .modal-dialog{-webkit-transform:translate(0,-25%);-ms-transform:translate(0,-25%);transform:translate(0,-25%);-webkit-transition:-webkit-transform .3s ease-out;-moz-transition:-moz-transform .3s ease-out;-o-transition:-o-transform .3s ease-out;transition:transform .3s ease-out}.modal.in .modal-dialog{-webkit-transform:translate(0,0);-ms-transform:translate(0,0);transform:translate(0,0)}.modal-dialog{position:relative;width:auto;margin:10px}.modal-content{position:relative;background-color:#fff;border:1px solid #999;border:1px solid rgba(0,0,0,.2);border-radius:3px;-webkit-box-shadow:0 3px 9px rgba(0,0,0,.5);box-shadow:0 3px 9px rgba(0,0,0,.5);background-clip:padding-box;outline:0}.modal-backdrop{position:fixed;top:0;right:0;bottom:0;left:0;z-index:1040;background-color:#000}.modal-backdrop.fade{opacity:0;filter:alpha(opacity=0)}.modal-backdrop.in{opacity:.5;filter:alpha(opacity=50)}.modal-header{padding:15px;border-bottom:1px solid #e5e5e5;min-height:16.42857143px}.modal-header .close{margin-top:-2px}.modal-title{margin:0;line-height:1.42857143}.modal-body{position:relative;padding:20px}.modal-footer{margin-top:15px;padding:19px 20px 20px;text-align:right;border-top:1px solid #e5e5e5}.modal-footer .btn+.btn{margin-left:5px;margin-bottom:0}.modal-footer .btn-group .btn+.btn{margin-left:-1px}.modal-footer .btn-block+.btn-block{margin-left:0}@media (min-width:768px){.modal-dialog{width:600px;margin:30px auto}.modal-content{-webkit-box-shadow:0 5px 15px rgba(0,0,0,.5);box-shadow:0 5px 15px rgba(0,0,0,.5)}.modal-sm{width:300px}}@media (min-width:992px){.modal-lg{width:900px}}.tooltip{position:absolute;z-index:1030;display:block;visibility:visible;font-size:14px;line-height:1.4;opacity:0;filter:alpha(opacity=0)}.tooltip.in{opacity:.9;filter:alpha(opacity=90)}.tooltip.top{margin-top:-3px;padding:5px 0}.tooltip.right{margin-left:3px;padding:0 5px}.tooltip.bottom{margin-top:3px;padding:5px 0}.tooltip.left{margin-left:-3px;padding:0 5px}.tooltip-inner{max-width:200px;padding:3px 8px;color:#fff;text-align:center;text-decoration:none;background-color:#000;border-radius:3px}.tooltip-arrow{position:absolute;width:0;height:0;border-color:transparent;border-style:solid}.tooltip.top .tooltip-arrow{bottom:0;left:50%;margin-left:-5px;border-width:5px 5px 0;border-top-color:#000}.tooltip.top-left .tooltip-arrow{bottom:0;left:5px;border-width:5px 5px 0;border-top-color:#000}.tooltip.top-right .tooltip-arrow{bottom:0;right:5px;border-width:5px 5px 0;border-top-color:#000}.tooltip.right .tooltip-arrow{top:50%;left:0;margin-top:-5px;border-width:5px 5px 5px 0;border-right-color:#000}.tooltip.left .tooltip-arrow{top:50%;right:0;margin-top:-5px;border-width:5px 0 5px 5px;border-left-color:#000}.tooltip.bottom .tooltip-arrow{top:0;left:50%;margin-left:-5px;border-width:0 5px 5px;border-bottom-color:#000}.tooltip.bottom-left .tooltip-arrow{top:0;left:5px;border-width:0 5px 5px;border-bottom-color:#000}.tooltip.bottom-right .tooltip-arrow{top:0;right:5px;border-width:0 5px 5px;border-bottom-color:#000}.popover{position:absolute;top:0;left:0;z-index:1010;display:none;max-width:276px;padding:1px;text-align:left;background-color:#fff;background-clip:padding-box;border:1px solid #ccc;border:1px solid rgba(0,0,0,.2);border-radius:3px;-webkit-box-shadow:0 5px 10px rgba(0,0,0,.2);box-shadow:0 5px 10px rgba(0,0,0,.2);white-space:normal}.popover.top{margin-top:-10px}.popover.right{margin-left:10px}.popover.bottom{margin-top:10px}.popover.left{margin-left:-10px}.popover-title{margin:0;padding:8px 14px;font-size:16px;font-weight:400;line-height:18px;background-color:#f7f7f7;border-bottom:1px solid #ebebeb;border-radius:5px 5px 0 0}.popover-content{padding:9px 14px}.popover>.arrow,.popover>.arrow:after{position:absolute;display:block;width:0;height:0;border-color:transparent;border-style:solid}.popover>.arrow{border-width:11px}.popover>.arrow:after{border-width:10px;content:""}.popover.top>.arrow{left:50%;margin-left:-11px;border-bottom-width:0;border-top-color:#999;border-top-color:rgba(0,0,0,.25);bottom:-11px}.popover.top>.arrow:after{content:" ";bottom:1px;margin-left:-10px;border-bottom-width:0;border-top-color:#fff}.popover.right>.arrow{top:50%;left:-11px;margin-top:-11px;border-left-width:0;border-right-color:#999;border-right-color:rgba(0,0,0,.25)}.popover.right>.arrow:after{content:" ";left:1px;bottom:-10px;border-left-width:0;border-right-color:#fff}.popover.bottom>.arrow{left:50%;margin-left:-11px;border-top-width:0;border-bottom-color:#999;border-bottom-color:rgba(0,0,0,.25);top:-11px}.popover.bottom>.arrow:after{content:" ";top:1px;margin-left:-10px;border-top-width:0;border-bottom-color:#fff}.popover.left>.arrow{top:50%;right:-11px;margin-top:-11px;border-right-width:0;border-left-color:#999;border-left-color:rgba(0,0,0,.25)}.popover.left>.arrow:after{content:" ";right:1px;border-right-width:0;border-left-color:#fff;bottom:-10px}.carousel{position:relative}.carousel-inner{position:relative;overflow:hidden;width:100%}.carousel-inner>.item{display:none;position:relative;-webkit-transition:.6s ease-in-out left;transition:.6s ease-in-out left}.carousel-inner>.item>img,.carousel-inner>.item>a>img{line-height:1}.carousel-inner>.active,.carousel-inner>.next,.carousel-inner>.prev{display:block}.carousel-inner>.active{left:0}.carousel-inner>.next,.carousel-inner>.prev{position:absolute;top:0;width:100%}.carousel-inner>.next{left:100%}.carousel-inner>.prev{left:-100%}.carousel-inner>.next.left,.carousel-inner>.prev.right{left:0}.carousel-inner>.active.left{left:-100%}.carousel-inner>.active.right{left:100%}.carousel-control{position:absolute;top:0;left:0;bottom:0;width:15%;opacity:.5;filter:alpha(opacity=50);font-size:20px;color:#fff;text-align:center;text-shadow:0 1px 2px rgba(0,0,0,.6)}.carousel-control.left{background-image:-webkit-linear-gradient(left,color-stop(rgba(0,0,0,.5) 0),color-stop(rgba(0,0,0,.0001) 100%));background-image:linear-gradient(to right,rgba(0,0,0,.5) 0,rgba(0,0,0,.0001) 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#80000000', endColorstr='#00000000', GradientType=1)}.carousel-control.right{left:auto;right:0;background-image:-webkit-linear-gradient(left,color-stop(rgba(0,0,0,.0001) 0),color-stop(rgba(0,0,0,.5) 100%));background-image:linear-gradient(to right,rgba(0,0,0,.0001) 0,rgba(0,0,0,.5) 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#00000000', endColorstr='#80000000', GradientType=1)}.carousel-control:hover,.carousel-control:focus{outline:0;color:#fff;text-decoration:none;opacity:.9;filter:alpha(opacity=90)}.carousel-control .icon-prev,.carousel-control .icon-next,.carousel-control .glyphicon-chevron-left,.carousel-control .glyphicon-chevron-right{position:absolute;top:50%;z-index:5;display:inline-block}.carousel-control .icon-prev,.carousel-control .glyphicon-chevron-left{left:50%}.carousel-control .icon-next,.carousel-control .glyphicon-chevron-right{right:50%}.carousel-control .icon-prev,.carousel-control .icon-next{width:20px;height:20px;margin-top:-10px;margin-left:-10px;font-family:serif}.carousel-control .icon-prev:before{content:'\2039'}.carousel-control .icon-next:before{content:'\203a'}.carousel-indicators{position:absolute;bottom:10px;left:50%;z-index:15;width:60%;margin-left:-30%;padding-left:0;list-style:none;text-align:center}.carousel-indicators li{display:inline-block;width:10px;height:10px;margin:1px;text-indent:-999px;border:1px solid #fff;border-radius:10px;cursor:pointer;background-color:#000 \9;background-color:rgba(0,0,0,0)}.carousel-indicators .active{margin:0;width:12px;height:12px;background-color:#fff}.carousel-caption{position:absolute;left:15%;right:15%;bottom:20px;z-index:10;padding-top:20px;padding-bottom:20px;color:#fff;text-align:center;text-shadow:0 1px 2px rgba(0,0,0,.6)}.carousel-caption .btn{text-shadow:none}@media screen and (min-width:768px){.carousel-control .glyphicon-chevron-left,.carousel-control .glyphicon-chevron-right,.carousel-control .icon-prev,.carousel-control .icon-next{width:30px;height:30px;margin-top:-15px;margin-left:-15px;font-size:30px}.carousel-caption{left:20%;right:20%;padding-bottom:30px}.carousel-indicators{bottom:20px}}.clearfix:before,.clearfix:after,.container:before,.container:after,.container-fluid:before,.container-fluid:after,.row:before,.row:after,.form-horizontal .form-group:before,.form-horizontal .form-group:after,.btn-toolbar:before,.btn-toolbar:after,.btn-group-vertical>.btn-group:before,.btn-group-vertical>.btn-group:after,.nav:before,.nav:after,.navbar:before,.navbar:after,.navbar-header:before,.navbar-header:after,.navbar-collapse:before,.navbar-collapse:after,.pager:before,.pager:after,.panel-body:before,.panel-body:after,.modal-footer:before,.modal-footer:after{content:" ";display:table}.clearfix:after,.container:after,.container-fluid:after,.row:after,.form-horizontal .form-group:after,.btn-toolbar:after,.btn-group-vertical>.btn-group:after,.nav:after,.navbar:after,.navbar-header:after,.navbar-collapse:after,.pager:after,.panel-body:after,.modal-footer:after{clear:both}.center-block{display:block;margin-left:auto;margin-right:auto}.pull-right{float:right!important}.pull-left{float:left!important}.hide{display:none!important}.show{display:block!important}.invisible{visibility:hidden}.text-hide{font:0/0 a;color:transparent;text-shadow:none;background-color:transparent;border:0}.hidden{display:none!important;visibility:hidden!important}.affix{position:fixed}@-ms-viewport{width:device-width}.visible-xs,.visible-sm,.visible-md,.visible-lg{display:none!important}@media (max-width:767px){.visible-xs{display:block!important}table.visible-xs{display:table}tr.visible-xs{display:table-row!important}th.visible-xs,td.visible-xs{display:table-cell!important}}@media (min-width:768px) and (max-width:991px){.visible-sm{display:block!important}table.visible-sm{display:table}tr.visible-sm{display:table-row!important}th.visible-sm,td.visible-sm{display:table-cell!important}}@media (min-width:992px) and (max-width:1199px){.visible-md{display:block!important}table.visible-md{display:table}tr.visible-md{display:table-row!important}th.visible-md,td.visible-md{display:table-cell!important}}@media (min-width:1200px){.visible-lg{display:block!important}table.visible-lg{display:table}tr.visible-lg{display:table-row!important}th.visible-lg,td.visible-lg{display:table-cell!important}}@media (max-width:767px){.hidden-xs{display:none!important}}@media (min-width:768px) and (max-width:991px){.hidden-sm{display:none!important}}@media (min-width:992px) and (max-width:1199px){.hidden-md{display:none!important}}@media (min-width:1200px){.hidden-lg{display:none!important}}.visible-print{display:none!important}@media print{.visible-print{display:block!important}table.visible-print{display:table}tr.visible-print{display:table-row!important}th.visible-print,td.visible-print{display:table-cell!important}}@media print{.hidden-print{display:none!important}} \ No newline at end of file diff --git a/cmd/infra/ursrv/serve/static/bootstrap/js/bootstrap.min.js b/cmd/infra/ursrv/serve/static/bootstrap/js/bootstrap.min.js deleted file mode 100644 index 54d62d678..000000000 --- a/cmd/infra/ursrv/serve/static/bootstrap/js/bootstrap.min.js +++ /dev/null @@ -1,7 +0,0 @@ -/*! - * Bootstrap v3.1.0 (http://getbootstrap.com) - * Copyright 2011-2014 Twitter, Inc. - * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) - */ - -+function(a){"use strict";var b='[data-dismiss="alert"]',c=function(c){a(c).on("click",b,this.close)};c.prototype.close=function(b){function f(){e.trigger("closed.bs.alert").remove()}var c=a(this),d=c.attr("data-target");d||(d=c.attr("href"),d=d&&d.replace(/.*(?=#[^\s]*$)/,""));var e=a(d);b&&b.preventDefault(),e.length||(e=c.hasClass("alert")?c:c.parent()),e.trigger(b=a.Event("close.bs.alert"));if(b.isDefaultPrevented())return;e.removeClass("in"),a.support.transition&&e.hasClass("fade")?e.one(a.support.transition.end,f).emulateTransitionEnd(150):f()};var d=a.fn.alert;a.fn.alert=function(b){return this.each(function(){var d=a(this),e=d.data("bs.alert");e||d.data("bs.alert",e=new c(this)),typeof b=="string"&&e[b].call(d)})},a.fn.alert.Constructor=c,a.fn.alert.noConflict=function(){return a.fn.alert=d,this},a(document).on("click.bs.alert.data-api",b,c.prototype.close)}(jQuery),+function(a){"use strict";var b=function(c,d){this.$element=a(c),this.options=a.extend({},b.DEFAULTS,d),this.isLoading=!1};b.DEFAULTS={loadingText:"loading..."},b.prototype.setState=function(b){var c="disabled",d=this.$element,e=d.is("input")?"val":"html",f=d.data();b+="Text",f.resetText||d.data("resetText",d[e]()),d[e](f[b]||this.options[b]),setTimeout(a.proxy(function(){b=="loadingText"?(this.isLoading=!0,d.addClass(c).attr(c,c)):this.isLoading&&(this.isLoading=!1,d.removeClass(c).removeAttr(c))},this),0)},b.prototype.toggle=function(){var a=!0,b=this.$element.closest('[data-toggle="buttons"]');if(b.length){var c=this.$element.find("input");c.prop("type")=="radio"&&(c.prop("checked")&&this.$element.hasClass("active")?a=!1:b.find(".active").removeClass("active")),a&&c.prop("checked",!this.$element.hasClass("active")).trigger("change")}a&&this.$element.toggleClass("active")};var c=a.fn.button;a.fn.button=function(c){return this.each(function(){var d=a(this),e=d.data("bs.button"),f=typeof c=="object"&&c;e||d.data("bs.button",e=new b(this,f)),c=="toggle"?e.toggle():c&&e.setState(c)})},a.fn.button.Constructor=b,a.fn.button.noConflict=function(){return a.fn.button=c,this},a(document).on("click.bs.button.data-api","[data-toggle^=button]",function(b){var c=a(b.target);c.hasClass("btn")||(c=c.closest(".btn")),c.button("toggle"),b.preventDefault()})}(jQuery),+function(a){"use strict";var b=function(b,c){this.$element=a(b),this.$indicators=this.$element.find(".carousel-indicators"),this.options=c,this.paused=this.sliding=this.interval=this.$active=this.$items=null,this.options.pause=="hover"&&this.$element.on("mouseenter",a.proxy(this.pause,this)).on("mouseleave",a.proxy(this.cycle,this))};b.DEFAULTS={interval:5e3,pause:"hover",wrap:!0},b.prototype.cycle=function(b){return b||(this.paused=!1),this.interval&&clearInterval(this.interval),this.options.interval&&!this.paused&&(this.interval=setInterval(a.proxy(this.next,this),this.options.interval)),this},b.prototype.getActiveIndex=function(){return this.$active=this.$element.find(".item.active"),this.$items=this.$active.parent().children(),this.$items.index(this.$active)},b.prototype.to=function(b){var c=this,d=this.getActiveIndex();if(b>this.$items.length-1||b<0)return;return this.sliding?this.$element.one("slid.bs.carousel",function(){c.to(b)}):d==b?this.pause().cycle():this.slide(b>d?"next":"prev",a(this.$items[b]))},b.prototype.pause=function(b){return b||(this.paused=!0),this.$element.find(".next, .prev").length&&a.support.transition&&(this.$element.trigger(a.support.transition.end),this.cycle(!0)),this.interval=clearInterval(this.interval),this},b.prototype.next=function(){if(this.sliding)return;return this.slide("next")},b.prototype.prev=function(){if(this.sliding)return;return this.slide("prev")},b.prototype.slide=function(b,c){var d=this.$element.find(".item.active"),e=c||d[b](),f=this.interval,g=b=="next"?"left":"right",h=b=="next"?"first":"last",i=this;if(!e.length){if(!this.options.wrap)return;e=this.$element.find(".item")[h]()}if(e.hasClass("active"))return this.sliding=!1;var j=a.Event("slide.bs.carousel",{relatedTarget:e[0],direction:g});this.$element.trigger(j);if(j.isDefaultPrevented())return;return this.sliding=!0,f&&this.pause(),this.$indicators.length&&(this.$indicators.find(".active").removeClass("active"),this.$element.one("slid.bs.carousel",function(){var b=a(i.$indicators.children()[i.getActiveIndex()]);b&&b.addClass("active")})),a.support.transition&&this.$element.hasClass("slide")?(e.addClass(b),e[0].offsetWidth,d.addClass(g),e.addClass(g),d.one(a.support.transition.end,function(){e.removeClass([b,g].join(" ")).addClass("active"),d.removeClass(["active",g].join(" ")),i.sliding=!1,setTimeout(function(){i.$element.trigger("slid.bs.carousel")},0)}).emulateTransitionEnd(d.css("transition-duration").slice(0,-1)*1e3)):(d.removeClass("active"),e.addClass("active"),this.sliding=!1,this.$element.trigger("slid.bs.carousel")),f&&this.cycle(),this};var c=a.fn.carousel;a.fn.carousel=function(c){return this.each(function(){var d=a(this),e=d.data("bs.carousel"),f=a.extend({},b.DEFAULTS,d.data(),typeof c=="object"&&c),g=typeof c=="string"?c:f.slide;e||d.data("bs.carousel",e=new b(this,f)),typeof c=="number"?e.to(c):g?e[g]():f.interval&&e.pause().cycle()})},a.fn.carousel.Constructor=b,a.fn.carousel.noConflict=function(){return a.fn.carousel=c,this},a(document).on("click.bs.carousel.data-api","[data-slide], [data-slide-to]",function(b){var c=a(this),d,e=a(c.attr("data-target")||(d=c.attr("href"))&&d.replace(/.*(?=#[^\s]+$)/,"")),f=a.extend({},e.data(),c.data()),g=c.attr("data-slide-to");g&&(f.interval=!1),e.carousel(f),(g=c.attr("data-slide-to"))&&e.data("bs.carousel").to(g),b.preventDefault()}),a(window).on("load",function(){a('[data-ride="carousel"]').each(function(){var b=a(this);b.carousel(b.data())})})}(jQuery),+function(a){function e(d){a(b).remove(),a(c).each(function(){var b=f(a(this)),c={relatedTarget:this};if(!b.hasClass("open"))return;b.trigger(d=a.Event("hide.bs.dropdown",c));if(d.isDefaultPrevented())return;b.removeClass("open").trigger("hidden.bs.dropdown",c)})}function f(b){var c=b.attr("data-target");c||(c=b.attr("href"),c=c&&/#[A-Za-z]/.test(c)&&c.replace(/.*(?=#[^\s]*$)/,""));var d=c&&a(c);return d&&d.length?d:b.parent()}"use strict";var b=".dropdown-backdrop",c="[data-toggle=dropdown]",d=function(b){a(b).on("click.bs.dropdown",this.toggle)};d.prototype.toggle=function(b){var c=a(this);if(c.is(".disabled, :disabled"))return;var d=f(c),g=d.hasClass("open");e();if(!g){"ontouchstart"in document.documentElement&&!d.closest(".navbar-nav").length&&a(''}),b.prototype=a.extend({},a.fn.tooltip.Constructor.prototype),b.prototype.constructor=b,b.prototype.getDefaults=function(){return b.DEFAULTS},b.prototype.setContent=function(){var a=this.tip(),b=this.getTitle(),c=this.getContent();a.find(".popover-title")[this.options.html?"html":"text"](b),a.find(".popover-content")[this.options.html?typeof c=="string"?"html":"append":"text"](c),a.removeClass("fade top bottom left right in"),a.find(".popover-title").html()||a.find(".popover-title").hide()},b.prototype.hasContent=function(){return this.getTitle()||this.getContent()},b.prototype.getContent=function(){var a=this.$element,b=this.options;return a.attr("data-content")||(typeof b.content=="function"?b.content.call(a[0]):b.content)},b.prototype.arrow=function(){return this.$arrow=this.$arrow||this.tip().find(".arrow")},b.prototype.tip=function(){return this.$tip||(this.$tip=a(this.options.template)),this.$tip};var c=a.fn.popover;a.fn.popover=function(c){return this.each(function(){var d=a(this),e=d.data("bs.popover"),f=typeof c=="object"&&c;if(!e&&c=="destroy")return;e||d.data("bs.popover",e=new b(this,f)),typeof c=="string"&&e[c]()})},a.fn.popover.Constructor=b,a.fn.popover.noConflict=function(){return a.fn.popover=c,this}}(jQuery),+function(a){"use strict";var b=function(b){this.element=a(b)};b.prototype.show=function(){var b=this.element,c=b.closest("ul:not(.dropdown-menu)"),d=b.data("target");d||(d=b.attr("href"),d=d&&d.replace(/.*(?=#[^\s]*$)/,""));if(b.parent("li").hasClass("active"))return;var e=c.find(".active:last a")[0],f=a.Event("show.bs.tab",{relatedTarget:e});b.trigger(f);if(f.isDefaultPrevented())return;var g=a(d);this.activate(b.parent("li"),c),this.activate(g,g.parent(),function(){b.trigger({type:"shown.bs.tab",relatedTarget:e})})},b.prototype.activate=function(b,c,d){function g(){e.removeClass("active").find("> .dropdown-menu > .active").removeClass("active"),b.addClass("active"),f?(b[0].offsetWidth,b.addClass("in")):b.removeClass("fade"),b.parent(".dropdown-menu")&&b.closest("li.dropdown").addClass("active"),d&&d()}var e=c.find("> .active"),f=d&&a.support.transition&&e.hasClass("fade");f?e.one(a.support.transition.end,g).emulateTransitionEnd(150):g(),e.removeClass("in")};var c=a.fn.tab;a.fn.tab=function(c){return this.each(function(){var d=a(this),e=d.data("bs.tab");e||d.data("bs.tab",e=new b(this)),typeof c=="string"&&e[c]()})},a.fn.tab.Constructor=b,a.fn.tab.noConflict=function(){return a.fn.tab=c,this},a(document).on("click.bs.tab.data-api",'[data-toggle="tab"], [data-toggle="pill"]',function(b){b.preventDefault(),a(this).tab("show")})}(jQuery),+function(a){"use strict";var b=function(c,d){this.options=a.extend({},b.DEFAULTS,d),this.$window=a(window).on("scroll.bs.affix.data-api",a.proxy(this.checkPosition,this)).on("click.bs.affix.data-api",a.proxy(this.checkPositionWithEventLoop,this)),this.$element=a(c),this.affixed=this.unpin=this.pinnedOffset=null,this.checkPosition()};b.RESET="affix affix-top affix-bottom",b.DEFAULTS={offset:0},b.prototype.getPinnedOffset=function(){if(this.pinnedOffset)return this.pinnedOffset;this.$element.removeClass(b.RESET).addClass("affix");var a=this.$window.scrollTop(),c=this.$element.offset();return this.pinnedOffset=c.top-a},b.prototype.checkPositionWithEventLoop=function(){setTimeout(a.proxy(this.checkPosition,this),1)},b.prototype.checkPosition=function(){if(!this.$element.is(":visible"))return;var c=a(document).height(),d=this.$window.scrollTop(),e=this.$element.offset(),f=this.options.offset,g=f.top,h=f.bottom;this.affixed=="top"&&(e.top+=d),typeof f!="object"&&(h=g=f),typeof g=="function"&&(g=f.top(this.$element)),typeof h=="function"&&(h=f.bottom(this.$element));var i=this.unpin!=null&&d+this.unpin<=e.top?!1:h!=null&&e.top+this.$element.height()>=c-h?"bottom":g!=null&&d<=g?"top":!1;if(this.affixed===i)return;this.unpin&&this.$element.css("top","");var j="affix"+(i?"-"+i:""),k=a.Event(j+".bs.affix");this.$element.trigger(k);if(k.isDefaultPrevented())return;this.affixed=i,this.unpin=i=="bottom"?this.getPinnedOffset():null,this.$element.removeClass(b.RESET).addClass(j).trigger(a.Event(j.replace("affix","affixed"))),i=="bottom"&&this.$element.offset({top:c-h-this.$element.height()})};var c=a.fn.affix;a.fn.affix=function(c){return this.each(function(){var d=a(this),e=d.data("bs.affix"),f=typeof c=="object"&&c;e||d.data("bs.affix",e=new b(this,f)),typeof c=="string"&&e[c]()})},a.fn.affix.Constructor=b,a.fn.affix.noConflict=function(){return a.fn.affix=c,this},a(window).on("load",function(){a('[data-spy="affix"]').each(function(){var b=a(this),c=b.data();c.offset=c.offset||{},c.offsetBottom&&(c.offset.bottom=c.offsetBottom),c.offsetTop&&(c.offset.top=c.offsetTop),b.affix(c)})})}(jQuery),+function(a){"use strict";var b=function(c,d){this.$element=a(c),this.options=a.extend({},b.DEFAULTS,d),this.transitioning=null,this.options.parent&&(this.$parent=a(this.options.parent)),this.options.toggle&&this.toggle()};b.DEFAULTS={toggle:!0},b.prototype.dimension=function(){var a=this.$element.hasClass("width");return a?"width":"height"},b.prototype.show=function(){if(this.transitioning||this.$element.hasClass("in"))return;var b=a.Event("show.bs.collapse");this.$element.trigger(b);if(b.isDefaultPrevented())return;var c=this.$parent&&this.$parent.find("> .panel > .in");if(c&&c.length){var d=c.data("bs.collapse");if(d&&d.transitioning)return;c.collapse("hide"),d||c.data("bs.collapse",null)}var e=this.dimension();this.$element.removeClass("collapse").addClass("collapsing")[e](0),this.transitioning=1;var f=function(){this.$element.removeClass("collapsing").addClass("collapse in")[e]("auto"),this.transitioning=0,this.$element.trigger("shown.bs.collapse")};if(!a.support.transition)return f.call(this);var g=a.camelCase(["scroll",e].join("-"));this.$element.one(a.support.transition.end,a.proxy(f,this)).emulateTransitionEnd(350)[e](this.$element[0][g])},b.prototype.hide=function(){if(this.transitioning||!this.$element.hasClass("in"))return;var b=a.Event("hide.bs.collapse");this.$element.trigger(b);if(b.isDefaultPrevented())return;var c=this.dimension();this.$element[c](this.$element[c]())[0].offsetHeight,this.$element.addClass("collapsing").removeClass("collapse").removeClass("in"),this.transitioning=1;var d=function(){this.transitioning=0,this.$element.trigger("hidden.bs.collapse").removeClass("collapsing").addClass("collapse")};if(!a.support.transition)return d.call(this);this.$element[c](0).one(a.support.transition.end,a.proxy(d,this)).emulateTransitionEnd(350)},b.prototype.toggle=function(){this[this.$element.hasClass("in")?"hide":"show"]()};var c=a.fn.collapse;a.fn.collapse=function(c){return this.each(function(){var d=a(this),e=d.data("bs.collapse"),f=a.extend({},b.DEFAULTS,d.data(),typeof c=="object"&&c);!e&&f.toggle&&c=="show"&&(c=!c),e||d.data("bs.collapse",e=new b(this,f)),typeof c=="string"&&e[c]()})},a.fn.collapse.Constructor=b,a.fn.collapse.noConflict=function(){return a.fn.collapse=c,this},a(document).on("click.bs.collapse.data-api","[data-toggle=collapse]",function(b){var c=a(this),d,e=c.attr("data-target")||b.preventDefault()||(d=c.attr("href"))&&d.replace(/.*(?=#[^\s]+$)/,""),f=a(e),g=f.data("bs.collapse"),h=g?"toggle":c.data(),i=c.attr("data-parent"),j=i&&a(i);if(!g||!g.transitioning)j&&j.find('[data-toggle=collapse][data-parent="'+i+'"]').not(c).addClass("collapsed"),c[f.hasClass("in")?"addClass":"removeClass"]("collapsed");f.collapse(h)})}(jQuery),+function(a){function b(c,d){var e,f=a.proxy(this.process,this);this.$element=a(c).is("body")?a(window):a(c),this.$body=a("body"),this.$scrollElement=this.$element.on("scroll.bs.scroll-spy.data-api",f),this.options=a.extend({},b.DEFAULTS,d),this.selector=(this.options.target||(e=a(c).attr("href"))&&e.replace(/.*(?=#[^\s]+$)/,"")||"")+" .nav li > a",this.offsets=a([]),this.targets=a([]),this.activeTarget=null,this.refresh(),this.process()}"use strict",b.DEFAULTS={offset:10},b.prototype.refresh=function(){var b=this.$element[0]==window?"offset":"position";this.offsets=a([]),this.targets=a([]);var c=this,d=this.$body.find(this.selector).map(function(){var d=a(this),e=d.data("target")||d.attr("href"),f=/^#./.test(e)&&a(e);return f&&f.length&&f.is(":visible")&&[[f[b]().top+(!a.isWindow(c.$scrollElement.get(0))&&c.$scrollElement.scrollTop()),e]]||null}).sort(function(a,b){return a[0]-b[0]}).each(function(){c.offsets.push(this[0]),c.targets.push(this[1])})},b.prototype.process=function(){var a=this.$scrollElement.scrollTop()+this.options.offset,b=this.$scrollElement[0].scrollHeight||this.$body[0].scrollHeight,c=b-this.$scrollElement.height(),d=this.offsets,e=this.targets,f=this.activeTarget,g;if(a>=c)return f!=(g=e.last()[0])&&this.activate(g);if(f&&a<=d[0])return f!=(g=e[0])&&this.activate(g);for(g=d.length;g--;)f!=e[g]&&a>=d[g]&&(!d[g+1]||a<=d[g+1])&&this.activate(e[g])},b.prototype.activate=function(b){this.activeTarget=b,a(this.selector).parentsUntil(this.options.target,".active").removeClass("active");var c=this.selector+'[data-target="'+b+'"],'+this.selector+'[href="'+b+'"]',d=a(c).parents("li").addClass("active");d.parent(".dropdown-menu").length&&(d=d.closest("li.dropdown").addClass("active")),d.trigger("activate.bs.scrollspy")};var c=a.fn.scrollspy;a.fn.scrollspy=function(c){return this.each(function(){var d=a(this),e=d.data("bs.scrollspy"),f=typeof c=="object"&&c;e||d.data("bs.scrollspy",e=new b(this,f)),typeof c=="string"&&e[c]()})},a.fn.scrollspy.Constructor=b,a.fn.scrollspy.noConflict=function(){return a.fn.scrollspy=c,this},a(window).on("load",function(){a('[data-spy="scroll"]').each(function(){var b=a(this);b.scrollspy(b.data())})})}(jQuery),+function(a){function b(){var a=document.createElement("bootstrap"),b={WebkitTransition:"webkitTransitionEnd",MozTransition:"transitionend",OTransition:"oTransitionEnd otransitionend",transition:"transitionend"};for(var c in b)if(a.style[c]!==undefined)return{end:b[c]};return!1}"use strict",a.fn.emulateTransitionEnd=function(b){var c=!1,d=this;a(this).one(a.support.transition.end,function(){c=!0});var e=function(){c||a(d).trigger(a.support.transition.end)};return setTimeout(e,b),this},a(function(){a.support.transition=b()})}(jQuery) \ No newline at end of file diff --git a/cmd/infra/ursrv/serve/static/fonts/glyphicons-halflings-regular.eot b/cmd/infra/ursrv/serve/static/fonts/glyphicons-halflings-regular.eot deleted file mode 100644 index 423bd5d3a20b804f596e04e5cd02fb4f16cfcbc1..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 20290 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 aAAkE7zj^)5x4-}Cx4(J))59NryZXOu!ZLOM diff --git a/cmd/infra/ursrv/serve/static/fonts/glyphicons-halflings-regular.ttf b/cmd/infra/ursrv/serve/static/fonts/glyphicons-halflings-regular.ttf deleted file mode 100644 index a498ef4e7c8b556fc36f580c5ff524025bb11c84..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 41236 zcmc${34B}Cl|TOOdr!M8>1nlW%aSZh@-ADltvHKgvhN959SD$s!WNdWGz16%Qr5Hq zLm`wxhZF|Lu$1?dP}&a6w6rkl;x0@`ftk{z3q#8?Eo6ReL;Ujlp8MoA3AF$DeLjCD zlHMl0d(S=h+;hHXc>)szLBX3Wc;?Jmx%k3A|K_)Xz-n-`X6~%nbC?xp1U3o#v85|A z*$bXrcnkLXvA_PjOE+x(^}IzP?0-`b#EZ|{a&=5-kZ#A1)#JSN{LL3!x?+FkN$j`a z{KgA5T(ud;J%V7qkIr9k$+hP<{q(UrvH!3j+*x_y#tj7~Z^HK7`*FVeLL9JXWjFTU z$A0~VmtMW~yZ@@(EeHen4e`h&m!G#Gd;iMo1mR26#&2G_Ve4j5W_twTz87(Q?6M7) zZanZW4}OgO{}cpi+vdx!y86eb4XhS~FQfg|TQ*<0akKhSvtJPQ;Jnaw&Bk-j-=Htg z3&Pi&*f--v)DeC>?a`mo=TFXRd%*bg-oVeeuvbY(1QGj8cndGI1beuhd@~ymOoA*q z#h+pS4C9miqmUIrEdi%a{ep`JtY53N14 z{?J8-u03?;p$87z4u=mn9_~3j=kWZ)YY$&^_}asF9=`wZgTEGzAIGm5zt@D{6DItg zaL9DXb0~JG{ZQYbW%#{w4{bhl)1iUG?6Bu>>~Q!asH*G5-F7f0ttPmA`|67~Nd|1t2u@Q*SYReFv6!$}$f<4-=-kPct) z|MMp?^teB8{@?g_x6mN|MHO09!M9Ldw5(rUuw|_(B&JuY=H~usYx%Jo*2WH~%-2@g zsMRu8VN#&!Ke z)gP>_PQ+DHbH6%g%UXV7?OObvsik7w8Lg_hMXO_X;O?xckEv2}ej=vIsRgRAtbgamof~4bF{wHpUt7JC?=3g>=!SNq zb)ITZ95->a#9rgwakj)Vs-<~de=IgPF=xZYvHn=$T;nI`x(d28ZXMeho4a$)hQ!X; z&IG?*LKT+xt9`f<{iEBeeH&>9-*NFfO*>c_k5|VI?gSa|rTJ*vs&d=VK3wK*NyHA8 zZ=Q(tFI-U_SJ~SBo#@c~#Lh%)=lq?C4b&3q4!u)*JTwem41+=)pbhVY4xpilIf)Gy zuOHhJ`l_!5o!EIhk!?XCvD2c)mi14q{tnLgTlNWktZ&8)w(y%C;XHxA)5WXM^4QMh z{fTqY`oxTCe6Yj}P`+<@e^H1DGtZk*WHE*hHFlmF-dMw1ieC)0s5lC`;H{My60#JM z#*Nw5fSn7a7$%uTXw#UGnOd~S;s;sHZ2HfsMM=b_phUL-FPLPEWu3K_K`r?NrSk!5OSM)e(3Ohp!Upus`hn3ceKQ;2eKyHol)oqyLDikr zdRVhomsh;1rAKX5ijG*er>BRgn9p_Q6Zu?szB`u<1w)C>HZf7>5-o8{+#JALt(?pD zid{Lg#hj>1x3P4gaE0lu!tKe0pWFY@=BeiAbBh+#R`$%A?qk;%^aEzL8}GLEo|(Bo zWWl1`*P|OYJvn$y{R}5NQpj`_o;+jMOBY<6?{5$LTh8b$v~?F2Ts@=NUDdv(>zRu` z_YZAPZ{>VeVgvFb@kQ{Lm-B)&$W%F_nT(MKSxeF_$F>nUY53Ujk64TRvV58l6rzGE zWmNZ|YR6YX8Lbju(d?4q)tug*p7svOAI!zG-CdojM4hFLCF;xpf5^pLS1c7j-1^j0 zTiaS%p1hbYJ@cvJ@8+p&HNT`ZJmNyTPT z*gy%b{$v?z(GQ6IVn0T^r9cPu%_Y8fWax46Ox?*^hW4V(((#Xve=NTwzl7OjCf&=D z1Uoal^4*;oma4N-i8Z1gy;vC5Y#{3@Sg5?$nX;H%EP!KXx&Dr& zr-2xK3zn|&Dt9iOv%+N`^4MM2|H5UBRe|+Q;@J-k{n-<$y0Sap7!IADm#(lor0+^T z`_NLQGE6Ib==l5c_vHr#pHMBV6^c-tnpJN`4GpT*8T5v!H5rv1R0D%*z(cY@HDL~b z-NOOJyH655-uh6FYEr=Yg64H$3fOwokfM5e)N1cOCRj{3-`?T%phE$_g$4a?X0A&! zu)F99#=1SJScuht)oPZo7K`OltKX_0xaO|X=U-;t?|xVRkbOYs^xu~5x<)^Mlb2d7 ztYwLKiT=lzzl$qqSV*?@%g@QPgs>10m|B%lg@dYV5dXDmgQYur#ab4^n;7uBBukrI zm~_T9*Ie7ue*M@#__LjZ9y-(h9?M%tjw`E1EJb%{gd2;KDEqy)L-gIMe)vDr+ zH(d)_9si~{s`S_p&$i9rx%r={xSdPn2R@DE&d7 z&V2d@>|gPTwo2oEBM3cOt$_IDVn_xPm8TRY(%4`3g)I3{I-f{ePQ1^|@6Z3v_ZEEj zy~RsTa!2v%yMFz}UBCO{zyCX@6W%btpv{1nyI5CUY8vb8&ITjQZ%zbQfDI(4tAA0a zC)vQ=j1}(BmA0wswo>l?f_@z42h9ii{vy6EIj~asu$ojuCM1M3H0=y#genwqQL`!! zYLzhvN=rtq%c<5uwLYslGHNQPItSH;tm@9FO*z#wsJ3KPUq)@qss2H=Jxl$s&E|+4 zOzq_3C=c$lIz9gSP*#;aB%=1&DwF{2Rt~B)csIB*l2v1a`|2B7+UZoxqs4J$vaz*; zcBMhBiv*R^0YOz&-P5DG6|E*h0;_|smtBdj-1wIdQV_E=&L$kE>tywl{e_V~h@YXo z{Pp6N@q7Da4?`?OyhN_Fh+RnKKqRG5pY2u5((&= z>3wut>>s-~b~`(IQAE6S%+AnDV|K=!5gQ6z;}a&8eVGy#$N^ zM(Qkpks=vw(KhV+2enyOW4|?{t@|SO>j$-!w`4(`0iurPA*Qo|`5NfcqqRd)^)178 z&!9H1pFTa>dK}w)6SglJ)VAJ{&1&~>%F$ey!i?F_%<57~*Qf8Z&p1Ev`+x8CkwA%t z;1q9c;FPEMiO)Kp9r<1M_{lbp{m;pcj=AMR;nbsdeVx)LM0e%y$LPBEg|hLew;KZwEX#-OG!nC8I5(WTL#dBJ5L<_V3~r|o|> zwZ#`{xQ1rY`^mS*(tLDiN9g?76s5H;BGkzr$xQ^LVChM-bc8)7We*H}?I-M2eVx>a zExFCBU(ly=4lFAMo|nxWcR2^MfLWmVQ3v8Pt_Q$BjknF;px#L&_4DFra&c~ zt5%BsFvHhAUH6b6&vSuXAQ4D(eX1TZr%);sN}r*P=xgbsLSdA4U*URHR5)uK?aGvi zjiF3gv%;#yHLK@Iv#N=V>E%S->Uq+wYHB}IyOOYso!GOjyGAsuIi#ns56f!Su50zz zEkWpER@S_jt648I&&%i-*A<13{2=s)YOMCN1u`7T3~1r&l4Y<6r5&Safib6AJem_@ z?HepQeRR+XJBmyu&1u0Pg(_2o!)!^+N>X{AdH4|SI`R$O{{AZnK6N}o*5H3 z^xBgbY&*)%J-Y3JCto}Bq1WGk{h>42FC&2h%_O{u{V%YF-Y4>gQV4?6QBZ&LDgY&$33Vi zT-xMeVKW%V!~Y5}PFhMB`Vu1pg&onIWO+kTSVnZK5~}6h@@`?SaJq1=Kk?J)6#Ud$s1%h~a(ys2GegOE8oV1+kgSP8YkUvruYV9zk8tSSuDRW!Kblar%Wm2V^ zec5FCGV_F_Wi3;0GqtvxjVnyq7SpX$+LlS-3h@CmyI^~9JN}DnGaIx+f11@bE-YuzkPfE z+U?t+K3Igp@#C^;@)?Cn=eC2St6RCAO;o}h)=XB2SH>r+jiH(R z9}@?}TT1!?`X{axZyDM)w3psFqQzKfa_sLng@$!Mg%ik zArXAWY~niU2t}B}3N8ox4>sU(9Q(S%CHAwHu)N*j(w#$Rp?i{-`c5)d7G(Ju`5CNn zKJdT}foyPK6MiyZiy=SVCKSN9z`~F*&M*wof(ne9NAqKxMlTBEqL7CsH|9MVjhep# za>_2be3)6962gv6c9X3uXnr^LEJB5cPWkARnJG@}&{E^AkI7z-D97r(W%JfYQX(Ml zVO}Eu{^ZG&rB#CEB>ZD>DIxiCQlh|~`+49||IgTS zL+>8zfbQ0{O~OG1y#;a7wfYSY=m&{Xu`50ki_90E{FptSH|76|y(P zb%Pp3t?f|*-u+IKFGy>wpoM&j_jzWu303746^KE$R^&?&8y-oCi+hQkv*+z2Z|^zB z_*nN5TlvvP`ZLRRmv$dzV@}|_DC*CAMCWxrUBR^DdA3T}FwC=M7KLUo!lI-Sz{Z7v zTjt9e>IwLAKk+3j;vTh9Q3E|Hju3MOc~5-c&gYrgB5*zE>aGLN9dMg=@XFsCDChI52^RiK{Y1aV}WT?!H-7*m-OD;UE5cw+g=I!O$(+jJ^Yeat4a#)%V{ z?Z>D;^E9USPIgZT(l%7qn`(p=0zu6XK}tpqqn$ADG2W0_ZjWX+__Y@8w9_D(WS>72 zreU@zS|CX4zCxqV1e+fK2vlK3<&E~&iUcAj{N`B7LqM}7u2`_D12ZfuO1qEh{{XG% zj?3<41NVIORcJ-xPe_5n=`B!~pjDktXRbT*AAjXvRJdY3;t`mw1&3nwT;9xNr zrFkB#!aN6VWg0A2nCL(SCO%W^xGDos$74*xszEJ*&Ui?bQ2-C4!7o@$4m?EAc#fV-844+yZ5$yDNuz3Amhkx8>EZ-lK2+ z(&pQ>qx0DS|J-dH7W+y0yN=E-JF3z0M4$YafRztomGdq6SSDgw%LLV$Q7dzVw7?+% z#{`@M7&L%PP!3}`6{052*}FbR$Y>Ix5N3|`U=c_aDID-0xV%AZkt(fKFUu<~)+U)P==Rjxw{E-g;zDD?^|uV% ze)SoC!rj=w)b@&awQ1?;?8xb}?F|j~*{2&a1Me8~2f)=G!fC<CLIBLA9HY za|C3XQMPAjC94B%ng`WpkCw&OltFchNAqASG^ou4YiFB5Bc~%$0~!fhDudZ+@%a1_ zakmre9hY^=h$Yj@Vzof-NA}x9_<{mHPFjPY1Uw}t?7JLL>URB>nSZ;BZ=Uzq+wZ>p z*m)(Vb&u7_-^BjWZRUfZbg-5ie}3haKfh5wVC-FuFW`Gu553NQOkdJF>3z&L9|u7w z$^Fv1z!os&mAFYU#Tje{m=UlH(g5BK$uFwAcFi6B45L3(;zW&j3EV%Ad54o|kFESB_FidiRrMSVp9Gk5!h=JoBWVd|tzg z#n(*>Y%b_~7LuSa?MUf@?geEAQyiK%oPj`kih|j}F*uTOxwwr9{!lOr7i=0HSOzQi zE%8NIb#Fv!SJX!64MXrBb~n^Lr}UeZk=oh_z2UwRt!$=Wg1&U$Fyyy!=MZKP-CXr! zIvDmH?oVDne*gWre~?rtC=(}XK{7`Ost9puwBr}X{cuy!0UpquS@tru$l;pMB9-=W z61v^69$|<7#_)Z?=S5mC%xSnG?QoTkGpFqkLq*X7y$3S}Lc&{QvWe3Ou@=zVpyR}q z!gJDB3q#(5_@T_6J5~wyD;(n?cT4~fhqY3J1|y*LK*!+aF$YTQW%hC;aO_YZ!d}#8 z%iI06wG`*X!?gH#Ik2*($-|qZ5rc&U%MmuCoqMP$v;wgoMTy5;j98G+Y0w35CW0~m zfe{!6Yy=iEL9mEdiv$-o0qao~S^XLSi%Z(Ye6)GA$s~CtZ??rU580Gk6G=siIJz5&QX&%&a z=t>mBpoV+2<}|t#uTRFPOIm9q_M&wOvIy09pS1Byo{t2m7^UvM%gA~ z@pg%B9`qm(ga!mn^ar!uovAuf{H8QY?-EM0TXyI2E1F7;%O|%voV%eV6$VNJ10{2B ze{XL;19j*sQkbmOv%8wH6Yx)Igei<`23U+P>OC7`M-;mFTzn2TaUEU;_aUyQcCaWq zNwPCFkwKuCp@DYQwXx|e9>Opn03n576RdLySc)#@X3Q7zb+Jnud+UAc*zLZu!I8t!oeo)#Ph)RY>m~^R`zztKgUaH}-=s z>fZy;VNOWjgS{Sugy;}93dI=lTzt^@MA#9=r)f~_;FeH@2OP#n38-s)kQS;qmMn}8 zEQw_7paN#)qm*pJC`o0RSXw-Jc!X0$;#zq4Asb~wO)?M*kF{m2&87s9(&Vm2a?GBxmllEpt}hv$(Wj1&Z{d=2OWtw}(>F<&%0WI6yr5?xU& z_7v;kR8$${Ph-u=hZ0K80=z4Z9gIXXQ$k?1yaH2H3M^c>@P-@kI=WkYad*}eXp7gC z3i{?ksV<)JD^MbzeDc_#C#Cafd5xq4Hu2ckvxP!dS}xiG=?Lb!D8!F{L%tibkNOLg z*Gl~r2f1lFw!3z;+ii3g0cC%8CnL~l_K8*-!yMN`_ zg%5c+`4aH=?neUhBC^0f*-!6MjNWPe!1lX*yOQ3;etI9;3zdbI6z**)ed^ZV(pH#2 zSQEH+mbV>P%eeiC=f}5owB4msx>`q?$c~I`>YGP4#~eLLdsAhE5qbqY(r^p_ra^ql zvfYC z{q%krJu-UtS^fGf-}uDyWBc{DY-dNB&-y-N6JkKXwCC&I=v)|%9a&x;H^dWQ=nzkU zULu|VL${L07F@z(3kq2p$!$6E-&_qbaTDnWMNh1qY#|#2VZ$V{c5deD=ES&xiBTP& zwLc1(7(6kNR-d&$>frqJEy7twdFF4~{yV6CY~VA7Wz4uCgXB0+L@uk$&{C^}CSfv= zs2I1_5demzu?~g$re=0CSM!uVxM3MgpuZxYRTojiv|cfefUYgTCz@6GPBowX{UV52GzD(IIcN zMY;uMx=-B6_qX7k!7`;F-eKE?=6MJaa`X#2>6#w{c71pir1sT=P$Tl|TtPV|=9;G~dNqfMVf{@AZfZp53zSVgy`d@bV0 z5jNi@<`Ku6Zxhog1T?tV=Vo1c)m62D`AgR{-fZqa62 zmuI`r{^r-d`pWvbcW=4os?Xgvd+mdTDYE(O7j9gBN!7XL;DUzvyE=21?Z!Md`0W+> zLgbRgg_N*HC{~e%2_y#I02;6~A27qKMAQflY7ImUc$M~d^E@s$!kF(37-`0OX#vnTa^!&ZY z^#hN;$M%1XJ$$9UiT(A8D+22XV1N8Qv-R6B5S?`84W+}6zxUq7S@!T1xaKccT(PQ# zWR&5jyB{*D2HxX&<(^^Mz-N;lRBaqXkv(wFGm44;TLPwPC;43G0Sg8q^Rcvt#w6al>Yj<6d9wC`3(l#HunYAE zEtT_TuAbRr^k`YEf4D~vcA-Noo!70S)LbhKYjqF)jCJFxz98wma4 zJ>u9J@5`vmpW|lSyKkwD5_Un+>T!&h4ISMVguPG4WJQa`$x&GrUZ)r>n}`5B^sQy; z%%c9-#Llf|)nfM@`tmOseF|yAU7B6`C+gEK{kLNNPW|*RQA`G2STi+9y4ga}OMHj9 z2kQ~`jSb5sVy*lKk!L`n&dQT?G>;#X(9C68km7+VLXc>pq6wIf0N7aoYXl-T@L^*> zTY(ng09HYYRbuJyaTK)lJ^fAKnkDf}*6^xvC*{lKe;?ZB0<5{(V}_7>3C2Pzxh zKnLPQAR-LfqCJH8VQm}nTp)%6&Rz0mU=fD$KrSr4ku{79eIffVfUfWA3$PmVd*F@h z3?%7`a0?;T$4${#=s4~I31sw|BTYtNZUFZ%{uy^F--vE?;?4AM`G%DvH)X;dBYKLz zoXbIRFqRAoEk8Kw*OTVZyAx;$xyuEIGHm;eA`zFtNJ0fL$o zl#yVziNS3k(r_5)*uY)xAv;m4E8iQ=LjL>o>tsFAuXAe(zc%`%-L%{ryZn22lN&IW zW~@jCVq_ZIXYh@J1)3cZJBNNOFQN`pb_#pf;L$N-gdYL`4Wwb1Ipr(~4MZ(~bo4V6 zYEA*w5Dc6Xy6D&uc4SnMB~^>=fYqlW@}i-) zjvAUVTF=~KC+5nx1dH@n`JZ@vE<@OD`di|%KkARL4Sy8Z45@!)8?Z%v^BjLoUM^ov z)=bjI@+@Qt;2_(eKk_GWYJd%?FY`->UI{Wbq@nX@FHms#S@~Iku-q9u;sIGMNLQm) zW1e889vAU|q2Lh@`zYc8QcchT6e3H(A$%bk8?EF+6f9RN;g*s1FdyWs53x!gAXe#v zJ4^hJhdB%%e1Fd#wwxax*Dg17h|!oNY8M>lBkiKNAfU$-7gRxO=19Ao6d7U>u*Aq% zH8lp0M*Fy6Dsq&c&@4*2I7y>Uq*a!;sjROWgdz}(GplA{xTDiUOSVkSsDNfT;pT9F z!VQXONlR#ABUZe=YuD>{-G%o9yH03Ju23XPQ zZX-pzQ_;-8FDK9yQ3Oz5drgy}*HXZ##U+Pwy>b_@LnstJELRgdSQ?Ps7PDv)ZL&-D zNxq;pWOAn?m8@j)w${}oI%aiLUvwK7b{qx3tYVdDcG@i_34z6)pwq+TP;^>KvNvY? zv$;hLmFCSue}npK zOC4|P z=168Z{tw?r@Ljn&NDh1>s5}KGs5VNu+DO%92tHTE5&2I{N(W$w2{C# z9uF{{6GtNa#zZ@uD&%Ya?YCb#{GW5#NKEJ0(9QoCz696uIXAWs;S>5WHZ--|2Z}-+ z?Sm1oHrfZnsX106jP?QIik+(Un|7`F@m=~8r);>M*tKTxE*;fNFcZeMxw_nDFh8aM zF~5-*YOFXEs|eY^6GMk%?A#Qhh?q5S7LT!WRiC)(_(P0ByL>#Xt22Ex&!Ht5-zV)J$o&+(kF^?Y_%U>>1@H%% zNtZ>U4p1OCg%Nv&kZP!wnoR9r<&bJ>$dB2}aN8ayKr;#w3#TV$#$qq)mEUWnnJ4=*Jix|yZ!(%-uIy}MZI zW_>fNz?2V2Hadb`$gesfA>Sq61-hUmFm&SzY+Z%_N*znnMf#g;@69ZIm;UC>Dvs!z zcj#}5UG!t=UHY3lz>`KS<%7`KDDQMB*VsQt}vqh(IkUS|SV! z?|GB6LXMM-2bq_EthUi|6+x_)u{@2%Ets#Ck=joFI+!wiK^l&zGy*Hx>dA7#-|bJx zljX|5PyLnckl?>AM^+ji;vD@oe1pggRWxTI{pX5Z&Th-7URdQ4yNXyZBXc|*2%dk&;?irzR_M&-Y>dj)Jd>(2lL%Y z@M|waxQOAWmMw4CtWsc7TjrvTU%B($3tJXkc*W=jI3hFAipJWKvBU?mAeug&LL?Ce2xwudV~3osm0XM=qvcSA|TV&X@7 zekf=(ww3{*gDz8x#JYU1obMLX!B8*_pRbsQhEprKWQ&=$+2tnNoH@}MlP5K}V=n*F z)ru(^wAQTAce%szMO@qY{k(sSM3r7KLiilz$|w7Es6Y-P;hsq&^Khb*qn z>FirGYA4;;8n7pOr`68*AiZpFAwIvw=a0EVRtJ;K{+eksFPr%cTXAX2sz*#HKXKce z_gkaqU;5+<=alNs>V{C*Biq{+ua31{29b08d%_L!2XYQ5*mT6K%@ioI21&-y4=Idv z9+Hv|s`)`}K8TQ?s(AbCws4iTv7xJ%$9DlrfgbpRpwzc@_0E{fg+2z+oUJt>DamE7 zYcr+uwWcg60}zw+zPeObXWoqZ7Wah44xduBE_wDPa zojs|!A-8VIg)TNfIeT(=!CFdpUp0TtRoiA>RJp#so~9{iA%GStutimvLbFsg=)QayQu6v)u?esP8^YHgDf3M>2 z_53|a??s%YGBOD>3^c?^BQ_e@UPyWDQ5`+P3l3+6CtOvZY%Bk-OY)b3Dr(^yI4ai*qW(p_hs0I=Jd>)+bXK6EXgxAerc54%3Yr$a z8}xU&cX^+@%%EsyP0jM^s-Y+Eai_AW>6LxrjqUe#-`(eLXmECJI+qL+>G(fDIC|x$ zVc&WoCxjG-HPUFZg)C{P&;g|yP}b$uNs}vC9T?i~pX49f{y*#`_LBZ2Iecc#nj4d2 zadYgGg9Y*5hguQjh71~L(D-@G>4FfzI;dhC=Lr-vO5EI(QIlNGLa}jVi$NY88LUJU zL^4QG5R{*)HG|WG2n*06wPcgoYOxtil08E{-aMfXgmbW3M)}0)q{8!xGb~{-Q;mhZ zVlt-+K?KnBZ|i59+`&pkf3Q&HJNxakeN_ehL8X$J8~q(FHk+;J?eFi^pVj}_)!}dS zS2+Kw|Mkoum7!U(#O4X~1W;XUK(~CEL^*dkPxHw&DhF%IiS?n(zy&|?Q z>~Q#N5)CbFm5TLfscHH4i?3Lg%PqU&;_b`XYN9N?h{f6QUkl%qFO=RUtw}-(d!E() zhOK8Cem(Rr?4jQfT=pArCeeD1@Rs~znQK>Y6hN<>BhC_M{91oR-y=naUJ_^ihCn#_ zP4W0-pI+2QQY`DNA63>1NL50GLfOX|n*34Rd z#BTlts`%XZ3w8tTH{Hk?9CeQwf;b))C2@#)J~xM4L4Rv169Uklt~*$iY)KT zNH!uu{}n{y8KEZ5 z9F#T^PR89eagsm?Y9ILt{1pFD{THvig7$&A@kZ;H8&Z$*3gEAG5*Jl*00_npQjQfO1iM@}OM!^E&mI#$^@ zCHjo1-Y@R)B~8!hcXP2_Foq0LimeiV6HK>;hU$6vJen*a9>j>#b-!E|_IgPzWrU@C6ajSx1hgv`EYDa3WG& zYGXDWmR)sK!4i|5wvzbR&{;@sw>#Y?X@x%`Pm+Eg2@uCqseo){wxZ&wXbA-4tB#6N zg~M$=dhF{Z{e7o{)dbk-`md$s+#&IGe1pg?BBDc(&j;<($mZx0ip@m#4B{s zX$a}!JeE3%%nGKqXDCZt(2~dr(i&R1szC0LJaU-w@Ltn|MSv=q&%@ZKSjTNRQ!SaC z=DG#der3ya_jN10X0QKjKi*ed=bpYr@mE)QgUg4G{%P`LZxwseIcd%$NBbr0>_FsM zHh1xMf6P}E@FjgWF4n*GEPC8vvDLISBFm=nKRc#P>i~+tke3pWAC?~`9gCNiq6{D4 z+xQ2F8~>2*6Zrj-L#+=z)Ou*iANKG6!|?X+_pz67==b~f@zW2t9A5JK{ri8v2J&f%&H}@`}N_2KT{pHBzhvB?yod zHJ#-GC_N}8(&Vr#OuOE5v@Q8zWLjGPX3ey8wz}Q5{vLl}H;MzXmyaI211s^+#|sNR ztUuaZXgPh0Wp~Tz4K=TRzbdKU$*wu@`g4bG(C_4WAhpw2myLEJKLb8;9t{hWSIANF zKUPYh@hnTlEvUwY;SRhzMr zw2|0u!b%c`?0~Cu3L`EEAqAQ0Z^iisF*YhP3Elvuq2=!eOBM0bq0UQK^9qPnTE)lcG~rr-B53M)u{T(Fh{y(t!m`BjfOxQTsl zMUN3R+{#0RTc<*zP(oZQI=|nkRQoAANYJY5(d9&s+Nh|NJ(?f*MKLt>G>$6g0bP*4 zcsfgB5+gf+(yt(Kj8%+LEJQvO$7}(OD0({)ZxSiyr3=<>+GH&iYLE|nvCE-2FLgOq zv9?v4E?v24ho#!BKW%vedVlis=4$tkJYKIy&ohT?lPt0Z*8Q#rs4%$gz#UF;*jzXA-i{ zKs)%7KsyLttkIJwpF*9SEl%QMU{Vi>foU8!pxgsq^dQ;-tqhAfi98V6@1a5w>eNB4 z7qm-38t=C_Yve{wy9m)PMUlpUEH!BoXvfmTRqY*OXLl%WkOH&|nNZfQoJyUB;{@UE zklXRRlC)4#o5f{n0y!yeY~v+FD2MCP3Xj9ZF17gLPh0h;+|}mKU%b-(Hhr?>#rjig z?y;Mg2?Vpr4yM;j@0P@w1B=+T9#5d+3a9xUxgxC$eN^$ah5%bpX!PsPu4Vt{gB9O& zxE(eS44NOD<)AQ4GYJ{)&{It=SSjRdnky9ZG}k6!PQkYn0FFTQ%ZiNwvb7o~gFHDL z@Q^M__4~-#)JV=1FK`yk1!0O$q^%{%nB5Yt{N`z=u2RQdpwtO@t( zriwXG=qQ3X&r3y8N6~X$EwZtj7=!nmDv-dBK8box;pTRfdC@9hd=eA@Mcf?4vN4^Z z(k2B^CwbNbW(VPYk}n=oP#ls3N~%kl3d=d2ax>E1nLD_-BIUl8Ego3HR`?qqtr+?k z{BM8g1NP^&`ZIo1*ODye%HTKeMaSnygO^n>2le)n%T``YGl{LXJW=Cv>pL*y`dd59 zHSQkKlRN=i>yn=cylAew=;AzzU2w=Po{R9zIkgVl+GDLF#^rNI+%?($9 zW>X+25uGO(ncte#XDpVK`&}-jAtvJ}T@{F%&e`+J>mD6(OuxSe*;_3lyH~$VKPaxc z?w5Pc*`vQt9&30!eW$(5QmhGzli@de8g24m#hX;N#1P|#02^u(CNV;5P_KeQ7c?Ib z7^*WBR8XxJP2<_1p24gb)hYscOgxGHM{j?Y`en`^Y@as92A zfAGo}`cPYXN7^zR=Ym#I)*o2FXpiP2!_`G3@*~oYB7E#{Q5zbPksm+OB9#5bKgNl4 zEvE%}?}A(4KY;KATT14w$^fYqnl@vM&0}L5n|VL7XP6`L&>5wTov;999EaPq1xoGILnfj7&1k4YFn(eM8f7s^r zNj66)9f(;Pr3%R;*C&EbNpgD4cH~!?&1ttIWU0II3TM({cPg^CBP}y4Y$sTkh^cu_ zz7^3>!c?FOpnP}86v_uNCMZ;!K~ztFe98KMyh|Ut=aY(myne^fGwx>h<##uG#5Eg# z(7kTs&Ud#zw{A{m=oya(*g4c|VLjyEGu%H#6;TO~Lp=%9kbolxf*PuD@Mqlf1q@EVrIE^e`Pk;O)}Ey)jrMPQ=2_E}j3z)s^7LPNm^ zV-2}eZNu_J#2febAXoGIqsHC0PPPdw6W||mrb*V~jpI@h&(bn-w90N&WSk<=*|4Pr zO~B&D1OI7xLZJbqz9P@{*aGPm{n3)V2q+>|02- zI3!q($Tjde7^7seMMy;rP#$_f0WD>9N+TJ>1Yb;PMBXN$7$6+~K*27$pg<{{ z&`XbS8$>4Mh}%l!3-v=o7>>sC!mm)1Ax}ESxkG_AV+jF{gl$HsWL`mLEdWX-ZMnI0 zSBX5W#)tT3d9OrnRIEb$xD?|b#~w6JitiZTF!)rE_sV+(2iEB*FvOX{V&S!N{T{5> zK*ty6P@+bigJNhIwTIUr=*$)yIL#VP1I-Y5La^BquHqVD09e(_N$PQ=tD~w$%A+;m zSnr_P>(ORmYyRNA{QOx~csjYYfvBVTBNcjZ?yyZQ{jt!-wVzRfb5UF-LSs#9)H{m?Hv=jYF`ncVI5sY*Xv*Ewxd zcQ|y;7OUmVV?&nNqG{$N#dH4B*()}k(J)sR*uj5U($iPt>1b+hph!BE zGuh{Yo=|<7esRY1L~mbxeSm&1-z6&#oxAbOzaAGXQ`zyE`_Ec)TYWrVi65gs5j5+T zzbE$tjq4`QCgR*sd>V$E1^76`Gn5@8g#=J8>0qRWM@V@H_o&UNwPw^7*ziE}1*$Uq2rT zO}=@~X_LFonYJudz52A?;2D>%yWH73r@vs%OmD<+NOMK)?Ra z=Xl#9`56ah?DAc7fZa;F(MTe1T&MqT2HS8pwrAiQ-^N!=^p(Gy<87UkpTXp_X6#b< zm)3jRx*~~-n{i;q4E=X~)K-b-PgA`>s+ba?_;>DMh46u8jgULo4wRPwk%ZB~zSpSo z!YgKQag*WYUaAq4STviU88@7y5TOsZ(XXBTqp8xPuUnxvBTq-C?Ftqpk z(^gNLwz?pFE0Argt!>K&j?IPC{*(CPu{Y_&G_;d+1w&?6jz+_TGa3quk*Ef&7sm*9 z=DV{Yl)1N%^1vXcS>~s&LA!M%+-_Hsi&gWFdj0nYe#W-_>;MbZOGAFh{vn?!1s*8{}eDfuvx~V1LaTx0znB;*1efx1S!eg=dYE(Td3INBNPYe z5??T_Sy0_JV@W37zhh}3HGBEgX6X@Y_kzBrtBgH5Pf={69R^ zznp1{&vUb-78k0Y_UG5#KGU*fsqAZ+e$kA13oGi&RfJ>;C*P3t47Atv`!%C`HY~i?h)iJO1;;H+i!$(8;_leq$qO9+V{yT16f4oNd)xytFdM|PPj9Ev@E_gqX15&s1F>zKo&&miiJ{1Ox^ zMtq1keGo`9K$foK$}R$pvZkEC3bK5lY9TD$eH0uIkru@g}i$BeO^=4jAt(d zfxy)XPn2uGm{A3jiVp);Lh(`zB5K47G8i54{D_a|=v*{&F=Gh0?=N_PAAz!)inSJqhsbC z)v91cKv)?mws`(Ug#xS!gKL=O2-6CnQW11rqwo=m+3_Msd8m=%t0nRs4WQN#O!D&z z=MmstVEB*h$Ya}hp;tN!ofwh?nmK$frExTIL4PEg>@o6KG>e@o4RKr&eFa(IFN5Sn zNL)3F*>RDIc!!Auu%I*U06Gg^R;Zek%ftO%5h4JH;sbH^RoNXN0F@#_^{Md$uowiW z1CY57Rc$ECK&wH}9l&28JXk_UsZs7dRdyOjl`+&H8la=BGPJ=vhHing$=WJ&H}NvY%otPZ5sfRf zbPOeG`=G=h9u7gE;i>z8Hlg+KQKP1|m)F$xQdtjl%7wKNeQ*$lwa>>#hk~K`Q#bU2uW-_XUKtxwGX5> zvR8%)PT=OqD;F3RCrC7+mKo)`xFuUAI(d^uU;p3Q>p*+myuA=G5I%OkX4t*dUVHE} z+KUQjBkhfkwwKxjs#1%O@GXN!Mw?2_Ci)t9<|6pSDF(J_G-nsM0vTj51)wK^zTjRm z$PoRCczCEN<0DPrUm1=ID(8(+BIBbUe()HjnUY5yNvB4}B0+GEzh|6y?=(7UoFm;0 ze>?|{+EPb|CPI6;d@Q#H0(N3+NM?p07I=!Kpw%FASc@TN_On~)Yh@okN^PNB*vCE? z*T@oEtnZ_iKK6l;DLb~My7TB!YU=;8y*#nkXm9*)X>X{S(s)N&G_Jh`)LrGR{qRvD z_}JDK(2>Re+qR;Ce;;k*618=BoX5A79pQ~N2oD~aKFS2(*Tn`;qCPd{6;{DFHnJRZ z=!Y@}yx>f%7*Gcg#e!fKBuG<;jj3n20)(n4s>FGK2SNZ98cu2C1)a#jg~bok1CWrx zm~4RBLqsg;j{-EpDT6c1snQs4CcGgq>7e{oa3}erF*i`^9SQ_UlulXV-QIjR!uRT+W(gMa8}=Y;d&p$6*=!XRVwKxwt;9_IiYQvGHjhnyN&lZk zifHla3;Y3xm3hQ1;AlLO^*N_vx4KQQ>;K;GLtFT~*CG z*B`RG~6whaY`|$;2D!Sajn9&Cm z3kOE^0^;lum8+bXNjaQ{11Bvn0e3=9OS$rU=*m4;Ub$ytPRmH~cil^;uN)(@C@#qZ zJrC92dCh+0L<52Yo=gvMgpG_uJu7qr?oad*U`$1~2}3N0S}8UWHn2hgJuZh_>F^w@ zMC9zt6uwB6FsX2?+pd2g#i-&iu?ebB;r1hPX!!ok6Yl@F-5eP+_{Ve5NA3=v4@>Ja z8LHV0-yKyK!HMk1C-02A_l@W~J#TEd?}qk3-aC*0+8b(SqVEdtyFz_864J-^9j52F zu6KwlzoO6CE#5lj=HJzSDz1D;pYy=bx$q$N~#B-mvP?Kd3QuvvWZ==}%oXFnNjg7lx~zP{nuVey~;8z=M% zB7%Vxk8Q^=6(+U=(XXJwXEX&7KLC{#s460~-#o_t3uk zJ`i7|;h<*);&~hLbI|at@Luv~rZB3sfXpWIAk{AiyCG?wa(Yn1LVi$B>OWj6?ipIo z9+5ns{D67%YuKJa>8YVf#8)H_k;4x9Ql{l%fmR7T9zrpbYOc`pG+f!DS)o0%j6EyZ z9Ek{q?18`p3`BM}BqXKExe+>6v<2ZIB@5FKC*ZhTh-aUZR$iAP@<#$k!R@75|L&n# zh*yT;Ti7kV>#yYk@YvT;ssNlHkuE54zVGGFT%d}h5ur~Yy%jBV^A@^cJQU4bQ5|WX z0a1ZDK@No637Q$=ujmLF1zg57DuC==-lQaQ^+JpWquen4{jJ;e+o)x;uiwfxT(2h& zk8R;w`UhKYL<2RPTz@@+GoIo)A?Y<{lMA$@XYwUL(c#(`Mq{X=_jsyU(wLEDn)u*d z;Eo3HXt@~|JcV?$7s>=GJoVI#!~aK#rGLyX;>7yob$&$YnuZl{L_#lj( za5rm2V2vNLV`&^iXL{Hs^%5!egf)=4IZWrxx|4Sg(guokX$%*@-UfxA=7I<+In^OW zmrm%@nJ4Mf$$EosQ+a=*{bL)Cv@^8=U7)0oqQe;m>(T-_u?yvaGTi%E*+;ri!Vq1? z`@kLih_@UwIG54ckzOF-YorfU^I#EV8ga_R+yGubf*f*2-L_Ab$*NHy5SI2)9vhsZ z;C)mC^zt7he5%v{s6gtgyED?M08A|y*#Hr2o)AC;tjh4q;PC;l!R$BzK!w6VAs+ESWr}<& zzgb3VV{GV3{;e`MlcD`L-rN19eBHDZaHaOPIk@w9% z(odryV*gr*bj2&pCjBbfm6u0-%I7?@ktbkap@d~Gf`=LrF*t&{(>YWOFNzKq+2IYD zVr5N|vdQ6Gs>0mt%oxwmY{+50nPX)A;L%2;eDWt51+d*F(af7p);M>P(h5l1wGx5w zZq)S}SQutU!VB^EVG7hmz^=Y|VOV#D7wVgbk4$o=*iL;*$~kEgGuZ+zX=^ad#7Q`; zZ(%z}4j;RN4uk9PSGGSZ;nRu19&UrjqljwBynrlpR+L!x@>CwLpD^7_#wcv$rFuWI z6sFq!!|L>C4Hd-C<&sp3dBj$ahXQz5O&lP9R}!^+$}* zV?2;ynZAf0BW23C+Av&D)A(HdAg(N%_5-DJ&n*>(<~(-mW3X2|f=B)b`4M=z1uvlU zS}BLX56b8S0pW^E1MsCxPdD?hXz#t}U-0t>u8&3^^O$|#@pXExxqI98jawA6>kF<{ z@1xRhoA12)!1)*4J1x#0RWhzST(Yv|f^FOH+M;y$U-p@mM@Mvhs-M&c&Nk{NK`g`P zOEG$3`y;ZIY$xM+=YDwfv9h5QEuqFhva~>Y9K%bPyK%YaiXeyZKIZ?a~q%BAJb9qtii(@i|&P+BB zf=)&-8LBn_gb3lhnnL-}{y;3z(8Ogc@KEem#ZnCvk&1}?5tSCUIK}5ep+|Oc0tv`a zv;qkeD##F~?Sp_TsN2LBDW7s^);5(_M&b-lwWdHfA|&?N5xPQm;+?WF_8LNrq;d$RK@I6ql2;|7#+%;q|Z~13P~sm52th_R^n$p6e(UCgIxQtSs_vQtEpsEI?{HVC1(VrLml~vWK#+dr_9^n}o zxd5d$eOiAC8%b21qBE%4gII48SG+UeyYc;@9IYf!gNH`@gJ-zZHA1UG!T{Khn+pVC zpe`X{sR)jI)N`kRE97!C zQc@v>!XcWzOfm?0V+WB%U(*5h&-3joMAqlbjabZ{5KL34Bo8? zEWG(0RXh*F(Sg}isD+HjJ`HA-E1 zvK;X5RKQ)NEPfz@PW|LYz92welFUS$o$-vy7<7U?!@WhFEq{)J6ahzK?8}S}aCKaV zQQD+BTa58^oLDWaX5-QJYB)=oCwR6!o>@wxTLxicAP2(dI8aGNxbS?0dOY>W?Ugw} z>QLQ@6NEq00?$YeRU*lkg2G0LGB#pv7|Vn&FvOK2tnx6Xa)DDs!i8xCC#9%xYSMg# z3>M=LcGdBZjz28FET0B+J}z9rquIEYq`D{~1r9^X;)V+wvdl2EXaX1+vG7(C_=9*( zO-6)PF<42DiPoY>v(kL^8K{%>p78eG*?h0nUV2}uYc2_b|8k_#lfbGhrjZxSGZ5NSvO z(L#bW6vQ$B*8dowfGsJ8Pf&o!35luWkDK3!JwP1!jDi{q|uroCv&}nP=91!E>Q) zNDA(l?V(}=%y0%tz=~u!EC(9e?=%BPoOz5eb{y_&$?IC(ey<_sn>dQ|oTQ^MwV1 z55kQu=DbS)9kLQI4`$MU$FjbgC(IwLH}b7RB_)T<7R;Nq_77c|x67J3?|FMTqp{?TJ??u-OilWBtqmEIF|osSGH z|EE=mr*V8PKAiPLT=tjtcO|}$88^mDy#2lf8tNtH_V2d;m-fA#_`Z!~s>DA>q{o_Q z&;|s|WOU-L4pS3Ur4&3ZOEs$gk>MEP<~X10NRx-UrapRFFbdDc>HoV~xRRKrpKb&K z%Jla*;Z|O}jFF=e*0ZcB&pK8fbb~LHZeVmlH+4)J;zp7b_6V{zzn=k?~-;&)el!J0!%I-UU|7jD*CF zr`(tto!U|Iqms+s2Jb%a&1rsLhVPV))g9XFcll2SmIn3(vx8m1zR>bePdFpIID9JN zjx3G55V;<$h#rq6$L7ZN#Lkx{m)4fHm7XulD_dFCTkb7iTz+A?fBM1ceKW!{PR#i8 z%z~MFXMR{Qzv5_RM&-83%doZ&^96xDCIue6DA=Z{O}++uXi+UDK*f8(Y1r zHnm`c_9kmHxVi=YF4w{zUYq5yUPAC&KKQ^4KwF7i4`%1Dur@-@L-}pcP5BMz3G`s> zY%{)|0SK*jY>m~5m8rI%^coxuUd&9b#R>xpaTb37TU}tyhwmH@Vk=O)5upkAYf)zr z%CCio`eu78ikd##mNM%hY<&spmE9NXUZj${u>M~QJa^SwY`3Eo7H+cl!9bf9+O2Rb zylv?^lx)K~+NS(Aw9={J#atyHtZzZfHUQI+gDnmO1<6K|AijUR;Ci zo7AxVKZJJxA$aa9wP$$U<|FSpuriljb!coP^=C za7QC0=p3GgGqz%V_J9N>Bw&7OZ&sXKhN}rK_ zBv9J<@cz)vf ziRUMtpLl-a`HANzo}YLD;suBoAYOoY0pbOS7a(4Mcmd)Ch!-SYka$7j1&J3VUXXY} z;suEpBwmnsA>xII7b0GWcp>72h!-MWhUYIyx;)ID4CQg_*Vd8{|6DCfC zI1$+xG2+FD7b9Mb zcroI|h!-PX%)wLgUdekU@73qjQ}SQQetO8zVPujD`GfID`O|4RNV`LA)_$DHFxW6p7et51*gKh-TyTl2b;7uKB? r*3W+&`;C+07ClD7NGtg|F8f5H!(3~86Y5F{~s0SKbSx7ABc;Hiv4KWKOFA| z1i(;0U~)?IOg~!J4;TJ{zFC=cu#t^{JrEGc4+X~fv6g!he=v+(oe6+|Krw$rsQ(28 zXqc(Jnaz*(qXYl_@iS3sqAxQuaQcY_Tl{~1KtPCQ)*hxm+9nW?%smiL1SZu?QG~gP zfiVz};_Qzf%MaLq!K|{)e?%Z4C9og<-_7H@-~JSD z;ml7TXj+FZ?f)#YkNdijzOlak4yYkC1fss7KG=Ykz!b<4BM=Z=IWQa$(0|uWEsV4K z`X>4YrUsn@0s;tOgqZ0J7!22e4?s)mgXFL6`5_=7{)zvZg8YI7T9RZ~1PZ}QNTy(5 z00DwEfL{K&2Oxo08dMN5)GSH+K*R_N1}~gh9kVdRVj(AnECji}gG!JDvmQ#dR62_; z28`R!zr>GB&HX-eU_#2qdYKgxT}?y%Wx$)3d8UsB>5#ISmT5Yv-9ANQ5q!bJ$X05Q&V-WBXr%h%L(^Hf}DXuSYAAwZ2iR0ABilT&V9spwLQj0E-lgH zE?t}Na6d-F;z*hxOECeB66Th?_a3|V4mQZ{C9|$=ROiZm$jp0S)O&2#HT&N#y-DN) zC@bf&<67tgtRfoE+X|H_{<0tQBe)B(iNt?X5C=p7^5VX(qtGd?t(&}=IEn)`qWegD9}=f-SeS$J6Ff<7e#JIZp94!XtybW9?=1upFx zGB6aUm+sN=mnwd>vK(7Z);A~2bpASIcHyPQf+CCj6d%^a|B?!LUFv2?Y;?W`u^v*^w7-fR>!zBqgzzQdq|dv&V>Ki4AsyevyiH`{;f4nXhfZ z9N7B))|JjA19)9~ZNKZ{#~!b9#CnT`+k=ohoFeZs1(`@5Y)_^}hx*~t!17o-k^&=O z-`Hy~!H7dng2f#llxL5P-?A}@`@PTjp%aO3TkrdgAk~hc4V&yS$sTHQ#!Q+&Ws6m2 zvP!e~iQVJO|Iz^HEEQW*3UIY!@#cE7sK_5?Ys;6EBde4oOr|C=Tx(hOR`llBfE*enVzK#>^b2(n7z#AJ06+pGUq4 z60d<@A7OpoJ4%_4H*7Z2Vzcuqba%Ma#^BJI-VKw>ZoTe-W1ub1K)H9y;?kAAM@rXb zZk+y_R!{SLE1dCV{ajRqA1xLV8#4I--l1nd1TTM)`Q2 z3SJ6dh(?{nriUFAK~^*Rs%BTR2*=Zn$tS-r7ll7w!tqMmn+Hus_i1?*dWc)3R$IVNH1tuEwg{F~y^|g@!v&)F-Yg3cf z;*c`^Df3oFX9asY$r8}Cd3c;#i4x_D=)KCaFnS-@d=V6Ki2a?=k|RsC_Bt*kImi$((qu~+)~BLFnTU~Zj4Z-!ZH%p zB*@gC6X*g@-uRg>z^z?t$rnHXdhA5n3R>#luBT)ISgK=fe@2pJ>U+iFwZ$MPb|>At z=ZauVCF;BCn#4GDA|fKav473?56MNV2N#_xKoodD1yJ-hW*^~(Jlbb7m{cGIcB z4^B#xKt9#%*Q@@1Ex8^*OXfGot;5JeId%e;-3>>dGT$TwD1>~Mkd4fD4|=DU-;7Y} zh7ptu?@cMy^}J=)Vy)PGUcB{qtZX*8xxYkc)n<^l9a(EE(9-4h?uh*L0;F<&u57vs zza}e9uy4A<&7Q5Yw~Ow5GCZMAL(rf<9`GpaF`~rDb0mChbboXou=GS zZ)@Fcxuw>nAH{yCxP3msa(~~1_+x2wN2g9%v{WvqE@flY5SO)AYO1N;8#g)2-m5laX$wvlo8b`qSpRta(mvX zm8U&akYB4NC=ZnR{LECMV-1tnf1G_}!k>}zEI_5Q}k+kVbC z8_p5E#VVH1t-BdVd~TA1-gwTi&d65Z7MvApiIBz39?pEhqSh1FE{?NTf=&hK4G9@WG>JSqY|95*{)U*AC@ zK{=d<$`~Qm_mcbo?bEpcqs2FJMQ2Edgbo!WFni=2#zlp40U9CMhKv&KJL zgm*j1MErI_#&pU& zpjrbWmTR`Y-x0)KRWN5tu}1!tcxD$1x}(hOgn>G1+6_d530KiI1NZwkzVv;tjQ*nA zDVVC??GX4zY`jyfb>~imUUtj-lAGR^&+k_k3Cg_-ian4=5DRSIF8MW0F2~}gW<_^z zb-&9HT6;9@Ki2zJ=+&K~vHsdrF{g~oZ4KenvE!+eNPv_%ks-(gAS!>xat$o5X-mn{ z`BETsHsJlXFEz0J;wlhfJwo&R_`wc1T041ERl==6?W8v8&0*R-*}duAcxY9X<`S$L zg!0x*#p|I;*TSkMoGW11_22mm5jf>k%Y^#xhj)BsiRa>~<}PUJw%-dPJNmz;!rNzp~ zZ2OGlcFu{(3W}t}*1zQ`mAgjNnasWY-Cjaewt`xJcX<68Z&6nwv-o57s}+#_SL%j) zJndH~JyIG~_1W((z%1|JSS^Eb=dV`yVl`-B?r;AD?fUL6+^>7=!b?dbxwPGufCot- zL|Lp~2scmp_KGXBHlek6AC69L^Xcadn{3ohiHP>~d2V3ANlcBl%*OL02hn|Rmm4c~ zt39~J1w&|YxG1ba7!O|#a7}$%{V7EpE1Lc5d2?AIB}6HdZpQD9`E)EQg2N&u19RY` z%vkCgiH=T346- zQJ%c^3U#oLe-I;25c6eGwM9l$6GIP&KrP8PgjDbPV3%a%Y&uVx5N8CqPc88Y@S+wB zK2K8SGXI1pTdn3HHzapNUkyV-zr}&>rL!dz636WQ244unj_y+fu z6ygu@`-1vSp0vz$Q;5Gjj$Km#Z9{PG?ikaJr1Yzwk&HbOTt+W7BoOpRlf^^fv1OIZ za)}`kB^3@zeT77GREy^|bGayf6DVEO0nh;1s2L}pX)(elALt%CB@2MJ?u zYAkh87*AGW*cDMR(Ba`YT4I8Lxni=ajl)94>Y@5aDPzdmrazmrq;|Q+E1~!A24tut zs;n|b$u_yPC$2zyA)C4FQX=FsA+M>T3|%dUpSa!{7BA_b^x-8VMz)2ujeGC?YZUj> zl97x2 z&85tzDY_CkICVX^;_U1?L#n+N`E2Y4iV|!*Dr%yUe6vh6D$SNzkRKxi&bjdFkkv^UV_8%LnP(co$` z6XLYMX$=T;LkLo}){;p}LNLSHH3fAQWSB8fx{{{zc|){S$|cBD1NPY}(yJG+a~pD! zUWupf6fr&pZbfZ*&5#Fo?@USbn1EVdk1?j<^^fCYB)4&O^b|iniT_2w&vU7EqL#RL z7tH&n>+1p1UAJrjE!~x92BJO2CAa3Uxe{m;5t;t}+vrOJ79()aW}Nq_=%0^<(g!Ph zu#5$9##;^~l%gR8UUSb>)J%P%(Zl`Qg9&1BSKK`6M<-0WWXTuCyug@y$4gd(x^7LT zF#+y;?A=z-%;4ywAL|5+WSSeEJj)s(& zqByXz-u#n!6o&h8t@>%a5iPcPh24+Mfzb9i=U?(%Aa&~_b@{ zLw6NQ;fEEcBuMF7q5BDE!c0+3a%5<02t{8HO7>r}j&k5_t+ni|PF5Vwtb;ETShPU) zp%mFbtqUp*48Cxn+33NO1fE@%Kw)b%X{h+M?@Y0LyHmR02$04xAeV6WCnB+4F$u-6 zxBx}vRDBgU#O6|pORhpcw5Gxt9Z!0!_G9Wgf7PMy1D(>}Hoz{>O_fPEQ_W?UN9nnv z3hp}E$(^axlN_ZCquxsmb>PSC^icPku}*c?>^s2RVYYXePV&mE7)Jl}n^7T+waX{Q zu6)5>z{mBQ{e6)|UxKa@*MiMoHT5GR6p;)@&VQXqnAvjol@f@H$c^~5W-1}tN(c^0T5j#1ib4}Nao7ir4cU?+ArjvV-jB}{JL$mVc&Y`zL zE6ZTYk|DD2j&PQte$w8&ck zMTAvh)4f77uqndPBhb7FlT?!2T?~JS4bX~jS93?o!^if{-Uruul!DZM7kNb)b;2=W zyAZ{%QN`*6pK{hP7>4O9PlOV{X9AbF%!W+n90B=f-QC@>;VV20*%}%Yh^l{D> z7AS3J^@31qz?>~@taRy+(pddnZV6hO7*z>h;?cLhCYzrC_-$D_Pm&R^M%m7z3*5c| zagLkfa+glZ{D;V(F#5XeH9bg;hsjBXKyZ#VA-(CkK2Wjs{(0!-J;(WeQ+(U~Jw|+{ zX7!KPAGWuVI{a-iJj7(xd6&VNy0*Pz_7ljpe=0ZNFaK1E>JstyLpJXF+E*S^M%{kl{OW#RIh#P316`{h9+sJGS+m4R5v6V2f z!W7#Fngn2eyb3_v!cqb0xbK&suymc~|1_VfK3_NT-rs6`(*Aka`F!-y<`RFfe*zHM zC5+TgDB)Lpu|I|J$lNvcoq0?#ans~XqFG``lGw&2f<+ z;M&s$97~n+7@chqDve528fiA|iV1E+GEj{$P>1~>1T2Xyp)ihX4iPr`w zCj?}H0+}VRlQy<{=zr55sv-|?bg>xmVUk=~ws)HWPekjNW}j(~L?=5IdU4`KnMidZ z#SRHl&VXc+jz-jD)TDZ16wNrH{iY)o#{4W=O7u?{N4$?;o9h}^Y3BL)uduKxTNd1+ zb80wbd2B8=I+|ws%XLc!tyTfFo#97hji4+&PWp06MGGo54X~uHI{YdKp_r5nj4}<@ zH@Tzw61cWj_Jf69)3LS6i`bo3tcIqzxScL;vDBuEYJ`}zLvfv9#P$y88Q7W4_DFu= zRp87OPm`v@7Y*Y=i3QUIff5B)8Q>`oTci%c_*+B(RM<9Ii!Pvzj9PF*6gKxnMm$_- zTa=0Zd!K@*GhJo+9@r2y{OZ@&@;i(htZlLRY!EPgTJkJEJjh z&z)H}7(}xTJowuCXp%iH=6&(en7Pq^qOcW993z>SG#M~&r0iu=5+HnJBCuvSS!fx> zMVL;hn#^jR^&d6T`>Bb*SQ7qF+715oIRA?wlT1-Y69l4}k68Tx`P3aI|fuQW_$ z5wBt-N13b|4wp`)hEqw9Qz4o>e=f@R0%!?k5Sb(?exWR4X@Ie3Je-*+zU^5Hw14VXDe6)KZh0IN?SSFsP7cdy zfG|ep3g&)ykF}m1Q)uM2K<5n`l~|{US#5o3(R`1m>bm6yxTc~*F%y#_BYYh`p01of zmpdBOpVCtBSJ_pCF3?MTm_b%zl0Xc&JV}>s9^8%NKC;;UD2F`WvXCm1f1!yv=C^+; zno9$Y`V(_x3aNetAp^*jEI`h+aiZ}d9gz1Fcs(2?-|ef8ogLpT)y#6eX_t@Sv18ug z%udqYvuto>$=8%+^;lO{RvydPJ5~TW(p)?iVLI;T}1E-ZOZJ|MyFSvZMki|;U}ANC}IMPEp6m19kdod+EI6_o_|4*@;P z=y#Jf+p0y3Rd7&S8|{a;DJgX}ZMSdC_+K9lQO{TZ2oBeS158Kebl2SPD%jELw0b;=vyui(l#gQ<#R6s#X~Tga#kv$&mK2c?rvl3m#u5B0 z;rk`QisV$NChJ&ujV!c`S+K`eUQepk`}Eu9n2Z#9S?GzgSsIsw!REK^BFm83Hs<`! za9N(5KK>qC@ewlLe7n|e4qY@c+1>048G**OD#W@0k81g2Cn^gt0nlq?(kbho!pids zF3JRP{1AgUe18vF1lGN-Wgb-Tc~fc#l&1b#G_|rYyoJiDju7}lo%#s;o#vD%J}qhh zDOQ*?MpdsV2%)4bpGv3W`T2Om)eyyBPkpX9Kc`+&ZbzqTI2Wx3;c^{89^3O8Y)?m5 zSCDLY6vvlEi{3b3`LDWI$oVn??>*F=eT;AD86JL-wlA$taiIxG2e$9h_(T)l$CE@j zf8kQ)ZkgC-TML;n{;0k(FkoOI2uy#!T*>prf zj=Fa9F`8*WZd4wBE3o|DZCRo25Qb$$u|4yqABtQDgzwT<0x7Kk{AteD8-wU2_8ii> zSEluo#j`zEjQ%-rB2XG8rbU_0_1rE%CAaDNHTWLI0C&3V)Nn z%nDCzmb!x(6BEjW0osV7=uwpsp(xdgQG{$HocC3(bvs=0Z^A{&$Zh!_Ofd8-ke%14 zQMSj{GVZrqcgAQ;*Sz4gj|!v1g}CM0meB+vCq4rd1tys+HUDj@Jw8s4*-P~cUc<~ht#x4u+k6MOYNHoU-nEi?I;O2lVXKKu@ zCBTe?q?9t!&(m#^k$B>`hK%EnHHDkT$v)B^QaD zBd1E~Rf+X`K<8R`Ie3(glD6t0lyT4Ubn38JCi=tJ^v0vy4N)}-YgLv})Q+hw*|d_~ zb7Gm1ZU~_&tp@w;E3KwBS>9P9-3C78jNnJUwGDDzJeKGl66#S4V#2;?%1-nA$Up}u zNZ)aSSD6D>g#FZK6Quw`9RJKDO5?GuYy&bjNfQ@b5lO1{crPOZ0LVg7Z^sneWTFr{ zh97eU`tIj+-RfVqi;bWqySx_tZX*HIs@7M?@SQ<|&kERGz0WaO_(X$mSqJrBC_Jqo zCr`sh_>q9UsB8?Dhl1Y_gb-e^AvuSB`6$anfhsaE@zZof)r7$+dmmGwSK!iA*krnu zf6IoIkv$?ZF-GWh@9(YZ-q%>8Fur~KdP!Zcu+&_qeNO|T*m!UH3Uog3TR-ngFYCTm zKGi-}HrtO@ODCUbK0oL@kAO{QR*bA*THSdXj!Y6*^@NQ9gW;8hW-_$_;RVp3Vvka~ z2ozG7f>~_7sYymCgQk=G^G)M(OpRYl!~>fCr;XVZA6fn5uL3jsKsE)4Y=vUN77mZb*9VX_mm~Jx zr?NPKVW$s;|b!uazlLgBtD8 zlpqN>GqfUL4t+{4eVWSP#TylA8woh<5r1I=7Hrl$ZOaHk!9SQ}szNl2gcI*Xf87g@ zJi%;HR4f7umEP*wZAsh&Sk-lxu3Erdx412qN8llcPrJ%p6I0@4%|R2M1G!IAmJa$5ty#AKEENSz zdS-%-8OSF->^en~b%L%~W=&H*QAK~Pm7T7JuM^{g zoVV-O0o*sq=f9iQsY%6-ux$<4e{U4dkuI>AspoI;=7VYWObbQ1NYgOL3KAw*@Q*;( zRMO+RwD+u8&IC}^iKj^5@l6xM5SWjcs87Jb1G3)m9s^Z-%D!R#QGZwzU!uAGY*w>= z?ogwhiTIdI9g}Q=usi{!Xt2y?7G3d)Y59v|NgwDZz=HVw0j^|tJgB!V!qzA~Jd+;p z^=r!Os-dqqW?eSnm3nIk{Br0-Y5e=~K<9{SRf`u{xoz?x+l)Oo6+p?p0NRZGHfk%? zHWPD7`A?G;@~B?|>%rNe2loAO=C=DK%R5mn_FF25-WJP|P(BSEu%nVpPpz%c7E+r= zi=&pFJjKS@Uc=pA!wKW*cZT~RkM8_s+a z^9z=RbLu(vOIxe<=L zSTlc8OnpdOd+eu>Hmz>R@}Ge}Fd`|a91?722;U+2%46kE$lcBlCisL!q-5t{u^4$s zc?CV2?JWEK3d4@9!R!32`-Jk7?yF%~2#bCN`jIq8+3j;wtqX7&cU@jf8hY*W7yIMfYA z$dAG?-^qh80ODo-A)*)yK&&aM8Zb&SdXI6O{g@#nflF3&s6|A925P07+O*{%%7mmP zBrZ&dR=Qj5_e-5ufzLtQWqtFy{Givr$O<5mc#z24K>y@2rsM20aF+FfWs{bW2{%T# zk6#`CnZ4qUy(8RzJ-cG(Ot>q(jTf9$c2O=8=Pj2~R(-685 z+swB8Dns7{j;m$b_7tw~H+kmVNK3*<1=&9=dGJ-wV^FYcvLWxX455)|9NXzuXa}Bc zu9q(l;f=4eT0?SIymP-o`$DjJ9r3ckK+1iZ>=Lb&Hz3zR31B)H$$W^-y^^dVZv zOdsn1P^>O2ej$hTJf`}_j2%jdlQ(l8c*C>Yc*{cHQxWVCBqGn0Nm4;pa^PH258ZRF zh6LGDm319lsMlLKl-Ny@J;(W?x*G@|!sfx|UG`dA9De=7R|Ywzuchf;{C09|V`?*y z>DR4rSKI2!cl`QyGD*+QYyY_?{lWh_9$lxJYOUz^LHu2cLY?H)%~O9zlby_rVKJ6b zCCSI~!Jrm-lvG~AZ?K9!jKyXTjC^`-4C z{`zFpLtD-ZN*(HvTTtnI0QP}DHD&m~JUT^AFB4l#`n3p4GPg8M@H#~(c?rPXm=p$#QkDyEC8`tR5ZS3W`kEsCb-AZ&LKi507377`=?c(iv(c(@{ z*={h>GJOK7LzscCYkwPmplW*l%U1j_RV}Z*PbB*nY>&&A8TMfeQV-?IeFIKLVq@uk z1=ttQO=8iR42ehD*PG1srf4GjX_g%kaWiNjR$L$5hi-IKlv{+`-1dIoY|MoId4pa= z0;+EDcjQHPMDf+UpGy*i_yd6ZLGRY%k;I zbq&MKjpLZ8Mv>k-r8++diJR@%yf6gcf-hJ*iUU#$cYGhLgEoWcTFKg=tp3LVs-*o1 z%H$(n&R@}m2Y6HFyiL@?^p_J1U^mZC{zEOEca7>pI@6R2nJA$8aEZpD`rX|qroXNC ziXD+5Z>gFRmrw@Z5HgLGpo~CXpy(*mZoQ|tk|Tq^29KX8uEm8b2&J=+>8TCT-4(*y zx5B=_*{;6|`jH&&g@V_@L=A5M^LUBx&}}`| zmV0XR)=oyhNchChLmT#AeK=>?7#^D!rQ0RPG3L`Z*sUqtJ;KtD_7(H$X45c7zyg(- zM)np9A2QcSD3}*AU}xU%aP9m`t;WshdOglv%IX|)&t(DB@fon}wp=w^5_Qq$HC9I))GD^pup**?oL*`__Bjx7+O~0h8e^>5hwml`VauX!)c!zqNrbn5*JSH`}_Yszdo8tkZ$2 z^CyF$_lVKoUXtY=OA;$s^nl>VX*fj2!#56?f;@HyQrjC%TR4f~uP2%t3Wm)XxxxDn zpqk#^kL@zqM>D)HuDzu!6BfE1V+hTz+w>*Z$2UY!2vyZ)bFxdMV*jljXgLis+nuP= zMC=yaY(6ViJ)svxb@KcRS7OzOFn?e}0CYP4TQCNY>Xh+V@06U_^mc47I)0JLRsV%! zd1Py@08TTPq}Rii)Qe<2+upCm*hX>EPR;_*?j1R_@iZ%aA}&bCO_>LU3Fy(#LJ*-s zm^|Y|aU!xbw;qOB_+qFr1>wDbkhhlJ4?1Be6d*V=nhu7d6GSnlvK7M^2%}RZp(|C- zQfzB6RPr_ZOF|0^8r=`1sM)sL9rVzu)oQO=|B~ga*UDV+Ss!2d=l*yGr$eqONyt*g zzghGdm&*6OoC{0;hvwe>_0cA^#f3btn<7cW`Dy%oodMQ)ujlZhfZ5Eo!uOLnJcBqhg1+SwMOQJ}eJr#0+r zpWhcinS&0^2gk zpZ{nT;7hw&*ZgD^;R{%w>DF&v(+SYGBGP#mKT_X`ALQKC=c)lfBgfADUMO`Ui3Ou; zOQ>cAnIU7j1g)hYF+g<3L3D`TA%}+}>nZQO8y-3vt!ra2S^JE_K+d`<6#87-f_e&~5X{OUId-F~QzotWr^E%MVlxyRm_06>-uPs@DrLoq- zMaljl!Yg~++OfqC-fuA4>-{Qs-^Qx((U$AjdmVeXiU4P8PbuH7jS-Spa_cuGkcN=- zZ)I~)TcXz&6B+0r;<@5z+vn+rSle&8J0cGSKM+v9`(ygZ@Pu;4ySW0Q@0p@4QB;#v z%Hn_ILIsYkxTdURF+}Wc#!X-;jeHlON>6ha5_#L38nQ2Ej};}dJI;C_rCt=#Y#E%t zvU_R#D0;J(rAx}o>jn|n0K#zL){t}}tNZ6Wej z1*f*}ncM222pI}eO=i?yy7}97OZ|a2j?|O}0fO1TZ+3Ld%ZTl*Y}2$SKJF=MQfPwi zPx@v_a3ubF+(_=r^EpOna*^~|#d-bShm6*g96e@BUV-HGsLTS$;3ENN~8BSo;0T~Ok`mp1uB1D_E02&5KoEBY(*3Y>NvXQ^O z@{t%|P!wl_Bg*vXwC=bNh=-4=fAq_KA1W!n4heWgS%WiUKYdml9{U_}>v7t7OxO)A z|0#~r)8lmXIC$`1IG&wTtQyx$?TbS5UG+L?-DDr0 zfwIeACMiFmfc=immSOvHeZU{P+Aiq4aQomXeiXWLxg8}^tBYb!3i~bx6ZLxVI_+hQMr5)fJ9na*a!znXVCPf0FDNud!nAE zN0?K5E`Cs|hv$>zeVcaRxp`fE11XX81-YIIWwp+B?nfX~J`Eaei`htSFx3EL!x_4d zHfEtC;FXqYtkI9@jZ`&8Mv)~TYB@Y5`bW*$bPiTNRmzgte^Ex9R0HTAa1N+X-pMN} zjyHJ$H5D%58`kI{8hzAAB4um;DHIet8Jx^r1_#!=Z(r8HRjRzW1V5CWMy6QNG-fyN zybWURT_P;@>;^Y6I`@+>%cY#PS7?bXu`574o=WGMQLaK zOH%U9gqmDe;l*SDF~F>wEH3(b3P>%3tI_q1BR6o@?Cl&wzBrBV$L0+A&Y@qbiEUAg zL)TexTe)+tA*gZGe_Zr>$E?asU=5L2fafhKM*7Uo{fJb~+4B|N} zyeC|4G`Fnyk|u=UCMZPiCY7Rm7)Sl@;$L^?I{?jZz4u%0@sj_Fn0`La=ixzEr&r^4 z^z;3@ZI4|C;jc@(dR0KUgN6FNIZgW|;>h@4is2QAi=!Gf3dC!mehN(W6`C~@n$h9$ zAYGyvGEUJ*Dj}W_;K{vNms;Y}q4$D<COQ*RYN#L#iH^g| zux~?8N#m-^Ji3M2ilhyo&YM4d_L@Kq-}|wBTf1&s!MYk$OEt)eS4<82poS?e9Mmw+>;jV(>`Y7z_7 z4ctYq2HC+!;Wq z9*(RzQT0b?aFOmX!=GSRzu~vaYMMwTxdCHOMC*rmni$){lU&ELQC{rQ<(H)zO4=HFbu; zEn@OTcpXi1#h2!gah&uX^{z?~N+qio_VH0Ts%x$hgPt&wc@3wDN$i*Lnb~hj^ZWVF zVoPGz6ojRTY>Y|MV5kz+No2{yTp{^I26B~!Y!yl=0Eo-|j+_f5P4MKh+X`aOv zpc+L@A!v5th`J0=Y)OM(1DS4Cju$+)oDQ@YN2ZQJ65M{g+^EYZ8R~KcfQeKyMMj23 zd<%AwG=ys2d>I7I4)sf5CV0g4^8qoWb^T_R=;(#O!=M(^zd7@Ci&9B6P3Ri?Z_)#Q zs!=6f6xMIMeJqm`Kqh_Q40>|glacrSD#IVTHW84M&{!tngu(|#n#l598G1&izOs(mP`di_aa|MmI`3xPZsMvj1qP)NX(bF<)7}X8tn3F?g&E02cQ^!@ zZqA@-DaM(HS?#UftR?VRHv{%?wC@Y)pm@3#)|2LjP}}tR{3I0*J#q{HvLG_(!Mm3w zy-Nov8LKFslZ;+{C}yz69J2K1%U0%FB9K<7#@LV$JidGqUq}7SKqH>4bs)pZ@+qtF z=*Q5HH){-EgxIp)Te;_7x@Py(#7i5~6f2Zw&nf)gGsga_ch*?jy<%g=f@~eEJR9&N ztd`^u_QkbIm7=*BXpg?j8=2b>09Ltyo73%?=$C*sR?!#nTYHughVx6RLiXROa2yMM6Z^tQJ;mgK5KPkYjG zJy2%I8q~c1F6_^^^~WAp+%U6p_#fK0_!R$2(Ix4-ZBOdy7VrlCQf}cJ=G0HgP+5@6 zR&H3n8|OHC7%cpkxDX1j-kxWA>`;BzX?*t(x8%Dr0On0Zl_4m|l-+#1vcflyh(}C0 zn>yD0R`N#pm2BnLeO%4^*4Z3hb{w20k?7o|y&{(flCE992dLIC%%uV`Dqn8IprLUo zIOyk-ww>Ci(&A{(Qzn;C6c`xTeEa)om;;Uovkea;TzHdm zBNJS7)|_?mMAIzLan5F1`-WwFAh3&~SZ73kXV$=^@p;9se_;%}QAS0cl{}-n4DN-u z%eyA$wcVFbGyMLsKvD1DUe&bR&Tk=F6(_tE(yqNblhZhS4&xng?)@@%IE^9qxt>dx zS=Sq)S&r?KYIfbOT&TQac?XY@8qSba20c5>1D$6sh{;mkz@{W0qv(BNvmlJo>uF?d zIw#b9E(Y@;nH<@azhFa*f%o@An&Qu-cay`Yl}3_5k0_slQg+1Pv%kUh(EoMW53=xw zH2ATyVi^q`-Dh>3`wV^(DrweJI>aSlPH(IuTcF`!Wf>J%<3$$hXrxI*UlQ5DfT_fd zS~_BGWJb5Jg$)u%LeJ?ZeDD=bF7BxUQlDO|vzF!+>osCdmt^BM*06BcIKy!Ntp)B7 z3Lzi`=j$ib*p8E;>~B6%?n|)^wXkGiKvd(+Av2l`6na&tSy&>+;6=ss@@#T#8j>X* zG$8-8jH&VtZOsDHo5zI-&K#s8CM5eQ?%1HC(3%(aPHrHkY~%D>Dk({cnqgi030g*c z*aYj_W6+5(V@8q}Dy9BX)3uV4M9H9U@lqzFTTh7(4rcmNA0M^}DiR31@-5|~doz#? zVNN2F_wse@UG#QJ<98nuzi;cb8a-H;mEAXVa_f9_-22YDy?MCxbbq!lV3>;Kxwg|C zn$HY228id?9tJY|ZBoH|!9J)e++drZcVVe$!zNRmr7>5vp^{ay93}B9pPk}g8)!@` zMbXBgW4j6sam;=f3I*vqQLgJ-781I3+0^qOoU^Ht>r{CAZMMBHJ7>KGoqX&gppJTR z=EM1`XjY3=p^KT|CT7qAQaF?V>Z6C_KyMKw7$L23bV#;y_!Z%kk?K=5_&Dd!imkM> zY;yKyN_B7rD%AxzmM~wKstt{iGsa?0c=Lu$lljb{U|>sNefcq+`_+(y=t094jF_&t z2aW1)!znoEnO_1rfl@|ci+>y7&nk*)&DWt@WVz>AXLT*`1-3yDW50?<7_cnx^@9hH zWi_3qW$F(Z(a*r)3UXtPrwxp8iBD;UBG;gTkMIlBki80^z<*^+v8!BF>KCW@-1Jsn zsxU-r_G9265!(Q0$EBanR4TYh@!cf*@Cm2lF^FQJ?M z{neKDL~sH~-Jk%h%QCnvYh6~GOMv>TbgLHQHM<(B#S~X90*{7Pt=Ctv;J2WwJ)@z| zu)A3DF0NB3HxCne7?}k~ozow88pf*; zrh8(q`VBU%jmFtEwdqVCtocd*QYS*If&*!d zT7fuAN^>DA_)PAiMZ7E~acS0)nzrmW1Qje~jwPf@bbwEbO1yFa0&UHX{kG9!iix*l zA23@`!Un^*Q@y+kmbGo0=>wm4$NsLg0pD))aZ?Kp4&a0-qt$T4llfrTNTR(9>DNKj zCJ*ogt$k{W{Ihd`$YNL!SK2JGj{S{P&yb*vj#1JB(vN8cQ#67M>|6C%l~$iXf>Wy# z2yh>$zw$3!6S~1J*BvoJ_AaC3Anq~Qy~vp3ysTi$*u;9~&XRr1T(~!UW3vEmA30aZ zN|aSQKdJM=z>sCd&Sut3@}=kOb~9Jf6X3OqlH|HPDR1&;pUR@_oYrgC2b3yppr7J! zJ|IxP9kX6OY9=R0?*sGqu5#x;)7F*8pxGkYknHF@{Cndp^ap!O8 z9-b0rm2<}@=-BWFrvM`sD_sq8Oz2Zyy};iGb-|m8b}#UkY7Gp;6@%RSE;nU!G__v4 z$3Zsi)%vZX_g0rEeI9KmSDiYCo2su2(Z}NK4bCJm`;KDQ-FK(3qm%&HNx~hxV(Nfw2g0GVm%69bgS`@YC;GqFxI}(-%f9O8C-vd>%2~< zD=aerp^Verr#yunp}J2x)|9!cw-tu%$M{>rIex-?rZ^oG+e_I79; z<_-0?Q);J|sR13*OnRqMsUFux&UDxwhD&Zh+L>Saps`oUGCd-9X)wcgj+i>=VuP#F zM*mnxSKmorPnL?_Y%G@Yrm=Zv8W}r9u2@hUuV(>4qjGGAiFWvef?Lh+UMBZ1VL9J+ zj;IjjNb_o6Kl97k+4aI3TGA}|umz376QcNazg+~JPqbXj%vt^|{#-beF?}OO)FrTe zu?l0m0{SZCJT;-i0RL>VjJz+9CM~PYQ)g!m36xLsrEm8eGvkdJc;sd@*BseTT5{i^ z$L~diuf4Kt0mW?Wi|cKFc*ee*zO6xv9ITp{Wmb68$s8i7-D&vvf&VGxEQ8|k)isW5 zad&rHtgyH)?ykk%DN@|s3Y6j$r)9AgD5bc&yR#H6zPRn>{Lh)W=kvXpNuIounKv`} zkVz(ae$VgW-|LOmhKTK@J9AU4(wUw~P0}{nGAV9SuB zSg0l2S?J@X7N@E&DPB82UkVAE(DHiUArTACiaj5|P@;8EK$Eu-H}T8iCFH2#wAF?_ z?tPTfoL;y7y$I)7$F$TdTc64#+zo%0v5EW1Gq;8ej#znhA9bs5Tk3440~@;aqMI*I zA)nP9F^_$QsW$ACD2<;gSr+S<%XjxhhLwl$hOX*(@Q)uK%1cBDA>JghuluOnR_*i2^e}<*Hw(EQ9Y4!T`f_GfZK^;FuUj%cZ~!>^QnB3b zi{)A9Yw|Cl3kz};?#!pcYsNU5g0rZJ#=fM)Z0g+C^)WT~ujl3i#a+d=&k{gcKK6}z zJRR=fdM>OCQ<@1&qQD|1$G56ZOJVoS{e#cuiAF>3-GiPgXe5MRU3L%~_ut(PLLb!F zVcnz5@{UDBk_z!bbj>b+)egS-;urcn94jMLC{D*7s{n1AG zI9+-5=1Q5|8oENB;n*n})|C+zBXI}M7YuKCUWXqW3?fOs)h=vn?QtU%_22vLogY+H z+V?9XFN>QJkl2m7R~A*RljU~4=M4H44yd#L*;rvoewo(BAV&eVsUa8gny3K-lxR-PjwR@yHk{%K!rM;-Bnt!fN9f3ju)Z!`zIkNdj=OA>Mj5T_jm5N3 zE-;JcF?LG*&@iRkqfO9E>leO4K4f?M%Pb*207r~9ul_ek97}_LxSrmFsV;s&%E{L# z!_y(9qM`I7eN8Lyr$4tyTOyLl6)l}Zse#z2F*(&h zjNGRYq+DT#V9TV{-b*BvbYxL1txm=*r;-c4w0!QP1J?@rd7)2m__RB^a7J6UWawKS z(=7(9J#i3t$T6ldn7LxtwtiZl0iF>QW{9az7KZ}nV-@_pl}{rsRv(q3QyS9_$YIBt zlOiV^RP;I(79>T!L)_5?wqmJxvf^-8U&K+g*yyy|J67zS!pmq@u&z=yy3!G4Ie{{G zO+1PQneq;HOc@{i8F9vG`mj~?6U2iTuzcH>CodvC`o?-#e5#f%^KRK&`4Wdtx|KG) z^37A|k}rvjVpb$FG7CEn%{{U>5+}CGgC;gouGo)(*;eS}>&ZYfwIL&jroYr^I<{$2 zR$);6B9j%HI3`lnC>yes6Bp^uhmDRQZat;TfZcfFaj^!XOd#}sDm9H)VcZ?fb+v|{ zkmJ<%7DNJHuizTEe$!qmh#g6vk5s`2ur=qD6}SWw^LIot+Ig6$u^J;YRGWV#$iIQF z?(|YN%byYftV|GR5L3jdoA{)*zxbUS!<(~2FNUYeu$vs@T6!|H5pS||<>^GBWDjoD z0BD`D{8MpG4O12L-8Xp6f2@i%F&a~GMD0}&TWQo%^vVn;kNOy11B)ed!#6fgb#C&A#5*poy>lc~-zB2G<8& zwWCYv4|xUC$UGbbf?vMlX|MbK8S+0q3&nDGq1-swd^M3o*|u5Zs)haZ|AQ8J^Q^!u zYl0+~1%s)tR)y6s41S;o|2fASK#D^vaYHd=(;#natOX2Vd0CJ0`aE0ohvoSQ zH5c=fWf)0iD$hlIvv+m)4o2tvNlic}cF((Y=~K15v(E0*GKAI>>7jR}aHVjrWkG=9 z@pa;bTp>ypVh|QVnwm1De`c;v2f>=jCDBz3BeeM4bnZZ3p03?EX?8FghL7Sz%tH3= z$DLxp&u)vic_+RS2LgFd0LjiVD09ZLE%Ce8=kc5|73$!4gNEF=#7zX2T*yt9|8OBk8{ZV~r8n6v=n=-$ zrKMUmFkEX|+OfFeN*~5r=M4V{u=ZNg0`4RYZglI#VUW`1Lrs$OH}RPYLt_UJNQo#e zUt~=={JgN#Sd*N~lf+pIz;WoS?s;&kr=r*% znNe_*sVfQcP;eY^l>u0Ir8y9t`0e|fuD>0|HgmE`++g4HFZ)XZgF0UrDPFvZ-`)0$ z@SFdJ6bz2poIJOlggkGvU2{|}IJ@N@$O?-k>v4iFQC2}=^JJt@#d(_dHxUla!uf7E z)%v=5TWGw>Z-1-orI^I_F6Jsw*5NC(TTK!f90Nn>QYbXuP1F9Ex;;b?=P~=c%(K`k zFcmAz-l#c=)C!->(mHKR2 zv#7MR$(ZIca?5@6Q*VWB`g&(EI~01{a&yWp?tkPTJe#2TqV=_xrd@D*L#V60q0)}Z zubG^}a8_w*!^NnrUDcgu=j0PxOXMMNdr$mn_|*V@3UPOBx%ay+x@0+9AdvuwaERUn zaraRKH@@(WePSQze*>OuNwqpH{du!p6PdwlfXPP3Zhh^*07rr2wl+p1>;>z79M&MO zg4OM}wO$;!-*v)pgo{^yU`?V^#4-d^3X3gw!V{*le?`_K9*|!4J}#p8DJ8o15f_?oMOeZ}YI%l0E8*E3 zWYSNcYS^8(X5car(o-WcSuO4}0NB|trwbXi|amBv>VA2*;3AZr}OUXeHn?@4u+Q!MJ+EtR3jdy0JL1bT+yzsn*COOXM+PDWWg3dxhwzl#8-bq~l5%EHH)S&q+t=|c=`^Nl{@BzA z&Sg`YoN5jTAuoGw4U4c>nMa z=DmWx_r`anr^pW_B6z3R7W$I2431~}AC37PTG3;cIG%nwUSUJsaN1?8KUj+&<(vsc ze&8}^f3%yU){37Xm`@m;k@%q^X!*`QX*Bz*om+$Uz6B0Js@KWakz+OTzXl)Atpq3h z-TiMe7p>l!JZexxOo77mG1uL&j?Pfs&%vofGGkq(+EAUd%_q|7l@d}VY`2iAI{~cJrZl@d zs7dWr*~n=J>q#<|0O1R&1EK*s6eXAhCPS<4Z#?`FFuJQS;y@YX2?sI4;NQz zYf|Bve}I|6X1nX-2NRpp9cYT%EkneuhKz zQ1+$=mfY~I>v85@o46}^-TuV&BI#9)#EWd%_xSzN+}pv!^LYj=!BJ@{l*&sgc`^Z^ z2UsVJy`qOPyoPHx4>z+kFc(kX&&&DZ2jf6RW{wpG`2N*7mj;{bB2h1M7r#Nta-_a0 zQk~Q5$1^>vdNNJ+iY|2V6XnJlE~loX@pohQSV{dW!+jHNT1F8F3In`ta=;Q(q&_LwACzAfPqJiG@2W&^Y`WK}cPvOyD~TDGsGFfA@3k!wTB3Z+o`y$>nWk%++)2Uk zDbdY76vRWs07e%jB%s$nT5zjHiwhIoRCq4w!GwJ|pAjF+&!SLUf=da8}6Bk6_O zkWg%^K$_8Y0HPq8dFnNod z*Zg&x3#4hE;7>8D#+i+8iTd{A z=p+XQ9)4N(=mqLI`%NQ(-+=B1k?9SboQlmg#uEj}W-}C`8*2M^!sN8b8@ke_8W}}? z`kzWp1C4U%VeIe0p5bLO=`jh+x1Z20sgR+g(N(AdQnDF>B2g^j-|={4+;8uY{(s71T^wyes?>V3>V8ePc|U z_=&}dxX6e-Rn(HfJXb=2>eEuxXe>_hy1j3!ymFdhBPh+|glza*CvuH?c{pn_nYXnZ zeBl=iJc$fcgTb9N<}fIQPYL8g32G}~xFiYgf8JV>g{VN#O>y@|b_Md1os@DB`L$KS z38D)YcH2l6L=E`fFBWvAag$mX_ZPg=vZT;aLu&}2ixU-V%u*hnmq4{U z7Y#)v9gbD?PxYS;{<<7A6mN4);f`OJWw!*rZG~bspD%7*F z4i{U3CXjxp!nTy2aNhMyj+~yJuFnP5n{FD^*|(#FRMMWt2*yJFgW2KYmDu>6zL+{g zD-f@=?MZ|5vhxyXB-nKt7FH#}xkV~##05GiV zcb-iz3HQZMxd|GPYrCD8QJQw;_vla2YcRyL%J`~(n24{;L<<{_ITIpYrozoVj!3al zlrLz#zYL3wNuM{5V3Z5L!T3_#sE7oLgmB7In4|yUEPlG%L}0FYF|%tQg(H-Phr-8; zqNu!%t#yCt{vI9XA4HzFS*OLJEH!lFN76s{-lE6&637et?R=p5#QoMvl zWJ6*6J0va3K~kL9TF_8bq|zm<-tSWR$a)+pQ@ymv3-V0D(lx9IOAwLyE%FFYe+ji+2x?|9!n`_&s;WRV+y$O?JPEP) zX*lAKJFWy`ADLnhlY?;A-M!Q;bqwU*um_n?C^f8+BCQ!=MkWqmH75)GL4un|f4Cc# zz#{WJi9uv9-}8o3f%XOv)(xY0^YSL^4NKUe0u}2(6awBBO16zOKAyc4GMfbfGA$V9 ztx2c257U52!tb)fTT;~q{%gG~rXqR-Vwmn|OW{jVt+96K2dtC!NnyM>yyF%ky;mtl zvCFadm@0VA7!)*l_<5MC48AlsSjRlV6&~as%pU675Qx|I(N@49)qr^XBXTO@B(phi z17kxl=xvZvka*DTojdv+`g?R!fKklYYw`UeJQ z+TR)}3bnGQpV|_i#O{MHaR?0w1qe+Ey$Bx&C0OlPskOZ{MJh~7+d%S)wh0XZXOyQTphU0wpWr= zE|%XaZ4OCwSrinfTSjk_F))`34rmRSG1D`9tG?tgXP*KH0GRwH_7hgrwjEUQ(Gwrqo_NXf`mI5AsDBq zC;DOxKrc-^uw-`{RQS%y5w^cCXqi z%)CWAjJ#KuqA+oSO}k^FnOgzpT_5Er(aRL|PRW5cy81~bF&s^Pm0KyTkGF~jv+a}}Ev`Bg$j z^>Isl5+(3PJpPHs9eA&zc7t*$m~(Q@5eQz@*L%FeaDthrM(gPt{W|xJ6<;%jJnp&cRD?R|2?i1l;otJa7c=&IR|cfO}iPgAXoU zF)n=rEJ;yXtU+y_2o$M z<;3>o*x=>VXJ8m2FfI}pB@0aI1x7Fc6H0+G*1(hO#Xh^FK7+#3T;kC{(Tgt0ilE5vE{Wbju{JNMHlc`;mjsef%+5=SPAF<ZZjR&nzhtKRioIRA?tjIp-MDh$tB+H`e*{!{VV-PWx_BTM z@E@r$uU$lnG z!53>-18gbu^eF|AZPf_W!@UFwWzSx>*{LQW!N1fq9mn z2@b9W9u{2>pA4r`kEUtZ01uyH)Br-^Fr=%;HBzZ3)PC)R8Bx`vaF`kz)f003iw~ - - - - - - - - - - - Syncthing Usage Reports - - - - - - - - - - - - - - - -
-
-
-

Syncthing Usage Data

- -

Active Users per Day and Version

-

- This is the total number of unique users with reporting enabled, per day. Area color represents the major version. -

-
- -
-

Data Transfers per Day

-

- This is total data transferred per day. Also shows how much data was saved (not transferred) by each of the methods syncthing uses. -

-
-

Totals to date

-

- No data -

-
- -

Usage Metrics

-

- This is the aggregated usage report data for the last 24 hours. Data based on {{.nodes}} devices that have reported in. -

- - {{if .locations}} -
-

- Heatmap max intensity is capped at 100 reports within a location. -

-
- -
-
-
-
- - - {{range .countries | slice 2 1}} - - - - - - - {{end}} - -
{{.Key}}{{if ge .Pct 10.0}}{{.Pct | printf "%.0f"}}{{else if ge .Pct 1.0}}{{.Pct | printf "%.01f"}}{{else}}{{.Pct | printf "%.02f"}}{{end}}%{{.Count}} -
-
-
-
- - - {{range .countries | slice 2 2}} - - - - - - - {{end}} - -
{{.Key}}{{if ge .Pct 10.0}}{{.Pct | printf "%.0f"}}{{else if ge .Pct 1.0}}{{.Pct | printf "%.01f"}}{{else}}{{.Pct | printf "%.02f"}}{{end}}%{{.Count}} -
-
-
-
-
-
-
- {{end}} - - - - - - - - - - - - - - - - {{range .categories}} - - - - - - - - {{end}} - -
- Percentile -
5%50%95%100%
{{.Descr}}{{index .Values 0 | number .Type | commatize " "}}{{.Unit}}{{index .Values 1 | number .Type | commatize " "}}{{.Unit}}{{index .Values 2 | number .Type | commatize " "}}{{.Unit}}{{index .Values 3 | number .Type | commatize " "}}{{.Unit}}
-
-
- -
-
- - - - - - - - {{range .versions}} - {{if gt .Percentage 0.1}} - - - - - - {{range .Items}} - {{if gt .Percentage 0.1}} - - - - - - {{end}} - {{end}} - {{end}} - {{end}} - -
VersionDevicesShare
{{.Key}}{{.Count}}{{.Percentage | printf "%.01f"}}%
{{.Key}}{{.Count}}{{.Percentage | printf "%.01f"}}%
- - - - - - - - - - {{range .versionPenetrations}} - - - - - - {{end}} - -
Penetration LevelVersionActual
{{.Count}}%≥ {{.Key}}{{.Percentage | printf "%.01f"}}%
-
- -
- - - - - - - - - - {{range .platforms}} - - - - - - {{range .Items}} - - - - - - {{end}} - {{end}} - -
PlatformDevicesShare
{{.Key}}{{.Count}}{{.Percentage | printf "%.01f"}}%
{{.Key}}{{.Count}}{{.Percentage | printf "%.01f"}}%
-
- -
-
- -
- - - - - - - - - - {{range .compilers}} - - - - - - {{range .Items}} - {{if or (gt .Percentage 0.1) (eq .Key "Others")}} - - - - - - {{end}} - {{end}} - {{end}} - -
CompilerDevicesShare
{{.Key}}{{.Count}}{{.Percentage | printf "%.01f"}}%
{{.Key}}{{.Count}}{{.Percentage | printf "%.01f"}}%
-
- -
- - - - - - - - - - {{range .distributions}} - - - - - - {{end}} - -
Distribution ChannelDevicesShare
{{.Key}}{{.Count}}{{.Percentage | printf "%.01f"}}%
-
- -
- - - - - - - - - - {{range .builders}} - - - - - - {{end}} - -
BuilderDevicesShare
{{.Key}}{{.Count}}{{.Percentage | printf "%.01f"}}%
-
- -
- -
-
-

Feature Usage

-

- The following lists feature usage. Some features are reported per report, some are per sum of units within report (eg. devices with static addresses among all known devices per report). - Currently there are {{.versionNodes.v2}} devices reporting for version 2 and {{.versionNodes.v3}} for version 3. -

-
-
- - -
- {{$i := counter}} - {{range $featureName := .featureOrder}} - {{$featureValues := index $.features $featureName }} - {{if $i.DrawTwoDivider}} -
-
- {{end}} - {{ $i.Increment }} -
- - - - - - {{range $featureValues}} - - - - - - {{end}} - -
{{$featureName}} FeaturesUsage
{{.Key}} ({{.Version}}){{if ge .Pct 10.0}}{{.Pct | printf "%.0f"}}{{else if ge .Pct 1.0}}{{.Pct | printf "%.01f"}}{{else}}{{.Pct | printf "%.02f"}}{{end}}% -
-
-
- {{end}} -
- -
-
-

Feature Group Usage

-

- The following lists feature usage groups, which might include multiple occourances of a feature use per report. -

-
-
- -
- {{$i := counter}} - {{range $featureName := .featureOrder}} - {{$featureValues := index $.featureGroups $featureName }} - {{if $i.DrawTwoDivider}} -
-
- {{end}} - {{ $i.Increment }} -
- - - - - - {{range $featureValues}} - {{$counts := .Counts}} - - - - - {{end}} - -
{{$featureName}} Group FeaturesUsage
-
- {{.Key}} ({{.Version}}) -
-
-
- {{$j := counter}} - {{range $key, $value := .Counts}} - {{with $valuePct := $value | proportion $counts}} -
- {{if ge $valuePct 30.0}}{{$key}}{{end}} -
- {{end}} - {{ $j.Increment }} - {{end}} -
-
-
- {{end}} -
-
-
-

Historical Performance Data

-

These charts are all the average of the corresponding metric, for the entire population of a given day.

- -

Hash Performance (MiB/s)

-
- -

Memory Usage (MiB)

-
- -

Total Number of Files

-
- -

Total Folder Size (GiB)

-
- -

System RAM Size (GiB)

-
-
-
-
-
-

- Source code. - This product includes GeoLite2 data created by MaxMind, available from - http://www.maxmind.com. -

- - - diff --git a/cmd/stdiscosrv/database.go b/cmd/stdiscosrv/database.go index 2a796848a..911ddd851 100644 --- a/cmd/stdiscosrv/database.go +++ b/cmd/stdiscosrv/database.go @@ -26,6 +26,8 @@ import ( "github.com/puzpuzpuz/xsync/v3" "github.com/syncthing/syncthing/lib/protocol" + "github.com/syncthing/syncthing/lib/rand" + "github.com/syncthing/syncthing/lib/s3" ) type clock interface { @@ -48,27 +50,38 @@ type inMemoryStore struct { m *xsync.MapOf[protocol.DeviceID, DatabaseRecord] dir string flushInterval time.Duration - s3 *s3Copier + s3 *s3.Session + objKey string clock clock } -func newInMemoryStore(dir string, flushInterval time.Duration, s3 *s3Copier) *inMemoryStore { +func newInMemoryStore(dir string, flushInterval time.Duration, s3sess *s3.Session) *inMemoryStore { + hn, err := os.Hostname() + if err != nil { + hn = rand.String(8) + } s := &inMemoryStore{ m: xsync.NewMapOf[protocol.DeviceID, DatabaseRecord](), dir: dir, flushInterval: flushInterval, - s3: s3, + s3: s3sess, + objKey: hn + ".db", clock: defaultClock{}, } nr, err := s.read() - if os.IsNotExist(err) && s3 != nil { + if os.IsNotExist(err) && s3sess != nil { // Try to read from AWS + latestKey, cerr := s3sess.LatestKey() + if cerr != nil { + log.Println("Error reading database from S3:", err) + return s + } fd, cerr := os.Create(path.Join(s.dir, "records.db")) if cerr != nil { log.Println("Error creating database file:", err) return s } - if err := s3.downloadLatest(fd); err != nil { + if cerr := s3sess.Download(fd, latestKey); cerr != nil { log.Printf("Error reading database from S3: %v", err) } _ = fd.Close() @@ -303,7 +316,7 @@ func (s *inMemoryStore) write() (err error) { return nil } defer fd.Close() - if err := s.s3.upload(fd); err != nil { + if err := s.s3.Upload(fd, s.objKey); err != nil { log.Printf("Error uploading database to S3: %v", err) } log.Println("Finished uploading database") diff --git a/cmd/stdiscosrv/main.go b/cmd/stdiscosrv/main.go index fed332f60..0005c09f5 100644 --- a/cmd/stdiscosrv/main.go +++ b/cmd/stdiscosrv/main.go @@ -24,6 +24,7 @@ import ( "github.com/syncthing/syncthing/lib/build" "github.com/syncthing/syncthing/lib/protocol" "github.com/syncthing/syncthing/lib/rand" + "github.com/syncthing/syncthing/lib/s3" "github.com/syncthing/syncthing/lib/tlsutil" "github.com/thejerf/suture/v4" ) @@ -117,14 +118,13 @@ func main() { }) // If configured, use S3 for database backups. - var s3c *s3Copier + var s3c *s3.Session if cli.DBS3Endpoint != "" { - hostname, err := os.Hostname() + var err error + s3c, err = s3.NewSession(cli.DBS3Endpoint, cli.DBS3Region, cli.DBS3Bucket, cli.DBS3AccessKeyID, cli.DBS3SecretKey) if err != nil { - log.Fatalf("Failed to get hostname: %v", err) + log.Fatalf("Failed to create S3 session: %v", err) } - key := hostname + ".db" - s3c = newS3Copier(cli.DBS3Endpoint, cli.DBS3Region, cli.DBS3Bucket, key, cli.DBS3AccessKeyID, cli.DBS3SecretKey) } // Start the database. diff --git a/cmd/stdiscosrv/s3.go b/cmd/stdiscosrv/s3.go deleted file mode 100644 index f60cfee27..000000000 --- a/cmd/stdiscosrv/s3.go +++ /dev/null @@ -1,97 +0,0 @@ -// Copyright (C) 2024 The Syncthing Authors. -// -// This Source Code Form is subject to the terms of the Mozilla Public -// License, v. 2.0. If a copy of the MPL was not distributed with this file, -// You can obtain one at https://mozilla.org/MPL/2.0/. - -package main - -import ( - "io" - "log" - "time" - - "github.com/aws/aws-sdk-go/aws" - "github.com/aws/aws-sdk-go/aws/credentials" - "github.com/aws/aws-sdk-go/aws/session" - "github.com/aws/aws-sdk-go/service/s3" - "github.com/aws/aws-sdk-go/service/s3/s3manager" -) - -type s3Copier struct { - endpoint string - region string - bucket string - key string - accessKeyID string - secretKey string -} - -func newS3Copier(endpoint, region, bucket, key, accessKeyID, secretKey string) *s3Copier { - return &s3Copier{ - endpoint: endpoint, - region: region, - bucket: bucket, - key: key, - accessKeyID: accessKeyID, - secretKey: secretKey, - } -} - -func (s *s3Copier) upload(r io.Reader) error { - sess, err := session.NewSession(&aws.Config{ - Region: aws.String(s.region), - Endpoint: aws.String(s.endpoint), - Credentials: credentials.NewStaticCredentials(s.accessKeyID, s.secretKey, ""), - }) - if err != nil { - return err - } - - uploader := s3manager.NewUploader(sess) - _, err = uploader.Upload(&s3manager.UploadInput{ - Bucket: aws.String(s.bucket), - Key: aws.String(s.key), - Body: r, - }) - return err -} - -func (s *s3Copier) downloadLatest(w io.WriterAt) error { - sess, err := session.NewSession(&aws.Config{ - Region: aws.String(s.region), - Endpoint: aws.String(s.endpoint), - Credentials: credentials.NewStaticCredentials(s.accessKeyID, s.secretKey, ""), - }) - if err != nil { - return err - } - - svc := s3.New(sess) - resp, err := svc.ListObjectsV2(&s3.ListObjectsV2Input{Bucket: aws.String(s.bucket)}) - if err != nil { - return err - } - - var lastKey string - var lastModified time.Time - var lastSize int64 - for _, item := range resp.Contents { - if item.LastModified.After(lastModified) && *item.Size > lastSize { - lastKey = *item.Key - lastModified = *item.LastModified - lastSize = *item.Size - } else if lastModified.Sub(*item.LastModified) < 5*time.Minute && *item.Size > lastSize { - lastKey = *item.Key - lastSize = *item.Size - } - } - - log.Println("Downloading database from", lastKey) - downloader := s3manager.NewDownloader(sess) - _, err = downloader.Download(w, &s3.GetObjectInput{ - Bucket: aws.String(s.bucket), - Key: aws.String(lastKey), - }) - return err -} diff --git a/go.mod b/go.mod index c493b19ca..427c2513b 100644 --- a/go.mod +++ b/go.mod @@ -21,7 +21,6 @@ require ( github.com/jackpal/go-nat-pmp v1.0.2 github.com/julienschmidt/httprouter v1.3.0 github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 - github.com/lib/pq v1.10.9 github.com/maruel/panicparse/v2 v2.3.1 github.com/maxbrunsfeld/counterfeiter/v6 v6.8.1 github.com/maxmind/geoipupdate/v6 v6.1.0 diff --git a/go.sum b/go.sum index d1c51271c..0f1202d1b 100644 --- a/go.sum +++ b/go.sum @@ -144,8 +144,6 @@ github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= -github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw= -github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= github.com/lufia/plan9stats v0.0.0-20240909124753-873cd0166683 h1:7UMa6KCCMjZEMDtTVdcGu0B1GmmC7QJKiCCjyTAWQy0= github.com/lufia/plan9stats v0.0.0-20240909124753-873cd0166683/go.mod h1:ilwx/Dta8jXAgpFYFvSWEMwxmbWXyiUHkd5FwyKhb5k= github.com/maruel/panicparse/v2 v2.3.1 h1:NtJavmbMn0DyzmmSStE8yUsmPZrZmudPH7kplxBinOA= diff --git a/gui/default/syncthing/core/aboutModalView.html b/gui/default/syncthing/core/aboutModalView.html index d210bf063..d9dd13655 100644 --- a/gui/default/syncthing/core/aboutModalView.html +++ b/gui/default/syncthing/core/aboutModalView.html @@ -61,7 +61,6 @@ Jakob Borg, Audrius Butkevicius, Jesse Lucas, Simon Frei, Tomasz WilczyƄski, Al
  • golang/snappy, Copyright © 2011 The Snappy-Go Authors.
  • jackpal/gateway, Copyright © 2010 Jack Palevich.
  • kballard/go-shellquote, Copyright © 2014 Kevin Ballard.
  • -
  • lib/pq, Copyright © 2011-2013, 'pq' Contributors, portions Copyright © 2011 Blake Mizerany.
  • mattn/go-isatty, Copyright © Yasuhiro MATSUMOTO.
  • matttproud/golang_protobuf_extensions, Copyright © 2012 Matt T. Proud.
  • oschwald/geoip2-golang, Copyright © 2015, Gregory J. Oschwald.
  • diff --git a/lib/s3/s3.go b/lib/s3/s3.go new file mode 100644 index 000000000..ea76ff07d --- /dev/null +++ b/lib/s3/s3.go @@ -0,0 +1,101 @@ +// Copyright (C) 2024 The Syncthing Authors. +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this file, +// You can obtain one at https://mozilla.org/MPL/2.0/. + +package s3 + +import ( + "io" + "time" + + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/aws/credentials" + "github.com/aws/aws-sdk-go/aws/session" + "github.com/aws/aws-sdk-go/service/s3" + "github.com/aws/aws-sdk-go/service/s3/s3manager" +) + +type Session struct { + bucket string + s3sess *session.Session +} + +type Object = s3.Object + +func NewSession(endpoint, region, bucket, accessKeyID, secretKey string) (*Session, error) { + sess, err := session.NewSession(&aws.Config{ + Region: aws.String(region), + Endpoint: aws.String(endpoint), + Credentials: credentials.NewStaticCredentials(accessKeyID, secretKey, ""), + }) + if err != nil { + return nil, err + } + return &Session{ + bucket: bucket, + s3sess: sess, + }, nil +} + +func (s *Session) Upload(r io.Reader, key string) error { + uploader := s3manager.NewUploader(s.s3sess) + _, err := uploader.Upload(&s3manager.UploadInput{ + Bucket: aws.String(s.bucket), + Key: aws.String(key), + Body: r, + }) + return err +} + +func (s *Session) List(fn func(*Object) bool) error { + svc := s3.New(s.s3sess) + + opts := &s3.ListObjectsV2Input{ + Bucket: aws.String(s.bucket), + } + for { + resp, err := svc.ListObjectsV2(opts) + if err != nil { + return err + } + + for _, item := range resp.Contents { + if !fn(item) { + return nil + } + } + + if resp.NextContinuationToken == nil || *resp.NextContinuationToken == "" { + break + } + opts.ContinuationToken = resp.NextContinuationToken + } + + return nil +} + +func (s *Session) LatestKey() (string, error) { + var latestKey string + var lastModified time.Time + if err := s.List(func(obj *Object) bool { + if latestKey == "" || obj.LastModified.After(lastModified) { + latestKey = *obj.Key + lastModified = *obj.LastModified + } + return true + }); err != nil { + return "", err + } + return latestKey, nil +} + +func (s *Session) Download(w io.WriterAt, key string) error { + downloader := s3manager.NewDownloader(s.s3sess) + _, err := downloader.Download(w, &s3.GetObjectInput{ + Bucket: aws.String(s.bucket), + Key: aws.String(key), + }) + return err +} diff --git a/lib/ur/contract/contract.go b/lib/ur/contract/contract.go index 52f07ab2f..be932dff7 100644 --- a/lib/ur/contract/contract.go +++ b/lib/ur/contract/contract.go @@ -18,163 +18,177 @@ import ( ) type Report struct { - // Generated - Received time.Time `json:"-"` // Only from DB - Date string `json:"date,omitempty"` - Address string `json:"address,omitempty"` - // v1 fields - UniqueID string `json:"uniqueID,omitempty" since:"1"` - Version string `json:"version,omitempty" since:"1"` - LongVersion string `json:"longVersion,omitempty" since:"1"` - Platform string `json:"platform,omitempty" since:"1"` - NumFolders int `json:"numFolders,omitempty" since:"1"` - NumDevices int `json:"numDevices,omitempty" since:"1"` - TotFiles int `json:"totFiles,omitempty" since:"1"` - FolderMaxFiles int `json:"folderMaxFiles,omitempty" since:"1"` - TotMiB int `json:"totMiB,omitempty" since:"1"` - FolderMaxMiB int `json:"folderMaxMiB,omitempty" since:"1"` - MemoryUsageMiB int `json:"memoryUsageMiB,omitempty" since:"1"` - SHA256Perf float64 `json:"sha256Perf,omitempty" since:"1"` - HashPerf float64 `json:"hashPerf,omitempty" since:"1"` // Was previously not stored server-side - MemorySize int `json:"memorySize,omitempty" since:"1"` + UniqueID string `json:"uniqueID,omitempty" metric:"-" since:"1"` + Version string `json:"version,omitempty" metric:"reports_total,gaugeVec:version" since:"1"` + LongVersion string `json:"longVersion,omitempty" metric:"-" since:"1"` + Platform string `json:"platform,omitempty" metric:"-" since:"1"` + NumFolders int `json:"numFolders,omitempty" metric:"num_folders,summary" since:"1"` + NumDevices int `json:"numDevices,omitempty" metric:"num_devices,summary" since:"1"` + TotFiles int `json:"totFiles,omitempty" metric:"total_files,summary" since:"1"` + FolderMaxFiles int `json:"folderMaxFiles,omitempty" metric:"folder_max_files,summary" since:"1"` + TotMiB int `json:"totMiB,omitempty" metric:"total_data_mib,summary" since:"1"` + FolderMaxMiB int `json:"folderMaxMiB,omitempty" metric:"folder_max_data_mib,summary" since:"1"` + MemoryUsageMiB int `json:"memoryUsageMiB,omitempty" metric:"memory_usage_mib,summary" since:"1"` + SHA256Perf float64 `json:"sha256Perf,omitempty" metric:"sha256_perf_mibps,summary" since:"1"` + HashPerf float64 `json:"hashPerf,omitempty" metric:"hash_perf_mibps,summary" since:"1"` + MemorySize int `json:"memorySize,omitempty" metric:"memory_size_mib,summary" since:"1"` // v2 fields - URVersion int `json:"urVersion,omitempty" since:"2"` - NumCPU int `json:"numCPU,omitempty" since:"2"` + URVersion int `json:"urVersion,omitempty" metric:"reports_by_urversion_total,gaugeVec:version" since:"2"` + NumCPU int `json:"numCPU,omitempty" metric:"num_cpu,summary" since:"2"` + FolderUses struct { - SendOnly int `json:"sendonly,omitempty" since:"2"` - SendReceive int `json:"sendreceive,omitempty" since:"2"` // Was previously not stored server-side - ReceiveOnly int `json:"receiveonly,omitempty" since:"2"` - IgnorePerms int `json:"ignorePerms,omitempty" since:"2"` - IgnoreDelete int `json:"ignoreDelete,omitempty" since:"2"` - AutoNormalize int `json:"autoNormalize,omitempty" since:"2"` - SimpleVersioning int `json:"simpleVersioning,omitempty" since:"2"` - ExternalVersioning int `json:"externalVersioning,omitempty" since:"2"` - StaggeredVersioning int `json:"staggeredVersioning,omitempty" since:"2"` - TrashcanVersioning int `json:"trashcanVersioning,omitempty" since:"2"` + SendOnly int `json:"sendonly,omitempty" metric:"folder_feature{feature=ModeSendonly},summary" since:"2"` + SendReceive int `json:"sendreceive,omitempty" metric:"folder_feature{feature=ModeSendReceive},summary" since:"2"` + ReceiveOnly int `json:"receiveonly,omitempty" metric:"folder_feature{feature=ModeReceiveOnly},summary" since:"2"` + IgnorePerms int `json:"ignorePerms,omitempty" metric:"folder_feature{feature=IgnorePerms},summary" since:"2"` + IgnoreDelete int `json:"ignoreDelete,omitempty" metric:"folder_feature{feature=IgnoreDelete},summary" since:"2"` + AutoNormalize int `json:"autoNormalize,omitempty" metric:"folder_feature{feature=AutoNormalize},summary" since:"2"` + SimpleVersioning int `json:"simpleVersioning,omitempty" metric:"folder_feature{feature=VersioningSimple},summary" since:"2"` + ExternalVersioning int `json:"externalVersioning,omitempty" metric:"folder_feature{feature=VersioningExternal},summary" since:"2"` + StaggeredVersioning int `json:"staggeredVersioning,omitempty" metric:"folder_feature{feature=VersioningStaggered},summary" since:"2"` + TrashcanVersioning int `json:"trashcanVersioning,omitempty" metric:"folder_feature{feature=VersioningTrashcan},summary" since:"2"` } `json:"folderUses,omitempty" since:"2"` DeviceUses struct { - Introducer int `json:"introducer,omitempty" since:"2"` - CustomCertName int `json:"customCertName,omitempty" since:"2"` - CompressAlways int `json:"compressAlways,omitempty" since:"2"` - CompressMetadata int `json:"compressMetadata,omitempty" since:"2"` - CompressNever int `json:"compressNever,omitempty" since:"2"` - DynamicAddr int `json:"dynamicAddr,omitempty" since:"2"` - StaticAddr int `json:"staticAddr,omitempty" since:"2"` + Introducer int `json:"introducer,omitempty" metric:"device_feature{feature=Introducer},summary" since:"2"` + CustomCertName int `json:"customCertName,omitempty" metric:"device_feature{feature=CustomCertName},summary" since:"2"` + CompressAlways int `json:"compressAlways,omitempty" metric:"device_feature{feature=CompressAlways},summary" since:"2"` + CompressMetadata int `json:"compressMetadata,omitempty" metric:"device_feature{feature=CompressMetadata},summary" since:"2"` + CompressNever int `json:"compressNever,omitempty" metric:"device_feature{feature=CompressNever},summary" since:"2"` + DynamicAddr int `json:"dynamicAddr,omitempty" metric:"device_feature{feature=AddressDynamic},summary" since:"2"` + StaticAddr int `json:"staticAddr,omitempty" metric:"device_feature{feature=AddressStatic},summary" since:"2"` } `json:"deviceUses,omitempty" since:"2"` Announce struct { - GlobalEnabled bool `json:"globalEnabled,omitempty" since:"2"` - LocalEnabled bool `json:"localEnabled,omitempty" since:"2"` - DefaultServersDNS int `json:"defaultServersDNS,omitempty" since:"2"` - DefaultServersIP int `json:"defaultServersIP,omitempty" since:"2"` // Deprecated and not provided client-side anymore - OtherServers int `json:"otherServers,omitempty" since:"2"` + GlobalEnabled bool `json:"globalEnabled,omitempty" metric:"discovery_feature_count{feature=GlobalEnabled},gauge" since:"2"` + LocalEnabled bool `json:"localEnabled,omitempty" metric:"discovery_feature_count{feature=LocalEnabled},gauge" since:"2"` + DefaultServersDNS int `json:"defaultServersDNS,omitempty" metric:"discovery_default_servers,summary" since:"2"` + OtherServers int `json:"otherServers,omitempty" metric:"discovery_other_servers,summary" since:"2"` } `json:"announce,omitempty" since:"2"` Relays struct { - Enabled bool `json:"enabled,omitempty" since:"2"` - DefaultServers int `json:"defaultServers,omitempty" since:"2"` - OtherServers int `json:"otherServers,omitempty" since:"2"` + Enabled bool `json:"enabled,omitempty" metric:"relay_feature_enabled,gauge" since:"2"` + DefaultServers int `json:"defaultServers,omitempty" metric:"relay_feature_count{feature=DefaultServers},summary" since:"2"` + OtherServers int `json:"otherServers,omitempty" metric:"relay_feature_count{feature=OtherServers},summary" since:"2"` } `json:"relays,omitempty" since:"2"` - UsesRateLimit bool `json:"usesRateLimit,omitempty" since:"2"` - UpgradeAllowedManual bool `json:"upgradeAllowedManual,omitempty" since:"2"` - UpgradeAllowedAuto bool `json:"upgradeAllowedAuto,omitempty" since:"2"` + UsesRateLimit bool `json:"usesRateLimit,omitempty" metric:"feature_count{feature=RateLimitsEnabled},gauge" since:"2"` + UpgradeAllowedManual bool `json:"upgradeAllowedManual,omitempty" metric:"feature_count{feature=UpgradeAllowedManual},gauge" since:"2"` + UpgradeAllowedAuto bool `json:"upgradeAllowedAuto,omitempty" metric:"feature_count{feature=UpgradeAllowedAuto},gauge" since:"2"` // V2.5 fields (fields that were in v2 but never added to the database - UpgradeAllowedPre bool `json:"upgradeAllowedPre,omitempty" since:"2"` - RescanIntvs []int `json:"rescanIntvs,omitempty" since:"2"` + UpgradeAllowedPre bool `json:"upgradeAllowedPre,omitempty" metric:"upgrade_allowed_pre,gauge" since:"2"` + RescanIntvs []int `json:"rescanIntvs,omitempty" metric:"folder_rescan_intervals,summary" since:"2"` // v3 fields - Uptime int `json:"uptime,omitempty" since:"3"` - NATType string `json:"natType,omitempty" since:"3"` - AlwaysLocalNets bool `json:"alwaysLocalNets,omitempty" since:"3"` - CacheIgnoredFiles bool `json:"cacheIgnoredFiles,omitempty" since:"3"` - OverwriteRemoteDeviceNames bool `json:"overwriteRemoteDeviceNames,omitempty" since:"3"` - ProgressEmitterEnabled bool `json:"progressEmitterEnabled,omitempty" since:"3"` - CustomDefaultFolderPath bool `json:"customDefaultFolderPath,omitempty" since:"3"` - WeakHashSelection string `json:"weakHashSelection,omitempty" since:"3"` // Deprecated and not provided client-side anymore - CustomTrafficClass bool `json:"customTrafficClass,omitempty" since:"3"` - CustomTempIndexMinBlocks bool `json:"customTempIndexMinBlocks,omitempty" since:"3"` - TemporariesDisabled bool `json:"temporariesDisabled,omitempty" since:"3"` - TemporariesCustom bool `json:"temporariesCustom,omitempty" since:"3"` - LimitBandwidthInLan bool `json:"limitBandwidthInLan,omitempty" since:"3"` - CustomReleaseURL bool `json:"customReleaseURL,omitempty" since:"3"` - RestartOnWakeup bool `json:"restartOnWakeup,omitempty" since:"3"` - CustomStunServers bool `json:"customStunServers,omitempty" since:"3"` + Uptime int `json:"uptime,omitempty" metric:"uptime_seconds,summary" since:"3"` + NATType string `json:"natType,omitempty" metric:"nat_detection,gaugeVec:type" since:"3"` + + AlwaysLocalNets bool `json:"alwaysLocalNets,omitempty" metric:"feature_count{feature=AlwaysLocalNets},gauge" since:"3"` + CacheIgnoredFiles bool `json:"cacheIgnoredFiles,omitempty" metric:"feature_count{feature=CacheIgnoredFiles},gauge" since:"3"` + OverwriteRemoteDeviceNames bool `json:"overwriteRemoteDeviceNames,omitempty" metric:"feature_count{feature=OverwriteRemoteDeviceNames},gauge" since:"3"` + ProgressEmitterEnabled bool `json:"progressEmitterEnabled,omitempty" metric:"feature_count{feature=ProgressEmitterEnabled},gauge" since:"3"` + CustomDefaultFolderPath bool `json:"customDefaultFolderPath,omitempty" metric:"feature_count{feature=CustomDefaultFolderPath},gauge" since:"3"` + CustomTrafficClass bool `json:"customTrafficClass,omitempty" metric:"feature_count{feature=CustomTrafficClass},gauge" since:"3"` + CustomTempIndexMinBlocks bool `json:"customTempIndexMinBlocks,omitempty" metric:"feature_count{feature=CustomTempIndexMinBlocks},gauge" since:"3"` + TemporariesDisabled bool `json:"temporariesDisabled,omitempty" metric:"feature_count{feature=TemporariesDisabled},gauge" since:"3"` + TemporariesCustom bool `json:"temporariesCustom,omitempty" metric:"feature_count{feature=TemporariesCustom},gauge" since:"3"` + LimitBandwidthInLan bool `json:"limitBandwidthInLan,omitempty" metric:"feature_count{feature=LimitBandwidthInLAN},gauge" since:"3"` + CustomReleaseURL bool `json:"customReleaseURL,omitempty" metric:"feature_count{feature=CustomReleaseURL},gauge" since:"3"` + RestartOnWakeup bool `json:"restartOnWakeup,omitempty" metric:"feature_count{feature=RestartOnWakeup},gauge" since:"3"` + CustomStunServers bool `json:"customStunServers,omitempty" metric:"feature_count{feature=CustomSTUNServers},gauge" since:"3"` FolderUsesV3 struct { - ScanProgressDisabled int `json:"scanProgressDisabled,omitempty" since:"3"` - ConflictsDisabled int `json:"conflictsDisabled,omitempty" since:"3"` - ConflictsUnlimited int `json:"conflictsUnlimited,omitempty" since:"3"` - ConflictsOther int `json:"conflictsOther,omitempty" since:"3"` - DisableSparseFiles int `json:"disableSparseFiles,omitempty" since:"3"` - DisableTempIndexes int `json:"disableTempIndexes,omitempty" since:"3"` - AlwaysWeakHash int `json:"alwaysWeakHash,omitempty" since:"3"` - CustomWeakHashThreshold int `json:"customWeakHashThreshold,omitempty" since:"3"` - FsWatcherEnabled int `json:"fsWatcherEnabled,omitempty" since:"3"` - PullOrder map[string]int `json:"pullOrder,omitempty" since:"3"` - FilesystemType map[string]int `json:"filesystemType,omitempty" since:"3"` - FsWatcherDelays []int `json:"fsWatcherDelays,omitempty" since:"3"` - CustomMarkerName int `json:"customMarkerName,omitempty" since:"3"` - CopyOwnershipFromParent int `json:"copyOwnershipFromParent,omitempty" since:"3"` - ModTimeWindowS []int `json:"modTimeWindowS,omitempty" since:"3"` - MaxConcurrentWrites []int `json:"maxConcurrentWrites,omitempty" since:"3"` - DisableFsync int `json:"disableFsync,omitempty" since:"3"` - BlockPullOrder map[string]int `json:"blockPullOrder,omitempty" since:"3"` - CopyRangeMethod map[string]int `json:"copyRangeMethod,omitempty" since:"3"` - CaseSensitiveFS int `json:"caseSensitiveFS,omitempty" since:"3"` - ReceiveEncrypted int `json:"receiveencrypted,omitempty" since:"3"` + ScanProgressDisabled int `json:"scanProgressDisabled,omitempty" metric:"folder_feature{feature=ScanProgressDisabled},summary" since:"3"` + ConflictsDisabled int `json:"conflictsDisabled,omitempty" metric:"folder_feature{feature=ConflictsDisabled},summary" since:"3"` + ConflictsUnlimited int `json:"conflictsUnlimited,omitempty" metric:"folder_feature{feature=ConflictsUnlimited},summary" since:"3"` + ConflictsOther int `json:"conflictsOther,omitempty" metric:"folder_feature{feature=ConflictsOther},summary" since:"3"` + DisableSparseFiles int `json:"disableSparseFiles,omitempty" metric:"folder_feature{feature=DisableSparseFiles},summary" since:"3"` + DisableTempIndexes int `json:"disableTempIndexes,omitempty" metric:"folder_feature{feature=DisableTempIndexes},summary" since:"3"` + AlwaysWeakHash int `json:"alwaysWeakHash,omitempty" metric:"folder_feature{feature=AlwaysWeakhash},summary" since:"3"` + CustomWeakHashThreshold int `json:"customWeakHashThreshold,omitempty" metric:"folder_feature{feature=CustomWeakhashThreshold},summary" since:"3"` + FsWatcherEnabled int `json:"fsWatcherEnabled,omitempty" metric:"folder_feature{feature=FSWatcherEnabled},summary" since:"3"` + PullOrder map[string]int `json:"pullOrder,omitempty" metric:"folder_pull_order,summaryVec:order" since:"3"` + FilesystemType map[string]int `json:"filesystemType,omitempty" metric:"folder_file_system_type,summaryVec:type" since:"3"` + FsWatcherDelays []int `json:"fsWatcherDelays,omitempty" metric:"folder_fswatcher_delays,summary" since:"3"` + CustomMarkerName int `json:"customMarkerName,omitempty" metric:"folder_feature{feature=CustomMarkername},summary" since:"3"` + CopyOwnershipFromParent int `json:"copyOwnershipFromParent,omitempty" metric:"folder_feature{feature=CopyParentOwnership},summary" since:"3"` + ModTimeWindowS []int `json:"modTimeWindowS,omitempty" metric:"folder_modtime_window_s,summary" since:"3"` + MaxConcurrentWrites []int `json:"maxConcurrentWrites,omitempty" metric:"folder_max_concurrent_writes,summary" since:"3"` + DisableFsync int `json:"disableFsync,omitempty" metric:"folder_feature{feature=DisableFsync},summary" since:"3"` + BlockPullOrder map[string]int `json:"blockPullOrder,omitempty" metric:"folder_block_pull_order:summaryVec:order" since:"3"` + CopyRangeMethod map[string]int `json:"copyRangeMethod,omitempty" metric:"folder_copy_range_method:summaryVec:method" since:"3"` + CaseSensitiveFS int `json:"caseSensitiveFS,omitempty" metric:"folder_feature{feature=CaseSensitiveFS},summary" since:"3"` + ReceiveEncrypted int `json:"receiveencrypted,omitempty" metric:"folder_feature{feature=ReceiveEncrypted},summary" since:"3"` + SendXattrs int `json:"sendXattrs,omitempty" metric:"folder_feature{feature=SendXattrs},summary" since:"3"` + SyncXattrs int `json:"syncXattrs,omitempty" metric:"folder_feature{feature=SyncXattrs},summary" since:"3"` + SendOwnership int `json:"sendOwnership,omitempty" metric:"folder_feature{feature=SendOwnership},summary" since:"3"` + SyncOwnership int `json:"syncOwnership,omitempty" metric:"folder_feature{feature=SyncOwnership},summary" since:"3"` } `json:"folderUsesV3,omitempty" since:"3"` DeviceUsesV3 struct { - Untrusted int `json:"untrusted,omitempty" since:"3"` + Untrusted int `json:"untrusted,omitempty" metric:"device_feature{feature=Untrusted},summary" since:"3"` + UsesRateLimit int `json:"usesRateLimit,omitempty" metric:"device_feature{feature=RateLimitsEnabled},summary" since:"3"` + MultipleConnections int `json:"multipleConnections,omitempty" metric:"device_feature{feature=MultipleConnections},summary" since:"3"` } `json:"deviceUsesV3,omitempty" since:"3"` GUIStats struct { - Enabled int `json:"enabled,omitempty" since:"3"` - UseTLS int `json:"useTLS,omitempty" since:"3"` - UseAuth int `json:"useAuth,omitempty" since:"3"` - InsecureAdminAccess int `json:"insecureAdminAccess,omitempty" since:"3"` - Debugging int `json:"debugging,omitempty" since:"3"` - InsecureSkipHostCheck int `json:"insecureSkipHostCheck,omitempty" since:"3"` - InsecureAllowFrameLoading int `json:"insecureAllowFrameLoading,omitempty" since:"3"` - ListenLocal int `json:"listenLocal,omitempty" since:"3"` - ListenUnspecified int `json:"listenUnspecified,omitempty" since:"3"` - Theme map[string]int `json:"theme,omitempty" since:"3"` + Enabled int `json:"enabled,omitempty" metric:"gui_feature_count{feature=Enabled},summary" since:"3"` + UseTLS int `json:"useTLS,omitempty" metric:"gui_feature_count{feature=TLS},summary" since:"3"` + UseAuth int `json:"useAuth,omitempty" metric:"gui_feature_count{feature=Authentication},summary" since:"3"` + InsecureAdminAccess int `json:"insecureAdminAccess,omitempty" metric:"gui_feature_count{feature=InsecureAdminAccess},summary" since:"3"` + Debugging int `json:"debugging,omitempty" metric:"gui_feature_count{feature=Debugging},summary" since:"3"` + InsecureSkipHostCheck int `json:"insecureSkipHostCheck,omitempty" metric:"gui_feature_count{feature=InsecureSkipHostCheck},summary" since:"3"` + InsecureAllowFrameLoading int `json:"insecureAllowFrameLoading,omitempty" metric:"gui_feature_count{feature=InsecureAllowFrameLoading},summary" since:"3"` + ListenLocal int `json:"listenLocal,omitempty" metric:"gui_feature_count{feature=ListenLocal},summary" since:"3"` + ListenUnspecified int `json:"listenUnspecified,omitempty" metric:"gui_feature_count{feature=ListenUnspecified},summary" since:"3"` + Theme map[string]int `json:"theme,omitempty" metric:"gui_theme,summaryVec:theme" since:"3"` } `json:"guiStats,omitempty" since:"3"` BlockStats struct { - Total int `json:"total,omitempty" since:"3"` - Renamed int `json:"renamed,omitempty" since:"3"` - Reused int `json:"reused,omitempty" since:"3"` - Pulled int `json:"pulled,omitempty" since:"3"` - CopyOrigin int `json:"copyOrigin,omitempty" since:"3"` - CopyOriginShifted int `json:"copyOriginShifted,omitempty" since:"3"` - CopyElsewhere int `json:"copyElsewhere,omitempty" since:"3"` + Total int `json:"total,omitempty" metric:"blocks_processed_total,gauge" since:"3"` + Renamed int `json:"renamed,omitempty" metric:"blocks_processed{source=renamed},gauge" since:"3"` + Reused int `json:"reused,omitempty" metric:"blocks_processed{source=reused},gauge" since:"3"` + Pulled int `json:"pulled,omitempty" metric:"blocks_processed{source=pulled},gauge" since:"3"` + CopyOrigin int `json:"copyOrigin,omitempty" metric:"blocks_processed{source=copy_origin},gauge" since:"3"` + CopyOriginShifted int `json:"copyOriginShifted,omitempty" metric:"blocks_processed{source=copy_origin_shifted},gauge" since:"3"` + CopyElsewhere int `json:"copyElsewhere,omitempty" metric:"blocks_processed{source=copy_elsewhere},gauge" since:"3"` } `json:"blockStats,omitempty" since:"3"` TransportStats map[string]int `json:"transportStats,omitempty" since:"3"` IgnoreStats struct { - Lines int `json:"lines,omitempty" since:"3"` - Inverts int `json:"inverts,omitempty" since:"3"` - Folded int `json:"folded,omitempty" since:"3"` - Deletable int `json:"deletable,omitempty" since:"3"` - Rooted int `json:"rooted,omitempty" since:"3"` - Includes int `json:"includes,omitempty" since:"3"` - EscapedIncludes int `json:"escapedIncludes,omitempty" since:"3"` - DoubleStars int `json:"doubleStars,omitempty" since:"3"` - Stars int `json:"stars,omitempty" since:"3"` + Lines int `json:"lines,omitempty" metric:"folder_ignore_lines_total,summary" since:"3"` + Inverts int `json:"inverts,omitempty" metric:"folder_ignore_lines{kind=inverts},summary" since:"3"` + Folded int `json:"folded,omitempty" metric:"folder_ignore_lines{kind=folded},summary" since:"3"` + Deletable int `json:"deletable,omitempty" metric:"folder_ignore_lines{kind=deletable},summary" since:"3"` + Rooted int `json:"rooted,omitempty" metric:"folder_ignore_lines{kind=rooted},summary" since:"3"` + Includes int `json:"includes,omitempty" metric:"folder_ignore_lines{kind=includes},summary" since:"3"` + EscapedIncludes int `json:"escapedIncludes,omitempty" metric:"folder_ignore_lines{kind=escapedIncludes},summary" since:"3"` + DoubleStars int `json:"doubleStars,omitempty" metric:"folder_ignore_lines{kind=doubleStars},summary" since:"3"` + Stars int `json:"stars,omitempty" metric:"folder_ignore_lines{kind=stars},summary" since:"3"` } `json:"ignoreStats,omitempty" since:"3"` // V3 fields added late in the RC - WeakHashEnabled bool `json:"weakHashEnabled,omitempty" since:"3"` // Deprecated and not provided client-side anymore + WeakHashEnabled bool `json:"weakHashEnabled,omitempty" metric:"-" since:"3"` // Deprecated and not provided client-side anymore + + // Added in post processing + Received time.Time `json:"received,omitempty"` + Date string `json:"date,omitempty"` + Address string `json:"address,omitempty"` + OS string `json:"os" metric:"reports_total,gaugeVec:os"` + Arch string `json:"arch" metric:"reports_total,gaugeVec:arch"` + Compiler string `json:"compiler" metric:"builder,gaugeVec:compiler"` + Builder string `json:"builder" metric:"builder,gaugeVec:builder"` + Distribution string `json:"distribution" metric:"builder,gaugeVec:distribution"` + Country string `json:"country" metric:"location,gaugeVec:country"` + CountryCode string `json:"countryCode" metric:"location,gaugeVec:countryCode"` + MajorVersion string `json:"majorVersion" metric:"reports_by_major_total,gaugeVec:version"` } func New() *Report { @@ -206,182 +220,6 @@ func (r *Report) ClearForVersion(version int) error { return clear(r, version) } -func (r *Report) FieldPointers() []interface{} { - // All the fields of the Report, in the same order as the database fields. - return []interface{}{ - &r.Received, &r.UniqueID, &r.Version, &r.LongVersion, &r.Platform, - &r.NumFolders, &r.NumDevices, &r.TotFiles, &r.FolderMaxFiles, - &r.TotMiB, &r.FolderMaxMiB, &r.MemoryUsageMiB, &r.SHA256Perf, - &r.MemorySize, &r.Date, - // V2 - &r.URVersion, &r.NumCPU, &r.FolderUses.SendOnly, &r.FolderUses.IgnorePerms, - &r.FolderUses.IgnoreDelete, &r.FolderUses.AutoNormalize, &r.DeviceUses.Introducer, - &r.DeviceUses.CustomCertName, &r.DeviceUses.CompressAlways, - &r.DeviceUses.CompressMetadata, &r.DeviceUses.CompressNever, - &r.DeviceUses.DynamicAddr, &r.DeviceUses.StaticAddr, - &r.Announce.GlobalEnabled, &r.Announce.LocalEnabled, - &r.Announce.DefaultServersDNS, &r.Announce.DefaultServersIP, - &r.Announce.OtherServers, &r.Relays.Enabled, &r.Relays.DefaultServers, - &r.Relays.OtherServers, &r.UsesRateLimit, &r.UpgradeAllowedManual, - &r.UpgradeAllowedAuto, &r.FolderUses.SimpleVersioning, - &r.FolderUses.ExternalVersioning, &r.FolderUses.StaggeredVersioning, - &r.FolderUses.TrashcanVersioning, - - // V2.5 - &r.UpgradeAllowedPre, - - // V3 - &r.Uptime, &r.NATType, &r.AlwaysLocalNets, &r.CacheIgnoredFiles, - &r.OverwriteRemoteDeviceNames, &r.ProgressEmitterEnabled, &r.CustomDefaultFolderPath, - &r.WeakHashSelection, &r.CustomTrafficClass, &r.CustomTempIndexMinBlocks, - &r.TemporariesDisabled, &r.TemporariesCustom, &r.LimitBandwidthInLan, - &r.CustomReleaseURL, &r.RestartOnWakeup, &r.CustomStunServers, - - &r.FolderUsesV3.ScanProgressDisabled, &r.FolderUsesV3.ConflictsDisabled, - &r.FolderUsesV3.ConflictsUnlimited, &r.FolderUsesV3.ConflictsOther, - &r.FolderUsesV3.DisableSparseFiles, &r.FolderUsesV3.DisableTempIndexes, - &r.FolderUsesV3.AlwaysWeakHash, &r.FolderUsesV3.CustomWeakHashThreshold, - &r.FolderUsesV3.FsWatcherEnabled, - - &r.GUIStats.Enabled, &r.GUIStats.UseTLS, &r.GUIStats.UseAuth, - &r.GUIStats.InsecureAdminAccess, - &r.GUIStats.Debugging, &r.GUIStats.InsecureSkipHostCheck, - &r.GUIStats.InsecureAllowFrameLoading, &r.GUIStats.ListenLocal, - &r.GUIStats.ListenUnspecified, - - &r.BlockStats.Total, &r.BlockStats.Renamed, - &r.BlockStats.Reused, &r.BlockStats.Pulled, &r.BlockStats.CopyOrigin, - &r.BlockStats.CopyOriginShifted, &r.BlockStats.CopyElsewhere, - - &r.IgnoreStats.Lines, &r.IgnoreStats.Inverts, &r.IgnoreStats.Folded, - &r.IgnoreStats.Deletable, &r.IgnoreStats.Rooted, &r.IgnoreStats.Includes, - &r.IgnoreStats.EscapedIncludes, &r.IgnoreStats.DoubleStars, &r.IgnoreStats.Stars, - - // V3 added late in the RC - &r.WeakHashEnabled, - &r.Address, - - // Receive only folders - &r.FolderUses.ReceiveOnly, - } -} - -func (*Report) FieldNames() []string { - // The database fields that back this struct in PostgreSQL - return []string{ - // V1 - "Received", - "UniqueID", - "Version", - "LongVersion", - "Platform", - "NumFolders", - "NumDevices", - "TotFiles", - "FolderMaxFiles", - "TotMiB", - "FolderMaxMiB", - "MemoryUsageMiB", - "SHA256Perf", - "MemorySize", - "Date", - // V2 - "ReportVersion", - "NumCPU", - "FolderRO", - "FolderIgnorePerms", - "FolderIgnoreDelete", - "FolderAutoNormalize", - "DeviceIntroducer", - "DeviceCustomCertName", - "DeviceCompressionAlways", - "DeviceCompressionMetadata", - "DeviceCompressionNever", - "DeviceDynamicAddr", - "DeviceStaticAddr", - "AnnounceGlobalEnabled", - "AnnounceLocalEnabled", - "AnnounceDefaultServersDNS", - "AnnounceDefaultServersIP", - "AnnounceOtherServers", - "RelayEnabled", - "RelayDefaultServers", - "RelayOtherServers", - "RateLimitEnabled", - "UpgradeAllowedManual", - "UpgradeAllowedAuto", - // v0.12.19+ - "FolderSimpleVersioning", - "FolderExternalVersioning", - "FolderStaggeredVersioning", - "FolderTrashcanVersioning", - // V2.5 - "UpgradeAllowedPre", - // V3 - "Uptime", - "NATType", - "AlwaysLocalNets", - "CacheIgnoredFiles", - "OverwriteRemoteDeviceNames", - "ProgressEmitterEnabled", - "CustomDefaultFolderPath", - "WeakHashSelection", - "CustomTrafficClass", - "CustomTempIndexMinBlocks", - "TemporariesDisabled", - "TemporariesCustom", - "LimitBandwidthInLan", - "CustomReleaseURL", - "RestartOnWakeup", - "CustomStunServers", - - "FolderScanProgressDisabled", - "FolderConflictsDisabled", - "FolderConflictsUnlimited", - "FolderConflictsOther", - "FolderDisableSparseFiles", - "FolderDisableTempIndexes", - "FolderAlwaysWeakHash", - "FolderCustomWeakHashThreshold", - "FolderFsWatcherEnabled", - - "GUIEnabled", - "GUIUseTLS", - "GUIUseAuth", - "GUIInsecureAdminAccess", - "GUIDebugging", - "GUIInsecureSkipHostCheck", - "GUIInsecureAllowFrameLoading", - "GUIListenLocal", - "GUIListenUnspecified", - - "BlocksTotal", - "BlocksRenamed", - "BlocksReused", - "BlocksPulled", - "BlocksCopyOrigin", - "BlocksCopyOriginShifted", - "BlocksCopyElsewhere", - - "IgnoreLines", - "IgnoreInverts", - "IgnoreFolded", - "IgnoreDeletable", - "IgnoreRooted", - "IgnoreIncludes", - "IgnoreEscapedIncludes", - "IgnoreDoubleStars", - "IgnoreStars", - - // V3 added late in the RC - "WeakHashEnabled", - "Address", - - // Receive only folders - "FolderRecvOnly", - } -} - func (r Report) Value() (driver.Value, error) { // This needs to be string, yet we read back bytes.. bs, err := json.Marshal(r) diff --git a/lib/ur/usage_report.go b/lib/ur/usage_report.go index 858e2ef07..807b09cc3 100644 --- a/lib/ur/usage_report.go +++ b/lib/ur/usage_report.go @@ -274,6 +274,18 @@ func (s *Service) reportData(ctx context.Context, urVersion int, preview bool) ( if cfg.Type == config.FolderTypeReceiveEncrypted { report.FolderUsesV3.ReceiveEncrypted++ } + if cfg.SendXattrs { + report.FolderUsesV3.SendXattrs++ + } + if cfg.SyncXattrs { + report.FolderUsesV3.SyncXattrs++ + } + if cfg.SendOwnership { + report.FolderUsesV3.SendOwnership++ + } + if cfg.SyncOwnership { + report.FolderUsesV3.SyncOwnership++ + } } sort.Ints(report.FolderUsesV3.FsWatcherDelays) @@ -281,6 +293,12 @@ func (s *Service) reportData(ctx context.Context, urVersion int, preview bool) ( if cfg.Untrusted { report.DeviceUsesV3.Untrusted++ } + if cfg.MaxRecvKbps > 0 || cfg.MaxSendKbps > 0 { + report.DeviceUsesV3.UsesRateLimit++ + } + if cfg.RawNumConnections > 1 { + report.DeviceUsesV3.MultipleConnections++ + } } guiCfg := s.cfg.GUI()