Compare commits
No commits in common. "master" and "php" have entirely different histories.
898 changed files with 66394 additions and 65000 deletions
|
|
@ -1,33 +1,7 @@
|
||||||
admin/admin
|
.git
|
||||||
checker/checker
|
TODO
|
||||||
dashboard/dashboard
|
db
|
||||||
evdist/evdist
|
docs/guide
|
||||||
generator/generator
|
front_synchro
|
||||||
receiver/receiver
|
misc
|
||||||
repochecker/repochecker
|
perl-mcrypt
|
||||||
frontend/fic/build
|
|
||||||
frontend/fic/node_modules
|
|
||||||
qa/ui/build
|
|
||||||
qa/ui/node_modules
|
|
||||||
fickit-backend-initrd.img
|
|
||||||
fickit-backend-kernel
|
|
||||||
fickit-backend-squashfs.img
|
|
||||||
fickit-backend-state
|
|
||||||
fickit-frontend-initrd.img
|
|
||||||
fickit-frontend-kernel
|
|
||||||
fickit-frontend-squashfs.img
|
|
||||||
fickit-frontend-state
|
|
||||||
fickit-prepare-initrd.img
|
|
||||||
fickit-prepare-kernel
|
|
||||||
fickit-update-initrd.img
|
|
||||||
fickit-update-kernel
|
|
||||||
DASHBOARD
|
|
||||||
FILES
|
|
||||||
PKI
|
|
||||||
REMOTE
|
|
||||||
repochecker/*.so
|
|
||||||
SETTINGS
|
|
||||||
SETTINGSDIST
|
|
||||||
submissions
|
|
||||||
TEAMS
|
|
||||||
vendor
|
|
||||||
|
|
|
||||||
|
|
@ -1,22 +0,0 @@
|
||||||
image: nemunaire/fic-admin:{{#if build.tag}}{{trimPrefix "v" build.tag}}{{else}}latest{{/if}}
|
|
||||||
{{#if build.tags}}
|
|
||||||
tags:
|
|
||||||
{{#each build.tags}}
|
|
||||||
- {{this}}
|
|
||||||
{{/each}}
|
|
||||||
{{/if}}
|
|
||||||
manifests:
|
|
||||||
- image: nemunaire/fic-admin:{{#if build.tag}}{{trimPrefix "v" build.tag}}-{{/if}}linux-amd64
|
|
||||||
platform:
|
|
||||||
architecture: amd64
|
|
||||||
os: linux
|
|
||||||
- image: nemunaire/fic-admin:{{#if build.tag}}{{trimPrefix "v" build.tag}}-{{/if}}linux-arm64
|
|
||||||
platform:
|
|
||||||
architecture: arm64
|
|
||||||
os: linux
|
|
||||||
variant: v8
|
|
||||||
- image: nemunaire/fic-admin:{{#if build.tag}}{{trimPrefix "v" build.tag}}-{{/if}}linux-arm
|
|
||||||
platform:
|
|
||||||
architecture: arm
|
|
||||||
os: linux
|
|
||||||
variant: v7
|
|
||||||
|
|
@ -1,22 +0,0 @@
|
||||||
image: nemunaire/fic-checker:{{#if build.tag}}{{trimPrefix "v" build.tag}}{{else}}latest{{/if}}
|
|
||||||
{{#if build.tags}}
|
|
||||||
tags:
|
|
||||||
{{#each build.tags}}
|
|
||||||
- {{this}}
|
|
||||||
{{/each}}
|
|
||||||
{{/if}}
|
|
||||||
manifests:
|
|
||||||
- image: nemunaire/fic-checker:{{#if build.tag}}{{trimPrefix "v" build.tag}}-{{/if}}linux-amd64
|
|
||||||
platform:
|
|
||||||
architecture: amd64
|
|
||||||
os: linux
|
|
||||||
- image: nemunaire/fic-checker:{{#if build.tag}}{{trimPrefix "v" build.tag}}-{{/if}}linux-arm64
|
|
||||||
platform:
|
|
||||||
architecture: arm64
|
|
||||||
os: linux
|
|
||||||
variant: v8
|
|
||||||
- image: nemunaire/fic-checker:{{#if build.tag}}{{trimPrefix "v" build.tag}}-{{/if}}linux-arm
|
|
||||||
platform:
|
|
||||||
architecture: arm
|
|
||||||
os: linux
|
|
||||||
variant: v7
|
|
||||||
|
|
@ -1,22 +0,0 @@
|
||||||
image: nemunaire/fic-dashboard:{{#if build.tag}}{{trimPrefix "v" build.tag}}{{else}}latest{{/if}}
|
|
||||||
{{#if build.tags}}
|
|
||||||
tags:
|
|
||||||
{{#each build.tags}}
|
|
||||||
- {{this}}
|
|
||||||
{{/each}}
|
|
||||||
{{/if}}
|
|
||||||
manifests:
|
|
||||||
- image: nemunaire/fic-dashboard:{{#if build.tag}}{{trimPrefix "v" build.tag}}-{{/if}}linux-amd64
|
|
||||||
platform:
|
|
||||||
architecture: amd64
|
|
||||||
os: linux
|
|
||||||
- image: nemunaire/fic-dashboard:{{#if build.tag}}{{trimPrefix "v" build.tag}}-{{/if}}linux-arm64
|
|
||||||
platform:
|
|
||||||
architecture: arm64
|
|
||||||
os: linux
|
|
||||||
variant: v8
|
|
||||||
- image: nemunaire/fic-dashboard:{{#if build.tag}}{{trimPrefix "v" build.tag}}-{{/if}}linux-arm
|
|
||||||
platform:
|
|
||||||
architecture: arm
|
|
||||||
os: linux
|
|
||||||
variant: v7
|
|
||||||
|
|
@ -1,22 +0,0 @@
|
||||||
image: nemunaire/fic-evdist:{{#if build.tag}}{{trimPrefix "v" build.tag}}{{else}}latest{{/if}}
|
|
||||||
{{#if build.tags}}
|
|
||||||
tags:
|
|
||||||
{{#each build.tags}}
|
|
||||||
- {{this}}
|
|
||||||
{{/each}}
|
|
||||||
{{/if}}
|
|
||||||
manifests:
|
|
||||||
- image: nemunaire/fic-evdist:{{#if build.tag}}{{trimPrefix "v" build.tag}}-{{/if}}linux-amd64
|
|
||||||
platform:
|
|
||||||
architecture: amd64
|
|
||||||
os: linux
|
|
||||||
- image: nemunaire/fic-evdist:{{#if build.tag}}{{trimPrefix "v" build.tag}}-{{/if}}linux-arm64
|
|
||||||
platform:
|
|
||||||
architecture: arm64
|
|
||||||
os: linux
|
|
||||||
variant: v8
|
|
||||||
- image: nemunaire/fic-evdist:{{#if build.tag}}{{trimPrefix "v" build.tag}}-{{/if}}linux-arm
|
|
||||||
platform:
|
|
||||||
architecture: arm
|
|
||||||
os: linux
|
|
||||||
variant: v7
|
|
||||||
|
|
@ -1,22 +0,0 @@
|
||||||
image: nemunaire/fic-frontend-ui:{{#if build.tag}}{{trimPrefix "v" build.tag}}{{else}}latest{{/if}}
|
|
||||||
{{#if build.tags}}
|
|
||||||
tags:
|
|
||||||
{{#each build.tags}}
|
|
||||||
- {{this}}
|
|
||||||
{{/each}}
|
|
||||||
{{/if}}
|
|
||||||
manifests:
|
|
||||||
- image: nemunaire/fic-frontend-ui:{{#if build.tag}}{{trimPrefix "v" build.tag}}-{{/if}}linux-amd64
|
|
||||||
platform:
|
|
||||||
architecture: amd64
|
|
||||||
os: linux
|
|
||||||
- image: nemunaire/fic-frontend-ui:{{#if build.tag}}{{trimPrefix "v" build.tag}}-{{/if}}linux-arm64
|
|
||||||
platform:
|
|
||||||
architecture: arm64
|
|
||||||
os: linux
|
|
||||||
variant: v8
|
|
||||||
- image: nemunaire/fic-frontend-ui:{{#if build.tag}}{{trimPrefix "v" build.tag}}-{{/if}}linux-arm
|
|
||||||
platform:
|
|
||||||
architecture: arm
|
|
||||||
os: linux
|
|
||||||
variant: v7
|
|
||||||
|
|
@ -1,22 +0,0 @@
|
||||||
image: nemunaire/fic-generator:{{#if build.tag}}{{trimPrefix "v" build.tag}}{{else}}latest{{/if}}
|
|
||||||
{{#if build.tags}}
|
|
||||||
tags:
|
|
||||||
{{#each build.tags}}
|
|
||||||
- {{this}}
|
|
||||||
{{/each}}
|
|
||||||
{{/if}}
|
|
||||||
manifests:
|
|
||||||
- image: nemunaire/fic-generator:{{#if build.tag}}{{trimPrefix "v" build.tag}}-{{/if}}linux-amd64
|
|
||||||
platform:
|
|
||||||
architecture: amd64
|
|
||||||
os: linux
|
|
||||||
- image: nemunaire/fic-generator:{{#if build.tag}}{{trimPrefix "v" build.tag}}-{{/if}}linux-arm64
|
|
||||||
platform:
|
|
||||||
architecture: arm64
|
|
||||||
os: linux
|
|
||||||
variant: v8
|
|
||||||
- image: nemunaire/fic-generator:{{#if build.tag}}{{trimPrefix "v" build.tag}}-{{/if}}linux-arm
|
|
||||||
platform:
|
|
||||||
architecture: arm
|
|
||||||
os: linux
|
|
||||||
variant: v7
|
|
||||||
|
|
@ -1,22 +0,0 @@
|
||||||
image: nemunaire/fic-get-remote-files:{{#if build.tag}}{{trimPrefix "v" build.tag}}{{else}}latest{{/if}}
|
|
||||||
{{#if build.tags}}
|
|
||||||
tags:
|
|
||||||
{{#each build.tags}}
|
|
||||||
- {{this}}
|
|
||||||
{{/each}}
|
|
||||||
{{/if}}
|
|
||||||
manifests:
|
|
||||||
- image: nemunaire/fic-get-remote-files:{{#if build.tag}}{{trimPrefix "v" build.tag}}-{{/if}}linux-amd64
|
|
||||||
platform:
|
|
||||||
architecture: amd64
|
|
||||||
os: linux
|
|
||||||
- image: nemunaire/fic-get-remote-files:{{#if build.tag}}{{trimPrefix "v" build.tag}}-{{/if}}linux-arm64
|
|
||||||
platform:
|
|
||||||
architecture: arm64
|
|
||||||
os: linux
|
|
||||||
variant: v8
|
|
||||||
- image: nemunaire/fic-get-remote-files:{{#if build.tag}}{{trimPrefix "v" build.tag}}-{{/if}}linux-arm
|
|
||||||
platform:
|
|
||||||
architecture: arm
|
|
||||||
os: linux
|
|
||||||
variant: v7
|
|
||||||
|
|
@ -1,22 +0,0 @@
|
||||||
image: nemunaire/fic-nginx:{{#if build.tag}}{{trimPrefix "v" build.tag}}{{else}}latest{{/if}}
|
|
||||||
{{#if build.tags}}
|
|
||||||
tags:
|
|
||||||
{{#each build.tags}}
|
|
||||||
- {{this}}
|
|
||||||
{{/each}}
|
|
||||||
{{/if}}
|
|
||||||
manifests:
|
|
||||||
- image: nemunaire/fic-nginx:{{#if build.tag}}{{trimPrefix "v" build.tag}}-{{/if}}linux-amd64
|
|
||||||
platform:
|
|
||||||
architecture: amd64
|
|
||||||
os: linux
|
|
||||||
- image: nemunaire/fic-nginx:{{#if build.tag}}{{trimPrefix "v" build.tag}}-{{/if}}linux-arm64
|
|
||||||
platform:
|
|
||||||
architecture: arm64
|
|
||||||
os: linux
|
|
||||||
variant: v8
|
|
||||||
- image: nemunaire/fic-nginx:{{#if build.tag}}{{trimPrefix "v" build.tag}}-{{/if}}linux-arm
|
|
||||||
platform:
|
|
||||||
architecture: arm
|
|
||||||
os: linux
|
|
||||||
variant: v7
|
|
||||||
|
|
@ -1,22 +0,0 @@
|
||||||
image: nemunaire/fic-qa:{{#if build.tag}}{{trimPrefix "v" build.tag}}{{else}}latest{{/if}}
|
|
||||||
{{#if build.tags}}
|
|
||||||
tags:
|
|
||||||
{{#each build.tags}}
|
|
||||||
- {{this}}
|
|
||||||
{{/each}}
|
|
||||||
{{/if}}
|
|
||||||
manifests:
|
|
||||||
- image: nemunaire/fic-qa:{{#if build.tag}}{{trimPrefix "v" build.tag}}-{{/if}}linux-amd64
|
|
||||||
platform:
|
|
||||||
architecture: amd64
|
|
||||||
os: linux
|
|
||||||
- image: nemunaire/fic-qa:{{#if build.tag}}{{trimPrefix "v" build.tag}}-{{/if}}linux-arm64
|
|
||||||
platform:
|
|
||||||
architecture: arm64
|
|
||||||
os: linux
|
|
||||||
variant: v8
|
|
||||||
- image: nemunaire/fic-qa:{{#if build.tag}}{{trimPrefix "v" build.tag}}-{{/if}}linux-arm
|
|
||||||
platform:
|
|
||||||
architecture: arm
|
|
||||||
os: linux
|
|
||||||
variant: v7
|
|
||||||
|
|
@ -1,22 +0,0 @@
|
||||||
image: nemunaire/fic-receiver:{{#if build.tag}}{{trimPrefix "v" build.tag}}{{else}}latest{{/if}}
|
|
||||||
{{#if build.tags}}
|
|
||||||
tags:
|
|
||||||
{{#each build.tags}}
|
|
||||||
- {{this}}
|
|
||||||
{{/each}}
|
|
||||||
{{/if}}
|
|
||||||
manifests:
|
|
||||||
- image: nemunaire/fic-receiver:{{#if build.tag}}{{trimPrefix "v" build.tag}}-{{/if}}linux-amd64
|
|
||||||
platform:
|
|
||||||
architecture: amd64
|
|
||||||
os: linux
|
|
||||||
- image: nemunaire/fic-receiver:{{#if build.tag}}{{trimPrefix "v" build.tag}}-{{/if}}linux-arm64
|
|
||||||
platform:
|
|
||||||
architecture: arm64
|
|
||||||
os: linux
|
|
||||||
variant: v8
|
|
||||||
- image: nemunaire/fic-receiver:{{#if build.tag}}{{trimPrefix "v" build.tag}}-{{/if}}linux-arm
|
|
||||||
platform:
|
|
||||||
architecture: arm
|
|
||||||
os: linux
|
|
||||||
variant: v7
|
|
||||||
|
|
@ -1,22 +0,0 @@
|
||||||
image: nemunaire/fic-repochecker:{{#if build.tag}}{{trimPrefix "v" build.tag}}{{else}}latest{{/if}}
|
|
||||||
{{#if build.tags}}
|
|
||||||
tags:
|
|
||||||
{{#each build.tags}}
|
|
||||||
- {{this}}
|
|
||||||
{{/each}}
|
|
||||||
{{/if}}
|
|
||||||
manifests:
|
|
||||||
- image: nemunaire/fic-repochecker:{{#if build.tag}}{{trimPrefix "v" build.tag}}-{{/if}}linux-amd64
|
|
||||||
platform:
|
|
||||||
architecture: amd64
|
|
||||||
os: linux
|
|
||||||
- image: nemunaire/fic-repochecker:{{#if build.tag}}{{trimPrefix "v" build.tag}}-{{/if}}linux-arm64
|
|
||||||
platform:
|
|
||||||
architecture: arm64
|
|
||||||
os: linux
|
|
||||||
variant: v8
|
|
||||||
- image: nemunaire/fic-repochecker:{{#if build.tag}}{{trimPrefix "v" build.tag}}-{{/if}}linux-arm
|
|
||||||
platform:
|
|
||||||
architecture: arm
|
|
||||||
os: linux
|
|
||||||
variant: v7
|
|
||||||
|
|
@ -1,22 +0,0 @@
|
||||||
image: nemunaire/fickit-deploy:{{#if build.tag}}{{trimPrefix "v" build.tag}}{{else}}latest{{/if}}
|
|
||||||
{{#if build.tags}}
|
|
||||||
tags:
|
|
||||||
{{#each build.tags}}
|
|
||||||
- {{this}}
|
|
||||||
{{/each}}
|
|
||||||
{{/if}}
|
|
||||||
manifests:
|
|
||||||
- image: nemunaire/fickit-deploy:{{#if build.tag}}{{trimPrefix "v" build.tag}}-{{/if}}linux-amd64
|
|
||||||
platform:
|
|
||||||
architecture: amd64
|
|
||||||
os: linux
|
|
||||||
- image: nemunaire/fickit-deploy:{{#if build.tag}}{{trimPrefix "v" build.tag}}-{{/if}}linux-arm64
|
|
||||||
platform:
|
|
||||||
architecture: arm64
|
|
||||||
os: linux
|
|
||||||
variant: v8
|
|
||||||
- image: nemunaire/fickit-deploy:{{#if build.tag}}{{trimPrefix "v" build.tag}}-{{/if}}linux-arm
|
|
||||||
platform:
|
|
||||||
architecture: arm
|
|
||||||
os: linux
|
|
||||||
variant: v7
|
|
||||||
816
.drone.yml
816
.drone.yml
|
|
@ -1,816 +0,0 @@
|
||||||
---
|
|
||||||
kind: pipeline
|
|
||||||
type: docker
|
|
||||||
name: build-amd64
|
|
||||||
|
|
||||||
platform:
|
|
||||||
os: linux
|
|
||||||
arch: amd64
|
|
||||||
|
|
||||||
workspace:
|
|
||||||
base: /go
|
|
||||||
path: src/srs.epita.fr/fic-server
|
|
||||||
|
|
||||||
steps:
|
|
||||||
- name: get deps
|
|
||||||
image: golang:alpine
|
|
||||||
commands:
|
|
||||||
- apk --no-cache add git
|
|
||||||
- go get -v -d ./...
|
|
||||||
- mkdir deploy
|
|
||||||
|
|
||||||
- name: build qa ui
|
|
||||||
image: node:23-alpine
|
|
||||||
commands:
|
|
||||||
- cd qa/ui
|
|
||||||
- npm install --network-timeout=100000
|
|
||||||
- npm run build
|
|
||||||
- tar chjf ../../deploy/htdocs-qa.tar.bz2 build
|
|
||||||
|
|
||||||
- name: vet and tests
|
|
||||||
image: golang:alpine
|
|
||||||
commands:
|
|
||||||
- apk --no-cache add build-base
|
|
||||||
- go vet -buildvcs=false -tags gitgo ./...
|
|
||||||
- go vet -buildvcs=false ./...
|
|
||||||
- go test ./...
|
|
||||||
|
|
||||||
- name: build admin
|
|
||||||
image: golang:alpine
|
|
||||||
commands:
|
|
||||||
- go build -buildvcs=false -tags gitgo -o deploy/admin-gitgo-${DRONE_STAGE_OS}-${DRONE_STAGE_ARCH} srs.epita.fr/fic-server/admin
|
|
||||||
- go build -buildvcs=false -o deploy/admin-${DRONE_STAGE_OS}-${DRONE_STAGE_ARCH} srs.epita.fr/fic-server/admin
|
|
||||||
- tar chjf deploy/htdocs-admin.tar.bz2 htdocs-admin
|
|
||||||
environment:
|
|
||||||
CGO_ENABLED: 0
|
|
||||||
when:
|
|
||||||
branch:
|
|
||||||
exclude:
|
|
||||||
- master
|
|
||||||
|
|
||||||
- name: build checker
|
|
||||||
image: golang:alpine
|
|
||||||
commands:
|
|
||||||
- go build -buildvcs=false -o deploy/checker-${DRONE_STAGE_OS}-${DRONE_STAGE_ARCH} srs.epita.fr/fic-server/checker
|
|
||||||
environment:
|
|
||||||
CGO_ENABLED: 0
|
|
||||||
when:
|
|
||||||
branch:
|
|
||||||
exclude:
|
|
||||||
- master
|
|
||||||
|
|
||||||
- name: build evdist
|
|
||||||
image: golang:alpine
|
|
||||||
commands:
|
|
||||||
- go build -buildvcs=false -o deploy/evdist-${DRONE_STAGE_OS}-${DRONE_STAGE_ARCH} srs.epita.fr/fic-server/evdist
|
|
||||||
environment:
|
|
||||||
CGO_ENABLED: 0
|
|
||||||
when:
|
|
||||||
branch:
|
|
||||||
exclude:
|
|
||||||
- master
|
|
||||||
|
|
||||||
- name: build generator
|
|
||||||
image: golang:alpine
|
|
||||||
commands:
|
|
||||||
- go build -buildvcs=false -o deploy/generator-${DRONE_STAGE_OS}-${DRONE_STAGE_ARCH} srs.epita.fr/fic-server/generator
|
|
||||||
environment:
|
|
||||||
CGO_ENABLED: 0
|
|
||||||
when:
|
|
||||||
branch:
|
|
||||||
exclude:
|
|
||||||
- master
|
|
||||||
|
|
||||||
- name: build receiver
|
|
||||||
image: golang:alpine
|
|
||||||
commands:
|
|
||||||
- go build -buildvcs=false -o deploy/receiver-${DRONE_STAGE_OS}-${DRONE_STAGE_ARCH} srs.epita.fr/fic-server/receiver
|
|
||||||
environment:
|
|
||||||
CGO_ENABLED: 0
|
|
||||||
when:
|
|
||||||
branch:
|
|
||||||
exclude:
|
|
||||||
- master
|
|
||||||
|
|
||||||
- name: build frontend fic ui
|
|
||||||
image: node:23-alpine
|
|
||||||
commands:
|
|
||||||
- cd frontend/fic
|
|
||||||
- npm install --network-timeout=100000
|
|
||||||
- npm run build
|
|
||||||
- tar chjf ../../deploy/htdocs-frontend-fic.tar.bz2 build
|
|
||||||
when:
|
|
||||||
branch:
|
|
||||||
exclude:
|
|
||||||
- master
|
|
||||||
|
|
||||||
- name: build dashboard
|
|
||||||
image: golang:alpine
|
|
||||||
commands:
|
|
||||||
- go build -buildvcs=false -o deploy/dashboard-${DRONE_STAGE_OS}-${DRONE_STAGE_ARCH} srs.epita.fr/fic-server/dashboard
|
|
||||||
- tar chjf deploy/htdocs-dashboard.tar.bz2 htdocs-dashboard
|
|
||||||
environment:
|
|
||||||
CGO_ENABLED: 0
|
|
||||||
when:
|
|
||||||
branch:
|
|
||||||
exclude:
|
|
||||||
- master
|
|
||||||
|
|
||||||
- name: build repochecker
|
|
||||||
image: golang:alpine
|
|
||||||
commands:
|
|
||||||
- apk --no-cache add build-base
|
|
||||||
- go build -buildvcs=false --tags checkupdate -o deploy/repochecker-${DRONE_STAGE_OS}-${DRONE_STAGE_ARCH} srs.epita.fr/fic-server/repochecker
|
|
||||||
- go build -buildvcs=false -buildmode=plugin -o deploy/repochecker-epita-rules-${DRONE_STAGE_OS}-${DRONE_STAGE_ARCH}.so srs.epita.fr/fic-server/repochecker/epita
|
|
||||||
- go build -buildvcs=false -buildmode=plugin -o deploy/repochecker-file-inspector-rules-${DRONE_STAGE_OS}-${DRONE_STAGE_ARCH}.so srs.epita.fr/fic-server/repochecker/file-inspector
|
|
||||||
- go build -buildvcs=false -buildmode=plugin -o deploy/repochecker-grammalecte-rules-${DRONE_STAGE_OS}-${DRONE_STAGE_ARCH}.so srs.epita.fr/fic-server/repochecker/grammalecte
|
|
||||||
- go build -buildvcs=false -buildmode=plugin -o deploy/repochecker-pcap-inspector-rules-${DRONE_STAGE_OS}-${DRONE_STAGE_ARCH}.so srs.epita.fr/fic-server/repochecker/pcap-inspector
|
|
||||||
- go build -buildvcs=false -buildmode=plugin -o deploy/repochecker-videos-rules-${DRONE_STAGE_OS}-${DRONE_STAGE_ARCH}.so srs.epita.fr/fic-server/repochecker/videos
|
|
||||||
- grep "const version" repochecker/update.go | sed -r 's/^.*=\s*(\S.*)$/\1/' > deploy/repochecker.version
|
|
||||||
when:
|
|
||||||
branch:
|
|
||||||
exclude:
|
|
||||||
- master
|
|
||||||
|
|
||||||
- name: build qa
|
|
||||||
image: golang:alpine
|
|
||||||
commands:
|
|
||||||
- go build -buildvcs=false -o deploy/qa-${DRONE_STAGE_OS}-${DRONE_STAGE_ARCH} srs.epita.fr/fic-server/qa
|
|
||||||
environment:
|
|
||||||
CGO_ENABLED: 0
|
|
||||||
when:
|
|
||||||
branch:
|
|
||||||
exclude:
|
|
||||||
- master
|
|
||||||
|
|
||||||
- name: docker admin
|
|
||||||
image: plugins/docker
|
|
||||||
settings:
|
|
||||||
username:
|
|
||||||
from_secret: docker_username
|
|
||||||
password:
|
|
||||||
from_secret: docker_password
|
|
||||||
repo: nemunaire/fic-admin
|
|
||||||
auto_tag: true
|
|
||||||
auto_tag_suffix: ${DRONE_STAGE_OS}-${DRONE_STAGE_ARCH}
|
|
||||||
dockerfile: Dockerfile-admin
|
|
||||||
when:
|
|
||||||
branch:
|
|
||||||
- master
|
|
||||||
|
|
||||||
- name: docker checker
|
|
||||||
image: plugins/docker
|
|
||||||
settings:
|
|
||||||
username:
|
|
||||||
from_secret: docker_username
|
|
||||||
password:
|
|
||||||
from_secret: docker_password
|
|
||||||
repo: nemunaire/fic-checker
|
|
||||||
auto_tag: true
|
|
||||||
auto_tag_suffix: ${DRONE_STAGE_OS}-${DRONE_STAGE_ARCH}
|
|
||||||
dockerfile: Dockerfile-checker
|
|
||||||
when:
|
|
||||||
branch:
|
|
||||||
- master
|
|
||||||
|
|
||||||
- name: docker evdist
|
|
||||||
image: plugins/docker
|
|
||||||
settings:
|
|
||||||
username:
|
|
||||||
from_secret: docker_username
|
|
||||||
password:
|
|
||||||
from_secret: docker_password
|
|
||||||
repo: nemunaire/fic-evdist
|
|
||||||
auto_tag: true
|
|
||||||
auto_tag_suffix: ${DRONE_STAGE_OS}-${DRONE_STAGE_ARCH}
|
|
||||||
dockerfile: Dockerfile-evdist
|
|
||||||
when:
|
|
||||||
branch:
|
|
||||||
- master
|
|
||||||
|
|
||||||
- name: docker generator
|
|
||||||
image: plugins/docker
|
|
||||||
settings:
|
|
||||||
username:
|
|
||||||
from_secret: docker_username
|
|
||||||
password:
|
|
||||||
from_secret: docker_password
|
|
||||||
repo: nemunaire/fic-generator
|
|
||||||
auto_tag: true
|
|
||||||
auto_tag_suffix: ${DRONE_STAGE_OS}-${DRONE_STAGE_ARCH}
|
|
||||||
dockerfile: Dockerfile-generator
|
|
||||||
when:
|
|
||||||
branch:
|
|
||||||
- master
|
|
||||||
|
|
||||||
- name: docker receiver
|
|
||||||
image: plugins/docker
|
|
||||||
settings:
|
|
||||||
username:
|
|
||||||
from_secret: docker_username
|
|
||||||
password:
|
|
||||||
from_secret: docker_password
|
|
||||||
repo: nemunaire/fic-receiver
|
|
||||||
auto_tag: true
|
|
||||||
auto_tag_suffix: ${DRONE_STAGE_OS}-${DRONE_STAGE_ARCH}
|
|
||||||
dockerfile: Dockerfile-receiver
|
|
||||||
when:
|
|
||||||
branch:
|
|
||||||
- master
|
|
||||||
|
|
||||||
- name: docker frontend nginx
|
|
||||||
image: plugins/docker
|
|
||||||
settings:
|
|
||||||
username:
|
|
||||||
from_secret: docker_username
|
|
||||||
password:
|
|
||||||
from_secret: docker_password
|
|
||||||
repo: nemunaire/fic-nginx
|
|
||||||
auto_tag: true
|
|
||||||
auto_tag_suffix: ${DRONE_STAGE_OS}-${DRONE_STAGE_ARCH}
|
|
||||||
dockerfile: Dockerfile-nginx
|
|
||||||
when:
|
|
||||||
branch:
|
|
||||||
- master
|
|
||||||
|
|
||||||
- name: docker frontend ui
|
|
||||||
image: plugins/docker
|
|
||||||
settings:
|
|
||||||
username:
|
|
||||||
from_secret: docker_username
|
|
||||||
password:
|
|
||||||
from_secret: docker_password
|
|
||||||
repo: nemunaire/fic-frontend-ui
|
|
||||||
auto_tag: true
|
|
||||||
auto_tag_suffix: ${DRONE_STAGE_OS}-${DRONE_STAGE_ARCH}
|
|
||||||
dockerfile: Dockerfile-frontend-ui
|
|
||||||
when:
|
|
||||||
branch:
|
|
||||||
- master
|
|
||||||
|
|
||||||
- name: docker dashboard
|
|
||||||
image: plugins/docker
|
|
||||||
settings:
|
|
||||||
username:
|
|
||||||
from_secret: docker_username
|
|
||||||
password:
|
|
||||||
from_secret: docker_password
|
|
||||||
repo: nemunaire/fic-dashboard
|
|
||||||
auto_tag: true
|
|
||||||
auto_tag_suffix: ${DRONE_STAGE_OS}-${DRONE_STAGE_ARCH}
|
|
||||||
dockerfile: Dockerfile-dashboard
|
|
||||||
when:
|
|
||||||
branch:
|
|
||||||
- master
|
|
||||||
|
|
||||||
- name: docker qa
|
|
||||||
image: plugins/docker
|
|
||||||
settings:
|
|
||||||
username:
|
|
||||||
from_secret: docker_username
|
|
||||||
password:
|
|
||||||
from_secret: docker_password
|
|
||||||
repo: nemunaire/fic-qa
|
|
||||||
auto_tag: true
|
|
||||||
auto_tag_suffix: ${DRONE_STAGE_OS}-${DRONE_STAGE_ARCH}
|
|
||||||
dockerfile: Dockerfile-qa
|
|
||||||
when:
|
|
||||||
branch:
|
|
||||||
- master
|
|
||||||
|
|
||||||
- name: docker repochecker
|
|
||||||
image: plugins/docker
|
|
||||||
settings:
|
|
||||||
username:
|
|
||||||
from_secret: docker_username
|
|
||||||
password:
|
|
||||||
from_secret: docker_password
|
|
||||||
repo: nemunaire/fic-repochecker
|
|
||||||
auto_tag: true
|
|
||||||
auto_tag_suffix: ${DRONE_STAGE_OS}-${DRONE_STAGE_ARCH}
|
|
||||||
dockerfile: Dockerfile-repochecker
|
|
||||||
when:
|
|
||||||
branch:
|
|
||||||
- master
|
|
||||||
|
|
||||||
- name: docker remote-scores-sync-zqds
|
|
||||||
image: plugins/docker
|
|
||||||
settings:
|
|
||||||
username:
|
|
||||||
from_secret: docker_username
|
|
||||||
password:
|
|
||||||
from_secret: docker_password
|
|
||||||
repo: nemunaire/fic-remote-scores-sync-zqds
|
|
||||||
auto_tag: true
|
|
||||||
auto_tag_suffix: ${DRONE_STAGE_OS}-${DRONE_STAGE_ARCH}
|
|
||||||
dockerfile: Dockerfile-remote-scores-sync-zqds
|
|
||||||
when:
|
|
||||||
branch:
|
|
||||||
- master
|
|
||||||
|
|
||||||
- name: docker remote-challenge-sync-airbus
|
|
||||||
image: plugins/docker
|
|
||||||
settings:
|
|
||||||
username:
|
|
||||||
from_secret: docker_username
|
|
||||||
password:
|
|
||||||
from_secret: docker_password
|
|
||||||
repo: nemunaire/fic-remote-challenge-sync-airbus
|
|
||||||
auto_tag: true
|
|
||||||
auto_tag_suffix: ${DRONE_STAGE_OS}-${DRONE_STAGE_ARCH}
|
|
||||||
dockerfile: Dockerfile-remote-challenge-sync-airbus
|
|
||||||
when:
|
|
||||||
branch:
|
|
||||||
- master
|
|
||||||
|
|
||||||
- name: docker fic-get-remote-files
|
|
||||||
failure: ignore
|
|
||||||
image: plugins/docker
|
|
||||||
settings:
|
|
||||||
username:
|
|
||||||
from_secret: docker_username
|
|
||||||
password:
|
|
||||||
from_secret: docker_password
|
|
||||||
repo: nemunaire/fic-get-remote-files
|
|
||||||
auto_tag: true
|
|
||||||
auto_tag_suffix: ${DRONE_STAGE_OS}-${DRONE_STAGE_ARCH}
|
|
||||||
dockerfile: Dockerfile-get-remote-files
|
|
||||||
when:
|
|
||||||
branch:
|
|
||||||
- master
|
|
||||||
|
|
||||||
- name: docker fickit-deploy
|
|
||||||
image: plugins/docker
|
|
||||||
settings:
|
|
||||||
username:
|
|
||||||
from_secret: docker_username
|
|
||||||
password:
|
|
||||||
from_secret: docker_password
|
|
||||||
repo: nemunaire/fickit-deploy
|
|
||||||
auto_tag: true
|
|
||||||
auto_tag_suffix: ${DRONE_STAGE_OS}-${DRONE_STAGE_ARCH}
|
|
||||||
dockerfile: Dockerfile-deploy
|
|
||||||
when:
|
|
||||||
branch:
|
|
||||||
- master
|
|
||||||
|
|
||||||
trigger:
|
|
||||||
event:
|
|
||||||
- cron
|
|
||||||
- push
|
|
||||||
- tag
|
|
||||||
|
|
||||||
---
|
|
||||||
kind: pipeline
|
|
||||||
type: docker
|
|
||||||
name: build-arm64
|
|
||||||
|
|
||||||
platform:
|
|
||||||
os: linux
|
|
||||||
arch: arm64
|
|
||||||
|
|
||||||
workspace:
|
|
||||||
base: /go
|
|
||||||
path: src/srs.epita.fr/fic-server
|
|
||||||
|
|
||||||
steps:
|
|
||||||
- name: get deps
|
|
||||||
image: golang:alpine
|
|
||||||
commands:
|
|
||||||
- apk --no-cache add git
|
|
||||||
- go get -d ./...
|
|
||||||
- mkdir deploy
|
|
||||||
|
|
||||||
- name: build admin
|
|
||||||
image: golang:alpine
|
|
||||||
commands:
|
|
||||||
- apk --no-cache add build-base
|
|
||||||
- go build -buildvcs=false -o deploy/admin-${DRONE_STAGE_OS}-${DRONE_STAGE_ARCH} srs.epita.fr/fic-server/admin
|
|
||||||
environment:
|
|
||||||
CGO_ENABLED: 0
|
|
||||||
when:
|
|
||||||
branch:
|
|
||||||
exclude:
|
|
||||||
- master
|
|
||||||
|
|
||||||
- name: build checker
|
|
||||||
image: golang:alpine
|
|
||||||
commands:
|
|
||||||
- go build -buildvcs=false -o deploy/checker-${DRONE_STAGE_OS}-${DRONE_STAGE_ARCH} srs.epita.fr/fic-server/checker
|
|
||||||
environment:
|
|
||||||
CGO_ENABLED: 0
|
|
||||||
when:
|
|
||||||
branch:
|
|
||||||
exclude:
|
|
||||||
- master
|
|
||||||
|
|
||||||
- name: build evdist
|
|
||||||
image: golang:alpine
|
|
||||||
commands:
|
|
||||||
- go build -buildvcs=false -o deploy/evdist-${DRONE_STAGE_OS}-${DRONE_STAGE_ARCH} srs.epita.fr/fic-server/evdist
|
|
||||||
environment:
|
|
||||||
CGO_ENABLED: 0
|
|
||||||
when:
|
|
||||||
branch:
|
|
||||||
exclude:
|
|
||||||
- master
|
|
||||||
|
|
||||||
- name: build generator
|
|
||||||
image: golang:alpine
|
|
||||||
commands:
|
|
||||||
- go build -buildvcs=false -o deploy/generator-${DRONE_STAGE_OS}-${DRONE_STAGE_ARCH} srs.epita.fr/fic-server/generator
|
|
||||||
environment:
|
|
||||||
CGO_ENABLED: 0
|
|
||||||
when:
|
|
||||||
branch:
|
|
||||||
exclude:
|
|
||||||
- master
|
|
||||||
|
|
||||||
- name: build receiver
|
|
||||||
image: golang:alpine
|
|
||||||
commands:
|
|
||||||
- go build -buildvcs=false -o deploy/receiver-${DRONE_STAGE_OS}-${DRONE_STAGE_ARCH} srs.epita.fr/fic-server/receiver
|
|
||||||
environment:
|
|
||||||
CGO_ENABLED: 0
|
|
||||||
when:
|
|
||||||
branch:
|
|
||||||
exclude:
|
|
||||||
- master
|
|
||||||
|
|
||||||
- name: build frontend fic ui
|
|
||||||
image: node:23-alpine
|
|
||||||
commands:
|
|
||||||
- cd frontend/fic
|
|
||||||
- npm install --network-timeout=100000
|
|
||||||
- npm run build
|
|
||||||
when:
|
|
||||||
branch:
|
|
||||||
exclude:
|
|
||||||
- master
|
|
||||||
|
|
||||||
- name: build dashboard
|
|
||||||
image: golang:alpine
|
|
||||||
commands:
|
|
||||||
- go build -buildvcs=false -o deploy/dashboard-${DRONE_STAGE_OS}-${DRONE_STAGE_ARCH} srs.epita.fr/fic-server/dashboard
|
|
||||||
environment:
|
|
||||||
CGO_ENABLED: 0
|
|
||||||
when:
|
|
||||||
branch:
|
|
||||||
exclude:
|
|
||||||
- master
|
|
||||||
|
|
||||||
- name: build repochecker
|
|
||||||
image: golang:alpine
|
|
||||||
commands:
|
|
||||||
- apk --no-cache add build-base
|
|
||||||
- go build -buildvcs=false --tags checkupdate -o deploy/repochecker-${DRONE_STAGE_OS}-${DRONE_STAGE_ARCH} srs.epita.fr/fic-server/repochecker
|
|
||||||
environment:
|
|
||||||
CGO_ENABLED: 0
|
|
||||||
when:
|
|
||||||
branch:
|
|
||||||
exclude:
|
|
||||||
- master
|
|
||||||
|
|
||||||
- name: build repochecker for macOS
|
|
||||||
image: golang:alpine
|
|
||||||
commands:
|
|
||||||
- apk --no-cache add build-base
|
|
||||||
- go build -buildvcs=false --tags checkupdate -o deploy/repochecker-darwin-${DRONE_STAGE_ARCH} srs.epita.fr/fic-server/repochecker
|
|
||||||
environment:
|
|
||||||
CGO_ENABLED: 0
|
|
||||||
GOOS: darwin
|
|
||||||
GOARCH: arm64
|
|
||||||
when:
|
|
||||||
branch:
|
|
||||||
exclude:
|
|
||||||
- master
|
|
||||||
|
|
||||||
- name: build qa ui
|
|
||||||
image: node:23-alpine
|
|
||||||
commands:
|
|
||||||
- cd qa/ui
|
|
||||||
- npm install --network-timeout=100000
|
|
||||||
- npm run build
|
|
||||||
- tar chjf ../../deploy/htdocs-qa.tar.bz2 build
|
|
||||||
when:
|
|
||||||
branch:
|
|
||||||
exclude:
|
|
||||||
- master
|
|
||||||
|
|
||||||
- name: build qa
|
|
||||||
image: golang:alpine
|
|
||||||
commands:
|
|
||||||
- go build -buildvcs=false -o deploy/qa-${DRONE_STAGE_OS}-${DRONE_STAGE_ARCH} srs.epita.fr/fic-server/qa
|
|
||||||
environment:
|
|
||||||
CGO_ENABLED: 0
|
|
||||||
when:
|
|
||||||
branch:
|
|
||||||
exclude:
|
|
||||||
- master
|
|
||||||
|
|
||||||
- name: docker admin
|
|
||||||
image: plugins/docker
|
|
||||||
settings:
|
|
||||||
username:
|
|
||||||
from_secret: docker_username
|
|
||||||
password:
|
|
||||||
from_secret: docker_password
|
|
||||||
repo: nemunaire/fic-admin
|
|
||||||
auto_tag: true
|
|
||||||
auto_tag_suffix: ${DRONE_STAGE_OS}-${DRONE_STAGE_ARCH}
|
|
||||||
dockerfile: Dockerfile-admin
|
|
||||||
when:
|
|
||||||
branch:
|
|
||||||
- master
|
|
||||||
|
|
||||||
- name: docker checker
|
|
||||||
image: plugins/docker
|
|
||||||
settings:
|
|
||||||
username:
|
|
||||||
from_secret: docker_username
|
|
||||||
password:
|
|
||||||
from_secret: docker_password
|
|
||||||
repo: nemunaire/fic-checker
|
|
||||||
auto_tag: true
|
|
||||||
auto_tag_suffix: ${DRONE_STAGE_OS}-${DRONE_STAGE_ARCH}
|
|
||||||
dockerfile: Dockerfile-checker
|
|
||||||
when:
|
|
||||||
branch:
|
|
||||||
- master
|
|
||||||
|
|
||||||
- name: docker evdist
|
|
||||||
image: plugins/docker
|
|
||||||
settings:
|
|
||||||
username:
|
|
||||||
from_secret: docker_username
|
|
||||||
password:
|
|
||||||
from_secret: docker_password
|
|
||||||
repo: nemunaire/fic-evdist
|
|
||||||
auto_tag: true
|
|
||||||
auto_tag_suffix: ${DRONE_STAGE_OS}-${DRONE_STAGE_ARCH}
|
|
||||||
dockerfile: Dockerfile-evdist
|
|
||||||
when:
|
|
||||||
branch:
|
|
||||||
- master
|
|
||||||
|
|
||||||
- name: docker generator
|
|
||||||
image: plugins/docker
|
|
||||||
settings:
|
|
||||||
username:
|
|
||||||
from_secret: docker_username
|
|
||||||
password:
|
|
||||||
from_secret: docker_password
|
|
||||||
repo: nemunaire/fic-generator
|
|
||||||
auto_tag: true
|
|
||||||
auto_tag_suffix: ${DRONE_STAGE_OS}-${DRONE_STAGE_ARCH}
|
|
||||||
dockerfile: Dockerfile-generator
|
|
||||||
when:
|
|
||||||
branch:
|
|
||||||
- master
|
|
||||||
|
|
||||||
- name: docker fic-get-remote-files
|
|
||||||
failure: ignore
|
|
||||||
image: plugins/docker
|
|
||||||
settings:
|
|
||||||
username:
|
|
||||||
from_secret: docker_username
|
|
||||||
password:
|
|
||||||
from_secret: docker_password
|
|
||||||
repo: nemunaire/fic-get-remote-files
|
|
||||||
auto_tag: true
|
|
||||||
auto_tag_suffix: ${DRONE_STAGE_OS}-${DRONE_STAGE_ARCH}
|
|
||||||
dockerfile: Dockerfile-get-remote-files
|
|
||||||
when:
|
|
||||||
branch:
|
|
||||||
- master
|
|
||||||
|
|
||||||
- name: docker receiver
|
|
||||||
image: plugins/docker
|
|
||||||
settings:
|
|
||||||
username:
|
|
||||||
from_secret: docker_username
|
|
||||||
password:
|
|
||||||
from_secret: docker_password
|
|
||||||
repo: nemunaire/fic-receiver
|
|
||||||
auto_tag: true
|
|
||||||
auto_tag_suffix: ${DRONE_STAGE_OS}-${DRONE_STAGE_ARCH}
|
|
||||||
dockerfile: Dockerfile-receiver
|
|
||||||
when:
|
|
||||||
branch:
|
|
||||||
- master
|
|
||||||
|
|
||||||
- name: docker frontend nginx
|
|
||||||
image: plugins/docker
|
|
||||||
settings:
|
|
||||||
username:
|
|
||||||
from_secret: docker_username
|
|
||||||
password:
|
|
||||||
from_secret: docker_password
|
|
||||||
repo: nemunaire/fic-nginx
|
|
||||||
auto_tag: true
|
|
||||||
auto_tag_suffix: ${DRONE_STAGE_OS}-${DRONE_STAGE_ARCH}
|
|
||||||
dockerfile: Dockerfile-nginx
|
|
||||||
when:
|
|
||||||
branch:
|
|
||||||
- master
|
|
||||||
|
|
||||||
- name: docker dashboard
|
|
||||||
image: plugins/docker
|
|
||||||
settings:
|
|
||||||
username:
|
|
||||||
from_secret: docker_username
|
|
||||||
password:
|
|
||||||
from_secret: docker_password
|
|
||||||
repo: nemunaire/fic-dashboard
|
|
||||||
auto_tag: true
|
|
||||||
auto_tag_suffix: ${DRONE_STAGE_OS}-${DRONE_STAGE_ARCH}
|
|
||||||
dockerfile: Dockerfile-dashboard
|
|
||||||
when:
|
|
||||||
branch:
|
|
||||||
- master
|
|
||||||
|
|
||||||
- name: docker qa
|
|
||||||
image: plugins/docker
|
|
||||||
settings:
|
|
||||||
username:
|
|
||||||
from_secret: docker_username
|
|
||||||
password:
|
|
||||||
from_secret: docker_password
|
|
||||||
repo: nemunaire/fic-qa
|
|
||||||
auto_tag: true
|
|
||||||
auto_tag_suffix: ${DRONE_STAGE_OS}-${DRONE_STAGE_ARCH}
|
|
||||||
dockerfile: Dockerfile-qa
|
|
||||||
when:
|
|
||||||
branch:
|
|
||||||
- master
|
|
||||||
|
|
||||||
- name: docker repochecker
|
|
||||||
image: plugins/docker
|
|
||||||
settings:
|
|
||||||
username:
|
|
||||||
from_secret: docker_username
|
|
||||||
password:
|
|
||||||
from_secret: docker_password
|
|
||||||
repo: nemunaire/fic-repochecker
|
|
||||||
auto_tag: true
|
|
||||||
auto_tag_suffix: ${DRONE_STAGE_OS}-${DRONE_STAGE_ARCH}
|
|
||||||
dockerfile: Dockerfile-repochecker
|
|
||||||
when:
|
|
||||||
branch:
|
|
||||||
- master
|
|
||||||
|
|
||||||
trigger:
|
|
||||||
event:
|
|
||||||
- cron
|
|
||||||
- push
|
|
||||||
- tag
|
|
||||||
|
|
||||||
---
|
|
||||||
kind: pipeline
|
|
||||||
name: docker-manifest
|
|
||||||
steps:
|
|
||||||
- name: publish admin
|
|
||||||
image: plugins/manifest
|
|
||||||
settings:
|
|
||||||
auto_tag: true
|
|
||||||
ignore_missing: true
|
|
||||||
spec: .drone-manifest-fic-admin.yml
|
|
||||||
username:
|
|
||||||
from_secret: docker_username
|
|
||||||
password:
|
|
||||||
from_secret: docker_password
|
|
||||||
|
|
||||||
- name: publish checker
|
|
||||||
image: plugins/manifest
|
|
||||||
settings:
|
|
||||||
auto_tag: true
|
|
||||||
ignore_missing: true
|
|
||||||
spec: .drone-manifest-fic-checker.yml
|
|
||||||
username:
|
|
||||||
from_secret: docker_username
|
|
||||||
password:
|
|
||||||
from_secret: docker_password
|
|
||||||
|
|
||||||
- name: publish evdist
|
|
||||||
image: plugins/manifest
|
|
||||||
settings:
|
|
||||||
auto_tag: true
|
|
||||||
ignore_missing: true
|
|
||||||
spec: .drone-manifest-fic-evdist.yml
|
|
||||||
username:
|
|
||||||
from_secret: docker_username
|
|
||||||
password:
|
|
||||||
from_secret: docker_password
|
|
||||||
|
|
||||||
- name: publish generator
|
|
||||||
image: plugins/manifest
|
|
||||||
settings:
|
|
||||||
auto_tag: true
|
|
||||||
ignore_missing: true
|
|
||||||
spec: .drone-manifest-fic-generator.yml
|
|
||||||
username:
|
|
||||||
from_secret: docker_username
|
|
||||||
password:
|
|
||||||
from_secret: docker_password
|
|
||||||
|
|
||||||
- name: publish receiver
|
|
||||||
image: plugins/manifest
|
|
||||||
settings:
|
|
||||||
auto_tag: true
|
|
||||||
ignore_missing: true
|
|
||||||
spec: .drone-manifest-fic-receiver.yml
|
|
||||||
username:
|
|
||||||
from_secret: docker_username
|
|
||||||
password:
|
|
||||||
from_secret: docker_password
|
|
||||||
|
|
||||||
- name: publish frontend nginx
|
|
||||||
image: plugins/manifest
|
|
||||||
settings:
|
|
||||||
auto_tag: true
|
|
||||||
ignore_missing: true
|
|
||||||
spec: .drone-manifest-fic-nginx.yml
|
|
||||||
username:
|
|
||||||
from_secret: docker_username
|
|
||||||
password:
|
|
||||||
from_secret: docker_password
|
|
||||||
|
|
||||||
- name: publish frontend ui
|
|
||||||
image: plugins/manifest
|
|
||||||
settings:
|
|
||||||
auto_tag: true
|
|
||||||
ignore_missing: true
|
|
||||||
spec: .drone-manifest-fic-frontend-ui.yml
|
|
||||||
username:
|
|
||||||
from_secret: docker_username
|
|
||||||
password:
|
|
||||||
from_secret: docker_password
|
|
||||||
|
|
||||||
- name: publish dashboard
|
|
||||||
image: plugins/manifest
|
|
||||||
settings:
|
|
||||||
auto_tag: true
|
|
||||||
ignore_missing: true
|
|
||||||
spec: .drone-manifest-fic-dashboard.yml
|
|
||||||
username:
|
|
||||||
from_secret: docker_username
|
|
||||||
password:
|
|
||||||
from_secret: docker_password
|
|
||||||
|
|
||||||
- name: publish repochecker
|
|
||||||
image: plugins/manifest
|
|
||||||
settings:
|
|
||||||
auto_tag: true
|
|
||||||
ignore_missing: true
|
|
||||||
spec: .drone-manifest-fic-repochecker.yml
|
|
||||||
username:
|
|
||||||
from_secret: docker_username
|
|
||||||
password:
|
|
||||||
from_secret: docker_password
|
|
||||||
|
|
||||||
- name: publish qa
|
|
||||||
image: plugins/manifest
|
|
||||||
settings:
|
|
||||||
auto_tag: true
|
|
||||||
ignore_missing: true
|
|
||||||
spec: .drone-manifest-fic-qa.yml
|
|
||||||
username:
|
|
||||||
from_secret: docker_username
|
|
||||||
password:
|
|
||||||
from_secret: docker_password
|
|
||||||
|
|
||||||
- name: docker fic-get-remote-files
|
|
||||||
failure: ignore
|
|
||||||
image: plugins/docker
|
|
||||||
settings:
|
|
||||||
username:
|
|
||||||
from_secret: docker_username
|
|
||||||
password:
|
|
||||||
from_secret: docker_password
|
|
||||||
repo: nemunaire/fic-get-remote-files
|
|
||||||
auto_tag: true
|
|
||||||
auto_tag_suffix: ${DRONE_STAGE_OS}-${DRONE_STAGE_ARCH}
|
|
||||||
dockerfile: Dockerfile-get-remote-files
|
|
||||||
when:
|
|
||||||
branch:
|
|
||||||
- master
|
|
||||||
|
|
||||||
- name: publish fickit-deploy
|
|
||||||
image: plugins/manifest
|
|
||||||
settings:
|
|
||||||
auto_tag: true
|
|
||||||
ignore_missing: true
|
|
||||||
spec: .drone-manifest-fickit-deploy.yml
|
|
||||||
username:
|
|
||||||
from_secret: docker_username
|
|
||||||
password:
|
|
||||||
from_secret: docker_password
|
|
||||||
|
|
||||||
trigger:
|
|
||||||
event:
|
|
||||||
- push
|
|
||||||
- tag
|
|
||||||
|
|
||||||
depends_on:
|
|
||||||
- build-amd64
|
|
||||||
- build-arm64
|
|
||||||
55
.gitignore
vendored
55
.gitignore
vendored
|
|
@ -1,43 +1,12 @@
|
||||||
vendor/
|
*.swp
|
||||||
DASHBOARD/
|
*~
|
||||||
FILES/
|
*#
|
||||||
PKI/
|
.#*
|
||||||
REMOTE/
|
onyx/cache/*.cache.php
|
||||||
SETTINGS/
|
onyx/cache/templates/*/*.php
|
||||||
SETTINGSDIST/
|
onyx/log/*
|
||||||
TEAMS/
|
onyx/config/root.xml
|
||||||
submissions/
|
onyx/db/*.profile.php
|
||||||
admin/sync/README.html
|
onyx/tpl/*/*.html
|
||||||
fickit-boot-cmdline
|
submission/*
|
||||||
fickit-boot-initrd.img
|
libmcrypt-perl_2.5.7.0-1_amd64.deb
|
||||||
fickit-boot-kernel
|
|
||||||
fickit-backend-cmdline
|
|
||||||
fickit-backend-initrd.img
|
|
||||||
fickit-backend-squashfs.img
|
|
||||||
fickit-backend-kernel
|
|
||||||
fickit-backend-state
|
|
||||||
fickit-frontend-cmdline
|
|
||||||
fickit-frontend-initrd.img
|
|
||||||
fickit-frontend-squashfs.img
|
|
||||||
fickit-frontend-kernel
|
|
||||||
fickit-frontend-state
|
|
||||||
fickit-prepare-bios.img
|
|
||||||
fickit-prepare-cmdline
|
|
||||||
fickit-prepare-initrd.img
|
|
||||||
fickit-prepare-kernel
|
|
||||||
fickit-prepare-state
|
|
||||||
fickit-update-cmdline
|
|
||||||
fickit-update-initrd.img
|
|
||||||
fickit-update-kernel
|
|
||||||
fickit-update-squashfs.img
|
|
||||||
result
|
|
||||||
started
|
|
||||||
|
|
||||||
# Standalone binaries
|
|
||||||
admin/get-remote-files/get-remote-files
|
|
||||||
fic-admin
|
|
||||||
fic-backend
|
|
||||||
fic-dashboard
|
|
||||||
fic-frontend
|
|
||||||
fic-qa
|
|
||||||
fic-repochecker
|
|
||||||
|
|
|
||||||
122
.gitlab-ci.yml
122
.gitlab-ci.yml
|
|
@ -1,122 +0,0 @@
|
||||||
---
|
|
||||||
|
|
||||||
stages:
|
|
||||||
- deps
|
|
||||||
- build
|
|
||||||
- fickit
|
|
||||||
- sast
|
|
||||||
- qa
|
|
||||||
- image
|
|
||||||
- container_scanning
|
|
||||||
|
|
||||||
cache:
|
|
||||||
paths:
|
|
||||||
- .go/pkg/mod/
|
|
||||||
- qa/ui/node_modules/
|
|
||||||
- frontend/ui/node_modules/
|
|
||||||
|
|
||||||
include:
|
|
||||||
- '.gitlab-ci/build.yml'
|
|
||||||
- '.gitlab-ci/image.yml'
|
|
||||||
- template: SAST.gitlab-ci.yml
|
|
||||||
- template: Security/Dependency-Scanning.gitlab-ci.yml
|
|
||||||
- template: Security/Secret-Detection.gitlab-ci.yml
|
|
||||||
- template: Security/Container-Scanning.gitlab-ci.yml
|
|
||||||
|
|
||||||
.scanners-matrix:
|
|
||||||
parallel:
|
|
||||||
matrix:
|
|
||||||
- IMAGE_NAME: [checker, admin, evdist, frontend-ui, nginx, dashboard, repochecker, qa, receiver, generator, remote-challenge-sync-airbus]
|
|
||||||
|
|
||||||
container_scanning:
|
|
||||||
stage: container_scanning
|
|
||||||
extends:
|
|
||||||
- .scanners-matrix
|
|
||||||
variables:
|
|
||||||
DOCKER_SERVICE: localhost
|
|
||||||
DOCKERFILE_PATH: Dockerfile-${IMAGE_NAME}
|
|
||||||
CI_APPLICATION_REPOSITORY: ${CI_REGISTRY_IMAGE}/${CI_COMMIT_REF_SLUG}/${IMAGE_NAME}
|
|
||||||
CI_APPLICATION_TAG: latest
|
|
||||||
GIT_STRATEGY: fetch
|
|
||||||
before_script:
|
|
||||||
- 'echo "Scanning: ${IMAGE_NAME}"'
|
|
||||||
rules:
|
|
||||||
- if: '$CI_COMMIT_BRANCH == "master"'
|
|
||||||
|
|
||||||
sast:
|
|
||||||
stage: sast
|
|
||||||
interruptible: true
|
|
||||||
needs: []
|
|
||||||
before_script:
|
|
||||||
- rm -rf .go/
|
|
||||||
|
|
||||||
secret_detection:
|
|
||||||
stage: sast
|
|
||||||
interruptible: true
|
|
||||||
needs: []
|
|
||||||
|
|
||||||
dependency_scanning:
|
|
||||||
stage: qa
|
|
||||||
interruptible: true
|
|
||||||
needs: []
|
|
||||||
|
|
||||||
get-deps:
|
|
||||||
stage: deps
|
|
||||||
image: golang:1-alpine
|
|
||||||
before_script:
|
|
||||||
- export GOPATH="$CI_PROJECT_DIR/.go"
|
|
||||||
- mkdir -p .go
|
|
||||||
script:
|
|
||||||
- apk --no-cache add git
|
|
||||||
- go get -v -d ./...
|
|
||||||
|
|
||||||
vet:
|
|
||||||
stage: sast
|
|
||||||
needs: ["build-qa-ui"]
|
|
||||||
dependencies:
|
|
||||||
- build-qa-ui
|
|
||||||
image: golang:1-alpine
|
|
||||||
before_script:
|
|
||||||
- export GOPATH="$CI_PROJECT_DIR/.go"
|
|
||||||
- mkdir -p .go
|
|
||||||
script:
|
|
||||||
- apk --no-cache add build-base
|
|
||||||
- go vet -v -buildvcs=false -tags gitgo ./...
|
|
||||||
- go vet -v -buildvcs=false ./...
|
|
||||||
|
|
||||||
fickit:
|
|
||||||
stage: fickit
|
|
||||||
interruptible: true
|
|
||||||
needs: ["build-admin","build-checker","build-dashboard","build-evdist","build-generator","build-qa","build-receiver","build-repochecker"]
|
|
||||||
image: nemunaire/linuxkit
|
|
||||||
tags: ['docker']
|
|
||||||
before_script:
|
|
||||||
- mkdir -p ~/.docker
|
|
||||||
- echo "{\"auths\":{\"${CI_REGISTRY}\":{\"username\":\"${CI_REGISTRY_USER}\",\"password\":\"${CI_REGISTRY_PASSWORD}\"}}}" > ~/.docker/config.json
|
|
||||||
script:
|
|
||||||
- dockerd & sleep 5
|
|
||||||
|
|
||||||
- linuxkit pkg push -force -org "${CI_REGISTRY_IMAGE}/${CI_COMMIT_REF_SLUG}" fickit-pkg/boot/
|
|
||||||
- linuxkit pkg push -force -org "${CI_REGISTRY_IMAGE}/${CI_COMMIT_REF_SLUG}" fickit-pkg/kexec/
|
|
||||||
- linuxkit pkg push -force -org "${CI_REGISTRY_IMAGE}/${CI_COMMIT_REF_SLUG}" fickit-pkg/mariadb-client/
|
|
||||||
- linuxkit pkg push -force -org "${CI_REGISTRY_IMAGE}/${CI_COMMIT_REF_SLUG}" fickit-pkg/mdadm/
|
|
||||||
- linuxkit pkg push -force -org "${CI_REGISTRY_IMAGE}/${CI_COMMIT_REF_SLUG}" fickit-pkg/rsync/
|
|
||||||
- linuxkit pkg push -force -org "${CI_REGISTRY_IMAGE}/${CI_COMMIT_REF_SLUG}" fickit-pkg/syslinux/
|
|
||||||
- linuxkit pkg push -force -org "${CI_REGISTRY_IMAGE}/${CI_COMMIT_REF_SLUG}" fickit-pkg/unbound/
|
|
||||||
|
|
||||||
- sed -i "s@nemunaire/fic-@${CI_REGISTRY_IMAGE}/master/@;s@nemunaire/@${CI_REGISTRY_IMAGE}/${CI_COMMIT_REF_SLUG}/@" fickit-backend.yml fickit-boot.yml fickit-frontend.yml fickit-prepare.yml fickit-update.yml
|
|
||||||
|
|
||||||
- linuxkit build -format kernel+squashfs fickit-backend.yml
|
|
||||||
- linuxkit build -format kernel+squashfs fickit-frontend.yml
|
|
||||||
- linuxkit build -format kernel+initrd fickit-boot.yml
|
|
||||||
- linuxkit build -format kernel+initrd fickit-prepare.yml
|
|
||||||
- linuxkit build -format kernel+initrd fickit-update.yml
|
|
||||||
artifacts:
|
|
||||||
expire_in: 8 hours
|
|
||||||
paths:
|
|
||||||
- fickit-backend-squashfs.img
|
|
||||||
- fickit-frontend-squashfs.img
|
|
||||||
- fickit-boot-kernel
|
|
||||||
- fickit-boot-initrd.img
|
|
||||||
- fickit-prepare-initrd.img
|
|
||||||
- fickit-update-initrd.img
|
|
||||||
|
|
@ -1,93 +0,0 @@
|
||||||
---
|
|
||||||
|
|
||||||
.build:
|
|
||||||
stage: build
|
|
||||||
image: golang:1-alpine
|
|
||||||
before_script:
|
|
||||||
- export GOPATH="$CI_PROJECT_DIR/.go"
|
|
||||||
- mkdir -p .go
|
|
||||||
variables:
|
|
||||||
CGO_ENABLED: 0
|
|
||||||
|
|
||||||
build-qa-ui:
|
|
||||||
stage: build
|
|
||||||
image: node:21-alpine
|
|
||||||
before_script:
|
|
||||||
script:
|
|
||||||
- cd qa/ui
|
|
||||||
- npm install --network-timeout=100000
|
|
||||||
- npm run build
|
|
||||||
artifacts:
|
|
||||||
paths:
|
|
||||||
- qa/ui/build/
|
|
||||||
when: on_success
|
|
||||||
|
|
||||||
build-checker:
|
|
||||||
extends:
|
|
||||||
- .build
|
|
||||||
script:
|
|
||||||
- go build -v -buildvcs=false -o deploy/checker srs.epita.fr/fic-server/checker
|
|
||||||
|
|
||||||
build-generator:
|
|
||||||
extends:
|
|
||||||
- .build
|
|
||||||
script:
|
|
||||||
- go build -v -buildvcs=false -o deploy/generator srs.epita.fr/fic-server/generator
|
|
||||||
|
|
||||||
build-receiver:
|
|
||||||
extends:
|
|
||||||
- .build
|
|
||||||
script:
|
|
||||||
- go build -v -buildvcs=false -o deploy/receiver srs.epita.fr/fic-server/receiver
|
|
||||||
|
|
||||||
build-admin:
|
|
||||||
extends:
|
|
||||||
- .build
|
|
||||||
script:
|
|
||||||
- go build -v -buildvcs=false -tags gitgo -o deploy/admin-gitgo srs.epita.fr/fic-server/admin
|
|
||||||
- go build -v -buildvcs=false -o deploy/admin srs.epita.fr/fic-server/admin
|
|
||||||
|
|
||||||
build-evdist:
|
|
||||||
extends:
|
|
||||||
- .build
|
|
||||||
script:
|
|
||||||
- go build -v -buildvcs=false -o deploy/evdist srs.epita.fr/fic-server/evdist
|
|
||||||
|
|
||||||
build-frontend-ui:
|
|
||||||
stage: build
|
|
||||||
image: node:21-alpine
|
|
||||||
before_script:
|
|
||||||
script:
|
|
||||||
- cd frontend/fic
|
|
||||||
- npm install --network-timeout=100000
|
|
||||||
- npm run build
|
|
||||||
|
|
||||||
build-dashboard:
|
|
||||||
extends:
|
|
||||||
- .build
|
|
||||||
script:
|
|
||||||
- go build -v -buildvcs=false -o deploy/dashboard srs.epita.fr/fic-server/dashboard
|
|
||||||
|
|
||||||
build-repochecker:
|
|
||||||
extends:
|
|
||||||
- .build
|
|
||||||
variables:
|
|
||||||
CGO_ENABLED: 1
|
|
||||||
script:
|
|
||||||
- apk --no-cache add build-base
|
|
||||||
- go build -buildvcs=false --tags checkupdate -v -o deploy/repochecker srs.epita.fr/fic-server/repochecker
|
|
||||||
- go build -buildvcs=false -buildmode=plugin -v -o deploy/repochecker-epita-rules.so srs.epita.fr/fic-server/repochecker/epita
|
|
||||||
- go build -buildvcs=false -buildmode=plugin -v -o deploy/repochecker-file-inspector-rules.so srs.epita.fr/fic-server/repochecker/file-inspector
|
|
||||||
- go build -buildvcs=false -buildmode=plugin -v -o deploy/repochecker-grammalecte-rules.so srs.epita.fr/fic-server/repochecker/grammalecte
|
|
||||||
- go build -buildvcs=false -buildmode=plugin -v -o deploy/repochecker-pcap-inspector-rules.so srs.epita.fr/fic-server/repochecker/pcap-inspector
|
|
||||||
- go build -buildvcs=false -buildmode=plugin -v -o deploy/repochecker-videos-rules.so srs.epita.fr/fic-server/repochecker/videos
|
|
||||||
- grep "const version" repochecker/update.go | sed -r 's/^.*=\s*(\S.*)$/\1/' > deploy/repochecker.version
|
|
||||||
|
|
||||||
build-qa:
|
|
||||||
extends:
|
|
||||||
- .build
|
|
||||||
needs: ["build-qa-ui"]
|
|
||||||
dependencies:
|
|
||||||
- build-qa-ui
|
|
||||||
script:
|
|
||||||
- go build -v -buildvcs=false -o deploy/qa srs.epita.fr/fic-server/qa
|
|
||||||
|
|
@ -1,99 +0,0 @@
|
||||||
---
|
|
||||||
|
|
||||||
.push:
|
|
||||||
stage: image
|
|
||||||
interruptible: true
|
|
||||||
needs: []
|
|
||||||
image:
|
|
||||||
name: gcr.io/kaniko-project/executor:v1.9.0-debug
|
|
||||||
entrypoint: [""]
|
|
||||||
before_script:
|
|
||||||
- mkdir -p /kaniko/.docker
|
|
||||||
- echo "{\"auths\":{\"${CI_REGISTRY}\":{\"username\":\"${CI_REGISTRY_USER}\",\"password\":\"${CI_REGISTRY_PASSWORD}\"}}}" > /kaniko/.docker/config.json
|
|
||||||
script:
|
|
||||||
- |
|
|
||||||
/kaniko/executor \
|
|
||||||
--context . \
|
|
||||||
--dockerfile "${DOCKERFILE}" \
|
|
||||||
--destination "${CI_REGISTRY_IMAGE}/${CI_COMMIT_REF_SLUG}/${CI_JOB_NAME}:${CI_COMMIT_SHA}" \
|
|
||||||
--destination "${CI_REGISTRY_IMAGE}/${CI_COMMIT_REF_SLUG}/${CI_JOB_NAME}:latest"
|
|
||||||
only:
|
|
||||||
- master
|
|
||||||
|
|
||||||
checker:
|
|
||||||
extends:
|
|
||||||
- .push
|
|
||||||
variables:
|
|
||||||
DOCKERFILE: Dockerfile-checker
|
|
||||||
|
|
||||||
receiver:
|
|
||||||
extends:
|
|
||||||
- .push
|
|
||||||
variables:
|
|
||||||
DOCKERFILE: Dockerfile-receiver
|
|
||||||
|
|
||||||
generator:
|
|
||||||
extends:
|
|
||||||
- .push
|
|
||||||
variables:
|
|
||||||
DOCKERFILE: Dockerfile-generator
|
|
||||||
|
|
||||||
admin:
|
|
||||||
extends:
|
|
||||||
- .push
|
|
||||||
variables:
|
|
||||||
DOCKERFILE: Dockerfile-admin
|
|
||||||
|
|
||||||
fickit-deploy:
|
|
||||||
extends:
|
|
||||||
- .push
|
|
||||||
variables:
|
|
||||||
DOCKERFILE: Dockerfile-deploy
|
|
||||||
|
|
||||||
get-remote-files:
|
|
||||||
extends:
|
|
||||||
- .push
|
|
||||||
variables:
|
|
||||||
DOCKERFILE: Dockerfile-get-remote-files
|
|
||||||
|
|
||||||
evdist:
|
|
||||||
extends:
|
|
||||||
- .push
|
|
||||||
variables:
|
|
||||||
DOCKERFILE: Dockerfile-evdist
|
|
||||||
|
|
||||||
frontend-ui:
|
|
||||||
extends:
|
|
||||||
- .push
|
|
||||||
variables:
|
|
||||||
DOCKERFILE: Dockerfile-frontend-ui
|
|
||||||
|
|
||||||
nginx:
|
|
||||||
extends:
|
|
||||||
- .push
|
|
||||||
variables:
|
|
||||||
DOCKERFILE: Dockerfile-nginx
|
|
||||||
|
|
||||||
dashboard:
|
|
||||||
extends:
|
|
||||||
- .push
|
|
||||||
variables:
|
|
||||||
DOCKERFILE: Dockerfile-dashboard
|
|
||||||
|
|
||||||
repochecker:
|
|
||||||
extends:
|
|
||||||
- .push
|
|
||||||
variables:
|
|
||||||
DOCKERFILE: Dockerfile-repochecker
|
|
||||||
|
|
||||||
qa:
|
|
||||||
extends:
|
|
||||||
- .push
|
|
||||||
variables:
|
|
||||||
DOCKERFILE: Dockerfile-qa
|
|
||||||
|
|
||||||
remote-challenge-sync-airbus:
|
|
||||||
extends:
|
|
||||||
- .push
|
|
||||||
variables:
|
|
||||||
DOCKERFILE: Dockerfile-remote-challenge-sync-airbus
|
|
||||||
61
Dockerfile
Normal file
61
Dockerfile
Normal file
|
|
@ -0,0 +1,61 @@
|
||||||
|
# /!\ WARNING: the container generated through this Dockerfile is made only for development purpose; it is NOT SAFE or production ready.
|
||||||
|
|
||||||
|
FROM debian:wheezy
|
||||||
|
MAINTAINER Pierre-Olivier Mercier <nemunaire@nemunai.re>
|
||||||
|
|
||||||
|
# Install packages ####################################################
|
||||||
|
|
||||||
|
RUN apt-get -y update && \
|
||||||
|
apt-get install -y \
|
||||||
|
most \
|
||||||
|
realpath \
|
||||||
|
nginx-light \
|
||||||
|
php5-fpm \
|
||||||
|
php5-mysql \
|
||||||
|
php5-mcrypt \
|
||||||
|
libmcrypt4 \
|
||||||
|
libwww-perl \
|
||||||
|
libdigest-whirlpool-perl \
|
||||||
|
libdbi-perl \
|
||||||
|
libdbd-mysql-perl \
|
||||||
|
pwgen \
|
||||||
|
curl \
|
||||||
|
openssl \
|
||||||
|
rsync \
|
||||||
|
openssh-client \
|
||||||
|
&& \
|
||||||
|
apt-get clean && rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/*
|
||||||
|
|
||||||
|
RUN useradd -d /var/www/fic-server -M -N -g www-data synchro
|
||||||
|
|
||||||
|
# ENVIRONNEMENT #######################################################
|
||||||
|
|
||||||
|
EXPOSE 80/tcp 443/tcp
|
||||||
|
VOLUME ["/var/www/fic-server/out","/var/www/fic-server/files","/var/www/fic-server/submission","/var/www/fic-server/shared"]
|
||||||
|
|
||||||
|
WORKDIR /var/www/fic-server
|
||||||
|
|
||||||
|
ENTRYPOINT ["/var/www/fic-server/entrypoint.sh"]
|
||||||
|
|
||||||
|
CMD service nginx start && \
|
||||||
|
service php5-fpm start && \
|
||||||
|
./nginx_gen_team.sh > ./shared/nginx-teams.conf && \
|
||||||
|
chown www-data ./shared/nginx-teams.conf && \
|
||||||
|
(./launch.sh &); \
|
||||||
|
/bin/bash
|
||||||
|
|
||||||
|
# Copying files #######################################################
|
||||||
|
|
||||||
|
COPY . /var/www/fic-server/
|
||||||
|
|
||||||
|
# Configure softwares #################################################
|
||||||
|
|
||||||
|
RUN dpkg -i /var/www/fic-server/libmcrypt-perl*.deb || \
|
||||||
|
(echo "Please build perl-mcrypt first. Consult the given README!"; exit 1) && \
|
||||||
|
rm /var/www/fic-server/libmcrypt-perl*.deb && \
|
||||||
|
ln -sf /var/www/fic-server/nginx-server.conf /etc/nginx/sites-enabled/default && \
|
||||||
|
ln -sf /var/www/fic-server/php-fpm.conf /etc/php5/fpm/pool.d/www.conf && \
|
||||||
|
\
|
||||||
|
ln -sf /var/www/fic-server/onyx/config/sample.root.xml /var/www/fic-server/onyx/config/root.xml && \
|
||||||
|
sed -i "s/challenge-public//" /var/www/fic-server/onyx/config/root.xml && \
|
||||||
|
chmod 777 /var/www/fic-server/onyx/cache/ /var/www/fic-server/onyx/cache/templates/cache/ /var/www/fic-server/onyx/cache/templates/compile/
|
||||||
|
|
@ -1,42 +0,0 @@
|
||||||
FROM golang:1-alpine AS gobuild
|
|
||||||
|
|
||||||
RUN apk add --no-cache git
|
|
||||||
|
|
||||||
WORKDIR /go/src/srs.epita.fr/fic-server/
|
|
||||||
|
|
||||||
RUN apk add --no-cache binutils-gold build-base
|
|
||||||
|
|
||||||
COPY go.mod go.sum ./
|
|
||||||
COPY settings settings/
|
|
||||||
COPY libfic ./libfic/
|
|
||||||
COPY admin ./admin/
|
|
||||||
COPY repochecker ./repochecker/
|
|
||||||
|
|
||||||
RUN go get -d -v ./admin && \
|
|
||||||
go build -v -o admin/admin ./admin && \
|
|
||||||
go build -v -buildmode=plugin -o repochecker/epita-rules.so ./repochecker/epita && \
|
|
||||||
go build -v -buildmode=plugin -o repochecker/file-inspector.so ./repochecker/file-inspector && \
|
|
||||||
go build -v -buildmode=plugin -o repochecker/grammalecte-rules.so ./repochecker/grammalecte && \
|
|
||||||
go build -v -buildmode=plugin -o repochecker/videos-rules.so ./repochecker/videos
|
|
||||||
|
|
||||||
|
|
||||||
FROM alpine:3.21
|
|
||||||
|
|
||||||
RUN apk add --no-cache \
|
|
||||||
ca-certificates \
|
|
||||||
git \
|
|
||||||
git-lfs \
|
|
||||||
openssh-client-default \
|
|
||||||
openssl
|
|
||||||
|
|
||||||
EXPOSE 8081
|
|
||||||
|
|
||||||
WORKDIR /srv
|
|
||||||
|
|
||||||
ENTRYPOINT ["/srv/admin", "-bind=:8081", "-baseurl=/admin/"]
|
|
||||||
|
|
||||||
COPY --from=gobuild /go/src/srs.epita.fr/fic-server/admin/admin /srv/admin
|
|
||||||
COPY --from=gobuild /go/src/srs.epita.fr/fic-server/repochecker/epita-rules.so /srv/epita-rules.so
|
|
||||||
COPY --from=gobuild /go/src/srs.epita.fr/fic-server/repochecker/file-inspector.so /usr/lib/file-inspector.so
|
|
||||||
COPY --from=gobuild /go/src/srs.epita.fr/fic-server/repochecker/grammalecte-rules.so /usr/lib/grammalecte-rules.so
|
|
||||||
COPY --from=gobuild /go/src/srs.epita.fr/fic-server/repochecker/videos-rules.so /usr/lib/videos-rules.so
|
|
||||||
|
|
@ -1,22 +0,0 @@
|
||||||
FROM golang:1-alpine AS gobuild
|
|
||||||
|
|
||||||
RUN apk add --no-cache git
|
|
||||||
|
|
||||||
WORKDIR /go/src/srs.epita.fr/fic-server/
|
|
||||||
|
|
||||||
COPY go.mod go.sum ./
|
|
||||||
COPY settings settings/
|
|
||||||
COPY libfic ./libfic/
|
|
||||||
COPY checker ./checker/
|
|
||||||
|
|
||||||
RUN go get -d -v ./checker && \
|
|
||||||
go build -v -buildvcs=false -o checker/checker ./checker
|
|
||||||
|
|
||||||
|
|
||||||
FROM alpine:3.21
|
|
||||||
|
|
||||||
WORKDIR /srv
|
|
||||||
|
|
||||||
ENTRYPOINT ["/srv/checker"]
|
|
||||||
|
|
||||||
COPY --from=gobuild /go/src/srs.epita.fr/fic-server/checker/checker /srv/checker
|
|
||||||
|
|
@ -1,32 +0,0 @@
|
||||||
FROM golang:1-alpine AS gobuild
|
|
||||||
|
|
||||||
RUN apk add --no-cache git
|
|
||||||
|
|
||||||
WORKDIR /go/src/srs.epita.fr/fic-server/
|
|
||||||
|
|
||||||
COPY go.mod go.sum ./
|
|
||||||
COPY settings settings/
|
|
||||||
COPY libfic ./libfic/
|
|
||||||
COPY dashboard ./dashboard/
|
|
||||||
|
|
||||||
RUN go get -d -v ./dashboard && \
|
|
||||||
go build -v -buildvcs=false -o dashboard/dashboard ./dashboard
|
|
||||||
|
|
||||||
|
|
||||||
FROM alpine:3.21
|
|
||||||
|
|
||||||
EXPOSE 8082
|
|
||||||
|
|
||||||
WORKDIR /srv
|
|
||||||
|
|
||||||
ENTRYPOINT ["/srv/dashboard", "--bind=:8082"]
|
|
||||||
|
|
||||||
VOLUME /srv/htdocs-dashboard/
|
|
||||||
|
|
||||||
COPY --from=gobuild /go/src/srs.epita.fr/fic-server/dashboard/dashboard /srv/dashboard
|
|
||||||
COPY dashboard/static/index.html /srv/htdocs-dashboard/
|
|
||||||
COPY admin/static/css/bootstrap.min.css dashboard/static/css/fic.css admin/static/css/glyphicon.css /srv/htdocs-dashboard/css/
|
|
||||||
COPY admin/static/fonts /srv/htdocs-dashboard/fonts
|
|
||||||
COPY dashboard/static/img/srs.png /srv/htdocs-dashboard/img/
|
|
||||||
COPY dashboard/static/js/dashboard.js admin/static/js/angular.min.js dashboard/static/js/angular-animate.min.js admin/static/js/angular-route.min.js admin/static/js/angular-sanitize.min.js admin/static/js/bootstrap.min.js admin/static/js/common.js admin/static/js/d3.v3.min.js admin/static/js/jquery.min.js /srv/htdocs-dashboard/js/
|
|
||||||
COPY admin/static/js/i18n/* /srv/htdocs-dashboard/js/i18n/
|
|
||||||
|
|
@ -1,24 +0,0 @@
|
||||||
FROM alpine:3.21
|
|
||||||
|
|
||||||
EXPOSE 67/udp
|
|
||||||
EXPOSE 69/udp
|
|
||||||
EXPOSE 80/tcp
|
|
||||||
|
|
||||||
ENTRYPOINT ["/usr/sbin/initial-config.sh"]
|
|
||||||
CMD ["/usr/bin/supervisord", "-c", "/etc/supervisord.conf"]
|
|
||||||
|
|
||||||
WORKDIR /srv/s
|
|
||||||
|
|
||||||
RUN apk add --no-cache \
|
|
||||||
busybox-extras \
|
|
||||||
supervisor \
|
|
||||||
syslinux \
|
|
||||||
tftp-hpa
|
|
||||||
|
|
||||||
RUN touch /var/lib/udhcpd/udhcpd.leases && \
|
|
||||||
mv /usr/share/syslinux/* /srv
|
|
||||||
|
|
||||||
COPY configs/deploy-initial-config.sh /usr/sbin/initial-config.sh
|
|
||||||
COPY configs/deploy-supervisord.conf /etc/supervisord.conf
|
|
||||||
COPY configs/udhcpd-sample.conf /etc/udhcpd.conf
|
|
||||||
COPY configs/pxelinux.cfg /srv/pxelinux.cfg/default
|
|
||||||
|
|
@ -1,21 +0,0 @@
|
||||||
FROM golang:1-alpine AS gobuild
|
|
||||||
|
|
||||||
RUN apk add --no-cache git
|
|
||||||
|
|
||||||
WORKDIR /go/src/srs.epita.fr/fic-server/
|
|
||||||
|
|
||||||
COPY go.mod go.sum ./
|
|
||||||
COPY settings settings/
|
|
||||||
COPY evdist ./evdist/
|
|
||||||
|
|
||||||
RUN go get -d -v ./evdist && \
|
|
||||||
go build -v -buildvcs=false -o evdist/evdist ./evdist
|
|
||||||
|
|
||||||
|
|
||||||
FROM alpine:3.21
|
|
||||||
|
|
||||||
WORKDIR /srv
|
|
||||||
|
|
||||||
ENTRYPOINT ["/srv/evdist"]
|
|
||||||
|
|
||||||
COPY --from=gobuild /go/src/srs.epita.fr/fic-server/evdist/evdist /srv/evdist
|
|
||||||
|
|
@ -1,13 +0,0 @@
|
||||||
FROM node:23-alpine AS nodebuild
|
|
||||||
|
|
||||||
WORKDIR /ui
|
|
||||||
|
|
||||||
COPY frontend/fic/ .
|
|
||||||
|
|
||||||
RUN npm install --network-timeout=100000 && \
|
|
||||||
npm run build
|
|
||||||
|
|
||||||
|
|
||||||
FROM scratch
|
|
||||||
|
|
||||||
COPY --from=nodebuild /ui/build/ /www/htdocs-frontend
|
|
||||||
|
|
@ -1,22 +0,0 @@
|
||||||
FROM golang:1-alpine AS gobuild
|
|
||||||
|
|
||||||
RUN apk add --no-cache git
|
|
||||||
|
|
||||||
WORKDIR /go/src/srs.epita.fr/fic-server/
|
|
||||||
|
|
||||||
COPY go.mod go.sum ./
|
|
||||||
COPY settings settings/
|
|
||||||
COPY libfic ./libfic/
|
|
||||||
COPY generator ./generator/
|
|
||||||
|
|
||||||
RUN go get -d -v ./generator && \
|
|
||||||
go build -v -buildvcs=false -o generator/generator ./generator
|
|
||||||
|
|
||||||
|
|
||||||
FROM alpine:3.21
|
|
||||||
|
|
||||||
WORKDIR /srv
|
|
||||||
|
|
||||||
ENTRYPOINT ["/srv/generator"]
|
|
||||||
|
|
||||||
COPY --from=gobuild /go/src/srs.epita.fr/fic-server/generator/generator /srv/generator
|
|
||||||
|
|
@ -1,27 +0,0 @@
|
||||||
FROM golang:1-alpine AS gobuild
|
|
||||||
|
|
||||||
RUN apk add --no-cache git
|
|
||||||
|
|
||||||
WORKDIR /go/src/srs.epita.fr/fic-server/
|
|
||||||
|
|
||||||
RUN apk add --no-cache build-base
|
|
||||||
|
|
||||||
COPY go.mod go.sum ./
|
|
||||||
COPY settings settings/
|
|
||||||
COPY libfic ./libfic/
|
|
||||||
COPY admin ./admin/
|
|
||||||
|
|
||||||
RUN go get -d -v ./admin && \
|
|
||||||
go build -v -o get-remote-files ./admin/get-remote-files
|
|
||||||
|
|
||||||
|
|
||||||
FROM alpine:3.21
|
|
||||||
|
|
||||||
RUN apk add --no-cache \
|
|
||||||
ca-certificates
|
|
||||||
|
|
||||||
WORKDIR /srv
|
|
||||||
|
|
||||||
ENTRYPOINT ["/srv/get-remote-files", "/mnt/fic/"]
|
|
||||||
|
|
||||||
COPY --from=gobuild /go/src/srs.epita.fr/fic-server/get-remote-files /srv/get-remote-files
|
|
||||||
|
|
@ -1,32 +0,0 @@
|
||||||
FROM node:23-alpine AS nodebuild
|
|
||||||
|
|
||||||
WORKDIR /ui
|
|
||||||
|
|
||||||
COPY frontend/fic/ .
|
|
||||||
|
|
||||||
RUN npm install --network-timeout=100000 && \
|
|
||||||
npm run build
|
|
||||||
|
|
||||||
|
|
||||||
FROM nginx:stable-alpine-slim
|
|
||||||
|
|
||||||
ENV FIC_BASEURL /
|
|
||||||
ENV HOST_RECEIVER receiver:8080
|
|
||||||
ENV HOST_ADMIN admin:8081
|
|
||||||
ENV HOST_DASHBOARD dashboard:8082
|
|
||||||
ENV HOST_QA qa:8083
|
|
||||||
ENV PATH_FILES /srv/FILES
|
|
||||||
ENV PATH_STARTINGBLOCK /srv/STARTINGBLOCK
|
|
||||||
ENV PATH_STATIC /srv/htdocs-frontend
|
|
||||||
ENV PATH_SETTINGS /srv/SETTINGSDIST
|
|
||||||
ENV PATH_TEAMS /srv/TEAMS
|
|
||||||
|
|
||||||
EXPOSE 80
|
|
||||||
|
|
||||||
COPY configs/nginx-chbase.sh /docker-entrypoint.d/40-update-baseurl.sh
|
|
||||||
|
|
||||||
COPY configs/nginx/get-team/upstream.conf /etc/nginx/fic-get-team.conf
|
|
||||||
COPY configs/nginx/auth/none.conf /etc/nginx/fic-auth.conf
|
|
||||||
COPY configs/nginx/base/docker.conf /etc/nginx/templates/default.conf.template
|
|
||||||
|
|
||||||
COPY --from=nodebuild /ui/build/ /srv/htdocs-frontend
|
|
||||||
|
|
@ -1,38 +0,0 @@
|
||||||
FROM node:23-alpine AS nodebuild
|
|
||||||
|
|
||||||
WORKDIR /ui
|
|
||||||
|
|
||||||
COPY qa/ui/ .
|
|
||||||
|
|
||||||
RUN npm install --network-timeout=100000 && \
|
|
||||||
npm run build
|
|
||||||
|
|
||||||
|
|
||||||
FROM golang:1-alpine AS gobuild
|
|
||||||
|
|
||||||
RUN apk add --no-cache git
|
|
||||||
|
|
||||||
WORKDIR /go/src/srs.epita.fr/fic-server/
|
|
||||||
|
|
||||||
COPY go.mod go.sum ./
|
|
||||||
COPY settings settings/
|
|
||||||
COPY libfic ./libfic/
|
|
||||||
COPY --from=nodebuild /ui ./qa/ui
|
|
||||||
COPY qa ./qa/
|
|
||||||
COPY admin ./admin/
|
|
||||||
|
|
||||||
RUN go get -d -v ./qa && \
|
|
||||||
go build -v -buildvcs=false -o qa/qa ./qa
|
|
||||||
|
|
||||||
|
|
||||||
FROM alpine:3.21
|
|
||||||
|
|
||||||
EXPOSE 8083
|
|
||||||
|
|
||||||
WORKDIR /srv
|
|
||||||
|
|
||||||
ENTRYPOINT ["/srv/qa", "--bind=:8083"]
|
|
||||||
|
|
||||||
VOLUME /srv/htdocs-qa/
|
|
||||||
|
|
||||||
COPY --from=gobuild /go/src/srs.epita.fr/fic-server/qa/qa /srv/qa
|
|
||||||
|
|
@ -1,27 +0,0 @@
|
||||||
FROM golang:1-alpine AS gobuild
|
|
||||||
|
|
||||||
RUN apk add --no-cache git
|
|
||||||
|
|
||||||
WORKDIR /go/src/srs.epita.fr/fic-server/
|
|
||||||
|
|
||||||
COPY go.mod go.sum ./
|
|
||||||
COPY settings settings/
|
|
||||||
COPY libfic ./libfic/
|
|
||||||
COPY receiver ./receiver/
|
|
||||||
|
|
||||||
RUN go get -d -v ./receiver && \
|
|
||||||
go build -v -buildvcs=false -o ./receiver/receiver ./receiver
|
|
||||||
|
|
||||||
|
|
||||||
FROM alpine:3.21
|
|
||||||
|
|
||||||
EXPOSE 8080
|
|
||||||
|
|
||||||
WORKDIR /srv
|
|
||||||
|
|
||||||
ENTRYPOINT ["/usr/sbin/entrypoint.sh"]
|
|
||||||
CMD ["--bind=:8080"]
|
|
||||||
|
|
||||||
COPY entrypoint-receiver.sh /usr/sbin/entrypoint.sh
|
|
||||||
|
|
||||||
COPY --from=gobuild /go/src/srs.epita.fr/fic-server/receiver/receiver /srv/receiver
|
|
||||||
|
|
@ -1,24 +0,0 @@
|
||||||
FROM golang:1-alpine AS gobuild
|
|
||||||
|
|
||||||
RUN apk add --no-cache git
|
|
||||||
|
|
||||||
WORKDIR /go/src/srs.epita.fr/fic-server/
|
|
||||||
|
|
||||||
COPY go.mod go.sum ./
|
|
||||||
COPY libfic ./libfic/
|
|
||||||
COPY settings ./settings/
|
|
||||||
COPY remote/challenge-sync-airbus ./remote/challenge-sync-airbus/
|
|
||||||
|
|
||||||
RUN go get -d -v ./remote/challenge-sync-airbus && \
|
|
||||||
go build -v -buildvcs=false -o ./challenge-sync-airbus ./remote/challenge-sync-airbus
|
|
||||||
|
|
||||||
|
|
||||||
FROM alpine:3.21
|
|
||||||
|
|
||||||
RUN apk add --no-cache openssl ca-certificates
|
|
||||||
|
|
||||||
WORKDIR /srv
|
|
||||||
|
|
||||||
ENTRYPOINT ["/srv/challenge-sync-airbus"]
|
|
||||||
|
|
||||||
COPY --from=gobuild /go/src/srs.epita.fr/fic-server/challenge-sync-airbus /srv/challenge-sync-airbus
|
|
||||||
|
|
@ -1,24 +0,0 @@
|
||||||
FROM golang:1-alpine AS gobuild
|
|
||||||
|
|
||||||
RUN apk add --no-cache git
|
|
||||||
|
|
||||||
WORKDIR /go/src/srs.epita.fr/fic-server/
|
|
||||||
|
|
||||||
COPY go.mod go.sum ./
|
|
||||||
COPY libfic ./libfic/
|
|
||||||
COPY settings ./settings/
|
|
||||||
COPY remote/scores-sync-zqds ./remote/scores-sync-zqds/
|
|
||||||
|
|
||||||
RUN go get -d -v ./remote/scores-sync-zqds && \
|
|
||||||
go build -v -buildvcs=false -o ./scores-sync-zqds ./remote/scores-sync-zqds
|
|
||||||
|
|
||||||
|
|
||||||
FROM alpine:3.21
|
|
||||||
|
|
||||||
RUN apk add --no-cache openssl ca-certificates
|
|
||||||
|
|
||||||
WORKDIR /srv
|
|
||||||
|
|
||||||
ENTRYPOINT ["/srv/scores-sync-zqds"]
|
|
||||||
|
|
||||||
COPY --from=gobuild /go/src/srs.epita.fr/fic-server/scores-sync-zqds /srv/scores-sync-zqds
|
|
||||||
|
|
@ -1,42 +0,0 @@
|
||||||
FROM golang:1-alpine AS gobuild
|
|
||||||
|
|
||||||
RUN apk add --no-cache git
|
|
||||||
|
|
||||||
WORKDIR /go/src/srs.epita.fr/fic-server/
|
|
||||||
|
|
||||||
RUN apk add --no-cache binutils-gold build-base
|
|
||||||
|
|
||||||
COPY go.mod go.sum ./
|
|
||||||
COPY settings settings/
|
|
||||||
COPY libfic ./libfic/
|
|
||||||
COPY admin ./admin/
|
|
||||||
COPY repochecker ./repochecker/
|
|
||||||
|
|
||||||
RUN go get -d -v ./repochecker && \
|
|
||||||
go build -v -o repochecker/repochecker ./repochecker && \
|
|
||||||
go build -v -buildmode=plugin -o repochecker/epita-rules.so ./repochecker/epita && \
|
|
||||||
go build -v -buildmode=plugin -o repochecker/file-inspector.so ./repochecker/file-inspector && \
|
|
||||||
go build -v -buildmode=plugin -o repochecker/grammalecte-rules.so ./repochecker/grammalecte && \
|
|
||||||
go build -v -buildmode=plugin -o repochecker/pcap-inspector.so ./repochecker/pcap-inspector && \
|
|
||||||
go build -v -buildmode=plugin -o repochecker/videos-rules.so ./repochecker/videos
|
|
||||||
|
|
||||||
|
|
||||||
ENV GRAMMALECTE_VERSION 2.1.1
|
|
||||||
|
|
||||||
ADD https://web.archive.org/web/20240926154729if_/https://grammalecte.net/zip/Grammalecte-fr-v$GRAMMALECTE_VERSION.zip /srv/grammalecte.zip
|
|
||||||
|
|
||||||
RUN mkdir /srv/grammalecte && cd /srv/grammalecte && unzip /srv/grammalecte.zip && sed -i 's/if sys.version_info.major < (3, 7):/if False:/' /srv/grammalecte/grammalecte-server.py
|
|
||||||
|
|
||||||
FROM alpine:3.19
|
|
||||||
|
|
||||||
ENTRYPOINT ["/usr/bin/repochecker", "--rules-plugins=/usr/lib/epita-rules.so", "--rules-plugins=/usr/lib/file-inspector.so", "--rules-plugins=/usr/lib/grammalecte-rules.so", "--rules-plugins=/usr/lib/pcap-inspector.so", "--rules-plugins=/usr/lib/videos-rules.so"]
|
|
||||||
|
|
||||||
RUN apk add --no-cache git python3 ffmpeg
|
|
||||||
|
|
||||||
COPY --from=gobuild /srv/grammalecte /srv/grammalecte
|
|
||||||
COPY --from=gobuild /go/src/srs.epita.fr/fic-server/repochecker/repochecker /usr/bin/repochecker
|
|
||||||
COPY --from=gobuild /go/src/srs.epita.fr/fic-server/repochecker/epita-rules.so /usr/lib/epita-rules.so
|
|
||||||
COPY --from=gobuild /go/src/srs.epita.fr/fic-server/repochecker/file-inspector.so /usr/lib/file-inspector.so
|
|
||||||
COPY --from=gobuild /go/src/srs.epita.fr/fic-server/repochecker/grammalecte-rules.so /usr/lib/grammalecte-rules.so
|
|
||||||
COPY --from=gobuild /go/src/srs.epita.fr/fic-server/repochecker/pcap-inspector.so /usr/lib/pcap-inspector.so
|
|
||||||
COPY --from=gobuild /go/src/srs.epita.fr/fic-server/repochecker/videos-rules.so /usr/lib/videos-rules.so
|
|
||||||
21
LICENSE
21
LICENSE
|
|
@ -1,21 +0,0 @@
|
||||||
MIT License
|
|
||||||
|
|
||||||
Copyright (c) 2016-2018 Pierre-Olivier Mercier <nemunaire@nemunai.re>
|
|
||||||
|
|
||||||
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.
|
|
||||||
442
README.md
442
README.md
|
|
@ -1,238 +1,314 @@
|
||||||
FIC Forensic CTF Platform
|
FIC forensic challenge validation server
|
||||||
=========================
|
========================================
|
||||||
|
|
||||||
This is a CTF server for distributing and validating challenges. It is design
|
This is a CTF server for distributing and validating exercices. It is design to
|
||||||
to be robust, so it uses some uncommon technics like client certificate for
|
be robust, so it uses some uncommon technologies like client certificate for
|
||||||
authentication, lots of state of the art cryptographic methods and aims to be
|
authentication, cryptographic functions and DMZ network architecture.
|
||||||
deployed in a DMZ network architecture.
|
|
||||||
|
|
||||||
## Features
|
Development And Testing
|
||||||
|
-----------------------
|
||||||
|
|
||||||
- **Collaborative Challenge Design and Review:** Facilitates large team collaboration for challenge creation and review.
|
The easiest way to have a working server is to build a Docker container.
|
||||||
- **Versatile Flag Formats:** Supports flags as strings, numbers, multiple-choice questions, unique-choice questions, selects, multiline inputs, and strings with capture regexp.
|
|
||||||
- **Engaging Challenge Interface:** A visually appealing interface that incorporates images to illustrate exercises.
|
### Docker
|
||||||
- **Public Dashboard:** Allow spectators to follow the competition alongside players.
|
|
||||||
- **Archival Mode:** Preserve past challenges and data in a static form, with no code. Your archive can lied on a S3 bucket.
|
First, build the container with the following command:
|
||||||
- **Export Capabilities:** Export challenges to other CTF platforms.
|
|
||||||
- **Security-Focused:** Designed with security as a top priority. Each service aims to be isolated with right restrictions. Answers are not stored in the database, ...
|
```
|
||||||
- **Choose your Authentication:** Authentication is not part of this project, integrate your own authentication methods.
|
docker build -t fic .
|
||||||
- **Extensible:** Easily extend and customize the platform. The main codebase in Golang is highly documented, each frontend part can be recreated in another language with ease.
|
```
|
||||||
- **Comprehensive Settings:** A wide range of settings for challenge customization. You can have first blood or not, dynamic exercice gain, evenemential bonus, ...
|
|
||||||
- **Git Integration:** Seamless verification and integration with Git.
|
Then, run it with:
|
||||||
- **Infrastructure as Code (IaC):** Ensure read-only and reproducible infrastructure.
|
|
||||||
- **Last-Minute Checks:** Ensure your challenge is ready with a comprehensive set of checks that can be performed anytime, verifying that downloadable files are as expected by the challenge creators.
|
```
|
||||||
- **Lightweight:** Optimized for minimal resource consumption, supporting features like serving gzipped files directly to browsers without CPU usage.
|
docker run -t -i -P fic
|
||||||
- **Scalable:** Designed to handle large-scale competitions with multiple receivers and frontend servers, smoothly queuing activity peaks on the backend.
|
```
|
||||||
- **Offline Capability:** Run your challenges offline.
|
|
||||||
- **Integrated Exercise Issue Ticketing System:** Manage and track issues related to exercises during the competition directly with teams. During designing phase, this transform in a complete dedicated QA platform.
|
It will ask you for a passphrase, you must provide one with at least 4
|
||||||
- **Detailed Statistics:** Provide administrators with insights into exercise preferences and complexity.
|
characters. This key is used to generate the server certificate.
|
||||||
- **Change Planning:** Schedule events in advance, such as new exercise availability or ephemeral bonuses, with second-by-second precision.
|
|
||||||
- **Frontend Time Synchronization:** Ensure accurate remaining time and event synchronization between servers and players.
|
When you see:
|
||||||
|
|
||||||
|
```
|
||||||
|
root@xxxxxxxxxxxx:/var/www/fic-server#
|
||||||
|
```
|
||||||
|
|
||||||
|
congratulations, the container is running!
|
||||||
|
|
||||||
|
Use `docker ps` to view to which local ports was assigned the contained
|
||||||
|
webserver.
|
||||||
|
|
||||||
|
|
||||||
## Overview
|
### Database
|
||||||
|
|
||||||
This is a [monorepo](https://danluu.com/monorepo/), containing several
|
Demo data are available in `/var/www/fic-server/db/feed.sql`. In test
|
||||||
micro-services :
|
environment, you can run the following command:
|
||||||
|
|
||||||
- `admin` is the web interface and API used to control the challenge
|
mysql -u root fic < /var/www/fic-server/db/feed.sql
|
||||||
and doing synchronization.
|
|
||||||
- `checker` is an inotify reacting service that handles submissions
|
|
||||||
checking.
|
|
||||||
- `dashboard` is a public interface to explain and follow the
|
|
||||||
conquest, aims to animate the challenge for visitors.
|
|
||||||
- `evdist` is an inotify reacting service that handles settings
|
|
||||||
changes during the challenge (eg. a 30 minutes event where hints are
|
|
||||||
free, ...).
|
|
||||||
- `generator` takes care of global and team's files generation.
|
|
||||||
- `qa` is an interface dedicated to challenge development, it stores
|
|
||||||
reports to be treated by challenges creators.
|
|
||||||
- `receiver` is only responsible for receiving submissions. It is the
|
|
||||||
only dynamic part accessibe to players, so it's codebase is reduce
|
|
||||||
to the minimum. It does not parse or try to understand players
|
|
||||||
submissions, it just write it down to a file in the file
|
|
||||||
system. Parsing and treatment is made by the `checker`.
|
|
||||||
- `remote/challenge-sync-airbus` is an inotify reacting service that
|
|
||||||
allows us to synchronize scores and exercice validations with the
|
|
||||||
Airbus scoring platform.
|
|
||||||
- `remote/scores-sync-zqds` is an inotify reacting service that allows
|
|
||||||
us to synchronize scores with the ZQDS scoring platform.
|
|
||||||
- `repochecker` is a side project to check offline for synchronization
|
|
||||||
issues.
|
|
||||||
|
|
||||||
Here is how thoses services speak to each others:
|
|
||||||
|
|
||||||

|
### Frontend container
|
||||||
|
|
||||||
In the production setup, each micro-service runs in a dedicated
|
To run the frontend on the same machine as the backend (but in another
|
||||||
container, isolated from each other. Moreover, two physical machines
|
container), run the following command:
|
||||||
should be used:
|
|
||||||
|
|
||||||
- `phobos` communicates with players: displaying the web interface,
|
docker run -P -ti --volumes-from BACKEND_CNTNR_NAME FRONTEND_IMG
|
||||||
authenticate teams and players, storing contest files and handling
|
|
||||||
submissions retrieval without understanding them. It can't access
|
|
||||||
`deimos` so its job stops after writing requests on the filesystem.
|
|
||||||
- `deimos` is hidden from players, isolated from the network. It can
|
|
||||||
only access `phobos` via a restricted ssh connection, to retrieve
|
|
||||||
requests from `phobos` filesystem and pushing to it newly generated
|
|
||||||
static files.
|
|
||||||
|
|
||||||
Concretely, the L2 looks like this:
|
|
||||||
|
|
||||||

|
Production Environnement
|
||||||
|
------------------------
|
||||||
|
|
||||||
So, the general filesystem is organized this way:
|
### Setup
|
||||||
|
|
||||||
- `DASHBOARD` contains files structuring the content of the dashboard
|
You should compile/install hardened kernel (with latest stable GrSec patch) on
|
||||||
screen(s).
|
each machine.
|
||||||
- `FILES` stores the contest file to be downloaded by players. To be
|
|
||||||
accessible without authentication and to avoid bruteforce, each file
|
|
||||||
is placed into a directory with a hashed name (the original file
|
|
||||||
name is preserved). It's rsynced as is to `deimos`.
|
|
||||||
- `GENERATOR` contains a socket to allow other services to communicate
|
|
||||||
with the `generator`.
|
|
||||||
- `PKI` takes care of the PKI used for the client certiciate
|
|
||||||
authorization process, and more generaly, all authentication related
|
|
||||||
files (htpasswd, dexidp config, ...). Only the `shared` subdirectory
|
|
||||||
is shared with `deimos`, private key and teams P12 don't go out.
|
|
||||||
- `SETTINGS` stores the challenge config as wanted by admins. It's not
|
|
||||||
always the config in use: it uses can be delayed waiting for a
|
|
||||||
trigger.
|
|
||||||
- `SETTINGSDIST` is the challenge configuration in use. It is the one
|
|
||||||
shared with players.
|
|
||||||
- `startingblock` keep the `started` state of the challenge. This
|
|
||||||
helps `nginx` to know when it can start distributing exercices
|
|
||||||
related files.
|
|
||||||
- `TEAMS` stores the static files generated by the `generator`, there is
|
|
||||||
one subdirectory by team (id of the team), plus some files at the
|
|
||||||
root, which are common to all teams. There is also symlink pointing
|
|
||||||
to team directory, each symlink represent an authentication
|
|
||||||
association (certificate ID, OpenID username, htpasswd user, ...).
|
|
||||||
- `submissions` is the directory where the `receiver` writes
|
|
||||||
requests. It creates subdirectories at the name of the
|
|
||||||
authentication association, as seen in `TEAMS`, `checker` then
|
|
||||||
resolve the association regarding `TEAMS` directory. There is also a
|
|
||||||
special directory to handle team registration.
|
|
||||||
|
|
||||||
Here is a diagram showing how each micro-service uses directories it has access to (blue for read access, red for write access):
|
Prefer GNU/Linux distributions where most packages are compiled with `-fPIC`
|
||||||
|
and `-fstack-protector`, like Ubuntu or
|
||||||
|
[Gentoo Hardened](http://www.gentoo.org/proj/en/hardened/).
|
||||||
|
|
||||||

|
As machines aren't always in safe place (transportation, night before CTF,
|
||||||
|
...), disks should be encrypted.
|
||||||
|
|
||||||
Local developer setup
|
**Always set strong password when it is possible** eg. SSL certificats, ...
|
||||||
---------------------
|
|
||||||
|
|
||||||
### Using Docker
|
|
||||||
|
|
||||||
Use `docker-compose build`, then `docker-compose up` to launch the infrastructure.
|
#### Backend
|
||||||
|
|
||||||
After booting, you'll be able to reach the main interface at:
|
##### Docker containers
|
||||||
<http://localhost:8042/> and the admin one at: <http://localhost:8081/> (or at <http://localhost:8042/admin/>).
|
|
||||||
The dashboard is available at <http://localhost:8042/dashboard/> and the QA service at <http://localhost:8042/qa/>.
|
|
||||||
|
|
||||||
In this setup, there is no authentication. You are identfied [as a team](./configs/nginx/get-team/team-1.conf). On first use you'll need to register.
|
Main Docker backend container relies on several other container:
|
||||||
|
|
||||||
#### Import folder
|
* MySQL database;
|
||||||
|
* Database storage (as data only container);
|
||||||
|
* PKI storage;
|
||||||
|
* PKI shared storage;
|
||||||
|
* challenge files containers;
|
||||||
|
* the backend.
|
||||||
|
|
||||||
##### Local import folder
|
To have a fully working backend:
|
||||||
The following changes is only required if your are trying to change the local import folder `~/fic` location.
|
|
||||||
|
|
||||||
Make the following changes inside this file `docker-compose.yml`:
|
1. Create a data-only container:
|
||||||
|
|
||||||
23 volumes:
|
```
|
||||||
24 - - ~/fic:/mnt/fic:ro
|
docker run --name mysql_data -v /var/lib/mysql busybox
|
||||||
24 + - <custom-path-to-import-folder>/fic:/mnt/fic:ro
|
```
|
||||||
|
|
||||||
##### Git import
|
2. Setup the MySQL server:
|
||||||
A git repository can be used:
|
|
||||||
|
|
||||||
29 - command: --baseurl /admin/ -localimport /mnt/fic -localimportsymlink
|
```
|
||||||
29 + command: --baseurl /admin/ -localimport /mnt/fic -localimportsymlink -git-import-remote git@gitlab.cri.epita.fr:ing/majeures/srs/fic/2042/challenges.git
|
docker run -d --name db_setup --volumes-from mysql_data -e MYSQL_ROOT_PASSWORD=mysecretpassword -e MYSQL_USER=fic -e MYSQL_PASSWORD=anotherpassword -e MYSQL_DATABASE=fic mysql mysqld --skip-name-resolve
|
||||||
|
```
|
||||||
|
|
||||||
##### Owncloud import folder
|
Fill the database:
|
||||||
If your are trying to use the folder available with the Owncloud service, make the following changes inside this file `docker-compose.yml`:
|
|
||||||
|
|
||||||
29 - command: --baseurl /admin/ -localimport /mnt/fic -localimportsymlink
|
```
|
||||||
29 + command: --baseurl /admin/ -clouddav=https://owncloud.srs.epita.fr/remote.php/webdav/FIC%202019/ -clouduser <login_x> -cloudpass '<passwd>'
|
docker build -t db_filler db/
|
||||||
|
docker run --rm -it --link db_setup:db db_filler
|
||||||
|
```
|
||||||
|
|
||||||
### Manual builds
|
Stop it:
|
||||||
|
|
||||||
Running this project requires a web server (configuration is given for nginx),
|
```
|
||||||
a database (currently supporting only MySQL/MariaDB), a Go compiler for the
|
docker stop db_setup
|
||||||
revision 1.18 at least and a `inotify`-aware system. You'll also need NodeJS to
|
docker rm db_setup
|
||||||
compile some user interfaces.
|
```
|
||||||
|
|
||||||
1. Above all, you need to build Node projects:
|
3. Run the database container:
|
||||||
|
|
||||||
cd frontend/fic; npm install && npm run build
|
```
|
||||||
cd qa/ui; npm install && npm run build
|
docker run -d --name db --volumes-from mysql_data -e MYSQL_USER=fic -e MYSQL_PASSWORD=anotherpassword -e MYSQL_DATABASE=fic mysql mysqld --skip-name-resolve
|
||||||
|
```
|
||||||
|
|
||||||
2. First, you'll need to retrieve the dependencies:
|
Here, we use the `--skip-name-resolve` option, because without internet connection, MySQL try to find hostname from IP and hang a lot.
|
||||||
|
|
||||||
go mod vendor
|
4. Setup the PKI storages:
|
||||||
|
|
||||||
2. Then, build the three Go projects:
|
```
|
||||||
|
docker run --name pki_storage -v /var/www/fic-server/PKI busybox
|
||||||
|
docker run --name shared_storage -v /var/www/fic-server/shared busybox
|
||||||
|
```
|
||||||
|
|
||||||
go build -o fic-admin ./admin
|
5. Build the PKI configuration container:
|
||||||
go build -o fic-checker ./checker
|
|
||||||
go build -o fic-dashboard ./dashboard
|
|
||||||
go build -o fic-generator ./generator
|
|
||||||
go build -o fic-qa ./qa
|
|
||||||
go build -o fic-receiver ./receiver
|
|
||||||
go build -o fic-repochecker ./repochecker
|
|
||||||
...
|
|
||||||
|
|
||||||
3. Before launching anything, you need to create a database:
|
```
|
||||||
|
docker build -t pki_setup pki/
|
||||||
|
```
|
||||||
|
|
||||||
mysql -u root -p <<EOF
|
6. Configure the PKI
|
||||||
CREATE DATABASE fic;
|
|
||||||
CREATE USER fic@localhost IDENTIFIED BY 'fic';
|
|
||||||
GRANT ALL ON fic.* TO fic@localhost;
|
|
||||||
EOF
|
|
||||||
|
|
||||||
By default, expected credentials for development purpose is `fic`,
|
For development purpose, you can run the default setup:
|
||||||
for both username, password and database name. If you want to use
|
|
||||||
other credentials, define the corresponding environment variable:
|
|
||||||
`MYSQL_HOST`, `MYSQL_USER`, `MYSQL_PASSWORD` and
|
|
||||||
`MYSQL_DATABASE`. Those variables are the one used by the `mysql`
|
|
||||||
docker image, so just link them together if you use containers.
|
|
||||||
|
|
||||||
4. Launch it!
|
```
|
||||||
|
docker run --rm -it --volumes-from pki_storage --volumes-from shared_storage pki_setup
|
||||||
|
```
|
||||||
|
|
||||||
./fic-admin &
|
For production environment:
|
||||||
|
|
||||||
After initializing the database, the server will listen on
|
```
|
||||||
<http://localhost:8081/>: this is the administration part.
|
docker run --rm -it --volumes-from pki_storage --volumes-from shared_storage pki_setup IPorURL
|
||||||
|
```
|
||||||
|
|
||||||
./fic-generator &
|
Where `IPorURL` is the way the certificat will authenticate: if challengers
|
||||||
|
will access the frontend with a custom domain, indicate this domain
|
||||||
|
(eg. `epita_challenge.fic.local`); else indicate the IP of the **front** host
|
||||||
|
on the network (eg. `192.168.0.5`).
|
||||||
|
|
||||||
This daemon generates static and team related files and then waits
|
For example, 2015 PKI generation looks like:
|
||||||
another process to tell it to regenerate some files.
|
|
||||||
|
|
||||||
./fic-receiver &
|
```
|
||||||
|
docker run --rm -it --volumes-from pki_storage --volumes-from shared_storage pki_setup 192.168.0.5
|
||||||
|
```
|
||||||
|
|
||||||
This one exposes an API that gives time synchronization to clients and
|
7. Build the perl `Mcrypt` debian package
|
||||||
handle submission reception (but without treating them).
|
|
||||||
|
|
||||||
./fic-checker &
|
```
|
||||||
|
docker build -t perl-mcrypt perl-mcrypt/
|
||||||
|
docker run --name mcrypt_builder perl-mcrypt
|
||||||
|
docker cp mcrypt_builder:$(docker diff mcrypt_builder | grep -oE '[^ ]+deb$') ./
|
||||||
|
docker rm mcrypt_builder
|
||||||
|
```
|
||||||
|
|
||||||
This service waits for new submissions (expected in `submissions`
|
8. Build and run the backend:
|
||||||
directory). It only watchs modifications on the file system, it has no web
|
|
||||||
interface.
|
|
||||||
|
|
||||||
./fic-dashboard &
|
```
|
||||||
|
docker build -t backend .
|
||||||
|
docker run --rm -it --link db:db --volumes-from pki_storage --volumes-from shared_storage -v /home/files:/var/www/fic-server/files-in backend
|
||||||
|
```
|
||||||
|
|
||||||
This last server runs the public dashboard. It serves all file, without the
|
Where `/home/files` is your local directory containing all challenge files.
|
||||||
need of a webserver. It listens on port 8082 by default.
|
|
||||||
|
|
||||||
./fic-qa &
|
|
||||||
|
|
||||||
If you need it, this will launch a web interface on the port 8083 by
|
##### Requirements
|
||||||
default, to perform quality control.
|
|
||||||
|
|
||||||
For the moment, a web server is mandatory to serve static files, look
|
* `realpath`;
|
||||||
at the samples given in the `configs/nginx` directory. You need to
|
* `mysql`;
|
||||||
pick one base configation flavor in the `configs/nginx/base`
|
* `nginx` with `fastcgi` module;
|
||||||
directory, and associated with an authentication mechanism in
|
* `php-fpm` with `mysql` module;
|
||||||
`configs/nginx/auth` (named the file `fic-auth.conf` in `/etc/nginx`),
|
* `openssl` and `pwgen` for client certificat generation;
|
||||||
and also pick the corresponding `configs/nginx/get-team` file, you
|
* `mcrypt`;
|
||||||
named `fic-get-team.conf`.
|
* `HTTP::Request::Common` perl module (provided by `libwww-perl`);
|
||||||
|
* `Digest::Whirlpool` perl module (provided by `lib-digest-whirlpool-perl`);
|
||||||
|
* `Mcrypt` from CPAN (`cpan -i Mcrypt`, on Debian, it requires `libltdl-dev` and
|
||||||
|
`build-essential`) to decrypt submissions (see
|
||||||
|
https://metacpan.org/pod/Mcrypt);
|
||||||
|
|
||||||
|
##### Files distribution
|
||||||
|
|
||||||
|
You need to manually place challenge given files in the tree. To avoid path
|
||||||
|
guessing, files path are hashed. To generate hashed paths, use the script
|
||||||
|
`gen_hash_link_files.sh`:
|
||||||
|
|
||||||
|
mkdir $TO
|
||||||
|
./gen_hash_link_files.sh FROM TO
|
||||||
|
|
||||||
|
Where `FROM` is the directory with the orignal tree and `TO` the directory
|
||||||
|
where placed symlink.
|
||||||
|
|
||||||
|
##### Firewall rules
|
||||||
|
|
||||||
|
This machine shouldn't have any network connection, except outgoing one to the
|
||||||
|
frontend for synchronization.
|
||||||
|
|
||||||
|
##### Others setups
|
||||||
|
|
||||||
|
Indicate in `/etc/hosts.conf` IP(s) of the frontend.
|
||||||
|
|
||||||
|
|
||||||
|
### Run
|
||||||
|
|
||||||
|
Two scripts are available, depending if directories synchronization has to be
|
||||||
|
made or not.
|
||||||
|
|
||||||
|
You don't need to handle synchronization if it's done by a separate container
|
||||||
|
or if frontend is linked to backend.
|
||||||
|
|
||||||
|
The `launch.sh` and `launch_local.sh` scripts do all backend stuff for you:
|
||||||
|
synchronization with frontend (only `launch.sh`), submission checking and
|
||||||
|
smart static pages regeneration.
|
||||||
|
|
||||||
|
|
||||||
|
#### Frontend
|
||||||
|
|
||||||
|
Keep in mind that this is the machine exposed to participant.
|
||||||
|
|
||||||
|
##### Docker containers
|
||||||
|
|
||||||
|
1. Generate the synchronization SSH key on the backend:
|
||||||
|
|
||||||
|
```
|
||||||
|
sudo su -c "ssh-keygen -t rsa -b 8192 -N '' -f /var/www/fic_server/.ssh/id_rsa" synchro
|
||||||
|
```
|
||||||
|
|
||||||
|
2. Copy `~synchro/.ssh/id_rsa.pub` into `front_synchro/authorized_keys` file.
|
||||||
|
|
||||||
|
3. Run the `front_synchro` container:
|
||||||
|
|
||||||
|
```
|
||||||
|
docker build -t synchro front_synchro/
|
||||||
|
docker run -d --name fsync -p 2242:22 synchro
|
||||||
|
```
|
||||||
|
|
||||||
|
4. Run the backend.
|
||||||
|
|
||||||
|
A first synchronization have to be made before the next step. This
|
||||||
|
synchronization will copy the frontend private key and various required
|
||||||
|
certificates.
|
||||||
|
|
||||||
|
5. Run the frontend container:
|
||||||
|
|
||||||
|
```
|
||||||
|
docker build -t frontend front/
|
||||||
|
docker run --rm -it -p 80:80 -p 443:443 --volumes-from fsync frontend
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
##### Requirements
|
||||||
|
|
||||||
|
* `nginx` with those modules: `aio` (for fast delivery of huge
|
||||||
|
content), `fastcgi`, `rewrite`, `ssl`;
|
||||||
|
* `php-fpm` with `mcrypt` module (for submission encryption);
|
||||||
|
|
||||||
|
##### Firewall rules
|
||||||
|
|
||||||
|
Expose to participants only 80 and 443 ports.
|
||||||
|
|
||||||
|
Expose on synchronization interface the 22 port, used for synchronization and
|
||||||
|
administration purpose from backend.
|
||||||
|
|
||||||
|
DROP **has to be** the default rule for INPUT, FORWARD and OUTPUT chains; use
|
||||||
|
CONNTRACK states.
|
||||||
|
|
||||||
|
|
||||||
|
### History
|
||||||
|
|
||||||
|
#### FIC2014
|
||||||
|
|
||||||
|
Two machines (DC7900: Core 2 Quad) were used : one for backend (Deimos) and one
|
||||||
|
for frontend (Phobos). They ran a GNU/Linux Gentoo Hardened with custom 3.2
|
||||||
|
kernel without module loading, unused and unecessary components and with all
|
||||||
|
GrSecurity features activated.
|
||||||
|
|
||||||
|
Each machine was two network interfaces: one was used to permit to the backend
|
||||||
|
machine to connect to the frontend (over IPv6). The second interface on the
|
||||||
|
backend was used for administration purpose (with a laptop not connected to
|
||||||
|
Internet). The second interface on the frontend was used to provide network
|
||||||
|
connectivity to participants.
|
||||||
|
|
||||||
|
Both frontend and backend were 2 500GB hard-drives with software RAID1. The
|
||||||
|
whole logical RAID disk was LUKS encrypted using Serpent algorithm.
|
||||||
|
|
||||||
|
|
||||||
|
The D Day
|
||||||
|
---------
|
||||||
|
|
||||||
|
### Interact with the scheduler
|
||||||
|
|
||||||
|
When you launch `launch.sh` or `launch_local.sh` script, a socket is open at
|
||||||
|
`/tmp/scheduler.sock`. Use `perl comm-socket.pl /tmp/scheduler.sock` to connect
|
||||||
|
to the scheduler. Consult `gen_site.pl` manual (`perldoc gen_site.pl`) for list
|
||||||
|
of available instructions.
|
||||||
|
|
||||||
|
### More
|
||||||
|
|
||||||
|
TODO
|
||||||
|
|
|
||||||
28
TODO
Normal file
28
TODO
Normal file
|
|
@ -0,0 +1,28 @@
|
||||||
|
* Lien sur la pages d'accueil sans /
|
||||||
|
* HTML/CSS/Templates
|
||||||
|
** TODO Mettre à jour les logos
|
||||||
|
** TODO trop de thèmes dans l'interface d'admin => menu
|
||||||
|
** TODO avoir la page avec le résumé
|
||||||
|
* PHP
|
||||||
|
** TODO les mauvaises pages sont generees (pas debug et non liens prod)
|
||||||
|
** TODO sur le frontend, en mode productoin, il va chercher les pages de debug
|
||||||
|
** TODO numéro des exercices
|
||||||
|
** TODO pouvoir désactiver les équipes qui ne participent pas (pour accélérer la génération)
|
||||||
|
** TODO ajouter des teams sans passer par l'import XML
|
||||||
|
** TODO page de statistiques
|
||||||
|
select count(*) as t, id_exercice, name from solved S inner join exercices E ON E.id = S.id_exercice INNER JOIN themes T ON E.id_theme = T.id group by id_exercice order by t DESC;
|
||||||
|
select id_team, COUNT(*) AS t from exercice_tries group by id_team ORDER BY t;
|
||||||
|
** Admin
|
||||||
|
*** TODO valider les documents avec la DTD à l'import
|
||||||
|
*** TODO upload/MAJ de fichiers depuis l'interface d'admin?
|
||||||
|
*** TODO lors de l'import, vérifier que les ID existent => afficher les erreurs MySQL
|
||||||
|
* Perl/shell
|
||||||
|
** TODO Gerer les espaces dans les fichiers (gen_hash_file plante)
|
||||||
|
** TODO Couleur l'output de check.pl
|
||||||
|
** TODO Pouvoir regénérer une série d'exercices pour toutes les teams
|
||||||
|
* Security
|
||||||
|
** TODO Quand est généré la CRL ?
|
||||||
|
* Other
|
||||||
|
** TODO docker pour monitorer
|
||||||
|
** TODO versionner la DTD et la doc associée
|
||||||
|
** TODO image dans la description des XML
|
||||||
5
admin/.gitignore
vendored
5
admin/.gitignore
vendored
|
|
@ -1,5 +0,0 @@
|
||||||
admin
|
|
||||||
fic.db
|
|
||||||
PKI/
|
|
||||||
FILES/
|
|
||||||
static/full_import_report.json
|
|
||||||
|
|
@ -1,479 +0,0 @@
|
||||||
package api
|
|
||||||
|
|
||||||
import (
|
|
||||||
"crypto/rand"
|
|
||||||
"crypto/sha1"
|
|
||||||
"crypto/x509"
|
|
||||||
"crypto/x509/pkix"
|
|
||||||
"encoding/base32"
|
|
||||||
"encoding/base64"
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"io/ioutil"
|
|
||||||
"log"
|
|
||||||
"math"
|
|
||||||
"math/big"
|
|
||||||
"net/http"
|
|
||||||
"os"
|
|
||||||
"path"
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"srs.epita.fr/fic-server/admin/pki"
|
|
||||||
"srs.epita.fr/fic-server/libfic"
|
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
|
||||||
)
|
|
||||||
|
|
||||||
var TeamsDir string
|
|
||||||
|
|
||||||
func declareCertificateRoutes(router *gin.RouterGroup) {
|
|
||||||
router.GET("/htpasswd", func(c *gin.Context) {
|
|
||||||
ret, err := genHtpasswd(true)
|
|
||||||
if err != nil {
|
|
||||||
c.AbortWithError(http.StatusInternalServerError, err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
c.String(http.StatusOK, ret)
|
|
||||||
})
|
|
||||||
router.POST("/htpasswd", func(c *gin.Context) {
|
|
||||||
if htpasswd, err := genHtpasswd(true); err != nil {
|
|
||||||
log.Println("Unable to generate htpasswd:", err)
|
|
||||||
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": err.Error()})
|
|
||||||
return
|
|
||||||
} else if err := ioutil.WriteFile(path.Join(pki.PKIDir, "shared", "ficpasswd"), []byte(htpasswd), 0644); err != nil {
|
|
||||||
log.Println("Unable to write htpasswd:", err)
|
|
||||||
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": err.Error()})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
c.AbortWithStatus(http.StatusOK)
|
|
||||||
})
|
|
||||||
router.DELETE("/htpasswd", func(c *gin.Context) {
|
|
||||||
if err := os.Remove(path.Join(pki.PKIDir, "shared", "ficpasswd")); err != nil {
|
|
||||||
log.Println("Unable to remove htpasswd:", err)
|
|
||||||
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": err.Error()})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
c.AbortWithStatus(http.StatusOK)
|
|
||||||
})
|
|
||||||
router.GET("/htpasswd.apr1", func(c *gin.Context) {
|
|
||||||
ret, err := genHtpasswd(false)
|
|
||||||
if err != nil {
|
|
||||||
c.AbortWithError(http.StatusInternalServerError, err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
c.String(http.StatusOK, ret)
|
|
||||||
})
|
|
||||||
router.GET("/ca", infoCA)
|
|
||||||
router.GET("/ca.pem", getCAPEM)
|
|
||||||
router.POST("/ca/new", func(c *gin.Context) {
|
|
||||||
var upki PKISettings
|
|
||||||
err := c.ShouldBindJSON(&upki)
|
|
||||||
if err != nil {
|
|
||||||
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"errmsg": err.Error()})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := pki.GenerateCA(upki.NotBefore, upki.NotAfter); err != nil {
|
|
||||||
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": err.Error()})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
c.JSON(http.StatusCreated, true)
|
|
||||||
})
|
|
||||||
|
|
||||||
router.GET("/certs", getCertificates)
|
|
||||||
router.POST("/certs", generateClientCert)
|
|
||||||
router.DELETE("/certs", func(c *gin.Context) {
|
|
||||||
v, err := fic.ClearCertificates()
|
|
||||||
if err != nil {
|
|
||||||
log.Println("Unable to ClearCertificates:", err.Error())
|
|
||||||
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": err.Error()})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
c.JSON(http.StatusOK, v)
|
|
||||||
})
|
|
||||||
|
|
||||||
apiCertificatesRoutes := router.Group("/certs/:certid")
|
|
||||||
apiCertificatesRoutes.Use(CertificateHandler)
|
|
||||||
apiCertificatesRoutes.HEAD("", getTeamP12File)
|
|
||||||
apiCertificatesRoutes.GET("", getTeamP12File)
|
|
||||||
apiCertificatesRoutes.PUT("", updateCertificateAssociation)
|
|
||||||
apiCertificatesRoutes.DELETE("", func(c *gin.Context) {
|
|
||||||
cert := c.MustGet("cert").(*fic.Certificate)
|
|
||||||
|
|
||||||
v, err := cert.Revoke()
|
|
||||||
if err != nil {
|
|
||||||
log.Println("Unable to Revoke:", err.Error())
|
|
||||||
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": err.Error()})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
c.JSON(http.StatusOK, v)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func declareTeamCertificateRoutes(router *gin.RouterGroup) {
|
|
||||||
router.GET("/certificates", func(c *gin.Context) {
|
|
||||||
team := c.MustGet("team").(*fic.Team)
|
|
||||||
|
|
||||||
if serials, err := pki.GetTeamSerials(TeamsDir, team.Id); err != nil {
|
|
||||||
log.Println("Unable to GetTeamSerials:", err.Error())
|
|
||||||
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": err.Error()})
|
|
||||||
return
|
|
||||||
} else {
|
|
||||||
var certs []CertExported
|
|
||||||
for _, serial := range serials {
|
|
||||||
if cert, err := fic.GetCertificate(serial); err == nil {
|
|
||||||
certs = append(certs, CertExported{fmt.Sprintf("%0[2]*[1]X", cert.Id, int(math.Ceil(math.Log2(float64(cert.Id))/8)*2)), cert.Creation, cert.Password, &team.Id, cert.Revoked})
|
|
||||||
} else {
|
|
||||||
log.Println("Unable to get back certificate, whereas an association exists on disk: ", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
c.JSON(http.StatusOK, certs)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
router.GET("/associations", func(c *gin.Context) {
|
|
||||||
team := c.MustGet("team").(*fic.Team)
|
|
||||||
|
|
||||||
assocs, err := pki.GetTeamAssociations(TeamsDir, team.Id)
|
|
||||||
if err != nil {
|
|
||||||
log.Println("Unable to GetTeamAssociations:", err.Error())
|
|
||||||
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": err.Error()})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
c.JSON(http.StatusOK, assocs)
|
|
||||||
})
|
|
||||||
|
|
||||||
apiTeamAssociationsRoutes := router.Group("/associations/:assoc")
|
|
||||||
apiTeamAssociationsRoutes.POST("", func(c *gin.Context) {
|
|
||||||
team := c.MustGet("team").(*fic.Team)
|
|
||||||
|
|
||||||
if err := os.Symlink(fmt.Sprintf("%d", team.Id), path.Join(TeamsDir, c.Params.ByName("assoc"))); err != nil {
|
|
||||||
log.Println("Unable to create association symlink:", err.Error())
|
|
||||||
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": fmt.Sprintf("Unable to create association symlink: %s", err.Error())})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
c.JSON(http.StatusOK, c.Params.ByName("assoc"))
|
|
||||||
})
|
|
||||||
apiTeamAssociationsRoutes.DELETE("", func(c *gin.Context) {
|
|
||||||
err := pki.DeleteTeamAssociation(TeamsDir, c.Params.ByName("assoc"))
|
|
||||||
if err != nil {
|
|
||||||
log.Printf("Unable to DeleteTeamAssociation(%s): %s", c.Params.ByName("assoc"), err.Error())
|
|
||||||
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": fmt.Sprintf("Unable to delete association symlink: %s", err.Error())})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
c.JSON(http.StatusOK, nil)
|
|
||||||
})
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
func CertificateHandler(c *gin.Context) {
|
|
||||||
var certid uint64
|
|
||||||
var err error
|
|
||||||
|
|
||||||
cid := strings.TrimSuffix(string(c.Params.ByName("certid")), ".p12")
|
|
||||||
if certid, err = strconv.ParseUint(cid, 10, 64); err != nil {
|
|
||||||
if certid, err = strconv.ParseUint(cid, 16, 64); err != nil {
|
|
||||||
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"errmsg": "Invalid certficate identifier"})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
cert, err := fic.GetCertificate(certid)
|
|
||||||
if err != nil {
|
|
||||||
c.AbortWithStatusJSON(http.StatusNotFound, gin.H{"errmsg": "Certificate not found"})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
c.Set("cert", cert)
|
|
||||||
|
|
||||||
c.Next()
|
|
||||||
}
|
|
||||||
|
|
||||||
func genHtpasswd(ssha bool) (ret string, err error) {
|
|
||||||
var teams []*fic.Team
|
|
||||||
teams, err = fic.GetTeams()
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, team := range teams {
|
|
||||||
var serials []uint64
|
|
||||||
serials, err = pki.GetTeamSerials(TeamsDir, team.Id)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(serials) == 0 {
|
|
||||||
// Don't include teams that don't have associated certificates
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, serial := range serials {
|
|
||||||
var cert *fic.Certificate
|
|
||||||
cert, err = fic.GetCertificate(serial)
|
|
||||||
if err != nil {
|
|
||||||
// Ignore invalid/incorrect/non-existant certificates
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
if cert.Revoked != nil {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
salt := make([]byte, 5)
|
|
||||||
if _, err = rand.Read(salt); err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if ssha {
|
|
||||||
hash := sha1.New()
|
|
||||||
hash.Write([]byte(cert.Password))
|
|
||||||
hash.Write([]byte(salt))
|
|
||||||
|
|
||||||
passwdline := fmt.Sprintf(":{SSHA}%s\n", base64.StdEncoding.EncodeToString(append(hash.Sum(nil), salt...)))
|
|
||||||
|
|
||||||
ret += strings.ToLower(team.Name) + passwdline
|
|
||||||
ret += fmt.Sprintf("%0[2]*[1]x", cert.Id, int(math.Ceil(math.Log2(float64(cert.Id))/8)*2)) + passwdline
|
|
||||||
ret += fmt.Sprintf("%0[2]*[1]X", cert.Id, int(math.Ceil(math.Log2(float64(cert.Id))/8)*2)) + passwdline
|
|
||||||
teamAssociations, _ := pki.GetTeamAssociations(TeamsDir, team.Id)
|
|
||||||
log.Println(path.Join(TeamsDir, fmt.Sprintf("%d", team.Id)), teamAssociations)
|
|
||||||
for _, ta := range teamAssociations {
|
|
||||||
ret += strings.Replace(ta, ":", "", -1) + passwdline
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
salt32 := base32.StdEncoding.EncodeToString(salt)
|
|
||||||
ret += fmt.Sprintf(
|
|
||||||
"%s:$apr1$%s$%s\n",
|
|
||||||
strings.ToLower(team.Name),
|
|
||||||
salt32,
|
|
||||||
fic.Apr1Md5(cert.Password, salt32),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
type PKISettings struct {
|
|
||||||
Version int `json:"version"`
|
|
||||||
SerialNumber *big.Int `json:"serialnumber"`
|
|
||||||
Issuer pkix.Name `json:"issuer"`
|
|
||||||
Subject pkix.Name `json:"subject"`
|
|
||||||
NotBefore time.Time `json:"notbefore"`
|
|
||||||
NotAfter time.Time `json:"notafter"`
|
|
||||||
SignatureAlgorithm x509.SignatureAlgorithm `json:"signatureAlgorithm,"`
|
|
||||||
PublicKeyAlgorithm x509.PublicKeyAlgorithm `json:"publicKeyAlgorithm"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func infoCA(c *gin.Context) {
|
|
||||||
_, cacert, err := pki.LoadCA()
|
|
||||||
if err != nil {
|
|
||||||
c.AbortWithStatusJSON(http.StatusNotFound, gin.H{"errmsg": "CA not found"})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
c.JSON(http.StatusOK, PKISettings{
|
|
||||||
Version: cacert.Version,
|
|
||||||
SerialNumber: cacert.SerialNumber,
|
|
||||||
Issuer: cacert.Issuer,
|
|
||||||
Subject: cacert.Subject,
|
|
||||||
NotBefore: cacert.NotBefore,
|
|
||||||
NotAfter: cacert.NotAfter,
|
|
||||||
SignatureAlgorithm: cacert.SignatureAlgorithm,
|
|
||||||
PublicKeyAlgorithm: cacert.PublicKeyAlgorithm,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func getCAPEM(c *gin.Context) {
|
|
||||||
if _, err := os.Stat(pki.CACertPath()); os.IsNotExist(err) {
|
|
||||||
c.AbortWithStatusJSON(http.StatusNotFound, gin.H{"errmsg": "Unable to locate the CA root certificate. Have you generated it?"})
|
|
||||||
return
|
|
||||||
} else if fd, err := os.Open(pki.CACertPath()); err != nil {
|
|
||||||
log.Println("Unable to open CA root certificate:", err)
|
|
||||||
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": err.Error()})
|
|
||||||
return
|
|
||||||
} else {
|
|
||||||
defer fd.Close()
|
|
||||||
|
|
||||||
cnt, err := ioutil.ReadAll(fd)
|
|
||||||
if err != nil {
|
|
||||||
log.Println("Unable to read CA root certificate:", err)
|
|
||||||
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": err.Error()})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
c.String(http.StatusOK, string(cnt))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func getTeamP12File(c *gin.Context) {
|
|
||||||
cert := c.MustGet("cert").(*fic.Certificate)
|
|
||||||
|
|
||||||
// Create p12 if necessary
|
|
||||||
if _, err := os.Stat(pki.ClientP12Path(cert.Id)); os.IsNotExist(err) {
|
|
||||||
if err := pki.WriteP12(cert.Id, cert.Password); err != nil {
|
|
||||||
log.Println("Unable to WriteP12:", err.Error())
|
|
||||||
c.AbortWithError(http.StatusInternalServerError, err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if _, err := os.Stat(pki.ClientP12Path(cert.Id)); os.IsNotExist(err) {
|
|
||||||
log.Println("Unable to compute ClientP12Path:", err.Error())
|
|
||||||
c.AbortWithError(http.StatusInternalServerError, errors.New("Unable to locate the p12. Have you generated it?"))
|
|
||||||
return
|
|
||||||
} else if fd, err := os.Open(pki.ClientP12Path(cert.Id)); err != nil {
|
|
||||||
log.Println("Unable to open ClientP12Path:", err.Error())
|
|
||||||
c.AbortWithError(http.StatusInternalServerError, fmt.Errorf("Unable to open the p12: %w", err))
|
|
||||||
return
|
|
||||||
} else {
|
|
||||||
defer fd.Close()
|
|
||||||
|
|
||||||
data, err := ioutil.ReadAll(fd)
|
|
||||||
if err != nil {
|
|
||||||
log.Println("Unable to open ClientP12Path:", err.Error())
|
|
||||||
c.AbortWithError(http.StatusInternalServerError, fmt.Errorf("Unable to open the p12: %w", err))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
c.Data(http.StatusOK, "application/x-pkcs12", data)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func generateClientCert(c *gin.Context) {
|
|
||||||
// First, generate a new, unique, serial
|
|
||||||
var serial_gen [8]byte
|
|
||||||
if _, err := rand.Read(serial_gen[:]); err != nil {
|
|
||||||
log.Println("Unable to read enough entropy to generate client certificate:", err)
|
|
||||||
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": "Unable to read enough entropy"})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
for fic.ExistingCertSerial(serial_gen) {
|
|
||||||
if _, err := rand.Read(serial_gen[:]); err != nil {
|
|
||||||
log.Println("Unable to read enough entropy to generate client certificate:", err)
|
|
||||||
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": "Unable to read enough entropy"})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var serial_b big.Int
|
|
||||||
serial_b.SetBytes(serial_gen[:])
|
|
||||||
serial := serial_b.Uint64()
|
|
||||||
|
|
||||||
// Let's pick a random password
|
|
||||||
password, err := fic.GeneratePassword()
|
|
||||||
if err != nil {
|
|
||||||
log.Println("Unable to generate password:", err)
|
|
||||||
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": "Unable to generate password: " + err.Error()})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Ok, now load CA
|
|
||||||
capriv, cacert, err := pki.LoadCA()
|
|
||||||
if err != nil {
|
|
||||||
log.Println("Unable to load the CA:", err)
|
|
||||||
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": "Unable to load the CA"})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Generate our privkey
|
|
||||||
if err := pki.GenerateClient(serial, cacert.NotBefore, cacert.NotAfter, &cacert, &capriv); err != nil {
|
|
||||||
log.Println("Unable to generate private key:", err)
|
|
||||||
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": "Unable to generate private key: " + err.Error()})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Save in DB
|
|
||||||
cert, err := fic.RegisterCertificate(serial, password)
|
|
||||||
if err != nil {
|
|
||||||
log.Println("Unable to register certificate:", err)
|
|
||||||
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": "Unable to register certificate."})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
c.JSON(http.StatusOK, CertExported{fmt.Sprintf("%0[2]*[1]X", cert.Id, int(math.Ceil(math.Log2(float64(cert.Id))/8)*2)), cert.Creation, cert.Password, nil, cert.Revoked})
|
|
||||||
}
|
|
||||||
|
|
||||||
type CertExported struct {
|
|
||||||
Id string `json:"id"`
|
|
||||||
Creation time.Time `json:"creation"`
|
|
||||||
Password string `json:"password,omitempty"`
|
|
||||||
IdTeam *int64 `json:"id_team"`
|
|
||||||
Revoked *time.Time `json:"revoked"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func getCertificates(c *gin.Context) {
|
|
||||||
certificates, err := fic.GetCertificates()
|
|
||||||
if err != nil {
|
|
||||||
log.Println("Unable to retrieve certificates list:", err)
|
|
||||||
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": "An error occurs during certificates retrieval."})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
ret := make([]CertExported, 0)
|
|
||||||
for _, cert := range certificates {
|
|
||||||
dstLinkPath := path.Join(TeamsDir, pki.GetCertificateAssociation(cert.Id))
|
|
||||||
|
|
||||||
var idTeam *int64 = nil
|
|
||||||
if lnk, err := os.Readlink(dstLinkPath); err == nil {
|
|
||||||
if tid, err := strconv.ParseInt(lnk, 10, 64); err == nil {
|
|
||||||
idTeam = &tid
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ret = append(ret, CertExported{fmt.Sprintf("%0[2]*[1]X", cert.Id, int(math.Ceil(math.Log2(float64(cert.Id))/8)*2)), cert.Creation, "", idTeam, cert.Revoked})
|
|
||||||
}
|
|
||||||
|
|
||||||
c.JSON(http.StatusOK, ret)
|
|
||||||
}
|
|
||||||
|
|
||||||
type CertUploaded struct {
|
|
||||||
Team *int64 `json:"id_team"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func updateCertificateAssociation(c *gin.Context) {
|
|
||||||
cert := c.MustGet("cert").(*fic.Certificate)
|
|
||||||
|
|
||||||
var uc CertUploaded
|
|
||||||
err := c.ShouldBindJSON(&uc)
|
|
||||||
if err != nil {
|
|
||||||
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"errmsg": err.Error()})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
dstLinkPath := path.Join(TeamsDir, pki.GetCertificateAssociation(cert.Id))
|
|
||||||
|
|
||||||
if uc.Team != nil {
|
|
||||||
srcLinkPath := fmt.Sprintf("%d", *uc.Team)
|
|
||||||
if err := os.Symlink(srcLinkPath, dstLinkPath); err != nil {
|
|
||||||
log.Println("Unable to create certificate symlink:", err.Error())
|
|
||||||
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": fmt.Sprintf("Unable to create certificate symlink: %s", err.Error())})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Mark team as active to ensure it'll be generated
|
|
||||||
if ut, err := fic.GetTeam(*uc.Team); err != nil {
|
|
||||||
log.Println("Unable to GetTeam:", err.Error())
|
|
||||||
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": "An error occurs during team retrieval."})
|
|
||||||
return
|
|
||||||
} else if !ut.Active {
|
|
||||||
ut.Active = true
|
|
||||||
_, err := ut.Update()
|
|
||||||
if err != nil {
|
|
||||||
log.Println("Unable to UpdateTeam after updateCertificateAssociation:", err.Error())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
os.Remove(dstLinkPath)
|
|
||||||
}
|
|
||||||
|
|
||||||
c.JSON(http.StatusOK, cert)
|
|
||||||
}
|
|
||||||
|
|
@ -1,499 +0,0 @@
|
||||||
package api
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
|
||||||
"io/ioutil"
|
|
||||||
"log"
|
|
||||||
"net/http"
|
|
||||||
"path"
|
|
||||||
"strconv"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"srs.epita.fr/fic-server/admin/generation"
|
|
||||||
"srs.epita.fr/fic-server/libfic"
|
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
|
||||||
)
|
|
||||||
|
|
||||||
func declareClaimsRoutes(router *gin.RouterGroup) {
|
|
||||||
// Tasks
|
|
||||||
router.GET("/claims", getClaims)
|
|
||||||
router.POST("/claims", newClaim)
|
|
||||||
router.DELETE("/claims", clearClaims)
|
|
||||||
|
|
||||||
apiClaimsRoutes := router.Group("/claims/:cid")
|
|
||||||
apiClaimsRoutes.Use(ClaimHandler)
|
|
||||||
apiClaimsRoutes.GET("", showClaim)
|
|
||||||
apiClaimsRoutes.PUT("", updateClaim)
|
|
||||||
apiClaimsRoutes.POST("", addClaimDescription)
|
|
||||||
apiClaimsRoutes.DELETE("", deleteClaim)
|
|
||||||
|
|
||||||
apiClaimsRoutes.GET("/last_update", getClaimLastUpdate)
|
|
||||||
apiClaimsRoutes.PUT("/descriptions", updateClaimDescription)
|
|
||||||
|
|
||||||
// Assignees
|
|
||||||
router.GET("/claims-assignees", getAssignees)
|
|
||||||
router.POST("/claims-assignees", newAssignee)
|
|
||||||
|
|
||||||
apiClaimAssigneesRoutes := router.Group("/claims-assignees/:aid")
|
|
||||||
apiClaimAssigneesRoutes.Use(ClaimAssigneeHandler)
|
|
||||||
router.GET("/claims-assignees/:aid", showClaimAssignee)
|
|
||||||
router.PUT("/claims-assignees/:aid", updateClaimAssignee)
|
|
||||||
router.DELETE("/claims-assignees/:aid", deleteClaimAssignee)
|
|
||||||
}
|
|
||||||
|
|
||||||
func declareExerciceClaimsRoutes(router *gin.RouterGroup) {
|
|
||||||
router.GET("/claims", getExerciceClaims)
|
|
||||||
}
|
|
||||||
|
|
||||||
func declareTeamClaimsRoutes(router *gin.RouterGroup) {
|
|
||||||
router.GET("/api/teams/:tid/issue.json", func(c *gin.Context) {
|
|
||||||
team := c.MustGet("team").(*fic.Team)
|
|
||||||
|
|
||||||
issues, err := team.MyIssueFile()
|
|
||||||
if err != nil {
|
|
||||||
log.Printf("Unable to MyIssueFile(tid=%d): %s", team.Id, err.Error())
|
|
||||||
c.JSON(http.StatusInternalServerError, gin.H{"errmsg": "Unable to generate issues.json."})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
c.JSON(http.StatusOK, issues)
|
|
||||||
})
|
|
||||||
|
|
||||||
router.GET("/claims", getTeamClaims)
|
|
||||||
}
|
|
||||||
|
|
||||||
func ClaimHandler(c *gin.Context) {
|
|
||||||
cid, err := strconv.ParseInt(string(c.Params.ByName("cid")), 10, 64)
|
|
||||||
if err != nil {
|
|
||||||
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"errmsg": "Invalid claim identifier"})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
claim, err := fic.GetClaim(cid)
|
|
||||||
if err != nil {
|
|
||||||
c.AbortWithStatusJSON(http.StatusNotFound, gin.H{"errmsg": "Requested claim not found"})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
c.Set("claim", claim)
|
|
||||||
|
|
||||||
c.Next()
|
|
||||||
}
|
|
||||||
|
|
||||||
func ClaimAssigneeHandler(c *gin.Context) {
|
|
||||||
aid, err := strconv.ParseInt(string(c.Params.ByName("aid")), 10, 64)
|
|
||||||
if err != nil {
|
|
||||||
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"errmsg": "Invalid claim assignee identifier"})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
assignee, err := fic.GetAssignee(aid)
|
|
||||||
if err != nil {
|
|
||||||
c.AbortWithStatusJSON(http.StatusNotFound, gin.H{"errmsg": "Requested claim-assignee not found"})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
c.Set("claim-assignee", assignee)
|
|
||||||
|
|
||||||
c.Next()
|
|
||||||
}
|
|
||||||
|
|
||||||
func getClaims(c *gin.Context) {
|
|
||||||
claims, err := fic.GetClaims()
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
log.Println("Unable to getClaims:", err.Error())
|
|
||||||
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": "An error occurs during claims retrieval."})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
c.JSON(http.StatusOK, claims)
|
|
||||||
}
|
|
||||||
|
|
||||||
func getTeamClaims(c *gin.Context) {
|
|
||||||
team := c.MustGet("team").(*fic.Team)
|
|
||||||
|
|
||||||
claims, err := team.GetClaims()
|
|
||||||
if err != nil {
|
|
||||||
log.Printf("Unable to GetClaims(tid=%d): %s", team.Id, err.Error())
|
|
||||||
c.JSON(http.StatusInternalServerError, gin.H{"errmsg": "Unable to retrieve claim list."})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
c.JSON(http.StatusOK, claims)
|
|
||||||
}
|
|
||||||
|
|
||||||
func getExerciceClaims(c *gin.Context) {
|
|
||||||
exercice := c.MustGet("exercice").(*fic.Exercice)
|
|
||||||
|
|
||||||
claims, err := exercice.GetClaims()
|
|
||||||
if err != nil {
|
|
||||||
log.Printf("Unable to GetClaims(eid=%d): %s", exercice.Id, err.Error())
|
|
||||||
c.JSON(http.StatusInternalServerError, gin.H{"errmsg": "Unable to retrieve claim list."})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
c.JSON(http.StatusOK, claims)
|
|
||||||
}
|
|
||||||
|
|
||||||
func getClaimLastUpdate(c *gin.Context) {
|
|
||||||
claim := c.MustGet("claim").(*fic.Claim)
|
|
||||||
|
|
||||||
v, err := claim.GetLastUpdate()
|
|
||||||
if err != nil {
|
|
||||||
log.Printf("Unable to GetLastUpdate: %s", err.Error())
|
|
||||||
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": "An error occurs during claim last update retrieval."})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
c.JSON(http.StatusOK, v)
|
|
||||||
}
|
|
||||||
|
|
||||||
type ClaimExported struct {
|
|
||||||
Id int64 `json:"id"`
|
|
||||||
Subject string `json:"subject"`
|
|
||||||
IdTeam *int64 `json:"id_team"`
|
|
||||||
Team *fic.Team `json:"team"`
|
|
||||||
IdExercice *int64 `json:"id_exercice"`
|
|
||||||
Exercice *fic.Exercice `json:"exercice"`
|
|
||||||
IdAssignee *int64 `json:"id_assignee"`
|
|
||||||
Assignee *fic.ClaimAssignee `json:"assignee"`
|
|
||||||
Creation time.Time `json:"creation"`
|
|
||||||
LastUpdate time.Time `json:"last_update"`
|
|
||||||
State string `json:"state"`
|
|
||||||
Priority string `json:"priority"`
|
|
||||||
Descriptions []*fic.ClaimDescription `json:"descriptions"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func showClaim(c *gin.Context) {
|
|
||||||
claim := c.MustGet("claim").(*fic.Claim)
|
|
||||||
|
|
||||||
var e ClaimExported
|
|
||||||
var err error
|
|
||||||
if e.Team, err = claim.GetTeam(); err != nil {
|
|
||||||
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"errmsg": fmt.Sprintf("Unable to find associated team: %s", err.Error())})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if e.Exercice, err = claim.GetExercice(); err != nil {
|
|
||||||
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"errmsg": fmt.Sprintf("Unable to find associated exercice: %s", err.Error())})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if e.Assignee, err = claim.GetAssignee(); err != nil {
|
|
||||||
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"errmsg": fmt.Sprintf("Unable to find associated assignee: %s", err.Error())})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if e.Descriptions, err = claim.GetDescriptions(); err != nil {
|
|
||||||
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"errmsg": fmt.Sprintf("Unable to find claim's descriptions: %s", err.Error())})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
e.LastUpdate = e.Creation
|
|
||||||
for _, d := range e.Descriptions {
|
|
||||||
if d.Date.After(e.LastUpdate) {
|
|
||||||
e.LastUpdate = d.Date
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
e.Id = claim.Id
|
|
||||||
e.IdAssignee = claim.IdAssignee
|
|
||||||
e.IdTeam = claim.IdTeam
|
|
||||||
e.IdExercice = claim.IdExercice
|
|
||||||
e.Subject = claim.Subject
|
|
||||||
e.Creation = claim.Creation
|
|
||||||
e.State = claim.State
|
|
||||||
e.Priority = claim.Priority
|
|
||||||
|
|
||||||
c.JSON(http.StatusOK, e)
|
|
||||||
}
|
|
||||||
|
|
||||||
type ClaimUploaded struct {
|
|
||||||
fic.Claim
|
|
||||||
Whoami *int64 `json:"whoami"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func newClaim(c *gin.Context) {
|
|
||||||
var uc ClaimUploaded
|
|
||||||
err := c.ShouldBindJSON(&uc)
|
|
||||||
if err != nil {
|
|
||||||
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"errmsg": err.Error()})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if uc.Subject == "" {
|
|
||||||
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"errmsg": "Claim's subject cannot be empty."})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
var t *fic.Team
|
|
||||||
if uc.IdTeam != nil {
|
|
||||||
if team, err := fic.GetTeam(*uc.IdTeam); err != nil {
|
|
||||||
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"errmsg": fmt.Sprintf("Unable to get associated team: %s", err.Error())})
|
|
||||||
return
|
|
||||||
} else {
|
|
||||||
t = team
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
t = nil
|
|
||||||
}
|
|
||||||
|
|
||||||
var e *fic.Exercice
|
|
||||||
if uc.IdExercice != nil {
|
|
||||||
if exercice, err := fic.GetExercice(*uc.IdExercice); err != nil {
|
|
||||||
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"errmsg": fmt.Sprintf("Unable to get associated exercice: %s", err.Error())})
|
|
||||||
return
|
|
||||||
} else {
|
|
||||||
e = exercice
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
e = nil
|
|
||||||
}
|
|
||||||
|
|
||||||
var a *fic.ClaimAssignee
|
|
||||||
if uc.IdAssignee != nil {
|
|
||||||
if assignee, err := fic.GetAssignee(*uc.IdAssignee); err != nil {
|
|
||||||
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"errmsg": fmt.Sprintf("Unable to get associated assignee: %s", err.Error())})
|
|
||||||
return
|
|
||||||
} else {
|
|
||||||
a = assignee
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
a = nil
|
|
||||||
}
|
|
||||||
|
|
||||||
if uc.Priority == "" {
|
|
||||||
uc.Priority = "medium"
|
|
||||||
}
|
|
||||||
|
|
||||||
claim, err := fic.NewClaim(uc.Subject, t, e, a, uc.Priority)
|
|
||||||
if err != nil {
|
|
||||||
log.Println("Unable to newClaim:", err.Error())
|
|
||||||
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": "Unable to register new claim"})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
c.JSON(http.StatusOK, claim)
|
|
||||||
}
|
|
||||||
|
|
||||||
func clearClaims(c *gin.Context) {
|
|
||||||
nb, err := fic.ClearClaims()
|
|
||||||
if err != nil {
|
|
||||||
log.Printf("Unable to clearClaims: %s", err.Error())
|
|
||||||
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": "An error occurs during claims clearing."})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
c.JSON(http.StatusOK, nb)
|
|
||||||
}
|
|
||||||
|
|
||||||
func generateTeamIssuesFile(team fic.Team) error {
|
|
||||||
if generation.GeneratorSocket == "" {
|
|
||||||
if my, err := team.MyIssueFile(); err != nil {
|
|
||||||
return fmt.Errorf("Unable to generate issue FILE (tid=%d): %w", team.Id, err)
|
|
||||||
} else if j, err := json.Marshal(my); err != nil {
|
|
||||||
return fmt.Errorf("Unable to encode issues' file JSON: %w", err)
|
|
||||||
} else if err = ioutil.WriteFile(path.Join(TeamsDir, fmt.Sprintf("%d", team.Id), "issues.json"), j, 0644); err != nil {
|
|
||||||
return fmt.Errorf("Unable to write issues' file: %w", err)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
resp, err := generation.PerformGeneration(fic.GenStruct{Type: fic.GenTeamIssues, TeamId: team.Id})
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer resp.Body.Close()
|
|
||||||
|
|
||||||
if resp.StatusCode != http.StatusOK {
|
|
||||||
v, _ := ioutil.ReadAll(resp.Body)
|
|
||||||
return fmt.Errorf("%s", string(v))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func addClaimDescription(c *gin.Context) {
|
|
||||||
claim := c.MustGet("claim").(*fic.Claim)
|
|
||||||
|
|
||||||
var ud fic.ClaimDescription
|
|
||||||
err := c.ShouldBindJSON(&ud)
|
|
||||||
if err != nil {
|
|
||||||
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"errmsg": err.Error()})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
assignee, err := fic.GetAssignee(ud.IdAssignee)
|
|
||||||
if err != nil {
|
|
||||||
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"errmsg": fmt.Sprintf("Unable to get associated assignee: %s", err.Error())})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
description, err := claim.AddDescription(ud.Content, assignee, ud.Publish)
|
|
||||||
if err != nil {
|
|
||||||
log.Println("Unable to addClaimDescription:", err.Error())
|
|
||||||
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": "Unable to add description"})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if team, _ := claim.GetTeam(); team != nil {
|
|
||||||
err = generateTeamIssuesFile(*team)
|
|
||||||
if err != nil {
|
|
||||||
log.Println("Unable to generateTeamIssuesFile after addClaimDescription:", err.Error())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
c.JSON(http.StatusOK, description)
|
|
||||||
}
|
|
||||||
|
|
||||||
func updateClaimDescription(c *gin.Context) {
|
|
||||||
claim := c.MustGet("claim").(*fic.Claim)
|
|
||||||
|
|
||||||
var ud fic.ClaimDescription
|
|
||||||
err := c.ShouldBindJSON(&ud)
|
|
||||||
if err != nil {
|
|
||||||
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"errmsg": err.Error()})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if _, err := ud.Update(); err != nil {
|
|
||||||
log.Println("Unable to updateClaimDescription:", err.Error())
|
|
||||||
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"errmsg": "An error occurs during claim description updating."})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if team, _ := claim.GetTeam(); team != nil {
|
|
||||||
err = generateTeamIssuesFile(*team)
|
|
||||||
if err != nil {
|
|
||||||
log.Println("Unable to generateTeamIssuesFile:", err.Error())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
c.JSON(http.StatusOK, ud)
|
|
||||||
}
|
|
||||||
|
|
||||||
func updateClaim(c *gin.Context) {
|
|
||||||
claim := c.MustGet("claim").(*fic.Claim)
|
|
||||||
|
|
||||||
var uc ClaimUploaded
|
|
||||||
err := c.ShouldBindJSON(&uc)
|
|
||||||
if err != nil {
|
|
||||||
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"errmsg": err.Error()})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
uc.Id = claim.Id
|
|
||||||
|
|
||||||
_, err = uc.Update()
|
|
||||||
if err != nil {
|
|
||||||
log.Printf("Unable to updateClaim: %s", err.Error())
|
|
||||||
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": "An error occurs during claim update."})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if claim.State != uc.State {
|
|
||||||
if uc.Whoami != nil {
|
|
||||||
if assignee, err := fic.GetAssignee(*uc.Whoami); err == nil {
|
|
||||||
claim.AddDescription(fmt.Sprintf("%s a changé l'état de la tâche vers %q (était %q).", assignee.Name, uc.State, claim.State), assignee, true)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if claim.IdAssignee != uc.IdAssignee {
|
|
||||||
if uc.Whoami != nil {
|
|
||||||
if whoami, err := fic.GetAssignee(*uc.Whoami); err == nil {
|
|
||||||
if uc.IdAssignee != nil {
|
|
||||||
if assignee, err := fic.GetAssignee(*uc.IdAssignee); err == nil {
|
|
||||||
if assignee.Id != whoami.Id {
|
|
||||||
claim.AddDescription(fmt.Sprintf("%s a assigné la tâche à %s.", whoami.Name, assignee.Name), whoami, false)
|
|
||||||
} else {
|
|
||||||
claim.AddDescription(fmt.Sprintf("%s s'est assigné la tâche.", assignee.Name), whoami, false)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
claim.AddDescription(fmt.Sprintf("%s a retiré l'attribution de la tâche.", whoami.Name), whoami, false)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if team, _ := claim.GetTeam(); team != nil {
|
|
||||||
err = generateTeamIssuesFile(*team)
|
|
||||||
}
|
|
||||||
|
|
||||||
c.JSON(http.StatusOK, uc)
|
|
||||||
}
|
|
||||||
|
|
||||||
func deleteClaim(c *gin.Context) {
|
|
||||||
claim := c.MustGet("claim").(*fic.Claim)
|
|
||||||
|
|
||||||
if nb, err := claim.Delete(); err != nil {
|
|
||||||
log.Println("Unable to deleteClaim:", err.Error())
|
|
||||||
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": "An error occurs during claim deletion."})
|
|
||||||
return
|
|
||||||
} else {
|
|
||||||
c.JSON(http.StatusOK, nb)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func getAssignees(c *gin.Context) {
|
|
||||||
assignees, err := fic.GetAssignees()
|
|
||||||
if err != nil {
|
|
||||||
log.Println("Unable to getAssignees:", err.Error())
|
|
||||||
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": "An error occurs during assignees retrieval."})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
c.JSON(http.StatusOK, assignees)
|
|
||||||
}
|
|
||||||
|
|
||||||
func showClaimAssignee(c *gin.Context) {
|
|
||||||
c.JSON(http.StatusOK, c.MustGet("claim-assignee").(*fic.ClaimAssignee))
|
|
||||||
}
|
|
||||||
func newAssignee(c *gin.Context) {
|
|
||||||
var ua fic.ClaimAssignee
|
|
||||||
err := c.ShouldBindJSON(&ua)
|
|
||||||
if err != nil {
|
|
||||||
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"errmsg": err.Error()})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
assignee, err := fic.NewClaimAssignee(ua.Name)
|
|
||||||
if err != nil {
|
|
||||||
log.Println("Unable to newAssignee:", err.Error())
|
|
||||||
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": "An error occurs during assignee creation."})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
c.JSON(http.StatusOK, assignee)
|
|
||||||
}
|
|
||||||
|
|
||||||
func updateClaimAssignee(c *gin.Context) {
|
|
||||||
assignee := c.MustGet("claim-assignee").(*fic.ClaimAssignee)
|
|
||||||
|
|
||||||
var ua fic.ClaimAssignee
|
|
||||||
err := c.ShouldBindJSON(&ua)
|
|
||||||
if err != nil {
|
|
||||||
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"errmsg": err.Error()})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
ua.Id = assignee.Id
|
|
||||||
|
|
||||||
if _, err := ua.Update(); err != nil {
|
|
||||||
log.Println("Unable to updateClaimAssignee:", err.Error())
|
|
||||||
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": "An error occurs during claim assignee update."})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
c.JSON(http.StatusOK, ua)
|
|
||||||
}
|
|
||||||
|
|
||||||
func deleteClaimAssignee(c *gin.Context) {
|
|
||||||
assignee := c.MustGet("claim-assignee").(*fic.ClaimAssignee)
|
|
||||||
|
|
||||||
if _, err := assignee.Delete(); err != nil {
|
|
||||||
log.Println("Unable to deleteClaimAssignee:", err.Error())
|
|
||||||
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": fmt.Sprintf("An error occurs during claim assignee deletion: %s", err.Error())})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
c.JSON(http.StatusOK, true)
|
|
||||||
}
|
|
||||||
|
|
@ -1,154 +0,0 @@
|
||||||
package api
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/json"
|
|
||||||
"io/ioutil"
|
|
||||||
"log"
|
|
||||||
"net/http"
|
|
||||||
"path"
|
|
||||||
"strconv"
|
|
||||||
|
|
||||||
"srs.epita.fr/fic-server/libfic"
|
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
|
||||||
)
|
|
||||||
|
|
||||||
func declareEventsRoutes(router *gin.RouterGroup) {
|
|
||||||
router.GET("/events", getEvents)
|
|
||||||
router.GET("/events.json", getLastEvents)
|
|
||||||
router.POST("/events", newEvent)
|
|
||||||
router.DELETE("/events", clearEvents)
|
|
||||||
|
|
||||||
apiEventsRoutes := router.Group("/events/:evid")
|
|
||||||
apiEventsRoutes.Use(EventHandler)
|
|
||||||
apiEventsRoutes.GET("", showEvent)
|
|
||||||
apiEventsRoutes.PUT("", updateEvent)
|
|
||||||
apiEventsRoutes.DELETE("", deleteEvent)
|
|
||||||
}
|
|
||||||
|
|
||||||
func EventHandler(c *gin.Context) {
|
|
||||||
evid, err := strconv.ParseInt(string(c.Params.ByName("evid")), 10, 64)
|
|
||||||
if err != nil {
|
|
||||||
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"errmsg": "Invalid event identifier"})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
event, err := fic.GetEvent(evid)
|
|
||||||
if err != nil {
|
|
||||||
c.AbortWithStatusJSON(http.StatusNotFound, gin.H{"errmsg": "Event not found"})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
c.Set("event", event)
|
|
||||||
|
|
||||||
c.Next()
|
|
||||||
}
|
|
||||||
|
|
||||||
func genEventsFile() error {
|
|
||||||
if evts, err := fic.GetLastEvents(); err != nil {
|
|
||||||
return err
|
|
||||||
} else if j, err := json.Marshal(evts); err != nil {
|
|
||||||
return err
|
|
||||||
} else if err := ioutil.WriteFile(path.Join(TeamsDir, "events.json"), j, 0666); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func getEvents(c *gin.Context) {
|
|
||||||
evts, err := fic.GetEvents()
|
|
||||||
if err != nil {
|
|
||||||
log.Println("Unable to GetEvents:", err.Error())
|
|
||||||
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": "Unable to retrieve events list"})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
c.JSON(http.StatusOK, evts)
|
|
||||||
}
|
|
||||||
|
|
||||||
func getLastEvents(c *gin.Context) {
|
|
||||||
evts, err := fic.GetLastEvents()
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
log.Println("Unable to GetLastEvents:", err.Error())
|
|
||||||
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": "Unable to retrieve last events list"})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
c.JSON(http.StatusOK, evts)
|
|
||||||
}
|
|
||||||
|
|
||||||
func newEvent(c *gin.Context) {
|
|
||||||
var ue fic.Event
|
|
||||||
err := c.ShouldBindJSON(&ue)
|
|
||||||
if err != nil {
|
|
||||||
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"errmsg": err.Error()})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
event, err := fic.NewEvent(ue.Text, ue.Kind)
|
|
||||||
if err != nil {
|
|
||||||
log.Printf("Unable to newEvent: %s", err.Error())
|
|
||||||
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": "An error occurs during event creation."})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
genEventsFile()
|
|
||||||
|
|
||||||
c.JSON(http.StatusOK, event)
|
|
||||||
}
|
|
||||||
|
|
||||||
func clearEvents(c *gin.Context) {
|
|
||||||
nb, err := fic.ClearEvents()
|
|
||||||
if err != nil {
|
|
||||||
log.Printf("Unable to clearEvent: %s", err.Error())
|
|
||||||
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": "An error occurs during event clearing."})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
c.JSON(http.StatusOK, nb)
|
|
||||||
}
|
|
||||||
|
|
||||||
func showEvent(c *gin.Context) {
|
|
||||||
event := c.MustGet("event").(*fic.Event)
|
|
||||||
c.JSON(http.StatusOK, event)
|
|
||||||
}
|
|
||||||
|
|
||||||
func updateEvent(c *gin.Context) {
|
|
||||||
event := c.MustGet("event").(*fic.Event)
|
|
||||||
|
|
||||||
var ue fic.Event
|
|
||||||
err := c.ShouldBindJSON(&ue)
|
|
||||||
if err != nil {
|
|
||||||
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"errmsg": err.Error()})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
ue.Id = event.Id
|
|
||||||
|
|
||||||
if _, err := ue.Update(); err != nil {
|
|
||||||
log.Printf("Unable to updateEvent: %s", err.Error())
|
|
||||||
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": "An error occurs during event update."})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
genEventsFile()
|
|
||||||
|
|
||||||
c.JSON(http.StatusOK, ue)
|
|
||||||
}
|
|
||||||
|
|
||||||
func deleteEvent(c *gin.Context) {
|
|
||||||
event := c.MustGet("event").(*fic.Event)
|
|
||||||
|
|
||||||
_, err := event.Delete()
|
|
||||||
if err != nil {
|
|
||||||
log.Printf("Unable to deleteEvent: %s", err.Error())
|
|
||||||
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": "An error occurs during event deletion."})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
genEventsFile()
|
|
||||||
|
|
||||||
c.JSON(http.StatusOK, true)
|
|
||||||
}
|
|
||||||
File diff suppressed because it is too large
Load diff
|
|
@ -1,126 +0,0 @@
|
||||||
package api
|
|
||||||
|
|
||||||
import (
|
|
||||||
"archive/zip"
|
|
||||||
"encoding/json"
|
|
||||||
"io"
|
|
||||||
"log"
|
|
||||||
"net/http"
|
|
||||||
"path"
|
|
||||||
|
|
||||||
"srs.epita.fr/fic-server/admin/sync"
|
|
||||||
"srs.epita.fr/fic-server/libfic"
|
|
||||||
"srs.epita.fr/fic-server/settings"
|
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
|
||||||
)
|
|
||||||
|
|
||||||
func declareExportRoutes(router *gin.RouterGroup) {
|
|
||||||
router.GET("/archive.zip", func(c *gin.Context) {
|
|
||||||
challengeinfo, err := GetChallengeInfo()
|
|
||||||
if err != nil {
|
|
||||||
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": err.Error()})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
my, err := fic.MyJSONTeam(nil, true)
|
|
||||||
if err != nil {
|
|
||||||
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": err.Error()})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
s, err := settings.ReadSettings(path.Join(settings.SettingsDir, settings.SettingsFile))
|
|
||||||
if err != nil {
|
|
||||||
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": err.Error()})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
s.End = nil
|
|
||||||
s.NextChangeTime = nil
|
|
||||||
s.DelegatedQA = []string{}
|
|
||||||
|
|
||||||
teams, err := fic.ExportTeams(false)
|
|
||||||
if err != nil {
|
|
||||||
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": err.Error()})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
themes, err := fic.ExportThemes()
|
|
||||||
if err != nil {
|
|
||||||
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": err.Error()})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
c.Writer.WriteHeader(http.StatusOK)
|
|
||||||
c.Header("Content-Disposition", "attachment; filename=archive.zip")
|
|
||||||
c.Header("Content-Type", "application/zip")
|
|
||||||
|
|
||||||
w := zip.NewWriter(c.Writer)
|
|
||||||
|
|
||||||
// challenge.json
|
|
||||||
f, err := w.Create("challenge.json")
|
|
||||||
if err == nil {
|
|
||||||
json.NewEncoder(f).Encode(challengeinfo)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Include partners' logos from challenge.json
|
|
||||||
if sync.GlobalImporter != nil {
|
|
||||||
if len(challengeinfo.MainLogo) > 0 {
|
|
||||||
for _, logo := range challengeinfo.MainLogo {
|
|
||||||
fd, closer, err := sync.OpenOrGetFile(sync.GlobalImporter, logo)
|
|
||||||
if err != nil {
|
|
||||||
log.Printf("Unable to archive main logo %q: %s", logo, err.Error())
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
f, err := w.Create(path.Join("logo", path.Base(logo)))
|
|
||||||
if err == nil {
|
|
||||||
io.Copy(f, fd)
|
|
||||||
}
|
|
||||||
closer()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(challengeinfo.Partners) > 0 {
|
|
||||||
for _, partner := range challengeinfo.Partners {
|
|
||||||
fd, closer, err := sync.OpenOrGetFile(sync.GlobalImporter, partner.Src)
|
|
||||||
if err != nil {
|
|
||||||
log.Printf("Unable to archive partner logo %q: %s", partner.Src, err.Error())
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
f, err := w.Create(path.Join("partner", path.Base(partner.Src)))
|
|
||||||
if err == nil {
|
|
||||||
io.Copy(f, fd)
|
|
||||||
}
|
|
||||||
closer()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// my.json
|
|
||||||
f, err = w.Create("my.json")
|
|
||||||
if err == nil {
|
|
||||||
json.NewEncoder(f).Encode(my)
|
|
||||||
}
|
|
||||||
|
|
||||||
// settings.json
|
|
||||||
f, err = w.Create("settings.json")
|
|
||||||
if err == nil {
|
|
||||||
json.NewEncoder(f).Encode(s)
|
|
||||||
}
|
|
||||||
|
|
||||||
// teams.json
|
|
||||||
f, err = w.Create("teams.json")
|
|
||||||
if err == nil {
|
|
||||||
json.NewEncoder(f).Encode(teams)
|
|
||||||
}
|
|
||||||
|
|
||||||
// themes.json
|
|
||||||
f, err = w.Create("themes.json")
|
|
||||||
if err == nil {
|
|
||||||
json.NewEncoder(f).Encode(themes)
|
|
||||||
}
|
|
||||||
|
|
||||||
w.Close()
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
@ -1,297 +0,0 @@
|
||||||
package api
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/hex"
|
|
||||||
"fmt"
|
|
||||||
"log"
|
|
||||||
"net/http"
|
|
||||||
"os"
|
|
||||||
"path/filepath"
|
|
||||||
"strconv"
|
|
||||||
|
|
||||||
"srs.epita.fr/fic-server/admin/sync"
|
|
||||||
"srs.epita.fr/fic-server/libfic"
|
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
|
||||||
)
|
|
||||||
|
|
||||||
func declareFilesGlobalRoutes(router *gin.RouterGroup) {
|
|
||||||
router.DELETE("/files/", clearFiles)
|
|
||||||
|
|
||||||
// Remote
|
|
||||||
router.GET("/remote/themes/:thid/exercices/:exid/files", sync.ApiGetRemoteExerciceFiles)
|
|
||||||
}
|
|
||||||
|
|
||||||
func declareFilesRoutes(router *gin.RouterGroup) {
|
|
||||||
router.GET("/files", listFiles)
|
|
||||||
router.POST("/files", createExerciceFile)
|
|
||||||
|
|
||||||
apiFilesRoutes := router.Group("/files/:fileid")
|
|
||||||
apiFilesRoutes.Use(FileHandler)
|
|
||||||
apiFilesRoutes.GET("", showFile)
|
|
||||||
apiFilesRoutes.PUT("", updateFile)
|
|
||||||
apiFilesRoutes.DELETE("", deleteFile)
|
|
||||||
|
|
||||||
apiFileDepsRoutes := apiFilesRoutes.Group("/dependancies/:depid")
|
|
||||||
apiFileDepsRoutes.Use(FileDepHandler)
|
|
||||||
apiFileDepsRoutes.DELETE("", deleteFileDep)
|
|
||||||
|
|
||||||
// Check
|
|
||||||
apiFilesRoutes.POST("/check", checkFile)
|
|
||||||
apiFilesRoutes.POST("/gunzip", gunzipFile)
|
|
||||||
}
|
|
||||||
|
|
||||||
func FileHandler(c *gin.Context) {
|
|
||||||
fileid, err := strconv.ParseInt(string(c.Params.ByName("fileid")), 10, 64)
|
|
||||||
if err != nil {
|
|
||||||
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"errmsg": "Invalid file identifier"})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
var file *fic.EFile
|
|
||||||
if exercice, exists := c.Get("exercice"); exists {
|
|
||||||
file, err = exercice.(*fic.Exercice).GetFile(fileid)
|
|
||||||
if err != nil {
|
|
||||||
c.AbortWithStatusJSON(http.StatusNotFound, gin.H{"errmsg": "File not found"})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
file, err = fic.GetFile(fileid)
|
|
||||||
if err != nil {
|
|
||||||
c.AbortWithStatusJSON(http.StatusNotFound, gin.H{"errmsg": "File not found"})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
c.Set("file", file)
|
|
||||||
|
|
||||||
c.Next()
|
|
||||||
}
|
|
||||||
|
|
||||||
func FileDepHandler(c *gin.Context) {
|
|
||||||
depid, err := strconv.ParseInt(string(c.Params.ByName("depid")), 10, 64)
|
|
||||||
if err != nil {
|
|
||||||
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"errmsg": "Invalid dependency identifier"})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
c.Set("file-depid", depid)
|
|
||||||
|
|
||||||
c.Next()
|
|
||||||
}
|
|
||||||
|
|
||||||
type APIFile struct {
|
|
||||||
*fic.EFile
|
|
||||||
Depends []fic.Flag `json:"depends,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func genFileList(in []*fic.EFile, e error) (out []APIFile, err error) {
|
|
||||||
if e != nil {
|
|
||||||
return nil, e
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, f := range in {
|
|
||||||
g := APIFile{EFile: f}
|
|
||||||
|
|
||||||
var deps []fic.Flag
|
|
||||||
deps, err = f.GetDepends()
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, d := range deps {
|
|
||||||
if k, ok := d.(*fic.FlagKey); ok {
|
|
||||||
k, err = fic.GetFlagKey(k.Id)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
g.Depends = append(g.Depends, k)
|
|
||||||
} else if m, ok := d.(*fic.MCQ); ok {
|
|
||||||
m, err = fic.GetMCQ(m.Id)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
g.Depends = append(g.Depends, m)
|
|
||||||
} else {
|
|
||||||
err = fmt.Errorf("Unknown type %T to handle file dependancy", k)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
out = append(out, g)
|
|
||||||
}
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func listFiles(c *gin.Context) {
|
|
||||||
var files []APIFile
|
|
||||||
var err error
|
|
||||||
|
|
||||||
if exercice, exists := c.Get("exercice"); exists {
|
|
||||||
files, err = genFileList(exercice.(*fic.Exercice).GetFiles())
|
|
||||||
} else {
|
|
||||||
files, err = genFileList(fic.GetFiles())
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": err.Error()})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
c.JSON(http.StatusOK, files)
|
|
||||||
}
|
|
||||||
|
|
||||||
func clearFiles(c *gin.Context) {
|
|
||||||
err := os.RemoveAll(fic.FilesDir)
|
|
||||||
if err != nil {
|
|
||||||
log.Println("Unable to remove files:", err.Error())
|
|
||||||
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": err.Error()})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
err = os.MkdirAll(fic.FilesDir, 0751)
|
|
||||||
if err != nil {
|
|
||||||
log.Println("Unable to create FILES:", err.Error())
|
|
||||||
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": err.Error()})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
_, err = fic.ClearFiles()
|
|
||||||
if err != nil {
|
|
||||||
log.Println("Unable to clean DB files:", err.Error())
|
|
||||||
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": "Les fichiers ont bien été effacés. Mais il n'a pas été possible d'effacer la base de données. Refaites une synchronisation maintenant. " + err.Error()})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
c.JSON(http.StatusOK, true)
|
|
||||||
}
|
|
||||||
|
|
||||||
func showFile(c *gin.Context) {
|
|
||||||
c.JSON(http.StatusOK, c.MustGet("file").(*fic.EFile))
|
|
||||||
}
|
|
||||||
|
|
||||||
type uploadedFile struct {
|
|
||||||
URI string
|
|
||||||
Digest string
|
|
||||||
}
|
|
||||||
|
|
||||||
func createExerciceFile(c *gin.Context) {
|
|
||||||
exercice, exists := c.Get("exercice")
|
|
||||||
if !exists {
|
|
||||||
c.AbortWithStatusJSON(http.StatusMethodNotAllowed, gin.H{"errmsg": "File can only be added inside an exercice."})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
paramsFiles, err := sync.GetExerciceFilesParams(sync.GlobalImporter, exercice.(*fic.Exercice))
|
|
||||||
if err != nil {
|
|
||||||
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": err.Error()})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
var uf uploadedFile
|
|
||||||
err = c.ShouldBindJSON(&uf)
|
|
||||||
if err != nil {
|
|
||||||
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"errmsg": err.Error()})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
ret, err := sync.ImportFile(sync.GlobalImporter, uf.URI,
|
|
||||||
func(filePath string, origin string) (interface{}, error) {
|
|
||||||
if digest, err := hex.DecodeString(uf.Digest); err != nil {
|
|
||||||
return nil, err
|
|
||||||
} else {
|
|
||||||
published := true
|
|
||||||
disclaimer := ""
|
|
||||||
|
|
||||||
if f, exists := paramsFiles[filepath.Base(filePath)]; exists {
|
|
||||||
published = !f.Hidden
|
|
||||||
|
|
||||||
if disclaimer, err = sync.ProcessMarkdown(sync.GlobalImporter, f.Disclaimer, exercice.(*fic.Exercice).Path); err != nil {
|
|
||||||
return nil, fmt.Errorf("error during markdown formating of disclaimer: %w", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return exercice.(*fic.Exercice).ImportFile(filePath, origin, digest, nil, disclaimer, published)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": err.Error()})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
c.JSON(http.StatusOK, ret)
|
|
||||||
}
|
|
||||||
|
|
||||||
func updateFile(c *gin.Context) {
|
|
||||||
file := c.MustGet("file").(*fic.EFile)
|
|
||||||
|
|
||||||
var uf fic.EFile
|
|
||||||
err := c.ShouldBindJSON(&uf)
|
|
||||||
if err != nil {
|
|
||||||
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"errmsg": err.Error()})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
uf.Id = file.Id
|
|
||||||
|
|
||||||
if _, err := uf.Update(); err != nil {
|
|
||||||
log.Println("Unable to updateFile:", err.Error())
|
|
||||||
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": "An error occurs when trying to update file."})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
c.JSON(http.StatusOK, uf)
|
|
||||||
}
|
|
||||||
|
|
||||||
func deleteFile(c *gin.Context) {
|
|
||||||
file := c.MustGet("file").(*fic.EFile)
|
|
||||||
|
|
||||||
_, err := file.Delete()
|
|
||||||
if err != nil {
|
|
||||||
log.Println("Unable to updateFile:", err.Error())
|
|
||||||
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": "An error occurs when trying to update file."})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
c.JSON(http.StatusOK, true)
|
|
||||||
}
|
|
||||||
|
|
||||||
func deleteFileDep(c *gin.Context) {
|
|
||||||
file := c.MustGet("file").(*fic.EFile)
|
|
||||||
depid := c.MustGet("file-depid").(int64)
|
|
||||||
|
|
||||||
err := file.DeleteDepend(&fic.FlagKey{Id: int(depid)})
|
|
||||||
if err != nil {
|
|
||||||
log.Println("Unable to deleteFileDep:", err.Error())
|
|
||||||
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": "An error occurs when trying to delete file dependency."})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
c.JSON(http.StatusOK, true)
|
|
||||||
}
|
|
||||||
|
|
||||||
func checkFile(c *gin.Context) {
|
|
||||||
file := c.MustGet("file").(*fic.EFile)
|
|
||||||
|
|
||||||
err := file.CheckFileOnDisk()
|
|
||||||
if err != nil {
|
|
||||||
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": err.Error()})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
c.JSON(http.StatusOK, true)
|
|
||||||
}
|
|
||||||
|
|
||||||
func gunzipFile(c *gin.Context) {
|
|
||||||
file := c.MustGet("file").(*fic.EFile)
|
|
||||||
|
|
||||||
err := file.GunzipFileOnDisk()
|
|
||||||
if err != nil {
|
|
||||||
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": err.Error()})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
c.JSON(http.StatusOK, true)
|
|
||||||
}
|
|
||||||
|
|
@ -1,156 +0,0 @@
|
||||||
package api
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"io/ioutil"
|
|
||||||
"log"
|
|
||||||
"net/http"
|
|
||||||
"os"
|
|
||||||
"path"
|
|
||||||
"strings"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"srs.epita.fr/fic-server/admin/pki"
|
|
||||||
"srs.epita.fr/fic-server/libfic"
|
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
|
||||||
)
|
|
||||||
|
|
||||||
var TimestampCheck = "submissions"
|
|
||||||
|
|
||||||
func declareHealthRoutes(router *gin.RouterGroup) {
|
|
||||||
router.GET("/timestamps.json", func(c *gin.Context) {
|
|
||||||
stat, err := os.Stat(TimestampCheck)
|
|
||||||
if err != nil {
|
|
||||||
c.AbortWithStatusJSON(http.StatusNotFound, gin.H{"errmsg": fmt.Sprintf("timestamp.json: %s", err.Error())})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
now := time.Now().UTC()
|
|
||||||
c.JSON(http.StatusOK, gin.H{
|
|
||||||
"frontend": stat.ModTime().UTC(),
|
|
||||||
"backend": now,
|
|
||||||
"diffFB": now.Sub(stat.ModTime()),
|
|
||||||
})
|
|
||||||
})
|
|
||||||
router.GET("/health.json", GetHealth)
|
|
||||||
router.GET("/submissions-stats.json", GetSubmissionsStats)
|
|
||||||
router.GET("/validations-stats.json", GetValidationsStats)
|
|
||||||
|
|
||||||
router.DELETE("/submissions/*path", func(c *gin.Context) {
|
|
||||||
err := os.Remove(path.Join(TimestampCheck, c.Params.ByName("path")))
|
|
||||||
if err != nil {
|
|
||||||
c.AbortWithStatusJSON(http.StatusNotFound, gin.H{"errmsg": err.Error()})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
c.Status(http.StatusOK)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
type healthFileReport struct {
|
|
||||||
IdTeam string `json:"id_team,omitempty"`
|
|
||||||
Path string `json:"path"`
|
|
||||||
Error string `json:"error"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func getHealth(pathname string) (ret []healthFileReport) {
|
|
||||||
if ds, err := ioutil.ReadDir(pathname); err != nil {
|
|
||||||
ret = append(ret, healthFileReport{
|
|
||||||
Path: strings.TrimPrefix(pathname, TimestampCheck),
|
|
||||||
Error: fmt.Sprintf("unable to ReadDir: %s", err),
|
|
||||||
})
|
|
||||||
return
|
|
||||||
} else {
|
|
||||||
for _, d := range ds {
|
|
||||||
p := path.Join(pathname, d.Name())
|
|
||||||
if d.IsDir() && d.Name() != ".tmp" && d.Mode()&os.ModeSymlink == 0 {
|
|
||||||
ret = append(ret, getHealth(p)...)
|
|
||||||
} else if !d.IsDir() && d.Mode()&os.ModeSymlink == 0 && time.Since(d.ModTime()) > 2*time.Second {
|
|
||||||
if d.Name() == ".locked" {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
teamDir := strings.TrimPrefix(pathname, TimestampCheck)
|
|
||||||
idteam, _ := pki.GetAssociation(path.Join(TeamsDir, teamDir))
|
|
||||||
ret = append(ret, healthFileReport{
|
|
||||||
IdTeam: idteam,
|
|
||||||
Path: path.Join(teamDir, d.Name()),
|
|
||||||
Error: "existing untreated file",
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func GetHealth(c *gin.Context) {
|
|
||||||
if _, err := os.Stat(TimestampCheck); err != nil {
|
|
||||||
c.AbortWithStatusJSON(http.StatusNotFound, gin.H{"errmsg": fmt.Sprintf("health.json: %s", err.Error())})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
c.JSON(http.StatusOK, getHealth(TimestampCheck))
|
|
||||||
}
|
|
||||||
|
|
||||||
type SubmissionsStats struct {
|
|
||||||
NbSubmissionLastMinute uint `json:"nbsubminute"`
|
|
||||||
NbSubmissionLast5Minute uint `json:"nbsub5minute"`
|
|
||||||
NbSubmissionLastQuarter uint `json:"nbsubquarter"`
|
|
||||||
NbSubmissionLastHour uint `json:"nbsubhour"`
|
|
||||||
NbSubmissionLastDay uint `json:"nbsubday"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func calcSubmissionsStats(tries []time.Time) (stats SubmissionsStats) {
|
|
||||||
lastMinute := time.Now().Add(-1 * time.Minute)
|
|
||||||
last5Minute := time.Now().Add(-5 * time.Minute)
|
|
||||||
lastQuarter := time.Now().Add(-15 * time.Minute)
|
|
||||||
lastHour := time.Now().Add(-1 * time.Hour)
|
|
||||||
lastDay := time.Now().Add(-24 * time.Hour)
|
|
||||||
|
|
||||||
for _, t := range tries {
|
|
||||||
if lastMinute.Before(t) {
|
|
||||||
stats.NbSubmissionLastMinute += 1
|
|
||||||
stats.NbSubmissionLast5Minute += 1
|
|
||||||
stats.NbSubmissionLastQuarter += 1
|
|
||||||
stats.NbSubmissionLastHour += 1
|
|
||||||
stats.NbSubmissionLastDay += 1
|
|
||||||
} else if last5Minute.Before(t) {
|
|
||||||
stats.NbSubmissionLast5Minute += 1
|
|
||||||
stats.NbSubmissionLastQuarter += 1
|
|
||||||
stats.NbSubmissionLastHour += 1
|
|
||||||
stats.NbSubmissionLastDay += 1
|
|
||||||
} else if lastQuarter.Before(t) {
|
|
||||||
stats.NbSubmissionLastQuarter += 1
|
|
||||||
stats.NbSubmissionLastHour += 1
|
|
||||||
stats.NbSubmissionLastDay += 1
|
|
||||||
} else if lastHour.Before(t) {
|
|
||||||
stats.NbSubmissionLastHour += 1
|
|
||||||
stats.NbSubmissionLastDay += 1
|
|
||||||
} else if lastDay.Before(t) {
|
|
||||||
stats.NbSubmissionLastDay += 1
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func GetSubmissionsStats(c *gin.Context) {
|
|
||||||
tries, err := fic.GetTries(nil, nil)
|
|
||||||
if err != nil {
|
|
||||||
log.Println("Unable to GetTries:", err.Error())
|
|
||||||
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": "Unable to retrieves tries."})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
c.JSON(http.StatusOK, calcSubmissionsStats(tries))
|
|
||||||
}
|
|
||||||
|
|
||||||
func GetValidationsStats(c *gin.Context) {
|
|
||||||
tries, err := fic.GetValidations(nil, nil)
|
|
||||||
if err != nil {
|
|
||||||
log.Println("Unable to GetTries:", err.Error())
|
|
||||||
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": "Unable to retrieves tries."})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
c.JSON(http.StatusOK, calcSubmissionsStats(tries))
|
|
||||||
}
|
|
||||||
|
|
@ -1,98 +0,0 @@
|
||||||
package api
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bufio"
|
|
||||||
"io/ioutil"
|
|
||||||
"net/http"
|
|
||||||
"os"
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
|
||||||
)
|
|
||||||
|
|
||||||
func declareMonitorRoutes(router *gin.RouterGroup) {
|
|
||||||
router.GET("/monitor", func(c *gin.Context) {
|
|
||||||
c.JSON(http.StatusOK, gin.H{
|
|
||||||
"localhost": genLocalConstants(),
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func readLoadAvg(fd *os.File) (ret map[string]float64) {
|
|
||||||
if s, err := ioutil.ReadAll(fd); err == nil {
|
|
||||||
f := strings.Fields(strings.TrimSpace(string(s)))
|
|
||||||
if len(f) >= 3 {
|
|
||||||
ret = map[string]float64{}
|
|
||||||
ret["1m"], _ = strconv.ParseFloat(f[0], 64)
|
|
||||||
ret["5m"], _ = strconv.ParseFloat(f[1], 64)
|
|
||||||
ret["15m"], _ = strconv.ParseFloat(f[2], 64)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func readMeminfo(fd *os.File) (ret map[string]uint64) {
|
|
||||||
ret = map[string]uint64{}
|
|
||||||
|
|
||||||
scanner := bufio.NewScanner(fd)
|
|
||||||
for scanner.Scan() {
|
|
||||||
f := strings.Fields(strings.TrimSpace(scanner.Text()))
|
|
||||||
if len(f) >= 2 {
|
|
||||||
if v, err := strconv.ParseUint(f[1], 10, 64); err == nil {
|
|
||||||
ret[strings.ToLower(strings.TrimSuffix(f[0], ":"))] = v * 1024
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func readCPUStats(fd *os.File) (ret map[string]map[string]uint64) {
|
|
||||||
ret = map[string]map[string]uint64{}
|
|
||||||
|
|
||||||
scanner := bufio.NewScanner(fd)
|
|
||||||
for scanner.Scan() {
|
|
||||||
f := strings.Fields(strings.TrimSpace(scanner.Text()))
|
|
||||||
if len(f[0]) >= 4 && f[0][0:3] == "cpu" && len(f) >= 8 {
|
|
||||||
ret[f[0]] = map[string]uint64{}
|
|
||||||
var total uint64 = 0
|
|
||||||
for i, k := range []string{"user", "nice", "system", "idle", "iowait", "irq", "softirq"} {
|
|
||||||
if v, err := strconv.ParseUint(f[i+1], 10, 64); err == nil {
|
|
||||||
ret[f[0]][k] = v
|
|
||||||
total += v
|
|
||||||
}
|
|
||||||
}
|
|
||||||
ret[f[0]]["total"] = total
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func genLocalConstants() interface{} {
|
|
||||||
ret := map[string]interface{}{}
|
|
||||||
|
|
||||||
fi, err := os.Open("/proc/loadavg")
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer fi.Close()
|
|
||||||
ret["loadavg"] = readLoadAvg(fi)
|
|
||||||
|
|
||||||
fi, err = os.Open("/proc/meminfo")
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer fi.Close()
|
|
||||||
ret["meminfo"] = readMeminfo(fi)
|
|
||||||
|
|
||||||
fi, err = os.Open("/proc/stat")
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer fi.Close()
|
|
||||||
ret["cpustat"] = readCPUStats(fi)
|
|
||||||
|
|
||||||
return ret
|
|
||||||
}
|
|
||||||
|
|
@ -1,360 +0,0 @@
|
||||||
package api
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"fmt"
|
|
||||||
"io/ioutil"
|
|
||||||
"log"
|
|
||||||
"net/http"
|
|
||||||
"os"
|
|
||||||
"path"
|
|
||||||
"text/template"
|
|
||||||
"unicode"
|
|
||||||
|
|
||||||
"srs.epita.fr/fic-server/admin/pki"
|
|
||||||
"srs.epita.fr/fic-server/libfic"
|
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
OidcIssuer = "live.fic.srs.epita.fr"
|
|
||||||
OidcClientId = "epita-challenge"
|
|
||||||
OidcSecret = ""
|
|
||||||
)
|
|
||||||
|
|
||||||
func declarePasswordRoutes(router *gin.RouterGroup) {
|
|
||||||
router.POST("/password", func(c *gin.Context) {
|
|
||||||
passwd, err := fic.GeneratePassword()
|
|
||||||
if err != nil {
|
|
||||||
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": err.Error()})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
c.JSON(http.StatusOK, gin.H{"password": passwd})
|
|
||||||
})
|
|
||||||
router.GET("/oauth-status", func(c *gin.Context) {
|
|
||||||
c.JSON(http.StatusOK, gin.H{
|
|
||||||
"secret_defined": OidcSecret != "",
|
|
||||||
})
|
|
||||||
})
|
|
||||||
router.GET("/dex.yaml", func(c *gin.Context) {
|
|
||||||
cfg, err := genDexConfig()
|
|
||||||
if err != nil {
|
|
||||||
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": err.Error()})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
c.String(http.StatusOK, string(cfg))
|
|
||||||
})
|
|
||||||
router.POST("/dex.yaml", func(c *gin.Context) {
|
|
||||||
if dexcfg, err := genDexConfig(); err != nil {
|
|
||||||
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": err.Error()})
|
|
||||||
return
|
|
||||||
} else if err := ioutil.WriteFile(path.Join(pki.PKIDir, "shared", "dex-config.yaml"), []byte(dexcfg), 0644); err != nil {
|
|
||||||
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": err.Error()})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
c.JSON(http.StatusOK, true)
|
|
||||||
})
|
|
||||||
router.GET("/dex-password.tpl", func(c *gin.Context) {
|
|
||||||
passtpl, err := genDexPasswordTpl()
|
|
||||||
if err != nil {
|
|
||||||
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": err.Error()})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
c.String(http.StatusOK, string(passtpl))
|
|
||||||
})
|
|
||||||
router.POST("/dex-password.tpl", func(c *gin.Context) {
|
|
||||||
if dexcfg, err := genDexPasswordTpl(); err != nil {
|
|
||||||
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": err.Error()})
|
|
||||||
return
|
|
||||||
} else if err := ioutil.WriteFile(path.Join(pki.PKIDir, "shared", "dex-password.tpl"), []byte(dexcfg), 0644); err != nil {
|
|
||||||
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": err.Error()})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
c.JSON(http.StatusOK, true)
|
|
||||||
})
|
|
||||||
router.GET("/vouch-proxy.yaml", func(c *gin.Context) {
|
|
||||||
cfg, err := genVouchProxyConfig()
|
|
||||||
if err != nil {
|
|
||||||
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": err.Error()})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
c.String(http.StatusOK, string(cfg))
|
|
||||||
})
|
|
||||||
router.POST("/vouch-proxy.yaml", func(c *gin.Context) {
|
|
||||||
if dexcfg, err := genVouchProxyConfig(); err != nil {
|
|
||||||
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": err.Error()})
|
|
||||||
return
|
|
||||||
} else if err := ioutil.WriteFile(path.Join(pki.PKIDir, "shared", "vouch-config.yaml"), []byte(dexcfg), 0644); err != nil {
|
|
||||||
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": err.Error()})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
c.JSON(http.StatusOK, true)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func declareTeamsPasswordRoutes(router *gin.RouterGroup) {
|
|
||||||
router.GET("/password", func(c *gin.Context) {
|
|
||||||
team := c.MustGet("team").(*fic.Team)
|
|
||||||
|
|
||||||
if team.Password != nil {
|
|
||||||
c.String(http.StatusOK, *team.Password)
|
|
||||||
} else {
|
|
||||||
c.AbortWithStatusJSON(http.StatusNotFound, nil)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
router.POST("/password", func(c *gin.Context) {
|
|
||||||
team := c.MustGet("team").(*fic.Team)
|
|
||||||
|
|
||||||
if passwd, err := fic.GeneratePassword(); err != nil {
|
|
||||||
log.Println("Unable to GeneratePassword:", err.Error())
|
|
||||||
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": "Something went wrong when generating the new team password"})
|
|
||||||
return
|
|
||||||
} else {
|
|
||||||
team.Password = &passwd
|
|
||||||
|
|
||||||
_, err := team.Update()
|
|
||||||
if err != nil {
|
|
||||||
log.Println("Unable to Update Team:", err.Error())
|
|
||||||
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": "Something went wrong when updating the new team password"})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
c.JSON(http.StatusOK, team)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
const dexcfgtpl = `issuer: {{ .Issuer }}
|
|
||||||
storage:
|
|
||||||
type: sqlite3
|
|
||||||
config:
|
|
||||||
file: /var/dex/dex.db
|
|
||||||
web:
|
|
||||||
http: 0.0.0.0:5556
|
|
||||||
frontend:
|
|
||||||
issuer: {{ .Name }}
|
|
||||||
logoURL: {{ .LogoPath }}
|
|
||||||
dir: /srv/dex/web/
|
|
||||||
oauth2:
|
|
||||||
skipApprovalScreen: true
|
|
||||||
staticClients:
|
|
||||||
{{ range $c := .Clients }}
|
|
||||||
- id: {{ $c.Id }}
|
|
||||||
name: {{ $c.Name }}
|
|
||||||
redirectURIs: [{{ range $u := $c.RedirectURIs }}'{{ $u }}'{{ end }}]
|
|
||||||
secret: {{ $c.Secret }}
|
|
||||||
{{ end }}
|
|
||||||
enablePasswordDB: true
|
|
||||||
staticPasswords:
|
|
||||||
{{ range $t := .Teams }}
|
|
||||||
- email: "team{{ printf "%02d" $t.Id }}"
|
|
||||||
hash: "{{with $t }}{{ .HashedPassword }}{{end}}"
|
|
||||||
{{ end }}
|
|
||||||
`
|
|
||||||
|
|
||||||
const dexpasswdtpl = `{{ "{{" }} template "header.html" . {{ "}}" }}
|
|
||||||
|
|
||||||
<div class="theme-panel">
|
|
||||||
<h2 class="theme-heading">
|
|
||||||
Bienvenue au {{ .Name }} !
|
|
||||||
</h2>
|
|
||||||
<form method="post" action="{{ "{{" }} .PostURL {{ "}}" }}">
|
|
||||||
<div class="theme-form-row">
|
|
||||||
<div class="theme-form-label">
|
|
||||||
<label for="userid">Votre équipe</label>
|
|
||||||
</div>
|
|
||||||
<select tabindex="1" required id="login" name="login" class="theme-form-input" autofocus>
|
|
||||||
{{ range $t := .Teams }} <option value="team{{ printf "%02d" $t.Id }}">{{ $t.Name }}</option>
|
|
||||||
{{ end }} </select>
|
|
||||||
</div>
|
|
||||||
<div class="theme-form-row">
|
|
||||||
<div class="theme-form-label">
|
|
||||||
<label for="password">Mot de passe</label>
|
|
||||||
</div>
|
|
||||||
<input tabindex="2" required id="password" name="password" type="password" class="theme-form-input" placeholder="mot de passe" {{ "{{" }} if .Invalid {{ "}}" }} autofocus {{ "{{" }} end {{ "}}" }}/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{{ "{{" }} if .Invalid {{ "}}" }}
|
|
||||||
<div id="login-error" class="dex-error-box">
|
|
||||||
Identifiants incorrects.
|
|
||||||
</div>
|
|
||||||
{{ "{{" }} end {{ "}}" }}
|
|
||||||
|
|
||||||
<button tabindex="3" id="submit-login" type="submit" class="dex-btn theme-btn--primary">C'est parti !</button>
|
|
||||||
|
|
||||||
</form>
|
|
||||||
{{ "{{" }} if .BackLink {{ "}}" }}
|
|
||||||
<div class="theme-link-back">
|
|
||||||
<a class="dex-subtle-text" href="{{ "{{" }} .BackLink {{ "}}" }}">Sélectionner une autre méthode d'authentification.</a>
|
|
||||||
</div>
|
|
||||||
{{ "{{" }} end {{ "}}" }}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{{ "{{" }} template "footer.html" . {{ "}}" }}
|
|
||||||
`
|
|
||||||
|
|
||||||
type dexConfigClient struct {
|
|
||||||
Id string
|
|
||||||
Name string
|
|
||||||
RedirectURIs []string
|
|
||||||
Secret string
|
|
||||||
}
|
|
||||||
|
|
||||||
type dexConfig struct {
|
|
||||||
Name string
|
|
||||||
Issuer string
|
|
||||||
Clients []dexConfigClient
|
|
||||||
Teams []*fic.Team
|
|
||||||
LogoPath string
|
|
||||||
}
|
|
||||||
|
|
||||||
func genDexConfig() ([]byte, error) {
|
|
||||||
if OidcSecret == "" {
|
|
||||||
return nil, fmt.Errorf("Unable to generate dex configuration: OIDC Secret not defined. Please define FICOIDC_SECRET in your environment.")
|
|
||||||
}
|
|
||||||
|
|
||||||
teams, err := fic.GetTeams()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
b := bytes.NewBufferString("")
|
|
||||||
|
|
||||||
challengeInfo, err := GetChallengeInfo()
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("Cannot create template: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Lower the first letter to be included in a sentence.
|
|
||||||
name := []rune(challengeInfo.Title)
|
|
||||||
if len(name) > 0 {
|
|
||||||
name[0] = unicode.ToLower(name[0])
|
|
||||||
}
|
|
||||||
|
|
||||||
logoPath := ""
|
|
||||||
if len(challengeInfo.MainLogo) > 0 {
|
|
||||||
logoPath = path.Join("../../files", "logo", path.Base(challengeInfo.MainLogo[len(challengeInfo.MainLogo)-1]))
|
|
||||||
}
|
|
||||||
|
|
||||||
dexTmpl, err := template.New("dexcfg").Parse(dexcfgtpl)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("Cannot create template: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
err = dexTmpl.Execute(b, dexConfig{
|
|
||||||
Name: string(name),
|
|
||||||
Issuer: "https://" + OidcIssuer,
|
|
||||||
Clients: []dexConfigClient{
|
|
||||||
dexConfigClient{
|
|
||||||
Id: OidcClientId,
|
|
||||||
Name: challengeInfo.Title,
|
|
||||||
RedirectURIs: []string{"https://" + OidcIssuer + "/challenge_access/auth"},
|
|
||||||
Secret: OidcSecret,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
Teams: teams,
|
|
||||||
LogoPath: logoPath,
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("An error occurs during template execution: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Also generate team associations
|
|
||||||
for _, team := range teams {
|
|
||||||
if _, err := os.Stat(path.Join(TeamsDir, fmt.Sprintf("team%02d", team.Id))); err == nil {
|
|
||||||
if err = os.Remove(path.Join(TeamsDir, fmt.Sprintf("team%02d", team.Id))); err != nil {
|
|
||||||
log.Println("Unable to remove existing association symlink:", err.Error())
|
|
||||||
return nil, fmt.Errorf("Unable to remove existing association symlink: %s", err.Error())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if err := os.Symlink(fmt.Sprintf("%d", team.Id), path.Join(TeamsDir, fmt.Sprintf("team%02d", team.Id))); err != nil {
|
|
||||||
log.Println("Unable to create association symlink:", err.Error())
|
|
||||||
return nil, fmt.Errorf("Unable to create association symlink: %s", err.Error())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return b.Bytes(), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func genDexPasswordTpl() ([]byte, error) {
|
|
||||||
challengeInfo, err := GetChallengeInfo()
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("Cannot create template: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if teams, err := fic.GetTeams(); err != nil {
|
|
||||||
return nil, err
|
|
||||||
} else {
|
|
||||||
b := bytes.NewBufferString("")
|
|
||||||
|
|
||||||
if dexTmpl, err := template.New("dexpasswd").Parse(dexpasswdtpl); err != nil {
|
|
||||||
return nil, fmt.Errorf("Cannot create template: %w", err)
|
|
||||||
} else if err = dexTmpl.Execute(b, dexConfig{
|
|
||||||
Teams: teams,
|
|
||||||
Name: challengeInfo.Title,
|
|
||||||
}); err != nil {
|
|
||||||
return nil, fmt.Errorf("An error occurs during template execution: %w", err)
|
|
||||||
} else {
|
|
||||||
return b.Bytes(), nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const vouchcfgtpl = `# CONFIGURATION FILE HANDLED BY fic-admin
|
|
||||||
# DO NOT MODIFY IT BY HAND
|
|
||||||
|
|
||||||
vouch:
|
|
||||||
logLevel: debug
|
|
||||||
allowAllUsers: true
|
|
||||||
document_root: /challenge_access
|
|
||||||
|
|
||||||
cookie:
|
|
||||||
domain: {{ .Domain }}
|
|
||||||
|
|
||||||
oauth:
|
|
||||||
provider: oidc
|
|
||||||
client_id: {{ .ClientId }}
|
|
||||||
client_secret: {{ .ClientSecret }}
|
|
||||||
callback_urls:
|
|
||||||
- https://{{ .Domain }}/challenge_access/auth
|
|
||||||
auth_url: https://{{ .Domain }}/auth
|
|
||||||
token_url: http://127.0.0.1:5556/token
|
|
||||||
user_info_url: http://127.0.0.1:5556/userinfo
|
|
||||||
scopes:
|
|
||||||
- openid
|
|
||||||
- email
|
|
||||||
`
|
|
||||||
|
|
||||||
type vouchProxyConfig struct {
|
|
||||||
Domain string
|
|
||||||
ClientId string
|
|
||||||
ClientSecret string
|
|
||||||
}
|
|
||||||
|
|
||||||
func genVouchProxyConfig() ([]byte, error) {
|
|
||||||
if OidcSecret == "" {
|
|
||||||
return nil, fmt.Errorf("Unable to generate vouch proxy configuration: OIDC Secret not defined. Please define FICOIDC_SECRET in your environment.")
|
|
||||||
}
|
|
||||||
|
|
||||||
b := bytes.NewBufferString("")
|
|
||||||
|
|
||||||
if vouchTmpl, err := template.New("vouchcfg").Parse(vouchcfgtpl); err != nil {
|
|
||||||
return nil, fmt.Errorf("Cannot create template: %w", err)
|
|
||||||
} else if err = vouchTmpl.Execute(b, vouchProxyConfig{
|
|
||||||
Domain: OidcIssuer,
|
|
||||||
ClientId: OidcClientId,
|
|
||||||
ClientSecret: OidcSecret,
|
|
||||||
}); err != nil {
|
|
||||||
return nil, fmt.Errorf("An error occurs during template execution: %w", err)
|
|
||||||
} else {
|
|
||||||
return b.Bytes(), nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,206 +0,0 @@
|
||||||
package api
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
|
||||||
"log"
|
|
||||||
"net/http"
|
|
||||||
"os"
|
|
||||||
"path"
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
|
||||||
)
|
|
||||||
|
|
||||||
var DashboardDir string
|
|
||||||
|
|
||||||
func declarePublicRoutes(router *gin.RouterGroup) {
|
|
||||||
router.GET("/public/", listPublic)
|
|
||||||
router.GET("/public/:sid", getPublic)
|
|
||||||
router.DELETE("/public/:sid", deletePublic)
|
|
||||||
router.PUT("/public/:sid", savePublic)
|
|
||||||
}
|
|
||||||
|
|
||||||
type FICPublicScene struct {
|
|
||||||
Type string `json:"type"`
|
|
||||||
Params map[string]interface{} `json:"params"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type FICPublicDisplay struct {
|
|
||||||
Scenes []FICPublicScene `json:"scenes"`
|
|
||||||
Side []FICPublicScene `json:"side"`
|
|
||||||
CustomCountdown map[string]interface{} `json:"customCountdown"`
|
|
||||||
HideEvents bool `json:"hideEvents"`
|
|
||||||
HideCountdown bool `json:"hideCountdown"`
|
|
||||||
HideCarousel bool `json:"hideCarousel"`
|
|
||||||
PropagationTime *time.Time `json:"propagationTime,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func InitDashboardPresets(dir string) error {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func readPublic(path string) (FICPublicDisplay, error) {
|
|
||||||
var s FICPublicDisplay
|
|
||||||
if fd, err := os.Open(path); err != nil {
|
|
||||||
return s, err
|
|
||||||
} else {
|
|
||||||
defer fd.Close()
|
|
||||||
jdec := json.NewDecoder(fd)
|
|
||||||
|
|
||||||
if err := jdec.Decode(&s); err != nil {
|
|
||||||
return s, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return s, nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func savePublicTo(path string, s FICPublicDisplay) error {
|
|
||||||
if fd, err := os.Create(path); err != nil {
|
|
||||||
return err
|
|
||||||
} else {
|
|
||||||
defer fd.Close()
|
|
||||||
jenc := json.NewEncoder(fd)
|
|
||||||
|
|
||||||
if err := jenc.Encode(s); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
type DashboardFiles struct {
|
|
||||||
Presets []string `json:"presets"`
|
|
||||||
Nexts []*NextDashboardFile `json:"nexts"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type NextDashboardFile struct {
|
|
||||||
Name string `json:"name"`
|
|
||||||
Screen int `json:"screen"`
|
|
||||||
Date time.Time `json:"date"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func listPublic(c *gin.Context) {
|
|
||||||
files, err := os.ReadDir(DashboardDir)
|
|
||||||
if err != nil {
|
|
||||||
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": err.Error()})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
var ret DashboardFiles
|
|
||||||
for _, file := range files {
|
|
||||||
if strings.HasPrefix(file.Name(), "preset-") {
|
|
||||||
ret.Presets = append(ret.Presets, strings.TrimSuffix(strings.TrimPrefix(file.Name(), "preset-"), ".json"))
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
if !strings.HasPrefix(file.Name(), "public") || len(file.Name()) < 18 {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
ts, err := strconv.ParseInt(file.Name()[8:18], 10, 64)
|
|
||||||
if err == nil {
|
|
||||||
s, _ := strconv.Atoi(file.Name()[6:7])
|
|
||||||
ret.Nexts = append(ret.Nexts, &NextDashboardFile{
|
|
||||||
Name: file.Name()[6:18],
|
|
||||||
Screen: s,
|
|
||||||
Date: time.Unix(ts, 0),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
c.JSON(http.StatusOK, ret)
|
|
||||||
}
|
|
||||||
|
|
||||||
func getPublic(c *gin.Context) {
|
|
||||||
if strings.Contains(c.Params.ByName("sid"), "/") {
|
|
||||||
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"errmsg": "sid cannot contains /"})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
filename := fmt.Sprintf("public%s.json", c.Params.ByName("sid"))
|
|
||||||
if strings.HasPrefix(c.Params.ByName("sid"), "preset-") {
|
|
||||||
filename = fmt.Sprintf("%s.json", c.Params.ByName("sid"))
|
|
||||||
}
|
|
||||||
|
|
||||||
if _, err := os.Stat(path.Join(DashboardDir, filename)); !os.IsNotExist(err) {
|
|
||||||
p, err := readPublic(path.Join(DashboardDir, filename))
|
|
||||||
if err != nil {
|
|
||||||
log.Println("Unable to readPublic in getPublic:", err.Error())
|
|
||||||
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": "An error occurs during scene retrieval."})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
c.JSON(http.StatusOK, p)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
c.JSON(http.StatusOK, FICPublicDisplay{Scenes: []FICPublicScene{}, Side: []FICPublicScene{}})
|
|
||||||
}
|
|
||||||
|
|
||||||
func deletePublic(c *gin.Context) {
|
|
||||||
if strings.Contains(c.Params.ByName("sid"), "/") {
|
|
||||||
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"errmsg": "sid cannot contains /"})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
filename := fmt.Sprintf("public%s.json", c.Params.ByName("sid"))
|
|
||||||
if strings.HasPrefix(c.Params.ByName("sid"), "preset-") {
|
|
||||||
filename = fmt.Sprintf("%s.json", c.Params.ByName("sid"))
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(filename) == 12 {
|
|
||||||
if err := savePublicTo(path.Join(DashboardDir, filename), FICPublicDisplay{}); err != nil {
|
|
||||||
log.Println("Unable to deletePublic:", err.Error())
|
|
||||||
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": "An error occurs during scene deletion."})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if err := os.Remove(path.Join(DashboardDir, filename)); err != nil {
|
|
||||||
log.Println("Unable to deletePublic:", err.Error())
|
|
||||||
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": "An error occurs during scene deletion."})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
c.JSON(http.StatusOK, FICPublicDisplay{Scenes: []FICPublicScene{}, Side: []FICPublicScene{}})
|
|
||||||
}
|
|
||||||
|
|
||||||
func savePublic(c *gin.Context) {
|
|
||||||
if strings.Contains(c.Params.ByName("sid"), "/") {
|
|
||||||
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"errmsg": "sid cannot contains /"})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
var scenes FICPublicDisplay
|
|
||||||
err := c.ShouldBindJSON(&scenes)
|
|
||||||
if err != nil {
|
|
||||||
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"errmsg": err.Error()})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
filename := fmt.Sprintf("public%s.json", c.Params.ByName("sid"))
|
|
||||||
if c.Request.URL.Query().Has("t") {
|
|
||||||
t, err := time.Parse(time.RFC3339, c.Request.URL.Query().Get("t"))
|
|
||||||
if err != nil {
|
|
||||||
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"errmsg": err.Error()})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
filename = fmt.Sprintf("public%s-%d.json", c.Params.ByName("sid"), t.Unix())
|
|
||||||
} else if c.Request.URL.Query().Has("p") {
|
|
||||||
filename = fmt.Sprintf("preset-%s.json", c.Request.URL.Query().Get("p"))
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := savePublicTo(path.Join(DashboardDir, filename), scenes); err != nil {
|
|
||||||
log.Println("Unable to savePublicTo:", err.Error())
|
|
||||||
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": "An error occurs during scene saving."})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
c.JSON(http.StatusOK, scenes)
|
|
||||||
}
|
|
||||||
119
admin/api/qa.go
119
admin/api/qa.go
|
|
@ -1,119 +0,0 @@
|
||||||
package api
|
|
||||||
|
|
||||||
import (
|
|
||||||
"log"
|
|
||||||
"net/http"
|
|
||||||
"strconv"
|
|
||||||
|
|
||||||
"srs.epita.fr/fic-server/libfic"
|
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
|
||||||
)
|
|
||||||
|
|
||||||
func declareQARoutes(router *gin.RouterGroup) {
|
|
||||||
router.POST("/qa/", importExerciceQA)
|
|
||||||
|
|
||||||
apiQARoutes := router.Group("/qa/:qid")
|
|
||||||
apiQARoutes.Use(QAHandler)
|
|
||||||
apiQARoutes.POST("/comments", importQAComment)
|
|
||||||
}
|
|
||||||
|
|
||||||
func QAHandler(c *gin.Context) {
|
|
||||||
qid, err := strconv.ParseInt(string(c.Params.ByName("qid")), 10, 64)
|
|
||||||
if err != nil {
|
|
||||||
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"errmsg": "Invalid QA identifier"})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
qa, err := fic.GetQAQuery(qid)
|
|
||||||
if err != nil {
|
|
||||||
c.AbortWithStatusJSON(http.StatusNotFound, gin.H{"errmsg": "QA query not found"})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
c.Set("qa-query", qa)
|
|
||||||
|
|
||||||
c.Next()
|
|
||||||
}
|
|
||||||
|
|
||||||
func importExerciceQA(c *gin.Context) {
|
|
||||||
// Create a new query
|
|
||||||
var uq fic.QAQuery
|
|
||||||
err := c.ShouldBindJSON(&uq)
|
|
||||||
if err != nil {
|
|
||||||
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"errmsg": err.Error()})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
var exercice *fic.Exercice
|
|
||||||
if uq.IdExercice == 0 {
|
|
||||||
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"errmsg": "id_exercice not filled"})
|
|
||||||
return
|
|
||||||
} else if exercice, err = fic.GetExercice(uq.IdExercice); err != nil {
|
|
||||||
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"errmsg": "Unable to find requested exercice"})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(uq.State) == 0 {
|
|
||||||
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"errmsg": "State not filled"})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(uq.Subject) == 0 {
|
|
||||||
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"errmsg": "Subject not filled"})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if qa, err := exercice.NewQAQuery(uq.Subject, uq.IdTeam, uq.User, uq.State, nil); err != nil {
|
|
||||||
log.Println("Unable to importExerciceQA:", err.Error())
|
|
||||||
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": "An error occurs during query creation."})
|
|
||||||
return
|
|
||||||
} else {
|
|
||||||
qa.Creation = uq.Creation
|
|
||||||
qa.Solved = uq.Solved
|
|
||||||
qa.Closed = uq.Closed
|
|
||||||
|
|
||||||
_, err = qa.Update()
|
|
||||||
if err != nil {
|
|
||||||
log.Println("Unable to update in importExerciceQA:", err.Error())
|
|
||||||
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": "An error occurs during query updating."})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
c.JSON(http.StatusOK, qa)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func importQAComment(c *gin.Context) {
|
|
||||||
query := c.MustGet("qa-query").(*fic.QAQuery)
|
|
||||||
|
|
||||||
// Create a new query
|
|
||||||
var uc fic.QAComment
|
|
||||||
err := c.ShouldBindJSON(&uc)
|
|
||||||
if err != nil {
|
|
||||||
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"errmsg": err.Error()})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(uc.Content) == 0 {
|
|
||||||
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"errmsg": "Empty comment"})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if qac, err := query.AddComment(uc.Content, uc.IdTeam, uc.User); err != nil {
|
|
||||||
log.Println("Unable to AddComment in importQAComment:", err.Error())
|
|
||||||
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": "An error occurs during comment creation."})
|
|
||||||
return
|
|
||||||
} else {
|
|
||||||
qac.Date = uc.Date
|
|
||||||
|
|
||||||
_, err = qac.Update()
|
|
||||||
if err != nil {
|
|
||||||
log.Println("Unable to Update comment in importQAComment")
|
|
||||||
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": "An error occurs during comment creation."})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
c.JSON(http.StatusOK, qac)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,67 +0,0 @@
|
||||||
package api
|
|
||||||
|
|
||||||
import (
|
|
||||||
"net/http"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"srs.epita.fr/fic-server/admin/sync"
|
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
|
||||||
)
|
|
||||||
|
|
||||||
func declareRepositoriesRoutes(router *gin.RouterGroup) {
|
|
||||||
if gi, ok := sync.GlobalImporter.(sync.GitImporter); ok {
|
|
||||||
router.GET("/repositories", func(c *gin.Context) {
|
|
||||||
mod, err := gi.GetSubmodules()
|
|
||||||
if err != nil {
|
|
||||||
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": err.Error()})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
c.JSON(http.StatusOK, gin.H{"repositories": mod})
|
|
||||||
})
|
|
||||||
|
|
||||||
router.GET("/repositories/*repopath", func(c *gin.Context) {
|
|
||||||
repopath := strings.TrimPrefix(c.Param("repopath"), "/")
|
|
||||||
|
|
||||||
mod, err := gi.GetSubmodule(repopath)
|
|
||||||
if err != nil {
|
|
||||||
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": err.Error()})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
c.JSON(http.StatusOK, mod)
|
|
||||||
})
|
|
||||||
|
|
||||||
router.POST("/repositories/*repopath", func(c *gin.Context) {
|
|
||||||
repopath := strings.TrimPrefix(c.Param("repopath"), "/")
|
|
||||||
|
|
||||||
mod, err := gi.IsRepositoryUptodate(repopath)
|
|
||||||
if err != nil {
|
|
||||||
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": err.Error()})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
c.JSON(http.StatusOK, mod)
|
|
||||||
})
|
|
||||||
|
|
||||||
router.DELETE("/repositories/*repopath", func(c *gin.Context) {
|
|
||||||
di, ok := sync.GlobalImporter.(sync.DeletableImporter)
|
|
||||||
if !ok {
|
|
||||||
c.AbortWithStatusJSON(http.StatusNotImplemented, gin.H{"errmsg": "Not implemented"})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if strings.Contains(c.Param("repopath"), "..") {
|
|
||||||
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"errmsg": "Repopath contains invalid characters"})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
repopath := strings.TrimPrefix(c.Param("repopath"), "/")
|
|
||||||
|
|
||||||
err := di.DeleteDir(repopath)
|
|
||||||
if err != nil {
|
|
||||||
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": err.Error()})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
c.JSON(http.StatusOK, true)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,29 +0,0 @@
|
||||||
package api
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/gin-gonic/gin"
|
|
||||||
)
|
|
||||||
|
|
||||||
func DeclareRoutes(router *gin.RouterGroup) {
|
|
||||||
apiRoutes := router.Group("/api")
|
|
||||||
|
|
||||||
declareCertificateRoutes(apiRoutes)
|
|
||||||
declareClaimsRoutes(apiRoutes)
|
|
||||||
declareEventsRoutes(apiRoutes)
|
|
||||||
declareExercicesRoutes(apiRoutes)
|
|
||||||
declareExportRoutes(apiRoutes)
|
|
||||||
declareFilesGlobalRoutes(apiRoutes)
|
|
||||||
declareFilesRoutes(apiRoutes)
|
|
||||||
declareGlobalExercicesRoutes(apiRoutes)
|
|
||||||
declareHealthRoutes(apiRoutes)
|
|
||||||
declareMonitorRoutes(apiRoutes)
|
|
||||||
declarePasswordRoutes(apiRoutes)
|
|
||||||
declarePublicRoutes(apiRoutes)
|
|
||||||
declareQARoutes(apiRoutes)
|
|
||||||
declareRepositoriesRoutes(apiRoutes)
|
|
||||||
declareTeamsRoutes(apiRoutes)
|
|
||||||
declareThemesRoutes(apiRoutes)
|
|
||||||
declareSettingsRoutes(apiRoutes)
|
|
||||||
declareSyncRoutes(apiRoutes)
|
|
||||||
DeclareVersionRoutes(apiRoutes)
|
|
||||||
}
|
|
||||||
|
|
@ -1,425 +0,0 @@
|
||||||
package api
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"log"
|
|
||||||
"net/http"
|
|
||||||
"os"
|
|
||||||
"path"
|
|
||||||
"strconv"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"srs.epita.fr/fic-server/admin/generation"
|
|
||||||
"srs.epita.fr/fic-server/admin/sync"
|
|
||||||
"srs.epita.fr/fic-server/libfic"
|
|
||||||
"srs.epita.fr/fic-server/settings"
|
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
|
||||||
)
|
|
||||||
|
|
||||||
var IsProductionEnv = false
|
|
||||||
|
|
||||||
func declareSettingsRoutes(router *gin.RouterGroup) {
|
|
||||||
router.GET("/challenge.json", getChallengeInfo)
|
|
||||||
router.PUT("/challenge.json", saveChallengeInfo)
|
|
||||||
|
|
||||||
router.GET("/settings.json", getSettings)
|
|
||||||
router.PUT("/settings.json", saveSettings)
|
|
||||||
router.DELETE("/settings.json", func(c *gin.Context) {
|
|
||||||
err := ResetSettings()
|
|
||||||
if err != nil {
|
|
||||||
log.Println("Unable to ResetSettings:", err.Error())
|
|
||||||
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": "An error occurs during setting reset."})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
c.JSON(http.StatusOK, true)
|
|
||||||
})
|
|
||||||
|
|
||||||
router.GET("/settings-next", listNextSettings)
|
|
||||||
|
|
||||||
apiNextSettingsRoutes := router.Group("/settings-next/:ts")
|
|
||||||
apiNextSettingsRoutes.Use(NextSettingsHandler)
|
|
||||||
apiNextSettingsRoutes.GET("", getNextSettings)
|
|
||||||
apiNextSettingsRoutes.DELETE("", deleteNextSettings)
|
|
||||||
|
|
||||||
router.POST("/reset", reset)
|
|
||||||
router.POST("/full-generation", fullGeneration)
|
|
||||||
|
|
||||||
router.GET("/prod", func(c *gin.Context) {
|
|
||||||
c.JSON(http.StatusOK, IsProductionEnv)
|
|
||||||
})
|
|
||||||
router.PUT("/prod", func(c *gin.Context) {
|
|
||||||
err := c.ShouldBindJSON(&IsProductionEnv)
|
|
||||||
if err != nil {
|
|
||||||
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"errmsg": err.Error()})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
c.JSON(http.StatusOK, IsProductionEnv)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func NextSettingsHandler(c *gin.Context) {
|
|
||||||
ts, err := strconv.ParseInt(string(c.Params.ByName("ts")), 10, 64)
|
|
||||||
if err != nil {
|
|
||||||
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"errmsg": "Invalid next settings identifier"})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
nsf, err := settings.ReadNextSettingsFile(path.Join(settings.SettingsDir, fmt.Sprintf("%d.json", ts)), ts)
|
|
||||||
if err != nil {
|
|
||||||
c.AbortWithStatusJSON(http.StatusNotFound, gin.H{"errmsg": "Next settings not found"})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
c.Set("next-settings", nsf)
|
|
||||||
|
|
||||||
c.Next()
|
|
||||||
}
|
|
||||||
|
|
||||||
func fullGeneration(c *gin.Context) {
|
|
||||||
resp, err := generation.FullGeneration()
|
|
||||||
if err != nil {
|
|
||||||
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{
|
|
||||||
"errmsg": err.Error(),
|
|
||||||
})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
defer resp.Body.Close()
|
|
||||||
|
|
||||||
v, _ := io.ReadAll(resp.Body)
|
|
||||||
c.JSON(resp.StatusCode, gin.H{
|
|
||||||
"errmsg": string(v),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func GetChallengeInfo() (*settings.ChallengeInfo, error) {
|
|
||||||
var challengeinfo string
|
|
||||||
var err error
|
|
||||||
if sync.GlobalImporter == nil {
|
|
||||||
if fd, err := os.Open(path.Join(settings.SettingsDir, settings.ChallengeFile)); err == nil {
|
|
||||||
defer fd.Close()
|
|
||||||
var buf []byte
|
|
||||||
buf, err = io.ReadAll(fd)
|
|
||||||
if err == nil {
|
|
||||||
challengeinfo = string(buf)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
challengeinfo, err = sync.GetFileContent(sync.GlobalImporter, settings.ChallengeFile)
|
|
||||||
}
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
log.Println("Unable to retrieve challenge.json:", err.Error())
|
|
||||||
return nil, fmt.Errorf("Unable to retrive challenge.json: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
s, err := settings.ReadChallengeInfo(challengeinfo)
|
|
||||||
if err != nil {
|
|
||||||
log.Println("Unable to ReadChallengeInfo:", err.Error())
|
|
||||||
return nil, fmt.Errorf("Unable to read challenge info: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return s, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func getChallengeInfo(c *gin.Context) {
|
|
||||||
if s, err := GetChallengeInfo(); err != nil {
|
|
||||||
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": err.Error()})
|
|
||||||
} else {
|
|
||||||
c.JSON(http.StatusOK, s)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func saveChallengeInfo(c *gin.Context) {
|
|
||||||
var info *settings.ChallengeInfo
|
|
||||||
err := c.ShouldBindJSON(&info)
|
|
||||||
if err != nil {
|
|
||||||
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"errmsg": err.Error()})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if sync.GlobalImporter != nil {
|
|
||||||
jenc, err := json.Marshal(info)
|
|
||||||
if err != nil {
|
|
||||||
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"errmsg": err.Error()})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
err = sync.WriteFileContent(sync.GlobalImporter, "challenge.json", jenc)
|
|
||||||
if err != nil {
|
|
||||||
log.Println("Unable to SaveChallengeInfo:", err.Error())
|
|
||||||
// Ignore the error, try to continue
|
|
||||||
}
|
|
||||||
|
|
||||||
err = sync.ImportChallengeInfo(info, DashboardDir)
|
|
||||||
if err != nil {
|
|
||||||
log.Println("Unable to ImportChallengeInfo:", err.Error())
|
|
||||||
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"errmsg": fmt.Sprintf("Something goes wrong when trying to import related files: %s", err.Error())})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := settings.SaveChallengeInfo(path.Join(settings.SettingsDir, settings.ChallengeFile), info); err != nil {
|
|
||||||
log.Println("Unable to SaveChallengeInfo:", err.Error())
|
|
||||||
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": fmt.Sprintf("Unable to save distributed challenge info: %s", err.Error())})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
c.JSON(http.StatusOK, info)
|
|
||||||
}
|
|
||||||
|
|
||||||
func getSettings(c *gin.Context) {
|
|
||||||
s, err := settings.ReadSettings(path.Join(settings.SettingsDir, settings.SettingsFile))
|
|
||||||
if err != nil {
|
|
||||||
log.Println("Unable to ReadSettings:", err.Error())
|
|
||||||
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": fmt.Sprintf("Unable to read settings: %s", err.Error())})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
s.WorkInProgress = !IsProductionEnv
|
|
||||||
c.Writer.Header().Add("X-FIC-Time", fmt.Sprintf("%d", time.Now().Unix()))
|
|
||||||
c.JSON(http.StatusOK, s)
|
|
||||||
}
|
|
||||||
|
|
||||||
func saveSettings(c *gin.Context) {
|
|
||||||
var config *settings.Settings
|
|
||||||
err := c.ShouldBindJSON(&config)
|
|
||||||
if err != nil {
|
|
||||||
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"errmsg": err.Error()})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Is this a future setting?
|
|
||||||
if c.Request.URL.Query().Has("t") {
|
|
||||||
t, err := time.Parse(time.RFC3339, c.Request.URL.Query().Get("t"))
|
|
||||||
if err != nil {
|
|
||||||
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"errmsg": err.Error()})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Load current settings to perform diff later
|
|
||||||
init_settings, err := settings.ReadSettings(path.Join(settings.SettingsDir, settings.SettingsFile))
|
|
||||||
if err != nil {
|
|
||||||
log.Println("Unable to ReadSettings:", err.Error())
|
|
||||||
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": fmt.Sprintf("Unable to read settings: %s", err.Error())})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
current_settings := init_settings
|
|
||||||
// Apply already registered settings
|
|
||||||
nsu, err := settings.MergeNextSettingsUntil(&t)
|
|
||||||
if err == nil {
|
|
||||||
current_settings = settings.MergeSettings(*init_settings, nsu)
|
|
||||||
} else {
|
|
||||||
log.Println("Unable to MergeNextSettingsUntil:", err.Error())
|
|
||||||
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": fmt.Sprintf("Unable to merge next settings: %s", err.Error())})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Keep only diff
|
|
||||||
diff := settings.DiffSettings(current_settings, config)
|
|
||||||
|
|
||||||
hasItems := false
|
|
||||||
for _, _ = range diff {
|
|
||||||
hasItems = true
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
if !hasItems {
|
|
||||||
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"errmsg": "No difference to apply."})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if !c.Request.URL.Query().Has("erase") {
|
|
||||||
// Check if there is already diff to apply at the given time
|
|
||||||
if nsf, err := settings.ReadNextSettingsFile(path.Join(settings.SettingsDir, fmt.Sprintf("%d.json", t.Unix())), t.Unix()); err == nil {
|
|
||||||
for k, v := range nsf.Values {
|
|
||||||
if _, ok := diff[k]; !ok {
|
|
||||||
diff[k] = v
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Save the diff
|
|
||||||
settings.SaveSettings(path.Join(settings.SettingsDir, fmt.Sprintf("%d.json", t.Unix())), diff)
|
|
||||||
|
|
||||||
// Return current settings
|
|
||||||
c.JSON(http.StatusOK, current_settings)
|
|
||||||
} else {
|
|
||||||
// Just apply settings right now!
|
|
||||||
if err := settings.SaveSettings(path.Join(settings.SettingsDir, settings.SettingsFile), config); err != nil {
|
|
||||||
log.Println("Unable to SaveSettings:", err.Error())
|
|
||||||
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": fmt.Sprintf("Unable to save settings: %s", err.Error())})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
ApplySettings(config)
|
|
||||||
c.JSON(http.StatusOK, config)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func listNextSettings(c *gin.Context) {
|
|
||||||
nsf, err := settings.ListNextSettingsFiles()
|
|
||||||
if err != nil {
|
|
||||||
log.Println("Unable to ListNextSettingsFiles:", err.Error())
|
|
||||||
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": fmt.Sprintf("Unable to list next settings files: %s", err.Error())})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
c.JSON(http.StatusOK, nsf)
|
|
||||||
}
|
|
||||||
|
|
||||||
func getNextSettings(c *gin.Context) {
|
|
||||||
c.JSON(http.StatusOK, c.MustGet("next-settings").(*settings.NextSettingsFile))
|
|
||||||
}
|
|
||||||
|
|
||||||
func deleteNextSettings(c *gin.Context) {
|
|
||||||
nsf := c.MustGet("next-settings").(*settings.NextSettingsFile)
|
|
||||||
|
|
||||||
err := os.Remove(path.Join(settings.SettingsDir, fmt.Sprintf("%d.json", nsf.Id)))
|
|
||||||
if err != nil {
|
|
||||||
log.Println("Unable to remove the file:", err.Error())
|
|
||||||
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": fmt.Sprintf("Unable to remove the file: %s", err.Error())})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
c.JSON(http.StatusOK, true)
|
|
||||||
}
|
|
||||||
|
|
||||||
func ApplySettings(config *settings.Settings) {
|
|
||||||
fic.PartialValidation = config.PartialValidation
|
|
||||||
fic.UnlockedChallengeDepth = config.UnlockedChallengeDepth
|
|
||||||
fic.UnlockedChallengeUpTo = config.UnlockedChallengeUpTo
|
|
||||||
fic.DisplayAllFlags = config.DisplayAllFlags
|
|
||||||
fic.HideCaseSensitivity = config.HideCaseSensitivity
|
|
||||||
fic.UnlockedStandaloneExercices = config.UnlockedStandaloneExercices
|
|
||||||
fic.UnlockedStandaloneExercicesByThemeStepValidation = config.UnlockedStandaloneExercicesByThemeStepValidation
|
|
||||||
fic.UnlockedStandaloneExercicesByStandaloneExerciceValidation = config.UnlockedStandaloneExercicesByStandaloneExerciceValidation
|
|
||||||
fic.DisplayMCQBadCount = config.DisplayMCQBadCount
|
|
||||||
fic.FirstBlood = config.FirstBlood
|
|
||||||
fic.SubmissionCostBase = config.SubmissionCostBase
|
|
||||||
fic.HintCoefficient = config.HintCurCoefficient
|
|
||||||
fic.WChoiceCoefficient = config.WChoiceCurCoefficient
|
|
||||||
fic.ExerciceCurrentCoefficient = config.ExerciceCurCoefficient
|
|
||||||
fic.GlobalScoreCoefficient = config.GlobalScoreCoefficient
|
|
||||||
fic.SubmissionCostBase = config.SubmissionCostBase
|
|
||||||
fic.SubmissionUniqueness = config.SubmissionUniqueness
|
|
||||||
fic.CountOnlyNotGoodTries = config.CountOnlyNotGoodTries
|
|
||||||
fic.QuestionGainRatio = config.QuestionGainRatio
|
|
||||||
|
|
||||||
if config.DiscountedFactor != fic.DiscountedFactor {
|
|
||||||
fic.DiscountedFactor = config.DiscountedFactor
|
|
||||||
if err := fic.DBRecreateDiscountedView(); err != nil {
|
|
||||||
log.Println("Unable to recreate exercices_discounted view:", err.Error())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func ResetSettings() error {
|
|
||||||
return settings.SaveSettings(path.Join(settings.SettingsDir, settings.SettingsFile), &settings.Settings{
|
|
||||||
WorkInProgress: IsProductionEnv,
|
|
||||||
FirstBlood: fic.FirstBlood,
|
|
||||||
SubmissionCostBase: fic.SubmissionCostBase,
|
|
||||||
ExerciceCurCoefficient: 1,
|
|
||||||
HintCurCoefficient: 1,
|
|
||||||
WChoiceCurCoefficient: 1,
|
|
||||||
GlobalScoreCoefficient: 1,
|
|
||||||
DiscountedFactor: 0,
|
|
||||||
QuestionGainRatio: 0,
|
|
||||||
UnlockedStandaloneExercices: 10,
|
|
||||||
UnlockedStandaloneExercicesByThemeStepValidation: 1,
|
|
||||||
UnlockedStandaloneExercicesByStandaloneExerciceValidation: 0,
|
|
||||||
AllowRegistration: false,
|
|
||||||
CanJoinTeam: false,
|
|
||||||
DenyTeamCreation: false,
|
|
||||||
DenyNameChange: false,
|
|
||||||
AcceptNewIssue: true,
|
|
||||||
QAenabled: false,
|
|
||||||
EnableResolutionRoute: false,
|
|
||||||
PartialValidation: true,
|
|
||||||
UnlockedChallengeDepth: 0,
|
|
||||||
SubmissionUniqueness: true,
|
|
||||||
CountOnlyNotGoodTries: true,
|
|
||||||
DisplayAllFlags: false,
|
|
||||||
DisplayMCQBadCount: false,
|
|
||||||
EventKindness: false,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func ResetChallengeInfo() error {
|
|
||||||
return settings.SaveChallengeInfo(path.Join(settings.SettingsDir, settings.ChallengeFile), &settings.ChallengeInfo{
|
|
||||||
Title: "Challenge forensic",
|
|
||||||
SubTitle: "sous le patronage du commandement de la cyberdéfense",
|
|
||||||
Authors: "Laboratoire SRS, ÉPITA",
|
|
||||||
VideosLink: "",
|
|
||||||
Description: `<p>Le challenge <em>forensic</em> vous place dans la peau de <strong>spécialistes en investigation numérique</strong>. Nous mettons à votre disposition une <strong>vingtaine de scénarios différents</strong>, dans lesquels vous devrez faire les différentes étapes <strong>de la caractérisation d’une réponse à incident</strong> proposées.</p>
|
|
||||||
<p>Chaque scénario met en scène un contexte d’<strong>entreprise</strong>, ayant découvert récemment qu’elle a été <strong>victime d’une cyberattaque</strong>. Elle vous demande alors de l’aider à <strong>caractériser</strong>, afin de mieux comprendre <strong>la situation</strong>, notamment le <strong>mode opératoire de l’adversaire</strong>, les <strong>impacts</strong> de la cyberattaque, le <strong>périmètre technique compromis</strong>, etc. Il faudra parfois aussi l’éclairer sur les premières étapes de la réaction.</p>`,
|
|
||||||
Rules: `<h3>Déroulement</h3>
|
|
||||||
<p>Pendant toute la durée du challenge, vous aurez <strong>accès à tous les scénarios</strong>, mais seulement à la première des 5 étapes. <strong>Chaque étape</strong> supplémentaire <strong>est débloquée lorsque vous validez l’intégralité de l’étape précédente</strong>. Toutefois, pour dynamiser le challenge toutes les étapes et tous les scénarios seront débloquées pour la dernière heure du challenge.</p>
|
|
||||||
<p>Nous mettons à votre disposition une <strong>plateforme</strong> sur laquelle vous pourrez <strong>obtenir les informations sur le contexte</strong> de l’entreprise et, généralement, une <strong>série de fichiers</strong> qui semblent appropriés pour avancer dans l’investigation.</p>
|
|
||||||
<p>La <strong>validation d’une étape</strong> se fait sur la plateforme, après avoir analysé les informations fournies, en <strong>répondant à des questions</strong> plus ou moins précises. Il s’agit le plus souvent des <strong>mots-clefs</strong> que l’on placerait dans un <strong>rapport</strong>.</p>
|
|
||||||
<p>Pour vous débloquer ou accélérer votre investigation, vous pouvez accéder à quelques <strong><em>indices</em></strong>, en échange d’une décote sur votre score d’un certain nombre de points préalablement affichés.</p>
|
|
||||||
<h3>Calcul des points, bonus, malus et classement</h3>
|
|
||||||
<p>Chaque équipe dispose d’un <strong>compteur de points</strong> dans l’intervalle ]-∞;+∞[ (aux détails techniques près), à partir duquel <strong>le classement est établi</strong>.</p>
|
|
||||||
<p>Vous <strong>perdez des points</strong> en <strong>dévoilant des indices</strong>, en <strong>demandant des propositions de réponses</strong> en remplacement de certains champs de texte, ou en <strong>essayant un trop grand nombre de fois une réponse</strong>.</p>
|
|
||||||
<p>Le nombre de points que vous fait perdre un indice dépend habituellement de l’aide qu’il vous apportera et est indiqué avant de le dévoiler, car il peut fluctuer en fonction de l’avancement du challenge.</p>
|
|
||||||
<p>Pour chaque champ de texte, vous disposez de 10 tentatives avant de perdre des points (vous perdez les points même si vous ne validez pas l’étape) pour chaque tentative supplémentaire : -0,25 point entre 11 et 20, -0,5 entre 21 et 30, -0,75 entre 31 et 40, …</p>
|
|
||||||
<p>La seule manière de <strong>gagner des points</strong> est de <strong>valider une étape d’un scénario dans son intégralité</strong>. Le nombre de points gagnés <strong>dépend de la difficulté théorique</strong> de l’étape ainsi que <strong>d’éventuels bonus</strong>. Un bonus de <strong>10 %</strong> est accordé à la première équipe qui valide une étape. D’<strong>autres bonus</strong> peuvent ponctuer le challenge, détaillé dans la partie suivante.</p>
|
|
||||||
<p>Le classement est établi par équipe, selon le nombre de points récoltés et perdus par tous les membres. En cas d’égalité au score, les équipes sont départagées en fonction de leur ordre d’arrivée à ce score.</p>
|
|
||||||
<h3>Temps forts</h3>
|
|
||||||
<p>Le challenge <em>forensic</em> est jalonné de plusieurs temps forts durant lesquels <strong>certains calculs</strong> détaillés dans la partie précédente <strong>peuvent être altérés</strong>. L’équipe d’animation du challenge vous <strong>avertira</strong> environ <strong>15 minutes avant</strong> le début de la modification.</p>
|
|
||||||
<p>Chaque modification se répercute instantanément dans votre interface, attendez simplement qu’elle apparaisse afin d’être certain d’en bénéficier. Un compte à rebours est généralement affiché sur les écrans pour indiquer la fin d’un temps fort. La fin d’application d’un bonus est déterminé par l’heure d’arrivée de votre demande sur nos serveurs.</p>
|
|
||||||
<p>Sans y être limité ou assuré, sachez que durant les précédentes éditions du challenge <em>forensic</em>, nous avons par exemple : <strong>doublé les points</strong> de défis peu tentés, <strong>doublé les points de tous les défis</strong> pendant 30 minutes, <strong>réduit le coût des indices</strong> pendant 15 minutes, etc.</p>
|
|
||||||
<p></p>
|
|
||||||
<p>Tous les étudiants de la majeure Système, Réseaux et Sécurité de l’ÉPITA, son équipe enseignante ainsi que le commandement de la cyberdéfense vous souhaitent bon courage pour cette nouvelle éditions du challenge !</p>`,
|
|
||||||
YourMission: `<h4>Bienvenue au challenge forensic !</h4>
|
|
||||||
<p>Vous voici aujourd'hui dans la peau de <strong>spécialistes en investigation numérique</strong>. Vous avez à votre disposition une vingtaine de scénarios différents dans lesquels vous devrez faire les différentes étapes <strong>de la caractérisation d’une réponse à incident</strong>.</p>
|
|
||||||
<p>Chaque scénario est découpé en 5 grandes <strong>étapes de difficulté croissante</strong>. Un certain nombre de points est attribué à chaque étape, avec un processus de validation automatique.</p>
|
|
||||||
<p>Un classement est établi en temps réel, tenant compte des différents bonus, en fonction du nombre de points de chaque équipe.</p>`,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func reset(c *gin.Context) {
|
|
||||||
var m map[string]string
|
|
||||||
err := c.ShouldBindJSON(&m)
|
|
||||||
if err != nil {
|
|
||||||
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"errmsg": err.Error()})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
t, ok := m["type"]
|
|
||||||
if !ok {
|
|
||||||
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"errmsg": "Field type not found"})
|
|
||||||
}
|
|
||||||
|
|
||||||
switch t {
|
|
||||||
case "teams":
|
|
||||||
err = fic.ResetTeams()
|
|
||||||
case "challenges":
|
|
||||||
err = fic.ResetExercices()
|
|
||||||
case "game":
|
|
||||||
err = fic.ResetGame()
|
|
||||||
case "annexes":
|
|
||||||
err = fic.ResetAnnexes()
|
|
||||||
case "settings":
|
|
||||||
err = ResetSettings()
|
|
||||||
case "challengeInfo":
|
|
||||||
err = ResetChallengeInfo()
|
|
||||||
default:
|
|
||||||
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"errmsg": "Unknown reset type"})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
log.Printf("Unable to reset (type=%q): %s", t, err)
|
|
||||||
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": fmt.Sprintf("Unable to performe the reset: %s", err.Error())})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
c.JSON(http.StatusOK, true)
|
|
||||||
}
|
|
||||||
|
|
@ -1,411 +0,0 @@
|
||||||
package api
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"log"
|
|
||||||
"net/http"
|
|
||||||
"net/url"
|
|
||||||
"os"
|
|
||||||
"path"
|
|
||||||
"reflect"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"srs.epita.fr/fic-server/admin/generation"
|
|
||||||
"srs.epita.fr/fic-server/admin/sync"
|
|
||||||
"srs.epita.fr/fic-server/libfic"
|
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
|
||||||
"go.uber.org/multierr"
|
|
||||||
)
|
|
||||||
|
|
||||||
var lastSyncError = ""
|
|
||||||
|
|
||||||
func flatifySyncErrors(errs error) (ret []string) {
|
|
||||||
for _, err := range multierr.Errors(errs) {
|
|
||||||
ret = append(ret, err.Error())
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func declareSyncRoutes(router *gin.RouterGroup) {
|
|
||||||
apiSyncRoutes := router.Group("/sync")
|
|
||||||
|
|
||||||
// Return the global sync status
|
|
||||||
apiSyncRoutes.GET("/status", func(c *gin.Context) {
|
|
||||||
syncMtd := "Disabled"
|
|
||||||
if sync.GlobalImporter != nil {
|
|
||||||
syncMtd = sync.GlobalImporter.Kind()
|
|
||||||
}
|
|
||||||
|
|
||||||
var syncId *string
|
|
||||||
if sync.GlobalImporter != nil {
|
|
||||||
syncId = sync.GlobalImporter.Id()
|
|
||||||
}
|
|
||||||
|
|
||||||
c.JSON(http.StatusOK, gin.H{
|
|
||||||
"sync-type": reflect.TypeOf(sync.GlobalImporter).Name(),
|
|
||||||
"sync-id": syncId,
|
|
||||||
"sync": syncMtd,
|
|
||||||
"pullMutex": !sync.OneGitPullStatus(),
|
|
||||||
"syncMutex": !sync.OneDeepSyncStatus() && !sync.OneThemeDeepSyncStatus(),
|
|
||||||
"progress": sync.DeepSyncProgress,
|
|
||||||
"lastError": lastSyncError,
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
// Base sync checks if the local directory is in sync with remote one.
|
|
||||||
apiSyncRoutes.POST("/base", func(c *gin.Context) {
|
|
||||||
err := sync.GlobalImporter.Sync()
|
|
||||||
if err != nil {
|
|
||||||
lastSyncError = err.Error()
|
|
||||||
c.JSON(http.StatusExpectationFailed, gin.H{"errmsg": err.Error()})
|
|
||||||
} else {
|
|
||||||
lastSyncError = ""
|
|
||||||
c.JSON(http.StatusOK, true)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
// Speedy sync performs a recursive synchronization without importing files.
|
|
||||||
apiSyncRoutes.POST("/speed", func(c *gin.Context) {
|
|
||||||
st := sync.SpeedySyncDeep(sync.GlobalImporter)
|
|
||||||
sync.EditDeepReport(&st, false)
|
|
||||||
c.JSON(http.StatusOK, st)
|
|
||||||
})
|
|
||||||
|
|
||||||
// Deep sync: a fully recursive synchronization (can be limited by theme).
|
|
||||||
apiSyncRoutes.POST("/deep", func(c *gin.Context) {
|
|
||||||
r := sync.SyncDeep(sync.GlobalImporter)
|
|
||||||
lastSyncError = ""
|
|
||||||
c.JSON(http.StatusOK, r)
|
|
||||||
})
|
|
||||||
|
|
||||||
apiSyncRoutes.POST("/local-diff", APIDiffDBWithRemote)
|
|
||||||
|
|
||||||
apiSyncDeepRoutes := apiSyncRoutes.Group("/deep/:thid")
|
|
||||||
apiSyncDeepRoutes.Use(ThemeHandler)
|
|
||||||
// Special route to handle standalone exercices
|
|
||||||
apiSyncRoutes.POST("/deep/0", func(c *gin.Context) {
|
|
||||||
var st []string
|
|
||||||
for _, se := range multierr.Errors(sync.SyncThemeDeep(sync.GlobalImporter, &fic.StandaloneExercicesTheme, 0, 250, nil)) {
|
|
||||||
st = append(st, se.Error())
|
|
||||||
}
|
|
||||||
sync.EditDeepReport(&sync.SyncReport{Exercices: st}, false)
|
|
||||||
sync.DeepSyncProgress = 255
|
|
||||||
lastSyncError = ""
|
|
||||||
c.JSON(http.StatusOK, st)
|
|
||||||
})
|
|
||||||
apiSyncDeepRoutes.POST("", func(c *gin.Context) {
|
|
||||||
theme := c.MustGet("theme").(*fic.Theme)
|
|
||||||
|
|
||||||
exceptions := sync.LoadThemeException(sync.GlobalImporter, theme)
|
|
||||||
|
|
||||||
var st []string
|
|
||||||
for _, se := range multierr.Errors(sync.SyncThemeDeep(sync.GlobalImporter, theme, 0, 250, exceptions)) {
|
|
||||||
st = append(st, se.Error())
|
|
||||||
}
|
|
||||||
sync.EditDeepReport(&sync.SyncReport{Themes: map[string][]string{theme.Name: st}}, false)
|
|
||||||
sync.DeepSyncProgress = 255
|
|
||||||
lastSyncError = ""
|
|
||||||
c.JSON(http.StatusOK, st)
|
|
||||||
})
|
|
||||||
|
|
||||||
// Auto sync: to use with continuous deployment, in a development env
|
|
||||||
apiSyncRoutes.POST("/auto/*p", autoSync)
|
|
||||||
|
|
||||||
// Themes
|
|
||||||
apiSyncRoutes.POST("/fixurlids", fixAllURLIds)
|
|
||||||
|
|
||||||
apiSyncRoutes.POST("/themes", func(c *gin.Context) {
|
|
||||||
_, errs := sync.SyncThemes(sync.GlobalImporter)
|
|
||||||
lastSyncError = ""
|
|
||||||
c.JSON(http.StatusOK, flatifySyncErrors(errs))
|
|
||||||
})
|
|
||||||
|
|
||||||
apiSyncThemesRoutes := apiSyncRoutes.Group("/themes/:thid")
|
|
||||||
apiSyncThemesRoutes.Use(ThemeHandler)
|
|
||||||
apiSyncThemesRoutes.POST("/fixurlid", func(c *gin.Context) {
|
|
||||||
theme := c.MustGet("theme").(*fic.Theme)
|
|
||||||
if theme.FixURLId() {
|
|
||||||
v, err := theme.Update()
|
|
||||||
if err != nil {
|
|
||||||
log.Println("Unable to UpdateTheme after fixurlid:", err.Error())
|
|
||||||
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": "An error occurs when saving the theme."})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
c.JSON(http.StatusOK, v)
|
|
||||||
} else {
|
|
||||||
c.AbortWithStatusJSON(http.StatusOK, 0)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
// Exercices
|
|
||||||
declareSyncExercicesRoutes(apiSyncRoutes)
|
|
||||||
declareSyncExercicesRoutes(apiSyncThemesRoutes)
|
|
||||||
|
|
||||||
// Videos sync imports resolution.mp4 from path stored in database.
|
|
||||||
apiSyncRoutes.POST("/videos", func(c *gin.Context) {
|
|
||||||
exercices, err := fic.GetExercices()
|
|
||||||
if err != nil {
|
|
||||||
log.Println("Unable to GetExercices:", err.Error())
|
|
||||||
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": "Unable to retrieve exercices list."})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, e := range exercices {
|
|
||||||
if len(e.VideoURI) == 0 || !strings.HasPrefix(e.VideoURI, "$RFILES$/") {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
vpath, err := url.PathUnescape(strings.TrimPrefix(e.VideoURI, "$RFILES$/"))
|
|
||||||
if err != nil {
|
|
||||||
c.JSON(http.StatusExpectationFailed, gin.H{"errmsg": fmt.Sprintf("Unable to perform URL unescape: %s", err.Error())})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
_, err = sync.ImportFile(sync.GlobalImporter, vpath, func(filePath, URI string) (interface{}, error) {
|
|
||||||
e.VideoURI = path.Join("$FILES$", strings.TrimPrefix(filePath, fic.FilesDir))
|
|
||||||
return e.Update()
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
c.JSON(http.StatusExpectationFailed, gin.H{"errmsg": err.Error()})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
c.JSON(http.StatusOK, true)
|
|
||||||
})
|
|
||||||
|
|
||||||
// Remove soluces from the database.
|
|
||||||
apiSyncRoutes.POST("/drop_soluces", func(c *gin.Context) {
|
|
||||||
exercices, err := fic.GetExercices()
|
|
||||||
if err != nil {
|
|
||||||
log.Println("Unable to GetExercices:", err.Error())
|
|
||||||
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": "Unable to retrieve exercices list."})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
var errs error
|
|
||||||
for _, e := range exercices {
|
|
||||||
// Remove any published video
|
|
||||||
if len(e.VideoURI) > 0 && strings.HasPrefix(e.VideoURI, "$FILES$") {
|
|
||||||
vpath := path.Join(fic.FilesDir, strings.TrimPrefix(e.VideoURI, "$FILES$/"))
|
|
||||||
err = os.Remove(vpath)
|
|
||||||
if err != nil {
|
|
||||||
errs = multierr.Append(errs, fmt.Errorf("unable to delete published video (%q): %w", e.VideoURI, err))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Clean the database
|
|
||||||
if len(e.VideoURI) > 0 || len(e.Resolution) > 0 {
|
|
||||||
e.VideoURI = ""
|
|
||||||
e.Resolution = ""
|
|
||||||
|
|
||||||
_, err = e.Update()
|
|
||||||
if err != nil {
|
|
||||||
errs = multierr.Append(errs, fmt.Errorf("unable to update exercice (%d: %s): %w", e.Id, e.Title, err))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
c.JSON(http.StatusInternalServerError, gin.H{"errmsg": flatifySyncErrors(err)})
|
|
||||||
} else {
|
|
||||||
c.JSON(http.StatusOK, true)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func declareSyncExercicesRoutes(router *gin.RouterGroup) {
|
|
||||||
router.POST("/exercices", func(c *gin.Context) {
|
|
||||||
theme := c.MustGet("theme").(*fic.Theme)
|
|
||||||
exceptions := sync.LoadThemeException(sync.GlobalImporter, theme)
|
|
||||||
|
|
||||||
_, errs := sync.SyncExercices(sync.GlobalImporter, theme, exceptions)
|
|
||||||
c.JSON(http.StatusOK, flatifySyncErrors(errs))
|
|
||||||
})
|
|
||||||
apiSyncExercicesRoutes := router.Group("/exercices/:eid")
|
|
||||||
apiSyncExercicesRoutes.Use(ExerciceHandler)
|
|
||||||
apiSyncExercicesRoutes.POST("", func(c *gin.Context) {
|
|
||||||
theme := c.MustGet("theme").(*fic.Theme)
|
|
||||||
exercice := c.MustGet("exercice").(*fic.Exercice)
|
|
||||||
|
|
||||||
exceptions := sync.LoadExerciceException(sync.GlobalImporter, theme, exercice, nil)
|
|
||||||
|
|
||||||
_, _, _, errs := sync.SyncExercice(sync.GlobalImporter, theme, exercice.Path, nil, exceptions)
|
|
||||||
c.JSON(http.StatusOK, flatifySyncErrors(errs))
|
|
||||||
})
|
|
||||||
apiSyncExercicesRoutes.POST("/files", func(c *gin.Context) {
|
|
||||||
exercice := c.MustGet("exercice").(*fic.Exercice)
|
|
||||||
theme := c.MustGet("theme").(*fic.Theme)
|
|
||||||
|
|
||||||
exceptions := sync.LoadExerciceException(sync.GlobalImporter, theme, exercice, nil)
|
|
||||||
|
|
||||||
c.JSON(http.StatusOK, flatifySyncErrors(sync.ImportExerciceFiles(sync.GlobalImporter, exercice, exceptions)))
|
|
||||||
})
|
|
||||||
apiSyncExercicesRoutes.POST("/fixurlid", func(c *gin.Context) {
|
|
||||||
exercice := c.MustGet("exercice").(*fic.Exercice)
|
|
||||||
if exercice.FixURLId() {
|
|
||||||
v, err := exercice.Update()
|
|
||||||
if err != nil {
|
|
||||||
log.Println("Unable to UpdateExercice after fixurlid:", err.Error())
|
|
||||||
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": "An error occurs when saving the exercice."})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
c.JSON(http.StatusOK, v)
|
|
||||||
} else {
|
|
||||||
c.AbortWithStatusJSON(http.StatusOK, 0)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
apiSyncExercicesRoutes.POST("/hints", func(c *gin.Context) {
|
|
||||||
exercice := c.MustGet("exercice").(*fic.Exercice)
|
|
||||||
theme := c.MustGet("theme").(*fic.Theme)
|
|
||||||
|
|
||||||
exceptions := sync.LoadExerciceException(sync.GlobalImporter, theme, exercice, nil)
|
|
||||||
|
|
||||||
_, errs := sync.SyncExerciceHints(sync.GlobalImporter, exercice, sync.ExerciceFlagsMap(sync.GlobalImporter, exercice), exceptions)
|
|
||||||
c.JSON(http.StatusOK, flatifySyncErrors(errs))
|
|
||||||
})
|
|
||||||
apiSyncExercicesRoutes.POST("/flags", func(c *gin.Context) {
|
|
||||||
exercice := c.MustGet("exercice").(*fic.Exercice)
|
|
||||||
theme := c.MustGet("theme").(*fic.Theme)
|
|
||||||
|
|
||||||
exceptions := sync.LoadExerciceException(sync.GlobalImporter, theme, exercice, nil)
|
|
||||||
_, errs := sync.SyncExerciceFlags(sync.GlobalImporter, exercice, exceptions)
|
|
||||||
_, herrs := sync.SyncExerciceHints(sync.GlobalImporter, exercice, sync.ExerciceFlagsMap(sync.GlobalImporter, exercice), exceptions)
|
|
||||||
c.JSON(http.StatusOK, flatifySyncErrors(multierr.Append(errs, herrs)))
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// autoSync tries to performs a smart synchronization, when in development environment.
|
|
||||||
// It'll sync most of modified things, and will delete out of sync data.
|
|
||||||
// Avoid using it in a production environment.
|
|
||||||
func autoSync(c *gin.Context) {
|
|
||||||
p := strings.Split(strings.TrimPrefix(c.Params.ByName("p"), "/"), "/")
|
|
||||||
|
|
||||||
if !IsProductionEnv {
|
|
||||||
if err := sync.GlobalImporter.Sync(); err != nil {
|
|
||||||
lastSyncError = err.Error()
|
|
||||||
log.Println("Unable to sync.GI.Sync:", err.Error())
|
|
||||||
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": "Unable to perform the pull."})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
lastSyncError = ""
|
|
||||||
}
|
|
||||||
|
|
||||||
themes, err := fic.GetThemes()
|
|
||||||
if err != nil {
|
|
||||||
log.Println("Unable to GetThemes:", err.Error())
|
|
||||||
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": "Unable to retrieve theme list."})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// No argument, do a deep sync
|
|
||||||
if len(p) == 0 {
|
|
||||||
if !IsProductionEnv {
|
|
||||||
for _, theme := range themes {
|
|
||||||
theme.DeleteDeep()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
st := sync.SyncDeep(sync.GlobalImporter)
|
|
||||||
c.JSON(http.StatusOK, st)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
var theTheme *fic.Theme
|
|
||||||
|
|
||||||
// Find the given theme
|
|
||||||
for _, theme := range themes {
|
|
||||||
if theme.Path == p[0] {
|
|
||||||
theTheme = theme
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if theTheme == nil {
|
|
||||||
// The theme doesn't exists locally, perhaps it has not been imported already?
|
|
||||||
rThemes, err := sync.GetThemes(sync.GlobalImporter)
|
|
||||||
if err == nil {
|
|
||||||
for _, theme := range rThemes {
|
|
||||||
if theme == p[0] {
|
|
||||||
sync.SyncThemes(sync.GlobalImporter)
|
|
||||||
|
|
||||||
themes, err := fic.GetThemes()
|
|
||||||
if err == nil {
|
|
||||||
for _, theme := range themes {
|
|
||||||
if theme.Path == p[0] {
|
|
||||||
theTheme = theme
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if theTheme == nil {
|
|
||||||
c.AbortWithStatusJSON(http.StatusNotFound, gin.H{"errmsg": fmt.Sprintf("Theme not found %q", p[0])})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if !IsProductionEnv {
|
|
||||||
exercices, err := theTheme.GetExercices()
|
|
||||||
if err == nil {
|
|
||||||
for _, exercice := range exercices {
|
|
||||||
if len(p) <= 1 || exercice.Path == path.Join(p[0], p[1]) {
|
|
||||||
exercice.DeleteDeep()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
exceptions := sync.LoadThemeException(sync.GlobalImporter, theTheme)
|
|
||||||
|
|
||||||
var st []string
|
|
||||||
for _, se := range multierr.Errors(sync.SyncThemeDeep(sync.GlobalImporter, theTheme, 0, 250, exceptions)) {
|
|
||||||
st = append(st, se.Error())
|
|
||||||
}
|
|
||||||
sync.EditDeepReport(&sync.SyncReport{Themes: map[string][]string{theTheme.Name: st}}, false)
|
|
||||||
sync.DeepSyncProgress = 255
|
|
||||||
|
|
||||||
resp, err := generation.FullGeneration()
|
|
||||||
if err == nil {
|
|
||||||
defer resp.Body.Close()
|
|
||||||
}
|
|
||||||
|
|
||||||
c.JSON(http.StatusOK, st)
|
|
||||||
}
|
|
||||||
|
|
||||||
func diffDBWithRemote() (map[string][]syncDiff, error) {
|
|
||||||
diffs := map[string][]syncDiff{}
|
|
||||||
|
|
||||||
themes, err := fic.GetThemesExtended()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Compare inner themes
|
|
||||||
for _, theme := range themes {
|
|
||||||
diffs[theme.Name], err = diffThemeWithRemote(theme)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("Unable to diffThemeWithRemote: %w", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return diffs, err
|
|
||||||
}
|
|
||||||
|
|
||||||
func APIDiffDBWithRemote(c *gin.Context) {
|
|
||||||
diffs, err := diffDBWithRemote()
|
|
||||||
if err != nil {
|
|
||||||
c.JSON(http.StatusInternalServerError, gin.H{"errmsg": err.Error()})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
c.JSON(http.StatusOK, diffs)
|
|
||||||
}
|
|
||||||
|
|
@ -1,641 +0,0 @@
|
||||||
package api
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
|
||||||
"log"
|
|
||||||
"net/http"
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"srs.epita.fr/fic-server/admin/pki"
|
|
||||||
"srs.epita.fr/fic-server/libfic"
|
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
|
||||||
)
|
|
||||||
|
|
||||||
func declareTeamsRoutes(router *gin.RouterGroup) {
|
|
||||||
router.GET("/teams.json", func(c *gin.Context) {
|
|
||||||
teams, err := fic.ExportTeams(false)
|
|
||||||
if err != nil {
|
|
||||||
log.Println("Unable to ExportTeams:", err.Error())
|
|
||||||
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": "An error occurs during teams export."})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
c.JSON(http.StatusOK, teams)
|
|
||||||
})
|
|
||||||
router.GET("/teams-members.json", func(c *gin.Context) {
|
|
||||||
teams, err := fic.ExportTeams(true)
|
|
||||||
if err != nil {
|
|
||||||
log.Println("Unable to ExportTeams:", err.Error())
|
|
||||||
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": "An error occurs during teams export."})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
c.JSON(http.StatusOK, teams)
|
|
||||||
})
|
|
||||||
router.GET("/teams-associations.json", allAssociations)
|
|
||||||
router.GET("/teams-binding", bindingTeams)
|
|
||||||
router.GET("/teams-nginx", nginxGenTeams)
|
|
||||||
router.POST("/refine_colors", refineTeamsColors)
|
|
||||||
router.POST("/disableinactiveteams", disableInactiveTeams)
|
|
||||||
router.POST("/enableallteams", enableAllTeams)
|
|
||||||
router.GET("/teams-members-nginx", nginxGenMember)
|
|
||||||
router.GET("/teams-tries.json", func(c *gin.Context) {
|
|
||||||
tries, err := fic.GetTries(nil, nil)
|
|
||||||
if err != nil {
|
|
||||||
log.Println("Unable to GetTries:", err.Error())
|
|
||||||
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": "Unable to retrieves tries."})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
c.JSON(http.StatusOK, tries)
|
|
||||||
})
|
|
||||||
|
|
||||||
router.GET("/teams", func(c *gin.Context) {
|
|
||||||
teams, err := fic.GetTeams()
|
|
||||||
if err != nil {
|
|
||||||
log.Println("Unable to GetTeams:", err.Error())
|
|
||||||
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": "An error occurs during teams listing."})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
c.JSON(http.StatusOK, teams)
|
|
||||||
})
|
|
||||||
router.POST("/teams", createTeam)
|
|
||||||
|
|
||||||
apiTeamsRoutes := router.Group("/teams/:tid")
|
|
||||||
apiTeamsRoutes.Use(TeamHandler)
|
|
||||||
apiTeamsRoutes.GET("/", func(c *gin.Context) {
|
|
||||||
c.JSON(http.StatusOK, c.MustGet("team").(*fic.Team))
|
|
||||||
})
|
|
||||||
apiTeamsRoutes.PUT("/", updateTeam)
|
|
||||||
apiTeamsRoutes.POST("/", addTeamMember)
|
|
||||||
apiTeamsRoutes.DELETE("/", deleteTeam)
|
|
||||||
apiTeamsRoutes.GET("/score-grid.json", func(c *gin.Context) {
|
|
||||||
team := c.MustGet("team").(*fic.Team)
|
|
||||||
|
|
||||||
sg, err := team.ScoreGrid()
|
|
||||||
if err != nil {
|
|
||||||
log.Printf("Unable to get ScoreGrid(tid=%d): %s", team.Id, err.Error())
|
|
||||||
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": "An error occurs during score grid calculation."})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
c.JSON(http.StatusOK, sg)
|
|
||||||
})
|
|
||||||
|
|
||||||
apiTeamsPublicRoutes := router.Group("/teams/:tid")
|
|
||||||
apiTeamsPublicRoutes.Use(TeamPublicHandler)
|
|
||||||
apiTeamsPublicRoutes.GET("/my.json", func(c *gin.Context) {
|
|
||||||
var team *fic.Team
|
|
||||||
if t, ok := c.Get("team"); ok && t != nil {
|
|
||||||
team = t.(*fic.Team)
|
|
||||||
}
|
|
||||||
tfile, err := fic.MyJSONTeam(team, true)
|
|
||||||
if err != nil {
|
|
||||||
log.Println("Unable to get MyJSONTeam:", err.Error())
|
|
||||||
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": "An error occurs during team JSON generation."})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
c.JSON(http.StatusOK, tfile)
|
|
||||||
})
|
|
||||||
apiTeamsPublicRoutes.GET("/wait.json", func(c *gin.Context) {
|
|
||||||
var team *fic.Team
|
|
||||||
if t, ok := c.Get("team"); ok && t != nil {
|
|
||||||
team = t.(*fic.Team)
|
|
||||||
}
|
|
||||||
tfile, err := fic.MyJSONTeam(team, false)
|
|
||||||
if err != nil {
|
|
||||||
log.Println("Unable to get MyJSONTeam:", err.Error())
|
|
||||||
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": "An error occurs during team JSON generation."})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
c.JSON(http.StatusOK, tfile)
|
|
||||||
})
|
|
||||||
apiTeamsPublicRoutes.GET("/stats.json", func(c *gin.Context) {
|
|
||||||
var team *fic.Team
|
|
||||||
if t, ok := c.Get("team"); ok && t != nil {
|
|
||||||
team = t.(*fic.Team)
|
|
||||||
}
|
|
||||||
if team != nil {
|
|
||||||
stats, err := team.GetStats()
|
|
||||||
if err != nil {
|
|
||||||
log.Println("Unable to get GetStats:", err.Error())
|
|
||||||
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": "An error occurs during stats calculation."})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
c.JSON(http.StatusOK, stats)
|
|
||||||
} else {
|
|
||||||
stats, err := fic.GetTeamsStats(nil)
|
|
||||||
if err != nil {
|
|
||||||
log.Println("Unable to get GetTeamsStats:", err.Error())
|
|
||||||
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": "An error occurs during global stats calculation."})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
c.JSON(http.StatusOK, stats)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
apiTeamsRoutes.GET("/history.json", func(c *gin.Context) {
|
|
||||||
team := c.MustGet("team").(*fic.Team)
|
|
||||||
|
|
||||||
history, err := team.GetHistory()
|
|
||||||
if err != nil {
|
|
||||||
log.Println("Unable to get GetHistory:", err.Error())
|
|
||||||
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": "An error occurs during history calculation."})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
c.JSON(http.StatusOK, history)
|
|
||||||
})
|
|
||||||
apiTeamsRoutes.PATCH("/history.json", updateHistory)
|
|
||||||
apiTeamsRoutes.DELETE("/history.json", delHistory)
|
|
||||||
apiTeamsPublicRoutes.GET("/tries", func(c *gin.Context) {
|
|
||||||
team := c.MustGet("team").(*fic.Team)
|
|
||||||
|
|
||||||
tries, err := fic.GetTries(team, nil)
|
|
||||||
if err != nil {
|
|
||||||
log.Println("Unable to GetTries:", err.Error())
|
|
||||||
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": "An error occurs during tries calculation."})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
c.JSON(http.StatusOK, tries)
|
|
||||||
})
|
|
||||||
apiTeamsRoutes.GET("/members", func(c *gin.Context) {
|
|
||||||
team := c.MustGet("team").(*fic.Team)
|
|
||||||
|
|
||||||
members, err := team.GetMembers()
|
|
||||||
if err != nil {
|
|
||||||
log.Println("Unable to GetMembers:", err.Error())
|
|
||||||
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": "An error occurs during members retrieval."})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
c.JSON(http.StatusOK, members)
|
|
||||||
})
|
|
||||||
apiTeamsRoutes.POST("/members", addTeamMember)
|
|
||||||
apiTeamsRoutes.PUT("/members", setTeamMember)
|
|
||||||
|
|
||||||
declareTeamsPasswordRoutes(apiTeamsRoutes)
|
|
||||||
declareTeamClaimsRoutes(apiTeamsRoutes)
|
|
||||||
declareTeamCertificateRoutes(apiTeamsRoutes)
|
|
||||||
|
|
||||||
// Import teams from cyberrange
|
|
||||||
router.POST("/cyberrange-teams.json", importTeamsFromCyberrange)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TeamHandler(c *gin.Context) {
|
|
||||||
tid, err := strconv.ParseInt(string(c.Params.ByName("tid")), 10, 64)
|
|
||||||
if err != nil {
|
|
||||||
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"errmsg": "Invalid team identifier"})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
team, err := fic.GetTeam(tid)
|
|
||||||
if err != nil {
|
|
||||||
c.AbortWithStatusJSON(http.StatusNotFound, gin.H{"errmsg": "Team not found"})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
c.Set("team", team)
|
|
||||||
|
|
||||||
c.Next()
|
|
||||||
}
|
|
||||||
|
|
||||||
func TeamPublicHandler(c *gin.Context) {
|
|
||||||
tid, err := strconv.ParseInt(string(c.Params.ByName("tid")), 10, 64)
|
|
||||||
if err != nil {
|
|
||||||
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"errmsg": "Invalid team identifier"})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if tid != 0 {
|
|
||||||
team, err := fic.GetTeam(tid)
|
|
||||||
if err != nil {
|
|
||||||
c.AbortWithStatusJSON(http.StatusNotFound, gin.H{"errmsg": "Team not found"})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
c.Set("team", team)
|
|
||||||
} else {
|
|
||||||
c.Set("team", nil)
|
|
||||||
}
|
|
||||||
|
|
||||||
c.Next()
|
|
||||||
}
|
|
||||||
|
|
||||||
func nginxGenTeams(c *gin.Context) {
|
|
||||||
teams, err := fic.GetTeams()
|
|
||||||
if err != nil {
|
|
||||||
log.Println("Unable to GetTeams:", err.Error())
|
|
||||||
c.AbortWithError(http.StatusInternalServerError, err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
ret := ""
|
|
||||||
for _, team := range teams {
|
|
||||||
ret += fmt.Sprintf(" if ($remote_user = \"%s\") { set $team \"%d\"; }\n", strings.ToLower(team.Name), team.Id)
|
|
||||||
}
|
|
||||||
|
|
||||||
c.String(http.StatusOK, ret)
|
|
||||||
}
|
|
||||||
|
|
||||||
func nginxGenMember(c *gin.Context) {
|
|
||||||
teams, err := fic.GetTeams()
|
|
||||||
if err != nil {
|
|
||||||
log.Println("Unable to GetTeams:", err.Error())
|
|
||||||
c.AbortWithError(http.StatusInternalServerError, err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
ret := ""
|
|
||||||
for _, team := range teams {
|
|
||||||
if members, err := team.GetMembers(); err == nil {
|
|
||||||
for _, member := range members {
|
|
||||||
ret += fmt.Sprintf(" if ($remote_user = \"%s\") { set $team \"%d\"; }\n", member.Nickname, team.Id)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
c.AbortWithError(http.StatusInternalServerError, err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
c.String(http.StatusOK, ret)
|
|
||||||
}
|
|
||||||
|
|
||||||
func bindingTeams(c *gin.Context) {
|
|
||||||
teams, err := fic.GetTeams()
|
|
||||||
if err != nil {
|
|
||||||
log.Println("Unable to GetTeams:", err.Error())
|
|
||||||
c.AbortWithError(http.StatusInternalServerError, err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
ret := ""
|
|
||||||
for _, team := range teams {
|
|
||||||
if members, err := team.GetMembers(); err != nil {
|
|
||||||
c.AbortWithError(http.StatusInternalServerError, err)
|
|
||||||
return
|
|
||||||
} else {
|
|
||||||
var mbs []string
|
|
||||||
for _, member := range members {
|
|
||||||
mbs = append(mbs, fmt.Sprintf("%s %s", member.Firstname, member.Lastname))
|
|
||||||
}
|
|
||||||
ret += fmt.Sprintf("%d;%s;%s\n", team.Id, team.Name, strings.Join(mbs, ";"))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
c.String(http.StatusOK, ret)
|
|
||||||
}
|
|
||||||
|
|
||||||
type teamAssociation struct {
|
|
||||||
Association string `json:"association"`
|
|
||||||
TeamId int64 `json:"team_id"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func allAssociations(c *gin.Context) {
|
|
||||||
teams, err := fic.GetTeams()
|
|
||||||
if err != nil {
|
|
||||||
log.Println("Unable to GetTeams:", err.Error())
|
|
||||||
c.AbortWithError(http.StatusInternalServerError, err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
var ret []teamAssociation
|
|
||||||
|
|
||||||
for _, team := range teams {
|
|
||||||
assocs, err := pki.GetTeamAssociations(TeamsDir, team.Id)
|
|
||||||
if err != nil {
|
|
||||||
c.AbortWithError(http.StatusInternalServerError, err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, a := range assocs {
|
|
||||||
ret = append(ret, teamAssociation{a, team.Id})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
c.JSON(http.StatusOK, ret)
|
|
||||||
}
|
|
||||||
|
|
||||||
func importTeamsFromCyberrange(c *gin.Context) {
|
|
||||||
file, err := c.FormFile("file")
|
|
||||||
if err != nil {
|
|
||||||
c.JSON(http.StatusBadRequest, gin.H{"errmsg": "Failed to get file: " + err.Error()})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
src, err := file.Open()
|
|
||||||
if err != nil {
|
|
||||||
c.JSON(http.StatusInternalServerError, gin.H{"errmsg": "Failed to open file: " + err.Error()})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
defer src.Close()
|
|
||||||
|
|
||||||
var ut []fic.CyberrangeTeamBase
|
|
||||||
err = json.NewDecoder(src).Decode(&fic.CyberrangeAPIResponse{Data: &ut})
|
|
||||||
if err != nil {
|
|
||||||
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"errmsg": err.Error()})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
teams, err := fic.GetTeams()
|
|
||||||
if err != nil {
|
|
||||||
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": fmt.Sprintf("Impossible de récupérer la liste des équipes actuelles: %s", err.Error())})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, crteam := range ut {
|
|
||||||
var exist_team *fic.Team
|
|
||||||
for _, team := range teams {
|
|
||||||
if team.Name == crteam.Name || team.ExternalId == crteam.UUID {
|
|
||||||
exist_team = team
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if exist_team != nil {
|
|
||||||
exist_team.Name = crteam.Name
|
|
||||||
exist_team.ExternalId = crteam.UUID
|
|
||||||
_, err = exist_team.Update()
|
|
||||||
} else {
|
|
||||||
exist_team, err = fic.CreateTeam(crteam.Name, fic.RandomColor().ToRGB(), crteam.UUID)
|
|
||||||
}
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": fmt.Sprintf("Impossible d'ajouter/de modifier l'équipe %v: %s", crteam, err.Error())})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Import members
|
|
||||||
if c.DefaultQuery("nomembers", "0") != "" && len(crteam.Members) > 0 {
|
|
||||||
exist_team.ClearMembers()
|
|
||||||
|
|
||||||
for _, member := range crteam.Members {
|
|
||||||
_, err = exist_team.AddMember(member.Name, "", member.Nickname, exist_team.Name)
|
|
||||||
if err != nil {
|
|
||||||
log.Printf("Unable to add member %q to team %s (tid=%d): %s", member.UUID, exist_team.Name, exist_team.Id, err.Error())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
teams, err = fic.GetTeams()
|
|
||||||
if err != nil {
|
|
||||||
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": fmt.Sprintf("Impossible de récupérer la liste des équipes après import: %s", err.Error())})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
c.JSON(http.StatusOK, teams)
|
|
||||||
}
|
|
||||||
|
|
||||||
func createTeam(c *gin.Context) {
|
|
||||||
var ut fic.Team
|
|
||||||
err := c.ShouldBindJSON(&ut)
|
|
||||||
if err != nil {
|
|
||||||
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"errmsg": err.Error()})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if ut.Color == 0 {
|
|
||||||
ut.Color = fic.RandomColor().ToRGB()
|
|
||||||
}
|
|
||||||
|
|
||||||
team, err := fic.CreateTeam(strings.TrimSpace(ut.Name), ut.Color, ut.ExternalId)
|
|
||||||
if err != nil {
|
|
||||||
log.Println("Unable to CreateTeam:", err.Error())
|
|
||||||
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": "An error occurs during team creation."})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
c.JSON(http.StatusOK, team)
|
|
||||||
}
|
|
||||||
|
|
||||||
func updateTeam(c *gin.Context) {
|
|
||||||
team := c.MustGet("team").(*fic.Team)
|
|
||||||
|
|
||||||
var ut fic.Team
|
|
||||||
err := c.ShouldBindJSON(&ut)
|
|
||||||
if err != nil {
|
|
||||||
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"errmsg": err.Error()})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
ut.Id = team.Id
|
|
||||||
|
|
||||||
if ut.Password != nil && *ut.Password == "" {
|
|
||||||
ut.Password = nil
|
|
||||||
}
|
|
||||||
|
|
||||||
_, err = ut.Update()
|
|
||||||
if err != nil {
|
|
||||||
log.Println("Unable to updateTeam:", err.Error())
|
|
||||||
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": "An error occurs during team updating."})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
c.JSON(http.StatusOK, ut)
|
|
||||||
}
|
|
||||||
|
|
||||||
func refineTeamsColors(c *gin.Context) {
|
|
||||||
teams, err := fic.GetTeams()
|
|
||||||
if err != nil {
|
|
||||||
log.Println("Unable to GetTeams:", err.Error())
|
|
||||||
c.AbortWithError(http.StatusInternalServerError, err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
for i, team := range teams {
|
|
||||||
team.Color = fic.HSL{
|
|
||||||
H: float64(i)/float64(len(teams)) - 0.2,
|
|
||||||
S: float64(1) / float64(1+i%2),
|
|
||||||
L: 0.25 + float64(0.5)/float64(1+i%3),
|
|
||||||
}.ToRGB()
|
|
||||||
|
|
||||||
_, err = team.Update()
|
|
||||||
if err != nil {
|
|
||||||
c.AbortWithError(http.StatusInternalServerError, err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
c.JSON(http.StatusOK, teams)
|
|
||||||
}
|
|
||||||
|
|
||||||
func disableInactiveTeams(c *gin.Context) {
|
|
||||||
teams, err := fic.GetTeams()
|
|
||||||
if err != nil {
|
|
||||||
log.Println("Unable to GetTeams:", err.Error())
|
|
||||||
c.AbortWithError(http.StatusInternalServerError, err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, team := range teams {
|
|
||||||
var serials []uint64
|
|
||||||
serials, err = pki.GetTeamSerials(TeamsDir, team.Id)
|
|
||||||
if err != nil {
|
|
||||||
c.AbortWithError(http.StatusInternalServerError, err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
var assocs []string
|
|
||||||
assocs, err = pki.GetTeamAssociations(TeamsDir, team.Id)
|
|
||||||
if err != nil {
|
|
||||||
c.AbortWithError(http.StatusInternalServerError, err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(serials) == 0 && len(assocs) == 0 {
|
|
||||||
if team.Active {
|
|
||||||
team.Active = false
|
|
||||||
team.Update()
|
|
||||||
}
|
|
||||||
} else if !team.Active {
|
|
||||||
team.Active = true
|
|
||||||
team.Update()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
c.JSON(http.StatusOK, true)
|
|
||||||
}
|
|
||||||
|
|
||||||
func enableAllTeams(c *gin.Context) {
|
|
||||||
teams, err := fic.GetTeams()
|
|
||||||
if err != nil {
|
|
||||||
log.Println("Unable to GetTeams:", err.Error())
|
|
||||||
c.AbortWithError(http.StatusInternalServerError, err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, team := range teams {
|
|
||||||
if !team.Active {
|
|
||||||
team.Active = true
|
|
||||||
team.Update()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
c.JSON(http.StatusOK, true)
|
|
||||||
}
|
|
||||||
|
|
||||||
func deleteTeam(c *gin.Context) {
|
|
||||||
team := c.MustGet("team").(*fic.Team)
|
|
||||||
|
|
||||||
assocs, err := pki.GetTeamAssociations(TeamsDir, team.Id)
|
|
||||||
if err != nil {
|
|
||||||
log.Printf("Unable to GetTeamAssociations(tid=%d): %s", team.Id, err.Error())
|
|
||||||
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": "An error occurs when trying to retrieve team association."})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, assoc := range assocs {
|
|
||||||
err = pki.DeleteTeamAssociation(TeamsDir, assoc)
|
|
||||||
if err != nil {
|
|
||||||
log.Printf("Unable to DeleteTeamAssociation(assoc=%s): %s", assoc, err.Error())
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
_, err = team.Delete()
|
|
||||||
if err != nil {
|
|
||||||
log.Println("Unable to deleteTeam:", err.Error())
|
|
||||||
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": "An error occurs during team deletion."})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
c.JSON(http.StatusOK, true)
|
|
||||||
}
|
|
||||||
|
|
||||||
func addTeamMember(c *gin.Context) {
|
|
||||||
team := c.MustGet("team").(*fic.Team)
|
|
||||||
|
|
||||||
var members []fic.Member
|
|
||||||
err := c.ShouldBindJSON(&members)
|
|
||||||
if err != nil {
|
|
||||||
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"errmsg": err.Error()})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, member := range members {
|
|
||||||
_, err := team.AddMember(strings.TrimSpace(member.Firstname), strings.TrimSpace(member.Lastname), strings.TrimSpace(member.Nickname), strings.TrimSpace(member.Company))
|
|
||||||
if err != nil {
|
|
||||||
log.Println("Unable to AddMember:", err.Error())
|
|
||||||
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": "An error occurs during member creation."})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
mmbrs, err := team.GetMembers()
|
|
||||||
if err != nil {
|
|
||||||
log.Println("Unable to retrieve members list:", err.Error())
|
|
||||||
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": "Unable to retrieve members list."})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
c.JSON(http.StatusOK, mmbrs)
|
|
||||||
}
|
|
||||||
|
|
||||||
func setTeamMember(c *gin.Context) {
|
|
||||||
team := c.MustGet("team").(*fic.Team)
|
|
||||||
team.ClearMembers()
|
|
||||||
addTeamMember(c)
|
|
||||||
}
|
|
||||||
|
|
||||||
type uploadedHistory struct {
|
|
||||||
Kind string
|
|
||||||
Time time.Time
|
|
||||||
Primary *int64
|
|
||||||
Secondary *int64
|
|
||||||
Coefficient float32
|
|
||||||
}
|
|
||||||
|
|
||||||
func updateHistory(c *gin.Context) {
|
|
||||||
team := c.MustGet("team").(*fic.Team)
|
|
||||||
|
|
||||||
var uh uploadedHistory
|
|
||||||
err := c.ShouldBindJSON(&uh)
|
|
||||||
if err != nil {
|
|
||||||
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"errmsg": err.Error()})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
var givenId int64
|
|
||||||
if uh.Secondary != nil {
|
|
||||||
givenId = *uh.Secondary
|
|
||||||
} else if uh.Primary != nil {
|
|
||||||
givenId = *uh.Primary
|
|
||||||
}
|
|
||||||
|
|
||||||
_, err = team.UpdateHistoryCoeff(uh.Kind, uh.Time, givenId, uh.Coefficient)
|
|
||||||
if err != nil {
|
|
||||||
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": fmt.Sprintf("Unable to update this history line: %s", err.Error())})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
c.JSON(http.StatusOK, true)
|
|
||||||
}
|
|
||||||
|
|
||||||
func delHistory(c *gin.Context) {
|
|
||||||
team := c.MustGet("team").(*fic.Team)
|
|
||||||
|
|
||||||
var uh uploadedHistory
|
|
||||||
err := c.ShouldBindJSON(&uh)
|
|
||||||
if err != nil {
|
|
||||||
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"errmsg": err.Error()})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
_, err = team.DelHistoryItem(uh.Kind, uh.Time, uh.Primary, uh.Secondary)
|
|
||||||
if err != nil {
|
|
||||||
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": fmt.Sprintf("Unable to delete this history line: %s", err.Error())})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
c.JSON(http.StatusOK, true)
|
|
||||||
}
|
|
||||||
|
|
@ -1,376 +0,0 @@
|
||||||
package api
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"log"
|
|
||||||
"net/http"
|
|
||||||
"path"
|
|
||||||
"reflect"
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"srs.epita.fr/fic-server/admin/sync"
|
|
||||||
"srs.epita.fr/fic-server/libfic"
|
|
||||||
"srs.epita.fr/fic-server/settings"
|
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
|
||||||
)
|
|
||||||
|
|
||||||
func declareThemesRoutes(router *gin.RouterGroup) {
|
|
||||||
router.GET("/themes", listThemes)
|
|
||||||
router.POST("/themes", createTheme)
|
|
||||||
router.GET("/themes.json", exportThemes)
|
|
||||||
router.GET("/session-forensic.yaml", func(c *gin.Context) {
|
|
||||||
if s, err := settings.ReadSettings(path.Join(settings.SettingsDir, settings.SettingsFile)); err != nil {
|
|
||||||
log.Printf("Unable to ReadSettings: %s", err.Error())
|
|
||||||
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": "An error occurs during settings reading."})
|
|
||||||
return
|
|
||||||
|
|
||||||
} else if challengeinfo, err := sync.GetFileContent(sync.GlobalImporter, "challenge.json"); err != nil {
|
|
||||||
log.Println("Unable to retrieve challenge.json:", err.Error())
|
|
||||||
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": fmt.Sprintf("Unable to retrive challenge.json: %s", err.Error())})
|
|
||||||
return
|
|
||||||
} else if ch, err := settings.ReadChallengeInfo(challengeinfo); err != nil {
|
|
||||||
log.Printf("Unable to ReadChallengeInfo: %s", err.Error())
|
|
||||||
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": "An error occurs during challenge info reading."})
|
|
||||||
return
|
|
||||||
} else if sf, err := fic.GenZQDSSessionFile(ch, s); err != nil {
|
|
||||||
log.Printf("Unable to GenZQDSSessionFile: %s", err.Error())
|
|
||||||
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": "An error occurs during session file generation."})
|
|
||||||
return
|
|
||||||
} else {
|
|
||||||
c.JSON(http.StatusOK, sf)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
router.GET("/files-bindings", bindingFiles)
|
|
||||||
|
|
||||||
apiThemesRoutes := router.Group("/themes/:thid")
|
|
||||||
apiThemesRoutes.Use(ThemeHandler)
|
|
||||||
apiThemesRoutes.GET("", showTheme)
|
|
||||||
apiThemesRoutes.PUT("", updateTheme)
|
|
||||||
apiThemesRoutes.DELETE("", deleteTheme)
|
|
||||||
|
|
||||||
apiThemesRoutes.POST("/diff-sync", APIDiffThemeWithRemote)
|
|
||||||
|
|
||||||
apiThemesRoutes.GET("/exercices_stats.json", getThemedExercicesStats)
|
|
||||||
|
|
||||||
declareExercicesRoutes(apiThemesRoutes)
|
|
||||||
|
|
||||||
// Remote
|
|
||||||
router.GET("/remote/themes", sync.ApiListRemoteThemes)
|
|
||||||
router.GET("/remote/themes/:thid", sync.ApiGetRemoteTheme)
|
|
||||||
router.GET("/remote/themes/:thid/exercices", sync.ApiListRemoteExercices)
|
|
||||||
}
|
|
||||||
|
|
||||||
type Theme struct {
|
|
||||||
*fic.Theme
|
|
||||||
ForgeLink string `json:"forge_link,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func ThemeHandler(c *gin.Context) {
|
|
||||||
thid, err := strconv.ParseInt(string(c.Params.ByName("thid")), 10, 64)
|
|
||||||
if err != nil {
|
|
||||||
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"errmsg": "Invalid theme identifier"})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if thid == 0 {
|
|
||||||
c.Set("theme", &fic.StandaloneExercicesTheme)
|
|
||||||
} else {
|
|
||||||
theme, err := fic.GetTheme(thid)
|
|
||||||
if err != nil {
|
|
||||||
c.AbortWithStatusJSON(http.StatusNotFound, gin.H{"errmsg": "Theme not found"})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
c.Set("theme", theme)
|
|
||||||
}
|
|
||||||
|
|
||||||
c.Next()
|
|
||||||
}
|
|
||||||
|
|
||||||
func fixAllURLIds(c *gin.Context) {
|
|
||||||
nbFix := 0
|
|
||||||
if themes, err := fic.GetThemes(); err == nil {
|
|
||||||
for _, theme := range themes {
|
|
||||||
if theme.FixURLId() {
|
|
||||||
theme.Update()
|
|
||||||
nbFix += 1
|
|
||||||
}
|
|
||||||
|
|
||||||
if exercices, err := theme.GetExercices(); err == nil {
|
|
||||||
for _, exercice := range exercices {
|
|
||||||
if exercice.FixURLId() {
|
|
||||||
exercice.Update()
|
|
||||||
nbFix += 1
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
c.JSON(http.StatusOK, nbFix)
|
|
||||||
}
|
|
||||||
|
|
||||||
func bindingFiles(c *gin.Context) {
|
|
||||||
files, err := fic.GetFiles()
|
|
||||||
if err != nil {
|
|
||||||
c.AbortWithError(http.StatusInternalServerError, err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
ret := ""
|
|
||||||
for _, file := range files {
|
|
||||||
ret += fmt.Sprintf("%s;%s\n", file.GetOrigin(), file.Path)
|
|
||||||
}
|
|
||||||
|
|
||||||
c.String(http.StatusOK, ret)
|
|
||||||
}
|
|
||||||
|
|
||||||
func listThemes(c *gin.Context) {
|
|
||||||
themes, err := fic.GetThemes()
|
|
||||||
if err != nil {
|
|
||||||
log.Println("Unable to listThemes:", err.Error())
|
|
||||||
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": "An error occurs when trying to list themes."})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if has, _ := fic.HasStandaloneExercice(); has {
|
|
||||||
themes = append([]*fic.Theme{&fic.StandaloneExercicesTheme}, themes...)
|
|
||||||
}
|
|
||||||
|
|
||||||
c.JSON(http.StatusOK, themes)
|
|
||||||
}
|
|
||||||
|
|
||||||
func exportThemes(c *gin.Context) {
|
|
||||||
themes, err := fic.ExportThemes()
|
|
||||||
if err != nil {
|
|
||||||
log.Println("Unable to exportthemes:", err.Error())
|
|
||||||
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": "An error occurs when trying to export themes."})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
c.JSON(http.StatusOK, themes)
|
|
||||||
}
|
|
||||||
|
|
||||||
func showTheme(c *gin.Context) {
|
|
||||||
theme := c.MustGet("theme").(*fic.Theme)
|
|
||||||
|
|
||||||
var forgelink string
|
|
||||||
if fli, ok := sync.GlobalImporter.(sync.ForgeLinkedImporter); ok {
|
|
||||||
if u, _ := fli.GetThemeLink(theme); u != nil {
|
|
||||||
forgelink = u.String()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
c.JSON(http.StatusOK, Theme{theme, forgelink})
|
|
||||||
}
|
|
||||||
|
|
||||||
func createTheme(c *gin.Context) {
|
|
||||||
var ut fic.Theme
|
|
||||||
err := c.ShouldBindJSON(&ut)
|
|
||||||
if err != nil {
|
|
||||||
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"errmsg": err.Error()})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(ut.Name) == 0 {
|
|
||||||
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"errmsg": "Theme's name not filled"})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
th, err := fic.CreateTheme(&ut)
|
|
||||||
if err != nil {
|
|
||||||
log.Println("Unable to createTheme:", err.Error())
|
|
||||||
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": "An error occurs during theme creation."})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
c.JSON(http.StatusOK, th)
|
|
||||||
}
|
|
||||||
|
|
||||||
func updateTheme(c *gin.Context) {
|
|
||||||
theme := c.MustGet("theme").(*fic.Theme)
|
|
||||||
|
|
||||||
var ut fic.Theme
|
|
||||||
err := c.ShouldBindJSON(&ut)
|
|
||||||
if err != nil {
|
|
||||||
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"errmsg": err.Error()})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
ut.Id = theme.Id
|
|
||||||
|
|
||||||
if len(ut.Name) == 0 {
|
|
||||||
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"errmsg": "Theme's name not filled"})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if _, err := ut.Update(); err != nil {
|
|
||||||
log.Println("Unable to updateTheme:", err.Error())
|
|
||||||
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"errmsg": "An error occurs during theme update."})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if theme.Locked != ut.Locked {
|
|
||||||
exercices, err := theme.GetExercices()
|
|
||||||
if err != nil {
|
|
||||||
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": err.Error()})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
for _, exercice := range exercices {
|
|
||||||
if exercice.Disabled != ut.Locked {
|
|
||||||
exercice.Disabled = ut.Locked
|
|
||||||
_, err = exercice.Update()
|
|
||||||
if err != nil {
|
|
||||||
log.Println("Unable to enable/disable exercice: ", exercice.Id, err.Error())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
c.JSON(http.StatusOK, ut)
|
|
||||||
}
|
|
||||||
|
|
||||||
func deleteTheme(c *gin.Context) {
|
|
||||||
theme := c.MustGet("theme").(*fic.Theme)
|
|
||||||
|
|
||||||
_, err := theme.Delete()
|
|
||||||
if err != nil {
|
|
||||||
log.Println("Unable to deleteTheme:", err.Error())
|
|
||||||
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"errmsg": "An error occurs during theme deletion."})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
c.JSON(http.StatusOK, true)
|
|
||||||
}
|
|
||||||
|
|
||||||
func getThemedExercicesStats(c *gin.Context) {
|
|
||||||
theme := c.MustGet("theme").(*fic.Theme)
|
|
||||||
|
|
||||||
exercices, err := theme.GetExercices()
|
|
||||||
if err != nil {
|
|
||||||
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": fmt.Sprintf("Unable to fetch exercices: %s", err.Error())})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
ret := []exerciceStats{}
|
|
||||||
for _, e := range exercices {
|
|
||||||
ret = append(ret, exerciceStats{
|
|
||||||
IdExercice: e.Id,
|
|
||||||
TeamTries: e.TriedTeamCount(),
|
|
||||||
TotalTries: e.TriedCount(),
|
|
||||||
SolvedCount: e.SolvedCount(),
|
|
||||||
FlagSolved: e.FlagSolved(),
|
|
||||||
MCQSolved: e.MCQSolved(),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
c.JSON(http.StatusOK, ret)
|
|
||||||
}
|
|
||||||
|
|
||||||
func diffThemeWithRemote(theme *fic.Theme) ([]syncDiff, error) {
|
|
||||||
var diffs []syncDiff
|
|
||||||
|
|
||||||
// Compare theme attributes
|
|
||||||
theme_remote, err := sync.GetRemoteTheme(theme.Path)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, field := range reflect.VisibleFields(reflect.TypeOf(*theme)) {
|
|
||||||
if ((field.Name == "Image") && path.Base(reflect.ValueOf(*theme_remote).FieldByName(field.Name).String()) != path.Base(reflect.ValueOf(*theme).FieldByName(field.Name).String())) || (field.Name != "Image" && !reflect.ValueOf(*theme_remote).FieldByName(field.Name).Equal(reflect.ValueOf(*theme).FieldByName(field.Name))) {
|
|
||||||
if !field.IsExported() || field.Name == "Id" || field.Name == "IdTheme" || field.Name == "IssueKind" || field.Name == "BackgroundColor" {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
diffs = append(diffs, syncDiff{
|
|
||||||
Field: field.Name,
|
|
||||||
Link: fmt.Sprintf("themes/%d", theme.Id),
|
|
||||||
Before: reflect.ValueOf(*theme).FieldByName(field.Name).Interface(),
|
|
||||||
After: reflect.ValueOf(*theme_remote).FieldByName(field.Name).Interface(),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Compare exercices list
|
|
||||||
exercices, err := theme.GetExercices()
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("Unable to GetExercices: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
exercices_remote, err := sync.ListRemoteExercices(theme.Path)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("Unable to ListRemoteExercices: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
var not_found []string
|
|
||||||
var extra_found []string
|
|
||||||
|
|
||||||
for _, exercice_remote := range exercices_remote {
|
|
||||||
found := false
|
|
||||||
for _, exercice := range exercices {
|
|
||||||
if exercice.Path[strings.Index(exercice.Path, "/")+1:] == exercice_remote {
|
|
||||||
found = true
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if !found {
|
|
||||||
not_found = append(not_found, exercice_remote)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, exercice := range exercices {
|
|
||||||
found := false
|
|
||||||
for _, exercice_remote := range exercices_remote {
|
|
||||||
if exercice.Path[strings.Index(exercice.Path, "/")+1:] == exercice_remote {
|
|
||||||
found = true
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if !found {
|
|
||||||
extra_found = append(extra_found, exercice.Path[strings.Index(exercice.Path, "/")+1:])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(not_found) > 0 || len(extra_found) > 0 {
|
|
||||||
diffs = append(diffs, syncDiff{
|
|
||||||
Field: "theme.Exercices",
|
|
||||||
Link: fmt.Sprintf("themes/%d", theme.Id),
|
|
||||||
Before: strings.Join(extra_found, ", "),
|
|
||||||
After: strings.Join(not_found, ", "),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// Compare inner exercices
|
|
||||||
for i, exercice := range exercices {
|
|
||||||
exdiffs, err := diffExerciceWithRemote(exercice, theme)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("Unable to diffExerciceWithRemote: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, exdiff := range exdiffs {
|
|
||||||
if theme.Id == 0 {
|
|
||||||
exdiff.Field = fmt.Sprintf("exercices[%d].%s", exercice.Id, exdiff.Field)
|
|
||||||
} else {
|
|
||||||
exdiff.Field = fmt.Sprintf("exercices[%d].%s", i, exdiff.Field)
|
|
||||||
}
|
|
||||||
diffs = append(diffs, exdiff)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return diffs, err
|
|
||||||
}
|
|
||||||
|
|
||||||
func APIDiffThemeWithRemote(c *gin.Context) {
|
|
||||||
theme := c.MustGet("theme").(*fic.Theme)
|
|
||||||
|
|
||||||
diffs, err := diffThemeWithRemote(theme)
|
|
||||||
if err != nil {
|
|
||||||
c.JSON(http.StatusInternalServerError, gin.H{"errmsg": err.Error()})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
c.JSON(http.StatusOK, diffs)
|
|
||||||
}
|
|
||||||
|
|
@ -1,15 +0,0 @@
|
||||||
package api
|
|
||||||
|
|
||||||
import (
|
|
||||||
"net/http"
|
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
|
||||||
)
|
|
||||||
|
|
||||||
func DeclareVersionRoutes(router *gin.RouterGroup) {
|
|
||||||
router.GET("/version", showVersion)
|
|
||||||
}
|
|
||||||
|
|
||||||
func showVersion(c *gin.Context) {
|
|
||||||
c.JSON(http.StatusOK, gin.H{"version": 1.0})
|
|
||||||
}
|
|
||||||
81
admin/app.go
81
admin/app.go
|
|
@ -1,81 +0,0 @@
|
||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"log"
|
|
||||||
"net/http"
|
|
||||||
"path/filepath"
|
|
||||||
"strings"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
|
||||||
|
|
||||||
"srs.epita.fr/fic-server/admin/api"
|
|
||||||
"srs.epita.fr/fic-server/settings"
|
|
||||||
)
|
|
||||||
|
|
||||||
type App struct {
|
|
||||||
router *gin.Engine
|
|
||||||
srv *http.Server
|
|
||||||
cfg *settings.Settings
|
|
||||||
bind string
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewApp(cfg *settings.Settings, baseURL string, bind string) App {
|
|
||||||
if !cfg.WorkInProgress {
|
|
||||||
gin.SetMode(gin.ReleaseMode)
|
|
||||||
}
|
|
||||||
gin.ForceConsoleColor()
|
|
||||||
router := gin.Default()
|
|
||||||
|
|
||||||
api.DeclareRoutes(router.Group(""))
|
|
||||||
|
|
||||||
var baserouter *gin.RouterGroup
|
|
||||||
if len(baseURL) > 0 {
|
|
||||||
router.GET("/", func(c *gin.Context) {
|
|
||||||
c.Redirect(http.StatusFound, baseURL)
|
|
||||||
})
|
|
||||||
router.GET(filepath.Dir(baseURL)+"/files/*_", func(c *gin.Context) {
|
|
||||||
path := c.Request.URL.Path
|
|
||||||
c.Redirect(http.StatusFound, filepath.Join(baseURL, strings.TrimPrefix(path, filepath.Dir(baseURL))))
|
|
||||||
})
|
|
||||||
|
|
||||||
baserouter = router.Group(baseURL)
|
|
||||||
|
|
||||||
api.DeclareRoutes(baserouter)
|
|
||||||
declareStaticRoutes(baserouter, cfg, baseURL)
|
|
||||||
} else {
|
|
||||||
declareStaticRoutes(router.Group(""), cfg, "")
|
|
||||||
}
|
|
||||||
|
|
||||||
app := App{
|
|
||||||
router: router,
|
|
||||||
bind: bind,
|
|
||||||
}
|
|
||||||
|
|
||||||
return app
|
|
||||||
}
|
|
||||||
|
|
||||||
func (app *App) Start() {
|
|
||||||
app.srv = &http.Server{
|
|
||||||
Addr: app.bind,
|
|
||||||
Handler: app.router,
|
|
||||||
ReadHeaderTimeout: 15 * time.Second,
|
|
||||||
ReadTimeout: 15 * time.Second,
|
|
||||||
WriteTimeout: 10 * time.Second,
|
|
||||||
IdleTimeout: 30 * time.Second,
|
|
||||||
}
|
|
||||||
|
|
||||||
log.Printf("Ready, listening on %s\n", app.bind)
|
|
||||||
if err := app.srv.ListenAndServe(); err != nil && err != http.ErrServerClosed {
|
|
||||||
log.Fatalf("listen: %s\n", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (app *App) Stop() {
|
|
||||||
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
|
||||||
defer cancel()
|
|
||||||
if err := app.srv.Shutdown(ctx); err != nil {
|
|
||||||
log.Fatal("Server Shutdown:", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,167 +0,0 @@
|
||||||
#!/bin/bash
|
|
||||||
|
|
||||||
BASEURL="http://127.0.0.1:8081/admin"
|
|
||||||
GEN_CERTS=0
|
|
||||||
GEN_PASSWD=0
|
|
||||||
EXTRA_TEAMS=0
|
|
||||||
CSV_SPLITER=","
|
|
||||||
CSV_COL_LASTNAME=2
|
|
||||||
CSV_COL_FIRSTNAME=3
|
|
||||||
CSV_COL_NICKNAME=5
|
|
||||||
CSV_COL_COMPANY=6
|
|
||||||
CSV_COL_TEAM=1
|
|
||||||
|
|
||||||
usage() {
|
|
||||||
echo "$0 [options] csv_file"
|
|
||||||
echo " -B -baseurl BASEURL URL to administration endpoint (default: $BASEURL)"
|
|
||||||
echo " -S -csv-spliter SEP CSV separator (default: $CSV_SPLITER)"
|
|
||||||
echo " -e -extra-teams NBS Number of extra teams to generate (default: ${EXTRA_TEAMS})"
|
|
||||||
echo " -c -generate-certificate Should team certificates be generated? (default: no)"
|
|
||||||
echo " -p -generate-password Should generate team password to teams.pass? (default: no)"
|
|
||||||
}
|
|
||||||
|
|
||||||
# Parse options
|
|
||||||
while [ "${1:0:1}" = "-" ]
|
|
||||||
do
|
|
||||||
case "$1" in
|
|
||||||
-B|-baseurl)
|
|
||||||
BASEURL=$2
|
|
||||||
shift;;
|
|
||||||
-S|-csv-spliter)
|
|
||||||
CSV_SPLITER=$2
|
|
||||||
shift;;
|
|
||||||
-e|-extra-teams)
|
|
||||||
EXTRA_TEAMS=$2
|
|
||||||
shift;;
|
|
||||||
-c|-generate-certificates)
|
|
||||||
GEN_CERTS=1;;
|
|
||||||
-p|-generate-password)
|
|
||||||
GEN_PASSWD=1;;
|
|
||||||
*)
|
|
||||||
echo "Unknown option '$1'"
|
|
||||||
usage
|
|
||||||
exit 1;;
|
|
||||||
esac
|
|
||||||
shift
|
|
||||||
done
|
|
||||||
|
|
||||||
[ "$#" -lt 1 ] && [ "${EXTRA_TEAMS}" -eq 0 ] && { usage; exit 1; }
|
|
||||||
|
|
||||||
new_team() {
|
|
||||||
head -n "$1" team-names.txt | tail -1 | sed -E 's/^.*\|\[\[([^|]+\|)?([^|]+)\]\][^|]*\|([A-Fa-f0-9]{1,2})\|([A-Fa-f0-9]{1,2})\|([A-Fa-f0-9]{1,2})\|([0-9]{1,3})\|([0-9]{1,3})\|([0-9]{1,3})\|.*$/\6 \7 \8 \2/' |
|
|
||||||
while read line;
|
|
||||||
do
|
|
||||||
R=`echo $line | cut -d " " -f 1`
|
|
||||||
G=`echo $line | cut -d " " -f 2`
|
|
||||||
B=`echo $line | cut -d " " -f 3`
|
|
||||||
if [ -z "$2" ]; then
|
|
||||||
N=`echo $line | cut -d " " -f 4`
|
|
||||||
else
|
|
||||||
N=`echo -n $2 | tr -d '\r\n'`
|
|
||||||
fi
|
|
||||||
|
|
||||||
COLOR=$((($R*256 + $G) * 256 + $B))
|
|
||||||
|
|
||||||
curl -s -d "{\"name\": \"$N\",\"color\": $COLOR}" "${BASEURL}/api/teams"
|
|
||||||
done | grep -Eo '"id":[0-9]+,' | grep -Eo "[0-9]+"
|
|
||||||
}
|
|
||||||
|
|
||||||
TNUM=0
|
|
||||||
|
|
||||||
for i in $(seq $EXTRA_TEAMS)
|
|
||||||
do
|
|
||||||
TNUM=$(($TNUM + 1))
|
|
||||||
|
|
||||||
echo "Doing extra team $TNUM..."
|
|
||||||
|
|
||||||
TID=`new_team $TNUM`
|
|
||||||
|
|
||||||
if [ "${GEN_CERTS}" -eq 1 ] && ! curl -s -f "${BASEURL}/api/teams/${TID}/certificate" > /dev/null
|
|
||||||
then
|
|
||||||
curl -s -f "${BASEURL}/api/teams/${TID}/certificate/generate"
|
|
||||||
elif [ "${GEN_PASSWD}" -eq 1 ]
|
|
||||||
then
|
|
||||||
TEAMID=$(curl -s -f "${BASEURL}/api/teams/${TID}/" | jq -r .name)
|
|
||||||
PASSWD=$(curl -X POST -s -f "${BASEURL}/api/teams/${TID}/password" | jq -r .password)
|
|
||||||
NP=$(echo "${TEAMID}" | cut -d : -f 1 | sed 's/[[:upper:]]/\l&/g;s/[âáàä]/a/g;s/[êéèë]/e/g')
|
|
||||||
cat >> teams.pass <<EOF
|
|
||||||
${TEAMID}:${PASSWD}
|
|
||||||
EOF
|
|
||||||
SALT="$(openssl rand -base64 3)"
|
|
||||||
HASHED="{SSHA}$(echo -n $PASSWD$SALT | openssl dgst -binary -sha1 | sed 's#$#'"$SALT"'#' | base64)"
|
|
||||||
cat >> htpasswd.ssha <<EOF
|
|
||||||
${NP}:${HASHED}
|
|
||||||
EOF
|
|
||||||
HASHED="$(echo -n $PASSWD | openssl passwd -apr1 -in -)"
|
|
||||||
cat >> htpasswd.apr1 <<EOF
|
|
||||||
${NP}:${HASHED}
|
|
||||||
EOF
|
|
||||||
fi
|
|
||||||
echo
|
|
||||||
done
|
|
||||||
|
|
||||||
[ "$#" -lt 1 ] && exit 0
|
|
||||||
PART_FILE="$1"
|
|
||||||
|
|
||||||
TMAX=`cat "$PART_FILE" | cut -d "${CSV_SPLITER}" -f $CSV_COL_TEAM | sort | uniq | wc -l`
|
|
||||||
TMAX=$(($TMAX + $TNUM))
|
|
||||||
cat "$PART_FILE" | cut -d "${CSV_SPLITER}" -f $CSV_COL_TEAM | sort | uniq | while read TEAMID
|
|
||||||
do
|
|
||||||
TNUM=$(($TNUM + 1))
|
|
||||||
|
|
||||||
echo "Doing team $TNUM/$TMAX ("$(($TNUM*100/$TMAX))"%)..."
|
|
||||||
|
|
||||||
TID=`new_team "${TNUM}" "${TEAMID}"`
|
|
||||||
|
|
||||||
if ! (
|
|
||||||
echo -n "["
|
|
||||||
HAS_MEMBER=1
|
|
||||||
grep "${TEAMID}${CSV_SPLITER}" "$PART_FILE" | while read MEMBER
|
|
||||||
do
|
|
||||||
LASTNAME=`echo $MEMBER | cut -d "${CSV_SPLITER}" -f $CSV_COL_LASTNAME | tr -d "\r\n"`
|
|
||||||
FIRSTNAME=`echo $MEMBER | cut -d "${CSV_SPLITER}" -f $CSV_COL_FIRSTNAME | tr -d "\r\n"`
|
|
||||||
NICKNAME=`echo $MEMBER | cut -d "${CSV_SPLITER}" -f $CSV_COL_NICKNAME | tr -d "\r\n"`
|
|
||||||
COMPANY=`echo $MEMBER | cut -d "${CSV_SPLITER}" -f $CSV_COL_COMPANY | tr -d "\r\n"`
|
|
||||||
|
|
||||||
if [ $HAS_MEMBER = 0 ]
|
|
||||||
then
|
|
||||||
echo -n ,
|
|
||||||
else
|
|
||||||
HAS_MEMBER=0
|
|
||||||
fi
|
|
||||||
|
|
||||||
cat <<EOF
|
|
||||||
{
|
|
||||||
"firstname": "$FIRSTNAME",
|
|
||||||
"lastname": "$LASTNAME",
|
|
||||||
"nickname": "$NICKNAME",
|
|
||||||
"company": "$COMPANY"
|
|
||||||
}
|
|
||||||
EOF
|
|
||||||
done
|
|
||||||
echo "]"
|
|
||||||
) | curl -f -s -d @- "${BASEURL}/api/teams/${TID}/members"
|
|
||||||
then
|
|
||||||
echo "An error occured"
|
|
||||||
elif [ "${GEN_CERTS}" -eq 1 ] && ! curl -s -f "${BASEURL}/api/teams/${TID}/certificate" > /dev/null
|
|
||||||
then
|
|
||||||
curl -s -f "${BASEURL}/api/teams/${TID}/certificate/generate"
|
|
||||||
elif [ "${GEN_PASSWD}" -eq 1 ]
|
|
||||||
then
|
|
||||||
PASSWD=$(curl -X POST -s -f "${BASEURL}/api/teams/${TID}/password" | jq -r .password)
|
|
||||||
NP=$(echo "${TEAMID}" | cut -d : -f 1 | sed 's/[[:upper:]]/\l&/g;s/[âáàä]/a/g;s/[êéèë]/e/g')
|
|
||||||
cat >> teams.pass <<EOF
|
|
||||||
${TEAMID}:${PASSWD}
|
|
||||||
EOF
|
|
||||||
SALT="$(openssl rand -base64 3)"
|
|
||||||
HASHED="{SSHA}$(echo -n $PASSWD$SALT | openssl dgst -binary -sha1 | sed 's#$#'"$SALT"'#' | base64)"
|
|
||||||
cat >> htpasswd.ssha <<EOF
|
|
||||||
${NP}:${HASHED}
|
|
||||||
EOF
|
|
||||||
HASHED="$(echo -n $PASSWD | openssl passwd -apr1 -in -)"
|
|
||||||
cat >> htpasswd.apr1 <<EOF
|
|
||||||
${NP}:${HASHED}
|
|
||||||
EOF
|
|
||||||
fi
|
|
||||||
echo
|
|
||||||
done
|
|
||||||
|
|
@ -1,15 +0,0 @@
|
||||||
#!/bin/sh
|
|
||||||
|
|
||||||
BASEURL="http://127.0.0.1:8081/admin"
|
|
||||||
|
|
||||||
EVENTID=6109ae5acbb7b36b789c9330
|
|
||||||
BASEURL_ZQDS="https://api.well-played.gg"
|
|
||||||
|
|
||||||
curl -s -H 'accept: */*' "${BASEURL_ZQDS}/teams?event_id=${EVENTID}&size=100" | jq --compact-output .content[] | while read TEAMOBJ; do
|
|
||||||
curl -s -d @- "${BASEURL}/api/teams/" <<EOF
|
|
||||||
{
|
|
||||||
"name": $(echo "${TEAMOBJ}" | jq .name),
|
|
||||||
"external_id": $(echo "${TEAMOBJ}" | jq .id)
|
|
||||||
}
|
|
||||||
EOF
|
|
||||||
done
|
|
||||||
|
|
@ -1,60 +0,0 @@
|
||||||
package generation
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"net"
|
|
||||||
"net/http"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"srs.epita.fr/fic-server/libfic"
|
|
||||||
)
|
|
||||||
|
|
||||||
var GeneratorSocket string
|
|
||||||
|
|
||||||
func doGeneration(uri string, contenttype string, buf io.Reader) (*http.Response, error) {
|
|
||||||
sockType := "unix"
|
|
||||||
if strings.Contains(GeneratorSocket, ":") {
|
|
||||||
sockType = "tcp"
|
|
||||||
}
|
|
||||||
|
|
||||||
socket, err := net.Dial(sockType, GeneratorSocket)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
defer socket.Close()
|
|
||||||
|
|
||||||
httpClient := &http.Client{
|
|
||||||
Transport: &http.Transport{
|
|
||||||
Dial: func(network, addr string) (net.Conn, error) {
|
|
||||||
return socket, nil
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
return httpClient.Post("http://localhost"+uri, contenttype, buf)
|
|
||||||
}
|
|
||||||
|
|
||||||
func EnqueueGeneration(gs fic.GenStruct) (*http.Response, error) {
|
|
||||||
buf, err := json.Marshal(gs)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("Something is wrong with JSON encoder: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return doGeneration("/enqueue", "application/json", bytes.NewReader(buf))
|
|
||||||
}
|
|
||||||
|
|
||||||
func PerformGeneration(gs fic.GenStruct) (*http.Response, error) {
|
|
||||||
buf, err := json.Marshal(gs)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("Something is wrong with JSON encoder: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return doGeneration("/perform", "application/json", bytes.NewReader(buf))
|
|
||||||
}
|
|
||||||
|
|
||||||
func FullGeneration() (*http.Response, error) {
|
|
||||||
return doGeneration("/full", "application/json", nil)
|
|
||||||
}
|
|
||||||
|
|
@ -1,133 +0,0 @@
|
||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"flag"
|
|
||||||
"log"
|
|
||||||
"os"
|
|
||||||
"path"
|
|
||||||
"path/filepath"
|
|
||||||
|
|
||||||
"srs.epita.fr/fic-server/admin/sync"
|
|
||||||
"srs.epita.fr/fic-server/libfic"
|
|
||||||
)
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
cloudDAVBase := ""
|
|
||||||
cloudUsername := "fic"
|
|
||||||
cloudPassword := ""
|
|
||||||
localImporterDirectory := ""
|
|
||||||
|
|
||||||
// Read paremeters from environment
|
|
||||||
if v, exists := os.LookupEnv("FICCLOUD_URL"); exists {
|
|
||||||
cloudDAVBase = v
|
|
||||||
}
|
|
||||||
if v, exists := os.LookupEnv("FICCLOUD_USER"); exists {
|
|
||||||
cloudUsername = v
|
|
||||||
}
|
|
||||||
if v, exists := os.LookupEnv("FICCLOUD_PASS"); exists {
|
|
||||||
cloudPassword = v
|
|
||||||
}
|
|
||||||
|
|
||||||
// Read parameters from command line
|
|
||||||
flag.StringVar(&localImporterDirectory, "localimport", localImporterDirectory,
|
|
||||||
"Base directory where to find challenges files to import, local part")
|
|
||||||
flag.StringVar(&cloudDAVBase, "clouddav", cloudDAVBase,
|
|
||||||
"Base directory where to find challenges files to import, cloud part")
|
|
||||||
flag.StringVar(&cloudUsername, "clouduser", cloudUsername, "Username used to sync")
|
|
||||||
flag.StringVar(&cloudPassword, "cloudpass", cloudPassword, "Password used to sync")
|
|
||||||
flag.Var(&sync.RemoteFileDomainWhitelist, "remote-file-domain-whitelist", "List of domains which are allowed to store remote files")
|
|
||||||
flag.Parse()
|
|
||||||
|
|
||||||
// Do not display timestamp
|
|
||||||
log.SetFlags(0)
|
|
||||||
|
|
||||||
// Instantiate importer
|
|
||||||
regenImporter := false
|
|
||||||
if localImporterDirectory != "" {
|
|
||||||
sync.GlobalImporter = sync.LocalImporter{Base: localImporterDirectory, Symlink: true}
|
|
||||||
} else if cloudDAVBase != "" {
|
|
||||||
sync.GlobalImporter, _ = sync.NewCloudImporter(cloudDAVBase, cloudUsername, cloudPassword)
|
|
||||||
} else {
|
|
||||||
// In this case, we want to treat the entier path given
|
|
||||||
regenImporter = true
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, p := range flag.Args() {
|
|
||||||
if regenImporter {
|
|
||||||
var err error
|
|
||||||
p, err = filepath.Abs(p)
|
|
||||||
if err != nil {
|
|
||||||
p = path.Clean(p)
|
|
||||||
}
|
|
||||||
sync.GlobalImporter = sync.LocalImporter{
|
|
||||||
Base: p,
|
|
||||||
Symlink: true,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Find all challenge.toml or challenge.txt
|
|
||||||
treatDir("")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func treatDir(p string) {
|
|
||||||
var expath string
|
|
||||||
|
|
||||||
for _, f := range []string{"challenge.toml", "challenge.txt"} {
|
|
||||||
if sync.GlobalImporter.Exists(path.Join(p, f)) {
|
|
||||||
expath = p
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if expath != "" {
|
|
||||||
treatExercice(expath)
|
|
||||||
} else {
|
|
||||||
files, err := sync.GlobalImporter.ListDir(p)
|
|
||||||
if err != nil {
|
|
||||||
log.Printf("Unable to readdir at %s: %s", p, err.Error())
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, f := range files {
|
|
||||||
st, err := sync.GlobalImporter.Stat(path.Join(p, f))
|
|
||||||
if err == nil && st.IsDir() {
|
|
||||||
treatDir(path.Join(p, f))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func treatExercice(expath string) {
|
|
||||||
// Load exercice
|
|
||||||
exercice, _, _, _, _, err := sync.BuildExercice(sync.GlobalImporter, &fic.Theme{}, expath, nil, nil)
|
|
||||||
if exercice == nil {
|
|
||||||
log.Printf("Unable to treat exercice %q: %s", expath, err.Error())
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
paramsFiles, err := sync.GetExerciceFilesParams(sync.GlobalImporter, exercice)
|
|
||||||
if err != nil {
|
|
||||||
log.Printf("Unable to read challenge.toml %q: %s", expath, err.Error())
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
for fname, pf := range paramsFiles {
|
|
||||||
if pf.URL == "" {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
dest := path.Join(exercice.Path, "files", fname)
|
|
||||||
|
|
||||||
log.Printf("Downloading %s...", fname)
|
|
||||||
if li, ok := sync.GlobalImporter.(sync.LocalImporter); ok {
|
|
||||||
err = sync.DownloadExerciceFile(paramsFiles[fname], li.GetLocalPath(dest), exercice, false)
|
|
||||||
} else {
|
|
||||||
err = sync.DownloadExerciceFile(paramsFiles[fname], dest, exercice, false)
|
|
||||||
}
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
log.Println("DownloadExerciceFile error:", err.Error())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
174
admin/index.go
174
admin/index.go
|
|
@ -1,174 +0,0 @@
|
||||||
package main
|
|
||||||
|
|
||||||
const indextpl = `<!DOCTYPE html>
|
|
||||||
<html ng-app="FICApp">
|
|
||||||
<head>
|
|
||||||
<meta charset="utf-8">
|
|
||||||
<title>{{ .title }} - Administration</title>
|
|
||||||
<link href="{{.urlbase}}css/bootstrap.min.css" type="text/css" rel="stylesheet">
|
|
||||||
<link href="{{.urlbase}}css/glyphicon.css" type="text/css" rel="stylesheet" media="screen">
|
|
||||||
<style>
|
|
||||||
.cksum {
|
|
||||||
overflow-x: hidden;
|
|
||||||
text-overflow: ellipsis;
|
|
||||||
max-width: 100%;
|
|
||||||
display: inline-block;
|
|
||||||
vertical-align: middle;
|
|
||||||
word-wrap: normal;
|
|
||||||
white-space: nowrap;
|
|
||||||
}
|
|
||||||
.bg-mfound {
|
|
||||||
background-color: #7bcfd0 !important;
|
|
||||||
}
|
|
||||||
.bg-ffound {
|
|
||||||
background-color: #7bdfc0 !important;
|
|
||||||
}
|
|
||||||
.bg-wchoices {
|
|
||||||
background-color: #c07bdf !important;
|
|
||||||
}
|
|
||||||
.table th.frotated {
|
|
||||||
border: 0;
|
|
||||||
}
|
|
||||||
.table th.rotated {
|
|
||||||
height: 100px;
|
|
||||||
width: 40px;
|
|
||||||
min-width: 40px;
|
|
||||||
max-width: 40px;
|
|
||||||
position: relative;
|
|
||||||
vertical-align: bottom;
|
|
||||||
padding: 0;
|
|
||||||
font-size: 12px;
|
|
||||||
line-height: 0.9;
|
|
||||||
border: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
th.rotated > div {
|
|
||||||
position: relative;
|
|
||||||
top: 0px;
|
|
||||||
left: -50px;
|
|
||||||
height: 100%;
|
|
||||||
transform: skew(45deg,0deg);
|
|
||||||
overflow: hidden;
|
|
||||||
border: 1px solid #000;
|
|
||||||
}
|
|
||||||
th.rotated div a {
|
|
||||||
transform: skew(-45deg,0deg) rotate(45deg);
|
|
||||||
position: absolute;
|
|
||||||
bottom: 40px;
|
|
||||||
left: -35px;
|
|
||||||
display: inline-block;
|
|
||||||
width: 110px;
|
|
||||||
text-align: left;
|
|
||||||
text-overflow: ellipsis;
|
|
||||||
}
|
|
||||||
.col img {
|
|
||||||
max-width: 100%;
|
|
||||||
}
|
|
||||||
.circle-anim {
|
|
||||||
z-index:1;
|
|
||||||
border: black 1px solid;
|
|
||||||
border-radius: .5em;
|
|
||||||
margin-top: .4em;
|
|
||||||
margin-left: .5em;
|
|
||||||
height: 1em;
|
|
||||||
width: 1em;
|
|
||||||
transition: transform ease-in .7s;
|
|
||||||
transform: scale(1);
|
|
||||||
}
|
|
||||||
.circle-anim.play {
|
|
||||||
transform: scale(250);
|
|
||||||
opacity:0;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
<base href="{{.urlbase}}">
|
|
||||||
<script src="js/d3.v3.min.js"></script>
|
|
||||||
</head>
|
|
||||||
<body class="bg-light text-dark">
|
|
||||||
<nav class="navbar sticky-top navbar-expand-lg navbar-dark text-light" ng-class="{'bg-dark': settings.wip, 'bg-danger': !settings.wip}">
|
|
||||||
<a class="navbar-brand" href=".">
|
|
||||||
<img alt="{{ .title }}" src="{{ .logo }}" style="height: 30px">
|
|
||||||
</a>
|
|
||||||
<button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#adminMenu" aria-controls="adminMenu" aria-expanded="false" aria-label="Toggle navigation">
|
|
||||||
<span class="navbar-toggler-icon"></span>
|
|
||||||
</button>
|
|
||||||
|
|
||||||
<div class="collapse navbar-collapse" id="adminMenu">
|
|
||||||
<ul class="navbar-nav mr-auto">
|
|
||||||
<li class="nav-item" ng-class="{'active': $location.path().startsWith('/teams')}"><a class="nav-link" href="teams">Équipes</a></li>
|
|
||||||
<li class="nav-item" ng-class="{'active': $location.path().startsWith('/auth')}"><a class="nav-link" href="auth">Authentification</a></li>
|
|
||||||
<li class="nav-item" ng-class="{'active': $location.path().startsWith('/themes')}"><a class="nav-link" href="themes">Thèmes</a></li>
|
|
||||||
<li class="nav-item" ng-class="{'active': $location.path().startsWith('/exercices')}"><a class="nav-link" href="exercices">Exercices</a></li>
|
|
||||||
<li class="nav-item" ng-class="{'active': $location.path().startsWith('/public')}"><a class="nav-link" href="public/0">Public</a></li>
|
|
||||||
<li class="nav-item" ng-class="{'active': $location.path().startsWith('/events')}"><a class="nav-link" href="events">Événements</a></li>
|
|
||||||
<li class="nav-item" ng-class="{'active': $location.path().startsWith('/claims')}"><a class="nav-link" href="claims" ng-controller="ClaimsTinyListController">
|
|
||||||
Tâches
|
|
||||||
<span class="badge badge-{{ "{{ priorities[myClaimsMaxLevel] }}" }}" ng-show="myClaims" title="Tâches qui me sont assignées">{{ "{{ myClaims }}" }}</span>
|
|
||||||
<span class="badge badge-{{ "{{ priorities[newClaimsMaxLevel] }}" }}" ng-show="newClaims" title="Nouvelles tâches en attente d'attribution">{{ "{{ newClaims }}" }}</span>
|
|
||||||
</a></li>
|
|
||||||
<li class="nav-item" ng-class="{'active': $location.path().startsWith('/sync') || $location.path().startsWith('/repositories')}">
|
|
||||||
<a class="nav-link" href="sync" ng-show="settings.wip">
|
|
||||||
Synchronisation
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
<li class="nav-item" ng-class="{'active': $location.path().startsWith('/settings')}">
|
|
||||||
<a class="nav-link" href="settings">
|
|
||||||
Paramètres
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
<span class="d-flex flex-column justify-content-center" ng-show="!settings.wip" ng-cloak>
|
|
||||||
<span class="badge badge-light p-1">
|
|
||||||
prod
|
|
||||||
</span>
|
|
||||||
</span>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<span id="clock" class="navbar-text" ng-controller="CountdownController" ng-cloak>
|
|
||||||
<div style="pointer-events: none; position: absolute;">
|
|
||||||
<div style="position: absolute;" id="circle1" class="circle-anim border-danger"></div>
|
|
||||||
<div style="position: absolute;" id="circle2" class="circle-anim border-info"></div>
|
|
||||||
</div>
|
|
||||||
<button type="button" class="mr-2 btn btn-sm" ng-class="{'btn-info':staticFilesNeedUpdate,'btn-secondary':!staticFilesNeedUpdate}" ng-click="regenerateStaticFiles()" ng-disabled="staticRegenerationInProgress">
|
|
||||||
<span class="glyphicon glyphicon-refresh" aria-hidden="true" title="Regénérer les fichiers statiques" ng-show="!staticRegenerationInProgress"></span>
|
|
||||||
<div class="spinner-border spinner-border-sm" role="status" ng-show="staticRegenerationInProgress">
|
|
||||||
<span class="sr-only">Loading...</span>
|
|
||||||
</div>
|
|
||||||
<span ng-if="staticFilesNeedUpdate"> {{ "{{ staticFilesNeedUpdate }}" }}</span>
|
|
||||||
</button>
|
|
||||||
<span ng-show="startIn > 0">
|
|
||||||
Démarrage dans :
|
|
||||||
<span>{{"{{ startIn }}"}}</span>"
|
|
||||||
<span class="point">|</span>
|
|
||||||
</span>
|
|
||||||
<span ng-show="settings && settings.end > 0">
|
|
||||||
<span id="hours">{{"{{ time.hours | time }}"}}</span>
|
|
||||||
<span class="point">:</span>
|
|
||||||
<span id="min">{{"{{ time.minutes | time }}"}}</span>
|
|
||||||
<span class="point">:</span>
|
|
||||||
<span id="sec">{{"{{ time.seconds | time }}"}}</span>
|
|
||||||
</span>
|
|
||||||
</span>
|
|
||||||
</nav>
|
|
||||||
|
|
||||||
<div class="progress" style="background-color: #4eaee6; height: 3px; border-radius: 0;">
|
|
||||||
<div class="progress-bar bg-secondary" role="progressbar" style="width: {{ "{{timeProgression * 100}}" }}%"></div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="container mt-1" ng-view></div>
|
|
||||||
|
|
||||||
<div style="position: fixed; top: 60px; right: 0; z-index: 10; min-width: 30vw;">
|
|
||||||
<toast ng-repeat="toast in toasts" yes-no="toast.yesFunc || toast.noFunc" onyes="toast.yesFunc" onno="toast.noFunc" date="toast.date" msg="toast.msg" timeout="toast.timeout" title="toast.title" variant="toast.variant"></toast>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<script src="js/jquery.min.js"></script>
|
|
||||||
<script src="js/popper.min.js"></script>
|
|
||||||
<script src="js/bootstrap.min.js"></script>
|
|
||||||
<script src="js/angular.min.js"></script>
|
|
||||||
<script src="js/angular-resource.min.js"></script>
|
|
||||||
<script src="js/angular-route.min.js"></script>
|
|
||||||
<script src="js/angular-sanitize.min.js"></script>
|
|
||||||
<script src="js/app.js"></script>
|
|
||||||
<script src="js/common.js"></script>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
`
|
|
||||||
333
admin/main.go
333
admin/main.go
|
|
@ -1,333 +0,0 @@
|
||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"flag"
|
|
||||||
"io/fs"
|
|
||||||
"io/ioutil"
|
|
||||||
"log"
|
|
||||||
"net/http"
|
|
||||||
"os"
|
|
||||||
"os/signal"
|
|
||||||
"path"
|
|
||||||
"path/filepath"
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
"syscall"
|
|
||||||
|
|
||||||
"srs.epita.fr/fic-server/admin/api"
|
|
||||||
"srs.epita.fr/fic-server/admin/generation"
|
|
||||||
"srs.epita.fr/fic-server/admin/pki"
|
|
||||||
"srs.epita.fr/fic-server/admin/sync"
|
|
||||||
"srs.epita.fr/fic-server/libfic"
|
|
||||||
"srs.epita.fr/fic-server/settings"
|
|
||||||
)
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
var err error
|
|
||||||
bind := "127.0.0.1:8081"
|
|
||||||
cloudDAVBase := ""
|
|
||||||
cloudUsername := "fic"
|
|
||||||
cloudPassword := ""
|
|
||||||
localImporterDirectory := ""
|
|
||||||
gitImporterRemote := ""
|
|
||||||
gitImporterBranch := ""
|
|
||||||
localImporterSymlink := false
|
|
||||||
baseURL := "/"
|
|
||||||
checkplugins := sync.CheckPluginList{}
|
|
||||||
|
|
||||||
// Read paremeters from environment
|
|
||||||
if v, exists := os.LookupEnv("FICOIDC_ISSUER"); exists {
|
|
||||||
api.OidcIssuer = v
|
|
||||||
} else if v, exists := os.LookupEnv("FICOIDC_ISSUER_FILE"); exists {
|
|
||||||
fd, err := os.Open(v)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal("Unable to open FICOIDC_ISSUER_FILE:", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
b, _ := ioutil.ReadAll(fd)
|
|
||||||
api.OidcIssuer = strings.TrimSpace(string(b))
|
|
||||||
|
|
||||||
fd.Close()
|
|
||||||
}
|
|
||||||
if v, exists := os.LookupEnv("FICOIDC_SECRET"); exists {
|
|
||||||
api.OidcSecret = v
|
|
||||||
} else if v, exists := os.LookupEnv("FICOIDC_SECRET_FILE"); exists {
|
|
||||||
fd, err := os.Open(v)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal("Unable to open FICOIDC_SECRET_FILE:", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
b, _ := ioutil.ReadAll(fd)
|
|
||||||
api.OidcSecret = strings.TrimSpace(string(b))
|
|
||||||
|
|
||||||
fd.Close()
|
|
||||||
}
|
|
||||||
if v, exists := os.LookupEnv("FICCA_PASS"); exists {
|
|
||||||
pki.SetCAPassword(v)
|
|
||||||
} else if v, exists := os.LookupEnv("FICCA_PASS_FILE"); exists {
|
|
||||||
fd, err := os.Open(v)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal("Unable to open FICCA_PASS_FILE:", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
b, _ := ioutil.ReadAll(fd)
|
|
||||||
pki.SetCAPassword(strings.TrimSpace(string(b)))
|
|
||||||
|
|
||||||
fd.Close()
|
|
||||||
} else {
|
|
||||||
log.Println("WARNING: no password defined for the CA, will use empty password to secure CA private key")
|
|
||||||
log.Println("WARNING: PLEASE DEFINE ENVIRONMENT VARIABLE: FICCA_PASS")
|
|
||||||
}
|
|
||||||
if v, exists := os.LookupEnv("FICCLOUD_URL"); exists {
|
|
||||||
cloudDAVBase = v
|
|
||||||
}
|
|
||||||
if v, exists := os.LookupEnv("FICCLOUD_USER"); exists {
|
|
||||||
cloudUsername = v
|
|
||||||
}
|
|
||||||
if v, exists := os.LookupEnv("FICCLOUD_PASS"); exists {
|
|
||||||
cloudPassword = v
|
|
||||||
} else if v, exists := os.LookupEnv("FICCLOUD_PASS_FILE"); exists {
|
|
||||||
fd, err := os.Open(v)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal("Unable to open FICCLOUD_PASS_FILE:", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
b, _ := ioutil.ReadAll(fd)
|
|
||||||
cloudPassword = strings.TrimSpace(string(b))
|
|
||||||
|
|
||||||
fd.Close()
|
|
||||||
}
|
|
||||||
if v, exists := os.LookupEnv("FIC_BASEURL"); exists {
|
|
||||||
baseURL = v
|
|
||||||
}
|
|
||||||
if v, exists := os.LookupEnv("FIC_4REAL"); exists {
|
|
||||||
api.IsProductionEnv, err = strconv.ParseBool(v)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal("Unable to parse FIC_4REAL variable:", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if v, exists := os.LookupEnv("FIC_ADMIN_BIND"); exists {
|
|
||||||
bind = v
|
|
||||||
}
|
|
||||||
if v, exists := os.LookupEnv("FIC_TIMESTAMPCHECK"); exists {
|
|
||||||
api.TimestampCheck = v
|
|
||||||
}
|
|
||||||
if v, exists := os.LookupEnv("FIC_SETTINGS"); exists {
|
|
||||||
settings.SettingsDir = v
|
|
||||||
}
|
|
||||||
if v, exists := os.LookupEnv("FIC_FILES"); exists {
|
|
||||||
fic.FilesDir = v
|
|
||||||
}
|
|
||||||
if v, exists := os.LookupEnv("FIC_SYNC_LOCALIMPORT"); exists {
|
|
||||||
localImporterDirectory = v
|
|
||||||
}
|
|
||||||
if v, exists := os.LookupEnv("FIC_SYNC_LOCALIMPORTSYMLINK"); exists {
|
|
||||||
localImporterSymlink, err = strconv.ParseBool(v)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal("Unable to parse FIC_SYNC_LOCALIMPORTSYMLINK variable:", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if v, exists := os.LookupEnv("FIC_SYNC_GIT_IMPORT_REMOTE"); exists {
|
|
||||||
gitImporterRemote = v
|
|
||||||
}
|
|
||||||
if v, exists := os.LookupEnv("FIC_SYNC_GIT_BRANCH"); exists {
|
|
||||||
gitImporterBranch = v
|
|
||||||
}
|
|
||||||
if v, exists := os.LookupEnv("FIC_OPTIONALDIGEST"); exists {
|
|
||||||
fic.OptionalDigest, err = strconv.ParseBool(v)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal("Unable to parse FIC_OPTIONALDIGEST variable:", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if v, exists := os.LookupEnv("FIC_STRONGDIGEST"); exists {
|
|
||||||
fic.StrongDigest, err = strconv.ParseBool(v)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal("Unable to parse FIC_STRONGDIGEST variable:", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Read parameters from command line
|
|
||||||
flag.StringVar(&bind, "bind", bind, "Bind port/socket")
|
|
||||||
var dsn = flag.String("dsn", fic.DSNGenerator(), "DSN to connect to the MySQL server")
|
|
||||||
flag.StringVar(&baseURL, "baseurl", baseURL, "URL prepended to each URL")
|
|
||||||
flag.StringVar(&api.TimestampCheck, "timestampCheck", api.TimestampCheck, "Path regularly touched by frontend to check time synchronisation")
|
|
||||||
flag.StringVar(&pki.PKIDir, "pki", "./PKI", "Base directory where found PKI scripts")
|
|
||||||
var staticDir = flag.String("static", "", "Directory containing static files (default if not provided: use embedded files)")
|
|
||||||
flag.StringVar(&api.TeamsDir, "teams", "./TEAMS", "Base directory where save teams JSON files")
|
|
||||||
flag.StringVar(&api.DashboardDir, "dashbord", "./DASHBOARD", "Base directory where save public JSON files")
|
|
||||||
flag.StringVar(&settings.SettingsDir, "settings", settings.SettingsDir, "Base directory where load and save settings")
|
|
||||||
flag.StringVar(&fic.FilesDir, "files", fic.FilesDir, "Base directory where found challenges files, local part")
|
|
||||||
flag.StringVar(&generation.GeneratorSocket, "generator", "./GENERATOR/generator.socket", "Path to the generator socket (used to trigger issues.json generations, use an empty string to generate locally)")
|
|
||||||
flag.StringVar(&localImporterDirectory, "localimport", localImporterDirectory,
|
|
||||||
"Base directory where found challenges files to import, local part")
|
|
||||||
flag.BoolVar(&localImporterSymlink, "localimportsymlink", localImporterSymlink,
|
|
||||||
"Copy files or just create symlink?")
|
|
||||||
flag.StringVar(&gitImporterRemote, "git-import-remote", gitImporterRemote,
|
|
||||||
"Remote URL of the git repository to use as synchronization source")
|
|
||||||
flag.StringVar(&gitImporterBranch, "git-branch", gitImporterBranch,
|
|
||||||
"Branch to use in the git repository")
|
|
||||||
flag.StringVar(&cloudDAVBase, "clouddav", cloudDAVBase,
|
|
||||||
"Base directory where found challenges files to import, cloud part")
|
|
||||||
flag.StringVar(&cloudUsername, "clouduser", cloudUsername, "Username used to sync")
|
|
||||||
flag.StringVar(&cloudPassword, "cloudpass", cloudPassword, "Password used to sync")
|
|
||||||
flag.BoolVar(&fic.OptionalDigest, "optionaldigest", fic.OptionalDigest, "Is the digest required when importing files?")
|
|
||||||
flag.BoolVar(&fic.StrongDigest, "strongdigest", fic.StrongDigest, "Are BLAKE2b digests required or is SHA-1 good enough?")
|
|
||||||
flag.BoolVar(&api.IsProductionEnv, "4real", api.IsProductionEnv, "Set this flag when running for a real challenge (it disallows or avoid most of mass user progression deletion)")
|
|
||||||
flag.Var(&checkplugins, "rules-plugins", "List of libraries containing others rules to checks")
|
|
||||||
flag.Var(&sync.RemoteFileDomainWhitelist, "remote-file-domain-whitelist", "List of domains which are allowed to store remote files")
|
|
||||||
flag.Parse()
|
|
||||||
|
|
||||||
log.SetPrefix("[admin] ")
|
|
||||||
|
|
||||||
// Instantiate importer
|
|
||||||
if localImporterDirectory != "" && cloudDAVBase != "" {
|
|
||||||
log.Fatal("Cannot have both --clouddav and --localimport defined.")
|
|
||||||
return
|
|
||||||
} else if gitImporterRemote != "" && cloudDAVBase != "" {
|
|
||||||
log.Fatal("Cannot have both --clouddav and --git-import-remote defined.")
|
|
||||||
return
|
|
||||||
} else if gitImporterRemote != "" {
|
|
||||||
sync.GlobalImporter = sync.NewGitImporter(sync.LocalImporter{Base: localImporterDirectory, Symlink: localImporterSymlink}, gitImporterRemote, gitImporterBranch)
|
|
||||||
} else if localImporterDirectory != "" {
|
|
||||||
sync.GlobalImporter = sync.LocalImporter{Base: localImporterDirectory, Symlink: localImporterSymlink}
|
|
||||||
} else if cloudDAVBase != "" {
|
|
||||||
sync.GlobalImporter, _ = sync.NewCloudImporter(cloudDAVBase, cloudUsername, cloudPassword)
|
|
||||||
}
|
|
||||||
if sync.GlobalImporter != nil {
|
|
||||||
if err := sync.GlobalImporter.Init(); err != nil {
|
|
||||||
log.Fatal("Unable to initialize the importer: ", err.Error())
|
|
||||||
}
|
|
||||||
log.Println("Using", sync.GlobalImporter.Kind())
|
|
||||||
|
|
||||||
challengeinfo, err := sync.GetFileContent(sync.GlobalImporter, settings.ChallengeFile)
|
|
||||||
if err == nil {
|
|
||||||
// Initial distribution of challenge.json
|
|
||||||
if _, err := os.Stat(path.Join(settings.SettingsDir, settings.ChallengeFile)); os.IsNotExist(err) {
|
|
||||||
if fd, err := os.Create(path.Join(settings.SettingsDir, settings.ChallengeFile)); err != nil {
|
|
||||||
log.Fatal("Unable to open SETTINGS/challenge.json:", err)
|
|
||||||
} else {
|
|
||||||
fd.Write([]byte(challengeinfo))
|
|
||||||
err = fd.Close()
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal("Something went wrong during SETTINGS/challenge.json writing:", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if ci, err := settings.ReadChallengeInfo(challengeinfo); err == nil {
|
|
||||||
fic.StandaloneExercicesTheme.Authors = ci.Authors
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Sanitize options
|
|
||||||
log.Println("Checking paths...")
|
|
||||||
if staticDir != nil && *staticDir != "" {
|
|
||||||
if sDir, err := filepath.Abs(*staticDir); err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
} else {
|
|
||||||
log.Println("Serving pages from", sDir)
|
|
||||||
staticFS = http.Dir(sDir)
|
|
||||||
sync.DeepReportPath = path.Join(sDir, sync.DeepReportPath)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
sub, err := fs.Sub(assets, "static")
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal("Unable to cd to static/ directory:", err)
|
|
||||||
}
|
|
||||||
log.Println("Serving pages from memory.")
|
|
||||||
staticFS = http.FS(sub)
|
|
||||||
|
|
||||||
sync.DeepReportPath = path.Join("SYNC", sync.DeepReportPath)
|
|
||||||
if _, err := os.Stat("SYNC"); os.IsNotExist(err) {
|
|
||||||
os.MkdirAll("SYNC", 0751)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if fic.FilesDir, err = filepath.Abs(fic.FilesDir); err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
if pki.PKIDir, err = filepath.Abs(pki.PKIDir); err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
if api.DashboardDir, err = filepath.Abs(api.DashboardDir); err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
if api.TeamsDir, err = filepath.Abs(api.TeamsDir); err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
if api.TimestampCheck, err = filepath.Abs(api.TimestampCheck); err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
if settings.SettingsDir, err = filepath.Abs(settings.SettingsDir); err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
if baseURL != "/" {
|
|
||||||
baseURL = path.Clean(baseURL)
|
|
||||||
} else {
|
|
||||||
baseURL = ""
|
|
||||||
}
|
|
||||||
|
|
||||||
// Creating minimal directories structure
|
|
||||||
os.MkdirAll(fic.FilesDir, 0751)
|
|
||||||
os.MkdirAll(pki.PKIDir, 0711)
|
|
||||||
os.MkdirAll(api.TeamsDir, 0751)
|
|
||||||
os.MkdirAll(api.DashboardDir, 0751)
|
|
||||||
os.MkdirAll(settings.SettingsDir, 0751)
|
|
||||||
|
|
||||||
// Load rules plugins
|
|
||||||
for _, p := range checkplugins {
|
|
||||||
if err := sync.LoadChecksPlugin(p); err != nil {
|
|
||||||
log.Fatalf("Unable to load rule plugin %q: %s", p, err.Error())
|
|
||||||
} else {
|
|
||||||
log.Printf("Rules plugin %q successfully loaded", p)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Initialize settings and load them
|
|
||||||
if !settings.ExistsSettings(path.Join(settings.SettingsDir, settings.SettingsFile)) {
|
|
||||||
if err = api.ResetSettings(); err != nil {
|
|
||||||
log.Fatal("Unable to initialize settings.json:", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
var config *settings.Settings
|
|
||||||
if config, err = settings.ReadSettings(path.Join(settings.SettingsDir, settings.SettingsFile)); err != nil {
|
|
||||||
log.Fatal("Unable to read settings.json:", err)
|
|
||||||
} else {
|
|
||||||
api.ApplySettings(config)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Initialize dashboard presets
|
|
||||||
if err = api.InitDashboardPresets(api.DashboardDir); err != nil {
|
|
||||||
log.Println("Unable to initialize dashboards presets:", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Database connection
|
|
||||||
log.Println("Opening database...")
|
|
||||||
if err = fic.DBInit(*dsn); err != nil {
|
|
||||||
log.Fatal("Cannot open the database: ", err)
|
|
||||||
}
|
|
||||||
defer fic.DBClose()
|
|
||||||
|
|
||||||
log.Println("Creating database...")
|
|
||||||
if err = fic.DBCreate(); err != nil {
|
|
||||||
log.Fatal("Cannot create database: ", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update base URL on main page
|
|
||||||
log.Println("Changing base URL to", baseURL+"/", "...")
|
|
||||||
genIndex(baseURL)
|
|
||||||
|
|
||||||
// Prepare graceful shutdown
|
|
||||||
interrupt := make(chan os.Signal, 1)
|
|
||||||
signal.Notify(interrupt, os.Interrupt, syscall.SIGTERM)
|
|
||||||
|
|
||||||
app := NewApp(config, baseURL, bind)
|
|
||||||
go app.Start()
|
|
||||||
|
|
||||||
// Wait shutdown signal
|
|
||||||
<-interrupt
|
|
||||||
|
|
||||||
log.Print("The service is shutting down...")
|
|
||||||
app.Stop()
|
|
||||||
log.Println("done")
|
|
||||||
}
|
|
||||||
133
admin/pki/ca.go
133
admin/pki/ca.go
|
|
@ -1,133 +0,0 @@
|
||||||
package pki
|
|
||||||
|
|
||||||
import (
|
|
||||||
"crypto/ecdsa"
|
|
||||||
"crypto/rand"
|
|
||||||
"crypto/x509"
|
|
||||||
"crypto/x509/pkix"
|
|
||||||
"encoding/pem"
|
|
||||||
"errors"
|
|
||||||
"io/ioutil"
|
|
||||||
"math/big"
|
|
||||||
"os"
|
|
||||||
"path"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
var passwordCA string
|
|
||||||
|
|
||||||
func SetCAPassword(pass string) {
|
|
||||||
passwordCA = pass
|
|
||||||
}
|
|
||||||
|
|
||||||
func CACertPath() string {
|
|
||||||
return path.Join(PKIDir, "shared", "ca.pem")
|
|
||||||
}
|
|
||||||
|
|
||||||
func CAPrivkeyPath() string {
|
|
||||||
return path.Join(PKIDir, "ca.key")
|
|
||||||
}
|
|
||||||
|
|
||||||
func GenerateCA(notBefore time.Time, notAfter time.Time) error {
|
|
||||||
ca := &x509.Certificate{
|
|
||||||
SerialNumber: big.NewInt(0),
|
|
||||||
Subject: pkix.Name{
|
|
||||||
Organization: []string{"EPITA"},
|
|
||||||
OrganizationalUnit: []string{"SRS laboratory"},
|
|
||||||
Country: []string{"FR"},
|
|
||||||
Locality: []string{"Paris"},
|
|
||||||
CommonName: "FIC CA",
|
|
||||||
},
|
|
||||||
NotBefore: notBefore,
|
|
||||||
NotAfter: notAfter,
|
|
||||||
IsCA: true,
|
|
||||||
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth},
|
|
||||||
KeyUsage: x509.KeyUsageCertSign | x509.KeyUsageCRLSign,
|
|
||||||
BasicConstraintsValid: true,
|
|
||||||
}
|
|
||||||
|
|
||||||
// Ensure directories exists
|
|
||||||
os.Mkdir(PKIDir, 0751)
|
|
||||||
os.Mkdir(path.Join(PKIDir, "shared"), 0751)
|
|
||||||
|
|
||||||
pub, priv, err := GeneratePrivKey()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
ca_b, err := x509.CreateCertificate(rand.Reader, ca, ca, pub, priv)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Save certificate to file
|
|
||||||
if err := saveCertificate(CACertPath(), ca_b); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Save private key to file
|
|
||||||
if err := savePrivateKeyEncrypted(CAPrivkeyPath(), priv, passwordCA); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func LoadCA() (priv ecdsa.PrivateKey, ca x509.Certificate, err error) {
|
|
||||||
// Load certificate
|
|
||||||
if fd, errr := os.Open(CACertPath()); errr != nil {
|
|
||||||
return priv, ca, errr
|
|
||||||
} else {
|
|
||||||
defer fd.Close()
|
|
||||||
if cert, errr := ioutil.ReadAll(fd); errr != nil {
|
|
||||||
return priv, ca, errr
|
|
||||||
} else {
|
|
||||||
block, _ := pem.Decode(cert)
|
|
||||||
if block == nil || block.Type != "CERTIFICATE" {
|
|
||||||
return priv, ca, errors.New("failed to decode PEM block containing certificate")
|
|
||||||
}
|
|
||||||
if catmp, errr := x509.ParseCertificate(block.Bytes); errr != nil {
|
|
||||||
return priv, ca, errr
|
|
||||||
} else if catmp == nil {
|
|
||||||
return priv, ca, errors.New("failed to parse certificate")
|
|
||||||
} else {
|
|
||||||
ca = *catmp
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Load private key
|
|
||||||
if fd, errr := os.Open(CAPrivkeyPath()); errr != nil {
|
|
||||||
return priv, ca, errr
|
|
||||||
} else {
|
|
||||||
defer fd.Close()
|
|
||||||
if privkey, errr := ioutil.ReadAll(fd); errr != nil {
|
|
||||||
return priv, ca, errr
|
|
||||||
} else {
|
|
||||||
block, _ := pem.Decode(privkey)
|
|
||||||
if block == nil || block.Type != "EC PRIVATE KEY" {
|
|
||||||
return priv, ca, errors.New("failed to decode PEM block containing EC private key")
|
|
||||||
}
|
|
||||||
|
|
||||||
var decrypted_der []byte
|
|
||||||
if x509.IsEncryptedPEMBlock(block) {
|
|
||||||
decrypted_der, err = x509.DecryptPEMBlock(block, []byte(passwordCA))
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
decrypted_der = block.Bytes
|
|
||||||
}
|
|
||||||
|
|
||||||
if tmppriv, errr := x509.ParseECPrivateKey(decrypted_der); errr != nil {
|
|
||||||
return priv, ca, errr
|
|
||||||
} else if tmppriv == nil {
|
|
||||||
return priv, ca, errors.New("failed to parse private key")
|
|
||||||
} else {
|
|
||||||
priv = *tmppriv
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
@ -1,84 +0,0 @@
|
||||||
package pki
|
|
||||||
|
|
||||||
import (
|
|
||||||
"crypto/ecdsa"
|
|
||||||
"crypto/rand"
|
|
||||||
"crypto/x509"
|
|
||||||
"crypto/x509/pkix"
|
|
||||||
"fmt"
|
|
||||||
"math"
|
|
||||||
"math/big"
|
|
||||||
"os"
|
|
||||||
"os/exec"
|
|
||||||
"path"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
func ClientCertificatePath(serial uint64) string {
|
|
||||||
return path.Join(PKIDir, fmt.Sprintf("%0[2]*[1]X", serial, int(math.Ceil(math.Log2(float64(serial))/8)*2)), "cert.pem")
|
|
||||||
}
|
|
||||||
|
|
||||||
func ClientPrivkeyPath(serial uint64) string {
|
|
||||||
return path.Join(PKIDir, fmt.Sprintf("%0[2]*[1]X", serial, int(math.Ceil(math.Log2(float64(serial))/8)*2)), "privkey.pem")
|
|
||||||
}
|
|
||||||
|
|
||||||
func ClientP12Path(serial uint64) string {
|
|
||||||
return path.Join(PKIDir, fmt.Sprintf("%0[2]*[1]X", serial, int(math.Ceil(math.Log2(float64(serial))/8)*2)), "team.p12")
|
|
||||||
}
|
|
||||||
|
|
||||||
func GenerateClient(serial uint64, notBefore time.Time, notAfter time.Time, parent_cert *x509.Certificate, parent_priv *ecdsa.PrivateKey) error {
|
|
||||||
var certid big.Int
|
|
||||||
certid.SetUint64(serial)
|
|
||||||
client := &x509.Certificate{
|
|
||||||
SerialNumber: &certid,
|
|
||||||
Subject: pkix.Name{
|
|
||||||
Organization: []string{"EPITA"},
|
|
||||||
OrganizationalUnit: []string{"SRS laboratory"},
|
|
||||||
Country: []string{"FR"},
|
|
||||||
Locality: []string{"Paris"},
|
|
||||||
CommonName: fmt.Sprintf("TEAM-%0[2]*[1]X", serial, int(math.Ceil(math.Log2(float64(serial))/8)*2)),
|
|
||||||
},
|
|
||||||
NotBefore: notBefore,
|
|
||||||
NotAfter: notAfter,
|
|
||||||
IsCA: false,
|
|
||||||
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth},
|
|
||||||
KeyUsage: x509.KeyUsageDigitalSignature,
|
|
||||||
BasicConstraintsValid: true,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub, priv, err := GeneratePrivKey()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
client_b, err := x509.CreateCertificate(rand.Reader, client, parent_cert, pub, parent_priv)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create intermediate directory
|
|
||||||
os.MkdirAll(path.Join(PKIDir, fmt.Sprintf("%0[2]*[1]X", serial, int(math.Ceil(math.Log2(float64(serial))/8)*2))), 0777)
|
|
||||||
|
|
||||||
// Save certificate to file
|
|
||||||
if err := saveCertificate(ClientCertificatePath(serial), client_b); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Save private key to file
|
|
||||||
if err := savePrivateKey(ClientPrivkeyPath(serial), priv); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func WriteP12(serial uint64, password string) error {
|
|
||||||
cmd := exec.Command("/usr/bin/openssl", "pkcs12", "-export",
|
|
||||||
"-inkey", ClientPrivkeyPath(serial),
|
|
||||||
"-in", ClientCertificatePath(serial),
|
|
||||||
"-name", fmt.Sprintf("TEAM-%0[2]*[1]X", serial, int(math.Ceil(math.Log2(float64(serial))/8)*2)),
|
|
||||||
"-passout", "pass:" + password,
|
|
||||||
"-out", ClientP12Path(serial))
|
|
||||||
|
|
||||||
return cmd.Run()
|
|
||||||
}
|
|
||||||
|
|
@ -1,62 +0,0 @@
|
||||||
package pki
|
|
||||||
|
|
||||||
import (
|
|
||||||
"crypto/ecdsa"
|
|
||||||
"crypto/elliptic"
|
|
||||||
"crypto/rand"
|
|
||||||
"crypto/x509"
|
|
||||||
"encoding/pem"
|
|
||||||
"os"
|
|
||||||
)
|
|
||||||
|
|
||||||
var PKIDir string
|
|
||||||
|
|
||||||
func GeneratePrivKey() (pub *ecdsa.PublicKey, priv *ecdsa.PrivateKey, err error) {
|
|
||||||
if priv, err = ecdsa.GenerateKey(elliptic.P384(), rand.Reader); err == nil {
|
|
||||||
pub = &priv.PublicKey
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func saveCertificate(path string, cert []byte) error {
|
|
||||||
if certOut, err := os.Create(path); err != nil {
|
|
||||||
return err
|
|
||||||
} else {
|
|
||||||
pem.Encode(certOut, &pem.Block{Type: "CERTIFICATE", Bytes: cert})
|
|
||||||
certOut.Close()
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func savePrivateKey(path string, private *ecdsa.PrivateKey) error {
|
|
||||||
if keyOut, err := os.OpenFile(path, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0600); err != nil {
|
|
||||||
return err
|
|
||||||
} else if key_b, err := x509.MarshalECPrivateKey(private); err != nil {
|
|
||||||
return err
|
|
||||||
} else {
|
|
||||||
pem.Encode(keyOut, &pem.Block{Type: "EC PRIVATE KEY", Bytes: key_b})
|
|
||||||
keyOut.Close()
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func savePrivateKeyEncrypted(path string, private *ecdsa.PrivateKey, password string) error {
|
|
||||||
if password == "" {
|
|
||||||
return savePrivateKey(path, private)
|
|
||||||
}
|
|
||||||
|
|
||||||
if keyOut, err := os.OpenFile(path, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0600); err != nil {
|
|
||||||
return err
|
|
||||||
} else {
|
|
||||||
defer keyOut.Close()
|
|
||||||
|
|
||||||
if key_b, err := x509.MarshalECPrivateKey(private); err != nil {
|
|
||||||
return err
|
|
||||||
} else if key_c, err := x509.EncryptPEMBlock(rand.Reader, "EC PRIVATE KEY", key_b, []byte(password), x509.PEMCipherAES256); err != nil {
|
|
||||||
return err
|
|
||||||
} else {
|
|
||||||
pem.Encode(keyOut, key_c)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
@ -1,79 +0,0 @@
|
||||||
package pki
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"io/ioutil"
|
|
||||||
"math"
|
|
||||||
"os"
|
|
||||||
"path"
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
)
|
|
||||||
|
|
||||||
const SymlinkPrefix = "_AUTH_ID_"
|
|
||||||
|
|
||||||
func GetCertificateAssociation(serial uint64) string {
|
|
||||||
return fmt.Sprintf(SymlinkPrefix+"%0[2]*[1]X", serial, int(math.Ceil(math.Log2(float64(serial))/8)*2))
|
|
||||||
}
|
|
||||||
|
|
||||||
func GetAssociation(dirname string) (assocs string, err error) {
|
|
||||||
return os.Readlink(dirname)
|
|
||||||
}
|
|
||||||
|
|
||||||
func GetAssociations(dirname string) (assocs []string, err error) {
|
|
||||||
if ds, errr := ioutil.ReadDir(dirname); errr != nil {
|
|
||||||
return nil, errr
|
|
||||||
} else {
|
|
||||||
for _, d := range ds {
|
|
||||||
if d.Mode()&os.ModeSymlink == os.ModeSymlink {
|
|
||||||
assocs = append(assocs, d.Name())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func GetTeamSerials(dirname string, id_team int64) (serials []uint64, err error) {
|
|
||||||
// As futher comparaisons will be made with strings, convert it only one time
|
|
||||||
str_tid := fmt.Sprintf("%d", id_team)
|
|
||||||
|
|
||||||
var assocs []string
|
|
||||||
if assocs, err = GetAssociations(dirname); err != nil {
|
|
||||||
return
|
|
||||||
} else {
|
|
||||||
for _, assoc := range assocs {
|
|
||||||
var tid string
|
|
||||||
if tid, err = os.Readlink(path.Join(dirname, assoc)); err == nil && tid == str_tid && strings.HasPrefix(assoc, SymlinkPrefix) {
|
|
||||||
if serial, err := strconv.ParseUint(assoc[9:], 16, 64); err == nil {
|
|
||||||
serials = append(serials, serial)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func GetTeamAssociations(dirname string, id_team int64) (teamAssocs []string, err error) {
|
|
||||||
// As futher comparaisons will be made with strings, convert it only one time
|
|
||||||
str_tid := fmt.Sprintf("%d", id_team)
|
|
||||||
|
|
||||||
var assocs []string
|
|
||||||
if assocs, err = GetAssociations(dirname); err != nil {
|
|
||||||
return
|
|
||||||
} else {
|
|
||||||
for _, assoc := range assocs {
|
|
||||||
var tid string
|
|
||||||
if tid, err = os.Readlink(path.Join(dirname, assoc)); err == nil && tid == str_tid && !strings.HasPrefix(assoc, SymlinkPrefix) {
|
|
||||||
teamAssocs = append(teamAssocs, assoc)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func DeleteTeamAssociation(dirname string, assoc string) error {
|
|
||||||
if err := os.Remove(path.Join(dirname, assoc)); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
160
admin/static.go
160
admin/static.go
|
|
@ -1,160 +0,0 @@
|
||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"embed"
|
|
||||||
"errors"
|
|
||||||
"log"
|
|
||||||
"net/http"
|
|
||||||
"os"
|
|
||||||
"path"
|
|
||||||
"strings"
|
|
||||||
"text/template"
|
|
||||||
|
|
||||||
"srs.epita.fr/fic-server/admin/api"
|
|
||||||
"srs.epita.fr/fic-server/admin/sync"
|
|
||||||
"srs.epita.fr/fic-server/libfic"
|
|
||||||
"srs.epita.fr/fic-server/settings"
|
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
|
||||||
)
|
|
||||||
|
|
||||||
//go:embed static
|
|
||||||
|
|
||||||
var assets embed.FS
|
|
||||||
|
|
||||||
var indexPage []byte
|
|
||||||
|
|
||||||
func genIndex(baseURL string) {
|
|
||||||
tplcfg := map[string]string{
|
|
||||||
"logo": "img/logo.png",
|
|
||||||
"title": "Challenge",
|
|
||||||
"urlbase": path.Clean(path.Join(baseURL+"/", "nuke"))[:len(path.Clean(path.Join(baseURL+"/", "nuke")))-4],
|
|
||||||
}
|
|
||||||
|
|
||||||
ci, err := api.GetChallengeInfo()
|
|
||||||
if err == nil && ci != nil {
|
|
||||||
tplcfg["title"] = ci.Title
|
|
||||||
if len(ci.MainLogo) > 0 {
|
|
||||||
tplcfg["logo"] = "/files/logo/" + path.Base(ci.MainLogo[0])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
b := bytes.NewBufferString("")
|
|
||||||
if indexTmpl, err := template.New("index").Parse(indextpl); err != nil {
|
|
||||||
log.Fatal("Cannot create template:", err)
|
|
||||||
} else if err = indexTmpl.Execute(b, tplcfg); err != nil {
|
|
||||||
log.Fatal("An error occurs during template execution:", err)
|
|
||||||
} else {
|
|
||||||
indexPage = b.Bytes()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func serveIndex(c *gin.Context) {
|
|
||||||
c.Writer.Write(indexPage)
|
|
||||||
}
|
|
||||||
|
|
||||||
var staticFS http.FileSystem
|
|
||||||
|
|
||||||
func serveFile(c *gin.Context, url string) {
|
|
||||||
c.Request.URL.Path = url
|
|
||||||
http.FileServer(staticFS).ServeHTTP(c.Writer, c.Request)
|
|
||||||
}
|
|
||||||
|
|
||||||
func declareStaticRoutes(router *gin.RouterGroup, cfg *settings.Settings, baseURL string) {
|
|
||||||
router.GET("/", func(c *gin.Context) {
|
|
||||||
serveIndex(c)
|
|
||||||
})
|
|
||||||
router.GET("/auth/*_", func(c *gin.Context) {
|
|
||||||
serveIndex(c)
|
|
||||||
})
|
|
||||||
router.GET("/claims/*_", func(c *gin.Context) {
|
|
||||||
serveIndex(c)
|
|
||||||
})
|
|
||||||
router.GET("/exercices/*_", func(c *gin.Context) {
|
|
||||||
serveIndex(c)
|
|
||||||
})
|
|
||||||
router.GET("/events/*_", func(c *gin.Context) {
|
|
||||||
serveIndex(c)
|
|
||||||
})
|
|
||||||
router.GET("/files", func(c *gin.Context) {
|
|
||||||
serveIndex(c)
|
|
||||||
})
|
|
||||||
router.GET("/forge-links", func(c *gin.Context) {
|
|
||||||
serveIndex(c)
|
|
||||||
})
|
|
||||||
router.GET("/public/*_", func(c *gin.Context) {
|
|
||||||
serveIndex(c)
|
|
||||||
})
|
|
||||||
router.GET("/pki/*_", func(c *gin.Context) {
|
|
||||||
serveIndex(c)
|
|
||||||
})
|
|
||||||
router.GET("/repositories", func(c *gin.Context) {
|
|
||||||
serveIndex(c)
|
|
||||||
})
|
|
||||||
router.GET("/settings", func(c *gin.Context) {
|
|
||||||
serveIndex(c)
|
|
||||||
})
|
|
||||||
router.GET("/sync", func(c *gin.Context) {
|
|
||||||
serveIndex(c)
|
|
||||||
})
|
|
||||||
router.GET("/tags/*_", func(c *gin.Context) {
|
|
||||||
serveIndex(c)
|
|
||||||
})
|
|
||||||
router.GET("/teams/*_", func(c *gin.Context) {
|
|
||||||
serveIndex(c)
|
|
||||||
})
|
|
||||||
router.GET("/themes/*_", func(c *gin.Context) {
|
|
||||||
serveIndex(c)
|
|
||||||
})
|
|
||||||
|
|
||||||
router.GET("/css/*_", func(c *gin.Context) {
|
|
||||||
serveFile(c, strings.TrimPrefix(c.Request.URL.Path, baseURL))
|
|
||||||
})
|
|
||||||
router.GET("/fonts/*_", func(c *gin.Context) {
|
|
||||||
serveFile(c, strings.TrimPrefix(c.Request.URL.Path, baseURL))
|
|
||||||
})
|
|
||||||
router.GET("/img/*_", func(c *gin.Context) {
|
|
||||||
serveFile(c, strings.TrimPrefix(c.Request.URL.Path, baseURL))
|
|
||||||
})
|
|
||||||
router.GET("/js/*_", func(c *gin.Context) {
|
|
||||||
serveFile(c, strings.TrimPrefix(c.Request.URL.Path, baseURL))
|
|
||||||
})
|
|
||||||
router.GET("/views/*_", func(c *gin.Context) {
|
|
||||||
serveFile(c, strings.TrimPrefix(c.Request.URL.Path, baseURL))
|
|
||||||
})
|
|
||||||
|
|
||||||
router.GET("/files/*_", func(c *gin.Context) {
|
|
||||||
filepath := path.Join(fic.FilesDir, strings.TrimPrefix(strings.TrimPrefix(c.Request.URL.Path, baseURL), "/files"))
|
|
||||||
|
|
||||||
if st, err := os.Stat(filepath); os.IsNotExist(err) || st.Size() == 0 {
|
|
||||||
if st, err := os.Stat(filepath + ".gz"); err == nil {
|
|
||||||
if fd, err := os.Open(filepath + ".gz"); err == nil {
|
|
||||||
c.DataFromReader(http.StatusOK, st.Size(), "application/octet-stream", fd, map[string]string{
|
|
||||||
"Content-Encoding": "gzip",
|
|
||||||
})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
c.File(filepath)
|
|
||||||
})
|
|
||||||
router.GET("/submissions/*_", func(c *gin.Context) {
|
|
||||||
http.ServeFile(c.Writer, c.Request, path.Join(api.TimestampCheck, strings.TrimPrefix(c.Request.URL.Path, path.Join(baseURL, "submissions"))))
|
|
||||||
})
|
|
||||||
router.GET("/vids/*_", func(c *gin.Context) {
|
|
||||||
if importer, ok := sync.GlobalImporter.(sync.DirectAccessImporter); ok {
|
|
||||||
http.ServeFile(c.Writer, c.Request, importer.GetLocalPath(strings.TrimPrefix(c.Request.URL.Path, path.Join(baseURL, "vids"))))
|
|
||||||
} else {
|
|
||||||
c.AbortWithError(http.StatusBadRequest, errors.New("Only available with local importer."))
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
router.GET("/check_import.html", func(c *gin.Context) {
|
|
||||||
serveFile(c, "check_import.html")
|
|
||||||
})
|
|
||||||
router.GET("/full_import_report.json", func(c *gin.Context) {
|
|
||||||
http.ServeFile(c.Writer, c.Request, sync.DeepReportPath)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
@ -1,52 +0,0 @@
|
||||||
<!DOCTYPE html>
|
|
||||||
<html>
|
|
||||||
<head>
|
|
||||||
<title>Rapport d'import FIC</title>
|
|
||||||
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
|
|
||||||
<link rel="stylesheet" href="css/bootstrap.min.css">
|
|
||||||
<style>li:hover { background: lightgrey; }
|
|
||||||
</style>
|
|
||||||
<script type="text/javascript">
|
|
||||||
function disp(data) {
|
|
||||||
if (data["_updated"]) {
|
|
||||||
document.getElementById("date_imp").innerHTML = new Intl.DateTimeFormat(undefined, {weekday: 'long', year: 'numeric', month: 'long', day: 'numeric', hour: 'numeric', minute: 'numeric', second: 'numeric'}).format(new Date(data["_updated"][data["_updated"].length - 1]));
|
|
||||||
}
|
|
||||||
data.themes["_ALL"] = data._themes;
|
|
||||||
Object.keys(data.themes).map(function(theme) {
|
|
||||||
if (data.themes[theme] != null && theme != "_date") {
|
|
||||||
var title = document.createElement("h3");
|
|
||||||
title.id = theme;
|
|
||||||
title.innerHTML = theme;
|
|
||||||
document.getElementById("content").appendChild(title);
|
|
||||||
|
|
||||||
var row = document.createElement("ul");
|
|
||||||
row.type = "square";
|
|
||||||
for (var i = 0; i < data.themes[theme].length; i++) {
|
|
||||||
var col = document.createElement("li");
|
|
||||||
col.innerHTML = data.themes[theme][i];
|
|
||||||
row.appendChild(col);
|
|
||||||
}
|
|
||||||
document.getElementById("content").appendChild(row);
|
|
||||||
document.getElementById("content").appendChild(document.createElement("hr"));
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
</head>
|
|
||||||
<body class="container">
|
|
||||||
<h1>Rapport d'import FIC</h1>
|
|
||||||
<p>
|
|
||||||
<strong>Date du dernier import :</strong> <span id="date_imp"></span>
|
|
||||||
</p>
|
|
||||||
<div id="content"></div>
|
|
||||||
<script type="text/javascript">
|
|
||||||
fetch('full_import_report.json')
|
|
||||||
.then(function(response) {
|
|
||||||
return response.json();
|
|
||||||
})
|
|
||||||
.then(function(report) {
|
|
||||||
disp(report);
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
7
admin/static/css/bootstrap.min.css
vendored
7
admin/static/css/bootstrap.min.css
vendored
File diff suppressed because one or more lines are too long
|
|
@ -1,805 +0,0 @@
|
||||||
@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.woff2') format('woff2'), url('../fonts/glyphicons-halflings-regular.woff') format('woff'), url('../fonts/glyphicons-halflings-regular.ttf') format('truetype'), url('../fonts/glyphicons-halflings-regular.svg#glyphicons_halflingsregular') format('svg');
|
|
||||||
}
|
|
||||||
.glyphicon {
|
|
||||||
position: relative;
|
|
||||||
top: 1px;
|
|
||||||
display: inline-block;
|
|
||||||
font-family: 'Glyphicons Halflings';
|
|
||||||
font-style: normal;
|
|
||||||
font-weight: normal;
|
|
||||||
line-height: 1;
|
|
||||||
|
|
||||||
-webkit-font-smoothing: antialiased;
|
|
||||||
-moz-osx-font-smoothing: grayscale;
|
|
||||||
}
|
|
||||||
.glyphicon-asterisk:before {
|
|
||||||
content: "\002a";
|
|
||||||
}
|
|
||||||
.glyphicon-plus:before {
|
|
||||||
content: "\002b";
|
|
||||||
}
|
|
||||||
.glyphicon-euro:before,
|
|
||||||
.glyphicon-eur: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";
|
|
||||||
}
|
|
||||||
.glyphicon-cd:before {
|
|
||||||
content: "\e201";
|
|
||||||
}
|
|
||||||
.glyphicon-save-file:before {
|
|
||||||
content: "\e202";
|
|
||||||
}
|
|
||||||
.glyphicon-open-file:before {
|
|
||||||
content: "\e203";
|
|
||||||
}
|
|
||||||
.glyphicon-level-up:before {
|
|
||||||
content: "\e204";
|
|
||||||
}
|
|
||||||
.glyphicon-copy:before {
|
|
||||||
content: "\e205";
|
|
||||||
}
|
|
||||||
.glyphicon-paste:before {
|
|
||||||
content: "\e206";
|
|
||||||
}
|
|
||||||
.glyphicon-alert:before {
|
|
||||||
content: "\e209";
|
|
||||||
}
|
|
||||||
.glyphicon-equalizer:before {
|
|
||||||
content: "\e210";
|
|
||||||
}
|
|
||||||
.glyphicon-king:before {
|
|
||||||
content: "\e211";
|
|
||||||
}
|
|
||||||
.glyphicon-queen:before {
|
|
||||||
content: "\e212";
|
|
||||||
}
|
|
||||||
.glyphicon-pawn:before {
|
|
||||||
content: "\e213";
|
|
||||||
}
|
|
||||||
.glyphicon-bishop:before {
|
|
||||||
content: "\e214";
|
|
||||||
}
|
|
||||||
.glyphicon-knight:before {
|
|
||||||
content: "\e215";
|
|
||||||
}
|
|
||||||
.glyphicon-baby-formula:before {
|
|
||||||
content: "\e216";
|
|
||||||
}
|
|
||||||
.glyphicon-tent:before {
|
|
||||||
content: "\26fa";
|
|
||||||
}
|
|
||||||
.glyphicon-blackboard:before {
|
|
||||||
content: "\e218";
|
|
||||||
}
|
|
||||||
.glyphicon-bed:before {
|
|
||||||
content: "\e219";
|
|
||||||
}
|
|
||||||
.glyphicon-apple:before {
|
|
||||||
content: "\f8ff";
|
|
||||||
}
|
|
||||||
.glyphicon-erase:before {
|
|
||||||
content: "\e221";
|
|
||||||
}
|
|
||||||
.glyphicon-hourglass:before {
|
|
||||||
content: "\231b";
|
|
||||||
}
|
|
||||||
.glyphicon-lamp:before {
|
|
||||||
content: "\e223";
|
|
||||||
}
|
|
||||||
.glyphicon-duplicate:before {
|
|
||||||
content: "\e224";
|
|
||||||
}
|
|
||||||
.glyphicon-piggy-bank:before {
|
|
||||||
content: "\e225";
|
|
||||||
}
|
|
||||||
.glyphicon-scissors:before {
|
|
||||||
content: "\e226";
|
|
||||||
}
|
|
||||||
.glyphicon-bitcoin:before {
|
|
||||||
content: "\e227";
|
|
||||||
}
|
|
||||||
.glyphicon-btc:before {
|
|
||||||
content: "\e227";
|
|
||||||
}
|
|
||||||
.glyphicon-xbt:before {
|
|
||||||
content: "\e227";
|
|
||||||
}
|
|
||||||
.glyphicon-yen:before {
|
|
||||||
content: "\00a5";
|
|
||||||
}
|
|
||||||
.glyphicon-jpy:before {
|
|
||||||
content: "\00a5";
|
|
||||||
}
|
|
||||||
.glyphicon-ruble:before {
|
|
||||||
content: "\20bd";
|
|
||||||
}
|
|
||||||
.glyphicon-rub:before {
|
|
||||||
content: "\20bd";
|
|
||||||
}
|
|
||||||
.glyphicon-scale:before {
|
|
||||||
content: "\e230";
|
|
||||||
}
|
|
||||||
.glyphicon-ice-lolly:before {
|
|
||||||
content: "\e231";
|
|
||||||
}
|
|
||||||
.glyphicon-ice-lolly-tasted:before {
|
|
||||||
content: "\e232";
|
|
||||||
}
|
|
||||||
.glyphicon-education:before {
|
|
||||||
content: "\e233";
|
|
||||||
}
|
|
||||||
.glyphicon-option-horizontal:before {
|
|
||||||
content: "\e234";
|
|
||||||
}
|
|
||||||
.glyphicon-option-vertical:before {
|
|
||||||
content: "\e235";
|
|
||||||
}
|
|
||||||
.glyphicon-menu-hamburger:before {
|
|
||||||
content: "\e236";
|
|
||||||
}
|
|
||||||
.glyphicon-modal-window:before {
|
|
||||||
content: "\e237";
|
|
||||||
}
|
|
||||||
.glyphicon-oil:before {
|
|
||||||
content: "\e238";
|
|
||||||
}
|
|
||||||
.glyphicon-grain:before {
|
|
||||||
content: "\e239";
|
|
||||||
}
|
|
||||||
.glyphicon-sunglasses:before {
|
|
||||||
content: "\e240";
|
|
||||||
}
|
|
||||||
.glyphicon-text-size:before {
|
|
||||||
content: "\e241";
|
|
||||||
}
|
|
||||||
.glyphicon-text-color:before {
|
|
||||||
content: "\e242";
|
|
||||||
}
|
|
||||||
.glyphicon-text-background:before {
|
|
||||||
content: "\e243";
|
|
||||||
}
|
|
||||||
.glyphicon-object-align-top:before {
|
|
||||||
content: "\e244";
|
|
||||||
}
|
|
||||||
.glyphicon-object-align-bottom:before {
|
|
||||||
content: "\e245";
|
|
||||||
}
|
|
||||||
.glyphicon-object-align-horizontal:before {
|
|
||||||
content: "\e246";
|
|
||||||
}
|
|
||||||
.glyphicon-object-align-left:before {
|
|
||||||
content: "\e247";
|
|
||||||
}
|
|
||||||
.glyphicon-object-align-vertical:before {
|
|
||||||
content: "\e248";
|
|
||||||
}
|
|
||||||
.glyphicon-object-align-right:before {
|
|
||||||
content: "\e249";
|
|
||||||
}
|
|
||||||
.glyphicon-triangle-right:before {
|
|
||||||
content: "\e250";
|
|
||||||
}
|
|
||||||
.glyphicon-triangle-left:before {
|
|
||||||
content: "\e251";
|
|
||||||
}
|
|
||||||
.glyphicon-triangle-bottom:before {
|
|
||||||
content: "\e252";
|
|
||||||
}
|
|
||||||
.glyphicon-triangle-top:before {
|
|
||||||
content: "\e253";
|
|
||||||
}
|
|
||||||
.glyphicon-console:before {
|
|
||||||
content: "\e254";
|
|
||||||
}
|
|
||||||
.glyphicon-superscript:before {
|
|
||||||
content: "\e255";
|
|
||||||
}
|
|
||||||
.glyphicon-subscript:before {
|
|
||||||
content: "\e256";
|
|
||||||
}
|
|
||||||
.glyphicon-menu-left:before {
|
|
||||||
content: "\e257";
|
|
||||||
}
|
|
||||||
.glyphicon-menu-right:before {
|
|
||||||
content: "\e258";
|
|
||||||
}
|
|
||||||
.glyphicon-menu-down:before {
|
|
||||||
content: "\e259";
|
|
||||||
}
|
|
||||||
.glyphicon-menu-up:before {
|
|
||||||
content: "\e260";
|
|
||||||
}
|
|
||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
|
@ -1,288 +0,0 @@
|
||||||
<?xml version="1.0" standalone="no"?>
|
|
||||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd" >
|
|
||||||
<svg xmlns="http://www.w3.org/2000/svg">
|
|
||||||
<metadata></metadata>
|
|
||||||
<defs>
|
|
||||||
<font id="glyphicons_halflingsregular" horiz-adv-x="1200" >
|
|
||||||
<font-face units-per-em="1200" ascent="960" descent="-240" />
|
|
||||||
<missing-glyph horiz-adv-x="500" />
|
|
||||||
<glyph horiz-adv-x="0" />
|
|
||||||
<glyph horiz-adv-x="400" />
|
|
||||||
<glyph unicode=" " />
|
|
||||||
<glyph unicode="*" d="M600 1100q15 0 34 -1.5t30 -3.5l11 -1q10 -2 17.5 -10.5t7.5 -18.5v-224l158 158q7 7 18 8t19 -6l106 -106q7 -8 6 -19t-8 -18l-158 -158h224q10 0 18.5 -7.5t10.5 -17.5q6 -41 6 -75q0 -15 -1.5 -34t-3.5 -30l-1 -11q-2 -10 -10.5 -17.5t-18.5 -7.5h-224l158 -158 q7 -7 8 -18t-6 -19l-106 -106q-8 -7 -19 -6t-18 8l-158 158v-224q0 -10 -7.5 -18.5t-17.5 -10.5q-41 -6 -75 -6q-15 0 -34 1.5t-30 3.5l-11 1q-10 2 -17.5 10.5t-7.5 18.5v224l-158 -158q-7 -7 -18 -8t-19 6l-106 106q-7 8 -6 19t8 18l158 158h-224q-10 0 -18.5 7.5 t-10.5 17.5q-6 41 -6 75q0 15 1.5 34t3.5 30l1 11q2 10 10.5 17.5t18.5 7.5h224l-158 158q-7 7 -8 18t6 19l106 106q8 7 19 6t18 -8l158 -158v224q0 10 7.5 18.5t17.5 10.5q41 6 75 6z" />
|
|
||||||
<glyph unicode="+" d="M450 1100h200q21 0 35.5 -14.5t14.5 -35.5v-350h350q21 0 35.5 -14.5t14.5 -35.5v-200q0 -21 -14.5 -35.5t-35.5 -14.5h-350v-350q0 -21 -14.5 -35.5t-35.5 -14.5h-200q-21 0 -35.5 14.5t-14.5 35.5v350h-350q-21 0 -35.5 14.5t-14.5 35.5v200q0 21 14.5 35.5t35.5 14.5 h350v350q0 21 14.5 35.5t35.5 14.5z" />
|
|
||||||
<glyph unicode=" " />
|
|
||||||
<glyph unicode="¥" d="M825 1100h250q10 0 12.5 -5t-5.5 -13l-364 -364q-6 -6 -11 -18h268q10 0 13 -6t-3 -14l-120 -160q-6 -8 -18 -14t-22 -6h-125v-100h275q10 0 13 -6t-3 -14l-120 -160q-6 -8 -18 -14t-22 -6h-125v-174q0 -11 -7.5 -18.5t-18.5 -7.5h-148q-11 0 -18.5 7.5t-7.5 18.5v174 h-275q-10 0 -13 6t3 14l120 160q6 8 18 14t22 6h125v100h-275q-10 0 -13 6t3 14l120 160q6 8 18 14t22 6h118q-5 12 -11 18l-364 364q-8 8 -5.5 13t12.5 5h250q25 0 43 -18l164 -164q8 -8 18 -8t18 8l164 164q18 18 43 18z" />
|
|
||||||
<glyph unicode=" " horiz-adv-x="650" />
|
|
||||||
<glyph unicode=" " horiz-adv-x="1300" />
|
|
||||||
<glyph unicode=" " horiz-adv-x="650" />
|
|
||||||
<glyph unicode=" " horiz-adv-x="1300" />
|
|
||||||
<glyph unicode=" " horiz-adv-x="433" />
|
|
||||||
<glyph unicode=" " horiz-adv-x="325" />
|
|
||||||
<glyph unicode=" " horiz-adv-x="216" />
|
|
||||||
<glyph unicode=" " horiz-adv-x="216" />
|
|
||||||
<glyph unicode=" " horiz-adv-x="162" />
|
|
||||||
<glyph unicode=" " horiz-adv-x="260" />
|
|
||||||
<glyph unicode=" " horiz-adv-x="72" />
|
|
||||||
<glyph unicode=" " horiz-adv-x="260" />
|
|
||||||
<glyph unicode=" " horiz-adv-x="325" />
|
|
||||||
<glyph unicode="€" d="M744 1198q242 0 354 -189q60 -104 66 -209h-181q0 45 -17.5 82.5t-43.5 61.5t-58 40.5t-60.5 24t-51.5 7.5q-19 0 -40.5 -5.5t-49.5 -20.5t-53 -38t-49 -62.5t-39 -89.5h379l-100 -100h-300q-6 -50 -6 -100h406l-100 -100h-300q9 -74 33 -132t52.5 -91t61.5 -54.5t59 -29 t47 -7.5q22 0 50.5 7.5t60.5 24.5t58 41t43.5 61t17.5 80h174q-30 -171 -128 -278q-107 -117 -274 -117q-206 0 -324 158q-36 48 -69 133t-45 204h-217l100 100h112q1 47 6 100h-218l100 100h134q20 87 51 153.5t62 103.5q117 141 297 141z" />
|
|
||||||
<glyph unicode="₽" d="M428 1200h350q67 0 120 -13t86 -31t57 -49.5t35 -56.5t17 -64.5t6.5 -60.5t0.5 -57v-16.5v-16.5q0 -36 -0.5 -57t-6.5 -61t-17 -65t-35 -57t-57 -50.5t-86 -31.5t-120 -13h-178l-2 -100h288q10 0 13 -6t-3 -14l-120 -160q-6 -8 -18 -14t-22 -6h-138v-175q0 -11 -5.5 -18 t-15.5 -7h-149q-10 0 -17.5 7.5t-7.5 17.5v175h-267q-10 0 -13 6t3 14l120 160q6 8 18 14t22 6h117v100h-267q-10 0 -13 6t3 14l120 160q6 8 18 14t22 6h117v475q0 10 7.5 17.5t17.5 7.5zM600 1000v-300h203q64 0 86.5 33t22.5 119q0 84 -22.5 116t-86.5 32h-203z" />
|
|
||||||
<glyph unicode="−" d="M250 700h800q21 0 35.5 -14.5t14.5 -35.5v-200q0 -21 -14.5 -35.5t-35.5 -14.5h-800q-21 0 -35.5 14.5t-14.5 35.5v200q0 21 14.5 35.5t35.5 14.5z" />
|
|
||||||
<glyph unicode="⌛" d="M1000 1200v-150q0 -21 -14.5 -35.5t-35.5 -14.5h-50v-100q0 -91 -49.5 -165.5t-130.5 -109.5q81 -35 130.5 -109.5t49.5 -165.5v-150h50q21 0 35.5 -14.5t14.5 -35.5v-150h-800v150q0 21 14.5 35.5t35.5 14.5h50v150q0 91 49.5 165.5t130.5 109.5q-81 35 -130.5 109.5 t-49.5 165.5v100h-50q-21 0 -35.5 14.5t-14.5 35.5v150h800zM400 1000v-100q0 -60 32.5 -109.5t87.5 -73.5q28 -12 44 -37t16 -55t-16 -55t-44 -37q-55 -24 -87.5 -73.5t-32.5 -109.5v-150h400v150q0 60 -32.5 109.5t-87.5 73.5q-28 12 -44 37t-16 55t16 55t44 37 q55 24 87.5 73.5t32.5 109.5v100h-400z" />
|
|
||||||
<glyph unicode="◼" horiz-adv-x="500" d="M0 0z" />
|
|
||||||
<glyph unicode="☁" d="M503 1089q110 0 200.5 -59.5t134.5 -156.5q44 14 90 14q120 0 205 -86.5t85 -206.5q0 -121 -85 -207.5t-205 -86.5h-750q-79 0 -135.5 57t-56.5 137q0 69 42.5 122.5t108.5 67.5q-2 12 -2 37q0 153 108 260.5t260 107.5z" />
|
|
||||||
<glyph unicode="⛺" d="M774 1193.5q16 -9.5 20.5 -27t-5.5 -33.5l-136 -187l467 -746h30q20 0 35 -18.5t15 -39.5v-42h-1200v42q0 21 15 39.5t35 18.5h30l468 746l-135 183q-10 16 -5.5 34t20.5 28t34 5.5t28 -20.5l111 -148l112 150q9 16 27 20.5t34 -5zM600 200h377l-182 112l-195 534v-646z " />
|
|
||||||
<glyph unicode="✉" d="M25 1100h1150q10 0 12.5 -5t-5.5 -13l-564 -567q-8 -8 -18 -8t-18 8l-564 567q-8 8 -5.5 13t12.5 5zM18 882l264 -264q8 -8 8 -18t-8 -18l-264 -264q-8 -8 -13 -5.5t-5 12.5v550q0 10 5 12.5t13 -5.5zM918 618l264 264q8 8 13 5.5t5 -12.5v-550q0 -10 -5 -12.5t-13 5.5 l-264 264q-8 8 -8 18t8 18zM818 482l364 -364q8 -8 5.5 -13t-12.5 -5h-1150q-10 0 -12.5 5t5.5 13l364 364q8 8 18 8t18 -8l164 -164q8 -8 18 -8t18 8l164 164q8 8 18 8t18 -8z" />
|
|
||||||
<glyph unicode="✏" d="M1011 1210q19 0 33 -13l153 -153q13 -14 13 -33t-13 -33l-99 -92l-214 214l95 96q13 14 32 14zM1013 800l-615 -614l-214 214l614 614zM317 96l-333 -112l110 335z" />
|
|
||||||
<glyph unicode="" d="M700 650v-550h250q21 0 35.5 -14.5t14.5 -35.5v-50h-800v50q0 21 14.5 35.5t35.5 14.5h250v550l-500 550h1200z" />
|
|
||||||
<glyph unicode="" d="M368 1017l645 163q39 15 63 0t24 -49v-831q0 -55 -41.5 -95.5t-111.5 -63.5q-79 -25 -147 -4.5t-86 75t25.5 111.5t122.5 82q72 24 138 8v521l-600 -155v-606q0 -42 -44 -90t-109 -69q-79 -26 -147 -5.5t-86 75.5t25.5 111.5t122.5 82.5q72 24 138 7v639q0 38 14.5 59 t53.5 34z" />
|
|
||||||
<glyph unicode="" d="M500 1191q100 0 191 -39t156.5 -104.5t104.5 -156.5t39 -191l-1 -2l1 -5q0 -141 -78 -262l275 -274q23 -26 22.5 -44.5t-22.5 -42.5l-59 -58q-26 -20 -46.5 -20t-39.5 20l-275 274q-119 -77 -261 -77l-5 1l-2 -1q-100 0 -191 39t-156.5 104.5t-104.5 156.5t-39 191 t39 191t104.5 156.5t156.5 104.5t191 39zM500 1022q-88 0 -162 -43t-117 -117t-43 -162t43 -162t117 -117t162 -43t162 43t117 117t43 162t-43 162t-117 117t-162 43z" />
|
|
||||||
<glyph unicode="" d="M649 949q48 68 109.5 104t121.5 38.5t118.5 -20t102.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-150 152.5t-126.5 127.5t-93.5 124.5t-33.5 117.5q0 64 28 123t73 100.5t104 64t119 20 t120.5 -38.5t104.5 -104z" />
|
|
||||||
<glyph unicode="" d="M407 800l131 353q7 19 17.5 19t17.5 -19l129 -353h421q21 0 24 -8.5t-14 -20.5l-342 -249l130 -401q7 -20 -0.5 -25.5t-24.5 6.5l-343 246l-342 -247q-17 -12 -24.5 -6.5t-0.5 25.5l130 400l-347 251q-17 12 -14 20.5t23 8.5h429z" />
|
|
||||||
<glyph unicode="" d="M407 800l131 353q7 19 17.5 19t17.5 -19l129 -353h421q21 0 24 -8.5t-14 -20.5l-342 -249l130 -401q7 -20 -0.5 -25.5t-24.5 6.5l-343 246l-342 -247q-17 -12 -24.5 -6.5t-0.5 25.5l130 400l-347 251q-17 12 -14 20.5t23 8.5h429zM477 700h-240l197 -142l-74 -226 l193 139l195 -140l-74 229l192 140h-234l-78 211z" />
|
|
||||||
<glyph unicode="" d="M600 1200q124 0 212 -88t88 -212v-250q0 -46 -31 -98t-69 -52v-75q0 -10 6 -21.5t15 -17.5l358 -230q9 -5 15 -16.5t6 -21.5v-93q0 -10 -7.5 -17.5t-17.5 -7.5h-1150q-10 0 -17.5 7.5t-7.5 17.5v93q0 10 6 21.5t15 16.5l358 230q9 6 15 17.5t6 21.5v75q-38 0 -69 52 t-31 98v250q0 124 88 212t212 88z" />
|
|
||||||
<glyph unicode="" d="M25 1100h1150q10 0 17.5 -7.5t7.5 -17.5v-1050q0 -10 -7.5 -17.5t-17.5 -7.5h-1150q-10 0 -17.5 7.5t-7.5 17.5v1050q0 10 7.5 17.5t17.5 7.5zM100 1000v-100h100v100h-100zM875 1000h-550q-10 0 -17.5 -7.5t-7.5 -17.5v-350q0 -10 7.5 -17.5t17.5 -7.5h550 q10 0 17.5 7.5t7.5 17.5v350q0 10 -7.5 17.5t-17.5 7.5zM1000 1000v-100h100v100h-100zM100 800v-100h100v100h-100zM1000 800v-100h100v100h-100zM100 600v-100h100v100h-100zM1000 600v-100h100v100h-100zM875 500h-550q-10 0 -17.5 -7.5t-7.5 -17.5v-350q0 -10 7.5 -17.5 t17.5 -7.5h550q10 0 17.5 7.5t7.5 17.5v350q0 10 -7.5 17.5t-17.5 7.5zM100 400v-100h100v100h-100zM1000 400v-100h100v100h-100zM100 200v-100h100v100h-100zM1000 200v-100h100v100h-100z" />
|
|
||||||
<glyph unicode="" d="M50 1100h400q21 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.5v400q0 21 14.5 35.5t35.5 14.5zM650 1100h400q21 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.5v400 q0 21 14.5 35.5t35.5 14.5zM50 500h400q21 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.5v400q0 21 14.5 35.5t35.5 14.5zM650 500h400q21 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.5v400q0 21 14.5 35.5t35.5 14.5z" />
|
|
||||||
<glyph unicode="" d="M50 1100h200q21 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.5v200q0 21 14.5 35.5t35.5 14.5zM450 1100h200q21 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.5v200 q0 21 14.5 35.5t35.5 14.5zM850 1100h200q21 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.5v200q0 21 14.5 35.5t35.5 14.5zM50 700h200q21 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.5v200q0 21 14.5 35.5t35.5 14.5zM450 700h200q21 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.5v200q0 21 14.5 35.5t35.5 14.5zM850 700h200q21 0 35.5 -14.5t14.5 -35.5v-200 q0 -21 -14.5 -35.5t-35.5 -14.5h-200q-21 0 -35.5 14.5t-14.5 35.5v200q0 21 14.5 35.5t35.5 14.5zM50 300h200q21 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.5v200q0 21 14.5 35.5t35.5 14.5zM450 300h200 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.5v200q0 21 14.5 35.5t35.5 14.5zM850 300h200q21 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.5v200q0 21 14.5 35.5 t35.5 14.5z" />
|
|
||||||
<glyph unicode="" d="M50 1100h200q21 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.5v200q0 21 14.5 35.5t35.5 14.5zM450 1100h700q21 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.5v200 q0 21 14.5 35.5t35.5 14.5zM50 700h200q21 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.5v200q0 21 14.5 35.5t35.5 14.5zM450 700h700q21 0 35.5 -14.5t14.5 -35.5v-200q0 -21 -14.5 -35.5t-35.5 -14.5h-700 q-21 0 -35.5 14.5t-14.5 35.5v200q0 21 14.5 35.5t35.5 14.5zM50 300h200q21 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.5v200q0 21 14.5 35.5t35.5 14.5zM450 300h700q21 0 35.5 -14.5t14.5 -35.5v-200 q0 -21 -14.5 -35.5t-35.5 -14.5h-700q-21 0 -35.5 14.5t-14.5 35.5v200q0 21 14.5 35.5t35.5 14.5z" />
|
|
||||||
<glyph unicode="" d="M465 477l571 571q8 8 18 8t17 -8l177 -177q8 -7 8 -17t-8 -18l-783 -784q-7 -8 -17.5 -8t-17.5 8l-384 384q-8 8 -8 18t8 17l177 177q7 8 17 8t18 -8l171 -171q7 -7 18 -7t18 7z" />
|
|
||||||
<glyph unicode="" d="M904 1083l178 -179q8 -8 8 -18.5t-8 -17.5l-267 -268l267 -268q8 -7 8 -17.5t-8 -18.5l-178 -178q-8 -8 -18.5 -8t-17.5 8l-268 267l-268 -267q-7 -8 -17.5 -8t-18.5 8l-178 178q-8 8 -8 18.5t8 17.5l267 268l-267 268q-8 7 -8 17.5t8 18.5l178 178q8 8 18.5 8t17.5 -8 l268 -267l268 268q7 7 17.5 7t18.5 -7z" />
|
|
||||||
<glyph unicode="" d="M507 1177q98 0 187.5 -38.5t154.5 -103.5t103.5 -154.5t38.5 -187.5q0 -141 -78 -262l300 -299q8 -8 8 -18.5t-8 -18.5l-109 -108q-7 -8 -17.5 -8t-18.5 8l-300 299q-119 -77 -261 -77q-98 0 -188 38.5t-154.5 103t-103 154.5t-38.5 188t38.5 187.5t103 154.5 t154.5 103.5t188 38.5zM506.5 1023q-89.5 0 -165.5 -44t-120 -120.5t-44 -166t44 -165.5t120 -120t165.5 -44t166 44t120.5 120t44 165.5t-44 166t-120.5 120.5t-166 44zM425 900h150q10 0 17.5 -7.5t7.5 -17.5v-75h75q10 0 17.5 -7.5t7.5 -17.5v-150q0 -10 -7.5 -17.5 t-17.5 -7.5h-75v-75q0 -10 -7.5 -17.5t-17.5 -7.5h-150q-10 0 -17.5 7.5t-7.5 17.5v75h-75q-10 0 -17.5 7.5t-7.5 17.5v150q0 10 7.5 17.5t17.5 7.5h75v75q0 10 7.5 17.5t17.5 7.5z" />
|
|
||||||
<glyph unicode="" d="M507 1177q98 0 187.5 -38.5t154.5 -103.5t103.5 -154.5t38.5 -187.5q0 -141 -78 -262l300 -299q8 -8 8 -18.5t-8 -18.5l-109 -108q-7 -8 -17.5 -8t-18.5 8l-300 299q-119 -77 -261 -77q-98 0 -188 38.5t-154.5 103t-103 154.5t-38.5 188t38.5 187.5t103 154.5 t154.5 103.5t188 38.5zM506.5 1023q-89.5 0 -165.5 -44t-120 -120.5t-44 -166t44 -165.5t120 -120t165.5 -44t166 44t120.5 120t44 165.5t-44 166t-120.5 120.5t-166 44zM325 800h350q10 0 17.5 -7.5t7.5 -17.5v-150q0 -10 -7.5 -17.5t-17.5 -7.5h-350q-10 0 -17.5 7.5 t-7.5 17.5v150q0 10 7.5 17.5t17.5 7.5z" />
|
|
||||||
<glyph unicode="" d="M550 1200h100q21 0 35.5 -14.5t14.5 -35.5v-400q0 -21 -14.5 -35.5t-35.5 -14.5h-100q-21 0 -35.5 14.5t-14.5 35.5v400q0 21 14.5 35.5t35.5 14.5zM800 975v166q167 -62 272 -209.5t105 -331.5q0 -117 -45.5 -224t-123 -184.5t-184.5 -123t-224 -45.5t-224 45.5 t-184.5 123t-123 184.5t-45.5 224q0 184 105 331.5t272 209.5v-166q-103 -55 -165 -155t-62 -220q0 -116 57 -214.5t155.5 -155.5t214.5 -57t214.5 57t155.5 155.5t57 214.5q0 120 -62 220t-165 155z" />
|
|
||||||
<glyph unicode="" d="M1025 1200h150q10 0 17.5 -7.5t7.5 -17.5v-1150q0 -10 -7.5 -17.5t-17.5 -7.5h-150q-10 0 -17.5 7.5t-7.5 17.5v1150q0 10 7.5 17.5t17.5 7.5zM725 800h150q10 0 17.5 -7.5t7.5 -17.5v-750q0 -10 -7.5 -17.5t-17.5 -7.5h-150q-10 0 -17.5 7.5t-7.5 17.5v750 q0 10 7.5 17.5t17.5 7.5zM425 500h150q10 0 17.5 -7.5t7.5 -17.5v-450q0 -10 -7.5 -17.5t-17.5 -7.5h-150q-10 0 -17.5 7.5t-7.5 17.5v450q0 10 7.5 17.5t17.5 7.5zM125 300h150q10 0 17.5 -7.5t7.5 -17.5v-250q0 -10 -7.5 -17.5t-17.5 -7.5h-150q-10 0 -17.5 7.5t-7.5 17.5 v250q0 10 7.5 17.5t17.5 7.5z" />
|
|
||||||
<glyph unicode="" d="M600 1174q33 0 74 -5l38 -152l5 -1q49 -14 94 -39l5 -2l134 80q61 -48 104 -105l-80 -134l3 -5q25 -44 39 -93l1 -6l152 -38q5 -43 5 -73q0 -34 -5 -74l-152 -38l-1 -6q-15 -49 -39 -93l-3 -5l80 -134q-48 -61 -104 -105l-134 81l-5 -3q-44 -25 -94 -39l-5 -2l-38 -151 q-43 -5 -74 -5q-33 0 -74 5l-38 151l-5 2q-49 14 -94 39l-5 3l-134 -81q-60 48 -104 105l80 134l-3 5q-25 45 -38 93l-2 6l-151 38q-6 42 -6 74q0 33 6 73l151 38l2 6q13 48 38 93l3 5l-80 134q47 61 105 105l133 -80l5 2q45 25 94 39l5 1l38 152q43 5 74 5zM600 815 q-89 0 -152 -63t-63 -151.5t63 -151.5t152 -63t152 63t63 151.5t-63 151.5t-152 63z" />
|
|
||||||
<glyph unicode="" d="M500 1300h300q41 0 70.5 -29.5t29.5 -70.5v-100h275q10 0 17.5 -7.5t7.5 -17.5v-75h-1100v75q0 10 7.5 17.5t17.5 7.5h275v100q0 41 29.5 70.5t70.5 29.5zM500 1200v-100h300v100h-300zM1100 900v-800q0 -41 -29.5 -70.5t-70.5 -29.5h-700q-41 0 -70.5 29.5t-29.5 70.5 v800h900zM300 800v-700h100v700h-100zM500 800v-700h100v700h-100zM700 800v-700h100v700h-100zM900 800v-700h100v700h-100z" />
|
|
||||||
<glyph unicode="" d="M18 618l620 608q8 7 18.5 7t17.5 -7l608 -608q8 -8 5.5 -13t-12.5 -5h-175v-575q0 -10 -7.5 -17.5t-17.5 -7.5h-250q-10 0 -17.5 7.5t-7.5 17.5v375h-300v-375q0 -10 -7.5 -17.5t-17.5 -7.5h-250q-10 0 -17.5 7.5t-7.5 17.5v575h-175q-10 0 -12.5 5t5.5 13z" />
|
|
||||||
<glyph unicode="" d="M600 1200v-400q0 -41 29.5 -70.5t70.5 -29.5h300v-650q0 -21 -14.5 -35.5t-35.5 -14.5h-800q-21 0 -35.5 14.5t-14.5 35.5v1100q0 21 14.5 35.5t35.5 14.5h450zM1000 800h-250q-21 0 -35.5 14.5t-14.5 35.5v250z" />
|
|
||||||
<glyph unicode="" d="M600 1177q117 0 224 -45.5t184.5 -123t123 -184.5t45.5 -224t-45.5 -224t-123 -184.5t-184.5 -123t-224 -45.5t-224 45.5t-184.5 123t-123 184.5t-45.5 224t45.5 224t123 184.5t184.5 123t224 45.5zM600 1027q-116 0 -214.5 -57t-155.5 -155.5t-57 -214.5t57 -214.5 t155.5 -155.5t214.5 -57t214.5 57t155.5 155.5t57 214.5t-57 214.5t-155.5 155.5t-214.5 57zM525 900h50q10 0 17.5 -7.5t7.5 -17.5v-275h175q10 0 17.5 -7.5t7.5 -17.5v-50q0 -10 -7.5 -17.5t-17.5 -7.5h-250q-10 0 -17.5 7.5t-7.5 17.5v350q0 10 7.5 17.5t17.5 7.5z" />
|
|
||||||
<glyph unicode="" d="M1300 0h-538l-41 400h-242l-41 -400h-538l431 1200h209l-21 -300h162l-20 300h208zM515 800l-27 -300h224l-27 300h-170z" />
|
|
||||||
<glyph unicode="" d="M550 1200h200q21 0 35.5 -14.5t14.5 -35.5v-450h191q20 0 25.5 -11.5t-7.5 -27.5l-327 -400q-13 -16 -32 -16t-32 16l-327 400q-13 16 -7.5 27.5t25.5 11.5h191v450q0 21 14.5 35.5t35.5 14.5zM1125 400h50q10 0 17.5 -7.5t7.5 -17.5v-350q0 -10 -7.5 -17.5t-17.5 -7.5 h-1050q-10 0 -17.5 7.5t-7.5 17.5v350q0 10 7.5 17.5t17.5 7.5h50q10 0 17.5 -7.5t7.5 -17.5v-175h900v175q0 10 7.5 17.5t17.5 7.5z" />
|
|
||||||
<glyph unicode="" d="M600 1177q117 0 224 -45.5t184.5 -123t123 -184.5t45.5 -224t-45.5 -224t-123 -184.5t-184.5 -123t-224 -45.5t-224 45.5t-184.5 123t-123 184.5t-45.5 224t45.5 224t123 184.5t184.5 123t224 45.5zM600 1027q-116 0 -214.5 -57t-155.5 -155.5t-57 -214.5t57 -214.5 t155.5 -155.5t214.5 -57t214.5 57t155.5 155.5t57 214.5t-57 214.5t-155.5 155.5t-214.5 57zM525 900h150q10 0 17.5 -7.5t7.5 -17.5v-275h137q21 0 26 -11.5t-8 -27.5l-223 -275q-13 -16 -32 -16t-32 16l-223 275q-13 16 -8 27.5t26 11.5h137v275q0 10 7.5 17.5t17.5 7.5z " />
|
|
||||||
<glyph unicode="" d="M600 1177q117 0 224 -45.5t184.5 -123t123 -184.5t45.5 -224t-45.5 -224t-123 -184.5t-184.5 -123t-224 -45.5t-224 45.5t-184.5 123t-123 184.5t-45.5 224t45.5 224t123 184.5t184.5 123t224 45.5zM600 1027q-116 0 -214.5 -57t-155.5 -155.5t-57 -214.5t57 -214.5 t155.5 -155.5t214.5 -57t214.5 57t155.5 155.5t57 214.5t-57 214.5t-155.5 155.5t-214.5 57zM632 914l223 -275q13 -16 8 -27.5t-26 -11.5h-137v-275q0 -10 -7.5 -17.5t-17.5 -7.5h-150q-10 0 -17.5 7.5t-7.5 17.5v275h-137q-21 0 -26 11.5t8 27.5l223 275q13 16 32 16 t32 -16z" />
|
|
||||||
<glyph unicode="" d="M225 1200h750q10 0 19.5 -7t12.5 -17l186 -652q7 -24 7 -49v-425q0 -12 -4 -27t-9 -17q-12 -6 -37 -6h-1100q-12 0 -27 4t-17 8q-6 13 -6 38l1 425q0 25 7 49l185 652q3 10 12.5 17t19.5 7zM878 1000h-556q-10 0 -19 -7t-11 -18l-87 -450q-2 -11 4 -18t16 -7h150 q10 0 19.5 -7t11.5 -17l38 -152q2 -10 11.5 -17t19.5 -7h250q10 0 19.5 7t11.5 17l38 152q2 10 11.5 17t19.5 7h150q10 0 16 7t4 18l-87 450q-2 11 -11 18t-19 7z" />
|
|
||||||
<glyph unicode="" d="M600 1177q117 0 224 -45.5t184.5 -123t123 -184.5t45.5 -224t-45.5 -224t-123 -184.5t-184.5 -123t-224 -45.5t-224 45.5t-184.5 123t-123 184.5t-45.5 224t45.5 224t123 184.5t184.5 123t224 45.5zM600 1027q-116 0 -214.5 -57t-155.5 -155.5t-57 -214.5t57 -214.5 t155.5 -155.5t214.5 -57t214.5 57t155.5 155.5t57 214.5t-57 214.5t-155.5 155.5t-214.5 57zM540 820l253 -190q17 -12 17 -30t-17 -30l-253 -190q-16 -12 -28 -6.5t-12 26.5v400q0 21 12 26.5t28 -6.5z" />
|
|
||||||
<glyph unicode="" d="M947 1060l135 135q7 7 12.5 5t5.5 -13v-362q0 -10 -7.5 -17.5t-17.5 -7.5h-362q-11 0 -13 5.5t5 12.5l133 133q-109 76 -238 76q-116 0 -214.5 -57t-155.5 -155.5t-57 -214.5t57 -214.5t155.5 -155.5t214.5 -57t214.5 57t155.5 155.5t57 214.5h150q0 -117 -45.5 -224 t-123 -184.5t-184.5 -123t-224 -45.5t-224 45.5t-184.5 123t-123 184.5t-45.5 224t45.5 224t123 184.5t184.5 123t224 45.5q192 0 347 -117z" />
|
|
||||||
<glyph unicode="" d="M947 1060l135 135q7 7 12.5 5t5.5 -13v-361q0 -11 -7.5 -18.5t-18.5 -7.5h-361q-11 0 -13 5.5t5 12.5l134 134q-110 75 -239 75q-116 0 -214.5 -57t-155.5 -155.5t-57 -214.5h-150q0 117 45.5 224t123 184.5t184.5 123t224 45.5q192 0 347 -117zM1027 600h150 q0 -117 -45.5 -224t-123 -184.5t-184.5 -123t-224 -45.5q-192 0 -348 118l-134 -134q-7 -8 -12.5 -5.5t-5.5 12.5v360q0 11 7.5 18.5t18.5 7.5h360q10 0 12.5 -5.5t-5.5 -12.5l-133 -133q110 -76 240 -76q116 0 214.5 57t155.5 155.5t57 214.5z" />
|
|
||||||
<glyph unicode="" d="M125 1200h1050q10 0 17.5 -7.5t7.5 -17.5v-1150q0 -10 -7.5 -17.5t-17.5 -7.5h-1050q-10 0 -17.5 7.5t-7.5 17.5v1150q0 10 7.5 17.5t17.5 7.5zM1075 1000h-850q-10 0 -17.5 -7.5t-7.5 -17.5v-850q0 -10 7.5 -17.5t17.5 -7.5h850q10 0 17.5 7.5t7.5 17.5v850 q0 10 -7.5 17.5t-17.5 7.5zM325 900h50q10 0 17.5 -7.5t7.5 -17.5v-50q0 -10 -7.5 -17.5t-17.5 -7.5h-50q-10 0 -17.5 7.5t-7.5 17.5v50q0 10 7.5 17.5t17.5 7.5zM525 900h450q10 0 17.5 -7.5t7.5 -17.5v-50q0 -10 -7.5 -17.5t-17.5 -7.5h-450q-10 0 -17.5 7.5t-7.5 17.5v50 q0 10 7.5 17.5t17.5 7.5zM325 700h50q10 0 17.5 -7.5t7.5 -17.5v-50q0 -10 -7.5 -17.5t-17.5 -7.5h-50q-10 0 -17.5 7.5t-7.5 17.5v50q0 10 7.5 17.5t17.5 7.5zM525 700h450q10 0 17.5 -7.5t7.5 -17.5v-50q0 -10 -7.5 -17.5t-17.5 -7.5h-450q-10 0 -17.5 7.5t-7.5 17.5v50 q0 10 7.5 17.5t17.5 7.5zM325 500h50q10 0 17.5 -7.5t7.5 -17.5v-50q0 -10 -7.5 -17.5t-17.5 -7.5h-50q-10 0 -17.5 7.5t-7.5 17.5v50q0 10 7.5 17.5t17.5 7.5zM525 500h450q10 0 17.5 -7.5t7.5 -17.5v-50q0 -10 -7.5 -17.5t-17.5 -7.5h-450q-10 0 -17.5 7.5t-7.5 17.5v50 q0 10 7.5 17.5t17.5 7.5zM325 300h50q10 0 17.5 -7.5t7.5 -17.5v-50q0 -10 -7.5 -17.5t-17.5 -7.5h-50q-10 0 -17.5 7.5t-7.5 17.5v50q0 10 7.5 17.5t17.5 7.5zM525 300h450q10 0 17.5 -7.5t7.5 -17.5v-50q0 -10 -7.5 -17.5t-17.5 -7.5h-450q-10 0 -17.5 7.5t-7.5 17.5v50 q0 10 7.5 17.5t17.5 7.5z" />
|
|
||||||
<glyph unicode="" d="M900 800v200q0 83 -58.5 141.5t-141.5 58.5h-300q-82 0 -141 -59t-59 -141v-200h-100q-41 0 -70.5 -29.5t-29.5 -70.5v-600q0 -41 29.5 -70.5t70.5 -29.5h900q41 0 70.5 29.5t29.5 70.5v600q0 41 -29.5 70.5t-70.5 29.5h-100zM400 800v150q0 21 15 35.5t35 14.5h200 q20 0 35 -14.5t15 -35.5v-150h-300z" />
|
|
||||||
<glyph unicode="" d="M125 1100h50q10 0 17.5 -7.5t7.5 -17.5v-1075h-100v1075q0 10 7.5 17.5t17.5 7.5zM1075 1052q4 0 9 -2q16 -6 16 -23v-421q0 -6 -3 -12q-33 -59 -66.5 -99t-65.5 -58t-56.5 -24.5t-52.5 -6.5q-26 0 -57.5 6.5t-52.5 13.5t-60 21q-41 15 -63 22.5t-57.5 15t-65.5 7.5 q-85 0 -160 -57q-7 -5 -15 -5q-6 0 -11 3q-14 7 -14 22v438q22 55 82 98.5t119 46.5q23 2 43 0.5t43 -7t32.5 -8.5t38 -13t32.5 -11q41 -14 63.5 -21t57 -14t63.5 -7q103 0 183 87q7 8 18 8z" />
|
|
||||||
<glyph unicode="" d="M600 1175q116 0 227 -49.5t192.5 -131t131 -192.5t49.5 -227v-300q0 -10 -7.5 -17.5t-17.5 -7.5h-50q-10 0 -17.5 7.5t-7.5 17.5v300q0 127 -70.5 231.5t-184.5 161.5t-245 57t-245 -57t-184.5 -161.5t-70.5 -231.5v-300q0 -10 -7.5 -17.5t-17.5 -7.5h-50 q-10 0 -17.5 7.5t-7.5 17.5v300q0 116 49.5 227t131 192.5t192.5 131t227 49.5zM220 500h160q8 0 14 -6t6 -14v-460q0 -8 -6 -14t-14 -6h-160q-8 0 -14 6t-6 14v460q0 8 6 14t14 6zM820 500h160q8 0 14 -6t6 -14v-460q0 -8 -6 -14t-14 -6h-160q-8 0 -14 6t-6 14v460 q0 8 6 14t14 6z" />
|
|
||||||
<glyph unicode="" d="M321 814l258 172q9 6 15 2.5t6 -13.5v-750q0 -10 -6 -13.5t-15 2.5l-258 172q-21 14 -46 14h-250q-10 0 -17.5 7.5t-7.5 17.5v350q0 10 7.5 17.5t17.5 7.5h250q25 0 46 14zM900 668l120 120q7 7 17 7t17 -7l34 -34q7 -7 7 -17t-7 -17l-120 -120l120 -120q7 -7 7 -17 t-7 -17l-34 -34q-7 -7 -17 -7t-17 7l-120 119l-120 -119q-7 -7 -17 -7t-17 7l-34 34q-7 7 -7 17t7 17l119 120l-119 120q-7 7 -7 17t7 17l34 34q7 8 17 8t17 -8z" />
|
|
||||||
<glyph unicode="" d="M321 814l258 172q9 6 15 2.5t6 -13.5v-750q0 -10 -6 -13.5t-15 2.5l-258 172q-21 14 -46 14h-250q-10 0 -17.5 7.5t-7.5 17.5v350q0 10 7.5 17.5t17.5 7.5h250q25 0 46 14zM766 900h4q10 -1 16 -10q96 -129 96 -290q0 -154 -90 -281q-6 -9 -17 -10l-3 -1q-9 0 -16 6 l-29 23q-7 7 -8.5 16.5t4.5 17.5q72 103 72 229q0 132 -78 238q-6 8 -4.5 18t9.5 17l29 22q7 5 15 5z" />
|
|
||||||
<glyph unicode="" d="M967 1004h3q11 -1 17 -10q135 -179 135 -396q0 -105 -34 -206.5t-98 -185.5q-7 -9 -17 -10h-3q-9 0 -16 6l-42 34q-8 6 -9 16t5 18q111 150 111 328q0 90 -29.5 176t-84.5 157q-6 9 -5 19t10 16l42 33q7 5 15 5zM321 814l258 172q9 6 15 2.5t6 -13.5v-750q0 -10 -6 -13.5 t-15 2.5l-258 172q-21 14 -46 14h-250q-10 0 -17.5 7.5t-7.5 17.5v350q0 10 7.5 17.5t17.5 7.5h250q25 0 46 14zM766 900h4q10 -1 16 -10q96 -129 96 -290q0 -154 -90 -281q-6 -9 -17 -10l-3 -1q-9 0 -16 6l-29 23q-7 7 -8.5 16.5t4.5 17.5q72 103 72 229q0 132 -78 238 q-6 8 -4.5 18.5t9.5 16.5l29 22q7 5 15 5z" />
|
|
||||||
<glyph unicode="" d="M500 900h100v-100h-100v-100h-400v-100h-100v600h500v-300zM1200 700h-200v-100h200v-200h-300v300h-200v300h-100v200h600v-500zM100 1100v-300h300v300h-300zM800 1100v-300h300v300h-300zM300 900h-100v100h100v-100zM1000 900h-100v100h100v-100zM300 500h200v-500 h-500v500h200v100h100v-100zM800 300h200v-100h-100v-100h-200v100h-100v100h100v200h-200v100h300v-300zM100 400v-300h300v300h-300zM300 200h-100v100h100v-100zM1200 200h-100v100h100v-100zM700 0h-100v100h100v-100zM1200 0h-300v100h300v-100z" />
|
|
||||||
<glyph unicode="" d="M100 200h-100v1000h100v-1000zM300 200h-100v1000h100v-1000zM700 200h-200v1000h200v-1000zM900 200h-100v1000h100v-1000zM1200 200h-200v1000h200v-1000zM400 0h-300v100h300v-100zM600 0h-100v91h100v-91zM800 0h-100v91h100v-91zM1100 0h-200v91h200v-91z" />
|
|
||||||
<glyph unicode="" d="M500 1200l682 -682q8 -8 8 -18t-8 -18l-464 -464q-8 -8 -18 -8t-18 8l-682 682l1 475q0 10 7.5 17.5t17.5 7.5h474zM319.5 1024.5q-29.5 29.5 -71 29.5t-71 -29.5t-29.5 -71.5t29.5 -71.5t71 -29.5t71 29.5t29.5 71.5t-29.5 71.5z" />
|
|
||||||
<glyph unicode="" d="M500 1200l682 -682q8 -8 8 -18t-8 -18l-464 -464q-8 -8 -18 -8t-18 8l-682 682l1 475q0 10 7.5 17.5t17.5 7.5h474zM800 1200l682 -682q8 -8 8 -18t-8 -18l-464 -464q-8 -8 -18 -8t-18 8l-56 56l424 426l-700 700h150zM319.5 1024.5q-29.5 29.5 -71 29.5t-71 -29.5 t-29.5 -71.5t29.5 -71.5t71 -29.5t71 29.5t29.5 71.5t-29.5 71.5z" />
|
|
||||||
<glyph unicode="" d="M300 1200h825q75 0 75 -75v-900q0 -25 -18 -43l-64 -64q-8 -8 -13 -5.5t-5 12.5v950q0 10 -7.5 17.5t-17.5 7.5h-700q-25 0 -43 -18l-64 -64q-8 -8 -5.5 -13t12.5 -5h700q10 0 17.5 -7.5t7.5 -17.5v-950q0 -10 -7.5 -17.5t-17.5 -7.5h-850q-10 0 -17.5 7.5t-7.5 17.5v975 q0 25 18 43l139 139q18 18 43 18z" />
|
|
||||||
<glyph unicode="" d="M250 1200h800q21 0 35.5 -14.5t14.5 -35.5v-1150l-450 444l-450 -445v1151q0 21 14.5 35.5t35.5 14.5z" />
|
|
||||||
<glyph unicode="" d="M822 1200h-444q-11 0 -19 -7.5t-9 -17.5l-78 -301q-7 -24 7 -45l57 -108q6 -9 17.5 -15t21.5 -6h450q10 0 21.5 6t17.5 15l62 108q14 21 7 45l-83 301q-1 10 -9 17.5t-19 7.5zM1175 800h-150q-10 0 -21 -6.5t-15 -15.5l-78 -156q-4 -9 -15 -15.5t-21 -6.5h-550 q-10 0 -21 6.5t-15 15.5l-78 156q-4 9 -15 15.5t-21 6.5h-150q-10 0 -17.5 -7.5t-7.5 -17.5v-650q0 -10 7.5 -17.5t17.5 -7.5h150q10 0 17.5 7.5t7.5 17.5v150q0 10 7.5 17.5t17.5 7.5h750q10 0 17.5 -7.5t7.5 -17.5v-150q0 -10 7.5 -17.5t17.5 -7.5h150q10 0 17.5 7.5 t7.5 17.5v650q0 10 -7.5 17.5t-17.5 7.5zM850 200h-500q-10 0 -19.5 -7t-11.5 -17l-38 -152q-2 -10 3.5 -17t15.5 -7h600q10 0 15.5 7t3.5 17l-38 152q-2 10 -11.5 17t-19.5 7z" />
|
|
||||||
<glyph unicode="" d="M500 1100h200q56 0 102.5 -20.5t72.5 -50t44 -59t25 -50.5l6 -20h150q41 0 70.5 -29.5t29.5 -70.5v-600q0 -41 -29.5 -70.5t-70.5 -29.5h-1000q-41 0 -70.5 29.5t-29.5 70.5v600q0 41 29.5 70.5t70.5 29.5h150q2 8 6.5 21.5t24 48t45 61t72 48t102.5 21.5zM900 800v-100 h100v100h-100zM600 730q-95 0 -162.5 -67.5t-67.5 -162.5t67.5 -162.5t162.5 -67.5t162.5 67.5t67.5 162.5t-67.5 162.5t-162.5 67.5zM600 603q43 0 73 -30t30 -73t-30 -73t-73 -30t-73 30t-30 73t30 73t73 30z" />
|
|
||||||
<glyph unicode="" d="M681 1199l385 -998q20 -50 60 -92q18 -19 36.5 -29.5t27.5 -11.5l10 -2v-66h-417v66q53 0 75 43.5t5 88.5l-82 222h-391q-58 -145 -92 -234q-11 -34 -6.5 -57t25.5 -37t46 -20t55 -6v-66h-365v66q56 24 84 52q12 12 25 30.5t20 31.5l7 13l399 1006h93zM416 521h340 l-162 457z" />
|
|
||||||
<glyph unicode="" d="M753 641q5 -1 14.5 -4.5t36 -15.5t50.5 -26.5t53.5 -40t50.5 -54.5t35.5 -70t14.5 -87q0 -67 -27.5 -125.5t-71.5 -97.5t-98.5 -66.5t-108.5 -40.5t-102 -13h-500v89q41 7 70.5 32.5t29.5 65.5v827q0 24 -0.5 34t-3.5 24t-8.5 19.5t-17 13.5t-28 12.5t-42.5 11.5v71 l471 -1q57 0 115.5 -20.5t108 -57t80.5 -94t31 -124.5q0 -51 -15.5 -96.5t-38 -74.5t-45 -50.5t-38.5 -30.5zM400 700h139q78 0 130.5 48.5t52.5 122.5q0 41 -8.5 70.5t-29.5 55.5t-62.5 39.5t-103.5 13.5h-118v-350zM400 200h216q80 0 121 50.5t41 130.5q0 90 -62.5 154.5 t-156.5 64.5h-159v-400z" />
|
|
||||||
<glyph unicode="" d="M877 1200l2 -57q-83 -19 -116 -45.5t-40 -66.5l-132 -839q-9 -49 13 -69t96 -26v-97h-500v97q186 16 200 98l173 832q3 17 3 30t-1.5 22.5t-9 17.5t-13.5 12.5t-21.5 10t-26 8.5t-33.5 10q-13 3 -19 5v57h425z" />
|
|
||||||
<glyph unicode="" d="M1300 900h-50q0 21 -4 37t-9.5 26.5t-18 17.5t-22 11t-28.5 5.5t-31 2t-37 0.5h-200v-850q0 -22 25 -34.5t50 -13.5l25 -2v-100h-400v100q4 0 11 0.5t24 3t30 7t24 15t11 24.5v850h-200q-25 0 -37 -0.5t-31 -2t-28.5 -5.5t-22 -11t-18 -17.5t-9.5 -26.5t-4 -37h-50v300 h1000v-300zM175 1000h-75v-800h75l-125 -167l-125 167h75v800h-75l125 167z" />
|
|
||||||
<glyph unicode="" d="M1100 900h-50q0 21 -4 37t-9.5 26.5t-18 17.5t-22 11t-28.5 5.5t-31 2t-37 0.5h-200v-650q0 -22 25 -34.5t50 -13.5l25 -2v-100h-400v100q4 0 11 0.5t24 3t30 7t24 15t11 24.5v650h-200q-25 0 -37 -0.5t-31 -2t-28.5 -5.5t-22 -11t-18 -17.5t-9.5 -26.5t-4 -37h-50v300 h1000v-300zM1167 50l-167 -125v75h-800v-75l-167 125l167 125v-75h800v75z" />
|
|
||||||
<glyph unicode="" d="M50 1100h600q21 0 35.5 -14.5t14.5 -35.5v-100q0 -21 -14.5 -35.5t-35.5 -14.5h-600q-21 0 -35.5 14.5t-14.5 35.5v100q0 21 14.5 35.5t35.5 14.5zM50 800h1000q21 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.5v100 q0 21 14.5 35.5t35.5 14.5zM50 500h800q21 0 35.5 -14.5t14.5 -35.5v-100q0 -21 -14.5 -35.5t-35.5 -14.5h-800q-21 0 -35.5 14.5t-14.5 35.5v100q0 21 14.5 35.5t35.5 14.5zM50 200h1100q21 0 35.5 -14.5t14.5 -35.5v-100q0 -21 -14.5 -35.5t-35.5 -14.5h-1100 q-21 0 -35.5 14.5t-14.5 35.5v100q0 21 14.5 35.5t35.5 14.5z" />
|
|
||||||
<glyph unicode="" d="M250 1100h700q21 0 35.5 -14.5t14.5 -35.5v-100q0 -21 -14.5 -35.5t-35.5 -14.5h-700q-21 0 -35.5 14.5t-14.5 35.5v100q0 21 14.5 35.5t35.5 14.5zM50 800h1100q21 0 35.5 -14.5t14.5 -35.5v-100q0 -21 -14.5 -35.5t-35.5 -14.5h-1100q-21 0 -35.5 14.5t-14.5 35.5v100 q0 21 14.5 35.5t35.5 14.5zM250 500h700q21 0 35.5 -14.5t14.5 -35.5v-100q0 -21 -14.5 -35.5t-35.5 -14.5h-700q-21 0 -35.5 14.5t-14.5 35.5v100q0 21 14.5 35.5t35.5 14.5zM50 200h1100q21 0 35.5 -14.5t14.5 -35.5v-100q0 -21 -14.5 -35.5t-35.5 -14.5h-1100 q-21 0 -35.5 14.5t-14.5 35.5v100q0 21 14.5 35.5t35.5 14.5z" />
|
|
||||||
<glyph unicode="" d="M500 950v100q0 21 14.5 35.5t35.5 14.5h600q21 0 35.5 -14.5t14.5 -35.5v-100q0 -21 -14.5 -35.5t-35.5 -14.5h-600q-21 0 -35.5 14.5t-14.5 35.5zM100 650v100q0 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-1000 q-21 0 -35.5 14.5t-14.5 35.5zM300 350v100q0 21 14.5 35.5t35.5 14.5h800q21 0 35.5 -14.5t14.5 -35.5v-100q0 -21 -14.5 -35.5t-35.5 -14.5h-800q-21 0 -35.5 14.5t-14.5 35.5zM0 50v100q0 21 14.5 35.5t35.5 14.5h1100q21 0 35.5 -14.5t14.5 -35.5v-100 q0 -21 -14.5 -35.5t-35.5 -14.5h-1100q-21 0 -35.5 14.5t-14.5 35.5z" />
|
|
||||||
<glyph unicode="" d="M50 1100h1100q21 0 35.5 -14.5t14.5 -35.5v-100q0 -21 -14.5 -35.5t-35.5 -14.5h-1100q-21 0 -35.5 14.5t-14.5 35.5v100q0 21 14.5 35.5t35.5 14.5zM50 800h1100q21 0 35.5 -14.5t14.5 -35.5v-100q0 -21 -14.5 -35.5t-35.5 -14.5h-1100q-21 0 -35.5 14.5t-14.5 35.5v100 q0 21 14.5 35.5t35.5 14.5zM50 500h1100q21 0 35.5 -14.5t14.5 -35.5v-100q0 -21 -14.5 -35.5t-35.5 -14.5h-1100q-21 0 -35.5 14.5t-14.5 35.5v100q0 21 14.5 35.5t35.5 14.5zM50 200h1100q21 0 35.5 -14.5t14.5 -35.5v-100q0 -21 -14.5 -35.5t-35.5 -14.5h-1100 q-21 0 -35.5 14.5t-14.5 35.5v100q0 21 14.5 35.5t35.5 14.5z" />
|
|
||||||
<glyph unicode="" d="M50 1100h100q21 0 35.5 -14.5t14.5 -35.5v-100q0 -21 -14.5 -35.5t-35.5 -14.5h-100q-21 0 -35.5 14.5t-14.5 35.5v100q0 21 14.5 35.5t35.5 14.5zM350 1100h800q21 0 35.5 -14.5t14.5 -35.5v-100q0 -21 -14.5 -35.5t-35.5 -14.5h-800q-21 0 -35.5 14.5t-14.5 35.5v100 q0 21 14.5 35.5t35.5 14.5zM50 800h100q21 0 35.5 -14.5t14.5 -35.5v-100q0 -21 -14.5 -35.5t-35.5 -14.5h-100q-21 0 -35.5 14.5t-14.5 35.5v100q0 21 14.5 35.5t35.5 14.5zM350 800h800q21 0 35.5 -14.5t14.5 -35.5v-100q0 -21 -14.5 -35.5t-35.5 -14.5h-800 q-21 0 -35.5 14.5t-14.5 35.5v100q0 21 14.5 35.5t35.5 14.5zM50 500h100q21 0 35.5 -14.5t14.5 -35.5v-100q0 -21 -14.5 -35.5t-35.5 -14.5h-100q-21 0 -35.5 14.5t-14.5 35.5v100q0 21 14.5 35.5t35.5 14.5zM350 500h800q21 0 35.5 -14.5t14.5 -35.5v-100 q0 -21 -14.5 -35.5t-35.5 -14.5h-800q-21 0 -35.5 14.5t-14.5 35.5v100q0 21 14.5 35.5t35.5 14.5zM50 200h100q21 0 35.5 -14.5t14.5 -35.5v-100q0 -21 -14.5 -35.5t-35.5 -14.5h-100q-21 0 -35.5 14.5t-14.5 35.5v100q0 21 14.5 35.5t35.5 14.5zM350 200h800 q21 0 35.5 -14.5t14.5 -35.5v-100q0 -21 -14.5 -35.5t-35.5 -14.5h-800q-21 0 -35.5 14.5t-14.5 35.5v100q0 21 14.5 35.5t35.5 14.5z" />
|
|
||||||
<glyph unicode="" d="M400 0h-100v1100h100v-1100zM550 1100h100q21 0 35.5 -14.5t14.5 -35.5v-100q0 -21 -14.5 -35.5t-35.5 -14.5h-100q-21 0 -35.5 14.5t-14.5 35.5v100q0 21 14.5 35.5t35.5 14.5zM550 800h500q21 0 35.5 -14.5t14.5 -35.5v-100q0 -21 -14.5 -35.5t-35.5 -14.5h-500 q-21 0 -35.5 14.5t-14.5 35.5v100q0 21 14.5 35.5t35.5 14.5zM267 550l-167 -125v75h-200v100h200v75zM550 500h300q21 0 35.5 -14.5t14.5 -35.5v-100q0 -21 -14.5 -35.5t-35.5 -14.5h-300q-21 0 -35.5 14.5t-14.5 35.5v100q0 21 14.5 35.5t35.5 14.5zM550 200h600 q21 0 35.5 -14.5t14.5 -35.5v-100q0 -21 -14.5 -35.5t-35.5 -14.5h-600q-21 0 -35.5 14.5t-14.5 35.5v100q0 21 14.5 35.5t35.5 14.5z" />
|
|
||||||
<glyph unicode="" d="M50 1100h100q21 0 35.5 -14.5t14.5 -35.5v-100q0 -21 -14.5 -35.5t-35.5 -14.5h-100q-21 0 -35.5 14.5t-14.5 35.5v100q0 21 14.5 35.5t35.5 14.5zM900 0h-100v1100h100v-1100zM50 800h500q21 0 35.5 -14.5t14.5 -35.5v-100q0 -21 -14.5 -35.5t-35.5 -14.5h-500 q-21 0 -35.5 14.5t-14.5 35.5v100q0 21 14.5 35.5t35.5 14.5zM1100 600h200v-100h-200v-75l-167 125l167 125v-75zM50 500h300q21 0 35.5 -14.5t14.5 -35.5v-100q0 -21 -14.5 -35.5t-35.5 -14.5h-300q-21 0 -35.5 14.5t-14.5 35.5v100q0 21 14.5 35.5t35.5 14.5zM50 200h600 q21 0 35.5 -14.5t14.5 -35.5v-100q0 -21 -14.5 -35.5t-35.5 -14.5h-600q-21 0 -35.5 14.5t-14.5 35.5v100q0 21 14.5 35.5t35.5 14.5z" />
|
|
||||||
<glyph unicode="" d="M75 1000h750q31 0 53 -22t22 -53v-650q0 -31 -22 -53t-53 -22h-750q-31 0 -53 22t-22 53v650q0 31 22 53t53 22zM1200 300l-300 300l300 300v-600z" />
|
|
||||||
<glyph unicode="" d="M44 1100h1112q18 0 31 -13t13 -31v-1012q0 -18 -13 -31t-31 -13h-1112q-18 0 -31 13t-13 31v1012q0 18 13 31t31 13zM100 1000v-737l247 182l298 -131l-74 156l293 318l236 -288v500h-1000zM342 884q56 0 95 -39t39 -94.5t-39 -95t-95 -39.5t-95 39.5t-39 95t39 94.5 t95 39z" />
|
|
||||||
<glyph unicode="" d="M648 1169q117 0 216 -60t156.5 -161t57.5 -218q0 -115 -70 -258q-69 -109 -158 -225.5t-143 -179.5l-54 -62q-9 8 -25.5 24.5t-63.5 67.5t-91 103t-98.5 128t-95.5 148q-60 132 -60 249q0 88 34 169.5t91.5 142t137 96.5t166.5 36zM652.5 974q-91.5 0 -156.5 -65 t-65 -157t65 -156.5t156.5 -64.5t156.5 64.5t65 156.5t-65 157t-156.5 65z" />
|
|
||||||
<glyph unicode="" d="M600 1177q117 0 224 -45.5t184.5 -123t123 -184.5t45.5 -224t-45.5 -224t-123 -184.5t-184.5 -123t-224 -45.5t-224 45.5t-184.5 123t-123 184.5t-45.5 224t45.5 224t123 184.5t184.5 123t224 45.5zM600 173v854q-116 0 -214.5 -57t-155.5 -155.5t-57 -214.5t57 -214.5 t155.5 -155.5t214.5 -57z" />
|
|
||||||
<glyph unicode="" d="M554 1295q21 -72 57.5 -143.5t76 -130t83 -118t82.5 -117t70 -116t49.5 -126t18.5 -136.5q0 -71 -25.5 -135t-68.5 -111t-99 -82t-118.5 -54t-125.5 -23q-84 5 -161.5 34t-139.5 78.5t-99 125t-37 164.5q0 69 18 136.5t49.5 126.5t69.5 116.5t81.5 117.5t83.5 119 t76.5 131t58.5 143zM344 710q-23 -33 -43.5 -70.5t-40.5 -102.5t-17 -123q1 -37 14.5 -69.5t30 -52t41 -37t38.5 -24.5t33 -15q21 -7 32 -1t13 22l6 34q2 10 -2.5 22t-13.5 19q-5 4 -14 12t-29.5 40.5t-32.5 73.5q-26 89 6 271q2 11 -6 11q-8 1 -15 -10z" />
|
|
||||||
<glyph unicode="" d="M1000 1013l108 115q2 1 5 2t13 2t20.5 -1t25 -9.5t28.5 -21.5q22 -22 27 -43t0 -32l-6 -10l-108 -115zM350 1100h400q50 0 105 -13l-187 -187h-368q-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.5v182l200 200v-332 q0 -165 -93.5 -257.5t-256.5 -92.5h-400q-165 0 -257.5 92.5t-92.5 257.5v400q0 165 92.5 257.5t257.5 92.5zM1009 803l-362 -362l-161 -50l55 170l355 355z" />
|
|
||||||
<glyph unicode="" d="M350 1100h361q-164 -146 -216 -200h-195q-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-103q0 -165 -92.5 -257.5t-257.5 -92.5h-400q-165 0 -257.5 92.5t-92.5 257.5v400q0 165 92.5 257.5t257.5 92.5z M824 1073l339 -301q8 -7 8 -17.5t-8 -17.5l-340 -306q-7 -6 -12.5 -4t-6.5 11v203q-26 1 -54.5 0t-78.5 -7.5t-92 -17.5t-86 -35t-70 -57q10 59 33 108t51.5 81.5t65 58.5t68.5 40.5t67 24.5t56 13.5t40 4.5v210q1 10 6.5 12.5t13.5 -4.5z" />
|
|
||||||
<glyph unicode="" d="M350 1100h350q60 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-219q0 -165 -92.5 -257.5t-257.5 -92.5h-400q-165 0 -257.5 92.5t-92.5 257.5v400q0 165 92.5 257.5t257.5 92.5z M643 639l395 395q7 7 17.5 7t17.5 -7l101 -101q7 -7 7 -17.5t-7 -17.5l-531 -532q-7 -7 -17.5 -7t-17.5 7l-248 248q-7 7 -7 17.5t7 17.5l101 101q7 7 17.5 7t17.5 -7l111 -111q8 -7 18 -7t18 7z" />
|
|
||||||
<glyph unicode="" d="M318 918l264 264q8 8 18 8t18 -8l260 -264q7 -8 4.5 -13t-12.5 -5h-170v-200h200v173q0 10 5 12t13 -5l264 -260q8 -7 8 -17.5t-8 -17.5l-264 -265q-8 -7 -13 -5t-5 12v173h-200v-200h170q10 0 12.5 -5t-4.5 -13l-260 -264q-8 -8 -18 -8t-18 8l-264 264q-8 8 -5.5 13 t12.5 5h175v200h-200v-173q0 -10 -5 -12t-13 5l-264 265q-8 7 -8 17.5t8 17.5l264 260q8 7 13 5t5 -12v-173h200v200h-175q-10 0 -12.5 5t5.5 13z" />
|
|
||||||
<glyph unicode="" d="M250 1100h100q21 0 35.5 -14.5t14.5 -35.5v-438l464 453q15 14 25.5 10t10.5 -25v-1000q0 -21 -10.5 -25t-25.5 10l-464 453v-438q0 -21 -14.5 -35.5t-35.5 -14.5h-100q-21 0 -35.5 14.5t-14.5 35.5v1000q0 21 14.5 35.5t35.5 14.5z" />
|
|
||||||
<glyph unicode="" d="M50 1100h100q21 0 35.5 -14.5t14.5 -35.5v-438l464 453q15 14 25.5 10t10.5 -25v-438l464 453q15 14 25.5 10t10.5 -25v-1000q0 -21 -10.5 -25t-25.5 10l-464 453v-438q0 -21 -10.5 -25t-25.5 10l-464 453v-438q0 -21 -14.5 -35.5t-35.5 -14.5h-100q-21 0 -35.5 14.5 t-14.5 35.5v1000q0 21 14.5 35.5t35.5 14.5z" />
|
|
||||||
<glyph unicode="" d="M1200 1050v-1000q0 -21 -10.5 -25t-25.5 10l-464 453v-438q0 -21 -10.5 -25t-25.5 10l-492 480q-15 14 -15 35t15 35l492 480q15 14 25.5 10t10.5 -25v-438l464 453q15 14 25.5 10t10.5 -25z" />
|
|
||||||
<glyph unicode="" d="M243 1074l814 -498q18 -11 18 -26t-18 -26l-814 -498q-18 -11 -30.5 -4t-12.5 28v1000q0 21 12.5 28t30.5 -4z" />
|
|
||||||
<glyph unicode="" d="M250 1000h200q21 0 35.5 -14.5t14.5 -35.5v-800q0 -21 -14.5 -35.5t-35.5 -14.5h-200q-21 0 -35.5 14.5t-14.5 35.5v800q0 21 14.5 35.5t35.5 14.5zM650 1000h200q21 0 35.5 -14.5t14.5 -35.5v-800q0 -21 -14.5 -35.5t-35.5 -14.5h-200q-21 0 -35.5 14.5t-14.5 35.5v800 q0 21 14.5 35.5t35.5 14.5z" />
|
|
||||||
<glyph unicode="" d="M1100 950v-800q0 -21 -14.5 -35.5t-35.5 -14.5h-800q-21 0 -35.5 14.5t-14.5 35.5v800q0 21 14.5 35.5t35.5 14.5h800q21 0 35.5 -14.5t14.5 -35.5z" />
|
|
||||||
<glyph unicode="" d="M500 612v438q0 21 10.5 25t25.5 -10l492 -480q15 -14 15 -35t-15 -35l-492 -480q-15 -14 -25.5 -10t-10.5 25v438l-464 -453q-15 -14 -25.5 -10t-10.5 25v1000q0 21 10.5 25t25.5 -10z" />
|
|
||||||
<glyph unicode="" d="M1048 1102l100 1q20 0 35 -14.5t15 -35.5l5 -1000q0 -21 -14.5 -35.5t-35.5 -14.5l-100 -1q-21 0 -35.5 14.5t-14.5 35.5l-2 437l-463 -454q-14 -15 -24.5 -10.5t-10.5 25.5l-2 437l-462 -455q-15 -14 -25.5 -9.5t-10.5 24.5l-5 1000q0 21 10.5 25.5t25.5 -10.5l466 -450 l-2 438q0 20 10.5 24.5t25.5 -9.5l466 -451l-2 438q0 21 14.5 35.5t35.5 14.5z" />
|
|
||||||
<glyph unicode="" d="M850 1100h100q21 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-464 -453q-15 -14 -25.5 -10t-10.5 25v1000q0 21 10.5 25t25.5 -10l464 -453v438q0 21 14.5 35.5t35.5 14.5z" />
|
|
||||||
<glyph unicode="" d="M686 1081l501 -540q15 -15 10.5 -26t-26.5 -11h-1042q-22 0 -26.5 11t10.5 26l501 540q15 15 36 15t36 -15zM150 400h1000q21 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.5v100q0 21 14.5 35.5t35.5 14.5z" />
|
|
||||||
<glyph unicode="" d="M885 900l-352 -353l352 -353l-197 -198l-552 552l552 550z" />
|
|
||||||
<glyph unicode="" d="M1064 547l-551 -551l-198 198l353 353l-353 353l198 198z" />
|
|
||||||
<glyph unicode="" d="M600 1177q117 0 224 -45.5t184.5 -123t123 -184.5t45.5 -224t-45.5 -224t-123 -184.5t-184.5 -123t-224 -45.5t-224 45.5t-184.5 123t-123 184.5t-45.5 224t45.5 224t123 184.5t184.5 123t224 45.5zM650 900h-100q-21 0 -35.5 -14.5t-14.5 -35.5v-150h-150 q-21 0 -35.5 -14.5t-14.5 -35.5v-100q0 -21 14.5 -35.5t35.5 -14.5h150v-150q0 -21 14.5 -35.5t35.5 -14.5h100q21 0 35.5 14.5t14.5 35.5v150h150q21 0 35.5 14.5t14.5 35.5v100q0 21 -14.5 35.5t-35.5 14.5h-150v150q0 21 -14.5 35.5t-35.5 14.5z" />
|
|
||||||
<glyph unicode="" d="M600 1177q117 0 224 -45.5t184.5 -123t123 -184.5t45.5 -224t-45.5 -224t-123 -184.5t-184.5 -123t-224 -45.5t-224 45.5t-184.5 123t-123 184.5t-45.5 224t45.5 224t123 184.5t184.5 123t224 45.5zM850 700h-500q-21 0 -35.5 -14.5t-14.5 -35.5v-100q0 -21 14.5 -35.5 t35.5 -14.5h500q21 0 35.5 14.5t14.5 35.5v100q0 21 -14.5 35.5t-35.5 14.5z" />
|
|
||||||
<glyph unicode="" d="M600 1177q117 0 224 -45.5t184.5 -123t123 -184.5t45.5 -224t-45.5 -224t-123 -184.5t-184.5 -123t-224 -45.5t-224 45.5t-184.5 123t-123 184.5t-45.5 224t45.5 224t123 184.5t184.5 123t224 45.5zM741.5 913q-12.5 0 -21.5 -9l-120 -120l-120 120q-9 9 -21.5 9 t-21.5 -9l-141 -141q-9 -9 -9 -21.5t9 -21.5l120 -120l-120 -120q-9 -9 -9 -21.5t9 -21.5l141 -141q9 -9 21.5 -9t21.5 9l120 120l120 -120q9 -9 21.5 -9t21.5 9l141 141q9 9 9 21.5t-9 21.5l-120 120l120 120q9 9 9 21.5t-9 21.5l-141 141q-9 9 -21.5 9z" />
|
|
||||||
<glyph unicode="" d="M600 1177q117 0 224 -45.5t184.5 -123t123 -184.5t45.5 -224t-45.5 -224t-123 -184.5t-184.5 -123t-224 -45.5t-224 45.5t-184.5 123t-123 184.5t-45.5 224t45.5 224t123 184.5t184.5 123t224 45.5zM546 623l-84 85q-7 7 -17.5 7t-18.5 -7l-139 -139q-7 -8 -7 -18t7 -18 l242 -241q7 -8 17.5 -8t17.5 8l375 375q7 7 7 17.5t-7 18.5l-139 139q-7 7 -17.5 7t-17.5 -7z" />
|
|
||||||
<glyph unicode="" d="M600 1177q117 0 224 -45.5t184.5 -123t123 -184.5t45.5 -224t-45.5 -224t-123 -184.5t-184.5 -123t-224 -45.5t-224 45.5t-184.5 123t-123 184.5t-45.5 224t45.5 224t123 184.5t184.5 123t224 45.5zM588 941q-29 0 -59 -5.5t-63 -20.5t-58 -38.5t-41.5 -63t-16.5 -89.5 q0 -25 20 -25h131q30 -5 35 11q6 20 20.5 28t45.5 8q20 0 31.5 -10.5t11.5 -28.5q0 -23 -7 -34t-26 -18q-1 0 -13.5 -4t-19.5 -7.5t-20 -10.5t-22 -17t-18.5 -24t-15.5 -35t-8 -46q-1 -8 5.5 -16.5t20.5 -8.5h173q7 0 22 8t35 28t37.5 48t29.5 74t12 100q0 47 -17 83 t-42.5 57t-59.5 34.5t-64 18t-59 4.5zM675 400h-150q-10 0 -17.5 -7.5t-7.5 -17.5v-150q0 -10 7.5 -17.5t17.5 -7.5h150q10 0 17.5 7.5t7.5 17.5v150q0 10 -7.5 17.5t-17.5 7.5z" />
|
|
||||||
<glyph unicode="" d="M600 1177q117 0 224 -45.5t184.5 -123t123 -184.5t45.5 -224t-45.5 -224t-123 -184.5t-184.5 -123t-224 -45.5t-224 45.5t-184.5 123t-123 184.5t-45.5 224t45.5 224t123 184.5t184.5 123t224 45.5zM675 1000h-150q-10 0 -17.5 -7.5t-7.5 -17.5v-150q0 -10 7.5 -17.5 t17.5 -7.5h150q10 0 17.5 7.5t7.5 17.5v150q0 10 -7.5 17.5t-17.5 7.5zM675 700h-250q-10 0 -17.5 -7.5t-7.5 -17.5v-50q0 -10 7.5 -17.5t17.5 -7.5h75v-200h-75q-10 0 -17.5 -7.5t-7.5 -17.5v-50q0 -10 7.5 -17.5t17.5 -7.5h350q10 0 17.5 7.5t7.5 17.5v50q0 10 -7.5 17.5 t-17.5 7.5h-75v275q0 10 -7.5 17.5t-17.5 7.5z" />
|
|
||||||
<glyph unicode="" d="M525 1200h150q10 0 17.5 -7.5t7.5 -17.5v-194q103 -27 178.5 -102.5t102.5 -178.5h194q10 0 17.5 -7.5t7.5 -17.5v-150q0 -10 -7.5 -17.5t-17.5 -7.5h-194q-27 -103 -102.5 -178.5t-178.5 -102.5v-194q0 -10 -7.5 -17.5t-17.5 -7.5h-150q-10 0 -17.5 7.5t-7.5 17.5v194 q-103 27 -178.5 102.5t-102.5 178.5h-194q-10 0 -17.5 7.5t-7.5 17.5v150q0 10 7.5 17.5t17.5 7.5h194q27 103 102.5 178.5t178.5 102.5v194q0 10 7.5 17.5t17.5 7.5zM700 893v-168q0 -10 -7.5 -17.5t-17.5 -7.5h-150q-10 0 -17.5 7.5t-7.5 17.5v168q-68 -23 -119 -74 t-74 -119h168q10 0 17.5 -7.5t7.5 -17.5v-150q0 -10 -7.5 -17.5t-17.5 -7.5h-168q23 -68 74 -119t119 -74v168q0 10 7.5 17.5t17.5 7.5h150q10 0 17.5 -7.5t7.5 -17.5v-168q68 23 119 74t74 119h-168q-10 0 -17.5 7.5t-7.5 17.5v150q0 10 7.5 17.5t17.5 7.5h168 q-23 68 -74 119t-119 74z" />
|
|
||||||
<glyph unicode="" d="M600 1177q117 0 224 -45.5t184.5 -123t123 -184.5t45.5 -224t-45.5 -224t-123 -184.5t-184.5 -123t-224 -45.5t-224 45.5t-184.5 123t-123 184.5t-45.5 224t45.5 224t123 184.5t184.5 123t224 45.5zM600 1027q-116 0 -214.5 -57t-155.5 -155.5t-57 -214.5t57 -214.5 t155.5 -155.5t214.5 -57t214.5 57t155.5 155.5t57 214.5t-57 214.5t-155.5 155.5t-214.5 57zM759 823l64 -64q7 -7 7 -17.5t-7 -17.5l-124 -124l124 -124q7 -7 7 -17.5t-7 -17.5l-64 -64q-7 -7 -17.5 -7t-17.5 7l-124 124l-124 -124q-7 -7 -17.5 -7t-17.5 7l-64 64 q-7 7 -7 17.5t7 17.5l124 124l-124 124q-7 7 -7 17.5t7 17.5l64 64q7 7 17.5 7t17.5 -7l124 -124l124 124q7 7 17.5 7t17.5 -7z" />
|
|
||||||
<glyph unicode="" d="M600 1177q117 0 224 -45.5t184.5 -123t123 -184.5t45.5 -224t-45.5 -224t-123 -184.5t-184.5 -123t-224 -45.5t-224 45.5t-184.5 123t-123 184.5t-45.5 224t45.5 224t123 184.5t184.5 123t224 45.5zM600 1027q-116 0 -214.5 -57t-155.5 -155.5t-57 -214.5t57 -214.5 t155.5 -155.5t214.5 -57t214.5 57t155.5 155.5t57 214.5t-57 214.5t-155.5 155.5t-214.5 57zM782 788l106 -106q7 -7 7 -17.5t-7 -17.5l-320 -321q-8 -7 -18 -7t-18 7l-202 203q-8 7 -8 17.5t8 17.5l106 106q7 8 17.5 8t17.5 -8l79 -79l197 197q7 7 17.5 7t17.5 -7z" />
|
|
||||||
<glyph unicode="" d="M600 1177q117 0 224 -45.5t184.5 -123t123 -184.5t45.5 -224t-45.5 -224t-123 -184.5t-184.5 -123t-224 -45.5t-224 45.5t-184.5 123t-123 184.5t-45.5 224t45.5 224t123 184.5t184.5 123t224 45.5zM600 1027q-116 0 -214.5 -57t-155.5 -155.5t-57 -214.5q0 -120 65 -225 l587 587q-105 65 -225 65zM965 819l-584 -584q104 -62 219 -62q116 0 214.5 57t155.5 155.5t57 214.5q0 115 -62 219z" />
|
|
||||||
<glyph unicode="" d="M39 582l522 427q16 13 27.5 8t11.5 -26v-291h550q21 0 35.5 -14.5t14.5 -35.5v-200q0 -21 -14.5 -35.5t-35.5 -14.5h-550v-291q0 -21 -11.5 -26t-27.5 8l-522 427q-16 13 -16 32t16 32z" />
|
|
||||||
<glyph unicode="" d="M639 1009l522 -427q16 -13 16 -32t-16 -32l-522 -427q-16 -13 -27.5 -8t-11.5 26v291h-550q-21 0 -35.5 14.5t-14.5 35.5v200q0 21 14.5 35.5t35.5 14.5h550v291q0 21 11.5 26t27.5 -8z" />
|
|
||||||
<glyph unicode="" d="M682 1161l427 -522q13 -16 8 -27.5t-26 -11.5h-291v-550q0 -21 -14.5 -35.5t-35.5 -14.5h-200q-21 0 -35.5 14.5t-14.5 35.5v550h-291q-21 0 -26 11.5t8 27.5l427 522q13 16 32 16t32 -16z" />
|
|
||||||
<glyph unicode="" d="M550 1200h200q21 0 35.5 -14.5t14.5 -35.5v-550h291q21 0 26 -11.5t-8 -27.5l-427 -522q-13 -16 -32 -16t-32 16l-427 522q-13 16 -8 27.5t26 11.5h291v550q0 21 14.5 35.5t35.5 14.5z" />
|
|
||||||
<glyph unicode="" d="M639 1109l522 -427q16 -13 16 -32t-16 -32l-522 -427q-16 -13 -27.5 -8t-11.5 26v291q-94 -2 -182 -20t-170.5 -52t-147 -92.5t-100.5 -135.5q5 105 27 193.5t67.5 167t113 135t167 91.5t225.5 42v262q0 21 11.5 26t27.5 -8z" />
|
|
||||||
<glyph unicode="" d="M850 1200h300q21 0 35.5 -14.5t14.5 -35.5v-300q0 -21 -10.5 -25t-24.5 10l-94 94l-249 -249q-8 -7 -18 -7t-18 7l-106 106q-7 8 -7 18t7 18l249 249l-94 94q-14 14 -10 24.5t25 10.5zM350 0h-300q-21 0 -35.5 14.5t-14.5 35.5v300q0 21 10.5 25t24.5 -10l94 -94l249 249 q8 7 18 7t18 -7l106 -106q7 -8 7 -18t-7 -18l-249 -249l94 -94q14 -14 10 -24.5t-25 -10.5z" />
|
|
||||||
<glyph unicode="" d="M1014 1120l106 -106q7 -8 7 -18t-7 -18l-249 -249l94 -94q14 -14 10 -24.5t-25 -10.5h-300q-21 0 -35.5 14.5t-14.5 35.5v300q0 21 10.5 25t24.5 -10l94 -94l249 249q8 7 18 7t18 -7zM250 600h300q21 0 35.5 -14.5t14.5 -35.5v-300q0 -21 -10.5 -25t-24.5 10l-94 94 l-249 -249q-8 -7 -18 -7t-18 7l-106 106q-7 8 -7 18t7 18l249 249l-94 94q-14 14 -10 24.5t25 10.5z" />
|
|
||||||
<glyph unicode="" d="M600 1177q117 0 224 -45.5t184.5 -123t123 -184.5t45.5 -224t-45.5 -224t-123 -184.5t-184.5 -123t-224 -45.5t-224 45.5t-184.5 123t-123 184.5t-45.5 224t45.5 224t123 184.5t184.5 123t224 45.5zM704 900h-208q-20 0 -32 -14.5t-8 -34.5l58 -302q4 -20 21.5 -34.5 t37.5 -14.5h54q20 0 37.5 14.5t21.5 34.5l58 302q4 20 -8 34.5t-32 14.5zM675 400h-150q-10 0 -17.5 -7.5t-7.5 -17.5v-150q0 -10 7.5 -17.5t17.5 -7.5h150q10 0 17.5 7.5t7.5 17.5v150q0 10 -7.5 17.5t-17.5 7.5z" />
|
|
||||||
<glyph unicode="" d="M260 1200q9 0 19 -2t15 -4l5 -2q22 -10 44 -23l196 -118q21 -13 36 -24q29 -21 37 -12q11 13 49 35l196 118q22 13 45 23q17 7 38 7q23 0 47 -16.5t37 -33.5l13 -16q14 -21 18 -45l25 -123l8 -44q1 -9 8.5 -14.5t17.5 -5.5h61q10 0 17.5 -7.5t7.5 -17.5v-50 q0 -10 -7.5 -17.5t-17.5 -7.5h-50q-10 0 -17.5 -7.5t-7.5 -17.5v-175h-400v300h-200v-300h-400v175q0 10 -7.5 17.5t-17.5 7.5h-50q-10 0 -17.5 7.5t-7.5 17.5v50q0 10 7.5 17.5t17.5 7.5h61q11 0 18 3t7 8q0 4 9 52l25 128q5 25 19 45q2 3 5 7t13.5 15t21.5 19.5t26.5 15.5 t29.5 7zM915 1079l-166 -162q-7 -7 -5 -12t12 -5h219q10 0 15 7t2 17l-51 149q-3 10 -11 12t-15 -6zM463 917l-177 157q-8 7 -16 5t-11 -12l-51 -143q-3 -10 2 -17t15 -7h231q11 0 12.5 5t-5.5 12zM500 0h-375q-10 0 -17.5 7.5t-7.5 17.5v375h400v-400zM1100 400v-375 q0 -10 -7.5 -17.5t-17.5 -7.5h-375v400h400z" />
|
|
||||||
<glyph unicode="" d="M1165 1190q8 3 21 -6.5t13 -17.5q-2 -178 -24.5 -323.5t-55.5 -245.5t-87 -174.5t-102.5 -118.5t-118 -68.5t-118.5 -33t-120 -4.5t-105 9.5t-90 16.5q-61 12 -78 11q-4 1 -12.5 0t-34 -14.5t-52.5 -40.5l-153 -153q-26 -24 -37 -14.5t-11 43.5q0 64 42 102q8 8 50.5 45 t66.5 58q19 17 35 47t13 61q-9 55 -10 102.5t7 111t37 130t78 129.5q39 51 80 88t89.5 63.5t94.5 45t113.5 36t129 31t157.5 37t182 47.5zM1116 1098q-8 9 -22.5 -3t-45.5 -50q-38 -47 -119 -103.5t-142 -89.5l-62 -33q-56 -30 -102 -57t-104 -68t-102.5 -80.5t-85.5 -91 t-64 -104.5q-24 -56 -31 -86t2 -32t31.5 17.5t55.5 59.5q25 30 94 75.5t125.5 77.5t147.5 81q70 37 118.5 69t102 79.5t99 111t86.5 148.5q22 50 24 60t-6 19z" />
|
|
||||||
<glyph unicode="" d="M653 1231q-39 -67 -54.5 -131t-10.5 -114.5t24.5 -96.5t47.5 -80t63.5 -62.5t68.5 -46.5t65 -30q-4 7 -17.5 35t-18.5 39.5t-17 39.5t-17 43t-13 42t-9.5 44.5t-2 42t4 43t13.5 39t23 38.5q96 -42 165 -107.5t105 -138t52 -156t13 -159t-19 -149.5q-13 -55 -44 -106.5 t-68 -87t-78.5 -64.5t-72.5 -45t-53 -22q-72 -22 -127 -11q-31 6 -13 19q6 3 17 7q13 5 32.5 21t41 44t38.5 63.5t21.5 81.5t-6.5 94.5t-50 107t-104 115.5q10 -104 -0.5 -189t-37 -140.5t-65 -93t-84 -52t-93.5 -11t-95 24.5q-80 36 -131.5 114t-53.5 171q-2 23 0 49.5 t4.5 52.5t13.5 56t27.5 60t46 64.5t69.5 68.5q-8 -53 -5 -102.5t17.5 -90t34 -68.5t44.5 -39t49 -2q31 13 38.5 36t-4.5 55t-29 64.5t-36 75t-26 75.5q-15 85 2 161.5t53.5 128.5t85.5 92.5t93.5 61t81.5 25.5z" />
|
|
||||||
<glyph unicode="" d="M600 1094q82 0 160.5 -22.5t140 -59t116.5 -82.5t94.5 -95t68 -95t42.5 -82.5t14 -57.5t-14 -57.5t-43 -82.5t-68.5 -95t-94.5 -95t-116.5 -82.5t-140 -59t-159.5 -22.5t-159.5 22.5t-140 59t-116.5 82.5t-94.5 95t-68.5 95t-43 82.5t-14 57.5t14 57.5t42.5 82.5t68 95 t94.5 95t116.5 82.5t140 59t160.5 22.5zM888 829q-15 15 -18 12t5 -22q25 -57 25 -119q0 -124 -88 -212t-212 -88t-212 88t-88 212q0 59 23 114q8 19 4.5 22t-17.5 -12q-70 -69 -160 -184q-13 -16 -15 -40.5t9 -42.5q22 -36 47 -71t70 -82t92.5 -81t113 -58.5t133.5 -24.5 t133.5 24t113 58.5t92.5 81.5t70 81.5t47 70.5q11 18 9 42.5t-14 41.5q-90 117 -163 189zM448 727l-35 -36q-15 -15 -19.5 -38.5t4.5 -41.5q37 -68 93 -116q16 -13 38.5 -11t36.5 17l35 34q14 15 12.5 33.5t-16.5 33.5q-44 44 -89 117q-11 18 -28 20t-32 -12z" />
|
|
||||||
<glyph unicode="" d="M592 0h-148l31 120q-91 20 -175.5 68.5t-143.5 106.5t-103.5 119t-66.5 110t-22 76q0 21 14 57.5t42.5 82.5t68 95t94.5 95t116.5 82.5t140 59t160.5 22.5q61 0 126 -15l32 121h148zM944 770l47 181q108 -85 176.5 -192t68.5 -159q0 -26 -19.5 -71t-59.5 -102t-93 -112 t-129 -104.5t-158 -75.5l46 173q77 49 136 117t97 131q11 18 9 42.5t-14 41.5q-54 70 -107 130zM310 824q-70 -69 -160 -184q-13 -16 -15 -40.5t9 -42.5q18 -30 39 -60t57 -70.5t74 -73t90 -61t105 -41.5l41 154q-107 18 -178.5 101.5t-71.5 193.5q0 59 23 114q8 19 4.5 22 t-17.5 -12zM448 727l-35 -36q-15 -15 -19.5 -38.5t4.5 -41.5q37 -68 93 -116q16 -13 38.5 -11t36.5 17l12 11l22 86l-3 4q-44 44 -89 117q-11 18 -28 20t-32 -12z" />
|
|
||||||
<glyph unicode="" d="M-90 100l642 1066q20 31 48 28.5t48 -35.5l642 -1056q21 -32 7.5 -67.5t-50.5 -35.5h-1294q-37 0 -50.5 34t7.5 66zM155 200h345v75q0 10 7.5 17.5t17.5 7.5h150q10 0 17.5 -7.5t7.5 -17.5v-75h345l-445 723zM496 700h208q20 0 32 -14.5t8 -34.5l-58 -252 q-4 -20 -21.5 -34.5t-37.5 -14.5h-54q-20 0 -37.5 14.5t-21.5 34.5l-58 252q-4 20 8 34.5t32 14.5z" />
|
|
||||||
<glyph unicode="" d="M650 1200q62 0 106 -44t44 -106v-339l363 -325q15 -14 26 -38.5t11 -44.5v-41q0 -20 -12 -26.5t-29 5.5l-359 249v-263q100 -93 100 -113v-64q0 -21 -13 -29t-32 1l-205 128l-205 -128q-19 -9 -32 -1t-13 29v64q0 20 100 113v263l-359 -249q-17 -12 -29 -5.5t-12 26.5v41 q0 20 11 44.5t26 38.5l363 325v339q0 62 44 106t106 44z" />
|
|
||||||
<glyph unicode="" d="M850 1200h100q21 0 35.5 -14.5t14.5 -35.5v-50h50q21 0 35.5 -14.5t14.5 -35.5v-150h-1100v150q0 21 14.5 35.5t35.5 14.5h50v50q0 21 14.5 35.5t35.5 14.5h100q21 0 35.5 -14.5t14.5 -35.5v-50h500v50q0 21 14.5 35.5t35.5 14.5zM1100 800v-750q0 -21 -14.5 -35.5 t-35.5 -14.5h-1000q-21 0 -35.5 14.5t-14.5 35.5v750h1100zM100 600v-100h100v100h-100zM300 600v-100h100v100h-100zM500 600v-100h100v100h-100zM700 600v-100h100v100h-100zM900 600v-100h100v100h-100zM100 400v-100h100v100h-100zM300 400v-100h100v100h-100zM500 400 v-100h100v100h-100zM700 400v-100h100v100h-100zM900 400v-100h100v100h-100zM100 200v-100h100v100h-100zM300 200v-100h100v100h-100zM500 200v-100h100v100h-100zM700 200v-100h100v100h-100zM900 200v-100h100v100h-100z" />
|
|
||||||
<glyph unicode="" d="M1135 1165l249 -230q15 -14 15 -35t-15 -35l-249 -230q-14 -14 -24.5 -10t-10.5 25v150h-159l-600 -600h-291q-21 0 -35.5 14.5t-14.5 35.5v100q0 21 14.5 35.5t35.5 14.5h209l600 600h241v150q0 21 10.5 25t24.5 -10zM522 819l-141 -141l-122 122h-209q-21 0 -35.5 14.5 t-14.5 35.5v100q0 21 14.5 35.5t35.5 14.5h291zM1135 565l249 -230q15 -14 15 -35t-15 -35l-249 -230q-14 -14 -24.5 -10t-10.5 25v150h-241l-181 181l141 141l122 -122h159v150q0 21 10.5 25t24.5 -10z" />
|
|
||||||
<glyph unicode="" d="M100 1100h1000q41 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.5v600q0 41 29.5 70.5t70.5 29.5z" />
|
|
||||||
<glyph unicode="" d="M150 1200h200q21 0 35.5 -14.5t14.5 -35.5v-250h-300v250q0 21 14.5 35.5t35.5 14.5zM850 1200h200q21 0 35.5 -14.5t14.5 -35.5v-250h-300v250q0 21 14.5 35.5t35.5 14.5zM1100 800v-300q0 -41 -3 -77.5t-15 -89.5t-32 -96t-58 -89t-89 -77t-129 -51t-174 -20t-174 20 t-129 51t-89 77t-58 89t-32 96t-15 89.5t-3 77.5v300h300v-250v-27v-42.5t1.5 -41t5 -38t10 -35t16.5 -30t25.5 -24.5t35 -19t46.5 -12t60 -4t60 4.5t46.5 12.5t35 19.5t25 25.5t17 30.5t10 35t5 38t2 40.5t-0.5 42v25v250h300z" />
|
|
||||||
<glyph unicode="" d="M1100 411l-198 -199l-353 353l-353 -353l-197 199l551 551z" />
|
|
||||||
<glyph unicode="" d="M1101 789l-550 -551l-551 551l198 199l353 -353l353 353z" />
|
|
||||||
<glyph unicode="" d="M404 1000h746q21 0 35.5 -14.5t14.5 -35.5v-551h150q21 0 25 -10.5t-10 -24.5l-230 -249q-14 -15 -35 -15t-35 15l-230 249q-14 14 -10 24.5t25 10.5h150v401h-381zM135 984l230 -249q14 -14 10 -24.5t-25 -10.5h-150v-400h385l215 -200h-750q-21 0 -35.5 14.5 t-14.5 35.5v550h-150q-21 0 -25 10.5t10 24.5l230 249q14 15 35 15t35 -15z" />
|
|
||||||
<glyph unicode="" d="M56 1200h94q17 0 31 -11t18 -27l38 -162h896q24 0 39 -18.5t10 -42.5l-100 -475q-5 -21 -27 -42.5t-55 -21.5h-633l48 -200h535q21 0 35.5 -14.5t14.5 -35.5t-14.5 -35.5t-35.5 -14.5h-50v-50q0 -21 -14.5 -35.5t-35.5 -14.5t-35.5 14.5t-14.5 35.5v50h-300v-50 q0 -21 -14.5 -35.5t-35.5 -14.5t-35.5 14.5t-14.5 35.5v50h-31q-18 0 -32.5 10t-20.5 19l-5 10l-201 961h-54q-20 0 -35 14.5t-15 35.5t15 35.5t35 14.5z" />
|
|
||||||
<glyph unicode="" d="M1200 1000v-100h-1200v100h200q0 41 29.5 70.5t70.5 29.5h300q41 0 70.5 -29.5t29.5 -70.5h500zM0 800h1200v-800h-1200v800z" />
|
|
||||||
<glyph unicode="" d="M200 800l-200 -400v600h200q0 41 29.5 70.5t70.5 29.5h300q42 0 71 -29.5t29 -70.5h500v-200h-1000zM1500 700l-300 -700h-1200l300 700h1200z" />
|
|
||||||
<glyph unicode="" d="M635 1184l230 -249q14 -14 10 -24.5t-25 -10.5h-150v-601h150q21 0 25 -10.5t-10 -24.5l-230 -249q-14 -15 -35 -15t-35 15l-230 249q-14 14 -10 24.5t25 10.5h150v601h-150q-21 0 -25 10.5t10 24.5l230 249q14 15 35 15t35 -15z" />
|
|
||||||
<glyph unicode="" d="M936 864l249 -229q14 -15 14 -35.5t-14 -35.5l-249 -229q-15 -15 -25.5 -10.5t-10.5 24.5v151h-600v-151q0 -20 -10.5 -24.5t-25.5 10.5l-249 229q-14 15 -14 35.5t14 35.5l249 229q15 15 25.5 10.5t10.5 -25.5v-149h600v149q0 21 10.5 25.5t25.5 -10.5z" />
|
|
||||||
<glyph unicode="" d="M1169 400l-172 732q-5 23 -23 45.5t-38 22.5h-672q-20 0 -38 -20t-23 -41l-172 -739h1138zM1100 300h-1000q-41 0 -70.5 -29.5t-29.5 -70.5v-100q0 -41 29.5 -70.5t70.5 -29.5h1000q41 0 70.5 29.5t29.5 70.5v100q0 41 -29.5 70.5t-70.5 29.5zM800 100v100h100v-100h-100 zM1000 100v100h100v-100h-100z" />
|
|
||||||
<glyph unicode="" d="M1150 1100q21 0 35.5 -14.5t14.5 -35.5v-850q0 -21 -14.5 -35.5t-35.5 -14.5t-35.5 14.5t-14.5 35.5v850q0 21 14.5 35.5t35.5 14.5zM1000 200l-675 200h-38l47 -276q3 -16 -5.5 -20t-29.5 -4h-7h-84q-20 0 -34.5 14t-18.5 35q-55 337 -55 351v250v6q0 16 1 23.5t6.5 14 t17.5 6.5h200l675 250v-850zM0 750v-250q-4 0 -11 0.5t-24 6t-30 15t-24 30t-11 48.5v50q0 26 10.5 46t25 30t29 16t25.5 7z" />
|
|
||||||
<glyph unicode="" d="M553 1200h94q20 0 29 -10.5t3 -29.5l-18 -37q83 -19 144 -82.5t76 -140.5l63 -327l118 -173h17q19 0 33 -14.5t14 -35t-13 -40.5t-31 -27q-8 -4 -23 -9.5t-65 -19.5t-103 -25t-132.5 -20t-158.5 -9q-57 0 -115 5t-104 12t-88.5 15.5t-73.5 17.5t-54.5 16t-35.5 12l-11 4 q-18 8 -31 28t-13 40.5t14 35t33 14.5h17l118 173l63 327q15 77 76 140t144 83l-18 32q-6 19 3.5 32t28.5 13zM498 110q50 -6 102 -6q53 0 102 6q-12 -49 -39.5 -79.5t-62.5 -30.5t-63 30.5t-39 79.5z" />
|
|
||||||
<glyph unicode="" d="M800 946l224 78l-78 -224l234 -45l-180 -155l180 -155l-234 -45l78 -224l-224 78l-45 -234l-155 180l-155 -180l-45 234l-224 -78l78 224l-234 45l180 155l-180 155l234 45l-78 224l224 -78l45 234l155 -180l155 180z" />
|
|
||||||
<glyph unicode="" d="M650 1200h50q40 0 70 -40.5t30 -84.5v-150l-28 -125h328q40 0 70 -40.5t30 -84.5v-100q0 -45 -29 -74l-238 -344q-16 -24 -38 -40.5t-45 -16.5h-250q-7 0 -42 25t-66 50l-31 25h-61q-45 0 -72.5 18t-27.5 57v400q0 36 20 63l145 196l96 198q13 28 37.5 48t51.5 20z M650 1100l-100 -212l-150 -213v-375h100l136 -100h214l250 375v125h-450l50 225v175h-50zM50 800h100q21 0 35.5 -14.5t14.5 -35.5v-500q0 -21 -14.5 -35.5t-35.5 -14.5h-100q-21 0 -35.5 14.5t-14.5 35.5v500q0 21 14.5 35.5t35.5 14.5z" />
|
|
||||||
<glyph unicode="" d="M600 1100h250q23 0 45 -16.5t38 -40.5l238 -344q29 -29 29 -74v-100q0 -44 -30 -84.5t-70 -40.5h-328q28 -118 28 -125v-150q0 -44 -30 -84.5t-70 -40.5h-50q-27 0 -51.5 20t-37.5 48l-96 198l-145 196q-20 27 -20 63v400q0 39 27.5 57t72.5 18h61q124 100 139 100z M50 1000h100q21 0 35.5 -14.5t14.5 -35.5v-500q0 -21 -14.5 -35.5t-35.5 -14.5h-100q-21 0 -35.5 14.5t-14.5 35.5v500q0 21 14.5 35.5t35.5 14.5zM636 1000l-136 -100h-100v-375l150 -213l100 -212h50v175l-50 225h450v125l-250 375h-214z" />
|
|
||||||
<glyph unicode="" d="M356 873l363 230q31 16 53 -6l110 -112q13 -13 13.5 -32t-11.5 -34l-84 -121h302q84 0 138 -38t54 -110t-55 -111t-139 -39h-106l-131 -339q-6 -21 -19.5 -41t-28.5 -20h-342q-7 0 -90 81t-83 94v525q0 17 14 35.5t28 28.5zM400 792v-503l100 -89h293l131 339 q6 21 19.5 41t28.5 20h203q21 0 30.5 25t0.5 50t-31 25h-456h-7h-6h-5.5t-6 0.5t-5 1.5t-5 2t-4 2.5t-4 4t-2.5 4.5q-12 25 5 47l146 183l-86 83zM50 800h100q21 0 35.5 -14.5t14.5 -35.5v-500q0 -21 -14.5 -35.5t-35.5 -14.5h-100q-21 0 -35.5 14.5t-14.5 35.5v500 q0 21 14.5 35.5t35.5 14.5z" />
|
|
||||||
<glyph unicode="" d="M475 1103l366 -230q2 -1 6 -3.5t14 -10.5t18 -16.5t14.5 -20t6.5 -22.5v-525q0 -13 -86 -94t-93 -81h-342q-15 0 -28.5 20t-19.5 41l-131 339h-106q-85 0 -139.5 39t-54.5 111t54 110t138 38h302l-85 121q-11 15 -10.5 34t13.5 32l110 112q22 22 53 6zM370 945l146 -183 q17 -22 5 -47q-2 -2 -3.5 -4.5t-4 -4t-4 -2.5t-5 -2t-5 -1.5t-6 -0.5h-6h-6.5h-6h-475v-100h221q15 0 29 -20t20 -41l130 -339h294l106 89v503l-342 236zM1050 800h100q21 0 35.5 -14.5t14.5 -35.5v-500q0 -21 -14.5 -35.5t-35.5 -14.5h-100q-21 0 -35.5 14.5t-14.5 35.5 v500q0 21 14.5 35.5t35.5 14.5z" />
|
|
||||||
<glyph unicode="" d="M550 1294q72 0 111 -55t39 -139v-106l339 -131q21 -6 41 -19.5t20 -28.5v-342q0 -7 -81 -90t-94 -83h-525q-17 0 -35.5 14t-28.5 28l-9 14l-230 363q-16 31 6 53l112 110q13 13 32 13.5t34 -11.5l121 -84v302q0 84 38 138t110 54zM600 972v203q0 21 -25 30.5t-50 0.5 t-25 -31v-456v-7v-6v-5.5t-0.5 -6t-1.5 -5t-2 -5t-2.5 -4t-4 -4t-4.5 -2.5q-25 -12 -47 5l-183 146l-83 -86l236 -339h503l89 100v293l-339 131q-21 6 -41 19.5t-20 28.5zM450 200h500q21 0 35.5 -14.5t14.5 -35.5v-100q0 -21 -14.5 -35.5t-35.5 -14.5h-500 q-21 0 -35.5 14.5t-14.5 35.5v100q0 21 14.5 35.5t35.5 14.5z" />
|
|
||||||
<glyph unicode="" d="M350 1100h500q21 0 35.5 14.5t14.5 35.5v100q0 21 -14.5 35.5t-35.5 14.5h-500q-21 0 -35.5 -14.5t-14.5 -35.5v-100q0 -21 14.5 -35.5t35.5 -14.5zM600 306v-106q0 -84 -39 -139t-111 -55t-110 54t-38 138v302l-121 -84q-15 -12 -34 -11.5t-32 13.5l-112 110 q-22 22 -6 53l230 363q1 2 3.5 6t10.5 13.5t16.5 17t20 13.5t22.5 6h525q13 0 94 -83t81 -90v-342q0 -15 -20 -28.5t-41 -19.5zM308 900l-236 -339l83 -86l183 146q22 17 47 5q2 -1 4.5 -2.5t4 -4t2.5 -4t2 -5t1.5 -5t0.5 -6v-5.5v-6v-7v-456q0 -22 25 -31t50 0.5t25 30.5 v203q0 15 20 28.5t41 19.5l339 131v293l-89 100h-503z" />
|
|
||||||
<glyph unicode="" d="M600 1178q118 0 225 -45.5t184.5 -123t123 -184.5t45.5 -225t-45.5 -225t-123 -184.5t-184.5 -123t-225 -45.5t-225 45.5t-184.5 123t-123 184.5t-45.5 225t45.5 225t123 184.5t184.5 123t225 45.5zM914 632l-275 223q-16 13 -27.5 8t-11.5 -26v-137h-275 q-10 0 -17.5 -7.5t-7.5 -17.5v-150q0 -10 7.5 -17.5t17.5 -7.5h275v-137q0 -21 11.5 -26t27.5 8l275 223q16 13 16 32t-16 32z" />
|
|
||||||
<glyph unicode="" d="M600 1178q118 0 225 -45.5t184.5 -123t123 -184.5t45.5 -225t-45.5 -225t-123 -184.5t-184.5 -123t-225 -45.5t-225 45.5t-184.5 123t-123 184.5t-45.5 225t45.5 225t123 184.5t184.5 123t225 45.5zM561 855l-275 -223q-16 -13 -16 -32t16 -32l275 -223q16 -13 27.5 -8 t11.5 26v137h275q10 0 17.5 7.5t7.5 17.5v150q0 10 -7.5 17.5t-17.5 7.5h-275v137q0 21 -11.5 26t-27.5 -8z" />
|
|
||||||
<glyph unicode="" d="M600 1178q118 0 225 -45.5t184.5 -123t123 -184.5t45.5 -225t-45.5 -225t-123 -184.5t-184.5 -123t-225 -45.5t-225 45.5t-184.5 123t-123 184.5t-45.5 225t45.5 225t123 184.5t184.5 123t225 45.5zM855 639l-223 275q-13 16 -32 16t-32 -16l-223 -275q-13 -16 -8 -27.5 t26 -11.5h137v-275q0 -10 7.5 -17.5t17.5 -7.5h150q10 0 17.5 7.5t7.5 17.5v275h137q21 0 26 11.5t-8 27.5z" />
|
|
||||||
<glyph unicode="" d="M600 1178q118 0 225 -45.5t184.5 -123t123 -184.5t45.5 -225t-45.5 -225t-123 -184.5t-184.5 -123t-225 -45.5t-225 45.5t-184.5 123t-123 184.5t-45.5 225t45.5 225t123 184.5t184.5 123t225 45.5zM675 900h-150q-10 0 -17.5 -7.5t-7.5 -17.5v-275h-137q-21 0 -26 -11.5 t8 -27.5l223 -275q13 -16 32 -16t32 16l223 275q13 16 8 27.5t-26 11.5h-137v275q0 10 -7.5 17.5t-17.5 7.5z" />
|
|
||||||
<glyph unicode="" d="M600 1176q116 0 222.5 -46t184 -123.5t123.5 -184t46 -222.5t-46 -222.5t-123.5 -184t-184 -123.5t-222.5 -46t-222.5 46t-184 123.5t-123.5 184t-46 222.5t46 222.5t123.5 184t184 123.5t222.5 46zM627 1101q-15 -12 -36.5 -20.5t-35.5 -12t-43 -8t-39 -6.5 q-15 -3 -45.5 0t-45.5 -2q-20 -7 -51.5 -26.5t-34.5 -34.5q-3 -11 6.5 -22.5t8.5 -18.5q-3 -34 -27.5 -91t-29.5 -79q-9 -34 5 -93t8 -87q0 -9 17 -44.5t16 -59.5q12 0 23 -5t23.5 -15t19.5 -14q16 -8 33 -15t40.5 -15t34.5 -12q21 -9 52.5 -32t60 -38t57.5 -11 q7 -15 -3 -34t-22.5 -40t-9.5 -38q13 -21 23 -34.5t27.5 -27.5t36.5 -18q0 -7 -3.5 -16t-3.5 -14t5 -17q104 -2 221 112q30 29 46.5 47t34.5 49t21 63q-13 8 -37 8.5t-36 7.5q-15 7 -49.5 15t-51.5 19q-18 0 -41 -0.5t-43 -1.5t-42 -6.5t-38 -16.5q-51 -35 -66 -12 q-4 1 -3.5 25.5t0.5 25.5q-6 13 -26.5 17.5t-24.5 6.5q1 15 -0.5 30.5t-7 28t-18.5 11.5t-31 -21q-23 -25 -42 4q-19 28 -8 58q6 16 22 22q6 -1 26 -1.5t33.5 -4t19.5 -13.5q7 -12 18 -24t21.5 -20.5t20 -15t15.5 -10.5l5 -3q2 12 7.5 30.5t8 34.5t-0.5 32q-3 18 3.5 29 t18 22.5t15.5 24.5q6 14 10.5 35t8 31t15.5 22.5t34 22.5q-6 18 10 36q8 0 24 -1.5t24.5 -1.5t20 4.5t20.5 15.5q-10 23 -31 42.5t-37.5 29.5t-49 27t-43.5 23q0 1 2 8t3 11.5t1.5 10.5t-1 9.5t-4.5 4.5q31 -13 58.5 -14.5t38.5 2.5l12 5q5 28 -9.5 46t-36.5 24t-50 15 t-41 20q-18 -4 -37 0zM613 994q0 -17 8 -42t17 -45t9 -23q-8 1 -39.5 5.5t-52.5 10t-37 16.5q3 11 16 29.5t16 25.5q10 -10 19 -10t14 6t13.5 14.5t16.5 12.5z" />
|
|
||||||
<glyph unicode="" d="M756 1157q164 92 306 -9l-259 -138l145 -232l251 126q6 -89 -34 -156.5t-117 -110.5q-60 -34 -127 -39.5t-126 16.5l-596 -596q-15 -16 -36.5 -16t-36.5 16l-111 110q-15 15 -15 36.5t15 37.5l600 599q-34 101 5.5 201.5t135.5 154.5z" />
|
|
||||||
<glyph unicode="" horiz-adv-x="1220" d="M100 1196h1000q41 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.5v100q0 41 29.5 70.5t70.5 29.5zM1100 1096h-200v-100h200v100zM100 796h1000q41 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.5v100q0 41 29.5 70.5t70.5 29.5zM1100 696h-500v-100h500v100zM100 396h1000q41 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.5v100q0 41 29.5 70.5t70.5 29.5zM1100 296h-300v-100h300v100z " />
|
|
||||||
<glyph unicode="" d="M150 1200h900q21 0 35.5 -14.5t14.5 -35.5t-14.5 -35.5t-35.5 -14.5h-900q-21 0 -35.5 14.5t-14.5 35.5t14.5 35.5t35.5 14.5zM700 500v-300l-200 -200v500l-350 500h900z" />
|
|
||||||
<glyph unicode="" d="M500 1200h200q41 0 70.5 -29.5t29.5 -70.5v-100h300q41 0 70.5 -29.5t29.5 -70.5v-400h-500v100h-200v-100h-500v400q0 41 29.5 70.5t70.5 29.5h300v100q0 41 29.5 70.5t70.5 29.5zM500 1100v-100h200v100h-200zM1200 400v-200q0 -41 -29.5 -70.5t-70.5 -29.5h-1000 q-41 0 -70.5 29.5t-29.5 70.5v200h1200z" />
|
|
||||||
<glyph unicode="" d="M50 1200h300q21 0 25 -10.5t-10 -24.5l-94 -94l199 -199q7 -8 7 -18t-7 -18l-106 -106q-8 -7 -18 -7t-18 7l-199 199l-94 -94q-14 -14 -24.5 -10t-10.5 25v300q0 21 14.5 35.5t35.5 14.5zM850 1200h300q21 0 35.5 -14.5t14.5 -35.5v-300q0 -21 -10.5 -25t-24.5 10l-94 94 l-199 -199q-8 -7 -18 -7t-18 7l-106 106q-7 8 -7 18t7 18l199 199l-94 94q-14 14 -10 24.5t25 10.5zM364 470l106 -106q7 -8 7 -18t-7 -18l-199 -199l94 -94q14 -14 10 -24.5t-25 -10.5h-300q-21 0 -35.5 14.5t-14.5 35.5v300q0 21 10.5 25t24.5 -10l94 -94l199 199 q8 7 18 7t18 -7zM1071 271l94 94q14 14 24.5 10t10.5 -25v-300q0 -21 -14.5 -35.5t-35.5 -14.5h-300q-21 0 -25 10.5t10 24.5l94 94l-199 199q-7 8 -7 18t7 18l106 106q8 7 18 7t18 -7z" />
|
|
||||||
<glyph unicode="" d="M596 1192q121 0 231.5 -47.5t190 -127t127 -190t47.5 -231.5t-47.5 -231.5t-127 -190.5t-190 -127t-231.5 -47t-231.5 47t-190.5 127t-127 190.5t-47 231.5t47 231.5t127 190t190.5 127t231.5 47.5zM596 1010q-112 0 -207.5 -55.5t-151 -151t-55.5 -207.5t55.5 -207.5 t151 -151t207.5 -55.5t207.5 55.5t151 151t55.5 207.5t-55.5 207.5t-151 151t-207.5 55.5zM454.5 905q22.5 0 38.5 -16t16 -38.5t-16 -39t-38.5 -16.5t-38.5 16.5t-16 39t16 38.5t38.5 16zM754.5 905q22.5 0 38.5 -16t16 -38.5t-16 -39t-38 -16.5q-14 0 -29 10l-55 -145 q17 -23 17 -51q0 -36 -25.5 -61.5t-61.5 -25.5t-61.5 25.5t-25.5 61.5q0 32 20.5 56.5t51.5 29.5l122 126l1 1q-9 14 -9 28q0 23 16 39t38.5 16zM345.5 709q22.5 0 38.5 -16t16 -38.5t-16 -38.5t-38.5 -16t-38.5 16t-16 38.5t16 38.5t38.5 16zM854.5 709q22.5 0 38.5 -16 t16 -38.5t-16 -38.5t-38.5 -16t-38.5 16t-16 38.5t16 38.5t38.5 16z" />
|
|
||||||
<glyph unicode="" d="M546 173l469 470q91 91 99 192q7 98 -52 175.5t-154 94.5q-22 4 -47 4q-34 0 -66.5 -10t-56.5 -23t-55.5 -38t-48 -41.5t-48.5 -47.5q-376 -375 -391 -390q-30 -27 -45 -41.5t-37.5 -41t-32 -46.5t-16 -47.5t-1.5 -56.5q9 -62 53.5 -95t99.5 -33q74 0 125 51l548 548 q36 36 20 75q-7 16 -21.5 26t-32.5 10q-26 0 -50 -23q-13 -12 -39 -38l-341 -338q-15 -15 -35.5 -15.5t-34.5 13.5t-14 34.5t14 34.5q327 333 361 367q35 35 67.5 51.5t78.5 16.5q14 0 29 -1q44 -8 74.5 -35.5t43.5 -68.5q14 -47 2 -96.5t-47 -84.5q-12 -11 -32 -32 t-79.5 -81t-114.5 -115t-124.5 -123.5t-123 -119.5t-96.5 -89t-57 -45q-56 -27 -120 -27q-70 0 -129 32t-93 89q-48 78 -35 173t81 163l511 511q71 72 111 96q91 55 198 55q80 0 152 -33q78 -36 129.5 -103t66.5 -154q17 -93 -11 -183.5t-94 -156.5l-482 -476 q-15 -15 -36 -16t-37 14t-17.5 34t14.5 35z" />
|
|
||||||
<glyph unicode="" d="M649 949q48 68 109.5 104t121.5 38.5t118.5 -20t102.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-150 152.5t-126.5 127.5t-93.5 124.5t-33.5 117.5q0 64 28 123t73 100.5t104 64t119 20 t120.5 -38.5t104.5 -104zM896 972q-33 0 -64.5 -19t-56.5 -46t-47.5 -53.5t-43.5 -45.5t-37.5 -19t-36 19t-40 45.5t-43 53.5t-54 46t-65.5 19q-67 0 -122.5 -55.5t-55.5 -132.5q0 -23 13.5 -51t46 -65t57.5 -63t76 -75l22 -22q15 -14 44 -44t50.5 -51t46 -44t41 -35t23 -12 t23.5 12t42.5 36t46 44t52.5 52t44 43q4 4 12 13q43 41 63.5 62t52 55t46 55t26 46t11.5 44q0 79 -53 133.5t-120 54.5z" />
|
|
||||||
<glyph unicode="" d="M776.5 1214q93.5 0 159.5 -66l141 -141q66 -66 66 -160q0 -42 -28 -95.5t-62 -87.5l-29 -29q-31 53 -77 99l-18 18l95 95l-247 248l-389 -389l212 -212l-105 -106l-19 18l-141 141q-66 66 -66 159t66 159l283 283q65 66 158.5 66zM600 706l105 105q10 -8 19 -17l141 -141 q66 -66 66 -159t-66 -159l-283 -283q-66 -66 -159 -66t-159 66l-141 141q-66 66 -66 159.5t66 159.5l55 55q29 -55 75 -102l18 -17l-95 -95l247 -248l389 389z" />
|
|
||||||
<glyph unicode="" d="M603 1200q85 0 162 -15t127 -38t79 -48t29 -46v-953q0 -41 -29.5 -70.5t-70.5 -29.5h-600q-41 0 -70.5 29.5t-29.5 70.5v953q0 21 30 46.5t81 48t129 37.5t163 15zM300 1000v-700h600v700h-600zM600 254q-43 0 -73.5 -30.5t-30.5 -73.5t30.5 -73.5t73.5 -30.5t73.5 30.5 t30.5 73.5t-30.5 73.5t-73.5 30.5z" />
|
|
||||||
<glyph unicode="" d="M902 1185l283 -282q15 -15 15 -36t-14.5 -35.5t-35.5 -14.5t-35 15l-36 35l-279 -267v-300l-212 210l-308 -307l-280 -203l203 280l307 308l-210 212h300l267 279l-35 36q-15 14 -15 35t14.5 35.5t35.5 14.5t35 -15z" />
|
|
||||||
<glyph unicode="" d="M700 1248v-78q38 -5 72.5 -14.5t75.5 -31.5t71 -53.5t52 -84t24 -118.5h-159q-4 36 -10.5 59t-21 45t-40 35.5t-64.5 20.5v-307l64 -13q34 -7 64 -16.5t70 -32t67.5 -52.5t47.5 -80t20 -112q0 -139 -89 -224t-244 -97v-77h-100v79q-150 16 -237 103q-40 40 -52.5 93.5 t-15.5 139.5h139q5 -77 48.5 -126t117.5 -65v335l-27 8q-46 14 -79 26.5t-72 36t-63 52t-40 72.5t-16 98q0 70 25 126t67.5 92t94.5 57t110 27v77h100zM600 754v274q-29 -4 -50 -11t-42 -21.5t-31.5 -41.5t-10.5 -65q0 -29 7 -50.5t16.5 -34t28.5 -22.5t31.5 -14t37.5 -10 q9 -3 13 -4zM700 547v-310q22 2 42.5 6.5t45 15.5t41.5 27t29 42t12 59.5t-12.5 59.5t-38 44.5t-53 31t-66.5 24.5z" />
|
|
||||||
<glyph unicode="" d="M561 1197q84 0 160.5 -40t123.5 -109.5t47 -147.5h-153q0 40 -19.5 71.5t-49.5 48.5t-59.5 26t-55.5 9q-37 0 -79 -14.5t-62 -35.5q-41 -44 -41 -101q0 -26 13.5 -63t26.5 -61t37 -66q6 -9 9 -14h241v-100h-197q8 -50 -2.5 -115t-31.5 -95q-45 -62 -99 -112 q34 10 83 17.5t71 7.5q32 1 102 -16t104 -17q83 0 136 30l50 -147q-31 -19 -58 -30.5t-55 -15.5t-42 -4.5t-46 -0.5q-23 0 -76 17t-111 32.5t-96 11.5q-39 -3 -82 -16t-67 -25l-23 -11l-55 145q4 3 16 11t15.5 10.5t13 9t15.5 12t14.5 14t17.5 18.5q48 55 54 126.5 t-30 142.5h-221v100h166q-23 47 -44 104q-7 20 -12 41.5t-6 55.5t6 66.5t29.5 70.5t58.5 71q97 88 263 88z" />
|
|
||||||
<glyph unicode="" d="M400 300h150q21 0 25 -11t-10 -25l-230 -250q-14 -15 -35 -15t-35 15l-230 250q-14 14 -10 25t25 11h150v900h200v-900zM935 1184l230 -249q14 -14 10 -24.5t-25 -10.5h-150v-900h-200v900h-150q-21 0 -25 10.5t10 24.5l230 249q14 15 35 15t35 -15z" />
|
|
||||||
<glyph unicode="" d="M1000 700h-100v100h-100v-100h-100v500h300v-500zM400 300h150q21 0 25 -11t-10 -25l-230 -250q-14 -15 -35 -15t-35 15l-230 250q-14 14 -10 25t25 11h150v900h200v-900zM801 1100v-200h100v200h-100zM1000 350l-200 -250h200v-100h-300v150l200 250h-200v100h300v-150z " />
|
|
||||||
<glyph unicode="" d="M400 300h150q21 0 25 -11t-10 -25l-230 -250q-14 -15 -35 -15t-35 15l-230 250q-14 14 -10 25t25 11h150v900h200v-900zM1000 1050l-200 -250h200v-100h-300v150l200 250h-200v100h300v-150zM1000 0h-100v100h-100v-100h-100v500h300v-500zM801 400v-200h100v200h-100z " />
|
|
||||||
<glyph unicode="" d="M400 300h150q21 0 25 -11t-10 -25l-230 -250q-14 -15 -35 -15t-35 15l-230 250q-14 14 -10 25t25 11h150v900h200v-900zM1000 700h-100v400h-100v100h200v-500zM1100 0h-100v100h-200v400h300v-500zM901 400v-200h100v200h-100z" />
|
|
||||||
<glyph unicode="" d="M400 300h150q21 0 25 -11t-10 -25l-230 -250q-14 -15 -35 -15t-35 15l-230 250q-14 14 -10 25t25 11h150v900h200v-900zM1100 700h-100v100h-200v400h300v-500zM901 1100v-200h100v200h-100zM1000 0h-100v400h-100v100h200v-500z" />
|
|
||||||
<glyph unicode="" d="M400 300h150q21 0 25 -11t-10 -25l-230 -250q-14 -15 -35 -15t-35 15l-230 250q-14 14 -10 25t25 11h150v900h200v-900zM900 1000h-200v200h200v-200zM1000 700h-300v200h300v-200zM1100 400h-400v200h400v-200zM1200 100h-500v200h500v-200z" />
|
|
||||||
<glyph unicode="" d="M400 300h150q21 0 25 -11t-10 -25l-230 -250q-14 -15 -35 -15t-35 15l-230 250q-14 14 -10 25t25 11h150v900h200v-900zM1200 1000h-500v200h500v-200zM1100 700h-400v200h400v-200zM1000 400h-300v200h300v-200zM900 100h-200v200h200v-200z" />
|
|
||||||
<glyph unicode="" d="M350 1100h400q162 0 256 -93.5t94 -256.5v-400q0 -165 -93.5 -257.5t-256.5 -92.5h-400q-165 0 -257.5 92.5t-92.5 257.5v400q0 165 92.5 257.5t257.5 92.5zM800 900h-500q-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.5 v500q0 41 -29.5 70.5t-70.5 29.5z" />
|
|
||||||
<glyph unicode="" d="M350 1100h400q165 0 257.5 -92.5t92.5 -257.5v-400q0 -165 -92.5 -257.5t-257.5 -92.5h-400q-163 0 -256.5 92.5t-93.5 257.5v400q0 163 94 256.5t256 93.5zM800 900h-500q-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.5 v500q0 41 -29.5 70.5t-70.5 29.5zM440 770l253 -190q17 -12 17 -30t-17 -30l-253 -190q-16 -12 -28 -6.5t-12 26.5v400q0 21 12 26.5t28 -6.5z" />
|
|
||||||
<glyph unicode="" d="M350 1100h400q163 0 256.5 -94t93.5 -256v-400q0 -165 -92.5 -257.5t-257.5 -92.5h-400q-165 0 -257.5 92.5t-92.5 257.5v400q0 163 92.5 256.5t257.5 93.5zM800 900h-500q-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.5 v500q0 41 -29.5 70.5t-70.5 29.5zM350 700h400q21 0 26.5 -12t-6.5 -28l-190 -253q-12 -17 -30 -17t-30 17l-190 253q-12 16 -6.5 28t26.5 12z" />
|
|
||||||
<glyph unicode="" d="M350 1100h400q165 0 257.5 -92.5t92.5 -257.5v-400q0 -163 -92.5 -256.5t-257.5 -93.5h-400q-163 0 -256.5 94t-93.5 256v400q0 165 92.5 257.5t257.5 92.5zM800 900h-500q-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.5 v500q0 41 -29.5 70.5t-70.5 29.5zM580 693l190 -253q12 -16 6.5 -28t-26.5 -12h-400q-21 0 -26.5 12t6.5 28l190 253q12 17 30 17t30 -17z" />
|
|
||||||
<glyph unicode="" d="M550 1100h400q165 0 257.5 -92.5t92.5 -257.5v-400q0 -165 -92.5 -257.5t-257.5 -92.5h-400q-21 0 -35.5 14.5t-14.5 35.5v100q0 21 14.5 35.5t35.5 14.5h450q41 0 70.5 29.5t29.5 70.5v500q0 41 -29.5 70.5t-70.5 29.5h-450q-21 0 -35.5 14.5t-14.5 35.5v100 q0 21 14.5 35.5t35.5 14.5zM338 867l324 -284q16 -14 16 -33t-16 -33l-324 -284q-16 -14 -27 -9t-11 26v150h-250q-21 0 -35.5 14.5t-14.5 35.5v200q0 21 14.5 35.5t35.5 14.5h250v150q0 21 11 26t27 -9z" />
|
|
||||||
<glyph unicode="" d="M793 1182l9 -9q8 -10 5 -27q-3 -11 -79 -225.5t-78 -221.5l300 1q24 0 32.5 -17.5t-5.5 -35.5q-1 0 -133.5 -155t-267 -312.5t-138.5 -162.5q-12 -15 -26 -15h-9l-9 8q-9 11 -4 32q2 9 42 123.5t79 224.5l39 110h-302q-23 0 -31 19q-10 21 6 41q75 86 209.5 237.5 t228 257t98.5 111.5q9 16 25 16h9z" />
|
|
||||||
<glyph unicode="" d="M350 1100h400q21 0 35.5 -14.5t14.5 -35.5v-100q0 -21 -14.5 -35.5t-35.5 -14.5h-450q-41 0 -70.5 -29.5t-29.5 -70.5v-500q0 -41 29.5 -70.5t70.5 -29.5h450q21 0 35.5 -14.5t14.5 -35.5v-100q0 -21 -14.5 -35.5t-35.5 -14.5h-400q-165 0 -257.5 92.5t-92.5 257.5v400 q0 165 92.5 257.5t257.5 92.5zM938 867l324 -284q16 -14 16 -33t-16 -33l-324 -284q-16 -14 -27 -9t-11 26v150h-250q-21 0 -35.5 14.5t-14.5 35.5v200q0 21 14.5 35.5t35.5 14.5h250v150q0 21 11 26t27 -9z" />
|
|
||||||
<glyph unicode="" d="M750 1200h400q21 0 35.5 -14.5t14.5 -35.5v-400q0 -21 -10.5 -25t-24.5 10l-109 109l-312 -312q-15 -15 -35.5 -15t-35.5 15l-141 141q-15 15 -15 35.5t15 35.5l312 312l-109 109q-14 14 -10 24.5t25 10.5zM456 900h-156q-41 0 -70.5 -29.5t-29.5 -70.5v-500 q0 -41 29.5 -70.5t70.5 -29.5h500q41 0 70.5 29.5t29.5 70.5v148l200 200v-298q0 -165 -93.5 -257.5t-256.5 -92.5h-400q-165 0 -257.5 92.5t-92.5 257.5v400q0 165 92.5 257.5t257.5 92.5h300z" />
|
|
||||||
<glyph unicode="" d="M600 1186q119 0 227.5 -46.5t187 -125t125 -187t46.5 -227.5t-46.5 -227.5t-125 -187t-187 -125t-227.5 -46.5t-227.5 46.5t-187 125t-125 187t-46.5 227.5t46.5 227.5t125 187t187 125t227.5 46.5zM600 1022q-115 0 -212 -56.5t-153.5 -153.5t-56.5 -212t56.5 -212 t153.5 -153.5t212 -56.5t212 56.5t153.5 153.5t56.5 212t-56.5 212t-153.5 153.5t-212 56.5zM600 794q80 0 137 -57t57 -137t-57 -137t-137 -57t-137 57t-57 137t57 137t137 57z" />
|
|
||||||
<glyph unicode="" d="M450 1200h200q21 0 35.5 -14.5t14.5 -35.5v-350h245q20 0 25 -11t-9 -26l-383 -426q-14 -15 -33.5 -15t-32.5 15l-379 426q-13 15 -8.5 26t25.5 11h250v350q0 21 14.5 35.5t35.5 14.5zM50 300h1000q21 0 35.5 -14.5t14.5 -35.5v-250h-1100v250q0 21 14.5 35.5t35.5 14.5z M900 200v-50h100v50h-100z" />
|
|
||||||
<glyph unicode="" d="M583 1182l378 -435q14 -15 9 -31t-26 -16h-244v-250q0 -20 -17 -35t-39 -15h-200q-20 0 -32 14.5t-12 35.5v250h-250q-20 0 -25.5 16.5t8.5 31.5l383 431q14 16 33.5 17t33.5 -14zM50 300h1000q21 0 35.5 -14.5t14.5 -35.5v-250h-1100v250q0 21 14.5 35.5t35.5 14.5z M900 200v-50h100v50h-100z" />
|
|
||||||
<glyph unicode="" d="M396 723l369 369q7 7 17.5 7t17.5 -7l139 -139q7 -8 7 -18.5t-7 -17.5l-525 -525q-7 -8 -17.5 -8t-17.5 8l-292 291q-7 8 -7 18t7 18l139 139q8 7 18.5 7t17.5 -7zM50 300h1000q21 0 35.5 -14.5t14.5 -35.5v-250h-1100v250q0 21 14.5 35.5t35.5 14.5zM900 200v-50h100v50 h-100z" />
|
|
||||||
<glyph unicode="" d="M135 1023l142 142q14 14 35 14t35 -14l77 -77l-212 -212l-77 76q-14 15 -14 36t14 35zM655 855l210 210q14 14 24.5 10t10.5 -25l-2 -599q-1 -20 -15.5 -35t-35.5 -15l-597 -1q-21 0 -25 10.5t10 24.5l208 208l-154 155l212 212zM50 300h1000q21 0 35.5 -14.5t14.5 -35.5 v-250h-1100v250q0 21 14.5 35.5t35.5 14.5zM900 200v-50h100v50h-100z" />
|
|
||||||
<glyph unicode="" d="M350 1200l599 -2q20 -1 35 -15.5t15 -35.5l1 -597q0 -21 -10.5 -25t-24.5 10l-208 208l-155 -154l-212 212l155 154l-210 210q-14 14 -10 24.5t25 10.5zM524 512l-76 -77q-15 -14 -36 -14t-35 14l-142 142q-14 14 -14 35t14 35l77 77zM50 300h1000q21 0 35.5 -14.5 t14.5 -35.5v-250h-1100v250q0 21 14.5 35.5t35.5 14.5zM900 200v-50h100v50h-100z" />
|
|
||||||
<glyph unicode="" d="M1200 103l-483 276l-314 -399v423h-399l1196 796v-1096zM483 424v-230l683 953z" />
|
|
||||||
<glyph unicode="" d="M1100 1000v-850q0 -21 -14.5 -35.5t-35.5 -14.5h-150v400h-700v-400h-150q-21 0 -35.5 14.5t-14.5 35.5v1000q0 20 14.5 35t35.5 15h250v-300h500v300h100zM700 1000h-100v200h100v-200z" />
|
|
||||||
<glyph unicode="" d="M1100 1000l-2 -149l-299 -299l-95 95q-9 9 -21.5 9t-21.5 -9l-149 -147h-312v-400h-150q-21 0 -35.5 14.5t-14.5 35.5v1000q0 20 14.5 35t35.5 15h250v-300h500v300h100zM700 1000h-100v200h100v-200zM1132 638l106 -106q7 -7 7 -17.5t-7 -17.5l-420 -421q-8 -7 -18 -7 t-18 7l-202 203q-8 7 -8 17.5t8 17.5l106 106q7 8 17.5 8t17.5 -8l79 -79l297 297q7 7 17.5 7t17.5 -7z" />
|
|
||||||
<glyph unicode="" d="M1100 1000v-269l-103 -103l-134 134q-15 15 -33.5 16.5t-34.5 -12.5l-266 -266h-329v-400h-150q-21 0 -35.5 14.5t-14.5 35.5v1000q0 20 14.5 35t35.5 15h250v-300h500v300h100zM700 1000h-100v200h100v-200zM1202 572l70 -70q15 -15 15 -35.5t-15 -35.5l-131 -131 l131 -131q15 -15 15 -35.5t-15 -35.5l-70 -70q-15 -15 -35.5 -15t-35.5 15l-131 131l-131 -131q-15 -15 -35.5 -15t-35.5 15l-70 70q-15 15 -15 35.5t15 35.5l131 131l-131 131q-15 15 -15 35.5t15 35.5l70 70q15 15 35.5 15t35.5 -15l131 -131l131 131q15 15 35.5 15 t35.5 -15z" />
|
|
||||||
<glyph unicode="" d="M1100 1000v-300h-350q-21 0 -35.5 -14.5t-14.5 -35.5v-150h-500v-400h-150q-21 0 -35.5 14.5t-14.5 35.5v1000q0 20 14.5 35t35.5 15h250v-300h500v300h100zM700 1000h-100v200h100v-200zM850 600h100q21 0 35.5 -14.5t14.5 -35.5v-250h150q21 0 25 -10.5t-10 -24.5 l-230 -230q-14 -14 -35 -14t-35 14l-230 230q-14 14 -10 24.5t25 10.5h150v250q0 21 14.5 35.5t35.5 14.5z" />
|
|
||||||
<glyph unicode="" d="M1100 1000v-400l-165 165q-14 15 -35 15t-35 -15l-263 -265h-402v-400h-150q-21 0 -35.5 14.5t-14.5 35.5v1000q0 20 14.5 35t35.5 15h250v-300h500v300h100zM700 1000h-100v200h100v-200zM935 565l230 -229q14 -15 10 -25.5t-25 -10.5h-150v-250q0 -20 -14.5 -35 t-35.5 -15h-100q-21 0 -35.5 15t-14.5 35v250h-150q-21 0 -25 10.5t10 25.5l230 229q14 15 35 15t35 -15z" />
|
|
||||||
<glyph unicode="" d="M50 1100h1100q21 0 35.5 -14.5t14.5 -35.5v-150h-1200v150q0 21 14.5 35.5t35.5 14.5zM1200 800v-550q0 -21 -14.5 -35.5t-35.5 -14.5h-1100q-21 0 -35.5 14.5t-14.5 35.5v550h1200zM100 500v-200h400v200h-400z" />
|
|
||||||
<glyph unicode="" d="M935 1165l248 -230q14 -14 14 -35t-14 -35l-248 -230q-14 -14 -24.5 -10t-10.5 25v150h-400v200h400v150q0 21 10.5 25t24.5 -10zM200 800h-50q-21 0 -35.5 14.5t-14.5 35.5v100q0 21 14.5 35.5t35.5 14.5h50v-200zM400 800h-100v200h100v-200zM18 435l247 230 q14 14 24.5 10t10.5 -25v-150h400v-200h-400v-150q0 -21 -10.5 -25t-24.5 10l-247 230q-15 14 -15 35t15 35zM900 300h-100v200h100v-200zM1000 500h51q20 0 34.5 -14.5t14.5 -35.5v-100q0 -21 -14.5 -35.5t-34.5 -14.5h-51v200z" />
|
|
||||||
<glyph unicode="" d="M862 1073l276 116q25 18 43.5 8t18.5 -41v-1106q0 -21 -14.5 -35.5t-35.5 -14.5h-200q-21 0 -35.5 14.5t-14.5 35.5v397q-4 1 -11 5t-24 17.5t-30 29t-24 42t-11 56.5v359q0 31 18.5 65t43.5 52zM550 1200q22 0 34.5 -12.5t14.5 -24.5l1 -13v-450q0 -28 -10.5 -59.5 t-25 -56t-29 -45t-25.5 -31.5l-10 -11v-447q0 -21 -14.5 -35.5t-35.5 -14.5h-200q-21 0 -35.5 14.5t-14.5 35.5v447q-4 4 -11 11.5t-24 30.5t-30 46t-24 55t-11 60v450q0 2 0.5 5.5t4 12t8.5 15t14.5 12t22.5 5.5q20 0 32.5 -12.5t14.5 -24.5l3 -13v-350h100v350v5.5t2.5 12 t7 15t15 12t25.5 5.5q23 0 35.5 -12.5t13.5 -24.5l1 -13v-350h100v350q0 2 0.5 5.5t3 12t7 15t15 12t24.5 5.5z" />
|
|
||||||
<glyph unicode="" d="M1200 1100v-56q-4 0 -11 -0.5t-24 -3t-30 -7.5t-24 -15t-11 -24v-888q0 -22 25 -34.5t50 -13.5l25 -2v-56h-400v56q75 0 87.5 6.5t12.5 43.5v394h-500v-394q0 -37 12.5 -43.5t87.5 -6.5v-56h-400v56q4 0 11 0.5t24 3t30 7.5t24 15t11 24v888q0 22 -25 34.5t-50 13.5 l-25 2v56h400v-56q-75 0 -87.5 -6.5t-12.5 -43.5v-394h500v394q0 37 -12.5 43.5t-87.5 6.5v56h400z" />
|
|
||||||
<glyph unicode="" d="M675 1000h375q21 0 35.5 -14.5t14.5 -35.5v-150h-105l-295 -98v98l-200 200h-400l100 100h375zM100 900h300q41 0 70.5 -29.5t29.5 -70.5v-500q0 -41 -29.5 -70.5t-70.5 -29.5h-300q-41 0 -70.5 29.5t-29.5 70.5v500q0 41 29.5 70.5t70.5 29.5zM100 800v-200h300v200 h-300zM1100 535l-400 -133v163l400 133v-163zM100 500v-200h300v200h-300zM1100 398v-248q0 -21 -14.5 -35.5t-35.5 -14.5h-375l-100 -100h-375l-100 100h400l200 200h105z" />
|
|
||||||
<glyph unicode="" d="M17 1007l162 162q17 17 40 14t37 -22l139 -194q14 -20 11 -44.5t-20 -41.5l-119 -118q102 -142 228 -268t267 -227l119 118q17 17 42.5 19t44.5 -12l192 -136q19 -14 22.5 -37.5t-13.5 -40.5l-163 -162q-3 -1 -9.5 -1t-29.5 2t-47.5 6t-62.5 14.5t-77.5 26.5t-90 42.5 t-101.5 60t-111 83t-119 108.5q-74 74 -133.5 150.5t-94.5 138.5t-60 119.5t-34.5 100t-15 74.5t-4.5 48z" />
|
|
||||||
<glyph unicode="" d="M600 1100q92 0 175 -10.5t141.5 -27t108.5 -36.5t81.5 -40t53.5 -37t31 -27l9 -10v-200q0 -21 -14.5 -33t-34.5 -9l-202 34q-20 3 -34.5 20t-14.5 38v146q-141 24 -300 24t-300 -24v-146q0 -21 -14.5 -38t-34.5 -20l-202 -34q-20 -3 -34.5 9t-14.5 33v200q3 4 9.5 10.5 t31 26t54 37.5t80.5 39.5t109 37.5t141 26.5t175 10.5zM600 795q56 0 97 -9.5t60 -23.5t30 -28t12 -24l1 -10v-50l365 -303q14 -15 24.5 -40t10.5 -45v-212q0 -21 -14.5 -35.5t-35.5 -14.5h-1100q-21 0 -35.5 14.5t-14.5 35.5v212q0 20 10.5 45t24.5 40l365 303v50 q0 4 1 10.5t12 23t30 29t60 22.5t97 10z" />
|
|
||||||
<glyph unicode="" d="M1100 700l-200 -200h-600l-200 200v500h200v-200h200v200h200v-200h200v200h200v-500zM250 400h700q21 0 35.5 -14.5t14.5 -35.5t-14.5 -35.5t-35.5 -14.5h-12l137 -100h-950l137 100h-12q-21 0 -35.5 14.5t-14.5 35.5t14.5 35.5t35.5 14.5zM50 100h1100q21 0 35.5 -14.5 t14.5 -35.5v-50h-1200v50q0 21 14.5 35.5t35.5 14.5z" />
|
|
||||||
<glyph unicode="" d="M700 1100h-100q-41 0 -70.5 -29.5t-29.5 -70.5v-1000h300v1000q0 41 -29.5 70.5t-70.5 29.5zM1100 800h-100q-41 0 -70.5 -29.5t-29.5 -70.5v-700h300v700q0 41 -29.5 70.5t-70.5 29.5zM400 0h-300v400q0 41 29.5 70.5t70.5 29.5h100q41 0 70.5 -29.5t29.5 -70.5v-400z " />
|
|
||||||
<glyph unicode="" d="M200 1100h700q124 0 212 -88t88 -212v-500q0 -124 -88 -212t-212 -88h-700q-124 0 -212 88t-88 212v500q0 124 88 212t212 88zM100 900v-700h900v700h-900zM500 700h-200v-100h200v-300h-300v100h200v100h-200v300h300v-100zM900 700v-300l-100 -100h-200v500h200z M700 700v-300h100v300h-100z" />
|
|
||||||
<glyph unicode="" d="M200 1100h700q124 0 212 -88t88 -212v-500q0 -124 -88 -212t-212 -88h-700q-124 0 -212 88t-88 212v500q0 124 88 212t212 88zM100 900v-700h900v700h-900zM500 300h-100v200h-100v-200h-100v500h100v-200h100v200h100v-500zM900 700v-300l-100 -100h-200v500h200z M700 700v-300h100v300h-100z" />
|
|
||||||
<glyph unicode="" d="M200 1100h700q124 0 212 -88t88 -212v-500q0 -124 -88 -212t-212 -88h-700q-124 0 -212 88t-88 212v500q0 124 88 212t212 88zM100 900v-700h900v700h-900zM500 700h-200v-300h200v-100h-300v500h300v-100zM900 700h-200v-300h200v-100h-300v500h300v-100z" />
|
|
||||||
<glyph unicode="" d="M200 1100h700q124 0 212 -88t88 -212v-500q0 -124 -88 -212t-212 -88h-700q-124 0 -212 88t-88 212v500q0 124 88 212t212 88zM100 900v-700h900v700h-900zM500 400l-300 150l300 150v-300zM900 550l-300 -150v300z" />
|
|
||||||
<glyph unicode="" d="M200 1100h700q124 0 212 -88t88 -212v-500q0 -124 -88 -212t-212 -88h-700q-124 0 -212 88t-88 212v500q0 124 88 212t212 88zM100 900v-700h900v700h-900zM900 300h-700v500h700v-500zM800 700h-130q-38 0 -66.5 -43t-28.5 -108t27 -107t68 -42h130v300zM300 700v-300 h130q41 0 68 42t27 107t-28.5 108t-66.5 43h-130z" />
|
|
||||||
<glyph unicode="" d="M200 1100h700q124 0 212 -88t88 -212v-500q0 -124 -88 -212t-212 -88h-700q-124 0 -212 88t-88 212v500q0 124 88 212t212 88zM100 900v-700h900v700h-900zM500 700h-200v-100h200v-300h-300v100h200v100h-200v300h300v-100zM900 300h-100v400h-100v100h200v-500z M700 300h-100v100h100v-100z" />
|
|
||||||
<glyph unicode="" d="M200 1100h700q124 0 212 -88t88 -212v-500q0 -124 -88 -212t-212 -88h-700q-124 0 -212 88t-88 212v500q0 124 88 212t212 88zM100 900v-700h900v700h-900zM300 700h200v-400h-300v500h100v-100zM900 300h-100v400h-100v100h200v-500zM300 600v-200h100v200h-100z M700 300h-100v100h100v-100z" />
|
|
||||||
<glyph unicode="" d="M200 1100h700q124 0 212 -88t88 -212v-500q0 -124 -88 -212t-212 -88h-700q-124 0 -212 88t-88 212v500q0 124 88 212t212 88zM100 900v-700h900v700h-900zM500 500l-199 -200h-100v50l199 200v150h-200v100h300v-300zM900 300h-100v400h-100v100h200v-500zM701 300h-100 v100h100v-100z" />
|
|
||||||
<glyph unicode="" d="M600 1191q120 0 229.5 -47t188.5 -126t126 -188.5t47 -229.5t-47 -229.5t-126 -188.5t-188.5 -126t-229.5 -47t-229.5 47t-188.5 126t-126 188.5t-47 229.5t47 229.5t126 188.5t188.5 126t229.5 47zM600 1021q-114 0 -211 -56.5t-153.5 -153.5t-56.5 -211t56.5 -211 t153.5 -153.5t211 -56.5t211 56.5t153.5 153.5t56.5 211t-56.5 211t-153.5 153.5t-211 56.5zM800 700h-300v-200h300v-100h-300l-100 100v200l100 100h300v-100z" />
|
|
||||||
<glyph unicode="" d="M600 1191q120 0 229.5 -47t188.5 -126t126 -188.5t47 -229.5t-47 -229.5t-126 -188.5t-188.5 -126t-229.5 -47t-229.5 47t-188.5 126t-126 188.5t-47 229.5t47 229.5t126 188.5t188.5 126t229.5 47zM600 1021q-114 0 -211 -56.5t-153.5 -153.5t-56.5 -211t56.5 -211 t153.5 -153.5t211 -56.5t211 56.5t153.5 153.5t56.5 211t-56.5 211t-153.5 153.5t-211 56.5zM800 700v-100l-50 -50l100 -100v-50h-100l-100 100h-150v-100h-100v400h300zM500 700v-100h200v100h-200z" />
|
|
||||||
<glyph unicode="" d="M503 1089q110 0 200.5 -59.5t134.5 -156.5q44 14 90 14q120 0 205 -86.5t85 -207t-85 -207t-205 -86.5h-128v250q0 21 -14.5 35.5t-35.5 14.5h-300q-21 0 -35.5 -14.5t-14.5 -35.5v-250h-222q-80 0 -136 57.5t-56 136.5q0 69 43 122.5t108 67.5q-2 19 -2 37q0 100 49 185 t134 134t185 49zM525 500h150q10 0 17.5 -7.5t7.5 -17.5v-275h137q21 0 26 -11.5t-8 -27.5l-223 -244q-13 -16 -32 -16t-32 16l-223 244q-13 16 -8 27.5t26 11.5h137v275q0 10 7.5 17.5t17.5 7.5z" />
|
|
||||||
<glyph unicode="" d="M502 1089q110 0 201 -59.5t135 -156.5q43 15 89 15q121 0 206 -86.5t86 -206.5q0 -99 -60 -181t-150 -110l-378 360q-13 16 -31.5 16t-31.5 -16l-381 -365h-9q-79 0 -135.5 57.5t-56.5 136.5q0 69 43 122.5t108 67.5q-2 19 -2 38q0 100 49 184.5t133.5 134t184.5 49.5z M632 467l223 -228q13 -16 8 -27.5t-26 -11.5h-137v-275q0 -10 -7.5 -17.5t-17.5 -7.5h-150q-10 0 -17.5 7.5t-7.5 17.5v275h-137q-21 0 -26 11.5t8 27.5q199 204 223 228q19 19 31.5 19t32.5 -19z" />
|
|
||||||
<glyph unicode="" d="M700 100v100h400l-270 300h170l-270 300h170l-300 333l-300 -333h170l-270 -300h170l-270 -300h400v-100h-50q-21 0 -35.5 -14.5t-14.5 -35.5v-50h400v50q0 21 -14.5 35.5t-35.5 14.5h-50z" />
|
|
||||||
<glyph unicode="" d="M600 1179q94 0 167.5 -56.5t99.5 -145.5q89 -6 150.5 -71.5t61.5 -155.5q0 -61 -29.5 -112.5t-79.5 -82.5q9 -29 9 -55q0 -74 -52.5 -126.5t-126.5 -52.5q-55 0 -100 30v-251q21 0 35.5 -14.5t14.5 -35.5v-50h-300v50q0 21 14.5 35.5t35.5 14.5v251q-45 -30 -100 -30 q-74 0 -126.5 52.5t-52.5 126.5q0 18 4 38q-47 21 -75.5 65t-28.5 97q0 74 52.5 126.5t126.5 52.5q5 0 23 -2q0 2 -1 10t-1 13q0 116 81.5 197.5t197.5 81.5z" />
|
|
||||||
<glyph unicode="" d="M1010 1010q111 -111 150.5 -260.5t0 -299t-150.5 -260.5q-83 -83 -191.5 -126.5t-218.5 -43.5t-218.5 43.5t-191.5 126.5q-111 111 -150.5 260.5t0 299t150.5 260.5q83 83 191.5 126.5t218.5 43.5t218.5 -43.5t191.5 -126.5zM476 1065q-4 0 -8 -1q-121 -34 -209.5 -122.5 t-122.5 -209.5q-4 -12 2.5 -23t18.5 -14l36 -9q3 -1 7 -1q23 0 29 22q27 96 98 166q70 71 166 98q11 3 17.5 13.5t3.5 22.5l-9 35q-3 13 -14 19q-7 4 -15 4zM512 920q-4 0 -9 -2q-80 -24 -138.5 -82.5t-82.5 -138.5q-4 -13 2 -24t19 -14l34 -9q4 -1 8 -1q22 0 28 21 q18 58 58.5 98.5t97.5 58.5q12 3 18 13.5t3 21.5l-9 35q-3 12 -14 19q-7 4 -15 4zM719.5 719.5q-49.5 49.5 -119.5 49.5t-119.5 -49.5t-49.5 -119.5t49.5 -119.5t119.5 -49.5t119.5 49.5t49.5 119.5t-49.5 119.5zM855 551q-22 0 -28 -21q-18 -58 -58.5 -98.5t-98.5 -57.5 q-11 -4 -17 -14.5t-3 -21.5l9 -35q3 -12 14 -19q7 -4 15 -4q4 0 9 2q80 24 138.5 82.5t82.5 138.5q4 13 -2.5 24t-18.5 14l-34 9q-4 1 -8 1zM1000 515q-23 0 -29 -22q-27 -96 -98 -166q-70 -71 -166 -98q-11 -3 -17.5 -13.5t-3.5 -22.5l9 -35q3 -13 14 -19q7 -4 15 -4 q4 0 8 1q121 34 209.5 122.5t122.5 209.5q4 12 -2.5 23t-18.5 14l-36 9q-3 1 -7 1z" />
|
|
||||||
<glyph unicode="" d="M700 800h300v-380h-180v200h-340v-200h-380v755q0 10 7.5 17.5t17.5 7.5h575v-400zM1000 900h-200v200zM700 300h162l-212 -212l-212 212h162v200h100v-200zM520 0h-395q-10 0 -17.5 7.5t-7.5 17.5v395zM1000 220v-195q0 -10 -7.5 -17.5t-17.5 -7.5h-195z" />
|
|
||||||
<glyph unicode="" d="M700 800h300v-520l-350 350l-550 -550v1095q0 10 7.5 17.5t17.5 7.5h575v-400zM1000 900h-200v200zM862 200h-162v-200h-100v200h-162l212 212zM480 0h-355q-10 0 -17.5 7.5t-7.5 17.5v55h380v-80zM1000 80v-55q0 -10 -7.5 -17.5t-17.5 -7.5h-155v80h180z" />
|
|
||||||
<glyph unicode="" d="M1162 800h-162v-200h100l100 -100h-300v300h-162l212 212zM200 800h200q27 0 40 -2t29.5 -10.5t23.5 -30t7 -57.5h300v-100h-600l-200 -350v450h100q0 36 7 57.5t23.5 30t29.5 10.5t40 2zM800 400h240l-240 -400h-800l300 500h500v-100z" />
|
|
||||||
<glyph unicode="" d="M650 1100h100q21 0 35.5 -14.5t14.5 -35.5v-50h50q21 0 35.5 -14.5t14.5 -35.5v-100q0 -21 -14.5 -35.5t-35.5 -14.5h-300q-21 0 -35.5 14.5t-14.5 35.5v100q0 21 14.5 35.5t35.5 14.5h50v50q0 21 14.5 35.5t35.5 14.5zM1000 850v150q41 0 70.5 -29.5t29.5 -70.5v-800 q0 -41 -29.5 -70.5t-70.5 -29.5h-600q-1 0 -20 4l246 246l-326 326v324q0 41 29.5 70.5t70.5 29.5v-150q0 -62 44 -106t106 -44h300q62 0 106 44t44 106zM412 250l-212 -212v162h-200v100h200v162z" />
|
|
||||||
<glyph unicode="" d="M450 1100h100q21 0 35.5 -14.5t14.5 -35.5v-50h50q21 0 35.5 -14.5t14.5 -35.5v-100q0 -21 -14.5 -35.5t-35.5 -14.5h-300q-21 0 -35.5 14.5t-14.5 35.5v100q0 21 14.5 35.5t35.5 14.5h50v50q0 21 14.5 35.5t35.5 14.5zM800 850v150q41 0 70.5 -29.5t29.5 -70.5v-500 h-200v-300h200q0 -36 -7 -57.5t-23.5 -30t-29.5 -10.5t-40 -2h-600q-41 0 -70.5 29.5t-29.5 70.5v800q0 41 29.5 70.5t70.5 29.5v-150q0 -62 44 -106t106 -44h300q62 0 106 44t44 106zM1212 250l-212 -212v162h-200v100h200v162z" />
|
|
||||||
<glyph unicode="" d="M658 1197l637 -1104q23 -38 7 -65.5t-60 -27.5h-1276q-44 0 -60 27.5t7 65.5l637 1104q22 39 54 39t54 -39zM704 800h-208q-20 0 -32 -14.5t-8 -34.5l58 -302q4 -20 21.5 -34.5t37.5 -14.5h54q20 0 37.5 14.5t21.5 34.5l58 302q4 20 -8 34.5t-32 14.5zM500 300v-100h200 v100h-200z" />
|
|
||||||
<glyph unicode="" d="M425 1100h250q10 0 17.5 -7.5t7.5 -17.5v-150q0 -10 -7.5 -17.5t-17.5 -7.5h-250q-10 0 -17.5 7.5t-7.5 17.5v150q0 10 7.5 17.5t17.5 7.5zM425 800h250q10 0 17.5 -7.5t7.5 -17.5v-150q0 -10 -7.5 -17.5t-17.5 -7.5h-250q-10 0 -17.5 7.5t-7.5 17.5v150q0 10 7.5 17.5 t17.5 7.5zM825 800h250q10 0 17.5 -7.5t7.5 -17.5v-150q0 -10 -7.5 -17.5t-17.5 -7.5h-250q-10 0 -17.5 7.5t-7.5 17.5v150q0 10 7.5 17.5t17.5 7.5zM25 500h250q10 0 17.5 -7.5t7.5 -17.5v-150q0 -10 -7.5 -17.5t-17.5 -7.5h-250q-10 0 -17.5 7.5t-7.5 17.5v150 q0 10 7.5 17.5t17.5 7.5zM425 500h250q10 0 17.5 -7.5t7.5 -17.5v-150q0 -10 -7.5 -17.5t-17.5 -7.5h-250q-10 0 -17.5 7.5t-7.5 17.5v150q0 10 7.5 17.5t17.5 7.5zM825 500h250q10 0 17.5 -7.5t7.5 -17.5v-150q0 -10 -7.5 -17.5t-17.5 -7.5h-250q-10 0 -17.5 7.5t-7.5 17.5 v150q0 10 7.5 17.5t17.5 7.5zM25 200h250q10 0 17.5 -7.5t7.5 -17.5v-150q0 -10 -7.5 -17.5t-17.5 -7.5h-250q-10 0 -17.5 7.5t-7.5 17.5v150q0 10 7.5 17.5t17.5 7.5zM425 200h250q10 0 17.5 -7.5t7.5 -17.5v-150q0 -10 -7.5 -17.5t-17.5 -7.5h-250q-10 0 -17.5 7.5 t-7.5 17.5v150q0 10 7.5 17.5t17.5 7.5zM825 200h250q10 0 17.5 -7.5t7.5 -17.5v-150q0 -10 -7.5 -17.5t-17.5 -7.5h-250q-10 0 -17.5 7.5t-7.5 17.5v150q0 10 7.5 17.5t17.5 7.5z" />
|
|
||||||
<glyph unicode="" d="M700 1200h100v-200h-100v-100h350q62 0 86.5 -39.5t-3.5 -94.5l-66 -132q-41 -83 -81 -134h-772q-40 51 -81 134l-66 132q-28 55 -3.5 94.5t86.5 39.5h350v100h-100v200h100v100h200v-100zM250 400h700q21 0 35.5 -14.5t14.5 -35.5t-14.5 -35.5t-35.5 -14.5h-12l137 -100 h-950l138 100h-13q-21 0 -35.5 14.5t-14.5 35.5t14.5 35.5t35.5 14.5zM50 100h1100q21 0 35.5 -14.5t14.5 -35.5v-50h-1200v50q0 21 14.5 35.5t35.5 14.5z" />
|
|
||||||
<glyph unicode="" d="M600 1300q40 0 68.5 -29.5t28.5 -70.5h-194q0 41 28.5 70.5t68.5 29.5zM443 1100h314q18 -37 18 -75q0 -8 -3 -25h328q41 0 44.5 -16.5t-30.5 -38.5l-175 -145h-678l-178 145q-34 22 -29 38.5t46 16.5h328q-3 17 -3 25q0 38 18 75zM250 700h700q21 0 35.5 -14.5 t14.5 -35.5t-14.5 -35.5t-35.5 -14.5h-150v-200l275 -200h-950l275 200v200h-150q-21 0 -35.5 14.5t-14.5 35.5t14.5 35.5t35.5 14.5zM50 100h1100q21 0 35.5 -14.5t14.5 -35.5v-50h-1200v50q0 21 14.5 35.5t35.5 14.5z" />
|
|
||||||
<glyph unicode="" d="M600 1181q75 0 128 -53t53 -128t-53 -128t-128 -53t-128 53t-53 128t53 128t128 53zM602 798h46q34 0 55.5 -28.5t21.5 -86.5q0 -76 39 -183h-324q39 107 39 183q0 58 21.5 86.5t56.5 28.5h45zM250 400h700q21 0 35.5 -14.5t14.5 -35.5t-14.5 -35.5t-35.5 -14.5h-13 l138 -100h-950l137 100h-12q-21 0 -35.5 14.5t-14.5 35.5t14.5 35.5t35.5 14.5zM50 100h1100q21 0 35.5 -14.5t14.5 -35.5v-50h-1200v50q0 21 14.5 35.5t35.5 14.5z" />
|
|
||||||
<glyph unicode="" d="M600 1300q47 0 92.5 -53.5t71 -123t25.5 -123.5q0 -78 -55.5 -133.5t-133.5 -55.5t-133.5 55.5t-55.5 133.5q0 62 34 143l144 -143l111 111l-163 163q34 26 63 26zM602 798h46q34 0 55.5 -28.5t21.5 -86.5q0 -76 39 -183h-324q39 107 39 183q0 58 21.5 86.5t56.5 28.5h45 zM250 400h700q21 0 35.5 -14.5t14.5 -35.5t-14.5 -35.5t-35.5 -14.5h-13l138 -100h-950l137 100h-12q-21 0 -35.5 14.5t-14.5 35.5t14.5 35.5t35.5 14.5zM50 100h1100q21 0 35.5 -14.5t14.5 -35.5v-50h-1200v50q0 21 14.5 35.5t35.5 14.5z" />
|
|
||||||
<glyph unicode="" d="M600 1200l300 -161v-139h-300q0 -57 18.5 -108t50 -91.5t63 -72t70 -67.5t57.5 -61h-530q-60 83 -90.5 177.5t-30.5 178.5t33 164.5t87.5 139.5t126 96.5t145.5 41.5v-98zM250 400h700q21 0 35.5 -14.5t14.5 -35.5t-14.5 -35.5t-35.5 -14.5h-13l138 -100h-950l137 100 h-12q-21 0 -35.5 14.5t-14.5 35.5t14.5 35.5t35.5 14.5zM50 100h1100q21 0 35.5 -14.5t14.5 -35.5v-50h-1200v50q0 21 14.5 35.5t35.5 14.5z" />
|
|
||||||
<glyph unicode="" d="M600 1300q41 0 70.5 -29.5t29.5 -70.5v-78q46 -26 73 -72t27 -100v-50h-400v50q0 54 27 100t73 72v78q0 41 29.5 70.5t70.5 29.5zM400 800h400q54 0 100 -27t72 -73h-172v-100h200v-100h-200v-100h200v-100h-200v-100h200q0 -83 -58.5 -141.5t-141.5 -58.5h-400 q-83 0 -141.5 58.5t-58.5 141.5v400q0 83 58.5 141.5t141.5 58.5z" />
|
|
||||||
<glyph unicode="" d="M150 1100h900q21 0 35.5 -14.5t14.5 -35.5v-500q0 -21 -14.5 -35.5t-35.5 -14.5h-900q-21 0 -35.5 14.5t-14.5 35.5v500q0 21 14.5 35.5t35.5 14.5zM125 400h950q10 0 17.5 -7.5t7.5 -17.5v-50q0 -10 -7.5 -17.5t-17.5 -7.5h-283l224 -224q13 -13 13 -31.5t-13 -32 t-31.5 -13.5t-31.5 13l-88 88h-524l-87 -88q-13 -13 -32 -13t-32 13.5t-13 32t13 31.5l224 224h-289q-10 0 -17.5 7.5t-7.5 17.5v50q0 10 7.5 17.5t17.5 7.5zM541 300l-100 -100h324l-100 100h-124z" />
|
|
||||||
<glyph unicode="" d="M200 1100h800q83 0 141.5 -58.5t58.5 -141.5v-200h-100q0 41 -29.5 70.5t-70.5 29.5h-250q-41 0 -70.5 -29.5t-29.5 -70.5h-100q0 41 -29.5 70.5t-70.5 29.5h-250q-41 0 -70.5 -29.5t-29.5 -70.5h-100v200q0 83 58.5 141.5t141.5 58.5zM100 600h1000q41 0 70.5 -29.5 t29.5 -70.5v-300h-1200v300q0 41 29.5 70.5t70.5 29.5zM300 100v-50q0 -21 -14.5 -35.5t-35.5 -14.5h-100q-21 0 -35.5 14.5t-14.5 35.5v50h200zM1100 100v-50q0 -21 -14.5 -35.5t-35.5 -14.5h-100q-21 0 -35.5 14.5t-14.5 35.5v50h200z" />
|
|
||||||
<glyph unicode="" d="M480 1165l682 -683q31 -31 31 -75.5t-31 -75.5l-131 -131h-481l-517 518q-32 31 -32 75.5t32 75.5l295 296q31 31 75.5 31t76.5 -31zM108 794l342 -342l303 304l-341 341zM250 100h800q21 0 35.5 -14.5t14.5 -35.5v-50h-900v50q0 21 14.5 35.5t35.5 14.5z" />
|
|
||||||
<glyph unicode="" d="M1057 647l-189 506q-8 19 -27.5 33t-40.5 14h-400q-21 0 -40.5 -14t-27.5 -33l-189 -506q-8 -19 1.5 -33t30.5 -14h625v-150q0 -21 14.5 -35.5t35.5 -14.5t35.5 14.5t14.5 35.5v150h125q21 0 30.5 14t1.5 33zM897 0h-595v50q0 21 14.5 35.5t35.5 14.5h50v50 q0 21 14.5 35.5t35.5 14.5h48v300h200v-300h47q21 0 35.5 -14.5t14.5 -35.5v-50h50q21 0 35.5 -14.5t14.5 -35.5v-50z" />
|
|
||||||
<glyph unicode="" d="M900 800h300v-575q0 -10 -7.5 -17.5t-17.5 -7.5h-375v591l-300 300v84q0 10 7.5 17.5t17.5 7.5h375v-400zM1200 900h-200v200zM400 600h300v-575q0 -10 -7.5 -17.5t-17.5 -7.5h-650q-10 0 -17.5 7.5t-7.5 17.5v950q0 10 7.5 17.5t17.5 7.5h375v-400zM700 700h-200v200z " />
|
|
||||||
<glyph unicode="" d="M484 1095h195q75 0 146 -32.5t124 -86t89.5 -122.5t48.5 -142q18 -14 35 -20q31 -10 64.5 6.5t43.5 48.5q10 34 -15 71q-19 27 -9 43q5 8 12.5 11t19 -1t23.5 -16q41 -44 39 -105q-3 -63 -46 -106.5t-104 -43.5h-62q-7 -55 -35 -117t-56 -100l-39 -234q-3 -20 -20 -34.5 t-38 -14.5h-100q-21 0 -33 14.5t-9 34.5l12 70q-49 -14 -91 -14h-195q-24 0 -65 8l-11 -64q-3 -20 -20 -34.5t-38 -14.5h-100q-21 0 -33 14.5t-9 34.5l26 157q-84 74 -128 175l-159 53q-19 7 -33 26t-14 40v50q0 21 14.5 35.5t35.5 14.5h124q11 87 56 166l-111 95 q-16 14 -12.5 23.5t24.5 9.5h203q116 101 250 101zM675 1000h-250q-10 0 -17.5 -7.5t-7.5 -17.5v-50q0 -10 7.5 -17.5t17.5 -7.5h250q10 0 17.5 7.5t7.5 17.5v50q0 10 -7.5 17.5t-17.5 7.5z" />
|
|
||||||
<glyph unicode="" d="M641 900l423 247q19 8 42 2.5t37 -21.5l32 -38q14 -15 12.5 -36t-17.5 -34l-139 -120h-390zM50 1100h106q67 0 103 -17t66 -71l102 -212h823q21 0 35.5 -14.5t14.5 -35.5v-50q0 -21 -14 -40t-33 -26l-737 -132q-23 -4 -40 6t-26 25q-42 67 -100 67h-300q-62 0 -106 44 t-44 106v200q0 62 44 106t106 44zM173 928h-80q-19 0 -28 -14t-9 -35v-56q0 -51 42 -51h134q16 0 21.5 8t5.5 24q0 11 -16 45t-27 51q-18 28 -43 28zM550 727q-32 0 -54.5 -22.5t-22.5 -54.5t22.5 -54.5t54.5 -22.5t54.5 22.5t22.5 54.5t-22.5 54.5t-54.5 22.5zM130 389 l152 130q18 19 34 24t31 -3.5t24.5 -17.5t25.5 -28q28 -35 50.5 -51t48.5 -13l63 5l48 -179q13 -61 -3.5 -97.5t-67.5 -79.5l-80 -69q-47 -40 -109 -35.5t-103 51.5l-130 151q-40 47 -35.5 109.5t51.5 102.5zM380 377l-102 -88q-31 -27 2 -65l37 -43q13 -15 27.5 -19.5 t31.5 6.5l61 53q19 16 14 49q-2 20 -12 56t-17 45q-11 12 -19 14t-23 -8z" />
|
|
||||||
<glyph unicode="" d="M625 1200h150q10 0 17.5 -7.5t7.5 -17.5v-109q79 -33 131 -87.5t53 -128.5q1 -46 -15 -84.5t-39 -61t-46 -38t-39 -21.5l-17 -6q6 0 15 -1.5t35 -9t50 -17.5t53 -30t50 -45t35.5 -64t14.5 -84q0 -59 -11.5 -105.5t-28.5 -76.5t-44 -51t-49.5 -31.5t-54.5 -16t-49.5 -6.5 t-43.5 -1v-75q0 -10 -7.5 -17.5t-17.5 -7.5h-150q-10 0 -17.5 7.5t-7.5 17.5v75h-100v-75q0 -10 -7.5 -17.5t-17.5 -7.5h-150q-10 0 -17.5 7.5t-7.5 17.5v75h-175q-10 0 -17.5 7.5t-7.5 17.5v150q0 10 7.5 17.5t17.5 7.5h75v600h-75q-10 0 -17.5 7.5t-7.5 17.5v150 q0 10 7.5 17.5t17.5 7.5h175v75q0 10 7.5 17.5t17.5 7.5h150q10 0 17.5 -7.5t7.5 -17.5v-75h100v75q0 10 7.5 17.5t17.5 7.5zM400 900v-200h263q28 0 48.5 10.5t30 25t15 29t5.5 25.5l1 10q0 4 -0.5 11t-6 24t-15 30t-30 24t-48.5 11h-263zM400 500v-200h363q28 0 48.5 10.5 t30 25t15 29t5.5 25.5l1 10q0 4 -0.5 11t-6 24t-15 30t-30 24t-48.5 11h-363z" />
|
|
||||||
<glyph unicode="" d="M212 1198h780q86 0 147 -61t61 -147v-416q0 -51 -18 -142.5t-36 -157.5l-18 -66q-29 -87 -93.5 -146.5t-146.5 -59.5h-572q-82 0 -147 59t-93 147q-8 28 -20 73t-32 143.5t-20 149.5v416q0 86 61 147t147 61zM600 1045q-70 0 -132.5 -11.5t-105.5 -30.5t-78.5 -41.5 t-57 -45t-36 -41t-20.5 -30.5l-6 -12l156 -243h560l156 243q-2 5 -6 12.5t-20 29.5t-36.5 42t-57 44.5t-79 42t-105 29.5t-132.5 12zM762 703h-157l195 261z" />
|
|
||||||
<glyph unicode="" d="M475 1300h150q103 0 189 -86t86 -189v-500q0 -41 -42 -83t-83 -42h-450q-41 0 -83 42t-42 83v500q0 103 86 189t189 86zM700 300v-225q0 -21 -27 -48t-48 -27h-150q-21 0 -48 27t-27 48v225h300z" />
|
|
||||||
<glyph unicode="" d="M475 1300h96q0 -150 89.5 -239.5t239.5 -89.5v-446q0 -41 -42 -83t-83 -42h-450q-41 0 -83 42t-42 83v500q0 103 86 189t189 86zM700 300v-225q0 -21 -27 -48t-48 -27h-150q-21 0 -48 27t-27 48v225h300z" />
|
|
||||||
<glyph unicode="" d="M1294 767l-638 -283l-378 170l-78 -60v-224l100 -150v-199l-150 148l-150 -149v200l100 150v250q0 4 -0.5 10.5t0 9.5t1 8t3 8t6.5 6l47 40l-147 65l642 283zM1000 380l-350 -166l-350 166v147l350 -165l350 165v-147z" />
|
|
||||||
<glyph unicode="" d="M250 800q62 0 106 -44t44 -106t-44 -106t-106 -44t-106 44t-44 106t44 106t106 44zM650 800q62 0 106 -44t44 -106t-44 -106t-106 -44t-106 44t-44 106t44 106t106 44zM1050 800q62 0 106 -44t44 -106t-44 -106t-106 -44t-106 44t-44 106t44 106t106 44z" />
|
|
||||||
<glyph unicode="" d="M550 1100q62 0 106 -44t44 -106t-44 -106t-106 -44t-106 44t-44 106t44 106t106 44zM550 700q62 0 106 -44t44 -106t-44 -106t-106 -44t-106 44t-44 106t44 106t106 44zM550 300q62 0 106 -44t44 -106t-44 -106t-106 -44t-106 44t-44 106t44 106t106 44z" />
|
|
||||||
<glyph unicode="" d="M125 1100h950q10 0 17.5 -7.5t7.5 -17.5v-150q0 -10 -7.5 -17.5t-17.5 -7.5h-950q-10 0 -17.5 7.5t-7.5 17.5v150q0 10 7.5 17.5t17.5 7.5zM125 700h950q10 0 17.5 -7.5t7.5 -17.5v-150q0 -10 -7.5 -17.5t-17.5 -7.5h-950q-10 0 -17.5 7.5t-7.5 17.5v150q0 10 7.5 17.5 t17.5 7.5zM125 300h950q10 0 17.5 -7.5t7.5 -17.5v-150q0 -10 -7.5 -17.5t-17.5 -7.5h-950q-10 0 -17.5 7.5t-7.5 17.5v150q0 10 7.5 17.5t17.5 7.5z" />
|
|
||||||
<glyph unicode="" d="M350 1200h500q162 0 256 -93.5t94 -256.5v-500q0 -165 -93.5 -257.5t-256.5 -92.5h-500q-165 0 -257.5 92.5t-92.5 257.5v500q0 165 92.5 257.5t257.5 92.5zM900 1000h-600q-41 0 -70.5 -29.5t-29.5 -70.5v-600q0 -41 29.5 -70.5t70.5 -29.5h600q41 0 70.5 29.5 t29.5 70.5v600q0 41 -29.5 70.5t-70.5 29.5zM350 900h500q21 0 35.5 -14.5t14.5 -35.5v-300q0 -21 -14.5 -35.5t-35.5 -14.5h-500q-21 0 -35.5 14.5t-14.5 35.5v300q0 21 14.5 35.5t35.5 14.5zM400 800v-200h400v200h-400z" />
|
|
||||||
<glyph unicode="" d="M150 1100h1000q21 0 35.5 -14.5t14.5 -35.5t-14.5 -35.5t-35.5 -14.5h-50v-200h50q21 0 35.5 -14.5t14.5 -35.5t-14.5 -35.5t-35.5 -14.5h-50v-200h50q21 0 35.5 -14.5t14.5 -35.5t-14.5 -35.5t-35.5 -14.5h-50v-200h50q21 0 35.5 -14.5t14.5 -35.5t-14.5 -35.5 t-35.5 -14.5h-1000q-21 0 -35.5 14.5t-14.5 35.5t14.5 35.5t35.5 14.5h50v200h-50q-21 0 -35.5 14.5t-14.5 35.5t14.5 35.5t35.5 14.5h50v200h-50q-21 0 -35.5 14.5t-14.5 35.5t14.5 35.5t35.5 14.5h50v200h-50q-21 0 -35.5 14.5t-14.5 35.5t14.5 35.5t35.5 14.5z" />
|
|
||||||
<glyph unicode="" d="M650 1187q87 -67 118.5 -156t0 -178t-118.5 -155q-87 66 -118.5 155t0 178t118.5 156zM300 800q124 0 212 -88t88 -212q-124 0 -212 88t-88 212zM1000 800q0 -124 -88 -212t-212 -88q0 124 88 212t212 88zM300 500q124 0 212 -88t88 -212q-124 0 -212 88t-88 212z M1000 500q0 -124 -88 -212t-212 -88q0 124 88 212t212 88zM700 199v-144q0 -21 -14.5 -35.5t-35.5 -14.5t-35.5 14.5t-14.5 35.5v142q40 -4 43 -4q17 0 57 6z" />
|
|
||||||
<glyph unicode="" d="M745 878l69 19q25 6 45 -12l298 -295q11 -11 15 -26.5t-2 -30.5q-5 -14 -18 -23.5t-28 -9.5h-8q1 0 1 -13q0 -29 -2 -56t-8.5 -62t-20 -63t-33 -53t-51 -39t-72.5 -14h-146q-184 0 -184 288q0 24 10 47q-20 4 -62 4t-63 -4q11 -24 11 -47q0 -288 -184 -288h-142 q-48 0 -84.5 21t-56 51t-32 71.5t-16 75t-3.5 68.5q0 13 2 13h-7q-15 0 -27.5 9.5t-18.5 23.5q-6 15 -2 30.5t15 25.5l298 296q20 18 46 11l76 -19q20 -5 30.5 -22.5t5.5 -37.5t-22.5 -31t-37.5 -5l-51 12l-182 -193h891l-182 193l-44 -12q-20 -5 -37.5 6t-22.5 31t6 37.5 t31 22.5z" />
|
|
||||||
<glyph unicode="" d="M1200 900h-50q0 21 -4 37t-9.5 26.5t-18 17.5t-22 11t-28.5 5.5t-31 2t-37 0.5h-200v-850q0 -22 25 -34.5t50 -13.5l25 -2v-100h-400v100q4 0 11 0.5t24 3t30 7t24 15t11 24.5v850h-200q-25 0 -37 -0.5t-31 -2t-28.5 -5.5t-22 -11t-18 -17.5t-9.5 -26.5t-4 -37h-50v300 h1000v-300zM500 450h-25q0 15 -4 24.5t-9 14.5t-17 7.5t-20 3t-25 0.5h-100v-425q0 -11 12.5 -17.5t25.5 -7.5h12v-50h-200v50q50 0 50 25v425h-100q-17 0 -25 -0.5t-20 -3t-17 -7.5t-9 -14.5t-4 -24.5h-25v150h500v-150z" />
|
|
||||||
<glyph unicode="" d="M1000 300v50q-25 0 -55 32q-14 14 -25 31t-16 27l-4 11l-289 747h-69l-300 -754q-18 -35 -39 -56q-9 -9 -24.5 -18.5t-26.5 -14.5l-11 -5v-50h273v50q-49 0 -78.5 21.5t-11.5 67.5l69 176h293l61 -166q13 -34 -3.5 -66.5t-55.5 -32.5v-50h312zM412 691l134 342l121 -342 h-255zM1100 150v-100q0 -21 -14.5 -35.5t-35.5 -14.5h-1000q-21 0 -35.5 14.5t-14.5 35.5v100q0 21 14.5 35.5t35.5 14.5h1000q21 0 35.5 -14.5t14.5 -35.5z" />
|
|
||||||
<glyph unicode="" d="M50 1200h1100q21 0 35.5 -14.5t14.5 -35.5v-1100q0 -21 -14.5 -35.5t-35.5 -14.5h-1100q-21 0 -35.5 14.5t-14.5 35.5v1100q0 21 14.5 35.5t35.5 14.5zM611 1118h-70q-13 0 -18 -12l-299 -753q-17 -32 -35 -51q-18 -18 -56 -34q-12 -5 -12 -18v-50q0 -8 5.5 -14t14.5 -6 h273q8 0 14 6t6 14v50q0 8 -6 14t-14 6q-55 0 -71 23q-10 14 0 39l63 163h266l57 -153q11 -31 -6 -55q-12 -17 -36 -17q-8 0 -14 -6t-6 -14v-50q0 -8 6 -14t14 -6h313q8 0 14 6t6 14v50q0 7 -5.5 13t-13.5 7q-17 0 -42 25q-25 27 -40 63h-1l-288 748q-5 12 -19 12zM639 611 h-197l103 264z" />
|
|
||||||
<glyph unicode="" d="M1200 1100h-1200v100h1200v-100zM50 1000h400q21 0 35.5 -14.5t14.5 -35.5v-900q0 -21 -14.5 -35.5t-35.5 -14.5h-400q-21 0 -35.5 14.5t-14.5 35.5v900q0 21 14.5 35.5t35.5 14.5zM650 1000h400q21 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.5v400q0 21 14.5 35.5t35.5 14.5zM700 900v-300h300v300h-300z" />
|
|
||||||
<glyph unicode="" d="M50 1200h400q21 0 35.5 -14.5t14.5 -35.5v-900q0 -21 -14.5 -35.5t-35.5 -14.5h-400q-21 0 -35.5 14.5t-14.5 35.5v900q0 21 14.5 35.5t35.5 14.5zM650 700h400q21 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.5v400 q0 21 14.5 35.5t35.5 14.5zM700 600v-300h300v300h-300zM1200 0h-1200v100h1200v-100z" />
|
|
||||||
<glyph unicode="" d="M50 1000h400q21 0 35.5 -14.5t14.5 -35.5v-350h100v150q0 21 14.5 35.5t35.5 14.5h400q21 0 35.5 -14.5t14.5 -35.5v-150h100v-100h-100v-150q0 -21 -14.5 -35.5t-35.5 -14.5h-400q-21 0 -35.5 14.5t-14.5 35.5v150h-100v-350q0 -21 -14.5 -35.5t-35.5 -14.5h-400 q-21 0 -35.5 14.5t-14.5 35.5v800q0 21 14.5 35.5t35.5 14.5zM700 700v-300h300v300h-300z" />
|
|
||||||
<glyph unicode="" d="M100 0h-100v1200h100v-1200zM250 1100h400q21 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.5v400q0 21 14.5 35.5t35.5 14.5zM300 1000v-300h300v300h-300zM250 500h900q21 0 35.5 -14.5t14.5 -35.5v-400 q0 -21 -14.5 -35.5t-35.5 -14.5h-900q-21 0 -35.5 14.5t-14.5 35.5v400q0 21 14.5 35.5t35.5 14.5z" />
|
|
||||||
<glyph unicode="" d="M600 1100h150q21 0 35.5 -14.5t14.5 -35.5v-400q0 -21 -14.5 -35.5t-35.5 -14.5h-150v-100h450q21 0 35.5 -14.5t14.5 -35.5v-400q0 -21 -14.5 -35.5t-35.5 -14.5h-900q-21 0 -35.5 14.5t-14.5 35.5v400q0 21 14.5 35.5t35.5 14.5h350v100h-150q-21 0 -35.5 14.5 t-14.5 35.5v400q0 21 14.5 35.5t35.5 14.5h150v100h100v-100zM400 1000v-300h300v300h-300z" />
|
|
||||||
<glyph unicode="" d="M1200 0h-100v1200h100v-1200zM550 1100h400q21 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.5v400q0 21 14.5 35.5t35.5 14.5zM600 1000v-300h300v300h-300zM50 500h900q21 0 35.5 -14.5t14.5 -35.5v-400 q0 -21 -14.5 -35.5t-35.5 -14.5h-900q-21 0 -35.5 14.5t-14.5 35.5v400q0 21 14.5 35.5t35.5 14.5z" />
|
|
||||||
<glyph unicode="" d="M865 565l-494 -494q-23 -23 -41 -23q-14 0 -22 13.5t-8 38.5v1000q0 25 8 38.5t22 13.5q18 0 41 -23l494 -494q14 -14 14 -35t-14 -35z" />
|
|
||||||
<glyph unicode="" d="M335 635l494 494q29 29 50 20.5t21 -49.5v-1000q0 -41 -21 -49.5t-50 20.5l-494 494q-14 14 -14 35t14 35z" />
|
|
||||||
<glyph unicode="" d="M100 900h1000q41 0 49.5 -21t-20.5 -50l-494 -494q-14 -14 -35 -14t-35 14l-494 494q-29 29 -20.5 50t49.5 21z" />
|
|
||||||
<glyph unicode="" d="M635 865l494 -494q29 -29 20.5 -50t-49.5 -21h-1000q-41 0 -49.5 21t20.5 50l494 494q14 14 35 14t35 -14z" />
|
|
||||||
<glyph unicode="" d="M700 741v-182l-692 -323v221l413 193l-413 193v221zM1200 0h-800v200h800v-200z" />
|
|
||||||
<glyph unicode="" d="M1200 900h-200v-100h200v-100h-300v300h200v100h-200v100h300v-300zM0 700h50q0 21 4 37t9.5 26.5t18 17.5t22 11t28.5 5.5t31 2t37 0.5h100v-550q0 -22 -25 -34.5t-50 -13.5l-25 -2v-100h400v100q-4 0 -11 0.5t-24 3t-30 7t-24 15t-11 24.5v550h100q25 0 37 -0.5t31 -2 t28.5 -5.5t22 -11t18 -17.5t9.5 -26.5t4 -37h50v300h-800v-300z" />
|
|
||||||
<glyph unicode="" d="M800 700h-50q0 21 -4 37t-9.5 26.5t-18 17.5t-22 11t-28.5 5.5t-31 2t-37 0.5h-100v-550q0 -22 25 -34.5t50 -14.5l25 -1v-100h-400v100q4 0 11 0.5t24 3t30 7t24 15t11 24.5v550h-100q-25 0 -37 -0.5t-31 -2t-28.5 -5.5t-22 -11t-18 -17.5t-9.5 -26.5t-4 -37h-50v300 h800v-300zM1100 200h-200v-100h200v-100h-300v300h200v100h-200v100h300v-300z" />
|
|
||||||
<glyph unicode="" d="M701 1098h160q16 0 21 -11t-7 -23l-464 -464l464 -464q12 -12 7 -23t-21 -11h-160q-13 0 -23 9l-471 471q-7 8 -7 18t7 18l471 471q10 9 23 9z" />
|
|
||||||
<glyph unicode="" d="M339 1098h160q13 0 23 -9l471 -471q7 -8 7 -18t-7 -18l-471 -471q-10 -9 -23 -9h-160q-16 0 -21 11t7 23l464 464l-464 464q-12 12 -7 23t21 11z" />
|
|
||||||
<glyph unicode="" d="M1087 882q11 -5 11 -21v-160q0 -13 -9 -23l-471 -471q-8 -7 -18 -7t-18 7l-471 471q-9 10 -9 23v160q0 16 11 21t23 -7l464 -464l464 464q12 12 23 7z" />
|
|
||||||
<glyph unicode="" d="M618 993l471 -471q9 -10 9 -23v-160q0 -16 -11 -21t-23 7l-464 464l-464 -464q-12 -12 -23 -7t-11 21v160q0 13 9 23l471 471q8 7 18 7t18 -7z" />
|
|
||||||
<glyph unicode="" d="M1000 1200q0 -124 -88 -212t-212 -88q0 124 88 212t212 88zM450 1000h100q21 0 40 -14t26 -33l79 -194q5 1 16 3q34 6 54 9.5t60 7t65.5 1t61 -10t56.5 -23t42.5 -42t29 -64t5 -92t-19.5 -121.5q-1 -7 -3 -19.5t-11 -50t-20.5 -73t-32.5 -81.5t-46.5 -83t-64 -70 t-82.5 -50q-13 -5 -42 -5t-65.5 2.5t-47.5 2.5q-14 0 -49.5 -3.5t-63 -3.5t-43.5 7q-57 25 -104.5 78.5t-75 111.5t-46.5 112t-26 90l-7 35q-15 63 -18 115t4.5 88.5t26 64t39.5 43.5t52 25.5t58.5 13t62.5 2t59.5 -4.5t55.5 -8l-147 192q-12 18 -5.5 30t27.5 12z" />
|
|
||||||
<glyph unicode="🔑" d="M250 1200h600q21 0 35.5 -14.5t14.5 -35.5v-400q0 -21 -14.5 -35.5t-35.5 -14.5h-150v-500l-255 -178q-19 -9 -32 -1t-13 29v650h-150q-21 0 -35.5 14.5t-14.5 35.5v400q0 21 14.5 35.5t35.5 14.5zM400 1100v-100h300v100h-300z" />
|
|
||||||
<glyph unicode="🚪" d="M250 1200h750q39 0 69.5 -40.5t30.5 -84.5v-933l-700 -117v950l600 125h-700v-1000h-100v1025q0 23 15.5 49t34.5 26zM500 525v-100l100 20v100z" />
|
|
||||||
</font>
|
|
||||||
</defs></svg>
|
|
||||||
|
Before Width: | Height: | Size: 106 KiB |
Binary file not shown.
Binary file not shown.
Binary file not shown.
15
admin/static/js/angular-resource.min.js
vendored
15
admin/static/js/angular-resource.min.js
vendored
|
|
@ -1,15 +0,0 @@
|
||||||
/*
|
|
||||||
AngularJS v1.7.9
|
|
||||||
(c) 2010-2018 Google, Inc. http://angularjs.org
|
|
||||||
License: MIT
|
|
||||||
*/
|
|
||||||
(function(T,a){'use strict';function M(m,f){f=f||{};a.forEach(f,function(a,d){delete f[d]});for(var d in m)!m.hasOwnProperty(d)||"$"===d.charAt(0)&&"$"===d.charAt(1)||(f[d]=m[d]);return f}var B=a.$$minErr("$resource"),H=/^(\.[a-zA-Z_$@][0-9a-zA-Z_$@]*)+$/;a.module("ngResource",["ng"]).info({angularVersion:"1.7.9"}).provider("$resource",function(){var m=/^https?:\/\/\[[^\]]*][^/]*/,f=this;this.defaults={stripTrailingSlashes:!0,cancellable:!1,actions:{get:{method:"GET"},save:{method:"POST"},query:{method:"GET",
|
|
||||||
isArray:!0},remove:{method:"DELETE"},"delete":{method:"DELETE"}}};this.$get=["$http","$log","$q","$timeout",function(d,F,G,N){function C(a,d){this.template=a;this.defaults=n({},f.defaults,d);this.urlParams={}}var O=a.noop,r=a.forEach,n=a.extend,R=a.copy,P=a.isArray,D=a.isDefined,x=a.isFunction,I=a.isNumber,y=a.$$encodeUriQuery,S=a.$$encodeUriSegment;C.prototype={setUrlParams:function(a,d,f){var g=this,c=f||g.template,s,h,n="",b=g.urlParams=Object.create(null);r(c.split(/\W/),function(a){if("hasOwnProperty"===
|
|
||||||
a)throw B("badname");!/^\d+$/.test(a)&&a&&(new RegExp("(^|[^\\\\]):"+a+"(\\W|$)")).test(c)&&(b[a]={isQueryParamValue:(new RegExp("\\?.*=:"+a+"(?:\\W|$)")).test(c)})});c=c.replace(/\\:/g,":");c=c.replace(m,function(b){n=b;return""});d=d||{};r(g.urlParams,function(b,a){s=d.hasOwnProperty(a)?d[a]:g.defaults[a];D(s)&&null!==s?(h=b.isQueryParamValue?y(s,!0):S(s),c=c.replace(new RegExp(":"+a+"(\\W|$)","g"),function(b,a){return h+a})):c=c.replace(new RegExp("(/?):"+a+"(\\W|$)","g"),function(b,a,e){return"/"===
|
|
||||||
e.charAt(0)?e:a+e})});g.defaults.stripTrailingSlashes&&(c=c.replace(/\/+$/,"")||"/");c=c.replace(/\/\.(?=\w+($|\?))/,".");a.url=n+c.replace(/\/(\\|%5C)\./,"/.");r(d,function(b,c){g.urlParams[c]||(a.params=a.params||{},a.params[c]=b)})}};return function(m,y,z,g){function c(b,c){var d={};c=n({},y,c);r(c,function(c,f){x(c)&&(c=c(b));var e;if(c&&c.charAt&&"@"===c.charAt(0)){e=b;var k=c.substr(1);if(null==k||""===k||"hasOwnProperty"===k||!H.test("."+k))throw B("badmember",k);for(var k=k.split("."),h=0,
|
|
||||||
n=k.length;h<n&&a.isDefined(e);h++){var g=k[h];e=null!==e?e[g]:void 0}}else e=c;d[f]=e});return d}function s(b){return b.resource}function h(b){M(b||{},this)}var Q=new C(m,g);z=n({},f.defaults.actions,z);h.prototype.toJSON=function(){var b=n({},this);delete b.$promise;delete b.$resolved;delete b.$cancelRequest;return b};r(z,function(b,a){var f=!0===b.hasBody||!1!==b.hasBody&&/^(POST|PUT|PATCH)$/i.test(b.method),g=b.timeout,m=D(b.cancellable)?b.cancellable:Q.defaults.cancellable;g&&!I(g)&&(F.debug("ngResource:\n Only numeric values are allowed as `timeout`.\n Promises are not supported in $resource, because the same value would be used for multiple requests. If you are looking for a way to cancel requests, you should use the `cancellable` option."),
|
|
||||||
delete b.timeout,g=null);h[a]=function(e,k,J,y){function z(a){p.catch(O);null!==u&&u.resolve(a)}var K={},v,t,w;switch(arguments.length){case 4:w=y,t=J;case 3:case 2:if(x(k)){if(x(e)){t=e;w=k;break}t=k;w=J}else{K=e;v=k;t=J;break}case 1:x(e)?t=e:f?v=e:K=e;break;case 0:break;default:throw B("badargs",arguments.length);}var E=this instanceof h,l=E?v:b.isArray?[]:new h(v),q={},C=b.interceptor&&b.interceptor.request||void 0,D=b.interceptor&&b.interceptor.requestError||void 0,F=b.interceptor&&b.interceptor.response||
|
|
||||||
s,H=b.interceptor&&b.interceptor.responseError||G.reject,I=t?function(a){t(a,A.headers,A.status,A.statusText)}:void 0;w=w||void 0;var u,L,A;r(b,function(a,b){switch(b){default:q[b]=R(a);case "params":case "isArray":case "interceptor":case "cancellable":}});!E&&m&&(u=G.defer(),q.timeout=u.promise,g&&(L=N(u.resolve,g)));f&&(q.data=v);Q.setUrlParams(q,n({},c(v,b.params||{}),K),b.url);var p=G.resolve(q).then(C).catch(D).then(d),p=p.then(function(c){var e=c.data;if(e){if(P(e)!==!!b.isArray)throw B("badcfg",
|
|
||||||
a,b.isArray?"array":"object",P(e)?"array":"object",q.method,q.url);if(b.isArray)l.length=0,r(e,function(a){"object"===typeof a?l.push(new h(a)):l.push(a)});else{var d=l.$promise;M(e,l);l.$promise=d}}c.resource=l;A=c;return F(c)},function(a){a.resource=l;A=a;return H(a)}),p=p["finally"](function(){l.$resolved=!0;!E&&m&&(l.$cancelRequest=O,N.cancel(L),u=L=q.timeout=null)});p.then(I,w);return E?p:(l.$promise=p,l.$resolved=!1,m&&(l.$cancelRequest=z),l)};h.prototype["$"+a]=function(b,c,d){x(b)&&(d=c,c=
|
|
||||||
b,b={});b=h[a].call(this,b,this,c,d);return b.$promise||b}});return h}}]})})(window,window.angular);
|
|
||||||
//# sourceMappingURL=angular-resource.min.js.map
|
|
||||||
17
admin/static/js/angular-route.min.js
vendored
17
admin/static/js/angular-route.min.js
vendored
|
|
@ -1,17 +0,0 @@
|
||||||
/*
|
|
||||||
AngularJS v1.7.9
|
|
||||||
(c) 2010-2018 Google, Inc. http://angularjs.org
|
|
||||||
License: MIT
|
|
||||||
*/
|
|
||||||
(function(I,b){'use strict';function z(b,h){var d=[],c=b.replace(/([().])/g,"\\$1").replace(/(\/)?:(\w+)(\*\?|[?*])?/g,function(b,c,h,k){b="?"===k||"*?"===k;k="*"===k||"*?"===k;d.push({name:h,optional:b});c=c||"";return(b?"(?:"+c:c+"(?:")+(k?"(.+?)":"([^/]+)")+(b?"?)?":")")}).replace(/([/$*])/g,"\\$1");h.ignoreTrailingSlashes&&(c=c.replace(/\/+$/,"")+"/*");return{keys:d,regexp:new RegExp("^"+c+"(?:[?#]|$)",h.caseInsensitiveMatch?"i":"")}}function A(b){p&&b.get("$route")}function v(u,h,d){return{restrict:"ECA",
|
|
||||||
terminal:!0,priority:400,transclude:"element",link:function(c,f,g,l,k){function q(){r&&(d.cancel(r),r=null);m&&(m.$destroy(),m=null);s&&(r=d.leave(s),r.done(function(b){!1!==b&&(r=null)}),s=null)}function C(){var g=u.current&&u.current.locals;if(b.isDefined(g&&g.$template)){var g=c.$new(),l=u.current;s=k(g,function(g){d.enter(g,null,s||f).done(function(d){!1===d||!b.isDefined(w)||w&&!c.$eval(w)||h()});q()});m=l.scope=g;m.$emit("$viewContentLoaded");m.$eval(p)}else q()}var m,s,r,w=g.autoscroll,p=g.onload||
|
|
||||||
"";c.$on("$routeChangeSuccess",C);C()}}}function x(b,h,d){return{restrict:"ECA",priority:-400,link:function(c,f){var g=d.current,l=g.locals;f.html(l.$template);var k=b(f.contents());if(g.controller){l.$scope=c;var q=h(g.controller,l);g.controllerAs&&(c[g.controllerAs]=q);f.data("$ngControllerController",q);f.children().data("$ngControllerController",q)}c[g.resolveAs||"$resolve"]=l;k(c)}}}var D,E,F,G,y=b.module("ngRoute",[]).info({angularVersion:"1.7.9"}).provider("$route",function(){function u(d,
|
|
||||||
c){return b.extend(Object.create(d),c)}D=b.isArray;E=b.isObject;F=b.isDefined;G=b.noop;var h={};this.when=function(d,c){var f;f=void 0;if(D(c)){f=f||[];for(var g=0,l=c.length;g<l;g++)f[g]=c[g]}else if(E(c))for(g in f=f||{},c)if("$"!==g.charAt(0)||"$"!==g.charAt(1))f[g]=c[g];f=f||c;b.isUndefined(f.reloadOnUrl)&&(f.reloadOnUrl=!0);b.isUndefined(f.reloadOnSearch)&&(f.reloadOnSearch=!0);b.isUndefined(f.caseInsensitiveMatch)&&(f.caseInsensitiveMatch=this.caseInsensitiveMatch);h[d]=b.extend(f,{originalPath:d},
|
|
||||||
d&&z(d,f));d&&(g="/"===d[d.length-1]?d.substr(0,d.length-1):d+"/",h[g]=b.extend({originalPath:d,redirectTo:d},z(g,f)));return this};this.caseInsensitiveMatch=!1;this.otherwise=function(b){"string"===typeof b&&(b={redirectTo:b});this.when(null,b);return this};p=!0;this.eagerInstantiationEnabled=function(b){return F(b)?(p=b,this):p};this.$get=["$rootScope","$location","$routeParams","$q","$injector","$templateRequest","$sce","$browser",function(d,c,f,g,l,k,q,p){function m(a){var e=t.current;n=A();(x=
|
|
||||||
!B&&n&&e&&n.$$route===e.$$route&&(!n.reloadOnUrl||!n.reloadOnSearch&&b.equals(n.pathParams,e.pathParams)))||!e&&!n||d.$broadcast("$routeChangeStart",n,e).defaultPrevented&&a&&a.preventDefault()}function s(){var a=t.current,e=n;if(x)a.params=e.params,b.copy(a.params,f),d.$broadcast("$routeUpdate",a);else if(e||a){B=!1;t.current=e;var c=g.resolve(e);p.$$incOutstandingRequestCount("$route");c.then(r).then(w).then(function(g){return g&&c.then(y).then(function(c){e===t.current&&(e&&(e.locals=c,b.copy(e.params,
|
|
||||||
f)),d.$broadcast("$routeChangeSuccess",e,a))})}).catch(function(b){e===t.current&&d.$broadcast("$routeChangeError",e,a,b)}).finally(function(){p.$$completeOutstandingRequest(G,"$route")})}}function r(a){var e={route:a,hasRedirection:!1};if(a)if(a.redirectTo)if(b.isString(a.redirectTo))e.path=v(a.redirectTo,a.params),e.search=a.params,e.hasRedirection=!0;else{var d=c.path(),f=c.search();a=a.redirectTo(a.pathParams,d,f);b.isDefined(a)&&(e.url=a,e.hasRedirection=!0)}else if(a.resolveRedirectTo)return g.resolve(l.invoke(a.resolveRedirectTo)).then(function(a){b.isDefined(a)&&
|
|
||||||
(e.url=a,e.hasRedirection=!0);return e});return e}function w(a){var b=!0;if(a.route!==t.current)b=!1;else if(a.hasRedirection){var g=c.url(),d=a.url;d?c.url(d).replace():d=c.path(a.path).search(a.search).replace().url();d!==g&&(b=!1)}return b}function y(a){if(a){var e=b.extend({},a.resolve);b.forEach(e,function(a,c){e[c]=b.isString(a)?l.get(a):l.invoke(a,null,null,c)});a=z(a);b.isDefined(a)&&(e.$template=a);return g.all(e)}}function z(a){var e,c;b.isDefined(e=a.template)?b.isFunction(e)&&(e=e(a.params)):
|
|
||||||
b.isDefined(c=a.templateUrl)&&(b.isFunction(c)&&(c=c(a.params)),b.isDefined(c)&&(a.loadedTemplateUrl=q.valueOf(c),e=k(c)));return e}function A(){var a,e;b.forEach(h,function(d,g){var f;if(f=!e){var h=c.path();f=d.keys;var l={};if(d.regexp)if(h=d.regexp.exec(h)){for(var k=1,p=h.length;k<p;++k){var m=f[k-1],n=h[k];m&&n&&(l[m.name]=n)}f=l}else f=null;else f=null;f=a=f}f&&(e=u(d,{params:b.extend({},c.search(),a),pathParams:a}),e.$$route=d)});return e||h[null]&&u(h[null],{params:{},pathParams:{}})}function v(a,
|
|
||||||
c){var d=[];b.forEach((a||"").split(":"),function(a,b){if(0===b)d.push(a);else{var f=a.match(/(\w+)(?:[?*])?(.*)/),g=f[1];d.push(c[g]);d.push(f[2]||"");delete c[g]}});return d.join("")}var B=!1,n,x,t={routes:h,reload:function(){B=!0;var a={defaultPrevented:!1,preventDefault:function(){this.defaultPrevented=!0;B=!1}};d.$evalAsync(function(){m(a);a.defaultPrevented||s()})},updateParams:function(a){if(this.current&&this.current.$$route)a=b.extend({},this.current.params,a),c.path(v(this.current.$$route.originalPath,
|
|
||||||
a)),c.search(a);else throw H("norout");}};d.$on("$locationChangeStart",m);d.$on("$locationChangeSuccess",s);return t}]}).run(A),H=b.$$minErr("ngRoute"),p;A.$inject=["$injector"];y.provider("$routeParams",function(){this.$get=function(){return{}}});y.directive("ngView",v);y.directive("ngView",x);v.$inject=["$route","$anchorScroll","$animate"];x.$inject=["$compile","$controller","$route"]})(window,window.angular);
|
|
||||||
//# sourceMappingURL=angular-route.min.js.map
|
|
||||||
18
admin/static/js/angular-sanitize.min.js
vendored
18
admin/static/js/angular-sanitize.min.js
vendored
|
|
@ -1,18 +0,0 @@
|
||||||
/*
|
|
||||||
AngularJS v1.7.9
|
|
||||||
(c) 2010-2018 Google, Inc. http://angularjs.org
|
|
||||||
License: MIT
|
|
||||||
*/
|
|
||||||
(function(s,c){'use strict';function P(c){var h=[];C(h,E).chars(c);return h.join("")}var D=c.$$minErr("$sanitize"),F,h,G,H,I,q,E,J,K,C;c.module("ngSanitize",[]).provider("$sanitize",function(){function f(a,e){return B(a.split(","),e)}function B(a,e){var d={},b;for(b=0;b<a.length;b++)d[e?q(a[b]):a[b]]=!0;return d}function t(a,e){e&&e.length&&h(a,B(e))}function Q(a){for(var e={},d=0,b=a.length;d<b;d++){var k=a[d];e[k.name]=k.value}return e}function L(a){return a.replace(/&/g,"&").replace(z,function(a){var d=
|
|
||||||
a.charCodeAt(0);a=a.charCodeAt(1);return"&#"+(1024*(d-55296)+(a-56320)+65536)+";"}).replace(u,function(a){return"&#"+a.charCodeAt(0)+";"}).replace(/</g,"<").replace(/>/g,">")}function A(a){for(;a;){if(a.nodeType===s.Node.ELEMENT_NODE)for(var e=a.attributes,d=0,b=e.length;d<b;d++){var k=e[d],g=k.name.toLowerCase();if("xmlns:ns1"===g||0===g.lastIndexOf("ns1:",0))a.removeAttributeNode(k),d--,b--}(e=a.firstChild)&&A(e);a=v("nextSibling",a)}}function v(a,e){var d=e[a];if(d&&J.call(e,d))throw D("elclob",
|
|
||||||
e.outerHTML||e.outerText);return d}var y=!1,g=!1;this.$get=["$$sanitizeUri",function(a){y=!0;g&&h(m,l);return function(e){var d=[];K(e,C(d,function(b,d){return!/^unsafe:/.test(a(b,d))}));return d.join("")}}];this.enableSvg=function(a){return I(a)?(g=a,this):g};this.addValidElements=function(a){y||(H(a)&&(a={htmlElements:a}),t(l,a.svgElements),t(r,a.htmlVoidElements),t(m,a.htmlVoidElements),t(m,a.htmlElements));return this};this.addValidAttrs=function(a){y||h(M,B(a,!0));return this};F=c.bind;h=c.extend;
|
|
||||||
G=c.forEach;H=c.isArray;I=c.isDefined;q=c.$$lowercase;E=c.noop;K=function(a,e){null===a||void 0===a?a="":"string"!==typeof a&&(a=""+a);var d=N(a);if(!d)return"";var b=5;do{if(0===b)throw D("uinput");b--;a=d.innerHTML;d=N(a)}while(a!==d.innerHTML);for(b=d.firstChild;b;){switch(b.nodeType){case 1:e.start(b.nodeName.toLowerCase(),Q(b.attributes));break;case 3:e.chars(b.textContent)}var k;if(!(k=b.firstChild)&&(1===b.nodeType&&e.end(b.nodeName.toLowerCase()),k=v("nextSibling",b),!k))for(;null==k;){b=
|
|
||||||
v("parentNode",b);if(b===d)break;k=v("nextSibling",b);1===b.nodeType&&e.end(b.nodeName.toLowerCase())}b=k}for(;b=d.firstChild;)d.removeChild(b)};C=function(a,e){var d=!1,b=F(a,a.push);return{start:function(a,g){a=q(a);!d&&w[a]&&(d=a);d||!0!==m[a]||(b("<"),b(a),G(g,function(d,g){var c=q(g),f="img"===a&&"src"===c||"background"===c;!0!==M[c]||!0===O[c]&&!e(d,f)||(b(" "),b(g),b('="'),b(L(d)),b('"'))}),b(">"))},end:function(a){a=q(a);d||!0!==m[a]||!0===r[a]||(b("</"),b(a),b(">"));a==d&&(d=!1)},chars:function(a){d||
|
|
||||||
b(L(a))}}};J=s.Node.prototype.contains||function(a){return!!(this.compareDocumentPosition(a)&16)};var z=/[\uD800-\uDBFF][\uDC00-\uDFFF]/g,u=/([^#-~ |!])/g,r=f("area,br,col,hr,img,wbr"),x=f("colgroup,dd,dt,li,p,tbody,td,tfoot,th,thead,tr"),p=f("rp,rt"),n=h({},p,x),x=h({},x,f("address,article,aside,blockquote,caption,center,del,dir,div,dl,figure,figcaption,footer,h1,h2,h3,h4,h5,h6,header,hgroup,hr,ins,map,menu,nav,ol,pre,section,table,ul")),p=h({},p,f("a,abbr,acronym,b,bdi,bdo,big,br,cite,code,del,dfn,em,font,i,img,ins,kbd,label,map,mark,q,ruby,rp,rt,s,samp,small,span,strike,strong,sub,sup,time,tt,u,var")),
|
|
||||||
l=f("circle,defs,desc,ellipse,font-face,font-face-name,font-face-src,g,glyph,hkern,image,linearGradient,line,marker,metadata,missing-glyph,mpath,path,polygon,polyline,radialGradient,rect,stop,svg,switch,text,title,tspan"),w=f("script,style"),m=h({},r,x,p,n),O=f("background,cite,href,longdesc,src,xlink:href,xml:base"),n=f("abbr,align,alt,axis,bgcolor,border,cellpadding,cellspacing,class,clear,color,cols,colspan,compact,coords,dir,face,headers,height,hreflang,hspace,ismap,lang,language,nohref,nowrap,rel,rev,rows,rowspan,rules,scope,scrolling,shape,size,span,start,summary,tabindex,target,title,type,valign,value,vspace,width"),
|
|
||||||
p=f("accent-height,accumulate,additive,alphabetic,arabic-form,ascent,baseProfile,bbox,begin,by,calcMode,cap-height,class,color,color-rendering,content,cx,cy,d,dx,dy,descent,display,dur,end,fill,fill-rule,font-family,font-size,font-stretch,font-style,font-variant,font-weight,from,fx,fy,g1,g2,glyph-name,gradientUnits,hanging,height,horiz-adv-x,horiz-origin-x,ideographic,k,keyPoints,keySplines,keyTimes,lang,marker-end,marker-mid,marker-start,markerHeight,markerUnits,markerWidth,mathematical,max,min,offset,opacity,orient,origin,overline-position,overline-thickness,panose-1,path,pathLength,points,preserveAspectRatio,r,refX,refY,repeatCount,repeatDur,requiredExtensions,requiredFeatures,restart,rotate,rx,ry,slope,stemh,stemv,stop-color,stop-opacity,strikethrough-position,strikethrough-thickness,stroke,stroke-dasharray,stroke-dashoffset,stroke-linecap,stroke-linejoin,stroke-miterlimit,stroke-opacity,stroke-width,systemLanguage,target,text-anchor,to,transform,type,u1,u2,underline-position,underline-thickness,unicode,unicode-range,units-per-em,values,version,viewBox,visibility,width,widths,x,x-height,x1,x2,xlink:actuate,xlink:arcrole,xlink:role,xlink:show,xlink:title,xlink:type,xml:base,xml:lang,xml:space,xmlns,xmlns:xlink,y,y1,y2,zoomAndPan",
|
|
||||||
!0),M=h({},O,p,n),N=function(a,e){function d(b){b="<remove></remove>"+b;try{var d=(new a.DOMParser).parseFromString(b,"text/html").body;d.firstChild.remove();return d}catch(e){}}function b(a){c.innerHTML=a;e.documentMode&&A(c);return c}var g;if(e&&e.implementation)g=e.implementation.createHTMLDocument("inert");else throw D("noinert");var c=(g.documentElement||g.getDocumentElement()).querySelector("body");c.innerHTML='<svg><g onload="this.parentNode.remove()"></g></svg>';return c.querySelector("svg")?
|
|
||||||
(c.innerHTML='<svg><p><style><img src="</style><img src=x onerror=alert(1)//">',c.querySelector("svg img")?d:b):function(b){b="<remove></remove>"+b;try{b=encodeURI(b)}catch(d){return}var e=new a.XMLHttpRequest;e.responseType="document";e.open("GET","data:text/html;charset=utf-8,"+b,!1);e.send(null);b=e.response.body;b.firstChild.remove();return b}}(s,s.document)}).info({angularVersion:"1.7.9"});c.module("ngSanitize").filter("linky",["$sanitize",function(f){var h=/((s?ftp|https?):\/\/|(www\.)|(mailto:)?[A-Za-z0-9._%+-]+@)\S*[^\s.;,(){}<>"\u201d\u2019]/i,
|
|
||||||
t=/^mailto:/i,q=c.$$minErr("linky"),s=c.isDefined,A=c.isFunction,v=c.isObject,y=c.isString;return function(c,z,u){function r(c){c&&l.push(P(c))}function x(c,g){var f,a=p(c);l.push("<a ");for(f in a)l.push(f+'="'+a[f]+'" ');!s(z)||"target"in a||l.push('target="',z,'" ');l.push('href="',c.replace(/"/g,"""),'">');r(g);l.push("</a>")}if(null==c||""===c)return c;if(!y(c))throw q("notstring",c);for(var p=A(u)?u:v(u)?function(){return u}:function(){return{}},n=c,l=[],w,m;c=n.match(h);)w=c[0],c[2]||
|
|
||||||
c[4]||(w=(c[3]?"http://":"mailto:")+w),m=c.index,r(n.substr(0,m)),x(w,c[0].replace(t,"")),n=n.substring(m+c[0].length);r(n);return f(l.join(""))}}])})(window,window.angular);
|
|
||||||
//# sourceMappingURL=angular-sanitize.min.js.map
|
|
||||||
350
admin/static/js/angular.min.js
vendored
350
admin/static/js/angular.min.js
vendored
|
|
@ -1,350 +0,0 @@
|
||||||
/*
|
|
||||||
AngularJS v1.7.9
|
|
||||||
(c) 2010-2018 Google, Inc. http://angularjs.org
|
|
||||||
License: MIT
|
|
||||||
*/
|
|
||||||
(function(C){'use strict';function re(a){if(D(a))w(a.objectMaxDepth)&&(Wb.objectMaxDepth=Xb(a.objectMaxDepth)?a.objectMaxDepth:NaN),w(a.urlErrorParamsEnabled)&&Ga(a.urlErrorParamsEnabled)&&(Wb.urlErrorParamsEnabled=a.urlErrorParamsEnabled);else return Wb}function Xb(a){return W(a)&&0<a}function F(a,b){b=b||Error;return function(){var d=arguments[0],c;c="["+(a?a+":":"")+d+"] http://errors.angularjs.org/1.7.9/"+(a?a+"/":"")+d;for(d=1;d<arguments.length;d++){c=c+(1==d?"?":"&")+"p"+(d-1)+"=";var e=encodeURIComponent,
|
|
||||||
f;f=arguments[d];f="function"==typeof f?f.toString().replace(/ \{[\s\S]*$/,""):"undefined"==typeof f?"undefined":"string"!=typeof f?JSON.stringify(f):f;c+=e(f)}return new b(c)}}function ya(a){if(null==a||$a(a))return!1;if(H(a)||A(a)||x&&a instanceof x)return!0;var b="length"in Object(a)&&a.length;return W(b)&&(0<=b&&b-1 in a||"function"===typeof a.item)}function r(a,b,d){var c,e;if(a)if(B(a))for(c in a)"prototype"!==c&&"length"!==c&&"name"!==c&&a.hasOwnProperty(c)&&b.call(d,a[c],c,a);else if(H(a)||
|
|
||||||
ya(a)){var f="object"!==typeof a;c=0;for(e=a.length;c<e;c++)(f||c in a)&&b.call(d,a[c],c,a)}else if(a.forEach&&a.forEach!==r)a.forEach(b,d,a);else if(Nc(a))for(c in a)b.call(d,a[c],c,a);else if("function"===typeof a.hasOwnProperty)for(c in a)a.hasOwnProperty(c)&&b.call(d,a[c],c,a);else for(c in a)ta.call(a,c)&&b.call(d,a[c],c,a);return a}function Oc(a,b,d){for(var c=Object.keys(a).sort(),e=0;e<c.length;e++)b.call(d,a[c[e]],c[e]);return c}function Yb(a){return function(b,d){a(d,b)}}function se(){return++pb}
|
|
||||||
function Zb(a,b,d){for(var c=a.$$hashKey,e=0,f=b.length;e<f;++e){var g=b[e];if(D(g)||B(g))for(var k=Object.keys(g),h=0,l=k.length;h<l;h++){var m=k[h],p=g[m];d&&D(p)?ha(p)?a[m]=new Date(p.valueOf()):ab(p)?a[m]=new RegExp(p):p.nodeName?a[m]=p.cloneNode(!0):$b(p)?a[m]=p.clone():"__proto__"!==m&&(D(a[m])||(a[m]=H(p)?[]:{}),Zb(a[m],[p],!0)):a[m]=p}}c?a.$$hashKey=c:delete a.$$hashKey;return a}function S(a){return Zb(a,Ha.call(arguments,1),!1)}function te(a){return Zb(a,Ha.call(arguments,1),!0)}function fa(a){return parseInt(a,
|
|
||||||
10)}function ac(a,b){return S(Object.create(a),b)}function E(){}function Ta(a){return a}function ia(a){return function(){return a}}function bc(a){return B(a.toString)&&a.toString!==la}function z(a){return"undefined"===typeof a}function w(a){return"undefined"!==typeof a}function D(a){return null!==a&&"object"===typeof a}function Nc(a){return null!==a&&"object"===typeof a&&!Pc(a)}function A(a){return"string"===typeof a}function W(a){return"number"===typeof a}function ha(a){return"[object Date]"===la.call(a)}
|
|
||||||
function H(a){return Array.isArray(a)||a instanceof Array}function cc(a){switch(la.call(a)){case "[object Error]":return!0;case "[object Exception]":return!0;case "[object DOMException]":return!0;default:return a instanceof Error}}function B(a){return"function"===typeof a}function ab(a){return"[object RegExp]"===la.call(a)}function $a(a){return a&&a.window===a}function bb(a){return a&&a.$evalAsync&&a.$watch}function Ga(a){return"boolean"===typeof a}function ue(a){return a&&W(a.length)&&ve.test(la.call(a))}
|
|
||||||
function $b(a){return!(!a||!(a.nodeName||a.prop&&a.attr&&a.find))}function we(a){var b={};a=a.split(",");var d;for(d=0;d<a.length;d++)b[a[d]]=!0;return b}function ua(a){return K(a.nodeName||a[0]&&a[0].nodeName)}function cb(a,b){var d=a.indexOf(b);0<=d&&a.splice(d,1);return d}function Ia(a,b,d){function c(a,b,c){c--;if(0>c)return"...";var d=b.$$hashKey,f;if(H(a)){f=0;for(var g=a.length;f<g;f++)b.push(e(a[f],c))}else if(Nc(a))for(f in a)b[f]=e(a[f],c);else if(a&&"function"===typeof a.hasOwnProperty)for(f in a)a.hasOwnProperty(f)&&
|
|
||||||
(b[f]=e(a[f],c));else for(f in a)ta.call(a,f)&&(b[f]=e(a[f],c));d?b.$$hashKey=d:delete b.$$hashKey;return b}function e(a,b){if(!D(a))return a;var d=g.indexOf(a);if(-1!==d)return k[d];if($a(a)||bb(a))throw pa("cpws");var d=!1,e=f(a);void 0===e&&(e=H(a)?[]:Object.create(Pc(a)),d=!0);g.push(a);k.push(e);return d?c(a,e,b):e}function f(a){switch(la.call(a)){case "[object Int8Array]":case "[object Int16Array]":case "[object Int32Array]":case "[object Float32Array]":case "[object Float64Array]":case "[object Uint8Array]":case "[object Uint8ClampedArray]":case "[object Uint16Array]":case "[object Uint32Array]":return new a.constructor(e(a.buffer),
|
|
||||||
a.byteOffset,a.length);case "[object ArrayBuffer]":if(!a.slice){var b=new ArrayBuffer(a.byteLength);(new Uint8Array(b)).set(new Uint8Array(a));return b}return a.slice(0);case "[object Boolean]":case "[object Number]":case "[object String]":case "[object Date]":return new a.constructor(a.valueOf());case "[object RegExp]":return b=new RegExp(a.source,a.toString().match(/[^/]*$/)[0]),b.lastIndex=a.lastIndex,b;case "[object Blob]":return new a.constructor([a],{type:a.type})}if(B(a.cloneNode))return a.cloneNode(!0)}
|
|
||||||
var g=[],k=[];d=Xb(d)?d:NaN;if(b){if(ue(b)||"[object ArrayBuffer]"===la.call(b))throw pa("cpta");if(a===b)throw pa("cpi");H(b)?b.length=0:r(b,function(a,c){"$$hashKey"!==c&&delete b[c]});g.push(a);k.push(b);return c(a,b,d)}return e(a,d)}function dc(a,b){return a===b||a!==a&&b!==b}function va(a,b){if(a===b)return!0;if(null===a||null===b)return!1;if(a!==a&&b!==b)return!0;var d=typeof a,c;if(d===typeof b&&"object"===d)if(H(a)){if(!H(b))return!1;if((d=a.length)===b.length){for(c=0;c<d;c++)if(!va(a[c],
|
|
||||||
b[c]))return!1;return!0}}else{if(ha(a))return ha(b)?dc(a.getTime(),b.getTime()):!1;if(ab(a))return ab(b)?a.toString()===b.toString():!1;if(bb(a)||bb(b)||$a(a)||$a(b)||H(b)||ha(b)||ab(b))return!1;d=T();for(c in a)if("$"!==c.charAt(0)&&!B(a[c])){if(!va(a[c],b[c]))return!1;d[c]=!0}for(c in b)if(!(c in d)&&"$"!==c.charAt(0)&&w(b[c])&&!B(b[c]))return!1;return!0}return!1}function db(a,b,d){return a.concat(Ha.call(b,d))}function Va(a,b){var d=2<arguments.length?Ha.call(arguments,2):[];return!B(b)||b instanceof
|
|
||||||
RegExp?b:d.length?function(){return arguments.length?b.apply(a,db(d,arguments,0)):b.apply(a,d)}:function(){return arguments.length?b.apply(a,arguments):b.call(a)}}function Qc(a,b){var d=b;"string"===typeof a&&"$"===a.charAt(0)&&"$"===a.charAt(1)?d=void 0:$a(b)?d="$WINDOW":b&&C.document===b?d="$DOCUMENT":bb(b)&&(d="$SCOPE");return d}function eb(a,b){if(!z(a))return W(b)||(b=b?2:null),JSON.stringify(a,Qc,b)}function Rc(a){return A(a)?JSON.parse(a):a}function ec(a,b){a=a.replace(xe,"");var d=Date.parse("Jan 01, 1970 00:00:00 "+
|
|
||||||
a)/6E4;return X(d)?b:d}function Sc(a,b){a=new Date(a.getTime());a.setMinutes(a.getMinutes()+b);return a}function fc(a,b,d){d=d?-1:1;var c=a.getTimezoneOffset();b=ec(b,c);return Sc(a,d*(b-c))}function za(a){a=x(a).clone().empty();var b=x("<div></div>").append(a).html();try{return a[0].nodeType===Pa?K(b):b.match(/^(<[^>]+>)/)[1].replace(/^<([\w-]+)/,function(a,b){return"<"+K(b)})}catch(d){return K(b)}}function Tc(a){try{return decodeURIComponent(a)}catch(b){}}function gc(a){var b={};r((a||"").split("&"),
|
|
||||||
function(a){var c,e,f;a&&(e=a=a.replace(/\+/g,"%20"),c=a.indexOf("="),-1!==c&&(e=a.substring(0,c),f=a.substring(c+1)),e=Tc(e),w(e)&&(f=w(f)?Tc(f):!0,ta.call(b,e)?H(b[e])?b[e].push(f):b[e]=[b[e],f]:b[e]=f))});return b}function ye(a){var b=[];r(a,function(a,c){H(a)?r(a,function(a){b.push(ba(c,!0)+(!0===a?"":"="+ba(a,!0)))}):b.push(ba(c,!0)+(!0===a?"":"="+ba(a,!0)))});return b.length?b.join("&"):""}function hc(a){return ba(a,!0).replace(/%26/gi,"&").replace(/%3D/gi,"=").replace(/%2B/gi,"+")}function ba(a,
|
|
||||||
b){return encodeURIComponent(a).replace(/%40/gi,"@").replace(/%3A/gi,":").replace(/%24/g,"$").replace(/%2C/gi,",").replace(/%3B/gi,";").replace(/%20/g,b?"%20":"+")}function ze(a,b){var d,c,e=Qa.length;for(c=0;c<e;++c)if(d=Qa[c]+b,A(d=a.getAttribute(d)))return d;return null}function Ae(a,b){var d,c,e={};r(Qa,function(b){b+="app";!d&&a.hasAttribute&&a.hasAttribute(b)&&(d=a,c=a.getAttribute(b))});r(Qa,function(b){b+="app";var e;!d&&(e=a.querySelector("["+b.replace(":","\\:")+"]"))&&(d=e,c=e.getAttribute(b))});
|
|
||||||
d&&(Be?(e.strictDi=null!==ze(d,"strict-di"),b(d,c?[c]:[],e)):C.console.error("AngularJS: disabling automatic bootstrap. <script> protocol indicates an extension, document.location.href does not match."))}function Uc(a,b,d){D(d)||(d={});d=S({strictDi:!1},d);var c=function(){a=x(a);if(a.injector()){var c=a[0]===C.document?"document":za(a);throw pa("btstrpd",c.replace(/</,"<").replace(/>/,">"));}b=b||[];b.unshift(["$provide",function(b){b.value("$rootElement",a)}]);d.debugInfoEnabled&&b.push(["$compileProvider",
|
|
||||||
function(a){a.debugInfoEnabled(!0)}]);b.unshift("ng");c=fb(b,d.strictDi);c.invoke(["$rootScope","$rootElement","$compile","$injector",function(a,b,c,d){a.$apply(function(){b.data("$injector",d);c(b)(a)})}]);return c},e=/^NG_ENABLE_DEBUG_INFO!/,f=/^NG_DEFER_BOOTSTRAP!/;C&&e.test(C.name)&&(d.debugInfoEnabled=!0,C.name=C.name.replace(e,""));if(C&&!f.test(C.name))return c();C.name=C.name.replace(f,"");ca.resumeBootstrap=function(a){r(a,function(a){b.push(a)});return c()};B(ca.resumeDeferredBootstrap)&&
|
|
||||||
ca.resumeDeferredBootstrap()}function Ce(){C.name="NG_ENABLE_DEBUG_INFO!"+C.name;C.location.reload()}function De(a){a=ca.element(a).injector();if(!a)throw pa("test");return a.get("$$testability")}function Vc(a,b){b=b||"_";return a.replace(Ee,function(a,c){return(c?b:"")+a.toLowerCase()})}function Fe(){var a;if(!Wc){var b=qb();(rb=z(b)?C.jQuery:b?C[b]:void 0)&&rb.fn.on?(x=rb,S(rb.fn,{scope:Wa.scope,isolateScope:Wa.isolateScope,controller:Wa.controller,injector:Wa.injector,inheritedData:Wa.inheritedData})):
|
|
||||||
x=Y;a=x.cleanData;x.cleanData=function(b){for(var c,e=0,f;null!=(f=b[e]);e++)(c=(x._data(f)||{}).events)&&c.$destroy&&x(f).triggerHandler("$destroy");a(b)};ca.element=x;Wc=!0}}function gb(a,b,d){if(!a)throw pa("areq",b||"?",d||"required");return a}function sb(a,b,d){d&&H(a)&&(a=a[a.length-1]);gb(B(a),b,"not a function, got "+(a&&"object"===typeof a?a.constructor.name||"Object":typeof a));return a}function Ja(a,b){if("hasOwnProperty"===a)throw pa("badname",b);}function Ge(a,b,d){if(!b)return a;b=b.split(".");
|
|
||||||
for(var c,e=a,f=b.length,g=0;g<f;g++)c=b[g],a&&(a=(e=a)[c]);return!d&&B(a)?Va(e,a):a}function tb(a){for(var b=a[0],d=a[a.length-1],c,e=1;b!==d&&(b=b.nextSibling);e++)if(c||a[e]!==b)c||(c=x(Ha.call(a,0,e))),c.push(b);return c||a}function T(){return Object.create(null)}function ic(a){if(null==a)return"";switch(typeof a){case "string":break;case "number":a=""+a;break;default:a=!bc(a)||H(a)||ha(a)?eb(a):a.toString()}return a}function He(a){function b(a,b,c){return a[b]||(a[b]=c())}var d=F("$injector"),
|
|
||||||
c=F("ng");a=b(a,"angular",Object);a.$$minErr=a.$$minErr||F;return b(a,"module",function(){var a={};return function(f,g,k){var h={};if("hasOwnProperty"===f)throw c("badname","module");g&&a.hasOwnProperty(f)&&(a[f]=null);return b(a,f,function(){function a(b,c,d,f){f||(f=e);return function(){f[d||"push"]([b,c,arguments]);return t}}function b(a,c,d){d||(d=e);return function(b,e){e&&B(e)&&(e.$$moduleName=f);d.push([a,c,arguments]);return t}}if(!g)throw d("nomod",f);var e=[],n=[],s=[],G=a("$injector","invoke",
|
|
||||||
"push",n),t={_invokeQueue:e,_configBlocks:n,_runBlocks:s,info:function(a){if(w(a)){if(!D(a))throw c("aobj","value");h=a;return this}return h},requires:g,name:f,provider:b("$provide","provider"),factory:b("$provide","factory"),service:b("$provide","service"),value:a("$provide","value"),constant:a("$provide","constant","unshift"),decorator:b("$provide","decorator",n),animation:b("$animateProvider","register"),filter:b("$filterProvider","register"),controller:b("$controllerProvider","register"),directive:b("$compileProvider",
|
|
||||||
"directive"),component:b("$compileProvider","component"),config:G,run:function(a){s.push(a);return this}};k&&G(k);return t})}})}function ja(a,b){if(H(a)){b=b||[];for(var d=0,c=a.length;d<c;d++)b[d]=a[d]}else if(D(a))for(d in b=b||{},a)if("$"!==d.charAt(0)||"$"!==d.charAt(1))b[d]=a[d];return b||a}function Ie(a,b){var d=[];Xb(b)&&(a=ca.copy(a,null,b));return JSON.stringify(a,function(a,b){b=Qc(a,b);if(D(b)){if(0<=d.indexOf(b))return"...";d.push(b)}return b})}function Je(a){S(a,{errorHandlingConfig:re,
|
|
||||||
bootstrap:Uc,copy:Ia,extend:S,merge:te,equals:va,element:x,forEach:r,injector:fb,noop:E,bind:Va,toJson:eb,fromJson:Rc,identity:Ta,isUndefined:z,isDefined:w,isString:A,isFunction:B,isObject:D,isNumber:W,isElement:$b,isArray:H,version:Ke,isDate:ha,callbacks:{$$counter:0},getTestability:De,reloadWithDebugInfo:Ce,$$minErr:F,$$csp:Aa,$$encodeUriSegment:hc,$$encodeUriQuery:ba,$$lowercase:K,$$stringify:ic,$$uppercase:ub});kc=He(C);kc("ng",["ngLocale"],["$provide",function(a){a.provider({$$sanitizeUri:Le});
|
|
||||||
a.provider("$compile",Xc).directive({a:Me,input:Yc,textarea:Yc,form:Ne,script:Oe,select:Pe,option:Qe,ngBind:Re,ngBindHtml:Se,ngBindTemplate:Te,ngClass:Ue,ngClassEven:Ve,ngClassOdd:We,ngCloak:Xe,ngController:Ye,ngForm:Ze,ngHide:$e,ngIf:af,ngInclude:bf,ngInit:cf,ngNonBindable:df,ngPluralize:ef,ngRef:ff,ngRepeat:gf,ngShow:hf,ngStyle:jf,ngSwitch:kf,ngSwitchWhen:lf,ngSwitchDefault:mf,ngOptions:nf,ngTransclude:of,ngModel:pf,ngList:qf,ngChange:rf,pattern:Zc,ngPattern:Zc,required:$c,ngRequired:$c,minlength:ad,
|
|
||||||
ngMinlength:ad,maxlength:bd,ngMaxlength:bd,ngValue:sf,ngModelOptions:tf}).directive({ngInclude:uf,input:vf}).directive(vb).directive(cd);a.provider({$anchorScroll:wf,$animate:xf,$animateCss:yf,$$animateJs:zf,$$animateQueue:Af,$$AnimateRunner:Bf,$$animateAsyncRun:Cf,$browser:Df,$cacheFactory:Ef,$controller:Ff,$document:Gf,$$isDocumentHidden:Hf,$exceptionHandler:If,$filter:dd,$$forceReflow:Jf,$interpolate:Kf,$interval:Lf,$$intervalFactory:Mf,$http:Nf,$httpParamSerializer:Of,$httpParamSerializerJQLike:Pf,
|
|
||||||
$httpBackend:Qf,$xhrFactory:Rf,$jsonpCallbacks:Sf,$location:Tf,$log:Uf,$parse:Vf,$rootScope:Wf,$q:Xf,$$q:Yf,$sce:Zf,$sceDelegate:$f,$sniffer:ag,$$taskTrackerFactory:bg,$templateCache:cg,$templateRequest:dg,$$testability:eg,$timeout:fg,$window:gg,$$rAF:hg,$$jqLite:ig,$$Map:jg,$$cookieReader:kg})}]).info({angularVersion:"1.7.9"})}function wb(a,b){return b.toUpperCase()}function xb(a){return a.replace(lg,wb)}function lc(a){a=a.nodeType;return 1===a||!a||9===a}function ed(a,b){var d,c,e=b.createDocumentFragment(),
|
|
||||||
f=[];if(mc.test(a)){d=e.appendChild(b.createElement("div"));c=(mg.exec(a)||["",""])[1].toLowerCase();c=oa[c]||oa._default;d.innerHTML=c[1]+a.replace(ng,"<$1></$2>")+c[2];for(c=c[0];c--;)d=d.lastChild;f=db(f,d.childNodes);d=e.firstChild;d.textContent=""}else f.push(b.createTextNode(a));e.textContent="";e.innerHTML="";r(f,function(a){e.appendChild(a)});return e}function Y(a){if(a instanceof Y)return a;var b;A(a)&&(a=U(a),b=!0);if(!(this instanceof Y)){if(b&&"<"!==a.charAt(0))throw nc("nosel");return new Y(a)}if(b){b=
|
|
||||||
C.document;var d;a=(d=og.exec(a))?[b.createElement(d[1])]:(d=ed(a,b))?d.childNodes:[];oc(this,a)}else B(a)?fd(a):oc(this,a)}function pc(a){return a.cloneNode(!0)}function yb(a,b){!b&&lc(a)&&x.cleanData([a]);a.querySelectorAll&&x.cleanData(a.querySelectorAll("*"))}function gd(a){for(var b in a)return!1;return!0}function hd(a){var b=a.ng339,d=b&&Ka[b],c=d&&d.events,d=d&&d.data;d&&!gd(d)||c&&!gd(c)||(delete Ka[b],a.ng339=void 0)}function id(a,b,d,c){if(w(c))throw nc("offargs");var e=(c=zb(a))&&c.events,
|
|
||||||
f=c&&c.handle;if(f){if(b){var g=function(b){var c=e[b];w(d)&&cb(c||[],d);w(d)&&c&&0<c.length||(a.removeEventListener(b,f),delete e[b])};r(b.split(" "),function(a){g(a);Ab[a]&&g(Ab[a])})}else for(b in e)"$destroy"!==b&&a.removeEventListener(b,f),delete e[b];hd(a)}}function qc(a,b){var d=a.ng339;if(d=d&&Ka[d])b?delete d.data[b]:d.data={},hd(a)}function zb(a,b){var d=a.ng339,d=d&&Ka[d];b&&!d&&(a.ng339=d=++pg,d=Ka[d]={events:{},data:{},handle:void 0});return d}function rc(a,b,d){if(lc(a)){var c,e=w(d),
|
|
||||||
f=!e&&b&&!D(b),g=!b;a=(a=zb(a,!f))&&a.data;if(e)a[xb(b)]=d;else{if(g)return a;if(f)return a&&a[xb(b)];for(c in b)a[xb(c)]=b[c]}}}function Bb(a,b){return a.getAttribute?-1<(" "+(a.getAttribute("class")||"")+" ").replace(/[\n\t]/g," ").indexOf(" "+b+" "):!1}function Cb(a,b){if(b&&a.setAttribute){var d=(" "+(a.getAttribute("class")||"")+" ").replace(/[\n\t]/g," "),c=d;r(b.split(" "),function(a){a=U(a);c=c.replace(" "+a+" "," ")});c!==d&&a.setAttribute("class",U(c))}}function Db(a,b){if(b&&a.setAttribute){var d=
|
|
||||||
(" "+(a.getAttribute("class")||"")+" ").replace(/[\n\t]/g," "),c=d;r(b.split(" "),function(a){a=U(a);-1===c.indexOf(" "+a+" ")&&(c+=a+" ")});c!==d&&a.setAttribute("class",U(c))}}function oc(a,b){if(b)if(b.nodeType)a[a.length++]=b;else{var d=b.length;if("number"===typeof d&&b.window!==b){if(d)for(var c=0;c<d;c++)a[a.length++]=b[c]}else a[a.length++]=b}}function jd(a,b){return Eb(a,"$"+(b||"ngController")+"Controller")}function Eb(a,b,d){9===a.nodeType&&(a=a.documentElement);for(b=H(b)?b:[b];a;){for(var c=
|
|
||||||
0,e=b.length;c<e;c++)if(w(d=x.data(a,b[c])))return d;a=a.parentNode||11===a.nodeType&&a.host}}function kd(a){for(yb(a,!0);a.firstChild;)a.removeChild(a.firstChild)}function Fb(a,b){b||yb(a);var d=a.parentNode;d&&d.removeChild(a)}function qg(a,b){b=b||C;if("complete"===b.document.readyState)b.setTimeout(a);else x(b).on("load",a)}function fd(a){function b(){C.document.removeEventListener("DOMContentLoaded",b);C.removeEventListener("load",b);a()}"complete"===C.document.readyState?C.setTimeout(a):(C.document.addEventListener("DOMContentLoaded",
|
|
||||||
b),C.addEventListener("load",b))}function ld(a,b){var d=Gb[b.toLowerCase()];return d&&md[ua(a)]&&d}function rg(a,b){var d=function(c,d){c.isDefaultPrevented=function(){return c.defaultPrevented};var f=b[d||c.type],g=f?f.length:0;if(g){if(z(c.immediatePropagationStopped)){var k=c.stopImmediatePropagation;c.stopImmediatePropagation=function(){c.immediatePropagationStopped=!0;c.stopPropagation&&c.stopPropagation();k&&k.call(c)}}c.isImmediatePropagationStopped=function(){return!0===c.immediatePropagationStopped};
|
|
||||||
var h=f.specialHandlerWrapper||sg;1<g&&(f=ja(f));for(var l=0;l<g;l++)c.isImmediatePropagationStopped()||h(a,c,f[l])}};d.elem=a;return d}function sg(a,b,d){d.call(a,b)}function tg(a,b,d){var c=b.relatedTarget;c&&(c===a||ug.call(a,c))||d.call(a,b)}function ig(){this.$get=function(){return S(Y,{hasClass:function(a,b){a.attr&&(a=a[0]);return Bb(a,b)},addClass:function(a,b){a.attr&&(a=a[0]);return Db(a,b)},removeClass:function(a,b){a.attr&&(a=a[0]);return Cb(a,b)}})}}function La(a,b){var d=a&&a.$$hashKey;
|
|
||||||
if(d)return"function"===typeof d&&(d=a.$$hashKey()),d;d=typeof a;return d="function"===d||"object"===d&&null!==a?a.$$hashKey=d+":"+(b||se)():d+":"+a}function nd(){this._keys=[];this._values=[];this._lastKey=NaN;this._lastIndex=-1}function od(a){a=Function.prototype.toString.call(a).replace(vg,"");return a.match(wg)||a.match(xg)}function yg(a){return(a=od(a))?"function("+(a[1]||"").replace(/[\s\r\n]+/," ")+")":"fn"}function fb(a,b){function d(a){return function(b,c){if(D(b))r(b,Yb(a));else return a(b,
|
|
||||||
c)}}function c(a,b){Ja(a,"service");if(B(b)||H(b))b=n.instantiate(b);if(!b.$get)throw Ba("pget",a);return p[a+"Provider"]=b}function e(a,b){return function(){var c=t.invoke(b,this);if(z(c))throw Ba("undef",a);return c}}function f(a,b,d){return c(a,{$get:!1!==d?e(a,b):b})}function g(a){gb(z(a)||H(a),"modulesToLoad","not an array");var b=[],c;r(a,function(a){function d(a){var b,c;b=0;for(c=a.length;b<c;b++){var e=a[b],f=n.get(e[0]);f[e[1]].apply(f,e[2])}}if(!m.get(a)){m.set(a,!0);try{A(a)?(c=kc(a),
|
|
||||||
t.modules[a]=c,b=b.concat(g(c.requires)).concat(c._runBlocks),d(c._invokeQueue),d(c._configBlocks)):B(a)?b.push(n.invoke(a)):H(a)?b.push(n.invoke(a)):sb(a,"module")}catch(e){throw H(a)&&(a=a[a.length-1]),e.message&&e.stack&&-1===e.stack.indexOf(e.message)&&(e=e.message+"\n"+e.stack),Ba("modulerr",a,e.stack||e.message||e);}}});return b}function k(a,c){function d(b,e){if(a.hasOwnProperty(b)){if(a[b]===h)throw Ba("cdep",b+" <- "+l.join(" <- "));return a[b]}try{return l.unshift(b),a[b]=h,a[b]=c(b,e),
|
|
||||||
a[b]}catch(f){throw a[b]===h&&delete a[b],f;}finally{l.shift()}}function e(a,c,f){var g=[];a=fb.$$annotate(a,b,f);for(var h=0,k=a.length;h<k;h++){var l=a[h];if("string"!==typeof l)throw Ba("itkn",l);g.push(c&&c.hasOwnProperty(l)?c[l]:d(l,f))}return g}return{invoke:function(a,b,c,d){"string"===typeof c&&(d=c,c=null);c=e(a,c,d);H(a)&&(a=a[a.length-1]);d=a;if(Ca||"function"!==typeof d)d=!1;else{var f=d.$$ngIsClass;Ga(f)||(f=d.$$ngIsClass=/^class\b/.test(Function.prototype.toString.call(d)));d=f}return d?
|
|
||||||
(c.unshift(null),new (Function.prototype.bind.apply(a,c))):a.apply(b,c)},instantiate:function(a,b,c){var d=H(a)?a[a.length-1]:a;a=e(a,b,c);a.unshift(null);return new (Function.prototype.bind.apply(d,a))},get:d,annotate:fb.$$annotate,has:function(b){return p.hasOwnProperty(b+"Provider")||a.hasOwnProperty(b)}}}b=!0===b;var h={},l=[],m=new Hb,p={$provide:{provider:d(c),factory:d(f),service:d(function(a,b){return f(a,["$injector",function(a){return a.instantiate(b)}])}),value:d(function(a,b){return f(a,
|
|
||||||
ia(b),!1)}),constant:d(function(a,b){Ja(a,"constant");p[a]=b;s[a]=b}),decorator:function(a,b){var c=n.get(a+"Provider"),d=c.$get;c.$get=function(){var a=t.invoke(d,c);return t.invoke(b,null,{$delegate:a})}}}},n=p.$injector=k(p,function(a,b){ca.isString(b)&&l.push(b);throw Ba("unpr",l.join(" <- "));}),s={},G=k(s,function(a,b){var c=n.get(a+"Provider",b);return t.invoke(c.$get,c,void 0,a)}),t=G;p.$injectorProvider={$get:ia(G)};t.modules=n.modules=T();var N=g(a),t=G.get("$injector");t.strictDi=b;r(N,
|
|
||||||
function(a){a&&t.invoke(a)});t.loadNewModules=function(a){r(g(a),function(a){a&&t.invoke(a)})};return t}function wf(){var a=!0;this.disableAutoScrolling=function(){a=!1};this.$get=["$window","$location","$rootScope",function(b,d,c){function e(a){var b=null;Array.prototype.some.call(a,function(a){if("a"===ua(a))return b=a,!0});return b}function f(a){if(a){a.scrollIntoView();var c;c=g.yOffset;B(c)?c=c():$b(c)?(c=c[0],c="fixed"!==b.getComputedStyle(c).position?0:c.getBoundingClientRect().bottom):W(c)||
|
|
||||||
(c=0);c&&(a=a.getBoundingClientRect().top,b.scrollBy(0,a-c))}else b.scrollTo(0,0)}function g(a){a=A(a)?a:W(a)?a.toString():d.hash();var b;a?(b=k.getElementById(a))?f(b):(b=e(k.getElementsByName(a)))?f(b):"top"===a&&f(null):f(null)}var k=b.document;a&&c.$watch(function(){return d.hash()},function(a,b){a===b&&""===a||qg(function(){c.$evalAsync(g)})});return g}]}function hb(a,b){if(!a&&!b)return"";if(!a)return b;if(!b)return a;H(a)&&(a=a.join(" "));H(b)&&(b=b.join(" "));return a+" "+b}function zg(a){A(a)&&
|
|
||||||
(a=a.split(" "));var b=T();r(a,function(a){a.length&&(b[a]=!0)});return b}function ra(a){return D(a)?a:{}}function Ag(a,b,d,c,e){function f(){qa=null;k()}function g(){t=y();t=z(t)?null:t;va(t,P)&&(t=P);N=P=t}function k(){var a=N;g();if(v!==h.url()||a!==t)v=h.url(),N=t,r(J,function(a){a(h.url(),t)})}var h=this,l=a.location,m=a.history,p=a.setTimeout,n=a.clearTimeout,s={},G=e(d);h.isMock=!1;h.$$completeOutstandingRequest=G.completeTask;h.$$incOutstandingRequestCount=G.incTaskCount;h.notifyWhenNoOutstandingRequests=
|
|
||||||
G.notifyWhenNoPendingTasks;var t,N,v=l.href,jc=b.find("base"),qa=null,y=c.history?function(){try{return m.state}catch(a){}}:E;g();h.url=function(b,d,e){z(e)&&(e=null);l!==a.location&&(l=a.location);m!==a.history&&(m=a.history);if(b){var f=N===e;b=ga(b).href;if(v===b&&(!c.history||f))return h;var k=v&&Da(v)===Da(b);v=b;N=e;!c.history||k&&f?(k||(qa=b),d?l.replace(b):k?(d=l,e=b,f=e.indexOf("#"),e=-1===f?"":e.substr(f),d.hash=e):l.href=b,l.href!==b&&(qa=b)):(m[d?"replaceState":"pushState"](e,"",b),g());
|
|
||||||
qa&&(qa=b);return h}return(qa||l.href).replace(/#$/,"")};h.state=function(){return t};var J=[],I=!1,P=null;h.onUrlChange=function(b){if(!I){if(c.history)x(a).on("popstate",f);x(a).on("hashchange",f);I=!0}J.push(b);return b};h.$$applicationDestroyed=function(){x(a).off("hashchange popstate",f)};h.$$checkUrlChange=k;h.baseHref=function(){var a=jc.attr("href");return a?a.replace(/^(https?:)?\/\/[^/]*/,""):""};h.defer=function(a,b,c){var d;b=b||0;c=c||G.DEFAULT_TASK_TYPE;G.incTaskCount(c);d=p(function(){delete s[d];
|
|
||||||
G.completeTask(a,c)},b);s[d]=c;return d};h.defer.cancel=function(a){if(s.hasOwnProperty(a)){var b=s[a];delete s[a];n(a);G.completeTask(E,b);return!0}return!1}}function Df(){this.$get=["$window","$log","$sniffer","$document","$$taskTrackerFactory",function(a,b,d,c,e){return new Ag(a,c,b,d,e)}]}function Ef(){this.$get=function(){function a(a,c){function e(a){a!==p&&(n?n===a&&(n=a.n):n=a,f(a.n,a.p),f(a,p),p=a,p.n=null)}function f(a,b){a!==b&&(a&&(a.p=b),b&&(b.n=a))}if(a in b)throw F("$cacheFactory")("iid",
|
|
||||||
a);var g=0,k=S({},c,{id:a}),h=T(),l=c&&c.capacity||Number.MAX_VALUE,m=T(),p=null,n=null;return b[a]={put:function(a,b){if(!z(b)){if(l<Number.MAX_VALUE){var c=m[a]||(m[a]={key:a});e(c)}a in h||g++;h[a]=b;g>l&&this.remove(n.key);return b}},get:function(a){if(l<Number.MAX_VALUE){var b=m[a];if(!b)return;e(b)}return h[a]},remove:function(a){if(l<Number.MAX_VALUE){var b=m[a];if(!b)return;b===p&&(p=b.p);b===n&&(n=b.n);f(b.n,b.p);delete m[a]}a in h&&(delete h[a],g--)},removeAll:function(){h=T();g=0;m=T();
|
|
||||||
p=n=null},destroy:function(){m=k=h=null;delete b[a]},info:function(){return S({},k,{size:g})}}}var b={};a.info=function(){var a={};r(b,function(b,e){a[e]=b.info()});return a};a.get=function(a){return b[a]};return a}}function cg(){this.$get=["$cacheFactory",function(a){return a("templates")}]}function Xc(a,b){function d(a,b,c){var d=/^([@&]|[=<](\*?))(\??)\s*([\w$]*)$/,e=T();r(a,function(a,f){a=a.trim();if(a in p)e[f]=p[a];else{var g=a.match(d);if(!g)throw $("iscp",b,f,a,c?"controller bindings definition":
|
|
||||||
"isolate scope definition");e[f]={mode:g[1][0],collection:"*"===g[2],optional:"?"===g[3],attrName:g[4]||f};g[4]&&(p[a]=e[f])}});return e}function c(a){var b=a.charAt(0);if(!b||b!==K(b))throw $("baddir",a);if(a!==a.trim())throw $("baddir",a);}function e(a){var b=a.require||a.controller&&a.name;!H(b)&&D(b)&&r(b,function(a,c){var d=a.match(l);a.substring(d[0].length)||(b[c]=d[0]+c)});return b}var f={},g=/^\s*directive:\s*([\w-]+)\s+(.*)$/,k=/(([\w-]+)(?::([^;]+))?;?)/,h=we("ngSrc,ngSrcset,src,srcset"),
|
|
||||||
l=/^(?:(\^\^?)?(\?)?(\^\^?)?)?/,m=/^(on[a-z]+|formaction)$/,p=T();this.directive=function qa(b,d){gb(b,"name");Ja(b,"directive");A(b)?(c(b),gb(d,"directiveFactory"),f.hasOwnProperty(b)||(f[b]=[],a.factory(b+"Directive",["$injector","$exceptionHandler",function(a,c){var d=[];r(f[b],function(f,g){try{var h=a.invoke(f);B(h)?h={compile:ia(h)}:!h.compile&&h.link&&(h.compile=ia(h.link));h.priority=h.priority||0;h.index=g;h.name=h.name||b;h.require=e(h);var k=h,l=h.restrict;if(l&&(!A(l)||!/[EACM]/.test(l)))throw $("badrestrict",
|
|
||||||
l,b);k.restrict=l||"EA";h.$$moduleName=f.$$moduleName;d.push(h)}catch(m){c(m)}});return d}])),f[b].push(d)):r(b,Yb(qa));return this};this.component=function y(a,b){function c(a){function e(b){return B(b)||H(b)?function(c,d){return a.invoke(b,this,{$element:c,$attrs:d})}:b}var f=b.template||b.templateUrl?b.template:"",g={controller:d,controllerAs:Bg(b.controller)||b.controllerAs||"$ctrl",template:e(f),templateUrl:e(b.templateUrl),transclude:b.transclude,scope:{},bindToController:b.bindings||{},restrict:"E",
|
|
||||||
require:b.require};r(b,function(a,b){"$"===b.charAt(0)&&(g[b]=a)});return g}if(!A(a))return r(a,Yb(Va(this,y))),this;var d=b.controller||function(){};r(b,function(a,b){"$"===b.charAt(0)&&(c[b]=a,B(d)&&(d[b]=a))});c.$inject=["$injector"];return this.directive(a,c)};this.aHrefSanitizationWhitelist=function(a){return w(a)?(b.aHrefSanitizationWhitelist(a),this):b.aHrefSanitizationWhitelist()};this.imgSrcSanitizationWhitelist=function(a){return w(a)?(b.imgSrcSanitizationWhitelist(a),this):b.imgSrcSanitizationWhitelist()};
|
|
||||||
var n=!0;this.debugInfoEnabled=function(a){return w(a)?(n=a,this):n};var s=!1;this.strictComponentBindingsEnabled=function(a){return w(a)?(s=a,this):s};var G=10;this.onChangesTtl=function(a){return arguments.length?(G=a,this):G};var t=!0;this.commentDirectivesEnabled=function(a){return arguments.length?(t=a,this):t};var N=!0;this.cssClassDirectivesEnabled=function(a){return arguments.length?(N=a,this):N};var v=T();this.addPropertySecurityContext=function(a,b,c){var d=a.toLowerCase()+"|"+b.toLowerCase();
|
|
||||||
if(d in v&&v[d]!==c)throw $("ctxoverride",a,b,v[d],c);v[d]=c;return this};(function(){function a(b,c){r(c,function(a){v[a.toLowerCase()]=b})}a(V.HTML,["iframe|srcdoc","*|innerHTML","*|outerHTML"]);a(V.CSS,["*|style"]);a(V.URL,"area|href area|ping a|href a|ping blockquote|cite body|background del|cite input|src ins|cite q|cite".split(" "));a(V.MEDIA_URL,"audio|src img|src img|srcset source|src source|srcset track|src video|src video|poster".split(" "));a(V.RESOURCE_URL,"*|formAction applet|code applet|codebase base|href embed|src frame|src form|action head|profile html|manifest iframe|src link|href media|src object|codebase object|data script|src".split(" "))})();
|
|
||||||
this.$get=["$injector","$interpolate","$exceptionHandler","$templateRequest","$parse","$controller","$rootScope","$sce","$animate",function(a,b,c,e,p,M,L,u,R){function q(){try{if(!--Ja)throw Ua=void 0,$("infchng",G);L.$apply(function(){for(var a=0,b=Ua.length;a<b;++a)try{Ua[a]()}catch(d){c(d)}Ua=void 0})}finally{Ja++}}function ma(a,b){if(!a)return a;if(!A(a))throw $("srcset",b,a.toString());for(var c="",d=U(a),e=/(\s+\d+x\s*,|\s+\d+w\s*,|\s+,|,\s+)/,e=/\s/.test(d)?e:/(,)/,d=d.split(e),e=Math.floor(d.length/
|
|
||||||
2),f=0;f<e;f++)var g=2*f,c=c+u.getTrustedMediaUrl(U(d[g])),c=c+(" "+U(d[g+1]));d=U(d[2*f]).split(/\s/);c+=u.getTrustedMediaUrl(U(d[0]));2===d.length&&(c+=" "+U(d[1]));return c}function w(a,b){if(b){var c=Object.keys(b),d,e,f;d=0;for(e=c.length;d<e;d++)f=c[d],this[f]=b[f]}else this.$attr={};this.$$element=a}function O(a,b,c){Fa.innerHTML="<span "+b+">";b=Fa.firstChild.attributes;var d=b[0];b.removeNamedItem(d.name);d.value=c;a.attributes.setNamedItem(d)}function sa(a,b){try{a.addClass(b)}catch(c){}}
|
|
||||||
function da(a,b,c,d,e){a instanceof x||(a=x(a));var f=Xa(a,b,a,c,d,e);da.$$addScopeClass(a);var g=null;return function(b,c,d){if(!a)throw $("multilink");gb(b,"scope");e&&e.needsNewScope&&(b=b.$parent.$new());d=d||{};var h=d.parentBoundTranscludeFn,k=d.transcludeControllers;d=d.futureParentElement;h&&h.$$boundTransclude&&(h=h.$$boundTransclude);g||(g=(d=d&&d[0])?"foreignobject"!==ua(d)&&la.call(d).match(/SVG/)?"svg":"html":"html");d="html"!==g?x(ja(g,x("<div></div>").append(a).html())):c?Wa.clone.call(a):
|
|
||||||
a;if(k)for(var l in k)d.data("$"+l+"Controller",k[l].instance);da.$$addScopeInfo(d,b);c&&c(d,b);f&&f(b,d,d,h);c||(a=f=null);return d}}function Xa(a,b,c,d,e,f){function g(a,c,d,e){var f,k,l,m,p,I,t;if(n)for(t=Array(c.length),m=0;m<h.length;m+=3)f=h[m],t[f]=c[f];else t=c;m=0;for(p=h.length;m<p;)k=t[h[m++]],c=h[m++],f=h[m++],c?(c.scope?(l=a.$new(),da.$$addScopeInfo(x(k),l)):l=a,I=c.transcludeOnThisElement?ka(a,c.transclude,e):!c.templateOnThisElement&&e?e:!e&&b?ka(a,b):null,c(f,l,k,d,I)):f&&f(a,k.childNodes,
|
|
||||||
void 0,e)}for(var h=[],k=H(a)||a instanceof x,l,m,p,I,n,t=0;t<a.length;t++){l=new w;11===Ca&&ib(a,t,k);m=sc(a[t],[],l,0===t?d:void 0,e);(f=m.length?aa(m,a[t],l,b,c,null,[],[],f):null)&&f.scope&&da.$$addScopeClass(l.$$element);l=f&&f.terminal||!(p=a[t].childNodes)||!p.length?null:Xa(p,f?(f.transcludeOnThisElement||!f.templateOnThisElement)&&f.transclude:b);if(f||l)h.push(t,f,l),I=!0,n=n||f;f=null}return I?g:null}function ib(a,b,c){var d=a[b],e=d.parentNode,f;if(d.nodeType===Pa)for(;;){f=e?d.nextSibling:
|
|
||||||
a[b+1];if(!f||f.nodeType!==Pa)break;d.nodeValue+=f.nodeValue;f.parentNode&&f.parentNode.removeChild(f);c&&f===a[b+1]&&a.splice(b+1,1)}}function ka(a,b,c){function d(e,f,g,h,k){e||(e=a.$new(!1,k),e.$$transcluded=!0);return b(e,f,{parentBoundTranscludeFn:c,transcludeControllers:g,futureParentElement:h})}var e=d.$$slots=T(),f;for(f in b.$$slots)e[f]=b.$$slots[f]?ka(a,b.$$slots[f],c):null;return d}function sc(a,b,d,e,f){var g=d.$attr,h;switch(a.nodeType){case 1:h=ua(a);X(b,wa(h),"E",e,f);for(var l,m,
|
|
||||||
n,t,J,s=a.attributes,v=0,G=s&&s.length;v<G;v++){var P=!1,N=!1,r=!1,y=!1,u=!1,M;l=s[v];m=l.name;t=l.value;n=wa(m.toLowerCase());(J=n.match(Ra))?(r="Attr"===J[1],y="Prop"===J[1],u="On"===J[1],m=m.replace(pd,"").toLowerCase().substr(4+J[1].length).replace(/_(.)/g,function(a,b){return b.toUpperCase()})):(M=n.match(Sa))&&ca(M[1])&&(P=m,N=m.substr(0,m.length-5)+"end",m=m.substr(0,m.length-6));if(y||u)d[n]=t,g[n]=l.name,y?Ea(a,b,n,m):b.push(qd(p,L,c,n,m,!1));else{n=wa(m.toLowerCase());g[n]=m;if(r||!d.hasOwnProperty(n))d[n]=
|
|
||||||
t,ld(a,n)&&(d[n]=!0);Ia(a,b,t,n,r);X(b,n,"A",e,f,P,N)}}"input"===h&&"hidden"===a.getAttribute("type")&&a.setAttribute("autocomplete","off");if(!Qa)break;g=a.className;D(g)&&(g=g.animVal);if(A(g)&&""!==g)for(;a=k.exec(g);)n=wa(a[2]),X(b,n,"C",e,f)&&(d[n]=U(a[3])),g=g.substr(a.index+a[0].length);break;case Pa:na(b,a.nodeValue);break;case 8:if(!Oa)break;F(a,b,d,e,f)}b.sort(ia);return b}function F(a,b,c,d,e){try{var f=g.exec(a.nodeValue);if(f){var h=wa(f[1]);X(b,h,"M",d,e)&&(c[h]=U(f[2]))}}catch(k){}}
|
|
||||||
function V(a,b,c){var d=[],e=0;if(b&&a.hasAttribute&&a.hasAttribute(b)){do{if(!a)throw $("uterdir",b,c);1===a.nodeType&&(a.hasAttribute(b)&&e++,a.hasAttribute(c)&&e--);d.push(a);a=a.nextSibling}while(0<e)}else d.push(a);return x(d)}function Y(a,b,c){return function(d,e,f,g,h){e=V(e[0],b,c);return a(d,e,f,g,h)}}function Z(a,b,c,d,e,f){var g;return a?da(b,c,d,e,f):function(){g||(g=da(b,c,d,e,f),b=c=f=null);return g.apply(this,arguments)}}function aa(a,b,d,e,f,g,h,k,l){function m(a,b,c,d){if(a){c&&(a=
|
|
||||||
Y(a,c,d));a.require=u.require;a.directiveName=Q;if(s===u||u.$$isolateScope)a=Aa(a,{isolateScope:!0});h.push(a)}if(b){c&&(b=Y(b,c,d));b.require=u.require;b.directiveName=Q;if(s===u||u.$$isolateScope)b=Aa(b,{isolateScope:!0});k.push(b)}}function p(a,e,f,g,l){function m(a,b,c,d){var e;bb(a)||(d=c,c=b,b=a,a=void 0);N&&(e=P);c||(c=N?Q.parent():Q);if(d){var f=l.$$slots[d];if(f)return f(a,b,e,c,R);if(z(f))throw $("noslot",d,za(Q));}else return l(a,b,e,c,R)}var n,u,L,y,G,P,M,Q;b===f?(g=d,Q=d.$$element):(Q=
|
|
||||||
x(f),g=new w(Q,d));G=e;s?y=e.$new(!0):t&&(G=e.$parent);l&&(M=m,M.$$boundTransclude=l,M.isSlotFilled=function(a){return!!l.$$slots[a]});J&&(P=ea(Q,g,M,J,y,e,s));s&&(da.$$addScopeInfo(Q,y,!0,!(v&&(v===s||v===s.$$originalDirective))),da.$$addScopeClass(Q,!0),y.$$isolateBindings=s.$$isolateBindings,u=Da(e,g,y,y.$$isolateBindings,s),u.removeWatches&&y.$on("$destroy",u.removeWatches));for(n in P){u=J[n];L=P[n];var Cg=u.$$bindings.bindToController;L.instance=L();Q.data("$"+u.name+"Controller",L.instance);
|
|
||||||
L.bindingInfo=Da(G,g,L.instance,Cg,u)}r(J,function(a,b){var c=a.require;a.bindToController&&!H(c)&&D(c)&&S(P[b].instance,W(b,c,Q,P))});r(P,function(a){var b=a.instance;if(B(b.$onChanges))try{b.$onChanges(a.bindingInfo.initialChanges)}catch(d){c(d)}if(B(b.$onInit))try{b.$onInit()}catch(e){c(e)}B(b.$doCheck)&&(G.$watch(function(){b.$doCheck()}),b.$doCheck());B(b.$onDestroy)&&G.$on("$destroy",function(){b.$onDestroy()})});n=0;for(u=h.length;n<u;n++)L=h[n],Ba(L,L.isolateScope?y:e,Q,g,L.require&&W(L.directiveName,
|
|
||||||
L.require,Q,P),M);var R=e;s&&(s.template||null===s.templateUrl)&&(R=y);a&&a(R,f.childNodes,void 0,l);for(n=k.length-1;0<=n;n--)L=k[n],Ba(L,L.isolateScope?y:e,Q,g,L.require&&W(L.directiveName,L.require,Q,P),M);r(P,function(a){a=a.instance;B(a.$postLink)&&a.$postLink()})}l=l||{};for(var n=-Number.MAX_VALUE,t=l.newScopeDirective,J=l.controllerDirectives,s=l.newIsolateScopeDirective,v=l.templateDirective,L=l.nonTlbTranscludeDirective,G=!1,P=!1,N=l.hasElementTranscludeDirective,y=d.$$element=x(b),u,Q,
|
|
||||||
M,R=e,q,ma=!1,Ib=!1,O,sa=0,A=a.length;sa<A;sa++){u=a[sa];var E=u.$$start,ib=u.$$end;E&&(y=V(b,E,ib));M=void 0;if(n>u.priority)break;if(O=u.scope)u.templateUrl||(D(O)?(ba("new/isolated scope",s||t,u,y),s=u):ba("new/isolated scope",s,u,y)),t=t||u;Q=u.name;if(!ma&&(u.replace&&(u.templateUrl||u.template)||u.transclude&&!u.$$tlb)){for(O=sa+1;ma=a[O++];)if(ma.transclude&&!ma.$$tlb||ma.replace&&(ma.templateUrl||ma.template)){Ib=!0;break}ma=!0}!u.templateUrl&&u.controller&&(J=J||T(),ba("'"+Q+"' controller",
|
|
||||||
J[Q],u,y),J[Q]=u);if(O=u.transclude)if(G=!0,u.$$tlb||(ba("transclusion",L,u,y),L=u),"element"===O)N=!0,n=u.priority,M=y,y=d.$$element=x(da.$$createComment(Q,d[Q])),b=y[0],pa(f,Ha.call(M,0),b),R=Z(Ib,M,e,n,g&&g.name,{nonTlbTranscludeDirective:L});else{var ka=T();if(D(O)){M=C.document.createDocumentFragment();var Xa=T(),F=T();r(O,function(a,b){var c="?"===a.charAt(0);a=c?a.substring(1):a;Xa[a]=b;ka[b]=null;F[b]=c});r(y.contents(),function(a){var b=Xa[wa(ua(a))];b?(F[b]=!0,ka[b]=ka[b]||C.document.createDocumentFragment(),
|
|
||||||
ka[b].appendChild(a)):M.appendChild(a)});r(F,function(a,b){if(!a)throw $("reqslot",b);});for(var K in ka)ka[K]&&(R=x(ka[K].childNodes),ka[K]=Z(Ib,R,e));M=x(M.childNodes)}else M=x(pc(b)).contents();y.empty();R=Z(Ib,M,e,void 0,void 0,{needsNewScope:u.$$isolateScope||u.$$newScope});R.$$slots=ka}if(u.template)if(P=!0,ba("template",v,u,y),v=u,O=B(u.template)?u.template(y,d):u.template,O=Na(O),u.replace){g=u;M=mc.test(O)?rd(ja(u.templateNamespace,U(O))):[];b=M[0];if(1!==M.length||1!==b.nodeType)throw $("tplrt",
|
|
||||||
Q,"");pa(f,y,b);A={$attr:{}};O=sc(b,[],A);var Dg=a.splice(sa+1,a.length-(sa+1));(s||t)&&fa(O,s,t);a=a.concat(O).concat(Dg);ga(d,A);A=a.length}else y.html(O);if(u.templateUrl)P=!0,ba("template",v,u,y),v=u,u.replace&&(g=u),p=ha(a.splice(sa,a.length-sa),y,d,f,G&&R,h,k,{controllerDirectives:J,newScopeDirective:t!==u&&t,newIsolateScopeDirective:s,templateDirective:v,nonTlbTranscludeDirective:L}),A=a.length;else if(u.compile)try{q=u.compile(y,d,R);var X=u.$$originalDirective||u;B(q)?m(null,Va(X,q),E,ib):
|
|
||||||
q&&m(Va(X,q.pre),Va(X,q.post),E,ib)}catch(ca){c(ca,za(y))}u.terminal&&(p.terminal=!0,n=Math.max(n,u.priority))}p.scope=t&&!0===t.scope;p.transcludeOnThisElement=G;p.templateOnThisElement=P;p.transclude=R;l.hasElementTranscludeDirective=N;return p}function W(a,b,c,d){var e;if(A(b)){var f=b.match(l);b=b.substring(f[0].length);var g=f[1]||f[3],f="?"===f[2];"^^"===g?c=c.parent():e=(e=d&&d[b])&&e.instance;if(!e){var h="$"+b+"Controller";e="^^"===g&&c[0]&&9===c[0].nodeType?null:g?c.inheritedData(h):c.data(h)}if(!e&&
|
|
||||||
!f)throw $("ctreq",b,a);}else if(H(b))for(e=[],g=0,f=b.length;g<f;g++)e[g]=W(a,b[g],c,d);else D(b)&&(e={},r(b,function(b,f){e[f]=W(a,b,c,d)}));return e||null}function ea(a,b,c,d,e,f,g){var h=T(),k;for(k in d){var l=d[k],m={$scope:l===g||l.$$isolateScope?e:f,$element:a,$attrs:b,$transclude:c},p=l.controller;"@"===p&&(p=b[l.name]);m=M(p,m,!0,l.controllerAs);h[l.name]=m;a.data("$"+l.name+"Controller",m.instance)}return h}function fa(a,b,c){for(var d=0,e=a.length;d<e;d++)a[d]=ac(a[d],{$$isolateScope:b,
|
|
||||||
$$newScope:c})}function X(b,c,e,g,h,k,l){if(c===h)return null;var m=null;if(f.hasOwnProperty(c)){h=a.get(c+"Directive");for(var p=0,n=h.length;p<n;p++)if(c=h[p],(z(g)||g>c.priority)&&-1!==c.restrict.indexOf(e)){k&&(c=ac(c,{$$start:k,$$end:l}));if(!c.$$bindings){var I=m=c,t=c.name,u={isolateScope:null,bindToController:null};D(I.scope)&&(!0===I.bindToController?(u.bindToController=d(I.scope,t,!0),u.isolateScope={}):u.isolateScope=d(I.scope,t,!1));D(I.bindToController)&&(u.bindToController=d(I.bindToController,
|
|
||||||
t,!0));if(u.bindToController&&!I.controller)throw $("noctrl",t);m=m.$$bindings=u;D(m.isolateScope)&&(c.$$isolateBindings=m.isolateScope)}b.push(c);m=c}}return m}function ca(b){if(f.hasOwnProperty(b))for(var c=a.get(b+"Directive"),d=0,e=c.length;d<e;d++)if(b=c[d],b.multiElement)return!0;return!1}function ga(a,b){var c=b.$attr,d=a.$attr;r(a,function(d,e){"$"!==e.charAt(0)&&(b[e]&&b[e]!==d&&(d=d.length?d+(("style"===e?";":" ")+b[e]):b[e]),a.$set(e,d,!0,c[e]))});r(b,function(b,e){a.hasOwnProperty(e)||
|
|
||||||
"$"===e.charAt(0)||(a[e]=b,"class"!==e&&"style"!==e&&(d[e]=c[e]))})}function ha(a,b,d,f,g,h,k,l){var m=[],p,n,t=b[0],u=a.shift(),J=ac(u,{templateUrl:null,transclude:null,replace:null,$$originalDirective:u}),s=B(u.templateUrl)?u.templateUrl(b,d):u.templateUrl,L=u.templateNamespace;b.empty();e(s).then(function(c){var e,I;c=Na(c);if(u.replace){c=mc.test(c)?rd(ja(L,U(c))):[];e=c[0];if(1!==c.length||1!==e.nodeType)throw $("tplrt",u.name,s);c={$attr:{}};pa(f,b,e);var v=sc(e,[],c);D(u.scope)&&fa(v,!0);a=
|
|
||||||
v.concat(a);ga(d,c)}else e=t,b.html(c);a.unshift(J);p=aa(a,e,d,g,b,u,h,k,l);r(f,function(a,c){a===e&&(f[c]=b[0])});for(n=Xa(b[0].childNodes,g);m.length;){c=m.shift();I=m.shift();var y=m.shift(),P=m.shift(),v=b[0];if(!c.$$destroyed){if(I!==t){var G=I.className;l.hasElementTranscludeDirective&&u.replace||(v=pc(e));pa(y,x(I),v);sa(x(v),G)}I=p.transcludeOnThisElement?ka(c,p.transclude,P):P;p(n,c,v,f,I)}}m=null}).catch(function(a){cc(a)&&c(a)});return function(a,b,c,d,e){a=e;b.$$destroyed||(m?m.push(b,
|
|
||||||
c,d,a):(p.transcludeOnThisElement&&(a=ka(b,p.transclude,e)),p(n,b,c,d,a)))}}function ia(a,b){var c=b.priority-a.priority;return 0!==c?c:a.name!==b.name?a.name<b.name?-1:1:a.index-b.index}function ba(a,b,c,d){function e(a){return a?" (module: "+a+")":""}if(b)throw $("multidir",b.name,e(b.$$moduleName),c.name,e(c.$$moduleName),a,za(d));}function na(a,c){var d=b(c,!0);d&&a.push({priority:0,compile:function(a){a=a.parent();var b=!!a.length;b&&da.$$addBindingClass(a);return function(a,c){var e=c.parent();
|
|
||||||
b||da.$$addBindingClass(e);da.$$addBindingInfo(e,d.expressions);a.$watch(d,function(a){c[0].nodeValue=a})}}})}function ja(a,b){a=K(a||"html");switch(a){case "svg":case "math":var c=C.document.createElement("div");c.innerHTML="<"+a+">"+b+"</"+a+">";return c.childNodes[0].childNodes;default:return b}}function oa(a,b){if("srcdoc"===b)return u.HTML;if("src"===b||"ngSrc"===b)return-1===["img","video","audio","source","track"].indexOf(a)?u.RESOURCE_URL:u.MEDIA_URL;if("xlinkHref"===b)return"image"===a?u.MEDIA_URL:
|
|
||||||
"a"===a?u.URL:u.RESOURCE_URL;if("form"===a&&"action"===b||"base"===a&&"href"===b||"link"===a&&"href"===b)return u.RESOURCE_URL;if("a"===a&&("href"===b||"ngHref"===b))return u.URL}function xa(a,b){var c=b.toLowerCase();return v[a+"|"+c]||v["*|"+c]}function ya(a){return ma(u.valueOf(a),"ng-prop-srcset")}function Ea(a,b,c,d){if(m.test(d))throw $("nodomevents");a=ua(a);var e=xa(a,d),f=Ta;"srcset"!==d||"img"!==a&&"source"!==a?e&&(f=u.getTrusted.bind(u,e)):f=ya;b.push({priority:100,compile:function(a,b){var e=
|
|
||||||
p(b[c]),g=p(b[c],function(a){return u.valueOf(a)});return{pre:function(a,b){function c(){var g=e(a);b[0][d]=f(g)}c();a.$watch(g,c)}}}})}function Ia(a,c,d,e,f){var g=ua(a),k=oa(g,e),l=h[e]||f,p=b(d,!f,k,l);if(p){if("multiple"===e&&"select"===g)throw $("selmulti",za(a));if(m.test(e))throw $("nodomevents");c.push({priority:100,compile:function(){return{pre:function(a,c,f){c=f.$$observers||(f.$$observers=T());var g=f[e];g!==d&&(p=g&&b(g,!0,k,l),d=g);p&&(f[e]=p(a),(c[e]||(c[e]=[])).$$inter=!0,(f.$$observers&&
|
|
||||||
f.$$observers[e].$$scope||a).$watch(p,function(a,b){"class"===e&&a!==b?f.$updateClass(a,b):f.$set(e,a)}))}}}})}}function pa(a,b,c){var d=b[0],e=b.length,f=d.parentNode,g,h;if(a)for(g=0,h=a.length;g<h;g++)if(a[g]===d){a[g++]=c;h=g+e-1;for(var k=a.length;g<k;g++,h++)h<k?a[g]=a[h]:delete a[g];a.length-=e-1;a.context===d&&(a.context=c);break}f&&f.replaceChild(c,d);a=C.document.createDocumentFragment();for(g=0;g<e;g++)a.appendChild(b[g]);x.hasData(d)&&(x.data(c,x.data(d)),x(d).off("$destroy"));x.cleanData(a.querySelectorAll("*"));
|
|
||||||
for(g=1;g<e;g++)delete b[g];b[0]=c;b.length=1}function Aa(a,b){return S(function(){return a.apply(null,arguments)},a,b)}function Ba(a,b,d,e,f,g){try{a(b,d,e,f,g)}catch(h){c(h,za(d))}}function ra(a,b){if(s)throw $("missingattr",a,b);}function Da(a,c,d,e,f){function g(b,c,e){B(d.$onChanges)&&!dc(c,e)&&(Ua||(a.$$postDigest(q),Ua=[]),m||(m={},Ua.push(h)),m[b]&&(e=m[b].previousValue),m[b]=new Jb(e,c))}function h(){d.$onChanges(m);m=void 0}var k=[],l={},m;r(e,function(e,h){var m=e.attrName,n=e.optional,
|
|
||||||
I,t,u,s;switch(e.mode){case "@":n||ta.call(c,m)||(ra(m,f.name),d[h]=c[m]=void 0);n=c.$observe(m,function(a){if(A(a)||Ga(a))g(h,a,d[h]),d[h]=a});c.$$observers[m].$$scope=a;I=c[m];A(I)?d[h]=b(I)(a):Ga(I)&&(d[h]=I);l[h]=new Jb(tc,d[h]);k.push(n);break;case "=":if(!ta.call(c,m)){if(n)break;ra(m,f.name);c[m]=void 0}if(n&&!c[m])break;t=p(c[m]);s=t.literal?va:dc;u=t.assign||function(){I=d[h]=t(a);throw $("nonassign",c[m],m,f.name);};I=d[h]=t(a);n=function(b){s(b,d[h])||(s(b,I)?u(a,b=d[h]):d[h]=b);return I=
|
|
||||||
b};n.$stateful=!0;n=e.collection?a.$watchCollection(c[m],n):a.$watch(p(c[m],n),null,t.literal);k.push(n);break;case "<":if(!ta.call(c,m)){if(n)break;ra(m,f.name);c[m]=void 0}if(n&&!c[m])break;t=p(c[m]);var v=t.literal,L=d[h]=t(a);l[h]=new Jb(tc,d[h]);n=a[e.collection?"$watchCollection":"$watch"](t,function(a,b){if(b===a){if(b===L||v&&va(b,L))return;b=L}g(h,a,b);d[h]=a});k.push(n);break;case "&":n||ta.call(c,m)||ra(m,f.name);t=c.hasOwnProperty(m)?p(c[m]):E;if(t===E&&n)break;d[h]=function(b){return t(a,
|
|
||||||
b)}}});return{initialChanges:l,removeWatches:k.length&&function(){for(var a=0,b=k.length;a<b;++a)k[a]()}}}var Ma=/^\w/,Fa=C.document.createElement("div"),Oa=t,Qa=N,Ja=G,Ua;w.prototype={$normalize:wa,$addClass:function(a){a&&0<a.length&&R.addClass(this.$$element,a)},$removeClass:function(a){a&&0<a.length&&R.removeClass(this.$$element,a)},$updateClass:function(a,b){var c=sd(a,b);c&&c.length&&R.addClass(this.$$element,c);(c=sd(b,a))&&c.length&&R.removeClass(this.$$element,c)},$set:function(a,b,d,e){var f=
|
|
||||||
ld(this.$$element[0],a),g=td[a],h=a;f?(this.$$element.prop(a,b),e=f):g&&(this[g]=b,h=g);this[a]=b;e?this.$attr[a]=e:(e=this.$attr[a])||(this.$attr[a]=e=Vc(a,"-"));"img"===ua(this.$$element)&&"srcset"===a&&(this[a]=b=ma(b,"$set('srcset', value)"));!1!==d&&(null===b||z(b)?this.$$element.removeAttr(e):Ma.test(e)?f&&!1===b?this.$$element.removeAttr(e):this.$$element.attr(e,b):O(this.$$element[0],e,b));(a=this.$$observers)&&r(a[h],function(a){try{a(b)}catch(d){c(d)}})},$observe:function(a,b){var c=this,
|
|
||||||
d=c.$$observers||(c.$$observers=T()),e=d[a]||(d[a]=[]);e.push(b);L.$evalAsync(function(){e.$$inter||!c.hasOwnProperty(a)||z(c[a])||b(c[a])});return function(){cb(e,b)}}};var Ka=b.startSymbol(),La=b.endSymbol(),Na="{{"===Ka&&"}}"===La?Ta:function(a){return a.replace(/\{\{/g,Ka).replace(/}}/g,La)},Ra=/^ng(Attr|Prop|On)([A-Z].*)$/,Sa=/^(.+)Start$/;da.$$addBindingInfo=n?function(a,b){var c=a.data("$binding")||[];H(b)?c=c.concat(b):c.push(b);a.data("$binding",c)}:E;da.$$addBindingClass=n?function(a){sa(a,
|
|
||||||
"ng-binding")}:E;da.$$addScopeInfo=n?function(a,b,c,d){a.data(c?d?"$isolateScopeNoTemplate":"$isolateScope":"$scope",b)}:E;da.$$addScopeClass=n?function(a,b){sa(a,b?"ng-isolate-scope":"ng-scope")}:E;da.$$createComment=function(a,b){var c="";n&&(c=" "+(a||"")+": ",b&&(c+=b+" "));return C.document.createComment(c)};return da}]}function Jb(a,b){this.previousValue=a;this.currentValue=b}function wa(a){return a.replace(pd,"").replace(Eg,function(a,d,c){return c?d.toUpperCase():d})}function sd(a,b){var d=
|
|
||||||
"",c=a.split(/\s+/),e=b.split(/\s+/),f=0;a:for(;f<c.length;f++){for(var g=c[f],k=0;k<e.length;k++)if(g===e[k])continue a;d+=(0<d.length?" ":"")+g}return d}function rd(a){a=x(a);var b=a.length;if(1>=b)return a;for(;b--;){var d=a[b];(8===d.nodeType||d.nodeType===Pa&&""===d.nodeValue.trim())&&Fg.call(a,b,1)}return a}function Bg(a,b){if(b&&A(b))return b;if(A(a)){var d=ud.exec(a);if(d)return d[3]}}function Ff(){var a={};this.has=function(b){return a.hasOwnProperty(b)};this.register=function(b,d){Ja(b,
|
|
||||||
"controller");D(b)?S(a,b):a[b]=d};this.$get=["$injector",function(b){function d(a,b,d,g){if(!a||!D(a.$scope))throw F("$controller")("noscp",g,b);a.$scope[b]=d}return function(c,e,f,g){var k,h,l;f=!0===f;g&&A(g)&&(l=g);if(A(c)){g=c.match(ud);if(!g)throw vd("ctrlfmt",c);h=g[1];l=l||g[3];c=a.hasOwnProperty(h)?a[h]:Ge(e.$scope,h,!0);if(!c)throw vd("ctrlreg",h);sb(c,h,!0)}if(f)return f=(H(c)?c[c.length-1]:c).prototype,k=Object.create(f||null),l&&d(e,l,k,h||c.name),S(function(){var a=b.invoke(c,k,e,h);
|
|
||||||
a!==k&&(D(a)||B(a))&&(k=a,l&&d(e,l,k,h||c.name));return k},{instance:k,identifier:l});k=b.instantiate(c,e,h);l&&d(e,l,k,h||c.name);return k}}]}function Gf(){this.$get=["$window",function(a){return x(a.document)}]}function Hf(){this.$get=["$document","$rootScope",function(a,b){function d(){e=c.hidden}var c=a[0],e=c&&c.hidden;a.on("visibilitychange",d);b.$on("$destroy",function(){a.off("visibilitychange",d)});return function(){return e}}]}function If(){this.$get=["$log",function(a){return function(b,
|
|
||||||
d){a.error.apply(a,arguments)}}]}function uc(a){return D(a)?ha(a)?a.toISOString():eb(a):a}function Of(){this.$get=function(){return function(a){if(!a)return"";var b=[];Oc(a,function(a,c){null===a||z(a)||B(a)||(H(a)?r(a,function(a){b.push(ba(c)+"="+ba(uc(a)))}):b.push(ba(c)+"="+ba(uc(a))))});return b.join("&")}}}function Pf(){this.$get=function(){return function(a){function b(a,e,f){H(a)?r(a,function(a,c){b(a,e+"["+(D(a)?c:"")+"]")}):D(a)&&!ha(a)?Oc(a,function(a,c){b(a,e+(f?"":"[")+c+(f?"":"]"))}):
|
|
||||||
(B(a)&&(a=a()),d.push(ba(e)+"="+(null==a?"":ba(uc(a)))))}if(!a)return"";var d=[];b(a,"",!0);return d.join("&")}}}function vc(a,b){if(A(a)){var d=a.replace(Gg,"").trim();if(d){var c=b("Content-Type"),c=c&&0===c.indexOf(wd),e;(e=c)||(e=(e=d.match(Hg))&&Ig[e[0]].test(d));if(e)try{a=Rc(d)}catch(f){if(!c)return a;throw Kb("baddata",a,f);}}}return a}function xd(a){var b=T(),d;A(a)?r(a.split("\n"),function(a){d=a.indexOf(":");var e=K(U(a.substr(0,d)));a=U(a.substr(d+1));e&&(b[e]=b[e]?b[e]+", "+a:a)}):D(a)&&
|
|
||||||
r(a,function(a,d){var f=K(d),g=U(a);f&&(b[f]=b[f]?b[f]+", "+g:g)});return b}function yd(a){var b;return function(d){b||(b=xd(a));return d?(d=b[K(d)],void 0===d&&(d=null),d):b}}function zd(a,b,d,c){if(B(c))return c(a,b,d);r(c,function(c){a=c(a,b,d)});return a}function Nf(){var a=this.defaults={transformResponse:[vc],transformRequest:[function(a){return D(a)&&"[object File]"!==la.call(a)&&"[object Blob]"!==la.call(a)&&"[object FormData]"!==la.call(a)?eb(a):a}],headers:{common:{Accept:"application/json, text/plain, */*"},
|
|
||||||
post:ja(wc),put:ja(wc),patch:ja(wc)},xsrfCookieName:"XSRF-TOKEN",xsrfHeaderName:"X-XSRF-TOKEN",paramSerializer:"$httpParamSerializer",jsonpCallbackParam:"callback"},b=!1;this.useApplyAsync=function(a){return w(a)?(b=!!a,this):b};var d=this.interceptors=[],c=this.xsrfWhitelistedOrigins=[];this.$get=["$browser","$httpBackend","$$cookieReader","$cacheFactory","$rootScope","$q","$injector","$sce",function(e,f,g,k,h,l,m,p){function n(b){function c(a,b){for(var d=0,e=b.length;d<e;){var f=b[d++],g=b[d++];
|
|
||||||
a=a.then(f,g)}b.length=0;return a}function d(a,b){var c,e={};r(a,function(a,d){B(a)?(c=a(b),null!=c&&(e[d]=c)):e[d]=a});return e}function f(a){var b=S({},a);b.data=zd(a.data,a.headers,a.status,g.transformResponse);a=a.status;return 200<=a&&300>a?b:l.reject(b)}if(!D(b))throw F("$http")("badreq",b);if(!A(p.valueOf(b.url)))throw F("$http")("badreq",b.url);var g=S({method:"get",transformRequest:a.transformRequest,transformResponse:a.transformResponse,paramSerializer:a.paramSerializer,jsonpCallbackParam:a.jsonpCallbackParam},
|
|
||||||
b);g.headers=function(b){var c=a.headers,e=S({},b.headers),f,g,h,c=S({},c.common,c[K(b.method)]);a:for(f in c){g=K(f);for(h in e)if(K(h)===g)continue a;e[f]=c[f]}return d(e,ja(b))}(b);g.method=ub(g.method);g.paramSerializer=A(g.paramSerializer)?m.get(g.paramSerializer):g.paramSerializer;e.$$incOutstandingRequestCount("$http");var h=[],k=[];b=l.resolve(g);r(v,function(a){(a.request||a.requestError)&&h.unshift(a.request,a.requestError);(a.response||a.responseError)&&k.push(a.response,a.responseError)});
|
|
||||||
b=c(b,h);b=b.then(function(b){var c=b.headers,d=zd(b.data,yd(c),void 0,b.transformRequest);z(d)&&r(c,function(a,b){"content-type"===K(b)&&delete c[b]});z(b.withCredentials)&&!z(a.withCredentials)&&(b.withCredentials=a.withCredentials);return s(b,d).then(f,f)});b=c(b,k);return b=b.finally(function(){e.$$completeOutstandingRequest(E,"$http")})}function s(c,d){function e(a){if(a){var c={};r(a,function(a,d){c[d]=function(c){function d(){a(c)}b?h.$applyAsync(d):h.$$phase?d():h.$apply(d)}});return c}}function k(a,
|
|
||||||
c,d,e,f){function g(){m(c,a,d,e,f)}R&&(200<=a&&300>a?R.put(O,[a,c,xd(d),e,f]):R.remove(O));b?h.$applyAsync(g):(g(),h.$$phase||h.$apply())}function m(a,b,d,e,f){b=-1<=b?b:0;(200<=b&&300>b?L.resolve:L.reject)({data:a,status:b,headers:yd(d),config:c,statusText:e,xhrStatus:f})}function s(a){m(a.data,a.status,ja(a.headers()),a.statusText,a.xhrStatus)}function v(){var a=n.pendingRequests.indexOf(c);-1!==a&&n.pendingRequests.splice(a,1)}var L=l.defer(),u=L.promise,R,q,ma=c.headers,x="jsonp"===K(c.method),
|
|
||||||
O=c.url;x?O=p.getTrustedResourceUrl(O):A(O)||(O=p.valueOf(O));O=G(O,c.paramSerializer(c.params));x&&(O=t(O,c.jsonpCallbackParam));n.pendingRequests.push(c);u.then(v,v);!c.cache&&!a.cache||!1===c.cache||"GET"!==c.method&&"JSONP"!==c.method||(R=D(c.cache)?c.cache:D(a.cache)?a.cache:N);R&&(q=R.get(O),w(q)?q&&B(q.then)?q.then(s,s):H(q)?m(q[1],q[0],ja(q[2]),q[3],q[4]):m(q,200,{},"OK","complete"):R.put(O,u));z(q)&&((q=jc(c.url)?g()[c.xsrfCookieName||a.xsrfCookieName]:void 0)&&(ma[c.xsrfHeaderName||a.xsrfHeaderName]=
|
|
||||||
q),f(c.method,O,d,k,ma,c.timeout,c.withCredentials,c.responseType,e(c.eventHandlers),e(c.uploadEventHandlers)));return u}function G(a,b){0<b.length&&(a+=(-1===a.indexOf("?")?"?":"&")+b);return a}function t(a,b){var c=a.split("?");if(2<c.length)throw Kb("badjsonp",a);c=gc(c[1]);r(c,function(c,d){if("JSON_CALLBACK"===c)throw Kb("badjsonp",a);if(d===b)throw Kb("badjsonp",b,a);});return a+=(-1===a.indexOf("?")?"?":"&")+b+"=JSON_CALLBACK"}var N=k("$http");a.paramSerializer=A(a.paramSerializer)?m.get(a.paramSerializer):
|
|
||||||
a.paramSerializer;var v=[];r(d,function(a){v.unshift(A(a)?m.get(a):m.invoke(a))});var jc=Jg(c);n.pendingRequests=[];(function(a){r(arguments,function(a){n[a]=function(b,c){return n(S({},c||{},{method:a,url:b}))}})})("get","delete","head","jsonp");(function(a){r(arguments,function(a){n[a]=function(b,c,d){return n(S({},d||{},{method:a,url:b,data:c}))}})})("post","put","patch");n.defaults=a;return n}]}function Rf(){this.$get=function(){return function(){return new C.XMLHttpRequest}}}function Qf(){this.$get=
|
|
||||||
["$browser","$jsonpCallbacks","$document","$xhrFactory",function(a,b,d,c){return Kg(a,c,a.defer,b,d[0])}]}function Kg(a,b,d,c,e){function f(a,b,d){a=a.replace("JSON_CALLBACK",b);var f=e.createElement("script"),m=null;f.type="text/javascript";f.src=a;f.async=!0;m=function(a){f.removeEventListener("load",m);f.removeEventListener("error",m);e.body.removeChild(f);f=null;var g=-1,s="unknown";a&&("load"!==a.type||c.wasCalled(b)||(a={type:"error"}),s=a.type,g="error"===a.type?404:200);d&&d(g,s)};f.addEventListener("load",
|
|
||||||
m);f.addEventListener("error",m);e.body.appendChild(f);return m}return function(e,k,h,l,m,p,n,s,G,t){function N(a){J="timeout"===a;qa&&qa();y&&y.abort()}function v(a,b,c,e,f,g){w(P)&&d.cancel(P);qa=y=null;a(b,c,e,f,g)}k=k||a.url();if("jsonp"===K(e))var q=c.createCallback(k),qa=f(k,q,function(a,b){var d=200===a&&c.getResponse(q);v(l,a,d,"",b,"complete");c.removeCallback(q)});else{var y=b(e,k),J=!1;y.open(e,k,!0);r(m,function(a,b){w(a)&&y.setRequestHeader(b,a)});y.onload=function(){var a=y.statusText||
|
|
||||||
"",b="response"in y?y.response:y.responseText,c=1223===y.status?204:y.status;0===c&&(c=b?200:"file"===ga(k).protocol?404:0);v(l,c,b,y.getAllResponseHeaders(),a,"complete")};y.onerror=function(){v(l,-1,null,null,"","error")};y.ontimeout=function(){v(l,-1,null,null,"","timeout")};y.onabort=function(){v(l,-1,null,null,"",J?"timeout":"abort")};r(G,function(a,b){y.addEventListener(b,a)});r(t,function(a,b){y.upload.addEventListener(b,a)});n&&(y.withCredentials=!0);if(s)try{y.responseType=s}catch(I){if("json"!==
|
|
||||||
s)throw I;}y.send(z(h)?null:h)}if(0<p)var P=d(function(){N("timeout")},p);else p&&B(p.then)&&p.then(function(){N(w(p.$$timeoutId)?"timeout":"abort")})}}function Kf(){var a="{{",b="}}";this.startSymbol=function(b){return b?(a=b,this):a};this.endSymbol=function(a){return a?(b=a,this):b};this.$get=["$parse","$exceptionHandler","$sce",function(d,c,e){function f(a){return"\\\\\\"+a}function g(c){return c.replace(p,a).replace(n,b)}function k(a,b,c,d){var e=a.$watch(function(a){e();return d(a)},b,c);return e}
|
|
||||||
function h(f,h,n,p){function v(a){try{return a=n&&!r?e.getTrusted(n,a):e.valueOf(a),p&&!w(a)?a:ic(a)}catch(b){c(Ma.interr(f,b))}}var r=n===e.URL||n===e.MEDIA_URL;if(!f.length||-1===f.indexOf(a)){if(h)return;h=g(f);r&&(h=e.getTrusted(n,h));h=ia(h);h.exp=f;h.expressions=[];h.$$watchDelegate=k;return h}p=!!p;for(var q,y,J=0,I=[],P,Q=f.length,M=[],L=[],u;J<Q;)if(-1!==(q=f.indexOf(a,J))&&-1!==(y=f.indexOf(b,q+l)))J!==q&&M.push(g(f.substring(J,q))),J=f.substring(q+l,y),I.push(J),J=y+m,L.push(M.length),
|
|
||||||
M.push("");else{J!==Q&&M.push(g(f.substring(J)));break}u=1===M.length&&1===L.length;var R=r&&u?void 0:v;P=I.map(function(a){return d(a,R)});if(!h||I.length){var x=function(a){for(var b=0,c=I.length;b<c;b++){if(p&&z(a[b]))return;M[L[b]]=a[b]}if(r)return e.getTrusted(n,u?M[0]:M.join(""));n&&1<M.length&&Ma.throwNoconcat(f);return M.join("")};return S(function(a){var b=0,d=I.length,e=Array(d);try{for(;b<d;b++)e[b]=P[b](a);return x(e)}catch(g){c(Ma.interr(f,g))}},{exp:f,expressions:I,$$watchDelegate:function(a,
|
|
||||||
b){var c;return a.$watchGroup(P,function(d,e){var f=x(d);b.call(this,f,d!==e?c:f,a);c=f})}})}}var l=a.length,m=b.length,p=new RegExp(a.replace(/./g,f),"g"),n=new RegExp(b.replace(/./g,f),"g");h.startSymbol=function(){return a};h.endSymbol=function(){return b};return h}]}function Lf(){this.$get=["$$intervalFactory","$window",function(a,b){var d={},c=function(a){b.clearInterval(a);delete d[a]},e=a(function(a,c,e){a=b.setInterval(a,c);d[a]=e;return a},c);e.cancel=function(a){if(!a)return!1;if(!a.hasOwnProperty("$$intervalId"))throw Lg("badprom");
|
|
||||||
if(!d.hasOwnProperty(a.$$intervalId))return!1;a=a.$$intervalId;var b=d[a],e=b.promise;e.$$state&&(e.$$state.pur=!0);b.reject("canceled");c(a);return!0};return e}]}function Mf(){this.$get=["$browser","$q","$$q","$rootScope",function(a,b,d,c){return function(e,f){return function(g,k,h,l){function m(){p?g.apply(null,n):g(s)}var p=4<arguments.length,n=p?Ha.call(arguments,4):[],s=0,G=w(l)&&!l,t=(G?d:b).defer(),r=t.promise;h=w(h)?h:0;r.$$intervalId=e(function(){G?a.defer(m):c.$evalAsync(m);t.notify(s++);
|
|
||||||
0<h&&s>=h&&(t.resolve(s),f(r.$$intervalId));G||c.$apply()},k,t,G);return r}}}]}function Ad(a,b){var d=ga(a);b.$$protocol=d.protocol;b.$$host=d.hostname;b.$$port=fa(d.port)||Mg[d.protocol]||null}function Bd(a,b,d){if(Ng.test(a))throw jb("badpath",a);var c="/"!==a.charAt(0);c&&(a="/"+a);a=ga(a);for(var c=(c&&"/"===a.pathname.charAt(0)?a.pathname.substring(1):a.pathname).split("/"),e=c.length;e--;)c[e]=decodeURIComponent(c[e]),d&&(c[e]=c[e].replace(/\//g,"%2F"));d=c.join("/");b.$$path=d;b.$$search=gc(a.search);
|
|
||||||
b.$$hash=decodeURIComponent(a.hash);b.$$path&&"/"!==b.$$path.charAt(0)&&(b.$$path="/"+b.$$path)}function xc(a,b){return a.slice(0,b.length)===b}function xa(a,b){if(xc(b,a))return b.substr(a.length)}function Da(a){var b=a.indexOf("#");return-1===b?a:a.substr(0,b)}function yc(a,b,d){this.$$html5=!0;d=d||"";Ad(a,this);this.$$parse=function(a){var d=xa(b,a);if(!A(d))throw jb("ipthprfx",a,b);Bd(d,this,!0);this.$$path||(this.$$path="/");this.$$compose()};this.$$normalizeUrl=function(a){return b+a.substr(1)};
|
|
||||||
this.$$parseLinkUrl=function(c,e){if(e&&"#"===e[0])return this.hash(e.slice(1)),!0;var f,g;w(f=xa(a,c))?(g=f,g=d&&w(f=xa(d,f))?b+(xa("/",f)||f):a+g):w(f=xa(b,c))?g=b+f:b===c+"/"&&(g=b);g&&this.$$parse(g);return!!g}}function zc(a,b,d){Ad(a,this);this.$$parse=function(c){var e=xa(a,c)||xa(b,c),f;z(e)||"#"!==e.charAt(0)?this.$$html5?f=e:(f="",z(e)&&(a=c,this.replace())):(f=xa(d,e),z(f)&&(f=e));Bd(f,this,!1);c=this.$$path;var e=a,g=/^\/[A-Z]:(\/.*)/;xc(f,e)&&(f=f.replace(e,""));g.exec(f)||(c=(f=g.exec(c))?
|
|
||||||
f[1]:c);this.$$path=c;this.$$compose()};this.$$normalizeUrl=function(b){return a+(b?d+b:"")};this.$$parseLinkUrl=function(b,d){return Da(a)===Da(b)?(this.$$parse(b),!0):!1}}function Cd(a,b,d){this.$$html5=!0;zc.apply(this,arguments);this.$$parseLinkUrl=function(c,e){if(e&&"#"===e[0])return this.hash(e.slice(1)),!0;var f,g;a===Da(c)?f=c:(g=xa(b,c))?f=a+d+g:b===c+"/"&&(f=b);f&&this.$$parse(f);return!!f};this.$$normalizeUrl=function(b){return a+d+b}}function Lb(a){return function(){return this[a]}}function Dd(a,
|
|
||||||
b){return function(d){if(z(d))return this[a];this[a]=b(d);this.$$compose();return this}}function Tf(){var a="!",b={enabled:!1,requireBase:!0,rewriteLinks:!0};this.hashPrefix=function(b){return w(b)?(a=b,this):a};this.html5Mode=function(a){if(Ga(a))return b.enabled=a,this;if(D(a)){Ga(a.enabled)&&(b.enabled=a.enabled);Ga(a.requireBase)&&(b.requireBase=a.requireBase);if(Ga(a.rewriteLinks)||A(a.rewriteLinks))b.rewriteLinks=a.rewriteLinks;return this}return b};this.$get=["$rootScope","$browser","$sniffer",
|
|
||||||
"$rootElement","$window",function(d,c,e,f,g){function k(a,b){return a===b||ga(a).href===ga(b).href}function h(a,b,d){var e=m.url(),f=m.$$state;try{c.url(a,b,d),m.$$state=c.state()}catch(g){throw m.url(e),m.$$state=f,g;}}function l(a,b){d.$broadcast("$locationChangeSuccess",m.absUrl(),a,m.$$state,b)}var m,p;p=c.baseHref();var n=c.url(),s;if(b.enabled){if(!p&&b.requireBase)throw jb("nobase");s=n.substring(0,n.indexOf("/",n.indexOf("//")+2))+(p||"/");p=e.history?yc:Cd}else s=Da(n),p=zc;var r=s.substr(0,
|
|
||||||
Da(s).lastIndexOf("/")+1);m=new p(s,r,"#"+a);m.$$parseLinkUrl(n,n);m.$$state=c.state();var t=/^\s*(javascript|mailto):/i;f.on("click",function(a){var e=b.rewriteLinks;if(e&&!a.ctrlKey&&!a.metaKey&&!a.shiftKey&&2!==a.which&&2!==a.button){for(var g=x(a.target);"a"!==ua(g[0]);)if(g[0]===f[0]||!(g=g.parent())[0])return;if(!A(e)||!z(g.attr(e))){var e=g.prop("href"),h=g.attr("href")||g.attr("xlink:href");D(e)&&"[object SVGAnimatedString]"===e.toString()&&(e=ga(e.animVal).href);t.test(e)||!e||g.attr("target")||
|
|
||||||
a.isDefaultPrevented()||!m.$$parseLinkUrl(e,h)||(a.preventDefault(),m.absUrl()!==c.url()&&d.$apply())}}});m.absUrl()!==n&&c.url(m.absUrl(),!0);var N=!0;c.onUrlChange(function(a,b){xc(a,r)?(d.$evalAsync(function(){var c=m.absUrl(),e=m.$$state,f;m.$$parse(a);m.$$state=b;f=d.$broadcast("$locationChangeStart",a,c,b,e).defaultPrevented;m.absUrl()===a&&(f?(m.$$parse(c),m.$$state=e,h(c,!1,e)):(N=!1,l(c,e)))}),d.$$phase||d.$digest()):g.location.href=a});d.$watch(function(){if(N||m.$$urlUpdatedByLocation){m.$$urlUpdatedByLocation=
|
|
||||||
!1;var a=c.url(),b=m.absUrl(),f=c.state(),g=m.$$replace,n=!k(a,b)||m.$$html5&&e.history&&f!==m.$$state;if(N||n)N=!1,d.$evalAsync(function(){var b=m.absUrl(),c=d.$broadcast("$locationChangeStart",b,a,m.$$state,f).defaultPrevented;m.absUrl()===b&&(c?(m.$$parse(a),m.$$state=f):(n&&h(b,g,f===m.$$state?null:m.$$state),l(a,f)))})}m.$$replace=!1});return m}]}function Uf(){var a=!0,b=this;this.debugEnabled=function(b){return w(b)?(a=b,this):a};this.$get=["$window",function(d){function c(a){cc(a)&&(a.stack&&
|
|
||||||
f?a=a.message&&-1===a.stack.indexOf(a.message)?"Error: "+a.message+"\n"+a.stack:a.stack:a.sourceURL&&(a=a.message+"\n"+a.sourceURL+":"+a.line));return a}function e(a){var b=d.console||{},e=b[a]||b.log||E;return function(){var a=[];r(arguments,function(b){a.push(c(b))});return Function.prototype.apply.call(e,b,a)}}var f=Ca||/\bEdge\//.test(d.navigator&&d.navigator.userAgent);return{log:e("log"),info:e("info"),warn:e("warn"),error:e("error"),debug:function(){var c=e("debug");return function(){a&&c.apply(b,
|
|
||||||
arguments)}}()}}]}function Og(a){return a+""}function Pg(a,b){return"undefined"!==typeof a?a:b}function Ed(a,b){return"undefined"===typeof a?b:"undefined"===typeof b?a:a+b}function Qg(a,b){switch(a.type){case q.MemberExpression:if(a.computed)return!1;break;case q.UnaryExpression:return 1;case q.BinaryExpression:return"+"!==a.operator?1:!1;case q.CallExpression:return!1}return void 0===b?Fd:b}function Z(a,b,d){var c,e,f=a.isPure=Qg(a,d);switch(a.type){case q.Program:c=!0;r(a.body,function(a){Z(a.expression,
|
|
||||||
b,f);c=c&&a.expression.constant});a.constant=c;break;case q.Literal:a.constant=!0;a.toWatch=[];break;case q.UnaryExpression:Z(a.argument,b,f);a.constant=a.argument.constant;a.toWatch=a.argument.toWatch;break;case q.BinaryExpression:Z(a.left,b,f);Z(a.right,b,f);a.constant=a.left.constant&&a.right.constant;a.toWatch=a.left.toWatch.concat(a.right.toWatch);break;case q.LogicalExpression:Z(a.left,b,f);Z(a.right,b,f);a.constant=a.left.constant&&a.right.constant;a.toWatch=a.constant?[]:[a];break;case q.ConditionalExpression:Z(a.test,
|
|
||||||
b,f);Z(a.alternate,b,f);Z(a.consequent,b,f);a.constant=a.test.constant&&a.alternate.constant&&a.consequent.constant;a.toWatch=a.constant?[]:[a];break;case q.Identifier:a.constant=!1;a.toWatch=[a];break;case q.MemberExpression:Z(a.object,b,f);a.computed&&Z(a.property,b,f);a.constant=a.object.constant&&(!a.computed||a.property.constant);a.toWatch=a.constant?[]:[a];break;case q.CallExpression:c=d=a.filter?!b(a.callee.name).$stateful:!1;e=[];r(a.arguments,function(a){Z(a,b,f);c=c&&a.constant;e.push.apply(e,
|
|
||||||
a.toWatch)});a.constant=c;a.toWatch=d?e:[a];break;case q.AssignmentExpression:Z(a.left,b,f);Z(a.right,b,f);a.constant=a.left.constant&&a.right.constant;a.toWatch=[a];break;case q.ArrayExpression:c=!0;e=[];r(a.elements,function(a){Z(a,b,f);c=c&&a.constant;e.push.apply(e,a.toWatch)});a.constant=c;a.toWatch=e;break;case q.ObjectExpression:c=!0;e=[];r(a.properties,function(a){Z(a.value,b,f);c=c&&a.value.constant;e.push.apply(e,a.value.toWatch);a.computed&&(Z(a.key,b,!1),c=c&&a.key.constant,e.push.apply(e,
|
|
||||||
a.key.toWatch))});a.constant=c;a.toWatch=e;break;case q.ThisExpression:a.constant=!1;a.toWatch=[];break;case q.LocalsExpression:a.constant=!1,a.toWatch=[]}}function Gd(a){if(1===a.length){a=a[0].expression;var b=a.toWatch;return 1!==b.length?b:b[0]!==a?b:void 0}}function Hd(a){return a.type===q.Identifier||a.type===q.MemberExpression}function Id(a){if(1===a.body.length&&Hd(a.body[0].expression))return{type:q.AssignmentExpression,left:a.body[0].expression,right:{type:q.NGValueParameter},operator:"="}}
|
|
||||||
function Jd(a){this.$filter=a}function Kd(a){this.$filter=a}function Mb(a,b,d){this.ast=new q(a,d);this.astCompiler=d.csp?new Kd(b):new Jd(b)}function Ac(a){return B(a.valueOf)?a.valueOf():Rg.call(a)}function Vf(){var a=T(),b={"true":!0,"false":!1,"null":null,undefined:void 0},d,c;this.addLiteral=function(a,c){b[a]=c};this.setIdentifierFns=function(a,b){d=a;c=b;return this};this.$get=["$filter",function(e){function f(b,c){var d,f;switch(typeof b){case "string":return f=b=b.trim(),d=a[f],d||(d=new Nb(G),
|
|
||||||
d=(new Mb(d,e,G)).parse(b),a[f]=p(d)),s(d,c);case "function":return s(b,c);default:return s(E,c)}}function g(a,b,c){return null==a||null==b?a===b:"object"!==typeof a||(a=Ac(a),"object"!==typeof a||c)?a===b||a!==a&&b!==b:!1}function k(a,b,c,d,e){var f=d.inputs,h;if(1===f.length){var k=g,f=f[0];return a.$watch(function(a){var b=f(a);g(b,k,f.isPure)||(h=d(a,void 0,void 0,[b]),k=b&&Ac(b));return h},b,c,e)}for(var l=[],m=[],n=0,p=f.length;n<p;n++)l[n]=g,m[n]=null;return a.$watch(function(a){for(var b=
|
|
||||||
!1,c=0,e=f.length;c<e;c++){var k=f[c](a);if(b||(b=!g(k,l[c],f[c].isPure)))m[c]=k,l[c]=k&&Ac(k)}b&&(h=d(a,void 0,void 0,m));return h},b,c,e)}function h(a,b,c,d,e){function f(){h(m)&&k()}function g(a,b,c,d){m=u&&d?d[0]:n(a,b,c,d);h(m)&&a.$$postDigest(f);return s(m)}var h=d.literal?l:w,k,m,n=d.$$intercepted||d,s=d.$$interceptor||Ta,u=d.inputs&&!n.inputs;g.literal=d.literal;g.constant=d.constant;g.inputs=d.inputs;p(g);return k=a.$watch(g,b,c,e)}function l(a){var b=!0;r(a,function(a){w(a)||(b=!1)});return b}
|
|
||||||
function m(a,b,c,d){var e=a.$watch(function(a){e();return d(a)},b,c);return e}function p(a){a.constant?a.$$watchDelegate=m:a.oneTime?a.$$watchDelegate=h:a.inputs&&(a.$$watchDelegate=k);return a}function n(a,b){function c(d){return b(a(d))}c.$stateful=a.$stateful||b.$stateful;c.$$pure=a.$$pure&&b.$$pure;return c}function s(a,b){if(!b)return a;a.$$interceptor&&(b=n(a.$$interceptor,b),a=a.$$intercepted);var c=!1,d=function(d,e,f,g){d=c&&g?g[0]:a(d,e,f,g);return b(d)};d.$$intercepted=a;d.$$interceptor=
|
|
||||||
b;d.literal=a.literal;d.oneTime=a.oneTime;d.constant=a.constant;b.$stateful||(c=!a.inputs,d.inputs=a.inputs?a.inputs:[a],b.$$pure||(d.inputs=d.inputs.map(function(a){return a.isPure===Fd?function(b){return a(b)}:a})));return p(d)}var G={csp:Aa().noUnsafeEval,literals:Ia(b),isIdentifierStart:B(d)&&d,isIdentifierContinue:B(c)&&c};f.$$getAst=function(a){var b=new Nb(G);return(new Mb(b,e,G)).getAst(a).ast};return f}]}function Xf(){var a=!0;this.$get=["$rootScope","$exceptionHandler",function(b,d){return Ld(function(a){b.$evalAsync(a)},
|
|
||||||
d,a)}];this.errorOnUnhandledRejections=function(b){return w(b)?(a=b,this):a}}function Yf(){var a=!0;this.$get=["$browser","$exceptionHandler",function(b,d){return Ld(function(a){b.defer(a)},d,a)}];this.errorOnUnhandledRejections=function(b){return w(b)?(a=b,this):a}}function Ld(a,b,d){function c(){return new e}function e(){var a=this.promise=new f;this.resolve=function(b){h(a,b)};this.reject=function(b){m(a,b)};this.notify=function(b){n(a,b)}}function f(){this.$$state={status:0}}function g(){for(;!w&&
|
|
||||||
x.length;){var a=x.shift();if(!a.pur){a.pur=!0;var c=a.value,c="Possibly unhandled rejection: "+("function"===typeof c?c.toString().replace(/ \{[\s\S]*$/,""):z(c)?"undefined":"string"!==typeof c?Ie(c,void 0):c);cc(a.value)?b(a.value,c):b(c)}}}function k(c){!d||c.pending||2!==c.status||c.pur||(0===w&&0===x.length&&a(g),x.push(c));!c.processScheduled&&c.pending&&(c.processScheduled=!0,++w,a(function(){var e,f,k;k=c.pending;c.processScheduled=!1;c.pending=void 0;try{for(var l=0,n=k.length;l<n;++l){c.pur=
|
|
||||||
!0;f=k[l][0];e=k[l][c.status];try{B(e)?h(f,e(c.value)):1===c.status?h(f,c.value):m(f,c.value)}catch(p){m(f,p),p&&!0===p.$$passToExceptionHandler&&b(p)}}}finally{--w,d&&0===w&&a(g)}}))}function h(a,b){a.$$state.status||(b===a?p(a,v("qcycle",b)):l(a,b))}function l(a,b){function c(b){g||(g=!0,l(a,b))}function d(b){g||(g=!0,p(a,b))}function e(b){n(a,b)}var f,g=!1;try{if(D(b)||B(b))f=b.then;B(f)?(a.$$state.status=-1,f.call(b,c,d,e)):(a.$$state.value=b,a.$$state.status=1,k(a.$$state))}catch(h){d(h)}}function m(a,
|
|
||||||
b){a.$$state.status||p(a,b)}function p(a,b){a.$$state.value=b;a.$$state.status=2;k(a.$$state)}function n(c,d){var e=c.$$state.pending;0>=c.$$state.status&&e&&e.length&&a(function(){for(var a,c,f=0,g=e.length;f<g;f++){c=e[f][0];a=e[f][3];try{n(c,B(a)?a(d):d)}catch(h){b(h)}}})}function s(a){var b=new f;m(b,a);return b}function G(a,b,c){var d=null;try{B(c)&&(d=c())}catch(e){return s(e)}return d&&B(d.then)?d.then(function(){return b(a)},s):b(a)}function t(a,b,c,d){var e=new f;h(e,a);return e.then(b,c,
|
|
||||||
d)}function q(a){if(!B(a))throw v("norslvr",a);var b=new f;a(function(a){h(b,a)},function(a){m(b,a)});return b}var v=F("$q",TypeError),w=0,x=[];S(f.prototype,{then:function(a,b,c){if(z(a)&&z(b)&&z(c))return this;var d=new f;this.$$state.pending=this.$$state.pending||[];this.$$state.pending.push([d,a,b,c]);0<this.$$state.status&&k(this.$$state);return d},"catch":function(a){return this.then(null,a)},"finally":function(a,b){return this.then(function(b){return G(b,y,a)},function(b){return G(b,s,a)},
|
|
||||||
b)}});var y=t;q.prototype=f.prototype;q.defer=c;q.reject=s;q.when=t;q.resolve=y;q.all=function(a){var b=new f,c=0,d=H(a)?[]:{};r(a,function(a,e){c++;t(a).then(function(a){d[e]=a;--c||h(b,d)},function(a){m(b,a)})});0===c&&h(b,d);return b};q.race=function(a){var b=c();r(a,function(a){t(a).then(b.resolve,b.reject)});return b.promise};return q}function hg(){this.$get=["$window","$timeout",function(a,b){var d=a.requestAnimationFrame||a.webkitRequestAnimationFrame,c=a.cancelAnimationFrame||a.webkitCancelAnimationFrame||
|
|
||||||
a.webkitCancelRequestAnimationFrame,e=!!d,f=e?function(a){var b=d(a);return function(){c(b)}}:function(a){var c=b(a,16.66,!1);return function(){b.cancel(c)}};f.supported=e;return f}]}function Wf(){function a(a){function b(){this.$$watchers=this.$$nextSibling=this.$$childHead=this.$$childTail=null;this.$$listeners={};this.$$listenerCount={};this.$$watchersCount=0;this.$id=++pb;this.$$ChildScope=null;this.$$suspended=!1}b.prototype=a;return b}var b=10,d=F("$rootScope"),c=null,e=null;this.digestTtl=
|
|
||||||
function(a){arguments.length&&(b=a);return b};this.$get=["$exceptionHandler","$parse","$browser",function(f,g,k){function h(a){a.currentScope.$$destroyed=!0}function l(a){9===Ca&&(a.$$childHead&&l(a.$$childHead),a.$$nextSibling&&l(a.$$nextSibling));a.$parent=a.$$nextSibling=a.$$prevSibling=a.$$childHead=a.$$childTail=a.$root=a.$$watchers=null}function m(){this.$id=++pb;this.$$phase=this.$parent=this.$$watchers=this.$$nextSibling=this.$$prevSibling=this.$$childHead=this.$$childTail=null;this.$root=
|
|
||||||
this;this.$$suspended=this.$$destroyed=!1;this.$$listeners={};this.$$listenerCount={};this.$$watchersCount=0;this.$$isolateBindings=null}function p(a){if(v.$$phase)throw d("inprog",v.$$phase);v.$$phase=a}function n(a,b){do a.$$watchersCount+=b;while(a=a.$parent)}function s(a,b,c){do a.$$listenerCount[c]-=b,0===a.$$listenerCount[c]&&delete a.$$listenerCount[c];while(a=a.$parent)}function G(){}function t(){for(;y.length;)try{y.shift()()}catch(a){f(a)}e=null}function q(){null===e&&(e=k.defer(function(){v.$apply(t)},
|
|
||||||
null,"$applyAsync"))}m.prototype={constructor:m,$new:function(b,c){var d;c=c||this;b?(d=new m,d.$root=this.$root):(this.$$ChildScope||(this.$$ChildScope=a(this)),d=new this.$$ChildScope);d.$parent=c;d.$$prevSibling=c.$$childTail;c.$$childHead?(c.$$childTail.$$nextSibling=d,c.$$childTail=d):c.$$childHead=c.$$childTail=d;(b||c!==this)&&d.$on("$destroy",h);return d},$watch:function(a,b,d,e){var f=g(a);b=B(b)?b:E;if(f.$$watchDelegate)return f.$$watchDelegate(this,b,d,f,a);var h=this,k=h.$$watchers,l=
|
|
||||||
{fn:b,last:G,get:f,exp:e||a,eq:!!d};c=null;k||(k=h.$$watchers=[],k.$$digestWatchIndex=-1);k.unshift(l);k.$$digestWatchIndex++;n(this,1);return function(){var a=cb(k,l);0<=a&&(n(h,-1),a<k.$$digestWatchIndex&&k.$$digestWatchIndex--);c=null}},$watchGroup:function(a,b){function c(){h=!1;try{k?(k=!1,b(e,e,g)):b(e,d,g)}finally{for(var f=0;f<a.length;f++)d[f]=e[f]}}var d=Array(a.length),e=Array(a.length),f=[],g=this,h=!1,k=!0;if(!a.length){var l=!0;g.$evalAsync(function(){l&&b(e,e,g)});return function(){l=
|
|
||||||
!1}}if(1===a.length)return this.$watch(a[0],function(a,c,f){e[0]=a;d[0]=c;b(e,a===c?e:d,f)});r(a,function(a,b){var d=g.$watch(a,function(a){e[b]=a;h||(h=!0,g.$evalAsync(c))});f.push(d)});return function(){for(;f.length;)f.shift()()}},$watchCollection:function(a,b){function c(a){e=a;var b,d,g,h;if(!z(e)){if(D(e))if(ya(e))for(f!==n&&(f=n,t=f.length=0,l++),a=e.length,t!==a&&(l++,f.length=t=a),b=0;b<a;b++)h=f[b],g=e[b],d=h!==h&&g!==g,d||h===g||(l++,f[b]=g);else{f!==p&&(f=p={},t=0,l++);a=0;for(b in e)ta.call(e,
|
|
||||||
b)&&(a++,g=e[b],h=f[b],b in f?(d=h!==h&&g!==g,d||h===g||(l++,f[b]=g)):(t++,f[b]=g,l++));if(t>a)for(b in l++,f)ta.call(e,b)||(t--,delete f[b])}else f!==e&&(f=e,l++);return l}}c.$$pure=g(a).literal;c.$stateful=!c.$$pure;var d=this,e,f,h,k=1<b.length,l=0,m=g(a,c),n=[],p={},s=!0,t=0;return this.$watch(m,function(){s?(s=!1,b(e,e,d)):b(e,h,d);if(k)if(D(e))if(ya(e)){h=Array(e.length);for(var a=0;a<e.length;a++)h[a]=e[a]}else for(a in h={},e)ta.call(e,a)&&(h[a]=e[a]);else h=e})},$digest:function(){var a,
|
|
||||||
g,h,l,m,n,s,r=b,q,y=w.length?v:this,N=[],z,A;p("$digest");k.$$checkUrlChange();this===v&&null!==e&&(k.defer.cancel(e),t());c=null;do{s=!1;q=y;for(n=0;n<w.length;n++){try{A=w[n],l=A.fn,l(A.scope,A.locals)}catch(C){f(C)}c=null}w.length=0;a:do{if(n=!q.$$suspended&&q.$$watchers)for(n.$$digestWatchIndex=n.length;n.$$digestWatchIndex--;)try{if(a=n[n.$$digestWatchIndex])if(m=a.get,(g=m(q))!==(h=a.last)&&!(a.eq?va(g,h):X(g)&&X(h)))s=!0,c=a,a.last=a.eq?Ia(g,null):g,l=a.fn,l(g,h===G?g:h,q),5>r&&(z=4-r,N[z]||
|
|
||||||
(N[z]=[]),N[z].push({msg:B(a.exp)?"fn: "+(a.exp.name||a.exp.toString()):a.exp,newVal:g,oldVal:h}));else if(a===c){s=!1;break a}}catch(E){f(E)}if(!(n=!q.$$suspended&&q.$$watchersCount&&q.$$childHead||q!==y&&q.$$nextSibling))for(;q!==y&&!(n=q.$$nextSibling);)q=q.$parent}while(q=n);if((s||w.length)&&!r--)throw v.$$phase=null,d("infdig",b,N);}while(s||w.length);for(v.$$phase=null;J<x.length;)try{x[J++]()}catch(D){f(D)}x.length=J=0;k.$$checkUrlChange()},$suspend:function(){this.$$suspended=!0},$isSuspended:function(){return this.$$suspended},
|
|
||||||
$resume:function(){this.$$suspended=!1},$destroy:function(){if(!this.$$destroyed){var a=this.$parent;this.$broadcast("$destroy");this.$$destroyed=!0;this===v&&k.$$applicationDestroyed();n(this,-this.$$watchersCount);for(var b in this.$$listenerCount)s(this,this.$$listenerCount[b],b);a&&a.$$childHead===this&&(a.$$childHead=this.$$nextSibling);a&&a.$$childTail===this&&(a.$$childTail=this.$$prevSibling);this.$$prevSibling&&(this.$$prevSibling.$$nextSibling=this.$$nextSibling);this.$$nextSibling&&(this.$$nextSibling.$$prevSibling=
|
|
||||||
this.$$prevSibling);this.$destroy=this.$digest=this.$apply=this.$evalAsync=this.$applyAsync=E;this.$on=this.$watch=this.$watchGroup=function(){return E};this.$$listeners={};this.$$nextSibling=null;l(this)}},$eval:function(a,b){return g(a)(this,b)},$evalAsync:function(a,b){v.$$phase||w.length||k.defer(function(){w.length&&v.$digest()},null,"$evalAsync");w.push({scope:this,fn:g(a),locals:b})},$$postDigest:function(a){x.push(a)},$apply:function(a){try{p("$apply");try{return this.$eval(a)}finally{v.$$phase=
|
|
||||||
null}}catch(b){f(b)}finally{try{v.$digest()}catch(c){throw f(c),c;}}},$applyAsync:function(a){function b(){c.$eval(a)}var c=this;a&&y.push(b);a=g(a);q()},$on:function(a,b){var c=this.$$listeners[a];c||(this.$$listeners[a]=c=[]);c.push(b);var d=this;do d.$$listenerCount[a]||(d.$$listenerCount[a]=0),d.$$listenerCount[a]++;while(d=d.$parent);var e=this;return function(){var d=c.indexOf(b);-1!==d&&(delete c[d],s(e,1,a))}},$emit:function(a,b){var c=[],d,e=this,g=!1,h={name:a,targetScope:e,stopPropagation:function(){g=
|
|
||||||
!0},preventDefault:function(){h.defaultPrevented=!0},defaultPrevented:!1},k=db([h],arguments,1),l,m;do{d=e.$$listeners[a]||c;h.currentScope=e;l=0;for(m=d.length;l<m;l++)if(d[l])try{d[l].apply(null,k)}catch(n){f(n)}else d.splice(l,1),l--,m--;if(g)break;e=e.$parent}while(e);h.currentScope=null;return h},$broadcast:function(a,b){var c=this,d=this,e={name:a,targetScope:this,preventDefault:function(){e.defaultPrevented=!0},defaultPrevented:!1};if(!this.$$listenerCount[a])return e;for(var g=db([e],arguments,
|
|
||||||
1),h,k;c=d;){e.currentScope=c;d=c.$$listeners[a]||[];h=0;for(k=d.length;h<k;h++)if(d[h])try{d[h].apply(null,g)}catch(l){f(l)}else d.splice(h,1),h--,k--;if(!(d=c.$$listenerCount[a]&&c.$$childHead||c!==this&&c.$$nextSibling))for(;c!==this&&!(d=c.$$nextSibling);)c=c.$parent}e.currentScope=null;return e}};var v=new m,w=v.$$asyncQueue=[],x=v.$$postDigestQueue=[],y=v.$$applyAsyncQueue=[],J=0;return v}]}function Le(){var a=/^\s*(https?|s?ftp|mailto|tel|file):/,b=/^\s*((https?|ftp|file|blob):|data:image\/)/;
|
|
||||||
this.aHrefSanitizationWhitelist=function(b){return w(b)?(a=b,this):a};this.imgSrcSanitizationWhitelist=function(a){return w(a)?(b=a,this):b};this.$get=function(){return function(d,c){var e=c?b:a,f=ga(d&&d.trim()).href;return""===f||f.match(e)?d:"unsafe:"+f}}}function Sg(a){if("self"===a)return a;if(A(a)){if(-1<a.indexOf("***"))throw Ea("iwcard",a);a=Md(a).replace(/\\\*\\\*/g,".*").replace(/\\\*/g,"[^:/.?&;]*");return new RegExp("^"+a+"$")}if(ab(a))return new RegExp("^"+a.source+"$");throw Ea("imatcher");
|
|
||||||
}function Nd(a){var b=[];w(a)&&r(a,function(a){b.push(Sg(a))});return b}function $f(){this.SCE_CONTEXTS=V;var a=["self"],b=[];this.resourceUrlWhitelist=function(b){arguments.length&&(a=Nd(b));return a};this.resourceUrlBlacklist=function(a){arguments.length&&(b=Nd(a));return b};this.$get=["$injector","$$sanitizeUri",function(d,c){function e(a,b){var c;"self"===a?(c=Bc(b,Od))||(C.document.baseURI?c=C.document.baseURI:(Na||(Na=C.document.createElement("a"),Na.href=".",Na=Na.cloneNode(!1)),c=Na.href),
|
|
||||||
c=Bc(b,c)):c=!!a.exec(b.href);return c}function f(a){var b=function(a){this.$$unwrapTrustedValue=function(){return a}};a&&(b.prototype=new a);b.prototype.valueOf=function(){return this.$$unwrapTrustedValue()};b.prototype.toString=function(){return this.$$unwrapTrustedValue().toString()};return b}var g=function(a){throw Ea("unsafe");};d.has("$sanitize")&&(g=d.get("$sanitize"));var k=f(),h={};h[V.HTML]=f(k);h[V.CSS]=f(k);h[V.MEDIA_URL]=f(k);h[V.URL]=f(h[V.MEDIA_URL]);h[V.JS]=f(k);h[V.RESOURCE_URL]=
|
|
||||||
f(h[V.URL]);return{trustAs:function(a,b){var c=h.hasOwnProperty(a)?h[a]:null;if(!c)throw Ea("icontext",a,b);if(null===b||z(b)||""===b)return b;if("string"!==typeof b)throw Ea("itype",a);return new c(b)},getTrusted:function(d,f){if(null===f||z(f)||""===f)return f;var k=h.hasOwnProperty(d)?h[d]:null;if(k&&f instanceof k)return f.$$unwrapTrustedValue();B(f.$$unwrapTrustedValue)&&(f=f.$$unwrapTrustedValue());if(d===V.MEDIA_URL||d===V.URL)return c(f.toString(),d===V.MEDIA_URL);if(d===V.RESOURCE_URL){var k=
|
|
||||||
ga(f.toString()),n,s,r=!1;n=0;for(s=a.length;n<s;n++)if(e(a[n],k)){r=!0;break}if(r)for(n=0,s=b.length;n<s;n++)if(e(b[n],k)){r=!1;break}if(r)return f;throw Ea("insecurl",f.toString());}if(d===V.HTML)return g(f);throw Ea("unsafe");},valueOf:function(a){return a instanceof k?a.$$unwrapTrustedValue():a}}}]}function Zf(){var a=!0;this.enabled=function(b){arguments.length&&(a=!!b);return a};this.$get=["$parse","$sceDelegate",function(b,d){if(a&&8>Ca)throw Ea("iequirks");var c=ja(V);c.isEnabled=function(){return a};
|
|
||||||
c.trustAs=d.trustAs;c.getTrusted=d.getTrusted;c.valueOf=d.valueOf;a||(c.trustAs=c.getTrusted=function(a,b){return b},c.valueOf=Ta);c.parseAs=function(a,d){var e=b(d);return e.literal&&e.constant?e:b(d,function(b){return c.getTrusted(a,b)})};var e=c.parseAs,f=c.getTrusted,g=c.trustAs;r(V,function(a,b){var d=K(b);c[("parse_as_"+d).replace(Cc,wb)]=function(b){return e(a,b)};c[("get_trusted_"+d).replace(Cc,wb)]=function(b){return f(a,b)};c[("trust_as_"+d).replace(Cc,wb)]=function(b){return g(a,b)}});
|
|
||||||
return c}]}function ag(){this.$get=["$window","$document",function(a,b){var d={},c=!((!a.nw||!a.nw.process)&&a.chrome&&(a.chrome.app&&a.chrome.app.runtime||!a.chrome.app&&a.chrome.runtime&&a.chrome.runtime.id))&&a.history&&a.history.pushState,e=fa((/android (\d+)/.exec(K((a.navigator||{}).userAgent))||[])[1]),f=/Boxee/i.test((a.navigator||{}).userAgent),g=b[0]||{},k=g.body&&g.body.style,h=!1,l=!1;k&&(h=!!("transition"in k||"webkitTransition"in k),l=!!("animation"in k||"webkitAnimation"in k));return{history:!(!c||
|
|
||||||
4>e||f),hasEvent:function(a){if("input"===a&&Ca)return!1;if(z(d[a])){var b=g.createElement("div");d[a]="on"+a in b}return d[a]},csp:Aa(),transitions:h,animations:l,android:e}}]}function bg(){this.$get=ia(function(a){return new Tg(a)})}function Tg(a){function b(){var a=e.pop();return a&&a.cb}function d(a){for(var b=e.length-1;0<=b;--b){var c=e[b];if(c.type===a)return e.splice(b,1),c.cb}}var c={},e=[],f=this.ALL_TASKS_TYPE="$$all$$",g=this.DEFAULT_TASK_TYPE="$$default$$";this.completeTask=function(e,
|
|
||||||
h){h=h||g;try{e()}finally{var l;l=h||g;c[l]&&(c[l]--,c[f]--);l=c[h];var m=c[f];if(!m||!l)for(l=m?d:b;m=l(h);)try{m()}catch(p){a.error(p)}}};this.incTaskCount=function(a){a=a||g;c[a]=(c[a]||0)+1;c[f]=(c[f]||0)+1};this.notifyWhenNoPendingTasks=function(a,b){b=b||f;c[b]?e.push({type:b,cb:a}):a()}}function dg(){var a;this.httpOptions=function(b){return b?(a=b,this):a};this.$get=["$exceptionHandler","$templateCache","$http","$q","$sce",function(b,d,c,e,f){function g(k,h){g.totalPendingRequests++;if(!A(k)||
|
|
||||||
z(d.get(k)))k=f.getTrustedResourceUrl(k);var l=c.defaults&&c.defaults.transformResponse;H(l)?l=l.filter(function(a){return a!==vc}):l===vc&&(l=null);return c.get(k,S({cache:d,transformResponse:l},a)).finally(function(){g.totalPendingRequests--}).then(function(a){return d.put(k,a.data)},function(a){h||(a=Ug("tpload",k,a.status,a.statusText),b(a));return e.reject(a)})}g.totalPendingRequests=0;return g}]}function eg(){this.$get=["$rootScope","$browser","$location",function(a,b,d){return{findBindings:function(a,
|
|
||||||
b,d){a=a.getElementsByClassName("ng-binding");var g=[];r(a,function(a){var c=ca.element(a).data("$binding");c&&r(c,function(c){d?(new RegExp("(^|\\s)"+Md(b)+"(\\s|\\||$)")).test(c)&&g.push(a):-1!==c.indexOf(b)&&g.push(a)})});return g},findModels:function(a,b,d){for(var g=["ng-","data-ng-","ng\\:"],k=0;k<g.length;++k){var h=a.querySelectorAll("["+g[k]+"model"+(d?"=":"*=")+'"'+b+'"]');if(h.length)return h}},getLocation:function(){return d.url()},setLocation:function(b){b!==d.url()&&(d.url(b),a.$digest())},
|
|
||||||
whenStable:function(a){b.notifyWhenNoOutstandingRequests(a)}}}]}function fg(){this.$get=["$rootScope","$browser","$q","$$q","$exceptionHandler",function(a,b,d,c,e){function f(f,h,l){B(f)||(l=h,h=f,f=E);var m=Ha.call(arguments,3),p=w(l)&&!l,n=(p?c:d).defer(),s=n.promise,r;r=b.defer(function(){try{n.resolve(f.apply(null,m))}catch(b){n.reject(b),e(b)}finally{delete g[s.$$timeoutId]}p||a.$apply()},h,"$timeout");s.$$timeoutId=r;g[r]=n;return s}var g={};f.cancel=function(a){if(!a)return!1;if(!a.hasOwnProperty("$$timeoutId"))throw Vg("badprom");
|
|
||||||
if(!g.hasOwnProperty(a.$$timeoutId))return!1;a=a.$$timeoutId;var c=g[a],d=c.promise;d.$$state&&(d.$$state.pur=!0);c.reject("canceled");delete g[a];return b.defer.cancel(a)};return f}]}function ga(a){if(!A(a))return a;Ca&&(aa.setAttribute("href",a),a=aa.href);aa.setAttribute("href",a);a=aa.hostname;!Wg&&-1<a.indexOf(":")&&(a="["+a+"]");return{href:aa.href,protocol:aa.protocol?aa.protocol.replace(/:$/,""):"",host:aa.host,search:aa.search?aa.search.replace(/^\?/,""):"",hash:aa.hash?aa.hash.replace(/^#/,
|
|
||||||
""):"",hostname:a,port:aa.port,pathname:"/"===aa.pathname.charAt(0)?aa.pathname:"/"+aa.pathname}}function Jg(a){var b=[Od].concat(a.map(ga));return function(a){a=ga(a);return b.some(Bc.bind(null,a))}}function Bc(a,b){a=ga(a);b=ga(b);return a.protocol===b.protocol&&a.host===b.host}function gg(){this.$get=ia(C)}function Pd(a){function b(a){try{return decodeURIComponent(a)}catch(b){return a}}var d=a[0]||{},c={},e="";return function(){var a,g,k,h,l;try{a=d.cookie||""}catch(m){a=""}if(a!==e)for(e=a,a=
|
|
||||||
e.split("; "),c={},k=0;k<a.length;k++)g=a[k],h=g.indexOf("="),0<h&&(l=b(g.substring(0,h)),z(c[l])&&(c[l]=b(g.substring(h+1))));return c}}function kg(){this.$get=Pd}function dd(a){function b(d,c){if(D(d)){var e={};r(d,function(a,c){e[c]=b(c,a)});return e}return a.factory(d+"Filter",c)}this.register=b;this.$get=["$injector",function(a){return function(b){return a.get(b+"Filter")}}];b("currency",Qd);b("date",Rd);b("filter",Xg);b("json",Yg);b("limitTo",Zg);b("lowercase",$g);b("number",Sd);b("orderBy",
|
|
||||||
Td);b("uppercase",ah)}function Xg(){return function(a,b,d,c){if(!ya(a)){if(null==a)return a;throw F("filter")("notarray",a);}c=c||"$";var e;switch(Dc(b)){case "function":break;case "boolean":case "null":case "number":case "string":e=!0;case "object":b=bh(b,d,c,e);break;default:return a}return Array.prototype.filter.call(a,b)}}function bh(a,b,d,c){var e=D(a)&&d in a;!0===b?b=va:B(b)||(b=function(a,b){if(z(a))return!1;if(null===a||null===b)return a===b;if(D(b)||D(a)&&!bc(a))return!1;a=K(""+a);b=K(""+
|
|
||||||
b);return-1!==a.indexOf(b)});return function(f){return e&&!D(f)?Fa(f,a[d],b,d,!1):Fa(f,a,b,d,c)}}function Fa(a,b,d,c,e,f){var g=Dc(a),k=Dc(b);if("string"===k&&"!"===b.charAt(0))return!Fa(a,b.substring(1),d,c,e);if(H(a))return a.some(function(a){return Fa(a,b,d,c,e)});switch(g){case "object":var h;if(e){for(h in a)if(h.charAt&&"$"!==h.charAt(0)&&Fa(a[h],b,d,c,!0))return!0;return f?!1:Fa(a,b,d,c,!1)}if("object"===k){for(h in b)if(f=b[h],!B(f)&&!z(f)&&(g=h===c,!Fa(g?a:a[h],f,d,c,g,g)))return!1;return!0}return d(a,
|
|
||||||
b);case "function":return!1;default:return d(a,b)}}function Dc(a){return null===a?"null":typeof a}function Qd(a){var b=a.NUMBER_FORMATS;return function(a,c,e){z(c)&&(c=b.CURRENCY_SYM);z(e)&&(e=b.PATTERNS[1].maxFrac);var f=c?/\u00A4/g:/\s*\u00A4\s*/g;return null==a?a:Ud(a,b.PATTERNS[1],b.GROUP_SEP,b.DECIMAL_SEP,e).replace(f,c)}}function Sd(a){var b=a.NUMBER_FORMATS;return function(a,c){return null==a?a:Ud(a,b.PATTERNS[0],b.GROUP_SEP,b.DECIMAL_SEP,c)}}function ch(a){var b=0,d,c,e,f,g;-1<(c=a.indexOf(Vd))&&
|
|
||||||
(a=a.replace(Vd,""));0<(e=a.search(/e/i))?(0>c&&(c=e),c+=+a.slice(e+1),a=a.substring(0,e)):0>c&&(c=a.length);for(e=0;a.charAt(e)===Ec;e++);if(e===(g=a.length))d=[0],c=1;else{for(g--;a.charAt(g)===Ec;)g--;c-=e;d=[];for(f=0;e<=g;e++,f++)d[f]=+a.charAt(e)}c>Wd&&(d=d.splice(0,Wd-1),b=c-1,c=1);return{d:d,e:b,i:c}}function dh(a,b,d,c){var e=a.d,f=e.length-a.i;b=z(b)?Math.min(Math.max(d,f),c):+b;d=b+a.i;c=e[d];if(0<d){e.splice(Math.max(a.i,d));for(var g=d;g<e.length;g++)e[g]=0}else for(f=Math.max(0,f),a.i=
|
|
||||||
1,e.length=Math.max(1,d=b+1),e[0]=0,g=1;g<d;g++)e[g]=0;if(5<=c)if(0>d-1){for(c=0;c>d;c--)e.unshift(0),a.i++;e.unshift(1);a.i++}else e[d-1]++;for(;f<Math.max(0,b);f++)e.push(0);if(b=e.reduceRight(function(a,b,c,d){b+=a;d[c]=b%10;return Math.floor(b/10)},0))e.unshift(b),a.i++}function Ud(a,b,d,c,e){if(!A(a)&&!W(a)||isNaN(a))return"";var f=!isFinite(a),g=!1,k=Math.abs(a)+"",h="";if(f)h="\u221e";else{g=ch(k);dh(g,e,b.minFrac,b.maxFrac);h=g.d;k=g.i;e=g.e;f=[];for(g=h.reduce(function(a,b){return a&&!b},
|
|
||||||
!0);0>k;)h.unshift(0),k++;0<k?f=h.splice(k,h.length):(f=h,h=[0]);k=[];for(h.length>=b.lgSize&&k.unshift(h.splice(-b.lgSize,h.length).join(""));h.length>b.gSize;)k.unshift(h.splice(-b.gSize,h.length).join(""));h.length&&k.unshift(h.join(""));h=k.join(d);f.length&&(h+=c+f.join(""));e&&(h+="e+"+e)}return 0>a&&!g?b.negPre+h+b.negSuf:b.posPre+h+b.posSuf}function Ob(a,b,d,c){var e="";if(0>a||c&&0>=a)c?a=-a+1:(a=-a,e="-");for(a=""+a;a.length<b;)a=Ec+a;d&&(a=a.substr(a.length-b));return e+a}function ea(a,
|
|
||||||
b,d,c,e){d=d||0;return function(f){f=f["get"+a]();if(0<d||f>-d)f+=d;0===f&&-12===d&&(f=12);return Ob(f,b,c,e)}}function kb(a,b,d){return function(c,e){var f=c["get"+a](),g=ub((d?"STANDALONE":"")+(b?"SHORT":"")+a);return e[g][f]}}function Xd(a){var b=(new Date(a,0,1)).getDay();return new Date(a,0,(4>=b?5:12)-b)}function Yd(a){return function(b){var d=Xd(b.getFullYear());b=+new Date(b.getFullYear(),b.getMonth(),b.getDate()+(4-b.getDay()))-+d;b=1+Math.round(b/6048E5);return Ob(b,a)}}function Fc(a,b){return 0>=
|
|
||||||
a.getFullYear()?b.ERAS[0]:b.ERAS[1]}function Rd(a){function b(a){var b;if(b=a.match(d)){a=new Date(0);var f=0,g=0,k=b[8]?a.setUTCFullYear:a.setFullYear,h=b[8]?a.setUTCHours:a.setHours;b[9]&&(f=fa(b[9]+b[10]),g=fa(b[9]+b[11]));k.call(a,fa(b[1]),fa(b[2])-1,fa(b[3]));f=fa(b[4]||0)-f;g=fa(b[5]||0)-g;k=fa(b[6]||0);b=Math.round(1E3*parseFloat("0."+(b[7]||0)));h.call(a,f,g,k,b)}return a}var d=/^(\d{4})-?(\d\d)-?(\d\d)(?:T(\d\d)(?::?(\d\d)(?::?(\d\d)(?:\.(\d+))?)?)?(Z|([+-])(\d\d):?(\d\d))?)?$/;return function(c,
|
|
||||||
d,f){var g="",k=[],h,l;d=d||"mediumDate";d=a.DATETIME_FORMATS[d]||d;A(c)&&(c=eh.test(c)?fa(c):b(c));W(c)&&(c=new Date(c));if(!ha(c)||!isFinite(c.getTime()))return c;for(;d;)(l=fh.exec(d))?(k=db(k,l,1),d=k.pop()):(k.push(d),d=null);var m=c.getTimezoneOffset();f&&(m=ec(f,m),c=fc(c,f,!0));r(k,function(b){h=gh[b];g+=h?h(c,a.DATETIME_FORMATS,m):"''"===b?"'":b.replace(/(^'|'$)/g,"").replace(/''/g,"'")});return g}}function Yg(){return function(a,b){z(b)&&(b=2);return eb(a,b)}}function Zg(){return function(a,
|
|
||||||
b,d){b=Infinity===Math.abs(Number(b))?Number(b):fa(b);if(X(b))return a;W(a)&&(a=a.toString());if(!ya(a))return a;d=!d||isNaN(d)?0:fa(d);d=0>d?Math.max(0,a.length+d):d;return 0<=b?Gc(a,d,d+b):0===d?Gc(a,b,a.length):Gc(a,Math.max(0,d+b),d)}}function Gc(a,b,d){return A(a)?a.slice(b,d):Ha.call(a,b,d)}function Td(a){function b(b){return b.map(function(b){var c=1,d=Ta;if(B(b))d=b;else if(A(b)){if("+"===b.charAt(0)||"-"===b.charAt(0))c="-"===b.charAt(0)?-1:1,b=b.substring(1);if(""!==b&&(d=a(b),d.constant))var e=
|
|
||||||
d(),d=function(a){return a[e]}}return{get:d,descending:c}})}function d(a){switch(typeof a){case "number":case "boolean":case "string":return!0;default:return!1}}function c(a,b){var c=0,d=a.type,h=b.type;if(d===h){var h=a.value,l=b.value;"string"===d?(h=h.toLowerCase(),l=l.toLowerCase()):"object"===d&&(D(h)&&(h=a.index),D(l)&&(l=b.index));h!==l&&(c=h<l?-1:1)}else c="undefined"===d?1:"undefined"===h?-1:"null"===d?1:"null"===h?-1:d<h?-1:1;return c}return function(a,f,g,k){if(null==a)return a;if(!ya(a))throw F("orderBy")("notarray",
|
|
||||||
a);H(f)||(f=[f]);0===f.length&&(f=["+"]);var h=b(f),l=g?-1:1,m=B(k)?k:c;a=Array.prototype.map.call(a,function(a,b){return{value:a,tieBreaker:{value:b,type:"number",index:b},predicateValues:h.map(function(c){var e=c.get(a);c=typeof e;if(null===e)c="null";else if("object"===c)a:{if(B(e.valueOf)&&(e=e.valueOf(),d(e)))break a;bc(e)&&(e=e.toString(),d(e))}return{value:e,type:c,index:b}})}});a.sort(function(a,b){for(var d=0,e=h.length;d<e;d++){var f=m(a.predicateValues[d],b.predicateValues[d]);if(f)return f*
|
|
||||||
h[d].descending*l}return(m(a.tieBreaker,b.tieBreaker)||c(a.tieBreaker,b.tieBreaker))*l});return a=a.map(function(a){return a.value})}}function Ra(a){B(a)&&(a={link:a});a.restrict=a.restrict||"AC";return ia(a)}function Pb(a,b,d,c,e){this.$$controls=[];this.$error={};this.$$success={};this.$pending=void 0;this.$name=e(b.name||b.ngForm||"")(d);this.$dirty=!1;this.$valid=this.$pristine=!0;this.$submitted=this.$invalid=!1;this.$$parentForm=lb;this.$$element=a;this.$$animate=c;Zd(this)}function Zd(a){a.$$classCache=
|
|
||||||
{};a.$$classCache[$d]=!(a.$$classCache[mb]=a.$$element.hasClass(mb))}function ae(a){function b(a,b,c){c&&!a.$$classCache[b]?(a.$$animate.addClass(a.$$element,b),a.$$classCache[b]=!0):!c&&a.$$classCache[b]&&(a.$$animate.removeClass(a.$$element,b),a.$$classCache[b]=!1)}function d(a,c,d){c=c?"-"+Vc(c,"-"):"";b(a,mb+c,!0===d);b(a,$d+c,!1===d)}var c=a.set,e=a.unset;a.clazz.prototype.$setValidity=function(a,g,k){z(g)?(this.$pending||(this.$pending={}),c(this.$pending,a,k)):(this.$pending&&e(this.$pending,
|
|
||||||
a,k),be(this.$pending)&&(this.$pending=void 0));Ga(g)?g?(e(this.$error,a,k),c(this.$$success,a,k)):(c(this.$error,a,k),e(this.$$success,a,k)):(e(this.$error,a,k),e(this.$$success,a,k));this.$pending?(b(this,"ng-pending",!0),this.$valid=this.$invalid=void 0,d(this,"",null)):(b(this,"ng-pending",!1),this.$valid=be(this.$error),this.$invalid=!this.$valid,d(this,"",this.$valid));g=this.$pending&&this.$pending[a]?void 0:this.$error[a]?!1:this.$$success[a]?!0:null;d(this,a,g);this.$$parentForm.$setValidity(a,
|
|
||||||
g,this)}}function be(a){if(a)for(var b in a)if(a.hasOwnProperty(b))return!1;return!0}function Hc(a){a.$formatters.push(function(b){return a.$isEmpty(b)?b:b.toString()})}function Sa(a,b,d,c,e,f){var g=K(b[0].type);if(!e.android){var k=!1;b.on("compositionstart",function(){k=!0});b.on("compositionupdate",function(a){if(z(a.data)||""===a.data)k=!1});b.on("compositionend",function(){k=!1;l()})}var h,l=function(a){h&&(f.defer.cancel(h),h=null);if(!k){var e=b.val();a=a&&a.type;"password"===g||d.ngTrim&&
|
|
||||||
"false"===d.ngTrim||(e=U(e));(c.$viewValue!==e||""===e&&c.$$hasNativeValidators)&&c.$setViewValue(e,a)}};if(e.hasEvent("input"))b.on("input",l);else{var m=function(a,b,c){h||(h=f.defer(function(){h=null;b&&b.value===c||l(a)}))};b.on("keydown",function(a){var b=a.keyCode;91===b||15<b&&19>b||37<=b&&40>=b||m(a,this,this.value)});if(e.hasEvent("paste"))b.on("paste cut drop",m)}b.on("change",l);if(ce[g]&&c.$$hasNativeValidators&&g===d.type)b.on("keydown wheel mousedown",function(a){if(!h){var b=this.validity,
|
|
||||||
c=b.badInput,d=b.typeMismatch;h=f.defer(function(){h=null;b.badInput===c&&b.typeMismatch===d||l(a)})}});c.$render=function(){var a=c.$isEmpty(c.$viewValue)?"":c.$viewValue;b.val()!==a&&b.val(a)}}function Qb(a,b){return function(d,c){var e,f;if(ha(d))return d;if(A(d)){'"'===d.charAt(0)&&'"'===d.charAt(d.length-1)&&(d=d.substring(1,d.length-1));if(hh.test(d))return new Date(d);a.lastIndex=0;if(e=a.exec(d))return e.shift(),f=c?{yyyy:c.getFullYear(),MM:c.getMonth()+1,dd:c.getDate(),HH:c.getHours(),mm:c.getMinutes(),
|
|
||||||
ss:c.getSeconds(),sss:c.getMilliseconds()/1E3}:{yyyy:1970,MM:1,dd:1,HH:0,mm:0,ss:0,sss:0},r(e,function(a,c){c<b.length&&(f[b[c]]=+a)}),e=new Date(f.yyyy,f.MM-1,f.dd,f.HH,f.mm,f.ss||0,1E3*f.sss||0),100>f.yyyy&&e.setFullYear(f.yyyy),e}return NaN}}function nb(a,b,d,c){return function(e,f,g,k,h,l,m,p){function n(a){return a&&!(a.getTime&&a.getTime()!==a.getTime())}function s(a){return w(a)&&!ha(a)?r(a)||void 0:a}function r(a,b){var c=k.$options.getOption("timezone");v&&v!==c&&(b=Sc(b,ec(v)));var e=d(a,
|
|
||||||
b);!isNaN(e)&&c&&(e=fc(e,c));return e}Ic(e,f,g,k,a);Sa(e,f,g,k,h,l);var t="time"===a||"datetimelocal"===a,q,v;k.$parsers.push(function(c){if(k.$isEmpty(c))return null;if(b.test(c))return r(c,q);k.$$parserName=a});k.$formatters.push(function(a){if(a&&!ha(a))throw ob("datefmt",a);if(n(a)){q=a;var b=k.$options.getOption("timezone");b&&(v=b,q=fc(q,b,!0));var d=c;t&&A(k.$options.getOption("timeSecondsFormat"))&&(d=c.replace("ss.sss",k.$options.getOption("timeSecondsFormat")).replace(/:$/,""));a=m("date")(a,
|
|
||||||
d,b);t&&k.$options.getOption("timeStripZeroSeconds")&&(a=a.replace(/(?::00)?(?:\.000)?$/,""));return a}v=q=null;return""});if(w(g.min)||g.ngMin){var x=g.min||p(g.ngMin)(e),B=s(x);k.$validators.min=function(a){return!n(a)||z(B)||d(a)>=B};g.$observe("min",function(a){a!==x&&(B=s(a),x=a,k.$validate())})}if(w(g.max)||g.ngMax){var y=g.max||p(g.ngMax)(e),J=s(y);k.$validators.max=function(a){return!n(a)||z(J)||d(a)<=J};g.$observe("max",function(a){a!==y&&(J=s(a),y=a,k.$validate())})}}}function Ic(a,b,d,
|
|
||||||
c,e){(c.$$hasNativeValidators=D(b[0].validity))&&c.$parsers.push(function(a){var d=b.prop("validity")||{};if(d.badInput||d.typeMismatch)c.$$parserName=e;else return a})}function de(a){a.$parsers.push(function(b){if(a.$isEmpty(b))return null;if(ih.test(b))return parseFloat(b);a.$$parserName="number"});a.$formatters.push(function(b){if(!a.$isEmpty(b)){if(!W(b))throw ob("numfmt",b);b=b.toString()}return b})}function na(a){w(a)&&!W(a)&&(a=parseFloat(a));return X(a)?void 0:a}function Jc(a){var b=a.toString(),
|
|
||||||
d=b.indexOf(".");return-1===d?-1<a&&1>a&&(a=/e-(\d+)$/.exec(b))?Number(a[1]):0:b.length-d-1}function ee(a,b,d){a=Number(a);var c=(a|0)!==a,e=(b|0)!==b,f=(d|0)!==d;if(c||e||f){var g=c?Jc(a):0,k=e?Jc(b):0,h=f?Jc(d):0,g=Math.max(g,k,h),g=Math.pow(10,g);a*=g;b*=g;d*=g;c&&(a=Math.round(a));e&&(b=Math.round(b));f&&(d=Math.round(d))}return 0===(a-b)%d}function fe(a,b,d,c,e){if(w(c)){a=a(c);if(!a.constant)throw ob("constexpr",d,c);return a(b)}return e}function Kc(a,b){function d(a,b){if(!a||!a.length)return[];
|
|
||||||
if(!b||!b.length)return a;var c=[],d=0;a:for(;d<a.length;d++){for(var e=a[d],m=0;m<b.length;m++)if(e===b[m])continue a;c.push(e)}return c}function c(a){if(!a)return a;var b=a;H(a)?b=a.map(c).join(" "):D(a)?b=Object.keys(a).filter(function(b){return a[b]}).join(" "):A(a)||(b=a+"");return b}a="ngClass"+a;var e;return["$parse",function(f){return{restrict:"AC",link:function(g,k,h){function l(a,b){var c=[];r(a,function(a){if(0<b||p[a])p[a]=(p[a]||0)+b,p[a]===+(0<b)&&c.push(a)});return c.join(" ")}function m(a){if(a===
|
|
||||||
b){var c=s,c=l(c&&c.split(" "),1);h.$addClass(c)}else c=s,c=l(c&&c.split(" "),-1),h.$removeClass(c);n=a}var p=k.data("$classCounts"),n=!0,s;p||(p=T(),k.data("$classCounts",p));"ngClass"!==a&&(e||(e=f("$index",function(a){return a&1})),g.$watch(e,m));g.$watch(f(h[a],c),function(a){if(n===b){var c=s&&s.split(" "),e=a&&a.split(" "),f=d(c,e),c=d(e,c),f=l(f,-1),c=l(c,1);h.$addClass(c);h.$removeClass(f)}s=a})}}}]}function qd(a,b,d,c,e,f){return{restrict:"A",compile:function(g,k){var h=a(k[c]);return function(a,
|
|
||||||
c){c.on(e,function(c){var e=function(){h(a,{$event:c})};if(b.$$phase)if(f)a.$evalAsync(e);else try{e()}catch(g){d(g)}else a.$apply(e)})}}}}function Rb(a,b,d,c,e,f,g,k,h){this.$modelValue=this.$viewValue=Number.NaN;this.$$rawModelValue=void 0;this.$validators={};this.$asyncValidators={};this.$parsers=[];this.$formatters=[];this.$viewChangeListeners=[];this.$untouched=!0;this.$touched=!1;this.$pristine=!0;this.$dirty=!1;this.$valid=!0;this.$invalid=!1;this.$error={};this.$$success={};this.$pending=
|
|
||||||
void 0;this.$name=h(d.name||"",!1)(a);this.$$parentForm=lb;this.$options=Sb;this.$$updateEvents="";this.$$updateEventHandler=this.$$updateEventHandler.bind(this);this.$$parsedNgModel=e(d.ngModel);this.$$parsedNgModelAssign=this.$$parsedNgModel.assign;this.$$ngModelGet=this.$$parsedNgModel;this.$$ngModelSet=this.$$parsedNgModelAssign;this.$$pendingDebounce=null;this.$$parserValid=void 0;this.$$parserName="parse";this.$$currentValidationRunId=0;this.$$scope=a;this.$$rootScope=a.$root;this.$$attr=d;
|
|
||||||
this.$$element=c;this.$$animate=f;this.$$timeout=g;this.$$parse=e;this.$$q=k;this.$$exceptionHandler=b;Zd(this);jh(this)}function jh(a){a.$$scope.$watch(function(b){b=a.$$ngModelGet(b);b===a.$modelValue||a.$modelValue!==a.$modelValue&&b!==b||a.$$setModelValue(b);return b})}function Lc(a){this.$$options=a}function ge(a,b){r(b,function(b,c){w(a[c])||(a[c]=b)})}function Oa(a,b){a.prop("selected",b);a.attr("selected",b)}function he(a,b,d){if(a){A(a)&&(a=new RegExp("^"+a+"$"));if(!a.test)throw F("ngPattern")("noregexp",
|
|
||||||
b,a,za(d));return a}}function Tb(a){a=fa(a);return X(a)?-1:a}var Wb={objectMaxDepth:5,urlErrorParamsEnabled:!0},ie=/^\/(.+)\/([a-z]*)$/,ta=Object.prototype.hasOwnProperty,K=function(a){return A(a)?a.toLowerCase():a},ub=function(a){return A(a)?a.toUpperCase():a},Ca,x,rb,Ha=[].slice,Fg=[].splice,kh=[].push,la=Object.prototype.toString,Pc=Object.getPrototypeOf,pa=F("ng"),ca=C.angular||(C.angular={}),kc,pb=0;Ca=C.document.documentMode;var X=Number.isNaN||function(a){return a!==a};E.$inject=[];Ta.$inject=
|
|
||||||
[];var ve=/^\[object (?:Uint8|Uint8Clamped|Uint16|Uint32|Int8|Int16|Int32|Float32|Float64)Array]$/,U=function(a){return A(a)?a.trim():a},Md=function(a){return a.replace(/([-()[\]{}+?*.$^|,:#<!\\])/g,"\\$1").replace(/\x08/g,"\\x08")},Aa=function(){if(!w(Aa.rules)){var a=C.document.querySelector("[ng-csp]")||C.document.querySelector("[data-ng-csp]");if(a){var b=a.getAttribute("ng-csp")||a.getAttribute("data-ng-csp");Aa.rules={noUnsafeEval:!b||-1!==b.indexOf("no-unsafe-eval"),noInlineStyle:!b||-1!==
|
|
||||||
b.indexOf("no-inline-style")}}else{a=Aa;try{new Function(""),b=!1}catch(d){b=!0}a.rules={noUnsafeEval:b,noInlineStyle:!1}}}return Aa.rules},qb=function(){if(w(qb.name_))return qb.name_;var a,b,d=Qa.length,c,e;for(b=0;b<d;++b)if(c=Qa[b],a=C.document.querySelector("["+c.replace(":","\\:")+"jq]")){e=a.getAttribute(c+"jq");break}return qb.name_=e},xe=/:/g,Qa=["ng-","data-ng-","ng:","x-ng-"],Be=function(a){var b=a.currentScript;if(!b)return!0;if(!(b instanceof C.HTMLScriptElement||b instanceof C.SVGScriptElement))return!1;
|
|
||||||
b=b.attributes;return[b.getNamedItem("src"),b.getNamedItem("href"),b.getNamedItem("xlink:href")].every(function(b){if(!b)return!0;if(!b.value)return!1;var c=a.createElement("a");c.href=b.value;if(a.location.origin===c.origin)return!0;switch(c.protocol){case "http:":case "https:":case "ftp:":case "blob:":case "file:":case "data:":return!0;default:return!1}})}(C.document),Ee=/[A-Z]/g,Wc=!1,Pa=3,Ke={full:"1.7.9",major:1,minor:7,dot:9,codeName:"pollution-eradication"};Y.expando="ng339";var Ka=Y.cache=
|
|
||||||
{},pg=1;Y._data=function(a){return this.cache[a[this.expando]]||{}};var lg=/-([a-z])/g,lh=/^-ms-/,Ab={mouseleave:"mouseout",mouseenter:"mouseover"},nc=F("jqLite"),og=/^<([\w-]+)\s*\/?>(?:<\/\1>|)$/,mc=/<|&#?\w+;/,mg=/<([\w:-]+)/,ng=/<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:-]+)[^>]*)\/>/gi,oa={option:[1,'<select multiple="multiple">',"</select>"],thead:[1,"<table>","</table>"],col:[2,"<table><colgroup>","</colgroup></table>"],tr:[2,"<table><tbody>","</tbody></table>"],td:[3,"<table><tbody><tr>",
|
|
||||||
"</tr></tbody></table>"],_default:[0,"",""]};oa.optgroup=oa.option;oa.tbody=oa.tfoot=oa.colgroup=oa.caption=oa.thead;oa.th=oa.td;var ug=C.Node.prototype.contains||function(a){return!!(this.compareDocumentPosition(a)&16)},Wa=Y.prototype={ready:fd,toString:function(){var a=[];r(this,function(b){a.push(""+b)});return"["+a.join(", ")+"]"},eq:function(a){return 0<=a?x(this[a]):x(this[this.length+a])},length:0,push:kh,sort:[].sort,splice:[].splice},Gb={};r("multiple selected checked disabled readOnly required open".split(" "),
|
|
||||||
function(a){Gb[K(a)]=a});var md={};r("input select option textarea button form details".split(" "),function(a){md[a]=!0});var td={ngMinlength:"minlength",ngMaxlength:"maxlength",ngMin:"min",ngMax:"max",ngPattern:"pattern",ngStep:"step"};r({data:rc,removeData:qc,hasData:function(a){for(var b in Ka[a.ng339])return!0;return!1},cleanData:function(a){for(var b=0,d=a.length;b<d;b++)qc(a[b]),id(a[b])}},function(a,b){Y[b]=a});r({data:rc,inheritedData:Eb,scope:function(a){return x.data(a,"$scope")||Eb(a.parentNode||
|
|
||||||
a,["$isolateScope","$scope"])},isolateScope:function(a){return x.data(a,"$isolateScope")||x.data(a,"$isolateScopeNoTemplate")},controller:jd,injector:function(a){return Eb(a,"$injector")},removeAttr:function(a,b){a.removeAttribute(b)},hasClass:Bb,css:function(a,b,d){b=xb(b.replace(lh,"ms-"));if(w(d))a.style[b]=d;else return a.style[b]},attr:function(a,b,d){var c=a.nodeType;if(c!==Pa&&2!==c&&8!==c&&a.getAttribute){var c=K(b),e=Gb[c];if(w(d))null===d||!1===d&&e?a.removeAttribute(b):a.setAttribute(b,
|
|
||||||
e?c:d);else return a=a.getAttribute(b),e&&null!==a&&(a=c),null===a?void 0:a}},prop:function(a,b,d){if(w(d))a[b]=d;else return a[b]},text:function(){function a(a,d){if(z(d)){var c=a.nodeType;return 1===c||c===Pa?a.textContent:""}a.textContent=d}a.$dv="";return a}(),val:function(a,b){if(z(b)){if(a.multiple&&"select"===ua(a)){var d=[];r(a.options,function(a){a.selected&&d.push(a.value||a.text)});return d}return a.value}a.value=b},html:function(a,b){if(z(b))return a.innerHTML;yb(a,!0);a.innerHTML=b},
|
|
||||||
empty:kd},function(a,b){Y.prototype[b]=function(b,c){var e,f,g=this.length;if(a!==kd&&z(2===a.length&&a!==Bb&&a!==jd?b:c)){if(D(b)){for(e=0;e<g;e++)if(a===rc)a(this[e],b);else for(f in b)a(this[e],f,b[f]);return this}e=a.$dv;g=z(e)?Math.min(g,1):g;for(f=0;f<g;f++){var k=a(this[f],b,c);e=e?e+k:k}return e}for(e=0;e<g;e++)a(this[e],b,c);return this}});r({removeData:qc,on:function(a,b,d,c){if(w(c))throw nc("onargs");if(lc(a)){c=zb(a,!0);var e=c.events,f=c.handle;f||(f=c.handle=rg(a,e));c=0<=b.indexOf(" ")?
|
|
||||||
b.split(" "):[b];for(var g=c.length,k=function(b,c,g){var k=e[b];k||(k=e[b]=[],k.specialHandlerWrapper=c,"$destroy"===b||g||a.addEventListener(b,f));k.push(d)};g--;)b=c[g],Ab[b]?(k(Ab[b],tg),k(b,void 0,!0)):k(b)}},off:id,one:function(a,b,d){a=x(a);a.on(b,function e(){a.off(b,d);a.off(b,e)});a.on(b,d)},replaceWith:function(a,b){var d,c=a.parentNode;yb(a);r(new Y(b),function(b){d?c.insertBefore(b,d.nextSibling):c.replaceChild(b,a);d=b})},children:function(a){var b=[];r(a.childNodes,function(a){1===
|
|
||||||
a.nodeType&&b.push(a)});return b},contents:function(a){return a.contentDocument||a.childNodes||[]},append:function(a,b){var d=a.nodeType;if(1===d||11===d){b=new Y(b);for(var d=0,c=b.length;d<c;d++)a.appendChild(b[d])}},prepend:function(a,b){if(1===a.nodeType){var d=a.firstChild;r(new Y(b),function(b){a.insertBefore(b,d)})}},wrap:function(a,b){var d=x(b).eq(0).clone()[0],c=a.parentNode;c&&c.replaceChild(d,a);d.appendChild(a)},remove:Fb,detach:function(a){Fb(a,!0)},after:function(a,b){var d=a,c=a.parentNode;
|
|
||||||
if(c){b=new Y(b);for(var e=0,f=b.length;e<f;e++){var g=b[e];c.insertBefore(g,d.nextSibling);d=g}}},addClass:Db,removeClass:Cb,toggleClass:function(a,b,d){b&&r(b.split(" "),function(b){var e=d;z(e)&&(e=!Bb(a,b));(e?Db:Cb)(a,b)})},parent:function(a){return(a=a.parentNode)&&11!==a.nodeType?a:null},next:function(a){return a.nextElementSibling},find:function(a,b){return a.getElementsByTagName?a.getElementsByTagName(b):[]},clone:pc,triggerHandler:function(a,b,d){var c,e,f=b.type||b,g=zb(a);if(g=(g=g&&g.events)&&
|
|
||||||
g[f])c={preventDefault:function(){this.defaultPrevented=!0},isDefaultPrevented:function(){return!0===this.defaultPrevented},stopImmediatePropagation:function(){this.immediatePropagationStopped=!0},isImmediatePropagationStopped:function(){return!0===this.immediatePropagationStopped},stopPropagation:E,type:f,target:a},b.type&&(c=S(c,b)),b=ja(g),e=d?[c].concat(d):[c],r(b,function(b){c.isImmediatePropagationStopped()||b.apply(a,e)})}},function(a,b){Y.prototype[b]=function(b,c,e){for(var f,g=0,k=this.length;g<
|
|
||||||
k;g++)z(f)?(f=a(this[g],b,c,e),w(f)&&(f=x(f))):oc(f,a(this[g],b,c,e));return w(f)?f:this}});Y.prototype.bind=Y.prototype.on;Y.prototype.unbind=Y.prototype.off;var mh=Object.create(null);nd.prototype={_idx:function(a){a!==this._lastKey&&(this._lastKey=a,this._lastIndex=this._keys.indexOf(a));return this._lastIndex},_transformKey:function(a){return X(a)?mh:a},get:function(a){a=this._transformKey(a);a=this._idx(a);if(-1!==a)return this._values[a]},has:function(a){a=this._transformKey(a);return-1!==this._idx(a)},
|
|
||||||
set:function(a,b){a=this._transformKey(a);var d=this._idx(a);-1===d&&(d=this._lastIndex=this._keys.length);this._keys[d]=a;this._values[d]=b},delete:function(a){a=this._transformKey(a);a=this._idx(a);if(-1===a)return!1;this._keys.splice(a,1);this._values.splice(a,1);this._lastKey=NaN;this._lastIndex=-1;return!0}};var Hb=nd,jg=[function(){this.$get=[function(){return Hb}]}],wg=/^([^(]+?)=>/,xg=/^[^(]*\(\s*([^)]*)\)/m,nh=/,/,oh=/^\s*(_?)(\S+?)\1\s*$/,vg=/((\/\/.*$)|(\/\*[\s\S]*?\*\/))/mg,Ba=F("$injector");
|
|
||||||
fb.$$annotate=function(a,b,d){var c;if("function"===typeof a){if(!(c=a.$inject)){c=[];if(a.length){if(b)throw A(d)&&d||(d=a.name||yg(a)),Ba("strictdi",d);b=od(a);r(b[1].split(nh),function(a){a.replace(oh,function(a,b,d){c.push(d)})})}a.$inject=c}}else H(a)?(b=a.length-1,sb(a[b],"fn"),c=a.slice(0,b)):sb(a,"fn",!0);return c};var je=F("$animate"),zf=function(){this.$get=E},Af=function(){var a=new Hb,b=[];this.$get=["$$AnimateRunner","$rootScope",function(d,c){function e(a,b,c){var d=!1;b&&(b=A(b)?b.split(" "):
|
|
||||||
H(b)?b:[],r(b,function(b){b&&(d=!0,a[b]=c)}));return d}function f(){r(b,function(b){var c=a.get(b);if(c){var d=zg(b.attr("class")),e="",f="";r(c,function(a,b){a!==!!d[b]&&(a?e+=(e.length?" ":"")+b:f+=(f.length?" ":"")+b)});r(b,function(a){e&&Db(a,e);f&&Cb(a,f)});a.delete(b)}});b.length=0}return{enabled:E,on:E,off:E,pin:E,push:function(g,k,h,l){l&&l();h=h||{};h.from&&g.css(h.from);h.to&&g.css(h.to);if(h.addClass||h.removeClass)if(k=h.addClass,l=h.removeClass,h=a.get(g)||{},k=e(h,k,!0),l=e(h,l,!1),
|
|
||||||
k||l)a.set(g,h),b.push(g),1===b.length&&c.$$postDigest(f);g=new d;g.complete();return g}}}]},xf=["$provide",function(a){var b=this,d=null,c=null;this.$$registeredAnimations=Object.create(null);this.register=function(c,d){if(c&&"."!==c.charAt(0))throw je("notcsel",c);var g=c+"-animation";b.$$registeredAnimations[c.substr(1)]=g;a.factory(g,d)};this.customFilter=function(a){1===arguments.length&&(c=B(a)?a:null);return c};this.classNameFilter=function(a){if(1===arguments.length&&(d=a instanceof RegExp?
|
|
||||||
a:null)&&/[(\s|\/)]ng-animate[(\s|\/)]/.test(d.toString()))throw d=null,je("nongcls","ng-animate");return d};this.$get=["$$animateQueue",function(a){function b(a,c,d){if(d){var e;a:{for(e=0;e<d.length;e++){var f=d[e];if(1===f.nodeType){e=f;break a}}e=void 0}!e||e.parentNode||e.previousElementSibling||(d=null)}d?d.after(a):c.prepend(a)}return{on:a.on,off:a.off,pin:a.pin,enabled:a.enabled,cancel:function(a){a.cancel&&a.cancel()},enter:function(c,d,h,l){d=d&&x(d);h=h&&x(h);d=d||h.parent();b(c,d,h);return a.push(c,
|
|
||||||
"enter",ra(l))},move:function(c,d,h,l){d=d&&x(d);h=h&&x(h);d=d||h.parent();b(c,d,h);return a.push(c,"move",ra(l))},leave:function(b,c){return a.push(b,"leave",ra(c),function(){b.remove()})},addClass:function(b,c,d){d=ra(d);d.addClass=hb(d.addclass,c);return a.push(b,"addClass",d)},removeClass:function(b,c,d){d=ra(d);d.removeClass=hb(d.removeClass,c);return a.push(b,"removeClass",d)},setClass:function(b,c,d,f){f=ra(f);f.addClass=hb(f.addClass,c);f.removeClass=hb(f.removeClass,d);return a.push(b,"setClass",
|
|
||||||
f)},animate:function(b,c,d,f,m){m=ra(m);m.from=m.from?S(m.from,c):c;m.to=m.to?S(m.to,d):d;m.tempClasses=hb(m.tempClasses,f||"ng-inline-animate");return a.push(b,"animate",m)}}}]}],Cf=function(){this.$get=["$$rAF",function(a){function b(b){d.push(b);1<d.length||a(function(){for(var a=0;a<d.length;a++)d[a]();d=[]})}var d=[];return function(){var a=!1;b(function(){a=!0});return function(d){a?d():b(d)}}}]},Bf=function(){this.$get=["$q","$sniffer","$$animateAsyncRun","$$isDocumentHidden","$timeout",function(a,
|
|
||||||
b,d,c,e){function f(a){this.setHost(a);var b=d();this._doneCallbacks=[];this._tick=function(a){c()?e(a,0,!1):b(a)};this._state=0}f.chain=function(a,b){function c(){if(d===a.length)b(!0);else a[d](function(a){!1===a?b(!1):(d++,c())})}var d=0;c()};f.all=function(a,b){function c(f){e=e&&f;++d===a.length&&b(e)}var d=0,e=!0;r(a,function(a){a.done(c)})};f.prototype={setHost:function(a){this.host=a||{}},done:function(a){2===this._state?a():this._doneCallbacks.push(a)},progress:E,getPromise:function(){if(!this.promise){var b=
|
|
||||||
this;this.promise=a(function(a,c){b.done(function(b){!1===b?c():a()})})}return this.promise},then:function(a,b){return this.getPromise().then(a,b)},"catch":function(a){return this.getPromise()["catch"](a)},"finally":function(a){return this.getPromise()["finally"](a)},pause:function(){this.host.pause&&this.host.pause()},resume:function(){this.host.resume&&this.host.resume()},end:function(){this.host.end&&this.host.end();this._resolve(!0)},cancel:function(){this.host.cancel&&this.host.cancel();this._resolve(!1)},
|
|
||||||
complete:function(a){var b=this;0===b._state&&(b._state=1,b._tick(function(){b._resolve(a)}))},_resolve:function(a){2!==this._state&&(r(this._doneCallbacks,function(b){b(a)}),this._doneCallbacks.length=0,this._state=2)}};return f}]},yf=function(){this.$get=["$$rAF","$q","$$AnimateRunner",function(a,b,d){return function(b,e){function f(){a(function(){g.addClass&&(b.addClass(g.addClass),g.addClass=null);g.removeClass&&(b.removeClass(g.removeClass),g.removeClass=null);g.to&&(b.css(g.to),g.to=null);k||
|
|
||||||
h.complete();k=!0});return h}var g=e||{};g.$$prepared||(g=Ia(g));g.cleanupStyles&&(g.from=g.to=null);g.from&&(b.css(g.from),g.from=null);var k,h=new d;return{start:f,end:f}}}]},$=F("$compile"),tc=new function(){};Xc.$inject=["$provide","$$sanitizeUriProvider"];Jb.prototype.isFirstChange=function(){return this.previousValue===tc};var pd=/^((?:x|data)[:\-_])/i,Eg=/[:\-_]+(.)/g,vd=F("$controller"),ud=/^(\S+)(\s+as\s+([\w$]+))?$/,Jf=function(){this.$get=["$document",function(a){return function(b){b?!b.nodeType&&
|
|
||||||
b instanceof x&&(b=b[0]):b=a[0].body;return b.offsetWidth+1}}]},wd="application/json",wc={"Content-Type":wd+";charset=utf-8"},Hg=/^\[|^\{(?!\{)/,Ig={"[":/]$/,"{":/}$/},Gg=/^\)]\}',?\n/,Kb=F("$http"),Ma=ca.$interpolateMinErr=F("$interpolate");Ma.throwNoconcat=function(a){throw Ma("noconcat",a);};Ma.interr=function(a,b){return Ma("interr",a,b.toString())};var Lg=F("$interval"),Sf=function(){this.$get=function(){function a(a){var b=function(a){b.data=a;b.called=!0};b.id=a;return b}var b=ca.callbacks,
|
|
||||||
d={};return{createCallback:function(c){c="_"+(b.$$counter++).toString(36);var e="angular.callbacks."+c,f=a(c);d[e]=b[c]=f;return e},wasCalled:function(a){return d[a].called},getResponse:function(a){return d[a].data},removeCallback:function(a){delete b[d[a].id];delete d[a]}}}},ph=/^([^?#]*)(\?([^#]*))?(#(.*))?$/,Mg={http:80,https:443,ftp:21},jb=F("$location"),Ng=/^\s*[\\/]{2,}/,qh={$$absUrl:"",$$html5:!1,$$replace:!1,$$compose:function(){for(var a=this.$$path,b=this.$$hash,d=ye(this.$$search),b=b?
|
|
||||||
"#"+hc(b):"",a=a.split("/"),c=a.length;c--;)a[c]=hc(a[c].replace(/%2F/g,"/"));this.$$url=a.join("/")+(d?"?"+d:"")+b;this.$$absUrl=this.$$normalizeUrl(this.$$url);this.$$urlUpdatedByLocation=!0},absUrl:Lb("$$absUrl"),url:function(a){if(z(a))return this.$$url;var b=ph.exec(a);(b[1]||""===a)&&this.path(decodeURIComponent(b[1]));(b[2]||b[1]||""===a)&&this.search(b[3]||"");this.hash(b[5]||"");return this},protocol:Lb("$$protocol"),host:Lb("$$host"),port:Lb("$$port"),path:Dd("$$path",function(a){a=null!==
|
|
||||||
a?a.toString():"";return"/"===a.charAt(0)?a:"/"+a}),search:function(a,b){switch(arguments.length){case 0:return this.$$search;case 1:if(A(a)||W(a))a=a.toString(),this.$$search=gc(a);else if(D(a))a=Ia(a,{}),r(a,function(b,c){null==b&&delete a[c]}),this.$$search=a;else throw jb("isrcharg");break;default:z(b)||null===b?delete this.$$search[a]:this.$$search[a]=b}this.$$compose();return this},hash:Dd("$$hash",function(a){return null!==a?a.toString():""}),replace:function(){this.$$replace=!0;return this}};
|
|
||||||
r([Cd,zc,yc],function(a){a.prototype=Object.create(qh);a.prototype.state=function(b){if(!arguments.length)return this.$$state;if(a!==yc||!this.$$html5)throw jb("nostate");this.$$state=z(b)?null:b;this.$$urlUpdatedByLocation=!0;return this}});var Ya=F("$parse"),Rg={}.constructor.prototype.valueOf,Ub=T();r("+ - * / % === !== == != < > <= >= && || ! = |".split(" "),function(a){Ub[a]=!0});var rh={n:"\n",f:"\f",r:"\r",t:"\t",v:"\v","'":"'",'"':'"'},Nb=function(a){this.options=a};Nb.prototype={constructor:Nb,
|
|
||||||
lex:function(a){this.text=a;this.index=0;for(this.tokens=[];this.index<this.text.length;)if(a=this.text.charAt(this.index),'"'===a||"'"===a)this.readString(a);else if(this.isNumber(a)||"."===a&&this.isNumber(this.peek()))this.readNumber();else if(this.isIdentifierStart(this.peekMultichar()))this.readIdent();else if(this.is(a,"(){}[].,;:?"))this.tokens.push({index:this.index,text:a}),this.index++;else if(this.isWhitespace(a))this.index++;else{var b=a+this.peek(),d=b+this.peek(2),c=Ub[b],e=Ub[d];Ub[a]||
|
|
||||||
c||e?(a=e?d:c?b:a,this.tokens.push({index:this.index,text:a,operator:!0}),this.index+=a.length):this.throwError("Unexpected next character ",this.index,this.index+1)}return this.tokens},is:function(a,b){return-1!==b.indexOf(a)},peek:function(a){a=a||1;return this.index+a<this.text.length?this.text.charAt(this.index+a):!1},isNumber:function(a){return"0"<=a&&"9">=a&&"string"===typeof a},isWhitespace:function(a){return" "===a||"\r"===a||"\t"===a||"\n"===a||"\v"===a||"\u00a0"===a},isIdentifierStart:function(a){return this.options.isIdentifierStart?
|
|
||||||
this.options.isIdentifierStart(a,this.codePointAt(a)):this.isValidIdentifierStart(a)},isValidIdentifierStart:function(a){return"a"<=a&&"z">=a||"A"<=a&&"Z">=a||"_"===a||"$"===a},isIdentifierContinue:function(a){return this.options.isIdentifierContinue?this.options.isIdentifierContinue(a,this.codePointAt(a)):this.isValidIdentifierContinue(a)},isValidIdentifierContinue:function(a,b){return this.isValidIdentifierStart(a,b)||this.isNumber(a)},codePointAt:function(a){return 1===a.length?a.charCodeAt(0):
|
|
||||||
(a.charCodeAt(0)<<10)+a.charCodeAt(1)-56613888},peekMultichar:function(){var a=this.text.charAt(this.index),b=this.peek();if(!b)return a;var d=a.charCodeAt(0),c=b.charCodeAt(0);return 55296<=d&&56319>=d&&56320<=c&&57343>=c?a+b:a},isExpOperator:function(a){return"-"===a||"+"===a||this.isNumber(a)},throwError:function(a,b,d){d=d||this.index;b=w(b)?"s "+b+"-"+this.index+" ["+this.text.substring(b,d)+"]":" "+d;throw Ya("lexerr",a,b,this.text);},readNumber:function(){for(var a="",b=this.index;this.index<
|
|
||||||
this.text.length;){var d=K(this.text.charAt(this.index));if("."===d||this.isNumber(d))a+=d;else{var c=this.peek();if("e"===d&&this.isExpOperator(c))a+=d;else if(this.isExpOperator(d)&&c&&this.isNumber(c)&&"e"===a.charAt(a.length-1))a+=d;else if(!this.isExpOperator(d)||c&&this.isNumber(c)||"e"!==a.charAt(a.length-1))break;else this.throwError("Invalid exponent")}this.index++}this.tokens.push({index:b,text:a,constant:!0,value:Number(a)})},readIdent:function(){var a=this.index;for(this.index+=this.peekMultichar().length;this.index<
|
|
||||||
this.text.length;){var b=this.peekMultichar();if(!this.isIdentifierContinue(b))break;this.index+=b.length}this.tokens.push({index:a,text:this.text.slice(a,this.index),identifier:!0})},readString:function(a){var b=this.index;this.index++;for(var d="",c=a,e=!1;this.index<this.text.length;){var f=this.text.charAt(this.index),c=c+f;if(e)"u"===f?(e=this.text.substring(this.index+1,this.index+5),e.match(/[\da-f]{4}/i)||this.throwError("Invalid unicode escape [\\u"+e+"]"),this.index+=4,d+=String.fromCharCode(parseInt(e,
|
|
||||||
16))):d+=rh[f]||f,e=!1;else if("\\"===f)e=!0;else{if(f===a){this.index++;this.tokens.push({index:b,text:c,constant:!0,value:d});return}d+=f}this.index++}this.throwError("Unterminated quote",b)}};var q=function(a,b){this.lexer=a;this.options=b};q.Program="Program";q.ExpressionStatement="ExpressionStatement";q.AssignmentExpression="AssignmentExpression";q.ConditionalExpression="ConditionalExpression";q.LogicalExpression="LogicalExpression";q.BinaryExpression="BinaryExpression";q.UnaryExpression="UnaryExpression";
|
|
||||||
q.CallExpression="CallExpression";q.MemberExpression="MemberExpression";q.Identifier="Identifier";q.Literal="Literal";q.ArrayExpression="ArrayExpression";q.Property="Property";q.ObjectExpression="ObjectExpression";q.ThisExpression="ThisExpression";q.LocalsExpression="LocalsExpression";q.NGValueParameter="NGValueParameter";q.prototype={ast:function(a){this.text=a;this.tokens=this.lexer.lex(a);a=this.program();0!==this.tokens.length&&this.throwError("is an unexpected token",this.tokens[0]);return a},
|
|
||||||
program:function(){for(var a=[];;)if(0<this.tokens.length&&!this.peek("}",")",";","]")&&a.push(this.expressionStatement()),!this.expect(";"))return{type:q.Program,body:a}},expressionStatement:function(){return{type:q.ExpressionStatement,expression:this.filterChain()}},filterChain:function(){for(var a=this.expression();this.expect("|");)a=this.filter(a);return a},expression:function(){return this.assignment()},assignment:function(){var a=this.ternary();if(this.expect("=")){if(!Hd(a))throw Ya("lval");
|
|
||||||
a={type:q.AssignmentExpression,left:a,right:this.assignment(),operator:"="}}return a},ternary:function(){var a=this.logicalOR(),b,d;return this.expect("?")&&(b=this.expression(),this.consume(":"))?(d=this.expression(),{type:q.ConditionalExpression,test:a,alternate:b,consequent:d}):a},logicalOR:function(){for(var a=this.logicalAND();this.expect("||");)a={type:q.LogicalExpression,operator:"||",left:a,right:this.logicalAND()};return a},logicalAND:function(){for(var a=this.equality();this.expect("&&");)a=
|
|
||||||
{type:q.LogicalExpression,operator:"&&",left:a,right:this.equality()};return a},equality:function(){for(var a=this.relational(),b;b=this.expect("==","!=","===","!==");)a={type:q.BinaryExpression,operator:b.text,left:a,right:this.relational()};return a},relational:function(){for(var a=this.additive(),b;b=this.expect("<",">","<=",">=");)a={type:q.BinaryExpression,operator:b.text,left:a,right:this.additive()};return a},additive:function(){for(var a=this.multiplicative(),b;b=this.expect("+","-");)a={type:q.BinaryExpression,
|
|
||||||
operator:b.text,left:a,right:this.multiplicative()};return a},multiplicative:function(){for(var a=this.unary(),b;b=this.expect("*","/","%");)a={type:q.BinaryExpression,operator:b.text,left:a,right:this.unary()};return a},unary:function(){var a;return(a=this.expect("+","-","!"))?{type:q.UnaryExpression,operator:a.text,prefix:!0,argument:this.unary()}:this.primary()},primary:function(){var a;this.expect("(")?(a=this.filterChain(),this.consume(")")):this.expect("[")?a=this.arrayDeclaration():this.expect("{")?
|
|
||||||
a=this.object():this.selfReferential.hasOwnProperty(this.peek().text)?a=Ia(this.selfReferential[this.consume().text]):this.options.literals.hasOwnProperty(this.peek().text)?a={type:q.Literal,value:this.options.literals[this.consume().text]}:this.peek().identifier?a=this.identifier():this.peek().constant?a=this.constant():this.throwError("not a primary expression",this.peek());for(var b;b=this.expect("(","[",".");)"("===b.text?(a={type:q.CallExpression,callee:a,arguments:this.parseArguments()},this.consume(")")):
|
|
||||||
"["===b.text?(a={type:q.MemberExpression,object:a,property:this.expression(),computed:!0},this.consume("]")):"."===b.text?a={type:q.MemberExpression,object:a,property:this.identifier(),computed:!1}:this.throwError("IMPOSSIBLE");return a},filter:function(a){a=[a];for(var b={type:q.CallExpression,callee:this.identifier(),arguments:a,filter:!0};this.expect(":");)a.push(this.expression());return b},parseArguments:function(){var a=[];if(")"!==this.peekToken().text){do a.push(this.filterChain());while(this.expect(","))
|
|
||||||
}return a},identifier:function(){var a=this.consume();a.identifier||this.throwError("is not a valid identifier",a);return{type:q.Identifier,name:a.text}},constant:function(){return{type:q.Literal,value:this.consume().value}},arrayDeclaration:function(){var a=[];if("]"!==this.peekToken().text){do{if(this.peek("]"))break;a.push(this.expression())}while(this.expect(","))}this.consume("]");return{type:q.ArrayExpression,elements:a}},object:function(){var a=[],b;if("}"!==this.peekToken().text){do{if(this.peek("}"))break;
|
|
||||||
b={type:q.Property,kind:"init"};this.peek().constant?(b.key=this.constant(),b.computed=!1,this.consume(":"),b.value=this.expression()):this.peek().identifier?(b.key=this.identifier(),b.computed=!1,this.peek(":")?(this.consume(":"),b.value=this.expression()):b.value=b.key):this.peek("[")?(this.consume("["),b.key=this.expression(),this.consume("]"),b.computed=!0,this.consume(":"),b.value=this.expression()):this.throwError("invalid key",this.peek());a.push(b)}while(this.expect(","))}this.consume("}");
|
|
||||||
return{type:q.ObjectExpression,properties:a}},throwError:function(a,b){throw Ya("syntax",b.text,a,b.index+1,this.text,this.text.substring(b.index));},consume:function(a){if(0===this.tokens.length)throw Ya("ueoe",this.text);var b=this.expect(a);b||this.throwError("is unexpected, expecting ["+a+"]",this.peek());return b},peekToken:function(){if(0===this.tokens.length)throw Ya("ueoe",this.text);return this.tokens[0]},peek:function(a,b,d,c){return this.peekAhead(0,a,b,d,c)},peekAhead:function(a,b,d,c,
|
|
||||||
e){if(this.tokens.length>a){a=this.tokens[a];var f=a.text;if(f===b||f===d||f===c||f===e||!(b||d||c||e))return a}return!1},expect:function(a,b,d,c){return(a=this.peek(a,b,d,c))?(this.tokens.shift(),a):!1},selfReferential:{"this":{type:q.ThisExpression},$locals:{type:q.LocalsExpression}}};var Fd=2;Jd.prototype={compile:function(a){var b=this;this.state={nextId:0,filters:{},fn:{vars:[],body:[],own:{}},assign:{vars:[],body:[],own:{}},inputs:[]};Z(a,b.$filter);var d="",c;this.stage="assign";if(c=Id(a))this.state.computing=
|
|
||||||
"assign",d=this.nextId(),this.recurse(c,d),this.return_(d),d="fn.assign="+this.generateFunction("assign","s,v,l");c=Gd(a.body);b.stage="inputs";r(c,function(a,c){var d="fn"+c;b.state[d]={vars:[],body:[],own:{}};b.state.computing=d;var k=b.nextId();b.recurse(a,k);b.return_(k);b.state.inputs.push({name:d,isPure:a.isPure});a.watchId=c});this.state.computing="fn";this.stage="main";this.recurse(a);a='"'+this.USE+" "+this.STRICT+'";\n'+this.filterPrefix()+"var fn="+this.generateFunction("fn","s,l,a,i")+
|
|
||||||
d+this.watchFns()+"return fn;";a=(new Function("$filter","getStringValue","ifDefined","plus",a))(this.$filter,Og,Pg,Ed);this.state=this.stage=void 0;return a},USE:"use",STRICT:"strict",watchFns:function(){var a=[],b=this.state.inputs,d=this;r(b,function(b){a.push("var "+b.name+"="+d.generateFunction(b.name,"s"));b.isPure&&a.push(b.name,".isPure="+JSON.stringify(b.isPure)+";")});b.length&&a.push("fn.inputs=["+b.map(function(a){return a.name}).join(",")+"];");return a.join("")},generateFunction:function(a,
|
|
||||||
b){return"function("+b+"){"+this.varsPrefix(a)+this.body(a)+"};"},filterPrefix:function(){var a=[],b=this;r(this.state.filters,function(d,c){a.push(d+"=$filter("+b.escape(c)+")")});return a.length?"var "+a.join(",")+";":""},varsPrefix:function(a){return this.state[a].vars.length?"var "+this.state[a].vars.join(",")+";":""},body:function(a){return this.state[a].body.join("")},recurse:function(a,b,d,c,e,f){var g,k,h=this,l,m,p;c=c||E;if(!f&&w(a.watchId))b=b||this.nextId(),this.if_("i",this.lazyAssign(b,
|
|
||||||
this.computedMember("i",a.watchId)),this.lazyRecurse(a,b,d,c,e,!0));else switch(a.type){case q.Program:r(a.body,function(b,c){h.recurse(b.expression,void 0,void 0,function(a){k=a});c!==a.body.length-1?h.current().body.push(k,";"):h.return_(k)});break;case q.Literal:m=this.escape(a.value);this.assign(b,m);c(b||m);break;case q.UnaryExpression:this.recurse(a.argument,void 0,void 0,function(a){k=a});m=a.operator+"("+this.ifDefined(k,0)+")";this.assign(b,m);c(m);break;case q.BinaryExpression:this.recurse(a.left,
|
|
||||||
void 0,void 0,function(a){g=a});this.recurse(a.right,void 0,void 0,function(a){k=a});m="+"===a.operator?this.plus(g,k):"-"===a.operator?this.ifDefined(g,0)+a.operator+this.ifDefined(k,0):"("+g+")"+a.operator+"("+k+")";this.assign(b,m);c(m);break;case q.LogicalExpression:b=b||this.nextId();h.recurse(a.left,b);h.if_("&&"===a.operator?b:h.not(b),h.lazyRecurse(a.right,b));c(b);break;case q.ConditionalExpression:b=b||this.nextId();h.recurse(a.test,b);h.if_(b,h.lazyRecurse(a.alternate,b),h.lazyRecurse(a.consequent,
|
|
||||||
b));c(b);break;case q.Identifier:b=b||this.nextId();d&&(d.context="inputs"===h.stage?"s":this.assign(this.nextId(),this.getHasOwnProperty("l",a.name)+"?l:s"),d.computed=!1,d.name=a.name);h.if_("inputs"===h.stage||h.not(h.getHasOwnProperty("l",a.name)),function(){h.if_("inputs"===h.stage||"s",function(){e&&1!==e&&h.if_(h.isNull(h.nonComputedMember("s",a.name)),h.lazyAssign(h.nonComputedMember("s",a.name),"{}"));h.assign(b,h.nonComputedMember("s",a.name))})},b&&h.lazyAssign(b,h.nonComputedMember("l",
|
|
||||||
a.name)));c(b);break;case q.MemberExpression:g=d&&(d.context=this.nextId())||this.nextId();b=b||this.nextId();h.recurse(a.object,g,void 0,function(){h.if_(h.notNull(g),function(){a.computed?(k=h.nextId(),h.recurse(a.property,k),h.getStringValue(k),e&&1!==e&&h.if_(h.not(h.computedMember(g,k)),h.lazyAssign(h.computedMember(g,k),"{}")),m=h.computedMember(g,k),h.assign(b,m),d&&(d.computed=!0,d.name=k)):(e&&1!==e&&h.if_(h.isNull(h.nonComputedMember(g,a.property.name)),h.lazyAssign(h.nonComputedMember(g,
|
|
||||||
a.property.name),"{}")),m=h.nonComputedMember(g,a.property.name),h.assign(b,m),d&&(d.computed=!1,d.name=a.property.name))},function(){h.assign(b,"undefined")});c(b)},!!e);break;case q.CallExpression:b=b||this.nextId();a.filter?(k=h.filter(a.callee.name),l=[],r(a.arguments,function(a){var b=h.nextId();h.recurse(a,b);l.push(b)}),m=k+"("+l.join(",")+")",h.assign(b,m),c(b)):(k=h.nextId(),g={},l=[],h.recurse(a.callee,k,g,function(){h.if_(h.notNull(k),function(){r(a.arguments,function(b){h.recurse(b,a.constant?
|
|
||||||
void 0:h.nextId(),void 0,function(a){l.push(a)})});m=g.name?h.member(g.context,g.name,g.computed)+"("+l.join(",")+")":k+"("+l.join(",")+")";h.assign(b,m)},function(){h.assign(b,"undefined")});c(b)}));break;case q.AssignmentExpression:k=this.nextId();g={};this.recurse(a.left,void 0,g,function(){h.if_(h.notNull(g.context),function(){h.recurse(a.right,k);m=h.member(g.context,g.name,g.computed)+a.operator+k;h.assign(b,m);c(b||m)})},1);break;case q.ArrayExpression:l=[];r(a.elements,function(b){h.recurse(b,
|
|
||||||
a.constant?void 0:h.nextId(),void 0,function(a){l.push(a)})});m="["+l.join(",")+"]";this.assign(b,m);c(b||m);break;case q.ObjectExpression:l=[];p=!1;r(a.properties,function(a){a.computed&&(p=!0)});p?(b=b||this.nextId(),this.assign(b,"{}"),r(a.properties,function(a){a.computed?(g=h.nextId(),h.recurse(a.key,g)):g=a.key.type===q.Identifier?a.key.name:""+a.key.value;k=h.nextId();h.recurse(a.value,k);h.assign(h.member(b,g,a.computed),k)})):(r(a.properties,function(b){h.recurse(b.value,a.constant?void 0:
|
|
||||||
h.nextId(),void 0,function(a){l.push(h.escape(b.key.type===q.Identifier?b.key.name:""+b.key.value)+":"+a)})}),m="{"+l.join(",")+"}",this.assign(b,m));c(b||m);break;case q.ThisExpression:this.assign(b,"s");c(b||"s");break;case q.LocalsExpression:this.assign(b,"l");c(b||"l");break;case q.NGValueParameter:this.assign(b,"v"),c(b||"v")}},getHasOwnProperty:function(a,b){var d=a+"."+b,c=this.current().own;c.hasOwnProperty(d)||(c[d]=this.nextId(!1,a+"&&("+this.escape(b)+" in "+a+")"));return c[d]},assign:function(a,
|
|
||||||
b){if(a)return this.current().body.push(a,"=",b,";"),a},filter:function(a){this.state.filters.hasOwnProperty(a)||(this.state.filters[a]=this.nextId(!0));return this.state.filters[a]},ifDefined:function(a,b){return"ifDefined("+a+","+this.escape(b)+")"},plus:function(a,b){return"plus("+a+","+b+")"},return_:function(a){this.current().body.push("return ",a,";")},if_:function(a,b,d){if(!0===a)b();else{var c=this.current().body;c.push("if(",a,"){");b();c.push("}");d&&(c.push("else{"),d(),c.push("}"))}},
|
|
||||||
not:function(a){return"!("+a+")"},isNull:function(a){return a+"==null"},notNull:function(a){return a+"!=null"},nonComputedMember:function(a,b){var d=/[^$_a-zA-Z0-9]/g;return/^[$_a-zA-Z][$_a-zA-Z0-9]*$/.test(b)?a+"."+b:a+'["'+b.replace(d,this.stringEscapeFn)+'"]'},computedMember:function(a,b){return a+"["+b+"]"},member:function(a,b,d){return d?this.computedMember(a,b):this.nonComputedMember(a,b)},getStringValue:function(a){this.assign(a,"getStringValue("+a+")")},lazyRecurse:function(a,b,d,c,e,f){var g=
|
|
||||||
this;return function(){g.recurse(a,b,d,c,e,f)}},lazyAssign:function(a,b){var d=this;return function(){d.assign(a,b)}},stringEscapeRegex:/[^ a-zA-Z0-9]/g,stringEscapeFn:function(a){return"\\u"+("0000"+a.charCodeAt(0).toString(16)).slice(-4)},escape:function(a){if(A(a))return"'"+a.replace(this.stringEscapeRegex,this.stringEscapeFn)+"'";if(W(a))return a.toString();if(!0===a)return"true";if(!1===a)return"false";if(null===a)return"null";if("undefined"===typeof a)return"undefined";throw Ya("esc");},nextId:function(a,
|
|
||||||
b){var d="v"+this.state.nextId++;a||this.current().vars.push(d+(b?"="+b:""));return d},current:function(){return this.state[this.state.computing]}};Kd.prototype={compile:function(a){var b=this;Z(a,b.$filter);var d,c;if(d=Id(a))c=this.recurse(d);d=Gd(a.body);var e;d&&(e=[],r(d,function(a,c){var d=b.recurse(a);d.isPure=a.isPure;a.input=d;e.push(d);a.watchId=c}));var f=[];r(a.body,function(a){f.push(b.recurse(a.expression))});a=0===a.body.length?E:1===a.body.length?f[0]:function(a,b){var c;r(f,function(d){c=
|
|
||||||
d(a,b)});return c};c&&(a.assign=function(a,b,d){return c(a,d,b)});e&&(a.inputs=e);return a},recurse:function(a,b,d){var c,e,f=this,g;if(a.input)return this.inputs(a.input,a.watchId);switch(a.type){case q.Literal:return this.value(a.value,b);case q.UnaryExpression:return e=this.recurse(a.argument),this["unary"+a.operator](e,b);case q.BinaryExpression:return c=this.recurse(a.left),e=this.recurse(a.right),this["binary"+a.operator](c,e,b);case q.LogicalExpression:return c=this.recurse(a.left),e=this.recurse(a.right),
|
|
||||||
this["binary"+a.operator](c,e,b);case q.ConditionalExpression:return this["ternary?:"](this.recurse(a.test),this.recurse(a.alternate),this.recurse(a.consequent),b);case q.Identifier:return f.identifier(a.name,b,d);case q.MemberExpression:return c=this.recurse(a.object,!1,!!d),a.computed||(e=a.property.name),a.computed&&(e=this.recurse(a.property)),a.computed?this.computedMember(c,e,b,d):this.nonComputedMember(c,e,b,d);case q.CallExpression:return g=[],r(a.arguments,function(a){g.push(f.recurse(a))}),
|
|
||||||
a.filter&&(e=this.$filter(a.callee.name)),a.filter||(e=this.recurse(a.callee,!0)),a.filter?function(a,c,d,f){for(var p=[],n=0;n<g.length;++n)p.push(g[n](a,c,d,f));a=e.apply(void 0,p,f);return b?{context:void 0,name:void 0,value:a}:a}:function(a,c,d,f){var p=e(a,c,d,f),n;if(null!=p.value){n=[];for(var s=0;s<g.length;++s)n.push(g[s](a,c,d,f));n=p.value.apply(p.context,n)}return b?{value:n}:n};case q.AssignmentExpression:return c=this.recurse(a.left,!0,1),e=this.recurse(a.right),function(a,d,f,g){var p=
|
|
||||||
c(a,d,f,g);a=e(a,d,f,g);p.context[p.name]=a;return b?{value:a}:a};case q.ArrayExpression:return g=[],r(a.elements,function(a){g.push(f.recurse(a))}),function(a,c,d,e){for(var f=[],n=0;n<g.length;++n)f.push(g[n](a,c,d,e));return b?{value:f}:f};case q.ObjectExpression:return g=[],r(a.properties,function(a){a.computed?g.push({key:f.recurse(a.key),computed:!0,value:f.recurse(a.value)}):g.push({key:a.key.type===q.Identifier?a.key.name:""+a.key.value,computed:!1,value:f.recurse(a.value)})}),function(a,
|
|
||||||
c,d,e){for(var f={},n=0;n<g.length;++n)g[n].computed?f[g[n].key(a,c,d,e)]=g[n].value(a,c,d,e):f[g[n].key]=g[n].value(a,c,d,e);return b?{value:f}:f};case q.ThisExpression:return function(a){return b?{value:a}:a};case q.LocalsExpression:return function(a,c){return b?{value:c}:c};case q.NGValueParameter:return function(a,c,d){return b?{value:d}:d}}},"unary+":function(a,b){return function(d,c,e,f){d=a(d,c,e,f);d=w(d)?+d:0;return b?{value:d}:d}},"unary-":function(a,b){return function(d,c,e,f){d=a(d,c,
|
|
||||||
e,f);d=w(d)?-d:-0;return b?{value:d}:d}},"unary!":function(a,b){return function(d,c,e,f){d=!a(d,c,e,f);return b?{value:d}:d}},"binary+":function(a,b,d){return function(c,e,f,g){var k=a(c,e,f,g);c=b(c,e,f,g);k=Ed(k,c);return d?{value:k}:k}},"binary-":function(a,b,d){return function(c,e,f,g){var k=a(c,e,f,g);c=b(c,e,f,g);k=(w(k)?k:0)-(w(c)?c:0);return d?{value:k}:k}},"binary*":function(a,b,d){return function(c,e,f,g){c=a(c,e,f,g)*b(c,e,f,g);return d?{value:c}:c}},"binary/":function(a,b,d){return function(c,
|
|
||||||
e,f,g){c=a(c,e,f,g)/b(c,e,f,g);return d?{value:c}:c}},"binary%":function(a,b,d){return function(c,e,f,g){c=a(c,e,f,g)%b(c,e,f,g);return d?{value:c}:c}},"binary===":function(a,b,d){return function(c,e,f,g){c=a(c,e,f,g)===b(c,e,f,g);return d?{value:c}:c}},"binary!==":function(a,b,d){return function(c,e,f,g){c=a(c,e,f,g)!==b(c,e,f,g);return d?{value:c}:c}},"binary==":function(a,b,d){return function(c,e,f,g){c=a(c,e,f,g)==b(c,e,f,g);return d?{value:c}:c}},"binary!=":function(a,b,d){return function(c,
|
|
||||||
e,f,g){c=a(c,e,f,g)!=b(c,e,f,g);return d?{value:c}:c}},"binary<":function(a,b,d){return function(c,e,f,g){c=a(c,e,f,g)<b(c,e,f,g);return d?{value:c}:c}},"binary>":function(a,b,d){return function(c,e,f,g){c=a(c,e,f,g)>b(c,e,f,g);return d?{value:c}:c}},"binary<=":function(a,b,d){return function(c,e,f,g){c=a(c,e,f,g)<=b(c,e,f,g);return d?{value:c}:c}},"binary>=":function(a,b,d){return function(c,e,f,g){c=a(c,e,f,g)>=b(c,e,f,g);return d?{value:c}:c}},"binary&&":function(a,b,d){return function(c,e,f,g){c=
|
|
||||||
a(c,e,f,g)&&b(c,e,f,g);return d?{value:c}:c}},"binary||":function(a,b,d){return function(c,e,f,g){c=a(c,e,f,g)||b(c,e,f,g);return d?{value:c}:c}},"ternary?:":function(a,b,d,c){return function(e,f,g,k){e=a(e,f,g,k)?b(e,f,g,k):d(e,f,g,k);return c?{value:e}:e}},value:function(a,b){return function(){return b?{context:void 0,name:void 0,value:a}:a}},identifier:function(a,b,d){return function(c,e,f,g){c=e&&a in e?e:c;d&&1!==d&&c&&null==c[a]&&(c[a]={});e=c?c[a]:void 0;return b?{context:c,name:a,value:e}:
|
|
||||||
e}},computedMember:function(a,b,d,c){return function(e,f,g,k){var h=a(e,f,g,k),l,m;null!=h&&(l=b(e,f,g,k),l+="",c&&1!==c&&h&&!h[l]&&(h[l]={}),m=h[l]);return d?{context:h,name:l,value:m}:m}},nonComputedMember:function(a,b,d,c){return function(e,f,g,k){e=a(e,f,g,k);c&&1!==c&&e&&null==e[b]&&(e[b]={});f=null!=e?e[b]:void 0;return d?{context:e,name:b,value:f}:f}},inputs:function(a,b){return function(d,c,e,f){return f?f[b]:a(d,c,e)}}};Mb.prototype={constructor:Mb,parse:function(a){a=this.getAst(a);var b=
|
|
||||||
this.astCompiler.compile(a.ast),d=a.ast;b.literal=0===d.body.length||1===d.body.length&&(d.body[0].expression.type===q.Literal||d.body[0].expression.type===q.ArrayExpression||d.body[0].expression.type===q.ObjectExpression);b.constant=a.ast.constant;b.oneTime=a.oneTime;return b},getAst:function(a){var b=!1;a=a.trim();":"===a.charAt(0)&&":"===a.charAt(1)&&(b=!0,a=a.substring(2));return{ast:this.ast.ast(a),oneTime:b}}};var Ea=F("$sce"),V={HTML:"html",CSS:"css",MEDIA_URL:"mediaUrl",URL:"url",RESOURCE_URL:"resourceUrl",
|
|
||||||
JS:"js"},Cc=/_([a-z])/g,Ug=F("$templateRequest"),Vg=F("$timeout"),aa=C.document.createElement("a"),Od=ga(C.location.href),Na;aa.href="http://[::1]";var Wg="[::1]"===aa.hostname;Pd.$inject=["$document"];dd.$inject=["$provide"];var Wd=22,Vd=".",Ec="0";Qd.$inject=["$locale"];Sd.$inject=["$locale"];var gh={yyyy:ea("FullYear",4,0,!1,!0),yy:ea("FullYear",2,0,!0,!0),y:ea("FullYear",1,0,!1,!0),MMMM:kb("Month"),MMM:kb("Month",!0),MM:ea("Month",2,1),M:ea("Month",1,1),LLLL:kb("Month",!1,!0),dd:ea("Date",2),
|
|
||||||
d:ea("Date",1),HH:ea("Hours",2),H:ea("Hours",1),hh:ea("Hours",2,-12),h:ea("Hours",1,-12),mm:ea("Minutes",2),m:ea("Minutes",1),ss:ea("Seconds",2),s:ea("Seconds",1),sss:ea("Milliseconds",3),EEEE:kb("Day"),EEE:kb("Day",!0),a:function(a,b){return 12>a.getHours()?b.AMPMS[0]:b.AMPMS[1]},Z:function(a,b,d){a=-1*d;return a=(0<=a?"+":"")+(Ob(Math[0<a?"floor":"ceil"](a/60),2)+Ob(Math.abs(a%60),2))},ww:Yd(2),w:Yd(1),G:Fc,GG:Fc,GGG:Fc,GGGG:function(a,b){return 0>=a.getFullYear()?b.ERANAMES[0]:b.ERANAMES[1]}},
|
|
||||||
fh=/((?:[^yMLdHhmsaZEwG']+)|(?:'(?:[^']|'')*')|(?:E+|y+|M+|L+|d+|H+|h+|m+|s+|a|Z|G+|w+))([\s\S]*)/,eh=/^-?\d+$/;Rd.$inject=["$locale"];var $g=ia(K),ah=ia(ub);Td.$inject=["$parse"];var Me=ia({restrict:"E",compile:function(a,b){if(!b.href&&!b.xlinkHref)return function(a,b){if("a"===b[0].nodeName.toLowerCase()){var e="[object SVGAnimatedString]"===la.call(b.prop("href"))?"xlink:href":"href";b.on("click",function(a){b.attr(e)||a.preventDefault()})}}}}),vb={};r(Gb,function(a,b){function d(a,d,e){a.$watch(e[c],
|
|
||||||
function(a){e.$set(b,!!a)})}if("multiple"!==a){var c=wa("ng-"+b),e=d;"checked"===a&&(e=function(a,b,e){e.ngModel!==e[c]&&d(a,b,e)});vb[c]=function(){return{restrict:"A",priority:100,link:e}}}});r(td,function(a,b){vb[b]=function(){return{priority:100,link:function(a,c,e){if("ngPattern"===b&&"/"===e.ngPattern.charAt(0)&&(c=e.ngPattern.match(ie))){e.$set("ngPattern",new RegExp(c[1],c[2]));return}a.$watch(e[b],function(a){e.$set(b,a)})}}}});r(["src","srcset","href"],function(a){var b=wa("ng-"+a);vb[b]=
|
|
||||||
["$sce",function(d){return{priority:99,link:function(c,e,f){var g=a,k=a;"href"===a&&"[object SVGAnimatedString]"===la.call(e.prop("href"))&&(k="xlinkHref",f.$attr[k]="xlink:href",g=null);f.$set(b,d.getTrustedMediaUrl(f[b]));f.$observe(b,function(b){b?(f.$set(k,b),Ca&&g&&e.prop(g,f[k])):"href"===a&&f.$set(k,null)})}}}]});var lb={$addControl:E,$getControls:ia([]),$$renameControl:function(a,b){a.$name=b},$removeControl:E,$setValidity:E,$setDirty:E,$setPristine:E,$setSubmitted:E,$$setSubmitted:E};Pb.$inject=
|
|
||||||
["$element","$attrs","$scope","$animate","$interpolate"];Pb.prototype={$rollbackViewValue:function(){r(this.$$controls,function(a){a.$rollbackViewValue()})},$commitViewValue:function(){r(this.$$controls,function(a){a.$commitViewValue()})},$addControl:function(a){Ja(a.$name,"input");this.$$controls.push(a);a.$name&&(this[a.$name]=a);a.$$parentForm=this},$getControls:function(){return ja(this.$$controls)},$$renameControl:function(a,b){var d=a.$name;this[d]===a&&delete this[d];this[b]=a;a.$name=b},$removeControl:function(a){a.$name&&
|
|
||||||
this[a.$name]===a&&delete this[a.$name];r(this.$pending,function(b,d){this.$setValidity(d,null,a)},this);r(this.$error,function(b,d){this.$setValidity(d,null,a)},this);r(this.$$success,function(b,d){this.$setValidity(d,null,a)},this);cb(this.$$controls,a);a.$$parentForm=lb},$setDirty:function(){this.$$animate.removeClass(this.$$element,Za);this.$$animate.addClass(this.$$element,Vb);this.$dirty=!0;this.$pristine=!1;this.$$parentForm.$setDirty()},$setPristine:function(){this.$$animate.setClass(this.$$element,
|
|
||||||
Za,Vb+" ng-submitted");this.$dirty=!1;this.$pristine=!0;this.$submitted=!1;r(this.$$controls,function(a){a.$setPristine()})},$setUntouched:function(){r(this.$$controls,function(a){a.$setUntouched()})},$setSubmitted:function(){for(var a=this;a.$$parentForm&&a.$$parentForm!==lb;)a=a.$$parentForm;a.$$setSubmitted()},$$setSubmitted:function(){this.$$animate.addClass(this.$$element,"ng-submitted");this.$submitted=!0;r(this.$$controls,function(a){a.$$setSubmitted&&a.$$setSubmitted()})}};ae({clazz:Pb,set:function(a,
|
|
||||||
b,d){var c=a[b];c?-1===c.indexOf(d)&&c.push(d):a[b]=[d]},unset:function(a,b,d){var c=a[b];c&&(cb(c,d),0===c.length&&delete a[b])}});var ke=function(a){return["$timeout","$parse",function(b,d){function c(a){return""===a?d('this[""]').assign:d(a).assign||E}return{name:"form",restrict:a?"EAC":"E",require:["form","^^?form"],controller:Pb,compile:function(d,f){d.addClass(Za).addClass(mb);var g=f.name?"name":a&&f.ngForm?"ngForm":!1;return{pre:function(a,d,e,f){var p=f[0];if(!("action"in e)){var n=function(b){a.$apply(function(){p.$commitViewValue();
|
|
||||||
p.$setSubmitted()});b.preventDefault()};d[0].addEventListener("submit",n);d.on("$destroy",function(){b(function(){d[0].removeEventListener("submit",n)},0,!1)})}(f[1]||p.$$parentForm).$addControl(p);var s=g?c(p.$name):E;g&&(s(a,p),e.$observe(g,function(b){p.$name!==b&&(s(a,void 0),p.$$parentForm.$$renameControl(p,b),s=c(p.$name),s(a,p))}));d.on("$destroy",function(){p.$$parentForm.$removeControl(p);s(a,void 0);S(p,lb)})}}}}}]},Ne=ke(),Ze=ke(!0),hh=/^\d{4,}-[01]\d-[0-3]\dT[0-2]\d:[0-5]\d:[0-5]\d\.\d+(?:[+-][0-2]\d:[0-5]\d|Z)$/,
|
|
||||||
sh=/^[a-z][a-z\d.+-]*:\/*(?:[^:@]+(?::[^@]+)?@)?(?:[^\s:/?#]+|\[[a-f\d:]+])(?::\d+)?(?:\/[^?#]*)?(?:\?[^#]*)?(?:#.*)?$/i,th=/^(?=.{1,254}$)(?=.{1,64}@)[-!#$%&'*+/0-9=?A-Z^_`a-z{|}~]+(\.[-!#$%&'*+/0-9=?A-Z^_`a-z{|}~]+)*@[A-Za-z0-9]([A-Za-z0-9-]{0,61}[A-Za-z0-9])?(\.[A-Za-z0-9]([A-Za-z0-9-]{0,61}[A-Za-z0-9])?)*$/,ih=/^\s*(-|\+)?(\d+|(\d*(\.\d*)))([eE][+-]?\d+)?\s*$/,le=/^(\d{4,})-(\d{2})-(\d{2})$/,me=/^(\d{4,})-(\d\d)-(\d\d)T(\d\d):(\d\d)(?::(\d\d)(\.\d{1,3})?)?$/,Mc=/^(\d{4,})-W(\d\d)$/,ne=/^(\d{4,})-(\d\d)$/,
|
|
||||||
oe=/^(\d\d):(\d\d)(?::(\d\d)(\.\d{1,3})?)?$/,ce=T();r(["date","datetime-local","month","time","week"],function(a){ce[a]=!0});var pe={text:function(a,b,d,c,e,f){Sa(a,b,d,c,e,f);Hc(c)},date:nb("date",le,Qb(le,["yyyy","MM","dd"]),"yyyy-MM-dd"),"datetime-local":nb("datetimelocal",me,Qb(me,"yyyy MM dd HH mm ss sss".split(" ")),"yyyy-MM-ddTHH:mm:ss.sss"),time:nb("time",oe,Qb(oe,["HH","mm","ss","sss"]),"HH:mm:ss.sss"),week:nb("week",Mc,function(a,b){if(ha(a))return a;if(A(a)){Mc.lastIndex=0;var d=Mc.exec(a);
|
|
||||||
if(d){var c=+d[1],e=+d[2],f=d=0,g=0,k=0,h=Xd(c),e=7*(e-1);b&&(d=b.getHours(),f=b.getMinutes(),g=b.getSeconds(),k=b.getMilliseconds());return new Date(c,0,h.getDate()+e,d,f,g,k)}}return NaN},"yyyy-Www"),month:nb("month",ne,Qb(ne,["yyyy","MM"]),"yyyy-MM"),number:function(a,b,d,c,e,f,g,k){Ic(a,b,d,c,"number");de(c);Sa(a,b,d,c,e,f);var h;if(w(d.min)||d.ngMin){var l=d.min||k(d.ngMin)(a);h=na(l);c.$validators.min=function(a,b){return c.$isEmpty(b)||z(h)||b>=h};d.$observe("min",function(a){a!==l&&(h=na(a),
|
|
||||||
l=a,c.$validate())})}if(w(d.max)||d.ngMax){var m=d.max||k(d.ngMax)(a),p=na(m);c.$validators.max=function(a,b){return c.$isEmpty(b)||z(p)||b<=p};d.$observe("max",function(a){a!==m&&(p=na(a),m=a,c.$validate())})}if(w(d.step)||d.ngStep){var n=d.step||k(d.ngStep)(a),s=na(n);c.$validators.step=function(a,b){return c.$isEmpty(b)||z(s)||ee(b,h||0,s)};d.$observe("step",function(a){a!==n&&(s=na(a),n=a,c.$validate())})}},url:function(a,b,d,c,e,f){Sa(a,b,d,c,e,f);Hc(c);c.$validators.url=function(a,b){var d=
|
|
||||||
a||b;return c.$isEmpty(d)||sh.test(d)}},email:function(a,b,d,c,e,f){Sa(a,b,d,c,e,f);Hc(c);c.$validators.email=function(a,b){var d=a||b;return c.$isEmpty(d)||th.test(d)}},radio:function(a,b,d,c){var e=!d.ngTrim||"false"!==U(d.ngTrim);z(d.name)&&b.attr("name",++pb);b.on("change",function(a){var g;b[0].checked&&(g=d.value,e&&(g=U(g)),c.$setViewValue(g,a&&a.type))});c.$render=function(){var a=d.value;e&&(a=U(a));b[0].checked=a===c.$viewValue};d.$observe("value",c.$render)},range:function(a,b,d,c,e,f){function g(a,
|
|
||||||
c){b.attr(a,d[a]);var e=d[a];d.$observe(a,function(a){a!==e&&(e=a,c(a))})}function k(a){p=na(a);X(c.$modelValue)||(m?(a=b.val(),p>a&&(a=p,b.val(a)),c.$setViewValue(a)):c.$validate())}function h(a){n=na(a);X(c.$modelValue)||(m?(a=b.val(),n<a&&(b.val(n),a=n<p?p:n),c.$setViewValue(a)):c.$validate())}function l(a){s=na(a);X(c.$modelValue)||(m?c.$viewValue!==b.val()&&c.$setViewValue(b.val()):c.$validate())}Ic(a,b,d,c,"range");de(c);Sa(a,b,d,c,e,f);var m=c.$$hasNativeValidators&&"range"===b[0].type,p=m?
|
|
||||||
0:void 0,n=m?100:void 0,s=m?1:void 0,r=b[0].validity;a=w(d.min);e=w(d.max);f=w(d.step);var q=c.$render;c.$render=m&&w(r.rangeUnderflow)&&w(r.rangeOverflow)?function(){q();c.$setViewValue(b.val())}:q;a&&(p=na(d.min),c.$validators.min=m?function(){return!0}:function(a,b){return c.$isEmpty(b)||z(p)||b>=p},g("min",k));e&&(n=na(d.max),c.$validators.max=m?function(){return!0}:function(a,b){return c.$isEmpty(b)||z(n)||b<=n},g("max",h));f&&(s=na(d.step),c.$validators.step=m?function(){return!r.stepMismatch}:
|
|
||||||
function(a,b){return c.$isEmpty(b)||z(s)||ee(b,p||0,s)},g("step",l))},checkbox:function(a,b,d,c,e,f,g,k){var h=fe(k,a,"ngTrueValue",d.ngTrueValue,!0),l=fe(k,a,"ngFalseValue",d.ngFalseValue,!1);b.on("change",function(a){c.$setViewValue(b[0].checked,a&&a.type)});c.$render=function(){b[0].checked=c.$viewValue};c.$isEmpty=function(a){return!1===a};c.$formatters.push(function(a){return va(a,h)});c.$parsers.push(function(a){return a?h:l})},hidden:E,button:E,submit:E,reset:E,file:E},Yc=["$browser","$sniffer",
|
|
||||||
"$filter","$parse",function(a,b,d,c){return{restrict:"E",require:["?ngModel"],link:{pre:function(e,f,g,k){k[0]&&(pe[K(g.type)]||pe.text)(e,f,g,k[0],b,a,d,c)}}}}],vf=function(){var a={configurable:!0,enumerable:!1,get:function(){return this.getAttribute("value")||""},set:function(a){this.setAttribute("value",a)}};return{restrict:"E",priority:200,compile:function(b,d){if("hidden"===K(d.type))return{pre:function(b,d,f,g){b=d[0];b.parentNode&&b.parentNode.insertBefore(b,b.nextSibling);Object.defineProperty&&
|
|
||||||
Object.defineProperty(b,"value",a)}}}}},uh=/^(true|false|\d+)$/,sf=function(){function a(a,d,c){var e=w(c)?c:9===Ca?"":null;a.prop("value",e);d.$set("value",c)}return{restrict:"A",priority:100,compile:function(b,d){return uh.test(d.ngValue)?function(b,d,f){b=b.$eval(f.ngValue);a(d,f,b)}:function(b,d,f){b.$watch(f.ngValue,function(b){a(d,f,b)})}}}},Re=["$compile",function(a){return{restrict:"AC",compile:function(b){a.$$addBindingClass(b);return function(b,c,e){a.$$addBindingInfo(c,e.ngBind);c=c[0];
|
|
||||||
b.$watch(e.ngBind,function(a){c.textContent=ic(a)})}}}}],Te=["$interpolate","$compile",function(a,b){return{compile:function(d){b.$$addBindingClass(d);return function(c,d,f){c=a(d.attr(f.$attr.ngBindTemplate));b.$$addBindingInfo(d,c.expressions);d=d[0];f.$observe("ngBindTemplate",function(a){d.textContent=z(a)?"":a})}}}}],Se=["$sce","$parse","$compile",function(a,b,d){return{restrict:"A",compile:function(c,e){var f=b(e.ngBindHtml),g=b(e.ngBindHtml,function(b){return a.valueOf(b)});d.$$addBindingClass(c);
|
|
||||||
return function(b,c,e){d.$$addBindingInfo(c,e.ngBindHtml);b.$watch(g,function(){var d=f(b);c.html(a.getTrustedHtml(d)||"")})}}}}],rf=ia({restrict:"A",require:"ngModel",link:function(a,b,d,c){c.$viewChangeListeners.push(function(){a.$eval(d.ngChange)})}}),Ue=Kc("",!0),We=Kc("Odd",0),Ve=Kc("Even",1),Xe=Ra({compile:function(a,b){b.$set("ngCloak",void 0);a.removeClass("ng-cloak")}}),Ye=[function(){return{restrict:"A",scope:!0,controller:"@",priority:500}}],cd={},vh={blur:!0,focus:!0};r("click dblclick mousedown mouseup mouseover mouseout mousemove mouseenter mouseleave keydown keyup keypress submit focus blur copy cut paste".split(" "),
|
|
||||||
function(a){var b=wa("ng-"+a);cd[b]=["$parse","$rootScope","$exceptionHandler",function(d,c,e){return qd(d,c,e,b,a,vh[a])}]});var af=["$animate","$compile",function(a,b){return{multiElement:!0,transclude:"element",priority:600,terminal:!0,restrict:"A",$$tlb:!0,link:function(d,c,e,f,g){var k,h,l;d.$watch(e.ngIf,function(d){d?h||g(function(d,f){h=f;d[d.length++]=b.$$createComment("end ngIf",e.ngIf);k={clone:d};a.enter(d,c.parent(),c)}):(l&&(l.remove(),l=null),h&&(h.$destroy(),h=null),k&&(l=tb(k.clone),
|
|
||||||
a.leave(l).done(function(a){!1!==a&&(l=null)}),k=null))})}}}],bf=["$templateRequest","$anchorScroll","$animate",function(a,b,d){return{restrict:"ECA",priority:400,terminal:!0,transclude:"element",controller:ca.noop,compile:function(c,e){var f=e.ngInclude||e.src,g=e.onload||"",k=e.autoscroll;return function(c,e,m,p,n){var r=0,q,t,x,v=function(){t&&(t.remove(),t=null);q&&(q.$destroy(),q=null);x&&(d.leave(x).done(function(a){!1!==a&&(t=null)}),t=x,x=null)};c.$watch(f,function(f){var m=function(a){!1===
|
|
||||||
a||!w(k)||k&&!c.$eval(k)||b()},t=++r;f?(a(f,!0).then(function(a){if(!c.$$destroyed&&t===r){var b=c.$new();p.template=a;a=n(b,function(a){v();d.enter(a,null,e).done(m)});q=b;x=a;q.$emit("$includeContentLoaded",f);c.$eval(g)}},function(){c.$$destroyed||t!==r||(v(),c.$emit("$includeContentError",f))}),c.$emit("$includeContentRequested",f)):(v(),p.template=null)})}}}}],uf=["$compile",function(a){return{restrict:"ECA",priority:-400,require:"ngInclude",link:function(b,d,c,e){la.call(d[0]).match(/SVG/)?
|
|
||||||
(d.empty(),a(ed(e.template,C.document).childNodes)(b,function(a){d.append(a)},{futureParentElement:d})):(d.html(e.template),a(d.contents())(b))}}}],cf=Ra({priority:450,compile:function(){return{pre:function(a,b,d){a.$eval(d.ngInit)}}}}),qf=function(){return{restrict:"A",priority:100,require:"ngModel",link:function(a,b,d,c){var e=d.ngList||", ",f="false"!==d.ngTrim,g=f?U(e):e;c.$parsers.push(function(a){if(!z(a)){var b=[];a&&r(a.split(g),function(a){a&&b.push(f?U(a):a)});return b}});c.$formatters.push(function(a){if(H(a))return a.join(e)});
|
|
||||||
c.$isEmpty=function(a){return!a||!a.length}}}},mb="ng-valid",$d="ng-invalid",Za="ng-pristine",Vb="ng-dirty",ob=F("ngModel");Rb.$inject="$scope $exceptionHandler $attrs $element $parse $animate $timeout $q $interpolate".split(" ");Rb.prototype={$$initGetterSetters:function(){if(this.$options.getOption("getterSetter")){var a=this.$$parse(this.$$attr.ngModel+"()"),b=this.$$parse(this.$$attr.ngModel+"($$$p)");this.$$ngModelGet=function(b){var c=this.$$parsedNgModel(b);B(c)&&(c=a(b));return c};this.$$ngModelSet=
|
|
||||||
function(a,c){B(this.$$parsedNgModel(a))?b(a,{$$$p:c}):this.$$parsedNgModelAssign(a,c)}}else if(!this.$$parsedNgModel.assign)throw ob("nonassign",this.$$attr.ngModel,za(this.$$element));},$render:E,$isEmpty:function(a){return z(a)||""===a||null===a||a!==a},$$updateEmptyClasses:function(a){this.$isEmpty(a)?(this.$$animate.removeClass(this.$$element,"ng-not-empty"),this.$$animate.addClass(this.$$element,"ng-empty")):(this.$$animate.removeClass(this.$$element,"ng-empty"),this.$$animate.addClass(this.$$element,
|
|
||||||
"ng-not-empty"))},$setPristine:function(){this.$dirty=!1;this.$pristine=!0;this.$$animate.removeClass(this.$$element,Vb);this.$$animate.addClass(this.$$element,Za)},$setDirty:function(){this.$dirty=!0;this.$pristine=!1;this.$$animate.removeClass(this.$$element,Za);this.$$animate.addClass(this.$$element,Vb);this.$$parentForm.$setDirty()},$setUntouched:function(){this.$touched=!1;this.$untouched=!0;this.$$animate.setClass(this.$$element,"ng-untouched","ng-touched")},$setTouched:function(){this.$touched=
|
|
||||||
!0;this.$untouched=!1;this.$$animate.setClass(this.$$element,"ng-touched","ng-untouched")},$rollbackViewValue:function(){this.$$timeout.cancel(this.$$pendingDebounce);this.$viewValue=this.$$lastCommittedViewValue;this.$render()},$validate:function(){if(!X(this.$modelValue)){var a=this.$$lastCommittedViewValue,b=this.$$rawModelValue,d=this.$valid,c=this.$modelValue,e=this.$options.getOption("allowInvalid"),f=this;this.$$runValidators(b,a,function(a){e||d===a||(f.$modelValue=a?b:void 0,f.$modelValue!==
|
|
||||||
c&&f.$$writeModelToScope())})}},$$runValidators:function(a,b,d){function c(){var c=!0;r(h.$validators,function(d,e){var g=Boolean(d(a,b));c=c&&g;f(e,g)});return c?!0:(r(h.$asyncValidators,function(a,b){f(b,null)}),!1)}function e(){var c=[],d=!0;r(h.$asyncValidators,function(e,g){var h=e(a,b);if(!h||!B(h.then))throw ob("nopromise",h);f(g,void 0);c.push(h.then(function(){f(g,!0)},function(){d=!1;f(g,!1)}))});c.length?h.$$q.all(c).then(function(){g(d)},E):g(!0)}function f(a,b){k===h.$$currentValidationRunId&&
|
|
||||||
h.$setValidity(a,b)}function g(a){k===h.$$currentValidationRunId&&d(a)}this.$$currentValidationRunId++;var k=this.$$currentValidationRunId,h=this;(function(){var a=h.$$parserName;if(z(h.$$parserValid))f(a,null);else return h.$$parserValid||(r(h.$validators,function(a,b){f(b,null)}),r(h.$asyncValidators,function(a,b){f(b,null)})),f(a,h.$$parserValid),h.$$parserValid;return!0})()?c()?e():g(!1):g(!1)},$commitViewValue:function(){var a=this.$viewValue;this.$$timeout.cancel(this.$$pendingDebounce);if(this.$$lastCommittedViewValue!==
|
|
||||||
a||""===a&&this.$$hasNativeValidators)this.$$updateEmptyClasses(a),this.$$lastCommittedViewValue=a,this.$pristine&&this.$setDirty(),this.$$parseAndValidate()},$$parseAndValidate:function(){var a=this.$$lastCommittedViewValue,b=this;this.$$parserValid=z(a)?void 0:!0;this.$setValidity(this.$$parserName,null);this.$$parserName="parse";if(this.$$parserValid)for(var d=0;d<this.$parsers.length;d++)if(a=this.$parsers[d](a),z(a)){this.$$parserValid=!1;break}X(this.$modelValue)&&(this.$modelValue=this.$$ngModelGet(this.$$scope));
|
|
||||||
var c=this.$modelValue,e=this.$options.getOption("allowInvalid");this.$$rawModelValue=a;e&&(this.$modelValue=a,b.$modelValue!==c&&b.$$writeModelToScope());this.$$runValidators(a,this.$$lastCommittedViewValue,function(d){e||(b.$modelValue=d?a:void 0,b.$modelValue!==c&&b.$$writeModelToScope())})},$$writeModelToScope:function(){this.$$ngModelSet(this.$$scope,this.$modelValue);r(this.$viewChangeListeners,function(a){try{a()}catch(b){this.$$exceptionHandler(b)}},this)},$setViewValue:function(a,b){this.$viewValue=
|
|
||||||
a;this.$options.getOption("updateOnDefault")&&this.$$debounceViewValueCommit(b)},$$debounceViewValueCommit:function(a){var b=this.$options.getOption("debounce");W(b[a])?b=b[a]:W(b["default"])&&-1===this.$options.getOption("updateOn").indexOf(a)?b=b["default"]:W(b["*"])&&(b=b["*"]);this.$$timeout.cancel(this.$$pendingDebounce);var d=this;0<b?this.$$pendingDebounce=this.$$timeout(function(){d.$commitViewValue()},b):this.$$rootScope.$$phase?this.$commitViewValue():this.$$scope.$apply(function(){d.$commitViewValue()})},
|
|
||||||
$overrideModelOptions:function(a){this.$options=this.$options.createChild(a);this.$$setUpdateOnEvents()},$processModelValue:function(){var a=this.$$format();this.$viewValue!==a&&(this.$$updateEmptyClasses(a),this.$viewValue=this.$$lastCommittedViewValue=a,this.$render(),this.$$runValidators(this.$modelValue,this.$viewValue,E))},$$format:function(){for(var a=this.$formatters,b=a.length,d=this.$modelValue;b--;)d=a[b](d);return d},$$setModelValue:function(a){this.$modelValue=this.$$rawModelValue=a;this.$$parserValid=
|
|
||||||
void 0;this.$processModelValue()},$$setUpdateOnEvents:function(){this.$$updateEvents&&this.$$element.off(this.$$updateEvents,this.$$updateEventHandler);if(this.$$updateEvents=this.$options.getOption("updateOn"))this.$$element.on(this.$$updateEvents,this.$$updateEventHandler)},$$updateEventHandler:function(a){this.$$debounceViewValueCommit(a&&a.type)}};ae({clazz:Rb,set:function(a,b){a[b]=!0},unset:function(a,b){delete a[b]}});var pf=["$rootScope",function(a){return{restrict:"A",require:["ngModel",
|
|
||||||
"^?form","^?ngModelOptions"],controller:Rb,priority:1,compile:function(b){b.addClass(Za).addClass("ng-untouched").addClass(mb);return{pre:function(a,b,e,f){var g=f[0];b=f[1]||g.$$parentForm;if(f=f[2])g.$options=f.$options;g.$$initGetterSetters();b.$addControl(g);e.$observe("name",function(a){g.$name!==a&&g.$$parentForm.$$renameControl(g,a)});a.$on("$destroy",function(){g.$$parentForm.$removeControl(g)})},post:function(b,c,e,f){function g(){k.$setTouched()}var k=f[0];k.$$setUpdateOnEvents();c.on("blur",
|
|
||||||
function(){k.$touched||(a.$$phase?b.$evalAsync(g):b.$apply(g))})}}}}}],Sb,wh=/(\s+|^)default(\s+|$)/;Lc.prototype={getOption:function(a){return this.$$options[a]},createChild:function(a){var b=!1;a=S({},a);r(a,function(d,c){"$inherit"===d?"*"===c?b=!0:(a[c]=this.$$options[c],"updateOn"===c&&(a.updateOnDefault=this.$$options.updateOnDefault)):"updateOn"===c&&(a.updateOnDefault=!1,a[c]=U(d.replace(wh,function(){a.updateOnDefault=!0;return" "})))},this);b&&(delete a["*"],ge(a,this.$$options));ge(a,Sb.$$options);
|
|
||||||
return new Lc(a)}};Sb=new Lc({updateOn:"",updateOnDefault:!0,debounce:0,getterSetter:!1,allowInvalid:!1,timezone:null});var tf=function(){function a(a,d){this.$$attrs=a;this.$$scope=d}a.$inject=["$attrs","$scope"];a.prototype={$onInit:function(){var a=this.parentCtrl?this.parentCtrl.$options:Sb,d=this.$$scope.$eval(this.$$attrs.ngModelOptions);this.$options=a.createChild(d)}};return{restrict:"A",priority:10,require:{parentCtrl:"?^^ngModelOptions"},bindToController:!0,controller:a}},df=Ra({terminal:!0,
|
|
||||||
priority:1E3}),xh=F("ngOptions"),yh=/^\s*([\s\S]+?)(?:\s+as\s+([\s\S]+?))?(?:\s+group\s+by\s+([\s\S]+?))?(?:\s+disable\s+when\s+([\s\S]+?))?\s+for\s+(?:([$\w][$\w]*)|(?:\(\s*([$\w][$\w]*)\s*,\s*([$\w][$\w]*)\s*\)))\s+in\s+([\s\S]+?)(?:\s+track\s+by\s+([\s\S]+?))?$/,nf=["$compile","$document","$parse",function(a,b,d){function c(a,b,c){function e(a,b,c,d,f){this.selectValue=a;this.viewValue=b;this.label=c;this.group=d;this.disabled=f}function f(a){var b;if(!r&&ya(a))b=a;else{b=[];for(var c in a)a.hasOwnProperty(c)&&
|
|
||||||
"$"!==c.charAt(0)&&b.push(c)}return b}var p=a.match(yh);if(!p)throw xh("iexp",a,za(b));var n=p[5]||p[7],r=p[6];a=/ as /.test(p[0])&&p[1];var q=p[9];b=d(p[2]?p[1]:n);var t=a&&d(a)||b,w=q&&d(q),v=q?function(a,b){return w(c,b)}:function(a){return La(a)},x=function(a,b){return v(a,A(a,b))},z=d(p[2]||p[1]),y=d(p[3]||""),J=d(p[4]||""),I=d(p[8]),B={},A=r?function(a,b){B[r]=b;B[n]=a;return B}:function(a){B[n]=a;return B};return{trackBy:q,getTrackByValue:x,getWatchables:d(I,function(a){var b=[];a=a||[];for(var d=
|
|
||||||
f(a),e=d.length,g=0;g<e;g++){var k=a===d?g:d[g],l=a[k],k=A(l,k),l=v(l,k);b.push(l);if(p[2]||p[1])l=z(c,k),b.push(l);p[4]&&(k=J(c,k),b.push(k))}return b}),getOptions:function(){for(var a=[],b={},d=I(c)||[],g=f(d),k=g.length,n=0;n<k;n++){var p=d===g?n:g[n],r=A(d[p],p),s=t(c,r),p=v(s,r),w=z(c,r),B=y(c,r),r=J(c,r),s=new e(p,s,w,B,r);a.push(s);b[p]=s}return{items:a,selectValueMap:b,getOptionFromViewValue:function(a){return b[x(a)]},getViewValueFromOption:function(a){return q?Ia(a.viewValue):a.viewValue}}}}}
|
|
||||||
var e=C.document.createElement("option"),f=C.document.createElement("optgroup");return{restrict:"A",terminal:!0,require:["select","ngModel"],link:{pre:function(a,b,c,d){d[0].registerOption=E},post:function(d,k,h,l){function m(a){var b=(a=v.getOptionFromViewValue(a))&&a.element;b&&!b.selected&&(b.selected=!0);return a}function p(a,b){a.element=b;b.disabled=a.disabled;a.label!==b.label&&(b.label=a.label,b.textContent=a.label);b.value=a.selectValue}var n=l[0],q=l[1],z=h.multiple;l=0;for(var t=k.children(),
|
|
||||||
B=t.length;l<B;l++)if(""===t[l].value){n.hasEmptyOption=!0;n.emptyOption=t.eq(l);break}k.empty();l=!!n.emptyOption;x(e.cloneNode(!1)).val("?");var v,A=c(h.ngOptions,k,d),C=b[0].createDocumentFragment();n.generateUnknownOptionValue=function(a){return"?"};z?(n.writeValue=function(a){if(v){var b=a&&a.map(m)||[];v.items.forEach(function(a){a.element.selected&&-1===Array.prototype.indexOf.call(b,a)&&(a.element.selected=!1)})}},n.readValue=function(){var a=k.val()||[],b=[];r(a,function(a){(a=v.selectValueMap[a])&&
|
|
||||||
!a.disabled&&b.push(v.getViewValueFromOption(a))});return b},A.trackBy&&d.$watchCollection(function(){if(H(q.$viewValue))return q.$viewValue.map(function(a){return A.getTrackByValue(a)})},function(){q.$render()})):(n.writeValue=function(a){if(v){var b=k[0].options[k[0].selectedIndex],c=v.getOptionFromViewValue(a);b&&b.removeAttribute("selected");c?(k[0].value!==c.selectValue&&(n.removeUnknownOption(),k[0].value=c.selectValue,c.element.selected=!0),c.element.setAttribute("selected","selected")):n.selectUnknownOrEmptyOption(a)}},
|
|
||||||
n.readValue=function(){var a=v.selectValueMap[k.val()];return a&&!a.disabled?(n.unselectEmptyOption(),n.removeUnknownOption(),v.getViewValueFromOption(a)):null},A.trackBy&&d.$watch(function(){return A.getTrackByValue(q.$viewValue)},function(){q.$render()}));l&&(a(n.emptyOption)(d),k.prepend(n.emptyOption),8===n.emptyOption[0].nodeType?(n.hasEmptyOption=!1,n.registerOption=function(a,b){""===b.val()&&(n.hasEmptyOption=!0,n.emptyOption=b,n.emptyOption.removeClass("ng-scope"),q.$render(),b.on("$destroy",
|
|
||||||
function(){var a=n.$isEmptyOptionSelected();n.hasEmptyOption=!1;n.emptyOption=void 0;a&&q.$render()}))}):n.emptyOption.removeClass("ng-scope"));d.$watchCollection(A.getWatchables,function(){var a=v&&n.readValue();if(v)for(var b=v.items.length-1;0<=b;b--){var c=v.items[b];w(c.group)?Fb(c.element.parentNode):Fb(c.element)}v=A.getOptions();var d={};v.items.forEach(function(a){var b;if(w(a.group)){b=d[a.group];b||(b=f.cloneNode(!1),C.appendChild(b),b.label=null===a.group?"null":a.group,d[a.group]=b);
|
|
||||||
var c=e.cloneNode(!1);b.appendChild(c);p(a,c)}else b=e.cloneNode(!1),C.appendChild(b),p(a,b)});k[0].appendChild(C);q.$render();q.$isEmpty(a)||(b=n.readValue(),(A.trackBy||z?va(a,b):a===b)||(q.$setViewValue(b),q.$render()))})}}}}],ef=["$locale","$interpolate","$log",function(a,b,d){var c=/{}/g,e=/^when(Minus)?(.+)$/;return{link:function(f,g,k){function h(a){g.text(a||"")}var l=k.count,m=k.$attr.when&&g.attr(k.$attr.when),p=k.offset||0,n=f.$eval(m)||{},q={},w=b.startSymbol(),t=b.endSymbol(),x=w+l+"-"+
|
|
||||||
p+t,v=ca.noop,A;r(k,function(a,b){var c=e.exec(b);c&&(c=(c[1]?"-":"")+K(c[2]),n[c]=g.attr(k.$attr[b]))});r(n,function(a,d){q[d]=b(a.replace(c,x))});f.$watch(l,function(b){var c=parseFloat(b),e=X(c);e||c in n||(c=a.pluralCat(c-p));c===A||e&&X(A)||(v(),e=q[c],z(e)?(null!=b&&d.debug("ngPluralize: no rule defined for '"+c+"' in "+m),v=E,h()):v=f.$watch(e,h),A=c)})}}}],qe=F("ngRef"),ff=["$parse",function(a){return{priority:-1,restrict:"A",compile:function(b,d){var c=wa(ua(b)),e=a(d.ngRef),f=e.assign||
|
|
||||||
function(){throw qe("nonassign",d.ngRef);};return function(a,b,h){var l;if(h.hasOwnProperty("ngRefRead"))if("$element"===h.ngRefRead)l=b;else{if(l=b.data("$"+h.ngRefRead+"Controller"),!l)throw qe("noctrl",h.ngRefRead,d.ngRef);}else l=b.data("$"+c+"Controller");l=l||b;f(a,l);b.on("$destroy",function(){e(a)===l&&f(a,null)})}}}}],gf=["$parse","$animate","$compile",function(a,b,d){var c=F("ngRepeat"),e=function(a,b,c,d,e,f,g){a[c]=d;e&&(a[e]=f);a.$index=b;a.$first=0===b;a.$last=b===g-1;a.$middle=!(a.$first||
|
|
||||||
a.$last);a.$odd=!(a.$even=0===(b&1))},f=function(a,b,c){return La(c)},g=function(a,b){return b};return{restrict:"A",multiElement:!0,transclude:"element",priority:1E3,terminal:!0,$$tlb:!0,compile:function(k,h){var l=h.ngRepeat,m=d.$$createComment("end ngRepeat",l),p=l.match(/^\s*([\s\S]+?)\s+in\s+([\s\S]+?)(?:\s+as\s+([\s\S]+?))?(?:\s+track\s+by\s+([\s\S]+?))?\s*$/);if(!p)throw c("iexp",l);var n=p[1],q=p[2],w=p[3],t=p[4],p=n.match(/^(?:(\s*[$\w]+)|\(\s*([$\w]+)\s*,\s*([$\w]+)\s*\))$/);if(!p)throw c("iidexp",
|
|
||||||
n);var x=p[3]||p[1],v=p[2];if(w&&(!/^[$a-zA-Z_][$a-zA-Z0-9_]*$/.test(w)||/^(null|undefined|this|\$index|\$first|\$middle|\$last|\$even|\$odd|\$parent|\$root|\$id)$/.test(w)))throw c("badident",w);var z;if(t){var A={$id:La},y=a(t);z=function(a,b,c,d){v&&(A[v]=b);A[x]=c;A.$index=d;return y(a,A)}}return function(a,d,h,k,n){var p=T();a.$watchCollection(q,function(h){var k,q,t=d[0],s,y=T(),B,C,E,D,H,F,K;w&&(a[w]=h);if(ya(h))H=h,q=z||f;else for(K in q=z||g,H=[],h)ta.call(h,K)&&"$"!==K.charAt(0)&&H.push(K);
|
|
||||||
B=H.length;K=Array(B);for(k=0;k<B;k++)if(C=h===H?k:H[k],E=h[C],D=q(a,C,E,k),p[D])F=p[D],delete p[D],y[D]=F,K[k]=F;else{if(y[D])throw r(K,function(a){a&&a.scope&&(p[a.id]=a)}),c("dupes",l,D,E);K[k]={id:D,scope:void 0,clone:void 0};y[D]=!0}A&&(A[x]=void 0);for(s in p){F=p[s];D=tb(F.clone);b.leave(D);if(D[0].parentNode)for(k=0,q=D.length;k<q;k++)D[k].$$NG_REMOVED=!0;F.scope.$destroy()}for(k=0;k<B;k++)if(C=h===H?k:H[k],E=h[C],F=K[k],F.scope){s=t;do s=s.nextSibling;while(s&&s.$$NG_REMOVED);F.clone[0]!==
|
|
||||||
s&&b.move(tb(F.clone),null,t);t=F.clone[F.clone.length-1];e(F.scope,k,x,E,v,C,B)}else n(function(a,c){F.scope=c;var d=m.cloneNode(!1);a[a.length++]=d;b.enter(a,null,t);t=d;F.clone=a;y[F.id]=F;e(F.scope,k,x,E,v,C,B)});p=y})}}}}],hf=["$animate",function(a){return{restrict:"A",multiElement:!0,link:function(b,d,c){b.$watch(c.ngShow,function(b){a[b?"removeClass":"addClass"](d,"ng-hide",{tempClasses:"ng-hide-animate"})})}}}],$e=["$animate",function(a){return{restrict:"A",multiElement:!0,link:function(b,
|
|
||||||
d,c){b.$watch(c.ngHide,function(b){a[b?"addClass":"removeClass"](d,"ng-hide",{tempClasses:"ng-hide-animate"})})}}}],jf=Ra(function(a,b,d){a.$watchCollection(d.ngStyle,function(a,d){d&&a!==d&&r(d,function(a,c){b.css(c,"")});a&&b.css(a)})}),kf=["$animate","$compile",function(a,b){return{require:"ngSwitch",controller:["$scope",function(){this.cases={}}],link:function(d,c,e,f){var g=[],k=[],h=[],l=[],m=function(a,b){return function(c){!1!==c&&a.splice(b,1)}};d.$watch(e.ngSwitch||e.on,function(c){for(var d,
|
|
||||||
e;h.length;)a.cancel(h.pop());d=0;for(e=l.length;d<e;++d){var q=tb(k[d].clone);l[d].$destroy();(h[d]=a.leave(q)).done(m(h,d))}k.length=0;l.length=0;(g=f.cases["!"+c]||f.cases["?"])&&r(g,function(c){c.transclude(function(d,e){l.push(e);var f=c.element;d[d.length++]=b.$$createComment("end ngSwitchWhen");k.push({clone:d});a.enter(d,f.parent(),f)})})})}}}],lf=Ra({transclude:"element",priority:1200,require:"^ngSwitch",multiElement:!0,link:function(a,b,d,c,e){a=d.ngSwitchWhen.split(d.ngSwitchWhenSeparator).sort().filter(function(a,
|
|
||||||
b,c){return c[b-1]!==a});r(a,function(a){c.cases["!"+a]=c.cases["!"+a]||[];c.cases["!"+a].push({transclude:e,element:b})})}}),mf=Ra({transclude:"element",priority:1200,require:"^ngSwitch",multiElement:!0,link:function(a,b,d,c,e){c.cases["?"]=c.cases["?"]||[];c.cases["?"].push({transclude:e,element:b})}}),zh=F("ngTransclude"),of=["$compile",function(a){return{restrict:"EAC",compile:function(b){var d=a(b.contents());b.empty();return function(a,b,f,g,k){function h(){d(a,function(a){b.append(a)})}if(!k)throw zh("orphan",
|
|
||||||
za(b));f.ngTransclude===f.$attr.ngTransclude&&(f.ngTransclude="");f=f.ngTransclude||f.ngTranscludeSlot;k(function(a,c){var d;if(d=a.length)a:{d=0;for(var f=a.length;d<f;d++){var g=a[d];if(g.nodeType!==Pa||g.nodeValue.trim()){d=!0;break a}}d=void 0}d?b.append(a):(h(),c.$destroy())},null,f);f&&!k.isSlotFilled(f)&&h()}}}}],Oe=["$templateCache",function(a){return{restrict:"E",terminal:!0,compile:function(b,d){"text/ng-template"===d.type&&a.put(d.id,b[0].text)}}}],Ah={$setViewValue:E,$render:E},Bh=["$element",
|
|
||||||
"$scope",function(a,b){function d(){g||(g=!0,b.$$postDigest(function(){g=!1;e.ngModelCtrl.$render()}))}function c(a){k||(k=!0,b.$$postDigest(function(){b.$$destroyed||(k=!1,e.ngModelCtrl.$setViewValue(e.readValue()),a&&e.ngModelCtrl.$render())}))}var e=this,f=new Hb;e.selectValueMap={};e.ngModelCtrl=Ah;e.multiple=!1;e.unknownOption=x(C.document.createElement("option"));e.hasEmptyOption=!1;e.emptyOption=void 0;e.renderUnknownOption=function(b){b=e.generateUnknownOptionValue(b);e.unknownOption.val(b);
|
|
||||||
a.prepend(e.unknownOption);Oa(e.unknownOption,!0);a.val(b)};e.updateUnknownOption=function(b){b=e.generateUnknownOptionValue(b);e.unknownOption.val(b);Oa(e.unknownOption,!0);a.val(b)};e.generateUnknownOptionValue=function(a){return"? "+La(a)+" ?"};e.removeUnknownOption=function(){e.unknownOption.parent()&&e.unknownOption.remove()};e.selectEmptyOption=function(){e.emptyOption&&(a.val(""),Oa(e.emptyOption,!0))};e.unselectEmptyOption=function(){e.hasEmptyOption&&Oa(e.emptyOption,!1)};b.$on("$destroy",
|
|
||||||
function(){e.renderUnknownOption=E});e.readValue=function(){var b=a.val(),b=b in e.selectValueMap?e.selectValueMap[b]:b;return e.hasOption(b)?b:null};e.writeValue=function(b){var c=a[0].options[a[0].selectedIndex];c&&Oa(x(c),!1);e.hasOption(b)?(e.removeUnknownOption(),c=La(b),a.val(c in e.selectValueMap?c:b),Oa(x(a[0].options[a[0].selectedIndex]),!0)):e.selectUnknownOrEmptyOption(b)};e.addOption=function(a,b){if(8!==b[0].nodeType){Ja(a,'"option value"');""===a&&(e.hasEmptyOption=!0,e.emptyOption=
|
|
||||||
b);var c=f.get(a)||0;f.set(a,c+1);d()}};e.removeOption=function(a){var b=f.get(a);b&&(1===b?(f.delete(a),""===a&&(e.hasEmptyOption=!1,e.emptyOption=void 0)):f.set(a,b-1))};e.hasOption=function(a){return!!f.get(a)};e.$hasEmptyOption=function(){return e.hasEmptyOption};e.$isUnknownOptionSelected=function(){return a[0].options[0]===e.unknownOption[0]};e.$isEmptyOptionSelected=function(){return e.hasEmptyOption&&a[0].options[a[0].selectedIndex]===e.emptyOption[0]};e.selectUnknownOrEmptyOption=function(a){null==
|
|
||||||
a&&e.emptyOption?(e.removeUnknownOption(),e.selectEmptyOption()):e.unknownOption.parent().length?e.updateUnknownOption(a):e.renderUnknownOption(a)};var g=!1,k=!1;e.registerOption=function(a,b,f,g,k){if(f.$attr.ngValue){var q,r;f.$observe("value",function(a){var d,f=b.prop("selected");w(r)&&(e.removeOption(q),delete e.selectValueMap[r],d=!0);r=La(a);q=a;e.selectValueMap[r]=a;e.addOption(a,b);b.attr("value",r);d&&f&&c()})}else g?f.$observe("value",function(a){e.readValue();var d,f=b.prop("selected");
|
|
||||||
w(q)&&(e.removeOption(q),d=!0);q=a;e.addOption(a,b);d&&f&&c()}):k?a.$watch(k,function(a,d){f.$set("value",a);var g=b.prop("selected");d!==a&&e.removeOption(d);e.addOption(a,b);d&&g&&c()}):e.addOption(f.value,b);f.$observe("disabled",function(a){if("true"===a||a&&b.prop("selected"))e.multiple?c(!0):(e.ngModelCtrl.$setViewValue(null),e.ngModelCtrl.$render())});b.on("$destroy",function(){var a=e.readValue(),b=f.value;e.removeOption(b);d();(e.multiple&&a&&-1!==a.indexOf(b)||a===b)&&c(!0)})}}],Pe=function(){return{restrict:"E",
|
|
||||||
require:["select","?ngModel"],controller:Bh,priority:1,link:{pre:function(a,b,d,c){var e=c[0],f=c[1];if(f){if(e.ngModelCtrl=f,b.on("change",function(){e.removeUnknownOption();a.$apply(function(){f.$setViewValue(e.readValue())})}),d.multiple){e.multiple=!0;e.readValue=function(){var a=[];r(b.find("option"),function(b){b.selected&&!b.disabled&&(b=b.value,a.push(b in e.selectValueMap?e.selectValueMap[b]:b))});return a};e.writeValue=function(a){r(b.find("option"),function(b){var c=!!a&&(-1!==Array.prototype.indexOf.call(a,
|
|
||||||
b.value)||-1!==Array.prototype.indexOf.call(a,e.selectValueMap[b.value]));c!==b.selected&&Oa(x(b),c)})};var g,k=NaN;a.$watch(function(){k!==f.$viewValue||va(g,f.$viewValue)||(g=ja(f.$viewValue),f.$render());k=f.$viewValue});f.$isEmpty=function(a){return!a||0===a.length}}}else e.registerOption=E},post:function(a,b,d,c){var e=c[1];if(e){var f=c[0];e.$render=function(){f.writeValue(e.$viewValue)}}}}}},Qe=["$interpolate",function(a){return{restrict:"E",priority:100,compile:function(b,d){var c,e;w(d.ngValue)||
|
|
||||||
(w(d.value)?c=a(d.value,!0):(e=a(b.text(),!0))||d.$set("value",b.text()));return function(a,b,d){var h=b.parent();(h=h.data("$selectController")||h.parent().data("$selectController"))&&h.registerOption(a,b,d,c,e)}}}}],$c=["$parse",function(a){return{restrict:"A",require:"?ngModel",link:function(b,d,c,e){if(e){var f=c.hasOwnProperty("required")||a(c.ngRequired)(b);c.ngRequired||(c.required=!0);e.$validators.required=function(a,b){return!f||!e.$isEmpty(b)};c.$observe("required",function(a){f!==a&&(f=
|
|
||||||
a,e.$validate())})}}}}],Zc=["$parse",function(a){return{restrict:"A",require:"?ngModel",compile:function(b,d){var c,e;d.ngPattern&&(c=d.ngPattern,e="/"===d.ngPattern.charAt(0)&&ie.test(d.ngPattern)?function(){return d.ngPattern}:a(d.ngPattern));return function(a,b,d,h){if(h){var l=d.pattern;d.ngPattern?l=e(a):c=d.pattern;var m=he(l,c,b);d.$observe("pattern",function(a){var d=m;m=he(a,c,b);(d&&d.toString())!==(m&&m.toString())&&h.$validate()});h.$validators.pattern=function(a,b){return h.$isEmpty(b)||
|
|
||||||
z(m)||m.test(b)}}}}}}],bd=["$parse",function(a){return{restrict:"A",require:"?ngModel",link:function(b,d,c,e){if(e){var f=c.maxlength||a(c.ngMaxlength)(b),g=Tb(f);c.$observe("maxlength",function(a){f!==a&&(g=Tb(a),f=a,e.$validate())});e.$validators.maxlength=function(a,b){return 0>g||e.$isEmpty(b)||b.length<=g}}}}}],ad=["$parse",function(a){return{restrict:"A",require:"?ngModel",link:function(b,d,c,e){if(e){var f=c.minlength||a(c.ngMinlength)(b),g=Tb(f)||-1;c.$observe("minlength",function(a){f!==
|
|
||||||
a&&(g=Tb(a)||-1,f=a,e.$validate())});e.$validators.minlength=function(a,b){return e.$isEmpty(b)||b.length>=g}}}}}];C.angular.bootstrap?C.console&&console.log("WARNING: Tried to load AngularJS more than once."):(Fe(),Je(ca),ca.module("ngLocale",[],["$provide",function(a){function b(a){a+="";var b=a.indexOf(".");return-1==b?0:a.length-b-1}a.value("$locale",{DATETIME_FORMATS:{AMPMS:["AM","PM"],DAY:"Sunday Monday Tuesday Wednesday Thursday Friday Saturday".split(" "),ERANAMES:["Before Christ","Anno Domini"],
|
|
||||||
ERAS:["BC","AD"],FIRSTDAYOFWEEK:6,MONTH:"January February March April May June July August September October November December".split(" "),SHORTDAY:"Sun Mon Tue Wed Thu Fri Sat".split(" "),SHORTMONTH:"Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec".split(" "),STANDALONEMONTH:"January February March April May June July August September October November December".split(" "),WEEKENDRANGE:[5,6],fullDate:"EEEE, MMMM d, y",longDate:"MMMM d, y",medium:"MMM d, y h:mm:ss a",mediumDate:"MMM d, y",mediumTime:"h:mm:ss a",
|
|
||||||
"short":"M/d/yy h:mm a",shortDate:"M/d/yy",shortTime:"h:mm a"},NUMBER_FORMATS:{CURRENCY_SYM:"$",DECIMAL_SEP:".",GROUP_SEP:",",PATTERNS:[{gSize:3,lgSize:3,maxFrac:3,minFrac:0,minInt:1,negPre:"-",negSuf:"",posPre:"",posSuf:""},{gSize:3,lgSize:3,maxFrac:2,minFrac:2,minInt:1,negPre:"-\u00a4",negSuf:"",posPre:"\u00a4",posSuf:""}]},id:"en-us",localeID:"en_US",pluralCat:function(a,c){var e=a|0,f=c;void 0===f&&(f=Math.min(b(a),3));Math.pow(10,f);return 1==e&&0==f?"one":"other"}})}]),x(function(){Ae(C.document,
|
|
||||||
Uc)}))})(window);!window.angular.$$csp().noInlineStyle&&window.angular.element(document.head).prepend('<style type="text/css">@charset "UTF-8";[ng\\:cloak],[ng-cloak],[data-ng-cloak],[x-ng-cloak],.ng-cloak,.x-ng-cloak,.ng-hide:not(.ng-hide-animate){display:none !important;}ng\\:form{display:block;}.ng-animate-shim{visibility:hidden;}.ng-anchor{position:absolute;}</style>');
|
|
||||||
//# sourceMappingURL=angular.min.js.map
|
|
||||||
File diff suppressed because it is too large
Load diff
7
admin/static/js/bootstrap.min.js
vendored
7
admin/static/js/bootstrap.min.js
vendored
File diff suppressed because one or more lines are too long
|
|
@ -1,355 +0,0 @@
|
||||||
var alertNbLines = true;
|
|
||||||
|
|
||||||
function treatFlagKey(flag) {
|
|
||||||
if (flag.values !== undefined) {
|
|
||||||
if (flag.separator) {
|
|
||||||
for (var i = flag.values.length - 1; i >= 0; i--) {
|
|
||||||
if (flag.nb_lines && (flag.values[i] == undefined || !flag.values[i].length)) {
|
|
||||||
if (alertNbLines) {
|
|
||||||
alertNbLines = false;
|
|
||||||
if (!confirm("Lorsque plusieurs flags sont attendus pour une même question, ceux-ci ne sont pas validés un par un. Ils ne sont validés qu'une fois tous les champs remplis correctement. (Sauf mention contraire, l'ordre n'importe pas)"))
|
|
||||||
console.log(flag.values[9999].length); // Launch exception here to avoid form validation
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (!flag.values[i].length) {
|
|
||||||
flag.values.splice(i, 1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (flag.ignore_order)
|
|
||||||
flag.value = flag.values.slice().sort().join(flag.separator) + flag.separator;
|
|
||||||
else
|
|
||||||
flag.value = flag.values.join(flag.separator) + flag.separator;
|
|
||||||
|
|
||||||
if (flag.values.length == 0)
|
|
||||||
flag.values = [""];
|
|
||||||
}
|
|
||||||
else
|
|
||||||
flag.value = flag.values[0];
|
|
||||||
}
|
|
||||||
|
|
||||||
if (flag.found == null && flag.soluce !== undefined) {
|
|
||||||
if (flag.value && flag.soluce) {
|
|
||||||
if (flag.ignore_case)
|
|
||||||
flag.value = flag.value.toLowerCase();
|
|
||||||
if (flag.capture_regexp) {
|
|
||||||
var re = new RegExp(flag.capture_regexp, flag.ignore_case?'ui':'u');
|
|
||||||
var match = re.exec(flag.value);
|
|
||||||
match.shift();
|
|
||||||
flag.value = match.join("+");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (flag.soluce == b2sum(flag.value))
|
|
||||||
flag.found = new Date();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return flag.found !== undefined && flag.found !== false;
|
|
||||||
}
|
|
||||||
|
|
||||||
String.prototype.capitalize = function() {
|
|
||||||
return this
|
|
||||||
.toLowerCase()
|
|
||||||
.replace(
|
|
||||||
/(^|\s|-)([a-z])/g,
|
|
||||||
function(m,p1,p2) { return p1+p2.toUpperCase(); }
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
Array.prototype.inArray = function(v) {
|
|
||||||
return this.reduce(function(presence, current) {
|
|
||||||
return presence || current == v;
|
|
||||||
}, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
angular.module("FICApp")
|
|
||||||
.directive('autofocus', ['$timeout', function($timeout) {
|
|
||||||
return {
|
|
||||||
restrict: 'A',
|
|
||||||
link : function($scope, $element) {
|
|
||||||
$timeout(function() {
|
|
||||||
$element[0].focus();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}])
|
|
||||||
.directive('autocarousel', ['$timeout', function($timeout) {
|
|
||||||
return {
|
|
||||||
restrict: 'A',
|
|
||||||
link : function($scope, $element) {
|
|
||||||
$timeout(function() {
|
|
||||||
$($element[0]).carousel();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}])
|
|
||||||
.directive('fileModel', ['$parse', function ($parse) {
|
|
||||||
return {
|
|
||||||
restrict: 'A',
|
|
||||||
link: function($scope, element, attrs) {
|
|
||||||
var model = $parse(attrs.fileModel);
|
|
||||||
var modelSetter = model.assign;
|
|
||||||
|
|
||||||
element.bind('change', function(){
|
|
||||||
$scope.$apply(function(){
|
|
||||||
modelSetter($scope, element[0].files[0]);
|
|
||||||
$scope.uploadFile();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}]);
|
|
||||||
|
|
||||||
angular.module("FICApp")
|
|
||||||
.filter("escapeURL", function() {
|
|
||||||
return function(input) {
|
|
||||||
return encodeURIComponent(input);
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.filter("stripHTML", function() {
|
|
||||||
return function(input) {
|
|
||||||
if (!input)
|
|
||||||
return input;
|
|
||||||
return input.replace(
|
|
||||||
/(<([^>]+)>)/ig,
|
|
||||||
""
|
|
||||||
);
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.filter("capitalize", function() {
|
|
||||||
return function(input) {
|
|
||||||
return input.capitalize();
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.filter("rankTitle", function() {
|
|
||||||
var itms = {
|
|
||||||
"rank": "Rang",
|
|
||||||
"name": "Équipe",
|
|
||||||
"score": "Score",
|
|
||||||
};
|
|
||||||
return function(input) {
|
|
||||||
if (itms[input] != undefined) {
|
|
||||||
return itms[input];
|
|
||||||
} else {
|
|
||||||
return input;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.filter("time", function() {
|
|
||||||
return function(input) {
|
|
||||||
input = Math.floor(input);
|
|
||||||
if (input == undefined) {
|
|
||||||
return "--";
|
|
||||||
} else if (input >= 10) {
|
|
||||||
return input;
|
|
||||||
} else {
|
|
||||||
return "0" + input;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.filter("timer", function() {
|
|
||||||
return function(input) {
|
|
||||||
input = Math.floor(input / 1000);
|
|
||||||
var res = ""
|
|
||||||
|
|
||||||
if (input >= 3600) {
|
|
||||||
res += Math.floor(input / 3600) + ":";
|
|
||||||
input = input % 3600;
|
|
||||||
}
|
|
||||||
if (res || input >= 60) {
|
|
||||||
if (res && Math.floor(input / 60) <= 9)
|
|
||||||
res += "0";
|
|
||||||
res += Math.floor(input / 60) + "'";
|
|
||||||
input = input % 60;
|
|
||||||
}
|
|
||||||
|
|
||||||
return res + (input>9?input:"0"+input) + '"';
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.filter("since", function() {
|
|
||||||
return function(passed) {
|
|
||||||
if (passed < 120000) {
|
|
||||||
return "Il y a " + Math.floor(passed/1000) + " secondes";
|
|
||||||
} else {
|
|
||||||
return "Il y a " + Math.floor(passed/60000) + " minutes";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.filter("size", function() {
|
|
||||||
var units = [
|
|
||||||
"o",
|
|
||||||
"kio",
|
|
||||||
"Mio",
|
|
||||||
"Gio",
|
|
||||||
"Tio",
|
|
||||||
"Pio",
|
|
||||||
"Eio",
|
|
||||||
"Zio",
|
|
||||||
"Yio",
|
|
||||||
]
|
|
||||||
return function(input) {
|
|
||||||
var res = input;
|
|
||||||
var unit = 0;
|
|
||||||
while (res > 1024) {
|
|
||||||
unit += 1;
|
|
||||||
res = res / 1024;
|
|
||||||
}
|
|
||||||
return (Math.round(res * 100) / 100) + " " + units[unit];
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
.filter("coeff", function() {
|
|
||||||
return function(input) {
|
|
||||||
if (input > 1) {
|
|
||||||
return "+" + Math.floor((input - 1) * 100) + " %"
|
|
||||||
} else if (input < 1) {
|
|
||||||
return "-" + Math.floor((1 - input) * 100) + " %"
|
|
||||||
} else {
|
|
||||||
return "";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
.filter("objectLength", function() {
|
|
||||||
return function(input) {
|
|
||||||
if (input !== undefined)
|
|
||||||
return Object.keys(input).length;
|
|
||||||
else
|
|
||||||
return "";
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
.filter("bto16", function() {
|
|
||||||
return function(input) {
|
|
||||||
const raw = atob(input);
|
|
||||||
let result = '';
|
|
||||||
for (let i = 0; i < raw.length; i++) {
|
|
||||||
const hex = raw.charCodeAt(i).toString(16);
|
|
||||||
result += (hex.length === 2 ? hex : '0' + hex);
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
angular.module("FICApp")
|
|
||||||
.component('flagKey', {
|
|
||||||
bindings: {
|
|
||||||
kid: '=',
|
|
||||||
key: '=',
|
|
||||||
settings: '=',
|
|
||||||
wantchoices: '=',
|
|
||||||
},
|
|
||||||
controller: function() {
|
|
||||||
this.additem = function(key) {
|
|
||||||
this.key.values.push("");
|
|
||||||
};
|
|
||||||
},
|
|
||||||
template: `
|
|
||||||
<div class="form-group">
|
|
||||||
<label for="sol_{{ $ctrl.kid }}_0" ng-class="{'text-light': !$ctrl.key.found}">{{ $ctrl.key.label }} :</label>
|
|
||||||
<span ng-if="$ctrl.key.found && $ctrl.key.value" ng-bind="$ctrl.key.value"></span>
|
|
||||||
<div class="input-group" ng-repeat="v in $ctrl.key.values track by $index" ng-class="{'mt-1': !$first}" ng-if="!$ctrl.key.found">
|
|
||||||
<input type="text" class="form-control flag" id="sol_{{ $ctrl.kid }}_{{ $index }}" autocomplete="off" name="sol_{{ $ctrl.kid }}_{{ $index }}" ng-model="$ctrl.key.values[$index]" ng-if="!$ctrl.key.choices && !$ctrl.key.multiline" placeholder="{{ $ctrl.key.placeholder }}" title="{{ $ctrl.key.placeholder }}">
|
|
||||||
<textarea class="form-control flag" id="sol_{{ $ctrl.kid }}_{{ $index }}" autocomplete="off" name="sol_{{ $ctrl.kid }}_{{ $index }}" ng-model="$ctrl.key.values[$index]" ng-if="!$ctrl.key.choices && $ctrl.key.multiline" placeholder="{{ $ctrl.key.placeholder }}" title="{{ $ctrl.key.placeholder }}"></textarea>
|
|
||||||
<select class="custom-select" id="sol_{{ $ctrl.kid }}" name="sol_{{ $ctrl.kid }}" ng-model="$ctrl.key.values[$index]" ng-if="$ctrl.key.choices" ng-options="l as v for (l, v) in $ctrl.key.choices"></select>
|
|
||||||
<div class="input-group-append" ng-if="$ctrl.key.choices_cost">
|
|
||||||
<button class="btn btn-success" type="button" ng-click="$ctrl.wantchoices($ctrl.kid)" ng-class="{disabled: $ctrl.key.wcsubmitted}" title="Cliquez pour échanger ce champ de texte par une liste de choix. L'opération vous coûtera {{ $ctrl.key.choices_cost * $ctrl.settings.wchoiceCurrentCoefficient }} points.">
|
|
||||||
<span class="glyphicon glyphicon-tasks" aria-hidden="true"></span>
|
|
||||||
Liste de propositions (<ng-pluralize count="$ctrl.key.choices_cost * $ctrl.settings.wchoiceCurrentCoefficient" when="{'one': '{} point', 'other': '{} points'}"></ng-pluralize>)
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
<div class="input-group-append" ng-if="$ctrl.key.separator && !$ctrl.key.nb_lines && $last">
|
|
||||||
<button class="btn btn-success" type="button" ng-click="$ctrl.additem(key)" title="Ajouter un élément.">
|
|
||||||
<span class="glyphicon glyphicon-plus" aria-hidden="true"></span>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<small class="form-text text-muted" ng-if="!$ctrl.key.found && $ctrl.key.help.length > 0" ng-bind-html="$ctrl.key.help"></small>
|
|
||||||
<span class="glyphicon glyphicon-ok form-control-feedback text-success" aria-hidden="true" ng-if="$ctrl.key.found" title="Flag trouvé à {{ $ctrl.key.found | date:'mediumTime'}}"></span>
|
|
||||||
</div>
|
|
||||||
`
|
|
||||||
});
|
|
||||||
|
|
||||||
angular.module("FICApp")
|
|
||||||
.run(function($rootScope) {
|
|
||||||
$rootScope.recvTime = function(response) {
|
|
||||||
time = {
|
|
||||||
"cu": Math.floor(response.headers("x-fic-time") * 1000),
|
|
||||||
"he": (new Date()).getTime(),
|
|
||||||
};
|
|
||||||
sessionStorage.time = angular.toJson(time);
|
|
||||||
return time;
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
.controller("CountdownController", function($scope, $rootScope, $interval) {
|
|
||||||
var time;
|
|
||||||
if (sessionStorage.time)
|
|
||||||
time = angular.fromJson(sessionStorage.time);
|
|
||||||
|
|
||||||
$scope.time = {};
|
|
||||||
|
|
||||||
$rootScope.getSrvTime = function() {
|
|
||||||
if (time && time.cu && time.he)
|
|
||||||
return new Date(Date.now() + (time.cu - time.he));
|
|
||||||
else
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
function updTime() {
|
|
||||||
if (time && $rootScope.settings && $rootScope.settings.end) {
|
|
||||||
var srv_cur = new Date(Date.now() + (time.cu - time.he));
|
|
||||||
|
|
||||||
// Refresh on start/activate time reached
|
|
||||||
if (Math.floor($rootScope.settings.start / 1000) == Math.floor(srv_cur / 1000) ||Math.floor($rootScope.settings.activateTime / 1000) == Math.floor(srv_cur / 1000))
|
|
||||||
$rootScope.refresh(true, true);
|
|
||||||
|
|
||||||
var remain = 0;
|
|
||||||
if ($rootScope.settings.start === undefined || $rootScope.settings.start == 0) {
|
|
||||||
$scope.time = {};
|
|
||||||
return
|
|
||||||
} else if ($rootScope.settings.start > srv_cur) {
|
|
||||||
$scope.startIn = Math.floor(($rootScope.settings.start - srv_cur) / 1000);
|
|
||||||
remain = $rootScope.settings.end - $rootScope.settings.start;
|
|
||||||
} else if ($rootScope.settings.end > srv_cur) {
|
|
||||||
$scope.startIn = 0;
|
|
||||||
remain = $rootScope.settings.end - srv_cur;
|
|
||||||
}
|
|
||||||
|
|
||||||
$rootScope.timeProgression = 1 - remain / ($rootScope.settings.end - $rootScope.settings.start);
|
|
||||||
$rootScope.timeRemaining = remain;
|
|
||||||
|
|
||||||
if ($rootScope.settings.activateTime) {
|
|
||||||
var now = new Date();
|
|
||||||
var actTime = new Date($rootScope.settings.activateTime);
|
|
||||||
|
|
||||||
if (actTime > now)
|
|
||||||
$rootScope.activateTimeCountDown = actTime - now;
|
|
||||||
else
|
|
||||||
$rootScope.activateTimeCountDown = null;
|
|
||||||
} else {
|
|
||||||
$rootScope.activateTimeCountDown = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
remain = remain / 1000;
|
|
||||||
|
|
||||||
if (remain < 0) {
|
|
||||||
remain = 0;
|
|
||||||
$scope.time.end = true;
|
|
||||||
$scope.time.expired = true;
|
|
||||||
} else if (remain < 60) {
|
|
||||||
$scope.time.end = false;
|
|
||||||
$scope.time.expired = true;
|
|
||||||
} else {
|
|
||||||
$scope.time.end = false;
|
|
||||||
$scope.time.expired = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
$scope.time.remaining = remain;
|
|
||||||
$scope.time.hours = Math.floor(remain / 3600);
|
|
||||||
$scope.time.minutes = Math.floor((remain % 3600) / 60);
|
|
||||||
$scope.time.seconds = Math.floor(remain % 60);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
updTime();
|
|
||||||
$interval(updTime, 1000);
|
|
||||||
})
|
|
||||||
5
admin/static/js/d3.v3.min.js
vendored
5
admin/static/js/d3.v3.min.js
vendored
File diff suppressed because one or more lines are too long
125
admin/static/js/i18n/angular-locale_fr-fr.js
vendored
125
admin/static/js/i18n/angular-locale_fr-fr.js
vendored
|
|
@ -1,125 +0,0 @@
|
||||||
'use strict';
|
|
||||||
angular.module("ngLocale", [], ["$provide", function($provide) {
|
|
||||||
var PLURAL_CATEGORY = {ZERO: "zero", ONE: "one", TWO: "two", FEW: "few", MANY: "many", OTHER: "other"};
|
|
||||||
$provide.value("$locale", {
|
|
||||||
"DATETIME_FORMATS": {
|
|
||||||
"AMPMS": [
|
|
||||||
"AM",
|
|
||||||
"PM"
|
|
||||||
],
|
|
||||||
"DAY": [
|
|
||||||
"dimanche",
|
|
||||||
"lundi",
|
|
||||||
"mardi",
|
|
||||||
"mercredi",
|
|
||||||
"jeudi",
|
|
||||||
"vendredi",
|
|
||||||
"samedi"
|
|
||||||
],
|
|
||||||
"ERANAMES": [
|
|
||||||
"avant J\u00e9sus-Christ",
|
|
||||||
"apr\u00e8s J\u00e9sus-Christ"
|
|
||||||
],
|
|
||||||
"ERAS": [
|
|
||||||
"av. J.-C.",
|
|
||||||
"ap. J.-C."
|
|
||||||
],
|
|
||||||
"FIRSTDAYOFWEEK": 0,
|
|
||||||
"MONTH": [
|
|
||||||
"janvier",
|
|
||||||
"f\u00e9vrier",
|
|
||||||
"mars",
|
|
||||||
"avril",
|
|
||||||
"mai",
|
|
||||||
"juin",
|
|
||||||
"juillet",
|
|
||||||
"ao\u00fbt",
|
|
||||||
"septembre",
|
|
||||||
"octobre",
|
|
||||||
"novembre",
|
|
||||||
"d\u00e9cembre"
|
|
||||||
],
|
|
||||||
"SHORTDAY": [
|
|
||||||
"dim.",
|
|
||||||
"lun.",
|
|
||||||
"mar.",
|
|
||||||
"mer.",
|
|
||||||
"jeu.",
|
|
||||||
"ven.",
|
|
||||||
"sam."
|
|
||||||
],
|
|
||||||
"SHORTMONTH": [
|
|
||||||
"janv.",
|
|
||||||
"f\u00e9vr.",
|
|
||||||
"mars",
|
|
||||||
"avr.",
|
|
||||||
"mai",
|
|
||||||
"juin",
|
|
||||||
"juil.",
|
|
||||||
"ao\u00fbt",
|
|
||||||
"sept.",
|
|
||||||
"oct.",
|
|
||||||
"nov.",
|
|
||||||
"d\u00e9c."
|
|
||||||
],
|
|
||||||
"STANDALONEMONTH": [
|
|
||||||
"janvier",
|
|
||||||
"f\u00e9vrier",
|
|
||||||
"mars",
|
|
||||||
"avril",
|
|
||||||
"mai",
|
|
||||||
"juin",
|
|
||||||
"juillet",
|
|
||||||
"ao\u00fbt",
|
|
||||||
"septembre",
|
|
||||||
"octobre",
|
|
||||||
"novembre",
|
|
||||||
"d\u00e9cembre"
|
|
||||||
],
|
|
||||||
"WEEKENDRANGE": [
|
|
||||||
5,
|
|
||||||
6
|
|
||||||
],
|
|
||||||
"fullDate": "EEEE d MMMM y",
|
|
||||||
"longDate": "d MMMM y",
|
|
||||||
"medium": "d MMM y HH:mm:ss",
|
|
||||||
"mediumDate": "d MMM y",
|
|
||||||
"mediumTime": "HH:mm:ss",
|
|
||||||
"short": "dd/MM/y HH:mm",
|
|
||||||
"shortDate": "dd/MM/y",
|
|
||||||
"shortTime": "HH:mm"
|
|
||||||
},
|
|
||||||
"NUMBER_FORMATS": {
|
|
||||||
"CURRENCY_SYM": "\u20ac",
|
|
||||||
"DECIMAL_SEP": ",",
|
|
||||||
"GROUP_SEP": "\u00a0",
|
|
||||||
"PATTERNS": [
|
|
||||||
{
|
|
||||||
"gSize": 3,
|
|
||||||
"lgSize": 3,
|
|
||||||
"maxFrac": 3,
|
|
||||||
"minFrac": 0,
|
|
||||||
"minInt": 1,
|
|
||||||
"negPre": "-",
|
|
||||||
"negSuf": "",
|
|
||||||
"posPre": "",
|
|
||||||
"posSuf": ""
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"gSize": 3,
|
|
||||||
"lgSize": 3,
|
|
||||||
"maxFrac": 2,
|
|
||||||
"minFrac": 2,
|
|
||||||
"minInt": 1,
|
|
||||||
"negPre": "-",
|
|
||||||
"negSuf": "\u00a0\u00a4",
|
|
||||||
"posPre": "",
|
|
||||||
"posSuf": "\u00a0\u00a4"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"id": "fr-fr",
|
|
||||||
"localeID": "fr_FR",
|
|
||||||
"pluralCat": function(n, opt_precision) { var i = n | 0; if (i == 0 || i == 1) { return PLURAL_CATEGORY.ONE; } return PLURAL_CATEGORY.OTHER;}
|
|
||||||
});
|
|
||||||
}]);
|
|
||||||
2
admin/static/js/jquery.min.js
vendored
2
admin/static/js/jquery.min.js
vendored
File diff suppressed because one or more lines are too long
5
admin/static/js/popper.min.js
vendored
5
admin/static/js/popper.min.js
vendored
File diff suppressed because one or more lines are too long
|
|
@ -1,86 +0,0 @@
|
||||||
<div class="d-flex justify-content-between align-items-center">
|
|
||||||
<h2>
|
|
||||||
Authentification
|
|
||||||
</h2>
|
|
||||||
<div>
|
|
||||||
<div class="btn-group mr-1" role="group">
|
|
||||||
<button type="button" ng-click="generateHtpasswd()" class="btn btn-sm btn-secondary"><span class="glyphicon glyphicon-save-file" aria-hidden="true"></span> Générer <code>fichtpasswd</code></button>
|
|
||||||
<button type="button" ng-click="removeHtpasswd()" class="btn btn-sm btn-danger"><span class="glyphicon glyphicon-trash" aria-hidden="true"></span></button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div ng-controller="OAuthController">
|
|
||||||
<div class="d-flex justify-content-between align-items-center">
|
|
||||||
<h3>
|
|
||||||
OAuth 2
|
|
||||||
<span class="badge badge-success" ng-if="oauth_status.secret_defined">Actif</span>
|
|
||||||
<span class="badge badge-danger" ng-if="!oauth_status.secret_defined">Non configuré</span>
|
|
||||||
</h3>
|
|
||||||
<div>
|
|
||||||
<button type="button" ng-click="genDexCfg()" class="btn btn-success mr-2"><span class="glyphicon glyphicon-refresh" aria-hidden="true"></span> DexIdP</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<hr>
|
|
||||||
|
|
||||||
<div ng-controller="PKIController">
|
|
||||||
<div class="d-flex justify-content-between align-items-center">
|
|
||||||
<h3>
|
|
||||||
Autorité de certification
|
|
||||||
<span class="badge badge-success" ng-if="ca.version">Générée</span>
|
|
||||||
<span class="badge badge-danger" ng-if="!ca.version">Introuvable</span>
|
|
||||||
</h3>
|
|
||||||
<div>
|
|
||||||
<a
|
|
||||||
class="btn btn-primary"
|
|
||||||
href="/pki"
|
|
||||||
>
|
|
||||||
<span class="glyphicon glyphicon-certificate" aria-hidden="true"></span>
|
|
||||||
Gérer la PKI
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="alert alert-info" ng-if="!ca.version">
|
|
||||||
<strong>Aucune CA n'a été générée pour le moment.</strong>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<dl ng-if="ca.version">
|
|
||||||
<ng-repeat ng-repeat="(k, v) in ca" class="row">
|
|
||||||
<dt class="col-3 text-right">{{ k }}</dt>
|
|
||||||
<dd class="col-9" ng-if="v.CommonName">/CN={{ v.CommonName }}/OU={{ v.OrganizationalUnit }}/O={{ v.Organization }}/L={{ v.Locality }}/P={{ v.Province }}/C={{ v.Country }}/</dd>
|
|
||||||
<dd class="col-9" ng-if="!v.CommonName">{{ v }}</dd>
|
|
||||||
</ng-repeat>
|
|
||||||
</dl>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<hr>
|
|
||||||
|
|
||||||
<div class="mb-4" ng-controller="AllTeamAssociationsController">
|
|
||||||
<div class="d-flex justify-content-between align-items-center">
|
|
||||||
<h3>
|
|
||||||
Association utilisateurs et équipes
|
|
||||||
</h3>
|
|
||||||
<div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<table class="table table-sm table-hover" ng-controller="TeamsListController" >
|
|
||||||
<tr>
|
|
||||||
<th class="text-right">Utilisateur</th>
|
|
||||||
<th></th>
|
|
||||||
<th>Équipe</th>
|
|
||||||
</tr>
|
|
||||||
<tr ng-repeat="association in allAssociations">
|
|
||||||
<td class="text-right">{{ association.association }}</td>
|
|
||||||
<td class="text-center">⬌</td>
|
|
||||||
<td ng-repeat="team in teams" ng-if="team.id == association.team_id">
|
|
||||||
<a ng-href="teams/{{ team.id }}">
|
|
||||||
{{ team.name }}
|
|
||||||
</a>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
|
|
@ -1,87 +0,0 @@
|
||||||
<h2>
|
|
||||||
Tâches et réclammations ({{ claims.length }})
|
|
||||||
<button type="button" ng-click="show('new')" class="float-right btn btn-sm btn-primary"><span class="glyphicon glyphicon-plus" aria-hidden="true"></span> Ajouter une tâche</button>
|
|
||||||
<small style="height: 0px;">
|
|
||||||
<div class="btn-group btn-group-toggle float-right mr-2">
|
|
||||||
<label class="btn btn-sm btn-secondary" ng-class="{active: showOnlyNew, 'btn-warning': showOnlyNew}">
|
|
||||||
<input type="checkbox" ng-model="showOnlyNew"> Nouvelles
|
|
||||||
</label>
|
|
||||||
<label class="btn btn-sm btn-secondary" ng-class="{active: showOnlyUnassigned, 'btn-warning': showOnlyUnassigned}">
|
|
||||||
<input type="checkbox" ng-model="showOnlyUnassigned"> Non assignée
|
|
||||||
</label>
|
|
||||||
<label class="btn btn-sm btn-secondary" ng-class="{active: showOnlyMines, 'btn-warning': showOnlyMines}" ng-show="whoami">
|
|
||||||
<input type="checkbox" ng-model="showOnlyMines"> Que mes tâches
|
|
||||||
</label>
|
|
||||||
<label class="btn btn-sm btn-secondary" ng-class="{active: showClosed, 'btn-warning': showClosed}">
|
|
||||||
<input type="checkbox" ng-model="showClosed"> Tâches closes
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
</small>
|
|
||||||
</h2>
|
|
||||||
|
|
||||||
<p><input type="search" class="form-control form-control-sm" placeholder="Search" ng-model="query" autofocus></p>
|
|
||||||
<table class="table table-hover table-bordered table-striped table-sm">
|
|
||||||
<thead class="thead-dark">
|
|
||||||
<tr>
|
|
||||||
<th ng-repeat="field in fields" ng-click="chOrder(field)">
|
|
||||||
{{ field }}
|
|
||||||
</th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
<tr ng-repeat="claim in claims | filter: query | orderBy:order" ng-click="show(claim.id)" ng-class="{'table-info': claim.priority == 'medium', 'table-warning': claim.priority == 'high', 'table-danger': claim.priority == 'critical'}" ng-if="((showClosed && (claim.state == 'closed' || claim.state == 'invalid')) || (!showClosed && (claim.state != 'closed' && claim.state != 'invalid'))) && (!showOnlyNew || claim.state == 'new') && (!showOnlyMines || claim.id_assignee == whoami) && (!showOnlyUnassigned || !claim.id_assignee)">
|
|
||||||
<td ng-repeat="field in fields">
|
|
||||||
<span ng-if="field != 'id_assignee' && field != 'id_team' && field != 'last_update'">
|
|
||||||
{{ claim[field] }}
|
|
||||||
</span>
|
|
||||||
<span ng-if="field == 'id_assignee'">
|
|
||||||
<span ng-repeat="assignee in assignees" ng-if="assignee.id == claim.id_assignee">
|
|
||||||
{{ assignee.name }}
|
|
||||||
</span>
|
|
||||||
</span>
|
|
||||||
<span ng-if="field == 'last_update'">
|
|
||||||
<span ng-controller="ClaimLastUpdateController" ng-init="init(claim)">
|
|
||||||
{{ last_update }}
|
|
||||||
</span>
|
|
||||||
</span>
|
|
||||||
<span ng-if="field == 'id_team'">
|
|
||||||
<a ng-href="teams/{{ claim.id_team }}">{{ teams[claim.id_team].name }}</a>
|
|
||||||
</span>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
|
|
||||||
<hr>
|
|
||||||
|
|
||||||
<div ng-controller="AssigneesListController">
|
|
||||||
<h2>
|
|
||||||
Assignables à
|
|
||||||
<button type="button" ng-click="newAssignee()" class="float-right btn btn-sm btn-primary"><span class="glyphicon glyphicon-plus" aria-hidden="true"></span> Ajouter une personne</button>
|
|
||||||
</h2>
|
|
||||||
<table class="table table-hover table-bordered table-striped table-sm">
|
|
||||||
<thead class="thead-dark">
|
|
||||||
<tr>
|
|
||||||
<th>ID</th>
|
|
||||||
<th>Nom</th>
|
|
||||||
<th></th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
<tr ng-repeat="a in assignees" ng-click="edit(a)" ng-if="a.id !== 0">
|
|
||||||
<td>
|
|
||||||
{{ a.id }}
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<span ng-if="a.id && !a.edit">{{ a.name }}</span>
|
|
||||||
<input type="text" class="form-control form-control-sm" ng-model="a.name" ng-if="!a.id || a.edit">
|
|
||||||
</td>
|
|
||||||
<td style="width: 10%;">
|
|
||||||
<button type="button" class="btn btn-sm btn-info" ng-if="a.id" ng-click="setMyAId(a.id)" ng-class="{'disabled': whoami == a.id}"><span class="glyphicon glyphicon-user"></span></button>
|
|
||||||
<button type="button" class="btn btn-sm btn-danger" ng-if="a.id && !a.edit" ng-click="removeAssignee(a)"><span class="glyphicon glyphicon-remove"></span></button>
|
|
||||||
<button type="button" class="btn btn-sm btn-success" ng-if="!a.id || a.edit" ng-click="updateAssignee(a)"><span class="glyphicon glyphicon-ok"></span></button>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
|
|
@ -1,75 +0,0 @@
|
||||||
<div class="float-right btn-group ml-1" role="group" ng-if="claim.id">
|
|
||||||
<button class="btn btn-secondary" ng-if="claim.state != 'new'" ng-click="changeState('new')"><span class="glyphicon glyphicon-repeat" aria-hidden="true"></span> Nouveau</button>
|
|
||||||
<button class="btn btn-warning" ng-if="claim.state != 'need-info' && (claim.state == 'new' || claim.state == 'confirmed' || claim.state == 'in-progress')" ng-click="changeState('need-info')"><span class="glyphicon glyphicon-question-sign" aria-hidden="true"></span> Besoin d'infos</button>
|
|
||||||
<button class="btn btn-danger" ng-if="claim.state != 'confirmed'" ng-click="changeState('confirmed')"><span class="glyphicon glyphicon-ok-sign" aria-hidden="true"></span> Confirmer</button>
|
|
||||||
<button class="btn btn-info" ng-if="claim.state != 'in-progress' && (claim.state == 'need-info' || claim.state == 'confirmed' || claim.state == 'need-review')" ng-click="changeState('in-progress')"><span class="glyphicon glyphicon-hourglass" aria-hidden="true"></span> En cours</button>
|
|
||||||
<button class="btn btn-success" ng-if="claim.state != 'need-review' && (claim.state == 'in-progress' || claim.state == 'confirmed' || claim.state == 'need-info')" ng-click="changeState('need-review')"><span class="glyphicon glyphicon-saved" aria-hidden="true"></span> Fait</button>
|
|
||||||
<button class="btn btn-success" ng-if="claim.state != 'closed' && claim.state == 'need-review'" ng-click="changeState('closed')"><span class="glyphicon glyphicon-thumbs-down" aria-hidden="true"></span> Clore</button>
|
|
||||||
<button class="btn btn-dark" ng-if="claim.state != 'invalid' && (claim.state == 'new' || claim.state == 'need-info')" ng-click="changeState('invalid')"><span class="glyphicon glyphicon-save" aria-hidden="true"></span> Invalide</button>
|
|
||||||
</div>
|
|
||||||
<button class="float-right btn btn-primary" ng-if="claim.id && whoami && whoami != claim.id_assignee" ng-click="assignToMe()">Me l'assigner</button>
|
|
||||||
<h2>Tâche</h2>
|
|
||||||
|
|
||||||
<form ng-submit="saveClaim()" class="row">
|
|
||||||
<div class="col-lg-7">
|
|
||||||
|
|
||||||
<div class="form-group row" ng-repeat="field in fields">
|
|
||||||
<label for="{{ field }}" class="col-2 col-form-label col-form-label-sm">{{ namedFields[field] }}</label>
|
|
||||||
<div class="col-10">
|
|
||||||
<input type="text" class="form-control form-control-sm" id="{{ field }}" ng-model="claim[field]" ng-if="field != 'state' && field != 'priority' && field != 'creation' && field != 'id_team' && field != 'id_exercice' && field != 'id_assignee' && field != 'description'">
|
|
||||||
<input type="datetime" class="form-control form-control-sm" id="{{ field }}" ng-model="claim[field]" ng-if="field == 'creation' && claim.id">
|
|
||||||
<select class="custom-select custom-select-sm" id="{{ field }}" ng-model="claim[field]" ng-options="k as v for (k, v) in states" ng-if="field == 'state'"></select>
|
|
||||||
<select class="custom-select custom-select-sm" id="{{ field }}" ng-model="claim[field]" ng-options="k as v for (k, v) in priorities" ng-if="field == 'priority'"></select>
|
|
||||||
<div class="input-group" ng-if="field == 'id_exercice'">
|
|
||||||
<select class="custom-select custom-select-sm" id="{{ field }}" ng-model="claim[field]" ng-options="ex.id as ex.title group by ex.path.split('/')[0] for ex in exercices"><option></option></select>
|
|
||||||
<div class="input-group-append" ng-if="claim.id_exercice">
|
|
||||||
<a type="button" class="btn btn-sm btn-outline-secondary" ng-href="exercices/{{ claim.id_exercice }}" target="_blank">Aller au challenge</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<select class="custom-select custom-select-sm" id="{{ field }}" ng-model="claim[field]" ng-options="k as t.name for (k, t) in teams" ng-if="field == 'id_team' && !claim.id"><option></option></select>
|
|
||||||
<div class="input-group" ng-if="field == 'id_team' && claim.id">
|
|
||||||
<select class="custom-select custom-select-sm" id="{{ field }}" ng-model="claim[field]" ng-options="k as t.name for (k, t) in teams"><option></option></select>
|
|
||||||
<div class="input-group-append" ng-if="claim.id_team">
|
|
||||||
<a type="button" class="btn btn-sm btn-outline-secondary" ng-href="teams/{{ claim.id_team }}" target="_blank">Aller à l'équipe</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="input-group" ng-if="field == 'id_assignee'">
|
|
||||||
<select class="custom-select custom-select-sm" id="{{ field }}" ng-model="claim[field]" ng-options="a.id as a.name for a in assignees"><option></option></select>
|
|
||||||
<div class="input-group-append" ng-if="whoami">
|
|
||||||
<button type="button" class="btn btn-sm btn-outline-primary" ng-class="{'disabled': whoami == claim[field]}" ng-click="assignToMe()">Me l'assigner</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="text-right mb-3" ng-show="claim.id">
|
|
||||||
<button type="submit" class="btn btn-primary"><span class="glyphicon glyphicon-save" aria-hidden="true"></span> Save</button>
|
|
||||||
<button type="button" class="btn btn-danger" ng-click="deleteClaim()"><span class="glyphicon glyphicon-remove" aria-hidden="true"></span> Delete</button>
|
|
||||||
</div>
|
|
||||||
<div class="text-right" ng-show="!claim.id">
|
|
||||||
<button type="submit" class="btn btn-primary"><span class="glyphicon glyphicon-plus" aria-hidden="true"></span> Ajouter la tâche</button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="col-lg-5">
|
|
||||||
|
|
||||||
<div class="form-group" ng-if="whoami">
|
|
||||||
<textarea class="form-control form-control-sm" placeholder="Ajouter un commentaire" rows="1" id="ndescription" ng-model="comm.ndescription"></textarea>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="form-group text-right" ng-show="claim.id && comm.ndescription.length">
|
|
||||||
<button type="submit" class="btn btn-primary"><span class="glyphicon glyphicon-save" aria-hidden="true"></span> Ajouter commentaire</button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div ng-repeat="description in claim.descriptions | orderBy:'id':true" class="alert" ng-class="{'alert-info': '' + description.id_assignee != whoami, 'alert-dark': '' + description.id_assignee == whoami}">
|
|
||||||
<div class="float-right btn-group-toggle" data-toggle="buttons">
|
|
||||||
<label class="btn btn-sm" ng-class="{'btn-outline-secondary':!description.publish, 'btn-success':description.publish}">
|
|
||||||
<input type="checkbox" ng-model="description.publish" ng-change="updateDescription(description)"><i class="glyphicon" ng-class="{'glyphicon-eye-open':description.publish,'glyphicon-eye-close':!description.publish}"></i>
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
<strong>Par <span ng-repeat="assignee in assignees" ng-if="assignee.id == description.id_assignee">{{ assignee.name }}</span> le {{ description.date | date:"mediumTime" }} :</strong>
|
|
||||||
<span style="white-space: pre-line">{{ description.content }}</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
|
|
@ -1,23 +0,0 @@
|
||||||
<h2>
|
|
||||||
Événements
|
|
||||||
<button type="button" ng-click="clearEvents()" class="float-right btn btn-sm btn-danger"><span class="glyphicon glyphicon-remove-sign" aria-hidden="true"></span> Vider la liste</button>
|
|
||||||
<button type="button" ng-click="show('new')" class="float-right btn btn-sm btn-primary mr-2"><span class="glyphicon glyphicon-plus" aria-hidden="true"></span> Ajouter un événement</button>
|
|
||||||
</h2>
|
|
||||||
|
|
||||||
<p><input type="search" class="form-control" placeholder="Search" ng-model="query" autofocus></p>
|
|
||||||
<table class="table table-hover table-bordered table-striped table-sm">
|
|
||||||
<thead class="thead-dark">
|
|
||||||
<tr>
|
|
||||||
<th ng-repeat="field in fields">
|
|
||||||
{{ field }}
|
|
||||||
</th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
<tr ng-repeat="event in events | filter: query" ng-click="show(event.id)">
|
|
||||||
<td ng-repeat="field in fields">
|
|
||||||
{{ event[field] }}
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
|
|
@ -1,23 +0,0 @@
|
||||||
<h2>
|
|
||||||
Événement
|
|
||||||
<a href="events/new" class="float-right btn btn-sm btn-primary ml-2"><span class="glyphicon glyphicon-plus" aria-hidden="true"></span> Ajouter un événement</a>
|
|
||||||
</h2>
|
|
||||||
|
|
||||||
<form ng-submit="saveEvent()">
|
|
||||||
<div class="form-group row" ng-repeat="field in fields">
|
|
||||||
<label for="{{ field }}" class="col-sm-1 col-form-label">{{ field | capitalize }}</label>
|
|
||||||
<div class="col-sm-11">
|
|
||||||
<input type="text" class="form-control" id="{{ field }}" ng-model="event[field]" ng-show="field != 'kind' && field != 'time'">
|
|
||||||
<input type="datetime" class="form-control" id="{{ field }}" ng-model="event[field]" ng-show="field == 'time' && event.id">
|
|
||||||
<select class="custom-select" id="{{ field }}" ng-model="event[field]" ng-options="k as v for (k, v) in kinds" ng-show="field == 'kind'">
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="text-right" ng-show="event.id">
|
|
||||||
<button type="submit" class="btn btn-primary"><span class="glyphicon glyphicon-save" aria-hidden="true"></span> Save</button>
|
|
||||||
<button type="button" class="btn btn-danger" ng-click="deleteEvent()"><span class="glyphicon glyphicon-remove" aria-hidden="true"></span> Delete</button>
|
|
||||||
</div>
|
|
||||||
<div class="text-right" ng-show="!event.id">
|
|
||||||
<button type="submit" class="btn btn-primary"><span class="glyphicon glyphicon-plus" aria-hidden="true"></span> Ajouter l'événement</button>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
|
|
@ -1,262 +0,0 @@
|
||||||
<div ng-controller="ExerciceFlagsController">
|
|
||||||
<button type="button" ng-click="syncFlags()" class="btn btn-light float-right"><span class="glyphicon glyphicon-refresh" aria-hidden="true"></span> Synchroniser</button>
|
|
||||||
<h2>
|
|
||||||
<a href="exercices/{{exercice.id}}">{{exercice.title}}</a> <small class="text-muted">Flags</small>
|
|
||||||
<div class="btn-group" role="group" ng-if="themes[exercice.id_theme].exercices">
|
|
||||||
<a href="exercices/{{ themes[exercice.id_theme].exercices[my_ex_num[exercice.id]-1].id }}/flags" title="Exercice précédent" ng-class="{'disabled': !themes[exercice.id_theme].exercices[my_ex_num[exercice.id]-1]}" class="btn btn-sm btn-light"><span class="glyphicon glyphicon-chevron-left" aria-hidden="true"></span></a>
|
|
||||||
<a href="exercices/{{ themes[exercice.id_theme].exercices[my_ex_num[exercice.id]-1+2].id }}/flags" title="Exercice suivant" ng-class="{'disabled': !themes[exercice.id_theme].exercices[my_ex_num[exercice.id]-1+2]}" class="btn btn-sm btn-light"><span class="glyphicon glyphicon-chevron-right" aria-hidden="true"></span></a>
|
|
||||||
</div>
|
|
||||||
</h2>
|
|
||||||
|
|
||||||
<div class="card border-success mb-3">
|
|
||||||
<div class="card-header bg-success text-light">
|
|
||||||
<button type="button" ng-click="addFlag()" class="btn btn-primary float-right"><span class="glyphicon glyphicon-plus" aria-hidden="true"></span> Ajouter</button>
|
|
||||||
<h4 class="m-0">Drapeaux</h4>
|
|
||||||
</div>
|
|
||||||
<div class="list-group">
|
|
||||||
<div ng-repeat="flag in flags" class="list-group-item">
|
|
||||||
<div class="row">
|
|
||||||
<div class="col-4">
|
|
||||||
<form ng-submit="saveFlag()" class="form-horizontal" id="flag-{{flag.id}}">
|
|
||||||
<div class="form-group">
|
|
||||||
<div class="float-right" ng-show="flag.id">
|
|
||||||
<button type="submit" class="btn btn-sm btn-success"><span class="glyphicon glyphicon-ok" aria-hidden="true"></span></button>
|
|
||||||
</div>
|
|
||||||
<label for="klabel{{flag.id}}" class="col-form-label-sm">Intitulé</label>
|
|
||||||
<input type="text" id="klabel{{flag.id}}" ng-model="flag.label" class="form-control form-control-sm" placeholder="Intitulé" title="Intitulé">
|
|
||||||
</div>
|
|
||||||
<div class="form-group">
|
|
||||||
<div class="float-right">
|
|
||||||
<button type="button" ng-click="deleteFlag()" class="btn btn-sm btn-danger"><span class="glyphicon glyphicon-remove" aria-hidden="true"></span></button>
|
|
||||||
</div>
|
|
||||||
<label for="kplaceholder{{flag.id}}" class="col-form-label-sm">Placeholder</label>
|
|
||||||
<input type="text" id="kplaceholder{{flag.id}}" ng-model="flag.placeholder" class="form-control form-control-sm" placeholder="Indication de formatage" title="Indication de formatage">
|
|
||||||
</div>
|
|
||||||
<div class="form-group">
|
|
||||||
<label for="khelp{{flag.id}}" class="col-form-label-sm">Help</label>
|
|
||||||
<input type="text" id="khelp{{flag.id}}" ng-model="flag.help" class="form-control form-control-sm" help="Description du champ" title="Description du champ">
|
|
||||||
</div>
|
|
||||||
<div class="form-group">
|
|
||||||
<label for="kvalue{{flag.id}}" class="col-form-label-sm">Valeur</label>
|
|
||||||
<div class="input-group" ng-if="flag.id && !flag.show_raw">
|
|
||||||
<input type="text" id="kvalue{{flag.id}}" ng-model="flag.value" class="form-control form-control-sm" placeholder="Condensat" title="Condensat">
|
|
||||||
<div class="input-group-append">
|
|
||||||
<button class="btn btn-sm btn-outline-secondary" ng-click="changeValue(flag)" title="Cliquez pour éditer la valeur brute du flag, au lieu du checksum" type="button"><span class="glyphicon glyphicon-erase" aria-hidden="true"></span></button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<input type="text" id="kflag{{flag.id}}" ng-model="flag.flag" class="form-control form-control-sm" placeholder="Chaîne brute à valider" ng-if="!flag.id || flag.show_raw" title="Chaîne brute à valider">
|
|
||||||
</div>
|
|
||||||
<div class="form-group">
|
|
||||||
<label for="kvre{{flag.id}}" class="col-form-label-sm">Capture regexp</label>
|
|
||||||
<input type="text" id="kvre{{flag.id}}" ng-model="flag.capture_regexp" class="form-control form-control-sm" placeholder="Regexp selecting validation string" title="Regexp selecting validation string">
|
|
||||||
</div>
|
|
||||||
<div class="form-group row">
|
|
||||||
<div class="col-auto custom-control custom-checkbox ml-1">
|
|
||||||
<input type="checkbox" class="custom-control-input" id="kicase{{flag.id}}" ng-model="flag.ignorecase">
|
|
||||||
<label class="custom-control-label" for="kicase{{flag.id}}">Ignore case</label>
|
|
||||||
</div>
|
|
||||||
<div class="col-auto custom-control custom-checkbox ml-1">
|
|
||||||
<input type="checkbox" class="custom-control-input" id="kmline{{flag.id}}" ng-model="flag.multiline">
|
|
||||||
<label class="custom-control-label" for="kmline{{flag.id}}">Multiline</label>
|
|
||||||
</div>
|
|
||||||
<div class="col-2">
|
|
||||||
<input type="text" id="kccost{{flag.id}}" ng-model="flag.choices_cost" class="form-control form-control-sm" placeholder="Choices cost" title="Choices cost" integer>
|
|
||||||
</div>
|
|
||||||
<div class="col-2">
|
|
||||||
<input type="text" id="kbgain{{flag.id}}" ng-model="flag.bonus_gain" class="form-control form-control-sm" placeholder="Bonus gain" title="Bonus gain" integer>
|
|
||||||
</div>
|
|
||||||
<div class="col-2">
|
|
||||||
<input type="text" id="korder{{flag.id}}" ng-model="flag.order" class="form-control form-control-sm" placeholder="Order" title="Order" integer>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
<div class="col-4">
|
|
||||||
<div ng-controller="ExerciceFlagDepsController" ng-init="init(flag)">
|
|
||||||
<strong>Dépendances :</strong>
|
|
||||||
<ul ng-if="deps.length > 0">
|
|
||||||
<dependancy ng-repeat="dep in deps" dep="dep"></dependancy>
|
|
||||||
</ul>
|
|
||||||
<span ng-if="deps.length == 0"> sans</span>
|
|
||||||
</div>
|
|
||||||
<hr>
|
|
||||||
<div ng-controller="ExerciceFlagStatsController" ng-init="init(flag)">
|
|
||||||
<strong>Statistiques</strong>
|
|
||||||
<ul>
|
|
||||||
<li>ID : {{ flag.id }}</li>
|
|
||||||
<li>Validés : {{ stats["completed"] }}</li>
|
|
||||||
<li>
|
|
||||||
Tentés : {{ stats["tries"] }}
|
|
||||||
<button type="button" ng-click="deleteTries()" class="btn btn-sm btn-danger" ng-if="stats['tries'] > 0"><span class="glyphicon glyphicon-trash" aria-hidden="true"></span></button>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
Équipes :
|
|
||||||
<span ng-if="stats['teams'].length == 0">aucune</span>
|
|
||||||
<team-link ng-repeat="team in stats['teams']" id-team="team"></team-link>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="col-4" ng-controller="ExerciceFlagChoicesController">
|
|
||||||
<div class="btn-toolbar justify-content-end mb-2" role="toolbar">
|
|
||||||
<div class="btn-group mx-2" role="group">
|
|
||||||
<button type="button" ng-click="addChoice()" class="btn btn-sm btn-primary"><span class="glyphicon glyphicon-plus" aria-hidden="true"></span> Ajouter choix</button>
|
|
||||||
</div>
|
|
||||||
<div class="btn-group" role="group" ng-show="logged">
|
|
||||||
<button type="button" class="btn btn-sm btn-dark" data-toggle="modal" data-target="#validationModal" data-flag="{{ flag.label }}" data-flagid="{{ flag.id }}" data-kind="flag_found"><span class="glyphicon glyphicon-check" aria-hidden="true"></span> Valider pour </button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div ng-repeat="choice in choices" ng-if="choices.length > 0">
|
|
||||||
<div class="row">
|
|
||||||
<label for="clabel_{{choice.id}}" class="col-sm-3 col-form-label-sm">Intitulé</label>
|
|
||||||
<div class="col-sm-7">
|
|
||||||
<input type="text" id="clabel_{{choice.id}}" ng-model="choice.label" class="form-control form-control-sm">
|
|
||||||
</div>
|
|
||||||
<div class="col-sm-2">
|
|
||||||
<button type="button" ng-click="saveChoice()" class="btn btn-sm btn-success"><span class="glyphicon glyphicon-ok" aria-hidden="true"></span></button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="row">
|
|
||||||
<label for="cvalue_{{choice.id}}" class="col-sm-3 col-form-label-sm">Valeur</label>
|
|
||||||
<div class="col-sm-7">
|
|
||||||
<input type="text" id="cvalue_{{choice.id}}" ng-model="choice.value" class="form-control form-control-sm">
|
|
||||||
</div>
|
|
||||||
<div class="col-sm-2">
|
|
||||||
<button type="button" ng-click="deleteChoice()" class="btn btn-sm btn-danger"><span class="glyphicon glyphicon-remove" aria-hidden="true"></span></button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<hr ng-if="!$last">
|
|
||||||
</div>
|
|
||||||
<span ng-if="choices.length == 0">Aucun choix</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<form ng-submit="testFlag(flag)" class="row bg-dark" style="border-radius: 5px; padding: 5px 0">
|
|
||||||
<div class="col-11">
|
|
||||||
<flag-key kid="flag.id" key="flag" settings="{'wchoiceCurrentCoefficient': 1.0}" wantchoices="flag.wantchoices"></flag-key>
|
|
||||||
</div>
|
|
||||||
<div class="col-1">
|
|
||||||
<button class="btn btn-warning" type="submit"><span class="glyphicon glyphicon-play" aria-hidden="true"></span></button>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="card border-success mb-3" ng-controller="ExerciceMCQFlagsController">
|
|
||||||
<div class="card-header bg-success text-light">
|
|
||||||
<button type="button" ng-click="addQuizz()" class="btn btn-primary float-right"><span class="glyphicon glyphicon-plus" aria-hidden="true"></span> Ajouter</button>
|
|
||||||
<h4 class="m-0">Quizz</h4>
|
|
||||||
</div>
|
|
||||||
<div class="list-group">
|
|
||||||
<form ng-submit="saveQuiz()" class="list-group-item" ng-repeat="(qk,q) in quiz">
|
|
||||||
<div class="form-group row" id="quiz-{{q.id}}">
|
|
||||||
<input type="text" id="qlabel{{q.id}}" ng-model="q.title" class="col form-control" placeholder="Intitulé">
|
|
||||||
<div class="col-1" ng-show="q.id">
|
|
||||||
<input type="text" id="qorder{{q.id}}" ng-model="q.order" class="col form-control" placeholder="Ordre" title="Ordre" integer>
|
|
||||||
</div>
|
|
||||||
<div class="col-auto" ng-show="q.id">
|
|
||||||
<button type="button" ng-click="deleteQuiz()" class="btn btn-danger"><span class="glyphicon glyphicon-remove" aria-hidden="true"></span></button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="form-group row" ng-repeat="(ck,choice) in q.entries">
|
|
||||||
<div class="col form-check">
|
|
||||||
<label class="custom-control custom-checkbox">
|
|
||||||
<input class="custom-control-input" type="checkbox" ng-model="choice.response">
|
|
||||||
<span class="custom-control-label">
|
|
||||||
<input type="text" id="kchoice{{q.id}}-{{choice.id}}" ng-model="choice.label" class="form-control form-control-sm" placeholder="Intitulé">
|
|
||||||
</span>
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
<div class="col-auto">
|
|
||||||
<button type="button" ng-click="deleteChoice()" class="btn btn-sm btn-danger"><span class="glyphicon glyphicon-trash" aria-hidden="true"></span></button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="form-group row">
|
|
||||||
<div class="col-auto">
|
|
||||||
<button type="button" ng-click="addChoice()" class="btn btn-primary"><span class="glyphicon glyphicon-plus" aria-hidden="true"></span> Ajouter un choix</button>
|
|
||||||
</div>
|
|
||||||
<div class="col-auto">
|
|
||||||
<button type="submit" class="btn btn-success"><span class="glyphicon glyphicon-ok" aria-hidden="true"></span></button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div ng-controller="ExerciceMCQDepsController" ng-init="init(q)">
|
|
||||||
Dépendances :
|
|
||||||
<ul ng-if="deps.length > 0">
|
|
||||||
<dependancy ng-repeat="dep in deps" dep="dep"></dependancy>
|
|
||||||
</ul>
|
|
||||||
<span ng-if="deps.length == 0"> sans</span>
|
|
||||||
<hr>
|
|
||||||
<div ng-controller="ExerciceMCQStatsController" ng-init="init(q)">
|
|
||||||
<strong>Statistiques</strong>
|
|
||||||
<ul>
|
|
||||||
<li>ID : {{ q.id }}</li>
|
|
||||||
<li>Validés : {{ stats["completed"] }}</li>
|
|
||||||
<li>
|
|
||||||
Tentés : {{ stats["tries"] }}
|
|
||||||
<button type="button" ng-click="deleteTries()" class="btn btn-sm btn-danger" ng-if="stats['tries'] > 0"><span class="glyphicon glyphicon-trash" aria-hidden="true"></span></button>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
Équipes :
|
|
||||||
<span ng-if="stats['teams'].length == 0">aucune</span>
|
|
||||||
<team-link ng-repeat="team in stats['teams']" id-team="team"></team-link>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="modal fade" id="validationModal" tabindex="-1" role="dialog">
|
|
||||||
<div class="modal-dialog" role="document">
|
|
||||||
<div class="modal-content bg-light">
|
|
||||||
<div class="modal-header">
|
|
||||||
<h5 class="modal-title">Valider ce flag pour une équipe</h5>
|
|
||||||
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
|
|
||||||
<span aria-hidden="true">×</span>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
<form ng-submit="validateForTeam()">
|
|
||||||
<div class="modal-body">
|
|
||||||
<div class="form-group row">
|
|
||||||
<label for="labelflag" class="col-md-3 col-form-label">Flag</label>
|
|
||||||
<div class="col-md-9">
|
|
||||||
<input type="text" readonly class="form-control-plaintext" id="labelflag">
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="form-group row">
|
|
||||||
<label for="tteam" class="col-md-3 col-form-label">Équipe</label>
|
|
||||||
<div class="col-md-9" ng-controller="TeamsListController">
|
|
||||||
<select class="custom-select custom-select-sm" id="tteam" ng-model="selectedTeam" ng-options="t.id as t.name for t in teams"></select>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="modal-footer">
|
|
||||||
<button type="button" class="btn btn-secondary" data-dismiss="modal">Annuler</button>
|
|
||||||
<button type="submit" class="btn btn-primary">Enregistrer</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
$('#validationModal').on('shown.bs.modal', function (event) {
|
|
||||||
$('#tteam').trigger('focus');
|
|
||||||
|
|
||||||
var button = $(event.relatedTarget);
|
|
||||||
var flag = button.data('flag');
|
|
||||||
var flagid = button.data('flagid');
|
|
||||||
var kind = button.data('kind');
|
|
||||||
|
|
||||||
var modal = $(this);
|
|
||||||
|
|
||||||
modal.data('kind', kind);
|
|
||||||
modal.data('flagid', flagid);
|
|
||||||
modal.data('flag', flag);
|
|
||||||
if (modal.data('flag')) {
|
|
||||||
$("#labelflag").val(modal.data('flag'))
|
|
||||||
}
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
|
|
@ -1,95 +0,0 @@
|
||||||
<div class="d-flex justify-content-between">
|
|
||||||
<h2>Exercices</h2>
|
|
||||||
<div class="align-self-center">
|
|
||||||
<a
|
|
||||||
href="tags"
|
|
||||||
class="btn btn-sm btn-dark"
|
|
||||||
>
|
|
||||||
<span class="glyphicon glyphicon-th" aria-hidden="true"></span> Tags
|
|
||||||
</a>
|
|
||||||
<a
|
|
||||||
href="files"
|
|
||||||
class="btn btn-sm btn-info"
|
|
||||||
>
|
|
||||||
<span class="glyphicon glyphicon-file" aria-hidden="true"></span> Voir les fichiers
|
|
||||||
</a>
|
|
||||||
<div class="btn-group btn-group-toggle ml-2 mr-1">
|
|
||||||
<label class="btn btn-sm btn-secondary" ng-class="{active: syncFiles, 'btn-warning': syncFiles}">
|
|
||||||
<input type="checkbox" ng-model="syncFiles"> Fichiers
|
|
||||||
</label>
|
|
||||||
<label class="btn btn-sm btn-secondary" ng-class="{active: syncHints, 'btn-warning': syncHints}" ng-show="whoami">
|
|
||||||
<input type="checkbox" ng-model="syncHints"> Indices
|
|
||||||
</label>
|
|
||||||
<label class="btn btn-sm btn-secondary" ng-class="{active: syncFlags, 'btn-warning': syncFlags}">
|
|
||||||
<input type="checkbox" ng-model="syncFlags"> Flags
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
ng-click="syncFull()"
|
|
||||||
ng-class="{'disabled': inSync}"
|
|
||||||
class="btn btn-sm btn-secondary"
|
|
||||||
>
|
|
||||||
<span class="glyphicon glyphicon-refresh" aria-hidden="true"></span> Synchroniser
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="progress" ng-if="inSync">
|
|
||||||
<div class="progress-bar" style="width: {{ done * 100 / total }}%;"></div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div>
|
|
||||||
<p><input type="search" class="form-control" placeholder="Search" ng-model="query" ng-keypress="validateSearch($event)" autofocus></p>
|
|
||||||
<table class="table table-hover table-bordered table-striped table-sm">
|
|
||||||
<thead class="thead-dark">
|
|
||||||
<tr>
|
|
||||||
<th>
|
|
||||||
<input type="checkbox" ng-click="toggleSelectAll()" ng-model="selectall">
|
|
||||||
</th>
|
|
||||||
<th ng-repeat="field in fields">
|
|
||||||
{{ field }}
|
|
||||||
</th>
|
|
||||||
<th>
|
|
||||||
Thème
|
|
||||||
</th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
<tr ng-repeat="exercice in exercices | filter: query">
|
|
||||||
<td>
|
|
||||||
<input type="checkbox" ng-model="exercice.selected">
|
|
||||||
</td>
|
|
||||||
<td ng-repeat="field in fields" ng-click="show(exercice.id)">
|
|
||||||
{{ exercice[field] | stripHTML }}
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<a ng-href="themes/{{ exercice.id_theme }}">{{ themes[exercice.id_theme].name }}</a>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<form ng-submit="updateExercices()">
|
|
||||||
<fieldset>
|
|
||||||
<legend class="text-dark">Édition de masse <button type="submit" class="float-right btn btn-sm btn-success"><span class="glyphicon glyphicon-ok" aria-hidden="true"></span></button></legend>
|
|
||||||
<p>
|
|
||||||
Les propriétés en gras seront écrasées.
|
|
||||||
</p>
|
|
||||||
<div ng-class="{'row': field != 'disabled', 'form-group': field != 'disabled', 'form-check': field == 'disabled'}" ng-repeat="field in ['disabled','gain','coefficient','issue','issuekind']">
|
|
||||||
<input type="checkbox" class="form-check-input" id="{{ field }}" ng-model="exercice[field]" ng-if="field == 'disabled'">
|
|
||||||
<label for="{{ field }}" class="col-form-label-sm" ng-class="{'col-sm-1': field != 'disabled', 'font-weight-bold': exercice[field] !== undefined}">{{ field | capitalize }}</label>
|
|
||||||
<div class="col-sm-11" ng-if="field != 'disabled'">
|
|
||||||
<input type="text" class="form-control form-control-sm" id="{{ field }}" ng-model="exercice[field]" ng-if="field != 'statement' && field != 'issue' && field != 'issuekind' && field != 'overview' && field != 'depend' && field != 'gain' && field != 'coefficient'">
|
|
||||||
<input type="text" class="form-control form-control-sm" id="{{ field }}" ng-model="exercice[field]" ng-if="field == 'gain'" integer>
|
|
||||||
<input type="text" class="form-control form-control-sm" id="{{ field }}" ng-model="exercice[field]" ng-if="field == 'coefficient'" float>
|
|
||||||
<textarea class="form-control form-control-sm" id="{{field}}" ng-model="exercice[field]" ng-if="field == 'statement' || field == 'overview' || field == 'issue'"></textarea>
|
|
||||||
<select class="form-control form-control-sm" id="{{field}}" ng-model="exercice[field]" ng-options="ex.id as ex.title group by ex.path.split('/')[0] for ex in exercices" ng-if="field == 'depend'">
|
|
||||||
<option value="">Aucune</option>
|
|
||||||
</select>
|
|
||||||
<select class="form-control form-control-sm" id="{{field}}" ng-model="exercice[field]" ng-options="v for v in ['primary', 'secondary', 'success', 'danger', 'warning', 'info', 'light', 'dark']" ng-if="field == 'issuekind'"></select>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</fieldset>
|
|
||||||
</form>
|
|
||||||
|
|
@ -1,15 +0,0 @@
|
||||||
<h2>
|
|
||||||
<a href="exercices/{{exercice.id}}">{{exercice.title}}</a> <small class="text-muted">Résolution</small>
|
|
||||||
<div class="btn-group" role="group" ng-if="themes[exercice.id_theme].exercices">
|
|
||||||
<a href="exercices/{{ themes[exercice.id_theme].exercices[my_ex_num[exercice.id]-1].id }}/resolution" title="Exercice précédent" ng-class="{'disabled': !themes[exercice.id_theme].exercices[my_ex_num[exercice.id]-1]}" class="btn btn-sm btn-light"><span class="glyphicon glyphicon-chevron-left" aria-hidden="true"></span></a>
|
|
||||||
<a href="exercices/{{ themes[exercice.id_theme].exercices[my_ex_num[exercice.id]-1+2].id }}/resolution" title="Exercice suivant" ng-class="{'disabled': !themes[exercice.id_theme].exercices[my_ex_num[exercice.id]-1+2]}" class="btn btn-sm btn-light"><span class="glyphicon glyphicon-chevron-right" aria-hidden="true"></span></a>
|
|
||||||
</div>
|
|
||||||
</h2>
|
|
||||||
|
|
||||||
<div class="row">
|
|
||||||
<div class="col text-center" ng-if="exercice.videoURI">
|
|
||||||
<video src="{{exercice.videoURI.replace('\$RFILES\$/','vids/').replace('\$FILES\$/','files/').replace('?', '%3F')}}" controls style="height: 80vh; margin: auto;"></video>
|
|
||||||
</div>
|
|
||||||
<div class="col" ng-if="exercice.resolution" ng-bind-html="exercice.resolution.replaceAll('\$FILES\$/','files/')">
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
@ -1,475 +0,0 @@
|
||||||
<div class="d-flex align-items-start">
|
|
||||||
<h2 class="text-truncate">
|
|
||||||
{{exercice.title}}
|
|
||||||
<small ng-if="themes && themes[exercice.id_theme]"><a href="themes/{{ exercice.id_theme }}" title="{{themes[exercice.id_theme].authors | stripHTML}}">{{themes[exercice.id_theme].name}}</a></small>
|
|
||||||
</h2>
|
|
||||||
<div class="btn-group" role="group" ng-if="themes[exercice.id_theme?exercice.id_theme:'0'].exercices">
|
|
||||||
<a href="exercices/{{ themes[exercice.id_theme?exercice.id_theme:'0'].exercices[my_ex_num[exercice.id]-1].id }}" title="Exercice précédent" ng-class="{'disabled': !themes[exercice.id_theme?exercice.id_theme:'0'].exercices[my_ex_num[exercice.id]-1]}" class="btn btn-sm btn-light"><span class="glyphicon glyphicon-chevron-left" aria-hidden="true"></span></a>
|
|
||||||
<a href="exercices/{{ themes[exercice.id_theme?exercice.id_theme:'0'].exercices[my_ex_num[exercice.id]-1+2].id }}" title="Exercice suivant" ng-class="{'disabled': !themes[exercice.id_theme?exercice.id_theme:'0'].exercices[my_ex_num[exercice.id]-1+2]}" class="btn btn-sm btn-light"><span class="glyphicon glyphicon-chevron-right" aria-hidden="true"></span></a>
|
|
||||||
</div>
|
|
||||||
<div class="ml-auto d-flex flex-row-reverse text-nowrap">
|
|
||||||
<a href="exercices/{{exercice.id}}/resolution" ng-disabled="!exercice.videoURI" class="ml-2 btn btn-sm btn-info"><span class="glyphicon glyphicon-facetime-video" aria-hidden="true"></span> Vidéo</a>
|
|
||||||
<a href="exercices/{{exercice.id}}/flags" class="ml-2 btn btn-sm btn-success"><span class="glyphicon glyphicon-flag" aria-hidden="true"></span> Flags</a>
|
|
||||||
<div class="btn-group ml-2" role="group">
|
|
||||||
<button type="button" ng-click="syncExo()" ng-class="{'disabled': inSync}" class="btn btn-sm btn-light"><span class="glyphicon glyphicon-refresh" aria-hidden="true"></span> Synchroniser</button>
|
|
||||||
<button type="button" ng-click="checkExoSync()" ng-class="{'disabled': inSync}" class="btn btn-sm btn-light" title="Exporter l'exercice actuel"><span class="glyphicon glyphicon-thumbs-up" aria-hidden="true"></span></button>
|
|
||||||
</div>
|
|
||||||
<a href="{{exercice.forge_link}}" target="_blank" class="ml-2 btn btn-sm btn-dark" ng-if="exercice.forge_link"><span class="glyphicon glyphicon-folder-open" aria-hidden="true"></span> Voir sur la forge</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div ng-if="diff">
|
|
||||||
<h3>Différences par rapport au dépôt</h3>
|
|
||||||
<div ng-repeat="diffline in diff" class="row">
|
|
||||||
<a ng-href="{{ diffline.link }}" class="col-2 d-flex justify-content-end align-items-center text-monospace" title="{{ diffline.field }}">{{ diffline.field }}</a>
|
|
||||||
<div class="col">
|
|
||||||
<div class="text-danger"><span class="text-monospace">-</span>{{ diffline.be }}</div>
|
|
||||||
<div class="text-success"><span class="text-monospace">+</span>{{ diffline.af }}</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<hr ng-if="diff" class="my-3">
|
|
||||||
|
|
||||||
<div class="row mb-5">
|
|
||||||
|
|
||||||
<form class="col-md-8" ng-submit="saveExercice()">
|
|
||||||
<div class="form-group row" ng-repeat="field in fields">
|
|
||||||
<label for="{{ field }}" class="col-sm-1 col-form-label-sm">{{ field | capitalize }}</label>
|
|
||||||
<div class="col-sm-11">
|
|
||||||
<input type="text" class="form-control form-control-sm" id="{{ field }}" ng-model="exercice[field]" ng-if="field != 'statement' && field != 'issue' && field != 'issuekind' && field != 'overview' && field != 'resolution' && field != 'finished' && field != 'depend' && field != 'gain' && field != 'coefficient' && field != 'wip' && field != 'disabled' && field != 'background_color'">
|
|
||||||
<input type="checkbox" id="{{ field }}" ng-model="exercice[field]" ng-if="field == 'wip' || field == 'disabled'">
|
|
||||||
<input type="text" class="form-control form-control-sm" id="{{ field }}" ng-model="exercice[field]" ng-if="field == 'gain'" integer>
|
|
||||||
<input type="text" class="form-control form-control-sm" id="{{ field }}" ng-model="exercice[field]" ng-if="field == 'coefficient'" float>
|
|
||||||
<textarea class="form-control form-control-sm" id="{{field}}" ng-model="exercice[field]" ng-if="field == 'statement' || field == 'overview' || field == 'finished' || field == 'resolution' || field == 'issue'"></textarea>
|
|
||||||
<select class="form-control form-control-sm" id="{{field}}" ng-model="exercice[field]" ng-options="ex.id as ex.title group by ex.path.split('/')[0] for ex in exercices" ng-if="field == 'depend'">
|
|
||||||
<option value="">Aucune</option>
|
|
||||||
</select>
|
|
||||||
<select class="form-control form-control-sm" id="{{field}}" ng-model="exercice[field]" ng-options="v for v in ['primary', 'secondary', 'success', 'danger', 'warning', 'info', 'light', 'dark']" ng-if="field == 'issuekind'"></select>
|
|
||||||
<input type="color" class="form-control form-control-sm" id="{{ field }}" ng-model="exercice[field]" ng-if="field == 'background_color'" color>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="text-right" ng-show="exercice.id">
|
|
||||||
<button type="submit" class="btn btn-primary"><span class="glyphicon glyphicon-save" aria-hidden="true"></span> Save</button>
|
|
||||||
<button type="button" class="btn btn-danger" ng-click="deleteExercice()"><span class="glyphicon glyphicon-remove" aria-hidden="true"></span> Delete</button>
|
|
||||||
</div>
|
|
||||||
<div class="text-right" ng-show="!exercice.id">
|
|
||||||
<button type="submit" class="btn btn-primary"><span class="glyphicon glyphicon-plus" aria-hidden="true"></span> Create exercice</button>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
|
|
||||||
<div class="col-md-4 accordion" ng-show="exercice.id" id="accordionExercice">
|
|
||||||
|
|
||||||
<div class="mb-2" style="overflow-y: auto; max-height: 250px" ng-controller="ExerciceClaimsController" ng-show="claims.length">
|
|
||||||
<table class="table table-sm table-hover table-striped">
|
|
||||||
<tbody>
|
|
||||||
<tr ng-repeat="claim in claims | orderBy: 'priority'" ng-click="show(claim.id)" ng-class="{'table-info': claim.priority == 'medium', 'table-warning': claim.priority == 'high', 'table-danger': claim.priority == 'critical'}" ng-if="showClosed || (claim.state != 'closed' && claim.state != 'invalid')">
|
|
||||||
<td ng-bind="claim.subject"></td>
|
|
||||||
<td><a ng-href="teams/{{ claim.id_team}}" style="background-color: {{ claim.team.color | toColor }}" ng-bind="claim.team.name"></a></td>
|
|
||||||
<td ng-bind="claim.state"></td>
|
|
||||||
<td ng-repeat="assignee in assignees" ng-if="assignee.id == claim.id_assignee" ng-bind="assignee.name"></td>
|
|
||||||
</tr>
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="card border-secondary" ng-controller="ExerciceStatsController">
|
|
||||||
<div class="card-header bg-primary text-light" type="button" data-toggle="collapse" data-target="#collapseStats" aria-expanded="true" aria-controls="collapseStats">
|
|
||||||
<h4 class="m-0"><small class="glyphicon glyphicon-chevron-right" aria-hidden="true"></small> Statistiques</h4>
|
|
||||||
</div>
|
|
||||||
<div class="collapse show" id="collapseStats" aria-labelledby="headingStats" data-parent="#accordionExercice">
|
|
||||||
<dl class="row mt-2 ml-2">
|
|
||||||
<dt class="col-sm-6 text-truncate" title="Nombre de points actuel">Points actuels</dt>
|
|
||||||
<dd class="col-sm-6"><ng-pluralize count="stats.current_gain" when="{'0': '0 points', 'one': '{} point', 'other': '{} points'}"></ng-pluralize></dd>
|
|
||||||
|
|
||||||
<dt class="col-sm-6 text-truncate" title="Nombre d'équipes">Défi tenté par</dt>
|
|
||||||
<dd class="col-sm-6"><ng-pluralize count="stats.team_tries" when="{'0': 'aucune équipe', 'one': '{} équipe', 'other': '{} équipes'}"></ng-pluralize></dd>
|
|
||||||
|
|
||||||
<dt class="col-sm-6 text-truncate" title="Nombre de tentatives">Nombre de tentatives</dt>
|
|
||||||
<dd class="col-sm-6"><ng-pluralize count="stats.total_tries" when="{'0': 'aucune tentative', 'one': '{} tentative', 'other': '{} tentatives'}"></ng-pluralize></dd>
|
|
||||||
|
|
||||||
<dt class="col-sm-6 text-truncate" title="Défi validé par">Défi validé par</dt>
|
|
||||||
<dd class="col-sm-6"><ng-pluralize count="stats.solved_count" when="{'0': 'aucune équipe', 'one': '{} équipe', 'other': '{} équipes'}"></ng-pluralize></dd>
|
|
||||||
|
|
||||||
<dt class="col-sm-6 text-truncate" title="Drapeaux validés">Drapeaux validés</dt>
|
|
||||||
<dd class="col-sm-6" title="{{ stats.flag_solved }}" ng-if="stats.flag_solved">{{ stats.flag_solved.length }}</dd>
|
|
||||||
<dd class="col-sm-6" title="{{ stats.flag_solved }}" ng-if="!stats.flag_solved">aucun</dd>
|
|
||||||
|
|
||||||
<dt class="col-sm-6 text-truncate" title="QCM validés">QCM validés</dt>
|
|
||||||
<dd class="col-sm-6" title="{{ stats.mcq_solved }}" ng-if="stats.mcq_solved">{{ stats.mcq_solved.length }}</dd>
|
|
||||||
<dd class="col-sm-6" title="{{ stats.mcq_solved }}" ng-if="!stats.mcq_solved">aucun</dd>
|
|
||||||
</dl>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="card border-secondary" ng-controller="ExerciceFilesController">
|
|
||||||
<div class="card-header bg-secondary text-light" type="button" data-toggle="collapse" data-target="#collapseFiles" aria-expanded="true" aria-controls="collapseFiles">
|
|
||||||
<h4 class="m-0"><small class="glyphicon glyphicon-chevron-right" aria-hidden="true"></small> Téléchargements</h4>
|
|
||||||
</div>
|
|
||||||
<div class="collapse" id="collapseFiles" aria-labelledby="headingFiles" data-parent="#accordionExercice">
|
|
||||||
<div class="list-group">
|
|
||||||
<div class="btn-toolbar bg-secondary justify-content-end" role="toolbar">
|
|
||||||
<div class="btn-group" role="group">
|
|
||||||
<button type="button" ng-click="syncFiles()" class="btn btn-sm btn-light"><span class="glyphicon glyphicon-refresh" aria-hidden="true"></span> Synchroniser</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<form ng-submit="saveFile()" class="list-group-item bg-light text-dark" ng-repeat="file in files">
|
|
||||||
<div class="row form-group">
|
|
||||||
<input type="text" ng-model="file.name" class="col form-control form-control-sm" placeholder="Nom de fichier">
|
|
||||||
<a href="../files{{file.path}}" target="_self" class="btn btn-sm btn-secondary col-auto"><span class="glyphicon glyphicon-download" aria-hidden="true"></span></a>
|
|
||||||
<button type="submit" class="btn btn-sm btn-success col-auto"><span class="glyphicon glyphicon-ok" aria-hidden="true"></span></button><br>
|
|
||||||
<button type="button" ng-click="deleteFile()" class="btn btn-sm btn-danger col-auto"><span class="glyphicon glyphicon-remove" aria-hidden="true"></span></button>
|
|
||||||
</div>
|
|
||||||
<div class="cksum">
|
|
||||||
Taille : <span title="{{ file.size }} octets">{{ file.size | size }}</span> ‐
|
|
||||||
BLAKE2b : <samp title="{{ file.checksum | cksum }}">{{ file.checksum | cksum }}</samp>
|
|
||||||
</div>
|
|
||||||
<div class="col-auto custom-control custom-checkbox">
|
|
||||||
<input type="checkbox" class="custom-control-input" ng-model="file.published" id="f{{file.id}}">
|
|
||||||
<label class="custom-control-label" for="f{{file.id}}">Publié aux équipes</label>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
Dépend de la validation de :
|
|
||||||
<span ng-if="!file.depends">aucun flag</span>
|
|
||||||
<ul ng-if="file.depends">
|
|
||||||
<dependancy ng-repeat="dep in file.depends" dep="dep" deleteDep="deleteFileDep"></dependancy>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="card border-info" ng-controller="ExerciceHintsController">
|
|
||||||
<div class="card-header bg-info text-light" type="button" data-toggle="collapse" data-target="#collapseHints" aria-expanded="true" aria-controls="collapseHints">
|
|
||||||
<h4 class="m-0"><small class="glyphicon glyphicon-chevron-right" aria-hidden="true"></small> Indices</h4>
|
|
||||||
</div>
|
|
||||||
<div class="collapse" id="collapseHints" aria-labelledby="headingHints" data-parent="#accordionExercice">
|
|
||||||
<div class="list-group">
|
|
||||||
<div class="btn-toolbar bg-info justify-content-end" role="toolbar">
|
|
||||||
<div class="btn-group" role="group">
|
|
||||||
<button type="button" ng-click="syncHints()" class="btn btn-sm btn-light"><span class="glyphicon glyphicon-refresh" aria-hidden="true"></span> Synchroniser</button>
|
|
||||||
<button type="button" ng-click="addHint()" class="btn btn-sm btn-primary"><span class="glyphicon glyphicon-plus" aria-hidden="true"></span> Ajouter</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<form ng-submit="saveHint()" class="list-group-item form-horizontal bg-light text-dark" ng-repeat="hint in hints">
|
|
||||||
<input type="text" id="htitle{{hint.id}}" ng-model="hint.title" class="form-control form-control-sm" placeholder="Titre">
|
|
||||||
<textarea class="form-control form-control-sm" id="hcnt{{hint.id}}" ng-model="hint.content" ng-if="!hint.file"></textarea>
|
|
||||||
<p ng-if="hint.file">
|
|
||||||
Fichier : <samp>{{ hint.file }}</samp><br>
|
|
||||||
Hash : <samp class="cksum">{{ hint.content }}</samp><br>
|
|
||||||
</p>
|
|
||||||
<div class="row">
|
|
||||||
<div class="col-8 input-group input-group-sm mb-3">
|
|
||||||
<div class="input-group-prepend">
|
|
||||||
<span class="input-group-text" id="inputGroup-sizing-sm">Coût</span>
|
|
||||||
</div>
|
|
||||||
<input type="text" id="hcost{{hint.id}}" ng-model="hint.cost" class="form-control" integer>
|
|
||||||
</div>
|
|
||||||
<div class="col-4">
|
|
||||||
<button type="submit" class="btn btn-sm btn-success"><span class="glyphicon glyphicon-ok" aria-hidden="true"></span></button>
|
|
||||||
<button type="button" ng-click="deleteHint()" class="btn btn-sm btn-danger" ng-show="hint.id"><span class="glyphicon glyphicon-remove" aria-hidden="true"></span></button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div ng-controller="ExerciceHintDepsController" ng-init="init(hint)">
|
|
||||||
Dépendances :
|
|
||||||
<ul ng-if="deps.length > 0">
|
|
||||||
<dependancy ng-repeat="dep in deps" dep="dep"></dependancy>
|
|
||||||
</ul>
|
|
||||||
<span ng-if="deps.length == 0"> sans</span>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="card border-success" ng-controller="ExerciceFlagsController">
|
|
||||||
<div class="card-header bg-success text-light" type="button" data-toggle="collapse" data-target="#collapseFlags" aria-expanded="true" aria-controls="collapseFlags">
|
|
||||||
<h4 class="m-0"><small class="glyphicon glyphicon-chevron-right" aria-hidden="true"></small> Drapeaux</h4>
|
|
||||||
</div>
|
|
||||||
<div class="collapse" id="collapseFlags" aria-labelledby="headingFlags" data-parent="#accordionExercice">
|
|
||||||
<div class="list-group">
|
|
||||||
<div class="btn-toolbar bg-success justify-content-end" role="toolbar">
|
|
||||||
<div class="btn-group" role="group">
|
|
||||||
<button type="button" ng-click="syncFlags()" class="btn btn-sm btn-light"><span class="glyphicon glyphicon-refresh" aria-hidden="true"></span> Synchroniser</button>
|
|
||||||
<button type="button" ng-click="addFlag()" class="btn btn-sm btn-primary"><span class="glyphicon glyphicon-plus" aria-hidden="true"></span> Ajouter</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div ng-repeat="flag in flags" class="list-group-item bg-light text-dark">
|
|
||||||
<form ng-submit="saveFlag()" class="form-horizontal">
|
|
||||||
<div class="row" id="flag-{{flag.id}}">
|
|
||||||
<input type="text" id="klabel{{flag.id}}" ng-model="flag.label" class="col form-control form-control-sm" placeholder="Intitulé" title="Intitulé">
|
|
||||||
<div class="col-auto" ng-show="flag.id">
|
|
||||||
<button type="button" ng-click="deleteFlag()" class="btn btn-sm btn-danger"><span class="glyphicon glyphicon-remove" aria-hidden="true"></span></button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="row">
|
|
||||||
<input type="text" id="kplaceholder{{flag.id}}" ng-model="flag.placeholder" class="col form-control form-control-sm" placeholder="Indication de formatage" title="Indication de formatage">
|
|
||||||
<div class="col-auto">
|
|
||||||
<button type="submit" class="btn btn-sm btn-success"><span class="glyphicon glyphicon-ok" aria-hidden="true"></span></button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="row">
|
|
||||||
<div class="col input-group" ng-if="flag.id && !flag.show_raw" style="padding:0">
|
|
||||||
<input type="text" id="kvalue{{flag.id}}" ng-model="flag.value" class="form-control form-control-sm" placeholder="Condensat" title="Condensat">
|
|
||||||
<div class="input-group-append">
|
|
||||||
<button class="btn btn-sm btn-outline-secondary" ng-click="changeValue(flag)" title="Cliquez pour éditer la valeur brute du flag, au lieu du checksum" type="button"><span class="glyphicon glyphicon-erase" aria-hidden="true"></span></button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<input type="text" id="kflag{{flag.id}}" ng-model="flag.flag" class="col form-control form-control-sm" placeholder="Chaîne brute à valider" ng-if="!flag.id || flag.show_raw" title="Chaîne brute à valider">
|
|
||||||
<div class="col-auto custom-control custom-checkbox ml-1">
|
|
||||||
<input type="checkbox" class="custom-control-input" id="kicase{{flag.id}}" ng-model="flag.ignorecase">
|
|
||||||
<label class="custom-control-label" for="kicase{{flag.id}}">Ignore case</label>
|
|
||||||
</div>
|
|
||||||
<div class="col-auto custom-control custom-checkbox ml-1">
|
|
||||||
<input type="checkbox" class="custom-control-input" id="kmline{{flag.id}}" ng-model="flag.multiline">
|
|
||||||
<label class="custom-control-label" for="kmline{{flag.id}}">Multiline</label>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="row">
|
|
||||||
<input type="text" id="kvre{{flag.id}}" ng-model="flag.capture_regexp" class="col form-control form-control-sm" placeholder="Regexp selecting validation string" title="Regexp selecting validation string">
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
<form ng-submit="testFlag(flag)">
|
|
||||||
<div class="input-group row">
|
|
||||||
<input type="text" id="ktest{{flag.id}}" ng-model="flag.test_str" class="form-control form-control-sm" placeholder="Test the flag">
|
|
||||||
<div class="input-group-append">
|
|
||||||
<button class="btn btn-sm btn-warning" type="submit"><span class="glyphicon glyphicon-play" aria-hidden="true"></span></button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div ng-controller="ExerciceFlagDepsController" ng-init="init(flag)">
|
|
||||||
Dépendances :
|
|
||||||
<ul ng-if="deps.length > 0">
|
|
||||||
<dependancy ng-repeat="dep in deps" dep="dep"></dependancy>
|
|
||||||
</ul>
|
|
||||||
<span ng-if="deps.length == 0"> sans</span>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="card border-success" ng-controller="ExerciceMCQFlagsController">
|
|
||||||
<div class="card-header bg-success text-light" type="button" data-toggle="collapse" data-target="#collapseQuizz" aria-expanded="true" aria-controls="collapseQuizz">
|
|
||||||
<h4 class="m-0"><small class="glyphicon glyphicon-chevron-right" aria-hidden="true"></small> Quizz</h4>
|
|
||||||
</div>
|
|
||||||
<div class="collapse" id="collapseQuizz" aria-labelledby="headingQuizz" data-parent="#accordionExercice">
|
|
||||||
<div class="list-group">
|
|
||||||
<div class="btn-toolbar bg-success justify-content-end" role="toolbar">
|
|
||||||
<div class="btn-group" role="group">
|
|
||||||
<button type="button" ng-click="addQuiz()" class="btn btn-sm btn-primary"><span class="glyphicon glyphicon-plus" aria-hidden="true"></span> Ajouter</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<form ng-submit="saveQuiz()" class="list-group-item form-horizontal bg-light text-dark" ng-repeat="(qk,q) in quiz">
|
|
||||||
<div class="form-group row" id="quiz-{{q.id}}">
|
|
||||||
<input type="text" id="qlabel{{q.id}}" ng-model="q.title" class="col form-control" placeholder="Intitulé">
|
|
||||||
<div class="col-auto" ng-show="q.id">
|
|
||||||
<button type="button" ng-click="deleteQuiz()" class="btn btn-danger"><span class="glyphicon glyphicon-remove" aria-hidden="true"></span></button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="form-group row" ng-repeat="(ck,choice) in q.entries">
|
|
||||||
<div class="col form-check">
|
|
||||||
<label class="custom-control custom-checkbox">
|
|
||||||
<input class="custom-control-input" type="checkbox" ng-model="choice.response">
|
|
||||||
<span class="custom-control-label">
|
|
||||||
<input type="text" id="kchoice{{q.id}}-{{choice.id}}" ng-model="choice.label" class="form-control form-control-sm" placeholder="Intitulé">
|
|
||||||
</span>
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
<div class="col-auto">
|
|
||||||
<button type="button" ng-click="deleteChoice()" class="btn btn-sm btn-danger"><span class="glyphicon glyphicon-trash" aria-hidden="true"></span></button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="form-group row">
|
|
||||||
<div class="col-auto">
|
|
||||||
<button type="button" ng-click="addChoice()" class="btn btn-primary"><span class="glyphicon glyphicon-plus" aria-hidden="true"></span> Ajouter un choix</button>
|
|
||||||
</div>
|
|
||||||
<div class="col-auto">
|
|
||||||
<button type="submit" class="btn btn-success"><span class="glyphicon glyphicon-ok" aria-hidden="true"></span></button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div ng-controller="ExerciceMCQDepsController" ng-init="init(q)">
|
|
||||||
Dépendances :
|
|
||||||
<ul ng-if="deps.length > 0">
|
|
||||||
<dependancy ng-repeat="dep in deps" dep="dep"></dependancy>
|
|
||||||
</ul>
|
|
||||||
<span ng-if="deps.length == 0"> sans</span>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="card border-warning" ng-controller="ExerciceTagsController">
|
|
||||||
<div class="card-header bg-warning text-light" type="button" data-toggle="collapse" data-target="#collapseTags" aria-expanded="true" aria-controls="collapseTags">
|
|
||||||
<h4 class="m-0"><small class="glyphicon glyphicon-chevron-right" aria-hidden="true"></small> Tags</h4>
|
|
||||||
</div>
|
|
||||||
<div class="collapse" id="collapseTags" aria-labelledby="headingTags" data-parent="#accordionExercice">
|
|
||||||
<div class="list-group">
|
|
||||||
<div class="btn-toolbar bg-warning justify-content-end" role="toolbar">
|
|
||||||
<div class="btn-group" role="group">
|
|
||||||
<button type="button" ng-click="addTag()" class="btn btn-sm btn-primary"><span class="glyphicon glyphicon-plus" aria-hidden="true"></span> Ajouter</button>
|
|
||||||
<button type="button" ng-click="saveTags()" class="btn btn-sm btn-success"><span class="glyphicon glyphicon-ok" aria-hidden="true"></span> Sauvegarder</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<form ng-submit="saveTags()" class="list-group-item bg-light text-dark">
|
|
||||||
<div class="row form-group" ng-repeat="(k, tag) in tags track by $index">
|
|
||||||
<input type="text" ng-model="tags[k]" class="col form-control form-control-sm" placeholder="#tag">
|
|
||||||
<button type="button" ng-click="deleteTag()" class="btn btn-sm btn-danger col-auto"><span class="glyphicon glyphicon-remove" aria-hidden="true"></span></button>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="mt-2" style="overflow-y: scroll; height: 450px" id="exercicehistory" ng-controller="ExerciceHistoryController">
|
|
||||||
<div class="d-flex justify-content-between align-items-start">
|
|
||||||
<h3>Historique</h3>
|
|
||||||
<div class="btn-group" role="group">
|
|
||||||
<button type="button" class="btn btn-sm btn-dark" data-toggle="modal" data-target="#appendHistoryModal" ng-show="logged"><span class="glyphicon glyphicon-plus" aria-hidden="true"></span></button>
|
|
||||||
<button type="button" class="btn btn-sm btn-primary" ng-click="refreshHistory()"><span class="glyphicon glyphicon-refresh" aria-hidden="true"></span></button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<table class="table table-hover table-striped table-bordered bg-primary text-light">
|
|
||||||
<tbody>
|
|
||||||
<tr ng-repeat="row in history" ng-class="{'bg-ffound': row.kind == 'flag_found', 'bg-mfound': row.kind == 'mcq_found', 'bg-wchoices': row.kind == 'wchoices', 'bg-success': row.kind == 'solved', 'bg-info': row.kind == 'hint', 'bg-warning': row.kind == 'tries'}">
|
|
||||||
<td>
|
|
||||||
<nobr title="{{ row.time }}">{{ row.time | date:"mediumTime" }}</nobr><br>{{ row.kind }} <span ng-if="row.kind != 'flag_found' && row.kind != 'tries' && row.kind != 'mcq_found'">x{{ row.coefficient }}</span>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<span ng-if="row.team_id">
|
|
||||||
<a href="teams/{{ row.team_id }}">{{ row.team_name }}</a>
|
|
||||||
</span>
|
|
||||||
<span ng-if="row.secondary_title">
|
|
||||||
:
|
|
||||||
<a href="exercices/{{ row.primary }}#key-{{ row.secondary }}" ng-if="row.kind == 'flag_found' || row.kind == 'wchoices'">{{ row.secondary_title }}</a>
|
|
||||||
<a href="exercices/{{ row.primary }}#quizz-{{ row.secondary }}" ng-if="row.kind == 'mcq_found'">{{ row.secondary_title }}</a>
|
|
||||||
<a href="exercices/{{ row.primary }}#hint-{{ row.secondary }}" ng-if="row.kind == 'hint'">{{ row.secondary_title }}</a>
|
|
||||||
</span>
|
|
||||||
<span ng-if="!row.secondary_title && row.secondary && row.kind != 'solved' && row.kind != 'tries'">: {{ row.secondary }}</span>
|
|
||||||
<span ng-if="!row.secondary_title && row.secondary && row.kind == 'tries'" ng-controller="SearchTryController"><br><span ng-repeat="line in tr.details"><span ng-if="!$first">, </span>{{ line.kind }}<span ng-if="line.related">#{{ line.related }}</span></span></span>
|
|
||||||
</td>
|
|
||||||
<td style="vertical-align: middle; padding: 0; background-color: {{ row.team_color }}" ng-show="logged">
|
|
||||||
<button type="button" data-toggle="modal" data-target="#updHistory" ng-if="row.kind != 'flag_found' && row.kind != 'tries' && row.kind != 'mcq_found'" data-idteam="{{ row.team_id }}" data-kind="{{ row.kind }}" data-time="{{ row.time }}" data-secondary="{{ row.secondary }}" data-coeff="{{ row.coefficient }}" class="float-right btn btn-sm btn-info"><span class="glyphicon glyphicon-edit" aria-hidden="true"></span></button>
|
|
||||||
<button type="button" ng-click="delHistory(row)" class="float-right btn btn-sm btn-danger"><span class="glyphicon glyphicon-remove" aria-hidden="true"></span></button>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
|
|
||||||
<div class="modal fade" id="updHistory" tabindex="-1" role="dialog">
|
|
||||||
<div class="modal-dialog" role="document">
|
|
||||||
<div class="modal-content bg-light">
|
|
||||||
<div class="modal-header">
|
|
||||||
<h5 class="modal-title">Édition de l'historique</h5>
|
|
||||||
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
|
|
||||||
<span aria-hidden="true">×</span>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
<div class="modal-body">
|
|
||||||
<form ng-submit="updHistory()">
|
|
||||||
<div class="form-group">
|
|
||||||
<label for="historycoeff">Coefficient</label>
|
|
||||||
<input class="form-control" id="historycoeff">
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
<div class="modal-footer">
|
|
||||||
<button type="button" class="btn btn-secondary" data-dismiss="modal">Annuler</button>
|
|
||||||
<button type="button" class="btn btn-primary" ng-click="updHistory()">Mettre à jour</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
$('#updHistory').on('shown.bs.modal', function (event) {
|
|
||||||
var button = $(event.relatedTarget);
|
|
||||||
var modal = $(this);
|
|
||||||
|
|
||||||
modal.data('idteam', button.data('idteam'));
|
|
||||||
modal.data('kind', button.data('kind'));
|
|
||||||
modal.data('time', button.data('time'));
|
|
||||||
modal.data('secondary', button.data('secondary'));
|
|
||||||
modal.data('coeff', button.data('coeff'));
|
|
||||||
$('#historycoeff').val(button.data('coeff'));
|
|
||||||
$('#historycoeff').focus();
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<div class="modal fade" id="appendHistoryModal" tabindex="-1" role="dialog">
|
|
||||||
<div class="modal-dialog" role="document">
|
|
||||||
<div class="modal-content bg-light">
|
|
||||||
<div class="modal-header">
|
|
||||||
<h5 class="modal-title">Ajout d'une entrée dans l'historique</h5>
|
|
||||||
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
|
|
||||||
<span aria-hidden="true">×</span>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
<form ng-submit="historyAppend()">
|
|
||||||
<div class="modal-body">
|
|
||||||
<div class="form-group row">
|
|
||||||
<label for="labelflag" class="col-md-3 col-form-label">Exercice</label>
|
|
||||||
<div class="col-md-9">
|
|
||||||
<input type="text" readonly class="form-control-plaintext" value="{{ exercice.title }}">
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="form-group row">
|
|
||||||
<label for="tteam" class="col-md-3 col-form-label">Équipe</label>
|
|
||||||
<div class="col-md-9" ng-controller="TeamsListController">
|
|
||||||
<select class="custom-select custom-select-sm" id="tteam" ng-model="selectedTeam" ng-options="t.id as t.name for t in teams"></select>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="form-group row">
|
|
||||||
<label for="tteam" class="col-md-3 col-form-label">Type d'événement</label>
|
|
||||||
<div class="col-md-9">
|
|
||||||
<select class="custom-select custom-select-sm" id="historyEvent" ng-model="selectedEvent">
|
|
||||||
<option value="tries">Tentative</option>
|
|
||||||
<option value="hint">Indice dévoilé</option>
|
|
||||||
<option value="wchoices">Liste de choix affichée</option>
|
|
||||||
<option value="flag_found">Flag trouvé</option>
|
|
||||||
<option value="mcq_found">QCM validé</option>
|
|
||||||
<option value="solved">Exercice terminé</option>
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="form-group row" ng-if="selectedEvent == 'hint'">
|
|
||||||
<label for="tteam" class="col-md-3 col-form-label">Indice</label>
|
|
||||||
<div class="col-md-9" ng-controller="ExerciceHintsController">
|
|
||||||
<select class="custom-select custom-select-sm" id="historySecondaryHint" ng-model="selectedSecondaryHint" ng-options="h.id as h.title for h in hints"></select>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="form-group row" ng-if="selectedEvent == 'wchoices' || selectedEvent == 'flag_found'">
|
|
||||||
<label for="tteam" class="col-md-3 col-form-label">Flag</label>
|
|
||||||
<div class="col-md-9" ng-controller="ExerciceFlagsController">
|
|
||||||
<select class="custom-select custom-select-sm" id="historySecondaryFlag" ng-model="selectedSecondaryFlag" ng-options="f.id as f.label for f in flags"></select>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="form-group row" ng-if="selectedEvent == 'mcq_found'">
|
|
||||||
<label for="tteam" class="col-md-3 col-form-label">QCM</label>
|
|
||||||
<div class="col-md-9" ng-controller="ExerciceMCQFlagsController">
|
|
||||||
<select class="custom-select custom-select-sm" id="historySecondaryQuiz" ng-model="selectedSecondaryFlag" ng-options="q.id as q.title for q in quiz"></select>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="modal-footer">
|
|
||||||
<button type="button" class="btn btn-secondary" data-dismiss="modal">Annuler</button>
|
|
||||||
<button type="submit" class="btn btn-primary">Enregistrer</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
$('#appendHistoryModal').on('shown.bs.modal', function (event) {
|
|
||||||
$('#tteam').trigger('focus');
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
|
|
@ -1,12 +0,0 @@
|
||||||
<h1>Accès rapide aux exercices</h1>
|
|
||||||
|
|
||||||
<div ng-repeat="theme in forge_links">
|
|
||||||
<h2>
|
|
||||||
<a ng-href="{{ theme.forge_link }}" target="_blank">{{ theme.name }}</a> : <span class="text-monospace">{{ theme.path }}</span>
|
|
||||||
</h2>
|
|
||||||
<ul>
|
|
||||||
<li ng-repeat="exercice in theme.exercices">
|
|
||||||
<a ng-href="{{ exercice.forge_link }}" target="_blank">{{ exercice.name }}</a> : <span class="text-monospace">{{ exercice.path }}</span>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
Some files were not shown because too many files have changed in this diff Show more
Reference in a new issue