pax_global_header00006660000000000000000000000064145342565210014521gustar00rootroot0000000000000052 comment=073d2ac2f761439d2132a2c1e34a42f1d873ec12 pat-0.15.1/000077500000000000000000000000001453425652100123715ustar00rootroot00000000000000pat-0.15.1/.github/000077500000000000000000000000001453425652100137315ustar00rootroot00000000000000pat-0.15.1/.github/workflows/000077500000000000000000000000001453425652100157665ustar00rootroot00000000000000pat-0.15.1/.github/workflows/docker.yaml000066400000000000000000000023101453425652100201150ustar00rootroot00000000000000name: docker-push on: push: branches: - 'ci-test/*' - 'release/*' tags: - 'v*' jobs: docker: runs-on: ubuntu-latest steps: - name: Checkout uses: actions/checkout@v4 - name: Generate Docker metadata id: meta uses: docker/metadata-action@v5 with: images: la5nta/pat tags: | type=ref,event=branch type=semver,pattern={{version}} type=semver,pattern={{major}}.{{minor}} - name: Set up QEMU uses: docker/setup-qemu-action@v3 - name: Set up Docker Buildx uses: docker/setup-buildx-action@v3 - name: Login to Docker Hub uses: docker/login-action@v3 with: username: ${{ secrets.DOCKERHUB_USERNAME }} password: ${{ secrets.DOCKERHUB_TOKEN }} - name: Build and push uses: docker/build-push-action@v5 with: context: . platforms: linux/amd64,linux/386,linux/arm64/v8,linux/arm/v7,linux/arm/v6 push: ${{ github.event_name == 'push' && startsWith(github.ref, 'refs/tags/v') }} tags: ${{ steps.meta.outputs.tags }} labels: ${{ steps.meta.outputs.labels }} pat-0.15.1/.github/workflows/go.yaml000066400000000000000000000024051453425652100172600ustar00rootroot00000000000000name: build on: push: pull_request: types: [ review_requested ] jobs: build: strategy: matrix: os: [ubuntu-latest, macos-latest, windows-latest] go-version: [ '1.x' ] include: - os: ubuntu-latest go-version: '1.19' runs-on: ${{ matrix.os }} steps: - uses: actions/checkout@v3 - name: Setup Go ${{ matrix.go-version }} uses: actions/setup-go@v3 with: go-version: ${{ matrix.go-version }} check-latest: true cache: true - if: ${{ matrix.os == 'ubuntu-latest' }} name: Cache libax25 id: cache-libax25 uses: actions/cache@v3 env: cache-name: cache-libax25 with: path: .build key: ${{ runner.os }}-build-${{ env.cache-name }}-${{ matrix.go-version }}-${{ hashFiles('make.bash') }} restore-keys: ${{ runner.os }}-build-${{ env.cache-name }}-${{ matrix.go-version }}- - if: ${{ matrix.os == 'ubuntu-latest' && steps.cache-libax25.outputs.cache-hit != 'true' }} name: Setup libax25 run: ./make.bash libax25 - name: Display Go version run: go version - name: Vet run: go vet ./... - name: Build run: ./make.bash pat-0.15.1/.gitignore000066400000000000000000000000421453425652100143550ustar00rootroot00000000000000.build/ pat pat*.pkg docker-data/ pat-0.15.1/.gitmodules000066400000000000000000000000001453425652100145340ustar00rootroot00000000000000pat-0.15.1/CONTRIBUTING.md000066400000000000000000000062511453425652100146260ustar00rootroot00000000000000# Contributing to Pat We welcome contributions to Pat of any kind including documentation, tutorials, bug reports, issues, feature requests, feature implementation, pull requests, answering questions on the mailing list, helping to manage issues, etc. If you have any questions about how to contribute or what to contribute, please ask on the [pat-users](https://groups.google.com/group/pat-users) list. ## Issue tracker Guidelines We use github's [issue tracker](https://github.com/la5nta/pat/issues) for keeping track of bugs, features and technical development discussions. To keep the issue tracker nice and tidy, we ask for the following: - Keep one issue per topic: - Don't report multiple bugs in the same issue unless they closely relates to each other. - Open one issue per feature request. - When reporting a bug, please add the following: - Output of pat version (including the SHA). - Operating system and architecture. - What you expected to happen. - What actually happened (including full stack trace and/or error message). - Issues should not be closed until they are either discarded or deployed. This means that code changing issues should not be closed until the changes have been merged to the master branch. ## Code Contribution Guideline We welcome your contributions. To make the process as seamless as possible, we ask for the following: - Go ahead and fork the project and make your changes. We encourage pull requests to discuss code changes. - When you’re ready to create a pull request, be sure to: - Run `go fmt` - Consider squashing your commits into a single commit. `git rebase -i`. It's okay to force update your pull request. - **Write a good commit message.** This [blog article](http://chris.beams.io/posts/git-commit/) is a good resource for learning how to write good commit messages, the most important part being that each commit message should have a title/subject in imperative mood starting with a capital letter and no trailing period: *"Return error on wrong use of the Paginator"*, **NOT** *"returning some error."* Also, if your commit references one or more GitHub issues, always end your commit message body with *See #1234* or *Fixes #1234*. Replace *1234* with the GitHub issue ID. The last example will close the issue when the commit is merged into *master*. - Make sure `go test ./...` passes, and `go build` completes. Our [Travis CI loop](https://app.travis-ci.com/github/la5nta/pat) (Linux and OS X) will catch most things that are missing. ## The release process New releases of Pat is done by these steps: 1. All issues targeted by the next release are moved into a milestone with the corresponding version name. 2. A release/*-branch is prepared and VERSION.go is updated. 3. A pull request to *master* is opened. 4. The release-branch is built and tested on *all targeted platforms*. 5. If all status checks (Travis CI) passes, the release-branch is merged into *master* and tagged. 6. Issues in the targeted milestone is either closed or moved to another milestone. The milestone is closed. 7. The various binary packages are built and uploaded to [releases/](https://github.com/la5nta/Pat/releases). pat-0.15.1/Dockerfile000066400000000000000000000012111453425652100143560ustar00rootroot00000000000000FROM golang:alpine as builder RUN apk add --no-cache git ca-certificates WORKDIR /src ADD go.mod go.sum ./ RUN go mod download ADD . . RUN go build -o /src/pat FROM scratch LABEL org.opencontainers.image.source=https://github.com/la5nta/pat LABEL org.opencontainers.image.description="Pat - A portable Winlink client for amateur radio email" LABEL org.opencontainers.image.licenses=MIT COPY --from=builder /etc/ssl/certs /etc/ssl/certs COPY --from=builder /src/pat /bin/pat USER 65534:65534 WORKDIR /app ENV XDG_CONFIG_HOME=/app ENV XDG_DATA_HOME=/app ENV XDG_STATE_HOME=/app ENV PAT_HTTPADDR=:8080 EXPOSE 8080 ENTRYPOINT ["/bin/pat"] CMD ["http"] pat-0.15.1/LICENSE000066400000000000000000000021121453425652100133720ustar00rootroot00000000000000The MIT License (MIT) Copyright (c) 2020 Martin Hebnes Pedersen (LA5NTA) Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. pat-0.15.1/README.md000066400000000000000000000066551453425652100136640ustar00rootroot00000000000000 [![Build status](https://github.com/la5nta/pat/actions/workflows/go.yaml/badge.svg)](https://github.com/la5nta/pat/actions) [![Go Report Card](https://goreportcard.com/badge/github.com/la5nta/pat)](https://goreportcard.com/report/github.com/la5nta/pat) [![Liberapay Patreons](http://img.shields.io/liberapay/patrons/la5nta.svg?logo=liberapay)](https://liberapay.com/la5nta) ## Overview Pat is a cross platform Winlink client with basic messaging capabilities. It is the primary sandbox/prototype application for the [wl2k-go](https://github.com/la5nta/wl2k-go) project, and provides both a command line interface and a responsive (mobile-friendly) web interface. It is mainly developed for Linux, but is also known to run on OS X, Windows and Android. #### Features * Message composer/reader (basic mailbox functionality). * Auto-shrink image attachments. * Post position reports with location from local GPS, browser location or manual entry. * Rig control (using hamlib). * CRON-like syntax for execution of scheduled commands (e.g. QSY or connect). * Built in http-server with web interface (mobile friendly). * Git style command line interface. * Listen for P2P connections using multiple modes concurrently. * AX.25, telnet, PACTOR and ARDOP support. * Experimental gzip message compression (See "Gzip experiment" below). ##### Example ``` martinhpedersen@duo:~$ pat interactive > listen winmor,telnet-p2p,ax25 2015/02/03 10:33:10 Listening for incoming traffic (winmor,telnet-p2p,ax25)... > connect winmor:///LA3F 2015/02/03 10:34:28 Connecting to winmor:LA3F... 2015/02/03 10:34:33 Connected to WINMOR:LA3F RMS Trimode 1.3.3.0 Follo.SE Oslo. Pactor & Winmor Hybrid Gateway LA5NTA has 117 minutes remaining with LA3F [WL2K-2.8.4.8-B2FWIHJM$] Wien CMS via LA3F > >FF FC EM FOYNU8AKXX59 260 221 0 F> 68 1 proposal(s) received Accepting FOYNU8AKXX59 Receiving [//WL2K test til linux] [offset 0] >FF FQ Waiting for remote node to close the connection... > _ ``` ### Gzip experiment Gzip message compression has been added as an experimental B2F extension. The extension is implemented as a backwards compatible alternative to the ancient LZHUF compression. This experiment is enabled by default and sessions between two Pat nodes (or other software supporting this B2F extension) will use gzip compression when transferring messages. For more information, see . ## Copyright/License Copyright (c) 2020 Martin Hebnes Pedersen LA5NTA ### Contributors (alphabetical) * DL1THM - Torsten Harenberg * HB9GPA - Matthias Renner * K0RET - Ryan Turner * K0SWE - Chris Keller * KD8DRX - Will Davidson * KE8HMG - Andrew Huebner * KI7RMJ - Rainer Grosskopf * LA3QMA - Kai Günter Brandt * LA4TTA - Erlend Grimseid * LA5NTA - Martin Hebnes Pedersen * N2YGK - Alan Crosswell * VE7GNU - Doug Collinge * W6IPA - JC Martin * WY2K - Benjamin Seidenberg ## Thanks to The JNOS developers for the properly maintained lzhuf implementation, as well as the original author Haruyasu Yoshizaki. The paclink-unix team (Nicholas S. Castellano N2QZ and others) - reference implementation Amateur Radio Safety Foundation, Inc. - The Winlink 2000 project F6FBB Jean-Paul ROUBELAT - the FBB forwarding protocol _Pat/wl2k-go is not affiliated with The Winlink Development Team nor the Winlink 2000 project [http://winlink.org]._ pat-0.15.1/cfg/000077500000000000000000000000001453425652100131305ustar00rootroot00000000000000pat-0.15.1/cfg/ax25_engine.go000066400000000000000000000010251453425652100155610ustar00rootroot00000000000000package cfg import ( "encoding/json" "fmt" ) const ( AX25EngineAGWPE AX25Engine = "agwpe" AX25EngineLinux = "linux" AX25EngineSerialTNC = "serial-tnc" ) type AX25Engine string func (a *AX25Engine) UnmarshalJSON(p []byte) error { var str string if err := json.Unmarshal(p, &str); err != nil { return err } switch v := AX25Engine(str); v { case AX25EngineLinux, AX25EngineAGWPE, AX25EngineSerialTNC: *a = v return nil default: return fmt.Errorf("invalid AX.25 engine '%s'", v) } } pat-0.15.1/cfg/ax25_engine_libax25.go000066400000000000000000000001621453425652100171100ustar00rootroot00000000000000//go:build libax25 // +build libax25 package cfg func DefaultAX25Engine() AX25Engine { return AX25EngineLinux } pat-0.15.1/cfg/ax25_engine_other.go000066400000000000000000000001641453425652100167650ustar00rootroot00000000000000//go:build !libax25 // +build !libax25 package cfg func DefaultAX25Engine() AX25Engine { return AX25EngineAGWPE } pat-0.15.1/cfg/config.go000066400000000000000000000254421453425652100147330ustar00rootroot00000000000000// Copyright 2016 Martin Hebnes Pedersen (LA5NTA). All rights reserved. // Use of this source code is governed by the MIT-license that can be // found in the LICENSE file. package cfg import ( "encoding/json" "fmt" "net" "strconv" "strings" "github.com/la5nta/wl2k-go/transport/ardop" ) const ( PlaceholderMycall = "{mycall}" ) type AuxAddr struct { Address string Password *string } func (a AuxAddr) MarshalJSON() ([]byte, error) { if a.Password == nil { return json.Marshal(a.Address) } return json.Marshal(a.Address + ":" + *a.Password) } func (a *AuxAddr) UnmarshalJSON(p []byte) error { var str string if err := json.Unmarshal(p, &str); err != nil { return err } parts := strings.SplitN(str, ":", 2) a.Address = parts[0] if len(parts) > 1 { a.Password = &parts[1] } return nil } type Config struct { // This station's callsign. MyCall string `json:"mycall"` // Secure login password used when a secure login challenge is received. // // The user is prompted if this is undefined. SecureLoginPassword string `json:"secure_login_password"` // Auxiliary callsigns to fetch email on behalf of. // // Passwords can optionally be specified by appending :MYPASS (e.g. EMCOMM-1:MyPassw0rd). // If no password is specified, the SecureLoginPassword is used. AuxAddrs []AuxAddr `json:"auxiliary_addresses"` // Maidenhead grid square (e.g. JP20qe). Locator string `json:"locator"` // List of service codes for rmslist (defaults to PUBLIC) ServiceCodes []string `json:"service_codes"` // Default HTTP listen address (for web UI). // // Use ":8080" to listen on any device, port 8080. HTTPAddr string `json:"http_addr"` // Handshake comment lines sent to remote node on incoming connections. // // Example: ["QTH: Hagavik, Norway. Operator: Martin", "Rig: FT-897 with Signalink USB"] MOTD []string `json:"motd"` // Connect aliases // // Example: {"LA1B-10": "ax25:///LD5GU/LA1B-10", "LA1B": "ardop://LA3F?freq=5350"} // Any occurrence of the substring "{mycall}" will be replaced with user's callsign. ConnectAliases map[string]string `json:"connect_aliases"` // Methods to listen for incoming P2P connections by default. // // Example: ["ax25", "telnet", "ardop"] Listen []string `json:"listen"` // Hamlib rigs available (with reference name) for ptt and frequency control. HamlibRigs map[string]HamlibConfig `json:"hamlib_rigs"` AX25 AX25Config `json:"ax25"` // See AX25Config. AX25Linux AX25LinuxConfig `json:"ax25_linux"` // See AX25LinuxConfig. AGWPE AGWPEConfig `json:"agwpe"` // See AGWPEConfig. SerialTNC SerialTNCConfig `json:"serial-tnc"` // See SerialTNCConfig. Ardop ArdopConfig `json:"ardop"` // See ArdopConfig. Pactor PactorConfig `json:"pactor"` // See PactorConfig. Telnet TelnetConfig `json:"telnet"` // See TelnetConfig. VaraHF VaraConfig `json:"varahf"` // See VaraConfig. VaraFM VaraConfig `json:"varafm"` // See VaraConfig. // See GPSdConfig. GPSd GPSdConfig `json:"gpsd"` // Legacy support for old config files only. This field is deprecated! // Please use "Addr" field in GPSd config struct (GPSd.Addr) GPSdAddrLegacy string `json:"gpsd_addr,omitempty"` // Command schedule (cron-like syntax). // // Examples: // # Connect to telnet once every hour // "@hourly": "connect telnet" // // # Change ardop listen frequency based on hour of day // "00 10 * * *": "freq ardop:7350.000", # 40m from 10:00 // "00 18 * * *": "freq ardop:5347.000", # 60m from 18:00 // "00 22 * * *": "freq ardop:3602.000" # 80m from 22:00 Schedule map[string]string `json:"schedule"` // By default, Pat posts your callsign and running version to the Winlink CMS Web Services // // Set to true if you don't want your information sent. VersionReportingDisabled bool `json:"version_reporting_disabled"` } type HamlibConfig struct { // The network type ("serial" or "tcp"). Use 'tcp' for rigctld (default). // // (For serial support: build with "-tags libhamlib".) Network string `json:"network,omitempty"` // The rig address. // // For tcp (rigctld): "address:port" (e.g. localhost:4532). // For serial: "/path/to/tty?model=&baudrate=" (e.g. /dev/ttyS0?model=123&baudrate=4800). Address string `json:"address,omitempty"` // The rig's VFO to control ("A" or "B"). If empty, the current active VFO is used. VFO string `json:"VFO"` } type ArdopConfig struct { // Network address of the Ardop TNC (e.g. localhost:8515). Addr string `json:"addr"` // Default/listen ARQ bandwidth (200/500/1000/2000 MAX/FORCED). ARQBandwidth ardop.Bandwidth `json:"arq_bandwidth"` // (optional) Reference name to the Hamlib rig to control frequency and ptt. Rig string `json:"rig"` // Set to true if hamlib should control PTT (SignaLink=false, most rigexpert=true). PTTControl bool `json:"ptt_ctrl"` // (optional) Send ID frame at a regular interval when the listener is active (unit is seconds) BeaconInterval int `json:"beacon_interval"` // Send FSK CW ID after an ID frame. CWID bool `json:"cwid_enabled"` } type VaraConfig struct { // Network host of the VARA modem (defaults to localhost:8300). Addr string `json:"addr"` // Default/listen bandwidth (HF: 500/2300/2750 Hz). Bandwidth int `json:"bandwidth"` // (optional) Reference name to the Hamlib rig to control frequency and ptt. Rig string `json:"rig"` // Set to true if hamlib should control PTT (SignaLink=false, most rigexpert=true). PTTControl bool `json:"ptt_ctrl"` } // UnmarshalJSON implements VaraConfig JSON unmarshalling with support for legacy format. func (v *VaraConfig) UnmarshalJSON(b []byte) error { type newFormat VaraConfig legacy := struct { newFormat Host string `json:"host"` CmdPort int `json:"cmdPort"` DataPort int `json:"dataPort"` }{} if err := json.Unmarshal(b, &legacy); err != nil { return err } if legacy.newFormat.Addr == "" && legacy.Host != "" { legacy.newFormat.Addr = fmt.Sprintf("%s:%d", legacy.Host, legacy.CmdPort) } *v = VaraConfig(legacy.newFormat) if !v.IsZero() && v.CmdPort() <= 0 { return fmt.Errorf("invalid addr format") } return nil } func (v VaraConfig) IsZero() bool { return v == (VaraConfig{}) } func (v VaraConfig) Host() string { host, _, _ := net.SplitHostPort(v.Addr) return host } func (v VaraConfig) CmdPort() int { _, portStr, _ := net.SplitHostPort(v.Addr) port, _ := strconv.Atoi(portStr) return port } func (v VaraConfig) DataPort() int { return v.CmdPort() + 1 } type PactorConfig struct { // Path/port to TNC device (e.g. /dev/ttyUSB0 or COM1). Path string `json:"path"` // Baudrate for the serial port (e.g. 57600). Baudrate int `json:"baudrate"` // (optional) Reference name to the Hamlib rig for frequency control. Rig string `json:"rig"` // (optional) Path to custom TNC initialization script. InitScript string `json:"custom_init_script"` } type TelnetConfig struct { // Network address (and port) to listen for telnet-p2p connections (e.g. :8774). ListenAddr string `json:"listen_addr"` // Telnet-p2p password. Password string `json:"password"` } type SerialTNCConfig struct { // Serial port (e.g. /dev/ttyUSB0 or COM1). Path string `json:"path"` // SerialBaud is the serial port's baudrate (e.g. 57600). SerialBaud int `json:"serial_baud"` // HBaud is the the packet connection's baudrate (1200 or 9600). HBaud int `json:"hbaud"` // Baudrate of the packet connection. // Deprecated: Use HBaud instead. BaudrateLegacy int `json:"baudrate,omitempty"` // Type of TNC (currently only 'kenwood'). Type string `json:"type"` // (optional) Reference name to the Hamlib rig for frequency control. Rig string `json:"rig"` } type AGWPEConfig struct { // The TCP address of the TNC. Addr string `json:"addr"` // The AGWPE "radio port" (0-3). RadioPort int `json:"radio_port"` } type AX25Config struct { // The AX.25 engine to be used. // // Valid options are: // - linux // - agwpe // - serial-tnc Engine AX25Engine `json:"engine"` // (optional) Reference name to the Hamlib rig for frequency control. Rig string `json:"rig"` // DEPRECATED: See AX25Linux.Port. AXPort string `json:"port,omitempty"` // Optional beacon when listening for incoming packet-p2p connections. Beacon BeaconConfig `json:"beacon"` } type AX25LinuxConfig struct { // axport to use (as defined in /etc/ax25/axports). Only applicable to ax25 engine 'linux'. Port string `json:"port"` } type BeaconConfig struct { // Beacon interval in seconds (e.g. 3600 for once every 1 hour) Every int `json:"every"` // (seconds) // Beacon data/message Message string `json:"message"` // Beacon destination (e.g. IDENT) Destination string `json:"destination"` } type GPSdConfig struct { // Enable GPSd proxy for HTTP (web GUI) // // Caution: Your GPS position will be accessible to any network device able to access Pat's HTTP interface. EnableHTTP bool `json:"enable_http"` // Allow Winlink forms to use GPSd for aquiring your position. // // Caution: Your current GPS position will be automatically injected, without your explicit consent, into forms requesting such information. AllowForms bool `json:"allow_forms"` // Use server time instead of timestamp provided by GPSd (e.g for older GPS device with week roll-over issue). UseServerTime bool `json:"use_server_time"` // Address and port of GPSd server (e.g. localhost:2947). Addr string `json:"addr"` } var DefaultConfig = Config{ MOTD: []string{"Open source Winlink client - getpat.io"}, AuxAddrs: []AuxAddr{}, ServiceCodes: []string{"PUBLIC"}, ConnectAliases: map[string]string{ "telnet": "telnet://{mycall}:CMSTelnet@cms.winlink.org:8772/wl2k", }, Listen: []string{}, HTTPAddr: "localhost:8080", AX25: AX25Config{ Engine: DefaultAX25Engine(), Beacon: BeaconConfig{ Every: 3600, Message: "Winlink P2P", Destination: "IDENT", }, }, AX25Linux: AX25LinuxConfig{ Port: "wl2k", }, SerialTNC: SerialTNCConfig{ Path: "/dev/ttyUSB0", SerialBaud: 9600, HBaud: 1200, Type: "Kenwood", }, AGWPE: AGWPEConfig{ Addr: "localhost:8000", RadioPort: 0, }, Ardop: ArdopConfig{ Addr: "localhost:8515", ARQBandwidth: ardop.Bandwidth500Max, CWID: true, }, Pactor: PactorConfig{ Path: "/dev/ttyUSB0", Baudrate: 57600, }, Telnet: TelnetConfig{ ListenAddr: ":8774", Password: "", }, VaraHF: VaraConfig{ Addr: "localhost:8300", Bandwidth: 2300, }, VaraFM: VaraConfig{ Addr: "localhost:8300", }, GPSd: GPSdConfig{ EnableHTTP: false, // Default to false to help protect privacy of unknowing users (see github.com//issues/146) AllowForms: false, // Default to false to help protect location privacy of unknowing users UseServerTime: false, Addr: "localhost:2947", // Default listen address for GPSd }, GPSdAddrLegacy: "", Schedule: map[string]string{}, HamlibRigs: map[string]HamlibConfig{}, } pat-0.15.1/cli_composer.go000066400000000000000000000203021453425652100153730ustar00rootroot00000000000000// Copyright 2016 Martin Hebnes Pedersen (LA5NTA). All rights reserved. // Use of this source code is governed by the MIT-license that can be // found in the LICENSE file. // A portable Winlink client for amateur radio email. package main import ( "bufio" "bytes" "context" "fmt" "io" "io/ioutil" "log" "os" "os/exec" "path/filepath" "strings" "time" "github.com/la5nta/pat/internal/buildinfo" "github.com/la5nta/wl2k-go/fbb" "github.com/spf13/pflag" ) func composeMessageHeader(replyMsg *fbb.Message) *fbb.Message { msg := fbb.NewMessage(fbb.Private, fOptions.MyCall) fmt.Printf(`From [%s]: `, fOptions.MyCall) from := readLine() if from == "" { from = fOptions.MyCall } msg.SetFrom(from) fmt.Print(`To`) if replyMsg != nil { fmt.Printf(" [%s]", replyMsg.From()) } fmt.Printf(": ") to := readLine() if to == "" && replyMsg != nil { msg.AddTo(replyMsg.From().String()) } else { for _, addr := range strings.FieldsFunc(to, SplitFunc) { msg.AddTo(addr) } } ccCand := make([]fbb.Address, 0) if replyMsg != nil { for _, addr := range append(replyMsg.To(), replyMsg.Cc()...) { if !addr.EqualString(fOptions.MyCall) { ccCand = append(ccCand, addr) } } } fmt.Printf("Cc") if replyMsg != nil { fmt.Printf(" %s", ccCand) } fmt.Print(`: `) cc := readLine() if cc == "" && replyMsg != nil { for _, addr := range ccCand { msg.AddCc(addr.String()) } } else { for _, addr := range strings.FieldsFunc(cc, SplitFunc) { msg.AddCc(addr) } } switch len(msg.Receivers()) { case 1: fmt.Print("P2P only [y/N]: ") ans := readLine() if strings.EqualFold("y", ans) { msg.Header.Set("X-P2POnly", "true") } case 0: fmt.Println("Message must have at least one recipient") os.Exit(1) } fmt.Print(`Subject: `) if replyMsg != nil { subject := strings.TrimSpace(strings.TrimPrefix(replyMsg.Subject(), "Re:")) subject = fmt.Sprintf("Re:%s", subject) fmt.Println(subject) msg.SetSubject(subject) } else { msg.SetSubject(readLine()) } // A message without subject is not valid, so let's use a sane default if msg.Subject() == "" { msg.SetSubject("") } return msg } func composeMessage(ctx context.Context, args []string) { set := pflag.NewFlagSet("compose", pflag.ExitOnError) // From default is --mycall but it can be overriden with -r from := set.StringP("from", "r", fOptions.MyCall, "") subject := set.StringP("subject", "s", "", "") attachments := set.StringArrayP("attachment", "a", nil, "") ccs := set.StringArrayP("cc", "c", nil, "") p2pOnly := set.BoolP("p2p-only", "", false, "") set.Parse(args) // Remaining args are recipients recipients := []string{} for _, r := range set.Args() { // Filter out empty args (this actually happens) if strings.TrimSpace(r) == "" { continue } recipients = append(recipients, r) } // Check if any args are set. If so, go non-interactive // Otherwise, interactive if (len(*subject) + len(*attachments) + len(*ccs) + len(recipients)) > 0 { noninteractiveComposeMessage(*from, *subject, *attachments, *ccs, recipients, *p2pOnly) } else { interactiveComposeMessage(nil) } } func noninteractiveComposeMessage(from string, subject string, attachments []string, ccs []string, recipients []string, p2pOnly bool) { // We have to verify the args here. Follow the same pattern as main() // We'll allow a missing recipient if CC is present (or vice versa) if len(recipients)+len(ccs) <= 0 { fmt.Fprint(os.Stderr, "ERROR: Missing recipients in non-interactive mode!\n") os.Exit(1) } // Subject is optional. Print a mailx style warning if subject == "" { fmt.Fprint(os.Stderr, "Warning: missing subject; hope that's OK\n") } msg := fbb.NewMessage(fbb.Private, fOptions.MyCall) msg.SetFrom(from) for _, to := range recipients { msg.AddTo(to) } for _, cc := range ccs { msg.AddCc(cc) } msg.SetSubject(subject) // Handle Attachments. Since we're not interactive, treat errors as fatal so the user can fix for _, filename := range attachments { file, err := readAttachment(filename) if err != nil { fmt.Fprint(os.Stderr, err.Error()+"\nAborting! (Message not posted)\n") os.Exit(1) } msg.AddFile(file) } // Read the message body from stdin body, _ := ioutil.ReadAll(os.Stdin) if len(body) == 0 { // Yeah, I've spent way too much time using mail(1) fmt.Fprint(os.Stderr, "Null message body; hope that's ok\n") } msg.SetBody(string(body)) if p2pOnly { msg.Header.Set("X-P2POnly", "true") } postMessage(msg) } // This is currently an alias for interactiveComposeMessage but keeping as a separate // call path for the future func composeReplyMessage(replyMsg *fbb.Message) { interactiveComposeMessage(replyMsg) } func interactiveComposeMessage(replyMsg *fbb.Message) { msg := composeMessageHeader(replyMsg) // Read body fmt.Printf(`Press ENTER to start composing the message body. `) readLine() f, err := ioutil.TempFile("", strings.ToLower(fmt.Sprintf("%s_new_%d.txt", buildinfo.AppName, time.Now().Unix()))) if err != nil { log.Fatalf("Unable to prepare temporary file for body: %s", err) } if replyMsg != nil { fmt.Fprintf(f, "--- %s %s wrote: ---\n", replyMsg.Date(), replyMsg.From().Addr) body, _ := replyMsg.Body() orig := ">" + strings.ReplaceAll( strings.TrimSpace(body), "\n", "\n>", ) + "\n" f.Write([]byte(orig)) f.Sync() } // Windows fix: Avoid 'cannot access the file because it is being used by another process' error. // Close the file before opening the editor. f.Close() cmd := exec.Command(EditorName(), f.Name()) cmd.Stdin, cmd.Stdout, cmd.Stderr = os.Stdin, os.Stdout, os.Stderr if err := cmd.Run(); err != nil { log.Fatalf("Unable to start body editor: %s", err) } f, err = os.OpenFile(f.Name(), os.O_RDWR, 0o666) if err != nil { log.Fatalf("Unable to read temporary file from editor: %s", err) } var buf bytes.Buffer io.Copy(&buf, f) msg.SetBody(buf.String()) f.Close() os.Remove(f.Name()) // An empty message body is illegal. Let's set a sane default. if msg.BodySize() == 0 { msg.SetBody("\n") } // END Read body fmt.Print("\n") for { fmt.Print(`Attachment [empty when done]: `) path := readLine() if path == "" { break } file, err := readAttachment(path) if err != nil { log.Println(err) continue } msg.AddFile(file) } fmt.Println(msg) postMessage(msg) } func readAttachment(path string) (*fbb.File, error) { f, err := os.Open(path) if err != nil { return nil, err } defer f.Close() name := filepath.Base(path) var resizeImage bool if isConvertableImageMediaType(name, "") { fmt.Print("This seems to be an image. Auto resize? [Y/n]: ") ans := readLine() resizeImage = ans == "" || strings.EqualFold("y", ans) } var data []byte data, err = ioutil.ReadAll(f) if resizeImage { data, err = convertImage(data) ext := filepath.Ext(name) name = name[:len(name)-len(ext)] + ".jpg" } return fbb.NewFile(name, data), err } var stdin *bufio.Reader func readLine() string { if stdin == nil { stdin = bufio.NewReader(os.Stdin) } str, _ := stdin.ReadString('\n') return strings.TrimSpace(str) } func composeFormReport(ctx context.Context, args []string) { var tmplPathArg string set := pflag.NewFlagSet("form", pflag.ExitOnError) set.StringVar(&tmplPathArg, "template", "ICS USA Forms/ICS213", "") set.Parse(args) msg := composeMessageHeader(nil) formMsg, err := formsMgr.ComposeForm(tmplPathArg, msg.Subject()) if err != nil { log.Printf("failed to compose message for template: %v", err) return } msg.SetSubject(formMsg.Subject) fmt.Println("================================================================") fmt.Print("To: ") fmt.Println(msg.To()) fmt.Print("Cc: ") fmt.Println(msg.Cc()) fmt.Print("From: ") fmt.Println(msg.From()) fmt.Println("Subject: " + msg.Subject()) fmt.Println(formMsg.Body) fmt.Println("================================================================") fmt.Println("Press ENTER to post this message in the outbox, Ctrl-C to abort.") fmt.Println("================================================================") readLine() msg.SetBody(formMsg.Body) if xml := formMsg.AttachmentXML; xml != "" { attachmentFile := fbb.NewFile(formMsg.AttachmentName, []byte(xml)) msg.AddFile(attachmentFile) } postMessage(msg) } pat-0.15.1/config.go000066400000000000000000000131121453425652100141630ustar00rootroot00000000000000// Copyright 2016 Martin Hebnes Pedersen (LA5NTA). All rights reserved. // Use of this source code is governed by the MIT-license that can be // found in the LICENSE file. package main import ( "encoding/json" "fmt" "log" "os" "path" "strings" "github.com/kelseyhightower/envconfig" "github.com/la5nta/pat/cfg" "github.com/la5nta/pat/internal/buildinfo" "github.com/la5nta/pat/internal/debug" ) func LoadConfig(cfgPath string, fallback cfg.Config) (config cfg.Config, err error) { config, err = ReadConfig(cfgPath) switch { case os.IsNotExist(err): config = fallback if err := WriteConfig(config, cfgPath); err != nil { return config, err } case err != nil: return config, err } // Environment variables overrides values from the config file if err := envconfig.Process(buildinfo.AppName, &config); err != nil { return config, err } // Environment variables for hamlib rigs (custom syntax not handled by envconfig) if err := readRigsFromEnv(&config.HamlibRigs); err != nil { return config, err } // Ensure the alias "telnet" exists if config.ConnectAliases == nil { config.ConnectAliases = make(map[string]string) } if _, exists := config.ConnectAliases["telnet"]; !exists { config.ConnectAliases["telnet"] = cfg.DefaultConfig.ConnectAliases["telnet"] } // TODO: Remove after some release cycles (2023-05-21) // Rewrite deprecated serial-tnc:// aliases to ax25-serial-tnc:// var deprecatedAliases []string for k, v := range config.ConnectAliases { if !strings.HasPrefix(v, MethodSerialTNCDeprecated+"://") { continue } deprecatedAliases = append(deprecatedAliases, k) config.ConnectAliases[k] = strings.Replace(v, MethodSerialTNCDeprecated, MethodAX25SerialTNC, 1) } if len(deprecatedAliases) > 0 { log.Printf("Alias(es) %s uses deprecated transport scheme %s://. Please use %s:// instead.", strings.Join(deprecatedAliases, ", "), MethodSerialTNCDeprecated, MethodAX25SerialTNC) } // Ensure ServiceCodes has a default value if len(config.ServiceCodes) == 0 { config.ServiceCodes = cfg.DefaultConfig.ServiceCodes } // Ensure we have a default AX.25 engine if config.AX25.Engine == "" { config.AX25.Engine = cfg.DefaultAX25Engine() } // Ensure we have a default AGWPE config if config.AGWPE == (cfg.AGWPEConfig{}) { config.AGWPE = cfg.DefaultConfig.AGWPE } // Ensure we have a default AX.25 Linux config if config.AX25Linux == (cfg.AX25LinuxConfig{}) { config.AX25Linux = cfg.DefaultConfig.AX25Linux } // TODO: Remove after some release cycles (2023-04-30) if v := config.AX25.AXPort; v != "" && v != config.AX25Linux.Port { log.Println("Using deprecated configuration option ax25.port. Please set ax25_linux.port instead.") config.AX25Linux.Port = v } // Ensure Pactor has a default value if config.Pactor == (cfg.PactorConfig{}) { config.Pactor = cfg.DefaultConfig.Pactor } // Ensure VARA FM and VARA HF has default values if config.VaraHF.IsZero() { config.VaraHF = cfg.DefaultConfig.VaraHF } if config.VaraFM.IsZero() { config.VaraFM = cfg.DefaultConfig.VaraFM } // Ensure GPSd has a default value if config.GPSd == (cfg.GPSdConfig{}) { config.GPSd = cfg.DefaultConfig.GPSd } // TODO: Remove after some release cycles (2019-09-29) if v := config.GPSdAddrLegacy; v != "" && v != config.GPSd.Addr { log.Println("Using deprecated configuration option gpsd_addr. Please set gpsd.addr instead.") config.GPSd.Addr = v } // Ensure SerialTNC has a default hbaud and serialbaud if config.SerialTNC.HBaud == 0 { config.SerialTNC.HBaud = cfg.DefaultConfig.SerialTNC.HBaud } if config.SerialTNC.SerialBaud == 0 { config.SerialTNC.SerialBaud = cfg.DefaultConfig.SerialTNC.SerialBaud } // Compatibility for the old baudrate field for serial-tnc if v := config.SerialTNC.BaudrateLegacy; v != 0 && v != config.SerialTNC.HBaud { // Since we changed the default value from 9600 to 1200, we can't warn about this without causing confusion. debug.Printf("Legacy serial_tnc.baudrate config detected (%d). Translating to serial_tnc.hbaud.", v) config.SerialTNC.HBaud = v } return config, nil } // readRigsFromEnv reads hamlib rigs config from environment. // Syntax: PAT_HAMLIB_RIGS_{rig name}_{ATTRIBUTE} // _{ATTRIBUTE} is optional (defaults to _ADDRESS). // Examples: // - PAT_HAMLIB_RIGS_rig1_NETWORK=tcp // - PAT_HAMLIB_RIGS_rig1_ADDRESS=localhost:8080 // - PAT_HAMLIB_RIGS_rig1_VFO=A // - PAT_HAMLIB_RIGS_rig2=localhost:8080 func readRigsFromEnv(rigs *map[string]cfg.HamlibConfig) error { prefix := strings.ToUpper(buildinfo.AppName) + "_HAMLIB_RIGS_" for _, env := range os.Environ() { attribute, value, _ := strings.Cut(env, "=") if !strings.HasPrefix(attribute, prefix) { continue } attribute = strings.TrimPrefix(attribute, prefix) name, attribute, _ := strings.Cut(attribute, "_") if *rigs == nil { *rigs = make(map[string]cfg.HamlibConfig) } rig := (*rigs)[name] switch attribute { case "ADDRESS", "": rig.Address = value case "NETWORK": rig.Network = value case "VFO": rig.VFO = value default: return fmt.Errorf("invalid attribute '%s' for rig '%s'", attribute, name) } (*rigs)[name] = rig } return nil } func ReadConfig(path string) (config cfg.Config, err error) { data, err := os.ReadFile(path) if err != nil { return } err = json.Unmarshal(data, &config) return } func WriteConfig(config cfg.Config, filePath string) error { b, err := json.MarshalIndent(config, "", " ") if err != nil { return err } // Add trailing new-line b = append(b, '\n') // Ensure path dir is available os.Mkdir(path.Dir(filePath), os.ModePerm|os.ModeDir) return os.WriteFile(filePath, b, 0o600) } pat-0.15.1/config_test.go000066400000000000000000000030101453425652100152160ustar00rootroot00000000000000package main import ( "os" "strings" "testing" "github.com/la5nta/pat/cfg" ) func TestReadRigsFromEnv(t *testing.T) { const prefix = "PAT_HAMLIB_RIGS" unset := func() { for _, env := range os.Environ() { key, _, _ := strings.Cut(env, "=") if strings.HasPrefix(key, prefix) { os.Unsetenv(key) } } } t.Run("simple", func(t *testing.T) { defer unset() var rigs map[string]cfg.HamlibConfig os.Setenv(prefix+"_rig", "localhost:4532") if err := readRigsFromEnv(&rigs); err != nil { t.Fatal(err) } if got := rigs["rig"]; (got != cfg.HamlibConfig{Address: "localhost:4532"}) { t.Fatalf("Got unexpected config: %#v", got) } }) t.Run("with VFO", func(t *testing.T) { defer unset() var rigs map[string]cfg.HamlibConfig os.Setenv(prefix+"_rig", "localhost:4532") os.Setenv(prefix+"_rig_VFO", "A") if err := readRigsFromEnv(&rigs); err != nil { t.Fatal(err) } if got := rigs["rig"]; (got != cfg.HamlibConfig{Address: "localhost:4532", VFO: "A"}) { t.Fatalf("Got unexpected config: %#v", got) } }) t.Run("full", func(t *testing.T) { defer unset() var rigs map[string]cfg.HamlibConfig os.Setenv(prefix+"_rig_ADDRESS", "/dev/ttyS0") os.Setenv(prefix+"_rig_NETWORK", "serial") os.Setenv(prefix+"_rig_VFO", "B") if err := readRigsFromEnv(&rigs); err != nil { t.Fatal(err) } expect := cfg.HamlibConfig{ Address: "/dev/ttyS0", Network: "serial", VFO: "B", } if got := rigs["rig"]; got != expect { t.Fatalf("Got unexpected config: %#v", got) } }) } pat-0.15.1/connect.go000066400000000000000000000230411453425652100143510ustar00rootroot00000000000000// Copyright 2016 Martin Hebnes Pedersen (LA5NTA). All rights reserved. // Use of this source code is governed by the MIT-license that can be // found in the LICENSE file. package main import ( "context" "errors" "fmt" "log" "strconv" "strings" "time" "github.com/la5nta/pat/cfg" "github.com/la5nta/pat/internal/debug" "github.com/harenber/ptc-go/v2/pactor" "github.com/la5nta/wl2k-go/transport" "github.com/la5nta/wl2k-go/transport/ardop" "github.com/la5nta/wl2k-go/transport/ax25/agwpe" "github.com/n8jja/Pat-Vara/vara" // Register stateless dialers _ "github.com/la5nta/wl2k-go/transport/ax25" _ "github.com/la5nta/wl2k-go/transport/telnet" ) var ( dialing *transport.URL // The connect URL currently being dialed (if any) adTNC *ardop.TNC // Pointer to the ARDOP TNC used by Listen and Connect agwpeTNC *agwpe.TNCPort // Pointer to the AGWPE TNC combined TNC and Port pModem *pactor.Modem varaHFModem *vara.Modem varaFMModem *vara.Modem // Context cancellation function for aborting while dialing. dialCancelFunc func() = func() {} ) func hasSSID(str string) bool { return strings.Contains(str, "-") } func connectAny(connectStr ...string) bool { for _, str := range connectStr { if Connect(str) { return true } } return false } func Connect(connectStr string) (success bool) { if connectStr == "" { return false } else if aliased, ok := config.ConnectAliases[connectStr]; ok { return Connect(aliased) } // Hack around bug in frontend which may occur if the status updates too quickly. if websocketHub != nil { defer func() { time.Sleep(time.Second); websocketHub.UpdateStatus() }() } debug.Printf("connectStr: %s", connectStr) url, err := transport.ParseURL(connectStr) if err != nil { log.Println(err) return false } // TODO: Remove after some release cycles (2023-05-21) // Rewrite legacy serial-tnc scheme. if url.Scheme == MethodSerialTNCDeprecated { log.Printf("Transport scheme %s:// is deprecated, use %s:// instead.", MethodSerialTNCDeprecated, MethodAX25SerialTNC) url.Scheme = MethodAX25SerialTNC } // Rewrite the generic ax25:// scheme to use a specified AX.25 engine. if url.Scheme == MethodAX25 { url.Scheme = defaultAX25Method() } // Init TNCs switch url.Scheme { case MethodAX25AGWPE: if err := initAGWPE(); err != nil { log.Println(err) return } case MethodArdop: if err := initArdopTNC(); err != nil { log.Println(err) return } case MethodPactor: ptCmdInit := "" if val, ok := url.Params["init"]; ok { ptCmdInit = strings.Join(val, "\n") } if err := initPactorModem(ptCmdInit); err != nil { log.Println(err) return } case MethodVaraHF: if err := initVaraHFModem(); err != nil { log.Println(err) return } case MethodVaraFM: if err := initVaraFMModem(); err != nil { log.Println(err) return } } // Set default userinfo (mycall) if url.User == nil { url.SetUser(fOptions.MyCall) } // Set default host interface address if url.Host == "" { switch url.Scheme { case MethodAX25Linux: url.Host = config.AX25Linux.Port case MethodAX25SerialTNC: url.Host = config.SerialTNC.Path if hbaud := config.SerialTNC.HBaud; hbaud > 0 { url.Params.Set("hbaud", fmt.Sprint(hbaud)) } if sbaud := config.SerialTNC.SerialBaud; sbaud > 0 { url.Params.Set("serial_baud", fmt.Sprint(sbaud)) } } } // Radio Only? radioOnly := fOptions.RadioOnly if v := url.Params.Get("radio_only"); v != "" { radioOnly, _ = strconv.ParseBool(v) } if radioOnly { if hasSSID(fOptions.MyCall) { log.Println("Radio Only does not support callsign with SSID") return } if strings.HasPrefix(url.Scheme, MethodAX25) { log.Printf("Radio-Only is not available for %s", url.Scheme) return } url.SetUser(url.User.Username() + "-T") } // QSY var revertFreq func() if freq := url.Params.Get("freq"); freq != "" { revertFreq, err = qsy(url.Scheme, freq) if err != nil { log.Printf("Unable to QSY: %s", err) return } defer revertFreq() } var currFreq Frequency if vfo, _, ok, _ := VFOForTransport(url.Scheme); ok { f, _ := vfo.GetFreq() currFreq = Frequency(f) } // Wait for a clear channel switch url.Scheme { case MethodArdop: waitBusy(adTNC) } ctx, cancel := context.WithCancel(context.Background()) dialCancelFunc = func() { dialing = nil; cancel() } defer dialCancelFunc() // Signal web gui that we are dialing a connection dialing = url websocketHub.UpdateStatus() log.Printf("Connecting to %s (%s)...", url.Target, url.Scheme) conn, err := transport.DialURLContext(ctx, url) // Signal web gui that we are no longer dialing dialing = nil websocketHub.UpdateStatus() eventLog.LogConn("connect "+connectStr, currFreq, conn, err) switch { case errors.Is(err, context.Canceled): log.Printf("Connect cancelled") return case err != nil: log.Printf("Unable to establish connection to remote: %s", err) return } err = exchange(conn, url.Target, false) if err != nil { log.Printf("Exchange failed: %s", err) } else { log.Println("Disconnected.") success = true } return } func qsy(method, addr string) (revert func(), err error) { noop := func() {} rig, rigName, ok, err := VFOForTransport(method) if err != nil { return noop, err } else if !ok { return noop, fmt.Errorf("hamlib rig '%s' not loaded", rigName) } log.Printf("QSY %s: %s", method, addr) _, oldFreq, err := setFreq(rig, addr) if err != nil { return noop, err } time.Sleep(3 * time.Second) return func() { time.Sleep(time.Second) log.Printf("QSX %s: %.3f", method, float64(oldFreq)/1e3) rig.SetFreq(oldFreq) }, nil } func waitBusy(b transport.BusyChannelChecker) { printed := false for b.Busy() { if !printed && fOptions.IgnoreBusy { log.Println("Ignoring busy channel!") break } else if !printed { log.Println("Waiting for clear channel...") printed = true } time.Sleep(300 * time.Millisecond) } } func initArdopTNC() error { if adTNC != nil && adTNC.Ping() == nil { return nil } if adTNC != nil { adTNC.Close() } var err error adTNC, err = ardop.OpenTCP(config.Ardop.Addr, fOptions.MyCall, config.Locator) if err != nil { return fmt.Errorf("ARDOP TNC initialization failed: %w", err) } if !config.Ardop.ARQBandwidth.IsZero() { if err := adTNC.SetARQBandwidth(config.Ardop.ARQBandwidth); err != nil { return fmt.Errorf("unable to set ARQ bandwidth for ardop TNC: %w", err) } } if err := adTNC.SetCWID(config.Ardop.CWID); err != nil { return fmt.Errorf("unable to configure CWID for ardop TNC: %w", err) } if v, err := adTNC.Version(); err != nil { return fmt.Errorf("ARDOP TNC initialization failed: %s", err) } else { log.Printf("ARDOP TNC (%s) initialized", v) } transport.RegisterDialer(MethodArdop, adTNC) if !config.Ardop.PTTControl { return nil } rig, ok := rigs[config.Ardop.Rig] if !ok { return fmt.Errorf("unable to set PTT rig '%s': Not defined or not loaded", config.Ardop.Rig) } adTNC.SetPTT(rig) return nil } func initPactorModem(cmdlineinit string) error { if pModem != nil { pModem.Close() } var err error pModem, err = pactor.OpenModem(config.Pactor.Path, config.Pactor.Baudrate, fOptions.MyCall, config.Pactor.InitScript, cmdlineinit) if err != nil || pModem == nil { return fmt.Errorf("pactor initialization failed: %w", err) } transport.RegisterDialer(MethodPactor, pModem) return nil } func initVaraHFModem() error { if varaHFModem != nil && varaHFModem.Ping() { return nil } if varaHFModem != nil { varaHFModem.Close() } m, err := initVaraModem(MethodVaraHF, config.VaraHF) if err != nil { return err } if bw := config.VaraHF.Bandwidth; bw != 0 { if err := m.SetBandwidth(fmt.Sprint(bw)); err != nil { m.Close() return err } } varaHFModem = m return nil } func initVaraFMModem() error { if varaFMModem != nil && varaFMModem.Ping() { return nil } if varaFMModem != nil { varaFMModem.Close() } m, err := initVaraModem(MethodVaraFM, config.VaraFM) if err != nil { return err } varaFMModem = m return nil } func initVaraModem(scheme string, conf cfg.VaraConfig) (*vara.Modem, error) { vConf := vara.ModemConfig{ Host: conf.Host(), CmdPort: conf.CmdPort(), DataPort: conf.DataPort(), } m, err := vara.NewModem(scheme, fOptions.MyCall, vConf) if err != nil { return nil, fmt.Errorf("vara initialization failed: %w", err) } transport.RegisterDialer(scheme, m) if conf.PTTControl { rig, ok := rigs[conf.Rig] if !ok { m.Close() return nil, fmt.Errorf("unable to set PTT rig '%s': not defined or not loaded", conf.Rig) } m.SetPTT(rig) } v, _ := m.Version() log.Printf("VARA modem (%s) initialized", v) return m, nil } func initAGWPE() error { if agwpeTNC != nil && agwpeTNC.Ping() == nil { return nil } if agwpeTNC != nil { agwpeTNC.Close() } var err error agwpeTNC, err = agwpe.OpenPortTCP(config.AGWPE.Addr, config.AGWPE.RadioPort, fOptions.MyCall) if err != nil { return fmt.Errorf("AGWPE TNC initialization failed: %w", err) } if v, err := agwpeTNC.Version(); err != nil { return fmt.Errorf("AGWPE TNC initialization failed: %w", err) } else { log.Printf("AGWPE TNC (%s) initialized", v) } transport.RegisterContextDialer(MethodAX25AGWPE, agwpeTNC) return nil } // defaultAX25Method resolves the generic ax25:// scheme to a implementation specific scheme. func defaultAX25Method() string { switch config.AX25.Engine { case cfg.AX25EngineAGWPE: return MethodAX25AGWPE case cfg.AX25EngineSerialTNC: return MethodAX25SerialTNC case cfg.AX25EngineLinux: return MethodAX25Linux default: panic(fmt.Sprintf("invalid ax25 engine: %s", config.AX25.Engine)) } } pat-0.15.1/convert_image.go000066400000000000000000000022531453425652100155440ustar00rootroot00000000000000// Copyright 2016 Martin Hebnes Pedersen (LA5NTA). All rights reserved. // Use of this source code is governed by the MIT-license that can be // found in the LICENSE file. package main import ( "bytes" "image" _ "image/gif" "image/jpeg" _ "image/png" "mime" "path" "strings" "github.com/nfnt/resize" ) func isConvertableImageMediaType(filename, contentType string) bool { var mediaType string if contentType != "" { mediaType, _, _ = mime.ParseMediaType(contentType) } if mediaType == "" { mediaType = mime.TypeByExtension(path.Ext(filename)) } switch mediaType { case "image/svg+xml": // This is a text file return false default: return strings.HasPrefix(mediaType, "image/") } } func convertImage(orig []byte) ([]byte, error) { img, _, err := image.Decode(bytes.NewReader(orig)) if err != nil { return nil, err } // Scale down if img.Bounds().Dx() > 600 { img = resize.Resize(600, 0, img, resize.NearestNeighbor) } // Re-encode as low quality jpeg var buf bytes.Buffer if err := jpeg.Encode(&buf, img, &jpeg.Options{Quality: 40}); err != nil { return orig, err } if buf.Len() >= len(orig) { return orig, nil } return buf.Bytes(), nil } pat-0.15.1/debian/000077500000000000000000000000001453425652100136135ustar00rootroot00000000000000pat-0.15.1/debian/.gitignore000066400000000000000000000000531453425652100156010ustar00rootroot00000000000000pat/ files pat.debhelper.log pat.substvars pat-0.15.1/debian/changelog000066400000000000000000000377171453425652100155040ustar00rootroot00000000000000pat (0.15.1) stable; urgency=medium * Support config overrides using env variables * Only attach Forms XML if a viewer file is defined * Fix handling of SVG attachments (don't attempt auto resize) * VARA: Silence "got a vara command I wasn't expecting..." messages * VARA: Fix leak when re-initializing the modem connection * hamlib: Fix compatibility with rigctld in VFO Mode * hamlib: Fix tests and compilation when statically linking libhamlib * New experiment FW_AUX_ONLY_EXPERIMENT (disabled by default) * Require Go 1.19 or later (due to updated dependencies) -- Martin Hebnes Pedersen Sun, 5 Nov 2023 12:44:08 +0100 pat (0.15.0) stable; urgency=medium * Restore previous connect parameters from browser's local storage * Add missing AX.25 schemes to connect modal's transport dropdown * Fix clearing of To/Cc fields after message is posted to outbox * Fix alignment of connect modal input fields * Improve the dirty disconnect feature * Add deprecation warning for newly deprecated config options * Remove support for previously deprecated config options * AGWPE: Add support for QtSoundModem * AGWPE: Wait for modem ack on dial cancellation * VARA: Add support for inbound (P2P) connections * VARA: Improved throughput, various bug fixes and other improvements * ARDOP: Experimental FSKONLY support (with ARDOP_FSKONLY_EXPERIMENT=1) -- Martin Hebnes Pedersen Sat, 10 Jun 2023 13:07:32 +0200 pat (0.14.1) stable; urgency=medium * VARA: Fix panic on 32-bit builds -- Martin Hebnes Pedersen Web, 02 May 2023 08:34:42 +0200 pat (0.14.0) stable; urgency=medium * AX.25: Implement ability to switch between different AX.25 engines * AX.25: Add AGWPE support (use Direwolf directly over TCP on all platforms) * Winlink HTML Forms: Various compatibility fixes * VARA: Switch to more idiomatic config fields * VARA: Improved progress report on outbound traffic * VARA: Add support for dial cancellation * VARA: Reject inbound P2P sessions (listener not supported yet) -- Martin Hebnes Pedersen Web, 19 Apr 2023 21:09:12 +0200 pat (0.13.1) stable; urgency=medium * Fix panic when using unregistered VARA instances * Use VARA HF/FM defaults if undefined in config * Fix case sensitive matching when resolving aux addresses' passwords -- Martin Hebnes Pedersen Sat, 17 Sep 2022 09:05:48 +0200 pat (0.13.0) stable; urgency=medium * Add support for VARAHF and VARAFM * Add support for setting the ARDOP ARQ bandwidth when dialing a connection * Include linux/arm64 deb package in releases * Remove support for WINMOR TNCs * Add generic support for dial cancellation * Implement dial cancellation for ax25:// and telnet:// * Improved non-interactive CLI compose command * Improved shutdown behavior * Improved FBB protocol compatibility with BPQ Mail * Minor improvements and bug fixes to the PACTOR and serial-tnc transports * Add a build system and package management for the Web GUI -- Martin Hebnes Pedersen Sat, 20 Aug 2022 21:42:05 +0200 pat (0.12.1) stable; urgency=medium * Add support for configurable telnet dial timeout (for Iridium GO users) * Add support for scriptable message composition * Add CLI command `env` for retrieving related environment variables (for scripting) * More reliable Forms updates by using a new API for retrieving latest version and archive URL * Improve websocket handling * Fix bug in Forms update procedure that would delete the OS temp directory in rare cases * Fix bug with pactor serial communication on macOS (Darwin) * Fix bug with Web GUI and Message IDs containing the hash (`#`) symbol -- Martin Hebnes Pedersen Sat, 11 Dec 2021 15:14:22 +0100 pat (0.12.0) stable; urgency=medium * Follow the XDG Base Directory Specification * Add support for sending in precedence order * Add new serial-tnc baudrate configuration options * Fix bug in forms parsing leading to missing forms * Fix permissions issue when updating forms * Fix FBB protocol handshake issue * Improve fsnotify handling for mailbox events * More descriptive error on premature disconnect * Add basic debug logging capabilities * Various dependency updates and refactoring -- Martin Hebnes Pedersen Sun, 31 Oct 2021 17:28:02 +0100 pat (0.11.0) stable; urgency=medium * Add support for Winlink HTML Forms * Add support for individual passords for auxiliary addresses * Add ability to abort ongoing dialing/connection in Web GUI * Add systemd unit file for rigctld * Improve version reporting to Winlink API * Improve websocket handling * Improve visibility of QSY errors in Web GUI * Improve 'reply' and 'forward' functionality in Web GUI * Fix issue with azimuth calculation when distance is zero * Fix incorrect transport URI scheme for packet nodes * Fix build on FreeBSD and macOS. * Avoid truncating rmslist cache on refresh failure * Avoid recompressing images where the resulting file size increases * Require Go 1.16 or later -- Martin Hebnes Pedersen Wed, 30 Jun 2021 21:13:40 +0100 pat (0.10.0) stable; urgency=medium * Add support for P4 Dragon modems * Add RMS list viewer in Web GUI's connect modal * Add support for additional connect parameters for pactor * New max length of message attachment filenames (255 characters) -- Martin Hebnes Pedersen Thu, 08 Sep 2020 19:39:40 +0100 pat (0.9.0) stable; urgency=medium * Less aggressive websocket timeout * Add column sorting in Web GUI * Require Go 1.10 or later * Fix GPSd config bug introduced in v0.8.0 * Fix (mainly macOS) bug related to many open file descriptors -- Martin Hebnes Pedersen Wed, 19 Feb 2020 20:13:18 +0100 pat (0.8.0) stable; urgency=medium * GPSd support in Web GUI * User configurable Service Code * High Accuracy HTML5 Geolocation * Minor PACTOR enhancements and bug fixes * Fixed ARDOP listener issue -- Martin Hebnes Pedersen Thu, 03 Oct 2019 21:48:51 +0200 pat (0.7.0) stable; urgency=medium * Support PACTOR PTC-II and PTC-III (https://github.com/la5nta/pat/issues/40) * Fix QSY frequency rounding error (https://github.com/la5nta/pat/issues/147) * Fix panic on ARDOP TNC connection teardown (https://github.com/la5nta/pat/issues/137) * Fix ARDOP compatibility issue (https://github.com/la5nta/pat/issues/139) -- Martin Hebnes Pedersen Wed, 18 Sep 2019 21:56:17 +0200 pat (0.6.1) stable; urgency=medium * Add deb package `dist` as conflicting package (https://github.com/la5nta/pat/issues/131) * Include systemd unit file for ARDOPc (https://github.com/la5nta/pat/issues/130) * Set correct URL parameter for serial-tnc.Baudrate (https://github.com/la5nta/pat/issues/129) * Fix Go 1.10 compatibility issue (https://github.com/la5nta/pat/issues/121) -- Martin Hebnes Pedersen Sun, 21 Apr 2018 11:23:40 +0200 pat (0.6.0) stable; urgency=high * Support Winlink's new mixed-case password scheme (https://github.com/la5nta/pat/issues/113) * Support for distance and azumuth in rmslist (https://github.com/la5nta/pat/pull/112) * Improved ARDOP ID-frame parser -- Martin Hebnes Pedersen Mon, 22 Jan 2018 21:41:13 +0100 pat (0.5.1) stable; urgency=medium * Support ARDOP >= v1.0 (https://github.com/la5nta/pat/issues/108) * Add rmslist support for ARDOP nodes * Switch to the new Winlink rest API (https://github.com/la5nta/pat/issues/110) * Fix bug which caused WINMOR connection failure when dialing the (non-idle) TNC -- Martin Hebnes Pedersen Tue, 12 Dec 2017 19:03:04 +0100 pat (0.5.0) stable; urgency=high * Fix XSS vulnerability when serving attachments over HTTP (https://github.com/la5nta/pat/issues/105) * Gracefully recover/initialize failed external devices (https://github.com/la5nta/pat/issues/88) * Switch to the new Winlink CMS and API hostname (https://github.com/la5nta/pat/issues/104) * Add config option for WINMOR's Drive Level parameter (https://github.com/la5nta/pat/issues/99) * Add password prompt in web GUI (https://github.com/la5nta/pat/issues/90) * Include man pages in deb and pkg packages (https://github.com/la5nta/pat/pull/91) * Various minor web GUI improvements (https://github.com/la5nta/pat/issues/97) -- Martin Hebnes Pedersen Sat, 18 Nov 2017 11:40:28 +0100 pat (0.4.0) stable; urgency=medium * Desktop notifications for web GUI users (https://github.com/la5nta/pat/issues/85) * New status indicator in web GUI for display of various alerts and info (https://github.com/la5nta/pat/issues/86) * Add Cc field to the web GUI composer (https://github.com/la5nta/pat/issues/83) * Tokenize address input in the web GUI composer (https://github.com/la5nta/pat/issues/84) * Check for empty To/Cc on compose (https://github.com/la5nta/pat/issues/89) -- Martin Hebnes Pedersen Tue, 17 Sep 2017 11:14:59 +0200 pat (0.3.0) stable; urgency=high (Fixes compatibility with an upcoming Winlink CMS release) * Fix critical compatibility issues with WL2K-4.0 aka "AWS-CMS" (https://github.com/la5nta/pat/issues/81) * Fix close of AX.25 listener on Linux (https://github.com/la5nta/pat/issues/68) * Add "Delete" and "Move to archive" actions in web GUI (https://github.com/la5nta/pat/issues/63) -- Martin Hebnes Pedersen Tue, 18 Jul 2017 21:13:08 +0200 pat (0.2.4) stable; urgency=medium * Add progress bar for message transfer in web GUI (https://github.com/la5nta/pat/pull/78) * Properly parse offset in B2 compressed message header for BPQ compatibility (https://github.com/la5nta/pat/issues/74) * Fix libax25 segfault on invalid axport (https://github.com/la5nta/pat/issues/73) * Silence FREQUENCY parse errors for ardop (https://github.com/la5nta/pat/issues/75) -- Martin Hebnes Pedersen Tue, 28 Feb 2017 19:07:00 +0100 pat (0.2.3) stable; urgency=medium * Support ARDOP >= v0.9 (https://github.com/la5nta/pat/issues/69) * Improve list parsing in various UI fields * Handle non-ascii attachment names -- Martin Hebnes Pedersen Fri, 27 Jan 2016 18:17:30 +0100 pat (0.2.2) stable; urgency=medium * Ensure default config is written before opening the configuration editor (https://github.com/la5nta/pat/issues/70) * Add some missing config defaults -- Martin Hebnes Pedersen Thu, 01 Dec 2016 18:14:09 +0100 pat (0.2.1) stable; urgency=medium * Support ARDOP >= v0.6 (https://github.com/la5nta/pat/issues/60) * Fix bug that caused 'configure' to fail if config format was invalid (https://github.com/la5nta/pat/issues/62) * Add position format examples for --latlon (https://github.com/la5nta/pat/issues/65) * Statically link libax25 (linux) to avoid crash on incompatible shared library (https://github.com/la5nta/pat/issues/59) -- Martin Hebnes Pedersen Wed, 12 Oct 2016 20:24:18 +0200 pat (0.2.0) stable; urgency=medium * Support Radio only - Winlink Hybrid Network (https://github.com/la5nta/pat/issues/44) * Switch to Go port of lzhuf (https://github.com/la5nta/pat/issues/50) * Linux ax25 scripts: Add method for custom TNC initialization (https://github.com/la5nta/pat/issues/53) * Fix ardop PTT rigcontrol (https://github.com/la5nta/pat/issues/58) * Minor bug fixes and improvements in the web GUI -- Martin Hebnes Pedersen Fri, 05 Aug 2016 15:16:51 +0200 pat (0.1.5) stable; urgency=medium * Fix bug that caused command-line interface composer's prompt scan to see whitespace as end of line (https://github.com/la5nta/pat/issues/45) * Fix Mac OS default install path (https://github.com/la5nta/pat/issues/47) -- Martin Hebnes Pedersen Mon, 27 Jun 2016 22:43:36 +0200 pat (0.1.4) stable; urgency=medium * Fix case where secure_login_password was ignored if mycall was not all upper case (https://github.com/la5nta/pat/issues/42) * Support image resize in cli composer (https://github.com/la5nta/pat/issues/38) * Remove imagemagick dependency for image resize (https://github.com/la5nta/pat/issues/13) * Minor improvement of cli mailbox navigation (https://github.com/la5nta/pat/issues/39) -- Martin Hebnes Pedersen Thu, 09 Jun 2016 21:02:42 +0200 pat (0.1.3) stable; urgency=medium * Add filename extension for mailbox messages (https://github.com/la5nta/pat/issues/34) * Fix broken ax25:// digipeater syntax (https://github.com/la5nta/pat/issues/33) * Enable gzip experiment by default (https://github.com/la5nta/pat/issues/29) -- Martin Hebnes Pedersen Sat, 07 May 2016 22:18:12 +0200 pat (0.1.2) stable; urgency=medium * Fix callsign casing bug (https://github.com/la5nta/pat/issues/19) * Fix web composer Re: prefix issues in replies (https://github.com/la5nta/pat/issues/30) * Support running http server while in interactive mode (https://github.com/la5nta/pat/issues/26) * Send smallest messages first (suggested in the Winlink FAQ) (https://github.com/la5nta/pat/issues/25) * Fix handling of proposal code H (https://github.com/la5nta/pat/issues/25) * Fix handling of blocks with all messages deferred/rejected (https://github.com/la5nta/pat/issues/25) * Fix unstable serialization of messages that could result in corrupt partial message transfer (https://github.com/la5nta/pat/issues/25) * Support both utf8 and iso-8859-1 encoded subject header (https://github.com/la5nta/pat/issues/23) * Re-implement ctrl+c for aborting connect/session (https://github.com/la5nta/pat/issues/22) * Fix GUI post button issues on some browsers (https://github.com/la5nta/pat/issues/21) * Fix WINMOR unexpected EOF issue on session termination (https://github.com/la5nta/pat/issues/20) * Fix improper handling of callsign casing (https://github.com/la5nta/pat/issues/19) -- Martin Hebnes Pedersen Sat, 02 Apr 2016 10:41:16 +0200 pat (0.1.1) stable; urgency=medium * Fix various file locking errors on Windows (https://github.com/la5nta/pat/issues/9). * Automatic version reporting to Winlink CMS Web Services. -- Martin Hebnes Pedersen Fri, 11 Mar 2016 21:06:16 +0100 pat (0.1.0) stable; urgency=medium * Initial release under new name. * Fix leak that caused increasing CPU load. * Add band filtering for rmslist command. * Fix winmor robust issues. -- Martin Hebnes Pedersen Sun, 06 Mar 2016 14:09:11 +0100 wl2k-go (0.0.4) stable; urgency=medium * Fixed parse error of Date field from RMS Relay'ed messages (https://github.com/la5nta/wl2k-go/issues/29). * Fixed parse of ax25 URLs with digipeaters (https://github.com/la5nta/wl2k-go/issues/28). * Fixed panic on misconfigured (empty) axport (https://github.com/la5nta/wl2k-go/issues/27). * Prompt user for login password if mycall is overridden by --mycall even though a password is defined in config. * Run winmor in robust mode during handshake and proposal chatter. * GPSd support (for position reporting using a local serial/usb GPS). -- Martin Hebnes Pedersen Sun, 14 Feb 2016 18:19:02 +0100 wl2k-go (0.0.3) stable; urgency=medium * Fixed web ui assets bug (https://github.com/la5nta/wl2k-go/issues/26). * Fixed systemd user install script. -- Martin Hebnes Pedersen Thu, 14 Jan 2016 19:26:49 +0100 wl2k-go (0.0.2) stable; urgency=medium * Fixed ARDOPc issues. -- Martin Hebnes Pedersen Sun, 10 Jan 2016 15:56:00 +0100 wl2k-go (0.0.1) stable; urgency=medium * Initial release. -- Martin Hebnes Pedersen Sun, 04 Nov 2016 16:24:24 +0100 pat-0.15.1/debian/compat000066400000000000000000000000021453425652100150110ustar00rootroot000000000000007 pat-0.15.1/debian/control000066400000000000000000000007471453425652100152260ustar00rootroot00000000000000Source: pat Section: ham Priority: extra Maintainer: Martin Hebnes Pedersen Homepage: http://getpat.io Build-Depends: debhelper (>= 7.0.50~), golang (>= 2:1.16), libax25, libax25-dev Standards-Version: 3.9.1 Package: pat Architecture: amd64 i386 armhf arm64 Conflicts: wl2k-go, dist Replaces: wl2k-go Recommends: libhamlib-utils (>= 1.2), ax25-tools, gpsd (>= 2.90) Suggests: tmd710-tncsetup Description: A portable Winlink client for amateur radio email. pat-0.15.1/debian/pat.manpages000066400000000000000000000000101453425652100161030ustar00rootroot00000000000000man/*.1 pat-0.15.1/debian/pat@.service000066400000000000000000000003521453425652100160610ustar00rootroot00000000000000[Unit] Description=pat - Winlink client for %I Documentation=https://github.com/la5nta/pat/wiki After=ax25.service network.target [Service] User=%i ExecStart=/usr/bin/pat http Restart=on-failure [Install] WantedBy=multi-user.target pat-0.15.1/debian/rules000077500000000000000000000010231453425652100146670ustar00rootroot00000000000000#!/usr/bin/make -f # -*- makefile -*- PKGDIR=debian/pat %: dh $@ clean: dh_clean rm -rf $(PKGDIR) build: ./make.bash binary-arch: clean build dh_prep dh_installdirs mkdir -p $(PKGDIR)/usr/bin mkdir -p $(PKGDIR)/usr/share/pat mkdir -p $(PKGDIR)/lib/systemd/system mv ./pat $(PKGDIR)/usr/bin/ cp -r share/* $(PKGDIR)/usr/share/pat/ cp debian/pat@.service $(PKGDIR)/lib/systemd/system/ dh_installman dh_strip dh_compress dh_fixperms dh_installdeb dh_gencontrol dh_md5sums dh_builddeb binary: binary-arch pat-0.15.1/docker-compose.yml000066400000000000000000000001751453425652100160310ustar00rootroot00000000000000services: pat: image: la5nta/pat build: . volumes: - ./docker-data:/app/pat ports: - 8080:8080 pat-0.15.1/env.go000066400000000000000000000023451453425652100135140ustar00rootroot00000000000000package main import ( "context" "fmt" "io" "os" "runtime" "github.com/la5nta/pat/internal/buildinfo" ) func envHandle(_ context.Context, _ []string) { writeEnvAll(os.Stdout) } func writeEnvAll(w io.Writer) { writeEnv(w, "PAT_MYCALL", fOptions.MyCall) writeEnv(w, "PAT_LOCATOR", config.Locator) writeEnv(w, "PAT_VERSION", buildinfo.Version) writeEnv(w, "PAT_ARCH", runtime.GOARCH) writeEnv(w, "PAT_OS", runtime.GOOS) writeEnv(w, "PAT_MAILBOX_PATH", fOptions.MailboxPath) writeEnv(w, "PAT_CONFIG_PATH", fOptions.ConfigPath) writeEnv(w, "PAT_LOG_PATH", fOptions.LogPath) writeEnv(w, "PAT_EVENTLOG_PATH", fOptions.EventLogPath) writeEnv(w, "PAT_FORMS_PATH", fOptions.FormsPath) writeEnv(w, "PAT_DEBUG", os.Getenv("PAT_DEBUG")) writeEnv(w, "PAT_WEB_DEV_ADDR", os.Getenv("PAT_WEB_DEV_ADDR")) writeEnv(w, "ARDOP_DEBUG", os.Getenv("ARDOP_DEBUG")) writeEnv(w, "PACTOR_DEBUG", os.Getenv("PACTOR_DEBUG")) writeEnv(w, "AGWPE_DEBUG", os.Getenv("AGWPE_DEBUG")) writeEnv(w, "VARA_DEBUG", os.Getenv("VARA_DEBUG")) writeEnv(w, "GZIP_EXPERIMENT", os.Getenv("GZIP_EXPERIMENT")) writeEnv(w, "ARDOP_FSKONLY_EXPERIMENT", os.Getenv("ARDOP_FSKONLY_EXPERIMENT")) } func writeEnv(w io.Writer, k, v string) { fmt.Fprintf(w, "%s=\"%s\"\n", k, v) } pat-0.15.1/event_log.go000066400000000000000000000023371453425652100147070ustar00rootroot00000000000000// Copyright 2016 Martin Hebnes Pedersen (LA5NTA). All rights reserved. // Use of this source code is governed by the MIT-license that can be // found in the LICENSE file. package main import ( "encoding/json" "net" "os" "time" ) type EventLogger struct { file *os.File enc *json.Encoder } func NewEventLogger(path string) (*EventLogger, error) { file, err := os.OpenFile(path, os.O_APPEND|os.O_CREATE|os.O_RDWR, 0o666) return &EventLogger{file, json.NewEncoder(file)}, err } func (l *EventLogger) Close() error { return l.file.Close() } func (l *EventLogger) Log(what string, event map[string]interface{}) { event["log_time"] = time.Now() event["what"] = what if err := l.enc.Encode(event); err != nil { panic(err) } } func (l *EventLogger) LogConn(op string, freq Frequency, conn net.Conn, err error) { e := map[string]interface{}{"success": err == nil} if err != nil { e["error"] = err.Error() } else { if remote := conn.RemoteAddr(); remote != nil { e["remote_addr"] = remote.String() e["network"] = conn.RemoteAddr().Network() } if local := conn.LocalAddr(); local != nil { e["local_addr"] = local.String() } } if freq > 0 { e["freq"] = freq } e["operation"] = op l.Log("connect", e) } pat-0.15.1/exchange.go000066400000000000000000000154231453425652100145070ustar00rootroot00000000000000// Copyright 2016 Martin Hebnes Pedersen (LA5NTA). All rights reserved. // Use of this source code is governed by the MIT-license that can be // found in the LICENSE file. package main import ( "fmt" "log" "net" "os" "strings" "time" "github.com/la5nta/pat/internal/buildinfo" "github.com/la5nta/wl2k-go/fbb" ) type ex struct { conn net.Conn target string master bool errors chan error } func exchangeLoop() (ce chan ex) { ce = make(chan ex) go func() { for ex := range ce { ex.errors <- sessionExchange(ex.conn, ex.target, ex.master) close(ex.errors) } }() return ce } func exchange(conn net.Conn, targetCall string, master bool) error { e := ex{ conn: conn, target: targetCall, master: master, errors: make(chan error), } exchangeChan <- e return <-e.errors } type NotifyMBox struct{ fbb.MBoxHandler } func (m NotifyMBox) ProcessInbound(msgs ...*fbb.Message) error { if err := m.MBoxHandler.ProcessInbound(msgs...); err != nil { return err } for _, msg := range msgs { websocketHub.WriteJSON(struct{ Notification Notification }{ Notification{ Title: fmt.Sprintf("New message from %s", msg.From().Addr), Body: msg.Subject(), }, }) } return nil } func sessionExchange(conn net.Conn, targetCall string, master bool) error { exchangeConn = conn websocketHub.UpdateStatus() defer func() { exchangeConn = nil; websocketHub.UpdateStatus() }() // New wl2k Session targetCall = strings.Split(targetCall, ` `)[0] session := fbb.NewSession( fOptions.MyCall, targetCall, config.Locator, NotifyMBox{mbox}, ) session.SetUserAgent(fbb.UserAgent{ Name: buildinfo.AppName, Version: buildinfo.Version, }) if len(config.MOTD) > 0 { session.SetMOTD(config.MOTD...) } // Handle secure login session.SetSecureLoginHandleFunc(func(addr fbb.Address) (string, error) { if addr.Addr == fOptions.MyCall && config.SecureLoginPassword != "" { return config.SecureLoginPassword, nil } for _, aux := range config.AuxAddrs { if !addr.EqualString(aux.Address) { continue } switch { case aux.Password != nil: return *aux.Password, nil case config.SecureLoginPassword != "": return config.SecureLoginPassword, nil } } resp := <-promptHub.Prompt("password", "Enter secure login password for "+addr.String()) return resp.Value, resp.Err }) for _, addr := range config.AuxAddrs { session.AddAuxiliaryAddress(fbb.AddressFromString(addr.Address)) } session.IsMaster(master) session.SetLogger(log.New(logWriter, "", 0)) session.SetStatusUpdater(new(StatusUpdate)) if fOptions.Robust { session.SetRobustMode(fbb.RobustForced) } log.Printf("Connected to %s (%s)", conn.RemoteAddr(), conn.RemoteAddr().Network()) start := time.Now() stats, err := session.Exchange(conn) if fbb.IsLoginFailure(err) { fmt.Println("NOTE: A new password scheme for Winlink is being implemented as of 2018-01-31.") fmt.Println(" Users with passwords created/changed prior to January 31, 2018 should be") fmt.Println(" aware that their password MUST be entered in ALL-UPPERCASE letters. Only") fmt.Println(" passwords created/changed/issued after January 31, 2018 should/may contain") fmt.Println(" lowercase letters. - https://github.com/la5nta/pat/issues/113") } event := map[string]interface{}{ "mycall": session.Mycall(), "targetcall": session.Targetcall(), "remote_fw": session.RemoteForwarders(), "remote_sid": session.RemoteSID(), "master": master, "local_locator": config.Locator, "auxiliary_addresses": config.AuxAddrs, "network": conn.RemoteAddr().Network(), "remote_addr": conn.RemoteAddr().String(), "local_addr": conn.LocalAddr().String(), "sent": stats.Sent, "received": stats.Received, "start": start.Unix(), "end": time.Now().Unix(), "success": err == nil, } if err != nil { event["error"] = err.Error() } eventLog.Log("exchange", event) return err } func abortActiveConnection(dirty bool) (ok bool) { switch { case dirty: // This mean we've already tried to abort, but the connection is still active. // Fallback to the below cases to try to identify the busy modem and abort hard. case dialing != nil: // If we're currently dialing a transport, attempt to abort by cancelling the associated context. log.Printf("Got abort signal while dialing %s, cancelling...", dialing.Scheme) go dialCancelFunc() return true case exchangeConn != nil: // If we have an active connection, close it gracefully. log.Println("Got abort signal, disconnecting...") go exchangeConn.Close() return true } // Any connection and/or dial operation has been cancelled at this point. // User is attempting to abort something, so try to identify any non-idling transports and abort. // It might be a "dirty disconnect" of an already cancelled connection or dial operation which is in the // process of gracefully terminating. It might also be an attempt to close an inbound P2P connection. switch { case adTNC != nil && !adTNC.Idle(): if dirty { log.Println("Dirty disconnecting ardop...") adTNC.Abort() return true } log.Println("Disconnecting ardop...") go func() { if err := adTNC.Disconnect(); err != nil { log.Println(err) } }() return true case varaFMModem != nil && !varaFMModem.Idle(): if dirty { log.Println("Dirty disconnecting varafm...") varaFMModem.Abort() return true } log.Println("Disconnecting varafm...") go func() { if err := varaFMModem.Close(); err != nil { log.Println(err) } }() return true case varaHFModem != nil && !varaHFModem.Idle(): if dirty { log.Println("Dirty disconnecting varahf...") varaHFModem.Abort() return true } log.Println("Disconnecting varahf...") go func() { if err := varaHFModem.Close(); err != nil { log.Println(err) } }() return true case pModem != nil: log.Println("Disconnecting pactor...") err := pModem.Close() if err != nil { log.Println(err) } return err == nil default: return false } } type StatusUpdate int func (s *StatusUpdate) UpdateStatus(stat fbb.Status) { var prop fbb.Proposal switch { case stat.Receiving != nil: prop = *stat.Receiving case stat.Sending != nil: prop = *stat.Sending } websocketHub.WriteProgress(Progress{ MID: prop.MID(), BytesTotal: stat.BytesTotal, BytesTransferred: stat.BytesTransferred, Subject: prop.Title(), Receiving: stat.Receiving != nil, Sending: stat.Sending != nil, Done: stat.Done, }) percent := float64(stat.BytesTransferred) / float64(stat.BytesTotal) * 100 fmt.Printf("\r%s: %3.0f%%", prop.Title(), percent) if stat.Done { fmt.Println("") } os.Stdout.Sync() } pat-0.15.1/flags.go000066400000000000000000000034221453425652100140150ustar00rootroot00000000000000// Copyright 2016 Martin Hebnes Pedersen (LA5NTA). All rights reserved. // Use of this source code is governed by the MIT-license that can be // found in the LICENSE file. package main import ( "context" "fmt" "os" "strings" "github.com/spf13/pflag" ) var ErrNoCmd = fmt.Errorf("no cmd") type Command struct { Str string Aliases []string Desc string HandleFunc func(ctx context.Context, args []string) Usage string Options map[string]string Example string LongLived bool MayConnect bool } func (cmd Command) PrintUsage() { fmt.Fprintf(os.Stderr, "%s - %s\n", cmd.Str, cmd.Desc) fmt.Fprintf(os.Stderr, "\nUsage:\n %s %s\n", cmd.Str, strings.TrimSpace(cmd.Usage)) if len(cmd.Options) > 0 { fmt.Fprint(os.Stderr, "\nOptions:\n") for f, desc := range cmd.Options { fmt.Fprintf(os.Stderr, " %-17s %s\n", f, desc) } } if cmd.Example != "" { fmt.Fprintf(os.Stderr, "\nExample:\n %s\n", strings.TrimSpace(cmd.Example)) } fmt.Fprint(os.Stderr, "\n") } func parseFlags(args []string) (cmd Command, arguments []string) { var options []string var err error cmd, options, arguments, err = findCommand(args) if err != nil { pflag.Usage() os.Exit(1) } optionsSet().Parse(options) if len(arguments) == 0 { arguments = append(arguments, "") } switch arguments[0] { case "--help", "-help", "help", "-h": cmd.PrintUsage() os.Exit(1) } return } func findCommand(args []string) (cmd Command, pre, post []string, err error) { cmdMap := make(map[string]Command, len(commands)) for _, c := range commands { cmdMap[c.Str] = c for _, alias := range c.Aliases { cmdMap[alias] = c } } for i, arg := range args { if cmd, ok := cmdMap[arg]; ok { return cmd, args[1:i], args[i+1:], nil } } err = ErrNoCmd return } pat-0.15.1/freq.go000066400000000000000000000067261453425652100136700ustar00rootroot00000000000000// Copyright 2016 Martin Hebnes Pedersen (LA5NTA). All rights reserved. // Use of this source code is governed by the MIT-license that can be // found in the LICENSE file. package main import ( "encoding/json" "fmt" "log" "strconv" "strings" "github.com/la5nta/wl2k-go/rigcontrol/hamlib" ) var bands = map[string]Band{ "160m": {1.8e6, 2.0e6}, "80m": {3.5e6, 4.0e6}, "60m": {5.2e6, 5.5e6}, "40m": {7.0e6, 7.3e6}, "30m": {10.1e6, 10.2e6}, "20m": {14.0e6, 14.4e6}, "17m": {18.0e6, 18.2e6}, "15m": {21.0e6, 21.5e6}, "12m": {24.8e6, 25.0e6}, "10m": {28.0e6, 30.0e6}, "6m": {50.0e6, 54.0e6}, "4m": {70.0e6, 70.5e6}, "2m": {144.0e6, 148.0e6}, "1.25m": {219.0e6, 225.0e6}, // 220, 222 (MHz) "70cm": {420.0e6, 450.0e6}, } type Band struct{ lower, upper Frequency } func (b Band) Contains(f Frequency) bool { if b.lower == 0 && b.upper == 0 { return true } return f >= b.lower && f <= b.upper } type Frequency int // Hz func (f Frequency) String() string { m := f / 1e6 k := (float64(f) - float64(m)*1e6) / 1e3 return fmt.Sprintf("%d.%06.2f MHz", m, k) } func (f Frequency) MarshalJSON() ([]byte, error) { type obj struct { Hz json.Number `json:"hz"` KHz json.Number `json:"khz"` Desc string `json:"desc"` } return json.Marshal(obj{ Hz: json.Number(fmt.Sprint(int(f))), KHz: json.Number(fmt.Sprint(f.KHz())), Desc: f.String(), }) } func (f Frequency) KHz() float64 { return float64(f) / 1e3 } func (f Frequency) Dial(mode string) Frequency { mode = strings.ToLower(mode) // Try to detect FM modes, e.g. `ARDOP 2000 FM` and `VARA FM WIDE` if strings.Contains(mode, "fm") { return f } offsets := map[string]Frequency{ MethodPactor: 1500, MethodArdop: 1500, // varahf doesn't appear in RMS list from WDT "vara": 1500, } var shift Frequency for m, offset := range offsets { if strings.Contains(mode, m) { shift = -offset break } } return f + shift } func VFOForTransport(transport string) (vfo hamlib.VFO, rigName string, ok bool, err error) { var rig string switch { case transport == MethodArdop: rig = config.Ardop.Rig case transport == MethodAX25, strings.HasPrefix(transport, MethodAX25+"+"): rig = config.AX25.Rig case transport == MethodPactor: rig = config.Pactor.Rig case transport == MethodVaraHF: rig = config.VaraHF.Rig case transport == MethodVaraFM: rig = config.VaraFM.Rig default: return vfo, "", false, fmt.Errorf("not supported with transport '%s'", transport) } if rig == "" { return vfo, "", false, fmt.Errorf("missing rig reference in config section for %s", transport) } vfo, ok = rigs[rig] return vfo, rig, ok, nil } func freq(param string) { parts := strings.SplitN(param, ":", 2) if parts[0] == "" { fmt.Println("Need freq method.") } rig, _, ok, _ := VFOForTransport(parts[0]) if !ok { log.Printf("Hamlib rig not loaded.") return } if len(parts) < 2 { freq, err := rig.GetFreq() if err != nil { log.Printf("Unable to get frequency: %s", err) } fmt.Printf("%.3f\n", float64(freq)/1e3) return } if _, _, err := setFreq(rig, parts[1]); err != nil { log.Printf("Unable to set frequency: %s", err) } } func setFreq(rig hamlib.VFO, freq string) (newFreq, oldFreq int, err error) { oldFreq, err = rig.GetFreq() if err != nil { return 0, 0, fmt.Errorf("unable to get rig frequency: %w", err) } f, err := strconv.ParseFloat(freq, 64) if err != nil { return 0, 0, err } newFreq = int(f * 1e3) err = rig.SetFreq(newFreq) return } pat-0.15.1/go.mod000066400000000000000000000027761453425652100135130ustar00rootroot00000000000000module github.com/la5nta/pat go 1.19 require ( github.com/adrg/xdg v0.3.3 github.com/bndr/gotabulate v1.1.3-0.20170315142410-bc555436bfd5 github.com/dimchansky/utfbom v1.1.1 github.com/fsnotify/fsnotify v1.4.9 github.com/gorhill/cronexpr v0.0.0-20180427100037-88b0669f7d75 github.com/gorilla/mux v1.8.0 github.com/gorilla/websocket v1.4.2 github.com/harenber/ptc-go/v2 v2.2.3 github.com/howeyc/gopass v0.0.0-20190910152052-7cb4b85ec19c github.com/kelseyhightower/envconfig v1.4.0 github.com/la5nta/wl2k-go v0.11.8 github.com/microcosm-cc/bluemonday v1.0.16 github.com/n8jja/Pat-Vara v1.1.4 github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646 github.com/pd0mz/go-maidenhead v1.0.0 github.com/peterh/liner v1.2.1 github.com/spf13/pflag v1.0.5 ) require ( dario.cat/mergo v1.0.0 // indirect github.com/albenik/go-serial/v2 v2.6.0 // indirect github.com/aymerick/douceur v0.2.0 // indirect github.com/creack/goselect v0.1.2 // indirect github.com/gorilla/css v1.0.0 // indirect github.com/howeyc/crc16 v0.0.0-20171223171357-2b2a61e366a6 // indirect github.com/mattn/go-runewidth v0.0.13 // indirect github.com/paulrosania/go-charset v0.0.0-20190326053356-55c9d7a5834c // indirect github.com/rivo/uniseg v0.2.0 // indirect go.uber.org/multierr v1.11.0 // indirect golang.org/x/crypto v0.0.0-20210813211128-0a44fdfbc16e // indirect golang.org/x/net v0.0.0-20210813160813-60bc85c4be6d // indirect golang.org/x/sys v0.13.0 // indirect golang.org/x/term v0.0.0-20210615171337-6886f2dfbf5b // indirect ) pat-0.15.1/go.sum000066400000000000000000000224031453425652100135250ustar00rootroot00000000000000dario.cat/mergo v1.0.0 h1:AGCNq9Evsj31mOgNPcLyXc+4PNABt905YmuqPYYpBWk= dario.cat/mergo v1.0.0/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk= github.com/adrg/xdg v0.3.3 h1:s/tV7MdqQnzB1nKY8aqHvAMD+uCiuEDzVB5HLRY849U= github.com/adrg/xdg v0.3.3/go.mod h1:61xAR2VZcggl2St4O9ohF5qCKe08+JDmE4VNzPFQvOQ= github.com/albenik/go-serial/v2 v2.4.0/go.mod h1:JUrQKdczCMB0FlXt2rlJJ8zbfFzmjTIAkLPyyVfr5ho= github.com/albenik/go-serial/v2 v2.5.0/go.mod h1:ySdCqoERscw1xluK1n62R8Faoyu+jXKwVHPa1lSSAew= github.com/albenik/go-serial/v2 v2.6.0 h1:UX30WZPL0qouDrKu4xwVFgvQA3YDTNhk3+aVC6X0jYg= github.com/albenik/go-serial/v2 v2.6.0/go.mod h1:sqQA6eeZHKUB6rAgrBsP/8d3Go5Md5cjCof1WcyaK0o= github.com/aymerick/douceur v0.2.0 h1:Mv+mAeH1Q+n9Fr+oyamOlAkUNPWPlA8PPGR0QAaYuPk= github.com/aymerick/douceur v0.2.0/go.mod h1:wlT5vV2O3h55X9m7iVYN0TBM0NH/MmbLnd30/FjWUq4= github.com/bndr/gotabulate v1.1.3-0.20170315142410-bc555436bfd5 h1:D48YSLPNJ8WpdwDqYF8bMMKUB2bgdWEiFx1MGwPIdbs= github.com/bndr/gotabulate v1.1.3-0.20170315142410-bc555436bfd5/go.mod h1:0+8yUgaPTtLRTjf49E8oju7ojpU11YmXyvq1LbPAb3U= github.com/creack/goselect v0.1.2 h1:2DNy14+JPjRBgPzAd1thbQp4BSIihxcBf0IXhQXDRa0= github.com/creack/goselect v0.1.2/go.mod h1:a/NhLweNvqIYMuxcMOuWY516Cimucms3DglDzQP3hKY= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/dimchansky/utfbom v1.1.1 h1:vV6w1AhK4VMnhBno/TPVCoK9U/LP0PkLCS9tbxHdi/U= github.com/dimchansky/utfbom v1.1.1/go.mod h1:SxdoEBH5qIqFocHMyGOXVAybYJdr71b1Q/j0mACtrfE= github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4= github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= github.com/gorhill/cronexpr v0.0.0-20180427100037-88b0669f7d75 h1:f0n1xnMSmBLzVfsMMvriDyA75NB/oBgILX2GcHXIQzY= github.com/gorhill/cronexpr v0.0.0-20180427100037-88b0669f7d75/go.mod h1:g2644b03hfBX9Ov0ZBDgXXens4rxSxmqFBbhvKv2yVA= github.com/gorilla/css v1.0.0 h1:BQqNyPTi50JCFMTw/b67hByjMVXZRwGha6wxVGkeihY= github.com/gorilla/css v1.0.0/go.mod h1:Dn721qIggHpt4+EFCcTLTU/vk5ySda2ReITrtgBl60c= github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI= github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So= github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc= github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/harenber/ptc-go/v2 v2.2.3 h1:saGN1zhWozAF2kNseDI9YCHwuCl1Seb3++gkCwVfcj8= github.com/harenber/ptc-go/v2 v2.2.3/go.mod h1:SDIy4XqnUq6YYPcLLjDTfbLPf1/Z82ho1VPkPGVXSTo= github.com/howeyc/crc16 v0.0.0-20171223171357-2b2a61e366a6 h1:IIVxLyDUYErC950b8kecjoqDet8P5S4lcVRUOM6rdkU= github.com/howeyc/crc16 v0.0.0-20171223171357-2b2a61e366a6/go.mod h1:JslaLRrzGsOKJgFEPBP65Whn+rdwDQSk0I0MCRFe2Zw= github.com/howeyc/gopass v0.0.0-20190910152052-7cb4b85ec19c h1:aY2hhxLhjEAbfXOx2nRJxCXezC6CO2V/yN+OCr1srtk= github.com/howeyc/gopass v0.0.0-20190910152052-7cb4b85ec19c/go.mod h1:lADxMC39cJJqL93Duh1xhAs4I2Zs8mKS89XWXFGp9cs= github.com/kelseyhightower/envconfig v1.4.0 h1:Im6hONhd3pLkfDFsbRgu68RDNkGF1r3dvMUtDTo2cv8= github.com/kelseyhightower/envconfig v1.4.0/go.mod h1:cccZRl6mQpaq41TPp5QxidR+Sa3axMbJDNb//FQX6Gg= github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/la5nta/wl2k-go v0.7.3/go.mod h1:rTQaxPiAFD3pWGWN8Lh+BskN3Fpii84GoVwpTHNiCjE= github.com/la5nta/wl2k-go v0.11.5/go.mod h1:0c+/9KyDj7Ra7C/O4rVUYx1CzvdtS65di/93wlI22fo= github.com/la5nta/wl2k-go v0.11.8 h1:fTrOYm7oJu/b+3RmQMGX9TfpADnrFFkLzkDpfRTaEIs= github.com/la5nta/wl2k-go v0.11.8/go.mod h1:rUK5mVAldeSuru47APLp9wJMJ5BiaZZ3YxZafSNs6CI= github.com/mattn/go-runewidth v0.0.3/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= github.com/mattn/go-runewidth v0.0.13 h1:lTGmDsbAYt5DmK6OnoV7EuIF1wEIFAcxld6ypU4OSgU= github.com/mattn/go-runewidth v0.0.13/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= github.com/microcosm-cc/bluemonday v1.0.16 h1:kHmAq2t7WPWLjiGvzKa5o3HzSfahUKiOq7fAPUiMNIc= github.com/microcosm-cc/bluemonday v1.0.16/go.mod h1:Z0r70sCuXHig8YpBzCc5eGHAap2K7e/u082ZUpDRRqM= github.com/n8jja/Pat-Vara v1.1.4 h1:yXqQjQQmpcXc9dA5XjRVvC1eYaFoErxvFeIHzLlPA90= github.com/n8jja/Pat-Vara v1.1.4/go.mod h1:9ovT5w1MeVtQ336AqhoPmgiQ4eGDgNiygBxFvAiSJbc= github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646 h1:zYyBkD/k9seD2A7fsi6Oo2LfFZAehjjQMERAvZLEDnQ= github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646/go.mod h1:jpp1/29i3P1S/RLdc7JQKbRpFeM1dOBd8T9ki5s+AY8= github.com/paulrosania/go-charset v0.0.0-20151028000031-621bb39fcc83/go.mod h1:YnNlZP7l4MhyGQ4CBRwv6ohZTPrUJJZtEv4ZgADkbs4= github.com/paulrosania/go-charset v0.0.0-20190326053356-55c9d7a5834c h1:P6XGcuPTigoHf4TSu+3D/7QOQ1MbL6alNwrGhcW7sKw= github.com/paulrosania/go-charset v0.0.0-20190326053356-55c9d7a5834c/go.mod h1:YnNlZP7l4MhyGQ4CBRwv6ohZTPrUJJZtEv4ZgADkbs4= github.com/pd0mz/go-maidenhead v1.0.0 h1:zl2AXA36LnmP5TDEfshM0fWi1mc08fNc6qhj7YD5xjw= github.com/pd0mz/go-maidenhead v1.0.0/go.mod h1:4Q+QSDCqWqlabstLGUVm47rAcL06nEEty2d3KzsTNMk= github.com/peterh/liner v1.2.1 h1:O4BlKaq/LWu6VRWmol4ByWfzx6MfXc5Op5HETyIy5yg= github.com/peterh/liner v1.2.1/go.mod h1:CRroGNssyjTd/qIG2FyxByd2S8JEAZXBl4qUrZf8GS0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY= github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1 h1:5TQK59W5E3v0r2duFAb7P95B6hEeOyEnHRa8MjYSMTY= github.com/tarm/goserial v0.0.0-20151007205400-b3440c3c6355/go.mod h1:jcMo2Odv5FpDA6rp8bnczbUolcICW6t54K3s9gOlgII= go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU= go.uber.org/multierr v1.7.0/go.mod h1:7EAYxJLBy9rStEaz58O2t4Uvip6FSURkq8/ppBp95ak= go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= golang.org/x/crypto v0.0.0-20210813211128-0a44fdfbc16e h1:VvfwVmMH40bpMeizC9/K7ipM5Qjucuu16RWfneFPyhQ= golang.org/x/crypto v0.0.0-20210813211128-0a44fdfbc16e/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/net v0.0.0-20210614182718-04defd469f4e/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20210813160813-60bc85c4be6d h1:LO7XpTYMwTqxjLcGWPijK3vRXg1aWdlNOVOHRq45d7c= golang.org/x/net v0.0.0-20210813160813-60bc85c4be6d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210223212115-eede4237b368/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.13.0 h1:Af8nKPmuFypiUBjVoU9V20FiaFXOcuZI21p0ycVYYGE= golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210615171337-6886f2dfbf5b h1:9zKuko04nR4gjZ4+DNjHqRlAJqbJETHwiNKDqTfOjfE= golang.org/x/term v0.0.0-20210615171337-6886f2dfbf5b/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= pat-0.15.1/http.go000066400000000000000000000553411453425652100137070ustar00rootroot00000000000000// Copyright 2016 Martin Hebnes Pedersen (LA5NTA). All rights reserved. // Use of this source code is governed by the MIT-license that can be // found in the LICENSE file. package main import ( "bufio" "bytes" "context" "embed" "encoding/json" "errors" "fmt" "html/template" "io" "io/fs" "log" "mime/multipart" "net" "net/http" "net/http/httputil" "net/url" "os" "path" "path/filepath" "sort" "strconv" "strings" "time" "github.com/la5nta/wl2k-go/transport/ardop" "github.com/la5nta/pat/internal/buildinfo" "github.com/la5nta/pat/internal/gpsd" "github.com/gorilla/mux" "github.com/gorilla/websocket" "github.com/la5nta/wl2k-go/catalog" "github.com/la5nta/wl2k-go/fbb" "github.com/la5nta/wl2k-go/mailbox" "github.com/microcosm-cc/bluemonday" "github.com/n8jja/Pat-Vara/vara" ) //go:embed web/dist/** var embeddedFS embed.FS var staticContent fs.FS // Status represents a status report as sent to the Web GUI type Status struct { ActiveListeners []string `json:"active_listeners"` Connected bool `json:"connected"` Dialing bool `json:"dialing"` RemoteAddr string `json:"remote_addr"` HTTPClients []string `json:"http_clients"` } // Progress represents a progress report as sent to the Web GUI type Progress struct { BytesTransferred int `json:"bytes_transferred"` BytesTotal int `json:"bytes_total"` MID string `json:"mid"` Subject string `json:"subject"` Receiving bool `json:"receiving"` Sending bool `json:"sending"` Done bool `json:"done"` } // Notification represents a desktop notification as sent to the Web GUI type Notification struct { Title string `json:"title"` Body string `json:"body"` } type HTTPError struct { error StatusCode int } var websocketHub *WSHub func init() { var err error staticContent, err = fs.Sub(embeddedFS, "web") if err != nil { panic(err) } } func devServerAddr() string { return strings.TrimSuffix(os.Getenv("PAT_WEB_DEV_ADDR"), "/") } func ListenAndServe(ctx context.Context, addr string) error { log.Printf("Starting HTTP service (http://%s)...", addr) if host, _, _ := net.SplitHostPort(addr); host == "" && config.GPSd.EnableHTTP { // TODO: maybe make a popup showing the warning ont the web UI? _, _ = fmt.Fprintf(logWriter, "\nWARNING: You have enable GPSd HTTP endpoint (enable_http). You might expose"+ "\n your current position to anyone who has access to the Pat web interface!\n\n") } r := mux.NewRouter() r.HandleFunc("/api/bandwidths", bandwidthsHandler).Methods("GET") r.HandleFunc("/api/connect_aliases", connectAliasesHandler).Methods("GET") r.HandleFunc("/api/connect", ConnectHandler) r.HandleFunc("/api/formcatalog", formsMgr.GetFormsCatalogHandler).Methods("GET") r.HandleFunc("/api/form", formsMgr.PostFormDataHandler).Methods("POST") r.HandleFunc("/api/form", formsMgr.GetFormDataHandler).Methods("GET") r.HandleFunc("/api/forms", formsMgr.GetFormTemplateHandler).Methods("GET") r.HandleFunc("/api/formsUpdate", formsMgr.UpdateFormTemplatesHandler).Methods("POST") r.HandleFunc("/api/disconnect", DisconnectHandler) r.HandleFunc("/api/mailbox/{box}", mailboxHandler).Methods("GET") r.HandleFunc("/api/mailbox/{box}/{mid}", messageHandler).Methods("GET") r.HandleFunc("/api/mailbox/{box}/{mid}", messageDeleteHandler).Methods("DELETE") r.HandleFunc("/api/mailbox/{box}/{mid}/{attachment}", attachmentHandler).Methods("GET") r.HandleFunc("/api/mailbox/{box}/{mid}/read", readHandler).Methods("POST") r.HandleFunc("/api/mailbox/{box}", postMessageHandler).Methods("POST") r.HandleFunc("/api/posreport", postPositionHandler).Methods("POST") r.HandleFunc("/api/status", statusHandler).Methods("GET") r.HandleFunc("/api/current_gps_position", positionHandler).Methods("GET") r.HandleFunc("/api/qsy", qsyHandler).Methods("POST") r.HandleFunc("/api/rmslist", rmslistHandler).Methods("GET") r.PathPrefix("/dist/").Handler(distHandler()) r.HandleFunc("/ws", wsHandler) r.HandleFunc("/ui", uiHandler()).Methods("GET") r.HandleFunc("/", rootHandler).Methods("GET") websocketHub = NewWSHub() srv := http.Server{ Addr: addr, Handler: r, } errs := make(chan error, 1) go func() { errs <- srv.ListenAndServe() }() select { case <-ctx.Done(): log.Println("Shutting down HTTP server...") ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) defer cancel() srv.Shutdown(ctx) return nil case err := <-errs: return err } } func distHandler() http.Handler { switch target := devServerAddr(); { case target != "": targetURL, err := url.Parse(target) if err != nil { log.Fatalf("invalid proxy target URL: %v", err) } return httputil.NewSingleHostReverseProxy(targetURL) default: return http.FileServer(http.FS(staticContent)) } } func rootHandler(w http.ResponseWriter, r *http.Request) { http.Redirect(w, r, "/ui", http.StatusFound) } func connectAliasesHandler(w http.ResponseWriter, _ *http.Request) { _ = json.NewEncoder(w).Encode(config.ConnectAliases) } func readHandler(w http.ResponseWriter, r *http.Request) { var data struct{ Read bool } if err := json.NewDecoder(r.Body).Decode(&data); err != nil { http.Error(w, err.Error(), http.StatusBadRequest) log.Printf("%s %s: %s", r.Method, r.URL.Path, err) return } box, mid := mux.Vars(r)["box"], mux.Vars(r)["mid"] msg, err := mailbox.OpenMessage(path.Join(mbox.MBoxPath, box, mid+mailbox.Ext)) if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } if err := mailbox.SetUnread(msg, !data.Read); err != nil { log.Printf("%s %s: %s", r.Method, r.URL.Path, err) http.Error(w, err.Error(), http.StatusInternalServerError) } } func postPositionHandler(w http.ResponseWriter, r *http.Request) { var pos catalog.PosReport if err := json.NewDecoder(r.Body).Decode(&pos); err != nil { http.Error(w, err.Error(), http.StatusBadRequest) return } _ = r.Body.Close() if pos.Date.IsZero() { pos.Date = time.Now() } // Post to outbox msg := pos.Message(fOptions.MyCall) if err := mbox.AddOut(msg); err != nil { log.Println(err) http.Error(w, err.Error(), http.StatusInternalServerError) } else { _, _ = fmt.Fprintln(w, "Position update posted") } } func isInPath(base string, path string) error { _, err := filepath.Rel(base, path) return err } func postMessageHandler(w http.ResponseWriter, r *http.Request) { box := mux.Vars(r)["box"] if box == "out" { postOutboundMessageHandler(w, r) return } srcPath := r.Header.Get("X-Pat-SourcePath") if srcPath == "" { http.Error(w, "Not implemented", http.StatusNotImplemented) return } srcPath, _ = url.PathUnescape(strings.TrimPrefix(srcPath, "/api/mailbox/")) srcPath = filepath.Join(mbox.MBoxPath, srcPath+mailbox.Ext) // Check that we don't escape our mailbox path srcPath = filepath.Clean(srcPath) if err := isInPath(mbox.MBoxPath, srcPath); err != nil { log.Println("Malicious source path in move:", err) http.Error(w, err.Error(), http.StatusBadRequest) return } targetPath := filepath.Join(mbox.MBoxPath, box, filepath.Base(srcPath)) if err := os.Rename(srcPath, targetPath); err != nil { log.Println("Could not move message:", err) http.Error(w, err.Error(), http.StatusBadRequest) } else { _ = json.NewEncoder(w).Encode("OK") } } func postOutboundMessageHandler(w http.ResponseWriter, r *http.Request) { err := r.ParseMultipartForm(10 * (1024 ^ 2)) // 10Mb if err != nil { if err := r.ParseForm(); err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } } msg := fbb.NewMessage(fbb.Private, fOptions.MyCall) // files if r.MultipartForm != nil { files := r.MultipartForm.File["files"] for _, f := range files { err := attachFile(f, msg) switch err := err.(type) { case nil: // No problem case HTTPError: http.Error(w, err.Error(), err.StatusCode) default: http.Error(w, err.Error(), http.StatusInternalServerError) } } } cookie, err := r.Cookie("forminstance") if err == nil { formData := formsMgr.GetPostedFormData(cookie.Value) if xml := formData.MsgXML; xml != "" { name := formsMgr.GetXMLAttachmentNameForForm(formData.TargetForm, formData.IsReply) msg.AddFile(fbb.NewFile(name, []byte(formData.MsgXML))) } } // Other fields if v := r.Form["to"]; len(v) == 1 { addrs := strings.FieldsFunc(v[0], SplitFunc) msg.AddTo(addrs...) } if v := r.Form["cc"]; len(v) == 1 { addrs := strings.FieldsFunc(v[0], SplitFunc) msg.AddCc(addrs...) } if v := r.Form["subject"]; len(v) == 1 { msg.SetSubject(v[0]) } if v := r.Form["body"]; len(v) == 1 { _ = msg.SetBody(v[0]) } if v := r.Form["p2ponly"]; len(v) == 1 && v[0] != "" { msg.Header.Set("X-P2POnly", "true") } if v := r.Form["date"]; len(v) == 1 { t, err := time.Parse(time.RFC3339, v[0]) if err != nil { log.Printf("Unable to parse message date: %s", err) http.Error(w, err.Error(), http.StatusBadRequest) return } msg.SetDate(t) } else { log.Printf("Missing date value") http.Error(w, "Missing date value", http.StatusBadRequest) return } if err := msg.Validate(); err != nil { http.Error(w, "Validation error: "+err.Error(), http.StatusBadRequest) return } // Post to outbox if err := mbox.AddOut(msg); err != nil { log.Println(err) http.Error(w, err.Error(), http.StatusInternalServerError) return } w.WriteHeader(http.StatusCreated) var buf bytes.Buffer _ = msg.Write(&buf) _, _ = fmt.Fprintf(w, "Message posted (%.2f kB)", float64(buf.Len()/1024)) } func attachFile(f *multipart.FileHeader, msg *fbb.Message) error { // For some unknown reason, we receive this empty unnamed file when no // attachment is provided. Prior to Go 1.10, this was filtered by // multipart.Reader. if f.Size == 0 && f.Filename == "" { return nil } if f.Filename == "" { err := errors.New("missing attachment name") return HTTPError{err, http.StatusBadRequest} } file, err := f.Open() if err != nil { return HTTPError{err, http.StatusInternalServerError} } p, err := io.ReadAll(file) _ = file.Close() if err != nil { return HTTPError{err, http.StatusInternalServerError} } if isConvertableImageMediaType(f.Filename, f.Header.Get("Content-Type")) { log.Printf("Auto converting '%s' [%s]...", f.Filename, f.Header.Get("Content-Type")) if converted, err := convertImage(p); err != nil { log.Printf("Error converting image: %s", err) } else { log.Printf("Done converting '%s'.", f.Filename) ext := path.Ext(f.Filename) f.Filename = f.Filename[:len(f.Filename)-len(ext)] + ".jpg" p = converted } } msg.AddFile(fbb.NewFile(f.Filename, p)) return nil } func wsHandler(w http.ResponseWriter, r *http.Request) { upgrader := websocket.Upgrader{ ReadBufferSize: 1024, WriteBufferSize: 1024, } conn, err := upgrader.Upgrade(w, r, nil) if err != nil { log.Println(err) return } _ = conn.WriteJSON(struct{ MyCall string }{fOptions.MyCall}) websocketHub.Handle(conn) } func uiHandler() http.HandlerFunc { const indexPath = "dist/index.html" templateFunc := func() ([]byte, error) { return fs.ReadFile(staticContent, indexPath) } if target := devServerAddr(); target != "" { templateFunc = func() ([]byte, error) { resp, err := http.Get(target + "/" + indexPath) if err != nil { return nil, fmt.Errorf("dev server not reachable: %w", err) } defer resp.Body.Close() return io.ReadAll(resp.Body) } } return func(w http.ResponseWriter, _ *http.Request) { data, err := templateFunc() if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } t, err := template.New("index.html").Parse(string(data)) if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } tmplData := struct{ AppName, Version, Mycall string }{buildinfo.AppName, buildinfo.VersionString(), fOptions.MyCall} if err := t.Execute(w, tmplData); err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } } } func getStatus() Status { status := Status{ ActiveListeners: []string{}, Dialing: dialing != nil, Connected: exchangeConn != nil, HTTPClients: websocketHub.ClientAddrs(), } for _, tl := range listenHub.Active() { status.ActiveListeners = append(status.ActiveListeners, tl.Name()) } sort.Strings(status.ActiveListeners) if exchangeConn != nil { addr := exchangeConn.RemoteAddr() status.RemoteAddr = fmt.Sprintf("%s:%s", addr.Network(), addr) } return status } func statusHandler(w http.ResponseWriter, _ *http.Request) { _ = json.NewEncoder(w).Encode(getStatus()) } func bandwidthsHandler(w http.ResponseWriter, req *http.Request) { type BandwidthResponse struct { Mode string `json:"mode"` Bandwidths []string `json:"bandwidths"` Default string `json:"default,omitempty"` } mode := strings.ToLower(req.FormValue("mode")) resp := BandwidthResponse{Mode: mode, Bandwidths: []string{}} switch { case mode == MethodArdop: for _, bw := range ardop.Bandwidths() { resp.Bandwidths = append(resp.Bandwidths, bw.String()) } if bw := config.Ardop.ARQBandwidth; !bw.IsZero() { resp.Default = bw.String() } case mode == MethodVaraHF: resp.Bandwidths = vara.Bandwidths() if bw := config.VaraHF.Bandwidth; bw != 0 { resp.Default = fmt.Sprintf("%d", bw) } } _ = json.NewEncoder(w).Encode(resp) } func rmslistHandler(w http.ResponseWriter, req *http.Request) { forceDownload, _ := strconv.ParseBool(req.FormValue("force-download")) band := req.FormValue("band") mode := strings.ToLower(req.FormValue("mode")) prefix := strings.ToUpper(req.FormValue("prefix")) list, err := ReadRMSList(req.Context(), forceDownload, func(r RMS) bool { switch { case r.URL == nil: return false case mode != "" && !r.IsMode(mode): return false case band != "" && !r.IsBand(band): return false case prefix != "" && !strings.HasPrefix(r.Callsign, prefix): return false default: return true } }) if err != nil { log.Println(err) http.Error(w, err.Error(), http.StatusInternalServerError) return } sort.Sort(byDist(list)) err = json.NewEncoder(w).Encode(list) if err != nil { log.Println(err) http.Error(w, err.Error(), http.StatusInternalServerError) } } func qsyHandler(w http.ResponseWriter, req *http.Request) { type QSYPayload struct { Transport string `json:"transport"` Freq json.Number `json:"freq"` } var payload QSYPayload if err := json.NewDecoder(req.Body).Decode(&payload); err != nil { http.Error(w, err.Error(), http.StatusBadRequest) return } rig, rigName, ok, err := VFOForTransport(payload.Transport) switch { case rigName == "": // Either unsupported mode or no rig configured for this transport w.WriteHeader(http.StatusServiceUnavailable) return case !ok: // A rig is configured, but not loaded properly w.WriteHeader(http.StatusInternalServerError) log.Printf("QSY failed: Hamlib rig '%s' not loaded.", rigName) case err != nil: w.WriteHeader(http.StatusInternalServerError) log.Printf("QSY failed: %v", err) default: if _, _, err := setFreq(rig, string(payload.Freq)); err != nil { w.WriteHeader(http.StatusInternalServerError) log.Printf("QSY failed: %v", err) return } _ = json.NewEncoder(w).Encode(payload) } } func positionHandler(w http.ResponseWriter, req *http.Request) { // Throw error if GPSd http endpoint is not enabled if !config.GPSd.EnableHTTP || config.GPSd.Addr == "" { http.Error(w, "GPSd not enabled or address not set in config file", http.StatusInternalServerError) return } host, _, _ := net.SplitHostPort(req.RemoteAddr) log.Printf("Location data from GPSd served to %s", host) conn, err := gpsd.Dial(config.GPSd.Addr) if err != nil { // do not pass error message to response as GPSd address might be leaked http.Error(w, "GPSd Dial failed", http.StatusInternalServerError) return } defer conn.Close() conn.Watch(true) pos, err := conn.NextPosTimeout(5 * time.Second) if err != nil { http.Error(w, "GPSd get next position failed: "+err.Error(), http.StatusInternalServerError) return } if config.GPSd.UseServerTime { pos.Time = time.Now() } _ = json.NewEncoder(w).Encode(pos) } func DisconnectHandler(w http.ResponseWriter, req *http.Request) { dirty, _ := strconv.ParseBool(req.FormValue("dirty")) if ok := abortActiveConnection(dirty); !ok { w.WriteHeader(http.StatusBadRequest) } _ = json.NewEncoder(w).Encode(struct{}{}) } func ConnectHandler(w http.ResponseWriter, req *http.Request) { connectStr := req.FormValue("url") nMsgs := mbox.InboxCount() if success := Connect(connectStr); !success { http.Error(w, "Session failure", http.StatusInternalServerError) } _ = json.NewEncoder(w).Encode(struct { NumReceived int }{ mbox.InboxCount() - nMsgs, }) } func mailboxHandler(w http.ResponseWriter, r *http.Request) { box := mux.Vars(r)["box"] var messages []*fbb.Message var err error switch box { case "in": messages, err = mbox.Inbox() case "out": messages, err = mbox.Outbox() case "sent": messages, err = mbox.Sent() case "archive": messages, err = mbox.Archive() default: http.NotFound(w, r) return } if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) log.Println(err) } sort.Sort(sort.Reverse(fbb.ByDate(messages))) jsonSlice := make([]JSONMessage, len(messages)) for i, msg := range messages { jsonSlice[i] = JSONMessage{Message: msg} } _ = json.NewEncoder(w).Encode(jsonSlice) } type JSONMessage struct { *fbb.Message inclBody bool } func (m JSONMessage) MarshalJSON() ([]byte, error) { msg := struct { MID string Date time.Time From fbb.Address To []fbb.Address Cc []fbb.Address Subject string Body string BodyHTML string Files []*fbb.File P2POnly bool Unread bool }{ MID: m.MID(), Date: m.Date(), From: m.From(), To: m.To(), Cc: m.Cc(), Subject: m.Subject(), Files: m.Files(), P2POnly: m.Header.Get("X-P2POnly") == "true", Unread: mailbox.IsUnread(m.Message), } if m.inclBody { msg.Body, _ = m.Body() unsafe := toHTML([]byte(msg.Body)) msg.BodyHTML = string(bluemonday.UGCPolicy().SanitizeBytes(unsafe)) } return json.Marshal(msg) } func messageDeleteHandler(w http.ResponseWriter, r *http.Request) { box, mid := mux.Vars(r)["box"], mux.Vars(r)["mid"] file := filepath.Clean(filepath.Join(mbox.MBoxPath, box, mid+mailbox.Ext)) if err := isInPath(mbox.MBoxPath, file); err != nil { log.Println("Malicious source path in move:", err) http.Error(w, err.Error(), http.StatusBadRequest) return } err := os.Remove(file) if os.IsNotExist(err) { http.NotFound(w, r) return } else if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) } _ = json.NewEncoder(w).Encode("OK") } func messageHandler(w http.ResponseWriter, r *http.Request) { box, mid := mux.Vars(r)["box"], mux.Vars(r)["mid"] msg, err := mailbox.OpenMessage(path.Join(mbox.MBoxPath, box, mid+mailbox.Ext)) if os.IsNotExist(err) { http.NotFound(w, r) return } else if err != nil { log.Println(err) http.Error(w, err.Error(), http.StatusInternalServerError) return } _ = json.NewEncoder(w).Encode(JSONMessage{msg, true}) } func attachmentHandler(w http.ResponseWriter, r *http.Request) { // Attachments are potentially unsanitized HTML and/or javascript. // To avoid XSS, we enable the CSP sandbox directive so that these // attachments can't call other parts of the API (deny same origin). w.Header().Set("Content-Security-Policy", "sandbox allow-forms allow-modals allow-orientation-lock allow-pointer-lock allow-popups allow-popups-to-escape-sandbox allow-presentation allow-scripts") // Allow different sandboxed attachments to refer to each other. // This can be useful to provide rich HTML content as attachments, // without having to bundle it all up in one big file. w.Header().Set("Access-Control-Allow-Origin", "null") box, mid, attachment := mux.Vars(r)["box"], mux.Vars(r)["mid"], mux.Vars(r)["attachment"] composereply, _ := strconv.ParseBool(r.URL.Query().Get("composereply")) renderToHtml, _ := strconv.ParseBool(r.URL.Query().Get("rendertohtml")) if composereply || renderToHtml { // no-store is needed for displaying and replying to Winlink form-based messages w.Header().Set("Cache-Control", "no-store") } msg, err := mailbox.OpenMessage(path.Join(mbox.MBoxPath, box, mid+mailbox.Ext)) if os.IsNotExist(err) { http.NotFound(w, r) return } else if err != nil { log.Println(err) http.Error(w, err.Error(), http.StatusInternalServerError) return } // Find and write attachment var found bool for _, f := range msg.Files() { if f.Name() != attachment { continue } found = true if !renderToHtml { http.ServeContent(w, r, f.Name(), msg.Date(), bytes.NewReader(f.Data())) return } formRendered, err := formsMgr.RenderForm(f.Data(), composereply) if err != nil { log.Println(err) http.Error(w, err.Error(), http.StatusInternalServerError) return } http.ServeContent(w, r, f.Name()+".html", msg.Date(), bytes.NewReader([]byte(formRendered))) } if !found { http.NotFound(w, r) } } // toHTML takes the given body and turns it into proper html with // paragraphs, blockquote, and
line breaks. func toHTML(body []byte) []byte { buf := bytes.NewBuffer(body) var out bytes.Buffer _, _ = fmt.Fprint(&out, "

") scanner := bufio.NewScanner(buf) var blockquote int for scanner.Scan() { line := scanner.Text() if len(line) == 0 { _, _ = fmt.Fprint(&out, "

") continue } depth := blockquoteDepth(line) for depth != blockquote { if depth > blockquote { _, _ = fmt.Fprintf(&out, "

") blockquote++ } else { _, _ = fmt.Fprintf(&out, "

") blockquote-- } } line = line[depth:] line = htmlEncode(line) line = linkify(line) _, _ = fmt.Fprint(&out, line+"\n") } for ; blockquote > 0; blockquote-- { _, _ = fmt.Fprintf(&out, "

") } _, _ = fmt.Fprint(&out, "

") return out.Bytes() } // blcokquoteDepth counts the number of '>' at the beginning of the string. func blockquoteDepth(str string) (n int) { for _, c := range str { if c != '>' { break } n++ } return } // htmlEncode encodes html characters func htmlEncode(str string) string { str = strings.ReplaceAll(str, ">", ">") str = strings.ReplaceAll(str, "<", "<") return str } // linkify detects url's in the given string and adds %s%s`, str[:start], link, str[start:end], linkify(str[end:])) } pat-0.15.1/interactive.go000066400000000000000000000066641453425652100152510ustar00rootroot00000000000000// Copyright 2016 Martin Hebnes Pedersen (LA5NTA). All rights reserved. // Use of this source code is governed by the MIT-license that can be // found in the LICENSE file. package main import ( "bytes" "context" "fmt" "log" "os" "runtime" "strings" "time" "github.com/la5nta/wl2k-go/transport/ax25" "github.com/peterh/liner" ) func Interactive(ctx context.Context) { line := liner.NewLiner() defer line.Close() done := make(chan struct{}) go func() { defer close(done) for { str, _ := line.Prompt(getPrompt()) if str == "" { continue } line.AppendHistory(str) if str[0] == '#' { continue } if quit := execCmd(str); quit { break } } }() select { case <-ctx.Done(): case <-done: } } func execCmd(line string) (quit bool) { cmd, param := parseCommand(line) switch cmd { case "connect": if param == "" { printInteractiveUsage() return } Connect(param) case "listen": Listen(param) case "unlisten": Unlisten(param) case "heard": PrintHeard() case "freq": freq(param) case "qtc": PrintQTC() case "debug": os.Setenv("ardop_debug", "1") fmt.Println("Number of goroutines:", runtime.NumGoroutine()) case "q", "quit": return true case "": return default: printInteractiveUsage() } return } func printInteractiveUsage() { fmt.Println("Uri examples: 'LA3F@5350', 'LA1B-10 v LA5NTA-1', 'LA5NTA:secret@192.168.1.1:54321'") methods := []string{ MethodArdop, MethodAX25, MethodAX25AGWPE, MethodAX25Linux, MethodAX25SerialTNC, MethodPactor, MethodTelnet, MethodVaraHF, MethodVaraFM, } fmt.Println("Methods:", strings.Join(methods, ", ")) cmds := []string{ "connect METHOD:[URI] or alias Connect to a remote station.", "listen METHOD Listen for incoming connections.", "unlisten METHOD Unregister listener for incoming connections.", "freq METHOD:FREQ Change rig frequency.", "heard Display all stations heard over the air.", "qtc Print pending outbound messages.", } fmt.Println("Commands: ") for _, cmd := range cmds { fmt.Printf(" %s\n", cmd) } } func getPrompt() string { var buf bytes.Buffer status := getStatus() if len(status.ActiveListeners) > 0 { fmt.Fprintf(&buf, "L%v", status.ActiveListeners) } fmt.Fprint(&buf, "> ") return buf.String() } func PrintHeard() { pf := func(call string, t time.Time) { fmt.Printf(" %-10s (%s)\n", call, t.Format(time.RFC1123)) } fmt.Println("ardop:") if adTNC == nil { fmt.Println(" (not initialized)") } else if heard := adTNC.Heard(); len(heard) == 0 { fmt.Println(" (none)") } else { for call, t := range heard { pf(call, t) } } fmt.Println("ax25+linux:") if heard, err := ax25.Heard(config.AX25Linux.Port); err != nil { fmt.Printf(" (%s)\n", err) } else if len(heard) == 0 { fmt.Println(" (none)") } else { for call, t := range heard { pf(call, t) } } } func PrintQTC() { msgs, err := mbox.Outbox() if err != nil { log.Println(err) return } fmt.Printf("QTC: %d.\n", len(msgs)) for _, msg := range msgs { fmt.Printf(`%-12.12s (%s): %s`, msg.MID(), msg.Subject(), fmt.Sprint(msg.To())) if msg.Header.Get("X-P2POnly") == "true" { fmt.Printf(" (P2P only)") } fmt.Println("") } } func parseCommand(str string) (mode, param string) { parts := strings.SplitN(str, " ", 2) if len(parts) == 1 { return parts[0], "" } return parts[0], parts[1] } pat-0.15.1/internal/000077500000000000000000000000001453425652100142055ustar00rootroot00000000000000pat-0.15.1/internal/buildinfo/000077500000000000000000000000001453425652100161605ustar00rootroot00000000000000pat-0.15.1/internal/buildinfo/VERSION.go000066400000000000000000000010521453425652100176320ustar00rootroot00000000000000// Copyright 2017 Martin Hebnes Pedersen (LA5NTA). All rights reserved. // Use of this source code is governed by the MIT-license that can be // found in the LICENSE file. package buildinfo const ( // AppName is the friendly name of the app. // // Forks should consider using a different name. AppName = "Pat" // Version is the app's SemVer. // // Forks should NOT bump this unless they use a unique AppName. The Winlink // system uses this to derive the "these users should upgrade" wall of shame // from CMS connects. Version = "0.15.1" ) pat-0.15.1/internal/buildinfo/gitrev.go000066400000000000000000000006071453425652100200120ustar00rootroot00000000000000//go:build go1.18 // +build go1.18 package buildinfo import "runtime/debug" // GitRev is the git commit hash that the binary was built at. var GitRev = func() string { if info, ok := debug.ReadBuildInfo(); ok { for _, setting := range info.Settings { if setting.Key == "vcs.revision" && len(setting.Value) > 7 { return setting.Value[:7] } } } return "unknown origin" }() pat-0.15.1/internal/buildinfo/gitrev_legacy.go000066400000000000000000000002521453425652100213320ustar00rootroot00000000000000//go:build !go1.18 // +build !go1.18 package buildinfo // GitRev is the git commit hash that the binary was built at. var GitRev = "unknown origin" // Set by make.bash pat-0.15.1/internal/buildinfo/strings.go000066400000000000000000000013751453425652100202060ustar00rootroot00000000000000package buildinfo import ( "fmt" "runtime" ) // VersionString returns a very descriptive version including the app SemVer, git rev plus the // Golang OS, architecture and version. func VersionString() string { return fmt.Sprintf("%s %s/%s - %s", VersionStringShort(), runtime.GOOS, runtime.GOARCH, runtime.Version()) } // VersionStringShort returns the app SemVer and git rev. func VersionStringShort() string { return fmt.Sprintf("v%s (%s)", Version, GitRev) } // UserAgent returns a suitable HTTP user agent string containing app name, SemVer, git rev, plus // the Golang OS, architecture and version. func UserAgent() string { return fmt.Sprintf("%v/%v (%v) %v (%v; %v)", AppName, Version, GitRev, runtime.Version(), runtime.GOOS, runtime.GOARCH) } pat-0.15.1/internal/cmsapi/000077500000000000000000000000001453425652100154615ustar00rootroot00000000000000pat-0.15.1/internal/cmsapi/api.go000066400000000000000000000110531453425652100165610ustar00rootroot00000000000000// Copyright 2016 Martin Hebnes Pedersen (LA5NTA). All rights reserved. // Use of this source code is governed by the MIT-license that can be // found in the LICENSE file. package cmsapi import ( "bytes" "context" "encoding/json" "fmt" "io" "log" "net/http" "net/url" "os" "strings" "time" ) const ( RootURL = "https://api.winlink.org" PathVersionAdd = "/version/add" PathGatewayStatus = "/gateway/status.json" PathAccountExists = "/account/exists" // AccessKey issued December 2017 by the WDT for use with Pat AccessKey = "1880278F11684B358F36845615BD039A" ) type VersionAdd struct { Callsign string `json:"callsign"` Program string `json:"program"` Version string `json:"version"` Comments string `json:"comments,omitempty"` } func (v VersionAdd) Post() error { b, _ := json.Marshal(v) buf := bytes.NewBuffer(b) versionURL := RootURL + PathVersionAdd + "?key=" + AccessKey req, _ := http.NewRequest("POST", versionURL, buf) req.Header.Set("content-type", "application/json") req.Header.Set("accept", "application/json") resp, err := http.DefaultClient.Do(req) if err != nil { return err } defer resp.Body.Close() var response map[string]interface{} if err := json.NewDecoder(resp.Body).Decode(&response); err != nil { return err } if errMsg, ok := response["ErrorMessage"]; ok { return fmt.Errorf("Winlink CMS Web Services: %s", errMsg) } return nil } func AccountExists(callsign string) (bool, error) { accountURL := RootURL + PathAccountExists + "?key=" + AccessKey + "&callsign=" + url.QueryEscape(callsign) req, _ := http.NewRequest("GET", accountURL, nil) req.Header.Set("Accept", "application/json") resp, err := http.DefaultClient.Do(req) if err != nil { return false, err } defer resp.Body.Close() var obj struct{ CallsignExists bool } return obj.CallsignExists, json.NewDecoder(resp.Body).Decode(&obj) } type GatewayStatus struct { ServerName string `json:"ServerName"` ErrorCode int `json:"ErrorCode"` Gateways []Gateway `json:"Gateways"` } type Gateway struct { Callsign string BaseCallsign string RequestedMode string Comments string LastStatus RFC1123Time Latitude float64 Longitude float64 Channels []GatewayChannel `json:"GatewayChannels"` } type GatewayChannel struct { OperatingHours string SupportedModes string Frequency float64 ServiceCode string Baud string RadioRange string Mode int Gridsquare string Antenna string } type RFC1123Time struct{ time.Time } // GetGatewayStatus fetches the gateway status list returned by GatewayStatusUrl // // mode can be any of [packet, pactor, robustpacket, allhf or anyall]. Empty is AnyAll. // historyHours is the number of hours of history to include (maximum: 48). If < 1, then API default is used. // serviceCodes defaults to "PUBLIC". func GetGatewayStatus(ctx context.Context, mode string, historyHours int, serviceCodes ...string) (io.ReadCloser, error) { switch { case mode == "": mode = "AnyAll" case historyHours > 48: historyHours = 48 case len(serviceCodes) == 0: serviceCodes = []string{"PUBLIC"} } params := url.Values{"Mode": {mode}} params.Set("key", AccessKey) if historyHours >= 0 { params.Add("HistoryHours", fmt.Sprintf("%d", historyHours)) } for _, str := range serviceCodes { params.Add("ServiceCodes", str) } req, err := http.NewRequestWithContext(ctx, "POST", RootURL+PathGatewayStatus, strings.NewReader(params.Encode())) if err != nil { return nil, err } req.Header.Set("Content-Type", "application/x-www-form-urlencoded") resp, err := http.DefaultClient.Do(req) switch { case err != nil: return nil, err case resp.StatusCode != http.StatusOK: return nil, fmt.Errorf("unexpected http status '%v'", resp.Status) } return resp.Body, err } func GetGatewayStatusCached(ctx context.Context, cacheFile string, forceDownload bool, serviceCodes ...string) (io.ReadCloser, error) { if !forceDownload { file, err := os.Open(cacheFile) if err == nil { return file, nil } } log.Println("Downloading latest gateway status information...") fresh, err := GetGatewayStatus(ctx, "", 48, serviceCodes...) if err != nil { return nil, err } file, err := os.Create(cacheFile) if err != nil { return nil, err } _, err = io.Copy(file, fresh) file.Seek(0, 0) if err == nil { log.Println("download succeeded.") } return file, err } func (t *RFC1123Time) UnmarshalJSON(b []byte) (err error) { var str string if err = json.Unmarshal(b, &str); err != nil { return err } t.Time, err = time.Parse(time.RFC1123, str) return err } pat-0.15.1/internal/debug/000077500000000000000000000000001453425652100152735ustar00rootroot00000000000000pat-0.15.1/internal/debug/debug.go000066400000000000000000000004541453425652100167130ustar00rootroot00000000000000package debug import ( "log" "os" "strconv" ) const ( EnvVar = "PAT_DEBUG" Prefix = "[DEBUG] " ) var enabled bool func init() { enabled, _ = strconv.ParseBool(os.Getenv(EnvVar)) } func Printf(format string, v ...interface{}) { if !enabled { return } log.Printf(Prefix+format, v...) } pat-0.15.1/internal/directories/000077500000000000000000000000001453425652100165215ustar00rootroot00000000000000pat-0.15.1/internal/directories/directories.go000066400000000000000000000067251453425652100213760ustar00rootroot00000000000000package directories import ( "errors" "log" "os" "path/filepath" "strings" "sync" "github.com/la5nta/pat/internal/buildinfo" "github.com/la5nta/pat/internal/debug" "github.com/adrg/xdg" ) var ( lock = &sync.Mutex{} dataPath string configPath string statePath string ) func DataDir() string { return getDir(&dataPath, xdg.DataHome, "DataDir") } func ConfigDir() string { return getDir(&configPath, xdg.ConfigHome, "ConfigDir") } func StateDir() string { return getDir(&statePath, xdg.StateHome, "StateDir") } func getDir(dir *string, basePath string, methodName string) string { lock.Lock() defer lock.Unlock() if *dir == "" { initDir(dir, basePath, methodName) } return *dir } func initDir(dir *string, basePath string, methodName string) { *dir = filepath.Join(basePath, strings.ToLower(buildinfo.AppName)) if _, err := os.Stat(*dir); os.IsNotExist(err) { err := os.MkdirAll(*dir, os.ModeDir|0o755) if err != nil { log.Fatalf("unable to create or open %s %s: %v", methodName, *dir, err) } } } func MigrateLegacyDataDir() { if f, err := os.Stat(ConfigDir()); err != nil && f.IsDir() { debug.Printf("new config directory %s already exists, we have already migrated", ConfigDir()) return } homeDir, err := os.UserHomeDir() if err != nil { log.Fatal(err) } legacyDataDir := filepath.Join(homeDir, ".wl2k") switch f, err := os.Stat(legacyDataDir); { case os.IsNotExist(err): debug.Printf("tried to migrate from %s but it doesn't exist; nothing to do", legacyDataDir) return case err != nil: log.Fatal(err) case !f.IsDir(): log.Printf("tried to migrate from %s but it's not a directory, that's weird; ignoring", legacyDataDir) return } log.Printf("Migrating your Pat files from %s to new locations", legacyDataDir) if err = migrateFile("config.json", legacyDataDir, ConfigDir()); err != nil { log.Fatal(err) } if err = migrateFile("mailbox", legacyDataDir, DataDir()); err != nil { log.Fatal(err) } if err = migrateFile("Standard_Forms", legacyDataDir, DataDir()); err != nil { log.Fatal(err) } matches, err := filepath.Glob(filepath.Join(legacyDataDir, "rmslist*.json")) if err != nil { log.Fatal(err) } for _, match := range matches { _, f := filepath.Split(match) if err = migrateFile(f, legacyDataDir, DataDir()); err != nil { log.Fatal(err) } } debug.Printf("migration from %s finished, renaming it", legacyDataDir) err = os.Rename(legacyDataDir, legacyDataDir+"-old") if err != nil { log.Fatal(err) } } func migrateFile(fileName string, fromDir string, toDir string) error { // make sure the old file is there fromFile := filepath.Join(fromDir, fileName) if _, err := os.Stat(fromFile); errors.Is(err, os.ErrNotExist) { // no legacy file, nothing to do debug.Printf("File %s doesn't exist, not migrating it", fromFile) return nil } else if err != nil { return err } // touch the new file to make sure it's not there, and we can write to it toFile := filepath.Join(toDir, fileName) switch f, err := os.OpenFile(toFile, os.O_RDWR|os.O_CREATE|os.O_EXCL, 0o666); { case errors.Is(err, os.ErrExist): // new file already exists, don't clobber it debug.Printf("new file %s already exists; ignoring %s", toFile, fromFile) return nil case err != nil: return err default: if err := f.Close(); err != nil { return err } if err := os.Remove(toFile); err != nil { return err } } debug.Printf("Migrating %s from %s to %s", fileName, fromDir, toDir) return os.Rename(fromFile, toFile) } pat-0.15.1/internal/forms/000077500000000000000000000000001453425652100153335ustar00rootroot00000000000000pat-0.15.1/internal/forms/date.go000066400000000000000000000012351453425652100166000ustar00rootroot00000000000000package forms import ( "strings" "time" ) func formatDateTime(t time.Time) string { return t.Format("2006-01-02 15:04:05") } func formatDateTimeUTC(t time.Time) string { return t.UTC().Format("2006-01-02 15:04:05Z07:00") } func formatDate(t time.Time) string { return t.Format("2006-01-02") } func formatTime(t time.Time) string { return t.Format("15:04:05") } func formatDateUTC(t time.Time) string { return t.UTC().Format("2006-01-02Z07:00") } func formatTimeUTC(t time.Time) string { return t.UTC().Format("15:04:05Z07:00") } func formatUDTG(t time.Time) string { return strings.ToUpper(t.UTC().Format("021504Z07:00 Jan 2006")) } pat-0.15.1/internal/forms/date_test.go000066400000000000000000000011671453425652100176430ustar00rootroot00000000000000package forms import ( "testing" "time" ) func TestDateFormat(t *testing.T) { now := time.Date(2023, 12, 31, 23, 59, 59, 0, time.FixedZone("UTC-4", -4*60*60)) tests := []struct { fn func(t time.Time) string expect string }{ {formatDateTime, "2023-12-31 23:59:59"}, {formatDateTimeUTC, "2024-01-01 03:59:59Z"}, {formatDate, "2023-12-31"}, {formatTime, "23:59:59"}, {formatDateUTC, "2024-01-01Z"}, {formatTimeUTC, "03:59:59Z"}, {formatUDTG, "010359Z JAN 2024"}, } for i, tt := range tests { if got := tt.fn(now); got != tt.expect { t.Errorf("%d: got %q expected %q", i, got, tt.expect) } } } pat-0.15.1/internal/forms/forms.go000066400000000000000000001024251453425652100170140ustar00rootroot00000000000000// Copyright 2020 Rainer Grosskopf (KI7RMJ). All rights reserved. // Use of this source code is governed by the MIT-license that can be // found in the LICENSE file. // Processes Winlink-compatible message template (aka Winlink forms) package forms import ( "archive/zip" "bufio" "bytes" "context" "encoding/json" "encoding/xml" "errors" "fmt" "io" "io/ioutil" "log" "math" "net/http" "os" "path" "path/filepath" "regexp" "sort" "strconv" "strings" "sync" "time" "unicode/utf8" "github.com/dimchansky/utfbom" "github.com/la5nta/pat/cfg" "github.com/la5nta/pat/internal/debug" "github.com/la5nta/pat/internal/gpsd" "github.com/pd0mz/go-maidenhead" ) const ( fieldValueFalseInXML = "False" htmlFileExt = ".html" txtFileExt = ".txt" formsVersionInfoURL = "https://api.getpat.io/v1/forms/standard-templates/latest" ) // Manager manages the forms subsystem // When the web frontend POSTs the form template data, this map holds the POST'ed data. // Each form composer instance renders into another browser tab, and has a unique instance cookie. // This instance cookie is the key into the map, so that we can keep the values // from different form authoring sessions separate from each other. type Manager struct { config Config postedFormData struct { sync.RWMutex internalFormDataMap map[string]FormData } } // Config passes config options to the forms package type Config struct { FormsPath string MyCall string Locator string AppVersion string LineReader func() string UserAgent string GPSd cfg.GPSdConfig } // Form holds information about a Winlink form template type Form struct { Name string `json:"name"` TxtFileURI string `json:"txt_file_uri"` InitialURI string `json:"initial_uri"` ViewerURI string `json:"viewer_uri"` ReplyTxtFileURI string `json:"reply_txt_file_uri"` ReplyInitialURI string `json:"reply_initial_uri"` ReplyViewerURI string `json:"reply_viewer_uri"` } // FormFolder is a folder with forms. A tree structure with Form leaves and sub-Folder branches type FormFolder struct { Name string `json:"name"` Path string `json:"path"` Version string `json:"version"` FormCount int `json:"form_count"` Forms []Form `json:"forms"` Folders []FormFolder `json:"folders"` } // FormData holds the instance data that define a filled-in form type FormData struct { TargetForm Form `json:"target_form"` Fields map[string]string `json:"fields"` MsgTo string `json:"msg_to"` MsgCc string `json:"msg_cc"` MsgSubject string `json:"msg_subject"` MsgBody string `json:"msg_body"` MsgXML string `json:"msg_xml"` IsReply bool `json:"is_reply"` Submitted time.Time `json:"submitted"` } // MessageForm represents a concrete form-based message type MessageForm struct { To string Cc string Subject string Body string AttachmentXML string AttachmentName string } // UpdateResponse is the API response format for the upgrade forms endpoint type UpdateResponse struct { NewestVersion string `json:"newestVersion"` Action string `json:"action"` } var client = httpClient{http.Client{Timeout: 10 * time.Second}} // NewManager instantiates the forms manager func NewManager(conf Config) *Manager { _ = os.MkdirAll(conf.FormsPath, 0o755) retval := &Manager{ config: conf, } retval.postedFormData.internalFormDataMap = make(map[string]FormData) return retval } // GetFormsCatalogHandler reads all forms from config.FormsPath and writes them in the http response as a JSON object graph // This lets the frontend present a tree-like GUI for the user to select a form for composing a message func (m *Manager) GetFormsCatalogHandler(w http.ResponseWriter, r *http.Request) { formFolder, err := m.buildFormFolder() if err != nil { http.Error(w, err.Error(), http.StatusBadRequest) log.Printf("%s %s: %s", r.Method, r.URL.Path, err) return } _ = json.NewEncoder(w).Encode(formFolder) } // PostFormDataHandler - When the user is done filling a form, the frontend posts the input fields to this handler, // which stores them in a map, so that other browser tabs can read the values back with GetFormDataHandler func (m *Manager) PostFormDataHandler(w http.ResponseWriter, r *http.Request) { if err := r.ParseMultipartForm(10e6); err != nil { http.Error(w, err.Error(), http.StatusBadRequest) return } formPath := r.URL.Query().Get("formPath") if formPath == "" { http.Error(w, "formPath query param missing", http.StatusBadRequest) log.Printf("formPath query param missing %s %s", r.Method, r.URL.Path) return } composeReply, _ := strconv.ParseBool(r.URL.Query().Get("composereply")) formFolder, err := m.buildFormFolder() if err != nil { http.Error(w, err.Error(), http.StatusBadRequest) log.Printf("%s %s: %s", r.Method, r.URL.Path, err) return } form, err := findFormFromURI(formPath, formFolder) if err != nil { http.Error(w, err.Error(), http.StatusBadRequest) log.Printf("can't find form to match posted form data %s %s", formPath, r.URL) return } formInstanceKey, err := r.Cookie("forminstance") if err != nil { http.Error(w, err.Error(), http.StatusBadRequest) log.Printf("missing cookie %s %s", formPath, r.URL) return } formData := FormData{ IsReply: composeReply, TargetForm: form, Fields: make(map[string]string), } for key, values := range r.PostForm { formData.Fields[strings.TrimSpace(strings.ToLower(key))] = values[0] } formMsg, err := formMessageBuilder{ Template: form, FormValues: formData.Fields, Interactive: false, IsReply: composeReply, FormsMgr: m, }.build() if err != nil { http.Error(w, err.Error(), http.StatusBadRequest) log.Printf("%s %s: %s", r.Method, r.URL.Path, err) } formData.MsgTo = formMsg.To formData.MsgCc = formMsg.Cc formData.MsgSubject = formMsg.Subject formData.MsgBody = formMsg.Body formData.MsgXML = formMsg.AttachmentXML formData.Submitted = time.Now() m.postedFormData.Lock() m.postedFormData.internalFormDataMap[formInstanceKey.Value] = formData m.postedFormData.Unlock() m.cleanupOldFormData() _, _ = io.WriteString(w, "") } // GetFormDataHandler is the counterpart to PostFormDataHandler. Returns the form field values to the frontend func (m *Manager) GetFormDataHandler(w http.ResponseWriter, r *http.Request) { formInstanceKey, err := r.Cookie("forminstance") if err != nil { http.Error(w, err.Error(), http.StatusBadRequest) log.Printf("missing cookie %s %s", formInstanceKey, r.URL) return } _ = json.NewEncoder(w).Encode(m.GetPostedFormData(formInstanceKey.Value)) } // GetPostedFormData is similar to GetFormDataHandler, but used when posting the form-based message to the outbox func (m *Manager) GetPostedFormData(key string) FormData { m.postedFormData.RLock() defer m.postedFormData.RUnlock() return m.postedFormData.internalFormDataMap[key] } // GetFormTemplateHandler handles the request for viewing a form filled-in with instance values func (m *Manager) GetFormTemplateHandler(w http.ResponseWriter, r *http.Request) { formPath := r.URL.Query().Get("formPath") if formPath == "" { http.Error(w, "formPath query param missing", http.StatusBadRequest) log.Printf("formPath query param missing %s %s", r.Method, r.URL.Path) return } absPathTemplate, err := m.findAbsPathForTemplatePath(formPath) if err != nil { http.Error(w, "find the full path for requested template "+formPath, http.StatusBadRequest) log.Printf("find the full path for requested template %s %s: %s", r.Method, r.URL.Path, "can't open template "+formPath) return } responseText, err := m.fillFormTemplate(absPathTemplate, "/api/form?"+r.URL.Query().Encode(), nil, make(map[string]string)) if err != nil { http.Error(w, "can't open template "+formPath, http.StatusBadRequest) log.Printf("problem filling form template file %s %s: can't open template %s. Err: %s", r.Method, r.URL.Path, formPath, err) return } _, err = io.WriteString(w, responseText) if err != nil { http.Error(w, err.Error(), http.StatusBadRequest) log.Printf("can't write form into response %s %s: %s", r.Method, r.URL.Path, err) return } } // UpdateFormTemplatesHandler handles API calls to update form templates. func (m *Manager) UpdateFormTemplatesHandler(w http.ResponseWriter, r *http.Request) { response, err := m.UpdateFormTemplates(r.Context()) if err != nil { http.Error(w, fmt.Sprint(err), http.StatusInternalServerError) return } jsn, _ := json.Marshal(response) _, _ = w.Write(jsn) } // UpdateFormTemplates handles searching for and installing the latest version of the form templates. func (m *Manager) UpdateFormTemplates(ctx context.Context) (UpdateResponse, error) { if err := os.MkdirAll(m.config.FormsPath, 0o755); err != nil { return UpdateResponse{}, fmt.Errorf("can't write to forms dir [%w]", err) } log.Printf("Updating form templates; current version is %v", m.getFormsVersion()) latest, err := m.getLatestFormsInfo(ctx) if err != nil { return UpdateResponse{}, err } if !m.isNewerVersion(latest.Version) { log.Printf("Latest forms version is %v; nothing to do", latest.Version) return UpdateResponse{ NewestVersion: latest.Version, Action: "none", }, nil } if err = m.downloadAndUnzipForms(ctx, latest.ArchiveURL); err != nil { return UpdateResponse{}, err } log.Printf("Finished forms update to %v", latest.Version) // TODO: re-init forms manager return UpdateResponse{ NewestVersion: latest.Version, Action: "update", }, nil } type formsInfo struct { Version string `json:"version"` ArchiveURL string `json:"archive_url"` } func (m *Manager) getLatestFormsInfo(ctx context.Context) (*formsInfo, error) { resp, err := client.Get(ctx, m.config.UserAgent, formsVersionInfoURL) if err != nil || resp.StatusCode != http.StatusOK { return nil, fmt.Errorf("can't fetch winlink forms version page: %w", err) } defer resp.Body.Close() var v formsInfo if err := json.NewDecoder(resp.Body).Decode(&v); err != nil { return nil, err } return &v, nil } func (m *Manager) downloadAndUnzipForms(ctx context.Context, downloadLink string) error { log.Printf("Updating forms via %v", downloadLink) resp, err := client.Get(ctx, m.config.UserAgent, downloadLink) if err != nil { return fmt.Errorf("can't download update ZIP: %w", err) } defer resp.Body.Close() f, err := ioutil.TempFile(os.TempDir(), "pat") if err != nil { return fmt.Errorf("can't create temp file for download: %w", err) } defer f.Close() defer os.Remove(f.Name()) if _, err := io.Copy(f, resp.Body); err != nil { return fmt.Errorf("can't write update ZIP: %w", err) } if err := unzip(f.Name(), m.config.FormsPath); err != nil { return fmt.Errorf("can't unzip forms update: %w", err) } return nil } func unzip(srcArchivePath, dstRoot string) error { // Closure to address file descriptors issue with all the deferred .Close() methods extractAndWriteFile := func(zf *zip.File) error { if zf.FileInfo().IsDir() { return nil } destPath := filepath.Join(dstRoot, zf.Name) // Check for ZipSlip (Directory traversal) if !strings.HasPrefix(destPath, filepath.Clean(dstRoot)+string(os.PathSeparator)) { return fmt.Errorf("illegal file path: %s", destPath) } // Ensure target directory exists if err := os.MkdirAll(filepath.Dir(destPath), 0o755); err != nil { return fmt.Errorf("can't create target directory: %w", err) } // Write file src, err := zf.Open() if err != nil { return err } defer src.Close() dst, err := os.OpenFile(destPath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, zf.Mode()) if err != nil { return err } defer dst.Close() _, err = io.Copy(dst, src) return err } r, err := zip.OpenReader(srcArchivePath) if err != nil { return err } defer r.Close() for _, f := range r.File { err := extractAndWriteFile(f) if err != nil { return err } } return nil } // GetXMLAttachmentNameForForm returns the user-visible filename for the message attachment that holds the form instance values func (m *Manager) GetXMLAttachmentNameForForm(f Form, isReply bool) string { attachmentName := filepath.Base(f.ViewerURI) if isReply { attachmentName = filepath.Base(f.ReplyViewerURI) } attachmentName = strings.TrimSuffix(attachmentName, filepath.Ext(attachmentName)) attachmentName = "RMS_Express_Form_" + attachmentName + ".xml" if len(attachmentName) > 255 { attachmentName = strings.TrimPrefix(attachmentName, "RMS_Express_Form_") } return attachmentName } // RenderForm finds the associated form and returns the filled-in form in HTML given the contents of a form attachment func (m *Manager) RenderForm(contentUnsanitized []byte, composeReply bool) (string, error) { type Node struct { XMLName xml.Name Content []byte `xml:",innerxml"` Nodes []Node `xml:",any"` } sr := utfbom.SkipOnly(bytes.NewReader(contentUnsanitized)) contentData, err := io.ReadAll(sr) if err != nil { return "", fmt.Errorf("error reading sanitized form xml: %w", err) } if !utf8.Valid(contentData) { log.Println("Warning: unsupported string encoding in form XML, expected utf-8") } var n1 Node formParams := make(map[string]string) formVars := make(map[string]string) if err := xml.Unmarshal(contentData, &n1); err != nil { return "", err } if n1.XMLName.Local != "RMS_Express_Form" { return "", errors.New("missing RMS_Express_Form tag in form XML") } for _, n2 := range n1.Nodes { switch n2.XMLName.Local { case "form_parameters": for _, n3 := range n2.Nodes { formParams[n3.XMLName.Local] = string(n3.Content) } case "variables": for _, n3 := range n2.Nodes { formVars[n3.XMLName.Local] = string(n3.Content) } } } switch { case formParams["display_form"] == "": return "", errors.New("missing display_form tag in form XML") case composeReply && formParams["reply_template"] == "": return "", errors.New("missing reply_template tag in form XML for a reply message") } formFolder, err := m.buildFormFolder() if err != nil { return "", err } formToLoad := formParams["display_form"] if composeReply { // we're authoring a reply formToLoad = formParams["reply_template"] } form, err := findFormFromURI(formToLoad, formFolder) if err != nil { return "", err } var formRelPath string switch { case composeReply: // authoring a form reply formRelPath = form.ReplyInitialURI case strings.HasSuffix(form.ReplyViewerURI, formParams["display_form"]): // viewing a form reply formRelPath = form.ReplyViewerURI default: // viewing a form formRelPath = form.ViewerURI } absPathTemplate, err := m.findAbsPathForTemplatePath(formRelPath) if err != nil { return "", err } return m.fillFormTemplate(absPathTemplate, "/api/form?composereply=true&formPath="+formRelPath, regexp.MustCompile(`{[vV][aA][rR]\s+(\w+)\s*}`), formVars) } // ComposeForm combines all data needed for the whole form-based message: subject, body, and attachment func (m *Manager) ComposeForm(tmplPath string, subject string) (MessageForm, error) { formFolder, err := m.buildFormFolder() if err != nil { return MessageForm{}, fmt.Errorf("failed to build form folder tree: %v", err) } tmplPath = filepath.Clean(tmplPath) form, err := findFormFromURI(tmplPath, formFolder) if err != nil { return MessageForm{}, fmt.Errorf("failed to find '%s': %v", tmplPath, err) } formValues := map[string]string{ "subjectline": subject, "templateversion": m.getFormsVersion(), "msgsender": m.config.MyCall, } fmt.Printf("Form '%s', version: %s", form.TxtFileURI, formValues["templateversion"]) formMsg, err := formMessageBuilder{ Template: form, FormValues: formValues, Interactive: true, IsReply: false, FormsMgr: m, }.build() if err != nil { return MessageForm{}, err } return formMsg, nil } func (f Form) matchesName(nameToMatch string) bool { return f.InitialURI == nameToMatch || strings.EqualFold(f.InitialURI, nameToMatch+htmlFileExt) || f.ViewerURI == nameToMatch || strings.EqualFold(f.ViewerURI, nameToMatch+htmlFileExt) || f.ReplyInitialURI == nameToMatch || f.ReplyInitialURI == nameToMatch+".0" || f.ReplyViewerURI == nameToMatch || f.ReplyViewerURI == nameToMatch+".0" || f.TxtFileURI == nameToMatch || strings.EqualFold(f.TxtFileURI, nameToMatch+txtFileExt) } func (f Form) containsName(partialName string) bool { return strings.Contains(f.InitialURI, partialName) || strings.Contains(f.ViewerURI, partialName) || strings.Contains(f.ReplyInitialURI, partialName) || strings.Contains(f.ReplyViewerURI, partialName) || strings.Contains(f.ReplyTxtFileURI, partialName) || strings.Contains(f.TxtFileURI, partialName) } func (m *Manager) buildFormFolder() (FormFolder, error) { formFolder, err := m.innerRecursiveBuildFormFolder(m.config.FormsPath) formFolder.Version = m.getFormsVersion() return formFolder, err } func (m *Manager) innerRecursiveBuildFormFolder(rootPath string) (FormFolder, error) { rootFile, err := os.Open(rootPath) if err != nil { return FormFolder{}, err } defer rootFile.Close() rootFileInfo, _ := os.Stat(rootPath) if !rootFileInfo.IsDir() { return FormFolder{}, errors.New(rootPath + " is not a directory") } folder := FormFolder{ Name: rootFileInfo.Name(), Path: rootFile.Name(), Forms: []Form{}, Folders: []FormFolder{}, } infos, err := rootFile.Readdir(0) if err != nil { return folder, err } _ = rootFile.Close() formCnt := 0 for _, info := range infos { if info.IsDir() { subfolder, err := m.innerRecursiveBuildFormFolder(path.Join(rootPath, info.Name())) if err != nil { return folder, err } folder.Folders = append(folder.Folders, subfolder) folder.FormCount += subfolder.FormCount continue } if !strings.EqualFold(filepath.Ext(info.Name()), txtFileExt) { continue } frm, err := m.buildFormFromTxt(path.Join(rootPath, info.Name())) if err != nil { continue } if frm.InitialURI != "" || frm.ViewerURI != "" { formCnt++ folder.Forms = append(folder.Forms, frm) folder.FormCount++ } } sort.Slice(folder.Folders, func(i, j int) bool { return folder.Folders[i].Name < folder.Folders[j].Name }) sort.Slice(folder.Forms, func(i, j int) bool { return folder.Forms[i].Name < folder.Forms[j].Name }) return folder, nil } func (m *Manager) buildFormFromTxt(txtPath string) (Form, error) { f, err := os.Open(txtPath) if err != nil { return Form{}, err } defer f.Close() formsPathWithSlash := m.config.FormsPath + "/" form := Form{ Name: strings.TrimSuffix(path.Base(txtPath), path.Ext(txtPath)), TxtFileURI: strings.TrimPrefix(txtPath, formsPathWithSlash), } scanner := bufio.NewScanner(f) baseURI := path.Dir(form.TxtFileURI) for scanner.Scan() { l := scanner.Text() switch { case strings.HasPrefix(l, "Form:"): // Form: , files := strings.Split(strings.TrimPrefix(l, "Form:"), ",") // Extend to absolute paths and add missing html extension for i := range files { files[i] = path.Join(baseURI, strings.TrimSpace(files[i])) if ext := path.Ext(files[i]); ext == "" { files[i] += ".html" } } form.InitialURI = files[0] if len(files) > 1 { form.ViewerURI = files[1] } case strings.HasPrefix(l, "ReplyTemplate:"): form.ReplyTxtFileURI = path.Join(baseURI, strings.TrimSpace(strings.TrimPrefix(l, "ReplyTemplate:"))) tmpForm, _ := m.buildFormFromTxt(path.Join(m.config.FormsPath, form.ReplyTxtFileURI)) form.ReplyInitialURI = tmpForm.InitialURI form.ReplyViewerURI = tmpForm.ViewerURI } } return form, err } func findFormFromURI(formName string, folder FormFolder) (Form, error) { form := Form{Name: "unknown"} for _, subFolder := range folder.Folders { form, err := findFormFromURI(formName, subFolder) if err == nil { return form, nil } } for _, form := range folder.Forms { if form.matchesName(formName) { return form, nil } } // couldn't find it by full path, so try to find match by guessing folder name formName = path.Join(folder.Name, formName) for _, form := range folder.Forms { if form.containsName(formName) { return form, nil } } return form, errors.New("form not found") } func (m *Manager) findAbsPathForTemplatePath(tmplPath string) (string, error) { absPathTemplate := filepath.Join(m.config.FormsPath, path.Clean(tmplPath)) // now deal with cases where the html file name specified in the .txt file, has different caseness than the actual .html file on disk. absPathTemplateFolder := filepath.Dir(absPathTemplate) templateDir, err := os.Open(absPathTemplateFolder) if err != nil { return "", errors.New("can't read template folder") } defer templateDir.Close() fileNames, err := templateDir.Readdirnames(0) if err != nil { return "", errors.New("can't read template folder") } for _, name := range fileNames { if strings.EqualFold(filepath.Base(tmplPath), name) { return filepath.Join(absPathTemplateFolder, name), nil } } return "", fmt.Errorf("unable to resolve absolute template path") } // gpsPos returns the current GPS Position func (m *Manager) gpsPos() (gpsd.Position, error) { addr := m.config.GPSd.Addr if addr == "" { return gpsd.Position{}, errors.New("GPSd: not configured.") } if !m.config.GPSd.AllowForms { return gpsd.Position{}, errors.New("GPSd: allow_forms is disabled. GPS position will not be available in form templates.") } conn, err := gpsd.Dial(addr) if err != nil { log.Printf("GPSd daemon: %s", err) return gpsd.Position{}, err } defer conn.Close() conn.Watch(true) log.Println("Waiting for position from GPSd...") // TODO: make the GPSd timeout configurable return conn.NextPosTimeout(3 * time.Second) } type gpsStyle int const ( // documentation: https://www.winlink.org/sites/default/files/RMSE_FORMS/insertion_tags.zip signedDecimal gpsStyle = iota // 41.1234 -73.4567 decimal // 46.3795N 121.5835W degreeMinute // 46-22.77N 121-35.01W ) func gpsFmt(style gpsStyle, pos gpsd.Position) string { var ( northing string easting string latDegrees int latMinutes float64 lonDegrees int lonMinutes float64 ) noPos := gpsd.Position{} if pos == noPos { return "(Not available)" } switch style { case degreeMinute: { latDegrees = int(math.Trunc(math.Abs(pos.Lat))) latMinutes = (math.Abs(pos.Lat) - float64(latDegrees)) * 60 lonDegrees = int(math.Trunc(math.Abs(pos.Lon))) lonMinutes = (math.Abs(pos.Lon) - float64(lonDegrees)) * 60 } fallthrough case decimal: { if pos.Lat >= 0 { northing = "N" } else { northing = "S" } if pos.Lon >= 0 { easting = "E" } else { easting = "W" } } } switch style { case signedDecimal: return fmt.Sprintf("%.4f %.4f", pos.Lat, pos.Lon) case decimal: return fmt.Sprintf("%.4f%s %.4f%s", math.Abs(pos.Lat), northing, math.Abs(pos.Lon), easting) case degreeMinute: return fmt.Sprintf("%02d-%05.2f%s %03d-%05.2f%s", latDegrees, latMinutes, northing, lonDegrees, lonMinutes, easting) default: return "(Not available)" } } func posToGridSquare(pos gpsd.Position) string { point := maidenhead.NewPoint(pos.Lat, pos.Lon) gridsquare, err := point.GridSquare() if err != nil { return "" } return gridsquare } func (m *Manager) fillFormTemplate(absPathTemplate string, formDestURL string, placeholderRegEx *regexp.Regexp, formVars map[string]string) (string, error) { fUnsanitized, err := os.Open(absPathTemplate) if err != nil { return "", err } defer fUnsanitized.Close() // skipping over UTF-8 byte-ordering mark EFBBEF, some 3rd party templates use it // (e.g. Sonoma county's ICS213_v2.1_SonomaACS_TwoWay_Initial_Viewer.html) f := utfbom.SkipOnly(fUnsanitized) sanitizedFileContent, err := io.ReadAll(f) if err != nil { return "", fmt.Errorf("error reading file %s", absPathTemplate) } if !utf8.Valid(sanitizedFileContent) { log.Printf("Warning: unsupported string encoding in template %s, expected utf-8", absPathTemplate) } now := time.Now() validPos := "NO" nowPos, err := m.gpsPos() if err != nil { debug.Printf("GPSd error: %v", err) } else { validPos = "YES" debug.Printf("GPSd position: %s", gpsFmt(signedDecimal, nowPos)) } var buf bytes.Buffer scanner := bufio.NewScanner(bytes.NewReader(sanitizedFileContent)) for scanner.Scan() { l := scanner.Text() l = strings.ReplaceAll(l, "http://{FormServer}:{FormPort}", formDestURL) // some Canada BC forms don't use the {FormServer} placeholder, it's OK, can deal with it here l = strings.ReplaceAll(l, "http://localhost:8001", formDestURL) l = strings.ReplaceAll(l, "{MsgSender}", m.config.MyCall) l = strings.ReplaceAll(l, "{Callsign}", m.config.MyCall) l = strings.ReplaceAll(l, "{ProgramVersion}", "Pat "+m.config.AppVersion) l = strings.ReplaceAll(l, "{DateTime}", formatDateTime(now)) l = strings.ReplaceAll(l, "{UDateTime}", formatDateTimeUTC(now)) l = strings.ReplaceAll(l, "{Date}", formatDate(now)) l = strings.ReplaceAll(l, "{UDate}", formatDateUTC(now)) l = strings.ReplaceAll(l, "{UDTG}", formatUDTG(now)) l = strings.ReplaceAll(l, "{Time}", formatTime(now)) l = strings.ReplaceAll(l, "{UTime}", formatTimeUTC(now)) l = strings.ReplaceAll(l, "{GPS}", gpsFmt(degreeMinute, nowPos)) l = strings.ReplaceAll(l, "{GPS_DECIMAL}", gpsFmt(decimal, nowPos)) l = strings.ReplaceAll(l, "{GPS_SIGNED_DECIMAL}", gpsFmt(signedDecimal, nowPos)) // Lots of undocumented tags found in the Winlink check in form. // Note also various ways of capitalizing. Perhaps best to do case insenstive string replacements.... l = strings.ReplaceAll(l, "{Latitude}", fmt.Sprintf("%.4f", nowPos.Lat)) l = strings.ReplaceAll(l, "{latitude}", fmt.Sprintf("%.4f", nowPos.Lat)) l = strings.ReplaceAll(l, "{Longitude}", fmt.Sprintf("%.4f", nowPos.Lon)) l = strings.ReplaceAll(l, "{longitude}", fmt.Sprintf("%.4f", nowPos.Lon)) l = strings.ReplaceAll(l, "{GridSquare}", posToGridSquare(nowPos)) l = strings.ReplaceAll(l, "{GPSValid}", fmt.Sprintf("%s ", validPos)) if placeholderRegEx != nil { l = fillPlaceholders(l, placeholderRegEx, formVars) } buf.WriteString(l + "\n") } return buf.String(), nil } func (m *Manager) getFormsVersion() string { // walking up the path to find a version file. // Winlink's Standard_Forms.zip includes it in its root. dir := m.config.FormsPath if filepath.Ext(dir) == txtFileExt { dir = filepath.Dir(dir) } var verFile *os.File // loop to walk up the subfolders until we find the top, or Winlink's Standard_Forms_Version.dat file for { f, err := os.Open(filepath.Join(dir, "Standard_Forms_Version.dat")) if err != nil { dir = filepath.Dir(dir) // have not found the version file or couldn't open it, going up by one if dir == "." || dir == ".." || strings.HasSuffix(dir, string(os.PathSeparator)) { return "unknown" // reached top-level and couldn't find version .dat file } continue } // found and opened the version file verFile = f break } if verFile != nil { defer verFile.Close() return readFileFirstLine(verFile) } return "unknown" } func readFileFirstLine(f *os.File) string { scanner := bufio.NewScanner(f) if scanner.Scan() { return scanner.Text() } return "" } type formMessageBuilder struct { Interactive bool IsReply bool Template Form FormValues map[string]string FormsMgr *Manager } // build returns message subject, body, and XML attachment content for the given template and variable map func (b formMessageBuilder) build() (MessageForm, error) { tmplPath := filepath.Join(b.FormsMgr.config.FormsPath, b.Template.TxtFileURI) if filepath.Ext(tmplPath) == "" { tmplPath += txtFileExt } if b.IsReply && b.Template.ReplyTxtFileURI != "" { tmplPath = filepath.Join(b.FormsMgr.config.FormsPath, b.Template.ReplyTxtFileURI) } b.initFormValues() formVarsAsXML := "" for varKey, varVal := range b.FormValues { formVarsAsXML += fmt.Sprintf(" <%s>%s\n", xmlEscape(varKey), xmlEscape(varVal), xmlEscape(varKey)) } viewer := "" if b.Template.ViewerURI != "" { viewer = filepath.Base(b.Template.ViewerURI) } if b.IsReply && b.Template.ReplyViewerURI != "" { viewer = filepath.Base(b.Template.ReplyViewerURI) } replier := "" if !b.IsReply && b.Template.ReplyTxtFileURI != "" { replier = filepath.Base(b.Template.ReplyTxtFileURI) } msgForm, err := b.scanTmplBuildMessage(tmplPath) if err != nil { return MessageForm{}, err } // Add XML if a viewer is defined for this form if b.Template.ViewerURI != "" { msgForm.AttachmentXML = fmt.Sprintf(`%s %s %s %s %s %s %s %s %s `, xml.Header, "1.0", b.FormsMgr.config.AppVersion, time.Now().UTC().Format("20060102150405"), b.FormsMgr.config.MyCall, b.FormsMgr.config.Locator, viewer, replier, formVarsAsXML) msgForm.AttachmentName = b.FormsMgr.GetXMLAttachmentNameForForm(b.Template, false) } msgForm.To = strings.TrimSpace(msgForm.To) msgForm.Cc = strings.TrimSpace(msgForm.Cc) msgForm.Subject = strings.TrimSpace(msgForm.Subject) msgForm.Body = strings.TrimSpace(msgForm.Body) return msgForm, nil } func (b formMessageBuilder) initFormValues() { if b.IsReply { b.FormValues["msgisreply"] = "True" } else { b.FormValues["msgisreply"] = "False" } b.FormValues["msgsender"] = b.FormsMgr.config.MyCall // some defaults that we can't set yet. Winlink doesn't seem to care about these b.FormValues["msgto"] = "" b.FormValues["msgcc"] = "" b.FormValues["msgsubject"] = "" b.FormValues["msgbody"] = "" b.FormValues["msgp2p"] = "" b.FormValues["msgisforward"] = fieldValueFalseInXML b.FormValues["msgisacknowledgement"] = fieldValueFalseInXML b.FormValues["msgseqnum"] = "0" } func (b formMessageBuilder) scanTmplBuildMessage(tmplPath string) (MessageForm, error) { infile, err := os.Open(tmplPath) if err != nil { return MessageForm{}, err } defer infile.Close() placeholderRegEx := regexp.MustCompile(`<[vV][aA][rR]\s+(\w+)\s*>`) scanner := bufio.NewScanner(infile) var msgForm MessageForm var inBody bool for scanner.Scan() { lineTmpl := scanner.Text() lineTmpl = fillPlaceholders(lineTmpl, placeholderRegEx, b.FormValues) lineTmpl = strings.ReplaceAll(lineTmpl, "", b.FormsMgr.config.MyCall) lineTmpl = strings.ReplaceAll(lineTmpl, "", "Pat "+b.FormsMgr.config.AppVersion) if strings.HasPrefix(lineTmpl, "Form:") { continue } if strings.HasPrefix(lineTmpl, "ReplyTemplate:") { continue } if strings.HasPrefix(lineTmpl, "Msg:") { lineTmpl = strings.TrimSpace(strings.TrimPrefix(lineTmpl, "Msg:")) inBody = true } if b.Interactive { matches := placeholderRegEx.FindAllStringSubmatch(lineTmpl, -1) fmt.Println(lineTmpl) for i := range matches { varName := matches[i][1] varNameLower := strings.ToLower(varName) if b.FormValues[varNameLower] != "" { continue } fmt.Print(varName + ": ") b.FormValues[varNameLower] = "blank" val := b.FormsMgr.config.LineReader() if val != "" { b.FormValues[varNameLower] = val } } } lineTmpl = fillPlaceholders(lineTmpl, placeholderRegEx, b.FormValues) switch { case strings.HasPrefix(lineTmpl, "Subject:"): msgForm.Subject = strings.TrimPrefix(lineTmpl, "Subject:") case strings.HasPrefix(lineTmpl, "To:"): msgForm.To = strings.TrimPrefix(lineTmpl, "To:") case strings.HasPrefix(lineTmpl, "Cc:"): msgForm.Cc = strings.TrimPrefix(lineTmpl, "Cc:") case inBody: msgForm.Body += lineTmpl + "\n" default: log.Printf("skipping unknown template line: '%s'", lineTmpl) } } return msgForm, nil } func xmlEscape(s string) string { var buf bytes.Buffer if err := xml.EscapeText(&buf, []byte(s)); err != nil { log.Printf("Error trying to escape XML string %s", err) } return buf.String() } func fillPlaceholders(s string, re *regexp.Regexp, values map[string]string) string { if _, ok := values["txtstr"]; !ok { values["txtstr"] = "" } result := s matches := re.FindAllStringSubmatch(s, -1) for _, match := range matches { value, ok := values[strings.ToLower(match[1])] if ok { result = strings.ReplaceAll(result, match[0], value) } } return result } func (m *Manager) cleanupOldFormData() { m.postedFormData.Lock() defer m.postedFormData.Unlock() for key, form := range m.postedFormData.internalFormDataMap { elapsed := time.Since(form.Submitted).Hours() if elapsed > 24 { log.Println("deleting old FormData after", elapsed, "hrs") delete(m.postedFormData.internalFormDataMap, key) } } } func (m *Manager) isNewerVersion(newestVersion string) bool { currentVersion := m.getFormsVersion() cv := strings.Split(currentVersion, ".") nv := strings.Split(newestVersion, ".") for i := 0; i < 4; i++ { var cp int64 if len(cv) > i { cp, _ = strconv.ParseInt(cv[i], 10, 16) } var np int64 if len(nv) > i { np, _ = strconv.ParseInt(nv[i], 10, 16) } if cp < np { return true } } return false } type httpClient struct{ http.Client } func (c httpClient) Get(ctx context.Context, userAgent, url string) (*http.Response, error) { req, err := http.NewRequestWithContext(ctx, "GET", url, nil) if err != nil { return nil, err } req.Header.Set("User-Agent", userAgent) req.Header.Set("Cache-Control", "no-cache") return c.Do(req) } pat-0.15.1/internal/gpsd/000077500000000000000000000000001453425652100151425ustar00rootroot00000000000000pat-0.15.1/internal/gpsd/gpsd.go000066400000000000000000000116661453425652100164400ustar00rootroot00000000000000package gpsd import ( "bufio" "encoding/json" "errors" "fmt" "io" "net" "sync" "time" ) type NMEAMode int const ( ModeUnknown NMEAMode = iota ModeNoFix Mode2D Mode3D ) var ErrUnsupportedProtocolVersion = errors.New("unsupported protocol version") // Positioner implementations provide geographic positioning data. // // This is particularly useful for testing if an object returned by Next can be used to determine the device position. type Positioner interface { Position() Position HasFix() bool } // Position holds geographic positioning data. type Position struct { Lat, Lon float64 // Latitude/longitude in degrees. +/- signifies north/south. Alt float64 // Altitude in meters. Track float64 // Course over ground, degrees from true north. Speed float64 // Speed over ground, meters per second. Time time.Time // Time as reported by the device. } // Conn represents a socket connection to an GPSd daemon. type Conn struct { Version Version mu sync.Mutex tcpConn net.Conn rd *bufio.Reader watchEnabled bool closed bool } // Dial establishes a socket connection to the GPSd daemon. func Dial(addr string) (*Conn, error) { tcpConn, err := net.DialTimeout("tcp", addr, 30*time.Second) if err != nil { return nil, err } c := &Conn{ tcpConn: tcpConn, rd: bufio.NewReader(tcpConn), } err = json.NewDecoder(c.rd).Decode(&c.Version) if err != nil || c.Version.Release == "" { tcpConn.Close() return nil, errors.New("unexpected server response") } if c.Version.ProtoMajor < 3 { tcpConn.Close() return nil, ErrUnsupportedProtocolVersion } return c, nil } // Watch enables or disables the watcher mode. // // In watcher mode, GPS reports are dumped as TPV and SKY objects. These objects are available through the Next method. func (c *Conn) Watch(enable bool) bool { c.mu.Lock() defer c.mu.Unlock() if c.closed { return false } if enable == c.watchEnabled { return enable } c.tcpConn.SetDeadline(time.Now().Add(30 * time.Second)) defer c.tcpConn.SetDeadline(time.Time{}) param, _ := json.Marshal( map[string]interface{}{ "class": "WATCH", "enable": enable, "json": true, }) c.send("?WATCH=%s", param) for { obj, err := c.next() if err != nil { return false } if watch, ok := obj.(watch); ok { c.watchEnabled = watch.Enable break } } return c.watchEnabled } // Close closes the GPSd daemon connection. func (c *Conn) Close() error { c.Watch(false) c.closed = true return c.tcpConn.Close() } // Next returns the next object sent from the daemon, or an error. // // The empty interface returned can be any of the following types: // - Sky: A Sky object reports a sky view of the GPS satellite positions. // - TPV: A TPV object is a time-position-velocity report. func (c *Conn) Next() (interface{}, error) { c.mu.Lock() defer c.mu.Unlock() for { obj, err := c.next() if err != nil { return nil, err } switch obj.(type) { case TPV, Sky: return obj, nil default: // Ignore other objects for now. } } } func (c *Conn) next() (interface{}, error) { line, err := c.rd.ReadBytes('\n') if err != nil { return nil, err } return parseJSONObject(line) } var ( ErrTimeout = errors.New("timeout") ErrWatchModeEnabled = errors.New("operation not available while in watch mode") ) // NextPos returns the next reported position. func (c *Conn) NextPos() (Position, error) { return c.NextPosTimeout(0) } // NextPosTimeout returns the next reported position, or an empty position on timeout. func (c *Conn) NextPosTimeout(timeout time.Duration) (Position, error) { var deadline time.Time if timeout > 0 { deadline = time.Now().Add(timeout) c.tcpConn.SetDeadline(deadline) defer c.tcpConn.SetDeadline(time.Time{}) } for { obj, err := c.Next() var netErr net.Error if ok := errors.As(err, &netErr); ok && netErr.Timeout() { return Position{}, ErrTimeout } else if err != nil { return Position{}, err } if pos, ok := obj.(Positioner); ok && pos.HasFix() { return pos.Position(), nil } if !deadline.IsZero() && time.Now().After(deadline) { return Position{}, ErrTimeout } } } // Devices returns a list of all devices GPSd is aware of. // // ErrWatchModeEnabled will be returned if the connection is in watch mode. // A nil-slice will be returned if the connection has been closed. func (c *Conn) Devices() ([]Device, error) { if c.closed { return nil, nil } else if c.watchEnabled { return nil, ErrWatchModeEnabled } c.mu.Lock() defer c.mu.Unlock() c.send("?DEVICES;") for { obj, err := c.next() if err != nil { return nil, errUnexpected(err) } if devs, ok := obj.([]Device); ok { return devs, nil } } } func (c *Conn) send(s string, params ...interface{}) error { _, err := fmt.Fprintf(c.tcpConn, s, params...) return errUnexpected(err) } func errUnexpected(err error) error { if errors.Is(err, io.EOF) { err = io.ErrUnexpectedEOF } return err } pat-0.15.1/internal/gpsd/objects.go000066400000000000000000000076621453425652100171350ustar00rootroot00000000000000package gpsd import ( "encoding/json" "errors" "time" ) // A Sky object reports a sky view of the GPS satellite positions. type Sky struct { Device string `json:"device,omitempty"` Time time.Time `json:"time,omitempty"` XDOP, YDOP, VDOP, TDOP, HDOP, PDOP, GDOP json.Number Satellites []Satellite `json:"satellites"` } // A TPV object is a time-position-velocity report. type TPV struct { Device string // Name of originating device. Mode NMEAMode // NMEA mode: %d, 0=no mode value yet seen, 1=no fix, 2=2D, 3=3D. Time time.Time // Time/date stamp. May have a fractional part of up to .001sec precision. May be absent if mode is not 2D or 3D. EPT json.Number // Estimated timestamp error (%f, seconds, 95% confidence). Present if time is present. Lat, Lon, Alt json.Number EPX, EPY, EPV json.Number // Lat, Lon, Alt error estimate in meters, 95% confidence. Present if mode is 2 or 3 and DOPs can be calculated from the satellite view. Track, Speed, Climb json.Number EPD, EPS, EPC json.Number } func (t TPV) Position() Position { lat, _ := t.Lat.Float64() lon, _ := t.Lon.Float64() alt, _ := t.Alt.Float64() track, _ := t.Track.Float64() speed, _ := t.Speed.Float64() return Position{Lat: lat, Lon: lon, Alt: alt, Track: track, Speed: speed, Time: t.Time} } func (t TPV) HasFix() bool { return t.Mode > ModeNoFix } // Satellite represents a GPS satellite. type Satellite struct { // PRN ID of the satellite. 1-63 are GNSS satellites, 64-96 are GLONASS satellites, 100-164 are SBAS satellites. PRN int `json:"PRN"` // Azimuth, degrees from true north. Azimuth json.Number `json:"az"` // Elevation in degrees. Elevation json.Number `json:"el"` // Signal strength in dB. SignalStrength json.Number `json:"ss"` // Used in current solution? // // (SBAS/WAAS/EGNOS satellites may be flagged used if the solution has corrections from them, but not all drivers make this information available). Used bool `json:"used"` } // Version holds GPSd version data. type Version struct { Release string `json:"release"` Rev string `json:"rev"` ProtoMajor int `json:"proto_major"` ProtoMinor int `json:"proto_minor"` } // Device represents a connected sensor/GPS. type Device struct { Path string `json:"path,omitempty"` Flags *int `json:"flags,omitempty"` Driver string `json:"driver,omitempty"` Subtype string `json:"subtype,omitempty"` Bps *int `json:"bps,omitempty"` Parity string `json:"parity"` StopBits int `json:"stopbits"` // Activated time.Time `json:"activated,omitempty"` (Must parse as fractional epoch time) } type watch struct { Class string `json:"class"` Enable bool `json:"enable,omitempty"` JSON *bool `json:"json,omitempty"` NMEA *bool `json:"nmea,omitempty"` Raw *int `json:"raw,omitempty"` Scaled *bool `json:"scaled,omitempty"` Split24 *bool `json:"split24,omitempty"` PPS *bool `json:"pps,omitempty"` Device string `json:"device,omitempty"` Devices []Device `json:"devices,omitempty"` // Only in response } func parseJSONObject(raw []byte) (interface{}, error) { var class struct{ Class string } err := json.Unmarshal(raw, &class) if err != nil { return nil, err } switch class.Class { case "WATCH": var w watch err = json.Unmarshal(raw, &w) return w, err case "DEVICES": var devs struct{ Devices []Device } err = json.Unmarshal(raw, &devs) return devs.Devices, err case "DEVICE": var dev Device err = json.Unmarshal(raw, &dev) return dev, err case "VERSION": var ver Version err = json.Unmarshal(raw, &ver) return ver, err case "ERROR": var err struct{ Message string } json.Unmarshal(raw, &err) return nil, errors.New(err.Message) case "SKY": var sky Sky err = json.Unmarshal(raw, &sky) return sky, err case "TPV": var tpv TPV err = json.Unmarshal(raw, &tpv) return tpv, err default: var m map[string]interface{} err = json.Unmarshal(raw, &m) return m, err } } pat-0.15.1/internal/osutil/000077500000000000000000000000001453425652100155245ustar00rootroot00000000000000pat-0.15.1/internal/osutil/rlimit_freebsd.go000066400000000000000000000011501453425652100210420ustar00rootroot00000000000000//go:build freebsd // +build freebsd package osutil import ( "fmt" "syscall" ) // RaiseOpenFileLimit tries to maximize the limit of open file descriptors, limited by max or the OS's hard limit func RaiseOpenFileLimit(max uint64) error { var limit syscall.Rlimit if err := syscall.Getrlimit(syscall.RLIMIT_NOFILE, &limit); err != nil { return fmt.Errorf("Could not get current limit: %v", err) } if limit.Cur >= limit.Max || limit.Cur >= int64(max) { return nil } limit.Cur = limit.Max if limit.Cur > int64(max) { limit.Cur = int64(max) } return syscall.Setrlimit(syscall.RLIMIT_NOFILE, &limit) } pat-0.15.1/internal/osutil/rlimit_unix.go000066400000000000000000000011521453425652100204150ustar00rootroot00000000000000//go:build !windows && !freebsd // +build !windows,!freebsd package osutil import ( "fmt" "syscall" ) // RaiseOpenFileLimit tries to maximize the limit of open file descriptors, limited by max or the OS's hard limit func RaiseOpenFileLimit(max uint64) error { var limit syscall.Rlimit if err := syscall.Getrlimit(syscall.RLIMIT_NOFILE, &limit); err != nil { return fmt.Errorf("could not get current limit: %w", err) } if limit.Cur >= limit.Max || limit.Cur >= max { return nil } limit.Cur = limit.Max if limit.Cur > max { limit.Cur = max } return syscall.Setrlimit(syscall.RLIMIT_NOFILE, &limit) } pat-0.15.1/internal/osutil/rlimit_windows.go000066400000000000000000000002421453425652100211230ustar00rootroot00000000000000//go:build windows // +build windows package osutil import "fmt" func RaiseOpenFileLimit(max uint64) error { return fmt.Errorf("Not available for Windows") } pat-0.15.1/listen.go000066400000000000000000000126151453425652100142230ustar00rootroot00000000000000// Copyright 2016 Martin Hebnes Pedersen (LA5NTA). All rights reserved. // Use of this source code is governed by the MIT-license that can be // found in the LICENSE file. package main import ( "context" "log" "net" "strings" "time" "github.com/la5nta/wl2k-go/transport/ax25" "github.com/la5nta/wl2k-go/transport/telnet" ) func Unlisten(param string) { methods := strings.FieldsFunc(param, SplitFunc) for _, method := range methods { ok, err := listenHub.Disable(method) if err != nil { log.Printf("Unable to close %s listener: %s", method, err) } else if !ok { log.Printf("No active %s listener, ignoring.\n", method) } } } func Listen(listenStr string) { methods := strings.FieldsFunc(listenStr, SplitFunc) for _, method := range methods { // Rewrite the generic ax25:// scheme to use a specified AX.25 engine. if method == MethodAX25 { method = defaultAX25Method() } switch strings.ToLower(method) { case MethodArdop: listenHub.Enable(ARDOPListener{}) case MethodTelnet: listenHub.Enable(TelnetListener{}) case MethodAX25AGWPE: listenHub.Enable(&AX25AGWPEListener{}) case MethodAX25Linux: listenHub.Enable(&AX25LinuxListener{}) case MethodVaraFM: listenHub.Enable(VaraFMListener{}) case MethodVaraHF: listenHub.Enable(VaraHFListener{}) case MethodAX25SerialTNC, MethodSerialTNCDeprecated: log.Printf("%s listen not implemented, ignoring.", method) default: log.Printf("'%s' is not a valid listen method", method) return } } log.Printf("Listening for incoming traffic on %s...", listenStr) } type AX25LinuxListener struct{ stopBeacon func() } func (l *AX25LinuxListener) Init() (net.Listener, error) { return ax25.ListenAX25(config.AX25Linux.Port, fOptions.MyCall) } func (l *AX25LinuxListener) BeaconStart() error { interval := time.Duration(config.AX25.Beacon.Every) * time.Second if interval == 0 { return nil } b, err := ax25.NewAX25Beacon(config.AX25Linux.Port, fOptions.MyCall, config.AX25.Beacon.Destination, config.AX25.Beacon.Message) if err != nil { return err } l.stopBeacon = doEvery(interval, func() { if err := b.Now(); err != nil { log.Printf("%s beacon failed: %s", l.Name(), err) l.stopBeacon() } }) return nil } func (l *AX25LinuxListener) BeaconStop() { if l.stopBeacon != nil { l.stopBeacon() } } func (l *AX25LinuxListener) CurrentFreq() (Frequency, bool) { return 0, false } func (l *AX25LinuxListener) Name() string { return MethodAX25Linux } type ARDOPListener struct{} func (l ARDOPListener) Name() string { return MethodArdop } func (l ARDOPListener) Init() (net.Listener, error) { if err := initArdopTNC(); err != nil { return nil, err } ln, err := adTNC.Listen() if err != nil { return nil, err } return ln, err } func (l ARDOPListener) CurrentFreq() (Frequency, bool) { if rig, ok := rigs[config.Ardop.Rig]; ok { f, _ := rig.GetFreq() return Frequency(f), ok } return 0, false } func (l ARDOPListener) BeaconStart() error { return adTNC.BeaconEvery(time.Duration(config.Ardop.BeaconInterval) * time.Second) } func (l ARDOPListener) BeaconStop() { adTNC.BeaconEvery(0) } type VaraFMListener struct{} func (l VaraFMListener) Name() string { return MethodVaraFM } func (l VaraFMListener) Init() (net.Listener, error) { if err := initVaraFMModem(); err != nil { return nil, err } ln, err := varaFMModem.Listen() if err != nil { return nil, err } return ln, err } func (l VaraFMListener) CurrentFreq() (Frequency, bool) { if rig, ok := rigs[config.VaraFM.Rig]; ok { f, _ := rig.GetFreq() return Frequency(f), ok } return 0, false } type VaraHFListener struct{} func (l VaraHFListener) Name() string { return MethodVaraHF } func (l VaraHFListener) Init() (net.Listener, error) { if err := initVaraHFModem(); err != nil { return nil, err } ln, err := varaHFModem.Listen() if err != nil { return nil, err } return ln, err } func (l VaraHFListener) CurrentFreq() (Frequency, bool) { if rig, ok := rigs[config.VaraHF.Rig]; ok { f, _ := rig.GetFreq() return Frequency(f), ok } return 0, false } type AX25AGWPEListener struct{ stopBeacon func() } func (l *AX25AGWPEListener) Name() string { return MethodAX25AGWPE } func (l *AX25AGWPEListener) Init() (net.Listener, error) { if err := initAGWPE(); err != nil { return nil, err } return agwpeTNC.Listen() } func (l *AX25AGWPEListener) CurrentFreq() (Frequency, bool) { return 0, false } func (l *AX25AGWPEListener) BeaconStart() error { b := config.AX25.Beacon interval := time.Duration(b.Every) * time.Second l.stopBeacon = doEvery(interval, func() { if err := agwpeTNC.SendUI([]byte(b.Message), b.Destination); err != nil { log.Printf("%s beacon failed: %s", l.Name(), err) l.stopBeacon() } }) return nil } func (l AX25AGWPEListener) BeaconStop() { if l.stopBeacon != nil { l.stopBeacon() } } type TelnetListener struct{} func (l TelnetListener) Name() string { return MethodTelnet } func (l TelnetListener) Init() (net.Listener, error) { return telnet.Listen(config.Telnet.ListenAddr) } func (l TelnetListener) CurrentFreq() (Frequency, bool) { return 0, false } func doEvery(interval time.Duration, fn func()) (cancel func()) { if interval == 0 { return } ctx, cancel := context.WithCancel(context.Background()) go func() { t := time.NewTicker(interval) defer t.Stop() for { select { case <-ctx.Done(): return case <-t.C: fn() } } }() return cancel } pat-0.15.1/listener_hub.go000066400000000000000000000071351453425652100154110ustar00rootroot00000000000000// Copyright 2017 Martin Hebnes Pedersen (LA5NTA). All rights reserved. // Use of this source code is governed by the MIT-license that can be // found in the LICENSE file. package main import ( "log" "net" "sync" "time" ) type TransportListener interface { Init() (net.Listener, error) Name() string CurrentFreq() (Frequency, bool) } type Beaconer interface { BeaconStop() BeaconStart() error } type Listener struct { t TransportListener hub *ListenerHub mu sync.Mutex isClosed bool err error ln net.Listener } func NewListener(t TransportListener) *Listener { return &Listener{t: t} } func (l *Listener) Err() error { l.mu.Lock() defer l.mu.Unlock() return l.err } func (l *Listener) Close() error { l.mu.Lock() defer l.mu.Unlock() if l.isClosed { return l.err } l.isClosed = true // If l.err is not nil, then the last attempt to open the listener failed and we don't have anything to close if l.err != nil { return l.err } return l.ln.Close() } func (l *Listener) listenLoop() { var silenceErr bool for { l.mu.Lock() if l.isClosed { l.mu.Unlock() break } // Try to init the TNC l.ln, l.err = l.t.Init() if l.err != nil { l.mu.Unlock() if !silenceErr { log.Printf("Listener %s failed: %s", l.t.Name(), l.err) log.Printf("Will try to re-establish listener in the background...") silenceErr = true websocketHub.UpdateStatus() } time.Sleep(time.Second) continue } l.mu.Unlock() if silenceErr { log.Printf("Listener %s re-established", l.t.Name()) silenceErr = false websocketHub.UpdateStatus() } if b, ok := l.t.(Beaconer); ok { b.BeaconStart() } // Run the accept loop until an error occurs if err := l.acceptLoop(); err != nil { log.Printf("Accept %s failed: %s", l.t.Name(), err) } if b, ok := l.t.(Beaconer); ok { b.BeaconStop() } } } type RemoteCaller interface { RemoteCall() string } func (l *Listener) acceptLoop() error { for { conn, err := l.ln.Accept() if err != nil { return err } remoteCall := conn.RemoteAddr().String() if c, ok := conn.(RemoteCaller); ok { remoteCall = c.RemoteCall() } freq, _ := l.t.CurrentFreq() eventLog.LogConn("accept", freq, conn, nil) log.Printf("Got connect (%s:%s)", l.t.Name(), remoteCall) err = exchange(conn, remoteCall, true) if err != nil { log.Printf("Exchange failed: %s", err) } else { log.Println("Disconnected.") } } } type ListenerHub struct { mu sync.Mutex listeners map[string]*Listener } func NewListenerHub() *ListenerHub { return &ListenerHub{ listeners: map[string]*Listener{}, } } func (h *ListenerHub) Active() []TransportListener { h.mu.Lock() defer h.mu.Unlock() slice := make([]TransportListener, 0, len(h.listeners)) for _, l := range h.listeners { if l.Err() != nil { continue } slice = append(slice, l.t) } return slice } func (h *ListenerHub) Enable(t TransportListener) { h.mu.Lock() defer func() { h.mu.Unlock() websocketHub.UpdateStatus() }() l := NewListener(t) if _, ok := h.listeners[t.Name()]; ok { return } h.listeners[t.Name()] = l go l.listenLoop() } func (h *ListenerHub) Disable(name string) (bool, error) { if name == MethodAX25 { name = defaultAX25Method() } h.mu.Lock() defer func() { h.mu.Unlock() websocketHub.UpdateStatus() }() l, ok := h.listeners[name] if !ok { return false, nil } delete(h.listeners, name) return true, l.Close() } func (h *ListenerHub) Close() { h.mu.Lock() defer func() { h.mu.Unlock() websocketHub.UpdateStatus() }() for k, l := range h.listeners { l.Close() delete(h.listeners, k) } } pat-0.15.1/main.go000066400000000000000000000440671453425652100136570ustar00rootroot00000000000000// Copyright 2016 Martin Hebnes Pedersen (LA5NTA). All rights reserved. // Use of this source code is governed by the MIT-license that can be // found in the LICENSE file. // A portable Winlink client for amateur radio email. package main import ( "context" "fmt" "io" "log" "net" "os" "os/exec" "os/signal" "path/filepath" "runtime" "strconv" "strings" "time" "github.com/la5nta/pat/cfg" "github.com/la5nta/pat/internal/buildinfo" "github.com/la5nta/pat/internal/debug" "github.com/la5nta/pat/internal/directories" "github.com/la5nta/pat/internal/forms" "github.com/la5nta/pat/internal/gpsd" "github.com/la5nta/wl2k-go/catalog" "github.com/la5nta/wl2k-go/fbb" "github.com/la5nta/wl2k-go/mailbox" "github.com/la5nta/wl2k-go/rigcontrol/hamlib" "github.com/spf13/pflag" ) const ( MethodArdop = "ardop" MethodTelnet = "telnet" MethodPactor = "pactor" MethodVaraHF = "varahf" MethodVaraFM = "varafm" MethodAX25 = "ax25" MethodAX25AGWPE = MethodAX25 + "+agwpe" MethodAX25Linux = MethodAX25 + "+linux" MethodAX25SerialTNC = MethodAX25 + "+serial-tnc" // TODO: Remove after some release cycles (2023-05-21) MethodSerialTNCDeprecated = "serial-tnc" ) var commands = []Command{ { Str: "connect", Desc: "Connect to a remote station.", HandleFunc: connectHandle, Usage: UsageConnect, Example: ExampleConnect, MayConnect: true, }, { Str: "interactive", Desc: "Run interactive mode.", Usage: "[options]", Options: map[string]string{ "--http, -h": "Start http server for web UI in the background.", }, HandleFunc: InteractiveHandle, MayConnect: true, LongLived: true, }, { Str: "http", Desc: "Run http server for web UI.", Usage: "[options]", Options: map[string]string{ "--addr, -a": "Listen address. Default is :8080.", }, HandleFunc: httpHandle, MayConnect: true, LongLived: true, }, { Str: "compose", Desc: "Compose a new message.", Usage: "[options]\n" + "\tIf no options are passed, composes interactively.\n" + "\tIf options are passed, reads message from stdin similar to mail(1).", Options: map[string]string{ "--from, -r": "Address to send from. Default is your call from config or --mycall, but can be specified to use tactical addresses.", "--subject, -s": "Subject", "--attachment , -a": "Attachment path (may be repeated)", "--cc, -c": "CC Address(es) (may be repeated)", "--p2p-only": "Send over peer to peer links only (avoid CMS)", "": "Recipient address (may be repeated)", }, HandleFunc: composeMessage, }, { Str: "read", Desc: "Read messages.", HandleFunc: func(ctx context.Context, args []string) { readMail(ctx) }, }, { Str: "composeform", Aliases: []string{"formPath"}, Desc: "Post form-based report.", Usage: "[options]", Options: map[string]string{ "--template": "path to the form template file. Uses the --forms directory as root. Defaults to 'ICS USA Forms/ICS213.txt'", }, HandleFunc: composeFormReport, }, { Str: "position", Aliases: []string{"pos"}, Desc: "Post a position report (GPSd or manual entry).", Usage: "[options]", Options: map[string]string{ "--latlon": "latitude,longitude in decimal degrees for manual entry. Will use GPSd if this is empty.", "--comment, -c": "Comment to be included in the position report.", }, Example: ExamplePosition, HandleFunc: posReportHandle, }, { Str: "extract", Desc: "Extract attachments from a message file.", Usage: "file", HandleFunc: extractMessageHandle, }, { Str: "rmslist", Desc: "Print/search in list of RMS nodes.", Usage: "[options] [search term]", Options: map[string]string{ "--mode, -m": "Mode filter.", "--band, -b": "Band filter (e.g. '80m').", "--force-download, -d": "Force download of latest list from winlink.org.", "--sort-distance, -s": "Sort by distance", }, HandleFunc: rmsListHandle, }, { Str: "updateforms", Desc: "Download the latest form templates from winlink.org.", HandleFunc: func(ctx context.Context, args []string) { if _, err := formsMgr.UpdateFormTemplates(ctx); err != nil { log.Printf("%v", err) } }, }, { Str: "configure", Desc: "Open configuration file for editing.", HandleFunc: configureHandle, }, { Str: "version", Desc: "Print the application version.", HandleFunc: func(_ context.Context, args []string) { fmt.Printf("%s %s\n", buildinfo.AppName, buildinfo.VersionString()) }, }, { Str: "env", Desc: "List environment variables.", HandleFunc: envHandle, }, { Str: "help", Desc: "Print detailed help for a given command.", // Avoid initialization loop by invoking helpHandler in main }, } var ( config cfg.Config rigs map[string]hamlib.VFO logWriter io.Writer eventLog *EventLogger exchangeChan chan ex // The channel that the exchange loop is listening on exchangeConn net.Conn // Pointer to the active session connection (exchange) mbox *mailbox.DirHandler // The mailbox listenHub *ListenerHub promptHub *PromptHub formsMgr *forms.Manager ) var fOptions struct { IgnoreBusy bool // Move to connect? SendOnly bool // Move to connect? RadioOnly bool Robust bool MyCall string Listen string MailboxPath string ConfigPath string LogPath string EventLogPath string FormsPath string } func optionsSet() *pflag.FlagSet { set := pflag.NewFlagSet("options", pflag.ExitOnError) set.StringVar(&fOptions.MyCall, "mycall", "", "Your callsign (winlink user).") set.StringVarP(&fOptions.Listen, "listen", "l", "", "Comma-separated list of methods to listen on (e.g. ardop,telnet,ax25).") set.BoolVarP(&fOptions.SendOnly, "send-only", "s", false, "Download inbound messages later, send only.") set.BoolVarP(&fOptions.RadioOnly, "radio-only", "", false, "Radio Only mode (Winlink Hybrid RMS only).") set.BoolVar(&fOptions.IgnoreBusy, "ignore-busy", false, "Don't wait for clear channel before connecting to a node.") defaultMBox := filepath.Join(directories.DataDir(), "mailbox") defaultFormsPath := filepath.Join(directories.DataDir(), "Standard_Forms") defaultConfigPath := filepath.Join(directories.ConfigDir(), "config.json") defaultLogPath := filepath.Join(directories.StateDir(), strings.ToLower(buildinfo.AppName+".log")) defaultEventLogPath := filepath.Join(directories.StateDir(), "eventlog.json") set.StringVar(&fOptions.MailboxPath, "mbox", defaultMBox, "Path to mailbox directory.") set.StringVar(&fOptions.FormsPath, "forms", defaultFormsPath, "Path to forms directory.") set.StringVar(&fOptions.ConfigPath, "config", defaultConfigPath, "Path to config file.") set.StringVar(&fOptions.LogPath, "log", defaultLogPath, "Path to log file. The file is truncated on each startup.") set.StringVar(&fOptions.EventLogPath, "event-log", defaultEventLogPath, "Path to event log file.") return set } func init() { listenHub = NewListenerHub() promptHub = NewPromptHub() pflag.Usage = func() { fmt.Fprintf(os.Stderr, "%s is a client for the Winlink 2000 Network.\n\n", buildinfo.AppName) fmt.Fprintf(os.Stderr, "Usage:\n %s [options] command [arguments]\n", os.Args[0]) fmt.Fprintln(os.Stderr, "\nCommands:") for _, cmd := range commands { fmt.Fprintf(os.Stderr, " %-15s %s\n", cmd.Str, cmd.Desc) } fmt.Fprintln(os.Stderr, "\nOptions:") optionsSet().PrintDefaults() fmt.Fprint(os.Stderr, "\n") } } func main() { cmd, args := parseFlags(os.Args) debug.Printf("Version: %s", buildinfo.VersionString()) debug.Printf("Command: %s %v", cmd.Str, args) fOptions.MailboxPath = filepath.Clean(fOptions.MailboxPath) fOptions.FormsPath = filepath.Clean(fOptions.FormsPath) fOptions.ConfigPath = filepath.Clean(fOptions.ConfigPath) fOptions.LogPath = filepath.Clean(fOptions.LogPath) fOptions.EventLogPath = filepath.Clean(fOptions.EventLogPath) debug.Printf("Mailbox dir is\t'%s'", fOptions.MailboxPath) debug.Printf("Forms dir is\t'%s'", fOptions.FormsPath) debug.Printf("Config file is\t'%s'", fOptions.ConfigPath) debug.Printf("Log file is \t'%s'", fOptions.LogPath) debug.Printf("Event log file is\t'%s'", fOptions.EventLogPath) directories.MigrateLegacyDataDir() // Graceful shutdown by cancelling background context on interrupt. // // If we have an active connection, cancel that instead. sig := make(chan os.Signal, 1) signal.Notify(sig, os.Interrupt) ctx, cancel := context.WithCancel(context.Background()) defer cancel() go func() { dirtyDisconnectNext := false // So we can do a dirty disconnect on the second interrupt for { <-sig if ok := abortActiveConnection(dirtyDisconnectNext); ok { dirtyDisconnectNext = !dirtyDisconnectNext } else { break } } cancel() }() // Skip initialization for some commands switch cmd.Str { case "help": helpHandle(args) return case "configure", "version": cmd.HandleFunc(ctx, args) return } // Enable the GZIP extension experiment by default if _, ok := os.LookupEnv("GZIP_EXPERIMENT"); !ok { os.Setenv("GZIP_EXPERIMENT", "1") } // Parse configuration file var err error config, err = LoadConfig(fOptions.ConfigPath, cfg.DefaultConfig) if err != nil { log.Fatalf("Unable to load/write config: %s", err) } // Initialize logger f, err := os.Create(fOptions.LogPath) if err != nil { log.Fatal(err) } logWriter = io.MultiWriter(f, os.Stdout) log.SetOutput(logWriter) eventLog, err = NewEventLogger(fOptions.EventLogPath) if err != nil { log.Fatal("Unable to open event log file:", err) } // Read command line options from config if unset if fOptions.MyCall == "" && config.MyCall == "" { fmt.Fprint(os.Stderr, "Missing mycall\n") os.Exit(1) } else if fOptions.MyCall == "" { fOptions.MyCall = config.MyCall } // Ensure mycall is all upper case. fOptions.MyCall = strings.ToUpper(fOptions.MyCall) // Don't use config password if we don't use config mycall if !strings.EqualFold(fOptions.MyCall, config.MyCall) { config.SecureLoginPassword = "" } // Replace placeholders in connect aliases for k, v := range config.ConnectAliases { config.ConnectAliases[k] = strings.ReplaceAll(v, cfg.PlaceholderMycall, fOptions.MyCall) } if fOptions.Listen == "" && len(config.Listen) > 0 { fOptions.Listen = strings.Join(config.Listen, ",") } // init forms subsystem formsMgr = forms.NewManager(forms.Config{ FormsPath: fOptions.FormsPath, MyCall: fOptions.MyCall, Locator: config.Locator, AppVersion: buildinfo.VersionStringShort(), UserAgent: buildinfo.UserAgent(), LineReader: readLine, GPSd: config.GPSd, }) // Make sure we clean up on exit, closing any open resources etc. defer cleanup() // Load the mailbox handler loadMBox() if cmd.MayConnect { rigs = loadHamlibRigs() exchangeChan = exchangeLoop() go func() { if config.VersionReportingDisabled { return } for { // Check every 6 hours, but it won't post more frequent than 24h. postVersionUpdate() // Ignore errors time.Sleep(6 * time.Hour) } }() } if cmd.LongLived { if fOptions.Listen != "" { Listen(fOptions.Listen) } scheduleLoop() } // Start command execution cmd.HandleFunc(ctx, args) } func configureHandle(ctx context.Context, args []string) { // Ensure config file has been written _, err := ReadConfig(fOptions.ConfigPath) if os.IsNotExist(err) { err = WriteConfig(cfg.DefaultConfig, fOptions.ConfigPath) if err != nil { log.Fatalf("Unable to write default config: %s", err) } } cmd := exec.CommandContext(ctx, EditorName(), fOptions.ConfigPath) cmd.Stdin, cmd.Stdout, cmd.Stderr = os.Stdin, os.Stdout, os.Stderr if err := cmd.Run(); err != nil { log.Fatalf("Unable to start editor: %s", err) } } func InteractiveHandle(ctx context.Context, args []string) { var http string set := pflag.NewFlagSet("interactive", pflag.ExitOnError) set.StringVar(&http, "http", "", "HTTP listen address") set.Lookup("http").NoOptDefVal = config.HTTPAddr set.Parse(args) if http == "" { Interactive(ctx) return } ctx, cancel := context.WithCancel(ctx) defer cancel() go func() { if err := ListenAndServe(ctx, http); err != nil { log.Println(err) } }() time.Sleep(time.Second) Interactive(ctx) } func httpHandle(ctx context.Context, args []string) { addr := config.HTTPAddr if addr == "" { addr = ":8080" // For backwards compatibility (remove in future) } set := pflag.NewFlagSet("http", pflag.ExitOnError) set.StringVarP(&addr, "addr", "a", addr, "Listen address.") set.Parse(args) if addr == "" { set.Usage() os.Exit(1) } promptHub.OmitTerminal(true) if err := ListenAndServe(ctx, addr); err != nil { log.Println(err) } } func connectHandle(_ context.Context, args []string) { if args[0] == "" { fmt.Println("Missing argument, try 'connect help'.") } if success := Connect(args[0]); !success { os.Exit(1) } } func helpHandle(args []string) { arg := args[0] var cmd *Command for _, c := range commands { if c.Str == arg { cmd = &c break } } if arg == "" || cmd == nil { pflag.Usage() return } cmd.PrintUsage() } func cleanup() { debug.Printf("Starting cleanup") defer debug.Printf("Cleanup done") abortActiveConnection(false) listenHub.Close() if adTNC != nil { if err := adTNC.Close(); err != nil { log.Printf("Failure to close ardop TNC: %s", err) } } if pModem != nil { if err := pModem.Close(); err != nil { log.Printf("Failure to close pactor modem: %s", err) } } if varaFMModem != nil { if err := varaFMModem.Close(); err != nil { log.Printf("Failure to close varafm modem: %s", err) } } if varaHFModem != nil { if err := varaHFModem.Close(); err != nil { log.Printf("Failure to close varahf modem: %s", err) } } if agwpeTNC != nil { if err := agwpeTNC.Close(); err != nil { log.Printf("Failure to close AGWPE TNC: %s", err) } } eventLog.Close() } func loadMBox() { mbox = mailbox.NewDirHandler( filepath.Join(fOptions.MailboxPath, fOptions.MyCall), fOptions.SendOnly, ) // Ensure the mailbox handler is ready if err := mbox.Prepare(); err != nil { log.Fatal(err) } } func loadHamlibRigs() map[string]hamlib.VFO { rigs := make(map[string]hamlib.VFO, len(config.HamlibRigs)) for name, conf := range config.HamlibRigs { if conf.Address == "" { log.Printf("Missing address-field for rig '%s', skipping.", name) continue } if conf.Network == "" { conf.Network = "tcp" } rig, err := hamlib.Open(conf.Network, conf.Address) if err != nil { log.Printf("Initialization hamlib rig %s failed: %s.", name, err) continue } var vfo hamlib.VFO switch strings.ToUpper(conf.VFO) { case "A", "VFOA": vfo, err = rig.VFOA() case "B", "VFOB": vfo, err = rig.VFOB() case "": vfo = rig.CurrentVFO() default: log.Printf("Cannot load rig '%s': Unrecognized VFO identifier '%s'", name, conf.VFO) continue } if err != nil { log.Printf("Cannot load rig '%s': Unable to select VFO: %s", name, err) continue } f, err := vfo.GetFreq() if err != nil { log.Printf("Unable to get frequency from rig %s: %s.", name, err) } else { log.Printf("%s ready. Dial frequency is %s.", name, Frequency(f)) } rigs[name] = vfo } return rigs } func extractMessageHandle(_ context.Context, args []string) { if len(args) == 0 || args[0] == "" { panic("TODO: usage") } file, _ := os.Open(args[0]) defer file.Close() msg := new(fbb.Message) if err := msg.ReadFrom(file); err != nil { log.Fatal(err) } else { fmt.Println(msg) for _, f := range msg.Files() { if err := os.WriteFile(f.Name(), f.Data(), 0o664); err != nil { log.Fatal(err) } } } } func EditorName() string { if e := os.Getenv("EDITOR"); e != "" { return e } else if e := os.Getenv("VISUAL"); e != "" { return e } switch runtime.GOOS { case "windows": return "notepad" case "linux": if path, err := exec.LookPath("editor"); err == nil { return path } } return "vi" } func posReportHandle(ctx context.Context, args []string) { var latlon, comment string set := pflag.NewFlagSet("position", pflag.ExitOnError) set.StringVar(&latlon, "latlon", "", "") set.StringVarP(&comment, "comment", "c", "", "") set.Parse(args) report := catalog.PosReport{Comment: comment} if latlon != "" { parts := strings.Split(latlon, ",") if len(parts) != 2 { log.Fatal(`Invalid position format. Expected "latitude,longitude".`) } lat, err := strconv.ParseFloat(parts[0], 64) if err != nil { log.Fatal(err) } report.Lat = &lat lon, err := strconv.ParseFloat(parts[1], 64) if err != nil { log.Fatal(err) } report.Lon = &lon } else if config.GPSd.Addr != "" { conn, err := gpsd.Dial(config.GPSd.Addr) if err != nil { log.Fatalf("GPSd daemon: %s", err) } defer conn.Close() conn.Watch(true) posChan := make(chan gpsd.Position) go func() { defer close(posChan) pos, err := conn.NextPos() if err != nil { log.Printf("GPSd: %s", err) return } posChan <- pos }() log.Println("Waiting for position from GPSd...") // TODO: Spinning bar? var pos gpsd.Position select { case p := <-posChan: pos = p case <-ctx.Done(): log.Println("Cancelled") return } report.Lat = &pos.Lat report.Lon = &pos.Lon if config.GPSd.UseServerTime { report.Date = time.Now() } else { report.Date = pos.Time } // Course and speed is part of the spec, but does not seem to be // supported by winlink.org anymore. Ignore it for now. if false && pos.Track != 0 { course := CourseFromFloat64(pos.Track, false) report.Course = &course } } else { fmt.Println("No position available. See --help") os.Exit(1) } if report.Date.IsZero() { report.Date = time.Now() } postMessage(report.Message(fOptions.MyCall)) } func CourseFromFloat64(f float64, magnetic bool) catalog.Course { c := catalog.Course{Magnetic: magnetic} str := fmt.Sprintf("%03.0f", f) for i := 0; i < 3; i++ { c.Digits[i] = str[i] } return c } func postMessage(msg *fbb.Message) { if err := msg.Validate(); err != nil { fmt.Printf("WARNING - Message does not validate: %s\n", err) } if err := mbox.AddOut(msg); err != nil { log.Fatal(err) } fmt.Println("Message posted") } pat-0.15.1/make.bash000077500000000000000000000047661453425652100141650ustar00rootroot00000000000000#!/usr/bin/env bash set -e export GO111MODULE=on if [ -d $GOOS ]; then OS=$(go env GOOS); else OS=$GOOS; fi if [ -d $CGO_ENABLED ]; then CGO_ENABLED=$(go env CGO_ENABLED); else OS=$CGO_ENABLED; fi GITREV=$(git rev-parse --short HEAD) VERSION=$(grep "Version =" internal/buildinfo/VERSION.go|cut -d '"' -f2) # Go 1.19 or later is required GO_POINT_VERSION=$(go version| perl -ne 'm/go1\.(\d+)/; print $1;') [ "$GO_POINT_VERSION" -lt "19" ] && echo "Go 1.19 or later required" && exit 1; AX25VERSION="0.0.12-rc4" AX25DIST="libax25-${AX25VERSION}" AX25DIST_URL="http://http.debian.net/debian/pool/main/liba/libax25/libax25_${AX25VERSION}.orig.tar.gz" function install_libax25 { mkdir -p .build && cd .build [[ -f "${AX25DIST}" ]] || curl -LSsf "${AX25DIST_URL}" | tar zx cd "${AX25DIST}/" && ./configure --prefix=/ && make && cd ../../ } function build_web { cd web if [ -d $NVM_DIR ]; then source $NVM_DIR/nvm.sh nvm install nvm use fi npm install npm run production } [[ "$1" == "libax25" ]] && install_libax25 && exit 0; [[ "$1" == "web" ]] && build_web && exit 0; # Link against libax25 (statically) on Linux if [[ "$OS" == "linux"* ]] && [[ "$CGO_ENABLED" == "1" ]]; then TAGS="libax25 $TAGS" LIB=".build/${AX25DIST}/.libs/libax25.a" if [[ -z "$CGO_LDFLAGS" ]] && [[ -f "$LIB" ]]; then export CGO_CFLAGS="-I$(pwd)/.build/${AX25DIST}" export CGO_LDFLAGS="$(pwd)/${LIB}" fi if [[ -z "$CGO_LDFLAGS" ]]; then echo "WARNING: No static libax25 library available." echo " Linking against shared library instead. To fix" echo " this issue, set CGO_LDFLAGS to the full path of" echo " libax25.a, or run 'make.bash libax25' to download" echo " and compile ${AX25DIST} in .build/" else TAGS="static $TAGS" fi else if [[ "$OS" == "linux"* ]]; then echo "WARNING: CGO unavailable. libax25 (ax25+linux) will not be supported with this build." fi fi echo -e "Downloading Go dependencies..." go mod download echo "Running tests..." if [[ "$SKIP_TESTS" == "1" ]]; then echo "Skipping." else go test -tags "$TAGS" ./... github.com/la5nta/wl2k-go/... fi echo echo "Building Pat v$VERSION..." go build -tags "$TAGS" -ldflags "-X \"github.com/la5nta/pat/internal/buildinfo.GitRev=$GITREV\"" $(go list .) # Build macOS pkg if [[ "$OS" == "darwin"* ]] && command -v packagesbuild >/dev/null 2>&1; then ARCH=$(go env GOARCH) echo "Generating macOS installer package..." packagesbuild osx/pat.pkgproj mv 'Pat :: A Modern Winlink Client.pkg' "pat_${VERSION}_darwin_${ARCH}_unsigned.pkg" fi echo -e "Enjoy!" pat-0.15.1/man/000077500000000000000000000000001453425652100131445ustar00rootroot00000000000000pat-0.15.1/man/pat-configure.1000066400000000000000000000011671453425652100157760ustar00rootroot00000000000000.TH PAT 1 "2017-09-04" "" "Pat Configure" .SH NAME pat configure \- opens Pat's configuration file using the system default editor .SH Configuration .SS Main Configuration The current configuration file is located in the \fIPAT_CONFIG_PATH\fP returned from \fIpat env\fP .sp 1 To get "on the air" you'll first have to set up your callsign, maidenhead locator, and secure login credentials. Look for the attributes \fImycall\fP, \fIlocator\fP and \fIsecure_login_password\fP and set them appropriately. .sp 1 .in 20 { "mycall": "LA5NTA", "locator": "JP20qe", "secure_login_password": "MYPASSWORD", } .in .SH "See Also" pat(1) pat-0.15.1/man/pat.1000066400000000000000000000033321453425652100140130ustar00rootroot00000000000000.TH PAT 1 "2017-09-04" "" "Pat Overview" .SH NAME pat \- a cross platform Winlink client with basic messaging capabilities .SH SYNOPSIS \fBpat\fP [options] \fIcommand\fP [arguments] .SS Commands .TP \fIconnect\fP Connect to a remote station. .TP \fIinteractive\fP Run interactive mode. .TP \fIhttp\fP Run http server for web gui. .TP \fIcompose\fP Compose a new message. .TP \fIcomposeform\fP Compose a new message based on a Winlink-style form, e.g. the ICS213 form. .TP \fIread\fP Read Messages. .TP \fIposition\fP Post a position report (GPSd or manual entry). .TP \fIextract\fP Extract attachments from a message file. .TP \fIrmslist\fP Print/search in list of RMS nodes. .TP \fIconfigure\fP Open configuration file for editing. .TP \fIversion\fP Print the application version. .TP \fIhelp\fP Print detailed help for a given command. .SS Options .TP \fR--config string\fP Path to config file (located in the \fIPAT_CONFIG_PATH\fP returned from \fIpat env\fP). .TP \fR--event-log string\fP Path to event log file (located in the \fIPAT_CONFIG_PATH\fP returned from \fIpat env\fP). .TP \fR--ignore-busy\fP Don't wait for clear channel before connevting to a node. .TP \fR-l, --listen string\fP Comma-separated list of methods to listen on (e.g. ardop,telnet,ax25). .TP \fR--log string\fP Path to log file. The file is truncated on each startup (located in the \fIPAT_LOG_PATH\fP returned from \fIpat env\fP). .TP \fR--mbox string\fP Path to mailbox directory (located in the \fIPAT_MAILBOX_PATH\fP returned from \fIpat env\fP). .TP \fR--mycall string\fP Your callsing (winlink user). .TP \fR--radio-only\fP Radio Only mode (Winlink Hybrid RMS only). .TP \fR-s, --send-only\fP Download inbound messages later, send only. .SH "See Also" pat-configure(1) pat-0.15.1/osx/000077500000000000000000000000001453425652100132025ustar00rootroot00000000000000pat-0.15.1/osx/Pat-Info.rtfd/000077500000000000000000000000001453425652100155555ustar00rootroot00000000000000pat-0.15.1/osx/Pat-Info.rtfd/TXT.rtf000066400000000000000000000010671453425652100167550ustar00rootroot00000000000000{\rtf1\ansi\ansicpg1252\cocoartf1404\cocoasubrtf470 {\fonttbl\f0\fswiss\fcharset0 Helvetica;\f1\fnil\fcharset0 Menlo-Regular;} {\colortbl;\red255\green255\blue255;} \margl1440\margr1440\vieww12540\viewh16140\viewkind1 \pard\tx720\tx1440\tx2160\tx2880\tx3600\tx4320\tx5040\tx5760\tx6480\tx7200\tx7920\tx8640\pardirnatural\qc\partightenfactor0 \f0\fs24 \cf0 This will install \f1 pat \f0 into \f1 /usr/local/bin \f0 . \ To run \f1 pat \f0 , use Terminal.app in the \f1 /Applications/Utilities \f0 folder.\ \ \fs26 For more help and information, visit getpat.io}pat-0.15.1/osx/Pat-License.txt000066400000000000000000000021171453425652100160500ustar00rootroot00000000000000The MIT License (MIT) Copyright (c) 2014-2017 Martin Hebnes Pedersen (LA5NTA) Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. pat-0.15.1/osx/Pat-Welcome.rtfd/000077500000000000000000000000001453425652100162555ustar00rootroot00000000000000pat-0.15.1/osx/Pat-Welcome.rtfd/Pasted Graphic.tiff000066400000000000000000000441321453425652100217110ustar00rootroot00000000000000MM*;8 P8$ BaPd6DbQ8V-FcQv=HdR9$M'JeRd]/LfS9m7NgS}?PhT:%GRiTe6OTjU:VWVkUv_XlV;%gZmVeo\nW;w^oW`pX<& bqXf7drY'ry\g7tz]>Wv{]wx|^?'z}^g|~_?~_ , Lj P*3 CNDBDq4JDs B4:`BBT; @"!H(dCN~g'{'B} r&DQ\\4fJB: ;6 @l:Gq"R8?I/S s' R*CByg(Irx|UE104r8B, : B}PR Ȁ924O jAp 'B?* T>v'U0u gMLRP"s 7PbLg3X0 bLP_%gCo0v g!4,q .S\pETF;hpWrE  AT. CNŀ3wŜDݑI#gE&i5M ` 9COD0 !PiUUnK`osMjO"vAgvu] 'o`5K T9O)Dh $&)}VYT``^*1 #2ŊT0 e>oAP'Y$ML qW Pěq Q*2i`{H8㢂2}$GkHϚiF(3PB>4ۅb*! Fqj {T)|foH'ˣk>p dzKc`E`h(079Nsuq2 .xgVUx$!zN"1 PjZDlGε]%,n$U [WN#=y1c-A'XVtu (y$eE,5}=('an/^s\cYֵs]׵Eؔ{(. ;L)Tс"j͸e4M ?8#?TvWxXcFh&J ƕ`8#mk4j>ns8Xi[W _c0 tSюKhWv  jgŠ'ևyXz4ZWa:((ۆ@|y8' ݍmXPS>c_F"Drڟ -drX k J95u|8YbN3^q-kr3-usn"M>&Ec{^=:yȪ`3o3"*`~!}civؒ0{Z]A#~(ܡnCMˍ@X!H3l 5,zg+EUr Ql4K aM=f?I3 } @˜3&$ۂ_A`,5~aڱ/h DR!J@К=2F\V~a꾆d^Ƒ@)QAlP5G'g!ëE}BZ0HoY259 S@6E>GJw5$5зZmA"h$08UCwPnK]g @))4X&,p_`%/ U3e*e ٪0GbD0b"]I {Pw7}kdp~2 <Q'2_P-]} 7-+jj Sde& 6x/DSόcό, # gIՀb | 0S{VX?"q)*^m4Y!h H7UBU𸠔7!l:u{O2Gd>Pc  ,I׏ʦC*Z&`#w!Wl-8a=Ҕd#/ E1bxeFPouj` pirGg,!o(g$C.rhʦ] @~< "@`*G^ ty?C 'oW Uh <~<@] p Cl ,pgS k\P?d7@֋ZVJH E4Z"sF.Ay@{"CPCpTe`%ri$Vv&4$ MP)@8~dDp=N(3h A>pqP[6:0:pY0"zSEk*!2@SN24cnBxO />/OF((D8wLP,yC/дalCo<\t#>GbKq*@菶k)]+{@P\o@(G#S2@ٕIիM#@EEѐ08D:| <`y.z ZaEBQ 1J,I¹MM[`1Q .l0eNy`>!0޽ݹbX h`L6<i MI!+RUȬ8* ^*|'{ 7T?M!¿-`] v5x،+m" hgjX3辘 Zwz302+x`/î/0R(S c8dhxpy3B/a}[4j2Ո( ¨@Hyw. X}8P hj cHNOkl;Xz2z)`+@4**+D9~|P=<&À0~@s@I\]h#4VDq2j#7@):Di| * xp87gBkq r`B(C`|Gtb*/sO{Ā(xThƜq((8w~t,~?`2 M\08RSemn@|ZXqVh~4Pebʸ  zisA l}<6lQܕIsE bH{ClA 1_r2&+el̷ijsKݷ \Zܾ6#VS!((nN( \fp| ݀_}0yϨkps@s5 &# 5,8{eaPmF& U>p~8p`louK9j_X,V-FX_fˆ ^zi=u_PÆdr0 ah2聇̀|Hh!X~=KSk}Vfj@jqLjmnwvbAlV5Z!3 ` Y)E|] |V>Evi3kQrM%h^NoG(w2 ;@&`s8ņx O,̸iwVfG4v@nGNi]hlLT֩h Wp(鈭"Pp n8n|H vm76^G|Xpp") q/hFfhZyH;FL{qRz^ǭ٣YNi͇l/q8sܝx]"CHpȀPF"`?B Ff cZWe2$ZX$Kc@y2k_0{VG%\\@u~Vh pMԀ!P;Q33[mpn(Ok8'3RЀ9 SXk\$͓ݏ/sB4 Ly*+KjЕRAJHН F(l')ılK2hɵHfHpH6Hi|NioR R*4 )K*Bˮu'(O 2d򰉄+j=(L wlgЋή 1LdbFt-H4\XS~{3D)R{D0;, /A$rtXs#QH+J+aٮc=FQn &Ei[O }z@"@PK}q\^$31^RȲX2uwEY1L igx] qenS>İ^ ")ѣ[%V-E`J6H2CC]f$9#aNn1S :K1s2q*IR aqJ`Pw$:+c2F/H%bw2SE3%XlT9֦&{l*jLFQT0^& Hbk6ĞEnF6ÙHy<הӁP%a&H7Bt.،'6tQm g$WaEQQRRs @8il:Pt2|NùQMMbvbx=]. h@PYNIT,239=m͝>UUG/ղpDiE`%WTt^W_VE͝O*TY@ ˳XHQSUՈr֔ _p%xRzJl}wvk1q^4`?a2 BC|!n5h^0uM#?\inlBʑ1A6y H- jع(Z#~;|FGX}_H]v<.*@T pv7ae#]D^8h@, 8I`"qF3؀y.̾Ü A7MwxM;~8ouv6(p¸eJ]k&|kU=Ǹt.+EhVb 0^ `9 ĽƆÔ<` ̩ _B[n_)櫹S[,!9B!z(Rzb1i[?uo}ge!v{`lÎ=pf4'Z=^r` #đw,#3A!n `,!Z`2m%'蝡D@.$>*MN4.B >@#+> V,{-Vo/P o6/* a8ւ@2#) !`a@hp<@ a0(&cmdB@`ZT<<, CNvA!#A,`D` "zFKNTQ#jOqa! *#p Nw/ʱ qP N"a N ; [ PB``'AD LŪ!aXމ,:+D.@ r t/q$N@=P/an (4<4B @ATVG@enHVe$Jq "{1qϣ4.N 8 XQ163j 6! .";+`.srK S~6V2N! @aVvf֬\! 0q/0f"x O=00q1s~85q !E !"2'" `!!9aaPmLf`hG `$Al`^ .2dR)zv/ L=D 'C"R:t#!-Pk.t ]- 3w /5@\ڳ5Q+ o ? 9@p@&#R/5p!106a$RQRTQOH!!D#!`;_a!pr$zETAmE-[ЕTF KX#&ĒK2b` t\.M Τ`JFNa1bɕ >j5 bnQ-Q%3?-.8$@Z  n@A /6>C}D# qjތҶ#dѐaN@9{4N!AT`55C@ ׯinloz>ߦ<KHS EQӀB]t(9c͖ag'(H@%`^` 粃9D,5}e\[ gln_h$S$<̶ mmލ7x"r#P cN ]0X?PS5cdTy͚Ҡ"6 m@$fi= 6@U+3Nb2V /P# awYت8غx8njxոx*.r]lN %&.3jpEPtL0+FLFUIc2Km?#X͂Gnq;&HC%8a6@Ay:7X~Π9ߍEaaC#b_X%X#ϥ⽛7c9h v1AlJ`$Ck|dǨC6Z `a`g`(;m!v4JfV!?n`"02#v:;CפMˤR.QnچJOAR1 6/+`x#U#д1R @ !YTo`NBg9 29l$!`!mv#AN DdAiهJal|.#vˣAAm~pk᳹Z[;j P1#Z@M_ `#i<$#s= B/wvOB#S E\V='@=,! A(U c,2kJa<q+V[{%< ك9ôP6>6@F  :ܼOB5}C@3튆jnMݝ V >#T`Rnڻ/%ÏN{[#y|x]1ߵ;2 AN~dOh!r` 3V=f n8B&K70ġy2i[`彝2!tA`c!᱕`Tb 0U7ijd:ǻX/%`NFg+=1 <(!63H# aa9 0!هa$A q (-NFC! @7DX^(  V*ť2P=fYjʉ`&QAPZ5^z='>ؠ dK`*q\@`sM`aF@P51Md > GZܫV0x<}A4]28SS`DP"LH2,h4B}^/T* IمC! J.7x PqZ?@+uo 1Y%a@ 'ӿ B.I_X* īzK vbyFn.h0E0!  .{x6@v{p< qh k\Ğ{i|  .$P(Nٴ22|0{D~F fF (/Qՠld@G,þg`X*Big~\"mՅh"@m7Ơ@Xg}c Dx` @f$6t2,ȣ׀w}g"V& ݌e9NS`6NF0(eDnk!<XE(@sGnA@A2^l9{`ل#U&,DUqGXDZ%DZ"`kvj@Su"`T(l,Sq\V|$(GjYupcX* v@!=Gv1%`WEn]Kh;IO#>O2 |DϕxV`8`X'ZP}!Y8KK".jq,M@J_ H p5ﳦ \QlD:`"B0"h@C;gtD@XVVҿp,A>jî",B*qT{܍94  Gr.D@CW0"@d>šlӾ^M mUGWQ8gM|x&ȇ>i]I6E34<Lj28ƤrKH|7#=H"8},̹hPOd.eɮTj,:!rǀNxReJ} 9D蠺X@&,&P^D +e, Գ"FOiGg?d2mH(y)紨vȣ)FHy`M-"l} TD`Ǯ70e&T%>[rUc glFuSI%QںRE*H׆ ` D l\Dz9Ao@iv4BerBJ0b٢QeűKdFɌ>BinVԣY76C2 rR8 J)uڽo\nW;w) 7}k>O5x+1 @O݌?`q#juZf]5À'|nXN}'[l 0 l 'v{]/f{͌ j@ ̀0 ' WAqgЁ;LAl6'q&Jw0țŽzd>h"`@Qm'[dvޥ0ITH кG$̵-O8y0&`/f|ê8% Nh~:H4t? J(S"|k0Mr@ٌh4~Cc+nhO#Yaq6Ǯ >W-G\cݟ76j:,CɏAk?tO/n8y/f ve5"~59x]<4ZY-7ݢ! !ơbdl1 !pI̋. (4N5E ၮW#ؽC3`ÕW b]".OP`!؁;yH7a{u1b1+vp6Xc9ec qDثTOB-^*r #6@yNS^a{gؓOjgRRRt:2yJRTػV'd59&'[LRVJ.FB@! WBƛEdԽ:&{ b) S.3:g#*%eπKvd /%ZOa>%[(|-`#j*D %RT$s@HH:FP4 S*q/p~Nk=8 Y9$Y$,@d hF-5l.FK5A4*rO(%y:S(v-V4df\,cz}VՏBͱV$aA)2Tq.¦hэwiQ ԫyEW^{W_[}W_X`\ F X/`XO a\-Xoa=!Xb\MF)Xb]1Xc\m9Xc}AY"d\FIY/&dܝQYO*e\YYo.eܽaY2f\͙FiY6fݛqY:g\yY>;<;=RS< s H<4!4) HLinomntrRGB XYZ  1acspMSFTIEC sRGB-HP cprtP3desclwtptbkptrXYZgXYZ,bXYZ@dmndTpdmddvuedLview$lumimeas $tech0 rTRC< gTRC< bTRC< textCopyright (c) 1998 Hewlett-Packard CompanydescsRGB IEC61966-2.1sRGB IEC61966-2.1XYZ QXYZ XYZ o8XYZ bXYZ $descIEC http://www.iec.chIEC http://www.iec.chdesc.IEC 61966-2.1 Default RGB colour space - sRGB.IEC 61966-2.1 Default RGB colour space - sRGBdesc,Reference Viewing Condition in IEC61966-2.1,Reference Viewing Condition in IEC61966-2.1view_. \XYZ L VPWmeassig CRT curv #(-27;@EJOTY^chmrw| %+28>ELRY`gnu| &/8AKT]gqz !-8COZfr~ -;HUcq~ +:IXgw'7HYj{+=Oat 2FZn  % : O d y  ' = T j " 9 Q i  * C \ u & @ Z t .Id %A^z &Ca~1Om&Ed#Cc'Ij4Vx&IlAe@e Ek*Qw;c*R{Gp@j>i  A l !!H!u!!!"'"U"""# #8#f###$$M$|$$% %8%h%%%&'&W&&&''I'z''( (?(q(())8)k))**5*h**++6+i++,,9,n,,- -A-v--..L.../$/Z///050l0011J1112*2c223 3F3334+4e4455M555676r667$7`7788P8899B999:6:t::;-;k;;<' >`>>?!?a??@#@d@@A)AjAAB0BrBBC:C}CDDGDDEEUEEF"FgFFG5G{GHHKHHIIcIIJ7J}JK KSKKL*LrLMMJMMN%NnNOOIOOP'PqPQQPQQR1R|RSS_SSTBTTU(UuUVV\VVWDWWX/X}XYYiYZZVZZ[E[[\5\\]']x]^^l^__a_``W``aOaabIbbcCccd@dde=eef=ffg=ggh?hhiCiijHjjkOkklWlmm`mnnknooxop+ppq:qqrKrss]sttptu(uuv>vvwVwxxnxy*yyzFz{{c{|!||}A}~~b~#G k͂0WGrׇ;iΉ3dʋ0cʍ1fΏ6n֑?zM _ɖ4 uL$h՛BdҞ@iءG&vVǥ8nRĩ7u\ЭD-u`ֲK³8%yhYѹJº;.! zpg_XQKFAǿ=ȼ:ɹ8ʷ6˶5̵5͵6ζ7ϸ9к<Ѿ?DINU\dlvۀ܊ݖޢ)߯6DScs 2F[p(@Xr4Pm8Ww)Kmpat-0.15.1/osx/Pat-Welcome.rtfd/TXT.rtf000066400000000000000000000012531453425652100174520ustar00rootroot00000000000000{\rtf1\ansi\ansicpg1252\cocoartf1404\cocoasubrtf470 {\fonttbl\f0\fswiss\fcharset0 Helvetica;} {\colortbl;\red255\green255\blue255;} \margl1440\margr1440\vieww13140\viewh16500\viewkind1 \pard\tx720\tx1440\tx2160\tx2880\tx3600\tx4320\tx5040\tx5760\tx6480\tx7200\tx7920\tx8640\pardirnatural\qc\partightenfactor0 \f0\fs24 \cf0 {{\NeXTGraphic Pasted Graphic.tiff \width3620 \height5120 }}\pard\tx720\tx1440\tx2160\tx2880\tx3600\tx4320\tx5040\tx5760\tx6480\tx7200\tx7920\tx8640\pardirnatural\qc\partightenfactor0 \cf0 \ \pard\tx720\tx1440\tx2160\tx2880\tx3600\tx4320\tx5040\tx5760\tx6480\tx7200\tx7920\tx8640\pardirnatural\qc\partightenfactor0 \fs48 \cf0 Pat :: A Modern Winlink Client}pat-0.15.1/osx/pat.pkgproj000066400000000000000000000637061453425652100154000ustar00rootroot00000000000000 PACKAGES PACKAGE_FILES DEFAULT_INSTALL_LOCATION / HIERARCHY CHILDREN CHILDREN GID 80 PATH Applications PATH_TYPE 0 PERMISSIONS 509 TYPE 1 UID 0 CHILDREN CHILDREN GID 80 PATH Application Support PATH_TYPE 0 PERMISSIONS 493 TYPE 1 UID 0 CHILDREN GID 0 PATH Automator PATH_TYPE 0 PERMISSIONS 493 TYPE 1 UID 0 CHILDREN GID 0 PATH Documentation PATH_TYPE 0 PERMISSIONS 493 TYPE 1 UID 0 CHILDREN GID 0 PATH Extensions PATH_TYPE 0 PERMISSIONS 493 TYPE 1 UID 0 CHILDREN GID 0 PATH Filesystems PATH_TYPE 0 PERMISSIONS 493 TYPE 1 UID 0 CHILDREN GID 0 PATH Frameworks PATH_TYPE 0 PERMISSIONS 493 TYPE 1 UID 0 CHILDREN GID 0 PATH Input Methods PATH_TYPE 0 PERMISSIONS 493 TYPE 1 UID 0 CHILDREN GID 0 PATH Internet Plug-Ins PATH_TYPE 0 PERMISSIONS 493 TYPE 1 UID 0 CHILDREN GID 0 PATH LaunchAgents PATH_TYPE 0 PERMISSIONS 493 TYPE 1 UID 0 CHILDREN GID 0 PATH LaunchDaemons PATH_TYPE 0 PERMISSIONS 493 TYPE 1 UID 0 CHILDREN GID 0 PATH PreferencePanes PATH_TYPE 0 PERMISSIONS 493 TYPE 1 UID 0 CHILDREN GID 0 PATH Preferences PATH_TYPE 0 PERMISSIONS 493 TYPE 1 UID 0 CHILDREN GID 80 PATH Printers PATH_TYPE 0 PERMISSIONS 493 TYPE 1 UID 0 CHILDREN GID 0 PATH PrivilegedHelperTools PATH_TYPE 0 PERMISSIONS 493 TYPE 1 UID 0 CHILDREN GID 0 PATH QuickLook PATH_TYPE 0 PERMISSIONS 493 TYPE 1 UID 0 CHILDREN GID 0 PATH QuickTime PATH_TYPE 0 PERMISSIONS 493 TYPE 1 UID 0 CHILDREN GID 0 PATH Screen Savers PATH_TYPE 0 PERMISSIONS 493 TYPE 1 UID 0 CHILDREN GID 0 PATH Scripts PATH_TYPE 0 PERMISSIONS 493 TYPE 1 UID 0 CHILDREN GID 0 PATH Services PATH_TYPE 0 PERMISSIONS 493 TYPE 1 UID 0 CHILDREN GID 0 PATH Widgets PATH_TYPE 0 PERMISSIONS 493 TYPE 1 UID 0 GID 0 PATH Library PATH_TYPE 0 PERMISSIONS 493 TYPE 1 UID 0 CHILDREN CHILDREN GID 0 PATH Shared PATH_TYPE 0 PERMISSIONS 1023 TYPE 1 UID 0 GID 80 PATH Users PATH_TYPE 0 PERMISSIONS 493 TYPE 1 UID 0 CHILDREN CHILDREN CHILDREN CHILDREN GID 0 PATH ../pat PATH_TYPE 1 PERMISSIONS 493 TYPE 3 UID 0 GID 0 PATH bin PATH_TYPE 0 PERMISSIONS 493 TYPE 2 UID 0 CHILDREN CHILDREN CHILDREN CHILDREN GID 0 PATH ../man/pat.1 PATH_TYPE 1 PERMISSIONS 420 TYPE 3 UID 0 CHILDREN GID 0 PATH ../man/pat-configure.1 PATH_TYPE 1 PERMISSIONS 420 TYPE 3 UID 0 GID 0 PATH man1 PATH_TYPE 0 PERMISSIONS 493 TYPE 2 UID 0 GID 0 PATH man PATH_TYPE 0 PERMISSIONS 493 TYPE 2 UID 0 GID 0 PATH share PATH_TYPE 0 PERMISSIONS 493 TYPE 2 UID 0 GID 0 PATH local PATH_TYPE 0 PERMISSIONS 493 TYPE 2 UID 0 GID 0 PATH usr PATH_TYPE 0 PERMISSIONS 493 TYPE 2 UID 0 GID 0 PATH / PATH_TYPE 0 PERMISSIONS 493 TYPE 1 UID 0 PAYLOAD_TYPE 0 VERSION 4 PACKAGE_SCRIPTS RESOURCES PACKAGE_SETTINGS AUTHENTICATION 1 CONCLUSION_ACTION 0 IDENTIFIER io.getpat.pat NAME pat OVERWRITE_PERMISSIONS USE_HFS+_COMPRESSION VERSION 0.15.1 UUID 5562F199-B4C6-48BC-98FA-5BBA494CB61F PROJECT PROJECT_COMMENTS NOTES PCFET0NUWVBFIGh0bWwgUFVCTElDICItLy9XM0MvL0RURCBIVE1M IDQuMDEvL0VOIiAiaHR0cDovL3d3dy53My5vcmcvVFIvaHRtbDQv c3RyaWN0LmR0ZCI+CjxodG1sPgo8aGVhZD4KPG1ldGEgaHR0cC1l cXVpdj0iQ29udGVudC1UeXBlIiBjb250ZW50PSJ0ZXh0L2h0bWw7 IGNoYXJzZXQ9VVRGLTgiPgo8bWV0YSBodHRwLWVxdWl2PSJDb250 ZW50LVN0eWxlLVR5cGUiIGNvbnRlbnQ9InRleHQvY3NzIj4KPHRp dGxlPjwvdGl0bGU+CjxtZXRhIG5hbWU9IkdlbmVyYXRvciIgY29u dGVudD0iQ29jb2EgSFRNTCBXcml0ZXIiPgo8bWV0YSBuYW1lPSJD b2NvYVZlcnNpb24iIGNvbnRlbnQ9IjE0MDQuNDciPgo8c3R5bGUg dHlwZT0idGV4dC9jc3MiPgo8L3N0eWxlPgo8L2hlYWQ+Cjxib2R5 Pgo8L2JvZHk+CjwvaHRtbD4K PROJECT_PRESENTATION BACKGROUND ALIGNMENT 4 BACKGROUND_PATH CUSTOM SCALING 0 INSTALLATION TYPE HIERARCHIES INSTALLER LIST DESCRIPTION OPTIONS HIDDEN STATE 0 PACKAGE_UUID 5562F199-B4C6-48BC-98FA-5BBA494CB61F REQUIREMENTS TITLE TOOLTIP TYPE 0 UUID D361C8B7-AA56-4C1A-B30D-0D443ABB3DAD REMOVED INSTALLATION TYPE 0 MODE 1 INSTALLATION_STEPS ICPRESENTATION_CHAPTER_VIEW_CONTROLLER_CLASS ICPresentationViewIntroductionController INSTALLER_PLUGIN Introduction LIST_TITLE_KEY InstallerSectionTitle ICPRESENTATION_CHAPTER_VIEW_CONTROLLER_CLASS ICPresentationViewReadMeController INSTALLER_PLUGIN ReadMe LIST_TITLE_KEY InstallerSectionTitle ICPRESENTATION_CHAPTER_VIEW_CONTROLLER_CLASS ICPresentationViewLicenseController INSTALLER_PLUGIN License LIST_TITLE_KEY InstallerSectionTitle ICPRESENTATION_CHAPTER_VIEW_CONTROLLER_CLASS ICPresentationViewDestinationSelectController INSTALLER_PLUGIN TargetSelect LIST_TITLE_KEY InstallerSectionTitle ICPRESENTATION_CHAPTER_VIEW_CONTROLLER_CLASS ICPresentationViewInstallationTypeController INSTALLER_PLUGIN PackageSelection LIST_TITLE_KEY InstallerSectionTitle ICPRESENTATION_CHAPTER_VIEW_CONTROLLER_CLASS ICPresentationViewInstallationController INSTALLER_PLUGIN Install LIST_TITLE_KEY InstallerSectionTitle ICPRESENTATION_CHAPTER_VIEW_CONTROLLER_CLASS ICPresentationViewSummaryController INSTALLER_PLUGIN Summary LIST_TITLE_KEY InstallerSectionTitle INTRODUCTION LOCALIZATIONS LANGUAGE English VALUE PATH Pat-Welcome.rtfd PATH_TYPE 1 LICENSE KEYWORDS LOCALIZATIONS LANGUAGE English VALUE PATH Pat-License.txt PATH_TYPE 1 MODE 0 README LOCALIZATIONS LANGUAGE English VALUE PATH Pat-Info.rtfd PATH_TYPE 1 SUMMARY LOCALIZATIONS TITLE LOCALIZATIONS LANGUAGE English VALUE Pat PROJECT_REQUIREMENTS LIST POSTINSTALL_PATH PREINSTALL_PATH RESOURCES ROOT_VOLUME_ONLY PROJECT_SETTINGS ADVANCED_OPTIONS installer-script.options.platforms.client:arch installer-script.options:hostArchitectures BUILD_FORMAT 0 BUILD_PATH PATH .. PATH_TYPE 1 EXCLUDED_FILES PATTERNS_ARRAY REGULAR_EXPRESSION STRING .DS_Store TYPE 0 PROTECTED PROXY_NAME Remove .DS_Store files PROXY_TOOLTIP Remove ".DS_Store" files created by the Finder. STATE PATTERNS_ARRAY REGULAR_EXPRESSION STRING .pbdevelopment TYPE 0 PROTECTED PROXY_NAME Remove .pbdevelopment files PROXY_TOOLTIP Remove ".pbdevelopment" files created by ProjectBuilder or Xcode. STATE PATTERNS_ARRAY REGULAR_EXPRESSION STRING CVS TYPE 1 REGULAR_EXPRESSION STRING .cvsignore TYPE 0 REGULAR_EXPRESSION STRING .cvspass TYPE 0 REGULAR_EXPRESSION STRING .svn TYPE 1 REGULAR_EXPRESSION STRING .git TYPE 1 REGULAR_EXPRESSION STRING .gitignore TYPE 0 PROTECTED PROXY_NAME Remove SCM metadata PROXY_TOOLTIP Remove helper files and folders used by the CVS, SVN or Git Source Code Management systems. STATE PATTERNS_ARRAY REGULAR_EXPRESSION STRING classes.nib TYPE 0 REGULAR_EXPRESSION STRING designable.db TYPE 0 REGULAR_EXPRESSION STRING info.nib TYPE 0 PROTECTED PROXY_NAME Optimize nib files PROXY_TOOLTIP Remove "classes.nib", "info.nib" and "designable.nib" files within .nib bundles. STATE PATTERNS_ARRAY REGULAR_EXPRESSION STRING Resources Disabled TYPE 1 PROTECTED PROXY_NAME Remove Resources Disabled folders PROXY_TOOLTIP Remove "Resources Disabled" folders. STATE SEPARATOR NAME Pat :: A Modern Winlink Client TYPE 0 VERSION 2 pat-0.15.1/prompt_hub.go000066400000000000000000000042551453425652100151050ustar00rootroot00000000000000package main import ( "fmt" "os" "time" "github.com/howeyc/gopass" ) type Prompt struct { resp chan PromptResponse cancel chan struct{} ID string `json:"id"` Kind string `json:"kind"` Deadline time.Time `json:"deadline"` Message string `json:"message"` } type PromptResponse struct { ID string `json:"id"` Value string `json:"value"` Err error `json:"error"` } type PromptHub struct { c chan *Prompt rc chan PromptResponse omitTerminal bool } func NewPromptHub() *PromptHub { p := new(PromptHub); go p.loop(); return p } func (p *PromptHub) OmitTerminal(t bool) { p.omitTerminal = t } func (p *PromptHub) loop() { p.c = make(chan *Prompt) p.rc = make(chan PromptResponse) for prompt := range p.c { timeout := time.After(prompt.Deadline.Sub(time.Now())) select { case <-timeout: prompt.resp <- PromptResponse{ID: prompt.ID, Err: fmt.Errorf("deadline reached")} close(prompt.cancel) case resp := <-p.rc: if resp.ID != prompt.ID { continue } select { case prompt.resp <- resp: default: } close(prompt.cancel) } } } func (p *PromptHub) Respond(id, value string, err error) { select { case p.rc <- PromptResponse{ID: id, Value: value, Err: err}: default: } } func (p *PromptHub) Prompt(kind, message string) <-chan PromptResponse { prompt := &Prompt{ resp: make(chan PromptResponse), cancel: make(chan struct{}), // Closed on cancel (e.g. prompt response received) ID: fmt.Sprint(time.Now().UnixNano()), Kind: kind, Message: message, Deadline: time.Now().Add(time.Minute), } p.c <- prompt websocketHub.Prompt(*prompt) if !p.omitTerminal { go p.promptTerminal(*prompt) } return prompt.resp } func (p *PromptHub) promptTerminal(prompt Prompt) { switch prompt.Kind { case "password": q := make(chan struct{}, 1) go func() { select { case <-prompt.cancel: fmt.Printf(" Prompt Aborted - Press ENTER to continue...") case <-q: return } }() passwd, err := gopass.GetPasswdPrompt(prompt.Message+": ", true, os.Stdin, os.Stdout) q <- struct{}{} p.Respond(prompt.ID, string(passwd), err) default: panic(prompt.Kind + " prompt not implemented") } } pat-0.15.1/read.go000066400000000000000000000063271453425652100136430ustar00rootroot00000000000000// Copyright 2016 Martin Hebnes Pedersen (LA5NTA). All rights reserved. // Use of this source code is governed by the MIT-license that can be // found in the LICENSE file. package main import ( "bytes" "context" "fmt" "io" "log" "os" "path" "sort" "strconv" "strings" "github.com/bndr/gotabulate" "github.com/la5nta/wl2k-go/fbb" "github.com/la5nta/wl2k-go/mailbox" ) var mailboxes = []string{"in", "out", "sent", "archive"} func readMail(ctx context.Context) { w := os.Stdout for { // Query user for mailbox to list printMailboxes(w) fmt.Fprintf(w, "\nChoose mailbox [n]: ") mailboxIdx, ok := readInt(ctx) if !ok { break } else if mailboxIdx+1 > len(mailboxes) { fmt.Fprintln(w, "Invalid mailbox number") continue } for { // Fetch messages msgs, err := mailbox.LoadMessageDir(path.Join(mailbox.UserPath(fOptions.MailboxPath, fOptions.MyCall), mailboxes[mailboxIdx])) if err != nil { log.Fatal(err) } else if len(msgs) == 0 { fmt.Fprintf(w, "(empty)\n") break } // Print messages (sorted by date) sort.Sort(fbb.ByDate(msgs)) printMessages(w, msgs) // Query user for message to print fmt.Fprintf(w, "Choose message [n]: ") msgIdx, ok := readInt(ctx) if !ok { break } else if msgIdx+1 > len(msgs) { fmt.Fprintf(w, "invalid message number\n") continue } printMsg(w, msgs[msgIdx]) // Mark as read? if mailbox.IsUnread(msgs[msgIdx]) { fmt.Fprintf(w, "Mark as read? [Y/n]: ") ans := readLine() if ans == "" || strings.EqualFold(ans, "y") { mailbox.SetUnread(msgs[msgIdx], false) } } // Reply? fmt.Fprintf(w, "Reply (ctrl+c to quit) [y/N]: ") ans := readLine() if strings.EqualFold(ans, "y") { composeReplyMessage(msgs[msgIdx]) } } } } func readInt(ctx context.Context) (int, bool) { cs := make(chan string, 1) go func() { cs <- readLine() }() select { case <-ctx.Done(): return 0, false case str := <-cs: if str == "" { return 0, false } i, _ := strconv.Atoi(str) return i, true } } type PrettyAddrSlice []fbb.Address func (addrs PrettyAddrSlice) String() string { var buf bytes.Buffer for i, addr := range addrs { fmt.Fprintf(&buf, "%s", addr.Addr) if i < len(addrs)-1 { fmt.Fprintf(&buf, ", ") } } return buf.String() } func printMsg(w io.Writer, msg *fbb.Message) { fmt.Fprintf(w, "========================================\n") fmt.Fprintln(w, msg) fmt.Fprintf(w, "========================================\n\n") } func printMailboxes(w io.Writer) { for i, mbox := range mailboxes { fmt.Fprintf(w, "%d:%s\t", i, mbox) } } func printMessages(w io.Writer, msgs []*fbb.Message) { rows := make([][]string, len(msgs)) for i, msg := range msgs { var to string if len(msg.To()) > 0 { to = msg.To()[0].Addr } if len(msg.To()) > 1 { to += ", ..." } var flags string if mailbox.IsUnread(msg) { flags += "N" // New } rows[i] = []string{ fmt.Sprintf("%2d", i), flags, msg.Subject(), msg.From().Addr, msg.Date().String(), to, } } t := gotabulate.Create(rows) t.SetHeaders([]string{"i", "Flags", "Subject", "From", "Date", "To"}) t.SetAlign("left") t.SetWrapStrings(true) t.SetMaxCellSize(60) fmt.Fprintln(w, t.Render("simple")) } pat-0.15.1/riglist.go000066400000000000000000000016261453425652100144020ustar00rootroot00000000000000// Copyright 2016 Martin Hebnes Pedersen (LA5NTA). All rights reserved. // Use of this source code is governed by the MIT-license that can be // found in the LICENSE file. //go:build libhamlib // +build libhamlib package main import ( "context" "fmt" "strings" "github.com/la5nta/wl2k-go/rigcontrol/hamlib" ) func init() { cmd := Command{ Str: "riglist", Usage: "[search term]", Desc: "Print/search a list of rigcontrol supported transceivers.", HandleFunc: riglistHandle, } commands = append(commands[:8], append([]Command{cmd}, commands[8:]...)...) } func riglistHandle(ctx context.Context, args []string) { if args[0] == "" { fmt.Println("Missing argument") } term := strings.ToLower(args[0]) fmt.Print("id\ttransceiver\n") for m, str := range hamlib.Rigs() { if !strings.Contains(strings.ToLower(str), term) { continue } fmt.Printf("%d\t%s\n", m, str) } } pat-0.15.1/rmslist.go000066400000000000000000000145701453425652100144240ustar00rootroot00000000000000// Copyright 2016 Martin Hebnes Pedersen (LA5NTA). All rights reserved. // Use of this source code is governed by the MIT-license that can be // found in the LICENSE file. package main import ( "context" "encoding/json" "fmt" "log" "math" "net/url" "path/filepath" "sort" "strconv" "strings" "github.com/la5nta/pat/internal/cmsapi" "github.com/la5nta/pat/internal/debug" "github.com/la5nta/pat/internal/directories" "github.com/pd0mz/go-maidenhead" "github.com/spf13/pflag" ) type JSONURL struct{ url.URL } func (url JSONURL) MarshalJSON() ([]byte, error) { return json.Marshal(url.String()) } // JSONFloat64 is a float64 which serializes NaN and Inf(+-) as JSON value null type JSONFloat64 float64 func (f JSONFloat64) MarshalJSON() ([]byte, error) { if math.IsNaN(float64(f)) || math.IsInf(float64(f), 0) { return json.Marshal(nil) } return json.Marshal(float64(f)) } type RMS struct { Callsign string `json:"callsign"` Gridsquare string `json:"gridsquare"` Distance JSONFloat64 `json:"distance"` Azimuth JSONFloat64 `json:"azimuth"` Modes string `json:"modes"` Freq Frequency `json:"freq"` Dial Frequency `json:"dial"` URL *JSONURL `json:"url"` } func (r RMS) IsMode(mode string) bool { if mode == MethodVaraFM { return strings.HasPrefix(r.Modes, "VARA FM") } if mode == MethodVaraHF { return strings.HasPrefix(r.Modes, "VARA") && !strings.HasPrefix(r.Modes, "VARA FM") } return strings.Contains(strings.ToLower(r.Modes), mode) } func (r RMS) IsBand(band string) bool { return bands[band].Contains(r.Freq) } type byDist []RMS func (r byDist) Len() int { return len(r) } func (r byDist) Swap(i, j int) { r[i], r[j] = r[j], r[i] } func (r byDist) Less(i, j int) bool { return r[i].Distance < r[j].Distance } func rmsListHandle(ctx context.Context, args []string) { set := pflag.NewFlagSet("rmslist", pflag.ExitOnError) mode := set.StringP("mode", "m", "", "") band := set.StringP("band", "b", "", "") forceDownload := set.BoolP("force-download", "d", false, "") byDistance := set.BoolP("sort-distance", "s", false, "") set.Parse(args) var query string if len(set.Args()) > 0 { query = strings.ToUpper(set.Args()[0]) } *mode = strings.ToLower(*mode) rList, err := ReadRMSList(ctx, *forceDownload, func(rms RMS) bool { switch { case query != "" && !strings.HasPrefix(rms.Callsign, query): return false case mode != nil && !rms.IsMode(*mode): return false case band != nil && !rms.IsBand(*band): return false default: return true } }) if err != nil { log.Fatal(err) } if *byDistance { sort.Sort(byDist(rList)) } fmtStr := "%-9.9s [%-6.6s] %-6.6s %3.3s %-15.15s %14.14s %14.14s %s\n" // Print header fmt.Printf(fmtStr, "callsign", "gridsq", "dist", "Az", "mode(s)", "dial freq", "center freq", "url") // Print gateways (separated by blank line) for i := 0; i < len(rList); i++ { r := rList[i] distance := strconv.FormatFloat(float64(r.Distance), 'f', 0, 64) azimuth := strconv.FormatFloat(float64(r.Azimuth), 'f', 0, 64) fmt.Printf(fmtStr, r.Callsign, r.Gridsquare, distance, azimuth, r.Modes, r.Dial, r.Freq, r.URL) if i+1 < len(rList) && rList[i].Callsign != rList[i+1].Callsign { fmt.Println("") } } } func ReadRMSList(ctx context.Context, forceDownload bool, filterFn func(rms RMS) (keep bool)) ([]RMS, error) { me, err := maidenhead.ParseLocator(config.Locator) if err != nil { log.Print("Missing or Invalid Locator, will not compute distance and Azimuth") } fileName := "rmslist" isDefaultServiceCode := len(config.ServiceCodes) == 1 && config.ServiceCodes[0] == "PUBLIC" if !isDefaultServiceCode { fileName += "-" + strings.Join(config.ServiceCodes, "-") } filePath := filepath.Join(directories.DataDir(), fileName+".json") debug.Printf("RMS list file is %s", filePath) f, err := cmsapi.GetGatewayStatusCached(ctx, filePath, forceDownload, config.ServiceCodes...) if err != nil { return nil, err } defer f.Close() var status cmsapi.GatewayStatus if err = json.NewDecoder(f).Decode(&status); err != nil { return nil, err } var slice = []RMS{} for _, gw := range status.Gateways { for _, channel := range gw.Channels { r := RMS{ Callsign: gw.Callsign, Gridsquare: channel.Gridsquare, Modes: channel.SupportedModes, Freq: Frequency(channel.Frequency), Dial: Frequency(channel.Frequency).Dial(channel.SupportedModes), } if chURL := toURL(channel, gw.Callsign); chURL != nil { r.URL = &JSONURL{*chURL} } hasLocator := me != maidenhead.Point{} if them, err := maidenhead.ParseLocator(channel.Gridsquare); err == nil && hasLocator { r.Distance = JSONFloat64(me.Distance(them)) r.Azimuth = JSONFloat64(me.Bearing(them)) } if keep := filterFn(r); !keep { continue } slice = append(slice, r) } } return slice, nil } func toURL(gc cmsapi.GatewayChannel, targetCall string) *url.URL { freq := Frequency(gc.Frequency).Dial(gc.SupportedModes) chURL, _ := url.Parse(fmt.Sprintf("%s:///%s?freq=%v", toTransport(gc), targetCall, freq.KHz())) addBandwidth(gc, chURL) return chURL } func addBandwidth(gc cmsapi.GatewayChannel, chURL *url.URL) { bw := "" modeF := strings.Fields(gc.SupportedModes) if modeF[0] == "ARDOP" { if len(modeF) > 1 { bw = modeF[1] + "MAX" } } else if modeF[0] == "VARA" { if len(modeF) > 1 && modeF[1] == "FM" { // VARA FM should not set bandwidth in connect URL or sent over the command port, // it's set in the VARA Setup dialog bw = "" } else { // VARA HF may be 500, 2750, or none which is implicitly 2300 if len(modeF) > 1 { if len(modeF) > 1 { bw = modeF[1] } } else { bw = "2300" } } } if bw != "" { v := chURL.Query() v.Set("bw", bw) chURL.RawQuery = v.Encode() } } var transports = []string{MethodAX25, MethodPactor, MethodArdop, MethodVaraFM, MethodVaraHF} func toTransport(gc cmsapi.GatewayChannel) string { modes := strings.ToLower(gc.SupportedModes) for _, transport := range transports { if strings.Contains(modes, "packet") { // bug(maritnhpedersen): We really don't know which transport to use here. It could be serial-tnc or ax25, but ax25 is most likely. return MethodAX25 } if strings.HasPrefix(modes, "vara fm") { return MethodVaraFM } if strings.HasPrefix(modes, "vara") { return MethodVaraHF } if strings.Contains(modes, transport) { return transport } } return "" } pat-0.15.1/rmslist_test.go000066400000000000000000000076121453425652100154620ustar00rootroot00000000000000package main import ( "github.com/la5nta/pat/internal/cmsapi" "net/url" "reflect" "testing" ) func Test_toURL(t *testing.T) { type args struct { channel cmsapi.GatewayChannel targetCall string } tests := []struct { name string args args want *url.URL }{ { name: "ax25 1200", args: args{ channel: cmsapi.GatewayChannel{ Frequency: 145050000, SupportedModes: "Packet 1200", }, targetCall: "K0NTS-10", }, want: parseUrl("ax25:///K0NTS-10?freq=145050"), }, { name: "ax25 9600", args: args{ channel: cmsapi.GatewayChannel{ Frequency: 438075000, SupportedModes: "Packet 9600", }, targetCall: "HB9AK-14", }, want: parseUrl("ax25:///HB9AK-14?freq=438075"), }, { name: "adrop 2000", args: args{ channel: cmsapi.GatewayChannel{ Frequency: 3586500, SupportedModes: "ARDOP 2000", }, targetCall: "K0SI", }, want: parseUrl("ardop:///K0SI?bw=2000MAX&freq=3585"), }, { name: "adrop 500", args: args{ channel: cmsapi.GatewayChannel{ Frequency: 3584000, SupportedModes: "ARDOP 500", }, targetCall: "F1ZWL", }, want: parseUrl("ardop:///F1ZWL?bw=500MAX&freq=3582.5"), }, { // These are quite rare but are seen in the wild name: "adrop 1000", args: args{ channel: cmsapi.GatewayChannel{ Frequency: 3588000, SupportedModes: "ARDOP 1000", }, targetCall: "N3HYM-10", }, want: parseUrl("ardop:///N3HYM-10?bw=1000MAX&freq=3586.5"), }, { // This is a notional ARDOP station that doesn't specify bandwidth in supportedModes. // None appear today in the RMS list, but maybe they could. name: "adrop unspec", args: args{ channel: cmsapi.GatewayChannel{ Frequency: 7584000, SupportedModes: "ARDOP", }, targetCall: "T3ST", }, want: parseUrl("ardop:///T3ST?freq=7582.5"), }, { name: "pactor", args: args{ channel: cmsapi.GatewayChannel{ Frequency: 1850000, SupportedModes: "Pactor 1,2", }, targetCall: "K1EHZ", }, want: parseUrl("pactor:///K1EHZ?freq=1848.5"), }, { name: "robust packet", args: args{ channel: cmsapi.GatewayChannel{ Frequency: 7099400, SupportedModes: "Robust Packet", }, targetCall: "N3HYM-10", }, want: parseUrl("ax25:///N3HYM-10?freq=7099.4"), }, { name: "vara hf 500", args: args{ channel: cmsapi.GatewayChannel{ Frequency: 7064000, SupportedModes: "VARA 500", }, targetCall: "W0VG", }, want: parseUrl("varahf:///W0VG?bw=500&freq=7062.5"), }, { name: "vara hf unspec", args: args{ channel: cmsapi.GatewayChannel{ Frequency: 7103000, SupportedModes: "VARA", }, targetCall: "W0VG", }, want: parseUrl("varahf:///W0VG?bw=2300&freq=7101.5"), }, { name: "vara hf 2750", args: args{ channel: cmsapi.GatewayChannel{ Frequency: 3597900, SupportedModes: "VARA 2750", }, targetCall: "W1EO", }, want: parseUrl("varahf:///W1EO?bw=2750&freq=3596.4"), }, { name: "vara fm narrow", args: args{ channel: cmsapi.GatewayChannel{ Frequency: 145070000, SupportedModes: "VARA FM", }, targetCall: "W0TQ", }, // vara transport adapter will default to narrow want: parseUrl("varafm:///W0TQ?freq=145070"), }, { name: "vara fm wide", args: args{ channel: cmsapi.GatewayChannel{ Frequency: 145510000, SupportedModes: "VARA FM WIDE", }, targetCall: "W1AW-10", }, want: parseUrl("varafm:///W1AW-10?freq=145510"), }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { if got := toURL(tt.args.channel, tt.args.targetCall); !reflect.DeepEqual(got, tt.want) { t.Errorf("toURL() = %v, want %v", got, tt.want) } }) } } func parseUrl(str string) *url.URL { parse, _ := url.Parse(str) return parse } pat-0.15.1/schedule.go000066400000000000000000000014651453425652100145220ustar00rootroot00000000000000// Copyright 2016 Martin Hebnes Pedersen (LA5NTA). All rights reserved. // Use of this source code is governed by the MIT-license that can be // found in the LICENSE file. package main import ( "log" "time" "github.com/gorhill/cronexpr" ) type Job struct { expr *cronexpr.Expression cmd string next time.Time } func scheduleLoop() { jobs := make([]*Job, 0, len(config.Schedule)) for exprStr, cmd := range config.Schedule { expr := cronexpr.MustParse(exprStr) jobs = append(jobs, &Job{ expr, cmd, expr.Next(time.Now()), }) } go func() { for range time.Tick(time.Second) { for _, j := range jobs { if time.Now().Before(j.next) { continue } log.Printf("Executing scheduled command '%s'...", j.cmd) execCmd(j.cmd) j.next = j.expr.Next(time.Now()) } } }() } pat-0.15.1/share/000077500000000000000000000000001453425652100134735ustar00rootroot00000000000000pat-0.15.1/share/ardopc/000077500000000000000000000000001453425652100147435ustar00rootroot00000000000000pat-0.15.1/share/ardopc/ardop@.service000066400000000000000000000003311453425652100175270ustar00rootroot00000000000000[Unit] Description=ardopc - ARDOP softmodem for %i After=network.target sound.target [Service] User=%i ExecStart=/bin/sh -c "cd /tmp && /usr/local/bin/ardopc" Restart=on-failure [Install] WantedBy=multi-user.target pat-0.15.1/share/ardopc/install-systemd-ardop-unit.bash000077500000000000000000000005621453425652100230240ustar00rootroot00000000000000#!/usr/bin/env bash set -e DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" [ -e "/etc/systemd/system/ardop@.service" ] && rm /etc/systemd/system/ardop@.service cp "$DIR/ardop@.service" "/lib/systemd/system/ardop@.service" systemctl daemon-reload echo "Installed. Install (pi)ardopc as /usr/local/bin/ardopc and start it with 'systemctl start ardop@username'" pat-0.15.1/share/ax25/000077500000000000000000000000001453425652100142525ustar00rootroot00000000000000pat-0.15.1/share/ax25/README.systemd000066400000000000000000000003041453425652100166160ustar00rootroot00000000000000For AX.25 as a systemd service: * Edit and copy ax25.service into /etc/systemd/system/ax25.service. * sudo systemctl enable ax25 * sudo systemctl start ax25 * systemctl status ax25 (optional) pat-0.15.1/share/ax25/ax25.default000066400000000000000000000013631453425652100164020ustar00rootroot00000000000000# Configuration for AX.25 systemd unit file from Pat. # The axport from /etc/ax25/axports to bring up. AXPORT=wl2k # The AX.25 baudrate the TNC is configured for. # Make sure this matches the HBAUD setting of your TNC. HBAUD=1200 # The TNC serial path. DEV=/dev/ttyUSB0 # Script for initializing the TNC. # # This optional parameter is convenient when dealing with TNCs that require # additional initialization, e.g. entering KISS mode. Modify to fit your own needs. # #TNC_INIT_CMD="/usr/local/bin/my_tnc_init_script --serial-tty $DEV --hbaud $HBAUD" # # Example (for Kenwood TH-D7x and TM-D7x0): # Download, modify, compile and install https://github.com/fmarier/tmd710_tncsetup #TNC_INIT_CMD="/usr/local/bin/tmd710_tncsetup -B 1 -S $DEV -b $HBAUD" pat-0.15.1/share/ax25/ax25.service000066400000000000000000000003261453425652100164140ustar00rootroot00000000000000[Unit] Description=AX.25 KISS interface After=network.target [Service] EnvironmentFile=/etc/default/ax25 Type=forking ExecStart=/usr/share/pat/bin/axup ${DEV} ${AXPORT} ${HBAUD} [Install] WantedBy=default.target pat-0.15.1/share/ax25/install-systemd-ax25-unit.bash000077500000000000000000000006211453425652100220010ustar00rootroot00000000000000#!/usr/bin/env bash set -e DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" [ -e "/etc/systemd/system/ax25.service" ] && rm /etc/systemd/system/ax25.service cp "$DIR/ax25.service" "/lib/systemd/system/ax25.service" [ -e "/etc/default/ax25" ] || cp "$DIR/ax25.default" "/etc/default/ax25" systemctl daemon-reload echo "Installed. Edit /etc/default/ax25 and start with 'systemctl start ax25'" pat-0.15.1/share/ax25/mheardd.service000066400000000000000000000002431453425652100172370ustar00rootroot00000000000000[Unit] Description=AX.25 mheard daemon After=ax25.service [Service] Type=forking ExecStart=/bin/sh -c "/usr/sbin/mheardd -l &" [Install] WantedBy=default.target pat-0.15.1/share/ax25/soundmodem-example.conf000066400000000000000000000011551453425652100207260ustar00rootroot00000000000000 pat-0.15.1/share/bin/000077500000000000000000000000001453425652100142435ustar00rootroot00000000000000pat-0.15.1/share/bin/axup000077500000000000000000000033321453425652100151470ustar00rootroot00000000000000#!/bin/sh # # This script is written to help configure an axport to use as a winlink node. # It sets AX.25 and KISS params appropriate to the given HBAUD (link baudrate) set -e if [ -z "$1" ] || [ -z "$2" ] || [ -z "$3" ] then echo "Usage: $0 [tty] [axport] [1200/9600]" exit 1; fi # Init TNC (optional) [ -z "$TNC_INIT_CMD" ] || eval "$TNC_INIT_CMD"; if [ "$3" -eq "9600" ]; then /usr/sbin/kissattach $1 $2 -m 256 /usr/sbin/kissparms -p $2 -t 100 -l 10 -s 12 -r 80 -f n echo 4 > /proc/sys/net/ax25/ax0/standard_window_size # 2-7 (max frames) echo 256 > /proc/sys/net/ax25/ax0/maximum_packet_length # 1-512 (paclen) echo 3100 > /proc/sys/net/ax25/ax0/t1_timeout # (Frack /1000 = seconds) echo 800 > /proc/sys/net/ax25/ax0/t2_timeout # (RESPtime /1000 = seconds) echo 300000 > /proc/sys/net/ax25/ax0/t3_timeout # (Check /1000 = seconds) echo 100000 > /proc/sys/net/ax25/ax0/idle_timeout # (/10000(?) = seconds) echo 5 > /proc/sys/net/ax25/ax0/maximum_retry_count # n echo 2 > /proc/sys/net/ax25/ax0/connect_mode # 0 = None, 1 = Network, 2 = All elif [ "$3" -eq "1200" ]; then /usr/sbin/kissattach $1 $2 -m 128 /usr/sbin/kissparms -p $2 -t 300 -l 10 -s 12 -r 80 -f n echo 4 > /proc/sys/net/ax25/ax0/standard_window_size echo 128 > /proc/sys/net/ax25/ax0/maximum_packet_length echo 2000 > /proc/sys/net/ax25/ax0/t1_timeout echo 1000 > /proc/sys/net/ax25/ax0/t2_timeout echo 300000 > /proc/sys/net/ax25/ax0/t3_timeout echo 100000 > /proc/sys/net/ax25/ax0/idle_timeout echo 5 > /proc/sys/net/ax25/ax0/maximum_retry_count echo 2 > /proc/sys/net/ax25/ax0/connect_mode else echo "Invalid HBAUD $3" return 1; fi pat-0.15.1/share/rigctld/000077500000000000000000000000001453425652100151235ustar00rootroot00000000000000pat-0.15.1/share/rigctld/install-systemd-rigctld-unit.bash000077500000000000000000000006541453425652100235310ustar00rootroot00000000000000#!/usr/bin/env bash set -e DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" [ -e "/etc/systemd/system/rigctld.service" ] && rm /etc/systemd/system/rigctld.service cp "$DIR/rigctld.service" "/lib/systemd/system/rigctld.service" [ -e "/etc/default/rigctld" ] || cp "$DIR/rigctld.default" "/etc/default/rigctld" systemctl daemon-reload echo "Installed. Edit /etc/default/rigctld and start with 'systemctl start rigctld'" pat-0.15.1/share/rigctld/rigctld.default000066400000000000000000000004321453425652100201200ustar00rootroot00000000000000# Configuration for rigctld systemd unit file from Pat. # Radio model number (-m) # (run rigctl -l to find yours) MODEL=1 # Path to your rig's tty device (-r) DEV=/dev/ttyUSB0 # Additional parameters passed to rigctld # (this example sets serial baud 9600) #EXTRA_OPTS="-s 9600" pat-0.15.1/share/rigctld/rigctld.service000066400000000000000000000003451453425652100201370ustar00rootroot00000000000000[Unit] Description=Rigcontrol with rigctld After=network.target [Service] EnvironmentFile=/etc/default/rigctld ExecStart=/usr/bin/rigctld -m ${MODEL} -r ${DEV} ${EXTRA_OPTS} Restart=on-failure [Install] WantedBy=default.target pat-0.15.1/usage.go000066400000000000000000000047061453425652100140330ustar00rootroot00000000000000// Copyright 2016 Martin Hebnes Pedersen (LA5NTA). All rights reserved. // Use of this source code is governed by the MIT-license that can be // found in the LICENSE file. package main var ( UsageConnect = `'alias' or 'transport://[host][/digi]/targetcall[?params...]' transport: telnet: TCP/IP ardop: ARDOP TNC pactor: SCS PTC modems varahf: VARA HF TNC varafm: VARA FM TNC ax25: AX.25 (Default - uses engine specified in config) ax25+agwpe: AX.25 (AGWPE/Direwolf) ax25+linux: AX.25 (Linux kernel) ax25+serial-tnc: AX.25 (Serial TNC) host: Used to address the host interface (TNC/modem), _not_ to be confused with the connection PATH. Format: [user[:pass]@]host[:port] telnet: [user:pass]@host:port ax25+linux: (optional) host=axport pactor: (optional) serial device (e.g. COM1 or /dev/ttyUSB0) path: The last element of the path is the target station's callsign. If the path has multiple hops (e.g. AX.25), they are separated by '/'. params: ?freq= Sets QSY frequency (ardop and ax25 only) ?host= Overrides the host part of the path. Useful for serial-tnc to specify e.g. /dev/ttyS0. ` ExampleConnect = ` connect telnet (alias) Connect to one of the Winlink Common Message Servers via tcp. connect ax25:///LA1B-10 Connect to the RMS Gateway LA1B-10 using AX.25 engine as per configuration. connect ax25+linux://tmd710/LA1B-10 Connect to LA1B-10 using Linux kernel's AX.25 stack on axport 'tmd710'. connect ax25:///LA1B/LA5NTA Peer-to-peer connection with LA5NTA via LA1B digipeater. connect ardop:///LA3F Connect to the RMS HF Gateway LA3F using ARDOP on the default tcp address and port. connect ardop:///LA3F?freq=5350 Same as above, but set dial frequency of the radio using rigcontrol. connect pactor:///LA3F Connect to RMS HF Gateway LA3F using PACTOR. connect varahf:///LA1B Connect to RMS HF Gateway LA1B using VARA HF TNC. connect varafm:///LA5NTA Connect to LA5NTA using VARA FM TNC. ` ) var ExamplePosition = ` position -c "QRV 145.500MHz" Send position and comment with coordinates retrieved from GPSd. position --latlon 59.123,005.123 Send position 59.123N 005.123E. position --latlon 40.704,-73.945 Send position 40.704N 073.945W. position --latlon -10.123,-60.123 Send position 10.123S 060.123W. ` pat-0.15.1/utils.go000066400000000000000000000004451453425652100140630ustar00rootroot00000000000000// Copyright 2016 Martin Hebnes Pedersen (LA5NTA). All rights reserved. // Use of this source code is governed by the MIT-license that can be // found in the LICENSE file. package main import ( "unicode" ) func SplitFunc(c rune) bool { return unicode.IsSpace(c) || c == ',' || c == ';' } pat-0.15.1/version_report.go000066400000000000000000000042611453425652100160030ustar00rootroot00000000000000// Copyright 2016 Martin Hebnes Pedersen (LA5NTA). All rights reserved. // Use of this source code is governed by the MIT-license that can be // found in the LICENSE file. package main import ( "encoding/json" "fmt" "os" "path/filepath" "runtime" "time" "github.com/la5nta/pat/internal/buildinfo" "github.com/la5nta/pat/internal/cmsapi" "github.com/la5nta/pat/internal/directories" ) func accountExistsCached(callsign string) (bool, error) { var cache struct { Expires time.Time AccountExists bool } fileName := fmt.Sprintf(".cached_account_check_%s.json", callsign) filePath := filepath.Join(directories.StateDir(), fileName) f, err := os.OpenFile(filePath, os.O_RDWR|os.O_CREATE, 0o600) if err != nil { return false, err } json.NewDecoder(f).Decode(&cache) if time.Since(cache.Expires) < 0 { return cache.AccountExists, nil } defer func() { f.Truncate(0) f.Seek(0, 0) json.NewEncoder(f).Encode(cache) }() exists, err := cmsapi.AccountExists(callsign) if !exists || err != nil { // Let's try again in 48 hours cache.Expires = time.Now().Add(48 * time.Hour) return false, err } // Keep this response for a month. It will probably not change. cache.Expires = time.Now().Add(30 * 24 * time.Hour) cache.AccountExists = exists return exists, err } func postVersionUpdate() error { var lastUpdated time.Time filePath := filepath.Join(directories.StateDir(), "last_version_report.json") file, err := os.OpenFile(filePath, os.O_RDWR|os.O_CREATE, 0o600) if err != nil { return err } defer file.Close() json.NewDecoder(file).Decode(&lastUpdated) if time.Since(lastUpdated) < 24*time.Hour { return nil } // WDT do not want us to post version reports for callsigns without a registered account if exists, err := accountExistsCached(fOptions.MyCall); err != nil { return err } else if !exists { return nil } v := cmsapi.VersionAdd{ Callsign: fOptions.MyCall, Program: buildinfo.AppName, Version: buildinfo.Version, Comments: fmt.Sprintf("%s - %s/%s", buildinfo.GitRev, runtime.GOOS, runtime.GOARCH), } if err := v.Post(); err != nil { return err } file.Truncate(0) file.Seek(0, 0) return json.NewEncoder(file).Encode(time.Now()) } pat-0.15.1/web/000077500000000000000000000000001453425652100131465ustar00rootroot00000000000000pat-0.15.1/web/.gitignore000066400000000000000000000000171453425652100151340ustar00rootroot00000000000000/node_modules/ pat-0.15.1/web/.nvmrc000066400000000000000000000000141453425652100142670ustar00rootroot00000000000000lts/hydrogenpat-0.15.1/web/.prettierignore000066400000000000000000000000221453425652100162030ustar00rootroot00000000000000dist node_modules pat-0.15.1/web/.prettierrc000066400000000000000000000001521453425652100153300ustar00rootroot00000000000000{ "printWidth": 100, "semi": true, "singleQuote": true, "tabWidth": 2, "trailingComma": "es5" } pat-0.15.1/web/dist/000077500000000000000000000000001453425652100141115ustar00rootroot00000000000000pat-0.15.1/web/dist/css/000077500000000000000000000000001453425652100147015ustar00rootroot00000000000000pat-0.15.1/web/dist/css/style.css000066400000000000000000005532731453425652100165720ustar00rootroot00000000000000/*! * Bootstrap v3.2.0 (http://getbootstrap.com) * Copyright 2011-2014 Twitter, Inc. * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) *//*! normalize.css v3.0.1 | MIT License | git.io/normalize */html{font-family:sans-serif;-webkit-text-size-adjust:100%;-ms-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{margin:.67em 0;font-size:2em}mark{color:#000;background:#ff0}small{font-size:80%}sub,sup{position:relative;font-size:75%;line-height:0;vertical-align:baseline}sup{top:-.5em}sub{bottom:-.25em}img{border:0}svg:not(:root){overflow:hidden}figure{margin:1em 40px}hr{height:0;-webkit-box-sizing:content-box;-moz-box-sizing:content-box;box-sizing:content-box}pre{overflow:auto}code,kbd,pre,samp{font-family:monospace,monospace;font-size:1em}button,input,optgroup,select,textarea{margin:0;font:inherit;color:inherit}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{padding:0;border:0}input{line-height:normal}input[type=checkbox],input[type=radio]{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}input[type=number]::-webkit-inner-spin-button,input[type=number]::-webkit-outer-spin-button{height:auto}input[type=search]{-webkit-box-sizing:content-box;-moz-box-sizing:content-box;box-sizing:content-box;-webkit-appearance:textfield}input[type=search]::-webkit-search-cancel-button,input[type=search]::-webkit-search-decoration{-webkit-appearance:none}fieldset{padding:.35em .625em .75em;margin:0 2px;border:1px solid silver}textarea{overflow:auto}optgroup{font-weight:700}table{border-spacing:0;border-collapse:collapse}td,th{padding:0}@media print{*{color:#000!important;text-shadow:none!important;background:transparent!important;-webkit-box-shadow:none!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^="#"]:after,a[href^="javascript:"]:after{content:""}blockquote,pre{border:1px solid #999;page-break-inside:avoid}thead{display:table-header-group}img,tr{page-break-inside:avoid}img{max-width:100%!important}h2,h3,p{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 td,.table-bordered th{border:1px solid #ddd!important}}@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("\"data:image/svg+xml,%3C?xml version='1.0' standalone='no'?%3E %3C!DOCTYPE svg PUBLIC '-/W3C/DTD SVG 1.1/EN' 'http:/www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd' %3E %3Csvg xmlns='http:/www.w3.org/2000/svg'%3E %3Cmetadata%3E%3C/metadata%3E %3Cdefs%3E %3Cfont id='glyphicons_halflingsregular' horiz-adv-x='1200' %3E %3Cfont-face units-per-em='1200' ascent='960' descent='-240' /%3E %3Cmissing-glyph horiz-adv-x='500' /%3E %3Cglyph /%3E %3Cglyph /%3E %3Cglyph unicode='&%23xd;' /%3E %3Cglyph unicode=' ' /%3E %3Cglyph unicode='*' d='M100 500v200h259l-183 183l141 141l183 -183v259h200v-259l183 183l141 -141l-183 -183h259v-200h-259l183 -183l-141 -141l-183 183v-259h-200v259l-183 -183l-141 141l183 183h-259z' /%3E %3Cglyph unicode='+' d='M0 400v300h400v400h300v-400h400v-300h-400v-400h-300v400h-400z' /%3E %3Cglyph unicode='&%23xa0;' /%3E %3Cglyph unicode='&%23x2000;' horiz-adv-x='652' /%3E %3Cglyph unicode='&%23x2001;' horiz-adv-x='1304' /%3E %3Cglyph unicode='&%23x2002;' horiz-adv-x='652' /%3E %3Cglyph unicode='&%23x2003;' horiz-adv-x='1304' /%3E %3Cglyph unicode='&%23x2004;' horiz-adv-x='434' /%3E %3Cglyph unicode='&%23x2005;' horiz-adv-x='326' /%3E %3Cglyph unicode='&%23x2006;' horiz-adv-x='217' /%3E %3Cglyph unicode='&%23x2007;' horiz-adv-x='217' /%3E %3Cglyph unicode='&%23x2008;' horiz-adv-x='163' /%3E %3Cglyph unicode='&%23x2009;' horiz-adv-x='260' /%3E %3Cglyph unicode='&%23x200a;' horiz-adv-x='72' /%3E %3Cglyph unicode='&%23x202f;' horiz-adv-x='260' /%3E %3Cglyph unicode='&%23x205f;' horiz-adv-x='326' /%3E %3Cglyph unicode='&%23x20ac;' d='M100 500l100 100h113q0 47 5 100h-218l100 100h135q37 167 112 257q117 141 297 141q242 0 354 -189q60 -103 66 -209h-181q0 55 -25.5 99t-63.5 68t-75 36.5t-67 12.5q-24 0 -52.5 -10t-62.5 -32t-65.5 -67t-50.5 -107h379l-100 -100h-300q-6 -46 -6 -100h406l-100 -100 h-300q9 -74 33 -132t52.5 -91t62 -54.5t59 -29t46.5 -7.5q29 0 66 13t75 37t63.5 67.5t25.5 96.5h174q-31 -172 -128 -278q-107 -117 -274 -117q-205 0 -324 158q-36 46 -69 131.5t-45 205.5h-217z' /%3E %3Cglyph unicode='&%23x2212;' d='M200 400h900v300h-900v-300z' /%3E %3Cglyph unicode='&%23x25fc;' horiz-adv-x='500' d='M0 0z' /%3E %3Cglyph unicode='&%23x2601;' d='M-14 494q0 -80 56.5 -137t135.5 -57h750q120 0 205 86.5t85 207.5t-85 207t-205 86q-46 0 -90 -14q-44 97 -134.5 156.5t-200.5 59.5q-152 0 -260 -107.5t-108 -260.5q0 -25 2 -37q-66 -14 -108.5 -67.5t-42.5 -122.5z' /%3E %3Cglyph unicode='&%23x2709;' d='M0 100l400 400l200 -200l200 200l400 -400h-1200zM0 300v600l300 -300zM0 1100l600 -603l600 603h-1200zM900 600l300 300v-600z' /%3E %3Cglyph unicode='&%23x270f;' d='M-13 -13l333 112l-223 223zM187 403l214 -214l614 614l-214 214zM887 1103l214 -214l99 92q13 13 13 32.5t-13 33.5l-153 153q-15 13 -33 13t-33 -13z' /%3E %3Cglyph unicode='&%23xe001;' d='M0 1200h1200l-500 -550v-550h300v-100h-800v100h300v550z' /%3E %3Cglyph unicode='&%23xe002;' d='M14 84q18 -55 86 -75.5t147 5.5q65 21 109 69t44 90v606l600 155v-521q-64 16 -138 -7q-79 -26 -122.5 -83t-25.5 -111q18 -55 86 -75.5t147 4.5q70 23 111.5 63.5t41.5 95.5v881q0 10 -7 15.5t-17 2.5l-752 -193q-10 -3 -17 -12.5t-7 -19.5v-689q-64 17 -138 -7 q-79 -25 -122.5 -82t-25.5 -112z' /%3E %3Cglyph unicode='&%23xe003;' d='M23 693q0 200 142 342t342 142t342 -142t142 -342q0 -142 -78 -261l300 -300q7 -8 7 -18t-7 -18l-109 -109q-8 -7 -18 -7t-18 7l-300 300q-119 -78 -261 -78q-200 0 -342 142t-142 342zM176 693q0 -136 97 -233t234 -97t233.5 96.5t96.5 233.5t-96.5 233.5t-233.5 96.5 t-234 -97t-97 -233z' /%3E %3Cglyph unicode='&%23xe005;' d='M100 784q0 64 28 123t73 100.5t104.5 64t119 20.5t120 -38.5t104.5 -104.5q48 69 109.5 105t121.5 38t118.5 -20.5t102.5 -64t71 -100.5t27 -123q0 -57 -33.5 -117.5t-94 -124.5t-126.5 -127.5t-150 -152.5t-146 -174q-62 85 -145.5 174t-149.5 152.5t-126.5 127.5 t-94 124.5t-33.5 117.5z' /%3E %3Cglyph unicode='&%23xe006;' d='M-72 800h479l146 400h2l146 -400h472l-382 -278l145 -449l-384 275l-382 -275l146 447zM168 71l2 1z' /%3E %3Cglyph unicode='&%23xe007;' d='M-72 800h479l146 400h2l146 -400h472l-382 -278l145 -449l-384 275l-382 -275l146 447zM168 71l2 1zM237 700l196 -142l-73 -226l192 140l195 -141l-74 229l193 140h-235l-77 211l-78 -211h-239z' /%3E %3Cglyph unicode='&%23xe008;' d='M0 0v143l400 257v100q-37 0 -68.5 74.5t-31.5 125.5v200q0 124 88 212t212 88t212 -88t88 -212v-200q0 -51 -31.5 -125.5t-68.5 -74.5v-100l400 -257v-143h-1200z' /%3E %3Cglyph unicode='&%23xe009;' d='M0 0v1100h1200v-1100h-1200zM100 100h100v100h-100v-100zM100 300h100v100h-100v-100zM100 500h100v100h-100v-100zM100 700h100v100h-100v-100zM100 900h100v100h-100v-100zM300 100h600v400h-600v-400zM300 600h600v400h-600v-400zM1000 100h100v100h-100v-100z M1000 300h100v100h-100v-100zM1000 500h100v100h-100v-100zM1000 700h100v100h-100v-100zM1000 900h100v100h-100v-100z' /%3E %3Cglyph unicode='&%23xe010;' d='M0 50v400q0 21 14.5 35.5t35.5 14.5h400q21 0 35.5 -14.5t14.5 -35.5v-400q0 -21 -14.5 -35.5t-35.5 -14.5h-400q-21 0 -35.5 14.5t-14.5 35.5zM0 650v400q0 21 14.5 35.5t35.5 14.5h400q21 0 35.5 -14.5t14.5 -35.5v-400q0 -21 -14.5 -35.5t-35.5 -14.5h-400 q-21 0 -35.5 14.5t-14.5 35.5zM600 50v400q0 21 14.5 35.5t35.5 14.5h400q21 0 35.5 -14.5t14.5 -35.5v-400q0 -21 -14.5 -35.5t-35.5 -14.5h-400q-21 0 -35.5 14.5t-14.5 35.5zM600 650v400q0 21 14.5 35.5t35.5 14.5h400q21 0 35.5 -14.5t14.5 -35.5v-400 q0 -21 -14.5 -35.5t-35.5 -14.5h-400q-21 0 -35.5 14.5t-14.5 35.5z' /%3E %3Cglyph unicode='&%23xe011;' d='M0 50v200q0 21 14.5 35.5t35.5 14.5h200q21 0 35.5 -14.5t14.5 -35.5v-200q0 -21 -14.5 -35.5t-35.5 -14.5h-200q-21 0 -35.5 14.5t-14.5 35.5zM0 450v200q0 21 14.5 35.5t35.5 14.5h200q21 0 35.5 -14.5t14.5 -35.5v-200q0 -21 -14.5 -35.5t-35.5 -14.5h-200 q-21 0 -35.5 14.5t-14.5 35.5zM0 850v200q0 21 14.5 35.5t35.5 14.5h200q21 0 35.5 -14.5t14.5 -35.5v-200q0 -21 -14.5 -35.5t-35.5 -14.5h-200q-21 0 -35.5 14.5t-14.5 35.5zM400 50v200q0 21 14.5 35.5t35.5 14.5h200q21 0 35.5 -14.5t14.5 -35.5v-200q0 -21 -14.5 -35.5 t-35.5 -14.5h-200q-21 0 -35.5 14.5t-14.5 35.5zM400 450v200q0 21 14.5 35.5t35.5 14.5h200q21 0 35.5 -14.5t14.5 -35.5v-200q0 -21 -14.5 -35.5t-35.5 -14.5h-200q-21 0 -35.5 14.5t-14.5 35.5zM400 850v200q0 21 14.5 35.5t35.5 14.5h200q21 0 35.5 -14.5t14.5 -35.5 v-200q0 -21 -14.5 -35.5t-35.5 -14.5h-200q-21 0 -35.5 14.5t-14.5 35.5zM800 50v200q0 21 14.5 35.5t35.5 14.5h200q21 0 35.5 -14.5t14.5 -35.5v-200q0 -21 -14.5 -35.5t-35.5 -14.5h-200q-21 0 -35.5 14.5t-14.5 35.5zM800 450v200q0 21 14.5 35.5t35.5 14.5h200 q21 0 35.5 -14.5t14.5 -35.5v-200q0 -21 -14.5 -35.5t-35.5 -14.5h-200q-21 0 -35.5 14.5t-14.5 35.5zM800 850v200q0 21 14.5 35.5t35.5 14.5h200q21 0 35.5 -14.5t14.5 -35.5v-200q0 -21 -14.5 -35.5t-35.5 -14.5h-200q-21 0 -35.5 14.5t-14.5 35.5z' /%3E %3Cglyph unicode='&%23xe012;' d='M0 50v200q0 21 14.5 35.5t35.5 14.5h200q21 0 35.5 -14.5t14.5 -35.5v-200q0 -21 -14.5 -35.5t-35.5 -14.5h-200q-21 0 -35.5 14.5t-14.5 35.5zM0 450q0 -21 14.5 -35.5t35.5 -14.5h200q21 0 35.5 14.5t14.5 35.5v200q0 21 -14.5 35.5t-35.5 14.5h-200q-21 0 -35.5 -14.5 t-14.5 -35.5v-200zM0 850v200q0 21 14.5 35.5t35.5 14.5h200q21 0 35.5 -14.5t14.5 -35.5v-200q0 -21 -14.5 -35.5t-35.5 -14.5h-200q-21 0 -35.5 14.5t-14.5 35.5zM400 50v200q0 21 14.5 35.5t35.5 14.5h700q21 0 35.5 -14.5t14.5 -35.5v-200q0 -21 -14.5 -35.5 t-35.5 -14.5h-700q-21 0 -35.5 14.5t-14.5 35.5zM400 450v200q0 21 14.5 35.5t35.5 14.5h700q21 0 35.5 -14.5t14.5 -35.5v-200q0 -21 -14.5 -35.5t-35.5 -14.5h-700q-21 0 -35.5 14.5t-14.5 35.5zM400 850v200q0 21 14.5 35.5t35.5 14.5h700q21 0 35.5 -14.5t14.5 -35.5 v-200q0 -21 -14.5 -35.5t-35.5 -14.5h-700q-21 0 -35.5 14.5t-14.5 35.5z' /%3E %3Cglyph unicode='&%23xe013;' d='M29 454l419 -420l818 820l-212 212l-607 -607l-206 207z' /%3E %3Cglyph unicode='&%23xe014;' d='M106 318l282 282l-282 282l212 212l282 -282l282 282l212 -212l-282 -282l282 -282l-212 -212l-282 282l-282 -282z' /%3E %3Cglyph unicode='&%23xe015;' d='M23 693q0 200 142 342t342 142t342 -142t142 -342q0 -142 -78 -261l300 -300q7 -8 7 -18t-7 -18l-109 -109q-8 -7 -18 -7t-18 7l-300 300q-119 -78 -261 -78q-200 0 -342 142t-142 342zM176 693q0 -136 97 -233t234 -97t233.5 96.5t96.5 233.5t-96.5 233.5t-233.5 96.5 t-234 -97t-97 -233zM300 600v200h100v100h200v-100h100v-200h-100v-100h-200v100h-100z' /%3E %3Cglyph unicode='&%23xe016;' d='M23 694q0 200 142 342t342 142t342 -142t142 -342q0 -141 -78 -262l300 -299q7 -7 7 -18t-7 -18l-109 -109q-8 -8 -18 -8t-18 8l-300 300q-119 -78 -261 -78q-200 0 -342 142t-142 342zM176 694q0 -136 97 -233t234 -97t233.5 97t96.5 233t-96.5 233t-233.5 97t-234 -97 t-97 -233zM300 601h400v200h-400v-200z' /%3E %3Cglyph unicode='&%23xe017;' d='M23 600q0 183 105 331t272 210v-166q-103 -55 -165 -155t-62 -220q0 -177 125 -302t302 -125t302 125t125 302q0 120 -62 220t-165 155v166q167 -62 272 -210t105 -331q0 -118 -45.5 -224.5t-123 -184t-184 -123t-224.5 -45.5t-224.5 45.5t-184 123t-123 184t-45.5 224.5 zM500 750q0 -21 14.5 -35.5t35.5 -14.5h100q21 0 35.5 14.5t14.5 35.5v400q0 21 -14.5 35.5t-35.5 14.5h-100q-21 0 -35.5 -14.5t-14.5 -35.5v-400z' /%3E %3Cglyph unicode='&%23xe018;' d='M100 1h200v300h-200v-300zM400 1v500h200v-500h-200zM700 1v800h200v-800h-200zM1000 1v1200h200v-1200h-200z' /%3E %3Cglyph unicode='&%23xe019;' d='M26 601q0 -33 6 -74l151 -38l2 -6q14 -49 38 -93l3 -5l-80 -134q45 -59 105 -105l133 81l5 -3q45 -26 94 -39l5 -2l38 -151q40 -5 74 -5q27 0 74 5l38 151l6 2q46 13 93 39l5 3l134 -81q56 44 104 105l-80 134l3 5q24 44 39 93l1 6l152 38q5 40 5 74q0 28 -5 73l-152 38 l-1 6q-16 51 -39 93l-3 5l80 134q-44 58 -104 105l-134 -81l-5 3q-45 25 -93 39l-6 1l-38 152q-40 5 -74 5q-27 0 -74 -5l-38 -152l-5 -1q-50 -14 -94 -39l-5 -3l-133 81q-59 -47 -105 -105l80 -134l-3 -5q-25 -47 -38 -93l-2 -6l-151 -38q-6 -48 -6 -73zM385 601 q0 88 63 151t152 63t152 -63t63 -151q0 -89 -63 -152t-152 -63t-152 63t-63 152z' /%3E %3Cglyph unicode='&%23xe020;' d='M100 1025v50q0 10 7.5 17.5t17.5 7.5h275v100q0 41 29.5 70.5t70.5 29.5h300q41 0 70.5 -29.5t29.5 -70.5v-100h275q10 0 17.5 -7.5t7.5 -17.5v-50q0 -11 -7 -18t-18 -7h-1050q-11 0 -18 7t-7 18zM200 100v800h900v-800q0 -41 -29.5 -71t-70.5 -30h-700q-41 0 -70.5 30 t-29.5 71zM300 100h100v700h-100v-700zM500 100h100v700h-100v-700zM500 1100h300v100h-300v-100zM700 100h100v700h-100v-700zM900 100h100v700h-100v-700z' /%3E %3Cglyph unicode='&%23xe021;' d='M1 601l656 644l644 -644h-200v-600h-300v400h-300v-400h-300v600h-200z' /%3E %3Cglyph unicode='&%23xe022;' d='M100 25v1150q0 11 7 18t18 7h475v-500h400v-675q0 -11 -7 -18t-18 -7h-850q-11 0 -18 7t-7 18zM700 800v300l300 -300h-300z' /%3E %3Cglyph unicode='&%23xe023;' d='M4 600q0 162 80 299t217 217t299 80t299 -80t217 -217t80 -299t-80 -299t-217 -217t-299 -80t-299 80t-217 217t-80 299zM186 600q0 -171 121.5 -292.5t292.5 -121.5t292.5 121.5t121.5 292.5t-121.5 292.5t-292.5 121.5t-292.5 -121.5t-121.5 -292.5zM500 500v400h100 v-300h200v-100h-300z' /%3E %3Cglyph unicode='&%23xe024;' d='M-100 0l431 1200h209l-21 -300h162l-20 300h208l431 -1200h-538l-41 400h-242l-40 -400h-539zM488 500h224l-27 300h-170z' /%3E %3Cglyph unicode='&%23xe025;' d='M0 0v400h490l-290 300h200v500h300v-500h200l-290 -300h490v-400h-1100zM813 200h175v100h-175v-100z' /%3E %3Cglyph unicode='&%23xe026;' d='M1 600q0 122 47.5 233t127.5 191t191 127.5t233 47.5t233 -47.5t191 -127.5t127.5 -191t47.5 -233t-47.5 -233t-127.5 -191t-191 -127.5t-233 -47.5t-233 47.5t-191 127.5t-127.5 191t-47.5 233zM188 600q0 -170 121 -291t291 -121t291 121t121 291t-121 291t-291 121 t-291 -121t-121 -291zM350 600h150v300h200v-300h150l-250 -300z' /%3E %3Cglyph unicode='&%23xe027;' d='M4 600q0 162 80 299t217 217t299 80t299 -80t217 -217t80 -299t-80 -299t-217 -217t-299 -80t-299 80t-217 217t-80 299zM186 600q0 -171 121.5 -292.5t292.5 -121.5t292.5 121.5t121.5 292.5t-121.5 292.5t-292.5 121.5t-292.5 -121.5t-121.5 -292.5zM350 600l250 300 l250 -300h-150v-300h-200v300h-150z' /%3E %3Cglyph unicode='&%23xe028;' d='M0 25v475l200 700h800l199 -700l1 -475q0 -11 -7 -18t-18 -7h-1150q-11 0 -18 7t-7 18zM200 500h200l50 -200h300l50 200h200l-97 500h-606z' /%3E %3Cglyph unicode='&%23xe029;' d='M4 600q0 162 80 299t217 217t299 80t299 -80t217 -217t80 -299t-80 -299t-217 -217t-299 -80t-299 80t-217 217t-80 299zM186 600q0 -172 121.5 -293t292.5 -121t292.5 121t121.5 293q0 171 -121.5 292.5t-292.5 121.5t-292.5 -121.5t-121.5 -292.5zM500 397v401 l297 -200z' /%3E %3Cglyph unicode='&%23xe030;' d='M23 600q0 -118 45.5 -224.5t123 -184t184 -123t224.5 -45.5t224.5 45.5t184 123t123 184t45.5 224.5h-150q0 -177 -125 -302t-302 -125t-302 125t-125 302t125 302t302 125q136 0 246 -81l-146 -146h400v400l-145 -145q-157 122 -355 122q-118 0 -224.5 -45.5t-184 -123 t-123 -184t-45.5 -224.5z' /%3E %3Cglyph unicode='&%23xe031;' d='M23 600q0 118 45.5 224.5t123 184t184 123t224.5 45.5q198 0 355 -122l145 145v-400h-400l147 147q-112 80 -247 80q-177 0 -302 -125t-125 -302h-150zM100 0v400h400l-147 -147q112 -80 247 -80q177 0 302 125t125 302h150q0 -118 -45.5 -224.5t-123 -184t-184 -123 t-224.5 -45.5q-198 0 -355 122z' /%3E %3Cglyph unicode='&%23xe032;' d='M100 0h1100v1200h-1100v-1200zM200 100v900h900v-900h-900zM300 200v100h100v-100h-100zM300 400v100h100v-100h-100zM300 600v100h100v-100h-100zM300 800v100h100v-100h-100zM500 200h500v100h-500v-100zM500 400v100h500v-100h-500zM500 600v100h500v-100h-500z M500 800v100h500v-100h-500z' /%3E %3Cglyph unicode='&%23xe033;' d='M0 100v600q0 41 29.5 70.5t70.5 29.5h100v200q0 82 59 141t141 59h300q82 0 141 -59t59 -141v-200h100q41 0 70.5 -29.5t29.5 -70.5v-600q0 -41 -29.5 -70.5t-70.5 -29.5h-900q-41 0 -70.5 29.5t-29.5 70.5zM400 800h300v150q0 21 -14.5 35.5t-35.5 14.5h-200 q-21 0 -35.5 -14.5t-14.5 -35.5v-150z' /%3E %3Cglyph unicode='&%23xe034;' d='M100 0v1100h100v-1100h-100zM300 400q60 60 127.5 84t127.5 17.5t122 -23t119 -30t110 -11t103 42t91 120.5v500q-40 -81 -101.5 -115.5t-127.5 -29.5t-138 25t-139.5 40t-125.5 25t-103 -29.5t-65 -115.5v-500z' /%3E %3Cglyph unicode='&%23xe035;' d='M0 275q0 -11 7 -18t18 -7h50q11 0 18 7t7 18v300q0 127 70.5 231.5t184.5 161.5t245 57t245 -57t184.5 -161.5t70.5 -231.5v-300q0 -11 7 -18t18 -7h50q11 0 18 7t7 18v300q0 116 -49.5 227t-131 192.5t-192.5 131t-227 49.5t-227 -49.5t-192.5 -131t-131 -192.5 t-49.5 -227v-300zM200 20v460q0 8 6 14t14 6h160q8 0 14 -6t6 -14v-460q0 -8 -6 -14t-14 -6h-160q-8 0 -14 6t-6 14zM800 20v460q0 8 6 14t14 6h160q8 0 14 -6t6 -14v-460q0 -8 -6 -14t-14 -6h-160q-8 0 -14 6t-6 14z' /%3E %3Cglyph unicode='&%23xe036;' d='M0 400h300l300 -200v800l-300 -200h-300v-400zM688 459l141 141l-141 141l71 71l141 -141l141 141l71 -71l-141 -141l141 -141l-71 -71l-141 141l-141 -141z' /%3E %3Cglyph unicode='&%23xe037;' d='M0 400h300l300 -200v800l-300 -200h-300v-400zM700 857l69 53q111 -135 111 -310q0 -169 -106 -302l-67 54q86 110 86 248q0 146 -93 257z' /%3E %3Cglyph unicode='&%23xe038;' d='M0 401v400h300l300 200v-800l-300 200h-300zM702 858l69 53q111 -135 111 -310q0 -170 -106 -303l-67 55q86 110 86 248q0 145 -93 257zM889 951l7 -8q123 -151 123 -344q0 -189 -119 -339l-7 -8l81 -66l6 8q142 178 142 405q0 230 -144 408l-6 8z' /%3E %3Cglyph unicode='&%23xe039;' d='M0 0h500v500h-200v100h-100v-100h-200v-500zM0 600h100v100h400v100h100v100h-100v300h-500v-600zM100 100v300h300v-300h-300zM100 800v300h300v-300h-300zM200 200v100h100v-100h-100zM200 900h100v100h-100v-100zM500 500v100h300v-300h200v-100h-100v-100h-200v100 h-100v100h100v200h-200zM600 0v100h100v-100h-100zM600 1000h100v-300h200v-300h300v200h-200v100h200v500h-600v-200zM800 800v300h300v-300h-300zM900 0v100h300v-100h-300zM900 900v100h100v-100h-100zM1100 200v100h100v-100h-100z' /%3E %3Cglyph unicode='&%23xe040;' d='M0 200h100v1000h-100v-1000zM100 0v100h300v-100h-300zM200 200v1000h100v-1000h-100zM500 0v91h100v-91h-100zM500 200v1000h200v-1000h-200zM700 0v91h100v-91h-100zM800 200v1000h100v-1000h-100zM900 0v91h200v-91h-200zM1000 200v1000h200v-1000h-200z' /%3E %3Cglyph unicode='&%23xe041;' d='M0 700l1 475q0 10 7.5 17.5t17.5 7.5h474l700 -700l-500 -500zM148 953q0 -42 29 -71q30 -30 71.5 -30t71.5 30q29 29 29 71t-29 71q-30 30 -71.5 30t-71.5 -30q-29 -29 -29 -71z' /%3E %3Cglyph unicode='&%23xe042;' d='M1 700l1 475q0 11 7 18t18 7h474l700 -700l-500 -500zM148 953q0 -42 30 -71q29 -30 71 -30t71 30q30 29 30 71t-30 71q-29 30 -71 30t-71 -30q-30 -29 -30 -71zM701 1200h100l700 -700l-500 -500l-50 50l450 450z' /%3E %3Cglyph unicode='&%23xe043;' d='M100 0v1025l175 175h925v-1000l-100 -100v1000h-750l-100 -100h750v-1000h-900z' /%3E %3Cglyph unicode='&%23xe044;' d='M200 0l450 444l450 -443v1150q0 20 -14.5 35t-35.5 15h-800q-21 0 -35.5 -15t-14.5 -35v-1151z' /%3E %3Cglyph unicode='&%23xe045;' d='M0 100v700h200l100 -200h600l100 200h200v-700h-200v200h-800v-200h-200zM253 829l40 -124h592l62 124l-94 346q-2 11 -10 18t-18 7h-450q-10 0 -18 -7t-10 -18zM281 24l38 152q2 10 11.5 17t19.5 7h500q10 0 19.5 -7t11.5 -17l38 -152q2 -10 -3.5 -17t-15.5 -7h-600 q-10 0 -15.5 7t-3.5 17z' /%3E %3Cglyph unicode='&%23xe046;' d='M0 200q0 -41 29.5 -70.5t70.5 -29.5h1000q41 0 70.5 29.5t29.5 70.5v600q0 41 -29.5 70.5t-70.5 29.5h-150q-4 8 -11.5 21.5t-33 48t-53 61t-69 48t-83.5 21.5h-200q-41 0 -82 -20.5t-70 -50t-52 -59t-34 -50.5l-12 -20h-150q-41 0 -70.5 -29.5t-29.5 -70.5v-600z M356 500q0 100 72 172t172 72t172 -72t72 -172t-72 -172t-172 -72t-172 72t-72 172zM494 500q0 -44 31 -75t75 -31t75 31t31 75t-31 75t-75 31t-75 -31t-31 -75zM900 700v100h100v-100h-100z' /%3E %3Cglyph unicode='&%23xe047;' d='M53 0h365v66q-41 0 -72 11t-49 38t1 71l92 234h391l82 -222q16 -45 -5.5 -88.5t-74.5 -43.5v-66h417v66q-34 1 -74 43q-18 19 -33 42t-21 37l-6 13l-385 998h-93l-399 -1006q-24 -48 -52 -75q-12 -12 -33 -25t-36 -20l-15 -7v-66zM416 521l178 457l46 -140l116 -317h-340 z' /%3E %3Cglyph unicode='&%23xe048;' d='M100 0v89q41 7 70.5 32.5t29.5 65.5v827q0 28 -1 39.5t-5.5 26t-15.5 21t-29 14t-49 14.5v71l471 -1q120 0 213 -88t93 -228q0 -55 -11.5 -101.5t-28 -74t-33.5 -47.5t-28 -28l-12 -7q8 -3 21.5 -9t48 -31.5t60.5 -58t47.5 -91.5t21.5 -129q0 -84 -59 -156.5t-142 -111 t-162 -38.5h-500zM400 200h161q89 0 153 48.5t64 132.5q0 90 -62.5 154.5t-156.5 64.5h-159v-400zM400 700h139q76 0 130 61.5t54 138.5q0 82 -84 130.5t-239 48.5v-379z' /%3E %3Cglyph unicode='&%23xe049;' d='M200 0v57q77 7 134.5 40.5t65.5 80.5l173 849q10 56 -10 74t-91 37q-6 1 -10.5 2.5t-9.5 2.5v57h425l2 -57q-33 -8 -62 -25.5t-46 -37t-29.5 -38t-17.5 -30.5l-5 -12l-128 -825q-10 -52 14 -82t95 -36v-57h-500z' /%3E %3Cglyph unicode='&%23xe050;' d='M-75 200h75v800h-75l125 167l125 -167h-75v-800h75l-125 -167zM300 900v300h150h700h150v-300h-50q0 29 -8 48.5t-18.5 30t-33.5 15t-39.5 5.5t-50.5 1h-200v-850l100 -50v-100h-400v100l100 50v850h-200q-34 0 -50.5 -1t-40 -5.5t-33.5 -15t-18.5 -30t-8.5 -48.5h-49z ' /%3E %3Cglyph unicode='&%23xe051;' d='M33 51l167 125v-75h800v75l167 -125l-167 -125v75h-800v-75zM100 901v300h150h700h150v-300h-50q0 29 -8 48.5t-18 30t-33.5 15t-40 5.5t-50.5 1h-200v-650l100 -50v-100h-400v100l100 50v650h-200q-34 0 -50.5 -1t-39.5 -5.5t-33.5 -15t-18.5 -30t-8 -48.5h-50z' /%3E %3Cglyph unicode='&%23xe052;' d='M0 50q0 -20 14.5 -35t35.5 -15h1100q21 0 35.5 15t14.5 35v100q0 21 -14.5 35.5t-35.5 14.5h-1100q-21 0 -35.5 -14.5t-14.5 -35.5v-100zM0 350q0 -20 14.5 -35t35.5 -15h800q21 0 35.5 15t14.5 35v100q0 21 -14.5 35.5t-35.5 14.5h-800q-21 0 -35.5 -14.5t-14.5 -35.5 v-100zM0 650q0 -20 14.5 -35t35.5 -15h1000q21 0 35.5 15t14.5 35v100q0 21 -14.5 35.5t-35.5 14.5h-1000q-21 0 -35.5 -14.5t-14.5 -35.5v-100zM0 950q0 -20 14.5 -35t35.5 -15h600q21 0 35.5 15t14.5 35v100q0 21 -14.5 35.5t-35.5 14.5h-600q-21 0 -35.5 -14.5 t-14.5 -35.5v-100z' /%3E %3Cglyph unicode='&%23xe053;' d='M0 50q0 -20 14.5 -35t35.5 -15h1100q21 0 35.5 15t14.5 35v100q0 21 -14.5 35.5t-35.5 14.5h-1100q-21 0 -35.5 -14.5t-14.5 -35.5v-100zM0 650q0 -20 14.5 -35t35.5 -15h1100q21 0 35.5 15t14.5 35v100q0 21 -14.5 35.5t-35.5 14.5h-1100q-21 0 -35.5 -14.5t-14.5 -35.5 v-100zM200 350q0 -20 14.5 -35t35.5 -15h700q21 0 35.5 15t14.5 35v100q0 21 -14.5 35.5t-35.5 14.5h-700q-21 0 -35.5 -14.5t-14.5 -35.5v-100zM200 950q0 -20 14.5 -35t35.5 -15h700q21 0 35.5 15t14.5 35v100q0 21 -14.5 35.5t-35.5 14.5h-700q-21 0 -35.5 -14.5 t-14.5 -35.5v-100z' /%3E %3Cglyph unicode='&%23xe054;' d='M0 50v100q0 21 14.5 35.5t35.5 14.5h1100q21 0 35.5 -14.5t14.5 -35.5v-100q0 -20 -14.5 -35t-35.5 -15h-1100q-21 0 -35.5 15t-14.5 35zM100 650v100q0 21 14.5 35.5t35.5 14.5h1000q21 0 35.5 -14.5t14.5 -35.5v-100q0 -20 -14.5 -35t-35.5 -15h-1000q-21 0 -35.5 15 t-14.5 35zM300 350v100q0 21 14.5 35.5t35.5 14.5h800q21 0 35.5 -14.5t14.5 -35.5v-100q0 -20 -14.5 -35t-35.5 -15h-800q-21 0 -35.5 15t-14.5 35zM500 950v100q0 21 14.5 35.5t35.5 14.5h600q21 0 35.5 -14.5t14.5 -35.5v-100q0 -20 -14.5 -35t-35.5 -15h-600 q-21 0 -35.5 15t-14.5 35z' /%3E %3Cglyph unicode='&%23xe055;' d='M0 50v100q0 21 14.5 35.5t35.5 14.5h1100q21 0 35.5 -14.5t14.5 -35.5v-100q0 -20 -14.5 -35t-35.5 -15h-1100q-21 0 -35.5 15t-14.5 35zM0 350v100q0 21 14.5 35.5t35.5 14.5h1100q21 0 35.5 -14.5t14.5 -35.5v-100q0 -20 -14.5 -35t-35.5 -15h-1100q-21 0 -35.5 15 t-14.5 35zM0 650v100q0 21 14.5 35.5t35.5 14.5h1100q21 0 35.5 -14.5t14.5 -35.5v-100q0 -20 -14.5 -35t-35.5 -15h-1100q-21 0 -35.5 15t-14.5 35zM0 950v100q0 21 14.5 35.5t35.5 14.5h1100q21 0 35.5 -14.5t14.5 -35.5v-100q0 -20 -14.5 -35t-35.5 -15h-1100 q-21 0 -35.5 15t-14.5 35z' /%3E %3Cglyph unicode='&%23xe056;' d='M0 50v100q0 21 14.5 35.5t35.5 14.5h100q21 0 35.5 -14.5t14.5 -35.5v-100q0 -20 -14.5 -35t-35.5 -15h-100q-21 0 -35.5 15t-14.5 35zM0 350v100q0 21 14.5 35.5t35.5 14.5h100q21 0 35.5 -14.5t14.5 -35.5v-100q0 -20 -14.5 -35t-35.5 -15h-100q-21 0 -35.5 15 t-14.5 35zM0 650v100q0 21 14.5 35.5t35.5 14.5h100q21 0 35.5 -14.5t14.5 -35.5v-100q0 -20 -14.5 -35t-35.5 -15h-100q-21 0 -35.5 15t-14.5 35zM0 950v100q0 21 14.5 35.5t35.5 14.5h100q21 0 35.5 -14.5t14.5 -35.5v-100q0 -20 -14.5 -35t-35.5 -15h-100q-21 0 -35.5 15 t-14.5 35zM300 50v100q0 21 14.5 35.5t35.5 14.5h800q21 0 35.5 -14.5t14.5 -35.5v-100q0 -20 -14.5 -35t-35.5 -15h-800q-21 0 -35.5 15t-14.5 35zM300 350v100q0 21 14.5 35.5t35.5 14.5h800q21 0 35.5 -14.5t14.5 -35.5v-100q0 -20 -14.5 -35t-35.5 -15h-800 q-21 0 -35.5 15t-14.5 35zM300 650v100q0 21 14.5 35.5t35.5 14.5h800q21 0 35.5 -14.5t14.5 -35.5v-100q0 -20 -14.5 -35t-35.5 -15h-800q-21 0 -35.5 15t-14.5 35zM300 950v100q0 21 14.5 35.5t35.5 14.5h800q21 0 35.5 -14.5t14.5 -35.5v-100q0 -20 -14.5 -35t-35.5 -15 h-800q-21 0 -35.5 15t-14.5 35z' /%3E %3Cglyph unicode='&%23xe057;' d='M-101 500v100h201v75l166 -125l-166 -125v75h-201zM300 0h100v1100h-100v-1100zM500 50q0 -20 14.5 -35t35.5 -15h600q20 0 35 15t15 35v100q0 21 -15 35.5t-35 14.5h-600q-21 0 -35.5 -14.5t-14.5 -35.5v-100zM500 350q0 -20 14.5 -35t35.5 -15h300q20 0 35 15t15 35 v100q0 21 -15 35.5t-35 14.5h-300q-21 0 -35.5 -14.5t-14.5 -35.5v-100zM500 650q0 -20 14.5 -35t35.5 -15h500q20 0 35 15t15 35v100q0 21 -15 35.5t-35 14.5h-500q-21 0 -35.5 -14.5t-14.5 -35.5v-100zM500 950q0 -20 14.5 -35t35.5 -15h100q20 0 35 15t15 35v100 q0 21 -15 35.5t-35 14.5h-100q-21 0 -35.5 -14.5t-14.5 -35.5v-100z' /%3E %3Cglyph unicode='&%23xe058;' d='M1 50q0 -20 14.5 -35t35.5 -15h600q20 0 35 15t15 35v100q0 21 -15 35.5t-35 14.5h-600q-21 0 -35.5 -14.5t-14.5 -35.5v-100zM1 350q0 -20 14.5 -35t35.5 -15h300q20 0 35 15t15 35v100q0 21 -15 35.5t-35 14.5h-300q-21 0 -35.5 -14.5t-14.5 -35.5v-100zM1 650 q0 -20 14.5 -35t35.5 -15h500q20 0 35 15t15 35v100q0 21 -15 35.5t-35 14.5h-500q-21 0 -35.5 -14.5t-14.5 -35.5v-100zM1 950q0 -20 14.5 -35t35.5 -15h100q20 0 35 15t15 35v100q0 21 -15 35.5t-35 14.5h-100q-21 0 -35.5 -14.5t-14.5 -35.5v-100zM801 0v1100h100v-1100 h-100zM934 550l167 -125v75h200v100h-200v75z' /%3E %3Cglyph unicode='&%23xe059;' d='M0 275v650q0 31 22 53t53 22h750q31 0 53 -22t22 -53v-650q0 -31 -22 -53t-53 -22h-750q-31 0 -53 22t-22 53zM900 600l300 300v-600z' /%3E %3Cglyph unicode='&%23xe060;' d='M0 44v1012q0 18 13 31t31 13h1112q19 0 31.5 -13t12.5 -31v-1012q0 -18 -12.5 -31t-31.5 -13h-1112q-18 0 -31 13t-13 31zM100 263l247 182l298 -131l-74 156l293 318l236 -288v500h-1000v-737zM208 750q0 56 39 95t95 39t95 -39t39 -95t-39 -95t-95 -39t-95 39t-39 95z ' /%3E %3Cglyph unicode='&%23xe062;' d='M148 745q0 124 60.5 231.5t165 172t226.5 64.5q123 0 227 -63t164.5 -169.5t60.5 -229.5t-73 -272q-73 -114 -166.5 -237t-150.5 -189l-57 -66q-10 9 -27 26t-66.5 70.5t-96 109t-104 135.5t-100.5 155q-63 139 -63 262zM342 772q0 -107 75.5 -182.5t181.5 -75.5 q107 0 182.5 75.5t75.5 182.5t-75.5 182t-182.5 75t-182 -75.5t-75 -181.5z' /%3E %3Cglyph unicode='&%23xe063;' d='M1 600q0 122 47.5 233t127.5 191t191 127.5t233 47.5t233 -47.5t191 -127.5t127.5 -191t47.5 -233t-47.5 -233t-127.5 -191t-191 -127.5t-233 -47.5t-233 47.5t-191 127.5t-127.5 191t-47.5 233zM173 600q0 -177 125.5 -302t301.5 -125v854q-176 0 -301.5 -125 t-125.5 -302z' /%3E %3Cglyph unicode='&%23xe064;' d='M117 406q0 94 34 186t88.5 172.5t112 159t115 177t87.5 194.5q21 -71 57.5 -142.5t76 -130.5t83 -118.5t82 -117t70 -116t50 -125.5t18.5 -136q0 -89 -39 -165.5t-102 -126.5t-140 -79.5t-156 -33.5q-114 6 -211.5 53t-161.5 139t-64 210zM243 414q14 -82 59.5 -136 t136.5 -80l16 98q-7 6 -18 17t-34 48t-33 77q-15 73 -14 143.5t10 122.5l9 51q-92 -110 -119.5 -185t-12.5 -156z' /%3E %3Cglyph unicode='&%23xe065;' d='M0 400v300q0 165 117.5 282.5t282.5 117.5q366 -6 397 -14l-186 -186h-311q-41 0 -70.5 -29.5t-29.5 -70.5v-500q0 -41 29.5 -70.5t70.5 -29.5h500q41 0 70.5 29.5t29.5 70.5v125l200 200v-225q0 -165 -117.5 -282.5t-282.5 -117.5h-300q-165 0 -282.5 117.5 t-117.5 282.5zM436 341l161 50l412 412l-114 113l-405 -405zM995 1015l113 -113l113 113l-21 85l-92 28z' /%3E %3Cglyph unicode='&%23xe066;' d='M0 400v300q0 165 117.5 282.5t282.5 117.5h261l2 -80q-133 -32 -218 -120h-145q-41 0 -70.5 -29.5t-29.5 -70.5v-500q0 -41 29.5 -70.5t70.5 -29.5h500q41 0 70.5 29.5t29.5 70.5l200 153v-53q0 -165 -117.5 -282.5t-282.5 -117.5h-300q-165 0 -282.5 117.5t-117.5 282.5 zM423 524q30 38 81.5 64t103 35.5t99 14t77.5 3.5l29 -1v-209l360 324l-359 318v-216q-7 0 -19 -1t-48 -8t-69.5 -18.5t-76.5 -37t-76.5 -59t-62 -88t-39.5 -121.5z' /%3E %3Cglyph unicode='&%23xe067;' d='M0 400v300q0 165 117.5 282.5t282.5 117.5h300q61 0 127 -23l-178 -177h-349q-41 0 -70.5 -29.5t-29.5 -70.5v-500q0 -41 29.5 -70.5t70.5 -29.5h500q41 0 70.5 29.5t29.5 70.5v69l200 200v-169q0 -165 -117.5 -282.5t-282.5 -117.5h-300q-165 0 -282.5 117.5 t-117.5 282.5zM342 632l283 -284l567 567l-137 137l-430 -431l-146 147z' /%3E %3Cglyph unicode='&%23xe068;' d='M0 603l300 296v-198h200v200h-200l300 300l295 -300h-195v-200h200v198l300 -296l-300 -300v198h-200v-200h195l-295 -300l-300 300h200v200h-200v-198z' /%3E %3Cglyph unicode='&%23xe069;' d='M200 50v1000q0 21 14.5 35.5t35.5 14.5h100q21 0 35.5 -14.5t14.5 -35.5v-437l500 487v-1100l-500 488v-438q0 -21 -14.5 -35.5t-35.5 -14.5h-100q-21 0 -35.5 14.5t-14.5 35.5z' /%3E %3Cglyph unicode='&%23xe070;' d='M0 50v1000q0 21 14.5 35.5t35.5 14.5h100q21 0 35.5 -14.5t14.5 -35.5v-437l500 487v-487l500 487v-1100l-500 488v-488l-500 488v-438q0 -21 -14.5 -35.5t-35.5 -14.5h-100q-21 0 -35.5 14.5t-14.5 35.5z' /%3E %3Cglyph unicode='&%23xe071;' d='M136 550l564 550v-487l500 487v-1100l-500 488v-488z' /%3E %3Cglyph unicode='&%23xe072;' d='M200 0l900 550l-900 550v-1100z' /%3E %3Cglyph unicode='&%23xe073;' d='M200 150q0 -21 14.5 -35.5t35.5 -14.5h200q21 0 35.5 14.5t14.5 35.5v800q0 21 -14.5 35.5t-35.5 14.5h-200q-21 0 -35.5 -14.5t-14.5 -35.5v-800zM600 150q0 -21 14.5 -35.5t35.5 -14.5h200q21 0 35.5 14.5t14.5 35.5v800q0 21 -14.5 35.5t-35.5 14.5h-200 q-21 0 -35.5 -14.5t-14.5 -35.5v-800z' /%3E %3Cglyph unicode='&%23xe074;' d='M200 150q0 -20 14.5 -35t35.5 -15h800q21 0 35.5 15t14.5 35v800q0 21 -14.5 35.5t-35.5 14.5h-800q-21 0 -35.5 -14.5t-14.5 -35.5v-800z' /%3E %3Cglyph unicode='&%23xe075;' d='M0 0v1100l500 -487v487l564 -550l-564 -550v488z' /%3E %3Cglyph unicode='&%23xe076;' d='M0 0v1100l500 -487v487l500 -487v437q0 21 14.5 35.5t35.5 14.5h100q21 0 35.5 -14.5t14.5 -35.5v-1000q0 -21 -14.5 -35.5t-35.5 -14.5h-100q-21 0 -35.5 14.5t-14.5 35.5v438l-500 -488v488z' /%3E %3Cglyph unicode='&%23xe077;' d='M300 0v1100l500 -487v437q0 21 14.5 35.5t35.5 14.5h100q21 0 35.5 -14.5t14.5 -35.5v-1000q0 -21 -14.5 -35.5t-35.5 -14.5h-100q-21 0 -35.5 14.5t-14.5 35.5v438z' /%3E %3Cglyph unicode='&%23xe078;' d='M100 250v100q0 21 14.5 35.5t35.5 14.5h1000q21 0 35.5 -14.5t14.5 -35.5v-100q0 -21 -14.5 -35.5t-35.5 -14.5h-1000q-21 0 -35.5 14.5t-14.5 35.5zM100 500h1100l-550 564z' /%3E %3Cglyph unicode='&%23xe079;' d='M185 599l592 -592l240 240l-353 353l353 353l-240 240z' /%3E %3Cglyph unicode='&%23xe080;' d='M272 194l353 353l-353 353l241 240l572 -571l21 -22l-1 -1v-1l-592 -591z' /%3E %3Cglyph unicode='&%23xe081;' d='M3 600q0 162 80 299.5t217.5 217.5t299.5 80t299.5 -80t217.5 -217.5t80 -299.5t-80 -299.5t-217.5 -217.5t-299.5 -80t-299.5 80t-217.5 217.5t-80 299.5zM300 500h200v-200h200v200h200v200h-200v200h-200v-200h-200v-200z' /%3E %3Cglyph unicode='&%23xe082;' d='M3 600q0 162 80 299.5t217.5 217.5t299.5 80t299.5 -80t217.5 -217.5t80 -299.5t-80 -299.5t-217.5 -217.5t-299.5 -80t-299.5 80t-217.5 217.5t-80 299.5zM300 500h600v200h-600v-200z' /%3E %3Cglyph unicode='&%23xe083;' d='M3 600q0 162 80 299.5t217.5 217.5t299.5 80t299.5 -80t217.5 -217.5t80 -299.5t-80 -299.5t-217.5 -217.5t-299.5 -80t-299.5 80t-217.5 217.5t-80 299.5zM246 459l213 -213l141 142l141 -142l213 213l-142 141l142 141l-213 212l-141 -141l-141 142l-212 -213l141 -141 z' /%3E %3Cglyph unicode='&%23xe084;' d='M3 600q0 162 80 299.5t217.5 217.5t299.5 80t299.5 -80t217.5 -217.5t80 -299.5t-80 -299.5t-217.5 -217.5t-299.5 -80t-299.5 80t-217.5 217.5t-80 299.5zM270 551l276 -277l411 411l-175 174l-236 -236l-102 102z' /%3E %3Cglyph unicode='&%23xe085;' d='M3 600q0 162 80 299.5t217.5 217.5t299.5 80t299.5 -80t217.5 -217.5t80 -299.5t-80 -299.5t-217.5 -217.5t-299.5 -80t-299.5 80t-217.5 217.5t-80 299.5zM364 700h143q4 0 11.5 -1t11 -1t6.5 3t3 9t1 11t3.5 8.5t3.5 6t5.5 4t6.5 2.5t9 1.5t9 0.5h11.5h12.5 q19 0 30 -10t11 -26q0 -22 -4 -28t-27 -22q-5 -1 -12.5 -3t-27 -13.5t-34 -27t-26.5 -46t-11 -68.5h200q5 3 14 8t31.5 25.5t39.5 45.5t31 69t14 94q0 51 -17.5 89t-42 58t-58.5 32t-58.5 15t-51.5 3q-50 0 -90.5 -12t-75 -38.5t-53.5 -74.5t-19 -114zM500 300h200v100h-200 v-100z' /%3E %3Cglyph unicode='&%23xe086;' d='M3 600q0 162 80 299.5t217.5 217.5t299.5 80t299.5 -80t217.5 -217.5t80 -299.5t-80 -299.5t-217.5 -217.5t-299.5 -80t-299.5 80t-217.5 217.5t-80 299.5zM400 300h400v100h-100v300h-300v-100h100v-200h-100v-100zM500 800h200v100h-200v-100z' /%3E %3Cglyph unicode='&%23xe087;' d='M0 500v200h195q31 125 98.5 199.5t206.5 100.5v200h200v-200q54 -20 113 -60t112.5 -105.5t71.5 -134.5h203v-200h-203q-25 -102 -116.5 -186t-180.5 -117v-197h-200v197q-140 27 -208 102.5t-98 200.5h-194zM290 500q24 -73 79.5 -127.5t130.5 -78.5v206h200v-206 q149 48 201 206h-201v200h200q-25 74 -75.5 127t-124.5 77v-204h-200v203q-75 -23 -130 -77t-79 -126h209v-200h-210z' /%3E %3Cglyph unicode='&%23xe088;' d='M4 600q0 162 80 299t217 217t299 80t299 -80t217 -217t80 -299t-80 -299t-217 -217t-299 -80t-299 80t-217 217t-80 299zM186 600q0 -171 121.5 -292.5t292.5 -121.5t292.5 121.5t121.5 292.5t-121.5 292.5t-292.5 121.5t-292.5 -121.5t-121.5 -292.5zM356 465l135 135 l-135 135l109 109l135 -135l135 135l109 -109l-135 -135l135 -135l-109 -109l-135 135l-135 -135z' /%3E %3Cglyph unicode='&%23xe089;' d='M4 600q0 162 80 299t217 217t299 80t299 -80t217 -217t80 -299t-80 -299t-217 -217t-299 -80t-299 80t-217 217t-80 299zM186 600q0 -171 121.5 -292.5t292.5 -121.5t292.5 121.5t121.5 292.5t-121.5 292.5t-292.5 121.5t-292.5 -121.5t-121.5 -292.5zM322 537l141 141 l87 -87l204 205l142 -142l-346 -345z' /%3E %3Cglyph unicode='&%23xe090;' d='M4 600q0 162 80 299t217 217t299 80t299 -80t217 -217t80 -299t-80 -299t-217 -217t-299 -80t-299 80t-217 217t-80 299zM186 600q0 -115 62 -215l568 567q-100 62 -216 62q-171 0 -292.5 -121.5t-121.5 -292.5zM391 245q97 -59 209 -59q171 0 292.5 121.5t121.5 292.5 q0 112 -59 209z' /%3E %3Cglyph unicode='&%23xe091;' d='M0 547l600 453v-300h600v-300h-600v-301z' /%3E %3Cglyph unicode='&%23xe092;' d='M0 400v300h600v300l600 -453l-600 -448v301h-600z' /%3E %3Cglyph unicode='&%23xe093;' d='M204 600l450 600l444 -600h-298v-600h-300v600h-296z' /%3E %3Cglyph unicode='&%23xe094;' d='M104 600h296v600h300v-600h298l-449 -600z' /%3E %3Cglyph unicode='&%23xe095;' d='M0 200q6 132 41 238.5t103.5 193t184 138t271.5 59.5v271l600 -453l-600 -448v301q-95 -2 -183 -20t-170 -52t-147 -92.5t-100 -135.5z' /%3E %3Cglyph unicode='&%23xe096;' d='M0 0v400l129 -129l294 294l142 -142l-294 -294l129 -129h-400zM635 777l142 -142l294 294l129 -129v400h-400l129 -129z' /%3E %3Cglyph unicode='&%23xe097;' d='M34 176l295 295l-129 129h400v-400l-129 130l-295 -295zM600 600v400l129 -129l295 295l142 -141l-295 -295l129 -130h-400z' /%3E %3Cglyph unicode='&%23xe101;' d='M23 600q0 118 45.5 224.5t123 184t184 123t224.5 45.5t224.5 -45.5t184 -123t123 -184t45.5 -224.5t-45.5 -224.5t-123 -184t-184 -123t-224.5 -45.5t-224.5 45.5t-184 123t-123 184t-45.5 224.5zM456 851l58 -302q4 -20 21.5 -34.5t37.5 -14.5h54q20 0 37.5 14.5 t21.5 34.5l58 302q4 20 -8 34.5t-32 14.5h-207q-21 0 -33 -14.5t-8 -34.5zM500 300h200v100h-200v-100z' /%3E %3Cglyph unicode='&%23xe102;' d='M0 800h100v-200h400v300h200v-300h400v200h100v100h-111q1 1 1 6.5t-1.5 15t-3.5 17.5l-34 172q-11 39 -41.5 63t-69.5 24q-32 0 -61 -17l-239 -144q-22 -13 -40 -35q-19 24 -40 36l-238 144q-33 18 -62 18q-39 0 -69.5 -23t-40.5 -61l-35 -177q-2 -8 -3 -18t-1 -15v-6 h-111v-100zM100 0h400v400h-400v-400zM200 900q-3 0 14 48t36 96l18 47l213 -191h-281zM700 0v400h400v-400h-400zM731 900l202 197q5 -12 12 -32.5t23 -64t25 -72t7 -28.5h-269z' /%3E %3Cglyph unicode='&%23xe103;' d='M0 -22v143l216 193q-9 53 -13 83t-5.5 94t9 113t38.5 114t74 124q47 60 99.5 102.5t103 68t127.5 48t145.5 37.5t184.5 43.5t220 58.5q0 -189 -22 -343t-59 -258t-89 -181.5t-108.5 -120t-122 -68t-125.5 -30t-121.5 -1.5t-107.5 12.5t-87.5 17t-56.5 7.5l-99 -55z M238.5 300.5q19.5 -6.5 86.5 76.5q55 66 367 234q70 38 118.5 69.5t102 79t99 111.5t86.5 148q22 50 24 60t-6 19q-7 5 -17 5t-26.5 -14.5t-33.5 -39.5q-35 -51 -113.5 -108.5t-139.5 -89.5l-61 -32q-369 -197 -458 -401q-48 -111 -28.5 -117.5z' /%3E %3Cglyph unicode='&%23xe104;' d='M111 408q0 -33 5 -63q9 -56 44 -119.5t105 -108.5q31 -21 64 -16t62 23.5t57 49.5t48 61.5t35 60.5q32 66 39 184.5t-13 157.5q79 -80 122 -164t26 -184q-5 -33 -20.5 -69.5t-37.5 -80.5q-10 -19 -14.5 -29t-12 -26t-9 -23.5t-3 -19t2.5 -15.5t11 -9.5t19.5 -5t30.5 2.5 t42 8q57 20 91 34t87.5 44.5t87 64t65.5 88.5t47 122q38 172 -44.5 341.5t-246.5 278.5q22 -44 43 -129q39 -159 -32 -154q-15 2 -33 9q-79 33 -120.5 100t-44 175.5t48.5 257.5q-13 -8 -34 -23.5t-72.5 -66.5t-88.5 -105.5t-60 -138t-8 -166.5q2 -12 8 -41.5t8 -43t6 -39.5 t3.5 -39.5t-1 -33.5t-6 -31.5t-13.5 -24t-21 -20.5t-31 -12q-38 -10 -67 13t-40.5 61.5t-15 81.5t10.5 75q-52 -46 -83.5 -101t-39 -107t-7.5 -85z' /%3E %3Cglyph unicode='&%23xe105;' d='M-61 600l26 40q6 10 20 30t49 63.5t74.5 85.5t97 90t116.5 83.5t132.5 59t145.5 23.5t145.5 -23.5t132.5 -59t116.5 -83.5t97 -90t74.5 -85.5t49 -63.5t20 -30l26 -40l-26 -40q-6 -10 -20 -30t-49 -63.5t-74.5 -85.5t-97 -90t-116.5 -83.5t-132.5 -59t-145.5 -23.5 t-145.5 23.5t-132.5 59t-116.5 83.5t-97 90t-74.5 85.5t-49 63.5t-20 30zM120 600q7 -10 40.5 -58t56 -78.5t68 -77.5t87.5 -75t103 -49.5t125 -21.5t123.5 20t100.5 45.5t85.5 71.5t66.5 75.5t58 81.5t47 66q-1 1 -28.5 37.5t-42 55t-43.5 53t-57.5 63.5t-58.5 54 q49 -74 49 -163q0 -124 -88 -212t-212 -88t-212 88t-88 212q0 85 46 158q-102 -87 -226 -258zM377 656q49 -124 154 -191l105 105q-37 24 -75 72t-57 84l-20 36z' /%3E %3Cglyph unicode='&%23xe106;' d='M-61 600l26 40q6 10 20 30t49 63.5t74.5 85.5t97 90t116.5 83.5t132.5 59t145.5 23.5q61 0 121 -17l37 142h148l-314 -1200h-148l37 143q-82 21 -165 71.5t-140 102t-109.5 112t-72 88.5t-29.5 43zM120 600q210 -282 393 -336l37 141q-107 18 -178.5 101.5t-71.5 193.5 q0 85 46 158q-102 -87 -226 -258zM377 656q49 -124 154 -191l47 47l23 87q-30 28 -59 69t-44 68l-14 26zM780 161l38 145q22 15 44.5 34t46 44t40.5 44t41 50.5t33.5 43.5t33 44t24.5 34q-97 127 -140 175l39 146q67 -54 131.5 -125.5t87.5 -103.5t36 -52l26 -40l-26 -40 q-7 -12 -25.5 -38t-63.5 -79.5t-95.5 -102.5t-124 -100t-146.5 -79z' /%3E %3Cglyph unicode='&%23xe107;' d='M-97.5 34q13.5 -34 50.5 -34h1294q37 0 50.5 35.5t-7.5 67.5l-642 1056q-20 34 -48 36.5t-48 -29.5l-642 -1066q-21 -32 -7.5 -66zM155 200l445 723l445 -723h-345v100h-200v-100h-345zM500 600l100 -300l100 300v100h-200v-100z' /%3E %3Cglyph unicode='&%23xe108;' d='M100 262v41q0 20 11 44.5t26 38.5l363 325v339q0 62 44 106t106 44t106 -44t44 -106v-339l363 -325q15 -14 26 -38.5t11 -44.5v-41q0 -20 -12 -26.5t-29 5.5l-359 249v-263q100 -91 100 -113v-64q0 -20 -13 -28.5t-32 0.5l-94 78h-222l-94 -78q-19 -9 -32 -0.5t-13 28.5 v64q0 22 100 113v263l-359 -249q-17 -12 -29 -5.5t-12 26.5z' /%3E %3Cglyph unicode='&%23xe109;' d='M0 50q0 -20 14.5 -35t35.5 -15h1000q21 0 35.5 15t14.5 35v750h-1100v-750zM0 900h1100v150q0 21 -14.5 35.5t-35.5 14.5h-150v100h-100v-100h-500v100h-100v-100h-150q-21 0 -35.5 -14.5t-14.5 -35.5v-150zM100 100v100h100v-100h-100zM100 300v100h100v-100h-100z M100 500v100h100v-100h-100zM300 100v100h100v-100h-100zM300 300v100h100v-100h-100zM300 500v100h100v-100h-100zM500 100v100h100v-100h-100zM500 300v100h100v-100h-100zM500 500v100h100v-100h-100zM700 100v100h100v-100h-100zM700 300v100h100v-100h-100zM700 500 v100h100v-100h-100zM900 100v100h100v-100h-100zM900 300v100h100v-100h-100zM900 500v100h100v-100h-100z' /%3E %3Cglyph unicode='&%23xe110;' d='M0 200v200h259l600 600h241v198l300 -295l-300 -300v197h-159l-600 -600h-341zM0 800h259l122 -122l141 142l-181 180h-341v-200zM678 381l141 142l122 -123h159v198l300 -295l-300 -300v197h-241z' /%3E %3Cglyph unicode='&%23xe111;' d='M0 400v600q0 41 29.5 70.5t70.5 29.5h1000q41 0 70.5 -29.5t29.5 -70.5v-600q0 -41 -29.5 -70.5t-70.5 -29.5h-596l-304 -300v300h-100q-41 0 -70.5 29.5t-29.5 70.5z' /%3E %3Cglyph unicode='&%23xe112;' d='M100 600v200h300v-250q0 -113 6 -145q17 -92 102 -117q39 -11 92 -11q37 0 66.5 5.5t50 15.5t36 24t24 31.5t14 37.5t7 42t2.5 45t0 47v25v250h300v-200q0 -42 -3 -83t-15 -104t-31.5 -116t-58 -109.5t-89 -96.5t-129 -65.5t-174.5 -25.5t-174.5 25.5t-129 65.5t-89 96.5 t-58 109.5t-31.5 116t-15 104t-3 83zM100 900v300h300v-300h-300zM800 900v300h300v-300h-300z' /%3E %3Cglyph unicode='&%23xe113;' d='M-30 411l227 -227l352 353l353 -353l226 227l-578 579z' /%3E %3Cglyph unicode='&%23xe114;' d='M70 797l580 -579l578 579l-226 227l-353 -353l-352 353z' /%3E %3Cglyph unicode='&%23xe115;' d='M-198 700l299 283l300 -283h-203v-400h385l215 -200h-800v600h-196zM402 1000l215 -200h381v-400h-198l299 -283l299 283h-200v600h-796z' /%3E %3Cglyph unicode='&%23xe116;' d='M18 939q-5 24 10 42q14 19 39 19h896l38 162q5 17 18.5 27.5t30.5 10.5h94q20 0 35 -14.5t15 -35.5t-15 -35.5t-35 -14.5h-54l-201 -961q-2 -4 -6 -10.5t-19 -17.5t-33 -11h-31v-50q0 -20 -14.5 -35t-35.5 -15t-35.5 15t-14.5 35v50h-300v-50q0 -20 -14.5 -35t-35.5 -15 t-35.5 15t-14.5 35v50h-50q-21 0 -35.5 15t-14.5 35q0 21 14.5 35.5t35.5 14.5h535l48 200h-633q-32 0 -54.5 21t-27.5 43z' /%3E %3Cglyph unicode='&%23xe117;' d='M0 0v800h1200v-800h-1200zM0 900v100h200q0 41 29.5 70.5t70.5 29.5h300q41 0 70.5 -29.5t29.5 -70.5h500v-100h-1200z' /%3E %3Cglyph unicode='&%23xe118;' d='M1 0l300 700h1200l-300 -700h-1200zM1 400v600h200q0 41 29.5 70.5t70.5 29.5h300q41 0 70.5 -29.5t29.5 -70.5h500v-200h-1000z' /%3E %3Cglyph unicode='&%23xe119;' d='M302 300h198v600h-198l298 300l298 -300h-198v-600h198l-298 -300z' /%3E %3Cglyph unicode='&%23xe120;' d='M0 600l300 298v-198h600v198l300 -298l-300 -297v197h-600v-197z' /%3E %3Cglyph unicode='&%23xe121;' d='M0 100v100q0 41 29.5 70.5t70.5 29.5h1000q41 0 70.5 -29.5t29.5 -70.5v-100q0 -41 -29.5 -70.5t-70.5 -29.5h-1000q-41 0 -70.5 29.5t-29.5 70.5zM31 400l172 739q5 22 23 41.5t38 19.5h672q19 0 37.5 -22.5t23.5 -45.5l172 -732h-1138zM800 100h100v100h-100v-100z M1000 100h100v100h-100v-100z' /%3E %3Cglyph unicode='&%23xe122;' d='M-101 600v50q0 24 25 49t50 38l25 13v-250l-11 5.5t-24 14t-30 21.5t-24 27.5t-11 31.5zM100 500v250v8v8v7t0.5 7t1.5 5.5t2 5t3 4t4.5 3.5t6 1.5t7.5 0.5h200l675 250v-850l-675 200h-38l47 -276q2 -12 -3 -17.5t-11 -6t-21 -0.5h-8h-83q-20 0 -34.5 14t-18.5 35 q-55 337 -55 351zM1100 200v850q0 21 14.5 35.5t35.5 14.5q20 0 35 -14.5t15 -35.5v-850q0 -20 -15 -35t-35 -15q-21 0 -35.5 15t-14.5 35z' /%3E %3Cglyph unicode='&%23xe123;' d='M74 350q0 21 13.5 35.5t33.5 14.5h18l117 173l63 327q15 77 76 140t144 83l-18 32q-6 19 3 32t29 13h94q20 0 29 -10.5t3 -29.5q-18 -36 -18 -37q83 -19 144 -82.5t76 -140.5l63 -327l118 -173h17q20 0 33.5 -14.5t13.5 -35.5q0 -20 -13 -40t-31 -27q-8 -3 -23 -8.5 t-65 -20t-103 -25t-132.5 -19.5t-158.5 -9q-125 0 -245.5 20.5t-178.5 40.5l-58 20q-18 7 -31 27.5t-13 40.5zM497 110q12 -49 40 -79.5t63 -30.5t63 30.5t39 79.5q-48 -6 -102 -6t-103 6z' /%3E %3Cglyph unicode='&%23xe124;' d='M21 445l233 -45l-78 -224l224 78l45 -233l155 179l155 -179l45 233l224 -78l-78 224l234 45l-180 155l180 156l-234 44l78 225l-224 -78l-45 233l-155 -180l-155 180l-45 -233l-224 78l78 -225l-233 -44l179 -156z' /%3E %3Cglyph unicode='&%23xe125;' d='M0 200h200v600h-200v-600zM300 275q0 -75 100 -75h61q124 -100 139 -100h250q46 0 83 57l238 344q29 31 29 74v100q0 44 -30.5 84.5t-69.5 40.5h-328q28 118 28 125v150q0 44 -30.5 84.5t-69.5 40.5h-50q-27 0 -51 -20t-38 -48l-96 -198l-145 -196q-20 -26 -20 -63v-400z M400 300v375l150 213l100 212h50v-175l-50 -225h450v-125l-250 -375h-214l-136 100h-100z' /%3E %3Cglyph unicode='&%23xe126;' d='M0 400v600h200v-600h-200zM300 525v400q0 75 100 75h61q124 100 139 100h250q46 0 83 -57l238 -344q29 -31 29 -74v-100q0 -44 -30.5 -84.5t-69.5 -40.5h-328q28 -118 28 -125v-150q0 -44 -30.5 -84.5t-69.5 -40.5h-50q-27 0 -51 20t-38 48l-96 198l-145 196 q-20 26 -20 63zM400 525l150 -212l100 -213h50v175l-50 225h450v125l-250 375h-214l-136 -100h-100v-375z' /%3E %3Cglyph unicode='&%23xe127;' d='M8 200v600h200v-600h-200zM308 275v525q0 17 14 35.5t28 28.5l14 9l362 230q14 6 25 6q17 0 29 -12l109 -112q14 -14 14 -34q0 -18 -11 -32l-85 -121h302q85 0 138.5 -38t53.5 -110t-54.5 -111t-138.5 -39h-107l-130 -339q-7 -22 -20.5 -41.5t-28.5 -19.5h-341 q-7 0 -90 81t-83 94zM408 289l100 -89h293l131 339q6 21 19.5 41t28.5 20h203q16 0 25 15t9 36q0 20 -9 34.5t-25 14.5h-457h-6.5h-7.5t-6.5 0.5t-6 1t-5 1.5t-5.5 2.5t-4 4t-4 5.5q-5 12 -5 20q0 14 10 27l147 183l-86 83l-339 -236v-503z' /%3E %3Cglyph unicode='&%23xe128;' d='M-101 651q0 72 54 110t139 38l302 -1l-85 121q-11 16 -11 32q0 21 14 34l109 113q13 12 29 12q11 0 25 -6l365 -230q7 -4 17 -10.5t26.5 -26t16.5 -36.5v-526q0 -13 -86 -93.5t-94 -80.5h-341q-16 0 -29.5 20t-19.5 41l-130 339h-107q-84 0 -139 39t-55 111zM-1 601h222 q15 0 28.5 -20.5t19.5 -40.5l131 -339h293l107 89v502l-343 237l-87 -83l145 -184q10 -11 10 -26q0 -11 -5 -20q-1 -3 -3.5 -5.5l-4 -4t-5 -2.5t-5.5 -1.5t-6.5 -1t-6.5 -0.5h-7.5h-6.5h-476v-100zM1000 201v600h200v-600h-200z' /%3E %3Cglyph unicode='&%23xe129;' d='M97 719l230 -363q4 -6 10.5 -15.5t26 -25t36.5 -15.5h525q13 0 94 83t81 90v342q0 15 -20 28.5t-41 19.5l-339 131v106q0 84 -39 139t-111 55t-110 -53.5t-38 -138.5v-302l-121 84q-15 12 -33.5 11.5t-32.5 -13.5l-112 -110q-22 -22 -6 -53zM172 739l83 86l183 -146 q22 -18 47 -5q3 1 5.5 3.5l4 4t2.5 5t1.5 5.5t1 6.5t0.5 6.5v7.5v6.5v456q0 22 25 31t50 -0.5t25 -30.5v-202q0 -16 20 -29.5t41 -19.5l339 -130v-294l-89 -100h-503zM400 0v200h600v-200h-600z' /%3E %3Cglyph unicode='&%23xe130;' d='M2 585q-16 -31 6 -53l112 -110q13 -13 32 -13.5t34 10.5l121 85q0 -51 -0.5 -153.5t-0.5 -148.5q0 -84 38.5 -138t110.5 -54t111 55t39 139v106l339 131q20 6 40.5 19.5t20.5 28.5v342q0 7 -81 90t-94 83h-525q-17 0 -35.5 -14t-28.5 -28l-10 -15zM77 565l236 339h503 l89 -100v-294l-340 -130q-20 -6 -40 -20t-20 -29v-202q0 -22 -25 -31t-50 0t-25 31v456v14.5t-1.5 11.5t-5 12t-9.5 7q-24 13 -46 -5l-184 -146zM305 1104v200h600v-200h-600z' /%3E %3Cglyph unicode='&%23xe131;' d='M5 597q0 122 47.5 232.5t127.5 190.5t190.5 127.5t232.5 47.5q162 0 299.5 -80t217.5 -218t80 -300t-80 -299.5t-217.5 -217.5t-299.5 -80t-300 80t-218 217.5t-80 299.5zM298 701l2 -201h300l-2 -194l402 294l-402 298v-197h-300z' /%3E %3Cglyph unicode='&%23xe132;' d='M0 597q0 122 47.5 232.5t127.5 190.5t190.5 127.5t231.5 47.5q122 0 232.5 -47.5t190.5 -127.5t127.5 -190.5t47.5 -232.5q0 -162 -80 -299.5t-218 -217.5t-300 -80t-299.5 80t-217.5 217.5t-80 299.5zM200 600l402 -294l-2 194h300l2 201h-300v197z' /%3E %3Cglyph unicode='&%23xe133;' d='M5 597q0 122 47.5 232.5t127.5 190.5t190.5 127.5t232.5 47.5q162 0 299.5 -80t217.5 -218t80 -300t-80 -299.5t-217.5 -217.5t-299.5 -80t-300 80t-218 217.5t-80 299.5zM300 600h200v-300h200v300h200l-300 400z' /%3E %3Cglyph unicode='&%23xe134;' d='M5 597q0 122 47.5 232.5t127.5 190.5t190.5 127.5t232.5 47.5q162 0 299.5 -80t217.5 -218t80 -300t-80 -299.5t-217.5 -217.5t-299.5 -80t-300 80t-218 217.5t-80 299.5zM300 600l300 -400l300 400h-200v300h-200v-300h-200z' /%3E %3Cglyph unicode='&%23xe135;' d='M5 597q0 122 47.5 232.5t127.5 190.5t190.5 127.5t232.5 47.5q121 0 231.5 -47.5t190.5 -127.5t127.5 -190.5t47.5 -232.5q0 -162 -80 -299.5t-217.5 -217.5t-299.5 -80t-300 80t-218 217.5t-80 299.5zM254 780q-8 -33 5.5 -92.5t7.5 -87.5q0 -9 17 -44t16 -60 q12 0 23 -5.5t23 -15t20 -13.5q24 -12 108 -42q22 -8 53 -31.5t59.5 -38.5t57.5 -11q8 -18 -15 -55t-20 -57q42 -71 87 -80q0 -6 -3 -15.5t-3.5 -14.5t4.5 -17q104 -3 221 112q30 29 47 47t34.5 49t20.5 62q-14 9 -37 9.5t-36 7.5q-14 7 -49 15t-52 19q-9 0 -39.5 -0.5 t-46.5 -1.5t-39 -6.5t-39 -16.5q-50 -35 -66 -12q-4 2 -3.5 25.5t0.5 25.5q-6 13 -26.5 17t-24.5 7q2 22 -2 41t-16.5 28t-38.5 -20q-23 -25 -42 4q-19 28 -8 58q6 16 22 22q6 -1 26 -1.5t33.5 -4t19.5 -13.5q12 -19 32 -37.5t34 -27.5l14 -8q0 3 9.5 39.5t5.5 57.5 q-4 23 14.5 44.5t22.5 31.5q5 14 10 35t8.5 31t15.5 22.5t34 21.5q-6 18 10 37q8 0 23.5 -1.5t24.5 -1.5t20.5 4.5t20.5 15.5q-10 23 -30.5 42.5t-38 30t-49 26.5t-43.5 23q11 39 2 44q31 -13 58 -14.5t39 3.5l11 4q7 36 -16.5 53.5t-64.5 28.5t-56 23q-19 -3 -37 0 q-15 -12 -36.5 -21t-34.5 -12t-44 -8t-39 -6q-15 -3 -45.5 0.5t-45.5 -2.5q-21 -7 -52 -26.5t-34 -34.5q-3 -11 6.5 -22.5t8.5 -18.5q-3 -34 -27.5 -90.5t-29.5 -79.5zM518 916q3 12 16 30t16 25q10 -10 18.5 -10t14 6t14.5 14.5t16 12.5q0 -24 17 -66.5t17 -43.5 q-9 2 -31 5t-36 5t-32 8t-30 14zM692 1003h1h-1z' /%3E %3Cglyph unicode='&%23xe136;' d='M0 164.5q0 21.5 15 37.5l600 599q-33 101 6 201.5t135 154.5q164 92 306 -9l-259 -138l145 -232l251 126q13 -175 -151 -267q-123 -70 -253 -23l-596 -596q-15 -16 -36.5 -16t-36.5 16l-111 110q-15 15 -15 36.5z' /%3E %3Cglyph unicode='&%23xe137;' horiz-adv-x='1220' d='M0 196v100q0 41 29.5 70.5t70.5 29.5h1000q41 0 70.5 -29.5t29.5 -70.5v-100q0 -41 -29.5 -70.5t-70.5 -29.5h-1000q-41 0 -70.5 29.5t-29.5 70.5zM0 596v100q0 41 29.5 70.5t70.5 29.5h1000q41 0 70.5 -29.5t29.5 -70.5v-100q0 -41 -29.5 -70.5t-70.5 -29.5h-1000 q-41 0 -70.5 29.5t-29.5 70.5zM0 996v100q0 41 29.5 70.5t70.5 29.5h1000q41 0 70.5 -29.5t29.5 -70.5v-100q0 -41 -29.5 -70.5t-70.5 -29.5h-1000q-41 0 -70.5 29.5t-29.5 70.5zM600 596h500v100h-500v-100zM800 196h300v100h-300v-100zM900 996h200v100h-200v-100z' /%3E %3Cglyph unicode='&%23xe138;' d='M100 1100v100h1000v-100h-1000zM150 1000h900l-350 -500v-300l-200 -200v500z' /%3E %3Cglyph unicode='&%23xe139;' d='M0 200v200h1200v-200q0 -41 -29.5 -70.5t-70.5 -29.5h-1000q-41 0 -70.5 29.5t-29.5 70.5zM0 500v400q0 41 29.5 70.5t70.5 29.5h300v100q0 41 29.5 70.5t70.5 29.5h200q41 0 70.5 -29.5t29.5 -70.5v-100h300q41 0 70.5 -29.5t29.5 -70.5v-400h-500v100h-200v-100h-500z M500 1000h200v100h-200v-100z' /%3E %3Cglyph unicode='&%23xe140;' d='M0 0v400l129 -129l200 200l142 -142l-200 -200l129 -129h-400zM0 800l129 129l200 -200l142 142l-200 200l129 129h-400v-400zM729 329l142 142l200 -200l129 129v-400h-400l129 129zM729 871l200 200l-129 129h400v-400l-129 129l-200 -200z' /%3E %3Cglyph unicode='&%23xe141;' d='M0 596q0 162 80 299t217 217t299 80t299 -80t217 -217t80 -299t-80 -299t-217 -217t-299 -80t-299 80t-217 217t-80 299zM182 596q0 -172 121.5 -293t292.5 -121t292.5 121t121.5 293q0 171 -121.5 292.5t-292.5 121.5t-292.5 -121.5t-121.5 -292.5zM291 655 q0 23 15.5 38.5t38.5 15.5t39 -16t16 -38q0 -23 -16 -39t-39 -16q-22 0 -38 16t-16 39zM400 850q0 22 16 38.5t39 16.5q22 0 38 -16t16 -39t-16 -39t-38 -16q-23 0 -39 16.5t-16 38.5zM514 609q0 32 20.5 56.5t51.5 29.5l122 126l1 1q-9 14 -9 28q0 22 16 38.5t39 16.5 q22 0 38 -16t16 -39t-16 -39t-38 -16q-14 0 -29 10l-55 -145q17 -22 17 -51q0 -36 -25.5 -61.5t-61.5 -25.5t-61.5 25.5t-25.5 61.5zM800 655q0 22 16 38t39 16t38.5 -15.5t15.5 -38.5t-16 -39t-38 -16q-23 0 -39 16t-16 39z' /%3E %3Cglyph unicode='&%23xe142;' d='M-40 375q-13 -95 35 -173q35 -57 94 -89t129 -32q63 0 119 28q33 16 65 40.5t52.5 45.5t59.5 64q40 44 57 61l394 394q35 35 47 84t-3 96q-27 87 -117 104q-20 2 -29 2q-46 0 -78.5 -16.5t-67.5 -51.5l-389 -396l-7 -7l69 -67l377 373q20 22 39 38q23 23 50 23 q38 0 53 -36q16 -39 -20 -75l-547 -547q-52 -52 -125 -52q-55 0 -100 33t-54 96q-5 35 2.5 66t31.5 63t42 50t56 54q24 21 44 41l348 348q52 52 82.5 79.5t84 54t107.5 26.5q25 0 48 -4q95 -17 154 -94.5t51 -175.5q-7 -101 -98 -192l-252 -249l-253 -256l7 -7l69 -60 l517 511q67 67 95 157t11 183q-16 87 -67 154t-130 103q-69 33 -152 33q-107 0 -197 -55q-40 -24 -111 -95l-512 -512q-68 -68 -81 -163z' /%3E %3Cglyph unicode='&%23xe143;' d='M80 784q0 131 98.5 229.5t230.5 98.5q143 0 241 -129q103 129 246 129q129 0 226 -98.5t97 -229.5q0 -46 -17.5 -91t-61 -99t-77 -89.5t-104.5 -105.5q-197 -191 -293 -322l-17 -23l-16 23q-43 58 -100 122.5t-92 99.5t-101 100q-71 70 -104.5 105.5t-77 89.5t-61 99 t-17.5 91zM250 784q0 -27 30.5 -70t61.5 -75.5t95 -94.5l22 -22q93 -90 190 -201q82 92 195 203l12 12q64 62 97.5 97t64.5 79t31 72q0 71 -48 119.5t-105 48.5q-74 0 -132 -83l-118 -171l-114 174q-51 80 -123 80q-60 0 -109.5 -49.5t-49.5 -118.5z' /%3E %3Cglyph unicode='&%23xe144;' d='M57 353q0 -95 66 -159l141 -142q68 -66 159 -66q93 0 159 66l283 283q66 66 66 159t-66 159l-141 141q-8 9 -19 17l-105 -105l212 -212l-389 -389l-247 248l95 95l-18 18q-46 45 -75 101l-55 -55q-66 -66 -66 -159zM269 706q0 -93 66 -159l141 -141q7 -7 19 -17l105 105 l-212 212l389 389l247 -247l-95 -96l18 -17q47 -49 77 -100l29 29q35 35 62.5 88t27.5 96q0 93 -66 159l-141 141q-66 66 -159 66q-95 0 -159 -66l-283 -283q-66 -64 -66 -159z' /%3E %3Cglyph unicode='&%23xe145;' d='M200 100v953q0 21 30 46t81 48t129 38t163 15t162 -15t127 -38t79 -48t29 -46v-953q0 -41 -29.5 -70.5t-70.5 -29.5h-600q-41 0 -70.5 29.5t-29.5 70.5zM300 300h600v700h-600v-700zM496 150q0 -43 30.5 -73.5t73.5 -30.5t73.5 30.5t30.5 73.5t-30.5 73.5t-73.5 30.5 t-73.5 -30.5t-30.5 -73.5z' /%3E %3Cglyph unicode='&%23xe146;' d='M0 0l303 380l207 208l-210 212h300l267 279l-35 36q-15 14 -15 35t15 35q14 15 35 15t35 -15l283 -282q15 -15 15 -36t-15 -35q-14 -15 -35 -15t-35 15l-36 35l-279 -267v-300l-212 210l-208 -207z' /%3E %3Cglyph unicode='&%23xe148;' d='M295 433h139q5 -77 48.5 -126.5t117.5 -64.5v335q-6 1 -15.5 4t-11.5 3q-46 14 -79 26.5t-72 36t-62.5 52t-40 72.5t-16.5 99q0 92 44 159.5t109 101t144 40.5v78h100v-79q38 -4 72.5 -13.5t75.5 -31.5t71 -53.5t51.5 -84t24.5 -118.5h-159q-8 72 -35 109.5t-101 50.5 v-307l64 -14q34 -7 64 -16.5t70 -31.5t67.5 -52t47.5 -80.5t20 -112.5q0 -139 -89 -224t-244 -96v-77h-100v78q-152 17 -237 104q-40 40 -52.5 93.5t-15.5 139.5zM466 889q0 -29 8 -51t16.5 -34t29.5 -22.5t31 -13.5t38 -10q7 -2 11 -3v274q-61 -8 -97.5 -37.5t-36.5 -102.5 zM700 237q170 18 170 151q0 64 -44 99.5t-126 60.5v-311z' /%3E %3Cglyph unicode='&%23xe149;' d='M100 600v100h166q-24 49 -44 104q-10 26 -14.5 55.5t-3 72.5t25 90t68.5 87q97 88 263 88q129 0 230 -89t101 -208h-153q0 52 -34 89.5t-74 51.5t-76 14q-37 0 -79 -14.5t-62 -35.5q-41 -44 -41 -101q0 -28 16.5 -69.5t28 -62.5t41.5 -72h241v-100h-197q8 -50 -2.5 -115 t-31.5 -94q-41 -59 -99 -113q35 11 84 18t70 7q33 1 103 -16t103 -17q76 0 136 30l50 -147q-41 -25 -80.5 -36.5t-59 -13t-61.5 -1.5q-23 0 -128 33t-155 29q-39 -4 -82 -17t-66 -25l-24 -11l-55 145l16.5 11t15.5 10t13.5 9.5t14.5 12t14.5 14t17.5 18.5q48 55 54 126.5 t-30 142.5h-221z' /%3E %3Cglyph unicode='&%23xe150;' d='M2 300l298 -300l298 300h-198v900h-200v-900h-198zM602 900l298 300l298 -300h-198v-900h-200v900h-198z' /%3E %3Cglyph unicode='&%23xe151;' d='M2 300h198v900h200v-900h198l-298 -300zM700 0v200h100v-100h200v-100h-300zM700 400v100h300v-200h-99v-100h-100v100h99v100h-200zM700 700v500h300v-500h-100v100h-100v-100h-100zM801 900h100v200h-100v-200z' /%3E %3Cglyph unicode='&%23xe152;' d='M2 300h198v900h200v-900h198l-298 -300zM700 0v500h300v-500h-100v100h-100v-100h-100zM700 700v200h100v-100h200v-100h-300zM700 1100v100h300v-200h-99v-100h-100v100h99v100h-200zM801 200h100v200h-100v-200z' /%3E %3Cglyph unicode='&%23xe153;' d='M2 300l298 -300l298 300h-198v900h-200v-900h-198zM800 100v400h300v-500h-100v100h-200zM800 1100v100h200v-500h-100v400h-100zM901 200h100v200h-100v-200z' /%3E %3Cglyph unicode='&%23xe154;' d='M2 300l298 -300l298 300h-198v900h-200v-900h-198zM800 400v100h200v-500h-100v400h-100zM800 800v400h300v-500h-100v100h-200zM901 900h100v200h-100v-200z' /%3E %3Cglyph unicode='&%23xe155;' d='M2 300l298 -300l298 300h-198v900h-200v-900h-198zM700 100v200h500v-200h-500zM700 400v200h400v-200h-400zM700 700v200h300v-200h-300zM700 1000v200h200v-200h-200z' /%3E %3Cglyph unicode='&%23xe156;' d='M2 300l298 -300l298 300h-198v900h-200v-900h-198zM700 100v200h200v-200h-200zM700 400v200h300v-200h-300zM700 700v200h400v-200h-400zM700 1000v200h500v-200h-500z' /%3E %3Cglyph unicode='&%23xe157;' d='M0 400v300q0 165 117.5 282.5t282.5 117.5h300q162 0 281 -118.5t119 -281.5v-300q0 -165 -118.5 -282.5t-281.5 -117.5h-300q-165 0 -282.5 117.5t-117.5 282.5zM200 300q0 -41 29.5 -70.5t70.5 -29.5h500q41 0 70.5 29.5t29.5 70.5v500q0 41 -29.5 70.5t-70.5 29.5 h-500q-41 0 -70.5 -29.5t-29.5 -70.5v-500z' /%3E %3Cglyph unicode='&%23xe158;' d='M0 400v300q0 163 119 281.5t281 118.5h300q165 0 282.5 -117.5t117.5 -282.5v-300q0 -165 -117.5 -282.5t-282.5 -117.5h-300q-163 0 -281.5 117.5t-118.5 282.5zM200 300q0 -41 29.5 -70.5t70.5 -29.5h500q41 0 70.5 29.5t29.5 70.5v500q0 41 -29.5 70.5t-70.5 29.5 h-500q-41 0 -70.5 -29.5t-29.5 -70.5v-500zM400 300l333 250l-333 250v-500z' /%3E %3Cglyph unicode='&%23xe159;' d='M0 400v300q0 163 117.5 281.5t282.5 118.5h300q163 0 281.5 -119t118.5 -281v-300q0 -165 -117.5 -282.5t-282.5 -117.5h-300q-165 0 -282.5 117.5t-117.5 282.5zM200 300q0 -41 29.5 -70.5t70.5 -29.5h500q41 0 70.5 29.5t29.5 70.5v500q0 41 -29.5 70.5t-70.5 29.5 h-500q-41 0 -70.5 -29.5t-29.5 -70.5v-500zM300 700l250 -333l250 333h-500z' /%3E %3Cglyph unicode='&%23xe160;' d='M0 400v300q0 165 117.5 282.5t282.5 117.5h300q165 0 282.5 -117.5t117.5 -282.5v-300q0 -162 -118.5 -281t-281.5 -119h-300q-165 0 -282.5 118.5t-117.5 281.5zM200 300q0 -41 29.5 -70.5t70.5 -29.5h500q41 0 70.5 29.5t29.5 70.5v500q0 41 -29.5 70.5t-70.5 29.5 h-500q-41 0 -70.5 -29.5t-29.5 -70.5v-500zM300 400h500l-250 333z' /%3E %3Cglyph unicode='&%23xe161;' d='M0 400v300h300v200l400 -350l-400 -350v200h-300zM500 0v200h500q41 0 70.5 29.5t29.5 70.5v500q0 41 -29.5 70.5t-70.5 29.5h-500v200h400q165 0 282.5 -117.5t117.5 -282.5v-300q0 -165 -117.5 -282.5t-282.5 -117.5h-400z' /%3E %3Cglyph unicode='&%23xe162;' d='M217 519q8 -19 31 -19h302q-155 -438 -160 -458q-5 -21 4 -32l9 -8h9q14 0 26 15q11 13 274.5 321.5t264.5 308.5q14 19 5 36q-8 17 -31 17l-301 -1q1 4 78 219.5t79 227.5q2 15 -5 27l-9 9h-9q-15 0 -25 -16q-4 -6 -98 -111.5t-228.5 -257t-209.5 -237.5q-16 -19 -6 -41 z' /%3E %3Cglyph unicode='&%23xe163;' d='M0 400q0 -165 117.5 -282.5t282.5 -117.5h300q47 0 100 15v185h-500q-41 0 -70.5 29.5t-29.5 70.5v500q0 41 29.5 70.5t70.5 29.5h500v185q-14 4 -114 7.5t-193 5.5l-93 2q-165 0 -282.5 -117.5t-117.5 -282.5v-300zM600 400v300h300v200l400 -350l-400 -350v200h-300z ' /%3E %3Cglyph unicode='&%23xe164;' d='M0 400q0 -165 117.5 -282.5t282.5 -117.5h300q163 0 281.5 117.5t118.5 282.5v98l-78 73l-122 -123v-148q0 -41 -29.5 -70.5t-70.5 -29.5h-500q-41 0 -70.5 29.5t-29.5 70.5v500q0 41 29.5 70.5t70.5 29.5h156l118 122l-74 78h-100q-165 0 -282.5 -117.5t-117.5 -282.5 v-300zM496 709l353 342l-149 149h500v-500l-149 149l-342 -353z' /%3E %3Cglyph unicode='&%23xe165;' d='M4 600q0 162 80 299t217 217t299 80t299 -80t217 -217t80 -299t-80 -299t-217 -217t-299 -80t-299 80t-217 217t-80 299zM186 600q0 -171 121.5 -292.5t292.5 -121.5t292.5 121.5t121.5 292.5t-121.5 292.5t-292.5 121.5t-292.5 -121.5t-121.5 -292.5zM406 600 q0 80 57 137t137 57t137 -57t57 -137t-57 -137t-137 -57t-137 57t-57 137z' /%3E %3Cglyph unicode='&%23xe166;' d='M0 0v275q0 11 7 18t18 7h1048q11 0 19 -7.5t8 -17.5v-275h-1100zM100 800l445 -500l450 500h-295v400h-300v-400h-300zM900 150h100v50h-100v-50z' /%3E %3Cglyph unicode='&%23xe167;' d='M0 0v275q0 11 7 18t18 7h1048q11 0 19 -7.5t8 -17.5v-275h-1100zM100 700h300v-300h300v300h295l-445 500zM900 150h100v50h-100v-50z' /%3E %3Cglyph unicode='&%23xe168;' d='M0 0v275q0 11 7 18t18 7h1048q11 0 19 -7.5t8 -17.5v-275h-1100zM100 705l305 -305l596 596l-154 155l-442 -442l-150 151zM900 150h100v50h-100v-50z' /%3E %3Cglyph unicode='&%23xe169;' d='M0 0v275q0 11 7 18t18 7h1048q11 0 19 -7.5t8 -17.5v-275h-1100zM100 988l97 -98l212 213l-97 97zM200 400l697 1l3 699l-250 -239l-149 149l-212 -212l149 -149zM900 150h100v50h-100v-50z' /%3E %3Cglyph unicode='&%23xe170;' d='M0 0v275q0 11 7 18t18 7h1048q11 0 19 -7.5t8 -17.5v-275h-1100zM200 612l212 -212l98 97l-213 212zM300 1200l239 -250l-149 -149l212 -212l149 148l249 -237l-1 697zM900 150h100v50h-100v-50z' /%3E %3Cglyph unicode='&%23xe171;' d='M23 415l1177 784v-1079l-475 272l-310 -393v416h-392zM494 210l672 938l-672 -712v-226z' /%3E %3Cglyph unicode='&%23xe172;' d='M0 150v1000q0 20 14.5 35t35.5 15h250v-300h500v300h100l200 -200v-850q0 -21 -15 -35.5t-35 -14.5h-150v400h-700v-400h-150q-21 0 -35.5 14.5t-14.5 35.5zM600 1000h100v200h-100v-200z' /%3E %3Cglyph unicode='&%23xe173;' d='M0 150v1000q0 20 14.5 35t35.5 15h250v-300h500v300h100l200 -200v-218l-276 -275l-120 120l-126 -127h-378v-400h-150q-21 0 -35.5 14.5t-14.5 35.5zM581 306l123 123l120 -120l353 352l123 -123l-475 -476zM600 1000h100v200h-100v-200z' /%3E %3Cglyph unicode='&%23xe174;' d='M0 150v1000q0 20 14.5 35t35.5 15h250v-300h500v300h100l200 -200v-269l-103 -103l-170 170l-298 -298h-329v-400h-150q-21 0 -35.5 14.5t-14.5 35.5zM600 1000h100v200h-100v-200zM700 133l170 170l-170 170l127 127l170 -170l170 170l127 -128l-170 -169l170 -170 l-127 -127l-170 170l-170 -170z' /%3E %3Cglyph unicode='&%23xe175;' d='M0 150v1000q0 20 14.5 35t35.5 15h250v-300h500v300h100l200 -200v-300h-400v-200h-500v-400h-150q-21 0 -35.5 14.5t-14.5 35.5zM600 300l300 -300l300 300h-200v300h-200v-300h-200zM600 1000v200h100v-200h-100z' /%3E %3Cglyph unicode='&%23xe176;' d='M0 150v1000q0 20 14.5 35t35.5 15h250v-300h500v300h100l200 -200v-402l-200 200l-298 -298h-402v-400h-150q-21 0 -35.5 14.5t-14.5 35.5zM600 300h200v-300h200v300h200l-300 300zM600 1000v200h100v-200h-100z' /%3E %3Cglyph unicode='&%23xe177;' d='M0 250q0 -21 14.5 -35.5t35.5 -14.5h1100q21 0 35.5 14.5t14.5 35.5v550h-1200v-550zM0 900h1200v150q0 21 -14.5 35.5t-35.5 14.5h-1100q-21 0 -35.5 -14.5t-14.5 -35.5v-150zM100 300v200h400v-200h-400z' /%3E %3Cglyph unicode='&%23xe178;' d='M0 400l300 298v-198h400v-200h-400v-198zM100 800v200h100v-200h-100zM300 800v200h100v-200h-100zM500 800v200h400v198l300 -298l-300 -298v198h-400zM800 300v200h100v-200h-100zM1000 300h100v200h-100v-200z' /%3E %3Cglyph unicode='&%23xe179;' d='M100 700v400l50 100l50 -100v-300h100v300l50 100l50 -100v-300h100v300l50 100l50 -100v-400l-100 -203v-447q0 -21 -14.5 -35.5t-35.5 -14.5h-200q-21 0 -35.5 14.5t-14.5 35.5v447zM800 597q0 -29 10.5 -55.5t25 -43t29 -28.5t25.5 -18l10 -5v-397q0 -21 14.5 -35.5 t35.5 -14.5h200q21 0 35.5 14.5t14.5 35.5v1106q0 31 -18 40.5t-44 -7.5l-276 -116q-25 -17 -43.5 -51.5t-18.5 -65.5v-359z' /%3E %3Cglyph unicode='&%23xe180;' d='M100 0h400v56q-75 0 -87.5 6t-12.5 44v394h500v-394q0 -38 -12.5 -44t-87.5 -6v-56h400v56q-4 0 -11 0.5t-24 3t-30 7t-24 15t-11 24.5v888q0 22 25 34.5t50 13.5l25 2v56h-400v-56q75 0 87.5 -6t12.5 -44v-394h-500v394q0 38 12.5 44t87.5 6v56h-400v-56q4 0 11 -0.5 t24 -3t30 -7t24 -15t11 -24.5v-888q0 -22 -25 -34.5t-50 -13.5l-25 -2v-56z' /%3E %3Cglyph unicode='&%23xe181;' d='M0 300q0 -41 29.5 -70.5t70.5 -29.5h300q41 0 70.5 29.5t29.5 70.5v500q0 41 -29.5 70.5t-70.5 29.5h-300q-41 0 -70.5 -29.5t-29.5 -70.5v-500zM100 100h400l200 200h105l295 98v-298h-425l-100 -100h-375zM100 300v200h300v-200h-300zM100 600v200h300v-200h-300z M100 1000h400l200 -200v-98l295 98h105v200h-425l-100 100h-375zM700 402v163l400 133v-163z' /%3E %3Cglyph unicode='&%23xe182;' d='M16.5 974.5q0.5 -21.5 16 -90t46.5 -140t104 -177.5t175 -208q103 -103 207.5 -176t180 -103.5t137 -47t92.5 -16.5l31 1l163 162q17 18 13.5 41t-22.5 37l-192 136q-19 14 -45 12t-42 -19l-118 -118q-142 101 -268 227t-227 268l118 118q17 17 20 41.5t-11 44.5 l-139 194q-14 19 -36.5 22t-40.5 -14l-162 -162q-1 -11 -0.5 -32.5z' /%3E %3Cglyph unicode='&%23xe183;' d='M0 50v212q0 20 10.5 45.5t24.5 39.5l365 303v50q0 4 1 10.5t12 22.5t30 28.5t60 23t97 10.5t97 -10t60 -23.5t30 -27.5t12 -24l1 -10v-50l365 -303q14 -14 24.5 -39.5t10.5 -45.5v-212q0 -21 -14.5 -35.5t-35.5 -14.5h-1100q-20 0 -35 14.5t-15 35.5zM0 712 q0 -21 14.5 -33.5t34.5 -8.5l202 33q20 4 34.5 21t14.5 38v146q141 24 300 24t300 -24v-146q0 -21 14.5 -38t34.5 -21l202 -33q20 -4 34.5 8.5t14.5 33.5v200q-6 8 -19 20.5t-63 45t-112 57t-171 45t-235 20.5q-92 0 -175 -10.5t-141.5 -27t-108.5 -36.5t-81.5 -40 t-53.5 -36.5t-31 -27.5l-9 -10v-200z' /%3E %3Cglyph unicode='&%23xe184;' d='M100 0v100h1100v-100h-1100zM175 200h950l-125 150v250l100 100v400h-100v-200h-100v200h-200v-200h-100v200h-200v-200h-100v200h-100v-400l100 -100v-250z' /%3E %3Cglyph unicode='&%23xe185;' d='M100 0h300v400q0 41 -29.5 70.5t-70.5 29.5h-100q-41 0 -70.5 -29.5t-29.5 -70.5v-400zM500 0v1000q0 41 29.5 70.5t70.5 29.5h100q41 0 70.5 -29.5t29.5 -70.5v-1000h-300zM900 0v700q0 41 29.5 70.5t70.5 29.5h100q41 0 70.5 -29.5t29.5 -70.5v-700h-300z' /%3E %3Cglyph unicode='&%23xe186;' d='M-100 300v500q0 124 88 212t212 88h700q124 0 212 -88t88 -212v-500q0 -124 -88 -212t-212 -88h-700q-124 0 -212 88t-88 212zM100 200h900v700h-900v-700zM200 300h300v300h-200v100h200v100h-300v-300h200v-100h-200v-100zM600 300h200v100h100v300h-100v100h-200v-500 zM700 400v300h100v-300h-100z' /%3E %3Cglyph unicode='&%23xe187;' d='M-100 300v500q0 124 88 212t212 88h700q124 0 212 -88t88 -212v-500q0 -124 -88 -212t-212 -88h-700q-124 0 -212 88t-88 212zM100 200h900v700h-900v-700zM200 300h100v200h100v-200h100v500h-100v-200h-100v200h-100v-500zM600 300h200v100h100v300h-100v100h-200v-500 zM700 400v300h100v-300h-100z' /%3E %3Cglyph unicode='&%23xe188;' d='M-100 300v500q0 124 88 212t212 88h700q124 0 212 -88t88 -212v-500q0 -124 -88 -212t-212 -88h-700q-124 0 -212 88t-88 212zM100 200h900v700h-900v-700zM200 300h300v100h-200v300h200v100h-300v-500zM600 300h300v100h-200v300h200v100h-300v-500z' /%3E %3Cglyph unicode='&%23xe189;' d='M-100 300v500q0 124 88 212t212 88h700q124 0 212 -88t88 -212v-500q0 -124 -88 -212t-212 -88h-700q-124 0 -212 88t-88 212zM100 200h900v700h-900v-700zM200 550l300 -150v300zM600 400l300 150l-300 150v-300z' /%3E %3Cglyph unicode='&%23xe190;' d='M-100 300v500q0 124 88 212t212 88h700q124 0 212 -88t88 -212v-500q0 -124 -88 -212t-212 -88h-700q-124 0 -212 88t-88 212zM100 200h900v700h-900v-700zM200 300v500h700v-500h-700zM300 400h130q41 0 68 42t27 107t-28.5 108t-66.5 43h-130v-300zM575 549 q0 -65 27 -107t68 -42h130v300h-130q-38 0 -66.5 -43t-28.5 -108z' /%3E %3Cglyph unicode='&%23xe191;' d='M-100 300v500q0 124 88 212t212 88h700q124 0 212 -88t88 -212v-500q0 -124 -88 -212t-212 -88h-700q-124 0 -212 88t-88 212zM100 200h900v700h-900v-700zM200 300h300v300h-200v100h200v100h-300v-300h200v-100h-200v-100zM601 300h100v100h-100v-100zM700 700h100 v-400h100v500h-200v-100z' /%3E %3Cglyph unicode='&%23xe192;' d='M-100 300v500q0 124 88 212t212 88h700q124 0 212 -88t88 -212v-500q0 -124 -88 -212t-212 -88h-700q-124 0 -212 88t-88 212zM100 200h900v700h-900v-700zM200 300h300v400h-200v100h-100v-500zM301 400v200h100v-200h-100zM601 300h100v100h-100v-100zM700 700h100 v-400h100v500h-200v-100z' /%3E %3Cglyph unicode='&%23xe193;' d='M-100 300v500q0 124 88 212t212 88h700q124 0 212 -88t88 -212v-500q0 -124 -88 -212t-212 -88h-700q-124 0 -212 88t-88 212zM100 200h900v700h-900v-700zM200 700v100h300v-300h-99v-100h-100v100h99v200h-200zM201 300v100h100v-100h-100zM601 300v100h100v-100h-100z M700 700v100h200v-500h-100v400h-100z' /%3E %3Cglyph unicode='&%23xe194;' d='M4 600q0 162 80 299t217 217t299 80t299 -80t217 -217t80 -299t-80 -299t-217 -217t-299 -80t-299 80t-217 217t-80 299zM186 600q0 -171 121.5 -292.5t292.5 -121.5t292.5 121.5t121.5 292.5t-121.5 292.5t-292.5 121.5t-292.5 -121.5t-121.5 -292.5zM400 500v200 l100 100h300v-100h-300v-200h300v-100h-300z' /%3E %3Cglyph unicode='&%23xe195;' d='M0 600q0 162 80 299t217 217t299 80t299 -80t217 -217t80 -299t-80 -299t-217 -217t-299 -80t-299 80t-217 217t-80 299zM182 600q0 -171 121.5 -292.5t292.5 -121.5t292.5 121.5t121.5 292.5t-121.5 292.5t-292.5 121.5t-292.5 -121.5t-121.5 -292.5zM400 400v400h300 l100 -100v-100h-100v100h-200v-100h200v-100h-200v-100h-100zM700 400v100h100v-100h-100z' /%3E %3Cglyph unicode='&%23xe197;' d='M-14 494q0 -80 56.5 -137t135.5 -57h222v300h400v-300h128q120 0 205 86.5t85 207.5t-85 207t-205 86q-46 0 -90 -14q-44 97 -134.5 156.5t-200.5 59.5q-152 0 -260 -107.5t-108 -260.5q0 -25 2 -37q-66 -14 -108.5 -67.5t-42.5 -122.5zM300 200h200v300h200v-300h200 l-300 -300z' /%3E %3Cglyph unicode='&%23xe198;' d='M-14 494q0 -80 56.5 -137t135.5 -57h8l414 414l403 -403q94 26 154.5 104.5t60.5 178.5q0 120 -85 206.5t-205 86.5q-46 0 -90 -14q-44 97 -134.5 156.5t-200.5 59.5q-152 0 -260 -107.5t-108 -260.5q0 -25 2 -37q-66 -14 -108.5 -67.5t-42.5 -122.5zM300 200l300 300 l300 -300h-200v-300h-200v300h-200z' /%3E %3Cglyph unicode='&%23xe199;' d='M100 200h400v-155l-75 -45h350l-75 45v155h400l-270 300h170l-270 300h170l-300 333l-300 -333h170l-270 -300h170z' /%3E %3Cglyph unicode='&%23xe200;' d='M121 700q0 -53 28.5 -97t75.5 -65q-4 -16 -4 -38q0 -74 52.5 -126.5t126.5 -52.5q56 0 100 30v-306l-75 -45h350l-75 45v306q46 -30 100 -30q74 0 126.5 52.5t52.5 126.5q0 24 -9 55q50 32 79.5 83t29.5 112q0 90 -61.5 155.5t-150.5 71.5q-26 89 -99.5 145.5 t-167.5 56.5q-116 0 -197.5 -81.5t-81.5 -197.5q0 -4 1 -11.5t1 -11.5q-14 2 -23 2q-74 0 -126.5 -52.5t-52.5 -126.5z' /%3E %3C/font%3E %3C/defs%3E%3C/svg%3E\"#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"}*,:after,:before{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}html{font-size:10px;-webkit-tap-highlight-color:rgba(0,0,0,0)}body{font-family:Helvetica Neue,Helvetica,Arial,sans-serif;font-size:14px;line-height:1.42857143;color:#333;background-color:#fff}button,input,select,textarea{font-family:inherit;font-size:inherit;line-height:inherit}a{color:#428bca;text-decoration:none}a:focus,a:hover{color:#2a6496;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}.carousel-inner>.item>a>img,.carousel-inner>.item>img,.img-responsive,.thumbnail>img,.thumbnail a>img{display:block;width:100%\9;max-width:100%;height:auto}.img-rounded{border-radius:6px}.img-thumbnail{display:inline-block;width:100%\9;max-width:100%;height:auto;padding:4px;line-height:1.42857143;background-color:#fff;border:1px solid #ddd;border-radius:4px;-webkit-transition:all .2s ease-in-out;-o-transition:all .2s ease-in-out;transition:all .2s ease-in-out}.img-circle{border-radius:50%}hr{margin-top:20px;margin-bottom:20px;border:0;border-top:1px solid #eee}.sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);border:0}.sr-only-focusable:active,.sr-only-focusable:focus{position:static;width:auto;height:auto;margin:0;overflow:visible;clip:auto}.h1,.h2,.h3,.h4,.h5,.h6,h1,h2,h3,h4,h5,h6{font-family:inherit;font-weight:500;line-height:1.1;color:inherit}.h1 .small,.h1 small,.h2 .small,.h2 small,.h3 .small,.h3 small,.h4 .small,.h4 small,.h5 .small,.h5 small,.h6 .small,.h6 small,h1 .small,h1 small,h2 .small,h2 small,h3 .small,h3 small,h4 .small,h4 small,h5 .small,h5 small,h6 .small,h6 small{font-weight:400;line-height:1;color:#777}.h1,.h2,.h3,h1,h2,h3{margin-top:20px;margin-bottom:10px}.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,.h5,.h6,h4,h5,h6{margin-top:10px;margin-bottom:10px}.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:36px}.h2,h2{font-size:30px}.h3,h3{font-size:24px}.h4,h4{font-size:18px}.h5,h5{font-size:14px}.h6,h6{font-size:12px}p{margin:0 0 10px}.lead{margin-bottom:20px;font-size:16px;font-weight:300;line-height:1.4}@media (min-width:768px){.lead{font-size:21px}}.small,small{font-size:85%}cite{font-style:normal}.mark,mark{padding:.2em;background-color:#fcf8e3}.text-left{text-align:left}.text-right{text-align:right}.text-center{text-align:center}.text-justify{text-align:justify}.text-nowrap{white-space:nowrap}.text-lowercase{text-transform:lowercase}.text-uppercase{text-transform:uppercase}.text-capitalize{text-transform:capitalize}.text-muted{color:#777}.text-primary{color:#428bca}a.text-primary:hover{color:#3071a9}.text-success{color:#3c763d}a.text-success:hover{color:#2b542c}.text-info{color:#31708f}a.text-info:hover{color:#245269}.text-warning{color:#8a6d3b}a.text-warning:hover{color:#66512c}.text-danger{color:#a94442}a.text-danger:hover{color:#843534}.bg-primary{color:#fff;background-color:#428bca}a.bg-primary:hover{background-color:#3071a9}.bg-success{background-color:#dff0d8}a.bg-success:hover{background-color:#c1e2b3}.bg-info{background-color:#d9edf7}a.bg-info:hover{background-color:#afd9ee}.bg-warning{background-color:#fcf8e3}a.bg-warning:hover{background-color:#f7ecb5}.bg-danger{background-color:#f2dede}a.bg-danger:hover{background-color:#e4b9b9}.page-header{padding-bottom:9px;margin:40px 0 20px;border-bottom:1px solid #eee}ol,ul{margin-top:0;margin-bottom:10px}ol ol,ol ul,ul ol,ul ul{margin-bottom:0}.list-inline,.list-unstyled{padding-left:0;list-style:none}.list-inline{margin-left:-5px}.list-inline>li{display:inline-block;padding-right:5px;padding-left:5px}dl{margin-top:0;margin-bottom:20px}dd,dt{line-height:1.42857143}dt{font-weight:700}dd{margin-left:0}@media (min-width:768px){.dl-horizontal dt{float:left;width:160px;overflow:hidden;clear:left;text-align:right;text-overflow:ellipsis;white-space:nowrap}.dl-horizontal dd{margin-left:180px}}abbr[data-original-title],abbr[title]{cursor:help;border-bottom:1px dotted #777}.initialism{font-size:90%;text-transform:uppercase}blockquote{padding:10px 20px;margin:0 0 20px;font-size:17.5px;border-left:5px solid #eee}blockquote ol:last-child,blockquote p:last-child,blockquote ul:last-child{margin-bottom:0}blockquote .small,blockquote footer,blockquote small{display:block;font-size:80%;line-height:1.42857143;color:#777}blockquote .small:before,blockquote footer:before,blockquote small:before{content:"\2014 \00A0"}.blockquote-reverse,blockquote.pull-right{padding-right:15px;padding-left:0;text-align:right;border-right:5px solid #eee;border-left:0}.blockquote-reverse .small:before,.blockquote-reverse footer:before,.blockquote-reverse small:before,blockquote.pull-right .small:before,blockquote.pull-right footer:before,blockquote.pull-right small:before{content:""}.blockquote-reverse .small:after,.blockquote-reverse footer:after,.blockquote-reverse small:after,blockquote.pull-right .small:after,blockquote.pull-right footer:after,blockquote.pull-right small:after{content:"\00A0 \2014"}blockquote:after,blockquote:before{content:""}address{margin-bottom:20px;font-style:normal;line-height:1.42857143}code,kbd,pre,samp{font-family:Menlo,Monaco,Consolas,Courier New,monospace}code{color:#c7254e;background-color:#f9f2f4;border-radius:4px}code,kbd{padding:2px 4px;font-size:90%}kbd{color:#fff;background-color:#333;border-radius:3px;-webkit-box-shadow:inset 0 -1px 0 rgba(0,0,0,.25);box-shadow:inset 0 -1px 0 rgba(0,0,0,.25)}kbd kbd{padding:0;font-size:100%;-webkit-box-shadow:none;box-shadow:none}pre{display:block;padding:9.5px;margin:0 0 10px;font-size:13px;line-height:1.42857143;color:#333;word-break:break-all;word-wrap:break-word;background-color:#f5f5f5;border:1px solid #ccc;border-radius:4px}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{padding-right:15px;padding-left:15px;margin-right:auto;margin-left:auto}@media (min-width:768px){.container{width:750px}}@media (min-width:992px){.container{width:970px}}@media (min-width:1200px){.container{width:1170px}}.container-fluid{padding-right:15px;padding-left:15px;margin-right:auto;margin-left:auto}.row{margin-right:-15px;margin-left:-15px}.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,.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,.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,.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{position:relative;min-height:1px;padding-right:15px;padding-left: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:auto}.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:auto}.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:auto}.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:auto}.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:auto}.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:auto}.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:auto}.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:auto}.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{background-color:transparent}th{text-align:left}.table{width:100%;max-width:100%;margin-bottom:20px}.table>tbody>tr>td,.table>tbody>tr>th,.table>tfoot>tr>td,.table>tfoot>tr>th,.table>thead>tr>td,.table>thead>tr>th{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>td,.table>caption+thead>tr:first-child>th,.table>colgroup+thead>tr:first-child>td,.table>colgroup+thead>tr:first-child>th,.table>thead:first-child>tr:first-child>td,.table>thead:first-child>tr:first-child>th{border-top:0}.table>tbody+tbody{border-top:2px solid #ddd}.table .table{background-color:#fff}.table-condensed>tbody>tr>td,.table-condensed>tbody>tr>th,.table-condensed>tfoot>tr>td,.table-condensed>tfoot>tr>th,.table-condensed>thead>tr>td,.table-condensed>thead>tr>th{padding:5px}.table-bordered,.table-bordered>tbody>tr>td,.table-bordered>tbody>tr>th,.table-bordered>tfoot>tr>td,.table-bordered>tfoot>tr>th,.table-bordered>thead>tr>td,.table-bordered>thead>tr>th{border:1px solid #ddd}.table-bordered>thead>tr>td,.table-bordered>thead>tr>th{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;display:table-column;float:none}table td[class*=col-],table th[class*=col-]{position:static;display:table-cell;float:none}.table>tbody>tr.active>td,.table>tbody>tr.active>th,.table>tbody>tr>td.active,.table>tbody>tr>th.active,.table>tfoot>tr.active>td,.table>tfoot>tr.active>th,.table>tfoot>tr>td.active,.table>tfoot>tr>th.active,.table>thead>tr.active>td,.table>thead>tr.active>th,.table>thead>tr>td.active,.table>thead>tr>th.active{background-color:#f5f5f5}.table-hover>tbody>tr.active:hover>td,.table-hover>tbody>tr.active:hover>th,.table-hover>tbody>tr:hover>.active,.table-hover>tbody>tr>td.active:hover,.table-hover>tbody>tr>th.active:hover{background-color:#e8e8e8}.table>tbody>tr.success>td,.table>tbody>tr.success>th,.table>tbody>tr>td.success,.table>tbody>tr>th.success,.table>tfoot>tr.success>td,.table>tfoot>tr.success>th,.table>tfoot>tr>td.success,.table>tfoot>tr>th.success,.table>thead>tr.success>td,.table>thead>tr.success>th,.table>thead>tr>td.success,.table>thead>tr>th.success{background-color:#dff0d8}.table-hover>tbody>tr.success:hover>td,.table-hover>tbody>tr.success:hover>th,.table-hover>tbody>tr:hover>.success,.table-hover>tbody>tr>td.success:hover,.table-hover>tbody>tr>th.success:hover{background-color:#d0e9c6}.table>tbody>tr.info>td,.table>tbody>tr.info>th,.table>tbody>tr>td.info,.table>tbody>tr>th.info,.table>tfoot>tr.info>td,.table>tfoot>tr.info>th,.table>tfoot>tr>td.info,.table>tfoot>tr>th.info,.table>thead>tr.info>td,.table>thead>tr.info>th,.table>thead>tr>td.info,.table>thead>tr>th.info{background-color:#d9edf7}.table-hover>tbody>tr.info:hover>td,.table-hover>tbody>tr.info:hover>th,.table-hover>tbody>tr:hover>.info,.table-hover>tbody>tr>td.info:hover,.table-hover>tbody>tr>th.info:hover{background-color:#c4e3f3}.table>tbody>tr.warning>td,.table>tbody>tr.warning>th,.table>tbody>tr>td.warning,.table>tbody>tr>th.warning,.table>tfoot>tr.warning>td,.table>tfoot>tr.warning>th,.table>tfoot>tr>td.warning,.table>tfoot>tr>th.warning,.table>thead>tr.warning>td,.table>thead>tr.warning>th,.table>thead>tr>td.warning,.table>thead>tr>th.warning{background-color:#fcf8e3}.table-hover>tbody>tr.warning:hover>td,.table-hover>tbody>tr.warning:hover>th,.table-hover>tbody>tr:hover>.warning,.table-hover>tbody>tr>td.warning:hover,.table-hover>tbody>tr>th.warning:hover{background-color:#faf2cc}.table>tbody>tr.danger>td,.table>tbody>tr.danger>th,.table>tbody>tr>td.danger,.table>tbody>tr>th.danger,.table>tfoot>tr.danger>td,.table>tfoot>tr.danger>th,.table>tfoot>tr>td.danger,.table>tfoot>tr>th.danger,.table>thead>tr.danger>td,.table>thead>tr.danger>th,.table>thead>tr>td.danger,.table>thead>tr>th.danger{background-color:#f2dede}.table-hover>tbody>tr.danger:hover>td,.table-hover>tbody>tr.danger:hover>th,.table-hover>tbody>tr:hover>.danger,.table-hover>tbody>tr>td.danger:hover,.table-hover>tbody>tr>th.danger:hover{background-color:#ebcccc}@media screen and (max-width:767px){.table-responsive{width:100%;margin-bottom:15px;overflow-x:auto;overflow-y:hidden;-webkit-overflow-scrolling:touch;-ms-overflow-style:-ms-autohiding-scrollbar;border:1px solid #ddd}.table-responsive>.table{margin-bottom:0}.table-responsive>.table>tbody>tr>td,.table-responsive>.table>tbody>tr>th,.table-responsive>.table>tfoot>tr>td,.table-responsive>.table>tfoot>tr>th,.table-responsive>.table>thead>tr>td,.table-responsive>.table>thead>tr>th{white-space:nowrap}.table-responsive>.table-bordered{border:0}.table-responsive>.table-bordered>tbody>tr>td:first-child,.table-responsive>.table-bordered>tbody>tr>th:first-child,.table-responsive>.table-bordered>tfoot>tr>td:first-child,.table-responsive>.table-bordered>tfoot>tr>th:first-child,.table-responsive>.table-bordered>thead>tr>td:first-child,.table-responsive>.table-bordered>thead>tr>th:first-child{border-left:0}.table-responsive>.table-bordered>tbody>tr>td:last-child,.table-responsive>.table-bordered>tbody>tr>th:last-child,.table-responsive>.table-bordered>tfoot>tr>td:last-child,.table-responsive>.table-bordered>tfoot>tr>th:last-child,.table-responsive>.table-bordered>thead>tr>td:last-child,.table-responsive>.table-bordered>thead>tr>th:last-child{border-right:0}.table-responsive>.table-bordered>tbody>tr:last-child>td,.table-responsive>.table-bordered>tbody>tr:last-child>th,.table-responsive>.table-bordered>tfoot>tr:last-child>td,.table-responsive>.table-bordered>tfoot>tr:last-child>th{border-bottom:0}}fieldset{min-width:0;margin:0}fieldset,legend{padding:0;border:0}legend{display:block;width:100%;margin-bottom:20px;font-size:21px;line-height:inherit;color:#333;border-bottom:1px solid #e5e5e5}label{display:inline-block;max-width:100%;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=checkbox],input[type=radio]{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=checkbox]:focus,input[type=file]:focus,input[type=radio]:focus{outline:thin dotted;outline:5px auto -webkit-focus-ring-color;outline-offset:-2px}output{padding-top:7px}.form-control,output{display:block;font-size:14px;line-height:1.42857143;color:#555}.form-control{width:100%;height:34px;padding:6px 12px;background-color:#fff;background-image:none;border:1px solid #ccc;border-radius:4px;-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 .15s ease-in-out,-webkit-box-shadow .15s ease-in-out;-o-transition:border-color ease-in-out .15s,box-shadow ease-in-out .15s;transition:border-color .15s ease-in-out,box-shadow .15s ease-in-out}.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:#777;opacity:1}.form-control:-ms-input-placeholder{color:#777}.form-control::-webkit-input-placeholder{color:#777}.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],input[type=datetime-local],input[type=month],input[type=time]{line-height:34px;line-height:1.42857143 \0}input[type=date].input-sm,input[type=datetime-local].input-sm,input[type=month].input-sm,input[type=time].input-sm{line-height:30px}input[type=date].input-lg,input[type=datetime-local].input-lg,input[type=month].input-lg,input[type=time].input-lg{line-height:46px}.form-group{margin-bottom:15px}.checkbox,.radio{position:relative;display:block;min-height:20px;margin-top:10px;margin-bottom:10px}.checkbox label,.radio label{padding-left:20px;margin-bottom:0;font-weight:400;cursor:pointer}.checkbox-inline input[type=checkbox],.checkbox input[type=checkbox],.radio-inline input[type=radio],.radio input[type=radio]{position:absolute;margin-top:4px\9;margin-left:-20px}.checkbox+.checkbox,.radio+.radio{margin-top:-5px}.checkbox-inline,.radio-inline{display:inline-block;padding-left:20px;margin-bottom:0;font-weight:400;vertical-align:middle;cursor:pointer}.checkbox-inline+.checkbox-inline,.radio-inline+.radio-inline{margin-top:0;margin-left:10px}.checkbox-inline.disabled,.checkbox.disabled label,.radio-inline.disabled,.radio.disabled label,fieldset[disabled] .checkbox-inline,fieldset[disabled] .checkbox label,fieldset[disabled] .radio-inline,fieldset[disabled] .radio label,fieldset[disabled] input[type=checkbox],fieldset[disabled] input[type=radio],input[type=checkbox].disabled,input[type=checkbox][disabled],input[type=radio].disabled,input[type=radio][disabled]{cursor:not-allowed}.form-control-static{padding-top:7px;padding-bottom:7px;margin-bottom:0}.form-control-static.input-lg,.form-control-static.input-sm{padding-right:0;padding-left:0}.form-horizontal .form-group-sm .form-control,.input-sm{height:30px;padding:5px 10px;font-size:12px;line-height:1.5;border-radius:3px}select.input-sm{height:30px;line-height:30px}select[multiple].input-sm,textarea.input-sm{height:auto}.form-horizontal .form-group-lg .form-control,.input-lg{height:46px;padding:10px 16px;font-size:18px;line-height:1.33;border-radius:6px}select.input-lg{height:46px;line-height:46px}select[multiple].input-lg,textarea.input-lg{height:auto}.has-feedback{position:relative}.has-feedback .form-control{padding-right:42.5px}.form-control-feedback{position:absolute;top:25px;right:0;z-index:2;display:block;width:34px;height:34px;line-height:34px;text-align:center}.input-lg+.form-control-feedback{width:46px;height:46px;line-height:46px}.input-sm+.form-control-feedback{width:30px;height:30px;line-height:30px}.has-success .checkbox,.has-success .checkbox-inline,.has-success .control-label,.has-success .help-block,.has-success .radio,.has-success .radio-inline{color:#3c763d}.has-success .form-control{border-color:#3c763d;-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:#2b542c;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 6px #67b168;box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 6px #67b168}.has-success .input-group-addon{color:#3c763d;background-color:#dff0d8;border-color:#3c763d}.has-success .form-control-feedback{color:#3c763d}.has-warning .checkbox,.has-warning .checkbox-inline,.has-warning .control-label,.has-warning .help-block,.has-warning .radio,.has-warning .radio-inline{color:#8a6d3b}.has-warning .form-control{border-color:#8a6d3b;-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:#66512c;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 6px #c0a16b;box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 6px #c0a16b}.has-warning .input-group-addon{color:#8a6d3b;background-color:#fcf8e3;border-color:#8a6d3b}.has-warning .form-control-feedback{color:#8a6d3b}.has-error .checkbox,.has-error .checkbox-inline,.has-error .control-label,.has-error .help-block,.has-error .radio,.has-error .radio-inline{color:#a94442}.has-error .form-control{border-color:#a94442;-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:#843534;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 6px #ce8483;box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 6px #ce8483}.has-error .input-group-addon{color:#a94442;background-color:#f2dede;border-color:#a94442}.has-error .form-control-feedback{color:#a94442}.has-feedback label.sr-only~.form-control-feedback{top: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{display:inline-table;vertical-align:middle}.form-inline .input-group .form-control,.form-inline .input-group .input-group-addon,.form-inline .input-group .input-group-btn{width:auto}.form-inline .input-group>.form-control{width:100%}.form-inline .control-label{margin-bottom:0;vertical-align:middle}.form-inline .checkbox,.form-inline .radio{display:inline-block;margin-top:0;margin-bottom:0;vertical-align:middle}.form-inline .checkbox label,.form-inline .radio label{padding-left:0}.form-inline .checkbox input[type=checkbox],.form-inline .radio input[type=radio]{position:relative;margin-left:0}.form-inline .has-feedback .form-control-feedback{top:0}}.form-horizontal .checkbox,.form-horizontal .checkbox-inline,.form-horizontal .radio,.form-horizontal .radio-inline{padding-top:7px;margin-top:0;margin-bottom:0}.form-horizontal .checkbox,.form-horizontal .radio{min-height:27px}.form-horizontal .form-group{margin-right:-15px;margin-left:-15px}@media (min-width:768px){.form-horizontal .control-label{padding-top:7px;margin-bottom:0;text-align:right}}.form-horizontal .has-feedback .form-control-feedback{top:0;right:15px}@media (min-width:768px){.form-horizontal .form-group-lg .control-label{padding-top:14.3px}}@media (min-width:768px){.form-horizontal .form-group-sm .control-label{padding-top:6px}}.btn{display:inline-block;padding:6px 12px;margin-bottom:0;font-size:14px;font-weight:400;line-height:1.42857143;text-align:center;white-space:nowrap;vertical-align:middle;cursor:pointer;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;background-image:none;border:1px solid transparent;border-radius:4px}.btn.active:focus,.btn:active:focus,.btn:focus{outline:thin dotted;outline:5px auto -webkit-focus-ring-color;outline-offset:-2px}.btn:focus,.btn:hover{color:#333;text-decoration:none}.btn.active,.btn:active{background-image:none;outline:0;-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{pointer-events:none;cursor:not-allowed;filter:alpha(opacity=65);-webkit-box-shadow:none;box-shadow:none;opacity:.65}.btn-default{color:#333;background-color:#fff;border-color:#ccc}.btn-default.active,.btn-default:active,.btn-default:focus,.btn-default:hover,.open>.dropdown-toggle.btn-default{color:#333;background-color:#e6e6e6;border-color:#adadad}.btn-default.active,.btn-default:active,.open>.dropdown-toggle.btn-default{background-image:none}.btn-default.disabled,.btn-default.disabled.active,.btn-default.disabled:active,.btn-default.disabled:focus,.btn-default.disabled:hover,.btn-default[disabled],.btn-default[disabled].active,.btn-default[disabled]:active,.btn-default[disabled]:focus,.btn-default[disabled]:hover,fieldset[disabled] .btn-default,fieldset[disabled] .btn-default.active,fieldset[disabled] .btn-default:active,fieldset[disabled] .btn-default:focus,fieldset[disabled] .btn-default:hover{background-color:#fff;border-color:#ccc}.btn-default .badge{color:#fff;background-color:#333}.btn-primary{color:#fff;background-color:#428bca;border-color:#357ebd}.btn-primary.active,.btn-primary:active,.btn-primary:focus,.btn-primary:hover,.open>.dropdown-toggle.btn-primary{color:#fff;background-color:#3071a9;border-color:#285e8e}.btn-primary.active,.btn-primary:active,.open>.dropdown-toggle.btn-primary{background-image:none}.btn-primary.disabled,.btn-primary.disabled.active,.btn-primary.disabled:active,.btn-primary.disabled:focus,.btn-primary.disabled:hover,.btn-primary[disabled],.btn-primary[disabled].active,.btn-primary[disabled]:active,.btn-primary[disabled]:focus,.btn-primary[disabled]:hover,fieldset[disabled] .btn-primary,fieldset[disabled] .btn-primary.active,fieldset[disabled] .btn-primary:active,fieldset[disabled] .btn-primary:focus,fieldset[disabled] .btn-primary:hover{background-color:#428bca;border-color:#357ebd}.btn-primary .badge{color:#428bca;background-color:#fff}.btn-success{color:#fff;background-color:#5cb85c;border-color:#4cae4c}.btn-success.active,.btn-success:active,.btn-success:focus,.btn-success:hover,.open>.dropdown-toggle.btn-success{color:#fff;background-color:#449d44;border-color:#398439}.btn-success.active,.btn-success:active,.open>.dropdown-toggle.btn-success{background-image:none}.btn-success.disabled,.btn-success.disabled.active,.btn-success.disabled:active,.btn-success.disabled:focus,.btn-success.disabled:hover,.btn-success[disabled],.btn-success[disabled].active,.btn-success[disabled]:active,.btn-success[disabled]:focus,.btn-success[disabled]:hover,fieldset[disabled] .btn-success,fieldset[disabled] .btn-success.active,fieldset[disabled] .btn-success:active,fieldset[disabled] .btn-success:focus,fieldset[disabled] .btn-success:hover{background-color:#5cb85c;border-color:#4cae4c}.btn-success .badge{color:#5cb85c;background-color:#fff}.btn-info{color:#fff;background-color:#5bc0de;border-color:#46b8da}.btn-info.active,.btn-info:active,.btn-info:focus,.btn-info:hover,.open>.dropdown-toggle.btn-info{color:#fff;background-color:#31b0d5;border-color:#269abc}.btn-info.active,.btn-info:active,.open>.dropdown-toggle.btn-info{background-image:none}.btn-info.disabled,.btn-info.disabled.active,.btn-info.disabled:active,.btn-info.disabled:focus,.btn-info.disabled:hover,.btn-info[disabled],.btn-info[disabled].active,.btn-info[disabled]:active,.btn-info[disabled]:focus,.btn-info[disabled]:hover,fieldset[disabled] .btn-info,fieldset[disabled] .btn-info.active,fieldset[disabled] .btn-info:active,fieldset[disabled] .btn-info:focus,fieldset[disabled] .btn-info:hover{background-color:#5bc0de;border-color:#46b8da}.btn-info .badge{color:#5bc0de;background-color:#fff}.btn-warning{color:#fff;background-color:#f0ad4e;border-color:#eea236}.btn-warning.active,.btn-warning:active,.btn-warning:focus,.btn-warning:hover,.open>.dropdown-toggle.btn-warning{color:#fff;background-color:#ec971f;border-color:#d58512}.btn-warning.active,.btn-warning:active,.open>.dropdown-toggle.btn-warning{background-image:none}.btn-warning.disabled,.btn-warning.disabled.active,.btn-warning.disabled:active,.btn-warning.disabled:focus,.btn-warning.disabled:hover,.btn-warning[disabled],.btn-warning[disabled].active,.btn-warning[disabled]:active,.btn-warning[disabled]:focus,.btn-warning[disabled]:hover,fieldset[disabled] .btn-warning,fieldset[disabled] .btn-warning.active,fieldset[disabled] .btn-warning:active,fieldset[disabled] .btn-warning:focus,fieldset[disabled] .btn-warning:hover{background-color:#f0ad4e;border-color:#eea236}.btn-warning .badge{color:#f0ad4e;background-color:#fff}.btn-danger{color:#fff;background-color:#d9534f;border-color:#d43f3a}.btn-danger.active,.btn-danger:active,.btn-danger:focus,.btn-danger:hover,.open>.dropdown-toggle.btn-danger{color:#fff;background-color:#c9302c;border-color:#ac2925}.btn-danger.active,.btn-danger:active,.open>.dropdown-toggle.btn-danger{background-image:none}.btn-danger.disabled,.btn-danger.disabled.active,.btn-danger.disabled:active,.btn-danger.disabled:focus,.btn-danger.disabled:hover,.btn-danger[disabled],.btn-danger[disabled].active,.btn-danger[disabled]:active,.btn-danger[disabled]:focus,.btn-danger[disabled]:hover,fieldset[disabled] .btn-danger,fieldset[disabled] .btn-danger.active,fieldset[disabled] .btn-danger:active,fieldset[disabled] .btn-danger:focus,fieldset[disabled] .btn-danger:hover{background-color:#d9534f;border-color:#d43f3a}.btn-danger .badge{color:#d9534f;background-color:#fff}.btn-link{font-weight:400;color:#428bca;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:active,.btn-link:focus,.btn-link:hover{border-color:transparent}.btn-link:focus,.btn-link:hover{color:#2a6496;text-decoration:underline;background-color:transparent}.btn-link[disabled]:focus,.btn-link[disabled]:hover,fieldset[disabled] .btn-link:focus,fieldset[disabled] .btn-link:hover{color:#777;text-decoration:none}.btn-group-lg>.btn,.btn-lg{padding:10px 16px;font-size:18px;line-height:1.33;border-radius:6px}.btn-group-sm>.btn,.btn-sm{padding:5px 10px;font-size:12px;line-height:1.5;border-radius:3px}.btn-group-xs>.btn,.btn-xs{padding:1px 5px;font-size:12px;line-height:1.5;border-radius:3px}.btn-block{display:block;width:100%}.btn-block+.btn-block{margin-top:5px}input[type=button].btn-block,input[type=reset].btn-block,input[type=submit].btn-block{width:100%}.fade{opacity:0;-webkit-transition:opacity .15s linear;-o-transition:opacity .15s linear;transition:opacity .15s linear}.fade.in{opacity:1}.collapse{display:none}.collapse.in{display:block}tr.collapse.in{display:table-row}tbody.collapse.in{display:table-row-group}.collapsing{position:relative;height:0;overflow:hidden;-webkit-transition:height .35s ease;-o-transition:height .35s ease;transition:height .35s ease}.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;font-size:14px;text-align:left;list-style:none;background-color:#fff;-webkit-background-clip:padding-box;background-clip:padding-box;border:1px solid #ccc;border:1px solid rgba(0,0,0,.15);border-radius:4px;-webkit-box-shadow:0 6px 12px rgba(0,0,0,.175);box-shadow:0 6px 12px rgba(0,0,0,.175)}.dropdown-menu.pull-right{right:0;left:auto}.dropdown-menu .divider{height:1px;margin:9px 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:focus,.dropdown-menu>li>a:hover{color:#262626;text-decoration:none;background-color:#f5f5f5}.dropdown-menu>.active>a,.dropdown-menu>.active>a:focus,.dropdown-menu>.active>a:hover{color:#fff;text-decoration:none;background-color:#428bca;outline:0}.dropdown-menu>.disabled>a,.dropdown-menu>.disabled>a:focus,.dropdown-menu>.disabled>a:hover{color:#777}.dropdown-menu>.disabled>a:focus,.dropdown-menu>.disabled>a:hover{text-decoration:none;cursor:not-allowed;background-color:transparent;background-image:none;filter:progid:DXImageTransform.Microsoft.gradient(enabled=false)}.open>.dropdown-menu{display:block}.open>a{outline:0}.dropdown-menu-right{right:0;left:auto}.dropdown-menu-left{right:auto;left:0}.dropdown-header{display:block;padding:3px 20px;font-size:12px;line-height:1.42857143;color:#777;white-space:nowrap}.dropdown-backdrop{position:fixed;top:0;right:0;bottom:0;left:0;z-index:990}.pull-right>.dropdown-menu{right:0;left:auto}.dropup .caret,.navbar-fixed-bottom .dropdown .caret{content:"";border-top:0;border-bottom:4px solid}.dropup .dropdown-menu,.navbar-fixed-bottom .dropdown .dropdown-menu{top:auto;bottom:100%;margin-bottom:1px}@media (min-width:768px){.navbar-right .dropdown-menu{right:0;left:auto}.navbar-right .dropdown-menu-left{right:auto;left:0}}.btn-group,.btn-group-vertical{position:relative;display:inline-block;vertical-align:middle}.btn-group-vertical>.btn,.btn-group>.btn{position:relative;float:left}.btn-group-vertical>.btn.active,.btn-group-vertical>.btn:active,.btn-group-vertical>.btn:focus,.btn-group-vertical>.btn:hover,.btn-group>.btn.active,.btn-group>.btn:active,.btn-group>.btn:focus,.btn-group>.btn:hover{z-index:2}.btn-group-vertical>.btn:focus,.btn-group>.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-top-right-radius:0;border-bottom-right-radius:0}.btn-group>.btn:last-child:not(:first-child),.btn-group>.dropdown-toggle:not(:first-child){border-top-left-radius:0;border-bottom-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-top-right-radius:0;border-bottom-right-radius:0}.btn-group>.btn-group:last-child>.btn:first-child{border-top-left-radius:0;border-bottom-left-radius:0}.btn-group .dropdown-toggle:active,.btn-group.open .dropdown-toggle{outline:0}.btn-group>.btn+.dropdown-toggle{padding-right:8px;padding-left:8px}.btn-group>.btn-lg+.dropdown-toggle{padding-right:12px;padding-left: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}.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:4px;border-bottom-right-radius:0;border-bottom-left-radius:0}.btn-group-vertical>.btn:last-child:not(:first-child){border-top-left-radius:0;border-top-right-radius:0;border-bottom-left-radius:4px}.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-left-radius:0;border-top-right-radius:0}.btn-group-justified{display:table;width:100%;table-layout:fixed;border-collapse:separate}.btn-group-justified>.btn,.btn-group-justified>.btn-group{display:table-cell;float:none;width:1%}.btn-group-justified>.btn-group .btn{width:100%}.btn-group-justified>.btn-group .dropdown-menu{left:auto}[data-toggle=buttons]>.btn>input[type=checkbox],[data-toggle=buttons]>.btn>input[type=radio]{position:absolute;z-index:-1;filter:alpha(opacity=0);opacity:0}.input-group{position:relative;display:table;border-collapse:separate}.input-group[class*=col-]{float:none;padding-right:0;padding-left: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:46px;padding:10px 16px;font-size:18px;line-height:1.33;border-radius:6px}select.input-group-lg>.form-control,select.input-group-lg>.input-group-addon,select.input-group-lg>.input-group-btn>.btn{height:46px;line-height:46px}select[multiple].input-group-lg>.form-control,select[multiple].input-group-lg>.input-group-addon,select[multiple].input-group-lg>.input-group-btn>.btn,textarea.input-group-lg>.form-control,textarea.input-group-lg>.input-group-addon,textarea.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:30px;padding:5px 10px;font-size:12px;line-height:1.5;border-radius:3px}select.input-group-sm>.form-control,select.input-group-sm>.input-group-addon,select.input-group-sm>.input-group-btn>.btn{height:30px;line-height:30px}select[multiple].input-group-sm>.form-control,select[multiple].input-group-sm>.input-group-addon,select[multiple].input-group-sm>.input-group-btn>.btn,textarea.input-group-sm>.form-control,textarea.input-group-sm>.input-group-addon,textarea.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:14px;font-weight:400;line-height:1;color:#555;text-align:center;background-color:#eee;border:1px solid #ccc;border-radius:4px}.input-group-addon.input-sm{padding:5px 10px;font-size:12px;border-radius:3px}.input-group-addon.input-lg{padding:10px 16px;font-size:18px;border-radius:6px}.input-group-addon input[type=checkbox],.input-group-addon input[type=radio]{margin-top:0}.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-group:not(:last-child)>.btn,.input-group-btn:last-child>.btn:not(:last-child):not(.dropdown-toggle),.input-group .form-control:first-child{border-top-right-radius:0;border-bottom-right-radius:0}.input-group-addon:first-child{border-right:0}.input-group-addon:last-child,.input-group-btn:first-child>.btn-group:not(:first-child)>.btn,.input-group-btn:first-child>.btn:not(:first-child),.input-group-btn:last-child>.btn,.input-group-btn:last-child>.btn-group>.btn,.input-group-btn:last-child>.dropdown-toggle,.input-group .form-control:last-child{border-top-left-radius:0;border-bottom-left-radius:0}.input-group-addon:last-child{border-left:0}.input-group-btn{font-size:0;white-space:nowrap}.input-group-btn,.input-group-btn>.btn{position:relative}.input-group-btn>.btn+.btn{margin-left:-1px}.input-group-btn>.btn:active,.input-group-btn>.btn:focus,.input-group-btn>.btn:hover{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{padding-left:0;margin-bottom:0;list-style:none}.nav>li,.nav>li>a{position:relative;display:block}.nav>li>a{padding:10px 15px}.nav>li>a:focus,.nav>li>a:hover{text-decoration:none;background-color:#eee}.nav>li.disabled>a{color:#777}.nav>li.disabled>a:focus,.nav>li.disabled>a:hover{color:#777;text-decoration:none;cursor:not-allowed;background-color:transparent}.nav .open>a,.nav .open>a:focus,.nav .open>a:hover{background-color:#eee;border-color:#428bca}.nav .nav-divider{height:1px;margin:9px 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:4px 4px 0 0}.nav-tabs>li>a:hover{border-color:#eee #eee #ddd}.nav-tabs>li.active>a,.nav-tabs>li.active>a:focus,.nav-tabs>li.active>a:hover{color:#555;cursor:default;background-color:#fff;border:1px solid;border-color:#ddd #ddd transparent}.nav-tabs.nav-justified{width:100%;border-bottom:0}.nav-tabs.nav-justified>li{float:none}.nav-tabs.nav-justified>li>a{margin-bottom:5px;text-align:center}.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:4px}.nav-tabs.nav-justified>.active>a,.nav-tabs.nav-justified>.active>a:focus,.nav-tabs.nav-justified>.active>a:hover{border:1px solid #ddd}@media (min-width:768px){.nav-tabs.nav-justified>li>a{border-bottom:1px solid #ddd;border-radius:4px 4px 0 0}.nav-tabs.nav-justified>.active>a,.nav-tabs.nav-justified>.active>a:focus,.nav-tabs.nav-justified>.active>a:hover{border-bottom-color:#fff}}.nav-pills>li{float:left}.nav-pills>li>a{border-radius:4px}.nav-pills>li+li{margin-left:2px}.nav-pills>li.active>a,.nav-pills>li.active>a:focus,.nav-pills>li.active>a:hover{color:#fff;background-color:#428bca}.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{margin-bottom:5px;text-align:center}.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:4px}.nav-tabs-justified>.active>a,.nav-tabs-justified>.active>a:focus,.nav-tabs-justified>.active>a:hover{border:1px solid #ddd}@media (min-width:768px){.nav-tabs-justified>li>a{border-bottom:1px solid #ddd;border-radius:4px 4px 0 0}.nav-tabs-justified>.active>a,.nav-tabs-justified>.active>a:focus,.nav-tabs-justified>.active>a:hover{border-bottom-color:#fff}}.tab-content>.tab-pane{display:none}.tab-content>.active{display:block}.nav-tabs .dropdown-menu{margin-top:-1px;border-top-left-radius:0;border-top-right-radius:0}.navbar{position:relative;min-height:50px;margin-bottom:20px;border:1px solid transparent}@media (min-width:768px){.navbar{border-radius:4px}}@media (min-width:768px){.navbar-header{float:left}}.navbar-collapse{padding-right:15px;padding-left:15px;overflow-x:visible;-webkit-overflow-scrolling:touch;border-top:1px solid transparent;-webkit-box-shadow:inset 0 1px 0 hsla(0,0%,100%,.1);box-shadow:inset 0 1px 0 hsla(0,0%,100%,.1)}.navbar-collapse.in{overflow-y:auto}@media (min-width:768px){.navbar-collapse{width:auto;border-top:0;-webkit-box-shadow:none;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-bottom .navbar-collapse,.navbar-fixed-top .navbar-collapse,.navbar-static-top .navbar-collapse{padding-right:0;padding-left:0}}.navbar-fixed-bottom .navbar-collapse,.navbar-fixed-top .navbar-collapse{max-height:340px}@media (max-width:480px) and (orientation:landscape){.navbar-fixed-bottom .navbar-collapse,.navbar-fixed-top .navbar-collapse{max-height:200px}}.container-fluid>.navbar-collapse,.container-fluid>.navbar-header,.container>.navbar-collapse,.container>.navbar-header{margin-right:-15px;margin-left:-15px}@media (min-width:768px){.container-fluid>.navbar-collapse,.container-fluid>.navbar-header,.container>.navbar-collapse,.container>.navbar-header{margin-right:0;margin-left:0}}.navbar-static-top{z-index:1000;border-width:0 0 1px}@media (min-width:768px){.navbar-static-top{border-radius:0}}.navbar-fixed-bottom,.navbar-fixed-top{position:fixed;right:0;left:0;z-index:1030;-webkit-transform:translateZ(0);-o-transform:translateZ(0);transform:translateZ(0)}@media (min-width:768px){.navbar-fixed-bottom,.navbar-fixed-top{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;height:50px;padding:15px;font-size:18px;line-height:20px}.navbar-brand:focus,.navbar-brand:hover{text-decoration:none}@media (min-width:768px){.navbar>.container-fluid .navbar-brand,.navbar>.container .navbar-brand{margin-left:-15px}}.navbar-toggle{position:relative;float:right;padding:9px 10px;margin-top:8px;margin-right:15px;margin-bottom:8px;background-color:transparent;background-image:none;border:1px solid transparent;border-radius:4px}.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:768px){.navbar-toggle{display:none}}.navbar-nav{margin:7.5px -15px}.navbar-nav>li>a{padding-top:10px;padding-bottom:10px;line-height:20px}@media (max-width:767px){.navbar-nav .open .dropdown-menu{position:static;float:none;width:auto;margin-top:0;background-color:transparent;border:0;-webkit-box-shadow:none;box-shadow:none}.navbar-nav .open .dropdown-menu .dropdown-header,.navbar-nav .open .dropdown-menu>li>a{padding:5px 15px 5px 25px}.navbar-nav .open .dropdown-menu>li>a{line-height:20px}.navbar-nav .open .dropdown-menu>li>a:focus,.navbar-nav .open .dropdown-menu>li>a:hover{background-image:none}}@media (min-width:768px){.navbar-nav{float:left;margin:0}.navbar-nav>li{float:left}.navbar-nav>li>a{padding-top:15px;padding-bottom:15px}.navbar-nav.navbar-right:last-child{margin-right:-15px}}@media (min-width:768px){.navbar-left{float:left!important}.navbar-right{float:right!important}}.navbar-form{padding:10px 15px;margin:8px -15px;border-top:1px solid transparent;border-bottom:1px solid transparent;-webkit-box-shadow:inset 0 1px 0 hsla(0,0%,100%,.1),0 1px 0 hsla(0,0%,100%,.1);box-shadow:inset 0 1px 0 hsla(0,0%,100%,.1),0 1px 0 hsla(0,0%,100%,.1)}@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{display:inline-table;vertical-align:middle}.navbar-form .input-group .form-control,.navbar-form .input-group .input-group-addon,.navbar-form .input-group .input-group-btn{width:auto}.navbar-form .input-group>.form-control{width:100%}.navbar-form .control-label{margin-bottom:0;vertical-align:middle}.navbar-form .checkbox,.navbar-form .radio{display:inline-block;margin-top:0;margin-bottom:0;vertical-align:middle}.navbar-form .checkbox label,.navbar-form .radio label{padding-left:0}.navbar-form .checkbox input[type=checkbox],.navbar-form .radio input[type=radio]{position:relative;margin-left:0}.navbar-form .has-feedback .form-control-feedback{top:0}}@media (max-width:767px){.navbar-form .form-group{margin-bottom:5px}}@media (min-width:768px){.navbar-form{width:auto;padding-top:0;padding-bottom:0;margin-right:0;margin-left:0;border: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-left-radius:0;border-top-right-radius:0}.navbar-fixed-bottom .navbar-nav>li>.dropdown-menu{border-bottom-right-radius:0;border-bottom-left-radius:0}.navbar-btn{margin-top:8px;margin-bottom:8px}.navbar-btn.btn-sm{margin-top:10px;margin-bottom:10px}.navbar-btn.btn-xs{margin-top:14px;margin-bottom:14px}.navbar-text{margin-top:15px;margin-bottom:15px}@media (min-width:768px){.navbar-text{float:left;margin-right:15px;margin-left:15px}.navbar-text.navbar-right:last-child{margin-right:0}}.navbar-default{background-color:#f8f8f8;border-color:#e7e7e7}.navbar-default .navbar-brand{color:#777}.navbar-default .navbar-brand:focus,.navbar-default .navbar-brand:hover{color:#5e5e5e;background-color:transparent}.navbar-default .navbar-nav>li>a,.navbar-default .navbar-text{color:#777}.navbar-default .navbar-nav>li>a:focus,.navbar-default .navbar-nav>li>a:hover{color:#333;background-color:transparent}.navbar-default .navbar-nav>.active>a,.navbar-default .navbar-nav>.active>a:focus,.navbar-default .navbar-nav>.active>a:hover{color:#555;background-color:#e7e7e7}.navbar-default .navbar-nav>.disabled>a,.navbar-default .navbar-nav>.disabled>a:focus,.navbar-default .navbar-nav>.disabled>a:hover{color:#ccc;background-color:transparent}.navbar-default .navbar-toggle{border-color:#ddd}.navbar-default .navbar-toggle:focus,.navbar-default .navbar-toggle:hover{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:focus,.navbar-default .navbar-nav>.open>a:hover{color:#555;background-color:#e7e7e7}@media (max-width:767px){.navbar-default .navbar-nav .open .dropdown-menu>li>a{color:#777}.navbar-default .navbar-nav .open .dropdown-menu>li>a:focus,.navbar-default .navbar-nav .open .dropdown-menu>li>a:hover{color:#333;background-color:transparent}.navbar-default .navbar-nav .open .dropdown-menu>.active>a,.navbar-default .navbar-nav .open .dropdown-menu>.active>a:focus,.navbar-default .navbar-nav .open .dropdown-menu>.active>a:hover{color:#555;background-color:#e7e7e7}.navbar-default .navbar-nav .open .dropdown-menu>.disabled>a,.navbar-default .navbar-nav .open .dropdown-menu>.disabled>a:focus,.navbar-default .navbar-nav .open .dropdown-menu>.disabled>a:hover{color:#ccc;background-color:transparent}}.navbar-default .navbar-link{color:#777}.navbar-default .navbar-link:hover{color:#333}.navbar-default .btn-link{color:#777}.navbar-default .btn-link:focus,.navbar-default .btn-link:hover{color:#333}.navbar-default .btn-link[disabled]:focus,.navbar-default .btn-link[disabled]:hover,fieldset[disabled] .navbar-default .btn-link:focus,fieldset[disabled] .navbar-default .btn-link:hover{color:#ccc}.navbar-inverse{background-color:#222;border-color:#080808}.navbar-inverse .navbar-brand{color:#777}.navbar-inverse .navbar-brand:focus,.navbar-inverse .navbar-brand:hover{color:#fff;background-color:transparent}.navbar-inverse .navbar-nav>li>a,.navbar-inverse .navbar-text{color:#777}.navbar-inverse .navbar-nav>li>a:focus,.navbar-inverse .navbar-nav>li>a:hover{color:#fff;background-color:transparent}.navbar-inverse .navbar-nav>.active>a,.navbar-inverse .navbar-nav>.active>a:focus,.navbar-inverse .navbar-nav>.active>a:hover{color:#fff;background-color:#080808}.navbar-inverse .navbar-nav>.disabled>a,.navbar-inverse .navbar-nav>.disabled>a:focus,.navbar-inverse .navbar-nav>.disabled>a:hover{color:#444;background-color:transparent}.navbar-inverse .navbar-toggle{border-color:#333}.navbar-inverse .navbar-toggle:focus,.navbar-inverse .navbar-toggle:hover{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:focus,.navbar-inverse .navbar-nav>.open>a:hover{color:#fff;background-color:#080808}@media (max-width:767px){.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:#777}.navbar-inverse .navbar-nav .open .dropdown-menu>li>a:focus,.navbar-inverse .navbar-nav .open .dropdown-menu>li>a:hover{color:#fff;background-color:transparent}.navbar-inverse .navbar-nav .open .dropdown-menu>.active>a,.navbar-inverse .navbar-nav .open .dropdown-menu>.active>a:focus,.navbar-inverse .navbar-nav .open .dropdown-menu>.active>a:hover{color:#fff;background-color:#080808}.navbar-inverse .navbar-nav .open .dropdown-menu>.disabled>a,.navbar-inverse .navbar-nav .open .dropdown-menu>.disabled>a:focus,.navbar-inverse .navbar-nav .open .dropdown-menu>.disabled>a:hover{color:#444;background-color:transparent}}.navbar-inverse .navbar-link{color:#777}.navbar-inverse .navbar-link:hover{color:#fff}.navbar-inverse .btn-link{color:#777}.navbar-inverse .btn-link:focus,.navbar-inverse .btn-link:hover{color:#fff}.navbar-inverse .btn-link[disabled]:focus,.navbar-inverse .btn-link[disabled]:hover,fieldset[disabled] .navbar-inverse .btn-link:focus,fieldset[disabled] .navbar-inverse .btn-link:hover{color:#444}.breadcrumb{padding:8px 15px;margin-bottom:20px;list-style:none;background-color:#f5f5f5;border-radius:4px}.breadcrumb>li{display:inline-block}.breadcrumb>li+li:before{padding:0 5px;color:#ccc;content:"/\00a0"}.breadcrumb>.active{color:#777}.pagination{display:inline-block;padding-left:0;margin:20px 0;border-radius:4px}.pagination>li{display:inline}.pagination>li>a,.pagination>li>span{position:relative;float:left;padding:6px 12px;margin-left:-1px;line-height:1.42857143;color:#428bca;text-decoration:none;background-color:#fff;border:1px solid #ddd}.pagination>li:first-child>a,.pagination>li:first-child>span{margin-left:0;border-top-left-radius:4px;border-bottom-left-radius:4px}.pagination>li:last-child>a,.pagination>li:last-child>span{border-top-right-radius:4px;border-bottom-right-radius:4px}.pagination>li>a:focus,.pagination>li>a:hover,.pagination>li>span:focus,.pagination>li>span:hover{color:#2a6496;background-color:#eee;border-color:#ddd}.pagination>.active>a,.pagination>.active>a:focus,.pagination>.active>a:hover,.pagination>.active>span,.pagination>.active>span:focus,.pagination>.active>span:hover{z-index:2;color:#fff;cursor:default;background-color:#428bca;border-color:#428bca}.pagination>.disabled>a,.pagination>.disabled>a:focus,.pagination>.disabled>a:hover,.pagination>.disabled>span,.pagination>.disabled>span:focus,.pagination>.disabled>span:hover{color:#777;cursor:not-allowed;background-color:#fff;border-color:#ddd}.pagination-lg>li>a,.pagination-lg>li>span{padding:10px 16px;font-size:18px}.pagination-lg>li:first-child>a,.pagination-lg>li:first-child>span{border-top-left-radius:6px;border-bottom-left-radius:6px}.pagination-lg>li:last-child>a,.pagination-lg>li:last-child>span{border-top-right-radius:6px;border-bottom-right-radius:6px}.pagination-sm>li>a,.pagination-sm>li>span{padding:5px 10px;font-size:12px}.pagination-sm>li:first-child>a,.pagination-sm>li:first-child>span{border-top-left-radius:3px;border-bottom-left-radius:3px}.pagination-sm>li:last-child>a,.pagination-sm>li:last-child>span{border-top-right-radius:3px;border-bottom-right-radius:3px}.pager{padding-left:0;margin:20px 0;text-align:center;list-style:none}.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:focus,.pager li>a:hover{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:focus,.pager .disabled>a:hover,.pager .disabled>span{color:#777;cursor:not-allowed;background-color:#fff}.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}a.label:focus,a.label:hover{color:#fff;text-decoration:none;cursor:pointer}.label:empty{display:none}.btn .label{position:relative;top:-1px}.label-default{background-color:#777}.label-default[href]:focus,.label-default[href]:hover{background-color:#5e5e5e}.label-primary{background-color:#428bca}.label-primary[href]:focus,.label-primary[href]:hover{background-color:#3071a9}.label-success{background-color:#5cb85c}.label-success[href]:focus,.label-success[href]:hover{background-color:#449d44}.label-info{background-color:#5bc0de}.label-info[href]:focus,.label-info[href]:hover{background-color:#31b0d5}.label-warning{background-color:#f0ad4e}.label-warning[href]:focus,.label-warning[href]:hover{background-color:#ec971f}.label-danger{background-color:#d9534f}.label-danger[href]:focus,.label-danger[href]:hover{background-color:#c9302c}.badge{display:inline-block;min-width:10px;padding:3px 7px;font-size:12px;font-weight:700;line-height:1;color:#fff;text-align:center;white-space:nowrap;vertical-align:baseline;background-color:#777;border-radius:10px}.badge:empty{display:none}.btn .badge{position:relative;top:-1px}.btn-xs .badge{top:0;padding:1px 5px}a.badge:focus,a.badge:hover{color:#fff;text-decoration:none;cursor:pointer}.nav-pills>.active>a>.badge,a.list-group-item.active>.badge{color:#428bca;background-color:#fff}.nav-pills>li>a>.badge{margin-left:3px}.jumbotron{padding:30px;margin-bottom:30px;background-color:#eee}.jumbotron,.jumbotron .h1,.jumbotron h1{color:inherit}.jumbotron p{margin-bottom:15px;font-size:21px;font-weight:200}.jumbotron>hr{border-top-color:#d5d5d5}.container .jumbotron{border-radius:6px}.jumbotron .container{max-width:100%}@media screen and (min-width:768px){.jumbotron{padding-top:48px;padding-bottom:48px}.container .jumbotron{padding-right:60px;padding-left:60px}.jumbotron .h1,.jumbotron h1{font-size:63px}}.thumbnail{display:block;padding:4px;margin-bottom:20px;line-height:1.42857143;background-color:#fff;border:1px solid #ddd;border-radius:4px;-webkit-transition:all .2s ease-in-out;-o-transition:all .2s ease-in-out;transition:all .2s ease-in-out}.thumbnail>img,.thumbnail a>img{margin-right:auto;margin-left:auto}a.thumbnail.active,a.thumbnail:focus,a.thumbnail:hover{border-color:#428bca}.thumbnail .caption{padding:9px;color:#333}.alert{padding:15px;margin-bottom:20px;border:1px solid transparent;border-radius:4px}.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,.alert-dismissible{padding-right:35px}.alert-dismissable .close,.alert-dismissible .close{position:relative;top:-2px;right:-21px;color:inherit}.alert-success{color:#3c763d;background-color:#dff0d8;border-color:#d6e9c6}.alert-success hr{border-top-color:#c9e2b3}.alert-success .alert-link{color:#2b542c}.alert-info{color:#31708f;background-color:#d9edf7;border-color:#bce8f1}.alert-info hr{border-top-color:#a6e1ec}.alert-info .alert-link{color:#245269}.alert-warning{color:#8a6d3b;background-color:#fcf8e3;border-color:#faebcc}.alert-warning hr{border-top-color:#f7e1b5}.alert-warning .alert-link{color:#66512c}.alert-danger{color:#a94442;background-color:#f2dede;border-color:#ebccd1}.alert-danger hr{border-top-color:#e4b9c0}.alert-danger .alert-link{color:#843534}@-webkit-keyframes progress-bar-stripes{0%{background-position:40px 0}to{background-position:0 0}}@-o-keyframes progress-bar-stripes{0%{background-position:40px 0}to{background-position:0 0}}@keyframes progress-bar-stripes{0%{background-position:40px 0}to{background-position:0 0}}.progress{height:20px;margin-bottom:20px;overflow:hidden;background-color:#f5f5f5;border-radius:4px;-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:12px;line-height:20px;color:#fff;text-align:center;background-color:#428bca;-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;-o-transition:width .6s ease;transition:width .6s ease}.progress-bar-striped,.progress-striped .progress-bar{background-image:-webkit-linear-gradient(45deg,hsla(0,0%,100%,.15) 25%,transparent 0,transparent 50%,hsla(0,0%,100%,.15) 0,hsla(0,0%,100%,.15) 75%,transparent 0,transparent);background-image:-o-linear-gradient(45deg,hsla(0,0%,100%,.15) 25%,transparent 25%,transparent 50%,hsla(0,0%,100%,.15) 50%,hsla(0,0%,100%,.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,hsla(0,0%,100%,.15) 25%,transparent 0,transparent 50%,hsla(0,0%,100%,.15) 0,hsla(0,0%,100%,.15) 75%,transparent 0,transparent);-webkit-background-size:40px 40px;background-size:40px 40px}.progress-bar.active,.progress.active .progress-bar{-webkit-animation:progress-bar-stripes 2s linear infinite;-o-animation:progress-bar-stripes 2s linear infinite;animation:progress-bar-stripes 2s linear infinite}.progress-bar[aria-valuenow="1"],.progress-bar[aria-valuenow="2"]{min-width:30px}.progress-bar[aria-valuenow="0"]{min-width:30px;color:#777;background-color:transparent;background-image:none;-webkit-box-shadow:none;box-shadow:none}.progress-bar-success{background-color:#5cb85c}.progress-striped .progress-bar-success{background-image:-webkit-linear-gradient(45deg,hsla(0,0%,100%,.15) 25%,transparent 0,transparent 50%,hsla(0,0%,100%,.15) 0,hsla(0,0%,100%,.15) 75%,transparent 0,transparent);background-image:-o-linear-gradient(45deg,hsla(0,0%,100%,.15) 25%,transparent 25%,transparent 50%,hsla(0,0%,100%,.15) 50%,hsla(0,0%,100%,.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,hsla(0,0%,100%,.15) 25%,transparent 0,transparent 50%,hsla(0,0%,100%,.15) 0,hsla(0,0%,100%,.15) 75%,transparent 0,transparent)}.progress-bar-info{background-color:#5bc0de}.progress-striped .progress-bar-info{background-image:-webkit-linear-gradient(45deg,hsla(0,0%,100%,.15) 25%,transparent 0,transparent 50%,hsla(0,0%,100%,.15) 0,hsla(0,0%,100%,.15) 75%,transparent 0,transparent);background-image:-o-linear-gradient(45deg,hsla(0,0%,100%,.15) 25%,transparent 25%,transparent 50%,hsla(0,0%,100%,.15) 50%,hsla(0,0%,100%,.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,hsla(0,0%,100%,.15) 25%,transparent 0,transparent 50%,hsla(0,0%,100%,.15) 0,hsla(0,0%,100%,.15) 75%,transparent 0,transparent)}.progress-bar-warning{background-color:#f0ad4e}.progress-striped .progress-bar-warning{background-image:-webkit-linear-gradient(45deg,hsla(0,0%,100%,.15) 25%,transparent 0,transparent 50%,hsla(0,0%,100%,.15) 0,hsla(0,0%,100%,.15) 75%,transparent 0,transparent);background-image:-o-linear-gradient(45deg,hsla(0,0%,100%,.15) 25%,transparent 25%,transparent 50%,hsla(0,0%,100%,.15) 50%,hsla(0,0%,100%,.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,hsla(0,0%,100%,.15) 25%,transparent 0,transparent 50%,hsla(0,0%,100%,.15) 0,hsla(0,0%,100%,.15) 75%,transparent 0,transparent)}.progress-bar-danger{background-color:#d9534f}.progress-striped .progress-bar-danger{background-image:-webkit-linear-gradient(45deg,hsla(0,0%,100%,.15) 25%,transparent 0,transparent 50%,hsla(0,0%,100%,.15) 0,hsla(0,0%,100%,.15) 75%,transparent 0,transparent);background-image:-o-linear-gradient(45deg,hsla(0,0%,100%,.15) 25%,transparent 25%,transparent 50%,hsla(0,0%,100%,.15) 50%,hsla(0,0%,100%,.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,hsla(0,0%,100%,.15) 25%,transparent 0,transparent 50%,hsla(0,0%,100%,.15) 0,hsla(0,0%,100%,.15) 75%,transparent 0,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{padding-left:0;margin-bottom:20px}.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-left-radius:4px;border-top-right-radius:4px}.list-group-item:last-child{margin-bottom:0;border-bottom-right-radius:4px;border-bottom-left-radius:4px}.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:focus,a.list-group-item:hover{color:#555;text-decoration:none;background-color:#f5f5f5}.list-group-item.disabled,.list-group-item.disabled:focus,.list-group-item.disabled:hover{color:#777;background-color:#eee}.list-group-item.disabled .list-group-item-heading,.list-group-item.disabled:focus .list-group-item-heading,.list-group-item.disabled:hover .list-group-item-heading{color:inherit}.list-group-item.disabled .list-group-item-text,.list-group-item.disabled:focus .list-group-item-text,.list-group-item.disabled:hover .list-group-item-text{color:#777}.list-group-item.active,.list-group-item.active:focus,.list-group-item.active:hover{z-index:2;color:#fff;background-color:#428bca;border-color:#428bca}.list-group-item.active .list-group-item-heading,.list-group-item.active .list-group-item-heading>.small,.list-group-item.active .list-group-item-heading>small,.list-group-item.active:focus .list-group-item-heading,.list-group-item.active:focus .list-group-item-heading>.small,.list-group-item.active:focus .list-group-item-heading>small,.list-group-item.active:hover .list-group-item-heading,.list-group-item.active:hover .list-group-item-heading>.small,.list-group-item.active:hover .list-group-item-heading>small{color:inherit}.list-group-item.active .list-group-item-text,.list-group-item.active:focus .list-group-item-text,.list-group-item.active:hover .list-group-item-text{color:#e1edf7}.list-group-item-success{color:#3c763d;background-color:#dff0d8}a.list-group-item-success{color:#3c763d}a.list-group-item-success .list-group-item-heading{color:inherit}a.list-group-item-success:focus,a.list-group-item-success:hover{color:#3c763d;background-color:#d0e9c6}a.list-group-item-success.active,a.list-group-item-success.active:focus,a.list-group-item-success.active:hover{color:#fff;background-color:#3c763d;border-color:#3c763d}.list-group-item-info{color:#31708f;background-color:#d9edf7}a.list-group-item-info{color:#31708f}a.list-group-item-info .list-group-item-heading{color:inherit}a.list-group-item-info:focus,a.list-group-item-info:hover{color:#31708f;background-color:#c4e3f3}a.list-group-item-info.active,a.list-group-item-info.active:focus,a.list-group-item-info.active:hover{color:#fff;background-color:#31708f;border-color:#31708f}.list-group-item-warning{color:#8a6d3b;background-color:#fcf8e3}a.list-group-item-warning{color:#8a6d3b}a.list-group-item-warning .list-group-item-heading{color:inherit}a.list-group-item-warning:focus,a.list-group-item-warning:hover{color:#8a6d3b;background-color:#faf2cc}a.list-group-item-warning.active,a.list-group-item-warning.active:focus,a.list-group-item-warning.active:hover{color:#fff;background-color:#8a6d3b;border-color:#8a6d3b}.list-group-item-danger{color:#a94442;background-color:#f2dede}a.list-group-item-danger{color:#a94442}a.list-group-item-danger .list-group-item-heading{color:inherit}a.list-group-item-danger:focus,a.list-group-item-danger:hover{color:#a94442;background-color:#ebcccc}a.list-group-item-danger.active,a.list-group-item-danger.active:focus,a.list-group-item-danger.active:hover{color:#fff;background-color:#a94442;border-color:#a94442}.list-group-item-heading{margin-top:0;margin-bottom:5px}.list-group-item-text{margin-bottom:0;line-height:1.3}.panel{margin-bottom:20px;background-color:#fff;border:1px solid transparent;border-radius:4px;-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-left-radius:3px;border-top-right-radius:3px}.panel-heading>.dropdown .dropdown-toggle,.panel-title{color:inherit}.panel-title{margin-top:0;margin-bottom:0;font-size:16px}.panel-title>a{color:inherit}.panel-footer{padding:10px 15px;background-color:#f5f5f5;border-top:1px solid #ddd;border-bottom-right-radius:3px;border-bottom-left-radius:3px}.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-left-radius:3px;border-top-right-radius:3px}.panel>.list-group:last-child .list-group-item:last-child{border-bottom:0;border-bottom-right-radius:3px;border-bottom-left-radius:3px}.list-group+.panel-footer,.panel-heading+.list-group .list-group-item:first-child{border-top-width:0}.panel>.panel-collapse>.table,.panel>.table,.panel>.table-responsive>.table{margin-bottom:0}.panel>.table-responsive:first-child>.table:first-child,.panel>.table:first-child{border-top-left-radius:3px;border-top-right-radius:3px}.panel>.table-responsive:first-child>.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 th:first-child,.panel>.table-responsive:first-child>.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 th:first-child,.panel>.table:first-child>tbody:first-child>tr:first-child td:first-child,.panel>.table:first-child>tbody:first-child>tr:first-child th:first-child,.panel>.table:first-child>thead:first-child>tr:first-child td:first-child,.panel>.table:first-child>thead:first-child>tr:first-child th:first-child{border-top-left-radius:3px}.panel>.table-responsive:first-child>.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 th:last-child,.panel>.table-responsive:first-child>.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 th:last-child,.panel>.table:first-child>tbody:first-child>tr:first-child td:last-child,.panel>.table:first-child>tbody:first-child>tr:first-child th:last-child,.panel>.table:first-child>thead:first-child>tr:first-child td:last-child,.panel>.table:first-child>thead:first-child>tr:first-child th:last-child{border-top-right-radius:3px}.panel>.table-responsive:last-child>.table:last-child,.panel>.table:last-child{border-bottom-right-radius:3px;border-bottom-left-radius:3px}.panel>.table-responsive:last-child>.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 th:first-child,.panel>.table-responsive:last-child>.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 th:first-child,.panel>.table:last-child>tbody:last-child>tr:last-child td:first-child,.panel>.table:last-child>tbody:last-child>tr:last-child th:first-child,.panel>.table:last-child>tfoot:last-child>tr:last-child td:first-child,.panel>.table:last-child>tfoot:last-child>tr:last-child th:first-child{border-bottom-left-radius:3px}.panel>.table-responsive:last-child>.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 th:last-child,.panel>.table-responsive:last-child>.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 th:last-child,.panel>.table:last-child>tbody:last-child>tr:last-child td:last-child,.panel>.table:last-child>tbody:last-child>tr:last-child th:last-child,.panel>.table:last-child>tfoot:last-child>tr:last-child td:last-child,.panel>.table:last-child>tfoot:last-child>tr:last-child th:last-child{border-bottom-right-radius:3px}.panel>.panel-body+.table,.panel>.panel-body+.table-responsive{border-top:1px solid #ddd}.panel>.table>tbody:first-child>tr:first-child td,.panel>.table>tbody:first-child>tr:first-child th{border-top:0}.panel>.table-bordered,.panel>.table-responsive>.table-bordered{border:0}.panel>.table-bordered>tbody>tr>td:first-child,.panel>.table-bordered>tbody>tr>th:first-child,.panel>.table-bordered>tfoot>tr>td:first-child,.panel>.table-bordered>tfoot>tr>th:first-child,.panel>.table-bordered>thead>tr>td:first-child,.panel>.table-bordered>thead>tr>th:first-child,.panel>.table-responsive>.table-bordered>tbody>tr>td:first-child,.panel>.table-responsive>.table-bordered>tbody>tr>th:first-child,.panel>.table-responsive>.table-bordered>tfoot>tr>td:first-child,.panel>.table-responsive>.table-bordered>tfoot>tr>th:first-child,.panel>.table-responsive>.table-bordered>thead>tr>td:first-child,.panel>.table-responsive>.table-bordered>thead>tr>th:first-child{border-left:0}.panel>.table-bordered>tbody>tr>td:last-child,.panel>.table-bordered>tbody>tr>th:last-child,.panel>.table-bordered>tfoot>tr>td:last-child,.panel>.table-bordered>tfoot>tr>th:last-child,.panel>.table-bordered>thead>tr>td:last-child,.panel>.table-bordered>thead>tr>th:last-child,.panel>.table-responsive>.table-bordered>tbody>tr>td:last-child,.panel>.table-responsive>.table-bordered>tbody>tr>th:last-child,.panel>.table-responsive>.table-bordered>tfoot>tr>td:last-child,.panel>.table-responsive>.table-bordered>tfoot>tr>th:last-child,.panel>.table-responsive>.table-bordered>thead>tr>td:last-child,.panel>.table-responsive>.table-bordered>thead>tr>th:last-child{border-right:0}.panel>.table-bordered>tbody>tr:first-child>td,.panel>.table-bordered>tbody>tr:first-child>th,.panel>.table-bordered>tbody>tr:last-child>td,.panel>.table-bordered>tbody>tr:last-child>th,.panel>.table-bordered>tfoot>tr:last-child>td,.panel>.table-bordered>tfoot>tr:last-child>th,.panel>.table-bordered>thead>tr:first-child>td,.panel>.table-bordered>thead>tr:first-child>th,.panel>.table-responsive>.table-bordered>tbody>tr:first-child>td,.panel>.table-responsive>.table-bordered>tbody>tr:first-child>th,.panel>.table-responsive>.table-bordered>tbody>tr:last-child>td,.panel>.table-responsive>.table-bordered>tbody>tr:last-child>th,.panel>.table-responsive>.table-bordered>tfoot>tr:last-child>td,.panel>.table-responsive>.table-bordered>tfoot>tr:last-child>th,.panel>.table-responsive>.table-bordered>thead>tr:first-child>td,.panel>.table-responsive>.table-bordered>thead>tr:first-child>th{border-bottom:0}.panel>.table-responsive{margin-bottom:0;border:0}.panel-group{margin-bottom:20px}.panel-group .panel{margin-bottom:0;border-radius:4px}.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-heading .badge{color:#f5f5f5;background-color:#333}.panel-default>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:#ddd}.panel-primary{border-color:#428bca}.panel-primary>.panel-heading{color:#fff;background-color:#428bca;border-color:#428bca}.panel-primary>.panel-heading+.panel-collapse>.panel-body{border-top-color:#428bca}.panel-primary>.panel-heading .badge{color:#428bca;background-color:#fff}.panel-primary>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:#428bca}.panel-success{border-color:#d6e9c6}.panel-success>.panel-heading{color:#3c763d;background-color:#dff0d8;border-color:#d6e9c6}.panel-success>.panel-heading+.panel-collapse>.panel-body{border-top-color:#d6e9c6}.panel-success>.panel-heading .badge{color:#dff0d8;background-color:#3c763d}.panel-success>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:#d6e9c6}.panel-info{border-color:#bce8f1}.panel-info>.panel-heading{color:#31708f;background-color:#d9edf7;border-color:#bce8f1}.panel-info>.panel-heading+.panel-collapse>.panel-body{border-top-color:#bce8f1}.panel-info>.panel-heading .badge{color:#d9edf7;background-color:#31708f}.panel-info>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:#bce8f1}.panel-warning{border-color:#faebcc}.panel-warning>.panel-heading{color:#8a6d3b;background-color:#fcf8e3;border-color:#faebcc}.panel-warning>.panel-heading+.panel-collapse>.panel-body{border-top-color:#faebcc}.panel-warning>.panel-heading .badge{color:#fcf8e3;background-color:#8a6d3b}.panel-warning>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:#faebcc}.panel-danger{border-color:#ebccd1}.panel-danger>.panel-heading{color:#a94442;background-color:#f2dede;border-color:#ebccd1}.panel-danger>.panel-heading+.panel-collapse>.panel-body{border-top-color:#ebccd1}.panel-danger>.panel-heading .badge{color:#f2dede;background-color:#a94442}.panel-danger>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:#ebccd1}.embed-responsive{position:relative;display:block;height:0;padding:0;overflow:hidden}.embed-responsive .embed-responsive-item,.embed-responsive embed,.embed-responsive iframe,.embed-responsive object{position:absolute;top:0;bottom:0;left:0;width:100%;height:100%;border:0}.embed-responsive.embed-responsive-16by9{padding-bottom:56.25%}.embed-responsive.embed-responsive-4by3{padding-bottom:75%}.well{min-height:20px;padding:19px;margin-bottom:20px;background-color:#f5f5f5;border:1px solid #e3e3e3;border-radius:4px;-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:6px}.well-sm{padding:9px;border-radius:3px}.close{float:right;font-size:21px;font-weight:700;line-height:1;color:#000;text-shadow:0 1px 0 #fff;filter:alpha(opacity=20);opacity:.2}.close:focus,.close:hover{color:#000;text-decoration:none;cursor:pointer;filter:alpha(opacity=50);opacity:.5}button.close{-webkit-appearance:none;padding:0;cursor:pointer;background:0 0;border:0}.modal,.modal-open{overflow:hidden}.modal{position:fixed;top:0;right:0;bottom:0;left:0;z-index:1050;display:none;-webkit-overflow-scrolling:touch;outline:0}.modal.fade .modal-dialog{-webkit-transition:-webkit-transform .3s ease-out;-o-transition:-o-transform .3s ease-out;transition:transform .3s ease-out;-webkit-transform:translate3d(0,-25%,0);-o-transform:translate3d(0,-25%,0);transform:translate3d(0,-25%,0)}.modal.in .modal-dialog{-webkit-transform:translateZ(0);-o-transform:translateZ(0);transform:translateZ(0)}.modal-open .modal{overflow-x:hidden;overflow-y:auto}.modal-dialog{position:relative;width:auto;margin:10px}.modal-content{position:relative;background-color:#fff;-webkit-background-clip:padding-box;background-clip:padding-box;border:1px solid #999;border:1px solid rgba(0,0,0,.2);border-radius:6px;outline:0;-webkit-box-shadow:0 3px 9px rgba(0,0,0,.5);box-shadow:0 3px 9px rgba(0,0,0,.5)}.modal-backdrop{position:fixed;top:0;right:0;bottom:0;left:0;z-index:1040;background-color:#000}.modal-backdrop.fade{filter:alpha(opacity=0);opacity:0}.modal-backdrop.in{filter:alpha(opacity=50);opacity:.5}.modal-header{min-height:16.43px;padding:15px;border-bottom:1px solid #e5e5e5}.modal-header .close{margin-top:-2px}.modal-title{margin:0;line-height:1.42857143}.modal-body{position:relative;padding:15px}.modal-footer{padding:15px;text-align:right;border-top:1px solid #e5e5e5}.modal-footer .btn+.btn{margin-bottom:0;margin-left:5px}.modal-footer .btn-group .btn+.btn{margin-left:-1px}.modal-footer .btn-block+.btn-block{margin-left:0}.modal-scrollbar-measure{position:absolute;top:-9999px;width:50px;height:50px;overflow:scroll}@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:1070;display:block;font-size:12px;line-height:1.4;visibility:visible;filter:alpha(opacity=0);opacity:0}.tooltip.in{filter:alpha(opacity=90);opacity:.9}.tooltip.top{padding:5px 0;margin-top:-3px}.tooltip.right{padding:0 5px;margin-left:3px}.tooltip.bottom{padding:5px 0;margin-top:3px}.tooltip.left{padding:0 5px;margin-left:-3px}.tooltip-inner{max-width:200px;padding:3px 8px;color:#fff;text-align:center;text-decoration:none;background-color:#000;border-radius:4px}.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{right:5px;bottom:0;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:1060;display:none;max-width:276px;padding:1px;text-align:left;white-space:normal;background-color:#fff;-webkit-background-clip:padding-box;background-clip:padding-box;border:1px solid #ccc;border:1px solid rgba(0,0,0,.2);border-radius:6px;-webkit-box-shadow:0 5px 10px rgba(0,0,0,.2);box-shadow:0 5px 10px rgba(0,0,0,.2)}.popover.top{margin-top:-10px}.popover.right{margin-left:10px}.popover.bottom{margin-top:10px}.popover.left{margin-left:-10px}.popover-title{padding:8px 14px;margin:0;font-size:14px;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{content:"";border-width:10px}.popover.top>.arrow{bottom:-11px;left:50%;margin-left:-11px;border-top-color:#999;border-top-color:rgba(0,0,0,.25);border-bottom-width:0}.popover.top>.arrow:after{bottom:1px;margin-left:-10px;content:" ";border-top-color:#fff;border-bottom-width:0}.popover.right>.arrow{top:50%;left:-11px;margin-top:-11px;border-right-color:#999;border-right-color:rgba(0,0,0,.25);border-left-width:0}.popover.right>.arrow:after{bottom:-10px;left:1px;content:" ";border-right-color:#fff;border-left-width:0}.popover.bottom>.arrow{top:-11px;left:50%;margin-left:-11px;border-top-width:0;border-bottom-color:#999;border-bottom-color:rgba(0,0,0,.25)}.popover.bottom>.arrow:after{top:1px;margin-left:-10px;content:" ";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{right:1px;bottom:-10px;content:" ";border-right-width:0;border-left-color:#fff}.carousel,.carousel-inner{position:relative}.carousel-inner{width:100%;overflow:hidden}.carousel-inner>.item{position:relative;display:none;-webkit-transition:left .6s ease-in-out;-o-transition:.6s ease-in-out left;transition:left .6s ease-in-out}.carousel-inner>.item>a>img,.carousel-inner>.item>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;bottom:0;left:0;width:15%;font-size:20px;color:#fff;text-align:center;text-shadow:0 1px 2px rgba(0,0,0,.6);filter:alpha(opacity=50);opacity:.5}.carousel-control.left{background-image:-webkit-linear-gradient(left,rgba(0,0,0,.5),rgba(0,0,0,.0001));background-image:-o-linear-gradient(left,rgba(0,0,0,.5) 0,rgba(0,0,0,.0001) 100%);background-image:-webkit-gradient(linear,left top,right top,from(rgba(0,0,0,.5)),to(rgba(0,0,0,.0001)));background-image:linear-gradient(90deg,rgba(0,0,0,.5) 0,rgba(0,0,0,.0001));filter:progid:DXImageTransform.Microsoft.gradient(startColorstr="#80000000",endColorstr="#00000000",GradientType=1);background-repeat:repeat-x}.carousel-control.right{right:0;left:auto;background-image:-webkit-linear-gradient(left,rgba(0,0,0,.0001),rgba(0,0,0,.5));background-image:-o-linear-gradient(left,rgba(0,0,0,.0001) 0,rgba(0,0,0,.5) 100%);background-image:-webkit-gradient(linear,left top,right top,from(rgba(0,0,0,.0001)),to(rgba(0,0,0,.5)));background-image:linear-gradient(90deg,rgba(0,0,0,.0001) 0,rgba(0,0,0,.5));filter:progid:DXImageTransform.Microsoft.gradient(startColorstr="#00000000",endColorstr="#80000000",GradientType=1);background-repeat:repeat-x}.carousel-control:focus,.carousel-control:hover{color:#fff;text-decoration:none;filter:alpha(opacity=90);outline:0;opacity:.9}.carousel-control .glyphicon-chevron-left,.carousel-control .glyphicon-chevron-right,.carousel-control .icon-next,.carousel-control .icon-prev{position:absolute;top:50%;z-index:5;display:inline-block}.carousel-control .glyphicon-chevron-left,.carousel-control .icon-prev{left:50%;margin-left:-10px}.carousel-control .glyphicon-chevron-right,.carousel-control .icon-next{right:50%;margin-right:-10px}.carousel-control .icon-next,.carousel-control .icon-prev{width:20px;height:20px;margin-top:-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%;padding-left:0;margin-left:-30%;text-align:center;list-style:none}.carousel-indicators li{display:inline-block;width:10px;height:10px;margin:1px;text-indent:-999px;cursor:pointer;background-color:#000\9;background-color:transparent;border:1px solid #fff;border-radius:10px}.carousel-indicators .active{width:12px;height:12px;margin:0;background-color:#fff}.carousel-caption{position:absolute;right:15%;bottom:20px;left:15%;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-next,.carousel-control .icon-prev{width:30px;height:30px;margin-top:-15px;font-size:30px}.carousel-control .glyphicon-chevron-left,.carousel-control .icon-prev{margin-left:-15px}.carousel-control .glyphicon-chevron-right,.carousel-control .icon-next{margin-right:-15px}.carousel-caption{right:20%;left:20%;padding-bottom:30px}.carousel-indicators{bottom:20px}}.btn-group-vertical>.btn-group:after,.btn-group-vertical>.btn-group:before,.btn-toolbar:after,.btn-toolbar:before,.clearfix:after,.clearfix:before,.container-fluid:after,.container-fluid:before,.container:after,.container:before,.dl-horizontal dd:after,.dl-horizontal dd:before,.form-horizontal .form-group:after,.form-horizontal .form-group:before,.modal-footer:after,.modal-footer:before,.nav:after,.nav:before,.navbar-collapse:after,.navbar-collapse:before,.navbar-header:after,.navbar-header:before,.navbar:after,.navbar:before,.pager:after,.pager:before,.panel-body:after,.panel-body:before,.row:after,.row:before{display:table;content:" "}.btn-group-vertical>.btn-group:after,.btn-toolbar:after,.clearfix:after,.container-fluid:after,.container:after,.dl-horizontal dd:after,.form-horizontal .form-group:after,.modal-footer:after,.nav:after,.navbar-collapse:after,.navbar-header:after,.navbar:after,.pager:after,.panel-body:after,.row:after{clear:both}.center-block{display:block;margin-right:auto;margin-left: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;-webkit-transform:translateZ(0);-o-transform:translateZ(0);transform:translateZ(0)}@-ms-viewport{width:device-width}.visible-lg,.visible-lg-block,.visible-lg-inline,.visible-lg-inline-block,.visible-md,.visible-md-block,.visible-md-inline,.visible-md-inline-block,.visible-sm,.visible-sm-block,.visible-sm-inline,.visible-sm-inline-block,.visible-xs,.visible-xs-block,.visible-xs-inline,.visible-xs-inline-block{display:none!important}@media (max-width:767px){.visible-xs{display:block!important}table.visible-xs{display:table}tr.visible-xs{display:table-row!important}td.visible-xs,th.visible-xs{display:table-cell!important}}@media (max-width:767px){.visible-xs-block{display:block!important}}@media (max-width:767px){.visible-xs-inline{display:inline!important}}@media (max-width:767px){.visible-xs-inline-block{display:inline-block!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}td.visible-sm,th.visible-sm{display:table-cell!important}}@media (min-width:768px) and (max-width:991px){.visible-sm-block{display:block!important}}@media (min-width:768px) and (max-width:991px){.visible-sm-inline{display:inline!important}}@media (min-width:768px) and (max-width:991px){.visible-sm-inline-block{display:inline-block!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}td.visible-md,th.visible-md{display:table-cell!important}}@media (min-width:992px) and (max-width:1199px){.visible-md-block{display:block!important}}@media (min-width:992px) and (max-width:1199px){.visible-md-inline{display:inline!important}}@media (min-width:992px) and (max-width:1199px){.visible-md-inline-block{display:inline-block!important}}@media (min-width:1200px){.visible-lg{display:block!important}table.visible-lg{display:table}tr.visible-lg{display:table-row!important}td.visible-lg,th.visible-lg{display:table-cell!important}}@media (min-width:1200px){.visible-lg-block{display:block!important}}@media (min-width:1200px){.visible-lg-inline{display:inline!important}}@media (min-width:1200px){.visible-lg-inline-block{display:inline-block!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}td.visible-print,th.visible-print{display:table-cell!important}}.visible-print-block{display:none!important}@media print{.visible-print-block{display:block!important}}.visible-print-inline{display:none!important}@media print{.visible-print-inline{display:inline!important}}.visible-print-inline-block{display:none!important}@media print{.visible-print-inline-block{display:inline-block!important}}@media print{.hidden-print{display:none!important}} /*! * Bootstrap-select v1.7.5 (http://silviomoreto.github.io/bootstrap-select) * * Copyright 2013-2015 bootstrap-select * Licensed under MIT (https://github.com/silviomoreto/bootstrap-select/blob/master/LICENSE) */.bootstrap-select{width:220px \0}.bootstrap-select>.dropdown-toggle{width:100%;padding-right:25px}.error .bootstrap-select .dropdown-toggle,.has-error .bootstrap-select .dropdown-toggle{border-color:#b94a48}.bootstrap-select.fit-width{width:auto!important}.bootstrap-select:not([class*=col-]):not([class*=form-control]):not(.input-group-btn){width:220px}.bootstrap-select .dropdown-toggle:focus{outline:thin dotted #333!important;outline:5px auto -webkit-focus-ring-color!important;outline-offset:-2px}.bootstrap-select.form-control{margin-bottom:0;padding:0;border:none}.bootstrap-select.form-control:not([class*=col-]){width:100%}.bootstrap-select.form-control.input-group-btn{z-index:auto}.bootstrap-select.btn-group:not(.input-group-btn),.bootstrap-select.btn-group[class*=col-]{float:none;display:inline-block;margin-left:0}.bootstrap-select.btn-group.dropdown-menu-right,.bootstrap-select.btn-group[class*=col-].dropdown-menu-right,.row .bootstrap-select.btn-group[class*=col-].dropdown-menu-right{float:right}.form-group .bootstrap-select.btn-group,.form-horizontal .bootstrap-select.btn-group,.form-inline .bootstrap-select.btn-group{margin-bottom:0}.form-group-lg .bootstrap-select.btn-group.form-control,.form-group-sm .bootstrap-select.btn-group.form-control{padding:0}.form-inline .bootstrap-select.btn-group .form-control{width:100%}.bootstrap-select.btn-group.disabled,.bootstrap-select.btn-group>.disabled{cursor:not-allowed}.bootstrap-select.btn-group.disabled:focus,.bootstrap-select.btn-group>.disabled:focus{outline:0!important}.bootstrap-select.btn-group.bs-container{position:absolute}.bootstrap-select.btn-group.bs-container .dropdown-menu{z-index:1060}.bootstrap-select.btn-group .dropdown-toggle .filter-option{display:inline-block;overflow:hidden;width:100%;text-align:left}.bootstrap-select.btn-group .dropdown-toggle .caret{position:absolute;top:50%;right:12px;margin-top:-2px;vertical-align:middle}.bootstrap-select.btn-group[class*=col-] .dropdown-toggle{width:100%}.bootstrap-select.btn-group .dropdown-menu{min-width:100%;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.bootstrap-select.btn-group .dropdown-menu.inner{position:static;float:none;border:0;padding:0;margin:0;border-radius:0;-webkit-box-shadow:none;box-shadow:none}.bootstrap-select.btn-group .dropdown-menu li{position:relative}.bootstrap-select.btn-group .dropdown-menu li.active small{color:#fff}.bootstrap-select.btn-group .dropdown-menu li.disabled a{cursor:not-allowed}.bootstrap-select.btn-group .dropdown-menu li a{cursor:pointer;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.bootstrap-select.btn-group .dropdown-menu li a.opt{position:relative;padding-left:2.25em}.bootstrap-select.btn-group .dropdown-menu li a span.check-mark{display:none}.bootstrap-select.btn-group .dropdown-menu li a span.text{display:inline-block}.bootstrap-select.btn-group .dropdown-menu li small{padding-left:.5em}.bootstrap-select.btn-group .dropdown-menu .notify{position:absolute;bottom:5px;width:96%;margin:0 2%;min-height:26px;padding:3px 5px;background:#f5f5f5;border:1px solid #e3e3e3;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.05);box-shadow:inset 0 1px 1px rgba(0,0,0,.05);pointer-events:none;opacity:.9;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.bootstrap-select.btn-group .no-results{padding:3px;background:#f5f5f5;margin:0 5px;white-space:nowrap}.bootstrap-select.btn-group.fit-width .dropdown-toggle .filter-option{position:static}.bootstrap-select.btn-group.fit-width .dropdown-toggle .caret{position:static;top:auto;margin-top:-1px}.bootstrap-select.btn-group.show-tick .dropdown-menu li.selected a span.check-mark{position:absolute;display:inline-block;right:15px;margin-top:5px}.bootstrap-select.btn-group.show-tick .dropdown-menu li a span.text{margin-right:34px}.bootstrap-select.show-menu-arrow.open>.dropdown-toggle{z-index:1061}.bootstrap-select.show-menu-arrow .dropdown-toggle:before{content:"";border-left:7px solid transparent;border-right:7px solid transparent;border-bottom:7px solid hsla(0,0%,80%,.2);position:absolute;bottom:-4px;left:9px;display:none}.bootstrap-select.show-menu-arrow .dropdown-toggle:after{content:"";border-left:6px solid transparent;border-right:6px solid transparent;border-bottom:6px solid #fff;position:absolute;bottom:-4px;left:10px;display:none}.bootstrap-select.show-menu-arrow.dropup .dropdown-toggle:before{bottom:auto;top:-3px;border-top:7px solid hsla(0,0%,80%,.2);border-bottom:0}.bootstrap-select.show-menu-arrow.dropup .dropdown-toggle:after{bottom:auto;top:-3px;border-top:6px solid #fff;border-bottom:0}.bootstrap-select.show-menu-arrow.pull-right .dropdown-toggle:before{right:12px;left:auto}.bootstrap-select.show-menu-arrow.pull-right .dropdown-toggle:after{right:13px;left:auto}.bootstrap-select.show-menu-arrow.open>.dropdown-toggle:after,.bootstrap-select.show-menu-arrow.open>.dropdown-toggle:before{display:block}.bs-actionsbox,.bs-donebutton,.bs-searchbox{padding:4px 8px}.bs-actionsbox{width:100%;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.bs-actionsbox .btn-group button{width:50%}.bs-donebutton{float:left;width:100%;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.bs-donebutton .btn-group button{width:100%}.bs-searchbox+.bs-actionsbox{padding:0 8px 4px}.bs-searchbox .form-control{margin-bottom:0;width:100%;float:none}select.bs-select-hidden,select.selectpicker{display:none!important}select.mobile-device{position:absolute!important;top:0;left:0;display:block!important;width:100%;height:100%!important;opacity:0} /*! * bootstrap-tokenfield * https://github.com/sliptree/bootstrap-tokenfield * Copyright 2013-2014 Sliptree and other contributors; Licensed MIT */@-webkit-keyframes "blink"{0%{border-color:#ededed}to{border-color:#b94a48}}@-moz-keyframes "blink"{0%{border-color:#ededed}to{border-color:#b94a48}}@keyframes "blink"{0%{border-color:#ededed}to{border-color:#b94a48}}.tokenfield{height:auto;min-height:34px;padding-bottom:0}.tokenfield.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)}.tokenfield .token{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px;display:inline-block;border:1px solid #d9d9d9;background-color:#ededed;white-space:nowrap;margin:-1px 5px 5px 0;height:22px;vertical-align:top;cursor:default}.tokenfield .token:hover{border-color:#b9b9b9}.tokenfield .token.active{border-color:#52a8ec;border-color:rgba(82,168,236,.8)}.tokenfield .token.duplicate{border-color:#ebccd1;-webkit-animation-name:blink;animation-name:blink;-webkit-animation-duration:.1s;animation-duration:.1s;-webkit-animation-direction:normal;animation-direction:normal;-webkit-animation-timing-function:ease;animation-timing-function:ease;-webkit-animation-iteration-count:infinite;animation-iteration-count:infinite}.tokenfield .token.invalid{background:0 0;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0;border:1px solid transparent;border-bottom:1px dotted #d9534f}.tokenfield .token.invalid.active{background:#ededed;border:1px solid #ededed;-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px}.tokenfield .token .token-label{display:inline-block;overflow:hidden;text-overflow:ellipsis;padding-left:4px;vertical-align:top}.tokenfield .token .close{font-family:Arial;display:inline-block;line-height:100%;font-size:1.1em;line-height:1.49em;margin-left:5px;float:none;height:100%;vertical-align:top;padding-right:4px}.tokenfield .token-input{background:0 0;width:60px;min-width:60px;border:0;height:20px;padding:0;margin-bottom:6px;-webkit-box-shadow:none;box-shadow:none}.tokenfield .token-input:focus{border-color:transparent;outline:0;-webkit-box-shadow:none;box-shadow:none}.tokenfield.disabled{cursor:not-allowed;background-color:#eee}.tokenfield.disabled .token-input{cursor:not-allowed}.tokenfield.disabled .token:hover{cursor:not-allowed;border-color:#d9d9d9}.tokenfield.disabled .token:hover .close{cursor:not-allowed;opacity:.2;filter:alpha(opacity=20)}.has-warning .tokenfield.focus{border-color:#66512c;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 6px #c0a16b;box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 6px #c0a16b}.has-error .tokenfield.focus{border-color:#843534;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 6px #ce8483;box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 6px #ce8483}.has-success .tokenfield.focus{border-color:#2b542c;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 6px #67b168;box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 6px #67b168}.input-group-sm .tokenfield,.tokenfield.input-sm{min-height:30px;padding-bottom:0}.input-group-sm .token,.tokenfield.input-sm .token{height:20px;margin-bottom:4px}.input-group-sm .token-input,.tokenfield.input-sm .token-input{height:18px;margin-bottom:5px}.input-group-lg .tokenfield,.tokenfield.input-lg{min-height:45px;padding-bottom:4px}.input-group-lg .token,.tokenfield.input-lg .token{height:25px}.input-group-lg .token-label,.tokenfield.input-lg .token-label{line-height:23px}.input-group-lg .token .close,.tokenfield.input-lg .token .close{line-height:1.3em}.input-group-lg .token-input,.tokenfield.input-lg .token-input{height:23px;line-height:23px;margin-bottom:6px;vertical-align:top}.tokenfield.rtl{direction:rtl;text-align:right}.tokenfield.rtl .token{margin:-1px 0 5px 5px}.tokenfield.rtl .token .token-label{padding-left:0;padding-right:4px}html{position:relative;min-height:100%}body{margin-bottom:130px}body>.container{padding:75px 15px 0}.btn:focus{outline:none!important}.navbar-link{text-decoration:none!important}.status-text{font-weight:700;font-size:11px!important;margin:0}@media screen and (min-width:1024px){.modal.modal-narrow .modal-dialog{width:30%}.modal-narrow .modal-body{overflow-y:auto}}.progress-text{padding-top:3px;font-size:11px!important;color:#555;white-space:nowrap;overflow:hidden;display:block;text-overflow:ellipsis}.navbar>.container{margin-bottom:-10px}.alert-small{width:300px}.strong{font-weight:700}.popover-content{padding:9px 12px 3px}.panel-sm{margin-bottom:6px}.panel-sm>.panel-heading{font-weight:700;font-size:14px;padding:5px 10px}.panel-sm>.panel-body{font-size:12px;line-height:1.4;padding:10px}.status-light{width:10px;height:10px;text-align:center;padding:1px 0;border-radius:15px;margin-bottom:3px;margin-right:-3px}#folder{max-height:345px;overflow-y:auto}#folder>table{font-size:80%}.sorted{color:maroon}#composer,#message_view{display:none;font-size:80%}.modal-header .btn-group{margin-right:5px;margin-top:-5px}.input-group-sm .tokenfield{height:auto}.modal-header .btn-group button,.modal-header .btn-group button:hover{color:#fff}.primary.modal-header{background-color:#337ab7;color:#fff}.primay.modal-body{padding:0;margin-left:5px}.primary.modal-footer{background-color:#333}#composer_attachments{margin-top:10px}#body{border:none;min-height:50px;margin:3px}#body,#msg_body{white-space:pre-wrap}#msg_body{min-height:150px}.panel-title{font-size:115%;font-weight:bolder}@media screen and (min-width:1024px){#body,#msg_body{min-height:400px;max-height:600px;overflow-y:auto!important;white-space:pre-wrap}#message_view{font-size:80%}#folder{max-height:700px;overflow-y:auto}}.panel-fluid{margin:0;padding:0}.panel-heading p{margin:0}#connectURLPreview{font-size:80%}.footer{position:absolute;bottom:15px;width:100%;height:120px}#console{height:120px;overflow-y:auto;background-color:#272822;color:#fff;line-height:.5em;padding:3px}#console .terminal{font-size:.6em}.msgbody blockquote{font-size:1em;color:#777;margin-top:0;padding-left:5px;margin-bottom:10px}.msgbody blockquote p{margin-top:0;margin-bottom:0}.msgbody blockquote ul{margin-top:0;margin-bottom:-20px;margin-left:0;padding-left:15px}@media screen and (min-width:1024px){.footer{position:absolute;bottom:15px;width:100%;height:130px}#console{height:120px;overflow-y:auto;background-color:#272822;color:#fff;line-height:1em;padding:3px}#console .terminal{font-size:.9em}}.btn-file{position:relative;overflow:hidden}.btn-file input[type=file]{position:absolute;top:0;right:0;min-width:100%;min-height:100%;font-size:100px;text-align:right;filter:alpha(opacity=0);opacity:0;outline:none;background:#fff;cursor:inherit;display:block}.compose-options{margin-top:10px;margin-bottom:-10px}#composer_error{font-size:12px;margin:5px 0;padding:5px}input[type=checkbox],input[type=radio]{vertical-align:-2px;margin:0;padding:0}.table-fixed{overflow-y:auto;height:400px}.table-fixed thead th{position:sticky;top:0;background:#eee}pat-0.15.1/web/dist/fonts/000077500000000000000000000000001453425652100152425ustar00rootroot00000000000000pat-0.15.1/web/dist/fonts/glyphicons-halflings-regular.eot000066400000000000000000000475571453425652100235600ustar00rootroot00000000000000oONLP(GLYPHICONS HalflingsRegularxVersion 1.001;PS 001.001;hotconv 1.0.70;makeotf.lib2.5.583298GLYPHICONS Halflings RegularBSGPv5v5|-\`WhKqJx"U:r,/4\ liʚELFMƀV(gW\+rK0 Q3-O>Cdge}\4[DdpWQ@J[MUAНj.p`k*CI4ثo\ wfvXDlBg1[/a 2lE2g29MÊ zGDUBA$L}Xʕfz;N2_;(k@B[!6P3G E]{JW/s4%ߙ_}}cO-4lPaDX[qv*X,u * z3JL} 0VjUyjn)O0;0!-!r"&bbl,-`Q|/^LVRuzԪIЪ!k&)J{.tS?iSdiVD1w b]D*%qsPDẍp EG`+w:V*X<[lM .ΡShqk" J MT6 ckazԪnqm0pDJv咽Fv(S(MIӶFKm"=-B(>QeYeg䜊7 ._j&LK 9zRuC Rȍ&B0 _C_{4W?N"$#D :J4/횶GJ;]wd:H9®[Ԩ* K #8A$a`]ڞ-TE7 $ :sp†f L$}Zb]b6Hrx-t jViMjN^[8>Cn7L &+3W6G86Xnfi?4 }<4 ζfVGeb} s i{OEn#&QP6D*BLB1SRkDXX><|0;ڈ55C~4?:P}"%ut5I!9l$!yMT>' Otqa?^Em ߲CsS|-nH q#Yt k ¹Ǯs%}NJbCW/|`F\b,ɏ2H2/94# ~ecĩ"Q7:ZI֋]%Y*B;8aFxʩb_͟- *)4?j@5Q'P8Gr)ՠxl֏("c{G݇_xۢƴHE-Bᔡ`\_`TXiCO!;#$QCU(75D Ѥd= >dZ0tjgv2-ēxU  3,7ta,(5¶YE$6sB܉O c: : R.yaHX%1yL7Az5o*n5e)oFaDe`hp"QYoO 4ops0OE Y)b8x.k+8U)+6-]K9pQt&}SnmbtLMwXsSIj>ISj 2`%94}=JAvD j踠ؓT[P#aW0Tt-~zI,6U2)'\v SRP Znq"abErwL1U[n @G[9y3s;! r퐽"04{2 faJ )"P: 63'Z7\H59NTt } 損;wL.tdqwo Ή)"f.dxCz KFNǢhF@ᡰ)1VaP JV,B{&f pphxO2%8 QPL:eϨiN`ߟҒ]Q${3qN% Ot=jxgPΰFK^{f?^/ O\:JD;Q`c^!dvk e3庑aIC d6 IȚ#\},?Hf3cUw+$U[":}]jb(Es)`%\¥U.#]v/)K:Ic4ó.c$C?f `+6cx$#[ߺ f& h,A4-g`?$1 1bB5 }˟ X`h^2*"`PRooפ)jk7L"3|{y@պiw@&}g$su"򩄕_[n 7()D`QŔm>@cgDD~xp }`toHTuҁ)m7$',h XNrlu5_@>[ƍĬ]6ݏS@&ƛ2@4w|{ ߤ9.Fdqr(A1ƴ#ADp#6=rR {xP(Q@"ޥ&Ϡ]a y[J(%;MA ~NdR;>4hYjQ=yæ[`\h :L]}~0Ml0F#뗸S2zB!TC l-c:Tl占bPIS;KΜNWpw}R#;~ -b;} 3@=6]ӦؒAy0h)?(o^2Ri}l[=qĴP418󅷞8@BB8Rjp>ފvP-'KULRCa=r'}ݨkFp6HbI,Fo1r'"չ&&lI{&Qf2a\L)ARہQDX:vaiGGw޶ /0$O0&#H9o(sQƬ"MLjL7L5 y*ɚ Y%wt+55Z()͊BvMK0:$»8!0AbJ#9pB́@E(CuOP<|h sRynx@fr:„i1ZBLnѫrtI-1x,Fޠ1Sb'1CI Y^\UF;=6-T-H2G'8H9&$+d k?K(J'qj s`*L9y2@ a-*PkvW2F0(tTlhш%qA.[_g>9dMx,mw첌-{ 3tw61}Lwf馵G,h jZZ8H5tÉ@wi Ku|AIgn).}88F@*ʠtxV~JUw6A.0n*"1-59dq?5L8v& |֓I4$H]ZMUj/R-l\w׭OU LQ*`r }krmVӛk^vJS ''xQi_᭒d4(Qn \T-VB+ uxdFȘW`q NB \5#3v$O#ȧ,*(w\h;^ʻƪ 'NYmLʿ"TR Cy. >f D kDo9/މ׮=4fQ؀'k5B$rL줁tIL?P!>Mf0+^˪!Ѡ>5Ĩ&y_dA+1f+#X@חč㍊S.r;0~RP72ř|$oVXBa?^ b~*FT@Jƪ3}/SaUȪj1SaB7~& FMsϜ[>+ {1YsȠ@z(=!؆73މfze(<;oe3_'DZs5"b4gdHCoL5.6:5}9$MQ'$¥9DscM |)lIE9#C5LJ`fK%{P,e& 'S7a:np-ąKzHhw%ɹm|lߝʍȝte0q9S_z/L[ P{)x.{hLٷV)kٮq;jz@b2Q` 9~'32jF vQTdM@>ܹPNq޺HіJMrN2Q9}l̀ (6,A; +":(-$V3x6AT\"zn %*bo@oJLBqIziTN9Xj<39'LRB$eYĞa+(n2uCca%˂*w `‹B=Uf{(INq_ zh.3fKH f]N LԩXʍ=zRg}퇍ZUJ"yxqESZDXJN4HOV_X/bYmOt)r!)mFL.P@0cgrTxg=] 3A?)z Ua0@ zd0w"jZpL_T_C e )~oQRNx3Î|T"b, 2& cT6@1# ,@-#z|VcXk58WYP!\-` |)2ooD4}ئ=Lg}Pi9І.xpTsq2yǿa-GuNhXH8uOi% (Ck%b9|B(#U Yt='`҃ >N-'׿ ]69A0&!Ge! mܥ#<*L£U.1=RI2')`> l٦hh%H^YY-aA xq[ '@L^ q=BQGͱ2\K oD9xx۳I 8, OȉC;'2ouPp CR  {|녴p`4tvX @X+6zP?,J*lKh:MѰdC^fhD ]wJү!}7?fYƉA"SEiD2#ե$=4:{pۿs> irrT׎ ; q!u_n&5ҁW%7 _ c59f5r~JDR5e?[T6mf8Or*~$QcڏƁ;B=E_MhhR` >~<@34TGa8vf&G[U%H-XP@ K/қ@^0ݩ'f܍klش\BG]W?sX(Vz$F;]HP{*6Dcb-7|X|o~mssiBJ[0{V \=T%CJ͵m9<?fK]ɒCy{ PƏ뇰G^JH@ \쓚c~Rbʂ@mhVS#4xSkȗ~y*q6&gY? gb Xn!$8[E'()C7"ONhC3V,%(eR39IHC̼_YDb;s~L\ed5 i3sђ9ItrôikXJB5F޾Uu\-*/Usw b OĿmOn,1GG[ՁxeXujz zҳݘ-6`9e$v:]]@/ yB(zÎ / oZm'N;=3%)y 19FL(gRK@ ; \'']̱2ievu# qZ~15I]9, 7Ji)H)j-ju՘ OO/ p:/(;RBU7edINRH?>X0y8Pr#b _8i؍Cf`ȑai2Uyd=2_Db9@:q"ڌSpF~"6TDgtӆ +tHh7 >TLҌ $AQ+ @;;M~oS<:.g&`$"CPbQ,`$`|ʛW18<~$+t >N]-me B5PNcxqE}n B685 }Uf<)SbQRUk)LGS*F:Hp%xسR)s.̀RqWjGi<5) /򈈳2`޲h6`눯LT ɖn QY\;uth26WZafQr`dڭ1t-1a|Ұ g0Ci[[^;`FOpb^ū^nP mJSr4K*X˓ .cnnmEPhJl|c! !ZI>NbTpPu$k[C%- QջQL&B88Jtqic2HšE/'W^yRrnզ*paK#51=̺6 jC#< /I͑2 ;ċ `SXԙ$jjgI\dOX%=grj{yV PsJIfM!]INFn 1Da6-4KX8)>8 )q@PȒR2GI8C@hW TQ];K=_1a ȱ@xZ urd`_1T*Kb{ p-n+W9?.W*m*nԤ'.CACdýDC1n*k]F  ܴOl7i%cXkԺ%i(ε=r$DyU6ʉ;ca k|Gr:9mmW^ÞXeUADQ_«tj$Uh4a| a eB CUMҵhރ8wƤiip`g6ρ#Mtʍ?<8W^%xpf1Sq iXYZKT6 /5QtΦhEzp)HJXC ΐR[S} qT/E:E,2P!ZNĂTs=4阃RNSwy#d8G6<` 3YˤM=9fts4 q9x0šaEgE7 [3n()* l<`T&v}Ou(! `4HAXOе>! +҆5! 0`!WB4`1@ X@( 8 L@M@9zOkU;Xj!I X*^vڥWj:vڡjܪn5ZgjYj V->"^ĵ+J!: ʯ dIdrRZZ$= btȨ΋\rCAt_X'.zbf0^f D V767f/>/IW+ A '6NAڹ‚g"  tTҹDLd@C[1M.yX-'GLB_|־Eg\MƗM!PS=*yU؆\ ;9BGDxj*e˙N-b ^IN۶gW`)N \$x+.0ћ2Q[SC[gX %|;›dDsj|[!8d &y~:<9\H@0wAT)C|%33H>0r=161P.*y4[ŦkD4>0ؘBlab "8>O''FdA7bmInv>)FZS2yb2j/3(7FĬ$G52 f(Ύ94I%*2"vX-Bxp:kԝ\ %("@x/gF T3 )|;oX9R(ogPQ*:pJfuvXص!RzQ#ACqi!PaY2]b3E6PHu~sFxR$HT`w,^[4:)6l~s9_2!. g ?i)eh|Ga #iejDdAo` 9$S)LEvB,/ePa 窀JI??Qg*[99{ Ӧ3{CK%:GJxI|ѳ`NeL輺tr6Ӻ-\jp-Q ¬Sl|ȃ62ʫy8_$*(#+[6 V#iS7u>z&Κq%]Pр6` Fb"2'-HQ{ /Wkc$/sتyn [@7 w 0aVGΙ }' C) -xU"!9[ϳP(pCZ,*BFς!ns#P#Ȣި>|E+ZSndMOH2 L 7ZCCS_#NK2 D<sT)Ĩ xy+YDիKjzoZQ$:^H1d)&II]u20.PUVYzsF;&np*Z8PN`D @?#x"pLB^{Z-ED]v(М` 1 Cm+KJ] 5]Mi h0jxyۛHy:TAuac)ysoܘaSG)$ ux" mTqvN@[Uj43Y n1i {N/Tj5fc絋X̽99@06| = R < vlb28o Q@F;D:l,ecd-Bl mXLL=#+Y$}n£{)pBS 4Jw.dDP#|%Jp+TUY46?qj,sJT`Jb L(7 qyK&M~ ]g7WE8Erty.GxB5Cfr!h{b#zpZِzNd,gMY})$O;- yq}fq7ֵ HS1R&~ZO(]4fFNHPg6y1ÕK*R[A1APȔS$, A'bCp P4>Y!Ov^\s(rЊۨn p) pi5UYZDtY:1Qq7/Rk @"B?Аox4 s(J(qoc^EcѢ4,ГmXh(b imE e&5C1yvI Z&yeQ`ovYIH޸XB{Π 0\#A4$8ЃΨ ZhN}hة%P}@ ?5d19.PŠjcoWHW G!N4K)f>-tnY4*cN aևD' &_0M& ^ژb-9?enב޷ 򂇥E,d!E|KOP :Yj٭O{ Cfm!X w2\~h&;ꍝgPҲxz_9uDy"H ey|ʠ'[qr|#Rr_kR=y8X]8w=Q-^f6,JEC0 G/Iт04Āc9]"9ÄP*R#LɻXz438/߹zeF$1_IVF2QX).TcfgWUv!! 6B"&Şgt9 hdEpj-ہʁ+~^FsR%a4/}0 Q'`EnɸٕtձW}9 _2PS0Ԟ!%RpB q <$ "ъm|BpZ*dğ_~/` %7y+M"fflnYl SyaU乒+NPZ@_ہ;DTYG:ID"i +9IL{Ћ>AD4EEq WJeE|96pX"HMI'$|K'FqB?Yf"%0q?F=O*æ:Cx*hXԠ~UČ>pat-0.15.1/web/dist/fonts/glyphicons-halflings-regular.ttf000066400000000000000000001205001453425652100235420ustar00rootroot00000000000000FFTMjUGDEF8 OS/2gKX`cmapJ rcvt (,fpgmS/4egaspglyf3!53#"'.'ddqd%Kup<3LJ9D?{dd 09C3JL3akw$B d/5dZgj7X0,Z>d.6dJtB+0W5ju.xL//+01!!|,1,A/Ͱ /+  99013!264&#".#"qOxx.,,nBUPrawי kdL 57% P,XX,dpX[ ,%'7'7764/&" MZfVc $ pQfV\ '  3+Ͱ2 /ְͲ +@ + +@ + +01!!!5! ,,dd&L &7>5%&7>54&&$OAXX@JOWOFS  @JOn)`*^r67)Q7q  OY+/Ͱ//ְͰ+ͱ+$99 99$901 "'#" 6& N,mwȃȎwm,NldXD/ְͰͱ+014>>.d8Zwwy,0{xuX6Cy>>yC@vS-IDEH-Sv@9yUUyG !3! 7Hߒ p??G  /3Ͱ2/+01!3! 77'7#'HߒCIJMN p??t⌍155"&=462#%??%d3||3d L #'+/3+ͱ 22/"3Ͱ$2 /&3 Ͱ(2/*3Ͱ,2/.3Ͱ02/2334/ְͳ $2+ $2Ͱ2+2 ͳ$(,0$2 !+%)-1$2ͱ5+ 9999011!%35#535#535#535#535#!!5!!35#535#535#535#535#ddddddddddXXddddddddddLdddddddddd|d|dddddddddLL/?B +,3Ͱ$2/<3Ͱ42@/ְ2 Ͱ2  +02)Ͱ82A+015463!2#!"&463!2#!"&463!2#!"&463!2#!"&ppXpp2pmppmp LL/?O_ov +'+/Ͱ/#3Ͱ!2 +@& + +@ +/(/ְͰ&+2%Ͱ 2%& +@%# +&% +@& +%+ͱ)+&999%9$999$999901 "'#" 6& 53533##5N,mwȃFdddȎwm,NlYdddd]+/Ͱ/ /ְͰ+ͱ!+$99 99$901 "'#" 6& !5!N,mwȃFpȍym,NlY+E/ ,/ְͲ +@ + +Ͳ  +@  +-+ #$90147 654&'5".;2654&+"ҧg|b|g[՛[ddX(>7xx7>طv՛[[ d 0+ 33/ְͰ+ Ͱ  +ͱ+0173#33333d,dd,  PGQb/PͰK/6R/ְHͰHM+$ͱS+H =99M39$9$/99P99K!'E$96+A9901732?6?67'76?654/&/7&''&/&#"'462"&P-<-1&("/&./80PP,<-0&("/&2,;.P g~~~~Y!)&1,;.Q  Q,=,1&("-&3*:/QQ/:/.&0X~~XY~~d#'+/37!+$Ͳ(04222'/*26333Ͱ/ͱ,22// 8/ְ$Ͱ$%+2(Ͱ,2%( +@% +()+0Ͱ01+-24Ͱ 241 +@4 +45+ͱ9+015463!5463!2!2#!"&!#!"&73#3#!5!3#3#d ;),);  d;)D);ddddd,dddd2 d);;)d 2 n )<<)D,dD , +3 / ְͰ+ͱ +901 #!!!Y|pXd" +/ְͲ +@ ++017463!!#!"&d  X,~  ] ,  /Ͱ/Ͳ +@ +//ְ Ͱ +Ͳ +@ ++ͱ+  $9 $9 $9 $901$  $ 6& 33DVGdD_Vd .+3 / Ͱ/Ͳ +@ +2/+01#333!#3#d)(1,Pp,L J + Ͱ/ Ͱ2/ְͲ +@ + + ͱ+ 99  99011!3!3!%35#,ᯯ, pdc +Ͱ//ְͰ+Ͱ+ ͱ + $99 $9 $9014>2". 6& 333_ޠ__ޠ\TPȖޠ__ޠ__Td, a /Ͱ//ְ Ͱ +Ͱ+ͱ+  $99 $9$901$  $ 6& ##DVOD_Vb,, ) + Ͱ2//+ 999015!#!"&3!73!  2,2aD%  F /Ͱ//ְ Ͱ +ͱ+  $9$901$  $ 654& DV:)DS/ Ͱ//ְͰ + ͱ+ $9 99  $99012>5# &632!&#"[՛[nv՛՛[[vbQz[!z+/Ͳ +@ + /Ͳ  +@ +"/ְͰ+ͱ#+99 !$9 99!9  999014>327!7&#"!32653#"'[vƝppIp[vƝXv՛[zpPPv՛[z d #'P/3 Ͱ2 /3 Ͱ2/ 3Ͱ!2/$3Ͱ%2(/ֲ 222 Ͳ222)+013!!!%53'53'53'53!5!=!%5!%5!dLddddddddd   |dddddddddddddddddL#J+ / $/ְͲ +@ ++Ͳ +@ +%+ $901546;5463!232#!"&!54&+";)dvR,Rvd);;)|);,dX);RvvR;));;dLL+/ְͱ+0133>>7.ddd3&Ͱ62 /B/ְͰ"++Ͱ+2+;Ͱ;+ͱC+2+ $9&/$9 9901;2654> ;2654."46;2+"&%46;2+"& 2  2 cޣc   X     ,rr  ,tޣcct 4  4 X!!7'77',,GGG GGGp/ְ ͱ+01!!%7'654,,EojCV 956nb<//ְ Ͱ +ͱ+ $9 $901!%%7'65477654/,,EojCV^{wQ57nB !/3?CGKO+0D33Ͳ)1E222/'+L333Ͳ%-M222"/33#Ͱ2/H33!ͱ4I22P/ֱ22ͱ220+ ,223Ͱ523. +*2%Ͱ@2.% +@." + 222%7+DH22;ͱ&J22;L+B2OͲ9=F222Q+04?$97%()8999"89$9#:;999@ 67<=@C$9011!#5##535!535#!!!5335#5!3##5#5355333!5#53!!5!5353dddd d,,dddd,,ddddddd,,,ddddddddd,,,ddd ddddd dddp,ddddDdd  #p +333 ͱ22 +$/ְ Ͱ + 2ͰͰ +Ͱ +Ͱ +#ͱ%+ 99990153#5!'353'3535353'3ddd,ddddddPdd[[[[[[)+//ְ ͱ+ 99901463! 2764'&"  SS D TT1+3/Ͱ2/ְ ͱ+ $901463! 2764'&"%3 '  TT d 2 D TTD 2d ?+/ /ְ Ͱ +ͱ + 9999990137!!!dddddL 3 4&#!"E~ 'Y%+Ͱ /Ͳ  +@ +2 +@ +2(/ְ Ͱ2 +2ͱ)+  '"$90153!73#5!!7.#!"7>3!2#!"&dXd5(P>^ > B&  & dD||Z   dL%-1o/%Ͱ)/-Ͱ!/2/ְͲ +@/ +'++ͱ3+9+'!$% $9-)"#$9!.199 /0$90153!2654&+.+"#"462"264&"%53;));;)37S*)R:. );dȐ>X>>XXd);;)X);E5+);;;)pȐȐ X>>X>^dd5"+ 3Ͳ 222#/$+013!5".?!#!5&'./#5m)>$\R+5"(]q *k.tB6,-WBB*. 0 Ɍd )1e +!Ͳ +Ͱ)/*Ͱ1/ Ͱ  2/ְ!Ͱ*2!.+Ͱ% ͱ3+%.9)!9*9190135>54.'52#'32654&+532654&#d); $x!" E4+vOȡY}^LlY3(; G7]7( 3AvFTMaTZd{MRao +Ͱ2 /3 /+0135>76&'.'5!Ms (G !:" 0G9C/Q8$99#'% 4<9 %~+/333 Ͳ +@ + 2&/ְͰ +%Ͱ%+Ͳ +@ + +@ ++ͱ'+ 99 9 901'3#7#33!3#4.+!57#"KKK}}KK}2.!"dpd"!/ c,' 2dd2R '! %+Ͱ/3 Ͳ +@ +2&/ ְ%Ͱ%+Ͳ +@ + +@ ++ͱ'+% $9$99999901?!55!3!3#4.+!57#"! d2/!"dpd"!.3}KK}}KK,' v2dd2 'L/?53!26=4&#!"53!26=4&#!"53!26=4&#!"53!26=4&#!"L X2ddddL/?53!26=4&#!"3!26=4&#!"3!26=4&#!"3!26=4&#!"LLDD2ddpddL/?& +Ͱ-/$Ͱ/Ͱ=/4@/A+01=463!2#!"&5463!2#!"&5463!2#!"&5463!2#!"&Ld X2ddlddddlddL/?& +Ͱ/Ͱ-/$Ͱ=/4@/A+01=463!2#!"&5463!2#!"&5463!2#!"&5463!2#!"&LLLL2dd@dd@dd@ddL/?O_oR +L3ͰD2/\3ͰT2-/l3$Ͱd2=/|34Ͱt2/ֲ 0222 Ͳ(8222+01=46;2+"&546;2+"&546;2+"&546;2+"&5463!2#!"&5463!2#!"&5463!2#!"&5463!2#!"&dddddddd,    2dd@dd@dd@dddd@dd@dd@ddL *:J /&3Ͱ.2K/L+90153553#3!26=4&#!"53!26=4&#!"53!26=4&#!"5;26=4&+"eɦddX, dddK}}K LddddL/?CJ@+K/@ְCͱL+0173!26=4&#!"53!26=4&#!"53!26=4&#!"5;26=4&+"3535#5X, dd d!2ddddL&}KdK- /Ͱ/ְ ͱ+ 9 9901463!2#!"& ,,,,,,,v,,d,LY +/Ͱ/ /ְͰ+Ͱ+ ͱ!+999 9999015463!2#!"&?'!462"Xd*J%lNpNNp, >pNNpN= +//ְͰ+ͱ + 99 999014>32.'&73264&"yz{yII99 "c]s+?—jk֖|ׁ~rBB "koKk֖I +Ͱ//ְͰ+ ͱ+ 99  99 $9014>2".3"_ޠ__ޠMޠ__ޠ__ Vu%4>7.77.'&6?uDmssIOWM?%N~OrÀ~[[.  \7^`GvwsuEYd;^~RlbJ(I43nh!&W+Ͱ/'/ְ Ͱ +ͱ(+  $9"99!$9 "#%$901463!"3!26=7#!"&%7' 7/n);;));ԥrkqq\,;) );;)}j2qkqqUL.H+Ͱ"/'Ͱ /Ͱ//ְ ͱ0+"$999 %9&901463!#"3!2657#!"&> "U);;));ԥgg_hHCVC9,P X;) );;)5!&4 D>3CmL#R+Ͱ /$/ְ Ͱ +ͱ%+ "$9!9 "#$9 !9901463!2!"3!26=7#!"& '',=B);;));ԥV7R,;) );;)Eȩ7QE+/3Ͱ 2/ְ2Ͱ2+99 99 990135# #35 5#3 35#,,',[(,,L+ 3/ְ Ͱ2+01746;2+"&d d2KJL+ 33/ְͰ2+01546;2+"&d d2KJL+3/+014 &&LL3 |&&d7;2654&+";2654&+"  dL73!2654&#!"  (L+3/+011 4LL+33/ְ2 ͱ+01146;2+"&5dd L,L+3/ְ2 ͱ+01!46;2+"&5,ddLd( //+0175463!2#!"&!dLdd47 'PaWaaRt% 7a<aa B /Ͱ//ְ Ͱ +ͱ+  $9$901$  $33535#5##DDQ I / Ͱ//ְ Ͱ  +ͱ+  $9 999901$  $!5!DXDQ 2 /Ͱ/ְͰͱ+ 99  9901$  $77'7''DSՍՎՍԍD(ՎՍԍՍ 2 /Ͱ/ְͰͱ+ 99  9901$  $ ''DkfDf 8<l /9Ͱ+!499. (:;$9'<99!*9 .99949901$  $32264>:323>54.#"35#Dɏ '-"#1D12QE&D  =& )2X23L(5`.d ; / Ͱ/3Ͱ/Ͱ//+999901$  $7!5#!3#35#DddddDd,dd/i+/Ͱ2/!333Ͳ #-2220/ֲ)222Ͳ'2221+"#./$999()9990153>7533##5.'35367#53.'#536vkYȌ`oKȕ4eJKn}PEf!}Im0JjlH F /Ͱ/ /ְ Ͱ +ͱ!+  $9$901$  $ 6& 7'77'DVImmmD_Vۇmmm F /Ͱ//ְ Ͱ +ͱ+  $9$901$  $ 6& 77DVkW̎D_V#W͎ F /Ͱ//ְ Ͱ +ͱ+  $9$901$  $&#"32654'D>8dtap;Dsd7>;pac//+901!!XX#c//+901! XX,,;@-J+/ְͱ+901 !!XXXh+/ְͱ+901!!!h(,*?XXL 5>7 FX_Ȅխg;@-$Du +/+011!&ځ&p&ځ &"# 7'!' "''ف'p5'ق#O / Ͱ#/Ͱ/$/ְͰ+ ͱ%+ !#$9# 9999014>2".;2676&+"35#[՛[[՛V:#6#:0՛[[՛[[F.d&*04;4'+13*Ͱ227>7.#676%>7>'&" 8./ieh,Jhqx{\Sc'C78Fak[)!#==Y57>'>7>76''&'.7.7o FFB:8( OV $9DkC@&'GOS3 *gJ.  &:4?B8- %>=B'Pd!I,  =CnCSm,U!ٕfmS ;4( .MV .n}3!?GC/)Ͱ:/ H/<ְ7ͱI+7<@ )(5>@C$9 :$.5>BG$9017>2".'72>7.'"&5477./=FOsvvsOFFOsvvsOFC-[TzwRY,H 7:91.f1ii%LX( (WT`G//G`TW( (( (WT`G//G`TW( ( `=^8+(3\;hI%E:JY||UIWs|Ci`$$ )A+3B/C+6=+ .    + + + +$ +% + #999$9%9 9 $%........@ $%..........@017>3273#7.'77.547?./7>7&'7=FOsvH=<%Ɣ%Rri' ҷ%k.f1i/:(&-/"0/a+'C. %ZeX( (WT`G/Pegy8(6nUIWs|C/WR&2&?@06@(( 4kbf &3!26'.7 !5#5#o%%~8~ddDDG  " d-dd,dd)H/ */%ְ2Ͱ 2% +@ +% +@%# +++%99 %99015467462'%/#&=47&dkX|Xkd^^d)1ES>XX>1) [@ NN @[ L #'+/37;?CGKOSW53!265!5!54&+5#!5##"53'53'5353'53'5353'53'5353'53'5353'53'53Ld ddddddddddddddddddddddddddddddd2dddddJddddddpddddddpddddddpddddddpddddddx A/3Ͱ2 /3Ͱ2/+99  $99901=!35 5# !7'!735 5#X,ԟzz,XXz{L+/ 3/+01463!2#!#"&;));;)d);X);;));,;dL%)-`+ Ͳ  +@  +2&/*3'Ͱ+2./ְ&2Ͱ(2+*2Ͱ,2/+$9999015!32>'4=!".!!!d,U'5%;) ,'MeeM',,Xq \ #(,.*R~jqP33Pqj~RV,,h 7`aaCF ' FDBCa:dv(//ְͱ+9901 #!!!# #+,}++pX,pX2F"+3,Ͱ,&ͱ22//Ͱ/ 3/$ְͰ+ͱ4+-901&763!7>;2++"&=!"&=#"&5463!7!"&'&^6**20 -*? 2222*L +Ͱ/Ͱ 2 /+011!53463!2!P;),); d);;)dL(+Ͱ/ Ͱ2 /+9013! 3463!2!!,P;),);DX);;). ! + /ְͱ + 99013# #3.**,X,/ / / +9901!5 5!,X,X*!I +Ͱ2/ 3Ͱ/"/ְͰ+ Ͳ +@ +#+ 901=463!2#!"&>3!235#35#;));;));$%dddddd);;)d);;U'-$ddddL )7&8/ְͰ*+2ͱ9+* 9901546?.5<>;%%##"+"&'4632#"&e2"]&/ S7X22 !U   QRJf+35/+3Ͱ)/3Ͱ2) +@)% +4/5+)3,1990146;7>7'&6;232#"&/.267"Ju?zS^Sz?vdjO}:: 8F8 0l^GM~ $M( ))1==1777'7'7'7'''N--N괴N--N-N鳳N-,N鴴N,d".//(ְͱ0+(90153#;;276=4&#!6=4&+"?3!#',d=|.%='='20`dd22ֈXKd9X+d,Qv,Q(%wԯ}dL".p+%Ͱ/3(Ͱ./Ͱ2,/ //ְͰ+#Ͱ#&+Ͱ)+ͱ0+&#(,9999)+9.*901374;6;2#!+"&/&735'!5##dd=|.%='='20`dd22ֈdX}Kd9+d,Qv,Q(%կ}wddU"Ay /$Ͱ/)Ͱ1/Ͱ21 +@1 +B/ְͰ+#Ͱ#-+Ͳ- +@-< +C+-# ?$9$#9)91;2654&#!*.'&54?'dj  m U.UkmTkdd%7   VXK %  pyLN'YS(  SeV8<y/$Ͱ/Ͱ8/Ͱ:28 +@89 +=/ְͲ +@, +&+Ͱ9+<ͱ>+& )$9$&998',99901463'&54?632#!"&'#"'32!7%*#!3elU.U m  m!kT %kW   $CLy q  ' (Sd)YS   XaL6:G7+8;/ְ72)Ͳ) +@): +32)/+ͱ<+)699/9013!2654&'%54&"'&77><546!5!a ' (NLy%p[S22(SY XVjTnkUT  n V   dp 48E5/69/ ְ52+Ͳ+ +@+8 +!2+'+ͱ:++  99' 901?26=%>54&#!"!&5<.'&5!p &yMNS) % Y(22XIn  U3.TlnTjVSdڂ  q: +Ͱ//ְͱ+  999999014>32 $! _z%,nUzݠ_ &*8+Ͱ//ְ ͱ+9 9999014>32 $7'!7!5_yzݠ_,Uzݠ__z> +/ְͰ+ ͱ+ 9999  99014>32 $%333_z'Uzݠ_,M +//ְͰ+ ͱ+ 9999  99 999014>32 $% ##_z',,Uzݠ_p,|+*ͰO///ְͰX+ ͱ+9X@!#$32 $277>7.'.'"'&65.'6.'&76746'&67>7&72267.'6'?6.''&%>72>7.73_zyݠ_" T>9.*-hu"#. F = .2) ( (%  )#? 6 /R+>=>1  " ,$Uzݠ__zY!w F  /JG  s$>   #/ & % I+ *  ' ' $#   ' "qq $ U _<7&6767'"/X!N` {+o+We6\e~\F/n`/37;P /4Ͱ7/Ͱ/0Ͱ3/Ͱ,/8Ͱ;/%Ͱ>C+Hͳ7HC+1Ͱ1/7ͰH+ͱN+& $9>*-<9971 /;$9 $9*-<>$9(99#/;9901$  $ 654& 462#"64632#"46?&54632#"'"&%462#"&DVm. M  R)z  73H3 .  D.  ,! . 1~! . $33R . ;O:/'Ͱ /Ͱ6/JP/Q+'?9  1$9239901327>767>'&'&#"67632#"&'&>767>32>'.'&#"0#vF?8!@)'(#Z .A#{Ey&$4I7Z 0$&\4=k6_v[EC8fOESkZ(G־N9@1*+,#b/W!#tCu$'$4B?#>@$$\475be[<C]W$!7GP6X5=3/-3Ͱ26/ְͰ*+ ͱ7+*$939014632632'.'.76?>54&#"'&#"Pńbg#WCG`+rFBGCW#=>@]aRq @C>`9J:vr3HZo\o >FXGaSPc9w332764/&''7'&'7'7>54/&#"9BD[]BBB i{_.7BB i_/#7BB]_@Ba_@BBBB i{_-87B]B i`13#j+]BBB@E+Ͱ/Ͱ//ְͰ+ Ͱ Ͱ/+9990174>2#!"&7!!264&"2- +@-, +;/3Ͳ; +@ +D/ְ22ͰͰ2,+:222+Ͳ>222+@+'Ͱ ͱE+29, 9-)9;':@C$99013.#.54>753#.'#5&'.654&''WJ.BN/!XOd&ER<+6J@" MNW(k,;+@GdfC1/*Ͱ/3Ͱ2/ Ͳ +@ +D/ְ92Ͱ$2 +@ + +@ ++ͱE+8BC$9  *13$9,9*1-999,<990153&'.>7632#4.#"3#>36327#"&'>7>'d /-aʙDP$%T)!):#b!!L<2)O'*2'V7   0 $Xd17;V^(Xw4K,9S*3d2;6 "B   7G  +/ ְ ͱ+ 901 ## ##****,,|X,| "+3 Ͳ+Ͱ/ͱ22 /Ͱ/Ͳ +@ +2"/Ͱ2#/ְͰ+ 22 ͱ22 + 22ͱ 22+Ͱ/ͱ$+999 901333!5335!##535!#5#735#d,cdc,dddd,|dddddd, dd"+ 33 /Ͱ"/Ͱ/ͰͰ/Ͱ/Ͱ2#/ְͰ+22ͱ22 + 22 ͱ22 +Ͱ/ͱ$+99"$99901333!!#5#5335!##53535#,dddd,cdccdd,| dddddddd|L k + /Ͱ/Ͱ /Ͳ  +@  +/ְͳ+Ͱ/ 3Ͱ + 2 ͱ+ $9 9901 ##!#553#35#**X,dddd,,| dd |L k +/Ͱ/Ͳ +@ +// ְ ͳ + Ͱ /3Ͱ  +2ͱ+ $99901 ##%53#!#5'35#**Xdd,dcdd,,|dd  dd R/Ͱ / Ͱ/Ͱ//ֲ 222ͰͲ +@ +@ ++ $901 ##5!5!5!53** p,,,|,,, R/Ͱ / Ͱ/Ͱ// ֲ222Ͱ Ͳ +@  +@  ++ $901 ##535!5!5!**,p,,|,,,LL* +Ͱ/ /ְͰ+ ͱ!+01463!2#!"&73!2654&#!",ԥ;));;) );,ԥA);;));;)LL"> +Ͱ/#/ְͰ+ ͱ$+ !99 "9901463!2#!"&73!2654&#!"-,ԣ;));;) );M,ԥA);;));;) LL"> +Ͱ/#/ְͰ+ ͱ$+ "99 !9901463!2#!"&73!2654&#!",ԥ;));;) );d,ԥA);;));;)dMLL"> +Ͱ/#/ְͰ+ ͱ$+ !99 "9901463!2#!"&73!2654&#!"!,ԥ;));;) );d,Ԣ?);;));;)pML<+Ͱ/Ͱ// ְͱ+99901!5 55!2654&#!5!2#,p);;) ,p;));ԥ!(/"/ְͱ#+99013!327636'&#676/#".     KJ  i  VL?+ͰͰ/Ͱ/Ͱ /ְ ͱ!+999013!275!"&5463!5./"!5 5,/5 );;)]]X,p;));,$T+Ͱ/%/ְͰ +ͱ&+  $$9#9 "#$$99013!26='#!"&546;7'#"%'!',Nz;) );;)vJdabI{);;));zN V  Z /Ͱ/Ͱ//ְ Ͱ +Ͱ+ͱ+ $9 $901$  $ 6& 462"DVrrrD_VrrrL . +Ͱ/Ͱ 2/ְͲ +@ ++011463!2 !!35#  dd   pv2L + +Ͱ//ְͲ +@ ++011463!2!!! 35#  ,,'C^dd  ,2L . +Ͱ//ְ 2Ͳ +@ ++011463!2 ''35#  1TFdd  TF:2L + +Ͱ//ְͲ +@ ++011463!27'%'35#  aapԕdd  baԕ 2L . +Ͱ//ְ2Ͳ +@ ++011463!27'7 35#  |bԕcdd  daԔ2+ /ְͱ +01  %O`w8dLM/Ͳ +@ +2 +@ +2/ְͰͰ+ ͱ+ 99901546;!3+!#"&35#dDXdd,pg>@/Ͳ +@ + +@ +2/ְͰͱ+ 99901546;!3'!#"&%735#dx~E{xa{%dd,xp{x`{$#$/ְͰͱ%+01546;!3'!#"&35#7'77'dgXddd,gpgժl/Ͳ +@ + Ͱ/ͱ22/ְͲ +@ +Ͱ+Ͱ 2+9 99 901546;!3!!#"&% ##53dp X,,d,p,,[/Ͳ +@ +/ͱ22/ְͰͰ+ͱ+9 9 999 901546;!3'!#"&%333 53dnXd,np,,L 53!265!5!54&#!"5!LPd&df /33ͱ22/ 33ͱ 22/ְ Ͱ  +Ͱ+ͱ+ $9$9$9999901!!5335335!5 553;5#,pdddd,ddddd* d/:+0/ְͰ +ͳ +Ͱ/ Ͱ +ͱ1+0173737+"&5%;2654&d22d22d22dX $%dd,dd,ddpAd5!sRtEdL38+ 33Ͳ 222(/%333'Ͱ24/5+(3 99013!5"&5!#!5".546?5!2!4635!2dKK"2pK Kp"28 &v& 88 x88 &v& 88 LL *.2+Ͱ/Ͳ +@ +//0+$9013!2654&#!"!73%!!5!5!!%35!'!5%;),);;));di'Wdd,,'iWd,);;));;)Dbd,,bbdF 3?6&/&.'7>/.>fgї{4vev-+fg=!.vev1L@/+ Ͱ(/8A/B+  /99(&)2@$901=46754>2#!"&?>=6 6=.#"m&RpR&m>d|~\ud?, 23/2  23!""!A1)!((! dL+/+0135!%!'57##5##5##5#dL}dddddddddȖdpddL $ +3/ ְͰ+ͱ+013!4&+"46;2346;2d,;)d);;)d);d;)d););;)p);;));;)DL'+H +Ͱ/,/ְͰ+ ͱ-+ #(*$9 &()$901463!2#!"&7!!!#535!3#353#5#3d|||D||d,dddd,|| |,ddddd,dp,L'+H +Ͱ/,/ְͰ+ ͱ-+ #(*$9 &()$901463!2#!"&7!!3533##5#353#5#3d|||D||ddddddddddd,|| | d,dp,L#D +Ͱ/$/ְͰ+ ͱ%+$9"$901463!2#!"&7!!!5#35!!5#35!d|||D||d,,,|| |d,d d,dLD +Ͱ//ְͰ+ ͱ+$9$901463!2#!"&7!!-d|||D||d,d,,|| |,ԖL'Z +Ͱ/Ͱ#2/%3Ͱ/(/ְͰ+Ͱ+!Ͱ!$+Ͱ+ ͱ)+01463!2#!"&7!!!%3264&+;#"d|||D||d)69&6)&,|| | dTVVT,L#)H +Ͱ/*/ְͰ+ ͱ++ !$'$9 "&($901463!2#!"&7!!!#535!3#35#33#d|||D||d,ddcdd,|| |,ddddd,pL!'L +Ͱ/(/ְͰ+ ͱ)+"%$9 $&$901463!2#!"&7!!!#5#5335#33#d|||D||d,dedddcdd,|| |dpdd,pL!%+ +Ͱ/")33Ͱ#2/Ͱ/&3Ͱ'2/,/ְͰ+2!Ͱ!+ͳ+Ͱ/Ͱ"+%Ͱ%*+)Ͱ)&Ͱ&/)+ ͱ-+9901463!2#!"&7!!5!##53553!5353#d|||D||d,cdcd,dd,|| |dddpddddd  y /Ͱ/Ͱ/Ͱ//ְ Ͱ +Ͱ+ͱ+ $9 $9 $99 $901$  $ 6& 57!!!!DVd,,D_Vddd  $ /Ͱ!/3"Ͱ/Ͱ/%/ְ Ͱ + Ͱ2  +@  + !+2$Ͱ2$+ͱ&+  $9!9$ $9"! $9999 $901$  $ 6& !#5#3#353DV,dddD_VddddddddA r/Ͱ Ͱ2!/ְͰ+Ͱ+ ͱ"+999 99 99 $9  $901;!3264&#".#"333qOxx.,,nBU:Pr,ԭawי k,A /ְͱ!+99901; >54&#".#" ##qO^yx.,,nBU:,,Prmdxawי k,,dLm7!!'5!33 33dK^KԪț--,,My7)327!'32654'>54&'.#"&#"y9/iJ8,K^K.6Ji 2;{Y^t Ji5XJi--2iJ f=ZYqtiA_< . .::(dFHFddjdddddd5d!u,dh"oddF:.JadP9'ddddddy****f0HP6,Lrd"DL 0 ` D V >  v :`L&` b&bL8,J|0NZ2F  * F n !j!"B"#~#$$$%%%%%&Z&&&''j'(8(d()6)*n*+h++,D,--.f../:001&1~1223`34455f566\67 7`778P899X99::^::;,;t;<@>H>>?0?@@`@A"AABBCCD?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~glyph1uni000Duni00A0uni2000uni2001uni2002uni2003uni2004uni2005uni2006uni2007uni2008uni2009uni200Auni202Funi205FEurouni25FCuni2601uni2709uni270FuniE001uniE002uniE003uniE005uniE006uniE007uniE008uniE009uniE010uniE011uniE012uniE013uniE014uniE015uniE016uniE017uniE018uniE019uniE020uniE021uniE022uniE023uniE024uniE025uniE026uniE027uniE028uniE029uniE030uniE031uniE032uniE033uniE034uniE035uniE036uniE037uniE038uniE039uniE040uniE041uniE042uniE043uniE044uniE045uniE046uniE047uniE048uniE049uniE050uniE051uniE052uniE053uniE054uniE055uniE056uniE057uniE058uniE059uniE060uniE062uniE063uniE064uniE065uniE066uniE067uniE068uniE069uniE070uniE071uniE072uniE073uniE074uniE075uniE076uniE077uniE078uniE079uniE080uniE081uniE082uniE083uniE084uniE085uniE086uniE087uniE088uniE089uniE090uniE091uniE092uniE093uniE094uniE095uniE096uniE097uniE101uniE102uniE103uniE104uniE105uniE106uniE107uniE108uniE109uniE110uniE111uniE112uniE113uniE114uniE115uniE116uniE117uniE118uniE119uniE120uniE121uniE122uniE123uniE124uniE125uniE126uniE127uniE128uniE129uniE130uniE131uniE132uniE133uniE134uniE135uniE136uniE137uniE138uniE139uniE140uniE141uniE142uniE143uniE144uniE145uniE146uniE148uniE149uniE150uniE151uniE152uniE153uniE154uniE155uniE156uniE157uniE158uniE159uniE160uniE161uniE162uniE163uniE164uniE165uniE166uniE167uniE168uniE169uniE170uniE171uniE172uniE173uniE174uniE175uniE176uniE177uniE178uniE179uniE180uniE181uniE182uniE183uniE184uniE185uniE186uniE187uniE188uniE189uniE190uniE191uniE192uniE193uniE194uniE195uniE197uniE198uniE199uniE200KPXYF+X!YKRX!Y+\XY+RXpat-0.15.1/web/dist/fonts/glyphicons-halflings-regular.woff000066400000000000000000000554301453425652100237170ustar00rootroot00000000000000wOFF[@FFTMjUGDEF OS/2C`gKcmaprJ cvt (fpgm$eS/gaspglyfN <3!headR46bXhheaS $ 2hmtxS<locaTP4VNmaxpU nameV|ԯpostW@FiprepZ..+webf[XR=  .xc`d``b `b`[@1  xc`fidt! B3.a0bPp?G  I0(00 a x͑?K`mHPDwZ]E :8 ݺv"isEC]|%"2$c=LZMhcHȷ ӭ@r]5UZRG=hMԖvC*#4BCqGJ])qhA}k%@G:ANF`M̓(R<'ڈfYQ*.eYW_|ŗ.w->S>C+7-6d_)_-wa7Sh(x]QN[A  9{ Սbd;i7rq@D گH!H|B>!3k4;;sΙ3KʑwkS$6NH덌Zlfu є;j=o)M;Z ;4: !qKͺb00.?R4j˰Ѽ34@Skm!qK˦6$tUS]`*́Vy &ҷ$, b 9@HƼIJ;ㆵƑ6O'ӿZxڽ `9ޛf1+DQ0Q0c4I^/ޖX|6-Yϙ!ՃF gТ%g"7=^uF3x ]}EWt:基𪯦1Q~ {Myϔ Q+-Ƕ"9FڿſȟHLQ˜ŗ)%3'KVZپd<&I__`XȸFH{xz3$d鿟z=C8[~vt{^0f&ǔBU`Y2̙s80MJ+F)y;&R{}bM;̗gIIańDzA$8f[o_~ ?kunrX\=s_]rB{@>]fr{ czr(񘉇+, ~{n&u=\ ERgg|>(vT Dr Ou,T!g,}ˎsGʝŽ?|?y%Ho_"^c\"MTG wR/W٪([+Gw18u8wdgRL'0e9- ()ҚU%SlU-83JGNqU(*Ӳ8Cr#_grV]( WJKu_k[4<*ED{wrR )߯8RsT]/JAQrsrt^:0~_x RNǶ֏0|F'0dFוd2HI 1EG?J-8LϖN|$OaLdL'p>\ BǷ':m8^ܰ3‰0 ='㾜9 jJE9CEJl!-JsL/~k,PgSr!әڲB@RZ҂*mco knfKwS\ l$}TAg{mEy|x/KrJsUc0aDhiG>8]4<=}Qf|?v#ǿ)(2<08H*DǾ#z: [?.d.@Nu3D0 IP} A-5VM RP 9%TUrs՜$hs2s6L,(}@z Wi)U@ PCK}N)׏.2`XV˗P`>.gܥΓ='dl3پD+ }Ͱ )$SB* 5:I(Ȃ6 J^c,4:܊[ =d[)43M;ɗ /̭{Q2:^Fr6ϞmO{ Hp?kr'w }Ճ_nNsȜ;ə3dm֟mB)~kŹ ,,L#ۗu *&*.8P!\uZ]ì҆i9F% ByL-Uꁭ[Uro^끋z]gPDu"R_I^nzB<}x,N[VGe}lN@3M$kq{YqdvXGk5 ]LLu{͇PW7Q=L_2@:Y$^s- mۋr]mFdbLN\X- hfI!"):Wda=']~ա872gx)) ΪE![xYqS èJ^@p\$prüG䵃p̬(Îw˔N Oy𔑈6][[x[Zݺ=M=}߇dO{gO|T Aj+=0/UZb 3CNx fdac$9Oɳ?<;Vm=o6=+@95=1e+S $;.*˼Mہ*+K5 _W ES}~ԏ"x"3+2f5HXB d$(-{wT7hp" _5LP_Q{lK$G=G7}YS+vUv! 'eէ>*y6l΢M ,e3b|ҋEEW2bq"&FzYkOēˏubTpL} O.MG\Tg%oGo3r}SB)H lÉ!A'I8'[әU alŌ8CN*Y4^ {r,$"Y[ ãsDbV\@ˈhА{(,g _-l9j$w\ެW'n-\rU}aiT[eS?Tg$3`-fqxi0Fr{\L9չD3Pʉx,)F8< h `/Z-.D 4ZJ0F%!;fG&WU*Juʖϒ;Ļ$\|gUZ*˫y(N`eJ@LT} Xڧck {өلݳ_084k6%~OTݒGљ G/"`TC ]́$(BEMFEF,4Qxvi棇^Klel !w%zQ[Emd x hs4?>Sד͚S8jI e\[iP0leQE}L+KkWQH\&44!<)+ |J?q]" Ž}_9~NMtO 6q ^sJZfm(݉ϔZu-4Lah>JX`9ɓۇ[=V:k6whToU'YatCp .VQX0C"0+DF~C]`>ހK0$B(mAGe;5ߖ1 ۹啯r g>%}k)' gLg˝e껗V6>KWnKR{#AhĀ(t!H売s蓥+LO[u6:#0nL/p'Oo⩄v_{ s_ s`Sn2kcQw[#TPj.Z0[@Q_CO9 r4_kksdZ2P-Ҥ$l`丘яKH?uuqȬODL-:~Kh~Cs:͡YM .,b51+AL܄:B2-K h 9^ ǿeOAcfj`f0`eD=I@445 )J`^MH8Cgs90p[=8>U[ j`Vmo"5lWfʳ?kqFuH]N)qгGhǵW*?: u½M:4sZb兵| u/\&aߏtuEܑ.0=t 5s({>aris=@$+X] ]Mퟚ87R#Fݺ&t=^wWұ$=he6wq g$#< vG !{®倠3֤$ GXCk*&^Ty{uڅ ;+:gx.[qW{dtşE܏:x|MbG׼Zt]hD& b€FюCˉGer̬~`78y F4[fSɾAsW-X/M|i5O/]g~k_yi\}U{x7(xѷm%Kgf& k:Dj8KӢKH _Ԇ԰a8FQH bPB%O.H6^&(:IcD̀b($m4;}yrueIwoh+ow޹0K .[<ϽR3feTU1k٥ ^E"SP+`/FL֧lka.B$3b c`o]sddY_0{~ -k=H41S2 uȹ㵗7xkqS /0* eDƫm^'-3")kh>2-ܡGI.P[:C=?Z~$3sQV=pYD uJ./LB#i> 0YبHFI>$fg 7= |O} +ie/Ճ/W3qE9h Hd-AOS*zrXэ媋OVrk%}YB?݉Y~z5S I2~'F4{`Ԁ~Cxz̅2œog{7$B.-(_V"w ]/# S69r]{%¸+Js6~i. C:p C#7",0;C~ "*)cDc s5(4!Yy<3ws&' k|Gauh|Y֪wݫN>AۨNuThPT ;Oͣd/{H] 'Nh-Fbm[?f׍)&a qs}3~%%Z3 3)Y ٨>vP"cʋұ|4Y144GKj̇3B4Y{9Je\s{g=;CN'b&q"LqDd7MU1׈wR}OEň'&Ȩx `+p1KpZdʎ@4Gʶx7LC\64S- 7zPsb_,sÞ"L +D,ڵ8^>7L fe@A)OTq8Ws TN^WlrO|+R O +έ7KM߰b~4E_"9_ Xϛ%~3Esq4/q Y'JZxm8}W W1SU'Cp| 9vNgwTs?_/${@r^ av4'N&])PӋW>au'?owE- 8OnXW>)La8VnJ1dᱽlD>zIu_֠}v,ҭC`QO2P/@s*P74U# ?$/!yl/|kC دnzt3kSɿR Fq ^u+&԰`[\(kMȔ'K7NI7o&ΌG`~/N{ Ql]Wb$7N8nޮR;׶cm ͩ|qf9ZJCZfit 5/JƵ,8i3.W!?oA|3xǒHߎv3o(%ix{ dN$Lczae)=9 ԆŸvf襝ѽjghaw1'Eir כwTMUN ^zbXBm aR±1P%>Gz?J Y,{is\~Wy 7˵hbX=RQou%Bh׀e-/#}'YZ;ˁ)^MRaC:4|0&1-V 48iz:s_t-eW9qfy7M7*ƿ24uo:{\w?ß^3=U#{3Ϻiuk6+ZZ^!ΙK?fpQR[UJ058gFMq{eX }a.&_cS>ٗJl40#nK y"#39[kECg1;9l=˙||Rjӽ|!g8Qx!VWН! ޙ[#7vKPxԊ.)7zf_2)5X!3EλUަ69-k[җ`ef2 .OUeN R B BTYs hjo@c6lY!U@4Ҋ 046[~Ŗ/~q5fw^yJ+)f{Qs%/Ҷn ׵a6}ˆ3wN,L̝Xt Y/ohmi `  '1_cΚ%:lP/_3REq;͡ؿ8]Ns{l˝-wƍ`+~%د+~v)=t%jFp:H[םMd`PHz-<.lS~u+cLf3w,o[w_sѓԭA}&_ڔ08M6tض"/13g%fh9z| $*waVc(rsGFIP9Î8e͒͝儯VZN%`hðKP| "dH\0bh0&>'ٴ~zŠ7۽=2^~X}ZGGkMiY^ѼlYFp5{'ua(jR9eV1F? 9_va.)T4ľ_0$[TKa¸@>i~YФm/eǍ3Bk|[}M(\`9pz@/)pQR}X OϩO!ˑ(ll1?֖զ:Im25UN3=4oiL2 /Bגu'o6"G-j'4-sW`a&"OZOzWQKCod ;_[k,su `вk eitÈ~ %0L;Hi^rÆqKr3<7X aY 3bsU_Iȼ0 GXAE} %G=SQߵYIDNV˭gh^ ,u6WhFk$2՟-ӜfC9G T,FTА345yWt^mt{3ަ.^2c;HM7:"rz_ JVq6$w+[PSAAԷh)K ;% eov?>z,\5EĘ44vcXk֪w`D ,m-dAGg$D%.eqhH:~h(~y(mpGݎm>\\'#ŮNnk~_ r ڰ%:r3jz{6USglvaaaiMڛh+@԰LEwօ.p^ݭtªFA=I9 ٣OzXM"0S<0> "7Q w2 ȓ鼄h5kt%4Cۋh{h|-9.Zrm7ͨEr݀~I¦V@9OH=3:BhIV*Az2G,MLbA.tXE^՛ MNI1569r]jXy3'!˗%=E}ۖr=ˆ;Uӫ/d8ZfSUp8288̓'43t}E7=iC]6tȺ}VLzM6`AD2=\$XP=%~=zC,H%]_}`r7]` 4lPb%7u-دUKKaWI  <@tm+I=DzT}W^BaFb:Z{_޼>)rRi$#6_\G>sU8ichjI.y25mm"}F)͖LYJ CNi~ G c2> K:i:3ǸZf;QvkVL 4 =+`UQW}M߫`H/8Џch'ٷkW^~/w>,a~wlwVS(,ӾrYIZ )$Qk'vHjpUvѸɂ‚٦NZO~XJ08h[0HR ?03;=|F: k=lg_G0niI8J\,epu>O-lzʹZA' tq$"«'l\Gڎ v΂ #pO>6ﻢ-oÇ;}\noCdo, RU@UWo`J)9ٯ`SRHr9jDaeqU+0,7%~ 6 jf7P@’y3GgDrgl:kNWk)twaT95{6䳶ӎ sYsWm:mʐCV :+*KnM/̌jm y^}#F$ƺϵF ~1ͷǯgԏ#d X5`fl}.8|.D5h~ uGavTL)Xd0mY\cNJ'㊗n_ :/72Có|gl\ym fl<`ٗnϦOh,ϸyI_tjiIþS/=R7zh5O@` mz>`'@. &D/13FH=˝Fl$~&Fvq,#dYvW=lYŸ߲om{rpddD,MWށA%y4VzdZwkU5 ʦPU\Q^_<'\VhڡE[R6ұ 8K?`1Gdo/+W@Ł& TZktt8HhUx'dOvN-'~R GR0=cr7O=.INHwcn9e@f].Y30 NodnJ :澩ZM0WDŌnmWo0 Wܘ1pq4|)q FĢw7M_Iu6=,]}y19w`'oѬB[ywKk{]bY+Ii9NL-=]3/YخVk?a]L\׶ Qmt:k~@ ? *v(aMlղ>i*RilOβ@_U;5Rҏo#bN9k'; KF,u j hIJ>BCK(>I紌}ђ4C&5lc~e7>v5y$I3gK/D fۗ:eSke(YDhfke{ g/_ݻ9$u7C}Vڬw*Z w5Dj?ņD(%+T4.Y|gi eFiI ݂=VF ~Ƙ!mhbZbx`o;͓rFGC2@>K8,m0.-#_,Ԅ]}ndrMiDX5ZH.*"fGii h8QkT}1"9x:[ !hW<8: * 4q:G, (|ލݴ{`X`E>;S8X4DD|qHP5M5@[iġH?ʕDR p&bjD\T;$`Rl;I,f|m58ZK/X핓jփֶ@tڶkx^0 |,t^I\ux+z3[՝w6O-ԂcX=EQ\T+whjЋ9 ThPd h%Sjfx%!tFX&Y!K}#iu#G~kxcFx&,C4#?<" wF96a%hLl fK S_+ZcPdD@Z" cB'_._Z{[Ex0>lB!.^Aw*Ō%F1׬%CcYy)o&"Îg<LT1-^`whP@lCM@ieVp: U$Q9 iiE8V-c5ѠHm"딠{ݴB j@MˮUT?eQwȟ'48{eup2r!%ٯn<&a?j?xdA.PqN+ǚ3Ԅ잱 !uR&{ck2Hq*̥ FH#H|#[6<왲1R!ZJ4'k^!MsdZ1:n,x#w؜])V,< 諛CôbP?B'esxBmD`*u$~Dˣqw٦~!{5dj7w͍` Nn^m垨࠰L>+DCg*Bii(8 NOX?!M_ Iݔv5h破sIctro 3@:]ǖN (`i9%$v2@NJfM)hVFͪ+ 4;z #>RaixLx%$bG BV[CHNFA(-o&(TPh_=؈%:6:g2v@\]+z:2z'kŘ Ɩ΃{F7c 氽IIn,Y9|\dҩ#ZZ+ > S"Y-hU4Շ xc@n$jOP9J 3!at[nz`ֿ_ѣzh iDrL{^Z]6-fjNيK>K6ag՘nRGa&EڛPgbxyIlVOFV n k`jD{`s1 ڏoe#^)΍46^0CF#m9%W-Vڻm9)|Vxk&[hSh!-I~>4pKn9(8@Qɛ ͋-X\;2ݖ$w[Ӊ,9k|Nrgl'?̴fLٯk R@,hZ8J*دnGe)褢$,sN4ZtuEb 0ScPiWk؜b}v86[qq1.!!|F;a.7a'0MRaN"{;_<" xz][v;wht|~}w<{awq{l(><1׷_ 㚡y{'eJ#/Mh_f`YhP a.+Q4+Ik%/v?cI$%Wn^B.vH(d2nŭo;3AoA2YMKV#vxR1)b .EڢVK}bp';FD4=]%=}2}zG7nкWvOsL;ڴvoJ;!MTi zZ\}B~p4&&>KJ漢jlOp힮}>%֡u#d[V9<qa7nr\u);T]xx׮<#S9uR/ ųآ1AY*.o%6i:.$<.vk߾=yiq׮ݾ[fMqZ:y:Bg&̇Xqۉ уp3 hUO)flڦbӮcW 6#D`S5űU|>24  Nf8>L)ZU5 SI0duf Ɛc~^U]4)+)5aQh^8>ydk=N$HKs+)BH*$iirߙVhhRCxc㺇R^_ч)ɻ& VY]Wԯ6NCDλиw;A:-àfMp ϖ[hNK,:@Q,>TwĶxiJuVb.>Xv)AbQ a9ʲ?V@,:Q'B Fxb| ~уI 7]V옩bҪ f6j4>kE_) f`|thO]NI{2 G%36@0$ D`;.['&%_%;̠7yOcտx `slRـ uGf1hV3䬉`h||k'9/(_U/~xS 5蹺} FI`PCÌ fݻ`Vz-N kdLe "7y8t |f]۟/X4 [t9;p)/y#ü@~- d,GZ:S.{Jɳ0.xc`d```dp\SO 9_@ f 5!j`aXjz4#f1Y yI!+VĮl~(zP T;uv43 ' A g {c P<n@ H !‡Q^@,*L@ gE ,G 0  'G(Ƅ&dAg@)` sP!HO?*R|xc``Ђ4% cc *s̘f0azǬ5MͅS8 88pqqpupǎg o*>+.;7|: NJ%,&'#$KTMtX-q6M$u$gH򒪑&uMꉴt6_dddȼ5=$' %FM~G l Z n y >)f)QrR:\NEDe3U9TߩEuS{Χ~NB#Ec<7ڛݧL/Mo/}+ mju8v'O02YdbfL,l9y?[,%,#,Y^jfc]b}&掭 ;;=v g909$8lqTrpùK<QˆxڭRN@4I8 "xFѣT(BTgŃG@ç`]$ivw|S/h+zX1#óQn“jO O!+#Px3g "=!߇ahn]w,囖&<5ب#G4Ha·vp];Ă߀\>Ɛ/wUUΗdn(mzWC}=G۸aEbgRz)WSiyDSy=u2XtX\@ j NUqrX#X-P>&]~6};ʵϪ@B 2W32,2c ɈxmUהeFـ`ݭs(*vwwwwawwǁ?Ÿ^3kf?5k:c: :E?;0333q,x&(8K$R,2,XXXUXX5XXuXX ؘؐMؔ؜-ؒ zT4ٚmؖ؞ؑ2`م]ٍك=ًه}ُ999C99#99c999iLg's 39Yf19yY|\|.B.b.R.rJjZznFnfnVnvNn^~AaQqIiYy^E^e^U^coo __ ?? i3ϙ fvS; pn-ʭmݾ;ypqysgFna_jxx<|xk'vb'k/b/bW+ {^aW+J{^iW+*{^eW٫U*{^mS۩vj;Nc^^ck5{^kkZ{^^^^^^^^؋>­{;GGGGGGGGGGG}tG}tG}tݧ?????????; =]3@KPXYF+X!YKRX!Y+\XY+RXpat-0.15.1/web/dist/index.html000066400000000000000000000655571453425652100161300ustar00rootroot00000000000000 {{.AppName}} - Mailbox
pat-0.15.1/web/dist/js/000077500000000000000000000000001453425652100145255ustar00rootroot00000000000000pat-0.15.1/web/dist/js/app.js000066400000000000000000007155271453425652100156640ustar00rootroot00000000000000/*! For license information please see app.js.LICENSE.txt */ !function(t){var e={};function n(i){if(e[i])return e[i].exports;var o=e[i]={i:i,l:!1,exports:{}};return t[i].call(o.exports,o,o.exports,n),o.l=!0,o.exports}n.m=t,n.c=e,n.d=function(t,e,i){n.o(t,e)||Object.defineProperty(t,e,{enumerable:!0,get:i})},n.r=function(t){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(t,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(t,"__esModule",{value:!0})},n.t=function(t,e){if(1&e&&(t=n(t)),8&e)return t;if(4&e&&"object"==typeof t&&t&&t.__esModule)return t;var i=Object.create(null);if(n.r(i),Object.defineProperty(i,"default",{enumerable:!0,value:t}),2&e&&"string"!=typeof t)for(var o in t)n.d(i,o,function(e){return t[e]}.bind(null,o));return i},n.n=function(t){var e=t&&t.__esModule?function(){return t.default}:function(){return t};return n.d(e,"a",e),e},n.o=function(t,e){return Object.prototype.hasOwnProperty.call(t,e)},n.p="../",n(n.s=5)}([function(t,e,n){var i;!function(e,n){"use strict";"object"==typeof t.exports?t.exports=e.document?n(e,!0):function(t){if(!t.document)throw new Error("jQuery requires a window with a document");return n(t)}:n(e)}("undefined"!=typeof window?window:this,(function(n,o){"use strict";var r=[],s=Object.getPrototypeOf,a=r.slice,l=r.flat?function(t){return r.flat.call(t)}:function(t){return r.concat.apply([],t)},c=r.push,u=r.indexOf,p={},d=p.toString,h=p.hasOwnProperty,f=h.toString,m=f.call(Object),g={},v=function(t){return"function"==typeof t&&"number"!=typeof t.nodeType&&"function"!=typeof t.item},y=function(t){return null!=t&&t===t.window},b=n.document,x={type:!0,src:!0,nonce:!0,noModule:!0};function w(t,e,n){var i,o,r=(n=n||b).createElement("script");if(r.text=t,e)for(i in x)(o=e[i]||e.getAttribute&&e.getAttribute(i))&&r.setAttribute(i,o);n.head.appendChild(r).parentNode.removeChild(r)}function k(t){return null==t?t+"":"object"==typeof t||"function"==typeof t?p[d.call(t)]||"object":typeof t}var $="3.6.0",C=function(t,e){return new C.fn.init(t,e)};function T(t){var e=!!t&&"length"in t&&t.length,n=k(t);return!v(t)&&!y(t)&&("array"===n||0===e||"number"==typeof e&&e>0&&e-1 in t)}C.fn=C.prototype={jquery:$,constructor:C,length:0,toArray:function(){return a.call(this)},get:function(t){return null==t?a.call(this):t<0?this[t+this.length]:this[t]},pushStack:function(t){var e=C.merge(this.constructor(),t);return e.prevObject=this,e},each:function(t){return C.each(this,t)},map:function(t){return this.pushStack(C.map(this,(function(e,n){return t.call(e,n,e)})))},slice:function(){return this.pushStack(a.apply(this,arguments))},first:function(){return this.eq(0)},last:function(){return this.eq(-1)},even:function(){return this.pushStack(C.grep(this,(function(t,e){return(e+1)%2})))},odd:function(){return this.pushStack(C.grep(this,(function(t,e){return e%2})))},eq:function(t){var e=this.length,n=+t+(t<0?e:0);return this.pushStack(n>=0&&n+~]|"+L+")"+L+"*"),B=new RegExp(L+"|>"),Q=new RegExp(F),V=new RegExp("^"+z+"$"),G={ID:new RegExp("^#("+z+")"),CLASS:new RegExp("^\\.("+z+")"),TAG:new RegExp("^("+z+"|[*])"),ATTR:new RegExp("^"+R),PSEUDO:new RegExp("^"+F),CHILD:new RegExp("^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\("+L+"*(even|odd|(([+-]|)(\\d*)n|)"+L+"*(?:([+-]|)"+L+"*(\\d+)|))"+L+"*\\)|)","i"),bool:new RegExp("^(?:"+q+")$","i"),needsContext:new RegExp("^"+L+"*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\("+L+"*((?:-\\d)?\\d*)"+L+"*\\)|)(?=[^-]|$)","i")},X=/HTML$/i,J=/^(?:input|select|textarea|button)$/i,K=/^h\d$/i,Y=/^[^{]+\{\s*\[native \w/,Z=/^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/,tt=/[+~]/,et=new RegExp("\\\\[\\da-fA-F]{1,6}"+L+"?|\\\\([^\\r\\n\\f])","g"),nt=function(t,e){var n="0x"+t.slice(1)-65536;return e||(n<0?String.fromCharCode(n+65536):String.fromCharCode(n>>10|55296,1023&n|56320))},it=/([\0-\x1f\x7f]|^-?\d)|^-$|[^\0-\x1f\x7f-\uFFFF\w-]/g,ot=function(t,e){return e?"\0"===t?"�":t.slice(0,-1)+"\\"+t.charCodeAt(t.length-1).toString(16)+" ":"\\"+t},rt=function(){d()},st=xt((function(t){return!0===t.disabled&&"fieldset"===t.nodeName.toLowerCase()}),{dir:"parentNode",next:"legend"});try{N.apply(D=P.call(w.childNodes),w.childNodes),D[w.childNodes.length].nodeType}catch(t){N={apply:D.length?function(t,e){j.apply(t,P.call(e))}:function(t,e){for(var n=t.length,i=0;t[n++]=e[i++];);t.length=n-1}}}function at(t,e,i,o){var r,a,c,u,p,f,v,y=e&&e.ownerDocument,w=e?e.nodeType:9;if(i=i||[],"string"!=typeof t||!t||1!==w&&9!==w&&11!==w)return i;if(!o&&(d(e),e=e||h,m)){if(11!==w&&(p=Z.exec(t)))if(r=p[1]){if(9===w){if(!(c=e.getElementById(r)))return i;if(c.id===r)return i.push(c),i}else if(y&&(c=y.getElementById(r))&&b(e,c)&&c.id===r)return i.push(c),i}else{if(p[2])return N.apply(i,e.getElementsByTagName(t)),i;if((r=p[3])&&n.getElementsByClassName&&e.getElementsByClassName)return N.apply(i,e.getElementsByClassName(r)),i}if(n.qsa&&!_[t+" "]&&(!g||!g.test(t))&&(1!==w||"object"!==e.nodeName.toLowerCase())){if(v=t,y=e,1===w&&(B.test(t)||W.test(t))){for((y=tt.test(t)&&vt(e.parentNode)||e)===e&&n.scope||((u=e.getAttribute("id"))?u=u.replace(it,ot):e.setAttribute("id",u=x)),a=(f=s(t)).length;a--;)f[a]=(u?"#"+u:":scope")+" "+bt(f[a]);v=f.join(",")}try{return N.apply(i,y.querySelectorAll(v)),i}catch(e){_(t,!0)}finally{u===x&&e.removeAttribute("id")}}}return l(t.replace(M,"$1"),e,i,o)}function lt(){var t=[];return function e(n,o){return t.push(n+" ")>i.cacheLength&&delete e[t.shift()],e[n+" "]=o}}function ct(t){return t[x]=!0,t}function ut(t){var e=h.createElement("fieldset");try{return!!t(e)}catch(t){return!1}finally{e.parentNode&&e.parentNode.removeChild(e),e=null}}function pt(t,e){for(var n=t.split("|"),o=n.length;o--;)i.attrHandle[n[o]]=e}function dt(t,e){var n=e&&t,i=n&&1===t.nodeType&&1===e.nodeType&&t.sourceIndex-e.sourceIndex;if(i)return i;if(n)for(;n=n.nextSibling;)if(n===e)return-1;return t?1:-1}function ht(t){return function(e){return"input"===e.nodeName.toLowerCase()&&e.type===t}}function ft(t){return function(e){var n=e.nodeName.toLowerCase();return("input"===n||"button"===n)&&e.type===t}}function mt(t){return function(e){return"form"in e?e.parentNode&&!1===e.disabled?"label"in e?"label"in e.parentNode?e.parentNode.disabled===t:e.disabled===t:e.isDisabled===t||e.isDisabled!==!t&&st(e)===t:e.disabled===t:"label"in e&&e.disabled===t}}function gt(t){return ct((function(e){return e=+e,ct((function(n,i){for(var o,r=t([],n.length,e),s=r.length;s--;)n[o=r[s]]&&(n[o]=!(i[o]=n[o]))}))}))}function vt(t){return t&&void 0!==t.getElementsByTagName&&t}for(e in n=at.support={},r=at.isXML=function(t){var e=t&&t.namespaceURI,n=t&&(t.ownerDocument||t).documentElement;return!X.test(e||n&&n.nodeName||"HTML")},d=at.setDocument=function(t){var e,o,s=t?t.ownerDocument||t:w;return s!=h&&9===s.nodeType&&s.documentElement?(f=(h=s).documentElement,m=!r(h),w!=h&&(o=h.defaultView)&&o.top!==o&&(o.addEventListener?o.addEventListener("unload",rt,!1):o.attachEvent&&o.attachEvent("onunload",rt)),n.scope=ut((function(t){return f.appendChild(t).appendChild(h.createElement("div")),void 0!==t.querySelectorAll&&!t.querySelectorAll(":scope fieldset div").length})),n.attributes=ut((function(t){return t.className="i",!t.getAttribute("className")})),n.getElementsByTagName=ut((function(t){return t.appendChild(h.createComment("")),!t.getElementsByTagName("*").length})),n.getElementsByClassName=Y.test(h.getElementsByClassName),n.getById=ut((function(t){return f.appendChild(t).id=x,!h.getElementsByName||!h.getElementsByName(x).length})),n.getById?(i.filter.ID=function(t){var e=t.replace(et,nt);return function(t){return t.getAttribute("id")===e}},i.find.ID=function(t,e){if(void 0!==e.getElementById&&m){var n=e.getElementById(t);return n?[n]:[]}}):(i.filter.ID=function(t){var e=t.replace(et,nt);return function(t){var n=void 0!==t.getAttributeNode&&t.getAttributeNode("id");return n&&n.value===e}},i.find.ID=function(t,e){if(void 0!==e.getElementById&&m){var n,i,o,r=e.getElementById(t);if(r){if((n=r.getAttributeNode("id"))&&n.value===t)return[r];for(o=e.getElementsByName(t),i=0;r=o[i++];)if((n=r.getAttributeNode("id"))&&n.value===t)return[r]}return[]}}),i.find.TAG=n.getElementsByTagName?function(t,e){return void 0!==e.getElementsByTagName?e.getElementsByTagName(t):n.qsa?e.querySelectorAll(t):void 0}:function(t,e){var n,i=[],o=0,r=e.getElementsByTagName(t);if("*"===t){for(;n=r[o++];)1===n.nodeType&&i.push(n);return i}return r},i.find.CLASS=n.getElementsByClassName&&function(t,e){if(void 0!==e.getElementsByClassName&&m)return e.getElementsByClassName(t)},v=[],g=[],(n.qsa=Y.test(h.querySelectorAll))&&(ut((function(t){var e;f.appendChild(t).innerHTML="",t.querySelectorAll("[msallowcapture^='']").length&&g.push("[*^$]="+L+"*(?:''|\"\")"),t.querySelectorAll("[selected]").length||g.push("\\["+L+"*(?:value|"+q+")"),t.querySelectorAll("[id~="+x+"-]").length||g.push("~="),(e=h.createElement("input")).setAttribute("name",""),t.appendChild(e),t.querySelectorAll("[name='']").length||g.push("\\["+L+"*name"+L+"*="+L+"*(?:''|\"\")"),t.querySelectorAll(":checked").length||g.push(":checked"),t.querySelectorAll("a#"+x+"+*").length||g.push(".#.+[+~]"),t.querySelectorAll("\\\f"),g.push("[\\r\\n\\f]")})),ut((function(t){t.innerHTML="";var e=h.createElement("input");e.setAttribute("type","hidden"),t.appendChild(e).setAttribute("name","D"),t.querySelectorAll("[name=d]").length&&g.push("name"+L+"*[*^$|!~]?="),2!==t.querySelectorAll(":enabled").length&&g.push(":enabled",":disabled"),f.appendChild(t).disabled=!0,2!==t.querySelectorAll(":disabled").length&&g.push(":enabled",":disabled"),t.querySelectorAll("*,:x"),g.push(",.*:")}))),(n.matchesSelector=Y.test(y=f.matches||f.webkitMatchesSelector||f.mozMatchesSelector||f.oMatchesSelector||f.msMatchesSelector))&&ut((function(t){n.disconnectedMatch=y.call(t,"*"),y.call(t,"[s!='']:x"),v.push("!=",F)})),g=g.length&&new RegExp(g.join("|")),v=v.length&&new RegExp(v.join("|")),e=Y.test(f.compareDocumentPosition),b=e||Y.test(f.contains)?function(t,e){var n=9===t.nodeType?t.documentElement:t,i=e&&e.parentNode;return t===i||!(!i||1!==i.nodeType||!(n.contains?n.contains(i):t.compareDocumentPosition&&16&t.compareDocumentPosition(i)))}:function(t,e){if(e)for(;e=e.parentNode;)if(e===t)return!0;return!1},E=e?function(t,e){if(t===e)return p=!0,0;var i=!t.compareDocumentPosition-!e.compareDocumentPosition;return i||(1&(i=(t.ownerDocument||t)==(e.ownerDocument||e)?t.compareDocumentPosition(e):1)||!n.sortDetached&&e.compareDocumentPosition(t)===i?t==h||t.ownerDocument==w&&b(w,t)?-1:e==h||e.ownerDocument==w&&b(w,e)?1:u?O(u,t)-O(u,e):0:4&i?-1:1)}:function(t,e){if(t===e)return p=!0,0;var n,i=0,o=t.parentNode,r=e.parentNode,s=[t],a=[e];if(!o||!r)return t==h?-1:e==h?1:o?-1:r?1:u?O(u,t)-O(u,e):0;if(o===r)return dt(t,e);for(n=t;n=n.parentNode;)s.unshift(n);for(n=e;n=n.parentNode;)a.unshift(n);for(;s[i]===a[i];)i++;return i?dt(s[i],a[i]):s[i]==w?-1:a[i]==w?1:0},h):h},at.matches=function(t,e){return at(t,null,null,e)},at.matchesSelector=function(t,e){if(d(t),n.matchesSelector&&m&&!_[e+" "]&&(!v||!v.test(e))&&(!g||!g.test(e)))try{var i=y.call(t,e);if(i||n.disconnectedMatch||t.document&&11!==t.document.nodeType)return i}catch(t){_(e,!0)}return at(e,h,null,[t]).length>0},at.contains=function(t,e){return(t.ownerDocument||t)!=h&&d(t),b(t,e)},at.attr=function(t,e){(t.ownerDocument||t)!=h&&d(t);var o=i.attrHandle[e.toLowerCase()],r=o&&A.call(i.attrHandle,e.toLowerCase())?o(t,e,!m):void 0;return void 0!==r?r:n.attributes||!m?t.getAttribute(e):(r=t.getAttributeNode(e))&&r.specified?r.value:null},at.escape=function(t){return(t+"").replace(it,ot)},at.error=function(t){throw new Error("Syntax error, unrecognized expression: "+t)},at.uniqueSort=function(t){var e,i=[],o=0,r=0;if(p=!n.detectDuplicates,u=!n.sortStable&&t.slice(0),t.sort(E),p){for(;e=t[r++];)e===t[r]&&(o=i.push(r));for(;o--;)t.splice(i[o],1)}return u=null,t},o=at.getText=function(t){var e,n="",i=0,r=t.nodeType;if(r){if(1===r||9===r||11===r){if("string"==typeof t.textContent)return t.textContent;for(t=t.firstChild;t;t=t.nextSibling)n+=o(t)}else if(3===r||4===r)return t.nodeValue}else for(;e=t[i++];)n+=o(e);return n},i=at.selectors={cacheLength:50,createPseudo:ct,match:G,attrHandle:{},find:{},relative:{">":{dir:"parentNode",first:!0}," ":{dir:"parentNode"},"+":{dir:"previousSibling",first:!0},"~":{dir:"previousSibling"}},preFilter:{ATTR:function(t){return t[1]=t[1].replace(et,nt),t[3]=(t[3]||t[4]||t[5]||"").replace(et,nt),"~="===t[2]&&(t[3]=" "+t[3]+" "),t.slice(0,4)},CHILD:function(t){return t[1]=t[1].toLowerCase(),"nth"===t[1].slice(0,3)?(t[3]||at.error(t[0]),t[4]=+(t[4]?t[5]+(t[6]||1):2*("even"===t[3]||"odd"===t[3])),t[5]=+(t[7]+t[8]||"odd"===t[3])):t[3]&&at.error(t[0]),t},PSEUDO:function(t){var e,n=!t[6]&&t[2];return G.CHILD.test(t[0])?null:(t[3]?t[2]=t[4]||t[5]||"":n&&Q.test(n)&&(e=s(n,!0))&&(e=n.indexOf(")",n.length-e)-n.length)&&(t[0]=t[0].slice(0,e),t[2]=n.slice(0,e)),t.slice(0,3))}},filter:{TAG:function(t){var e=t.replace(et,nt).toLowerCase();return"*"===t?function(){return!0}:function(t){return t.nodeName&&t.nodeName.toLowerCase()===e}},CLASS:function(t){var e=C[t+" "];return e||(e=new RegExp("(^|"+L+")"+t+"("+L+"|$)"))&&C(t,(function(t){return e.test("string"==typeof t.className&&t.className||void 0!==t.getAttribute&&t.getAttribute("class")||"")}))},ATTR:function(t,e,n){return function(i){var o=at.attr(i,t);return null==o?"!="===e:!e||(o+="","="===e?o===n:"!="===e?o!==n:"^="===e?n&&0===o.indexOf(n):"*="===e?n&&o.indexOf(n)>-1:"$="===e?n&&o.slice(-n.length)===n:"~="===e?(" "+o.replace(H," ")+" ").indexOf(n)>-1:"|="===e&&(o===n||o.slice(0,n.length+1)===n+"-"))}},CHILD:function(t,e,n,i,o){var r="nth"!==t.slice(0,3),s="last"!==t.slice(-4),a="of-type"===e;return 1===i&&0===o?function(t){return!!t.parentNode}:function(e,n,l){var c,u,p,d,h,f,m=r!==s?"nextSibling":"previousSibling",g=e.parentNode,v=a&&e.nodeName.toLowerCase(),y=!l&&!a,b=!1;if(g){if(r){for(;m;){for(d=e;d=d[m];)if(a?d.nodeName.toLowerCase()===v:1===d.nodeType)return!1;f=m="only"===t&&!f&&"nextSibling"}return!0}if(f=[s?g.firstChild:g.lastChild],s&&y){for(b=(h=(c=(u=(p=(d=g)[x]||(d[x]={}))[d.uniqueID]||(p[d.uniqueID]={}))[t]||[])[0]===k&&c[1])&&c[2],d=h&&g.childNodes[h];d=++h&&d&&d[m]||(b=h=0)||f.pop();)if(1===d.nodeType&&++b&&d===e){u[t]=[k,h,b];break}}else if(y&&(b=h=(c=(u=(p=(d=e)[x]||(d[x]={}))[d.uniqueID]||(p[d.uniqueID]={}))[t]||[])[0]===k&&c[1]),!1===b)for(;(d=++h&&d&&d[m]||(b=h=0)||f.pop())&&((a?d.nodeName.toLowerCase()!==v:1!==d.nodeType)||!++b||(y&&((u=(p=d[x]||(d[x]={}))[d.uniqueID]||(p[d.uniqueID]={}))[t]=[k,b]),d!==e)););return(b-=o)===i||b%i==0&&b/i>=0}}},PSEUDO:function(t,e){var n,o=i.pseudos[t]||i.setFilters[t.toLowerCase()]||at.error("unsupported pseudo: "+t);return o[x]?o(e):o.length>1?(n=[t,t,"",e],i.setFilters.hasOwnProperty(t.toLowerCase())?ct((function(t,n){for(var i,r=o(t,e),s=r.length;s--;)t[i=O(t,r[s])]=!(n[i]=r[s])})):function(t){return o(t,0,n)}):o}},pseudos:{not:ct((function(t){var e=[],n=[],i=a(t.replace(M,"$1"));return i[x]?ct((function(t,e,n,o){for(var r,s=i(t,null,o,[]),a=t.length;a--;)(r=s[a])&&(t[a]=!(e[a]=r))})):function(t,o,r){return e[0]=t,i(e,null,r,n),e[0]=null,!n.pop()}})),has:ct((function(t){return function(e){return at(t,e).length>0}})),contains:ct((function(t){return t=t.replace(et,nt),function(e){return(e.textContent||o(e)).indexOf(t)>-1}})),lang:ct((function(t){return V.test(t||"")||at.error("unsupported lang: "+t),t=t.replace(et,nt).toLowerCase(),function(e){var n;do{if(n=m?e.lang:e.getAttribute("xml:lang")||e.getAttribute("lang"))return(n=n.toLowerCase())===t||0===n.indexOf(t+"-")}while((e=e.parentNode)&&1===e.nodeType);return!1}})),target:function(e){var n=t.location&&t.location.hash;return n&&n.slice(1)===e.id},root:function(t){return t===f},focus:function(t){return t===h.activeElement&&(!h.hasFocus||h.hasFocus())&&!!(t.type||t.href||~t.tabIndex)},enabled:mt(!1),disabled:mt(!0),checked:function(t){var e=t.nodeName.toLowerCase();return"input"===e&&!!t.checked||"option"===e&&!!t.selected},selected:function(t){return t.parentNode&&t.parentNode.selectedIndex,!0===t.selected},empty:function(t){for(t=t.firstChild;t;t=t.nextSibling)if(t.nodeType<6)return!1;return!0},parent:function(t){return!i.pseudos.empty(t)},header:function(t){return K.test(t.nodeName)},input:function(t){return J.test(t.nodeName)},button:function(t){var e=t.nodeName.toLowerCase();return"input"===e&&"button"===t.type||"button"===e},text:function(t){var e;return"input"===t.nodeName.toLowerCase()&&"text"===t.type&&(null==(e=t.getAttribute("type"))||"text"===e.toLowerCase())},first:gt((function(){return[0]})),last:gt((function(t,e){return[e-1]})),eq:gt((function(t,e,n){return[n<0?n+e:n]})),even:gt((function(t,e){for(var n=0;ne?e:n;--i>=0;)t.push(i);return t})),gt:gt((function(t,e,n){for(var i=n<0?n+e:n;++i1?function(e,n,i){for(var o=t.length;o--;)if(!t[o](e,n,i))return!1;return!0}:t[0]}function kt(t,e,n,i,o){for(var r,s=[],a=0,l=t.length,c=null!=e;a-1&&(r[c]=!(s[c]=p))}}else v=kt(v===s?v.splice(f,v.length):v),o?o(null,s,v,l):N.apply(s,v)}))}function Ct(t){for(var e,n,o,r=t.length,s=i.relative[t[0].type],a=s||i.relative[" "],l=s?1:0,u=xt((function(t){return t===e}),a,!0),p=xt((function(t){return O(e,t)>-1}),a,!0),d=[function(t,n,i){var o=!s&&(i||n!==c)||((e=n).nodeType?u(t,n,i):p(t,n,i));return e=null,o}];l1&&wt(d),l>1&&bt(t.slice(0,l-1).concat({value:" "===t[l-2].type?"*":""})).replace(M,"$1"),n,l0,o=t.length>0,r=function(r,s,a,l,u){var p,f,g,v=0,y="0",b=r&&[],x=[],w=c,$=r||o&&i.find.TAG("*",u),C=k+=null==w?1:Math.random()||.1,T=$.length;for(u&&(c=s==h||s||u);y!==T&&null!=(p=$[y]);y++){if(o&&p){for(f=0,s||p.ownerDocument==h||(d(p),a=!m);g=t[f++];)if(g(p,s||h,a)){l.push(p);break}u&&(k=C)}n&&((p=!g&&p)&&v--,r&&b.push(p))}if(v+=y,n&&y!==v){for(f=0;g=e[f++];)g(b,x,s,a);if(r){if(v>0)for(;y--;)b[y]||x[y]||(x[y]=I.call(l));x=kt(x)}N.apply(l,x),u&&!r&&x.length>0&&v+e.length>1&&at.uniqueSort(l)}return u&&(k=C,c=w),b};return n?ct(r):r}(r,o)),a.selector=t}return a},l=at.select=function(t,e,n,o){var r,l,c,u,p,d="function"==typeof t&&t,h=!o&&s(t=d.selector||t);if(n=n||[],1===h.length){if((l=h[0]=h[0].slice(0)).length>2&&"ID"===(c=l[0]).type&&9===e.nodeType&&m&&i.relative[l[1].type]){if(!(e=(i.find.ID(c.matches[0].replace(et,nt),e)||[])[0]))return n;d&&(e=e.parentNode),t=t.slice(l.shift().value.length)}for(r=G.needsContext.test(t)?0:l.length;r--&&(c=l[r],!i.relative[u=c.type]);)if((p=i.find[u])&&(o=p(c.matches[0].replace(et,nt),tt.test(l[0].type)&&vt(e.parentNode)||e))){if(l.splice(r,1),!(t=o.length&&bt(l)))return N.apply(n,o),n;break}}return(d||a(t,h))(o,e,!m,n,!e||tt.test(t)&&vt(e.parentNode)||e),n},n.sortStable=x.split("").sort(E).join("")===x,n.detectDuplicates=!!p,d(),n.sortDetached=ut((function(t){return 1&t.compareDocumentPosition(h.createElement("fieldset"))})),ut((function(t){return t.innerHTML="","#"===t.firstChild.getAttribute("href")}))||pt("type|href|height|width",(function(t,e,n){if(!n)return t.getAttribute(e,"type"===e.toLowerCase()?1:2)})),n.attributes&&ut((function(t){return t.innerHTML="",t.firstChild.setAttribute("value",""),""===t.firstChild.getAttribute("value")}))||pt("value",(function(t,e,n){if(!n&&"input"===t.nodeName.toLowerCase())return t.defaultValue})),ut((function(t){return null==t.getAttribute("disabled")}))||pt(q,(function(t,e,n){var i;if(!n)return!0===t[e]?e.toLowerCase():(i=t.getAttributeNode(e))&&i.specified?i.value:null})),at}(n);C.find=S,C.expr=S.selectors,C.expr[":"]=C.expr.pseudos,C.uniqueSort=C.unique=S.uniqueSort,C.text=S.getText,C.isXMLDoc=S.isXML,C.contains=S.contains,C.escapeSelector=S.escape;var _=function(t,e,n){for(var i=[],o=void 0!==n;(t=t[e])&&9!==t.nodeType;)if(1===t.nodeType){if(o&&C(t).is(n))break;i.push(t)}return i},E=function(t,e){for(var n=[];t;t=t.nextSibling)1===t.nodeType&&t!==e&&n.push(t);return n},A=C.expr.match.needsContext;function D(t,e){return t.nodeName&&t.nodeName.toLowerCase()===e.toLowerCase()}var I=/^<([a-z][^\/\0>:\x20\t\r\n\f]*)[\x20\t\r\n\f]*\/?>(?:<\/\1>|)$/i;function j(t,e,n){return v(e)?C.grep(t,(function(t,i){return!!e.call(t,i,t)!==n})):e.nodeType?C.grep(t,(function(t){return t===e!==n})):"string"!=typeof e?C.grep(t,(function(t){return u.call(e,t)>-1!==n})):C.filter(e,t,n)}C.filter=function(t,e,n){var i=e[0];return n&&(t=":not("+t+")"),1===e.length&&1===i.nodeType?C.find.matchesSelector(i,t)?[i]:[]:C.find.matches(t,C.grep(e,(function(t){return 1===t.nodeType})))},C.fn.extend({find:function(t){var e,n,i=this.length,o=this;if("string"!=typeof t)return this.pushStack(C(t).filter((function(){for(e=0;e1?C.uniqueSort(n):n},filter:function(t){return this.pushStack(j(this,t||[],!1))},not:function(t){return this.pushStack(j(this,t||[],!0))},is:function(t){return!!j(this,"string"==typeof t&&A.test(t)?C(t):t||[],!1).length}});var N,P=/^(?:\s*(<[\w\W]+>)[^>]*|#([\w-]+))$/;(C.fn.init=function(t,e,n){var i,o;if(!t)return this;if(n=n||N,"string"==typeof t){if(!(i="<"===t[0]&&">"===t[t.length-1]&&t.length>=3?[null,t,null]:P.exec(t))||!i[1]&&e)return!e||e.jquery?(e||n).find(t):this.constructor(e).find(t);if(i[1]){if(e=e instanceof C?e[0]:e,C.merge(this,C.parseHTML(i[1],e&&e.nodeType?e.ownerDocument||e:b,!0)),I.test(i[1])&&C.isPlainObject(e))for(i in e)v(this[i])?this[i](e[i]):this.attr(i,e[i]);return this}return(o=b.getElementById(i[2]))&&(this[0]=o,this.length=1),this}return t.nodeType?(this[0]=t,this.length=1,this):v(t)?void 0!==n.ready?n.ready(t):t(C):C.makeArray(t,this)}).prototype=C.fn,N=C(b);var O=/^(?:parents|prev(?:Until|All))/,q={children:!0,contents:!0,next:!0,prev:!0};function L(t,e){for(;(t=t[e])&&1!==t.nodeType;);return t}C.fn.extend({has:function(t){var e=C(t,this),n=e.length;return this.filter((function(){for(var t=0;t-1:1===n.nodeType&&C.find.matchesSelector(n,t))){r.push(n);break}return this.pushStack(r.length>1?C.uniqueSort(r):r)},index:function(t){return t?"string"==typeof t?u.call(C(t),this[0]):u.call(this,t.jquery?t[0]:t):this[0]&&this[0].parentNode?this.first().prevAll().length:-1},add:function(t,e){return this.pushStack(C.uniqueSort(C.merge(this.get(),C(t,e))))},addBack:function(t){return this.add(null==t?this.prevObject:this.prevObject.filter(t))}}),C.each({parent:function(t){var e=t.parentNode;return e&&11!==e.nodeType?e:null},parents:function(t){return _(t,"parentNode")},parentsUntil:function(t,e,n){return _(t,"parentNode",n)},next:function(t){return L(t,"nextSibling")},prev:function(t){return L(t,"previousSibling")},nextAll:function(t){return _(t,"nextSibling")},prevAll:function(t){return _(t,"previousSibling")},nextUntil:function(t,e,n){return _(t,"nextSibling",n)},prevUntil:function(t,e,n){return _(t,"previousSibling",n)},siblings:function(t){return E((t.parentNode||{}).firstChild,t)},children:function(t){return E(t.firstChild)},contents:function(t){return null!=t.contentDocument&&s(t.contentDocument)?t.contentDocument:(D(t,"template")&&(t=t.content||t),C.merge([],t.childNodes))}},(function(t,e){C.fn[t]=function(n,i){var o=C.map(this,e,n);return"Until"!==t.slice(-5)&&(i=n),i&&"string"==typeof i&&(o=C.filter(i,o)),this.length>1&&(q[t]||C.uniqueSort(o),O.test(t)&&o.reverse()),this.pushStack(o)}}));var z=/[^\x20\t\r\n\f]+/g;function R(t){return t}function F(t){throw t}function H(t,e,n,i){var o;try{t&&v(o=t.promise)?o.call(t).done(e).fail(n):t&&v(o=t.then)?o.call(t,e,n):e.apply(void 0,[t].slice(i))}catch(t){n.apply(void 0,[t])}}C.Callbacks=function(t){t="string"==typeof t?function(t){var e={};return C.each(t.match(z)||[],(function(t,n){e[n]=!0})),e}(t):C.extend({},t);var e,n,i,o,r=[],s=[],a=-1,l=function(){for(o=o||t.once,i=e=!0;s.length;a=-1)for(n=s.shift();++a-1;)r.splice(n,1),n<=a&&a--})),this},has:function(t){return t?C.inArray(t,r)>-1:r.length>0},empty:function(){return r&&(r=[]),this},disable:function(){return o=s=[],r=n="",this},disabled:function(){return!r},lock:function(){return o=s=[],n||e||(r=n=""),this},locked:function(){return!!o},fireWith:function(t,n){return o||(n=[t,(n=n||[]).slice?n.slice():n],s.push(n),e||l()),this},fire:function(){return c.fireWith(this,arguments),this},fired:function(){return!!i}};return c},C.extend({Deferred:function(t){var e=[["notify","progress",C.Callbacks("memory"),C.Callbacks("memory"),2],["resolve","done",C.Callbacks("once memory"),C.Callbacks("once memory"),0,"resolved"],["reject","fail",C.Callbacks("once memory"),C.Callbacks("once memory"),1,"rejected"]],i="pending",o={state:function(){return i},always:function(){return r.done(arguments).fail(arguments),this},catch:function(t){return o.then(null,t)},pipe:function(){var t=arguments;return C.Deferred((function(n){C.each(e,(function(e,i){var o=v(t[i[4]])&&t[i[4]];r[i[1]]((function(){var t=o&&o.apply(this,arguments);t&&v(t.promise)?t.promise().progress(n.notify).done(n.resolve).fail(n.reject):n[i[0]+"With"](this,o?[t]:arguments)}))})),t=null})).promise()},then:function(t,i,o){var r=0;function s(t,e,i,o){return function(){var a=this,l=arguments,c=function(){var n,c;if(!(t=r&&(i!==F&&(a=void 0,l=[n]),e.rejectWith(a,l))}};t?u():(C.Deferred.getStackHook&&(u.stackTrace=C.Deferred.getStackHook()),n.setTimeout(u))}}return C.Deferred((function(n){e[0][3].add(s(0,n,v(o)?o:R,n.notifyWith)),e[1][3].add(s(0,n,v(t)?t:R)),e[2][3].add(s(0,n,v(i)?i:F))})).promise()},promise:function(t){return null!=t?C.extend(t,o):o}},r={};return C.each(e,(function(t,n){var s=n[2],a=n[5];o[n[1]]=s.add,a&&s.add((function(){i=a}),e[3-t][2].disable,e[3-t][3].disable,e[0][2].lock,e[0][3].lock),s.add(n[3].fire),r[n[0]]=function(){return r[n[0]+"With"](this===r?void 0:this,arguments),this},r[n[0]+"With"]=s.fireWith})),o.promise(r),t&&t.call(r,r),r},when:function(t){var e=arguments.length,n=e,i=Array(n),o=a.call(arguments),r=C.Deferred(),s=function(t){return function(n){i[t]=this,o[t]=arguments.length>1?a.call(arguments):n,--e||r.resolveWith(i,o)}};if(e<=1&&(H(t,r.done(s(n)).resolve,r.reject,!e),"pending"===r.state()||v(o[n]&&o[n].then)))return r.then();for(;n--;)H(o[n],s(n),r.reject);return r.promise()}});var M=/^(Eval|Internal|Range|Reference|Syntax|Type|URI)Error$/;C.Deferred.exceptionHook=function(t,e){n.console&&n.console.warn&&t&&M.test(t.name)&&n.console.warn("jQuery.Deferred exception: "+t.message,t.stack,e)},C.readyException=function(t){n.setTimeout((function(){throw t}))};var U=C.Deferred();function W(){b.removeEventListener("DOMContentLoaded",W),n.removeEventListener("load",W),C.ready()}C.fn.ready=function(t){return U.then(t).catch((function(t){C.readyException(t)})),this},C.extend({isReady:!1,readyWait:1,ready:function(t){(!0===t?--C.readyWait:C.isReady)||(C.isReady=!0,!0!==t&&--C.readyWait>0||U.resolveWith(b,[C]))}}),C.ready.then=U.then,"complete"===b.readyState||"loading"!==b.readyState&&!b.documentElement.doScroll?n.setTimeout(C.ready):(b.addEventListener("DOMContentLoaded",W),n.addEventListener("load",W));var B=function(t,e,n,i,o,r,s){var a=0,l=t.length,c=null==n;if("object"===k(n))for(a in o=!0,n)B(t,e,a,n[a],!0,r,s);else if(void 0!==i&&(o=!0,v(i)||(s=!0),c&&(s?(e.call(t,i),e=null):(c=e,e=function(t,e,n){return c.call(C(t),n)})),e))for(;a1,null,!0)},removeData:function(t){return this.each((function(){Z.remove(this,t)}))}}),C.extend({queue:function(t,e,n){var i;if(t)return e=(e||"fx")+"queue",i=Y.get(t,e),n&&(!i||Array.isArray(n)?i=Y.access(t,e,C.makeArray(n)):i.push(n)),i||[]},dequeue:function(t,e){e=e||"fx";var n=C.queue(t,e),i=n.length,o=n.shift(),r=C._queueHooks(t,e);"inprogress"===o&&(o=n.shift(),i--),o&&("fx"===e&&n.unshift("inprogress"),delete r.stop,o.call(t,(function(){C.dequeue(t,e)}),r)),!i&&r&&r.empty.fire()},_queueHooks:function(t,e){var n=e+"queueHooks";return Y.get(t,n)||Y.access(t,n,{empty:C.Callbacks("once memory").add((function(){Y.remove(t,[e+"queue",n])}))})}}),C.fn.extend({queue:function(t,e){var n=2;return"string"!=typeof t&&(e=t,t="fx",n--),arguments.length\x20\t\r\n\f]*)/i,yt=/^$|^module$|\/(?:java|ecma)script/i;ft=b.createDocumentFragment().appendChild(b.createElement("div")),(mt=b.createElement("input")).setAttribute("type","radio"),mt.setAttribute("checked","checked"),mt.setAttribute("name","t"),ft.appendChild(mt),g.checkClone=ft.cloneNode(!0).cloneNode(!0).lastChild.checked,ft.innerHTML="",g.noCloneChecked=!!ft.cloneNode(!0).lastChild.defaultValue,ft.innerHTML="",g.option=!!ft.lastChild;var bt={thead:[1,"","
"],col:[2,"","
"],tr:[2,"","
"],td:[3,"","
"],_default:[0,"",""]};function xt(t,e){var n;return n=void 0!==t.getElementsByTagName?t.getElementsByTagName(e||"*"):void 0!==t.querySelectorAll?t.querySelectorAll(e||"*"):[],void 0===e||e&&D(t,e)?C.merge([t],n):n}function wt(t,e){for(var n=0,i=t.length;n",""]);var kt=/<|&#?\w+;/;function $t(t,e,n,i,o){for(var r,s,a,l,c,u,p=e.createDocumentFragment(),d=[],h=0,f=t.length;h-1)o&&o.push(r);else if(c=at(r),s=xt(p.appendChild(r),"script"),c&&wt(s),n)for(u=0;r=s[u++];)yt.test(r.type||"")&&n.push(r);return p}var Ct=/^([^.]*)(?:\.(.+)|)/;function Tt(){return!0}function St(){return!1}function _t(t,e){return t===function(){try{return b.activeElement}catch(t){}}()==("focus"===e)}function Et(t,e,n,i,o,r){var s,a;if("object"==typeof e){for(a in"string"!=typeof n&&(i=i||n,n=void 0),e)Et(t,a,n,i,e[a],r);return t}if(null==i&&null==o?(o=n,i=n=void 0):null==o&&("string"==typeof n?(o=i,i=void 0):(o=i,i=n,n=void 0)),!1===o)o=St;else if(!o)return t;return 1===r&&(s=o,o=function(t){return C().off(t),s.apply(this,arguments)},o.guid=s.guid||(s.guid=C.guid++)),t.each((function(){C.event.add(this,e,o,i,n)}))}function At(t,e,n){n?(Y.set(t,e,!1),C.event.add(t,e,{namespace:!1,handler:function(t){var i,o,r=Y.get(this,e);if(1&t.isTrigger&&this[e]){if(r.length)(C.event.special[e]||{}).delegateType&&t.stopPropagation();else if(r=a.call(arguments),Y.set(this,e,r),i=n(this,e),this[e](),r!==(o=Y.get(this,e))||i?Y.set(this,e,!1):o={},r!==o)return t.stopImmediatePropagation(),t.preventDefault(),o&&o.value}else r.length&&(Y.set(this,e,{value:C.event.trigger(C.extend(r[0],C.Event.prototype),r.slice(1),this)}),t.stopImmediatePropagation())}})):void 0===Y.get(t,e)&&C.event.add(t,e,Tt)}C.event={global:{},add:function(t,e,n,i,o){var r,s,a,l,c,u,p,d,h,f,m,g=Y.get(t);if(J(t))for(n.handler&&(n=(r=n).handler,o=r.selector),o&&C.find.matchesSelector(st,o),n.guid||(n.guid=C.guid++),(l=g.events)||(l=g.events=Object.create(null)),(s=g.handle)||(s=g.handle=function(e){return void 0!==C&&C.event.triggered!==e.type?C.event.dispatch.apply(t,arguments):void 0}),c=(e=(e||"").match(z)||[""]).length;c--;)h=m=(a=Ct.exec(e[c])||[])[1],f=(a[2]||"").split(".").sort(),h&&(p=C.event.special[h]||{},h=(o?p.delegateType:p.bindType)||h,p=C.event.special[h]||{},u=C.extend({type:h,origType:m,data:i,handler:n,guid:n.guid,selector:o,needsContext:o&&C.expr.match.needsContext.test(o),namespace:f.join(".")},r),(d=l[h])||((d=l[h]=[]).delegateCount=0,p.setup&&!1!==p.setup.call(t,i,f,s)||t.addEventListener&&t.addEventListener(h,s)),p.add&&(p.add.call(t,u),u.handler.guid||(u.handler.guid=n.guid)),o?d.splice(d.delegateCount++,0,u):d.push(u),C.event.global[h]=!0)},remove:function(t,e,n,i,o){var r,s,a,l,c,u,p,d,h,f,m,g=Y.hasData(t)&&Y.get(t);if(g&&(l=g.events)){for(c=(e=(e||"").match(z)||[""]).length;c--;)if(h=m=(a=Ct.exec(e[c])||[])[1],f=(a[2]||"").split(".").sort(),h){for(p=C.event.special[h]||{},d=l[h=(i?p.delegateType:p.bindType)||h]||[],a=a[2]&&new RegExp("(^|\\.)"+f.join("\\.(?:.*\\.|)")+"(\\.|$)"),s=r=d.length;r--;)u=d[r],!o&&m!==u.origType||n&&n.guid!==u.guid||a&&!a.test(u.namespace)||i&&i!==u.selector&&("**"!==i||!u.selector)||(d.splice(r,1),u.selector&&d.delegateCount--,p.remove&&p.remove.call(t,u));s&&!d.length&&(p.teardown&&!1!==p.teardown.call(t,f,g.handle)||C.removeEvent(t,h,g.handle),delete l[h])}else for(h in l)C.event.remove(t,h+e[c],n,i,!0);C.isEmptyObject(l)&&Y.remove(t,"handle events")}},dispatch:function(t){var e,n,i,o,r,s,a=new Array(arguments.length),l=C.event.fix(t),c=(Y.get(this,"events")||Object.create(null))[l.type]||[],u=C.event.special[l.type]||{};for(a[0]=l,e=1;e=1))for(;c!==this;c=c.parentNode||this)if(1===c.nodeType&&("click"!==t.type||!0!==c.disabled)){for(r=[],s={},n=0;n-1:C.find(o,this,null,[c]).length),s[o]&&r.push(i);r.length&&a.push({elem:c,handlers:r})}return c=this,l\s*$/g;function Nt(t,e){return D(t,"table")&&D(11!==e.nodeType?e:e.firstChild,"tr")&&C(t).children("tbody")[0]||t}function Pt(t){return t.type=(null!==t.getAttribute("type"))+"/"+t.type,t}function Ot(t){return"true/"===(t.type||"").slice(0,5)?t.type=t.type.slice(5):t.removeAttribute("type"),t}function qt(t,e){var n,i,o,r,s,a;if(1===e.nodeType){if(Y.hasData(t)&&(a=Y.get(t).events))for(o in Y.remove(e,"handle events"),a)for(n=0,i=a[o].length;n1&&"string"==typeof f&&!g.checkClone&&It.test(f))return t.each((function(o){var r=t.eq(o);m&&(e[0]=f.call(this,o,r.html())),zt(r,e,n,i)}));if(d&&(r=(o=$t(e,t[0].ownerDocument,!1,t,i)).firstChild,1===o.childNodes.length&&(o=r),r||i)){for(a=(s=C.map(xt(o,"script"),Pt)).length;p0&&wt(s,!l&&xt(t,"script")),a},cleanData:function(t){for(var e,n,i,o=C.event.special,r=0;void 0!==(n=t[r]);r++)if(J(n)){if(e=n[Y.expando]){if(e.events)for(i in e.events)o[i]?C.event.remove(n,i):C.removeEvent(n,i,e.handle);n[Y.expando]=void 0}n[Z.expando]&&(n[Z.expando]=void 0)}}}),C.fn.extend({detach:function(t){return Rt(this,t,!0)},remove:function(t){return Rt(this,t)},text:function(t){return B(this,(function(t){return void 0===t?C.text(this):this.empty().each((function(){1!==this.nodeType&&11!==this.nodeType&&9!==this.nodeType||(this.textContent=t)}))}),null,t,arguments.length)},append:function(){return zt(this,arguments,(function(t){1!==this.nodeType&&11!==this.nodeType&&9!==this.nodeType||Nt(this,t).appendChild(t)}))},prepend:function(){return zt(this,arguments,(function(t){if(1===this.nodeType||11===this.nodeType||9===this.nodeType){var e=Nt(this,t);e.insertBefore(t,e.firstChild)}}))},before:function(){return zt(this,arguments,(function(t){this.parentNode&&this.parentNode.insertBefore(t,this)}))},after:function(){return zt(this,arguments,(function(t){this.parentNode&&this.parentNode.insertBefore(t,this.nextSibling)}))},empty:function(){for(var t,e=0;null!=(t=this[e]);e++)1===t.nodeType&&(C.cleanData(xt(t,!1)),t.textContent="");return this},clone:function(t,e){return t=null!=t&&t,e=null==e?t:e,this.map((function(){return C.clone(this,t,e)}))},html:function(t){return B(this,(function(t){var e=this[0]||{},n=0,i=this.length;if(void 0===t&&1===e.nodeType)return e.innerHTML;if("string"==typeof t&&!Dt.test(t)&&!bt[(vt.exec(t)||["",""])[1].toLowerCase()]){t=C.htmlPrefilter(t);try{for(;n=0&&(l+=Math.max(0,Math.ceil(t["offset"+e[0].toUpperCase()+e.slice(1)]-r-l-a-.5))||0),l}function ne(t,e,n){var i=Ht(t),o=(!g.boxSizingReliable()||n)&&"border-box"===C.css(t,"boxSizing",!1,i),r=o,s=Wt(t,e,i),a="offset"+e[0].toUpperCase()+e.slice(1);if(Ft.test(s)){if(!n)return s;s="auto"}return(!g.boxSizingReliable()&&o||!g.reliableTrDimensions()&&D(t,"tr")||"auto"===s||!parseFloat(s)&&"inline"===C.css(t,"display",!1,i))&&t.getClientRects().length&&(o="border-box"===C.css(t,"boxSizing",!1,i),(r=a in t)&&(s=t[a])),(s=parseFloat(s)||0)+ee(t,e,n||(o?"border":"content"),r,i,s)+"px"}function ie(t,e,n,i,o){return new ie.prototype.init(t,e,n,i,o)}C.extend({cssHooks:{opacity:{get:function(t,e){if(e){var n=Wt(t,"opacity");return""===n?"1":n}}}},cssNumber:{animationIterationCount:!0,columnCount:!0,fillOpacity:!0,flexGrow:!0,flexShrink:!0,fontWeight:!0,gridArea:!0,gridColumn:!0,gridColumnEnd:!0,gridColumnStart:!0,gridRow:!0,gridRowEnd:!0,gridRowStart:!0,lineHeight:!0,opacity:!0,order:!0,orphans:!0,widows:!0,zIndex:!0,zoom:!0},cssProps:{},style:function(t,e,n,i){if(t&&3!==t.nodeType&&8!==t.nodeType&&t.style){var o,r,s,a=X(e),l=Kt.test(e),c=t.style;if(l||(e=Xt(a)),s=C.cssHooks[e]||C.cssHooks[a],void 0===n)return s&&"get"in s&&void 0!==(o=s.get(t,!1,i))?o:c[e];"string"===(r=typeof n)&&(o=ot.exec(n))&&o[1]&&(n=ut(t,e,o),r="number"),null!=n&&n==n&&("number"!==r||l||(n+=o&&o[3]||(C.cssNumber[a]?"":"px")),g.clearCloneStyle||""!==n||0!==e.indexOf("background")||(c[e]="inherit"),s&&"set"in s&&void 0===(n=s.set(t,n,i))||(l?c.setProperty(e,n):c[e]=n))}},css:function(t,e,n,i){var o,r,s,a=X(e);return Kt.test(e)||(e=Xt(a)),(s=C.cssHooks[e]||C.cssHooks[a])&&"get"in s&&(o=s.get(t,!0,n)),void 0===o&&(o=Wt(t,e,i)),"normal"===o&&e in Zt&&(o=Zt[e]),""===n||n?(r=parseFloat(o),!0===n||isFinite(r)?r||0:o):o}}),C.each(["height","width"],(function(t,e){C.cssHooks[e]={get:function(t,n,i){if(n)return!Jt.test(C.css(t,"display"))||t.getClientRects().length&&t.getBoundingClientRect().width?ne(t,e,i):Mt(t,Yt,(function(){return ne(t,e,i)}))},set:function(t,n,i){var o,r=Ht(t),s=!g.scrollboxSize()&&"absolute"===r.position,a=(s||i)&&"border-box"===C.css(t,"boxSizing",!1,r),l=i?ee(t,e,i,a,r):0;return a&&s&&(l-=Math.ceil(t["offset"+e[0].toUpperCase()+e.slice(1)]-parseFloat(r[e])-ee(t,e,"border",!1,r)-.5)),l&&(o=ot.exec(n))&&"px"!==(o[3]||"px")&&(t.style[e]=n,n=C.css(t,e)),te(0,n,l)}}})),C.cssHooks.marginLeft=Bt(g.reliableMarginLeft,(function(t,e){if(e)return(parseFloat(Wt(t,"marginLeft"))||t.getBoundingClientRect().left-Mt(t,{marginLeft:0},(function(){return t.getBoundingClientRect().left})))+"px"})),C.each({margin:"",padding:"",border:"Width"},(function(t,e){C.cssHooks[t+e]={expand:function(n){for(var i=0,o={},r="string"==typeof n?n.split(" "):[n];i<4;i++)o[t+rt[i]+e]=r[i]||r[i-2]||r[0];return o}},"margin"!==t&&(C.cssHooks[t+e].set=te)})),C.fn.extend({css:function(t,e){return B(this,(function(t,e,n){var i,o,r={},s=0;if(Array.isArray(e)){for(i=Ht(t),o=e.length;s1)}}),C.Tween=ie,ie.prototype={constructor:ie,init:function(t,e,n,i,o,r){this.elem=t,this.prop=n,this.easing=o||C.easing._default,this.options=e,this.start=this.now=this.cur(),this.end=i,this.unit=r||(C.cssNumber[n]?"":"px")},cur:function(){var t=ie.propHooks[this.prop];return t&&t.get?t.get(this):ie.propHooks._default.get(this)},run:function(t){var e,n=ie.propHooks[this.prop];return this.options.duration?this.pos=e=C.easing[this.easing](t,this.options.duration*t,0,1,this.options.duration):this.pos=e=t,this.now=(this.end-this.start)*e+this.start,this.options.step&&this.options.step.call(this.elem,this.now,this),n&&n.set?n.set(this):ie.propHooks._default.set(this),this}},ie.prototype.init.prototype=ie.prototype,ie.propHooks={_default:{get:function(t){var e;return 1!==t.elem.nodeType||null!=t.elem[t.prop]&&null==t.elem.style[t.prop]?t.elem[t.prop]:(e=C.css(t.elem,t.prop,""))&&"auto"!==e?e:0},set:function(t){C.fx.step[t.prop]?C.fx.step[t.prop](t):1!==t.elem.nodeType||!C.cssHooks[t.prop]&&null==t.elem.style[Xt(t.prop)]?t.elem[t.prop]=t.now:C.style(t.elem,t.prop,t.now+t.unit)}}},ie.propHooks.scrollTop=ie.propHooks.scrollLeft={set:function(t){t.elem.nodeType&&t.elem.parentNode&&(t.elem[t.prop]=t.now)}},C.easing={linear:function(t){return t},swing:function(t){return.5-Math.cos(t*Math.PI)/2},_default:"swing"},C.fx=ie.prototype.init,C.fx.step={};var oe,re,se=/^(?:toggle|show|hide)$/,ae=/queueHooks$/;function le(){re&&(!1===b.hidden&&n.requestAnimationFrame?n.requestAnimationFrame(le):n.setTimeout(le,C.fx.interval),C.fx.tick())}function ce(){return n.setTimeout((function(){oe=void 0})),oe=Date.now()}function ue(t,e){var n,i=0,o={height:t};for(e=e?1:0;i<4;i+=2-e)o["margin"+(n=rt[i])]=o["padding"+n]=t;return e&&(o.opacity=o.width=t),o}function pe(t,e,n){for(var i,o=(de.tweeners[e]||[]).concat(de.tweeners["*"]),r=0,s=o.length;r1)},removeAttr:function(t){return this.each((function(){C.removeAttr(this,t)}))}}),C.extend({attr:function(t,e,n){var i,o,r=t.nodeType;if(3!==r&&8!==r&&2!==r)return void 0===t.getAttribute?C.prop(t,e,n):(1===r&&C.isXMLDoc(t)||(o=C.attrHooks[e.toLowerCase()]||(C.expr.match.bool.test(e)?he:void 0)),void 0!==n?null===n?void C.removeAttr(t,e):o&&"set"in o&&void 0!==(i=o.set(t,n,e))?i:(t.setAttribute(e,n+""),n):o&&"get"in o&&null!==(i=o.get(t,e))?i:null==(i=C.find.attr(t,e))?void 0:i)},attrHooks:{type:{set:function(t,e){if(!g.radioValue&&"radio"===e&&D(t,"input")){var n=t.value;return t.setAttribute("type",e),n&&(t.value=n),e}}}},removeAttr:function(t,e){var n,i=0,o=e&&e.match(z);if(o&&1===t.nodeType)for(;n=o[i++];)t.removeAttribute(n)}}),he={set:function(t,e,n){return!1===e?C.removeAttr(t,n):t.setAttribute(n,n),n}},C.each(C.expr.match.bool.source.match(/\w+/g),(function(t,e){var n=fe[e]||C.find.attr;fe[e]=function(t,e,i){var o,r,s=e.toLowerCase();return i||(r=fe[s],fe[s]=o,o=null!=n(t,e,i)?s:null,fe[s]=r),o}}));var me=/^(?:input|select|textarea|button)$/i,ge=/^(?:a|area)$/i;function ve(t){return(t.match(z)||[]).join(" ")}function ye(t){return t.getAttribute&&t.getAttribute("class")||""}function be(t){return Array.isArray(t)?t:"string"==typeof t&&t.match(z)||[]}C.fn.extend({prop:function(t,e){return B(this,C.prop,t,e,arguments.length>1)},removeProp:function(t){return this.each((function(){delete this[C.propFix[t]||t]}))}}),C.extend({prop:function(t,e,n){var i,o,r=t.nodeType;if(3!==r&&8!==r&&2!==r)return 1===r&&C.isXMLDoc(t)||(e=C.propFix[e]||e,o=C.propHooks[e]),void 0!==n?o&&"set"in o&&void 0!==(i=o.set(t,n,e))?i:t[e]=n:o&&"get"in o&&null!==(i=o.get(t,e))?i:t[e]},propHooks:{tabIndex:{get:function(t){var e=C.find.attr(t,"tabindex");return e?parseInt(e,10):me.test(t.nodeName)||ge.test(t.nodeName)&&t.href?0:-1}}},propFix:{for:"htmlFor",class:"className"}}),g.optSelected||(C.propHooks.selected={get:function(t){var e=t.parentNode;return e&&e.parentNode&&e.parentNode.selectedIndex,null},set:function(t){var e=t.parentNode;e&&(e.selectedIndex,e.parentNode&&e.parentNode.selectedIndex)}}),C.each(["tabIndex","readOnly","maxLength","cellSpacing","cellPadding","rowSpan","colSpan","useMap","frameBorder","contentEditable"],(function(){C.propFix[this.toLowerCase()]=this})),C.fn.extend({addClass:function(t){var e,n,i,o,r,s,a,l=0;if(v(t))return this.each((function(e){C(this).addClass(t.call(this,e,ye(this)))}));if((e=be(t)).length)for(;n=this[l++];)if(o=ye(n),i=1===n.nodeType&&" "+ve(o)+" "){for(s=0;r=e[s++];)i.indexOf(" "+r+" ")<0&&(i+=r+" ");o!==(a=ve(i))&&n.setAttribute("class",a)}return this},removeClass:function(t){var e,n,i,o,r,s,a,l=0;if(v(t))return this.each((function(e){C(this).removeClass(t.call(this,e,ye(this)))}));if(!arguments.length)return this.attr("class","");if((e=be(t)).length)for(;n=this[l++];)if(o=ye(n),i=1===n.nodeType&&" "+ve(o)+" "){for(s=0;r=e[s++];)for(;i.indexOf(" "+r+" ")>-1;)i=i.replace(" "+r+" "," ");o!==(a=ve(i))&&n.setAttribute("class",a)}return this},toggleClass:function(t,e){var n=typeof t,i="string"===n||Array.isArray(t);return"boolean"==typeof e&&i?e?this.addClass(t):this.removeClass(t):v(t)?this.each((function(n){C(this).toggleClass(t.call(this,n,ye(this),e),e)})):this.each((function(){var e,o,r,s;if(i)for(o=0,r=C(this),s=be(t);e=s[o++];)r.hasClass(e)?r.removeClass(e):r.addClass(e);else void 0!==t&&"boolean"!==n||((e=ye(this))&&Y.set(this,"__className__",e),this.setAttribute&&this.setAttribute("class",e||!1===t?"":Y.get(this,"__className__")||""))}))},hasClass:function(t){var e,n,i=0;for(e=" "+t+" ";n=this[i++];)if(1===n.nodeType&&(" "+ve(ye(n))+" ").indexOf(e)>-1)return!0;return!1}});var xe=/\r/g;C.fn.extend({val:function(t){var e,n,i,o=this[0];return arguments.length?(i=v(t),this.each((function(n){var o;1===this.nodeType&&(null==(o=i?t.call(this,n,C(this).val()):t)?o="":"number"==typeof o?o+="":Array.isArray(o)&&(o=C.map(o,(function(t){return null==t?"":t+""}))),(e=C.valHooks[this.type]||C.valHooks[this.nodeName.toLowerCase()])&&"set"in e&&void 0!==e.set(this,o,"value")||(this.value=o))}))):o?(e=C.valHooks[o.type]||C.valHooks[o.nodeName.toLowerCase()])&&"get"in e&&void 0!==(n=e.get(o,"value"))?n:"string"==typeof(n=o.value)?n.replace(xe,""):null==n?"":n:void 0}}),C.extend({valHooks:{option:{get:function(t){var e=C.find.attr(t,"value");return null!=e?e:ve(C.text(t))}},select:{get:function(t){var e,n,i,o=t.options,r=t.selectedIndex,s="select-one"===t.type,a=s?null:[],l=s?r+1:o.length;for(i=r<0?l:s?r:0;i-1)&&(n=!0);return n||(t.selectedIndex=-1),r}}}}),C.each(["radio","checkbox"],(function(){C.valHooks[this]={set:function(t,e){if(Array.isArray(e))return t.checked=C.inArray(C(t).val(),e)>-1}},g.checkOn||(C.valHooks[this].get=function(t){return null===t.getAttribute("value")?"on":t.value})})),g.focusin="onfocusin"in n;var we=/^(?:focusinfocus|focusoutblur)$/,ke=function(t){t.stopPropagation()};C.extend(C.event,{trigger:function(t,e,i,o){var r,s,a,l,c,u,p,d,f=[i||b],m=h.call(t,"type")?t.type:t,g=h.call(t,"namespace")?t.namespace.split("."):[];if(s=d=a=i=i||b,3!==i.nodeType&&8!==i.nodeType&&!we.test(m+C.event.triggered)&&(m.indexOf(".")>-1&&(g=m.split("."),m=g.shift(),g.sort()),c=m.indexOf(":")<0&&"on"+m,(t=t[C.expando]?t:new C.Event(m,"object"==typeof t&&t)).isTrigger=o?2:3,t.namespace=g.join("."),t.rnamespace=t.namespace?new RegExp("(^|\\.)"+g.join("\\.(?:.*\\.|)")+"(\\.|$)"):null,t.result=void 0,t.target||(t.target=i),e=null==e?[t]:C.makeArray(e,[t]),p=C.event.special[m]||{},o||!p.trigger||!1!==p.trigger.apply(i,e))){if(!o&&!p.noBubble&&!y(i)){for(l=p.delegateType||m,we.test(l+m)||(s=s.parentNode);s;s=s.parentNode)f.push(s),a=s;a===(i.ownerDocument||b)&&f.push(a.defaultView||a.parentWindow||n)}for(r=0;(s=f[r++])&&!t.isPropagationStopped();)d=s,t.type=r>1?l:p.bindType||m,(u=(Y.get(s,"events")||Object.create(null))[t.type]&&Y.get(s,"handle"))&&u.apply(s,e),(u=c&&s[c])&&u.apply&&J(s)&&(t.result=u.apply(s,e),!1===t.result&&t.preventDefault());return t.type=m,o||t.isDefaultPrevented()||p._default&&!1!==p._default.apply(f.pop(),e)||!J(i)||c&&v(i[m])&&!y(i)&&((a=i[c])&&(i[c]=null),C.event.triggered=m,t.isPropagationStopped()&&d.addEventListener(m,ke),i[m](),t.isPropagationStopped()&&d.removeEventListener(m,ke),C.event.triggered=void 0,a&&(i[c]=a)),t.result}},simulate:function(t,e,n){var i=C.extend(new C.Event,n,{type:t,isSimulated:!0});C.event.trigger(i,null,e)}}),C.fn.extend({trigger:function(t,e){return this.each((function(){C.event.trigger(t,e,this)}))},triggerHandler:function(t,e){var n=this[0];if(n)return C.event.trigger(t,e,n,!0)}}),g.focusin||C.each({focus:"focusin",blur:"focusout"},(function(t,e){var n=function(t){C.event.simulate(e,t.target,C.event.fix(t))};C.event.special[e]={setup:function(){var i=this.ownerDocument||this.document||this,o=Y.access(i,e);o||i.addEventListener(t,n,!0),Y.access(i,e,(o||0)+1)},teardown:function(){var i=this.ownerDocument||this.document||this,o=Y.access(i,e)-1;o?Y.access(i,e,o):(i.removeEventListener(t,n,!0),Y.remove(i,e))}}}));var $e=n.location,Ce={guid:Date.now()},Te=/\?/;C.parseXML=function(t){var e,i;if(!t||"string"!=typeof t)return null;try{e=(new n.DOMParser).parseFromString(t,"text/xml")}catch(t){}return i=e&&e.getElementsByTagName("parsererror")[0],e&&!i||C.error("Invalid XML: "+(i?C.map(i.childNodes,(function(t){return t.textContent})).join("\n"):t)),e};var Se=/\[\]$/,_e=/\r?\n/g,Ee=/^(?:submit|button|image|reset|file)$/i,Ae=/^(?:input|select|textarea|keygen)/i;function De(t,e,n,i){var o;if(Array.isArray(e))C.each(e,(function(e,o){n||Se.test(t)?i(t,o):De(t+"["+("object"==typeof o&&null!=o?e:"")+"]",o,n,i)}));else if(n||"object"!==k(e))i(t,e);else for(o in e)De(t+"["+o+"]",e[o],n,i)}C.param=function(t,e){var n,i=[],o=function(t,e){var n=v(e)?e():e;i[i.length]=encodeURIComponent(t)+"="+encodeURIComponent(null==n?"":n)};if(null==t)return"";if(Array.isArray(t)||t.jquery&&!C.isPlainObject(t))C.each(t,(function(){o(this.name,this.value)}));else for(n in t)De(n,t[n],e,o);return i.join("&")},C.fn.extend({serialize:function(){return C.param(this.serializeArray())},serializeArray:function(){return this.map((function(){var t=C.prop(this,"elements");return t?C.makeArray(t):this})).filter((function(){var t=this.type;return this.name&&!C(this).is(":disabled")&&Ae.test(this.nodeName)&&!Ee.test(t)&&(this.checked||!gt.test(t))})).map((function(t,e){var n=C(this).val();return null==n?null:Array.isArray(n)?C.map(n,(function(t){return{name:e.name,value:t.replace(_e,"\r\n")}})):{name:e.name,value:n.replace(_e,"\r\n")}})).get()}});var Ie=/%20/g,je=/#.*$/,Ne=/([?&])_=[^&]*/,Pe=/^(.*?):[ \t]*([^\r\n]*)$/gm,Oe=/^(?:GET|HEAD)$/,qe=/^\/\//,Le={},ze={},Re="*/".concat("*"),Fe=b.createElement("a");function He(t){return function(e,n){"string"!=typeof e&&(n=e,e="*");var i,o=0,r=e.toLowerCase().match(z)||[];if(v(n))for(;i=r[o++];)"+"===i[0]?(i=i.slice(1)||"*",(t[i]=t[i]||[]).unshift(n)):(t[i]=t[i]||[]).push(n)}}function Me(t,e,n,i){var o={},r=t===ze;function s(a){var l;return o[a]=!0,C.each(t[a]||[],(function(t,a){var c=a(e,n,i);return"string"!=typeof c||r||o[c]?r?!(l=c):void 0:(e.dataTypes.unshift(c),s(c),!1)})),l}return s(e.dataTypes[0])||!o["*"]&&s("*")}function Ue(t,e){var n,i,o=C.ajaxSettings.flatOptions||{};for(n in e)void 0!==e[n]&&((o[n]?t:i||(i={}))[n]=e[n]);return i&&C.extend(!0,t,i),t}Fe.href=$e.href,C.extend({active:0,lastModified:{},etag:{},ajaxSettings:{url:$e.href,type:"GET",isLocal:/^(?:about|app|app-storage|.+-extension|file|res|widget):$/.test($e.protocol),global:!0,processData:!0,async:!0,contentType:"application/x-www-form-urlencoded; charset=UTF-8",accepts:{"*":Re,text:"text/plain",html:"text/html",xml:"application/xml, text/xml",json:"application/json, text/javascript"},contents:{xml:/\bxml\b/,html:/\bhtml/,json:/\bjson\b/},responseFields:{xml:"responseXML",text:"responseText",json:"responseJSON"},converters:{"* text":String,"text html":!0,"text json":JSON.parse,"text xml":C.parseXML},flatOptions:{url:!0,context:!0}},ajaxSetup:function(t,e){return e?Ue(Ue(t,C.ajaxSettings),e):Ue(C.ajaxSettings,t)},ajaxPrefilter:He(Le),ajaxTransport:He(ze),ajax:function(t,e){"object"==typeof t&&(e=t,t=void 0),e=e||{};var i,o,r,s,a,l,c,u,p,d,h=C.ajaxSetup({},e),f=h.context||h,m=h.context&&(f.nodeType||f.jquery)?C(f):C.event,g=C.Deferred(),v=C.Callbacks("once memory"),y=h.statusCode||{},x={},w={},k="canceled",$={readyState:0,getResponseHeader:function(t){var e;if(c){if(!s)for(s={};e=Pe.exec(r);)s[e[1].toLowerCase()+" "]=(s[e[1].toLowerCase()+" "]||[]).concat(e[2]);e=s[t.toLowerCase()+" "]}return null==e?null:e.join(", ")},getAllResponseHeaders:function(){return c?r:null},setRequestHeader:function(t,e){return null==c&&(t=w[t.toLowerCase()]=w[t.toLowerCase()]||t,x[t]=e),this},overrideMimeType:function(t){return null==c&&(h.mimeType=t),this},statusCode:function(t){var e;if(t)if(c)$.always(t[$.status]);else for(e in t)y[e]=[y[e],t[e]];return this},abort:function(t){var e=t||k;return i&&i.abort(e),T(0,e),this}};if(g.promise($),h.url=((t||h.url||$e.href)+"").replace(qe,$e.protocol+"//"),h.type=e.method||e.type||h.method||h.type,h.dataTypes=(h.dataType||"*").toLowerCase().match(z)||[""],null==h.crossDomain){l=b.createElement("a");try{l.href=h.url,l.href=l.href,h.crossDomain=Fe.protocol+"//"+Fe.host!=l.protocol+"//"+l.host}catch(t){h.crossDomain=!0}}if(h.data&&h.processData&&"string"!=typeof h.data&&(h.data=C.param(h.data,h.traditional)),Me(Le,h,e,$),c)return $;for(p in(u=C.event&&h.global)&&0==C.active++&&C.event.trigger("ajaxStart"),h.type=h.type.toUpperCase(),h.hasContent=!Oe.test(h.type),o=h.url.replace(je,""),h.hasContent?h.data&&h.processData&&0===(h.contentType||"").indexOf("application/x-www-form-urlencoded")&&(h.data=h.data.replace(Ie,"+")):(d=h.url.slice(o.length),h.data&&(h.processData||"string"==typeof h.data)&&(o+=(Te.test(o)?"&":"?")+h.data,delete h.data),!1===h.cache&&(o=o.replace(Ne,"$1"),d=(Te.test(o)?"&":"?")+"_="+Ce.guid+++d),h.url=o+d),h.ifModified&&(C.lastModified[o]&&$.setRequestHeader("If-Modified-Since",C.lastModified[o]),C.etag[o]&&$.setRequestHeader("If-None-Match",C.etag[o])),(h.data&&h.hasContent&&!1!==h.contentType||e.contentType)&&$.setRequestHeader("Content-Type",h.contentType),$.setRequestHeader("Accept",h.dataTypes[0]&&h.accepts[h.dataTypes[0]]?h.accepts[h.dataTypes[0]]+("*"!==h.dataTypes[0]?", "+Re+"; q=0.01":""):h.accepts["*"]),h.headers)$.setRequestHeader(p,h.headers[p]);if(h.beforeSend&&(!1===h.beforeSend.call(f,$,h)||c))return $.abort();if(k="abort",v.add(h.complete),$.done(h.success),$.fail(h.error),i=Me(ze,h,e,$)){if($.readyState=1,u&&m.trigger("ajaxSend",[$,h]),c)return $;h.async&&h.timeout>0&&(a=n.setTimeout((function(){$.abort("timeout")}),h.timeout));try{c=!1,i.send(x,T)}catch(t){if(c)throw t;T(-1,t)}}else T(-1,"No Transport");function T(t,e,s,l){var p,d,b,x,w,k=e;c||(c=!0,a&&n.clearTimeout(a),i=void 0,r=l||"",$.readyState=t>0?4:0,p=t>=200&&t<300||304===t,s&&(x=function(t,e,n){for(var i,o,r,s,a=t.contents,l=t.dataTypes;"*"===l[0];)l.shift(),void 0===i&&(i=t.mimeType||e.getResponseHeader("Content-Type"));if(i)for(o in a)if(a[o]&&a[o].test(i)){l.unshift(o);break}if(l[0]in n)r=l[0];else{for(o in n){if(!l[0]||t.converters[o+" "+l[0]]){r=o;break}s||(s=o)}r=r||s}if(r)return r!==l[0]&&l.unshift(r),n[r]}(h,$,s)),!p&&C.inArray("script",h.dataTypes)>-1&&C.inArray("json",h.dataTypes)<0&&(h.converters["text script"]=function(){}),x=function(t,e,n,i){var o,r,s,a,l,c={},u=t.dataTypes.slice();if(u[1])for(s in t.converters)c[s.toLowerCase()]=t.converters[s];for(r=u.shift();r;)if(t.responseFields[r]&&(n[t.responseFields[r]]=e),!l&&i&&t.dataFilter&&(e=t.dataFilter(e,t.dataType)),l=r,r=u.shift())if("*"===r)r=l;else if("*"!==l&&l!==r){if(!(s=c[l+" "+r]||c["* "+r]))for(o in c)if((a=o.split(" "))[1]===r&&(s=c[l+" "+a[0]]||c["* "+a[0]])){!0===s?s=c[o]:!0!==c[o]&&(r=a[0],u.unshift(a[1]));break}if(!0!==s)if(s&&t.throws)e=s(e);else try{e=s(e)}catch(t){return{state:"parsererror",error:s?t:"No conversion from "+l+" to "+r}}}return{state:"success",data:e}}(h,x,$,p),p?(h.ifModified&&((w=$.getResponseHeader("Last-Modified"))&&(C.lastModified[o]=w),(w=$.getResponseHeader("etag"))&&(C.etag[o]=w)),204===t||"HEAD"===h.type?k="nocontent":304===t?k="notmodified":(k=x.state,d=x.data,p=!(b=x.error))):(b=k,!t&&k||(k="error",t<0&&(t=0))),$.status=t,$.statusText=(e||k)+"",p?g.resolveWith(f,[d,k,$]):g.rejectWith(f,[$,k,b]),$.statusCode(y),y=void 0,u&&m.trigger(p?"ajaxSuccess":"ajaxError",[$,h,p?d:b]),v.fireWith(f,[$,k]),u&&(m.trigger("ajaxComplete",[$,h]),--C.active||C.event.trigger("ajaxStop")))}return $},getJSON:function(t,e,n){return C.get(t,e,n,"json")},getScript:function(t,e){return C.get(t,void 0,e,"script")}}),C.each(["get","post"],(function(t,e){C[e]=function(t,n,i,o){return v(n)&&(o=o||i,i=n,n=void 0),C.ajax(C.extend({url:t,type:e,dataType:o,data:n,success:i},C.isPlainObject(t)&&t))}})),C.ajaxPrefilter((function(t){var e;for(e in t.headers)"content-type"===e.toLowerCase()&&(t.contentType=t.headers[e]||"")})),C._evalUrl=function(t,e,n){return C.ajax({url:t,type:"GET",dataType:"script",cache:!0,async:!1,global:!1,converters:{"text script":function(){}},dataFilter:function(t){C.globalEval(t,e,n)}})},C.fn.extend({wrapAll:function(t){var e;return this[0]&&(v(t)&&(t=t.call(this[0])),e=C(t,this[0].ownerDocument).eq(0).clone(!0),this[0].parentNode&&e.insertBefore(this[0]),e.map((function(){for(var t=this;t.firstElementChild;)t=t.firstElementChild;return t})).append(this)),this},wrapInner:function(t){return v(t)?this.each((function(e){C(this).wrapInner(t.call(this,e))})):this.each((function(){var e=C(this),n=e.contents();n.length?n.wrapAll(t):e.append(t)}))},wrap:function(t){var e=v(t);return this.each((function(n){C(this).wrapAll(e?t.call(this,n):t)}))},unwrap:function(t){return this.parent(t).not("body").each((function(){C(this).replaceWith(this.childNodes)})),this}}),C.expr.pseudos.hidden=function(t){return!C.expr.pseudos.visible(t)},C.expr.pseudos.visible=function(t){return!!(t.offsetWidth||t.offsetHeight||t.getClientRects().length)},C.ajaxSettings.xhr=function(){try{return new n.XMLHttpRequest}catch(t){}};var We={0:200,1223:204},Be=C.ajaxSettings.xhr();g.cors=!!Be&&"withCredentials"in Be,g.ajax=Be=!!Be,C.ajaxTransport((function(t){var e,i;if(g.cors||Be&&!t.crossDomain)return{send:function(o,r){var s,a=t.xhr();if(a.open(t.type,t.url,t.async,t.username,t.password),t.xhrFields)for(s in t.xhrFields)a[s]=t.xhrFields[s];for(s in t.mimeType&&a.overrideMimeType&&a.overrideMimeType(t.mimeType),t.crossDomain||o["X-Requested-With"]||(o["X-Requested-With"]="XMLHttpRequest"),o)a.setRequestHeader(s,o[s]);e=function(t){return function(){e&&(e=i=a.onload=a.onerror=a.onabort=a.ontimeout=a.onreadystatechange=null,"abort"===t?a.abort():"error"===t?"number"!=typeof a.status?r(0,"error"):r(a.status,a.statusText):r(We[a.status]||a.status,a.statusText,"text"!==(a.responseType||"text")||"string"!=typeof a.responseText?{binary:a.response}:{text:a.responseText},a.getAllResponseHeaders()))}},a.onload=e(),i=a.onerror=a.ontimeout=e("error"),void 0!==a.onabort?a.onabort=i:a.onreadystatechange=function(){4===a.readyState&&n.setTimeout((function(){e&&i()}))},e=e("abort");try{a.send(t.hasContent&&t.data||null)}catch(t){if(e)throw t}},abort:function(){e&&e()}}})),C.ajaxPrefilter((function(t){t.crossDomain&&(t.contents.script=!1)})),C.ajaxSetup({accepts:{script:"text/javascript, application/javascript, application/ecmascript, application/x-ecmascript"},contents:{script:/\b(?:java|ecma)script\b/},converters:{"text script":function(t){return C.globalEval(t),t}}}),C.ajaxPrefilter("script",(function(t){void 0===t.cache&&(t.cache=!1),t.crossDomain&&(t.type="GET")})),C.ajaxTransport("script",(function(t){var e,n;if(t.crossDomain||t.scriptAttrs)return{send:function(i,o){e=C(" pat-0.15.1/web/src/js/000077500000000000000000000000001453425652100143515ustar00rootroot00000000000000pat-0.15.1/web/src/js/app.js000066400000000000000000001225761453425652100155040ustar00rootroot00000000000000import 'jquery'; import 'bootstrap/dist/js/bootstrap'; import 'bootstrap-select'; import 'bootstrap-tokenfield'; import URI from 'urijs'; let wsURL = ''; let posId = 0; let connectAliases; let mycall = ''; let formsCatalog; const uploadFiles = new Array(); let statusPopoverDiv; const statusPos = $('#pos_status'); $(document).ready(function () { wsURL = (location.protocol == 'https:' ? 'wss://' : 'ws://') + location.host + '/ws'; $(function () { initStatusPopover(); // Setup actions $('#connect_btn').click(connect); $('#connectForm input').keypress(function (e) { if (e.which == 13) { connect(); return false; } }); $('#connectForm input').keyup(function (e) { onConnectInputChange(); }); $('#pos_btn').click(postPosition); // Setup composer initComposeModal(); // Setup folder navigation $('#inbox_tab').click(function (evt) { displayFolder('in'); }); $('#outbox_tab').click(function (evt) { displayFolder('out'); }); $('#sent_tab').click(function (evt) { displayFolder('sent'); }); $('#archive_tab').click(function (evt) { displayFolder('archive'); }); $('.navbar li').click(function (e) { $('.navbar li.active').removeClass('active'); const $this = $(this); if (!$this.hasClass('active')) { $this.addClass('active'); } e.preventDefault(); }); $('.nav :not(.dropdown) a').on('click', function () { if ($('.navbar-toggle').css('display') != 'none') { $('.navbar-toggle').trigger('click'); } }); $('#posModal').on('shown.bs.modal', function (e) { $.ajax({ url: '/api/current_gps_position', dataType: 'json', beforeSend: function () { statusPos.html('Checking if GPS device is available'); }, success: function (gpsData) { statusPos.html('GPS position received'); statusPos.html('Waiting for position form GPS device...'); updatePositionGPS(gpsData); }, error: function (jqXHR, textStatus, errorThrown) { statusPos.html('GPS device not available!'); if (navigator.geolocation) { statusPos.html('Waiting for position (geolocation)...'); const options = { enableHighAccuracy: true, maximumAge: 0 }; posId = navigator.geolocation.watchPosition( updatePositionGeolocation, handleGeolocationError, options ); } else { statusPos.html('Geolocation is not supported by this browser.'); } }, }); }); $('#posModal').on('hidden.bs.modal', function (e) { if (navigator.geolocation) { navigator.geolocation.clearWatch(posId); } }); $('#updateFormsButton').click(updateForms); initConnectModal(); initConsole(); displayFolder('in'); initNotifications(); initForms(); }); }); function initNotifications() { if (!isNotificationsSupported()) { statusPopoverDiv .find('#notifications_error') .find('.panel-body') .html('Not supported by this browser.'); return; } Notification.requestPermission(function (permission) { if (permission === 'granted') { showGUIStatus(statusPopoverDiv.find('#notifications_error'), false); } else if (isInsecureOrigin()) { // There is no way of knowing for sure if the permission was denied by the user // or prohibited because of insecure origin (Chrome). This is just a lucky guess. appendInsecureOriginWarning(statusPopoverDiv.find('#notifications_error')); } }); } function isNotificationsSupported() { if (!window.Notification || !Notification.requestPermission) return false; if (Notification.permission === 'granted') return true; // Chrome on Android support notifications only in the context of a Service worker. // This is a hack to detect this case, so we can avoid asking for a pointless permission. try { new Notification(''); } catch (e) { if (e.name == 'TypeError') return false; } return true; } let cancelCloseTimer = false; function updateProgress(p) { cancelCloseTimer = !p.done; if (p.receiving || p.sending) { const percent = Math.ceil((p.bytes_transferred * 100) / p.bytes_total); const op = p.receiving ? 'Receiving' : 'Sending'; let text = op + ' ' + p.mid + ' (' + p.bytes_total + ' bytes)'; if (p.subject) { text += ' - ' + htmlEscape(p.subject); } $('#navbar_progress .progress-text').text(text); $('#navbar_progress .progress-bar') .css('width', percent + '%') .text(percent + '%'); } if ($('#navbar_progress').is(':visible') && p.done) { window.setTimeout(function () { if (!cancelCloseTimer) { $('#navbar_progress').fadeOut(500); } }, 3000); } else if ((p.receiving || p.sending) && !p.done) { $('#navbar_progress').show(); } } function initStatusPopover() { statusPopoverDiv = $('#status_popover_content'); showGUIStatus($('#websocket_error'), true); showGUIStatus($('#notifications_error'), true); $('#gui_status_light').popover({ placement: 'bottom', content: statusPopoverDiv, html: true, }); // Hack to force popover to grab it's content div $('#gui_status_light').popover('show'); $('#gui_status_light').popover('hide'); statusPopoverDiv.show(); // Bind click on navbar-brand $('#gui_status_light').unbind(); $('.navbar-brand').click(function (e) { $('#gui_status_light').popover('toggle'); }); } function onFormLaunching(target) { $('#selectForm').modal('hide'); startPollingFormData(); window.open(target); } function startPollingFormData() { setCookie('forminstance', Math.floor(Math.random() * 1000000000), 1); pollFormData(); } function forgetFormData() { window.clearTimeout(pollTimer); deleteCookie('forminstance'); } let pollTimer; function pollFormData() { $.get( 'api/form', {}, function (data) { console.log(data); if (!$('#composer').hasClass('hidden') && (!data.target_form || !data.target_form.name)) { pollTimer = window.setTimeout(pollFormData, 1000); } else { console.log('done polling'); if (!$('#composer').hasClass('hidden') && data.target_form && data.target_form.name) { writeFormDataToComposer(data); } } }, 'json' ); } function writeFormDataToComposer(data) { if (data.target_form) { $('#msg_body').val(data.msg_body); if (data.msg_to) { $('#msg_to').tokenfield('setTokens', data.msg_to.split(/[ ;,]/).filter(Boolean)); } if (data.msg_cc) { $('#msg_cc').tokenfield('setTokens', data.msg_cc.split(/[ ;,]/).filter(Boolean)); } if (data.msg_subject) { // in case of composing a form-based reply we keep the 'Re: ...' subject line $('#msg_subject').val(data.msg_subject); } } } function initComposeModal() { $('#compose_btn').click(function (evt) { $('#composer').modal('toggle'); }); const tokenfieldConfig = { delimiter: [',', ';', ' '], // Must be in sync with SplitFunc (utils.go) inputType: 'email', createTokensOnBlur: true, }; $('#msg_to').tokenfield(tokenfieldConfig); $('#msg_cc').tokenfield(tokenfieldConfig); $('#composer').on('change', '.btn-file :file', previewAttachmentFiles); $('#composer').on('hidden.bs.modal', forgetFormData); $('#composer_error').hide(); $('#compose_cancel').click(function (evt) { closeComposer(true); }); $('#composer_form').submit(function (e) { const form = $('#composer_form'); const d = new Date().toJSON(); $('#msg_form_date').remove(); form.append(''); // Set some defaults that makes the message pass validation (as Winlink Express does) if ($('#msg_body').val().length == 0) { $('#msg_body').val(''); } if ($('#msg_subject').val().length == 0) { $('#msg_subject').val(''); } $.ajax({ url: '/api/mailbox/out', method: 'POST', data: new FormData(form[0]), processData: false, contentType: false, success: function (result) { $('#composer').modal('hide'); closeComposer(true); alert(result); }, error: function (error) { $('#composer_error').html(error.responseText); $('#composer_error').show(); }, }); e.preventDefault(); }); } function initForms() { $.getJSON('/api/formcatalog') .done(function (data) { initFormSelect(data); }) .fail(function (data) { initFormSelect(null); }); } function initFormSelect(data) { formsCatalog = data; if ( data && data.path && ((data.folders && data.folders.length > 0) || (data.forms && data.forms.length > 0)) ) { $('#formsVersion').html( '(ver ' + data.version + ')' ); $('#updateFormsVersion').html(data.version); $('#formsRootFolderName').text(data.path); $('#formFolderRoot').html(''); appendFormFolder('formFolderRoot', data); } else { $('#formsRootFolderName').text('missing form templates'); $(`#formFolderRoot`).append(`
Form templates not downloaded
Use Action → Update Form Templates to download now `); } } function updateForms() { $('#updateFormsResponse').text(''); $('#updateFormsError').text(''); $.ajax({ method: 'POST', url: '/api/formsUpdate', success: (msg) => { $('#updateFormsError').text(''); let response = JSON.parse(msg); switch (response.action) { case 'none': $('#updateFormsResponse').text('You already have the latest forms version'); break; case 'update': $('#updateFormsResponse').text('Updated forms to ' + response.newestVersion); // Update views to reflect new state initForms(); break; } }, error: (err) => { $('#updateFormsResponse').text(''); $('#updateFormsError').text(err.responseText); }, }); } function setCookie(cname, cvalue, exdays) { const d = new Date(); d.setTime(d.getTime() + exdays * 24 * 60 * 60 * 1000); const expires = 'expires=' + d.toUTCString(); document.cookie = cname + '=' + cvalue + ';' + expires + ';path=/'; } function deleteCookie(cname) { document.cookie = cname + '=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/;'; } function appendFormFolder(rootId, data) { if (data.folders && data.folders.length > 0 && data.form_count > 0) { const rootAcc = `${rootId}Acc`; $(`#${rootId}`).append(`
`); data.folders.forEach(function (folder) { if (folder.form_count > 0) { const folderNameId = rootId + folder.name.replace(/\s/g, '_').replace(/&/g, 'and'); const cardBodyId = folderNameId + 'Body'; const card = `
`; $(`#${rootAcc}`).append(card); appendFormFolder(`${cardBodyId}`, folder); if (folder.forms && folder.forms.length > 0) { const cardBodyFormsId = `${cardBodyId}Forms`; $(`#${cardBodyId}`).append(`
`); folder.forms.forEach((form) => { const newDiv = $( `
${form.name}
` ); const pathEncoded = encodeURIComponent(form.initial_uri); newDiv.on('click', () => onFormLaunching(`/api/forms?formPath=${pathEncoded}`)); $(`#${cardBodyFormsId}`).append(newDiv); }); } } }); } } function initConnectModal() { $('#freqInput').on('focusin focusout', (e) => { // Disable the connect button while the user is editing the frequency value. // We do this because we really don't want the user to hit the connect // button until they know that the QSY command succeeded or failed. window.setTimeout(() => { $('#connect_btn').prop('disabled', e.type == 'focusin'); }, 300); }); $('#freqInput').change(() => { onConnectInputChange(); onConnectFreqChange(); }); $('#bandwidthInput').change(onConnectBandwidthChange); $('#radioOnlyInput').change(onConnectInputChange); $('#addrInput').change(onConnectInputChange); $('#targetInput').change(onConnectInputChange); $('#updateRmslistButton').click((e) => { $(e.target).prop('disabled', true); updateRmslist(true); }); $('#modeSearchSelect').change(updateRmslist); $('#bandSearchSelect').change(updateRmslist); $('#transportSelect').change(function (e) { $('#bandwidthInput').val('').change(); refreshExtraInputGroups(); onConnectInputChange(); onConnectFreqChange(); switch ($(e.target).val()) { case 'ardop': case 'pactor': case 'varafm': case 'varahf': $('#modeSearchSelect').val($(e.target).val()); break; case 'ax25': case 'ax25+linux': case 'ax25+agwpe': case 'ax25+serial-tnc': $('#modeSearchSelect').val('packet'); break; default: return; } $('#modeSearchSelect').selectpicker('refresh'); updateRmslist(); }); let url = localStorage.getItem('pat_connect_url'); if( url != null ) { setConnectValues(url); } refreshExtraInputGroups(); updateConnectAliases(); updateRmslist(); } function updateRmslist(forceDownload) { let tbody = $('#rmslist tbody'); let params = { mode: $('#modeSearchSelect').val(), band: $('#bandSearchSelect').val(), 'force-download': forceDownload === true, }; $.ajax({ method: 'GET', url: '/api/rmslist', dataType: 'json', data: params, success: function (data) { tbody.empty(); data.forEach((rms) => { let tr = $('') .append($('').text(rms.callsign)) .append($('').text(rms.distance.toFixed(0) + ' km')) .append($('').text(rms.modes)) .append($('').text(rms.dial.desc)); tr.click((e) => { tbody.find('.active').removeClass('active'); tr.addClass('active'); setConnectValues(rms.url); }); tbody.append(tr); }); }, }); } function updateConnectAliases() { $.getJSON('/api/connect_aliases', function (data) { connectAliases = data; const select = $('#aliasSelect'); Object.keys(data).forEach(function (key) { select.append(''); }); select.change(function () { $('#aliasSelect option:selected').each(function () { const alias = $(this).text(); const url = connectAliases[$(this).text()]; setConnectValues(url); select.val(''); select.selectpicker('refresh'); }); }); select.selectpicker('refresh'); }); } function setConnectValues(url) { url = URI(url.toString()); $('#transportSelect').val(url.protocol()); $('#transportSelect').selectpicker('refresh'); $('#targetInput').val(url.path().substr(1)); const query = url.search(true); if (url.hasQuery('freq')) { $('#freqInput').val(query['freq']); } else { $('#freqInput').val(''); } if (url.hasQuery('bw')) { $('#bandwidthInput').val(query['bw']).change(); $('#bandwidthInput').attr('x-value', query['bw']); // Since the option might not be available yet. } else { $('#bandwidthInput').val('').change(); $('#bandwidthInput').removeAttr('x-value'); } if (url.hasQuery('radio_only')) { $('#radioOnlyInput')[0].checked = query['radio_only']; } else { $('#radioOnlyInput')[0].checked = false; } let usri = ''; if (url.username()) { usri += url.username(); } if (url.password()) { usri += ':' + url.password(); } if (usri != '') { usri += '@'; } $('#addrInput').val(usri + url.host()); refreshExtraInputGroups(); onConnectInputChange(); onConnectFreqChange(); } function getConnectURL() { let url = $('#transportSelect').val() + '://' + $('#addrInput').val() + '/' + $('#targetInput').val(); let params = ''; if ($('#freqInput').val() && $('#freqInput').parent().hasClass('has-success')) { params += '&freq=' + $('#freqInput').val(); } if ($('#bandwidthInput').val()) { params += '&bw=' + $('#bandwidthInput').val(); } if ($('#radioOnlyInput').is(':checked')) { params += '&radio_only=true'; } if (params) { url += params.replace('&', '?'); } return url; } function onConnectFreqChange() { $('#qsyWarning').empty().attr('hidden', true); const freqInput = $('#freqInput'); freqInput.css('text-decoration', 'none currentcolor solid'); const inputGroup = freqInput.parent(); ['has-error', 'has-success', 'has-warning'].forEach((v) => { inputGroup.removeClass(v); }); inputGroup.tooltip('destroy'); const data = { transport: $('#transportSelect').val(), freq: new Number(freqInput.val()), }; if (data.freq == 0) { return; } console.log('QSY: ' + JSON.stringify(data)); $.ajax({ method: 'POST', url: '/api/qsy', data: JSON.stringify(data), contentType: 'application/json', success: () => { inputGroup.addClass('has-success'); }, error: (xhr) => { freqInput.css('text-decoration', 'line-through'); if (xhr.status == 503) { // The feature is unavailable inputGroup .attr('data-toggle', 'tooltip') .attr( 'title', 'Rigcontrol is not configured for the selected transport. Set radio frequency manually.' ) .tooltip('fixTitle'); } else { // An unexpected error occured [inputGroup, $('#qsyWarning')].forEach((e) => { e.attr('data-toggle', 'tooltip') .attr( 'title', 'Could not set radio frequency. See log output for more details and/or set the frequency manually.' ) .tooltip('fixTitle'); }); inputGroup.addClass('has-error'); $('#qsyWarning') .html(' QSY failure') .attr('hidden', false); } }, complete: () => { onConnectInputChange(); }, // This removes freq= from URL in case of failure }); } function onConnectBandwidthChange() { const input = $(this); console.log("connect bandwidth change " + input.val()); input.attr('x-value', input.val()); if (input.val() === '') { input.removeAttr('x-value'); } onConnectInputChange(); } function onConnectInputChange() { $('#connectURLPreview').empty().append(getConnectURL()); } function refreshExtraInputGroups() { const transport = $('#transportSelect').val(); populateBandwidths(transport); switch (transport) { case 'telnet': $('#freqInputDiv').hide(); $('#freqInput').val(''); $('#addrInputDiv').show(); break; case 'ardop': case 'varahf': $('#addrInputDiv').hide(); $('#addrInput').val(''); $('#freqInputDiv').show(); break; default: $('#addrInputDiv').hide(); $('#addrInput').val(''); $('#freqInputDiv').show(); } if (transport.startsWith('ax25')) { $('#radioOnlyInput')[0].checked = false; $('#radioOnlyInputDiv').hide(); } else { $('#radioOnlyInputDiv').show(); } } function populateBandwidths(transport) { const select = $('#bandwidthInput'); const div = $('#bandwidthInputDiv'); var selected = select.attr('x-value'); select.empty(); select.prop('disabled', true); $.ajax({ method: 'GET', url: `/api/bandwidths?mode=${transport}`, dataType: 'json', success: function (data) { if (data.bandwidths.length === 0) { return; } if (selected === undefined) { selected = data.default; } data.bandwidths.forEach((bw) => { const option = $(``); option.prop('selected', bw === selected); select.append(option); }); select.val(selected).change(); }, complete: function (xhr) { select.attr('x-for-transport', transport); div.toggle(select.find('option').length > 0); select.prop('disabled', false); select.selectpicker('refresh'); }, }); } function handleGeolocationError(error) { if (error.message.search('insecure origin') > 0 || isInsecureOrigin()) { appendInsecureOriginWarning(statusPopoverDiv.find('#geolocation_error')); } showGUIStatus(statusPopoverDiv.find('#geolocation_error'), true); statusPos.html('Geolocation unavailable.'); } function updatePositionGeolocation(pos) { const d = new Date(pos.timestamp); statusPos.html('Last position update ' + dateFormat(d) + '...'); $('#pos_lat').val(pos.coords.latitude); $('#pos_long').val(pos.coords.longitude); $('#pos_ts').val(pos.timestamp); } function updatePositionGPS(pos) { const d = new Date(pos.Time); statusPos.html('Last position update ' + dateFormat(d) + '...'); $('#pos_lat').val(pos.Lat); $('#pos_long').val(pos.Lon); $('#pos_ts').val(d.getTime()); } function postPosition() { const pos = { lat: parseFloat($('#pos_lat').val()), lon: parseFloat($('#pos_long').val()), comment: $('#pos_comment').val(), date: new Date(parseInt($('#pos_ts').val())), }; $.ajax('/api/posreport', { data: JSON.stringify(pos), contentType: 'application/json', type: 'POST', success: function (resp) { $('#posModal').modal('toggle'); alert(resp); }, error: function (xhr, st, resp) { alert(resp + ': ' + xhr.responseText); }, }); } function previewAttachmentFiles() { const files = $(this).get(0).files; let attachments = $('#composer_attachments'); for (let i = 0; i < files.length; i++) { let file = files.item(i); uploadFiles[uploadFiles.length] = file; if (isImageSuffix(file.name)) { const reader = new FileReader(); reader.onload = function (e) { attachments.append( '' ); }; reader.readAsDataURL(file); } else { attachments.append( '' ); } } } function notify(data) { const options = { body: data.body, icon: '/res/images/pat_logo.png', }; const n = new Notification(data.title, options); } function alert(msg) { const div = $('#navbar_status'); div.empty(); div.append('' + msg + '

'); div.show(); window.setTimeout(function () { div.fadeOut(500); }, 5000); } function updateStatus(data) { const st = $('#status_text'); st.empty().off('click').attr('data-toggle', 'tooltip').attr('data-placement', 'bottom').tooltip(); const onDisconnect = function () { st.tooltip('hide'); disconnect(false, () => { // This will be reset by the next updateStatus when the session is aborted st.empty().append('Disconnecting... '); // Issue dirty disconnect on second click st.off('click').click(() => { st.off('click'); disconnect(true); st.tooltip('hide'); }); st.attr('title', 'Click to force disconnect').tooltip('fixTitle').tooltip('show'); }); }; if (data.dialing) { st.append('Dialing... '); st.click(onDisconnect); st.attr('title', 'Click to abort').tooltip('fixTitle').tooltip('show'); } else if (data.connected) { st.append('Connected ' + data.remote_addr); st.click(onDisconnect); st.attr('title', 'Click to disconnect').tooltip('fixTitle').tooltip('hide'); } else { if (data.active_listeners.length > 0) { st.append('Listening ' + data.active_listeners + ''); } else { st.append('Ready'); } st.attr('title', 'Click to connect').tooltip('fixTitle').tooltip('hide'); st.click(() => { $('#connectModal').modal('toggle'); }); } const n = data.http_clients.length; statusPopoverDiv .find('#webserver_info') .find('.panel-body') .html(n + (n == 1 ? ' client ' : ' clients ') + 'connected.'); } function closeComposer(clear) { if (clear) { $('#composer_error').val('').hide(); $('#msg_body').val(''); $('#msg_subject').val(''); $('#msg_to').tokenfield('setTokens', []); $('#msg_cc').tokenfield('setTokens', []); $('#composer_form')[0].reset(); // Attachment previews $('#composer_attachments').empty(); // Attachment input field let attachments = $('#msg_attachments_input'); attachments.replaceWith((attachments = attachments.clone(true))); } $('#composer').modal('hide'); } function connect(evt) { const url = getConnectURL(); localStorage.setItem('pat_connect_url', url); $('#connectModal').modal('hide'); $.getJSON('/api/connect?url=' + encodeURIComponent(url), function (data) { if (data.NumReceived == 0) { window.setTimeout(function () { alert('No new messages.'); }, 1000); } }).fail(function () { alert('Connect failed. See console for detailed information.'); }); } function disconnect(dirty, successHandler) { if (successHandler === undefined) { successHandler = () => {}; } $.post( '/api/disconnect?dirty=' + dirty, {}, function (response) { successHandler(); }, 'json' ); } function updateGUIStatus() { let color = 'success'; statusPopoverDiv .find('.panel-info') .not('.hidden') .not('.ignore-status') .each(function (i) { color = 'info'; }); statusPopoverDiv .find('.panel-warning') .not('.hidden') .not('.ignore-status') .each(function (i) { color = 'warning'; }); statusPopoverDiv .find('.panel-danger') .not('.hidden') .not('.ignore-status') .each(function (i) { color = 'danger'; }); $('#gui_status_light') .removeClass(function (index, className) { return (className.match(/(^|\s)btn-\S+/g) || []).join(' '); }) .addClass('btn-' + color); if (color == 'success') { statusPopoverDiv.find('#no_error').show(); } else { statusPopoverDiv.find('#no_error').hide(); } } function isInsecureOrigin() { if (hasOwnProperty.call(window, 'isSecureContext')) { return !window.isSecureContext; } if (window.location.protocol == 'https:') { return false; } if (window.location.protocol == 'file:') { return false; } if (location.hostname === 'localhost' || location.hostname.startsWith('127.0')) { return false; } return true; } function appendInsecureOriginWarning(e) { e.removeClass('panel-info').addClass('panel-warning'); e.find('.panel-body').append( '

Ensure the secure origin criteria for Powerful Features are met.

' ); updateGUIStatus(); } function showGUIStatus(e, show) { show ? e.removeClass('hidden') : e.addClass('hidden'); updateGUIStatus(); } let ws; function initConsole() { if ('WebSocket' in window) { ws = new WebSocket(wsURL); ws.onopen = function (evt) { console.log('Websocket opened'); showGUIStatus(statusPopoverDiv.find('#websocket_error'), false); showGUIStatus(statusPopoverDiv.find('#webserver_info'), true); $('#console').empty(); }; ws.onmessage = function (evt) { const msg = JSON.parse(evt.data); if (msg.MyCall) { mycall = msg.MyCall; } if (msg.Notification) { notify(msg.Notification); } if (msg.LogLine) { updateConsole(msg.LogLine + '\n'); } if (msg.UpdateMailbox) { displayFolder(currentFolder); } if (msg.Status) { updateStatus(msg.Status); } if (msg.Progress) { updateProgress(msg.Progress); } if (msg.Prompt) { processPromptQuery(msg.Prompt); } if (msg.PromptAbort) { $('#promptModal').modal('hide'); } if (msg.Ping) { ws.send(JSON.stringify({ Pong: true })); } }; ws.onclose = function (evt) { console.log('Websocket closed'); showGUIStatus(statusPopoverDiv.find('#websocket_error'), true); showGUIStatus(statusPopoverDiv.find('#webserver_info'), false); $('#status_text').empty(); window.setTimeout(function () { initConsole(); }, 1000); }; } else { // The browser doesn't support WebSocket let wsError = true; alert('Websocket not supported by your browser, please upgrade your browser.'); } } function processPromptQuery(p) { console.log(p); if (p.kind != 'password') { console.log('Ignoring unsupported prompt of kind: ' + p.kind); return; } $('#promptID').val(p.id); $('#promptResponseValue').val(''); $('#promptMessage').text(p.message); $('#promptOkButton').click(postPromptResponse); $('#promptModal').modal('show'); } function postPromptResponse() { const id = $('#promptID').val(); const value = $('#promptResponseValue').val(); $('#promptModal').modal('hide'); ws.send( JSON.stringify({ prompt_response: { id: id, value: value, }, }) ); } function updateConsole(msg) { const pre = $('#console'); pre.append('' + msg + ''); pre.scrollTop(pre.prop('scrollHeight')); } const getCellValue = (tr, idx) => tr.children[idx].innerText || tr.children[idx].textContent; const comparer = (idx, asc) => (a, b) => ((v1, v2) => v1 !== '' && v2 !== '' && !isNaN(v1) && !isNaN(v2) ? v1 - v2 : v1.toString().localeCompare(v2))( getCellValue(asc ? a : b, idx), getCellValue(asc ? b : a, idx) ); let currentFolder; function displayFolder(dir) { currentFolder = dir; const is_from = dir == 'in' || dir == 'archive'; const table = $('#folder table'); table.empty(); table.append( 'Subject' + '' + (is_from ? 'From' : 'To') + '' + (is_from ? '' : 'P2P') + 'DateMessage ID' ); const tbody = $('#folder table tbody'); $.getJSON('/api/mailbox/' + dir, function (data) { for (let i = 0; i < data.length; i++) { const msg = data[i]; //TODO: Cleanup (Sorry about this...) let html = ''; if (msg.Files.length > 0) { html += ''; } html += '' + htmlEscape(msg.Subject) + ''; if (!is_from && !msg.To) { html += ''; } else if (is_from) { html += msg.From.Addr; } else if (msg.To.length == 1) { html += msg.To[0].Addr; } else if (msg.To.length > 1) { html += msg.To[0].Addr + '...'; } html += ''; html += is_from ? '' : '' + (msg.P2POnly ? '' : '') + ''; html += '' + msg.Date + '' + msg.MID + ''; const elem = $(html); tbody.append(elem); elem.click(function (evt) { displayMessage($(this)); }); } }); // Adapted from https://stackoverflow.com/a/49041392 document.querySelectorAll('th').forEach((th) => th.addEventListener('click', () => { const table = th.closest('table'); const tbody = table.querySelector('tbody'); Array.from(tbody.querySelectorAll('tr')) .sort(comparer(Array.from(th.parentNode.children).indexOf(th), (this.asc = !this.asc))) .forEach((tr) => tbody.appendChild(tr)); const previousTh = table.querySelector('th.sorted'); if (previousTh != null) { previousTh.classList.remove('sorted'); } th.classList.add('sorted'); }) ); } function displayMessage(elem) { const mid = elem.attr('ID'); const msg_url = buildMessagePath(currentFolder, mid); $.getJSON(msg_url, function (data) { elem.attr('class', 'info'); const view = $('#message_view'); view.find('#subject').text(data.Subject); view.find('#headers').empty(); view.find('#headers').append('Date: ' + data.Date + '
'); view.find('#headers').append('From: ' + data.From.Addr + '
'); view.find('#headers').append('To: '); for (let i = 0; data.To && i < data.To.length; i++) { view .find('#headers') .append('' + data.To[i].Addr + '' + (data.To.length - 1 > i ? ', ' : '')); } if (data.P2POnly) { view.find('#headers').append(' (P2P only)'); } if (data.Cc) { view.find('#headers').append('
Cc: '); for (let i = 0; i < data.Cc.length; i++) { view .find('#headers') .append('' + data.Cc[i].Addr + '' + (data.Cc.length - 1 > i ? ', ' : '')); } } view.find('#body').html(data.BodyHTML); const attachments = view.find('#attachments'); attachments.empty(); if (!data.Files) { attachments.hide(); } else { attachments.show(); } for (let i = 0; data.Files && i < data.Files.length; i++) { const file = data.Files[i]; const formName = formXmlToFormName(file.Name); let renderToHtml = 'false'; if (formName) { renderToHtml = 'true'; } const attachUrl = msg_url + '/' + file.Name + '?rendertohtml=' + renderToHtml; if (isImageSuffix(file.Name)) { attachments.append( '' ); } else if (formName) { attachments.append( '' ); } else { attachments.append( '' ); } } $('#reply_btn').off('click'); $('#reply_btn').click(function (evt) { $('#message_view').modal('hide'); $('#msg_to').tokenfield('setTokens', [data.From.Addr]); $('#msg_cc').tokenfield('setTokens', replyCarbonCopyList(data)); if (data.Subject.lastIndexOf('Re:', 0) != 0) { $('#msg_subject').val('Re: ' + data.Subject); } else { $('#msg_subject').val(data.Subject); } $('#msg_body').val('\n\n' + quoteMsg(data)); $('#composer').modal('show'); $('#msg_body').focus(); $('#msg_body')[0].setSelectionRange(0, 0); //opens browser window for a form-based reply, // or does nothing if this is not a form-based message showReplyForm(msg_url, data); }); $('#forward_btn').off('click'); $('#forward_btn').click(function (evt) { $('#message_view').modal('hide'); $('#msg_to').tokenfield('setTokens', ''); $('#msg_subject').val('Fw: ' + data.Subject); $('#msg_body').val(quoteMsg(data)); $('#msg_body')[0].setSelectionRange(0, 0); $('#composer').modal('show'); $('#msg_to-tokenfield').focus(); }); $('#delete_btn').off('click'); $('#delete_btn').click(function (evt) { deleteMessage(currentFolder, mid); }); $('#archive_btn').off('click'); $('#archive_btn').click(function (evt) { archiveMessage(currentFolder, mid); }); // Archive button should be hidden for already archived messages if (currentFolder == 'archive') { $('#archive_btn').parent().hide(); } else { $('#archive_btn').parent().show(); } view.show(); $('#message_view').modal('show'); let mbox = currentFolder; if (!data.Read) { window.setTimeout(function () { setRead(mbox, data.MID); }, 2000); } elem.attr('class', 'active'); }); } function formXmlToFormName(fileName) { let match = fileName.match(/^RMS_Express_Form_([\w \.]+)-\d+\.xml$/i); if (match) { return match[1]; } match = fileName.match(/^RMS_Express_Form_([\w \.]+)\.xml$/i); if (match) { return match[1]; } return null; } function showReplyForm(orgMsgUrl, msg) { for (let i = 0; msg.Files && i < msg.Files.length; i++) { const file = msg.Files[i]; const formName = formXmlToFormName(file.Name); if (!formName) { continue; } // retrieve form XML attachment and determine if it specifies a form-based reply const attachUrl = orgMsgUrl + '/' + file.Name; $.get( attachUrl + '?rendertohtml=false&composereply=false', {}, function (data) { let parser = new DOMParser(); let xmlDoc = parser.parseFromString(data, 'text/xml'); if (xmlDoc) { let replyTmpl = xmlDoc.evaluate( '/RMS_Express_Form/form_parameters/reply_template', xmlDoc, null, XPathResult.STRING_TYPE, null ); if (replyTmpl && replyTmpl.stringValue) { window.setTimeout(startPollingFormData, 500); open(attachUrl + '?rendertohtml=true&composereply=true'); } } }, 'text' ); return; } } function replyCarbonCopyList(msg) { let addrs = msg.To; if (msg.Cc != null && msg.Cc.length > 0) { addrs = addrs.concat(msg.Cc); } const seen = {}; seen[mycall] = true; seen[msg.From.Addr] = true; const strings = []; for (let i = 0; i < addrs.length; i++) { if (seen[addrs[i].Addr]) { continue; } seen[addrs[i].Addr] = true; strings.push(addrs[i].Addr); } return strings; } function quoteMsg(data) { let output = '--- ' + data.Date + ' ' + data.From.Addr + ' wrote: ---\n'; const lines = data.Body.split('\n'); for (let i = 0; i < lines.length; i++) { output += '>' + lines[i] + '\n'; } return output; } function htmlEscape(str) { return $('
').text(str).html(); } function archiveMessage(box, mid) { $.ajax('/api/mailbox/archive', { headers: { 'X-Pat-SourcePath': buildMessagePath(box, mid), }, contentType: 'application/json', type: 'POST', success: function (resp) { $('#message_view').modal('hide'); alert('Message archived'); }, error: function (xhr, st, resp) { alert(resp + ': ' + xhr.responseText); }, }); } function deleteMessage(box, mid) { $('#confirm_delete').on('click', '.btn-ok', function (e) { $('#message_view').modal('hide'); const $modalDiv = $(e.delegateTarget); $.ajax(buildMessagePath(box, mid), { type: 'DELETE', success: function (resp) { $modalDiv.modal('hide'); alert('Message deleted'); }, error: function (xhr, st, resp) { $modalDiv.modal('hide'); alert(resp + ': ' + xhr.responseText); }, }); }); $('#confirm_delete').modal('show'); } function setRead(box, mid) { const data = { read: true }; $.ajax(buildMessagePath(box, mid) + '/read', { data: JSON.stringify(data), contentType: 'application/json', type: 'POST', success: function (resp) {}, error: function (xhr, st, resp) { alert(resp + ': ' + xhr.responseText); }, }); } function isImageSuffix(name) { return name.toLowerCase().match(/\.(jpg|jpeg|png|gif)$/); } function dateFormat(previous) { const current = new Date(); const msPerMinute = 60 * 1000; const msPerHour = msPerMinute * 60; const msPerDay = msPerHour * 24; const msPerMonth = msPerDay * 30; const msPerYear = msPerDay * 365; const elapsed = current - previous; if (elapsed < msPerDay) { return ( (previous.getHours() < 10 ? '0' : '') + previous.getHours() + ':' + (previous.getMinutes() < 10 ? '0' : '') + previous.getMinutes() ); } else if (elapsed < msPerMonth) { return 'approximately ' + Math.round(elapsed / msPerDay) + ' days ago'; } else if (elapsed < msPerYear) { return 'approximately ' + Math.round(elapsed / msPerMonth) + ' months ago'; } else { return 'approximately ' + Math.round(elapsed / msPerYear) + ' years ago'; } } function buildMessagePath(folder, mid) { return '/api/mailbox/' + encodeURIComponent(folder) + '/' + encodeURIComponent(mid); } pat-0.15.1/web/src/scss/000077500000000000000000000000001453425652100147105ustar00rootroot00000000000000pat-0.15.1/web/src/scss/app.scss000066400000000000000000000111261453425652100163660ustar00rootroot00000000000000@import '~bootstrap/dist/css/bootstrap.min.css'; @import '~bootstrap-select/dist/css/bootstrap-select.min.css'; @import '~bootstrap-tokenfield/dist/css/bootstrap-tokenfield.min.css'; html { position: relative; min-height: 100%; } body { /* Margin bottom by footer height */ margin-bottom: 130px; } body > .container { padding: 75px 15px 0; } .btn:focus { outline: none !important; } .navbar-link { text-decoration: none !important; } .status-text { font-weight: bold; font-size: 11px !important; margin: 0px 0px 0; } @media screen and (min-width: 1024px) { .modal.modal-narrow .modal-dialog { width: 30%; } .modal-narrow .modal-body { overflow-y: auto; } } .progress-text { padding-top: 3px; font-size: 11px !important; color: #555555; white-space: nowrap; overflow: hidden; display: block; text-overflow: ellipsis; } .navbar > .container { margin-bottom: -10px; } .alert-small { width: 300px; } .strong { font-weight: bold; } /* * GUI Status Popover */ .popover-content { padding: 9px 12px; padding-bottom: 3px; } .panel-sm { margin-bottom: 6px; } .panel-sm > .panel-heading { font-weight: bold; font-size: 14px; padding: 5px 10px; } .panel-sm > .panel-body { font-size: 12px; line-height: 1.4; padding: 10px; } .status-light { width: 10px; height: 10px; text-align: center; padding: 1px 0; border-radius: 15px; margin-bottom: 3px; margin-right: -3px; } /* * Mailbox folder */ #folder { max-height: 345px; overflow-y: auto; } #folder > table { font-size: 80%; } .sorted { color: maroon; } /* * Message viewer */ #message_view { display: none; font-size: 80%; } #composer { display: none; font-size: 80%; } .modal-header .btn-group { margin-right: 5px; margin-top: -5px; } /* Work-around for issue with tokenfield and input-group-sm on bootstrap 3.1 or newer */ .input-group-sm .tokenfield { height: auto; } .modal-header .btn-group button { color: #fff; } .modal-header .btn-group button:hover { color: #fff; } .primary.modal-header { background-color: #337ab7; color: #fff; } .primay.modal-body { padding: 0px 0; margin-left: 5px; } .primary.modal-footer { background-color: #333; } #composer_attachments { margin-top: 10px; } #body { border: none; white-space: pre-wrap; min-height: 50px; margin: 3px; } #msg_body { min-height: 150px; white-space: pre-wrap; } .panel-title { font-size: 115%; font-weight: bolder; } @media screen and (min-width: 1024px) { #body { min-height: 400px; max-height: 600px; overflow-y: auto !important; white-space: pre-wrap; } #msg_body { min-height: 400px; max-height: 600px; overflow-y: auto !important; white-space: pre-wrap; } #message_view { font-size: 80%; } #folder { max-height: 700px; overflow-y: auto; } } .panel-fluid { margin: 0px 0; padding: 0px 0; } .panel-heading p { margin: 0px 0; } #connectURLPreview { font-size: 80%; } /* * Log console */ .footer { position: absolute; bottom: 15px; width: 100%; height: 120px; } #console { height: 120px; overflow-y: auto; background-color: #272822; color: #ffffff; line-height: 0.5em; padding: 3px; } #console .terminal { font-size: 0.6em; } .msgbody p { } .msgbody blockquote { font-size: 1em; color: #777777; margin-top: 0px; padding-left: 5px; margin-bottom: 10px; } .msgbody blockquote p { margin-top: 0px; margin-bottom: 0px; } .msgbody blockquote ul { margin-top: 0px; margin-bottom: -20px; margin-left: 0px; padding-left: 15px; } @media screen and (min-width: 1024px) { .footer { position: absolute; bottom: 15px; width: 100%; height: 130px; } #console { height: 120px; overflow-y: auto; background-color: #272822; color: #ffffff; line-height: 1em; padding: 3px; } #console .terminal { font-size: 0.9em; } } /* Attachment add */ .btn-file { position: relative; overflow: hidden; } .btn-file input[type='file'] { position: absolute; top: 0; right: 0; min-width: 100%; min-height: 100%; font-size: 100px; text-align: right; filter: alpha(opacity=0); opacity: 0; outline: none; background: white; cursor: inherit; display: block; } .compose-options { margin-top: 10px; margin-bottom: -10px; } #composer_error { font-size: 12px; margin: 5px 0px; padding: 5px 5px; } input[type='checkbox'], input[type='radio'] { vertical-align: -2px; margin: 0; padding: 0; } .table-fixed { overflow-y: auto; height: 400px; } .table-fixed thead th { position: sticky; top: 0; background: #eee; } pat-0.15.1/web/src/static/000077500000000000000000000000001453425652100152245ustar00rootroot00000000000000pat-0.15.1/web/src/static/LICENSES.md000066400000000000000000000146041453425652100167600ustar00rootroot00000000000000The following licenses covers all third party software used by the web GUI ============================================================================== Name: Bootstrap Version: v3.2.0 Repository: github.com/twbs/bootstrap The MIT License (MIT) Copyright (c) 2011-2016 Twitter, Inc. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ============================================================================== Name: JQuery Version: v1.11.1 Repository: github.com/jquery/jquery Copyright 2014 jQuery Foundation and other contributors http://jquery.com/ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ============================================================================== Name: URI.js Version: v1.16.1 (?) Repository: github.com/medialize/URI.js The MIT License (MIT) Copyright (c) 2011 Rodney Rehm Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ============================================================================== Name: Bootstrap-select Version: v1.7.5 Repository: github.com/silviomoreto/bootstrap-select The MIT License (MIT) Copyright (c) 2013-2015 bootstrap-select Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ============================================================================== Name: Boostrap-tokenfield (Open-Xchange-Frontend fork) Version: v0.12.1 (db79219699545ddea9b82ce31716045d064a5d5c) Repository: github.com/Open-Xchange-Frontend/bootstrap-tokenfield #### Sliptree - by Illimar Tambek for [Sliptree](http://sliptree.com) - Copyright (c) 2013 by Sliptree Available for use under the [MIT License](http://en.wikipedia.org/wiki/MIT_License) Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. pat-0.15.1/web/src/static/pat_logo.png000066400000000000000000000463061453425652100175470ustar00rootroot00000000000000PNG  IHDR>vhRsBIT|d pHYs:tEXtSoftwarewww.inkscape.org< IDATxy|dgy'UڒZݭn^0f5!6.0!$w@̇ޛp0!dX $-/ܶ{{ծ>RKju[jj}Ma-#Oϻbf!D;Q>!5 >!DۑB >!DۑB >!DۑB >!DۑB >!DۑB >!DۑB >!DۑB >!DۑB >!DۑB >!DۑB >!DۑB >!DۑB >!DۑB >!DۑB >!DۑB >!DۑB >!DۑB >!DۑB >!DۑB >!DۑB >!DۑB >!DۑB >!DۑB >!DۑB >!DۑB >!DۑB >!DۑB >!DۑB >!DۑB >!DۑB >!DۑB >!DۑB >!DۑB >!DۑB >!DۑB >!DۑB >!DۑB >!DۑB >!DۑB >!DۑB >!DۑB >!DۑB >!D1}҈{-=;1~H~FZ˫>W[JqS[04t 1 b^|m󢎈V&.W7hJ8oU&Wk]% F0Lah 3la3bftK_}DCm.\"8PT E"8JQ J[(Ņ(AI|[voFfF ~>m\~V)ax|>$G-#@4D{@"E-b:"J!(AX|U*,&# #̌ +π >RY( B ,KҠ M+9((@@^%BV2DH{^}7MR)8 B ۤeW;02))1]4`;_٪QZ;AXDL)tjУ5xQ" D,#.EJ֘,g2HcY`;V4k-@Q"tzz B]x o !*[#ρq fƔea!BQ`̺~:hֆI`4Af “h&. Y`ƔR81Ng\)tmzǥfm\)tC?L08ߤ$DsZfdpSpRkm̦ȠbF˪<?DD B?14O@iҊPy+0 ,`+FB'@9,9"j STb1 $ B0aOOA<0L2ʈbZ# tL* "LӷmoYС1 `nbZN&Ė=|Oy' @@X1B:0l DG[\l*A 0`M[!jLh`H@cqvk.f 茣t~`RW|Ћ˄m` nj}yB?" X!`r>jo߲A ;A(C'1`"@Ο TA|e2KMߦ aDnc C t8zBRXf_ +f~M~-|B !2aD FGy24mLbX Q`30 lRl "E2a1\B-5}AT0f`D0~eE2r xpB2h!w9*V@ [D&'' zB^u>\H^*HoCLpn[6 `@1FxBZ>/P$ _,9 (&Pi%Dm0rI'D}-_۩h9M#nʞzB:n& hB"&1H ,y(fPߥ׃ QRR 8JG1 rDRȩgQΏ8~&ĩ*CxFzQ*Rr.!"r(_:M\+BX4aU;' !W.R @\ǁ5l-f*80Xٯ'#B46`_"0Kn:6P.~D1D2^BOƷi(,8>Q@uj6Hh Azn+ү'DP,"D@A|AiFF ;);>$GA+DY>/Bi{:Z_ DB7Ai>,Mq BoяPj2*W{$<Dz@aCB+*P~DBFBOv $G>ׇ8j\5LUP:гluFⲕ.WI > ֶk0!GS"F/3TӄxvV} |˪=RA0d@CVS$Ba#\ei5D\:~=Nbt3˜=!ZXhB"xkGW +DH!DJ5Dbȑg#>v>*]!RBHj|Ha D0]XD2}t>Pj~fDrݥE USO.|1aA іӈC>mfs7v&jAKG4s#Kdh?̰:;{w:tݳ{7#?Pi"}uP̬`҄uPQ!DjiZ;C}7u,>9\vpiC;".@0)Dj|fZGZ# EFwO~ζs]f^a0g |.Ih70ݻo}24kh'? 0vVo~bGg-Zçfz )w$;w}wi5/9waCkJ?I f=Qc͌R 4# Ƿmowvth0/={|#N(s=tE>V _\U?7_V a;\}a/0 ʃ?4:NW+Mtɚ&\CE)D7¢y=-7e__,;8i P X ӺI: MTihh,jĭܴflۧ_u7lZ=Z#8ofy|UɬZf-#I۷Ox ye(i/uVnyǝJ=m4.RWVbM+>.]< l:*FG0~ߜ sw#w 7Dv/m#D R/OJ+VWb ,(i _,ޱ{}~p`Ԕ+2̉r3,V ;dM^c&,:_JĀ ƶon,җ]?s,gwj6vҶҔR KscSZj ,.}3ҿ'Z3PH֨ƍtv$~m#IO-a#U!UbhhṵΉo-?66Zp+q=>^O6^JdYK W{C}~{Ʋmy7OK@ί3Dװ(PUi%2W'7<*mq.X|B4"@hNG"a3k(wQ_y_6WZU=7j(*=hI0ΉosxAʊua =$Z})*M]1ܿPo@bXv`(Wm۾069{eʽo-ݞFS\T¯j]d ]~H @0RhKVVTUAk|T.Q( BaFbɝW:ۻ7o(C}}~;2}ݷss+BM~\بF=.6&D+PaF"؝w9c۶Zy)>sok1Jz=$BM~h⦮h5P(yM*L(٫AnSCA L&{'&XWmzvԣMBO fp|޽{d SmzYZKb7U#w}all,dҶ A!."evk^}炿JU:]F`N&οyF+/m >!.3|plݚrfv.z|WgOf%m3nW_1 7f ݈,;믟3L=e.4 >0?d׿w {߆I ~bWM~k<_b֙{˲3?ym +Ù ? >QM 0\i?mXqQUJco{{̢kɿ|b*aghRd&@KhovH+jC*xbbW*ͩ|MO\aש <>=gϞǎ~\9-!jNrbVOڶnE5@izrrB6U>yB),OOc/ϜF:q5M}םmq%kߏns|ĉ{I `eE=."m,zɰ[s''uOOM%OB |0#;&'wwwjf lBWrϞ=3D(nm% "F'|jǶ̾}{KeP)==ݹ׽'~O>yE=JЧ>0-Dv4II@'ᥰm?{bbv`?k]=۶ݻ{+{zj*uHyVi :tL w5g+MطCD<48?|p8X& ;˶lau ՔR^wwW;nG?qS?yz }GUf?cdd[fp?MDwnܫF@+xltd/Dim|guu0TmY`ozĮفmZ)z>\uՕg{zz2t:Wۂ(Rfb10^ IDATܘ?ͨV@;wnSO=)66 q< Ti4#`[ Ke7@Eիmwt>7}n/mk\KanjPe}}];wfm^v hfN@8nSG}.c?Y,ΙmvesC`hm?6,;p`|/uAI5aiޮm[Z+RLTFkV4-۹sl#잙T1JR eYޣiz^ٻf7,=xB0pܹ]&WS+;4Mݙ16חmۻ` - HDّ̼򕷼d3Gw\i>H3|߾{b׹P(Th,mubf}vCOÇfri_}4 ӑ޾}lgwamQe'g;?ەyGl42? uvv8##CsDЍ c0(>|жTkm|<삕0@ww2566:ߛmgpzS|-GQӧ'5 fJչocinDegGG$.:tă=-N,mZ DjttdR3SOZáPk>GO>Ը:q4QM_fL+c|T4ͱf-#h?"X,;xyۊEvᒕ|շ::*aOl}>-m0 w|lgG2Ï-.,N?k(,`mFzdt )xeV\}?L&3{_я:^ݗZ&w[h f+RF0Ol^yѶmw 0.$t<|ˎ?ӝǎ߫~4pҤbM12މ,ksMf7ڒ9"pwwgjϞ]'z]'*)qi[̓Ki,ѲU+dW x}?ϗt_u}W=^s%"2 d,5<<4[6cu}Q:mv&=ՙя+ qsDp ˿cxxh&eK u4to|P49.ɻJ,4ǯ qq mX X#xWKH &@imҊ*]^ҥi VPKo+m3 ci&,m&Qyijmej07)"ӽx/ǏwOM0 ""Bf貽䏒)/\ݖպ⣀?}"2"RJi TEK!S m*0جiiQe(VT |HG"^y]pߋwcNp8 䯺ʓǎ?qo玱i:5A7|#.0<[(3J~i[MΕ'wb! ke޹Do[5VS ^ۭtᎍ /ضla˒w2N1gffx^Z/mudži8J)U5V\kNBjĦj-xmYNJHWi:c/8;{+wæy|x<:븎qNN ~T|.qiߤ 7.fx7k#rט۫3U5.J||mf8< 7Y^Ǎ Ց8::d{T<koF<Wk?v-&ENOwykUkefi]4/HNip9#R]z9}}]?њ!U9B4&>+ڏ MSpepCvj>Yl?7=C:8ͺldG{쑅 /p*DTk2Xlqo<__/b(i^mt뭷%Vȑg>{$_cS#4l߾/`C߾[>FLvXӿ8go|zؘz nH'_ _|oXXNVn-w|C>\cbIoK%7?={|>ƶ&~+^?M?}fkX \.,_}eYϼ? g/G>񍿻IebdFr3=`&Y ##w=}>6!hs_ۿ?gP"ѨeZ93Թ>8;;f\v4K ُ}k~n~U{Wuߐ⊃a\df>zԣ<}')3?~l{y_sN\WvwXo{1: g>G^'z[g?ؓ}}dpC^\eG}x]UKq]:9#s:=B}Wk<[:F4?W's=:Svܟzrw~߾Q_mylјdpCT={><[Y~ w_{ao=Ҥ5ņDx[kcؚN<}tkAWk.h&?!a /̶mHv۷nī^ugpR{ϸ۰{R=L&7ۜM7Z7U5ſ:W*sK5>ɽȇ/V2Mz{x~~a&B!ӟÉ;_"ڒ4G0*_Ti"xT(/*_'{O}G|kWOS߿`zamm5u7m˗qy-H nQ wl& J6.7oK-*F2z%h|}:jfwppȖuRTj nl9S22*aU7c&G)ei ײ)*7!Y}﯌ܹ낁KՑ?vť=r恁_eX 1D @"4UEf*BA3\6?_pstP(d2i Zکsro/|+W;X)Ny/ү\HĊD"5G(w !) *Uy|5mZ@2,p˓%Z\z[KRT?Y;d[X/}ѹsg/X[?q!/ M <PTX,RT7QR짹{~ÃǎO{o{xmz1̌{ǟtaa);1@.X-XԭuuEߗ1 # (mL*_xʛ3 g§>[Ouwc_8#O>}*P,^QT8z<O;m*CDH?ܿGEL+Pj{@+>۶L&uGG2H;D˅*{J>; b6W|[BI feAY*ە! WLHtGGG!WUX /GQspvvJ2`PA^eg[3ﯨyׯ~:9E];y^xPZ͵xXt!PJ5DRDoaddӺ-Pjz==]P?oR* +8hWf"cxzddĉF׿+A/EӴAidf ?! (kZ|8ɍXyP4wdT{;vts2g@HhY)ȘmT,NLL[u>TMLL=Cx8:k)TUBQSJmۜFBS{$*UD.9U_Z>!Z(Lcֶbxfpp& "UH߶t"|Y0HB#}u. [ܻwo˫=oy= yNZBQVsJ7rVʜg顡Ė]Kox<%|34f([(UMHQ4՜Oѳ#} ̗۫R895ͦN]'Ξ燆Qn+D"@CM:OwLo۹3;11*9mۙN&;i0*%B4RG3Mcƶ}gbĹ:T\J"__= Uota♂S3he?4 -H'D`O ̎ۗjY |[F'&gΤnщkY&o\MvV$P)0ήscc{:X.Z!=99={vqnnᬓ/D=]dMlBʜaӶw2>8:Сݻk^ Z:N:U?;dN='`i  2f}u:59K |022ܹ[煠u"JFyhH Fh4`DWW=W^y-^{m1;3dO Š ;ESdf XJ+<_Jtڹs|k׫[l7?+κncЎk3>!ʊ~=B,axl޽Wf'''k:go- |@)&''ݳgfr^}&3-nyB& Erp8b]&S\sM5E> +IDAT\sMqvv6J8?<k]3RT~BIe^^7-?:{} 7xc.i7xcY\\fsyZ[ -S뚳ZΘp҄H[+32M?'ۿW\QҰ+7.%W_L.;`QP:.'Ėʴ"JqK$ǶF fkonpף_Ld0Zǖ鍊n*y5Q9<\2IW)Q@i2|Hh_]97y z@-ߪ H೟,{ܳf8bӎj $0?ʇ\>R_PvŽVķGj0>x8;:00UWjܵsCߺ#L0 +`eeP12!Z  2V>cUK;|N.r jV)uڲc8GF ;vmK_: |WLSw/[aDQDC6QMF)A=XҬ fd0$GqNgXPqIKU|O(XDG r!B2h Q%<3RP"Ƌp^s Q =lhDTL〗H,YHHWcrĘoΚH0 '@ oӎlоT(d:BAo̒cqޥC'={6-Q0gJb0!ow^qsWHS3O;ߟ3>O5z |b,%^a\4K_MZm)?ݸNKSZhDu33&g89\k6>tOzY)olpk78W鈛}Xn >x鱼̌U`Aa <xk Ę WHsx`_Blz8'Vgg̫c7%qyso[4F32`6p>hhZOyOʷs|ߪ9﷾խq\,%^[  ~qsl&Rh-0枙102c21*)1) &f[qٿ}G)p}7HpXu9̙N aϝ'9j!CJ'Z/}3q>3~?Xvٞ3vd,X`17gcflM̘Ĭ,,w*vHA"ƒV;Q.x;Gx'lX?{AǦ#^GM#',AVoP*5U:%fEb4lN<Z# OZ V\@QRL3;sX:Po~}pkm[+z֞%%,Sba"%V)JC5:P~!5& Cg;-Mͽ~M{Q6Ûye4$'}L w=0nW?#&Z|~oo` EۯH8G!DD'"(|"'"(|"'"(|"'"(|"'"(|"'"(|"'"(|"'"(|"'"(|"'"(|"'"(|"'"(|"'"(|"'"(|"'"(|"'"(|"'"(|"'"(|"'"(|"'"(|"'"(|"'"(|"'"(|"'"(|"'"(|"'"(|"'"(|"'"(|"'"(|"'"(|"'"(|"'"(|"'"(|"'"(|"'"(|"'"(|"'"(|"'"(|"'"(|"'"(|"'"(|" 0&CIENDB`pat-0.15.1/web/webpack.config.js000066400000000000000000000102461453425652100163670ustar00rootroot00000000000000/* * Created by Artyom Manchenkov * artyom@manchenkoff.me * manchenkoff.me © 2019 */ // import plugins const path = require('path'); const webpack = require('webpack'); const { CleanWebpackPlugin } = require('clean-webpack-plugin'); const MiniCssExtractPlugin = require('mini-css-extract-plugin'); const TerserPlugin = require('terser-webpack-plugin'); const OptimizeCSSAssetsPlugin = require('optimize-css-assets-webpack-plugin'); const CopyPlugin = require('copy-webpack-plugin'); const ImageminPlugin = require('imagemin-webpack-plugin').default; /** * Base webpack configuration * * @param env -> env parameters * @param argv -> CLI arguments, 'argv.mode' is the current webpack mode (development | production) * @returns object */ module.exports = (env, argv) => { let isProduction = argv.mode === 'production'; let config = { // absolute path to the base directory context: path.resolve(__dirname, 'src'), // development server with hot-reload devServer: { publicPath: '/dist/', watchContentBase: true, compress: true, }, // entry files to compile (relative to the base dir) entry: ['./js/app.js', './scss/app.scss'], // enable development source maps // * will be overwritten by 'source-maps' in production mode devtool: 'inline-source-map', // path to store compiled JS bundle output: { // bundle relative name filename: 'js/app.js', // base build directory path: path.resolve(__dirname, 'dist'), // path to build relative asset links publicPath: '../', }, // plugins configurations plugins: [ // save compiled SCSS into separated CSS file new MiniCssExtractPlugin({ filename: 'css/style.css', }), // copy static assets directory new CopyPlugin([ { from: 'static', to: 'static' }, { from: 'index.html', to: 'index.html' }, ]), // image optimization new ImageminPlugin({ // disable for dev builds disable: !isProduction, test: /\.(jpe?g|png|gif)$/i, pngquant: { quality: '70-85' }, optipng: { optimizationLevel: 9 }, }), // provide jQuery and Popper.js dependencies new webpack.ProvidePlugin({ $: 'jquery', jQuery: 'jquery', jquery: 'jquery', 'window.jQuery': 'jquery', Popper: ['popper.js', 'default'], }), ], // production mode optimization optimization: { minimizer: [ // CSS optimizer new OptimizeCSSAssetsPlugin(), // JS optimizer by default new TerserPlugin(), ], }, // custom loaders configuration module: { rules: [ // styles loader { test: /\.(sa|sc|c)ss$/, use: [MiniCssExtractPlugin.loader, 'css-loader', 'sass-loader'], }, // images loader { test: /\.(png|jpe?g|gif)$/, loaders: [ { loader: 'file-loader', options: { name: 'img/[name].[ext]', }, }, { loader: 'image-webpack-loader', options: { disable: !isProduction, mozjpeg: { progressive: true, quality: 65, }, pngquant: { quality: '65-90', speed: 4, }, optipng: { enabled: false }, gifsicle: { interlaced: false }, webp: { quality: 75 }, }, }, ], }, // fonts loader { test: /\.(woff|woff2|eot|ttf|otf)$/, use: [ { loader: 'file-loader', options: { name: 'fonts/[name].[ext]', }, }, ], }, // svg inline 'data:image' loader { test: /\.svg$/, loader: 'svg-url-loader', }, ], }, }; // PRODUCTION ONLY configuration if (isProduction) { config.plugins.push( // clean 'dist' directory new CleanWebpackPlugin() ); } return config; }; pat-0.15.1/websocket_hub.go000066400000000000000000000150651453425652100155530ustar00rootroot00000000000000// Copyright 2016 Martin Hebnes Pedersen (LA5NTA). All rights reserved. // Use of this source code is governed by the MIT-license that can be // found in the LICENSE file. package main import ( "bufio" "encoding/json" "errors" "io" "log" "os" "path" "runtime" "sync" "time" "github.com/fsnotify/fsnotify" "github.com/gorilla/websocket" "github.com/la5nta/pat/internal/debug" "github.com/la5nta/pat/internal/osutil" "github.com/la5nta/wl2k-go/mailbox" ) const KeepaliveInterval = 4 * time.Minute // WSConn represent one connection in the WSHub pool type WSConn struct { conn *websocket.Conn out chan interface{} } // WSHub is a hub for broadcasting data to several websocket connections type WSHub struct { mu sync.Mutex pool map[*WSConn]struct{} } func NewWSHub() *WSHub { w := &WSHub{pool: map[*WSConn]struct{}{}} go w.watchMBox() return w } func (w *WSHub) UpdateStatus() { w.WriteJSON(struct{ Status Status }{getStatus()}) } func (w *WSHub) WriteProgress(p Progress) { w.WriteJSON(struct{ Progress Progress }{p}) } func (w *WSHub) WriteNotification(n Notification) { w.WriteJSON(struct{ Notification Notification }{n}) } func (w *WSHub) Prompt(p Prompt) { w.WriteJSON(struct{ Prompt Prompt }{p}) go func() { <-p.cancel; w.WriteJSON(struct{ PromptAbort Prompt }{p}) }() } func (w *WSHub) WriteJSON(v interface{}) { if w == nil { return } w.mu.Lock() for c := range w.pool { select { case c.out <- v: case <-time.After(3 * time.Second): log.Println("Closing one unresponsive web socket") c.conn.Close() delete(w.pool, c) } } w.mu.Unlock() } func (w *WSHub) ClientAddrs() []string { if w == nil { return nil } w.mu.Lock() defer w.mu.Unlock() addrs := make([]string, 0, len(w.pool)) for c := range w.pool { addrs = append(addrs, c.conn.RemoteAddr().String()) } return addrs } func (w *WSHub) watchMBox() { // Maximise ulimit -n: // fsnotify opens a file descriptor for every file in the directories it watches, which // may more files than the current soft limit. The is especially a problem on macOS which // has a default soft limit of only 256 files. Windows does not have a such a limit. if runtime.GOOS != "windows" { if err := osutil.RaiseOpenFileLimit(4096); err != nil { log.Printf("Unable to raise open file limit: %v", err) } } fsWatcher, err := fsnotify.NewWatcher() if err != nil { log.Println("Unable to start fs watcher: ", err) return } defer fsWatcher.Close() // Add all directories in the mailbox to the watcher for _, dir := range []string{mailbox.DIR_INBOX, mailbox.DIR_OUTBOX, mailbox.DIR_SENT, mailbox.DIR_ARCHIVE} { p := path.Join(mbox.MBoxPath, dir) debug.Printf("Adding '%s' to fs watcher", p) if err := fsWatcher.Add(p); err != nil { log.Printf("Unable to add path '%s' to fs watcher: %v", p, err) } } // Listen for filesystem events and broadcast updates to all clients for { select { case e := <-fsWatcher.Events: if e.Op == fsnotify.Chmod { continue } // Make sure we don't send many of these events over a short period. drainUntilSilence(fsWatcher, 100*time.Millisecond) websocketHub.WriteJSON(struct { UpdateMailbox bool }{true}) case err := <-fsWatcher.Errors: log.Println(err) } } } // Handle adds a new websocket to the hub // // It will block until the client either stops responding or closes the connection. func (w *WSHub) Handle(conn *websocket.Conn) { debug.Printf("ws[%s] subscribed", conn.RemoteAddr()) c := &WSConn{ conn: conn, out: make(chan interface{}, 1), } w.mu.Lock() w.pool[c] = struct{}{} w.mu.Unlock() // Initial status update // (broadcasted as it includes info to other clients about this new one) w.UpdateStatus() quit := wsReadLoop(conn) // Disconnect and remove client when this handler returns. defer func() { debug.Printf("ws[%s] unsubscribing...", conn.RemoteAddr()) c.conn.Close() w.mu.Lock() delete(w.pool, c) w.mu.Unlock() w.UpdateStatus() debug.Printf("ws[%s] unsubscribed", conn.RemoteAddr()) }() lines, done, err := tailFile(fOptions.LogPath) if err != nil { log.Println(err) return } defer close(done) ticker := time.NewTicker(KeepaliveInterval) defer ticker.Stop() for { var err error c.conn.SetWriteDeadline(time.Time{}) select { case <-ticker.C: debug.Printf("ws[%s] ping", conn.RemoteAddr()) c.conn.SetWriteDeadline(time.Now().Add(5 * time.Second)) err = c.conn.WriteJSON(struct { Ping bool }{true}) case line := <-lines: c.conn.SetWriteDeadline(time.Now().Add(5 * time.Second)) err = c.conn.WriteJSON(struct { LogLine string }{string(line)}) case v := <-c.out: c.conn.SetWriteDeadline(time.Now().Add(5 * time.Second)) err = c.conn.WriteJSON(v) case <-quit: // The read loop failed/disconnected. Abort. return } if err != nil { debug.Printf("ws[%s] write error: %v", conn.RemoteAddr(), err) return } } } // drainEvents reads from w.Events and blocks until the channel has been silent for at least 50 ms. func drainUntilSilence(w *fsnotify.Watcher, silenceDur time.Duration) { timer := time.NewTimer(silenceDur) defer timer.Stop() for { select { case <-w.Events: if !timer.Stop() { <-timer.C } timer.Reset(silenceDur) case <-timer.C: return } } } // Expects the file to never get renamed/truncated or deleted func tailFile(path string) (<-chan []byte, chan<- struct{}, error) { lines := make(chan []byte) done := make(chan struct{}) file, err := os.Open(path) if err != nil { return nil, nil, err } go func() { rd := bufio.NewReader(file) for { data, _, err := rd.ReadLine() if errors.Is(err, io.EOF) { time.Sleep(time.Millisecond * 100) continue } select { case <-done: file.Close() return case lines <- data: } } }() return lines, done, nil } func handleWSMessage(v map[string]json.RawMessage) { raw, ok := v["prompt_response"] if !ok { return } var resp PromptResponse json.Unmarshal(raw, &resp) promptHub.Respond(resp.ID, resp.Value, resp.Err) } func wsReadLoop(c *websocket.Conn) <-chan struct{} { quit := make(chan struct{}) go func() { for { v := map[string]json.RawMessage{} // We should at least get a ping response once per KeepaliveInterval. c.SetReadDeadline(time.Now().Add(KeepaliveInterval + 10*time.Second)) err := c.ReadJSON(&v) if err != nil { debug.Printf("ws[%s] read error: %v", c.RemoteAddr(), err) close(quit) return } if _, ok := v["Pong"]; ok { // That's the Ping response. debug.Printf("ws[%s] pong", c.RemoteAddr()) continue } go handleWSMessage(v) } }() return quit }