diff --git a/.dockerignore b/.dockerignore deleted file mode 100644 index fedf55d1..00000000 --- a/.dockerignore +++ /dev/null @@ -1,33 +0,0 @@ -admin/admin -checker/checker -dashboard/dashboard -evdist/evdist -generator/generator -receiver/receiver -repochecker/repochecker -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 \ No newline at end of file diff --git a/.drone-manifest-fic-admin.yml b/.drone-manifest-fic-admin.yml deleted file mode 100644 index fc1154f7..00000000 --- a/.drone-manifest-fic-admin.yml +++ /dev/null @@ -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 diff --git a/.drone-manifest-fic-checker.yml b/.drone-manifest-fic-checker.yml deleted file mode 100644 index 13721117..00000000 --- a/.drone-manifest-fic-checker.yml +++ /dev/null @@ -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 diff --git a/.drone-manifest-fic-dashboard.yml b/.drone-manifest-fic-dashboard.yml deleted file mode 100644 index 4f74d234..00000000 --- a/.drone-manifest-fic-dashboard.yml +++ /dev/null @@ -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 diff --git a/.drone-manifest-fic-evdist.yml b/.drone-manifest-fic-evdist.yml deleted file mode 100644 index 70b1e5c5..00000000 --- a/.drone-manifest-fic-evdist.yml +++ /dev/null @@ -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 diff --git a/.drone-manifest-fic-frontend-ui.yml b/.drone-manifest-fic-frontend-ui.yml deleted file mode 100644 index 0fc4a3a6..00000000 --- a/.drone-manifest-fic-frontend-ui.yml +++ /dev/null @@ -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 diff --git a/.drone-manifest-fic-generator.yml b/.drone-manifest-fic-generator.yml deleted file mode 100644 index a2b2443f..00000000 --- a/.drone-manifest-fic-generator.yml +++ /dev/null @@ -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 diff --git a/.drone-manifest-fic-get-remote-files.yml b/.drone-manifest-fic-get-remote-files.yml deleted file mode 100644 index 0f64b8c1..00000000 --- a/.drone-manifest-fic-get-remote-files.yml +++ /dev/null @@ -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 diff --git a/.drone-manifest-fic-nginx.yml b/.drone-manifest-fic-nginx.yml deleted file mode 100644 index cbd2dcce..00000000 --- a/.drone-manifest-fic-nginx.yml +++ /dev/null @@ -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 diff --git a/.drone-manifest-fic-qa.yml b/.drone-manifest-fic-qa.yml deleted file mode 100644 index 5a158970..00000000 --- a/.drone-manifest-fic-qa.yml +++ /dev/null @@ -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 diff --git a/.drone-manifest-fic-receiver.yml b/.drone-manifest-fic-receiver.yml deleted file mode 100644 index eda2fe25..00000000 --- a/.drone-manifest-fic-receiver.yml +++ /dev/null @@ -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 diff --git a/.drone-manifest-fic-repochecker.yml b/.drone-manifest-fic-repochecker.yml deleted file mode 100644 index 9b239931..00000000 --- a/.drone-manifest-fic-repochecker.yml +++ /dev/null @@ -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 diff --git a/.drone-manifest-fickit-deploy.yml b/.drone-manifest-fickit-deploy.yml deleted file mode 100644 index bfa37052..00000000 --- a/.drone-manifest-fickit-deploy.yml +++ /dev/null @@ -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 diff --git a/.drone.yml b/.drone.yml deleted file mode 100644 index 3e0e2cbc..00000000 --- a/.drone.yml +++ /dev/null @@ -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 diff --git a/.gitignore b/.gitignore deleted file mode 100644 index 4327ccd0..00000000 --- a/.gitignore +++ /dev/null @@ -1,43 +0,0 @@ -vendor/ -DASHBOARD/ -FILES/ -PKI/ -REMOTE/ -SETTINGS/ -SETTINGSDIST/ -TEAMS/ -submissions/ -admin/sync/README.html -fickit-boot-cmdline -fickit-boot-initrd.img -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 diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml deleted file mode 100644 index 0e57016f..00000000 --- a/.gitlab-ci.yml +++ /dev/null @@ -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 diff --git a/.gitlab-ci/build.yml b/.gitlab-ci/build.yml deleted file mode 100644 index 621d8bbb..00000000 --- a/.gitlab-ci/build.yml +++ /dev/null @@ -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 diff --git a/.gitlab-ci/image.yml b/.gitlab-ci/image.yml deleted file mode 100644 index 988bb2b0..00000000 --- a/.gitlab-ci/image.yml +++ /dev/null @@ -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 diff --git a/Dockerfile-admin b/Dockerfile-admin deleted file mode 100644 index f2c285f2..00000000 --- a/Dockerfile-admin +++ /dev/null @@ -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 diff --git a/Dockerfile-checker b/Dockerfile-checker deleted file mode 100644 index 144ac0a7..00000000 --- a/Dockerfile-checker +++ /dev/null @@ -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 diff --git a/Dockerfile-dashboard b/Dockerfile-dashboard deleted file mode 100644 index b8291bba..00000000 --- a/Dockerfile-dashboard +++ /dev/null @@ -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/ diff --git a/Dockerfile-deploy b/Dockerfile-deploy deleted file mode 100644 index 99765543..00000000 --- a/Dockerfile-deploy +++ /dev/null @@ -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 \ No newline at end of file diff --git a/Dockerfile-evdist b/Dockerfile-evdist deleted file mode 100644 index 45a2c506..00000000 --- a/Dockerfile-evdist +++ /dev/null @@ -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 diff --git a/Dockerfile-frontend-ui b/Dockerfile-frontend-ui deleted file mode 100644 index a14b0c58..00000000 --- a/Dockerfile-frontend-ui +++ /dev/null @@ -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 diff --git a/Dockerfile-generator b/Dockerfile-generator deleted file mode 100644 index 2574e614..00000000 --- a/Dockerfile-generator +++ /dev/null @@ -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 diff --git a/Dockerfile-get-remote-files b/Dockerfile-get-remote-files deleted file mode 100644 index 95e1c5f3..00000000 --- a/Dockerfile-get-remote-files +++ /dev/null @@ -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 diff --git a/Dockerfile-nginx b/Dockerfile-nginx deleted file mode 100644 index 41326840..00000000 --- a/Dockerfile-nginx +++ /dev/null @@ -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 diff --git a/Dockerfile-qa b/Dockerfile-qa deleted file mode 100644 index 37f3a1b0..00000000 --- a/Dockerfile-qa +++ /dev/null @@ -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 diff --git a/Dockerfile-receiver b/Dockerfile-receiver deleted file mode 100644 index f2cac038..00000000 --- a/Dockerfile-receiver +++ /dev/null @@ -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 diff --git a/Dockerfile-remote-challenge-sync-airbus b/Dockerfile-remote-challenge-sync-airbus deleted file mode 100644 index 47a5e167..00000000 --- a/Dockerfile-remote-challenge-sync-airbus +++ /dev/null @@ -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 diff --git a/Dockerfile-remote-scores-sync-zqds b/Dockerfile-remote-scores-sync-zqds deleted file mode 100644 index e5ff87fb..00000000 --- a/Dockerfile-remote-scores-sync-zqds +++ /dev/null @@ -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 diff --git a/Dockerfile-repochecker b/Dockerfile-repochecker deleted file mode 100644 index d02bc1de..00000000 --- a/Dockerfile-repochecker +++ /dev/null @@ -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 diff --git a/LICENSE b/LICENSE deleted file mode 100644 index 5b7eaadc..00000000 --- a/LICENSE +++ /dev/null @@ -1,21 +0,0 @@ -MIT License - -Copyright (c) 2016-2018 Pierre-Olivier Mercier - -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. diff --git a/README.md b/README.md deleted file mode 100644 index d908ce27..00000000 --- a/README.md +++ /dev/null @@ -1,238 +0,0 @@ -FIC Forensic CTF Platform -========================= - -This is a CTF server for distributing and validating challenges. It is design -to be robust, so it uses some uncommon technics like client certificate for -authentication, lots of state of the art cryptographic methods and aims to be -deployed in a DMZ network architecture. - -## Features - -- **Collaborative Challenge Design and Review:** Facilitates large team collaboration for challenge creation and review. -- **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. -- **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. -- **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. -- **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. -- **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. -- **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. -- **Detailed Statistics:** Provide administrators with insights into exercise preferences and complexity. -- **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. - - -## Overview - -This is a [monorepo](https://danluu.com/monorepo/), containing several -micro-services : - -- `admin` is the web interface and API used to control the challenge - 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: - -![Overview of the micro-services](doc/micro-services.png) - -In the production setup, each micro-service runs in a dedicated -container, isolated from each other. Moreover, two physical machines -should be used: - -- `phobos` communicates with players: displaying the web interface, - 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: - -![Layer 2 connections](doc/l2.png) - -So, the general filesystem is organized this way: - -- `DASHBOARD` contains files structuring the content of the dashboard - screen(s). -- `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): - -![Usage of directories by each micro-service](doc/directories.png) - -Local developer setup ---------------------- - -### Using Docker - -Use `docker-compose build`, then `docker-compose up` to launch the infrastructure. - -After booting, you'll be able to reach the main interface at: - and the admin one at: (or at ). -The dashboard is available at and the QA service at . - -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. - -#### Import folder - -##### Local import folder -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`: - - 23 volumes: - 24 - - ~/fic:/mnt/fic:ro - 24 + - /fic:/mnt/fic:ro - -##### Git import -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 - -##### Owncloud import folder -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 -cloudpass '' - -### Manual builds - -Running this project requires a web server (configuration is given for nginx), -a database (currently supporting only MySQL/MariaDB), a Go compiler for the -revision 1.18 at least and a `inotify`-aware system. You'll also need NodeJS to -compile some user interfaces. - -1. Above all, you need to build Node projects: - - cd frontend/fic; npm install && npm run build - cd qa/ui; npm install && npm run build - -2. First, you'll need to retrieve the dependencies: - - go mod vendor - -2. Then, build the three Go projects: - - go build -o fic-admin ./admin - 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: - - mysql -u root -p <: this is the administration part. - - ./fic-generator & - - This daemon generates static and team related files and then waits - another process to tell it to regenerate some files. - - ./fic-receiver & - - This one exposes an API that gives time synchronization to clients and - handle submission reception (but without treating them). - - ./fic-checker & - - This service waits for new submissions (expected in `submissions` - directory). It only watchs modifications on the file system, it has no web - interface. - - ./fic-dashboard & - - This last server runs the public dashboard. It serves all file, without the - 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 - default, to perform quality control. - -For the moment, a web server is mandatory to serve static files, look -at the samples given in the `configs/nginx` directory. You need to -pick one base configation flavor in the `configs/nginx/base` -directory, and associated with an authentication mechanism in -`configs/nginx/auth` (named the file `fic-auth.conf` in `/etc/nginx`), -and also pick the corresponding `configs/nginx/get-team` file, you -named `fic-get-team.conf`. diff --git a/admin/.gitignore b/admin/.gitignore index 0ccbc66d..e2c3cd5d 100644 --- a/admin/.gitignore +++ b/admin/.gitignore @@ -2,4 +2,3 @@ admin fic.db PKI/ FILES/ -static/full_import_report.json diff --git a/admin/api.go b/admin/api.go new file mode 100644 index 00000000..ec8e80a8 --- /dev/null +++ b/admin/api.go @@ -0,0 +1,102 @@ +package main + +import ( + "encoding/json" + "errors" + "fmt" + "io" + "log" + "net/http" + "strings" +) + +type DispatchFunction func([]string, []byte) (interface{}, error) + +var apiRoutes = map[string]*(map[string]DispatchFunction){ + "version": &ApiVersionRouting, + "ca": &ApiCARouting, + "events": &ApiEventsRouting, + "exercices": &ApiExercicesRouting, + "themes": &ApiThemesRouting, + "teams": &ApiTeamsRouting, +} + +type apiRouting struct{} + +func ApiHandler() http.Handler { + return apiRouting{} +} + +func (a apiRouting) ServeHTTP(w http.ResponseWriter, r *http.Request) { + log.Printf("Handling %s request from %s: %s [%s]\n", r.Method, r.RemoteAddr, r.URL.Path, r.UserAgent()) + + // Extract URL arguments + var sURL = strings.Split(r.URL.Path, "/")[1:] + if len(sURL) > 1 && sURL[len(sURL)-1] == "" { + // Remove trailing / + sURL = sURL[:len(sURL)-1] + } + + w.Header().Set("Content-Type", "application/json") + + var ret interface{} + var err error = nil + + // Read the body + if r.ContentLength < 0 || r.ContentLength > 6553600 { + http.Error(w, fmt.Sprintf("{errmsg:\"Request too large or request size unknown\"}", err), http.StatusRequestEntityTooLarge) + return + } + var body []byte + if r.ContentLength > 0 { + tmp := make([]byte, 1024) + for { + n, err := r.Body.Read(tmp) + for j := 0; j < n; j++ { + body = append(body, tmp[j]) + } + if err != nil || n <= 0 { + break + } + } + } + + // Route request + if len(sURL) > 0 { + if h, ok := apiRoutes[sURL[0]]; ok { + if f, ok := (*h)[r.Method]; ok { + ret, err = f(sURL[1:], body) + } else { + err = errors.New(fmt.Sprintf("Invalid action (%s) provided for %s.", r.Method, sURL[0])) + } + } + } else { + err = errors.New("No action provided.") + } + + // Format response + resStatus := http.StatusOK + if err != nil { + ret = map[string]string{"errmsg": err.Error()} + resStatus = http.StatusBadRequest + log.Println(r.RemoteAddr, resStatus, err.Error()) + } + + if ret == nil { + ret = map[string]string{"errmsg": "Page not found"} + resStatus = http.StatusNotFound + } + + if str, found := ret.(string); found { + w.WriteHeader(resStatus) + io.WriteString(w, str) + } else if bts, found := ret.([]byte); found { + w.WriteHeader(resStatus) + w.Write(bts) + } else if j, err := json.Marshal(ret); err != nil { + http.Error(w, fmt.Sprintf("{\"errmsg\":\"%q\"}", err), http.StatusInternalServerError) + } else { + w.WriteHeader(resStatus) + w.Write(j) + } +} diff --git a/admin/api/certificate.go b/admin/api/certificate.go deleted file mode 100644 index 5c98c116..00000000 --- a/admin/api/certificate.go +++ /dev/null @@ -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) -} diff --git a/admin/api/claim.go b/admin/api/claim.go deleted file mode 100644 index cf545fff..00000000 --- a/admin/api/claim.go +++ /dev/null @@ -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) -} diff --git a/admin/api/events.go b/admin/api/events.go deleted file mode 100644 index 17500535..00000000 --- a/admin/api/events.go +++ /dev/null @@ -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) -} diff --git a/admin/api/exercice.go b/admin/api/exercice.go deleted file mode 100644 index 2c196153..00000000 --- a/admin/api/exercice.go +++ /dev/null @@ -1,1749 +0,0 @@ -package api - -import ( - "bytes" - "fmt" - "log" - "net/http" - "path" - "reflect" - "strconv" - "strings" - "time" - - "srs.epita.fr/fic-server/admin/sync" - "srs.epita.fr/fic-server/libfic" - - "github.com/gin-gonic/gin" -) - -func declareGlobalExercicesRoutes(router *gin.RouterGroup) { - router.GET("/resolutions.json", exportResolutionMovies) - router.GET("/exercices_stats.json", getExercicesStats) - router.GET("/exercices_forge_bindings.json", getExercicesForgeLinks) - router.GET("/tags", listTags) -} - -func declareExercicesRoutes(router *gin.RouterGroup) { - router.GET("/exercices", listExercices) - router.POST("/exercices", createExercice) - - apiExercicesRoutes := router.Group("/exercices/:eid") - apiExercicesRoutes.Use(ExerciceHandler) - apiExercicesRoutes.GET("", showExercice) - apiExercicesRoutes.PUT("", updateExercice) - apiExercicesRoutes.PATCH("", partUpdateExercice) - apiExercicesRoutes.DELETE("", deleteExercice) - - apiExercicesRoutes.POST("/diff-sync", APIDiffExerciceWithRemote) - - apiExercicesRoutes.GET("/history.json", getExerciceHistory) - - apiExercicesRoutes.GET("/stats.json", getExerciceStats) - - apiExercicesRoutes.GET("/tries", listTries) - - apiTriesRoutes := apiExercicesRoutes.Group("/tries/:trid") - apiTriesRoutes.Use(ExerciceTryHandler) - apiTriesRoutes.GET("", getExerciceTry) - apiTriesRoutes.DELETE("", deleteExerciceTry) - - apiHistoryRoutes := apiExercicesRoutes.Group("/history.json") - apiHistoryRoutes.Use(AssigneeCookieHandler) - apiHistoryRoutes.PUT("", appendExerciceHistory) - apiHistoryRoutes.PATCH("", updateExerciceHistory) - apiHistoryRoutes.DELETE("", delExerciceHistory) - - apiExercicesRoutes.GET("/hints", listExerciceHints) - apiExercicesRoutes.POST("/hints", createExerciceHint) - - apiHintsRoutes := apiExercicesRoutes.Group("/hints/:hid") - apiHintsRoutes.Use(HintHandler) - apiHintsRoutes.GET("", showExerciceHint) - apiHintsRoutes.PUT("", updateExerciceHint) - apiHintsRoutes.DELETE("", deleteExerciceHint) - apiHintsRoutes.GET("/dependancies", showExerciceHintDeps) - - apiExercicesRoutes.GET("/flags", listExerciceFlags) - apiExercicesRoutes.POST("/flags", createExerciceFlag) - - apiFlagsRoutes := apiExercicesRoutes.Group("/flags/:kid") - apiFlagsRoutes.Use(FlagKeyHandler) - apiFlagsRoutes.GET("", showExerciceFlag) - apiFlagsRoutes.PUT("", updateExerciceFlag) - apiFlagsRoutes.POST("/try", tryExerciceFlag) - apiFlagsRoutes.DELETE("/", deleteExerciceFlag) - apiFlagsRoutes.GET("/dependancies", showExerciceFlagDeps) - apiFlagsRoutes.GET("/statistics", showExerciceFlagStats) - apiFlagsRoutes.DELETE("/tries", deleteExerciceFlagTries) - apiFlagsRoutes.GET("/choices/", listFlagChoices) - apiFlagsChoicesRoutes := apiExercicesRoutes.Group("/choices/:cid") - apiFlagsChoicesRoutes.Use(FlagChoiceHandler) - apiFlagsChoicesRoutes.GET("", showFlagChoice) - apiFlagsRoutes.POST("/choices/", createFlagChoice) - apiFlagsChoicesRoutes.PUT("", updateFlagChoice) - apiFlagsChoicesRoutes.DELETE("", deleteFlagChoice) - - apiQuizRoutes := apiExercicesRoutes.Group("/quiz/:qid") - apiQuizRoutes.Use(FlagQuizHandler) - apiExercicesRoutes.GET("/quiz", listExerciceQuiz) - apiQuizRoutes.GET("", showExerciceQuiz) - apiQuizRoutes.PUT("", updateExerciceQuiz) - apiQuizRoutes.DELETE("", deleteExerciceQuiz) - apiQuizRoutes.GET("/dependancies", showExerciceQuizDeps) - apiQuizRoutes.GET("/statistics", showExerciceQuizStats) - apiQuizRoutes.DELETE("/tries", deleteExerciceQuizTries) - - apiExercicesRoutes.GET("/tags", listExerciceTags) - apiExercicesRoutes.POST("/tags", addExerciceTag) - apiExercicesRoutes.PUT("/tags", updateExerciceTags) - - declareFilesRoutes(apiExercicesRoutes) - declareExerciceClaimsRoutes(apiExercicesRoutes) - - // Remote - router.GET("/remote/themes/:thid/exercices/:exid", sync.ApiGetRemoteExercice) - router.GET("/remote/themes/:thid/exercices/:exid/flags", sync.ApiGetRemoteExerciceFlags) - router.GET("/remote/themes/:thid/exercices/:exid/hints", sync.ApiGetRemoteExerciceHints) -} - -type Exercice struct { - *fic.Exercice - ForgeLink string `json:"forge_link,omitempty"` -} - -func ExerciceHandler(c *gin.Context) { - eid, err := strconv.ParseInt(string(c.Params.ByName("eid")), 10, 32) - if err != nil { - c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"errmsg": "Invalid exercice identifier"}) - return - } - - var exercice *fic.Exercice - if theme, exists := c.Get("theme"); exists { - exercice, err = theme.(*fic.Theme).GetExercice(int(eid)) - if err != nil { - c.AbortWithStatusJSON(http.StatusNotFound, gin.H{"errmsg": "Exercice not found"}) - return - } - } else { - exercice, err = fic.GetExercice(eid) - if err != nil { - c.AbortWithStatusJSON(http.StatusNotFound, gin.H{"errmsg": "Exercice not found"}) - return - } - - if exercice.IdTheme != nil { - theme, err = exercice.GetTheme() - if err != nil { - c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": "Unable to find the attached theme."}) - return - } - - c.Set("theme", theme) - } else { - c.Set("theme", &fic.StandaloneExercicesTheme) - } - } - - c.Set("exercice", exercice) - - c.Next() -} - -func HintHandler(c *gin.Context) { - hid, err := strconv.ParseInt(string(c.Params.ByName("hid")), 10, 32) - if err != nil { - c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"errmsg": "Invalid hint identifier"}) - return - } - - exercice := c.MustGet("exercice").(*fic.Exercice) - hint, err := exercice.GetHint(hid) - if err != nil { - c.AbortWithStatusJSON(http.StatusNotFound, gin.H{"errmsg": "Hint not found"}) - return - } - - c.Set("hint", hint) - - c.Next() -} - -func FlagKeyHandler(c *gin.Context) { - kid, err := strconv.ParseInt(string(c.Params.ByName("kid")), 10, 32) - if err != nil { - c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"errmsg": "Invalid flag identifier"}) - return - } - - var flag *fic.FlagKey - if exercice, exists := c.Get("exercice"); exists { - flag, err = exercice.(*fic.Exercice).GetFlagKey(int(kid)) - if err != nil { - c.AbortWithStatusJSON(http.StatusNotFound, gin.H{"errmsg": "Flag not found"}) - return - } - } else { - flag, err = fic.GetFlagKey(int(kid)) - if err != nil { - c.AbortWithStatusJSON(http.StatusNotFound, gin.H{"errmsg": "Flag not found"}) - return - } - } - - c.Set("flag-key", flag) - - c.Next() -} - -func FlagChoiceHandler(c *gin.Context) { - cid, err := strconv.ParseInt(string(c.Params.ByName("cid")), 10, 32) - if err != nil { - c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"errmsg": "Invalid choice identifier"}) - return - } - - flagkey := c.MustGet("flag-key").(*fic.FlagKey) - choice, err := flagkey.GetChoice(int(cid)) - if err != nil { - c.AbortWithStatusJSON(http.StatusNotFound, gin.H{"errmsg": "Choice not found"}) - return - } - - c.Set("flag-choice", choice) - - c.Next() -} - -func FlagQuizHandler(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 quiz identifier"}) - return - } - - var quiz *fic.MCQ - if exercice, exists := c.Get("exercice"); exists { - quiz, err = exercice.(*fic.Exercice).GetMCQById(int(qid)) - if err != nil { - c.AbortWithStatusJSON(http.StatusNotFound, gin.H{"errmsg": "Quiz not found"}) - return - } - } else { - quiz, err = fic.GetMCQ(int(qid)) - if err != nil { - c.AbortWithStatusJSON(http.StatusNotFound, gin.H{"errmsg": "Quiz not found"}) - return - } - } - - c.Set("flag-quiz", quiz) - - c.Next() -} - -func listExercices(c *gin.Context) { - if theme, exists := c.Get("theme"); exists { - exercices, err := theme.(*fic.Theme).GetExercices() - if err != nil { - log.Println("Unable to listThemedExercices:", err.Error()) - c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": "An error occurs during exercices listing."}) - return - } - - c.JSON(http.StatusOK, exercices) - } else { - exercices, err := fic.GetExercices() - if err != nil { - log.Println("Unable to listThemedExercices:", err.Error()) - c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": "An error occurs during exercices listing."}) - return - } - - c.JSON(http.StatusOK, exercices) - } -} - -func listTags(c *gin.Context) { - exercices, err := fic.GetExercices() - if err != nil { - c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": err.Error()}) - return - } - - ret := map[string][]*fic.Exercice{} - for _, exercice := range exercices { - tags, err := exercice.GetTags() - if err != nil { - c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": err.Error()}) - return - } - for _, t := range tags { - if _, ok := ret[t]; !ok { - ret[t] = []*fic.Exercice{} - } - - ret[t] = append(ret[t], exercice) - } - } - - c.JSON(http.StatusOK, ret) -} - -// Generate the csv to export with: -// -// curl -s http://127.0.0.1:8081/api/resolutions.json | jq -r ".[] | [ .theme,.level,.title, @uri \"https://fic.srs.epita.fr/$(date +%Y)/\\(.videoURI)\" ] | join(\";\")" -func exportResolutionMovies(c *gin.Context) { - exercices, err := fic.GetExercices() - if err != nil { - c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": err.Error()}) - return - } - - export := []map[string]string{} - for _, exercice := range exercices { - var tname string - if exercice.IdTheme != nil { - theme, err := fic.GetTheme(*exercice.IdTheme) - if err != nil { - c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": err.Error()}) - return - } - tname = theme.Name - } - if len(exercice.VideoURI) > 0 { - level, _ := exercice.GetLevel() - export = append(export, map[string]string{ - "videoURI": strings.Replace(exercice.VideoURI, "$FILES$/", "files/", 1), - "theme": tname, - "title": exercice.Title, - "level": fmt.Sprintf("%d", level), - }) - } - } - - c.JSON(http.StatusOK, export) -} - -func loadFlags(n func() ([]fic.Flag, error)) (interface{}, error) { - if flags, err := n(); err != nil { - return nil, err - } else { - var ret []fic.Flag - - for _, flag := range flags { - if f, ok := flag.(*fic.FlagKey); ok { - if k, err := fic.GetFlagKey(f.Id); err != nil { - return nil, err - } else { - ret = append(ret, k) - } - } else if f, ok := flag.(*fic.MCQ); ok { - if m, err := fic.GetMCQ(f.Id); err != nil { - return nil, err - } else { - ret = append(ret, m) - } - } else { - return nil, fmt.Errorf("Flag type %T not implemented for this flag.", f) - } - } - - return ret, nil - } -} - -func listExerciceHints(c *gin.Context) { - exercice := c.MustGet("exercice").(*fic.Exercice) - - hints, err := exercice.GetHints() - if err != nil { - log.Println("Unable to listExerciceHints:", err.Error()) - c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": "An error occurs when retrieving hints"}) - return - } - - c.JSON(http.StatusOK, hints) -} - -func listExerciceFlags(c *gin.Context) { - exercice := c.MustGet("exercice").(*fic.Exercice) - - flags, err := exercice.GetFlagKeys() - if err != nil { - log.Println("Unable to listExerciceFlags:", err.Error()) - c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": "An error occurs when retrieving exercice flags"}) - return - } - - c.JSON(http.StatusOK, flags) -} - -func listFlagChoices(c *gin.Context) { - flag := c.MustGet("flag-key").(*fic.FlagKey) - - choices, err := flag.GetChoices() - if err != nil { - log.Println("Unable to listFlagChoices:", err.Error()) - c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": "An error occurs when retrieving flag choices"}) - return - } - - c.JSON(http.StatusOK, choices) -} - -func listExerciceQuiz(c *gin.Context) { - exercice := c.MustGet("exercice").(*fic.Exercice) - - quiz, err := exercice.GetMCQ() - if err != nil { - log.Println("Unable to listExerciceQuiz:", err.Error()) - c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": "An error occurs when retrieving quiz list"}) - return - } - - c.JSON(http.StatusOK, quiz) -} - -func showExercice(c *gin.Context) { - exercice := c.MustGet("exercice").(*fic.Exercice) - - var forgelink string - if fli, ok := sync.GlobalImporter.(sync.ForgeLinkedImporter); ok { - if u, _ := fli.GetExerciceLink(exercice); u != nil { - forgelink = u.String() - } - } - - c.JSON(http.StatusOK, Exercice{exercice, forgelink}) -} - -func getExerciceHistory(c *gin.Context) { - exercice := c.MustGet("exercice").(*fic.Exercice) - - history, err := exercice.GetHistory() - if err != nil { - log.Println("Unable to getExerciceHistory:", err.Error()) - c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": "An error occurs when retrieving exercice history"}) - return - } - - c.JSON(http.StatusOK, history) -} - -type exerciceStats struct { - IdExercice int64 `json:"id_exercice,omitempty"` - TeamTries int64 `json:"team_tries"` - TotalTries int64 `json:"total_tries"` - SolvedCount int64 `json:"solved_count"` - FlagSolved []int64 `json:"flag_solved"` - MCQSolved []int64 `json:"mcq_solved"` - CurrentGain int64 `json:"current_gain"` -} - -func getExerciceStats(c *gin.Context) { - e := c.MustGet("exercice").(*fic.Exercice) - - current_gain := e.Gain - if fic.DiscountedFactor > 0 { - decoted_exercice, err := fic.GetDiscountedExercice(e.Id) - if err == nil { - current_gain = decoted_exercice.Gain - } else { - log.Println("Unable to fetch decotedExercice:", err.Error()) - } - } - - c.JSON(http.StatusOK, exerciceStats{ - TeamTries: e.TriedTeamCount(), - TotalTries: e.TriedCount(), - SolvedCount: e.SolvedCount(), - FlagSolved: e.FlagSolved(), - MCQSolved: e.MCQSolved(), - CurrentGain: current_gain, - }) -} - -func getExercicesStats(c *gin.Context) { - exercices, err := fic.GetDiscountedExercices() - if err != nil { - c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": 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(), - CurrentGain: e.Gain, - }) - } - - c.JSON(http.StatusOK, ret) -} - -type themeForgeBinding struct { - ThemeName string `json:"name"` - ThemePath string `json:"path"` - ForgeLink string `json:"forge_link"` - Exercices []exerciceForgeBinding `json:"exercices"` -} - -type exerciceForgeBinding struct { - ExerciceName string `json:"name"` - ExercicePath string `json:"path"` - ForgeLink string `json:"forge_link"` -} - -func getExercicesForgeLinks(c *gin.Context) { - themes, err := fic.GetThemesExtended() - if err != nil { - log.Println("Unable to listThemes:", err.Error()) - c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": "An error occurs during themes listing."}) - return - } - - fli, ok := sync.GlobalImporter.(sync.ForgeLinkedImporter) - if !ok { - c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"errmsg": "Current importer is not compatible with ForgeLinkedImporter"}) - return - } - - ret := []themeForgeBinding{} - for _, theme := range themes { - exercices, err := theme.GetExercices() - if err != nil { - log.Println("Unable to listExercices:", err.Error()) - c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": "An error occurs during exercice listing."}) - return - } - - var exlinks []exerciceForgeBinding - for _, exercice := range exercices { - var forgelink string - if u, _ := fli.GetExerciceLink(exercice); u != nil { - forgelink = u.String() - } - - exlinks = append(exlinks, exerciceForgeBinding{ - ExerciceName: exercice.Title, - ExercicePath: exercice.Path, - ForgeLink: forgelink, - }) - } - - var forgelink string - if u, _ := fli.GetThemeLink(theme); u != nil { - forgelink = u.String() - } - ret = append(ret, themeForgeBinding{ - ThemeName: theme.Name, - ThemePath: theme.Path, - ForgeLink: forgelink, - Exercices: exlinks, - }) - } - - c.JSON(http.StatusOK, ret) -} - -func AssigneeCookieHandler(c *gin.Context) { - myassignee, err := c.Cookie("myassignee") - if err != nil { - c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"errmsg": "You must be authenticated to perform this action."}) - return - } - - aid, err := strconv.ParseInt(myassignee, 10, 32) - if err != nil { - c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"errmsg": "You must be authenticated to perform this action: invalid assignee identifier."}) - return - } - - assignee, err := fic.GetAssignee(aid) - if err != nil { - c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"errmsg": "You must be authenticated to perform this action: assignee not found."}) - return - } - - c.Set("assignee", assignee) - - c.Next() -} - -type uploadedExerciceHistory struct { - IdTeam int64 `json:"team_id"` - Kind string - Time time.Time - Secondary *int64 - Coeff float32 -} - -func appendExerciceHistory(c *gin.Context) { - exercice := c.MustGet("exercice").(*fic.Exercice) - myassignee := c.MustGet("assignee").(*fic.ClaimAssignee) - - var uh uploadedExerciceHistory - err := c.ShouldBindJSON(&uh) - if err != nil { - c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"errmsg": err.Error()}) - return - } - - err = exercice.AppendHistoryItem(uh.IdTeam, uh.Kind, uh.Secondary) - if err != nil { - log.Println("Unable to appendExerciceHistory:", err.Error()) - c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": "An error occurs during history moditication."}) - return - } - log.Printf("AUDIT: %s performs an history append: %s for team %d, exercice %d and optional %v", myassignee.Name, uh.Kind, uh.IdTeam, exercice.Id, uh.Secondary) - - c.JSON(http.StatusOK, uh) -} - -func updateExerciceHistory(c *gin.Context) { - exercice := c.MustGet("exercice").(*fic.Exercice) - myassignee := c.MustGet("assignee").(*fic.ClaimAssignee) - - var uh uploadedExerciceHistory - err := c.ShouldBindJSON(&uh) - if err != nil { - c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"errmsg": err.Error()}) - return - } - - _, err = exercice.UpdateHistoryItem(uh.Coeff, uh.IdTeam, uh.Kind, uh.Time, uh.Secondary) - if err != nil { - log.Println("Unable to updateExerciceHistory:", err.Error()) - c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": "An error occurs during history update."}) - return - } - log.Printf("AUDIT: %s performs an history update: %s for team %d, exercice %d and optional %v, with coeff %f", myassignee.Name, uh.Kind, uh.IdTeam, exercice.Id, uh.Secondary, uh.Coeff) - - c.JSON(http.StatusOK, uh) -} - -func delExerciceHistory(c *gin.Context) { - exercice := c.MustGet("exercice").(*fic.Exercice) - myassignee := c.MustGet("assignee").(*fic.ClaimAssignee) - - var uh uploadedExerciceHistory - err := c.ShouldBindJSON(&uh) - if err != nil { - c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"errmsg": err.Error()}) - return - } - - _, err = exercice.DelHistoryItem(uh.IdTeam, uh.Kind, uh.Time, uh.Secondary) - if err != nil { - log.Println("Unable to delExerciceHistory:", err.Error()) - c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": "An error occurs during history deletion."}) - return - } - log.Printf("AUDIT: %s performs an history deletion: %s for team %d, exercice %d and optional %v", myassignee.Name, uh.Kind, uh.IdTeam, exercice.Id, uh.Secondary) - - c.JSON(http.StatusOK, true) -} - -func deleteExercice(c *gin.Context) { - exercice := c.MustGet("exercice").(*fic.Exercice) - - _, err := exercice.DeleteCascade() - if err != nil { - log.Println("Unable to deleteExercice:", err.Error()) - c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"errmsg": "An error occurs during exercice deletion"}) - return - } - - c.JSON(http.StatusOK, true) -} - -func updateExercice(c *gin.Context) { - exercice := c.MustGet("exercice").(*fic.Exercice) - - var ue fic.Exercice - err := c.ShouldBindJSON(&ue) - if err != nil { - c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"errmsg": err.Error()}) - return - } - - ue.Id = exercice.Id - - if len(ue.Title) == 0 { - c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"errmsg": "Exercice's title not filled"}) - return - } - - if _, err := ue.Update(); err != nil { - log.Println("Unable to updateExercice:", err.Error()) - c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"errmsg": "An error occurs during exercice update"}) - return - } - - c.JSON(http.StatusOK, ue) -} - -type patchExercice struct { - Language *string `json:"lang,omitempty"` - Title *string `json:"title"` - Disabled *bool `json:"disabled"` - WIP *bool `json:"wip"` - URLId *string `json:"urlid"` - Statement *string `json:"statement"` - Overview *string `json:"overview"` - Headline *string `json:"headline"` - Finished *string `json:"finished"` - Issue *string `json:"issue"` - IssueKind *string `json:"issuekind"` - Gain *int64 `json:"gain"` - Coefficient *float64 `json:"coefficient"` -} - -func partUpdateExercice(c *gin.Context) { - exercice := c.MustGet("exercice").(*fic.Exercice) - - var ue patchExercice - err := c.ShouldBindJSON(&ue) - if err != nil { - c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"errmsg": err.Error()}) - return - } - - for _, field := range reflect.VisibleFields(reflect.TypeOf(ue)) { - if !reflect.ValueOf(ue).FieldByName(field.Name).IsNil() { - reflect.ValueOf(exercice).Elem().FieldByName(field.Name).Set(reflect.ValueOf(reflect.ValueOf(ue).FieldByName(field.Name).Elem().Interface())) - } - } - - if _, err := exercice.Update(); err != nil { - log.Println("Unable to partUpdateExercice:", err.Error()) - c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": "An error occurs during exercice update."}) - return - } - - c.JSON(http.StatusOK, exercice) -} - -func createExercice(c *gin.Context) { - theme := c.MustGet("theme").(*fic.Theme) - - // Create a new exercice - var ue fic.Exercice - err := c.ShouldBindJSON(&ue) - if err != nil { - c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"errmsg": err.Error()}) - return - } - - if len(ue.Title) == 0 { - c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"errmsg": "Title not filled"}) - return - } - - var depend *fic.Exercice = nil - if ue.Depend != nil { - if d, err := fic.GetExercice(*ue.Depend); err != nil { - log.Println("Unable to createExercice:", err.Error()) - c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": "An error occurs during exercice creation."}) - return - } else { - depend = d - } - } - - exercice, err := theme.AddExercice(ue.Title, ue.Authors, ue.Image, ue.BackgroundColor, ue.WIP, ue.URLId, ue.Path, ue.Statement, ue.Overview, ue.Headline, depend, ue.Gain, ue.VideoURI, ue.Resolution, ue.SeeAlso, ue.Finished) - if err != nil { - log.Println("Unable to createExercice:", err.Error()) - c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": "An error occurs during exercice creation."}) - return - } - - c.JSON(http.StatusOK, exercice) -} - -type uploadedHint struct { - Title string - Path string - Content string - Cost int64 - URI string -} - -func createExerciceHint(c *gin.Context) { - exercice := c.MustGet("exercice").(*fic.Exercice) - - var uh uploadedHint - err := c.ShouldBindJSON(&uh) - if err != nil { - c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"errmsg": err.Error()}) - return - } - - if len(uh.Content) != 0 { - hint, err := exercice.AddHint(uh.Title, uh.Content, uh.Cost) - if err != nil { - log.Println("Unable to AddHint in createExerciceHint:", err.Error()) - c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": "An error occurs when trying to add hint."}) - return - } - - c.JSON(http.StatusOK, hint) - } else if len(uh.URI) != 0 { - hint, err := sync.ImportFile(sync.GlobalImporter, uh.URI, - func(filePath string, origin string) (interface{}, error) { - return exercice.AddHint(uh.Title, "$FILES"+strings.TrimPrefix(filePath, fic.FilesDir), uh.Cost) - }) - - if err != nil { - log.Println("Unable to AddHint (after ImportFile) in createExerciceHint:", err.Error()) - c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": "An error occurs when trying to add hint."}) - return - } - - c.JSON(http.StatusOK, hint) - } else { - c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"errmsg": "Hint's content not filled"}) - return - } -} - -func showExerciceHint(c *gin.Context) { - c.JSON(http.StatusOK, c.MustGet("hint").(*fic.EHint)) -} - -func showExerciceHintDeps(c *gin.Context) { - hint := c.MustGet("hint").(*fic.EHint) - - deps, err := loadFlags(hint.GetDepends) - if err != nil { - log.Println("Unable to loaddeps:", err.Error()) - c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": "An error occurs when trying to retrieve hint dependencies."}) - return - } - - c.JSON(http.StatusOK, deps) -} - -func updateExerciceHint(c *gin.Context) { - hint := c.MustGet("hint").(*fic.EHint) - - var uh fic.EHint - err := c.ShouldBindJSON(&uh) - if err != nil { - c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"errmsg": err.Error()}) - return - } - - uh.Id = hint.Id - - if len(uh.Title) == 0 { - c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"errmsg": "Hint's title not filled"}) - return - } - - if _, err := uh.Update(); err != nil { - log.Println("Unable to updateExerciceHint:", err.Error()) - c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": "An error occurs when trying to update hint."}) - return - } - - c.JSON(http.StatusOK, uh) -} - -func deleteExerciceHint(c *gin.Context) { - hint := c.MustGet("hint").(*fic.EHint) - - _, err := hint.Delete() - if err != nil { - log.Println("Unable to deleteExerciceHint:", err.Error()) - c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": "An error occurs when trying to delete hint."}) - return - } - - c.JSON(http.StatusOK, true) -} - -type uploadedFlag struct { - Type string - Label string - Placeholder string - Help string - IgnoreCase bool - Multiline bool - NoTrim bool - CaptureRe *string `json:"capture_regexp"` - SortReGroups bool `json:"sort_re_grps"` - Flag string - Value []byte - ChoicesCost int32 `json:"choices_cost"` - BonusGain int32 `json:"bonus_gain"` -} - -func createExerciceFlag(c *gin.Context) { - var uk uploadedFlag - err := c.ShouldBindJSON(&uk) - if err != nil { - c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"errmsg": err.Error()}) - return - } - - if len(uk.Flag) == 0 { - c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"errmsg": "Flag not filled"}) - return - } - - var vre *string = nil - if uk.CaptureRe != nil && len(*uk.CaptureRe) > 0 { - vre = uk.CaptureRe - } - - exercice := c.MustGet("exercice").(*fic.Exercice) - - flag, err := exercice.AddRawFlagKey(uk.Label, uk.Type, uk.Placeholder, uk.IgnoreCase, uk.NoTrim, uk.Multiline, vre, uk.SortReGroups, []byte(uk.Flag), uk.ChoicesCost, uk.BonusGain) - if err != nil { - log.Println("Unable to createExerciceFlag:", err.Error()) - c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": "An error occurs when trying to create flag."}) - return - } - - c.JSON(http.StatusOK, flag) -} - -func showExerciceFlag(c *gin.Context) { - c.JSON(http.StatusOK, c.MustGet("flag-key").(*fic.FlagKey)) -} - -func showExerciceFlagDeps(c *gin.Context) { - flag := c.MustGet("flag-key").(*fic.FlagKey) - - deps, err := loadFlags(flag.GetDepends) - if err != nil { - log.Println("Unable to loaddeps:", err.Error()) - c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": "An error occurs when trying to retrieve hint dependencies."}) - return - } - - c.JSON(http.StatusOK, deps) -} - -func showExerciceFlagStats(c *gin.Context) { - exercice := c.MustGet("exercice").(*fic.Exercice) - flag := c.MustGet("flag-key").(*fic.FlagKey) - - history, err := exercice.GetHistory() - if err != nil { - log.Println("Unable to getExerciceHistory:", err.Error()) - c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": "An error occurs when retrieving exercice history"}) - return - } - - var completed int64 - - for _, hline := range history { - if hline["kind"].(string) == "flag_found" { - if int(*hline["secondary"].(*int64)) == flag.Id { - completed += 1 - } - } - } - - tries, err := flag.NbTries() - if err != nil { - log.Println("Unable to nbTries:", err.Error()) - c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": "An error occurs when retrieving flag tries"}) - return - } - - teams, err := flag.TeamsOnIt() - if err != nil { - log.Println("Unable to teamsOnIt:", err.Error()) - c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": "An error occurs when retrieving flag related teams"}) - return - } - - c.JSON(http.StatusOK, gin.H{ - "completed": completed, - "tries": tries, - "teams": teams, - }) -} - -func deleteExerciceFlagTries(c *gin.Context) { - flag := c.MustGet("flag-key").(*fic.FlagKey) - - err := flag.DeleteTries() - if err != nil { - c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"errmsg": err.Error()}) - return - } - - c.AbortWithStatusJSON(http.StatusOK, true) -} - -func tryExerciceFlag(c *gin.Context) { - flag := c.MustGet("flag-key").(*fic.FlagKey) - - var uk uploadedFlag - err := c.ShouldBindJSON(&uk) - if err != nil { - c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"errmsg": err.Error()}) - return - } - - if len(uk.Flag) == 0 { - c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"errmsg": "Empty submission"}) - return - } - - if flag.Check([]byte(uk.Flag)) == 0 { - c.AbortWithStatusJSON(http.StatusOK, true) - return - } - - c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"errmsg": "Bad submission"}) -} - -func updateExerciceFlag(c *gin.Context) { - flag := c.MustGet("flag-key").(*fic.FlagKey) - - var uk uploadedFlag - err := c.ShouldBindJSON(&uk) - if err != nil { - c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"errmsg": err.Error()}) - return - } - - if len(uk.Label) == 0 { - flag.Label = "Flag" - } else { - flag.Label = uk.Label - } - - flag.Placeholder = uk.Placeholder - flag.Help = uk.Help - flag.IgnoreCase = uk.IgnoreCase - flag.Multiline = uk.Multiline - flag.ChoicesCost = uk.ChoicesCost - flag.BonusGain = uk.BonusGain - - if uk.CaptureRe != nil && len(*uk.CaptureRe) > 0 { - if flag.CaptureRegexp != uk.CaptureRe && uk.Flag == "" { - c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"errmsg": "Pour changer la capture_regexp, vous devez rentrer la réponse attendue à nouveau, car le flag doit être recalculé."}) - return - } - flag.CaptureRegexp = uk.CaptureRe - } else { - if flag.CaptureRegexp != nil && uk.Flag == "" { - c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"errmsg": "Pour changer la capture_regexp, vous devez rentrer la réponse attendue à nouveau, car le flag doit être recalculé."}) - return - } - flag.CaptureRegexp = nil - } - - if len(uk.Flag) > 0 { - var err error - flag.Checksum, err = flag.ComputeChecksum([]byte(uk.Flag)) - if err != nil { - log.Println("Unable to ComputeChecksum:", err.Error()) - c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": "Unable to compute flag checksum"}) - return - } - } else { - flag.Checksum = uk.Value - } - - if _, err := flag.Update(); err != nil { - log.Println("Unable to updateExerciceFlag:", err.Error()) - c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": "An error occurs when trying to update flag."}) - return - } - - c.JSON(http.StatusOK, flag) -} - -func deleteExerciceFlag(c *gin.Context) { - flag := c.MustGet("flag-key").(*fic.FlagKey) - - _, err := flag.Delete() - if err != nil { - log.Println("Unable to deleteExerciceFlag:", err.Error()) - c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": "An error occurs when trying to delete flag."}) - return - } - - c.JSON(http.StatusOK, true) -} - -func createFlagChoice(c *gin.Context) { - flag := c.MustGet("flag-key").(*fic.FlagKey) - - var uc fic.FlagChoice - err := c.ShouldBindJSON(&uc) - if err != nil { - c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"errmsg": err.Error()}) - return - } - - if len(uc.Label) == 0 { - uc.Label = uc.Value - } - - choice, err := flag.AddChoice(&uc) - if err != nil { - log.Println("Unable to createFlagChoice:", err.Error()) - c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": "Unable to create flag choice."}) - return - } - - c.JSON(http.StatusOK, choice) -} - -func showFlagChoice(c *gin.Context) { - c.JSON(http.StatusOK, c.MustGet("flag-choice").(*fic.FlagChoice)) -} - -func updateFlagChoice(c *gin.Context) { - choice := c.MustGet("flag-choice").(*fic.FlagChoice) - - var uc fic.FlagChoice - err := c.ShouldBindJSON(&uc) - if err != nil { - c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"errmsg": err.Error()}) - return - } - - if len(uc.Label) == 0 { - choice.Label = uc.Value - } else { - choice.Label = uc.Label - } - - choice.Value = uc.Value - - if _, err := choice.Update(); err != nil { - log.Println("Unable to updateFlagChoice:", err.Error()) - c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": "Unable to update flag choice."}) - return - } - - c.JSON(http.StatusOK, choice) -} - -func deleteFlagChoice(c *gin.Context) { - choice := c.MustGet("flag-choice").(*fic.FlagChoice) - - _, err := choice.Delete() - if err != nil { - log.Println("Unable to deleteExerciceChoice:", err.Error()) - c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": "An error occurs when trying to delete choice."}) - return - } - - c.JSON(http.StatusOK, true) -} - -func showExerciceQuiz(c *gin.Context) { - c.JSON(http.StatusOK, c.MustGet("flag-quiz").(*fic.MCQ)) -} - -func showExerciceQuizDeps(c *gin.Context) { - quiz := c.MustGet("flag-quiz").(*fic.MCQ) - - deps, err := loadFlags(quiz.GetDepends) - if err != nil { - log.Println("Unable to loaddeps:", err.Error()) - c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": "An error occurs when trying to retrieve quiz dependencies."}) - return - } - - c.JSON(http.StatusOK, deps) -} - -func showExerciceQuizStats(c *gin.Context) { - exercice := c.MustGet("exercice").(*fic.Exercice) - quiz := c.MustGet("flag-quiz").(*fic.MCQ) - - history, err := exercice.GetHistory() - if err != nil { - log.Println("Unable to getExerciceHistory:", err.Error()) - c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": "An error occurs when retrieving exercice history"}) - return - } - - var completed int64 - - for _, hline := range history { - if hline["kind"].(string) == "mcq_found" { - if *hline["secondary"].(*int) == quiz.Id { - completed += 1 - } - } - } - - tries, err := quiz.NbTries() - if err != nil { - log.Println("Unable to nbTries:", err.Error()) - c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": "An error occurs when retrieving flag tries"}) - return - } - - teams, err := quiz.TeamsOnIt() - if err != nil { - log.Println("Unable to teamsOnIt:", err.Error()) - c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": "An error occurs when retrieving flag related teams"}) - return - } - - c.JSON(http.StatusOK, gin.H{ - "completed": completed, - "tries": tries, - "teams": teams, - }) -} - -func deleteExerciceQuizTries(c *gin.Context) { - quiz := c.MustGet("flag-quiz").(*fic.MCQ) - - err := quiz.DeleteTries() - if err != nil { - c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"errmsg": err.Error()}) - return - } - - c.AbortWithStatusJSON(http.StatusOK, true) -} - -func updateExerciceQuiz(c *gin.Context) { - quiz := c.MustGet("flag-quiz").(*fic.MCQ) - - var uq fic.MCQ - err := c.ShouldBindJSON(&uq) - if err != nil { - c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"errmsg": err.Error()}) - return - } - - quiz.Title = uq.Title - - if _, err := quiz.Update(); err != nil { - log.Println("Unable to updateExerciceQuiz:", err.Error()) - c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": "An error occurs when trying to update quiz."}) - return - } - - // Update and remove old entries - var delete []int - for i, cur := range quiz.Entries { - seen := false - for _, next := range uq.Entries { - if cur.Id == next.Id { - seen = true - - if cur.Label != next.Label || cur.Response != next.Response { - cur.Label = next.Label - cur.Response = next.Response - if _, err := cur.Update(); err != nil { - log.Println("Unable to update MCQ entry:", err.Error()) - c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": "Unable to update some MCQ entry"}) - return - } - } - - break - } - } - - if seen == false { - if _, err := cur.Delete(); err != nil { - log.Println("Unable to delete MCQ entry:", err.Error()) - c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": "Unable to delete some MCQ entry"}) - return - } else { - delete = append(delete, i) - } - } - } - for n, i := range delete { - quiz.Entries = append(quiz.Entries[:i-n-1], quiz.Entries[:i-n+1]...) - } - - // Add new choices - for _, choice := range uq.Entries { - if choice.Id == 0 { - if ch, err := quiz.AddEntry(choice); err != nil { - log.Println("Unable to add MCQ entry:", err.Error()) - c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": "Unable to add some MCQ entry"}) - return - } else { - quiz.Entries = append(quiz.Entries, ch) - } - } - } - - c.JSON(http.StatusOK, quiz) -} - -func deleteExerciceQuiz(c *gin.Context) { - quiz := c.MustGet("flag-quiz").(*fic.MCQ) - - for _, choice := range quiz.Entries { - if _, err := choice.Delete(); err != nil { - log.Println("Unable to deleteExerciceQuiz (entry):", err.Error()) - c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": "An error occurs when trying to delete quiz entry."}) - return - } - } - - _, err := quiz.Delete() - if err != nil { - log.Println("Unable to deleteExerciceQuiz:", err.Error()) - c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": "An error occurs when trying to delete quiz."}) - return - } - - c.JSON(http.StatusOK, true) -} - -func listExerciceTags(c *gin.Context) { - exercice := c.MustGet("exercice").(*fic.Exercice) - - tags, err := exercice.GetTags() - if err != nil { - log.Println("Unable to listExerciceTags:", err.Error()) - c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": "An error occurs when trying to get tags."}) - return - } - - c.JSON(http.StatusOK, tags) -} - -func addExerciceTag(c *gin.Context) { - exercice := c.MustGet("exercice").(*fic.Exercice) - - var ut []string - err := c.ShouldBindJSON(&ut) - if err != nil { - c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"errmsg": err.Error()}) - return - } - - // TODO: a DB transaction should be done here: on error we should rollback - for _, t := range ut { - if _, err := exercice.AddTag(t); err != nil { - log.Println("Unable to addExerciceTag:", err.Error()) - c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": "An error occurs when trying to add some tag."}) - return - } - } - - c.JSON(http.StatusOK, ut) -} - -func updateExerciceTags(c *gin.Context) { - exercice := c.MustGet("exercice").(*fic.Exercice) - - exercice.WipeTags() - addExerciceTag(c) -} - -type syncDiff struct { - Field string `json:"field"` - Link string `json:"link"` - Before interface{} `json:"be"` - After interface{} `json:"af"` -} - -func diffExerciceWithRemote(exercice *fic.Exercice, theme *fic.Theme) ([]syncDiff, error) { - var diffs []syncDiff - - // Compare exercice attributes - thid := exercice.Path[:strings.Index(exercice.Path, "/")] - exid := exercice.Path[strings.Index(exercice.Path, "/")+1:] - exercice_remote, err := sync.GetRemoteExercice(thid, exid, theme) - if err != nil { - return nil, err - } - - for _, field := range reflect.VisibleFields(reflect.TypeOf(*exercice)) { - if ((field.Name == "Image") && path.Base(reflect.ValueOf(*exercice_remote).FieldByName(field.Name).String()) != path.Base(reflect.ValueOf(*exercice).FieldByName(field.Name).String())) || ((field.Name == "Depend") && (((exercice_remote.Depend == nil || exercice.Depend == nil) && exercice.Depend != exercice_remote.Depend) || (exercice_remote.Depend != nil && exercice.Depend != nil && *exercice.Depend != *exercice_remote.Depend))) || (field.Name != "Image" && field.Name != "Depend" && !reflect.ValueOf(*exercice_remote).FieldByName(field.Name).Equal(reflect.ValueOf(*exercice).FieldByName(field.Name))) { - if !field.IsExported() || field.Name == "Id" || field.Name == "IdTheme" || field.Name == "IssueKind" || field.Name == "Coefficient" || field.Name == "BackgroundColor" { - continue - } - - diffs = append(diffs, syncDiff{ - Field: field.Name, - Link: fmt.Sprintf("exercices/%d", exercice.Id), - Before: reflect.ValueOf(*exercice).FieldByName(field.Name).Interface(), - After: reflect.ValueOf(*exercice_remote).FieldByName(field.Name).Interface(), - }) - } - } - - // Compare files - files, err := exercice.GetFiles() - if err != nil { - return nil, fmt.Errorf("Unable to GetFiles: %w", err) - } - - files_remote, err := sync.GetRemoteExerciceFiles(thid, exid) - if err != nil { - return nil, fmt.Errorf("Unable to GetRemoteFiles: %w", err) - } - - for i, file_remote := range files_remote { - if len(files) <= i { - diffs = append(diffs, syncDiff{ - Field: fmt.Sprintf("files[%d]", i), - Link: fmt.Sprintf("exercices/%d", exercice.Id), - Before: nil, - After: file_remote, - }) - continue - } - - for _, field := range reflect.VisibleFields(reflect.TypeOf(*file_remote)) { - if !field.IsExported() || field.Name == "Id" || field.Name == "IdExercice" { - continue - } - if ((field.Name == "Path") && path.Base(reflect.ValueOf(*file_remote).FieldByName(field.Name).String()) != path.Base(reflect.ValueOf(*files[i]).FieldByName(field.Name).String())) || ((field.Name == "Checksum" || field.Name == "ChecksumShown") && !bytes.Equal(reflect.ValueOf(*file_remote).FieldByName(field.Name).Bytes(), reflect.ValueOf(*files[i]).FieldByName(field.Name).Bytes())) || (field.Name != "Checksum" && field.Name != "ChecksumShown" && field.Name != "Path" && !reflect.ValueOf(*file_remote).FieldByName(field.Name).Equal(reflect.ValueOf(*files[i]).FieldByName(field.Name))) { - diffs = append(diffs, syncDiff{ - Field: fmt.Sprintf("files[%d].%s", i, field.Name), - Link: fmt.Sprintf("exercices/%d", exercice.Id), - Before: reflect.ValueOf(*files[i]).FieldByName(field.Name).Interface(), - After: reflect.ValueOf(*file_remote).FieldByName(field.Name).Interface(), - }) - } - } - } - - // Compare flags - flags, err := exercice.GetFlags() - if err != nil { - return nil, fmt.Errorf("Unable to GetFlags: %w", err) - } - - flags_remote, err := sync.GetRemoteExerciceFlags(thid, exid) - if err != nil { - return nil, fmt.Errorf("Unable to GetRemoteFlags: %w", err) - } - - var flags_not_found []interface{} - var flags_extra_found []interface{} - - for i, flag_remote := range flags_remote { - if key_remote, ok := flag_remote.(*fic.FlagKey); ok { - found := false - - for _, flag := range flags { - if key, ok := flag.(*fic.FlagKey); ok && (key.Label == key_remote.Label || key.Order == key_remote.Order) { - found = true - - // Parse flag label - if len(key.Label) > 3 && key.Label[0] == '%' { - spl := strings.Split(key.Label, "%") - key.Label = strings.Join(spl[2:], "%") - } - - for _, field := range reflect.VisibleFields(reflect.TypeOf(*key_remote)) { - if !field.IsExported() || field.Name == "Id" || field.Name == "IdExercice" { - continue - } - if (field.Name == "Checksum" && !bytes.Equal(key.Checksum, key_remote.Checksum)) || (field.Name == "CaptureRegexp" && ((key.CaptureRegexp == nil || key_remote.CaptureRegexp == nil) && key.CaptureRegexp != key_remote.CaptureRegexp) || (key.CaptureRegexp != nil && key_remote.CaptureRegexp != nil && *key.CaptureRegexp != *key_remote.CaptureRegexp)) || (field.Name != "Checksum" && field.Name != "CaptureRegexp" && !reflect.ValueOf(*key_remote).FieldByName(field.Name).Equal(reflect.ValueOf(*key).FieldByName(field.Name))) { - diffs = append(diffs, syncDiff{ - Field: fmt.Sprintf("flags[%d].%s", i, field.Name), - Link: fmt.Sprintf("exercices/%d/flags#flag-%d", exercice.Id, key.Id), - Before: reflect.ValueOf(*key).FieldByName(field.Name).Interface(), - After: reflect.ValueOf(*key_remote).FieldByName(field.Name).Interface(), - }) - } - } - - break - } - } - - if !found { - flags_not_found = append(flags_not_found, key_remote) - } - } else if mcq_remote, ok := flag_remote.(*fic.MCQ); ok { - found := false - - for _, flag := range flags { - if mcq, ok := flag.(*fic.MCQ); ok && (mcq.Title == mcq_remote.Title || mcq.Order == mcq_remote.Order) { - found = true - - for _, field := range reflect.VisibleFields(reflect.TypeOf(*mcq_remote)) { - if !field.IsExported() || field.Name == "Id" || field.Name == "IdExercice" { - continue - } - if field.Name == "Entries" { - var not_found []*fic.MCQ_entry - var extra_found []*fic.MCQ_entry - - for i, entry_remote := range mcq_remote.Entries { - found := false - - for j, entry := range mcq.Entries { - if entry.Label == entry_remote.Label { - for _, field := range reflect.VisibleFields(reflect.TypeOf(*entry_remote)) { - if field.Name == "Id" { - continue - } - - if !reflect.ValueOf(*entry_remote).FieldByName(field.Name).Equal(reflect.ValueOf(*entry).FieldByName(field.Name)) { - diffs = append(diffs, syncDiff{ - Field: fmt.Sprintf("flags[%d].entries[%d].%s", i, j, field.Name), - Link: fmt.Sprintf("exercices/%d/flags#quiz-%d", exercice.Id, mcq.Id), - Before: reflect.ValueOf(*mcq.Entries[j]).FieldByName(field.Name).Interface(), - After: reflect.ValueOf(*entry_remote).FieldByName(field.Name).Interface(), - }) - } - } - - found = true - break - } - } - - if !found { - not_found = append(not_found, entry_remote) - } - } - - for _, entry := range mcq.Entries { - found := false - for _, entry_remote := range mcq_remote.Entries { - if entry.Label == entry_remote.Label { - found = true - break - } - } - - if !found { - extra_found = append(extra_found, entry) - } - } - - if len(not_found) > 0 || len(extra_found) > 0 { - diffs = append(diffs, syncDiff{ - Field: fmt.Sprintf("flags[%d].entries", i), - Link: fmt.Sprintf("exercices/%d/flags", exercice.Id), - Before: extra_found, - After: not_found, - }) - } - } else if !reflect.ValueOf(*mcq_remote).FieldByName(field.Name).Equal(reflect.ValueOf(*mcq).FieldByName(field.Name)) { - diffs = append(diffs, syncDiff{ - Field: fmt.Sprintf("flags[%d].%s", i, field.Name), - Link: fmt.Sprintf("exercices/%d/flags", exercice.Id), - Before: reflect.ValueOf(*mcq).FieldByName(field.Name).Interface(), - After: reflect.ValueOf(*mcq_remote).FieldByName(field.Name).Interface(), - }) - } - } - - break - } - } - - if !found { - flags_not_found = append(flags_not_found, mcq_remote) - } - } else if label_remote, ok := flag_remote.(*fic.FlagLabel); ok { - found := false - - for _, flag := range flags { - if label, ok := flag.(*fic.FlagLabel); ok && (label.Label == label_remote.Label || label.Order == label_remote.Order) { - found = true - - for _, field := range reflect.VisibleFields(reflect.TypeOf(*label_remote)) { - if !field.IsExported() || field.Name == "Id" || field.Name == "IdExercice" { - continue - } - if !reflect.ValueOf(*label_remote).FieldByName(field.Name).Equal(reflect.ValueOf(*label).FieldByName(field.Name)) { - diffs = append(diffs, syncDiff{ - Field: fmt.Sprintf("flags[%d].%s", i, field.Name), - Link: fmt.Sprintf("exercices/%d/flags#flag-%d", exercice.Id, label.Id), - Before: reflect.ValueOf(*label).FieldByName(field.Name).Interface(), - After: reflect.ValueOf(*label_remote).FieldByName(field.Name).Interface(), - }) - } - } - - break - } - } - - if !found { - flags_not_found = append(flags_not_found, label_remote) - } - } else { - log.Printf("unknown flag type: %T", flag_remote) - } - } - - for _, flag := range flags { - if key, ok := flag.(*fic.FlagKey); ok { - found := false - - for _, flag_remote := range flags_remote { - if key_remote, ok := flag_remote.(*fic.FlagKey); ok && (key.Label == key_remote.Label || key.Order == key_remote.Order) { - found = true - break - } - } - - if !found { - flags_extra_found = append(flags_extra_found, flag) - } - } else if mcq, ok := flag.(*fic.MCQ); ok { - found := false - - for _, flag_remote := range flags_remote { - if mcq_remote, ok := flag_remote.(*fic.MCQ); ok && (mcq.Title == mcq_remote.Title || mcq.Order == mcq_remote.Order) { - found = true - break - } - } - - if !found { - flags_extra_found = append(flags_extra_found, flag) - } - } else if label, ok := flag.(*fic.FlagLabel); ok { - found := false - - for _, flag_remote := range flags_remote { - if label_remote, ok := flag_remote.(*fic.FlagLabel); ok && (label.Label == label_remote.Label || label.Order == label_remote.Order) { - found = true - break - } - } - - if !found { - flags_extra_found = append(flags_extra_found, flag) - } - } - } - - if len(flags_not_found) > 0 || len(flags_extra_found) > 0 { - diffs = append(diffs, syncDiff{ - Field: "flags", - Link: fmt.Sprintf("exercices/%d/flags", exercice.Id), - Before: flags_extra_found, - After: flags_not_found, - }) - } - - // Compare hints - hints, err := exercice.GetHints() - if err != nil { - return nil, fmt.Errorf("Unable to GetHints: %w", err) - } - - hints_remote, err := sync.GetRemoteExerciceHints(thid, exid) - if err != nil { - return nil, fmt.Errorf("Unable to GetRemoteHints: %w", err) - } - - for i, hint_remote := range hints_remote { - hint_remote.Hint.TreatHintContent() - - for _, field := range reflect.VisibleFields(reflect.TypeOf(*hint_remote.Hint)) { - if !field.IsExported() || field.Name == "Id" || field.Name == "IdExercice" { - continue - } - if len(hints) <= i { - diffs = append(diffs, syncDiff{ - Field: fmt.Sprintf("hints[%d].%s", i, field.Name), - Link: fmt.Sprintf("exercices/%d", exercice.Id), - Before: nil, - After: reflect.ValueOf(*hint_remote.Hint).FieldByName(field.Name).Interface(), - }) - } else if !reflect.ValueOf(*hint_remote.Hint).FieldByName(field.Name).Equal(reflect.ValueOf(*hints[i]).FieldByName(field.Name)) { - diffs = append(diffs, syncDiff{ - Field: fmt.Sprintf("hints[%d].%s", i, field.Name), - Link: fmt.Sprintf("exercices/%d", exercice.Id), - Before: reflect.ValueOf(*hints[i]).FieldByName(field.Name).Interface(), - After: reflect.ValueOf(*hint_remote.Hint).FieldByName(field.Name).Interface(), - }) - } - } - } - - return diffs, err -} - -func APIDiffExerciceWithRemote(c *gin.Context) { - theme := c.MustGet("theme").(*fic.Theme) - exercice := c.MustGet("exercice").(*fic.Exercice) - - diffs, err := diffExerciceWithRemote(exercice, theme) - if err != nil { - c.JSON(http.StatusInternalServerError, gin.H{"errmsg": err.Error()}) - return - } - - c.JSON(http.StatusOK, diffs) -} - -func listTries(c *gin.Context) { - exercice := c.MustGet("exercice").(*fic.Exercice) - - tries, err := exercice.TriesList() - if err != nil { - c.JSON(http.StatusInternalServerError, gin.H{"errmsg": err.Error()}) - return - } - - c.JSON(http.StatusOK, tries) -} - -func ExerciceTryHandler(c *gin.Context) { - trid, err := strconv.ParseInt(string(c.Params.ByName("trid")), 10, 32) - if err != nil { - c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"errmsg": "Invalid try identifier"}) - return - } - - exercice := c.MustGet("exercice").(*fic.Exercice) - try, err := exercice.GetTry(trid) - if err != nil { - c.AbortWithStatusJSON(http.StatusNotFound, gin.H{"errmsg": "Try not found"}) - return - } - - c.Set("try", try) - - c.Next() -} - -func getExerciceTry(c *gin.Context) { - try := c.MustGet("try").(*fic.ExerciceTry) - - err := try.FillDetails() - if err != nil { - c.JSON(http.StatusInternalServerError, gin.H{"errmsg": err.Error()}) - return - } - - c.JSON(http.StatusOK, try) -} - -func deleteExerciceTry(c *gin.Context) { - try := c.MustGet("try").(*fic.ExerciceTry) - - _, err := try.Delete() - if err != nil { - c.JSON(http.StatusInternalServerError, gin.H{"errmsg": err.Error()}) - return - } - - c.Status(http.StatusNoContent) -} diff --git a/admin/api/export.go b/admin/api/export.go deleted file mode 100644 index aa24920a..00000000 --- a/admin/api/export.go +++ /dev/null @@ -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() - }) -} diff --git a/admin/api/file.go b/admin/api/file.go deleted file mode 100644 index 6d94776c..00000000 --- a/admin/api/file.go +++ /dev/null @@ -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) -} diff --git a/admin/api/health.go b/admin/api/health.go deleted file mode 100644 index 13d7a0cc..00000000 --- a/admin/api/health.go +++ /dev/null @@ -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)) -} diff --git a/admin/api/monitor.go b/admin/api/monitor.go deleted file mode 100644 index 3d6a9114..00000000 --- a/admin/api/monitor.go +++ /dev/null @@ -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 -} diff --git a/admin/api/password.go b/admin/api/password.go deleted file mode 100644 index d37153e9..00000000 --- a/admin/api/password.go +++ /dev/null @@ -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" . {{ "}}" }} - -
-

- Bienvenue au {{ .Name }} ! -

-
-
-
- -
- -
-
-
- -
- -
- - {{ "{{" }} if .Invalid {{ "}}" }} -
- Identifiants incorrects. -
- {{ "{{" }} end {{ "}}" }} - - - -
- {{ "{{" }} if .BackLink {{ "}}" }} - - {{ "{{" }} end {{ "}}" }} -
- -{{ "{{" }} 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 - } -} diff --git a/admin/api/public.go b/admin/api/public.go deleted file mode 100644 index f2de4ea0..00000000 --- a/admin/api/public.go +++ /dev/null @@ -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) -} diff --git a/admin/api/qa.go b/admin/api/qa.go deleted file mode 100644 index 10bc02c4..00000000 --- a/admin/api/qa.go +++ /dev/null @@ -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) - } -} diff --git a/admin/api/repositories.go b/admin/api/repositories.go deleted file mode 100644 index 9afaea5d..00000000 --- a/admin/api/repositories.go +++ /dev/null @@ -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) - }) - } -} diff --git a/admin/api/router.go b/admin/api/router.go deleted file mode 100644 index 743bc22e..00000000 --- a/admin/api/router.go +++ /dev/null @@ -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) -} diff --git a/admin/api/settings.go b/admin/api/settings.go deleted file mode 100644 index c16d698d..00000000 --- a/admin/api/settings.go +++ /dev/null @@ -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: `

Le challenge forensic vous place dans la peau de spécialistes en investigation numérique. Nous mettons à votre disposition une vingtaine de scénarios différents, dans lesquels vous devrez faire les différentes étapes de la caractérisation d’une réponse à incident proposées.

-

Chaque scénario met en scène un contexte d’entreprise, ayant découvert récemment qu’elle a été victime d’une cyberattaque. Elle vous demande alors de l’aider à caractériser, afin de mieux comprendre la situation, notamment le mode opératoire de l’adversaire, les impacts de la cyberattaque, le périmètre technique compromis, etc. Il faudra parfois aussi l’éclairer sur les premières étapes de la réaction.

`, - Rules: `

Déroulement

-

Pendant toute la durée du challenge, vous aurez accès à tous les scénarios, mais seulement à la première des 5 étapes. Chaque étape supplémentaire est débloquée lorsque vous validez l’intégralité de l’étape précédente. Toutefois, pour dynamiser le challenge toutes les étapes et tous les scénarios seront débloquées pour la dernière heure du challenge.

-

Nous mettons à votre disposition une plateforme sur laquelle vous pourrez obtenir les informations sur le contexte de l’entreprise et, généralement, une série de fichiers qui semblent appropriés pour avancer dans l’investigation.

-

La validation d’une étape se fait sur la plateforme, après avoir analysé les informations fournies, en répondant à des questions plus ou moins précises. Il s’agit le plus souvent des mots-clefs que l’on placerait dans un rapport.

-

Pour vous débloquer ou accélérer votre investigation, vous pouvez accéder à quelques indices, en échange d’une décote sur votre score d’un certain nombre de points préalablement affichés.

-

Calcul des points, bonus, malus et classement

-

Chaque équipe dispose d’un compteur de points dans l’intervalle ]-∞;+∞[ (aux détails techniques près), à partir duquel le classement est établi.

-

Vous perdez des points en dévoilant des indices, en demandant des propositions de réponses en remplacement de certains champs de texte, ou en essayant un trop grand nombre de fois une réponse.

-

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.

-

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, …

-

La seule manière de gagner des points est de valider une étape d’un scénario dans son intégralité. Le nombre de points gagnés dépend de la difficulté théorique de l’étape ainsi que d’éventuels bonus. Un bonus de 10 % est accordé à la première équipe qui valide une étape. D’autres bonus peuvent ponctuer le challenge, détaillé dans la partie suivante.

-

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.

-

Temps forts

-

Le challenge forensic est jalonné de plusieurs temps forts durant lesquels certains calculs détaillés dans la partie précédente peuvent être altérés. L’équipe d’animation du challenge vous avertira environ 15 minutes avant le début de la modification.

-

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.

-

Sans y être limité ou assuré, sachez que durant les précédentes éditions du challenge forensic, nous avons par exemple : doublé les points de défis peu tentés, doublé les points de tous les défis pendant 30 minutes, réduit le coût des indices pendant 15 minutes, etc.

-

-

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 !

`, - YourMission: `

Bienvenue au challenge forensic !

-

Vous voici aujourd'hui dans la peau de spécialistes en investigation numérique. Vous avez à votre disposition une vingtaine de scénarios différents dans lesquels vous devrez faire les différentes étapes de la caractérisation d’une réponse à incident.

-

Chaque scénario est découpé en 5 grandes étapes de difficulté croissante. Un certain nombre de points est attribué à chaque étape, avec un processus de validation automatique.

-

Un classement est établi en temps réel, tenant compte des différents bonus, en fonction du nombre de points de chaque équipe.

`, - }) -} - -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) -} diff --git a/admin/api/sync.go b/admin/api/sync.go deleted file mode 100644 index 582c55e0..00000000 --- a/admin/api/sync.go +++ /dev/null @@ -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) -} diff --git a/admin/api/team.go b/admin/api/team.go deleted file mode 100644 index d5d21992..00000000 --- a/admin/api/team.go +++ /dev/null @@ -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) -} diff --git a/admin/api/theme.go b/admin/api/theme.go deleted file mode 100644 index c1057397..00000000 --- a/admin/api/theme.go +++ /dev/null @@ -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) -} diff --git a/admin/api/version.go b/admin/api/version.go deleted file mode 100644 index 52cb0726..00000000 --- a/admin/api/version.go +++ /dev/null @@ -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}) -} diff --git a/admin/api_certificate.go b/admin/api_certificate.go new file mode 100644 index 00000000..68f6af93 --- /dev/null +++ b/admin/api_certificate.go @@ -0,0 +1,32 @@ +package main + +import ( + "io/ioutil" + "os" + + "srs.epita.fr/fic-server/libfic" +) + +func CertificateAPI(team fic.Team, args []string) (interface{}, error) { + if len(args) == 1 { + if args[0] == "generate" { + return team.GenerateCert(), nil + } else if args[0] == "revoke" { + return team.RevokeCert(), nil + } else { + return nil, nil + } + } else if fd, err := os.Open("../PKI/pkcs/" + team.Name + ".p12"); err == nil { + return ioutil.ReadAll(fd) + } else { + return nil, err + } +} + +var ApiCARouting = map[string]DispatchFunction{ + "GET": genCA, +} + +func genCA(args []string, body []byte) (interface{}, error) { + return fic.GenerateCA(), nil +} diff --git a/admin/api_events.go b/admin/api_events.go new file mode 100644 index 00000000..66958376 --- /dev/null +++ b/admin/api_events.go @@ -0,0 +1,17 @@ +package main + +import ( + "srs.epita.fr/fic-server/libfic" +) + +var ApiEventsRouting = map[string]DispatchFunction{ + "GET": getEvents, +} + +func getEvents(args []string, body []byte) (interface{}, error) { + if evts, err := fic.GetEvents(); err != nil { + return nil, err + } else { + return evts, nil + } +} diff --git a/admin/api_exercice.go b/admin/api_exercice.go new file mode 100644 index 00000000..5dadccc2 --- /dev/null +++ b/admin/api_exercice.go @@ -0,0 +1,144 @@ +package main + +import ( + "encoding/json" + "errors" + "strconv" + + "srs.epita.fr/fic-server/libfic" +) + +var ApiExercicesRouting = map[string]DispatchFunction{ + "GET": listExercice, + "PATCH": updateExercice, + "DELETE": deletionExercice, +} + +func listExercice(args []string, body []byte) (interface{}, error) { + if len(args) == 1 { + if eid, err := strconv.Atoi(string(args[0])); err != nil { + return nil, err + } else { + return fic.GetExercice(int64(eid)) + } + } else { + // List all exercices + return fic.GetExercices() + } +} + +func deletionExercice(args []string, body []byte) (interface{}, error) { + if len(args) == 1 { + if eid, err := strconv.Atoi(string(args[0])); err != nil { + return nil, err + } else if exercice, err := fic.GetExercice(int64(eid)); err != nil { + return nil, err + } else { + return exercice.Delete() + } + } else { + return nil, nil + } +} + +type uploadedExercice struct { + Title string + Statement string + Hint string + Depend *int64 + Gain int + VideoURI string +} + +func updateExercice(args []string, body []byte) (interface{}, error) { + if len(args) == 1 { + if eid, err := strconv.Atoi(string(args[0])); err != nil { + return nil, err + } else if exercice, err := fic.GetExercice(int64(eid)); err != nil { + return nil, err + } else { + // Update an exercice + var ue uploadedExercice + if err := json.Unmarshal(body, &ue); err != nil { + return nil, err + } + + if len(ue.Title) == 0 { + return nil, errors.New("Exercice's title not filled") + } + + if ue.Depend != nil { + if _, err := fic.GetExercice(*ue.Depend); err != nil { + return nil, err + } + } + + exercice.Title = ue.Title + exercice.Statement = ue.Statement + exercice.Hint = ue.Hint + exercice.Depend = ue.Depend + exercice.Gain = int64(ue.Gain) + exercice.VideoURI = ue.VideoURI + + return exercice.Update() + } + } else { + return nil, nil + } +} + +func createExercice(theme fic.Theme, args []string, body []byte) (interface{}, error) { + if len(args) >= 1 { + if eid, err := strconv.Atoi(args[0]); err != nil { + return nil, err + } else if exercice, err := theme.GetExercice(eid); err != nil { + return nil, err + } else { + if args[1] == "files" { + return createExerciceFile(theme, exercice, args[2:], body) + } else if args[1] == "keys" { + return createExerciceKey(theme, exercice, args[2:], body) + } + } + return nil, nil + } else { + // Create a new exercice + var ue uploadedExercice + if err := json.Unmarshal(body, &ue); err != nil { + return nil, err + } + + if len(ue.Title) == 0 { + return nil, errors.New("Title not filled") + } + + var depend *fic.Exercice = nil + if ue.Depend != nil { + if d, err := fic.GetExercice(*ue.Depend); err != nil { + return nil, err + } else { + depend = &d + } + } + + return theme.AddExercice(ue.Title, ue.Statement, ue.Hint, depend, ue.Gain, ue.VideoURI) + } +} + +type uploadedKey struct { + Name string + Key string +} + +func createExerciceKey(theme fic.Theme, exercice fic.Exercice, args []string, body []byte) (interface{}, error) { + var uk uploadedKey + if err := json.Unmarshal(body, &uk); err != nil { + return nil, err + } + + if len(uk.Key) == 0 { + return nil, errors.New("Key not filled") + } + + return exercice.AddRawKey(uk.Name, uk.Key) +} diff --git a/admin/api_file.go b/admin/api_file.go new file mode 100644 index 00000000..f77e6d9a --- /dev/null +++ b/admin/api_file.go @@ -0,0 +1,75 @@ +package main + +import ( + "bufio" + "crypto/sha512" + "encoding/base32" + "encoding/json" + "errors" + "log" + "net/http" + "os" + "path" + "strings" + + "srs.epita.fr/fic-server/libfic" +) + +type uploadedFile struct { + URI string +} + +func createExerciceFile(theme fic.Theme, exercice fic.Exercice, args []string, body []byte) (interface{}, error) { + var uf uploadedFile + if err := json.Unmarshal(body, &uf); err != nil { + return nil, err + } + + if len(uf.URI) == 0 { + return nil, errors.New("URI not filled") + } + + hash := sha512.Sum512([]byte(uf.URI)) + pathname := path.Join(fic.FilesDir, strings.ToLower(base32.StdEncoding.EncodeToString(hash[:])), path.Base(uf.URI)) + + if _, err := os.Stat(pathname); os.IsNotExist(err) { + log.Println("Import file from Cloud:", uf.URI, "=>", pathname) + if err := os.MkdirAll(path.Dir(pathname), 0777); err != nil { + return nil, err + } else if err := getCloudFile(uf.URI, pathname); err != nil { + return nil, err + } + } + + return exercice.ImportFile(pathname, uf.URI) +} + +func getCloudFile(pathname string, dest string) error { + client := http.Client{} + if req, err := http.NewRequest("GET", CloudDAVBase+pathname, nil); err != nil { + return err + } else { + req.SetBasicAuth(CloudUsername, CloudPassword) + if resp, err := client.Do(req); err != nil { + return err + } else { + defer resp.Body.Close() + + if fd, err := os.Create(dest); err != nil { + return err + } else { + defer fd.Close() + + if resp.StatusCode != http.StatusOK { + return errors.New(resp.Status) + } else { + writer := bufio.NewWriter(fd) + reader := bufio.NewReader(resp.Body) + reader.WriteTo(writer) + writer.Flush() + } + } + } + } + return nil +} diff --git a/admin/api_stats.go b/admin/api_stats.go new file mode 100644 index 00000000..887d3b56 --- /dev/null +++ b/admin/api_stats.go @@ -0,0 +1,43 @@ +package main + +import ( + "fmt" + + "srs.epita.fr/fic-server/libfic" +) + +type statsTheme struct { + SolvedByLevel []int `json:"solvedByLevel"` +} + +type stats struct { + Themes map[string]statsTheme `json:"themes"` + TryRank []int64 `json:"tryRank"` +} + +func genStats() (interface{}, error) { + ret := map[string]statsTheme{} + + if themes, err := fic.GetThemes(); err != nil { + return nil, err + } else { + for _, theme := range themes { + if exercices, err := theme.GetExercices(); err != nil { + return nil, err + } else { + exos := map[string]fic.ExportedExercice{} + for _, exercice := range exercices { + exos[fmt.Sprintf("%d", exercice.Id)] = fic.ExportedExercice{ + exercice.Title, + exercice.Gain, + exercice.SolvedCount(), + exercice.TriedTeamCount(), + } + } + ret[fmt.Sprintf("%d", theme.Id)] = statsTheme{} + } + } + + return ret, nil + } +} diff --git a/admin/api_team.go b/admin/api_team.go new file mode 100644 index 00000000..a5ba9a7a --- /dev/null +++ b/admin/api_team.go @@ -0,0 +1,210 @@ +package main + +import ( + "encoding/json" + "fmt" + "strconv" + "strings" + + "srs.epita.fr/fic-server/libfic" +) + +var ApiTeamsRouting = map[string]DispatchFunction{ + "GET": listTeam, + "PUT": creationTeamMembers, + "POST": creationTeam, + "DELETE": deletionTeam, +} + +func nginxGenTeam() (string, error) { + if teams, err := fic.GetTeams(); err != nil { + return "", err + } else { + ret := "" + for _, team := range teams { + ret += fmt.Sprintf(" if ($ssl_client_s_dn ~ \"/C=FR/ST=France/O=Epita/OU=SRS/CN=%s\") { set $team \"%s\"; }\n", team.InitialName, team.InitialName) + } + + return ret, nil + } +} + +func bindingTeams() (string, error) { + if teams, err := fic.GetTeams(); err != nil { + return "", err + } else { + ret := "" + for _, team := range teams { + if members, err := team.GetMembers(); err != nil { + return "", err + } 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, ";")) + } + } + return ret, nil + } +} + +type uploadedTeam struct { + Name string + Color uint32 +} + +type uploadedMember struct { + Firstname string + Lastname string + Nickname string + Company string +} + +func listTeam(args []string, body []byte) (interface{}, error) { + if len(args) >= 2 { + var team *fic.Team + if tid, err := strconv.Atoi(args[0]); err != nil { + if t, err := fic.GetTeamByInitialName(args[0]); err != nil { + return nil, err + } else { + team = &t + } + } else { + if tid == 0 { + team = nil + } else if t, err := fic.GetTeam(tid); err != nil { + return nil, err + } else { + team = &t + } + + } + + if args[1] == "my.json" { + return fic.MyJSONTeam(team, true) + } else if args[1] == "wait.json" { + return fic.MyJSONTeam(team, false) + } else if args[1] == "stats.json" { + if team != nil { + return team.GetStats() + } else { + return fic.GetTeamsStats(nil) + } + } else if args[1] == "tries" { + return fic.GetTries(team, nil) + } else if team != nil && args[1] == "members" { + return team.GetMembers() + } else if args[1] == "certificate" && team != nil { + return CertificateAPI(*team, args[2:]) + } else if team != nil && args[1] == "name" { + return team.Name, nil + } + } else if len(args) == 1 { + if args[0] == "teams.json" { + return fic.ExportTeams() + } else if args[0] == "tries" { + return fic.GetTries(nil, nil) + } else if args[0] == "nginx" { + return nginxGenTeam() + } else if args[0] == "binding" { + return bindingTeams() + } else if tid, err := strconv.Atoi(string(args[0])); err != nil { + return fic.GetTeamByInitialName(args[0]) + } else if team, err := fic.GetTeam(tid); err != nil { + return nil, err + } else { + return team, nil + } + } else if len(args) == 0 { + // List all teams + return fic.GetTeams() + } + return nil, nil +} + +func creationTeam(args []string, body []byte) (interface{}, error) { + if len(args) == 1 { + // List given team + if tid, err := strconv.Atoi(string(args[0])); err != nil { + return nil, err + } else if team, err := fic.GetTeam(tid); err != nil { + return nil, err + } else { + var members []uploadedMember + if err := json.Unmarshal(body, &members); err != nil { + return nil, err + } + + for _, member := range members { + team.AddMember(member.Firstname, member.Lastname, member.Nickname, member.Company) + } + + return team.GetMembers() + } + } else if len(args) == 0 { + // Create a new team + var ut uploadedTeam + if err := json.Unmarshal(body, &ut); err != nil { + return nil, err + } + + return fic.CreateTeam(ut.Name, ut.Color) + } else { + return nil, nil + } +} + +func creationTeamMembers(args []string, body []byte) (interface{}, error) { + if len(args) == 1 { + // List given team + if tid, err := strconv.Atoi(string(args[0])); err != nil { + return nil, err + } else if team, err := fic.GetTeam(tid); err != nil { + return nil, err + } else { + var member uploadedMember + if err := json.Unmarshal(body, &member); err != nil { + return nil, err + } + + team.AddMember(member.Firstname, member.Lastname, member.Nickname, member.Company) + + return team.GetMembers() + } + } else if len(args) == 0 { + // Create a new team + var members []uploadedMember + if err := json.Unmarshal(body, &members); err != nil { + return nil, err + } + + if team, err := fic.CreateTeam("", 0); err != nil { + return nil, err + } else { + for _, member := range members { + if _, err := team.AddMember(member.Firstname, member.Lastname, member.Nickname, member.Company); err != nil { + return nil, err + } + } + + return team, nil + } + } else { + return nil, nil + } +} + +func deletionTeam(args []string, body []byte) (interface{}, error) { + if len(args) == 1 { + if tid, err := strconv.Atoi(string(args[0])); err != nil { + return nil, err + } else if team, err := fic.GetTeam(tid); err != nil { + return nil, err + } else { + return team.Delete() + } + } else { + return nil, nil + } +} diff --git a/admin/api_theme.go b/admin/api_theme.go new file mode 100644 index 00000000..a023b217 --- /dev/null +++ b/admin/api_theme.go @@ -0,0 +1,164 @@ +package main + +import ( + "encoding/json" + "errors" + "fmt" + "strconv" + + "srs.epita.fr/fic-server/libfic" +) + +var ApiThemesRouting = map[string]DispatchFunction{ + "GET": listTheme, + "PATCH": updateTheme, + "POST": creationTheme, + "DELETE": deletionTheme, +} + +func bindingFiles() (string, error) { + if files, err := fic.GetFiles(); err != nil { + return "", err + } else { + ret := "" + for _, file := range files { + ret += fmt.Sprintf("%s;%s\n", file.GetOrigin(), file.Path) + } + return ret, nil + } +} + +func getTheme(args []string) (fic.Theme, error) { + if tid, err := strconv.Atoi(string(args[0])); err != nil { + return fic.Theme{}, err + } else { + return fic.GetTheme(tid) + } +} + +func getExercice(args []string) (fic.Exercice, error) { + if theme, err := getTheme(args); err != nil { + return fic.Exercice{}, err + } else if eid, err := strconv.Atoi(string(args[1])); err != nil { + return fic.Exercice{}, err + } else { + return theme.GetExercice(eid) + } +} + +func listTheme(args []string, body []byte) (interface{}, error) { + if len(args) == 3 { + if e, err := getExercice(args); err != nil { + return nil, err + } else { + if args[2] == "files" { + return e.GetFiles() + } else if args[2] == "keys" { + return e.GetKeys() + } + } + } else if len(args) == 2 { + if args[1] == "exercices" { + if theme, err := getTheme(args); err != nil { + return nil, err + } else { + return theme.GetExercices() + } + } else { + return getExercice(args) + } + } else if len(args) == 1 { + if args[0] == "files-bindings" { + return bindingFiles() + } else if args[0] == "themes.json" { + return fic.ExportThemes() + } else { + return getTheme(args) + } + } else if len(args) == 0 { + // List all themes + return fic.GetThemes() + } + return nil, nil +} + +type uploadedTheme struct { + Name string + Authors string +} + +func creationTheme(args []string, body []byte) (interface{}, error) { + if len(args) >= 1 { + if theme, err := getTheme(args); err != nil { + return nil, err + } else { + return createExercice(theme, args[1:], body) + } + } else if len(args) == 0 { + // Create a new theme + var ut uploadedTheme + if err := json.Unmarshal(body, &ut); err != nil { + return nil, err + } + + if len(ut.Name) == 0 { + return nil, errors.New("Theme's name not filled") + } + + return fic.CreateTheme(ut.Name, ut.Authors) + } else { + return nil, nil + } +} + +func updateTheme(args []string, body []byte) (interface{}, error) { + if len(args) == 2 { + // Update an exercice + var ue fic.Exercice + if err := json.Unmarshal(body, &ue); err != nil { + return nil, err + } + + if len(ue.Title) == 0 { + return nil, errors.New("Exercice's title not filled") + } + + if _, err := ue.Update(); err != nil { + return nil, err + } + + return ue, nil + } else if len(args) == 1 { + // Update a theme + var ut fic.Theme + if err := json.Unmarshal(body, &ut); err != nil { + return nil, err + } + + if len(ut.Name) == 0 { + return nil, errors.New("Theme's name not filled") + } + + return ut.Update() + } else { + return nil, nil + } +} + +func deletionTheme(args []string, body []byte) (interface{}, error) { + if len(args) == 2 { + if exercice, err := getExercice(args); err != nil { + return nil, err + } else { + return exercice.Delete() + } + } else if len(args) == 1 { + if theme, err := getTheme(args); err != nil { + return nil, err + } else { + return theme.Delete() + } + } else { + return nil, nil + } +} diff --git a/admin/api_version.go b/admin/api_version.go new file mode 100644 index 00000000..9b4cbbfb --- /dev/null +++ b/admin/api_version.go @@ -0,0 +1,11 @@ +package main + +import () + +var ApiVersionRouting = map[string]DispatchFunction{ + "GET": showVersion, +} + +func showVersion(args []string, body []byte) (interface{}, error) { + return map[string]interface{}{"version": 0.1}, nil +} diff --git a/admin/app.go b/admin/app.go deleted file mode 100644 index 1ba1910e..00000000 --- a/admin/app.go +++ /dev/null @@ -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) - } -} diff --git a/admin/fill_exercices.sh b/admin/fill_exercices.sh new file mode 100755 index 00000000..9d66f680 --- /dev/null +++ b/admin/fill_exercices.sh @@ -0,0 +1,137 @@ +#!/bin/sh + +BASEURL="http://localhost:8081" +BASEURI="https://srs.epita.fr/owncloud/remote.php/webdav/FIC 2016" +BASEFILE="/files" +CLOUDPASS=fic:'f>t\nV33R|(+?$i*' + +new_theme() { + NAME=`echo $1 | sed 's/"/\\\\"/g'` + AUTHORS=`echo $2 | sed 's/"/\\\\"/g'` + curl -f -s -d "{\"name\": \"$NAME\", \"authors\": \"$AUTHORS\"}" "${BASEURL}/api/themes/" | + grep -Eo '"id":[0-9]+,' | grep -Eo "[0-9]+" +} + +new_exercice() { + THEME="$1" + TITLE=`echo "$2" | sed 's/"/\\\\"/g'` + STATEMENT=`echo "$3" | sed 's/"/\\\\"/g' | sed ':a;N;$!ba;s/\n/
/g'` + HINT=`echo "$4" | sed 's/"/\\\\"/g' | sed ':a;N;$!ba;s/\n/
/g'` + DEPEND="$5" + GAIN="$6" + VIDEO="$7" + + curl -f -s -d "{\"title\": \"$TITLE\", \"statement\": \"$STATEMENT\", \"hint\": \"$HINT\", \"depend\": $DEPEND, \"gain\": $GAIN, \"videoURI\": \"$VIDEO\"}" "${BASEURL}/api/themes/$THEME" | + grep -Eo '"id":[0-9]+,' | grep -Eo "[0-9]+" +} + +new_file() { + THEME="$1" + EXERCICE="$2" + URI="$3" + + curl -f -s -d "{\"URI\": \"${BASEFILE}${URI}\"}" "${BASEURL}/api/themes/$THEME/$EXERCICE/files" | + grep -Eo '"id":[0-9]+,' | grep -Eo "[0-9]+" +} + +new_key() { + THEME="$1" + EXERCICE="$2" + NAME="$3" + KEY=`echo $4 | sed 's/"/\\\\"/g' | sed 's#\\\\#\\\\\\\\#g'` + + curl -f -s -d "{\"name\": \"$NAME\", \"key\": \"$KEY\"}" "${BASEURL}/api/themes/$THEME/$EXERCICE/keys" | + grep -Eo '"id":[0-9]+,' | grep -Eo "[0-9]+" +} + +# Theme +curl -f -s -X PROPFIND -u "${CLOUDPASS}" "${BASEURI}" | xmllint --format - | grep 'd:href' | sed -E 's/^.*>(.*)<.*$/\1/' | sed 1d | tac | while read f; do basename "$f"; done | while read THEME_URI +do + THM_BASEURI="/${THEME_URI}/" + THEME_NAME=$(echo "${THEME_URI}" | sed -E 's/%20/ /g' | sed -E 's/%c3%a9/é/g' | sed -E 's/%c3%a8/è/g') + THEME_AUTHORS=$(curl -f -s -u "${CLOUDPASS}" "${BASEURI}${THM_BASEURI}/AUTHORS.txt" | sed 's/$/,/' | xargs) + THEME_ID=`new_theme "$THEME_NAME" "$THEME_AUTHORS"` + if [ -z "$THEME_ID" ]; then + echo -e "\e[31;01m!!! An error occured during theme add\e[00m" + continue + else + echo -e "\e[33m>>> New theme created:\e[00m $THEME_ID - $THEME_NAME" + fi + + LAST=null + EXO_NUM=0 + curl -f -s -X PROPFIND -u "${CLOUDPASS}" "${BASEURI}${THM_BASEURI}" | xmllint --format - | grep 'd:href' | sed -E 's/^.*>(.*)<.*$/\1/' | sed -E 's/%20/ /g' | sed -E 's/%c3%a9/é/g' | sed -E 's/%c3%a8/è/g' | sed 1d | while read f; do basename "$f"; done | while read EXO_NAME + do + if ! echo $EXO_NAME | grep Exercice > /dev/null + then + continue + fi + + EXO_NUM=$((EXO_NUM + 1)) + echo + echo -e "\e[36m--- Filling exercice ${EXO_NUM} in theme ${THEME_NAME}\e[00m" + + EXO_BASEURI="${EXO_NAME}/" + + FILES=$(curl -f -s -X PROPFIND -u "${CLOUDPASS}" "${BASEURI}${THM_BASEURI}${EXO_BASEURI}" | xmllint --format - | grep 'd:href' | sed -E 's/^.*>(.*)<.*$/\1/' | sed 1d | while read f; do basename $f; done) + + EXO_VIDEO=$(echo "$FILES" | grep -E "\.(mov|mkv|mp4|avi|flv|ogv|webm)$" | tail -1) + + if [ "${LAST}" = "null" ]; then + echo ">>> Assuming this exercice has no dependency" + else + echo ">>> Assuming this exercice depends on the last entry (id=${LAST})" + fi + + EXO_GAIN=$((3 * (2 ** $EXO_NUM) - 1)) + echo ">>> Using default gain: ${EXO_GAIN} points" + + EXO_DESC=$(curl -f -s -u "${CLOUDPASS}" "${BASEURI}${THM_BASEURI}${EXO_BASEURI}/description.txt") + EXO_HINT=$(curl -f -s -u "${CLOUDPASS}" "${BASEURI}${THM_BASEURI}${EXO_BASEURI}/hint.txt") + + EXO_ID=`new_exercice "${THEME_ID}" "${EXO_NAME}" "${EXO_DESC}" "${EXO_HINT}" "${LAST}" "${EXO_GAIN}" "${THM_BASEURI}${EXO_BASEURI}${EXO_VIDEO}"` + if [ -z "$EXO_ID" ]; then + echo -e "\e[31;01m!!! An error occured during exercice add.\e[00m" + continue + else + echo -e "\e[32m>>> New exercice created:\e[00m $EXO_ID - $EXO_NAME" + fi + + + # Keys + curl -f -s -u "${CLOUDPASS}" "${BASEURI}${THM_BASEURI}${EXO_BASEURI}/keys.txt" | while read KEYLINE + do + KEY_NAME=$(echo "$KEYLINE" | cut -d : -f 1) + KEY_RAW=$(echo "$KEYLINE" | cut -d : -f 2-) + + if [ -z "${KEY_NAME}" ]; then + KEY_NAME="Flag" + fi + + KEY_ID=`new_key "${THEME_ID}" "${EXO_ID}" "${KEY_NAME}" "${KEY_RAW}"` + if [ -z "$KEY_ID" ]; then + echo -e "\e[31;01m!!! An error occured during key import!\e[00m (name=${KEYNAME};raw=${KEY_RAW})" + else + echo -e "\e[32m>>> New key added:\e[00m $KEY_ID - $KEY_NAME" + fi + done + + + # Files + for f in $FILES; do echo $f; done | grep -vEi "(ressources|readme|description.txt|hint.txt|keys.txt|${EXO_VIDEO})" | + while read FBASE + do + echo "Import file ${BASEURI}${THM_BASEURI}${EXO_BASEURI}${FBASE}" + FILE_ID=`new_file "${THEME_ID}" "${EXO_ID}" "${THM_BASEURI}${EXO_BASEURI}${FBASE}"` + if [ -z "$FILE_ID" ]; then + echo -e "\e[31;01m!!! An error occured during file import! Please check path.\e[00m" + else + echo -e "\e[32m>>> New file added:\e[00m $FILE_ID - $FBASE" + fi + done + + + LAST=$EXO_ID + done + echo +done diff --git a/admin/fill_teams.sh b/admin/fill_teams.sh index 6a1ea7a5..92931112 100755 --- a/admin/fill_teams.sh +++ b/admin/fill_teams.sh @@ -1,51 +1,7 @@ -#!/bin/bash +#!/bin/sh -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; } +BASEURL="http://localhost:8081" +PART_FILE="Challenge_Liste des participants.csv" 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/' | @@ -54,21 +10,17 @@ new_team() { 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 + N=`echo $line | cut -d " " -f 4` COLOR=$((($R*256 + $G) * 256 + $B)) - curl -s -d "{\"name\": \"$N\",\"color\": $COLOR}" "${BASEURL}/api/teams" + 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) +for i in `seq 12` do TNUM=$(($TNUM + 1)) @@ -76,52 +28,31 @@ do TID=`new_team $TNUM` - if [ "${GEN_CERTS}" -eq 1 ] && ! curl -s -f "${BASEURL}/api/teams/${TID}/certificate" > /dev/null + if ! 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 <> htpasswd.ssha <> htpasswd.apr1 < /dev/null + ) | curl -s -d @- "${BASEURL}/api/teams/${TID}" > /dev/null + + if ! 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 <> htpasswd.ssha <> htpasswd.apr1 < - - - - {{ .title }} - Administration - - - - - - - - - -
-
-
- -
- -
- -
- - - - - - - - - - - - -` diff --git a/admin/main.go b/admin/main.go index 06470166..d1dc8443 100644 --- a/admin/main.go +++ b/admin/main.go @@ -2,332 +2,78 @@ package main import ( "flag" - "io/fs" - "io/ioutil" + "fmt" "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" ) +var PKIDir string +var SubmissionDir string +var BaseURL string +var CloudDAVBase string +var CloudUsername string +var CloudPassword string + 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") + var bind = flag.String("bind", "0.0.0.0:8081", "Bind port/socket") + var dsn = flag.String("dsn", "fic:fic@/fic", "DSN to connect to the MySQL server") + flag.StringVar(&BaseURL, "baseurl", "http://fic.srs.epita.fr/", "URL prepended to each URL") + flag.StringVar(&SubmissionDir, "submission", "./submissions/", "Base directory where save submissions") + flag.StringVar(&PKIDir, "pki", "./pki/", "Base directory where found PKI scripts") + flag.StringVar(&fic.FilesDir, "files", "./FILES/", "Base directory where found challenges files, local part") + flag.StringVar(&CloudDAVBase, "clouddav", "https://srs.epita.fr/owncloud/remote.php/webdav/FIC 2016", + "Base directory where found challenges files, cloud part") + flag.StringVar(&CloudUsername, "clouduser", "fic", "Username used to sync") + flag.StringVar(&CloudPassword, "cloudpass", "", "Password used to sync") flag.Parse() - log.SetPrefix("[admin] ") + log.Prefix("[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 + var staticDir string + var err error 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 staticDir, err = filepath.Abs("./static/"); err != nil { + log.Fatal(err) } if fic.FilesDir, err = filepath.Abs(fic.FilesDir); err != nil { log.Fatal(err) } - if pki.PKIDir, err = filepath.Abs(pki.PKIDir); err != nil { + if PKIDir, err = filepath.Abs(PKIDir); err != nil { log.Fatal(err) } - if api.DashboardDir, err = filepath.Abs(api.DashboardDir); err != nil { + if SubmissionDir, err = filepath.Abs(SubmissionDir); err != nil { log.Fatal(err) } - if api.TeamsDir, err = filepath.Abs(api.TeamsDir); err != nil { + if fic.FilesDir, err = filepath.Abs(fic.FilesDir); 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 { + if err := fic.DBInit(fmt.Sprintf("%s?parseTime=true", *dsn)); err != nil { log.Fatal("Cannot open the database: ", err) } defer fic.DBClose() log.Println("Creating database...") - if err = fic.DBCreate(); err != nil { + 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) + os.Chdir(PKIDir) - // Prepare graceful shutdown - interrupt := make(chan os.Signal, 1) - signal.Notify(interrupt, os.Interrupt, syscall.SIGTERM) + log.Println("Registering handlers...") + mux := http.NewServeMux() + mux.Handle("/api/", http.StripPrefix("/api", ApiHandler())) + mux.Handle("/teams/", StaticHandler(staticDir)) + mux.Handle("/themes/", StaticHandler(staticDir)) + mux.Handle("/", http.FileServer(http.Dir(staticDir))) - app := NewApp(config, baseURL, bind) - go app.Start() - - // Wait shutdown signal - <-interrupt - - log.Print("The service is shutting down...") - app.Stop() - log.Println("done") + log.Println(fmt.Sprintf("Ready, listening on %s", *bind)) + if err := http.ListenAndServe(*bind, mux); err != nil { + log.Fatal("Unable to listen and serve: ", err) + } } diff --git a/admin/pki/CA.sh b/admin/pki/CA.sh new file mode 100755 index 00000000..dd861959 --- /dev/null +++ b/admin/pki/CA.sh @@ -0,0 +1,284 @@ +#!/bin/bash + +cd $(dirname "$0") + +if [ -z "${PKI_BASEDIR}" ]; then + PKI_BASEDIR=$(dirname `pwd`) # equivalent to $(realpath `pwd`/.. +fi + +PKI_DIR=${PKI_BASEDIR}/PKI +SHARED_DIR=${PKI_DIR}/shared +OPENSSL_CONF=`pwd`/openssl.cnf + +CAKEY=${PKI_DIR}/private/cakey.key +CAREQ=${PKI_DIR}/careq.csr +CACRT=${SHARED_DIR}/cacert.crt +CADER=${SHARED_DIR}/cacert.der + +SRVKEY=${SHARED_DIR}/server.key +SRVREQ=${SHARED_DIR}/server.csr +SRVCRT=${SHARED_DIR}/server.crt + +# Generate certificates valid for: +DAYS=2 +STARTDATE=160125000000Z +ENDDATE=160126235959Z +VALIDITY="-startdate ${STARTDATE} -enddate ${ENDDATE}" +#VALIDITY="-days ${DAYS}" + +if [ -z "$PS1" ] +then + GREEN="\033[1;32m" + RED="\033[1;31m" + COLOR_RST="\033[0m" + BOLD="" + END_BOLD="" + ECHO_OPTS="-e" +else + GREEN="" + RED="" + COLOR_RST="" + BOLD="" + END_BOLD="" + ECHO_OPTS="" +fi + +usage() +{ + echo "Usage: $0 (-newca|-newserver IP/URL|-revokeserver|-newclient NAME|-revoke NAME|-gencrl)" + exit 1 +} + +clean() +{ + if [ "$1" = "ca" ]; then + rm -rf ${PKI_DIR}/* ${SHARED_DIR}/* + mkdir -p ${PKI_DIR}/certs ${PKI_DIR}/crl ${PKI_DIR}/newcerts \ + ${PKI_DIR}/private ${PKI_DIR}/pkcs ${SHARED_DIR} + echo "01" > ${PKI_DIR}/crlnumber + elif [ "$1" = "client" ]; then + rm -rf ${PKI_DIR}/${2}.key ${PKI_DIR}/${2}.csr + fi + rm -rf $OUTPUT +} + +gen_crl() +{ + echo $ECHO_OPTS "${GREEN}Generate shared/crl.pem${COLOR_RST}" + if ! openssl ca -config ${OPENSSL_CONF} -gencrl -out ${SHARED_DIR}/crl.pem > $OUTPUT 2>&1 + then + echo $ECHO_OPTS "${RED}Generate shared/crl.pem failed" + cat $OUTPUT + exit 5 + fi +} + +[ $# -lt 1 ] && usage + +OUTPUT=$(mktemp) + +case $1 in + "-newca" ) + echo $ECHO_OPTS "${GREEN}Create the directories, take care this will delete the old directories ${COLOR_RST}" + + clean "ca" + touch ${PKI_DIR}/index.txt + + ESCAPED=$(echo "${PKI_DIR}" | sed 's/[\/\.]/\\&/g') + + echo $ECHO_OPTS "${GREEN}Making CA key and csr${COLOR_RST}" + sed -i 's/=.*#COMMONNAME/= FIC CA #COMMONNAME/' $OPENSSL_CONF + sed -i "s/=.*#DIR/= ${ESCAPED} #DIR/" $OPENSSL_CONF + sed -i "s/=.*#DAYS/= ${DAYS} #DAYS/" $OPENSSL_CONF + + type pwgen > /dev/null + if [ $? -ne 0 ]; then + echo "command not found: pwgen" + exit 5 + fi + + pass=`pwgen -n -B -y 12 1` + if ! openssl req -batch -new -keyout ${CAKEY} \ + -out ${CAREQ} -passout pass:$pass \ + -config ${OPENSSL_CONF} -extensions CORE_CA > $OUTPUT 2>&1 + then + cat $OUTPUT + clean "ca" + exit 4 + fi + + # This line deleted the passphase for the FIC 2014 automatisation + if ! openssl rsa -passin pass:$pass -in ${CAKEY} \ + -out ${CAKEY} > $OUTPUT 2>&1 + then + cat $OUTPUT + clean "ca" + exit 4 + fi + + echo $ECHO_OPTS "${GREEN}Self signes the CA certificate${COLOR_RST}" + if ! openssl ca -batch -create_serial -out ${CACRT} \ + ${VALIDITY} -keyfile ${CAKEY} \ + -selfsign -extensions CORE_CA -config ${OPENSSL_CONF} \ + -infiles ${CAREQ} > $OUTPUT 2>&1 + then + cat $OUTPUT + clean "ca" + exit 4 + fi + + echo $ECHO_OPTS "${GREEN}Generate DER format${COLOR_RST}" + openssl x509 -in ${CACRT} -inform PEM -out ${CADER} -outform DER + ;; + + "-newserver" ) + if [ $# -lt 2 ]; then + echo "Give as first argument the production IP or the domain that this certificat will cover." + echo "eg.: $0 -newserver 10.42.23.69" + echo " $0 -newserver fic.srs.epita.fr" + exit 1 + fi + + echo $ECHO_OPTS "${GREEN}Making the Server key and cert${COLOR_RST}" + if ! [ -f ${CAKEY} ]; then + echo $ECHO_OPTS "${RED}Can not found the CA's key${COLOR_RST}" + exit 2 + fi + sed -i "s/=.*#COMMONNAME/=$2#COMMONNAME/" $OPENSSL_CONF + sed -i "s/=.*#DAYS/= ${DAYS} #DAYS/" $OPENSSL_CONF + if ! openssl req -batch -new -keyout ${SRVKEY} -out ${SRVREQ} \ + -days ${DAYS} -config ${OPENSSL_CONF} -extensions SERVER_SSL > $OUTPUT 2>&1 + then + cat $OUTPUT + exit 4 + fi + echo $ECHO_OPTS "${GREEN}Signing the Server crt${COLOR_RST}" + if ! openssl ca -policy policy_match -config ${OPENSSL_CONF} \ + -out ${SRVCRT} -extensions SERVER_SSL -infiles ${SRVREQ} + then + echo $ECHO_OPTS "${RED}Signing failed for new server${COLOR_RST}" + rm -f ${SRVKEY} ${SRVREQ} ${SRVCRT} + cat $OUTPUT + exit 3 + else + rm ${SRVREQ} + echo $ECHO_OPTS "${GREEN}Signed certificate is in ${SRVCRT}${COLOR_RST}" + fi + ;; + + "-revokeserver" ) + echo $ECHO_OPTS "${GREEN}Revocate server certificate${COLOR_RST}" + if ! [ -f ${CAKEY} ]; then + echo $ECHO_OPTS "${RED}Can not found the CA's key${COLOR_RST}" + exit 2 + fi + if ! openssl ca -revoke ${SRVCRT} -config ${OPENSSL_CONF} \ + -keyfile ${CAKEY} -cert ${CACRT} > $OUTPUT 2>&1 + then + echo $ECHO_OPTS "${RED}Server certificate revocation failed${COLOR_RST}" + cat $OUTPUT + exit 4 + fi + rm ${SRVKEY} ${SRVCRT} + + gen_crl + ;; + + "-newclient" ) + if [ $# -ne 2 ]; then + echo "Usage: $0 -newclient NAME" + exit 1 + fi + + CLTNAM=$2 + CLTREQ=${PKI_DIR}/${CLTNAM}.csr + CLTCRT=${PKI_DIR}/certs/${CLTNAM}.crt + CLTKEY=${PKI_DIR}/${CLTNAM}.key + CLTP12=${PKI_DIR}/pkcs/${CLTNAM}.p12 + + echo "==============================================================" + echo $ECHO_OPTS "${GREEN}Making the client key and csr of ${BOLD}${2}${END_BOLD}${COLOR_RST}" + + ESCAPED=$(echo "${PKI_DIR}" | sed 's/[\/\.]/\\&/g') + sed -i "s/=.*#DIR/= ${ESCAPED} #DIR/" $OPENSSL_CONF + sed -i "s/=.*#DAYS/= ${DAYS} #DAYS/" $OPENSSL_CONF + + if ! [ -f ${CAKEY} ]; then + echo $ECHO_OPTS "${RED}Can not found the CA's key${COLOR_RST}" + exit 2 + fi + sed -i "s/=.*#COMMONNAME/= $2#COMMONNAME/" $OPENSSL_CONF + + type pwgen > /dev/null + if [ $? -ne 0 ]; then + echo "command not found: pwgen" + exit 5 + fi + + pass=`pwgen -n -B -y 12 1` + + if ! openssl req -batch -new -keyout "${CLTKEY}" -out "${CLTREQ}" \ + -config ${OPENSSL_CONF} -passout pass:$pass -days ${DAYS} -extensions CLIENT_SSL > $OUTPUT 2>&1 + then + cat $OUTPUT + clean "client" ${CLTNAM} + exit 4 + fi + + echo $ECHO_OPTS "${GREEN}Signing the Client crt${COLOR_RST}" + if ! openssl ca -batch -policy policy_match -out "${CLTCRT}" \ + -config ${OPENSSL_CONF} -extensions CLIENT_SSL -infiles "${CLTREQ}" > $OUTPUT 2>&1 + then + echo $ECHO_OPTS "${RED}Signing failed for $2 ${COLOR_RST}" + cat $OUTPUT + clean "client" ${CLTNAM} + exit 3 + fi + echo $ECHO_OPTS "${GREEN}Export the Client files to pkcs12${COLOR_RST}" + if ! openssl pkcs12 -export -inkey "${CLTKEY}" -in "${CLTCRT}" -name ${2} \ + -passin pass:$pass -out "${CLTP12}" \ + -passout pass:$pass > $OUTPUT 2>&1 + then + echo $ECHO_OPTS "${RED}pkcs12 export failed for ${BOLD}$2${END_BOLD}${COLOR_RST}" + cat $OUTPUT + clean "client" ${CLTNAM} + exit 4 + else + echo $ECHO_OPTS "Exported pkcs12 file is ${CLTP12}" + fi + echo "$CLTNAM:$pass" >> ${PKI_DIR}/teams.pass + echo "$CLTNAM:$pass" + clean "client" ${CLTNAM} + ;; + + "-revoke" ) + if [ $# -ne 2 ]; then + echo "Usage: $0 -revoke NAME" + exit 1 + fi + + CLTNAM=$2 + CLTCRT=${PKI_DIR}/certs/${CLTNAM}.crt + CLTP12=${PKI_DIR}/pkcs/${CLTNAM}.p12 + + echo $ECHO_OPTS "${GREEN}Revocate ${BOLD}${CLTNAM}${END_BOLD}${COLOR_RST}" + if ! openssl ca -revoke "${CLTCRT}" -config "${OPENSSL_CONF}" \ + -keyfile "${CAKEY}" -cert "${CACRT}" > $OUTPUT 2>&1 + then + echo $ECHO_OPTS "${RED}Revocation failed for ${BOLD}${CLTNAM}${END_BOLD}${COLOR_RST}" + cat $OUTPUT + exit 4 + fi + rm "${CLTCRT}" "${CLTP12}" + + gen_crl + ;; + + "-gencrl" ) + gen_crl + ;; + + * ) + usage + ;; +esac diff --git a/admin/pki/ca.go b/admin/pki/ca.go deleted file mode 100644 index dfbb9f1f..00000000 --- a/admin/pki/ca.go +++ /dev/null @@ -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 -} diff --git a/admin/pki/client.go b/admin/pki/client.go deleted file mode 100644 index a64ebc4b..00000000 --- a/admin/pki/client.go +++ /dev/null @@ -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() -} diff --git a/admin/pki/common.go b/admin/pki/common.go deleted file mode 100644 index 4310084a..00000000 --- a/admin/pki/common.go +++ /dev/null @@ -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 -} diff --git a/admin/pki/openssl.cnf b/admin/pki/openssl.cnf new file mode 100644 index 00000000..495674f9 --- /dev/null +++ b/admin/pki/openssl.cnf @@ -0,0 +1,197 @@ +# +# OpenSSL example configuration file. +# This is mostly being used for generation of certificate requests. +# + +# This definition stops the following lines choking if HOME isn't +# defined. +HOME = . +RANDFILE = $ENV::HOME/.rnd + +# Extra OBJECT IDENTIFIER info: +#oid_file = $ENV::HOME/.oid +oid_section = new_oids + +# To use this configuration file with the "-extfile" option of the +# "openssl x509" utility, name here the section containing the +# X.509v3 extensions to use: +# extensions = +# (Alternatively, use a configuration file that has only +# X.509v3 extensions in its main [= default] section.) + +[ new_oids ] + +# We can add new OIDs in here for use by 'ca', 'req' and 'ts'. +# Add a simple OID like this: +# testoid1=1.2.3.4 +# Or use config file substitution like this: +# testoid2=${testoid1}.5.6 + +# Policies used by the TSA examples. +tsa_policy1 = 1.2.3.4.1 +tsa_policy2 = 1.2.3.4.5.6 +tsa_policy3 = 1.2.3.4.5.7 + +#################################################################### +[ ca ] +default_ca = CA_default # The default ca section + +#################################################################### +[ CA_default ] + +dir = /home/nemunaire/workspace/gowks/src/srs.epita.fr/fic-server/admin/PKI #DIR # Where everything is kept +certs = $dir/certs # Where the issued certs are kept +crl_dir = $dir/crl # Where the issued crl are kept +database = $dir/index.txt # database index file. +#unique_subject = no # Set to 'no' to allow creation of + # several ctificates with same subject. +new_certs_dir = $dir/newcerts # default place for new certs. + +certificate = $dir/shared/cacert.crt # The CA certificate +serial = $dir/serial # The current serial number +crlnumber = $dir/crlnumber # the current crl number + # must be commented out to leave a V1 CRL +crl = $dir/shared/crl.pem # The current CRL +private_key = $dir/private/cakey.key # The private key +RANDFILE = $dir/private/.rand # private random number file + +# Comment out the following two lines for the "traditional" +# (and highly broken) format. +name_opt = ca_default # Subject Name options +cert_opt = ca_default # Certificate field options + +# Extension copying option: use with caution. +# copy_extensions = copy + +# Extensions to add to a CRL. Note: Netscape communicator chokes on V2 CRLs +# so this is commented out by default to leave a V1 CRL. +# crlnumber must also be commented out to leave a V1 CRL. +# crl_extensions = crl_ext + +default_days = 2 #DAYS +default_crl_days= 1 # how long before next CRL +default_md = default # use public key default MD +preserve = no # keep passed DN ordering + +# A few difference way of specifying how similar the request should look +# For type CA, the listed attributes must be the same, and the optional +# and supplied fields are just that :-) +policy = policy_match + +# For the CA policy +[ policy_match ] +countryName = match +stateOrProvinceName = match +organizationName = match +organizationalUnitName = optional +commonName = supplied +emailAddress = optional + +# For the 'anything' policy +# At this point in time, you must list all acceptable 'object' +# types. +[ policy_anything ] +countryName = optional +stateOrProvinceName = optional +localityName = optional +organizationName = optional +organizationalUnitName = optional +commonName = supplied +emailAddress = optional + +#################################################################### +[ req ] +default_bits = 2048 +default_keyfile = privkey.pem +distinguished_name = req_distinguished_name +attributes = req_attributes +x509_extensions = v3_ca # The extentions to add to the self signed cert + +# Passwords for private keys if not present they will be prompted for +# input_password = secret +# output_password = secret + +# This sets a mask for permitted string types. There are several options. +# default: PrintableString, T61String, BMPString. +# pkix : PrintableString, BMPString (PKIX recommendation before 2004) +# utf8only: only UTF8Strings (PKIX recommendation after 2004). +# nombstr : PrintableString, T61String (no BMPStrings or UTF8Strings). +# MASK:XXXX a literal mask value. +# WARNING: ancient versions of Netscape crash on BMPStrings or UTF8Strings. +string_mask = utf8only + +# req_extensions = v3_req # The extensions to add to a certificate request + +[ req_distinguished_name ] +countryName = Country Name (2 letter code) +countryName_default = FR +countryName_min = 2 +countryName_max = 2 + +stateOrProvinceName = State or Province Name (full name) +stateOrProvinceName_default = France + +localityName = Locality Name (eg, city) +localityName_default = Paris + +0.organizationName = Organization Name (eg, company) +0.organizationName_default = Epita + +# we can do this but it is not needed normally :-) +#1.organizationName = Second Organization Name (eg, company) +#1.organizationName_default = World Wide Web Pty Ltd + +organizationalUnitName = Organizational Unit Name (eg, section) +organizationalUnitName_default = SRS + +commonName = Common Name (e.g. server FQDN or YOUR name) +commonName_default = Acier#COMMONNAME +commonName_max = 64 + +emailAddress = Email Address +emailAddress_max = 64 +emailAddress_default = root@srs.epita.fr + +# SET-ex3 = SET extension number 3 + +[ req_attributes ] +challengePassword = A challenge password +challengePassword_min = 4 +challengePassword_max = 20 + +unstructuredName = An optional company name + +[CORE_CA] +nsComment = "FIC CA" +basicConstraints = critical,CA:TRUE,pathlen:1 +subjectKeyIdentifier = hash +authorityKeyIdentifier = keyid,issuer:always +issuerAltName = issuer:copy +keyUsage = keyCertSign, cRLSign +nsCertType = sslCA +subjectKeyIdentifier=hash +authorityKeyIdentifier=keyid:always,issuer + +[SERVER_SSL] +nsComment = "FIC Server" +subjectKeyIdentifier = hash +authorityKeyIdentifier = keyid,issuer:always +issuerAltName = issuer:copy +basicConstraints = critical,CA:FALSE +keyUsage = digitalSignature, keyEncipherment +nsCertType = server +extendedKeyUsage = serverAuth +subjectKeyIdentifier=hash +authorityKeyIdentifier=keyid:always,issuer + +[CLIENT_SSL] +nsComment = "FIC Client" +subjectKeyIdentifier = hash +authorityKeyIdentifier = keyid,issuer:always +issuerAltName = issuer:copy +basicConstraints = critical,CA:FALSE +keyUsage = digitalSignature, nonRepudiation +nsCertType = client +extendedKeyUsage = clientAuth +subjectKeyIdentifier=hash +authorityKeyIdentifier=keyid:always,issuer diff --git a/admin/pki/team.go b/admin/pki/team.go deleted file mode 100644 index 8ea91535..00000000 --- a/admin/pki/team.go +++ /dev/null @@ -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 -} diff --git a/admin/static.go b/admin/static.go index 23ad6da6..6c7b7cc6 100644 --- a/admin/static.go +++ b/admin/static.go @@ -1,160 +1,18 @@ 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() - } +type staticRouting struct { + StaticDir string } -func serveIndex(c *gin.Context) { - c.Writer.Write(indexPage) +func StaticHandler(staticDir string) http.Handler { + return staticRouting{staticDir} } -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) - }) +func (a staticRouting) ServeHTTP(w http.ResponseWriter, r *http.Request) { + http.ServeFile(w, r, path.Join(a.StaticDir, "index.html")) } diff --git a/admin/static/check_import.html b/admin/static/check_import.html deleted file mode 100644 index da2176e6..00000000 --- a/admin/static/check_import.html +++ /dev/null @@ -1,52 +0,0 @@ - - - - Rapport d'import FIC - - - - - - -

Rapport d'import FIC

-

- Date du dernier import : -

-
- - - diff --git a/admin/static/css/bootstrap.min.css b/admin/static/css/bootstrap.min.css index 83a71b1f..4cf729e4 100644 --- a/admin/static/css/bootstrap.min.css +++ b/admin/static/css/bootstrap.min.css @@ -1,7 +1,6 @@ /*! - * Bootstrap v4.6.2 (https://getbootstrap.com/) - * Copyright 2011-2022 The Bootstrap Authors - * Copyright 2011-2022 Twitter, Inc. - * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) - */:root{--blue:#007bff;--indigo:#6610f2;--purple:#6f42c1;--pink:#e83e8c;--red:#dc3545;--orange:#fd7e14;--yellow:#ffc107;--green:#28a745;--teal:#20c997;--cyan:#17a2b8;--white:#fff;--gray:#6c757d;--gray-dark:#343a40;--primary:#007bff;--secondary:#6c757d;--success:#28a745;--info:#17a2b8;--warning:#ffc107;--danger:#dc3545;--light:#f8f9fa;--dark:#343a40;--breakpoint-xs:0;--breakpoint-sm:576px;--breakpoint-md:768px;--breakpoint-lg:992px;--breakpoint-xl:1200px;--font-family-sans-serif:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Arial,"Noto Sans","Liberation Sans",sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji";--font-family-monospace:SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace}*,::after,::before{box-sizing:border-box}html{font-family:sans-serif;line-height:1.15;-webkit-text-size-adjust:100%;-webkit-tap-highlight-color:transparent}article,aside,figcaption,figure,footer,header,hgroup,main,nav,section{display:block}body{margin:0;font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Arial,"Noto Sans","Liberation Sans",sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji";font-size:1rem;font-weight:400;line-height:1.5;color:#212529;text-align:left;background-color:#fff}[tabindex="-1"]:focus:not(:focus-visible){outline:0!important}hr{box-sizing:content-box;height:0;overflow:visible}h1,h2,h3,h4,h5,h6{margin-top:0;margin-bottom:.5rem}p{margin-top:0;margin-bottom:1rem}abbr[data-original-title],abbr[title]{text-decoration:underline;-webkit-text-decoration:underline dotted;text-decoration:underline dotted;cursor:help;border-bottom:0;-webkit-text-decoration-skip-ink:none;text-decoration-skip-ink:none}address{margin-bottom:1rem;font-style:normal;line-height:inherit}dl,ol,ul{margin-top:0;margin-bottom:1rem}ol ol,ol ul,ul ol,ul ul{margin-bottom:0}dt{font-weight:700}dd{margin-bottom:.5rem;margin-left:0}blockquote{margin:0 0 1rem}b,strong{font-weight:bolder}small{font-size:80%}sub,sup{position:relative;font-size:75%;line-height:0;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}a{color:#007bff;text-decoration:none;background-color:transparent}a:hover{color:#0056b3;text-decoration:underline}a:not([href]):not([class]){color:inherit;text-decoration:none}a:not([href]):not([class]):hover{color:inherit;text-decoration:none}code,kbd,pre,samp{font-family:SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace;font-size:1em}pre{margin-top:0;margin-bottom:1rem;overflow:auto;-ms-overflow-style:scrollbar}figure{margin:0 0 1rem}img{vertical-align:middle;border-style:none}svg{overflow:hidden;vertical-align:middle}table{border-collapse:collapse}caption{padding-top:.75rem;padding-bottom:.75rem;color:#6c757d;text-align:left;caption-side:bottom}th{text-align:inherit;text-align:-webkit-match-parent}label{display:inline-block;margin-bottom:.5rem}button{border-radius:0}button:focus:not(:focus-visible){outline:0}button,input,optgroup,select,textarea{margin:0;font-family:inherit;font-size:inherit;line-height:inherit}button,input{overflow:visible}button,select{text-transform:none}[role=button]{cursor:pointer}select{word-wrap:normal}[type=button],[type=reset],[type=submit],button{-webkit-appearance:button}[type=button]:not(:disabled),[type=reset]:not(:disabled),[type=submit]:not(:disabled),button:not(:disabled){cursor:pointer}[type=button]::-moz-focus-inner,[type=reset]::-moz-focus-inner,[type=submit]::-moz-focus-inner,button::-moz-focus-inner{padding:0;border-style:none}input[type=checkbox],input[type=radio]{box-sizing:border-box;padding:0}textarea{overflow:auto;resize:vertical}fieldset{min-width:0;padding:0;margin:0;border:0}legend{display:block;width:100%;max-width:100%;padding:0;margin-bottom:.5rem;font-size:1.5rem;line-height:inherit;color:inherit;white-space:normal}progress{vertical-align:baseline}[type=number]::-webkit-inner-spin-button,[type=number]::-webkit-outer-spin-button{height:auto}[type=search]{outline-offset:-2px;-webkit-appearance:none}[type=search]::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{font:inherit;-webkit-appearance:button}output{display:inline-block}summary{display:list-item;cursor:pointer}template{display:none}[hidden]{display:none!important}.h1,.h2,.h3,.h4,.h5,.h6,h1,h2,h3,h4,h5,h6{margin-bottom:.5rem;font-weight:500;line-height:1.2}.h1,h1{font-size:2.5rem}.h2,h2{font-size:2rem}.h3,h3{font-size:1.75rem}.h4,h4{font-size:1.5rem}.h5,h5{font-size:1.25rem}.h6,h6{font-size:1rem}.lead{font-size:1.25rem;font-weight:300}.display-1{font-size:6rem;font-weight:300;line-height:1.2}.display-2{font-size:5.5rem;font-weight:300;line-height:1.2}.display-3{font-size:4.5rem;font-weight:300;line-height:1.2}.display-4{font-size:3.5rem;font-weight:300;line-height:1.2}hr{margin-top:1rem;margin-bottom:1rem;border:0;border-top:1px solid rgba(0,0,0,.1)}.small,small{font-size:.875em;font-weight:400}.mark,mark{padding:.2em;background-color:#fcf8e3}.list-unstyled{padding-left:0;list-style:none}.list-inline{padding-left:0;list-style:none}.list-inline-item{display:inline-block}.list-inline-item:not(:last-child){margin-right:.5rem}.initialism{font-size:90%;text-transform:uppercase}.blockquote{margin-bottom:1rem;font-size:1.25rem}.blockquote-footer{display:block;font-size:.875em;color:#6c757d}.blockquote-footer::before{content:"\2014\00A0"}.img-fluid{max-width:100%;height:auto}.img-thumbnail{padding:.25rem;background-color:#fff;border:1px solid #dee2e6;border-radius:.25rem;max-width:100%;height:auto}.figure{display:inline-block}.figure-img{margin-bottom:.5rem;line-height:1}.figure-caption{font-size:90%;color:#6c757d}code{font-size:87.5%;color:#e83e8c;word-wrap:break-word}a>code{color:inherit}kbd{padding:.2rem .4rem;font-size:87.5%;color:#fff;background-color:#212529;border-radius:.2rem}kbd kbd{padding:0;font-size:100%;font-weight:700}pre{display:block;font-size:87.5%;color:#212529}pre code{font-size:inherit;color:inherit;word-break:normal}.pre-scrollable{max-height:340px;overflow-y:scroll}.container,.container-fluid,.container-lg,.container-md,.container-sm,.container-xl{width:100%;padding-right:15px;padding-left:15px;margin-right:auto;margin-left:auto}@media (min-width:576px){.container,.container-sm{max-width:540px}}@media (min-width:768px){.container,.container-md,.container-sm{max-width:720px}}@media (min-width:992px){.container,.container-lg,.container-md,.container-sm{max-width:960px}}@media (min-width:1200px){.container,.container-lg,.container-md,.container-sm,.container-xl{max-width:1140px}}.row{display:-ms-flexbox;display:flex;-ms-flex-wrap:wrap;flex-wrap:wrap;margin-right:-15px;margin-left:-15px}.no-gutters{margin-right:0;margin-left:0}.no-gutters>.col,.no-gutters>[class*=col-]{padding-right:0;padding-left:0}.col,.col-1,.col-10,.col-11,.col-12,.col-2,.col-3,.col-4,.col-5,.col-6,.col-7,.col-8,.col-9,.col-auto,.col-lg,.col-lg-1,.col-lg-10,.col-lg-11,.col-lg-12,.col-lg-2,.col-lg-3,.col-lg-4,.col-lg-5,.col-lg-6,.col-lg-7,.col-lg-8,.col-lg-9,.col-lg-auto,.col-md,.col-md-1,.col-md-10,.col-md-11,.col-md-12,.col-md-2,.col-md-3,.col-md-4,.col-md-5,.col-md-6,.col-md-7,.col-md-8,.col-md-9,.col-md-auto,.col-sm,.col-sm-1,.col-sm-10,.col-sm-11,.col-sm-12,.col-sm-2,.col-sm-3,.col-sm-4,.col-sm-5,.col-sm-6,.col-sm-7,.col-sm-8,.col-sm-9,.col-sm-auto,.col-xl,.col-xl-1,.col-xl-10,.col-xl-11,.col-xl-12,.col-xl-2,.col-xl-3,.col-xl-4,.col-xl-5,.col-xl-6,.col-xl-7,.col-xl-8,.col-xl-9,.col-xl-auto{position:relative;width:100%;padding-right:15px;padding-left:15px}.col{-ms-flex-preferred-size:0;flex-basis:0;-ms-flex-positive:1;flex-grow:1;max-width:100%}.row-cols-1>*{-ms-flex:0 0 100%;flex:0 0 100%;max-width:100%}.row-cols-2>*{-ms-flex:0 0 50%;flex:0 0 50%;max-width:50%}.row-cols-3>*{-ms-flex:0 0 33.333333%;flex:0 0 33.333333%;max-width:33.333333%}.row-cols-4>*{-ms-flex:0 0 25%;flex:0 0 25%;max-width:25%}.row-cols-5>*{-ms-flex:0 0 20%;flex:0 0 20%;max-width:20%}.row-cols-6>*{-ms-flex:0 0 16.666667%;flex:0 0 16.666667%;max-width:16.666667%}.col-auto{-ms-flex:0 0 auto;flex:0 0 auto;width:auto;max-width:100%}.col-1{-ms-flex:0 0 8.333333%;flex:0 0 8.333333%;max-width:8.333333%}.col-2{-ms-flex:0 0 16.666667%;flex:0 0 16.666667%;max-width:16.666667%}.col-3{-ms-flex:0 0 25%;flex:0 0 25%;max-width:25%}.col-4{-ms-flex:0 0 33.333333%;flex:0 0 33.333333%;max-width:33.333333%}.col-5{-ms-flex:0 0 41.666667%;flex:0 0 41.666667%;max-width:41.666667%}.col-6{-ms-flex:0 0 50%;flex:0 0 50%;max-width:50%}.col-7{-ms-flex:0 0 58.333333%;flex:0 0 58.333333%;max-width:58.333333%}.col-8{-ms-flex:0 0 66.666667%;flex:0 0 66.666667%;max-width:66.666667%}.col-9{-ms-flex:0 0 75%;flex:0 0 75%;max-width:75%}.col-10{-ms-flex:0 0 83.333333%;flex:0 0 83.333333%;max-width:83.333333%}.col-11{-ms-flex:0 0 91.666667%;flex:0 0 91.666667%;max-width:91.666667%}.col-12{-ms-flex:0 0 100%;flex:0 0 100%;max-width:100%}.order-first{-ms-flex-order:-1;order:-1}.order-last{-ms-flex-order:13;order:13}.order-0{-ms-flex-order:0;order:0}.order-1{-ms-flex-order:1;order:1}.order-2{-ms-flex-order:2;order:2}.order-3{-ms-flex-order:3;order:3}.order-4{-ms-flex-order:4;order:4}.order-5{-ms-flex-order:5;order:5}.order-6{-ms-flex-order:6;order:6}.order-7{-ms-flex-order:7;order:7}.order-8{-ms-flex-order:8;order:8}.order-9{-ms-flex-order:9;order:9}.order-10{-ms-flex-order:10;order:10}.order-11{-ms-flex-order:11;order:11}.order-12{-ms-flex-order:12;order:12}.offset-1{margin-left:8.333333%}.offset-2{margin-left:16.666667%}.offset-3{margin-left:25%}.offset-4{margin-left:33.333333%}.offset-5{margin-left:41.666667%}.offset-6{margin-left:50%}.offset-7{margin-left:58.333333%}.offset-8{margin-left:66.666667%}.offset-9{margin-left:75%}.offset-10{margin-left:83.333333%}.offset-11{margin-left:91.666667%}@media (min-width:576px){.col-sm{-ms-flex-preferred-size:0;flex-basis:0;-ms-flex-positive:1;flex-grow:1;max-width:100%}.row-cols-sm-1>*{-ms-flex:0 0 100%;flex:0 0 100%;max-width:100%}.row-cols-sm-2>*{-ms-flex:0 0 50%;flex:0 0 50%;max-width:50%}.row-cols-sm-3>*{-ms-flex:0 0 33.333333%;flex:0 0 33.333333%;max-width:33.333333%}.row-cols-sm-4>*{-ms-flex:0 0 25%;flex:0 0 25%;max-width:25%}.row-cols-sm-5>*{-ms-flex:0 0 20%;flex:0 0 20%;max-width:20%}.row-cols-sm-6>*{-ms-flex:0 0 16.666667%;flex:0 0 16.666667%;max-width:16.666667%}.col-sm-auto{-ms-flex:0 0 auto;flex:0 0 auto;width:auto;max-width:100%}.col-sm-1{-ms-flex:0 0 8.333333%;flex:0 0 8.333333%;max-width:8.333333%}.col-sm-2{-ms-flex:0 0 16.666667%;flex:0 0 16.666667%;max-width:16.666667%}.col-sm-3{-ms-flex:0 0 25%;flex:0 0 25%;max-width:25%}.col-sm-4{-ms-flex:0 0 33.333333%;flex:0 0 33.333333%;max-width:33.333333%}.col-sm-5{-ms-flex:0 0 41.666667%;flex:0 0 41.666667%;max-width:41.666667%}.col-sm-6{-ms-flex:0 0 50%;flex:0 0 50%;max-width:50%}.col-sm-7{-ms-flex:0 0 58.333333%;flex:0 0 58.333333%;max-width:58.333333%}.col-sm-8{-ms-flex:0 0 66.666667%;flex:0 0 66.666667%;max-width:66.666667%}.col-sm-9{-ms-flex:0 0 75%;flex:0 0 75%;max-width:75%}.col-sm-10{-ms-flex:0 0 83.333333%;flex:0 0 83.333333%;max-width:83.333333%}.col-sm-11{-ms-flex:0 0 91.666667%;flex:0 0 91.666667%;max-width:91.666667%}.col-sm-12{-ms-flex:0 0 100%;flex:0 0 100%;max-width:100%}.order-sm-first{-ms-flex-order:-1;order:-1}.order-sm-last{-ms-flex-order:13;order:13}.order-sm-0{-ms-flex-order:0;order:0}.order-sm-1{-ms-flex-order:1;order:1}.order-sm-2{-ms-flex-order:2;order:2}.order-sm-3{-ms-flex-order:3;order:3}.order-sm-4{-ms-flex-order:4;order:4}.order-sm-5{-ms-flex-order:5;order:5}.order-sm-6{-ms-flex-order:6;order:6}.order-sm-7{-ms-flex-order:7;order:7}.order-sm-8{-ms-flex-order:8;order:8}.order-sm-9{-ms-flex-order:9;order:9}.order-sm-10{-ms-flex-order:10;order:10}.order-sm-11{-ms-flex-order:11;order:11}.order-sm-12{-ms-flex-order:12;order:12}.offset-sm-0{margin-left:0}.offset-sm-1{margin-left:8.333333%}.offset-sm-2{margin-left:16.666667%}.offset-sm-3{margin-left:25%}.offset-sm-4{margin-left:33.333333%}.offset-sm-5{margin-left:41.666667%}.offset-sm-6{margin-left:50%}.offset-sm-7{margin-left:58.333333%}.offset-sm-8{margin-left:66.666667%}.offset-sm-9{margin-left:75%}.offset-sm-10{margin-left:83.333333%}.offset-sm-11{margin-left:91.666667%}}@media (min-width:768px){.col-md{-ms-flex-preferred-size:0;flex-basis:0;-ms-flex-positive:1;flex-grow:1;max-width:100%}.row-cols-md-1>*{-ms-flex:0 0 100%;flex:0 0 100%;max-width:100%}.row-cols-md-2>*{-ms-flex:0 0 50%;flex:0 0 50%;max-width:50%}.row-cols-md-3>*{-ms-flex:0 0 33.333333%;flex:0 0 33.333333%;max-width:33.333333%}.row-cols-md-4>*{-ms-flex:0 0 25%;flex:0 0 25%;max-width:25%}.row-cols-md-5>*{-ms-flex:0 0 20%;flex:0 0 20%;max-width:20%}.row-cols-md-6>*{-ms-flex:0 0 16.666667%;flex:0 0 16.666667%;max-width:16.666667%}.col-md-auto{-ms-flex:0 0 auto;flex:0 0 auto;width:auto;max-width:100%}.col-md-1{-ms-flex:0 0 8.333333%;flex:0 0 8.333333%;max-width:8.333333%}.col-md-2{-ms-flex:0 0 16.666667%;flex:0 0 16.666667%;max-width:16.666667%}.col-md-3{-ms-flex:0 0 25%;flex:0 0 25%;max-width:25%}.col-md-4{-ms-flex:0 0 33.333333%;flex:0 0 33.333333%;max-width:33.333333%}.col-md-5{-ms-flex:0 0 41.666667%;flex:0 0 41.666667%;max-width:41.666667%}.col-md-6{-ms-flex:0 0 50%;flex:0 0 50%;max-width:50%}.col-md-7{-ms-flex:0 0 58.333333%;flex:0 0 58.333333%;max-width:58.333333%}.col-md-8{-ms-flex:0 0 66.666667%;flex:0 0 66.666667%;max-width:66.666667%}.col-md-9{-ms-flex:0 0 75%;flex:0 0 75%;max-width:75%}.col-md-10{-ms-flex:0 0 83.333333%;flex:0 0 83.333333%;max-width:83.333333%}.col-md-11{-ms-flex:0 0 91.666667%;flex:0 0 91.666667%;max-width:91.666667%}.col-md-12{-ms-flex:0 0 100%;flex:0 0 100%;max-width:100%}.order-md-first{-ms-flex-order:-1;order:-1}.order-md-last{-ms-flex-order:13;order:13}.order-md-0{-ms-flex-order:0;order:0}.order-md-1{-ms-flex-order:1;order:1}.order-md-2{-ms-flex-order:2;order:2}.order-md-3{-ms-flex-order:3;order:3}.order-md-4{-ms-flex-order:4;order:4}.order-md-5{-ms-flex-order:5;order:5}.order-md-6{-ms-flex-order:6;order:6}.order-md-7{-ms-flex-order:7;order:7}.order-md-8{-ms-flex-order:8;order:8}.order-md-9{-ms-flex-order:9;order:9}.order-md-10{-ms-flex-order:10;order:10}.order-md-11{-ms-flex-order:11;order:11}.order-md-12{-ms-flex-order:12;order:12}.offset-md-0{margin-left:0}.offset-md-1{margin-left:8.333333%}.offset-md-2{margin-left:16.666667%}.offset-md-3{margin-left:25%}.offset-md-4{margin-left:33.333333%}.offset-md-5{margin-left:41.666667%}.offset-md-6{margin-left:50%}.offset-md-7{margin-left:58.333333%}.offset-md-8{margin-left:66.666667%}.offset-md-9{margin-left:75%}.offset-md-10{margin-left:83.333333%}.offset-md-11{margin-left:91.666667%}}@media (min-width:992px){.col-lg{-ms-flex-preferred-size:0;flex-basis:0;-ms-flex-positive:1;flex-grow:1;max-width:100%}.row-cols-lg-1>*{-ms-flex:0 0 100%;flex:0 0 100%;max-width:100%}.row-cols-lg-2>*{-ms-flex:0 0 50%;flex:0 0 50%;max-width:50%}.row-cols-lg-3>*{-ms-flex:0 0 33.333333%;flex:0 0 33.333333%;max-width:33.333333%}.row-cols-lg-4>*{-ms-flex:0 0 25%;flex:0 0 25%;max-width:25%}.row-cols-lg-5>*{-ms-flex:0 0 20%;flex:0 0 20%;max-width:20%}.row-cols-lg-6>*{-ms-flex:0 0 16.666667%;flex:0 0 16.666667%;max-width:16.666667%}.col-lg-auto{-ms-flex:0 0 auto;flex:0 0 auto;width:auto;max-width:100%}.col-lg-1{-ms-flex:0 0 8.333333%;flex:0 0 8.333333%;max-width:8.333333%}.col-lg-2{-ms-flex:0 0 16.666667%;flex:0 0 16.666667%;max-width:16.666667%}.col-lg-3{-ms-flex:0 0 25%;flex:0 0 25%;max-width:25%}.col-lg-4{-ms-flex:0 0 33.333333%;flex:0 0 33.333333%;max-width:33.333333%}.col-lg-5{-ms-flex:0 0 41.666667%;flex:0 0 41.666667%;max-width:41.666667%}.col-lg-6{-ms-flex:0 0 50%;flex:0 0 50%;max-width:50%}.col-lg-7{-ms-flex:0 0 58.333333%;flex:0 0 58.333333%;max-width:58.333333%}.col-lg-8{-ms-flex:0 0 66.666667%;flex:0 0 66.666667%;max-width:66.666667%}.col-lg-9{-ms-flex:0 0 75%;flex:0 0 75%;max-width:75%}.col-lg-10{-ms-flex:0 0 83.333333%;flex:0 0 83.333333%;max-width:83.333333%}.col-lg-11{-ms-flex:0 0 91.666667%;flex:0 0 91.666667%;max-width:91.666667%}.col-lg-12{-ms-flex:0 0 100%;flex:0 0 100%;max-width:100%}.order-lg-first{-ms-flex-order:-1;order:-1}.order-lg-last{-ms-flex-order:13;order:13}.order-lg-0{-ms-flex-order:0;order:0}.order-lg-1{-ms-flex-order:1;order:1}.order-lg-2{-ms-flex-order:2;order:2}.order-lg-3{-ms-flex-order:3;order:3}.order-lg-4{-ms-flex-order:4;order:4}.order-lg-5{-ms-flex-order:5;order:5}.order-lg-6{-ms-flex-order:6;order:6}.order-lg-7{-ms-flex-order:7;order:7}.order-lg-8{-ms-flex-order:8;order:8}.order-lg-9{-ms-flex-order:9;order:9}.order-lg-10{-ms-flex-order:10;order:10}.order-lg-11{-ms-flex-order:11;order:11}.order-lg-12{-ms-flex-order:12;order:12}.offset-lg-0{margin-left:0}.offset-lg-1{margin-left:8.333333%}.offset-lg-2{margin-left:16.666667%}.offset-lg-3{margin-left:25%}.offset-lg-4{margin-left:33.333333%}.offset-lg-5{margin-left:41.666667%}.offset-lg-6{margin-left:50%}.offset-lg-7{margin-left:58.333333%}.offset-lg-8{margin-left:66.666667%}.offset-lg-9{margin-left:75%}.offset-lg-10{margin-left:83.333333%}.offset-lg-11{margin-left:91.666667%}}@media (min-width:1200px){.col-xl{-ms-flex-preferred-size:0;flex-basis:0;-ms-flex-positive:1;flex-grow:1;max-width:100%}.row-cols-xl-1>*{-ms-flex:0 0 100%;flex:0 0 100%;max-width:100%}.row-cols-xl-2>*{-ms-flex:0 0 50%;flex:0 0 50%;max-width:50%}.row-cols-xl-3>*{-ms-flex:0 0 33.333333%;flex:0 0 33.333333%;max-width:33.333333%}.row-cols-xl-4>*{-ms-flex:0 0 25%;flex:0 0 25%;max-width:25%}.row-cols-xl-5>*{-ms-flex:0 0 20%;flex:0 0 20%;max-width:20%}.row-cols-xl-6>*{-ms-flex:0 0 16.666667%;flex:0 0 16.666667%;max-width:16.666667%}.col-xl-auto{-ms-flex:0 0 auto;flex:0 0 auto;width:auto;max-width:100%}.col-xl-1{-ms-flex:0 0 8.333333%;flex:0 0 8.333333%;max-width:8.333333%}.col-xl-2{-ms-flex:0 0 16.666667%;flex:0 0 16.666667%;max-width:16.666667%}.col-xl-3{-ms-flex:0 0 25%;flex:0 0 25%;max-width:25%}.col-xl-4{-ms-flex:0 0 33.333333%;flex:0 0 33.333333%;max-width:33.333333%}.col-xl-5{-ms-flex:0 0 41.666667%;flex:0 0 41.666667%;max-width:41.666667%}.col-xl-6{-ms-flex:0 0 50%;flex:0 0 50%;max-width:50%}.col-xl-7{-ms-flex:0 0 58.333333%;flex:0 0 58.333333%;max-width:58.333333%}.col-xl-8{-ms-flex:0 0 66.666667%;flex:0 0 66.666667%;max-width:66.666667%}.col-xl-9{-ms-flex:0 0 75%;flex:0 0 75%;max-width:75%}.col-xl-10{-ms-flex:0 0 83.333333%;flex:0 0 83.333333%;max-width:83.333333%}.col-xl-11{-ms-flex:0 0 91.666667%;flex:0 0 91.666667%;max-width:91.666667%}.col-xl-12{-ms-flex:0 0 100%;flex:0 0 100%;max-width:100%}.order-xl-first{-ms-flex-order:-1;order:-1}.order-xl-last{-ms-flex-order:13;order:13}.order-xl-0{-ms-flex-order:0;order:0}.order-xl-1{-ms-flex-order:1;order:1}.order-xl-2{-ms-flex-order:2;order:2}.order-xl-3{-ms-flex-order:3;order:3}.order-xl-4{-ms-flex-order:4;order:4}.order-xl-5{-ms-flex-order:5;order:5}.order-xl-6{-ms-flex-order:6;order:6}.order-xl-7{-ms-flex-order:7;order:7}.order-xl-8{-ms-flex-order:8;order:8}.order-xl-9{-ms-flex-order:9;order:9}.order-xl-10{-ms-flex-order:10;order:10}.order-xl-11{-ms-flex-order:11;order:11}.order-xl-12{-ms-flex-order:12;order:12}.offset-xl-0{margin-left:0}.offset-xl-1{margin-left:8.333333%}.offset-xl-2{margin-left:16.666667%}.offset-xl-3{margin-left:25%}.offset-xl-4{margin-left:33.333333%}.offset-xl-5{margin-left:41.666667%}.offset-xl-6{margin-left:50%}.offset-xl-7{margin-left:58.333333%}.offset-xl-8{margin-left:66.666667%}.offset-xl-9{margin-left:75%}.offset-xl-10{margin-left:83.333333%}.offset-xl-11{margin-left:91.666667%}}.table{width:100%;margin-bottom:1rem;color:#212529}.table td,.table th{padding:.75rem;vertical-align:top;border-top:1px solid #dee2e6}.table thead th{vertical-align:bottom;border-bottom:2px solid #dee2e6}.table tbody+tbody{border-top:2px solid #dee2e6}.table-sm td,.table-sm th{padding:.3rem}.table-bordered{border:1px solid #dee2e6}.table-bordered td,.table-bordered th{border:1px solid #dee2e6}.table-bordered thead td,.table-bordered thead th{border-bottom-width:2px}.table-borderless tbody+tbody,.table-borderless td,.table-borderless th,.table-borderless thead th{border:0}.table-striped tbody tr:nth-of-type(odd){background-color:rgba(0,0,0,.05)}.table-hover tbody tr:hover{color:#212529;background-color:rgba(0,0,0,.075)}.table-primary,.table-primary>td,.table-primary>th{background-color:#b8daff}.table-primary tbody+tbody,.table-primary td,.table-primary th,.table-primary thead th{border-color:#7abaff}.table-hover .table-primary:hover{background-color:#9fcdff}.table-hover .table-primary:hover>td,.table-hover .table-primary:hover>th{background-color:#9fcdff}.table-secondary,.table-secondary>td,.table-secondary>th{background-color:#d6d8db}.table-secondary tbody+tbody,.table-secondary td,.table-secondary th,.table-secondary thead th{border-color:#b3b7bb}.table-hover .table-secondary:hover{background-color:#c8cbcf}.table-hover .table-secondary:hover>td,.table-hover .table-secondary:hover>th{background-color:#c8cbcf}.table-success,.table-success>td,.table-success>th{background-color:#c3e6cb}.table-success tbody+tbody,.table-success td,.table-success th,.table-success thead th{border-color:#8fd19e}.table-hover .table-success:hover{background-color:#b1dfbb}.table-hover .table-success:hover>td,.table-hover .table-success:hover>th{background-color:#b1dfbb}.table-info,.table-info>td,.table-info>th{background-color:#bee5eb}.table-info tbody+tbody,.table-info td,.table-info th,.table-info thead th{border-color:#86cfda}.table-hover .table-info:hover{background-color:#abdde5}.table-hover .table-info:hover>td,.table-hover .table-info:hover>th{background-color:#abdde5}.table-warning,.table-warning>td,.table-warning>th{background-color:#ffeeba}.table-warning tbody+tbody,.table-warning td,.table-warning th,.table-warning thead th{border-color:#ffdf7e}.table-hover .table-warning:hover{background-color:#ffe8a1}.table-hover .table-warning:hover>td,.table-hover .table-warning:hover>th{background-color:#ffe8a1}.table-danger,.table-danger>td,.table-danger>th{background-color:#f5c6cb}.table-danger tbody+tbody,.table-danger td,.table-danger th,.table-danger thead th{border-color:#ed969e}.table-hover .table-danger:hover{background-color:#f1b0b7}.table-hover .table-danger:hover>td,.table-hover .table-danger:hover>th{background-color:#f1b0b7}.table-light,.table-light>td,.table-light>th{background-color:#fdfdfe}.table-light tbody+tbody,.table-light td,.table-light th,.table-light thead th{border-color:#fbfcfc}.table-hover .table-light:hover{background-color:#ececf6}.table-hover .table-light:hover>td,.table-hover .table-light:hover>th{background-color:#ececf6}.table-dark,.table-dark>td,.table-dark>th{background-color:#c6c8ca}.table-dark tbody+tbody,.table-dark td,.table-dark th,.table-dark thead th{border-color:#95999c}.table-hover .table-dark:hover{background-color:#b9bbbe}.table-hover .table-dark:hover>td,.table-hover .table-dark:hover>th{background-color:#b9bbbe}.table-active,.table-active>td,.table-active>th{background-color:rgba(0,0,0,.075)}.table-hover .table-active:hover{background-color:rgba(0,0,0,.075)}.table-hover .table-active:hover>td,.table-hover .table-active:hover>th{background-color:rgba(0,0,0,.075)}.table .thead-dark th{color:#fff;background-color:#343a40;border-color:#454d55}.table .thead-light th{color:#495057;background-color:#e9ecef;border-color:#dee2e6}.table-dark{color:#fff;background-color:#343a40}.table-dark td,.table-dark th,.table-dark thead th{border-color:#454d55}.table-dark.table-bordered{border:0}.table-dark.table-striped tbody tr:nth-of-type(odd){background-color:rgba(255,255,255,.05)}.table-dark.table-hover tbody tr:hover{color:#fff;background-color:rgba(255,255,255,.075)}@media (max-width:575.98px){.table-responsive-sm{display:block;width:100%;overflow-x:auto;-webkit-overflow-scrolling:touch}.table-responsive-sm>.table-bordered{border:0}}@media (max-width:767.98px){.table-responsive-md{display:block;width:100%;overflow-x:auto;-webkit-overflow-scrolling:touch}.table-responsive-md>.table-bordered{border:0}}@media (max-width:991.98px){.table-responsive-lg{display:block;width:100%;overflow-x:auto;-webkit-overflow-scrolling:touch}.table-responsive-lg>.table-bordered{border:0}}@media (max-width:1199.98px){.table-responsive-xl{display:block;width:100%;overflow-x:auto;-webkit-overflow-scrolling:touch}.table-responsive-xl>.table-bordered{border:0}}.table-responsive{display:block;width:100%;overflow-x:auto;-webkit-overflow-scrolling:touch}.table-responsive>.table-bordered{border:0}.form-control{display:block;width:100%;height:calc(1.5em + .75rem + 2px);padding:.375rem .75rem;font-size:1rem;font-weight:400;line-height:1.5;color:#495057;background-color:#fff;background-clip:padding-box;border:1px solid #ced4da;border-radius:.25rem;transition:border-color .15s ease-in-out,box-shadow .15s ease-in-out}@media (prefers-reduced-motion:reduce){.form-control{transition:none}}.form-control::-ms-expand{background-color:transparent;border:0}.form-control:focus{color:#495057;background-color:#fff;border-color:#80bdff;outline:0;box-shadow:0 0 0 .2rem rgba(0,123,255,.25)}.form-control::-webkit-input-placeholder{color:#6c757d;opacity:1}.form-control::-moz-placeholder{color:#6c757d;opacity:1}.form-control:-ms-input-placeholder{color:#6c757d;opacity:1}.form-control::-ms-input-placeholder{color:#6c757d;opacity:1}.form-control::placeholder{color:#6c757d;opacity:1}.form-control:disabled,.form-control[readonly]{background-color:#e9ecef;opacity:1}input[type=date].form-control,input[type=datetime-local].form-control,input[type=month].form-control,input[type=time].form-control{-webkit-appearance:none;-moz-appearance:none;appearance:none}select.form-control:-moz-focusring{color:transparent;text-shadow:0 0 0 #495057}select.form-control:focus::-ms-value{color:#495057;background-color:#fff}.form-control-file,.form-control-range{display:block;width:100%}.col-form-label{padding-top:calc(.375rem + 1px);padding-bottom:calc(.375rem + 1px);margin-bottom:0;font-size:inherit;line-height:1.5}.col-form-label-lg{padding-top:calc(.5rem + 1px);padding-bottom:calc(.5rem + 1px);font-size:1.25rem;line-height:1.5}.col-form-label-sm{padding-top:calc(.25rem + 1px);padding-bottom:calc(.25rem + 1px);font-size:.875rem;line-height:1.5}.form-control-plaintext{display:block;width:100%;padding:.375rem 0;margin-bottom:0;font-size:1rem;line-height:1.5;color:#212529;background-color:transparent;border:solid transparent;border-width:1px 0}.form-control-plaintext.form-control-lg,.form-control-plaintext.form-control-sm{padding-right:0;padding-left:0}.form-control-sm{height:calc(1.5em + .5rem + 2px);padding:.25rem .5rem;font-size:.875rem;line-height:1.5;border-radius:.2rem}.form-control-lg{height:calc(1.5em + 1rem + 2px);padding:.5rem 1rem;font-size:1.25rem;line-height:1.5;border-radius:.3rem}select.form-control[multiple],select.form-control[size]{height:auto}textarea.form-control{height:auto}.form-group{margin-bottom:1rem}.form-text{display:block;margin-top:.25rem}.form-row{display:-ms-flexbox;display:flex;-ms-flex-wrap:wrap;flex-wrap:wrap;margin-right:-5px;margin-left:-5px}.form-row>.col,.form-row>[class*=col-]{padding-right:5px;padding-left:5px}.form-check{position:relative;display:block;padding-left:1.25rem}.form-check-input{position:absolute;margin-top:.3rem;margin-left:-1.25rem}.form-check-input:disabled~.form-check-label,.form-check-input[disabled]~.form-check-label{color:#6c757d}.form-check-label{margin-bottom:0}.form-check-inline{display:-ms-inline-flexbox;display:inline-flex;-ms-flex-align:center;align-items:center;padding-left:0;margin-right:.75rem}.form-check-inline .form-check-input{position:static;margin-top:0;margin-right:.3125rem;margin-left:0}.valid-feedback{display:none;width:100%;margin-top:.25rem;font-size:.875em;color:#28a745}.valid-tooltip{position:absolute;top:100%;left:0;z-index:5;display:none;max-width:100%;padding:.25rem .5rem;margin-top:.1rem;font-size:.875rem;line-height:1.5;color:#fff;background-color:rgba(40,167,69,.9);border-radius:.25rem}.form-row>.col>.valid-tooltip,.form-row>[class*=col-]>.valid-tooltip{left:5px}.is-valid~.valid-feedback,.is-valid~.valid-tooltip,.was-validated :valid~.valid-feedback,.was-validated :valid~.valid-tooltip{display:block}.form-control.is-valid,.was-validated .form-control:valid{border-color:#28a745;padding-right:calc(1.5em + .75rem)!important;background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='8' height='8' viewBox='0 0 8 8'%3e%3cpath fill='%2328a745' d='M2.3 6.73L.6 4.53c-.4-1.04.46-1.4 1.1-.8l1.1 1.4 3.4-3.8c.6-.63 1.6-.27 1.2.7l-4 4.6c-.43.5-.8.4-1.1.1z'/%3e%3c/svg%3e");background-repeat:no-repeat;background-position:right calc(.375em + .1875rem) center;background-size:calc(.75em + .375rem) calc(.75em + .375rem)}.form-control.is-valid:focus,.was-validated .form-control:valid:focus{border-color:#28a745;box-shadow:0 0 0 .2rem rgba(40,167,69,.25)}.was-validated select.form-control:valid,select.form-control.is-valid{padding-right:3rem!important;background-position:right 1.5rem center}.was-validated textarea.form-control:valid,textarea.form-control.is-valid{padding-right:calc(1.5em + .75rem);background-position:top calc(.375em + .1875rem) right calc(.375em + .1875rem)}.custom-select.is-valid,.was-validated .custom-select:valid{border-color:#28a745;padding-right:calc(.75em + 2.3125rem)!important;background:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='4' height='5' viewBox='0 0 4 5'%3e%3cpath fill='%23343a40' d='M2 0L0 2h4zm0 5L0 3h4z'/%3e%3c/svg%3e") right .75rem center/8px 10px no-repeat,#fff url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='8' height='8' viewBox='0 0 8 8'%3e%3cpath fill='%2328a745' d='M2.3 6.73L.6 4.53c-.4-1.04.46-1.4 1.1-.8l1.1 1.4 3.4-3.8c.6-.63 1.6-.27 1.2.7l-4 4.6c-.43.5-.8.4-1.1.1z'/%3e%3c/svg%3e") center right 1.75rem/calc(.75em + .375rem) calc(.75em + .375rem) no-repeat}.custom-select.is-valid:focus,.was-validated .custom-select:valid:focus{border-color:#28a745;box-shadow:0 0 0 .2rem rgba(40,167,69,.25)}.form-check-input.is-valid~.form-check-label,.was-validated .form-check-input:valid~.form-check-label{color:#28a745}.form-check-input.is-valid~.valid-feedback,.form-check-input.is-valid~.valid-tooltip,.was-validated .form-check-input:valid~.valid-feedback,.was-validated .form-check-input:valid~.valid-tooltip{display:block}.custom-control-input.is-valid~.custom-control-label,.was-validated .custom-control-input:valid~.custom-control-label{color:#28a745}.custom-control-input.is-valid~.custom-control-label::before,.was-validated .custom-control-input:valid~.custom-control-label::before{border-color:#28a745}.custom-control-input.is-valid:checked~.custom-control-label::before,.was-validated .custom-control-input:valid:checked~.custom-control-label::before{border-color:#34ce57;background-color:#34ce57}.custom-control-input.is-valid:focus~.custom-control-label::before,.was-validated .custom-control-input:valid:focus~.custom-control-label::before{box-shadow:0 0 0 .2rem rgba(40,167,69,.25)}.custom-control-input.is-valid:focus:not(:checked)~.custom-control-label::before,.was-validated .custom-control-input:valid:focus:not(:checked)~.custom-control-label::before{border-color:#28a745}.custom-file-input.is-valid~.custom-file-label,.was-validated .custom-file-input:valid~.custom-file-label{border-color:#28a745}.custom-file-input.is-valid:focus~.custom-file-label,.was-validated .custom-file-input:valid:focus~.custom-file-label{border-color:#28a745;box-shadow:0 0 0 .2rem rgba(40,167,69,.25)}.invalid-feedback{display:none;width:100%;margin-top:.25rem;font-size:.875em;color:#dc3545}.invalid-tooltip{position:absolute;top:100%;left:0;z-index:5;display:none;max-width:100%;padding:.25rem .5rem;margin-top:.1rem;font-size:.875rem;line-height:1.5;color:#fff;background-color:rgba(220,53,69,.9);border-radius:.25rem}.form-row>.col>.invalid-tooltip,.form-row>[class*=col-]>.invalid-tooltip{left:5px}.is-invalid~.invalid-feedback,.is-invalid~.invalid-tooltip,.was-validated :invalid~.invalid-feedback,.was-validated :invalid~.invalid-tooltip{display:block}.form-control.is-invalid,.was-validated .form-control:invalid{border-color:#dc3545;padding-right:calc(1.5em + .75rem)!important;background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' fill='none' stroke='%23dc3545' viewBox='0 0 12 12'%3e%3ccircle cx='6' cy='6' r='4.5'/%3e%3cpath stroke-linejoin='round' d='M5.8 3.6h.4L6 6.5z'/%3e%3ccircle cx='6' cy='8.2' r='.6' fill='%23dc3545' stroke='none'/%3e%3c/svg%3e");background-repeat:no-repeat;background-position:right calc(.375em + .1875rem) center;background-size:calc(.75em + .375rem) calc(.75em + .375rem)}.form-control.is-invalid:focus,.was-validated .form-control:invalid:focus{border-color:#dc3545;box-shadow:0 0 0 .2rem rgba(220,53,69,.25)}.was-validated select.form-control:invalid,select.form-control.is-invalid{padding-right:3rem!important;background-position:right 1.5rem center}.was-validated textarea.form-control:invalid,textarea.form-control.is-invalid{padding-right:calc(1.5em + .75rem);background-position:top calc(.375em + .1875rem) right calc(.375em + .1875rem)}.custom-select.is-invalid,.was-validated .custom-select:invalid{border-color:#dc3545;padding-right:calc(.75em + 2.3125rem)!important;background:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='4' height='5' viewBox='0 0 4 5'%3e%3cpath fill='%23343a40' d='M2 0L0 2h4zm0 5L0 3h4z'/%3e%3c/svg%3e") right .75rem center/8px 10px no-repeat,#fff url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' fill='none' stroke='%23dc3545' viewBox='0 0 12 12'%3e%3ccircle cx='6' cy='6' r='4.5'/%3e%3cpath stroke-linejoin='round' d='M5.8 3.6h.4L6 6.5z'/%3e%3ccircle cx='6' cy='8.2' r='.6' fill='%23dc3545' stroke='none'/%3e%3c/svg%3e") center right 1.75rem/calc(.75em + .375rem) calc(.75em + .375rem) no-repeat}.custom-select.is-invalid:focus,.was-validated .custom-select:invalid:focus{border-color:#dc3545;box-shadow:0 0 0 .2rem rgba(220,53,69,.25)}.form-check-input.is-invalid~.form-check-label,.was-validated .form-check-input:invalid~.form-check-label{color:#dc3545}.form-check-input.is-invalid~.invalid-feedback,.form-check-input.is-invalid~.invalid-tooltip,.was-validated .form-check-input:invalid~.invalid-feedback,.was-validated .form-check-input:invalid~.invalid-tooltip{display:block}.custom-control-input.is-invalid~.custom-control-label,.was-validated .custom-control-input:invalid~.custom-control-label{color:#dc3545}.custom-control-input.is-invalid~.custom-control-label::before,.was-validated .custom-control-input:invalid~.custom-control-label::before{border-color:#dc3545}.custom-control-input.is-invalid:checked~.custom-control-label::before,.was-validated .custom-control-input:invalid:checked~.custom-control-label::before{border-color:#e4606d;background-color:#e4606d}.custom-control-input.is-invalid:focus~.custom-control-label::before,.was-validated .custom-control-input:invalid:focus~.custom-control-label::before{box-shadow:0 0 0 .2rem rgba(220,53,69,.25)}.custom-control-input.is-invalid:focus:not(:checked)~.custom-control-label::before,.was-validated .custom-control-input:invalid:focus:not(:checked)~.custom-control-label::before{border-color:#dc3545}.custom-file-input.is-invalid~.custom-file-label,.was-validated .custom-file-input:invalid~.custom-file-label{border-color:#dc3545}.custom-file-input.is-invalid:focus~.custom-file-label,.was-validated .custom-file-input:invalid:focus~.custom-file-label{border-color:#dc3545;box-shadow:0 0 0 .2rem rgba(220,53,69,.25)}.form-inline{display:-ms-flexbox;display:flex;-ms-flex-flow:row wrap;flex-flow:row wrap;-ms-flex-align:center;align-items:center}.form-inline .form-check{width:100%}@media (min-width:576px){.form-inline label{display:-ms-flexbox;display:flex;-ms-flex-align:center;align-items:center;-ms-flex-pack:center;justify-content:center;margin-bottom:0}.form-inline .form-group{display:-ms-flexbox;display:flex;-ms-flex:0 0 auto;flex:0 0 auto;-ms-flex-flow:row wrap;flex-flow:row wrap;-ms-flex-align:center;align-items:center;margin-bottom:0}.form-inline .form-control{display:inline-block;width:auto;vertical-align:middle}.form-inline .form-control-plaintext{display:inline-block}.form-inline .custom-select,.form-inline .input-group{width:auto}.form-inline .form-check{display:-ms-flexbox;display:flex;-ms-flex-align:center;align-items:center;-ms-flex-pack:center;justify-content:center;width:auto;padding-left:0}.form-inline .form-check-input{position:relative;-ms-flex-negative:0;flex-shrink:0;margin-top:0;margin-right:.25rem;margin-left:0}.form-inline .custom-control{-ms-flex-align:center;align-items:center;-ms-flex-pack:center;justify-content:center}.form-inline .custom-control-label{margin-bottom:0}}.btn{display:inline-block;font-weight:400;color:#212529;text-align:center;vertical-align:middle;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;background-color:transparent;border:1px solid transparent;padding:.375rem .75rem;font-size:1rem;line-height:1.5;border-radius:.25rem;transition:color .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out}@media (prefers-reduced-motion:reduce){.btn{transition:none}}.btn:hover{color:#212529;text-decoration:none}.btn.focus,.btn:focus{outline:0;box-shadow:0 0 0 .2rem rgba(0,123,255,.25)}.btn.disabled,.btn:disabled{opacity:.65}.btn:not(:disabled):not(.disabled){cursor:pointer}a.btn.disabled,fieldset:disabled a.btn{pointer-events:none}.btn-primary{color:#fff;background-color:#007bff;border-color:#007bff}.btn-primary:hover{color:#fff;background-color:#0069d9;border-color:#0062cc}.btn-primary.focus,.btn-primary:focus{color:#fff;background-color:#0069d9;border-color:#0062cc;box-shadow:0 0 0 .2rem rgba(38,143,255,.5)}.btn-primary.disabled,.btn-primary:disabled{color:#fff;background-color:#007bff;border-color:#007bff}.btn-primary:not(:disabled):not(.disabled).active,.btn-primary:not(:disabled):not(.disabled):active,.show>.btn-primary.dropdown-toggle{color:#fff;background-color:#0062cc;border-color:#005cbf}.btn-primary:not(:disabled):not(.disabled).active:focus,.btn-primary:not(:disabled):not(.disabled):active:focus,.show>.btn-primary.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(38,143,255,.5)}.btn-secondary{color:#fff;background-color:#6c757d;border-color:#6c757d}.btn-secondary:hover{color:#fff;background-color:#5a6268;border-color:#545b62}.btn-secondary.focus,.btn-secondary:focus{color:#fff;background-color:#5a6268;border-color:#545b62;box-shadow:0 0 0 .2rem rgba(130,138,145,.5)}.btn-secondary.disabled,.btn-secondary:disabled{color:#fff;background-color:#6c757d;border-color:#6c757d}.btn-secondary:not(:disabled):not(.disabled).active,.btn-secondary:not(:disabled):not(.disabled):active,.show>.btn-secondary.dropdown-toggle{color:#fff;background-color:#545b62;border-color:#4e555b}.btn-secondary:not(:disabled):not(.disabled).active:focus,.btn-secondary:not(:disabled):not(.disabled):active:focus,.show>.btn-secondary.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(130,138,145,.5)}.btn-success{color:#fff;background-color:#28a745;border-color:#28a745}.btn-success:hover{color:#fff;background-color:#218838;border-color:#1e7e34}.btn-success.focus,.btn-success:focus{color:#fff;background-color:#218838;border-color:#1e7e34;box-shadow:0 0 0 .2rem rgba(72,180,97,.5)}.btn-success.disabled,.btn-success:disabled{color:#fff;background-color:#28a745;border-color:#28a745}.btn-success:not(:disabled):not(.disabled).active,.btn-success:not(:disabled):not(.disabled):active,.show>.btn-success.dropdown-toggle{color:#fff;background-color:#1e7e34;border-color:#1c7430}.btn-success:not(:disabled):not(.disabled).active:focus,.btn-success:not(:disabled):not(.disabled):active:focus,.show>.btn-success.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(72,180,97,.5)}.btn-info{color:#fff;background-color:#17a2b8;border-color:#17a2b8}.btn-info:hover{color:#fff;background-color:#138496;border-color:#117a8b}.btn-info.focus,.btn-info:focus{color:#fff;background-color:#138496;border-color:#117a8b;box-shadow:0 0 0 .2rem rgba(58,176,195,.5)}.btn-info.disabled,.btn-info:disabled{color:#fff;background-color:#17a2b8;border-color:#17a2b8}.btn-info:not(:disabled):not(.disabled).active,.btn-info:not(:disabled):not(.disabled):active,.show>.btn-info.dropdown-toggle{color:#fff;background-color:#117a8b;border-color:#10707f}.btn-info:not(:disabled):not(.disabled).active:focus,.btn-info:not(:disabled):not(.disabled):active:focus,.show>.btn-info.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(58,176,195,.5)}.btn-warning{color:#212529;background-color:#ffc107;border-color:#ffc107}.btn-warning:hover{color:#212529;background-color:#e0a800;border-color:#d39e00}.btn-warning.focus,.btn-warning:focus{color:#212529;background-color:#e0a800;border-color:#d39e00;box-shadow:0 0 0 .2rem rgba(222,170,12,.5)}.btn-warning.disabled,.btn-warning:disabled{color:#212529;background-color:#ffc107;border-color:#ffc107}.btn-warning:not(:disabled):not(.disabled).active,.btn-warning:not(:disabled):not(.disabled):active,.show>.btn-warning.dropdown-toggle{color:#212529;background-color:#d39e00;border-color:#c69500}.btn-warning:not(:disabled):not(.disabled).active:focus,.btn-warning:not(:disabled):not(.disabled):active:focus,.show>.btn-warning.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(222,170,12,.5)}.btn-danger{color:#fff;background-color:#dc3545;border-color:#dc3545}.btn-danger:hover{color:#fff;background-color:#c82333;border-color:#bd2130}.btn-danger.focus,.btn-danger:focus{color:#fff;background-color:#c82333;border-color:#bd2130;box-shadow:0 0 0 .2rem rgba(225,83,97,.5)}.btn-danger.disabled,.btn-danger:disabled{color:#fff;background-color:#dc3545;border-color:#dc3545}.btn-danger:not(:disabled):not(.disabled).active,.btn-danger:not(:disabled):not(.disabled):active,.show>.btn-danger.dropdown-toggle{color:#fff;background-color:#bd2130;border-color:#b21f2d}.btn-danger:not(:disabled):not(.disabled).active:focus,.btn-danger:not(:disabled):not(.disabled):active:focus,.show>.btn-danger.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(225,83,97,.5)}.btn-light{color:#212529;background-color:#f8f9fa;border-color:#f8f9fa}.btn-light:hover{color:#212529;background-color:#e2e6ea;border-color:#dae0e5}.btn-light.focus,.btn-light:focus{color:#212529;background-color:#e2e6ea;border-color:#dae0e5;box-shadow:0 0 0 .2rem rgba(216,217,219,.5)}.btn-light.disabled,.btn-light:disabled{color:#212529;background-color:#f8f9fa;border-color:#f8f9fa}.btn-light:not(:disabled):not(.disabled).active,.btn-light:not(:disabled):not(.disabled):active,.show>.btn-light.dropdown-toggle{color:#212529;background-color:#dae0e5;border-color:#d3d9df}.btn-light:not(:disabled):not(.disabled).active:focus,.btn-light:not(:disabled):not(.disabled):active:focus,.show>.btn-light.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(216,217,219,.5)}.btn-dark{color:#fff;background-color:#343a40;border-color:#343a40}.btn-dark:hover{color:#fff;background-color:#23272b;border-color:#1d2124}.btn-dark.focus,.btn-dark:focus{color:#fff;background-color:#23272b;border-color:#1d2124;box-shadow:0 0 0 .2rem rgba(82,88,93,.5)}.btn-dark.disabled,.btn-dark:disabled{color:#fff;background-color:#343a40;border-color:#343a40}.btn-dark:not(:disabled):not(.disabled).active,.btn-dark:not(:disabled):not(.disabled):active,.show>.btn-dark.dropdown-toggle{color:#fff;background-color:#1d2124;border-color:#171a1d}.btn-dark:not(:disabled):not(.disabled).active:focus,.btn-dark:not(:disabled):not(.disabled):active:focus,.show>.btn-dark.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(82,88,93,.5)}.btn-outline-primary{color:#007bff;border-color:#007bff}.btn-outline-primary:hover{color:#fff;background-color:#007bff;border-color:#007bff}.btn-outline-primary.focus,.btn-outline-primary:focus{box-shadow:0 0 0 .2rem rgba(0,123,255,.5)}.btn-outline-primary.disabled,.btn-outline-primary:disabled{color:#007bff;background-color:transparent}.btn-outline-primary:not(:disabled):not(.disabled).active,.btn-outline-primary:not(:disabled):not(.disabled):active,.show>.btn-outline-primary.dropdown-toggle{color:#fff;background-color:#007bff;border-color:#007bff}.btn-outline-primary:not(:disabled):not(.disabled).active:focus,.btn-outline-primary:not(:disabled):not(.disabled):active:focus,.show>.btn-outline-primary.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(0,123,255,.5)}.btn-outline-secondary{color:#6c757d;border-color:#6c757d}.btn-outline-secondary:hover{color:#fff;background-color:#6c757d;border-color:#6c757d}.btn-outline-secondary.focus,.btn-outline-secondary:focus{box-shadow:0 0 0 .2rem rgba(108,117,125,.5)}.btn-outline-secondary.disabled,.btn-outline-secondary:disabled{color:#6c757d;background-color:transparent}.btn-outline-secondary:not(:disabled):not(.disabled).active,.btn-outline-secondary:not(:disabled):not(.disabled):active,.show>.btn-outline-secondary.dropdown-toggle{color:#fff;background-color:#6c757d;border-color:#6c757d}.btn-outline-secondary:not(:disabled):not(.disabled).active:focus,.btn-outline-secondary:not(:disabled):not(.disabled):active:focus,.show>.btn-outline-secondary.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(108,117,125,.5)}.btn-outline-success{color:#28a745;border-color:#28a745}.btn-outline-success:hover{color:#fff;background-color:#28a745;border-color:#28a745}.btn-outline-success.focus,.btn-outline-success:focus{box-shadow:0 0 0 .2rem rgba(40,167,69,.5)}.btn-outline-success.disabled,.btn-outline-success:disabled{color:#28a745;background-color:transparent}.btn-outline-success:not(:disabled):not(.disabled).active,.btn-outline-success:not(:disabled):not(.disabled):active,.show>.btn-outline-success.dropdown-toggle{color:#fff;background-color:#28a745;border-color:#28a745}.btn-outline-success:not(:disabled):not(.disabled).active:focus,.btn-outline-success:not(:disabled):not(.disabled):active:focus,.show>.btn-outline-success.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(40,167,69,.5)}.btn-outline-info{color:#17a2b8;border-color:#17a2b8}.btn-outline-info:hover{color:#fff;background-color:#17a2b8;border-color:#17a2b8}.btn-outline-info.focus,.btn-outline-info:focus{box-shadow:0 0 0 .2rem rgba(23,162,184,.5)}.btn-outline-info.disabled,.btn-outline-info:disabled{color:#17a2b8;background-color:transparent}.btn-outline-info:not(:disabled):not(.disabled).active,.btn-outline-info:not(:disabled):not(.disabled):active,.show>.btn-outline-info.dropdown-toggle{color:#fff;background-color:#17a2b8;border-color:#17a2b8}.btn-outline-info:not(:disabled):not(.disabled).active:focus,.btn-outline-info:not(:disabled):not(.disabled):active:focus,.show>.btn-outline-info.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(23,162,184,.5)}.btn-outline-warning{color:#ffc107;border-color:#ffc107}.btn-outline-warning:hover{color:#212529;background-color:#ffc107;border-color:#ffc107}.btn-outline-warning.focus,.btn-outline-warning:focus{box-shadow:0 0 0 .2rem rgba(255,193,7,.5)}.btn-outline-warning.disabled,.btn-outline-warning:disabled{color:#ffc107;background-color:transparent}.btn-outline-warning:not(:disabled):not(.disabled).active,.btn-outline-warning:not(:disabled):not(.disabled):active,.show>.btn-outline-warning.dropdown-toggle{color:#212529;background-color:#ffc107;border-color:#ffc107}.btn-outline-warning:not(:disabled):not(.disabled).active:focus,.btn-outline-warning:not(:disabled):not(.disabled):active:focus,.show>.btn-outline-warning.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(255,193,7,.5)}.btn-outline-danger{color:#dc3545;border-color:#dc3545}.btn-outline-danger:hover{color:#fff;background-color:#dc3545;border-color:#dc3545}.btn-outline-danger.focus,.btn-outline-danger:focus{box-shadow:0 0 0 .2rem rgba(220,53,69,.5)}.btn-outline-danger.disabled,.btn-outline-danger:disabled{color:#dc3545;background-color:transparent}.btn-outline-danger:not(:disabled):not(.disabled).active,.btn-outline-danger:not(:disabled):not(.disabled):active,.show>.btn-outline-danger.dropdown-toggle{color:#fff;background-color:#dc3545;border-color:#dc3545}.btn-outline-danger:not(:disabled):not(.disabled).active:focus,.btn-outline-danger:not(:disabled):not(.disabled):active:focus,.show>.btn-outline-danger.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(220,53,69,.5)}.btn-outline-light{color:#f8f9fa;border-color:#f8f9fa}.btn-outline-light:hover{color:#212529;background-color:#f8f9fa;border-color:#f8f9fa}.btn-outline-light.focus,.btn-outline-light:focus{box-shadow:0 0 0 .2rem rgba(248,249,250,.5)}.btn-outline-light.disabled,.btn-outline-light:disabled{color:#f8f9fa;background-color:transparent}.btn-outline-light:not(:disabled):not(.disabled).active,.btn-outline-light:not(:disabled):not(.disabled):active,.show>.btn-outline-light.dropdown-toggle{color:#212529;background-color:#f8f9fa;border-color:#f8f9fa}.btn-outline-light:not(:disabled):not(.disabled).active:focus,.btn-outline-light:not(:disabled):not(.disabled):active:focus,.show>.btn-outline-light.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(248,249,250,.5)}.btn-outline-dark{color:#343a40;border-color:#343a40}.btn-outline-dark:hover{color:#fff;background-color:#343a40;border-color:#343a40}.btn-outline-dark.focus,.btn-outline-dark:focus{box-shadow:0 0 0 .2rem rgba(52,58,64,.5)}.btn-outline-dark.disabled,.btn-outline-dark:disabled{color:#343a40;background-color:transparent}.btn-outline-dark:not(:disabled):not(.disabled).active,.btn-outline-dark:not(:disabled):not(.disabled):active,.show>.btn-outline-dark.dropdown-toggle{color:#fff;background-color:#343a40;border-color:#343a40}.btn-outline-dark:not(:disabled):not(.disabled).active:focus,.btn-outline-dark:not(:disabled):not(.disabled):active:focus,.show>.btn-outline-dark.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(52,58,64,.5)}.btn-link{font-weight:400;color:#007bff;text-decoration:none}.btn-link:hover{color:#0056b3;text-decoration:underline}.btn-link.focus,.btn-link:focus{text-decoration:underline}.btn-link.disabled,.btn-link:disabled{color:#6c757d;pointer-events:none}.btn-group-lg>.btn,.btn-lg{padding:.5rem 1rem;font-size:1.25rem;line-height:1.5;border-radius:.3rem}.btn-group-sm>.btn,.btn-sm{padding:.25rem .5rem;font-size:.875rem;line-height:1.5;border-radius:.2rem}.btn-block{display:block;width:100%}.btn-block+.btn-block{margin-top:.5rem}input[type=button].btn-block,input[type=reset].btn-block,input[type=submit].btn-block{width:100%}.fade{transition:opacity .15s linear}@media (prefers-reduced-motion:reduce){.fade{transition:none}}.fade:not(.show){opacity:0}.collapse:not(.show){display:none}.collapsing{position:relative;height:0;overflow:hidden;transition:height .35s ease}@media (prefers-reduced-motion:reduce){.collapsing{transition:none}}.collapsing.width{width:0;height:auto;transition:width .35s ease}@media (prefers-reduced-motion:reduce){.collapsing.width{transition:none}}.dropdown,.dropleft,.dropright,.dropup{position:relative}.dropdown-toggle{white-space:nowrap}.dropdown-toggle::after{display:inline-block;margin-left:.255em;vertical-align:.255em;content:"";border-top:.3em solid;border-right:.3em solid transparent;border-bottom:0;border-left:.3em solid transparent}.dropdown-toggle:empty::after{margin-left:0}.dropdown-menu{position:absolute;top:100%;left:0;z-index:1000;display:none;float:left;min-width:10rem;padding:.5rem 0;margin:.125rem 0 0;font-size:1rem;color:#212529;text-align:left;list-style:none;background-color:#fff;background-clip:padding-box;border:1px solid rgba(0,0,0,.15);border-radius:.25rem}.dropdown-menu-left{right:auto;left:0}.dropdown-menu-right{right:0;left:auto}@media (min-width:576px){.dropdown-menu-sm-left{right:auto;left:0}.dropdown-menu-sm-right{right:0;left:auto}}@media (min-width:768px){.dropdown-menu-md-left{right:auto;left:0}.dropdown-menu-md-right{right:0;left:auto}}@media (min-width:992px){.dropdown-menu-lg-left{right:auto;left:0}.dropdown-menu-lg-right{right:0;left:auto}}@media (min-width:1200px){.dropdown-menu-xl-left{right:auto;left:0}.dropdown-menu-xl-right{right:0;left:auto}}.dropup .dropdown-menu{top:auto;bottom:100%;margin-top:0;margin-bottom:.125rem}.dropup .dropdown-toggle::after{display:inline-block;margin-left:.255em;vertical-align:.255em;content:"";border-top:0;border-right:.3em solid transparent;border-bottom:.3em solid;border-left:.3em solid transparent}.dropup .dropdown-toggle:empty::after{margin-left:0}.dropright .dropdown-menu{top:0;right:auto;left:100%;margin-top:0;margin-left:.125rem}.dropright .dropdown-toggle::after{display:inline-block;margin-left:.255em;vertical-align:.255em;content:"";border-top:.3em solid transparent;border-right:0;border-bottom:.3em solid transparent;border-left:.3em solid}.dropright .dropdown-toggle:empty::after{margin-left:0}.dropright .dropdown-toggle::after{vertical-align:0}.dropleft .dropdown-menu{top:0;right:100%;left:auto;margin-top:0;margin-right:.125rem}.dropleft .dropdown-toggle::after{display:inline-block;margin-left:.255em;vertical-align:.255em;content:""}.dropleft .dropdown-toggle::after{display:none}.dropleft .dropdown-toggle::before{display:inline-block;margin-right:.255em;vertical-align:.255em;content:"";border-top:.3em solid transparent;border-right:.3em solid;border-bottom:.3em solid transparent}.dropleft .dropdown-toggle:empty::after{margin-left:0}.dropleft .dropdown-toggle::before{vertical-align:0}.dropdown-menu[x-placement^=bottom],.dropdown-menu[x-placement^=left],.dropdown-menu[x-placement^=right],.dropdown-menu[x-placement^=top]{right:auto;bottom:auto}.dropdown-divider{height:0;margin:.5rem 0;overflow:hidden;border-top:1px solid #e9ecef}.dropdown-item{display:block;width:100%;padding:.25rem 1.5rem;clear:both;font-weight:400;color:#212529;text-align:inherit;white-space:nowrap;background-color:transparent;border:0}.dropdown-item:focus,.dropdown-item:hover{color:#16181b;text-decoration:none;background-color:#e9ecef}.dropdown-item.active,.dropdown-item:active{color:#fff;text-decoration:none;background-color:#007bff}.dropdown-item.disabled,.dropdown-item:disabled{color:#adb5bd;pointer-events:none;background-color:transparent}.dropdown-menu.show{display:block}.dropdown-header{display:block;padding:.5rem 1.5rem;margin-bottom:0;font-size:.875rem;color:#6c757d;white-space:nowrap}.dropdown-item-text{display:block;padding:.25rem 1.5rem;color:#212529}.btn-group,.btn-group-vertical{position:relative;display:-ms-inline-flexbox;display:inline-flex;vertical-align:middle}.btn-group-vertical>.btn,.btn-group>.btn{position:relative;-ms-flex:1 1 auto;flex:1 1 auto}.btn-group-vertical>.btn:hover,.btn-group>.btn:hover{z-index:1}.btn-group-vertical>.btn.active,.btn-group-vertical>.btn:active,.btn-group-vertical>.btn:focus,.btn-group>.btn.active,.btn-group>.btn:active,.btn-group>.btn:focus{z-index:1}.btn-toolbar{display:-ms-flexbox;display:flex;-ms-flex-wrap:wrap;flex-wrap:wrap;-ms-flex-pack:start;justify-content:flex-start}.btn-toolbar .input-group{width:auto}.btn-group>.btn-group:not(:first-child),.btn-group>.btn:not(:first-child){margin-left:-1px}.btn-group>.btn-group:not(:last-child)>.btn,.btn-group>.btn:not(:last-child):not(.dropdown-toggle){border-top-right-radius:0;border-bottom-right-radius:0}.btn-group>.btn-group:not(:first-child)>.btn,.btn-group>.btn:not(:first-child){border-top-left-radius:0;border-bottom-left-radius:0}.dropdown-toggle-split{padding-right:.5625rem;padding-left:.5625rem}.dropdown-toggle-split::after,.dropright .dropdown-toggle-split::after,.dropup .dropdown-toggle-split::after{margin-left:0}.dropleft .dropdown-toggle-split::before{margin-right:0}.btn-group-sm>.btn+.dropdown-toggle-split,.btn-sm+.dropdown-toggle-split{padding-right:.375rem;padding-left:.375rem}.btn-group-lg>.btn+.dropdown-toggle-split,.btn-lg+.dropdown-toggle-split{padding-right:.75rem;padding-left:.75rem}.btn-group-vertical{-ms-flex-direction:column;flex-direction:column;-ms-flex-align:start;align-items:flex-start;-ms-flex-pack:center;justify-content:center}.btn-group-vertical>.btn,.btn-group-vertical>.btn-group{width:100%}.btn-group-vertical>.btn-group:not(:first-child),.btn-group-vertical>.btn:not(:first-child){margin-top:-1px}.btn-group-vertical>.btn-group:not(:last-child)>.btn,.btn-group-vertical>.btn:not(:last-child):not(.dropdown-toggle){border-bottom-right-radius:0;border-bottom-left-radius:0}.btn-group-vertical>.btn-group:not(:first-child)>.btn,.btn-group-vertical>.btn:not(:first-child){border-top-left-radius:0;border-top-right-radius:0}.btn-group-toggle>.btn,.btn-group-toggle>.btn-group>.btn{margin-bottom:0}.btn-group-toggle>.btn input[type=checkbox],.btn-group-toggle>.btn input[type=radio],.btn-group-toggle>.btn-group>.btn input[type=checkbox],.btn-group-toggle>.btn-group>.btn input[type=radio]{position:absolute;clip:rect(0,0,0,0);pointer-events:none}.input-group{position:relative;display:-ms-flexbox;display:flex;-ms-flex-wrap:wrap;flex-wrap:wrap;-ms-flex-align:stretch;align-items:stretch;width:100%}.input-group>.custom-file,.input-group>.custom-select,.input-group>.form-control,.input-group>.form-control-plaintext{position:relative;-ms-flex:1 1 auto;flex:1 1 auto;width:1%;min-width:0;margin-bottom:0}.input-group>.custom-file+.custom-file,.input-group>.custom-file+.custom-select,.input-group>.custom-file+.form-control,.input-group>.custom-select+.custom-file,.input-group>.custom-select+.custom-select,.input-group>.custom-select+.form-control,.input-group>.form-control+.custom-file,.input-group>.form-control+.custom-select,.input-group>.form-control+.form-control,.input-group>.form-control-plaintext+.custom-file,.input-group>.form-control-plaintext+.custom-select,.input-group>.form-control-plaintext+.form-control{margin-left:-1px}.input-group>.custom-file .custom-file-input:focus~.custom-file-label,.input-group>.custom-select:focus,.input-group>.form-control:focus{z-index:3}.input-group>.custom-file .custom-file-input:focus{z-index:4}.input-group>.custom-select:not(:first-child),.input-group>.form-control:not(:first-child){border-top-left-radius:0;border-bottom-left-radius:0}.input-group>.custom-file{display:-ms-flexbox;display:flex;-ms-flex-align:center;align-items:center}.input-group>.custom-file:not(:last-child) .custom-file-label,.input-group>.custom-file:not(:last-child) .custom-file-label::after{border-top-right-radius:0;border-bottom-right-radius:0}.input-group>.custom-file:not(:first-child) .custom-file-label{border-top-left-radius:0;border-bottom-left-radius:0}.input-group:not(.has-validation)>.custom-file:not(:last-child) .custom-file-label,.input-group:not(.has-validation)>.custom-file:not(:last-child) .custom-file-label::after,.input-group:not(.has-validation)>.custom-select:not(:last-child),.input-group:not(.has-validation)>.form-control:not(:last-child){border-top-right-radius:0;border-bottom-right-radius:0}.input-group.has-validation>.custom-file:nth-last-child(n+3) .custom-file-label,.input-group.has-validation>.custom-file:nth-last-child(n+3) .custom-file-label::after,.input-group.has-validation>.custom-select:nth-last-child(n+3),.input-group.has-validation>.form-control:nth-last-child(n+3){border-top-right-radius:0;border-bottom-right-radius:0}.input-group-append,.input-group-prepend{display:-ms-flexbox;display:flex}.input-group-append .btn,.input-group-prepend .btn{position:relative;z-index:2}.input-group-append .btn:focus,.input-group-prepend .btn:focus{z-index:3}.input-group-append .btn+.btn,.input-group-append .btn+.input-group-text,.input-group-append .input-group-text+.btn,.input-group-append .input-group-text+.input-group-text,.input-group-prepend .btn+.btn,.input-group-prepend .btn+.input-group-text,.input-group-prepend .input-group-text+.btn,.input-group-prepend .input-group-text+.input-group-text{margin-left:-1px}.input-group-prepend{margin-right:-1px}.input-group-append{margin-left:-1px}.input-group-text{display:-ms-flexbox;display:flex;-ms-flex-align:center;align-items:center;padding:.375rem .75rem;margin-bottom:0;font-size:1rem;font-weight:400;line-height:1.5;color:#495057;text-align:center;white-space:nowrap;background-color:#e9ecef;border:1px solid #ced4da;border-radius:.25rem}.input-group-text input[type=checkbox],.input-group-text input[type=radio]{margin-top:0}.input-group-lg>.custom-select,.input-group-lg>.form-control:not(textarea){height:calc(1.5em + 1rem + 2px)}.input-group-lg>.custom-select,.input-group-lg>.form-control,.input-group-lg>.input-group-append>.btn,.input-group-lg>.input-group-append>.input-group-text,.input-group-lg>.input-group-prepend>.btn,.input-group-lg>.input-group-prepend>.input-group-text{padding:.5rem 1rem;font-size:1.25rem;line-height:1.5;border-radius:.3rem}.input-group-sm>.custom-select,.input-group-sm>.form-control:not(textarea){height:calc(1.5em + .5rem + 2px)}.input-group-sm>.custom-select,.input-group-sm>.form-control,.input-group-sm>.input-group-append>.btn,.input-group-sm>.input-group-append>.input-group-text,.input-group-sm>.input-group-prepend>.btn,.input-group-sm>.input-group-prepend>.input-group-text{padding:.25rem .5rem;font-size:.875rem;line-height:1.5;border-radius:.2rem}.input-group-lg>.custom-select,.input-group-sm>.custom-select{padding-right:1.75rem}.input-group.has-validation>.input-group-append:nth-last-child(n+3)>.btn,.input-group.has-validation>.input-group-append:nth-last-child(n+3)>.input-group-text,.input-group:not(.has-validation)>.input-group-append:not(:last-child)>.btn,.input-group:not(.has-validation)>.input-group-append:not(:last-child)>.input-group-text,.input-group>.input-group-append:last-child>.btn:not(:last-child):not(.dropdown-toggle),.input-group>.input-group-append:last-child>.input-group-text:not(:last-child),.input-group>.input-group-prepend>.btn,.input-group>.input-group-prepend>.input-group-text{border-top-right-radius:0;border-bottom-right-radius:0}.input-group>.input-group-append>.btn,.input-group>.input-group-append>.input-group-text,.input-group>.input-group-prepend:first-child>.btn:not(:first-child),.input-group>.input-group-prepend:first-child>.input-group-text:not(:first-child),.input-group>.input-group-prepend:not(:first-child)>.btn,.input-group>.input-group-prepend:not(:first-child)>.input-group-text{border-top-left-radius:0;border-bottom-left-radius:0}.custom-control{position:relative;z-index:1;display:block;min-height:1.5rem;padding-left:1.5rem;-webkit-print-color-adjust:exact;color-adjust:exact;print-color-adjust:exact}.custom-control-inline{display:-ms-inline-flexbox;display:inline-flex;margin-right:1rem}.custom-control-input{position:absolute;left:0;z-index:-1;width:1rem;height:1.25rem;opacity:0}.custom-control-input:checked~.custom-control-label::before{color:#fff;border-color:#007bff;background-color:#007bff}.custom-control-input:focus~.custom-control-label::before{box-shadow:0 0 0 .2rem rgba(0,123,255,.25)}.custom-control-input:focus:not(:checked)~.custom-control-label::before{border-color:#80bdff}.custom-control-input:not(:disabled):active~.custom-control-label::before{color:#fff;background-color:#b3d7ff;border-color:#b3d7ff}.custom-control-input:disabled~.custom-control-label,.custom-control-input[disabled]~.custom-control-label{color:#6c757d}.custom-control-input:disabled~.custom-control-label::before,.custom-control-input[disabled]~.custom-control-label::before{background-color:#e9ecef}.custom-control-label{position:relative;margin-bottom:0;vertical-align:top}.custom-control-label::before{position:absolute;top:.25rem;left:-1.5rem;display:block;width:1rem;height:1rem;pointer-events:none;content:"";background-color:#fff;border:1px solid #adb5bd}.custom-control-label::after{position:absolute;top:.25rem;left:-1.5rem;display:block;width:1rem;height:1rem;content:"";background:50%/50% 50% no-repeat}.custom-checkbox .custom-control-label::before{border-radius:.25rem}.custom-checkbox .custom-control-input:checked~.custom-control-label::after{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='8' height='8' viewBox='0 0 8 8'%3e%3cpath fill='%23fff' d='M6.564.75l-3.59 3.612-1.538-1.55L0 4.26l2.974 2.99L8 2.193z'/%3e%3c/svg%3e")}.custom-checkbox .custom-control-input:indeterminate~.custom-control-label::before{border-color:#007bff;background-color:#007bff}.custom-checkbox .custom-control-input:indeterminate~.custom-control-label::after{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='4' height='4' viewBox='0 0 4 4'%3e%3cpath stroke='%23fff' d='M0 2h4'/%3e%3c/svg%3e")}.custom-checkbox .custom-control-input:disabled:checked~.custom-control-label::before{background-color:rgba(0,123,255,.5)}.custom-checkbox .custom-control-input:disabled:indeterminate~.custom-control-label::before{background-color:rgba(0,123,255,.5)}.custom-radio .custom-control-label::before{border-radius:50%}.custom-radio .custom-control-input:checked~.custom-control-label::after{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='-4 -4 8 8'%3e%3ccircle r='3' fill='%23fff'/%3e%3c/svg%3e")}.custom-radio .custom-control-input:disabled:checked~.custom-control-label::before{background-color:rgba(0,123,255,.5)}.custom-switch{padding-left:2.25rem}.custom-switch .custom-control-label::before{left:-2.25rem;width:1.75rem;pointer-events:all;border-radius:.5rem}.custom-switch .custom-control-label::after{top:calc(.25rem + 2px);left:calc(-2.25rem + 2px);width:calc(1rem - 4px);height:calc(1rem - 4px);background-color:#adb5bd;border-radius:.5rem;transition:background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out,-webkit-transform .15s ease-in-out;transition:transform .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out;transition:transform .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out,-webkit-transform .15s ease-in-out}@media (prefers-reduced-motion:reduce){.custom-switch .custom-control-label::after{transition:none}}.custom-switch .custom-control-input:checked~.custom-control-label::after{background-color:#fff;-webkit-transform:translateX(.75rem);transform:translateX(.75rem)}.custom-switch .custom-control-input:disabled:checked~.custom-control-label::before{background-color:rgba(0,123,255,.5)}.custom-select{display:inline-block;width:100%;height:calc(1.5em + .75rem + 2px);padding:.375rem 1.75rem .375rem .75rem;font-size:1rem;font-weight:400;line-height:1.5;color:#495057;vertical-align:middle;background:#fff url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='4' height='5' viewBox='0 0 4 5'%3e%3cpath fill='%23343a40' d='M2 0L0 2h4zm0 5L0 3h4z'/%3e%3c/svg%3e") right .75rem center/8px 10px no-repeat;border:1px solid #ced4da;border-radius:.25rem;-webkit-appearance:none;-moz-appearance:none;appearance:none}.custom-select:focus{border-color:#80bdff;outline:0;box-shadow:0 0 0 .2rem rgba(0,123,255,.25)}.custom-select:focus::-ms-value{color:#495057;background-color:#fff}.custom-select[multiple],.custom-select[size]:not([size="1"]){height:auto;padding-right:.75rem;background-image:none}.custom-select:disabled{color:#6c757d;background-color:#e9ecef}.custom-select::-ms-expand{display:none}.custom-select:-moz-focusring{color:transparent;text-shadow:0 0 0 #495057}.custom-select-sm{height:calc(1.5em + .5rem + 2px);padding-top:.25rem;padding-bottom:.25rem;padding-left:.5rem;font-size:.875rem}.custom-select-lg{height:calc(1.5em + 1rem + 2px);padding-top:.5rem;padding-bottom:.5rem;padding-left:1rem;font-size:1.25rem}.custom-file{position:relative;display:inline-block;width:100%;height:calc(1.5em + .75rem + 2px);margin-bottom:0}.custom-file-input{position:relative;z-index:2;width:100%;height:calc(1.5em + .75rem + 2px);margin:0;overflow:hidden;opacity:0}.custom-file-input:focus~.custom-file-label{border-color:#80bdff;box-shadow:0 0 0 .2rem rgba(0,123,255,.25)}.custom-file-input:disabled~.custom-file-label,.custom-file-input[disabled]~.custom-file-label{background-color:#e9ecef}.custom-file-input:lang(en)~.custom-file-label::after{content:"Browse"}.custom-file-input~.custom-file-label[data-browse]::after{content:attr(data-browse)}.custom-file-label{position:absolute;top:0;right:0;left:0;z-index:1;height:calc(1.5em + .75rem + 2px);padding:.375rem .75rem;overflow:hidden;font-weight:400;line-height:1.5;color:#495057;background-color:#fff;border:1px solid #ced4da;border-radius:.25rem}.custom-file-label::after{position:absolute;top:0;right:0;bottom:0;z-index:3;display:block;height:calc(1.5em + .75rem);padding:.375rem .75rem;line-height:1.5;color:#495057;content:"Browse";background-color:#e9ecef;border-left:inherit;border-radius:0 .25rem .25rem 0}.custom-range{width:100%;height:1.4rem;padding:0;background-color:transparent;-webkit-appearance:none;-moz-appearance:none;appearance:none}.custom-range:focus{outline:0}.custom-range:focus::-webkit-slider-thumb{box-shadow:0 0 0 1px #fff,0 0 0 .2rem rgba(0,123,255,.25)}.custom-range:focus::-moz-range-thumb{box-shadow:0 0 0 1px #fff,0 0 0 .2rem rgba(0,123,255,.25)}.custom-range:focus::-ms-thumb{box-shadow:0 0 0 1px #fff,0 0 0 .2rem rgba(0,123,255,.25)}.custom-range::-moz-focus-outer{border:0}.custom-range::-webkit-slider-thumb{width:1rem;height:1rem;margin-top:-.25rem;background-color:#007bff;border:0;border-radius:1rem;-webkit-transition:background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out;transition:background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out;-webkit-appearance:none;appearance:none}@media (prefers-reduced-motion:reduce){.custom-range::-webkit-slider-thumb{-webkit-transition:none;transition:none}}.custom-range::-webkit-slider-thumb:active{background-color:#b3d7ff}.custom-range::-webkit-slider-runnable-track{width:100%;height:.5rem;color:transparent;cursor:pointer;background-color:#dee2e6;border-color:transparent;border-radius:1rem}.custom-range::-moz-range-thumb{width:1rem;height:1rem;background-color:#007bff;border:0;border-radius:1rem;-moz-transition:background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out;transition:background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out;-moz-appearance:none;appearance:none}@media (prefers-reduced-motion:reduce){.custom-range::-moz-range-thumb{-moz-transition:none;transition:none}}.custom-range::-moz-range-thumb:active{background-color:#b3d7ff}.custom-range::-moz-range-track{width:100%;height:.5rem;color:transparent;cursor:pointer;background-color:#dee2e6;border-color:transparent;border-radius:1rem}.custom-range::-ms-thumb{width:1rem;height:1rem;margin-top:0;margin-right:.2rem;margin-left:.2rem;background-color:#007bff;border:0;border-radius:1rem;-ms-transition:background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out;transition:background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out;appearance:none}@media (prefers-reduced-motion:reduce){.custom-range::-ms-thumb{-ms-transition:none;transition:none}}.custom-range::-ms-thumb:active{background-color:#b3d7ff}.custom-range::-ms-track{width:100%;height:.5rem;color:transparent;cursor:pointer;background-color:transparent;border-color:transparent;border-width:.5rem}.custom-range::-ms-fill-lower{background-color:#dee2e6;border-radius:1rem}.custom-range::-ms-fill-upper{margin-right:15px;background-color:#dee2e6;border-radius:1rem}.custom-range:disabled::-webkit-slider-thumb{background-color:#adb5bd}.custom-range:disabled::-webkit-slider-runnable-track{cursor:default}.custom-range:disabled::-moz-range-thumb{background-color:#adb5bd}.custom-range:disabled::-moz-range-track{cursor:default}.custom-range:disabled::-ms-thumb{background-color:#adb5bd}.custom-control-label::before,.custom-file-label,.custom-select{transition:background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out}@media (prefers-reduced-motion:reduce){.custom-control-label::before,.custom-file-label,.custom-select{transition:none}}.nav{display:-ms-flexbox;display:flex;-ms-flex-wrap:wrap;flex-wrap:wrap;padding-left:0;margin-bottom:0;list-style:none}.nav-link{display:block;padding:.5rem 1rem}.nav-link:focus,.nav-link:hover{text-decoration:none}.nav-link.disabled{color:#6c757d;pointer-events:none;cursor:default}.nav-tabs{border-bottom:1px solid #dee2e6}.nav-tabs .nav-link{margin-bottom:-1px;background-color:transparent;border:1px solid transparent;border-top-left-radius:.25rem;border-top-right-radius:.25rem}.nav-tabs .nav-link:focus,.nav-tabs .nav-link:hover{isolation:isolate;border-color:#e9ecef #e9ecef #dee2e6}.nav-tabs .nav-link.disabled{color:#6c757d;background-color:transparent;border-color:transparent}.nav-tabs .nav-item.show .nav-link,.nav-tabs .nav-link.active{color:#495057;background-color:#fff;border-color:#dee2e6 #dee2e6 #fff}.nav-tabs .dropdown-menu{margin-top:-1px;border-top-left-radius:0;border-top-right-radius:0}.nav-pills .nav-link{background:0 0;border:0;border-radius:.25rem}.nav-pills .nav-link.active,.nav-pills .show>.nav-link{color:#fff;background-color:#007bff}.nav-fill .nav-item,.nav-fill>.nav-link{-ms-flex:1 1 auto;flex:1 1 auto;text-align:center}.nav-justified .nav-item,.nav-justified>.nav-link{-ms-flex-preferred-size:0;flex-basis:0;-ms-flex-positive:1;flex-grow:1;text-align:center}.tab-content>.tab-pane{display:none}.tab-content>.active{display:block}.navbar{position:relative;display:-ms-flexbox;display:flex;-ms-flex-wrap:wrap;flex-wrap:wrap;-ms-flex-align:center;align-items:center;-ms-flex-pack:justify;justify-content:space-between;padding:.5rem 1rem}.navbar .container,.navbar .container-fluid,.navbar .container-lg,.navbar .container-md,.navbar .container-sm,.navbar .container-xl{display:-ms-flexbox;display:flex;-ms-flex-wrap:wrap;flex-wrap:wrap;-ms-flex-align:center;align-items:center;-ms-flex-pack:justify;justify-content:space-between}.navbar-brand{display:inline-block;padding-top:.3125rem;padding-bottom:.3125rem;margin-right:1rem;font-size:1.25rem;line-height:inherit;white-space:nowrap}.navbar-brand:focus,.navbar-brand:hover{text-decoration:none}.navbar-nav{display:-ms-flexbox;display:flex;-ms-flex-direction:column;flex-direction:column;padding-left:0;margin-bottom:0;list-style:none}.navbar-nav .nav-link{padding-right:0;padding-left:0}.navbar-nav .dropdown-menu{position:static;float:none}.navbar-text{display:inline-block;padding-top:.5rem;padding-bottom:.5rem}.navbar-collapse{-ms-flex-preferred-size:100%;flex-basis:100%;-ms-flex-positive:1;flex-grow:1;-ms-flex-align:center;align-items:center}.navbar-toggler{padding:.25rem .75rem;font-size:1.25rem;line-height:1;background-color:transparent;border:1px solid transparent;border-radius:.25rem}.navbar-toggler:focus,.navbar-toggler:hover{text-decoration:none}.navbar-toggler-icon{display:inline-block;width:1.5em;height:1.5em;vertical-align:middle;content:"";background:50%/100% 100% no-repeat}.navbar-nav-scroll{max-height:75vh;overflow-y:auto}@media (max-width:575.98px){.navbar-expand-sm>.container,.navbar-expand-sm>.container-fluid,.navbar-expand-sm>.container-lg,.navbar-expand-sm>.container-md,.navbar-expand-sm>.container-sm,.navbar-expand-sm>.container-xl{padding-right:0;padding-left:0}}@media (min-width:576px){.navbar-expand-sm{-ms-flex-flow:row nowrap;flex-flow:row nowrap;-ms-flex-pack:start;justify-content:flex-start}.navbar-expand-sm .navbar-nav{-ms-flex-direction:row;flex-direction:row}.navbar-expand-sm .navbar-nav .dropdown-menu{position:absolute}.navbar-expand-sm .navbar-nav .nav-link{padding-right:.5rem;padding-left:.5rem}.navbar-expand-sm>.container,.navbar-expand-sm>.container-fluid,.navbar-expand-sm>.container-lg,.navbar-expand-sm>.container-md,.navbar-expand-sm>.container-sm,.navbar-expand-sm>.container-xl{-ms-flex-wrap:nowrap;flex-wrap:nowrap}.navbar-expand-sm .navbar-nav-scroll{overflow:visible}.navbar-expand-sm .navbar-collapse{display:-ms-flexbox!important;display:flex!important;-ms-flex-preferred-size:auto;flex-basis:auto}.navbar-expand-sm .navbar-toggler{display:none}}@media (max-width:767.98px){.navbar-expand-md>.container,.navbar-expand-md>.container-fluid,.navbar-expand-md>.container-lg,.navbar-expand-md>.container-md,.navbar-expand-md>.container-sm,.navbar-expand-md>.container-xl{padding-right:0;padding-left:0}}@media (min-width:768px){.navbar-expand-md{-ms-flex-flow:row nowrap;flex-flow:row nowrap;-ms-flex-pack:start;justify-content:flex-start}.navbar-expand-md .navbar-nav{-ms-flex-direction:row;flex-direction:row}.navbar-expand-md .navbar-nav .dropdown-menu{position:absolute}.navbar-expand-md .navbar-nav .nav-link{padding-right:.5rem;padding-left:.5rem}.navbar-expand-md>.container,.navbar-expand-md>.container-fluid,.navbar-expand-md>.container-lg,.navbar-expand-md>.container-md,.navbar-expand-md>.container-sm,.navbar-expand-md>.container-xl{-ms-flex-wrap:nowrap;flex-wrap:nowrap}.navbar-expand-md .navbar-nav-scroll{overflow:visible}.navbar-expand-md .navbar-collapse{display:-ms-flexbox!important;display:flex!important;-ms-flex-preferred-size:auto;flex-basis:auto}.navbar-expand-md .navbar-toggler{display:none}}@media (max-width:991.98px){.navbar-expand-lg>.container,.navbar-expand-lg>.container-fluid,.navbar-expand-lg>.container-lg,.navbar-expand-lg>.container-md,.navbar-expand-lg>.container-sm,.navbar-expand-lg>.container-xl{padding-right:0;padding-left:0}}@media (min-width:992px){.navbar-expand-lg{-ms-flex-flow:row nowrap;flex-flow:row nowrap;-ms-flex-pack:start;justify-content:flex-start}.navbar-expand-lg .navbar-nav{-ms-flex-direction:row;flex-direction:row}.navbar-expand-lg .navbar-nav .dropdown-menu{position:absolute}.navbar-expand-lg .navbar-nav .nav-link{padding-right:.5rem;padding-left:.5rem}.navbar-expand-lg>.container,.navbar-expand-lg>.container-fluid,.navbar-expand-lg>.container-lg,.navbar-expand-lg>.container-md,.navbar-expand-lg>.container-sm,.navbar-expand-lg>.container-xl{-ms-flex-wrap:nowrap;flex-wrap:nowrap}.navbar-expand-lg .navbar-nav-scroll{overflow:visible}.navbar-expand-lg .navbar-collapse{display:-ms-flexbox!important;display:flex!important;-ms-flex-preferred-size:auto;flex-basis:auto}.navbar-expand-lg .navbar-toggler{display:none}}@media (max-width:1199.98px){.navbar-expand-xl>.container,.navbar-expand-xl>.container-fluid,.navbar-expand-xl>.container-lg,.navbar-expand-xl>.container-md,.navbar-expand-xl>.container-sm,.navbar-expand-xl>.container-xl{padding-right:0;padding-left:0}}@media (min-width:1200px){.navbar-expand-xl{-ms-flex-flow:row nowrap;flex-flow:row nowrap;-ms-flex-pack:start;justify-content:flex-start}.navbar-expand-xl .navbar-nav{-ms-flex-direction:row;flex-direction:row}.navbar-expand-xl .navbar-nav .dropdown-menu{position:absolute}.navbar-expand-xl .navbar-nav .nav-link{padding-right:.5rem;padding-left:.5rem}.navbar-expand-xl>.container,.navbar-expand-xl>.container-fluid,.navbar-expand-xl>.container-lg,.navbar-expand-xl>.container-md,.navbar-expand-xl>.container-sm,.navbar-expand-xl>.container-xl{-ms-flex-wrap:nowrap;flex-wrap:nowrap}.navbar-expand-xl .navbar-nav-scroll{overflow:visible}.navbar-expand-xl .navbar-collapse{display:-ms-flexbox!important;display:flex!important;-ms-flex-preferred-size:auto;flex-basis:auto}.navbar-expand-xl .navbar-toggler{display:none}}.navbar-expand{-ms-flex-flow:row nowrap;flex-flow:row nowrap;-ms-flex-pack:start;justify-content:flex-start}.navbar-expand>.container,.navbar-expand>.container-fluid,.navbar-expand>.container-lg,.navbar-expand>.container-md,.navbar-expand>.container-sm,.navbar-expand>.container-xl{padding-right:0;padding-left:0}.navbar-expand .navbar-nav{-ms-flex-direction:row;flex-direction:row}.navbar-expand .navbar-nav .dropdown-menu{position:absolute}.navbar-expand .navbar-nav .nav-link{padding-right:.5rem;padding-left:.5rem}.navbar-expand>.container,.navbar-expand>.container-fluid,.navbar-expand>.container-lg,.navbar-expand>.container-md,.navbar-expand>.container-sm,.navbar-expand>.container-xl{-ms-flex-wrap:nowrap;flex-wrap:nowrap}.navbar-expand .navbar-nav-scroll{overflow:visible}.navbar-expand .navbar-collapse{display:-ms-flexbox!important;display:flex!important;-ms-flex-preferred-size:auto;flex-basis:auto}.navbar-expand .navbar-toggler{display:none}.navbar-light .navbar-brand{color:rgba(0,0,0,.9)}.navbar-light .navbar-brand:focus,.navbar-light .navbar-brand:hover{color:rgba(0,0,0,.9)}.navbar-light .navbar-nav .nav-link{color:rgba(0,0,0,.5)}.navbar-light .navbar-nav .nav-link:focus,.navbar-light .navbar-nav .nav-link:hover{color:rgba(0,0,0,.7)}.navbar-light .navbar-nav .nav-link.disabled{color:rgba(0,0,0,.3)}.navbar-light .navbar-nav .active>.nav-link,.navbar-light .navbar-nav .nav-link.active,.navbar-light .navbar-nav .nav-link.show,.navbar-light .navbar-nav .show>.nav-link{color:rgba(0,0,0,.9)}.navbar-light .navbar-toggler{color:rgba(0,0,0,.5);border-color:rgba(0,0,0,.1)}.navbar-light .navbar-toggler-icon{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='30' height='30' viewBox='0 0 30 30'%3e%3cpath stroke='rgba%280, 0, 0, 0.5%29' stroke-linecap='round' stroke-miterlimit='10' stroke-width='2' d='M4 7h22M4 15h22M4 23h22'/%3e%3c/svg%3e")}.navbar-light .navbar-text{color:rgba(0,0,0,.5)}.navbar-light .navbar-text a{color:rgba(0,0,0,.9)}.navbar-light .navbar-text a:focus,.navbar-light .navbar-text a:hover{color:rgba(0,0,0,.9)}.navbar-dark .navbar-brand{color:#fff}.navbar-dark .navbar-brand:focus,.navbar-dark .navbar-brand:hover{color:#fff}.navbar-dark .navbar-nav .nav-link{color:rgba(255,255,255,.5)}.navbar-dark .navbar-nav .nav-link:focus,.navbar-dark .navbar-nav .nav-link:hover{color:rgba(255,255,255,.75)}.navbar-dark .navbar-nav .nav-link.disabled{color:rgba(255,255,255,.25)}.navbar-dark .navbar-nav .active>.nav-link,.navbar-dark .navbar-nav .nav-link.active,.navbar-dark .navbar-nav .nav-link.show,.navbar-dark .navbar-nav .show>.nav-link{color:#fff}.navbar-dark .navbar-toggler{color:rgba(255,255,255,.5);border-color:rgba(255,255,255,.1)}.navbar-dark .navbar-toggler-icon{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='30' height='30' viewBox='0 0 30 30'%3e%3cpath stroke='rgba%28255, 255, 255, 0.5%29' stroke-linecap='round' stroke-miterlimit='10' stroke-width='2' d='M4 7h22M4 15h22M4 23h22'/%3e%3c/svg%3e")}.navbar-dark .navbar-text{color:rgba(255,255,255,.5)}.navbar-dark .navbar-text a{color:#fff}.navbar-dark .navbar-text a:focus,.navbar-dark .navbar-text a:hover{color:#fff}.card{position:relative;display:-ms-flexbox;display:flex;-ms-flex-direction:column;flex-direction:column;min-width:0;word-wrap:break-word;background-color:#fff;background-clip:border-box;border:1px solid rgba(0,0,0,.125);border-radius:.25rem}.card>hr{margin-right:0;margin-left:0}.card>.list-group{border-top:inherit;border-bottom:inherit}.card>.list-group:first-child{border-top-width:0;border-top-left-radius:calc(.25rem - 1px);border-top-right-radius:calc(.25rem - 1px)}.card>.list-group:last-child{border-bottom-width:0;border-bottom-right-radius:calc(.25rem - 1px);border-bottom-left-radius:calc(.25rem - 1px)}.card>.card-header+.list-group,.card>.list-group+.card-footer{border-top:0}.card-body{-ms-flex:1 1 auto;flex:1 1 auto;min-height:1px;padding:1.25rem}.card-title{margin-bottom:.75rem}.card-subtitle{margin-top:-.375rem;margin-bottom:0}.card-text:last-child{margin-bottom:0}.card-link:hover{text-decoration:none}.card-link+.card-link{margin-left:1.25rem}.card-header{padding:.75rem 1.25rem;margin-bottom:0;background-color:rgba(0,0,0,.03);border-bottom:1px solid rgba(0,0,0,.125)}.card-header:first-child{border-radius:calc(.25rem - 1px) calc(.25rem - 1px) 0 0}.card-footer{padding:.75rem 1.25rem;background-color:rgba(0,0,0,.03);border-top:1px solid rgba(0,0,0,.125)}.card-footer:last-child{border-radius:0 0 calc(.25rem - 1px) calc(.25rem - 1px)}.card-header-tabs{margin-right:-.625rem;margin-bottom:-.75rem;margin-left:-.625rem;border-bottom:0}.card-header-pills{margin-right:-.625rem;margin-left:-.625rem}.card-img-overlay{position:absolute;top:0;right:0;bottom:0;left:0;padding:1.25rem;border-radius:calc(.25rem - 1px)}.card-img,.card-img-bottom,.card-img-top{-ms-flex-negative:0;flex-shrink:0;width:100%}.card-img,.card-img-top{border-top-left-radius:calc(.25rem - 1px);border-top-right-radius:calc(.25rem - 1px)}.card-img,.card-img-bottom{border-bottom-right-radius:calc(.25rem - 1px);border-bottom-left-radius:calc(.25rem - 1px)}.card-deck .card{margin-bottom:15px}@media (min-width:576px){.card-deck{display:-ms-flexbox;display:flex;-ms-flex-flow:row wrap;flex-flow:row wrap;margin-right:-15px;margin-left:-15px}.card-deck .card{-ms-flex:1 0 0%;flex:1 0 0%;margin-right:15px;margin-bottom:0;margin-left:15px}}.card-group>.card{margin-bottom:15px}@media (min-width:576px){.card-group{display:-ms-flexbox;display:flex;-ms-flex-flow:row wrap;flex-flow:row wrap}.card-group>.card{-ms-flex:1 0 0%;flex:1 0 0%;margin-bottom:0}.card-group>.card+.card{margin-left:0;border-left:0}.card-group>.card:not(:last-child){border-top-right-radius:0;border-bottom-right-radius:0}.card-group>.card:not(:last-child) .card-header,.card-group>.card:not(:last-child) .card-img-top{border-top-right-radius:0}.card-group>.card:not(:last-child) .card-footer,.card-group>.card:not(:last-child) .card-img-bottom{border-bottom-right-radius:0}.card-group>.card:not(:first-child){border-top-left-radius:0;border-bottom-left-radius:0}.card-group>.card:not(:first-child) .card-header,.card-group>.card:not(:first-child) .card-img-top{border-top-left-radius:0}.card-group>.card:not(:first-child) .card-footer,.card-group>.card:not(:first-child) .card-img-bottom{border-bottom-left-radius:0}}.card-columns .card{margin-bottom:.75rem}@media (min-width:576px){.card-columns{-webkit-column-count:3;-moz-column-count:3;column-count:3;-webkit-column-gap:1.25rem;-moz-column-gap:1.25rem;column-gap:1.25rem;orphans:1;widows:1}.card-columns .card{display:inline-block;width:100%}}.accordion{overflow-anchor:none}.accordion>.card{overflow:hidden}.accordion>.card:not(:last-of-type){border-bottom:0;border-bottom-right-radius:0;border-bottom-left-radius:0}.accordion>.card:not(:first-of-type){border-top-left-radius:0;border-top-right-radius:0}.accordion>.card>.card-header{border-radius:0;margin-bottom:-1px}.breadcrumb{display:-ms-flexbox;display:flex;-ms-flex-wrap:wrap;flex-wrap:wrap;padding:.75rem 1rem;margin-bottom:1rem;list-style:none;background-color:#e9ecef;border-radius:.25rem}.breadcrumb-item+.breadcrumb-item{padding-left:.5rem}.breadcrumb-item+.breadcrumb-item::before{float:left;padding-right:.5rem;color:#6c757d;content:"/"}.breadcrumb-item+.breadcrumb-item:hover::before{text-decoration:underline}.breadcrumb-item+.breadcrumb-item:hover::before{text-decoration:none}.breadcrumb-item.active{color:#6c757d}.pagination{display:-ms-flexbox;display:flex;padding-left:0;list-style:none;border-radius:.25rem}.page-link{position:relative;display:block;padding:.5rem .75rem;margin-left:-1px;line-height:1.25;color:#007bff;background-color:#fff;border:1px solid #dee2e6}.page-link:hover{z-index:2;color:#0056b3;text-decoration:none;background-color:#e9ecef;border-color:#dee2e6}.page-link:focus{z-index:3;outline:0;box-shadow:0 0 0 .2rem rgba(0,123,255,.25)}.page-item:first-child .page-link{margin-left:0;border-top-left-radius:.25rem;border-bottom-left-radius:.25rem}.page-item:last-child .page-link{border-top-right-radius:.25rem;border-bottom-right-radius:.25rem}.page-item.active .page-link{z-index:3;color:#fff;background-color:#007bff;border-color:#007bff}.page-item.disabled .page-link{color:#6c757d;pointer-events:none;cursor:auto;background-color:#fff;border-color:#dee2e6}.pagination-lg .page-link{padding:.75rem 1.5rem;font-size:1.25rem;line-height:1.5}.pagination-lg .page-item:first-child .page-link{border-top-left-radius:.3rem;border-bottom-left-radius:.3rem}.pagination-lg .page-item:last-child .page-link{border-top-right-radius:.3rem;border-bottom-right-radius:.3rem}.pagination-sm .page-link{padding:.25rem .5rem;font-size:.875rem;line-height:1.5}.pagination-sm .page-item:first-child .page-link{border-top-left-radius:.2rem;border-bottom-left-radius:.2rem}.pagination-sm .page-item:last-child .page-link{border-top-right-radius:.2rem;border-bottom-right-radius:.2rem}.badge{display:inline-block;padding:.25em .4em;font-size:75%;font-weight:700;line-height:1;text-align:center;white-space:nowrap;vertical-align:baseline;border-radius:.25rem;transition:color .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out}@media (prefers-reduced-motion:reduce){.badge{transition:none}}a.badge:focus,a.badge:hover{text-decoration:none}.badge:empty{display:none}.btn .badge{position:relative;top:-1px}.badge-pill{padding-right:.6em;padding-left:.6em;border-radius:10rem}.badge-primary{color:#fff;background-color:#007bff}a.badge-primary:focus,a.badge-primary:hover{color:#fff;background-color:#0062cc}a.badge-primary.focus,a.badge-primary:focus{outline:0;box-shadow:0 0 0 .2rem rgba(0,123,255,.5)}.badge-secondary{color:#fff;background-color:#6c757d}a.badge-secondary:focus,a.badge-secondary:hover{color:#fff;background-color:#545b62}a.badge-secondary.focus,a.badge-secondary:focus{outline:0;box-shadow:0 0 0 .2rem rgba(108,117,125,.5)}.badge-success{color:#fff;background-color:#28a745}a.badge-success:focus,a.badge-success:hover{color:#fff;background-color:#1e7e34}a.badge-success.focus,a.badge-success:focus{outline:0;box-shadow:0 0 0 .2rem rgba(40,167,69,.5)}.badge-info{color:#fff;background-color:#17a2b8}a.badge-info:focus,a.badge-info:hover{color:#fff;background-color:#117a8b}a.badge-info.focus,a.badge-info:focus{outline:0;box-shadow:0 0 0 .2rem rgba(23,162,184,.5)}.badge-warning{color:#212529;background-color:#ffc107}a.badge-warning:focus,a.badge-warning:hover{color:#212529;background-color:#d39e00}a.badge-warning.focus,a.badge-warning:focus{outline:0;box-shadow:0 0 0 .2rem rgba(255,193,7,.5)}.badge-danger{color:#fff;background-color:#dc3545}a.badge-danger:focus,a.badge-danger:hover{color:#fff;background-color:#bd2130}a.badge-danger.focus,a.badge-danger:focus{outline:0;box-shadow:0 0 0 .2rem rgba(220,53,69,.5)}.badge-light{color:#212529;background-color:#f8f9fa}a.badge-light:focus,a.badge-light:hover{color:#212529;background-color:#dae0e5}a.badge-light.focus,a.badge-light:focus{outline:0;box-shadow:0 0 0 .2rem rgba(248,249,250,.5)}.badge-dark{color:#fff;background-color:#343a40}a.badge-dark:focus,a.badge-dark:hover{color:#fff;background-color:#1d2124}a.badge-dark.focus,a.badge-dark:focus{outline:0;box-shadow:0 0 0 .2rem rgba(52,58,64,.5)}.jumbotron{padding:2rem 1rem;margin-bottom:2rem;background-color:#e9ecef;border-radius:.3rem}@media (min-width:576px){.jumbotron{padding:4rem 2rem}}.jumbotron-fluid{padding-right:0;padding-left:0;border-radius:0}.alert{position:relative;padding:.75rem 1.25rem;margin-bottom:1rem;border:1px solid transparent;border-radius:.25rem}.alert-heading{color:inherit}.alert-link{font-weight:700}.alert-dismissible{padding-right:4rem}.alert-dismissible .close{position:absolute;top:0;right:0;z-index:2;padding:.75rem 1.25rem;color:inherit}.alert-primary{color:#004085;background-color:#cce5ff;border-color:#b8daff}.alert-primary hr{border-top-color:#9fcdff}.alert-primary .alert-link{color:#002752}.alert-secondary{color:#383d41;background-color:#e2e3e5;border-color:#d6d8db}.alert-secondary hr{border-top-color:#c8cbcf}.alert-secondary .alert-link{color:#202326}.alert-success{color:#155724;background-color:#d4edda;border-color:#c3e6cb}.alert-success hr{border-top-color:#b1dfbb}.alert-success .alert-link{color:#0b2e13}.alert-info{color:#0c5460;background-color:#d1ecf1;border-color:#bee5eb}.alert-info hr{border-top-color:#abdde5}.alert-info .alert-link{color:#062c33}.alert-warning{color:#856404;background-color:#fff3cd;border-color:#ffeeba}.alert-warning hr{border-top-color:#ffe8a1}.alert-warning .alert-link{color:#533f03}.alert-danger{color:#721c24;background-color:#f8d7da;border-color:#f5c6cb}.alert-danger hr{border-top-color:#f1b0b7}.alert-danger .alert-link{color:#491217}.alert-light{color:#818182;background-color:#fefefe;border-color:#fdfdfe}.alert-light hr{border-top-color:#ececf6}.alert-light .alert-link{color:#686868}.alert-dark{color:#1b1e21;background-color:#d6d8d9;border-color:#c6c8ca}.alert-dark hr{border-top-color:#b9bbbe}.alert-dark .alert-link{color:#040505}@-webkit-keyframes progress-bar-stripes{from{background-position:1rem 0}to{background-position:0 0}}@keyframes progress-bar-stripes{from{background-position:1rem 0}to{background-position:0 0}}.progress{display:-ms-flexbox;display:flex;height:1rem;overflow:hidden;line-height:0;font-size:.75rem;background-color:#e9ecef;border-radius:.25rem}.progress-bar{display:-ms-flexbox;display:flex;-ms-flex-direction:column;flex-direction:column;-ms-flex-pack:center;justify-content:center;overflow:hidden;color:#fff;text-align:center;white-space:nowrap;background-color:#007bff;transition:width .6s ease}@media (prefers-reduced-motion:reduce){.progress-bar{transition:none}}.progress-bar-striped{background-image:linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-size:1rem 1rem}.progress-bar-animated{-webkit-animation:1s linear infinite progress-bar-stripes;animation:1s linear infinite progress-bar-stripes}@media (prefers-reduced-motion:reduce){.progress-bar-animated{-webkit-animation:none;animation:none}}.media{display:-ms-flexbox;display:flex;-ms-flex-align:start;align-items:flex-start}.media-body{-ms-flex:1;flex:1}.list-group{display:-ms-flexbox;display:flex;-ms-flex-direction:column;flex-direction:column;padding-left:0;margin-bottom:0;border-radius:.25rem}.list-group-item-action{width:100%;color:#495057;text-align:inherit}.list-group-item-action:focus,.list-group-item-action:hover{z-index:1;color:#495057;text-decoration:none;background-color:#f8f9fa}.list-group-item-action:active{color:#212529;background-color:#e9ecef}.list-group-item{position:relative;display:block;padding:.75rem 1.25rem;background-color:#fff;border:1px solid rgba(0,0,0,.125)}.list-group-item:first-child{border-top-left-radius:inherit;border-top-right-radius:inherit}.list-group-item:last-child{border-bottom-right-radius:inherit;border-bottom-left-radius:inherit}.list-group-item.disabled,.list-group-item:disabled{color:#6c757d;pointer-events:none;background-color:#fff}.list-group-item.active{z-index:2;color:#fff;background-color:#007bff;border-color:#007bff}.list-group-item+.list-group-item{border-top-width:0}.list-group-item+.list-group-item.active{margin-top:-1px;border-top-width:1px}.list-group-horizontal{-ms-flex-direction:row;flex-direction:row}.list-group-horizontal>.list-group-item:first-child{border-bottom-left-radius:.25rem;border-top-right-radius:0}.list-group-horizontal>.list-group-item:last-child{border-top-right-radius:.25rem;border-bottom-left-radius:0}.list-group-horizontal>.list-group-item.active{margin-top:0}.list-group-horizontal>.list-group-item+.list-group-item{border-top-width:1px;border-left-width:0}.list-group-horizontal>.list-group-item+.list-group-item.active{margin-left:-1px;border-left-width:1px}@media (min-width:576px){.list-group-horizontal-sm{-ms-flex-direction:row;flex-direction:row}.list-group-horizontal-sm>.list-group-item:first-child{border-bottom-left-radius:.25rem;border-top-right-radius:0}.list-group-horizontal-sm>.list-group-item:last-child{border-top-right-radius:.25rem;border-bottom-left-radius:0}.list-group-horizontal-sm>.list-group-item.active{margin-top:0}.list-group-horizontal-sm>.list-group-item+.list-group-item{border-top-width:1px;border-left-width:0}.list-group-horizontal-sm>.list-group-item+.list-group-item.active{margin-left:-1px;border-left-width:1px}}@media (min-width:768px){.list-group-horizontal-md{-ms-flex-direction:row;flex-direction:row}.list-group-horizontal-md>.list-group-item:first-child{border-bottom-left-radius:.25rem;border-top-right-radius:0}.list-group-horizontal-md>.list-group-item:last-child{border-top-right-radius:.25rem;border-bottom-left-radius:0}.list-group-horizontal-md>.list-group-item.active{margin-top:0}.list-group-horizontal-md>.list-group-item+.list-group-item{border-top-width:1px;border-left-width:0}.list-group-horizontal-md>.list-group-item+.list-group-item.active{margin-left:-1px;border-left-width:1px}}@media (min-width:992px){.list-group-horizontal-lg{-ms-flex-direction:row;flex-direction:row}.list-group-horizontal-lg>.list-group-item:first-child{border-bottom-left-radius:.25rem;border-top-right-radius:0}.list-group-horizontal-lg>.list-group-item:last-child{border-top-right-radius:.25rem;border-bottom-left-radius:0}.list-group-horizontal-lg>.list-group-item.active{margin-top:0}.list-group-horizontal-lg>.list-group-item+.list-group-item{border-top-width:1px;border-left-width:0}.list-group-horizontal-lg>.list-group-item+.list-group-item.active{margin-left:-1px;border-left-width:1px}}@media (min-width:1200px){.list-group-horizontal-xl{-ms-flex-direction:row;flex-direction:row}.list-group-horizontal-xl>.list-group-item:first-child{border-bottom-left-radius:.25rem;border-top-right-radius:0}.list-group-horizontal-xl>.list-group-item:last-child{border-top-right-radius:.25rem;border-bottom-left-radius:0}.list-group-horizontal-xl>.list-group-item.active{margin-top:0}.list-group-horizontal-xl>.list-group-item+.list-group-item{border-top-width:1px;border-left-width:0}.list-group-horizontal-xl>.list-group-item+.list-group-item.active{margin-left:-1px;border-left-width:1px}}.list-group-flush{border-radius:0}.list-group-flush>.list-group-item{border-width:0 0 1px}.list-group-flush>.list-group-item:last-child{border-bottom-width:0}.list-group-item-primary{color:#004085;background-color:#b8daff}.list-group-item-primary.list-group-item-action:focus,.list-group-item-primary.list-group-item-action:hover{color:#004085;background-color:#9fcdff}.list-group-item-primary.list-group-item-action.active{color:#fff;background-color:#004085;border-color:#004085}.list-group-item-secondary{color:#383d41;background-color:#d6d8db}.list-group-item-secondary.list-group-item-action:focus,.list-group-item-secondary.list-group-item-action:hover{color:#383d41;background-color:#c8cbcf}.list-group-item-secondary.list-group-item-action.active{color:#fff;background-color:#383d41;border-color:#383d41}.list-group-item-success{color:#155724;background-color:#c3e6cb}.list-group-item-success.list-group-item-action:focus,.list-group-item-success.list-group-item-action:hover{color:#155724;background-color:#b1dfbb}.list-group-item-success.list-group-item-action.active{color:#fff;background-color:#155724;border-color:#155724}.list-group-item-info{color:#0c5460;background-color:#bee5eb}.list-group-item-info.list-group-item-action:focus,.list-group-item-info.list-group-item-action:hover{color:#0c5460;background-color:#abdde5}.list-group-item-info.list-group-item-action.active{color:#fff;background-color:#0c5460;border-color:#0c5460}.list-group-item-warning{color:#856404;background-color:#ffeeba}.list-group-item-warning.list-group-item-action:focus,.list-group-item-warning.list-group-item-action:hover{color:#856404;background-color:#ffe8a1}.list-group-item-warning.list-group-item-action.active{color:#fff;background-color:#856404;border-color:#856404}.list-group-item-danger{color:#721c24;background-color:#f5c6cb}.list-group-item-danger.list-group-item-action:focus,.list-group-item-danger.list-group-item-action:hover{color:#721c24;background-color:#f1b0b7}.list-group-item-danger.list-group-item-action.active{color:#fff;background-color:#721c24;border-color:#721c24}.list-group-item-light{color:#818182;background-color:#fdfdfe}.list-group-item-light.list-group-item-action:focus,.list-group-item-light.list-group-item-action:hover{color:#818182;background-color:#ececf6}.list-group-item-light.list-group-item-action.active{color:#fff;background-color:#818182;border-color:#818182}.list-group-item-dark{color:#1b1e21;background-color:#c6c8ca}.list-group-item-dark.list-group-item-action:focus,.list-group-item-dark.list-group-item-action:hover{color:#1b1e21;background-color:#b9bbbe}.list-group-item-dark.list-group-item-action.active{color:#fff;background-color:#1b1e21;border-color:#1b1e21}.close{float:right;font-size:1.5rem;font-weight:700;line-height:1;color:#000;text-shadow:0 1px 0 #fff;opacity:.5}.close:hover{color:#000;text-decoration:none}.close:not(:disabled):not(.disabled):focus,.close:not(:disabled):not(.disabled):hover{opacity:.75}button.close{padding:0;background-color:transparent;border:0}a.close.disabled{pointer-events:none}.toast{-ms-flex-preferred-size:350px;flex-basis:350px;max-width:350px;font-size:.875rem;background-color:rgba(255,255,255,.85);background-clip:padding-box;border:1px solid rgba(0,0,0,.1);box-shadow:0 .25rem .75rem rgba(0,0,0,.1);opacity:0;border-radius:.25rem}.toast:not(:last-child){margin-bottom:.75rem}.toast.showing{opacity:1}.toast.show{display:block;opacity:1}.toast.hide{display:none}.toast-header{display:-ms-flexbox;display:flex;-ms-flex-align:center;align-items:center;padding:.25rem .75rem;color:#6c757d;background-color:rgba(255,255,255,.85);background-clip:padding-box;border-bottom:1px solid rgba(0,0,0,.05);border-top-left-radius:calc(.25rem - 1px);border-top-right-radius:calc(.25rem - 1px)}.toast-body{padding:.75rem}.modal-open{overflow:hidden}.modal-open .modal{overflow-x:hidden;overflow-y:auto}.modal{position:fixed;top:0;left:0;z-index:1050;display:none;width:100%;height:100%;overflow:hidden;outline:0}.modal-dialog{position:relative;width:auto;margin:.5rem;pointer-events:none}.modal.fade .modal-dialog{transition:-webkit-transform .3s ease-out;transition:transform .3s ease-out;transition:transform .3s ease-out,-webkit-transform .3s ease-out;-webkit-transform:translate(0,-50px);transform:translate(0,-50px)}@media (prefers-reduced-motion:reduce){.modal.fade .modal-dialog{transition:none}}.modal.show .modal-dialog{-webkit-transform:none;transform:none}.modal.modal-static .modal-dialog{-webkit-transform:scale(1.02);transform:scale(1.02)}.modal-dialog-scrollable{display:-ms-flexbox;display:flex;max-height:calc(100% - 1rem)}.modal-dialog-scrollable .modal-content{max-height:calc(100vh - 1rem);overflow:hidden}.modal-dialog-scrollable .modal-footer,.modal-dialog-scrollable .modal-header{-ms-flex-negative:0;flex-shrink:0}.modal-dialog-scrollable .modal-body{overflow-y:auto}.modal-dialog-centered{display:-ms-flexbox;display:flex;-ms-flex-align:center;align-items:center;min-height:calc(100% - 1rem)}.modal-dialog-centered::before{display:block;height:calc(100vh - 1rem);height:-webkit-min-content;height:-moz-min-content;height:min-content;content:""}.modal-dialog-centered.modal-dialog-scrollable{-ms-flex-direction:column;flex-direction:column;-ms-flex-pack:center;justify-content:center;height:100%}.modal-dialog-centered.modal-dialog-scrollable .modal-content{max-height:none}.modal-dialog-centered.modal-dialog-scrollable::before{content:none}.modal-content{position:relative;display:-ms-flexbox;display:flex;-ms-flex-direction:column;flex-direction:column;width:100%;pointer-events:auto;background-color:#fff;background-clip:padding-box;border:1px solid rgba(0,0,0,.2);border-radius:.3rem;outline:0}.modal-backdrop{position:fixed;top:0;left:0;z-index:1040;width:100vw;height:100vh;background-color:#000}.modal-backdrop.fade{opacity:0}.modal-backdrop.show{opacity:.5}.modal-header{display:-ms-flexbox;display:flex;-ms-flex-align:start;align-items:flex-start;-ms-flex-pack:justify;justify-content:space-between;padding:1rem 1rem;border-bottom:1px solid #dee2e6;border-top-left-radius:calc(.3rem - 1px);border-top-right-radius:calc(.3rem - 1px)}.modal-header .close{padding:1rem 1rem;margin:-1rem -1rem -1rem auto}.modal-title{margin-bottom:0;line-height:1.5}.modal-body{position:relative;-ms-flex:1 1 auto;flex:1 1 auto;padding:1rem}.modal-footer{display:-ms-flexbox;display:flex;-ms-flex-wrap:wrap;flex-wrap:wrap;-ms-flex-align:center;align-items:center;-ms-flex-pack:end;justify-content:flex-end;padding:.75rem;border-top:1px solid #dee2e6;border-bottom-right-radius:calc(.3rem - 1px);border-bottom-left-radius:calc(.3rem - 1px)}.modal-footer>*{margin:.25rem}.modal-scrollbar-measure{position:absolute;top:-9999px;width:50px;height:50px;overflow:scroll}@media (min-width:576px){.modal-dialog{max-width:500px;margin:1.75rem auto}.modal-dialog-scrollable{max-height:calc(100% - 3.5rem)}.modal-dialog-scrollable .modal-content{max-height:calc(100vh - 3.5rem)}.modal-dialog-centered{min-height:calc(100% - 3.5rem)}.modal-dialog-centered::before{height:calc(100vh - 3.5rem);height:-webkit-min-content;height:-moz-min-content;height:min-content}.modal-sm{max-width:300px}}@media (min-width:992px){.modal-lg,.modal-xl{max-width:800px}}@media (min-width:1200px){.modal-xl{max-width:1140px}}.tooltip{position:absolute;z-index:1070;display:block;margin:0;font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Arial,"Noto Sans","Liberation Sans",sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji";font-style:normal;font-weight:400;line-height:1.5;text-align:left;text-align:start;text-decoration:none;text-shadow:none;text-transform:none;letter-spacing:normal;word-break:normal;white-space:normal;word-spacing:normal;line-break:auto;font-size:.875rem;word-wrap:break-word;opacity:0}.tooltip.show{opacity:.9}.tooltip .arrow{position:absolute;display:block;width:.8rem;height:.4rem}.tooltip .arrow::before{position:absolute;content:"";border-color:transparent;border-style:solid}.bs-tooltip-auto[x-placement^=top],.bs-tooltip-top{padding:.4rem 0}.bs-tooltip-auto[x-placement^=top] .arrow,.bs-tooltip-top .arrow{bottom:0}.bs-tooltip-auto[x-placement^=top] .arrow::before,.bs-tooltip-top .arrow::before{top:0;border-width:.4rem .4rem 0;border-top-color:#000}.bs-tooltip-auto[x-placement^=right],.bs-tooltip-right{padding:0 .4rem}.bs-tooltip-auto[x-placement^=right] .arrow,.bs-tooltip-right .arrow{left:0;width:.4rem;height:.8rem}.bs-tooltip-auto[x-placement^=right] .arrow::before,.bs-tooltip-right .arrow::before{right:0;border-width:.4rem .4rem .4rem 0;border-right-color:#000}.bs-tooltip-auto[x-placement^=bottom],.bs-tooltip-bottom{padding:.4rem 0}.bs-tooltip-auto[x-placement^=bottom] .arrow,.bs-tooltip-bottom .arrow{top:0}.bs-tooltip-auto[x-placement^=bottom] .arrow::before,.bs-tooltip-bottom .arrow::before{bottom:0;border-width:0 .4rem .4rem;border-bottom-color:#000}.bs-tooltip-auto[x-placement^=left],.bs-tooltip-left{padding:0 .4rem}.bs-tooltip-auto[x-placement^=left] .arrow,.bs-tooltip-left .arrow{right:0;width:.4rem;height:.8rem}.bs-tooltip-auto[x-placement^=left] .arrow::before,.bs-tooltip-left .arrow::before{left:0;border-width:.4rem 0 .4rem .4rem;border-left-color:#000}.tooltip-inner{max-width:200px;padding:.25rem .5rem;color:#fff;text-align:center;background-color:#000;border-radius:.25rem}.popover{position:absolute;top:0;left:0;z-index:1060;display:block;max-width:276px;font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Arial,"Noto Sans","Liberation Sans",sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji";font-style:normal;font-weight:400;line-height:1.5;text-align:left;text-align:start;text-decoration:none;text-shadow:none;text-transform:none;letter-spacing:normal;word-break:normal;white-space:normal;word-spacing:normal;line-break:auto;font-size:.875rem;word-wrap:break-word;background-color:#fff;background-clip:padding-box;border:1px solid rgba(0,0,0,.2);border-radius:.3rem}.popover .arrow{position:absolute;display:block;width:1rem;height:.5rem;margin:0 .3rem}.popover .arrow::after,.popover .arrow::before{position:absolute;display:block;content:"";border-color:transparent;border-style:solid}.bs-popover-auto[x-placement^=top],.bs-popover-top{margin-bottom:.5rem}.bs-popover-auto[x-placement^=top]>.arrow,.bs-popover-top>.arrow{bottom:calc(-.5rem - 1px)}.bs-popover-auto[x-placement^=top]>.arrow::before,.bs-popover-top>.arrow::before{bottom:0;border-width:.5rem .5rem 0;border-top-color:rgba(0,0,0,.25)}.bs-popover-auto[x-placement^=top]>.arrow::after,.bs-popover-top>.arrow::after{bottom:1px;border-width:.5rem .5rem 0;border-top-color:#fff}.bs-popover-auto[x-placement^=right],.bs-popover-right{margin-left:.5rem}.bs-popover-auto[x-placement^=right]>.arrow,.bs-popover-right>.arrow{left:calc(-.5rem - 1px);width:.5rem;height:1rem;margin:.3rem 0}.bs-popover-auto[x-placement^=right]>.arrow::before,.bs-popover-right>.arrow::before{left:0;border-width:.5rem .5rem .5rem 0;border-right-color:rgba(0,0,0,.25)}.bs-popover-auto[x-placement^=right]>.arrow::after,.bs-popover-right>.arrow::after{left:1px;border-width:.5rem .5rem .5rem 0;border-right-color:#fff}.bs-popover-auto[x-placement^=bottom],.bs-popover-bottom{margin-top:.5rem}.bs-popover-auto[x-placement^=bottom]>.arrow,.bs-popover-bottom>.arrow{top:calc(-.5rem - 1px)}.bs-popover-auto[x-placement^=bottom]>.arrow::before,.bs-popover-bottom>.arrow::before{top:0;border-width:0 .5rem .5rem .5rem;border-bottom-color:rgba(0,0,0,.25)}.bs-popover-auto[x-placement^=bottom]>.arrow::after,.bs-popover-bottom>.arrow::after{top:1px;border-width:0 .5rem .5rem .5rem;border-bottom-color:#fff}.bs-popover-auto[x-placement^=bottom] .popover-header::before,.bs-popover-bottom .popover-header::before{position:absolute;top:0;left:50%;display:block;width:1rem;margin-left:-.5rem;content:"";border-bottom:1px solid #f7f7f7}.bs-popover-auto[x-placement^=left],.bs-popover-left{margin-right:.5rem}.bs-popover-auto[x-placement^=left]>.arrow,.bs-popover-left>.arrow{right:calc(-.5rem - 1px);width:.5rem;height:1rem;margin:.3rem 0}.bs-popover-auto[x-placement^=left]>.arrow::before,.bs-popover-left>.arrow::before{right:0;border-width:.5rem 0 .5rem .5rem;border-left-color:rgba(0,0,0,.25)}.bs-popover-auto[x-placement^=left]>.arrow::after,.bs-popover-left>.arrow::after{right:1px;border-width:.5rem 0 .5rem .5rem;border-left-color:#fff}.popover-header{padding:.5rem .75rem;margin-bottom:0;font-size:1rem;background-color:#f7f7f7;border-bottom:1px solid #ebebeb;border-top-left-radius:calc(.3rem - 1px);border-top-right-radius:calc(.3rem - 1px)}.popover-header:empty{display:none}.popover-body{padding:.5rem .75rem;color:#212529}.carousel{position:relative}.carousel.pointer-event{-ms-touch-action:pan-y;touch-action:pan-y}.carousel-inner{position:relative;width:100%;overflow:hidden}.carousel-inner::after{display:block;clear:both;content:""}.carousel-item{position:relative;display:none;float:left;width:100%;margin-right:-100%;-webkit-backface-visibility:hidden;backface-visibility:hidden;transition:-webkit-transform .6s ease-in-out;transition:transform .6s ease-in-out;transition:transform .6s ease-in-out,-webkit-transform .6s ease-in-out}@media (prefers-reduced-motion:reduce){.carousel-item{transition:none}}.carousel-item-next,.carousel-item-prev,.carousel-item.active{display:block}.active.carousel-item-right,.carousel-item-next:not(.carousel-item-left){-webkit-transform:translateX(100%);transform:translateX(100%)}.active.carousel-item-left,.carousel-item-prev:not(.carousel-item-right){-webkit-transform:translateX(-100%);transform:translateX(-100%)}.carousel-fade .carousel-item{opacity:0;transition-property:opacity;-webkit-transform:none;transform:none}.carousel-fade .carousel-item-next.carousel-item-left,.carousel-fade .carousel-item-prev.carousel-item-right,.carousel-fade .carousel-item.active{z-index:1;opacity:1}.carousel-fade .active.carousel-item-left,.carousel-fade .active.carousel-item-right{z-index:0;opacity:0;transition:opacity 0s .6s}@media (prefers-reduced-motion:reduce){.carousel-fade .active.carousel-item-left,.carousel-fade .active.carousel-item-right{transition:none}}.carousel-control-next,.carousel-control-prev{position:absolute;top:0;bottom:0;z-index:1;display:-ms-flexbox;display:flex;-ms-flex-align:center;align-items:center;-ms-flex-pack:center;justify-content:center;width:15%;padding:0;color:#fff;text-align:center;background:0 0;border:0;opacity:.5;transition:opacity .15s ease}@media (prefers-reduced-motion:reduce){.carousel-control-next,.carousel-control-prev{transition:none}}.carousel-control-next:focus,.carousel-control-next:hover,.carousel-control-prev:focus,.carousel-control-prev:hover{color:#fff;text-decoration:none;outline:0;opacity:.9}.carousel-control-prev{left:0}.carousel-control-next{right:0}.carousel-control-next-icon,.carousel-control-prev-icon{display:inline-block;width:20px;height:20px;background:50%/100% 100% no-repeat}.carousel-control-prev-icon{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' fill='%23fff' width='8' height='8' viewBox='0 0 8 8'%3e%3cpath d='M5.25 0l-4 4 4 4 1.5-1.5L4.25 4l2.5-2.5L5.25 0z'/%3e%3c/svg%3e")}.carousel-control-next-icon{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' fill='%23fff' width='8' height='8' viewBox='0 0 8 8'%3e%3cpath d='M2.75 0l-1.5 1.5L3.75 4l-2.5 2.5L2.75 8l4-4-4-4z'/%3e%3c/svg%3e")}.carousel-indicators{position:absolute;right:0;bottom:0;left:0;z-index:15;display:-ms-flexbox;display:flex;-ms-flex-pack:center;justify-content:center;padding-left:0;margin-right:15%;margin-left:15%;list-style:none}.carousel-indicators li{box-sizing:content-box;-ms-flex:0 1 auto;flex:0 1 auto;width:30px;height:3px;margin-right:3px;margin-left:3px;text-indent:-999px;cursor:pointer;background-color:#fff;background-clip:padding-box;border-top:10px solid transparent;border-bottom:10px solid transparent;opacity:.5;transition:opacity .6s ease}@media (prefers-reduced-motion:reduce){.carousel-indicators li{transition:none}}.carousel-indicators .active{opacity:1}.carousel-caption{position:absolute;right:15%;bottom:20px;left:15%;z-index:10;padding-top:20px;padding-bottom:20px;color:#fff;text-align:center}@-webkit-keyframes spinner-border{to{-webkit-transform:rotate(360deg);transform:rotate(360deg)}}@keyframes spinner-border{to{-webkit-transform:rotate(360deg);transform:rotate(360deg)}}.spinner-border{display:inline-block;width:2rem;height:2rem;vertical-align:-.125em;border:.25em solid currentcolor;border-right-color:transparent;border-radius:50%;-webkit-animation:.75s linear infinite spinner-border;animation:.75s linear infinite spinner-border}.spinner-border-sm{width:1rem;height:1rem;border-width:.2em}@-webkit-keyframes spinner-grow{0%{-webkit-transform:scale(0);transform:scale(0)}50%{opacity:1;-webkit-transform:none;transform:none}}@keyframes spinner-grow{0%{-webkit-transform:scale(0);transform:scale(0)}50%{opacity:1;-webkit-transform:none;transform:none}}.spinner-grow{display:inline-block;width:2rem;height:2rem;vertical-align:-.125em;background-color:currentcolor;border-radius:50%;opacity:0;-webkit-animation:.75s linear infinite spinner-grow;animation:.75s linear infinite spinner-grow}.spinner-grow-sm{width:1rem;height:1rem}@media (prefers-reduced-motion:reduce){.spinner-border,.spinner-grow{-webkit-animation-duration:1.5s;animation-duration:1.5s}}.align-baseline{vertical-align:baseline!important}.align-top{vertical-align:top!important}.align-middle{vertical-align:middle!important}.align-bottom{vertical-align:bottom!important}.align-text-bottom{vertical-align:text-bottom!important}.align-text-top{vertical-align:text-top!important}.bg-primary{background-color:#007bff!important}a.bg-primary:focus,a.bg-primary:hover,button.bg-primary:focus,button.bg-primary:hover{background-color:#0062cc!important}.bg-secondary{background-color:#6c757d!important}a.bg-secondary:focus,a.bg-secondary:hover,button.bg-secondary:focus,button.bg-secondary:hover{background-color:#545b62!important}.bg-success{background-color:#28a745!important}a.bg-success:focus,a.bg-success:hover,button.bg-success:focus,button.bg-success:hover{background-color:#1e7e34!important}.bg-info{background-color:#17a2b8!important}a.bg-info:focus,a.bg-info:hover,button.bg-info:focus,button.bg-info:hover{background-color:#117a8b!important}.bg-warning{background-color:#ffc107!important}a.bg-warning:focus,a.bg-warning:hover,button.bg-warning:focus,button.bg-warning:hover{background-color:#d39e00!important}.bg-danger{background-color:#dc3545!important}a.bg-danger:focus,a.bg-danger:hover,button.bg-danger:focus,button.bg-danger:hover{background-color:#bd2130!important}.bg-light{background-color:#f8f9fa!important}a.bg-light:focus,a.bg-light:hover,button.bg-light:focus,button.bg-light:hover{background-color:#dae0e5!important}.bg-dark{background-color:#343a40!important}a.bg-dark:focus,a.bg-dark:hover,button.bg-dark:focus,button.bg-dark:hover{background-color:#1d2124!important}.bg-white{background-color:#fff!important}.bg-transparent{background-color:transparent!important}.border{border:1px solid #dee2e6!important}.border-top{border-top:1px solid #dee2e6!important}.border-right{border-right:1px solid #dee2e6!important}.border-bottom{border-bottom:1px solid #dee2e6!important}.border-left{border-left:1px solid #dee2e6!important}.border-0{border:0!important}.border-top-0{border-top:0!important}.border-right-0{border-right:0!important}.border-bottom-0{border-bottom:0!important}.border-left-0{border-left:0!important}.border-primary{border-color:#007bff!important}.border-secondary{border-color:#6c757d!important}.border-success{border-color:#28a745!important}.border-info{border-color:#17a2b8!important}.border-warning{border-color:#ffc107!important}.border-danger{border-color:#dc3545!important}.border-light{border-color:#f8f9fa!important}.border-dark{border-color:#343a40!important}.border-white{border-color:#fff!important}.rounded-sm{border-radius:.2rem!important}.rounded{border-radius:.25rem!important}.rounded-top{border-top-left-radius:.25rem!important;border-top-right-radius:.25rem!important}.rounded-right{border-top-right-radius:.25rem!important;border-bottom-right-radius:.25rem!important}.rounded-bottom{border-bottom-right-radius:.25rem!important;border-bottom-left-radius:.25rem!important}.rounded-left{border-top-left-radius:.25rem!important;border-bottom-left-radius:.25rem!important}.rounded-lg{border-radius:.3rem!important}.rounded-circle{border-radius:50%!important}.rounded-pill{border-radius:50rem!important}.rounded-0{border-radius:0!important}.clearfix::after{display:block;clear:both;content:""}.d-none{display:none!important}.d-inline{display:inline!important}.d-inline-block{display:inline-block!important}.d-block{display:block!important}.d-table{display:table!important}.d-table-row{display:table-row!important}.d-table-cell{display:table-cell!important}.d-flex{display:-ms-flexbox!important;display:flex!important}.d-inline-flex{display:-ms-inline-flexbox!important;display:inline-flex!important}@media (min-width:576px){.d-sm-none{display:none!important}.d-sm-inline{display:inline!important}.d-sm-inline-block{display:inline-block!important}.d-sm-block{display:block!important}.d-sm-table{display:table!important}.d-sm-table-row{display:table-row!important}.d-sm-table-cell{display:table-cell!important}.d-sm-flex{display:-ms-flexbox!important;display:flex!important}.d-sm-inline-flex{display:-ms-inline-flexbox!important;display:inline-flex!important}}@media (min-width:768px){.d-md-none{display:none!important}.d-md-inline{display:inline!important}.d-md-inline-block{display:inline-block!important}.d-md-block{display:block!important}.d-md-table{display:table!important}.d-md-table-row{display:table-row!important}.d-md-table-cell{display:table-cell!important}.d-md-flex{display:-ms-flexbox!important;display:flex!important}.d-md-inline-flex{display:-ms-inline-flexbox!important;display:inline-flex!important}}@media (min-width:992px){.d-lg-none{display:none!important}.d-lg-inline{display:inline!important}.d-lg-inline-block{display:inline-block!important}.d-lg-block{display:block!important}.d-lg-table{display:table!important}.d-lg-table-row{display:table-row!important}.d-lg-table-cell{display:table-cell!important}.d-lg-flex{display:-ms-flexbox!important;display:flex!important}.d-lg-inline-flex{display:-ms-inline-flexbox!important;display:inline-flex!important}}@media (min-width:1200px){.d-xl-none{display:none!important}.d-xl-inline{display:inline!important}.d-xl-inline-block{display:inline-block!important}.d-xl-block{display:block!important}.d-xl-table{display:table!important}.d-xl-table-row{display:table-row!important}.d-xl-table-cell{display:table-cell!important}.d-xl-flex{display:-ms-flexbox!important;display:flex!important}.d-xl-inline-flex{display:-ms-inline-flexbox!important;display:inline-flex!important}}@media print{.d-print-none{display:none!important}.d-print-inline{display:inline!important}.d-print-inline-block{display:inline-block!important}.d-print-block{display:block!important}.d-print-table{display:table!important}.d-print-table-row{display:table-row!important}.d-print-table-cell{display:table-cell!important}.d-print-flex{display:-ms-flexbox!important;display:flex!important}.d-print-inline-flex{display:-ms-inline-flexbox!important;display:inline-flex!important}}.embed-responsive{position:relative;display:block;width:100%;padding:0;overflow:hidden}.embed-responsive::before{display:block;content:""}.embed-responsive .embed-responsive-item,.embed-responsive embed,.embed-responsive iframe,.embed-responsive object,.embed-responsive video{position:absolute;top:0;bottom:0;left:0;width:100%;height:100%;border:0}.embed-responsive-21by9::before{padding-top:42.857143%}.embed-responsive-16by9::before{padding-top:56.25%}.embed-responsive-4by3::before{padding-top:75%}.embed-responsive-1by1::before{padding-top:100%}.flex-row{-ms-flex-direction:row!important;flex-direction:row!important}.flex-column{-ms-flex-direction:column!important;flex-direction:column!important}.flex-row-reverse{-ms-flex-direction:row-reverse!important;flex-direction:row-reverse!important}.flex-column-reverse{-ms-flex-direction:column-reverse!important;flex-direction:column-reverse!important}.flex-wrap{-ms-flex-wrap:wrap!important;flex-wrap:wrap!important}.flex-nowrap{-ms-flex-wrap:nowrap!important;flex-wrap:nowrap!important}.flex-wrap-reverse{-ms-flex-wrap:wrap-reverse!important;flex-wrap:wrap-reverse!important}.flex-fill{-ms-flex:1 1 auto!important;flex:1 1 auto!important}.flex-grow-0{-ms-flex-positive:0!important;flex-grow:0!important}.flex-grow-1{-ms-flex-positive:1!important;flex-grow:1!important}.flex-shrink-0{-ms-flex-negative:0!important;flex-shrink:0!important}.flex-shrink-1{-ms-flex-negative:1!important;flex-shrink:1!important}.justify-content-start{-ms-flex-pack:start!important;justify-content:flex-start!important}.justify-content-end{-ms-flex-pack:end!important;justify-content:flex-end!important}.justify-content-center{-ms-flex-pack:center!important;justify-content:center!important}.justify-content-between{-ms-flex-pack:justify!important;justify-content:space-between!important}.justify-content-around{-ms-flex-pack:distribute!important;justify-content:space-around!important}.align-items-start{-ms-flex-align:start!important;align-items:flex-start!important}.align-items-end{-ms-flex-align:end!important;align-items:flex-end!important}.align-items-center{-ms-flex-align:center!important;align-items:center!important}.align-items-baseline{-ms-flex-align:baseline!important;align-items:baseline!important}.align-items-stretch{-ms-flex-align:stretch!important;align-items:stretch!important}.align-content-start{-ms-flex-line-pack:start!important;align-content:flex-start!important}.align-content-end{-ms-flex-line-pack:end!important;align-content:flex-end!important}.align-content-center{-ms-flex-line-pack:center!important;align-content:center!important}.align-content-between{-ms-flex-line-pack:justify!important;align-content:space-between!important}.align-content-around{-ms-flex-line-pack:distribute!important;align-content:space-around!important}.align-content-stretch{-ms-flex-line-pack:stretch!important;align-content:stretch!important}.align-self-auto{-ms-flex-item-align:auto!important;align-self:auto!important}.align-self-start{-ms-flex-item-align:start!important;align-self:flex-start!important}.align-self-end{-ms-flex-item-align:end!important;align-self:flex-end!important}.align-self-center{-ms-flex-item-align:center!important;align-self:center!important}.align-self-baseline{-ms-flex-item-align:baseline!important;align-self:baseline!important}.align-self-stretch{-ms-flex-item-align:stretch!important;align-self:stretch!important}@media (min-width:576px){.flex-sm-row{-ms-flex-direction:row!important;flex-direction:row!important}.flex-sm-column{-ms-flex-direction:column!important;flex-direction:column!important}.flex-sm-row-reverse{-ms-flex-direction:row-reverse!important;flex-direction:row-reverse!important}.flex-sm-column-reverse{-ms-flex-direction:column-reverse!important;flex-direction:column-reverse!important}.flex-sm-wrap{-ms-flex-wrap:wrap!important;flex-wrap:wrap!important}.flex-sm-nowrap{-ms-flex-wrap:nowrap!important;flex-wrap:nowrap!important}.flex-sm-wrap-reverse{-ms-flex-wrap:wrap-reverse!important;flex-wrap:wrap-reverse!important}.flex-sm-fill{-ms-flex:1 1 auto!important;flex:1 1 auto!important}.flex-sm-grow-0{-ms-flex-positive:0!important;flex-grow:0!important}.flex-sm-grow-1{-ms-flex-positive:1!important;flex-grow:1!important}.flex-sm-shrink-0{-ms-flex-negative:0!important;flex-shrink:0!important}.flex-sm-shrink-1{-ms-flex-negative:1!important;flex-shrink:1!important}.justify-content-sm-start{-ms-flex-pack:start!important;justify-content:flex-start!important}.justify-content-sm-end{-ms-flex-pack:end!important;justify-content:flex-end!important}.justify-content-sm-center{-ms-flex-pack:center!important;justify-content:center!important}.justify-content-sm-between{-ms-flex-pack:justify!important;justify-content:space-between!important}.justify-content-sm-around{-ms-flex-pack:distribute!important;justify-content:space-around!important}.align-items-sm-start{-ms-flex-align:start!important;align-items:flex-start!important}.align-items-sm-end{-ms-flex-align:end!important;align-items:flex-end!important}.align-items-sm-center{-ms-flex-align:center!important;align-items:center!important}.align-items-sm-baseline{-ms-flex-align:baseline!important;align-items:baseline!important}.align-items-sm-stretch{-ms-flex-align:stretch!important;align-items:stretch!important}.align-content-sm-start{-ms-flex-line-pack:start!important;align-content:flex-start!important}.align-content-sm-end{-ms-flex-line-pack:end!important;align-content:flex-end!important}.align-content-sm-center{-ms-flex-line-pack:center!important;align-content:center!important}.align-content-sm-between{-ms-flex-line-pack:justify!important;align-content:space-between!important}.align-content-sm-around{-ms-flex-line-pack:distribute!important;align-content:space-around!important}.align-content-sm-stretch{-ms-flex-line-pack:stretch!important;align-content:stretch!important}.align-self-sm-auto{-ms-flex-item-align:auto!important;align-self:auto!important}.align-self-sm-start{-ms-flex-item-align:start!important;align-self:flex-start!important}.align-self-sm-end{-ms-flex-item-align:end!important;align-self:flex-end!important}.align-self-sm-center{-ms-flex-item-align:center!important;align-self:center!important}.align-self-sm-baseline{-ms-flex-item-align:baseline!important;align-self:baseline!important}.align-self-sm-stretch{-ms-flex-item-align:stretch!important;align-self:stretch!important}}@media (min-width:768px){.flex-md-row{-ms-flex-direction:row!important;flex-direction:row!important}.flex-md-column{-ms-flex-direction:column!important;flex-direction:column!important}.flex-md-row-reverse{-ms-flex-direction:row-reverse!important;flex-direction:row-reverse!important}.flex-md-column-reverse{-ms-flex-direction:column-reverse!important;flex-direction:column-reverse!important}.flex-md-wrap{-ms-flex-wrap:wrap!important;flex-wrap:wrap!important}.flex-md-nowrap{-ms-flex-wrap:nowrap!important;flex-wrap:nowrap!important}.flex-md-wrap-reverse{-ms-flex-wrap:wrap-reverse!important;flex-wrap:wrap-reverse!important}.flex-md-fill{-ms-flex:1 1 auto!important;flex:1 1 auto!important}.flex-md-grow-0{-ms-flex-positive:0!important;flex-grow:0!important}.flex-md-grow-1{-ms-flex-positive:1!important;flex-grow:1!important}.flex-md-shrink-0{-ms-flex-negative:0!important;flex-shrink:0!important}.flex-md-shrink-1{-ms-flex-negative:1!important;flex-shrink:1!important}.justify-content-md-start{-ms-flex-pack:start!important;justify-content:flex-start!important}.justify-content-md-end{-ms-flex-pack:end!important;justify-content:flex-end!important}.justify-content-md-center{-ms-flex-pack:center!important;justify-content:center!important}.justify-content-md-between{-ms-flex-pack:justify!important;justify-content:space-between!important}.justify-content-md-around{-ms-flex-pack:distribute!important;justify-content:space-around!important}.align-items-md-start{-ms-flex-align:start!important;align-items:flex-start!important}.align-items-md-end{-ms-flex-align:end!important;align-items:flex-end!important}.align-items-md-center{-ms-flex-align:center!important;align-items:center!important}.align-items-md-baseline{-ms-flex-align:baseline!important;align-items:baseline!important}.align-items-md-stretch{-ms-flex-align:stretch!important;align-items:stretch!important}.align-content-md-start{-ms-flex-line-pack:start!important;align-content:flex-start!important}.align-content-md-end{-ms-flex-line-pack:end!important;align-content:flex-end!important}.align-content-md-center{-ms-flex-line-pack:center!important;align-content:center!important}.align-content-md-between{-ms-flex-line-pack:justify!important;align-content:space-between!important}.align-content-md-around{-ms-flex-line-pack:distribute!important;align-content:space-around!important}.align-content-md-stretch{-ms-flex-line-pack:stretch!important;align-content:stretch!important}.align-self-md-auto{-ms-flex-item-align:auto!important;align-self:auto!important}.align-self-md-start{-ms-flex-item-align:start!important;align-self:flex-start!important}.align-self-md-end{-ms-flex-item-align:end!important;align-self:flex-end!important}.align-self-md-center{-ms-flex-item-align:center!important;align-self:center!important}.align-self-md-baseline{-ms-flex-item-align:baseline!important;align-self:baseline!important}.align-self-md-stretch{-ms-flex-item-align:stretch!important;align-self:stretch!important}}@media (min-width:992px){.flex-lg-row{-ms-flex-direction:row!important;flex-direction:row!important}.flex-lg-column{-ms-flex-direction:column!important;flex-direction:column!important}.flex-lg-row-reverse{-ms-flex-direction:row-reverse!important;flex-direction:row-reverse!important}.flex-lg-column-reverse{-ms-flex-direction:column-reverse!important;flex-direction:column-reverse!important}.flex-lg-wrap{-ms-flex-wrap:wrap!important;flex-wrap:wrap!important}.flex-lg-nowrap{-ms-flex-wrap:nowrap!important;flex-wrap:nowrap!important}.flex-lg-wrap-reverse{-ms-flex-wrap:wrap-reverse!important;flex-wrap:wrap-reverse!important}.flex-lg-fill{-ms-flex:1 1 auto!important;flex:1 1 auto!important}.flex-lg-grow-0{-ms-flex-positive:0!important;flex-grow:0!important}.flex-lg-grow-1{-ms-flex-positive:1!important;flex-grow:1!important}.flex-lg-shrink-0{-ms-flex-negative:0!important;flex-shrink:0!important}.flex-lg-shrink-1{-ms-flex-negative:1!important;flex-shrink:1!important}.justify-content-lg-start{-ms-flex-pack:start!important;justify-content:flex-start!important}.justify-content-lg-end{-ms-flex-pack:end!important;justify-content:flex-end!important}.justify-content-lg-center{-ms-flex-pack:center!important;justify-content:center!important}.justify-content-lg-between{-ms-flex-pack:justify!important;justify-content:space-between!important}.justify-content-lg-around{-ms-flex-pack:distribute!important;justify-content:space-around!important}.align-items-lg-start{-ms-flex-align:start!important;align-items:flex-start!important}.align-items-lg-end{-ms-flex-align:end!important;align-items:flex-end!important}.align-items-lg-center{-ms-flex-align:center!important;align-items:center!important}.align-items-lg-baseline{-ms-flex-align:baseline!important;align-items:baseline!important}.align-items-lg-stretch{-ms-flex-align:stretch!important;align-items:stretch!important}.align-content-lg-start{-ms-flex-line-pack:start!important;align-content:flex-start!important}.align-content-lg-end{-ms-flex-line-pack:end!important;align-content:flex-end!important}.align-content-lg-center{-ms-flex-line-pack:center!important;align-content:center!important}.align-content-lg-between{-ms-flex-line-pack:justify!important;align-content:space-between!important}.align-content-lg-around{-ms-flex-line-pack:distribute!important;align-content:space-around!important}.align-content-lg-stretch{-ms-flex-line-pack:stretch!important;align-content:stretch!important}.align-self-lg-auto{-ms-flex-item-align:auto!important;align-self:auto!important}.align-self-lg-start{-ms-flex-item-align:start!important;align-self:flex-start!important}.align-self-lg-end{-ms-flex-item-align:end!important;align-self:flex-end!important}.align-self-lg-center{-ms-flex-item-align:center!important;align-self:center!important}.align-self-lg-baseline{-ms-flex-item-align:baseline!important;align-self:baseline!important}.align-self-lg-stretch{-ms-flex-item-align:stretch!important;align-self:stretch!important}}@media (min-width:1200px){.flex-xl-row{-ms-flex-direction:row!important;flex-direction:row!important}.flex-xl-column{-ms-flex-direction:column!important;flex-direction:column!important}.flex-xl-row-reverse{-ms-flex-direction:row-reverse!important;flex-direction:row-reverse!important}.flex-xl-column-reverse{-ms-flex-direction:column-reverse!important;flex-direction:column-reverse!important}.flex-xl-wrap{-ms-flex-wrap:wrap!important;flex-wrap:wrap!important}.flex-xl-nowrap{-ms-flex-wrap:nowrap!important;flex-wrap:nowrap!important}.flex-xl-wrap-reverse{-ms-flex-wrap:wrap-reverse!important;flex-wrap:wrap-reverse!important}.flex-xl-fill{-ms-flex:1 1 auto!important;flex:1 1 auto!important}.flex-xl-grow-0{-ms-flex-positive:0!important;flex-grow:0!important}.flex-xl-grow-1{-ms-flex-positive:1!important;flex-grow:1!important}.flex-xl-shrink-0{-ms-flex-negative:0!important;flex-shrink:0!important}.flex-xl-shrink-1{-ms-flex-negative:1!important;flex-shrink:1!important}.justify-content-xl-start{-ms-flex-pack:start!important;justify-content:flex-start!important}.justify-content-xl-end{-ms-flex-pack:end!important;justify-content:flex-end!important}.justify-content-xl-center{-ms-flex-pack:center!important;justify-content:center!important}.justify-content-xl-between{-ms-flex-pack:justify!important;justify-content:space-between!important}.justify-content-xl-around{-ms-flex-pack:distribute!important;justify-content:space-around!important}.align-items-xl-start{-ms-flex-align:start!important;align-items:flex-start!important}.align-items-xl-end{-ms-flex-align:end!important;align-items:flex-end!important}.align-items-xl-center{-ms-flex-align:center!important;align-items:center!important}.align-items-xl-baseline{-ms-flex-align:baseline!important;align-items:baseline!important}.align-items-xl-stretch{-ms-flex-align:stretch!important;align-items:stretch!important}.align-content-xl-start{-ms-flex-line-pack:start!important;align-content:flex-start!important}.align-content-xl-end{-ms-flex-line-pack:end!important;align-content:flex-end!important}.align-content-xl-center{-ms-flex-line-pack:center!important;align-content:center!important}.align-content-xl-between{-ms-flex-line-pack:justify!important;align-content:space-between!important}.align-content-xl-around{-ms-flex-line-pack:distribute!important;align-content:space-around!important}.align-content-xl-stretch{-ms-flex-line-pack:stretch!important;align-content:stretch!important}.align-self-xl-auto{-ms-flex-item-align:auto!important;align-self:auto!important}.align-self-xl-start{-ms-flex-item-align:start!important;align-self:flex-start!important}.align-self-xl-end{-ms-flex-item-align:end!important;align-self:flex-end!important}.align-self-xl-center{-ms-flex-item-align:center!important;align-self:center!important}.align-self-xl-baseline{-ms-flex-item-align:baseline!important;align-self:baseline!important}.align-self-xl-stretch{-ms-flex-item-align:stretch!important;align-self:stretch!important}}.float-left{float:left!important}.float-right{float:right!important}.float-none{float:none!important}@media (min-width:576px){.float-sm-left{float:left!important}.float-sm-right{float:right!important}.float-sm-none{float:none!important}}@media (min-width:768px){.float-md-left{float:left!important}.float-md-right{float:right!important}.float-md-none{float:none!important}}@media (min-width:992px){.float-lg-left{float:left!important}.float-lg-right{float:right!important}.float-lg-none{float:none!important}}@media (min-width:1200px){.float-xl-left{float:left!important}.float-xl-right{float:right!important}.float-xl-none{float:none!important}}.user-select-all{-webkit-user-select:all!important;-moz-user-select:all!important;user-select:all!important}.user-select-auto{-webkit-user-select:auto!important;-moz-user-select:auto!important;-ms-user-select:auto!important;user-select:auto!important}.user-select-none{-webkit-user-select:none!important;-moz-user-select:none!important;-ms-user-select:none!important;user-select:none!important}.overflow-auto{overflow:auto!important}.overflow-hidden{overflow:hidden!important}.position-static{position:static!important}.position-relative{position:relative!important}.position-absolute{position:absolute!important}.position-fixed{position:fixed!important}.position-sticky{position:-webkit-sticky!important;position:sticky!important}.fixed-top{position:fixed;top:0;right:0;left:0;z-index:1030}.fixed-bottom{position:fixed;right:0;bottom:0;left:0;z-index:1030}@supports ((position:-webkit-sticky) or (position:sticky)){.sticky-top{position:-webkit-sticky;position:sticky;top:0;z-index:1020}}.sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border:0}.sr-only-focusable:active,.sr-only-focusable:focus{position:static;width:auto;height:auto;overflow:visible;clip:auto;white-space:normal}.shadow-sm{box-shadow:0 .125rem .25rem rgba(0,0,0,.075)!important}.shadow{box-shadow:0 .5rem 1rem rgba(0,0,0,.15)!important}.shadow-lg{box-shadow:0 1rem 3rem rgba(0,0,0,.175)!important}.shadow-none{box-shadow:none!important}.w-25{width:25%!important}.w-50{width:50%!important}.w-75{width:75%!important}.w-100{width:100%!important}.w-auto{width:auto!important}.h-25{height:25%!important}.h-50{height:50%!important}.h-75{height:75%!important}.h-100{height:100%!important}.h-auto{height:auto!important}.mw-100{max-width:100%!important}.mh-100{max-height:100%!important}.min-vw-100{min-width:100vw!important}.min-vh-100{min-height:100vh!important}.vw-100{width:100vw!important}.vh-100{height:100vh!important}.m-0{margin:0!important}.mt-0,.my-0{margin-top:0!important}.mr-0,.mx-0{margin-right:0!important}.mb-0,.my-0{margin-bottom:0!important}.ml-0,.mx-0{margin-left:0!important}.m-1{margin:.25rem!important}.mt-1,.my-1{margin-top:.25rem!important}.mr-1,.mx-1{margin-right:.25rem!important}.mb-1,.my-1{margin-bottom:.25rem!important}.ml-1,.mx-1{margin-left:.25rem!important}.m-2{margin:.5rem!important}.mt-2,.my-2{margin-top:.5rem!important}.mr-2,.mx-2{margin-right:.5rem!important}.mb-2,.my-2{margin-bottom:.5rem!important}.ml-2,.mx-2{margin-left:.5rem!important}.m-3{margin:1rem!important}.mt-3,.my-3{margin-top:1rem!important}.mr-3,.mx-3{margin-right:1rem!important}.mb-3,.my-3{margin-bottom:1rem!important}.ml-3,.mx-3{margin-left:1rem!important}.m-4{margin:1.5rem!important}.mt-4,.my-4{margin-top:1.5rem!important}.mr-4,.mx-4{margin-right:1.5rem!important}.mb-4,.my-4{margin-bottom:1.5rem!important}.ml-4,.mx-4{margin-left:1.5rem!important}.m-5{margin:3rem!important}.mt-5,.my-5{margin-top:3rem!important}.mr-5,.mx-5{margin-right:3rem!important}.mb-5,.my-5{margin-bottom:3rem!important}.ml-5,.mx-5{margin-left:3rem!important}.p-0{padding:0!important}.pt-0,.py-0{padding-top:0!important}.pr-0,.px-0{padding-right:0!important}.pb-0,.py-0{padding-bottom:0!important}.pl-0,.px-0{padding-left:0!important}.p-1{padding:.25rem!important}.pt-1,.py-1{padding-top:.25rem!important}.pr-1,.px-1{padding-right:.25rem!important}.pb-1,.py-1{padding-bottom:.25rem!important}.pl-1,.px-1{padding-left:.25rem!important}.p-2{padding:.5rem!important}.pt-2,.py-2{padding-top:.5rem!important}.pr-2,.px-2{padding-right:.5rem!important}.pb-2,.py-2{padding-bottom:.5rem!important}.pl-2,.px-2{padding-left:.5rem!important}.p-3{padding:1rem!important}.pt-3,.py-3{padding-top:1rem!important}.pr-3,.px-3{padding-right:1rem!important}.pb-3,.py-3{padding-bottom:1rem!important}.pl-3,.px-3{padding-left:1rem!important}.p-4{padding:1.5rem!important}.pt-4,.py-4{padding-top:1.5rem!important}.pr-4,.px-4{padding-right:1.5rem!important}.pb-4,.py-4{padding-bottom:1.5rem!important}.pl-4,.px-4{padding-left:1.5rem!important}.p-5{padding:3rem!important}.pt-5,.py-5{padding-top:3rem!important}.pr-5,.px-5{padding-right:3rem!important}.pb-5,.py-5{padding-bottom:3rem!important}.pl-5,.px-5{padding-left:3rem!important}.m-n1{margin:-.25rem!important}.mt-n1,.my-n1{margin-top:-.25rem!important}.mr-n1,.mx-n1{margin-right:-.25rem!important}.mb-n1,.my-n1{margin-bottom:-.25rem!important}.ml-n1,.mx-n1{margin-left:-.25rem!important}.m-n2{margin:-.5rem!important}.mt-n2,.my-n2{margin-top:-.5rem!important}.mr-n2,.mx-n2{margin-right:-.5rem!important}.mb-n2,.my-n2{margin-bottom:-.5rem!important}.ml-n2,.mx-n2{margin-left:-.5rem!important}.m-n3{margin:-1rem!important}.mt-n3,.my-n3{margin-top:-1rem!important}.mr-n3,.mx-n3{margin-right:-1rem!important}.mb-n3,.my-n3{margin-bottom:-1rem!important}.ml-n3,.mx-n3{margin-left:-1rem!important}.m-n4{margin:-1.5rem!important}.mt-n4,.my-n4{margin-top:-1.5rem!important}.mr-n4,.mx-n4{margin-right:-1.5rem!important}.mb-n4,.my-n4{margin-bottom:-1.5rem!important}.ml-n4,.mx-n4{margin-left:-1.5rem!important}.m-n5{margin:-3rem!important}.mt-n5,.my-n5{margin-top:-3rem!important}.mr-n5,.mx-n5{margin-right:-3rem!important}.mb-n5,.my-n5{margin-bottom:-3rem!important}.ml-n5,.mx-n5{margin-left:-3rem!important}.m-auto{margin:auto!important}.mt-auto,.my-auto{margin-top:auto!important}.mr-auto,.mx-auto{margin-right:auto!important}.mb-auto,.my-auto{margin-bottom:auto!important}.ml-auto,.mx-auto{margin-left:auto!important}@media (min-width:576px){.m-sm-0{margin:0!important}.mt-sm-0,.my-sm-0{margin-top:0!important}.mr-sm-0,.mx-sm-0{margin-right:0!important}.mb-sm-0,.my-sm-0{margin-bottom:0!important}.ml-sm-0,.mx-sm-0{margin-left:0!important}.m-sm-1{margin:.25rem!important}.mt-sm-1,.my-sm-1{margin-top:.25rem!important}.mr-sm-1,.mx-sm-1{margin-right:.25rem!important}.mb-sm-1,.my-sm-1{margin-bottom:.25rem!important}.ml-sm-1,.mx-sm-1{margin-left:.25rem!important}.m-sm-2{margin:.5rem!important}.mt-sm-2,.my-sm-2{margin-top:.5rem!important}.mr-sm-2,.mx-sm-2{margin-right:.5rem!important}.mb-sm-2,.my-sm-2{margin-bottom:.5rem!important}.ml-sm-2,.mx-sm-2{margin-left:.5rem!important}.m-sm-3{margin:1rem!important}.mt-sm-3,.my-sm-3{margin-top:1rem!important}.mr-sm-3,.mx-sm-3{margin-right:1rem!important}.mb-sm-3,.my-sm-3{margin-bottom:1rem!important}.ml-sm-3,.mx-sm-3{margin-left:1rem!important}.m-sm-4{margin:1.5rem!important}.mt-sm-4,.my-sm-4{margin-top:1.5rem!important}.mr-sm-4,.mx-sm-4{margin-right:1.5rem!important}.mb-sm-4,.my-sm-4{margin-bottom:1.5rem!important}.ml-sm-4,.mx-sm-4{margin-left:1.5rem!important}.m-sm-5{margin:3rem!important}.mt-sm-5,.my-sm-5{margin-top:3rem!important}.mr-sm-5,.mx-sm-5{margin-right:3rem!important}.mb-sm-5,.my-sm-5{margin-bottom:3rem!important}.ml-sm-5,.mx-sm-5{margin-left:3rem!important}.p-sm-0{padding:0!important}.pt-sm-0,.py-sm-0{padding-top:0!important}.pr-sm-0,.px-sm-0{padding-right:0!important}.pb-sm-0,.py-sm-0{padding-bottom:0!important}.pl-sm-0,.px-sm-0{padding-left:0!important}.p-sm-1{padding:.25rem!important}.pt-sm-1,.py-sm-1{padding-top:.25rem!important}.pr-sm-1,.px-sm-1{padding-right:.25rem!important}.pb-sm-1,.py-sm-1{padding-bottom:.25rem!important}.pl-sm-1,.px-sm-1{padding-left:.25rem!important}.p-sm-2{padding:.5rem!important}.pt-sm-2,.py-sm-2{padding-top:.5rem!important}.pr-sm-2,.px-sm-2{padding-right:.5rem!important}.pb-sm-2,.py-sm-2{padding-bottom:.5rem!important}.pl-sm-2,.px-sm-2{padding-left:.5rem!important}.p-sm-3{padding:1rem!important}.pt-sm-3,.py-sm-3{padding-top:1rem!important}.pr-sm-3,.px-sm-3{padding-right:1rem!important}.pb-sm-3,.py-sm-3{padding-bottom:1rem!important}.pl-sm-3,.px-sm-3{padding-left:1rem!important}.p-sm-4{padding:1.5rem!important}.pt-sm-4,.py-sm-4{padding-top:1.5rem!important}.pr-sm-4,.px-sm-4{padding-right:1.5rem!important}.pb-sm-4,.py-sm-4{padding-bottom:1.5rem!important}.pl-sm-4,.px-sm-4{padding-left:1.5rem!important}.p-sm-5{padding:3rem!important}.pt-sm-5,.py-sm-5{padding-top:3rem!important}.pr-sm-5,.px-sm-5{padding-right:3rem!important}.pb-sm-5,.py-sm-5{padding-bottom:3rem!important}.pl-sm-5,.px-sm-5{padding-left:3rem!important}.m-sm-n1{margin:-.25rem!important}.mt-sm-n1,.my-sm-n1{margin-top:-.25rem!important}.mr-sm-n1,.mx-sm-n1{margin-right:-.25rem!important}.mb-sm-n1,.my-sm-n1{margin-bottom:-.25rem!important}.ml-sm-n1,.mx-sm-n1{margin-left:-.25rem!important}.m-sm-n2{margin:-.5rem!important}.mt-sm-n2,.my-sm-n2{margin-top:-.5rem!important}.mr-sm-n2,.mx-sm-n2{margin-right:-.5rem!important}.mb-sm-n2,.my-sm-n2{margin-bottom:-.5rem!important}.ml-sm-n2,.mx-sm-n2{margin-left:-.5rem!important}.m-sm-n3{margin:-1rem!important}.mt-sm-n3,.my-sm-n3{margin-top:-1rem!important}.mr-sm-n3,.mx-sm-n3{margin-right:-1rem!important}.mb-sm-n3,.my-sm-n3{margin-bottom:-1rem!important}.ml-sm-n3,.mx-sm-n3{margin-left:-1rem!important}.m-sm-n4{margin:-1.5rem!important}.mt-sm-n4,.my-sm-n4{margin-top:-1.5rem!important}.mr-sm-n4,.mx-sm-n4{margin-right:-1.5rem!important}.mb-sm-n4,.my-sm-n4{margin-bottom:-1.5rem!important}.ml-sm-n4,.mx-sm-n4{margin-left:-1.5rem!important}.m-sm-n5{margin:-3rem!important}.mt-sm-n5,.my-sm-n5{margin-top:-3rem!important}.mr-sm-n5,.mx-sm-n5{margin-right:-3rem!important}.mb-sm-n5,.my-sm-n5{margin-bottom:-3rem!important}.ml-sm-n5,.mx-sm-n5{margin-left:-3rem!important}.m-sm-auto{margin:auto!important}.mt-sm-auto,.my-sm-auto{margin-top:auto!important}.mr-sm-auto,.mx-sm-auto{margin-right:auto!important}.mb-sm-auto,.my-sm-auto{margin-bottom:auto!important}.ml-sm-auto,.mx-sm-auto{margin-left:auto!important}}@media (min-width:768px){.m-md-0{margin:0!important}.mt-md-0,.my-md-0{margin-top:0!important}.mr-md-0,.mx-md-0{margin-right:0!important}.mb-md-0,.my-md-0{margin-bottom:0!important}.ml-md-0,.mx-md-0{margin-left:0!important}.m-md-1{margin:.25rem!important}.mt-md-1,.my-md-1{margin-top:.25rem!important}.mr-md-1,.mx-md-1{margin-right:.25rem!important}.mb-md-1,.my-md-1{margin-bottom:.25rem!important}.ml-md-1,.mx-md-1{margin-left:.25rem!important}.m-md-2{margin:.5rem!important}.mt-md-2,.my-md-2{margin-top:.5rem!important}.mr-md-2,.mx-md-2{margin-right:.5rem!important}.mb-md-2,.my-md-2{margin-bottom:.5rem!important}.ml-md-2,.mx-md-2{margin-left:.5rem!important}.m-md-3{margin:1rem!important}.mt-md-3,.my-md-3{margin-top:1rem!important}.mr-md-3,.mx-md-3{margin-right:1rem!important}.mb-md-3,.my-md-3{margin-bottom:1rem!important}.ml-md-3,.mx-md-3{margin-left:1rem!important}.m-md-4{margin:1.5rem!important}.mt-md-4,.my-md-4{margin-top:1.5rem!important}.mr-md-4,.mx-md-4{margin-right:1.5rem!important}.mb-md-4,.my-md-4{margin-bottom:1.5rem!important}.ml-md-4,.mx-md-4{margin-left:1.5rem!important}.m-md-5{margin:3rem!important}.mt-md-5,.my-md-5{margin-top:3rem!important}.mr-md-5,.mx-md-5{margin-right:3rem!important}.mb-md-5,.my-md-5{margin-bottom:3rem!important}.ml-md-5,.mx-md-5{margin-left:3rem!important}.p-md-0{padding:0!important}.pt-md-0,.py-md-0{padding-top:0!important}.pr-md-0,.px-md-0{padding-right:0!important}.pb-md-0,.py-md-0{padding-bottom:0!important}.pl-md-0,.px-md-0{padding-left:0!important}.p-md-1{padding:.25rem!important}.pt-md-1,.py-md-1{padding-top:.25rem!important}.pr-md-1,.px-md-1{padding-right:.25rem!important}.pb-md-1,.py-md-1{padding-bottom:.25rem!important}.pl-md-1,.px-md-1{padding-left:.25rem!important}.p-md-2{padding:.5rem!important}.pt-md-2,.py-md-2{padding-top:.5rem!important}.pr-md-2,.px-md-2{padding-right:.5rem!important}.pb-md-2,.py-md-2{padding-bottom:.5rem!important}.pl-md-2,.px-md-2{padding-left:.5rem!important}.p-md-3{padding:1rem!important}.pt-md-3,.py-md-3{padding-top:1rem!important}.pr-md-3,.px-md-3{padding-right:1rem!important}.pb-md-3,.py-md-3{padding-bottom:1rem!important}.pl-md-3,.px-md-3{padding-left:1rem!important}.p-md-4{padding:1.5rem!important}.pt-md-4,.py-md-4{padding-top:1.5rem!important}.pr-md-4,.px-md-4{padding-right:1.5rem!important}.pb-md-4,.py-md-4{padding-bottom:1.5rem!important}.pl-md-4,.px-md-4{padding-left:1.5rem!important}.p-md-5{padding:3rem!important}.pt-md-5,.py-md-5{padding-top:3rem!important}.pr-md-5,.px-md-5{padding-right:3rem!important}.pb-md-5,.py-md-5{padding-bottom:3rem!important}.pl-md-5,.px-md-5{padding-left:3rem!important}.m-md-n1{margin:-.25rem!important}.mt-md-n1,.my-md-n1{margin-top:-.25rem!important}.mr-md-n1,.mx-md-n1{margin-right:-.25rem!important}.mb-md-n1,.my-md-n1{margin-bottom:-.25rem!important}.ml-md-n1,.mx-md-n1{margin-left:-.25rem!important}.m-md-n2{margin:-.5rem!important}.mt-md-n2,.my-md-n2{margin-top:-.5rem!important}.mr-md-n2,.mx-md-n2{margin-right:-.5rem!important}.mb-md-n2,.my-md-n2{margin-bottom:-.5rem!important}.ml-md-n2,.mx-md-n2{margin-left:-.5rem!important}.m-md-n3{margin:-1rem!important}.mt-md-n3,.my-md-n3{margin-top:-1rem!important}.mr-md-n3,.mx-md-n3{margin-right:-1rem!important}.mb-md-n3,.my-md-n3{margin-bottom:-1rem!important}.ml-md-n3,.mx-md-n3{margin-left:-1rem!important}.m-md-n4{margin:-1.5rem!important}.mt-md-n4,.my-md-n4{margin-top:-1.5rem!important}.mr-md-n4,.mx-md-n4{margin-right:-1.5rem!important}.mb-md-n4,.my-md-n4{margin-bottom:-1.5rem!important}.ml-md-n4,.mx-md-n4{margin-left:-1.5rem!important}.m-md-n5{margin:-3rem!important}.mt-md-n5,.my-md-n5{margin-top:-3rem!important}.mr-md-n5,.mx-md-n5{margin-right:-3rem!important}.mb-md-n5,.my-md-n5{margin-bottom:-3rem!important}.ml-md-n5,.mx-md-n5{margin-left:-3rem!important}.m-md-auto{margin:auto!important}.mt-md-auto,.my-md-auto{margin-top:auto!important}.mr-md-auto,.mx-md-auto{margin-right:auto!important}.mb-md-auto,.my-md-auto{margin-bottom:auto!important}.ml-md-auto,.mx-md-auto{margin-left:auto!important}}@media (min-width:992px){.m-lg-0{margin:0!important}.mt-lg-0,.my-lg-0{margin-top:0!important}.mr-lg-0,.mx-lg-0{margin-right:0!important}.mb-lg-0,.my-lg-0{margin-bottom:0!important}.ml-lg-0,.mx-lg-0{margin-left:0!important}.m-lg-1{margin:.25rem!important}.mt-lg-1,.my-lg-1{margin-top:.25rem!important}.mr-lg-1,.mx-lg-1{margin-right:.25rem!important}.mb-lg-1,.my-lg-1{margin-bottom:.25rem!important}.ml-lg-1,.mx-lg-1{margin-left:.25rem!important}.m-lg-2{margin:.5rem!important}.mt-lg-2,.my-lg-2{margin-top:.5rem!important}.mr-lg-2,.mx-lg-2{margin-right:.5rem!important}.mb-lg-2,.my-lg-2{margin-bottom:.5rem!important}.ml-lg-2,.mx-lg-2{margin-left:.5rem!important}.m-lg-3{margin:1rem!important}.mt-lg-3,.my-lg-3{margin-top:1rem!important}.mr-lg-3,.mx-lg-3{margin-right:1rem!important}.mb-lg-3,.my-lg-3{margin-bottom:1rem!important}.ml-lg-3,.mx-lg-3{margin-left:1rem!important}.m-lg-4{margin:1.5rem!important}.mt-lg-4,.my-lg-4{margin-top:1.5rem!important}.mr-lg-4,.mx-lg-4{margin-right:1.5rem!important}.mb-lg-4,.my-lg-4{margin-bottom:1.5rem!important}.ml-lg-4,.mx-lg-4{margin-left:1.5rem!important}.m-lg-5{margin:3rem!important}.mt-lg-5,.my-lg-5{margin-top:3rem!important}.mr-lg-5,.mx-lg-5{margin-right:3rem!important}.mb-lg-5,.my-lg-5{margin-bottom:3rem!important}.ml-lg-5,.mx-lg-5{margin-left:3rem!important}.p-lg-0{padding:0!important}.pt-lg-0,.py-lg-0{padding-top:0!important}.pr-lg-0,.px-lg-0{padding-right:0!important}.pb-lg-0,.py-lg-0{padding-bottom:0!important}.pl-lg-0,.px-lg-0{padding-left:0!important}.p-lg-1{padding:.25rem!important}.pt-lg-1,.py-lg-1{padding-top:.25rem!important}.pr-lg-1,.px-lg-1{padding-right:.25rem!important}.pb-lg-1,.py-lg-1{padding-bottom:.25rem!important}.pl-lg-1,.px-lg-1{padding-left:.25rem!important}.p-lg-2{padding:.5rem!important}.pt-lg-2,.py-lg-2{padding-top:.5rem!important}.pr-lg-2,.px-lg-2{padding-right:.5rem!important}.pb-lg-2,.py-lg-2{padding-bottom:.5rem!important}.pl-lg-2,.px-lg-2{padding-left:.5rem!important}.p-lg-3{padding:1rem!important}.pt-lg-3,.py-lg-3{padding-top:1rem!important}.pr-lg-3,.px-lg-3{padding-right:1rem!important}.pb-lg-3,.py-lg-3{padding-bottom:1rem!important}.pl-lg-3,.px-lg-3{padding-left:1rem!important}.p-lg-4{padding:1.5rem!important}.pt-lg-4,.py-lg-4{padding-top:1.5rem!important}.pr-lg-4,.px-lg-4{padding-right:1.5rem!important}.pb-lg-4,.py-lg-4{padding-bottom:1.5rem!important}.pl-lg-4,.px-lg-4{padding-left:1.5rem!important}.p-lg-5{padding:3rem!important}.pt-lg-5,.py-lg-5{padding-top:3rem!important}.pr-lg-5,.px-lg-5{padding-right:3rem!important}.pb-lg-5,.py-lg-5{padding-bottom:3rem!important}.pl-lg-5,.px-lg-5{padding-left:3rem!important}.m-lg-n1{margin:-.25rem!important}.mt-lg-n1,.my-lg-n1{margin-top:-.25rem!important}.mr-lg-n1,.mx-lg-n1{margin-right:-.25rem!important}.mb-lg-n1,.my-lg-n1{margin-bottom:-.25rem!important}.ml-lg-n1,.mx-lg-n1{margin-left:-.25rem!important}.m-lg-n2{margin:-.5rem!important}.mt-lg-n2,.my-lg-n2{margin-top:-.5rem!important}.mr-lg-n2,.mx-lg-n2{margin-right:-.5rem!important}.mb-lg-n2,.my-lg-n2{margin-bottom:-.5rem!important}.ml-lg-n2,.mx-lg-n2{margin-left:-.5rem!important}.m-lg-n3{margin:-1rem!important}.mt-lg-n3,.my-lg-n3{margin-top:-1rem!important}.mr-lg-n3,.mx-lg-n3{margin-right:-1rem!important}.mb-lg-n3,.my-lg-n3{margin-bottom:-1rem!important}.ml-lg-n3,.mx-lg-n3{margin-left:-1rem!important}.m-lg-n4{margin:-1.5rem!important}.mt-lg-n4,.my-lg-n4{margin-top:-1.5rem!important}.mr-lg-n4,.mx-lg-n4{margin-right:-1.5rem!important}.mb-lg-n4,.my-lg-n4{margin-bottom:-1.5rem!important}.ml-lg-n4,.mx-lg-n4{margin-left:-1.5rem!important}.m-lg-n5{margin:-3rem!important}.mt-lg-n5,.my-lg-n5{margin-top:-3rem!important}.mr-lg-n5,.mx-lg-n5{margin-right:-3rem!important}.mb-lg-n5,.my-lg-n5{margin-bottom:-3rem!important}.ml-lg-n5,.mx-lg-n5{margin-left:-3rem!important}.m-lg-auto{margin:auto!important}.mt-lg-auto,.my-lg-auto{margin-top:auto!important}.mr-lg-auto,.mx-lg-auto{margin-right:auto!important}.mb-lg-auto,.my-lg-auto{margin-bottom:auto!important}.ml-lg-auto,.mx-lg-auto{margin-left:auto!important}}@media (min-width:1200px){.m-xl-0{margin:0!important}.mt-xl-0,.my-xl-0{margin-top:0!important}.mr-xl-0,.mx-xl-0{margin-right:0!important}.mb-xl-0,.my-xl-0{margin-bottom:0!important}.ml-xl-0,.mx-xl-0{margin-left:0!important}.m-xl-1{margin:.25rem!important}.mt-xl-1,.my-xl-1{margin-top:.25rem!important}.mr-xl-1,.mx-xl-1{margin-right:.25rem!important}.mb-xl-1,.my-xl-1{margin-bottom:.25rem!important}.ml-xl-1,.mx-xl-1{margin-left:.25rem!important}.m-xl-2{margin:.5rem!important}.mt-xl-2,.my-xl-2{margin-top:.5rem!important}.mr-xl-2,.mx-xl-2{margin-right:.5rem!important}.mb-xl-2,.my-xl-2{margin-bottom:.5rem!important}.ml-xl-2,.mx-xl-2{margin-left:.5rem!important}.m-xl-3{margin:1rem!important}.mt-xl-3,.my-xl-3{margin-top:1rem!important}.mr-xl-3,.mx-xl-3{margin-right:1rem!important}.mb-xl-3,.my-xl-3{margin-bottom:1rem!important}.ml-xl-3,.mx-xl-3{margin-left:1rem!important}.m-xl-4{margin:1.5rem!important}.mt-xl-4,.my-xl-4{margin-top:1.5rem!important}.mr-xl-4,.mx-xl-4{margin-right:1.5rem!important}.mb-xl-4,.my-xl-4{margin-bottom:1.5rem!important}.ml-xl-4,.mx-xl-4{margin-left:1.5rem!important}.m-xl-5{margin:3rem!important}.mt-xl-5,.my-xl-5{margin-top:3rem!important}.mr-xl-5,.mx-xl-5{margin-right:3rem!important}.mb-xl-5,.my-xl-5{margin-bottom:3rem!important}.ml-xl-5,.mx-xl-5{margin-left:3rem!important}.p-xl-0{padding:0!important}.pt-xl-0,.py-xl-0{padding-top:0!important}.pr-xl-0,.px-xl-0{padding-right:0!important}.pb-xl-0,.py-xl-0{padding-bottom:0!important}.pl-xl-0,.px-xl-0{padding-left:0!important}.p-xl-1{padding:.25rem!important}.pt-xl-1,.py-xl-1{padding-top:.25rem!important}.pr-xl-1,.px-xl-1{padding-right:.25rem!important}.pb-xl-1,.py-xl-1{padding-bottom:.25rem!important}.pl-xl-1,.px-xl-1{padding-left:.25rem!important}.p-xl-2{padding:.5rem!important}.pt-xl-2,.py-xl-2{padding-top:.5rem!important}.pr-xl-2,.px-xl-2{padding-right:.5rem!important}.pb-xl-2,.py-xl-2{padding-bottom:.5rem!important}.pl-xl-2,.px-xl-2{padding-left:.5rem!important}.p-xl-3{padding:1rem!important}.pt-xl-3,.py-xl-3{padding-top:1rem!important}.pr-xl-3,.px-xl-3{padding-right:1rem!important}.pb-xl-3,.py-xl-3{padding-bottom:1rem!important}.pl-xl-3,.px-xl-3{padding-left:1rem!important}.p-xl-4{padding:1.5rem!important}.pt-xl-4,.py-xl-4{padding-top:1.5rem!important}.pr-xl-4,.px-xl-4{padding-right:1.5rem!important}.pb-xl-4,.py-xl-4{padding-bottom:1.5rem!important}.pl-xl-4,.px-xl-4{padding-left:1.5rem!important}.p-xl-5{padding:3rem!important}.pt-xl-5,.py-xl-5{padding-top:3rem!important}.pr-xl-5,.px-xl-5{padding-right:3rem!important}.pb-xl-5,.py-xl-5{padding-bottom:3rem!important}.pl-xl-5,.px-xl-5{padding-left:3rem!important}.m-xl-n1{margin:-.25rem!important}.mt-xl-n1,.my-xl-n1{margin-top:-.25rem!important}.mr-xl-n1,.mx-xl-n1{margin-right:-.25rem!important}.mb-xl-n1,.my-xl-n1{margin-bottom:-.25rem!important}.ml-xl-n1,.mx-xl-n1{margin-left:-.25rem!important}.m-xl-n2{margin:-.5rem!important}.mt-xl-n2,.my-xl-n2{margin-top:-.5rem!important}.mr-xl-n2,.mx-xl-n2{margin-right:-.5rem!important}.mb-xl-n2,.my-xl-n2{margin-bottom:-.5rem!important}.ml-xl-n2,.mx-xl-n2{margin-left:-.5rem!important}.m-xl-n3{margin:-1rem!important}.mt-xl-n3,.my-xl-n3{margin-top:-1rem!important}.mr-xl-n3,.mx-xl-n3{margin-right:-1rem!important}.mb-xl-n3,.my-xl-n3{margin-bottom:-1rem!important}.ml-xl-n3,.mx-xl-n3{margin-left:-1rem!important}.m-xl-n4{margin:-1.5rem!important}.mt-xl-n4,.my-xl-n4{margin-top:-1.5rem!important}.mr-xl-n4,.mx-xl-n4{margin-right:-1.5rem!important}.mb-xl-n4,.my-xl-n4{margin-bottom:-1.5rem!important}.ml-xl-n4,.mx-xl-n4{margin-left:-1.5rem!important}.m-xl-n5{margin:-3rem!important}.mt-xl-n5,.my-xl-n5{margin-top:-3rem!important}.mr-xl-n5,.mx-xl-n5{margin-right:-3rem!important}.mb-xl-n5,.my-xl-n5{margin-bottom:-3rem!important}.ml-xl-n5,.mx-xl-n5{margin-left:-3rem!important}.m-xl-auto{margin:auto!important}.mt-xl-auto,.my-xl-auto{margin-top:auto!important}.mr-xl-auto,.mx-xl-auto{margin-right:auto!important}.mb-xl-auto,.my-xl-auto{margin-bottom:auto!important}.ml-xl-auto,.mx-xl-auto{margin-left:auto!important}}.stretched-link::after{position:absolute;top:0;right:0;bottom:0;left:0;z-index:1;pointer-events:auto;content:"";background-color:rgba(0,0,0,0)}.text-monospace{font-family:SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace!important}.text-justify{text-align:justify!important}.text-wrap{white-space:normal!important}.text-nowrap{white-space:nowrap!important}.text-truncate{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.text-left{text-align:left!important}.text-right{text-align:right!important}.text-center{text-align:center!important}@media (min-width:576px){.text-sm-left{text-align:left!important}.text-sm-right{text-align:right!important}.text-sm-center{text-align:center!important}}@media (min-width:768px){.text-md-left{text-align:left!important}.text-md-right{text-align:right!important}.text-md-center{text-align:center!important}}@media (min-width:992px){.text-lg-left{text-align:left!important}.text-lg-right{text-align:right!important}.text-lg-center{text-align:center!important}}@media (min-width:1200px){.text-xl-left{text-align:left!important}.text-xl-right{text-align:right!important}.text-xl-center{text-align:center!important}}.text-lowercase{text-transform:lowercase!important}.text-uppercase{text-transform:uppercase!important}.text-capitalize{text-transform:capitalize!important}.font-weight-light{font-weight:300!important}.font-weight-lighter{font-weight:lighter!important}.font-weight-normal{font-weight:400!important}.font-weight-bold{font-weight:700!important}.font-weight-bolder{font-weight:bolder!important}.font-italic{font-style:italic!important}.text-white{color:#fff!important}.text-primary{color:#007bff!important}a.text-primary:focus,a.text-primary:hover{color:#0056b3!important}.text-secondary{color:#6c757d!important}a.text-secondary:focus,a.text-secondary:hover{color:#494f54!important}.text-success{color:#28a745!important}a.text-success:focus,a.text-success:hover{color:#19692c!important}.text-info{color:#17a2b8!important}a.text-info:focus,a.text-info:hover{color:#0f6674!important}.text-warning{color:#ffc107!important}a.text-warning:focus,a.text-warning:hover{color:#ba8b00!important}.text-danger{color:#dc3545!important}a.text-danger:focus,a.text-danger:hover{color:#a71d2a!important}.text-light{color:#f8f9fa!important}a.text-light:focus,a.text-light:hover{color:#cbd3da!important}.text-dark{color:#343a40!important}a.text-dark:focus,a.text-dark:hover{color:#121416!important}.text-body{color:#212529!important}.text-muted{color:#6c757d!important}.text-black-50{color:rgba(0,0,0,.5)!important}.text-white-50{color:rgba(255,255,255,.5)!important}.text-hide{font:0/0 a;color:transparent;text-shadow:none;background-color:transparent;border:0}.text-decoration-none{text-decoration:none!important}.text-break{word-break:break-word!important;word-wrap:break-word!important}.text-reset{color:inherit!important}.visible{visibility:visible!important}.invisible{visibility:hidden!important}@media print{*,::after,::before{text-shadow:none!important;box-shadow:none!important}a:not(.btn){text-decoration:underline}abbr[title]::after{content:" (" attr(title) ")"}pre{white-space:pre-wrap!important}blockquote,pre{border:1px solid #adb5bd;page-break-inside:avoid}img,tr{page-break-inside:avoid}h2,h3,p{orphans:3;widows:3}h2,h3{page-break-after:avoid}@page{size:a3}body{min-width:992px!important}.container{min-width:992px!important}.navbar{display:none}.badge{border:1px solid #000}.table{border-collapse:collapse!important}.table td,.table th{background-color:#fff!important}.table-bordered td,.table-bordered th{border:1px solid #dee2e6!important}.table-dark{color:inherit}.table-dark tbody+tbody,.table-dark td,.table-dark th,.table-dark thead th{border-color:#dee2e6}.table .thead-dark th{color:inherit;border-color:#dee2e6}} + * Bootstrap v3.3.6 (http://getbootstrap.com) + * Copyright 2011-2015 Twitter, Inc. + * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) + *//*! normalize.css v3.0.3 | MIT License | github.com/necolas/normalize.css */html{font-family:sans-serif;-webkit-text-size-adjust:100%;-ms-text-size-adjust:100%}body{margin:0}article,aside,details,figcaption,figure,footer,header,hgroup,main,menu,nav,section,summary{display:block}audio,canvas,progress,video{display:inline-block;vertical-align:baseline}audio:not([controls]){display:none;height:0}[hidden],template{display:none}a{background-color:transparent}a:active,a:hover{outline:0}abbr[title]{border-bottom:1px dotted}b,strong{font-weight:700}dfn{font-style:italic}h1{margin:.67em 0;font-size:2em}mark{color:#000;background:#ff0}small{font-size:80%}sub,sup{position:relative;font-size:75%;line-height:0;vertical-align:baseline}sup{top:-.5em}sub{bottom:-.25em}img{border:0}svg:not(:root){overflow:hidden}figure{margin:1em 40px}hr{height:0;-webkit-box-sizing:content-box;-moz-box-sizing:content-box;box-sizing:content-box}pre{overflow:auto}code,kbd,pre,samp{font-family:monospace,monospace;font-size:1em}button,input,optgroup,select,textarea{margin:0;font:inherit;color:inherit}button{overflow:visible}button,select{text-transform:none}button,html input[type=button],input[type=reset],input[type=submit]{-webkit-appearance:button;cursor:pointer}button[disabled],html input[disabled]{cursor:default}button::-moz-focus-inner,input::-moz-focus-inner{padding:0;border:0}input{line-height:normal}input[type=checkbox],input[type=radio]{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;padding:0}input[type=number]::-webkit-inner-spin-button,input[type=number]::-webkit-outer-spin-button{height:auto}input[type=search]{-webkit-box-sizing:content-box;-moz-box-sizing:content-box;box-sizing:content-box;-webkit-appearance:textfield}input[type=search]::-webkit-search-cancel-button,input[type=search]::-webkit-search-decoration{-webkit-appearance:none}fieldset{padding:.35em .625em .75em;margin:0 2px;border:1px solid silver}legend{padding:0;border:0}textarea{overflow:auto}optgroup{font-weight:700}table{border-spacing:0;border-collapse:collapse}td,th{padding:0}/*! Source: https://github.com/h5bp/html5-boilerplate/blob/master/src/css/main.css */@media print{*,:after,:before{color:#000!important;text-shadow:none!important;background:0 0!important;-webkit-box-shadow:none!important;box-shadow:none!important}a,a:visited{text-decoration:underline}a[href]:after{content:" (" attr(href) ")"}abbr[title]:after{content:" (" attr(title) ")"}a[href^="javascript:"]:after,a[href^="#"]:after{content:""}blockquote,pre{border:1px solid #999;page-break-inside:avoid}thead{display:table-header-group}img,tr{page-break-inside:avoid}img{max-width:100%!important}h2,h3,p{orphans:3;widows:3}h2,h3{page-break-after:avoid}.navbar{display:none}.btn>.caret,.dropup>.btn>.caret{border-top-color:#000!important}.label{border:1px solid #000}.table{border-collapse:collapse!important}.table td,.table th{background-color:#fff!important}.table-bordered td,.table-bordered th{border:1px solid #ddd!important}}@font-face{font-family:'Glyphicons Halflings';src:url(../fonts/glyphicons-halflings-regular.eot);src:url(../fonts/glyphicons-halflings-regular.eot?#iefix) format('embedded-opentype'),url(../fonts/glyphicons-halflings-regular.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:400;line-height:1;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.glyphicon-asterisk:before{content:"\002a"}.glyphicon-plus:before{content:"\002b"}.glyphicon-eur:before,.glyphicon-euro:before{content:"\20ac"}.glyphicon-minus:before{content:"\2212"}.glyphicon-cloud:before{content:"\2601"}.glyphicon-envelope:before{content:"\2709"}.glyphicon-pencil:before{content:"\270f"}.glyphicon-glass:before{content:"\e001"}.glyphicon-music:before{content:"\e002"}.glyphicon-search:before{content:"\e003"}.glyphicon-heart:before{content:"\e005"}.glyphicon-star:before{content:"\e006"}.glyphicon-star-empty:before{content:"\e007"}.glyphicon-user:before{content:"\e008"}.glyphicon-film:before{content:"\e009"}.glyphicon-th-large:before{content:"\e010"}.glyphicon-th:before{content:"\e011"}.glyphicon-th-list:before{content:"\e012"}.glyphicon-ok:before{content:"\e013"}.glyphicon-remove:before{content:"\e014"}.glyphicon-zoom-in:before{content:"\e015"}.glyphicon-zoom-out:before{content:"\e016"}.glyphicon-off:before{content:"\e017"}.glyphicon-signal:before{content:"\e018"}.glyphicon-cog:before{content:"\e019"}.glyphicon-trash:before{content:"\e020"}.glyphicon-home:before{content:"\e021"}.glyphicon-file:before{content:"\e022"}.glyphicon-time:before{content:"\e023"}.glyphicon-road:before{content:"\e024"}.glyphicon-download-alt:before{content:"\e025"}.glyphicon-download:before{content:"\e026"}.glyphicon-upload:before{content:"\e027"}.glyphicon-inbox:before{content:"\e028"}.glyphicon-play-circle:before{content:"\e029"}.glyphicon-repeat:before{content:"\e030"}.glyphicon-refresh:before{content:"\e031"}.glyphicon-list-alt:before{content:"\e032"}.glyphicon-lock:before{content:"\e033"}.glyphicon-flag:before{content:"\e034"}.glyphicon-headphones:before{content:"\e035"}.glyphicon-volume-off:before{content:"\e036"}.glyphicon-volume-down:before{content:"\e037"}.glyphicon-volume-up:before{content:"\e038"}.glyphicon-qrcode:before{content:"\e039"}.glyphicon-barcode:before{content:"\e040"}.glyphicon-tag:before{content:"\e041"}.glyphicon-tags:before{content:"\e042"}.glyphicon-book:before{content:"\e043"}.glyphicon-bookmark:before{content:"\e044"}.glyphicon-print:before{content:"\e045"}.glyphicon-camera:before{content:"\e046"}.glyphicon-font:before{content:"\e047"}.glyphicon-bold:before{content:"\e048"}.glyphicon-italic:before{content:"\e049"}.glyphicon-text-height:before{content:"\e050"}.glyphicon-text-width:before{content:"\e051"}.glyphicon-align-left:before{content:"\e052"}.glyphicon-align-center:before{content:"\e053"}.glyphicon-align-right:before{content:"\e054"}.glyphicon-align-justify:before{content:"\e055"}.glyphicon-list:before{content:"\e056"}.glyphicon-indent-left:before{content:"\e057"}.glyphicon-indent-right:before{content:"\e058"}.glyphicon-facetime-video:before{content:"\e059"}.glyphicon-picture:before{content:"\e060"}.glyphicon-map-marker:before{content:"\e062"}.glyphicon-adjust:before{content:"\e063"}.glyphicon-tint:before{content:"\e064"}.glyphicon-edit:before{content:"\e065"}.glyphicon-share:before{content:"\e066"}.glyphicon-check:before{content:"\e067"}.glyphicon-move:before{content:"\e068"}.glyphicon-step-backward:before{content:"\e069"}.glyphicon-fast-backward:before{content:"\e070"}.glyphicon-backward:before{content:"\e071"}.glyphicon-play:before{content:"\e072"}.glyphicon-pause:before{content:"\e073"}.glyphicon-stop:before{content:"\e074"}.glyphicon-forward:before{content:"\e075"}.glyphicon-fast-forward:before{content:"\e076"}.glyphicon-step-forward:before{content:"\e077"}.glyphicon-eject:before{content:"\e078"}.glyphicon-chevron-left:before{content:"\e079"}.glyphicon-chevron-right:before{content:"\e080"}.glyphicon-plus-sign:before{content:"\e081"}.glyphicon-minus-sign:before{content:"\e082"}.glyphicon-remove-sign:before{content:"\e083"}.glyphicon-ok-sign:before{content:"\e084"}.glyphicon-question-sign:before{content:"\e085"}.glyphicon-info-sign:before{content:"\e086"}.glyphicon-screenshot:before{content:"\e087"}.glyphicon-remove-circle:before{content:"\e088"}.glyphicon-ok-circle:before{content:"\e089"}.glyphicon-ban-circle:before{content:"\e090"}.glyphicon-arrow-left:before{content:"\e091"}.glyphicon-arrow-right:before{content:"\e092"}.glyphicon-arrow-up:before{content:"\e093"}.glyphicon-arrow-down:before{content:"\e094"}.glyphicon-share-alt:before{content:"\e095"}.glyphicon-resize-full:before{content:"\e096"}.glyphicon-resize-small:before{content:"\e097"}.glyphicon-exclamation-sign:before{content:"\e101"}.glyphicon-gift:before{content:"\e102"}.glyphicon-leaf:before{content:"\e103"}.glyphicon-fire:before{content:"\e104"}.glyphicon-eye-open:before{content:"\e105"}.glyphicon-eye-close:before{content:"\e106"}.glyphicon-warning-sign:before{content:"\e107"}.glyphicon-plane:before{content:"\e108"}.glyphicon-calendar:before{content:"\e109"}.glyphicon-random:before{content:"\e110"}.glyphicon-comment:before{content:"\e111"}.glyphicon-magnet:before{content:"\e112"}.glyphicon-chevron-up:before{content:"\e113"}.glyphicon-chevron-down:before{content:"\e114"}.glyphicon-retweet:before{content:"\e115"}.glyphicon-shopping-cart:before{content:"\e116"}.glyphicon-folder-close:before{content:"\e117"}.glyphicon-folder-open:before{content:"\e118"}.glyphicon-resize-vertical:before{content:"\e119"}.glyphicon-resize-horizontal:before{content:"\e120"}.glyphicon-hdd:before{content:"\e121"}.glyphicon-bullhorn:before{content:"\e122"}.glyphicon-bell:before{content:"\e123"}.glyphicon-certificate:before{content:"\e124"}.glyphicon-thumbs-up:before{content:"\e125"}.glyphicon-thumbs-down:before{content:"\e126"}.glyphicon-hand-right:before{content:"\e127"}.glyphicon-hand-left:before{content:"\e128"}.glyphicon-hand-up:before{content:"\e129"}.glyphicon-hand-down:before{content:"\e130"}.glyphicon-circle-arrow-right:before{content:"\e131"}.glyphicon-circle-arrow-left:before{content:"\e132"}.glyphicon-circle-arrow-up:before{content:"\e133"}.glyphicon-circle-arrow-down:before{content:"\e134"}.glyphicon-globe:before{content:"\e135"}.glyphicon-wrench:before{content:"\e136"}.glyphicon-tasks:before{content:"\e137"}.glyphicon-filter:before{content:"\e138"}.glyphicon-briefcase:before{content:"\e139"}.glyphicon-fullscreen:before{content:"\e140"}.glyphicon-dashboard:before{content:"\e141"}.glyphicon-paperclip:before{content:"\e142"}.glyphicon-heart-empty:before{content:"\e143"}.glyphicon-link:before{content:"\e144"}.glyphicon-phone:before{content:"\e145"}.glyphicon-pushpin:before{content:"\e146"}.glyphicon-usd:before{content:"\e148"}.glyphicon-gbp:before{content:"\e149"}.glyphicon-sort:before{content:"\e150"}.glyphicon-sort-by-alphabet:before{content:"\e151"}.glyphicon-sort-by-alphabet-alt:before{content:"\e152"}.glyphicon-sort-by-order:before{content:"\e153"}.glyphicon-sort-by-order-alt:before{content:"\e154"}.glyphicon-sort-by-attributes:before{content:"\e155"}.glyphicon-sort-by-attributes-alt:before{content:"\e156"}.glyphicon-unchecked:before{content:"\e157"}.glyphicon-expand:before{content:"\e158"}.glyphicon-collapse-down:before{content:"\e159"}.glyphicon-collapse-up:before{content:"\e160"}.glyphicon-log-in:before{content:"\e161"}.glyphicon-flash:before{content:"\e162"}.glyphicon-log-out:before{content:"\e163"}.glyphicon-new-window:before{content:"\e164"}.glyphicon-record:before{content:"\e165"}.glyphicon-save:before{content:"\e166"}.glyphicon-open:before{content:"\e167"}.glyphicon-saved:before{content:"\e168"}.glyphicon-import:before{content:"\e169"}.glyphicon-export:before{content:"\e170"}.glyphicon-send:before{content:"\e171"}.glyphicon-floppy-disk:before{content:"\e172"}.glyphicon-floppy-saved:before{content:"\e173"}.glyphicon-floppy-remove:before{content:"\e174"}.glyphicon-floppy-save:before{content:"\e175"}.glyphicon-floppy-open:before{content:"\e176"}.glyphicon-credit-card:before{content:"\e177"}.glyphicon-transfer:before{content:"\e178"}.glyphicon-cutlery:before{content:"\e179"}.glyphicon-header:before{content:"\e180"}.glyphicon-compressed:before{content:"\e181"}.glyphicon-earphone:before{content:"\e182"}.glyphicon-phone-alt:before{content:"\e183"}.glyphicon-tower:before{content:"\e184"}.glyphicon-stats:before{content:"\e185"}.glyphicon-sd-video:before{content:"\e186"}.glyphicon-hd-video:before{content:"\e187"}.glyphicon-subtitles:before{content:"\e188"}.glyphicon-sound-stereo:before{content:"\e189"}.glyphicon-sound-dolby:before{content:"\e190"}.glyphicon-sound-5-1:before{content:"\e191"}.glyphicon-sound-6-1:before{content:"\e192"}.glyphicon-sound-7-1:before{content:"\e193"}.glyphicon-copyright-mark:before{content:"\e194"}.glyphicon-registration-mark:before{content:"\e195"}.glyphicon-cloud-download:before{content:"\e197"}.glyphicon-cloud-upload:before{content:"\e198"}.glyphicon-tree-conifer:before{content:"\e199"}.glyphicon-tree-deciduous:before{content:"\e200"}.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"}*{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}:after,:before{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}html{font-size:10px;-webkit-tap-highlight-color:rgba(0,0,0,0)}body{font-family:"Helvetica Neue",Helvetica,Arial,sans-serif;font-size:14px;line-height:1.42857143;color:#333;background-color:#fff}button,input,select,textarea{font-family:inherit;font-size:inherit;line-height:inherit}a{color:#337ab7;text-decoration:none}a:focus,a:hover{color:#23527c;text-decoration:underline}a:focus{outline:thin dotted;outline:5px auto -webkit-focus-ring-color;outline-offset:-2px}figure{margin:0}img{vertical-align:middle}.carousel-inner>.item>a>img,.carousel-inner>.item>img,.img-responsive,.thumbnail a>img,.thumbnail>img{display:block;max-width:100%;height:auto}.img-rounded{border-radius:6px}.img-thumbnail{display:inline-block;max-width:100%;height:auto;padding:4px;line-height:1.42857143;background-color:#fff;border:1px solid #ddd;border-radius:4px;-webkit-transition:all .2s ease-in-out;-o-transition:all .2s ease-in-out;transition:all .2s ease-in-out}.img-circle{border-radius:50%}hr{margin-top:20px;margin-bottom:20px;border:0;border-top:1px solid #eee}.sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);border:0}.sr-only-focusable:active,.sr-only-focusable:focus{position:static;width:auto;height:auto;margin:0;overflow:visible;clip:auto}[role=button]{cursor:pointer}.h1,.h2,.h3,.h4,.h5,.h6,h1,h2,h3,h4,h5,h6{font-family:inherit;font-weight:500;line-height:1.1;color:inherit}.h1 .small,.h1 small,.h2 .small,.h2 small,.h3 .small,.h3 small,.h4 .small,.h4 small,.h5 .small,.h5 small,.h6 .small,.h6 small,h1 .small,h1 small,h2 .small,h2 small,h3 .small,h3 small,h4 .small,h4 small,h5 .small,h5 small,h6 .small,h6 small{font-weight:400;line-height:1;color:#777}.h1,.h2,.h3,h1,h2,h3{margin-top:20px;margin-bottom:10px}.h1 .small,.h1 small,.h2 .small,.h2 small,.h3 .small,.h3 small,h1 .small,h1 small,h2 .small,h2 small,h3 .small,h3 small{font-size:65%}.h4,.h5,.h6,h4,h5,h6{margin-top:10px;margin-bottom:10px}.h4 .small,.h4 small,.h5 .small,.h5 small,.h6 .small,.h6 small,h4 .small,h4 small,h5 .small,h5 small,h6 .small,h6 small{font-size:75%}.h1,h1{font-size:36px}.h2,h2{font-size:30px}.h3,h3{font-size:24px}.h4,h4{font-size:18px}.h5,h5{font-size:14px}.h6,h6{font-size:12px}p{margin:0 0 10px}.lead{margin-bottom:20px;font-size:16px;font-weight:300;line-height:1.4}@media (min-width:768px){.lead{font-size:21px}}.small,small{font-size:85%}.mark,mark{padding:.2em;background-color:#fcf8e3}.text-left{text-align:left}.text-right{text-align:right}.text-center{text-align:center}.text-justify{text-align:justify}.text-nowrap{white-space:nowrap}.text-lowercase{text-transform:lowercase}.text-uppercase{text-transform:uppercase}.text-capitalize{text-transform:capitalize}.text-muted{color:#777}.text-primary{color:#337ab7}a.text-primary:focus,a.text-primary:hover{color:#286090}.text-success{color:#3c763d}a.text-success:focus,a.text-success:hover{color:#2b542c}.text-info{color:#31708f}a.text-info:focus,a.text-info:hover{color:#245269}.text-warning{color:#8a6d3b}a.text-warning:focus,a.text-warning:hover{color:#66512c}.text-danger{color:#a94442}a.text-danger:focus,a.text-danger:hover{color:#843534}.bg-primary{color:#fff;background-color:#337ab7}a.bg-primary:focus,a.bg-primary:hover{background-color:#286090}.bg-success{background-color:#dff0d8}a.bg-success:focus,a.bg-success:hover{background-color:#c1e2b3}.bg-info{background-color:#d9edf7}a.bg-info:focus,a.bg-info:hover{background-color:#afd9ee}.bg-warning{background-color:#fcf8e3}a.bg-warning:focus,a.bg-warning:hover{background-color:#f7ecb5}.bg-danger{background-color:#f2dede}a.bg-danger:focus,a.bg-danger:hover{background-color:#e4b9b9}.page-header{padding-bottom:9px;margin:40px 0 20px;border-bottom:1px solid #eee}ol,ul{margin-top:0;margin-bottom:10px}ol ol,ol ul,ul ol,ul ul{margin-bottom:0}.list-unstyled{padding-left:0;list-style:none}.list-inline{padding-left:0;margin-left:-5px;list-style:none}.list-inline>li{display:inline-block;padding-right:5px;padding-left:5px}dl{margin-top:0;margin-bottom:20px}dd,dt{line-height:1.42857143}dt{font-weight:700}dd{margin-left:0}@media (min-width:768px){.dl-horizontal dt{float:left;width:160px;overflow:hidden;clear:left;text-align:right;text-overflow:ellipsis;white-space:nowrap}.dl-horizontal dd{margin-left:180px}}abbr[data-original-title],abbr[title]{cursor:help;border-bottom:1px dotted #777}.initialism{font-size:90%;text-transform:uppercase}blockquote{padding:10px 20px;margin:0 0 20px;font-size:17.5px;border-left:5px solid #eee}blockquote ol:last-child,blockquote p:last-child,blockquote ul:last-child{margin-bottom:0}blockquote .small,blockquote footer,blockquote small{display:block;font-size:80%;line-height:1.42857143;color:#777}blockquote .small:before,blockquote footer:before,blockquote small:before{content:'\2014 \00A0'}.blockquote-reverse,blockquote.pull-right{padding-right:15px;padding-left:0;text-align:right;border-right:5px solid #eee;border-left:0}.blockquote-reverse .small:before,.blockquote-reverse footer:before,.blockquote-reverse small:before,blockquote.pull-right .small:before,blockquote.pull-right footer:before,blockquote.pull-right small:before{content:''}.blockquote-reverse .small:after,.blockquote-reverse footer:after,.blockquote-reverse small:after,blockquote.pull-right .small:after,blockquote.pull-right footer:after,blockquote.pull-right small:after{content:'\00A0 \2014'}address{margin-bottom:20px;font-style:normal;line-height:1.42857143}code,kbd,pre,samp{font-family:Menlo,Monaco,Consolas,"Courier New",monospace}code{padding:2px 4px;font-size:90%;color:#c7254e;background-color:#f9f2f4;border-radius:4px}kbd{padding:2px 4px;font-size:90%;color:#fff;background-color:#333;border-radius:3px;-webkit-box-shadow:inset 0 -1px 0 rgba(0,0,0,.25);box-shadow:inset 0 -1px 0 rgba(0,0,0,.25)}kbd kbd{padding:0;font-size:100%;font-weight:700;-webkit-box-shadow:none;box-shadow:none}pre{display:block;padding:9.5px;margin:0 0 10px;font-size:13px;line-height:1.42857143;color:#333;word-break:break-all;word-wrap:break-word;background-color:#f5f5f5;border:1px solid #ccc;border-radius:4px}pre code{padding:0;font-size:inherit;color:inherit;white-space:pre-wrap;background-color:transparent;border-radius:0}.pre-scrollable{max-height:340px;overflow-y:scroll}.container{padding-right:15px;padding-left:15px;margin-right:auto;margin-left:auto}@media (min-width:768px){.container{width:750px}}@media (min-width:992px){.container{width:970px}}@media (min-width:1200px){.container{width:1170px}}.container-fluid{padding-right:15px;padding-left:15px;margin-right:auto;margin-left:auto}.row{margin-right:-15px;margin-left:-15px}.col-lg-1,.col-lg-10,.col-lg-11,.col-lg-12,.col-lg-2,.col-lg-3,.col-lg-4,.col-lg-5,.col-lg-6,.col-lg-7,.col-lg-8,.col-lg-9,.col-md-1,.col-md-10,.col-md-11,.col-md-12,.col-md-2,.col-md-3,.col-md-4,.col-md-5,.col-md-6,.col-md-7,.col-md-8,.col-md-9,.col-sm-1,.col-sm-10,.col-sm-11,.col-sm-12,.col-sm-2,.col-sm-3,.col-sm-4,.col-sm-5,.col-sm-6,.col-sm-7,.col-sm-8,.col-sm-9,.col-xs-1,.col-xs-10,.col-xs-11,.col-xs-12,.col-xs-2,.col-xs-3,.col-xs-4,.col-xs-5,.col-xs-6,.col-xs-7,.col-xs-8,.col-xs-9{position:relative;min-height:1px;padding-right:15px;padding-left:15px}.col-xs-1,.col-xs-10,.col-xs-11,.col-xs-12,.col-xs-2,.col-xs-3,.col-xs-4,.col-xs-5,.col-xs-6,.col-xs-7,.col-xs-8,.col-xs-9{float:left}.col-xs-12{width:100%}.col-xs-11{width:91.66666667%}.col-xs-10{width:83.33333333%}.col-xs-9{width:75%}.col-xs-8{width:66.66666667%}.col-xs-7{width:58.33333333%}.col-xs-6{width:50%}.col-xs-5{width:41.66666667%}.col-xs-4{width:33.33333333%}.col-xs-3{width:25%}.col-xs-2{width:16.66666667%}.col-xs-1{width:8.33333333%}.col-xs-pull-12{right:100%}.col-xs-pull-11{right:91.66666667%}.col-xs-pull-10{right:83.33333333%}.col-xs-pull-9{right:75%}.col-xs-pull-8{right:66.66666667%}.col-xs-pull-7{right:58.33333333%}.col-xs-pull-6{right:50%}.col-xs-pull-5{right:41.66666667%}.col-xs-pull-4{right:33.33333333%}.col-xs-pull-3{right:25%}.col-xs-pull-2{right:16.66666667%}.col-xs-pull-1{right:8.33333333%}.col-xs-pull-0{right:auto}.col-xs-push-12{left:100%}.col-xs-push-11{left:91.66666667%}.col-xs-push-10{left:83.33333333%}.col-xs-push-9{left:75%}.col-xs-push-8{left:66.66666667%}.col-xs-push-7{left:58.33333333%}.col-xs-push-6{left:50%}.col-xs-push-5{left:41.66666667%}.col-xs-push-4{left:33.33333333%}.col-xs-push-3{left:25%}.col-xs-push-2{left:16.66666667%}.col-xs-push-1{left:8.33333333%}.col-xs-push-0{left:auto}.col-xs-offset-12{margin-left:100%}.col-xs-offset-11{margin-left:91.66666667%}.col-xs-offset-10{margin-left:83.33333333%}.col-xs-offset-9{margin-left:75%}.col-xs-offset-8{margin-left:66.66666667%}.col-xs-offset-7{margin-left:58.33333333%}.col-xs-offset-6{margin-left:50%}.col-xs-offset-5{margin-left:41.66666667%}.col-xs-offset-4{margin-left:33.33333333%}.col-xs-offset-3{margin-left:25%}.col-xs-offset-2{margin-left:16.66666667%}.col-xs-offset-1{margin-left:8.33333333%}.col-xs-offset-0{margin-left:0}@media (min-width:768px){.col-sm-1,.col-sm-10,.col-sm-11,.col-sm-12,.col-sm-2,.col-sm-3,.col-sm-4,.col-sm-5,.col-sm-6,.col-sm-7,.col-sm-8,.col-sm-9{float:left}.col-sm-12{width:100%}.col-sm-11{width:91.66666667%}.col-sm-10{width:83.33333333%}.col-sm-9{width:75%}.col-sm-8{width:66.66666667%}.col-sm-7{width:58.33333333%}.col-sm-6{width:50%}.col-sm-5{width:41.66666667%}.col-sm-4{width:33.33333333%}.col-sm-3{width:25%}.col-sm-2{width:16.66666667%}.col-sm-1{width:8.33333333%}.col-sm-pull-12{right:100%}.col-sm-pull-11{right:91.66666667%}.col-sm-pull-10{right:83.33333333%}.col-sm-pull-9{right:75%}.col-sm-pull-8{right:66.66666667%}.col-sm-pull-7{right:58.33333333%}.col-sm-pull-6{right:50%}.col-sm-pull-5{right:41.66666667%}.col-sm-pull-4{right:33.33333333%}.col-sm-pull-3{right:25%}.col-sm-pull-2{right:16.66666667%}.col-sm-pull-1{right:8.33333333%}.col-sm-pull-0{right:auto}.col-sm-push-12{left:100%}.col-sm-push-11{left:91.66666667%}.col-sm-push-10{left:83.33333333%}.col-sm-push-9{left:75%}.col-sm-push-8{left:66.66666667%}.col-sm-push-7{left:58.33333333%}.col-sm-push-6{left:50%}.col-sm-push-5{left:41.66666667%}.col-sm-push-4{left:33.33333333%}.col-sm-push-3{left:25%}.col-sm-push-2{left:16.66666667%}.col-sm-push-1{left:8.33333333%}.col-sm-push-0{left:auto}.col-sm-offset-12{margin-left:100%}.col-sm-offset-11{margin-left:91.66666667%}.col-sm-offset-10{margin-left:83.33333333%}.col-sm-offset-9{margin-left:75%}.col-sm-offset-8{margin-left:66.66666667%}.col-sm-offset-7{margin-left:58.33333333%}.col-sm-offset-6{margin-left:50%}.col-sm-offset-5{margin-left:41.66666667%}.col-sm-offset-4{margin-left:33.33333333%}.col-sm-offset-3{margin-left:25%}.col-sm-offset-2{margin-left:16.66666667%}.col-sm-offset-1{margin-left:8.33333333%}.col-sm-offset-0{margin-left:0}}@media (min-width:992px){.col-md-1,.col-md-10,.col-md-11,.col-md-12,.col-md-2,.col-md-3,.col-md-4,.col-md-5,.col-md-6,.col-md-7,.col-md-8,.col-md-9{float:left}.col-md-12{width:100%}.col-md-11{width:91.66666667%}.col-md-10{width:83.33333333%}.col-md-9{width:75%}.col-md-8{width:66.66666667%}.col-md-7{width:58.33333333%}.col-md-6{width:50%}.col-md-5{width:41.66666667%}.col-md-4{width:33.33333333%}.col-md-3{width:25%}.col-md-2{width:16.66666667%}.col-md-1{width:8.33333333%}.col-md-pull-12{right:100%}.col-md-pull-11{right:91.66666667%}.col-md-pull-10{right:83.33333333%}.col-md-pull-9{right:75%}.col-md-pull-8{right:66.66666667%}.col-md-pull-7{right:58.33333333%}.col-md-pull-6{right:50%}.col-md-pull-5{right:41.66666667%}.col-md-pull-4{right:33.33333333%}.col-md-pull-3{right:25%}.col-md-pull-2{right:16.66666667%}.col-md-pull-1{right:8.33333333%}.col-md-pull-0{right:auto}.col-md-push-12{left:100%}.col-md-push-11{left:91.66666667%}.col-md-push-10{left:83.33333333%}.col-md-push-9{left:75%}.col-md-push-8{left:66.66666667%}.col-md-push-7{left:58.33333333%}.col-md-push-6{left:50%}.col-md-push-5{left:41.66666667%}.col-md-push-4{left:33.33333333%}.col-md-push-3{left:25%}.col-md-push-2{left:16.66666667%}.col-md-push-1{left:8.33333333%}.col-md-push-0{left:auto}.col-md-offset-12{margin-left:100%}.col-md-offset-11{margin-left:91.66666667%}.col-md-offset-10{margin-left:83.33333333%}.col-md-offset-9{margin-left:75%}.col-md-offset-8{margin-left:66.66666667%}.col-md-offset-7{margin-left:58.33333333%}.col-md-offset-6{margin-left:50%}.col-md-offset-5{margin-left:41.66666667%}.col-md-offset-4{margin-left:33.33333333%}.col-md-offset-3{margin-left:25%}.col-md-offset-2{margin-left:16.66666667%}.col-md-offset-1{margin-left:8.33333333%}.col-md-offset-0{margin-left:0}}@media (min-width:1200px){.col-lg-1,.col-lg-10,.col-lg-11,.col-lg-12,.col-lg-2,.col-lg-3,.col-lg-4,.col-lg-5,.col-lg-6,.col-lg-7,.col-lg-8,.col-lg-9{float:left}.col-lg-12{width:100%}.col-lg-11{width:91.66666667%}.col-lg-10{width:83.33333333%}.col-lg-9{width:75%}.col-lg-8{width:66.66666667%}.col-lg-7{width:58.33333333%}.col-lg-6{width:50%}.col-lg-5{width:41.66666667%}.col-lg-4{width:33.33333333%}.col-lg-3{width:25%}.col-lg-2{width:16.66666667%}.col-lg-1{width:8.33333333%}.col-lg-pull-12{right:100%}.col-lg-pull-11{right:91.66666667%}.col-lg-pull-10{right:83.33333333%}.col-lg-pull-9{right:75%}.col-lg-pull-8{right:66.66666667%}.col-lg-pull-7{right:58.33333333%}.col-lg-pull-6{right:50%}.col-lg-pull-5{right:41.66666667%}.col-lg-pull-4{right:33.33333333%}.col-lg-pull-3{right:25%}.col-lg-pull-2{right:16.66666667%}.col-lg-pull-1{right:8.33333333%}.col-lg-pull-0{right:auto}.col-lg-push-12{left:100%}.col-lg-push-11{left:91.66666667%}.col-lg-push-10{left:83.33333333%}.col-lg-push-9{left:75%}.col-lg-push-8{left:66.66666667%}.col-lg-push-7{left:58.33333333%}.col-lg-push-6{left:50%}.col-lg-push-5{left:41.66666667%}.col-lg-push-4{left:33.33333333%}.col-lg-push-3{left:25%}.col-lg-push-2{left:16.66666667%}.col-lg-push-1{left:8.33333333%}.col-lg-push-0{left:auto}.col-lg-offset-12{margin-left:100%}.col-lg-offset-11{margin-left:91.66666667%}.col-lg-offset-10{margin-left:83.33333333%}.col-lg-offset-9{margin-left:75%}.col-lg-offset-8{margin-left:66.66666667%}.col-lg-offset-7{margin-left:58.33333333%}.col-lg-offset-6{margin-left:50%}.col-lg-offset-5{margin-left:41.66666667%}.col-lg-offset-4{margin-left:33.33333333%}.col-lg-offset-3{margin-left:25%}.col-lg-offset-2{margin-left:16.66666667%}.col-lg-offset-1{margin-left:8.33333333%}.col-lg-offset-0{margin-left:0}}table{background-color:transparent}caption{padding-top:8px;padding-bottom:8px;color:#777;text-align:left}th{text-align:left}.table{width:100%;max-width:100%;margin-bottom:20px}.table>tbody>tr>td,.table>tbody>tr>th,.table>tfoot>tr>td,.table>tfoot>tr>th,.table>thead>tr>td,.table>thead>tr>th{padding:8px;line-height:1.42857143;vertical-align:top;border-top:1px solid #ddd}.table>thead>tr>th{vertical-align:bottom;border-bottom:2px solid #ddd}.table>caption+thead>tr:first-child>td,.table>caption+thead>tr:first-child>th,.table>colgroup+thead>tr:first-child>td,.table>colgroup+thead>tr:first-child>th,.table>thead:first-child>tr:first-child>td,.table>thead:first-child>tr:first-child>th{border-top:0}.table>tbody+tbody{border-top:2px solid #ddd}.table .table{background-color:#fff}.table-condensed>tbody>tr>td,.table-condensed>tbody>tr>th,.table-condensed>tfoot>tr>td,.table-condensed>tfoot>tr>th,.table-condensed>thead>tr>td,.table-condensed>thead>tr>th{padding:5px}.table-bordered{border:1px solid #ddd}.table-bordered>tbody>tr>td,.table-bordered>tbody>tr>th,.table-bordered>tfoot>tr>td,.table-bordered>tfoot>tr>th,.table-bordered>thead>tr>td,.table-bordered>thead>tr>th{border:1px solid #ddd}.table-bordered>thead>tr>td,.table-bordered>thead>tr>th{border-bottom-width:2px}.table-striped>tbody>tr:nth-of-type(odd){background-color:#f9f9f9}.table-hover>tbody>tr:hover{background-color:#f5f5f5}table col[class*=col-]{position:static;display:table-column;float:none}table td[class*=col-],table th[class*=col-]{position:static;display:table-cell;float:none}.table>tbody>tr.active>td,.table>tbody>tr.active>th,.table>tbody>tr>td.active,.table>tbody>tr>th.active,.table>tfoot>tr.active>td,.table>tfoot>tr.active>th,.table>tfoot>tr>td.active,.table>tfoot>tr>th.active,.table>thead>tr.active>td,.table>thead>tr.active>th,.table>thead>tr>td.active,.table>thead>tr>th.active{background-color:#f5f5f5}.table-hover>tbody>tr.active:hover>td,.table-hover>tbody>tr.active:hover>th,.table-hover>tbody>tr:hover>.active,.table-hover>tbody>tr>td.active:hover,.table-hover>tbody>tr>th.active:hover{background-color:#e8e8e8}.table>tbody>tr.success>td,.table>tbody>tr.success>th,.table>tbody>tr>td.success,.table>tbody>tr>th.success,.table>tfoot>tr.success>td,.table>tfoot>tr.success>th,.table>tfoot>tr>td.success,.table>tfoot>tr>th.success,.table>thead>tr.success>td,.table>thead>tr.success>th,.table>thead>tr>td.success,.table>thead>tr>th.success{background-color:#dff0d8}.table-hover>tbody>tr.success:hover>td,.table-hover>tbody>tr.success:hover>th,.table-hover>tbody>tr:hover>.success,.table-hover>tbody>tr>td.success:hover,.table-hover>tbody>tr>th.success:hover{background-color:#d0e9c6}.table>tbody>tr.info>td,.table>tbody>tr.info>th,.table>tbody>tr>td.info,.table>tbody>tr>th.info,.table>tfoot>tr.info>td,.table>tfoot>tr.info>th,.table>tfoot>tr>td.info,.table>tfoot>tr>th.info,.table>thead>tr.info>td,.table>thead>tr.info>th,.table>thead>tr>td.info,.table>thead>tr>th.info{background-color:#d9edf7}.table-hover>tbody>tr.info:hover>td,.table-hover>tbody>tr.info:hover>th,.table-hover>tbody>tr:hover>.info,.table-hover>tbody>tr>td.info:hover,.table-hover>tbody>tr>th.info:hover{background-color:#c4e3f3}.table>tbody>tr.warning>td,.table>tbody>tr.warning>th,.table>tbody>tr>td.warning,.table>tbody>tr>th.warning,.table>tfoot>tr.warning>td,.table>tfoot>tr.warning>th,.table>tfoot>tr>td.warning,.table>tfoot>tr>th.warning,.table>thead>tr.warning>td,.table>thead>tr.warning>th,.table>thead>tr>td.warning,.table>thead>tr>th.warning{background-color:#fcf8e3}.table-hover>tbody>tr.warning:hover>td,.table-hover>tbody>tr.warning:hover>th,.table-hover>tbody>tr:hover>.warning,.table-hover>tbody>tr>td.warning:hover,.table-hover>tbody>tr>th.warning:hover{background-color:#faf2cc}.table>tbody>tr.danger>td,.table>tbody>tr.danger>th,.table>tbody>tr>td.danger,.table>tbody>tr>th.danger,.table>tfoot>tr.danger>td,.table>tfoot>tr.danger>th,.table>tfoot>tr>td.danger,.table>tfoot>tr>th.danger,.table>thead>tr.danger>td,.table>thead>tr.danger>th,.table>thead>tr>td.danger,.table>thead>tr>th.danger{background-color:#f2dede}.table-hover>tbody>tr.danger:hover>td,.table-hover>tbody>tr.danger:hover>th,.table-hover>tbody>tr:hover>.danger,.table-hover>tbody>tr>td.danger:hover,.table-hover>tbody>tr>th.danger:hover{background-color:#ebcccc}.table-responsive{min-height:.01%;overflow-x:auto}@media screen and (max-width:767px){.table-responsive{width:100%;margin-bottom:15px;overflow-y:hidden;-ms-overflow-style:-ms-autohiding-scrollbar;border:1px solid #ddd}.table-responsive>.table{margin-bottom:0}.table-responsive>.table>tbody>tr>td,.table-responsive>.table>tbody>tr>th,.table-responsive>.table>tfoot>tr>td,.table-responsive>.table>tfoot>tr>th,.table-responsive>.table>thead>tr>td,.table-responsive>.table>thead>tr>th{white-space:nowrap}.table-responsive>.table-bordered{border:0}.table-responsive>.table-bordered>tbody>tr>td:first-child,.table-responsive>.table-bordered>tbody>tr>th:first-child,.table-responsive>.table-bordered>tfoot>tr>td:first-child,.table-responsive>.table-bordered>tfoot>tr>th:first-child,.table-responsive>.table-bordered>thead>tr>td:first-child,.table-responsive>.table-bordered>thead>tr>th:first-child{border-left:0}.table-responsive>.table-bordered>tbody>tr>td:last-child,.table-responsive>.table-bordered>tbody>tr>th:last-child,.table-responsive>.table-bordered>tfoot>tr>td:last-child,.table-responsive>.table-bordered>tfoot>tr>th:last-child,.table-responsive>.table-bordered>thead>tr>td:last-child,.table-responsive>.table-bordered>thead>tr>th:last-child{border-right:0}.table-responsive>.table-bordered>tbody>tr:last-child>td,.table-responsive>.table-bordered>tbody>tr:last-child>th,.table-responsive>.table-bordered>tfoot>tr:last-child>td,.table-responsive>.table-bordered>tfoot>tr:last-child>th{border-bottom:0}}fieldset{min-width:0;padding:0;margin:0;border:0}legend{display:block;width:100%;padding:0;margin-bottom:20px;font-size:21px;line-height:inherit;color:#333;border:0;border-bottom:1px solid #e5e5e5}label{display:inline-block;max-width:100%;margin-bottom:5px;font-weight:700}input[type=search]{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}input[type=checkbox],input[type=radio]{margin:4px 0 0;margin-top:1px\9;line-height:normal}input[type=file]{display:block}input[type=range]{display:block;width:100%}select[multiple],select[size]{height:auto}input[type=file]:focus,input[type=checkbox]:focus,input[type=radio]:focus{outline:thin dotted;outline:5px auto -webkit-focus-ring-color;outline-offset:-2px}output{display:block;padding-top:7px;font-size:14px;line-height:1.42857143;color:#555}.form-control{display:block;width:100%;height:34px;padding:6px 12px;font-size:14px;line-height:1.42857143;color:#555;background-color:#fff;background-image:none;border:1px solid #ccc;border-radius:4px;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075);box-shadow:inset 0 1px 1px rgba(0,0,0,.075);-webkit-transition:border-color ease-in-out .15s,-webkit-box-shadow ease-in-out .15s;-o-transition:border-color ease-in-out .15s,box-shadow ease-in-out .15s;transition:border-color ease-in-out .15s,box-shadow ease-in-out .15s}.form-control:focus{border-color:#66afe9;outline:0;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 8px rgba(102,175,233,.6);box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 8px rgba(102,175,233,.6)}.form-control::-moz-placeholder{color:#999;opacity:1}.form-control:-ms-input-placeholder{color:#999}.form-control::-webkit-input-placeholder{color:#999}.form-control::-ms-expand{background-color:transparent;border:0}.form-control[disabled],.form-control[readonly],fieldset[disabled] .form-control{background-color:#eee;opacity:1}.form-control[disabled],fieldset[disabled] .form-control{cursor:not-allowed}textarea.form-control{height:auto}input[type=search]{-webkit-appearance:none}@media screen and (-webkit-min-device-pixel-ratio:0){input[type=date].form-control,input[type=time].form-control,input[type=datetime-local].form-control,input[type=month].form-control{line-height:34px}.input-group-sm input[type=date],.input-group-sm input[type=time],.input-group-sm input[type=datetime-local],.input-group-sm input[type=month],input[type=date].input-sm,input[type=time].input-sm,input[type=datetime-local].input-sm,input[type=month].input-sm{line-height:30px}.input-group-lg input[type=date],.input-group-lg input[type=time],.input-group-lg input[type=datetime-local],.input-group-lg input[type=month],input[type=date].input-lg,input[type=time].input-lg,input[type=datetime-local].input-lg,input[type=month].input-lg{line-height:46px}}.form-group{margin-bottom:15px}.checkbox,.radio{position:relative;display:block;margin-top:10px;margin-bottom:10px}.checkbox label,.radio label{min-height:20px;padding-left:20px;margin-bottom:0;font-weight:400;cursor:pointer}.checkbox input[type=checkbox],.checkbox-inline input[type=checkbox],.radio input[type=radio],.radio-inline input[type=radio]{position:absolute;margin-top:4px\9;margin-left:-20px}.checkbox+.checkbox,.radio+.radio{margin-top:-5px}.checkbox-inline,.radio-inline{position:relative;display:inline-block;padding-left:20px;margin-bottom:0;font-weight:400;vertical-align:middle;cursor:pointer}.checkbox-inline+.checkbox-inline,.radio-inline+.radio-inline{margin-top:0;margin-left:10px}fieldset[disabled] input[type=checkbox],fieldset[disabled] input[type=radio],input[type=checkbox].disabled,input[type=checkbox][disabled],input[type=radio].disabled,input[type=radio][disabled]{cursor:not-allowed}.checkbox-inline.disabled,.radio-inline.disabled,fieldset[disabled] .checkbox-inline,fieldset[disabled] .radio-inline{cursor:not-allowed}.checkbox.disabled label,.radio.disabled label,fieldset[disabled] .checkbox label,fieldset[disabled] .radio label{cursor:not-allowed}.form-control-static{min-height:34px;padding-top:7px;padding-bottom:7px;margin-bottom:0}.form-control-static.input-lg,.form-control-static.input-sm{padding-right:0;padding-left:0}.input-sm{height:30px;padding:5px 10px;font-size:12px;line-height:1.5;border-radius:3px}select.input-sm{height:30px;line-height:30px}select[multiple].input-sm,textarea.input-sm{height:auto}.form-group-sm .form-control{height:30px;padding:5px 10px;font-size:12px;line-height:1.5;border-radius:3px}.form-group-sm select.form-control{height:30px;line-height:30px}.form-group-sm select[multiple].form-control,.form-group-sm textarea.form-control{height:auto}.form-group-sm .form-control-static{height:30px;min-height:32px;padding:6px 10px;font-size:12px;line-height:1.5}.input-lg{height:46px;padding:10px 16px;font-size:18px;line-height:1.3333333;border-radius:6px}select.input-lg{height:46px;line-height:46px}select[multiple].input-lg,textarea.input-lg{height:auto}.form-group-lg .form-control{height:46px;padding:10px 16px;font-size:18px;line-height:1.3333333;border-radius:6px}.form-group-lg select.form-control{height:46px;line-height:46px}.form-group-lg select[multiple].form-control,.form-group-lg textarea.form-control{height:auto}.form-group-lg .form-control-static{height:46px;min-height:38px;padding:11px 16px;font-size:18px;line-height:1.3333333}.has-feedback{position:relative}.has-feedback .form-control{padding-right:42.5px}.form-control-feedback{position:absolute;top:0;right:0;z-index:2;display:block;width:34px;height:34px;line-height:34px;text-align:center;pointer-events:none}.form-group-lg .form-control+.form-control-feedback,.input-group-lg+.form-control-feedback,.input-lg+.form-control-feedback{width:46px;height:46px;line-height:46px}.form-group-sm .form-control+.form-control-feedback,.input-group-sm+.form-control-feedback,.input-sm+.form-control-feedback{width:30px;height:30px;line-height:30px}.has-success .checkbox,.has-success .checkbox-inline,.has-success .control-label,.has-success .help-block,.has-success .radio,.has-success .radio-inline,.has-success.checkbox label,.has-success.checkbox-inline label,.has-success.radio label,.has-success.radio-inline label{color:#3c763d}.has-success .form-control{border-color:#3c763d;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075);box-shadow:inset 0 1px 1px rgba(0,0,0,.075)}.has-success .form-control:focus{border-color:#2b542c;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 6px #67b168;box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 6px #67b168}.has-success .input-group-addon{color:#3c763d;background-color:#dff0d8;border-color:#3c763d}.has-success .form-control-feedback{color:#3c763d}.has-warning .checkbox,.has-warning .checkbox-inline,.has-warning .control-label,.has-warning .help-block,.has-warning .radio,.has-warning .radio-inline,.has-warning.checkbox label,.has-warning.checkbox-inline label,.has-warning.radio label,.has-warning.radio-inline label{color:#8a6d3b}.has-warning .form-control{border-color:#8a6d3b;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075);box-shadow:inset 0 1px 1px rgba(0,0,0,.075)}.has-warning .form-control:focus{border-color:#66512c;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 6px #c0a16b;box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 6px #c0a16b}.has-warning .input-group-addon{color:#8a6d3b;background-color:#fcf8e3;border-color:#8a6d3b}.has-warning .form-control-feedback{color:#8a6d3b}.has-error .checkbox,.has-error .checkbox-inline,.has-error .control-label,.has-error .help-block,.has-error .radio,.has-error .radio-inline,.has-error.checkbox label,.has-error.checkbox-inline label,.has-error.radio label,.has-error.radio-inline label{color:#a94442}.has-error .form-control{border-color:#a94442;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075);box-shadow:inset 0 1px 1px rgba(0,0,0,.075)}.has-error .form-control:focus{border-color:#843534;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 6px #ce8483;box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 6px #ce8483}.has-error .input-group-addon{color:#a94442;background-color:#f2dede;border-color:#a94442}.has-error .form-control-feedback{color:#a94442}.has-feedback label~.form-control-feedback{top:25px}.has-feedback label.sr-only~.form-control-feedback{top:0}.help-block{display:block;margin-top:5px;margin-bottom:10px;color:#737373}@media (min-width:768px){.form-inline .form-group{display:inline-block;margin-bottom:0;vertical-align:middle}.form-inline .form-control{display:inline-block;width:auto;vertical-align:middle}.form-inline .form-control-static{display:inline-block}.form-inline .input-group{display:inline-table;vertical-align:middle}.form-inline .input-group .form-control,.form-inline .input-group .input-group-addon,.form-inline .input-group .input-group-btn{width:auto}.form-inline .input-group>.form-control{width:100%}.form-inline .control-label{margin-bottom:0;vertical-align:middle}.form-inline .checkbox,.form-inline .radio{display:inline-block;margin-top:0;margin-bottom:0;vertical-align:middle}.form-inline .checkbox label,.form-inline .radio label{padding-left:0}.form-inline .checkbox input[type=checkbox],.form-inline .radio input[type=radio]{position:relative;margin-left:0}.form-inline .has-feedback .form-control-feedback{top:0}}.form-horizontal .checkbox,.form-horizontal .checkbox-inline,.form-horizontal .radio,.form-horizontal .radio-inline{padding-top:7px;margin-top:0;margin-bottom:0}.form-horizontal .checkbox,.form-horizontal .radio{min-height:27px}.form-horizontal .form-group{margin-right:-15px;margin-left:-15px}@media (min-width:768px){.form-horizontal .control-label{padding-top:7px;margin-bottom:0;text-align:right}}.form-horizontal .has-feedback .form-control-feedback{right:15px}@media (min-width:768px){.form-horizontal .form-group-lg .control-label{padding-top:11px;font-size:18px}}@media (min-width:768px){.form-horizontal .form-group-sm .control-label{padding-top:6px;font-size:12px}}.btn{display:inline-block;padding:6px 12px;margin-bottom:0;font-size:14px;font-weight:400;line-height:1.42857143;text-align:center;white-space:nowrap;vertical-align:middle;-ms-touch-action:manipulation;touch-action:manipulation;cursor:pointer;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;background-image:none;border:1px solid transparent;border-radius:4px}.btn.active.focus,.btn.active:focus,.btn.focus,.btn:active.focus,.btn:active:focus,.btn:focus{outline:thin dotted;outline:5px auto -webkit-focus-ring-color;outline-offset:-2px}.btn.focus,.btn:focus,.btn:hover{color:#333;text-decoration:none}.btn.active,.btn:active{background-image:none;outline:0;-webkit-box-shadow:inset 0 3px 5px rgba(0,0,0,.125);box-shadow:inset 0 3px 5px rgba(0,0,0,.125)}.btn.disabled,.btn[disabled],fieldset[disabled] .btn{cursor:not-allowed;filter:alpha(opacity=65);-webkit-box-shadow:none;box-shadow:none;opacity:.65}a.btn.disabled,fieldset[disabled] a.btn{pointer-events:none}.btn-default{color:#333;background-color:#fff;border-color:#ccc}.btn-default.focus,.btn-default:focus{color:#333;background-color:#e6e6e6;border-color:#8c8c8c}.btn-default:hover{color:#333;background-color:#e6e6e6;border-color:#adadad}.btn-default.active,.btn-default:active,.open>.dropdown-toggle.btn-default{color:#333;background-color:#e6e6e6;border-color:#adadad}.btn-default.active.focus,.btn-default.active:focus,.btn-default.active:hover,.btn-default:active.focus,.btn-default:active:focus,.btn-default:active:hover,.open>.dropdown-toggle.btn-default.focus,.open>.dropdown-toggle.btn-default:focus,.open>.dropdown-toggle.btn-default:hover{color:#333;background-color:#d4d4d4;border-color:#8c8c8c}.btn-default.active,.btn-default:active,.open>.dropdown-toggle.btn-default{background-image:none}.btn-default.disabled.focus,.btn-default.disabled:focus,.btn-default.disabled:hover,.btn-default[disabled].focus,.btn-default[disabled]:focus,.btn-default[disabled]:hover,fieldset[disabled] .btn-default.focus,fieldset[disabled] .btn-default:focus,fieldset[disabled] .btn-default:hover{background-color:#fff;border-color:#ccc}.btn-default .badge{color:#fff;background-color:#333}.btn-primary{color:#fff;background-color:#337ab7;border-color:#2e6da4}.btn-primary.focus,.btn-primary:focus{color:#fff;background-color:#286090;border-color:#122b40}.btn-primary:hover{color:#fff;background-color:#286090;border-color:#204d74}.btn-primary.active,.btn-primary:active,.open>.dropdown-toggle.btn-primary{color:#fff;background-color:#286090;border-color:#204d74}.btn-primary.active.focus,.btn-primary.active:focus,.btn-primary.active:hover,.btn-primary:active.focus,.btn-primary:active:focus,.btn-primary:active:hover,.open>.dropdown-toggle.btn-primary.focus,.open>.dropdown-toggle.btn-primary:focus,.open>.dropdown-toggle.btn-primary:hover{color:#fff;background-color:#204d74;border-color:#122b40}.btn-primary.active,.btn-primary:active,.open>.dropdown-toggle.btn-primary{background-image:none}.btn-primary.disabled.focus,.btn-primary.disabled:focus,.btn-primary.disabled:hover,.btn-primary[disabled].focus,.btn-primary[disabled]:focus,.btn-primary[disabled]:hover,fieldset[disabled] .btn-primary.focus,fieldset[disabled] .btn-primary:focus,fieldset[disabled] .btn-primary:hover{background-color:#337ab7;border-color:#2e6da4}.btn-primary .badge{color:#337ab7;background-color:#fff}.btn-success{color:#fff;background-color:#5cb85c;border-color:#4cae4c}.btn-success.focus,.btn-success:focus{color:#fff;background-color:#449d44;border-color:#255625}.btn-success:hover{color:#fff;background-color:#449d44;border-color:#398439}.btn-success.active,.btn-success:active,.open>.dropdown-toggle.btn-success{color:#fff;background-color:#449d44;border-color:#398439}.btn-success.active.focus,.btn-success.active:focus,.btn-success.active:hover,.btn-success:active.focus,.btn-success:active:focus,.btn-success:active:hover,.open>.dropdown-toggle.btn-success.focus,.open>.dropdown-toggle.btn-success:focus,.open>.dropdown-toggle.btn-success:hover{color:#fff;background-color:#398439;border-color:#255625}.btn-success.active,.btn-success:active,.open>.dropdown-toggle.btn-success{background-image:none}.btn-success.disabled.focus,.btn-success.disabled:focus,.btn-success.disabled:hover,.btn-success[disabled].focus,.btn-success[disabled]:focus,.btn-success[disabled]:hover,fieldset[disabled] .btn-success.focus,fieldset[disabled] .btn-success:focus,fieldset[disabled] .btn-success:hover{background-color:#5cb85c;border-color:#4cae4c}.btn-success .badge{color:#5cb85c;background-color:#fff}.btn-info{color:#fff;background-color:#5bc0de;border-color:#46b8da}.btn-info.focus,.btn-info:focus{color:#fff;background-color:#31b0d5;border-color:#1b6d85}.btn-info:hover{color:#fff;background-color:#31b0d5;border-color:#269abc}.btn-info.active,.btn-info:active,.open>.dropdown-toggle.btn-info{color:#fff;background-color:#31b0d5;border-color:#269abc}.btn-info.active.focus,.btn-info.active:focus,.btn-info.active:hover,.btn-info:active.focus,.btn-info:active:focus,.btn-info:active:hover,.open>.dropdown-toggle.btn-info.focus,.open>.dropdown-toggle.btn-info:focus,.open>.dropdown-toggle.btn-info:hover{color:#fff;background-color:#269abc;border-color:#1b6d85}.btn-info.active,.btn-info:active,.open>.dropdown-toggle.btn-info{background-image:none}.btn-info.disabled.focus,.btn-info.disabled:focus,.btn-info.disabled:hover,.btn-info[disabled].focus,.btn-info[disabled]:focus,.btn-info[disabled]:hover,fieldset[disabled] .btn-info.focus,fieldset[disabled] .btn-info:focus,fieldset[disabled] .btn-info:hover{background-color:#5bc0de;border-color:#46b8da}.btn-info .badge{color:#5bc0de;background-color:#fff}.btn-warning{color:#fff;background-color:#f0ad4e;border-color:#eea236}.btn-warning.focus,.btn-warning:focus{color:#fff;background-color:#ec971f;border-color:#985f0d}.btn-warning:hover{color:#fff;background-color:#ec971f;border-color:#d58512}.btn-warning.active,.btn-warning:active,.open>.dropdown-toggle.btn-warning{color:#fff;background-color:#ec971f;border-color:#d58512}.btn-warning.active.focus,.btn-warning.active:focus,.btn-warning.active:hover,.btn-warning:active.focus,.btn-warning:active:focus,.btn-warning:active:hover,.open>.dropdown-toggle.btn-warning.focus,.open>.dropdown-toggle.btn-warning:focus,.open>.dropdown-toggle.btn-warning:hover{color:#fff;background-color:#d58512;border-color:#985f0d}.btn-warning.active,.btn-warning:active,.open>.dropdown-toggle.btn-warning{background-image:none}.btn-warning.disabled.focus,.btn-warning.disabled:focus,.btn-warning.disabled:hover,.btn-warning[disabled].focus,.btn-warning[disabled]:focus,.btn-warning[disabled]:hover,fieldset[disabled] .btn-warning.focus,fieldset[disabled] .btn-warning:focus,fieldset[disabled] .btn-warning:hover{background-color:#f0ad4e;border-color:#eea236}.btn-warning .badge{color:#f0ad4e;background-color:#fff}.btn-danger{color:#fff;background-color:#d9534f;border-color:#d43f3a}.btn-danger.focus,.btn-danger:focus{color:#fff;background-color:#c9302c;border-color:#761c19}.btn-danger:hover{color:#fff;background-color:#c9302c;border-color:#ac2925}.btn-danger.active,.btn-danger:active,.open>.dropdown-toggle.btn-danger{color:#fff;background-color:#c9302c;border-color:#ac2925}.btn-danger.active.focus,.btn-danger.active:focus,.btn-danger.active:hover,.btn-danger:active.focus,.btn-danger:active:focus,.btn-danger:active:hover,.open>.dropdown-toggle.btn-danger.focus,.open>.dropdown-toggle.btn-danger:focus,.open>.dropdown-toggle.btn-danger:hover{color:#fff;background-color:#ac2925;border-color:#761c19}.btn-danger.active,.btn-danger:active,.open>.dropdown-toggle.btn-danger{background-image:none}.btn-danger.disabled.focus,.btn-danger.disabled:focus,.btn-danger.disabled:hover,.btn-danger[disabled].focus,.btn-danger[disabled]:focus,.btn-danger[disabled]:hover,fieldset[disabled] .btn-danger.focus,fieldset[disabled] .btn-danger:focus,fieldset[disabled] .btn-danger:hover{background-color:#d9534f;border-color:#d43f3a}.btn-danger .badge{color:#d9534f;background-color:#fff}.btn-link{font-weight:400;color:#337ab7;border-radius:0}.btn-link,.btn-link.active,.btn-link:active,.btn-link[disabled],fieldset[disabled] .btn-link{background-color:transparent;-webkit-box-shadow:none;box-shadow:none}.btn-link,.btn-link:active,.btn-link:focus,.btn-link:hover{border-color:transparent}.btn-link:focus,.btn-link:hover{color:#23527c;text-decoration:underline;background-color:transparent}.btn-link[disabled]:focus,.btn-link[disabled]:hover,fieldset[disabled] .btn-link:focus,fieldset[disabled] .btn-link:hover{color:#777;text-decoration:none}.btn-group-lg>.btn,.btn-lg{padding:10px 16px;font-size:18px;line-height:1.3333333;border-radius:6px}.btn-group-sm>.btn,.btn-sm{padding:5px 10px;font-size:12px;line-height:1.5;border-radius:3px}.btn-group-xs>.btn,.btn-xs{padding:1px 5px;font-size:12px;line-height:1.5;border-radius:3px}.btn-block{display:block;width:100%}.btn-block+.btn-block{margin-top:5px}input[type=button].btn-block,input[type=reset].btn-block,input[type=submit].btn-block{width:100%}.fade{opacity:0;-webkit-transition:opacity .15s linear;-o-transition:opacity .15s linear;transition:opacity .15s linear}.fade.in{opacity:1}.collapse{display:none}.collapse.in{display:block}tr.collapse.in{display:table-row}tbody.collapse.in{display:table-row-group}.collapsing{position:relative;height:0;overflow:hidden;-webkit-transition-timing-function:ease;-o-transition-timing-function:ease;transition-timing-function:ease;-webkit-transition-duration:.35s;-o-transition-duration:.35s;transition-duration:.35s;-webkit-transition-property:height,visibility;-o-transition-property:height,visibility;transition-property:height,visibility}.caret{display:inline-block;width:0;height:0;margin-left:2px;vertical-align:middle;border-top:4px dashed;border-top:4px solid\9;border-right:4px solid transparent;border-left:4px solid transparent}.dropdown,.dropup{position:relative}.dropdown-toggle:focus{outline:0}.dropdown-menu{position:absolute;top:100%;left:0;z-index:1000;display:none;float:left;min-width:160px;padding:5px 0;margin:2px 0 0;font-size:14px;text-align:left;list-style:none;background-color:#fff;-webkit-background-clip:padding-box;background-clip:padding-box;border:1px solid #ccc;border:1px solid rgba(0,0,0,.15);border-radius:4px;-webkit-box-shadow:0 6px 12px rgba(0,0,0,.175);box-shadow:0 6px 12px rgba(0,0,0,.175)}.dropdown-menu.pull-right{right:0;left:auto}.dropdown-menu .divider{height:1px;margin:9px 0;overflow:hidden;background-color:#e5e5e5}.dropdown-menu>li>a{display:block;padding:3px 20px;clear:both;font-weight:400;line-height:1.42857143;color:#333;white-space:nowrap}.dropdown-menu>li>a:focus,.dropdown-menu>li>a:hover{color:#262626;text-decoration:none;background-color:#f5f5f5}.dropdown-menu>.active>a,.dropdown-menu>.active>a:focus,.dropdown-menu>.active>a:hover{color:#fff;text-decoration:none;background-color:#337ab7;outline:0}.dropdown-menu>.disabled>a,.dropdown-menu>.disabled>a:focus,.dropdown-menu>.disabled>a:hover{color:#777}.dropdown-menu>.disabled>a:focus,.dropdown-menu>.disabled>a:hover{text-decoration:none;cursor:not-allowed;background-color:transparent;background-image:none;filter:progid:DXImageTransform.Microsoft.gradient(enabled=false)}.open>.dropdown-menu{display:block}.open>a{outline:0}.dropdown-menu-right{right:0;left:auto}.dropdown-menu-left{right:auto;left:0}.dropdown-header{display:block;padding:3px 20px;font-size:12px;line-height:1.42857143;color:#777;white-space:nowrap}.dropdown-backdrop{position:fixed;top:0;right:0;bottom:0;left:0;z-index:990}.pull-right>.dropdown-menu{right:0;left:auto}.dropup .caret,.navbar-fixed-bottom .dropdown .caret{content:"";border-top:0;border-bottom:4px dashed;border-bottom:4px solid\9}.dropup .dropdown-menu,.navbar-fixed-bottom .dropdown .dropdown-menu{top:auto;bottom:100%;margin-bottom:2px}@media (min-width:768px){.navbar-right .dropdown-menu{right:0;left:auto}.navbar-right .dropdown-menu-left{right:auto;left:0}}.btn-group,.btn-group-vertical{position:relative;display:inline-block;vertical-align:middle}.btn-group-vertical>.btn,.btn-group>.btn{position:relative;float:left}.btn-group-vertical>.btn.active,.btn-group-vertical>.btn:active,.btn-group-vertical>.btn:focus,.btn-group-vertical>.btn:hover,.btn-group>.btn.active,.btn-group>.btn:active,.btn-group>.btn:focus,.btn-group>.btn:hover{z-index:2}.btn-group .btn+.btn,.btn-group .btn+.btn-group,.btn-group .btn-group+.btn,.btn-group .btn-group+.btn-group{margin-left:-1px}.btn-toolbar{margin-left:-5px}.btn-toolbar .btn,.btn-toolbar .btn-group,.btn-toolbar .input-group{float:left}.btn-toolbar>.btn,.btn-toolbar>.btn-group,.btn-toolbar>.input-group{margin-left:5px}.btn-group>.btn:not(:first-child):not(:last-child):not(.dropdown-toggle){border-radius:0}.btn-group>.btn:first-child{margin-left:0}.btn-group>.btn:first-child:not(:last-child):not(.dropdown-toggle){border-top-right-radius:0;border-bottom-right-radius:0}.btn-group>.btn:last-child:not(:first-child),.btn-group>.dropdown-toggle:not(:first-child){border-top-left-radius:0;border-bottom-left-radius:0}.btn-group>.btn-group{float:left}.btn-group>.btn-group:not(:first-child):not(:last-child)>.btn{border-radius:0}.btn-group>.btn-group:first-child:not(:last-child)>.btn:last-child,.btn-group>.btn-group:first-child:not(:last-child)>.dropdown-toggle{border-top-right-radius:0;border-bottom-right-radius:0}.btn-group>.btn-group:last-child:not(:first-child)>.btn:first-child{border-top-left-radius:0;border-bottom-left-radius:0}.btn-group .dropdown-toggle:active,.btn-group.open .dropdown-toggle{outline:0}.btn-group>.btn+.dropdown-toggle{padding-right:8px;padding-left:8px}.btn-group>.btn-lg+.dropdown-toggle{padding-right:12px;padding-left:12px}.btn-group.open .dropdown-toggle{-webkit-box-shadow:inset 0 3px 5px rgba(0,0,0,.125);box-shadow:inset 0 3px 5px rgba(0,0,0,.125)}.btn-group.open .dropdown-toggle.btn-link{-webkit-box-shadow:none;box-shadow:none}.btn .caret{margin-left:0}.btn-lg .caret{border-width:5px 5px 0;border-bottom-width:0}.dropup .btn-lg .caret{border-width:0 5px 5px}.btn-group-vertical>.btn,.btn-group-vertical>.btn-group,.btn-group-vertical>.btn-group>.btn{display:block;float:none;width:100%;max-width:100%}.btn-group-vertical>.btn-group>.btn{float:none}.btn-group-vertical>.btn+.btn,.btn-group-vertical>.btn+.btn-group,.btn-group-vertical>.btn-group+.btn,.btn-group-vertical>.btn-group+.btn-group{margin-top:-1px;margin-left:0}.btn-group-vertical>.btn:not(:first-child):not(:last-child){border-radius:0}.btn-group-vertical>.btn:first-child:not(:last-child){border-top-left-radius:4px;border-top-right-radius:4px;border-bottom-right-radius:0;border-bottom-left-radius:0}.btn-group-vertical>.btn:last-child:not(:first-child){border-top-left-radius:0;border-top-right-radius:0;border-bottom-right-radius:4px;border-bottom-left-radius:4px}.btn-group-vertical>.btn-group:not(:first-child):not(:last-child)>.btn{border-radius:0}.btn-group-vertical>.btn-group:first-child:not(:last-child)>.btn:last-child,.btn-group-vertical>.btn-group:first-child:not(:last-child)>.dropdown-toggle{border-bottom-right-radius:0;border-bottom-left-radius:0}.btn-group-vertical>.btn-group:last-child:not(:first-child)>.btn:first-child{border-top-left-radius:0;border-top-right-radius:0}.btn-group-justified{display:table;width:100%;table-layout:fixed;border-collapse:separate}.btn-group-justified>.btn,.btn-group-justified>.btn-group{display:table-cell;float:none;width:1%}.btn-group-justified>.btn-group .btn{width:100%}.btn-group-justified>.btn-group .dropdown-menu{left:auto}[data-toggle=buttons]>.btn input[type=checkbox],[data-toggle=buttons]>.btn input[type=radio],[data-toggle=buttons]>.btn-group>.btn input[type=checkbox],[data-toggle=buttons]>.btn-group>.btn input[type=radio]{position:absolute;clip:rect(0,0,0,0);pointer-events:none}.input-group{position:relative;display:table;border-collapse:separate}.input-group[class*=col-]{float:none;padding-right:0;padding-left:0}.input-group .form-control{position:relative;z-index:2;float:left;width:100%;margin-bottom:0}.input-group .form-control:focus{z-index:3}.input-group-lg>.form-control,.input-group-lg>.input-group-addon,.input-group-lg>.input-group-btn>.btn{height:46px;padding:10px 16px;font-size:18px;line-height:1.3333333;border-radius:6px}select.input-group-lg>.form-control,select.input-group-lg>.input-group-addon,select.input-group-lg>.input-group-btn>.btn{height:46px;line-height:46px}select[multiple].input-group-lg>.form-control,select[multiple].input-group-lg>.input-group-addon,select[multiple].input-group-lg>.input-group-btn>.btn,textarea.input-group-lg>.form-control,textarea.input-group-lg>.input-group-addon,textarea.input-group-lg>.input-group-btn>.btn{height:auto}.input-group-sm>.form-control,.input-group-sm>.input-group-addon,.input-group-sm>.input-group-btn>.btn{height:30px;padding:5px 10px;font-size:12px;line-height:1.5;border-radius:3px}select.input-group-sm>.form-control,select.input-group-sm>.input-group-addon,select.input-group-sm>.input-group-btn>.btn{height:30px;line-height:30px}select[multiple].input-group-sm>.form-control,select[multiple].input-group-sm>.input-group-addon,select[multiple].input-group-sm>.input-group-btn>.btn,textarea.input-group-sm>.form-control,textarea.input-group-sm>.input-group-addon,textarea.input-group-sm>.input-group-btn>.btn{height:auto}.input-group .form-control,.input-group-addon,.input-group-btn{display:table-cell}.input-group .form-control:not(:first-child):not(:last-child),.input-group-addon:not(:first-child):not(:last-child),.input-group-btn:not(:first-child):not(:last-child){border-radius:0}.input-group-addon,.input-group-btn{width:1%;white-space:nowrap;vertical-align:middle}.input-group-addon{padding:6px 12px;font-size:14px;font-weight:400;line-height:1;color:#555;text-align:center;background-color:#eee;border:1px solid #ccc;border-radius:4px}.input-group-addon.input-sm{padding:5px 10px;font-size:12px;border-radius:3px}.input-group-addon.input-lg{padding:10px 16px;font-size:18px;border-radius:6px}.input-group-addon input[type=checkbox],.input-group-addon input[type=radio]{margin-top:0}.input-group .form-control:first-child,.input-group-addon:first-child,.input-group-btn:first-child>.btn,.input-group-btn:first-child>.btn-group>.btn,.input-group-btn:first-child>.dropdown-toggle,.input-group-btn:last-child>.btn-group:not(:last-child)>.btn,.input-group-btn:last-child>.btn:not(:last-child):not(.dropdown-toggle){border-top-right-radius:0;border-bottom-right-radius:0}.input-group-addon:first-child{border-right:0}.input-group .form-control:last-child,.input-group-addon:last-child,.input-group-btn:first-child>.btn-group:not(:first-child)>.btn,.input-group-btn:first-child>.btn:not(:first-child),.input-group-btn:last-child>.btn,.input-group-btn:last-child>.btn-group>.btn,.input-group-btn:last-child>.dropdown-toggle{border-top-left-radius:0;border-bottom-left-radius:0}.input-group-addon:last-child{border-left:0}.input-group-btn{position:relative;font-size:0;white-space:nowrap}.input-group-btn>.btn{position:relative}.input-group-btn>.btn+.btn{margin-left:-1px}.input-group-btn>.btn:active,.input-group-btn>.btn:focus,.input-group-btn>.btn:hover{z-index:2}.input-group-btn:first-child>.btn,.input-group-btn:first-child>.btn-group{margin-right:-1px}.input-group-btn:last-child>.btn,.input-group-btn:last-child>.btn-group{z-index:2;margin-left:-1px}.nav{padding-left:0;margin-bottom:0;list-style:none}.nav>li{position:relative;display:block}.nav>li>a{position:relative;display:block;padding:10px 15px}.nav>li>a:focus,.nav>li>a:hover{text-decoration:none;background-color:#eee}.nav>li.disabled>a{color:#777}.nav>li.disabled>a:focus,.nav>li.disabled>a:hover{color:#777;text-decoration:none;cursor:not-allowed;background-color:transparent}.nav .open>a,.nav .open>a:focus,.nav .open>a:hover{background-color:#eee;border-color:#337ab7}.nav .nav-divider{height:1px;margin:9px 0;overflow:hidden;background-color:#e5e5e5}.nav>li>a>img{max-width:none}.nav-tabs{border-bottom:1px solid #ddd}.nav-tabs>li{float:left;margin-bottom:-1px}.nav-tabs>li>a{margin-right:2px;line-height:1.42857143;border:1px solid transparent;border-radius:4px 4px 0 0}.nav-tabs>li>a:hover{border-color:#eee #eee #ddd}.nav-tabs>li.active>a,.nav-tabs>li.active>a:focus,.nav-tabs>li.active>a:hover{color:#555;cursor:default;background-color:#fff;border:1px solid #ddd;border-bottom-color:transparent}.nav-tabs.nav-justified{width:100%;border-bottom:0}.nav-tabs.nav-justified>li{float:none}.nav-tabs.nav-justified>li>a{margin-bottom:5px;text-align:center}.nav-tabs.nav-justified>.dropdown .dropdown-menu{top:auto;left:auto}@media (min-width:768px){.nav-tabs.nav-justified>li{display:table-cell;width:1%}.nav-tabs.nav-justified>li>a{margin-bottom:0}}.nav-tabs.nav-justified>li>a{margin-right:0;border-radius:4px}.nav-tabs.nav-justified>.active>a,.nav-tabs.nav-justified>.active>a:focus,.nav-tabs.nav-justified>.active>a:hover{border:1px solid #ddd}@media (min-width:768px){.nav-tabs.nav-justified>li>a{border-bottom:1px solid #ddd;border-radius:4px 4px 0 0}.nav-tabs.nav-justified>.active>a,.nav-tabs.nav-justified>.active>a:focus,.nav-tabs.nav-justified>.active>a:hover{border-bottom-color:#fff}}.nav-pills>li{float:left}.nav-pills>li>a{border-radius:4px}.nav-pills>li+li{margin-left:2px}.nav-pills>li.active>a,.nav-pills>li.active>a:focus,.nav-pills>li.active>a:hover{color:#fff;background-color:#337ab7}.nav-stacked>li{float:none}.nav-stacked>li+li{margin-top:2px;margin-left:0}.nav-justified{width:100%}.nav-justified>li{float:none}.nav-justified>li>a{margin-bottom:5px;text-align:center}.nav-justified>.dropdown .dropdown-menu{top:auto;left:auto}@media (min-width:768px){.nav-justified>li{display:table-cell;width:1%}.nav-justified>li>a{margin-bottom:0}}.nav-tabs-justified{border-bottom:0}.nav-tabs-justified>li>a{margin-right:0;border-radius:4px}.nav-tabs-justified>.active>a,.nav-tabs-justified>.active>a:focus,.nav-tabs-justified>.active>a:hover{border:1px solid #ddd}@media (min-width:768px){.nav-tabs-justified>li>a{border-bottom:1px solid #ddd;border-radius:4px 4px 0 0}.nav-tabs-justified>.active>a,.nav-tabs-justified>.active>a:focus,.nav-tabs-justified>.active>a:hover{border-bottom-color:#fff}}.tab-content>.tab-pane{display:none}.tab-content>.active{display:block}.nav-tabs .dropdown-menu{margin-top:-1px;border-top-left-radius:0;border-top-right-radius:0}.navbar{position:relative;min-height:50px;margin-bottom:20px;border:1px solid transparent}@media (min-width:768px){.navbar{border-radius:4px}}@media (min-width:768px){.navbar-header{float:left}}.navbar-collapse{padding-right:15px;padding-left:15px;overflow-x:visible;-webkit-overflow-scrolling:touch;border-top:1px solid transparent;-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,.1);box-shadow:inset 0 1px 0 rgba(255,255,255,.1)}.navbar-collapse.in{overflow-y:auto}@media (min-width:768px){.navbar-collapse{width:auto;border-top:0;-webkit-box-shadow:none;box-shadow:none}.navbar-collapse.collapse{display:block!important;height:auto!important;padding-bottom:0;overflow:visible!important}.navbar-collapse.in{overflow-y:visible}.navbar-fixed-bottom .navbar-collapse,.navbar-fixed-top .navbar-collapse,.navbar-static-top .navbar-collapse{padding-right:0;padding-left:0}}.navbar-fixed-bottom .navbar-collapse,.navbar-fixed-top .navbar-collapse{max-height:340px}@media (max-device-width:480px) and (orientation:landscape){.navbar-fixed-bottom .navbar-collapse,.navbar-fixed-top .navbar-collapse{max-height:200px}}.container-fluid>.navbar-collapse,.container-fluid>.navbar-header,.container>.navbar-collapse,.container>.navbar-header{margin-right:-15px;margin-left:-15px}@media (min-width:768px){.container-fluid>.navbar-collapse,.container-fluid>.navbar-header,.container>.navbar-collapse,.container>.navbar-header{margin-right:0;margin-left:0}}.navbar-static-top{z-index:1000;border-width:0 0 1px}@media (min-width:768px){.navbar-static-top{border-radius:0}}.navbar-fixed-bottom,.navbar-fixed-top{position:fixed;right:0;left:0;z-index:1030}@media (min-width:768px){.navbar-fixed-bottom,.navbar-fixed-top{border-radius:0}}.navbar-fixed-top{top:0;border-width:0 0 1px}.navbar-fixed-bottom{bottom:0;margin-bottom:0;border-width:1px 0 0}.navbar-brand{float:left;height:50px;padding:15px 15px;font-size:18px;line-height:20px}.navbar-brand:focus,.navbar-brand:hover{text-decoration:none}.navbar-brand>img{display:block}@media (min-width:768px){.navbar>.container .navbar-brand,.navbar>.container-fluid .navbar-brand{margin-left:-15px}}.navbar-toggle{position:relative;float:right;padding:9px 10px;margin-top:8px;margin-right:15px;margin-bottom:8px;background-color:transparent;background-image:none;border:1px solid transparent;border-radius:4px}.navbar-toggle:focus{outline:0}.navbar-toggle .icon-bar{display:block;width:22px;height:2px;border-radius:1px}.navbar-toggle .icon-bar+.icon-bar{margin-top:4px}@media (min-width:768px){.navbar-toggle{display:none}}.navbar-nav{margin:7.5px -15px}.navbar-nav>li>a{padding-top:10px;padding-bottom:10px;line-height:20px}@media (max-width:767px){.navbar-nav .open .dropdown-menu{position:static;float:none;width:auto;margin-top:0;background-color:transparent;border:0;-webkit-box-shadow:none;box-shadow:none}.navbar-nav .open .dropdown-menu .dropdown-header,.navbar-nav .open .dropdown-menu>li>a{padding:5px 15px 5px 25px}.navbar-nav .open .dropdown-menu>li>a{line-height:20px}.navbar-nav .open .dropdown-menu>li>a:focus,.navbar-nav .open .dropdown-menu>li>a:hover{background-image:none}}@media (min-width:768px){.navbar-nav{float:left;margin:0}.navbar-nav>li{float:left}.navbar-nav>li>a{padding-top:15px;padding-bottom:15px}}.navbar-form{padding:10px 15px;margin-top:8px;margin-right:-15px;margin-bottom:8px;margin-left:-15px;border-top:1px solid transparent;border-bottom:1px solid transparent;-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,.1),0 1px 0 rgba(255,255,255,.1);box-shadow:inset 0 1px 0 rgba(255,255,255,.1),0 1px 0 rgba(255,255,255,.1)}@media (min-width:768px){.navbar-form .form-group{display:inline-block;margin-bottom:0;vertical-align:middle}.navbar-form .form-control{display:inline-block;width:auto;vertical-align:middle}.navbar-form .form-control-static{display:inline-block}.navbar-form .input-group{display:inline-table;vertical-align:middle}.navbar-form .input-group .form-control,.navbar-form .input-group .input-group-addon,.navbar-form .input-group .input-group-btn{width:auto}.navbar-form .input-group>.form-control{width:100%}.navbar-form .control-label{margin-bottom:0;vertical-align:middle}.navbar-form .checkbox,.navbar-form .radio{display:inline-block;margin-top:0;margin-bottom:0;vertical-align:middle}.navbar-form .checkbox label,.navbar-form .radio label{padding-left:0}.navbar-form .checkbox input[type=checkbox],.navbar-form .radio input[type=radio]{position:relative;margin-left:0}.navbar-form .has-feedback .form-control-feedback{top:0}}@media (max-width:767px){.navbar-form .form-group{margin-bottom:5px}.navbar-form .form-group:last-child{margin-bottom:0}}@media (min-width:768px){.navbar-form{width:auto;padding-top:0;padding-bottom:0;margin-right:0;margin-left:0;border:0;-webkit-box-shadow:none;box-shadow:none}}.navbar-nav>li>.dropdown-menu{margin-top:0;border-top-left-radius:0;border-top-right-radius:0}.navbar-fixed-bottom .navbar-nav>li>.dropdown-menu{margin-bottom:0;border-top-left-radius:4px;border-top-right-radius:4px;border-bottom-right-radius:0;border-bottom-left-radius:0}.navbar-btn{margin-top:8px;margin-bottom:8px}.navbar-btn.btn-sm{margin-top:10px;margin-bottom:10px}.navbar-btn.btn-xs{margin-top:14px;margin-bottom:14px}.navbar-text{margin-top:15px;margin-bottom:15px}@media (min-width:768px){.navbar-text{float:left;margin-right:15px;margin-left:15px}}@media (min-width:768px){.navbar-left{float:left!important}.navbar-right{float:right!important;margin-right:-15px}.navbar-right~.navbar-right{margin-right:0}}.navbar-default{background-color:#f8f8f8;border-color:#e7e7e7}.navbar-default .navbar-brand{color:#777}.navbar-default .navbar-brand:focus,.navbar-default .navbar-brand:hover{color:#5e5e5e;background-color:transparent}.navbar-default .navbar-text{color:#777}.navbar-default .navbar-nav>li>a{color:#777}.navbar-default .navbar-nav>li>a:focus,.navbar-default .navbar-nav>li>a:hover{color:#333;background-color:transparent}.navbar-default .navbar-nav>.active>a,.navbar-default .navbar-nav>.active>a:focus,.navbar-default .navbar-nav>.active>a:hover{color:#555;background-color:#e7e7e7}.navbar-default .navbar-nav>.disabled>a,.navbar-default .navbar-nav>.disabled>a:focus,.navbar-default .navbar-nav>.disabled>a:hover{color:#ccc;background-color:transparent}.navbar-default .navbar-toggle{border-color:#ddd}.navbar-default .navbar-toggle:focus,.navbar-default .navbar-toggle:hover{background-color:#ddd}.navbar-default .navbar-toggle .icon-bar{background-color:#888}.navbar-default .navbar-collapse,.navbar-default .navbar-form{border-color:#e7e7e7}.navbar-default .navbar-nav>.open>a,.navbar-default .navbar-nav>.open>a:focus,.navbar-default .navbar-nav>.open>a:hover{color:#555;background-color:#e7e7e7}@media (max-width:767px){.navbar-default .navbar-nav .open .dropdown-menu>li>a{color:#777}.navbar-default .navbar-nav .open .dropdown-menu>li>a:focus,.navbar-default .navbar-nav .open .dropdown-menu>li>a:hover{color:#333;background-color:transparent}.navbar-default .navbar-nav .open .dropdown-menu>.active>a,.navbar-default .navbar-nav .open .dropdown-menu>.active>a:focus,.navbar-default .navbar-nav .open .dropdown-menu>.active>a:hover{color:#555;background-color:#e7e7e7}.navbar-default .navbar-nav .open .dropdown-menu>.disabled>a,.navbar-default .navbar-nav .open .dropdown-menu>.disabled>a:focus,.navbar-default .navbar-nav .open .dropdown-menu>.disabled>a:hover{color:#ccc;background-color:transparent}}.navbar-default .navbar-link{color:#777}.navbar-default .navbar-link:hover{color:#333}.navbar-default .btn-link{color:#777}.navbar-default .btn-link:focus,.navbar-default .btn-link:hover{color:#333}.navbar-default .btn-link[disabled]:focus,.navbar-default .btn-link[disabled]:hover,fieldset[disabled] .navbar-default .btn-link:focus,fieldset[disabled] .navbar-default .btn-link:hover{color:#ccc}.navbar-inverse{background-color:#222;border-color:#080808}.navbar-inverse .navbar-brand{color:#9d9d9d}.navbar-inverse .navbar-brand:focus,.navbar-inverse .navbar-brand:hover{color:#fff;background-color:transparent}.navbar-inverse .navbar-text{color:#9d9d9d}.navbar-inverse .navbar-nav>li>a{color:#9d9d9d}.navbar-inverse .navbar-nav>li>a:focus,.navbar-inverse .navbar-nav>li>a:hover{color:#fff;background-color:transparent}.navbar-inverse .navbar-nav>.active>a,.navbar-inverse .navbar-nav>.active>a:focus,.navbar-inverse .navbar-nav>.active>a:hover{color:#fff;background-color:#080808}.navbar-inverse .navbar-nav>.disabled>a,.navbar-inverse .navbar-nav>.disabled>a:focus,.navbar-inverse .navbar-nav>.disabled>a:hover{color:#444;background-color:transparent}.navbar-inverse .navbar-toggle{border-color:#333}.navbar-inverse .navbar-toggle:focus,.navbar-inverse .navbar-toggle:hover{background-color:#333}.navbar-inverse .navbar-toggle .icon-bar{background-color:#fff}.navbar-inverse .navbar-collapse,.navbar-inverse .navbar-form{border-color:#101010}.navbar-inverse .navbar-nav>.open>a,.navbar-inverse .navbar-nav>.open>a:focus,.navbar-inverse .navbar-nav>.open>a:hover{color:#fff;background-color:#080808}@media (max-width:767px){.navbar-inverse .navbar-nav .open .dropdown-menu>.dropdown-header{border-color:#080808}.navbar-inverse .navbar-nav .open .dropdown-menu .divider{background-color:#080808}.navbar-inverse .navbar-nav .open .dropdown-menu>li>a{color:#9d9d9d}.navbar-inverse .navbar-nav .open .dropdown-menu>li>a:focus,.navbar-inverse .navbar-nav .open .dropdown-menu>li>a:hover{color:#fff;background-color:transparent}.navbar-inverse .navbar-nav .open .dropdown-menu>.active>a,.navbar-inverse .navbar-nav .open .dropdown-menu>.active>a:focus,.navbar-inverse .navbar-nav .open .dropdown-menu>.active>a:hover{color:#fff;background-color:#080808}.navbar-inverse .navbar-nav .open .dropdown-menu>.disabled>a,.navbar-inverse .navbar-nav .open .dropdown-menu>.disabled>a:focus,.navbar-inverse .navbar-nav .open .dropdown-menu>.disabled>a:hover{color:#444;background-color:transparent}}.navbar-inverse .navbar-link{color:#9d9d9d}.navbar-inverse .navbar-link:hover{color:#fff}.navbar-inverse .btn-link{color:#9d9d9d}.navbar-inverse .btn-link:focus,.navbar-inverse .btn-link:hover{color:#fff}.navbar-inverse .btn-link[disabled]:focus,.navbar-inverse .btn-link[disabled]:hover,fieldset[disabled] .navbar-inverse .btn-link:focus,fieldset[disabled] .navbar-inverse .btn-link:hover{color:#444}.breadcrumb{padding:8px 15px;margin-bottom:20px;list-style:none;background-color:#f5f5f5;border-radius:4px}.breadcrumb>li{display:inline-block}.breadcrumb>li+li:before{padding:0 5px;color:#ccc;content:"/\00a0"}.breadcrumb>.active{color:#777}.pagination{display:inline-block;padding-left:0;margin:20px 0;border-radius:4px}.pagination>li{display:inline}.pagination>li>a,.pagination>li>span{position:relative;float:left;padding:6px 12px;margin-left:-1px;line-height:1.42857143;color:#337ab7;text-decoration:none;background-color:#fff;border:1px solid #ddd}.pagination>li:first-child>a,.pagination>li:first-child>span{margin-left:0;border-top-left-radius:4px;border-bottom-left-radius:4px}.pagination>li:last-child>a,.pagination>li:last-child>span{border-top-right-radius:4px;border-bottom-right-radius:4px}.pagination>li>a:focus,.pagination>li>a:hover,.pagination>li>span:focus,.pagination>li>span:hover{z-index:2;color:#23527c;background-color:#eee;border-color:#ddd}.pagination>.active>a,.pagination>.active>a:focus,.pagination>.active>a:hover,.pagination>.active>span,.pagination>.active>span:focus,.pagination>.active>span:hover{z-index:3;color:#fff;cursor:default;background-color:#337ab7;border-color:#337ab7}.pagination>.disabled>a,.pagination>.disabled>a:focus,.pagination>.disabled>a:hover,.pagination>.disabled>span,.pagination>.disabled>span:focus,.pagination>.disabled>span:hover{color:#777;cursor:not-allowed;background-color:#fff;border-color:#ddd}.pagination-lg>li>a,.pagination-lg>li>span{padding:10px 16px;font-size:18px;line-height:1.3333333}.pagination-lg>li:first-child>a,.pagination-lg>li:first-child>span{border-top-left-radius:6px;border-bottom-left-radius:6px}.pagination-lg>li:last-child>a,.pagination-lg>li:last-child>span{border-top-right-radius:6px;border-bottom-right-radius:6px}.pagination-sm>li>a,.pagination-sm>li>span{padding:5px 10px;font-size:12px;line-height:1.5}.pagination-sm>li:first-child>a,.pagination-sm>li:first-child>span{border-top-left-radius:3px;border-bottom-left-radius:3px}.pagination-sm>li:last-child>a,.pagination-sm>li:last-child>span{border-top-right-radius:3px;border-bottom-right-radius:3px}.pager{padding-left:0;margin:20px 0;text-align:center;list-style:none}.pager li{display:inline}.pager li>a,.pager li>span{display:inline-block;padding:5px 14px;background-color:#fff;border:1px solid #ddd;border-radius:15px}.pager li>a:focus,.pager li>a:hover{text-decoration:none;background-color:#eee}.pager .next>a,.pager .next>span{float:right}.pager .previous>a,.pager .previous>span{float:left}.pager .disabled>a,.pager .disabled>a:focus,.pager .disabled>a:hover,.pager .disabled>span{color:#777;cursor:not-allowed;background-color:#fff}.label{display:inline;padding:.2em .6em .3em;font-size:75%;font-weight:700;line-height:1;color:#fff;text-align:center;white-space:nowrap;vertical-align:baseline;border-radius:.25em}a.label:focus,a.label:hover{color:#fff;text-decoration:none;cursor:pointer}.label:empty{display:none}.btn .label{position:relative;top:-1px}.label-default{background-color:#777}.label-default[href]:focus,.label-default[href]:hover{background-color:#5e5e5e}.label-primary{background-color:#337ab7}.label-primary[href]:focus,.label-primary[href]:hover{background-color:#286090}.label-success{background-color:#5cb85c}.label-success[href]:focus,.label-success[href]:hover{background-color:#449d44}.label-info{background-color:#5bc0de}.label-info[href]:focus,.label-info[href]:hover{background-color:#31b0d5}.label-warning{background-color:#f0ad4e}.label-warning[href]:focus,.label-warning[href]:hover{background-color:#ec971f}.label-danger{background-color:#d9534f}.label-danger[href]:focus,.label-danger[href]:hover{background-color:#c9302c}.badge{display:inline-block;min-width:10px;padding:3px 7px;font-size:12px;font-weight:700;line-height:1;color:#fff;text-align:center;white-space:nowrap;vertical-align:middle;background-color:#777;border-radius:10px}.badge:empty{display:none}.btn .badge{position:relative;top:-1px}.btn-group-xs>.btn .badge,.btn-xs .badge{top:0;padding:1px 5px}a.badge:focus,a.badge:hover{color:#fff;text-decoration:none;cursor:pointer}.list-group-item.active>.badge,.nav-pills>.active>a>.badge{color:#337ab7;background-color:#fff}.list-group-item>.badge{float:right}.list-group-item>.badge+.badge{margin-right:5px}.nav-pills>li>a>.badge{margin-left:3px}.jumbotron{padding-top:30px;padding-bottom:30px;margin-bottom:30px;color:inherit;background-color:#eee}.jumbotron .h1,.jumbotron h1{color:inherit}.jumbotron p{margin-bottom:15px;font-size:21px;font-weight:200}.jumbotron>hr{border-top-color:#d5d5d5}.container .jumbotron,.container-fluid .jumbotron{padding-right:15px;padding-left:15px;border-radius:6px}.jumbotron .container{max-width:100%}@media screen and (min-width:768px){.jumbotron{padding-top:48px;padding-bottom:48px}.container .jumbotron,.container-fluid .jumbotron{padding-right:60px;padding-left:60px}.jumbotron .h1,.jumbotron h1{font-size:63px}}.thumbnail{display:block;padding:4px;margin-bottom:20px;line-height:1.42857143;background-color:#fff;border:1px solid #ddd;border-radius:4px;-webkit-transition:border .2s ease-in-out;-o-transition:border .2s ease-in-out;transition:border .2s ease-in-out}.thumbnail a>img,.thumbnail>img{margin-right:auto;margin-left:auto}a.thumbnail.active,a.thumbnail:focus,a.thumbnail:hover{border-color:#337ab7}.thumbnail .caption{padding:9px;color:#333}.alert{padding:15px;margin-bottom:20px;border:1px solid transparent;border-radius:4px}.alert h4{margin-top:0;color:inherit}.alert .alert-link{font-weight:700}.alert>p,.alert>ul{margin-bottom:0}.alert>p+p{margin-top:5px}.alert-dismissable,.alert-dismissible{padding-right:35px}.alert-dismissable .close,.alert-dismissible .close{position:relative;top:-2px;right:-21px;color:inherit}.alert-success{color:#3c763d;background-color:#dff0d8;border-color:#d6e9c6}.alert-success hr{border-top-color:#c9e2b3}.alert-success .alert-link{color:#2b542c}.alert-info{color:#31708f;background-color:#d9edf7;border-color:#bce8f1}.alert-info hr{border-top-color:#a6e1ec}.alert-info .alert-link{color:#245269}.alert-warning{color:#8a6d3b;background-color:#fcf8e3;border-color:#faebcc}.alert-warning hr{border-top-color:#f7e1b5}.alert-warning .alert-link{color:#66512c}.alert-danger{color:#a94442;background-color:#f2dede;border-color:#ebccd1}.alert-danger hr{border-top-color:#e4b9c0}.alert-danger .alert-link{color:#843534}@-webkit-keyframes progress-bar-stripes{from{background-position:40px 0}to{background-position:0 0}}@-o-keyframes progress-bar-stripes{from{background-position:40px 0}to{background-position:0 0}}@keyframes progress-bar-stripes{from{background-position:40px 0}to{background-position:0 0}}.progress{height:20px;margin-bottom:20px;overflow:hidden;background-color:#f5f5f5;border-radius:4px;-webkit-box-shadow:inset 0 1px 2px rgba(0,0,0,.1);box-shadow:inset 0 1px 2px rgba(0,0,0,.1)}.progress-bar{float:left;width:0;height:100%;font-size:12px;line-height:20px;color:#fff;text-align:center;background-color:#337ab7;-webkit-box-shadow:inset 0 -1px 0 rgba(0,0,0,.15);box-shadow:inset 0 -1px 0 rgba(0,0,0,.15);-webkit-transition:width .6s ease;-o-transition:width .6s ease;transition:width .6s ease}.progress-bar-striped,.progress-striped .progress-bar{background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:-o-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);-webkit-background-size:40px 40px;background-size:40px 40px}.progress-bar.active,.progress.active .progress-bar{-webkit-animation:progress-bar-stripes 2s linear infinite;-o-animation:progress-bar-stripes 2s linear infinite;animation:progress-bar-stripes 2s linear infinite}.progress-bar-success{background-color:#5cb85c}.progress-striped .progress-bar-success{background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:-o-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent)}.progress-bar-info{background-color:#5bc0de}.progress-striped .progress-bar-info{background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:-o-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent)}.progress-bar-warning{background-color:#f0ad4e}.progress-striped .progress-bar-warning{background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:-o-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent)}.progress-bar-danger{background-color:#d9534f}.progress-striped .progress-bar-danger{background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:-o-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent)}.media{margin-top:15px}.media:first-child{margin-top:0}.media,.media-body{overflow:hidden;zoom:1}.media-body{width:10000px}.media-object{display:block}.media-object.img-thumbnail{max-width:none}.media-right,.media>.pull-right{padding-left:10px}.media-left,.media>.pull-left{padding-right:10px}.media-body,.media-left,.media-right{display:table-cell;vertical-align:top}.media-middle{vertical-align:middle}.media-bottom{vertical-align:bottom}.media-heading{margin-top:0;margin-bottom:5px}.media-list{padding-left:0;list-style:none}.list-group{padding-left:0;margin-bottom:20px}.list-group-item{position:relative;display:block;padding:10px 15px;margin-bottom:-1px;background-color:#fff;border:1px solid #ddd}.list-group-item:first-child{border-top-left-radius:4px;border-top-right-radius:4px}.list-group-item:last-child{margin-bottom:0;border-bottom-right-radius:4px;border-bottom-left-radius:4px}a.list-group-item,button.list-group-item{color:#555}a.list-group-item .list-group-item-heading,button.list-group-item .list-group-item-heading{color:#333}a.list-group-item:focus,a.list-group-item:hover,button.list-group-item:focus,button.list-group-item:hover{color:#555;text-decoration:none;background-color:#f5f5f5}button.list-group-item{width:100%;text-align:left}.list-group-item.disabled,.list-group-item.disabled:focus,.list-group-item.disabled:hover{color:#777;cursor:not-allowed;background-color:#eee}.list-group-item.disabled .list-group-item-heading,.list-group-item.disabled:focus .list-group-item-heading,.list-group-item.disabled:hover .list-group-item-heading{color:inherit}.list-group-item.disabled .list-group-item-text,.list-group-item.disabled:focus .list-group-item-text,.list-group-item.disabled:hover .list-group-item-text{color:#777}.list-group-item.active,.list-group-item.active:focus,.list-group-item.active:hover{z-index:2;color:#fff;background-color:#337ab7;border-color:#337ab7}.list-group-item.active .list-group-item-heading,.list-group-item.active .list-group-item-heading>.small,.list-group-item.active .list-group-item-heading>small,.list-group-item.active:focus .list-group-item-heading,.list-group-item.active:focus .list-group-item-heading>.small,.list-group-item.active:focus .list-group-item-heading>small,.list-group-item.active:hover .list-group-item-heading,.list-group-item.active:hover .list-group-item-heading>.small,.list-group-item.active:hover .list-group-item-heading>small{color:inherit}.list-group-item.active .list-group-item-text,.list-group-item.active:focus .list-group-item-text,.list-group-item.active:hover .list-group-item-text{color:#c7ddef}.list-group-item-success{color:#3c763d;background-color:#dff0d8}a.list-group-item-success,button.list-group-item-success{color:#3c763d}a.list-group-item-success .list-group-item-heading,button.list-group-item-success .list-group-item-heading{color:inherit}a.list-group-item-success:focus,a.list-group-item-success:hover,button.list-group-item-success:focus,button.list-group-item-success:hover{color:#3c763d;background-color:#d0e9c6}a.list-group-item-success.active,a.list-group-item-success.active:focus,a.list-group-item-success.active:hover,button.list-group-item-success.active,button.list-group-item-success.active:focus,button.list-group-item-success.active:hover{color:#fff;background-color:#3c763d;border-color:#3c763d}.list-group-item-info{color:#31708f;background-color:#d9edf7}a.list-group-item-info,button.list-group-item-info{color:#31708f}a.list-group-item-info .list-group-item-heading,button.list-group-item-info .list-group-item-heading{color:inherit}a.list-group-item-info:focus,a.list-group-item-info:hover,button.list-group-item-info:focus,button.list-group-item-info:hover{color:#31708f;background-color:#c4e3f3}a.list-group-item-info.active,a.list-group-item-info.active:focus,a.list-group-item-info.active:hover,button.list-group-item-info.active,button.list-group-item-info.active:focus,button.list-group-item-info.active:hover{color:#fff;background-color:#31708f;border-color:#31708f}.list-group-item-warning{color:#8a6d3b;background-color:#fcf8e3}a.list-group-item-warning,button.list-group-item-warning{color:#8a6d3b}a.list-group-item-warning .list-group-item-heading,button.list-group-item-warning .list-group-item-heading{color:inherit}a.list-group-item-warning:focus,a.list-group-item-warning:hover,button.list-group-item-warning:focus,button.list-group-item-warning:hover{color:#8a6d3b;background-color:#faf2cc}a.list-group-item-warning.active,a.list-group-item-warning.active:focus,a.list-group-item-warning.active:hover,button.list-group-item-warning.active,button.list-group-item-warning.active:focus,button.list-group-item-warning.active:hover{color:#fff;background-color:#8a6d3b;border-color:#8a6d3b}.list-group-item-danger{color:#a94442;background-color:#f2dede}a.list-group-item-danger,button.list-group-item-danger{color:#a94442}a.list-group-item-danger .list-group-item-heading,button.list-group-item-danger .list-group-item-heading{color:inherit}a.list-group-item-danger:focus,a.list-group-item-danger:hover,button.list-group-item-danger:focus,button.list-group-item-danger:hover{color:#a94442;background-color:#ebcccc}a.list-group-item-danger.active,a.list-group-item-danger.active:focus,a.list-group-item-danger.active:hover,button.list-group-item-danger.active,button.list-group-item-danger.active:focus,button.list-group-item-danger.active:hover{color:#fff;background-color:#a94442;border-color:#a94442}.list-group-item-heading{margin-top:0;margin-bottom:5px}.list-group-item-text{margin-bottom:0;line-height:1.3}.panel{margin-bottom:20px;background-color:#fff;border:1px solid transparent;border-radius:4px;-webkit-box-shadow:0 1px 1px rgba(0,0,0,.05);box-shadow:0 1px 1px rgba(0,0,0,.05)}.panel-body{padding:15px}.panel-heading{padding:10px 15px;border-bottom:1px solid transparent;border-top-left-radius:3px;border-top-right-radius:3px}.panel-heading>.dropdown .dropdown-toggle{color:inherit}.panel-title{margin-top:0;margin-bottom:0;font-size:16px;color:inherit}.panel-title>.small,.panel-title>.small>a,.panel-title>a,.panel-title>small,.panel-title>small>a{color:inherit}.panel-footer{padding:10px 15px;background-color:#f5f5f5;border-top:1px solid #ddd;border-bottom-right-radius:3px;border-bottom-left-radius:3px}.panel>.list-group,.panel>.panel-collapse>.list-group{margin-bottom:0}.panel>.list-group .list-group-item,.panel>.panel-collapse>.list-group .list-group-item{border-width:1px 0;border-radius:0}.panel>.list-group:first-child .list-group-item:first-child,.panel>.panel-collapse>.list-group:first-child .list-group-item:first-child{border-top:0;border-top-left-radius:3px;border-top-right-radius:3px}.panel>.list-group:last-child .list-group-item:last-child,.panel>.panel-collapse>.list-group:last-child .list-group-item:last-child{border-bottom:0;border-bottom-right-radius:3px;border-bottom-left-radius:3px}.panel>.panel-heading+.panel-collapse>.list-group .list-group-item:first-child{border-top-left-radius:0;border-top-right-radius:0}.panel-heading+.list-group .list-group-item:first-child{border-top-width:0}.list-group+.panel-footer{border-top-width:0}.panel>.panel-collapse>.table,.panel>.table,.panel>.table-responsive>.table{margin-bottom:0}.panel>.panel-collapse>.table caption,.panel>.table caption,.panel>.table-responsive>.table caption{padding-right:15px;padding-left:15px}.panel>.table-responsive:first-child>.table:first-child,.panel>.table:first-child{border-top-left-radius:3px;border-top-right-radius:3px}.panel>.table-responsive:first-child>.table:first-child>tbody:first-child>tr:first-child,.panel>.table-responsive:first-child>.table:first-child>thead:first-child>tr:first-child,.panel>.table:first-child>tbody:first-child>tr:first-child,.panel>.table:first-child>thead:first-child>tr:first-child{border-top-left-radius:3px;border-top-right-radius:3px}.panel>.table-responsive:first-child>.table:first-child>tbody:first-child>tr:first-child td:first-child,.panel>.table-responsive:first-child>.table:first-child>tbody:first-child>tr:first-child th:first-child,.panel>.table-responsive:first-child>.table:first-child>thead:first-child>tr:first-child td:first-child,.panel>.table-responsive:first-child>.table:first-child>thead:first-child>tr:first-child th:first-child,.panel>.table:first-child>tbody:first-child>tr:first-child td:first-child,.panel>.table:first-child>tbody:first-child>tr:first-child th:first-child,.panel>.table:first-child>thead:first-child>tr:first-child td:first-child,.panel>.table:first-child>thead:first-child>tr:first-child th:first-child{border-top-left-radius:3px}.panel>.table-responsive:first-child>.table:first-child>tbody:first-child>tr:first-child td:last-child,.panel>.table-responsive:first-child>.table:first-child>tbody:first-child>tr:first-child th:last-child,.panel>.table-responsive:first-child>.table:first-child>thead:first-child>tr:first-child td:last-child,.panel>.table-responsive:first-child>.table:first-child>thead:first-child>tr:first-child th:last-child,.panel>.table:first-child>tbody:first-child>tr:first-child td:last-child,.panel>.table:first-child>tbody:first-child>tr:first-child th:last-child,.panel>.table:first-child>thead:first-child>tr:first-child td:last-child,.panel>.table:first-child>thead:first-child>tr:first-child th:last-child{border-top-right-radius:3px}.panel>.table-responsive:last-child>.table:last-child,.panel>.table:last-child{border-bottom-right-radius:3px;border-bottom-left-radius:3px}.panel>.table-responsive:last-child>.table:last-child>tbody:last-child>tr:last-child,.panel>.table-responsive:last-child>.table:last-child>tfoot:last-child>tr:last-child,.panel>.table:last-child>tbody:last-child>tr:last-child,.panel>.table:last-child>tfoot:last-child>tr:last-child{border-bottom-right-radius:3px;border-bottom-left-radius:3px}.panel>.table-responsive:last-child>.table:last-child>tbody:last-child>tr:last-child td:first-child,.panel>.table-responsive:last-child>.table:last-child>tbody:last-child>tr:last-child th:first-child,.panel>.table-responsive:last-child>.table:last-child>tfoot:last-child>tr:last-child td:first-child,.panel>.table-responsive:last-child>.table:last-child>tfoot:last-child>tr:last-child th:first-child,.panel>.table:last-child>tbody:last-child>tr:last-child td:first-child,.panel>.table:last-child>tbody:last-child>tr:last-child th:first-child,.panel>.table:last-child>tfoot:last-child>tr:last-child td:first-child,.panel>.table:last-child>tfoot:last-child>tr:last-child th:first-child{border-bottom-left-radius:3px}.panel>.table-responsive:last-child>.table:last-child>tbody:last-child>tr:last-child td:last-child,.panel>.table-responsive:last-child>.table:last-child>tbody:last-child>tr:last-child th:last-child,.panel>.table-responsive:last-child>.table:last-child>tfoot:last-child>tr:last-child td:last-child,.panel>.table-responsive:last-child>.table:last-child>tfoot:last-child>tr:last-child th:last-child,.panel>.table:last-child>tbody:last-child>tr:last-child td:last-child,.panel>.table:last-child>tbody:last-child>tr:last-child th:last-child,.panel>.table:last-child>tfoot:last-child>tr:last-child td:last-child,.panel>.table:last-child>tfoot:last-child>tr:last-child th:last-child{border-bottom-right-radius:3px}.panel>.panel-body+.table,.panel>.panel-body+.table-responsive,.panel>.table+.panel-body,.panel>.table-responsive+.panel-body{border-top:1px solid #ddd}.panel>.table>tbody:first-child>tr:first-child td,.panel>.table>tbody:first-child>tr:first-child th{border-top:0}.panel>.table-bordered,.panel>.table-responsive>.table-bordered{border:0}.panel>.table-bordered>tbody>tr>td:first-child,.panel>.table-bordered>tbody>tr>th:first-child,.panel>.table-bordered>tfoot>tr>td:first-child,.panel>.table-bordered>tfoot>tr>th:first-child,.panel>.table-bordered>thead>tr>td:first-child,.panel>.table-bordered>thead>tr>th:first-child,.panel>.table-responsive>.table-bordered>tbody>tr>td:first-child,.panel>.table-responsive>.table-bordered>tbody>tr>th:first-child,.panel>.table-responsive>.table-bordered>tfoot>tr>td:first-child,.panel>.table-responsive>.table-bordered>tfoot>tr>th:first-child,.panel>.table-responsive>.table-bordered>thead>tr>td:first-child,.panel>.table-responsive>.table-bordered>thead>tr>th:first-child{border-left:0}.panel>.table-bordered>tbody>tr>td:last-child,.panel>.table-bordered>tbody>tr>th:last-child,.panel>.table-bordered>tfoot>tr>td:last-child,.panel>.table-bordered>tfoot>tr>th:last-child,.panel>.table-bordered>thead>tr>td:last-child,.panel>.table-bordered>thead>tr>th:last-child,.panel>.table-responsive>.table-bordered>tbody>tr>td:last-child,.panel>.table-responsive>.table-bordered>tbody>tr>th:last-child,.panel>.table-responsive>.table-bordered>tfoot>tr>td:last-child,.panel>.table-responsive>.table-bordered>tfoot>tr>th:last-child,.panel>.table-responsive>.table-bordered>thead>tr>td:last-child,.panel>.table-responsive>.table-bordered>thead>tr>th:last-child{border-right:0}.panel>.table-bordered>tbody>tr:first-child>td,.panel>.table-bordered>tbody>tr:first-child>th,.panel>.table-bordered>thead>tr:first-child>td,.panel>.table-bordered>thead>tr:first-child>th,.panel>.table-responsive>.table-bordered>tbody>tr:first-child>td,.panel>.table-responsive>.table-bordered>tbody>tr:first-child>th,.panel>.table-responsive>.table-bordered>thead>tr:first-child>td,.panel>.table-responsive>.table-bordered>thead>tr:first-child>th{border-bottom:0}.panel>.table-bordered>tbody>tr:last-child>td,.panel>.table-bordered>tbody>tr:last-child>th,.panel>.table-bordered>tfoot>tr:last-child>td,.panel>.table-bordered>tfoot>tr:last-child>th,.panel>.table-responsive>.table-bordered>tbody>tr:last-child>td,.panel>.table-responsive>.table-bordered>tbody>tr:last-child>th,.panel>.table-responsive>.table-bordered>tfoot>tr:last-child>td,.panel>.table-responsive>.table-bordered>tfoot>tr:last-child>th{border-bottom:0}.panel>.table-responsive{margin-bottom:0;border:0}.panel-group{margin-bottom:20px}.panel-group .panel{margin-bottom:0;border-radius:4px}.panel-group .panel+.panel{margin-top:5px}.panel-group .panel-heading{border-bottom:0}.panel-group .panel-heading+.panel-collapse>.list-group,.panel-group .panel-heading+.panel-collapse>.panel-body{border-top:1px solid #ddd}.panel-group .panel-footer{border-top:0}.panel-group .panel-footer+.panel-collapse .panel-body{border-bottom:1px solid #ddd}.panel-default{border-color:#ddd}.panel-default>.panel-heading{color:#333;background-color:#f5f5f5;border-color:#ddd}.panel-default>.panel-heading+.panel-collapse>.panel-body{border-top-color:#ddd}.panel-default>.panel-heading .badge{color:#f5f5f5;background-color:#333}.panel-default>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:#ddd}.panel-primary{border-color:#337ab7}.panel-primary>.panel-heading{color:#fff;background-color:#337ab7;border-color:#337ab7}.panel-primary>.panel-heading+.panel-collapse>.panel-body{border-top-color:#337ab7}.panel-primary>.panel-heading .badge{color:#337ab7;background-color:#fff}.panel-primary>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:#337ab7}.panel-success{border-color:#d6e9c6}.panel-success>.panel-heading{color:#3c763d;background-color:#dff0d8;border-color:#d6e9c6}.panel-success>.panel-heading+.panel-collapse>.panel-body{border-top-color:#d6e9c6}.panel-success>.panel-heading .badge{color:#dff0d8;background-color:#3c763d}.panel-success>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:#d6e9c6}.panel-info{border-color:#bce8f1}.panel-info>.panel-heading{color:#31708f;background-color:#d9edf7;border-color:#bce8f1}.panel-info>.panel-heading+.panel-collapse>.panel-body{border-top-color:#bce8f1}.panel-info>.panel-heading .badge{color:#d9edf7;background-color:#31708f}.panel-info>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:#bce8f1}.panel-warning{border-color:#faebcc}.panel-warning>.panel-heading{color:#8a6d3b;background-color:#fcf8e3;border-color:#faebcc}.panel-warning>.panel-heading+.panel-collapse>.panel-body{border-top-color:#faebcc}.panel-warning>.panel-heading .badge{color:#fcf8e3;background-color:#8a6d3b}.panel-warning>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:#faebcc}.panel-danger{border-color:#ebccd1}.panel-danger>.panel-heading{color:#a94442;background-color:#f2dede;border-color:#ebccd1}.panel-danger>.panel-heading+.panel-collapse>.panel-body{border-top-color:#ebccd1}.panel-danger>.panel-heading .badge{color:#f2dede;background-color:#a94442}.panel-danger>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:#ebccd1}.embed-responsive{position:relative;display:block;height:0;padding:0;overflow:hidden}.embed-responsive .embed-responsive-item,.embed-responsive embed,.embed-responsive iframe,.embed-responsive object,.embed-responsive video{position:absolute;top:0;bottom:0;left:0;width:100%;height:100%;border:0}.embed-responsive-16by9{padding-bottom:56.25%}.embed-responsive-4by3{padding-bottom:75%}.well{min-height:20px;padding:19px;margin-bottom:20px;background-color:#f5f5f5;border:1px solid #e3e3e3;border-radius:4px;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.05);box-shadow:inset 0 1px 1px rgba(0,0,0,.05)}.well blockquote{border-color:#ddd;border-color:rgba(0,0,0,.15)}.well-lg{padding:24px;border-radius:6px}.well-sm{padding:9px;border-radius:3px}.close{float:right;font-size:21px;font-weight:700;line-height:1;color:#000;text-shadow:0 1px 0 #fff;filter:alpha(opacity=20);opacity:.2}.close:focus,.close:hover{color:#000;text-decoration:none;cursor:pointer;filter:alpha(opacity=50);opacity:.5}button.close{-webkit-appearance:none;padding:0;cursor:pointer;background:0 0;border:0}.modal-open{overflow:hidden}.modal{position:fixed;top:0;right:0;bottom:0;left:0;z-index:1050;display:none;overflow:hidden;-webkit-overflow-scrolling:touch;outline:0}.modal.fade .modal-dialog{-webkit-transition:-webkit-transform .3s ease-out;-o-transition:-o-transform .3s ease-out;transition:transform .3s ease-out;-webkit-transform:translate(0,-25%);-ms-transform:translate(0,-25%);-o-transform:translate(0,-25%);transform:translate(0,-25%)}.modal.in .modal-dialog{-webkit-transform:translate(0,0);-ms-transform:translate(0,0);-o-transform:translate(0,0);transform:translate(0,0)}.modal-open .modal{overflow-x:hidden;overflow-y:auto}.modal-dialog{position:relative;width:auto;margin:10px}.modal-content{position:relative;background-color:#fff;-webkit-background-clip:padding-box;background-clip:padding-box;border:1px solid #999;border:1px solid rgba(0,0,0,.2);border-radius:6px;outline:0;-webkit-box-shadow:0 3px 9px rgba(0,0,0,.5);box-shadow:0 3px 9px rgba(0,0,0,.5)}.modal-backdrop{position:fixed;top:0;right:0;bottom:0;left:0;z-index:1040;background-color:#000}.modal-backdrop.fade{filter:alpha(opacity=0);opacity:0}.modal-backdrop.in{filter:alpha(opacity=50);opacity:.5}.modal-header{padding:15px;border-bottom:1px solid #e5e5e5}.modal-header .close{margin-top:-2px}.modal-title{margin:0;line-height:1.42857143}.modal-body{position:relative;padding:15px}.modal-footer{padding:15px;text-align:right;border-top:1px solid #e5e5e5}.modal-footer .btn+.btn{margin-bottom:0;margin-left:5px}.modal-footer .btn-group .btn+.btn{margin-left:-1px}.modal-footer .btn-block+.btn-block{margin-left:0}.modal-scrollbar-measure{position:absolute;top:-9999px;width:50px;height:50px;overflow:scroll}@media (min-width:768px){.modal-dialog{width:600px;margin:30px auto}.modal-content{-webkit-box-shadow:0 5px 15px rgba(0,0,0,.5);box-shadow:0 5px 15px rgba(0,0,0,.5)}.modal-sm{width:300px}}@media (min-width:992px){.modal-lg{width:900px}}.tooltip{position:absolute;z-index:1070;display:block;font-family:"Helvetica Neue",Helvetica,Arial,sans-serif;font-size:12px;font-style:normal;font-weight:400;line-height:1.42857143;text-align:left;text-align:start;text-decoration:none;text-shadow:none;text-transform:none;letter-spacing:normal;word-break:normal;word-spacing:normal;word-wrap:normal;white-space:normal;filter:alpha(opacity=0);opacity:0;line-break:auto}.tooltip.in{filter:alpha(opacity=90);opacity:.9}.tooltip.top{padding:5px 0;margin-top:-3px}.tooltip.right{padding:0 5px;margin-left:3px}.tooltip.bottom{padding:5px 0;margin-top:3px}.tooltip.left{padding:0 5px;margin-left:-3px}.tooltip-inner{max-width:200px;padding:3px 8px;color:#fff;text-align:center;background-color:#000;border-radius:4px}.tooltip-arrow{position:absolute;width:0;height:0;border-color:transparent;border-style:solid}.tooltip.top .tooltip-arrow{bottom:0;left:50%;margin-left:-5px;border-width:5px 5px 0;border-top-color:#000}.tooltip.top-left .tooltip-arrow{right:5px;bottom:0;margin-bottom:-5px;border-width:5px 5px 0;border-top-color:#000}.tooltip.top-right .tooltip-arrow{bottom:0;left:5px;margin-bottom:-5px;border-width:5px 5px 0;border-top-color:#000}.tooltip.right .tooltip-arrow{top:50%;left:0;margin-top:-5px;border-width:5px 5px 5px 0;border-right-color:#000}.tooltip.left .tooltip-arrow{top:50%;right:0;margin-top:-5px;border-width:5px 0 5px 5px;border-left-color:#000}.tooltip.bottom .tooltip-arrow{top:0;left:50%;margin-left:-5px;border-width:0 5px 5px;border-bottom-color:#000}.tooltip.bottom-left .tooltip-arrow{top:0;right:5px;margin-top:-5px;border-width:0 5px 5px;border-bottom-color:#000}.tooltip.bottom-right .tooltip-arrow{top:0;left:5px;margin-top:-5px;border-width:0 5px 5px;border-bottom-color:#000}.popover{position:absolute;top:0;left:0;z-index:1060;display:none;max-width:276px;padding:1px;font-family:"Helvetica Neue",Helvetica,Arial,sans-serif;font-size:14px;font-style:normal;font-weight:400;line-height:1.42857143;text-align:left;text-align:start;text-decoration:none;text-shadow:none;text-transform:none;letter-spacing:normal;word-break:normal;word-spacing:normal;word-wrap:normal;white-space:normal;background-color:#fff;-webkit-background-clip:padding-box;background-clip:padding-box;border:1px solid #ccc;border:1px solid rgba(0,0,0,.2);border-radius:6px;-webkit-box-shadow:0 5px 10px rgba(0,0,0,.2);box-shadow:0 5px 10px rgba(0,0,0,.2);line-break:auto}.popover.top{margin-top:-10px}.popover.right{margin-left:10px}.popover.bottom{margin-top:10px}.popover.left{margin-left:-10px}.popover-title{padding:8px 14px;margin:0;font-size:14px;background-color:#f7f7f7;border-bottom:1px solid #ebebeb;border-radius:5px 5px 0 0}.popover-content{padding:9px 14px}.popover>.arrow,.popover>.arrow:after{position:absolute;display:block;width:0;height:0;border-color:transparent;border-style:solid}.popover>.arrow{border-width:11px}.popover>.arrow:after{content:"";border-width:10px}.popover.top>.arrow{bottom:-11px;left:50%;margin-left:-11px;border-top-color:#999;border-top-color:rgba(0,0,0,.25);border-bottom-width:0}.popover.top>.arrow:after{bottom:1px;margin-left:-10px;content:" ";border-top-color:#fff;border-bottom-width:0}.popover.right>.arrow{top:50%;left:-11px;margin-top:-11px;border-right-color:#999;border-right-color:rgba(0,0,0,.25);border-left-width:0}.popover.right>.arrow:after{bottom:-10px;left:1px;content:" ";border-right-color:#fff;border-left-width:0}.popover.bottom>.arrow{top:-11px;left:50%;margin-left:-11px;border-top-width:0;border-bottom-color:#999;border-bottom-color:rgba(0,0,0,.25)}.popover.bottom>.arrow:after{top:1px;margin-left:-10px;content:" ";border-top-width:0;border-bottom-color:#fff}.popover.left>.arrow{top:50%;right:-11px;margin-top:-11px;border-right-width:0;border-left-color:#999;border-left-color:rgba(0,0,0,.25)}.popover.left>.arrow:after{right:1px;bottom:-10px;content:" ";border-right-width:0;border-left-color:#fff}.carousel{position:relative}.carousel-inner{position:relative;width:100%;overflow:hidden}.carousel-inner>.item{position:relative;display:none;-webkit-transition:.6s ease-in-out left;-o-transition:.6s ease-in-out left;transition:.6s ease-in-out left}.carousel-inner>.item>a>img,.carousel-inner>.item>img{line-height:1}@media all and (transform-3d),(-webkit-transform-3d){.carousel-inner>.item{-webkit-transition:-webkit-transform .6s ease-in-out;-o-transition:-o-transform .6s ease-in-out;transition:transform .6s ease-in-out;-webkit-backface-visibility:hidden;backface-visibility:hidden;-webkit-perspective:1000px;perspective:1000px}.carousel-inner>.item.active.right,.carousel-inner>.item.next{left:0;-webkit-transform:translate3d(100%,0,0);transform:translate3d(100%,0,0)}.carousel-inner>.item.active.left,.carousel-inner>.item.prev{left:0;-webkit-transform:translate3d(-100%,0,0);transform:translate3d(-100%,0,0)}.carousel-inner>.item.active,.carousel-inner>.item.next.left,.carousel-inner>.item.prev.right{left:0;-webkit-transform:translate3d(0,0,0);transform:translate3d(0,0,0)}}.carousel-inner>.active,.carousel-inner>.next,.carousel-inner>.prev{display:block}.carousel-inner>.active{left:0}.carousel-inner>.next,.carousel-inner>.prev{position:absolute;top:0;width:100%}.carousel-inner>.next{left:100%}.carousel-inner>.prev{left:-100%}.carousel-inner>.next.left,.carousel-inner>.prev.right{left:0}.carousel-inner>.active.left{left:-100%}.carousel-inner>.active.right{left:100%}.carousel-control{position:absolute;top:0;bottom:0;left:0;width:15%;font-size:20px;color:#fff;text-align:center;text-shadow:0 1px 2px rgba(0,0,0,.6);background-color:rgba(0,0,0,0);filter:alpha(opacity=50);opacity:.5}.carousel-control.left{background-image:-webkit-linear-gradient(left,rgba(0,0,0,.5) 0,rgba(0,0,0,.0001) 100%);background-image:-o-linear-gradient(left,rgba(0,0,0,.5) 0,rgba(0,0,0,.0001) 100%);background-image:-webkit-gradient(linear,left top,right top,from(rgba(0,0,0,.5)),to(rgba(0,0,0,.0001)));background-image:linear-gradient(to right,rgba(0,0,0,.5) 0,rgba(0,0,0,.0001) 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#80000000', endColorstr='#00000000', GradientType=1);background-repeat:repeat-x}.carousel-control.right{right:0;left:auto;background-image:-webkit-linear-gradient(left,rgba(0,0,0,.0001) 0,rgba(0,0,0,.5) 100%);background-image:-o-linear-gradient(left,rgba(0,0,0,.0001) 0,rgba(0,0,0,.5) 100%);background-image:-webkit-gradient(linear,left top,right top,from(rgba(0,0,0,.0001)),to(rgba(0,0,0,.5)));background-image:linear-gradient(to right,rgba(0,0,0,.0001) 0,rgba(0,0,0,.5) 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#00000000', endColorstr='#80000000', GradientType=1);background-repeat:repeat-x}.carousel-control:focus,.carousel-control:hover{color:#fff;text-decoration:none;filter:alpha(opacity=90);outline:0;opacity:.9}.carousel-control .glyphicon-chevron-left,.carousel-control .glyphicon-chevron-right,.carousel-control .icon-next,.carousel-control .icon-prev{position:absolute;top:50%;z-index:5;display:inline-block;margin-top:-10px}.carousel-control .glyphicon-chevron-left,.carousel-control .icon-prev{left:50%;margin-left:-10px}.carousel-control .glyphicon-chevron-right,.carousel-control .icon-next{right:50%;margin-right:-10px}.carousel-control .icon-next,.carousel-control .icon-prev{width:20px;height:20px;font-family:serif;line-height:1}.carousel-control .icon-prev:before{content:'\2039'}.carousel-control .icon-next:before{content:'\203a'}.carousel-indicators{position:absolute;bottom:10px;left:50%;z-index:15;width:60%;padding-left:0;margin-left:-30%;text-align:center;list-style:none}.carousel-indicators li{display:inline-block;width:10px;height:10px;margin:1px;text-indent:-999px;cursor:pointer;background-color:#000\9;background-color:rgba(0,0,0,0);border:1px solid #fff;border-radius:10px}.carousel-indicators .active{width:12px;height:12px;margin:0;background-color:#fff}.carousel-caption{position:absolute;right:15%;bottom:20px;left:15%;z-index:10;padding-top:20px;padding-bottom:20px;color:#fff;text-align:center;text-shadow:0 1px 2px rgba(0,0,0,.6)}.carousel-caption .btn{text-shadow:none}@media screen and (min-width:768px){.carousel-control .glyphicon-chevron-left,.carousel-control .glyphicon-chevron-right,.carousel-control .icon-next,.carousel-control .icon-prev{width:30px;height:30px;margin-top:-10px;font-size:30px}.carousel-control .glyphicon-chevron-left,.carousel-control .icon-prev{margin-left:-10px}.carousel-control .glyphicon-chevron-right,.carousel-control .icon-next{margin-right:-10px}.carousel-caption{right:20%;left:20%;padding-bottom:30px}.carousel-indicators{bottom:20px}}.btn-group-vertical>.btn-group:after,.btn-group-vertical>.btn-group:before,.btn-toolbar:after,.btn-toolbar:before,.clearfix:after,.clearfix:before,.container-fluid:after,.container-fluid:before,.container:after,.container:before,.dl-horizontal dd:after,.dl-horizontal dd:before,.form-horizontal .form-group:after,.form-horizontal .form-group:before,.modal-footer:after,.modal-footer:before,.modal-header:after,.modal-header:before,.nav:after,.nav:before,.navbar-collapse:after,.navbar-collapse:before,.navbar-header:after,.navbar-header:before,.navbar:after,.navbar:before,.pager:after,.pager:before,.panel-body:after,.panel-body:before,.row:after,.row:before{display:table;content:" "}.btn-group-vertical>.btn-group:after,.btn-toolbar:after,.clearfix:after,.container-fluid:after,.container:after,.dl-horizontal dd:after,.form-horizontal .form-group:after,.modal-footer:after,.modal-header:after,.nav:after,.navbar-collapse:after,.navbar-header:after,.navbar:after,.pager:after,.panel-body:after,.row:after{clear:both}.center-block{display:block;margin-right:auto;margin-left:auto}.pull-right{float:right!important}.pull-left{float:left!important}.hide{display:none!important}.show{display:block!important}.invisible{visibility:hidden}.text-hide{font:0/0 a;color:transparent;text-shadow:none;background-color:transparent;border:0}.hidden{display:none!important}.affix{position:fixed}@-ms-viewport{width:device-width}.visible-lg,.visible-md,.visible-sm,.visible-xs{display:none!important}.visible-lg-block,.visible-lg-inline,.visible-lg-inline-block,.visible-md-block,.visible-md-inline,.visible-md-inline-block,.visible-sm-block,.visible-sm-inline,.visible-sm-inline-block,.visible-xs-block,.visible-xs-inline,.visible-xs-inline-block{display:none!important}@media (max-width:767px){.visible-xs{display:block!important}table.visible-xs{display:table!important}tr.visible-xs{display:table-row!important}td.visible-xs,th.visible-xs{display:table-cell!important}}@media (max-width:767px){.visible-xs-block{display:block!important}}@media (max-width:767px){.visible-xs-inline{display:inline!important}}@media (max-width:767px){.visible-xs-inline-block{display:inline-block!important}}@media (min-width:768px) and (max-width:991px){.visible-sm{display:block!important}table.visible-sm{display:table!important}tr.visible-sm{display:table-row!important}td.visible-sm,th.visible-sm{display:table-cell!important}}@media (min-width:768px) and (max-width:991px){.visible-sm-block{display:block!important}}@media (min-width:768px) and (max-width:991px){.visible-sm-inline{display:inline!important}}@media (min-width:768px) and (max-width:991px){.visible-sm-inline-block{display:inline-block!important}}@media (min-width:992px) and (max-width:1199px){.visible-md{display:block!important}table.visible-md{display:table!important}tr.visible-md{display:table-row!important}td.visible-md,th.visible-md{display:table-cell!important}}@media (min-width:992px) and (max-width:1199px){.visible-md-block{display:block!important}}@media (min-width:992px) and (max-width:1199px){.visible-md-inline{display:inline!important}}@media (min-width:992px) and (max-width:1199px){.visible-md-inline-block{display:inline-block!important}}@media (min-width:1200px){.visible-lg{display:block!important}table.visible-lg{display:table!important}tr.visible-lg{display:table-row!important}td.visible-lg,th.visible-lg{display:table-cell!important}}@media (min-width:1200px){.visible-lg-block{display:block!important}}@media (min-width:1200px){.visible-lg-inline{display:inline!important}}@media (min-width:1200px){.visible-lg-inline-block{display:inline-block!important}}@media (max-width:767px){.hidden-xs{display:none!important}}@media (min-width:768px) and (max-width:991px){.hidden-sm{display:none!important}}@media (min-width:992px) and (max-width:1199px){.hidden-md{display:none!important}}@media (min-width:1200px){.hidden-lg{display:none!important}}.visible-print{display:none!important}@media print{.visible-print{display:block!important}table.visible-print{display:table!important}tr.visible-print{display:table-row!important}td.visible-print,th.visible-print{display:table-cell!important}}.visible-print-block{display:none!important}@media print{.visible-print-block{display:block!important}}.visible-print-inline{display:none!important}@media print{.visible-print-inline{display:inline!important}}.visible-print-inline-block{display:none!important}@media print{.visible-print-inline-block{display:inline-block!important}}@media print{.hidden-print{display:none!important}} /*# sourceMappingURL=bootstrap.min.css.map */ \ No newline at end of file diff --git a/admin/static/css/glyphicon.css b/admin/static/css/glyphicon.css deleted file mode 100644 index 1bf9e4de..00000000 --- a/admin/static/css/glyphicon.css +++ /dev/null @@ -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"; -} diff --git a/admin/static/css/slate.min.css b/admin/static/css/slate.min.css new file mode 100644 index 00000000..af7e2033 --- /dev/null +++ b/admin/static/css/slate.min.css @@ -0,0 +1,11 @@ +/*! + * bootswatch v3.3.6 + * Homepage: http://bootswatch.com + * Copyright 2012-2015 Thomas Park + * Licensed under MIT + * Based on Bootstrap +*//*! + * Bootstrap v3.3.6 (http://getbootstrap.com) + * Copyright 2011-2015 Twitter, Inc. + * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) + *//*! normalize.css v3.0.3 | MIT License | github.com/necolas/normalize.css */html{font-family:sans-serif;-ms-text-size-adjust:100%;-webkit-text-size-adjust:100%}body{margin:0}article,aside,details,figcaption,figure,footer,header,hgroup,main,menu,nav,section,summary{display:block}audio,canvas,progress,video{display:inline-block;vertical-align:baseline}audio:not([controls]){display:none;height:0}[hidden],template{display:none}a{background-color:transparent}a:active,a:hover{outline:0}abbr[title]{border-bottom:1px dotted}b,strong{font-weight:bold}dfn{font-style:italic}h1{font-size:2em;margin:0.67em 0}mark{background:#ff0;color:#000}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sup{top:-0.5em}sub{bottom:-0.25em}img{border:0}svg:not(:root){overflow:hidden}figure{margin:1em 40px}hr{-webkit-box-sizing:content-box;-moz-box-sizing:content-box;box-sizing:content-box;height:0}pre{overflow:auto}code,kbd,pre,samp{font-family:monospace, monospace;font-size:1em}button,input,optgroup,select,textarea{color:inherit;font:inherit;margin:0}button{overflow:visible}button,select{text-transform:none}button,html input[type="button"],input[type="reset"],input[type="submit"]{-webkit-appearance:button;cursor:pointer}button[disabled],html input[disabled]{cursor:default}button::-moz-focus-inner,input::-moz-focus-inner{border:0;padding:0}input{line-height:normal}input[type="checkbox"],input[type="radio"]{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;padding:0}input[type="number"]::-webkit-inner-spin-button,input[type="number"]::-webkit-outer-spin-button{height:auto}input[type="search"]{-webkit-appearance:textfield;-webkit-box-sizing:content-box;-moz-box-sizing:content-box;box-sizing:content-box}input[type="search"]::-webkit-search-cancel-button,input[type="search"]::-webkit-search-decoration{-webkit-appearance:none}fieldset{border:1px solid #c0c0c0;margin:0 2px;padding:0.35em 0.625em 0.75em}legend{border:0;padding:0}textarea{overflow:auto}optgroup{font-weight:bold}table{border-collapse:collapse;border-spacing:0}td,th{padding:0}/*! Source: https://github.com/h5bp/html5-boilerplate/blob/master/src/css/main.css */@media print{*,*:before,*:after{background:transparent !important;color:#000 !important;-webkit-box-shadow:none !important;box-shadow:none !important;text-shadow:none !important}a,a:visited{text-decoration:underline}a[href]:after{content:" (" attr(href) ")"}abbr[title]:after{content:" (" attr(title) ")"}a[href^="#"]:after,a[href^="javascript:"]:after{content:""}pre,blockquote{border:1px solid #999;page-break-inside:avoid}thead{display:table-header-group}tr,img{page-break-inside:avoid}img{max-width:100% !important}p,h2,h3{orphans:3;widows:3}h2,h3{page-break-after:avoid}.navbar{display:none}.btn>.caret,.dropup>.btn>.caret{border-top-color:#000 !important}.label{border:1px solid #000}.table{border-collapse:collapse !important}.table td,.table th{background-color:#fff !important}.table-bordered th,.table-bordered td{border:1px solid #ddd !important}}@font-face{font-family:'Glyphicons Halflings';src:url('../fonts/glyphicons-halflings-regular.eot');src:url('../fonts/glyphicons-halflings-regular.eot?#iefix') format('embedded-opentype'),url('../fonts/glyphicons-halflings-regular.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"}*{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}*:before,*:after{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}html{font-size:10px;-webkit-tap-highlight-color:rgba(0,0,0,0)}body{font-family:"Helvetica Neue",Helvetica,Arial,sans-serif;font-size:14px;line-height:1.42857143;color:#c8c8c8;background-color:#272b30}input,button,select,textarea{font-family:inherit;font-size:inherit;line-height:inherit}a{color:#ffffff;text-decoration:none}a:hover,a:focus{color:#ffffff;text-decoration:underline}a:focus{outline:thin dotted;outline:5px auto -webkit-focus-ring-color;outline-offset:-2px}figure{margin:0}img{vertical-align:middle}.img-responsive,.thumbnail>img,.thumbnail a>img,.carousel-inner>.item>img,.carousel-inner>.item>a>img{display:block;max-width:100%;height:auto}.img-rounded{border-radius:6px}.img-thumbnail{padding:4px;line-height:1.42857143;background-color:#1c1e22;border:1px solid #0c0d0e;border-radius:4px;-webkit-transition:all .2s ease-in-out;-o-transition:all .2s ease-in-out;transition:all .2s ease-in-out;display:inline-block;max-width:100%;height:auto}.img-circle{border-radius:50%}hr{margin-top:20px;margin-bottom:20px;border:0;border-top:1px solid #1c1e22}.sr-only{position:absolute;width:1px;height:1px;margin:-1px;padding:0;overflow:hidden;clip:rect(0, 0, 0, 0);border:0}.sr-only-focusable:active,.sr-only-focusable:focus{position:static;width:auto;height:auto;margin:0;overflow:visible;clip:auto}[role="button"]{cursor:pointer}h1,h2,h3,h4,h5,h6,.h1,.h2,.h3,.h4,.h5,.h6{font-family:"Helvetica Neue",Helvetica,Arial,sans-serif;font-weight:500;line-height:1.1;color:inherit}h1 small,h2 small,h3 small,h4 small,h5 small,h6 small,.h1 small,.h2 small,.h3 small,.h4 small,.h5 small,.h6 small,h1 .small,h2 .small,h3 .small,h4 .small,h5 .small,h6 .small,.h1 .small,.h2 .small,.h3 .small,.h4 .small,.h5 .small,.h6 .small{font-weight:normal;line-height:1;color:#7a8288}h1,.h1,h2,.h2,h3,.h3{margin-top:20px;margin-bottom:10px}h1 small,.h1 small,h2 small,.h2 small,h3 small,.h3 small,h1 .small,.h1 .small,h2 .small,.h2 .small,h3 .small,.h3 .small{font-size:65%}h4,.h4,h5,.h5,h6,.h6{margin-top:10px;margin-bottom:10px}h4 small,.h4 small,h5 small,.h5 small,h6 small,.h6 small,h4 .small,.h4 .small,h5 .small,.h5 .small,h6 .small,.h6 .small{font-size:75%}h1,.h1{font-size:36px}h2,.h2{font-size:30px}h3,.h3{font-size:24px}h4,.h4{font-size:18px}h5,.h5{font-size:14px}h6,.h6{font-size:12px}p{margin:0 0 10px}.lead{margin-bottom:20px;font-size:16px;font-weight:300;line-height:1.4}@media (min-width:768px){.lead{font-size:21px}}small,.small{font-size:85%}mark,.mark{background-color:#f89406;padding:.2em}.text-left{text-align:left}.text-right{text-align:right}.text-center{text-align:center}.text-justify{text-align:justify}.text-nowrap{white-space:nowrap}.text-lowercase{text-transform:lowercase}.text-uppercase{text-transform:uppercase}.text-capitalize{text-transform:capitalize}.text-muted{color:#7a8288}.text-primary{color:#7a8288}a.text-primary:hover,a.text-primary:focus{color:#62686d}.text-success{color:#ffffff}a.text-success:hover,a.text-success:focus{color:#e6e6e6}.text-info{color:#ffffff}a.text-info:hover,a.text-info:focus{color:#e6e6e6}.text-warning{color:#ffffff}a.text-warning:hover,a.text-warning:focus{color:#e6e6e6}.text-danger{color:#ffffff}a.text-danger:hover,a.text-danger:focus{color:#e6e6e6}.bg-primary{color:#fff;background-color:#7a8288}a.bg-primary:hover,a.bg-primary:focus{background-color:#62686d}.bg-success{background-color:#62c462}a.bg-success:hover,a.bg-success:focus{background-color:#42b142}.bg-info{background-color:#5bc0de}a.bg-info:hover,a.bg-info:focus{background-color:#31b0d5}.bg-warning{background-color:#f89406}a.bg-warning:hover,a.bg-warning:focus{background-color:#c67605}.bg-danger{background-color:#ee5f5b}a.bg-danger:hover,a.bg-danger:focus{background-color:#e9322d}.page-header{padding-bottom:9px;margin:40px 0 20px;border-bottom:1px solid #1c1e22}ul,ol{margin-top:0;margin-bottom:10px}ul ul,ol ul,ul ol,ol ol{margin-bottom:0}.list-unstyled{padding-left:0;list-style:none}.list-inline{padding-left:0;list-style:none;margin-left:-5px}.list-inline>li{display:inline-block;padding-left:5px;padding-right:5px}dl{margin-top:0;margin-bottom:20px}dt,dd{line-height:1.42857143}dt{font-weight:bold}dd{margin-left:0}@media (min-width:768px){.dl-horizontal dt{float:left;width:160px;clear:left;text-align:right;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.dl-horizontal dd{margin-left:180px}}abbr[title],abbr[data-original-title]{cursor:help;border-bottom:1px dotted #7a8288}.initialism{font-size:90%;text-transform:uppercase}blockquote{padding:10px 20px;margin:0 0 20px;font-size:17.5px;border-left:5px solid #7a8288}blockquote p:last-child,blockquote ul:last-child,blockquote ol:last-child{margin-bottom:0}blockquote footer,blockquote small,blockquote .small{display:block;font-size:80%;line-height:1.42857143;color:#7a8288}blockquote footer:before,blockquote small:before,blockquote .small:before{content:'\2014 \00A0'}.blockquote-reverse,blockquote.pull-right{padding-right:15px;padding-left:0;border-right:5px solid #7a8288;border-left:0;text-align:right}.blockquote-reverse footer:before,blockquote.pull-right footer:before,.blockquote-reverse small:before,blockquote.pull-right small:before,.blockquote-reverse .small:before,blockquote.pull-right .small:before{content:''}.blockquote-reverse footer:after,blockquote.pull-right footer:after,.blockquote-reverse small:after,blockquote.pull-right small:after,.blockquote-reverse .small:after,blockquote.pull-right .small:after{content:'\00A0 \2014'}address{margin-bottom:20px;font-style:normal;line-height:1.42857143}code,kbd,pre,samp{font-family:Menlo,Monaco,Consolas,"Courier New",monospace}code{padding:2px 4px;font-size:90%;color:#c7254e;background-color:#f9f2f4;border-radius:4px}kbd{padding:2px 4px;font-size:90%;color:#ffffff;background-color:#333333;border-radius:3px;-webkit-box-shadow:inset 0 -1px 0 rgba(0,0,0,0.25);box-shadow:inset 0 -1px 0 rgba(0,0,0,0.25)}kbd kbd{padding:0;font-size:100%;font-weight:bold;-webkit-box-shadow:none;box-shadow:none}pre{display:block;padding:9.5px;margin:0 0 10px;font-size:13px;line-height:1.42857143;word-break:break-all;word-wrap:break-word;color:#3a3f44;background-color:#f5f5f5;border:1px solid #cccccc;border-radius:4px}pre code{padding:0;font-size:inherit;color:inherit;white-space:pre-wrap;background-color:transparent;border-radius:0}.pre-scrollable{max-height:340px;overflow-y:scroll}.container{margin-right:auto;margin-left:auto;padding-left:15px;padding-right:15px}@media (min-width:768px){.container{width:750px}}@media (min-width:992px){.container{width:970px}}@media (min-width:1200px){.container{width:1170px}}.container-fluid{margin-right:auto;margin-left:auto;padding-left:15px;padding-right:15px}.row{margin-left:-15px;margin-right:-15px}.col-xs-1,.col-sm-1,.col-md-1,.col-lg-1,.col-xs-2,.col-sm-2,.col-md-2,.col-lg-2,.col-xs-3,.col-sm-3,.col-md-3,.col-lg-3,.col-xs-4,.col-sm-4,.col-md-4,.col-lg-4,.col-xs-5,.col-sm-5,.col-md-5,.col-lg-5,.col-xs-6,.col-sm-6,.col-md-6,.col-lg-6,.col-xs-7,.col-sm-7,.col-md-7,.col-lg-7,.col-xs-8,.col-sm-8,.col-md-8,.col-lg-8,.col-xs-9,.col-sm-9,.col-md-9,.col-lg-9,.col-xs-10,.col-sm-10,.col-md-10,.col-lg-10,.col-xs-11,.col-sm-11,.col-md-11,.col-lg-11,.col-xs-12,.col-sm-12,.col-md-12,.col-lg-12{position:relative;min-height:1px;padding-left:15px;padding-right:15px}.col-xs-1,.col-xs-2,.col-xs-3,.col-xs-4,.col-xs-5,.col-xs-6,.col-xs-7,.col-xs-8,.col-xs-9,.col-xs-10,.col-xs-11,.col-xs-12{float:left}.col-xs-12{width:100%}.col-xs-11{width:91.66666667%}.col-xs-10{width:83.33333333%}.col-xs-9{width:75%}.col-xs-8{width:66.66666667%}.col-xs-7{width:58.33333333%}.col-xs-6{width:50%}.col-xs-5{width:41.66666667%}.col-xs-4{width:33.33333333%}.col-xs-3{width:25%}.col-xs-2{width:16.66666667%}.col-xs-1{width:8.33333333%}.col-xs-pull-12{right:100%}.col-xs-pull-11{right:91.66666667%}.col-xs-pull-10{right:83.33333333%}.col-xs-pull-9{right:75%}.col-xs-pull-8{right:66.66666667%}.col-xs-pull-7{right:58.33333333%}.col-xs-pull-6{right:50%}.col-xs-pull-5{right:41.66666667%}.col-xs-pull-4{right:33.33333333%}.col-xs-pull-3{right:25%}.col-xs-pull-2{right:16.66666667%}.col-xs-pull-1{right:8.33333333%}.col-xs-pull-0{right:auto}.col-xs-push-12{left:100%}.col-xs-push-11{left:91.66666667%}.col-xs-push-10{left:83.33333333%}.col-xs-push-9{left:75%}.col-xs-push-8{left:66.66666667%}.col-xs-push-7{left:58.33333333%}.col-xs-push-6{left:50%}.col-xs-push-5{left:41.66666667%}.col-xs-push-4{left:33.33333333%}.col-xs-push-3{left:25%}.col-xs-push-2{left:16.66666667%}.col-xs-push-1{left:8.33333333%}.col-xs-push-0{left:auto}.col-xs-offset-12{margin-left:100%}.col-xs-offset-11{margin-left:91.66666667%}.col-xs-offset-10{margin-left:83.33333333%}.col-xs-offset-9{margin-left:75%}.col-xs-offset-8{margin-left:66.66666667%}.col-xs-offset-7{margin-left:58.33333333%}.col-xs-offset-6{margin-left:50%}.col-xs-offset-5{margin-left:41.66666667%}.col-xs-offset-4{margin-left:33.33333333%}.col-xs-offset-3{margin-left:25%}.col-xs-offset-2{margin-left:16.66666667%}.col-xs-offset-1{margin-left:8.33333333%}.col-xs-offset-0{margin-left:0%}@media (min-width:768px){.col-sm-1,.col-sm-2,.col-sm-3,.col-sm-4,.col-sm-5,.col-sm-6,.col-sm-7,.col-sm-8,.col-sm-9,.col-sm-10,.col-sm-11,.col-sm-12{float:left}.col-sm-12{width:100%}.col-sm-11{width:91.66666667%}.col-sm-10{width:83.33333333%}.col-sm-9{width:75%}.col-sm-8{width:66.66666667%}.col-sm-7{width:58.33333333%}.col-sm-6{width:50%}.col-sm-5{width:41.66666667%}.col-sm-4{width:33.33333333%}.col-sm-3{width:25%}.col-sm-2{width:16.66666667%}.col-sm-1{width:8.33333333%}.col-sm-pull-12{right:100%}.col-sm-pull-11{right:91.66666667%}.col-sm-pull-10{right:83.33333333%}.col-sm-pull-9{right:75%}.col-sm-pull-8{right:66.66666667%}.col-sm-pull-7{right:58.33333333%}.col-sm-pull-6{right:50%}.col-sm-pull-5{right:41.66666667%}.col-sm-pull-4{right:33.33333333%}.col-sm-pull-3{right:25%}.col-sm-pull-2{right:16.66666667%}.col-sm-pull-1{right:8.33333333%}.col-sm-pull-0{right:auto}.col-sm-push-12{left:100%}.col-sm-push-11{left:91.66666667%}.col-sm-push-10{left:83.33333333%}.col-sm-push-9{left:75%}.col-sm-push-8{left:66.66666667%}.col-sm-push-7{left:58.33333333%}.col-sm-push-6{left:50%}.col-sm-push-5{left:41.66666667%}.col-sm-push-4{left:33.33333333%}.col-sm-push-3{left:25%}.col-sm-push-2{left:16.66666667%}.col-sm-push-1{left:8.33333333%}.col-sm-push-0{left:auto}.col-sm-offset-12{margin-left:100%}.col-sm-offset-11{margin-left:91.66666667%}.col-sm-offset-10{margin-left:83.33333333%}.col-sm-offset-9{margin-left:75%}.col-sm-offset-8{margin-left:66.66666667%}.col-sm-offset-7{margin-left:58.33333333%}.col-sm-offset-6{margin-left:50%}.col-sm-offset-5{margin-left:41.66666667%}.col-sm-offset-4{margin-left:33.33333333%}.col-sm-offset-3{margin-left:25%}.col-sm-offset-2{margin-left:16.66666667%}.col-sm-offset-1{margin-left:8.33333333%}.col-sm-offset-0{margin-left:0%}}@media (min-width:992px){.col-md-1,.col-md-2,.col-md-3,.col-md-4,.col-md-5,.col-md-6,.col-md-7,.col-md-8,.col-md-9,.col-md-10,.col-md-11,.col-md-12{float:left}.col-md-12{width:100%}.col-md-11{width:91.66666667%}.col-md-10{width:83.33333333%}.col-md-9{width:75%}.col-md-8{width:66.66666667%}.col-md-7{width:58.33333333%}.col-md-6{width:50%}.col-md-5{width:41.66666667%}.col-md-4{width:33.33333333%}.col-md-3{width:25%}.col-md-2{width:16.66666667%}.col-md-1{width:8.33333333%}.col-md-pull-12{right:100%}.col-md-pull-11{right:91.66666667%}.col-md-pull-10{right:83.33333333%}.col-md-pull-9{right:75%}.col-md-pull-8{right:66.66666667%}.col-md-pull-7{right:58.33333333%}.col-md-pull-6{right:50%}.col-md-pull-5{right:41.66666667%}.col-md-pull-4{right:33.33333333%}.col-md-pull-3{right:25%}.col-md-pull-2{right:16.66666667%}.col-md-pull-1{right:8.33333333%}.col-md-pull-0{right:auto}.col-md-push-12{left:100%}.col-md-push-11{left:91.66666667%}.col-md-push-10{left:83.33333333%}.col-md-push-9{left:75%}.col-md-push-8{left:66.66666667%}.col-md-push-7{left:58.33333333%}.col-md-push-6{left:50%}.col-md-push-5{left:41.66666667%}.col-md-push-4{left:33.33333333%}.col-md-push-3{left:25%}.col-md-push-2{left:16.66666667%}.col-md-push-1{left:8.33333333%}.col-md-push-0{left:auto}.col-md-offset-12{margin-left:100%}.col-md-offset-11{margin-left:91.66666667%}.col-md-offset-10{margin-left:83.33333333%}.col-md-offset-9{margin-left:75%}.col-md-offset-8{margin-left:66.66666667%}.col-md-offset-7{margin-left:58.33333333%}.col-md-offset-6{margin-left:50%}.col-md-offset-5{margin-left:41.66666667%}.col-md-offset-4{margin-left:33.33333333%}.col-md-offset-3{margin-left:25%}.col-md-offset-2{margin-left:16.66666667%}.col-md-offset-1{margin-left:8.33333333%}.col-md-offset-0{margin-left:0%}}@media (min-width:1200px){.col-lg-1,.col-lg-2,.col-lg-3,.col-lg-4,.col-lg-5,.col-lg-6,.col-lg-7,.col-lg-8,.col-lg-9,.col-lg-10,.col-lg-11,.col-lg-12{float:left}.col-lg-12{width:100%}.col-lg-11{width:91.66666667%}.col-lg-10{width:83.33333333%}.col-lg-9{width:75%}.col-lg-8{width:66.66666667%}.col-lg-7{width:58.33333333%}.col-lg-6{width:50%}.col-lg-5{width:41.66666667%}.col-lg-4{width:33.33333333%}.col-lg-3{width:25%}.col-lg-2{width:16.66666667%}.col-lg-1{width:8.33333333%}.col-lg-pull-12{right:100%}.col-lg-pull-11{right:91.66666667%}.col-lg-pull-10{right:83.33333333%}.col-lg-pull-9{right:75%}.col-lg-pull-8{right:66.66666667%}.col-lg-pull-7{right:58.33333333%}.col-lg-pull-6{right:50%}.col-lg-pull-5{right:41.66666667%}.col-lg-pull-4{right:33.33333333%}.col-lg-pull-3{right:25%}.col-lg-pull-2{right:16.66666667%}.col-lg-pull-1{right:8.33333333%}.col-lg-pull-0{right:auto}.col-lg-push-12{left:100%}.col-lg-push-11{left:91.66666667%}.col-lg-push-10{left:83.33333333%}.col-lg-push-9{left:75%}.col-lg-push-8{left:66.66666667%}.col-lg-push-7{left:58.33333333%}.col-lg-push-6{left:50%}.col-lg-push-5{left:41.66666667%}.col-lg-push-4{left:33.33333333%}.col-lg-push-3{left:25%}.col-lg-push-2{left:16.66666667%}.col-lg-push-1{left:8.33333333%}.col-lg-push-0{left:auto}.col-lg-offset-12{margin-left:100%}.col-lg-offset-11{margin-left:91.66666667%}.col-lg-offset-10{margin-left:83.33333333%}.col-lg-offset-9{margin-left:75%}.col-lg-offset-8{margin-left:66.66666667%}.col-lg-offset-7{margin-left:58.33333333%}.col-lg-offset-6{margin-left:50%}.col-lg-offset-5{margin-left:41.66666667%}.col-lg-offset-4{margin-left:33.33333333%}.col-lg-offset-3{margin-left:25%}.col-lg-offset-2{margin-left:16.66666667%}.col-lg-offset-1{margin-left:8.33333333%}.col-lg-offset-0{margin-left:0%}}table{background-color:#2e3338}caption{padding-top:8px;padding-bottom:8px;color:#7a8288;text-align:left}th{text-align:left}.table{width:100%;max-width:100%;margin-bottom:20px}.table>thead>tr>th,.table>tbody>tr>th,.table>tfoot>tr>th,.table>thead>tr>td,.table>tbody>tr>td,.table>tfoot>tr>td{padding:8px;line-height:1.42857143;vertical-align:top;border-top:1px solid #1c1e22}.table>thead>tr>th{vertical-align:bottom;border-bottom:2px solid #1c1e22}.table>caption+thead>tr:first-child>th,.table>colgroup+thead>tr:first-child>th,.table>thead:first-child>tr:first-child>th,.table>caption+thead>tr:first-child>td,.table>colgroup+thead>tr:first-child>td,.table>thead:first-child>tr:first-child>td{border-top:0}.table>tbody+tbody{border-top:2px solid #1c1e22}.table .table{background-color:#272b30}.table-condensed>thead>tr>th,.table-condensed>tbody>tr>th,.table-condensed>tfoot>tr>th,.table-condensed>thead>tr>td,.table-condensed>tbody>tr>td,.table-condensed>tfoot>tr>td{padding:5px}.table-bordered{border:1px solid #1c1e22}.table-bordered>thead>tr>th,.table-bordered>tbody>tr>th,.table-bordered>tfoot>tr>th,.table-bordered>thead>tr>td,.table-bordered>tbody>tr>td,.table-bordered>tfoot>tr>td{border:1px solid #1c1e22}.table-bordered>thead>tr>th,.table-bordered>thead>tr>td{border-bottom-width:2px}.table-striped>tbody>tr:nth-of-type(odd){background-color:#353a41}.table-hover>tbody>tr:hover{background-color:#49515a}table col[class*="col-"]{position:static;float:none;display:table-column}table td[class*="col-"],table th[class*="col-"]{position:static;float:none;display:table-cell}.table>thead>tr>td.active,.table>tbody>tr>td.active,.table>tfoot>tr>td.active,.table>thead>tr>th.active,.table>tbody>tr>th.active,.table>tfoot>tr>th.active,.table>thead>tr.active>td,.table>tbody>tr.active>td,.table>tfoot>tr.active>td,.table>thead>tr.active>th,.table>tbody>tr.active>th,.table>tfoot>tr.active>th{background-color:#49515a}.table-hover>tbody>tr>td.active:hover,.table-hover>tbody>tr>th.active:hover,.table-hover>tbody>tr.active:hover>td,.table-hover>tbody>tr:hover>.active,.table-hover>tbody>tr.active:hover>th{background-color:#3e444c}.table>thead>tr>td.success,.table>tbody>tr>td.success,.table>tfoot>tr>td.success,.table>thead>tr>th.success,.table>tbody>tr>th.success,.table>tfoot>tr>th.success,.table>thead>tr.success>td,.table>tbody>tr.success>td,.table>tfoot>tr.success>td,.table>thead>tr.success>th,.table>tbody>tr.success>th,.table>tfoot>tr.success>th{background-color:#62c462}.table-hover>tbody>tr>td.success:hover,.table-hover>tbody>tr>th.success:hover,.table-hover>tbody>tr.success:hover>td,.table-hover>tbody>tr:hover>.success,.table-hover>tbody>tr.success:hover>th{background-color:#4fbd4f}.table>thead>tr>td.info,.table>tbody>tr>td.info,.table>tfoot>tr>td.info,.table>thead>tr>th.info,.table>tbody>tr>th.info,.table>tfoot>tr>th.info,.table>thead>tr.info>td,.table>tbody>tr.info>td,.table>tfoot>tr.info>td,.table>thead>tr.info>th,.table>tbody>tr.info>th,.table>tfoot>tr.info>th{background-color:#5bc0de}.table-hover>tbody>tr>td.info:hover,.table-hover>tbody>tr>th.info:hover,.table-hover>tbody>tr.info:hover>td,.table-hover>tbody>tr:hover>.info,.table-hover>tbody>tr.info:hover>th{background-color:#46b8da}.table>thead>tr>td.warning,.table>tbody>tr>td.warning,.table>tfoot>tr>td.warning,.table>thead>tr>th.warning,.table>tbody>tr>th.warning,.table>tfoot>tr>th.warning,.table>thead>tr.warning>td,.table>tbody>tr.warning>td,.table>tfoot>tr.warning>td,.table>thead>tr.warning>th,.table>tbody>tr.warning>th,.table>tfoot>tr.warning>th{background-color:#f89406}.table-hover>tbody>tr>td.warning:hover,.table-hover>tbody>tr>th.warning:hover,.table-hover>tbody>tr.warning:hover>td,.table-hover>tbody>tr:hover>.warning,.table-hover>tbody>tr.warning:hover>th{background-color:#df8505}.table>thead>tr>td.danger,.table>tbody>tr>td.danger,.table>tfoot>tr>td.danger,.table>thead>tr>th.danger,.table>tbody>tr>th.danger,.table>tfoot>tr>th.danger,.table>thead>tr.danger>td,.table>tbody>tr.danger>td,.table>tfoot>tr.danger>td,.table>thead>tr.danger>th,.table>tbody>tr.danger>th,.table>tfoot>tr.danger>th{background-color:#ee5f5b}.table-hover>tbody>tr>td.danger:hover,.table-hover>tbody>tr>th.danger:hover,.table-hover>tbody>tr.danger:hover>td,.table-hover>tbody>tr:hover>.danger,.table-hover>tbody>tr.danger:hover>th{background-color:#ec4844}.table-responsive{overflow-x:auto;min-height:0.01%}@media screen and (max-width:767px){.table-responsive{width:100%;margin-bottom:15px;overflow-y:hidden;-ms-overflow-style:-ms-autohiding-scrollbar;border:1px solid #1c1e22}.table-responsive>.table{margin-bottom:0}.table-responsive>.table>thead>tr>th,.table-responsive>.table>tbody>tr>th,.table-responsive>.table>tfoot>tr>th,.table-responsive>.table>thead>tr>td,.table-responsive>.table>tbody>tr>td,.table-responsive>.table>tfoot>tr>td{white-space:nowrap}.table-responsive>.table-bordered{border:0}.table-responsive>.table-bordered>thead>tr>th:first-child,.table-responsive>.table-bordered>tbody>tr>th:first-child,.table-responsive>.table-bordered>tfoot>tr>th:first-child,.table-responsive>.table-bordered>thead>tr>td:first-child,.table-responsive>.table-bordered>tbody>tr>td:first-child,.table-responsive>.table-bordered>tfoot>tr>td:first-child{border-left:0}.table-responsive>.table-bordered>thead>tr>th:last-child,.table-responsive>.table-bordered>tbody>tr>th:last-child,.table-responsive>.table-bordered>tfoot>tr>th:last-child,.table-responsive>.table-bordered>thead>tr>td:last-child,.table-responsive>.table-bordered>tbody>tr>td:last-child,.table-responsive>.table-bordered>tfoot>tr>td:last-child{border-right:0}.table-responsive>.table-bordered>tbody>tr:last-child>th,.table-responsive>.table-bordered>tfoot>tr:last-child>th,.table-responsive>.table-bordered>tbody>tr:last-child>td,.table-responsive>.table-bordered>tfoot>tr:last-child>td{border-bottom:0}}fieldset{padding:0;margin:0;border:0;min-width:0}legend{display:block;width:100%;padding:0;margin-bottom:20px;font-size:21px;line-height:inherit;color:#c8c8c8;border:0;border-bottom:1px solid #1c1e22}label{display:inline-block;max-width:100%;margin-bottom:5px;font-weight:bold}input[type="search"]{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}input[type="radio"],input[type="checkbox"]{margin:4px 0 0;margin-top:1px \9;line-height:normal}input[type="file"]{display:block}input[type="range"]{display:block;width:100%}select[multiple],select[size]{height:auto}input[type="file"]:focus,input[type="radio"]:focus,input[type="checkbox"]:focus{outline:thin dotted;outline:5px auto -webkit-focus-ring-color;outline-offset:-2px}output{display:block;padding-top:9px;font-size:14px;line-height:1.42857143;color:#272b30}.form-control{display:block;width:100%;height:38px;padding:8px 12px;font-size:14px;line-height:1.42857143;color:#272b30;background-color:#ffffff;background-image:none;border:1px solid #cccccc;border-radius:4px;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075);box-shadow:inset 0 1px 1px rgba(0,0,0,0.075);-webkit-transition:border-color ease-in-out .15s,-webkit-box-shadow ease-in-out .15s;-o-transition:border-color ease-in-out .15s,box-shadow ease-in-out .15s;transition:border-color ease-in-out .15s,box-shadow ease-in-out .15s}.form-control:focus{border-color:#66afe9;outline:0;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 8px rgba(102,175,233,0.6);box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 8px rgba(102,175,233,0.6)}.form-control::-moz-placeholder{color:#7a8288;opacity:1}.form-control:-ms-input-placeholder{color:#7a8288}.form-control::-webkit-input-placeholder{color:#7a8288}.form-control::-ms-expand{border:0;background-color:transparent}.form-control[disabled],.form-control[readonly],fieldset[disabled] .form-control{background-color:#999999;opacity:1}.form-control[disabled],fieldset[disabled] .form-control{cursor:not-allowed}textarea.form-control{height:auto}input[type="search"]{-webkit-appearance:none}@media screen and (-webkit-min-device-pixel-ratio:0){input[type="date"].form-control,input[type="time"].form-control,input[type="datetime-local"].form-control,input[type="month"].form-control{line-height:38px}input[type="date"].input-sm,input[type="time"].input-sm,input[type="datetime-local"].input-sm,input[type="month"].input-sm,.input-group-sm input[type="date"],.input-group-sm input[type="time"],.input-group-sm input[type="datetime-local"],.input-group-sm input[type="month"]{line-height:30px}input[type="date"].input-lg,input[type="time"].input-lg,input[type="datetime-local"].input-lg,input[type="month"].input-lg,.input-group-lg input[type="date"],.input-group-lg input[type="time"],.input-group-lg input[type="datetime-local"],.input-group-lg input[type="month"]{line-height:54px}}.form-group{margin-bottom:15px}.radio,.checkbox{position:relative;display:block;margin-top:10px;margin-bottom:10px}.radio label,.checkbox label{min-height:20px;padding-left:20px;margin-bottom:0;font-weight:normal;cursor:pointer}.radio input[type="radio"],.radio-inline input[type="radio"],.checkbox input[type="checkbox"],.checkbox-inline input[type="checkbox"]{position:absolute;margin-left:-20px;margin-top:4px \9}.radio+.radio,.checkbox+.checkbox{margin-top:-5px}.radio-inline,.checkbox-inline{position:relative;display:inline-block;padding-left:20px;margin-bottom:0;vertical-align:middle;font-weight:normal;cursor:pointer}.radio-inline+.radio-inline,.checkbox-inline+.checkbox-inline{margin-top:0;margin-left:10px}input[type="radio"][disabled],input[type="checkbox"][disabled],input[type="radio"].disabled,input[type="checkbox"].disabled,fieldset[disabled] input[type="radio"],fieldset[disabled] input[type="checkbox"]{cursor:not-allowed}.radio-inline.disabled,.checkbox-inline.disabled,fieldset[disabled] .radio-inline,fieldset[disabled] .checkbox-inline{cursor:not-allowed}.radio.disabled label,.checkbox.disabled label,fieldset[disabled] .radio label,fieldset[disabled] .checkbox label{cursor:not-allowed}.form-control-static{padding-top:9px;padding-bottom:9px;margin-bottom:0;min-height:34px}.form-control-static.input-lg,.form-control-static.input-sm{padding-left:0;padding-right:0}.input-sm{height:30px;padding:5px 10px;font-size:12px;line-height:1.5;border-radius:3px}select.input-sm{height:30px;line-height:30px}textarea.input-sm,select[multiple].input-sm{height:auto}.form-group-sm .form-control{height:30px;padding:5px 10px;font-size:12px;line-height:1.5;border-radius:3px}.form-group-sm select.form-control{height:30px;line-height:30px}.form-group-sm textarea.form-control,.form-group-sm select[multiple].form-control{height:auto}.form-group-sm .form-control-static{height:30px;min-height:32px;padding:6px 10px;font-size:12px;line-height:1.5}.input-lg{height:54px;padding:14px 16px;font-size:18px;line-height:1.3333333;border-radius:6px}select.input-lg{height:54px;line-height:54px}textarea.input-lg,select[multiple].input-lg{height:auto}.form-group-lg .form-control{height:54px;padding:14px 16px;font-size:18px;line-height:1.3333333;border-radius:6px}.form-group-lg select.form-control{height:54px;line-height:54px}.form-group-lg textarea.form-control,.form-group-lg select[multiple].form-control{height:auto}.form-group-lg .form-control-static{height:54px;min-height:38px;padding:15px 16px;font-size:18px;line-height:1.3333333}.has-feedback{position:relative}.has-feedback .form-control{padding-right:47.5px}.form-control-feedback{position:absolute;top:0;right:0;z-index:2;display:block;width:38px;height:38px;line-height:38px;text-align:center;pointer-events:none}.input-lg+.form-control-feedback,.input-group-lg+.form-control-feedback,.form-group-lg .form-control+.form-control-feedback{width:54px;height:54px;line-height:54px}.input-sm+.form-control-feedback,.input-group-sm+.form-control-feedback,.form-group-sm .form-control+.form-control-feedback{width:30px;height:30px;line-height:30px}.has-success .help-block,.has-success .control-label,.has-success .radio,.has-success .checkbox,.has-success .radio-inline,.has-success .checkbox-inline,.has-success.radio label,.has-success.checkbox label,.has-success.radio-inline label,.has-success.checkbox-inline label{color:#ffffff}.has-success .form-control{border-color:#ffffff;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075);box-shadow:inset 0 1px 1px rgba(0,0,0,0.075)}.has-success .form-control:focus{border-color:#e6e6e6;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #fff;box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #fff}.has-success .input-group-addon{color:#ffffff;border-color:#ffffff;background-color:#62c462}.has-success .form-control-feedback{color:#ffffff}.has-warning .help-block,.has-warning .control-label,.has-warning .radio,.has-warning .checkbox,.has-warning .radio-inline,.has-warning .checkbox-inline,.has-warning.radio label,.has-warning.checkbox label,.has-warning.radio-inline label,.has-warning.checkbox-inline label{color:#ffffff}.has-warning .form-control{border-color:#ffffff;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075);box-shadow:inset 0 1px 1px rgba(0,0,0,0.075)}.has-warning .form-control:focus{border-color:#e6e6e6;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #fff;box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #fff}.has-warning .input-group-addon{color:#ffffff;border-color:#ffffff;background-color:#f89406}.has-warning .form-control-feedback{color:#ffffff}.has-error .help-block,.has-error .control-label,.has-error .radio,.has-error .checkbox,.has-error .radio-inline,.has-error .checkbox-inline,.has-error.radio label,.has-error.checkbox label,.has-error.radio-inline label,.has-error.checkbox-inline label{color:#ffffff}.has-error .form-control{border-color:#ffffff;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075);box-shadow:inset 0 1px 1px rgba(0,0,0,0.075)}.has-error .form-control:focus{border-color:#e6e6e6;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #fff;box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #fff}.has-error .input-group-addon{color:#ffffff;border-color:#ffffff;background-color:#ee5f5b}.has-error .form-control-feedback{color:#ffffff}.has-feedback label~.form-control-feedback{top:25px}.has-feedback label.sr-only~.form-control-feedback{top:0}.help-block{display:block;margin-top:5px;margin-bottom:10px;color:#ffffff}@media (min-width:768px){.form-inline .form-group{display:inline-block;margin-bottom:0;vertical-align:middle}.form-inline .form-control{display:inline-block;width:auto;vertical-align:middle}.form-inline .form-control-static{display:inline-block}.form-inline .input-group{display:inline-table;vertical-align:middle}.form-inline .input-group .input-group-addon,.form-inline .input-group .input-group-btn,.form-inline .input-group .form-control{width:auto}.form-inline .input-group>.form-control{width:100%}.form-inline .control-label{margin-bottom:0;vertical-align:middle}.form-inline .radio,.form-inline .checkbox{display:inline-block;margin-top:0;margin-bottom:0;vertical-align:middle}.form-inline .radio label,.form-inline .checkbox label{padding-left:0}.form-inline .radio input[type="radio"],.form-inline .checkbox input[type="checkbox"]{position:relative;margin-left:0}.form-inline .has-feedback .form-control-feedback{top:0}}.form-horizontal .radio,.form-horizontal .checkbox,.form-horizontal .radio-inline,.form-horizontal .checkbox-inline{margin-top:0;margin-bottom:0;padding-top:9px}.form-horizontal .radio,.form-horizontal .checkbox{min-height:29px}.form-horizontal .form-group{margin-left:-15px;margin-right:-15px}@media (min-width:768px){.form-horizontal .control-label{text-align:right;margin-bottom:0;padding-top:9px}}.form-horizontal .has-feedback .form-control-feedback{right:15px}@media (min-width:768px){.form-horizontal .form-group-lg .control-label{padding-top:15px;font-size:18px}}@media (min-width:768px){.form-horizontal .form-group-sm .control-label{padding-top:6px;font-size:12px}}.btn{display:inline-block;margin-bottom:0;font-weight:normal;text-align:center;vertical-align:middle;-ms-touch-action:manipulation;touch-action:manipulation;cursor:pointer;background-image:none;border:1px solid transparent;white-space:nowrap;padding:8px 12px;font-size:14px;line-height:1.42857143;border-radius:4px;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.btn:focus,.btn:active:focus,.btn.active:focus,.btn.focus,.btn:active.focus,.btn.active.focus{outline:thin dotted;outline:5px auto -webkit-focus-ring-color;outline-offset:-2px}.btn:hover,.btn:focus,.btn.focus{color:#ffffff;text-decoration:none}.btn:active,.btn.active{outline:0;background-image:none;-webkit-box-shadow:inset 0 3px 5px rgba(0,0,0,0.125);box-shadow:inset 0 3px 5px rgba(0,0,0,0.125)}.btn.disabled,.btn[disabled],fieldset[disabled] .btn{cursor:not-allowed;opacity:0.65;filter:alpha(opacity=65);-webkit-box-shadow:none;box-shadow:none}a.btn.disabled,fieldset[disabled] a.btn{pointer-events:none}.btn-default{color:#ffffff;background-color:#3a3f44;border-color:#3a3f44}.btn-default:focus,.btn-default.focus{color:#ffffff;background-color:#232628;border-color:#000000}.btn-default:hover{color:#ffffff;background-color:#232628;border-color:#1e2023}.btn-default:active,.btn-default.active,.open>.dropdown-toggle.btn-default{color:#ffffff;background-color:#232628;border-color:#1e2023}.btn-default:active:hover,.btn-default.active:hover,.open>.dropdown-toggle.btn-default:hover,.btn-default:active:focus,.btn-default.active:focus,.open>.dropdown-toggle.btn-default:focus,.btn-default:active.focus,.btn-default.active.focus,.open>.dropdown-toggle.btn-default.focus{color:#ffffff;background-color:#121415;border-color:#000000}.btn-default:active,.btn-default.active,.open>.dropdown-toggle.btn-default{background-image:none}.btn-default.disabled:hover,.btn-default[disabled]:hover,fieldset[disabled] .btn-default:hover,.btn-default.disabled:focus,.btn-default[disabled]:focus,fieldset[disabled] .btn-default:focus,.btn-default.disabled.focus,.btn-default[disabled].focus,fieldset[disabled] .btn-default.focus{background-color:#3a3f44;border-color:#3a3f44}.btn-default .badge{color:#3a3f44;background-color:#ffffff}.btn-primary{color:#ffffff;background-color:#7a8288;border-color:#7a8288}.btn-primary:focus,.btn-primary.focus{color:#ffffff;background-color:#62686d;border-color:#3e4245}.btn-primary:hover{color:#ffffff;background-color:#62686d;border-color:#5d6368}.btn-primary:active,.btn-primary.active,.open>.dropdown-toggle.btn-primary{color:#ffffff;background-color:#62686d;border-color:#5d6368}.btn-primary:active:hover,.btn-primary.active:hover,.open>.dropdown-toggle.btn-primary:hover,.btn-primary:active:focus,.btn-primary.active:focus,.open>.dropdown-toggle.btn-primary:focus,.btn-primary:active.focus,.btn-primary.active.focus,.open>.dropdown-toggle.btn-primary.focus{color:#ffffff;background-color:#51565a;border-color:#3e4245}.btn-primary:active,.btn-primary.active,.open>.dropdown-toggle.btn-primary{background-image:none}.btn-primary.disabled:hover,.btn-primary[disabled]:hover,fieldset[disabled] .btn-primary:hover,.btn-primary.disabled:focus,.btn-primary[disabled]:focus,fieldset[disabled] .btn-primary:focus,.btn-primary.disabled.focus,.btn-primary[disabled].focus,fieldset[disabled] .btn-primary.focus{background-color:#7a8288;border-color:#7a8288}.btn-primary .badge{color:#7a8288;background-color:#ffffff}.btn-success{color:#ffffff;background-color:#62c462;border-color:#62c462}.btn-success:focus,.btn-success.focus{color:#ffffff;background-color:#42b142;border-color:#2d792d}.btn-success:hover{color:#ffffff;background-color:#42b142;border-color:#40a940}.btn-success:active,.btn-success.active,.open>.dropdown-toggle.btn-success{color:#ffffff;background-color:#42b142;border-color:#40a940}.btn-success:active:hover,.btn-success.active:hover,.open>.dropdown-toggle.btn-success:hover,.btn-success:active:focus,.btn-success.active:focus,.open>.dropdown-toggle.btn-success:focus,.btn-success:active.focus,.btn-success.active.focus,.open>.dropdown-toggle.btn-success.focus{color:#ffffff;background-color:#399739;border-color:#2d792d}.btn-success:active,.btn-success.active,.open>.dropdown-toggle.btn-success{background-image:none}.btn-success.disabled:hover,.btn-success[disabled]:hover,fieldset[disabled] .btn-success:hover,.btn-success.disabled:focus,.btn-success[disabled]:focus,fieldset[disabled] .btn-success:focus,.btn-success.disabled.focus,.btn-success[disabled].focus,fieldset[disabled] .btn-success.focus{background-color:#62c462;border-color:#62c462}.btn-success .badge{color:#62c462;background-color:#ffffff}.btn-info{color:#ffffff;background-color:#5bc0de;border-color:#5bc0de}.btn-info:focus,.btn-info.focus{color:#ffffff;background-color:#31b0d5;border-color:#1f7e9a}.btn-info:hover{color:#ffffff;background-color:#31b0d5;border-color:#2aabd2}.btn-info:active,.btn-info.active,.open>.dropdown-toggle.btn-info{color:#ffffff;background-color:#31b0d5;border-color:#2aabd2}.btn-info:active:hover,.btn-info.active:hover,.open>.dropdown-toggle.btn-info:hover,.btn-info:active:focus,.btn-info.active:focus,.open>.dropdown-toggle.btn-info:focus,.btn-info:active.focus,.btn-info.active.focus,.open>.dropdown-toggle.btn-info.focus{color:#ffffff;background-color:#269abc;border-color:#1f7e9a}.btn-info:active,.btn-info.active,.open>.dropdown-toggle.btn-info{background-image:none}.btn-info.disabled:hover,.btn-info[disabled]:hover,fieldset[disabled] .btn-info:hover,.btn-info.disabled:focus,.btn-info[disabled]:focus,fieldset[disabled] .btn-info:focus,.btn-info.disabled.focus,.btn-info[disabled].focus,fieldset[disabled] .btn-info.focus{background-color:#5bc0de;border-color:#5bc0de}.btn-info .badge{color:#5bc0de;background-color:#ffffff}.btn-warning{color:#ffffff;background-color:#f89406;border-color:#f89406}.btn-warning:focus,.btn-warning.focus{color:#ffffff;background-color:#c67605;border-color:#7c4a03}.btn-warning:hover{color:#ffffff;background-color:#c67605;border-color:#bc7005}.btn-warning:active,.btn-warning.active,.open>.dropdown-toggle.btn-warning{color:#ffffff;background-color:#c67605;border-color:#bc7005}.btn-warning:active:hover,.btn-warning.active:hover,.open>.dropdown-toggle.btn-warning:hover,.btn-warning:active:focus,.btn-warning.active:focus,.open>.dropdown-toggle.btn-warning:focus,.btn-warning:active.focus,.btn-warning.active.focus,.open>.dropdown-toggle.btn-warning.focus{color:#ffffff;background-color:#a36104;border-color:#7c4a03}.btn-warning:active,.btn-warning.active,.open>.dropdown-toggle.btn-warning{background-image:none}.btn-warning.disabled:hover,.btn-warning[disabled]:hover,fieldset[disabled] .btn-warning:hover,.btn-warning.disabled:focus,.btn-warning[disabled]:focus,fieldset[disabled] .btn-warning:focus,.btn-warning.disabled.focus,.btn-warning[disabled].focus,fieldset[disabled] .btn-warning.focus{background-color:#f89406;border-color:#f89406}.btn-warning .badge{color:#f89406;background-color:#ffffff}.btn-danger{color:#ffffff;background-color:#ee5f5b;border-color:#ee5f5b}.btn-danger:focus,.btn-danger.focus{color:#ffffff;background-color:#e9322d;border-color:#b71713}.btn-danger:hover{color:#ffffff;background-color:#e9322d;border-color:#e82924}.btn-danger:active,.btn-danger.active,.open>.dropdown-toggle.btn-danger{color:#ffffff;background-color:#e9322d;border-color:#e82924}.btn-danger:active:hover,.btn-danger.active:hover,.open>.dropdown-toggle.btn-danger:hover,.btn-danger:active:focus,.btn-danger.active:focus,.open>.dropdown-toggle.btn-danger:focus,.btn-danger:active.focus,.btn-danger.active.focus,.open>.dropdown-toggle.btn-danger.focus{color:#ffffff;background-color:#dc1c17;border-color:#b71713}.btn-danger:active,.btn-danger.active,.open>.dropdown-toggle.btn-danger{background-image:none}.btn-danger.disabled:hover,.btn-danger[disabled]:hover,fieldset[disabled] .btn-danger:hover,.btn-danger.disabled:focus,.btn-danger[disabled]:focus,fieldset[disabled] .btn-danger:focus,.btn-danger.disabled.focus,.btn-danger[disabled].focus,fieldset[disabled] .btn-danger.focus{background-color:#ee5f5b;border-color:#ee5f5b}.btn-danger .badge{color:#ee5f5b;background-color:#ffffff}.btn-link{color:#ffffff;font-weight:normal;border-radius:0}.btn-link,.btn-link:active,.btn-link.active,.btn-link[disabled],fieldset[disabled] .btn-link{background-color:transparent;-webkit-box-shadow:none;box-shadow:none}.btn-link,.btn-link:hover,.btn-link:focus,.btn-link:active{border-color:transparent}.btn-link:hover,.btn-link:focus{color:#ffffff;text-decoration:underline;background-color:transparent}.btn-link[disabled]:hover,fieldset[disabled] .btn-link:hover,.btn-link[disabled]:focus,fieldset[disabled] .btn-link:focus{color:#7a8288;text-decoration:none}.btn-lg,.btn-group-lg>.btn{padding:14px 16px;font-size:18px;line-height:1.3333333;border-radius:6px}.btn-sm,.btn-group-sm>.btn{padding:5px 10px;font-size:12px;line-height:1.5;border-radius:3px}.btn-xs,.btn-group-xs>.btn{padding:1px 5px;font-size:12px;line-height:1.5;border-radius:3px}.btn-block{display:block;width:100%}.btn-block+.btn-block{margin-top:5px}input[type="submit"].btn-block,input[type="reset"].btn-block,input[type="button"].btn-block{width:100%}.fade{opacity:0;-webkit-transition:opacity 0.15s linear;-o-transition:opacity 0.15s linear;transition:opacity 0.15s linear}.fade.in{opacity:1}.collapse{display:none}.collapse.in{display:block}tr.collapse.in{display:table-row}tbody.collapse.in{display:table-row-group}.collapsing{position:relative;height:0;overflow:hidden;-webkit-transition-property:height, visibility;-o-transition-property:height, visibility;transition-property:height, visibility;-webkit-transition-duration:0.35s;-o-transition-duration:0.35s;transition-duration:0.35s;-webkit-transition-timing-function:ease;-o-transition-timing-function:ease;transition-timing-function:ease}.caret{display:inline-block;width:0;height:0;margin-left:2px;vertical-align:middle;border-top:4px dashed;border-top:4px solid \9;border-right:4px solid transparent;border-left:4px solid transparent}.dropup,.dropdown{position:relative}.dropdown-toggle:focus{outline:0}.dropdown-menu{position:absolute;top:100%;left:0;z-index:1000;display:none;float:left;min-width:160px;padding:5px 0;margin:2px 0 0;list-style:none;font-size:14px;text-align:left;background-color:#3a3f44;border:1px solid #272b30;border:1px solid rgba(0,0,0,0.15);border-radius:4px;-webkit-box-shadow:0 6px 12px rgba(0,0,0,0.175);box-shadow:0 6px 12px rgba(0,0,0,0.175);-webkit-background-clip:padding-box;background-clip:padding-box}.dropdown-menu.pull-right{right:0;left:auto}.dropdown-menu .divider{height:1px;margin:9px 0;overflow:hidden;background-color:#272b30}.dropdown-menu>li>a{display:block;padding:3px 20px;clear:both;font-weight:normal;line-height:1.42857143;color:#c8c8c8;white-space:nowrap}.dropdown-menu>li>a:hover,.dropdown-menu>li>a:focus{text-decoration:none;color:#ffffff;background-color:#272b30}.dropdown-menu>.active>a,.dropdown-menu>.active>a:hover,.dropdown-menu>.active>a:focus{color:#ffffff;text-decoration:none;outline:0;background-color:#272b30}.dropdown-menu>.disabled>a,.dropdown-menu>.disabled>a:hover,.dropdown-menu>.disabled>a:focus{color:#7a8288}.dropdown-menu>.disabled>a:hover,.dropdown-menu>.disabled>a:focus{text-decoration:none;background-color:transparent;background-image:none;filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);cursor:not-allowed}.open>.dropdown-menu{display:block}.open>a{outline:0}.dropdown-menu-right{left:auto;right:0}.dropdown-menu-left{left:0;right:auto}.dropdown-header{display:block;padding:3px 20px;font-size:12px;line-height:1.42857143;color:#7a8288;white-space:nowrap}.dropdown-backdrop{position:fixed;left:0;right:0;bottom:0;top:0;z-index:990}.pull-right>.dropdown-menu{right:0;left:auto}.dropup .caret,.navbar-fixed-bottom .dropdown .caret{border-top:0;border-bottom:4px dashed;border-bottom:4px solid \9;content:""}.dropup .dropdown-menu,.navbar-fixed-bottom .dropdown .dropdown-menu{top:auto;bottom:100%;margin-bottom:2px}@media (min-width:768px){.navbar-right .dropdown-menu{left:auto;right:0}.navbar-right .dropdown-menu-left{left:0;right:auto}}.btn-group,.btn-group-vertical{position:relative;display:inline-block;vertical-align:middle}.btn-group>.btn,.btn-group-vertical>.btn{position:relative;float:left}.btn-group>.btn:hover,.btn-group-vertical>.btn:hover,.btn-group>.btn:focus,.btn-group-vertical>.btn:focus,.btn-group>.btn:active,.btn-group-vertical>.btn:active,.btn-group>.btn.active,.btn-group-vertical>.btn.active{z-index:2}.btn-group .btn+.btn,.btn-group .btn+.btn-group,.btn-group .btn-group+.btn,.btn-group .btn-group+.btn-group{margin-left:-1px}.btn-toolbar{margin-left:-5px}.btn-toolbar .btn,.btn-toolbar .btn-group,.btn-toolbar .input-group{float:left}.btn-toolbar>.btn,.btn-toolbar>.btn-group,.btn-toolbar>.input-group{margin-left:5px}.btn-group>.btn:not(:first-child):not(:last-child):not(.dropdown-toggle){border-radius:0}.btn-group>.btn:first-child{margin-left:0}.btn-group>.btn:first-child:not(:last-child):not(.dropdown-toggle){border-bottom-right-radius:0;border-top-right-radius:0}.btn-group>.btn:last-child:not(:first-child),.btn-group>.dropdown-toggle:not(:first-child){border-bottom-left-radius:0;border-top-left-radius:0}.btn-group>.btn-group{float:left}.btn-group>.btn-group:not(:first-child):not(:last-child)>.btn{border-radius:0}.btn-group>.btn-group:first-child:not(:last-child)>.btn:last-child,.btn-group>.btn-group:first-child:not(:last-child)>.dropdown-toggle{border-bottom-right-radius:0;border-top-right-radius:0}.btn-group>.btn-group:last-child:not(:first-child)>.btn:first-child{border-bottom-left-radius:0;border-top-left-radius:0}.btn-group .dropdown-toggle:active,.btn-group.open .dropdown-toggle{outline:0}.btn-group>.btn+.dropdown-toggle{padding-left:8px;padding-right:8px}.btn-group>.btn-lg+.dropdown-toggle{padding-left:12px;padding-right:12px}.btn-group.open .dropdown-toggle{-webkit-box-shadow:inset 0 3px 5px rgba(0,0,0,0.125);box-shadow:inset 0 3px 5px rgba(0,0,0,0.125)}.btn-group.open .dropdown-toggle.btn-link{-webkit-box-shadow:none;box-shadow:none}.btn .caret{margin-left:0}.btn-lg .caret{border-width:5px 5px 0;border-bottom-width:0}.dropup .btn-lg .caret{border-width:0 5px 5px}.btn-group-vertical>.btn,.btn-group-vertical>.btn-group,.btn-group-vertical>.btn-group>.btn{display:block;float:none;width:100%;max-width:100%}.btn-group-vertical>.btn-group>.btn{float:none}.btn-group-vertical>.btn+.btn,.btn-group-vertical>.btn+.btn-group,.btn-group-vertical>.btn-group+.btn,.btn-group-vertical>.btn-group+.btn-group{margin-top:-1px;margin-left:0}.btn-group-vertical>.btn:not(:first-child):not(:last-child){border-radius:0}.btn-group-vertical>.btn:first-child:not(:last-child){border-top-right-radius:4px;border-top-left-radius:4px;border-bottom-right-radius:0;border-bottom-left-radius:0}.btn-group-vertical>.btn:last-child:not(:first-child){border-top-right-radius:0;border-top-left-radius:0;border-bottom-right-radius:4px;border-bottom-left-radius:4px}.btn-group-vertical>.btn-group:not(:first-child):not(:last-child)>.btn{border-radius:0}.btn-group-vertical>.btn-group:first-child:not(:last-child)>.btn:last-child,.btn-group-vertical>.btn-group:first-child:not(:last-child)>.dropdown-toggle{border-bottom-right-radius:0;border-bottom-left-radius:0}.btn-group-vertical>.btn-group:last-child:not(:first-child)>.btn:first-child{border-top-right-radius:0;border-top-left-radius:0}.btn-group-justified{display:table;width:100%;table-layout:fixed;border-collapse:separate}.btn-group-justified>.btn,.btn-group-justified>.btn-group{float:none;display:table-cell;width:1%}.btn-group-justified>.btn-group .btn{width:100%}.btn-group-justified>.btn-group .dropdown-menu{left:auto}[data-toggle="buttons"]>.btn input[type="radio"],[data-toggle="buttons"]>.btn-group>.btn input[type="radio"],[data-toggle="buttons"]>.btn input[type="checkbox"],[data-toggle="buttons"]>.btn-group>.btn input[type="checkbox"]{position:absolute;clip:rect(0, 0, 0, 0);pointer-events:none}.input-group{position:relative;display:table;border-collapse:separate}.input-group[class*="col-"]{float:none;padding-left:0;padding-right:0}.input-group .form-control{position:relative;z-index:2;float:left;width:100%;margin-bottom:0}.input-group .form-control:focus{z-index:3}.input-group-lg>.form-control,.input-group-lg>.input-group-addon,.input-group-lg>.input-group-btn>.btn{height:54px;padding:14px 16px;font-size:18px;line-height:1.3333333;border-radius:6px}select.input-group-lg>.form-control,select.input-group-lg>.input-group-addon,select.input-group-lg>.input-group-btn>.btn{height:54px;line-height:54px}textarea.input-group-lg>.form-control,textarea.input-group-lg>.input-group-addon,textarea.input-group-lg>.input-group-btn>.btn,select[multiple].input-group-lg>.form-control,select[multiple].input-group-lg>.input-group-addon,select[multiple].input-group-lg>.input-group-btn>.btn{height:auto}.input-group-sm>.form-control,.input-group-sm>.input-group-addon,.input-group-sm>.input-group-btn>.btn{height:30px;padding:5px 10px;font-size:12px;line-height:1.5;border-radius:3px}select.input-group-sm>.form-control,select.input-group-sm>.input-group-addon,select.input-group-sm>.input-group-btn>.btn{height:30px;line-height:30px}textarea.input-group-sm>.form-control,textarea.input-group-sm>.input-group-addon,textarea.input-group-sm>.input-group-btn>.btn,select[multiple].input-group-sm>.form-control,select[multiple].input-group-sm>.input-group-addon,select[multiple].input-group-sm>.input-group-btn>.btn{height:auto}.input-group-addon,.input-group-btn,.input-group .form-control{display:table-cell}.input-group-addon:not(:first-child):not(:last-child),.input-group-btn:not(:first-child):not(:last-child),.input-group .form-control:not(:first-child):not(:last-child){border-radius:0}.input-group-addon,.input-group-btn{width:1%;white-space:nowrap;vertical-align:middle}.input-group-addon{padding:8px 12px;font-size:14px;font-weight:normal;line-height:1;color:#272b30;text-align:center;background-color:#999999;border:1px solid #cccccc;border-radius:4px}.input-group-addon.input-sm{padding:5px 10px;font-size:12px;border-radius:3px}.input-group-addon.input-lg{padding:14px 16px;font-size:18px;border-radius:6px}.input-group-addon input[type="radio"],.input-group-addon input[type="checkbox"]{margin-top:0}.input-group .form-control:first-child,.input-group-addon:first-child,.input-group-btn:first-child>.btn,.input-group-btn:first-child>.btn-group>.btn,.input-group-btn:first-child>.dropdown-toggle,.input-group-btn:last-child>.btn:not(:last-child):not(.dropdown-toggle),.input-group-btn:last-child>.btn-group:not(:last-child)>.btn{border-bottom-right-radius:0;border-top-right-radius:0}.input-group-addon:first-child{border-right:0}.input-group .form-control:last-child,.input-group-addon:last-child,.input-group-btn:last-child>.btn,.input-group-btn:last-child>.btn-group>.btn,.input-group-btn:last-child>.dropdown-toggle,.input-group-btn:first-child>.btn:not(:first-child),.input-group-btn:first-child>.btn-group:not(:first-child)>.btn{border-bottom-left-radius:0;border-top-left-radius:0}.input-group-addon:last-child{border-left:0}.input-group-btn{position:relative;font-size:0;white-space:nowrap}.input-group-btn>.btn{position:relative}.input-group-btn>.btn+.btn{margin-left:-1px}.input-group-btn>.btn:hover,.input-group-btn>.btn:focus,.input-group-btn>.btn:active{z-index:2}.input-group-btn:first-child>.btn,.input-group-btn:first-child>.btn-group{margin-right:-1px}.input-group-btn:last-child>.btn,.input-group-btn:last-child>.btn-group{z-index:2;margin-left:-1px}.nav{margin-bottom:0;padding-left:0;list-style:none}.nav>li{position:relative;display:block}.nav>li>a{position:relative;display:block;padding:10px 15px}.nav>li>a:hover,.nav>li>a:focus{text-decoration:none;background-color:#3e444c}.nav>li.disabled>a{color:#7a8288}.nav>li.disabled>a:hover,.nav>li.disabled>a:focus{color:#7a8288;text-decoration:none;background-color:transparent;cursor:not-allowed}.nav .open>a,.nav .open>a:hover,.nav .open>a:focus{background-color:#3e444c;border-color:#ffffff}.nav .nav-divider{height:1px;margin:9px 0;overflow:hidden;background-color:#e5e5e5}.nav>li>a>img{max-width:none}.nav-tabs{border-bottom:1px solid #1c1e22}.nav-tabs>li{float:left;margin-bottom:-1px}.nav-tabs>li>a{margin-right:2px;line-height:1.42857143;border:1px solid transparent;border-radius:4px 4px 0 0}.nav-tabs>li>a:hover{border-color:#1c1e22 #1c1e22 #1c1e22}.nav-tabs>li.active>a,.nav-tabs>li.active>a:hover,.nav-tabs>li.active>a:focus{color:#ffffff;background-color:#3e444c;border:1px solid #1c1e22;border-bottom-color:transparent;cursor:default}.nav-tabs.nav-justified{width:100%;border-bottom:0}.nav-tabs.nav-justified>li{float:none}.nav-tabs.nav-justified>li>a{text-align:center;margin-bottom:5px}.nav-tabs.nav-justified>.dropdown .dropdown-menu{top:auto;left:auto}@media (min-width:768px){.nav-tabs.nav-justified>li{display:table-cell;width:1%}.nav-tabs.nav-justified>li>a{margin-bottom:0}}.nav-tabs.nav-justified>li>a{margin-right:0;border-radius:4px}.nav-tabs.nav-justified>.active>a,.nav-tabs.nav-justified>.active>a:hover,.nav-tabs.nav-justified>.active>a:focus{border:1px solid #1c1e22}@media (min-width:768px){.nav-tabs.nav-justified>li>a{border-bottom:1px solid #1c1e22;border-radius:4px 4px 0 0}.nav-tabs.nav-justified>.active>a,.nav-tabs.nav-justified>.active>a:hover,.nav-tabs.nav-justified>.active>a:focus{border-bottom-color:#272b30}}.nav-pills>li{float:left}.nav-pills>li>a{border-radius:4px}.nav-pills>li+li{margin-left:2px}.nav-pills>li.active>a,.nav-pills>li.active>a:hover,.nav-pills>li.active>a:focus{color:#ffffff;background-color:transparent}.nav-stacked>li{float:none}.nav-stacked>li+li{margin-top:2px;margin-left:0}.nav-justified{width:100%}.nav-justified>li{float:none}.nav-justified>li>a{text-align:center;margin-bottom:5px}.nav-justified>.dropdown .dropdown-menu{top:auto;left:auto}@media (min-width:768px){.nav-justified>li{display:table-cell;width:1%}.nav-justified>li>a{margin-bottom:0}}.nav-tabs-justified{border-bottom:0}.nav-tabs-justified>li>a{margin-right:0;border-radius:4px}.nav-tabs-justified>.active>a,.nav-tabs-justified>.active>a:hover,.nav-tabs-justified>.active>a:focus{border:1px solid #1c1e22}@media (min-width:768px){.nav-tabs-justified>li>a{border-bottom:1px solid #1c1e22;border-radius:4px 4px 0 0}.nav-tabs-justified>.active>a,.nav-tabs-justified>.active>a:hover,.nav-tabs-justified>.active>a:focus{border-bottom-color:#272b30}}.tab-content>.tab-pane{display:none}.tab-content>.active{display:block}.nav-tabs .dropdown-menu{margin-top:-1px;border-top-right-radius:0;border-top-left-radius:0}.navbar{position:relative;min-height:50px;margin-bottom:20px;border:1px solid transparent}@media (min-width:768px){.navbar{border-radius:4px}}@media (min-width:768px){.navbar-header{float:left}}.navbar-collapse{overflow-x:visible;padding-right:15px;padding-left:15px;border-top:1px solid transparent;-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,0.1);box-shadow:inset 0 1px 0 rgba(255,255,255,0.1);-webkit-overflow-scrolling:touch}.navbar-collapse.in{overflow-y:auto}@media (min-width:768px){.navbar-collapse{width:auto;border-top:0;-webkit-box-shadow:none;box-shadow:none}.navbar-collapse.collapse{display:block !important;height:auto !important;padding-bottom:0;overflow:visible !important}.navbar-collapse.in{overflow-y:visible}.navbar-fixed-top .navbar-collapse,.navbar-static-top .navbar-collapse,.navbar-fixed-bottom .navbar-collapse{padding-left:0;padding-right:0}}.navbar-fixed-top .navbar-collapse,.navbar-fixed-bottom .navbar-collapse{max-height:340px}@media (max-device-width:480px) and (orientation:landscape){.navbar-fixed-top .navbar-collapse,.navbar-fixed-bottom .navbar-collapse{max-height:200px}}.container>.navbar-header,.container-fluid>.navbar-header,.container>.navbar-collapse,.container-fluid>.navbar-collapse{margin-right:-15px;margin-left:-15px}@media (min-width:768px){.container>.navbar-header,.container-fluid>.navbar-header,.container>.navbar-collapse,.container-fluid>.navbar-collapse{margin-right:0;margin-left:0}}.navbar-static-top{z-index:1000;border-width:0 0 1px}@media (min-width:768px){.navbar-static-top{border-radius:0}}.navbar-fixed-top,.navbar-fixed-bottom{position:fixed;right:0;left:0;z-index:1030}@media (min-width:768px){.navbar-fixed-top,.navbar-fixed-bottom{border-radius:0}}.navbar-fixed-top{top:0;border-width:0 0 1px}.navbar-fixed-bottom{bottom:0;margin-bottom:0;border-width:1px 0 0}.navbar-brand{float:left;padding:15px 15px;font-size:18px;line-height:20px;height:50px}.navbar-brand:hover,.navbar-brand:focus{text-decoration:none}.navbar-brand>img{display:block}@media (min-width:768px){.navbar>.container .navbar-brand,.navbar>.container-fluid .navbar-brand{margin-left:-15px}}.navbar-toggle{position:relative;float:right;margin-right:15px;padding:9px 10px;margin-top:8px;margin-bottom:8px;background-color:transparent;background-image:none;border:1px solid transparent;border-radius:4px}.navbar-toggle:focus{outline:0}.navbar-toggle .icon-bar{display:block;width:22px;height:2px;border-radius:1px}.navbar-toggle .icon-bar+.icon-bar{margin-top:4px}@media (min-width:768px){.navbar-toggle{display:none}}.navbar-nav{margin:7.5px -15px}.navbar-nav>li>a{padding-top:10px;padding-bottom:10px;line-height:20px}@media (max-width:767px){.navbar-nav .open .dropdown-menu{position:static;float:none;width:auto;margin-top:0;background-color:transparent;border:0;-webkit-box-shadow:none;box-shadow:none}.navbar-nav .open .dropdown-menu>li>a,.navbar-nav .open .dropdown-menu .dropdown-header{padding:5px 15px 5px 25px}.navbar-nav .open .dropdown-menu>li>a{line-height:20px}.navbar-nav .open .dropdown-menu>li>a:hover,.navbar-nav .open .dropdown-menu>li>a:focus{background-image:none}}@media (min-width:768px){.navbar-nav{float:left;margin:0}.navbar-nav>li{float:left}.navbar-nav>li>a{padding-top:15px;padding-bottom:15px}}.navbar-form{margin-left:-15px;margin-right:-15px;padding:10px 15px;border-top:1px solid transparent;border-bottom:1px solid transparent;-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,0.1),0 1px 0 rgba(255,255,255,0.1);box-shadow:inset 0 1px 0 rgba(255,255,255,0.1),0 1px 0 rgba(255,255,255,0.1);margin-top:6px;margin-bottom:6px}@media (min-width:768px){.navbar-form .form-group{display:inline-block;margin-bottom:0;vertical-align:middle}.navbar-form .form-control{display:inline-block;width:auto;vertical-align:middle}.navbar-form .form-control-static{display:inline-block}.navbar-form .input-group{display:inline-table;vertical-align:middle}.navbar-form .input-group .input-group-addon,.navbar-form .input-group .input-group-btn,.navbar-form .input-group .form-control{width:auto}.navbar-form .input-group>.form-control{width:100%}.navbar-form .control-label{margin-bottom:0;vertical-align:middle}.navbar-form .radio,.navbar-form .checkbox{display:inline-block;margin-top:0;margin-bottom:0;vertical-align:middle}.navbar-form .radio label,.navbar-form .checkbox label{padding-left:0}.navbar-form .radio input[type="radio"],.navbar-form .checkbox input[type="checkbox"]{position:relative;margin-left:0}.navbar-form .has-feedback .form-control-feedback{top:0}}@media (max-width:767px){.navbar-form .form-group{margin-bottom:5px}.navbar-form .form-group:last-child{margin-bottom:0}}@media (min-width:768px){.navbar-form{width:auto;border:0;margin-left:0;margin-right:0;padding-top:0;padding-bottom:0;-webkit-box-shadow:none;box-shadow:none}}.navbar-nav>li>.dropdown-menu{margin-top:0;border-top-right-radius:0;border-top-left-radius:0}.navbar-fixed-bottom .navbar-nav>li>.dropdown-menu{margin-bottom:0;border-top-right-radius:4px;border-top-left-radius:4px;border-bottom-right-radius:0;border-bottom-left-radius:0}.navbar-btn{margin-top:6px;margin-bottom:6px}.navbar-btn.btn-sm{margin-top:10px;margin-bottom:10px}.navbar-btn.btn-xs{margin-top:14px;margin-bottom:14px}.navbar-text{margin-top:15px;margin-bottom:15px}@media (min-width:768px){.navbar-text{float:left;margin-left:15px;margin-right:15px}}@media (min-width:768px){.navbar-left{float:left !important}.navbar-right{float:right !important;margin-right:-15px}.navbar-right~.navbar-right{margin-right:0}}.navbar-default{background-color:#3a3f44;border-color:#2b2e32}.navbar-default .navbar-brand{color:#c8c8c8}.navbar-default .navbar-brand:hover,.navbar-default .navbar-brand:focus{color:#ffffff;background-color:none}.navbar-default .navbar-text{color:#c8c8c8}.navbar-default .navbar-nav>li>a{color:#c8c8c8}.navbar-default .navbar-nav>li>a:hover,.navbar-default .navbar-nav>li>a:focus{color:#ffffff;background-color:#272b2e}.navbar-default .navbar-nav>.active>a,.navbar-default .navbar-nav>.active>a:hover,.navbar-default .navbar-nav>.active>a:focus{color:#ffffff;background-color:#272b2e}.navbar-default .navbar-nav>.disabled>a,.navbar-default .navbar-nav>.disabled>a:hover,.navbar-default .navbar-nav>.disabled>a:focus{color:#cccccc;background-color:transparent}.navbar-default .navbar-toggle{border-color:#272b2e}.navbar-default .navbar-toggle:hover,.navbar-default .navbar-toggle:focus{background-color:#272b2e}.navbar-default .navbar-toggle .icon-bar{background-color:#c8c8c8}.navbar-default .navbar-collapse,.navbar-default .navbar-form{border-color:#2b2e32}.navbar-default .navbar-nav>.open>a,.navbar-default .navbar-nav>.open>a:hover,.navbar-default .navbar-nav>.open>a:focus{background-color:#272b2e;color:#ffffff}@media (max-width:767px){.navbar-default .navbar-nav .open .dropdown-menu>li>a{color:#c8c8c8}.navbar-default .navbar-nav .open .dropdown-menu>li>a:hover,.navbar-default .navbar-nav .open .dropdown-menu>li>a:focus{color:#ffffff;background-color:#272b2e}.navbar-default .navbar-nav .open .dropdown-menu>.active>a,.navbar-default .navbar-nav .open .dropdown-menu>.active>a:hover,.navbar-default .navbar-nav .open .dropdown-menu>.active>a:focus{color:#ffffff;background-color:#272b2e}.navbar-default .navbar-nav .open .dropdown-menu>.disabled>a,.navbar-default .navbar-nav .open .dropdown-menu>.disabled>a:hover,.navbar-default .navbar-nav .open .dropdown-menu>.disabled>a:focus{color:#cccccc;background-color:transparent}}.navbar-default .navbar-link{color:#c8c8c8}.navbar-default .navbar-link:hover{color:#ffffff}.navbar-default .btn-link{color:#c8c8c8}.navbar-default .btn-link:hover,.navbar-default .btn-link:focus{color:#ffffff}.navbar-default .btn-link[disabled]:hover,fieldset[disabled] .navbar-default .btn-link:hover,.navbar-default .btn-link[disabled]:focus,fieldset[disabled] .navbar-default .btn-link:focus{color:#cccccc}.navbar-inverse{background-color:#7a8288;border-color:#62686d}.navbar-inverse .navbar-brand{color:#cccccc}.navbar-inverse .navbar-brand:hover,.navbar-inverse .navbar-brand:focus{color:#ffffff;background-color:none}.navbar-inverse .navbar-text{color:#cccccc}.navbar-inverse .navbar-nav>li>a{color:#cccccc}.navbar-inverse .navbar-nav>li>a:hover,.navbar-inverse .navbar-nav>li>a:focus{color:#ffffff;background-color:#5d6368}.navbar-inverse .navbar-nav>.active>a,.navbar-inverse .navbar-nav>.active>a:hover,.navbar-inverse .navbar-nav>.active>a:focus{color:#ffffff;background-color:#5d6368}.navbar-inverse .navbar-nav>.disabled>a,.navbar-inverse .navbar-nav>.disabled>a:hover,.navbar-inverse .navbar-nav>.disabled>a:focus{color:#cccccc;background-color:transparent}.navbar-inverse .navbar-toggle{border-color:#5d6368}.navbar-inverse .navbar-toggle:hover,.navbar-inverse .navbar-toggle:focus{background-color:#5d6368}.navbar-inverse .navbar-toggle .icon-bar{background-color:#ffffff}.navbar-inverse .navbar-collapse,.navbar-inverse .navbar-form{border-color:#697075}.navbar-inverse .navbar-nav>.open>a,.navbar-inverse .navbar-nav>.open>a:hover,.navbar-inverse .navbar-nav>.open>a:focus{background-color:#5d6368;color:#ffffff}@media (max-width:767px){.navbar-inverse .navbar-nav .open .dropdown-menu>.dropdown-header{border-color:#62686d}.navbar-inverse .navbar-nav .open .dropdown-menu .divider{background-color:#62686d}.navbar-inverse .navbar-nav .open .dropdown-menu>li>a{color:#cccccc}.navbar-inverse .navbar-nav .open .dropdown-menu>li>a:hover,.navbar-inverse .navbar-nav .open .dropdown-menu>li>a:focus{color:#ffffff;background-color:#5d6368}.navbar-inverse .navbar-nav .open .dropdown-menu>.active>a,.navbar-inverse .navbar-nav .open .dropdown-menu>.active>a:hover,.navbar-inverse .navbar-nav .open .dropdown-menu>.active>a:focus{color:#ffffff;background-color:#5d6368}.navbar-inverse .navbar-nav .open .dropdown-menu>.disabled>a,.navbar-inverse .navbar-nav .open .dropdown-menu>.disabled>a:hover,.navbar-inverse .navbar-nav .open .dropdown-menu>.disabled>a:focus{color:#cccccc;background-color:transparent}}.navbar-inverse .navbar-link{color:#cccccc}.navbar-inverse .navbar-link:hover{color:#ffffff}.navbar-inverse .btn-link{color:#cccccc}.navbar-inverse .btn-link:hover,.navbar-inverse .btn-link:focus{color:#ffffff}.navbar-inverse .btn-link[disabled]:hover,fieldset[disabled] .navbar-inverse .btn-link:hover,.navbar-inverse .btn-link[disabled]:focus,fieldset[disabled] .navbar-inverse .btn-link:focus{color:#cccccc}.breadcrumb{padding:8px 15px;margin-bottom:20px;list-style:none;background-color:transparent;border-radius:4px}.breadcrumb>li{display:inline-block}.breadcrumb>li+li:before{content:"/\00a0";padding:0 5px;color:#cccccc}.breadcrumb>.active{color:#7a8288}.pagination{display:inline-block;padding-left:0;margin:20px 0;border-radius:4px}.pagination>li{display:inline}.pagination>li>a,.pagination>li>span{position:relative;float:left;padding:8px 12px;line-height:1.42857143;text-decoration:none;color:#ffffff;background-color:#3a3f44;border:1px solid rgba(0,0,0,0.6);margin-left:-1px}.pagination>li:first-child>a,.pagination>li:first-child>span{margin-left:0;border-bottom-left-radius:4px;border-top-left-radius:4px}.pagination>li:last-child>a,.pagination>li:last-child>span{border-bottom-right-radius:4px;border-top-right-radius:4px}.pagination>li>a:hover,.pagination>li>span:hover,.pagination>li>a:focus,.pagination>li>span:focus{z-index:2;color:#ffffff;background-color:transparent;border-color:rgba(0,0,0,0.6)}.pagination>.active>a,.pagination>.active>span,.pagination>.active>a:hover,.pagination>.active>span:hover,.pagination>.active>a:focus,.pagination>.active>span:focus{z-index:3;color:#ffffff;background-color:#232628;border-color:rgba(0,0,0,0.6);cursor:default}.pagination>.disabled>span,.pagination>.disabled>span:hover,.pagination>.disabled>span:focus,.pagination>.disabled>a,.pagination>.disabled>a:hover,.pagination>.disabled>a:focus{color:#7a8288;background-color:#ffffff;border-color:rgba(0,0,0,0.6);cursor:not-allowed}.pagination-lg>li>a,.pagination-lg>li>span{padding:14px 16px;font-size:18px;line-height:1.3333333}.pagination-lg>li:first-child>a,.pagination-lg>li:first-child>span{border-bottom-left-radius:6px;border-top-left-radius:6px}.pagination-lg>li:last-child>a,.pagination-lg>li:last-child>span{border-bottom-right-radius:6px;border-top-right-radius:6px}.pagination-sm>li>a,.pagination-sm>li>span{padding:5px 10px;font-size:12px;line-height:1.5}.pagination-sm>li:first-child>a,.pagination-sm>li:first-child>span{border-bottom-left-radius:3px;border-top-left-radius:3px}.pagination-sm>li:last-child>a,.pagination-sm>li:last-child>span{border-bottom-right-radius:3px;border-top-right-radius:3px}.pager{padding-left:0;margin:20px 0;list-style:none;text-align:center}.pager li{display:inline}.pager li>a,.pager li>span{display:inline-block;padding:5px 14px;background-color:#3a3f44;border:1px solid rgba(0,0,0,0.6);border-radius:15px}.pager li>a:hover,.pager li>a:focus{text-decoration:none;background-color:transparent}.pager .next>a,.pager .next>span{float:right}.pager .previous>a,.pager .previous>span{float:left}.pager .disabled>a,.pager .disabled>a:hover,.pager .disabled>a:focus,.pager .disabled>span{color:#7a8288;background-color:#3a3f44;cursor:not-allowed}.label{display:inline;padding:.2em .6em .3em;font-size:75%;font-weight:bold;line-height:1;color:#ffffff;text-align:center;white-space:nowrap;vertical-align:baseline;border-radius:.25em}a.label:hover,a.label:focus{color:#ffffff;text-decoration:none;cursor:pointer}.label:empty{display:none}.btn .label{position:relative;top:-1px}.label-default{background-color:#3a3f44}.label-default[href]:hover,.label-default[href]:focus{background-color:#232628}.label-primary{background-color:#7a8288}.label-primary[href]:hover,.label-primary[href]:focus{background-color:#62686d}.label-success{background-color:#62c462}.label-success[href]:hover,.label-success[href]:focus{background-color:#42b142}.label-info{background-color:#5bc0de}.label-info[href]:hover,.label-info[href]:focus{background-color:#31b0d5}.label-warning{background-color:#f89406}.label-warning[href]:hover,.label-warning[href]:focus{background-color:#c67605}.label-danger{background-color:#ee5f5b}.label-danger[href]:hover,.label-danger[href]:focus{background-color:#e9322d}.badge{display:inline-block;min-width:10px;padding:3px 7px;font-size:12px;font-weight:bold;color:#ffffff;line-height:1;vertical-align:middle;white-space:nowrap;text-align:center;background-color:#7a8288;border-radius:10px}.badge:empty{display:none}.btn .badge{position:relative;top:-1px}.btn-xs .badge,.btn-group-xs>.btn .badge{top:0;padding:1px 5px}a.badge:hover,a.badge:focus{color:#ffffff;text-decoration:none;cursor:pointer}.list-group-item.active>.badge,.nav-pills>.active>a>.badge{color:#ffffff;background-color:#7a8288}.list-group-item>.badge{float:right}.list-group-item>.badge+.badge{margin-right:5px}.nav-pills>li>a>.badge{margin-left:3px}.jumbotron{padding-top:30px;padding-bottom:30px;margin-bottom:30px;color:inherit;background-color:#1c1e22}.jumbotron h1,.jumbotron .h1{color:inherit}.jumbotron p{margin-bottom:15px;font-size:21px;font-weight:200}.jumbotron>hr{border-top-color:#050506}.container .jumbotron,.container-fluid .jumbotron{border-radius:6px;padding-left:15px;padding-right:15px}.jumbotron .container{max-width:100%}@media screen and (min-width:768px){.jumbotron{padding-top:48px;padding-bottom:48px}.container .jumbotron,.container-fluid .jumbotron{padding-left:60px;padding-right:60px}.jumbotron h1,.jumbotron .h1{font-size:63px}}.thumbnail{display:block;padding:4px;margin-bottom:20px;line-height:1.42857143;background-color:#1c1e22;border:1px solid #0c0d0e;border-radius:4px;-webkit-transition:border .2s ease-in-out;-o-transition:border .2s ease-in-out;transition:border .2s ease-in-out}.thumbnail>img,.thumbnail a>img{margin-left:auto;margin-right:auto}a.thumbnail:hover,a.thumbnail:focus,a.thumbnail.active{border-color:#ffffff}.thumbnail .caption{padding:9px;color:#c8c8c8}.alert{padding:15px;margin-bottom:20px;border:1px solid transparent;border-radius:4px}.alert h4{margin-top:0;color:inherit}.alert .alert-link{font-weight:bold}.alert>p,.alert>ul{margin-bottom:0}.alert>p+p{margin-top:5px}.alert-dismissable,.alert-dismissible{padding-right:35px}.alert-dismissable .close,.alert-dismissible .close{position:relative;top:-2px;right:-21px;color:inherit}.alert-success{background-color:#62c462;border-color:#62bd4f;color:#ffffff}.alert-success hr{border-top-color:#55b142}.alert-success .alert-link{color:#e6e6e6}.alert-info{background-color:#5bc0de;border-color:#3dced8;color:#ffffff}.alert-info hr{border-top-color:#2ac7d2}.alert-info .alert-link{color:#e6e6e6}.alert-warning{background-color:#f89406;border-color:#e96506;color:#ffffff}.alert-warning hr{border-top-color:#d05a05}.alert-warning .alert-link{color:#e6e6e6}.alert-danger{background-color:#ee5f5b;border-color:#ed4d63;color:#ffffff}.alert-danger hr{border-top-color:#ea364f}.alert-danger .alert-link{color:#e6e6e6}@-webkit-keyframes progress-bar-stripes{from{background-position:40px 0}to{background-position:0 0}}@-o-keyframes progress-bar-stripes{from{background-position:40px 0}to{background-position:0 0}}@keyframes progress-bar-stripes{from{background-position:40px 0}to{background-position:0 0}}.progress{overflow:hidden;height:20px;margin-bottom:20px;background-color:#1c1e22;border-radius:4px;-webkit-box-shadow:inset 0 1px 2px rgba(0,0,0,0.1);box-shadow:inset 0 1px 2px rgba(0,0,0,0.1)}.progress-bar{float:left;width:0%;height:100%;font-size:12px;line-height:20px;color:#ffffff;text-align:center;background-color:#7a8288;-webkit-box-shadow:inset 0 -1px 0 rgba(0,0,0,0.15);box-shadow:inset 0 -1px 0 rgba(0,0,0,0.15);-webkit-transition:width 0.6s ease;-o-transition:width 0.6s ease;transition:width 0.6s ease}.progress-striped .progress-bar,.progress-bar-striped{background-image:-webkit-linear-gradient(45deg, rgba(255,255,255,0.15) 25%, transparent 25%, transparent 50%, rgba(255,255,255,0.15) 50%, rgba(255,255,255,0.15) 75%, transparent 75%, transparent);background-image:-o-linear-gradient(45deg, rgba(255,255,255,0.15) 25%, transparent 25%, transparent 50%, rgba(255,255,255,0.15) 50%, rgba(255,255,255,0.15) 75%, transparent 75%, transparent);background-image:linear-gradient(45deg, rgba(255,255,255,0.15) 25%, transparent 25%, transparent 50%, rgba(255,255,255,0.15) 50%, rgba(255,255,255,0.15) 75%, transparent 75%, transparent);-webkit-background-size:40px 40px;background-size:40px 40px}.progress.active .progress-bar,.progress-bar.active{-webkit-animation:progress-bar-stripes 2s linear infinite;-o-animation:progress-bar-stripes 2s linear infinite;animation:progress-bar-stripes 2s linear infinite}.progress-bar-success{background-color:#62c462}.progress-striped .progress-bar-success{background-image:-webkit-linear-gradient(45deg, rgba(255,255,255,0.15) 25%, transparent 25%, transparent 50%, rgba(255,255,255,0.15) 50%, rgba(255,255,255,0.15) 75%, transparent 75%, transparent);background-image:-o-linear-gradient(45deg, rgba(255,255,255,0.15) 25%, transparent 25%, transparent 50%, rgba(255,255,255,0.15) 50%, rgba(255,255,255,0.15) 75%, transparent 75%, transparent);background-image:linear-gradient(45deg, rgba(255,255,255,0.15) 25%, transparent 25%, transparent 50%, rgba(255,255,255,0.15) 50%, rgba(255,255,255,0.15) 75%, transparent 75%, transparent)}.progress-bar-info{background-color:#5bc0de}.progress-striped .progress-bar-info{background-image:-webkit-linear-gradient(45deg, rgba(255,255,255,0.15) 25%, transparent 25%, transparent 50%, rgba(255,255,255,0.15) 50%, rgba(255,255,255,0.15) 75%, transparent 75%, transparent);background-image:-o-linear-gradient(45deg, rgba(255,255,255,0.15) 25%, transparent 25%, transparent 50%, rgba(255,255,255,0.15) 50%, rgba(255,255,255,0.15) 75%, transparent 75%, transparent);background-image:linear-gradient(45deg, rgba(255,255,255,0.15) 25%, transparent 25%, transparent 50%, rgba(255,255,255,0.15) 50%, rgba(255,255,255,0.15) 75%, transparent 75%, transparent)}.progress-bar-warning{background-color:#f89406}.progress-striped .progress-bar-warning{background-image:-webkit-linear-gradient(45deg, rgba(255,255,255,0.15) 25%, transparent 25%, transparent 50%, rgba(255,255,255,0.15) 50%, rgba(255,255,255,0.15) 75%, transparent 75%, transparent);background-image:-o-linear-gradient(45deg, rgba(255,255,255,0.15) 25%, transparent 25%, transparent 50%, rgba(255,255,255,0.15) 50%, rgba(255,255,255,0.15) 75%, transparent 75%, transparent);background-image:linear-gradient(45deg, rgba(255,255,255,0.15) 25%, transparent 25%, transparent 50%, rgba(255,255,255,0.15) 50%, rgba(255,255,255,0.15) 75%, transparent 75%, transparent)}.progress-bar-danger{background-color:#ee5f5b}.progress-striped .progress-bar-danger{background-image:-webkit-linear-gradient(45deg, rgba(255,255,255,0.15) 25%, transparent 25%, transparent 50%, rgba(255,255,255,0.15) 50%, rgba(255,255,255,0.15) 75%, transparent 75%, transparent);background-image:-o-linear-gradient(45deg, rgba(255,255,255,0.15) 25%, transparent 25%, transparent 50%, rgba(255,255,255,0.15) 50%, rgba(255,255,255,0.15) 75%, transparent 75%, transparent);background-image:linear-gradient(45deg, rgba(255,255,255,0.15) 25%, transparent 25%, transparent 50%, rgba(255,255,255,0.15) 50%, rgba(255,255,255,0.15) 75%, transparent 75%, transparent)}.media{margin-top:15px}.media:first-child{margin-top:0}.media,.media-body{zoom:1;overflow:hidden}.media-body{width:10000px}.media-object{display:block}.media-object.img-thumbnail{max-width:none}.media-right,.media>.pull-right{padding-left:10px}.media-left,.media>.pull-left{padding-right:10px}.media-left,.media-right,.media-body{display:table-cell;vertical-align:top}.media-middle{vertical-align:middle}.media-bottom{vertical-align:bottom}.media-heading{margin-top:0;margin-bottom:5px}.media-list{padding-left:0;list-style:none}.list-group{margin-bottom:20px;padding-left:0}.list-group-item{position:relative;display:block;padding:10px 15px;margin-bottom:-1px;background-color:#32383e;border:1px solid rgba(0,0,0,0.6)}.list-group-item:first-child{border-top-right-radius:4px;border-top-left-radius:4px}.list-group-item:last-child{margin-bottom:0;border-bottom-right-radius:4px;border-bottom-left-radius:4px}a.list-group-item,button.list-group-item{color:#c8c8c8}a.list-group-item .list-group-item-heading,button.list-group-item .list-group-item-heading{color:#ffffff}a.list-group-item:hover,button.list-group-item:hover,a.list-group-item:focus,button.list-group-item:focus{text-decoration:none;color:#c8c8c8;background-color:#3e444c}button.list-group-item{width:100%;text-align:left}.list-group-item.disabled,.list-group-item.disabled:hover,.list-group-item.disabled:focus{background-color:#999999;color:#7a8288;cursor:not-allowed}.list-group-item.disabled .list-group-item-heading,.list-group-item.disabled:hover .list-group-item-heading,.list-group-item.disabled:focus .list-group-item-heading{color:inherit}.list-group-item.disabled .list-group-item-text,.list-group-item.disabled:hover .list-group-item-text,.list-group-item.disabled:focus .list-group-item-text{color:#7a8288}.list-group-item.active,.list-group-item.active:hover,.list-group-item.active:focus{z-index:2;color:#ffffff;background-color:#3e444c;border-color:rgba(0,0,0,0.6)}.list-group-item.active .list-group-item-heading,.list-group-item.active:hover .list-group-item-heading,.list-group-item.active:focus .list-group-item-heading,.list-group-item.active .list-group-item-heading>small,.list-group-item.active:hover .list-group-item-heading>small,.list-group-item.active:focus .list-group-item-heading>small,.list-group-item.active .list-group-item-heading>.small,.list-group-item.active:hover .list-group-item-heading>.small,.list-group-item.active:focus .list-group-item-heading>.small{color:inherit}.list-group-item.active .list-group-item-text,.list-group-item.active:hover .list-group-item-text,.list-group-item.active:focus .list-group-item-text{color:#a2aab4}.list-group-item-success{color:#ffffff;background-color:#62c462}a.list-group-item-success,button.list-group-item-success{color:#ffffff}a.list-group-item-success .list-group-item-heading,button.list-group-item-success .list-group-item-heading{color:inherit}a.list-group-item-success:hover,button.list-group-item-success:hover,a.list-group-item-success:focus,button.list-group-item-success:focus{color:#ffffff;background-color:#4fbd4f}a.list-group-item-success.active,button.list-group-item-success.active,a.list-group-item-success.active:hover,button.list-group-item-success.active:hover,a.list-group-item-success.active:focus,button.list-group-item-success.active:focus{color:#fff;background-color:#ffffff;border-color:#ffffff}.list-group-item-info{color:#ffffff;background-color:#5bc0de}a.list-group-item-info,button.list-group-item-info{color:#ffffff}a.list-group-item-info .list-group-item-heading,button.list-group-item-info .list-group-item-heading{color:inherit}a.list-group-item-info:hover,button.list-group-item-info:hover,a.list-group-item-info:focus,button.list-group-item-info:focus{color:#ffffff;background-color:#46b8da}a.list-group-item-info.active,button.list-group-item-info.active,a.list-group-item-info.active:hover,button.list-group-item-info.active:hover,a.list-group-item-info.active:focus,button.list-group-item-info.active:focus{color:#fff;background-color:#ffffff;border-color:#ffffff}.list-group-item-warning{color:#ffffff;background-color:#f89406}a.list-group-item-warning,button.list-group-item-warning{color:#ffffff}a.list-group-item-warning .list-group-item-heading,button.list-group-item-warning .list-group-item-heading{color:inherit}a.list-group-item-warning:hover,button.list-group-item-warning:hover,a.list-group-item-warning:focus,button.list-group-item-warning:focus{color:#ffffff;background-color:#df8505}a.list-group-item-warning.active,button.list-group-item-warning.active,a.list-group-item-warning.active:hover,button.list-group-item-warning.active:hover,a.list-group-item-warning.active:focus,button.list-group-item-warning.active:focus{color:#fff;background-color:#ffffff;border-color:#ffffff}.list-group-item-danger{color:#ffffff;background-color:#ee5f5b}a.list-group-item-danger,button.list-group-item-danger{color:#ffffff}a.list-group-item-danger .list-group-item-heading,button.list-group-item-danger .list-group-item-heading{color:inherit}a.list-group-item-danger:hover,button.list-group-item-danger:hover,a.list-group-item-danger:focus,button.list-group-item-danger:focus{color:#ffffff;background-color:#ec4844}a.list-group-item-danger.active,button.list-group-item-danger.active,a.list-group-item-danger.active:hover,button.list-group-item-danger.active:hover,a.list-group-item-danger.active:focus,button.list-group-item-danger.active:focus{color:#fff;background-color:#ffffff;border-color:#ffffff}.list-group-item-heading{margin-top:0;margin-bottom:5px}.list-group-item-text{margin-bottom:0;line-height:1.3}.panel{margin-bottom:20px;background-color:#2e3338;border:1px solid transparent;border-radius:4px;-webkit-box-shadow:0 1px 1px rgba(0,0,0,0.05);box-shadow:0 1px 1px rgba(0,0,0,0.05)}.panel-body{padding:15px}.panel-heading{padding:10px 15px;border-bottom:1px solid transparent;border-top-right-radius:3px;border-top-left-radius:3px}.panel-heading>.dropdown .dropdown-toggle{color:inherit}.panel-title{margin-top:0;margin-bottom:0;font-size:16px;color:inherit}.panel-title>a,.panel-title>small,.panel-title>.small,.panel-title>small>a,.panel-title>.small>a{color:inherit}.panel-footer{padding:10px 15px;background-color:#3e444c;border-top:1px solid rgba(0,0,0,0.6);border-bottom-right-radius:3px;border-bottom-left-radius:3px}.panel>.list-group,.panel>.panel-collapse>.list-group{margin-bottom:0}.panel>.list-group .list-group-item,.panel>.panel-collapse>.list-group .list-group-item{border-width:1px 0;border-radius:0}.panel>.list-group:first-child .list-group-item:first-child,.panel>.panel-collapse>.list-group:first-child .list-group-item:first-child{border-top:0;border-top-right-radius:3px;border-top-left-radius:3px}.panel>.list-group:last-child .list-group-item:last-child,.panel>.panel-collapse>.list-group:last-child .list-group-item:last-child{border-bottom:0;border-bottom-right-radius:3px;border-bottom-left-radius:3px}.panel>.panel-heading+.panel-collapse>.list-group .list-group-item:first-child{border-top-right-radius:0;border-top-left-radius:0}.panel-heading+.list-group .list-group-item:first-child{border-top-width:0}.list-group+.panel-footer{border-top-width:0}.panel>.table,.panel>.table-responsive>.table,.panel>.panel-collapse>.table{margin-bottom:0}.panel>.table caption,.panel>.table-responsive>.table caption,.panel>.panel-collapse>.table caption{padding-left:15px;padding-right:15px}.panel>.table:first-child,.panel>.table-responsive:first-child>.table:first-child{border-top-right-radius:3px;border-top-left-radius:3px}.panel>.table:first-child>thead:first-child>tr:first-child,.panel>.table-responsive:first-child>.table:first-child>thead:first-child>tr:first-child,.panel>.table:first-child>tbody:first-child>tr:first-child,.panel>.table-responsive:first-child>.table:first-child>tbody:first-child>tr:first-child{border-top-left-radius:3px;border-top-right-radius:3px}.panel>.table:first-child>thead:first-child>tr:first-child td:first-child,.panel>.table-responsive:first-child>.table:first-child>thead:first-child>tr:first-child td:first-child,.panel>.table:first-child>tbody:first-child>tr:first-child td:first-child,.panel>.table-responsive:first-child>.table:first-child>tbody:first-child>tr:first-child td:first-child,.panel>.table:first-child>thead:first-child>tr:first-child th:first-child,.panel>.table-responsive:first-child>.table:first-child>thead:first-child>tr:first-child th:first-child,.panel>.table:first-child>tbody:first-child>tr:first-child th:first-child,.panel>.table-responsive:first-child>.table:first-child>tbody:first-child>tr:first-child th:first-child{border-top-left-radius:3px}.panel>.table:first-child>thead:first-child>tr:first-child td:last-child,.panel>.table-responsive:first-child>.table:first-child>thead:first-child>tr:first-child td:last-child,.panel>.table:first-child>tbody:first-child>tr:first-child td:last-child,.panel>.table-responsive:first-child>.table:first-child>tbody:first-child>tr:first-child td:last-child,.panel>.table:first-child>thead:first-child>tr:first-child th:last-child,.panel>.table-responsive:first-child>.table:first-child>thead:first-child>tr:first-child th:last-child,.panel>.table:first-child>tbody:first-child>tr:first-child th:last-child,.panel>.table-responsive:first-child>.table:first-child>tbody:first-child>tr:first-child th:last-child{border-top-right-radius:3px}.panel>.table:last-child,.panel>.table-responsive:last-child>.table:last-child{border-bottom-right-radius:3px;border-bottom-left-radius:3px}.panel>.table:last-child>tbody:last-child>tr:last-child,.panel>.table-responsive:last-child>.table:last-child>tbody:last-child>tr:last-child,.panel>.table:last-child>tfoot:last-child>tr:last-child,.panel>.table-responsive:last-child>.table:last-child>tfoot:last-child>tr:last-child{border-bottom-left-radius:3px;border-bottom-right-radius:3px}.panel>.table:last-child>tbody:last-child>tr:last-child td:first-child,.panel>.table-responsive:last-child>.table:last-child>tbody:last-child>tr:last-child td:first-child,.panel>.table:last-child>tfoot:last-child>tr:last-child td:first-child,.panel>.table-responsive:last-child>.table:last-child>tfoot:last-child>tr:last-child td:first-child,.panel>.table:last-child>tbody:last-child>tr:last-child th:first-child,.panel>.table-responsive:last-child>.table:last-child>tbody:last-child>tr:last-child th:first-child,.panel>.table:last-child>tfoot:last-child>tr:last-child th:first-child,.panel>.table-responsive:last-child>.table:last-child>tfoot:last-child>tr:last-child th:first-child{border-bottom-left-radius:3px}.panel>.table:last-child>tbody:last-child>tr:last-child td:last-child,.panel>.table-responsive:last-child>.table:last-child>tbody:last-child>tr:last-child td:last-child,.panel>.table:last-child>tfoot:last-child>tr:last-child td:last-child,.panel>.table-responsive:last-child>.table:last-child>tfoot:last-child>tr:last-child td:last-child,.panel>.table:last-child>tbody:last-child>tr:last-child th:last-child,.panel>.table-responsive:last-child>.table:last-child>tbody:last-child>tr:last-child th:last-child,.panel>.table:last-child>tfoot:last-child>tr:last-child th:last-child,.panel>.table-responsive:last-child>.table:last-child>tfoot:last-child>tr:last-child th:last-child{border-bottom-right-radius:3px}.panel>.panel-body+.table,.panel>.panel-body+.table-responsive,.panel>.table+.panel-body,.panel>.table-responsive+.panel-body{border-top:1px solid #1c1e22}.panel>.table>tbody:first-child>tr:first-child th,.panel>.table>tbody:first-child>tr:first-child td{border-top:0}.panel>.table-bordered,.panel>.table-responsive>.table-bordered{border:0}.panel>.table-bordered>thead>tr>th:first-child,.panel>.table-responsive>.table-bordered>thead>tr>th:first-child,.panel>.table-bordered>tbody>tr>th:first-child,.panel>.table-responsive>.table-bordered>tbody>tr>th:first-child,.panel>.table-bordered>tfoot>tr>th:first-child,.panel>.table-responsive>.table-bordered>tfoot>tr>th:first-child,.panel>.table-bordered>thead>tr>td:first-child,.panel>.table-responsive>.table-bordered>thead>tr>td:first-child,.panel>.table-bordered>tbody>tr>td:first-child,.panel>.table-responsive>.table-bordered>tbody>tr>td:first-child,.panel>.table-bordered>tfoot>tr>td:first-child,.panel>.table-responsive>.table-bordered>tfoot>tr>td:first-child{border-left:0}.panel>.table-bordered>thead>tr>th:last-child,.panel>.table-responsive>.table-bordered>thead>tr>th:last-child,.panel>.table-bordered>tbody>tr>th:last-child,.panel>.table-responsive>.table-bordered>tbody>tr>th:last-child,.panel>.table-bordered>tfoot>tr>th:last-child,.panel>.table-responsive>.table-bordered>tfoot>tr>th:last-child,.panel>.table-bordered>thead>tr>td:last-child,.panel>.table-responsive>.table-bordered>thead>tr>td:last-child,.panel>.table-bordered>tbody>tr>td:last-child,.panel>.table-responsive>.table-bordered>tbody>tr>td:last-child,.panel>.table-bordered>tfoot>tr>td:last-child,.panel>.table-responsive>.table-bordered>tfoot>tr>td:last-child{border-right:0}.panel>.table-bordered>thead>tr:first-child>td,.panel>.table-responsive>.table-bordered>thead>tr:first-child>td,.panel>.table-bordered>tbody>tr:first-child>td,.panel>.table-responsive>.table-bordered>tbody>tr:first-child>td,.panel>.table-bordered>thead>tr:first-child>th,.panel>.table-responsive>.table-bordered>thead>tr:first-child>th,.panel>.table-bordered>tbody>tr:first-child>th,.panel>.table-responsive>.table-bordered>tbody>tr:first-child>th{border-bottom:0}.panel>.table-bordered>tbody>tr:last-child>td,.panel>.table-responsive>.table-bordered>tbody>tr:last-child>td,.panel>.table-bordered>tfoot>tr:last-child>td,.panel>.table-responsive>.table-bordered>tfoot>tr:last-child>td,.panel>.table-bordered>tbody>tr:last-child>th,.panel>.table-responsive>.table-bordered>tbody>tr:last-child>th,.panel>.table-bordered>tfoot>tr:last-child>th,.panel>.table-responsive>.table-bordered>tfoot>tr:last-child>th{border-bottom:0}.panel>.table-responsive{border:0;margin-bottom:0}.panel-group{margin-bottom:20px}.panel-group .panel{margin-bottom:0;border-radius:4px}.panel-group .panel+.panel{margin-top:5px}.panel-group .panel-heading{border-bottom:0}.panel-group .panel-heading+.panel-collapse>.panel-body,.panel-group .panel-heading+.panel-collapse>.list-group{border-top:1px solid rgba(0,0,0,0.6)}.panel-group .panel-footer{border-top:0}.panel-group .panel-footer+.panel-collapse .panel-body{border-bottom:1px solid rgba(0,0,0,0.6)}.panel-default{border-color:rgba(0,0,0,0.6)}.panel-default>.panel-heading{color:#c8c8c8;background-color:#3e444c;border-color:rgba(0,0,0,0.6)}.panel-default>.panel-heading+.panel-collapse>.panel-body{border-top-color:rgba(0,0,0,0.6)}.panel-default>.panel-heading .badge{color:#3e444c;background-color:#c8c8c8}.panel-default>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:rgba(0,0,0,0.6)}.panel-primary{border-color:rgba(0,0,0,0.6)}.panel-primary>.panel-heading{color:#ffffff;background-color:#7a8288;border-color:rgba(0,0,0,0.6)}.panel-primary>.panel-heading+.panel-collapse>.panel-body{border-top-color:rgba(0,0,0,0.6)}.panel-primary>.panel-heading .badge{color:#7a8288;background-color:#ffffff}.panel-primary>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:rgba(0,0,0,0.6)}.panel-success{border-color:rgba(0,0,0,0.6)}.panel-success>.panel-heading{color:#ffffff;background-color:#62c462;border-color:rgba(0,0,0,0.6)}.panel-success>.panel-heading+.panel-collapse>.panel-body{border-top-color:rgba(0,0,0,0.6)}.panel-success>.panel-heading .badge{color:#62c462;background-color:#ffffff}.panel-success>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:rgba(0,0,0,0.6)}.panel-info{border-color:rgba(0,0,0,0.6)}.panel-info>.panel-heading{color:#ffffff;background-color:#5bc0de;border-color:rgba(0,0,0,0.6)}.panel-info>.panel-heading+.panel-collapse>.panel-body{border-top-color:rgba(0,0,0,0.6)}.panel-info>.panel-heading .badge{color:#5bc0de;background-color:#ffffff}.panel-info>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:rgba(0,0,0,0.6)}.panel-warning{border-color:rgba(0,0,0,0.6)}.panel-warning>.panel-heading{color:#ffffff;background-color:#f89406;border-color:rgba(0,0,0,0.6)}.panel-warning>.panel-heading+.panel-collapse>.panel-body{border-top-color:rgba(0,0,0,0.6)}.panel-warning>.panel-heading .badge{color:#f89406;background-color:#ffffff}.panel-warning>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:rgba(0,0,0,0.6)}.panel-danger{border-color:rgba(0,0,0,0.6)}.panel-danger>.panel-heading{color:#ffffff;background-color:#ee5f5b;border-color:rgba(0,0,0,0.6)}.panel-danger>.panel-heading+.panel-collapse>.panel-body{border-top-color:rgba(0,0,0,0.6)}.panel-danger>.panel-heading .badge{color:#ee5f5b;background-color:#ffffff}.panel-danger>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:rgba(0,0,0,0.6)}.embed-responsive{position:relative;display:block;height:0;padding:0;overflow:hidden}.embed-responsive .embed-responsive-item,.embed-responsive iframe,.embed-responsive embed,.embed-responsive object,.embed-responsive video{position:absolute;top:0;left:0;bottom:0;height:100%;width:100%;border:0}.embed-responsive-16by9{padding-bottom:56.25%}.embed-responsive-4by3{padding-bottom:75%}.well{min-height:20px;padding:19px;margin-bottom:20px;background-color:#1c1e22;border:1px solid #0c0d0e;border-radius:4px;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.05);box-shadow:inset 0 1px 1px rgba(0,0,0,0.05)}.well blockquote{border-color:#ddd;border-color:rgba(0,0,0,0.15)}.well-lg{padding:24px;border-radius:6px}.well-sm{padding:9px;border-radius:3px}.close{float:right;font-size:21px;font-weight:bold;line-height:1;color:#000000;text-shadow:0 1px 0 #ffffff;opacity:0.2;filter:alpha(opacity=20)}.close:hover,.close:focus{color:#000000;text-decoration:none;cursor:pointer;opacity:0.5;filter:alpha(opacity=50)}button.close{padding:0;cursor:pointer;background:transparent;border:0;-webkit-appearance:none}.modal-open{overflow:hidden}.modal{display:none;overflow:hidden;position:fixed;top:0;right:0;bottom:0;left:0;z-index:1050;-webkit-overflow-scrolling:touch;outline:0}.modal.fade .modal-dialog{-webkit-transform:translate(0, -25%);-ms-transform:translate(0, -25%);-o-transform:translate(0, -25%);transform:translate(0, -25%);-webkit-transition:-webkit-transform .3s ease-out;-o-transition:-o-transform .3s ease-out;transition:transform .3s ease-out}.modal.in .modal-dialog{-webkit-transform:translate(0, 0);-ms-transform:translate(0, 0);-o-transform:translate(0, 0);transform:translate(0, 0)}.modal-open .modal{overflow-x:hidden;overflow-y:auto}.modal-dialog{position:relative;width:auto;margin:10px}.modal-content{position:relative;background-color:#2e3338;border:1px solid #999999;border:1px solid rgba(0,0,0,0.2);border-radius:6px;-webkit-box-shadow:0 3px 9px rgba(0,0,0,0.5);box-shadow:0 3px 9px rgba(0,0,0,0.5);-webkit-background-clip:padding-box;background-clip:padding-box;outline:0}.modal-backdrop{position:fixed;top:0;right:0;bottom:0;left:0;z-index:1040;background-color:#000000}.modal-backdrop.fade{opacity:0;filter:alpha(opacity=0)}.modal-backdrop.in{opacity:0.5;filter:alpha(opacity=50)}.modal-header{padding:15px;border-bottom:1px solid #1c1e22}.modal-header .close{margin-top:-2px}.modal-title{margin:0;line-height:1.42857143}.modal-body{position:relative;padding:20px}.modal-footer{padding:20px;text-align:right;border-top:1px solid #1c1e22}.modal-footer .btn+.btn{margin-left:5px;margin-bottom:0}.modal-footer .btn-group .btn+.btn{margin-left:-1px}.modal-footer .btn-block+.btn-block{margin-left:0}.modal-scrollbar-measure{position:absolute;top:-9999px;width:50px;height:50px;overflow:scroll}@media (min-width:768px){.modal-dialog{width:600px;margin:30px auto}.modal-content{-webkit-box-shadow:0 5px 15px rgba(0,0,0,0.5);box-shadow:0 5px 15px rgba(0,0,0,0.5)}.modal-sm{width:300px}}@media (min-width:992px){.modal-lg{width:900px}}.tooltip{position:absolute;z-index:1070;display:block;font-family:"Helvetica Neue",Helvetica,Arial,sans-serif;font-style:normal;font-weight:normal;letter-spacing:normal;line-break:auto;line-height:1.42857143;text-align:left;text-align:start;text-decoration:none;text-shadow:none;text-transform:none;white-space:normal;word-break:normal;word-spacing:normal;word-wrap:normal;font-size:12px;opacity:0;filter:alpha(opacity=0)}.tooltip.in{opacity:0.9;filter:alpha(opacity=90)}.tooltip.top{margin-top:-3px;padding:5px 0}.tooltip.right{margin-left:3px;padding:0 5px}.tooltip.bottom{margin-top:3px;padding:5px 0}.tooltip.left{margin-left:-3px;padding:0 5px}.tooltip-inner{max-width:200px;padding:3px 8px;color:#ffffff;text-align:center;background-color:#000000;border-radius:4px}.tooltip-arrow{position:absolute;width:0;height:0;border-color:transparent;border-style:solid}.tooltip.top .tooltip-arrow{bottom:0;left:50%;margin-left:-5px;border-width:5px 5px 0;border-top-color:#000000}.tooltip.top-left .tooltip-arrow{bottom:0;right:5px;margin-bottom:-5px;border-width:5px 5px 0;border-top-color:#000000}.tooltip.top-right .tooltip-arrow{bottom:0;left:5px;margin-bottom:-5px;border-width:5px 5px 0;border-top-color:#000000}.tooltip.right .tooltip-arrow{top:50%;left:0;margin-top:-5px;border-width:5px 5px 5px 0;border-right-color:#000000}.tooltip.left .tooltip-arrow{top:50%;right:0;margin-top:-5px;border-width:5px 0 5px 5px;border-left-color:#000000}.tooltip.bottom .tooltip-arrow{top:0;left:50%;margin-left:-5px;border-width:0 5px 5px;border-bottom-color:#000000}.tooltip.bottom-left .tooltip-arrow{top:0;right:5px;margin-top:-5px;border-width:0 5px 5px;border-bottom-color:#000000}.tooltip.bottom-right .tooltip-arrow{top:0;left:5px;margin-top:-5px;border-width:0 5px 5px;border-bottom-color:#000000}.popover{position:absolute;top:0;left:0;z-index:1060;display:none;max-width:276px;padding:1px;font-family:"Helvetica Neue",Helvetica,Arial,sans-serif;font-style:normal;font-weight:normal;letter-spacing:normal;line-break:auto;line-height:1.42857143;text-align:left;text-align:start;text-decoration:none;text-shadow:none;text-transform:none;white-space:normal;word-break:normal;word-spacing:normal;word-wrap:normal;font-size:14px;background-color:#2e3338;-webkit-background-clip:padding-box;background-clip:padding-box;border:1px solid #999999;border:1px solid rgba(0,0,0,0.2);border-radius:6px;-webkit-box-shadow:0 5px 10px rgba(0,0,0,0.2);box-shadow:0 5px 10px rgba(0,0,0,0.2)}.popover.top{margin-top:-10px}.popover.right{margin-left:10px}.popover.bottom{margin-top:10px}.popover.left{margin-left:-10px}.popover-title{margin:0;padding:8px 14px;font-size:14px;background-color:#2e3338;border-bottom:1px solid #22262a;border-radius:5px 5px 0 0}.popover-content{padding:9px 14px}.popover>.arrow,.popover>.arrow:after{position:absolute;display:block;width:0;height:0;border-color:transparent;border-style:solid}.popover>.arrow{border-width:11px}.popover>.arrow:after{border-width:10px;content:""}.popover.top>.arrow{left:50%;margin-left:-11px;border-bottom-width:0;border-top-color:#666666;border-top-color:rgba(0,0,0,0.25);bottom:-11px}.popover.top>.arrow:after{content:" ";bottom:1px;margin-left:-10px;border-bottom-width:0;border-top-color:#2e3338}.popover.right>.arrow{top:50%;left:-11px;margin-top:-11px;border-left-width:0;border-right-color:#666666;border-right-color:rgba(0,0,0,0.25)}.popover.right>.arrow:after{content:" ";left:1px;bottom:-10px;border-left-width:0;border-right-color:#2e3338}.popover.bottom>.arrow{left:50%;margin-left:-11px;border-top-width:0;border-bottom-color:#666666;border-bottom-color:rgba(0,0,0,0.25);top:-11px}.popover.bottom>.arrow:after{content:" ";top:1px;margin-left:-10px;border-top-width:0;border-bottom-color:#2e3338}.popover.left>.arrow{top:50%;right:-11px;margin-top:-11px;border-right-width:0;border-left-color:#666666;border-left-color:rgba(0,0,0,0.25)}.popover.left>.arrow:after{content:" ";right:1px;border-right-width:0;border-left-color:#2e3338;bottom:-10px}.carousel{position:relative}.carousel-inner{position:relative;overflow:hidden;width:100%}.carousel-inner>.item{display:none;position:relative;-webkit-transition:.6s ease-in-out left;-o-transition:.6s ease-in-out left;transition:.6s ease-in-out left}.carousel-inner>.item>img,.carousel-inner>.item>a>img{line-height:1}@media all and (transform-3d),(-webkit-transform-3d){.carousel-inner>.item{-webkit-transition:-webkit-transform .6s ease-in-out;-o-transition:-o-transform .6s ease-in-out;transition:transform .6s ease-in-out;-webkit-backface-visibility:hidden;backface-visibility:hidden;-webkit-perspective:1000px;perspective:1000px}.carousel-inner>.item.next,.carousel-inner>.item.active.right{-webkit-transform:translate3d(100%, 0, 0);transform:translate3d(100%, 0, 0);left:0}.carousel-inner>.item.prev,.carousel-inner>.item.active.left{-webkit-transform:translate3d(-100%, 0, 0);transform:translate3d(-100%, 0, 0);left:0}.carousel-inner>.item.next.left,.carousel-inner>.item.prev.right,.carousel-inner>.item.active{-webkit-transform:translate3d(0, 0, 0);transform:translate3d(0, 0, 0);left:0}}.carousel-inner>.active,.carousel-inner>.next,.carousel-inner>.prev{display:block}.carousel-inner>.active{left:0}.carousel-inner>.next,.carousel-inner>.prev{position:absolute;top:0;width:100%}.carousel-inner>.next{left:100%}.carousel-inner>.prev{left:-100%}.carousel-inner>.next.left,.carousel-inner>.prev.right{left:0}.carousel-inner>.active.left{left:-100%}.carousel-inner>.active.right{left:100%}.carousel-control{position:absolute;top:0;left:0;bottom:0;width:15%;opacity:0.5;filter:alpha(opacity=50);font-size:20px;color:#ffffff;text-align:center;text-shadow:0 1px 2px rgba(0,0,0,0.6);background-color:rgba(0,0,0,0)}.carousel-control.left{background-image:-webkit-linear-gradient(left, rgba(0,0,0,0.5) 0, rgba(0,0,0,0.0001) 100%);background-image:-o-linear-gradient(left, rgba(0,0,0,0.5) 0, rgba(0,0,0,0.0001) 100%);background-image:-webkit-gradient(linear, left top, right top, from(rgba(0,0,0,0.5)), to(rgba(0,0,0,0.0001)));background-image:linear-gradient(to right, rgba(0,0,0,0.5) 0, rgba(0,0,0,0.0001) 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#80000000', endColorstr='#00000000', GradientType=1)}.carousel-control.right{left:auto;right:0;background-image:-webkit-linear-gradient(left, rgba(0,0,0,0.0001) 0, rgba(0,0,0,0.5) 100%);background-image:-o-linear-gradient(left, rgba(0,0,0,0.0001) 0, rgba(0,0,0,0.5) 100%);background-image:-webkit-gradient(linear, left top, right top, from(rgba(0,0,0,0.0001)), to(rgba(0,0,0,0.5)));background-image:linear-gradient(to right, rgba(0,0,0,0.0001) 0, rgba(0,0,0,0.5) 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#00000000', endColorstr='#80000000', GradientType=1)}.carousel-control:hover,.carousel-control:focus{outline:0;color:#ffffff;text-decoration:none;opacity:0.9;filter:alpha(opacity=90)}.carousel-control .icon-prev,.carousel-control .icon-next,.carousel-control .glyphicon-chevron-left,.carousel-control .glyphicon-chevron-right{position:absolute;top:50%;margin-top:-10px;z-index:5;display:inline-block}.carousel-control .icon-prev,.carousel-control .glyphicon-chevron-left{left:50%;margin-left:-10px}.carousel-control .icon-next,.carousel-control .glyphicon-chevron-right{right:50%;margin-right:-10px}.carousel-control .icon-prev,.carousel-control .icon-next{width:20px;height:20px;line-height:1;font-family:serif}.carousel-control .icon-prev:before{content:'\2039'}.carousel-control .icon-next:before{content:'\203a'}.carousel-indicators{position:absolute;bottom:10px;left:50%;z-index:15;width:60%;margin-left:-30%;padding-left:0;list-style:none;text-align:center}.carousel-indicators li{display:inline-block;width:10px;height:10px;margin:1px;text-indent:-999px;border:1px solid #ffffff;border-radius:10px;cursor:pointer;background-color:#000 \9;background-color:rgba(0,0,0,0)}.carousel-indicators .active{margin:0;width:12px;height:12px;background-color:#ffffff}.carousel-caption{position:absolute;left:15%;right:15%;bottom:20px;z-index:10;padding-top:20px;padding-bottom:20px;color:#ffffff;text-align:center;text-shadow:0 1px 2px rgba(0,0,0,0.6)}.carousel-caption .btn{text-shadow:none}@media screen and (min-width:768px){.carousel-control .glyphicon-chevron-left,.carousel-control .glyphicon-chevron-right,.carousel-control .icon-prev,.carousel-control .icon-next{width:30px;height:30px;margin-top:-10px;font-size:30px}.carousel-control .glyphicon-chevron-left,.carousel-control .icon-prev{margin-left:-10px}.carousel-control .glyphicon-chevron-right,.carousel-control .icon-next{margin-right:-10px}.carousel-caption{left:20%;right:20%;padding-bottom:30px}.carousel-indicators{bottom:20px}}.clearfix:before,.clearfix:after,.dl-horizontal dd:before,.dl-horizontal dd:after,.container:before,.container:after,.container-fluid:before,.container-fluid:after,.row:before,.row:after,.form-horizontal .form-group:before,.form-horizontal .form-group:after,.btn-toolbar:before,.btn-toolbar:after,.btn-group-vertical>.btn-group:before,.btn-group-vertical>.btn-group:after,.nav:before,.nav:after,.navbar:before,.navbar:after,.navbar-header:before,.navbar-header:after,.navbar-collapse:before,.navbar-collapse:after,.pager:before,.pager:after,.panel-body:before,.panel-body:after,.modal-header:before,.modal-header:after,.modal-footer:before,.modal-footer:after{content:" ";display:table}.clearfix:after,.dl-horizontal dd:after,.container:after,.container-fluid:after,.row:after,.form-horizontal .form-group:after,.btn-toolbar:after,.btn-group-vertical>.btn-group:after,.nav:after,.navbar:after,.navbar-header:after,.navbar-collapse:after,.pager:after,.panel-body:after,.modal-header:after,.modal-footer:after{clear:both}.center-block{display:block;margin-left:auto;margin-right:auto}.pull-right{float:right !important}.pull-left{float:left !important}.hide{display:none !important}.show{display:block !important}.invisible{visibility:hidden}.text-hide{font:0/0 a;color:transparent;text-shadow:none;background-color:transparent;border:0}.hidden{display:none !important}.affix{position:fixed}@-ms-viewport{width:device-width}.visible-xs,.visible-sm,.visible-md,.visible-lg{display:none !important}.visible-xs-block,.visible-xs-inline,.visible-xs-inline-block,.visible-sm-block,.visible-sm-inline,.visible-sm-inline-block,.visible-md-block,.visible-md-inline,.visible-md-inline-block,.visible-lg-block,.visible-lg-inline,.visible-lg-inline-block{display:none !important}@media (max-width:767px){.visible-xs{display:block !important}table.visible-xs{display:table !important}tr.visible-xs{display:table-row !important}th.visible-xs,td.visible-xs{display:table-cell !important}}@media (max-width:767px){.visible-xs-block{display:block !important}}@media (max-width:767px){.visible-xs-inline{display:inline !important}}@media (max-width:767px){.visible-xs-inline-block{display:inline-block !important}}@media (min-width:768px) and (max-width:991px){.visible-sm{display:block !important}table.visible-sm{display:table !important}tr.visible-sm{display:table-row !important}th.visible-sm,td.visible-sm{display:table-cell !important}}@media (min-width:768px) and (max-width:991px){.visible-sm-block{display:block !important}}@media (min-width:768px) and (max-width:991px){.visible-sm-inline{display:inline !important}}@media (min-width:768px) and (max-width:991px){.visible-sm-inline-block{display:inline-block !important}}@media (min-width:992px) and (max-width:1199px){.visible-md{display:block !important}table.visible-md{display:table !important}tr.visible-md{display:table-row !important}th.visible-md,td.visible-md{display:table-cell !important}}@media (min-width:992px) and (max-width:1199px){.visible-md-block{display:block !important}}@media (min-width:992px) and (max-width:1199px){.visible-md-inline{display:inline !important}}@media (min-width:992px) and (max-width:1199px){.visible-md-inline-block{display:inline-block !important}}@media (min-width:1200px){.visible-lg{display:block !important}table.visible-lg{display:table !important}tr.visible-lg{display:table-row !important}th.visible-lg,td.visible-lg{display:table-cell !important}}@media (min-width:1200px){.visible-lg-block{display:block !important}}@media (min-width:1200px){.visible-lg-inline{display:inline !important}}@media (min-width:1200px){.visible-lg-inline-block{display:inline-block !important}}@media (max-width:767px){.hidden-xs{display:none !important}}@media (min-width:768px) and (max-width:991px){.hidden-sm{display:none !important}}@media (min-width:992px) and (max-width:1199px){.hidden-md{display:none !important}}@media (min-width:1200px){.hidden-lg{display:none !important}}.visible-print{display:none !important}@media print{.visible-print{display:block !important}table.visible-print{display:table !important}tr.visible-print{display:table-row !important}th.visible-print,td.visible-print{display:table-cell !important}}.visible-print-block{display:none !important}@media print{.visible-print-block{display:block !important}}.visible-print-inline{display:none !important}@media print{.visible-print-inline{display:inline !important}}.visible-print-inline-block{display:none !important}@media print{.visible-print-inline-block{display:inline-block !important}}@media print{.hidden-print{display:none !important}}.navbar{background-image:-webkit-linear-gradient(#484e55, #3a3f44 60%, #313539);background-image:-o-linear-gradient(#484e55, #3a3f44 60%, #313539);background-image:-webkit-gradient(linear, left top, left bottom, from(#484e55), color-stop(60%, #3a3f44), to(#313539));background-image:linear-gradient(#484e55, #3a3f44 60%, #313539);background-repeat:no-repeat;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff484e55', endColorstr='#ff313539', GradientType=0);-webkit-filter:none;filter:none;border:1px solid rgba(0,0,0,0.6);text-shadow:1px 1px 1px rgba(0,0,0,0.3)}.navbar-inverse{background-image:-webkit-linear-gradient(#8a9196, #7a8288 60%, #70787d);background-image:-o-linear-gradient(#8a9196, #7a8288 60%, #70787d);background-image:-webkit-gradient(linear, left top, left bottom, from(#8a9196), color-stop(60%, #7a8288), to(#70787d));background-image:linear-gradient(#8a9196, #7a8288 60%, #70787d);background-repeat:no-repeat;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff8a9196', endColorstr='#ff70787d', GradientType=0);-webkit-filter:none;filter:none}.navbar-inverse .badge{background-color:#5d6368}.navbar-nav>li>a{border-right:1px solid rgba(0,0,0,0.2);border-left:1px solid rgba(255,255,255,0.1)}.navbar-nav>li>a:hover{background-image:-webkit-linear-gradient(#020202, #101112 40%, #191b1d);background-image:-o-linear-gradient(#020202, #101112 40%, #191b1d);background-image:-webkit-gradient(linear, left top, left bottom, from(#020202), color-stop(40%, #101112), to(#191b1d));background-image:linear-gradient(#020202, #101112 40%, #191b1d);background-repeat:no-repeat;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff020202', endColorstr='#ff191b1d', GradientType=0);-webkit-filter:none;filter:none;border-left-color:transparent}.navbar .nav .open>a{border-color:transparent}.navbar-nav>li.active>a{border-left-color:transparent}.navbar-form{margin-left:5px;margin-right:5px}.btn,.btn:hover{border-color:rgba(0,0,0,0.6);text-shadow:1px 1px 1px rgba(0,0,0,0.3)}.btn-default{background-image:-webkit-linear-gradient(#484e55, #3a3f44 60%, #313539);background-image:-o-linear-gradient(#484e55, #3a3f44 60%, #313539);background-image:-webkit-gradient(linear, left top, left bottom, from(#484e55), color-stop(60%, #3a3f44), to(#313539));background-image:linear-gradient(#484e55, #3a3f44 60%, #313539);background-repeat:no-repeat;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff484e55', endColorstr='#ff313539', GradientType=0);-webkit-filter:none;filter:none}.btn-default:hover{background-image:-webkit-linear-gradient(#020202, #101112 40%, #191b1d);background-image:-o-linear-gradient(#020202, #101112 40%, #191b1d);background-image:-webkit-gradient(linear, left top, left bottom, from(#020202), color-stop(40%, #101112), to(#191b1d));background-image:linear-gradient(#020202, #101112 40%, #191b1d);background-repeat:no-repeat;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff020202', endColorstr='#ff191b1d', GradientType=0);-webkit-filter:none;filter:none}.btn-primary{background-image:-webkit-linear-gradient(#8a9196, #7a8288 60%, #70787d);background-image:-o-linear-gradient(#8a9196, #7a8288 60%, #70787d);background-image:-webkit-gradient(linear, left top, left bottom, from(#8a9196), color-stop(60%, #7a8288), to(#70787d));background-image:linear-gradient(#8a9196, #7a8288 60%, #70787d);background-repeat:no-repeat;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff8a9196', endColorstr='#ff70787d', GradientType=0);-webkit-filter:none;filter:none}.btn-primary:hover{background-image:-webkit-linear-gradient(#404448, #4e5458 40%, #585e62);background-image:-o-linear-gradient(#404448, #4e5458 40%, #585e62);background-image:-webkit-gradient(linear, left top, left bottom, from(#404448), color-stop(40%, #4e5458), to(#585e62));background-image:linear-gradient(#404448, #4e5458 40%, #585e62);background-repeat:no-repeat;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff404448', endColorstr='#ff585e62', GradientType=0);-webkit-filter:none;filter:none}.btn-success{background-image:-webkit-linear-gradient(#78cc78, #62c462 60%, #53be53);background-image:-o-linear-gradient(#78cc78, #62c462 60%, #53be53);background-image:-webkit-gradient(linear, left top, left bottom, from(#78cc78), color-stop(60%, #62c462), to(#53be53));background-image:linear-gradient(#78cc78, #62c462 60%, #53be53);background-repeat:no-repeat;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff78cc78', endColorstr='#ff53be53', GradientType=0);-webkit-filter:none;filter:none}.btn-success:hover{background-image:-webkit-linear-gradient(#2f7d2f, #379337 40%, #3da23d);background-image:-o-linear-gradient(#2f7d2f, #379337 40%, #3da23d);background-image:-webkit-gradient(linear, left top, left bottom, from(#2f7d2f), color-stop(40%, #379337), to(#3da23d));background-image:linear-gradient(#2f7d2f, #379337 40%, #3da23d);background-repeat:no-repeat;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff2f7d2f', endColorstr='#ff3da23d', GradientType=0);-webkit-filter:none;filter:none}.btn-info{background-image:-webkit-linear-gradient(#74cae3, #5bc0de 60%, #4ab9db);background-image:-o-linear-gradient(#74cae3, #5bc0de 60%, #4ab9db);background-image:-webkit-gradient(linear, left top, left bottom, from(#74cae3), color-stop(60%, #5bc0de), to(#4ab9db));background-image:linear-gradient(#74cae3, #5bc0de 60%, #4ab9db);background-repeat:no-repeat;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff74cae3', endColorstr='#ff4ab9db', GradientType=0);-webkit-filter:none;filter:none}.btn-info:hover{background-image:-webkit-linear-gradient(#20829f, #2596b8 40%, #28a4c9);background-image:-o-linear-gradient(#20829f, #2596b8 40%, #28a4c9);background-image:-webkit-gradient(linear, left top, left bottom, from(#20829f), color-stop(40%, #2596b8), to(#28a4c9));background-image:linear-gradient(#20829f, #2596b8 40%, #28a4c9);background-repeat:no-repeat;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff20829f', endColorstr='#ff28a4c9', GradientType=0);-webkit-filter:none;filter:none}.btn-warning{background-image:-webkit-linear-gradient(#faa123, #f89406 60%, #e48806);background-image:-o-linear-gradient(#faa123, #f89406 60%, #e48806);background-image:-webkit-gradient(linear, left top, left bottom, from(#faa123), color-stop(60%, #f89406), to(#e48806));background-image:linear-gradient(#faa123, #f89406 60%, #e48806);background-repeat:no-repeat;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fffaa123', endColorstr='#ffe48806', GradientType=0);-webkit-filter:none;filter:none}.btn-warning:hover{background-image:-webkit-linear-gradient(#804d03, #9e5f04 40%, #b26a04);background-image:-o-linear-gradient(#804d03, #9e5f04 40%, #b26a04);background-image:-webkit-gradient(linear, left top, left bottom, from(#804d03), color-stop(40%, #9e5f04), to(#b26a04));background-image:linear-gradient(#804d03, #9e5f04 40%, #b26a04);background-repeat:no-repeat;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff804d03', endColorstr='#ffb26a04', GradientType=0);-webkit-filter:none;filter:none}.btn-danger{background-image:-webkit-linear-gradient(#f17a77, #ee5f5b 60%, #ec4d49);background-image:-o-linear-gradient(#f17a77, #ee5f5b 60%, #ec4d49);background-image:-webkit-gradient(linear, left top, left bottom, from(#f17a77), color-stop(60%, #ee5f5b), to(#ec4d49));background-image:linear-gradient(#f17a77, #ee5f5b 60%, #ec4d49);background-repeat:no-repeat;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff17a77', endColorstr='#ffec4d49', GradientType=0);-webkit-filter:none;filter:none}.btn-danger:hover{background-image:-webkit-linear-gradient(#bb1813, #d71c16 40%, #e7201a);background-image:-o-linear-gradient(#bb1813, #d71c16 40%, #e7201a);background-image:-webkit-gradient(linear, left top, left bottom, from(#bb1813), color-stop(40%, #d71c16), to(#e7201a));background-image:linear-gradient(#bb1813, #d71c16 40%, #e7201a);background-repeat:no-repeat;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffbb1813', endColorstr='#ffe7201a', GradientType=0);-webkit-filter:none;filter:none}.btn-link,.btn-link:hover{border-color:transparent}h1,h2,h3,h4,h5,h6{text-shadow:-1px -1px 0 rgba(0,0,0,0.3)}.text-primary,.text-primary:hover{color:#7a8288}.text-success,.text-success:hover{color:#62c462}.text-danger,.text-danger:hover{color:#ee5f5b}.text-warning,.text-warning:hover{color:#f89406}.text-info,.text-info:hover{color:#5bc0de}.table .success,.table .warning,.table .danger,.table .info{color:#fff}.table-bordered tbody tr.success td,.table-bordered tbody tr.warning td,.table-bordered tbody tr.danger td,.table-bordered tbody tr.success:hover td,.table-bordered tbody tr.warning:hover td,.table-bordered tbody tr.danger:hover td{border-color:#1c1e22}.table-responsive>.table{background-color:#2e3338}input,textarea{color:#272b30}.has-warning .help-block,.has-warning .control-label,.has-warning .radio,.has-warning .checkbox,.has-warning .radio-inline,.has-warning .checkbox-inline,.has-warning.radio label,.has-warning.checkbox label,.has-warning.radio-inline label,.has-warning.checkbox-inline label,.has-warning .form-control-feedback{color:#f89406}.has-warning .form-control,.has-warning .form-control:focus{border-color:#f89406}.has-warning .input-group-addon{background-color:#272b30;border:none}.has-error .help-block,.has-error .control-label,.has-error .radio,.has-error .checkbox,.has-error .radio-inline,.has-error .checkbox-inline,.has-error.radio label,.has-error.checkbox label,.has-error.radio-inline label,.has-error.checkbox-inline label,.has-error .form-control-feedback{color:#ee5f5b}.has-error .form-control,.has-error .form-control:focus{border-color:#ee5f5b}.has-error .input-group-addon{background-color:#272b30;border:none}.has-success .help-block,.has-success .control-label,.has-success .radio,.has-success .checkbox,.has-success .radio-inline,.has-success .checkbox-inline,.has-success.radio label,.has-success.checkbox label,.has-success.radio-inline label,.has-success.checkbox-inline label,.has-success .form-control-feedback{color:#62c462}.has-success .form-control,.has-success .form-control:focus{border-color:#62c462}.has-success .input-group-addon{background-color:#272b30;border:none}legend{color:#fff}.input-group-addon{border-color:rgba(0,0,0,0.6);text-shadow:1px 1px 1px rgba(0,0,0,0.3);background-image:-webkit-linear-gradient(#484e55, #3a3f44 60%, #313539);background-image:-o-linear-gradient(#484e55, #3a3f44 60%, #313539);background-image:-webkit-gradient(linear, left top, left bottom, from(#484e55), color-stop(60%, #3a3f44), to(#313539));background-image:linear-gradient(#484e55, #3a3f44 60%, #313539);background-repeat:no-repeat;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff484e55', endColorstr='#ff313539', GradientType=0);-webkit-filter:none;filter:none;color:#ffffff}.nav .open>a,.nav .open>a:hover,.nav .open>a:focus{border-color:rgba(0,0,0,0.6)}.nav-pills>li>a{background-image:-webkit-linear-gradient(#484e55, #3a3f44 60%, #313539);background-image:-o-linear-gradient(#484e55, #3a3f44 60%, #313539);background-image:-webkit-gradient(linear, left top, left bottom, from(#484e55), color-stop(60%, #3a3f44), to(#313539));background-image:linear-gradient(#484e55, #3a3f44 60%, #313539);background-repeat:no-repeat;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff484e55', endColorstr='#ff313539', GradientType=0);-webkit-filter:none;filter:none;border:1px solid rgba(0,0,0,0.6);text-shadow:1px 1px 1px rgba(0,0,0,0.3)}.nav-pills>li>a:hover{background-image:-webkit-linear-gradient(#020202, #101112 40%, #191b1d);background-image:-o-linear-gradient(#020202, #101112 40%, #191b1d);background-image:-webkit-gradient(linear, left top, left bottom, from(#020202), color-stop(40%, #101112), to(#191b1d));background-image:linear-gradient(#020202, #101112 40%, #191b1d);background-repeat:no-repeat;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff020202', endColorstr='#ff191b1d', GradientType=0);-webkit-filter:none;filter:none;border:1px solid rgba(0,0,0,0.6)}.nav-pills>li.active>a,.nav-pills>li.active>a:hover{background-color:none;background-image:-webkit-linear-gradient(#020202, #101112 40%, #191b1d);background-image:-o-linear-gradient(#020202, #101112 40%, #191b1d);background-image:-webkit-gradient(linear, left top, left bottom, from(#020202), color-stop(40%, #101112), to(#191b1d));background-image:linear-gradient(#020202, #101112 40%, #191b1d);background-repeat:no-repeat;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff020202', endColorstr='#ff191b1d', GradientType=0);-webkit-filter:none;filter:none;border:1px solid rgba(0,0,0,0.6)}.nav-pills>li.disabled>a,.nav-pills>li.disabled>a:hover{background-image:-webkit-linear-gradient(#484e55, #3a3f44 60%, #313539);background-image:-o-linear-gradient(#484e55, #3a3f44 60%, #313539);background-image:-webkit-gradient(linear, left top, left bottom, from(#484e55), color-stop(60%, #3a3f44), to(#313539));background-image:linear-gradient(#484e55, #3a3f44 60%, #313539);background-repeat:no-repeat;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff484e55', endColorstr='#ff313539', GradientType=0);-webkit-filter:none;filter:none}.pagination>li>a,.pagination>li>span{text-shadow:1px 1px 1px rgba(0,0,0,0.3);background-image:-webkit-linear-gradient(#484e55, #3a3f44 60%, #313539);background-image:-o-linear-gradient(#484e55, #3a3f44 60%, #313539);background-image:-webkit-gradient(linear, left top, left bottom, from(#484e55), color-stop(60%, #3a3f44), to(#313539));background-image:linear-gradient(#484e55, #3a3f44 60%, #313539);background-repeat:no-repeat;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff484e55', endColorstr='#ff313539', GradientType=0);-webkit-filter:none;filter:none}.pagination>li>a:hover,.pagination>li>span:hover{background-image:-webkit-linear-gradient(#020202, #101112 40%, #191b1d);background-image:-o-linear-gradient(#020202, #101112 40%, #191b1d);background-image:-webkit-gradient(linear, left top, left bottom, from(#020202), color-stop(40%, #101112), to(#191b1d));background-image:linear-gradient(#020202, #101112 40%, #191b1d);background-repeat:no-repeat;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff020202', endColorstr='#ff191b1d', GradientType=0);-webkit-filter:none;filter:none}.pagination>li.active>a,.pagination>li.active>span{background-image:-webkit-linear-gradient(#020202, #101112 40%, #191b1d);background-image:-o-linear-gradient(#020202, #101112 40%, #191b1d);background-image:-webkit-gradient(linear, left top, left bottom, from(#020202), color-stop(40%, #101112), to(#191b1d));background-image:linear-gradient(#020202, #101112 40%, #191b1d);background-repeat:no-repeat;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff020202', endColorstr='#ff191b1d', GradientType=0);-webkit-filter:none;filter:none}.pagination>li.disabled>a,.pagination>li.disabled>a:hover,.pagination>li.disabled>span,.pagination>li.disabled>span:hover{background-color:transparent;background-image:-webkit-linear-gradient(#484e55, #3a3f44 60%, #313539);background-image:-o-linear-gradient(#484e55, #3a3f44 60%, #313539);background-image:-webkit-gradient(linear, left top, left bottom, from(#484e55), color-stop(60%, #3a3f44), to(#313539));background-image:linear-gradient(#484e55, #3a3f44 60%, #313539);background-repeat:no-repeat;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff484e55', endColorstr='#ff313539', GradientType=0);-webkit-filter:none;filter:none}.pager>li>a{background-image:-webkit-linear-gradient(#484e55, #3a3f44 60%, #313539);background-image:-o-linear-gradient(#484e55, #3a3f44 60%, #313539);background-image:-webkit-gradient(linear, left top, left bottom, from(#484e55), color-stop(60%, #3a3f44), to(#313539));background-image:linear-gradient(#484e55, #3a3f44 60%, #313539);background-repeat:no-repeat;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff484e55', endColorstr='#ff313539', GradientType=0);-webkit-filter:none;filter:none;text-shadow:1px 1px 1px rgba(0,0,0,0.3)}.pager>li>a:hover{background-image:-webkit-linear-gradient(#020202, #101112 40%, #191b1d);background-image:-o-linear-gradient(#020202, #101112 40%, #191b1d);background-image:-webkit-gradient(linear, left top, left bottom, from(#020202), color-stop(40%, #101112), to(#191b1d));background-image:linear-gradient(#020202, #101112 40%, #191b1d);background-repeat:no-repeat;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff020202', endColorstr='#ff191b1d', GradientType=0);-webkit-filter:none;filter:none}.pager>li.disabled>a,.pager>li.disabled>a:hover{background-color:transparent;background-image:-webkit-linear-gradient(#484e55, #3a3f44 60%, #313539);background-image:-o-linear-gradient(#484e55, #3a3f44 60%, #313539);background-image:-webkit-gradient(linear, left top, left bottom, from(#484e55), color-stop(60%, #3a3f44), to(#313539));background-image:linear-gradient(#484e55, #3a3f44 60%, #313539);background-repeat:no-repeat;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff484e55', endColorstr='#ff313539', GradientType=0);-webkit-filter:none;filter:none}.breadcrumb{border:1px solid rgba(0,0,0,0.6);text-shadow:1px 1px 1px rgba(0,0,0,0.3);background-image:-webkit-linear-gradient(#484e55, #3a3f44 60%, #313539);background-image:-o-linear-gradient(#484e55, #3a3f44 60%, #313539);background-image:-webkit-gradient(linear, left top, left bottom, from(#484e55), color-stop(60%, #3a3f44), to(#313539));background-image:linear-gradient(#484e55, #3a3f44 60%, #313539);background-repeat:no-repeat;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff484e55', endColorstr='#ff313539', GradientType=0);-webkit-filter:none;filter:none}.alert .alert-link,.alert a{color:#fff;text-decoration:underline}.alert .close{color:#000000;text-decoration:none}a.thumbnail:hover,a.thumbnail:focus,a.thumbnail.active{border-color:#0c0d0e}a.list-group-item.active,a.list-group-item.active:hover,a.list-group-item.active:focus{border-color:rgba(0,0,0,0.6)}a.list-group-item-success.active{background-color:#62c462}a.list-group-item-success.active:hover,a.list-group-item-success.active:focus{background-color:#4fbd4f}a.list-group-item-warning.active{background-color:#f89406}a.list-group-item-warning.active:hover,a.list-group-item-warning.active:focus{background-color:#df8505}a.list-group-item-danger.active{background-color:#ee5f5b}a.list-group-item-danger.active:hover,a.list-group-item-danger.active:focus{background-color:#ec4844}.jumbotron{border:1px solid rgba(0,0,0,0.6)}.panel-primary .panel-heading,.panel-success .panel-heading,.panel-danger .panel-heading,.panel-warning .panel-heading,.panel-info .panel-heading{border-color:#000} \ No newline at end of file diff --git a/admin/static/fonts/FantasqueSansMono-Regular.woff b/admin/static/fonts/FantasqueSansMono-Regular.woff deleted file mode 100644 index c88cd830..00000000 Binary files a/admin/static/fonts/FantasqueSansMono-Regular.woff and /dev/null differ diff --git a/admin/static/fonts/LinBiolinum_R.woff b/admin/static/fonts/LinBiolinum_R.woff deleted file mode 100644 index 5c399fd8..00000000 Binary files a/admin/static/fonts/LinBiolinum_R.woff and /dev/null differ diff --git a/admin/static/fonts/LinBiolinum_RB.woff b/admin/static/fonts/LinBiolinum_RB.woff deleted file mode 100644 index fbeead0f..00000000 Binary files a/admin/static/fonts/LinBiolinum_RB.woff and /dev/null differ diff --git a/admin/static/fonts/LinBiolinum_RI.woff b/admin/static/fonts/LinBiolinum_RI.woff deleted file mode 100644 index 40661f13..00000000 Binary files a/admin/static/fonts/LinBiolinum_RI.woff and /dev/null differ diff --git a/admin/static/img/epita.png b/admin/static/img/epita.png new file mode 100644 index 00000000..e89f7181 Binary files /dev/null and b/admin/static/img/epita.png differ diff --git a/admin/static/img/fic.png b/admin/static/img/fic.png new file mode 100644 index 00000000..7956bd4d Binary files /dev/null and b/admin/static/img/fic.png differ diff --git a/admin/static/img/srs.png b/admin/static/img/srs.png new file mode 100644 index 00000000..82294f52 Binary files /dev/null and b/admin/static/img/srs.png differ diff --git a/admin/static/index.html b/admin/static/index.html new file mode 100644 index 00000000..c409ffa9 --- /dev/null +++ b/admin/static/index.html @@ -0,0 +1,51 @@ + + + + + Challenge Forensic - Administration + + + + + + + +
+
+
+
+
+ + + + + + + + + diff --git a/admin/static/js/angular-resource.min.js b/admin/static/js/angular-resource.min.js index 8b924c37..c3fb7aab 100644 --- a/admin/static/js/angular-resource.min.js +++ b/admin/static/js/angular-resource.min.js @@ -1,15 +1,14 @@ /* - AngularJS v1.7.9 - (c) 2010-2018 Google, Inc. http://angularjs.org + AngularJS v1.4.8 + (c) 2010-2015 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/g,">")}function A(a){for(;a;){if(a.nodeType===s.Node.ELEMENT_NODE)for(var e=a.attributes,d=0,b=e.length;d"))},end:function(a){a=q(a);d||!0!==m[a]||!0===r[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=""+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='';return c.querySelector("svg")? -(c.innerHTML='

',c.querySelector("svg img")?d:b):function(b){b=""+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("');r(g);l.push("")}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 diff --git a/admin/static/js/angular.min.js b/admin/static/js/angular.min.js index f6bf3370..b4f9b07e 100644 --- a/admin/static/js/angular.min.js +++ b/admin/static/js/angular.min.js @@ -1,350 +1,295 @@ /* - AngularJS v1.7.9 - (c) 2010-2018 Google, Inc. http://angularjs.org + AngularJS v1.4.8 + (c) 2010-2015 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)&&0c)return"...";var d=b.$$hashKey,f;if(H(a)){f=0;for(var g=a.length;f").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 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(//,">"));}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")+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&&0l&&this.remove(n.key);return b}},get:function(a){if(l";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("

").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;mu.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;gc.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"+b+"";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=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;da?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=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=c.$$state.status&&e&&e.length&&a(function(){for(var a,c,f=0,g=e.length;fa)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=1r&&(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;JCa)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;kc&&(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(0d-1){for(c=0;c>d;c--)e.unshift(0),a.i++;e.unshift(1);a.i++}else e[d-1]++;for(;fk;)h.unshift(0),k++;0=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-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=hb||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){cf.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?-1a&&(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(?:<\/\1>|)$/,mc=/<|&#?\w+;/,mg=/<([\w:-]+)/,ng=/<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:-]+)[^>]*)\/>/gi,oa={option:[1,'"],thead:[1,"","
"],col:[2,"","
"],tr:[2,"","
"],td:[3,"", -"
"],_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/,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 <= >= && || ! = |".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=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","<=",">=");)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":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.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=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;dg||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(''); +(function(S,X,u){'use strict';function G(a){return function(){var b=arguments[0],d;d="["+(a?a+":":"")+b+"] http://errors.angularjs.org/1.4.8/"+(a?a+"/":"")+b;for(b=1;b").append(a).html();try{return a[0].nodeType===Na?F(d):d.match(/^(<[^>]+>)/)[1].replace(/^<([\w\-]+)/,function(a,b){return"<"+F(b)})}catch(c){return F(d)}}function wc(a){try{return decodeURIComponent(a)}catch(b){}} +function xc(a){var b={};n((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=wc(e),y(e)&&(f=y(f)?wc(f):!0,qa.call(b,e)?I(b[e])?b[e].push(f):b[e]=[b[e],f]:b[e]=f))});return b}function Qb(a){var b=[];n(a,function(a,c){I(a)?n(a,function(a){b.push(ja(c,!0)+(!0===a?"":"="+ja(a,!0)))}):b.push(ja(c,!0)+(!0===a?"":"="+ja(a,!0)))});return b.length?b.join("&"):""}function ob(a){return ja(a,!0).replace(/%26/gi,"&").replace(/%3D/gi, +"=").replace(/%2B/gi,"+")}function ja(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 Yd(a,b){var d,c,e=Oa.length;for(c=0;c/,">"));}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=eb(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!/;S&&e.test(S.name)&&(d.debugInfoEnabled=!0,S.name=S.name.replace(e,""));if(S&&!f.test(S.name))return c();S.name=S.name.replace(f,"");fa.resumeBootstrap=function(a){n(a,function(a){b.push(a)});return c()};z(fa.resumeDeferredBootstrap)&&fa.resumeDeferredBootstrap()}function $d(){S.name="NG_ENABLE_DEBUG_INFO!"+S.name;S.location.reload()} +function ae(a){a=fa.element(a).injector();if(!a)throw Aa("test");return a.get("$$testability")}function zc(a,b){b=b||"_";return a.replace(be,function(a,c){return(c?b:"")+a.toLowerCase()})}function ce(){var a;if(!Ac){var b=pb();(oa=q(b)?S.jQuery:b?S[b]:u)&&oa.fn.on?(B=oa,M(oa.fn,{scope:Pa.scope,isolateScope:Pa.isolateScope,controller:Pa.controller,injector:Pa.injector,inheritedData:Pa.inheritedData}),a=oa.cleanData,oa.cleanData=function(b){var c;if(Rb)Rb=!1;else for(var e=0,f;null!=(f=b[e]);e++)(c= +oa._data(f,"events"))&&c.$destroy&&oa(f).triggerHandler("$destroy");a(b)}):B=N;fa.element=B;Ac=!0}}function qb(a,b,d){if(!a)throw Aa("areq",b||"?",d||"required");return a}function Qa(a,b,d){d&&I(a)&&(a=a[a.length-1]);qb(z(a),b,"not a function, got "+(a&&"object"===typeof a?a.constructor.name||"Object":typeof a));return a}function Ra(a,b){if("hasOwnProperty"===a)throw Aa("badname",b);}function Bc(a,b,d){if(!b)return a;b=b.split(".");for(var c,e=a,f=b.length,g=0;g")+c[2];for(c=c[0];c--;)d=d.lastChild;f=cb(f,d.childNodes);d=e.firstChild;d.textContent=""}else f.push(b.createTextNode(a));e.textContent="";e.innerHTML="";n(f,function(a){e.appendChild(a)});return e}function N(a){if(a instanceof N)return a;var b;E(a)&&(a=U(a), +b=!0);if(!(this instanceof N)){if(b&&"<"!=a.charAt(0))throw Ub("nosel");return new N(a)}if(b){b=X;var d;a=(d=Ef.exec(a))?[b.createElement(d[1])]:(d=Lc(a,b))?d.childNodes:[]}Mc(this,a)}function Vb(a){return a.cloneNode(!0)}function ub(a,b){b||vb(a);if(a.querySelectorAll)for(var d=a.querySelectorAll("*"),c=0,e=d.length;cl&&this.remove(t.key);return b}},get:function(a){if(l").parent()[0])});var f=O(a,b,a,c,d,e);K.$$addScopeClass(a);var g=null;return function(b,c,d){qb(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"!==ta(d)&&d.toString().match(/SVG/)?"svg":"html":"html");d="html"!==g?B(Yb(g,B("
").append(a).html())):c?Pa.clone.call(a):a;if(k)for(var l in k)d.data("$"+l+"Controller",k[l].instance);K.$$addScopeInfo(d,b);c&&c(d,b);f&&f(b,d,d,h);return d}}function O(a,b,c,d,e,f){function g(a,c,d,e){var f,k,l,m,t,w,D;if(p)for(D=Array(c.length),m=0;mq.priority)break;if(P=q.scope)q.templateUrl||(H(P)?(Ua("new/isolated scope",O||R,q,Z),O=q):Ua("new/isolated scope",O,q,Z)),R=R||q;x=q.name;!q.templateUrl&&q.controller&&(P=q.controller,T=T||$(),Ua("'"+x+"' controller",T[x],q,Z),T[x]=q);if(P=q.transclude)ga=!0,q.$$tlb||(Ua("transclusion",n,q,Z),n=q),"element"==P?(aa=!0,A=q.priority,P=Z,Z=d.$$element=B(X.createComment(" "+x+": "+d[x]+" ")),b=Z[0],Y(f,ra.call(P,0), +b),Ia=K(P,e,A,g&&g.name,{nonTlbTranscludeDirective:n})):(P=B(Vb(b)).contents(),Z.empty(),Ia=K(P,e,u,u,{needsNewScope:q.$$isolateScope||q.$$newScope}));if(q.template)if(L=!0,Ua("template",J,q,Z),J=q,P=z(q.template)?q.template(Z,d):q.template,P=ja(P),q.replace){g=q;P=Tb.test(P)?Xc(Yb(q.templateNamespace,U(P))):[];b=P[0];if(1!=P.length||1!==b.nodeType)throw ha("tplrt",x,"");Y(f,Z,b);P={$attr:{}};var Wc=V(b,[],P),W=a.splice(F+1,a.length-(F+1));(O||R)&&y(Wc,O,R);a=a.concat(Wc).concat(W);S(d,P);M=a.length}else Z.html(P); +if(q.templateUrl)L=!0,Ua("template",J,q,Z),J=q,q.replace&&(g=q),D=Of(a.splice(F,a.length-F),Z,d,f,ga&&Ia,h,l,{controllerDirectives:T,newScopeDirective:R!==q&&R,newIsolateScopeDirective:O,templateDirective:J,nonTlbTranscludeDirective:n}),M=a.length;else if(q.compile)try{G=q.compile(Z,d,Ia),z(G)?t(null,G,N,Q):G&&t(G.pre,G.post,N,Q)}catch(da){c(da,ua(Z))}q.terminal&&(D.terminal=!0,A=Math.max(A,q.priority))}D.scope=R&&!0===R.scope;D.transcludeOnThisElement=ga;D.templateOnThisElement=L;D.transclude=Ia; +m.hasElementTranscludeDirective=aa;return D}function y(a,b,c){for(var d=0,e=a.length;dm.priority)&&-1!=m.restrict.indexOf(f)&&(k&&(m=Ob(m,{$$start:k,$$end:l})),b.push(m),h=m)}catch(D){c(D)}}return h}function G(b){if(e.hasOwnProperty(b))for(var c=a.get(b+"Directive"),d=0,f=c.length;d"+b+"";return c.childNodes[0].childNodes;default:return b}}function Q(a,b){if("srcdoc"==b)return L.HTML;var c=ta(a);if("xlinkHref"==b||"form"==c&&"action"==b||"img"!=c&&("src"==b||"ngSrc"==b))return L.RESOURCE_URL}function W(a,c,d,e,f){var g=Q(a,e);f=h[e]||f;var k=b(d,!0,g,f);if(k){if("multiple"===e&&"select"===ta(a))throw ha("selmulti",ua(a));c.push({priority:100,compile:function(){return{pre:function(a,c,h){c=h.$$observers||(h.$$observers=$());if(l.test(e))throw ha("nodomevents"); +var m=h[e];m!==d&&(k=m&&b(m,!0,g,f),d=m);k&&(h[e]=k(a),(c[e]||(c[e]=[])).$$inter=!0,(h.$$observers&&h.$$observers[e].$$scope||a).$watch(k,function(a,b){"class"===e&&a!=b?h.$updateClass(a,b):h.$set(e,a)}))}}}})}}function Y(a,b,c){var d=b[0],e=b.length,f=d.parentNode,g,h;if(a)for(g=0,h=a.length;g=b)return a;for(;b--;)8===a[b].nodeType&&Pf.call(a,b,1);return a}function Xe(){var a={},b=!1;this.register=function(b,c){Ra(b,"controller");H(b)?M(a,b):a[b]=c};this.allowGlobals=function(){b=!0};this.$get=["$injector","$window",function(d,c){function e(a,b,c,d){if(!a||!H(a.$scope))throw G("$controller")("noscp", +d,b);a.$scope[b]=c}return function(f,g,h,k){var l,m,r;h=!0===h;k&&E(k)&&(r=k);if(E(f)){k=f.match(Uc);if(!k)throw Qf("ctrlfmt",f);m=k[1];r=r||k[3];f=a.hasOwnProperty(m)?a[m]:Bc(g.$scope,m,!0)||(b?Bc(c,m,!0):u);Qa(f,m,!0)}if(h)return h=(I(f)?f[f.length-1]:f).prototype,l=Object.create(h||null),r&&e(g,r,l,m||f.name),M(function(){var a=d.invoke(f,l,g,m);a!==l&&(H(a)||z(a))&&(l=a,r&&e(g,r,l,m||f.name));return l},{instance:l,identifier:r});l=d.instantiate(f,g,m);r&&e(g,r,l,m||f.name);return l}}]}function Ye(){this.$get= +["$window",function(a){return B(a.document)}]}function Ze(){this.$get=["$log",function(a){return function(b,d){a.error.apply(a,arguments)}}]}function Zb(a){return H(a)?da(a)?a.toISOString():db(a):a}function df(){this.$get=function(){return function(a){if(!a)return"";var b=[];oc(a,function(a,c){null===a||q(a)||(I(a)?n(a,function(a,d){b.push(ja(c)+"="+ja(Zb(a)))}):b.push(ja(c)+"="+ja(Zb(a))))});return b.join("&")}}}function ef(){this.$get=function(){return function(a){function b(a,e,f){null===a||q(a)|| +(I(a)?n(a,function(a,c){b(a,e+"["+(H(a)?c:"")+"]")}):H(a)&&!da(a)?oc(a,function(a,c){b(a,e+(f?"":"[")+c+(f?"":"]"))}):d.push(ja(e)+"="+ja(Zb(a))))}if(!a)return"";var d=[];b(a,"",!0);return d.join("&")}}}function $b(a,b){if(E(a)){var d=a.replace(Rf,"").trim();if(d){var c=b("Content-Type");(c=c&&0===c.indexOf($c))||(c=(c=d.match(Sf))&&Tf[c[0]].test(d));c&&(a=uc(d))}}return a}function ad(a){var b=$(),d;E(a)?n(a.split("\n"),function(a){d=a.indexOf(":");var e=F(U(a.substr(0,d)));a=U(a.substr(d+1));e&& +(b[e]=b[e]?b[e]+", "+a:a)}):H(a)&&n(a,function(a,d){var f=F(d),g=U(a);f&&(b[f]=b[f]?b[f]+", "+g:g)});return b}function bd(a){var b;return function(d){b||(b=ad(a));return d?(d=b[F(d)],void 0===d&&(d=null),d):b}}function cd(a,b,d,c){if(z(c))return c(a,b,d);n(c,function(c){a=c(a,b,d)});return a}function cf(){var a=this.defaults={transformResponse:[$b],transformRequest:[function(a){return H(a)&&"[object File]"!==sa.call(a)&&"[object Blob]"!==sa.call(a)&&"[object FormData]"!==sa.call(a)?db(a):a}],headers:{common:{Accept:"application/json, text/plain, */*"}, +post:ia(ac),put:ia(ac),patch:ia(ac)},xsrfCookieName:"XSRF-TOKEN",xsrfHeaderName:"X-XSRF-TOKEN",paramSerializer:"$httpParamSerializer"},b=!1;this.useApplyAsync=function(a){return y(a)?(b=!!a,this):b};var d=!0;this.useLegacyPromiseExtensions=function(a){return y(a)?(d=!!a,this):d};var c=this.interceptors=[];this.$get=["$httpBackend","$$cookieReader","$cacheFactory","$rootScope","$q","$injector",function(e,f,g,h,k,l){function m(b){function c(a){var b=M({},a);b.data=cd(a.data,a.headers,a.status,f.transformResponse); +a=a.status;return 200<=a&&300>a?b:k.reject(b)}function e(a,b){var c,d={};n(a,function(a,e){z(a)?(c=a(b),null!=c&&(d[e]=c)):d[e]=a});return d}if(!fa.isObject(b))throw G("$http")("badreq",b);var f=M({method:"get",transformRequest:a.transformRequest,transformResponse:a.transformResponse,paramSerializer:a.paramSerializer},b);f.headers=function(b){var c=a.headers,d=M({},b.headers),f,g,h,c=M({},c.common,c[F(b.method)]);a:for(f in c){g=F(f);for(h in d)if(F(h)===g)continue a;d[f]=c[f]}return e(d,ia(b))}(b); +f.method=sb(f.method);f.paramSerializer=E(f.paramSerializer)?l.get(f.paramSerializer):f.paramSerializer;var g=[function(b){var d=b.headers,e=cd(b.data,bd(d),u,b.transformRequest);q(e)&&n(d,function(a,b){"content-type"===F(b)&&delete d[b]});q(b.withCredentials)&&!q(a.withCredentials)&&(b.withCredentials=a.withCredentials);return r(b,e).then(c,c)},u],h=k.when(f);for(n(v,function(a){(a.request||a.requestError)&&g.unshift(a.request,a.requestError);(a.response||a.responseError)&&g.push(a.response,a.responseError)});g.length;){b= +g.shift();var m=g.shift(),h=h.then(b,m)}d?(h.success=function(a){Qa(a,"fn");h.then(function(b){a(b.data,b.status,b.headers,f)});return h},h.error=function(a){Qa(a,"fn");h.then(null,function(b){a(b.data,b.status,b.headers,f)});return h}):(h.success=dd("success"),h.error=dd("error"));return h}function r(c,d){function g(a,c,d,e){function f(){l(c,a,d,e)}J&&(200<=a&&300>a?J.put(R,[a,c,ad(d),e]):J.remove(R));b?h.$applyAsync(f):(f(),h.$$phase||h.$apply())}function l(a,b,d,e){b=-1<=b?b:0;(200<=b&&300>b?n.resolve: +n.reject)({data:a,status:b,headers:bd(d),config:c,statusText:e})}function r(a){l(a.data,a.status,ia(a.headers()),a.statusText)}function v(){var a=m.pendingRequests.indexOf(c);-1!==a&&m.pendingRequests.splice(a,1)}var n=k.defer(),D=n.promise,J,K,O=c.headers,R=t(c.url,c.paramSerializer(c.params));m.pendingRequests.push(c);D.then(v,v);!c.cache&&!a.cache||!1===c.cache||"GET"!==c.method&&"JSONP"!==c.method||(J=H(c.cache)?c.cache:H(a.cache)?a.cache:A);J&&(K=J.get(R),y(K)?K&&z(K.then)?K.then(r,r):I(K)?l(K[1], +K[0],ia(K[2]),K[3]):l(K,200,{},"OK"):J.put(R,D));q(K)&&((K=ed(c.url)?f()[c.xsrfCookieName||a.xsrfCookieName]:u)&&(O[c.xsrfHeaderName||a.xsrfHeaderName]=K),e(c.method,R,d,g,O,c.timeout,c.withCredentials,c.responseType));return D}function t(a,b){0=k&&(p.resolve(v),A(C.$$intervalId),delete f[C.$$intervalId]);n||a.$apply()},h);f[C.$$intervalId]=p;return C}var f={};e.cancel=function(a){return a&&a.$$intervalId in f?(f[a.$$intervalId].reject("canceled"),b.clearInterval(a.$$intervalId),delete f[a.$$intervalId],!0):!1};return e}]}function bc(a){a=a.split("/");for(var b=a.length;b--;)a[b]=ob(a[b]);return a.join("/")}function fd(a,b){var d=wa(a);b.$$protocol=d.protocol;b.$$host=d.hostname;b.$$port=ea(d.port)||Vf[d.protocol]|| +null}function gd(a,b){var d="/"!==a.charAt(0);d&&(a="/"+a);var c=wa(a);b.$$path=decodeURIComponent(d&&"/"===c.pathname.charAt(0)?c.pathname.substring(1):c.pathname);b.$$search=xc(c.search);b.$$hash=decodeURIComponent(c.hash);b.$$path&&"/"!=b.$$path.charAt(0)&&(b.$$path="/"+b.$$path)}function pa(a,b){if(0===b.indexOf(a))return b.substr(a.length)}function Fa(a){var b=a.indexOf("#");return-1==b?a:a.substr(0,b)}function ib(a){return a.replace(/(#.+)|#$/,"$1")}function cc(a,b,d){this.$$html5=!0;d=d||""; +fd(a,this);this.$$parse=function(a){var d=pa(b,a);if(!E(d))throw Db("ipthprfx",a,b);gd(d,this);this.$$path||(this.$$path="/");this.$$compose()};this.$$compose=function(){var a=Qb(this.$$search),d=this.$$hash?"#"+ob(this.$$hash):"";this.$$url=bc(this.$$path)+(a?"?"+a:"")+d;this.$$absUrl=b+this.$$url.substr(1)};this.$$parseLinkUrl=function(c,e){if(e&&"#"===e[0])return this.hash(e.slice(1)),!0;var f,g;y(f=pa(a,c))?(g=f,g=y(f=pa(d,f))?b+(pa("/",f)||f):a+g):y(f=pa(b,c))?g=b+f:b==c+"/"&&(g=b);g&&this.$$parse(g); +return!!g}}function dc(a,b,d){fd(a,this);this.$$parse=function(c){var e=pa(a,c)||pa(b,c),f;q(e)||"#"!==e.charAt(0)?this.$$html5?f=e:(f="",q(e)&&(a=c,this.replace())):(f=pa(d,e),q(f)&&(f=e));gd(f,this);c=this.$$path;var e=a,g=/^\/[A-Z]:(\/.*)/;0===f.indexOf(e)&&(f=f.replace(e,""));g.exec(f)||(c=(f=g.exec(c))?f[1]:c);this.$$path=c;this.$$compose()};this.$$compose=function(){var b=Qb(this.$$search),e=this.$$hash?"#"+ob(this.$$hash):"";this.$$url=bc(this.$$path)+(b?"?"+b:"")+e;this.$$absUrl=a+(this.$$url? +d+this.$$url:"")};this.$$parseLinkUrl=function(b,d){return Fa(a)==Fa(b)?(this.$$parse(b),!0):!1}}function hd(a,b,d){this.$$html5=!0;dc.apply(this,arguments);this.$$parseLinkUrl=function(c,e){if(e&&"#"===e[0])return this.hash(e.slice(1)),!0;var f,g;a==Fa(c)?f=c:(g=pa(b,c))?f=a+d+g:b===c+"/"&&(f=b);f&&this.$$parse(f);return!!f};this.$$compose=function(){var b=Qb(this.$$search),e=this.$$hash?"#"+ob(this.$$hash):"";this.$$url=bc(this.$$path)+(b?"?"+b:"")+e;this.$$absUrl=a+d+this.$$url}}function Eb(a){return function(){return this[a]}} +function id(a,b){return function(d){if(q(d))return this[a];this[a]=b(d);this.$$compose();return this}}function hf(){var a="",b={enabled:!1,requireBase:!0,rewriteLinks:!0};this.hashPrefix=function(b){return y(b)?(a=b,this):a};this.html5Mode=function(a){return $a(a)?(b.enabled=a,this):H(a)?($a(a.enabled)&&(b.enabled=a.enabled),$a(a.requireBase)&&(b.requireBase=a.requireBase),$a(a.rewriteLinks)&&(b.rewriteLinks=a.rewriteLinks),this):b};this.$get=["$rootScope","$browser","$sniffer","$rootElement","$window", +function(d,c,e,f,g){function h(a,b,d){var e=l.url(),f=l.$$state;try{c.url(a,b,d),l.$$state=c.state()}catch(g){throw l.url(e),l.$$state=f,g;}}function k(a,b){d.$broadcast("$locationChangeSuccess",l.absUrl(),a,l.$$state,b)}var l,m;m=c.baseHref();var r=c.url(),t;if(b.enabled){if(!m&&b.requireBase)throw Db("nobase");t=r.substring(0,r.indexOf("/",r.indexOf("//")+2))+(m||"/");m=e.history?cc:hd}else t=Fa(r),m=dc;var A=t.substr(0,Fa(t).lastIndexOf("/")+1);l=new m(t,A,"#"+a);l.$$parseLinkUrl(r,r);l.$$state= +c.state();var v=/^\s*(javascript|mailto):/i;f.on("click",function(a){if(b.rewriteLinks&&!a.ctrlKey&&!a.metaKey&&!a.shiftKey&&2!=a.which&&2!=a.button){for(var e=B(a.target);"a"!==ta(e[0]);)if(e[0]===f[0]||!(e=e.parent())[0])return;var h=e.prop("href"),k=e.attr("href")||e.attr("xlink:href");H(h)&&"[object SVGAnimatedString]"===h.toString()&&(h=wa(h.animVal).href);v.test(h)||!h||e.attr("target")||a.isDefaultPrevented()||!l.$$parseLinkUrl(h,k)||(a.preventDefault(),l.absUrl()!=c.url()&&(d.$apply(),g.angular["ff-684208-preventDefault"]= +!0))}});ib(l.absUrl())!=ib(r)&&c.url(l.absUrl(),!0);var n=!0;c.onUrlChange(function(a,b){q(pa(A,a))?g.location.href=a:(d.$evalAsync(function(){var c=l.absUrl(),e=l.$$state,f;a=ib(a);l.$$parse(a);l.$$state=b;f=d.$broadcast("$locationChangeStart",a,c,b,e).defaultPrevented;l.absUrl()===a&&(f?(l.$$parse(c),l.$$state=e,h(c,!1,e)):(n=!1,k(c,e)))}),d.$$phase||d.$digest())});d.$watch(function(){var a=ib(c.url()),b=ib(l.absUrl()),f=c.state(),g=l.$$replace,m=a!==b||l.$$html5&&e.history&&f!==l.$$state;if(n|| +m)n=!1,d.$evalAsync(function(){var b=l.absUrl(),c=d.$broadcast("$locationChangeStart",b,a,l.$$state,f).defaultPrevented;l.absUrl()===b&&(c?(l.$$parse(a),l.$$state=f):(m&&h(b,g,f===l.$$state?null:l.$$state),k(a,f)))});l.$$replace=!1});return l}]}function jf(){var a=!0,b=this;this.debugEnabled=function(b){return y(b)?(a=b,this):a};this.$get=["$window",function(d){function c(a){a instanceof Error&&(a.stack?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||x;a=!1;try{a=!!e.apply}catch(k){}return a?function(){var a=[];n(arguments,function(b){a.push(c(b))});return e.apply(b,a)}:function(a,b){e(a,null==b?"":b)}}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 Va(a,b){if("__defineGetter__"===a||"__defineSetter__"===a||"__lookupGetter__"===a||"__lookupSetter__"=== +a||"__proto__"===a)throw ba("isecfld",b);return a}function jd(a,b){a+="";if(!E(a))throw ba("iseccst",b);return a}function xa(a,b){if(a){if(a.constructor===a)throw ba("isecfn",b);if(a.window===a)throw ba("isecwindow",b);if(a.children&&(a.nodeName||a.prop&&a.attr&&a.find))throw ba("isecdom",b);if(a===Object)throw ba("isecobj",b);}return a}function kd(a,b){if(a){if(a.constructor===a)throw ba("isecfn",b);if(a===Wf||a===Xf||a===Yf)throw ba("isecff",b);}}function ld(a,b){if(a&&(a===(0).constructor||a=== +(!1).constructor||a==="".constructor||a==={}.constructor||a===[].constructor||a===Function.constructor))throw ba("isecaf",b);}function Zf(a,b){return"undefined"!==typeof a?a:b}function md(a,b){return"undefined"===typeof a?b:"undefined"===typeof b?a:a+b}function W(a,b){var d,c;switch(a.type){case s.Program:d=!0;n(a.body,function(a){W(a.expression,b);d=d&&a.expression.constant});a.constant=d;break;case s.Literal:a.constant=!0;a.toWatch=[];break;case s.UnaryExpression:W(a.argument,b);a.constant=a.argument.constant; +a.toWatch=a.argument.toWatch;break;case s.BinaryExpression:W(a.left,b);W(a.right,b);a.constant=a.left.constant&&a.right.constant;a.toWatch=a.left.toWatch.concat(a.right.toWatch);break;case s.LogicalExpression:W(a.left,b);W(a.right,b);a.constant=a.left.constant&&a.right.constant;a.toWatch=a.constant?[]:[a];break;case s.ConditionalExpression:W(a.test,b);W(a.alternate,b);W(a.consequent,b);a.constant=a.test.constant&&a.alternate.constant&&a.consequent.constant;a.toWatch=a.constant?[]:[a];break;case s.Identifier:a.constant= +!1;a.toWatch=[a];break;case s.MemberExpression:W(a.object,b);a.computed&&W(a.property,b);a.constant=a.object.constant&&(!a.computed||a.property.constant);a.toWatch=[a];break;case s.CallExpression:d=a.filter?!b(a.callee.name).$stateful:!1;c=[];n(a.arguments,function(a){W(a,b);d=d&&a.constant;a.constant||c.push.apply(c,a.toWatch)});a.constant=d;a.toWatch=a.filter&&!b(a.callee.name).$stateful?c:[a];break;case s.AssignmentExpression:W(a.left,b);W(a.right,b);a.constant=a.left.constant&&a.right.constant; +a.toWatch=[a];break;case s.ArrayExpression:d=!0;c=[];n(a.elements,function(a){W(a,b);d=d&&a.constant;a.constant||c.push.apply(c,a.toWatch)});a.constant=d;a.toWatch=c;break;case s.ObjectExpression:d=!0;c=[];n(a.properties,function(a){W(a.value,b);d=d&&a.value.constant;a.value.constant||c.push.apply(c,a.value.toWatch)});a.constant=d;a.toWatch=c;break;case s.ThisExpression:a.constant=!1,a.toWatch=[]}}function nd(a){if(1==a.length){a=a[0].expression;var b=a.toWatch;return 1!==b.length?b:b[0]!==a?b:u}} +function od(a){return a.type===s.Identifier||a.type===s.MemberExpression}function pd(a){if(1===a.body.length&&od(a.body[0].expression))return{type:s.AssignmentExpression,left:a.body[0].expression,right:{type:s.NGValueParameter},operator:"="}}function qd(a){return 0===a.body.length||1===a.body.length&&(a.body[0].expression.type===s.Literal||a.body[0].expression.type===s.ArrayExpression||a.body[0].expression.type===s.ObjectExpression)}function rd(a,b){this.astBuilder=a;this.$filter=b}function sd(a, +b){this.astBuilder=a;this.$filter=b}function Fb(a){return"constructor"==a}function ec(a){return z(a.valueOf)?a.valueOf():$f.call(a)}function kf(){var a=$(),b=$();this.$get=["$filter",function(d){function c(a,b){return null==a||null==b?a===b:"object"===typeof a&&(a=ec(a),"object"===typeof a)?!1:a===b||a!==a&&b!==b}function e(a,b,d,e,f){var g=e.inputs,h;if(1===g.length){var k=c,g=g[0];return a.$watch(function(a){var b=g(a);c(b,k)||(h=e(a,u,u,[b]),k=b&&ec(b));return h},b,d,f)}for(var l=[],m=[],r=0,n= +g.length;r=this.promise.$$state.status&&d&&d.length&&a(function(){for(var a,e,f=0,g=d.length;fa)for(b in l++,f)qa.call(e,b)||(n--,delete f[b])}else f!==e&&(f=e,l++);return l}}c.$stateful=!0;var d=this,e,f,g,k=1n&&(v=4-n,q[v]||(q[v]=[]),q[v].push({msg:z(a.exp)?"fn: "+(a.exp.name||a.exp.toString()):a.exp,newVal:f,oldVal:h}));else if(a===c){r=!1;break a}}catch(y){g(y)}if(!(l=A.$$watchersCount&&A.$$childHead||A!==this&&A.$$nextSibling))for(;A!==this&&!(l=A.$$nextSibling);)A=A.$parent}while(A=l);if((r||u.length)&&!n--)throw w.$$phase=null,d("infdig", +b,q);}while(r||u.length);for(w.$$phase=null;L.length;)try{L.shift()()}catch(x){g(x)}},$destroy:function(){if(!this.$$destroyed){var a=this.$parent;this.$broadcast("$destroy");this.$$destroyed=!0;this===w&&k.$$applicationDestroyed();A(this,-this.$$watchersCount);for(var b in this.$$listenerCount)v(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=x;this.$on=this.$watch=this.$watchGroup=function(){return x};this.$$listeners={};this.$$nextSibling=null;m(this)}},$eval:function(a,b){return h(a)(this,b)},$evalAsync:function(a,b){w.$$phase||u.length||k.defer(function(){u.length&&w.$digest()});u.push({scope:this,expression:a,locals:b})},$$postDigest:function(a){L.push(a)},$apply:function(a){try{t("$apply"); +try{return this.$eval(a)}finally{w.$$phase=null}}catch(b){g(b)}finally{try{w.$digest()}catch(c){throw g(c),c;}}},$applyAsync:function(a){function b(){c.$eval(a)}var c=this;a&&aa.push(b);C()},$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&&(c[d]=null,v(e,1,a))}},$emit:function(a,b){var c=[],d,e=this,f=!1,h= +{name:a,targetScope:e,stopPropagation:function(){f=!0},preventDefault:function(){h.defaultPrevented=!0},defaultPrevented:!1},k=cb([h],arguments,1),l,m;do{d=e.$$listeners[a]||c;h.currentScope=e;l=0;for(m=d.length;lHa)throw ya("iequirks");var c=ia(la);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=Ya);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;n(la,function(a, +b){var d=F(b);c[fb("parse_as_"+d)]=function(b){return e(a,b)};c[fb("get_trusted_"+d)]=function(b){return f(a,b)};c[fb("trust_as_"+d)]=function(b){return g(a,b)}});return c}]}function qf(){this.$get=["$window","$document",function(a,b){var d={},c=ea((/android (\d+)/.exec(F((a.navigator||{}).userAgent))||[])[1]),e=/Boxee/i.test((a.navigator||{}).userAgent),f=b[0]||{},g,h=/^(Moz|webkit|ms)(?=[A-Z])/,k=f.body&&f.body.style,l=!1,m=!1;if(k){for(var r in k)if(l=h.exec(r)){g=l[0];g=g.substr(0,1).toUpperCase()+ +g.substr(1);break}g||(g="WebkitOpacity"in k&&"webkit");l=!!("transition"in k||g+"Transition"in k);m=!!("animation"in k||g+"Animation"in k);!c||l&&m||(l=E(k.webkitTransition),m=E(k.webkitAnimation))}return{history:!(!a.history||!a.history.pushState||4>c||e),hasEvent:function(a){if("input"===a&&11>=Ha)return!1;if(q(d[a])){var b=f.createElement("div");d[a]="on"+a in b}return d[a]},csp:Ba(),vendorPrefix:g,transitions:l,animations:m,android:c}}]}function sf(){this.$get=["$templateCache","$http","$q","$sce", +function(a,b,d,c){function e(f,g){e.totalPendingRequests++;E(f)&&a.get(f)||(f=c.getTrustedResourceUrl(f));var h=b.defaults&&b.defaults.transformResponse;I(h)?h=h.filter(function(a){return a!==$b}):h===$b&&(h=null);return b.get(f,{cache:a,transformResponse:h})["finally"](function(){e.totalPendingRequests--}).then(function(b){a.put(f,b.data);return b.data},function(a){if(!g)throw ha("tpload",f,a.status,a.statusText);return d.reject(a)})}e.totalPendingRequests=0;return e}]}function tf(){this.$get=["$rootScope", +"$browser","$location",function(a,b,d){return{findBindings:function(a,b,d){a=a.getElementsByClassName("ng-binding");var g=[];n(a,function(a){var c=fa.element(a).data("$binding");c&&n(c,function(c){d?(new RegExp("(^|\\s)"+ud(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\\:"],h=0;ha;a=Math.abs(a);var g=Infinity===a;if(!g&&!isFinite(a))return"";var h=a+"",k="",l=!1,m=[];g&&(k="\u221e");if(!g&&-1!==h.indexOf("e")){var r=h.match(/([\d\.]+)e(-?)(\d+)/);r&&"-"==r[2]&&r[3]>e+1?a=0:(k=h,l=!0)}if(g||l)0a&&(k=a.toFixed(e),a=parseFloat(k),k=k.replace(ic,c));else{g=(h.split(ic)[1]||"").length; +q(e)&&(e=Math.min(Math.max(b.minFrac,g),b.maxFrac));a=+(Math.round(+(a.toString()+"e"+e)).toString()+"e"+-e);var g=(""+a).split(ic),h=g[0],g=g[1]||"",r=0,t=b.lgSize,n=b.gSize;if(h.length>=t+n)for(r=h.length-t,l=0;la&&(c="-",a=-a);for(a=""+a;a.length-d)e+=d;0===e&&-12==d&&(e=12);return Gb(e,b,c)}}function Hb(a,b){return function(d,c){var e=d["get"+a](),f=sb(b?"SHORT"+a:a);return c[f][e]}}function Dd(a){var b=(new Date(a,0,1)).getDay();return new Date(a,0,(4>=b?5:12)-b)}function Ed(a){return function(b){var d=Dd(b.getFullYear());b=+new Date(b.getFullYear(),b.getMonth(),b.getDate()+(4-b.getDay()))- ++d;b=1+Math.round(b/6048E5);return Gb(b,a)}}function jc(a,b){return 0>=a.getFullYear()?b.ERAS[0]:b.ERAS[1]}function zd(a){function b(a){var b;if(b=a.match(d)){a=new Date(0);var f=0,g=0,h=b[8]?a.setUTCFullYear:a.setFullYear,k=b[8]?a.setUTCHours:a.setHours;b[9]&&(f=ea(b[9]+b[10]),g=ea(b[9]+b[11]));h.call(a,ea(b[1]),ea(b[2])-1,ea(b[3]));f=ea(b[4]||0)-f;g=ea(b[5]||0)-g;h=ea(b[6]||0);b=Math.round(1E3*parseFloat("0."+(b[7]||0)));k.call(a,f,g,h,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="",h=[],k,l;d=d||"mediumDate";d=a.DATETIME_FORMATS[d]||d;E(c)&&(c=hg.test(c)?ea(c):b(c));Q(c)&&(c=new Date(c));if(!da(c)||!isFinite(c.getTime()))return c;for(;d;)(l=ig.exec(d))?(h=cb(h,l,1),d=h.pop()):(h.push(d),d=null);var m=c.getTimezoneOffset();f&&(m=vc(f,c.getTimezoneOffset()),c=Pb(c,f,!0));n(h,function(b){k=jg[b];g+=k?k(c,a.DATETIME_FORMATS,m):b.replace(/(^'|'$)/g,"").replace(/''/g,"'")});return g}}function cg(){return function(a,b){q(b)&&(b=2);return db(a,b)}}function dg(){return function(a, +b,d){b=Infinity===Math.abs(Number(b))?Number(b):ea(b);if(isNaN(b))return a;Q(a)&&(a=a.toString());if(!I(a)&&!E(a))return a;d=!d||isNaN(d)?0:ea(d);d=0>d?Math.max(0,a.length+d):d;return 0<=b?a.slice(d,d+b):0===d?a.slice(b,a.length):a.slice(Math.max(0,d+b),d)}}function Bd(a){function b(b,d){d=d?-1:1;return b.map(function(b){var c=1,h=Ya;if(z(b))h=b;else if(E(b)){if("+"==b.charAt(0)||"-"==b.charAt(0))c="-"==b.charAt(0)?-1:1,b=b.substring(1);if(""!==b&&(h=a(b),h.constant))var k=h(),h=function(a){return a[k]}}return{get:h, +descending:c*d}})}function d(a){switch(typeof a){case "number":case "boolean":case "string":return!0;default:return!1}}return function(a,e,f){if(!za(a))return a;I(e)||(e=[e]);0===e.length&&(e=["+"]);var g=b(e,f);g.push({get:function(){return{}},descending:f?-1:1});a=Array.prototype.map.call(a,function(a,b){return{value:a,predicateValues:g.map(function(c){var e=c.get(a);c=typeof e;if(null===e)c="string",e="null";else if("string"===c)e=e.toLowerCase();else if("object"===c)a:{if("function"===typeof e.valueOf&& +(e=e.valueOf(),d(e)))break a;if(qc(e)&&(e=e.toString(),d(e)))break a;e=b}return{value:e,type:c}})}});a.sort(function(a,b){for(var c=0,d=0,e=g.length;db||37<=b&&40>=b||m(a,this,this.value)});if(e.hasEvent("paste"))b.on("paste cut", +m)}b.on("change",k);c.$render=function(){var a=c.$isEmpty(c.$viewValue)?"":c.$viewValue;b.val()!==a&&b.val(a)}}function Kb(a,b){return function(d,c){var e,f;if(da(d))return d;if(E(d)){'"'==d.charAt(0)&&'"'==d.charAt(d.length-1)&&(d=d.substring(1,d.length-1));if(kg.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},n(e,function(a,c){c=s};g.$observe("min",function(a){s=n(a);h.$validate()})}if(y(g.max)||g.ngMax){var p;h.$validators.max=function(a){return!r(a)||q(p)||d(a)<=p};g.$observe("max",function(a){p=n(a);h.$validate()})}}}function Hd(a,b,d,c){(c.$$hasNativeValidators=H(b[0].validity))&&c.$parsers.push(function(a){var c=b.prop("validity")||{}; +return c.badInput&&!c.typeMismatch?u:a})}function Id(a,b,d,c,e){if(y(c)){a=a(c);if(!a.constant)throw lb("constexpr",d,c);return a(b)}return e}function lc(a,b){a="ngClass"+a;return["$animate",function(d){function c(a,b){var c=[],d=0;a:for(;d(?:<\/\1>|)$/,Tb=/<|&#?\w+;/, +Cf=/<([\w:-]+)/,Df=/<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:-]+)[^>]*)\/>/gi,ka={option:[1,'"],thead:[1,"","
"],col:[2,"","
"],tr:[2,"","
"],td:[3,"","
"],_default:[0,"",""]};ka.optgroup=ka.option;ka.tbody=ka.tfoot=ka.colgroup=ka.caption=ka.thead;ka.th=ka.td;var Kf=Node.prototype.contains||function(a){return!!(this.compareDocumentPosition(a)& +16)},Pa=N.prototype={ready:function(a){function b(){d||(d=!0,a())}var d=!1;"complete"===X.readyState?setTimeout(b):(this.on("DOMContentLoaded",b),N(S).on("load",b))},toString:function(){var a=[];n(this,function(b){a.push(""+b)});return"["+a.join(", ")+"]"},eq:function(a){return 0<=a?B(this[a]):B(this[this.length+a])},length:0,push:mg,sort:[].sort,splice:[].splice},Cb={};n("multiple selected checked disabled readOnly required open".split(" "),function(a){Cb[F(a)]=a});var Rc={};n("input select option textarea button form details".split(" "), +function(a){Rc[a]=!0});var Zc={ngMinlength:"minlength",ngMaxlength:"maxlength",ngMin:"min",ngMax:"max",ngPattern:"pattern"};n({data:Wb,removeData:vb,hasData:function(a){for(var b in gb[a.ng339])return!0;return!1}},function(a,b){N[b]=a});n({data:Wb,inheritedData:Bb,scope:function(a){return B.data(a,"$scope")||Bb(a.parentNode||a,["$isolateScope","$scope"])},isolateScope:function(a){return B.data(a,"$isolateScope")||B.data(a,"$isolateScopeNoTemplate")},controller:Oc,injector:function(a){return Bb(a, +"$injector")},removeAttr:function(a,b){a.removeAttribute(b)},hasClass:yb,css:function(a,b,d){b=fb(b);if(y(d))a.style[b]=d;else return a.style[b]},attr:function(a,b,d){var c=a.nodeType;if(c!==Na&&2!==c&&8!==c)if(c=F(b),Cb[c])if(y(d))d?(a[b]=!0,a.setAttribute(b,c)):(a[b]=!1,a.removeAttribute(c));else return a[b]||(a.attributes.getNamedItem(b)||x).specified?c:u;else if(y(d))a.setAttribute(b,d);else if(a.getAttribute)return a=a.getAttribute(b,2),null===a?u:a},prop:function(a,b,d){if(y(d))a[b]=d;else return a[b]}, +text:function(){function a(a,d){if(q(d)){var c=a.nodeType;return 1===c||c===Na?a.textContent:""}a.textContent=d}a.$dv="";return a}(),val:function(a,b){if(q(b)){if(a.multiple&&"select"===ta(a)){var d=[];n(a.options,function(a){a.selected&&d.push(a.value||a.text)});return 0===d.length?null:d}return a.value}a.value=b},html:function(a,b){if(q(b))return a.innerHTML;ub(a,!0);a.innerHTML=b},empty:Pc},function(a,b){N.prototype[b]=function(b,c){var e,f,g=this.length;if(a!==Pc&&q(2==a.length&&a!==yb&&a!==Oc? +b:c)){if(H(b)){for(e=0;e <= >= && || ! = |".split(" "),function(a){Lb[a]=!0});var sg={n:"\n",f:"\f",r:"\r", +t:"\t",v:"\v","'":"'",'"':'"'},fc=function(a){this.options=a};fc.prototype={constructor:fc,lex:function(a){this.text=a;this.index=0;for(this.tokens=[];this.index=a&&"string"===typeof a},isWhitespace:function(a){return" "===a||"\r"===a|| +"\t"===a||"\n"===a||"\v"===a||"\u00a0"===a},isIdent:function(a){return"a"<=a&&"z">=a||"A"<=a&&"Z">=a||"_"===a||"$"===a},isExpOperator:function(a){return"-"===a||"+"===a||this.isNumber(a)},throwError:function(a,b,d){d=d||this.index;b=y(b)?"s "+b+"-"+this.index+" ["+this.text.substring(b,d)+"]":" "+d;throw ba("lexerr",a,b,this.text);},readNumber:function(){for(var a="",b=this.index;this.index","<=",">=");)a={type:s.BinaryExpression,operator:b.text,left:a,right:this.additive()};return a},additive:function(){for(var a=this.multiplicative(),b;b=this.expect("+","-");)a={type:s.BinaryExpression,operator:b.text,left:a,right:this.multiplicative()};return a},multiplicative:function(){for(var a=this.unary(),b;b=this.expect("*","/","%");)a={type:s.BinaryExpression,operator:b.text, +left:a,right:this.unary()};return a},unary:function(){var a;return(a=this.expect("+","-","!"))?{type:s.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.constants.hasOwnProperty(this.peek().text)?a=bb(this.constants[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:s.CallExpression,callee:a,arguments:this.parseArguments()},this.consume(")")):"["===b.text?(a={type:s.MemberExpression,object:a,property:this.expression(),computed:!0},this.consume("]")):"."===b.text?a={type:s.MemberExpression,object:a,property:this.identifier(),computed:!1}:this.throwError("IMPOSSIBLE");return a},filter:function(a){a=[a];for(var b={type:s.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.expression());while(this.expect(","))}return a},identifier:function(){var a=this.consume();a.identifier||this.throwError("is not a valid identifier",a);return{type:s.Identifier,name:a.text}},constant:function(){return{type:s.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:s.ArrayExpression,elements:a}},object:function(){var a=[],b;if("}"!==this.peekToken().text){do{if(this.peek("}"))break;b={type:s.Property,kind:"init"};this.peek().constant?b.key=this.constant():this.peek().identifier?b.key=this.identifier():this.throwError("invalid key",this.peek());this.consume(":");b.value=this.expression();a.push(b)}while(this.expect(","))}this.consume("}");return{type:s.ObjectExpression,properties:a}}, +throwError:function(a,b){throw ba("syntax",b.text,a,b.index+1,this.text,this.text.substring(b.index));},consume:function(a){if(0===this.tokens.length)throw ba("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 ba("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},constants:{"true":{type:s.Literal,value:!0},"false":{type:s.Literal,value:!1},"null":{type:s.Literal,value:null},undefined:{type:s.Literal,value:u},"this":{type:s.ThisExpression}}};rd.prototype={compile:function(a,b){var d=this,c=this.astBuilder.ast(a);this.state={nextId:0,filters:{},expensiveChecks:b,fn:{vars:[],body:[],own:{}},assign:{vars:[], +body:[],own:{}},inputs:[]};W(c,d.$filter);var e="",f;this.stage="assign";if(f=pd(c))this.state.computing="assign",e=this.nextId(),this.recurse(f,e),this.return_(e),e="fn.assign="+this.generateFunction("assign","s,v,l");f=nd(c.body);d.stage="inputs";n(f,function(a,b){var c="fn"+b;d.state[c]={vars:[],body:[],own:{}};d.state.computing=c;var e=d.nextId();d.recurse(a,e);d.return_(e);d.state.inputs.push(c);a.watchId=b});this.state.computing="fn";this.stage="main";this.recurse(c);e='"'+this.USE+" "+this.STRICT+ +'";\n'+this.filterPrefix()+"var fn="+this.generateFunction("fn","s,l,a,i")+e+this.watchFns()+"return fn;";e=(new Function("$filter","ensureSafeMemberName","ensureSafeObject","ensureSafeFunction","getStringValue","ensureSafeAssignContext","ifDefined","plus","text",e))(this.$filter,Va,xa,kd,jd,ld,Zf,md,a);this.state=this.stage=u;e.literal=qd(c);e.constant=c.constant;return e},USE:"use",STRICT:"strict",watchFns:function(){var a=[],b=this.state.inputs,d=this;n(b,function(b){a.push("var "+b+"="+d.generateFunction(b, +"s"))});b.length&&a.push("fn.inputs=["+b.join(",")+"];");return a.join("")},generateFunction:function(a,b){return"function("+b+"){"+this.varsPrefix(a)+this.body(a)+"};"},filterPrefix:function(){var a=[],b=this;n(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,h,k=this,l,m;c=c||x;if(!f&&y(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 s.Program:n(a.body,function(b,c){k.recurse(b.expression,u,u,function(a){h=a});c!==a.body.length-1?k.current().body.push(h,";"):k.return_(h)});break;case s.Literal:m=this.escape(a.value);this.assign(b,m);c(m);break;case s.UnaryExpression:this.recurse(a.argument,u,u,function(a){h=a});m=a.operator+"("+this.ifDefined(h, +0)+")";this.assign(b,m);c(m);break;case s.BinaryExpression:this.recurse(a.left,u,u,function(a){g=a});this.recurse(a.right,u,u,function(a){h=a});m="+"===a.operator?this.plus(g,h):"-"===a.operator?this.ifDefined(g,0)+a.operator+this.ifDefined(h,0):"("+g+")"+a.operator+"("+h+")";this.assign(b,m);c(m);break;case s.LogicalExpression:b=b||this.nextId();k.recurse(a.left,b);k.if_("&&"===a.operator?b:k.not(b),k.lazyRecurse(a.right,b));c(b);break;case s.ConditionalExpression:b=b||this.nextId();k.recurse(a.test, +b);k.if_(b,k.lazyRecurse(a.alternate,b),k.lazyRecurse(a.consequent,b));c(b);break;case s.Identifier:b=b||this.nextId();d&&(d.context="inputs"===k.stage?"s":this.assign(this.nextId(),this.getHasOwnProperty("l",a.name)+"?l:s"),d.computed=!1,d.name=a.name);Va(a.name);k.if_("inputs"===k.stage||k.not(k.getHasOwnProperty("l",a.name)),function(){k.if_("inputs"===k.stage||"s",function(){e&&1!==e&&k.if_(k.not(k.nonComputedMember("s",a.name)),k.lazyAssign(k.nonComputedMember("s",a.name),"{}"));k.assign(b,k.nonComputedMember("s", +a.name))})},b&&k.lazyAssign(b,k.nonComputedMember("l",a.name)));(k.state.expensiveChecks||Fb(a.name))&&k.addEnsureSafeObject(b);c(b);break;case s.MemberExpression:g=d&&(d.context=this.nextId())||this.nextId();b=b||this.nextId();k.recurse(a.object,g,u,function(){k.if_(k.notNull(g),function(){if(a.computed)h=k.nextId(),k.recurse(a.property,h),k.getStringValue(h),k.addEnsureSafeMemberName(h),e&&1!==e&&k.if_(k.not(k.computedMember(g,h)),k.lazyAssign(k.computedMember(g,h),"{}")),m=k.ensureSafeObject(k.computedMember(g, +h)),k.assign(b,m),d&&(d.computed=!0,d.name=h);else{Va(a.property.name);e&&1!==e&&k.if_(k.not(k.nonComputedMember(g,a.property.name)),k.lazyAssign(k.nonComputedMember(g,a.property.name),"{}"));m=k.nonComputedMember(g,a.property.name);if(k.state.expensiveChecks||Fb(a.property.name))m=k.ensureSafeObject(m);k.assign(b,m);d&&(d.computed=!1,d.name=a.property.name)}},function(){k.assign(b,"undefined")});c(b)},!!e);break;case s.CallExpression:b=b||this.nextId();a.filter?(h=k.filter(a.callee.name),l=[],n(a.arguments, +function(a){var b=k.nextId();k.recurse(a,b);l.push(b)}),m=h+"("+l.join(",")+")",k.assign(b,m),c(b)):(h=k.nextId(),g={},l=[],k.recurse(a.callee,h,g,function(){k.if_(k.notNull(h),function(){k.addEnsureSafeFunction(h);n(a.arguments,function(a){k.recurse(a,k.nextId(),u,function(a){l.push(k.ensureSafeObject(a))})});g.name?(k.state.expensiveChecks||k.addEnsureSafeObject(g.context),m=k.member(g.context,g.name,g.computed)+"("+l.join(",")+")"):m=h+"("+l.join(",")+")";m=k.ensureSafeObject(m);k.assign(b,m)}, +function(){k.assign(b,"undefined")});c(b)}));break;case s.AssignmentExpression:h=this.nextId();g={};if(!od(a.left))throw ba("lval");this.recurse(a.left,u,g,function(){k.if_(k.notNull(g.context),function(){k.recurse(a.right,h);k.addEnsureSafeObject(k.member(g.context,g.name,g.computed));k.addEnsureSafeAssignContext(g.context);m=k.member(g.context,g.name,g.computed)+a.operator+h;k.assign(b,m);c(b||m)})},1);break;case s.ArrayExpression:l=[];n(a.elements,function(a){k.recurse(a,k.nextId(),u,function(a){l.push(a)})}); +m="["+l.join(",")+"]";this.assign(b,m);c(m);break;case s.ObjectExpression:l=[];n(a.properties,function(a){k.recurse(a.value,k.nextId(),u,function(b){l.push(k.escape(a.key.type===s.Identifier?a.key.name:""+a.key.value)+":"+b)})});m="{"+l.join(",")+"}";this.assign(b,m);c(m);break;case s.ThisExpression:this.assign(b,"s");c("s");break;case s.NGValueParameter:this.assign(b,"v"),c("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+")"},notNull:function(a){return a+"!=null"},nonComputedMember:function(a,b){return a+"."+b},computedMember:function(a,b){return a+"["+b+"]"},member:function(a,b,d){return d?this.computedMember(a,b):this.nonComputedMember(a,b)},addEnsureSafeObject:function(a){this.current().body.push(this.ensureSafeObject(a),";")},addEnsureSafeMemberName:function(a){this.current().body.push(this.ensureSafeMemberName(a),";")}, +addEnsureSafeFunction:function(a){this.current().body.push(this.ensureSafeFunction(a),";")},addEnsureSafeAssignContext:function(a){this.current().body.push(this.ensureSafeAssignContext(a),";")},ensureSafeObject:function(a){return"ensureSafeObject("+a+",text)"},ensureSafeMemberName:function(a){return"ensureSafeMemberName("+a+",text)"},ensureSafeFunction:function(a){return"ensureSafeFunction("+a+",text)"},getStringValue:function(a){this.assign(a,"getStringValue("+a+",text)")},ensureSafeAssignContext:function(a){return"ensureSafeAssignContext("+ +a+",text)"},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(E(a))return"'"+a.replace(this.stringEscapeRegex,this.stringEscapeFn)+"'";if(Q(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 ba("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]}};sd.prototype={compile:function(a,b){var d=this,c=this.astBuilder.ast(a);this.expression=a;this.expensiveChecks=b;W(c,d.$filter);var e,f;if(e=pd(c))f=this.recurse(e);e=nd(c.body);var g;e&&(g=[],n(e,function(a,b){var c=d.recurse(a);a.input=c;g.push(c);a.watchId=b}));var h=[];n(c.body,function(a){h.push(d.recurse(a.expression))}); +e=0===c.body.length?function(){}:1===c.body.length?h[0]:function(a,b){var c;n(h,function(d){c=d(a,b)});return c};f&&(e.assign=function(a,b,c){return f(a,c,b)});g&&(e.inputs=g);e.literal=qd(c);e.constant=c.constant;return e},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 s.Literal:return this.value(a.value,b);case s.UnaryExpression:return e=this.recurse(a.argument),this["unary"+a.operator](e,b);case s.BinaryExpression:return c=this.recurse(a.left), +e=this.recurse(a.right),this["binary"+a.operator](c,e,b);case s.LogicalExpression:return c=this.recurse(a.left),e=this.recurse(a.right),this["binary"+a.operator](c,e,b);case s.ConditionalExpression:return this["ternary?:"](this.recurse(a.test),this.recurse(a.alternate),this.recurse(a.consequent),b);case s.Identifier:return Va(a.name,f.expression),f.identifier(a.name,f.expensiveChecks||Fb(a.name),b,d,f.expression);case s.MemberExpression:return c=this.recurse(a.object,!1,!!d),a.computed||(Va(a.property.name, +f.expression),e=a.property.name),a.computed&&(e=this.recurse(a.property)),a.computed?this.computedMember(c,e,b,d,f.expression):this.nonComputedMember(c,e,f.expensiveChecks,b,d,f.expression);case s.CallExpression:return g=[],n(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 r=[],n=0;n":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,h){e=a(e,f,g,h)?b(e,f,g,h):d(e,f,g,h);return c?{value:e}:e}},value:function(a,b){return function(){return b?{context:u,name:u,value:a}:a}},identifier:function(a,b,d,c,e){return function(f,g,h,k){f= +g&&a in g?g:f;c&&1!==c&&f&&!f[a]&&(f[a]={});g=f?f[a]:u;b&&xa(g,e);return d?{context:f,name:a,value:g}:g}},computedMember:function(a,b,d,c,e){return function(f,g,h,k){var l=a(f,g,h,k),m,n;null!=l&&(m=b(f,g,h,k),m=jd(m),Va(m,e),c&&1!==c&&l&&!l[m]&&(l[m]={}),n=l[m],xa(n,e));return d?{context:l,name:m,value:n}:n}},nonComputedMember:function(a,b,d,c,e,f){return function(g,h,k,l){g=a(g,h,k,l);e&&1!==e&&g&&!g[b]&&(g[b]={});h=null!=g?g[b]:u;(d||Fb(b))&&xa(h,f);return c?{context:g,name:b,value:h}:h}},inputs:function(a, +b){return function(d,c,e,f){return f?f[b]:a(d,c,e)}}};var gc=function(a,b,d){this.lexer=a;this.$filter=b;this.options=d;this.ast=new s(this.lexer);this.astCompiler=d.csp?new sd(this.ast,b):new rd(this.ast,b)};gc.prototype={constructor:gc,parse:function(a){return this.astCompiler.compile(a,this.options.expensiveChecks)}};$();$();var $f=Object.prototype.valueOf,ya=G("$sce"),la={HTML:"html",CSS:"css",URL:"url",RESOURCE_URL:"resourceUrl",JS:"js"},ha=G("$compile"),Y=X.createElement("a"),wd=wa(S.location.href); +xd.$inject=["$document"];Jc.$inject=["$provide"];yd.$inject=["$locale"];Ad.$inject=["$locale"];var ic=".",jg={yyyy:ca("FullYear",4),yy:ca("FullYear",2,0,!0),y:ca("FullYear",1),MMMM:Hb("Month"),MMM:Hb("Month",!0),MM:ca("Month",2,1),M:ca("Month",1,1),dd:ca("Date",2),d:ca("Date",1),HH:ca("Hours",2),H:ca("Hours",1),hh:ca("Hours",2,-12),h:ca("Hours",1,-12),mm:ca("Minutes",2),m:ca("Minutes",1),ss:ca("Seconds",2),s:ca("Seconds",1),sss:ca("Milliseconds",3),EEEE:Hb("Day"),EEE:Hb("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?"+":"")+(Gb(Math[0=a.getFullYear()?b.ERANAMES[0]:b.ERANAMES[1]}},ig=/((?:[^yMdHhmsaZEwG']+)|(?:'(?:[^']|'')*')|(?:E+|y+|M+|d+|H+|h+|m+|s+|a|Z|G+|w+))(.*)/,hg=/^\-?\d+$/;zd.$inject=["$locale"];var eg=na(F),fg=na(sb);Bd.$inject=["$parse"];var he=na({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]"===sa.call(b.prop("href"))?"xlink:href":"href";b.on("click",function(a){b.attr(e)||a.preventDefault()})}}}}),tb={};n(Cb,function(a,b){function d(a,d,e){a.$watch(e[c],function(a){e.$set(b,!!a)})}if("multiple"!=a){var c=va("ng-"+b),e=d;"checked"===a&&(e=function(a,b,e){e.ngModel!==e[c]&&d(a,b,e)});tb[c]=function(){return{restrict:"A",priority:100,link:e}}}});n(Zc,function(a,b){tb[b]=function(){return{priority:100,link:function(a, +c,e){if("ngPattern"===b&&"/"==e.ngPattern.charAt(0)&&(c=e.ngPattern.match(lg))){e.$set("ngPattern",new RegExp(c[1],c[2]));return}a.$watch(e[b],function(a){e.$set(b,a)})}}}});n(["src","srcset","href"],function(a){var b=va("ng-"+a);tb[b]=function(){return{priority:99,link:function(d,c,e){var f=a,g=a;"href"===a&&"[object SVGAnimatedString]"===sa.call(c.prop("href"))&&(g="xlinkHref",e.$attr[g]="xlink:href",f=null);e.$observe(b,function(b){b?(e.$set(g,b),Ha&&f&&c.prop(f,e[g])):"href"===a&&e.$set(g,null)})}}}}); +var Ib={$addControl:x,$$renameControl:function(a,b){a.$name=b},$removeControl:x,$setValidity:x,$setDirty:x,$setPristine:x,$setSubmitted:x};Fd.$inject=["$element","$attrs","$scope","$animate","$interpolate"];var Nd=function(a){return["$timeout","$parse",function(b,d){function c(a){return""===a?d('this[""]').assign:d(a).assign||x}return{name:"form",restrict:a?"EAC":"E",require:["form","^^?form"],controller:Fd,compile:function(d,f){d.addClass(Wa).addClass(mb);var g=f.name?"name":a&&f.ngForm?"ngForm": +!1;return{pre:function(a,d,e,f){var n=f[0];if(!("action"in e)){var q=function(b){a.$apply(function(){n.$commitViewValue();n.$setSubmitted()});b.preventDefault()};d[0].addEventListener("submit",q,!1);d.on("$destroy",function(){b(function(){d[0].removeEventListener("submit",q,!1)},0,!1)})}(f[1]||n.$$parentForm).$addControl(n);var s=g?c(n.$name):x;g&&(s(a,n),e.$observe(g,function(b){n.$name!==b&&(s(a,u),n.$$parentForm.$$renameControl(n,b),s=c(n.$name),s(a,n))}));d.on("$destroy",function(){n.$$parentForm.$removeControl(n); +s(a,u);M(n,Ib)})}}}}}]},ie=Nd(),ve=Nd(!0),kg=/\d{4}-[01]\d-[0-3]\dT[0-2]\d:[0-5]\d:[0-5]\d\.\d+([+-][0-2]\d:[0-5]\d|Z)/,tg=/^[A-Za-z][A-Za-z\d.+-]*:\/*(?:\w+(?::\w+)?@)?[^\s/]+(?::\d+)?(?:\/[\w#!:.?+=&%@\-/]*)?$/,ug=/^[a-z0-9!#$%&'*+\/=?^_`{|}~.-]+@[a-z0-9]([a-z0-9-]*[a-z0-9])?(\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$/i,vg=/^\s*(\-|\+)?(\d+|(\d*(\.\d*)))([eE][+-]?\d+)?\s*$/,Od=/^(\d{4})-(\d{2})-(\d{2})$/,Pd=/^(\d{4})-(\d\d)-(\d\d)T(\d\d):(\d\d)(?::(\d\d)(\.\d{1,3})?)?$/,mc=/^(\d{4})-W(\d\d)$/,Qd=/^(\d{4})-(\d\d)$/, +Rd=/^(\d\d):(\d\d)(?::(\d\d)(\.\d{1,3})?)?$/,Sd={text:function(a,b,d,c,e,f){jb(a,b,d,c,e,f);kc(c)},date:kb("date",Od,Kb(Od,["yyyy","MM","dd"]),"yyyy-MM-dd"),"datetime-local":kb("datetimelocal",Pd,Kb(Pd,"yyyy MM dd HH mm ss sss".split(" ")),"yyyy-MM-ddTHH:mm:ss.sss"),time:kb("time",Rd,Kb(Rd,["HH","mm","ss","sss"]),"HH:mm:ss.sss"),week:kb("week",mc,function(a,b){if(da(a))return a;if(E(a)){mc.lastIndex=0;var d=mc.exec(a);if(d){var c=+d[1],e=+d[2],f=d=0,g=0,h=0,k=Dd(c),e=7*(e-1);b&&(d=b.getHours(),f= +b.getMinutes(),g=b.getSeconds(),h=b.getMilliseconds());return new Date(c,0,k.getDate()+e,d,f,g,h)}}return NaN},"yyyy-Www"),month:kb("month",Qd,Kb(Qd,["yyyy","MM"]),"yyyy-MM"),number:function(a,b,d,c,e,f){Hd(a,b,d,c);jb(a,b,d,c,e,f);c.$$parserName="number";c.$parsers.push(function(a){return c.$isEmpty(a)?null:vg.test(a)?parseFloat(a):u});c.$formatters.push(function(a){if(!c.$isEmpty(a)){if(!Q(a))throw lb("numfmt",a);a=a.toString()}return a});if(y(d.min)||d.ngMin){var g;c.$validators.min=function(a){return c.$isEmpty(a)|| +q(g)||a>=g};d.$observe("min",function(a){y(a)&&!Q(a)&&(a=parseFloat(a,10));g=Q(a)&&!isNaN(a)?a:u;c.$validate()})}if(y(d.max)||d.ngMax){var h;c.$validators.max=function(a){return c.$isEmpty(a)||q(h)||a<=h};d.$observe("max",function(a){y(a)&&!Q(a)&&(a=parseFloat(a,10));h=Q(a)&&!isNaN(a)?a:u;c.$validate()})}},url:function(a,b,d,c,e,f){jb(a,b,d,c,e,f);kc(c);c.$$parserName="url";c.$validators.url=function(a,b){var d=a||b;return c.$isEmpty(d)||tg.test(d)}},email:function(a,b,d,c,e,f){jb(a,b,d,c,e,f);kc(c); +c.$$parserName="email";c.$validators.email=function(a,b){var d=a||b;return c.$isEmpty(d)||ug.test(d)}},radio:function(a,b,d,c){q(d.name)&&b.attr("name",++nb);b.on("click",function(a){b[0].checked&&c.$setViewValue(d.value,a&&a.type)});c.$render=function(){b[0].checked=d.value==c.$viewValue};d.$observe("value",c.$render)},checkbox:function(a,b,d,c,e,f,g,h){var k=Id(h,a,"ngTrueValue",d.ngTrueValue,!0),l=Id(h,a,"ngFalseValue",d.ngFalseValue,!1);b.on("click",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 ma(a,k)});c.$parsers.push(function(a){return a?k:l})},hidden:x,button:x,submit:x,reset:x,file:x},Dc=["$browser","$sniffer","$filter","$parse",function(a,b,d,c){return{restrict:"E",require:["?ngModel"],link:{pre:function(e,f,g,h){h[0]&&(Sd[F(g.type)]||Sd.text)(e,f,g,h[0],b,a,d,c)}}}}],wg=/^(true|false|\d+)$/,Ne=function(){return{restrict:"A",priority:100,compile:function(a, +b){return wg.test(b.ngValue)?function(a,b,e){e.$set("value",a.$eval(e.ngValue))}:function(a,b,e){a.$watch(e.ngValue,function(a){e.$set("value",a)})}}}},ne=["$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=q(a)?"":a})}}}}],pe=["$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=q(a)?"":a})}}}}],oe=["$sce","$parse","$compile",function(a,b,d){return{restrict:"A",compile:function(c,e){var f=b(e.ngBindHtml),g=b(e.ngBindHtml,function(a){return(a||"").toString()});d.$$addBindingClass(c);return function(b,c,e){d.$$addBindingInfo(c,e.ngBindHtml);b.$watch(g,function(){c.html(a.getTrustedHtml(f(b))||"")})}}}}],Me=na({restrict:"A",require:"ngModel",link:function(a,b,d,c){c.$viewChangeListeners.push(function(){a.$eval(d.ngChange)})}}), +qe=lc("",!0),se=lc("Odd",0),re=lc("Even",1),te=La({compile:function(a,b){b.$set("ngCloak",u);a.removeClass("ng-cloak")}}),ue=[function(){return{restrict:"A",scope:!0,controller:"@",priority:500}}],Ic={},xg={blur:!0,focus:!0};n("click dblclick mousedown mouseup mouseover mouseout mousemove mouseenter mouseleave keydown keyup keypress submit focus blur copy cut paste".split(" "),function(a){var b=va("ng-"+a);Ic[b]=["$parse","$rootScope",function(d,c){return{restrict:"A",compile:function(e,f){var g= +d(f[b],null,!0);return function(b,d){d.on(a,function(d){var e=function(){g(b,{$event:d})};xg[a]&&c.$$phase?b.$evalAsync(e):b.$apply(e)})}}}}]});var xe=["$animate",function(a){return{multiElement:!0,transclude:"element",priority:600,terminal:!0,restrict:"A",$$tlb:!0,link:function(b,d,c,e,f){var g,h,k;b.$watch(c.ngIf,function(b){b?h||f(function(b,e){h=e;b[b.length++]=X.createComment(" end ngIf: "+c.ngIf+" ");g={clone:b};a.enter(b,d.parent(),d)}):(k&&(k.remove(),k=null),h&&(h.$destroy(),h=null),g&&(k= +rb(g.clone),a.leave(k).then(function(){k=null}),g=null))})}}}],ye=["$templateRequest","$anchorScroll","$animate",function(a,b,d){return{restrict:"ECA",priority:400,terminal:!0,transclude:"element",controller:fa.noop,compile:function(c,e){var f=e.ngInclude||e.src,g=e.onload||"",h=e.autoscroll;return function(c,e,m,n,q){var s=0,v,u,p,C=function(){u&&(u.remove(),u=null);v&&(v.$destroy(),v=null);p&&(d.leave(p).then(function(){u=null}),u=p,p=null)};c.$watch(f,function(f){var m=function(){!y(h)||h&&!c.$eval(h)|| +b()},u=++s;f?(a(f,!0).then(function(a){if(u===s){var b=c.$new();n.template=a;a=q(b,function(a){C();d.enter(a,null,e).then(m)});v=b;p=a;v.$emit("$includeContentLoaded",f);c.$eval(g)}},function(){u===s&&(C(),c.$emit("$includeContentError",f))}),c.$emit("$includeContentRequested",f)):(C(),n.template=null)})}}}}],Pe=["$compile",function(a){return{restrict:"ECA",priority:-400,require:"ngInclude",link:function(b,d,c,e){/SVG/.test(d[0].toString())?(d.empty(),a(Lc(e.template,X).childNodes)(b,function(a){d.append(a)}, +{futureParentElement:d})):(d.html(e.template),a(d.contents())(b))}}}],ze=La({priority:450,compile:function(){return{pre:function(a,b,d){a.$eval(d.ngInit)}}}}),Le=function(){return{restrict:"A",priority:100,require:"ngModel",link:function(a,b,d,c){var e=b.attr(d.$attr.ngList)||", ",f="false"!==d.ngTrim,g=f?U(e):e;c.$parsers.push(function(a){if(!q(a)){var b=[];a&&n(a.split(g),function(a){a&&b.push(f?U(a):a)});return b}});c.$formatters.push(function(a){return I(a)?a.join(e):u});c.$isEmpty=function(a){return!a|| +!a.length}}}},mb="ng-valid",Jd="ng-invalid",Wa="ng-pristine",Jb="ng-dirty",Ld="ng-pending",lb=G("ngModel"),yg=["$scope","$exceptionHandler","$attrs","$element","$parse","$animate","$timeout","$rootScope","$q","$interpolate",function(a,b,d,c,e,f,g,h,k,l){this.$modelValue=this.$viewValue=Number.NaN;this.$$rawModelValue=u;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=u;this.$name=l(d.name||"",!1)(a);this.$$parentForm=Ib;var m=e(d.ngModel),r=m.assign,t=m,s=r,v=null,B,p=this;this.$$setOptions=function(a){if((p.$options=a)&&a.getterSetter){var b=e(d.ngModel+"()"),f=e(d.ngModel+"($$$p)");t=function(a){var c=m(a);z(c)&&(c=b(a));return c};s=function(a,b){z(m(a))?f(a,{$$$p:p.$modelValue}):r(a,p.$modelValue)}}else if(!m.assign)throw lb("nonassign",d.ngModel,ua(c));};this.$render=x;this.$isEmpty= +function(a){return q(a)||""===a||null===a||a!==a};var C=0;Gd({ctrl:this,$element:c,set:function(a,b){a[b]=!0},unset:function(a,b){delete a[b]},$animate:f});this.$setPristine=function(){p.$dirty=!1;p.$pristine=!0;f.removeClass(c,Jb);f.addClass(c,Wa)};this.$setDirty=function(){p.$dirty=!0;p.$pristine=!1;f.removeClass(c,Wa);f.addClass(c,Jb);p.$$parentForm.$setDirty()};this.$setUntouched=function(){p.$touched=!1;p.$untouched=!0;f.setClass(c,"ng-untouched","ng-touched")};this.$setTouched=function(){p.$touched= +!0;p.$untouched=!1;f.setClass(c,"ng-touched","ng-untouched")};this.$rollbackViewValue=function(){g.cancel(v);p.$viewValue=p.$$lastCommittedViewValue;p.$render()};this.$validate=function(){if(!Q(p.$modelValue)||!isNaN(p.$modelValue)){var a=p.$$rawModelValue,b=p.$valid,c=p.$modelValue,d=p.$options&&p.$options.allowInvalid;p.$$runValidators(a,p.$$lastCommittedViewValue,function(e){d||b===e||(p.$modelValue=e?a:u,p.$modelValue!==c&&p.$$writeModelToScope())})}};this.$$runValidators=function(a,b,c){function d(){var c= +!0;n(p.$validators,function(d,e){var g=d(a,b);c=c&&g;f(e,g)});return c?!0:(n(p.$asyncValidators,function(a,b){f(b,null)}),!1)}function e(){var c=[],d=!0;n(p.$asyncValidators,function(e,g){var h=e(a,b);if(!h||!z(h.then))throw lb("$asyncValidators",h);f(g,u);c.push(h.then(function(){f(g,!0)},function(a){d=!1;f(g,!1)}))});c.length?k.all(c).then(function(){g(d)},x):g(!0)}function f(a,b){h===C&&p.$setValidity(a,b)}function g(a){h===C&&c(a)}C++;var h=C;(function(){var a=p.$$parserName||"parse";if(q(B))f(a, +null);else return B||(n(p.$validators,function(a,b){f(b,null)}),n(p.$asyncValidators,function(a,b){f(b,null)})),f(a,B),B;return!0})()?d()?e():g(!1):g(!1)};this.$commitViewValue=function(){var a=p.$viewValue;g.cancel(v);if(p.$$lastCommittedViewValue!==a||""===a&&p.$$hasNativeValidators)p.$$lastCommittedViewValue=a,p.$pristine&&this.$setDirty(),this.$$parseAndValidate()};this.$$parseAndValidate=function(){var b=p.$$lastCommittedViewValue;if(B=q(b)?u:!0)for(var c=0;ce||c.$isEmpty(b)||b.length<=e}}}}},Gc=function(){return{restrict:"A",require:"?ngModel",link:function(a,b,d,c){if(c){var e=0;d.$observe("minlength",function(a){e=ea(a)||0;c.$validate()});c.$validators.minlength=function(a,b){return c.$isEmpty(b)||b.length>=e}}}}};S.angular.bootstrap? +console.log("WARNING: Tried to load angular more than once."):(ce(),ee(fa),fa.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(" "),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",pluralCat:function(a,c){var e=a|0,f=c;u===f&&(f=Math.min(b(a),3));Math.pow(10,f);return 1==e&&0==f?"one":"other"}})}]),B(X).ready(function(){Zd(X,yc)}))})(window,document);!window.angular.$$csp().noInlineStyle&&window.angular.element(document.head).prepend(''); //# sourceMappingURL=angular.min.js.map diff --git a/admin/static/js/app.js b/admin/static/js/app.js index 9b6fc13b..efa3caba 100644 --- a/admin/static/js/app.js +++ b/admin/static/js/app.js @@ -1,2988 +1,413 @@ -angular.module("FICApp", ["ngRoute", "ngResource", "ngSanitize"]) - .config(function ($routeProvider, $locationProvider) { - $routeProvider - .when("/themes", { - controller: "ThemesListController", - templateUrl: "views/theme-list.html" - }) - .when("/themes/:themeId", { - controller: "ThemeController", - templateUrl: "views/theme.html" - }) - .when("/themes/:themeId/exercices/:exerciceId", { - controller: "ExerciceController", - templateUrl: "views/exercice.html" - }) - .when("/repositories", { - controller: "RepositoriesController", - templateUrl: "views/repositories.html" - }) - .when("/sync", { - controller: "SyncController", - templateUrl: "views/sync.html" - }) - .when("/settings", { - controller: "SettingsController", - templateUrl: "views/settings.html" - }) - .when("/auth", { - controller: "AuthController", - templateUrl: "views/auth.html" - }) - .when("/pki", { - controller: "PKIController", - templateUrl: "views/pki.html" - }) - .when("/exercices", { - controller: "AllExercicesListController", - templateUrl: "views/exercice-list.html" - }) - .when("/exercices/:exerciceId", { - controller: "ExerciceController", - templateUrl: "views/exercice.html" - }) - .when("/exercices/:exerciceId/flags", { - controller: "ExerciceController", - templateUrl: "views/exercice-flags.html" - }) - .when("/exercices/:exerciceId/resolution", { - controller: "ExerciceController", - templateUrl: "views/exercice-resolution.html" - }) - .when("/forge-links", { - controller: "ForgeLinksController", - templateUrl: "views/exercices-forgelink.html" - }) - .when("/tags", { - controller: "TagsListController", - templateUrl: "views/tags.html" - }) - .when("/teams", { - controller: "TeamsListController", - templateUrl: "views/team-list.html" - }) - .when("/teams/print", { - controller: "TeamsListController", - templateUrl: "views/team-print.html" - }) - .when("/teams/export", { - controller: "TeamsListController", - templateUrl: "views/team-export.html" - }) - .when("/teams/:teamId", { - controller: "TeamController", - templateUrl: "views/team-edit.html" - }) - .when("/teams/:teamId/stats", { - controller: "TeamController", - templateUrl: "views/team-stats.html" - }) - .when("/teams/:teamId/score", { - controller: "TeamController", - templateUrl: "views/team-score.html" - }) - .when("/public/:screenId", { - controller: "PublicController", - templateUrl: "views/public.html" - }) - .when("/files", { - controller: "FilesListController", - templateUrl: "views/file-list.html" - }) - .when("/events", { - controller: "EventsListController", - templateUrl: "views/event-list.html" - }) - .when("/events/:eventId", { - controller: "EventController", - templateUrl: "views/event.html" - }) - .when("/claims", { - controller: "ClaimsListController", - templateUrl: "views/claim-list.html" - }) - .when("/claims/:claimId", { - controller: "ClaimController", - templateUrl: "views/claim.html" - }) - .when("/", { - templateUrl: "views/home.html" - }); - $locationProvider.html5Mode(true); - }); +angular.module("FICApp", ["ngRoute", "ngResource"]) + .config(function($routeProvider, $locationProvider) { + $routeProvider + .when("/themes", { + controller: "ThemesListController", + templateUrl: "views/theme-list.html" + }) + .when("/themes/:themeId", { + controller: "ThemeController", + templateUrl: "views/theme.html" + }) + .when("/themes/:themeId/:exerciceId", { + controller: "ExerciceController", + templateUrl: "views/exercice.html" + }) + .when("/teams", { + controller: "TeamsListController", + templateUrl: "views/team-list.html" + }) + .when("/teams/:teamId", { + controller: "TeamController", + templateUrl: "views/team.html" + }) + .when("/teams/new", { + controller: "TeamNewController", + templateUrl: "views/team-new.html" + }) + .when("/", { + templateUrl: "views/home.html" + }); + $locationProvider.html5Mode(true); + }); -function setCookie(name, value, days) { - var expires; +angular.module("FICApp") + .factory("Version", function($resource) { + return $resource("/api/version") + }) + .factory("Team", function($resource) { + return $resource("/api/teams/:teamId", { teamId: '@id' }, { + 'save': {method: 'PATCH'}, + }) + }) + .factory("TeamMember", function($resource) { + return $resource("/api/teams/:teamId/members", { teamId: '@id' }) + }) + .factory("TeamMy", function($resource) { + return $resource("/api/teams/:teamId/my.json", { teamId: '@id' }) + }) + .factory("Teams", function($resource) { + return $resource("/api/teams/teams.json") + }) + .factory("TeamStats", function($resource) { + return $resource("/api/teams/:teamId/stats.json", { teamId: '@id' }) + }) + .factory("TeamPresence", function($resource) { + return $resource("/api/teams/:teamId/tries", { teamId: '@id' }) + }) + .factory("Theme", function($resource) { + return $resource("/api/themes/:themeId", null, { + 'save': {method: 'PATCH'}, + }) + }) + .factory("Themes", function($resource) { + return $resource("/api/themes/themes.json", null, { + 'get': {method: 'GET'}, + }) + }) + .factory("Exercice", function($resource) { + return $resource("/api/exercices/:exerciceId") + }); - if (days) { - var date = new Date(); - date.setTime(date.getTime() + (days * 24 * 60 * 60 * 1000)); - expires = "; expires=" + date.toGMTString(); - } else { - expires = ""; - } - document.cookie = encodeURIComponent(name) + "=" + encodeURIComponent(value) + expires + "; path=/"; -} - -function getCookie(name) { - var nameEQ = encodeURIComponent(name) + "="; - var ca = document.cookie.split(';'); - for (var i = 0; i < ca.length; i++) { - var c = ca[i]; - while (c.charAt(0) === ' ') - c = c.substring(1, c.length); - if (c.indexOf(nameEQ) === 0) - return decodeURIComponent(c.substring(nameEQ.length, c.length)); - } - return null; +String.prototype.capitalize = function() { + return this + .toLowerCase() + .replace( + /(^|\s)([a-z])/g, + function(m,p1,p2) { return p1+p2.toUpperCase(); } + ); } angular.module("FICApp") - .directive('autofocus', ['$timeout', function ($timeout) { - return { - restrict: 'A', - link: function ($scope, $element) { - $timeout(function () { - $element[0].focus(); - }); - } - } - }]) + .filter("capitalize", function() { + return function(input) { + return input.capitalize(); + } + }) + .filter("time", function() { + return function(input) { + if (input == undefined) { + return "--"; + } else if (input >= 10) { + return input; + } else { + return "0" + input; + } + } + }) - .component('toast', { - bindings: { - date: '=', - msg: '=', - timeout: '=', - title: '=', - variant: '=', - yesNo: '=', - onyes: '=', - onno: '=', - }, - controller: function ($element) { - if (this.timeout === 0) - $element.children(0).toast({ autohide: false }); - else if (!this.timeout && this.timeout !== 0) - $element.children(0).toast({ delay: 7000 }); - else - $element.children(0).toast({ delay: this.timeout }); - $element.children(0).toast('show'); - $element.children(0).on('hidden.bs.toast', function () { - $element.children(0).toast('dispose'); - }); - this.yesFunc = function () { - $element.children(0).toast('hide'); - if (this.onyes) - this.onyes(); - } - this.noFunc = function () { - $element.children(0).toast('hide'); - if (this.onno) - this.onno(); - } - }, - template: `` + .controller("VersionController", function($scope, Version) { + $scope.v = Version.get(); + }) + + .controller("ThemesListController", function($scope, Theme, $location) { + $scope.themes = Theme.query(); + $scope.fields = ["id", "name"]; + + $scope.show = function(id) { + $location.url("/themes/" + id); + }; + }) + .controller("ThemeController", function($scope, Theme, $routeParams) { + $scope.theme = Theme.get({ themeId: $routeParams.themeId }); + $scope.fields = ["name"]; + + $scope.saveTheme = function() { + this.theme.$save({themeId: this.theme.themeId}); + } + }) + + .controller("ExercicesListController", function($scope, Exercice, $routeParams, $location) { + $scope.exercices = Exercice.query({ themeId: $routeParams.themeId }); + $scope.fields = ["id", "title", "statement", "videoURI"]; + + $scope.show = function(id) { + $location.url("/themes/" + $routeParams.themeId + "/" + id); + }; + }) + .controller("ExerciceController", function($scope, Theme, $routeParams) { + $scope.exercice = Exercice.get({ themeId: $routeParams.themeId }); + $scope.fields = ["name", "statement", "hint", "videoURI"]; + + $scope.saveTheme = function() { + this.exercice.$save({ themeId: this.exercice.themeId, exerciceId: this.exercice.exerciceId}); + } + }) + + .controller("TeamsListController", function($scope, Team, $location) { + $scope.teams = Team.query(); + $scope.fields = ["id", "name", "initialName"]; + + $scope.show = function(id) { + $location.url("/teams/" + id); + }; + }) + .controller("TeamController", function($scope, Team, TeamMember, $routeParams) { + $scope.team = Team.get({ teamId: $routeParams.teamId }); + $scope.members = TeamMember.query({ teamId: $routeParams.teamId }); + }) + .controller("TeamStatsController", function($scope, TeamStats, $routeParams) { + $scope.teamstats = TeamStats.get({ teamId: $routeParams.teamId }); + $scope.teamstats.$promise.then(function(res) { + solvedByLevelPie("#pieLevels", res.levels); + solvedByThemesPie("#pieThemes", res.themes); + }); + }) + .controller("TeamExercicesController", function($scope, Teams, Themes, TeamMy, Exercice, $routeParams) { + $scope.teams = Teams.get(); + $scope.themes = Themes.get(); + $scope.exercices = Exercice.query(); + $scope.my = TeamMy.get({ teamId: $routeParams.teamId }); + + $scope.teams.$promise.then(function(res){ + $scope.nb_teams = 0; + $scope.nb_reg_teams = Object.keys(res).length; + angular.forEach(res, function(team, tid) { + if (team.rank) + $scope.nb_teams += 1; + }, 0); }); -angular.module("FICApp") - .factory("Version", function ($resource) { - return $resource("api/version") - }) - .factory("Timestamp", function ($resource) { - return $resource("api/timestamps.json") - }) - .factory("Health", function ($resource) { - return $resource("api/health.json") - }) - .factory("Monitor", function ($resource) { - return $resource("api/monitor/:machineId", { machineId: '@id' }) - }) - .factory("Event", function ($resource) { - return $resource("api/events/:eventId", { eventId: '@id' }, { - 'update': { method: 'PUT' }, - }) - }) - .factory("Claim", function ($resource) { - return $resource("api/claims/:claimId", { claimId: '@id' }, { - 'update': { method: 'PUT' }, - }) - }) - .factory("ClaimAssignee", function ($resource) { - return $resource("api/claims-assignees/:assigneeId", { assigneeId: '@id' }, { - 'update': { method: 'PUT' }, - }) - }) - .factory("Certificate", function ($resource) { - return $resource("api/certs/:serial", { serial: '@id' }, { - 'update': { method: 'PUT' }, - }) - }) - .factory("CACertificate", function ($resource) { - return $resource("api/ca/:serial", { serial: '@id' }) - }) - .factory("File", function ($resource) { - return $resource("api/files/:fileId", { fileId: '@id' }) - }) - .factory("Settings", function ($resource) { - return $resource("api/settings.json", null, { - 'update': { method: 'PUT' }, - }) - }) - .factory("NextSettings", function ($resource) { - return $resource("api/settings-next/:tsId", { tsId: '@id' }, { - 'update': { method: 'PUT' }, - }) - }) - .factory("SettingsChallenge", function ($resource) { - return $resource("api/challenge.json", null, { - 'update': { method: 'PUT' }, - }) - }) - .factory("Scene", function ($resource) { - return $resource("api/public/:screenId", { screenId: '@id' }, { - 'update': { method: 'PUT' }, - }) - }) - .factory("Team", function ($resource) { - return $resource("api/teams/:teamId", { teamId: '@id' }, { - 'update': { method: 'PUT' }, - }) - }) - .factory("TeamCertificate", function ($resource) { - return $resource("api/teams/:teamId/certificates", { teamId: '@id' }) - }) - .factory("TeamAssociation", function ($resource) { - return $resource("api/teams/:teamId/associations/:assoc", { teamId: '@teamId', assoc: '@assoc' }) - }) - .factory("TeamMember", function ($resource) { - return $resource("api/teams/:teamId/members", { teamId: '@id' }, { - 'save': { method: 'PUT' }, - }) - }) - .factory("TeamMy", function ($resource) { - return $resource("api/teams/:teamId/my.json", { teamId: '@id' }) - }) - .factory("Teams", function ($resource) { - return $resource("api/teams.json") - }) - .factory("TeamHistory", function ($resource) { - return $resource("api/teams/:teamId/history.json", { teamId: '@id' }) - }) - .factory("TeamScore", function ($resource) { - return $resource("api/teams/:teamId/score-grid.json", { teamId: '@id' }) - }) - .factory("TeamStats", function ($resource) { - return $resource("api/teams/:teamId/stats.json", { teamId: '@id' }) - }) - .factory("TeamPresence", function ($resource) { - return $resource("api/teams/:teamId/tries", { teamId: '@id' }) - }) - .factory("Theme", function ($resource) { - return $resource("api/themes/:themeId", { themeId: '@id' }, { - update: { method: 'PUT' } - }); - }) - .factory("Themes", function ($resource) { - return $resource("api/themes.json", null, { - 'get': { method: 'GET' }, - }) - }) - .factory("ThemedExercice", function ($resource) { - return $resource("api/themes/:themeId/exercices/:exerciceId", { themeId: '@id', exerciceId: '@idExercice' }, { - update: { method: 'PUT' } - }) - }) - .factory("Exercice", function ($resource) { - return $resource("api/exercices/:exerciceId", { exerciceId: '@id' }, { - update: { method: 'PUT' }, - patch: { method: 'PATCH' } - }) - }) - .factory("ExerciceClaims", function ($resource) { - return $resource("api/exercices/:exerciceId/claims", { exerciceId: '@idExercice' }) - }) - .factory("ExerciceTags", function ($resource) { - return $resource("api/exercices/:exerciceId/tags", { exerciceId: '@idExercice' }, { - update: { method: 'PUT' } - }) - }) - .factory("ExerciceHistory", function ($resource) { - return $resource("api/exercices/:exerciceId/history.json", { exerciceId: '@id' }) - }) - .factory("ExerciceTries", function ($resource) { - return $resource("api/exercices/:exerciceId/tries/:tryId", { exerciceId: '@idExercice', tryId: '@id' }) - }) - .factory("ExercicesStats", function ($resource) { - return $resource("api/exercices_stats.json", { themeId: '@id' }) - }) - .factory("ExerciceStats", function ($resource) { - return $resource("api/exercices/:exerciceId/stats.json", { exerciceId: '@id' }) - }) - .factory("ExerciceFile", function ($resource) { - return $resource("api/exercices/:exerciceId/files/:fileId", { exerciceId: '@idExercice', fileId: '@id' }, { - update: { method: 'PUT' } - }) - }) - .factory("ExerciceHint", function ($resource) { - return $resource("api/exercices/:exerciceId/hints/:hintId", { exerciceId: '@idExercice', hintId: '@id' }, { - update: { method: 'PUT' } - }) - }) - .factory("ExerciceHintDeps", function ($resource) { - return $resource("api/exercices/:exerciceId/hints/:hintId/dependancies", { exerciceId: '@idExercice', hintId: '@id' }) - }) - .factory("ExerciceFlag", function ($resource) { - return $resource("api/exercices/:exerciceId/flags/:flagId", { exerciceId: '@idExercice', flagId: '@id' }, { - update: { method: 'PUT' } - }) - }) - .factory("ExerciceFlagChoices", function ($resource) { - return $resource("api/exercices/:exerciceId/flags/:flagId/choices/:choiceId", { exerciceId: '@idExercice', flagId: '@idFlag', choiceId: '@id' }, { - 'update': { method: 'PUT' }, - }) - }) - .factory("ExerciceFlagDeps", function ($resource) { - return $resource("api/exercices/:exerciceId/flags/:flagId/dependancies", { exerciceId: '@idExercice', flagId: '@id' }) - }) - .factory("ExerciceFlagStats", function ($resource) { - return $resource("api/exercices/:exerciceId/flags/:flagId/statistics", { exerciceId: '@idExercice', flagId: '@id' }) - }) - .factory("ExerciceMCQFlag", function ($resource) { - return $resource("api/exercices/:exerciceId/quiz/:mcqId", { exerciceId: '@idExercice', mcqId: '@id' }, { - update: { method: 'PUT' } - }) - }) - .factory("ExerciceMCQDeps", function ($resource) { - return $resource("api/exercices/:exerciceId/quiz/:mcqId/dependancies", { exerciceId: '@idExercice', mcqId: '@id' }) - }) - .factory("ExerciceMCQStats", function ($resource) { - return $resource("api/exercices/:exerciceId/quiz/:mcqId/statistics", { exerciceId: '@idExercice', mcqId: '@id' }) + $scope.my.$promise.then(function(res){ + $scope.solved_exercices = 0; + angular.forEach(res.exercices, function(exercice, eid) { + if (exercice.solved) { + $scope.solved_exercices += 1; + } + }, 0); }); -angular.module("FICApp") - .filter("countHints", function () { - return function (input) { - if (input == undefined) - return 0; - return input.reduce(function (sum, n) { return sum + (n.content || n.file) ? 1 : 0; }, 0); - } + }) + .controller("TeamNewController", function($scope, Team, $location) { + $scope.contact = new Team({ + }) - .filter("toColor", function () { - return function (num) { - num >>>= 0; - var b = (num & 0xFF).toString(16), - g = ((num & 0xFF00) >>> 8).toString(16), - r = ((num & 0xFF0000) >>> 16).toString(16), - a = ((num & 0xFF000000) >>> 24) / 255; - if (r.length <= 1) r = "0" + r; - if (g.length <= 1) g = "0" + g; - if (b.length <= 1) b = "0" + b; - return "#" + r + g + b; + }) + + .controller("PresenceController", function($scope, TeamPresence, $routeParams) { + $scope.presence = TeamPresence.query({ teamId: $routeParams.teamId }); + $scope.presence.$promise.then(function(res) { + presenceCal("#presenceCal", res); + }); + }) + .controller("CountdownController", function($scope, $http, $timeout) { + $scope.time = {}; + function updTime() { + $timeout.cancel($scope.cbm); + $scope.cbm = $timeout(updTime, 1000); + if (sessionStorage.userService) { + var time = angular.fromJson(sessionStorage.userService); + var srv_cur = (Date.now() + (time.cu * 1000 - time.he)) / 1000; + var remain = time.du; + if (time.st == Math.floor(srv_cur)) { + $scope.refresh(true); } - }) - .filter("cksum", function () { - return function (input) { - if (input == undefined) - return input; - var raw = atob(input).toString(16); - var hex = ''; - for (var i = 0; i < raw.length; i++) { - var _hex = raw.charCodeAt(i).toString(16) - hex += (_hex.length == 2 ? _hex : '0' + _hex); - } - return hex + if (time.st > 0 && time.st <= srv_cur) { + remain = time.st + time.du - srv_cur; } - }) - - .component('dependancy', { - bindings: { - dep: '=', - deleteDep: '=', - }, - controller: function () { }, - template: ` -
  • - Flag {{ $ctrl.dep.label }} - QCM {{ $ctrl.dep.title }} - -
  • - ` - }) - - .directive('color', function () { - return { - require: 'ngModel', - link: function (scope, ele, attr, ctrl) { - ctrl.$formatters.unshift(function (num) { - num >>>= 0; - var b = (num & 0xFF).toString(16), - g = ((num & 0xFF00) >>> 8).toString(16), - r = ((num & 0xFF0000) >>> 16).toString(16), - a = ((num & 0xFF000000) >>> 24) / 255; - if (r.length <= 1) r = "0" + r; - if (g.length <= 1) g = "0" + g; - if (b.length <= 1) b = "0" + b; - return "#" + r + g + b; - }); - ctrl.$parsers.unshift(function (viewValue) { - var result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(viewValue); - return result ? ( - parseInt(result[1], 16) * 256 * 256 + - parseInt(result[2], 16) * 256 + - parseInt(result[3], 16) - - ) : 0; - }); - } - }; - }) - - .directive('integer', function () { - return { - require: 'ngModel', - link: function (scope, ele, attr, ctrl) { - ctrl.$parsers.unshift(function (viewValue) { - return parseInt(viewValue, 10); - }); - } - }; - }) - - .directive('float', function () { - return { - require: 'ngModel', - link: function (scope, ele, attr, ctrl) { - ctrl.$parsers.unshift(function (viewValue) { - return parseFloat(viewValue, 10); - }); - } - }; - }) - - .run(function ($rootScope, $http, $interval, $timeout, Settings, $location) { - $rootScope.$location = $location; - $rootScope.Utils = { - keys: Object.keys - }; - $rootScope.refreshSettings = function () { - $http.get("api/settings.json").then(function (response) { - response.data.start = new Date(response.data.start); - response.data.end = new Date(response.data.end); - response.data.generation = new Date(response.data.generation); - if ($rootScope.settings && $rootScope.settings.activateTime instanceof Date) - response.data.activateTime = $rootScope.settings.activateTime; - $rootScope.settings = response.data; - $rootScope.recvTime(response); - }) - } - $rootScope.refreshSettings(); - $interval($rootScope.refreshSettings, 10000); - - $rootScope.toasts = []; - $rootScope.addToast = function (kind, title, msg, yesFunc, noFunc, tmout) { - $rootScope.toasts.unshift({ - variant: kind, - title: title, - msg: msg, - timeout: tmout, - yesFunc: yesFunc, - noFunc: noFunc, - }); - } - - $rootScope.staticFilesNeedUpdate = 0; - $rootScope.staticRegenerationInProgress = false; - $rootScope.regenerateStaticFiles = function () { - $rootScope.staticRegenerationInProgress = true; - $http.post("api/full-generation").then(function (response) { - $rootScope.staticFilesNeedUpdate = 0; - $rootScope.staticRegenerationInProgress = false; - $rootScope.addToast('success', 'Regeneration ended'); - }, function (response) { - $rootScope.staticRegenerationInProgress = false; - $rootScope.addToast('error', 'An error occurs when saving settings:', response.data.errmsg); - }) - } - - $rootScope.$on('$locationChangeStart', function (event, next, current) { - if ($rootScope.staticFilesNeedUpdate) { - $timeout(function () { - document.getElementById("circle1").classList.add("play"); - }, 10); - $timeout(function () { - document.getElementById("circle1").classList.remove("play"); - }, 710); - - $timeout(function () { - document.getElementById("circle2").classList.add("play"); - }, 50); - $timeout(function () { - document.getElementById("circle2").classList.remove("play"); - }, 750); - } - }); - - $rootScope.logged = parseInt(getCookie("myassignee")) > 0; - }) - - .controller("VersionController", function ($scope, Version) { - $scope.v = Version.get(); - }) - - .controller("TimestampController", function ($scope, $interval, Timestamp) { - $scope.t = Timestamp.get(); - var refresh = function () { - $scope.t = Timestamp.get(); - } - var myinterval = $interval(refresh, 2500); - $scope.$on('$destroy', function () { $interval.cancel(myinterval); }); - }) - - .controller("HealthController", function ($scope, $interval, Health, $http) { - var refresh = function () { - $scope.health = Health.query(); - } - refresh(); - var myinterval = $interval(refresh, 2500); - $scope.drop_submission = function (path) { - $scope.addToast('info', 'Delete submission', 'Ensure this submission is not interesting. Continue?', - function () { - $http.delete("api/submissions" + path).then(function (response) { - refresh(); - }, function (response) { - $scope.addToast('danger', 'An error occurs when deleting submission:', response.data.errmsg); - }); - } - ); - } - $scope.$on('$destroy', function () { $interval.cancel(myinterval); }); - }) - - .controller("MonitorController", function ($scope, Monitor) { - $scope.monitor = Monitor.get(); - }) - - .controller("AllTeamAssociationsController", function ($scope, $http) { - $scope.newdqa = ""; - $scope.addDelegatedQA = function () { - if ($scope.newdqa.length) { - if (!$scope.config.delegated_qa) - $scope.config.delegated_qa = []; - - $scope.config.delegated_qa.push($scope.newdqa); - $scope.saveSettings(); - $scope.newdqa = ""; - } - } - - $scope.allAssociations = []; - $http.get("api/teams-associations.json").then(function (response) { - $scope.allAssociations = response.data; - }) - }) - - .controller("SettingsController", function ($scope, $rootScope, NextSettings, Settings, SettingsChallenge, $location, $http, $interval) { - $scope.nextsettings = NextSettings.query(); - $scope.erase = false; - $scope.editNextSettings = function (ns) { - $scope.activateTime = new Date(ns.date); - $rootScope.settings.activateTime = $scope.activateTime; - $scope.erase = true; - Object.keys(ns.values).forEach(function (k) { - $scope.config[k] = ns.values[k]; - }); - $scope.config.enableExerciceDepend = $scope.config.unlockedChallengeDepth >= 0; - $scope.config.disabledsubmitbutton = $scope.config.disablesubmitbutton && $scope.config.disablesubmitbutton.length > 0; - } - $scope.deleteNextSettings = function (ns) { - ns.$delete().then(function () { - $scope.nextsettings = NextSettings.query(); - }) - } - - $scope.displayDangerousActions = false; - $scope.config = Settings.get(); - $scope.dist_config = {}; - $scope.config.$promise.then(function (response) { - response.start = new Date(response.start); - if (response.end) response.end = new Date(response.end); - else response.end = null; - response.generation = new Date(response.generation); - response.activateTime = new Date(response.activateTime); - $scope.dist_config = Object.assign({}, response); - - response.enableExerciceDepend = response.unlockedChallengeDepth >= 0; - response.disabledsubmitbutton = response.disablesubmitbutton && response.disablesubmitbutton.length > 0; - if (response.end) { - $scope.duration = (response.end - response.start)/60000; - } - }) - $scope.challenge = SettingsChallenge.get(); - $scope.duration = 360; - $scope.durationChange = function(endChanged) { - if (endChanged) - $scope.duration = (new Date($scope.config.end).getTime() - new Date($scope.config.start).getTime())/60000; - else - $scope.config.end = new Date(new Date($scope.config.start).getTime() + $scope.duration * 60000); - } - $scope.activateTime = ""; - $scope.challenge.$promise.then(function (c) { - if (c.duration) - $scope.duration = c.duration; - }); - - $scope.exerciceDependChange = function () { - if ($scope.config.enableExerciceDepend) - $scope.config.unlockedChallengeDepth = 0; - else - $scope.config.unlockedChallengeDepth = -1; - }; - - $scope.submitButtonStateChange = function () { - if ($scope.config.disabledsubmitbutton) - $scope.config.disablesubmitbutton = "Mise à jour en cours..."; - else - $scope.config.disablesubmitbutton = ""; - }; - - $scope.dropDelegatedQA = function (member) { - if (!$scope.config.delegated_qa) { - $scope.config.delegated_qa = []; - } - angular.forEach($scope.config.delegated_qa, function (m, k) { - if (member == m) - $scope.config.delegated_qa.splice(k, 1); - }); - $scope.saveSettings(); - } - - $scope.saveChallengeInfo = function () { - this.challenge.duration = $scope.duration; - this.challenge.$update(function (response) { - $scope.addToast('success', 'Infos du challenge mises à jour avec succès !'); - }, function (response) { - $scope.addToast('danger', 'An error occurs when saving challenge info:', response.data.errmsg); - }); - } - $scope.saveSettings = function (msg) { - if (msg === undefined) { msg = 'New settings saved!'; } - if (this.config.end == "") this.config.end = null; - - var nStart = this.config.start; - var nEnd = this.config.end; - var nGen = this.config.generation; - var state = this.config.enableExerciceDepend; - this.config.unlockedChallengeDepth = (this.config.enableExerciceDepend ? this.config.unlockedChallengeDepth : -1) - this.config.disablesubmitbutton = (this.config.disabledsubmitbutton ? this.config.disablesubmitbutton : '') - var updateQuery = {}; - if (this.activateTime && this.activateTime != '') { - updateQuery['t'] = this.activateTime; - this.activateTime = null; - } - if (this.erase) { - updateQuery['erase'] = true; - this.erase = false; - } - this.config.$update(updateQuery, function (response) { - $scope.dist_config = Object.assign({}, response); - $scope.addToast('success', msg); - $scope.nextsettings = NextSettings.query(); - response.enableExerciceDepend = response.unlockedChallengeDepth >= 0; - response.disabledsubmitbutton = response.disablesubmitbutton && response.disablesubmitbutton.length > 0; - $rootScope.settings.start = new Date(nStart); - if (nEnd) { - $rootScope.settings.end = new Date(nEnd); - } else { - $rootScope.settings.end = null; - } - $rootScope.settings.generation = new Date(nGen); - $scope.updateActivateTime(); - }, function (response) { - $scope.addToast('danger', 'An error occurs when saving settings:', response.data.errmsg); - }); - } - $scope.launchChallenge = function () { - var ts = $rootScope.getSrvTime().getTime() - $rootScope.getSrvTime().getTime() % 60000; - this.config.start = new Date(ts + 120000); - this.config.end = new Date(ts + 120000 + this.duration * 60000); - - $scope.addToast('info', 'Challenge ready to start,', 'propagate the changes?', - function () { - $scope.saveSettings(); - }); - } - $scope.updateActivateTime = function () { - $rootScope.settings.activateTime = this.activateTime; - } - $scope.updActivateTime = function (modulo) { - if (modulo) { - var ts = Math.floor((new Date(this.config.end) - $rootScope.getSrvTime().getTime() - (60000 * modulo / 2)) / (60000 * modulo)) * (60000 * modulo); - var d = new Date(this.config.end) - ts; - this.activateTime = new Date(d); - this.updateActivateTime(); - } else { - this.activateTime = null; - this.updateActivateTime(); - } - } - $scope.reset = function (type) { - var txts = { - "settings": "En validant, vous remettrez les paramètres de cette page à leur valeur initiale, y compris la date de début du challenge.", - "challengeInfo": "En validant, vous effacerez les informations descriptives du challenge.", - "challenges": "En validant, vous retirerez toutes les données statiques des challenges.", - "teams": "En validant, vous supprimerez l'ensemble des équipes enregistreées.", - "game": "En validant, vous supprimerez toutes les tentatives, les validations, ... faites par les équipes.", - } - $scope.addToast('warning', txts[type], 'Êtes-vous sûr de vouloir continuer ?', - function () { - if (confirm("Êtes-vous vraiment sûr ?\n" + txts[type])) { - $http.post("api/reset", { "type": type }).then(function (time) { - $scope.addToast('success', type + 'reseted'); - $location.url("/"); - }, function (response) { - $scope.addToast('danger', 'An error occurs when reseting ' + type + ':', response.data.errmsg); - }); - - } - }); - }; - $scope.switchToProd = function () { - $scope.addToast('warning', "Activer le mode challenge ?", "L'activation du mode challenge est temporaire (vous devriez plutôt relancer le daemon avec l'option `-4real`). Ce mode permet d'éviter les mauvaises manipulations et désactive le hook git de synchronisation automatique. Êtes-vous sûr de vouloir continuer ?", - function () { - $http.put("api/prod", true).then(function (time) { - $rootScope.refreshSettings() - $scope.addToast('success', 'Mode challenge activé'); - }, function (response) { - $scope.addToast('danger', 'An error occurs when activating challenge mode:', response.data.errmsg); - }); - }); - }; - }) - - .controller("RepositoriesController", function ($scope, $http) { - $http.get("api/repositories").then(function (response) { - $scope.repositories = response.data.repositories; - }); - - $scope.deleteRepository = function(repo) { - $http.delete("api/repositories/" + repo.path).then(function (response) { - $scope.repositories[$scope.repositories.indexOf(repo)].hash = "- DELETED -"; - }); - }; - }) - .component('teamLink', { - bindings: { - idTeam: '=', - }, - controller: function (Team) { - var ctrl = this; - ctrl.team = {}; - - ctrl.$onInit = function () { - ctrl.team = Team.get({teamId: ctrl.idTeam}); - }; - }, - template: `{{ $ctrl.team.name }} ` - }) - .component('repositoryUptodate', { - bindings: { - repository: '<', - }, - controller: function ($http) { - var ctrl = this; - - ctrl.status = {}; - ctrl.color = "badge-secondary"; - - ctrl.$onInit = function () { - $http.post("api/repositories/" + ctrl.repository.path).then(function (response) { - ctrl.status = response.data; - - if (ctrl.repository.hash.startsWith(ctrl.status.hash)) { - ctrl.color = "badge-success"; - } else { - ctrl.color = "badge-danger"; - } - }); - }; - }, - template: `{{ $ctrl.status.hash }} {{ $ctrl.status.text }}` - }) - - .controller("SyncController", function ($scope, $rootScope, $location, $http, $interval) { - $scope.displayDangerousActions = false; - - var needRefreshSyncReportWhenReady = false; - var refreshSyncReport = function () { - needRefreshSyncReportWhenReady = false; - $http.get("full_import_report.json").then(function (response) { - $scope.syncReport = response.data; - }) - }; - refreshSyncReport() - - $scope.deepSyncInProgress = false; - - var progressInterval = $interval(function () { - $http.get("api/sync/status").then(function (response) { - if (response.data.progress && response.data.progress != 255) - needRefreshSyncReportWhenReady = true; - else if (needRefreshSyncReportWhenReady) - refreshSyncReport(); - if (response.data && response.data.progress) { - $scope.syncPercent = Math.floor(response.data.progress * 100 / 255); - $scope.deepSyncInProgress = response.data.pullMutex && response.data.syncMutex; - } else { - $scope.syncPercent = 0; - } - $scope.syncStatus = response.data; - }, function (response) { - $scope.syncPercent = 0; - $scope.syncStatus = response.data; - }) - }, 1500); - $scope.$on('$destroy', function () { $interval.cancel(progressInterval); }); - - $scope.deepSync = function (theme) { - if (theme) { - question = 'Faire une synchronisation intégrale du thème ' + theme.name + ' ?' - url = "api/sync/deep/" + theme.id - } else { - question = 'Faire une synchronisation intégrale ?' - url = "api/sync/deep" - } - $scope.addToast('warning', question, '', - function () { - $scope.deepSyncInProgress = true; - $http.post(url).then(function () { - $scope.deepSyncInProgress = false; - $scope.addToast('success', 'Synchronisation intégrale terminée.', 'Voir le rapport.', null, null, 15000); - }, function (response) { - $scope.deepSyncInProgress = false; - $scope.addToast('warning', 'Synchronisation intégrale terminée.', 'Voir le rapport.', null, null, 15000); - }); - }); - }; - $scope.speedyDeepSync = function () { - $scope.addToast('warning', 'Faire une synchronisation profonde rapide, sans s\'occuper des fichiers ?', '', - function () { - $scope.deepSyncInProgress = true; - $http.post("api/sync/speed").then(function () { - $scope.deepSyncInProgress = false; - $scope.addToast('success', 'Synchronisation profonde rapide terminée.', 'Voir le rapport.', null, null, 15000); - }, function (response) { - $scope.deepSyncInProgress = false; - $scope.addToast('warning', 'Synchronisation profinde rapide terminée.', 'Voir le rapport.', null, null, 15000); - }); - }); - }; - $scope.baseSync = function () { - $scope.addToast('warning', 'Tirer les mises à jour du dépôt ?', '', - function () { - $scope.deepSyncInProgress = true; - $http.post("api/sync/base").then(function () { - $scope.deepSyncInProgress = false; - $scope.addToast('success', 'Mise à jour terminée.'); - }, function (response) { - $scope.deepSyncInProgress = false; - $scope.addToast('danger', 'Mise à jour terminée.', response.data.errmsg); - }); - }); - }; - $scope.syncVideos = function () { - $scope.addToast('warning', 'Synchroniser les vidéos de résolution ?', 'ATTENTION il ne faut pas lancer cette synchronisation durant le challenge. Seulement une fois le challenge terminé, cela permet de rendre les vidéos accessibles dans l\'interface joueurs.', - function () { - $scope.deepSyncInProgress = true; - $http.post("api/sync/videos").then(function () { - $scope.deepSyncInProgress = false; - $scope.addToast('success', 'Import des vidéos terminé.'); - }, function (response) { - $scope.deepSyncInProgress = false; - $scope.addToast('danger', 'Import des vidéos terminé.', response.data.errmsg); - }); - }); - }; - $scope.dropSoluces = function () { - $scope.addToast('warning', 'Effacer les solutions', 'Ceci va retirer les textes de résolution de la base de données ainsi que les liens vers les vidéos.', - function () { - $scope.deepSyncInProgress = true; - $http.post("api/sync/drop_soluces").then(function () { - $scope.deepSyncInProgress = false; - $scope.addToast('success', 'Effacement des solutions terminé.'); - }, function (response) { - $scope.deepSyncInProgress = false; - $scope.addToast('danger', 'Effacement des solutions terminé avec des erreurs.', response.data.errmsg); - }); - }); - }; - $scope.diffWithRepo = function () { - $scope.diff = null; - $http({ - url: "api/sync/local-diff", - method: "POST" - }).then(function (response) { - $scope.diff = response.data; - if (response.data === null) { - $scope.addToast('success', 'Changements par rapport au dépôt', "Tout est pareil !"); - } - }, function (response) { - $scope.diff = null; - $scope.addToast('danger', 'An error occurs when synchronizing exercice:', response.data.errmsg); - }); - }; - }) - - .controller("AuthController", function ($scope, $http) { - $scope.generateHtpasswd = function () { - $http.post("api/htpasswd").then(function () { - $scope.addToast('success', 'Fichier htpasswd généré avec succès'); - }, function (response) { - $scope.addToast('danger', 'An error occurs when generating htpasswd file:', response.data.errmsg); - }); - }; - $scope.removeHtpasswd = function () { - $http.delete("api/htpasswd").then(function () { - $scope.addToast('success', 'Fichier htpasswd supprimé avec succès'); - }, function (response) { - $scope.addToast('danger', 'An error occurs when deleting htpasswd file:', response.data.errmsg); - }); - }; - }) - - .controller("OAuthController", function ($scope, $http) { - $scope.oauth_status = {}; - $scope.refreshOAuthStatus = function () { - $http.get("api/oauth-status").then(function (res) { - $scope.oauth_status = res.data; - }); - }; - $scope.refreshOAuthStatus(); - - $scope.genDexCfg = function () { - $http.post("api/dex.yaml").then(function () { - $http.post("api/dex-password.tpl").then(function () { - $scope.addToast('success', 'Dex config refreshed.', "Don't forget to reload/reboot frontend host."); - }, function (response) { - $scope.addToast('danger', 'An error occurs when generating dex password tpl:', response.data.errmsg); - }); - }, function (response) { - $scope.addToast('danger', 'An error occurs when generating dex config:', response.data.errmsg); - }); - $http.post("api/vouch-proxy.yaml").then(function () { - $scope.addToast('success', 'VouchProxy config refreshed.', "Don't forget to reload/reboot frontend host."); - }, function (response) { - $scope.addToast('danger', 'An error occurs when generating VouchProxy config:', response.data.errmsg); - }); - } - }) - - .controller("PKIController", function ($scope, $rootScope, Certificate, CACertificate, Team, $location, $http) { - var ts = Date.now() - Date.now() % 86400000; - var d = new Date(ts); - var f = new Date(ts + 3 * 86400000); - $scope.newca = { - notAfter: f.toISOString(), - notBefore: d.toISOString(), - }; - - $scope.teams = Team.query(); - $scope.certificates = Certificate.query(); - $scope.ca = CACertificate.get(); - - $scope.revoke = function () { - var targetserial = $("#revokeModal").data("certificate"); - if (targetserial) { - Certificate.delete({ serial: targetserial }).$promise.then( - function () { - $('#revokeModal').modal('hide'); - $scope.certificates = Certificate.query().$promise.then(function (certificates) { - certificates.forEach(function (certificate, cid) { - certificate.serial = parseInt(certificate.id).toString(16); - }); - }); - }, function (response) { - $scope.addToast('danger', 'An error occurs when trying to associate certificate:', response.data.errmsg); - } - ); - } - }; - - $scope.validateSearch = function (keyEvent) { - if (keyEvent.which === 13) { - var myCertificate = null; - $scope.certificates.forEach(function (certificate) { - if (String(certificate.id).indexOf($scope.query.toUpperCase()) >= 0) { - if (myCertificate === null) - myCertificate = certificate; - else - myCertificate = false; - } - }); - if (myCertificate && myCertificate.id_team == null) { - $('#associationModal').data('certificate', myCertificate.id) - $('#associationModal').modal() - } - } - }; - $scope.validatePKIForm = function (keyEvent) { - if (keyEvent.which === 13) - $scope.associate() - }; - $scope.associate = function () { - var targetserial = $("#associationModal").data("certificate"); - if (!targetserial) return; - Certificate.update({ serial: targetserial }, { id_team: $scope.selectedTeam }).$promise.then( - function () { - $('#associationModal').modal('hide'); - $scope.certificates = Certificate.query(); - $scope.selectedTeam = null; - }, function (response) { - $scope.addToast('danger', 'An error occurs when trying to associate certificate:', response.data.errmsg); - } - ); - }; - - $scope.generateCA = function () { - $http.post("api/ca/new", $scope.newca).then(function () { - $scope.ca = CACertificate.get(); - }, function (response) { - $scope.addToast('danger', 'An error occurs when generating CA:', response.data.errmsg); - }); - }; - $scope.renewCA = function () { - $scope.ca = {}; - }; - - $scope.generateCert = function () { - $http.post("api/certs").then(function () { - $scope.certificates = Certificate.query(); - }, function (response) { - $scope.addToast('danger', 'An error occurs when generating certificate:', response.data.errmsg); - }); - }; - }) - - .controller("PublicController", function ($scope, $rootScope, $routeParams, $location, Scene, Theme, Teams, Exercice) { - $scope.propagationtime = null; - $scope.presetName = ""; - $scope.screens = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]; - $scope.screenid = $routeParams.screenId; - $scope.display = Scene.get({ screenId: $routeParams.screenId }); - $scope.listScenes = Scene.get(); - $scope.themes = Theme.query(); - $scope.teams = Teams.get(); - - $scope.chScreen = function (sid) { - if ($scope.screenid) - $location.url("/public/" + $scope.screenid); - else - $location.url("/public/"); - } - - $scope.types = { - "welcome": "Messages de bienvenue", - "countdown": "Compte à rebours", - "message": "Message", - "panel": "Boîte", - "carousel": "Carousel", - "exercice": "Exercice", - "table": "Tableau", - "rank": "Classement", - "graph": "Graphique", - }; - $scope.typeside = { - "welcome": "Messages de bienvenue", - "themes": "Présentation des thèmes", - "exercice_follow": "Dernier exercice des événements", - "exercice": "Exercice", - "rank": "Classement", - "graph": "Graphique", - "message": "Message", - "panel": "Boîte", - }; - $scope.welcome_types = { - "teams": "Accueil des équipes", - "public": "Accueil du public", - }; - $scope.carousel_types = { - "exercices": "Exercices", - "teams": "Équipes", - "themes": "Thèmes", - "ranking": "Classement", - }; - $scope.colors = { - "primary": "Primaire", - "secondary": "Secondaire", - "info": "Info", - "success": "Success", - "warning": "Warning", - "danger": "Danger", - "light": "Clair", - "dark": "Foncé", - }; - $scope.rank_types = { - "general": "Classement général", - "final": "Classement final", - }; - $scope.rank_types_side = { - "carousel": "Classement général carousel", - "general": "Classement général", - }; - $scope.table_types = { - "levels": "Niveaux d'exercices", - "teams": "Équipes", - }; - $scope.exercices = Exercice.query(); - - $scope.clearScene = function () { - $scope.someUpdt = true; - $scope.display.scenes = []; - }; - $scope.presetScene = function (scene) { - $scope.someUpdt = true; - if (scene == "registration") { - $scope.display.scenes = [ - { - type: "welcome", - params: { kind: "teams", url: "https://fic.srs.epita.fr/" }, - }, - { - type: "welcome", - params: { kind: "public", notitle: true }, - }, - ]; - $scope.display.side = [ - { - type: "themes", - params: {}, - }, - ]; - } - else if (scene == "welcome") { - $scope.display.scenes = [ - { - type: "carousel", - params: { color: "info", kind: "themes", title: "Présentation des entreprises ciblées" }, - }, - ]; - $scope.display.side = [ - { - type: "welcome", - params: { kind: "public" }, - }, - ]; - } - else if (scene == "start") { - $scope.display.scenes = [ - { - type: "welcome", - params: { kind: "public" }, - }, - { - type: "countdown", - params: { color: "success", end: null, lead: "Go, go, go !", title: "Le challenge forensic va bientôt commencer !" }, - }, - ]; - $scope.display.side = [ - { - type: "themes", - params: {}, - }, - ]; - } - else if (scene == "end") { - $scope.display.scenes = [ - { - type: "rank", - params: { which: "final" }, - }, - { - type: "table", - params: { kind: "teams", themes: $scope.themes.map(function (z, i) { return z.id; }), total: true, teams: [] }, - }, - ]; - angular.forEach($scope.teams, function (team, tid) { - if (team.rank >= 1 && team.rank <= 4) - $scope.display.scenes[1].params.teams.push(tid) - }); - $scope.display.side = [ - { - type: "rank", - params: { which: "carousel" }, - }, - { - type: "graph", - params: { teams: [], height: 400, legend: false, hide: true }, - }, - { - type: "message", - params: { html: '
    Epita
    Réserves de cyberdéfense
    ', hide: true }, - }, - ]; - angular.forEach($scope.teams, function (team, tid) { - if (team.rank >= 1 && team.rank <= 9) - $scope.display.side[1].params.teams.push(tid) - }); - $scope.display.hideEvents = true; - $scope.display.hideCountdown = true; - } - else if (scene == "summary") { - $scope.display.scenes = [ - { - type: "table", - params: { kind: "levels", levels: [1, 2, 3, 4, 5], themes: $scope.themes.map(function (z, i) { return z.id; }), total: true }, - }, - { - type: "graph", - params: { teams: [], height: 337, legend: true }, - }, - ]; - angular.forEach($scope.teams, function (team, tid) { - if (team.rank >= 1 && team.rank <= 9) - $scope.display.scenes[1].params.teams.push(tid) - }); - $scope.display.side = [ - { - type: "exercice_follow", - params: {}, - }, - ]; - $scope.display.hideEvents = false; - $scope.display.hideCountdown = false; - } - else if (scene == "summary2") { - $scope.display.scenes = [ - { - type: "graph", - params: { teams: [], height: 400, legend: false }, - }, - { - type: "rank", - params: { limit: 10, which: "general", legend: true }, - }, - ]; - angular.forEach($scope.teams, function (team, tid) { - if (team.rank >= 1 && team.rank <= 9) - $scope.display.scenes[0].params.teams.push(tid) - }); - $scope.display.side = [ - { - type: "exercice_follow", - params: {}, - }, - ]; - $scope.display.hideEvents = false; - $scope.display.hideCountdown = false; - } - else if (scene == "summary3") { - $scope.display.scenes = [ - { - type: "table", - params: { kind: "levels", levels: [1, 2, 3, 4, 5], themes: $scope.themes.map(function (z, i) { return z.id; }), total: true }, - }, - { - type: "rank", - params: { limit: 10, which: "general", legend: false }, - }, - ]; - $scope.display.side = [ - { - type: "exercice_follow", - params: {}, - }, - ]; - $scope.display.hideEvents = false; - $scope.display.hideCountdown = false; - } - else if (scene == "happyhour") { - $scope.display.customCountdown = { - show: true, - shadow: "#E8CF5C", - end: new Date($rootScope.getSrvTime().getTime() + 1802000).toISOString(), - before: "Heure joyeuse : chaque résolution compte double !", - after: "Heure joyeuse terminée !", - } - } - else if (scene == "freehintquarter") { - $scope.display.customCountdown = { - show: true, - shadow: "#3DD28F", - end: new Date($rootScope.getSrvTime().getTime() + 902000).toISOString(), - before: "Quart d'heure facile : indices dévoilés !", - after: "Quart d'heure facile terminée !", - } - } - }; - - $scope.genSceneCountdownDate = function (scene, duration) { - scene.params.end = (new Date($rootScope.getSrvTime().getTime() + duration)).toISOString(); - } - $scope.genCustomCountdownDate = function (duration) { - if (duration == null) { - $scope.display.customCountdown.end = $rootScope.settings.activateTime; - } else { - $scope.display.customCountdown.end = (new Date($rootScope.getSrvTime().getTime() + duration)).toISOString(); - } - } - - $scope.loadFile = function (fname) { - $scope.display = Scene.get({ screenId: fname }); - $scope.someUpdt = true; - }; - $scope.deleteFile = function (fname) { - Scene.delete({ screenId: fname }); - $scope.listScenes = Scene.get(); - }; - $scope.latePropagation = function () { - $scope.someUpdt = false; - var prms = Scene.update({ screenId: $scope.screenid, t: $scope.propagationTime }, $scope.display); - prms.$promise.then(function () { - $scope.addToast('success', 'Scene successfully planned!'); - }, function (response) { - $scope.addToast('danger', 'An error occurs when planning scene:', response.data.errmsg); - }); - }; - $scope.savePreset = function () { - $scope.someUpdt = false; - var prms = Scene.update({ screenId: $scope.screenid, p: $scope.presetName }, $scope.display); - prms.$promise.then(function () { - $scope.addToast('success', 'Preset successfully saved!'); - }, function (response) { - $scope.addToast('danger', 'An error occurs when saving preset:', response.data.errmsg); - }); - }; - $scope.saveScenes = function () { - $scope.someUpdt = false; - var prms = Scene.update({ screenId: $scope.screenid }, $scope.display); - prms.$promise.then(function () { - $scope.addToast('success', 'Scene successfully published!'); - }, function (response) { - $scope.addToast('danger', 'An error occurs when saving scene:', response.data.errmsg); - }); - }; - $scope.addSide = function () { - $scope.someUpdt = true; - $scope.display.side.push({ params: {} }); - }; - $scope.delSide = function (s) { - $scope.someUpdt = true; - angular.forEach($scope.display.side, function (scene, k) { - if (scene == s) - $scope.display.side.splice(k, 1); - }); - }; - $scope.upSide = function (s) { - $scope.someUpdt = true; - angular.forEach($scope.display.side, function (scene, k) { - if (scene == s && k > 0) { - $scope.display.side.splice(k, 1); - $scope.display.side.splice(k - 1, 0, scene); - } - }); - }; - $scope.downSide = function (s) { - $scope.someUpdt = true; - var move = true; - angular.forEach($scope.display.side, function (scene, k) { - if (move && scene == s) { - $scope.display.side.splice(k, 1); - $scope.display.side.splice(k + 1, 0, scene); - move = false; - } - }); - }; - $scope.addScene = function () { - $scope.someUpdt = true; - $scope.display.scenes.push({ params: {} }); - }; - $scope.delScene = function (s) { - $scope.someUpdt = true; - angular.forEach($scope.display.scenes, function (scene, k) { - if (scene == s) - $scope.display.scenes.splice(k, 1); - }); - }; - $scope.upScene = function (s) { - $scope.someUpdt = true; - angular.forEach($scope.display.scenes, function (scene, k) { - if (scene == s && k > 0) { - $scope.display.scenes.splice(k, 1); - $scope.display.scenes.splice(k - 1, 0, scene); - } - }); - }; - $scope.downScene = function (s) { - $scope.someUpdt = true; - var move = true; - angular.forEach($scope.display.scenes, function (scene, k) { - if (move && scene == s) { - $scope.display.scenes.splice(k, 1); - $scope.display.scenes.splice(k + 1, 0, scene); - move = false; - } - }); - }; - }) - - .controller("FilesListController", function ($scope, File, $location, $http, $rootScope) { - $scope.files = File.query(); - $scope.errfnd = null; - $scope.errzip = null; - $scope.clearFilesWIP = false; - $scope.fields = ["id", "path", "name", "size"]; - - $scope.clearFiles = function (id) { - File.delete(function () { - $rootScope.staticFilesNeedUpdate++; - $scope.files = []; - }); - }; - $scope.clearFilesDir = function () { - $scope.addToast('warning', 'Êtes-vous sûr de vouloir continuer ?', "Ceci va supprimer tout le contenu du dossier FILES. Il s'agit des fichiers ci-dessous, il faudra refaire une synchronisation ensuite.", - function () { - $scope.clearFilesWIP = true; - $http({ - url: "api/files", - method: "DELETE" - }).then(function (response) { - $scope.clearFilesWIP = false; - }, function (response) { - $scope.clearFilesWIP = false; - $scope.addToast('danger', 'An error occurs when trying to clear files:', response.data.errmsg); - }); - }); - }; - $scope.gunzipFile = function (f) { - f.gunzipWIP = true; - $http({ - url: "api/files/" + f.id + "/gunzip", - method: "POST" - }).then(function (response) { - f.gunzipWIP = false; - f.err = true; - }, function (response) { - f.gunzipWIP = false; - $scope.inSync = false; - $scope.errzip += 1; - f.err = response.data.errmsg; - }) - }; - $scope.checksum = function (f) { - f.checkWIP = true; - $http({ - url: "api/files/" + f.id + "/check", - method: "POST" - }).then(function (response) { - f.checkWIP = false; - f.err = true; - }, function (response) { - f.checkWIP = false; - $scope.inSync = false; - $scope.errfnd += 1; - f.err = response.data.errmsg; - }) - }; - $scope.checksumAll = function () { - $scope.errfnd = null; - angular.forEach($scope.files, function (file) { - $scope.checksum(file); - }); - if ($scope.errfnd === null) $scope.errfnd = 0; - }; - $scope.gunzipFiles = function () { - $scope.errzip = null; - angular.forEach($scope.files, function (file) { - $scope.gunzipFile(file); - }); - if ($scope.errzip === null) $scope.errzip = 0; - }; - $scope.show = function (f) { - $location.url("/exercices/" + f.idExercice); - }; - }) - - .controller("EventsListController", function ($scope, Event, $location) { - $scope.events = Event.query(); - $scope.fields = ["id", "kind", "txt", "time"]; - - $scope.clearEvents = function (id) { - Event.delete(function () { - $scope.events = []; - }); - }; - $scope.show = function (id) { - $location.url("/events/" + id); - }; - }) - .controller("EventController", function ($scope, Event, $routeParams, $location) { - $scope.event = Event.get({ eventId: $routeParams.eventId }); - $scope.fields = ["kind", "txt", "time"]; - $scope.kinds = { - "secondary": "Par défaut", - "primary": "Mise en valeur", - "info": "Info", - "warning": "Warning", - "success": "Success", - "danger": "Danger", - "light": "Clair", - "dark": "Foncé", - }; - - $scope.saveEvent = function () { - if (this.event.id) { - this.event.$update(); - } else { - this.event.$save(function () { - $location.url("/events/" + $scope.event.id); - }); - } - } - $scope.deleteEvent = function () { - this.event.$remove(function () { $location.url("/events/"); }); - } - }) - - .controller("AssigneesListController", function ($scope, ClaimAssignee, $location) { - $scope.assignees = ClaimAssignee.query(); - - $scope.setMyAId = function (aid) { - setCookie("myassignee", aid, 5); - $location.url("/claims/"); - } - $scope.whoami = getCookie("myassignee"); - $scope.newAssignee = function () { - $scope.assignees.push(new ClaimAssignee()); - } - $scope.edit = function (a) { - a.edit = true; - } - $scope.updateAssignee = function (a) { - if (a.id) { - a.$update(function () { $location.url("/claims/"); }); - } else { - a.$save() - } - } - $scope.removeAssignee = function (a) { - a.$remove(function () { $location.url("/claims/"); }); - } - }) - .controller("ClaimsTinyListController", function ($scope, Claim, ClaimAssignee, $interval) { - $scope.whoami = getCookie("myassignee"); - - var priorities = { - "low": 1, - "medium": 2, - "high": 3, - "critical": 4, - }; - $scope.priorities = [ - "secondary", - "light", - "info", - "warning", - "danger", - ]; - - var refresh = function () { - Claim.query().$promise.then(function (claims) { - $scope.newClaims = 0; - $scope.newClaimsMaxLevel = 0; - $scope.myClaims = 0; - $scope.myClaimsMaxLevel = 0; - - claims.forEach(function (claim, cid) { - if ($scope.whoami && !claim.id_assignee && claim.state == 'new') { - $scope.newClaims++; - if (priorities[claim.priority] > $scope.newClaimsMaxLevel) - $scope.newClaimsMaxLevel = priorities[claim.priority]; - } - else if ($scope.whoami && claim.id_assignee == $scope.whoami && claim.state != 'closed' && claim.state != 'invalid') { - $scope.myClaims++; - if (claim.state == 'new' && priorities[claim.priority] > $scope.myClaimsMaxLevel) - $scope.myClaimsMaxLevel = priorities[claim.priority]; - } - }) - }); - }; - refresh(); - $interval(refresh, 10000); - }) - .controller("ClaimsListController", function ($scope, Claim, ClaimAssignee, Teams, $interval, $location) { - var refresh = function () { - $scope.claims = Claim.query(); - $scope.assignees = ClaimAssignee.query(); - } - refresh(); - var myinterval = $interval(refresh, 10000); - $scope.$on('$destroy', function () { $interval.cancel(myinterval); }); - - $scope.whoami = getCookie("myassignee"); - $scope.teams = Teams.get(); - $scope.fields = ["subject", "id_team", "state", "id_assignee", "last_update", "id"]; - - $scope.order = "priority"; - $scope.chOrder = function (no) { - $scope.order = no; - }; - - $scope.clearClaims = function (id) { - Claim.delete(function () { - $scope.claims = []; - }); - }; - $scope.show = function (id) { - $location.url("/claims/" + id); - }; - }) - .controller("ClaimLastUpdateController", function ($scope, $http) { - $scope.init = function (claim) { - $http.get("api/claims/" + claim.id + "/last_update").then(function (response) { - if (response.data) - $scope.last_update = response.data; - else - $scope.last_update = claim.creation; - claim.last_update = $scope.last_update; - }, function (response) { - $scope.last_update = claim.creation; - }) - } - }) - .controller("ClaimController", function ($scope, Claim, ClaimAssignee, Teams, Exercice, $routeParams, $location, $http, $rootScope) { - $scope.claim = Claim.get({ claimId: $routeParams.claimId }, function (v) { - v.id_team = "" + v.id_team; - if (!v.priority) - v.priority = "medium"; - }); - if ($routeParams.claimId == "new") - $scope.fields = ["id_team", "id_exercice", "subject", "priority", "id_assignee"]; - else - $scope.fields = ["subject", "priority", "id_exercice", "id_assignee", "id_team", "creation", "state"]; - $scope.assignees = ClaimAssignee.query(); - $scope.comm = { ndescription: "" }; - $scope.whoami = Math.floor(getCookie("myassignee")); - $scope.teams = Teams.get(); - $scope.exercices = Exercice.query(); - $scope.namedFields = { - "subject": "Objet", - "id_assignee": "Assigné à", - "state": "État", - "id_team": "Équipe", - "id_exercice": "Challenge", - "creation": "Création", - "priority": "Priorité", - "description": "Description", - }; - $scope.states = { - "new": "Nouveau", - "need-info": "Besoin d'infos", - "confirmed": "Confirmé", - "in-progress": "En cours", - "need-review": "Fait", - "closed": "Clos", - "invalid": "Invalide", - }; - $scope.priorities = { - "low": "Basse", - "medium": "Moyenne", - "high": "Haute", - "critical": "Critique", - }; - - $scope.changeState = function (state) { - this.claim.state = state; - if ((state == "in-progress" || state == "invalid") && this.claim.id_assignee) - this.claim.id_assignee = $scope.whoami; - if (this.claim.id) - this.saveClaim(state == "invalid" || state == "closed"); - } - $scope.assignToMe = function () { - this.claim.id_assignee = $scope.whoami; - if (this.claim.id) - this.saveClaim(false); - } - $scope.updateDescription = function (description) { - $http({ - url: "api/claims/" + $scope.claim.id + "/descriptions", - method: "PUT", - data: description - }).then(function (response) { - $scope.claim = Claim.get({ claimId: $routeParams.claimId }, function (v) { - v.id_team = "" + v.id_team; - if (!v.priority) - v.priority = "medium"; - }); - }); - } - $scope.saveDescription = function () { - $http({ - url: "api/claims/" + $scope.claim.id, - method: "POST", - data: { - "id_assignee": $scope.whoami, - "content": $scope.comm.ndescription - } - }).then(function (response) { - $location.url("/claims/" + $scope.claim.id + "/"); - }); - } - $scope.saveClaim = function (backToList) { - this.claim.whoami = $scope.whoami; - if (this.claim.id_team) { - this.claim.id_team = parseInt(this.claim.id_team, 10); - } else { - this.claim.id_team = null; - } - if (this.claim.id) { - this.claim.$update(function (v) { - v.id_team = "" + v.id_team; - if ($scope.comm.ndescription) - $scope.saveDescription(); - else if (backToList) - $location.url("/claims/"); - else - $scope.claim = Claim.get({ claimId: $routeParams.claimId }, function (v) { - v.id_team = "" + v.id_team; - if (!v.priority) - v.priority = "medium"; - }); - }); - } else { - this.claim.$save(function () { - if (!$scope.comm.ndescription) - $scope.comm.ndescription = "Création de la tâche"; - $scope.saveDescription(); - }, function (response) { - $scope.addToast('danger', 'An error occurs when trying to save claim:', response.data.errmsg); - }); - } - } - $scope.deleteClaim = function () { - this.claim.$remove(function () { $location.url("/claims/"); }); - } - }) - - .controller("ThemesListController", function ($scope, Theme, $location, $rootScope, $http) { - $scope.themes = Theme.query(); - $scope.fields = ["name", "authors", "headline", "path"]; - - $scope.validateSearch = function (keyEvent) { - if (keyEvent.which === 13) { - var myTheme = null; - $scope.themes.forEach(function (theme) { - if (String(theme.name.toLowerCase()).indexOf($scope.query.toLowerCase()) >= 0) { - if (myTheme === null) - myTheme = theme; - else - myTheme = false; - } - }); - if (myTheme) - $location.url("themes/" + myTheme.id); - } - }; - - $scope.show = function (id) { - $location.url("/themes/" + id); - }; - $scope.inSync = false; - $scope.sync = function () { - $scope.inSync = true; - $http({ - url: "api/sync/themes", - method: "POST" - }).then(function (response) { - $scope.inSync = false; - $scope.themes = Theme.query(); - $rootScope.staticFilesNeedUpdate++; - if (response.data) - $scope.addToast('danger', 'An error occurs when synchronizing theme list:', response.data); - else - $scope.addToast('success', 'Synchronisation de la liste des thèmes terminée avec succès.'); - }, function (response) { - $scope.inSync = false; - $scope.addToast('danger', 'An error occurs when synchronizing theme list:', response.data.errmsg); - }); - }; - }) - .controller("ThemeController", function ($scope, Theme, $routeParams, $location, $rootScope, $http) { - $scope.theme = Theme.get({ themeId: $routeParams.themeId }); - $scope.fields = ["name", "urlid", "locked", "authors", "headline", "intro", "image", "background_color", "partner_txt", "partner_href", "partner_img"]; - - $scope.saveTheme = function () { - if (this.theme.id) { - this.theme.$update(); - } else { - this.theme.$save(function () { - $location.url("/themes/" + $scope.theme.id); - }); - } - $rootScope.staticFilesNeedUpdate++; - } - $scope.deleteTheme = function () { - this.theme.$remove(function () { - $rootScope.staticFilesNeedUpdate++; - $location.url("/themes/"); - }, function (response) { - $scope.addToast('danger', 'An error occurs when trying to delete theme:', response.data.errmsg); - }); - } - $scope.checkExoSync = function () { - $scope.diff = null; - $http({ - url: "api/themes/" + $scope.theme.id + "/diff-sync", - method: "POST" - }).then(function (response) { - $scope.diff = response.data; - if (response.data === null) { - $scope.addToast('success', 'Changements par rapport au dépôt', "Tout est pareil !"); - } - }, function (response) { - $scope.diff = null; - $scope.addToast('danger', 'An error occurs when synchronizing exercice:', response.data.errmsg); - }); - }; - }) - - .controller("TagsListController", function ($scope, $http) { - $scope.tags = []; - $http({ - url: "api/tags", - method: "GET" - }).then(function (response) { - $scope.tags = response.data - }); - }) - - .controller("AllExercicesListController", function ($scope, Exercice, Theme, $routeParams, $location, $rootScope, $http, $filter) { - $http({ - url: "api/themes.json", - method: "GET" - }).then(function (response) { - $scope.themes = response.data - }); - - $scope.exercices = Exercice.query(); - $scope.exercice = {}; // Array used to save fields to updates in selected exercices - $scope.fields = ["title", "headline"]; - - $scope.validateSearch = function (keyEvent) { - if (keyEvent.which === 13) { - var myExercice = null; - $scope.exercices.forEach(function (exercice) { - if (String(exercice.title.toLowerCase()).indexOf($scope.query.toLowerCase()) >= 0) { - if (myExercice === null) - myExercice = exercice; - else - myExercice = false; - } - }); - if (myExercice) - $location.url("exercices/" + myExercice.id); - } - }; - - $scope.toggleSelectAll = function () { - angular.forEach($filter('filter')($scope.exercices, $scope.query), function (ex) { - ex.selected = !$scope.selectall - }) - } - - $scope.updateExercices = function () { - angular.forEach($scope.exercices, function (ex) { - if (ex.selected) { - Exercice.patch({ exerciceId: ex.id }, $scope.exercice); - } - }) - $scope.exercice = {}; - $rootScope.staticFilesNeedUpdate++; - $scope.addToast('success', 'Édition de masse terminée avec succès'); - } - - $scope.show = function (id) { - $location.url("/exercices/" + id); - }; - $scope.inSync = false; - $scope.syncFull = function () { - $scope.inSync = true; - $scope.done = -1; - $scope.total = 0; - var work = []; - var go = function () { - if (!work.length) { - $scope.addToast('info', "Synchronisation des exercices terminée."); - $scope.inSync = false; - return; - } - var u = work.pop(); - - $http({ - url: u, - method: "GET" - }).then(function (response) { - $rootScope.staticFilesNeedUpdate++; - $scope.done += 1; - go(); - }, function (response) { - $scope.done += 1; - go(); - }); - }; - - angular.forEach($scope.exercices, function (ex) { - if ($scope.syncFiles) - work.push("api/sync/exercices/" + ex.id + "/files"); - if ($scope.syncHints) - work.push("api/sync/exercices/" + ex.id + "/hints"); - if ($scope.syncFlags) - work.push("api/sync/exercices/" + ex.id + "/flags"); - }); - $scope.total = work.length; - go(); - - }; - $scope.syncFiles = true; - $scope.syncHints = true; - $scope.syncFlags = true; - }) - .controller("ExercicesListController", function ($scope, ThemedExercice, $location, $rootScope, $http) { - $scope.exercices = ThemedExercice.query({ themeId: $scope.theme.id }); - $scope.fields = ["title", "headline", "issue"]; - - $scope.show = function (id) { - $location.url("/themes/" + $scope.theme.id + "/exercices/" + id); - }; - - $scope.inSync = false; - $scope.syncExo = function () { - $scope.inSync = true; - $http({ - url: "api/sync/themes/" + $scope.theme.id + "/exercices", - method: "POST" - }).then(function (response) { - $scope.inSync = false; - $scope.exercices = ThemedExercice.query({ themeId: $scope.theme.id }); - $rootScope.staticFilesNeedUpdate++; - if (response.data) - $scope.addToast('warning', 'An error occurs when synchrinizing exercices:', response.data); - else - $scope.addToast('success', 'Synchronisation de la liste des exercices terminée avec succès.'); - }, function (response) { - $scope.inSync = false; - $scope.addToast('danger', 'An error occurs when synchrinizing exercices:', response.data.errmsg); - }); - }; - }) - .controller("ExerciceController", function ($scope, $rootScope, Exercice, ThemedExercice, $routeParams, $location, $http) { - if ($routeParams.themeId && $routeParams.exerciceId == "new") { - $scope.exercice = new ThemedExercice(); + 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.exercice = Exercice.get({ exerciceId: $routeParams.exerciceId }); + $scope.time.end = false; + $scope.time.expired = false; } + $scope.time.start = time.st * 1000; + $scope.time.duration = time.du; + $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); + } + } - $scope.my_ex_num = {}; - - $http({ - url: "api/themes.json", - method: "GET" - }).then(function (response) { - $scope.themes = response.data; - if ($scope.exercice.id_theme) { - for (var k in $scope.themes[$scope.exercice.id_theme].exercices) { - var exercice = $scope.themes[$scope.exercice.id_theme].exercices[k]; - $scope.my_ex_num[exercice.id] = k; - } - } else { - for (var k in $scope.themes["0"].exercices) { - var exercice = $scope.themes["0"].exercices[k]; - $scope.my_ex_num[exercice.id] = k; - } - } - }); - $scope.exercices = Exercice.query(); - $scope.fields = ["title", "urlid", "authors", "disabled", "statement", "headline", "overview", "finished", "depend", "gain", "coefficient", "videoURI", "image", "background_color", "resolution", "issue", "issuekind", "wip"]; - - $scope.inSync = false; - $scope.syncExo = function () { - $scope.inSync = true; - $http({ - url: $scope.exercice.id_theme ? ("api/sync/themes/" + $scope.exercice.id_theme + "/exercices/" + $routeParams.exerciceId) : ("api/sync/exercices/" + $routeParams.exerciceId), - method: "POST" - }).then(function (response) { - $scope.inSync = false; - $scope.exercice = Exercice.get({ exerciceId: $routeParams.exerciceId }); - $rootScope.staticFilesNeedUpdate++; - if (response.data) - $scope.addToast('danger', 'An error occurs when synchronizing exercice:', response.data); - else - $scope.addToast('success', "Synchronisation de l'exercice terminée avec succès."); - }, function (response) { - $scope.inSync = false; - $scope.addToast('danger', 'An error occurs when synchronizing exercice:', response.data.errmsg); - }); - }; - $scope.checkExoSync = function () { - $scope.diff = null; - $http({ - url: ($scope.exercice.id_theme ? ("api/themes/" + $scope.exercice.id_theme + "/exercices/" + $routeParams.exerciceId) : ("api/exercices/" + $routeParams.exerciceId)) + "/diff-sync", - method: "POST" - }).then(function (response) { - $scope.diff = response.data; - if (response.data === null) { - $scope.addToast('success', 'Changements par rapport au dépôt', "Tout est pareil !"); - } - }, function (response) { - $scope.diff = null; - $scope.addToast('danger', 'An error occurs when synchronizing exercice:', response.data.errmsg); - }); - }; - - $scope.deleteExercice = function () { - var tid = $scope.exercice.id_theme; - this.exercice.$remove(function () { - $rootScope.staticFilesNeedUpdate++; - $location.url("/themes/" + tid); - }, function (response) { - $scope.addToast('danger', 'An error occurs when trying to delete exercice:', response.data.errmsg); - }); - } - $scope.saveExercice = function () { - if (this.exercice.id) { - this.exercice.$update(); - $rootScope.staticFilesNeedUpdate++; - } else if ($routeParams.themeId) { - this.exercice.$save({ themeId: $routeParams.themeId }, function () { - $rootScope.staticFilesNeedUpdate++; - $location.url("/themes/" + $scope.exercice.idTheme + "/exercices/" + $scope.exercice.id); - }, function (response) { - $scope.addToast('danger', 'An error occurs when trying to save exercice:', response.data.errmsg); - }); - } - } - $scope.selectedTeam = ""; - $scope.validateForTeam = function () { - var flagid = $("#validationModal").data("flagid"); - if (!flagid) return; - var target = { - team_id: parseInt($("#tteam").val().replace(/number:/, '')), - kind: $("#validationModal").data("kind"), - secondary: flagid, - }; - $http({ - url: "api/exercices/" + $scope.exercice.id + "/history.json", - method: "PUT", - data: target - }).then(function (response) { - $rootScope.staticFilesNeedUpdate++; - $("#validationModal").modal('hide'); - $scope.addToast('success', 'Flag validé avec succès'); - }, function (response) { - $scope.addToast('danger', 'An error occurs when trying to validate flag for team:', response.data.errmsg); - }); - } - $scope.historyAppend = function () { - var secondary = null; - if ($("#historyEvent").val() == "hint") - secondary = parseInt($("#historySecondaryHint").val().replace(/number:/, '')); - else if ($("#historyEvent").val() == "wchoices" || $("#historyEvent").val() == "flag_found") - secondary = parseInt($("#historySecondaryFlag").val().replace(/number:/, '')); - else if ($("#historyEvent").val() == "mcq_found") - secondary = parseInt($("#historySecondaryQuiz").val().replace(/number:/, '')); - - var target = { - team_id: parseInt($("#tteam").val().replace(/number:/, '')), - kind: $("#historyEvent").val(), - secondary: secondary, - }; - $http({ - url: "api/exercices/" + $scope.exercice.id + "/history.json", - method: "PUT", - data: target - }).then(function (response) { - $rootScope.staticFilesNeedUpdate++; - $("#appendHistoryModal").modal('hide'); - $scope.addToast('success', 'Événement ajouté avec succès'); - $scope.refreshHistory(); - }, function (response) { - $scope.addToast('danger', 'An error occurs when trying to add event in history:', response.data.errmsg); - }); - } - }) - - .controller("SearchTryController", function ($scope, ExerciceTries) { - $scope.tr = ExerciceTries.get({ exerciceId: $scope.exercice.id, tryId: $scope.row.secondary }); - }) - - .controller("SubmissionsStatsController", function ($scope, $http, $interval) { - var refresh = function () { - $http({ - url: "api/submissions-stats.json", - }).then(function (response) { - $scope.submissionsstats = response.data; - }); - } - var myinterval = $interval(refresh, 15000); - refresh(); - $scope.$on('$destroy', function () { $interval.cancel(myinterval); }); - }) - - .controller("ValidationsStatsController", function ($scope, $http, $interval) { - var refresh = function () { - $http({ - url: "api/validations-stats.json", - }).then(function (response) { - $scope.validationsstats = response.data; - }); - } - var myinterval = $interval(refresh, 15000); - refresh(); - $scope.$on('$destroy', function () { $interval.cancel(myinterval); }); - }) - - .controller("ExercicesStatsController", function ($scope, Themes, ExercicesStats) { - $scope.themes = Themes.get(); - $scope.exercices = {}; - ExercicesStats.query().$promise.then(function (exs) { - exs.forEach(function (ex) { - $scope.exercices[ex.id_exercice] = ex; - }) - }); - $scope.lenExoArray = []; - $scope.themes.$promise.then(function (themes) { - if (themes['0'] && themes['0'].exercices.length) { - var j = 0; - for (var i = themes['0'].exercices.length / 10; i >= 0; i--) { - $scope.lenExoArray.push(j++); - } - } - }); - }) - - .controller("ExerciceStatsController", function ($scope, ExerciceStats, $routeParams) { - $scope.stats = ExerciceStats.get({ exerciceId: $routeParams.exerciceId }); - }) - - .controller("ExerciceClaimsController", function ($scope, ExerciceClaims, Team, ClaimAssignee, $routeParams, $location) { - $scope.claims = ExerciceClaims.query({ exerciceId: $routeParams.exerciceId }); - $scope.assignees = ClaimAssignee.query(); - - $scope.claims.$promise.then(function (claims) { - claims.forEach(function (claim, cid) { - $scope.claims[cid].team = Team.get({ teamId: claim.id_team }); - }) - }); - - $scope.showClosed = false; - $scope.show = function (id) { - $location.url("/claims/" + id); - }; - }) - - .controller("ForgeLinksController", function ($scope, $http) { - $http({ - url: "api/exercices_forge_bindings.json", - }).then(function (response) { - $scope.forge_links = response.data; - }, function (response) { - $scope.addToast('danger', 'An error occurs when generating exercice forge links: ', response.data.errmsg); - }); - }) - - .controller("ExerciceTagsController", function ($scope, ExerciceTags, $routeParams, $rootScope) { - $scope.tags = ExerciceTags.query({ exerciceId: $routeParams.exerciceId }); - - $scope.addTag = function () { - $scope.tags.push(""); - } - $scope.deleteTag = function () { - $scope.tags.splice($scope.tags.indexOf(this.tag), 1); - return $scope.saveTags(); - } - $scope.saveTags = function () { - ExerciceTags.update({ exerciceId: $routeParams.exerciceId }, this.tags); - $rootScope.staticFilesNeedUpdate++; - } - }) - - .controller("ExerciceHistoryController", function ($scope, ExerciceHistory, $routeParams, $http, $rootScope) { - $scope.history = []; - $scope.refreshHistory = function () { - $scope.history = ExerciceHistory.query({ exerciceId: $routeParams.exerciceId }); - } - $scope.$parent.refreshHistory = $scope.refreshHistory; - $scope.refreshHistory(); - $scope.updHistory = function () { - var target = { - team_id: $("#updHistory").data("idteam"), - kind: $("#updHistory").data("kind"), - time: $("#updHistory").data("time"), - secondary: $("#updHistory").data("secondary") != "" ? $("#updHistory").data("secondary") : null, - coeff: parseFloat($('#historycoeff').val()), - }; - if (target) { - $http({ - url: "api/exercices/" + $routeParams.exerciceId + "/history.json", - method: "PATCH", - data: target - }).then(function (response) { - $rootScope.staticFilesNeedUpdate++; - $scope.history = ExerciceHistory.query({ exerciceId: $routeParams.exerciceId }); - $('#updHistory').modal('hide'); - }, function (response) { - $scope.addToast('danger', 'An error occurs when updating history item: ', response.data.errmsg); - }); - } - } - $scope.delHistory = function (row) { - $http({ - url: "api/exercices/" + $routeParams.exerciceId + "/history.json", - method: "DELETE", - data: row - }).then(function (response) { - $rootScope.staticFilesNeedUpdate++; - $scope.history = ExerciceHistory.query({ exerciceId: $routeParams.exerciceId }); - }, function (response) { - $scope.addToast('danger', 'An error occurs when removing history item: ', response.data.errmsg); - }); - } - }) - - .controller("ExerciceFilesController", function ($scope, ExerciceFile, $routeParams, $rootScope, $http) { - $scope.files = ExerciceFile.query({ exerciceId: $routeParams.exerciceId }); - - $scope.deleteFile = function () { - this.file.$delete(function () { - $scope.files = ExerciceFile.query({ exerciceId: $routeParams.exerciceId }); - }); - $rootScope.staticFilesNeedUpdate++; - return false; - } - $scope.deleteFileDep = function () { - $http({ - url: "api//files/" + this.file.id + "/dependancies/" + this.dep.id, - method: "DELETE" - }).then(function (response) { - $scope.files = ExerciceFile.query({ exerciceId: $routeParams.exerciceId }); - }, function (response) { - $scope.addToast('danger', 'An error occurs when removing file dep:', response.data.errmsg); - }); - $rootScope.staticFilesNeedUpdate++; - return false; - } - $scope.saveFile = function () { - this.file.$update(); - $rootScope.staticFilesNeedUpdate++; - } - $scope.inSync = false; - $scope.syncFiles = function () { - $scope.inSync = true; - $http({ - url: "api/sync/exercices/" + $routeParams.exerciceId + "/files", - method: "POST" - }).then(function (response) { - $scope.inSync = false; - $rootScope.staticFilesNeedUpdate++; - $scope.files = ExerciceFile.query({ exerciceId: $routeParams.exerciceId }); - if (response.data) - $scope.addToast('danger', 'An error occurs when synchronizing flags list:', response.data); - else - $scope.addToast('success', "Synchronisation de la liste de fichiers terminée avec succès."); - }, function (response) { - $scope.inSync = false; - $scope.addToast('danger', 'An error occurs when synchronizing flags list:', response.data.errmsg); - }); - }; - }) - - .controller("ExerciceHintsController", function ($scope, ExerciceHint, $routeParams, $rootScope, $http) { - $scope.hints = ExerciceHint.query({ exerciceId: $routeParams.exerciceId }); - - $scope.addHint = function () { - $scope.hints.push(new ExerciceHint()); - } - $scope.deleteHint = function () { - this.hint.$delete(function () { - $scope.hints = ExerciceHint.query({ exerciceId: $routeParams.exerciceId }); - $rootScope.staticFilesNeedUpdate++; - }, function (response) { - $scope.addToast('danger', 'An error occurs when trying to delete hint:', response.data.errmsg); - }); - } - $scope.saveHint = function () { - if (this.hint.id) { - this.hint.$update(); - } else { - this.hint.$save({ exerciceId: $routeParams.exerciceId }); - } - $rootScope.staticFilesNeedUpdate++; - } - $scope.inSync = false; - $scope.syncHints = function () { - $scope.inSync = true; - $http({ - url: "api/sync/exercices/" + $routeParams.exerciceId + "/hints", - method: "POST" - }).then(function (response) { - $scope.inSync = false; - $scope.hints = ExerciceHint.query({ exerciceId: $routeParams.exerciceId }); - $rootScope.staticFilesNeedUpdate++; - if (response.data) - $scope.addToast('danger', 'An error occurs when synchronizing hints list:', response.data); - else - $scope.addToast('success', "Synchronisation de la liste d'indices terminée avec succès."); - }, function (response) { - $scope.inSync = false; - $scope.addToast('danger', 'An error occurs when synchronizing hints list:', response.data.errmsg); - }); - }; - }) - - .controller("ExerciceHintDepsController", function ($scope, $routeParams, ExerciceHintDeps) { - $scope.init = function (hint) { - $scope.deps = ExerciceHintDeps.query({ exerciceId: $routeParams.exerciceId, hintId: hint.id }); - } - }) - - .controller("ExerciceFlagsController", function ($scope, ExerciceFlag, $routeParams, $rootScope, $http) { - $scope.flags = ExerciceFlag.query({ exerciceId: $routeParams.exerciceId }); - - $scope.flags.$promise.then(function (flags) { - flags.forEach(function (flag, fid) { - flags[fid].values = ['']; - }); - }); - - $scope.changeValue = function (flag) { - flag.value = undefined; - flag.show_raw = true; - } - $scope.addFlag = function () { - $scope.flags.push(new ExerciceFlag()); - } - $scope.deleteFlag = function () { - this.flag.$delete(function () { - $scope.flags = ExerciceFlag.query({ exerciceId: $routeParams.exerciceId }); - $rootScope.staticFilesNeedUpdate++; - }, function (response) { - $scope.addToast('danger', 'An error occurs when trying to delete flag:', response.data.errmsg); - }); - } - $scope.saveFlag = function () { - if (this.flag.id) { - this.flag.$update().then(function() {}, function(error) { $scope.addToast('danger', 'Impossible de mettre à jour le flag :', error.data.errmsg); }); - } else { - this.flag.$save({ exerciceId: $routeParams.exerciceId }).then(function() {}, function(error) { $scope.addToast('danger', 'Impossible de créer le flag :', error.data.errmsg); }); - } - $rootScope.staticFilesNeedUpdate++; - } - $scope.testFlag = function (flag) { - if (flag.values) { - var val = flag.value; - treatFlagKey(flag); - flag.test_str = flag.value; - flag.value = val; - } - - if (flag.test_str) { - $http({ - url: "api/exercices/" + $routeParams.exerciceId + "/flags/" + flag.id + "/try", - data: { "flag": flag.test_str }, - method: "POST" - }).then(function (response) { - flag.test_str = ""; - $scope.addToast('success', "Flag Ok !"); - }, function (response) { - flag.test_str = ""; - $scope.addToast('danger', 'An error occurs: ', response.data.errmsg); - }); - } - } - $scope.inSync = false; - $scope.syncFlags = function () { - $scope.inSync = true; - $http({ - url: "api/sync/exercices/" + $routeParams.exerciceId + "/flags", - method: "POST" - }).then(function (response) { - $scope.inSync = false; - $scope.flags = ExerciceFlag.query({ exerciceId: $routeParams.exerciceId }); - $rootScope.staticFilesNeedUpdate++; - if (response.data) - $scope.addToast('danger', 'An error occurs when synchronizing flags list:', response.data); - else - $scope.addToast('success', "Synchronisation de la liste de drapeaux terminée avec succès."); - }, function (response) { - $scope.inSync = false; - $scope.addToast('danger', 'An error occurs when synchronizing flags list:', response.data.errmsg); - }); - }; - }) - - .controller("ExerciceFlagChoicesController", function ($scope, ExerciceFlagChoices, $routeParams) { - $scope.choices = ExerciceFlagChoices.query({ exerciceId: $routeParams.exerciceId, flagId: $scope.flag.id }) - - $scope.flag.wantchoices = function () { - $scope.flag.choices = {}; - $scope.choices.forEach(function (choice) { - $scope.flag.choices[choice.value] = choice.label - }) - } - $scope.choices.$promise.then(function (choices) { - if ($scope.flag.choices_cost == 0 && choices.length > 0) { - $scope.flag.wantchoices() - } - }) - - - $scope.addChoice = function () { - $scope.choices.push(new ExerciceFlagChoices()) - } - - $scope.saveChoice = function () { - if (this.choice.id) - this.choice.$update({ exerciceId: $routeParams.exerciceId, flagId: this.flag.id }) - else - this.choice.$save({ exerciceId: $routeParams.exerciceId, flagId: this.flag.id }) - } - - $scope.deleteChoice = function () { - if (this.choice.id) - this.choice.$delete({ exerciceId: $routeParams.exerciceId, flagId: this.flag.id }, function () { - $scope.choices = ExerciceFlagChoices.query({ exerciceId: $routeParams.exerciceId, flagId: $scope.flag.id }) - }) - } - }) - - .controller("ExerciceFlagDepsController", function ($scope, $routeParams, ExerciceFlagDeps) { - $scope.init = function (flag) { - $scope.deps = ExerciceFlagDeps.query({ exerciceId: $routeParams.exerciceId, flagId: flag.id }); - } - }) - - .controller("ExerciceFlagStatsController", function ($scope, $routeParams, ExerciceFlagStats, $http) { - $scope.init = function (flag) { - $scope.flag_id = flag.id; - $scope.stats = ExerciceFlagStats.get({ exerciceId: $routeParams.exerciceId, flagId: flag.id }); - } - $scope.deleteTries = function () { - $http.delete(`/api/exercices/${$routeParams.exerciceId}/flags/${$scope.flag_id}/tries`).then(function () { - $scope.stats = ExerciceFlagStats.get({ exerciceId: $routeParams.exerciceId, flagId: $scope.flag_id }); - }); - } - }) - - .controller("ExerciceMCQFlagsController", function ($scope, ExerciceMCQFlag, $routeParams, $rootScope) { - $scope.quiz = ExerciceMCQFlag.query({ exerciceId: $routeParams.exerciceId }); - - $scope.addQuiz = function () { - $scope.quiz.push(new ExerciceMCQFlag()); - } - $scope.deleteQuiz = function () { - this.q.$delete(function () { - $scope.quiz = ExerciceMCQFlag.query({ exerciceId: $routeParams.exerciceId }); - $rootScope.staticFilesNeedUpdate++; - }, function (response) { - $scope.addToast('danger', 'An error occurs when trying to delete flag:', response.data.errmsg); - }); - } - $scope.saveQuiz = function () { - if (this.q.id) { - this.q.$update(); - } else { - this.q.$save({ exerciceId: $routeParams.exerciceId }); - } - $rootScope.staticFilesNeedUpdate++; - } - - $scope.addChoice = function () { - this.quiz[this.qk].entries.push({ label: "", response: false }) - } - $scope.deleteChoice = function () { - this.quiz[this.qk].entries.splice(this.quiz[this.qk].entries.indexOf(this.choice), 1); - } - }) - - .controller("ExerciceMCQDepsController", function ($scope, $routeParams, ExerciceMCQDeps) { - $scope.init = function (flag) { - $scope.deps = ExerciceMCQDeps.query({ exerciceId: $routeParams.exerciceId, mcqId: flag.id }); - } - }) - - .controller("ExerciceMCQStatsController", function ($scope, $routeParams, ExerciceMCQStats, $http) { - $scope.init = function (mcq) { - $scope.mcq_id = mcq.id; - $scope.stats = ExerciceMCQStats.get({ exerciceId: $routeParams.exerciceId, mcqId: mcq.id }); - } - $scope.deleteTries = function () { - $http.delete(`/api/exercices/${$routeParams.exerciceId}/quiz/${$scope.mcq_id}/tries`).then(function () { - $scope.stats = ExerciceMCQStats.get({ exerciceId: $routeParams.exerciceId, mcqId: $scope.mcq_id }); - }); - } - }) - - .controller("TeamsListController", function ($scope, $rootScope, Team, $location, $http) { - $scope.teams = Team.query(); - $scope.fields = ["id", "name"]; - - $scope.order = []; - $scope.teams.$promise.then(function (teams) { - teams.forEach(function (team) { - $scope.order.push(team.id); - }); - }); - $scope.validateSearch = function (keyEvent) { - if (keyEvent.which === 13) { - var myTeam = null; - $scope.teams.forEach(function (team) { - if (String(team.name.toLowerCase()).indexOf($scope.query.toLowerCase()) >= 0) { - if (myTeam === null) - myTeam = team; - else - myTeam = false; - } - }); - if (myTeam) - $location.url("teams/" + myTeam.id); - } - }; - - $scope.desactiveTeams = function () { - $http.post("api/disableinactiveteams").then(function () { - $scope.teams = Team.query(); - }, function (response) { - $scope.addToast('danger', 'An error occurs when disabling inactive teams:', response.data.errmsg); - }); - } - $scope.refineTeamsColors = function () { - $http.post("api/refine_colors").then(function () { - $scope.teams = Team.query(); - }, function (response) { - $scope.addToast('danger', 'An error occurs when updating teams:', response.data.errmsg); - }); - } - $scope.show = function (id) { - $location.url("/teams/" + id); - }; - $scope.triggerTeamsImport = function() { - document.getElementById('crTeamsInput').click(); - }; - $scope.uploadFile = function() { - var formData = new FormData(); - formData.append('file', $scope.selectedFile); - - $http.post('api/cyberrange-teams.json', formData, { - transformRequest: angular.identity, - headers: {'Content-Type': undefined}, - }).then(function(response) { - $scope.teams = response.data; - $scope.addToast('success', 'Import des équipes', "L'import a été réalisé avec succès !"); - }, function(error) { - console.log(error); - $scope.addToast('danger', 'Import des équipes', error.data.errmsg); - }); - }; - }) - .controller("TeamMembersController", function ($scope, TeamMember) { - $scope.fields = ["firstname", "lastname", "nickname", "company"]; - if ($scope.team != null) { - $scope.members = TeamMember.query({ teamId: $scope.team.id }); - - $scope.newMember = function () { - $scope.members.push(new TeamMember()); - } - $scope.saveTeamMembers = function () { - if (this.team.id) { - TeamMember.save({ teamId: this.team.id }, $scope.members); - } - } - $scope.removeMember = function (member) { - angular.forEach($scope.members, function (m, k) { - if (member == m) - $scope.members.splice(k, 1); - }); - } - } - }) - .controller("TeamController", function ($scope, $rootScope, $location, Team, TeamMember, $routeParams, $http) { - if ($scope.team && $scope.team.id) - $routeParams.teamId = $scope.team.id; - $scope.team = Team.get({ teamId: $routeParams.teamId }); - $scope.fields = ["name", "color", "external_id"]; - - $scope.saveTeam = function () { - if (this.team.id) { - this.team.$update(); - $rootScope.staticFilesNeedUpdate++; - } else { - this.team.$save(function () { - $rootScope.staticFilesNeedUpdate++; - $location.url("/teams/" + $scope.team.id); - }); - } - } - $scope.resetPasswd = function (team) { - $http({ - url: "api/password", - method: "POST" - }).then(function (response) { - team.password = response.data.password; - }); - } - $scope.deleteTeam = function () { - backName = this.team.name; - this.team.$remove(function () { $scope.addToast('success', 'Team ' + backName + ' successfully removed.'); $location.url("/teams/"); $rootScope.staticFilesNeedUpdate++; }, - function (response) { $scope.addToast('danger', 'An error occurs during suppression of the team:', response.data.errmsg); }); - } - $scope.showStats = function () { - $location.url("/teams/" + $scope.team.id + "/stats"); - } - $scope.showScore = function () { - $location.url("/teams/" + $scope.team.id + "/score"); - } - }) - .controller("TeamCertificatesController", function ($scope, $rootScope, TeamCertificate, $routeParams, $http) { - $scope.certificates = TeamCertificate.query({ teamId: $routeParams.teamId }); - $scope.certificates.$promise.then(function (certificates) { - certificates.forEach(function (certificate, cid) { - certificate.serial = parseInt(certificate.id).toString(16); - }); - }); - - $scope.dissociateCertificate = function (certificate) { - $http({ - url: "api/certs/" + certificate.id, - method: "PUT", - data: { - id_team: null - } - }).then(function (response) { - $scope.certificates = TeamCertificate.query({ teamId: $routeParams.teamId }).$promise.then(function (certificates) { - certificates.forEach(function (certificate, cid) { - certificate.serial = parseInt(certificate.id).toString(16); - }); - }); - $scope.addToast('success', 'Certificate successfully dissociated!'); - }, function (response) { - $scope.addToast('danger', 'An error occurs when dissociating certiticate:', response.data.errmsg); - }); - } - }) - .controller("TeamAssociationsController", function ($scope, $rootScope, TeamAssociation, $routeParams, $http) { - $scope.associations = TeamAssociation.query({ teamId: $routeParams.teamId }); - $scope.form = { "newassoc": "" }; - - $scope.addAssociation = function () { - if ($scope.form.newassoc) { - TeamAssociation.save({ teamId: $scope.team.id, assoc: $scope.form.newassoc }).$promise.then( - function () { - $scope.form.newassoc = ""; - $scope.associations = TeamAssociation.query({ teamId: $routeParams.teamId }); - }, function (response) { - if (response.data) - $scope.addToast('danger', 'An error occurs when creating user association: ', response.data.errmsg); - }); - } - } - - $scope.dropAssociation = function (assoc) { - TeamAssociation.delete({ teamId: $routeParams.teamId, assoc: assoc }).$promise.then( - function () { - $scope.associations = TeamAssociation.query({ teamId: $routeParams.teamId }); - }, function (response) { - $scope.addToast('danger', 'An error occurs when removing user association: ', response.data.errmsg); - }); - } - }) - .controller("TeamHistoryController", function ($scope, TeamHistory, $routeParams, $http, $rootScope) { - $scope.history = TeamHistory.query({ teamId: $routeParams.teamId }); - $scope.updHistory = function () { - var target = { - team_id: parseInt($routeParams.teamId), - kind: $("#updHistory").data("kind"), - time: $("#updHistory").data("time"), - secondary: $("#updHistory").data("secondary") != "" ? $("#updHistory").data("secondary") : null, - coeff: parseFloat($('#historycoeff').val()), - }; - if (target) { - $http({ - url: "api/exercices/" + $("#updHistory").data("primary") + "/history.json", - method: "PATCH", - data: target - }).then(function (response) { - $rootScope.staticFilesNeedUpdate++; - $scope.history = TeamHistory.query({ teamId: $routeParams.teamId }); - $('#updHistory').modal('hide'); - }, function (response) { - $scope.addToast('danger', 'An error occurs when updating history item: ', response.data.errmsg); - }); - } - } - $scope.delHistory = function (row) { - $http({ - url: "api/teams/" + $routeParams.teamId + "/history.json", - method: "DELETE", - data: row - }).then(function (response) { - $rootScope.staticFilesNeedUpdate++; - $scope.history = TeamHistory.query({ teamId: $routeParams.teamId }); - }, function (response) { - $scope.addToast('danger', 'An error occurs when removing history item: ', response.data.errmsg); - }); - } - }) - .controller("TeamScoreController", function ($scope, TeamScore, TeamMy, Exercice, $routeParams) { - $scope.scores = TeamScore.query({ teamId: $routeParams.teamId }); - $scope.my = TeamMy.get({ teamId: $routeParams.teamId }); - $scope.exercices = Exercice.query(); - }) - .controller("TeamStatsController", function ($scope, TeamStats, $routeParams) { - $scope.teamstats = TeamStats.get({ teamId: $routeParams.teamId }); - $scope.teamstats.$promise.then(function (res) { - solvedByLevelPie("#pieLevels", res.levels); - var themes = []; - angular.forEach(res.themes, function (theme, tid) { - themes.push(theme); - }) - solvedByThemesPie("#pieThemes", themes); - }); - }) - .controller("TeamsJSONController", function ($scope, Teams) { - $scope.teams = Teams.get(); - $scope.teams.$promise.then(function (teams) { - $scope.rank = []; - Object.keys(teams).forEach(function (team) { - $scope.teams[team].id = team; - $scope.rank.push($scope.teams[team]); - }) - }); - }) - .controller("TeamExercicesController", function ($scope, Teams, Themes, TeamMy, Exercice, $routeParams) { - $scope.teams = Teams.get(); - $scope.themes = Themes.get(); - $scope.exercices = Exercice.query(); - $scope.my = TeamMy.get({ teamId: $routeParams.teamId }); - - $scope.teams.$promise.then(function (res) { - $scope.nb_teams = 0; - $scope.nb_reg_teams = Object.keys(res).length; - angular.forEach(res, function (team, tid) { - if (team.rank) - $scope.nb_teams += 1; - }, 0); - }); - - $scope.my.$promise.then(function (res) { - $scope.solved_exercices = 0; - angular.forEach(res.exercices, function (exercice, eid) { - if (exercice.solved_rank) { - $scope.solved_exercices += 1; - } - }, 0); - }); - - }) - - .controller("PresenceController", function ($scope, TeamPresence, $routeParams) { - $scope.presence = TeamPresence.query({ teamId: $routeParams.teamId }); - $scope.presence.$promise.then(function (res) { - presenceCal($scope, "#presenceCal", res); - }); + $http.get("/time.json").success(function(time) { + time.he = (new Date()).getTime(); + sessionStorage.userService = angular.toJson(time); + updTime(); }); + }); function solvedByLevelPie(location, data) { - var width = d3.select(location).node().getBoundingClientRect().width - parseInt(d3.select(location).style("padding-right")) - parseInt(d3.select(location).style("padding-left")), - height = d3.select(location).node().getBoundingClientRect().width - 10, - radius = Math.min(width, height) / 2, - innerRadius = 0.1 * radius; + var width = d3.select(location).node().getBoundingClientRect().width - 10, + height = d3.select(location).node().getBoundingClientRect().width - 10, + radius = Math.min(width, height) / 2, + innerRadius = 0.1 * radius; - var color = d3.scale.ordinal() - .range(["#9E0041", "#C32F4B", "#E1514B", "#F47245", "#FB9F59", "#FEC574", "#FAE38C", "#EAD195", "#C7E89E", "#9CD6A4", "#6CC4A4", "#4D9DB4", "#4776B4", "#5E4EA1"]); + var color = d3.scale.ordinal() + .range(["#9E0041", "#C32F4B", "#E1514B", "#F47245", "#FB9F59", "#FEC574", "#FAE38C", "#EAD195", "#C7E89E", "#9CD6A4", "#6CC4A4", "#4D9DB4", "#4776B4", "#5E4EA1"]); - var pie = d3.layout.pie() - .sort(null) - .value(function (d) { return d.width; }); + var pie = d3.layout.pie() + .sort(null) + .value(function(d) { return d.width; }); - var arc = d3.svg.arc() - .innerRadius(innerRadius) - .outerRadius(function (d) { - return (radius - innerRadius) * (d.data.score / 100.0) + innerRadius; - }); - - var outlineArc = d3.svg.arc() - .innerRadius(innerRadius) - .outerRadius(radius); - - var svg = d3.select(location).append("svg") - .attr("width", width) - .attr("height", height) - .append("g") - .attr("transform", "translate(" + width / 2 + "," + height / 2 + ")"); - - data.forEach(function (d) { - d.score = d.solved * 100 / d.total; - d.width = d.tries + 1; + var arc = d3.svg.arc() + .innerRadius(innerRadius) + .outerRadius(function (d) { + return (radius - innerRadius) * (d.data.score / 100.0) + innerRadius; }); - var path = svg.selectAll(".solidArc") - .data(pie(data)) - .enter().append("path") - .attr("fill", function (d) { return color(d.data.tip); }) - .attr("class", "solidArc") - .attr("stroke", "gray") - .attr("d", arc); + var outlineArc = d3.svg.arc() + .innerRadius(innerRadius) + .outerRadius(radius); - var outerPath = svg.selectAll(".outlineArc") - .data(pie(data)) - .enter().append("path") - .attr("fill", "none") - .attr("stroke", "gray") - .attr("class", "outlineArc") - .attr("d", outlineArc); + var svg = d3.select(location).append("svg") + .attr("width", width) + .attr("height", height) + .append("g") + .attr("transform", "translate(" + width / 2 + "," + height / 2 + ")"); - var labelArc = d3.svg.arc() - .outerRadius(0.8 * radius) - .innerRadius(0.8 * radius); + data.forEach(function(d) { + d.score = d.solved * 100 / d.total; + d.width = d.tries + 1; + }); - svg.selectAll(".labelArc") - .data(pie(data)) - .enter().append("text") - .attr("transform", function (d) { return "translate(" + labelArc.centroid(d) + ")"; }) - .attr("dy", ".35em") - .attr("text-anchor", "middle") - .text(function (d) { return d.data.tip + ": " + d.data.solved + "/" + d.data.total; }); + var path = svg.selectAll(".solidArc") + .data(pie(data)) + .enter().append("path") + .attr("fill", function(d) { return color(d.data.tip); }) + .attr("class", "solidArc") + .attr("stroke", "gray") + .attr("d", arc); - svg.selectAll(".label2Arc") - .data(pie(data)) - .enter().append("text") - .attr("transform", function (d) { return "translate(" + arc.centroid(d) + ")"; }) - .attr("dy", ".35em") - .attr("text-anchor", "middle") - .text(function (d) { return d.data.tries; }); + var outerPath = svg.selectAll(".outlineArc") + .data(pie(data)) + .enter().append("path") + .attr("fill", "none") + .attr("stroke", "gray") + .attr("class", "outlineArc") + .attr("d", outlineArc); + + var labelArc = d3.svg.arc() + .outerRadius(0.8 * radius) + .innerRadius(0.8 * radius); + + svg.selectAll(".labelArc") + .data(pie(data)) + .enter().append("text") + .attr("transform", function(d) { return "translate(" + labelArc.centroid(d) + ")"; }) + .attr("dy", ".35em") + .attr("text-anchor", "middle") + .text(function(d) { return d.data.tip + ": " + d.data.solved + "/" + d.data.total; }); + + svg.selectAll(".label2Arc") + .data(pie(data)) + .enter().append("text") + .attr("transform", function(d) { return "translate(" + arc.centroid(d) + ")"; }) + .attr("dy", ".35em") + .attr("text-anchor", "middle") + .text(function(d) { return d.data.tries; }); } function solvedByThemesPie(location, data) { - var width = d3.select(location).node().getBoundingClientRect().width - parseInt(d3.select(location).style("padding-right")) - parseInt(d3.select(location).style("padding-left")), - height = d3.select(location).node().getBoundingClientRect().width, - radius = Math.min(width, height) / 2, - innerRadius = 0.1 * radius; + var width = d3.select(location).node().getBoundingClientRect().width, + height = d3.select(location).node().getBoundingClientRect().width, + radius = Math.min(width, height) / 2, + innerRadius = 0.1 * radius; - var color = d3.scale.ordinal() - .range(["#9E0041", "#C32F4B", "#E1514B", "#F47245", "#FB9F59", "#FEC574", "#FAE38C", "#EAD195", "#C7E89E", "#9CD6A4", "#6CC4A4", "#4D9DB4", "#4776B4", "#5E4EA1"]); + var color = d3.scale.ordinal() + .range(["#9E0041", "#C32F4B", "#E1514B", "#F47245", "#FB9F59", "#FEC574", "#FAE38C", "#EAD195", "#C7E89E", "#9CD6A4", "#6CC4A4", "#4D9DB4", "#4776B4", "#5E4EA1"]); - var pie = d3.layout.pie() - .sort(null) - .value(function (d) { return d.width; }); + var pie = d3.layout.pie() + .sort(null) + .value(function(d) { return d.width; }); - var arc = d3.svg.arc() - .innerRadius(innerRadius) - .outerRadius(function (d) { - return (radius - innerRadius) * (d.data.score / 100.0) + innerRadius; - }); - - var outlineArc = d3.svg.arc() - .innerRadius(innerRadius) - .outerRadius(radius); - - var svg = d3.select(location).append("svg") - .attr("width", width) - .attr("height", height) - .append("g") - .attr("transform", "translate(" + width / 2 + "," + height / 2 + ")"); - - data.forEach(function (d) { - d.score = d.solved * 100 / d.total; - d.width = d.tries + 0.5; + var arc = d3.svg.arc() + .innerRadius(innerRadius) + .outerRadius(function (d) { + return (radius - innerRadius) * (d.data.score / 100.0) + innerRadius; }); - var path = svg.selectAll(".solidArc") - .data(pie(data)) - .enter().append("path") - .attr("fill", function (d) { return color(d.data.tip); }) - .attr("class", "solidArc") - .attr("stroke", "gray") - .attr("d", arc); + var outlineArc = d3.svg.arc() + .innerRadius(innerRadius) + .outerRadius(radius); - var outerPath = svg.selectAll(".outlineArc") - .data(pie(data)) - .enter().append("path") - .attr("fill", "none") - .attr("stroke", "gray") - .attr("class", "outlineArc") - .attr("d", outlineArc); + var svg = d3.select(location).append("svg") + .attr("width", width) + .attr("height", height) + .append("g") + .attr("transform", "translate(" + width / 2 + "," + height / 2 + ")"); - svg.selectAll(".label2Arc") - .data(pie(data)) - .enter().append("text") - .attr("transform", function (d) { return "translate(" + arc.centroid(d) + ")"; }) - .attr("dy", ".35em") - .attr("text-anchor", "middle") - .text(function (d) { return d.data.solved; }); + data.forEach(function(d) { + d.score = d.solved * 100 / d.total; + d.width = d.tries + 0.5; + }); - var labelArc = d3.svg.arc() - .outerRadius(0.8 * radius) - .innerRadius(0.8 * radius); + var path = svg.selectAll(".solidArc") + .data(pie(data)) + .enter().append("path") + .attr("fill", function(d) { return color(d.data.tip); }) + .attr("class", "solidArc") + .attr("stroke", "gray") + .attr("d", arc); - svg.selectAll(".labelArc") - .data(pie(data)) - .enter().append("text") - .attr("transform", function (d) { return "translate(" + labelArc.centroid(d) + ")"; }) - .attr("dy", ".35em") - .attr("text-anchor", "middle") - .text(function (d) { return d.data.tip + ": " + d.data.tries; }); + var outerPath = svg.selectAll(".outlineArc") + .data(pie(data)) + .enter().append("path") + .attr("fill", "none") + .attr("stroke", "gray") + .attr("class", "outlineArc") + .attr("d", outlineArc); + + svg.selectAll(".label2Arc") + .data(pie(data)) + .enter().append("text") + .attr("transform", function(d) { return "translate(" + arc.centroid(d) + ")"; }) + .attr("dy", ".35em") + .attr("text-anchor", "middle") + .text(function(d) { return d.data.solved; }); + + var labelArc = d3.svg.arc() + .outerRadius(0.8 * radius) + .innerRadius(0.8 * radius); + + svg.selectAll(".labelArc") + .data(pie(data)) + .enter().append("text") + .attr("transform", function(d) { return "translate(" + labelArc.centroid(d) + ")"; }) + .attr("dy", ".35em") + .attr("text-anchor", "middle") + .text(function(d) { return d.data.tip + ": " + d.data.tries; }); } -function presenceCal(scope, location, data) { - var width = d3.select(location).node().getBoundingClientRect().width, - height = 80, - cellSize = 17; // cell size +function presenceCal(location, data) { + var width = d3.select(location).node().getBoundingClientRect().width, + height = 80, + cellSize = 17; // cell size - var percent = d3.format(".1%"), - format = d3.time.format("%H:%M"); + var percent = d3.format(".1%"), + format = d3.time.format("%H:%M"); - var color = d3.scale.quantize() - .domain([0, 16]) - .range(d3.range(8).map(function (d) { return "q" + d + "-8"; })); + var color = d3.scale.quantize() + .domain([0, 16]) + .range(d3.range(8).map(function(d) { return "q" + d + "-8"; })); - var svg = d3.select(location).selectAll("svg") - .data(d3.range(scope.settings.start, scope.time.start + (scope.settings.start % 86400000 + scope.settings.end - scope.settings.start), 86400000).map(function (t) { return new Date(t); })) - .enter().append("svg") - .attr("width", width) - .attr("height", height) - .attr("class", "RdYlGn") - .append("g") - .attr("transform", "translate(" + ((width - cellSize * 24) / 2) + "," + (height - cellSize * 4 - 1) + ")"); + var svg = d3.select(location).selectAll("svg") + .data(d3.range(26, 29)) + .enter().append("svg") + .attr("width", width) + .attr("height", height) + .attr("class", "RdYlGn") + .append("g") + .attr("transform", "translate(" + ((width - cellSize * 24) / 2) + "," + (height - cellSize * 4 - 1) + ")"); - svg.append("text") - .attr("transform", "translate(-6," + cellSize * 2.6 + ")rotate(-90)") - .style("text-anchor", "middle") - .text(function (d) { return d.getDate() + "-" + (d.getMonth() + 1); }); + svg.append("text") + .attr("transform", "translate(-6," + cellSize * 2.6 + ")rotate(-90)") + .style("text-anchor", "middle") + .text(function(d) { return d + "-02"; }); - var rect = svg.selectAll(".quarter") - .data(function (d) { return d3.time.minutes(new Date(d.getFullYear(), d.getMonth(), d.getDate(), 0), new Date(d.getFullYear(), d.getMonth(), d.getDate(), 23), 15); }) - .enter().append("rect") - .attr("width", cellSize) - .attr("height", cellSize) - .attr("transform", function (d) { return "translate(" + (d.getHours() * cellSize) + "," + (d.getMinutes() / 15 * cellSize) + ")"; }) - .attr("class", function (d) { - if (d >= scope.settings.start && d < scope.settings.start + scope.settings.end - scope.settings.start) return color(data.reduce(function (prev, cur) { - cur = new Date(cur).getTime(); - dv = d.getTime(); - return prev + ((dv <= cur && cur < dv + 15 * 60000) ? 1 : 0); - }, 0)); - }); + var rect = svg.selectAll(".quarter") + .data(function(d) { return d3.time.minutes(new Date(2016, 1, d, 0), new Date(2016, 1, d, 24), 15); }) + .enter().append("rect") + .attr("width", cellSize) + .attr("height", cellSize) + .attr("class", function(d) { return color(data.reduce(function(prev, cur){ + cur = new Date(cur).getTime(); + dv = d.getTime(); + return prev + ((dv <= cur && cur < dv+15*60000)?1:0); + }, 0)); } diff --git a/admin/static/js/bootstrap.min.js b/admin/static/js/bootstrap.min.js index 97206dcd..e79c0651 100644 --- a/admin/static/js/bootstrap.min.js +++ b/admin/static/js/bootstrap.min.js @@ -1,7 +1,7 @@ /*! - * Bootstrap v4.6.2 (https://getbootstrap.com/) - * Copyright 2011-2022 The Bootstrap Authors (https://github.com/twbs/bootstrap/graphs/contributors) - * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) - */ -!function(t,e){"object"==typeof exports&&"undefined"!=typeof module?e(exports,require("jquery"),require("popper.js")):"function"==typeof define&&define.amd?define(["exports","jquery","popper.js"],e):e((t="undefined"!=typeof globalThis?globalThis:t||self).bootstrap={},t.jQuery,t.Popper)}(this,(function(t,e,n){"use strict";function i(t){return t&&"object"==typeof t&&"default"in t?t:{default:t}}var o=i(e),a=i(n);function s(t,e){for(var n=0;n=4)throw new Error("Bootstrap's JavaScript requires at least jQuery v1.9.1 but less than v4.0.0")}};d.jQueryDetection(),o.default.fn.emulateTransitionEnd=function(t){var e=this,n=!1;return o.default(this).one(d.TRANSITION_END,(function(){n=!0})),setTimeout((function(){n||d.triggerTransitionEnd(e)}),t),this},o.default.event.special[d.TRANSITION_END]={bindType:f,delegateType:f,handle:function(t){if(o.default(t.target).is(this))return t.handleObj.handler.apply(this,arguments)}};var c="bs.alert",h=o.default.fn.alert,g=function(){function t(t){this._element=t}var e=t.prototype;return e.close=function(t){var e=this._element;t&&(e=this._getRootElement(t)),this._triggerCloseEvent(e).isDefaultPrevented()||this._removeElement(e)},e.dispose=function(){o.default.removeData(this._element,c),this._element=null},e._getRootElement=function(t){var e=d.getSelectorFromElement(t),n=!1;return e&&(n=document.querySelector(e)),n||(n=o.default(t).closest(".alert")[0]),n},e._triggerCloseEvent=function(t){var e=o.default.Event("close.bs.alert");return o.default(t).trigger(e),e},e._removeElement=function(t){var e=this;if(o.default(t).removeClass("show"),o.default(t).hasClass("fade")){var n=d.getTransitionDurationFromElement(t);o.default(t).one(d.TRANSITION_END,(function(n){return e._destroyElement(t,n)})).emulateTransitionEnd(n)}else this._destroyElement(t)},e._destroyElement=function(t){o.default(t).detach().trigger("closed.bs.alert").remove()},t._jQueryInterface=function(e){return this.each((function(){var n=o.default(this),i=n.data(c);i||(i=new t(this),n.data(c,i)),"close"===e&&i[e](this)}))},t._handleDismiss=function(t){return function(e){e&&e.preventDefault(),t.close(this)}},l(t,null,[{key:"VERSION",get:function(){return"4.6.2"}}]),t}();o.default(document).on("click.bs.alert.data-api",'[data-dismiss="alert"]',g._handleDismiss(new g)),o.default.fn.alert=g._jQueryInterface,o.default.fn.alert.Constructor=g,o.default.fn.alert.noConflict=function(){return o.default.fn.alert=h,g._jQueryInterface};var m="bs.button",p=o.default.fn.button,_="active",v='[data-toggle^="button"]',y='input:not([type="hidden"])',b=".btn",E=function(){function t(t){this._element=t,this.shouldAvoidTriggerChange=!1}var e=t.prototype;return e.toggle=function(){var t=!0,e=!0,n=o.default(this._element).closest('[data-toggle="buttons"]')[0];if(n){var i=this._element.querySelector(y);if(i){if("radio"===i.type)if(i.checked&&this._element.classList.contains(_))t=!1;else{var a=n.querySelector(".active");a&&o.default(a).removeClass(_)}t&&("checkbox"!==i.type&&"radio"!==i.type||(i.checked=!this._element.classList.contains(_)),this.shouldAvoidTriggerChange||o.default(i).trigger("change")),i.focus(),e=!1}}this._element.hasAttribute("disabled")||this._element.classList.contains("disabled")||(e&&this._element.setAttribute("aria-pressed",!this._element.classList.contains(_)),t&&o.default(this._element).toggleClass(_))},e.dispose=function(){o.default.removeData(this._element,m),this._element=null},t._jQueryInterface=function(e,n){return this.each((function(){var i=o.default(this),a=i.data(m);a||(a=new t(this),i.data(m,a)),a.shouldAvoidTriggerChange=n,"toggle"===e&&a[e]()}))},l(t,null,[{key:"VERSION",get:function(){return"4.6.2"}}]),t}();o.default(document).on("click.bs.button.data-api",v,(function(t){var e=t.target,n=e;if(o.default(e).hasClass("btn")||(e=o.default(e).closest(b)[0]),!e||e.hasAttribute("disabled")||e.classList.contains("disabled"))t.preventDefault();else{var i=e.querySelector(y);if(i&&(i.hasAttribute("disabled")||i.classList.contains("disabled")))return void t.preventDefault();"INPUT"!==n.tagName&&"LABEL"===e.tagName||E._jQueryInterface.call(o.default(e),"toggle","INPUT"===n.tagName)}})).on("focus.bs.button.data-api blur.bs.button.data-api",v,(function(t){var e=o.default(t.target).closest(b)[0];o.default(e).toggleClass("focus",/^focus(in)?$/.test(t.type))})),o.default(window).on("load.bs.button.data-api",(function(){for(var t=[].slice.call(document.querySelectorAll('[data-toggle="buttons"] .btn')),e=0,n=t.length;e0,this._pointerEvent=Boolean(window.PointerEvent||window.MSPointerEvent),this._addEventListeners()}var e=t.prototype;return e.next=function(){this._isSliding||this._slide(N)},e.nextWhenVisible=function(){var t=o.default(this._element);!document.hidden&&t.is(":visible")&&"hidden"!==t.css("visibility")&&this.next()},e.prev=function(){this._isSliding||this._slide(D)},e.pause=function(t){t||(this._isPaused=!0),this._element.querySelector(".carousel-item-next, .carousel-item-prev")&&(d.triggerTransitionEnd(this._element),this.cycle(!0)),clearInterval(this._interval),this._interval=null},e.cycle=function(t){t||(this._isPaused=!1),this._interval&&(clearInterval(this._interval),this._interval=null),this._config.interval&&!this._isPaused&&(this._updateInterval(),this._interval=setInterval((document.visibilityState?this.nextWhenVisible:this.next).bind(this),this._config.interval))},e.to=function(t){var e=this;this._activeElement=this._element.querySelector(I);var n=this._getItemIndex(this._activeElement);if(!(t>this._items.length-1||t<0))if(this._isSliding)o.default(this._element).one(A,(function(){return e.to(t)}));else{if(n===t)return this.pause(),void this.cycle();var i=t>n?N:D;this._slide(i,this._items[t])}},e.dispose=function(){o.default(this._element).off(".bs.carousel"),o.default.removeData(this._element,w),this._items=null,this._config=null,this._element=null,this._interval=null,this._isPaused=null,this._isSliding=null,this._activeElement=null,this._indicatorsElement=null},e._getConfig=function(t){return t=r({},k,t),d.typeCheckConfig(T,t,O),t},e._handleSwipe=function(){var t=Math.abs(this.touchDeltaX);if(!(t<=40)){var e=t/this.touchDeltaX;this.touchDeltaX=0,e>0&&this.prev(),e<0&&this.next()}},e._addEventListeners=function(){var t=this;this._config.keyboard&&o.default(this._element).on("keydown.bs.carousel",(function(e){return t._keydown(e)})),"hover"===this._config.pause&&o.default(this._element).on("mouseenter.bs.carousel",(function(e){return t.pause(e)})).on("mouseleave.bs.carousel",(function(e){return t.cycle(e)})),this._config.touch&&this._addTouchEventListeners()},e._addTouchEventListeners=function(){var t=this;if(this._touchSupported){var e=function(e){t._pointerEvent&&j[e.originalEvent.pointerType.toUpperCase()]?t.touchStartX=e.originalEvent.clientX:t._pointerEvent||(t.touchStartX=e.originalEvent.touches[0].clientX)},n=function(e){t._pointerEvent&&j[e.originalEvent.pointerType.toUpperCase()]&&(t.touchDeltaX=e.originalEvent.clientX-t.touchStartX),t._handleSwipe(),"hover"===t._config.pause&&(t.pause(),t.touchTimeout&&clearTimeout(t.touchTimeout),t.touchTimeout=setTimeout((function(e){return t.cycle(e)}),500+t._config.interval))};o.default(this._element.querySelectorAll(".carousel-item img")).on("dragstart.bs.carousel",(function(t){return t.preventDefault()})),this._pointerEvent?(o.default(this._element).on("pointerdown.bs.carousel",(function(t){return e(t)})),o.default(this._element).on("pointerup.bs.carousel",(function(t){return n(t)})),this._element.classList.add("pointer-event")):(o.default(this._element).on("touchstart.bs.carousel",(function(t){return e(t)})),o.default(this._element).on("touchmove.bs.carousel",(function(e){return function(e){t.touchDeltaX=e.originalEvent.touches&&e.originalEvent.touches.length>1?0:e.originalEvent.touches[0].clientX-t.touchStartX}(e)})),o.default(this._element).on("touchend.bs.carousel",(function(t){return n(t)})))}},e._keydown=function(t){if(!/input|textarea/i.test(t.target.tagName))switch(t.which){case 37:t.preventDefault(),this.prev();break;case 39:t.preventDefault(),this.next()}},e._getItemIndex=function(t){return this._items=t&&t.parentNode?[].slice.call(t.parentNode.querySelectorAll(".carousel-item")):[],this._items.indexOf(t)},e._getItemByDirection=function(t,e){var n=t===N,i=t===D,o=this._getItemIndex(e),a=this._items.length-1;if((i&&0===o||n&&o===a)&&!this._config.wrap)return e;var s=(o+(t===D?-1:1))%this._items.length;return-1===s?this._items[this._items.length-1]:this._items[s]},e._triggerSlideEvent=function(t,e){var n=this._getItemIndex(t),i=this._getItemIndex(this._element.querySelector(I)),a=o.default.Event("slide.bs.carousel",{relatedTarget:t,direction:e,from:i,to:n});return o.default(this._element).trigger(a),a},e._setActiveIndicatorElement=function(t){if(this._indicatorsElement){var e=[].slice.call(this._indicatorsElement.querySelectorAll(".active"));o.default(e).removeClass(S);var n=this._indicatorsElement.children[this._getItemIndex(t)];n&&o.default(n).addClass(S)}},e._updateInterval=function(){var t=this._activeElement||this._element.querySelector(I);if(t){var e=parseInt(t.getAttribute("data-interval"),10);e?(this._config.defaultInterval=this._config.defaultInterval||this._config.interval,this._config.interval=e):this._config.interval=this._config.defaultInterval||this._config.interval}},e._slide=function(t,e){var n,i,a,s=this,l=this._element.querySelector(I),r=this._getItemIndex(l),u=e||l&&this._getItemByDirection(t,l),f=this._getItemIndex(u),c=Boolean(this._interval);if(t===N?(n="carousel-item-left",i="carousel-item-next",a="left"):(n="carousel-item-right",i="carousel-item-prev",a="right"),u&&o.default(u).hasClass(S))this._isSliding=!1;else if(!this._triggerSlideEvent(u,a).isDefaultPrevented()&&l&&u){this._isSliding=!0,c&&this.pause(),this._setActiveIndicatorElement(u),this._activeElement=u;var h=o.default.Event(A,{relatedTarget:u,direction:a,from:r,to:f});if(o.default(this._element).hasClass("slide")){o.default(u).addClass(i),d.reflow(u),o.default(l).addClass(n),o.default(u).addClass(n);var g=d.getTransitionDurationFromElement(l);o.default(l).one(d.TRANSITION_END,(function(){o.default(u).removeClass(n+" "+i).addClass(S),o.default(l).removeClass("active "+i+" "+n),s._isSliding=!1,setTimeout((function(){return o.default(s._element).trigger(h)}),0)})).emulateTransitionEnd(g)}else o.default(l).removeClass(S),o.default(u).addClass(S),this._isSliding=!1,o.default(this._element).trigger(h);c&&this.cycle()}},t._jQueryInterface=function(e){return this.each((function(){var n=o.default(this).data(w),i=r({},k,o.default(this).data());"object"==typeof e&&(i=r({},i,e));var a="string"==typeof e?e:i.slide;if(n||(n=new t(this,i),o.default(this).data(w,n)),"number"==typeof e)n.to(e);else if("string"==typeof a){if("undefined"==typeof n[a])throw new TypeError('No method named "'+a+'"');n[a]()}else i.interval&&i.ride&&(n.pause(),n.cycle())}))},t._dataApiClickHandler=function(e){var n=d.getSelectorFromElement(this);if(n){var i=o.default(n)[0];if(i&&o.default(i).hasClass("carousel")){var a=r({},o.default(i).data(),o.default(this).data()),s=this.getAttribute("data-slide-to");s&&(a.interval=!1),t._jQueryInterface.call(o.default(i),a),s&&o.default(i).data(w).to(s),e.preventDefault()}}},l(t,null,[{key:"VERSION",get:function(){return"4.6.2"}},{key:"Default",get:function(){return k}}]),t}();o.default(document).on("click.bs.carousel.data-api","[data-slide], [data-slide-to]",P._dataApiClickHandler),o.default(window).on("load.bs.carousel.data-api",(function(){for(var t=[].slice.call(document.querySelectorAll('[data-ride="carousel"]')),e=0,n=t.length;e0&&(this._selector=s,this._triggerArray.push(a))}this._parent=this._config.parent?this._getParent():null,this._config.parent||this._addAriaAndCollapsedClass(this._element,this._triggerArray),this._config.toggle&&this.toggle()}var e=t.prototype;return e.toggle=function(){o.default(this._element).hasClass(q)?this.hide():this.show()},e.show=function(){var e,n,i=this;if(!(this._isTransitioning||o.default(this._element).hasClass(q)||(this._parent&&0===(e=[].slice.call(this._parent.querySelectorAll(".show, .collapsing")).filter((function(t){return"string"==typeof i._config.parent?t.getAttribute("data-parent")===i._config.parent:t.classList.contains(F)}))).length&&(e=null),e&&(n=o.default(e).not(this._selector).data(R))&&n._isTransitioning))){var a=o.default.Event("show.bs.collapse");if(o.default(this._element).trigger(a),!a.isDefaultPrevented()){e&&(t._jQueryInterface.call(o.default(e).not(this._selector),"hide"),n||o.default(e).data(R,null));var s=this._getDimension();o.default(this._element).removeClass(F).addClass(Q),this._element.style[s]=0,this._triggerArray.length&&o.default(this._triggerArray).removeClass(B).attr("aria-expanded",!0),this.setTransitioning(!0);var l="scroll"+(s[0].toUpperCase()+s.slice(1)),r=d.getTransitionDurationFromElement(this._element);o.default(this._element).one(d.TRANSITION_END,(function(){o.default(i._element).removeClass(Q).addClass("collapse show"),i._element.style[s]="",i.setTransitioning(!1),o.default(i._element).trigger("shown.bs.collapse")})).emulateTransitionEnd(r),this._element.style[s]=this._element[l]+"px"}}},e.hide=function(){var t=this;if(!this._isTransitioning&&o.default(this._element).hasClass(q)){var e=o.default.Event("hide.bs.collapse");if(o.default(this._element).trigger(e),!e.isDefaultPrevented()){var n=this._getDimension();this._element.style[n]=this._element.getBoundingClientRect()[n]+"px",d.reflow(this._element),o.default(this._element).addClass(Q).removeClass("collapse show");var i=this._triggerArray.length;if(i>0)for(var a=0;a0},e._getOffset=function(){var t=this,e={};return"function"==typeof this._config.offset?e.fn=function(e){return e.offsets=r({},e.offsets,t._config.offset(e.offsets,t._element)),e}:e.offset=this._config.offset,e},e._getPopperConfig=function(){var t={placement:this._getPlacement(),modifiers:{offset:this._getOffset(),flip:{enabled:this._config.flip},preventOverflow:{boundariesElement:this._config.boundary}}};return"static"===this._config.display&&(t.modifiers.applyStyle={enabled:!1}),r({},t,this._config.popperConfig)},t._jQueryInterface=function(e){return this.each((function(){var n=o.default(this).data(K);if(n||(n=new t(this,"object"==typeof e?e:null),o.default(this).data(K,n)),"string"==typeof e){if("undefined"==typeof n[e])throw new TypeError('No method named "'+e+'"');n[e]()}}))},t._clearMenus=function(e){if(!e||3!==e.which&&("keyup"!==e.type||9===e.which))for(var n=[].slice.call(document.querySelectorAll(it)),i=0,a=n.length;i0&&s--,40===e.which&&sdocument.documentElement.clientHeight;n||(this._element.style.overflowY="hidden"),this._element.classList.add(ht);var i=d.getTransitionDurationFromElement(this._dialog);o.default(this._element).off(d.TRANSITION_END),o.default(this._element).one(d.TRANSITION_END,(function(){t._element.classList.remove(ht),n||o.default(t._element).one(d.TRANSITION_END,(function(){t._element.style.overflowY=""})).emulateTransitionEnd(t._element,i)})).emulateTransitionEnd(i),this._element.focus()}},e._showElement=function(t){var e=this,n=o.default(this._element).hasClass(dt),i=this._dialog?this._dialog.querySelector(".modal-body"):null;this._element.parentNode&&this._element.parentNode.nodeType===Node.ELEMENT_NODE||document.body.appendChild(this._element),this._element.style.display="block",this._element.removeAttribute("aria-hidden"),this._element.setAttribute("aria-modal",!0),this._element.setAttribute("role","dialog"),o.default(this._dialog).hasClass("modal-dialog-scrollable")&&i?i.scrollTop=0:this._element.scrollTop=0,n&&d.reflow(this._element),o.default(this._element).addClass(ct),this._config.focus&&this._enforceFocus();var a=o.default.Event("shown.bs.modal",{relatedTarget:t}),s=function(){e._config.focus&&e._element.focus(),e._isTransitioning=!1,o.default(e._element).trigger(a)};if(n){var l=d.getTransitionDurationFromElement(this._dialog);o.default(this._dialog).one(d.TRANSITION_END,s).emulateTransitionEnd(l)}else s()},e._enforceFocus=function(){var t=this;o.default(document).off(pt).on(pt,(function(e){document!==e.target&&t._element!==e.target&&0===o.default(t._element).has(e.target).length&&t._element.focus()}))},e._setEscapeEvent=function(){var t=this;this._isShown?o.default(this._element).on(yt,(function(e){t._config.keyboard&&27===e.which?(e.preventDefault(),t.hide()):t._config.keyboard||27!==e.which||t._triggerBackdropTransition()})):this._isShown||o.default(this._element).off(yt)},e._setResizeEvent=function(){var t=this;this._isShown?o.default(window).on(_t,(function(e){return t.handleUpdate(e)})):o.default(window).off(_t)},e._hideModal=function(){var t=this;this._element.style.display="none",this._element.setAttribute("aria-hidden",!0),this._element.removeAttribute("aria-modal"),this._element.removeAttribute("role"),this._isTransitioning=!1,this._showBackdrop((function(){o.default(document.body).removeClass(ft),t._resetAdjustments(),t._resetScrollbar(),o.default(t._element).trigger(gt)}))},e._removeBackdrop=function(){this._backdrop&&(o.default(this._backdrop).remove(),this._backdrop=null)},e._showBackdrop=function(t){var e=this,n=o.default(this._element).hasClass(dt)?dt:"";if(this._isShown&&this._config.backdrop){if(this._backdrop=document.createElement("div"),this._backdrop.className="modal-backdrop",n&&this._backdrop.classList.add(n),o.default(this._backdrop).appendTo(document.body),o.default(this._element).on(vt,(function(t){e._ignoreBackdropClick?e._ignoreBackdropClick=!1:t.target===t.currentTarget&&("static"===e._config.backdrop?e._triggerBackdropTransition():e.hide())})),n&&d.reflow(this._backdrop),o.default(this._backdrop).addClass(ct),!t)return;if(!n)return void t();var i=d.getTransitionDurationFromElement(this._backdrop);o.default(this._backdrop).one(d.TRANSITION_END,t).emulateTransitionEnd(i)}else if(!this._isShown&&this._backdrop){o.default(this._backdrop).removeClass(ct);var a=function(){e._removeBackdrop(),t&&t()};if(o.default(this._element).hasClass(dt)){var s=d.getTransitionDurationFromElement(this._backdrop);o.default(this._backdrop).one(d.TRANSITION_END,a).emulateTransitionEnd(s)}else a()}else t&&t()},e._adjustDialog=function(){var t=this._element.scrollHeight>document.documentElement.clientHeight;!this._isBodyOverflowing&&t&&(this._element.style.paddingLeft=this._scrollbarWidth+"px"),this._isBodyOverflowing&&!t&&(this._element.style.paddingRight=this._scrollbarWidth+"px")},e._resetAdjustments=function(){this._element.style.paddingLeft="",this._element.style.paddingRight=""},e._checkScrollbar=function(){var t=document.body.getBoundingClientRect();this._isBodyOverflowing=Math.round(t.left+t.right)
    ',trigger:"hover focus",title:"",delay:0,html:!1,selector:!1,placement:"top",offset:0,container:!1,fallbackPlacement:"flip",boundary:"scrollParent",customClass:"",sanitize:!0,sanitizeFn:null,whiteList:{"*":["class","dir","id","lang","role",/^aria-[\w-]*$/i],a:["target","href","title","rel"],area:[],b:[],br:[],col:[],code:[],div:[],em:[],hr:[],h1:[],h2:[],h3:[],h4:[],h5:[],h6:[],i:[],img:["src","srcset","alt","title","width","height"],li:[],ol:[],p:[],pre:[],s:[],small:[],span:[],sub:[],sup:[],strong:[],u:[],ul:[]},popperConfig:null},Ut={animation:"boolean",template:"string",title:"(string|element|function)",trigger:"string",delay:"(number|object)",html:"boolean",selector:"(string|boolean)",placement:"(string|function)",offset:"(number|string|function)",container:"(string|element|boolean)",fallbackPlacement:"(string|array)",boundary:"(string|element)",customClass:"(string|function)",sanitize:"boolean",sanitizeFn:"(null|function)",whiteList:"object",popperConfig:"(null|object)"},Mt={HIDE:"hide.bs.tooltip",HIDDEN:"hidden.bs.tooltip",SHOW:"show.bs.tooltip",SHOWN:"shown.bs.tooltip",INSERTED:"inserted.bs.tooltip",CLICK:"click.bs.tooltip",FOCUSIN:"focusin.bs.tooltip",FOCUSOUT:"focusout.bs.tooltip",MOUSEENTER:"mouseenter.bs.tooltip",MOUSELEAVE:"mouseleave.bs.tooltip"},Wt=function(){function t(t,e){if("undefined"==typeof a.default)throw new TypeError("Bootstrap's tooltips require Popper (https://popper.js.org)");this._isEnabled=!0,this._timeout=0,this._hoverState="",this._activeTrigger={},this._popper=null,this.element=t,this.config=this._getConfig(e),this.tip=null,this._setListeners()}var e=t.prototype;return e.enable=function(){this._isEnabled=!0},e.disable=function(){this._isEnabled=!1},e.toggleEnabled=function(){this._isEnabled=!this._isEnabled},e.toggle=function(t){if(this._isEnabled)if(t){var e=this.constructor.DATA_KEY,n=o.default(t.currentTarget).data(e);n||(n=new this.constructor(t.currentTarget,this._getDelegateConfig()),o.default(t.currentTarget).data(e,n)),n._activeTrigger.click=!n._activeTrigger.click,n._isWithActiveTrigger()?n._enter(null,n):n._leave(null,n)}else{if(o.default(this.getTipElement()).hasClass(Rt))return void this._leave(null,this);this._enter(null,this)}},e.dispose=function(){clearTimeout(this._timeout),o.default.removeData(this.element,this.constructor.DATA_KEY),o.default(this.element).off(this.constructor.EVENT_KEY),o.default(this.element).closest(".modal").off("hide.bs.modal",this._hideModalHandler),this.tip&&o.default(this.tip).remove(),this._isEnabled=null,this._timeout=null,this._hoverState=null,this._activeTrigger=null,this._popper&&this._popper.destroy(),this._popper=null,this.element=null,this.config=null,this.tip=null},e.show=function(){var t=this;if("none"===o.default(this.element).css("display"))throw new Error("Please use show on visible elements");var e=o.default.Event(this.constructor.Event.SHOW);if(this.isWithContent()&&this._isEnabled){o.default(this.element).trigger(e);var n=d.findShadowRoot(this.element),i=o.default.contains(null!==n?n:this.element.ownerDocument.documentElement,this.element);if(e.isDefaultPrevented()||!i)return;var s=this.getTipElement(),l=d.getUID(this.constructor.NAME);s.setAttribute("id",l),this.element.setAttribute("aria-describedby",l),this.setContent(),this.config.animation&&o.default(s).addClass(Lt);var r="function"==typeof this.config.placement?this.config.placement.call(this,s,this.element):this.config.placement,u=this._getAttachment(r);this.addAttachmentClass(u);var f=this._getContainer();o.default(s).data(this.constructor.DATA_KEY,this),o.default.contains(this.element.ownerDocument.documentElement,this.tip)||o.default(s).appendTo(f),o.default(this.element).trigger(this.constructor.Event.INSERTED),this._popper=new a.default(this.element,s,this._getPopperConfig(u)),o.default(s).addClass(Rt),o.default(s).addClass(this.config.customClass),"ontouchstart"in document.documentElement&&o.default(document.body).children().on("mouseover",null,o.default.noop);var c=function(){t.config.animation&&t._fixTransition();var e=t._hoverState;t._hoverState=null,o.default(t.element).trigger(t.constructor.Event.SHOWN),e===qt&&t._leave(null,t)};if(o.default(this.tip).hasClass(Lt)){var h=d.getTransitionDurationFromElement(this.tip);o.default(this.tip).one(d.TRANSITION_END,c).emulateTransitionEnd(h)}else c()}},e.hide=function(t){var e=this,n=this.getTipElement(),i=o.default.Event(this.constructor.Event.HIDE),a=function(){e._hoverState!==xt&&n.parentNode&&n.parentNode.removeChild(n),e._cleanTipClass(),e.element.removeAttribute("aria-describedby"),o.default(e.element).trigger(e.constructor.Event.HIDDEN),null!==e._popper&&e._popper.destroy(),t&&t()};if(o.default(this.element).trigger(i),!i.isDefaultPrevented()){if(o.default(n).removeClass(Rt),"ontouchstart"in document.documentElement&&o.default(document.body).children().off("mouseover",null,o.default.noop),this._activeTrigger.click=!1,this._activeTrigger.focus=!1,this._activeTrigger.hover=!1,o.default(this.tip).hasClass(Lt)){var s=d.getTransitionDurationFromElement(n);o.default(n).one(d.TRANSITION_END,a).emulateTransitionEnd(s)}else a();this._hoverState=""}},e.update=function(){null!==this._popper&&this._popper.scheduleUpdate()},e.isWithContent=function(){return Boolean(this.getTitle())},e.addAttachmentClass=function(t){o.default(this.getTipElement()).addClass("bs-tooltip-"+t)},e.getTipElement=function(){return this.tip=this.tip||o.default(this.config.template)[0],this.tip},e.setContent=function(){var t=this.getTipElement();this.setElementContent(o.default(t.querySelectorAll(".tooltip-inner")),this.getTitle()),o.default(t).removeClass("fade show")},e.setElementContent=function(t,e){"object"!=typeof e||!e.nodeType&&!e.jquery?this.config.html?(this.config.sanitize&&(e=At(e,this.config.whiteList,this.config.sanitizeFn)),t.html(e)):t.text(e):this.config.html?o.default(e).parent().is(t)||t.empty().append(e):t.text(o.default(e).text())},e.getTitle=function(){var t=this.element.getAttribute("data-original-title");return t||(t="function"==typeof this.config.title?this.config.title.call(this.element):this.config.title),t},e._getPopperConfig=function(t){var e=this;return r({},{placement:t,modifiers:{offset:this._getOffset(),flip:{behavior:this.config.fallbackPlacement},arrow:{element:".arrow"},preventOverflow:{boundariesElement:this.config.boundary}},onCreate:function(t){t.originalPlacement!==t.placement&&e._handlePopperPlacementChange(t)},onUpdate:function(t){return e._handlePopperPlacementChange(t)}},this.config.popperConfig)},e._getOffset=function(){var t=this,e={};return"function"==typeof this.config.offset?e.fn=function(e){return e.offsets=r({},e.offsets,t.config.offset(e.offsets,t.element)),e}:e.offset=this.config.offset,e},e._getContainer=function(){return!1===this.config.container?document.body:d.isElement(this.config.container)?o.default(this.config.container):o.default(document).find(this.config.container)},e._getAttachment=function(t){return Bt[t.toUpperCase()]},e._setListeners=function(){var t=this;this.config.trigger.split(" ").forEach((function(e){if("click"===e)o.default(t.element).on(t.constructor.Event.CLICK,t.config.selector,(function(e){return t.toggle(e)}));else if("manual"!==e){var n=e===Ft?t.constructor.Event.MOUSEENTER:t.constructor.Event.FOCUSIN,i=e===Ft?t.constructor.Event.MOUSELEAVE:t.constructor.Event.FOCUSOUT;o.default(t.element).on(n,t.config.selector,(function(e){return t._enter(e)})).on(i,t.config.selector,(function(e){return t._leave(e)}))}})),this._hideModalHandler=function(){t.element&&t.hide()},o.default(this.element).closest(".modal").on("hide.bs.modal",this._hideModalHandler),this.config.selector?this.config=r({},this.config,{trigger:"manual",selector:""}):this._fixTitle()},e._fixTitle=function(){var t=typeof this.element.getAttribute("data-original-title");(this.element.getAttribute("title")||"string"!==t)&&(this.element.setAttribute("data-original-title",this.element.getAttribute("title")||""),this.element.setAttribute("title",""))},e._enter=function(t,e){var n=this.constructor.DATA_KEY;(e=e||o.default(t.currentTarget).data(n))||(e=new this.constructor(t.currentTarget,this._getDelegateConfig()),o.default(t.currentTarget).data(n,e)),t&&(e._activeTrigger["focusin"===t.type?Qt:Ft]=!0),o.default(e.getTipElement()).hasClass(Rt)||e._hoverState===xt?e._hoverState=xt:(clearTimeout(e._timeout),e._hoverState=xt,e.config.delay&&e.config.delay.show?e._timeout=setTimeout((function(){e._hoverState===xt&&e.show()}),e.config.delay.show):e.show())},e._leave=function(t,e){var n=this.constructor.DATA_KEY;(e=e||o.default(t.currentTarget).data(n))||(e=new this.constructor(t.currentTarget,this._getDelegateConfig()),o.default(t.currentTarget).data(n,e)),t&&(e._activeTrigger["focusout"===t.type?Qt:Ft]=!1),e._isWithActiveTrigger()||(clearTimeout(e._timeout),e._hoverState=qt,e.config.delay&&e.config.delay.hide?e._timeout=setTimeout((function(){e._hoverState===qt&&e.hide()}),e.config.delay.hide):e.hide())},e._isWithActiveTrigger=function(){for(var t in this._activeTrigger)if(this._activeTrigger[t])return!0;return!1},e._getConfig=function(t){var e=o.default(this.element).data();return Object.keys(e).forEach((function(t){-1!==Pt.indexOf(t)&&delete e[t]})),"number"==typeof(t=r({},this.constructor.Default,e,"object"==typeof t&&t?t:{})).delay&&(t.delay={show:t.delay,hide:t.delay}),"number"==typeof t.title&&(t.title=t.title.toString()),"number"==typeof t.content&&(t.content=t.content.toString()),d.typeCheckConfig(It,t,this.constructor.DefaultType),t.sanitize&&(t.template=At(t.template,t.whiteList,t.sanitizeFn)),t},e._getDelegateConfig=function(){var t={};if(this.config)for(var e in this.config)this.constructor.Default[e]!==this.config[e]&&(t[e]=this.config[e]);return t},e._cleanTipClass=function(){var t=o.default(this.getTipElement()),e=t.attr("class").match(jt);null!==e&&e.length&&t.removeClass(e.join(""))},e._handlePopperPlacementChange=function(t){this.tip=t.instance.popper,this._cleanTipClass(),this.addAttachmentClass(this._getAttachment(t.placement))},e._fixTransition=function(){var t=this.getTipElement(),e=this.config.animation;null===t.getAttribute("x-placement")&&(o.default(t).removeClass(Lt),this.config.animation=!1,this.hide(),this.show(),this.config.animation=e)},t._jQueryInterface=function(e){return this.each((function(){var n=o.default(this),i=n.data(kt),a="object"==typeof e&&e;if((i||!/dispose|hide/.test(e))&&(i||(i=new t(this,a),n.data(kt,i)),"string"==typeof e)){if("undefined"==typeof i[e])throw new TypeError('No method named "'+e+'"');i[e]()}}))},l(t,null,[{key:"VERSION",get:function(){return"4.6.2"}},{key:"Default",get:function(){return Ht}},{key:"NAME",get:function(){return It}},{key:"DATA_KEY",get:function(){return kt}},{key:"Event",get:function(){return Mt}},{key:"EVENT_KEY",get:function(){return".bs.tooltip"}},{key:"DefaultType",get:function(){return Ut}}]),t}();o.default.fn.tooltip=Wt._jQueryInterface,o.default.fn.tooltip.Constructor=Wt,o.default.fn.tooltip.noConflict=function(){return o.default.fn.tooltip=Ot,Wt._jQueryInterface};var Vt="bs.popover",zt=o.default.fn.popover,Kt=new RegExp("(^|\\s)bs-popover\\S+","g"),Xt=r({},Wt.Default,{placement:"right",trigger:"click",content:"",template:''}),Yt=r({},Wt.DefaultType,{content:"(string|element|function)"}),$t={HIDE:"hide.bs.popover",HIDDEN:"hidden.bs.popover",SHOW:"show.bs.popover",SHOWN:"shown.bs.popover",INSERTED:"inserted.bs.popover",CLICK:"click.bs.popover",FOCUSIN:"focusin.bs.popover",FOCUSOUT:"focusout.bs.popover",MOUSEENTER:"mouseenter.bs.popover",MOUSELEAVE:"mouseleave.bs.popover"},Jt=function(t){var e,n;function i(){return t.apply(this,arguments)||this}n=t,(e=i).prototype=Object.create(n.prototype),e.prototype.constructor=e,u(e,n);var a=i.prototype;return a.isWithContent=function(){return this.getTitle()||this._getContent()},a.addAttachmentClass=function(t){o.default(this.getTipElement()).addClass("bs-popover-"+t)},a.getTipElement=function(){return this.tip=this.tip||o.default(this.config.template)[0],this.tip},a.setContent=function(){var t=o.default(this.getTipElement());this.setElementContent(t.find(".popover-header"),this.getTitle());var e=this._getContent();"function"==typeof e&&(e=e.call(this.element)),this.setElementContent(t.find(".popover-body"),e),t.removeClass("fade show")},a._getContent=function(){return this.element.getAttribute("data-content")||this.config.content},a._cleanTipClass=function(){var t=o.default(this.getTipElement()),e=t.attr("class").match(Kt);null!==e&&e.length>0&&t.removeClass(e.join(""))},i._jQueryInterface=function(t){return this.each((function(){var e=o.default(this).data(Vt),n="object"==typeof t?t:null;if((e||!/dispose|hide/.test(t))&&(e||(e=new i(this,n),o.default(this).data(Vt,e)),"string"==typeof t)){if("undefined"==typeof e[t])throw new TypeError('No method named "'+t+'"');e[t]()}}))},l(i,null,[{key:"VERSION",get:function(){return"4.6.2"}},{key:"Default",get:function(){return Xt}},{key:"NAME",get:function(){return"popover"}},{key:"DATA_KEY",get:function(){return Vt}},{key:"Event",get:function(){return $t}},{key:"EVENT_KEY",get:function(){return".bs.popover"}},{key:"DefaultType",get:function(){return Yt}}]),i}(Wt);o.default.fn.popover=Jt._jQueryInterface,o.default.fn.popover.Constructor=Jt,o.default.fn.popover.noConflict=function(){return o.default.fn.popover=zt,Jt._jQueryInterface};var Gt="scrollspy",Zt="bs.scrollspy",te=o.default.fn[Gt],ee="active",ne="position",ie=".nav, .list-group",oe={offset:10,method:"auto",target:""},ae={offset:"number",method:"string",target:"(string|element)"},se=function(){function t(t,e){var n=this;this._element=t,this._scrollElement="BODY"===t.tagName?window:t,this._config=this._getConfig(e),this._selector=this._config.target+" .nav-link,"+this._config.target+" .list-group-item,"+this._config.target+" .dropdown-item",this._offsets=[],this._targets=[],this._activeTarget=null,this._scrollHeight=0,o.default(this._scrollElement).on("scroll.bs.scrollspy",(function(t){return n._process(t)})),this.refresh(),this._process()}var e=t.prototype;return e.refresh=function(){var t=this,e=this._scrollElement===this._scrollElement.window?"offset":ne,n="auto"===this._config.method?e:this._config.method,i=n===ne?this._getScrollTop():0;this._offsets=[],this._targets=[],this._scrollHeight=this._getScrollHeight(),[].slice.call(document.querySelectorAll(this._selector)).map((function(t){var e,a=d.getSelectorFromElement(t);if(a&&(e=document.querySelector(a)),e){var s=e.getBoundingClientRect();if(s.width||s.height)return[o.default(e)[n]().top+i,a]}return null})).filter(Boolean).sort((function(t,e){return t[0]-e[0]})).forEach((function(e){t._offsets.push(e[0]),t._targets.push(e[1])}))},e.dispose=function(){o.default.removeData(this._element,Zt),o.default(this._scrollElement).off(".bs.scrollspy"),this._element=null,this._scrollElement=null,this._config=null,this._selector=null,this._offsets=null,this._targets=null,this._activeTarget=null,this._scrollHeight=null},e._getConfig=function(t){if("string"!=typeof(t=r({},oe,"object"==typeof t&&t?t:{})).target&&d.isElement(t.target)){var e=o.default(t.target).attr("id");e||(e=d.getUID(Gt),o.default(t.target).attr("id",e)),t.target="#"+e}return d.typeCheckConfig(Gt,t,ae),t},e._getScrollTop=function(){return this._scrollElement===window?this._scrollElement.pageYOffset:this._scrollElement.scrollTop},e._getScrollHeight=function(){return this._scrollElement.scrollHeight||Math.max(document.body.scrollHeight,document.documentElement.scrollHeight)},e._getOffsetHeight=function(){return this._scrollElement===window?window.innerHeight:this._scrollElement.getBoundingClientRect().height},e._process=function(){var t=this._getScrollTop()+this._config.offset,e=this._getScrollHeight(),n=this._config.offset+e-this._getOffsetHeight();if(this._scrollHeight!==e&&this.refresh(),t>=n){var i=this._targets[this._targets.length-1];this._activeTarget!==i&&this._activate(i)}else{if(this._activeTarget&&t0)return this._activeTarget=null,void this._clear();for(var o=this._offsets.length;o--;)this._activeTarget!==this._targets[o]&&t>=this._offsets[o]&&("undefined"==typeof this._offsets[o+1]||t li > .active",ge=function(){function t(t){this._element=t}var e=t.prototype;return e.show=function(){var t=this;if(!(this._element.parentNode&&this._element.parentNode.nodeType===Node.ELEMENT_NODE&&o.default(this._element).hasClass(ue)||o.default(this._element).hasClass("disabled")||this._element.hasAttribute("disabled"))){var e,n,i=o.default(this._element).closest(".nav, .list-group")[0],a=d.getSelectorFromElement(this._element);if(i){var s="UL"===i.nodeName||"OL"===i.nodeName?he:ce;n=(n=o.default.makeArray(o.default(i).find(s)))[n.length-1]}var l=o.default.Event("hide.bs.tab",{relatedTarget:this._element}),r=o.default.Event("show.bs.tab",{relatedTarget:n});if(n&&o.default(n).trigger(l),o.default(this._element).trigger(r),!r.isDefaultPrevented()&&!l.isDefaultPrevented()){a&&(e=document.querySelector(a)),this._activate(this._element,i);var u=function(){var e=o.default.Event("hidden.bs.tab",{relatedTarget:t._element}),i=o.default.Event("shown.bs.tab",{relatedTarget:n});o.default(n).trigger(e),o.default(t._element).trigger(i)};e?this._activate(e,e.parentNode,u):u()}}},e.dispose=function(){o.default.removeData(this._element,le),this._element=null},e._activate=function(t,e,n){var i=this,a=(!e||"UL"!==e.nodeName&&"OL"!==e.nodeName?o.default(e).children(ce):o.default(e).find(he))[0],s=n&&a&&o.default(a).hasClass(fe),l=function(){return i._transitionComplete(t,a,n)};if(a&&s){var r=d.getTransitionDurationFromElement(a);o.default(a).removeClass(de).one(d.TRANSITION_END,l).emulateTransitionEnd(r)}else l()},e._transitionComplete=function(t,e,n){if(e){o.default(e).removeClass(ue);var i=o.default(e.parentNode).find("> .dropdown-menu .active")[0];i&&o.default(i).removeClass(ue),"tab"===e.getAttribute("role")&&e.setAttribute("aria-selected",!1)}o.default(t).addClass(ue),"tab"===t.getAttribute("role")&&t.setAttribute("aria-selected",!0),d.reflow(t),t.classList.contains(fe)&&t.classList.add(de);var a=t.parentNode;if(a&&"LI"===a.nodeName&&(a=a.parentNode),a&&o.default(a).hasClass("dropdown-menu")){var s=o.default(t).closest(".dropdown")[0];if(s){var l=[].slice.call(s.querySelectorAll(".dropdown-toggle"));o.default(l).addClass(ue)}t.setAttribute("aria-expanded",!0)}n&&n()},t._jQueryInterface=function(e){return this.each((function(){var n=o.default(this),i=n.data(le);if(i||(i=new t(this),n.data(le,i)),"string"==typeof e){if("undefined"==typeof i[e])throw new TypeError('No method named "'+e+'"');i[e]()}}))},l(t,null,[{key:"VERSION",get:function(){return"4.6.2"}}]),t}();o.default(document).on("click.bs.tab.data-api",'[data-toggle="tab"], [data-toggle="pill"], [data-toggle="list"]',(function(t){t.preventDefault(),ge._jQueryInterface.call(o.default(this),"show")})),o.default.fn.tab=ge._jQueryInterface,o.default.fn.tab.Constructor=ge,o.default.fn.tab.noConflict=function(){return o.default.fn.tab=re,ge._jQueryInterface};var me="bs.toast",pe=o.default.fn.toast,_e="hide",ve="show",ye="showing",be="click.dismiss.bs.toast",Ee={animation:!0,autohide:!0,delay:500},Te={animation:"boolean",autohide:"boolean",delay:"number"},we=function(){function t(t,e){this._element=t,this._config=this._getConfig(e),this._timeout=null,this._setListeners()}var e=t.prototype;return e.show=function(){var t=this,e=o.default.Event("show.bs.toast");if(o.default(this._element).trigger(e),!e.isDefaultPrevented()){this._clearTimeout(),this._config.animation&&this._element.classList.add("fade");var n=function(){t._element.classList.remove(ye),t._element.classList.add(ve),o.default(t._element).trigger("shown.bs.toast"),t._config.autohide&&(t._timeout=setTimeout((function(){t.hide()}),t._config.delay))};if(this._element.classList.remove(_e),d.reflow(this._element),this._element.classList.add(ye),this._config.animation){var i=d.getTransitionDurationFromElement(this._element);o.default(this._element).one(d.TRANSITION_END,n).emulateTransitionEnd(i)}else n()}},e.hide=function(){if(this._element.classList.contains(ve)){var t=o.default.Event("hide.bs.toast");o.default(this._element).trigger(t),t.isDefaultPrevented()||this._close()}},e.dispose=function(){this._clearTimeout(),this._element.classList.contains(ve)&&this._element.classList.remove(ve),o.default(this._element).off(be),o.default.removeData(this._element,me),this._element=null,this._config=null},e._getConfig=function(t){return t=r({},Ee,o.default(this._element).data(),"object"==typeof t&&t?t:{}),d.typeCheckConfig("toast",t,this.constructor.DefaultType),t},e._setListeners=function(){var t=this;o.default(this._element).on(be,'[data-dismiss="toast"]',(function(){return t.hide()}))},e._close=function(){var t=this,e=function(){t._element.classList.add(_e),o.default(t._element).trigger("hidden.bs.toast")};if(this._element.classList.remove(ve),this._config.animation){var n=d.getTransitionDurationFromElement(this._element);o.default(this._element).one(d.TRANSITION_END,e).emulateTransitionEnd(n)}else e()},e._clearTimeout=function(){clearTimeout(this._timeout),this._timeout=null},t._jQueryInterface=function(e){return this.each((function(){var n=o.default(this),i=n.data(me);if(i||(i=new t(this,"object"==typeof e&&e),n.data(me,i)),"string"==typeof e){if("undefined"==typeof i[e])throw new TypeError('No method named "'+e+'"');i[e](this)}}))},l(t,null,[{key:"VERSION",get:function(){return"4.6.2"}},{key:"DefaultType",get:function(){return Te}},{key:"Default",get:function(){return Ee}}]),t}();o.default.fn.toast=we._jQueryInterface,o.default.fn.toast.Constructor=we,o.default.fn.toast.noConflict=function(){return o.default.fn.toast=pe,we._jQueryInterface},t.Alert=g,t.Button=E,t.Carousel=P,t.Collapse=V,t.Dropdown=lt,t.Modal=Ct,t.Popover=Jt,t.Scrollspy=se,t.Tab=ge,t.Toast=we,t.Tooltip=Wt,t.Util=d,Object.defineProperty(t,"__esModule",{value:!0})})); -//# sourceMappingURL=bootstrap.min.js.map \ No newline at end of file + * Bootstrap v3.3.6 (http://getbootstrap.com) + * Copyright 2011-2015 Twitter, Inc. + * Licensed under the MIT license + */ +if("undefined"==typeof jQuery)throw new Error("Bootstrap's JavaScript requires jQuery");+function(a){"use strict";var b=a.fn.jquery.split(" ")[0].split(".");if(b[0]<2&&b[1]<9||1==b[0]&&9==b[1]&&b[2]<1||b[0]>2)throw new Error("Bootstrap's JavaScript requires jQuery version 1.9.1 or higher, but lower than version 3")}(jQuery),+function(a){"use strict";function b(){var a=document.createElement("bootstrap"),b={WebkitTransition:"webkitTransitionEnd",MozTransition:"transitionend",OTransition:"oTransitionEnd otransitionend",transition:"transitionend"};for(var c in b)if(void 0!==a.style[c])return{end:b[c]};return!1}a.fn.emulateTransitionEnd=function(b){var c=!1,d=this;a(this).one("bsTransitionEnd",function(){c=!0});var e=function(){c||a(d).trigger(a.support.transition.end)};return setTimeout(e,b),this},a(function(){a.support.transition=b(),a.support.transition&&(a.event.special.bsTransitionEnd={bindType:a.support.transition.end,delegateType:a.support.transition.end,handle:function(b){return a(b.target).is(this)?b.handleObj.handler.apply(this,arguments):void 0}})})}(jQuery),+function(a){"use strict";function b(b){return this.each(function(){var c=a(this),e=c.data("bs.alert");e||c.data("bs.alert",e=new d(this)),"string"==typeof b&&e[b].call(c)})}var c='[data-dismiss="alert"]',d=function(b){a(b).on("click",c,this.close)};d.VERSION="3.3.6",d.TRANSITION_DURATION=150,d.prototype.close=function(b){function c(){g.detach().trigger("closed.bs.alert").remove()}var e=a(this),f=e.attr("data-target");f||(f=e.attr("href"),f=f&&f.replace(/.*(?=#[^\s]*$)/,""));var g=a(f);b&&b.preventDefault(),g.length||(g=e.closest(".alert")),g.trigger(b=a.Event("close.bs.alert")),b.isDefaultPrevented()||(g.removeClass("in"),a.support.transition&&g.hasClass("fade")?g.one("bsTransitionEnd",c).emulateTransitionEnd(d.TRANSITION_DURATION):c())};var e=a.fn.alert;a.fn.alert=b,a.fn.alert.Constructor=d,a.fn.alert.noConflict=function(){return a.fn.alert=e,this},a(document).on("click.bs.alert.data-api",c,d.prototype.close)}(jQuery),+function(a){"use strict";function b(b){return this.each(function(){var d=a(this),e=d.data("bs.button"),f="object"==typeof b&&b;e||d.data("bs.button",e=new c(this,f)),"toggle"==b?e.toggle():b&&e.setState(b)})}var c=function(b,d){this.$element=a(b),this.options=a.extend({},c.DEFAULTS,d),this.isLoading=!1};c.VERSION="3.3.6",c.DEFAULTS={loadingText:"loading..."},c.prototype.setState=function(b){var c="disabled",d=this.$element,e=d.is("input")?"val":"html",f=d.data();b+="Text",null==f.resetText&&d.data("resetText",d[e]()),setTimeout(a.proxy(function(){d[e](null==f[b]?this.options[b]:f[b]),"loadingText"==b?(this.isLoading=!0,d.addClass(c).attr(c,c)):this.isLoading&&(this.isLoading=!1,d.removeClass(c).removeAttr(c))},this),0)},c.prototype.toggle=function(){var a=!0,b=this.$element.closest('[data-toggle="buttons"]');if(b.length){var c=this.$element.find("input");"radio"==c.prop("type")?(c.prop("checked")&&(a=!1),b.find(".active").removeClass("active"),this.$element.addClass("active")):"checkbox"==c.prop("type")&&(c.prop("checked")!==this.$element.hasClass("active")&&(a=!1),this.$element.toggleClass("active")),c.prop("checked",this.$element.hasClass("active")),a&&c.trigger("change")}else this.$element.attr("aria-pressed",!this.$element.hasClass("active")),this.$element.toggleClass("active")};var d=a.fn.button;a.fn.button=b,a.fn.button.Constructor=c,a.fn.button.noConflict=function(){return a.fn.button=d,this},a(document).on("click.bs.button.data-api",'[data-toggle^="button"]',function(c){var d=a(c.target);d.hasClass("btn")||(d=d.closest(".btn")),b.call(d,"toggle"),a(c.target).is('input[type="radio"]')||a(c.target).is('input[type="checkbox"]')||c.preventDefault()}).on("focus.bs.button.data-api blur.bs.button.data-api",'[data-toggle^="button"]',function(b){a(b.target).closest(".btn").toggleClass("focus",/^focus(in)?$/.test(b.type))})}(jQuery),+function(a){"use strict";function b(b){return this.each(function(){var d=a(this),e=d.data("bs.carousel"),f=a.extend({},c.DEFAULTS,d.data(),"object"==typeof b&&b),g="string"==typeof b?b:f.slide;e||d.data("bs.carousel",e=new c(this,f)),"number"==typeof b?e.to(b):g?e[g]():f.interval&&e.pause().cycle()})}var c=function(b,c){this.$element=a(b),this.$indicators=this.$element.find(".carousel-indicators"),this.options=c,this.paused=null,this.sliding=null,this.interval=null,this.$active=null,this.$items=null,this.options.keyboard&&this.$element.on("keydown.bs.carousel",a.proxy(this.keydown,this)),"hover"==this.options.pause&&!("ontouchstart"in document.documentElement)&&this.$element.on("mouseenter.bs.carousel",a.proxy(this.pause,this)).on("mouseleave.bs.carousel",a.proxy(this.cycle,this))};c.VERSION="3.3.6",c.TRANSITION_DURATION=600,c.DEFAULTS={interval:5e3,pause:"hover",wrap:!0,keyboard:!0},c.prototype.keydown=function(a){if(!/input|textarea/i.test(a.target.tagName)){switch(a.which){case 37:this.prev();break;case 39:this.next();break;default:return}a.preventDefault()}},c.prototype.cycle=function(b){return b||(this.paused=!1),this.interval&&clearInterval(this.interval),this.options.interval&&!this.paused&&(this.interval=setInterval(a.proxy(this.next,this),this.options.interval)),this},c.prototype.getItemIndex=function(a){return this.$items=a.parent().children(".item"),this.$items.index(a||this.$active)},c.prototype.getItemForDirection=function(a,b){var c=this.getItemIndex(b),d="prev"==a&&0===c||"next"==a&&c==this.$items.length-1;if(d&&!this.options.wrap)return b;var e="prev"==a?-1:1,f=(c+e)%this.$items.length;return this.$items.eq(f)},c.prototype.to=function(a){var b=this,c=this.getItemIndex(this.$active=this.$element.find(".item.active"));return a>this.$items.length-1||0>a?void 0:this.sliding?this.$element.one("slid.bs.carousel",function(){b.to(a)}):c==a?this.pause().cycle():this.slide(a>c?"next":"prev",this.$items.eq(a))},c.prototype.pause=function(b){return b||(this.paused=!0),this.$element.find(".next, .prev").length&&a.support.transition&&(this.$element.trigger(a.support.transition.end),this.cycle(!0)),this.interval=clearInterval(this.interval),this},c.prototype.next=function(){return this.sliding?void 0:this.slide("next")},c.prototype.prev=function(){return this.sliding?void 0:this.slide("prev")},c.prototype.slide=function(b,d){var e=this.$element.find(".item.active"),f=d||this.getItemForDirection(b,e),g=this.interval,h="next"==b?"left":"right",i=this;if(f.hasClass("active"))return this.sliding=!1;var j=f[0],k=a.Event("slide.bs.carousel",{relatedTarget:j,direction:h});if(this.$element.trigger(k),!k.isDefaultPrevented()){if(this.sliding=!0,g&&this.pause(),this.$indicators.length){this.$indicators.find(".active").removeClass("active");var l=a(this.$indicators.children()[this.getItemIndex(f)]);l&&l.addClass("active")}var m=a.Event("slid.bs.carousel",{relatedTarget:j,direction:h});return a.support.transition&&this.$element.hasClass("slide")?(f.addClass(b),f[0].offsetWidth,e.addClass(h),f.addClass(h),e.one("bsTransitionEnd",function(){f.removeClass([b,h].join(" ")).addClass("active"),e.removeClass(["active",h].join(" ")),i.sliding=!1,setTimeout(function(){i.$element.trigger(m)},0)}).emulateTransitionEnd(c.TRANSITION_DURATION)):(e.removeClass("active"),f.addClass("active"),this.sliding=!1,this.$element.trigger(m)),g&&this.cycle(),this}};var d=a.fn.carousel;a.fn.carousel=b,a.fn.carousel.Constructor=c,a.fn.carousel.noConflict=function(){return a.fn.carousel=d,this};var e=function(c){var d,e=a(this),f=a(e.attr("data-target")||(d=e.attr("href"))&&d.replace(/.*(?=#[^\s]+$)/,""));if(f.hasClass("carousel")){var g=a.extend({},f.data(),e.data()),h=e.attr("data-slide-to");h&&(g.interval=!1),b.call(f,g),h&&f.data("bs.carousel").to(h),c.preventDefault()}};a(document).on("click.bs.carousel.data-api","[data-slide]",e).on("click.bs.carousel.data-api","[data-slide-to]",e),a(window).on("load",function(){a('[data-ride="carousel"]').each(function(){var c=a(this);b.call(c,c.data())})})}(jQuery),+function(a){"use strict";function b(b){var c,d=b.attr("data-target")||(c=b.attr("href"))&&c.replace(/.*(?=#[^\s]+$)/,"");return a(d)}function c(b){return this.each(function(){var c=a(this),e=c.data("bs.collapse"),f=a.extend({},d.DEFAULTS,c.data(),"object"==typeof b&&b);!e&&f.toggle&&/show|hide/.test(b)&&(f.toggle=!1),e||c.data("bs.collapse",e=new d(this,f)),"string"==typeof b&&e[b]()})}var d=function(b,c){this.$element=a(b),this.options=a.extend({},d.DEFAULTS,c),this.$trigger=a('[data-toggle="collapse"][href="#'+b.id+'"],[data-toggle="collapse"][data-target="#'+b.id+'"]'),this.transitioning=null,this.options.parent?this.$parent=this.getParent():this.addAriaAndCollapsedClass(this.$element,this.$trigger),this.options.toggle&&this.toggle()};d.VERSION="3.3.6",d.TRANSITION_DURATION=350,d.DEFAULTS={toggle:!0},d.prototype.dimension=function(){var a=this.$element.hasClass("width");return a?"width":"height"},d.prototype.show=function(){if(!this.transitioning&&!this.$element.hasClass("in")){var b,e=this.$parent&&this.$parent.children(".panel").children(".in, .collapsing");if(!(e&&e.length&&(b=e.data("bs.collapse"),b&&b.transitioning))){var f=a.Event("show.bs.collapse");if(this.$element.trigger(f),!f.isDefaultPrevented()){e&&e.length&&(c.call(e,"hide"),b||e.data("bs.collapse",null));var g=this.dimension();this.$element.removeClass("collapse").addClass("collapsing")[g](0).attr("aria-expanded",!0),this.$trigger.removeClass("collapsed").attr("aria-expanded",!0),this.transitioning=1;var h=function(){this.$element.removeClass("collapsing").addClass("collapse in")[g](""),this.transitioning=0,this.$element.trigger("shown.bs.collapse")};if(!a.support.transition)return h.call(this);var i=a.camelCase(["scroll",g].join("-"));this.$element.one("bsTransitionEnd",a.proxy(h,this)).emulateTransitionEnd(d.TRANSITION_DURATION)[g](this.$element[0][i])}}}},d.prototype.hide=function(){if(!this.transitioning&&this.$element.hasClass("in")){var b=a.Event("hide.bs.collapse");if(this.$element.trigger(b),!b.isDefaultPrevented()){var c=this.dimension();this.$element[c](this.$element[c]())[0].offsetHeight,this.$element.addClass("collapsing").removeClass("collapse in").attr("aria-expanded",!1),this.$trigger.addClass("collapsed").attr("aria-expanded",!1),this.transitioning=1;var e=function(){this.transitioning=0,this.$element.removeClass("collapsing").addClass("collapse").trigger("hidden.bs.collapse")};return a.support.transition?void this.$element[c](0).one("bsTransitionEnd",a.proxy(e,this)).emulateTransitionEnd(d.TRANSITION_DURATION):e.call(this)}}},d.prototype.toggle=function(){this[this.$element.hasClass("in")?"hide":"show"]()},d.prototype.getParent=function(){return a(this.options.parent).find('[data-toggle="collapse"][data-parent="'+this.options.parent+'"]').each(a.proxy(function(c,d){var e=a(d);this.addAriaAndCollapsedClass(b(e),e)},this)).end()},d.prototype.addAriaAndCollapsedClass=function(a,b){var c=a.hasClass("in");a.attr("aria-expanded",c),b.toggleClass("collapsed",!c).attr("aria-expanded",c)};var e=a.fn.collapse;a.fn.collapse=c,a.fn.collapse.Constructor=d,a.fn.collapse.noConflict=function(){return a.fn.collapse=e,this},a(document).on("click.bs.collapse.data-api",'[data-toggle="collapse"]',function(d){var e=a(this);e.attr("data-target")||d.preventDefault();var f=b(e),g=f.data("bs.collapse"),h=g?"toggle":e.data();c.call(f,h)})}(jQuery),+function(a){"use strict";function b(b){var c=b.attr("data-target");c||(c=b.attr("href"),c=c&&/#[A-Za-z]/.test(c)&&c.replace(/.*(?=#[^\s]*$)/,""));var d=c&&a(c);return d&&d.length?d:b.parent()}function c(c){c&&3===c.which||(a(e).remove(),a(f).each(function(){var d=a(this),e=b(d),f={relatedTarget:this};e.hasClass("open")&&(c&&"click"==c.type&&/input|textarea/i.test(c.target.tagName)&&a.contains(e[0],c.target)||(e.trigger(c=a.Event("hide.bs.dropdown",f)),c.isDefaultPrevented()||(d.attr("aria-expanded","false"),e.removeClass("open").trigger(a.Event("hidden.bs.dropdown",f)))))}))}function d(b){return this.each(function(){var c=a(this),d=c.data("bs.dropdown");d||c.data("bs.dropdown",d=new g(this)),"string"==typeof b&&d[b].call(c)})}var e=".dropdown-backdrop",f='[data-toggle="dropdown"]',g=function(b){a(b).on("click.bs.dropdown",this.toggle)};g.VERSION="3.3.6",g.prototype.toggle=function(d){var e=a(this);if(!e.is(".disabled, :disabled")){var f=b(e),g=f.hasClass("open");if(c(),!g){"ontouchstart"in document.documentElement&&!f.closest(".navbar-nav").length&&a(document.createElement("div")).addClass("dropdown-backdrop").insertAfter(a(this)).on("click",c);var h={relatedTarget:this};if(f.trigger(d=a.Event("show.bs.dropdown",h)),d.isDefaultPrevented())return;e.trigger("focus").attr("aria-expanded","true"),f.toggleClass("open").trigger(a.Event("shown.bs.dropdown",h))}return!1}},g.prototype.keydown=function(c){if(/(38|40|27|32)/.test(c.which)&&!/input|textarea/i.test(c.target.tagName)){var d=a(this);if(c.preventDefault(),c.stopPropagation(),!d.is(".disabled, :disabled")){var e=b(d),g=e.hasClass("open");if(!g&&27!=c.which||g&&27==c.which)return 27==c.which&&e.find(f).trigger("focus"),d.trigger("click");var h=" li:not(.disabled):visible a",i=e.find(".dropdown-menu"+h);if(i.length){var j=i.index(c.target);38==c.which&&j>0&&j--,40==c.which&&jdocument.documentElement.clientHeight;this.$element.css({paddingLeft:!this.bodyIsOverflowing&&a?this.scrollbarWidth:"",paddingRight:this.bodyIsOverflowing&&!a?this.scrollbarWidth:""})},c.prototype.resetAdjustments=function(){this.$element.css({paddingLeft:"",paddingRight:""})},c.prototype.checkScrollbar=function(){var a=window.innerWidth;if(!a){var b=document.documentElement.getBoundingClientRect();a=b.right-Math.abs(b.left)}this.bodyIsOverflowing=document.body.clientWidth
    ',trigger:"hover focus",title:"",delay:0,html:!1,container:!1,viewport:{selector:"body",padding:0}},c.prototype.init=function(b,c,d){if(this.enabled=!0,this.type=b,this.$element=a(c),this.options=this.getOptions(d),this.$viewport=this.options.viewport&&a(a.isFunction(this.options.viewport)?this.options.viewport.call(this,this.$element):this.options.viewport.selector||this.options.viewport),this.inState={click:!1,hover:!1,focus:!1},this.$element[0]instanceof document.constructor&&!this.options.selector)throw new Error("`selector` option must be specified when initializing "+this.type+" on the window.document object!");for(var e=this.options.trigger.split(" "),f=e.length;f--;){var g=e[f];if("click"==g)this.$element.on("click."+this.type,this.options.selector,a.proxy(this.toggle,this));else if("manual"!=g){var h="hover"==g?"mouseenter":"focusin",i="hover"==g?"mouseleave":"focusout";this.$element.on(h+"."+this.type,this.options.selector,a.proxy(this.enter,this)),this.$element.on(i+"."+this.type,this.options.selector,a.proxy(this.leave,this))}}this.options.selector?this._options=a.extend({},this.options,{trigger:"manual",selector:""}):this.fixTitle()},c.prototype.getDefaults=function(){return c.DEFAULTS},c.prototype.getOptions=function(b){return b=a.extend({},this.getDefaults(),this.$element.data(),b),b.delay&&"number"==typeof b.delay&&(b.delay={show:b.delay,hide:b.delay}),b},c.prototype.getDelegateOptions=function(){var b={},c=this.getDefaults();return this._options&&a.each(this._options,function(a,d){c[a]!=d&&(b[a]=d)}),b},c.prototype.enter=function(b){var c=b instanceof this.constructor?b:a(b.currentTarget).data("bs."+this.type);return c||(c=new this.constructor(b.currentTarget,this.getDelegateOptions()),a(b.currentTarget).data("bs."+this.type,c)),b instanceof a.Event&&(c.inState["focusin"==b.type?"focus":"hover"]=!0),c.tip().hasClass("in")||"in"==c.hoverState?void(c.hoverState="in"):(clearTimeout(c.timeout),c.hoverState="in",c.options.delay&&c.options.delay.show?void(c.timeout=setTimeout(function(){"in"==c.hoverState&&c.show()},c.options.delay.show)):c.show())},c.prototype.isInStateTrue=function(){for(var a in this.inState)if(this.inState[a])return!0;return!1},c.prototype.leave=function(b){var c=b instanceof this.constructor?b:a(b.currentTarget).data("bs."+this.type);return c||(c=new this.constructor(b.currentTarget,this.getDelegateOptions()),a(b.currentTarget).data("bs."+this.type,c)),b instanceof a.Event&&(c.inState["focusout"==b.type?"focus":"hover"]=!1),c.isInStateTrue()?void 0:(clearTimeout(c.timeout),c.hoverState="out",c.options.delay&&c.options.delay.hide?void(c.timeout=setTimeout(function(){"out"==c.hoverState&&c.hide()},c.options.delay.hide)):c.hide())},c.prototype.show=function(){var b=a.Event("show.bs."+this.type);if(this.hasContent()&&this.enabled){this.$element.trigger(b);var d=a.contains(this.$element[0].ownerDocument.documentElement,this.$element[0]);if(b.isDefaultPrevented()||!d)return;var e=this,f=this.tip(),g=this.getUID(this.type);this.setContent(),f.attr("id",g),this.$element.attr("aria-describedby",g),this.options.animation&&f.addClass("fade");var h="function"==typeof this.options.placement?this.options.placement.call(this,f[0],this.$element[0]):this.options.placement,i=/\s?auto?\s?/i,j=i.test(h);j&&(h=h.replace(i,"")||"top"),f.detach().css({top:0,left:0,display:"block"}).addClass(h).data("bs."+this.type,this),this.options.container?f.appendTo(this.options.container):f.insertAfter(this.$element),this.$element.trigger("inserted.bs."+this.type);var k=this.getPosition(),l=f[0].offsetWidth,m=f[0].offsetHeight;if(j){var n=h,o=this.getPosition(this.$viewport);h="bottom"==h&&k.bottom+m>o.bottom?"top":"top"==h&&k.top-mo.width?"left":"left"==h&&k.left-lg.top+g.height&&(e.top=g.top+g.height-i)}else{var j=b.left-f,k=b.left+f+c;jg.right&&(e.left=g.left+g.width-k)}return e},c.prototype.getTitle=function(){var a,b=this.$element,c=this.options;return a=b.attr("data-original-title")||("function"==typeof c.title?c.title.call(b[0]):c.title)},c.prototype.getUID=function(a){do a+=~~(1e6*Math.random());while(document.getElementById(a));return a},c.prototype.tip=function(){if(!this.$tip&&(this.$tip=a(this.options.template),1!=this.$tip.length))throw new Error(this.type+" `template` option must consist of exactly 1 top-level element!");return this.$tip},c.prototype.arrow=function(){return this.$arrow=this.$arrow||this.tip().find(".tooltip-arrow")},c.prototype.enable=function(){this.enabled=!0},c.prototype.disable=function(){this.enabled=!1},c.prototype.toggleEnabled=function(){this.enabled=!this.enabled},c.prototype.toggle=function(b){var c=this;b&&(c=a(b.currentTarget).data("bs."+this.type),c||(c=new this.constructor(b.currentTarget,this.getDelegateOptions()),a(b.currentTarget).data("bs."+this.type,c))),b?(c.inState.click=!c.inState.click,c.isInStateTrue()?c.enter(c):c.leave(c)):c.tip().hasClass("in")?c.leave(c):c.enter(c)},c.prototype.destroy=function(){var a=this;clearTimeout(this.timeout),this.hide(function(){a.$element.off("."+a.type).removeData("bs."+a.type),a.$tip&&a.$tip.detach(),a.$tip=null,a.$arrow=null,a.$viewport=null})};var d=a.fn.tooltip;a.fn.tooltip=b,a.fn.tooltip.Constructor=c,a.fn.tooltip.noConflict=function(){return a.fn.tooltip=d,this}}(jQuery),+function(a){"use strict";function b(b){return this.each(function(){var d=a(this),e=d.data("bs.popover"),f="object"==typeof b&&b;(e||!/destroy|hide/.test(b))&&(e||d.data("bs.popover",e=new c(this,f)),"string"==typeof b&&e[b]())})}var c=function(a,b){this.init("popover",a,b)};if(!a.fn.tooltip)throw new Error("Popover requires tooltip.js");c.VERSION="3.3.6",c.DEFAULTS=a.extend({},a.fn.tooltip.Constructor.DEFAULTS,{placement:"right",trigger:"click",content:"",template:''}),c.prototype=a.extend({},a.fn.tooltip.Constructor.prototype),c.prototype.constructor=c,c.prototype.getDefaults=function(){return c.DEFAULTS},c.prototype.setContent=function(){var a=this.tip(),b=this.getTitle(),c=this.getContent();a.find(".popover-title")[this.options.html?"html":"text"](b),a.find(".popover-content").children().detach().end()[this.options.html?"string"==typeof c?"html":"append":"text"](c),a.removeClass("fade top bottom left right in"),a.find(".popover-title").html()||a.find(".popover-title").hide()},c.prototype.hasContent=function(){return this.getTitle()||this.getContent()},c.prototype.getContent=function(){var a=this.$element,b=this.options;return a.attr("data-content")||("function"==typeof b.content?b.content.call(a[0]):b.content)},c.prototype.arrow=function(){return this.$arrow=this.$arrow||this.tip().find(".arrow")};var d=a.fn.popover;a.fn.popover=b,a.fn.popover.Constructor=c,a.fn.popover.noConflict=function(){return a.fn.popover=d,this}}(jQuery),+function(a){"use strict";function b(c,d){this.$body=a(document.body),this.$scrollElement=a(a(c).is(document.body)?window:c),this.options=a.extend({},b.DEFAULTS,d),this.selector=(this.options.target||"")+" .nav li > a",this.offsets=[],this.targets=[],this.activeTarget=null,this.scrollHeight=0,this.$scrollElement.on("scroll.bs.scrollspy",a.proxy(this.process,this)),this.refresh(),this.process()}function c(c){return this.each(function(){var d=a(this),e=d.data("bs.scrollspy"),f="object"==typeof c&&c;e||d.data("bs.scrollspy",e=new b(this,f)),"string"==typeof c&&e[c]()})}b.VERSION="3.3.6",b.DEFAULTS={offset:10},b.prototype.getScrollHeight=function(){return this.$scrollElement[0].scrollHeight||Math.max(this.$body[0].scrollHeight,document.documentElement.scrollHeight)},b.prototype.refresh=function(){var b=this,c="offset",d=0;this.offsets=[],this.targets=[],this.scrollHeight=this.getScrollHeight(),a.isWindow(this.$scrollElement[0])||(c="position",d=this.$scrollElement.scrollTop()),this.$body.find(this.selector).map(function(){var b=a(this),e=b.data("target")||b.attr("href"),f=/^#./.test(e)&&a(e);return f&&f.length&&f.is(":visible")&&[[f[c]().top+d,e]]||null}).sort(function(a,b){return a[0]-b[0]}).each(function(){b.offsets.push(this[0]),b.targets.push(this[1])})},b.prototype.process=function(){var a,b=this.$scrollElement.scrollTop()+this.options.offset,c=this.getScrollHeight(),d=this.options.offset+c-this.$scrollElement.height(),e=this.offsets,f=this.targets,g=this.activeTarget;if(this.scrollHeight!=c&&this.refresh(),b>=d)return g!=(a=f[f.length-1])&&this.activate(a);if(g&&b=e[a]&&(void 0===e[a+1]||b .dropdown-menu > .active").removeClass("active").end().find('[data-toggle="tab"]').attr("aria-expanded",!1),b.addClass("active").find('[data-toggle="tab"]').attr("aria-expanded",!0),h?(b[0].offsetWidth,b.addClass("in")):b.removeClass("fade"),b.parent(".dropdown-menu").length&&b.closest("li.dropdown").addClass("active").end().find('[data-toggle="tab"]').attr("aria-expanded",!0),e&&e()}var g=d.find("> .active"),h=e&&a.support.transition&&(g.length&&g.hasClass("fade")||!!d.find("> .fade").length);g.length&&h?g.one("bsTransitionEnd",f).emulateTransitionEnd(c.TRANSITION_DURATION):f(),g.removeClass("in")};var d=a.fn.tab;a.fn.tab=b,a.fn.tab.Constructor=c,a.fn.tab.noConflict=function(){return a.fn.tab=d,this};var e=function(c){c.preventDefault(),b.call(a(this),"show")};a(document).on("click.bs.tab.data-api",'[data-toggle="tab"]',e).on("click.bs.tab.data-api",'[data-toggle="pill"]',e)}(jQuery),+function(a){"use strict";function b(b){return this.each(function(){var d=a(this),e=d.data("bs.affix"),f="object"==typeof b&&b;e||d.data("bs.affix",e=new c(this,f)),"string"==typeof b&&e[b]()})}var c=function(b,d){this.options=a.extend({},c.DEFAULTS,d),this.$target=a(this.options.target).on("scroll.bs.affix.data-api",a.proxy(this.checkPosition,this)).on("click.bs.affix.data-api",a.proxy(this.checkPositionWithEventLoop,this)),this.$element=a(b),this.affixed=null,this.unpin=null,this.pinnedOffset=null,this.checkPosition()};c.VERSION="3.3.6",c.RESET="affix affix-top affix-bottom",c.DEFAULTS={offset:0,target:window},c.prototype.getState=function(a,b,c,d){var e=this.$target.scrollTop(),f=this.$element.offset(),g=this.$target.height();if(null!=c&&"top"==this.affixed)return c>e?"top":!1;if("bottom"==this.affixed)return null!=c?e+this.unpin<=f.top?!1:"bottom":a-d>=e+g?!1:"bottom";var h=null==this.affixed,i=h?e:f.top,j=h?g:b;return null!=c&&c>=e?"top":null!=d&&i+j>=a-d?"bottom":!1},c.prototype.getPinnedOffset=function(){if(this.pinnedOffset)return this.pinnedOffset;this.$element.removeClass(c.RESET).addClass("affix");var a=this.$target.scrollTop(),b=this.$element.offset();return this.pinnedOffset=b.top-a},c.prototype.checkPositionWithEventLoop=function(){setTimeout(a.proxy(this.checkPosition,this),1)},c.prototype.checkPosition=function(){if(this.$element.is(":visible")){var b=this.$element.height(),d=this.options.offset,e=d.top,f=d.bottom,g=Math.max(a(document).height(),a(document.body).height());"object"!=typeof d&&(f=e=d),"function"==typeof e&&(e=d.top(this.$element)),"function"==typeof f&&(f=d.bottom(this.$element));var h=this.getState(g,b,e,f);if(this.affixed!=h){null!=this.unpin&&this.$element.css("top","");var i="affix"+(h?"-"+h:""),j=a.Event(i+".bs.affix");if(this.$element.trigger(j),j.isDefaultPrevented())return;this.affixed=h,this.unpin="bottom"==h?this.getPinnedOffset():null,this.$element.removeClass(c.RESET).addClass(i).trigger(i.replace("affix","affixed")+".bs.affix")}"bottom"==h&&this.$element.offset({top:g-b-f})}};var d=a.fn.affix;a.fn.affix=b,a.fn.affix.Constructor=c,a.fn.affix.noConflict=function(){return a.fn.affix=d,this},a(window).on("load",function(){a('[data-spy="affix"]').each(function(){var c=a(this),d=c.data();d.offset=d.offset||{},null!=d.offsetBottom&&(d.offset.bottom=d.offsetBottom),null!=d.offsetTop&&(d.offset.top=d.offsetTop),b.call(c,d)})})}(jQuery); \ No newline at end of file diff --git a/admin/static/js/common.js b/admin/static/js/common.js deleted file mode 100644 index 08eff2b1..00000000 --- a/admin/static/js/common.js +++ /dev/null @@ -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: ` -
    - - -
    - - - -
    - -
    -
    - -
    -
    - - -
    - ` - }); - -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); - }) diff --git a/admin/static/js/d3.v3.min.js b/admin/static/js/d3.v3.min.js deleted file mode 100644 index 16648730..00000000 --- a/admin/static/js/d3.v3.min.js +++ /dev/null @@ -1,5 +0,0 @@ -!function(){function n(n){return n&&(n.ownerDocument||n.document||n).documentElement}function t(n){return n&&(n.ownerDocument&&n.ownerDocument.defaultView||n.document&&n||n.defaultView)}function e(n,t){return t>n?-1:n>t?1:n>=t?0:NaN}function r(n){return null===n?NaN:+n}function i(n){return!isNaN(n)}function u(n){return{left:function(t,e,r,i){for(arguments.length<3&&(r=0),arguments.length<4&&(i=t.length);i>r;){var u=r+i>>>1;n(t[u],e)<0?r=u+1:i=u}return r},right:function(t,e,r,i){for(arguments.length<3&&(r=0),arguments.length<4&&(i=t.length);i>r;){var u=r+i>>>1;n(t[u],e)>0?i=u:r=u+1}return r}}}function o(n){return n.length}function a(n){for(var t=1;n*t%1;)t*=10;return t}function l(n,t){for(var e in t)Object.defineProperty(n.prototype,e,{value:t[e],enumerable:!1})}function c(){this._=Object.create(null)}function f(n){return(n+="")===bo||n[0]===_o?_o+n:n}function s(n){return(n+="")[0]===_o?n.slice(1):n}function h(n){return f(n)in this._}function p(n){return(n=f(n))in this._&&delete this._[n]}function g(){var n=[];for(var t in this._)n.push(s(t));return n}function v(){var n=0;for(var t in this._)++n;return n}function d(){for(var n in this._)return!1;return!0}function y(){this._=Object.create(null)}function m(n){return n}function M(n,t,e){return function(){var r=e.apply(t,arguments);return r===t?n:r}}function x(n,t){if(t in n)return t;t=t.charAt(0).toUpperCase()+t.slice(1);for(var e=0,r=wo.length;r>e;++e){var i=wo[e]+t;if(i in n)return i}}function b(){}function _(){}function w(n){function t(){for(var t,r=e,i=-1,u=r.length;++ie;e++)for(var i,u=n[e],o=0,a=u.length;a>o;o++)(i=u[o])&&t(i,o,e);return n}function Z(n){return ko(n,qo),n}function V(n){var t,e;return function(r,i,u){var o,a=n[u].update,l=a.length;for(u!=e&&(e=u,t=0),i>=t&&(t=i+1);!(o=a[t])&&++t0&&(n=n.slice(0,a));var c=To.get(n);return c&&(n=c,l=B),a?t?i:r:t?b:u}function $(n,t){return function(e){var r=ao.event;ao.event=e,t[0]=this.__data__;try{n.apply(this,t)}finally{ao.event=r}}}function B(n,t){var e=$(n,t);return function(n){var t=this,r=n.relatedTarget;r&&(r===t||8&r.compareDocumentPosition(t))||e.call(t,n)}}function W(e){var r=".dragsuppress-"+ ++Do,i="click"+r,u=ao.select(t(e)).on("touchmove"+r,S).on("dragstart"+r,S).on("selectstart"+r,S);if(null==Ro&&(Ro="onselectstart"in e?!1:x(e.style,"userSelect")),Ro){var o=n(e).style,a=o[Ro];o[Ro]="none"}return function(n){if(u.on(r,null),Ro&&(o[Ro]=a),n){var t=function(){u.on(i,null)};u.on(i,function(){S(),t()},!0),setTimeout(t,0)}}}function J(n,e){e.changedTouches&&(e=e.changedTouches[0]);var r=n.ownerSVGElement||n;if(r.createSVGPoint){var i=r.createSVGPoint();if(0>Po){var u=t(n);if(u.scrollX||u.scrollY){r=ao.select("body").append("svg").style({position:"absolute",top:0,left:0,margin:0,padding:0,border:"none"},"important");var o=r[0][0].getScreenCTM();Po=!(o.f||o.e),r.remove()}}return Po?(i.x=e.pageX,i.y=e.pageY):(i.x=e.clientX,i.y=e.clientY),i=i.matrixTransform(n.getScreenCTM().inverse()),[i.x,i.y]}var a=n.getBoundingClientRect();return[e.clientX-a.left-n.clientLeft,e.clientY-a.top-n.clientTop]}function G(){return ao.event.changedTouches[0].identifier}function K(n){return n>0?1:0>n?-1:0}function Q(n,t,e){return(t[0]-n[0])*(e[1]-n[1])-(t[1]-n[1])*(e[0]-n[0])}function nn(n){return n>1?0:-1>n?Fo:Math.acos(n)}function tn(n){return n>1?Io:-1>n?-Io:Math.asin(n)}function en(n){return((n=Math.exp(n))-1/n)/2}function rn(n){return((n=Math.exp(n))+1/n)/2}function un(n){return((n=Math.exp(2*n))-1)/(n+1)}function on(n){return(n=Math.sin(n/2))*n}function an(){}function ln(n,t,e){return this instanceof ln?(this.h=+n,this.s=+t,void(this.l=+e)):arguments.length<2?n instanceof ln?new ln(n.h,n.s,n.l):_n(""+n,wn,ln):new ln(n,t,e)}function cn(n,t,e){function r(n){return n>360?n-=360:0>n&&(n+=360),60>n?u+(o-u)*n/60:180>n?o:240>n?u+(o-u)*(240-n)/60:u}function i(n){return Math.round(255*r(n))}var u,o;return n=isNaN(n)?0:(n%=360)<0?n+360:n,t=isNaN(t)?0:0>t?0:t>1?1:t,e=0>e?0:e>1?1:e,o=.5>=e?e*(1+t):e+t-e*t,u=2*e-o,new mn(i(n+120),i(n),i(n-120))}function fn(n,t,e){return this instanceof fn?(this.h=+n,this.c=+t,void(this.l=+e)):arguments.length<2?n instanceof fn?new fn(n.h,n.c,n.l):n instanceof hn?gn(n.l,n.a,n.b):gn((n=Sn((n=ao.rgb(n)).r,n.g,n.b)).l,n.a,n.b):new fn(n,t,e)}function sn(n,t,e){return isNaN(n)&&(n=0),isNaN(t)&&(t=0),new hn(e,Math.cos(n*=Yo)*t,Math.sin(n)*t)}function hn(n,t,e){return this instanceof hn?(this.l=+n,this.a=+t,void(this.b=+e)):arguments.length<2?n instanceof hn?new hn(n.l,n.a,n.b):n instanceof fn?sn(n.h,n.c,n.l):Sn((n=mn(n)).r,n.g,n.b):new hn(n,t,e)}function pn(n,t,e){var r=(n+16)/116,i=r+t/500,u=r-e/200;return i=vn(i)*na,r=vn(r)*ta,u=vn(u)*ea,new mn(yn(3.2404542*i-1.5371385*r-.4985314*u),yn(-.969266*i+1.8760108*r+.041556*u),yn(.0556434*i-.2040259*r+1.0572252*u))}function gn(n,t,e){return n>0?new fn(Math.atan2(e,t)*Zo,Math.sqrt(t*t+e*e),n):new fn(NaN,NaN,n)}function vn(n){return n>.206893034?n*n*n:(n-4/29)/7.787037}function dn(n){return n>.008856?Math.pow(n,1/3):7.787037*n+4/29}function yn(n){return Math.round(255*(.00304>=n?12.92*n:1.055*Math.pow(n,1/2.4)-.055))}function mn(n,t,e){return this instanceof mn?(this.r=~~n,this.g=~~t,void(this.b=~~e)):arguments.length<2?n instanceof mn?new mn(n.r,n.g,n.b):_n(""+n,mn,cn):new mn(n,t,e)}function Mn(n){return new mn(n>>16,n>>8&255,255&n)}function xn(n){return Mn(n)+""}function bn(n){return 16>n?"0"+Math.max(0,n).toString(16):Math.min(255,n).toString(16)}function _n(n,t,e){var r,i,u,o=0,a=0,l=0;if(r=/([a-z]+)\((.*)\)/.exec(n=n.toLowerCase()))switch(i=r[2].split(","),r[1]){case"hsl":return e(parseFloat(i[0]),parseFloat(i[1])/100,parseFloat(i[2])/100);case"rgb":return t(Nn(i[0]),Nn(i[1]),Nn(i[2]))}return(u=ua.get(n))?t(u.r,u.g,u.b):(null==n||"#"!==n.charAt(0)||isNaN(u=parseInt(n.slice(1),16))||(4===n.length?(o=(3840&u)>>4,o=o>>4|o,a=240&u,a=a>>4|a,l=15&u,l=l<<4|l):7===n.length&&(o=(16711680&u)>>16,a=(65280&u)>>8,l=255&u)),t(o,a,l))}function wn(n,t,e){var r,i,u=Math.min(n/=255,t/=255,e/=255),o=Math.max(n,t,e),a=o-u,l=(o+u)/2;return a?(i=.5>l?a/(o+u):a/(2-o-u),r=n==o?(t-e)/a+(e>t?6:0):t==o?(e-n)/a+2:(n-t)/a+4,r*=60):(r=NaN,i=l>0&&1>l?0:r),new ln(r,i,l)}function Sn(n,t,e){n=kn(n),t=kn(t),e=kn(e);var r=dn((.4124564*n+.3575761*t+.1804375*e)/na),i=dn((.2126729*n+.7151522*t+.072175*e)/ta),u=dn((.0193339*n+.119192*t+.9503041*e)/ea);return hn(116*i-16,500*(r-i),200*(i-u))}function kn(n){return(n/=255)<=.04045?n/12.92:Math.pow((n+.055)/1.055,2.4)}function Nn(n){var t=parseFloat(n);return"%"===n.charAt(n.length-1)?Math.round(2.55*t):t}function En(n){return"function"==typeof n?n:function(){return n}}function An(n){return function(t,e,r){return 2===arguments.length&&"function"==typeof e&&(r=e,e=null),Cn(t,e,n,r)}}function Cn(n,t,e,r){function i(){var n,t=l.status;if(!t&&Ln(l)||t>=200&&300>t||304===t){try{n=e.call(u,l)}catch(r){return void o.error.call(u,r)}o.load.call(u,n)}else o.error.call(u,l)}var u={},o=ao.dispatch("beforesend","progress","load","error"),a={},l=new XMLHttpRequest,c=null;return!this.XDomainRequest||"withCredentials"in l||!/^(http(s)?:)?\/\//.test(n)||(l=new XDomainRequest),"onload"in l?l.onload=l.onerror=i:l.onreadystatechange=function(){l.readyState>3&&i()},l.onprogress=function(n){var t=ao.event;ao.event=n;try{o.progress.call(u,l)}finally{ao.event=t}},u.header=function(n,t){return n=(n+"").toLowerCase(),arguments.length<2?a[n]:(null==t?delete a[n]:a[n]=t+"",u)},u.mimeType=function(n){return arguments.length?(t=null==n?null:n+"",u):t},u.responseType=function(n){return arguments.length?(c=n,u):c},u.response=function(n){return e=n,u},["get","post"].forEach(function(n){u[n]=function(){return u.send.apply(u,[n].concat(co(arguments)))}}),u.send=function(e,r,i){if(2===arguments.length&&"function"==typeof r&&(i=r,r=null),l.open(e,n,!0),null==t||"accept"in a||(a.accept=t+",*/*"),l.setRequestHeader)for(var f in a)l.setRequestHeader(f,a[f]);return null!=t&&l.overrideMimeType&&l.overrideMimeType(t),null!=c&&(l.responseType=c),null!=i&&u.on("error",i).on("load",function(n){i(null,n)}),o.beforesend.call(u,l),l.send(null==r?null:r),u},u.abort=function(){return l.abort(),u},ao.rebind(u,o,"on"),null==r?u:u.get(zn(r))}function zn(n){return 1===n.length?function(t,e){n(null==t?e:null)}:n}function Ln(n){var t=n.responseType;return t&&"text"!==t?n.response:n.responseText}function qn(n,t,e){var r=arguments.length;2>r&&(t=0),3>r&&(e=Date.now());var i=e+t,u={c:n,t:i,n:null};return aa?aa.n=u:oa=u,aa=u,la||(ca=clearTimeout(ca),la=1,fa(Tn)),u}function Tn(){var n=Rn(),t=Dn()-n;t>24?(isFinite(t)&&(clearTimeout(ca),ca=setTimeout(Tn,t)),la=0):(la=1,fa(Tn))}function Rn(){for(var n=Date.now(),t=oa;t;)n>=t.t&&t.c(n-t.t)&&(t.c=null),t=t.n;return n}function Dn(){for(var n,t=oa,e=1/0;t;)t.c?(t.t8?function(n){return n/e}:function(n){return n*e},symbol:n}}function jn(n){var t=n.decimal,e=n.thousands,r=n.grouping,i=n.currency,u=r&&e?function(n,t){for(var i=n.length,u=[],o=0,a=r[0],l=0;i>0&&a>0&&(l+a+1>t&&(a=Math.max(1,t-l)),u.push(n.substring(i-=a,i+a)),!((l+=a+1)>t));)a=r[o=(o+1)%r.length];return u.reverse().join(e)}:m;return function(n){var e=ha.exec(n),r=e[1]||" ",o=e[2]||">",a=e[3]||"-",l=e[4]||"",c=e[5],f=+e[6],s=e[7],h=e[8],p=e[9],g=1,v="",d="",y=!1,m=!0;switch(h&&(h=+h.substring(1)),(c||"0"===r&&"="===o)&&(c=r="0",o="="),p){case"n":s=!0,p="g";break;case"%":g=100,d="%",p="f";break;case"p":g=100,d="%",p="r";break;case"b":case"o":case"x":case"X":"#"===l&&(v="0"+p.toLowerCase());case"c":m=!1;case"d":y=!0,h=0;break;case"s":g=-1,p="r"}"$"===l&&(v=i[0],d=i[1]),"r"!=p||h||(p="g"),null!=h&&("g"==p?h=Math.max(1,Math.min(21,h)):"e"!=p&&"f"!=p||(h=Math.max(0,Math.min(20,h)))),p=pa.get(p)||Fn;var M=c&&s;return function(n){var e=d;if(y&&n%1)return"";var i=0>n||0===n&&0>1/n?(n=-n,"-"):"-"===a?"":a;if(0>g){var l=ao.formatPrefix(n,h);n=l.scale(n),e=l.symbol+d}else n*=g;n=p(n,h);var x,b,_=n.lastIndexOf(".");if(0>_){var w=m?n.lastIndexOf("e"):-1;0>w?(x=n,b=""):(x=n.substring(0,w),b=n.substring(w))}else x=n.substring(0,_),b=t+n.substring(_+1);!c&&s&&(x=u(x,1/0));var S=v.length+x.length+b.length+(M?0:i.length),k=f>S?new Array(S=f-S+1).join(r):"";return M&&(x=u(k+x,k.length?f-b.length:1/0)),i+=v,n=x+b,("<"===o?i+n+k:">"===o?k+i+n:"^"===o?k.substring(0,S>>=1)+i+n+k.substring(S):i+(M?n:k+n))+e}}}function Fn(n){return n+""}function Hn(){this._=new Date(arguments.length>1?Date.UTC.apply(this,arguments):arguments[0])}function On(n,t,e){function r(t){var e=n(t),r=u(e,1);return r-t>t-e?e:r}function i(e){return t(e=n(new va(e-1)),1),e}function u(n,e){return t(n=new va(+n),e),n}function o(n,r,u){var o=i(n),a=[];if(u>1)for(;r>o;)e(o)%u||a.push(new Date(+o)),t(o,1);else for(;r>o;)a.push(new Date(+o)),t(o,1);return a}function a(n,t,e){try{va=Hn;var r=new Hn;return r._=n,o(r,t,e)}finally{va=Date}}n.floor=n,n.round=r,n.ceil=i,n.offset=u,n.range=o;var l=n.utc=In(n);return l.floor=l,l.round=In(r),l.ceil=In(i),l.offset=In(u),l.range=a,n}function In(n){return function(t,e){try{va=Hn;var r=new Hn;return r._=t,n(r,e)._}finally{va=Date}}}function Yn(n){function t(n){function t(t){for(var e,i,u,o=[],a=-1,l=0;++aa;){if(r>=c)return-1;if(i=t.charCodeAt(a++),37===i){if(o=t.charAt(a++),u=C[o in ya?t.charAt(a++):o],!u||(r=u(n,e,r))<0)return-1}else if(i!=e.charCodeAt(r++))return-1}return r}function r(n,t,e){_.lastIndex=0;var r=_.exec(t.slice(e));return r?(n.w=w.get(r[0].toLowerCase()),e+r[0].length):-1}function i(n,t,e){x.lastIndex=0;var r=x.exec(t.slice(e));return r?(n.w=b.get(r[0].toLowerCase()),e+r[0].length):-1}function u(n,t,e){N.lastIndex=0;var r=N.exec(t.slice(e));return r?(n.m=E.get(r[0].toLowerCase()),e+r[0].length):-1}function o(n,t,e){S.lastIndex=0;var r=S.exec(t.slice(e));return r?(n.m=k.get(r[0].toLowerCase()),e+r[0].length):-1}function a(n,t,r){return e(n,A.c.toString(),t,r)}function l(n,t,r){return e(n,A.x.toString(),t,r)}function c(n,t,r){return e(n,A.X.toString(),t,r)}function f(n,t,e){var r=M.get(t.slice(e,e+=2).toLowerCase());return null==r?-1:(n.p=r,e)}var s=n.dateTime,h=n.date,p=n.time,g=n.periods,v=n.days,d=n.shortDays,y=n.months,m=n.shortMonths;t.utc=function(n){function e(n){try{va=Hn;var t=new va;return t._=n,r(t)}finally{va=Date}}var r=t(n);return e.parse=function(n){try{va=Hn;var t=r.parse(n);return t&&t._}finally{va=Date}},e.toString=r.toString,e},t.multi=t.utc.multi=ct;var M=ao.map(),x=Vn(v),b=Xn(v),_=Vn(d),w=Xn(d),S=Vn(y),k=Xn(y),N=Vn(m),E=Xn(m);g.forEach(function(n,t){M.set(n.toLowerCase(),t)});var A={a:function(n){return d[n.getDay()]},A:function(n){return v[n.getDay()]},b:function(n){return m[n.getMonth()]},B:function(n){return y[n.getMonth()]},c:t(s),d:function(n,t){return Zn(n.getDate(),t,2)},e:function(n,t){return Zn(n.getDate(),t,2)},H:function(n,t){return Zn(n.getHours(),t,2)},I:function(n,t){return Zn(n.getHours()%12||12,t,2)},j:function(n,t){return Zn(1+ga.dayOfYear(n),t,3)},L:function(n,t){return Zn(n.getMilliseconds(),t,3)},m:function(n,t){return Zn(n.getMonth()+1,t,2)},M:function(n,t){return Zn(n.getMinutes(),t,2)},p:function(n){return g[+(n.getHours()>=12)]},S:function(n,t){return Zn(n.getSeconds(),t,2)},U:function(n,t){return Zn(ga.sundayOfYear(n),t,2)},w:function(n){return n.getDay()},W:function(n,t){return Zn(ga.mondayOfYear(n),t,2)},x:t(h),X:t(p),y:function(n,t){return Zn(n.getFullYear()%100,t,2)},Y:function(n,t){return Zn(n.getFullYear()%1e4,t,4)},Z:at,"%":function(){return"%"}},C={a:r,A:i,b:u,B:o,c:a,d:tt,e:tt,H:rt,I:rt,j:et,L:ot,m:nt,M:it,p:f,S:ut,U:Bn,w:$n,W:Wn,x:l,X:c,y:Gn,Y:Jn,Z:Kn,"%":lt};return t}function Zn(n,t,e){var r=0>n?"-":"",i=(r?-n:n)+"",u=i.length;return r+(e>u?new Array(e-u+1).join(t)+i:i)}function Vn(n){return new RegExp("^(?:"+n.map(ao.requote).join("|")+")","i")}function Xn(n){for(var t=new c,e=-1,r=n.length;++e68?1900:2e3)}function nt(n,t,e){ma.lastIndex=0;var r=ma.exec(t.slice(e,e+2));return r?(n.m=r[0]-1,e+r[0].length):-1}function tt(n,t,e){ma.lastIndex=0;var r=ma.exec(t.slice(e,e+2));return r?(n.d=+r[0],e+r[0].length):-1}function et(n,t,e){ma.lastIndex=0;var r=ma.exec(t.slice(e,e+3));return r?(n.j=+r[0],e+r[0].length):-1}function rt(n,t,e){ma.lastIndex=0;var r=ma.exec(t.slice(e,e+2));return r?(n.H=+r[0],e+r[0].length):-1}function it(n,t,e){ma.lastIndex=0;var r=ma.exec(t.slice(e,e+2));return r?(n.M=+r[0],e+r[0].length):-1}function ut(n,t,e){ma.lastIndex=0;var r=ma.exec(t.slice(e,e+2));return r?(n.S=+r[0],e+r[0].length):-1}function ot(n,t,e){ma.lastIndex=0;var r=ma.exec(t.slice(e,e+3));return r?(n.L=+r[0],e+r[0].length):-1}function at(n){var t=n.getTimezoneOffset(),e=t>0?"-":"+",r=xo(t)/60|0,i=xo(t)%60;return e+Zn(r,"0",2)+Zn(i,"0",2)}function lt(n,t,e){Ma.lastIndex=0;var r=Ma.exec(t.slice(e,e+1));return r?e+r[0].length:-1}function ct(n){for(var t=n.length,e=-1;++e=0?1:-1,a=o*e,l=Math.cos(t),c=Math.sin(t),f=u*c,s=i*l+f*Math.cos(a),h=f*o*Math.sin(a);ka.add(Math.atan2(h,s)),r=n,i=l,u=c}var t,e,r,i,u;Na.point=function(o,a){Na.point=n,r=(t=o)*Yo,i=Math.cos(a=(e=a)*Yo/2+Fo/4),u=Math.sin(a)},Na.lineEnd=function(){n(t,e)}}function dt(n){var t=n[0],e=n[1],r=Math.cos(e);return[r*Math.cos(t),r*Math.sin(t),Math.sin(e)]}function yt(n,t){return n[0]*t[0]+n[1]*t[1]+n[2]*t[2]}function mt(n,t){return[n[1]*t[2]-n[2]*t[1],n[2]*t[0]-n[0]*t[2],n[0]*t[1]-n[1]*t[0]]}function Mt(n,t){n[0]+=t[0],n[1]+=t[1],n[2]+=t[2]}function xt(n,t){return[n[0]*t,n[1]*t,n[2]*t]}function bt(n){var t=Math.sqrt(n[0]*n[0]+n[1]*n[1]+n[2]*n[2]);n[0]/=t,n[1]/=t,n[2]/=t}function _t(n){return[Math.atan2(n[1],n[0]),tn(n[2])]}function wt(n,t){return xo(n[0]-t[0])a;++a)i.point((e=n[a])[0],e[1]);return void i.lineEnd()}var l=new Tt(e,n,null,!0),c=new Tt(e,null,l,!1);l.o=c,u.push(l),o.push(c),l=new Tt(r,n,null,!1),c=new Tt(r,null,l,!0),l.o=c,u.push(l),o.push(c)}}),o.sort(t),qt(u),qt(o),u.length){for(var a=0,l=e,c=o.length;c>a;++a)o[a].e=l=!l;for(var f,s,h=u[0];;){for(var p=h,g=!0;p.v;)if((p=p.n)===h)return;f=p.z,i.lineStart();do{if(p.v=p.o.v=!0,p.e){if(g)for(var a=0,c=f.length;c>a;++a)i.point((s=f[a])[0],s[1]);else r(p.x,p.n.x,1,i);p=p.n}else{if(g){f=p.p.z;for(var a=f.length-1;a>=0;--a)i.point((s=f[a])[0],s[1])}else r(p.x,p.p.x,-1,i);p=p.p}p=p.o,f=p.z,g=!g}while(!p.v);i.lineEnd()}}}function qt(n){if(t=n.length){for(var t,e,r=0,i=n[0];++r0){for(b||(u.polygonStart(),b=!0),u.lineStart();++o1&&2&t&&e.push(e.pop().concat(e.shift())),p.push(e.filter(Dt))}var p,g,v,d=t(u),y=i.invert(r[0],r[1]),m={point:o,lineStart:l,lineEnd:c,polygonStart:function(){m.point=f,m.lineStart=s,m.lineEnd=h,p=[],g=[]},polygonEnd:function(){m.point=o,m.lineStart=l,m.lineEnd=c,p=ao.merge(p);var n=Ot(y,g);p.length?(b||(u.polygonStart(),b=!0),Lt(p,Ut,n,e,u)):n&&(b||(u.polygonStart(),b=!0),u.lineStart(),e(null,null,1,u),u.lineEnd()),b&&(u.polygonEnd(),b=!1),p=g=null},sphere:function(){u.polygonStart(),u.lineStart(),e(null,null,1,u),u.lineEnd(),u.polygonEnd()}},M=Pt(),x=t(M),b=!1;return m}}function Dt(n){return n.length>1}function Pt(){var n,t=[];return{lineStart:function(){t.push(n=[])},point:function(t,e){n.push([t,e])},lineEnd:b,buffer:function(){var e=t;return t=[],n=null,e},rejoin:function(){t.length>1&&t.push(t.pop().concat(t.shift()))}}}function Ut(n,t){return((n=n.x)[0]<0?n[1]-Io-Uo:Io-n[1])-((t=t.x)[0]<0?t[1]-Io-Uo:Io-t[1])}function jt(n){var t,e=NaN,r=NaN,i=NaN;return{lineStart:function(){n.lineStart(),t=1},point:function(u,o){var a=u>0?Fo:-Fo,l=xo(u-e);xo(l-Fo)0?Io:-Io),n.point(i,r),n.lineEnd(),n.lineStart(),n.point(a,r),n.point(u,r),t=0):i!==a&&l>=Fo&&(xo(e-i)Uo?Math.atan((Math.sin(t)*(u=Math.cos(r))*Math.sin(e)-Math.sin(r)*(i=Math.cos(t))*Math.sin(n))/(i*u*o)):(t+r)/2}function Ht(n,t,e,r){var i;if(null==n)i=e*Io,r.point(-Fo,i),r.point(0,i),r.point(Fo,i),r.point(Fo,0),r.point(Fo,-i),r.point(0,-i),r.point(-Fo,-i),r.point(-Fo,0),r.point(-Fo,i);else if(xo(n[0]-t[0])>Uo){var u=n[0]a;++a){var c=t[a],f=c.length;if(f)for(var s=c[0],h=s[0],p=s[1]/2+Fo/4,g=Math.sin(p),v=Math.cos(p),d=1;;){d===f&&(d=0),n=c[d];var y=n[0],m=n[1]/2+Fo/4,M=Math.sin(m),x=Math.cos(m),b=y-h,_=b>=0?1:-1,w=_*b,S=w>Fo,k=g*M;if(ka.add(Math.atan2(k*_*Math.sin(w),v*x+k*Math.cos(w))),u+=S?b+_*Ho:b,S^h>=e^y>=e){var N=mt(dt(s),dt(n));bt(N);var E=mt(i,N);bt(E);var A=(S^b>=0?-1:1)*tn(E[2]);(r>A||r===A&&(N[0]||N[1]))&&(o+=S^b>=0?1:-1)}if(!d++)break;h=y,g=M,v=x,s=n}}return(-Uo>u||Uo>u&&-Uo>ka)^1&o}function It(n){function t(n,t){return Math.cos(n)*Math.cos(t)>u}function e(n){var e,u,l,c,f;return{lineStart:function(){c=l=!1,f=1},point:function(s,h){var p,g=[s,h],v=t(s,h),d=o?v?0:i(s,h):v?i(s+(0>s?Fo:-Fo),h):0;if(!e&&(c=l=v)&&n.lineStart(),v!==l&&(p=r(e,g),(wt(e,p)||wt(g,p))&&(g[0]+=Uo,g[1]+=Uo,v=t(g[0],g[1]))),v!==l)f=0,v?(n.lineStart(),p=r(g,e),n.point(p[0],p[1])):(p=r(e,g),n.point(p[0],p[1]),n.lineEnd()),e=p;else if(a&&e&&o^v){var y;d&u||!(y=r(g,e,!0))||(f=0,o?(n.lineStart(),n.point(y[0][0],y[0][1]),n.point(y[1][0],y[1][1]),n.lineEnd()):(n.point(y[1][0],y[1][1]),n.lineEnd(),n.lineStart(),n.point(y[0][0],y[0][1])))}!v||e&&wt(e,g)||n.point(g[0],g[1]),e=g,l=v,u=d},lineEnd:function(){l&&n.lineEnd(),e=null},clean:function(){return f|(c&&l)<<1}}}function r(n,t,e){var r=dt(n),i=dt(t),o=[1,0,0],a=mt(r,i),l=yt(a,a),c=a[0],f=l-c*c;if(!f)return!e&&n;var s=u*l/f,h=-u*c/f,p=mt(o,a),g=xt(o,s),v=xt(a,h);Mt(g,v);var d=p,y=yt(g,d),m=yt(d,d),M=y*y-m*(yt(g,g)-1);if(!(0>M)){var x=Math.sqrt(M),b=xt(d,(-y-x)/m);if(Mt(b,g),b=_t(b),!e)return b;var _,w=n[0],S=t[0],k=n[1],N=t[1];w>S&&(_=w,w=S,S=_);var E=S-w,A=xo(E-Fo)E;if(!A&&k>N&&(_=k,k=N,N=_),C?A?k+N>0^b[1]<(xo(b[0]-w)Fo^(w<=b[0]&&b[0]<=S)){var z=xt(d,(-y+x)/m);return Mt(z,g),[b,_t(z)]}}}function i(t,e){var r=o?n:Fo-n,i=0;return-r>t?i|=1:t>r&&(i|=2),-r>e?i|=4:e>r&&(i|=8),i}var u=Math.cos(n),o=u>0,a=xo(u)>Uo,l=ve(n,6*Yo);return Rt(t,e,l,o?[0,-n]:[-Fo,n-Fo])}function Yt(n,t,e,r){return function(i){var u,o=i.a,a=i.b,l=o.x,c=o.y,f=a.x,s=a.y,h=0,p=1,g=f-l,v=s-c;if(u=n-l,g||!(u>0)){if(u/=g,0>g){if(h>u)return;p>u&&(p=u)}else if(g>0){if(u>p)return;u>h&&(h=u)}if(u=e-l,g||!(0>u)){if(u/=g,0>g){if(u>p)return;u>h&&(h=u)}else if(g>0){if(h>u)return;p>u&&(p=u)}if(u=t-c,v||!(u>0)){if(u/=v,0>v){if(h>u)return;p>u&&(p=u)}else if(v>0){if(u>p)return;u>h&&(h=u)}if(u=r-c,v||!(0>u)){if(u/=v,0>v){if(u>p)return;u>h&&(h=u)}else if(v>0){if(h>u)return;p>u&&(p=u)}return h>0&&(i.a={x:l+h*g,y:c+h*v}),1>p&&(i.b={x:l+p*g,y:c+p*v}),i}}}}}}function Zt(n,t,e,r){function i(r,i){return xo(r[0]-n)0?0:3:xo(r[0]-e)0?2:1:xo(r[1]-t)0?1:0:i>0?3:2}function u(n,t){return o(n.x,t.x)}function o(n,t){var e=i(n,1),r=i(t,1);return e!==r?e-r:0===e?t[1]-n[1]:1===e?n[0]-t[0]:2===e?n[1]-t[1]:t[0]-n[0]}return function(a){function l(n){for(var t=0,e=d.length,r=n[1],i=0;e>i;++i)for(var u,o=1,a=d[i],l=a.length,c=a[0];l>o;++o)u=a[o],c[1]<=r?u[1]>r&&Q(c,u,n)>0&&++t:u[1]<=r&&Q(c,u,n)<0&&--t,c=u;return 0!==t}function c(u,a,l,c){var f=0,s=0;if(null==u||(f=i(u,l))!==(s=i(a,l))||o(u,a)<0^l>0){do c.point(0===f||3===f?n:e,f>1?r:t);while((f=(f+l+4)%4)!==s)}else c.point(a[0],a[1])}function f(i,u){return i>=n&&e>=i&&u>=t&&r>=u}function s(n,t){f(n,t)&&a.point(n,t)}function h(){C.point=g,d&&d.push(y=[]),S=!0,w=!1,b=_=NaN}function p(){v&&(g(m,M),x&&w&&E.rejoin(),v.push(E.buffer())),C.point=s,w&&a.lineEnd()}function g(n,t){n=Math.max(-Ha,Math.min(Ha,n)),t=Math.max(-Ha,Math.min(Ha,t));var e=f(n,t);if(d&&y.push([n,t]),S)m=n,M=t,x=e,S=!1,e&&(a.lineStart(),a.point(n,t));else if(e&&w)a.point(n,t);else{var r={a:{x:b,y:_},b:{x:n,y:t}};A(r)?(w||(a.lineStart(),a.point(r.a.x,r.a.y)),a.point(r.b.x,r.b.y),e||a.lineEnd(),k=!1):e&&(a.lineStart(),a.point(n,t),k=!1)}b=n,_=t,w=e}var v,d,y,m,M,x,b,_,w,S,k,N=a,E=Pt(),A=Yt(n,t,e,r),C={point:s,lineStart:h,lineEnd:p,polygonStart:function(){a=E,v=[],d=[],k=!0},polygonEnd:function(){a=N,v=ao.merge(v);var t=l([n,r]),e=k&&t,i=v.length;(e||i)&&(a.polygonStart(),e&&(a.lineStart(),c(null,null,1,a),a.lineEnd()),i&&Lt(v,u,t,c,a),a.polygonEnd()),v=d=y=null}};return C}}function Vt(n){var t=0,e=Fo/3,r=ae(n),i=r(t,e);return i.parallels=function(n){return arguments.length?r(t=n[0]*Fo/180,e=n[1]*Fo/180):[t/Fo*180,e/Fo*180]},i}function Xt(n,t){function e(n,t){var e=Math.sqrt(u-2*i*Math.sin(t))/i;return[e*Math.sin(n*=i),o-e*Math.cos(n)]}var r=Math.sin(n),i=(r+Math.sin(t))/2,u=1+r*(2*i-r),o=Math.sqrt(u)/i;return e.invert=function(n,t){var e=o-t;return[Math.atan2(n,e)/i,tn((u-(n*n+e*e)*i*i)/(2*i))]},e}function $t(){function n(n,t){Ia+=i*n-r*t,r=n,i=t}var t,e,r,i;$a.point=function(u,o){$a.point=n,t=r=u,e=i=o},$a.lineEnd=function(){n(t,e)}}function Bt(n,t){Ya>n&&(Ya=n),n>Va&&(Va=n),Za>t&&(Za=t),t>Xa&&(Xa=t)}function Wt(){function n(n,t){o.push("M",n,",",t,u)}function t(n,t){o.push("M",n,",",t),a.point=e}function e(n,t){o.push("L",n,",",t)}function r(){a.point=n}function i(){o.push("Z")}var u=Jt(4.5),o=[],a={point:n,lineStart:function(){a.point=t},lineEnd:r,polygonStart:function(){a.lineEnd=i},polygonEnd:function(){a.lineEnd=r,a.point=n},pointRadius:function(n){return u=Jt(n),a},result:function(){if(o.length){var n=o.join("");return o=[],n}}};return a}function Jt(n){return"m0,"+n+"a"+n+","+n+" 0 1,1 0,"+-2*n+"a"+n+","+n+" 0 1,1 0,"+2*n+"z"}function Gt(n,t){Ca+=n,za+=t,++La}function Kt(){function n(n,r){var i=n-t,u=r-e,o=Math.sqrt(i*i+u*u);qa+=o*(t+n)/2,Ta+=o*(e+r)/2,Ra+=o,Gt(t=n,e=r)}var t,e;Wa.point=function(r,i){Wa.point=n,Gt(t=r,e=i)}}function Qt(){Wa.point=Gt}function ne(){function n(n,t){var e=n-r,u=t-i,o=Math.sqrt(e*e+u*u);qa+=o*(r+n)/2,Ta+=o*(i+t)/2,Ra+=o,o=i*n-r*t,Da+=o*(r+n),Pa+=o*(i+t),Ua+=3*o,Gt(r=n,i=t)}var t,e,r,i;Wa.point=function(u,o){Wa.point=n,Gt(t=r=u,e=i=o)},Wa.lineEnd=function(){n(t,e)}}function te(n){function t(t,e){n.moveTo(t+o,e),n.arc(t,e,o,0,Ho)}function e(t,e){n.moveTo(t,e),a.point=r}function r(t,e){n.lineTo(t,e)}function i(){a.point=t}function u(){n.closePath()}var o=4.5,a={point:t,lineStart:function(){a.point=e},lineEnd:i,polygonStart:function(){a.lineEnd=u},polygonEnd:function(){a.lineEnd=i,a.point=t},pointRadius:function(n){return o=n,a},result:b};return a}function ee(n){function t(n){return(a?r:e)(n)}function e(t){return ue(t,function(e,r){e=n(e,r),t.point(e[0],e[1])})}function r(t){function e(e,r){e=n(e,r),t.point(e[0],e[1])}function r(){M=NaN,S.point=u,t.lineStart()}function u(e,r){var u=dt([e,r]),o=n(e,r);i(M,x,m,b,_,w,M=o[0],x=o[1],m=e,b=u[0],_=u[1],w=u[2],a,t),t.point(M,x)}function o(){S.point=e,t.lineEnd()}function l(){ -r(),S.point=c,S.lineEnd=f}function c(n,t){u(s=n,h=t),p=M,g=x,v=b,d=_,y=w,S.point=u}function f(){i(M,x,m,b,_,w,p,g,s,v,d,y,a,t),S.lineEnd=o,o()}var s,h,p,g,v,d,y,m,M,x,b,_,w,S={point:e,lineStart:r,lineEnd:o,polygonStart:function(){t.polygonStart(),S.lineStart=l},polygonEnd:function(){t.polygonEnd(),S.lineStart=r}};return S}function i(t,e,r,a,l,c,f,s,h,p,g,v,d,y){var m=f-t,M=s-e,x=m*m+M*M;if(x>4*u&&d--){var b=a+p,_=l+g,w=c+v,S=Math.sqrt(b*b+_*_+w*w),k=Math.asin(w/=S),N=xo(xo(w)-1)u||xo((m*z+M*L)/x-.5)>.3||o>a*p+l*g+c*v)&&(i(t,e,r,a,l,c,A,C,N,b/=S,_/=S,w,d,y),y.point(A,C),i(A,C,N,b,_,w,f,s,h,p,g,v,d,y))}}var u=.5,o=Math.cos(30*Yo),a=16;return t.precision=function(n){return arguments.length?(a=(u=n*n)>0&&16,t):Math.sqrt(u)},t}function re(n){var t=ee(function(t,e){return n([t*Zo,e*Zo])});return function(n){return le(t(n))}}function ie(n){this.stream=n}function ue(n,t){return{point:t,sphere:function(){n.sphere()},lineStart:function(){n.lineStart()},lineEnd:function(){n.lineEnd()},polygonStart:function(){n.polygonStart()},polygonEnd:function(){n.polygonEnd()}}}function oe(n){return ae(function(){return n})()}function ae(n){function t(n){return n=a(n[0]*Yo,n[1]*Yo),[n[0]*h+l,c-n[1]*h]}function e(n){return n=a.invert((n[0]-l)/h,(c-n[1])/h),n&&[n[0]*Zo,n[1]*Zo]}function r(){a=Ct(o=se(y,M,x),u);var n=u(v,d);return l=p-n[0]*h,c=g+n[1]*h,i()}function i(){return f&&(f.valid=!1,f=null),t}var u,o,a,l,c,f,s=ee(function(n,t){return n=u(n,t),[n[0]*h+l,c-n[1]*h]}),h=150,p=480,g=250,v=0,d=0,y=0,M=0,x=0,b=Fa,_=m,w=null,S=null;return t.stream=function(n){return f&&(f.valid=!1),f=le(b(o,s(_(n)))),f.valid=!0,f},t.clipAngle=function(n){return arguments.length?(b=null==n?(w=n,Fa):It((w=+n)*Yo),i()):w},t.clipExtent=function(n){return arguments.length?(S=n,_=n?Zt(n[0][0],n[0][1],n[1][0],n[1][1]):m,i()):S},t.scale=function(n){return arguments.length?(h=+n,r()):h},t.translate=function(n){return arguments.length?(p=+n[0],g=+n[1],r()):[p,g]},t.center=function(n){return arguments.length?(v=n[0]%360*Yo,d=n[1]%360*Yo,r()):[v*Zo,d*Zo]},t.rotate=function(n){return arguments.length?(y=n[0]%360*Yo,M=n[1]%360*Yo,x=n.length>2?n[2]%360*Yo:0,r()):[y*Zo,M*Zo,x*Zo]},ao.rebind(t,s,"precision"),function(){return u=n.apply(this,arguments),t.invert=u.invert&&e,r()}}function le(n){return ue(n,function(t,e){n.point(t*Yo,e*Yo)})}function ce(n,t){return[n,t]}function fe(n,t){return[n>Fo?n-Ho:-Fo>n?n+Ho:n,t]}function se(n,t,e){return n?t||e?Ct(pe(n),ge(t,e)):pe(n):t||e?ge(t,e):fe}function he(n){return function(t,e){return t+=n,[t>Fo?t-Ho:-Fo>t?t+Ho:t,e]}}function pe(n){var t=he(n);return t.invert=he(-n),t}function ge(n,t){function e(n,t){var e=Math.cos(t),a=Math.cos(n)*e,l=Math.sin(n)*e,c=Math.sin(t),f=c*r+a*i;return[Math.atan2(l*u-f*o,a*r-c*i),tn(f*u+l*o)]}var r=Math.cos(n),i=Math.sin(n),u=Math.cos(t),o=Math.sin(t);return e.invert=function(n,t){var e=Math.cos(t),a=Math.cos(n)*e,l=Math.sin(n)*e,c=Math.sin(t),f=c*u-l*o;return[Math.atan2(l*u+c*o,a*r+f*i),tn(f*r-a*i)]},e}function ve(n,t){var e=Math.cos(n),r=Math.sin(n);return function(i,u,o,a){var l=o*t;null!=i?(i=de(e,i),u=de(e,u),(o>0?u>i:i>u)&&(i+=o*Ho)):(i=n+o*Ho,u=n-.5*l);for(var c,f=i;o>0?f>u:u>f;f-=l)a.point((c=_t([e,-r*Math.cos(f),-r*Math.sin(f)]))[0],c[1])}}function de(n,t){var e=dt(t);e[0]-=n,bt(e);var r=nn(-e[1]);return((-e[2]<0?-r:r)+2*Math.PI-Uo)%(2*Math.PI)}function ye(n,t,e){var r=ao.range(n,t-Uo,e).concat(t);return function(n){return r.map(function(t){return[n,t]})}}function me(n,t,e){var r=ao.range(n,t-Uo,e).concat(t);return function(n){return r.map(function(t){return[t,n]})}}function Me(n){return n.source}function xe(n){return n.target}function be(n,t,e,r){var i=Math.cos(t),u=Math.sin(t),o=Math.cos(r),a=Math.sin(r),l=i*Math.cos(n),c=i*Math.sin(n),f=o*Math.cos(e),s=o*Math.sin(e),h=2*Math.asin(Math.sqrt(on(r-t)+i*o*on(e-n))),p=1/Math.sin(h),g=h?function(n){var t=Math.sin(n*=h)*p,e=Math.sin(h-n)*p,r=e*l+t*f,i=e*c+t*s,o=e*u+t*a;return[Math.atan2(i,r)*Zo,Math.atan2(o,Math.sqrt(r*r+i*i))*Zo]}:function(){return[n*Zo,t*Zo]};return g.distance=h,g}function _e(){function n(n,i){var u=Math.sin(i*=Yo),o=Math.cos(i),a=xo((n*=Yo)-t),l=Math.cos(a);Ja+=Math.atan2(Math.sqrt((a=o*Math.sin(a))*a+(a=r*u-e*o*l)*a),e*u+r*o*l),t=n,e=u,r=o}var t,e,r;Ga.point=function(i,u){t=i*Yo,e=Math.sin(u*=Yo),r=Math.cos(u),Ga.point=n},Ga.lineEnd=function(){Ga.point=Ga.lineEnd=b}}function we(n,t){function e(t,e){var r=Math.cos(t),i=Math.cos(e),u=n(r*i);return[u*i*Math.sin(t),u*Math.sin(e)]}return e.invert=function(n,e){var r=Math.sqrt(n*n+e*e),i=t(r),u=Math.sin(i),o=Math.cos(i);return[Math.atan2(n*u,r*o),Math.asin(r&&e*u/r)]},e}function Se(n,t){function e(n,t){o>0?-Io+Uo>t&&(t=-Io+Uo):t>Io-Uo&&(t=Io-Uo);var e=o/Math.pow(i(t),u);return[e*Math.sin(u*n),o-e*Math.cos(u*n)]}var r=Math.cos(n),i=function(n){return Math.tan(Fo/4+n/2)},u=n===t?Math.sin(n):Math.log(r/Math.cos(t))/Math.log(i(t)/i(n)),o=r*Math.pow(i(n),u)/u;return u?(e.invert=function(n,t){var e=o-t,r=K(u)*Math.sqrt(n*n+e*e);return[Math.atan2(n,e)/u,2*Math.atan(Math.pow(o/r,1/u))-Io]},e):Ne}function ke(n,t){function e(n,t){var e=u-t;return[e*Math.sin(i*n),u-e*Math.cos(i*n)]}var r=Math.cos(n),i=n===t?Math.sin(n):(r-Math.cos(t))/(t-n),u=r/i+n;return xo(i)i;i++){for(;r>1&&Q(n[e[r-2]],n[e[r-1]],n[i])<=0;)--r;e[r++]=i}return e.slice(0,r)}function qe(n,t){return n[0]-t[0]||n[1]-t[1]}function Te(n,t,e){return(e[0]-t[0])*(n[1]-t[1])<(e[1]-t[1])*(n[0]-t[0])}function Re(n,t,e,r){var i=n[0],u=e[0],o=t[0]-i,a=r[0]-u,l=n[1],c=e[1],f=t[1]-l,s=r[1]-c,h=(a*(l-c)-s*(i-u))/(s*o-a*f);return[i+h*o,l+h*f]}function De(n){var t=n[0],e=n[n.length-1];return!(t[0]-e[0]||t[1]-e[1])}function Pe(){rr(this),this.edge=this.site=this.circle=null}function Ue(n){var t=cl.pop()||new Pe;return t.site=n,t}function je(n){Be(n),ol.remove(n),cl.push(n),rr(n)}function Fe(n){var t=n.circle,e=t.x,r=t.cy,i={x:e,y:r},u=n.P,o=n.N,a=[n];je(n);for(var l=u;l.circle&&xo(e-l.circle.x)f;++f)c=a[f],l=a[f-1],nr(c.edge,l.site,c.site,i);l=a[0],c=a[s-1],c.edge=Ke(l.site,c.site,null,i),$e(l),$e(c)}function He(n){for(var t,e,r,i,u=n.x,o=n.y,a=ol._;a;)if(r=Oe(a,o)-u,r>Uo)a=a.L;else{if(i=u-Ie(a,o),!(i>Uo)){r>-Uo?(t=a.P,e=a):i>-Uo?(t=a,e=a.N):t=e=a;break}if(!a.R){t=a;break}a=a.R}var l=Ue(n);if(ol.insert(t,l),t||e){if(t===e)return Be(t),e=Ue(t.site),ol.insert(l,e),l.edge=e.edge=Ke(t.site,l.site),$e(t),void $e(e);if(!e)return void(l.edge=Ke(t.site,l.site));Be(t),Be(e);var c=t.site,f=c.x,s=c.y,h=n.x-f,p=n.y-s,g=e.site,v=g.x-f,d=g.y-s,y=2*(h*d-p*v),m=h*h+p*p,M=v*v+d*d,x={x:(d*m-p*M)/y+f,y:(h*M-v*m)/y+s};nr(e.edge,c,g,x),l.edge=Ke(c,n,null,x),e.edge=Ke(n,g,null,x),$e(t),$e(e)}}function Oe(n,t){var e=n.site,r=e.x,i=e.y,u=i-t;if(!u)return r;var o=n.P;if(!o)return-(1/0);e=o.site;var a=e.x,l=e.y,c=l-t;if(!c)return a;var f=a-r,s=1/u-1/c,h=f/c;return s?(-h+Math.sqrt(h*h-2*s*(f*f/(-2*c)-l+c/2+i-u/2)))/s+r:(r+a)/2}function Ie(n,t){var e=n.N;if(e)return Oe(e,t);var r=n.site;return r.y===t?r.x:1/0}function Ye(n){this.site=n,this.edges=[]}function Ze(n){for(var t,e,r,i,u,o,a,l,c,f,s=n[0][0],h=n[1][0],p=n[0][1],g=n[1][1],v=ul,d=v.length;d--;)if(u=v[d],u&&u.prepare())for(a=u.edges,l=a.length,o=0;l>o;)f=a[o].end(),r=f.x,i=f.y,c=a[++o%l].start(),t=c.x,e=c.y,(xo(r-t)>Uo||xo(i-e)>Uo)&&(a.splice(o,0,new tr(Qe(u.site,f,xo(r-s)Uo?{x:s,y:xo(t-s)Uo?{x:xo(e-g)Uo?{x:h,y:xo(t-h)Uo?{x:xo(e-p)=-jo)){var p=l*l+c*c,g=f*f+s*s,v=(s*p-c*g)/h,d=(l*g-f*p)/h,s=d+a,y=fl.pop()||new Xe;y.arc=n,y.site=i,y.x=v+o,y.y=s+Math.sqrt(v*v+d*d),y.cy=s,n.circle=y;for(var m=null,M=ll._;M;)if(y.yd||d>=a)return;if(h>g){if(u){if(u.y>=c)return}else u={x:d,y:l};e={x:d,y:c}}else{if(u){if(u.yr||r>1)if(h>g){if(u){if(u.y>=c)return}else u={x:(l-i)/r,y:l};e={x:(c-i)/r,y:c}}else{if(u){if(u.yp){if(u){if(u.x>=a)return}else u={x:o,y:r*o+i};e={x:a,y:r*a+i}}else{if(u){if(u.xu||s>o||r>h||i>p)){if(g=n.point){var g,v=t-n.x,d=e-n.y,y=v*v+d*d;if(l>y){var m=Math.sqrt(l=y);r=t-m,i=e-m,u=t+m,o=e+m,a=g}}for(var M=n.nodes,x=.5*(f+h),b=.5*(s+p),_=t>=x,w=e>=b,S=w<<1|_,k=S+4;k>S;++S)if(n=M[3&S])switch(3&S){case 0:c(n,f,s,x,b);break;case 1:c(n,x,s,h,b);break;case 2:c(n,f,b,x,p);break;case 3:c(n,x,b,h,p)}}}(n,r,i,u,o),a}function vr(n,t){n=ao.rgb(n),t=ao.rgb(t);var e=n.r,r=n.g,i=n.b,u=t.r-e,o=t.g-r,a=t.b-i;return function(n){return"#"+bn(Math.round(e+u*n))+bn(Math.round(r+o*n))+bn(Math.round(i+a*n))}}function dr(n,t){var e,r={},i={};for(e in n)e in t?r[e]=Mr(n[e],t[e]):i[e]=n[e];for(e in t)e in n||(i[e]=t[e]);return function(n){for(e in r)i[e]=r[e](n);return i}}function yr(n,t){return n=+n,t=+t,function(e){return n*(1-e)+t*e}}function mr(n,t){var e,r,i,u=hl.lastIndex=pl.lastIndex=0,o=-1,a=[],l=[];for(n+="",t+="";(e=hl.exec(n))&&(r=pl.exec(t));)(i=r.index)>u&&(i=t.slice(u,i),a[o]?a[o]+=i:a[++o]=i),(e=e[0])===(r=r[0])?a[o]?a[o]+=r:a[++o]=r:(a[++o]=null,l.push({i:o,x:yr(e,r)})),u=pl.lastIndex;return ur;++r)a[(e=l[r]).i]=e.x(n);return a.join("")})}function Mr(n,t){for(var e,r=ao.interpolators.length;--r>=0&&!(e=ao.interpolators[r](n,t)););return e}function xr(n,t){var e,r=[],i=[],u=n.length,o=t.length,a=Math.min(n.length,t.length);for(e=0;a>e;++e)r.push(Mr(n[e],t[e]));for(;u>e;++e)i[e]=n[e];for(;o>e;++e)i[e]=t[e];return function(n){for(e=0;a>e;++e)i[e]=r[e](n);return i}}function br(n){return function(t){return 0>=t?0:t>=1?1:n(t)}}function _r(n){return function(t){return 1-n(1-t)}}function wr(n){return function(t){return.5*(.5>t?n(2*t):2-n(2-2*t))}}function Sr(n){return n*n}function kr(n){return n*n*n}function Nr(n){if(0>=n)return 0;if(n>=1)return 1;var t=n*n,e=t*n;return 4*(.5>n?e:3*(n-t)+e-.75)}function Er(n){return function(t){return Math.pow(t,n)}}function Ar(n){return 1-Math.cos(n*Io)}function Cr(n){return Math.pow(2,10*(n-1))}function zr(n){return 1-Math.sqrt(1-n*n)}function Lr(n,t){var e;return arguments.length<2&&(t=.45),arguments.length?e=t/Ho*Math.asin(1/n):(n=1,e=t/4),function(r){return 1+n*Math.pow(2,-10*r)*Math.sin((r-e)*Ho/t)}}function qr(n){return n||(n=1.70158),function(t){return t*t*((n+1)*t-n)}}function Tr(n){return 1/2.75>n?7.5625*n*n:2/2.75>n?7.5625*(n-=1.5/2.75)*n+.75:2.5/2.75>n?7.5625*(n-=2.25/2.75)*n+.9375:7.5625*(n-=2.625/2.75)*n+.984375}function Rr(n,t){n=ao.hcl(n),t=ao.hcl(t);var e=n.h,r=n.c,i=n.l,u=t.h-e,o=t.c-r,a=t.l-i;return isNaN(o)&&(o=0,r=isNaN(r)?t.c:r),isNaN(u)?(u=0,e=isNaN(e)?t.h:e):u>180?u-=360:-180>u&&(u+=360),function(n){return sn(e+u*n,r+o*n,i+a*n)+""}}function Dr(n,t){n=ao.hsl(n),t=ao.hsl(t);var e=n.h,r=n.s,i=n.l,u=t.h-e,o=t.s-r,a=t.l-i;return isNaN(o)&&(o=0,r=isNaN(r)?t.s:r),isNaN(u)?(u=0,e=isNaN(e)?t.h:e):u>180?u-=360:-180>u&&(u+=360),function(n){return cn(e+u*n,r+o*n,i+a*n)+""}}function Pr(n,t){n=ao.lab(n),t=ao.lab(t);var e=n.l,r=n.a,i=n.b,u=t.l-e,o=t.a-r,a=t.b-i;return function(n){return pn(e+u*n,r+o*n,i+a*n)+""}}function Ur(n,t){return t-=n,function(e){return Math.round(n+t*e)}}function jr(n){var t=[n.a,n.b],e=[n.c,n.d],r=Hr(t),i=Fr(t,e),u=Hr(Or(e,t,-i))||0;t[0]*e[1]180?t+=360:t-n>180&&(n+=360),r.push({i:e.push(Ir(e)+"rotate(",null,")")-2,x:yr(n,t)})):t&&e.push(Ir(e)+"rotate("+t+")")}function Vr(n,t,e,r){n!==t?r.push({i:e.push(Ir(e)+"skewX(",null,")")-2,x:yr(n,t)}):t&&e.push(Ir(e)+"skewX("+t+")")}function Xr(n,t,e,r){if(n[0]!==t[0]||n[1]!==t[1]){var i=e.push(Ir(e)+"scale(",null,",",null,")");r.push({i:i-4,x:yr(n[0],t[0])},{i:i-2,x:yr(n[1],t[1])})}else 1===t[0]&&1===t[1]||e.push(Ir(e)+"scale("+t+")")}function $r(n,t){var e=[],r=[];return n=ao.transform(n),t=ao.transform(t),Yr(n.translate,t.translate,e,r),Zr(n.rotate,t.rotate,e,r),Vr(n.skew,t.skew,e,r),Xr(n.scale,t.scale,e,r),n=t=null,function(n){for(var t,i=-1,u=r.length;++i=0;)e.push(i[r])}function oi(n,t){for(var e=[n],r=[];null!=(n=e.pop());)if(r.push(n),(u=n.children)&&(i=u.length))for(var i,u,o=-1;++oe;++e)(t=n[e][1])>i&&(r=e,i=t);return r}function yi(n){return n.reduce(mi,0)}function mi(n,t){return n+t[1]}function Mi(n,t){return xi(n,Math.ceil(Math.log(t.length)/Math.LN2+1))}function xi(n,t){for(var e=-1,r=+n[0],i=(n[1]-r)/t,u=[];++e<=t;)u[e]=i*e+r;return u}function bi(n){return[ao.min(n),ao.max(n)]}function _i(n,t){return n.value-t.value}function wi(n,t){var e=n._pack_next;n._pack_next=t,t._pack_prev=n,t._pack_next=e,e._pack_prev=t}function Si(n,t){n._pack_next=t,t._pack_prev=n}function ki(n,t){var e=t.x-n.x,r=t.y-n.y,i=n.r+t.r;return.999*i*i>e*e+r*r}function Ni(n){function t(n){f=Math.min(n.x-n.r,f),s=Math.max(n.x+n.r,s),h=Math.min(n.y-n.r,h),p=Math.max(n.y+n.r,p)}if((e=n.children)&&(c=e.length)){var e,r,i,u,o,a,l,c,f=1/0,s=-(1/0),h=1/0,p=-(1/0);if(e.forEach(Ei),r=e[0],r.x=-r.r,r.y=0,t(r),c>1&&(i=e[1],i.x=i.r,i.y=0,t(i),c>2))for(u=e[2],zi(r,i,u),t(u),wi(r,u),r._pack_prev=u,wi(u,i),i=r._pack_next,o=3;c>o;o++){zi(r,i,u=e[o]);var g=0,v=1,d=1;for(a=i._pack_next;a!==i;a=a._pack_next,v++)if(ki(a,u)){g=1;break}if(1==g)for(l=r._pack_prev;l!==a._pack_prev&&!ki(l,u);l=l._pack_prev,d++);g?(d>v||v==d&&i.ro;o++)u=e[o],u.x-=y,u.y-=m,M=Math.max(M,u.r+Math.sqrt(u.x*u.x+u.y*u.y));n.r=M,e.forEach(Ai)}}function Ei(n){n._pack_next=n._pack_prev=n}function Ai(n){delete n._pack_next,delete n._pack_prev}function Ci(n,t,e,r){var i=n.children;if(n.x=t+=r*n.x,n.y=e+=r*n.y,n.r*=r,i)for(var u=-1,o=i.length;++u=0;)t=i[u],t.z+=e,t.m+=e,e+=t.s+(r+=t.c)}function Pi(n,t,e){return n.a.parent===t.parent?n.a:e}function Ui(n){return 1+ao.max(n,function(n){return n.y})}function ji(n){return n.reduce(function(n,t){return n+t.x},0)/n.length}function Fi(n){var t=n.children;return t&&t.length?Fi(t[0]):n}function Hi(n){var t,e=n.children;return e&&(t=e.length)?Hi(e[t-1]):n}function Oi(n){return{x:n.x,y:n.y,dx:n.dx,dy:n.dy}}function Ii(n,t){var e=n.x+t[3],r=n.y+t[0],i=n.dx-t[1]-t[3],u=n.dy-t[0]-t[2];return 0>i&&(e+=i/2,i=0),0>u&&(r+=u/2,u=0),{x:e,y:r,dx:i,dy:u}}function Yi(n){var t=n[0],e=n[n.length-1];return e>t?[t,e]:[e,t]}function Zi(n){return n.rangeExtent?n.rangeExtent():Yi(n.range())}function Vi(n,t,e,r){var i=e(n[0],n[1]),u=r(t[0],t[1]);return function(n){return u(i(n))}}function Xi(n,t){var e,r=0,i=n.length-1,u=n[r],o=n[i];return u>o&&(e=r,r=i,i=e,e=u,u=o,o=e),n[r]=t.floor(u),n[i]=t.ceil(o),n}function $i(n){return n?{floor:function(t){return Math.floor(t/n)*n},ceil:function(t){return Math.ceil(t/n)*n}}:Sl}function Bi(n,t,e,r){var i=[],u=[],o=0,a=Math.min(n.length,t.length)-1;for(n[a]2?Bi:Vi,l=r?Wr:Br;return o=i(n,t,l,e),a=i(t,n,l,Mr),u}function u(n){return o(n)}var o,a;return u.invert=function(n){return a(n)},u.domain=function(t){return arguments.length?(n=t.map(Number),i()):n},u.range=function(n){return arguments.length?(t=n,i()):t},u.rangeRound=function(n){return u.range(n).interpolate(Ur)},u.clamp=function(n){return arguments.length?(r=n,i()):r},u.interpolate=function(n){return arguments.length?(e=n,i()):e},u.ticks=function(t){return Qi(n,t)},u.tickFormat=function(t,e){return nu(n,t,e)},u.nice=function(t){return Gi(n,t),i()},u.copy=function(){return Wi(n,t,e,r)},i()}function Ji(n,t){return ao.rebind(n,t,"range","rangeRound","interpolate","clamp")}function Gi(n,t){return Xi(n,$i(Ki(n,t)[2])),Xi(n,$i(Ki(n,t)[2])),n}function Ki(n,t){null==t&&(t=10);var e=Yi(n),r=e[1]-e[0],i=Math.pow(10,Math.floor(Math.log(r/t)/Math.LN10)),u=t/r*i;return.15>=u?i*=10:.35>=u?i*=5:.75>=u&&(i*=2),e[0]=Math.ceil(e[0]/i)*i,e[1]=Math.floor(e[1]/i)*i+.5*i,e[2]=i,e}function Qi(n,t){return ao.range.apply(ao,Ki(n,t))}function nu(n,t,e){var r=Ki(n,t);if(e){var i=ha.exec(e);if(i.shift(),"s"===i[8]){var u=ao.formatPrefix(Math.max(xo(r[0]),xo(r[1])));return i[7]||(i[7]="."+tu(u.scale(r[2]))),i[8]="f",e=ao.format(i.join("")),function(n){return e(u.scale(n))+u.symbol}}i[7]||(i[7]="."+eu(i[8],r)),e=i.join("")}else e=",."+tu(r[2])+"f";return ao.format(e)}function tu(n){return-Math.floor(Math.log(n)/Math.LN10+.01)}function eu(n,t){var e=tu(t[2]);return n in kl?Math.abs(e-tu(Math.max(xo(t[0]),xo(t[1]))))+ +("e"!==n):e-2*("%"===n)}function ru(n,t,e,r){function i(n){return(e?Math.log(0>n?0:n):-Math.log(n>0?0:-n))/Math.log(t)}function u(n){return e?Math.pow(t,n):-Math.pow(t,-n)}function o(t){return n(i(t))}return o.invert=function(t){return u(n.invert(t))},o.domain=function(t){return arguments.length?(e=t[0]>=0,n.domain((r=t.map(Number)).map(i)),o):r},o.base=function(e){return arguments.length?(t=+e,n.domain(r.map(i)),o):t},o.nice=function(){var t=Xi(r.map(i),e?Math:El);return n.domain(t),r=t.map(u),o},o.ticks=function(){var n=Yi(r),o=[],a=n[0],l=n[1],c=Math.floor(i(a)),f=Math.ceil(i(l)),s=t%1?2:t;if(isFinite(f-c)){if(e){for(;f>c;c++)for(var h=1;s>h;h++)o.push(u(c)*h);o.push(u(c))}else for(o.push(u(c));c++0;h--)o.push(u(c)*h);for(c=0;o[c]l;f--);o=o.slice(c,f)}return o},o.tickFormat=function(n,e){if(!arguments.length)return Nl;arguments.length<2?e=Nl:"function"!=typeof e&&(e=ao.format(e));var r=Math.max(1,t*n/o.ticks().length);return function(n){var o=n/u(Math.round(i(n)));return t-.5>o*t&&(o*=t),r>=o?e(n):""}},o.copy=function(){return ru(n.copy(),t,e,r)},Ji(o,n)}function iu(n,t,e){function r(t){return n(i(t))}var i=uu(t),u=uu(1/t);return r.invert=function(t){return u(n.invert(t))},r.domain=function(t){return arguments.length?(n.domain((e=t.map(Number)).map(i)),r):e},r.ticks=function(n){return Qi(e,n)},r.tickFormat=function(n,t){return nu(e,n,t)},r.nice=function(n){return r.domain(Gi(e,n))},r.exponent=function(o){return arguments.length?(i=uu(t=o),u=uu(1/t),n.domain(e.map(i)),r):t},r.copy=function(){return iu(n.copy(),t,e)},Ji(r,n)}function uu(n){return function(t){return 0>t?-Math.pow(-t,n):Math.pow(t,n)}}function ou(n,t){function e(e){return u[((i.get(e)||("range"===t.t?i.set(e,n.push(e)):NaN))-1)%u.length]}function r(t,e){return ao.range(n.length).map(function(n){return t+e*n})}var i,u,o;return e.domain=function(r){if(!arguments.length)return n;n=[],i=new c;for(var u,o=-1,a=r.length;++oe?[NaN,NaN]:[e>0?a[e-1]:n[0],et?NaN:t/u+n,[t,t+1/u]},r.copy=function(){return lu(n,t,e)},i()}function cu(n,t){function e(e){return e>=e?t[ao.bisect(n,e)]:void 0}return e.domain=function(t){return arguments.length?(n=t,e):n},e.range=function(n){return arguments.length?(t=n,e):t},e.invertExtent=function(e){return e=t.indexOf(e),[n[e-1],n[e]]},e.copy=function(){return cu(n,t)},e}function fu(n){function t(n){return+n}return t.invert=t,t.domain=t.range=function(e){return arguments.length?(n=e.map(t),t):n},t.ticks=function(t){return Qi(n,t)},t.tickFormat=function(t,e){return nu(n,t,e)},t.copy=function(){return fu(n)},t}function su(){return 0}function hu(n){return n.innerRadius}function pu(n){return n.outerRadius}function gu(n){return n.startAngle}function vu(n){return n.endAngle}function du(n){return n&&n.padAngle}function yu(n,t,e,r){return(n-e)*t-(t-r)*n>0?0:1}function mu(n,t,e,r,i){var u=n[0]-t[0],o=n[1]-t[1],a=(i?r:-r)/Math.sqrt(u*u+o*o),l=a*o,c=-a*u,f=n[0]+l,s=n[1]+c,h=t[0]+l,p=t[1]+c,g=(f+h)/2,v=(s+p)/2,d=h-f,y=p-s,m=d*d+y*y,M=e-r,x=f*p-h*s,b=(0>y?-1:1)*Math.sqrt(Math.max(0,M*M*m-x*x)),_=(x*y-d*b)/m,w=(-x*d-y*b)/m,S=(x*y+d*b)/m,k=(-x*d+y*b)/m,N=_-g,E=w-v,A=S-g,C=k-v;return N*N+E*E>A*A+C*C&&(_=S,w=k),[[_-l,w-c],[_*e/M,w*e/M]]}function Mu(n){function t(t){function o(){c.push("M",u(n(f),a))}for(var l,c=[],f=[],s=-1,h=t.length,p=En(e),g=En(r);++s1?n.join("L"):n+"Z"}function bu(n){return n.join("L")+"Z"}function _u(n){for(var t=0,e=n.length,r=n[0],i=[r[0],",",r[1]];++t1&&i.push("H",r[0]),i.join("")}function wu(n){for(var t=0,e=n.length,r=n[0],i=[r[0],",",r[1]];++t1){a=t[1],u=n[l],l++,r+="C"+(i[0]+o[0])+","+(i[1]+o[1])+","+(u[0]-a[0])+","+(u[1]-a[1])+","+u[0]+","+u[1];for(var c=2;c9&&(i=3*t/Math.sqrt(i),o[a]=i*e,o[a+1]=i*r));for(a=-1;++a<=l;)i=(n[Math.min(l,a+1)][0]-n[Math.max(0,a-1)][0])/(6*(1+o[a]*o[a])),u.push([i||0,o[a]*i||0]);return u}function Fu(n){return n.length<3?xu(n):n[0]+Au(n,ju(n))}function Hu(n){for(var t,e,r,i=-1,u=n.length;++i=t?o(n-t):void(f.c=o)}function o(e){var i=g.active,u=g[i];u&&(u.timer.c=null,u.timer.t=NaN,--g.count,delete g[i],u.event&&u.event.interrupt.call(n,n.__data__,u.index));for(var o in g)if(r>+o){var c=g[o];c.timer.c=null,c.timer.t=NaN,--g.count,delete g[o]}f.c=a,qn(function(){return f.c&&a(e||1)&&(f.c=null,f.t=NaN),1},0,l),g.active=r,v.event&&v.event.start.call(n,n.__data__,t),p=[],v.tween.forEach(function(e,r){(r=r.call(n,n.__data__,t))&&p.push(r)}),h=v.ease,s=v.duration}function a(i){for(var u=i/s,o=h(u),a=p.length;a>0;)p[--a].call(n,o);return u>=1?(v.event&&v.event.end.call(n,n.__data__,t),--g.count?delete g[r]:delete n[e],1):void 0}var l,f,s,h,p,g=n[e]||(n[e]={active:0,count:0}),v=g[r];v||(l=i.time,f=qn(u,0,l),v=g[r]={tween:new c,time:l,timer:f,delay:i.delay,duration:i.duration,ease:i.ease,index:t},i=null,++g.count)}function no(n,t,e){n.attr("transform",function(n){var r=t(n);return"translate("+(isFinite(r)?r:e(n))+",0)"})}function to(n,t,e){n.attr("transform",function(n){var r=t(n);return"translate(0,"+(isFinite(r)?r:e(n))+")"})}function eo(n){return n.toISOString()}function ro(n,t,e){function r(t){return n(t)}function i(n,e){var r=n[1]-n[0],i=r/e,u=ao.bisect(Kl,i);return u==Kl.length?[t.year,Ki(n.map(function(n){return n/31536e6}),e)[2]]:u?t[i/Kl[u-1]1?{floor:function(t){for(;e(t=n.floor(t));)t=io(t-1);return t},ceil:function(t){for(;e(t=n.ceil(t));)t=io(+t+1);return t}}:n))},r.ticks=function(n,t){var e=Yi(r.domain()),u=null==n?i(e,10):"number"==typeof n?i(e,n):!n.range&&[{range:n},t];return u&&(n=u[0],t=u[1]),n.range(e[0],io(+e[1]+1),1>t?1:t)},r.tickFormat=function(){return e},r.copy=function(){return ro(n.copy(),t,e)},Ji(r,n)}function io(n){return new Date(n)}function uo(n){return JSON.parse(n.responseText)}function oo(n){var t=fo.createRange();return t.selectNode(fo.body),t.createContextualFragment(n.responseText)}var ao={version:"3.5.17"},lo=[].slice,co=function(n){return lo.call(n)},fo=this.document;if(fo)try{co(fo.documentElement.childNodes)[0].nodeType}catch(so){co=function(n){for(var t=n.length,e=new Array(t);t--;)e[t]=n[t];return e}}if(Date.now||(Date.now=function(){return+new Date}),fo)try{fo.createElement("DIV").style.setProperty("opacity",0,"")}catch(ho){var po=this.Element.prototype,go=po.setAttribute,vo=po.setAttributeNS,yo=this.CSSStyleDeclaration.prototype,mo=yo.setProperty;po.setAttribute=function(n,t){go.call(this,n,t+"")},po.setAttributeNS=function(n,t,e){vo.call(this,n,t,e+"")},yo.setProperty=function(n,t,e){mo.call(this,n,t+"",e)}}ao.ascending=e,ao.descending=function(n,t){return n>t?-1:t>n?1:t>=n?0:NaN},ao.min=function(n,t){var e,r,i=-1,u=n.length;if(1===arguments.length){for(;++i=r){e=r;break}for(;++ir&&(e=r)}else{for(;++i=r){e=r;break}for(;++ir&&(e=r)}return e},ao.max=function(n,t){var e,r,i=-1,u=n.length;if(1===arguments.length){for(;++i=r){e=r;break}for(;++ie&&(e=r)}else{for(;++i=r){e=r;break}for(;++ie&&(e=r)}return e},ao.extent=function(n,t){var e,r,i,u=-1,o=n.length;if(1===arguments.length){for(;++u=r){e=i=r;break}for(;++ur&&(e=r),r>i&&(i=r))}else{for(;++u=r){e=i=r;break}for(;++ur&&(e=r),r>i&&(i=r))}return[e,i]},ao.sum=function(n,t){var e,r=0,u=n.length,o=-1;if(1===arguments.length)for(;++o1?l/(f-1):void 0},ao.deviation=function(){var n=ao.variance.apply(this,arguments);return n?Math.sqrt(n):n};var Mo=u(e);ao.bisectLeft=Mo.left,ao.bisect=ao.bisectRight=Mo.right,ao.bisector=function(n){return u(1===n.length?function(t,r){return e(n(t),r)}:n)},ao.shuffle=function(n,t,e){(u=arguments.length)<3&&(e=n.length,2>u&&(t=0));for(var r,i,u=e-t;u;)i=Math.random()*u--|0,r=n[u+t],n[u+t]=n[i+t],n[i+t]=r;return n},ao.permute=function(n,t){for(var e=t.length,r=new Array(e);e--;)r[e]=n[t[e]];return r},ao.pairs=function(n){for(var t,e=0,r=n.length-1,i=n[0],u=new Array(0>r?0:r);r>e;)u[e]=[t=i,i=n[++e]];return u},ao.transpose=function(n){if(!(i=n.length))return[];for(var t=-1,e=ao.min(n,o),r=new Array(e);++t=0;)for(r=n[i],t=r.length;--t>=0;)e[--o]=r[t];return e};var xo=Math.abs;ao.range=function(n,t,e){if(arguments.length<3&&(e=1,arguments.length<2&&(t=n,n=0)),(t-n)/e===1/0)throw new Error("infinite range");var r,i=[],u=a(xo(e)),o=-1;if(n*=u,t*=u,e*=u,0>e)for(;(r=n+e*++o)>t;)i.push(r/u);else for(;(r=n+e*++o)=u.length)return r?r.call(i,o):e?o.sort(e):o;for(var l,f,s,h,p=-1,g=o.length,v=u[a++],d=new c;++p=u.length)return n;var r=[],i=o[e++];return n.forEach(function(n,i){r.push({key:n,values:t(i,e)})}),i?r.sort(function(n,t){return i(n.key,t.key)}):r}var e,r,i={},u=[],o=[];return i.map=function(t,e){return n(e,t,0)},i.entries=function(e){return t(n(ao.map,e,0),0)},i.key=function(n){return u.push(n),i},i.sortKeys=function(n){return o[u.length-1]=n,i},i.sortValues=function(n){return e=n,i},i.rollup=function(n){return r=n,i},i},ao.set=function(n){var t=new y;if(n)for(var e=0,r=n.length;r>e;++e)t.add(n[e]);return t},l(y,{has:h,add:function(n){return this._[f(n+="")]=!0,n},remove:p,values:g,size:v,empty:d,forEach:function(n){for(var t in this._)n.call(this,s(t))}}),ao.behavior={},ao.rebind=function(n,t){for(var e,r=1,i=arguments.length;++r=0&&(r=n.slice(e+1),n=n.slice(0,e)),n)return arguments.length<2?this[n].on(r):this[n].on(r,t);if(2===arguments.length){if(null==t)for(n in this)this.hasOwnProperty(n)&&this[n].on(r,null);return this}},ao.event=null,ao.requote=function(n){return n.replace(So,"\\$&")};var So=/[\\\^\$\*\+\?\|\[\]\(\)\.\{\}]/g,ko={}.__proto__?function(n,t){n.__proto__=t}:function(n,t){for(var e in t)n[e]=t[e]},No=function(n,t){return t.querySelector(n)},Eo=function(n,t){return t.querySelectorAll(n)},Ao=function(n,t){var e=n.matches||n[x(n,"matchesSelector")];return(Ao=function(n,t){return e.call(n,t)})(n,t)};"function"==typeof Sizzle&&(No=function(n,t){return Sizzle(n,t)[0]||null},Eo=Sizzle,Ao=Sizzle.matchesSelector),ao.selection=function(){return ao.select(fo.documentElement)};var Co=ao.selection.prototype=[];Co.select=function(n){var t,e,r,i,u=[];n=A(n);for(var o=-1,a=this.length;++o=0&&"xmlns"!==(e=n.slice(0,t))&&(n=n.slice(t+1)),Lo.hasOwnProperty(e)?{space:Lo[e],local:n}:n}},Co.attr=function(n,t){if(arguments.length<2){if("string"==typeof n){var e=this.node();return n=ao.ns.qualify(n),n.local?e.getAttributeNS(n.space,n.local):e.getAttribute(n)}for(t in n)this.each(z(t,n[t]));return this}return this.each(z(n,t))},Co.classed=function(n,t){if(arguments.length<2){if("string"==typeof n){var e=this.node(),r=(n=T(n)).length,i=-1;if(t=e.classList){for(;++ii){if("string"!=typeof n){2>i&&(e="");for(r in n)this.each(P(r,n[r],e));return this}if(2>i){var u=this.node();return t(u).getComputedStyle(u,null).getPropertyValue(n)}r=""}return this.each(P(n,e,r))},Co.property=function(n,t){if(arguments.length<2){if("string"==typeof n)return this.node()[n];for(t in n)this.each(U(t,n[t]));return this}return this.each(U(n,t))},Co.text=function(n){return arguments.length?this.each("function"==typeof n?function(){var t=n.apply(this,arguments);this.textContent=null==t?"":t}:null==n?function(){this.textContent=""}:function(){this.textContent=n}):this.node().textContent},Co.html=function(n){return arguments.length?this.each("function"==typeof n?function(){var t=n.apply(this,arguments);this.innerHTML=null==t?"":t}:null==n?function(){this.innerHTML=""}:function(){this.innerHTML=n}):this.node().innerHTML},Co.append=function(n){return n=j(n),this.select(function(){return this.appendChild(n.apply(this,arguments))})},Co.insert=function(n,t){return n=j(n),t=A(t),this.select(function(){return this.insertBefore(n.apply(this,arguments),t.apply(this,arguments)||null)})},Co.remove=function(){return this.each(F)},Co.data=function(n,t){function e(n,e){var r,i,u,o=n.length,s=e.length,h=Math.min(o,s),p=new Array(s),g=new Array(s),v=new Array(o);if(t){var d,y=new c,m=new Array(o);for(r=-1;++rr;++r)g[r]=H(e[r]);for(;o>r;++r)v[r]=n[r]}g.update=p,g.parentNode=p.parentNode=v.parentNode=n.parentNode,a.push(g),l.push(p),f.push(v)}var r,i,u=-1,o=this.length;if(!arguments.length){for(n=new Array(o=(r=this[0]).length);++uu;u++){i.push(t=[]),t.parentNode=(e=this[u]).parentNode;for(var a=0,l=e.length;l>a;a++)(r=e[a])&&n.call(r,r.__data__,a,u)&&t.push(r)}return E(i)},Co.order=function(){for(var n=-1,t=this.length;++n=0;)(e=r[i])&&(u&&u!==e.nextSibling&&u.parentNode.insertBefore(e,u),u=e);return this},Co.sort=function(n){n=I.apply(this,arguments);for(var t=-1,e=this.length;++tn;n++)for(var e=this[n],r=0,i=e.length;i>r;r++){var u=e[r];if(u)return u}return null},Co.size=function(){var n=0;return Y(this,function(){++n}),n};var qo=[];ao.selection.enter=Z,ao.selection.enter.prototype=qo,qo.append=Co.append,qo.empty=Co.empty,qo.node=Co.node,qo.call=Co.call,qo.size=Co.size,qo.select=function(n){for(var t,e,r,i,u,o=[],a=-1,l=this.length;++ar){if("string"!=typeof n){2>r&&(t=!1);for(e in n)this.each(X(e,n[e],t));return this}if(2>r)return(r=this.node()["__on"+n])&&r._;e=!1}return this.each(X(n,t,e))};var To=ao.map({mouseenter:"mouseover",mouseleave:"mouseout"});fo&&To.forEach(function(n){"on"+n in fo&&To.remove(n)});var Ro,Do=0;ao.mouse=function(n){return J(n,k())};var Po=this.navigator&&/WebKit/.test(this.navigator.userAgent)?-1:0;ao.touch=function(n,t,e){if(arguments.length<3&&(e=t,t=k().changedTouches),t)for(var r,i=0,u=t.length;u>i;++i)if((r=t[i]).identifier===e)return J(n,r)},ao.behavior.drag=function(){function n(){this.on("mousedown.drag",u).on("touchstart.drag",o)}function e(n,t,e,u,o){return function(){function a(){var n,e,r=t(h,v);r&&(n=r[0]-M[0],e=r[1]-M[1],g|=n|e,M=r,p({type:"drag",x:r[0]+c[0],y:r[1]+c[1],dx:n,dy:e}))}function l(){t(h,v)&&(y.on(u+d,null).on(o+d,null),m(g),p({type:"dragend"}))}var c,f=this,s=ao.event.target.correspondingElement||ao.event.target,h=f.parentNode,p=r.of(f,arguments),g=0,v=n(),d=".drag"+(null==v?"":"-"+v),y=ao.select(e(s)).on(u+d,a).on(o+d,l),m=W(s),M=t(h,v);i?(c=i.apply(f,arguments),c=[c.x-M[0],c.y-M[1]]):c=[0,0],p({type:"dragstart"})}}var r=N(n,"drag","dragstart","dragend"),i=null,u=e(b,ao.mouse,t,"mousemove","mouseup"),o=e(G,ao.touch,m,"touchmove","touchend");return n.origin=function(t){return arguments.length?(i=t,n):i},ao.rebind(n,r,"on")},ao.touches=function(n,t){return arguments.length<2&&(t=k().touches),t?co(t).map(function(t){var e=J(n,t);return e.identifier=t.identifier,e}):[]};var Uo=1e-6,jo=Uo*Uo,Fo=Math.PI,Ho=2*Fo,Oo=Ho-Uo,Io=Fo/2,Yo=Fo/180,Zo=180/Fo,Vo=Math.SQRT2,Xo=2,$o=4;ao.interpolateZoom=function(n,t){var e,r,i=n[0],u=n[1],o=n[2],a=t[0],l=t[1],c=t[2],f=a-i,s=l-u,h=f*f+s*s;if(jo>h)r=Math.log(c/o)/Vo,e=function(n){return[i+n*f,u+n*s,o*Math.exp(Vo*n*r)]};else{var p=Math.sqrt(h),g=(c*c-o*o+$o*h)/(2*o*Xo*p),v=(c*c-o*o-$o*h)/(2*c*Xo*p),d=Math.log(Math.sqrt(g*g+1)-g),y=Math.log(Math.sqrt(v*v+1)-v);r=(y-d)/Vo,e=function(n){var t=n*r,e=rn(d),a=o/(Xo*p)*(e*un(Vo*t+d)-en(d));return[i+a*f,u+a*s,o*e/rn(Vo*t+d)]}}return e.duration=1e3*r,e},ao.behavior.zoom=function(){function n(n){n.on(L,s).on(Wo+".zoom",p).on("dblclick.zoom",g).on(R,h)}function e(n){return[(n[0]-k.x)/k.k,(n[1]-k.y)/k.k]}function r(n){return[n[0]*k.k+k.x,n[1]*k.k+k.y]}function i(n){k.k=Math.max(A[0],Math.min(A[1],n))}function u(n,t){t=r(t),k.x+=n[0]-t[0],k.y+=n[1]-t[1]}function o(t,e,r,o){t.__chart__={x:k.x,y:k.y,k:k.k},i(Math.pow(2,o)),u(d=e,r),t=ao.select(t),C>0&&(t=t.transition().duration(C)),t.call(n.event)}function a(){b&&b.domain(x.range().map(function(n){return(n-k.x)/k.k}).map(x.invert)),w&&w.domain(_.range().map(function(n){return(n-k.y)/k.k}).map(_.invert))}function l(n){z++||n({type:"zoomstart"})}function c(n){a(),n({type:"zoom",scale:k.k,translate:[k.x,k.y]})}function f(n){--z||(n({type:"zoomend"}),d=null)}function s(){function n(){a=1,u(ao.mouse(i),h),c(o)}function r(){s.on(q,null).on(T,null),p(a),f(o)}var i=this,o=D.of(i,arguments),a=0,s=ao.select(t(i)).on(q,n).on(T,r),h=e(ao.mouse(i)),p=W(i);Il.call(i),l(o)}function h(){function n(){var n=ao.touches(g);return p=k.k,n.forEach(function(n){n.identifier in d&&(d[n.identifier]=e(n))}),n}function t(){var t=ao.event.target;ao.select(t).on(x,r).on(b,a),_.push(t);for(var e=ao.event.changedTouches,i=0,u=e.length;u>i;++i)d[e[i].identifier]=null;var l=n(),c=Date.now();if(1===l.length){if(500>c-M){var f=l[0];o(g,f,d[f.identifier],Math.floor(Math.log(k.k)/Math.LN2)+1),S()}M=c}else if(l.length>1){var f=l[0],s=l[1],h=f[0]-s[0],p=f[1]-s[1];y=h*h+p*p}}function r(){var n,t,e,r,o=ao.touches(g);Il.call(g);for(var a=0,l=o.length;l>a;++a,r=null)if(e=o[a],r=d[e.identifier]){if(t)break;n=e,t=r}if(r){var f=(f=e[0]-n[0])*f+(f=e[1]-n[1])*f,s=y&&Math.sqrt(f/y);n=[(n[0]+e[0])/2,(n[1]+e[1])/2],t=[(t[0]+r[0])/2,(t[1]+r[1])/2],i(s*p)}M=null,u(n,t),c(v)}function a(){if(ao.event.touches.length){for(var t=ao.event.changedTouches,e=0,r=t.length;r>e;++e)delete d[t[e].identifier];for(var i in d)return void n()}ao.selectAll(_).on(m,null),w.on(L,s).on(R,h),N(),f(v)}var p,g=this,v=D.of(g,arguments),d={},y=0,m=".zoom-"+ao.event.changedTouches[0].identifier,x="touchmove"+m,b="touchend"+m,_=[],w=ao.select(g),N=W(g);t(),l(v),w.on(L,null).on(R,t)}function p(){var n=D.of(this,arguments);m?clearTimeout(m):(Il.call(this),v=e(d=y||ao.mouse(this)),l(n)),m=setTimeout(function(){m=null,f(n)},50),S(),i(Math.pow(2,.002*Bo())*k.k),u(d,v),c(n)}function g(){var n=ao.mouse(this),t=Math.log(k.k)/Math.LN2;o(this,n,e(n),ao.event.shiftKey?Math.ceil(t)-1:Math.floor(t)+1)}var v,d,y,m,M,x,b,_,w,k={x:0,y:0,k:1},E=[960,500],A=Jo,C=250,z=0,L="mousedown.zoom",q="mousemove.zoom",T="mouseup.zoom",R="touchstart.zoom",D=N(n,"zoomstart","zoom","zoomend");return Wo||(Wo="onwheel"in fo?(Bo=function(){return-ao.event.deltaY*(ao.event.deltaMode?120:1)},"wheel"):"onmousewheel"in fo?(Bo=function(){return ao.event.wheelDelta},"mousewheel"):(Bo=function(){return-ao.event.detail},"MozMousePixelScroll")),n.event=function(n){n.each(function(){var n=D.of(this,arguments),t=k;Hl?ao.select(this).transition().each("start.zoom",function(){k=this.__chart__||{x:0,y:0,k:1},l(n)}).tween("zoom:zoom",function(){var e=E[0],r=E[1],i=d?d[0]:e/2,u=d?d[1]:r/2,o=ao.interpolateZoom([(i-k.x)/k.k,(u-k.y)/k.k,e/k.k],[(i-t.x)/t.k,(u-t.y)/t.k,e/t.k]);return function(t){var r=o(t),a=e/r[2];this.__chart__=k={x:i-r[0]*a,y:u-r[1]*a,k:a},c(n)}}).each("interrupt.zoom",function(){f(n)}).each("end.zoom",function(){f(n)}):(this.__chart__=k,l(n),c(n),f(n))})},n.translate=function(t){return arguments.length?(k={x:+t[0],y:+t[1],k:k.k},a(),n):[k.x,k.y]},n.scale=function(t){return arguments.length?(k={x:k.x,y:k.y,k:null},i(+t),a(),n):k.k},n.scaleExtent=function(t){return arguments.length?(A=null==t?Jo:[+t[0],+t[1]],n):A},n.center=function(t){return arguments.length?(y=t&&[+t[0],+t[1]],n):y},n.size=function(t){return arguments.length?(E=t&&[+t[0],+t[1]],n):E},n.duration=function(t){return arguments.length?(C=+t,n):C},n.x=function(t){return arguments.length?(b=t,x=t.copy(),k={x:0,y:0,k:1},n):b},n.y=function(t){return arguments.length?(w=t,_=t.copy(),k={x:0,y:0,k:1},n):w},ao.rebind(n,D,"on")};var Bo,Wo,Jo=[0,1/0];ao.color=an,an.prototype.toString=function(){return this.rgb()+""},ao.hsl=ln;var Go=ln.prototype=new an;Go.brighter=function(n){return n=Math.pow(.7,arguments.length?n:1),new ln(this.h,this.s,this.l/n)},Go.darker=function(n){return n=Math.pow(.7,arguments.length?n:1),new ln(this.h,this.s,n*this.l)},Go.rgb=function(){return cn(this.h,this.s,this.l)},ao.hcl=fn;var Ko=fn.prototype=new an;Ko.brighter=function(n){return new fn(this.h,this.c,Math.min(100,this.l+Qo*(arguments.length?n:1)))},Ko.darker=function(n){return new fn(this.h,this.c,Math.max(0,this.l-Qo*(arguments.length?n:1)))},Ko.rgb=function(){return sn(this.h,this.c,this.l).rgb()},ao.lab=hn;var Qo=18,na=.95047,ta=1,ea=1.08883,ra=hn.prototype=new an;ra.brighter=function(n){return new hn(Math.min(100,this.l+Qo*(arguments.length?n:1)),this.a,this.b)},ra.darker=function(n){return new hn(Math.max(0,this.l-Qo*(arguments.length?n:1)),this.a,this.b)},ra.rgb=function(){return pn(this.l,this.a,this.b)},ao.rgb=mn;var ia=mn.prototype=new an;ia.brighter=function(n){n=Math.pow(.7,arguments.length?n:1);var t=this.r,e=this.g,r=this.b,i=30;return t||e||r?(t&&i>t&&(t=i),e&&i>e&&(e=i),r&&i>r&&(r=i),new mn(Math.min(255,t/n),Math.min(255,e/n),Math.min(255,r/n))):new mn(i,i,i)},ia.darker=function(n){return n=Math.pow(.7,arguments.length?n:1),new mn(n*this.r,n*this.g,n*this.b)},ia.hsl=function(){return wn(this.r,this.g,this.b)},ia.toString=function(){return"#"+bn(this.r)+bn(this.g)+bn(this.b)};var ua=ao.map({aliceblue:15792383,antiquewhite:16444375,aqua:65535,aquamarine:8388564,azure:15794175,beige:16119260,bisque:16770244,black:0,blanchedalmond:16772045,blue:255,blueviolet:9055202,brown:10824234,burlywood:14596231,cadetblue:6266528,chartreuse:8388352,chocolate:13789470,coral:16744272,cornflowerblue:6591981,cornsilk:16775388,crimson:14423100,cyan:65535,darkblue:139,darkcyan:35723,darkgoldenrod:12092939,darkgray:11119017,darkgreen:25600,darkgrey:11119017,darkkhaki:12433259,darkmagenta:9109643,darkolivegreen:5597999,darkorange:16747520,darkorchid:10040012,darkred:9109504,darksalmon:15308410,darkseagreen:9419919,darkslateblue:4734347,darkslategray:3100495,darkslategrey:3100495,darkturquoise:52945,darkviolet:9699539,deeppink:16716947,deepskyblue:49151,dimgray:6908265,dimgrey:6908265,dodgerblue:2003199,firebrick:11674146,floralwhite:16775920,forestgreen:2263842,fuchsia:16711935,gainsboro:14474460,ghostwhite:16316671,gold:16766720,goldenrod:14329120,gray:8421504,green:32768,greenyellow:11403055,grey:8421504,honeydew:15794160,hotpink:16738740,indianred:13458524,indigo:4915330,ivory:16777200,khaki:15787660,lavender:15132410,lavenderblush:16773365,lawngreen:8190976,lemonchiffon:16775885,lightblue:11393254,lightcoral:15761536,lightcyan:14745599,lightgoldenrodyellow:16448210,lightgray:13882323,lightgreen:9498256,lightgrey:13882323,lightpink:16758465,lightsalmon:16752762,lightseagreen:2142890,lightskyblue:8900346,lightslategray:7833753,lightslategrey:7833753,lightsteelblue:11584734,lightyellow:16777184,lime:65280,limegreen:3329330,linen:16445670,magenta:16711935,maroon:8388608,mediumaquamarine:6737322,mediumblue:205,mediumorchid:12211667,mediumpurple:9662683,mediumseagreen:3978097,mediumslateblue:8087790,mediumspringgreen:64154,mediumturquoise:4772300,mediumvioletred:13047173,midnightblue:1644912,mintcream:16121850,mistyrose:16770273,moccasin:16770229,navajowhite:16768685,navy:128,oldlace:16643558,olive:8421376,olivedrab:7048739,orange:16753920,orangered:16729344,orchid:14315734,palegoldenrod:15657130,palegreen:10025880,paleturquoise:11529966,palevioletred:14381203,papayawhip:16773077,peachpuff:16767673,peru:13468991,pink:16761035,plum:14524637,powderblue:11591910,purple:8388736,rebeccapurple:6697881,red:16711680,rosybrown:12357519,royalblue:4286945,saddlebrown:9127187,salmon:16416882,sandybrown:16032864,seagreen:3050327,seashell:16774638,sienna:10506797,silver:12632256,skyblue:8900331,slateblue:6970061,slategray:7372944,slategrey:7372944,snow:16775930,springgreen:65407,steelblue:4620980,tan:13808780,teal:32896,thistle:14204888,tomato:16737095,turquoise:4251856,violet:15631086,wheat:16113331,white:16777215,whitesmoke:16119285,yellow:16776960,yellowgreen:10145074});ua.forEach(function(n,t){ua.set(n,Mn(t))}),ao.functor=En,ao.xhr=An(m),ao.dsv=function(n,t){function e(n,e,u){arguments.length<3&&(u=e,e=null);var o=Cn(n,t,null==e?r:i(e),u);return o.row=function(n){return arguments.length?o.response(null==(e=n)?r:i(n)):e},o}function r(n){return e.parse(n.responseText)}function i(n){return function(t){return e.parse(t.responseText,n)}}function u(t){return t.map(o).join(n)}function o(n){return a.test(n)?'"'+n.replace(/\"/g,'""')+'"':n}var a=new RegExp('["'+n+"\n]"),l=n.charCodeAt(0);return e.parse=function(n,t){var r;return e.parseRows(n,function(n,e){if(r)return r(n,e-1);var i=new Function("d","return {"+n.map(function(n,t){return JSON.stringify(n)+": d["+t+"]"}).join(",")+"}");r=t?function(n,e){return t(i(n),e)}:i})},e.parseRows=function(n,t){function e(){if(f>=c)return o;if(i)return i=!1,u;var t=f;if(34===n.charCodeAt(t)){for(var e=t;e++f;){var r=n.charCodeAt(f++),a=1;if(10===r)i=!0;else if(13===r)i=!0,10===n.charCodeAt(f)&&(++f,++a);else if(r!==l)continue;return n.slice(t,f-a)}return n.slice(t)}for(var r,i,u={},o={},a=[],c=n.length,f=0,s=0;(r=e())!==o;){for(var h=[];r!==u&&r!==o;)h.push(r),r=e();t&&null==(h=t(h,s++))||a.push(h)}return a},e.format=function(t){if(Array.isArray(t[0]))return e.formatRows(t);var r=new y,i=[];return t.forEach(function(n){for(var t in n)r.has(t)||i.push(r.add(t))}),[i.map(o).join(n)].concat(t.map(function(t){return i.map(function(n){return o(t[n])}).join(n)})).join("\n")},e.formatRows=function(n){return n.map(u).join("\n")},e},ao.csv=ao.dsv(",","text/csv"),ao.tsv=ao.dsv(" ","text/tab-separated-values");var oa,aa,la,ca,fa=this[x(this,"requestAnimationFrame")]||function(n){setTimeout(n,17)};ao.timer=function(){qn.apply(this,arguments)},ao.timer.flush=function(){Rn(),Dn()},ao.round=function(n,t){return t?Math.round(n*(t=Math.pow(10,t)))/t:Math.round(n)};var sa=["y","z","a","f","p","n","\xb5","m","","k","M","G","T","P","E","Z","Y"].map(Un);ao.formatPrefix=function(n,t){var e=0;return(n=+n)&&(0>n&&(n*=-1),t&&(n=ao.round(n,Pn(n,t))),e=1+Math.floor(1e-12+Math.log(n)/Math.LN10),e=Math.max(-24,Math.min(24,3*Math.floor((e-1)/3)))),sa[8+e/3]};var ha=/(?:([^{])?([<>=^]))?([+\- ])?([$#])?(0)?(\d+)?(,)?(\.-?\d+)?([a-z%])?/i,pa=ao.map({b:function(n){return n.toString(2)},c:function(n){return String.fromCharCode(n)},o:function(n){return n.toString(8)},x:function(n){return n.toString(16)},X:function(n){return n.toString(16).toUpperCase()},g:function(n,t){return n.toPrecision(t)},e:function(n,t){return n.toExponential(t)},f:function(n,t){return n.toFixed(t)},r:function(n,t){return(n=ao.round(n,Pn(n,t))).toFixed(Math.max(0,Math.min(20,Pn(n*(1+1e-15),t))))}}),ga=ao.time={},va=Date;Hn.prototype={getDate:function(){return this._.getUTCDate()},getDay:function(){return this._.getUTCDay()},getFullYear:function(){return this._.getUTCFullYear()},getHours:function(){return this._.getUTCHours()},getMilliseconds:function(){return this._.getUTCMilliseconds()},getMinutes:function(){return this._.getUTCMinutes()},getMonth:function(){return this._.getUTCMonth()},getSeconds:function(){return this._.getUTCSeconds()},getTime:function(){return this._.getTime()},getTimezoneOffset:function(){return 0},valueOf:function(){return this._.valueOf()},setDate:function(){da.setUTCDate.apply(this._,arguments)},setDay:function(){da.setUTCDay.apply(this._,arguments)},setFullYear:function(){da.setUTCFullYear.apply(this._,arguments)},setHours:function(){da.setUTCHours.apply(this._,arguments)},setMilliseconds:function(){da.setUTCMilliseconds.apply(this._,arguments)},setMinutes:function(){da.setUTCMinutes.apply(this._,arguments)},setMonth:function(){da.setUTCMonth.apply(this._,arguments)},setSeconds:function(){da.setUTCSeconds.apply(this._,arguments)},setTime:function(){da.setTime.apply(this._,arguments)}};var da=Date.prototype;ga.year=On(function(n){return n=ga.day(n),n.setMonth(0,1),n},function(n,t){n.setFullYear(n.getFullYear()+t)},function(n){return n.getFullYear()}),ga.years=ga.year.range,ga.years.utc=ga.year.utc.range,ga.day=On(function(n){var t=new va(2e3,0);return t.setFullYear(n.getFullYear(),n.getMonth(),n.getDate()),t},function(n,t){n.setDate(n.getDate()+t)},function(n){return n.getDate()-1}),ga.days=ga.day.range,ga.days.utc=ga.day.utc.range,ga.dayOfYear=function(n){var t=ga.year(n);return Math.floor((n-t-6e4*(n.getTimezoneOffset()-t.getTimezoneOffset()))/864e5)},["sunday","monday","tuesday","wednesday","thursday","friday","saturday"].forEach(function(n,t){t=7-t;var e=ga[n]=On(function(n){return(n=ga.day(n)).setDate(n.getDate()-(n.getDay()+t)%7),n},function(n,t){n.setDate(n.getDate()+7*Math.floor(t))},function(n){var e=ga.year(n).getDay();return Math.floor((ga.dayOfYear(n)+(e+t)%7)/7)-(e!==t)});ga[n+"s"]=e.range,ga[n+"s"].utc=e.utc.range,ga[n+"OfYear"]=function(n){var e=ga.year(n).getDay();return Math.floor((ga.dayOfYear(n)+(e+t)%7)/7)}}),ga.week=ga.sunday,ga.weeks=ga.sunday.range,ga.weeks.utc=ga.sunday.utc.range,ga.weekOfYear=ga.sundayOfYear;var ya={"-":"",_:" ",0:"0"},ma=/^\s*\d+/,Ma=/^%/;ao.locale=function(n){return{numberFormat:jn(n),timeFormat:Yn(n)}};var xa=ao.locale({decimal:".",thousands:",",grouping:[3],currency:["$",""],dateTime:"%a %b %e %X %Y",date:"%m/%d/%Y",time:"%H:%M:%S",periods:["AM","PM"],days:["Sunday","Monday","Tuesday","Wednesday","Thursday","Friday","Saturday"], -shortDays:["Sun","Mon","Tue","Wed","Thu","Fri","Sat"],months:["January","February","March","April","May","June","July","August","September","October","November","December"],shortMonths:["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"]});ao.format=xa.numberFormat,ao.geo={},ft.prototype={s:0,t:0,add:function(n){st(n,this.t,ba),st(ba.s,this.s,this),this.s?this.t+=ba.t:this.s=ba.t},reset:function(){this.s=this.t=0},valueOf:function(){return this.s}};var ba=new ft;ao.geo.stream=function(n,t){n&&_a.hasOwnProperty(n.type)?_a[n.type](n,t):ht(n,t)};var _a={Feature:function(n,t){ht(n.geometry,t)},FeatureCollection:function(n,t){for(var e=n.features,r=-1,i=e.length;++rn?4*Fo+n:n,Na.lineStart=Na.lineEnd=Na.point=b}};ao.geo.bounds=function(){function n(n,t){M.push(x=[f=n,h=n]),s>t&&(s=t),t>p&&(p=t)}function t(t,e){var r=dt([t*Yo,e*Yo]);if(y){var i=mt(y,r),u=[i[1],-i[0],0],o=mt(u,i);bt(o),o=_t(o);var l=t-g,c=l>0?1:-1,v=o[0]*Zo*c,d=xo(l)>180;if(d^(v>c*g&&c*t>v)){var m=o[1]*Zo;m>p&&(p=m)}else if(v=(v+360)%360-180,d^(v>c*g&&c*t>v)){var m=-o[1]*Zo;s>m&&(s=m)}else s>e&&(s=e),e>p&&(p=e);d?g>t?a(f,t)>a(f,h)&&(h=t):a(t,h)>a(f,h)&&(f=t):h>=f?(f>t&&(f=t),t>h&&(h=t)):t>g?a(f,t)>a(f,h)&&(h=t):a(t,h)>a(f,h)&&(f=t)}else n(t,e);y=r,g=t}function e(){b.point=t}function r(){x[0]=f,x[1]=h,b.point=n,y=null}function i(n,e){if(y){var r=n-g;m+=xo(r)>180?r+(r>0?360:-360):r}else v=n,d=e;Na.point(n,e),t(n,e)}function u(){Na.lineStart()}function o(){i(v,d),Na.lineEnd(),xo(m)>Uo&&(f=-(h=180)),x[0]=f,x[1]=h,y=null}function a(n,t){return(t-=n)<0?t+360:t}function l(n,t){return n[0]-t[0]}function c(n,t){return t[0]<=t[1]?t[0]<=n&&n<=t[1]:nka?(f=-(h=180),s=-(p=90)):m>Uo?p=90:-Uo>m&&(s=-90),x[0]=f,x[1]=h}};return function(n){p=h=-(f=s=1/0),M=[],ao.geo.stream(n,b);var t=M.length;if(t){M.sort(l);for(var e,r=1,i=M[0],u=[i];t>r;++r)e=M[r],c(e[0],i)||c(e[1],i)?(a(i[0],e[1])>a(i[0],i[1])&&(i[1]=e[1]),a(e[0],i[1])>a(i[0],i[1])&&(i[0]=e[0])):u.push(i=e);for(var o,e,g=-(1/0),t=u.length-1,r=0,i=u[t];t>=r;i=e,++r)e=u[r],(o=a(i[1],e[0]))>g&&(g=o,f=e[0],h=i[1])}return M=x=null,f===1/0||s===1/0?[[NaN,NaN],[NaN,NaN]]:[[f,s],[h,p]]}}(),ao.geo.centroid=function(n){Ea=Aa=Ca=za=La=qa=Ta=Ra=Da=Pa=Ua=0,ao.geo.stream(n,ja);var t=Da,e=Pa,r=Ua,i=t*t+e*e+r*r;return jo>i&&(t=qa,e=Ta,r=Ra,Uo>Aa&&(t=Ca,e=za,r=La),i=t*t+e*e+r*r,jo>i)?[NaN,NaN]:[Math.atan2(e,t)*Zo,tn(r/Math.sqrt(i))*Zo]};var Ea,Aa,Ca,za,La,qa,Ta,Ra,Da,Pa,Ua,ja={sphere:b,point:St,lineStart:Nt,lineEnd:Et,polygonStart:function(){ja.lineStart=At},polygonEnd:function(){ja.lineStart=Nt}},Fa=Rt(zt,jt,Ht,[-Fo,-Fo/2]),Ha=1e9;ao.geo.clipExtent=function(){var n,t,e,r,i,u,o={stream:function(n){return i&&(i.valid=!1),i=u(n),i.valid=!0,i},extent:function(a){return arguments.length?(u=Zt(n=+a[0][0],t=+a[0][1],e=+a[1][0],r=+a[1][1]),i&&(i.valid=!1,i=null),o):[[n,t],[e,r]]}};return o.extent([[0,0],[960,500]])},(ao.geo.conicEqualArea=function(){return Vt(Xt)}).raw=Xt,ao.geo.albers=function(){return ao.geo.conicEqualArea().rotate([96,0]).center([-.6,38.7]).parallels([29.5,45.5]).scale(1070)},ao.geo.albersUsa=function(){function n(n){var u=n[0],o=n[1];return t=null,e(u,o),t||(r(u,o),t)||i(u,o),t}var t,e,r,i,u=ao.geo.albers(),o=ao.geo.conicEqualArea().rotate([154,0]).center([-2,58.5]).parallels([55,65]),a=ao.geo.conicEqualArea().rotate([157,0]).center([-3,19.9]).parallels([8,18]),l={point:function(n,e){t=[n,e]}};return n.invert=function(n){var t=u.scale(),e=u.translate(),r=(n[0]-e[0])/t,i=(n[1]-e[1])/t;return(i>=.12&&.234>i&&r>=-.425&&-.214>r?o:i>=.166&&.234>i&&r>=-.214&&-.115>r?a:u).invert(n)},n.stream=function(n){var t=u.stream(n),e=o.stream(n),r=a.stream(n);return{point:function(n,i){t.point(n,i),e.point(n,i),r.point(n,i)},sphere:function(){t.sphere(),e.sphere(),r.sphere()},lineStart:function(){t.lineStart(),e.lineStart(),r.lineStart()},lineEnd:function(){t.lineEnd(),e.lineEnd(),r.lineEnd()},polygonStart:function(){t.polygonStart(),e.polygonStart(),r.polygonStart()},polygonEnd:function(){t.polygonEnd(),e.polygonEnd(),r.polygonEnd()}}},n.precision=function(t){return arguments.length?(u.precision(t),o.precision(t),a.precision(t),n):u.precision()},n.scale=function(t){return arguments.length?(u.scale(t),o.scale(.35*t),a.scale(t),n.translate(u.translate())):u.scale()},n.translate=function(t){if(!arguments.length)return u.translate();var c=u.scale(),f=+t[0],s=+t[1];return e=u.translate(t).clipExtent([[f-.455*c,s-.238*c],[f+.455*c,s+.238*c]]).stream(l).point,r=o.translate([f-.307*c,s+.201*c]).clipExtent([[f-.425*c+Uo,s+.12*c+Uo],[f-.214*c-Uo,s+.234*c-Uo]]).stream(l).point,i=a.translate([f-.205*c,s+.212*c]).clipExtent([[f-.214*c+Uo,s+.166*c+Uo],[f-.115*c-Uo,s+.234*c-Uo]]).stream(l).point,n},n.scale(1070)};var Oa,Ia,Ya,Za,Va,Xa,$a={point:b,lineStart:b,lineEnd:b,polygonStart:function(){Ia=0,$a.lineStart=$t},polygonEnd:function(){$a.lineStart=$a.lineEnd=$a.point=b,Oa+=xo(Ia/2)}},Ba={point:Bt,lineStart:b,lineEnd:b,polygonStart:b,polygonEnd:b},Wa={point:Gt,lineStart:Kt,lineEnd:Qt,polygonStart:function(){Wa.lineStart=ne},polygonEnd:function(){Wa.point=Gt,Wa.lineStart=Kt,Wa.lineEnd=Qt}};ao.geo.path=function(){function n(n){return n&&("function"==typeof a&&u.pointRadius(+a.apply(this,arguments)),o&&o.valid||(o=i(u)),ao.geo.stream(n,o)),u.result()}function t(){return o=null,n}var e,r,i,u,o,a=4.5;return n.area=function(n){return Oa=0,ao.geo.stream(n,i($a)),Oa},n.centroid=function(n){return Ca=za=La=qa=Ta=Ra=Da=Pa=Ua=0,ao.geo.stream(n,i(Wa)),Ua?[Da/Ua,Pa/Ua]:Ra?[qa/Ra,Ta/Ra]:La?[Ca/La,za/La]:[NaN,NaN]},n.bounds=function(n){return Va=Xa=-(Ya=Za=1/0),ao.geo.stream(n,i(Ba)),[[Ya,Za],[Va,Xa]]},n.projection=function(n){return arguments.length?(i=(e=n)?n.stream||re(n):m,t()):e},n.context=function(n){return arguments.length?(u=null==(r=n)?new Wt:new te(n),"function"!=typeof a&&u.pointRadius(a),t()):r},n.pointRadius=function(t){return arguments.length?(a="function"==typeof t?t:(u.pointRadius(+t),+t),n):a},n.projection(ao.geo.albersUsa()).context(null)},ao.geo.transform=function(n){return{stream:function(t){var e=new ie(t);for(var r in n)e[r]=n[r];return e}}},ie.prototype={point:function(n,t){this.stream.point(n,t)},sphere:function(){this.stream.sphere()},lineStart:function(){this.stream.lineStart()},lineEnd:function(){this.stream.lineEnd()},polygonStart:function(){this.stream.polygonStart()},polygonEnd:function(){this.stream.polygonEnd()}},ao.geo.projection=oe,ao.geo.projectionMutator=ae,(ao.geo.equirectangular=function(){return oe(ce)}).raw=ce.invert=ce,ao.geo.rotation=function(n){function t(t){return t=n(t[0]*Yo,t[1]*Yo),t[0]*=Zo,t[1]*=Zo,t}return n=se(n[0]%360*Yo,n[1]*Yo,n.length>2?n[2]*Yo:0),t.invert=function(t){return t=n.invert(t[0]*Yo,t[1]*Yo),t[0]*=Zo,t[1]*=Zo,t},t},fe.invert=ce,ao.geo.circle=function(){function n(){var n="function"==typeof r?r.apply(this,arguments):r,t=se(-n[0]*Yo,-n[1]*Yo,0).invert,i=[];return e(null,null,1,{point:function(n,e){i.push(n=t(n,e)),n[0]*=Zo,n[1]*=Zo}}),{type:"Polygon",coordinates:[i]}}var t,e,r=[0,0],i=6;return n.origin=function(t){return arguments.length?(r=t,n):r},n.angle=function(r){return arguments.length?(e=ve((t=+r)*Yo,i*Yo),n):t},n.precision=function(r){return arguments.length?(e=ve(t*Yo,(i=+r)*Yo),n):i},n.angle(90)},ao.geo.distance=function(n,t){var e,r=(t[0]-n[0])*Yo,i=n[1]*Yo,u=t[1]*Yo,o=Math.sin(r),a=Math.cos(r),l=Math.sin(i),c=Math.cos(i),f=Math.sin(u),s=Math.cos(u);return Math.atan2(Math.sqrt((e=s*o)*e+(e=c*f-l*s*a)*e),l*f+c*s*a)},ao.geo.graticule=function(){function n(){return{type:"MultiLineString",coordinates:t()}}function t(){return ao.range(Math.ceil(u/d)*d,i,d).map(h).concat(ao.range(Math.ceil(c/y)*y,l,y).map(p)).concat(ao.range(Math.ceil(r/g)*g,e,g).filter(function(n){return xo(n%d)>Uo}).map(f)).concat(ao.range(Math.ceil(a/v)*v,o,v).filter(function(n){return xo(n%y)>Uo}).map(s))}var e,r,i,u,o,a,l,c,f,s,h,p,g=10,v=g,d=90,y=360,m=2.5;return n.lines=function(){return t().map(function(n){return{type:"LineString",coordinates:n}})},n.outline=function(){return{type:"Polygon",coordinates:[h(u).concat(p(l).slice(1),h(i).reverse().slice(1),p(c).reverse().slice(1))]}},n.extent=function(t){return arguments.length?n.majorExtent(t).minorExtent(t):n.minorExtent()},n.majorExtent=function(t){return arguments.length?(u=+t[0][0],i=+t[1][0],c=+t[0][1],l=+t[1][1],u>i&&(t=u,u=i,i=t),c>l&&(t=c,c=l,l=t),n.precision(m)):[[u,c],[i,l]]},n.minorExtent=function(t){return arguments.length?(r=+t[0][0],e=+t[1][0],a=+t[0][1],o=+t[1][1],r>e&&(t=r,r=e,e=t),a>o&&(t=a,a=o,o=t),n.precision(m)):[[r,a],[e,o]]},n.step=function(t){return arguments.length?n.majorStep(t).minorStep(t):n.minorStep()},n.majorStep=function(t){return arguments.length?(d=+t[0],y=+t[1],n):[d,y]},n.minorStep=function(t){return arguments.length?(g=+t[0],v=+t[1],n):[g,v]},n.precision=function(t){return arguments.length?(m=+t,f=ye(a,o,90),s=me(r,e,m),h=ye(c,l,90),p=me(u,i,m),n):m},n.majorExtent([[-180,-90+Uo],[180,90-Uo]]).minorExtent([[-180,-80-Uo],[180,80+Uo]])},ao.geo.greatArc=function(){function n(){return{type:"LineString",coordinates:[t||r.apply(this,arguments),e||i.apply(this,arguments)]}}var t,e,r=Me,i=xe;return n.distance=function(){return ao.geo.distance(t||r.apply(this,arguments),e||i.apply(this,arguments))},n.source=function(e){return arguments.length?(r=e,t="function"==typeof e?null:e,n):r},n.target=function(t){return arguments.length?(i=t,e="function"==typeof t?null:t,n):i},n.precision=function(){return arguments.length?n:0},n},ao.geo.interpolate=function(n,t){return be(n[0]*Yo,n[1]*Yo,t[0]*Yo,t[1]*Yo)},ao.geo.length=function(n){return Ja=0,ao.geo.stream(n,Ga),Ja};var Ja,Ga={sphere:b,point:b,lineStart:_e,lineEnd:b,polygonStart:b,polygonEnd:b},Ka=we(function(n){return Math.sqrt(2/(1+n))},function(n){return 2*Math.asin(n/2)});(ao.geo.azimuthalEqualArea=function(){return oe(Ka)}).raw=Ka;var Qa=we(function(n){var t=Math.acos(n);return t&&t/Math.sin(t)},m);(ao.geo.azimuthalEquidistant=function(){return oe(Qa)}).raw=Qa,(ao.geo.conicConformal=function(){return Vt(Se)}).raw=Se,(ao.geo.conicEquidistant=function(){return Vt(ke)}).raw=ke;var nl=we(function(n){return 1/n},Math.atan);(ao.geo.gnomonic=function(){return oe(nl)}).raw=nl,Ne.invert=function(n,t){return[n,2*Math.atan(Math.exp(t))-Io]},(ao.geo.mercator=function(){return Ee(Ne)}).raw=Ne;var tl=we(function(){return 1},Math.asin);(ao.geo.orthographic=function(){return oe(tl)}).raw=tl;var el=we(function(n){return 1/(1+n)},function(n){return 2*Math.atan(n)});(ao.geo.stereographic=function(){return oe(el)}).raw=el,Ae.invert=function(n,t){return[-t,2*Math.atan(Math.exp(n))-Io]},(ao.geo.transverseMercator=function(){var n=Ee(Ae),t=n.center,e=n.rotate;return n.center=function(n){return n?t([-n[1],n[0]]):(n=t(),[n[1],-n[0]])},n.rotate=function(n){return n?e([n[0],n[1],n.length>2?n[2]+90:90]):(n=e(),[n[0],n[1],n[2]-90])},e([0,0,90])}).raw=Ae,ao.geom={},ao.geom.hull=function(n){function t(n){if(n.length<3)return[];var t,i=En(e),u=En(r),o=n.length,a=[],l=[];for(t=0;o>t;t++)a.push([+i.call(this,n[t],t),+u.call(this,n[t],t),t]);for(a.sort(qe),t=0;o>t;t++)l.push([a[t][0],-a[t][1]]);var c=Le(a),f=Le(l),s=f[0]===c[0],h=f[f.length-1]===c[c.length-1],p=[];for(t=c.length-1;t>=0;--t)p.push(n[a[c[t]][2]]);for(t=+s;t=r&&c.x<=u&&c.y>=i&&c.y<=o?[[r,o],[u,o],[u,i],[r,i]]:[];f.point=n[a]}),t}function e(n){return n.map(function(n,t){return{x:Math.round(u(n,t)/Uo)*Uo,y:Math.round(o(n,t)/Uo)*Uo,i:t}})}var r=Ce,i=ze,u=r,o=i,a=sl;return n?t(n):(t.links=function(n){return ar(e(n)).edges.filter(function(n){return n.l&&n.r}).map(function(t){return{source:n[t.l.i],target:n[t.r.i]}})},t.triangles=function(n){var t=[];return ar(e(n)).cells.forEach(function(e,r){for(var i,u,o=e.site,a=e.edges.sort(Ve),l=-1,c=a.length,f=a[c-1].edge,s=f.l===o?f.r:f.l;++l=c,h=r>=f,p=h<<1|s;n.leaf=!1,n=n.nodes[p]||(n.nodes[p]=hr()),s?i=c:a=c,h?o=f:l=f,u(n,t,e,r,i,o,a,l)}var f,s,h,p,g,v,d,y,m,M=En(a),x=En(l);if(null!=t)v=t,d=e,y=r,m=i;else if(y=m=-(v=d=1/0),s=[],h=[],g=n.length,o)for(p=0;g>p;++p)f=n[p],f.xy&&(y=f.x),f.y>m&&(m=f.y),s.push(f.x),h.push(f.y);else for(p=0;g>p;++p){var b=+M(f=n[p],p),_=+x(f,p);v>b&&(v=b),d>_&&(d=_),b>y&&(y=b),_>m&&(m=_),s.push(b),h.push(_)}var w=y-v,S=m-d;w>S?m=d+w:y=v+S;var k=hr();if(k.add=function(n){u(k,n,+M(n,++p),+x(n,p),v,d,y,m)},k.visit=function(n){pr(n,k,v,d,y,m)},k.find=function(n){return gr(k,n[0],n[1],v,d,y,m)},p=-1,null==t){for(;++p=0?n.slice(0,t):n,r=t>=0?n.slice(t+1):"in";return e=vl.get(e)||gl,r=dl.get(r)||m,br(r(e.apply(null,lo.call(arguments,1))))},ao.interpolateHcl=Rr,ao.interpolateHsl=Dr,ao.interpolateLab=Pr,ao.interpolateRound=Ur,ao.transform=function(n){var t=fo.createElementNS(ao.ns.prefix.svg,"g");return(ao.transform=function(n){if(null!=n){t.setAttribute("transform",n);var e=t.transform.baseVal.consolidate()}return new jr(e?e.matrix:yl)})(n)},jr.prototype.toString=function(){return"translate("+this.translate+")rotate("+this.rotate+")skewX("+this.skew+")scale("+this.scale+")"};var yl={a:1,b:0,c:0,d:1,e:0,f:0};ao.interpolateTransform=$r,ao.layout={},ao.layout.bundle=function(){return function(n){for(var t=[],e=-1,r=n.length;++ea*a/y){if(v>l){var c=t.charge/l;n.px-=u*c,n.py-=o*c}return!0}if(t.point&&l&&v>l){var c=t.pointCharge/l;n.px-=u*c,n.py-=o*c}}return!t.charge}}function t(n){n.px=ao.event.x,n.py=ao.event.y,l.resume()}var e,r,i,u,o,a,l={},c=ao.dispatch("start","tick","end"),f=[1,1],s=.9,h=ml,p=Ml,g=-30,v=xl,d=.1,y=.64,M=[],x=[];return l.tick=function(){if((i*=.99)<.005)return e=null,c.end({type:"end",alpha:i=0}),!0;var t,r,l,h,p,v,y,m,b,_=M.length,w=x.length;for(r=0;w>r;++r)l=x[r],h=l.source,p=l.target,m=p.x-h.x,b=p.y-h.y,(v=m*m+b*b)&&(v=i*o[r]*((v=Math.sqrt(v))-u[r])/v,m*=v,b*=v,p.x-=m*(y=h.weight+p.weight?h.weight/(h.weight+p.weight):.5),p.y-=b*y,h.x+=m*(y=1-y),h.y+=b*y);if((y=i*d)&&(m=f[0]/2,b=f[1]/2,r=-1,y))for(;++r<_;)l=M[r],l.x+=(m-l.x)*y,l.y+=(b-l.y)*y;if(g)for(ri(t=ao.geom.quadtree(M),i,a),r=-1;++r<_;)(l=M[r]).fixed||t.visit(n(l));for(r=-1;++r<_;)l=M[r],l.fixed?(l.x=l.px,l.y=l.py):(l.x-=(l.px-(l.px=l.x))*s,l.y-=(l.py-(l.py=l.y))*s);c.tick({type:"tick",alpha:i})},l.nodes=function(n){return arguments.length?(M=n,l):M},l.links=function(n){return arguments.length?(x=n,l):x},l.size=function(n){return arguments.length?(f=n,l):f},l.linkDistance=function(n){return arguments.length?(h="function"==typeof n?n:+n,l):h},l.distance=l.linkDistance,l.linkStrength=function(n){return arguments.length?(p="function"==typeof n?n:+n,l):p},l.friction=function(n){return arguments.length?(s=+n,l):s},l.charge=function(n){return arguments.length?(g="function"==typeof n?n:+n,l):g},l.chargeDistance=function(n){return arguments.length?(v=n*n,l):Math.sqrt(v)},l.gravity=function(n){return arguments.length?(d=+n,l):d},l.theta=function(n){return arguments.length?(y=n*n,l):Math.sqrt(y)},l.alpha=function(n){return arguments.length?(n=+n,i?n>0?i=n:(e.c=null,e.t=NaN,e=null,c.end({type:"end",alpha:i=0})):n>0&&(c.start({type:"start",alpha:i=n}),e=qn(l.tick)),l):i},l.start=function(){function n(n,r){if(!e){for(e=new Array(i),l=0;i>l;++l)e[l]=[];for(l=0;c>l;++l){var u=x[l];e[u.source.index].push(u.target),e[u.target.index].push(u.source)}}for(var o,a=e[t],l=-1,f=a.length;++lt;++t)(r=M[t]).index=t,r.weight=0;for(t=0;c>t;++t)r=x[t],"number"==typeof r.source&&(r.source=M[r.source]),"number"==typeof r.target&&(r.target=M[r.target]),++r.source.weight,++r.target.weight;for(t=0;i>t;++t)r=M[t],isNaN(r.x)&&(r.x=n("x",s)),isNaN(r.y)&&(r.y=n("y",v)),isNaN(r.px)&&(r.px=r.x),isNaN(r.py)&&(r.py=r.y);if(u=[],"function"==typeof h)for(t=0;c>t;++t)u[t]=+h.call(this,x[t],t);else for(t=0;c>t;++t)u[t]=h;if(o=[],"function"==typeof p)for(t=0;c>t;++t)o[t]=+p.call(this,x[t],t);else for(t=0;c>t;++t)o[t]=p;if(a=[],"function"==typeof g)for(t=0;i>t;++t)a[t]=+g.call(this,M[t],t);else for(t=0;i>t;++t)a[t]=g;return l.resume()},l.resume=function(){return l.alpha(.1)},l.stop=function(){return l.alpha(0)},l.drag=function(){return r||(r=ao.behavior.drag().origin(m).on("dragstart.force",Qr).on("drag.force",t).on("dragend.force",ni)),arguments.length?void this.on("mouseover.force",ti).on("mouseout.force",ei).call(r):r},ao.rebind(l,c,"on")};var ml=20,Ml=1,xl=1/0;ao.layout.hierarchy=function(){function n(i){var u,o=[i],a=[];for(i.depth=0;null!=(u=o.pop());)if(a.push(u),(c=e.call(n,u,u.depth))&&(l=c.length)){for(var l,c,f;--l>=0;)o.push(f=c[l]),f.parent=u,f.depth=u.depth+1;r&&(u.value=0),u.children=c}else r&&(u.value=+r.call(n,u,u.depth)||0),delete u.children;return oi(i,function(n){var e,i;t&&(e=n.children)&&e.sort(t),r&&(i=n.parent)&&(i.value+=n.value)}),a}var t=ci,e=ai,r=li;return n.sort=function(e){return arguments.length?(t=e,n):t},n.children=function(t){return arguments.length?(e=t,n):e},n.value=function(t){return arguments.length?(r=t,n):r},n.revalue=function(t){return r&&(ui(t,function(n){n.children&&(n.value=0)}),oi(t,function(t){var e;t.children||(t.value=+r.call(n,t,t.depth)||0),(e=t.parent)&&(e.value+=t.value)})),t},n},ao.layout.partition=function(){function n(t,e,r,i){var u=t.children;if(t.x=e,t.y=t.depth*i,t.dx=r,t.dy=i,u&&(o=u.length)){var o,a,l,c=-1;for(r=t.value?r/t.value:0;++cs?-1:1),g=ao.sum(c),v=g?(s-l*p)/g:0,d=ao.range(l),y=[];return null!=e&&d.sort(e===bl?function(n,t){return c[t]-c[n]}:function(n,t){return e(o[n],o[t])}),d.forEach(function(n){y[n]={data:o[n],value:a=c[n],startAngle:f,endAngle:f+=a*v+p,padAngle:h}}),y}var t=Number,e=bl,r=0,i=Ho,u=0;return n.value=function(e){return arguments.length?(t=e,n):t},n.sort=function(t){return arguments.length?(e=t,n):e},n.startAngle=function(t){return arguments.length?(r=t,n):r},n.endAngle=function(t){return arguments.length?(i=t,n):i},n.padAngle=function(t){return arguments.length?(u=t,n):u},n};var bl={};ao.layout.stack=function(){function n(a,l){if(!(h=a.length))return a;var c=a.map(function(e,r){return t.call(n,e,r)}),f=c.map(function(t){return t.map(function(t,e){return[u.call(n,t,e),o.call(n,t,e)]})}),s=e.call(n,f,l);c=ao.permute(c,s),f=ao.permute(f,s);var h,p,g,v,d=r.call(n,f,l),y=c[0].length;for(g=0;y>g;++g)for(i.call(n,c[0][g],v=d[g],f[0][g][1]),p=1;h>p;++p)i.call(n,c[p][g],v+=f[p-1][g][1],f[p][g][1]);return a}var t=m,e=gi,r=vi,i=pi,u=si,o=hi;return n.values=function(e){return arguments.length?(t=e,n):t},n.order=function(t){return arguments.length?(e="function"==typeof t?t:_l.get(t)||gi,n):e},n.offset=function(t){return arguments.length?(r="function"==typeof t?t:wl.get(t)||vi,n):r},n.x=function(t){return arguments.length?(u=t,n):u},n.y=function(t){return arguments.length?(o=t,n):o},n.out=function(t){return arguments.length?(i=t,n):i},n};var _l=ao.map({"inside-out":function(n){var t,e,r=n.length,i=n.map(di),u=n.map(yi),o=ao.range(r).sort(function(n,t){return i[n]-i[t]}),a=0,l=0,c=[],f=[];for(t=0;r>t;++t)e=o[t],l>a?(a+=u[e],c.push(e)):(l+=u[e],f.push(e));return f.reverse().concat(c)},reverse:function(n){return ao.range(n.length).reverse()},"default":gi}),wl=ao.map({silhouette:function(n){var t,e,r,i=n.length,u=n[0].length,o=[],a=0,l=[];for(e=0;u>e;++e){for(t=0,r=0;i>t;t++)r+=n[t][e][1];r>a&&(a=r),o.push(r)}for(e=0;u>e;++e)l[e]=(a-o[e])/2;return l},wiggle:function(n){var t,e,r,i,u,o,a,l,c,f=n.length,s=n[0],h=s.length,p=[];for(p[0]=l=c=0,e=1;h>e;++e){for(t=0,i=0;f>t;++t)i+=n[t][e][1];for(t=0,u=0,a=s[e][0]-s[e-1][0];f>t;++t){for(r=0,o=(n[t][e][1]-n[t][e-1][1])/(2*a);t>r;++r)o+=(n[r][e][1]-n[r][e-1][1])/a;u+=o*n[t][e][1]}p[e]=l-=i?u/i*a:0,c>l&&(c=l)}for(e=0;h>e;++e)p[e]-=c;return p},expand:function(n){var t,e,r,i=n.length,u=n[0].length,o=1/i,a=[];for(e=0;u>e;++e){for(t=0,r=0;i>t;t++)r+=n[t][e][1];if(r)for(t=0;i>t;t++)n[t][e][1]/=r;else for(t=0;i>t;t++)n[t][e][1]=o}for(e=0;u>e;++e)a[e]=0;return a},zero:vi});ao.layout.histogram=function(){function n(n,u){for(var o,a,l=[],c=n.map(e,this),f=r.call(this,c,u),s=i.call(this,f,c,u),u=-1,h=c.length,p=s.length-1,g=t?1:1/h;++u0)for(u=-1;++u=f[0]&&a<=f[1]&&(o=l[ao.bisect(s,a,1,p)-1],o.y+=g,o.push(n[u]));return l}var t=!0,e=Number,r=bi,i=Mi;return n.value=function(t){return arguments.length?(e=t,n):e},n.range=function(t){return arguments.length?(r=En(t),n):r},n.bins=function(t){return arguments.length?(i="number"==typeof t?function(n){return xi(n,t)}:En(t),n):i},n.frequency=function(e){return arguments.length?(t=!!e,n):t},n},ao.layout.pack=function(){function n(n,u){var o=e.call(this,n,u),a=o[0],l=i[0],c=i[1],f=null==t?Math.sqrt:"function"==typeof t?t:function(){return t};if(a.x=a.y=0,oi(a,function(n){n.r=+f(n.value)}),oi(a,Ni),r){var s=r*(t?1:Math.max(2*a.r/l,2*a.r/c))/2;oi(a,function(n){n.r+=s}),oi(a,Ni),oi(a,function(n){n.r-=s})}return Ci(a,l/2,c/2,t?1:1/Math.max(2*a.r/l,2*a.r/c)),o}var t,e=ao.layout.hierarchy().sort(_i),r=0,i=[1,1];return n.size=function(t){return arguments.length?(i=t,n):i},n.radius=function(e){return arguments.length?(t=null==e||"function"==typeof e?e:+e,n):t},n.padding=function(t){return arguments.length?(r=+t,n):r},ii(n,e)},ao.layout.tree=function(){function n(n,i){var f=o.call(this,n,i),s=f[0],h=t(s);if(oi(h,e),h.parent.m=-h.z,ui(h,r),c)ui(s,u);else{var p=s,g=s,v=s;ui(s,function(n){n.xg.x&&(g=n),n.depth>v.depth&&(v=n)});var d=a(p,g)/2-p.x,y=l[0]/(g.x+a(g,p)/2+d),m=l[1]/(v.depth||1);ui(s,function(n){n.x=(n.x+d)*y,n.y=n.depth*m})}return f}function t(n){for(var t,e={A:null,children:[n]},r=[e];null!=(t=r.pop());)for(var i,u=t.children,o=0,a=u.length;a>o;++o)r.push((u[o]=i={_:u[o],parent:t,children:(i=u[o].children)&&i.slice()||[],A:null,a:null,z:0,m:0,c:0,s:0,t:null,i:o}).a=i);return e.children[0]}function e(n){var t=n.children,e=n.parent.children,r=n.i?e[n.i-1]:null;if(t.length){Di(n);var u=(t[0].z+t[t.length-1].z)/2;r?(n.z=r.z+a(n._,r._),n.m=n.z-u):n.z=u}else r&&(n.z=r.z+a(n._,r._));n.parent.A=i(n,r,n.parent.A||e[0])}function r(n){n._.x=n.z+n.parent.m,n.m+=n.parent.m}function i(n,t,e){if(t){for(var r,i=n,u=n,o=t,l=i.parent.children[0],c=i.m,f=u.m,s=o.m,h=l.m;o=Ti(o),i=qi(i),o&&i;)l=qi(l),u=Ti(u),u.a=n,r=o.z+s-i.z-c+a(o._,i._),r>0&&(Ri(Pi(o,n,e),n,r),c+=r,f+=r),s+=o.m,c+=i.m,h+=l.m,f+=u.m;o&&!Ti(u)&&(u.t=o,u.m+=s-f),i&&!qi(l)&&(l.t=i,l.m+=c-h,e=n)}return e}function u(n){n.x*=l[0],n.y=n.depth*l[1]}var o=ao.layout.hierarchy().sort(null).value(null),a=Li,l=[1,1],c=null;return n.separation=function(t){return arguments.length?(a=t,n):a},n.size=function(t){return arguments.length?(c=null==(l=t)?u:null,n):c?null:l},n.nodeSize=function(t){return arguments.length?(c=null==(l=t)?null:u,n):c?l:null},ii(n,o)},ao.layout.cluster=function(){function n(n,u){var o,a=t.call(this,n,u),l=a[0],c=0;oi(l,function(n){var t=n.children;t&&t.length?(n.x=ji(t),n.y=Ui(t)):(n.x=o?c+=e(n,o):0,n.y=0,o=n)});var f=Fi(l),s=Hi(l),h=f.x-e(f,s)/2,p=s.x+e(s,f)/2;return oi(l,i?function(n){n.x=(n.x-l.x)*r[0],n.y=(l.y-n.y)*r[1]}:function(n){n.x=(n.x-h)/(p-h)*r[0],n.y=(1-(l.y?n.y/l.y:1))*r[1]}),a}var t=ao.layout.hierarchy().sort(null).value(null),e=Li,r=[1,1],i=!1;return n.separation=function(t){return arguments.length?(e=t,n):e},n.size=function(t){return arguments.length?(i=null==(r=t),n):i?null:r},n.nodeSize=function(t){return arguments.length?(i=null!=(r=t),n):i?r:null},ii(n,t)},ao.layout.treemap=function(){function n(n,t){for(var e,r,i=-1,u=n.length;++it?0:t),e.area=isNaN(r)||0>=r?0:r}function t(e){var u=e.children;if(u&&u.length){var o,a,l,c=s(e),f=[],h=u.slice(),g=1/0,v="slice"===p?c.dx:"dice"===p?c.dy:"slice-dice"===p?1&e.depth?c.dy:c.dx:Math.min(c.dx,c.dy);for(n(h,c.dx*c.dy/e.value),f.area=0;(l=h.length)>0;)f.push(o=h[l-1]),f.area+=o.area,"squarify"!==p||(a=r(f,v))<=g?(h.pop(),g=a):(f.area-=f.pop().area,i(f,v,c,!1),v=Math.min(c.dx,c.dy),f.length=f.area=0,g=1/0);f.length&&(i(f,v,c,!0),f.length=f.area=0),u.forEach(t)}}function e(t){var r=t.children;if(r&&r.length){var u,o=s(t),a=r.slice(),l=[];for(n(a,o.dx*o.dy/t.value),l.area=0;u=a.pop();)l.push(u),l.area+=u.area,null!=u.z&&(i(l,u.z?o.dx:o.dy,o,!a.length),l.length=l.area=0);r.forEach(e)}}function r(n,t){for(var e,r=n.area,i=0,u=1/0,o=-1,a=n.length;++oe&&(u=e),e>i&&(i=e));return r*=r,t*=t,r?Math.max(t*i*g/r,r/(t*u*g)):1/0}function i(n,t,e,r){var i,u=-1,o=n.length,a=e.x,c=e.y,f=t?l(n.area/t):0; -if(t==e.dx){for((r||f>e.dy)&&(f=e.dy);++ue.dx)&&(f=e.dx);++ue&&(t=1),1>e&&(n=0),function(){var e,r,i;do e=2*Math.random()-1,r=2*Math.random()-1,i=e*e+r*r;while(!i||i>1);return n+t*e*Math.sqrt(-2*Math.log(i)/i)}},logNormal:function(){var n=ao.random.normal.apply(ao,arguments);return function(){return Math.exp(n())}},bates:function(n){var t=ao.random.irwinHall(n);return function(){return t()/n}},irwinHall:function(n){return function(){for(var t=0,e=0;n>e;e++)t+=Math.random();return t}}},ao.scale={};var Sl={floor:m,ceil:m};ao.scale.linear=function(){return Wi([0,1],[0,1],Mr,!1)};var kl={s:1,g:1,p:1,r:1,e:1};ao.scale.log=function(){return ru(ao.scale.linear().domain([0,1]),10,!0,[1,10])};var Nl=ao.format(".0e"),El={floor:function(n){return-Math.ceil(-n)},ceil:function(n){return-Math.floor(-n)}};ao.scale.pow=function(){return iu(ao.scale.linear(),1,[0,1])},ao.scale.sqrt=function(){return ao.scale.pow().exponent(.5)},ao.scale.ordinal=function(){return ou([],{t:"range",a:[[]]})},ao.scale.category10=function(){return ao.scale.ordinal().range(Al)},ao.scale.category20=function(){return ao.scale.ordinal().range(Cl)},ao.scale.category20b=function(){return ao.scale.ordinal().range(zl)},ao.scale.category20c=function(){return ao.scale.ordinal().range(Ll)};var Al=[2062260,16744206,2924588,14034728,9725885,9197131,14907330,8355711,12369186,1556175].map(xn),Cl=[2062260,11454440,16744206,16759672,2924588,10018698,14034728,16750742,9725885,12955861,9197131,12885140,14907330,16234194,8355711,13092807,12369186,14408589,1556175,10410725].map(xn),zl=[3750777,5395619,7040719,10264286,6519097,9216594,11915115,13556636,9202993,12426809,15186514,15190932,8666169,11356490,14049643,15177372,8077683,10834324,13528509,14589654].map(xn),Ll=[3244733,7057110,10406625,13032431,15095053,16616764,16625259,16634018,3253076,7652470,10607003,13101504,7695281,10394312,12369372,14342891,6513507,9868950,12434877,14277081].map(xn);ao.scale.quantile=function(){return au([],[])},ao.scale.quantize=function(){return lu(0,1,[0,1])},ao.scale.threshold=function(){return cu([.5],[0,1])},ao.scale.identity=function(){return fu([0,1])},ao.svg={},ao.svg.arc=function(){function n(){var n=Math.max(0,+e.apply(this,arguments)),c=Math.max(0,+r.apply(this,arguments)),f=o.apply(this,arguments)-Io,s=a.apply(this,arguments)-Io,h=Math.abs(s-f),p=f>s?0:1;if(n>c&&(g=c,c=n,n=g),h>=Oo)return t(c,p)+(n?t(n,1-p):"")+"Z";var g,v,d,y,m,M,x,b,_,w,S,k,N=0,E=0,A=[];if((y=(+l.apply(this,arguments)||0)/2)&&(d=u===ql?Math.sqrt(n*n+c*c):+u.apply(this,arguments),p||(E*=-1),c&&(E=tn(d/c*Math.sin(y))),n&&(N=tn(d/n*Math.sin(y)))),c){m=c*Math.cos(f+E),M=c*Math.sin(f+E),x=c*Math.cos(s-E),b=c*Math.sin(s-E);var C=Math.abs(s-f-2*E)<=Fo?0:1;if(E&&yu(m,M,x,b)===p^C){var z=(f+s)/2;m=c*Math.cos(z),M=c*Math.sin(z),x=b=null}}else m=M=0;if(n){_=n*Math.cos(s-N),w=n*Math.sin(s-N),S=n*Math.cos(f+N),k=n*Math.sin(f+N);var L=Math.abs(f-s+2*N)<=Fo?0:1;if(N&&yu(_,w,S,k)===1-p^L){var q=(f+s)/2;_=n*Math.cos(q),w=n*Math.sin(q),S=k=null}}else _=w=0;if(h>Uo&&(g=Math.min(Math.abs(c-n)/2,+i.apply(this,arguments)))>.001){v=c>n^p?0:1;var T=g,R=g;if(Fo>h){var D=null==S?[_,w]:null==x?[m,M]:Re([m,M],[S,k],[x,b],[_,w]),P=m-D[0],U=M-D[1],j=x-D[0],F=b-D[1],H=1/Math.sin(Math.acos((P*j+U*F)/(Math.sqrt(P*P+U*U)*Math.sqrt(j*j+F*F)))/2),O=Math.sqrt(D[0]*D[0]+D[1]*D[1]);R=Math.min(g,(n-O)/(H-1)),T=Math.min(g,(c-O)/(H+1))}if(null!=x){var I=mu(null==S?[_,w]:[S,k],[m,M],c,T,p),Y=mu([x,b],[_,w],c,T,p);g===T?A.push("M",I[0],"A",T,",",T," 0 0,",v," ",I[1],"A",c,",",c," 0 ",1-p^yu(I[1][0],I[1][1],Y[1][0],Y[1][1]),",",p," ",Y[1],"A",T,",",T," 0 0,",v," ",Y[0]):A.push("M",I[0],"A",T,",",T," 0 1,",v," ",Y[0])}else A.push("M",m,",",M);if(null!=S){var Z=mu([m,M],[S,k],n,-R,p),V=mu([_,w],null==x?[m,M]:[x,b],n,-R,p);g===R?A.push("L",V[0],"A",R,",",R," 0 0,",v," ",V[1],"A",n,",",n," 0 ",p^yu(V[1][0],V[1][1],Z[1][0],Z[1][1]),",",1-p," ",Z[1],"A",R,",",R," 0 0,",v," ",Z[0]):A.push("L",V[0],"A",R,",",R," 0 0,",v," ",Z[0])}else A.push("L",_,",",w)}else A.push("M",m,",",M),null!=x&&A.push("A",c,",",c," 0 ",C,",",p," ",x,",",b),A.push("L",_,",",w),null!=S&&A.push("A",n,",",n," 0 ",L,",",1-p," ",S,",",k);return A.push("Z"),A.join("")}function t(n,t){return"M0,"+n+"A"+n+","+n+" 0 1,"+t+" 0,"+-n+"A"+n+","+n+" 0 1,"+t+" 0,"+n}var e=hu,r=pu,i=su,u=ql,o=gu,a=vu,l=du;return n.innerRadius=function(t){return arguments.length?(e=En(t),n):e},n.outerRadius=function(t){return arguments.length?(r=En(t),n):r},n.cornerRadius=function(t){return arguments.length?(i=En(t),n):i},n.padRadius=function(t){return arguments.length?(u=t==ql?ql:En(t),n):u},n.startAngle=function(t){return arguments.length?(o=En(t),n):o},n.endAngle=function(t){return arguments.length?(a=En(t),n):a},n.padAngle=function(t){return arguments.length?(l=En(t),n):l},n.centroid=function(){var n=(+e.apply(this,arguments)+ +r.apply(this,arguments))/2,t=(+o.apply(this,arguments)+ +a.apply(this,arguments))/2-Io;return[Math.cos(t)*n,Math.sin(t)*n]},n};var ql="auto";ao.svg.line=function(){return Mu(m)};var Tl=ao.map({linear:xu,"linear-closed":bu,step:_u,"step-before":wu,"step-after":Su,basis:zu,"basis-open":Lu,"basis-closed":qu,bundle:Tu,cardinal:Eu,"cardinal-open":ku,"cardinal-closed":Nu,monotone:Fu});Tl.forEach(function(n,t){t.key=n,t.closed=/-closed$/.test(n)});var Rl=[0,2/3,1/3,0],Dl=[0,1/3,2/3,0],Pl=[0,1/6,2/3,1/6];ao.svg.line.radial=function(){var n=Mu(Hu);return n.radius=n.x,delete n.x,n.angle=n.y,delete n.y,n},wu.reverse=Su,Su.reverse=wu,ao.svg.area=function(){return Ou(m)},ao.svg.area.radial=function(){var n=Ou(Hu);return n.radius=n.x,delete n.x,n.innerRadius=n.x0,delete n.x0,n.outerRadius=n.x1,delete n.x1,n.angle=n.y,delete n.y,n.startAngle=n.y0,delete n.y0,n.endAngle=n.y1,delete n.y1,n},ao.svg.chord=function(){function n(n,a){var l=t(this,u,n,a),c=t(this,o,n,a);return"M"+l.p0+r(l.r,l.p1,l.a1-l.a0)+(e(l,c)?i(l.r,l.p1,l.r,l.p0):i(l.r,l.p1,c.r,c.p0)+r(c.r,c.p1,c.a1-c.a0)+i(c.r,c.p1,l.r,l.p0))+"Z"}function t(n,t,e,r){var i=t.call(n,e,r),u=a.call(n,i,r),o=l.call(n,i,r)-Io,f=c.call(n,i,r)-Io;return{r:u,a0:o,a1:f,p0:[u*Math.cos(o),u*Math.sin(o)],p1:[u*Math.cos(f),u*Math.sin(f)]}}function e(n,t){return n.a0==t.a0&&n.a1==t.a1}function r(n,t,e){return"A"+n+","+n+" 0 "+ +(e>Fo)+",1 "+t}function i(n,t,e,r){return"Q 0,0 "+r}var u=Me,o=xe,a=Iu,l=gu,c=vu;return n.radius=function(t){return arguments.length?(a=En(t),n):a},n.source=function(t){return arguments.length?(u=En(t),n):u},n.target=function(t){return arguments.length?(o=En(t),n):o},n.startAngle=function(t){return arguments.length?(l=En(t),n):l},n.endAngle=function(t){return arguments.length?(c=En(t),n):c},n},ao.svg.diagonal=function(){function n(n,i){var u=t.call(this,n,i),o=e.call(this,n,i),a=(u.y+o.y)/2,l=[u,{x:u.x,y:a},{x:o.x,y:a},o];return l=l.map(r),"M"+l[0]+"C"+l[1]+" "+l[2]+" "+l[3]}var t=Me,e=xe,r=Yu;return n.source=function(e){return arguments.length?(t=En(e),n):t},n.target=function(t){return arguments.length?(e=En(t),n):e},n.projection=function(t){return arguments.length?(r=t,n):r},n},ao.svg.diagonal.radial=function(){var n=ao.svg.diagonal(),t=Yu,e=n.projection;return n.projection=function(n){return arguments.length?e(Zu(t=n)):t},n},ao.svg.symbol=function(){function n(n,r){return(Ul.get(t.call(this,n,r))||$u)(e.call(this,n,r))}var t=Xu,e=Vu;return n.type=function(e){return arguments.length?(t=En(e),n):t},n.size=function(t){return arguments.length?(e=En(t),n):e},n};var Ul=ao.map({circle:$u,cross:function(n){var t=Math.sqrt(n/5)/2;return"M"+-3*t+","+-t+"H"+-t+"V"+-3*t+"H"+t+"V"+-t+"H"+3*t+"V"+t+"H"+t+"V"+3*t+"H"+-t+"V"+t+"H"+-3*t+"Z"},diamond:function(n){var t=Math.sqrt(n/(2*Fl)),e=t*Fl;return"M0,"+-t+"L"+e+",0 0,"+t+" "+-e+",0Z"},square:function(n){var t=Math.sqrt(n)/2;return"M"+-t+","+-t+"L"+t+","+-t+" "+t+","+t+" "+-t+","+t+"Z"},"triangle-down":function(n){var t=Math.sqrt(n/jl),e=t*jl/2;return"M0,"+e+"L"+t+","+-e+" "+-t+","+-e+"Z"},"triangle-up":function(n){var t=Math.sqrt(n/jl),e=t*jl/2;return"M0,"+-e+"L"+t+","+e+" "+-t+","+e+"Z"}});ao.svg.symbolTypes=Ul.keys();var jl=Math.sqrt(3),Fl=Math.tan(30*Yo);Co.transition=function(n){for(var t,e,r=Hl||++Zl,i=Ku(n),u=[],o=Ol||{time:Date.now(),ease:Nr,delay:0,duration:250},a=-1,l=this.length;++au;u++){i.push(t=[]);for(var e=this[u],a=0,l=e.length;l>a;a++)(r=e[a])&&n.call(r,r.__data__,a,u)&&t.push(r)}return Wu(i,this.namespace,this.id)},Yl.tween=function(n,t){var e=this.id,r=this.namespace;return arguments.length<2?this.node()[r][e].tween.get(n):Y(this,null==t?function(t){t[r][e].tween.remove(n)}:function(i){i[r][e].tween.set(n,t)})},Yl.attr=function(n,t){function e(){this.removeAttribute(a)}function r(){this.removeAttributeNS(a.space,a.local)}function i(n){return null==n?e:(n+="",function(){var t,e=this.getAttribute(a);return e!==n&&(t=o(e,n),function(n){this.setAttribute(a,t(n))})})}function u(n){return null==n?r:(n+="",function(){var t,e=this.getAttributeNS(a.space,a.local);return e!==n&&(t=o(e,n),function(n){this.setAttributeNS(a.space,a.local,t(n))})})}if(arguments.length<2){for(t in n)this.attr(t,n[t]);return this}var o="transform"==n?$r:Mr,a=ao.ns.qualify(n);return Ju(this,"attr."+n,t,a.local?u:i)},Yl.attrTween=function(n,t){function e(n,e){var r=t.call(this,n,e,this.getAttribute(i));return r&&function(n){this.setAttribute(i,r(n))}}function r(n,e){var r=t.call(this,n,e,this.getAttributeNS(i.space,i.local));return r&&function(n){this.setAttributeNS(i.space,i.local,r(n))}}var i=ao.ns.qualify(n);return this.tween("attr."+n,i.local?r:e)},Yl.style=function(n,e,r){function i(){this.style.removeProperty(n)}function u(e){return null==e?i:(e+="",function(){var i,u=t(this).getComputedStyle(this,null).getPropertyValue(n);return u!==e&&(i=Mr(u,e),function(t){this.style.setProperty(n,i(t),r)})})}var o=arguments.length;if(3>o){if("string"!=typeof n){2>o&&(e="");for(r in n)this.style(r,n[r],e);return this}r=""}return Ju(this,"style."+n,e,u)},Yl.styleTween=function(n,e,r){function i(i,u){var o=e.call(this,i,u,t(this).getComputedStyle(this,null).getPropertyValue(n));return o&&function(t){this.style.setProperty(n,o(t),r)}}return arguments.length<3&&(r=""),this.tween("style."+n,i)},Yl.text=function(n){return Ju(this,"text",n,Gu)},Yl.remove=function(){var n=this.namespace;return this.each("end.transition",function(){var t;this[n].count<2&&(t=this.parentNode)&&t.removeChild(this)})},Yl.ease=function(n){var t=this.id,e=this.namespace;return arguments.length<1?this.node()[e][t].ease:("function"!=typeof n&&(n=ao.ease.apply(ao,arguments)),Y(this,function(r){r[e][t].ease=n}))},Yl.delay=function(n){var t=this.id,e=this.namespace;return arguments.length<1?this.node()[e][t].delay:Y(this,"function"==typeof n?function(r,i,u){r[e][t].delay=+n.call(r,r.__data__,i,u)}:(n=+n,function(r){r[e][t].delay=n}))},Yl.duration=function(n){var t=this.id,e=this.namespace;return arguments.length<1?this.node()[e][t].duration:Y(this,"function"==typeof n?function(r,i,u){r[e][t].duration=Math.max(1,n.call(r,r.__data__,i,u))}:(n=Math.max(1,n),function(r){r[e][t].duration=n}))},Yl.each=function(n,t){var e=this.id,r=this.namespace;if(arguments.length<2){var i=Ol,u=Hl;try{Hl=e,Y(this,function(t,i,u){Ol=t[r][e],n.call(t,t.__data__,i,u)})}finally{Ol=i,Hl=u}}else Y(this,function(i){var u=i[r][e];(u.event||(u.event=ao.dispatch("start","end","interrupt"))).on(n,t)});return this},Yl.transition=function(){for(var n,t,e,r,i=this.id,u=++Zl,o=this.namespace,a=[],l=0,c=this.length;c>l;l++){a.push(n=[]);for(var t=this[l],f=0,s=t.length;s>f;f++)(e=t[f])&&(r=e[o][i],Qu(e,f,o,u,{time:r.time,ease:r.ease,delay:r.delay+r.duration,duration:r.duration})),n.push(e)}return Wu(a,o,u)},ao.svg.axis=function(){function n(n){n.each(function(){var n,c=ao.select(this),f=this.__chart__||e,s=this.__chart__=e.copy(),h=null==l?s.ticks?s.ticks.apply(s,a):s.domain():l,p=null==t?s.tickFormat?s.tickFormat.apply(s,a):m:t,g=c.selectAll(".tick").data(h,s),v=g.enter().insert("g",".domain").attr("class","tick").style("opacity",Uo),d=ao.transition(g.exit()).style("opacity",Uo).remove(),y=ao.transition(g.order()).style("opacity",1),M=Math.max(i,0)+o,x=Zi(s),b=c.selectAll(".domain").data([0]),_=(b.enter().append("path").attr("class","domain"),ao.transition(b));v.append("line"),v.append("text");var w,S,k,N,E=v.select("line"),A=y.select("line"),C=g.select("text").text(p),z=v.select("text"),L=y.select("text"),q="top"===r||"left"===r?-1:1;if("bottom"===r||"top"===r?(n=no,w="x",k="y",S="x2",N="y2",C.attr("dy",0>q?"0em":".71em").style("text-anchor","middle"),_.attr("d","M"+x[0]+","+q*u+"V0H"+x[1]+"V"+q*u)):(n=to,w="y",k="x",S="y2",N="x2",C.attr("dy",".32em").style("text-anchor",0>q?"end":"start"),_.attr("d","M"+q*u+","+x[0]+"H0V"+x[1]+"H"+q*u)),E.attr(N,q*i),z.attr(k,q*M),A.attr(S,0).attr(N,q*i),L.attr(w,0).attr(k,q*M),s.rangeBand){var T=s,R=T.rangeBand()/2;f=s=function(n){return T(n)+R}}else f.rangeBand?f=s:d.call(n,s,f);v.call(n,f,s),y.call(n,s,s)})}var t,e=ao.scale.linear(),r=Vl,i=6,u=6,o=3,a=[10],l=null;return n.scale=function(t){return arguments.length?(e=t,n):e},n.orient=function(t){return arguments.length?(r=t in Xl?t+"":Vl,n):r},n.ticks=function(){return arguments.length?(a=co(arguments),n):a},n.tickValues=function(t){return arguments.length?(l=t,n):l},n.tickFormat=function(e){return arguments.length?(t=e,n):t},n.tickSize=function(t){var e=arguments.length;return e?(i=+t,u=+arguments[e-1],n):i},n.innerTickSize=function(t){return arguments.length?(i=+t,n):i},n.outerTickSize=function(t){return arguments.length?(u=+t,n):u},n.tickPadding=function(t){return arguments.length?(o=+t,n):o},n.tickSubdivide=function(){return arguments.length&&n},n};var Vl="bottom",Xl={top:1,right:1,bottom:1,left:1};ao.svg.brush=function(){function n(t){t.each(function(){var t=ao.select(this).style("pointer-events","all").style("-webkit-tap-highlight-color","rgba(0,0,0,0)").on("mousedown.brush",u).on("touchstart.brush",u),o=t.selectAll(".background").data([0]);o.enter().append("rect").attr("class","background").style("visibility","hidden").style("cursor","crosshair"),t.selectAll(".extent").data([0]).enter().append("rect").attr("class","extent").style("cursor","move");var a=t.selectAll(".resize").data(v,m);a.exit().remove(),a.enter().append("g").attr("class",function(n){return"resize "+n}).style("cursor",function(n){return $l[n]}).append("rect").attr("x",function(n){return/[ew]$/.test(n)?-3:null}).attr("y",function(n){return/^[ns]/.test(n)?-3:null}).attr("width",6).attr("height",6).style("visibility","hidden"),a.style("display",n.empty()?"none":null);var l,s=ao.transition(t),h=ao.transition(o);c&&(l=Zi(c),h.attr("x",l[0]).attr("width",l[1]-l[0]),r(s)),f&&(l=Zi(f),h.attr("y",l[0]).attr("height",l[1]-l[0]),i(s)),e(s)})}function e(n){n.selectAll(".resize").attr("transform",function(n){return"translate("+s[+/e$/.test(n)]+","+h[+/^s/.test(n)]+")"})}function r(n){n.select(".extent").attr("x",s[0]),n.selectAll(".extent,.n>rect,.s>rect").attr("width",s[1]-s[0])}function i(n){n.select(".extent").attr("y",h[0]),n.selectAll(".extent,.e>rect,.w>rect").attr("height",h[1]-h[0])}function u(){function u(){32==ao.event.keyCode&&(C||(M=null,L[0]-=s[1],L[1]-=h[1],C=2),S())}function v(){32==ao.event.keyCode&&2==C&&(L[0]+=s[1],L[1]+=h[1],C=0,S())}function d(){var n=ao.mouse(b),t=!1;x&&(n[0]+=x[0],n[1]+=x[1]),C||(ao.event.altKey?(M||(M=[(s[0]+s[1])/2,(h[0]+h[1])/2]),L[0]=s[+(n[0]f?(i=r,r=f):i=f),v[0]!=r||v[1]!=i?(e?a=null:o=null,v[0]=r,v[1]=i,!0):void 0}function m(){d(),k.style("pointer-events","all").selectAll(".resize").style("display",n.empty()?"none":null),ao.select("body").style("cursor",null),q.on("mousemove.brush",null).on("mouseup.brush",null).on("touchmove.brush",null).on("touchend.brush",null).on("keydown.brush",null).on("keyup.brush",null),z(),w({type:"brushend"})}var M,x,b=this,_=ao.select(ao.event.target),w=l.of(b,arguments),k=ao.select(b),N=_.datum(),E=!/^(n|s)$/.test(N)&&c,A=!/^(e|w)$/.test(N)&&f,C=_.classed("extent"),z=W(b),L=ao.mouse(b),q=ao.select(t(b)).on("keydown.brush",u).on("keyup.brush",v);if(ao.event.changedTouches?q.on("touchmove.brush",d).on("touchend.brush",m):q.on("mousemove.brush",d).on("mouseup.brush",m),k.interrupt().selectAll("*").interrupt(),C)L[0]=s[0]-L[0],L[1]=h[0]-L[1];else if(N){var T=+/w$/.test(N),R=+/^n/.test(N);x=[s[1-T]-L[0],h[1-R]-L[1]],L[0]=s[T],L[1]=h[R]}else ao.event.altKey&&(M=L.slice());k.style("pointer-events","none").selectAll(".resize").style("display",null),ao.select("body").style("cursor",_.style("cursor")),w({type:"brushstart"}),d()}var o,a,l=N(n,"brushstart","brush","brushend"),c=null,f=null,s=[0,0],h=[0,0],p=!0,g=!0,v=Bl[0];return n.event=function(n){n.each(function(){var n=l.of(this,arguments),t={x:s,y:h,i:o,j:a},e=this.__chart__||t;this.__chart__=t,Hl?ao.select(this).transition().each("start.brush",function(){o=e.i,a=e.j,s=e.x,h=e.y,n({type:"brushstart"})}).tween("brush:brush",function(){var e=xr(s,t.x),r=xr(h,t.y);return o=a=null,function(i){s=t.x=e(i),h=t.y=r(i),n({type:"brush",mode:"resize"})}}).each("end.brush",function(){o=t.i,a=t.j,n({type:"brush",mode:"resize"}),n({type:"brushend"})}):(n({type:"brushstart"}),n({type:"brush",mode:"resize"}),n({type:"brushend"}))})},n.x=function(t){return arguments.length?(c=t,v=Bl[!c<<1|!f],n):c},n.y=function(t){return arguments.length?(f=t,v=Bl[!c<<1|!f],n):f},n.clamp=function(t){return arguments.length?(c&&f?(p=!!t[0],g=!!t[1]):c?p=!!t:f&&(g=!!t),n):c&&f?[p,g]:c?p:f?g:null},n.extent=function(t){var e,r,i,u,l;return arguments.length?(c&&(e=t[0],r=t[1],f&&(e=e[0],r=r[0]),o=[e,r],c.invert&&(e=c(e),r=c(r)),e>r&&(l=e,e=r,r=l),e==s[0]&&r==s[1]||(s=[e,r])),f&&(i=t[0],u=t[1],c&&(i=i[1],u=u[1]),a=[i,u],f.invert&&(i=f(i),u=f(u)),i>u&&(l=i,i=u,u=l),i==h[0]&&u==h[1]||(h=[i,u])),n):(c&&(o?(e=o[0],r=o[1]):(e=s[0],r=s[1],c.invert&&(e=c.invert(e),r=c.invert(r)),e>r&&(l=e,e=r,r=l))),f&&(a?(i=a[0],u=a[1]):(i=h[0],u=h[1],f.invert&&(i=f.invert(i),u=f.invert(u)),i>u&&(l=i,i=u,u=l))),c&&f?[[e,i],[r,u]]:c?[e,r]:f&&[i,u])},n.clear=function(){return n.empty()||(s=[0,0],h=[0,0],o=a=null),n},n.empty=function(){return!!c&&s[0]==s[1]||!!f&&h[0]==h[1]},ao.rebind(n,l,"on")};var $l={n:"ns-resize",e:"ew-resize",s:"ns-resize",w:"ew-resize",nw:"nwse-resize",ne:"nesw-resize",se:"nwse-resize",sw:"nesw-resize"},Bl=[["n","e","s","w","nw","ne","se","sw"],["e","w"],["n","s"],[]],Wl=ga.format=xa.timeFormat,Jl=Wl.utc,Gl=Jl("%Y-%m-%dT%H:%M:%S.%LZ");Wl.iso=Date.prototype.toISOString&&+new Date("2000-01-01T00:00:00.000Z")?eo:Gl,eo.parse=function(n){var t=new Date(n);return isNaN(t)?null:t},eo.toString=Gl.toString,ga.second=On(function(n){return new va(1e3*Math.floor(n/1e3))},function(n,t){n.setTime(n.getTime()+1e3*Math.floor(t))},function(n){return n.getSeconds()}),ga.seconds=ga.second.range,ga.seconds.utc=ga.second.utc.range,ga.minute=On(function(n){return new va(6e4*Math.floor(n/6e4))},function(n,t){n.setTime(n.getTime()+6e4*Math.floor(t))},function(n){return n.getMinutes()}),ga.minutes=ga.minute.range,ga.minutes.utc=ga.minute.utc.range,ga.hour=On(function(n){var t=n.getTimezoneOffset()/60;return new va(36e5*(Math.floor(n/36e5-t)+t))},function(n,t){n.setTime(n.getTime()+36e5*Math.floor(t))},function(n){return n.getHours()}),ga.hours=ga.hour.range,ga.hours.utc=ga.hour.utc.range,ga.month=On(function(n){return n=ga.day(n),n.setDate(1),n},function(n,t){n.setMonth(n.getMonth()+t)},function(n){return n.getMonth()}),ga.months=ga.month.range,ga.months.utc=ga.month.utc.range;var Kl=[1e3,5e3,15e3,3e4,6e4,3e5,9e5,18e5,36e5,108e5,216e5,432e5,864e5,1728e5,6048e5,2592e6,7776e6,31536e6],Ql=[[ga.second,1],[ga.second,5],[ga.second,15],[ga.second,30],[ga.minute,1],[ga.minute,5],[ga.minute,15],[ga.minute,30],[ga.hour,1],[ga.hour,3],[ga.hour,6],[ga.hour,12],[ga.day,1],[ga.day,2],[ga.week,1],[ga.month,1],[ga.month,3],[ga.year,1]],nc=Wl.multi([[".%L",function(n){return n.getMilliseconds()}],[":%S",function(n){return n.getSeconds()}],["%I:%M",function(n){return n.getMinutes()}],["%I %p",function(n){return n.getHours()}],["%a %d",function(n){return n.getDay()&&1!=n.getDate()}],["%b %d",function(n){return 1!=n.getDate()}],["%B",function(n){return n.getMonth()}],["%Y",zt]]),tc={range:function(n,t,e){return ao.range(Math.ceil(n/e)*e,+t,e).map(io)},floor:m,ceil:m};Ql.year=ga.year,ga.scale=function(){return ro(ao.scale.linear(),Ql,nc)};var ec=Ql.map(function(n){return[n[0].utc,n[1]]}),rc=Jl.multi([[".%L",function(n){return n.getUTCMilliseconds()}],[":%S",function(n){return n.getUTCSeconds()}],["%I:%M",function(n){return n.getUTCMinutes()}],["%I %p",function(n){return n.getUTCHours()}],["%a %d",function(n){return n.getUTCDay()&&1!=n.getUTCDate()}],["%b %d",function(n){return 1!=n.getUTCDate()}],["%B",function(n){return n.getUTCMonth()}],["%Y",zt]]);ec.year=ga.year.utc,ga.scale.utc=function(){return ro(ao.scale.linear(),ec,rc)},ao.text=An(function(n){return n.responseText}),ao.json=function(n,t){return Cn(n,"application/json",uo,t)},ao.html=function(n,t){return Cn(n,"text/html",oo,t)},ao.xml=An(function(n){return n.responseXML}),"function"==typeof define&&define.amd?(this.d3=ao,define(ao)):"object"==typeof module&&module.exports?module.exports=ao:this.d3=ao}(); \ No newline at end of file diff --git a/admin/static/js/jquery.min.js b/admin/static/js/jquery.min.js index 36b4e1a1..6c60672f 100644 --- a/admin/static/js/jquery.min.js +++ b/admin/static/js/jquery.min.js @@ -1,2 +1,5 @@ -/*! jQuery v3.5.1 -ajax,-ajax/jsonp,-ajax/load,-ajax/script,-ajax/var/location,-ajax/var/nonce,-ajax/var/rquery,-ajax/xhr,-manipulation/_evalUrl,-deprecated/ajax-event-alias,-effects,-effects/Tween,-effects/animatedSelector | (c) JS Foundation and other contributors | jquery.org/license */ -!function(e,t){"use strict";"object"==typeof module&&"object"==typeof module.exports?module.exports=e.document?t(e,!0):function(e){if(!e.document)throw new Error("jQuery requires a window with a document");return t(e)}:t(e)}("undefined"!=typeof window?window:this,function(g,e){"use strict";var t=[],r=Object.getPrototypeOf,s=t.slice,v=t.flat?function(e){return t.flat.call(e)}:function(e){return t.concat.apply([],e)},u=t.push,i=t.indexOf,n={},o=n.toString,y=n.hasOwnProperty,a=y.toString,l=a.call(Object),m={},b=function(e){return"function"==typeof e&&"number"!=typeof e.nodeType},x=function(e){return null!=e&&e===e.window},w=g.document,c={type:!0,src:!0,nonce:!0,noModule:!0};function C(e,t,n){var r,i,o=(n=n||w).createElement("script");if(o.text=e,t)for(r in c)(i=t[r]||t.getAttribute&&t.getAttribute(r))&&o.setAttribute(r,i);n.head.appendChild(o).parentNode.removeChild(o)}function T(e){return null==e?e+"":"object"==typeof e||"function"==typeof e?n[o.call(e)]||"object":typeof e}var f="3.5.1 -ajax,-ajax/jsonp,-ajax/load,-ajax/script,-ajax/var/location,-ajax/var/nonce,-ajax/var/rquery,-ajax/xhr,-manipulation/_evalUrl,-deprecated/ajax-event-alias,-effects,-effects/Tween,-effects/animatedSelector",E=function(e,t){return new E.fn.init(e,t)};function d(e){var t=!!e&&"length"in e&&e.length,n=T(e);return!b(e)&&!x(e)&&("array"===n||0===t||"number"==typeof t&&0+~]|"+R+")"+R+"*"),U=new RegExp(R+"|>"),V=new RegExp(W),X=new RegExp("^"+B+"$"),Q={ID:new RegExp("^#("+B+")"),CLASS:new RegExp("^\\.("+B+")"),TAG:new RegExp("^("+B+"|[*])"),ATTR:new RegExp("^"+M),PSEUDO:new RegExp("^"+W),CHILD:new RegExp("^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\("+R+"*(even|odd|(([+-]|)(\\d*)n|)"+R+"*(?:([+-]|)"+R+"*(\\d+)|))"+R+"*\\)|)","i"),bool:new RegExp("^(?:"+I+")$","i"),needsContext:new RegExp("^"+R+"*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\("+R+"*((?:-\\d)?\\d*)"+R+"*\\)|)(?=[^-]|$)","i")},Y=/HTML$/i,G=/^(?:input|select|textarea|button)$/i,K=/^h\d$/i,J=/^[^{]+\{\s*\[native \w/,Z=/^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/,ee=/[+~]/,te=new RegExp("\\\\[\\da-fA-F]{1,6}"+R+"?|\\\\([^\\r\\n\\f])","g"),ne=function(e,t){var n="0x"+e.slice(1)-65536;return t||(n<0?String.fromCharCode(n+65536):String.fromCharCode(n>>10|55296,1023&n|56320))},re=/([\0-\x1f\x7f]|^-?\d)|^-$|[^\0-\x1f\x7f-\uFFFF\w-]/g,ie=function(e,t){return t?"\0"===e?"\ufffd":e.slice(0,-1)+"\\"+e.charCodeAt(e.length-1).toString(16)+" ":"\\"+e},oe=function(){C()},ae=xe(function(e){return!0===e.disabled&&"fieldset"===e.nodeName.toLowerCase()},{dir:"parentNode",next:"legend"});try{O.apply(t=P.call(d.childNodes),d.childNodes),t[d.childNodes.length].nodeType}catch(e){O={apply:t.length?function(e,t){q.apply(e,P.call(t))}:function(e,t){var n=e.length,r=0;while(e[n++]=t[r++]);e.length=n-1}}}function se(t,e,n,r){var i,o,a,s,u,l,c,f=e&&e.ownerDocument,d=e?e.nodeType:9;if(n=n||[],"string"!=typeof t||!t||1!==d&&9!==d&&11!==d)return n;if(!r&&(C(e),e=e||T,E)){if(11!==d&&(u=Z.exec(t)))if(i=u[1]){if(9===d){if(!(a=e.getElementById(i)))return n;if(a.id===i)return n.push(a),n}else if(f&&(a=f.getElementById(i))&&y(e,a)&&a.id===i)return n.push(a),n}else{if(u[2])return O.apply(n,e.getElementsByTagName(t)),n;if((i=u[3])&&p.getElementsByClassName&&e.getElementsByClassName)return O.apply(n,e.getElementsByClassName(i)),n}if(p.qsa&&!k[t+" "]&&(!v||!v.test(t))&&(1!==d||"object"!==e.nodeName.toLowerCase())){if(c=t,f=e,1===d&&(U.test(t)||_.test(t))){(f=ee.test(t)&&ye(e.parentNode)||e)===e&&p.scope||((s=e.getAttribute("id"))?s=s.replace(re,ie):e.setAttribute("id",s=A)),o=(l=h(t)).length;while(o--)l[o]=(s?"#"+s:":scope")+" "+be(l[o]);c=l.join(",")}try{return O.apply(n,f.querySelectorAll(c)),n}catch(e){k(t,!0)}finally{s===A&&e.removeAttribute("id")}}}return g(t.replace($,"$1"),e,n,r)}function ue(){var r=[];return function e(t,n){return r.push(t+" ")>x.cacheLength&&delete e[r.shift()],e[t+" "]=n}}function le(e){return e[A]=!0,e}function ce(e){var t=T.createElement("fieldset");try{return!!e(t)}catch(e){return!1}finally{t.parentNode&&t.parentNode.removeChild(t),t=null}}function fe(e,t){var n=e.split("|"),r=n.length;while(r--)x.attrHandle[n[r]]=t}function de(e,t){var n=t&&e,r=n&&1===e.nodeType&&1===t.nodeType&&e.sourceIndex-t.sourceIndex;if(r)return r;if(n)while(n=n.nextSibling)if(n===t)return-1;return e?1:-1}function pe(t){return function(e){return"input"===e.nodeName.toLowerCase()&&e.type===t}}function he(n){return function(e){var t=e.nodeName.toLowerCase();return("input"===t||"button"===t)&&e.type===n}}function ge(t){return function(e){return"form"in e?e.parentNode&&!1===e.disabled?"label"in e?"label"in e.parentNode?e.parentNode.disabled===t:e.disabled===t:e.isDisabled===t||e.isDisabled!==!t&&ae(e)===t:e.disabled===t:"label"in e&&e.disabled===t}}function ve(a){return le(function(o){return o=+o,le(function(e,t){var n,r=a([],e.length,o),i=r.length;while(i--)e[n=r[i]]&&(e[n]=!(t[n]=e[n]))})})}function ye(e){return e&&"undefined"!=typeof e.getElementsByTagName&&e}for(e in p=se.support={},i=se.isXML=function(e){var t=e.namespaceURI,n=(e.ownerDocument||e).documentElement;return!Y.test(t||n&&n.nodeName||"HTML")},C=se.setDocument=function(e){var t,n,r=e?e.ownerDocument||e:d;return r!=T&&9===r.nodeType&&r.documentElement&&(a=(T=r).documentElement,E=!i(T),d!=T&&(n=T.defaultView)&&n.top!==n&&(n.addEventListener?n.addEventListener("unload",oe,!1):n.attachEvent&&n.attachEvent("onunload",oe)),p.scope=ce(function(e){return a.appendChild(e).appendChild(T.createElement("div")),"undefined"!=typeof e.querySelectorAll&&!e.querySelectorAll(":scope fieldset div").length}),p.attributes=ce(function(e){return e.className="i",!e.getAttribute("className")}),p.getElementsByTagName=ce(function(e){return e.appendChild(T.createComment("")),!e.getElementsByTagName("*").length}),p.getElementsByClassName=J.test(T.getElementsByClassName),p.getById=ce(function(e){return a.appendChild(e).id=A,!T.getElementsByName||!T.getElementsByName(A).length}),p.getById?(x.filter.ID=function(e){var t=e.replace(te,ne);return function(e){return e.getAttribute("id")===t}},x.find.ID=function(e,t){if("undefined"!=typeof t.getElementById&&E){var n=t.getElementById(e);return n?[n]:[]}}):(x.filter.ID=function(e){var n=e.replace(te,ne);return function(e){var t="undefined"!=typeof e.getAttributeNode&&e.getAttributeNode("id");return t&&t.value===n}},x.find.ID=function(e,t){if("undefined"!=typeof t.getElementById&&E){var n,r,i,o=t.getElementById(e);if(o){if((n=o.getAttributeNode("id"))&&n.value===e)return[o];i=t.getElementsByName(e),r=0;while(o=i[r++])if((n=o.getAttributeNode("id"))&&n.value===e)return[o]}return[]}}),x.find.TAG=p.getElementsByTagName?function(e,t){return"undefined"!=typeof t.getElementsByTagName?t.getElementsByTagName(e):p.qsa?t.querySelectorAll(e):void 0}:function(e,t){var n,r=[],i=0,o=t.getElementsByTagName(e);if("*"===e){while(n=o[i++])1===n.nodeType&&r.push(n);return r}return o},x.find.CLASS=p.getElementsByClassName&&function(e,t){if("undefined"!=typeof t.getElementsByClassName&&E)return t.getElementsByClassName(e)},s=[],v=[],(p.qsa=J.test(T.querySelectorAll))&&(ce(function(e){var t;a.appendChild(e).innerHTML="",e.querySelectorAll("[msallowcapture^='']").length&&v.push("[*^$]="+R+"*(?:''|\"\")"),e.querySelectorAll("[selected]").length||v.push("\\["+R+"*(?:value|"+I+")"),e.querySelectorAll("[id~="+A+"-]").length||v.push("~="),(t=T.createElement("input")).setAttribute("name",""),e.appendChild(t),e.querySelectorAll("[name='']").length||v.push("\\["+R+"*name"+R+"*="+R+"*(?:''|\"\")"),e.querySelectorAll(":checked").length||v.push(":checked"),e.querySelectorAll("a#"+A+"+*").length||v.push(".#.+[+~]"),e.querySelectorAll("\\\f"),v.push("[\\r\\n\\f]")}),ce(function(e){e.innerHTML="";var t=T.createElement("input");t.setAttribute("type","hidden"),e.appendChild(t).setAttribute("name","D"),e.querySelectorAll("[name=d]").length&&v.push("name"+R+"*[*^$|!~]?="),2!==e.querySelectorAll(":enabled").length&&v.push(":enabled",":disabled"),a.appendChild(e).disabled=!0,2!==e.querySelectorAll(":disabled").length&&v.push(":enabled",":disabled"),e.querySelectorAll("*,:x"),v.push(",.*:")})),(p.matchesSelector=J.test(c=a.matches||a.webkitMatchesSelector||a.mozMatchesSelector||a.oMatchesSelector||a.msMatchesSelector))&&ce(function(e){p.disconnectedMatch=c.call(e,"*"),c.call(e,"[s!='']:x"),s.push("!=",W)}),v=v.length&&new RegExp(v.join("|")),s=s.length&&new RegExp(s.join("|")),t=J.test(a.compareDocumentPosition),y=t||J.test(a.contains)?function(e,t){var n=9===e.nodeType?e.documentElement:e,r=t&&t.parentNode;return e===r||!(!r||1!==r.nodeType||!(n.contains?n.contains(r):e.compareDocumentPosition&&16&e.compareDocumentPosition(r)))}:function(e,t){if(t)while(t=t.parentNode)if(t===e)return!0;return!1},D=t?function(e,t){if(e===t)return l=!0,0;var n=!e.compareDocumentPosition-!t.compareDocumentPosition;return n||(1&(n=(e.ownerDocument||e)==(t.ownerDocument||t)?e.compareDocumentPosition(t):1)||!p.sortDetached&&t.compareDocumentPosition(e)===n?e==T||e.ownerDocument==d&&y(d,e)?-1:t==T||t.ownerDocument==d&&y(d,t)?1:u?H(u,e)-H(u,t):0:4&n?-1:1)}:function(e,t){if(e===t)return l=!0,0;var n,r=0,i=e.parentNode,o=t.parentNode,a=[e],s=[t];if(!i||!o)return e==T?-1:t==T?1:i?-1:o?1:u?H(u,e)-H(u,t):0;if(i===o)return de(e,t);n=e;while(n=n.parentNode)a.unshift(n);n=t;while(n=n.parentNode)s.unshift(n);while(a[r]===s[r])r++;return r?de(a[r],s[r]):a[r]==d?-1:s[r]==d?1:0}),T},se.matches=function(e,t){return se(e,null,null,t)},se.matchesSelector=function(e,t){if(C(e),p.matchesSelector&&E&&!k[t+" "]&&(!s||!s.test(t))&&(!v||!v.test(t)))try{var n=c.call(e,t);if(n||p.disconnectedMatch||e.document&&11!==e.document.nodeType)return n}catch(e){k(t,!0)}return 0":{dir:"parentNode",first:!0}," ":{dir:"parentNode"},"+":{dir:"previousSibling",first:!0},"~":{dir:"previousSibling"}},preFilter:{ATTR:function(e){return e[1]=e[1].replace(te,ne),e[3]=(e[3]||e[4]||e[5]||"").replace(te,ne),"~="===e[2]&&(e[3]=" "+e[3]+" "),e.slice(0,4)},CHILD:function(e){return e[1]=e[1].toLowerCase(),"nth"===e[1].slice(0,3)?(e[3]||se.error(e[0]),e[4]=+(e[4]?e[5]+(e[6]||1):2*("even"===e[3]||"odd"===e[3])),e[5]=+(e[7]+e[8]||"odd"===e[3])):e[3]&&se.error(e[0]),e},PSEUDO:function(e){var t,n=!e[6]&&e[2];return Q.CHILD.test(e[0])?null:(e[3]?e[2]=e[4]||e[5]||"":n&&V.test(n)&&(t=h(n,!0))&&(t=n.indexOf(")",n.length-t)-n.length)&&(e[0]=e[0].slice(0,t),e[2]=n.slice(0,t)),e.slice(0,3))}},filter:{TAG:function(e){var t=e.replace(te,ne).toLowerCase();return"*"===e?function(){return!0}:function(e){return e.nodeName&&e.nodeName.toLowerCase()===t}},CLASS:function(e){var t=m[e+" "];return t||(t=new RegExp("(^|"+R+")"+e+"("+R+"|$)"))&&m(e,function(e){return t.test("string"==typeof e.className&&e.className||"undefined"!=typeof e.getAttribute&&e.getAttribute("class")||"")})},ATTR:function(n,r,i){return function(e){var t=se.attr(e,n);return null==t?"!="===r:!r||(t+="","="===r?t===i:"!="===r?t!==i:"^="===r?i&&0===t.indexOf(i):"*="===r?i&&-1:\x20\t\r\n\f]*)[\x20\t\r\n\f]*\/?>(?:<\/\1>|)$/i;function D(e,n,r){return b(n)?E.grep(e,function(e,t){return!!n.call(e,t,e)!==r}):n.nodeType?E.grep(e,function(e){return e===n!==r}):"string"!=typeof n?E.grep(e,function(e){return-1)[^>]*|#([\w-]+))$/;(E.fn.init=function(e,t,n){var r,i;if(!e)return this;if(n=n||L,"string"==typeof e){if(!(r="<"===e[0]&&">"===e[e.length-1]&&3<=e.length?[null,e,null]:j.exec(e))||!r[1]&&t)return!t||t.jquery?(t||n).find(e):this.constructor(t).find(e);if(r[1]){if(t=t instanceof E?t[0]:t,E.merge(this,E.parseHTML(r[1],t&&t.nodeType?t.ownerDocument||t:w,!0)),k.test(r[1])&&E.isPlainObject(t))for(r in t)b(this[r])?this[r](t[r]):this.attr(r,t[r]);return this}return(i=w.getElementById(r[2]))&&(this[0]=i,this.length=1),this}return e.nodeType?(this[0]=e,this.length=1,this):b(e)?void 0!==n.ready?n.ready(e):e(E):E.makeArray(e,this)}).prototype=E.fn,L=E(w);var q=/^(?:parents|prev(?:Until|All))/,O={children:!0,contents:!0,next:!0,prev:!0};function P(e,t){while((e=e[t])&&1!==e.nodeType);return e}E.fn.extend({has:function(e){var t=E(e,this),n=t.length;return this.filter(function(){for(var e=0;e\x20\t\r\n\f]*)/i,pe=/^$|^module$|\/(?:java|ecma)script/i;le=w.createDocumentFragment().appendChild(w.createElement("div")),(ce=w.createElement("input")).setAttribute("type","radio"),ce.setAttribute("checked","checked"),ce.setAttribute("name","t"),le.appendChild(ce),m.checkClone=le.cloneNode(!0).cloneNode(!0).lastChild.checked,le.innerHTML="",m.noCloneChecked=!!le.cloneNode(!0).lastChild.defaultValue,le.innerHTML="",m.option=!!le.lastChild;var he={thead:[1,"","
    "],col:[2,"","
    "],tr:[2,"","
    "],td:[3,"","
    "],_default:[0,"",""]};function ge(e,t){var n;return n="undefined"!=typeof e.getElementsByTagName?e.getElementsByTagName(t||"*"):"undefined"!=typeof e.querySelectorAll?e.querySelectorAll(t||"*"):[],void 0===t||t&&S(e,t)?E.merge([e],n):n}function ve(e,t){for(var n=0,r=e.length;n",""]);var ye=/<|&#?\w+;/;function me(e,t,n,r,i){for(var o,a,s,u,l,c,f=t.createDocumentFragment(),d=[],p=0,h=e.length;p\s*$/g;function Le(e,t){return S(e,"table")&&S(11!==t.nodeType?t:t.firstChild,"tr")&&E(e).children("tbody")[0]||e}function je(e){return e.type=(null!==e.getAttribute("type"))+"/"+e.type,e}function qe(e){return"true/"===(e.type||"").slice(0,5)?e.type=e.type.slice(5):e.removeAttribute("type"),e}function Oe(e,t){var n,r,i,o,a,s;if(1===t.nodeType){if(Y.hasData(e)&&(s=Y.get(e).events))for(i in Y.remove(t,"handle events"),s)for(n=0,r=s[i].length;n
    ",2===ft.childNodes.length),E.parseHTML=function(e,t,n){return"string"!=typeof e?[]:("boolean"==typeof t&&(n=t,t=!1),t||(m.createHTMLDocument?((r=(t=w.implementation.createHTMLDocument("")).createElement("base")).href=w.location.href,t.head.appendChild(r)):t=w),o=!n&&[],(i=k.exec(e))?[t.createElement(i[1])]:(i=me([e],t,o),o&&o.length&&E(o).remove(),E.merge([],i.childNodes)));var r,i,o},E.offset={setOffset:function(e,t,n){var r,i,o,a,s,u,l=E.css(e,"position"),c=E(e),f={};"static"===l&&(e.style.position="relative"),s=c.offset(),o=E.css(e,"top"),u=E.css(e,"left"),("absolute"===l||"fixed"===l)&&-1<(o+u).indexOf("auto")?(a=(r=c.position()).top,i=r.left):(a=parseFloat(o)||0,i=parseFloat(u)||0),b(t)&&(t=t.call(e,n,E.extend({},s))),null!=t.top&&(f.top=t.top-s.top+a),null!=t.left&&(f.left=t.left-s.left+i),"using"in t?t.using.call(e,f):("number"==typeof f.top&&(f.top+="px"),"number"==typeof f.left&&(f.left+="px"),c.css(f))}},E.fn.extend({offset:function(t){if(arguments.length)return void 0===t?this:this.each(function(e){E.offset.setOffset(this,t,e)});var e,n,r=this[0];return r?r.getClientRects().length?(e=r.getBoundingClientRect(),n=r.ownerDocument.defaultView,{top:e.top+n.pageYOffset,left:e.left+n.pageXOffset}):{top:0,left:0}:void 0},position:function(){if(this[0]){var e,t,n,r=this[0],i={top:0,left:0};if("fixed"===E.css(r,"position"))t=r.getBoundingClientRect();else{t=this.offset(),n=r.ownerDocument,e=r.offsetParent||n.documentElement;while(e&&(e===n.body||e===n.documentElement)&&"static"===E.css(e,"position"))e=e.parentNode;e&&e!==r&&1===e.nodeType&&((i=E(e).offset()).top+=E.css(e,"borderTopWidth",!0),i.left+=E.css(e,"borderLeftWidth",!0))}return{top:t.top-i.top-E.css(r,"marginTop",!0),left:t.left-i.left-E.css(r,"marginLeft",!0)}}},offsetParent:function(){return this.map(function(){var e=this.offsetParent;while(e&&"static"===E.css(e,"position"))e=e.offsetParent;return e||re})}}),E.each({scrollLeft:"pageXOffset",scrollTop:"pageYOffset"},function(t,i){var o="pageYOffset"===i;E.fn[t]=function(e){return $(this,function(e,t,n){var r;if(x(e)?r=e:9===e.nodeType&&(r=e.defaultView),void 0===n)return r?r[i]:e[t];r?r.scrollTo(o?r.pageXOffset:n,o?n:r.pageYOffset):e[t]=n},t,e,arguments.length)}}),E.each(["top","left"],function(e,n){E.cssHooks[n]=Fe(m.pixelPosition,function(e,t){if(t)return t=We(e,n),Ie.test(t)?E(e).position()[n]+"px":t})}),E.each({Height:"height",Width:"width"},function(a,s){E.each({padding:"inner"+a,content:s,"":"outer"+a},function(r,o){E.fn[o]=function(e,t){var n=arguments.length&&(r||"boolean"!=typeof e),i=r||(!0===e||!0===t?"margin":"border");return $(this,function(e,t,n){var r;return x(e)?0===o.indexOf("outer")?e["inner"+a]:e.document.documentElement["client"+a]:9===e.nodeType?(r=e.documentElement,Math.max(e.body["scroll"+a],r["scroll"+a],e.body["offset"+a],r["offset"+a],r["client"+a])):void 0===n?E.css(e,t,i):E.style(e,t,n,i)},s,n?e:void 0,n)}})}),E.fn.extend({bind:function(e,t,n){return this.on(e,null,t,n)},unbind:function(e,t){return this.off(e,null,t)},delegate:function(e,t,n,r){return this.on(t,e,n,r)},undelegate:function(e,t,n){return 1===arguments.length?this.off(e,"**"):this.off(t,e||"**",n)},hover:function(e,t){return this.mouseenter(e).mouseleave(t||e)}}),E.each("blur focus focusin focusout resize scroll click dblclick mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave change select submit keydown keypress keyup contextmenu".split(" "),function(e,n){E.fn[n]=function(e,t){return 0a?this[a+this.length]:this[a]:e.call(this)},pushStack:function(a){var b=n.merge(this.constructor(),a);return b.prevObject=this,b.context=this.context,b},each:function(a){return n.each(this,a)},map:function(a){return this.pushStack(n.map(this,function(b,c){return a.call(b,c,b)}))},slice:function(){return this.pushStack(e.apply(this,arguments))},first:function(){return this.eq(0)},last:function(){return this.eq(-1)},eq:function(a){var b=this.length,c=+a+(0>a?b:0);return this.pushStack(c>=0&&b>c?[this[c]]:[])},end:function(){return this.prevObject||this.constructor()},push:g,sort:c.sort,splice:c.splice},n.extend=n.fn.extend=function(){var a,b,c,d,e,f,g=arguments[0]||{},h=1,i=arguments.length,j=!1;for("boolean"==typeof g&&(j=g,g=arguments[h]||{},h++),"object"==typeof g||n.isFunction(g)||(g={}),h===i&&(g=this,h--);i>h;h++)if(null!=(e=arguments[h]))for(d in e)a=g[d],c=e[d],g!==c&&(j&&c&&(n.isPlainObject(c)||(b=n.isArray(c)))?(b?(b=!1,f=a&&n.isArray(a)?a:[]):f=a&&n.isPlainObject(a)?a:{},g[d]=n.extend(j,f,c)):void 0!==c&&(g[d]=c));return g},n.extend({expando:"jQuery"+(m+Math.random()).replace(/\D/g,""),isReady:!0,error:function(a){throw new Error(a)},noop:function(){},isFunction:function(a){return"function"===n.type(a)},isArray:Array.isArray||function(a){return"array"===n.type(a)},isWindow:function(a){return null!=a&&a==a.window},isNumeric:function(a){var b=a&&a.toString();return!n.isArray(a)&&b-parseFloat(b)+1>=0},isEmptyObject:function(a){var b;for(b in a)return!1;return!0},isPlainObject:function(a){var b;if(!a||"object"!==n.type(a)||a.nodeType||n.isWindow(a))return!1;try{if(a.constructor&&!k.call(a,"constructor")&&!k.call(a.constructor.prototype,"isPrototypeOf"))return!1}catch(c){return!1}if(!l.ownFirst)for(b in a)return k.call(a,b);for(b in a);return void 0===b||k.call(a,b)},type:function(a){return null==a?a+"":"object"==typeof a||"function"==typeof a?i[j.call(a)]||"object":typeof a},globalEval:function(b){b&&n.trim(b)&&(a.execScript||function(b){a.eval.call(a,b)})(b)},camelCase:function(a){return a.replace(p,"ms-").replace(q,r)},nodeName:function(a,b){return a.nodeName&&a.nodeName.toLowerCase()===b.toLowerCase()},each:function(a,b){var c,d=0;if(s(a)){for(c=a.length;c>d;d++)if(b.call(a[d],d,a[d])===!1)break}else for(d in a)if(b.call(a[d],d,a[d])===!1)break;return a},trim:function(a){return null==a?"":(a+"").replace(o,"")},makeArray:function(a,b){var c=b||[];return null!=a&&(s(Object(a))?n.merge(c,"string"==typeof a?[a]:a):g.call(c,a)),c},inArray:function(a,b,c){var d;if(b){if(h)return h.call(b,a,c);for(d=b.length,c=c?0>c?Math.max(0,d+c):c:0;d>c;c++)if(c in b&&b[c]===a)return c}return-1},merge:function(a,b){var c=+b.length,d=0,e=a.length;while(c>d)a[e++]=b[d++];if(c!==c)while(void 0!==b[d])a[e++]=b[d++];return a.length=e,a},grep:function(a,b,c){for(var d,e=[],f=0,g=a.length,h=!c;g>f;f++)d=!b(a[f],f),d!==h&&e.push(a[f]);return e},map:function(a,b,c){var d,e,g=0,h=[];if(s(a))for(d=a.length;d>g;g++)e=b(a[g],g,c),null!=e&&h.push(e);else for(g in a)e=b(a[g],g,c),null!=e&&h.push(e);return f.apply([],h)},guid:1,proxy:function(a,b){var c,d,f;return"string"==typeof b&&(f=a[b],b=a,a=f),n.isFunction(a)?(c=e.call(arguments,2),d=function(){return a.apply(b||this,c.concat(e.call(arguments)))},d.guid=a.guid=a.guid||n.guid++,d):void 0},now:function(){return+new Date},support:l}),"function"==typeof Symbol&&(n.fn[Symbol.iterator]=c[Symbol.iterator]),n.each("Boolean Number String Function Array Date RegExp Object Error Symbol".split(" "),function(a,b){i["[object "+b+"]"]=b.toLowerCase()});function s(a){var b=!!a&&"length"in a&&a.length,c=n.type(a);return"function"===c||n.isWindow(a)?!1:"array"===c||0===b||"number"==typeof b&&b>0&&b-1 in a}var t=function(a){var b,c,d,e,f,g,h,i,j,k,l,m,n,o,p,q,r,s,t,u="sizzle"+1*new Date,v=a.document,w=0,x=0,y=ga(),z=ga(),A=ga(),B=function(a,b){return a===b&&(l=!0),0},C=1<<31,D={}.hasOwnProperty,E=[],F=E.pop,G=E.push,H=E.push,I=E.slice,J=function(a,b){for(var c=0,d=a.length;d>c;c++)if(a[c]===b)return c;return-1},K="checked|selected|async|autofocus|autoplay|controls|defer|disabled|hidden|ismap|loop|multiple|open|readonly|required|scoped",L="[\\x20\\t\\r\\n\\f]",M="(?:\\\\.|[\\w-]|[^\\x00-\\xa0])+",N="\\["+L+"*("+M+")(?:"+L+"*([*^$|!~]?=)"+L+"*(?:'((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\"|("+M+"))|)"+L+"*\\]",O=":("+M+")(?:\\((('((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\")|((?:\\\\.|[^\\\\()[\\]]|"+N+")*)|.*)\\)|)",P=new RegExp(L+"+","g"),Q=new RegExp("^"+L+"+|((?:^|[^\\\\])(?:\\\\.)*)"+L+"+$","g"),R=new RegExp("^"+L+"*,"+L+"*"),S=new RegExp("^"+L+"*([>+~]|"+L+")"+L+"*"),T=new RegExp("="+L+"*([^\\]'\"]*?)"+L+"*\\]","g"),U=new RegExp(O),V=new RegExp("^"+M+"$"),W={ID:new RegExp("^#("+M+")"),CLASS:new RegExp("^\\.("+M+")"),TAG:new RegExp("^("+M+"|[*])"),ATTR:new RegExp("^"+N),PSEUDO:new RegExp("^"+O),CHILD:new RegExp("^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\("+L+"*(even|odd|(([+-]|)(\\d*)n|)"+L+"*(?:([+-]|)"+L+"*(\\d+)|))"+L+"*\\)|)","i"),bool:new RegExp("^(?:"+K+")$","i"),needsContext:new RegExp("^"+L+"*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\("+L+"*((?:-\\d)?\\d*)"+L+"*\\)|)(?=[^-]|$)","i")},X=/^(?:input|select|textarea|button)$/i,Y=/^h\d$/i,Z=/^[^{]+\{\s*\[native \w/,$=/^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/,_=/[+~]/,aa=/'|\\/g,ba=new RegExp("\\\\([\\da-f]{1,6}"+L+"?|("+L+")|.)","ig"),ca=function(a,b,c){var d="0x"+b-65536;return d!==d||c?b:0>d?String.fromCharCode(d+65536):String.fromCharCode(d>>10|55296,1023&d|56320)},da=function(){m()};try{H.apply(E=I.call(v.childNodes),v.childNodes),E[v.childNodes.length].nodeType}catch(ea){H={apply:E.length?function(a,b){G.apply(a,I.call(b))}:function(a,b){var c=a.length,d=0;while(a[c++]=b[d++]);a.length=c-1}}}function fa(a,b,d,e){var f,h,j,k,l,o,r,s,w=b&&b.ownerDocument,x=b?b.nodeType:9;if(d=d||[],"string"!=typeof a||!a||1!==x&&9!==x&&11!==x)return d;if(!e&&((b?b.ownerDocument||b:v)!==n&&m(b),b=b||n,p)){if(11!==x&&(o=$.exec(a)))if(f=o[1]){if(9===x){if(!(j=b.getElementById(f)))return d;if(j.id===f)return d.push(j),d}else if(w&&(j=w.getElementById(f))&&t(b,j)&&j.id===f)return d.push(j),d}else{if(o[2])return H.apply(d,b.getElementsByTagName(a)),d;if((f=o[3])&&c.getElementsByClassName&&b.getElementsByClassName)return H.apply(d,b.getElementsByClassName(f)),d}if(c.qsa&&!A[a+" "]&&(!q||!q.test(a))){if(1!==x)w=b,s=a;else if("object"!==b.nodeName.toLowerCase()){(k=b.getAttribute("id"))?k=k.replace(aa,"\\$&"):b.setAttribute("id",k=u),r=g(a),h=r.length,l=V.test(k)?"#"+k:"[id='"+k+"']";while(h--)r[h]=l+" "+qa(r[h]);s=r.join(","),w=_.test(a)&&oa(b.parentNode)||b}if(s)try{return H.apply(d,w.querySelectorAll(s)),d}catch(y){}finally{k===u&&b.removeAttribute("id")}}}return i(a.replace(Q,"$1"),b,d,e)}function ga(){var a=[];function b(c,e){return a.push(c+" ")>d.cacheLength&&delete b[a.shift()],b[c+" "]=e}return b}function ha(a){return a[u]=!0,a}function ia(a){var b=n.createElement("div");try{return!!a(b)}catch(c){return!1}finally{b.parentNode&&b.parentNode.removeChild(b),b=null}}function ja(a,b){var c=a.split("|"),e=c.length;while(e--)d.attrHandle[c[e]]=b}function ka(a,b){var c=b&&a,d=c&&1===a.nodeType&&1===b.nodeType&&(~b.sourceIndex||C)-(~a.sourceIndex||C);if(d)return d;if(c)while(c=c.nextSibling)if(c===b)return-1;return a?1:-1}function la(a){return function(b){var c=b.nodeName.toLowerCase();return"input"===c&&b.type===a}}function ma(a){return function(b){var c=b.nodeName.toLowerCase();return("input"===c||"button"===c)&&b.type===a}}function na(a){return ha(function(b){return b=+b,ha(function(c,d){var e,f=a([],c.length,b),g=f.length;while(g--)c[e=f[g]]&&(c[e]=!(d[e]=c[e]))})})}function oa(a){return a&&"undefined"!=typeof a.getElementsByTagName&&a}c=fa.support={},f=fa.isXML=function(a){var b=a&&(a.ownerDocument||a).documentElement;return b?"HTML"!==b.nodeName:!1},m=fa.setDocument=function(a){var b,e,g=a?a.ownerDocument||a:v;return g!==n&&9===g.nodeType&&g.documentElement?(n=g,o=n.documentElement,p=!f(n),(e=n.defaultView)&&e.top!==e&&(e.addEventListener?e.addEventListener("unload",da,!1):e.attachEvent&&e.attachEvent("onunload",da)),c.attributes=ia(function(a){return a.className="i",!a.getAttribute("className")}),c.getElementsByTagName=ia(function(a){return a.appendChild(n.createComment("")),!a.getElementsByTagName("*").length}),c.getElementsByClassName=Z.test(n.getElementsByClassName),c.getById=ia(function(a){return o.appendChild(a).id=u,!n.getElementsByName||!n.getElementsByName(u).length}),c.getById?(d.find.ID=function(a,b){if("undefined"!=typeof b.getElementById&&p){var c=b.getElementById(a);return c?[c]:[]}},d.filter.ID=function(a){var b=a.replace(ba,ca);return function(a){return a.getAttribute("id")===b}}):(delete d.find.ID,d.filter.ID=function(a){var b=a.replace(ba,ca);return function(a){var c="undefined"!=typeof a.getAttributeNode&&a.getAttributeNode("id");return c&&c.value===b}}),d.find.TAG=c.getElementsByTagName?function(a,b){return"undefined"!=typeof b.getElementsByTagName?b.getElementsByTagName(a):c.qsa?b.querySelectorAll(a):void 0}:function(a,b){var c,d=[],e=0,f=b.getElementsByTagName(a);if("*"===a){while(c=f[e++])1===c.nodeType&&d.push(c);return d}return f},d.find.CLASS=c.getElementsByClassName&&function(a,b){return"undefined"!=typeof b.getElementsByClassName&&p?b.getElementsByClassName(a):void 0},r=[],q=[],(c.qsa=Z.test(n.querySelectorAll))&&(ia(function(a){o.appendChild(a).innerHTML="",a.querySelectorAll("[msallowcapture^='']").length&&q.push("[*^$]="+L+"*(?:''|\"\")"),a.querySelectorAll("[selected]").length||q.push("\\["+L+"*(?:value|"+K+")"),a.querySelectorAll("[id~="+u+"-]").length||q.push("~="),a.querySelectorAll(":checked").length||q.push(":checked"),a.querySelectorAll("a#"+u+"+*").length||q.push(".#.+[+~]")}),ia(function(a){var b=n.createElement("input");b.setAttribute("type","hidden"),a.appendChild(b).setAttribute("name","D"),a.querySelectorAll("[name=d]").length&&q.push("name"+L+"*[*^$|!~]?="),a.querySelectorAll(":enabled").length||q.push(":enabled",":disabled"),a.querySelectorAll("*,:x"),q.push(",.*:")})),(c.matchesSelector=Z.test(s=o.matches||o.webkitMatchesSelector||o.mozMatchesSelector||o.oMatchesSelector||o.msMatchesSelector))&&ia(function(a){c.disconnectedMatch=s.call(a,"div"),s.call(a,"[s!='']:x"),r.push("!=",O)}),q=q.length&&new RegExp(q.join("|")),r=r.length&&new RegExp(r.join("|")),b=Z.test(o.compareDocumentPosition),t=b||Z.test(o.contains)?function(a,b){var c=9===a.nodeType?a.documentElement:a,d=b&&b.parentNode;return a===d||!(!d||1!==d.nodeType||!(c.contains?c.contains(d):a.compareDocumentPosition&&16&a.compareDocumentPosition(d)))}:function(a,b){if(b)while(b=b.parentNode)if(b===a)return!0;return!1},B=b?function(a,b){if(a===b)return l=!0,0;var d=!a.compareDocumentPosition-!b.compareDocumentPosition;return d?d:(d=(a.ownerDocument||a)===(b.ownerDocument||b)?a.compareDocumentPosition(b):1,1&d||!c.sortDetached&&b.compareDocumentPosition(a)===d?a===n||a.ownerDocument===v&&t(v,a)?-1:b===n||b.ownerDocument===v&&t(v,b)?1:k?J(k,a)-J(k,b):0:4&d?-1:1)}:function(a,b){if(a===b)return l=!0,0;var c,d=0,e=a.parentNode,f=b.parentNode,g=[a],h=[b];if(!e||!f)return a===n?-1:b===n?1:e?-1:f?1:k?J(k,a)-J(k,b):0;if(e===f)return ka(a,b);c=a;while(c=c.parentNode)g.unshift(c);c=b;while(c=c.parentNode)h.unshift(c);while(g[d]===h[d])d++;return d?ka(g[d],h[d]):g[d]===v?-1:h[d]===v?1:0},n):n},fa.matches=function(a,b){return fa(a,null,null,b)},fa.matchesSelector=function(a,b){if((a.ownerDocument||a)!==n&&m(a),b=b.replace(T,"='$1']"),c.matchesSelector&&p&&!A[b+" "]&&(!r||!r.test(b))&&(!q||!q.test(b)))try{var d=s.call(a,b);if(d||c.disconnectedMatch||a.document&&11!==a.document.nodeType)return d}catch(e){}return fa(b,n,null,[a]).length>0},fa.contains=function(a,b){return(a.ownerDocument||a)!==n&&m(a),t(a,b)},fa.attr=function(a,b){(a.ownerDocument||a)!==n&&m(a);var e=d.attrHandle[b.toLowerCase()],f=e&&D.call(d.attrHandle,b.toLowerCase())?e(a,b,!p):void 0;return void 0!==f?f:c.attributes||!p?a.getAttribute(b):(f=a.getAttributeNode(b))&&f.specified?f.value:null},fa.error=function(a){throw new Error("Syntax error, unrecognized expression: "+a)},fa.uniqueSort=function(a){var b,d=[],e=0,f=0;if(l=!c.detectDuplicates,k=!c.sortStable&&a.slice(0),a.sort(B),l){while(b=a[f++])b===a[f]&&(e=d.push(f));while(e--)a.splice(d[e],1)}return k=null,a},e=fa.getText=function(a){var b,c="",d=0,f=a.nodeType;if(f){if(1===f||9===f||11===f){if("string"==typeof a.textContent)return a.textContent;for(a=a.firstChild;a;a=a.nextSibling)c+=e(a)}else if(3===f||4===f)return a.nodeValue}else while(b=a[d++])c+=e(b);return c},d=fa.selectors={cacheLength:50,createPseudo:ha,match:W,attrHandle:{},find:{},relative:{">":{dir:"parentNode",first:!0}," ":{dir:"parentNode"},"+":{dir:"previousSibling",first:!0},"~":{dir:"previousSibling"}},preFilter:{ATTR:function(a){return a[1]=a[1].replace(ba,ca),a[3]=(a[3]||a[4]||a[5]||"").replace(ba,ca),"~="===a[2]&&(a[3]=" "+a[3]+" "),a.slice(0,4)},CHILD:function(a){return a[1]=a[1].toLowerCase(),"nth"===a[1].slice(0,3)?(a[3]||fa.error(a[0]),a[4]=+(a[4]?a[5]+(a[6]||1):2*("even"===a[3]||"odd"===a[3])),a[5]=+(a[7]+a[8]||"odd"===a[3])):a[3]&&fa.error(a[0]),a},PSEUDO:function(a){var b,c=!a[6]&&a[2];return W.CHILD.test(a[0])?null:(a[3]?a[2]=a[4]||a[5]||"":c&&U.test(c)&&(b=g(c,!0))&&(b=c.indexOf(")",c.length-b)-c.length)&&(a[0]=a[0].slice(0,b),a[2]=c.slice(0,b)),a.slice(0,3))}},filter:{TAG:function(a){var b=a.replace(ba,ca).toLowerCase();return"*"===a?function(){return!0}:function(a){return a.nodeName&&a.nodeName.toLowerCase()===b}},CLASS:function(a){var b=y[a+" "];return b||(b=new RegExp("(^|"+L+")"+a+"("+L+"|$)"))&&y(a,function(a){return b.test("string"==typeof a.className&&a.className||"undefined"!=typeof a.getAttribute&&a.getAttribute("class")||"")})},ATTR:function(a,b,c){return function(d){var e=fa.attr(d,a);return null==e?"!="===b:b?(e+="","="===b?e===c:"!="===b?e!==c:"^="===b?c&&0===e.indexOf(c):"*="===b?c&&e.indexOf(c)>-1:"$="===b?c&&e.slice(-c.length)===c:"~="===b?(" "+e.replace(P," ")+" ").indexOf(c)>-1:"|="===b?e===c||e.slice(0,c.length+1)===c+"-":!1):!0}},CHILD:function(a,b,c,d,e){var f="nth"!==a.slice(0,3),g="last"!==a.slice(-4),h="of-type"===b;return 1===d&&0===e?function(a){return!!a.parentNode}:function(b,c,i){var j,k,l,m,n,o,p=f!==g?"nextSibling":"previousSibling",q=b.parentNode,r=h&&b.nodeName.toLowerCase(),s=!i&&!h,t=!1;if(q){if(f){while(p){m=b;while(m=m[p])if(h?m.nodeName.toLowerCase()===r:1===m.nodeType)return!1;o=p="only"===a&&!o&&"nextSibling"}return!0}if(o=[g?q.firstChild:q.lastChild],g&&s){m=q,l=m[u]||(m[u]={}),k=l[m.uniqueID]||(l[m.uniqueID]={}),j=k[a]||[],n=j[0]===w&&j[1],t=n&&j[2],m=n&&q.childNodes[n];while(m=++n&&m&&m[p]||(t=n=0)||o.pop())if(1===m.nodeType&&++t&&m===b){k[a]=[w,n,t];break}}else if(s&&(m=b,l=m[u]||(m[u]={}),k=l[m.uniqueID]||(l[m.uniqueID]={}),j=k[a]||[],n=j[0]===w&&j[1],t=n),t===!1)while(m=++n&&m&&m[p]||(t=n=0)||o.pop())if((h?m.nodeName.toLowerCase()===r:1===m.nodeType)&&++t&&(s&&(l=m[u]||(m[u]={}),k=l[m.uniqueID]||(l[m.uniqueID]={}),k[a]=[w,t]),m===b))break;return t-=e,t===d||t%d===0&&t/d>=0}}},PSEUDO:function(a,b){var c,e=d.pseudos[a]||d.setFilters[a.toLowerCase()]||fa.error("unsupported pseudo: "+a);return e[u]?e(b):e.length>1?(c=[a,a,"",b],d.setFilters.hasOwnProperty(a.toLowerCase())?ha(function(a,c){var d,f=e(a,b),g=f.length;while(g--)d=J(a,f[g]),a[d]=!(c[d]=f[g])}):function(a){return e(a,0,c)}):e}},pseudos:{not:ha(function(a){var b=[],c=[],d=h(a.replace(Q,"$1"));return d[u]?ha(function(a,b,c,e){var f,g=d(a,null,e,[]),h=a.length;while(h--)(f=g[h])&&(a[h]=!(b[h]=f))}):function(a,e,f){return b[0]=a,d(b,null,f,c),b[0]=null,!c.pop()}}),has:ha(function(a){return function(b){return fa(a,b).length>0}}),contains:ha(function(a){return a=a.replace(ba,ca),function(b){return(b.textContent||b.innerText||e(b)).indexOf(a)>-1}}),lang:ha(function(a){return V.test(a||"")||fa.error("unsupported lang: "+a),a=a.replace(ba,ca).toLowerCase(),function(b){var c;do if(c=p?b.lang:b.getAttribute("xml:lang")||b.getAttribute("lang"))return c=c.toLowerCase(),c===a||0===c.indexOf(a+"-");while((b=b.parentNode)&&1===b.nodeType);return!1}}),target:function(b){var c=a.location&&a.location.hash;return c&&c.slice(1)===b.id},root:function(a){return a===o},focus:function(a){return a===n.activeElement&&(!n.hasFocus||n.hasFocus())&&!!(a.type||a.href||~a.tabIndex)},enabled:function(a){return a.disabled===!1},disabled:function(a){return a.disabled===!0},checked:function(a){var b=a.nodeName.toLowerCase();return"input"===b&&!!a.checked||"option"===b&&!!a.selected},selected:function(a){return a.parentNode&&a.parentNode.selectedIndex,a.selected===!0},empty:function(a){for(a=a.firstChild;a;a=a.nextSibling)if(a.nodeType<6)return!1;return!0},parent:function(a){return!d.pseudos.empty(a)},header:function(a){return Y.test(a.nodeName)},input:function(a){return X.test(a.nodeName)},button:function(a){var b=a.nodeName.toLowerCase();return"input"===b&&"button"===a.type||"button"===b},text:function(a){var b;return"input"===a.nodeName.toLowerCase()&&"text"===a.type&&(null==(b=a.getAttribute("type"))||"text"===b.toLowerCase())},first:na(function(){return[0]}),last:na(function(a,b){return[b-1]}),eq:na(function(a,b,c){return[0>c?c+b:c]}),even:na(function(a,b){for(var c=0;b>c;c+=2)a.push(c);return a}),odd:na(function(a,b){for(var c=1;b>c;c+=2)a.push(c);return a}),lt:na(function(a,b,c){for(var d=0>c?c+b:c;--d>=0;)a.push(d);return a}),gt:na(function(a,b,c){for(var d=0>c?c+b:c;++db;b++)d+=a[b].value;return d}function ra(a,b,c){var d=b.dir,e=c&&"parentNode"===d,f=x++;return b.first?function(b,c,f){while(b=b[d])if(1===b.nodeType||e)return a(b,c,f)}:function(b,c,g){var h,i,j,k=[w,f];if(g){while(b=b[d])if((1===b.nodeType||e)&&a(b,c,g))return!0}else while(b=b[d])if(1===b.nodeType||e){if(j=b[u]||(b[u]={}),i=j[b.uniqueID]||(j[b.uniqueID]={}),(h=i[d])&&h[0]===w&&h[1]===f)return k[2]=h[2];if(i[d]=k,k[2]=a(b,c,g))return!0}}}function sa(a){return a.length>1?function(b,c,d){var e=a.length;while(e--)if(!a[e](b,c,d))return!1;return!0}:a[0]}function ta(a,b,c){for(var d=0,e=b.length;e>d;d++)fa(a,b[d],c);return c}function ua(a,b,c,d,e){for(var f,g=[],h=0,i=a.length,j=null!=b;i>h;h++)(f=a[h])&&(!c||c(f,d,e))&&(g.push(f),j&&b.push(h));return g}function va(a,b,c,d,e,f){return d&&!d[u]&&(d=va(d)),e&&!e[u]&&(e=va(e,f)),ha(function(f,g,h,i){var j,k,l,m=[],n=[],o=g.length,p=f||ta(b||"*",h.nodeType?[h]:h,[]),q=!a||!f&&b?p:ua(p,m,a,h,i),r=c?e||(f?a:o||d)?[]:g:q;if(c&&c(q,r,h,i),d){j=ua(r,n),d(j,[],h,i),k=j.length;while(k--)(l=j[k])&&(r[n[k]]=!(q[n[k]]=l))}if(f){if(e||a){if(e){j=[],k=r.length;while(k--)(l=r[k])&&j.push(q[k]=l);e(null,r=[],j,i)}k=r.length;while(k--)(l=r[k])&&(j=e?J(f,l):m[k])>-1&&(f[j]=!(g[j]=l))}}else r=ua(r===g?r.splice(o,r.length):r),e?e(null,g,r,i):H.apply(g,r)})}function wa(a){for(var b,c,e,f=a.length,g=d.relative[a[0].type],h=g||d.relative[" "],i=g?1:0,k=ra(function(a){return a===b},h,!0),l=ra(function(a){return J(b,a)>-1},h,!0),m=[function(a,c,d){var e=!g&&(d||c!==j)||((b=c).nodeType?k(a,c,d):l(a,c,d));return b=null,e}];f>i;i++)if(c=d.relative[a[i].type])m=[ra(sa(m),c)];else{if(c=d.filter[a[i].type].apply(null,a[i].matches),c[u]){for(e=++i;f>e;e++)if(d.relative[a[e].type])break;return va(i>1&&sa(m),i>1&&qa(a.slice(0,i-1).concat({value:" "===a[i-2].type?"*":""})).replace(Q,"$1"),c,e>i&&wa(a.slice(i,e)),f>e&&wa(a=a.slice(e)),f>e&&qa(a))}m.push(c)}return sa(m)}function xa(a,b){var c=b.length>0,e=a.length>0,f=function(f,g,h,i,k){var l,o,q,r=0,s="0",t=f&&[],u=[],v=j,x=f||e&&d.find.TAG("*",k),y=w+=null==v?1:Math.random()||.1,z=x.length;for(k&&(j=g===n||g||k);s!==z&&null!=(l=x[s]);s++){if(e&&l){o=0,g||l.ownerDocument===n||(m(l),h=!p);while(q=a[o++])if(q(l,g||n,h)){i.push(l);break}k&&(w=y)}c&&((l=!q&&l)&&r--,f&&t.push(l))}if(r+=s,c&&s!==r){o=0;while(q=b[o++])q(t,u,g,h);if(f){if(r>0)while(s--)t[s]||u[s]||(u[s]=F.call(i));u=ua(u)}H.apply(i,u),k&&!f&&u.length>0&&r+b.length>1&&fa.uniqueSort(i)}return k&&(w=y,j=v),t};return c?ha(f):f}return h=fa.compile=function(a,b){var c,d=[],e=[],f=A[a+" "];if(!f){b||(b=g(a)),c=b.length;while(c--)f=wa(b[c]),f[u]?d.push(f):e.push(f);f=A(a,xa(e,d)),f.selector=a}return f},i=fa.select=function(a,b,e,f){var i,j,k,l,m,n="function"==typeof a&&a,o=!f&&g(a=n.selector||a);if(e=e||[],1===o.length){if(j=o[0]=o[0].slice(0),j.length>2&&"ID"===(k=j[0]).type&&c.getById&&9===b.nodeType&&p&&d.relative[j[1].type]){if(b=(d.find.ID(k.matches[0].replace(ba,ca),b)||[])[0],!b)return e;n&&(b=b.parentNode),a=a.slice(j.shift().value.length)}i=W.needsContext.test(a)?0:j.length;while(i--){if(k=j[i],d.relative[l=k.type])break;if((m=d.find[l])&&(f=m(k.matches[0].replace(ba,ca),_.test(j[0].type)&&oa(b.parentNode)||b))){if(j.splice(i,1),a=f.length&&qa(j),!a)return H.apply(e,f),e;break}}}return(n||h(a,o))(f,b,!p,e,!b||_.test(a)&&oa(b.parentNode)||b),e},c.sortStable=u.split("").sort(B).join("")===u,c.detectDuplicates=!!l,m(),c.sortDetached=ia(function(a){return 1&a.compareDocumentPosition(n.createElement("div"))}),ia(function(a){return a.innerHTML="","#"===a.firstChild.getAttribute("href")})||ja("type|href|height|width",function(a,b,c){return c?void 0:a.getAttribute(b,"type"===b.toLowerCase()?1:2)}),c.attributes&&ia(function(a){return a.innerHTML="",a.firstChild.setAttribute("value",""),""===a.firstChild.getAttribute("value")})||ja("value",function(a,b,c){return c||"input"!==a.nodeName.toLowerCase()?void 0:a.defaultValue}),ia(function(a){return null==a.getAttribute("disabled")})||ja(K,function(a,b,c){var d;return c?void 0:a[b]===!0?b.toLowerCase():(d=a.getAttributeNode(b))&&d.specified?d.value:null}),fa}(a);n.find=t,n.expr=t.selectors,n.expr[":"]=n.expr.pseudos,n.uniqueSort=n.unique=t.uniqueSort,n.text=t.getText,n.isXMLDoc=t.isXML,n.contains=t.contains;var u=function(a,b,c){var d=[],e=void 0!==c;while((a=a[b])&&9!==a.nodeType)if(1===a.nodeType){if(e&&n(a).is(c))break;d.push(a)}return d},v=function(a,b){for(var c=[];a;a=a.nextSibling)1===a.nodeType&&a!==b&&c.push(a);return c},w=n.expr.match.needsContext,x=/^<([\w-]+)\s*\/?>(?:<\/\1>|)$/,y=/^.[^:#\[\.,]*$/;function z(a,b,c){if(n.isFunction(b))return n.grep(a,function(a,d){return!!b.call(a,d,a)!==c});if(b.nodeType)return n.grep(a,function(a){return a===b!==c});if("string"==typeof b){if(y.test(b))return n.filter(b,a,c);b=n.filter(b,a)}return n.grep(a,function(a){return n.inArray(a,b)>-1!==c})}n.filter=function(a,b,c){var d=b[0];return c&&(a=":not("+a+")"),1===b.length&&1===d.nodeType?n.find.matchesSelector(d,a)?[d]:[]:n.find.matches(a,n.grep(b,function(a){return 1===a.nodeType}))},n.fn.extend({find:function(a){var b,c=[],d=this,e=d.length;if("string"!=typeof a)return this.pushStack(n(a).filter(function(){for(b=0;e>b;b++)if(n.contains(d[b],this))return!0}));for(b=0;e>b;b++)n.find(a,d[b],c);return c=this.pushStack(e>1?n.unique(c):c),c.selector=this.selector?this.selector+" "+a:a,c},filter:function(a){return this.pushStack(z(this,a||[],!1))},not:function(a){return this.pushStack(z(this,a||[],!0))},is:function(a){return!!z(this,"string"==typeof a&&w.test(a)?n(a):a||[],!1).length}});var A,B=/^(?:\s*(<[\w\W]+>)[^>]*|#([\w-]*))$/,C=n.fn.init=function(a,b,c){var e,f;if(!a)return this;if(c=c||A,"string"==typeof a){if(e="<"===a.charAt(0)&&">"===a.charAt(a.length-1)&&a.length>=3?[null,a,null]:B.exec(a),!e||!e[1]&&b)return!b||b.jquery?(b||c).find(a):this.constructor(b).find(a);if(e[1]){if(b=b instanceof n?b[0]:b,n.merge(this,n.parseHTML(e[1],b&&b.nodeType?b.ownerDocument||b:d,!0)),x.test(e[1])&&n.isPlainObject(b))for(e in b)n.isFunction(this[e])?this[e](b[e]):this.attr(e,b[e]);return this}if(f=d.getElementById(e[2]),f&&f.parentNode){if(f.id!==e[2])return A.find(a);this.length=1,this[0]=f}return this.context=d,this.selector=a,this}return a.nodeType?(this.context=this[0]=a,this.length=1,this):n.isFunction(a)?"undefined"!=typeof c.ready?c.ready(a):a(n):(void 0!==a.selector&&(this.selector=a.selector,this.context=a.context),n.makeArray(a,this))};C.prototype=n.fn,A=n(d);var D=/^(?:parents|prev(?:Until|All))/,E={children:!0,contents:!0,next:!0,prev:!0};n.fn.extend({has:function(a){var b,c=n(a,this),d=c.length;return this.filter(function(){for(b=0;d>b;b++)if(n.contains(this,c[b]))return!0})},closest:function(a,b){for(var c,d=0,e=this.length,f=[],g=w.test(a)||"string"!=typeof a?n(a,b||this.context):0;e>d;d++)for(c=this[d];c&&c!==b;c=c.parentNode)if(c.nodeType<11&&(g?g.index(c)>-1:1===c.nodeType&&n.find.matchesSelector(c,a))){f.push(c);break}return this.pushStack(f.length>1?n.uniqueSort(f):f)},index:function(a){return a?"string"==typeof a?n.inArray(this[0],n(a)):n.inArray(a.jquery?a[0]:a,this):this[0]&&this[0].parentNode?this.first().prevAll().length:-1},add:function(a,b){return this.pushStack(n.uniqueSort(n.merge(this.get(),n(a,b))))},addBack:function(a){return this.add(null==a?this.prevObject:this.prevObject.filter(a))}});function F(a,b){do a=a[b];while(a&&1!==a.nodeType);return a}n.each({parent:function(a){var b=a.parentNode;return b&&11!==b.nodeType?b:null},parents:function(a){return u(a,"parentNode")},parentsUntil:function(a,b,c){return u(a,"parentNode",c)},next:function(a){return F(a,"nextSibling")},prev:function(a){return F(a,"previousSibling")},nextAll:function(a){return u(a,"nextSibling")},prevAll:function(a){return u(a,"previousSibling")},nextUntil:function(a,b,c){return u(a,"nextSibling",c)},prevUntil:function(a,b,c){return u(a,"previousSibling",c)},siblings:function(a){return v((a.parentNode||{}).firstChild,a)},children:function(a){return v(a.firstChild)},contents:function(a){return n.nodeName(a,"iframe")?a.contentDocument||a.contentWindow.document:n.merge([],a.childNodes)}},function(a,b){n.fn[a]=function(c,d){var e=n.map(this,b,c);return"Until"!==a.slice(-5)&&(d=c),d&&"string"==typeof d&&(e=n.filter(d,e)),this.length>1&&(E[a]||(e=n.uniqueSort(e)),D.test(a)&&(e=e.reverse())),this.pushStack(e)}});var G=/\S+/g;function H(a){var b={};return n.each(a.match(G)||[],function(a,c){b[c]=!0}),b}n.Callbacks=function(a){a="string"==typeof a?H(a):n.extend({},a);var b,c,d,e,f=[],g=[],h=-1,i=function(){for(e=a.once,d=b=!0;g.length;h=-1){c=g.shift();while(++h-1)f.splice(c,1),h>=c&&h--}),this},has:function(a){return a?n.inArray(a,f)>-1:f.length>0},empty:function(){return f&&(f=[]),this},disable:function(){return e=g=[],f=c="",this},disabled:function(){return!f},lock:function(){return e=!0,c||j.disable(),this},locked:function(){return!!e},fireWith:function(a,c){return e||(c=c||[],c=[a,c.slice?c.slice():c],g.push(c),b||i()),this},fire:function(){return j.fireWith(this,arguments),this},fired:function(){return!!d}};return j},n.extend({Deferred:function(a){var b=[["resolve","done",n.Callbacks("once memory"),"resolved"],["reject","fail",n.Callbacks("once memory"),"rejected"],["notify","progress",n.Callbacks("memory")]],c="pending",d={state:function(){return c},always:function(){return e.done(arguments).fail(arguments),this},then:function(){var a=arguments;return n.Deferred(function(c){n.each(b,function(b,f){var g=n.isFunction(a[b])&&a[b];e[f[1]](function(){var a=g&&g.apply(this,arguments);a&&n.isFunction(a.promise)?a.promise().progress(c.notify).done(c.resolve).fail(c.reject):c[f[0]+"With"](this===d?c.promise():this,g?[a]:arguments)})}),a=null}).promise()},promise:function(a){return null!=a?n.extend(a,d):d}},e={};return d.pipe=d.then,n.each(b,function(a,f){var g=f[2],h=f[3];d[f[1]]=g.add,h&&g.add(function(){c=h},b[1^a][2].disable,b[2][2].lock),e[f[0]]=function(){return e[f[0]+"With"](this===e?d:this,arguments),this},e[f[0]+"With"]=g.fireWith}),d.promise(e),a&&a.call(e,e),e},when:function(a){var b=0,c=e.call(arguments),d=c.length,f=1!==d||a&&n.isFunction(a.promise)?d:0,g=1===f?a:n.Deferred(),h=function(a,b,c){return function(d){b[a]=this,c[a]=arguments.length>1?e.call(arguments):d,c===i?g.notifyWith(b,c):--f||g.resolveWith(b,c)}},i,j,k;if(d>1)for(i=new Array(d),j=new Array(d),k=new Array(d);d>b;b++)c[b]&&n.isFunction(c[b].promise)?c[b].promise().progress(h(b,j,i)).done(h(b,k,c)).fail(g.reject):--f;return f||g.resolveWith(k,c),g.promise()}});var I;n.fn.ready=function(a){return n.ready.promise().done(a),this},n.extend({isReady:!1,readyWait:1,holdReady:function(a){a?n.readyWait++:n.ready(!0)},ready:function(a){(a===!0?--n.readyWait:n.isReady)||(n.isReady=!0,a!==!0&&--n.readyWait>0||(I.resolveWith(d,[n]),n.fn.triggerHandler&&(n(d).triggerHandler("ready"),n(d).off("ready"))))}});function J(){d.addEventListener?(d.removeEventListener("DOMContentLoaded",K),a.removeEventListener("load",K)):(d.detachEvent("onreadystatechange",K),a.detachEvent("onload",K))}function K(){(d.addEventListener||"load"===a.event.type||"complete"===d.readyState)&&(J(),n.ready())}n.ready.promise=function(b){if(!I)if(I=n.Deferred(),"complete"===d.readyState)a.setTimeout(n.ready);else if(d.addEventListener)d.addEventListener("DOMContentLoaded",K),a.addEventListener("load",K);else{d.attachEvent("onreadystatechange",K),a.attachEvent("onload",K);var c=!1;try{c=null==a.frameElement&&d.documentElement}catch(e){}c&&c.doScroll&&!function f(){if(!n.isReady){try{c.doScroll("left")}catch(b){return a.setTimeout(f,50)}J(),n.ready()}}()}return I.promise(b)},n.ready.promise();var L;for(L in n(l))break;l.ownFirst="0"===L,l.inlineBlockNeedsLayout=!1,n(function(){var a,b,c,e;c=d.getElementsByTagName("body")[0],c&&c.style&&(b=d.createElement("div"),e=d.createElement("div"),e.style.cssText="position:absolute;border:0;width:0;height:0;top:0;left:-9999px",c.appendChild(e).appendChild(b),"undefined"!=typeof b.style.zoom&&(b.style.cssText="display:inline;margin:0;border:0;padding:1px;width:1px;zoom:1",l.inlineBlockNeedsLayout=a=3===b.offsetWidth,a&&(c.style.zoom=1)),c.removeChild(e))}),function(){var a=d.createElement("div");l.deleteExpando=!0;try{delete a.test}catch(b){l.deleteExpando=!1}a=null}();var M=function(a){var b=n.noData[(a.nodeName+" ").toLowerCase()],c=+a.nodeType||1;return 1!==c&&9!==c?!1:!b||b!==!0&&a.getAttribute("classid")===b},N=/^(?:\{[\w\W]*\}|\[[\w\W]*\])$/,O=/([A-Z])/g;function P(a,b,c){if(void 0===c&&1===a.nodeType){var d="data-"+b.replace(O,"-$1").toLowerCase();if(c=a.getAttribute(d),"string"==typeof c){try{c="true"===c?!0:"false"===c?!1:"null"===c?null:+c+""===c?+c:N.test(c)?n.parseJSON(c):c}catch(e){}n.data(a,b,c)}else c=void 0}return c}function Q(a){var b;for(b in a)if(("data"!==b||!n.isEmptyObject(a[b]))&&"toJSON"!==b)return!1; +return!0}function R(a,b,d,e){if(M(a)){var f,g,h=n.expando,i=a.nodeType,j=i?n.cache:a,k=i?a[h]:a[h]&&h;if(k&&j[k]&&(e||j[k].data)||void 0!==d||"string"!=typeof b)return k||(k=i?a[h]=c.pop()||n.guid++:h),j[k]||(j[k]=i?{}:{toJSON:n.noop}),("object"==typeof b||"function"==typeof b)&&(e?j[k]=n.extend(j[k],b):j[k].data=n.extend(j[k].data,b)),g=j[k],e||(g.data||(g.data={}),g=g.data),void 0!==d&&(g[n.camelCase(b)]=d),"string"==typeof b?(f=g[b],null==f&&(f=g[n.camelCase(b)])):f=g,f}}function S(a,b,c){if(M(a)){var d,e,f=a.nodeType,g=f?n.cache:a,h=f?a[n.expando]:n.expando;if(g[h]){if(b&&(d=c?g[h]:g[h].data)){n.isArray(b)?b=b.concat(n.map(b,n.camelCase)):b in d?b=[b]:(b=n.camelCase(b),b=b in d?[b]:b.split(" ")),e=b.length;while(e--)delete d[b[e]];if(c?!Q(d):!n.isEmptyObject(d))return}(c||(delete g[h].data,Q(g[h])))&&(f?n.cleanData([a],!0):l.deleteExpando||g!=g.window?delete g[h]:g[h]=void 0)}}}n.extend({cache:{},noData:{"applet ":!0,"embed ":!0,"object ":"clsid:D27CDB6E-AE6D-11cf-96B8-444553540000"},hasData:function(a){return a=a.nodeType?n.cache[a[n.expando]]:a[n.expando],!!a&&!Q(a)},data:function(a,b,c){return R(a,b,c)},removeData:function(a,b){return S(a,b)},_data:function(a,b,c){return R(a,b,c,!0)},_removeData:function(a,b){return S(a,b,!0)}}),n.fn.extend({data:function(a,b){var c,d,e,f=this[0],g=f&&f.attributes;if(void 0===a){if(this.length&&(e=n.data(f),1===f.nodeType&&!n._data(f,"parsedAttrs"))){c=g.length;while(c--)g[c]&&(d=g[c].name,0===d.indexOf("data-")&&(d=n.camelCase(d.slice(5)),P(f,d,e[d])));n._data(f,"parsedAttrs",!0)}return e}return"object"==typeof a?this.each(function(){n.data(this,a)}):arguments.length>1?this.each(function(){n.data(this,a,b)}):f?P(f,a,n.data(f,a)):void 0},removeData:function(a){return this.each(function(){n.removeData(this,a)})}}),n.extend({queue:function(a,b,c){var d;return a?(b=(b||"fx")+"queue",d=n._data(a,b),c&&(!d||n.isArray(c)?d=n._data(a,b,n.makeArray(c)):d.push(c)),d||[]):void 0},dequeue:function(a,b){b=b||"fx";var c=n.queue(a,b),d=c.length,e=c.shift(),f=n._queueHooks(a,b),g=function(){n.dequeue(a,b)};"inprogress"===e&&(e=c.shift(),d--),e&&("fx"===b&&c.unshift("inprogress"),delete f.stop,e.call(a,g,f)),!d&&f&&f.empty.fire()},_queueHooks:function(a,b){var c=b+"queueHooks";return n._data(a,c)||n._data(a,c,{empty:n.Callbacks("once memory").add(function(){n._removeData(a,b+"queue"),n._removeData(a,c)})})}}),n.fn.extend({queue:function(a,b){var c=2;return"string"!=typeof a&&(b=a,a="fx",c--),arguments.lengthh;h++)b(a[h],c,g?d:d.call(a[h],h,b(a[h],c)));return e?a:j?b.call(a):i?b(a[0],c):f},Z=/^(?:checkbox|radio)$/i,$=/<([\w:-]+)/,_=/^$|\/(?:java|ecma)script/i,aa=/^\s+/,ba="abbr|article|aside|audio|bdi|canvas|data|datalist|details|dialog|figcaption|figure|footer|header|hgroup|main|mark|meter|nav|output|picture|progress|section|summary|template|time|video";function ca(a){var b=ba.split("|"),c=a.createDocumentFragment();if(c.createElement)while(b.length)c.createElement(b.pop());return c}!function(){var a=d.createElement("div"),b=d.createDocumentFragment(),c=d.createElement("input");a.innerHTML="
    a",l.leadingWhitespace=3===a.firstChild.nodeType,l.tbody=!a.getElementsByTagName("tbody").length,l.htmlSerialize=!!a.getElementsByTagName("link").length,l.html5Clone="<:nav>"!==d.createElement("nav").cloneNode(!0).outerHTML,c.type="checkbox",c.checked=!0,b.appendChild(c),l.appendChecked=c.checked,a.innerHTML="",l.noCloneChecked=!!a.cloneNode(!0).lastChild.defaultValue,b.appendChild(a),c=d.createElement("input"),c.setAttribute("type","radio"),c.setAttribute("checked","checked"),c.setAttribute("name","t"),a.appendChild(c),l.checkClone=a.cloneNode(!0).cloneNode(!0).lastChild.checked,l.noCloneEvent=!!a.addEventListener,a[n.expando]=1,l.attributes=!a.getAttribute(n.expando)}();var da={option:[1,""],legend:[1,"
    ","
    "],area:[1,"",""],param:[1,"",""],thead:[1,"","
    "],tr:[2,"","
    "],col:[2,"","
    "],td:[3,"","
    "],_default:l.htmlSerialize?[0,"",""]:[1,"X
    ","
    "]};da.optgroup=da.option,da.tbody=da.tfoot=da.colgroup=da.caption=da.thead,da.th=da.td;function ea(a,b){var c,d,e=0,f="undefined"!=typeof a.getElementsByTagName?a.getElementsByTagName(b||"*"):"undefined"!=typeof a.querySelectorAll?a.querySelectorAll(b||"*"):void 0;if(!f)for(f=[],c=a.childNodes||a;null!=(d=c[e]);e++)!b||n.nodeName(d,b)?f.push(d):n.merge(f,ea(d,b));return void 0===b||b&&n.nodeName(a,b)?n.merge([a],f):f}function fa(a,b){for(var c,d=0;null!=(c=a[d]);d++)n._data(c,"globalEval",!b||n._data(b[d],"globalEval"))}var ga=/<|&#?\w+;/,ha=/r;r++)if(g=a[r],g||0===g)if("object"===n.type(g))n.merge(q,g.nodeType?[g]:g);else if(ga.test(g)){i=i||p.appendChild(b.createElement("div")),j=($.exec(g)||["",""])[1].toLowerCase(),m=da[j]||da._default,i.innerHTML=m[1]+n.htmlPrefilter(g)+m[2],f=m[0];while(f--)i=i.lastChild;if(!l.leadingWhitespace&&aa.test(g)&&q.push(b.createTextNode(aa.exec(g)[0])),!l.tbody){g="table"!==j||ha.test(g)?""!==m[1]||ha.test(g)?0:i:i.firstChild,f=g&&g.childNodes.length;while(f--)n.nodeName(k=g.childNodes[f],"tbody")&&!k.childNodes.length&&g.removeChild(k)}n.merge(q,i.childNodes),i.textContent="";while(i.firstChild)i.removeChild(i.firstChild);i=p.lastChild}else q.push(b.createTextNode(g));i&&p.removeChild(i),l.appendChecked||n.grep(ea(q,"input"),ia),r=0;while(g=q[r++])if(d&&n.inArray(g,d)>-1)e&&e.push(g);else if(h=n.contains(g.ownerDocument,g),i=ea(p.appendChild(g),"script"),h&&fa(i),c){f=0;while(g=i[f++])_.test(g.type||"")&&c.push(g)}return i=null,p}!function(){var b,c,e=d.createElement("div");for(b in{submit:!0,change:!0,focusin:!0})c="on"+b,(l[b]=c in a)||(e.setAttribute(c,"t"),l[b]=e.attributes[c].expando===!1);e=null}();var ka=/^(?:input|select|textarea)$/i,la=/^key/,ma=/^(?:mouse|pointer|contextmenu|drag|drop)|click/,na=/^(?:focusinfocus|focusoutblur)$/,oa=/^([^.]*)(?:\.(.+)|)/;function pa(){return!0}function qa(){return!1}function ra(){try{return d.activeElement}catch(a){}}function sa(a,b,c,d,e,f){var g,h;if("object"==typeof b){"string"!=typeof c&&(d=d||c,c=void 0);for(h in b)sa(a,h,c,d,b[h],f);return a}if(null==d&&null==e?(e=c,d=c=void 0):null==e&&("string"==typeof c?(e=d,d=void 0):(e=d,d=c,c=void 0)),e===!1)e=qa;else if(!e)return a;return 1===f&&(g=e,e=function(a){return n().off(a),g.apply(this,arguments)},e.guid=g.guid||(g.guid=n.guid++)),a.each(function(){n.event.add(this,b,e,d,c)})}n.event={global:{},add:function(a,b,c,d,e){var f,g,h,i,j,k,l,m,o,p,q,r=n._data(a);if(r){c.handler&&(i=c,c=i.handler,e=i.selector),c.guid||(c.guid=n.guid++),(g=r.events)||(g=r.events={}),(k=r.handle)||(k=r.handle=function(a){return"undefined"==typeof n||a&&n.event.triggered===a.type?void 0:n.event.dispatch.apply(k.elem,arguments)},k.elem=a),b=(b||"").match(G)||[""],h=b.length;while(h--)f=oa.exec(b[h])||[],o=q=f[1],p=(f[2]||"").split(".").sort(),o&&(j=n.event.special[o]||{},o=(e?j.delegateType:j.bindType)||o,j=n.event.special[o]||{},l=n.extend({type:o,origType:q,data:d,handler:c,guid:c.guid,selector:e,needsContext:e&&n.expr.match.needsContext.test(e),namespace:p.join(".")},i),(m=g[o])||(m=g[o]=[],m.delegateCount=0,j.setup&&j.setup.call(a,d,p,k)!==!1||(a.addEventListener?a.addEventListener(o,k,!1):a.attachEvent&&a.attachEvent("on"+o,k))),j.add&&(j.add.call(a,l),l.handler.guid||(l.handler.guid=c.guid)),e?m.splice(m.delegateCount++,0,l):m.push(l),n.event.global[o]=!0);a=null}},remove:function(a,b,c,d,e){var f,g,h,i,j,k,l,m,o,p,q,r=n.hasData(a)&&n._data(a);if(r&&(k=r.events)){b=(b||"").match(G)||[""],j=b.length;while(j--)if(h=oa.exec(b[j])||[],o=q=h[1],p=(h[2]||"").split(".").sort(),o){l=n.event.special[o]||{},o=(d?l.delegateType:l.bindType)||o,m=k[o]||[],h=h[2]&&new RegExp("(^|\\.)"+p.join("\\.(?:.*\\.|)")+"(\\.|$)"),i=f=m.length;while(f--)g=m[f],!e&&q!==g.origType||c&&c.guid!==g.guid||h&&!h.test(g.namespace)||d&&d!==g.selector&&("**"!==d||!g.selector)||(m.splice(f,1),g.selector&&m.delegateCount--,l.remove&&l.remove.call(a,g));i&&!m.length&&(l.teardown&&l.teardown.call(a,p,r.handle)!==!1||n.removeEvent(a,o,r.handle),delete k[o])}else for(o in k)n.event.remove(a,o+b[j],c,d,!0);n.isEmptyObject(k)&&(delete r.handle,n._removeData(a,"events"))}},trigger:function(b,c,e,f){var g,h,i,j,l,m,o,p=[e||d],q=k.call(b,"type")?b.type:b,r=k.call(b,"namespace")?b.namespace.split("."):[];if(i=m=e=e||d,3!==e.nodeType&&8!==e.nodeType&&!na.test(q+n.event.triggered)&&(q.indexOf(".")>-1&&(r=q.split("."),q=r.shift(),r.sort()),h=q.indexOf(":")<0&&"on"+q,b=b[n.expando]?b:new n.Event(q,"object"==typeof b&&b),b.isTrigger=f?2:3,b.namespace=r.join("."),b.rnamespace=b.namespace?new RegExp("(^|\\.)"+r.join("\\.(?:.*\\.|)")+"(\\.|$)"):null,b.result=void 0,b.target||(b.target=e),c=null==c?[b]:n.makeArray(c,[b]),l=n.event.special[q]||{},f||!l.trigger||l.trigger.apply(e,c)!==!1)){if(!f&&!l.noBubble&&!n.isWindow(e)){for(j=l.delegateType||q,na.test(j+q)||(i=i.parentNode);i;i=i.parentNode)p.push(i),m=i;m===(e.ownerDocument||d)&&p.push(m.defaultView||m.parentWindow||a)}o=0;while((i=p[o++])&&!b.isPropagationStopped())b.type=o>1?j:l.bindType||q,g=(n._data(i,"events")||{})[b.type]&&n._data(i,"handle"),g&&g.apply(i,c),g=h&&i[h],g&&g.apply&&M(i)&&(b.result=g.apply(i,c),b.result===!1&&b.preventDefault());if(b.type=q,!f&&!b.isDefaultPrevented()&&(!l._default||l._default.apply(p.pop(),c)===!1)&&M(e)&&h&&e[q]&&!n.isWindow(e)){m=e[h],m&&(e[h]=null),n.event.triggered=q;try{e[q]()}catch(s){}n.event.triggered=void 0,m&&(e[h]=m)}return b.result}},dispatch:function(a){a=n.event.fix(a);var b,c,d,f,g,h=[],i=e.call(arguments),j=(n._data(this,"events")||{})[a.type]||[],k=n.event.special[a.type]||{};if(i[0]=a,a.delegateTarget=this,!k.preDispatch||k.preDispatch.call(this,a)!==!1){h=n.event.handlers.call(this,a,j),b=0;while((f=h[b++])&&!a.isPropagationStopped()){a.currentTarget=f.elem,c=0;while((g=f.handlers[c++])&&!a.isImmediatePropagationStopped())(!a.rnamespace||a.rnamespace.test(g.namespace))&&(a.handleObj=g,a.data=g.data,d=((n.event.special[g.origType]||{}).handle||g.handler).apply(f.elem,i),void 0!==d&&(a.result=d)===!1&&(a.preventDefault(),a.stopPropagation()))}return k.postDispatch&&k.postDispatch.call(this,a),a.result}},handlers:function(a,b){var c,d,e,f,g=[],h=b.delegateCount,i=a.target;if(h&&i.nodeType&&("click"!==a.type||isNaN(a.button)||a.button<1))for(;i!=this;i=i.parentNode||this)if(1===i.nodeType&&(i.disabled!==!0||"click"!==a.type)){for(d=[],c=0;h>c;c++)f=b[c],e=f.selector+" ",void 0===d[e]&&(d[e]=f.needsContext?n(e,this).index(i)>-1:n.find(e,this,null,[i]).length),d[e]&&d.push(f);d.length&&g.push({elem:i,handlers:d})}return h]","i"),va=/<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:-]+)[^>]*)\/>/gi,wa=/\s*$/g,Aa=ca(d),Ba=Aa.appendChild(d.createElement("div"));function Ca(a,b){return n.nodeName(a,"table")&&n.nodeName(11!==b.nodeType?b:b.firstChild,"tr")?a.getElementsByTagName("tbody")[0]||a.appendChild(a.ownerDocument.createElement("tbody")):a}function Da(a){return a.type=(null!==n.find.attr(a,"type"))+"/"+a.type,a}function Ea(a){var b=ya.exec(a.type);return b?a.type=b[1]:a.removeAttribute("type"),a}function Fa(a,b){if(1===b.nodeType&&n.hasData(a)){var c,d,e,f=n._data(a),g=n._data(b,f),h=f.events;if(h){delete g.handle,g.events={};for(c in h)for(d=0,e=h[c].length;e>d;d++)n.event.add(b,c,h[c][d])}g.data&&(g.data=n.extend({},g.data))}}function Ga(a,b){var c,d,e;if(1===b.nodeType){if(c=b.nodeName.toLowerCase(),!l.noCloneEvent&&b[n.expando]){e=n._data(b);for(d in e.events)n.removeEvent(b,d,e.handle);b.removeAttribute(n.expando)}"script"===c&&b.text!==a.text?(Da(b).text=a.text,Ea(b)):"object"===c?(b.parentNode&&(b.outerHTML=a.outerHTML),l.html5Clone&&a.innerHTML&&!n.trim(b.innerHTML)&&(b.innerHTML=a.innerHTML)):"input"===c&&Z.test(a.type)?(b.defaultChecked=b.checked=a.checked,b.value!==a.value&&(b.value=a.value)):"option"===c?b.defaultSelected=b.selected=a.defaultSelected:("input"===c||"textarea"===c)&&(b.defaultValue=a.defaultValue)}}function Ha(a,b,c,d){b=f.apply([],b);var e,g,h,i,j,k,m=0,o=a.length,p=o-1,q=b[0],r=n.isFunction(q);if(r||o>1&&"string"==typeof q&&!l.checkClone&&xa.test(q))return a.each(function(e){var f=a.eq(e);r&&(b[0]=q.call(this,e,f.html())),Ha(f,b,c,d)});if(o&&(k=ja(b,a[0].ownerDocument,!1,a,d),e=k.firstChild,1===k.childNodes.length&&(k=e),e||d)){for(i=n.map(ea(k,"script"),Da),h=i.length;o>m;m++)g=k,m!==p&&(g=n.clone(g,!0,!0),h&&n.merge(i,ea(g,"script"))),c.call(a[m],g,m);if(h)for(j=i[i.length-1].ownerDocument,n.map(i,Ea),m=0;h>m;m++)g=i[m],_.test(g.type||"")&&!n._data(g,"globalEval")&&n.contains(j,g)&&(g.src?n._evalUrl&&n._evalUrl(g.src):n.globalEval((g.text||g.textContent||g.innerHTML||"").replace(za,"")));k=e=null}return a}function Ia(a,b,c){for(var d,e=b?n.filter(b,a):a,f=0;null!=(d=e[f]);f++)c||1!==d.nodeType||n.cleanData(ea(d)),d.parentNode&&(c&&n.contains(d.ownerDocument,d)&&fa(ea(d,"script")),d.parentNode.removeChild(d));return a}n.extend({htmlPrefilter:function(a){return a.replace(va,"<$1>")},clone:function(a,b,c){var d,e,f,g,h,i=n.contains(a.ownerDocument,a);if(l.html5Clone||n.isXMLDoc(a)||!ua.test("<"+a.nodeName+">")?f=a.cloneNode(!0):(Ba.innerHTML=a.outerHTML,Ba.removeChild(f=Ba.firstChild)),!(l.noCloneEvent&&l.noCloneChecked||1!==a.nodeType&&11!==a.nodeType||n.isXMLDoc(a)))for(d=ea(f),h=ea(a),g=0;null!=(e=h[g]);++g)d[g]&&Ga(e,d[g]);if(b)if(c)for(h=h||ea(a),d=d||ea(f),g=0;null!=(e=h[g]);g++)Fa(e,d[g]);else Fa(a,f);return d=ea(f,"script"),d.length>0&&fa(d,!i&&ea(a,"script")),d=h=e=null,f},cleanData:function(a,b){for(var d,e,f,g,h=0,i=n.expando,j=n.cache,k=l.attributes,m=n.event.special;null!=(d=a[h]);h++)if((b||M(d))&&(f=d[i],g=f&&j[f])){if(g.events)for(e in g.events)m[e]?n.event.remove(d,e):n.removeEvent(d,e,g.handle);j[f]&&(delete j[f],k||"undefined"==typeof d.removeAttribute?d[i]=void 0:d.removeAttribute(i),c.push(f))}}}),n.fn.extend({domManip:Ha,detach:function(a){return Ia(this,a,!0)},remove:function(a){return Ia(this,a)},text:function(a){return Y(this,function(a){return void 0===a?n.text(this):this.empty().append((this[0]&&this[0].ownerDocument||d).createTextNode(a))},null,a,arguments.length)},append:function(){return Ha(this,arguments,function(a){if(1===this.nodeType||11===this.nodeType||9===this.nodeType){var b=Ca(this,a);b.appendChild(a)}})},prepend:function(){return Ha(this,arguments,function(a){if(1===this.nodeType||11===this.nodeType||9===this.nodeType){var b=Ca(this,a);b.insertBefore(a,b.firstChild)}})},before:function(){return Ha(this,arguments,function(a){this.parentNode&&this.parentNode.insertBefore(a,this)})},after:function(){return Ha(this,arguments,function(a){this.parentNode&&this.parentNode.insertBefore(a,this.nextSibling)})},empty:function(){for(var a,b=0;null!=(a=this[b]);b++){1===a.nodeType&&n.cleanData(ea(a,!1));while(a.firstChild)a.removeChild(a.firstChild);a.options&&n.nodeName(a,"select")&&(a.options.length=0)}return this},clone:function(a,b){return a=null==a?!1:a,b=null==b?a:b,this.map(function(){return n.clone(this,a,b)})},html:function(a){return Y(this,function(a){var b=this[0]||{},c=0,d=this.length;if(void 0===a)return 1===b.nodeType?b.innerHTML.replace(ta,""):void 0;if("string"==typeof a&&!wa.test(a)&&(l.htmlSerialize||!ua.test(a))&&(l.leadingWhitespace||!aa.test(a))&&!da[($.exec(a)||["",""])[1].toLowerCase()]){a=n.htmlPrefilter(a);try{for(;d>c;c++)b=this[c]||{},1===b.nodeType&&(n.cleanData(ea(b,!1)),b.innerHTML=a);b=0}catch(e){}}b&&this.empty().append(a)},null,a,arguments.length)},replaceWith:function(){var a=[];return Ha(this,arguments,function(b){var c=this.parentNode;n.inArray(this,a)<0&&(n.cleanData(ea(this)),c&&c.replaceChild(b,this))},a)}}),n.each({appendTo:"append",prependTo:"prepend",insertBefore:"before",insertAfter:"after",replaceAll:"replaceWith"},function(a,b){n.fn[a]=function(a){for(var c,d=0,e=[],f=n(a),h=f.length-1;h>=d;d++)c=d===h?this:this.clone(!0),n(f[d])[b](c),g.apply(e,c.get());return this.pushStack(e)}});var Ja,Ka={HTML:"block",BODY:"block"};function La(a,b){var c=n(b.createElement(a)).appendTo(b.body),d=n.css(c[0],"display");return c.detach(),d}function Ma(a){var b=d,c=Ka[a];return c||(c=La(a,b),"none"!==c&&c||(Ja=(Ja||n(" - {/if} - diff --git a/frontend/fic/src/lib/components/FileSize.svelte b/frontend/fic/src/lib/components/FileSize.svelte deleted file mode 100644 index f0338c5b..00000000 --- a/frontend/fic/src/lib/components/FileSize.svelte +++ /dev/null @@ -1,28 +0,0 @@ - - - - {formatSize(size)} - diff --git a/frontend/fic/src/lib/components/FlagKey.svelte b/frontend/fic/src/lib/components/FlagKey.svelte deleted file mode 100644 index 411aa26b..00000000 --- a/frontend/fic/src/lib/components/FlagKey.svelte +++ /dev/null @@ -1,268 +0,0 @@ - - -
    - {#if flag.bonus_gain} -
    - optionnel | {#if flag.bonus_gain > 0}+{/if}{flag.bonus_gain} pts -
    - {/if} - {#if !no_label} - - {/if} - {#if !flag.found && !$settings.hideCaseSensitivity && !flag.ignore_case} - - aA - - {/if} - {#if !flag.found} - {#each values as v, index} - {#if !flag.choices} -
    - {#if flag.type == 'number'} - - {:else if !flag.multiline} - {if (flag.separator && e.keyCode === 13) { e.preventDefault(); addItem(); tick().then(() => { document.getElementById('sol_' + flag.type + '' + flag.id + '_' + (values.length - 1)).focus(); }); return false;}}} - > - {:else} - - {/if} - {#if flag.unit} - {flag.unit} - {/if} - {#if flag.choices_cost > 0} - - {:else if flag.separator && !flag.nb_lines && index == values.length - 1} - - {/if} -
    - {:else if flag.type == 'radio'} - {#each Object.keys(flag.choices) as l, i} -
    - - -
    - {/each} - {:else} - - {/if} - {/each} - {#if flag.help} - {@html flag.help} - {/if} - {:else if value} - - {:else if $submissions && $submissions.flags && $submissions.flags[flag.id]} - - {:else} -
    diff --git a/frontend/fic/src/lib/components/FlagMCQ.svelte b/frontend/fic/src/lib/components/FlagMCQ.svelte deleted file mode 100644 index 0b437746..00000000 --- a/frontend/fic/src/lib/components/FlagMCQ.svelte +++ /dev/null @@ -1,58 +0,0 @@ - - -{#if flag.label} -

    - {flag.label} : - {#if flag.found} -

    -{/if} -{#if !flag.found || flag.justify} - {#each Object.keys(flag.choices) as cid, index} -
    - {#if typeof flag.choices[cid] != "object"} - acc + (previous_values.mcqs[Number(cur)] !== undefined ? 1 : 0), 0) > 0 && Object.keys(flag.choices).reduce((acc, cur) => acc && previous_values.mcqs[Number(cur)] == values[Number(cur)], true)} type="checkbox" id="mcq_{flag.id}_{cid}" bind:checked={values[Number(cid)]} disabled={flag.found || flag.part_solved}> - - {#if values[Number(cid)] && flag.justify} - - {/if} - {:else} - - - {/if} - {#if flag.choices[cid].justification && flag.choices[cid].justification.solved} -
    - {/each} -{/if} -
    diff --git a/frontend/fic/src/lib/components/FormIssue.svelte b/frontend/fic/src/lib/components/FormIssue.svelte deleted file mode 100644 index f2859d23..00000000 --- a/frontend/fic/src/lib/components/FormIssue.svelte +++ /dev/null @@ -1,54 +0,0 @@ - - -
    - {#if exercice || issue.id_exercice} -
    - -
    - {#if exercice.id} - - {:else} - - {/if} -
    -
    - {/if} -
    - -
    - {#if issue.id && $issues_idx[issue.id]} - - {:else} - - {/if} -
    -
    - -
    - -
    - -
    -
    - - - diff --git a/frontend/fic/src/lib/components/Header.svelte b/frontend/fic/src/lib/components/Header.svelte deleted file mode 100644 index dd3cf472..00000000 --- a/frontend/fic/src/lib/components/Header.svelte +++ /dev/null @@ -1,132 +0,0 @@ - - -{#if !$settings.hide_header} - -{/if} -
    - - (isOpen = !isOpen)} /> - - - {#if $settings.hide_header && $settings.end - $settings.start > 0} - - {/if} - - - - -
    - - diff --git a/frontend/fic/src/lib/components/HeaderClock.svelte b/frontend/fic/src/lib/components/HeaderClock.svelte deleted file mode 100644 index 87f2c672..00000000 --- a/frontend/fic/src/lib/components/HeaderClock.svelte +++ /dev/null @@ -1,53 +0,0 @@ - - -{#if $settings && $settings.end} - {#if $settings.end - $settings.start > 0} - - {:else} - - {/if} -{:else} -
    -

    - {$challengeInfo.title} -

    -
    -{/if} diff --git a/frontend/fic/src/lib/components/HeaderIssues.svelte b/frontend/fic/src/lib/components/HeaderIssues.svelte deleted file mode 100644 index 94d6a5af..00000000 --- a/frontend/fic/src/lib/components/HeaderIssues.svelte +++ /dev/null @@ -1,36 +0,0 @@ - - -{#if $issues.length} - - - - Problèmes - {$issues_nb_responses} - - -{/if} diff --git a/frontend/fic/src/lib/components/HeaderPartners.svelte b/frontend/fic/src/lib/components/HeaderPartners.svelte deleted file mode 100644 index fa50cf77..00000000 --- a/frontend/fic/src/lib/components/HeaderPartners.svelte +++ /dev/null @@ -1,30 +0,0 @@ - - -{#if $challengeInfo && $challengeInfo.partners} - - - -{/if} diff --git a/frontend/fic/src/lib/components/NavTags.svelte b/frontend/fic/src/lib/components/NavTags.svelte deleted file mode 100644 index 0bb644ba..00000000 --- a/frontend/fic/src/lib/components/NavTags.svelte +++ /dev/null @@ -1,51 +0,0 @@ - - - - - - Tags - - - -
    - {#each Object.keys($tags).sort(function (a, b) { return a.normalize("NFD").replace(/[\u0300-\u036f]/g, "").toLowerCase().localeCompare(b.normalize("NFD").replace(/[\u0300-\u036f]/g, "").toLowerCase()); }) as itag, index} - {#if (filter === "" && $tags[itag].count > 1) || (filter !== "" && itag.normalize("NFD").replace(/[\u0300-\u036f]/g, "").toLowerCase().indexOf(filter.normalize("NFD").replace(/[\u0300-\u036f]/g, "").toLowerCase()) >= 0)} - - #{itag} - - {#if $my && $my.team_id}{$tags[itag].solved}/{/if}{$tags[itag].count} - - - {/if} - {/each} -
    -
    -
    - - diff --git a/frontend/fic/src/lib/components/NavThemes.svelte b/frontend/fic/src/lib/components/NavThemes.svelte deleted file mode 100644 index d006dc8c..00000000 --- a/frontend/fic/src/lib/components/NavThemes.svelte +++ /dev/null @@ -1,97 +0,0 @@ - - -{#if $themes.length > 0 && ($themes[0].id != 0 || $themes.length > 1)} - - - - Scenarii - - -
    - {#each $themes as th, index} - {#if th.id != 0} - - {th.name} - {#if $my && $my.team_id && th.exercice_count && $myThemes[th.id].exercice_solved >= th.exercice_count} - - - - {/if} - {#if $max_solved > 1 && th.solved == $max_solved} - - - - {/if} - {#if th.exercice_coeff_max > 1} - - - - {/if} - {#if th.locked} - - - - {/if} - - {#if $my && $my.team_id}{$myThemes[th.id].exercice_solved}/{/if}{th.exercice_count} - - - {/if} - {/each} -
    -
    -
    -{/if} -{#if $themesStore && $themesStore["0"] && $themesStore["0"].exercices} - - - - Défis - - -
    - {#each $themesStore["0"].exercices as exercice, index} - - {exercice.title} - {#if $my && $my.id_team && exercice.solved} - - - - {/if} - {#if exercice.curcoeff > 1} - - - - {/if} - {#if $themesStore["0"].locked || exercice.disabled || ($my && !$my.exercices[exercice.id])} - - - - {/if} - - {/each} -
    -
    -
    -{/if} - - diff --git a/frontend/fic/src/lib/components/RegistrationFormCreateTeam.svelte b/frontend/fic/src/lib/components/RegistrationFormCreateTeam.svelte deleted file mode 100644 index 9e3bb945..00000000 --- a/frontend/fic/src/lib/components/RegistrationFormCreateTeam.svelte +++ /dev/null @@ -1,113 +0,0 @@ - - -
    - - -
    -
    - - - -
    - Veuillez indiquer un nom d'équipe valide. -
    -
    -
    -
    - - {#if partR} -

    - {#if !$settings.canJoinTeam} - Membres d'équipe - - {:else} - Chef d'équipe - {/if} -

    - {#if message} -

    {message}

    - {/if} - - {#each value.members as member, mid} - 1} - bind:member={member} - on:delete={RemoveMember} - /> - {/each} - - -
    - - - - {/if} - diff --git a/frontend/fic/src/lib/components/RegistrationFormJoinTeam.svelte b/frontend/fic/src/lib/components/RegistrationFormJoinTeam.svelte deleted file mode 100644 index 81f43702..00000000 --- a/frontend/fic/src/lib/components/RegistrationFormJoinTeam.svelte +++ /dev/null @@ -1,109 +0,0 @@ - - -{#if Object.keys($teams).length} - - - -
    -
    - - {#if partJ} - - {:else} - - {/if} -
    - Veuillez indiquer une équipe valide. -
    -
    -
    -
    - - {#if partJ} -

    - Vos informations -

    - {#if message} -

    {message}

    - {/if} - - - - - - - - - {/if} - -{:else} -

    - Aucune équipe enregistrée pour l'instant. -

    -{/if} diff --git a/frontend/fic/src/lib/components/RegistrationRowMember.svelte b/frontend/fic/src/lib/components/RegistrationRowMember.svelte deleted file mode 100644 index 3f5de459..00000000 --- a/frontend/fic/src/lib/components/RegistrationRowMember.svelte +++ /dev/null @@ -1,37 +0,0 @@ - - - -
    - - -
    -
    - -
    -
    - -
    -
    - -
    - {#if canDelete} -
    - -
    - {/if} -
    diff --git a/frontend/fic/src/lib/components/ResolutionModal.svelte b/frontend/fic/src/lib/components/ResolutionModal.svelte deleted file mode 100644 index 058d02db..00000000 --- a/frontend/fic/src/lib/components/ResolutionModal.svelte +++ /dev/null @@ -1,26 +0,0 @@ - - - - - - Solution du défi - {#if exercice} - : {exercice.title} - {/if} - - - {@html resolution} - - diff --git a/frontend/fic/src/lib/components/ScoreGrid.svelte b/frontend/fic/src/lib/components/ScoreGrid.svelte deleted file mode 100644 index 1ed3909b..00000000 --- a/frontend/fic/src/lib/components/ScoreGrid.svelte +++ /dev/null @@ -1,95 +0,0 @@ - - -{#await req} - - Veuillez patienter &hellips; - -{:then scores} - {#if scores} -
    - - - - - {#if row.reason == "Validation"} - - {#if !$exercices_idx[row.id_exercice].id_theme == 0} - Défi validé - {:else} - Étape validée - {/if} - {:else if row.reason == "First blood"} - - Bonus premier sang - {:else if row.reason == "Bonus flag"} - - Flag bonus complété - {:else if row.reason == "Tries"} - - Malus nombre de tentatives - {:else if row.reason == "Hint"} - - Indice dévoilé - {:else if row.reason == "Display choices"} - - Échange champ de texte contre liste de choix - {:else if row.reason.startsWith("Response ")} - {@const fields = row.reason.split(" ")} - - Validation {fields[1]} no {fields[3]} - {:else} - - {row.reason} - {/if} - {#if row.id_exercice && $exercices_idx[row.id_exercice]} - : - {$exercices_idx[row.id_exercice].title} - - {/if} - - - {Math.trunc(10*row.points)/10} × {#if row.reason.startsWith("Response ")}{Math.trunc($settings.questionGainRatio * 1000)/10} % ÷ {$settings.questionGainRatio / row.coeff}{:else if row.reason == "Validation" && $settings.questionGainRatio != 0}({row.coeff + $settings.questionGainRatio}{Math.trunc($settings.questionGainRatio * 1000)/10} %){:else}{row.coeff}{/if} - - - {Math.trunc(10*row.points * row.coeff)/10} - -
    - {:else} - - Vous n'avez fait aucune action vous faisant gagner ou perdre des points. - - {/if} - -{:catch error} - - Une erreur s'est produite: {JSON.stringify(error)} - - -{/await} diff --git a/frontend/fic/src/lib/components/TeamChangeName.svelte b/frontend/fic/src/lib/components/TeamChangeName.svelte deleted file mode 100644 index e8d8d13f..00000000 --- a/frontend/fic/src/lib/components/TeamChangeName.svelte +++ /dev/null @@ -1,109 +0,0 @@ - - - - - - Changer de nom d'équipe - - - {#if sberr || message} -

    - {#if !sberr} - Votre demande a bien été envoyée ! - {:else} - {sberr} - {/if} - {message} -

    - {/if} -
    -
    - -
    -
    - - -
    -
    -
    -
    -
    -
    diff --git a/frontend/fic/src/lib/components/TeamChangePassword.svelte b/frontend/fic/src/lib/components/TeamChangePassword.svelte deleted file mode 100644 index cccc818f..00000000 --- a/frontend/fic/src/lib/components/TeamChangePassword.svelte +++ /dev/null @@ -1,27 +0,0 @@ - - - - - - Changer de mot de passe - - -

    - Si vous souhaitez changer de mot de passe : -

    - -
    -
    diff --git a/frontend/fic/src/lib/components/TeamMembers.svelte b/frontend/fic/src/lib/components/TeamMembers.svelte deleted file mode 100644 index 33960f54..00000000 --- a/frontend/fic/src/lib/components/TeamMembers.svelte +++ /dev/null @@ -1,37 +0,0 @@ - - - - - - Membres de l'équipe - - {#if members && members.length} - - {#each members as member (member.id)} - - {member.firstname} - {#if member.nickname} - {member.nickname} - {/if} - {member.lastname} - {#if member.company}– {member.company}{/if} - - {/each} - - {:else} - - Passez voir l'équipe d'organisation pour compléter ces informations. - - {/if} - diff --git a/frontend/fic/src/lib/components/ThemeNav.svelte b/frontend/fic/src/lib/components/ThemeNav.svelte deleted file mode 100644 index 8bd14b8f..00000000 --- a/frontend/fic/src/lib/components/ThemeNav.svelte +++ /dev/null @@ -1,57 +0,0 @@ - - - - {#each theme.exercices as ex, index} - - {#if ex.id == exercice.id} - - {ex.title} - {#if ex.curcoeff > 1.0} - - {:else if $my && $my.exercices[ex.id]} - - {ex.title} - {#if ex.curcoeff > 1.0} - - {:else} - - {ex.title} - {#if ex.curcoeff > 1.0} - - {/if} - - {/each} - - - diff --git a/frontend/fic/src/lib/stores/challengeinfo.js b/frontend/fic/src/lib/stores/challengeinfo.js deleted file mode 100644 index 734fdcd6..00000000 --- a/frontend/fic/src/lib/stores/challengeinfo.js +++ /dev/null @@ -1,27 +0,0 @@ -import { readable, writable } from 'svelte/store'; - -function createChallengeStore() { - const { subscribe, set, update } = writable({}); - - return { - subscribe, - - refresh: async (cb) => { - challengeInfo.update(await fetch('challenge.json', {headers: {'Accept': 'application/json'}}), cb); - }, - - update: (res_challenge, cb) => { - if (res_challenge.status === 200) { - res_challenge.json().then((challenge) => { - update((s) => (Object.assign({}, challenge))); - - if (cb) { - cb(challenge); - } - }); - } - }, - } -} - -export const challengeInfo = createChallengeStore(); diff --git a/frontend/fic/src/lib/stores/common.js b/frontend/fic/src/lib/stores/common.js deleted file mode 100644 index 73a83efc..00000000 --- a/frontend/fic/src/lib/stores/common.js +++ /dev/null @@ -1 +0,0 @@ -export let stop_refresh = { state: false }; diff --git a/frontend/fic/src/lib/stores/downloaded.js b/frontend/fic/src/lib/stores/downloaded.js deleted file mode 100644 index f9170135..00000000 --- a/frontend/fic/src/lib/stores/downloaded.js +++ /dev/null @@ -1,25 +0,0 @@ -import { writable } from 'svelte/store'; - -function createDownloadedStore() { - let init = { }; - try { - if (window.localStorage && window.localStorage.getItem("downloadedStore")) { - init = JSON.parse(window.localStorage.getItem("downloadedStore")); - } - } catch { - init = { }; - } - - const { subscribe, set, update } = writable(init); - - return { - subscribe, - - update: (u) => { - update(u); - if (window.localStorage) localStorage.setItem("downloadedStore", JSON.stringify(init)); - }, - } -} - -export const hasDownloaded = createDownloadedStore(); diff --git a/frontend/fic/src/lib/stores/exercices.js b/frontend/fic/src/lib/stores/exercices.js deleted file mode 100644 index 139a50a5..00000000 --- a/frontend/fic/src/lib/stores/exercices.js +++ /dev/null @@ -1,19 +0,0 @@ -import { derived, writable } from 'svelte/store'; - -import { exercices_idx_urlid } from './themes'; - -export const set_current_exercice = writable(null) - -export const current_exercice = derived( - [set_current_exercice, exercices_idx_urlid], - ([$set_current_exercice, $exercices_idx_urlid]) => { - if ($exercices_idx_urlid === null || Object.keys($exercices_idx_urlid).length == 0) { - return null; - } - - if ($exercices_idx_urlid[$set_current_exercice]) - return $exercices_idx_urlid[$set_current_exercice]; - - return undefined; - } -) diff --git a/frontend/fic/src/lib/stores/issues.js b/frontend/fic/src/lib/stores/issues.js deleted file mode 100644 index 09309915..00000000 --- a/frontend/fic/src/lib/stores/issues.js +++ /dev/null @@ -1,98 +0,0 @@ -import { derived, writable } from 'svelte/store'; - -import { stop_refresh } from './common'; - -let refresh_interval_issues = null; - -function createIssuesStore() { - const { subscribe, set, update } = writable([]); - - function updateFunc (res_issues, cb=null) { - if (res_issues.status === 200) { - res_issues.json().then((issues) => { - update((i) => issues); - - if (cb) { - cb(issues); - } - }); - } else if (res_issues.status === 404) { - update((i) => ([])); - } - } - - async function refreshFunc(cb=null, interval=null) { - if (refresh_interval_issues) - clearInterval(refresh_interval_issues); - if (interval === null) { - interval = Math.floor(Math.random() * 24000) + 32000; - } - if (stop_refresh.state) { - return; - } - refresh_interval_issues = setInterval(refreshFunc, interval); - - updateFunc(await fetch('issues.json', {headers: {'Accept': 'application/json'}}), cb); - } - - return { - subscribe, - - refresh: refreshFunc, - - update: updateFunc, - }; -} - -export const issuesStore = createIssuesStore(); - -export const issues = derived( - issuesStore, - ($issuesStore) => { - $issuesStore.forEach(function(issue, k) { - $issuesStore[k].texts.reverse(); - }) - return $issuesStore; - }, -); - -export const issues_idx = derived( - issues, - ($issues) => { - const issues_idx = {}; - - $issues.forEach(function(issue, k) { - issues_idx[issue.id] = issue; - }) - - return issues_idx; - }, -); - -export const issues_nb_responses = derived( - issuesStore, - ($issuesStore) => { - let issues_nb_responses = 0; - - $issuesStore.forEach(function(issue, k) { - issues_nb_responses += issue.texts.length; - }) - - return issues_nb_responses; - }, -); - -export const issues_need_info = derived( - issuesStore, - ($issuesStore) => { - let issues_need_info = 0; - - $issuesStore.forEach(function(issue, k) { - if (issue.state == 'need-info') issues_need_info++; - }) - - return issues_need_info; - }, -); - -export const issues_known_responses = writable(0); diff --git a/frontend/fic/src/lib/stores/my.js b/frontend/fic/src/lib/stores/my.js deleted file mode 100644 index 3dfe3ac9..00000000 --- a/frontend/fic/src/lib/stores/my.js +++ /dev/null @@ -1,69 +0,0 @@ -import { writable } from 'svelte/store'; - -import { stop_refresh } from './common'; - -let refresh_interval_my = null; - -function createMyStore() { - const { subscribe, set, update } = writable(null); - - function updateFunc(res_my, cb=null) { - if (res_my.status === 200) { - res_my.json().then((my) => { - for (let k in my.exercices) { - my.exercices[k].id = k; - - if (my.exercices[k].flags) { - let nb = 0; - for (let j in my.exercices[k].flags) { - if (my.exercices[k].flags[j].type && !my.exercices[k].flags[j].found) - nb += 1; - } - my.exercices[k].non_found_flags = nb; - } - - if (my.team_id === 0 && my.exercices[k].hints) { - for (let j in my.exercices[k].hints) { - my.exercices[k].hints[j].hidden = true; - } - } - } - - update((m) => (Object.assign(m?m:{}, my))); - - if (cb) { - cb(my); - } - }); - } else if (res_my.status === 404) { - update((m) => (null)); - if (cb) { - cb(null); - } - } - } - - async function refreshFunc(cb=null, interval=null) { - if (refresh_interval_my) - clearInterval(refresh_interval_my); - if (interval === null) { - interval = Math.floor(Math.random() * 24000) + 24000; - } - if (stop_refresh.state) { - return; - } - refresh_interval_my = setInterval(refreshFunc, interval); - - updateFunc(await fetch('my.json', {headers: {'Accept': 'application/json'}}), cb); - } - - return { - subscribe, - - refresh: refreshFunc, - - update: updateFunc, - }; -} - -export const my = createMyStore(); diff --git a/frontend/fic/src/lib/stores/myresponses.js b/frontend/fic/src/lib/stores/myresponses.js deleted file mode 100644 index e69de29b..00000000 diff --git a/frontend/fic/src/lib/stores/mythemes.js b/frontend/fic/src/lib/stores/mythemes.js deleted file mode 100644 index e1744866..00000000 --- a/frontend/fic/src/lib/stores/mythemes.js +++ /dev/null @@ -1,75 +0,0 @@ -import { derived } from 'svelte/store'; - -import seedrandom from 'seedrandom'; - -import { my } from './my.js'; -import { themes as themesStore } from './themes.js'; - -export const myThemes = derived([my, themesStore], ([$my, $themesStore]) => { - const mythemes = {}; - - for (let key in $themesStore) { - mythemes[key] = {exercice_solved: 0}; - - if ($my && $my.exercices) { - for (const exercice of $themesStore[key].exercices) { - if ($my.exercices[exercice.id] && $my.exercices[exercice.id].solved_rank) { - mythemes[key].exercice_solved++; - } - } - } - } - - return mythemes; -}); - -export const themes = derived( - [my, themesStore], - ([$my, $themesStore]) => { - const arr = []; - for (let th in $themesStore) { - $themesStore[th].id = th - arr.push($themesStore[th]); - } - const size = arr.length; - const rng = new seedrandom($my && $my.team_id ? $my.team_id : 0); - const respD = []; - const respE = []; - const keys = []; - - for(let i=0;i { - const tags = {}; - - for (const key in $themesStore) { - for (const exercice of $themesStore[key].exercices) { - exercice.tags.forEach((tag) => { - tag = tag.normalize("NFD").replace(/[\u0300-\u036f]/g, "").toLowerCase(); - if (!tags[tag]) - tags[tag] = {count: 1, solved: 0}; - else - tags[tag].count += 1; - - if ($my && $my.exercices && $my.exercices[exercice.id] && $my.exercices[exercice.id].solved_rank) - tags[tag].solved += 1; - }); - } - } - - return tags; -}); diff --git a/frontend/fic/src/lib/stores/settings.js b/frontend/fic/src/lib/stores/settings.js deleted file mode 100644 index cc8db08a..00000000 --- a/frontend/fic/src/lib/stores/settings.js +++ /dev/null @@ -1,165 +0,0 @@ -import { readable, writable } from 'svelte/store'; - -import { stop_refresh } from './common'; -import { my } from './my'; - -let refresh_interval_settings = null; -let refresh_timeout_settings = null; - -function createSettingsStore() { - const { subscribe, set, update } = writable({}); - - function updateFunc(res_settings, cb) { - const recvTime = (new Date()).getTime(); - - if (res_settings.status === 200) { - res_settings.json().then((settings) => { - if (settings.start) - settings.start = new Date(settings.start); - if (settings.end) - settings.end = new Date(settings.end); - if (settings.generation) - settings.generation = new Date(settings.generation); - if (settings.nextchangetime) - settings.nextchangetime = new Date(settings.nextchangetime); - if (!settings.disablesubmitbutton) - settings.disablesubmitbutton = null; - - settings.recvTime = recvTime; - const x_fic_time = res_settings.headers.get("x-fic-time"); - if (x_fic_time) { - settings.currentTime = Math.floor(x_fic_time * 1000); - } else { - settings.currentTime = settings.recvTime; - } - - update((s) => (Object.assign({}, settings))); - - if (cb) { - cb(settings); - } - }); - } - } - - async function refreshFunc(cb=null, interval=null) { - if (refresh_interval_settings) - clearInterval(refresh_interval_settings); - if (interval === null) { - interval = Math.floor(Math.random() * 24000) + 32000; - } - if (stop_refresh.state) { - return; - } - refresh_interval_settings = setInterval(refreshFunc, interval); - - if (!cb) { - // Before we start, update settings more frequently. - cb = function(stgs) { - const srv_cur = new Date(Date.now() + (stgs.currentTime - stgs.recvTime)); - - if (stgs.start > srv_cur) { - const startIn = stgs.start - srv_cur; - if (refresh_timeout_settings) - clearTimeout(refresh_timeout_settings); - - if (startIn > 15000) { - refresh_timeout_settings = setTimeout(refreshFunc, Math.floor(Math.random() * 3500) + 10000); - } else if (startIn > 1500) { - refresh_timeout_settings = setTimeout(refreshFunc, startIn - 1000 - Math.floor(Math.random() * 500)) - } else { - // On scheduled start time, refresh my.json file - refresh_timeout_settings = setTimeout(my.refresh, startIn + Math.floor(Math.random() * 200)) - } - } else if (stgs.nextchangetime > srv_cur) { - if (refresh_timeout_settings) - clearTimeout(refresh_timeout_settings); - refresh_timeout_settings = setTimeout(() => { - refreshFunc(); - setTimeout(my.refresh, 4500 + Math.floor(Math.random() * 2000)); - }, stgs.nextchangetime - srv_cur + 500 + Math.floor(Math.random() * 500)); - } - }; - } - - updateFunc(await fetch('settings.json', {headers: {'Accept': 'application/json'}}), cb); - }; - - return { - subscribe, - - refresh: refreshFunc, - - update: updateFunc, - } -} - -export const settings = createSettingsStore(); - -function updateTime(settings) { - const time = {}; - - const srv_cur = new Date(Date.now() + (settings.currentTime - settings.recvTime)); - - let remain = 0; - if (settings.start === undefined || settings.start == 0) { - return time; - } else if (settings.start > srv_cur) { - time.startIn = Math.floor((settings.start - srv_cur) / 1000); - remain = settings.end - settings.start; - } else if (settings.end > srv_cur) { - time.startIn = 0; - remain = settings.end - srv_cur; - } - - time.progression = 1 - remain / (settings.end - settings.start); - - remain = remain / 1000; - - if (remain < 0) { - remain = 0; - time.end = true; - time.expired = true; - } else if (remain < 60) { - time.end = false; - time.expired = true; - } else { - time.end = false; - time.expired = false; - } - - time.remaining = remain; - time.hours = Math.floor(remain / 3600); - time.minutes = Math.floor((remain % 3600) / 60); - time.seconds = Math.floor(remain % 60); - - if (time.hours <= 9) { - time.hours = "0" + time.hours; - } - if (time.minutes <= 9) { - time.minutes = "0" + time.minutes; - } - if (time.seconds <= 9) { - time.seconds = "0" + time.seconds; - } - - return time; -} - -export const time = readable({}, function start(set) { - let _settings = {}; - - const unsubscribe = settings.subscribe((settings) => { - _settings = settings; - }); - - set(updateTime(_settings)); - const interval = setInterval(() => { - set(updateTime(_settings)); - }, 1000); - - return function stop() { - clearInterval(interval); - unsubscribe(); - } -}); diff --git a/frontend/fic/src/lib/stores/submissions.js b/frontend/fic/src/lib/stores/submissions.js deleted file mode 100644 index c4b5abd8..00000000 --- a/frontend/fic/src/lib/stores/submissions.js +++ /dev/null @@ -1,33 +0,0 @@ -import { writable } from 'svelte/store'; - -function createSubmissionsStore() { - let init = { - flags: { }, - mcqs: { }, - justifications: { }, - }; - try { - if (window.localStorage && window.localStorage.getItem("submissionsStore")) { - init = JSON.parse(window.localStorage.getItem("submissionsStore")); - } - } catch { - init = { - flags: { }, - mcqs: { }, - justifications: { }, - }; - } - - const { subscribe, set, update } = writable(init); - - return { - subscribe, - - update: (u) => { - update(u); - if (window.localStorage) localStorage.setItem("submissionsStore", JSON.stringify(init)); - }, - } -} - -export const submissions = createSubmissionsStore(); diff --git a/frontend/fic/src/lib/stores/teams.js b/frontend/fic/src/lib/stores/teams.js deleted file mode 100644 index a583f60d..00000000 --- a/frontend/fic/src/lib/stores/teams.js +++ /dev/null @@ -1,78 +0,0 @@ -import { derived, writable } from 'svelte/store'; - -import { stop_refresh } from './common'; - -let refresh_interval_teams = null; - -function createTeamsStore() { - const { subscribe, set, update } = writable({}); - - function updateFunc(res_teams, cb=null) { - if (res_teams.status === 200) { - res_teams.json().then((teams) => { - update((t) => teams); - - if (cb) { - cb(teams); - } - }); - } - } - - async function refreshFunc(cb=null, interval=null) { - if (refresh_interval_teams) - clearInterval(refresh_interval_teams); - if (interval === null) { - interval = Math.floor(Math.random() * 24000) + 32000; - } - if (stop_refresh.state) { - return; - } - refresh_interval_teams = setInterval(refreshFunc, interval); - - updateFunc(await fetch('teams.json', {headers: {'Accept': 'application/json'}}), cb); - } - - return { - subscribe, - - refresh: refreshFunc, - - update: updateFunc, - }; -} - -export const teamsStore = createTeamsStore(); - -export const teams = derived( - teamsStore, - ($teamsStore) => { - const teams = {}; - - for (const tid in $teamsStore) { - teams[tid] = $teamsStore[tid]; - teams[tid].id = Number(tid); - } - - return teams; - } -); - -export const teams_count = derived( - teamsStore, - ($teamsStore) => Object.keys(teams).length -); - -export const rank = derived( - teams, - ($teams) => { - const rank = []; - - for (const tid in $teams) { - rank.push($teams[tid]); - } - rank.sort((a, b) => (a.rank > b.rank ? 1 : (a.rank == b.rank ? 0 : -1))); - - return rank; - } -); diff --git a/frontend/fic/src/lib/stores/themes.js b/frontend/fic/src/lib/stores/themes.js deleted file mode 100644 index 25c5ea83..00000000 --- a/frontend/fic/src/lib/stores/themes.js +++ /dev/null @@ -1,157 +0,0 @@ -import { derived, writable } from 'svelte/store'; - -import { stop_refresh } from './common'; - -let refresh_interval_themes = null; - -function createThemesStore() { - const { subscribe, set, update } = writable({}); - - async function updateFunc (res_themes, cb=null) { - if (res_themes.status === 200) { - const themes = await res_themes.json(); - - update((t) => themes); - - if (cb) { - cb(themes); - } - } - } - - async function refreshFunc(cb=null, interval=null) { - if (refresh_interval_themes) - clearInterval(refresh_interval_themes); - if (interval === null) { - interval = Math.floor(Math.random() * 24000) + 32000; - } - if (stop_refresh.state) { - return; - } - refresh_interval_themes = setInterval(refreshFunc, interval); - - await updateFunc(await fetch('themes.json', {headers: {'Accept': 'application/json'}}), cb); - } - - return { - subscribe, - - refresh: refreshFunc, - - update: updateFunc, - }; -} - -export const themesStore = createThemesStore(); - -export const themes = derived( - themesStore, - ($themesStore) => { - const themes = {}; - - for (const key in $themesStore) { - const theme = $themesStore[key]; - - themes[key] = theme - themes[key].exercice_count = theme.exercices.length; - themes[key].exercice_coeff_max = 0; - themes[key].max_gain = 0; - - for (const k in theme.exercices) { - const exercice = theme.exercices[k]; - - themes[key].max_gain += exercice.gain; - if (themes[key].exercice_coeff_max < exercice.curcoeff) { - themes[key].exercice_coeff_max = exercice.curcoeff; - } - - if (k > 0) - themes[key].exercices[k-1].next = k; - } - } - - return themes; - }, -); - -export const themes_idx = derived( - themes, - ($themes) => { - const ret = {}; - - for (const key in $themes) { - const theme = $themes[key]; - - ret[theme.urlid] = theme; - } - - return ret; - }, - null, -); - -export const exercices_idx = derived( - themesStore, - ($themesStore) => { - const ret = {}; - - for (const key in $themesStore) { - const theme = $themesStore[key]; - for (let exercice of theme.exercices) { - ret[exercice.id] = exercice; - ret[exercice.id].id_theme = key; - } - } - - return ret; - }, -); - -export const exercices_idx_urlid = derived( - themesStore, - ($themesStore) => { - const ret = {}; - - for (const key in $themesStore) { - const theme = $themesStore[key]; - for (let exercice of theme.exercices) { - ret[exercice.urlid] = exercice; - } - } - - return ret; - }, - null -); - -export const max_solved = derived( - themesStore, - ($themesStore) => { - let ret = 0; - - for (const key in $themesStore) { - const theme = $themesStore[key]; - if (theme.solved > ret) { - ret = theme.solved; - } - } - - return ret; - }, -); - -export const set_current_theme = writable(null) - -export const current_theme = derived( - [set_current_theme, themes_idx], - ([$set_current_theme, $themes_idx]) => { - if ($themes_idx === null || Object.keys($themes_idx).length == 0) { - return null; - } - - if ($themes_idx[$set_current_theme]) - return $themes_idx[$set_current_theme]; - - return undefined; - } -) diff --git a/frontend/fic/src/lib/wait.js b/frontend/fic/src/lib/wait.js deleted file mode 100644 index 9b165a98..00000000 --- a/frontend/fic/src/lib/wait.js +++ /dev/null @@ -1,22 +0,0 @@ -import { writable } from 'svelte/store'; - -import { my } from '$lib/stores/my.js'; -import { teamsStore } from '$lib/stores/teams.js'; - -export let waitInProgress = writable(false); -export let timeouted = writable(false); - -export function waitDiff(i, exercice) { - timeouted.set(false); - my.refresh((my) => { - if (my && (my.exercices[exercice.id].tries != exercice.tries || my.exercices[exercice.id].solved_rank != exercice.solved_rank || my.exercices[exercice.id].solved_time != exercice.solved_time)) { - waitInProgress.set(false); - teamsStore.refresh(); - } else if (i > 0) { - setTimeout(waitDiff, (12-i)*50+440, i-1, exercice); - } else { - timeouted.set(true); - waitInProgress.set(false); - } - }) -} diff --git a/frontend/fic/src/routes/+error.svelte b/frontend/fic/src/routes/+error.svelte deleted file mode 100644 index c284ab7b..00000000 --- a/frontend/fic/src/routes/+error.svelte +++ /dev/null @@ -1,5 +0,0 @@ - - -

    {$page.status} : {$page.error.message}

    diff --git a/frontend/fic/src/routes/+layout.js b/frontend/fic/src/routes/+layout.js deleted file mode 100644 index 2b7d9506..00000000 --- a/frontend/fic/src/routes/+layout.js +++ /dev/null @@ -1,22 +0,0 @@ -import { challengeInfo } from '$lib/stores/challengeinfo.js'; -import { stop_refresh } from '$lib/stores/common'; -import { issuesStore } from '$lib/stores/issues.js'; -import { my } from '$lib/stores/my.js'; -import { teamsStore } from '$lib/stores/teams.js'; -import { themesStore } from '$lib/stores/themes.js'; -import { settings, time } from '$lib/stores/settings.js'; - -export const ssr = false; - -export async function load() { - await challengeInfo.refresh(); - await settings.refresh(); - await themesStore.refresh(); - teamsStore.refresh(); - my.refresh((my) => { - if (my && my.team_id === 0) { - stop_refresh.state = true; - } - }); - issuesStore.refresh(); -} diff --git a/frontend/fic/src/routes/+layout.svelte b/frontend/fic/src/routes/+layout.svelte deleted file mode 100644 index bec2495b..00000000 --- a/frontend/fic/src/routes/+layout.svelte +++ /dev/null @@ -1,70 +0,0 @@ - - - - {#if $challengeInfo} - {$challengeInfo.title} - - {#if $challengeInfo.main_logo && $challengeInfo.main_logo.length} - - {/if} - {/if} - - - - -{#if $settings.globaltopmessage} -
    - {$settings.globaltopmessage} -
    -{/if} -
    - -{#if !$my && $page.route.id != "/register"} - - {#if $settings.allowRegistration} - - Votre équipe n'est pas encore enregistrée. Rendez-vous sur cette page pour procéder à votre inscription. - - {:else} - - Il semblerait qu'il y ait eu un problème lors de l'attribution de votre certificat. Veuillez vous signaler auprès de notre équipe afin de corriger ce problème. - - {/if} - -{/if} - - - - diff --git a/frontend/fic/src/routes/+page.js b/frontend/fic/src/routes/+page.js deleted file mode 100644 index afe2aa7f..00000000 --- a/frontend/fic/src/routes/+page.js +++ /dev/null @@ -1,5 +0,0 @@ -import { set_current_theme } from '$lib/stores/themes'; - -export function load() { - set_current_theme.set(null); -} diff --git a/frontend/fic/src/routes/+page.svelte b/frontend/fic/src/routes/+page.svelte deleted file mode 100644 index 5b4a0bac..00000000 --- a/frontend/fic/src/routes/+page.svelte +++ /dev/null @@ -1,123 +0,0 @@ - - - - {#if $my} - {#if !($my.team_id)} - - Attention : puisqu'il s'agit de captures effectuées dans le but de découvrir si des actes malveillants ont été commis sur différents systèmes d'information, les contenus qui sont téléchargeables peuvent contenir du contenu malveillant ! - - {:else if $teams[$my.team_id]} - - Félicitations {#if $my.members}{#each $my.members as member, index (member.id)}{#if member.id !== $my.members[0].id}{#if member.id === $my.members[$my.members.length - 1].id} et {:else}, {/if}{/if}{member.firstname} {member.lastname}{/each} {/if}! vous êtes maintenant connecté à l'espace de votre équipe {$teams[$my.team_id].name}. - {#if !$settings.denyNameChange}Vous pouvez changer ce nom dès maintenant en vous rendant sur la page de votre équipe.{/if} - - - {#if !$settings.ignoreTeamMembers && $my.team_id && (!$my.members || !$my.members.length)} - - Les membres de votre équipe ne sont pas encore enregistrés. Passez voir l'équipe serveur pour corriger cela. - - {/if} - {/if} - {/if} - - - {#if item.exercice} - {@const theme = item.theme} - {@const exercice = item.exercice} - - {:else} - {@const th = item.theme} - - {/if} - - diff --git a/frontend/fic/src/routes/[theme]/+layout.js b/frontend/fic/src/routes/[theme]/+layout.js deleted file mode 100644 index c5264093..00000000 --- a/frontend/fic/src/routes/[theme]/+layout.js +++ /dev/null @@ -1,5 +0,0 @@ -import { set_current_theme } from '$lib/stores/themes'; - -export function load({ params }) { - set_current_theme.set(params.theme); -} diff --git a/frontend/fic/src/routes/[theme]/+layout.svelte b/frontend/fic/src/routes/[theme]/+layout.svelte deleted file mode 100644 index bbb71a88..00000000 --- a/frontend/fic/src/routes/[theme]/+layout.svelte +++ /dev/null @@ -1,121 +0,0 @@ - - - - {$current_theme?($current_theme.name + " - "):""}{$challengeInfo.title} - - -{#if $current_theme === null} - - - Chargement en cours… - -{:else if !$current_theme} - - - - Ce scénario n'existe pas. - - -{:else} - -{/if} - - diff --git a/frontend/fic/src/routes/[theme]/+page.svelte b/frontend/fic/src/routes/[theme]/+page.svelte deleted file mode 100644 index 32903d10..00000000 --- a/frontend/fic/src/routes/[theme]/+page.svelte +++ /dev/null @@ -1,165 +0,0 @@ - - -{#if $current_theme.id == 0} - - - -{:else} - - - - - -
    - {#if $current_theme.locked} -
    -
    -
    - CONFIDENTIEL -
    -
    -
    - {/if} - - -

    {@html $current_theme.headline}

    -

    {@html $current_theme.intro}

    - - {#if $current_theme.partner_txt || $current_theme.partner_img || $current_theme.partner_href} - - - {#if $current_theme.partner_img} - En-tête du scénario - {/if} - {#if $current_theme.partner_txt || $current_theme.partner_href} - - {#if $current_theme.partner_txt} - {@html $current_theme.partner_txt} - {/if} - {#if $current_theme.partner_href} - - {/if} - - {/if} - - - {/if} -
    -
    -
    - - - {#if $current_theme.exercices && $current_theme.exercices.length} -
      - {#each $current_theme.exercices as exercice, index} -
    • -
      - {#if index + 1 == $current_theme.exercices.length} -
      - {:else} -
      - {/if} -
      -
      - - - - -
      -
      -
      - - {#if $my && $my.exercices[exercice.id] && $my.exercices[exercice.id].wip} - - {#if exercice.curcoeff > 1.0} -
      -
      - {#each exercice.tags as tag, idx} - #{tag} - {/each} -
      -
      -

      {@html exercice.headline}

      -
      -
      - {#if $my && $my.exercices[exercice.id]} - - - {:else} - - - {/if} -
      -
      -
    • - {/each} -
    - {:else} -

    - Aucun contenu disponible actuellement. -

    - {/if} - -
    - -
    -{/if} - - diff --git a/frontend/fic/src/routes/[theme]/[exercice]/+layout.js b/frontend/fic/src/routes/[theme]/[exercice]/+layout.js deleted file mode 100644 index 998cbe53..00000000 --- a/frontend/fic/src/routes/[theme]/[exercice]/+layout.js +++ /dev/null @@ -1,5 +0,0 @@ -import { set_current_exercice } from '$lib/stores/exercices'; - -export function load({ params }) { - set_current_exercice.set(params.exercice); -} diff --git a/frontend/fic/src/routes/[theme]/[exercice]/+layout.svelte b/frontend/fic/src/routes/[theme]/[exercice]/+layout.svelte deleted file mode 100644 index 4d40077c..00000000 --- a/frontend/fic/src/routes/[theme]/[exercice]/+layout.svelte +++ /dev/null @@ -1,34 +0,0 @@ - - - - {$current_exercice?$current_exercice.title+" - ":""}{$challengeInfo.title} - - -{#if $current_exercice === null} -
    - - Chargement en cours… -
    -{:else if !$current_exercice} - - - Vous n'avez pas encore accès à ce défi. - -{:else} - {#if $current_theme.id != 0} - - {/if} - -{/if} diff --git a/frontend/fic/src/routes/[theme]/[exercice]/+page.svelte b/frontend/fic/src/routes/[theme]/[exercice]/+page.svelte deleted file mode 100644 index 42ad7b06..00000000 --- a/frontend/fic/src/routes/[theme]/[exercice]/+page.svelte +++ /dev/null @@ -1,293 +0,0 @@ - - -{#if $current_exercice} - - - {#if $current_theme.locked} -
    -
    -
    - CONFIDENTIEL -
    -
    -
    - {/if} -

    {$current_exercice.title}

    -
    - {#each $current_exercice.tags as tag, index} - #{tag} - {/each} -
    - {#if !$my || !$my.exercices[$current_exercice.id]} -

    {@html $current_exercice.headline}

    - {:else} - {#if $my.exercices[$current_exercice.id].wip} - - - - Cette étape est marquée comme étant en cours d'élaboration. - - Elle n'est pas prête à être tentée. Vous devriez directement passer à l'étape suivante. - - {/if} -

    {@html $my.exercices[$current_exercice.id].statement}

    - {#if $my.exercices[$current_exercice.id].issue} - - {@html $my.exercices[$current_exercice.id].issue} - - {/if} - {/if} -
    - - - - - -
    - {#if $settings.discountedFactor > 0 && $my && $my.exercices[$current_exercice.id]} -
    - Cote -
    - {:else} -
    - Gain -
    - {/if} -
    - {#if $settings.discountedFactor && $current_exercice.solved} - - {Math.trunc($current_exercice.gain * (1-$settings.discountedFactor*$current_exercice.solved)*10)/10} {$current_exercice.gain==1?"point":"points"} - {:else} - {$current_exercice.gain} {$current_exercice.gain==1?"point":"points"} - {/if} -
    - {#if $settings.firstBlood && $current_exercice.solved < 1} -
    - +{$settings.firstBlood * 100}% (prem's) -
    - {:else if $settings.discountedFactor > 0 && $my && $my.exercices[$current_exercice.id]} -
    - initialement {$current_exercice.gain} {$current_exercice.gain==1?"point":"points"} -
    - {/if} - {#if $current_exercice.curcoeff != 1.0 || $settings.exerciceCurrentCoefficient != 1.0} -
    - {#if $current_exercice.curcoeff * $settings.exerciceCurrentCoefficient > 1}+{Math.round(($current_exercice.curcoeff * $settings.exerciceCurrentCoefficient - 1) * 100)}{:else}-{Math.round((1-($current_exercice.curcoeff * $settings.exerciceCurrentCoefficient)) * 100)}{/if}% (bonus) -
    - {/if} -
    - - -
    -
    - Tenté par -
    -
    - {#if !$current_exercice.tried} - aucune équipe - {:else} - {$current_exercice.tried} {$current_exercice.tried == 1?"équipe":"équipes"} - {#if $my && $my.exercices[$current_exercice.id] && $my.exercices[$current_exercice.id].total_tries} - (cumulant {$my.exercices[$current_exercice.id].total_tries} {$my.exercices[$current_exercice.id].total_tries == 1?"tentative":"tentatives"}) - {/if} - {/if} -
    -
    - - -
    -
    - Résolu par -
    -
    - {#if !$current_exercice.solved} - aucune équipe - {:else} - {$current_exercice.solved} {$current_exercice.solved == 1?"équipe":"équipes"} - {/if} -
    -
    - - -
    -
    - {#if !$current_exercice.solved} - Tenté par - {:else} - Résolu par - {/if} -
    -
    - {#if !$current_exercice.solved} - {#if !$current_exercice.tried} - aucune équipe - {:else} - {$current_exercice.tried} {$current_exercice.tried == 1?"équipe":"équipes"} - {#if $my && $my.exercices[$current_exercice.id] && $my.exercices[$current_exercice.id].total_tries} - (cumulant {$my.exercices[$current_exercice.id].total_tries} {$my.exercices[$current_exercice.id].total_tries == 1?"tentative":"tentatives"}) - {/if} - {/if} - {:else} - {$current_exercice.solved} {$current_exercice.solved == 1?"équipe":"équipes"} - {/if} -
    -
    - -
    - - {#if $my && $my.team_id} - - {#if $settings.acceptNewIssue} - - - Rapporter une anomalie sur ce défi - - {/if} - {#if $settings.QAenabled} - - - Voir les éléments QA sur ce défi - - {/if} - {#if $settings.wip && $settings.canResetProgress} - - {/if} - - {/if} -
    -
    -
    - - {#if $my && $my.exercices[$current_exercice.id]} - - - {#if $my.exercices[$current_exercice.id].files} - - {/if} - {#if $my.exercices[$current_exercice.id].hints} - - {/if} - - - {#if $my.exercices[$current_exercice.id].flags && ($my.exercices[$current_exercice.id].non_found_flags > 0 || !$my.exercices[$current_exercice.id].solved_rank) && !solved[$current_exercice.id]} - {#if $current_theme.locked} - - - - Faire son rapport - - -

    - Ce scénario n'est pas accessible ! -

    -

    - Vous ne pouvez pas compléter son rapport. -

    -
    -
    - {:else} - - {/if} - {/if} - {#if $my.exercices[$current_exercice.id].solved_rank || solved[$current_exercice.id]} - - {/if} - {#if $my.exercices[$current_exercice.id].resolution || $my.exercices[$current_exercice.id].video_uri} - - -
    - - Solution du défi -
    - {#if $my.exercices[$current_exercice.id].resolution} - - - {/if} -
    - {#if $my.exercices[$current_exercice.id].resolution} - - {@html $my.exercices[$current_exercice.id].resolution} - - {/if} - {#if $my.exercices[$current_exercice.id].video_uri} - - {/if} -
    - {/if} - -
    - {/if} -{/if} diff --git a/frontend/fic/src/routes/edit/+page.js b/frontend/fic/src/routes/edit/+page.js deleted file mode 100644 index afe2aa7f..00000000 --- a/frontend/fic/src/routes/edit/+page.js +++ /dev/null @@ -1,5 +0,0 @@ -import { set_current_theme } from '$lib/stores/themes'; - -export function load() { - set_current_theme.set(null); -} diff --git a/frontend/fic/src/routes/edit/+page.svelte b/frontend/fic/src/routes/edit/+page.svelte deleted file mode 100644 index e5689bc8..00000000 --- a/frontend/fic/src/routes/edit/+page.svelte +++ /dev/null @@ -1,61 +0,0 @@ - - - -

    - Votre équipe - {#if $my} - {$my.name} - {/if} -

    - - {#if $my} - - - - {#if !$settings.denyNameChange} - - {/if} - {#if $settings.acceptNewIssue} - - {/if} - - - - - - Détail du score - - - - - - - {:else} - - Vous n'avez pas encore d'équipe ! - Rendez-vous sur la page d'inscription pour plus d'information. - - {/if} -
    diff --git a/frontend/fic/src/routes/issues/+page.js b/frontend/fic/src/routes/issues/+page.js deleted file mode 100644 index caae4fd7..00000000 --- a/frontend/fic/src/routes/issues/+page.js +++ /dev/null @@ -1,14 +0,0 @@ -import { get_store_value } from 'svelte/internal'; - -import { exercices_idx } from '$lib/stores/themes.js'; - -export async function load({ url }) { - const eidx = get_store_value(exercices_idx); - - const exercice = eidx[url.searchParams.get("eid")]?eidx[url.searchParams.get("eid")]:null; - - return { - exercice: exercice, - fillIssue: exercice !== null || url.searchParams.get("fill-issue") !== null, - }; -} diff --git a/frontend/fic/src/routes/issues/+page.svelte b/frontend/fic/src/routes/issues/+page.svelte deleted file mode 100644 index ee33b539..00000000 --- a/frontend/fic/src/routes/issues/+page.svelte +++ /dev/null @@ -1,184 +0,0 @@ - - - -{#if message || sberr} - - {#if !sberr} - Votre rapport a bien été envoyé ! - {:else} - {sberr} - {/if} - {message} - -{/if} - -{#if data.fillIssue} - - - - {#if issue.id} - Répondre à un message - {:else} - Rapporter une anomalie sur un défi - {/if} - - - {#if !$settings.acceptNewIssue} -

    Rapprochez-vous d'un membre de l'équipe afin d'obtenir de l'aide.

    - {:else} - - {/if} -
    -
    -{/if} - - - - - - - - - - - - - - {#each $issues as issue (issue.id)} - - - - - - - - {:else} - - - - {/each} - -
    ObjetÉtat / PrioritéGéré parMessages - {#if !data.fillIssue} - - {/if} -
    - {issue.subject} - {#if issue.exercice} (défi {issue.exercice}){/if} - {issue.state} / {issue.priority}{#if issue.assignee}{issue.assignee}{:else}En attente d'attribution{/if} - {#each issue.texts as text, index} -

    - {#if !text.assignee || text.assignee == '$team'}Vous{:else}{text.assignee}{/if} - le  : - {text.cnt} -

    - {/each} -
    - -
    - Aucune anomalie remontée pour l'instant.
    - Vous souhaitez nous faire remonter un problème ? -
    -
    -
    diff --git a/frontend/fic/src/routes/rank/+page.js b/frontend/fic/src/routes/rank/+page.js deleted file mode 100644 index afe2aa7f..00000000 --- a/frontend/fic/src/routes/rank/+page.js +++ /dev/null @@ -1,5 +0,0 @@ -import { set_current_theme } from '$lib/stores/themes'; - -export function load() { - set_current_theme.set(null); -} diff --git a/frontend/fic/src/routes/rank/+page.svelte b/frontend/fic/src/routes/rank/+page.svelte deleted file mode 100644 index 5b5d3e63..00000000 --- a/frontend/fic/src/routes/rank/+page.svelte +++ /dev/null @@ -1,55 +0,0 @@ - - - -

    - {$challengeInfo.title} - Classement -

    -
    -
    - -
    - - - - - - - - - - {#each $rank as team (team.id)} - {#if team.rank != 0 || ($my && $my.team_id == team.id)} - = 0}> - - - - - {/if} - {/each} - -
    RangÉquipePoints
    {team.rank}{team.name}{Math.round(team.score*100)/100}
    -
    -
    diff --git a/frontend/fic/src/routes/register/+page.js b/frontend/fic/src/routes/register/+page.js deleted file mode 100644 index afe2aa7f..00000000 --- a/frontend/fic/src/routes/register/+page.js +++ /dev/null @@ -1,5 +0,0 @@ -import { set_current_theme } from '$lib/stores/themes'; - -export function load() { - set_current_theme.set(null); -} diff --git a/frontend/fic/src/routes/register/+page.svelte b/frontend/fic/src/routes/register/+page.svelte deleted file mode 100644 index 66f16581..00000000 --- a/frontend/fic/src/routes/register/+page.svelte +++ /dev/null @@ -1,147 +0,0 @@ - - - - - - Félicitations ! vous êtes maintenant authentifié auprès de notre serveur ! - - {#if !$my} - {#if message} - - {message} - - {/if} - {#if !$settings.allowRegistration} - - Oups, il semblerait qu'il y ait eu un problème lors de l'attribution de votre certificat. - Veuillez vous signaler auprès de notre équipe afin de corriger ce problème. - - {:else if registrationInProgress} -
    - Inscription en cours… -
    - {:else} - {#if !$settings.denyTeamCreation && !partJ} - -

    - Votre équipe n'est pas encore enregistrée sur notre serveur. Afin de - pouvoir participer au challenge, nous vous remercions de bien vouloir - remplir le formulaire d'inscription suivant : -

    - -
    - {/if} - {#if $settings.canJoinTeam && !partR} - -

    - {#if !$settings.denyTeamCreation} - Si votre équipe est déjà créée, rejoignez-là ! - {:else} - Vous n'êtes pas encore enregistré·e sur notre serveur. Afin de - pouvoir participer au challenge, nous vous remercions de bien vouloir - rejoindre votre équipe : - {/if} -

    - -
    - {/if} - {/if} - {/if} -
    diff --git a/frontend/fic/src/routes/rules/+page.js b/frontend/fic/src/routes/rules/+page.js deleted file mode 100644 index afe2aa7f..00000000 --- a/frontend/fic/src/routes/rules/+page.js +++ /dev/null @@ -1,5 +0,0 @@ -import { set_current_theme } from '$lib/stores/themes'; - -export function load() { - set_current_theme.set(null); -} diff --git a/frontend/fic/src/routes/rules/+page.svelte b/frontend/fic/src/routes/rules/+page.svelte deleted file mode 100644 index 23224da3..00000000 --- a/frontend/fic/src/routes/rules/+page.svelte +++ /dev/null @@ -1,231 +0,0 @@ - - - -

    - {$challengeInfo.title} - Règles générales -

    - -
    -
    -
    -

    Débloquage des challenges

    -

    - Au début, seul le premier défi de chaque scénario est - accessible. Les défis de niveau supérieur sont débloqués en - validant celui du niveau qui le précéde. -

    - {#if $settings.unlockedStandaloneExercicesByThemeStepValidation > 0 || $settings.unlockedStandaloneExercicesByStandaloneExerciceValidation > 0} -

    - Vous avez également accès à {$settings.unlockedStandaloneExercices} défis indépendants. - D'autres défis sont débloqués - {#if $settings.unlockedStandaloneExercicesByThemeStepValidation > 0}{#if $settings.unlockedStandaloneExercicesByThemeStepValidation < 1} toutes les {1/$settings.unlockedStandaloneExercicesByThemeStepValidation} étape{#if 1/$settings.unlockedStandaloneExercicesByThemeStepValidation > 1}s{/if} de scénario que vous validez{:else}par {$settings.unlockedStandaloneExercicesByThemeStepValidation} défis pour chaque étape de scénario validée{/if}{/if} - {#if $settings.unlockedStandaloneExercicesByStandaloneExerciceValidation > 0}{#if $settings.unlockedStandaloneExercicesByStandaloneExerciceValidation < 1} tous les {1/$settings.unlockedStandaloneExercicesByStandaloneExerciceValidation} défi{#if 1/$settings.unlockedStandaloneExercicesByStandaloneExerciceValidation > 1}s{/if} indépendant que vous validez{:else}par {$settings.unlockedStandaloneExercicesByStandaloneExerciceValidation} exercice indépendant validé{/if}{/if} -

    - {/if} -
    - -

    Le classement

    -

    - Pour figurer dans le classement, il faut avoir réalisé au moins une - action : qu'elle ajoute ou retire des points. -

    -

    - En cas d'égalité au score, les équipes sont départagées selon leur - ordre d'arrivée à ce score. -

    - -
    -

    Calcul des points

    -

    - Pour gagner des points, vous devez résoudre les défis qui vous sont - proposés. Plus le challenge est compliqué, plus il rapporte de points. -

    - - {#if $settings.questionGainRatio != 0} -

    - Même si vous n'arrivez pas à valider un défi, toutes les questions validées augmentent votre score. - {Math.trunc($settings.questionGainRatio * 1000)/10} % des points du défi sont répartis à parts égales entre toutes les questions. -

    -

    - Par exemple, pour un défi de 5 questions valant 20 points, en ayant répondu à 3 questions sur les 5, votre score sera augmenté de :
    - 20 × {Math.trunc($settings.questionGainRatio * 1000)/10} % ÷ 5 × 3 = {Math.trunc(20 * $settings.questionGainRatio / 5 * 3 * 100)/100} points. -

    -

    - Les {Math.trunc(1000 - $settings.questionGainRatio * 1000)/10} % restants sont obtenus à la validation complète du défi. -

    - {/if} - - {#if $settings.submissionCostBase != 0} -

    Coût des tentatives

    -

    - Vous disposez de 10 tentatives pour trouver la/les solutions d'un - challenge. Au delà, chaque tentative vous fait perdre une petite quantité - de points comme suit : -

    - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    Nombre de tentativesCoût par tentative
    0 à 100 point
    11 à 20{Math.round($settings.submissionCostBase * 10) / 10} {$settings.submissionCostBase < 2?"point":"points"}
    21 à 30{Math.round($settings.submissionCostBase * 20) / 10} {$settings.submissionCostBase * 2 < 2?"point":"points"}
    31 à 40{Math.round($settings.submissionCostBase * 30) / 10} {$settings.submissionCostBase * 3 < 2?"point":"points"}
    41 à 50{Math.round($settings.submissionCostBase * 40) / 10} {$settings.submissionCostBase * 4 < 2?"point":"points"}
    ......
    -

    - Par exemple : -

    -
      -
    • À 10 tentatives, vous aurez perdu {$settings.submissionCostBase * 0} {$settings.submissionCostBase * 0 < 2?"point":"points"}.
    • -
    • À 15 tentatives, vous aurez perdu en tout {$settings.submissionCostBase * 5} {$settings.submissionCostBase * 5 < 2?"point":"points"} : {$settings.submissionCostBase} × 5.
    • -
    • 25 tentatives vous coûteront en tout {$settings.submissionCostBase * 20} {$settings.submissionCostBase * 20 < 2?"point":"points"} : {$settings.submissionCostBase} × 10 + {$settings.submissionCostBase} × 2 × 5.
    • -
    • 50 tentatives vous coûteront en tout {$settings.submissionCostBase * 100} {$settings.submissionCostBase * 100 < 2?"point":"points"} : {$settings.submissionCostBase} × 10 + {$settings.submissionCostBase} × 2 × 10 + {$settings.submissionCostBase} × 3 × 10 + {$settings.submissionCostBase} × 4 × 10.
    • -
    - {#if $settings.countOnlyNotGoodTries} -

    - Seules les tentatives sans aucune bonne réponse sont prises en compte dans ce calcul. Lorsque vous complétez un formulaire avec un champ valide et un/des champs invalides, ceci n'est pas pris en compte dans le nombre de tentatives. -

    - {:else} -

    - La dernière tentative (lorsque tous les flags sont bons) est comptabilisée - parmi ce nombre de tentatives. -

    - {/if} - {/if} -
    -
    -
    -
    - {#if $settings.discountedFactor > 0} -

    Décote des gains

    -

    - Une validation d'étape ne vous garantit pas un solde de points fixe. -

    -

    - Selon le nombre d'équipes qui valident un challenge donné, sa cote diminue et vous rapporte alors moins de points. Le gain final est donc indépendant du fait que vous ayez validé l'étape avant une autre équipe : le gain affiché est un gain maximum que vous obtiendriez si aucune autre équipe ne valide cette étape. -

    -

    - Chaque validation réduit de {$settings.discountedFactor*100} % la cote de l'exercice. -

    -

    - Ainsi, pour un exercice d'une valeur initiale de {10*$settings.globalScoreCoefficient} points : -

    - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    Nombre d'équipes validant l'étape
    à la fin de la compétition
    Gain réel
    1{10*$settings.globalScoreCoefficient} points
    2{Math.round(100*$settings.globalScoreCoefficient*(1-$settings.discountedFactor))/10} points
    5{Math.round(100*$settings.globalScoreCoefficient*(1-$settings.discountedFactor*5))/10} points
    10{Math.round(100*$settings.globalScoreCoefficient*(1-$settings.discountedFactor*10))/10} points
    20{Math.round(100*$settings.globalScoreCoefficient*(1-$settings.discountedFactor*20))/10} points
    ......
    -
    - {/if} - -

    Coût des indices

    -

    - Pour vous aider, certains défis vous proposent un ou - plusieurs indices. Ces indices vous font perdre des - points, la valeur de points perdus est indiquée pour chaque indice. -

    -

    - Ces points sont perdus, que vous réussissiez ou non le défi. -

    -

    - Vous pouvez débloquer des indices même si vous ne disposez pas de - suffisamment de points (ou même si vous n'en avez pas encore) ; dans ce - cas, votre score sera négatif. -

    -
    - -

    Bonus

    -

    - Plusieurs bonus peuvent s'appliquer en même temps, dans ce cas, le calcul - du bonus est toujours effectué à partir du nombre de points initiaux du - défi. -

    - - {#if $settings.firstBlood} -

    Prem's

    -

    - Un bonus de +{$settings.firstBlood * 100} % est attribué à la première équipe qui résout un défi. -

    - {/if} - -

    Bonus temporaires

    -

    - Au cours du challenge, afin de booster les équipes ou certains challenges, - un bonus peut-être attribué si une tentative valide est envoyée durant la - période d'activité du bonus. Restez à l'écoute et observez les challenges - portant cette icône :

    -
    -
    -
    -
    diff --git a/frontend/fic/src/routes/tags/+page.js b/frontend/fic/src/routes/tags/+page.js deleted file mode 100644 index afe2aa7f..00000000 --- a/frontend/fic/src/routes/tags/+page.js +++ /dev/null @@ -1,5 +0,0 @@ -import { set_current_theme } from '$lib/stores/themes'; - -export function load() { - set_current_theme.set(null); -} diff --git a/frontend/fic/src/routes/tags/+page.svelte b/frontend/fic/src/routes/tags/+page.svelte deleted file mode 100644 index 90f5b20d..00000000 --- a/frontend/fic/src/routes/tags/+page.svelte +++ /dev/null @@ -1,60 +0,0 @@ - - - - - diff --git a/frontend/fic/src/routes/tags/[tag]/+page.js b/frontend/fic/src/routes/tags/[tag]/+page.js deleted file mode 100644 index b3c0d650..00000000 --- a/frontend/fic/src/routes/tags/[tag]/+page.js +++ /dev/null @@ -1,9 +0,0 @@ -import { set_current_theme } from '$lib/stores/themes'; - -export async function load({ params }) { - set_current_theme.set(null); - - return { - tag: params.tag, - }; -} diff --git a/frontend/fic/src/routes/tags/[tag]/+page.svelte b/frontend/fic/src/routes/tags/[tag]/+page.svelte deleted file mode 100644 index a7f0fd53..00000000 --- a/frontend/fic/src/routes/tags/[tag]/+page.svelte +++ /dev/null @@ -1,65 +0,0 @@ - - - -

    - Challenges {data.tag} -

    - -{#if exercices.length} - - {#key exercices} - {#each exercices as {theme, exercice, index} (index)} - - - - {/each} - {/key} - -{:else} -

    - Il n'y a aucun défi sur ce thème. -

    -{/if} -
    diff --git a/frontend/fic/static/e404.html b/frontend/fic/static/e404.html deleted file mode 100644 index 9cf59734..00000000 --- a/frontend/fic/static/e404.html +++ /dev/null @@ -1,44 +0,0 @@ - - - - - Challenge Forensic - - - - - - - - - - - -
    -
    - -
    -
    - -
    -
    - -
    -
    -

    Page introuvable Erreur 404

    -
    -

    - La page à laquelle vous tentez d'accéder n'existe pas ou l'adresse que vous avez tapée est incorrecte. -

    -

    - Si le problème persiste, contactez un administrateur. -

    -
    -
    - - - diff --git a/frontend/fic/static/e404.json b/frontend/fic/static/e404.json deleted file mode 100644 index e92fea36..00000000 --- a/frontend/fic/static/e404.json +++ /dev/null @@ -1 +0,0 @@ -{"errmsg": "La page à laquelle vous tentez d'accéder n'existe pas ou l'adresse que vous avez tapée est incorrecte."} diff --git a/frontend/fic/static/e413.html b/frontend/fic/static/e413.html deleted file mode 100644 index 330f2c69..00000000 --- a/frontend/fic/static/e413.html +++ /dev/null @@ -1,41 +0,0 @@ - - - - - Challenge Forensic - - - - - - - - - - - -
    -
    - -
    -
    - -
    -
    - -
    -
    -

    Requête trop grosse Erreur 413

    -
    -

    - La quantité de données que vous souhaitez envoyer au serveur est trop importante pour qu'il accepte de la traiter. -

    -
    -
    - - - diff --git a/frontend/fic/static/e413.json b/frontend/fic/static/e413.json deleted file mode 100644 index 194d473c..00000000 --- a/frontend/fic/static/e413.json +++ /dev/null @@ -1 +0,0 @@ -{"errmsg": "La quantité de données que vous souhaitez envoyer au serveur est trop importante pour qu'il accepte de la traiter."} diff --git a/frontend/fic/static/e500.html b/frontend/fic/static/e500.html deleted file mode 100644 index 62e4c887..00000000 --- a/frontend/fic/static/e500.html +++ /dev/null @@ -1,44 +0,0 @@ - - - - - Challenge Forensic - - - - - - - - - - - -
    -
    - -
    -
    - -
    -
    - -
    -
    -

    Erreur interne Erreur 500

    -
    -

    - Notre serveur est actuellement dans l'incapacité de répondre à votre requête.
    Veuillez recommencer dans quelques instants. -

    -

    - Si le problème persiste, contactez un administrateur. -

    -
    -
    - - - diff --git a/frontend/fic/static/e500.json b/frontend/fic/static/e500.json deleted file mode 100644 index 43ee1302..00000000 --- a/frontend/fic/static/e500.json +++ /dev/null @@ -1 +0,0 @@ -{"errmsg": "Notre serveur est actuellement dans l'incapacité de répondre à votre requête. \nVeuillez recommencer dans quelques instants."} diff --git a/frontend/fic/svelte.config.js b/frontend/fic/svelte.config.js deleted file mode 100644 index f83b5ae2..00000000 --- a/frontend/fic/svelte.config.js +++ /dev/null @@ -1,15 +0,0 @@ -/** @type {import('@sveltejs/kit').Config} */ -import adapt from '@sveltejs/adapter-static'; - -const config = { - kit: { - adapter: adapt({ - fallback: 'index.html' - }), - paths: { - relative: false - }, - } -}; - -export default config; diff --git a/frontend/fic/vite.config.js b/frontend/fic/vite.config.js deleted file mode 100644 index af1b952c..00000000 --- a/frontend/fic/vite.config.js +++ /dev/null @@ -1,14 +0,0 @@ -import { sveltekit } from '@sveltejs/kit/vite'; - -/** @type {import('vite').UserConfig} */ -const config = { - server: { - hmr: { - port: 10000 - } - }, - - plugins: [sveltekit()] -}; - -export default config; diff --git a/frontend/main.go b/frontend/main.go new file mode 100644 index 00000000..3a3ee78f --- /dev/null +++ b/frontend/main.go @@ -0,0 +1,78 @@ +package main + +import ( + "bufio" + "flag" + "fmt" + "log" + "net/http" + "os" + "path" + "time" +) + +const startedFile = "started" + +var TeamsDir string +var SubmissionDir string + +func touchStartedFile(startSub time.Duration) { + time.Sleep(startSub) + if fd, err := os.Create(path.Join(TeamsDir, startedFile)); err == nil { + log.Println("Started! Go, Go, Go!!") + fd.Close() + } else { + log.Println("Unable to start challenge:", err) + } +} + +func main() { + var bind = flag.String("bind", "0.0.0.0:8080", "Bind port/socket") + var prefix = flag.String("prefix", "", "Request path prefix to strip (from proxy)") + var start = flag.Int64("start", 0, fmt.Sprintf("Challenge start timestamp (in 2 minutes: %d)", time.Now().Unix()/60*60+120)) + var duration = flag.Duration("duration", 180*time.Minute, "Challenge duration") + var denyChName = flag.Bool("denyChName", false, "Deny team to change their name") + var allowRegistration = flag.Bool("allowRegistration", false, "New team can add itself") + flag.StringVar(&TeamsDir, "teams", "../TEAMS", "Base directory where save teams JSON files") + flag.StringVar(&SubmissionDir, "submission", "./submissions/", "Base directory where save submissions") + flag.Parse() + + log.Prefix("[frontend] ") + + log.Println("Creating submission directory...") + if _, err := os.Stat(SubmissionDir); os.IsNotExist(err) { + if err := os.MkdirAll(SubmissionDir, 0777); err != nil { + log.Fatal("Unable to create submission directory: ", err) + } + } + + startTime := time.Unix(*start, 0) + startSub := startTime.Sub(time.Now()) + end := startTime.Add(*duration).Add(time.Duration(1 * time.Second)) + + log.Println("Challenge ends on", end) + if startSub > 0 { + log.Println("Challenge starts at", startTime, "in", startSub) + + fmt.Printf("PRESS ENTER TO LAUNCH THE COUNTDOWN ") + bufio.NewReader(os.Stdin).ReadLine() + + if _, err := os.Stat(path.Join(TeamsDir, startedFile)); !os.IsNotExist(err) { + os.Remove(path.Join(TeamsDir, startedFile)) + } + + go touchStartedFile(startTime.Sub(time.Now().Add(time.Duration(1 * time.Second)))) + } else { + log.Println("Challenge started at", startTime, "since", -startSub) + go touchStartedFile(time.Duration(0)) + } + + log.Println("Registering handlers...") + http.Handle(fmt.Sprintf("%s/time.json", *prefix), http.StripPrefix(*prefix, TimeHandler{startTime, *duration})) + http.Handle(fmt.Sprintf("%s/", *prefix), http.StripPrefix(*prefix, SubmissionHandler{end, *denyChName, *allowRegistration})) + + log.Println(fmt.Sprintf("Ready, listening on %s", *bind)) + if err := http.ListenAndServe(*bind, nil); err != nil { + log.Fatal("Unable to listen and serve: ", err) + } +} diff --git a/frontend/static/.htaccess b/frontend/static/.htaccess new file mode 100644 index 00000000..1a79758b --- /dev/null +++ b/frontend/static/.htaccess @@ -0,0 +1,6 @@ +RewriteEngine On +RewriteBase // +RewriteRule "^/[0-9]" "index.html" +RewriteRule "^/edit" "index.html" +RewriteRule "^/chbase.sh" "index.html" +RewriteRule "^/rank" "index.html" diff --git a/frontend/static/chbase.sh b/frontend/static/chbase.sh new file mode 100644 index 00000000..160641e5 --- /dev/null +++ b/frontend/static/chbase.sh @@ -0,0 +1,23 @@ +#!/bin/sh + +if [ $# -lt 1 ] +then + echo "Indicate as first parameter the new base. Eg.: $0 /fic/2042" + echo "The path cannot be relative" + echo "If this is not the first time you execute this script, a second argument with the previous base can be given." + exit 1 +fi + +NEW=${1%/} +if [ $# -gt 1 ] +then + OLD=$2 +else + OLD="/" +fi + +sed -i -E "s@(src|href)=\"$OLD@\1=\"$NEW/@" e404.html e413.html e500.html index.html public.html welcome.html views/theme.html +sed -i -E "s@path\":\"$OLD@path\":\"$NEW/@g" my.json +sed -i -E "s@.(get)\(\"$OLD@.\1(\"$NEW/@" js/app.js js/public.js +sed -i -E "s@url: *\"$OLD@url: \"$NEW/@" js/app.js js/public.js +sed -i -E "s@^RewriteBase.*\$@RewriteBase $NEW@" .htaccess diff --git a/frontend/static/css/bootstrap.min.css b/frontend/static/css/bootstrap.min.css new file mode 100644 index 00000000..5eb3a4cf --- /dev/null +++ b/frontend/static/css/bootstrap.min.css @@ -0,0 +1,11 @@ +/*! + * bootswatch v3.3.7 + * Homepage: http://bootswatch.com + * Copyright 2012-2016 Thomas Park + * Licensed under MIT + * Based on Bootstrap +*//*! + * Bootstrap v3.3.7 (http://getbootstrap.com) + * Copyright 2011-2016 Twitter, Inc. + * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) + *//*! normalize.css v3.0.3 | MIT License | github.com/necolas/normalize.css */html{font-family:sans-serif;-ms-text-size-adjust:100%;-webkit-text-size-adjust:100%}body{margin:0}article,aside,details,figcaption,figure,footer,header,hgroup,main,menu,nav,section,summary{display:block}audio,canvas,progress,video{display:inline-block;vertical-align:baseline}audio:not([controls]){display:none;height:0}[hidden],template{display:none}a{background-color:transparent}a:active,a:hover{outline:0}abbr[title]{border-bottom:1px dotted}b,strong{font-weight:bold}dfn{font-style:italic}h1{font-size:2em;margin:0.67em 0}mark{background:#ff0;color:#000}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sup{top:-0.5em}sub{bottom:-0.25em}img{border:0}svg:not(:root){overflow:hidden}figure{margin:1em 40px}hr{-webkit-box-sizing:content-box;-moz-box-sizing:content-box;box-sizing:content-box;height:0}pre{overflow:auto}code,kbd,pre,samp{font-family:monospace, monospace;font-size:1em}button,input,optgroup,select,textarea{color:inherit;font:inherit;margin:0}button{overflow:visible}button,select{text-transform:none}button,html input[type="button"],input[type="reset"],input[type="submit"]{-webkit-appearance:button;cursor:pointer}button[disabled],html input[disabled]{cursor:default}button::-moz-focus-inner,input::-moz-focus-inner{border:0;padding:0}input{line-height:normal}input[type="checkbox"],input[type="radio"]{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;padding:0}input[type="number"]::-webkit-inner-spin-button,input[type="number"]::-webkit-outer-spin-button{height:auto}input[type="search"]{-webkit-appearance:textfield;-webkit-box-sizing:content-box;-moz-box-sizing:content-box;box-sizing:content-box}input[type="search"]::-webkit-search-cancel-button,input[type="search"]::-webkit-search-decoration{-webkit-appearance:none}fieldset{border:1px solid #c0c0c0;margin:0 2px;padding:0.35em 0.625em 0.75em}legend{border:0;padding:0}textarea{overflow:auto}optgroup{font-weight:bold}table{border-collapse:collapse;border-spacing:0}td,th{padding:0}/*! Source: https://github.com/h5bp/html5-boilerplate/blob/master/src/css/main.css */@media print{*,*:before,*:after{background:transparent !important;color:#000 !important;-webkit-box-shadow:none !important;box-shadow:none !important;text-shadow:none !important}a,a:visited{text-decoration:underline}a[href]:after{content:" (" attr(href) ")"}abbr[title]:after{content:" (" attr(title) ")"}a[href^="#"]:after,a[href^="javascript:"]:after{content:""}pre,blockquote{border:1px solid #999;page-break-inside:avoid}thead{display:table-header-group}tr,img{page-break-inside:avoid}img{max-width:100% !important}p,h2,h3{orphans:3;widows:3}h2,h3{page-break-after:avoid}.navbar{display:none}.btn>.caret,.dropup>.btn>.caret{border-top-color:#000 !important}.label{border:1px solid #000}.table{border-collapse:collapse !important}.table td,.table th{background-color:#fff !important}.table-bordered th,.table-bordered td{border:1px solid #ddd !important}}@font-face{font-family:'Glyphicons Halflings';src:url('../fonts/glyphicons-halflings-regular.eot');src:url('../fonts/glyphicons-halflings-regular.eot?#iefix') format('embedded-opentype'),url('../fonts/glyphicons-halflings-regular.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"}*{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}*:before,*:after{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}html{font-size:10px;-webkit-tap-highlight-color:rgba(0,0,0,0)}body{font-family:"Helvetica Neue",Helvetica,Arial,sans-serif;font-size:14px;line-height:1.42857143;color:#c8c8c8;background-color:#272b30}input,button,select,textarea{font-family:inherit;font-size:inherit;line-height:inherit}a{color:#ffffff;text-decoration:none}a:hover,a:focus{color:#ffffff;text-decoration:underline}a:focus{outline:5px auto -webkit-focus-ring-color;outline-offset:-2px}figure{margin:0}img{vertical-align:middle}.img-responsive,.thumbnail>img,.thumbnail a>img,.carousel-inner>.item>img,.carousel-inner>.item>a>img{display:block;max-width:100%;height:auto}.img-rounded{border-radius:6px}.img-thumbnail{padding:4px;line-height:1.42857143;background-color:#1c1e22;border:1px solid #0c0d0e;border-radius:4px;-webkit-transition:all .2s ease-in-out;-o-transition:all .2s ease-in-out;transition:all .2s ease-in-out;display:inline-block;max-width:100%;height:auto}.img-circle{border-radius:50%}hr{margin-top:20px;margin-bottom:20px;border:0;border-top:1px solid #1c1e22}.sr-only{position:absolute;width:1px;height:1px;margin:-1px;padding:0;overflow:hidden;clip:rect(0, 0, 0, 0);border:0}.sr-only-focusable:active,.sr-only-focusable:focus{position:static;width:auto;height:auto;margin:0;overflow:visible;clip:auto}[role="button"]{cursor:pointer}h1,h2,h3,h4,h5,h6,.h1,.h2,.h3,.h4,.h5,.h6{font-family:"Helvetica Neue",Helvetica,Arial,sans-serif;font-weight:500;line-height:1.1;color:inherit}h1 small,h2 small,h3 small,h4 small,h5 small,h6 small,.h1 small,.h2 small,.h3 small,.h4 small,.h5 small,.h6 small,h1 .small,h2 .small,h3 .small,h4 .small,h5 .small,h6 .small,.h1 .small,.h2 .small,.h3 .small,.h4 .small,.h5 .small,.h6 .small{font-weight:normal;line-height:1;color:#7a8288}h1,.h1,h2,.h2,h3,.h3{margin-top:20px;margin-bottom:10px}h1 small,.h1 small,h2 small,.h2 small,h3 small,.h3 small,h1 .small,.h1 .small,h2 .small,.h2 .small,h3 .small,.h3 .small{font-size:65%}h4,.h4,h5,.h5,h6,.h6{margin-top:10px;margin-bottom:10px}h4 small,.h4 small,h5 small,.h5 small,h6 small,.h6 small,h4 .small,.h4 .small,h5 .small,.h5 .small,h6 .small,.h6 .small{font-size:75%}h1,.h1{font-size:36px}h2,.h2{font-size:30px}h3,.h3{font-size:24px}h4,.h4{font-size:18px}h5,.h5{font-size:14px}h6,.h6{font-size:12px}p{margin:0 0 10px}.lead{margin-bottom:20px;font-size:16px;font-weight:300;line-height:1.4}@media (min-width:768px){.lead{font-size:21px}}small,.small{font-size:85%}mark,.mark{background-color:#f89406;padding:.2em}.text-left{text-align:left}.text-right{text-align:right}.text-center{text-align:center}.text-justify{text-align:justify}.text-nowrap{white-space:nowrap}.text-lowercase{text-transform:lowercase}.text-uppercase{text-transform:uppercase}.text-capitalize{text-transform:capitalize}.text-muted{color:#7a8288}.text-primary{color:#7a8288}a.text-primary:hover,a.text-primary:focus{color:#62686d}.text-success{color:#ffffff}a.text-success:hover,a.text-success:focus{color:#e6e6e6}.text-info{color:#ffffff}a.text-info:hover,a.text-info:focus{color:#e6e6e6}.text-warning{color:#ffffff}a.text-warning:hover,a.text-warning:focus{color:#e6e6e6}.text-danger{color:#ffffff}a.text-danger:hover,a.text-danger:focus{color:#e6e6e6}.bg-primary{color:#fff;background-color:#7a8288}a.bg-primary:hover,a.bg-primary:focus{background-color:#62686d}.bg-success{background-color:#62c462}a.bg-success:hover,a.bg-success:focus{background-color:#42b142}.bg-info{background-color:#5bc0de}a.bg-info:hover,a.bg-info:focus{background-color:#31b0d5}.bg-warning{background-color:#f89406}a.bg-warning:hover,a.bg-warning:focus{background-color:#c67605}.bg-danger{background-color:#ee5f5b}a.bg-danger:hover,a.bg-danger:focus{background-color:#e9322d}.page-header{padding-bottom:9px;margin:40px 0 20px;border-bottom:1px solid #1c1e22}ul,ol{margin-top:0;margin-bottom:10px}ul ul,ol ul,ul ol,ol ol{margin-bottom:0}.list-unstyled{padding-left:0;list-style:none}.list-inline{padding-left:0;list-style:none;margin-left:-5px}.list-inline>li{display:inline-block;padding-left:5px;padding-right:5px}dl{margin-top:0;margin-bottom:20px}dt,dd{line-height:1.42857143}dt{font-weight:bold}dd{margin-left:0}@media (min-width:768px){.dl-horizontal dt{float:left;width:160px;clear:left;text-align:right;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.dl-horizontal dd{margin-left:180px}}abbr[title],abbr[data-original-title]{cursor:help;border-bottom:1px dotted #7a8288}.initialism{font-size:90%;text-transform:uppercase}blockquote{padding:10px 20px;margin:0 0 20px;font-size:17.5px;border-left:5px solid #7a8288}blockquote p:last-child,blockquote ul:last-child,blockquote ol:last-child{margin-bottom:0}blockquote footer,blockquote small,blockquote .small{display:block;font-size:80%;line-height:1.42857143;color:#7a8288}blockquote footer:before,blockquote small:before,blockquote .small:before{content:'\2014 \00A0'}.blockquote-reverse,blockquote.pull-right{padding-right:15px;padding-left:0;border-right:5px solid #7a8288;border-left:0;text-align:right}.blockquote-reverse footer:before,blockquote.pull-right footer:before,.blockquote-reverse small:before,blockquote.pull-right small:before,.blockquote-reverse .small:before,blockquote.pull-right .small:before{content:''}.blockquote-reverse footer:after,blockquote.pull-right footer:after,.blockquote-reverse small:after,blockquote.pull-right small:after,.blockquote-reverse .small:after,blockquote.pull-right .small:after{content:'\00A0 \2014'}address{margin-bottom:20px;font-style:normal;line-height:1.42857143}code,kbd,pre,samp{font-family:Menlo,Monaco,Consolas,"Courier New",monospace}code{padding:2px 4px;font-size:90%;color:#c7254e;background-color:#f9f2f4;border-radius:4px}kbd{padding:2px 4px;font-size:90%;color:#ffffff;background-color:#333333;border-radius:3px;-webkit-box-shadow:inset 0 -1px 0 rgba(0,0,0,0.25);box-shadow:inset 0 -1px 0 rgba(0,0,0,0.25)}kbd kbd{padding:0;font-size:100%;font-weight:bold;-webkit-box-shadow:none;box-shadow:none}pre{display:block;padding:9.5px;margin:0 0 10px;font-size:13px;line-height:1.42857143;word-break:break-all;word-wrap:break-word;color:#3a3f44;background-color:#f5f5f5;border:1px solid #cccccc;border-radius:4px}pre code{padding:0;font-size:inherit;color:inherit;white-space:pre-wrap;background-color:transparent;border-radius:0}.pre-scrollable{max-height:340px;overflow-y:scroll}.container{margin-right:auto;margin-left:auto;padding-left:15px;padding-right:15px}@media (min-width:768px){.container{width:750px}}@media (min-width:992px){.container{width:970px}}@media (min-width:1200px){.container{width:1170px}}.container-fluid{margin-right:auto;margin-left:auto;padding-left:15px;padding-right:15px}.row{margin-left:-15px;margin-right:-15px}.col-xs-1,.col-sm-1,.col-md-1,.col-lg-1,.col-xs-2,.col-sm-2,.col-md-2,.col-lg-2,.col-xs-3,.col-sm-3,.col-md-3,.col-lg-3,.col-xs-4,.col-sm-4,.col-md-4,.col-lg-4,.col-xs-5,.col-sm-5,.col-md-5,.col-lg-5,.col-xs-6,.col-sm-6,.col-md-6,.col-lg-6,.col-xs-7,.col-sm-7,.col-md-7,.col-lg-7,.col-xs-8,.col-sm-8,.col-md-8,.col-lg-8,.col-xs-9,.col-sm-9,.col-md-9,.col-lg-9,.col-xs-10,.col-sm-10,.col-md-10,.col-lg-10,.col-xs-11,.col-sm-11,.col-md-11,.col-lg-11,.col-xs-12,.col-sm-12,.col-md-12,.col-lg-12{position:relative;min-height:1px;padding-left:15px;padding-right:15px}.col-xs-1,.col-xs-2,.col-xs-3,.col-xs-4,.col-xs-5,.col-xs-6,.col-xs-7,.col-xs-8,.col-xs-9,.col-xs-10,.col-xs-11,.col-xs-12{float:left}.col-xs-12{width:100%}.col-xs-11{width:91.66666667%}.col-xs-10{width:83.33333333%}.col-xs-9{width:75%}.col-xs-8{width:66.66666667%}.col-xs-7{width:58.33333333%}.col-xs-6{width:50%}.col-xs-5{width:41.66666667%}.col-xs-4{width:33.33333333%}.col-xs-3{width:25%}.col-xs-2{width:16.66666667%}.col-xs-1{width:8.33333333%}.col-xs-pull-12{right:100%}.col-xs-pull-11{right:91.66666667%}.col-xs-pull-10{right:83.33333333%}.col-xs-pull-9{right:75%}.col-xs-pull-8{right:66.66666667%}.col-xs-pull-7{right:58.33333333%}.col-xs-pull-6{right:50%}.col-xs-pull-5{right:41.66666667%}.col-xs-pull-4{right:33.33333333%}.col-xs-pull-3{right:25%}.col-xs-pull-2{right:16.66666667%}.col-xs-pull-1{right:8.33333333%}.col-xs-pull-0{right:auto}.col-xs-push-12{left:100%}.col-xs-push-11{left:91.66666667%}.col-xs-push-10{left:83.33333333%}.col-xs-push-9{left:75%}.col-xs-push-8{left:66.66666667%}.col-xs-push-7{left:58.33333333%}.col-xs-push-6{left:50%}.col-xs-push-5{left:41.66666667%}.col-xs-push-4{left:33.33333333%}.col-xs-push-3{left:25%}.col-xs-push-2{left:16.66666667%}.col-xs-push-1{left:8.33333333%}.col-xs-push-0{left:auto}.col-xs-offset-12{margin-left:100%}.col-xs-offset-11{margin-left:91.66666667%}.col-xs-offset-10{margin-left:83.33333333%}.col-xs-offset-9{margin-left:75%}.col-xs-offset-8{margin-left:66.66666667%}.col-xs-offset-7{margin-left:58.33333333%}.col-xs-offset-6{margin-left:50%}.col-xs-offset-5{margin-left:41.66666667%}.col-xs-offset-4{margin-left:33.33333333%}.col-xs-offset-3{margin-left:25%}.col-xs-offset-2{margin-left:16.66666667%}.col-xs-offset-1{margin-left:8.33333333%}.col-xs-offset-0{margin-left:0%}@media (min-width:768px){.col-sm-1,.col-sm-2,.col-sm-3,.col-sm-4,.col-sm-5,.col-sm-6,.col-sm-7,.col-sm-8,.col-sm-9,.col-sm-10,.col-sm-11,.col-sm-12{float:left}.col-sm-12{width:100%}.col-sm-11{width:91.66666667%}.col-sm-10{width:83.33333333%}.col-sm-9{width:75%}.col-sm-8{width:66.66666667%}.col-sm-7{width:58.33333333%}.col-sm-6{width:50%}.col-sm-5{width:41.66666667%}.col-sm-4{width:33.33333333%}.col-sm-3{width:25%}.col-sm-2{width:16.66666667%}.col-sm-1{width:8.33333333%}.col-sm-pull-12{right:100%}.col-sm-pull-11{right:91.66666667%}.col-sm-pull-10{right:83.33333333%}.col-sm-pull-9{right:75%}.col-sm-pull-8{right:66.66666667%}.col-sm-pull-7{right:58.33333333%}.col-sm-pull-6{right:50%}.col-sm-pull-5{right:41.66666667%}.col-sm-pull-4{right:33.33333333%}.col-sm-pull-3{right:25%}.col-sm-pull-2{right:16.66666667%}.col-sm-pull-1{right:8.33333333%}.col-sm-pull-0{right:auto}.col-sm-push-12{left:100%}.col-sm-push-11{left:91.66666667%}.col-sm-push-10{left:83.33333333%}.col-sm-push-9{left:75%}.col-sm-push-8{left:66.66666667%}.col-sm-push-7{left:58.33333333%}.col-sm-push-6{left:50%}.col-sm-push-5{left:41.66666667%}.col-sm-push-4{left:33.33333333%}.col-sm-push-3{left:25%}.col-sm-push-2{left:16.66666667%}.col-sm-push-1{left:8.33333333%}.col-sm-push-0{left:auto}.col-sm-offset-12{margin-left:100%}.col-sm-offset-11{margin-left:91.66666667%}.col-sm-offset-10{margin-left:83.33333333%}.col-sm-offset-9{margin-left:75%}.col-sm-offset-8{margin-left:66.66666667%}.col-sm-offset-7{margin-left:58.33333333%}.col-sm-offset-6{margin-left:50%}.col-sm-offset-5{margin-left:41.66666667%}.col-sm-offset-4{margin-left:33.33333333%}.col-sm-offset-3{margin-left:25%}.col-sm-offset-2{margin-left:16.66666667%}.col-sm-offset-1{margin-left:8.33333333%}.col-sm-offset-0{margin-left:0%}}@media (min-width:992px){.col-md-1,.col-md-2,.col-md-3,.col-md-4,.col-md-5,.col-md-6,.col-md-7,.col-md-8,.col-md-9,.col-md-10,.col-md-11,.col-md-12{float:left}.col-md-12{width:100%}.col-md-11{width:91.66666667%}.col-md-10{width:83.33333333%}.col-md-9{width:75%}.col-md-8{width:66.66666667%}.col-md-7{width:58.33333333%}.col-md-6{width:50%}.col-md-5{width:41.66666667%}.col-md-4{width:33.33333333%}.col-md-3{width:25%}.col-md-2{width:16.66666667%}.col-md-1{width:8.33333333%}.col-md-pull-12{right:100%}.col-md-pull-11{right:91.66666667%}.col-md-pull-10{right:83.33333333%}.col-md-pull-9{right:75%}.col-md-pull-8{right:66.66666667%}.col-md-pull-7{right:58.33333333%}.col-md-pull-6{right:50%}.col-md-pull-5{right:41.66666667%}.col-md-pull-4{right:33.33333333%}.col-md-pull-3{right:25%}.col-md-pull-2{right:16.66666667%}.col-md-pull-1{right:8.33333333%}.col-md-pull-0{right:auto}.col-md-push-12{left:100%}.col-md-push-11{left:91.66666667%}.col-md-push-10{left:83.33333333%}.col-md-push-9{left:75%}.col-md-push-8{left:66.66666667%}.col-md-push-7{left:58.33333333%}.col-md-push-6{left:50%}.col-md-push-5{left:41.66666667%}.col-md-push-4{left:33.33333333%}.col-md-push-3{left:25%}.col-md-push-2{left:16.66666667%}.col-md-push-1{left:8.33333333%}.col-md-push-0{left:auto}.col-md-offset-12{margin-left:100%}.col-md-offset-11{margin-left:91.66666667%}.col-md-offset-10{margin-left:83.33333333%}.col-md-offset-9{margin-left:75%}.col-md-offset-8{margin-left:66.66666667%}.col-md-offset-7{margin-left:58.33333333%}.col-md-offset-6{margin-left:50%}.col-md-offset-5{margin-left:41.66666667%}.col-md-offset-4{margin-left:33.33333333%}.col-md-offset-3{margin-left:25%}.col-md-offset-2{margin-left:16.66666667%}.col-md-offset-1{margin-left:8.33333333%}.col-md-offset-0{margin-left:0%}}@media (min-width:1200px){.col-lg-1,.col-lg-2,.col-lg-3,.col-lg-4,.col-lg-5,.col-lg-6,.col-lg-7,.col-lg-8,.col-lg-9,.col-lg-10,.col-lg-11,.col-lg-12{float:left}.col-lg-12{width:100%}.col-lg-11{width:91.66666667%}.col-lg-10{width:83.33333333%}.col-lg-9{width:75%}.col-lg-8{width:66.66666667%}.col-lg-7{width:58.33333333%}.col-lg-6{width:50%}.col-lg-5{width:41.66666667%}.col-lg-4{width:33.33333333%}.col-lg-3{width:25%}.col-lg-2{width:16.66666667%}.col-lg-1{width:8.33333333%}.col-lg-pull-12{right:100%}.col-lg-pull-11{right:91.66666667%}.col-lg-pull-10{right:83.33333333%}.col-lg-pull-9{right:75%}.col-lg-pull-8{right:66.66666667%}.col-lg-pull-7{right:58.33333333%}.col-lg-pull-6{right:50%}.col-lg-pull-5{right:41.66666667%}.col-lg-pull-4{right:33.33333333%}.col-lg-pull-3{right:25%}.col-lg-pull-2{right:16.66666667%}.col-lg-pull-1{right:8.33333333%}.col-lg-pull-0{right:auto}.col-lg-push-12{left:100%}.col-lg-push-11{left:91.66666667%}.col-lg-push-10{left:83.33333333%}.col-lg-push-9{left:75%}.col-lg-push-8{left:66.66666667%}.col-lg-push-7{left:58.33333333%}.col-lg-push-6{left:50%}.col-lg-push-5{left:41.66666667%}.col-lg-push-4{left:33.33333333%}.col-lg-push-3{left:25%}.col-lg-push-2{left:16.66666667%}.col-lg-push-1{left:8.33333333%}.col-lg-push-0{left:auto}.col-lg-offset-12{margin-left:100%}.col-lg-offset-11{margin-left:91.66666667%}.col-lg-offset-10{margin-left:83.33333333%}.col-lg-offset-9{margin-left:75%}.col-lg-offset-8{margin-left:66.66666667%}.col-lg-offset-7{margin-left:58.33333333%}.col-lg-offset-6{margin-left:50%}.col-lg-offset-5{margin-left:41.66666667%}.col-lg-offset-4{margin-left:33.33333333%}.col-lg-offset-3{margin-left:25%}.col-lg-offset-2{margin-left:16.66666667%}.col-lg-offset-1{margin-left:8.33333333%}.col-lg-offset-0{margin-left:0%}}table{background-color:#2e3338}caption{padding-top:8px;padding-bottom:8px;color:#7a8288;text-align:left}th{text-align:left}.table{width:100%;max-width:100%;margin-bottom:20px}.table>thead>tr>th,.table>tbody>tr>th,.table>tfoot>tr>th,.table>thead>tr>td,.table>tbody>tr>td,.table>tfoot>tr>td{padding:8px;line-height:1.42857143;vertical-align:top;border-top:1px solid #1c1e22}.table>thead>tr>th{vertical-align:bottom;border-bottom:2px solid #1c1e22}.table>caption+thead>tr:first-child>th,.table>colgroup+thead>tr:first-child>th,.table>thead:first-child>tr:first-child>th,.table>caption+thead>tr:first-child>td,.table>colgroup+thead>tr:first-child>td,.table>thead:first-child>tr:first-child>td{border-top:0}.table>tbody+tbody{border-top:2px solid #1c1e22}.table .table{background-color:#272b30}.table-condensed>thead>tr>th,.table-condensed>tbody>tr>th,.table-condensed>tfoot>tr>th,.table-condensed>thead>tr>td,.table-condensed>tbody>tr>td,.table-condensed>tfoot>tr>td{padding:5px}.table-bordered{border:1px solid #1c1e22}.table-bordered>thead>tr>th,.table-bordered>tbody>tr>th,.table-bordered>tfoot>tr>th,.table-bordered>thead>tr>td,.table-bordered>tbody>tr>td,.table-bordered>tfoot>tr>td{border:1px solid #1c1e22}.table-bordered>thead>tr>th,.table-bordered>thead>tr>td{border-bottom-width:2px}.table-striped>tbody>tr:nth-of-type(odd){background-color:#353a41}.table-hover>tbody>tr:hover{background-color:#49515a}table col[class*="col-"]{position:static;float:none;display:table-column}table td[class*="col-"],table th[class*="col-"]{position:static;float:none;display:table-cell}.table>thead>tr>td.active,.table>tbody>tr>td.active,.table>tfoot>tr>td.active,.table>thead>tr>th.active,.table>tbody>tr>th.active,.table>tfoot>tr>th.active,.table>thead>tr.active>td,.table>tbody>tr.active>td,.table>tfoot>tr.active>td,.table>thead>tr.active>th,.table>tbody>tr.active>th,.table>tfoot>tr.active>th{background-color:#49515a}.table-hover>tbody>tr>td.active:hover,.table-hover>tbody>tr>th.active:hover,.table-hover>tbody>tr.active:hover>td,.table-hover>tbody>tr:hover>.active,.table-hover>tbody>tr.active:hover>th{background-color:#3e444c}.table>thead>tr>td.success,.table>tbody>tr>td.success,.table>tfoot>tr>td.success,.table>thead>tr>th.success,.table>tbody>tr>th.success,.table>tfoot>tr>th.success,.table>thead>tr.success>td,.table>tbody>tr.success>td,.table>tfoot>tr.success>td,.table>thead>tr.success>th,.table>tbody>tr.success>th,.table>tfoot>tr.success>th{background-color:#62c462}.table-hover>tbody>tr>td.success:hover,.table-hover>tbody>tr>th.success:hover,.table-hover>tbody>tr.success:hover>td,.table-hover>tbody>tr:hover>.success,.table-hover>tbody>tr.success:hover>th{background-color:#4fbd4f}.table>thead>tr>td.info,.table>tbody>tr>td.info,.table>tfoot>tr>td.info,.table>thead>tr>th.info,.table>tbody>tr>th.info,.table>tfoot>tr>th.info,.table>thead>tr.info>td,.table>tbody>tr.info>td,.table>tfoot>tr.info>td,.table>thead>tr.info>th,.table>tbody>tr.info>th,.table>tfoot>tr.info>th{background-color:#5bc0de}.table-hover>tbody>tr>td.info:hover,.table-hover>tbody>tr>th.info:hover,.table-hover>tbody>tr.info:hover>td,.table-hover>tbody>tr:hover>.info,.table-hover>tbody>tr.info:hover>th{background-color:#46b8da}.table>thead>tr>td.warning,.table>tbody>tr>td.warning,.table>tfoot>tr>td.warning,.table>thead>tr>th.warning,.table>tbody>tr>th.warning,.table>tfoot>tr>th.warning,.table>thead>tr.warning>td,.table>tbody>tr.warning>td,.table>tfoot>tr.warning>td,.table>thead>tr.warning>th,.table>tbody>tr.warning>th,.table>tfoot>tr.warning>th{background-color:#f89406}.table-hover>tbody>tr>td.warning:hover,.table-hover>tbody>tr>th.warning:hover,.table-hover>tbody>tr.warning:hover>td,.table-hover>tbody>tr:hover>.warning,.table-hover>tbody>tr.warning:hover>th{background-color:#df8505}.table>thead>tr>td.danger,.table>tbody>tr>td.danger,.table>tfoot>tr>td.danger,.table>thead>tr>th.danger,.table>tbody>tr>th.danger,.table>tfoot>tr>th.danger,.table>thead>tr.danger>td,.table>tbody>tr.danger>td,.table>tfoot>tr.danger>td,.table>thead>tr.danger>th,.table>tbody>tr.danger>th,.table>tfoot>tr.danger>th{background-color:#ee5f5b}.table-hover>tbody>tr>td.danger:hover,.table-hover>tbody>tr>th.danger:hover,.table-hover>tbody>tr.danger:hover>td,.table-hover>tbody>tr:hover>.danger,.table-hover>tbody>tr.danger:hover>th{background-color:#ec4844}.table-responsive{overflow-x:auto;min-height:0.01%}@media screen and (max-width:767px){.table-responsive{width:100%;margin-bottom:15px;overflow-y:hidden;-ms-overflow-style:-ms-autohiding-scrollbar;border:1px solid #1c1e22}.table-responsive>.table{margin-bottom:0}.table-responsive>.table>thead>tr>th,.table-responsive>.table>tbody>tr>th,.table-responsive>.table>tfoot>tr>th,.table-responsive>.table>thead>tr>td,.table-responsive>.table>tbody>tr>td,.table-responsive>.table>tfoot>tr>td{white-space:nowrap}.table-responsive>.table-bordered{border:0}.table-responsive>.table-bordered>thead>tr>th:first-child,.table-responsive>.table-bordered>tbody>tr>th:first-child,.table-responsive>.table-bordered>tfoot>tr>th:first-child,.table-responsive>.table-bordered>thead>tr>td:first-child,.table-responsive>.table-bordered>tbody>tr>td:first-child,.table-responsive>.table-bordered>tfoot>tr>td:first-child{border-left:0}.table-responsive>.table-bordered>thead>tr>th:last-child,.table-responsive>.table-bordered>tbody>tr>th:last-child,.table-responsive>.table-bordered>tfoot>tr>th:last-child,.table-responsive>.table-bordered>thead>tr>td:last-child,.table-responsive>.table-bordered>tbody>tr>td:last-child,.table-responsive>.table-bordered>tfoot>tr>td:last-child{border-right:0}.table-responsive>.table-bordered>tbody>tr:last-child>th,.table-responsive>.table-bordered>tfoot>tr:last-child>th,.table-responsive>.table-bordered>tbody>tr:last-child>td,.table-responsive>.table-bordered>tfoot>tr:last-child>td{border-bottom:0}}fieldset{padding:0;margin:0;border:0;min-width:0}legend{display:block;width:100%;padding:0;margin-bottom:20px;font-size:21px;line-height:inherit;color:#c8c8c8;border:0;border-bottom:1px solid #1c1e22}label{display:inline-block;max-width:100%;margin-bottom:5px;font-weight:bold}input[type="search"]{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}input[type="radio"],input[type="checkbox"]{margin:4px 0 0;margin-top:1px \9;line-height:normal}input[type="file"]{display:block}input[type="range"]{display:block;width:100%}select[multiple],select[size]{height:auto}input[type="file"]:focus,input[type="radio"]:focus,input[type="checkbox"]:focus{outline:5px auto -webkit-focus-ring-color;outline-offset:-2px}output{display:block;padding-top:9px;font-size:14px;line-height:1.42857143;color:#272b30}.form-control{display:block;width:100%;height:38px;padding:8px 12px;font-size:14px;line-height:1.42857143;color:#272b30;background-color:#ffffff;background-image:none;border:1px solid #000000;border-radius:4px;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075);box-shadow:inset 0 1px 1px rgba(0,0,0,0.075);-webkit-transition:border-color ease-in-out .15s,-webkit-box-shadow ease-in-out .15s;-o-transition:border-color ease-in-out .15s,box-shadow ease-in-out .15s;transition:border-color ease-in-out .15s,box-shadow ease-in-out .15s}.form-control:focus{border-color:#66afe9;outline:0;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 8px rgba(102,175,233,0.6);box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 8px rgba(102,175,233,0.6)}.form-control::-moz-placeholder{color:#7a8288;opacity:1}.form-control:-ms-input-placeholder{color:#7a8288}.form-control::-webkit-input-placeholder{color:#7a8288}.form-control::-ms-expand{border:0;background-color:transparent}.form-control[disabled],.form-control[readonly],fieldset[disabled] .form-control{background-color:#999999;opacity:1}.form-control[disabled],fieldset[disabled] .form-control{cursor:not-allowed}textarea.form-control{height:auto}input[type="search"]{-webkit-appearance:none}@media screen and (-webkit-min-device-pixel-ratio:0){input[type="date"].form-control,input[type="time"].form-control,input[type="datetime-local"].form-control,input[type="month"].form-control{line-height:38px}input[type="date"].input-sm,input[type="time"].input-sm,input[type="datetime-local"].input-sm,input[type="month"].input-sm,.input-group-sm input[type="date"],.input-group-sm input[type="time"],.input-group-sm input[type="datetime-local"],.input-group-sm input[type="month"]{line-height:30px}input[type="date"].input-lg,input[type="time"].input-lg,input[type="datetime-local"].input-lg,input[type="month"].input-lg,.input-group-lg input[type="date"],.input-group-lg input[type="time"],.input-group-lg input[type="datetime-local"],.input-group-lg input[type="month"]{line-height:54px}}.form-group{margin-bottom:15px}.radio,.checkbox{position:relative;display:block;margin-top:10px;margin-bottom:10px}.radio label,.checkbox label{min-height:20px;padding-left:20px;margin-bottom:0;font-weight:normal;cursor:pointer}.radio input[type="radio"],.radio-inline input[type="radio"],.checkbox input[type="checkbox"],.checkbox-inline input[type="checkbox"]{position:absolute;margin-left:-20px;margin-top:4px \9}.radio+.radio,.checkbox+.checkbox{margin-top:-5px}.radio-inline,.checkbox-inline{position:relative;display:inline-block;padding-left:20px;margin-bottom:0;vertical-align:middle;font-weight:normal;cursor:pointer}.radio-inline+.radio-inline,.checkbox-inline+.checkbox-inline{margin-top:0;margin-left:10px}input[type="radio"][disabled],input[type="checkbox"][disabled],input[type="radio"].disabled,input[type="checkbox"].disabled,fieldset[disabled] input[type="radio"],fieldset[disabled] input[type="checkbox"]{cursor:not-allowed}.radio-inline.disabled,.checkbox-inline.disabled,fieldset[disabled] .radio-inline,fieldset[disabled] .checkbox-inline{cursor:not-allowed}.radio.disabled label,.checkbox.disabled label,fieldset[disabled] .radio label,fieldset[disabled] .checkbox label{cursor:not-allowed}.form-control-static{padding-top:9px;padding-bottom:9px;margin-bottom:0;min-height:34px}.form-control-static.input-lg,.form-control-static.input-sm{padding-left:0;padding-right:0}.input-sm{height:30px;padding:5px 10px;font-size:12px;line-height:1.5;border-radius:3px}select.input-sm{height:30px;line-height:30px}textarea.input-sm,select[multiple].input-sm{height:auto}.form-group-sm .form-control{height:30px;padding:5px 10px;font-size:12px;line-height:1.5;border-radius:3px}.form-group-sm select.form-control{height:30px;line-height:30px}.form-group-sm textarea.form-control,.form-group-sm select[multiple].form-control{height:auto}.form-group-sm .form-control-static{height:30px;min-height:32px;padding:6px 10px;font-size:12px;line-height:1.5}.input-lg{height:54px;padding:14px 16px;font-size:18px;line-height:1.3333333;border-radius:6px}select.input-lg{height:54px;line-height:54px}textarea.input-lg,select[multiple].input-lg{height:auto}.form-group-lg .form-control{height:54px;padding:14px 16px;font-size:18px;line-height:1.3333333;border-radius:6px}.form-group-lg select.form-control{height:54px;line-height:54px}.form-group-lg textarea.form-control,.form-group-lg select[multiple].form-control{height:auto}.form-group-lg .form-control-static{height:54px;min-height:38px;padding:15px 16px;font-size:18px;line-height:1.3333333}.has-feedback{position:relative}.has-feedback .form-control{padding-right:47.5px}.form-control-feedback{position:absolute;top:0;right:0;z-index:2;display:block;width:38px;height:38px;line-height:38px;text-align:center;pointer-events:none}.input-lg+.form-control-feedback,.input-group-lg+.form-control-feedback,.form-group-lg .form-control+.form-control-feedback{width:54px;height:54px;line-height:54px}.input-sm+.form-control-feedback,.input-group-sm+.form-control-feedback,.form-group-sm .form-control+.form-control-feedback{width:30px;height:30px;line-height:30px}.has-success .help-block,.has-success .control-label,.has-success .radio,.has-success .checkbox,.has-success .radio-inline,.has-success .checkbox-inline,.has-success.radio label,.has-success.checkbox label,.has-success.radio-inline label,.has-success.checkbox-inline label{color:#ffffff}.has-success .form-control{border-color:#ffffff;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075);box-shadow:inset 0 1px 1px rgba(0,0,0,0.075)}.has-success .form-control:focus{border-color:#e6e6e6;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #fff;box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #fff}.has-success .input-group-addon{color:#ffffff;border-color:#ffffff;background-color:#62c462}.has-success .form-control-feedback{color:#ffffff}.has-warning .help-block,.has-warning .control-label,.has-warning .radio,.has-warning .checkbox,.has-warning .radio-inline,.has-warning .checkbox-inline,.has-warning.radio label,.has-warning.checkbox label,.has-warning.radio-inline label,.has-warning.checkbox-inline label{color:#ffffff}.has-warning .form-control{border-color:#ffffff;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075);box-shadow:inset 0 1px 1px rgba(0,0,0,0.075)}.has-warning .form-control:focus{border-color:#e6e6e6;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #fff;box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #fff}.has-warning .input-group-addon{color:#ffffff;border-color:#ffffff;background-color:#f89406}.has-warning .form-control-feedback{color:#ffffff}.has-error .help-block,.has-error .control-label,.has-error .radio,.has-error .checkbox,.has-error .radio-inline,.has-error .checkbox-inline,.has-error.radio label,.has-error.checkbox label,.has-error.radio-inline label,.has-error.checkbox-inline label{color:#ffffff}.has-error .form-control{border-color:#ffffff;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075);box-shadow:inset 0 1px 1px rgba(0,0,0,0.075)}.has-error .form-control:focus{border-color:#e6e6e6;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #fff;box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #fff}.has-error .input-group-addon{color:#ffffff;border-color:#ffffff;background-color:#ee5f5b}.has-error .form-control-feedback{color:#ffffff}.has-feedback label~.form-control-feedback{top:25px}.has-feedback label.sr-only~.form-control-feedback{top:0}.help-block{display:block;margin-top:5px;margin-bottom:10px;color:#ffffff}@media (min-width:768px){.form-inline .form-group{display:inline-block;margin-bottom:0;vertical-align:middle}.form-inline .form-control{display:inline-block;width:auto;vertical-align:middle}.form-inline .form-control-static{display:inline-block}.form-inline .input-group{display:inline-table;vertical-align:middle}.form-inline .input-group .input-group-addon,.form-inline .input-group .input-group-btn,.form-inline .input-group .form-control{width:auto}.form-inline .input-group>.form-control{width:100%}.form-inline .control-label{margin-bottom:0;vertical-align:middle}.form-inline .radio,.form-inline .checkbox{display:inline-block;margin-top:0;margin-bottom:0;vertical-align:middle}.form-inline .radio label,.form-inline .checkbox label{padding-left:0}.form-inline .radio input[type="radio"],.form-inline .checkbox input[type="checkbox"]{position:relative;margin-left:0}.form-inline .has-feedback .form-control-feedback{top:0}}.form-horizontal .radio,.form-horizontal .checkbox,.form-horizontal .radio-inline,.form-horizontal .checkbox-inline{margin-top:0;margin-bottom:0;padding-top:9px}.form-horizontal .radio,.form-horizontal .checkbox{min-height:29px}.form-horizontal .form-group{margin-left:-15px;margin-right:-15px}@media (min-width:768px){.form-horizontal .control-label{text-align:right;margin-bottom:0;padding-top:9px}}.form-horizontal .has-feedback .form-control-feedback{right:15px}@media (min-width:768px){.form-horizontal .form-group-lg .control-label{padding-top:15px;font-size:18px}}@media (min-width:768px){.form-horizontal .form-group-sm .control-label{padding-top:6px;font-size:12px}}.btn{display:inline-block;margin-bottom:0;font-weight:normal;text-align:center;vertical-align:middle;-ms-touch-action:manipulation;touch-action:manipulation;cursor:pointer;background-image:none;border:1px solid transparent;white-space:nowrap;padding:8px 12px;font-size:14px;line-height:1.42857143;border-radius:4px;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.btn:focus,.btn:active:focus,.btn.active:focus,.btn.focus,.btn:active.focus,.btn.active.focus{outline:5px auto -webkit-focus-ring-color;outline-offset:-2px}.btn:hover,.btn:focus,.btn.focus{color:#ffffff;text-decoration:none}.btn:active,.btn.active{outline:0;background-image:none;-webkit-box-shadow:inset 0 3px 5px rgba(0,0,0,0.125);box-shadow:inset 0 3px 5px rgba(0,0,0,0.125)}.btn.disabled,.btn[disabled],fieldset[disabled] .btn{cursor:not-allowed;opacity:0.65;filter:alpha(opacity=65);-webkit-box-shadow:none;box-shadow:none}a.btn.disabled,fieldset[disabled] a.btn{pointer-events:none}.btn-default{color:#ffffff;background-color:#3a3f44;border-color:#3a3f44}.btn-default:focus,.btn-default.focus{color:#ffffff;background-color:#232628;border-color:#000000}.btn-default:hover{color:#ffffff;background-color:#232628;border-color:#1e2023}.btn-default:active,.btn-default.active,.open>.dropdown-toggle.btn-default{color:#ffffff;background-color:#232628;border-color:#1e2023}.btn-default:active:hover,.btn-default.active:hover,.open>.dropdown-toggle.btn-default:hover,.btn-default:active:focus,.btn-default.active:focus,.open>.dropdown-toggle.btn-default:focus,.btn-default:active.focus,.btn-default.active.focus,.open>.dropdown-toggle.btn-default.focus{color:#ffffff;background-color:#121415;border-color:#000000}.btn-default:active,.btn-default.active,.open>.dropdown-toggle.btn-default{background-image:none}.btn-default.disabled:hover,.btn-default[disabled]:hover,fieldset[disabled] .btn-default:hover,.btn-default.disabled:focus,.btn-default[disabled]:focus,fieldset[disabled] .btn-default:focus,.btn-default.disabled.focus,.btn-default[disabled].focus,fieldset[disabled] .btn-default.focus{background-color:#3a3f44;border-color:#3a3f44}.btn-default .badge{color:#3a3f44;background-color:#ffffff}.btn-primary{color:#ffffff;background-color:#7a8288;border-color:#7a8288}.btn-primary:focus,.btn-primary.focus{color:#ffffff;background-color:#62686d;border-color:#3e4245}.btn-primary:hover{color:#ffffff;background-color:#62686d;border-color:#5d6368}.btn-primary:active,.btn-primary.active,.open>.dropdown-toggle.btn-primary{color:#ffffff;background-color:#62686d;border-color:#5d6368}.btn-primary:active:hover,.btn-primary.active:hover,.open>.dropdown-toggle.btn-primary:hover,.btn-primary:active:focus,.btn-primary.active:focus,.open>.dropdown-toggle.btn-primary:focus,.btn-primary:active.focus,.btn-primary.active.focus,.open>.dropdown-toggle.btn-primary.focus{color:#ffffff;background-color:#51565a;border-color:#3e4245}.btn-primary:active,.btn-primary.active,.open>.dropdown-toggle.btn-primary{background-image:none}.btn-primary.disabled:hover,.btn-primary[disabled]:hover,fieldset[disabled] .btn-primary:hover,.btn-primary.disabled:focus,.btn-primary[disabled]:focus,fieldset[disabled] .btn-primary:focus,.btn-primary.disabled.focus,.btn-primary[disabled].focus,fieldset[disabled] .btn-primary.focus{background-color:#7a8288;border-color:#7a8288}.btn-primary .badge{color:#7a8288;background-color:#ffffff}.btn-success{color:#ffffff;background-color:#62c462;border-color:#62c462}.btn-success:focus,.btn-success.focus{color:#ffffff;background-color:#42b142;border-color:#2d792d}.btn-success:hover{color:#ffffff;background-color:#42b142;border-color:#40a940}.btn-success:active,.btn-success.active,.open>.dropdown-toggle.btn-success{color:#ffffff;background-color:#42b142;border-color:#40a940}.btn-success:active:hover,.btn-success.active:hover,.open>.dropdown-toggle.btn-success:hover,.btn-success:active:focus,.btn-success.active:focus,.open>.dropdown-toggle.btn-success:focus,.btn-success:active.focus,.btn-success.active.focus,.open>.dropdown-toggle.btn-success.focus{color:#ffffff;background-color:#399739;border-color:#2d792d}.btn-success:active,.btn-success.active,.open>.dropdown-toggle.btn-success{background-image:none}.btn-success.disabled:hover,.btn-success[disabled]:hover,fieldset[disabled] .btn-success:hover,.btn-success.disabled:focus,.btn-success[disabled]:focus,fieldset[disabled] .btn-success:focus,.btn-success.disabled.focus,.btn-success[disabled].focus,fieldset[disabled] .btn-success.focus{background-color:#62c462;border-color:#62c462}.btn-success .badge{color:#62c462;background-color:#ffffff}.btn-info{color:#ffffff;background-color:#5bc0de;border-color:#5bc0de}.btn-info:focus,.btn-info.focus{color:#ffffff;background-color:#31b0d5;border-color:#1f7e9a}.btn-info:hover{color:#ffffff;background-color:#31b0d5;border-color:#2aabd2}.btn-info:active,.btn-info.active,.open>.dropdown-toggle.btn-info{color:#ffffff;background-color:#31b0d5;border-color:#2aabd2}.btn-info:active:hover,.btn-info.active:hover,.open>.dropdown-toggle.btn-info:hover,.btn-info:active:focus,.btn-info.active:focus,.open>.dropdown-toggle.btn-info:focus,.btn-info:active.focus,.btn-info.active.focus,.open>.dropdown-toggle.btn-info.focus{color:#ffffff;background-color:#269abc;border-color:#1f7e9a}.btn-info:active,.btn-info.active,.open>.dropdown-toggle.btn-info{background-image:none}.btn-info.disabled:hover,.btn-info[disabled]:hover,fieldset[disabled] .btn-info:hover,.btn-info.disabled:focus,.btn-info[disabled]:focus,fieldset[disabled] .btn-info:focus,.btn-info.disabled.focus,.btn-info[disabled].focus,fieldset[disabled] .btn-info.focus{background-color:#5bc0de;border-color:#5bc0de}.btn-info .badge{color:#5bc0de;background-color:#ffffff}.btn-warning{color:#ffffff;background-color:#f89406;border-color:#f89406}.btn-warning:focus,.btn-warning.focus{color:#ffffff;background-color:#c67605;border-color:#7c4a03}.btn-warning:hover{color:#ffffff;background-color:#c67605;border-color:#bc7005}.btn-warning:active,.btn-warning.active,.open>.dropdown-toggle.btn-warning{color:#ffffff;background-color:#c67605;border-color:#bc7005}.btn-warning:active:hover,.btn-warning.active:hover,.open>.dropdown-toggle.btn-warning:hover,.btn-warning:active:focus,.btn-warning.active:focus,.open>.dropdown-toggle.btn-warning:focus,.btn-warning:active.focus,.btn-warning.active.focus,.open>.dropdown-toggle.btn-warning.focus{color:#ffffff;background-color:#a36104;border-color:#7c4a03}.btn-warning:active,.btn-warning.active,.open>.dropdown-toggle.btn-warning{background-image:none}.btn-warning.disabled:hover,.btn-warning[disabled]:hover,fieldset[disabled] .btn-warning:hover,.btn-warning.disabled:focus,.btn-warning[disabled]:focus,fieldset[disabled] .btn-warning:focus,.btn-warning.disabled.focus,.btn-warning[disabled].focus,fieldset[disabled] .btn-warning.focus{background-color:#f89406;border-color:#f89406}.btn-warning .badge{color:#f89406;background-color:#ffffff}.btn-danger{color:#ffffff;background-color:#ee5f5b;border-color:#ee5f5b}.btn-danger:focus,.btn-danger.focus{color:#ffffff;background-color:#e9322d;border-color:#b71713}.btn-danger:hover{color:#ffffff;background-color:#e9322d;border-color:#e82924}.btn-danger:active,.btn-danger.active,.open>.dropdown-toggle.btn-danger{color:#ffffff;background-color:#e9322d;border-color:#e82924}.btn-danger:active:hover,.btn-danger.active:hover,.open>.dropdown-toggle.btn-danger:hover,.btn-danger:active:focus,.btn-danger.active:focus,.open>.dropdown-toggle.btn-danger:focus,.btn-danger:active.focus,.btn-danger.active.focus,.open>.dropdown-toggle.btn-danger.focus{color:#ffffff;background-color:#dc1c17;border-color:#b71713}.btn-danger:active,.btn-danger.active,.open>.dropdown-toggle.btn-danger{background-image:none}.btn-danger.disabled:hover,.btn-danger[disabled]:hover,fieldset[disabled] .btn-danger:hover,.btn-danger.disabled:focus,.btn-danger[disabled]:focus,fieldset[disabled] .btn-danger:focus,.btn-danger.disabled.focus,.btn-danger[disabled].focus,fieldset[disabled] .btn-danger.focus{background-color:#ee5f5b;border-color:#ee5f5b}.btn-danger .badge{color:#ee5f5b;background-color:#ffffff}.btn-link{color:#ffffff;font-weight:normal;border-radius:0}.btn-link,.btn-link:active,.btn-link.active,.btn-link[disabled],fieldset[disabled] .btn-link{background-color:transparent;-webkit-box-shadow:none;box-shadow:none}.btn-link,.btn-link:hover,.btn-link:focus,.btn-link:active{border-color:transparent}.btn-link:hover,.btn-link:focus{color:#ffffff;text-decoration:underline;background-color:transparent}.btn-link[disabled]:hover,fieldset[disabled] .btn-link:hover,.btn-link[disabled]:focus,fieldset[disabled] .btn-link:focus{color:#7a8288;text-decoration:none}.btn-lg,.btn-group-lg>.btn{padding:14px 16px;font-size:18px;line-height:1.3333333;border-radius:6px}.btn-sm,.btn-group-sm>.btn{padding:5px 10px;font-size:12px;line-height:1.5;border-radius:3px}.btn-xs,.btn-group-xs>.btn{padding:1px 5px;font-size:12px;line-height:1.5;border-radius:3px}.btn-block{display:block;width:100%}.btn-block+.btn-block{margin-top:5px}input[type="submit"].btn-block,input[type="reset"].btn-block,input[type="button"].btn-block{width:100%}.fade{opacity:0;-webkit-transition:opacity 0.15s linear;-o-transition:opacity 0.15s linear;transition:opacity 0.15s linear}.fade.in{opacity:1}.collapse{display:none}.collapse.in{display:block}tr.collapse.in{display:table-row}tbody.collapse.in{display:table-row-group}.collapsing{position:relative;height:0;overflow:hidden;-webkit-transition-property:height, visibility;-o-transition-property:height, visibility;transition-property:height, visibility;-webkit-transition-duration:0.35s;-o-transition-duration:0.35s;transition-duration:0.35s;-webkit-transition-timing-function:ease;-o-transition-timing-function:ease;transition-timing-function:ease}.caret{display:inline-block;width:0;height:0;margin-left:2px;vertical-align:middle;border-top:4px dashed;border-top:4px solid \9;border-right:4px solid transparent;border-left:4px solid transparent}.dropup,.dropdown{position:relative}.dropdown-toggle:focus{outline:0}.dropdown-menu{position:absolute;top:100%;left:0;z-index:1000;display:none;float:left;min-width:160px;padding:5px 0;margin:2px 0 0;list-style:none;font-size:14px;text-align:left;background-color:#3a3f44;border:1px solid #272b30;border:1px solid rgba(0,0,0,0.15);border-radius:4px;-webkit-box-shadow:0 6px 12px rgba(0,0,0,0.175);box-shadow:0 6px 12px rgba(0,0,0,0.175);-webkit-background-clip:padding-box;background-clip:padding-box}.dropdown-menu.pull-right{right:0;left:auto}.dropdown-menu .divider{height:1px;margin:9px 0;overflow:hidden;background-color:#272b30}.dropdown-menu>li>a{display:block;padding:3px 20px;clear:both;font-weight:normal;line-height:1.42857143;color:#c8c8c8;white-space:nowrap}.dropdown-menu>li>a:hover,.dropdown-menu>li>a:focus{text-decoration:none;color:#ffffff;background-color:#272b30}.dropdown-menu>.active>a,.dropdown-menu>.active>a:hover,.dropdown-menu>.active>a:focus{color:#ffffff;text-decoration:none;outline:0;background-color:#272b30}.dropdown-menu>.disabled>a,.dropdown-menu>.disabled>a:hover,.dropdown-menu>.disabled>a:focus{color:#7a8288}.dropdown-menu>.disabled>a:hover,.dropdown-menu>.disabled>a:focus{text-decoration:none;background-color:transparent;background-image:none;filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);cursor:not-allowed}.open>.dropdown-menu{display:block}.open>a{outline:0}.dropdown-menu-right{left:auto;right:0}.dropdown-menu-left{left:0;right:auto}.dropdown-header{display:block;padding:3px 20px;font-size:12px;line-height:1.42857143;color:#7a8288;white-space:nowrap}.dropdown-backdrop{position:fixed;left:0;right:0;bottom:0;top:0;z-index:990}.pull-right>.dropdown-menu{right:0;left:auto}.dropup .caret,.navbar-fixed-bottom .dropdown .caret{border-top:0;border-bottom:4px dashed;border-bottom:4px solid \9;content:""}.dropup .dropdown-menu,.navbar-fixed-bottom .dropdown .dropdown-menu{top:auto;bottom:100%;margin-bottom:2px}@media (min-width:768px){.navbar-right .dropdown-menu{left:auto;right:0}.navbar-right .dropdown-menu-left{left:0;right:auto}}.btn-group,.btn-group-vertical{position:relative;display:inline-block;vertical-align:middle}.btn-group>.btn,.btn-group-vertical>.btn{position:relative;float:left}.btn-group>.btn:hover,.btn-group-vertical>.btn:hover,.btn-group>.btn:focus,.btn-group-vertical>.btn:focus,.btn-group>.btn:active,.btn-group-vertical>.btn:active,.btn-group>.btn.active,.btn-group-vertical>.btn.active{z-index:2}.btn-group .btn+.btn,.btn-group .btn+.btn-group,.btn-group .btn-group+.btn,.btn-group .btn-group+.btn-group{margin-left:-1px}.btn-toolbar{margin-left:-5px}.btn-toolbar .btn,.btn-toolbar .btn-group,.btn-toolbar .input-group{float:left}.btn-toolbar>.btn,.btn-toolbar>.btn-group,.btn-toolbar>.input-group{margin-left:5px}.btn-group>.btn:not(:first-child):not(:last-child):not(.dropdown-toggle){border-radius:0}.btn-group>.btn:first-child{margin-left:0}.btn-group>.btn:first-child:not(:last-child):not(.dropdown-toggle){border-bottom-right-radius:0;border-top-right-radius:0}.btn-group>.btn:last-child:not(:first-child),.btn-group>.dropdown-toggle:not(:first-child){border-bottom-left-radius:0;border-top-left-radius:0}.btn-group>.btn-group{float:left}.btn-group>.btn-group:not(:first-child):not(:last-child)>.btn{border-radius:0}.btn-group>.btn-group:first-child:not(:last-child)>.btn:last-child,.btn-group>.btn-group:first-child:not(:last-child)>.dropdown-toggle{border-bottom-right-radius:0;border-top-right-radius:0}.btn-group>.btn-group:last-child:not(:first-child)>.btn:first-child{border-bottom-left-radius:0;border-top-left-radius:0}.btn-group .dropdown-toggle:active,.btn-group.open .dropdown-toggle{outline:0}.btn-group>.btn+.dropdown-toggle{padding-left:8px;padding-right:8px}.btn-group>.btn-lg+.dropdown-toggle{padding-left:12px;padding-right:12px}.btn-group.open .dropdown-toggle{-webkit-box-shadow:inset 0 3px 5px rgba(0,0,0,0.125);box-shadow:inset 0 3px 5px rgba(0,0,0,0.125)}.btn-group.open .dropdown-toggle.btn-link{-webkit-box-shadow:none;box-shadow:none}.btn .caret{margin-left:0}.btn-lg .caret{border-width:5px 5px 0;border-bottom-width:0}.dropup .btn-lg .caret{border-width:0 5px 5px}.btn-group-vertical>.btn,.btn-group-vertical>.btn-group,.btn-group-vertical>.btn-group>.btn{display:block;float:none;width:100%;max-width:100%}.btn-group-vertical>.btn-group>.btn{float:none}.btn-group-vertical>.btn+.btn,.btn-group-vertical>.btn+.btn-group,.btn-group-vertical>.btn-group+.btn,.btn-group-vertical>.btn-group+.btn-group{margin-top:-1px;margin-left:0}.btn-group-vertical>.btn:not(:first-child):not(:last-child){border-radius:0}.btn-group-vertical>.btn:first-child:not(:last-child){border-top-right-radius:4px;border-top-left-radius:4px;border-bottom-right-radius:0;border-bottom-left-radius:0}.btn-group-vertical>.btn:last-child:not(:first-child){border-top-right-radius:0;border-top-left-radius:0;border-bottom-right-radius:4px;border-bottom-left-radius:4px}.btn-group-vertical>.btn-group:not(:first-child):not(:last-child)>.btn{border-radius:0}.btn-group-vertical>.btn-group:first-child:not(:last-child)>.btn:last-child,.btn-group-vertical>.btn-group:first-child:not(:last-child)>.dropdown-toggle{border-bottom-right-radius:0;border-bottom-left-radius:0}.btn-group-vertical>.btn-group:last-child:not(:first-child)>.btn:first-child{border-top-right-radius:0;border-top-left-radius:0}.btn-group-justified{display:table;width:100%;table-layout:fixed;border-collapse:separate}.btn-group-justified>.btn,.btn-group-justified>.btn-group{float:none;display:table-cell;width:1%}.btn-group-justified>.btn-group .btn{width:100%}.btn-group-justified>.btn-group .dropdown-menu{left:auto}[data-toggle="buttons"]>.btn input[type="radio"],[data-toggle="buttons"]>.btn-group>.btn input[type="radio"],[data-toggle="buttons"]>.btn input[type="checkbox"],[data-toggle="buttons"]>.btn-group>.btn input[type="checkbox"]{position:absolute;clip:rect(0, 0, 0, 0);pointer-events:none}.input-group{position:relative;display:table;border-collapse:separate}.input-group[class*="col-"]{float:none;padding-left:0;padding-right:0}.input-group .form-control{position:relative;z-index:2;float:left;width:100%;margin-bottom:0}.input-group .form-control:focus{z-index:3}.input-group-lg>.form-control,.input-group-lg>.input-group-addon,.input-group-lg>.input-group-btn>.btn{height:54px;padding:14px 16px;font-size:18px;line-height:1.3333333;border-radius:6px}select.input-group-lg>.form-control,select.input-group-lg>.input-group-addon,select.input-group-lg>.input-group-btn>.btn{height:54px;line-height:54px}textarea.input-group-lg>.form-control,textarea.input-group-lg>.input-group-addon,textarea.input-group-lg>.input-group-btn>.btn,select[multiple].input-group-lg>.form-control,select[multiple].input-group-lg>.input-group-addon,select[multiple].input-group-lg>.input-group-btn>.btn{height:auto}.input-group-sm>.form-control,.input-group-sm>.input-group-addon,.input-group-sm>.input-group-btn>.btn{height:30px;padding:5px 10px;font-size:12px;line-height:1.5;border-radius:3px}select.input-group-sm>.form-control,select.input-group-sm>.input-group-addon,select.input-group-sm>.input-group-btn>.btn{height:30px;line-height:30px}textarea.input-group-sm>.form-control,textarea.input-group-sm>.input-group-addon,textarea.input-group-sm>.input-group-btn>.btn,select[multiple].input-group-sm>.form-control,select[multiple].input-group-sm>.input-group-addon,select[multiple].input-group-sm>.input-group-btn>.btn{height:auto}.input-group-addon,.input-group-btn,.input-group .form-control{display:table-cell}.input-group-addon:not(:first-child):not(:last-child),.input-group-btn:not(:first-child):not(:last-child),.input-group .form-control:not(:first-child):not(:last-child){border-radius:0}.input-group-addon,.input-group-btn{width:1%;white-space:nowrap;vertical-align:middle}.input-group-addon{padding:8px 12px;font-size:14px;font-weight:normal;line-height:1;color:#272b30;text-align:center;background-color:#3a3f44;border:1px solid rgba(0,0,0,0.6);border-radius:4px}.input-group-addon.input-sm{padding:5px 10px;font-size:12px;border-radius:3px}.input-group-addon.input-lg{padding:14px 16px;font-size:18px;border-radius:6px}.input-group-addon input[type="radio"],.input-group-addon input[type="checkbox"]{margin-top:0}.input-group .form-control:first-child,.input-group-addon:first-child,.input-group-btn:first-child>.btn,.input-group-btn:first-child>.btn-group>.btn,.input-group-btn:first-child>.dropdown-toggle,.input-group-btn:last-child>.btn:not(:last-child):not(.dropdown-toggle),.input-group-btn:last-child>.btn-group:not(:last-child)>.btn{border-bottom-right-radius:0;border-top-right-radius:0}.input-group-addon:first-child{border-right:0}.input-group .form-control:last-child,.input-group-addon:last-child,.input-group-btn:last-child>.btn,.input-group-btn:last-child>.btn-group>.btn,.input-group-btn:last-child>.dropdown-toggle,.input-group-btn:first-child>.btn:not(:first-child),.input-group-btn:first-child>.btn-group:not(:first-child)>.btn{border-bottom-left-radius:0;border-top-left-radius:0}.input-group-addon:last-child{border-left:0}.input-group-btn{position:relative;font-size:0;white-space:nowrap}.input-group-btn>.btn{position:relative}.input-group-btn>.btn+.btn{margin-left:-1px}.input-group-btn>.btn:hover,.input-group-btn>.btn:focus,.input-group-btn>.btn:active{z-index:2}.input-group-btn:first-child>.btn,.input-group-btn:first-child>.btn-group{margin-right:-1px}.input-group-btn:last-child>.btn,.input-group-btn:last-child>.btn-group{z-index:2;margin-left:-1px}.nav{margin-bottom:0;padding-left:0;list-style:none}.nav>li{position:relative;display:block}.nav>li>a{position:relative;display:block;padding:10px 15px}.nav>li>a:hover,.nav>li>a:focus{text-decoration:none;background-color:#3e444c}.nav>li.disabled>a{color:#7a8288}.nav>li.disabled>a:hover,.nav>li.disabled>a:focus{color:#7a8288;text-decoration:none;background-color:transparent;cursor:not-allowed}.nav .open>a,.nav .open>a:hover,.nav .open>a:focus{background-color:#3e444c;border-color:#ffffff}.nav .nav-divider{height:1px;margin:9px 0;overflow:hidden;background-color:#e5e5e5}.nav>li>a>img{max-width:none}.nav-tabs{border-bottom:1px solid #1c1e22}.nav-tabs>li{float:left;margin-bottom:-1px}.nav-tabs>li>a{margin-right:2px;line-height:1.42857143;border:1px solid transparent;border-radius:4px 4px 0 0}.nav-tabs>li>a:hover{border-color:#1c1e22 #1c1e22 #1c1e22}.nav-tabs>li.active>a,.nav-tabs>li.active>a:hover,.nav-tabs>li.active>a:focus{color:#ffffff;background-color:#3e444c;border:1px solid #1c1e22;border-bottom-color:transparent;cursor:default}.nav-tabs.nav-justified{width:100%;border-bottom:0}.nav-tabs.nav-justified>li{float:none}.nav-tabs.nav-justified>li>a{text-align:center;margin-bottom:5px}.nav-tabs.nav-justified>.dropdown .dropdown-menu{top:auto;left:auto}@media (min-width:768px){.nav-tabs.nav-justified>li{display:table-cell;width:1%}.nav-tabs.nav-justified>li>a{margin-bottom:0}}.nav-tabs.nav-justified>li>a{margin-right:0;border-radius:4px}.nav-tabs.nav-justified>.active>a,.nav-tabs.nav-justified>.active>a:hover,.nav-tabs.nav-justified>.active>a:focus{border:1px solid #1c1e22}@media (min-width:768px){.nav-tabs.nav-justified>li>a{border-bottom:1px solid #1c1e22;border-radius:4px 4px 0 0}.nav-tabs.nav-justified>.active>a,.nav-tabs.nav-justified>.active>a:hover,.nav-tabs.nav-justified>.active>a:focus{border-bottom-color:#272b30}}.nav-pills>li{float:left}.nav-pills>li>a{border-radius:4px}.nav-pills>li+li{margin-left:2px}.nav-pills>li.active>a,.nav-pills>li.active>a:hover,.nav-pills>li.active>a:focus{color:#ffffff;background-color:transparent}.nav-stacked>li{float:none}.nav-stacked>li+li{margin-top:2px;margin-left:0}.nav-justified{width:100%}.nav-justified>li{float:none}.nav-justified>li>a{text-align:center;margin-bottom:5px}.nav-justified>.dropdown .dropdown-menu{top:auto;left:auto}@media (min-width:768px){.nav-justified>li{display:table-cell;width:1%}.nav-justified>li>a{margin-bottom:0}}.nav-tabs-justified{border-bottom:0}.nav-tabs-justified>li>a{margin-right:0;border-radius:4px}.nav-tabs-justified>.active>a,.nav-tabs-justified>.active>a:hover,.nav-tabs-justified>.active>a:focus{border:1px solid #1c1e22}@media (min-width:768px){.nav-tabs-justified>li>a{border-bottom:1px solid #1c1e22;border-radius:4px 4px 0 0}.nav-tabs-justified>.active>a,.nav-tabs-justified>.active>a:hover,.nav-tabs-justified>.active>a:focus{border-bottom-color:#272b30}}.tab-content>.tab-pane{display:none}.tab-content>.active{display:block}.nav-tabs .dropdown-menu{margin-top:-1px;border-top-right-radius:0;border-top-left-radius:0}.navbar{position:relative;min-height:50px;margin-bottom:20px;border:1px solid transparent}@media (min-width:768px){.navbar{border-radius:4px}}@media (min-width:768px){.navbar-header{float:left}}.navbar-collapse{overflow-x:visible;padding-right:15px;padding-left:15px;border-top:1px solid transparent;-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,0.1);box-shadow:inset 0 1px 0 rgba(255,255,255,0.1);-webkit-overflow-scrolling:touch}.navbar-collapse.in{overflow-y:auto}@media (min-width:768px){.navbar-collapse{width:auto;border-top:0;-webkit-box-shadow:none;box-shadow:none}.navbar-collapse.collapse{display:block !important;height:auto !important;padding-bottom:0;overflow:visible !important}.navbar-collapse.in{overflow-y:visible}.navbar-fixed-top .navbar-collapse,.navbar-static-top .navbar-collapse,.navbar-fixed-bottom .navbar-collapse{padding-left:0;padding-right:0}}.navbar-fixed-top .navbar-collapse,.navbar-fixed-bottom .navbar-collapse{max-height:340px}@media (max-device-width:480px) and (orientation:landscape){.navbar-fixed-top .navbar-collapse,.navbar-fixed-bottom .navbar-collapse{max-height:200px}}.container>.navbar-header,.container-fluid>.navbar-header,.container>.navbar-collapse,.container-fluid>.navbar-collapse{margin-right:-15px;margin-left:-15px}@media (min-width:768px){.container>.navbar-header,.container-fluid>.navbar-header,.container>.navbar-collapse,.container-fluid>.navbar-collapse{margin-right:0;margin-left:0}}.navbar-static-top{z-index:1000;border-width:0 0 1px}@media (min-width:768px){.navbar-static-top{border-radius:0}}.navbar-fixed-top,.navbar-fixed-bottom{position:fixed;right:0;left:0;z-index:1030}@media (min-width:768px){.navbar-fixed-top,.navbar-fixed-bottom{border-radius:0}}.navbar-fixed-top{top:0;border-width:0 0 1px}.navbar-fixed-bottom{bottom:0;margin-bottom:0;border-width:1px 0 0}.navbar-brand{float:left;padding:15px 15px;font-size:18px;line-height:20px;height:50px}.navbar-brand:hover,.navbar-brand:focus{text-decoration:none}.navbar-brand>img{display:block}@media (min-width:768px){.navbar>.container .navbar-brand,.navbar>.container-fluid .navbar-brand{margin-left:-15px}}.navbar-toggle{position:relative;float:right;margin-right:15px;padding:9px 10px;margin-top:8px;margin-bottom:8px;background-color:transparent;background-image:none;border:1px solid transparent;border-radius:4px}.navbar-toggle:focus{outline:0}.navbar-toggle .icon-bar{display:block;width:22px;height:2px;border-radius:1px}.navbar-toggle .icon-bar+.icon-bar{margin-top:4px}@media (min-width:768px){.navbar-toggle{display:none}}.navbar-nav{margin:7.5px -15px}.navbar-nav>li>a{padding-top:10px;padding-bottom:10px;line-height:20px}@media (max-width:767px){.navbar-nav .open .dropdown-menu{position:static;float:none;width:auto;margin-top:0;background-color:transparent;border:0;-webkit-box-shadow:none;box-shadow:none}.navbar-nav .open .dropdown-menu>li>a,.navbar-nav .open .dropdown-menu .dropdown-header{padding:5px 15px 5px 25px}.navbar-nav .open .dropdown-menu>li>a{line-height:20px}.navbar-nav .open .dropdown-menu>li>a:hover,.navbar-nav .open .dropdown-menu>li>a:focus{background-image:none}}@media (min-width:768px){.navbar-nav{float:left;margin:0}.navbar-nav>li{float:left}.navbar-nav>li>a{padding-top:15px;padding-bottom:15px}}.navbar-form{margin-left:-15px;margin-right:-15px;padding:10px 15px;border-top:1px solid transparent;border-bottom:1px solid transparent;-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,0.1),0 1px 0 rgba(255,255,255,0.1);box-shadow:inset 0 1px 0 rgba(255,255,255,0.1),0 1px 0 rgba(255,255,255,0.1);margin-top:6px;margin-bottom:6px}@media (min-width:768px){.navbar-form .form-group{display:inline-block;margin-bottom:0;vertical-align:middle}.navbar-form .form-control{display:inline-block;width:auto;vertical-align:middle}.navbar-form .form-control-static{display:inline-block}.navbar-form .input-group{display:inline-table;vertical-align:middle}.navbar-form .input-group .input-group-addon,.navbar-form .input-group .input-group-btn,.navbar-form .input-group .form-control{width:auto}.navbar-form .input-group>.form-control{width:100%}.navbar-form .control-label{margin-bottom:0;vertical-align:middle}.navbar-form .radio,.navbar-form .checkbox{display:inline-block;margin-top:0;margin-bottom:0;vertical-align:middle}.navbar-form .radio label,.navbar-form .checkbox label{padding-left:0}.navbar-form .radio input[type="radio"],.navbar-form .checkbox input[type="checkbox"]{position:relative;margin-left:0}.navbar-form .has-feedback .form-control-feedback{top:0}}@media (max-width:767px){.navbar-form .form-group{margin-bottom:5px}.navbar-form .form-group:last-child{margin-bottom:0}}@media (min-width:768px){.navbar-form{width:auto;border:0;margin-left:0;margin-right:0;padding-top:0;padding-bottom:0;-webkit-box-shadow:none;box-shadow:none}}.navbar-nav>li>.dropdown-menu{margin-top:0;border-top-right-radius:0;border-top-left-radius:0}.navbar-fixed-bottom .navbar-nav>li>.dropdown-menu{margin-bottom:0;border-top-right-radius:4px;border-top-left-radius:4px;border-bottom-right-radius:0;border-bottom-left-radius:0}.navbar-btn{margin-top:6px;margin-bottom:6px}.navbar-btn.btn-sm{margin-top:10px;margin-bottom:10px}.navbar-btn.btn-xs{margin-top:14px;margin-bottom:14px}.navbar-text{margin-top:15px;margin-bottom:15px}@media (min-width:768px){.navbar-text{float:left;margin-left:15px;margin-right:15px}}@media (min-width:768px){.navbar-left{float:left !important}.navbar-right{float:right !important;margin-right:-15px}.navbar-right~.navbar-right{margin-right:0}}.navbar-default{background-color:#3a3f44;border-color:#2b2e32}.navbar-default .navbar-brand{color:#c8c8c8}.navbar-default .navbar-brand:hover,.navbar-default .navbar-brand:focus{color:#ffffff;background-color:none}.navbar-default .navbar-text{color:#c8c8c8}.navbar-default .navbar-nav>li>a{color:#c8c8c8}.navbar-default .navbar-nav>li>a:hover,.navbar-default .navbar-nav>li>a:focus{color:#ffffff;background-color:#272b2e}.navbar-default .navbar-nav>.active>a,.navbar-default .navbar-nav>.active>a:hover,.navbar-default .navbar-nav>.active>a:focus{color:#ffffff;background-color:#272b2e}.navbar-default .navbar-nav>.disabled>a,.navbar-default .navbar-nav>.disabled>a:hover,.navbar-default .navbar-nav>.disabled>a:focus{color:#cccccc;background-color:transparent}.navbar-default .navbar-toggle{border-color:#272b2e}.navbar-default .navbar-toggle:hover,.navbar-default .navbar-toggle:focus{background-color:#272b2e}.navbar-default .navbar-toggle .icon-bar{background-color:#c8c8c8}.navbar-default .navbar-collapse,.navbar-default .navbar-form{border-color:#2b2e32}.navbar-default .navbar-nav>.open>a,.navbar-default .navbar-nav>.open>a:hover,.navbar-default .navbar-nav>.open>a:focus{background-color:#272b2e;color:#ffffff}@media (max-width:767px){.navbar-default .navbar-nav .open .dropdown-menu>li>a{color:#c8c8c8}.navbar-default .navbar-nav .open .dropdown-menu>li>a:hover,.navbar-default .navbar-nav .open .dropdown-menu>li>a:focus{color:#ffffff;background-color:#272b2e}.navbar-default .navbar-nav .open .dropdown-menu>.active>a,.navbar-default .navbar-nav .open .dropdown-menu>.active>a:hover,.navbar-default .navbar-nav .open .dropdown-menu>.active>a:focus{color:#ffffff;background-color:#272b2e}.navbar-default .navbar-nav .open .dropdown-menu>.disabled>a,.navbar-default .navbar-nav .open .dropdown-menu>.disabled>a:hover,.navbar-default .navbar-nav .open .dropdown-menu>.disabled>a:focus{color:#cccccc;background-color:transparent}}.navbar-default .navbar-link{color:#c8c8c8}.navbar-default .navbar-link:hover{color:#ffffff}.navbar-default .btn-link{color:#c8c8c8}.navbar-default .btn-link:hover,.navbar-default .btn-link:focus{color:#ffffff}.navbar-default .btn-link[disabled]:hover,fieldset[disabled] .navbar-default .btn-link:hover,.navbar-default .btn-link[disabled]:focus,fieldset[disabled] .navbar-default .btn-link:focus{color:#cccccc}.navbar-inverse{background-color:#7a8288;border-color:#62686d}.navbar-inverse .navbar-brand{color:#cccccc}.navbar-inverse .navbar-brand:hover,.navbar-inverse .navbar-brand:focus{color:#ffffff;background-color:none}.navbar-inverse .navbar-text{color:#cccccc}.navbar-inverse .navbar-nav>li>a{color:#cccccc}.navbar-inverse .navbar-nav>li>a:hover,.navbar-inverse .navbar-nav>li>a:focus{color:#ffffff;background-color:#5d6368}.navbar-inverse .navbar-nav>.active>a,.navbar-inverse .navbar-nav>.active>a:hover,.navbar-inverse .navbar-nav>.active>a:focus{color:#ffffff;background-color:#5d6368}.navbar-inverse .navbar-nav>.disabled>a,.navbar-inverse .navbar-nav>.disabled>a:hover,.navbar-inverse .navbar-nav>.disabled>a:focus{color:#cccccc;background-color:transparent}.navbar-inverse .navbar-toggle{border-color:#5d6368}.navbar-inverse .navbar-toggle:hover,.navbar-inverse .navbar-toggle:focus{background-color:#5d6368}.navbar-inverse .navbar-toggle .icon-bar{background-color:#ffffff}.navbar-inverse .navbar-collapse,.navbar-inverse .navbar-form{border-color:#697075}.navbar-inverse .navbar-nav>.open>a,.navbar-inverse .navbar-nav>.open>a:hover,.navbar-inverse .navbar-nav>.open>a:focus{background-color:#5d6368;color:#ffffff}@media (max-width:767px){.navbar-inverse .navbar-nav .open .dropdown-menu>.dropdown-header{border-color:#62686d}.navbar-inverse .navbar-nav .open .dropdown-menu .divider{background-color:#62686d}.navbar-inverse .navbar-nav .open .dropdown-menu>li>a{color:#cccccc}.navbar-inverse .navbar-nav .open .dropdown-menu>li>a:hover,.navbar-inverse .navbar-nav .open .dropdown-menu>li>a:focus{color:#ffffff;background-color:#5d6368}.navbar-inverse .navbar-nav .open .dropdown-menu>.active>a,.navbar-inverse .navbar-nav .open .dropdown-menu>.active>a:hover,.navbar-inverse .navbar-nav .open .dropdown-menu>.active>a:focus{color:#ffffff;background-color:#5d6368}.navbar-inverse .navbar-nav .open .dropdown-menu>.disabled>a,.navbar-inverse .navbar-nav .open .dropdown-menu>.disabled>a:hover,.navbar-inverse .navbar-nav .open .dropdown-menu>.disabled>a:focus{color:#cccccc;background-color:transparent}}.navbar-inverse .navbar-link{color:#cccccc}.navbar-inverse .navbar-link:hover{color:#ffffff}.navbar-inverse .btn-link{color:#cccccc}.navbar-inverse .btn-link:hover,.navbar-inverse .btn-link:focus{color:#ffffff}.navbar-inverse .btn-link[disabled]:hover,fieldset[disabled] .navbar-inverse .btn-link:hover,.navbar-inverse .btn-link[disabled]:focus,fieldset[disabled] .navbar-inverse .btn-link:focus{color:#cccccc}.breadcrumb{padding:8px 15px;margin-bottom:20px;list-style:none;background-color:transparent;border-radius:4px}.breadcrumb>li{display:inline-block}.breadcrumb>li+li:before{content:"/\00a0";padding:0 5px;color:#cccccc}.breadcrumb>.active{color:#7a8288}.pagination{display:inline-block;padding-left:0;margin:20px 0;border-radius:4px}.pagination>li{display:inline}.pagination>li>a,.pagination>li>span{position:relative;float:left;padding:8px 12px;line-height:1.42857143;text-decoration:none;color:#ffffff;background-color:#3a3f44;border:1px solid rgba(0,0,0,0.6);margin-left:-1px}.pagination>li:first-child>a,.pagination>li:first-child>span{margin-left:0;border-bottom-left-radius:4px;border-top-left-radius:4px}.pagination>li:last-child>a,.pagination>li:last-child>span{border-bottom-right-radius:4px;border-top-right-radius:4px}.pagination>li>a:hover,.pagination>li>span:hover,.pagination>li>a:focus,.pagination>li>span:focus{z-index:2;color:#ffffff;background-color:transparent;border-color:rgba(0,0,0,0.6)}.pagination>.active>a,.pagination>.active>span,.pagination>.active>a:hover,.pagination>.active>span:hover,.pagination>.active>a:focus,.pagination>.active>span:focus{z-index:3;color:#ffffff;background-color:#232628;border-color:rgba(0,0,0,0.6);cursor:default}.pagination>.disabled>span,.pagination>.disabled>span:hover,.pagination>.disabled>span:focus,.pagination>.disabled>a,.pagination>.disabled>a:hover,.pagination>.disabled>a:focus{color:#7a8288;background-color:#ffffff;border-color:rgba(0,0,0,0.6);cursor:not-allowed}.pagination-lg>li>a,.pagination-lg>li>span{padding:14px 16px;font-size:18px;line-height:1.3333333}.pagination-lg>li:first-child>a,.pagination-lg>li:first-child>span{border-bottom-left-radius:6px;border-top-left-radius:6px}.pagination-lg>li:last-child>a,.pagination-lg>li:last-child>span{border-bottom-right-radius:6px;border-top-right-radius:6px}.pagination-sm>li>a,.pagination-sm>li>span{padding:5px 10px;font-size:12px;line-height:1.5}.pagination-sm>li:first-child>a,.pagination-sm>li:first-child>span{border-bottom-left-radius:3px;border-top-left-radius:3px}.pagination-sm>li:last-child>a,.pagination-sm>li:last-child>span{border-bottom-right-radius:3px;border-top-right-radius:3px}.pager{padding-left:0;margin:20px 0;list-style:none;text-align:center}.pager li{display:inline}.pager li>a,.pager li>span{display:inline-block;padding:5px 14px;background-color:#3a3f44;border:1px solid rgba(0,0,0,0.6);border-radius:15px}.pager li>a:hover,.pager li>a:focus{text-decoration:none;background-color:transparent}.pager .next>a,.pager .next>span{float:right}.pager .previous>a,.pager .previous>span{float:left}.pager .disabled>a,.pager .disabled>a:hover,.pager .disabled>a:focus,.pager .disabled>span{color:#7a8288;background-color:#3a3f44;cursor:not-allowed}.label{display:inline;padding:.2em .6em .3em;font-size:75%;font-weight:bold;line-height:1;color:#ffffff;text-align:center;white-space:nowrap;vertical-align:baseline;border-radius:.25em}a.label:hover,a.label:focus{color:#ffffff;text-decoration:none;cursor:pointer}.label:empty{display:none}.btn .label{position:relative;top:-1px}.label-default{background-color:#3a3f44}.label-default[href]:hover,.label-default[href]:focus{background-color:#232628}.label-primary{background-color:#7a8288}.label-primary[href]:hover,.label-primary[href]:focus{background-color:#62686d}.label-success{background-color:#62c462}.label-success[href]:hover,.label-success[href]:focus{background-color:#42b142}.label-info{background-color:#5bc0de}.label-info[href]:hover,.label-info[href]:focus{background-color:#31b0d5}.label-warning{background-color:#f89406}.label-warning[href]:hover,.label-warning[href]:focus{background-color:#c67605}.label-danger{background-color:#ee5f5b}.label-danger[href]:hover,.label-danger[href]:focus{background-color:#e9322d}.badge{display:inline-block;min-width:10px;padding:3px 7px;font-size:12px;font-weight:bold;color:#ffffff;line-height:1;vertical-align:middle;white-space:nowrap;text-align:center;background-color:#7a8288;border-radius:10px}.badge:empty{display:none}.btn .badge{position:relative;top:-1px}.btn-xs .badge,.btn-group-xs>.btn .badge{top:0;padding:1px 5px}a.badge:hover,a.badge:focus{color:#ffffff;text-decoration:none;cursor:pointer}.list-group-item.active>.badge,.nav-pills>.active>a>.badge{color:#ffffff;background-color:#7a8288}.list-group-item>.badge{float:right}.list-group-item>.badge+.badge{margin-right:5px}.nav-pills>li>a>.badge{margin-left:3px}.jumbotron{padding-top:30px;padding-bottom:30px;margin-bottom:30px;color:inherit;background-color:#1c1e22}.jumbotron h1,.jumbotron .h1{color:inherit}.jumbotron p{margin-bottom:15px;font-size:21px;font-weight:200}.jumbotron>hr{border-top-color:#050506}.container .jumbotron,.container-fluid .jumbotron{border-radius:6px;padding-left:15px;padding-right:15px}.jumbotron .container{max-width:100%}@media screen and (min-width:768px){.jumbotron{padding-top:48px;padding-bottom:48px}.container .jumbotron,.container-fluid .jumbotron{padding-left:60px;padding-right:60px}.jumbotron h1,.jumbotron .h1{font-size:63px}}.thumbnail{display:block;padding:4px;margin-bottom:20px;line-height:1.42857143;background-color:#1c1e22;border:1px solid #0c0d0e;border-radius:4px;-webkit-transition:border .2s ease-in-out;-o-transition:border .2s ease-in-out;transition:border .2s ease-in-out}.thumbnail>img,.thumbnail a>img{margin-left:auto;margin-right:auto}a.thumbnail:hover,a.thumbnail:focus,a.thumbnail.active{border-color:#ffffff}.thumbnail .caption{padding:9px;color:#c8c8c8}.alert{padding:15px;margin-bottom:20px;border:1px solid transparent;border-radius:4px}.alert h4{margin-top:0;color:inherit}.alert .alert-link{font-weight:bold}.alert>p,.alert>ul{margin-bottom:0}.alert>p+p{margin-top:5px}.alert-dismissable,.alert-dismissible{padding-right:35px}.alert-dismissable .close,.alert-dismissible .close{position:relative;top:-2px;right:-21px;color:inherit}.alert-success{background-color:#62c462;border-color:#62bd4f;color:#ffffff}.alert-success hr{border-top-color:#55b142}.alert-success .alert-link{color:#e6e6e6}.alert-info{background-color:#5bc0de;border-color:#3dced8;color:#ffffff}.alert-info hr{border-top-color:#2ac7d2}.alert-info .alert-link{color:#e6e6e6}.alert-warning{background-color:#f89406;border-color:#e96506;color:#ffffff}.alert-warning hr{border-top-color:#d05a05}.alert-warning .alert-link{color:#e6e6e6}.alert-danger{background-color:#ee5f5b;border-color:#ed4d63;color:#ffffff}.alert-danger hr{border-top-color:#ea364f}.alert-danger .alert-link{color:#e6e6e6}@-webkit-keyframes progress-bar-stripes{from{background-position:40px 0}to{background-position:0 0}}@-o-keyframes progress-bar-stripes{from{background-position:40px 0}to{background-position:0 0}}@keyframes progress-bar-stripes{from{background-position:40px 0}to{background-position:0 0}}.progress{overflow:hidden;height:20px;margin-bottom:20px;background-color:#1c1e22;border-radius:4px;-webkit-box-shadow:inset 0 1px 2px rgba(0,0,0,0.1);box-shadow:inset 0 1px 2px rgba(0,0,0,0.1)}.progress-bar{float:left;width:0%;height:100%;font-size:12px;line-height:20px;color:#ffffff;text-align:center;background-color:#7a8288;-webkit-box-shadow:inset 0 -1px 0 rgba(0,0,0,0.15);box-shadow:inset 0 -1px 0 rgba(0,0,0,0.15);-webkit-transition:width 0.6s ease;-o-transition:width 0.6s ease;transition:width 0.6s ease}.progress-striped .progress-bar,.progress-bar-striped{background-image:-webkit-linear-gradient(45deg, rgba(255,255,255,0.15) 25%, transparent 25%, transparent 50%, rgba(255,255,255,0.15) 50%, rgba(255,255,255,0.15) 75%, transparent 75%, transparent);background-image:-o-linear-gradient(45deg, rgba(255,255,255,0.15) 25%, transparent 25%, transparent 50%, rgba(255,255,255,0.15) 50%, rgba(255,255,255,0.15) 75%, transparent 75%, transparent);background-image:linear-gradient(45deg, rgba(255,255,255,0.15) 25%, transparent 25%, transparent 50%, rgba(255,255,255,0.15) 50%, rgba(255,255,255,0.15) 75%, transparent 75%, transparent);-webkit-background-size:40px 40px;background-size:40px 40px}.progress.active .progress-bar,.progress-bar.active{-webkit-animation:progress-bar-stripes 2s linear infinite;-o-animation:progress-bar-stripes 2s linear infinite;animation:progress-bar-stripes 2s linear infinite}.progress-bar-success{background-color:#62c462}.progress-striped .progress-bar-success{background-image:-webkit-linear-gradient(45deg, rgba(255,255,255,0.15) 25%, transparent 25%, transparent 50%, rgba(255,255,255,0.15) 50%, rgba(255,255,255,0.15) 75%, transparent 75%, transparent);background-image:-o-linear-gradient(45deg, rgba(255,255,255,0.15) 25%, transparent 25%, transparent 50%, rgba(255,255,255,0.15) 50%, rgba(255,255,255,0.15) 75%, transparent 75%, transparent);background-image:linear-gradient(45deg, rgba(255,255,255,0.15) 25%, transparent 25%, transparent 50%, rgba(255,255,255,0.15) 50%, rgba(255,255,255,0.15) 75%, transparent 75%, transparent)}.progress-bar-info{background-color:#5bc0de}.progress-striped .progress-bar-info{background-image:-webkit-linear-gradient(45deg, rgba(255,255,255,0.15) 25%, transparent 25%, transparent 50%, rgba(255,255,255,0.15) 50%, rgba(255,255,255,0.15) 75%, transparent 75%, transparent);background-image:-o-linear-gradient(45deg, rgba(255,255,255,0.15) 25%, transparent 25%, transparent 50%, rgba(255,255,255,0.15) 50%, rgba(255,255,255,0.15) 75%, transparent 75%, transparent);background-image:linear-gradient(45deg, rgba(255,255,255,0.15) 25%, transparent 25%, transparent 50%, rgba(255,255,255,0.15) 50%, rgba(255,255,255,0.15) 75%, transparent 75%, transparent)}.progress-bar-warning{background-color:#f89406}.progress-striped .progress-bar-warning{background-image:-webkit-linear-gradient(45deg, rgba(255,255,255,0.15) 25%, transparent 25%, transparent 50%, rgba(255,255,255,0.15) 50%, rgba(255,255,255,0.15) 75%, transparent 75%, transparent);background-image:-o-linear-gradient(45deg, rgba(255,255,255,0.15) 25%, transparent 25%, transparent 50%, rgba(255,255,255,0.15) 50%, rgba(255,255,255,0.15) 75%, transparent 75%, transparent);background-image:linear-gradient(45deg, rgba(255,255,255,0.15) 25%, transparent 25%, transparent 50%, rgba(255,255,255,0.15) 50%, rgba(255,255,255,0.15) 75%, transparent 75%, transparent)}.progress-bar-danger{background-color:#ee5f5b}.progress-striped .progress-bar-danger{background-image:-webkit-linear-gradient(45deg, rgba(255,255,255,0.15) 25%, transparent 25%, transparent 50%, rgba(255,255,255,0.15) 50%, rgba(255,255,255,0.15) 75%, transparent 75%, transparent);background-image:-o-linear-gradient(45deg, rgba(255,255,255,0.15) 25%, transparent 25%, transparent 50%, rgba(255,255,255,0.15) 50%, rgba(255,255,255,0.15) 75%, transparent 75%, transparent);background-image:linear-gradient(45deg, rgba(255,255,255,0.15) 25%, transparent 25%, transparent 50%, rgba(255,255,255,0.15) 50%, rgba(255,255,255,0.15) 75%, transparent 75%, transparent)}.media{margin-top:15px}.media:first-child{margin-top:0}.media,.media-body{zoom:1;overflow:hidden}.media-body{width:10000px}.media-object{display:block}.media-object.img-thumbnail{max-width:none}.media-right,.media>.pull-right{padding-left:10px}.media-left,.media>.pull-left{padding-right:10px}.media-left,.media-right,.media-body{display:table-cell;vertical-align:top}.media-middle{vertical-align:middle}.media-bottom{vertical-align:bottom}.media-heading{margin-top:0;margin-bottom:5px}.media-list{padding-left:0;list-style:none}.list-group{margin-bottom:20px;padding-left:0}.list-group-item{position:relative;display:block;padding:10px 15px;margin-bottom:-1px;background-color:#32383e;border:1px solid rgba(0,0,0,0.6)}.list-group-item:first-child{border-top-right-radius:4px;border-top-left-radius:4px}.list-group-item:last-child{margin-bottom:0;border-bottom-right-radius:4px;border-bottom-left-radius:4px}a.list-group-item,button.list-group-item{color:#c8c8c8}a.list-group-item .list-group-item-heading,button.list-group-item .list-group-item-heading{color:#ffffff}a.list-group-item:hover,button.list-group-item:hover,a.list-group-item:focus,button.list-group-item:focus{text-decoration:none;color:#c8c8c8;background-color:#3e444c}button.list-group-item{width:100%;text-align:left}.list-group-item.disabled,.list-group-item.disabled:hover,.list-group-item.disabled:focus{background-color:#999999;color:#7a8288;cursor:not-allowed}.list-group-item.disabled .list-group-item-heading,.list-group-item.disabled:hover .list-group-item-heading,.list-group-item.disabled:focus .list-group-item-heading{color:inherit}.list-group-item.disabled .list-group-item-text,.list-group-item.disabled:hover .list-group-item-text,.list-group-item.disabled:focus .list-group-item-text{color:#7a8288}.list-group-item.active,.list-group-item.active:hover,.list-group-item.active:focus{z-index:2;color:#ffffff;background-color:#3e444c;border-color:rgba(0,0,0,0.6)}.list-group-item.active .list-group-item-heading,.list-group-item.active:hover .list-group-item-heading,.list-group-item.active:focus .list-group-item-heading,.list-group-item.active .list-group-item-heading>small,.list-group-item.active:hover .list-group-item-heading>small,.list-group-item.active:focus .list-group-item-heading>small,.list-group-item.active .list-group-item-heading>.small,.list-group-item.active:hover .list-group-item-heading>.small,.list-group-item.active:focus .list-group-item-heading>.small{color:inherit}.list-group-item.active .list-group-item-text,.list-group-item.active:hover .list-group-item-text,.list-group-item.active:focus .list-group-item-text{color:#a2aab4}.list-group-item-success{color:#ffffff;background-color:#62c462}a.list-group-item-success,button.list-group-item-success{color:#ffffff}a.list-group-item-success .list-group-item-heading,button.list-group-item-success .list-group-item-heading{color:inherit}a.list-group-item-success:hover,button.list-group-item-success:hover,a.list-group-item-success:focus,button.list-group-item-success:focus{color:#ffffff;background-color:#4fbd4f}a.list-group-item-success.active,button.list-group-item-success.active,a.list-group-item-success.active:hover,button.list-group-item-success.active:hover,a.list-group-item-success.active:focus,button.list-group-item-success.active:focus{color:#fff;background-color:#ffffff;border-color:#ffffff}.list-group-item-info{color:#ffffff;background-color:#5bc0de}a.list-group-item-info,button.list-group-item-info{color:#ffffff}a.list-group-item-info .list-group-item-heading,button.list-group-item-info .list-group-item-heading{color:inherit}a.list-group-item-info:hover,button.list-group-item-info:hover,a.list-group-item-info:focus,button.list-group-item-info:focus{color:#ffffff;background-color:#46b8da}a.list-group-item-info.active,button.list-group-item-info.active,a.list-group-item-info.active:hover,button.list-group-item-info.active:hover,a.list-group-item-info.active:focus,button.list-group-item-info.active:focus{color:#fff;background-color:#ffffff;border-color:#ffffff}.list-group-item-warning{color:#ffffff;background-color:#f89406}a.list-group-item-warning,button.list-group-item-warning{color:#ffffff}a.list-group-item-warning .list-group-item-heading,button.list-group-item-warning .list-group-item-heading{color:inherit}a.list-group-item-warning:hover,button.list-group-item-warning:hover,a.list-group-item-warning:focus,button.list-group-item-warning:focus{color:#ffffff;background-color:#df8505}a.list-group-item-warning.active,button.list-group-item-warning.active,a.list-group-item-warning.active:hover,button.list-group-item-warning.active:hover,a.list-group-item-warning.active:focus,button.list-group-item-warning.active:focus{color:#fff;background-color:#ffffff;border-color:#ffffff}.list-group-item-danger{color:#ffffff;background-color:#ee5f5b}a.list-group-item-danger,button.list-group-item-danger{color:#ffffff}a.list-group-item-danger .list-group-item-heading,button.list-group-item-danger .list-group-item-heading{color:inherit}a.list-group-item-danger:hover,button.list-group-item-danger:hover,a.list-group-item-danger:focus,button.list-group-item-danger:focus{color:#ffffff;background-color:#ec4844}a.list-group-item-danger.active,button.list-group-item-danger.active,a.list-group-item-danger.active:hover,button.list-group-item-danger.active:hover,a.list-group-item-danger.active:focus,button.list-group-item-danger.active:focus{color:#fff;background-color:#ffffff;border-color:#ffffff}.list-group-item-heading{margin-top:0;margin-bottom:5px}.list-group-item-text{margin-bottom:0;line-height:1.3}.panel{margin-bottom:20px;background-color:#2e3338;border:1px solid transparent;border-radius:4px;-webkit-box-shadow:0 1px 1px rgba(0,0,0,0.05);box-shadow:0 1px 1px rgba(0,0,0,0.05)}.panel-body{padding:15px}.panel-heading{padding:10px 15px;border-bottom:1px solid transparent;border-top-right-radius:3px;border-top-left-radius:3px}.panel-heading>.dropdown .dropdown-toggle{color:inherit}.panel-title{margin-top:0;margin-bottom:0;font-size:16px;color:inherit}.panel-title>a,.panel-title>small,.panel-title>.small,.panel-title>small>a,.panel-title>.small>a{color:inherit}.panel-footer{padding:10px 15px;background-color:#3e444c;border-top:1px solid rgba(0,0,0,0.6);border-bottom-right-radius:3px;border-bottom-left-radius:3px}.panel>.list-group,.panel>.panel-collapse>.list-group{margin-bottom:0}.panel>.list-group .list-group-item,.panel>.panel-collapse>.list-group .list-group-item{border-width:1px 0;border-radius:0}.panel>.list-group:first-child .list-group-item:first-child,.panel>.panel-collapse>.list-group:first-child .list-group-item:first-child{border-top:0;border-top-right-radius:3px;border-top-left-radius:3px}.panel>.list-group:last-child .list-group-item:last-child,.panel>.panel-collapse>.list-group:last-child .list-group-item:last-child{border-bottom:0;border-bottom-right-radius:3px;border-bottom-left-radius:3px}.panel>.panel-heading+.panel-collapse>.list-group .list-group-item:first-child{border-top-right-radius:0;border-top-left-radius:0}.panel-heading+.list-group .list-group-item:first-child{border-top-width:0}.list-group+.panel-footer{border-top-width:0}.panel>.table,.panel>.table-responsive>.table,.panel>.panel-collapse>.table{margin-bottom:0}.panel>.table caption,.panel>.table-responsive>.table caption,.panel>.panel-collapse>.table caption{padding-left:15px;padding-right:15px}.panel>.table:first-child,.panel>.table-responsive:first-child>.table:first-child{border-top-right-radius:3px;border-top-left-radius:3px}.panel>.table:first-child>thead:first-child>tr:first-child,.panel>.table-responsive:first-child>.table:first-child>thead:first-child>tr:first-child,.panel>.table:first-child>tbody:first-child>tr:first-child,.panel>.table-responsive:first-child>.table:first-child>tbody:first-child>tr:first-child{border-top-left-radius:3px;border-top-right-radius:3px}.panel>.table:first-child>thead:first-child>tr:first-child td:first-child,.panel>.table-responsive:first-child>.table:first-child>thead:first-child>tr:first-child td:first-child,.panel>.table:first-child>tbody:first-child>tr:first-child td:first-child,.panel>.table-responsive:first-child>.table:first-child>tbody:first-child>tr:first-child td:first-child,.panel>.table:first-child>thead:first-child>tr:first-child th:first-child,.panel>.table-responsive:first-child>.table:first-child>thead:first-child>tr:first-child th:first-child,.panel>.table:first-child>tbody:first-child>tr:first-child th:first-child,.panel>.table-responsive:first-child>.table:first-child>tbody:first-child>tr:first-child th:first-child{border-top-left-radius:3px}.panel>.table:first-child>thead:first-child>tr:first-child td:last-child,.panel>.table-responsive:first-child>.table:first-child>thead:first-child>tr:first-child td:last-child,.panel>.table:first-child>tbody:first-child>tr:first-child td:last-child,.panel>.table-responsive:first-child>.table:first-child>tbody:first-child>tr:first-child td:last-child,.panel>.table:first-child>thead:first-child>tr:first-child th:last-child,.panel>.table-responsive:first-child>.table:first-child>thead:first-child>tr:first-child th:last-child,.panel>.table:first-child>tbody:first-child>tr:first-child th:last-child,.panel>.table-responsive:first-child>.table:first-child>tbody:first-child>tr:first-child th:last-child{border-top-right-radius:3px}.panel>.table:last-child,.panel>.table-responsive:last-child>.table:last-child{border-bottom-right-radius:3px;border-bottom-left-radius:3px}.panel>.table:last-child>tbody:last-child>tr:last-child,.panel>.table-responsive:last-child>.table:last-child>tbody:last-child>tr:last-child,.panel>.table:last-child>tfoot:last-child>tr:last-child,.panel>.table-responsive:last-child>.table:last-child>tfoot:last-child>tr:last-child{border-bottom-left-radius:3px;border-bottom-right-radius:3px}.panel>.table:last-child>tbody:last-child>tr:last-child td:first-child,.panel>.table-responsive:last-child>.table:last-child>tbody:last-child>tr:last-child td:first-child,.panel>.table:last-child>tfoot:last-child>tr:last-child td:first-child,.panel>.table-responsive:last-child>.table:last-child>tfoot:last-child>tr:last-child td:first-child,.panel>.table:last-child>tbody:last-child>tr:last-child th:first-child,.panel>.table-responsive:last-child>.table:last-child>tbody:last-child>tr:last-child th:first-child,.panel>.table:last-child>tfoot:last-child>tr:last-child th:first-child,.panel>.table-responsive:last-child>.table:last-child>tfoot:last-child>tr:last-child th:first-child{border-bottom-left-radius:3px}.panel>.table:last-child>tbody:last-child>tr:last-child td:last-child,.panel>.table-responsive:last-child>.table:last-child>tbody:last-child>tr:last-child td:last-child,.panel>.table:last-child>tfoot:last-child>tr:last-child td:last-child,.panel>.table-responsive:last-child>.table:last-child>tfoot:last-child>tr:last-child td:last-child,.panel>.table:last-child>tbody:last-child>tr:last-child th:last-child,.panel>.table-responsive:last-child>.table:last-child>tbody:last-child>tr:last-child th:last-child,.panel>.table:last-child>tfoot:last-child>tr:last-child th:last-child,.panel>.table-responsive:last-child>.table:last-child>tfoot:last-child>tr:last-child th:last-child{border-bottom-right-radius:3px}.panel>.panel-body+.table,.panel>.panel-body+.table-responsive,.panel>.table+.panel-body,.panel>.table-responsive+.panel-body{border-top:1px solid #1c1e22}.panel>.table>tbody:first-child>tr:first-child th,.panel>.table>tbody:first-child>tr:first-child td{border-top:0}.panel>.table-bordered,.panel>.table-responsive>.table-bordered{border:0}.panel>.table-bordered>thead>tr>th:first-child,.panel>.table-responsive>.table-bordered>thead>tr>th:first-child,.panel>.table-bordered>tbody>tr>th:first-child,.panel>.table-responsive>.table-bordered>tbody>tr>th:first-child,.panel>.table-bordered>tfoot>tr>th:first-child,.panel>.table-responsive>.table-bordered>tfoot>tr>th:first-child,.panel>.table-bordered>thead>tr>td:first-child,.panel>.table-responsive>.table-bordered>thead>tr>td:first-child,.panel>.table-bordered>tbody>tr>td:first-child,.panel>.table-responsive>.table-bordered>tbody>tr>td:first-child,.panel>.table-bordered>tfoot>tr>td:first-child,.panel>.table-responsive>.table-bordered>tfoot>tr>td:first-child{border-left:0}.panel>.table-bordered>thead>tr>th:last-child,.panel>.table-responsive>.table-bordered>thead>tr>th:last-child,.panel>.table-bordered>tbody>tr>th:last-child,.panel>.table-responsive>.table-bordered>tbody>tr>th:last-child,.panel>.table-bordered>tfoot>tr>th:last-child,.panel>.table-responsive>.table-bordered>tfoot>tr>th:last-child,.panel>.table-bordered>thead>tr>td:last-child,.panel>.table-responsive>.table-bordered>thead>tr>td:last-child,.panel>.table-bordered>tbody>tr>td:last-child,.panel>.table-responsive>.table-bordered>tbody>tr>td:last-child,.panel>.table-bordered>tfoot>tr>td:last-child,.panel>.table-responsive>.table-bordered>tfoot>tr>td:last-child{border-right:0}.panel>.table-bordered>thead>tr:first-child>td,.panel>.table-responsive>.table-bordered>thead>tr:first-child>td,.panel>.table-bordered>tbody>tr:first-child>td,.panel>.table-responsive>.table-bordered>tbody>tr:first-child>td,.panel>.table-bordered>thead>tr:first-child>th,.panel>.table-responsive>.table-bordered>thead>tr:first-child>th,.panel>.table-bordered>tbody>tr:first-child>th,.panel>.table-responsive>.table-bordered>tbody>tr:first-child>th{border-bottom:0}.panel>.table-bordered>tbody>tr:last-child>td,.panel>.table-responsive>.table-bordered>tbody>tr:last-child>td,.panel>.table-bordered>tfoot>tr:last-child>td,.panel>.table-responsive>.table-bordered>tfoot>tr:last-child>td,.panel>.table-bordered>tbody>tr:last-child>th,.panel>.table-responsive>.table-bordered>tbody>tr:last-child>th,.panel>.table-bordered>tfoot>tr:last-child>th,.panel>.table-responsive>.table-bordered>tfoot>tr:last-child>th{border-bottom:0}.panel>.table-responsive{border:0;margin-bottom:0}.panel-group{margin-bottom:20px}.panel-group .panel{margin-bottom:0;border-radius:4px}.panel-group .panel+.panel{margin-top:5px}.panel-group .panel-heading{border-bottom:0}.panel-group .panel-heading+.panel-collapse>.panel-body,.panel-group .panel-heading+.panel-collapse>.list-group{border-top:1px solid rgba(0,0,0,0.6)}.panel-group .panel-footer{border-top:0}.panel-group .panel-footer+.panel-collapse .panel-body{border-bottom:1px solid rgba(0,0,0,0.6)}.panel-default{border-color:rgba(0,0,0,0.6)}.panel-default>.panel-heading{color:#c8c8c8;background-color:#3e444c;border-color:rgba(0,0,0,0.6)}.panel-default>.panel-heading+.panel-collapse>.panel-body{border-top-color:rgba(0,0,0,0.6)}.panel-default>.panel-heading .badge{color:#3e444c;background-color:#c8c8c8}.panel-default>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:rgba(0,0,0,0.6)}.panel-primary{border-color:rgba(0,0,0,0.6)}.panel-primary>.panel-heading{color:#ffffff;background-color:#7a8288;border-color:rgba(0,0,0,0.6)}.panel-primary>.panel-heading+.panel-collapse>.panel-body{border-top-color:rgba(0,0,0,0.6)}.panel-primary>.panel-heading .badge{color:#7a8288;background-color:#ffffff}.panel-primary>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:rgba(0,0,0,0.6)}.panel-success{border-color:rgba(0,0,0,0.6)}.panel-success>.panel-heading{color:#ffffff;background-color:#62c462;border-color:rgba(0,0,0,0.6)}.panel-success>.panel-heading+.panel-collapse>.panel-body{border-top-color:rgba(0,0,0,0.6)}.panel-success>.panel-heading .badge{color:#62c462;background-color:#ffffff}.panel-success>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:rgba(0,0,0,0.6)}.panel-info{border-color:rgba(0,0,0,0.6)}.panel-info>.panel-heading{color:#ffffff;background-color:#5bc0de;border-color:rgba(0,0,0,0.6)}.panel-info>.panel-heading+.panel-collapse>.panel-body{border-top-color:rgba(0,0,0,0.6)}.panel-info>.panel-heading .badge{color:#5bc0de;background-color:#ffffff}.panel-info>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:rgba(0,0,0,0.6)}.panel-warning{border-color:rgba(0,0,0,0.6)}.panel-warning>.panel-heading{color:#ffffff;background-color:#f89406;border-color:rgba(0,0,0,0.6)}.panel-warning>.panel-heading+.panel-collapse>.panel-body{border-top-color:rgba(0,0,0,0.6)}.panel-warning>.panel-heading .badge{color:#f89406;background-color:#ffffff}.panel-warning>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:rgba(0,0,0,0.6)}.panel-danger{border-color:rgba(0,0,0,0.6)}.panel-danger>.panel-heading{color:#ffffff;background-color:#ee5f5b;border-color:rgba(0,0,0,0.6)}.panel-danger>.panel-heading+.panel-collapse>.panel-body{border-top-color:rgba(0,0,0,0.6)}.panel-danger>.panel-heading .badge{color:#ee5f5b;background-color:#ffffff}.panel-danger>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:rgba(0,0,0,0.6)}.embed-responsive{position:relative;display:block;height:0;padding:0;overflow:hidden}.embed-responsive .embed-responsive-item,.embed-responsive iframe,.embed-responsive embed,.embed-responsive object,.embed-responsive video{position:absolute;top:0;left:0;bottom:0;height:100%;width:100%;border:0}.embed-responsive-16by9{padding-bottom:56.25%}.embed-responsive-4by3{padding-bottom:75%}.well{min-height:20px;padding:19px;margin-bottom:20px;background-color:#1c1e22;border:1px solid #0c0d0e;border-radius:4px;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.05);box-shadow:inset 0 1px 1px rgba(0,0,0,0.05)}.well blockquote{border-color:#ddd;border-color:rgba(0,0,0,0.15)}.well-lg{padding:24px;border-radius:6px}.well-sm{padding:9px;border-radius:3px}.close{float:right;font-size:21px;font-weight:bold;line-height:1;color:#000000;text-shadow:0 1px 0 #ffffff;opacity:0.2;filter:alpha(opacity=20)}.close:hover,.close:focus{color:#000000;text-decoration:none;cursor:pointer;opacity:0.5;filter:alpha(opacity=50)}button.close{padding:0;cursor:pointer;background:transparent;border:0;-webkit-appearance:none}.modal-open{overflow:hidden}.modal{display:none;overflow:hidden;position:fixed;top:0;right:0;bottom:0;left:0;z-index:1050;-webkit-overflow-scrolling:touch;outline:0}.modal.fade .modal-dialog{-webkit-transform:translate(0, -25%);-ms-transform:translate(0, -25%);-o-transform:translate(0, -25%);transform:translate(0, -25%);-webkit-transition:-webkit-transform .3s ease-out;-o-transition:-o-transform .3s ease-out;transition:transform .3s ease-out}.modal.in .modal-dialog{-webkit-transform:translate(0, 0);-ms-transform:translate(0, 0);-o-transform:translate(0, 0);transform:translate(0, 0)}.modal-open .modal{overflow-x:hidden;overflow-y:auto}.modal-dialog{position:relative;width:auto;margin:10px}.modal-content{position:relative;background-color:#2e3338;border:1px solid #999999;border:1px solid rgba(0,0,0,0.2);border-radius:6px;-webkit-box-shadow:0 3px 9px rgba(0,0,0,0.5);box-shadow:0 3px 9px rgba(0,0,0,0.5);-webkit-background-clip:padding-box;background-clip:padding-box;outline:0}.modal-backdrop{position:fixed;top:0;right:0;bottom:0;left:0;z-index:1040;background-color:#000000}.modal-backdrop.fade{opacity:0;filter:alpha(opacity=0)}.modal-backdrop.in{opacity:0.5;filter:alpha(opacity=50)}.modal-header{padding:15px;border-bottom:1px solid #1c1e22}.modal-header .close{margin-top:-2px}.modal-title{margin:0;line-height:1.42857143}.modal-body{position:relative;padding:20px}.modal-footer{padding:20px;text-align:right;border-top:1px solid #1c1e22}.modal-footer .btn+.btn{margin-left:5px;margin-bottom:0}.modal-footer .btn-group .btn+.btn{margin-left:-1px}.modal-footer .btn-block+.btn-block{margin-left:0}.modal-scrollbar-measure{position:absolute;top:-9999px;width:50px;height:50px;overflow:scroll}@media (min-width:768px){.modal-dialog{width:600px;margin:30px auto}.modal-content{-webkit-box-shadow:0 5px 15px rgba(0,0,0,0.5);box-shadow:0 5px 15px rgba(0,0,0,0.5)}.modal-sm{width:300px}}@media (min-width:992px){.modal-lg{width:900px}}.tooltip{position:absolute;z-index:1070;display:block;font-family:"Helvetica Neue",Helvetica,Arial,sans-serif;font-style:normal;font-weight:normal;letter-spacing:normal;line-break:auto;line-height:1.42857143;text-align:left;text-align:start;text-decoration:none;text-shadow:none;text-transform:none;white-space:normal;word-break:normal;word-spacing:normal;word-wrap:normal;font-size:12px;opacity:0;filter:alpha(opacity=0)}.tooltip.in{opacity:0.9;filter:alpha(opacity=90)}.tooltip.top{margin-top:-3px;padding:5px 0}.tooltip.right{margin-left:3px;padding:0 5px}.tooltip.bottom{margin-top:3px;padding:5px 0}.tooltip.left{margin-left:-3px;padding:0 5px}.tooltip-inner{max-width:200px;padding:3px 8px;color:#ffffff;text-align:center;background-color:#000000;border-radius:4px}.tooltip-arrow{position:absolute;width:0;height:0;border-color:transparent;border-style:solid}.tooltip.top .tooltip-arrow{bottom:0;left:50%;margin-left:-5px;border-width:5px 5px 0;border-top-color:#000000}.tooltip.top-left .tooltip-arrow{bottom:0;right:5px;margin-bottom:-5px;border-width:5px 5px 0;border-top-color:#000000}.tooltip.top-right .tooltip-arrow{bottom:0;left:5px;margin-bottom:-5px;border-width:5px 5px 0;border-top-color:#000000}.tooltip.right .tooltip-arrow{top:50%;left:0;margin-top:-5px;border-width:5px 5px 5px 0;border-right-color:#000000}.tooltip.left .tooltip-arrow{top:50%;right:0;margin-top:-5px;border-width:5px 0 5px 5px;border-left-color:#000000}.tooltip.bottom .tooltip-arrow{top:0;left:50%;margin-left:-5px;border-width:0 5px 5px;border-bottom-color:#000000}.tooltip.bottom-left .tooltip-arrow{top:0;right:5px;margin-top:-5px;border-width:0 5px 5px;border-bottom-color:#000000}.tooltip.bottom-right .tooltip-arrow{top:0;left:5px;margin-top:-5px;border-width:0 5px 5px;border-bottom-color:#000000}.popover{position:absolute;top:0;left:0;z-index:1060;display:none;max-width:276px;padding:1px;font-family:"Helvetica Neue",Helvetica,Arial,sans-serif;font-style:normal;font-weight:normal;letter-spacing:normal;line-break:auto;line-height:1.42857143;text-align:left;text-align:start;text-decoration:none;text-shadow:none;text-transform:none;white-space:normal;word-break:normal;word-spacing:normal;word-wrap:normal;font-size:14px;background-color:#2e3338;-webkit-background-clip:padding-box;background-clip:padding-box;border:1px solid #999999;border:1px solid rgba(0,0,0,0.2);border-radius:6px;-webkit-box-shadow:0 5px 10px rgba(0,0,0,0.2);box-shadow:0 5px 10px rgba(0,0,0,0.2)}.popover.top{margin-top:-10px}.popover.right{margin-left:10px}.popover.bottom{margin-top:10px}.popover.left{margin-left:-10px}.popover-title{margin:0;padding:8px 14px;font-size:14px;background-color:#2e3338;border-bottom:1px solid #22262a;border-radius:5px 5px 0 0}.popover-content{padding:9px 14px}.popover>.arrow,.popover>.arrow:after{position:absolute;display:block;width:0;height:0;border-color:transparent;border-style:solid}.popover>.arrow{border-width:11px}.popover>.arrow:after{border-width:10px;content:""}.popover.top>.arrow{left:50%;margin-left:-11px;border-bottom-width:0;border-top-color:#666666;border-top-color:rgba(0,0,0,0.25);bottom:-11px}.popover.top>.arrow:after{content:" ";bottom:1px;margin-left:-10px;border-bottom-width:0;border-top-color:#2e3338}.popover.right>.arrow{top:50%;left:-11px;margin-top:-11px;border-left-width:0;border-right-color:#666666;border-right-color:rgba(0,0,0,0.25)}.popover.right>.arrow:after{content:" ";left:1px;bottom:-10px;border-left-width:0;border-right-color:#2e3338}.popover.bottom>.arrow{left:50%;margin-left:-11px;border-top-width:0;border-bottom-color:#666666;border-bottom-color:rgba(0,0,0,0.25);top:-11px}.popover.bottom>.arrow:after{content:" ";top:1px;margin-left:-10px;border-top-width:0;border-bottom-color:#2e3338}.popover.left>.arrow{top:50%;right:-11px;margin-top:-11px;border-right-width:0;border-left-color:#666666;border-left-color:rgba(0,0,0,0.25)}.popover.left>.arrow:after{content:" ";right:1px;border-right-width:0;border-left-color:#2e3338;bottom:-10px}.carousel{position:relative}.carousel-inner{position:relative;overflow:hidden;width:100%}.carousel-inner>.item{display:none;position:relative;-webkit-transition:.6s ease-in-out left;-o-transition:.6s ease-in-out left;transition:.6s ease-in-out left}.carousel-inner>.item>img,.carousel-inner>.item>a>img{line-height:1}@media all and (transform-3d),(-webkit-transform-3d){.carousel-inner>.item{-webkit-transition:-webkit-transform .6s ease-in-out;-o-transition:-o-transform .6s ease-in-out;transition:transform .6s ease-in-out;-webkit-backface-visibility:hidden;backface-visibility:hidden;-webkit-perspective:1000px;perspective:1000px}.carousel-inner>.item.next,.carousel-inner>.item.active.right{-webkit-transform:translate3d(100%, 0, 0);transform:translate3d(100%, 0, 0);left:0}.carousel-inner>.item.prev,.carousel-inner>.item.active.left{-webkit-transform:translate3d(-100%, 0, 0);transform:translate3d(-100%, 0, 0);left:0}.carousel-inner>.item.next.left,.carousel-inner>.item.prev.right,.carousel-inner>.item.active{-webkit-transform:translate3d(0, 0, 0);transform:translate3d(0, 0, 0);left:0}}.carousel-inner>.active,.carousel-inner>.next,.carousel-inner>.prev{display:block}.carousel-inner>.active{left:0}.carousel-inner>.next,.carousel-inner>.prev{position:absolute;top:0;width:100%}.carousel-inner>.next{left:100%}.carousel-inner>.prev{left:-100%}.carousel-inner>.next.left,.carousel-inner>.prev.right{left:0}.carousel-inner>.active.left{left:-100%}.carousel-inner>.active.right{left:100%}.carousel-control{position:absolute;top:0;left:0;bottom:0;width:15%;opacity:0.5;filter:alpha(opacity=50);font-size:20px;color:#ffffff;text-align:center;text-shadow:0 1px 2px rgba(0,0,0,0.6);background-color:rgba(0,0,0,0)}.carousel-control.left{background-image:-webkit-linear-gradient(left, rgba(0,0,0,0.5) 0, rgba(0,0,0,0.0001) 100%);background-image:-o-linear-gradient(left, rgba(0,0,0,0.5) 0, rgba(0,0,0,0.0001) 100%);background-image:-webkit-gradient(linear, left top, right top, from(rgba(0,0,0,0.5)), to(rgba(0,0,0,0.0001)));background-image:linear-gradient(to right, rgba(0,0,0,0.5) 0, rgba(0,0,0,0.0001) 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#80000000', endColorstr='#00000000', GradientType=1)}.carousel-control.right{left:auto;right:0;background-image:-webkit-linear-gradient(left, rgba(0,0,0,0.0001) 0, rgba(0,0,0,0.5) 100%);background-image:-o-linear-gradient(left, rgba(0,0,0,0.0001) 0, rgba(0,0,0,0.5) 100%);background-image:-webkit-gradient(linear, left top, right top, from(rgba(0,0,0,0.0001)), to(rgba(0,0,0,0.5)));background-image:linear-gradient(to right, rgba(0,0,0,0.0001) 0, rgba(0,0,0,0.5) 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#00000000', endColorstr='#80000000', GradientType=1)}.carousel-control:hover,.carousel-control:focus{outline:0;color:#ffffff;text-decoration:none;opacity:0.9;filter:alpha(opacity=90)}.carousel-control .icon-prev,.carousel-control .icon-next,.carousel-control .glyphicon-chevron-left,.carousel-control .glyphicon-chevron-right{position:absolute;top:50%;margin-top:-10px;z-index:5;display:inline-block}.carousel-control .icon-prev,.carousel-control .glyphicon-chevron-left{left:50%;margin-left:-10px}.carousel-control .icon-next,.carousel-control .glyphicon-chevron-right{right:50%;margin-right:-10px}.carousel-control .icon-prev,.carousel-control .icon-next{width:20px;height:20px;line-height:1;font-family:serif}.carousel-control .icon-prev:before{content:'\2039'}.carousel-control .icon-next:before{content:'\203a'}.carousel-indicators{position:absolute;bottom:10px;left:50%;z-index:15;width:60%;margin-left:-30%;padding-left:0;list-style:none;text-align:center}.carousel-indicators li{display:inline-block;width:10px;height:10px;margin:1px;text-indent:-999px;border:1px solid #ffffff;border-radius:10px;cursor:pointer;background-color:#000 \9;background-color:rgba(0,0,0,0)}.carousel-indicators .active{margin:0;width:12px;height:12px;background-color:#ffffff}.carousel-caption{position:absolute;left:15%;right:15%;bottom:20px;z-index:10;padding-top:20px;padding-bottom:20px;color:#ffffff;text-align:center;text-shadow:0 1px 2px rgba(0,0,0,0.6)}.carousel-caption .btn{text-shadow:none}@media screen and (min-width:768px){.carousel-control .glyphicon-chevron-left,.carousel-control .glyphicon-chevron-right,.carousel-control .icon-prev,.carousel-control .icon-next{width:30px;height:30px;margin-top:-10px;font-size:30px}.carousel-control .glyphicon-chevron-left,.carousel-control .icon-prev{margin-left:-10px}.carousel-control .glyphicon-chevron-right,.carousel-control .icon-next{margin-right:-10px}.carousel-caption{left:20%;right:20%;padding-bottom:30px}.carousel-indicators{bottom:20px}}.clearfix:before,.clearfix:after,.dl-horizontal dd:before,.dl-horizontal dd:after,.container:before,.container:after,.container-fluid:before,.container-fluid:after,.row:before,.row:after,.form-horizontal .form-group:before,.form-horizontal .form-group:after,.btn-toolbar:before,.btn-toolbar:after,.btn-group-vertical>.btn-group:before,.btn-group-vertical>.btn-group:after,.nav:before,.nav:after,.navbar:before,.navbar:after,.navbar-header:before,.navbar-header:after,.navbar-collapse:before,.navbar-collapse:after,.pager:before,.pager:after,.panel-body:before,.panel-body:after,.modal-header:before,.modal-header:after,.modal-footer:before,.modal-footer:after{content:" ";display:table}.clearfix:after,.dl-horizontal dd:after,.container:after,.container-fluid:after,.row:after,.form-horizontal .form-group:after,.btn-toolbar:after,.btn-group-vertical>.btn-group:after,.nav:after,.navbar:after,.navbar-header:after,.navbar-collapse:after,.pager:after,.panel-body:after,.modal-header:after,.modal-footer:after{clear:both}.center-block{display:block;margin-left:auto;margin-right:auto}.pull-right{float:right !important}.pull-left{float:left !important}.hide{display:none !important}.show{display:block !important}.invisible{visibility:hidden}.text-hide{font:0/0 a;color:transparent;text-shadow:none;background-color:transparent;border:0}.hidden{display:none !important}.affix{position:fixed}@-ms-viewport{width:device-width}.visible-xs,.visible-sm,.visible-md,.visible-lg{display:none !important}.visible-xs-block,.visible-xs-inline,.visible-xs-inline-block,.visible-sm-block,.visible-sm-inline,.visible-sm-inline-block,.visible-md-block,.visible-md-inline,.visible-md-inline-block,.visible-lg-block,.visible-lg-inline,.visible-lg-inline-block{display:none !important}@media (max-width:767px){.visible-xs{display:block !important}table.visible-xs{display:table !important}tr.visible-xs{display:table-row !important}th.visible-xs,td.visible-xs{display:table-cell !important}}@media (max-width:767px){.visible-xs-block{display:block !important}}@media (max-width:767px){.visible-xs-inline{display:inline !important}}@media (max-width:767px){.visible-xs-inline-block{display:inline-block !important}}@media (min-width:768px) and (max-width:991px){.visible-sm{display:block !important}table.visible-sm{display:table !important}tr.visible-sm{display:table-row !important}th.visible-sm,td.visible-sm{display:table-cell !important}}@media (min-width:768px) and (max-width:991px){.visible-sm-block{display:block !important}}@media (min-width:768px) and (max-width:991px){.visible-sm-inline{display:inline !important}}@media (min-width:768px) and (max-width:991px){.visible-sm-inline-block{display:inline-block !important}}@media (min-width:992px) and (max-width:1199px){.visible-md{display:block !important}table.visible-md{display:table !important}tr.visible-md{display:table-row !important}th.visible-md,td.visible-md{display:table-cell !important}}@media (min-width:992px) and (max-width:1199px){.visible-md-block{display:block !important}}@media (min-width:992px) and (max-width:1199px){.visible-md-inline{display:inline !important}}@media (min-width:992px) and (max-width:1199px){.visible-md-inline-block{display:inline-block !important}}@media (min-width:1200px){.visible-lg{display:block !important}table.visible-lg{display:table !important}tr.visible-lg{display:table-row !important}th.visible-lg,td.visible-lg{display:table-cell !important}}@media (min-width:1200px){.visible-lg-block{display:block !important}}@media (min-width:1200px){.visible-lg-inline{display:inline !important}}@media (min-width:1200px){.visible-lg-inline-block{display:inline-block !important}}@media (max-width:767px){.hidden-xs{display:none !important}}@media (min-width:768px) and (max-width:991px){.hidden-sm{display:none !important}}@media (min-width:992px) and (max-width:1199px){.hidden-md{display:none !important}}@media (min-width:1200px){.hidden-lg{display:none !important}}.visible-print{display:none !important}@media print{.visible-print{display:block !important}table.visible-print{display:table !important}tr.visible-print{display:table-row !important}th.visible-print,td.visible-print{display:table-cell !important}}.visible-print-block{display:none !important}@media print{.visible-print-block{display:block !important}}.visible-print-inline{display:none !important}@media print{.visible-print-inline{display:inline !important}}.visible-print-inline-block{display:none !important}@media print{.visible-print-inline-block{display:inline-block !important}}@media print{.hidden-print{display:none !important}}.navbar-default,.navbar-inverse{border:1px solid rgba(0,0,0,0.6);text-shadow:1px 1px 1px rgba(0,0,0,0.3)}@media (min-width:768px){.navbar-default .navbar-nav>li>a,.navbar-inverse .navbar-nav>li>a{border-right:1px solid rgba(0,0,0,0.2);border-left:1px solid rgba(255,255,255,0.1)}.navbar-default .navbar-nav>li>a:hover,.navbar-inverse .navbar-nav>li>a:hover{border-left-color:transparent}.navbar-default .nav .open>a,.navbar-inverse .nav .open>a{border-color:transparent}.navbar-default .navbar-nav>li.active>a,.navbar-inverse .navbar-nav>li.active>a{border-left-color:transparent}.navbar-default .navbar-form,.navbar-inverse .navbar-form{margin-left:5px;margin-right:5px}}.navbar-default{background-image:-webkit-linear-gradient(#484e55, #3a3f44 60%, #313539);background-image:-o-linear-gradient(#484e55, #3a3f44 60%, #313539);background-image:-webkit-gradient(linear, left top, left bottom, from(#484e55), color-stop(60%, #3a3f44), to(#313539));background-image:linear-gradient(#484e55, #3a3f44 60%, #313539);background-repeat:no-repeat;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff484e55', endColorstr='#ff313539', GradientType=0);-webkit-filter:none;filter:none}.navbar-default .navbar-nav>li>a:hover{background-image:-webkit-linear-gradient(#020202, #101112 40%, #141618);background-image:-o-linear-gradient(#020202, #101112 40%, #141618);background-image:-webkit-gradient(linear, left top, left bottom, from(#020202), color-stop(40%, #101112), to(#141618));background-image:linear-gradient(#020202, #101112 40%, #141618);background-repeat:no-repeat;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff020202', endColorstr='#ff141618', GradientType=0);-webkit-filter:none;filter:none}.navbar-inverse{background-image:-webkit-linear-gradient(#8a9196, #7a8288 60%, #70787d);background-image:-o-linear-gradient(#8a9196, #7a8288 60%, #70787d);background-image:-webkit-gradient(linear, left top, left bottom, from(#8a9196), color-stop(60%, #7a8288), to(#70787d));background-image:linear-gradient(#8a9196, #7a8288 60%, #70787d);background-repeat:no-repeat;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff8a9196', endColorstr='#ff70787d', GradientType=0);-webkit-filter:none;filter:none}.navbar-inverse .badge{background-color:#5d6368}.navbar-inverse .navbar-nav>li>a:hover{background-image:-webkit-linear-gradient(#404448, #4e5458 40%, #53595d);background-image:-o-linear-gradient(#404448, #4e5458 40%, #53595d);background-image:-webkit-gradient(linear, left top, left bottom, from(#404448), color-stop(40%, #4e5458), to(#53595d));background-image:linear-gradient(#404448, #4e5458 40%, #53595d);background-repeat:no-repeat;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff404448', endColorstr='#ff53595d', GradientType=0);-webkit-filter:none;filter:none}.btn,.btn:hover{border-color:rgba(0,0,0,0.6);text-shadow:1px 1px 1px rgba(0,0,0,0.3)}.btn-default{background-image:-webkit-linear-gradient(#484e55, #3a3f44 60%, #313539);background-image:-o-linear-gradient(#484e55, #3a3f44 60%, #313539);background-image:-webkit-gradient(linear, left top, left bottom, from(#484e55), color-stop(60%, #3a3f44), to(#313539));background-image:linear-gradient(#484e55, #3a3f44 60%, #313539);background-repeat:no-repeat;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff484e55', endColorstr='#ff313539', GradientType=0);-webkit-filter:none;filter:none}.btn-default:hover{background-image:-webkit-linear-gradient(#020202, #101112 40%, #141618);background-image:-o-linear-gradient(#020202, #101112 40%, #141618);background-image:-webkit-gradient(linear, left top, left bottom, from(#020202), color-stop(40%, #101112), to(#141618));background-image:linear-gradient(#020202, #101112 40%, #141618);background-repeat:no-repeat;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff020202', endColorstr='#ff141618', GradientType=0);-webkit-filter:none;filter:none}.btn-primary{background-image:-webkit-linear-gradient(#8a9196, #7a8288 60%, #70787d);background-image:-o-linear-gradient(#8a9196, #7a8288 60%, #70787d);background-image:-webkit-gradient(linear, left top, left bottom, from(#8a9196), color-stop(60%, #7a8288), to(#70787d));background-image:linear-gradient(#8a9196, #7a8288 60%, #70787d);background-repeat:no-repeat;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff8a9196', endColorstr='#ff70787d', GradientType=0);-webkit-filter:none;filter:none}.btn-primary:hover{background-image:-webkit-linear-gradient(#404448, #4e5458 40%, #53595d);background-image:-o-linear-gradient(#404448, #4e5458 40%, #53595d);background-image:-webkit-gradient(linear, left top, left bottom, from(#404448), color-stop(40%, #4e5458), to(#53595d));background-image:linear-gradient(#404448, #4e5458 40%, #53595d);background-repeat:no-repeat;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff404448', endColorstr='#ff53595d', GradientType=0);-webkit-filter:none;filter:none}.btn-success{background-image:-webkit-linear-gradient(#78cc78, #62c462 60%, #53be53);background-image:-o-linear-gradient(#78cc78, #62c462 60%, #53be53);background-image:-webkit-gradient(linear, left top, left bottom, from(#78cc78), color-stop(60%, #62c462), to(#53be53));background-image:linear-gradient(#78cc78, #62c462 60%, #53be53);background-repeat:no-repeat;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff78cc78', endColorstr='#ff53be53', GradientType=0);-webkit-filter:none;filter:none}.btn-success:hover{background-image:-webkit-linear-gradient(#2f7d2f, #379337 40%, #3a9a3a);background-image:-o-linear-gradient(#2f7d2f, #379337 40%, #3a9a3a);background-image:-webkit-gradient(linear, left top, left bottom, from(#2f7d2f), color-stop(40%, #379337), to(#3a9a3a));background-image:linear-gradient(#2f7d2f, #379337 40%, #3a9a3a);background-repeat:no-repeat;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff2f7d2f', endColorstr='#ff3a9a3a', GradientType=0);-webkit-filter:none;filter:none}.btn-info{background-image:-webkit-linear-gradient(#74cae3, #5bc0de 60%, #4ab9db);background-image:-o-linear-gradient(#74cae3, #5bc0de 60%, #4ab9db);background-image:-webkit-gradient(linear, left top, left bottom, from(#74cae3), color-stop(60%, #5bc0de), to(#4ab9db));background-image:linear-gradient(#74cae3, #5bc0de 60%, #4ab9db);background-repeat:no-repeat;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff74cae3', endColorstr='#ff4ab9db', GradientType=0);-webkit-filter:none;filter:none}.btn-info:hover{background-image:-webkit-linear-gradient(#20829f, #2596b8 40%, #279dc1);background-image:-o-linear-gradient(#20829f, #2596b8 40%, #279dc1);background-image:-webkit-gradient(linear, left top, left bottom, from(#20829f), color-stop(40%, #2596b8), to(#279dc1));background-image:linear-gradient(#20829f, #2596b8 40%, #279dc1);background-repeat:no-repeat;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff20829f', endColorstr='#ff279dc1', GradientType=0);-webkit-filter:none;filter:none}.btn-warning{background-image:-webkit-linear-gradient(#faa123, #f89406 60%, #e48806);background-image:-o-linear-gradient(#faa123, #f89406 60%, #e48806);background-image:-webkit-gradient(linear, left top, left bottom, from(#faa123), color-stop(60%, #f89406), to(#e48806));background-image:linear-gradient(#faa123, #f89406 60%, #e48806);background-repeat:no-repeat;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fffaa123', endColorstr='#ffe48806', GradientType=0);-webkit-filter:none;filter:none}.btn-warning:hover{background-image:-webkit-linear-gradient(#804d03, #9e5f04 40%, #a86404);background-image:-o-linear-gradient(#804d03, #9e5f04 40%, #a86404);background-image:-webkit-gradient(linear, left top, left bottom, from(#804d03), color-stop(40%, #9e5f04), to(#a86404));background-image:linear-gradient(#804d03, #9e5f04 40%, #a86404);background-repeat:no-repeat;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff804d03', endColorstr='#ffa86404', GradientType=0);-webkit-filter:none;filter:none}.btn-danger{background-image:-webkit-linear-gradient(#f17a77, #ee5f5b 60%, #ec4d49);background-image:-o-linear-gradient(#f17a77, #ee5f5b 60%, #ec4d49);background-image:-webkit-gradient(linear, left top, left bottom, from(#f17a77), color-stop(60%, #ee5f5b), to(#ec4d49));background-image:linear-gradient(#f17a77, #ee5f5b 60%, #ec4d49);background-repeat:no-repeat;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff17a77', endColorstr='#ffec4d49', GradientType=0);-webkit-filter:none;filter:none}.btn-danger:hover{background-image:-webkit-linear-gradient(#bb1813, #d71c16 40%, #e01d17);background-image:-o-linear-gradient(#bb1813, #d71c16 40%, #e01d17);background-image:-webkit-gradient(linear, left top, left bottom, from(#bb1813), color-stop(40%, #d71c16), to(#e01d17));background-image:linear-gradient(#bb1813, #d71c16 40%, #e01d17);background-repeat:no-repeat;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffbb1813', endColorstr='#ffe01d17', GradientType=0);-webkit-filter:none;filter:none}.btn-link,.btn-link:hover{border-color:transparent}h1,h2,h3,h4,h5,h6{text-shadow:-1px -1px 0 rgba(0,0,0,0.3)}.text-primary,.text-primary:hover{color:#7a8288}.text-success,.text-success:hover{color:#62c462}.text-danger,.text-danger:hover{color:#ee5f5b}.text-warning,.text-warning:hover{color:#f89406}.text-info,.text-info:hover{color:#5bc0de}.table .success,.table .warning,.table .danger,.table .info{color:#fff}.table-bordered tbody tr.success td,.table-bordered tbody tr.warning td,.table-bordered tbody tr.danger td,.table-bordered tbody tr.success:hover td,.table-bordered tbody tr.warning:hover td,.table-bordered tbody tr.danger:hover td{border-color:#1c1e22}.table-responsive>.table{background-color:#2e3338}input,textarea{color:#272b30}.has-warning .help-block,.has-warning .control-label,.has-warning .radio,.has-warning .checkbox,.has-warning .radio-inline,.has-warning .checkbox-inline,.has-warning.radio label,.has-warning.checkbox label,.has-warning.radio-inline label,.has-warning.checkbox-inline label,.has-warning .form-control-feedback{color:#f89406}.has-warning .form-control,.has-warning .form-control:focus{border-color:#f89406}.has-warning .input-group-addon{border-color:rgba(0,0,0,0.6)}.has-error .help-block,.has-error .control-label,.has-error .radio,.has-error .checkbox,.has-error .radio-inline,.has-error .checkbox-inline,.has-error.radio label,.has-error.checkbox label,.has-error.radio-inline label,.has-error.checkbox-inline label,.has-error .form-control-feedback{color:#ee5f5b}.has-error .form-control,.has-error .form-control:focus{border-color:#ee5f5b}.has-error .input-group-addon{border-color:rgba(0,0,0,0.6)}.has-success .help-block,.has-success .control-label,.has-success .radio,.has-success .checkbox,.has-success .radio-inline,.has-success .checkbox-inline,.has-success.radio label,.has-success.checkbox label,.has-success.radio-inline label,.has-success.checkbox-inline label,.has-success .form-control-feedback{color:#62c462}.has-success .form-control,.has-success .form-control:focus{border-color:#62c462}.has-success .input-group-addon{border-color:rgba(0,0,0,0.6)}legend{color:#fff}.input-group-addon{background-image:-webkit-linear-gradient(#484e55, #3a3f44 60%, #313539);background-image:-o-linear-gradient(#484e55, #3a3f44 60%, #313539);background-image:-webkit-gradient(linear, left top, left bottom, from(#484e55), color-stop(60%, #3a3f44), to(#313539));background-image:linear-gradient(#484e55, #3a3f44 60%, #313539);background-repeat:no-repeat;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff484e55', endColorstr='#ff313539', GradientType=0);-webkit-filter:none;filter:none;text-shadow:1px 1px 1px rgba(0,0,0,0.3);color:#ffffff}.nav .open>a,.nav .open>a:hover,.nav .open>a:focus{border-color:rgba(0,0,0,0.6)}.nav-pills>li>a{background-image:-webkit-linear-gradient(#484e55, #3a3f44 60%, #313539);background-image:-o-linear-gradient(#484e55, #3a3f44 60%, #313539);background-image:-webkit-gradient(linear, left top, left bottom, from(#484e55), color-stop(60%, #3a3f44), to(#313539));background-image:linear-gradient(#484e55, #3a3f44 60%, #313539);background-repeat:no-repeat;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff484e55', endColorstr='#ff313539', GradientType=0);-webkit-filter:none;filter:none;border:1px solid rgba(0,0,0,0.6);text-shadow:1px 1px 1px rgba(0,0,0,0.3)}.nav-pills>li>a:hover{background-image:-webkit-linear-gradient(#020202, #101112 40%, #141618);background-image:-o-linear-gradient(#020202, #101112 40%, #141618);background-image:-webkit-gradient(linear, left top, left bottom, from(#020202), color-stop(40%, #101112), to(#141618));background-image:linear-gradient(#020202, #101112 40%, #141618);background-repeat:no-repeat;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff020202', endColorstr='#ff141618', GradientType=0);-webkit-filter:none;filter:none;border:1px solid rgba(0,0,0,0.6)}.nav-pills>li.active>a,.nav-pills>li.active>a:hover{background-color:none;background-image:-webkit-linear-gradient(#020202, #101112 40%, #141618);background-image:-o-linear-gradient(#020202, #101112 40%, #141618);background-image:-webkit-gradient(linear, left top, left bottom, from(#020202), color-stop(40%, #101112), to(#141618));background-image:linear-gradient(#020202, #101112 40%, #141618);background-repeat:no-repeat;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff020202', endColorstr='#ff141618', GradientType=0);-webkit-filter:none;filter:none;border:1px solid rgba(0,0,0,0.6)}.nav-pills>li.disabled>a,.nav-pills>li.disabled>a:hover{background-image:-webkit-linear-gradient(#484e55, #3a3f44 60%, #313539);background-image:-o-linear-gradient(#484e55, #3a3f44 60%, #313539);background-image:-webkit-gradient(linear, left top, left bottom, from(#484e55), color-stop(60%, #3a3f44), to(#313539));background-image:linear-gradient(#484e55, #3a3f44 60%, #313539);background-repeat:no-repeat;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff484e55', endColorstr='#ff313539', GradientType=0);-webkit-filter:none;filter:none}.pagination>li>a,.pagination>li>span{text-shadow:1px 1px 1px rgba(0,0,0,0.3);background-image:-webkit-linear-gradient(#484e55, #3a3f44 60%, #313539);background-image:-o-linear-gradient(#484e55, #3a3f44 60%, #313539);background-image:-webkit-gradient(linear, left top, left bottom, from(#484e55), color-stop(60%, #3a3f44), to(#313539));background-image:linear-gradient(#484e55, #3a3f44 60%, #313539);background-repeat:no-repeat;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff484e55', endColorstr='#ff313539', GradientType=0);-webkit-filter:none;filter:none}.pagination>li>a:hover,.pagination>li>span:hover{background-image:-webkit-linear-gradient(#020202, #101112 40%, #141618);background-image:-o-linear-gradient(#020202, #101112 40%, #141618);background-image:-webkit-gradient(linear, left top, left bottom, from(#020202), color-stop(40%, #101112), to(#141618));background-image:linear-gradient(#020202, #101112 40%, #141618);background-repeat:no-repeat;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff020202', endColorstr='#ff141618', GradientType=0);-webkit-filter:none;filter:none}.pagination>li.active>a,.pagination>li.active>span{background-image:-webkit-linear-gradient(#020202, #101112 40%, #141618);background-image:-o-linear-gradient(#020202, #101112 40%, #141618);background-image:-webkit-gradient(linear, left top, left bottom, from(#020202), color-stop(40%, #101112), to(#141618));background-image:linear-gradient(#020202, #101112 40%, #141618);background-repeat:no-repeat;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff020202', endColorstr='#ff141618', GradientType=0);-webkit-filter:none;filter:none}.pagination>li.disabled>a,.pagination>li.disabled>a:hover,.pagination>li.disabled>span,.pagination>li.disabled>span:hover{background-color:transparent;background-image:-webkit-linear-gradient(#484e55, #3a3f44 60%, #313539);background-image:-o-linear-gradient(#484e55, #3a3f44 60%, #313539);background-image:-webkit-gradient(linear, left top, left bottom, from(#484e55), color-stop(60%, #3a3f44), to(#313539));background-image:linear-gradient(#484e55, #3a3f44 60%, #313539);background-repeat:no-repeat;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff484e55', endColorstr='#ff313539', GradientType=0);-webkit-filter:none;filter:none}.pager>li>a{background-image:-webkit-linear-gradient(#484e55, #3a3f44 60%, #313539);background-image:-o-linear-gradient(#484e55, #3a3f44 60%, #313539);background-image:-webkit-gradient(linear, left top, left bottom, from(#484e55), color-stop(60%, #3a3f44), to(#313539));background-image:linear-gradient(#484e55, #3a3f44 60%, #313539);background-repeat:no-repeat;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff484e55', endColorstr='#ff313539', GradientType=0);-webkit-filter:none;filter:none;text-shadow:1px 1px 1px rgba(0,0,0,0.3)}.pager>li>a:hover{background-image:-webkit-linear-gradient(#020202, #101112 40%, #141618);background-image:-o-linear-gradient(#020202, #101112 40%, #141618);background-image:-webkit-gradient(linear, left top, left bottom, from(#020202), color-stop(40%, #101112), to(#141618));background-image:linear-gradient(#020202, #101112 40%, #141618);background-repeat:no-repeat;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff020202', endColorstr='#ff141618', GradientType=0);-webkit-filter:none;filter:none}.pager>li.disabled>a,.pager>li.disabled>a:hover{background-color:transparent;background-image:-webkit-linear-gradient(#484e55, #3a3f44 60%, #313539);background-image:-o-linear-gradient(#484e55, #3a3f44 60%, #313539);background-image:-webkit-gradient(linear, left top, left bottom, from(#484e55), color-stop(60%, #3a3f44), to(#313539));background-image:linear-gradient(#484e55, #3a3f44 60%, #313539);background-repeat:no-repeat;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff484e55', endColorstr='#ff313539', GradientType=0);-webkit-filter:none;filter:none}.breadcrumb{border:1px solid rgba(0,0,0,0.6);text-shadow:1px 1px 1px rgba(0,0,0,0.3);background-image:-webkit-linear-gradient(#484e55, #3a3f44 60%, #313539);background-image:-o-linear-gradient(#484e55, #3a3f44 60%, #313539);background-image:-webkit-gradient(linear, left top, left bottom, from(#484e55), color-stop(60%, #3a3f44), to(#313539));background-image:linear-gradient(#484e55, #3a3f44 60%, #313539);background-repeat:no-repeat;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff484e55', endColorstr='#ff313539', GradientType=0);-webkit-filter:none;filter:none}.alert .alert-link,.alert a{color:#fff;text-decoration:underline}.alert .close{color:#000000;text-decoration:none}a.thumbnail:hover,a.thumbnail:focus,a.thumbnail.active{border-color:#0c0d0e}a.list-group-item.active,a.list-group-item.active:hover,a.list-group-item.active:focus{border-color:rgba(0,0,0,0.6)}a.list-group-item-success.active{background-color:#62c462}a.list-group-item-success.active:hover,a.list-group-item-success.active:focus{background-color:#4fbd4f}a.list-group-item-warning.active{background-color:#f89406}a.list-group-item-warning.active:hover,a.list-group-item-warning.active:focus{background-color:#df8505}a.list-group-item-danger.active{background-color:#ee5f5b}a.list-group-item-danger.active:hover,a.list-group-item-danger.active:focus{background-color:#ec4844}.jumbotron{border:1px solid rgba(0,0,0,0.6)}.panel-primary .panel-heading,.panel-success .panel-heading,.panel-danger .panel-heading,.panel-warning .panel-heading,.panel-info .panel-heading{border-color:#000} \ No newline at end of file diff --git a/frontend/static/css/fic.css b/frontend/static/css/fic.css new file mode 100644 index 00000000..4ca6a5de --- /dev/null +++ b/frontend/static/css/fic.css @@ -0,0 +1,115 @@ +body { + overflow-y: scroll; +} + +.text-bold { + font-weight: bolder; +} + +.navbar { + margin-bottom: 0; +} +.navbar img { + margin: 3px auto; + height: 100px; +} +.navbar #clock { + font-size: 70px; + text-align: center; +} +.point, .expired { + transition: color text-shadow 1s; + position: relative; + animation: clockanim 1s ease infinite; + -moz-animation: clockanim 1s ease infinite; + -webkit-animation: clockanim 1s ease infinite; +} +.end { + color: #A94442; +} +.point { + text-shadow: 0 0 20px #0055ff; +} +.end .point { + text-shadow: 0 0 20px #ff5500; +} +@-webkit-keyframes clockanim { + 0% { opacity: 1.0; } + 50% { opacity: 0; } + 100% { opacity: 1.0; }; +} +@-moz-keyframes clockanim { + 0% { opacity: 1.0; } + 50% { opacity: 0; } + 100% { opacity: 1.0; }; +} +keyframes clockanim { + 0% { opacity: 1.0; } + 50% { opacity: 0; } + 100% { opacity: 1.0; }; +} + +.well { + text-align: justify; +} + +.samp { + overflow-x: auto; + text-overflow: ellipsis; +} + +h1 small.authors { + float: right; + font-style: italic; + font-size: 42%; +} +.lead small.authors { + color: #7a8288; + font-style: italic; +} + +.teamname { + padding: 2px 7px; + border-radius: 2px; + box-shadow: #444 0 0 3px; +} +.teamname span { + -webkit-filter: invert(100%); + filter: invert(100%); +} + +.heading { + font-style: italic; + margin-top: -7px; + text-align: right; +} + +.repeated-item.ng-enter, +.repeated-item.ng-leave { + transition-duration: 1s; +} + +.repeated-item.ng-enter { + opacity: 0; + transform: translateY(-1000px); +} + +.repeated-item.ng-enter.ng-enter-active { + opacity: 1; + transform: translateY(0px); +} + +.repeated-item.ng-leave.ng-leave-active { + opacity: 0; +} +.col-sm-8 .repeated-item.ng-leave.ng-leave-active { + transform: translateX(-800px); +} +.col-sm-4 .repeated-item.ng-leave.ng-leave-active { + transform: translateX(800px); +} + +.repeated-item.ng-enter-stagger { + transition-delay: 0.7s; + transition-duration: 0s; +} diff --git a/frontend/static/e404.html b/frontend/static/e404.html new file mode 100644 index 00000000..45fde4f8 --- /dev/null +++ b/frontend/static/e404.html @@ -0,0 +1,59 @@ + + + + + Challenge Forensic + + + + + + + + + + + + + + + + +
    +
    +
    + cat not found! +
    +

    Page introuvable Erreur 404

    +

    + La page à laquelle vous tentez d'accéder n'existe pas ou l'adresse que vous avez tapée est incorrecte. +

    +

    + Si le problème persiste, contactez un administrateur. +

    +
    +
    + + + diff --git a/frontend/static/e413.html b/frontend/static/e413.html new file mode 100644 index 00000000..2901911e --- /dev/null +++ b/frontend/static/e413.html @@ -0,0 +1,56 @@ + + + + + Challenge Forensic + + + + + + + + + + + + + + + + +
    +
    +
    + cat not found! +
    +

    Requête trop grosse Erreur 413

    +

    + La quantité de données que vous souhaitez envoyer au serveur est trop importante pour qu'il accepte de la traiter. +

    +
    +
    + + + diff --git a/frontend/static/e500.html b/frontend/static/e500.html new file mode 100644 index 00000000..90417f5b --- /dev/null +++ b/frontend/static/e500.html @@ -0,0 +1,59 @@ + + + + + Challenge Forensic + + + + + + + + + + + + + + + + +
    +
    +
    + cat not found! +
    +

    Erreur interne Erreur 500

    +

    + Notre serveur est actuellement dans l'incapacité de repondre à votre requête.
    Veuillez recommencer dans quelques instants. +

    +

    + Si le problème persiste, contactez un administrateur. +

    +
    +
    + + + diff --git a/frontend/static/favicon.ico b/frontend/static/favicon.ico new file mode 100644 index 00000000..810dc39f Binary files /dev/null and b/frontend/static/favicon.ico differ diff --git a/frontend/static/fonts/glyphicons-halflings-regular.eot b/frontend/static/fonts/glyphicons-halflings-regular.eot new file mode 100644 index 00000000..b93a4953 Binary files /dev/null and b/frontend/static/fonts/glyphicons-halflings-regular.eot differ diff --git a/frontend/static/fonts/glyphicons-halflings-regular.svg b/frontend/static/fonts/glyphicons-halflings-regular.svg new file mode 100644 index 00000000..94fb5490 --- /dev/null +++ b/frontend/static/fonts/glyphicons-halflings-regular.svg @@ -0,0 +1,288 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/frontend/static/fonts/glyphicons-halflings-regular.ttf b/frontend/static/fonts/glyphicons-halflings-regular.ttf new file mode 100644 index 00000000..1413fc60 Binary files /dev/null and b/frontend/static/fonts/glyphicons-halflings-regular.ttf differ diff --git a/frontend/static/fonts/glyphicons-halflings-regular.woff b/frontend/static/fonts/glyphicons-halflings-regular.woff new file mode 100644 index 00000000..9e612858 Binary files /dev/null and b/frontend/static/fonts/glyphicons-halflings-regular.woff differ diff --git a/frontend/static/fonts/glyphicons-halflings-regular.woff2 b/frontend/static/fonts/glyphicons-halflings-regular.woff2 new file mode 100644 index 00000000..64539b54 Binary files /dev/null and b/frontend/static/fonts/glyphicons-halflings-regular.woff2 differ diff --git a/frontend/static/img/epita.png b/frontend/static/img/epita.png new file mode 100644 index 00000000..e89f7181 Binary files /dev/null and b/frontend/static/img/epita.png differ diff --git a/frontend/static/img/fic.png b/frontend/static/img/fic.png new file mode 100644 index 00000000..7956bd4d Binary files /dev/null and b/frontend/static/img/fic.png differ diff --git a/frontend/static/img/srs.png b/frontend/static/img/srs.png new file mode 100644 index 00000000..82294f52 Binary files /dev/null and b/frontend/static/img/srs.png differ diff --git a/frontend/static/index.html b/frontend/static/index.html new file mode 100644 index 00000000..aea8f2ca --- /dev/null +++ b/frontend/static/index.html @@ -0,0 +1,121 @@ + + + + + Challenge Forensic + + + + + + + + + + + + + + + + + + + + +
    + +
    +
    + +
    +
    + +
    + +
    +
    +
    + {{ my.name }} + voir

    + + {{ teams[my.team_id].rank }}e sur {{ teams_count }} – + {{ my.score }} points + classement +
    +
    + + + + + Epita + +
    + +
    +
    + +
    +
    + + + + + + > + + + diff --git a/frontend/static/js/angular-animate.min.js b/frontend/static/js/angular-animate.min.js new file mode 100644 index 00000000..fdbd3c1b --- /dev/null +++ b/frontend/static/js/angular-animate.min.js @@ -0,0 +1,55 @@ +/* + AngularJS v1.4.9 + (c) 2010-2015 Google, Inc. http://angularjs.org + License: MIT +*/ +(function(E,l,Va){'use strict';function xa(a,b,c){if(!a)throw Ka("areq",b||"?",c||"required");return a}function ya(a,b){if(!a&&!b)return"";if(!a)return b;if(!b)return a;aa(a)&&(a=a.join(" "));aa(b)&&(b=b.join(" "));return a+" "+b}function La(a){var b={};a&&(a.to||a.from)&&(b.to=a.to,b.from=a.from);return b}function W(a,b,c){var d="";a=aa(a)?a:a&&R(a)&&a.length?a.split(/\s+/):[];u(a,function(a,m){a&&0=a&&(a=k,k=0,b.push(f),f=[]);f.push(t.fn);t.children.forEach(function(a){k++;c.push(a)});a--}f.length&&b.push(f);return b}(c)}var h=[],l=T(a);return function(x,z,v){function k(a){a=a.hasAttribute("ng-animate-ref")?[a]:a.querySelectorAll("[ng-animate-ref]");var b=[];u(a,function(a){var c=a.getAttribute("ng-animate-ref");c&&c.length&&b.push(a)});return b}function N(a){var b=[],c={};u(a,function(a,g){var d=F(a.element),H=0<=["enter","move"].indexOf(a.event), +d=a.structural?k(d):[];if(d.length){var f=H?"to":"from";u(d,function(a){var b=a.getAttribute("ng-animate-ref");c[b]=c[b]||{};c[b][f]={animationID:g,element:I(a)}})}else b.push(a)});var g={},d={};u(c,function(c,f){var k=c.from,w=c.to;if(k&&w){var B=a[k.animationID],t=a[w.animationID],A=k.animationID.toString();if(!d[A]){var h=d[A]={structural:!0,beforeStart:function(){B.beforeStart();t.beforeStart()},close:function(){B.close();t.close()},classes:y(B.classes,t.classes),from:B,to:t,anchors:[]};h.classes.length? +b.push(h):(b.push(B),b.push(t))}d[A].anchors.push({out:k.element,"in":w.element})}else k=k?k.animationID:w.animationID,w=k.toString(),g[w]||(g[w]=!0,b.push(a[k]))});return b}function y(a,b){a=a.split(" ");b=b.split(" ");for(var c=[],d=0;d=O&&b>=K&&(ha=!0,r())}function H(){function b(){if(!l){A(!1);u(y,function(a){g.style[a[0]]=a[1]});k(a,e);f.addClass(a,ba);if(p.recalculateTimingStyles){ka= +g.className+" "+ca;fa=Ia(g,ka);C=v(g,ka,fa);Z=C.maxDelay;n=Math.max(Z,0);K=C.maxDuration;if(0===K){r();return}p.hasTransitions=0s.expectedEndTime)?J.cancel(s.timer):h.push(r)}H&&(t=J(c,t,!1),h[0]={timer:t,expectedEndTime:d},h.push(r),a.data("$$animateCss",h));if(da.length)a.on(da.join(" "),B);e.to&&(e.cleanupStyles&&Fa(G,g,Object.keys(e.to)),Aa(a,e))}}function c(){var b=a.data("$$animateCss");if(b){for(var d=1;d=c;d--)f.end&&f.end(e[d]);e.length=c}}"string"!==typeof a&&(a=null===a||"undefined"===typeof a?"":""+a);var b,k,e=[],m=a,l;for(e.last=function(){return e[e.length-1]};a;){l="";k=!0;if(e.last()&&w[e.last()])a=a.replace(new RegExp("([\\W\\w]*)<\\s*\\/\\s*"+e.last()+"[^>]*>","i"),function(a,b){b=b.replace(H,"$1").replace(I,"$1");f.chars&&f.chars(q(b));return""}),c("",e.last());else{if(0===a.indexOf("\x3c!--"))b=a.indexOf("--",4),0<=b&&a.lastIndexOf("--\x3e", +b)===b&&(f.comment&&f.comment(a.substring(4,b)),a=a.substring(b+3),k=!1);else if(x.test(a)){if(b=a.match(x))a=a.replace(b[0],""),k=!1}else if(J.test(a)){if(b=a.match(y))a=a.substring(b[0].length),b[0].replace(y,c),k=!1}else K.test(a)&&((b=a.match(z))?(b[4]&&(a=a.substring(b[0].length),b[0].replace(z,d)),k=!1):(l+="<",a=a.substring(1)));k&&(b=a.indexOf("<"),l+=0>b?a:a.substring(0,b),a=0>b?"":a.substring(b),f.chars&&f.chars(q(l)))}if(a==m)throw L("badparse",a);m=a}c()}function q(a){if(!a)return"";A.innerHTML= +a.replace(//g,">")}function r(a,f){var d=!1,c=h.bind(a,a.push);return{start:function(a,k,e){a=h.lowercase(a);!d&&w[a]&&(d=a);d||!0!==C[a]||(c("<"),c(a),h.forEach(k,function(d,e){var k=h.lowercase(e),g="img"===a&&"src"===k|| +"background"===k;!0!==O[k]||!0===D[k]&&!f(d,g)||(c(" "),c(e),c('="'),c(B(d)),c('"'))}),c(e?"/>":">"))},end:function(a){a=h.lowercase(a);d||!0!==C[a]||(c(""));a==d&&(d=!1)},chars:function(a){d||c(B(a))}}}var L=h.$$minErr("$sanitize"),z=/^<((?:[a-zA-Z])[\w:-]*)((?:\s+[\w:-]+(?:\s*=\s*(?:(?:"[^"]*")|(?:'[^']*')|[^>\s]+))?)*)\s*(\/?)\s*(>?)/,y=/^<\/\s*([\w:-]+)[^>]*>/,G=/([\w:-]+)(?:\s*=\s*(?:(?:"((?:[^"])*)")|(?:'((?:[^'])*)')|([^>\s]+)))?/g,K=/^]*?)>/i, +I=/"\u201d\u2019]/i,d=/^mailto:/i;return function(c,b){function k(a){a&&g.push(E(a))}function e(a, +c){g.push("');k(c);g.push("")}if(!c)return c;for(var m,l=c,g=[],n,p;m=l.match(f);)n=m[0],m[2]||m[4]||(n=(m[3]?"http://":"mailto:")+n),p=m.index,k(l.substr(0,p)),e(n,m[0].replace(d,"")),l=l.substring(p+m[0].length);k(l);return a(g.join(""))}}])})(window,window.angular); +//# sourceMappingURL=angular-sanitize.min.js.map diff --git a/frontend/static/js/angular.min.js b/frontend/static/js/angular.min.js new file mode 100644 index 00000000..91af11c8 --- /dev/null +++ b/frontend/static/js/angular.min.js @@ -0,0 +1,298 @@ +/* + AngularJS v1.4.9 + (c) 2010-2015 Google, Inc. http://angularjs.org + License: MIT +*/ +(function(S,W,w){'use strict';function M(a){return function(){var b=arguments[0],d;d="["+(a?a+":":"")+b+"] http://errors.angularjs.org/1.4.9/"+(a?a+"/":"")+b;for(b=1;b").append(a).html();try{return a[0].nodeType===Na?K(d):d.match(/^(<[^>]+>)/)[1].replace(/^<([\w\-]+)/,function(a,b){return"<"+K(b)})}catch(c){return K(d)}}function xc(a){try{return decodeURIComponent(a)}catch(b){}} +function yc(a){var b={};n((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=xc(e),u(e)&&(f=u(f)?xc(f):!0,ra.call(b,e)?E(b[e])?b[e].push(f):b[e]=[b[e],f]:b[e]=f))});return b}function Sb(a){var b=[];n(a,function(a,c){E(a)?n(a,function(a){b.push(ia(c,!0)+(!0===a?"":"="+ia(a,!0)))}):b.push(ia(c,!0)+(!0===a?"":"="+ia(a,!0)))});return b.length?b.join("&"):""}function pb(a){return ia(a,!0).replace(/%26/gi,"&").replace(/%3D/gi, +"=").replace(/%2B/gi,"+")}function ia(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 be(a,b){var d,c,e=Oa.length;for(c=0;c/,">"));}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=db(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!/;S&&e.test(S.name)&&(d.debugInfoEnabled=!0,S.name=S.name.replace(e,""));if(S&&!f.test(S.name))return c();S.name=S.name.replace(f,"");$.resumeBootstrap=function(a){n(a,function(a){b.push(a)});return c()};B($.resumeDeferredBootstrap)&&$.resumeDeferredBootstrap()}function de(){S.name="NG_ENABLE_DEBUG_INFO!"+S.name;S.location.reload()} +function ee(a){a=$.element(a).injector();if(!a)throw Ba("test");return a.get("$$testability")}function Ac(a,b){b=b||"_";return a.replace(fe,function(a,c){return(c?b:"")+a.toLowerCase()})}function ge(){var a;if(!Bc){var b=qb();(pa=q(b)?S.jQuery:b?S[b]:w)&&pa.fn.on?(A=pa,N(pa.fn,{scope:Pa.scope,isolateScope:Pa.isolateScope,controller:Pa.controller,injector:Pa.injector,inheritedData:Pa.inheritedData}),a=pa.cleanData,pa.cleanData=function(b){var c;if(Tb)Tb=!1;else for(var e=0,f;null!=(f=b[e]);e++)(c= +pa._data(f,"events"))&&c.$destroy&&pa(f).triggerHandler("$destroy");a(b)}):A=P;$.element=A;Bc=!0}}function rb(a,b,d){if(!a)throw Ba("areq",b||"?",d||"required");return a}function Qa(a,b,d){d&&E(a)&&(a=a[a.length-1]);rb(B(a),b,"not a function, got "+(a&&"object"===typeof a?a.constructor.name||"Object":typeof a));return a}function Ra(a,b){if("hasOwnProperty"===a)throw Ba("badname",b);}function Cc(a,b,d){if(!b)return a;b=b.split(".");for(var c,e=a,f=b.length,g=0;g")+c[2];for(c=c[0];c--;)d=d.lastChild;f=bb(f,d.childNodes);d=e.firstChild;d.textContent=""}else f.push(b.createTextNode(a));e.textContent="";e.innerHTML="";n(f,function(a){e.appendChild(a)});return e}function P(a){if(a instanceof +P)return a;var b;F(a)&&(a=T(a),b=!0);if(!(this instanceof P)){if(b&&"<"!=a.charAt(0))throw Wb("nosel");return new P(a)}if(b){b=W;var d;a=(d=Kf.exec(a))?[b.createElement(d[1])]:(d=Mc(a,b))?d.childNodes:[]}Nc(this,a)}function Xb(a){return a.cloneNode(!0)}function vb(a,b){b||wb(a);if(a.querySelectorAll)for(var d=a.querySelectorAll("*"),c=0,e=d.length;cl&&this.remove(t.key);return b}},get:function(a){if(l").parent()[0])});var f=L(a,b,a,c,d,e);D.$$addScopeClass(a);var g=null;return function(b,c,d){rb(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"!==oa(d)&&d.toString().match(/SVG/)?"svg":"html":"html");d="html"!==g?A(Q(g,A("
    ").append(a).html())):c?Pa.clone.call(a):a;if(k)for(var l in k)d.data("$"+l+"Controller",k[l].instance);D.$$addScopeInfo(d,b);c&&c(d,b);f&&f(b,d,d,h);return d}}function L(a,b,c,d,e,f){function g(a,c,d,e){var f,k,l,m,t,v,I;if(p)for(I=Array(c.length),m=0;mq.priority)break;if(J=q.scope)q.templateUrl||(G(J)?(Ua("new/isolated scope",O||C,q,u),O=q):Ua("new/isolated scope",O,q,u)),C=C||q;z=q.name;!q.templateUrl&&q.controller&&(J=q.controller,R=R||ea(),Ua("'"+z+"' controller",R[z],q,u),R[z]=q);if(J=q.transclude)H=!0,q.$$tlb||(Ua("transclusion",Y,q,u),Y=q),"element"==J?(hb=!0,L=q.priority,J=u,u=d.$$element=A(W.createComment(" "+z+": "+d[z]+" ")),b=u[0],V(f,sa.call(J,0),b),ib=D(J,e,L,g&&g.name, +{nonTlbTranscludeDirective:Y})):(J=A(Xb(b)).contents(),u.empty(),ib=D(J,e,w,w,{needsNewScope:q.$$isolateScope||q.$$newScope}));if(q.template)if(la=!0,Ua("template",n,q,u),n=q,J=B(q.template)?q.template(u,d):q.template,J=ha(J),q.replace){g=q;J=Vb.test(J)?Zc(Q(q.templateNamespace,T(J))):[];b=J[0];if(1!=J.length||1!==b.nodeType)throw ga("tplrt",z,"");V(f,u,b);J={$attr:{}};var Eb=X(b,[],J),$=a.splice(fa+1,a.length-(fa+1));(O||C)&&Wc(Eb,O,C);a=a.concat(Eb).concat($);M(d,J);K=a.length}else u.html(J);if(q.templateUrl)la= +!0,Ua("template",n,q,u),n=q,q.replace&&(g=q),I=S(a.splice(fa,a.length-fa),u,d,f,H&&ib,h,l,{controllerDirectives:R,newScopeDirective:C!==q&&C,newIsolateScopeDirective:O,templateDirective:n,nonTlbTranscludeDirective:Y}),K=a.length;else if(q.compile)try{wa=q.compile(u,d,ib),B(wa)?t(null,wa,N,P):wa&&t(wa.pre,wa.post,N,P)}catch(da){c(da,ua(u))}q.terminal&&(I.terminal=!0,L=Math.max(L,q.priority))}I.scope=C&&!0===C.scope;I.transcludeOnThisElement=H;I.templateOnThisElement=la;I.transclude=ib;m.hasElementTranscludeDirective= +hb;return I}function Wc(a,b,c){for(var d=0,e=a.length;dm.priority)&&-1!=m.restrict.indexOf(f)&&(k&&(m=Qb(m,{$$start:k,$$end:l})),b.push(m),h=m)}catch(I){c(I)}}return h}function fa(b){if(e.hasOwnProperty(b))for(var c=a.get(b+"Directive"),d=0,f=c.length;d"+b+"";return c.childNodes[0].childNodes;default:return b}}function Eb(a,b){if("srcdoc"==b)return Y.HTML;var c=oa(a);if("xlinkHref"==b||"form"==c&&"action"==b||"img"!=c&&("src"==b||"ngSrc"==b))return Y.RESOURCE_URL}function P(a,c,d,e,f){var g=Eb(a,e);f=h[e]||f;var k=b(d,!0,g,f);if(k){if("multiple"===e&&"select"===oa(a))throw ga("selmulti",ua(a));c.push({priority:100,compile:function(){return{pre:function(a,c,h){c=h.$$observers||(h.$$observers=ea());if(l.test(e))throw ga("nodomevents");var m=h[e];m!== +d&&(k=m&&b(m,!0,g,f),d=m);k&&(h[e]=k(a),(c[e]||(c[e]=[])).$$inter=!0,(h.$$observers&&h.$$observers[e].$$scope||a).$watch(k,function(a,b){"class"===e&&a!=b?h.$updateClass(a,b):h.$set(e,a)}))}}}})}}function V(a,b,c){var d=b[0],e=b.length,f=d.parentNode,g,h;if(a)for(g=0,h=a.length;g=b)return a;for(;b--;)8===a[b].nodeType&&Uf.call(a,b,1);return a}function cf(){var a={},b=!1;this.register=function(b,c){Ra(b,"controller");G(b)?N(a,b):a[b]=c};this.allowGlobals=function(){b=!0};this.$get=["$injector","$window",function(d,c){function e(a,b,c,d){if(!a||!G(a.$scope))throw M("$controller")("noscp", +d,b);a.$scope[b]=c}return function(f,g,h,k){var l,m,r;h=!0===h;k&&F(k)&&(r=k);if(F(f)){k=f.match(Vc);if(!k)throw Vf("ctrlfmt",f);m=k[1];r=r||k[3];f=a.hasOwnProperty(m)?a[m]:Cc(g.$scope,m,!0)||(b?Cc(c,m,!0):w);Qa(f,m,!0)}if(h)return h=(E(f)?f[f.length-1]:f).prototype,l=Object.create(h||null),r&&e(g,r,l,m||f.name),N(function(){var a=d.invoke(f,l,g,m);a!==l&&(G(a)||B(a))&&(l=a,r&&e(g,r,l,m||f.name));return l},{instance:l,identifier:r});l=d.instantiate(f,g,m);r&&e(g,r,l,m||f.name);return l}}]}function df(){this.$get= +["$window",function(a){return A(a.document)}]}function ef(){this.$get=["$log",function(a){return function(b,d){a.error.apply(a,arguments)}}]}function $b(a){return G(a)?da(a)?a.toISOString():cb(a):a}function kf(){this.$get=function(){return function(a){if(!a)return"";var b=[];pc(a,function(a,c){null===a||q(a)||(E(a)?n(a,function(a,d){b.push(ia(c)+"="+ia($b(a)))}):b.push(ia(c)+"="+ia($b(a))))});return b.join("&")}}}function lf(){this.$get=function(){return function(a){function b(a,e,f){null===a||q(a)|| +(E(a)?n(a,function(a,c){b(a,e+"["+(G(a)?c:"")+"]")}):G(a)&&!da(a)?pc(a,function(a,c){b(a,e+(f?"":"[")+c+(f?"":"]"))}):d.push(ia(e)+"="+ia($b(a))))}if(!a)return"";var d=[];b(a,"",!0);return d.join("&")}}}function ac(a,b){if(F(a)){var d=a.replace(Wf,"").trim();if(d){var c=b("Content-Type");(c=c&&0===c.indexOf(bd))||(c=(c=d.match(Xf))&&Yf[c[0]].test(d));c&&(a=vc(d))}}return a}function cd(a){var b=ea(),d;F(a)?n(a.split("\n"),function(a){d=a.indexOf(":");var e=K(T(a.substr(0,d)));a=T(a.substr(d+1));e&& +(b[e]=b[e]?b[e]+", "+a:a)}):G(a)&&n(a,function(a,d){var f=K(d),g=T(a);f&&(b[f]=b[f]?b[f]+", "+g:g)});return b}function dd(a){var b;return function(d){b||(b=cd(a));return d?(d=b[K(d)],void 0===d&&(d=null),d):b}}function ed(a,b,d,c){if(B(c))return c(a,b,d);n(c,function(c){a=c(a,b,d)});return a}function jf(){var a=this.defaults={transformResponse:[ac],transformRequest:[function(a){return G(a)&&"[object File]"!==ta.call(a)&&"[object Blob]"!==ta.call(a)&&"[object FormData]"!==ta.call(a)?cb(a):a}],headers:{common:{Accept:"application/json, text/plain, */*"}, +post:ha(bc),put:ha(bc),patch:ha(bc)},xsrfCookieName:"XSRF-TOKEN",xsrfHeaderName:"X-XSRF-TOKEN",paramSerializer:"$httpParamSerializer"},b=!1;this.useApplyAsync=function(a){return u(a)?(b=!!a,this):b};var d=!0;this.useLegacyPromiseExtensions=function(a){return u(a)?(d=!!a,this):d};var c=this.interceptors=[];this.$get=["$httpBackend","$$cookieReader","$cacheFactory","$rootScope","$q","$injector",function(e,f,g,h,k,l){function m(b){function c(a){var b=N({},a);b.data=ed(a.data,a.headers,a.status,f.transformResponse); +a=a.status;return 200<=a&&300>a?b:k.reject(b)}function e(a,b){var c,d={};n(a,function(a,e){B(a)?(c=a(b),null!=c&&(d[e]=c)):d[e]=a});return d}if(!$.isObject(b))throw M("$http")("badreq",b);if(!F(b.url))throw M("$http")("badreq",b.url);var f=N({method:"get",transformRequest:a.transformRequest,transformResponse:a.transformResponse,paramSerializer:a.paramSerializer},b);f.headers=function(b){var c=a.headers,d=N({},b.headers),f,g,h,c=N({},c.common,c[K(b.method)]);a:for(f in c){g=K(f);for(h in d)if(K(h)=== +g)continue a;d[f]=c[f]}return e(d,ha(b))}(b);f.method=tb(f.method);f.paramSerializer=F(f.paramSerializer)?l.get(f.paramSerializer):f.paramSerializer;var g=[function(b){var d=b.headers,e=ed(b.data,dd(d),w,b.transformRequest);q(e)&&n(d,function(a,b){"content-type"===K(b)&&delete d[b]});q(b.withCredentials)&&!q(a.withCredentials)&&(b.withCredentials=a.withCredentials);return r(b,e).then(c,c)},w],h=k.when(f);for(n(y,function(a){(a.request||a.requestError)&&g.unshift(a.request,a.requestError);(a.response|| +a.responseError)&&g.push(a.response,a.responseError)});g.length;){b=g.shift();var m=g.shift(),h=h.then(b,m)}d?(h.success=function(a){Qa(a,"fn");h.then(function(b){a(b.data,b.status,b.headers,f)});return h},h.error=function(a){Qa(a,"fn");h.then(null,function(b){a(b.data,b.status,b.headers,f)});return h}):(h.success=fd("success"),h.error=fd("error"));return h}function r(c,d){function g(a,c,d,e){function f(){l(c,a,d,e)}D&&(200<=a&&300>a?D.put(X,[a,c,cd(d),e]):D.remove(X));b?h.$applyAsync(f):(f(),h.$$phase|| +h.$apply())}function l(a,b,d,e){b=-1<=b?b:0;(200<=b&&300>b?n.resolve:n.reject)({data:a,status:b,headers:dd(d),config:c,statusText:e})}function r(a){l(a.data,a.status,ha(a.headers()),a.statusText)}function y(){var a=m.pendingRequests.indexOf(c);-1!==a&&m.pendingRequests.splice(a,1)}var n=k.defer(),I=n.promise,D,L,O=c.headers,X=t(c.url,c.paramSerializer(c.params));m.pendingRequests.push(c);I.then(y,y);!c.cache&&!a.cache||!1===c.cache||"GET"!==c.method&&"JSONP"!==c.method||(D=G(c.cache)?c.cache:G(a.cache)? +a.cache:C);D&&(L=D.get(X),u(L)?L&&B(L.then)?L.then(r,r):E(L)?l(L[1],L[0],ha(L[2]),L[3]):l(L,200,{},"OK"):D.put(X,I));q(L)&&((L=gd(c.url)?f()[c.xsrfCookieName||a.xsrfCookieName]:w)&&(O[c.xsrfHeaderName||a.xsrfHeaderName]=L),e(c.method,X,d,g,O,c.timeout,c.withCredentials,c.responseType));return I}function t(a,b){0=k&&(p.resolve(y),C(x.$$intervalId),delete f[x.$$intervalId]);n||a.$apply()},h);f[x.$$intervalId]=p;return x}var f={};e.cancel=function(a){return a&&a.$$intervalId in f?(f[a.$$intervalId].reject("canceled"),b.clearInterval(a.$$intervalId),delete f[a.$$intervalId],!0):!1};return e}]}function cc(a){a=a.split("/");for(var b=a.length;b--;)a[b]=pb(a[b]);return a.join("/")}function hd(a,b){var d= +xa(a);b.$$protocol=d.protocol;b.$$host=d.hostname;b.$$port=Z(d.port)||$f[d.protocol]||null}function id(a,b){var d="/"!==a.charAt(0);d&&(a="/"+a);var c=xa(a);b.$$path=decodeURIComponent(d&&"/"===c.pathname.charAt(0)?c.pathname.substring(1):c.pathname);b.$$search=yc(c.search);b.$$hash=decodeURIComponent(c.hash);b.$$path&&"/"!=b.$$path.charAt(0)&&(b.$$path="/"+b.$$path)}function qa(a,b){if(0===b.indexOf(a))return b.substr(a.length)}function Ga(a){var b=a.indexOf("#");return-1==b?a:a.substr(0,b)}function jb(a){return a.replace(/(#.+)|#$/, +"$1")}function dc(a,b,d){this.$$html5=!0;d=d||"";hd(a,this);this.$$parse=function(a){var d=qa(b,a);if(!F(d))throw Fb("ipthprfx",a,b);id(d,this);this.$$path||(this.$$path="/");this.$$compose()};this.$$compose=function(){var a=Sb(this.$$search),d=this.$$hash?"#"+pb(this.$$hash):"";this.$$url=cc(this.$$path)+(a?"?"+a:"")+d;this.$$absUrl=b+this.$$url.substr(1)};this.$$parseLinkUrl=function(c,e){if(e&&"#"===e[0])return this.hash(e.slice(1)),!0;var f,g;u(f=qa(a,c))?(g=f,g=u(f=qa(d,f))?b+(qa("/",f)||f): +a+g):u(f=qa(b,c))?g=b+f:b==c+"/"&&(g=b);g&&this.$$parse(g);return!!g}}function ec(a,b,d){hd(a,this);this.$$parse=function(c){var e=qa(a,c)||qa(b,c),f;q(e)||"#"!==e.charAt(0)?this.$$html5?f=e:(f="",q(e)&&(a=c,this.replace())):(f=qa(d,e),q(f)&&(f=e));id(f,this);c=this.$$path;var e=a,g=/^\/[A-Z]:(\/.*)/;0===f.indexOf(e)&&(f=f.replace(e,""));g.exec(f)||(c=(f=g.exec(c))?f[1]:c);this.$$path=c;this.$$compose()};this.$$compose=function(){var b=Sb(this.$$search),e=this.$$hash?"#"+pb(this.$$hash):"";this.$$url= +cc(this.$$path)+(b?"?"+b:"")+e;this.$$absUrl=a+(this.$$url?d+this.$$url:"")};this.$$parseLinkUrl=function(b,d){return Ga(a)==Ga(b)?(this.$$parse(b),!0):!1}}function jd(a,b,d){this.$$html5=!0;ec.apply(this,arguments);this.$$parseLinkUrl=function(c,e){if(e&&"#"===e[0])return this.hash(e.slice(1)),!0;var f,g;a==Ga(c)?f=c:(g=qa(b,c))?f=a+d+g:b===c+"/"&&(f=b);f&&this.$$parse(f);return!!f};this.$$compose=function(){var b=Sb(this.$$search),e=this.$$hash?"#"+pb(this.$$hash):"";this.$$url=cc(this.$$path)+ +(b?"?"+b:"")+e;this.$$absUrl=a+d+this.$$url}}function Gb(a){return function(){return this[a]}}function kd(a,b){return function(d){if(q(d))return this[a];this[a]=b(d);this.$$compose();return this}}function of(){var a="",b={enabled:!1,requireBase:!0,rewriteLinks:!0};this.hashPrefix=function(b){return u(b)?(a=b,this):a};this.html5Mode=function(a){return $a(a)?(b.enabled=a,this):G(a)?($a(a.enabled)&&(b.enabled=a.enabled),$a(a.requireBase)&&(b.requireBase=a.requireBase),$a(a.rewriteLinks)&&(b.rewriteLinks= +a.rewriteLinks),this):b};this.$get=["$rootScope","$browser","$sniffer","$rootElement","$window",function(d,c,e,f,g){function h(a,b,d){var e=l.url(),f=l.$$state;try{c.url(a,b,d),l.$$state=c.state()}catch(g){throw l.url(e),l.$$state=f,g;}}function k(a,b){d.$broadcast("$locationChangeSuccess",l.absUrl(),a,l.$$state,b)}var l,m;m=c.baseHref();var r=c.url(),t;if(b.enabled){if(!m&&b.requireBase)throw Fb("nobase");t=r.substring(0,r.indexOf("/",r.indexOf("//")+2))+(m||"/");m=e.history?dc:jd}else t=Ga(r),m= +ec;var C=t.substr(0,Ga(t).lastIndexOf("/")+1);l=new m(t,C,"#"+a);l.$$parseLinkUrl(r,r);l.$$state=c.state();var y=/^\s*(javascript|mailto):/i;f.on("click",function(a){if(b.rewriteLinks&&!a.ctrlKey&&!a.metaKey&&!a.shiftKey&&2!=a.which&&2!=a.button){for(var e=A(a.target);"a"!==oa(e[0]);)if(e[0]===f[0]||!(e=e.parent())[0])return;var h=e.prop("href"),k=e.attr("href")||e.attr("xlink:href");G(h)&&"[object SVGAnimatedString]"===h.toString()&&(h=xa(h.animVal).href);y.test(h)||!h||e.attr("target")||a.isDefaultPrevented()|| +!l.$$parseLinkUrl(h,k)||(a.preventDefault(),l.absUrl()!=c.url()&&(d.$apply(),g.angular["ff-684208-preventDefault"]=!0))}});jb(l.absUrl())!=jb(r)&&c.url(l.absUrl(),!0);var n=!0;c.onUrlChange(function(a,b){q(qa(C,a))?g.location.href=a:(d.$evalAsync(function(){var c=l.absUrl(),e=l.$$state,f;a=jb(a);l.$$parse(a);l.$$state=b;f=d.$broadcast("$locationChangeStart",a,c,b,e).defaultPrevented;l.absUrl()===a&&(f?(l.$$parse(c),l.$$state=e,h(c,!1,e)):(n=!1,k(c,e)))}),d.$$phase||d.$digest())});d.$watch(function(){var a= +jb(c.url()),b=jb(l.absUrl()),f=c.state(),g=l.$$replace,m=a!==b||l.$$html5&&e.history&&f!==l.$$state;if(n||m)n=!1,d.$evalAsync(function(){var b=l.absUrl(),c=d.$broadcast("$locationChangeStart",b,a,l.$$state,f).defaultPrevented;l.absUrl()===b&&(c?(l.$$parse(a),l.$$state=f):(m&&h(b,g,f===l.$$state?null:l.$$state),k(a,f)))});l.$$replace=!1});return l}]}function pf(){var a=!0,b=this;this.debugEnabled=function(b){return u(b)?(a=b,this):a};this.$get=["$window",function(d){function c(a){a instanceof Error&& +(a.stack?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||z;a=!1;try{a=!!e.apply}catch(k){}return a?function(){var a=[];n(arguments,function(b){a.push(c(b))});return e.apply(b,a)}:function(a,b){e(a,null==b?"":b)}}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 Va(a,b){if("__defineGetter__"===a||"__defineSetter__"===a||"__lookupGetter__"===a||"__lookupSetter__"===a||"__proto__"===a)throw aa("isecfld",b);return a}function ld(a,b){a+="";if(!F(a))throw aa("iseccst",b);return a}function ya(a,b){if(a){if(a.constructor===a)throw aa("isecfn",b);if(a.window===a)throw aa("isecwindow",b);if(a.children&&(a.nodeName||a.prop&&a.attr&&a.find))throw aa("isecdom",b);if(a===Object)throw aa("isecobj",b);}return a}function md(a,b){if(a){if(a.constructor===a)throw aa("isecfn", +b);if(a===ag||a===bg||a===cg)throw aa("isecff",b);}}function nd(a,b){if(a&&(a===(0).constructor||a===(!1).constructor||a==="".constructor||a==={}.constructor||a===[].constructor||a===Function.constructor))throw aa("isecaf",b);}function dg(a,b){return"undefined"!==typeof a?a:b}function od(a,b){return"undefined"===typeof a?b:"undefined"===typeof b?a:a+b}function V(a,b){var d,c;switch(a.type){case s.Program:d=!0;n(a.body,function(a){V(a.expression,b);d=d&&a.expression.constant});a.constant=d;break;case s.Literal:a.constant= +!0;a.toWatch=[];break;case s.UnaryExpression:V(a.argument,b);a.constant=a.argument.constant;a.toWatch=a.argument.toWatch;break;case s.BinaryExpression:V(a.left,b);V(a.right,b);a.constant=a.left.constant&&a.right.constant;a.toWatch=a.left.toWatch.concat(a.right.toWatch);break;case s.LogicalExpression:V(a.left,b);V(a.right,b);a.constant=a.left.constant&&a.right.constant;a.toWatch=a.constant?[]:[a];break;case s.ConditionalExpression:V(a.test,b);V(a.alternate,b);V(a.consequent,b);a.constant=a.test.constant&& +a.alternate.constant&&a.consequent.constant;a.toWatch=a.constant?[]:[a];break;case s.Identifier:a.constant=!1;a.toWatch=[a];break;case s.MemberExpression:V(a.object,b);a.computed&&V(a.property,b);a.constant=a.object.constant&&(!a.computed||a.property.constant);a.toWatch=[a];break;case s.CallExpression:d=a.filter?!b(a.callee.name).$stateful:!1;c=[];n(a.arguments,function(a){V(a,b);d=d&&a.constant;a.constant||c.push.apply(c,a.toWatch)});a.constant=d;a.toWatch=a.filter&&!b(a.callee.name).$stateful?c: +[a];break;case s.AssignmentExpression:V(a.left,b);V(a.right,b);a.constant=a.left.constant&&a.right.constant;a.toWatch=[a];break;case s.ArrayExpression:d=!0;c=[];n(a.elements,function(a){V(a,b);d=d&&a.constant;a.constant||c.push.apply(c,a.toWatch)});a.constant=d;a.toWatch=c;break;case s.ObjectExpression:d=!0;c=[];n(a.properties,function(a){V(a.value,b);d=d&&a.value.constant;a.value.constant||c.push.apply(c,a.value.toWatch)});a.constant=d;a.toWatch=c;break;case s.ThisExpression:a.constant=!1,a.toWatch= +[]}}function pd(a){if(1==a.length){a=a[0].expression;var b=a.toWatch;return 1!==b.length?b:b[0]!==a?b:w}}function qd(a){return a.type===s.Identifier||a.type===s.MemberExpression}function rd(a){if(1===a.body.length&&qd(a.body[0].expression))return{type:s.AssignmentExpression,left:a.body[0].expression,right:{type:s.NGValueParameter},operator:"="}}function sd(a){return 0===a.body.length||1===a.body.length&&(a.body[0].expression.type===s.Literal||a.body[0].expression.type===s.ArrayExpression||a.body[0].expression.type=== +s.ObjectExpression)}function td(a,b){this.astBuilder=a;this.$filter=b}function ud(a,b){this.astBuilder=a;this.$filter=b}function Hb(a){return"constructor"==a}function fc(a){return B(a.valueOf)?a.valueOf():eg.call(a)}function qf(){var a=ea(),b=ea();this.$get=["$filter",function(d){function c(a,b){return null==a||null==b?a===b:"object"===typeof a&&(a=fc(a),"object"===typeof a)?!1:a===b||a!==a&&b!==b}function e(a,b,d,e,f){var g=e.inputs,h;if(1===g.length){var k=c,g=g[0];return a.$watch(function(a){var b= +g(a);c(b,k)||(h=e(a,w,w,[b]),k=b&&fc(b));return h},b,d,f)}for(var l=[],m=[],r=0,n=g.length;r=this.promise.$$state.status&&d&&d.length&&a(function(){for(var a, +e,f=0,g=d.length;f +a)for(b in l++,f)ra.call(e,b)||(p--,delete f[b])}else f!==e&&(f=e,l++);return l}}c.$stateful=!0;var d=this,e,f,g,k=1n&&(q=4-n,x[q]||(x[q]=[]),x[q].push({msg:B(a.exp)?"fn: "+(a.exp.name||a.exp.toString()):a.exp,newVal:f,oldVal:h}));else if(a===c){r=!1;break a}}catch(la){g(la)}if(!(l=C.$$watchersCount&&C.$$childHead|| +C!==this&&C.$$nextSibling))for(;C!==this&&!(l=C.$$nextSibling);)C=C.$parent}while(C=l);if((r||u.length)&&!n--)throw v.$$phase=null,d("infdig",b,x);}while(r||u.length);for(v.$$phase=null;H.length;)try{H.shift()()}catch(A){g(A)}},$destroy:function(){if(!this.$$destroyed){var a=this.$parent;this.$broadcast("$destroy");this.$$destroyed=!0;this===v&&k.$$applicationDestroyed();C(this,-this.$$watchersCount);for(var b in this.$$listenerCount)y(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=z;this.$on=this.$watch=this.$watchGroup=function(){return z};this.$$listeners={};this.$$nextSibling=null;m(this)}},$eval:function(a,b){return h(a)(this,b)},$evalAsync:function(a,b){v.$$phase||u.length|| +k.defer(function(){u.length&&v.$digest()});u.push({scope:this,expression:a,locals:b})},$$postDigest:function(a){H.push(a)},$apply:function(a){try{t("$apply");try{return this.$eval(a)}finally{v.$$phase=null}}catch(b){g(b)}finally{try{v.$digest()}catch(c){throw g(c),c;}}},$applyAsync:function(a){function b(){c.$eval(a)}var c=this;a&&w.push(b);x()},$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&&(c[d]=null,y(e,1,a))}},$emit:function(a,b){var c=[],d,e=this,f=!1,h={name:a,targetScope:e,stopPropagation:function(){f=!0},preventDefault:function(){h.defaultPrevented=!0},defaultPrevented:!1},k=bb([h],arguments,1),l,m;do{d=e.$$listeners[a]||c;h.currentScope=e;l=0;for(m=d.length;lHa)throw za("iequirks");var c=ha(ma);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=Ya);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;n(ma,function(a,b){var d=K(b);c[eb("parse_as_"+d)]=function(b){return e(a,b)};c[eb("get_trusted_"+d)]=function(b){return f(a,b)};c[eb("trust_as_"+d)]=function(b){return g(a,b)}});return c}]}function wf(){this.$get=["$window","$document",function(a,b){var d={},c=Z((/android (\d+)/.exec(K((a.navigator||{}).userAgent))||[])[1]),e=/Boxee/i.test((a.navigator|| +{}).userAgent),f=b[0]||{},g,h=/^(Moz|webkit|ms)(?=[A-Z])/,k=f.body&&f.body.style,l=!1,m=!1;if(k){for(var r in k)if(l=h.exec(r)){g=l[0];g=g.substr(0,1).toUpperCase()+g.substr(1);break}g||(g="WebkitOpacity"in k&&"webkit");l=!!("transition"in k||g+"Transition"in k);m=!!("animation"in k||g+"Animation"in k);!c||l&&m||(l=F(k.webkitTransition),m=F(k.webkitAnimation))}return{history:!(!a.history||!a.history.pushState||4>c||e),hasEvent:function(a){if("input"===a&&11>=Ha)return!1;if(q(d[a])){var b=f.createElement("div"); +d[a]="on"+a in b}return d[a]},csp:Ca(),vendorPrefix:g,transitions:l,animations:m,android:c}}]}function yf(){this.$get=["$templateCache","$http","$q","$sce",function(a,b,d,c){function e(f,g){e.totalPendingRequests++;F(f)&&a.get(f)||(f=c.getTrustedResourceUrl(f));var h=b.defaults&&b.defaults.transformResponse;E(h)?h=h.filter(function(a){return a!==ac}):h===ac&&(h=null);return b.get(f,{cache:a,transformResponse:h})["finally"](function(){e.totalPendingRequests--}).then(function(b){a.put(f,b.data);return b.data}, +function(a){if(!g)throw ga("tpload",f,a.status,a.statusText);return d.reject(a)})}e.totalPendingRequests=0;return e}]}function zf(){this.$get=["$rootScope","$browser","$location",function(a,b,d){return{findBindings:function(a,b,d){a=a.getElementsByClassName("ng-binding");var g=[];n(a,function(a){var c=$.element(a).data("$binding");c&&n(c,function(c){d?(new RegExp("(^|\\s)"+wd(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\\:"],h=0;hc&&(c=e),c+=+a.slice(e+1),a=a.substring(0,e)):0>c&&(c= +a.length);for(e=0;a.charAt(e)==jc;e++);if(e==(g=a.length))d=[0],c=1;else{for(g--;a.charAt(g)==jc;)g--;c-=e;d=[];for(f=0;e<=g;e++,f++)d[f]=+a.charAt(e)}c>Gd&&(d=d.splice(0,Gd-1),b=c-1,c=1);return{d:d,e:b,i:c}}function ng(a,b,d,c){var e=a.d,f=e.length-a.i;b=q(b)?Math.min(Math.max(d,f),c):+b;d=b+a.i;c=e[d];if(0h;)k.unshift(0),h++;0b.lgSize&&h.unshift(k.splice(-b.lgSize).join(""));k.length>b.gSize;)h.unshift(k.splice(-b.gSize).join(""));k.length&&h.unshift(k.join(""));k=h.join(d);f.length&&(k+=c+f.join(""));e&&(k+="e+"+e)}return 0> +a&&!g?b.negPre+k+b.negSuf:b.posPre+k+b.posSuf}function Ib(a,b,d){var c="";0>a&&(c="-",a=-a);for(a=""+a;a.length-d)e+=d;0===e&&-12==d&&(e=12);return Ib(e,b,c)}}function Jb(a,b){return function(d,c){var e=d["get"+a](),f=tb(b?"SHORT"+a:a);return c[f][e]}}function Hd(a){var b=(new Date(a,0,1)).getDay();return new Date(a,0,(4>=b?5:12)-b)}function Id(a){return function(b){var d=Hd(b.getFullYear()); +b=+new Date(b.getFullYear(),b.getMonth(),b.getDate()+(4-b.getDay()))-+d;b=1+Math.round(b/6048E5);return Ib(b,a)}}function kc(a,b){return 0>=a.getFullYear()?b.ERAS[0]:b.ERAS[1]}function Bd(a){function b(a){var b;if(b=a.match(d)){a=new Date(0);var f=0,g=0,h=b[8]?a.setUTCFullYear:a.setFullYear,k=b[8]?a.setUTCHours:a.setHours;b[9]&&(f=Z(b[9]+b[10]),g=Z(b[9]+b[11]));h.call(a,Z(b[1]),Z(b[2])-1,Z(b[3]));f=Z(b[4]||0)-f;g=Z(b[5]||0)-g;h=Z(b[6]||0);b=Math.round(1E3*parseFloat("0."+(b[7]||0)));k.call(a,f,g, +h,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="",h=[],k,l;d=d||"mediumDate";d=a.DATETIME_FORMATS[d]||d;F(c)&&(c=og.test(c)?Z(c):b(c));Q(c)&&(c=new Date(c));if(!da(c)||!isFinite(c.getTime()))return c;for(;d;)(l=pg.exec(d))?(h=bb(h,l,1),d=h.pop()):(h.push(d),d=null);var m=c.getTimezoneOffset();f&&(m=wc(f,c.getTimezoneOffset()),c=Rb(c,f,!0));n(h,function(b){k=qg[b];g+=k?k(c,a.DATETIME_FORMATS,m): +b.replace(/(^'|'$)/g,"").replace(/''/g,"'")});return g}}function hg(){return function(a,b){q(b)&&(b=2);return cb(a,b)}}function ig(){return function(a,b,d){b=Infinity===Math.abs(Number(b))?Number(b):Z(b);if(isNaN(b))return a;Q(a)&&(a=a.toString());if(!E(a)&&!F(a))return a;d=!d||isNaN(d)?0:Z(d);d=0>d?Math.max(0,a.length+d):d;return 0<=b?a.slice(d,d+b):0===d?a.slice(b,a.length):a.slice(Math.max(0,d+b),d)}}function Dd(a){function b(b,d){d=d?-1:1;return b.map(function(b){var c=1,h=Ya;if(B(b))h=b;else if(F(b)){if("+"== +b.charAt(0)||"-"==b.charAt(0))c="-"==b.charAt(0)?-1:1,b=b.substring(1);if(""!==b&&(h=a(b),h.constant))var k=h(),h=function(a){return a[k]}}return{get:h,descending:c*d}})}function d(a){switch(typeof a){case "number":case "boolean":case "string":return!0;default:return!1}}return function(a,e,f){if(!Aa(a))return a;E(e)||(e=[e]);0===e.length&&(e=["+"]);var g=b(e,f);g.push({get:function(){return{}},descending:f?-1:1});a=Array.prototype.map.call(a,function(a,b){return{value:a,predicateValues:g.map(function(c){var e= +c.get(a);c=typeof e;if(null===e)c="string",e="null";else if("string"===c)e=e.toLowerCase();else if("object"===c)a:{if("function"===typeof e.valueOf&&(e=e.valueOf(),d(e)))break a;if(rc(e)&&(e=e.toString(),d(e)))break a;e=b}return{value:e,type:c}})}});a.sort(function(a,b){for(var c=0,d=0,e=g.length;db||37<=b&&40>=b||m(a,this,this.value)});if(e.hasEvent("paste"))b.on("paste cut",m)}b.on("change",k);c.$render=function(){var a=c.$isEmpty(c.$viewValue)?"":c.$viewValue;b.val()!==a&&b.val(a)}}function Mb(a,b){return function(d,c){var e,f;if(da(d))return d;if(F(d)){'"'==d.charAt(0)&&'"'==d.charAt(d.length-1)&&(d=d.substring(1,d.length-1));if(rg.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},n(e,function(a,c){c=s};g.$observe("min",function(a){s=n(a);h.$validate()})}if(u(g.max)||g.ngMax){var p;h.$validators.max=function(a){return!r(a)||q(p)||d(a)<=p};g.$observe("max",function(a){p= +n(a);h.$validate()})}}}function Ld(a,b,d,c){(c.$$hasNativeValidators=G(b[0].validity))&&c.$parsers.push(function(a){var c=b.prop("validity")||{};return c.badInput&&!c.typeMismatch?w:a})}function Md(a,b,d,c,e){if(u(c)){a=a(c);if(!a.constant)throw mb("constexpr",d,c);return a(b)}return e}function mc(a,b){a="ngClass"+a;return["$animate",function(d){function c(a,b){var c=[],d=0;a:for(;d(?:<\/\1>|)$/,Vb=/<|&#?\w+;/,If=/<([\w:-]+)/,Jf=/<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:-]+)[^>]*)\/>/gi,ja={option:[1,'"],thead:[1,"","
    "],col:[2,"","
    "],tr:[2,"","
    "],td:[3,"","
    "],_default:[0,"",""]};ja.optgroup=ja.option;ja.tbody=ja.tfoot=ja.colgroup=ja.caption=ja.thead; +ja.th=ja.td;var Qf=Node.prototype.contains||function(a){return!!(this.compareDocumentPosition(a)&16)},Pa=P.prototype={ready:function(a){function b(){d||(d=!0,a())}var d=!1;"complete"===W.readyState?setTimeout(b):(this.on("DOMContentLoaded",b),P(S).on("load",b))},toString:function(){var a=[];n(this,function(b){a.push(""+b)});return"["+a.join(", ")+"]"},eq:function(a){return 0<=a?A(this[a]):A(this[this.length+a])},length:0,push:tg,sort:[].sort,splice:[].splice},Db={};n("multiple selected checked disabled readOnly required open".split(" "), +function(a){Db[K(a)]=a});var Sc={};n("input select option textarea button form details".split(" "),function(a){Sc[a]=!0});var ad={ngMinlength:"minlength",ngMaxlength:"maxlength",ngMin:"min",ngMax:"max",ngPattern:"pattern"};n({data:Yb,removeData:wb,hasData:function(a){for(var b in fb[a.ng339])return!0;return!1}},function(a,b){P[b]=a});n({data:Yb,inheritedData:Cb,scope:function(a){return A.data(a,"$scope")||Cb(a.parentNode||a,["$isolateScope","$scope"])},isolateScope:function(a){return A.data(a,"$isolateScope")|| +A.data(a,"$isolateScopeNoTemplate")},controller:Pc,injector:function(a){return Cb(a,"$injector")},removeAttr:function(a,b){a.removeAttribute(b)},hasClass:zb,css:function(a,b,d){b=eb(b);if(u(d))a.style[b]=d;else return a.style[b]},attr:function(a,b,d){var c=a.nodeType;if(c!==Na&&2!==c&&8!==c)if(c=K(b),Db[c])if(u(d))d?(a[b]=!0,a.setAttribute(b,c)):(a[b]=!1,a.removeAttribute(c));else return a[b]||(a.attributes.getNamedItem(b)||z).specified?c:w;else if(u(d))a.setAttribute(b,d);else if(a.getAttribute)return a= +a.getAttribute(b,2),null===a?w:a},prop:function(a,b,d){if(u(d))a[b]=d;else return a[b]},text:function(){function a(a,d){if(q(d)){var c=a.nodeType;return 1===c||c===Na?a.textContent:""}a.textContent=d}a.$dv="";return a}(),val:function(a,b){if(q(b)){if(a.multiple&&"select"===oa(a)){var d=[];n(a.options,function(a){a.selected&&d.push(a.value||a.text)});return 0===d.length?null:d}return a.value}a.value=b},html:function(a,b){if(q(b))return a.innerHTML;vb(a,!0);a.innerHTML=b},empty:Qc},function(a,b){P.prototype[b]= +function(b,c){var e,f,g=this.length;if(a!==Qc&&q(2==a.length&&a!==zb&&a!==Pc?b:c)){if(G(b)){for(e=0;e <= >= && || ! = |".split(" "), +function(a){Nb[a]=!0});var zg={n:"\n",f:"\f",r:"\r",t:"\t",v:"\v","'":"'",'"':'"'},gc=function(a){this.options=a};gc.prototype={constructor:gc,lex:function(a){this.text=a;this.index=0;for(this.tokens=[];this.index=a&&"string"===typeof a},isWhitespace:function(a){return" "=== +a||"\r"===a||"\t"===a||"\n"===a||"\v"===a||"\u00a0"===a},isIdent:function(a){return"a"<=a&&"z">=a||"A"<=a&&"Z">=a||"_"===a||"$"===a},isExpOperator:function(a){return"-"===a||"+"===a||this.isNumber(a)},throwError:function(a,b,d){d=d||this.index;b=u(b)?"s "+b+"-"+this.index+" ["+this.text.substring(b,d)+"]":" "+d;throw aa("lexerr",a,b,this.text);},readNumber:function(){for(var a="",b=this.index;this.index","<=",">=");)a={type:s.BinaryExpression,operator:b.text,left:a,right:this.additive()};return a},additive:function(){for(var a=this.multiplicative(),b;b=this.expect("+","-");)a={type:s.BinaryExpression,operator:b.text,left:a,right:this.multiplicative()};return a},multiplicative:function(){for(var a=this.unary(),b;b=this.expect("*","/","%");)a={type:s.BinaryExpression,operator:b.text, +left:a,right:this.unary()};return a},unary:function(){var a;return(a=this.expect("+","-","!"))?{type:s.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.constants.hasOwnProperty(this.peek().text)?a=Ma(this.constants[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:s.CallExpression,callee:a,arguments:this.parseArguments()},this.consume(")")):"["===b.text?(a={type:s.MemberExpression,object:a,property:this.expression(),computed:!0},this.consume("]")):"."===b.text?a={type:s.MemberExpression,object:a,property:this.identifier(),computed:!1}:this.throwError("IMPOSSIBLE");return a},filter:function(a){a=[a];for(var b={type:s.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.expression());while(this.expect(","))}return a},identifier:function(){var a=this.consume();a.identifier||this.throwError("is not a valid identifier",a);return{type:s.Identifier,name:a.text}},constant:function(){return{type:s.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:s.ArrayExpression,elements:a}},object:function(){var a=[],b;if("}"!==this.peekToken().text){do{if(this.peek("}"))break;b={type:s.Property,kind:"init"};this.peek().constant?b.key=this.constant():this.peek().identifier?b.key=this.identifier():this.throwError("invalid key",this.peek());this.consume(":");b.value=this.expression();a.push(b)}while(this.expect(","))}this.consume("}");return{type:s.ObjectExpression,properties:a}}, +throwError:function(a,b){throw aa("syntax",b.text,a,b.index+1,this.text,this.text.substring(b.index));},consume:function(a){if(0===this.tokens.length)throw aa("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 aa("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},constants:{"true":{type:s.Literal,value:!0},"false":{type:s.Literal,value:!1},"null":{type:s.Literal,value:null},undefined:{type:s.Literal,value:w},"this":{type:s.ThisExpression}}};td.prototype={compile:function(a,b){var d=this,c=this.astBuilder.ast(a);this.state={nextId:0,filters:{},expensiveChecks:b,fn:{vars:[],body:[],own:{}},assign:{vars:[], +body:[],own:{}},inputs:[]};V(c,d.$filter);var e="",f;this.stage="assign";if(f=rd(c))this.state.computing="assign",e=this.nextId(),this.recurse(f,e),this.return_(e),e="fn.assign="+this.generateFunction("assign","s,v,l");f=pd(c.body);d.stage="inputs";n(f,function(a,b){var c="fn"+b;d.state[c]={vars:[],body:[],own:{}};d.state.computing=c;var e=d.nextId();d.recurse(a,e);d.return_(e);d.state.inputs.push(c);a.watchId=b});this.state.computing="fn";this.stage="main";this.recurse(c);e='"'+this.USE+" "+this.STRICT+ +'";\n'+this.filterPrefix()+"var fn="+this.generateFunction("fn","s,l,a,i")+e+this.watchFns()+"return fn;";e=(new Function("$filter","ensureSafeMemberName","ensureSafeObject","ensureSafeFunction","getStringValue","ensureSafeAssignContext","ifDefined","plus","text",e))(this.$filter,Va,ya,md,ld,nd,dg,od,a);this.state=this.stage=w;e.literal=sd(c);e.constant=c.constant;return e},USE:"use",STRICT:"strict",watchFns:function(){var a=[],b=this.state.inputs,d=this;n(b,function(b){a.push("var "+b+"="+d.generateFunction(b, +"s"))});b.length&&a.push("fn.inputs=["+b.join(",")+"];");return a.join("")},generateFunction:function(a,b){return"function("+b+"){"+this.varsPrefix(a)+this.body(a)+"};"},filterPrefix:function(){var a=[],b=this;n(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,h,k=this,l,m;c=c||z;if(!f&&u(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 s.Program:n(a.body,function(b,c){k.recurse(b.expression,w,w,function(a){h=a});c!==a.body.length-1?k.current().body.push(h,";"):k.return_(h)});break;case s.Literal:m=this.escape(a.value);this.assign(b,m);c(m);break;case s.UnaryExpression:this.recurse(a.argument,w,w,function(a){h=a});m=a.operator+"("+this.ifDefined(h, +0)+")";this.assign(b,m);c(m);break;case s.BinaryExpression:this.recurse(a.left,w,w,function(a){g=a});this.recurse(a.right,w,w,function(a){h=a});m="+"===a.operator?this.plus(g,h):"-"===a.operator?this.ifDefined(g,0)+a.operator+this.ifDefined(h,0):"("+g+")"+a.operator+"("+h+")";this.assign(b,m);c(m);break;case s.LogicalExpression:b=b||this.nextId();k.recurse(a.left,b);k.if_("&&"===a.operator?b:k.not(b),k.lazyRecurse(a.right,b));c(b);break;case s.ConditionalExpression:b=b||this.nextId();k.recurse(a.test, +b);k.if_(b,k.lazyRecurse(a.alternate,b),k.lazyRecurse(a.consequent,b));c(b);break;case s.Identifier:b=b||this.nextId();d&&(d.context="inputs"===k.stage?"s":this.assign(this.nextId(),this.getHasOwnProperty("l",a.name)+"?l:s"),d.computed=!1,d.name=a.name);Va(a.name);k.if_("inputs"===k.stage||k.not(k.getHasOwnProperty("l",a.name)),function(){k.if_("inputs"===k.stage||"s",function(){e&&1!==e&&k.if_(k.not(k.nonComputedMember("s",a.name)),k.lazyAssign(k.nonComputedMember("s",a.name),"{}"));k.assign(b,k.nonComputedMember("s", +a.name))})},b&&k.lazyAssign(b,k.nonComputedMember("l",a.name)));(k.state.expensiveChecks||Hb(a.name))&&k.addEnsureSafeObject(b);c(b);break;case s.MemberExpression:g=d&&(d.context=this.nextId())||this.nextId();b=b||this.nextId();k.recurse(a.object,g,w,function(){k.if_(k.notNull(g),function(){if(a.computed)h=k.nextId(),k.recurse(a.property,h),k.getStringValue(h),k.addEnsureSafeMemberName(h),e&&1!==e&&k.if_(k.not(k.computedMember(g,h)),k.lazyAssign(k.computedMember(g,h),"{}")),m=k.ensureSafeObject(k.computedMember(g, +h)),k.assign(b,m),d&&(d.computed=!0,d.name=h);else{Va(a.property.name);e&&1!==e&&k.if_(k.not(k.nonComputedMember(g,a.property.name)),k.lazyAssign(k.nonComputedMember(g,a.property.name),"{}"));m=k.nonComputedMember(g,a.property.name);if(k.state.expensiveChecks||Hb(a.property.name))m=k.ensureSafeObject(m);k.assign(b,m);d&&(d.computed=!1,d.name=a.property.name)}},function(){k.assign(b,"undefined")});c(b)},!!e);break;case s.CallExpression:b=b||this.nextId();a.filter?(h=k.filter(a.callee.name),l=[],n(a.arguments, +function(a){var b=k.nextId();k.recurse(a,b);l.push(b)}),m=h+"("+l.join(",")+")",k.assign(b,m),c(b)):(h=k.nextId(),g={},l=[],k.recurse(a.callee,h,g,function(){k.if_(k.notNull(h),function(){k.addEnsureSafeFunction(h);n(a.arguments,function(a){k.recurse(a,k.nextId(),w,function(a){l.push(k.ensureSafeObject(a))})});g.name?(k.state.expensiveChecks||k.addEnsureSafeObject(g.context),m=k.member(g.context,g.name,g.computed)+"("+l.join(",")+")"):m=h+"("+l.join(",")+")";m=k.ensureSafeObject(m);k.assign(b,m)}, +function(){k.assign(b,"undefined")});c(b)}));break;case s.AssignmentExpression:h=this.nextId();g={};if(!qd(a.left))throw aa("lval");this.recurse(a.left,w,g,function(){k.if_(k.notNull(g.context),function(){k.recurse(a.right,h);k.addEnsureSafeObject(k.member(g.context,g.name,g.computed));k.addEnsureSafeAssignContext(g.context);m=k.member(g.context,g.name,g.computed)+a.operator+h;k.assign(b,m);c(b||m)})},1);break;case s.ArrayExpression:l=[];n(a.elements,function(a){k.recurse(a,k.nextId(),w,function(a){l.push(a)})}); +m="["+l.join(",")+"]";this.assign(b,m);c(m);break;case s.ObjectExpression:l=[];n(a.properties,function(a){k.recurse(a.value,k.nextId(),w,function(b){l.push(k.escape(a.key.type===s.Identifier?a.key.name:""+a.key.value)+":"+b)})});m="{"+l.join(",")+"}";this.assign(b,m);c(m);break;case s.ThisExpression:this.assign(b,"s");c("s");break;case s.NGValueParameter:this.assign(b,"v"),c("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+")"},notNull:function(a){return a+"!=null"},nonComputedMember:function(a,b){return a+"."+b},computedMember:function(a,b){return a+"["+b+"]"},member:function(a,b,d){return d?this.computedMember(a,b):this.nonComputedMember(a,b)},addEnsureSafeObject:function(a){this.current().body.push(this.ensureSafeObject(a),";")},addEnsureSafeMemberName:function(a){this.current().body.push(this.ensureSafeMemberName(a),";")}, +addEnsureSafeFunction:function(a){this.current().body.push(this.ensureSafeFunction(a),";")},addEnsureSafeAssignContext:function(a){this.current().body.push(this.ensureSafeAssignContext(a),";")},ensureSafeObject:function(a){return"ensureSafeObject("+a+",text)"},ensureSafeMemberName:function(a){return"ensureSafeMemberName("+a+",text)"},ensureSafeFunction:function(a){return"ensureSafeFunction("+a+",text)"},getStringValue:function(a){this.assign(a,"getStringValue("+a+",text)")},ensureSafeAssignContext:function(a){return"ensureSafeAssignContext("+ +a+",text)"},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(F(a))return"'"+a.replace(this.stringEscapeRegex,this.stringEscapeFn)+"'";if(Q(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 aa("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]}};ud.prototype={compile:function(a,b){var d=this,c=this.astBuilder.ast(a);this.expression=a;this.expensiveChecks=b;V(c,d.$filter);var e,f;if(e=rd(c))f=this.recurse(e);e=pd(c.body);var g;e&&(g=[],n(e,function(a,b){var c=d.recurse(a);a.input=c;g.push(c);a.watchId=b}));var h=[];n(c.body,function(a){h.push(d.recurse(a.expression))}); +e=0===c.body.length?function(){}:1===c.body.length?h[0]:function(a,b){var c;n(h,function(d){c=d(a,b)});return c};f&&(e.assign=function(a,b,c){return f(a,c,b)});g&&(e.inputs=g);e.literal=sd(c);e.constant=c.constant;return e},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 s.Literal:return this.value(a.value,b);case s.UnaryExpression:return e=this.recurse(a.argument),this["unary"+a.operator](e,b);case s.BinaryExpression:return c=this.recurse(a.left), +e=this.recurse(a.right),this["binary"+a.operator](c,e,b);case s.LogicalExpression:return c=this.recurse(a.left),e=this.recurse(a.right),this["binary"+a.operator](c,e,b);case s.ConditionalExpression:return this["ternary?:"](this.recurse(a.test),this.recurse(a.alternate),this.recurse(a.consequent),b);case s.Identifier:return Va(a.name,f.expression),f.identifier(a.name,f.expensiveChecks||Hb(a.name),b,d,f.expression);case s.MemberExpression:return c=this.recurse(a.object,!1,!!d),a.computed||(Va(a.property.name, +f.expression),e=a.property.name),a.computed&&(e=this.recurse(a.property)),a.computed?this.computedMember(c,e,b,d,f.expression):this.nonComputedMember(c,e,f.expensiveChecks,b,d,f.expression);case s.CallExpression:return g=[],n(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 r=[],n=0;n":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,h){e=a(e,f,g,h)?b(e,f,g,h):d(e,f,g,h);return c?{value:e}:e}},value:function(a,b){return function(){return b?{context:w,name:w,value:a}:a}},identifier:function(a,b,d,c,e){return function(f,g,h,k){f= +g&&a in g?g:f;c&&1!==c&&f&&!f[a]&&(f[a]={});g=f?f[a]:w;b&&ya(g,e);return d?{context:f,name:a,value:g}:g}},computedMember:function(a,b,d,c,e){return function(f,g,h,k){var l=a(f,g,h,k),m,n;null!=l&&(m=b(f,g,h,k),m=ld(m),Va(m,e),c&&1!==c&&l&&!l[m]&&(l[m]={}),n=l[m],ya(n,e));return d?{context:l,name:m,value:n}:n}},nonComputedMember:function(a,b,d,c,e,f){return function(g,h,k,l){g=a(g,h,k,l);e&&1!==e&&g&&!g[b]&&(g[b]={});h=null!=g?g[b]:w;(d||Hb(b))&&ya(h,f);return c?{context:g,name:b,value:h}:h}},inputs:function(a, +b){return function(d,c,e,f){return f?f[b]:a(d,c,e)}}};var hc=function(a,b,d){this.lexer=a;this.$filter=b;this.options=d;this.ast=new s(this.lexer);this.astCompiler=d.csp?new ud(this.ast,b):new td(this.ast,b)};hc.prototype={constructor:hc,parse:function(a){return this.astCompiler.compile(a,this.options.expensiveChecks)}};var eg=Object.prototype.valueOf,za=M("$sce"),ma={HTML:"html",CSS:"css",URL:"url",RESOURCE_URL:"resourceUrl",JS:"js"},ga=M("$compile"),ba=W.createElement("a"),yd=xa(S.location.href); +zd.$inject=["$document"];Kc.$inject=["$provide"];var Gd=22,Fd=".",jc="0";Ad.$inject=["$locale"];Cd.$inject=["$locale"];var qg={yyyy:ca("FullYear",4),yy:ca("FullYear",2,0,!0),y:ca("FullYear",1),MMMM:Jb("Month"),MMM:Jb("Month",!0),MM:ca("Month",2,1),M:ca("Month",1,1),dd:ca("Date",2),d:ca("Date",1),HH:ca("Hours",2),H:ca("Hours",1),hh:ca("Hours",2,-12),h:ca("Hours",1,-12),mm:ca("Minutes",2),m:ca("Minutes",1),ss:ca("Seconds",2),s:ca("Seconds",1),sss:ca("Milliseconds",3),EEEE:Jb("Day"),EEE:Jb("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?"+":"")+(Ib(Math[0=a.getFullYear()?b.ERANAMES[0]:b.ERANAMES[1]}},pg=/((?:[^yMdHhmsaZEwG']+)|(?:'(?:[^']|'')*')|(?:E+|y+|M+|d+|H+|h+|m+|s+|a|Z|G+|w+))(.*)/,og=/^\-?\d+$/;Bd.$inject=["$locale"];var jg=na(K),kg=na(tb);Dd.$inject=["$parse"];var le=na({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]"===ta.call(b.prop("href"))?"xlink:href":"href";b.on("click",function(a){b.attr(e)||a.preventDefault()})}}}}),ub={};n(Db,function(a,b){function d(a,d,e){a.$watch(e[c],function(a){e.$set(b,!!a)})}if("multiple"!=a){var c=va("ng-"+b),e=d;"checked"===a&&(e=function(a,b,e){e.ngModel!==e[c]&&d(a,b,e)});ub[c]=function(){return{restrict:"A",priority:100,link:e}}}});n(ad,function(a,b){ub[b]=function(){return{priority:100, +link:function(a,c,e){if("ngPattern"===b&&"/"==e.ngPattern.charAt(0)&&(c=e.ngPattern.match(sg))){e.$set("ngPattern",new RegExp(c[1],c[2]));return}a.$watch(e[b],function(a){e.$set(b,a)})}}}});n(["src","srcset","href"],function(a){var b=va("ng-"+a);ub[b]=function(){return{priority:99,link:function(d,c,e){var f=a,g=a;"href"===a&&"[object SVGAnimatedString]"===ta.call(c.prop("href"))&&(g="xlinkHref",e.$attr[g]="xlink:href",f=null);e.$observe(b,function(b){b?(e.$set(g,b),Ha&&f&&c.prop(f,e[g])):"href"=== +a&&e.$set(g,null)})}}}});var Kb={$addControl:z,$$renameControl:function(a,b){a.$name=b},$removeControl:z,$setValidity:z,$setDirty:z,$setPristine:z,$setSubmitted:z};Jd.$inject=["$element","$attrs","$scope","$animate","$interpolate"];var Rd=function(a){return["$timeout","$parse",function(b,d){function c(a){return""===a?d('this[""]').assign:d(a).assign||z}return{name:"form",restrict:a?"EAC":"E",require:["form","^^?form"],controller:Jd,compile:function(d,f){d.addClass(Wa).addClass(nb);var g=f.name?"name": +a&&f.ngForm?"ngForm":!1;return{pre:function(a,d,e,f){var n=f[0];if(!("action"in e)){var t=function(b){a.$apply(function(){n.$commitViewValue();n.$setSubmitted()});b.preventDefault()};d[0].addEventListener("submit",t,!1);d.on("$destroy",function(){b(function(){d[0].removeEventListener("submit",t,!1)},0,!1)})}(f[1]||n.$$parentForm).$addControl(n);var q=g?c(n.$name):z;g&&(q(a,n),e.$observe(g,function(b){n.$name!==b&&(q(a,w),n.$$parentForm.$$renameControl(n,b),q=c(n.$name),q(a,n))}));d.on("$destroy", +function(){n.$$parentForm.$removeControl(n);q(a,w);N(n,Kb)})}}}}}]},me=Rd(),ze=Rd(!0),rg=/\d{4}-[01]\d-[0-3]\dT[0-2]\d:[0-5]\d:[0-5]\d\.\d+([+-][0-2]\d:[0-5]\d|Z)/,Ag=/^[a-z][a-z\d.+-]*:\/*(?:[^:@]+(?::[^@]+)?@)?(?:[^\s:/?#]+|\[[a-f\d:]+\])(?::\d+)?(?:\/[^?#]*)?(?:\?[^#]*)?(?:#.*)?$/i,Bg=/^[a-z0-9!#$%&'*+\/=?^_`{|}~.-]+@[a-z0-9]([a-z0-9-]*[a-z0-9])?(\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$/i,Cg=/^\s*(\-|\+)?(\d+|(\d*(\.\d*)))([eE][+-]?\d+)?\s*$/,Sd=/^(\d{4})-(\d{2})-(\d{2})$/,Td=/^(\d{4})-(\d\d)-(\d\d)T(\d\d):(\d\d)(?::(\d\d)(\.\d{1,3})?)?$/, +nc=/^(\d{4})-W(\d\d)$/,Ud=/^(\d{4})-(\d\d)$/,Vd=/^(\d\d):(\d\d)(?::(\d\d)(\.\d{1,3})?)?$/,Wd={text:function(a,b,d,c,e,f){kb(a,b,d,c,e,f);lc(c)},date:lb("date",Sd,Mb(Sd,["yyyy","MM","dd"]),"yyyy-MM-dd"),"datetime-local":lb("datetimelocal",Td,Mb(Td,"yyyy MM dd HH mm ss sss".split(" ")),"yyyy-MM-ddTHH:mm:ss.sss"),time:lb("time",Vd,Mb(Vd,["HH","mm","ss","sss"]),"HH:mm:ss.sss"),week:lb("week",nc,function(a,b){if(da(a))return a;if(F(a)){nc.lastIndex=0;var d=nc.exec(a);if(d){var c=+d[1],e=+d[2],f=d=0,g= +0,h=0,k=Hd(c),e=7*(e-1);b&&(d=b.getHours(),f=b.getMinutes(),g=b.getSeconds(),h=b.getMilliseconds());return new Date(c,0,k.getDate()+e,d,f,g,h)}}return NaN},"yyyy-Www"),month:lb("month",Ud,Mb(Ud,["yyyy","MM"]),"yyyy-MM"),number:function(a,b,d,c,e,f){Ld(a,b,d,c);kb(a,b,d,c,e,f);c.$$parserName="number";c.$parsers.push(function(a){return c.$isEmpty(a)?null:Cg.test(a)?parseFloat(a):w});c.$formatters.push(function(a){if(!c.$isEmpty(a)){if(!Q(a))throw mb("numfmt",a);a=a.toString()}return a});if(u(d.min)|| +d.ngMin){var g;c.$validators.min=function(a){return c.$isEmpty(a)||q(g)||a>=g};d.$observe("min",function(a){u(a)&&!Q(a)&&(a=parseFloat(a,10));g=Q(a)&&!isNaN(a)?a:w;c.$validate()})}if(u(d.max)||d.ngMax){var h;c.$validators.max=function(a){return c.$isEmpty(a)||q(h)||a<=h};d.$observe("max",function(a){u(a)&&!Q(a)&&(a=parseFloat(a,10));h=Q(a)&&!isNaN(a)?a:w;c.$validate()})}},url:function(a,b,d,c,e,f){kb(a,b,d,c,e,f);lc(c);c.$$parserName="url";c.$validators.url=function(a,b){var d=a||b;return c.$isEmpty(d)|| +Ag.test(d)}},email:function(a,b,d,c,e,f){kb(a,b,d,c,e,f);lc(c);c.$$parserName="email";c.$validators.email=function(a,b){var d=a||b;return c.$isEmpty(d)||Bg.test(d)}},radio:function(a,b,d,c){q(d.name)&&b.attr("name",++ob);b.on("click",function(a){b[0].checked&&c.$setViewValue(d.value,a&&a.type)});c.$render=function(){b[0].checked=d.value==c.$viewValue};d.$observe("value",c.$render)},checkbox:function(a,b,d,c,e,f,g,h){var k=Md(h,a,"ngTrueValue",d.ngTrueValue,!0),l=Md(h,a,"ngFalseValue",d.ngFalseValue, +!1);b.on("click",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 ka(a,k)});c.$parsers.push(function(a){return a?k:l})},hidden:z,button:z,submit:z,reset:z,file:z},Ec=["$browser","$sniffer","$filter","$parse",function(a,b,d,c){return{restrict:"E",require:["?ngModel"],link:{pre:function(e,f,g,h){h[0]&&(Wd[K(g.type)]||Wd.text)(e,f,g,h[0],b,a,d,c)}}}}],Dg=/^(true|false|\d+)$/, +Re=function(){return{restrict:"A",priority:100,compile:function(a,b){return Dg.test(b.ngValue)?function(a,b,e){e.$set("value",a.$eval(e.ngValue))}:function(a,b,e){a.$watch(e.ngValue,function(a){e.$set("value",a)})}}}},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=q(a)?"":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=q(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(a){return(a||"").toString()});d.$$addBindingClass(c);return function(b,c,e){d.$$addBindingInfo(c,e.ngBindHtml);b.$watch(g,function(){c.html(a.getTrustedHtml(f(b))||"")})}}}}],Qe=na({restrict:"A", +require:"ngModel",link:function(a,b,d,c){c.$viewChangeListeners.push(function(){a.$eval(d.ngChange)})}}),ue=mc("",!0),we=mc("Odd",0),ve=mc("Even",1),xe=Ka({compile:function(a,b){b.$set("ngCloak",w);a.removeClass("ng-cloak")}}),ye=[function(){return{restrict:"A",scope:!0,controller:"@",priority:500}}],Jc={},Eg={blur:!0,focus:!0};n("click dblclick mousedown mouseup mouseover mouseout mousemove mouseenter mouseleave keydown keyup keypress submit focus blur copy cut paste".split(" "),function(a){var b= +va("ng-"+a);Jc[b]=["$parse","$rootScope",function(d,c){return{restrict:"A",compile:function(e,f){var g=d(f[b],null,!0);return function(b,d){d.on(a,function(d){var e=function(){g(b,{$event:d})};Eg[a]&&c.$$phase?b.$evalAsync(e):b.$apply(e)})}}}}]});var Be=["$animate",function(a){return{multiElement:!0,transclude:"element",priority:600,terminal:!0,restrict:"A",$$tlb:!0,link:function(b,d,c,e,f){var g,h,k;b.$watch(c.ngIf,function(b){b?h||f(function(b,e){h=e;b[b.length++]=W.createComment(" end ngIf: "+ +c.ngIf+" ");g={clone:b};a.enter(b,d.parent(),d)}):(k&&(k.remove(),k=null),h&&(h.$destroy(),h=null),g&&(k=sb(g.clone),a.leave(k).then(function(){k=null}),g=null))})}}}],Ce=["$templateRequest","$anchorScroll","$animate",function(a,b,d){return{restrict:"ECA",priority:400,terminal:!0,transclude:"element",controller:$.noop,compile:function(c,e){var f=e.ngInclude||e.src,g=e.onload||"",h=e.autoscroll;return function(c,e,m,n,q){var s=0,y,w,p,x=function(){w&&(w.remove(),w=null);y&&(y.$destroy(),y=null);p&& +(d.leave(p).then(function(){w=null}),w=p,p=null)};c.$watch(f,function(f){var m=function(){!u(h)||h&&!c.$eval(h)||b()},H=++s;f?(a(f,!0).then(function(a){if(!c.$$destroyed&&H===s){var b=c.$new();n.template=a;a=q(b,function(a){x();d.enter(a,null,e).then(m)});y=b;p=a;y.$emit("$includeContentLoaded",f);c.$eval(g)}},function(){c.$$destroyed||H!==s||(x(),c.$emit("$includeContentError",f))}),c.$emit("$includeContentRequested",f)):(x(),n.template=null)})}}}}],Te=["$compile",function(a){return{restrict:"ECA", +priority:-400,require:"ngInclude",link:function(b,d,c,e){/SVG/.test(d[0].toString())?(d.empty(),a(Mc(e.template,W).childNodes)(b,function(a){d.append(a)},{futureParentElement:d})):(d.html(e.template),a(d.contents())(b))}}}],De=Ka({priority:450,compile:function(){return{pre:function(a,b,d){a.$eval(d.ngInit)}}}}),Pe=function(){return{restrict:"A",priority:100,require:"ngModel",link:function(a,b,d,c){var e=b.attr(d.$attr.ngList)||", ",f="false"!==d.ngTrim,g=f?T(e):e;c.$parsers.push(function(a){if(!q(a)){var b= +[];a&&n(a.split(g),function(a){a&&b.push(f?T(a):a)});return b}});c.$formatters.push(function(a){return E(a)?a.join(e):w});c.$isEmpty=function(a){return!a||!a.length}}}},nb="ng-valid",Nd="ng-invalid",Wa="ng-pristine",Lb="ng-dirty",Pd="ng-pending",mb=M("ngModel"),Fg=["$scope","$exceptionHandler","$attrs","$element","$parse","$animate","$timeout","$rootScope","$q","$interpolate",function(a,b,d,c,e,f,g,h,k,l){this.$modelValue=this.$viewValue=Number.NaN;this.$$rawModelValue=w;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=w;this.$name=l(d.name||"",!1)(a);this.$$parentForm=Kb;var m=e(d.ngModel),r=m.assign,t=m,s=r,y=null,A,p=this;this.$$setOptions=function(a){if((p.$options=a)&&a.getterSetter){var b=e(d.ngModel+"()"),f=e(d.ngModel+"($$$p)");t=function(a){var c=m(a);B(c)&&(c=b(a));return c};s=function(a, +b){B(m(a))?f(a,{$$$p:p.$modelValue}):r(a,p.$modelValue)}}else if(!m.assign)throw mb("nonassign",d.ngModel,ua(c));};this.$render=z;this.$isEmpty=function(a){return q(a)||""===a||null===a||a!==a};var x=0;Kd({ctrl:this,$element:c,set:function(a,b){a[b]=!0},unset:function(a,b){delete a[b]},$animate:f});this.$setPristine=function(){p.$dirty=!1;p.$pristine=!0;f.removeClass(c,Lb);f.addClass(c,Wa)};this.$setDirty=function(){p.$dirty=!0;p.$pristine=!1;f.removeClass(c,Wa);f.addClass(c,Lb);p.$$parentForm.$setDirty()}; +this.$setUntouched=function(){p.$touched=!1;p.$untouched=!0;f.setClass(c,"ng-untouched","ng-touched")};this.$setTouched=function(){p.$touched=!0;p.$untouched=!1;f.setClass(c,"ng-touched","ng-untouched")};this.$rollbackViewValue=function(){g.cancel(y);p.$viewValue=p.$$lastCommittedViewValue;p.$render()};this.$validate=function(){if(!Q(p.$modelValue)||!isNaN(p.$modelValue)){var a=p.$$rawModelValue,b=p.$valid,c=p.$modelValue,d=p.$options&&p.$options.allowInvalid;p.$$runValidators(a,p.$$lastCommittedViewValue, +function(e){d||b===e||(p.$modelValue=e?a:w,p.$modelValue!==c&&p.$$writeModelToScope())})}};this.$$runValidators=function(a,b,c){function d(){var c=!0;n(p.$validators,function(d,e){var g=d(a,b);c=c&&g;f(e,g)});return c?!0:(n(p.$asyncValidators,function(a,b){f(b,null)}),!1)}function e(){var c=[],d=!0;n(p.$asyncValidators,function(e,g){var h=e(a,b);if(!h||!B(h.then))throw mb("nopromise",h);f(g,w);c.push(h.then(function(){f(g,!0)},function(a){d=!1;f(g,!1)}))});c.length?k.all(c).then(function(){g(d)}, +z):g(!0)}function f(a,b){h===x&&p.$setValidity(a,b)}function g(a){h===x&&c(a)}x++;var h=x;(function(){var a=p.$$parserName||"parse";if(q(A))f(a,null);else return A||(n(p.$validators,function(a,b){f(b,null)}),n(p.$asyncValidators,function(a,b){f(b,null)})),f(a,A),A;return!0})()?d()?e():g(!1):g(!1)};this.$commitViewValue=function(){var a=p.$viewValue;g.cancel(y);if(p.$$lastCommittedViewValue!==a||""===a&&p.$$hasNativeValidators)p.$$lastCommittedViewValue=a,p.$pristine&&this.$setDirty(),this.$$parseAndValidate()}; +this.$$parseAndValidate=function(){var b=p.$$lastCommittedViewValue;if(A=q(b)?w:!0)for(var c=0;ce||c.$isEmpty(b)||b.length<=e}}}}},Hc=function(){return{restrict:"A",require:"?ngModel",link:function(a,b,d,c){if(c){var e=0;d.$observe("minlength",function(a){e=Z(a)||0;c.$validate()}); +c.$validators.minlength=function(a,b){return c.$isEmpty(b)||b.length>=e}}}}};S.angular.bootstrap?console.log("WARNING: Tried to load angular more than once."):(ge(),ie($),$.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",pluralCat:function(a,c){var e=a|0,f=c;w===f&&(f=Math.min(b(a),3));Math.pow(10,f);return 1==e&&0==f?"one":"other"}})}]),A(W).ready(function(){ce(W,zc)}))})(window,document);!window.angular.$$csp().noInlineStyle&&window.angular.element(document.head).prepend(''); +//# sourceMappingURL=angular.min.js.map diff --git a/frontend/static/js/app.js b/frontend/static/js/app.js new file mode 100644 index 00000000..df0ea4fd --- /dev/null +++ b/frontend/static/js/app.js @@ -0,0 +1,347 @@ +angular.module("FICApp", ["ngRoute", "ngSanitize"]) + .config(function($routeProvider, $locationProvider) { + $routeProvider + .when("/edit", { + controller: "MyTeamController", + templateUrl: "views/team-edit.html" + }) + .when("/rank", { + controller: "RankController", + templateUrl: "views/rank.html" + }) + .when("/:theme", { + controller: "ExerciceController", + templateUrl: "views/theme.html" + }) + .when("/:theme/:exercice", { + controller: "ExerciceController", + templateUrl: "views/theme.html" + }) + .when("/", { + controller: "HomeController", + templateUrl: "views/home.html" + }) + .otherwise({ + redirectTo: "/" + }); + $locationProvider.html5Mode(true); + }) + .run(function($rootScope, $timeout) { + $rootScope.current_theme = 0; + $rootScope.current_exercice = 0; + $rootScope.time = {}; + + function updTime() { + $timeout.cancel($rootScope.cbm); + $rootScope.cbm = $timeout(updTime, 1000); + if (sessionStorage.userService) { + var time = angular.fromJson(sessionStorage.userService); + var srv_cur = (Date.now() + (time.cu * 1000 - time.he)) / 1000; + var remain = time.du; + if (time.st == Math.floor(srv_cur)) { + $rootScope.refresh(true); + } + if (time.st > 0 && time.st <= srv_cur) { + remain = time.st + time.du - srv_cur; + } + if (remain < 0) { + remain = 0; + $rootScope.time.end = true; + $rootScope.time.expired = true; + } else if (remain < 60) { + $rootScope.time.end = false; + $rootScope.time.expired = true; + } else { + $rootScope.time.end = false; + $rootScope.time.expired = false; + } + $rootScope.time.start = time.st * 1000; + $rootScope.time.duration = time.du; + $rootScope.time.remaining = remain; + $rootScope.time.hours = Math.floor(remain / 3600); + $rootScope.time.minutes = Math.floor((remain % 3600) / 60); + $rootScope.time.seconds = Math.floor(remain % 60); + } + } + updTime(); + }); + +String.prototype.capitalize = function() { + return this + .toLowerCase() + .replace( + /(^|\s)([a-z])/g, + function(m,p1,p2) { return p1+p2.toUpperCase(); } + ); +} + +angular.module("FICApp") + .filter("capitalize", function() { + return function(input) { + return input.capitalize(); + } + }) + .filter("time", function() { + return function(input) { + if (input == undefined) { + return "--"; + } else if (input >= 10) { + return input; + } else { + return "0" + input; + } + } + }) + .controller("DataController", function($sce, $scope, $http, $rootScope, $timeout) { + var actMenu = function() { + if ($scope.my && $scope.themes) { + angular.forEach($scope.themes, function(theme, key) { + $scope.themes[key].exercice_solved = 0; + angular.forEach(theme.exercices, function(exercice, k) { + if ($scope.my.exercices[k] && $scope.my.exercices[k].solved) { + $scope.themes[key].exercice_solved++; + } + }); + }); + } + } + $rootScope.refresh = function(justMy) { + if (!justMy) { + $timeout.cancel($scope.cbr); + $scope.cbr = $timeout($rootScope.refresh, 42000); + $http.get("/time.json").success(function(time) { + time.he = (new Date()).getTime(); + sessionStorage.userService = angular.toJson(time); + }); + $http.get("/themes.json").success(function(themes) { + $scope.themes = themes; + $scope.max_gain = 0; + angular.forEach(themes, function(theme, key) { + this[key].exercice_count = Object.keys(theme.exercices).length; + this[key].gain = 0; + angular.forEach(theme.exercices, function(ex, k) { + this.gain += ex.gain; + }, theme); + $scope.max_gain += theme.gain; + }, themes); + actMenu(); + }); + $http.get("/teams.json").success(function(teams) { + $scope.teams_count = Object.keys(teams).length + $scope.teams = teams; + + $scope.rank = []; + angular.forEach($scope.teams, function(team, tid) { + team.id = tid; + this.push(team); + }, $scope.rank); + }); + } + $http.get("/my.json").success(function(my) { + $scope.my = my; + angular.forEach($scope.my.exercices, function(exercice, eid) { + if (exercice.video_uri) { + exercice.video_uri = $sce.trustAsResourceUrl(exercice.video_uri); + } + }); + actMenu(); + }); + console.log("refresh!"); + } + $rootScope.refresh(); + }) + .controller("ExerciceController", function($scope, $routeParams, $http, $rootScope) { + $rootScope.current_theme = $routeParams.theme; + + if ($routeParams.exercice) { + $rootScope.current_exercice = $routeParams.exercice; + } else { + if ($scope.themes && $scope.my && $scope.themes[$scope.current_theme]) { + var exos = Object.keys($scope.themes[$scope.current_theme].exercices); + var i = 0; + for (; i < exos.length; i++) { + if (!$scope.my.exercices[exos[i]] || !$scope.my.exercices[exos[i]].solved) + break; + } + if (i < exos.length) { + $rootScope.current_exercice = exos[i]; + } else { + $rootScope.current_exercice = exos[0]; + } + } else { + $rootScope.current_exercice = 0; + } + } + + }) + .controller("SubmissionController", function($scope, $http, $rootScope, $timeout) { + $scope.flags = [] + $rootScope.sberr = ""; + + var waitMy = function() { + if (!$scope.my || !$scope.my.exercices || !$scope.my.exercices[$rootScope.current_exercice]) { + $timeout.cancel($scope.cbs); + $scope.cbs = $timeout(waitMy, 420); + } else { + angular.forEach($scope.my.exercices[$rootScope.current_exercice].keys, function(key,kid) { + this.push({ + id: kid, + name: key, + value: "" + }); + }, $scope.flags); + } + } + waitMy(); + + $scope.ssubmit = function() { + var flgs = {} + + var filled = true; + angular.forEach($scope.flags, function(flag,kid) { + flgs[flag.name] = flag.value; + filled = filled && flag.value.length > 0; + }); + + if (!filled) { + $rootScope.messageClass = {"text-danger": true}; + $rootScope.sberr = "Tous les champs sont obligatoires."; + $timeout(function() { + if ($rootScope.sberr == "Tous les champs sont obligatoires.") { + $rootScope.sberr = ""; + } + }, 2345); + return; + } + + $http({ + url: "/submit/" + $rootScope.current_exercice, + method: "POST", + data: flgs + }).success(function(data, status, header, config) { + $rootScope.messageClass = {"text-success": true}; + $rootScope.message = data.errmsg; + $rootScope.sberr = ""; + + angular.forEach($scope.flags, function(flag,kid) { + flag.value = ""; + }); + + var checkDiff = function() { + $http.get("/my.json").success(function(my) { + if ($scope.my.exercices[$rootScope.current_exercice].solved_time != my.exercices[$rootScope.current_exercice].solved_time) { + $rootScope.refresh(); + } else { + $timeout.cancel($scope.cbd); + $scope.cbd = $timeout(checkDiff, 750); + } + }); + }; + checkDiff(); + }).error(function(data, status, header, config) { + if (status >= 500) { + $scope.my.exercices[$rootScope.current_exercice].submitted = false; + } + $rootScope.messageClass = {"text-danger": true}; + $rootScope.message = data.errmsg; + if (status != 402) { + $rootScope.sberr = "Une erreur est survenue lors de l'envoi. Veuillez réessayer dans quelques instants."; + } + }); + $scope.my.exercices[$rootScope.current_exercice].submitted = true; + }; + }) + .controller("MyTeamController", function($scope, $http, $rootScope, $timeout) { + $rootScope.current_theme = 0; + $rootScope.current_exercice = 0; + if ($scope.my) { + $rootScope.title = $scope.my.name; + $rootScope.authors = $scope.my.members.map(function (cur) { + return cur.firstname.capitalize() + " " + cur.lastname.capitalize(); + }).join(", "); + } + $scope.newName = ""; + $rootScope.message = ""; + $rootScope.sberr = ""; + + $scope.tsubmit = function() { + $rootScope.sberr = ""; + if ($scope.newName.length < 1) { + $rootScope.messageClass = {"text-danger": true}; + $rootScope.sberr = "Nom d'équipe invalide: pas d'entrée."; + return false; + } + else if ($scope.newName.length > 32) { + $rootScope.messageClass = {"text-danger": true}; + $rootScope.sberr = "Nom d'équipe invalide: pas plus de 32 caractères."; + return false; + } + else if (!$scope.newName.match(/^[A-Za-z0-9 àéèêëîïôùûü_-]+$/)) { + $rootScope.messageClass = {"text-danger": true}; + $rootScope.sberr = "Nom d'équipe invalide: seuls les caractères alpha-numériques sont autorisés."; + return false; + } + + $http({ + url: "/submit/name", + method: "POST", + data: {newName: $scope.newName} + }).success(function(data, status, header, config) { + $rootScope.messageClass = {"text-success": true}; + $rootScope.message = data.errmsg; + + var checkDiff = function() { + $http.get("/my.json").success(function(my) { + console.log(my.name); + if ($scope.my.name != my.name) { + $scope.newName = ""; + $rootScope.message = ""; + $rootScope.refresh(); + } else { + $timeout.cancel($scope.cbt); + $scope.cbt = $timeout(checkDiff, 750); + } + }); + }; + checkDiff(); + }).error(function(data, status, header, config) { + $rootScope.messageClass = {"text-danger": true}; + $rootScope.message = data.errmsg; + if (status != 402) { + $rootScope.sberr = "Une erreur est survenue lors de l'envoi. Veuillez réessayer dans quelques instants."; + } + }); + }; + }) + .controller("RankController", function($scope, $rootScope) { + $rootScope.current_theme = 0; + $rootScope.current_exercice = 0; + $rootScope.title = "Classement général"; + $rootScope.authors = ""; + + $scope.fields = ["rank", "name", "score"]; + $scope.rankOrder = "rank"; + $scope.reverse = false; + $scope.order = function(fld) { + if ($scope.rankOrder == fld) { + $scope.reverse = !$scope.reverse; + } else { + $scope.rankOrder = fld; + $scope.reverse = false; + } + }; + }) + .controller("HomeController", function($scope, $rootScope) { + $rootScope.current_theme = 0; + $rootScope.current_exercice = 0; + $rootScope.title = ""; + $rootScope.authors = ""; + }); + +function sready() { + if ($("#solution").val().length) { + $("#sbmt").removeClass("disabled"); + } else { + $("#sbmt").addClass("disabled"); + } +}; diff --git a/frontend/static/js/bootstrap.min.js b/frontend/static/js/bootstrap.min.js new file mode 100644 index 00000000..e79c0651 --- /dev/null +++ b/frontend/static/js/bootstrap.min.js @@ -0,0 +1,7 @@ +/*! + * Bootstrap v3.3.6 (http://getbootstrap.com) + * Copyright 2011-2015 Twitter, Inc. + * Licensed under the MIT license + */ +if("undefined"==typeof jQuery)throw new Error("Bootstrap's JavaScript requires jQuery");+function(a){"use strict";var b=a.fn.jquery.split(" ")[0].split(".");if(b[0]<2&&b[1]<9||1==b[0]&&9==b[1]&&b[2]<1||b[0]>2)throw new Error("Bootstrap's JavaScript requires jQuery version 1.9.1 or higher, but lower than version 3")}(jQuery),+function(a){"use strict";function b(){var a=document.createElement("bootstrap"),b={WebkitTransition:"webkitTransitionEnd",MozTransition:"transitionend",OTransition:"oTransitionEnd otransitionend",transition:"transitionend"};for(var c in b)if(void 0!==a.style[c])return{end:b[c]};return!1}a.fn.emulateTransitionEnd=function(b){var c=!1,d=this;a(this).one("bsTransitionEnd",function(){c=!0});var e=function(){c||a(d).trigger(a.support.transition.end)};return setTimeout(e,b),this},a(function(){a.support.transition=b(),a.support.transition&&(a.event.special.bsTransitionEnd={bindType:a.support.transition.end,delegateType:a.support.transition.end,handle:function(b){return a(b.target).is(this)?b.handleObj.handler.apply(this,arguments):void 0}})})}(jQuery),+function(a){"use strict";function b(b){return this.each(function(){var c=a(this),e=c.data("bs.alert");e||c.data("bs.alert",e=new d(this)),"string"==typeof b&&e[b].call(c)})}var c='[data-dismiss="alert"]',d=function(b){a(b).on("click",c,this.close)};d.VERSION="3.3.6",d.TRANSITION_DURATION=150,d.prototype.close=function(b){function c(){g.detach().trigger("closed.bs.alert").remove()}var e=a(this),f=e.attr("data-target");f||(f=e.attr("href"),f=f&&f.replace(/.*(?=#[^\s]*$)/,""));var g=a(f);b&&b.preventDefault(),g.length||(g=e.closest(".alert")),g.trigger(b=a.Event("close.bs.alert")),b.isDefaultPrevented()||(g.removeClass("in"),a.support.transition&&g.hasClass("fade")?g.one("bsTransitionEnd",c).emulateTransitionEnd(d.TRANSITION_DURATION):c())};var e=a.fn.alert;a.fn.alert=b,a.fn.alert.Constructor=d,a.fn.alert.noConflict=function(){return a.fn.alert=e,this},a(document).on("click.bs.alert.data-api",c,d.prototype.close)}(jQuery),+function(a){"use strict";function b(b){return this.each(function(){var d=a(this),e=d.data("bs.button"),f="object"==typeof b&&b;e||d.data("bs.button",e=new c(this,f)),"toggle"==b?e.toggle():b&&e.setState(b)})}var c=function(b,d){this.$element=a(b),this.options=a.extend({},c.DEFAULTS,d),this.isLoading=!1};c.VERSION="3.3.6",c.DEFAULTS={loadingText:"loading..."},c.prototype.setState=function(b){var c="disabled",d=this.$element,e=d.is("input")?"val":"html",f=d.data();b+="Text",null==f.resetText&&d.data("resetText",d[e]()),setTimeout(a.proxy(function(){d[e](null==f[b]?this.options[b]:f[b]),"loadingText"==b?(this.isLoading=!0,d.addClass(c).attr(c,c)):this.isLoading&&(this.isLoading=!1,d.removeClass(c).removeAttr(c))},this),0)},c.prototype.toggle=function(){var a=!0,b=this.$element.closest('[data-toggle="buttons"]');if(b.length){var c=this.$element.find("input");"radio"==c.prop("type")?(c.prop("checked")&&(a=!1),b.find(".active").removeClass("active"),this.$element.addClass("active")):"checkbox"==c.prop("type")&&(c.prop("checked")!==this.$element.hasClass("active")&&(a=!1),this.$element.toggleClass("active")),c.prop("checked",this.$element.hasClass("active")),a&&c.trigger("change")}else this.$element.attr("aria-pressed",!this.$element.hasClass("active")),this.$element.toggleClass("active")};var d=a.fn.button;a.fn.button=b,a.fn.button.Constructor=c,a.fn.button.noConflict=function(){return a.fn.button=d,this},a(document).on("click.bs.button.data-api",'[data-toggle^="button"]',function(c){var d=a(c.target);d.hasClass("btn")||(d=d.closest(".btn")),b.call(d,"toggle"),a(c.target).is('input[type="radio"]')||a(c.target).is('input[type="checkbox"]')||c.preventDefault()}).on("focus.bs.button.data-api blur.bs.button.data-api",'[data-toggle^="button"]',function(b){a(b.target).closest(".btn").toggleClass("focus",/^focus(in)?$/.test(b.type))})}(jQuery),+function(a){"use strict";function b(b){return this.each(function(){var d=a(this),e=d.data("bs.carousel"),f=a.extend({},c.DEFAULTS,d.data(),"object"==typeof b&&b),g="string"==typeof b?b:f.slide;e||d.data("bs.carousel",e=new c(this,f)),"number"==typeof b?e.to(b):g?e[g]():f.interval&&e.pause().cycle()})}var c=function(b,c){this.$element=a(b),this.$indicators=this.$element.find(".carousel-indicators"),this.options=c,this.paused=null,this.sliding=null,this.interval=null,this.$active=null,this.$items=null,this.options.keyboard&&this.$element.on("keydown.bs.carousel",a.proxy(this.keydown,this)),"hover"==this.options.pause&&!("ontouchstart"in document.documentElement)&&this.$element.on("mouseenter.bs.carousel",a.proxy(this.pause,this)).on("mouseleave.bs.carousel",a.proxy(this.cycle,this))};c.VERSION="3.3.6",c.TRANSITION_DURATION=600,c.DEFAULTS={interval:5e3,pause:"hover",wrap:!0,keyboard:!0},c.prototype.keydown=function(a){if(!/input|textarea/i.test(a.target.tagName)){switch(a.which){case 37:this.prev();break;case 39:this.next();break;default:return}a.preventDefault()}},c.prototype.cycle=function(b){return b||(this.paused=!1),this.interval&&clearInterval(this.interval),this.options.interval&&!this.paused&&(this.interval=setInterval(a.proxy(this.next,this),this.options.interval)),this},c.prototype.getItemIndex=function(a){return this.$items=a.parent().children(".item"),this.$items.index(a||this.$active)},c.prototype.getItemForDirection=function(a,b){var c=this.getItemIndex(b),d="prev"==a&&0===c||"next"==a&&c==this.$items.length-1;if(d&&!this.options.wrap)return b;var e="prev"==a?-1:1,f=(c+e)%this.$items.length;return this.$items.eq(f)},c.prototype.to=function(a){var b=this,c=this.getItemIndex(this.$active=this.$element.find(".item.active"));return a>this.$items.length-1||0>a?void 0:this.sliding?this.$element.one("slid.bs.carousel",function(){b.to(a)}):c==a?this.pause().cycle():this.slide(a>c?"next":"prev",this.$items.eq(a))},c.prototype.pause=function(b){return b||(this.paused=!0),this.$element.find(".next, .prev").length&&a.support.transition&&(this.$element.trigger(a.support.transition.end),this.cycle(!0)),this.interval=clearInterval(this.interval),this},c.prototype.next=function(){return this.sliding?void 0:this.slide("next")},c.prototype.prev=function(){return this.sliding?void 0:this.slide("prev")},c.prototype.slide=function(b,d){var e=this.$element.find(".item.active"),f=d||this.getItemForDirection(b,e),g=this.interval,h="next"==b?"left":"right",i=this;if(f.hasClass("active"))return this.sliding=!1;var j=f[0],k=a.Event("slide.bs.carousel",{relatedTarget:j,direction:h});if(this.$element.trigger(k),!k.isDefaultPrevented()){if(this.sliding=!0,g&&this.pause(),this.$indicators.length){this.$indicators.find(".active").removeClass("active");var l=a(this.$indicators.children()[this.getItemIndex(f)]);l&&l.addClass("active")}var m=a.Event("slid.bs.carousel",{relatedTarget:j,direction:h});return a.support.transition&&this.$element.hasClass("slide")?(f.addClass(b),f[0].offsetWidth,e.addClass(h),f.addClass(h),e.one("bsTransitionEnd",function(){f.removeClass([b,h].join(" ")).addClass("active"),e.removeClass(["active",h].join(" ")),i.sliding=!1,setTimeout(function(){i.$element.trigger(m)},0)}).emulateTransitionEnd(c.TRANSITION_DURATION)):(e.removeClass("active"),f.addClass("active"),this.sliding=!1,this.$element.trigger(m)),g&&this.cycle(),this}};var d=a.fn.carousel;a.fn.carousel=b,a.fn.carousel.Constructor=c,a.fn.carousel.noConflict=function(){return a.fn.carousel=d,this};var e=function(c){var d,e=a(this),f=a(e.attr("data-target")||(d=e.attr("href"))&&d.replace(/.*(?=#[^\s]+$)/,""));if(f.hasClass("carousel")){var g=a.extend({},f.data(),e.data()),h=e.attr("data-slide-to");h&&(g.interval=!1),b.call(f,g),h&&f.data("bs.carousel").to(h),c.preventDefault()}};a(document).on("click.bs.carousel.data-api","[data-slide]",e).on("click.bs.carousel.data-api","[data-slide-to]",e),a(window).on("load",function(){a('[data-ride="carousel"]').each(function(){var c=a(this);b.call(c,c.data())})})}(jQuery),+function(a){"use strict";function b(b){var c,d=b.attr("data-target")||(c=b.attr("href"))&&c.replace(/.*(?=#[^\s]+$)/,"");return a(d)}function c(b){return this.each(function(){var c=a(this),e=c.data("bs.collapse"),f=a.extend({},d.DEFAULTS,c.data(),"object"==typeof b&&b);!e&&f.toggle&&/show|hide/.test(b)&&(f.toggle=!1),e||c.data("bs.collapse",e=new d(this,f)),"string"==typeof b&&e[b]()})}var d=function(b,c){this.$element=a(b),this.options=a.extend({},d.DEFAULTS,c),this.$trigger=a('[data-toggle="collapse"][href="#'+b.id+'"],[data-toggle="collapse"][data-target="#'+b.id+'"]'),this.transitioning=null,this.options.parent?this.$parent=this.getParent():this.addAriaAndCollapsedClass(this.$element,this.$trigger),this.options.toggle&&this.toggle()};d.VERSION="3.3.6",d.TRANSITION_DURATION=350,d.DEFAULTS={toggle:!0},d.prototype.dimension=function(){var a=this.$element.hasClass("width");return a?"width":"height"},d.prototype.show=function(){if(!this.transitioning&&!this.$element.hasClass("in")){var b,e=this.$parent&&this.$parent.children(".panel").children(".in, .collapsing");if(!(e&&e.length&&(b=e.data("bs.collapse"),b&&b.transitioning))){var f=a.Event("show.bs.collapse");if(this.$element.trigger(f),!f.isDefaultPrevented()){e&&e.length&&(c.call(e,"hide"),b||e.data("bs.collapse",null));var g=this.dimension();this.$element.removeClass("collapse").addClass("collapsing")[g](0).attr("aria-expanded",!0),this.$trigger.removeClass("collapsed").attr("aria-expanded",!0),this.transitioning=1;var h=function(){this.$element.removeClass("collapsing").addClass("collapse in")[g](""),this.transitioning=0,this.$element.trigger("shown.bs.collapse")};if(!a.support.transition)return h.call(this);var i=a.camelCase(["scroll",g].join("-"));this.$element.one("bsTransitionEnd",a.proxy(h,this)).emulateTransitionEnd(d.TRANSITION_DURATION)[g](this.$element[0][i])}}}},d.prototype.hide=function(){if(!this.transitioning&&this.$element.hasClass("in")){var b=a.Event("hide.bs.collapse");if(this.$element.trigger(b),!b.isDefaultPrevented()){var c=this.dimension();this.$element[c](this.$element[c]())[0].offsetHeight,this.$element.addClass("collapsing").removeClass("collapse in").attr("aria-expanded",!1),this.$trigger.addClass("collapsed").attr("aria-expanded",!1),this.transitioning=1;var e=function(){this.transitioning=0,this.$element.removeClass("collapsing").addClass("collapse").trigger("hidden.bs.collapse")};return a.support.transition?void this.$element[c](0).one("bsTransitionEnd",a.proxy(e,this)).emulateTransitionEnd(d.TRANSITION_DURATION):e.call(this)}}},d.prototype.toggle=function(){this[this.$element.hasClass("in")?"hide":"show"]()},d.prototype.getParent=function(){return a(this.options.parent).find('[data-toggle="collapse"][data-parent="'+this.options.parent+'"]').each(a.proxy(function(c,d){var e=a(d);this.addAriaAndCollapsedClass(b(e),e)},this)).end()},d.prototype.addAriaAndCollapsedClass=function(a,b){var c=a.hasClass("in");a.attr("aria-expanded",c),b.toggleClass("collapsed",!c).attr("aria-expanded",c)};var e=a.fn.collapse;a.fn.collapse=c,a.fn.collapse.Constructor=d,a.fn.collapse.noConflict=function(){return a.fn.collapse=e,this},a(document).on("click.bs.collapse.data-api",'[data-toggle="collapse"]',function(d){var e=a(this);e.attr("data-target")||d.preventDefault();var f=b(e),g=f.data("bs.collapse"),h=g?"toggle":e.data();c.call(f,h)})}(jQuery),+function(a){"use strict";function b(b){var c=b.attr("data-target");c||(c=b.attr("href"),c=c&&/#[A-Za-z]/.test(c)&&c.replace(/.*(?=#[^\s]*$)/,""));var d=c&&a(c);return d&&d.length?d:b.parent()}function c(c){c&&3===c.which||(a(e).remove(),a(f).each(function(){var d=a(this),e=b(d),f={relatedTarget:this};e.hasClass("open")&&(c&&"click"==c.type&&/input|textarea/i.test(c.target.tagName)&&a.contains(e[0],c.target)||(e.trigger(c=a.Event("hide.bs.dropdown",f)),c.isDefaultPrevented()||(d.attr("aria-expanded","false"),e.removeClass("open").trigger(a.Event("hidden.bs.dropdown",f)))))}))}function d(b){return this.each(function(){var c=a(this),d=c.data("bs.dropdown");d||c.data("bs.dropdown",d=new g(this)),"string"==typeof b&&d[b].call(c)})}var e=".dropdown-backdrop",f='[data-toggle="dropdown"]',g=function(b){a(b).on("click.bs.dropdown",this.toggle)};g.VERSION="3.3.6",g.prototype.toggle=function(d){var e=a(this);if(!e.is(".disabled, :disabled")){var f=b(e),g=f.hasClass("open");if(c(),!g){"ontouchstart"in document.documentElement&&!f.closest(".navbar-nav").length&&a(document.createElement("div")).addClass("dropdown-backdrop").insertAfter(a(this)).on("click",c);var h={relatedTarget:this};if(f.trigger(d=a.Event("show.bs.dropdown",h)),d.isDefaultPrevented())return;e.trigger("focus").attr("aria-expanded","true"),f.toggleClass("open").trigger(a.Event("shown.bs.dropdown",h))}return!1}},g.prototype.keydown=function(c){if(/(38|40|27|32)/.test(c.which)&&!/input|textarea/i.test(c.target.tagName)){var d=a(this);if(c.preventDefault(),c.stopPropagation(),!d.is(".disabled, :disabled")){var e=b(d),g=e.hasClass("open");if(!g&&27!=c.which||g&&27==c.which)return 27==c.which&&e.find(f).trigger("focus"),d.trigger("click");var h=" li:not(.disabled):visible a",i=e.find(".dropdown-menu"+h);if(i.length){var j=i.index(c.target);38==c.which&&j>0&&j--,40==c.which&&jdocument.documentElement.clientHeight;this.$element.css({paddingLeft:!this.bodyIsOverflowing&&a?this.scrollbarWidth:"",paddingRight:this.bodyIsOverflowing&&!a?this.scrollbarWidth:""})},c.prototype.resetAdjustments=function(){this.$element.css({paddingLeft:"",paddingRight:""})},c.prototype.checkScrollbar=function(){var a=window.innerWidth;if(!a){var b=document.documentElement.getBoundingClientRect();a=b.right-Math.abs(b.left)}this.bodyIsOverflowing=document.body.clientWidth
    ',trigger:"hover focus",title:"",delay:0,html:!1,container:!1,viewport:{selector:"body",padding:0}},c.prototype.init=function(b,c,d){if(this.enabled=!0,this.type=b,this.$element=a(c),this.options=this.getOptions(d),this.$viewport=this.options.viewport&&a(a.isFunction(this.options.viewport)?this.options.viewport.call(this,this.$element):this.options.viewport.selector||this.options.viewport),this.inState={click:!1,hover:!1,focus:!1},this.$element[0]instanceof document.constructor&&!this.options.selector)throw new Error("`selector` option must be specified when initializing "+this.type+" on the window.document object!");for(var e=this.options.trigger.split(" "),f=e.length;f--;){var g=e[f];if("click"==g)this.$element.on("click."+this.type,this.options.selector,a.proxy(this.toggle,this));else if("manual"!=g){var h="hover"==g?"mouseenter":"focusin",i="hover"==g?"mouseleave":"focusout";this.$element.on(h+"."+this.type,this.options.selector,a.proxy(this.enter,this)),this.$element.on(i+"."+this.type,this.options.selector,a.proxy(this.leave,this))}}this.options.selector?this._options=a.extend({},this.options,{trigger:"manual",selector:""}):this.fixTitle()},c.prototype.getDefaults=function(){return c.DEFAULTS},c.prototype.getOptions=function(b){return b=a.extend({},this.getDefaults(),this.$element.data(),b),b.delay&&"number"==typeof b.delay&&(b.delay={show:b.delay,hide:b.delay}),b},c.prototype.getDelegateOptions=function(){var b={},c=this.getDefaults();return this._options&&a.each(this._options,function(a,d){c[a]!=d&&(b[a]=d)}),b},c.prototype.enter=function(b){var c=b instanceof this.constructor?b:a(b.currentTarget).data("bs."+this.type);return c||(c=new this.constructor(b.currentTarget,this.getDelegateOptions()),a(b.currentTarget).data("bs."+this.type,c)),b instanceof a.Event&&(c.inState["focusin"==b.type?"focus":"hover"]=!0),c.tip().hasClass("in")||"in"==c.hoverState?void(c.hoverState="in"):(clearTimeout(c.timeout),c.hoverState="in",c.options.delay&&c.options.delay.show?void(c.timeout=setTimeout(function(){"in"==c.hoverState&&c.show()},c.options.delay.show)):c.show())},c.prototype.isInStateTrue=function(){for(var a in this.inState)if(this.inState[a])return!0;return!1},c.prototype.leave=function(b){var c=b instanceof this.constructor?b:a(b.currentTarget).data("bs."+this.type);return c||(c=new this.constructor(b.currentTarget,this.getDelegateOptions()),a(b.currentTarget).data("bs."+this.type,c)),b instanceof a.Event&&(c.inState["focusout"==b.type?"focus":"hover"]=!1),c.isInStateTrue()?void 0:(clearTimeout(c.timeout),c.hoverState="out",c.options.delay&&c.options.delay.hide?void(c.timeout=setTimeout(function(){"out"==c.hoverState&&c.hide()},c.options.delay.hide)):c.hide())},c.prototype.show=function(){var b=a.Event("show.bs."+this.type);if(this.hasContent()&&this.enabled){this.$element.trigger(b);var d=a.contains(this.$element[0].ownerDocument.documentElement,this.$element[0]);if(b.isDefaultPrevented()||!d)return;var e=this,f=this.tip(),g=this.getUID(this.type);this.setContent(),f.attr("id",g),this.$element.attr("aria-describedby",g),this.options.animation&&f.addClass("fade");var h="function"==typeof this.options.placement?this.options.placement.call(this,f[0],this.$element[0]):this.options.placement,i=/\s?auto?\s?/i,j=i.test(h);j&&(h=h.replace(i,"")||"top"),f.detach().css({top:0,left:0,display:"block"}).addClass(h).data("bs."+this.type,this),this.options.container?f.appendTo(this.options.container):f.insertAfter(this.$element),this.$element.trigger("inserted.bs."+this.type);var k=this.getPosition(),l=f[0].offsetWidth,m=f[0].offsetHeight;if(j){var n=h,o=this.getPosition(this.$viewport);h="bottom"==h&&k.bottom+m>o.bottom?"top":"top"==h&&k.top-mo.width?"left":"left"==h&&k.left-lg.top+g.height&&(e.top=g.top+g.height-i)}else{var j=b.left-f,k=b.left+f+c;jg.right&&(e.left=g.left+g.width-k)}return e},c.prototype.getTitle=function(){var a,b=this.$element,c=this.options;return a=b.attr("data-original-title")||("function"==typeof c.title?c.title.call(b[0]):c.title)},c.prototype.getUID=function(a){do a+=~~(1e6*Math.random());while(document.getElementById(a));return a},c.prototype.tip=function(){if(!this.$tip&&(this.$tip=a(this.options.template),1!=this.$tip.length))throw new Error(this.type+" `template` option must consist of exactly 1 top-level element!");return this.$tip},c.prototype.arrow=function(){return this.$arrow=this.$arrow||this.tip().find(".tooltip-arrow")},c.prototype.enable=function(){this.enabled=!0},c.prototype.disable=function(){this.enabled=!1},c.prototype.toggleEnabled=function(){this.enabled=!this.enabled},c.prototype.toggle=function(b){var c=this;b&&(c=a(b.currentTarget).data("bs."+this.type),c||(c=new this.constructor(b.currentTarget,this.getDelegateOptions()),a(b.currentTarget).data("bs."+this.type,c))),b?(c.inState.click=!c.inState.click,c.isInStateTrue()?c.enter(c):c.leave(c)):c.tip().hasClass("in")?c.leave(c):c.enter(c)},c.prototype.destroy=function(){var a=this;clearTimeout(this.timeout),this.hide(function(){a.$element.off("."+a.type).removeData("bs."+a.type),a.$tip&&a.$tip.detach(),a.$tip=null,a.$arrow=null,a.$viewport=null})};var d=a.fn.tooltip;a.fn.tooltip=b,a.fn.tooltip.Constructor=c,a.fn.tooltip.noConflict=function(){return a.fn.tooltip=d,this}}(jQuery),+function(a){"use strict";function b(b){return this.each(function(){var d=a(this),e=d.data("bs.popover"),f="object"==typeof b&&b;(e||!/destroy|hide/.test(b))&&(e||d.data("bs.popover",e=new c(this,f)),"string"==typeof b&&e[b]())})}var c=function(a,b){this.init("popover",a,b)};if(!a.fn.tooltip)throw new Error("Popover requires tooltip.js");c.VERSION="3.3.6",c.DEFAULTS=a.extend({},a.fn.tooltip.Constructor.DEFAULTS,{placement:"right",trigger:"click",content:"",template:''}),c.prototype=a.extend({},a.fn.tooltip.Constructor.prototype),c.prototype.constructor=c,c.prototype.getDefaults=function(){return c.DEFAULTS},c.prototype.setContent=function(){var a=this.tip(),b=this.getTitle(),c=this.getContent();a.find(".popover-title")[this.options.html?"html":"text"](b),a.find(".popover-content").children().detach().end()[this.options.html?"string"==typeof c?"html":"append":"text"](c),a.removeClass("fade top bottom left right in"),a.find(".popover-title").html()||a.find(".popover-title").hide()},c.prototype.hasContent=function(){return this.getTitle()||this.getContent()},c.prototype.getContent=function(){var a=this.$element,b=this.options;return a.attr("data-content")||("function"==typeof b.content?b.content.call(a[0]):b.content)},c.prototype.arrow=function(){return this.$arrow=this.$arrow||this.tip().find(".arrow")};var d=a.fn.popover;a.fn.popover=b,a.fn.popover.Constructor=c,a.fn.popover.noConflict=function(){return a.fn.popover=d,this}}(jQuery),+function(a){"use strict";function b(c,d){this.$body=a(document.body),this.$scrollElement=a(a(c).is(document.body)?window:c),this.options=a.extend({},b.DEFAULTS,d),this.selector=(this.options.target||"")+" .nav li > a",this.offsets=[],this.targets=[],this.activeTarget=null,this.scrollHeight=0,this.$scrollElement.on("scroll.bs.scrollspy",a.proxy(this.process,this)),this.refresh(),this.process()}function c(c){return this.each(function(){var d=a(this),e=d.data("bs.scrollspy"),f="object"==typeof c&&c;e||d.data("bs.scrollspy",e=new b(this,f)),"string"==typeof c&&e[c]()})}b.VERSION="3.3.6",b.DEFAULTS={offset:10},b.prototype.getScrollHeight=function(){return this.$scrollElement[0].scrollHeight||Math.max(this.$body[0].scrollHeight,document.documentElement.scrollHeight)},b.prototype.refresh=function(){var b=this,c="offset",d=0;this.offsets=[],this.targets=[],this.scrollHeight=this.getScrollHeight(),a.isWindow(this.$scrollElement[0])||(c="position",d=this.$scrollElement.scrollTop()),this.$body.find(this.selector).map(function(){var b=a(this),e=b.data("target")||b.attr("href"),f=/^#./.test(e)&&a(e);return f&&f.length&&f.is(":visible")&&[[f[c]().top+d,e]]||null}).sort(function(a,b){return a[0]-b[0]}).each(function(){b.offsets.push(this[0]),b.targets.push(this[1])})},b.prototype.process=function(){var a,b=this.$scrollElement.scrollTop()+this.options.offset,c=this.getScrollHeight(),d=this.options.offset+c-this.$scrollElement.height(),e=this.offsets,f=this.targets,g=this.activeTarget;if(this.scrollHeight!=c&&this.refresh(),b>=d)return g!=(a=f[f.length-1])&&this.activate(a);if(g&&b=e[a]&&(void 0===e[a+1]||b .dropdown-menu > .active").removeClass("active").end().find('[data-toggle="tab"]').attr("aria-expanded",!1),b.addClass("active").find('[data-toggle="tab"]').attr("aria-expanded",!0),h?(b[0].offsetWidth,b.addClass("in")):b.removeClass("fade"),b.parent(".dropdown-menu").length&&b.closest("li.dropdown").addClass("active").end().find('[data-toggle="tab"]').attr("aria-expanded",!0),e&&e()}var g=d.find("> .active"),h=e&&a.support.transition&&(g.length&&g.hasClass("fade")||!!d.find("> .fade").length);g.length&&h?g.one("bsTransitionEnd",f).emulateTransitionEnd(c.TRANSITION_DURATION):f(),g.removeClass("in")};var d=a.fn.tab;a.fn.tab=b,a.fn.tab.Constructor=c,a.fn.tab.noConflict=function(){return a.fn.tab=d,this};var e=function(c){c.preventDefault(),b.call(a(this),"show")};a(document).on("click.bs.tab.data-api",'[data-toggle="tab"]',e).on("click.bs.tab.data-api",'[data-toggle="pill"]',e)}(jQuery),+function(a){"use strict";function b(b){return this.each(function(){var d=a(this),e=d.data("bs.affix"),f="object"==typeof b&&b;e||d.data("bs.affix",e=new c(this,f)),"string"==typeof b&&e[b]()})}var c=function(b,d){this.options=a.extend({},c.DEFAULTS,d),this.$target=a(this.options.target).on("scroll.bs.affix.data-api",a.proxy(this.checkPosition,this)).on("click.bs.affix.data-api",a.proxy(this.checkPositionWithEventLoop,this)),this.$element=a(b),this.affixed=null,this.unpin=null,this.pinnedOffset=null,this.checkPosition()};c.VERSION="3.3.6",c.RESET="affix affix-top affix-bottom",c.DEFAULTS={offset:0,target:window},c.prototype.getState=function(a,b,c,d){var e=this.$target.scrollTop(),f=this.$element.offset(),g=this.$target.height();if(null!=c&&"top"==this.affixed)return c>e?"top":!1;if("bottom"==this.affixed)return null!=c?e+this.unpin<=f.top?!1:"bottom":a-d>=e+g?!1:"bottom";var h=null==this.affixed,i=h?e:f.top,j=h?g:b;return null!=c&&c>=e?"top":null!=d&&i+j>=a-d?"bottom":!1},c.prototype.getPinnedOffset=function(){if(this.pinnedOffset)return this.pinnedOffset;this.$element.removeClass(c.RESET).addClass("affix");var a=this.$target.scrollTop(),b=this.$element.offset();return this.pinnedOffset=b.top-a},c.prototype.checkPositionWithEventLoop=function(){setTimeout(a.proxy(this.checkPosition,this),1)},c.prototype.checkPosition=function(){if(this.$element.is(":visible")){var b=this.$element.height(),d=this.options.offset,e=d.top,f=d.bottom,g=Math.max(a(document).height(),a(document.body).height());"object"!=typeof d&&(f=e=d),"function"==typeof e&&(e=d.top(this.$element)),"function"==typeof f&&(f=d.bottom(this.$element));var h=this.getState(g,b,e,f);if(this.affixed!=h){null!=this.unpin&&this.$element.css("top","");var i="affix"+(h?"-"+h:""),j=a.Event(i+".bs.affix");if(this.$element.trigger(j),j.isDefaultPrevented())return;this.affixed=h,this.unpin="bottom"==h?this.getPinnedOffset():null,this.$element.removeClass(c.RESET).addClass(i).trigger(i.replace("affix","affixed")+".bs.affix")}"bottom"==h&&this.$element.offset({top:g-b-f})}};var d=a.fn.affix;a.fn.affix=b,a.fn.affix.Constructor=c,a.fn.affix.noConflict=function(){return a.fn.affix=d,this},a(window).on("load",function(){a('[data-spy="affix"]').each(function(){var c=a(this),d=c.data();d.offset=d.offset||{},null!=d.offsetBottom&&(d.offset.bottom=d.offsetBottom),null!=d.offsetTop&&(d.offset.top=d.offsetTop),b.call(c,d)})})}(jQuery); \ No newline at end of file diff --git a/admin/static/js/i18n/angular-locale_fr-fr.js b/frontend/static/js/i18n/angular-locale_fr-fr.js similarity index 90% rename from admin/static/js/i18n/angular-locale_fr-fr.js rename to frontend/static/js/i18n/angular-locale_fr-fr.js index 2e6dbbbb..745a956b 100644 --- a/admin/static/js/i18n/angular-locale_fr-fr.js +++ b/frontend/static/js/i18n/angular-locale_fr-fr.js @@ -63,18 +63,18 @@ $provide.value("$locale", { "d\u00e9c." ], "STANDALONEMONTH": [ - "janvier", - "f\u00e9vrier", - "mars", - "avril", - "mai", - "juin", - "juillet", - "ao\u00fbt", - "septembre", - "octobre", - "novembre", - "d\u00e9cembre" + "Janvier", + "F\u00e9vrier", + "Mars", + "Avril", + "Mai", + "Juin", + "Juillet", + "Ao\u00fbt", + "Septembre", + "Octobre", + "Novembre", + "D\u00e9cembre" ], "WEEKENDRANGE": [ 5, @@ -119,7 +119,6 @@ $provide.value("$locale", { ] }, "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;} }); }]); diff --git a/frontend/static/js/jquery.min.js b/frontend/static/js/jquery.min.js new file mode 100644 index 00000000..6c60672f --- /dev/null +++ b/frontend/static/js/jquery.min.js @@ -0,0 +1,5 @@ +/*! jQuery v1.12.0 | (c) jQuery Foundation | jquery.org/license */ +!function(a,b){"object"==typeof module&&"object"==typeof module.exports?module.exports=a.document?b(a,!0):function(a){if(!a.document)throw new Error("jQuery requires a window with a document");return b(a)}:b(a)}("undefined"!=typeof window?window:this,function(a,b){var c=[],d=a.document,e=c.slice,f=c.concat,g=c.push,h=c.indexOf,i={},j=i.toString,k=i.hasOwnProperty,l={},m="1.12.0",n=function(a,b){return new n.fn.init(a,b)},o=/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g,p=/^-ms-/,q=/-([\da-z])/gi,r=function(a,b){return b.toUpperCase()};n.fn=n.prototype={jquery:m,constructor:n,selector:"",length:0,toArray:function(){return e.call(this)},get:function(a){return null!=a?0>a?this[a+this.length]:this[a]:e.call(this)},pushStack:function(a){var b=n.merge(this.constructor(),a);return b.prevObject=this,b.context=this.context,b},each:function(a){return n.each(this,a)},map:function(a){return this.pushStack(n.map(this,function(b,c){return a.call(b,c,b)}))},slice:function(){return this.pushStack(e.apply(this,arguments))},first:function(){return this.eq(0)},last:function(){return this.eq(-1)},eq:function(a){var b=this.length,c=+a+(0>a?b:0);return this.pushStack(c>=0&&b>c?[this[c]]:[])},end:function(){return this.prevObject||this.constructor()},push:g,sort:c.sort,splice:c.splice},n.extend=n.fn.extend=function(){var a,b,c,d,e,f,g=arguments[0]||{},h=1,i=arguments.length,j=!1;for("boolean"==typeof g&&(j=g,g=arguments[h]||{},h++),"object"==typeof g||n.isFunction(g)||(g={}),h===i&&(g=this,h--);i>h;h++)if(null!=(e=arguments[h]))for(d in e)a=g[d],c=e[d],g!==c&&(j&&c&&(n.isPlainObject(c)||(b=n.isArray(c)))?(b?(b=!1,f=a&&n.isArray(a)?a:[]):f=a&&n.isPlainObject(a)?a:{},g[d]=n.extend(j,f,c)):void 0!==c&&(g[d]=c));return g},n.extend({expando:"jQuery"+(m+Math.random()).replace(/\D/g,""),isReady:!0,error:function(a){throw new Error(a)},noop:function(){},isFunction:function(a){return"function"===n.type(a)},isArray:Array.isArray||function(a){return"array"===n.type(a)},isWindow:function(a){return null!=a&&a==a.window},isNumeric:function(a){var b=a&&a.toString();return!n.isArray(a)&&b-parseFloat(b)+1>=0},isEmptyObject:function(a){var b;for(b in a)return!1;return!0},isPlainObject:function(a){var b;if(!a||"object"!==n.type(a)||a.nodeType||n.isWindow(a))return!1;try{if(a.constructor&&!k.call(a,"constructor")&&!k.call(a.constructor.prototype,"isPrototypeOf"))return!1}catch(c){return!1}if(!l.ownFirst)for(b in a)return k.call(a,b);for(b in a);return void 0===b||k.call(a,b)},type:function(a){return null==a?a+"":"object"==typeof a||"function"==typeof a?i[j.call(a)]||"object":typeof a},globalEval:function(b){b&&n.trim(b)&&(a.execScript||function(b){a.eval.call(a,b)})(b)},camelCase:function(a){return a.replace(p,"ms-").replace(q,r)},nodeName:function(a,b){return a.nodeName&&a.nodeName.toLowerCase()===b.toLowerCase()},each:function(a,b){var c,d=0;if(s(a)){for(c=a.length;c>d;d++)if(b.call(a[d],d,a[d])===!1)break}else for(d in a)if(b.call(a[d],d,a[d])===!1)break;return a},trim:function(a){return null==a?"":(a+"").replace(o,"")},makeArray:function(a,b){var c=b||[];return null!=a&&(s(Object(a))?n.merge(c,"string"==typeof a?[a]:a):g.call(c,a)),c},inArray:function(a,b,c){var d;if(b){if(h)return h.call(b,a,c);for(d=b.length,c=c?0>c?Math.max(0,d+c):c:0;d>c;c++)if(c in b&&b[c]===a)return c}return-1},merge:function(a,b){var c=+b.length,d=0,e=a.length;while(c>d)a[e++]=b[d++];if(c!==c)while(void 0!==b[d])a[e++]=b[d++];return a.length=e,a},grep:function(a,b,c){for(var d,e=[],f=0,g=a.length,h=!c;g>f;f++)d=!b(a[f],f),d!==h&&e.push(a[f]);return e},map:function(a,b,c){var d,e,g=0,h=[];if(s(a))for(d=a.length;d>g;g++)e=b(a[g],g,c),null!=e&&h.push(e);else for(g in a)e=b(a[g],g,c),null!=e&&h.push(e);return f.apply([],h)},guid:1,proxy:function(a,b){var c,d,f;return"string"==typeof b&&(f=a[b],b=a,a=f),n.isFunction(a)?(c=e.call(arguments,2),d=function(){return a.apply(b||this,c.concat(e.call(arguments)))},d.guid=a.guid=a.guid||n.guid++,d):void 0},now:function(){return+new Date},support:l}),"function"==typeof Symbol&&(n.fn[Symbol.iterator]=c[Symbol.iterator]),n.each("Boolean Number String Function Array Date RegExp Object Error Symbol".split(" "),function(a,b){i["[object "+b+"]"]=b.toLowerCase()});function s(a){var b=!!a&&"length"in a&&a.length,c=n.type(a);return"function"===c||n.isWindow(a)?!1:"array"===c||0===b||"number"==typeof b&&b>0&&b-1 in a}var t=function(a){var b,c,d,e,f,g,h,i,j,k,l,m,n,o,p,q,r,s,t,u="sizzle"+1*new Date,v=a.document,w=0,x=0,y=ga(),z=ga(),A=ga(),B=function(a,b){return a===b&&(l=!0),0},C=1<<31,D={}.hasOwnProperty,E=[],F=E.pop,G=E.push,H=E.push,I=E.slice,J=function(a,b){for(var c=0,d=a.length;d>c;c++)if(a[c]===b)return c;return-1},K="checked|selected|async|autofocus|autoplay|controls|defer|disabled|hidden|ismap|loop|multiple|open|readonly|required|scoped",L="[\\x20\\t\\r\\n\\f]",M="(?:\\\\.|[\\w-]|[^\\x00-\\xa0])+",N="\\["+L+"*("+M+")(?:"+L+"*([*^$|!~]?=)"+L+"*(?:'((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\"|("+M+"))|)"+L+"*\\]",O=":("+M+")(?:\\((('((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\")|((?:\\\\.|[^\\\\()[\\]]|"+N+")*)|.*)\\)|)",P=new RegExp(L+"+","g"),Q=new RegExp("^"+L+"+|((?:^|[^\\\\])(?:\\\\.)*)"+L+"+$","g"),R=new RegExp("^"+L+"*,"+L+"*"),S=new RegExp("^"+L+"*([>+~]|"+L+")"+L+"*"),T=new RegExp("="+L+"*([^\\]'\"]*?)"+L+"*\\]","g"),U=new RegExp(O),V=new RegExp("^"+M+"$"),W={ID:new RegExp("^#("+M+")"),CLASS:new RegExp("^\\.("+M+")"),TAG:new RegExp("^("+M+"|[*])"),ATTR:new RegExp("^"+N),PSEUDO:new RegExp("^"+O),CHILD:new RegExp("^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\("+L+"*(even|odd|(([+-]|)(\\d*)n|)"+L+"*(?:([+-]|)"+L+"*(\\d+)|))"+L+"*\\)|)","i"),bool:new RegExp("^(?:"+K+")$","i"),needsContext:new RegExp("^"+L+"*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\("+L+"*((?:-\\d)?\\d*)"+L+"*\\)|)(?=[^-]|$)","i")},X=/^(?:input|select|textarea|button)$/i,Y=/^h\d$/i,Z=/^[^{]+\{\s*\[native \w/,$=/^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/,_=/[+~]/,aa=/'|\\/g,ba=new RegExp("\\\\([\\da-f]{1,6}"+L+"?|("+L+")|.)","ig"),ca=function(a,b,c){var d="0x"+b-65536;return d!==d||c?b:0>d?String.fromCharCode(d+65536):String.fromCharCode(d>>10|55296,1023&d|56320)},da=function(){m()};try{H.apply(E=I.call(v.childNodes),v.childNodes),E[v.childNodes.length].nodeType}catch(ea){H={apply:E.length?function(a,b){G.apply(a,I.call(b))}:function(a,b){var c=a.length,d=0;while(a[c++]=b[d++]);a.length=c-1}}}function fa(a,b,d,e){var f,h,j,k,l,o,r,s,w=b&&b.ownerDocument,x=b?b.nodeType:9;if(d=d||[],"string"!=typeof a||!a||1!==x&&9!==x&&11!==x)return d;if(!e&&((b?b.ownerDocument||b:v)!==n&&m(b),b=b||n,p)){if(11!==x&&(o=$.exec(a)))if(f=o[1]){if(9===x){if(!(j=b.getElementById(f)))return d;if(j.id===f)return d.push(j),d}else if(w&&(j=w.getElementById(f))&&t(b,j)&&j.id===f)return d.push(j),d}else{if(o[2])return H.apply(d,b.getElementsByTagName(a)),d;if((f=o[3])&&c.getElementsByClassName&&b.getElementsByClassName)return H.apply(d,b.getElementsByClassName(f)),d}if(c.qsa&&!A[a+" "]&&(!q||!q.test(a))){if(1!==x)w=b,s=a;else if("object"!==b.nodeName.toLowerCase()){(k=b.getAttribute("id"))?k=k.replace(aa,"\\$&"):b.setAttribute("id",k=u),r=g(a),h=r.length,l=V.test(k)?"#"+k:"[id='"+k+"']";while(h--)r[h]=l+" "+qa(r[h]);s=r.join(","),w=_.test(a)&&oa(b.parentNode)||b}if(s)try{return H.apply(d,w.querySelectorAll(s)),d}catch(y){}finally{k===u&&b.removeAttribute("id")}}}return i(a.replace(Q,"$1"),b,d,e)}function ga(){var a=[];function b(c,e){return a.push(c+" ")>d.cacheLength&&delete b[a.shift()],b[c+" "]=e}return b}function ha(a){return a[u]=!0,a}function ia(a){var b=n.createElement("div");try{return!!a(b)}catch(c){return!1}finally{b.parentNode&&b.parentNode.removeChild(b),b=null}}function ja(a,b){var c=a.split("|"),e=c.length;while(e--)d.attrHandle[c[e]]=b}function ka(a,b){var c=b&&a,d=c&&1===a.nodeType&&1===b.nodeType&&(~b.sourceIndex||C)-(~a.sourceIndex||C);if(d)return d;if(c)while(c=c.nextSibling)if(c===b)return-1;return a?1:-1}function la(a){return function(b){var c=b.nodeName.toLowerCase();return"input"===c&&b.type===a}}function ma(a){return function(b){var c=b.nodeName.toLowerCase();return("input"===c||"button"===c)&&b.type===a}}function na(a){return ha(function(b){return b=+b,ha(function(c,d){var e,f=a([],c.length,b),g=f.length;while(g--)c[e=f[g]]&&(c[e]=!(d[e]=c[e]))})})}function oa(a){return a&&"undefined"!=typeof a.getElementsByTagName&&a}c=fa.support={},f=fa.isXML=function(a){var b=a&&(a.ownerDocument||a).documentElement;return b?"HTML"!==b.nodeName:!1},m=fa.setDocument=function(a){var b,e,g=a?a.ownerDocument||a:v;return g!==n&&9===g.nodeType&&g.documentElement?(n=g,o=n.documentElement,p=!f(n),(e=n.defaultView)&&e.top!==e&&(e.addEventListener?e.addEventListener("unload",da,!1):e.attachEvent&&e.attachEvent("onunload",da)),c.attributes=ia(function(a){return a.className="i",!a.getAttribute("className")}),c.getElementsByTagName=ia(function(a){return a.appendChild(n.createComment("")),!a.getElementsByTagName("*").length}),c.getElementsByClassName=Z.test(n.getElementsByClassName),c.getById=ia(function(a){return o.appendChild(a).id=u,!n.getElementsByName||!n.getElementsByName(u).length}),c.getById?(d.find.ID=function(a,b){if("undefined"!=typeof b.getElementById&&p){var c=b.getElementById(a);return c?[c]:[]}},d.filter.ID=function(a){var b=a.replace(ba,ca);return function(a){return a.getAttribute("id")===b}}):(delete d.find.ID,d.filter.ID=function(a){var b=a.replace(ba,ca);return function(a){var c="undefined"!=typeof a.getAttributeNode&&a.getAttributeNode("id");return c&&c.value===b}}),d.find.TAG=c.getElementsByTagName?function(a,b){return"undefined"!=typeof b.getElementsByTagName?b.getElementsByTagName(a):c.qsa?b.querySelectorAll(a):void 0}:function(a,b){var c,d=[],e=0,f=b.getElementsByTagName(a);if("*"===a){while(c=f[e++])1===c.nodeType&&d.push(c);return d}return f},d.find.CLASS=c.getElementsByClassName&&function(a,b){return"undefined"!=typeof b.getElementsByClassName&&p?b.getElementsByClassName(a):void 0},r=[],q=[],(c.qsa=Z.test(n.querySelectorAll))&&(ia(function(a){o.appendChild(a).innerHTML="",a.querySelectorAll("[msallowcapture^='']").length&&q.push("[*^$]="+L+"*(?:''|\"\")"),a.querySelectorAll("[selected]").length||q.push("\\["+L+"*(?:value|"+K+")"),a.querySelectorAll("[id~="+u+"-]").length||q.push("~="),a.querySelectorAll(":checked").length||q.push(":checked"),a.querySelectorAll("a#"+u+"+*").length||q.push(".#.+[+~]")}),ia(function(a){var b=n.createElement("input");b.setAttribute("type","hidden"),a.appendChild(b).setAttribute("name","D"),a.querySelectorAll("[name=d]").length&&q.push("name"+L+"*[*^$|!~]?="),a.querySelectorAll(":enabled").length||q.push(":enabled",":disabled"),a.querySelectorAll("*,:x"),q.push(",.*:")})),(c.matchesSelector=Z.test(s=o.matches||o.webkitMatchesSelector||o.mozMatchesSelector||o.oMatchesSelector||o.msMatchesSelector))&&ia(function(a){c.disconnectedMatch=s.call(a,"div"),s.call(a,"[s!='']:x"),r.push("!=",O)}),q=q.length&&new RegExp(q.join("|")),r=r.length&&new RegExp(r.join("|")),b=Z.test(o.compareDocumentPosition),t=b||Z.test(o.contains)?function(a,b){var c=9===a.nodeType?a.documentElement:a,d=b&&b.parentNode;return a===d||!(!d||1!==d.nodeType||!(c.contains?c.contains(d):a.compareDocumentPosition&&16&a.compareDocumentPosition(d)))}:function(a,b){if(b)while(b=b.parentNode)if(b===a)return!0;return!1},B=b?function(a,b){if(a===b)return l=!0,0;var d=!a.compareDocumentPosition-!b.compareDocumentPosition;return d?d:(d=(a.ownerDocument||a)===(b.ownerDocument||b)?a.compareDocumentPosition(b):1,1&d||!c.sortDetached&&b.compareDocumentPosition(a)===d?a===n||a.ownerDocument===v&&t(v,a)?-1:b===n||b.ownerDocument===v&&t(v,b)?1:k?J(k,a)-J(k,b):0:4&d?-1:1)}:function(a,b){if(a===b)return l=!0,0;var c,d=0,e=a.parentNode,f=b.parentNode,g=[a],h=[b];if(!e||!f)return a===n?-1:b===n?1:e?-1:f?1:k?J(k,a)-J(k,b):0;if(e===f)return ka(a,b);c=a;while(c=c.parentNode)g.unshift(c);c=b;while(c=c.parentNode)h.unshift(c);while(g[d]===h[d])d++;return d?ka(g[d],h[d]):g[d]===v?-1:h[d]===v?1:0},n):n},fa.matches=function(a,b){return fa(a,null,null,b)},fa.matchesSelector=function(a,b){if((a.ownerDocument||a)!==n&&m(a),b=b.replace(T,"='$1']"),c.matchesSelector&&p&&!A[b+" "]&&(!r||!r.test(b))&&(!q||!q.test(b)))try{var d=s.call(a,b);if(d||c.disconnectedMatch||a.document&&11!==a.document.nodeType)return d}catch(e){}return fa(b,n,null,[a]).length>0},fa.contains=function(a,b){return(a.ownerDocument||a)!==n&&m(a),t(a,b)},fa.attr=function(a,b){(a.ownerDocument||a)!==n&&m(a);var e=d.attrHandle[b.toLowerCase()],f=e&&D.call(d.attrHandle,b.toLowerCase())?e(a,b,!p):void 0;return void 0!==f?f:c.attributes||!p?a.getAttribute(b):(f=a.getAttributeNode(b))&&f.specified?f.value:null},fa.error=function(a){throw new Error("Syntax error, unrecognized expression: "+a)},fa.uniqueSort=function(a){var b,d=[],e=0,f=0;if(l=!c.detectDuplicates,k=!c.sortStable&&a.slice(0),a.sort(B),l){while(b=a[f++])b===a[f]&&(e=d.push(f));while(e--)a.splice(d[e],1)}return k=null,a},e=fa.getText=function(a){var b,c="",d=0,f=a.nodeType;if(f){if(1===f||9===f||11===f){if("string"==typeof a.textContent)return a.textContent;for(a=a.firstChild;a;a=a.nextSibling)c+=e(a)}else if(3===f||4===f)return a.nodeValue}else while(b=a[d++])c+=e(b);return c},d=fa.selectors={cacheLength:50,createPseudo:ha,match:W,attrHandle:{},find:{},relative:{">":{dir:"parentNode",first:!0}," ":{dir:"parentNode"},"+":{dir:"previousSibling",first:!0},"~":{dir:"previousSibling"}},preFilter:{ATTR:function(a){return a[1]=a[1].replace(ba,ca),a[3]=(a[3]||a[4]||a[5]||"").replace(ba,ca),"~="===a[2]&&(a[3]=" "+a[3]+" "),a.slice(0,4)},CHILD:function(a){return a[1]=a[1].toLowerCase(),"nth"===a[1].slice(0,3)?(a[3]||fa.error(a[0]),a[4]=+(a[4]?a[5]+(a[6]||1):2*("even"===a[3]||"odd"===a[3])),a[5]=+(a[7]+a[8]||"odd"===a[3])):a[3]&&fa.error(a[0]),a},PSEUDO:function(a){var b,c=!a[6]&&a[2];return W.CHILD.test(a[0])?null:(a[3]?a[2]=a[4]||a[5]||"":c&&U.test(c)&&(b=g(c,!0))&&(b=c.indexOf(")",c.length-b)-c.length)&&(a[0]=a[0].slice(0,b),a[2]=c.slice(0,b)),a.slice(0,3))}},filter:{TAG:function(a){var b=a.replace(ba,ca).toLowerCase();return"*"===a?function(){return!0}:function(a){return a.nodeName&&a.nodeName.toLowerCase()===b}},CLASS:function(a){var b=y[a+" "];return b||(b=new RegExp("(^|"+L+")"+a+"("+L+"|$)"))&&y(a,function(a){return b.test("string"==typeof a.className&&a.className||"undefined"!=typeof a.getAttribute&&a.getAttribute("class")||"")})},ATTR:function(a,b,c){return function(d){var e=fa.attr(d,a);return null==e?"!="===b:b?(e+="","="===b?e===c:"!="===b?e!==c:"^="===b?c&&0===e.indexOf(c):"*="===b?c&&e.indexOf(c)>-1:"$="===b?c&&e.slice(-c.length)===c:"~="===b?(" "+e.replace(P," ")+" ").indexOf(c)>-1:"|="===b?e===c||e.slice(0,c.length+1)===c+"-":!1):!0}},CHILD:function(a,b,c,d,e){var f="nth"!==a.slice(0,3),g="last"!==a.slice(-4),h="of-type"===b;return 1===d&&0===e?function(a){return!!a.parentNode}:function(b,c,i){var j,k,l,m,n,o,p=f!==g?"nextSibling":"previousSibling",q=b.parentNode,r=h&&b.nodeName.toLowerCase(),s=!i&&!h,t=!1;if(q){if(f){while(p){m=b;while(m=m[p])if(h?m.nodeName.toLowerCase()===r:1===m.nodeType)return!1;o=p="only"===a&&!o&&"nextSibling"}return!0}if(o=[g?q.firstChild:q.lastChild],g&&s){m=q,l=m[u]||(m[u]={}),k=l[m.uniqueID]||(l[m.uniqueID]={}),j=k[a]||[],n=j[0]===w&&j[1],t=n&&j[2],m=n&&q.childNodes[n];while(m=++n&&m&&m[p]||(t=n=0)||o.pop())if(1===m.nodeType&&++t&&m===b){k[a]=[w,n,t];break}}else if(s&&(m=b,l=m[u]||(m[u]={}),k=l[m.uniqueID]||(l[m.uniqueID]={}),j=k[a]||[],n=j[0]===w&&j[1],t=n),t===!1)while(m=++n&&m&&m[p]||(t=n=0)||o.pop())if((h?m.nodeName.toLowerCase()===r:1===m.nodeType)&&++t&&(s&&(l=m[u]||(m[u]={}),k=l[m.uniqueID]||(l[m.uniqueID]={}),k[a]=[w,t]),m===b))break;return t-=e,t===d||t%d===0&&t/d>=0}}},PSEUDO:function(a,b){var c,e=d.pseudos[a]||d.setFilters[a.toLowerCase()]||fa.error("unsupported pseudo: "+a);return e[u]?e(b):e.length>1?(c=[a,a,"",b],d.setFilters.hasOwnProperty(a.toLowerCase())?ha(function(a,c){var d,f=e(a,b),g=f.length;while(g--)d=J(a,f[g]),a[d]=!(c[d]=f[g])}):function(a){return e(a,0,c)}):e}},pseudos:{not:ha(function(a){var b=[],c=[],d=h(a.replace(Q,"$1"));return d[u]?ha(function(a,b,c,e){var f,g=d(a,null,e,[]),h=a.length;while(h--)(f=g[h])&&(a[h]=!(b[h]=f))}):function(a,e,f){return b[0]=a,d(b,null,f,c),b[0]=null,!c.pop()}}),has:ha(function(a){return function(b){return fa(a,b).length>0}}),contains:ha(function(a){return a=a.replace(ba,ca),function(b){return(b.textContent||b.innerText||e(b)).indexOf(a)>-1}}),lang:ha(function(a){return V.test(a||"")||fa.error("unsupported lang: "+a),a=a.replace(ba,ca).toLowerCase(),function(b){var c;do if(c=p?b.lang:b.getAttribute("xml:lang")||b.getAttribute("lang"))return c=c.toLowerCase(),c===a||0===c.indexOf(a+"-");while((b=b.parentNode)&&1===b.nodeType);return!1}}),target:function(b){var c=a.location&&a.location.hash;return c&&c.slice(1)===b.id},root:function(a){return a===o},focus:function(a){return a===n.activeElement&&(!n.hasFocus||n.hasFocus())&&!!(a.type||a.href||~a.tabIndex)},enabled:function(a){return a.disabled===!1},disabled:function(a){return a.disabled===!0},checked:function(a){var b=a.nodeName.toLowerCase();return"input"===b&&!!a.checked||"option"===b&&!!a.selected},selected:function(a){return a.parentNode&&a.parentNode.selectedIndex,a.selected===!0},empty:function(a){for(a=a.firstChild;a;a=a.nextSibling)if(a.nodeType<6)return!1;return!0},parent:function(a){return!d.pseudos.empty(a)},header:function(a){return Y.test(a.nodeName)},input:function(a){return X.test(a.nodeName)},button:function(a){var b=a.nodeName.toLowerCase();return"input"===b&&"button"===a.type||"button"===b},text:function(a){var b;return"input"===a.nodeName.toLowerCase()&&"text"===a.type&&(null==(b=a.getAttribute("type"))||"text"===b.toLowerCase())},first:na(function(){return[0]}),last:na(function(a,b){return[b-1]}),eq:na(function(a,b,c){return[0>c?c+b:c]}),even:na(function(a,b){for(var c=0;b>c;c+=2)a.push(c);return a}),odd:na(function(a,b){for(var c=1;b>c;c+=2)a.push(c);return a}),lt:na(function(a,b,c){for(var d=0>c?c+b:c;--d>=0;)a.push(d);return a}),gt:na(function(a,b,c){for(var d=0>c?c+b:c;++db;b++)d+=a[b].value;return d}function ra(a,b,c){var d=b.dir,e=c&&"parentNode"===d,f=x++;return b.first?function(b,c,f){while(b=b[d])if(1===b.nodeType||e)return a(b,c,f)}:function(b,c,g){var h,i,j,k=[w,f];if(g){while(b=b[d])if((1===b.nodeType||e)&&a(b,c,g))return!0}else while(b=b[d])if(1===b.nodeType||e){if(j=b[u]||(b[u]={}),i=j[b.uniqueID]||(j[b.uniqueID]={}),(h=i[d])&&h[0]===w&&h[1]===f)return k[2]=h[2];if(i[d]=k,k[2]=a(b,c,g))return!0}}}function sa(a){return a.length>1?function(b,c,d){var e=a.length;while(e--)if(!a[e](b,c,d))return!1;return!0}:a[0]}function ta(a,b,c){for(var d=0,e=b.length;e>d;d++)fa(a,b[d],c);return c}function ua(a,b,c,d,e){for(var f,g=[],h=0,i=a.length,j=null!=b;i>h;h++)(f=a[h])&&(!c||c(f,d,e))&&(g.push(f),j&&b.push(h));return g}function va(a,b,c,d,e,f){return d&&!d[u]&&(d=va(d)),e&&!e[u]&&(e=va(e,f)),ha(function(f,g,h,i){var j,k,l,m=[],n=[],o=g.length,p=f||ta(b||"*",h.nodeType?[h]:h,[]),q=!a||!f&&b?p:ua(p,m,a,h,i),r=c?e||(f?a:o||d)?[]:g:q;if(c&&c(q,r,h,i),d){j=ua(r,n),d(j,[],h,i),k=j.length;while(k--)(l=j[k])&&(r[n[k]]=!(q[n[k]]=l))}if(f){if(e||a){if(e){j=[],k=r.length;while(k--)(l=r[k])&&j.push(q[k]=l);e(null,r=[],j,i)}k=r.length;while(k--)(l=r[k])&&(j=e?J(f,l):m[k])>-1&&(f[j]=!(g[j]=l))}}else r=ua(r===g?r.splice(o,r.length):r),e?e(null,g,r,i):H.apply(g,r)})}function wa(a){for(var b,c,e,f=a.length,g=d.relative[a[0].type],h=g||d.relative[" "],i=g?1:0,k=ra(function(a){return a===b},h,!0),l=ra(function(a){return J(b,a)>-1},h,!0),m=[function(a,c,d){var e=!g&&(d||c!==j)||((b=c).nodeType?k(a,c,d):l(a,c,d));return b=null,e}];f>i;i++)if(c=d.relative[a[i].type])m=[ra(sa(m),c)];else{if(c=d.filter[a[i].type].apply(null,a[i].matches),c[u]){for(e=++i;f>e;e++)if(d.relative[a[e].type])break;return va(i>1&&sa(m),i>1&&qa(a.slice(0,i-1).concat({value:" "===a[i-2].type?"*":""})).replace(Q,"$1"),c,e>i&&wa(a.slice(i,e)),f>e&&wa(a=a.slice(e)),f>e&&qa(a))}m.push(c)}return sa(m)}function xa(a,b){var c=b.length>0,e=a.length>0,f=function(f,g,h,i,k){var l,o,q,r=0,s="0",t=f&&[],u=[],v=j,x=f||e&&d.find.TAG("*",k),y=w+=null==v?1:Math.random()||.1,z=x.length;for(k&&(j=g===n||g||k);s!==z&&null!=(l=x[s]);s++){if(e&&l){o=0,g||l.ownerDocument===n||(m(l),h=!p);while(q=a[o++])if(q(l,g||n,h)){i.push(l);break}k&&(w=y)}c&&((l=!q&&l)&&r--,f&&t.push(l))}if(r+=s,c&&s!==r){o=0;while(q=b[o++])q(t,u,g,h);if(f){if(r>0)while(s--)t[s]||u[s]||(u[s]=F.call(i));u=ua(u)}H.apply(i,u),k&&!f&&u.length>0&&r+b.length>1&&fa.uniqueSort(i)}return k&&(w=y,j=v),t};return c?ha(f):f}return h=fa.compile=function(a,b){var c,d=[],e=[],f=A[a+" "];if(!f){b||(b=g(a)),c=b.length;while(c--)f=wa(b[c]),f[u]?d.push(f):e.push(f);f=A(a,xa(e,d)),f.selector=a}return f},i=fa.select=function(a,b,e,f){var i,j,k,l,m,n="function"==typeof a&&a,o=!f&&g(a=n.selector||a);if(e=e||[],1===o.length){if(j=o[0]=o[0].slice(0),j.length>2&&"ID"===(k=j[0]).type&&c.getById&&9===b.nodeType&&p&&d.relative[j[1].type]){if(b=(d.find.ID(k.matches[0].replace(ba,ca),b)||[])[0],!b)return e;n&&(b=b.parentNode),a=a.slice(j.shift().value.length)}i=W.needsContext.test(a)?0:j.length;while(i--){if(k=j[i],d.relative[l=k.type])break;if((m=d.find[l])&&(f=m(k.matches[0].replace(ba,ca),_.test(j[0].type)&&oa(b.parentNode)||b))){if(j.splice(i,1),a=f.length&&qa(j),!a)return H.apply(e,f),e;break}}}return(n||h(a,o))(f,b,!p,e,!b||_.test(a)&&oa(b.parentNode)||b),e},c.sortStable=u.split("").sort(B).join("")===u,c.detectDuplicates=!!l,m(),c.sortDetached=ia(function(a){return 1&a.compareDocumentPosition(n.createElement("div"))}),ia(function(a){return a.innerHTML="","#"===a.firstChild.getAttribute("href")})||ja("type|href|height|width",function(a,b,c){return c?void 0:a.getAttribute(b,"type"===b.toLowerCase()?1:2)}),c.attributes&&ia(function(a){return a.innerHTML="",a.firstChild.setAttribute("value",""),""===a.firstChild.getAttribute("value")})||ja("value",function(a,b,c){return c||"input"!==a.nodeName.toLowerCase()?void 0:a.defaultValue}),ia(function(a){return null==a.getAttribute("disabled")})||ja(K,function(a,b,c){var d;return c?void 0:a[b]===!0?b.toLowerCase():(d=a.getAttributeNode(b))&&d.specified?d.value:null}),fa}(a);n.find=t,n.expr=t.selectors,n.expr[":"]=n.expr.pseudos,n.uniqueSort=n.unique=t.uniqueSort,n.text=t.getText,n.isXMLDoc=t.isXML,n.contains=t.contains;var u=function(a,b,c){var d=[],e=void 0!==c;while((a=a[b])&&9!==a.nodeType)if(1===a.nodeType){if(e&&n(a).is(c))break;d.push(a)}return d},v=function(a,b){for(var c=[];a;a=a.nextSibling)1===a.nodeType&&a!==b&&c.push(a);return c},w=n.expr.match.needsContext,x=/^<([\w-]+)\s*\/?>(?:<\/\1>|)$/,y=/^.[^:#\[\.,]*$/;function z(a,b,c){if(n.isFunction(b))return n.grep(a,function(a,d){return!!b.call(a,d,a)!==c});if(b.nodeType)return n.grep(a,function(a){return a===b!==c});if("string"==typeof b){if(y.test(b))return n.filter(b,a,c);b=n.filter(b,a)}return n.grep(a,function(a){return n.inArray(a,b)>-1!==c})}n.filter=function(a,b,c){var d=b[0];return c&&(a=":not("+a+")"),1===b.length&&1===d.nodeType?n.find.matchesSelector(d,a)?[d]:[]:n.find.matches(a,n.grep(b,function(a){return 1===a.nodeType}))},n.fn.extend({find:function(a){var b,c=[],d=this,e=d.length;if("string"!=typeof a)return this.pushStack(n(a).filter(function(){for(b=0;e>b;b++)if(n.contains(d[b],this))return!0}));for(b=0;e>b;b++)n.find(a,d[b],c);return c=this.pushStack(e>1?n.unique(c):c),c.selector=this.selector?this.selector+" "+a:a,c},filter:function(a){return this.pushStack(z(this,a||[],!1))},not:function(a){return this.pushStack(z(this,a||[],!0))},is:function(a){return!!z(this,"string"==typeof a&&w.test(a)?n(a):a||[],!1).length}});var A,B=/^(?:\s*(<[\w\W]+>)[^>]*|#([\w-]*))$/,C=n.fn.init=function(a,b,c){var e,f;if(!a)return this;if(c=c||A,"string"==typeof a){if(e="<"===a.charAt(0)&&">"===a.charAt(a.length-1)&&a.length>=3?[null,a,null]:B.exec(a),!e||!e[1]&&b)return!b||b.jquery?(b||c).find(a):this.constructor(b).find(a);if(e[1]){if(b=b instanceof n?b[0]:b,n.merge(this,n.parseHTML(e[1],b&&b.nodeType?b.ownerDocument||b:d,!0)),x.test(e[1])&&n.isPlainObject(b))for(e in b)n.isFunction(this[e])?this[e](b[e]):this.attr(e,b[e]);return this}if(f=d.getElementById(e[2]),f&&f.parentNode){if(f.id!==e[2])return A.find(a);this.length=1,this[0]=f}return this.context=d,this.selector=a,this}return a.nodeType?(this.context=this[0]=a,this.length=1,this):n.isFunction(a)?"undefined"!=typeof c.ready?c.ready(a):a(n):(void 0!==a.selector&&(this.selector=a.selector,this.context=a.context),n.makeArray(a,this))};C.prototype=n.fn,A=n(d);var D=/^(?:parents|prev(?:Until|All))/,E={children:!0,contents:!0,next:!0,prev:!0};n.fn.extend({has:function(a){var b,c=n(a,this),d=c.length;return this.filter(function(){for(b=0;d>b;b++)if(n.contains(this,c[b]))return!0})},closest:function(a,b){for(var c,d=0,e=this.length,f=[],g=w.test(a)||"string"!=typeof a?n(a,b||this.context):0;e>d;d++)for(c=this[d];c&&c!==b;c=c.parentNode)if(c.nodeType<11&&(g?g.index(c)>-1:1===c.nodeType&&n.find.matchesSelector(c,a))){f.push(c);break}return this.pushStack(f.length>1?n.uniqueSort(f):f)},index:function(a){return a?"string"==typeof a?n.inArray(this[0],n(a)):n.inArray(a.jquery?a[0]:a,this):this[0]&&this[0].parentNode?this.first().prevAll().length:-1},add:function(a,b){return this.pushStack(n.uniqueSort(n.merge(this.get(),n(a,b))))},addBack:function(a){return this.add(null==a?this.prevObject:this.prevObject.filter(a))}});function F(a,b){do a=a[b];while(a&&1!==a.nodeType);return a}n.each({parent:function(a){var b=a.parentNode;return b&&11!==b.nodeType?b:null},parents:function(a){return u(a,"parentNode")},parentsUntil:function(a,b,c){return u(a,"parentNode",c)},next:function(a){return F(a,"nextSibling")},prev:function(a){return F(a,"previousSibling")},nextAll:function(a){return u(a,"nextSibling")},prevAll:function(a){return u(a,"previousSibling")},nextUntil:function(a,b,c){return u(a,"nextSibling",c)},prevUntil:function(a,b,c){return u(a,"previousSibling",c)},siblings:function(a){return v((a.parentNode||{}).firstChild,a)},children:function(a){return v(a.firstChild)},contents:function(a){return n.nodeName(a,"iframe")?a.contentDocument||a.contentWindow.document:n.merge([],a.childNodes)}},function(a,b){n.fn[a]=function(c,d){var e=n.map(this,b,c);return"Until"!==a.slice(-5)&&(d=c),d&&"string"==typeof d&&(e=n.filter(d,e)),this.length>1&&(E[a]||(e=n.uniqueSort(e)),D.test(a)&&(e=e.reverse())),this.pushStack(e)}});var G=/\S+/g;function H(a){var b={};return n.each(a.match(G)||[],function(a,c){b[c]=!0}),b}n.Callbacks=function(a){a="string"==typeof a?H(a):n.extend({},a);var b,c,d,e,f=[],g=[],h=-1,i=function(){for(e=a.once,d=b=!0;g.length;h=-1){c=g.shift();while(++h-1)f.splice(c,1),h>=c&&h--}),this},has:function(a){return a?n.inArray(a,f)>-1:f.length>0},empty:function(){return f&&(f=[]),this},disable:function(){return e=g=[],f=c="",this},disabled:function(){return!f},lock:function(){return e=!0,c||j.disable(),this},locked:function(){return!!e},fireWith:function(a,c){return e||(c=c||[],c=[a,c.slice?c.slice():c],g.push(c),b||i()),this},fire:function(){return j.fireWith(this,arguments),this},fired:function(){return!!d}};return j},n.extend({Deferred:function(a){var b=[["resolve","done",n.Callbacks("once memory"),"resolved"],["reject","fail",n.Callbacks("once memory"),"rejected"],["notify","progress",n.Callbacks("memory")]],c="pending",d={state:function(){return c},always:function(){return e.done(arguments).fail(arguments),this},then:function(){var a=arguments;return n.Deferred(function(c){n.each(b,function(b,f){var g=n.isFunction(a[b])&&a[b];e[f[1]](function(){var a=g&&g.apply(this,arguments);a&&n.isFunction(a.promise)?a.promise().progress(c.notify).done(c.resolve).fail(c.reject):c[f[0]+"With"](this===d?c.promise():this,g?[a]:arguments)})}),a=null}).promise()},promise:function(a){return null!=a?n.extend(a,d):d}},e={};return d.pipe=d.then,n.each(b,function(a,f){var g=f[2],h=f[3];d[f[1]]=g.add,h&&g.add(function(){c=h},b[1^a][2].disable,b[2][2].lock),e[f[0]]=function(){return e[f[0]+"With"](this===e?d:this,arguments),this},e[f[0]+"With"]=g.fireWith}),d.promise(e),a&&a.call(e,e),e},when:function(a){var b=0,c=e.call(arguments),d=c.length,f=1!==d||a&&n.isFunction(a.promise)?d:0,g=1===f?a:n.Deferred(),h=function(a,b,c){return function(d){b[a]=this,c[a]=arguments.length>1?e.call(arguments):d,c===i?g.notifyWith(b,c):--f||g.resolveWith(b,c)}},i,j,k;if(d>1)for(i=new Array(d),j=new Array(d),k=new Array(d);d>b;b++)c[b]&&n.isFunction(c[b].promise)?c[b].promise().progress(h(b,j,i)).done(h(b,k,c)).fail(g.reject):--f;return f||g.resolveWith(k,c),g.promise()}});var I;n.fn.ready=function(a){return n.ready.promise().done(a),this},n.extend({isReady:!1,readyWait:1,holdReady:function(a){a?n.readyWait++:n.ready(!0)},ready:function(a){(a===!0?--n.readyWait:n.isReady)||(n.isReady=!0,a!==!0&&--n.readyWait>0||(I.resolveWith(d,[n]),n.fn.triggerHandler&&(n(d).triggerHandler("ready"),n(d).off("ready"))))}});function J(){d.addEventListener?(d.removeEventListener("DOMContentLoaded",K),a.removeEventListener("load",K)):(d.detachEvent("onreadystatechange",K),a.detachEvent("onload",K))}function K(){(d.addEventListener||"load"===a.event.type||"complete"===d.readyState)&&(J(),n.ready())}n.ready.promise=function(b){if(!I)if(I=n.Deferred(),"complete"===d.readyState)a.setTimeout(n.ready);else if(d.addEventListener)d.addEventListener("DOMContentLoaded",K),a.addEventListener("load",K);else{d.attachEvent("onreadystatechange",K),a.attachEvent("onload",K);var c=!1;try{c=null==a.frameElement&&d.documentElement}catch(e){}c&&c.doScroll&&!function f(){if(!n.isReady){try{c.doScroll("left")}catch(b){return a.setTimeout(f,50)}J(),n.ready()}}()}return I.promise(b)},n.ready.promise();var L;for(L in n(l))break;l.ownFirst="0"===L,l.inlineBlockNeedsLayout=!1,n(function(){var a,b,c,e;c=d.getElementsByTagName("body")[0],c&&c.style&&(b=d.createElement("div"),e=d.createElement("div"),e.style.cssText="position:absolute;border:0;width:0;height:0;top:0;left:-9999px",c.appendChild(e).appendChild(b),"undefined"!=typeof b.style.zoom&&(b.style.cssText="display:inline;margin:0;border:0;padding:1px;width:1px;zoom:1",l.inlineBlockNeedsLayout=a=3===b.offsetWidth,a&&(c.style.zoom=1)),c.removeChild(e))}),function(){var a=d.createElement("div");l.deleteExpando=!0;try{delete a.test}catch(b){l.deleteExpando=!1}a=null}();var M=function(a){var b=n.noData[(a.nodeName+" ").toLowerCase()],c=+a.nodeType||1;return 1!==c&&9!==c?!1:!b||b!==!0&&a.getAttribute("classid")===b},N=/^(?:\{[\w\W]*\}|\[[\w\W]*\])$/,O=/([A-Z])/g;function P(a,b,c){if(void 0===c&&1===a.nodeType){var d="data-"+b.replace(O,"-$1").toLowerCase();if(c=a.getAttribute(d),"string"==typeof c){try{c="true"===c?!0:"false"===c?!1:"null"===c?null:+c+""===c?+c:N.test(c)?n.parseJSON(c):c}catch(e){}n.data(a,b,c)}else c=void 0}return c}function Q(a){var b;for(b in a)if(("data"!==b||!n.isEmptyObject(a[b]))&&"toJSON"!==b)return!1; +return!0}function R(a,b,d,e){if(M(a)){var f,g,h=n.expando,i=a.nodeType,j=i?n.cache:a,k=i?a[h]:a[h]&&h;if(k&&j[k]&&(e||j[k].data)||void 0!==d||"string"!=typeof b)return k||(k=i?a[h]=c.pop()||n.guid++:h),j[k]||(j[k]=i?{}:{toJSON:n.noop}),("object"==typeof b||"function"==typeof b)&&(e?j[k]=n.extend(j[k],b):j[k].data=n.extend(j[k].data,b)),g=j[k],e||(g.data||(g.data={}),g=g.data),void 0!==d&&(g[n.camelCase(b)]=d),"string"==typeof b?(f=g[b],null==f&&(f=g[n.camelCase(b)])):f=g,f}}function S(a,b,c){if(M(a)){var d,e,f=a.nodeType,g=f?n.cache:a,h=f?a[n.expando]:n.expando;if(g[h]){if(b&&(d=c?g[h]:g[h].data)){n.isArray(b)?b=b.concat(n.map(b,n.camelCase)):b in d?b=[b]:(b=n.camelCase(b),b=b in d?[b]:b.split(" ")),e=b.length;while(e--)delete d[b[e]];if(c?!Q(d):!n.isEmptyObject(d))return}(c||(delete g[h].data,Q(g[h])))&&(f?n.cleanData([a],!0):l.deleteExpando||g!=g.window?delete g[h]:g[h]=void 0)}}}n.extend({cache:{},noData:{"applet ":!0,"embed ":!0,"object ":"clsid:D27CDB6E-AE6D-11cf-96B8-444553540000"},hasData:function(a){return a=a.nodeType?n.cache[a[n.expando]]:a[n.expando],!!a&&!Q(a)},data:function(a,b,c){return R(a,b,c)},removeData:function(a,b){return S(a,b)},_data:function(a,b,c){return R(a,b,c,!0)},_removeData:function(a,b){return S(a,b,!0)}}),n.fn.extend({data:function(a,b){var c,d,e,f=this[0],g=f&&f.attributes;if(void 0===a){if(this.length&&(e=n.data(f),1===f.nodeType&&!n._data(f,"parsedAttrs"))){c=g.length;while(c--)g[c]&&(d=g[c].name,0===d.indexOf("data-")&&(d=n.camelCase(d.slice(5)),P(f,d,e[d])));n._data(f,"parsedAttrs",!0)}return e}return"object"==typeof a?this.each(function(){n.data(this,a)}):arguments.length>1?this.each(function(){n.data(this,a,b)}):f?P(f,a,n.data(f,a)):void 0},removeData:function(a){return this.each(function(){n.removeData(this,a)})}}),n.extend({queue:function(a,b,c){var d;return a?(b=(b||"fx")+"queue",d=n._data(a,b),c&&(!d||n.isArray(c)?d=n._data(a,b,n.makeArray(c)):d.push(c)),d||[]):void 0},dequeue:function(a,b){b=b||"fx";var c=n.queue(a,b),d=c.length,e=c.shift(),f=n._queueHooks(a,b),g=function(){n.dequeue(a,b)};"inprogress"===e&&(e=c.shift(),d--),e&&("fx"===b&&c.unshift("inprogress"),delete f.stop,e.call(a,g,f)),!d&&f&&f.empty.fire()},_queueHooks:function(a,b){var c=b+"queueHooks";return n._data(a,c)||n._data(a,c,{empty:n.Callbacks("once memory").add(function(){n._removeData(a,b+"queue"),n._removeData(a,c)})})}}),n.fn.extend({queue:function(a,b){var c=2;return"string"!=typeof a&&(b=a,a="fx",c--),arguments.lengthh;h++)b(a[h],c,g?d:d.call(a[h],h,b(a[h],c)));return e?a:j?b.call(a):i?b(a[0],c):f},Z=/^(?:checkbox|radio)$/i,$=/<([\w:-]+)/,_=/^$|\/(?:java|ecma)script/i,aa=/^\s+/,ba="abbr|article|aside|audio|bdi|canvas|data|datalist|details|dialog|figcaption|figure|footer|header|hgroup|main|mark|meter|nav|output|picture|progress|section|summary|template|time|video";function ca(a){var b=ba.split("|"),c=a.createDocumentFragment();if(c.createElement)while(b.length)c.createElement(b.pop());return c}!function(){var a=d.createElement("div"),b=d.createDocumentFragment(),c=d.createElement("input");a.innerHTML="
    a",l.leadingWhitespace=3===a.firstChild.nodeType,l.tbody=!a.getElementsByTagName("tbody").length,l.htmlSerialize=!!a.getElementsByTagName("link").length,l.html5Clone="<:nav>"!==d.createElement("nav").cloneNode(!0).outerHTML,c.type="checkbox",c.checked=!0,b.appendChild(c),l.appendChecked=c.checked,a.innerHTML="",l.noCloneChecked=!!a.cloneNode(!0).lastChild.defaultValue,b.appendChild(a),c=d.createElement("input"),c.setAttribute("type","radio"),c.setAttribute("checked","checked"),c.setAttribute("name","t"),a.appendChild(c),l.checkClone=a.cloneNode(!0).cloneNode(!0).lastChild.checked,l.noCloneEvent=!!a.addEventListener,a[n.expando]=1,l.attributes=!a.getAttribute(n.expando)}();var da={option:[1,""],legend:[1,"
    ","
    "],area:[1,"",""],param:[1,"",""],thead:[1,"","
    "],tr:[2,"","
    "],col:[2,"","
    "],td:[3,"","
    "],_default:l.htmlSerialize?[0,"",""]:[1,"X
    ","
    "]};da.optgroup=da.option,da.tbody=da.tfoot=da.colgroup=da.caption=da.thead,da.th=da.td;function ea(a,b){var c,d,e=0,f="undefined"!=typeof a.getElementsByTagName?a.getElementsByTagName(b||"*"):"undefined"!=typeof a.querySelectorAll?a.querySelectorAll(b||"*"):void 0;if(!f)for(f=[],c=a.childNodes||a;null!=(d=c[e]);e++)!b||n.nodeName(d,b)?f.push(d):n.merge(f,ea(d,b));return void 0===b||b&&n.nodeName(a,b)?n.merge([a],f):f}function fa(a,b){for(var c,d=0;null!=(c=a[d]);d++)n._data(c,"globalEval",!b||n._data(b[d],"globalEval"))}var ga=/<|&#?\w+;/,ha=/r;r++)if(g=a[r],g||0===g)if("object"===n.type(g))n.merge(q,g.nodeType?[g]:g);else if(ga.test(g)){i=i||p.appendChild(b.createElement("div")),j=($.exec(g)||["",""])[1].toLowerCase(),m=da[j]||da._default,i.innerHTML=m[1]+n.htmlPrefilter(g)+m[2],f=m[0];while(f--)i=i.lastChild;if(!l.leadingWhitespace&&aa.test(g)&&q.push(b.createTextNode(aa.exec(g)[0])),!l.tbody){g="table"!==j||ha.test(g)?""!==m[1]||ha.test(g)?0:i:i.firstChild,f=g&&g.childNodes.length;while(f--)n.nodeName(k=g.childNodes[f],"tbody")&&!k.childNodes.length&&g.removeChild(k)}n.merge(q,i.childNodes),i.textContent="";while(i.firstChild)i.removeChild(i.firstChild);i=p.lastChild}else q.push(b.createTextNode(g));i&&p.removeChild(i),l.appendChecked||n.grep(ea(q,"input"),ia),r=0;while(g=q[r++])if(d&&n.inArray(g,d)>-1)e&&e.push(g);else if(h=n.contains(g.ownerDocument,g),i=ea(p.appendChild(g),"script"),h&&fa(i),c){f=0;while(g=i[f++])_.test(g.type||"")&&c.push(g)}return i=null,p}!function(){var b,c,e=d.createElement("div");for(b in{submit:!0,change:!0,focusin:!0})c="on"+b,(l[b]=c in a)||(e.setAttribute(c,"t"),l[b]=e.attributes[c].expando===!1);e=null}();var ka=/^(?:input|select|textarea)$/i,la=/^key/,ma=/^(?:mouse|pointer|contextmenu|drag|drop)|click/,na=/^(?:focusinfocus|focusoutblur)$/,oa=/^([^.]*)(?:\.(.+)|)/;function pa(){return!0}function qa(){return!1}function ra(){try{return d.activeElement}catch(a){}}function sa(a,b,c,d,e,f){var g,h;if("object"==typeof b){"string"!=typeof c&&(d=d||c,c=void 0);for(h in b)sa(a,h,c,d,b[h],f);return a}if(null==d&&null==e?(e=c,d=c=void 0):null==e&&("string"==typeof c?(e=d,d=void 0):(e=d,d=c,c=void 0)),e===!1)e=qa;else if(!e)return a;return 1===f&&(g=e,e=function(a){return n().off(a),g.apply(this,arguments)},e.guid=g.guid||(g.guid=n.guid++)),a.each(function(){n.event.add(this,b,e,d,c)})}n.event={global:{},add:function(a,b,c,d,e){var f,g,h,i,j,k,l,m,o,p,q,r=n._data(a);if(r){c.handler&&(i=c,c=i.handler,e=i.selector),c.guid||(c.guid=n.guid++),(g=r.events)||(g=r.events={}),(k=r.handle)||(k=r.handle=function(a){return"undefined"==typeof n||a&&n.event.triggered===a.type?void 0:n.event.dispatch.apply(k.elem,arguments)},k.elem=a),b=(b||"").match(G)||[""],h=b.length;while(h--)f=oa.exec(b[h])||[],o=q=f[1],p=(f[2]||"").split(".").sort(),o&&(j=n.event.special[o]||{},o=(e?j.delegateType:j.bindType)||o,j=n.event.special[o]||{},l=n.extend({type:o,origType:q,data:d,handler:c,guid:c.guid,selector:e,needsContext:e&&n.expr.match.needsContext.test(e),namespace:p.join(".")},i),(m=g[o])||(m=g[o]=[],m.delegateCount=0,j.setup&&j.setup.call(a,d,p,k)!==!1||(a.addEventListener?a.addEventListener(o,k,!1):a.attachEvent&&a.attachEvent("on"+o,k))),j.add&&(j.add.call(a,l),l.handler.guid||(l.handler.guid=c.guid)),e?m.splice(m.delegateCount++,0,l):m.push(l),n.event.global[o]=!0);a=null}},remove:function(a,b,c,d,e){var f,g,h,i,j,k,l,m,o,p,q,r=n.hasData(a)&&n._data(a);if(r&&(k=r.events)){b=(b||"").match(G)||[""],j=b.length;while(j--)if(h=oa.exec(b[j])||[],o=q=h[1],p=(h[2]||"").split(".").sort(),o){l=n.event.special[o]||{},o=(d?l.delegateType:l.bindType)||o,m=k[o]||[],h=h[2]&&new RegExp("(^|\\.)"+p.join("\\.(?:.*\\.|)")+"(\\.|$)"),i=f=m.length;while(f--)g=m[f],!e&&q!==g.origType||c&&c.guid!==g.guid||h&&!h.test(g.namespace)||d&&d!==g.selector&&("**"!==d||!g.selector)||(m.splice(f,1),g.selector&&m.delegateCount--,l.remove&&l.remove.call(a,g));i&&!m.length&&(l.teardown&&l.teardown.call(a,p,r.handle)!==!1||n.removeEvent(a,o,r.handle),delete k[o])}else for(o in k)n.event.remove(a,o+b[j],c,d,!0);n.isEmptyObject(k)&&(delete r.handle,n._removeData(a,"events"))}},trigger:function(b,c,e,f){var g,h,i,j,l,m,o,p=[e||d],q=k.call(b,"type")?b.type:b,r=k.call(b,"namespace")?b.namespace.split("."):[];if(i=m=e=e||d,3!==e.nodeType&&8!==e.nodeType&&!na.test(q+n.event.triggered)&&(q.indexOf(".")>-1&&(r=q.split("."),q=r.shift(),r.sort()),h=q.indexOf(":")<0&&"on"+q,b=b[n.expando]?b:new n.Event(q,"object"==typeof b&&b),b.isTrigger=f?2:3,b.namespace=r.join("."),b.rnamespace=b.namespace?new RegExp("(^|\\.)"+r.join("\\.(?:.*\\.|)")+"(\\.|$)"):null,b.result=void 0,b.target||(b.target=e),c=null==c?[b]:n.makeArray(c,[b]),l=n.event.special[q]||{},f||!l.trigger||l.trigger.apply(e,c)!==!1)){if(!f&&!l.noBubble&&!n.isWindow(e)){for(j=l.delegateType||q,na.test(j+q)||(i=i.parentNode);i;i=i.parentNode)p.push(i),m=i;m===(e.ownerDocument||d)&&p.push(m.defaultView||m.parentWindow||a)}o=0;while((i=p[o++])&&!b.isPropagationStopped())b.type=o>1?j:l.bindType||q,g=(n._data(i,"events")||{})[b.type]&&n._data(i,"handle"),g&&g.apply(i,c),g=h&&i[h],g&&g.apply&&M(i)&&(b.result=g.apply(i,c),b.result===!1&&b.preventDefault());if(b.type=q,!f&&!b.isDefaultPrevented()&&(!l._default||l._default.apply(p.pop(),c)===!1)&&M(e)&&h&&e[q]&&!n.isWindow(e)){m=e[h],m&&(e[h]=null),n.event.triggered=q;try{e[q]()}catch(s){}n.event.triggered=void 0,m&&(e[h]=m)}return b.result}},dispatch:function(a){a=n.event.fix(a);var b,c,d,f,g,h=[],i=e.call(arguments),j=(n._data(this,"events")||{})[a.type]||[],k=n.event.special[a.type]||{};if(i[0]=a,a.delegateTarget=this,!k.preDispatch||k.preDispatch.call(this,a)!==!1){h=n.event.handlers.call(this,a,j),b=0;while((f=h[b++])&&!a.isPropagationStopped()){a.currentTarget=f.elem,c=0;while((g=f.handlers[c++])&&!a.isImmediatePropagationStopped())(!a.rnamespace||a.rnamespace.test(g.namespace))&&(a.handleObj=g,a.data=g.data,d=((n.event.special[g.origType]||{}).handle||g.handler).apply(f.elem,i),void 0!==d&&(a.result=d)===!1&&(a.preventDefault(),a.stopPropagation()))}return k.postDispatch&&k.postDispatch.call(this,a),a.result}},handlers:function(a,b){var c,d,e,f,g=[],h=b.delegateCount,i=a.target;if(h&&i.nodeType&&("click"!==a.type||isNaN(a.button)||a.button<1))for(;i!=this;i=i.parentNode||this)if(1===i.nodeType&&(i.disabled!==!0||"click"!==a.type)){for(d=[],c=0;h>c;c++)f=b[c],e=f.selector+" ",void 0===d[e]&&(d[e]=f.needsContext?n(e,this).index(i)>-1:n.find(e,this,null,[i]).length),d[e]&&d.push(f);d.length&&g.push({elem:i,handlers:d})}return h]","i"),va=/<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:-]+)[^>]*)\/>/gi,wa=/\s*$/g,Aa=ca(d),Ba=Aa.appendChild(d.createElement("div"));function Ca(a,b){return n.nodeName(a,"table")&&n.nodeName(11!==b.nodeType?b:b.firstChild,"tr")?a.getElementsByTagName("tbody")[0]||a.appendChild(a.ownerDocument.createElement("tbody")):a}function Da(a){return a.type=(null!==n.find.attr(a,"type"))+"/"+a.type,a}function Ea(a){var b=ya.exec(a.type);return b?a.type=b[1]:a.removeAttribute("type"),a}function Fa(a,b){if(1===b.nodeType&&n.hasData(a)){var c,d,e,f=n._data(a),g=n._data(b,f),h=f.events;if(h){delete g.handle,g.events={};for(c in h)for(d=0,e=h[c].length;e>d;d++)n.event.add(b,c,h[c][d])}g.data&&(g.data=n.extend({},g.data))}}function Ga(a,b){var c,d,e;if(1===b.nodeType){if(c=b.nodeName.toLowerCase(),!l.noCloneEvent&&b[n.expando]){e=n._data(b);for(d in e.events)n.removeEvent(b,d,e.handle);b.removeAttribute(n.expando)}"script"===c&&b.text!==a.text?(Da(b).text=a.text,Ea(b)):"object"===c?(b.parentNode&&(b.outerHTML=a.outerHTML),l.html5Clone&&a.innerHTML&&!n.trim(b.innerHTML)&&(b.innerHTML=a.innerHTML)):"input"===c&&Z.test(a.type)?(b.defaultChecked=b.checked=a.checked,b.value!==a.value&&(b.value=a.value)):"option"===c?b.defaultSelected=b.selected=a.defaultSelected:("input"===c||"textarea"===c)&&(b.defaultValue=a.defaultValue)}}function Ha(a,b,c,d){b=f.apply([],b);var e,g,h,i,j,k,m=0,o=a.length,p=o-1,q=b[0],r=n.isFunction(q);if(r||o>1&&"string"==typeof q&&!l.checkClone&&xa.test(q))return a.each(function(e){var f=a.eq(e);r&&(b[0]=q.call(this,e,f.html())),Ha(f,b,c,d)});if(o&&(k=ja(b,a[0].ownerDocument,!1,a,d),e=k.firstChild,1===k.childNodes.length&&(k=e),e||d)){for(i=n.map(ea(k,"script"),Da),h=i.length;o>m;m++)g=k,m!==p&&(g=n.clone(g,!0,!0),h&&n.merge(i,ea(g,"script"))),c.call(a[m],g,m);if(h)for(j=i[i.length-1].ownerDocument,n.map(i,Ea),m=0;h>m;m++)g=i[m],_.test(g.type||"")&&!n._data(g,"globalEval")&&n.contains(j,g)&&(g.src?n._evalUrl&&n._evalUrl(g.src):n.globalEval((g.text||g.textContent||g.innerHTML||"").replace(za,"")));k=e=null}return a}function Ia(a,b,c){for(var d,e=b?n.filter(b,a):a,f=0;null!=(d=e[f]);f++)c||1!==d.nodeType||n.cleanData(ea(d)),d.parentNode&&(c&&n.contains(d.ownerDocument,d)&&fa(ea(d,"script")),d.parentNode.removeChild(d));return a}n.extend({htmlPrefilter:function(a){return a.replace(va,"<$1>")},clone:function(a,b,c){var d,e,f,g,h,i=n.contains(a.ownerDocument,a);if(l.html5Clone||n.isXMLDoc(a)||!ua.test("<"+a.nodeName+">")?f=a.cloneNode(!0):(Ba.innerHTML=a.outerHTML,Ba.removeChild(f=Ba.firstChild)),!(l.noCloneEvent&&l.noCloneChecked||1!==a.nodeType&&11!==a.nodeType||n.isXMLDoc(a)))for(d=ea(f),h=ea(a),g=0;null!=(e=h[g]);++g)d[g]&&Ga(e,d[g]);if(b)if(c)for(h=h||ea(a),d=d||ea(f),g=0;null!=(e=h[g]);g++)Fa(e,d[g]);else Fa(a,f);return d=ea(f,"script"),d.length>0&&fa(d,!i&&ea(a,"script")),d=h=e=null,f},cleanData:function(a,b){for(var d,e,f,g,h=0,i=n.expando,j=n.cache,k=l.attributes,m=n.event.special;null!=(d=a[h]);h++)if((b||M(d))&&(f=d[i],g=f&&j[f])){if(g.events)for(e in g.events)m[e]?n.event.remove(d,e):n.removeEvent(d,e,g.handle);j[f]&&(delete j[f],k||"undefined"==typeof d.removeAttribute?d[i]=void 0:d.removeAttribute(i),c.push(f))}}}),n.fn.extend({domManip:Ha,detach:function(a){return Ia(this,a,!0)},remove:function(a){return Ia(this,a)},text:function(a){return Y(this,function(a){return void 0===a?n.text(this):this.empty().append((this[0]&&this[0].ownerDocument||d).createTextNode(a))},null,a,arguments.length)},append:function(){return Ha(this,arguments,function(a){if(1===this.nodeType||11===this.nodeType||9===this.nodeType){var b=Ca(this,a);b.appendChild(a)}})},prepend:function(){return Ha(this,arguments,function(a){if(1===this.nodeType||11===this.nodeType||9===this.nodeType){var b=Ca(this,a);b.insertBefore(a,b.firstChild)}})},before:function(){return Ha(this,arguments,function(a){this.parentNode&&this.parentNode.insertBefore(a,this)})},after:function(){return Ha(this,arguments,function(a){this.parentNode&&this.parentNode.insertBefore(a,this.nextSibling)})},empty:function(){for(var a,b=0;null!=(a=this[b]);b++){1===a.nodeType&&n.cleanData(ea(a,!1));while(a.firstChild)a.removeChild(a.firstChild);a.options&&n.nodeName(a,"select")&&(a.options.length=0)}return this},clone:function(a,b){return a=null==a?!1:a,b=null==b?a:b,this.map(function(){return n.clone(this,a,b)})},html:function(a){return Y(this,function(a){var b=this[0]||{},c=0,d=this.length;if(void 0===a)return 1===b.nodeType?b.innerHTML.replace(ta,""):void 0;if("string"==typeof a&&!wa.test(a)&&(l.htmlSerialize||!ua.test(a))&&(l.leadingWhitespace||!aa.test(a))&&!da[($.exec(a)||["",""])[1].toLowerCase()]){a=n.htmlPrefilter(a);try{for(;d>c;c++)b=this[c]||{},1===b.nodeType&&(n.cleanData(ea(b,!1)),b.innerHTML=a);b=0}catch(e){}}b&&this.empty().append(a)},null,a,arguments.length)},replaceWith:function(){var a=[];return Ha(this,arguments,function(b){var c=this.parentNode;n.inArray(this,a)<0&&(n.cleanData(ea(this)),c&&c.replaceChild(b,this))},a)}}),n.each({appendTo:"append",prependTo:"prepend",insertBefore:"before",insertAfter:"after",replaceAll:"replaceWith"},function(a,b){n.fn[a]=function(a){for(var c,d=0,e=[],f=n(a),h=f.length-1;h>=d;d++)c=d===h?this:this.clone(!0),n(f[d])[b](c),g.apply(e,c.get());return this.pushStack(e)}});var Ja,Ka={HTML:"block",BODY:"block"};function La(a,b){var c=n(b.createElement(a)).appendTo(b.body),d=n.css(c[0],"display");return c.detach(),d}function Ma(a){var b=d,c=Ka[a];return c||(c=La(a,b),"none"!==c&&c||(Ja=(Ja||n(" + + diff --git a/frontend/static/welcome.html b/frontend/static/welcome.html new file mode 100644 index 00000000..20ccbbe1 --- /dev/null +++ b/frontend/static/welcome.html @@ -0,0 +1,70 @@ + + + + + Challenge Forensic + + + + + + + + + + + + + + + + +
    +
    +

    Bienvenue !

    +

    + Vous n'êtes pas encore connecté en tant qu'équipe sur notre serveur. +

    +

    + Après avoir suivi le guide présent sur la clef USB que nous vous + avons remis et ajouté votre certificat dans votre système ou votre + navigateur, ce dernier devrait vous afficher une boîte de dialogue + vous demandant de choisir parmi les certificats installés sur votre + machine. +

    +

    + Si vous avez accédé à cette page avant d'avoir ajouté le certificat, + il peut être nécessaire de relancer votre navigateur, afin qu'il + démarre une nouvelle session. +

    +

    + Si malgré tout, vous n'arrivez pas à accéder à l'espace de votre + équipe ou si votre clef USB est illisible, n'hésitez pas à nous + solliciter ! +

    +
    +
    + + + diff --git a/frontend/static/winner.jpg b/frontend/static/winner.jpg new file mode 100644 index 00000000..f4684166 Binary files /dev/null and b/frontend/static/winner.jpg differ diff --git a/frontend/submit.go b/frontend/submit.go new file mode 100644 index 00000000..c39ec0f8 --- /dev/null +++ b/frontend/submit.go @@ -0,0 +1,147 @@ +package main + +import ( + "fmt" + "io/ioutil" + "log" + "net/http" + "os" + "path" + "strconv" + "strings" + "time" +) + +type SubmissionHandler struct { + ChallengeEnd time.Time + DenyChName bool + AllowRegistration bool +} + +func (s SubmissionHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { + log.Printf("Handling %s request from %s: %s [%s]\n", r.Method, r.RemoteAddr, r.URL.Path, r.UserAgent()) + + w.Header().Set("Content-Type", "application/json") + + // Check request type and size + if r.Method != "POST" { + http.Error(w, "{\"errmsg\":\"Requête invalide.\"}", http.StatusBadRequest) + return + } else if r.ContentLength < 0 || r.ContentLength > 1023 { + http.Error(w, "{\"errmsg\":\"Requête trop longue ou de taille inconnue\"}", http.StatusRequestEntityTooLarge) + return + } + + // Extract URL arguments + var sURL = strings.Split(r.URL.Path, "/") + + if len(sURL) == 2 && s.AllowRegistration && sURL[1] == "registration" { + if _, err := os.Stat(path.Join(SubmissionDir, "_registration")); os.IsNotExist(err) { + log.Println("Creating _registration directory") + if err := os.MkdirAll(path.Join(SubmissionDir, "_registration"), 0777); err != nil { + log.Println("Unable to create _registration directory: ", err) + http.Error(w, "{\"errmsg\":\"Internal server error. Please retry in few seconds.\"}", http.StatusInternalServerError) + return + } + } + + if f, err := ioutil.TempFile(path.Join(SubmissionDir, "_registration"), ""); err != nil { + log.Println("Unable to open registration file:", err) + http.Error(w, "{\"errmsg\":\"Internal server error. Please retry in few seconds..\"}", http.StatusInternalServerError) + } else { + // Read request body + var body []byte + if r.ContentLength > 0 { + tmp := make([]byte, 1024) + for { + n, err := r.Body.Read(tmp) + for j := 0; j < n; j++ { + body = append(body, tmp[j]) + } + if err != nil || n <= 0 { + break + } + } + } + + f.Write(body) + f.Close() + } + + http.Error(w, "{\"errmsg\":\"Demande d'enregistrement acceptée\"}", http.StatusAccepted) + return + } else if len(sURL) != 3 { + http.Error(w, "{\"errmsg\":\"Requête invalide.\"}", http.StatusBadRequest) + return + } + + if time.Now().Sub(s.ChallengeEnd) > 0 { + http.Error(w, "{\"errmsg\":\"Vous ne pouvez plus soumettre, le challenge est terminé.\"}", http.StatusForbidden) + return + } + + // Parse arguments + if _, err := os.Stat(path.Join(TeamsDir, sURL[1])); os.IsNotExist(err) || len(sURL[1]) < 1 || sURL[1][0] == '_' { + http.Error(w, "{\"errmsg\":\"Requête invalide.\"}", http.StatusBadRequest) + return + } else if pex, err := strconv.Atoi(sURL[2]); (s.DenyChName || sURL[2] != "name") && err != nil { + http.Error(w, "{\"errmsg\":\"Requête invalide.\"}", http.StatusBadRequest) + return + } else { + team := sURL[1] + + var exercice string + if sURL[2] == "name" { + exercice = sURL[2] + } else { + exercice = fmt.Sprintf("%d", pex) + } + + if len(exercice) <= 0 { + log.Println("EMPTY $EXERCICE RECEIVED:", exercice) + http.Error(w, "{\"errmsg\":\"Internal server error. Please retry in few seconds.\"}", http.StatusInternalServerError) + return + } + + if _, err := os.Stat(path.Join(SubmissionDir, team)); os.IsNotExist(err) { + log.Println("Creating submission directory for", team) + if err := os.MkdirAll(path.Join(SubmissionDir, team), 0777); err != nil { + log.Println("Unable to create submission directory: ", err) + http.Error(w, "{\"errmsg\":\"Internal server error. Please retry in few seconds.\"}", http.StatusInternalServerError) + return + } + } + + // Previous submission not treated + if _, err := os.Stat(path.Join(SubmissionDir, team, exercice)); !os.IsNotExist(err) { + http.Error(w, "{\"errmsg\":\"Du calme ! une requête est déjà en cours de traitement.\"}", http.StatusPaymentRequired) + return + } + + // Read request body + var body []byte + if r.ContentLength > 0 { + tmp := make([]byte, 1024) + for { + n, err := r.Body.Read(tmp) + for j := 0; j < n; j++ { + body = append(body, tmp[j]) + } + if err != nil || n <= 0 { + break + } + } + } + + // Store content in file + if file, err := os.Create(path.Join(SubmissionDir, team, exercice)); err != nil { + log.Println("Unable to open exercice file:", err) + http.Error(w, "{\"errmsg\":\"Internal server error. Please retry in few seconds.\"}", http.StatusInternalServerError) + return + } else { + file.Write(body) + file.Close() + } + http.Error(w, "{\"errmsg\":\"Son traitement est en cours...\"}", http.StatusAccepted) + } +} diff --git a/frontend/time.go b/frontend/time.go new file mode 100644 index 00000000..ec6bf695 --- /dev/null +++ b/frontend/time.go @@ -0,0 +1,33 @@ +package main + +import ( + "encoding/json" + "fmt" + "log" + "net/http" + "time" +) + +type TimeHandler struct { + StartTime time.Time + Duration time.Duration +} + +type timeObject struct { + Started int64 `json:"st"` + Time int64 `json:"cu"` + Duration int `json:"du"` +} + +func (t TimeHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { + log.Printf("Handling %s request from %s: %s [%s]\n", r.Method, r.RemoteAddr, r.URL.Path, r.UserAgent()) + + w.Header().Set("Content-Type", "application/json") + + if j, err := json.Marshal(timeObject{t.StartTime.Unix(), time.Now().Unix(), int(t.Duration.Seconds())}); err != nil { + http.Error(w, fmt.Sprintf("{\"errmsg\":\"%q\"}", err), http.StatusInternalServerError) + } else { + w.WriteHeader(http.StatusOK) + w.Write(j) + } +} diff --git a/generator/.gitignore b/generator/.gitignore deleted file mode 100644 index a8a6bda7..00000000 --- a/generator/.gitignore +++ /dev/null @@ -1 +0,0 @@ -generator \ No newline at end of file diff --git a/generator/generation.go b/generator/generation.go deleted file mode 100644 index 9f128197..00000000 --- a/generator/generation.go +++ /dev/null @@ -1,366 +0,0 @@ -package main - -import ( - "encoding/json" - "fmt" - "io" - "io/ioutil" - "log" - "net/http" - "os" - "path" - "runtime" - "sync" - "time" - - "srs.epita.fr/fic-server/libfic" -) - -var parallelJobs = runtime.NumCPU() -var genQueue chan *fic.GenStruct -var inQueueMutex sync.RWMutex -var inGenQueue map[fic.GenerateType]bool - -func init() { - genQueue = make(chan *fic.GenStruct) - inGenQueue = map[fic.GenerateType]bool{} -} - -func launchWorkers() { - log.Println("Running with", parallelJobs, "worker(s)") - for i := parallelJobs; i > 0; i-- { - go consumer() - } -} - -func enqueueHandler(w http.ResponseWriter, r *http.Request) { - var gs fic.GenStruct - - dec := json.NewDecoder(r.Body) - err := dec.Decode(&gs) - if err != nil { - http.Error(w, err.Error(), http.StatusBadRequest) - return - } - - log.Printf("POST /enqueue | %v", gs) - - appendGenQueue(gs) - http.Error(w, "OK", http.StatusOK) -} - -func performHandler(w http.ResponseWriter, r *http.Request) { - var gs fic.GenStruct - - dec := json.NewDecoder(r.Body) - err := dec.Decode(&gs) - if err != nil { - http.Error(w, err.Error(), http.StatusBadRequest) - return - } - - log.Printf("POST /perform | %v", gs) - - err = <-appendGenQueue(gs).GenEnded() - if err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - } else { - http.Error(w, "OK", http.StatusOK) - } -} - -func performFullResyncHandler(w http.ResponseWriter, r *http.Request) { - log.Printf("POST /full-resync started") - - waitList := genAll() - var errs string - for _, e := range waitList { - err := <-e - if err != nil { - errs += err.Error() + "\n" - } - } - - if errs != "" { - lastRegeneration = time.Now() - http.Error(w, errs, http.StatusInternalServerError) - } else { - http.Error(w, "done", http.StatusOK) - } - - log.Printf("POST /full-resync done") -} - -func appendGenQueue(gs fic.GenStruct) *fic.GenStruct { - if gs.Type == fic.GenTeam || gs.Type == fic.GenTeamIssues { - genQueue <- &gs - return &gs - } - - // Append only if not already in queue - inQueueMutex.RLock() - if v, ok := inGenQueue[gs.Type]; !ok || !v { - inQueueMutex.RUnlock() - - inQueueMutex.Lock() - inGenQueue[gs.Type] = true - inQueueMutex.Unlock() - - genQueue <- &gs - } else { - inQueueMutex.RUnlock() - } - - return &gs -} - -func consumer() { - var id string - var err error - - for { - gs := <-genQueue - id = gs.Id - - inQueueMutex.Lock() - inGenQueue[gs.Type] = false - inQueueMutex.Unlock() - - switch gs.Type { - case fic.GenPublic: - err = genMyPublicFile() - case fic.GenEvents: - err = genEventsFile() - case fic.GenTeam: - err = genTeamMyFile(gs.TeamId) - case fic.GenTeams: - err = genTeamsFile() - case fic.GenThemes: - err = genThemesFile() - case fic.GenTeamIssues: - err = genTeamIssuesFile(gs.TeamId) - } - - if err != nil { - log.Println(id, "[ERR] Unable to generate:", err) - } - gs.End(err) - } -} - -// Generate issues.json for a given team -func genTeamIssuesFile(teamid int64) error { - team, err := fic.GetTeam(teamid) - if err != nil { - return fmt.Errorf("Unable to GetTeam: %w", err) - } - - dirPath := path.Join(TeamsDir, fmt.Sprintf("%d", team.Id)) - - my, err := team.MyIssueFile() - if err != nil { - return err - } - - if my == nil { - if _, err := os.Stat(path.Join(dirPath, "issues.json")); !os.IsNotExist(err) { - err = os.Remove(path.Join(dirPath, "issues.json")) - if err != nil { - log.Printf("Unable to remove empty issues file: %s", path.Join(dirPath, "issues.json")) - } - } - return nil - } - - if s, err := os.Stat(dirPath); os.IsNotExist(err) { - os.MkdirAll(dirPath, 0751) - } else if !s.IsDir() { - return fmt.Errorf("%s is not a directory", dirPath) - } - - if j, err := json.Marshal(my); err != nil { - return err - } else if err = ioutil.WriteFile(path.Join(dirPath, "issues.json"), j, 0644); err != nil { - return err - } - - return nil -} - -// Generate my.json, wait.json and scores.json for a given team -func genTeamMyFile(teamid int64) error { - team, err := fic.GetTeam(teamid) - if err != nil { - return fmt.Errorf("Unable to GetTeam: %w", err) - } - - dirPath := path.Join(TeamsDir, fmt.Sprintf("%d", team.Id)) - - if s, err := os.Stat(dirPath); os.IsNotExist(err) { - os.MkdirAll(dirPath, 0751) - } else if !s.IsDir() { - return fmt.Errorf("%s is not a directory", dirPath) - } - - if my, err := fic.MyJSONTeam(team, true); err != nil { - return err - } else if j, err := json.Marshal(my); err != nil { - return err - } else if err = ioutil.WriteFile(path.Join(dirPath, "my.json"), j, 0666); err != nil { - return err - } - - // Speed up generation when challenge is started - if !ChStarted { - if my, err := fic.MyJSONTeam(team, false); err != nil { - return err - } else if j, err := json.Marshal(my); err != nil { - return err - } else if err = ioutil.WriteFile(path.Join(dirPath, "wait.json"), j, 0666); err != nil { - return err - } - } else { - if scores, err := team.ScoreGrid(); err != nil { - return err - } else if j, err := json.Marshal(scores); err != nil { - return err - } else if err = ioutil.WriteFile(path.Join(dirPath, "scores.json"), j, 0666); err != nil { - return err - } - } - - return nil -} - -// Generate public my.json file -func genMyPublicFile() error { - dirPath := path.Join(TeamsDir, "public") - - if s, err := os.Stat(dirPath); os.IsNotExist(err) { - os.MkdirAll(dirPath, 0751) - } else if !s.IsDir() { - return fmt.Errorf("%s is not a directory", dirPath) - } - - if my, err := fic.MyJSONTeam(nil, true); err != nil { - return err - } else if j, err := json.Marshal(my); err != nil { - return err - } else if err = ioutil.WriteFile(path.Join(dirPath, "my.json"), j, 0666); err != nil { - return err - } - - os.Symlink("my.json", path.Join(dirPath, "wait.json")) - - if teams, err := fic.ExportTeams(true); err != nil { - return err - } else if j, err := json.Marshal(teams); err != nil { - return err - } else if err = ioutil.WriteFile(path.Join(dirPath, "teams.json"), j, 0666); err != nil { - return err - } - - return nil -} - -// Generate general evemts.json file -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 -} - -// Generate general teams.json file -func genTeamsFile() error { - if teams, err := fic.ExportTeams(false); err != nil { - return err - } else if j, err := json.Marshal(teams); err != nil { - return err - } else if err = ioutil.WriteFile(path.Join(TeamsDir, "teams.json"), j, 0666); err != nil { - return err - } - - if teams, err := fic.ExportTeams(true); err != nil { - return err - } else if j, err := json.Marshal(teams); err != nil { - return err - } else if err = ioutil.WriteFile(path.Join(TeamsDir, "public", "teams.json"), j, 0666); err != nil { - return err - } - - return nil -} - -// Generate general themes.json file -func genThemesFile() error { - themes, err := fic.ExportThemes() - if err != nil { - return fmt.Errorf("unable to generate themes: %w", err) - } - - var wr io.Writer - - themesfd, err := os.Create(path.Join(TeamsDir, "themes.json")) - if err != nil { - return fmt.Errorf("unable to re-create themes.json: %w", err) - } - defer themesfd.Close() - - themeswait, err := os.Create(path.Join(TeamsDir, "themes-wait.json")) - if err != nil { - return fmt.Errorf("unable to re-create themes-wait.json: %w", err) - } - defer themeswait.Close() - - if allowRegistration { - wr = io.MultiWriter(themesfd, themeswait) - } else { - wr = themesfd - } - - enc := json.NewEncoder(wr) - err = enc.Encode(themes) - if err != nil { - return fmt.Errorf("unable to encode themes.json: %w", err) - } - - if !allowRegistration { - enc = json.NewEncoder(themeswait) - err = enc.Encode(map[string]string{}) - if err != nil { - return fmt.Errorf("unable to encode themes-wait.json: %w", err) - } - } - - return nil -} - -func genAll() (waitList []chan error) { - waitList = append( - waitList, - appendGenQueue(fic.GenStruct{Type: fic.GenThemes}).GenEnded(), - appendGenQueue(fic.GenStruct{Type: fic.GenTeams}).GenEnded(), - appendGenQueue(fic.GenStruct{Type: fic.GenEvents}).GenEnded(), - appendGenQueue(fic.GenStruct{Type: fic.GenPublic}).GenEnded(), - ) - - if teams, err := fic.GetActiveTeams(); err != nil { - log.Println("Team retrieval error: ", err) - } else { - for _, team := range teams { - waitList = append( - waitList, - appendGenQueue(fic.GenStruct{Type: fic.GenTeam, TeamId: team.Id}).GenEnded(), - appendGenQueue(fic.GenStruct{Type: fic.GenTeamIssues, TeamId: team.Id}).GenEnded(), - ) - } - } - - return -} diff --git a/generator/main.go b/generator/main.go deleted file mode 100644 index de5ddba5..00000000 --- a/generator/main.go +++ /dev/null @@ -1,168 +0,0 @@ -package main - -import ( - "context" - "flag" - "fmt" - "log" - "net" - "net/http" - "os" - "os/signal" - "path" - "strings" - "syscall" - "time" - - "srs.epita.fr/fic-server/libfic" - "srs.epita.fr/fic-server/settings" -) - -var TeamsDir string - -var ChStarted = false -var lastRegeneration time.Time -var skipInitialGeneration = false -var allowRegistration bool - -func reloadSettings(config *settings.Settings) { - fic.HintCoefficient = config.HintCurCoefficient - fic.WChoiceCoefficient = config.WChoiceCurCoefficient - fic.ExerciceCurrentCoefficient = config.ExerciceCurCoefficient - ChStarted = config.Start.Unix() > 0 && time.Since(config.Start) >= 0 - if allowRegistration != config.AllowRegistration || fic.PartialValidation != config.PartialValidation || fic.UnlockedChallengeDepth != config.UnlockedChallengeDepth || fic.UnlockedStandaloneExercices != config.UnlockedStandaloneExercices || fic.UnlockedStandaloneExercicesByThemeStepValidation != config.UnlockedStandaloneExercicesByThemeStepValidation || fic.UnlockedStandaloneExercicesByStandaloneExerciceValidation != config.UnlockedStandaloneExercicesByStandaloneExerciceValidation || fic.UnlockedChallengeUpTo != config.UnlockedChallengeUpTo || fic.DisplayAllFlags != config.DisplayAllFlags || fic.FirstBlood != config.FirstBlood || fic.SubmissionCostBase != config.SubmissionCostBase || fic.SubmissionUniqueness != config.SubmissionUniqueness || fic.DiscountedFactor != config.DiscountedFactor || fic.QuestionGainRatio != config.QuestionGainRatio || fic.HideCaseSensitivity != config.HideCaseSensitivity { - allowRegistration = config.AllowRegistration - - fic.PartialValidation = config.PartialValidation - fic.UnlockedChallengeDepth = config.UnlockedChallengeDepth - fic.UnlockedChallengeUpTo = config.UnlockedChallengeUpTo - fic.UnlockedStandaloneExercices = config.UnlockedStandaloneExercices - fic.UnlockedStandaloneExercicesByThemeStepValidation = config.UnlockedStandaloneExercicesByThemeStepValidation - fic.UnlockedStandaloneExercicesByStandaloneExerciceValidation = config.UnlockedStandaloneExercicesByStandaloneExerciceValidation - fic.DisplayAllFlags = config.DisplayAllFlags - - fic.FirstBlood = config.FirstBlood - fic.SubmissionCostBase = config.SubmissionCostBase - fic.SubmissionUniqueness = config.SubmissionUniqueness - fic.GlobalScoreCoefficient = config.GlobalScoreCoefficient - fic.CountOnlyNotGoodTries = config.CountOnlyNotGoodTries - fic.HideCaseSensitivity = config.HideCaseSensitivity - fic.DiscountedFactor = config.DiscountedFactor - fic.QuestionGainRatio = config.QuestionGainRatio - - if !skipInitialGeneration { - log.Println("Generating files...") - go func() { - waitList := genAll() - for _, e := range waitList { - <-e - } - log.Println("Full generation done") - }() - lastRegeneration = time.Now() - } else { - skipInitialGeneration = false - log.Println("Regeneration skipped by option.") - } - } else { - log.Println("No change found. Skipping regeneration.") - } -} - -func main() { - if v, exists := os.LookupEnv("FIC_BASEURL"); exists { - fic.FilesDir = v + "files" - } else { - fic.FilesDir = "/files" - } - - var bind = flag.String("bind", "./GENERATOR/generator.socket", "Bind path or port/socket") - var dsn = flag.String("dsn", fic.DSNGenerator(), "DSN to connect to the MySQL server") - flag.StringVar(&settings.SettingsDir, "settings", "./SETTINGSDIST", "Base directory where load and save settings") - flag.StringVar(&TeamsDir, "teams", "./TEAMS", "Base directory where save teams JSON files") - flag.StringVar(&fic.FilesDir, "files", fic.FilesDir, "Request path prefix to reach files") - flag.BoolVar(&skipInitialGeneration, "skipfullgeneration", skipInitialGeneration, "Skip the initial regeneration") - flag.IntVar(¶llelJobs, "jobs", parallelJobs, "Number of generation workers") - flag.Parse() - - log.SetPrefix("[generator] ") - - settings.SettingsDir = path.Clean(settings.SettingsDir) - TeamsDir = path.Clean(TeamsDir) - - launchWorkers() - - log.Println("Opening DB...") - if err := fic.DBInit(*dsn); err != nil { - log.Fatal("Cannot open the database: ", err) - } - defer fic.DBClose() - - // Load configuration - settings.LoadAndWatchSettings(path.Join(settings.SettingsDir, settings.SettingsFile), reloadSettings) - - // Register SIGUSR1, SIGUSR2 - interrupt1 := make(chan os.Signal, 1) - signal.Notify(interrupt1, syscall.SIGUSR1) - interrupt2 := make(chan os.Signal, 1) - signal.Notify(interrupt2, syscall.SIGUSR2) - - // Prepare graceful shutdown - interrupt := make(chan os.Signal, 1) - signal.Notify(interrupt, os.Interrupt, syscall.SIGTERM) - - srv := &http.Server{ - Addr: *bind, - ReadHeaderTimeout: 15 * time.Second, - ReadTimeout: 15 * time.Second, - WriteTimeout: 10 * time.Second, - IdleTimeout: 30 * time.Second, - } - - http.HandleFunc("/enqueue", enqueueHandler) - http.HandleFunc("/perform", performHandler) - http.HandleFunc("/full", performFullResyncHandler) - - // Serve pages - go func() { - if !strings.Contains(*bind, ":") { - if _, err := os.Stat(*bind); !os.IsNotExist(err) { - if err := os.Remove(*bind); err != nil { - log.Fatal(err) - } - } - - os.MkdirAll(path.Dir(*bind), 0751) - - unixListener, err := net.Listen("unix", *bind) - if err != nil { - log.Fatal(err) - } - log.Fatal(srv.Serve(unixListener)) - } else if err := srv.ListenAndServe(); err != nil { - log.Fatal(err) - } - }() - log.Println(fmt.Sprintf("Ready, listening on %s", *bind)) - - // Wait shutdown signal -loop: - for { - select { - case <-interrupt: - break loop - case <-interrupt1: - log.Println("SIGUSR1 received, regenerating all files...") - genAll() - log.Println("SIGUSR1 treated.") - case <-interrupt2: - inQueueMutex.Lock() - log.Printf("SIGUSR2 received, dumping statistics:\n parallelJobs: %d\n genQueue: %d\n Teams in queue: %v\n Challenge started: %v\n Last regeneration: %v\n", parallelJobs, len(genQueue), inGenQueue, ChStarted, lastRegeneration) - inQueueMutex.Unlock() - } - } - - log.Print("The service is shutting down...") - srv.Shutdown(context.Background()) - log.Println("done") -} diff --git a/go.mod b/go.mod deleted file mode 100644 index 9ace7117..00000000 --- a/go.mod +++ /dev/null @@ -1,92 +0,0 @@ -module srs.epita.fr/fic-server - -go 1.23.0 - -toolchain go1.24.1 - -require ( - github.com/BurntSushi/toml v1.5.0 - github.com/asticode/go-astisub v0.34.0 - github.com/cenkalti/dominantcolor v1.0.3 - github.com/gin-contrib/sessions v1.0.2 - github.com/gin-gonic/gin v1.10.0 - github.com/go-git/go-git/v5 v5.14.0 - github.com/go-sql-driver/mysql v1.9.1 - github.com/google/gopacket v1.1.19 - github.com/studio-b12/gowebdav v0.10.0 - github.com/u2takey/ffmpeg-go v0.5.0 - github.com/yuin/goldmark v1.7.8 - gitlab.com/nyarla/go-crypt v0.0.0-20160106005555-d9a5dc2b789b - go.uber.org/multierr v1.11.0 - golang.org/x/crypto v0.36.0 - golang.org/x/image v0.25.0 - golang.org/x/oauth2 v0.28.0 - gopkg.in/fsnotify.v1 v1.4.7 -) - -require ( - dario.cat/mergo v1.0.0 // indirect - filippo.io/edwards25519 v1.1.0 // indirect - github.com/Microsoft/go-winio v0.6.2 // indirect - github.com/ProtonMail/go-crypto v1.1.5 // indirect - github.com/acomagu/bufpipe v1.0.4 // indirect - github.com/asticode/go-astikit v0.20.0 // indirect - github.com/asticode/go-astits v1.8.0 // indirect - github.com/aws/aws-sdk-go v1.38.20 // indirect - github.com/bytedance/sonic v1.11.6 // indirect - github.com/bytedance/sonic/loader v0.1.1 // indirect - github.com/chenzhuoyu/base64x v0.0.0-20230717121745-296ad89f973d // indirect - github.com/chenzhuoyu/iasm v0.9.1 // indirect - github.com/cloudflare/circl v1.6.0 // indirect - github.com/cloudwego/base64x v0.1.4 // indirect - github.com/cloudwego/iasm v0.2.0 // indirect - github.com/cyphar/filepath-securejoin v0.4.1 // indirect - github.com/disintegration/imaging v1.6.2 // indirect - github.com/emirpasic/gods v1.18.1 // indirect - github.com/fsnotify/fsnotify v1.6.0 // indirect - github.com/gabriel-vasile/mimetype v1.4.3 // indirect - github.com/gin-contrib/sse v0.1.0 // indirect - github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect - github.com/go-git/go-billy/v5 v5.6.2 // indirect - github.com/go-playground/locales v0.14.1 // indirect - github.com/go-playground/universal-translator v0.18.1 // indirect - github.com/go-playground/validator/v10 v10.20.0 // indirect - github.com/goccy/go-json v0.10.2 // indirect - github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 // indirect - github.com/golang/protobuf v1.5.4 // indirect - github.com/gorilla/context v1.1.2 // indirect - github.com/gorilla/securecookie v1.1.2 // indirect - github.com/gorilla/sessions v1.2.2 // indirect - github.com/imdario/mergo v0.3.15 // indirect - github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect - github.com/jmespath/go-jmespath v0.4.0 // indirect - github.com/json-iterator/go v1.1.12 // indirect - github.com/kevinburke/ssh_config v1.2.0 // indirect - github.com/klauspost/cpuid/v2 v2.2.7 // indirect - github.com/leodido/go-urn v1.4.0 // indirect - github.com/mattn/go-isatty v0.0.20 // indirect - github.com/mitchellh/go-homedir v1.1.0 // indirect - github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect - github.com/modern-go/reflect2 v1.0.2 // indirect - github.com/pelletier/go-toml/v2 v2.2.2 // indirect - github.com/pjbgf/sha1cd v0.3.2 // indirect - github.com/quasoft/memstore v0.0.0-20191010062613-2bce066d2b0b // indirect - github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 // indirect - github.com/skeema/knownhosts v1.3.1 // indirect - github.com/twitchyliquid64/golang-asm v0.15.1 // indirect - github.com/u2takey/go-utils v0.3.1 // indirect - github.com/ugorji/go/codec v1.2.12 // indirect - github.com/xanzy/ssh-agent v0.3.3 // indirect - golang.org/x/arch v0.8.0 // indirect - golang.org/x/mod v0.17.0 // indirect - golang.org/x/net v0.35.0 // indirect - golang.org/x/sync v0.12.0 // indirect - golang.org/x/sys v0.31.0 // indirect - golang.org/x/text v0.23.0 // indirect - golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d // indirect - google.golang.org/appengine v1.6.7 // indirect - google.golang.org/protobuf v1.34.1 // indirect - gopkg.in/warnings.v0 v0.1.2 // indirect - gopkg.in/yaml.v2 v2.4.0 // indirect - gopkg.in/yaml.v3 v3.0.1 // indirect -) diff --git a/go.sum b/go.sum deleted file mode 100644 index 1fd08549..00000000 --- a/go.sum +++ /dev/null @@ -1,967 +0,0 @@ -dario.cat/mergo v1.0.0 h1:AGCNq9Evsj31mOgNPcLyXc+4PNABt905YmuqPYYpBWk= -dario.cat/mergo v1.0.0/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk= -filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA= -filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4= -github.com/BurntSushi/toml v1.2.1 h1:9F2/+DoOYIOksmaJFPw1tGFy1eDnIJXg+UHjuD8lTak= -github.com/BurntSushi/toml v1.2.1/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= -github.com/BurntSushi/toml v1.3.0 h1:Ws8e5YmnrGEHzZEzg0YvK/7COGYtTC5PbaH9oSSbgfA= -github.com/BurntSushi/toml v1.3.0/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= -github.com/BurntSushi/toml v1.3.2 h1:o7IhLm0Msx3BaB+n3Ag7L8EVlByGnpq14C4YWiu/gL8= -github.com/BurntSushi/toml v1.3.2/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= -github.com/BurntSushi/toml v1.4.0 h1:kuoIxZQy2WRRk1pttg9asf+WVv6tWQuBNVmK8+nqPr0= -github.com/BurntSushi/toml v1.4.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho= -github.com/BurntSushi/toml v1.5.0 h1:W5quZX/G/csjUnuI8SUYlsHs9M38FC7znL0lIO+DvMg= -github.com/BurntSushi/toml v1.5.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho= -github.com/Microsoft/go-winio v0.4.14/go.mod h1:qXqCSQ3Xa7+6tgxaGTIe4Kpcdsi+P8jBhyzoq1bpyYA= -github.com/Microsoft/go-winio v0.4.16 h1:FtSW/jqD+l4ba5iPBj9CODVtgfYAD8w2wS923g/cFDk= -github.com/Microsoft/go-winio v0.4.16/go.mod h1:XB6nPKklQyQ7GC9LdcBEcBl8PF76WugXOPRXwdLnMv0= -github.com/Microsoft/go-winio v0.5.2 h1:a9IhgEQBCUEk6QCdml9CiJGhAws+YwffDHEMp1VMrpA= -github.com/Microsoft/go-winio v0.5.2/go.mod h1:WpS1mjBmmwHBEWmogvA2mj8546UReBk4v8QkMxJ6pZY= -github.com/Microsoft/go-winio v0.6.1 h1:9/kr64B9VUZrLm5YYwbGtUJnMgqWVOdUAXu6Migciow= -github.com/Microsoft/go-winio v0.6.1/go.mod h1:LRdKpFKfdobln8UmuiYcKPot9D2v6svN5+sAH+4kjUM= -github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY= -github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU= -github.com/ProtonMail/go-crypto v0.0.0-20210428141323-04723f9f07d7 h1:YoJbenK9C67SkzkDfmQuVln04ygHj3vjZfd9FL+GmQQ= -github.com/ProtonMail/go-crypto v0.0.0-20210428141323-04723f9f07d7/go.mod h1:z4/9nQmJSSwwds7ejkxaJwO37dru3geImFUdJlaLzQo= -github.com/ProtonMail/go-crypto v0.0.0-20221026131551-cf6655e29de4 h1:ra2OtmuW0AE5csawV4YXMNGNQQXvLRps3z2Z59OPO+I= -github.com/ProtonMail/go-crypto v0.0.0-20221026131551-cf6655e29de4/go.mod h1:UBYPn8k0D56RtnR8RFQMjmh4KrZzWJ5o7Z9SYjossQ8= -github.com/ProtonMail/go-crypto v0.0.0-20230217124315-7d5c6f04bbb8 h1:wPbRQzjjwFc0ih8puEVAOFGELsn1zoIIYdxvML7mDxA= -github.com/ProtonMail/go-crypto v0.0.0-20230217124315-7d5c6f04bbb8/go.mod h1:I0gYDMZ6Z5GRU7l58bNFSkPTFN6Yl12dsUlAZ8xy98g= -github.com/ProtonMail/go-crypto v0.0.0-20230518184743-7afd39499903 h1:ZK3C5DtzV2nVAQTx5S5jQvMeDqWtD1By5mOoyY/xJek= -github.com/ProtonMail/go-crypto v0.0.0-20230518184743-7afd39499903/go.mod h1:8TI4H3IbrackdNgv+92dI+rhpCaLqM0IfpgCgenFvRE= -github.com/ProtonMail/go-crypto v0.0.0-20230717121422-5aa5874ade95 h1:KLq8BE0KwCL+mmXnjLWEAOYO+2l2AE4YMmqG1ZpZHBs= -github.com/ProtonMail/go-crypto v0.0.0-20230717121422-5aa5874ade95/go.mod h1:EjAoLdwvbIOoOQr3ihjnSoLZRtE8azugULFRteWMNc0= -github.com/ProtonMail/go-crypto v0.0.0-20230828082145-3c4c8a2d2371 h1:kkhsdkhsCvIsutKu5zLMgWtgh9YxGCNAw8Ad8hjwfYg= -github.com/ProtonMail/go-crypto v0.0.0-20230828082145-3c4c8a2d2371/go.mod h1:EjAoLdwvbIOoOQr3ihjnSoLZRtE8azugULFRteWMNc0= -github.com/ProtonMail/go-crypto v1.0.0 h1:LRuvITjQWX+WIfr930YHG2HNfjR1uOfyf5vE0kC2U78= -github.com/ProtonMail/go-crypto v1.0.0/go.mod h1:EjAoLdwvbIOoOQr3ihjnSoLZRtE8azugULFRteWMNc0= -github.com/ProtonMail/go-crypto v1.1.3 h1:nRBOetoydLeUb4nHajyO2bKqMLfWQ/ZPwkXqXxPxCFk= -github.com/ProtonMail/go-crypto v1.1.3/go.mod h1:rA3QumHc/FZ8pAHreoekgiAbzpNsfQAosU5td4SnOrE= -github.com/ProtonMail/go-crypto v1.1.5 h1:eoAQfK2dwL+tFSFpr7TbOaPNUbPiJj4fLYwwGE1FQO4= -github.com/ProtonMail/go-crypto v1.1.5/go.mod h1:rA3QumHc/FZ8pAHreoekgiAbzpNsfQAosU5td4SnOrE= -github.com/acomagu/bufpipe v1.0.3 h1:fxAGrHZTgQ9w5QqVItgzwj235/uYZYgbXitB+dLupOk= -github.com/acomagu/bufpipe v1.0.3/go.mod h1:mxdxdup/WdsKVreO5GpW4+M/1CE2sMG4jeGJ2sYmHc4= -github.com/acomagu/bufpipe v1.0.4 h1:e3H4WUzM3npvo5uv95QuJM3cQspFNtFBzvJ2oNjKIDQ= -github.com/acomagu/bufpipe v1.0.4/go.mod h1:mxdxdup/WdsKVreO5GpW4+M/1CE2sMG4jeGJ2sYmHc4= -github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239 h1:kFOfPq6dUM1hTo4JG6LR5AXSUEsOjtdm0kw0FtQtMJA= -github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239/go.mod h1:2FmKhYUyUczH0OGQWaF5ceTx0UBShxjsH6f8oGKYe2c= -github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be/go.mod h1:ySMOLuWl6zY27l47sB3qLNK6tF2fkHG55UZxx8oIVo4= -github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio= -github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs= -github.com/asticode/go-astikit v0.20.0 h1:+7N+J4E4lWx2QOkRdOf6DafWJMv6O4RRfgClwQokrH8= -github.com/asticode/go-astikit v0.20.0/go.mod h1:h4ly7idim1tNhaVkdVBeXQZEE3L0xblP7fCWbgwipF0= -github.com/asticode/go-astisub v0.21.0 h1:xaCx7SnqblsR7ZqFbo9wq/JYwen7IAG2AVIcfecLxNI= -github.com/asticode/go-astisub v0.21.0/go.mod h1:WTkuSzFB+Bp7wezuSf2Oxulj5A8zu2zLRVFf6bIFQK8= -github.com/asticode/go-astisub v0.22.0 h1:1oHRHnrm5AWpzTKnjQnxkroFbQuVbkBysbyKUF2y2jY= -github.com/asticode/go-astisub v0.22.0/go.mod h1:WTkuSzFB+Bp7wezuSf2Oxulj5A8zu2zLRVFf6bIFQK8= -github.com/asticode/go-astisub v0.23.0 h1:WzkWty0Phy9rGrG6r0FjShBy9f1Wn7sMLvjdYj5hki4= -github.com/asticode/go-astisub v0.23.0/go.mod h1:WTkuSzFB+Bp7wezuSf2Oxulj5A8zu2zLRVFf6bIFQK8= -github.com/asticode/go-astisub v0.24.0 h1:Y3eDWeDyt+QlydjLrBuK91RZBkUenFH3EhUWoHqHdMo= -github.com/asticode/go-astisub v0.24.0/go.mod h1:WTkuSzFB+Bp7wezuSf2Oxulj5A8zu2zLRVFf6bIFQK8= -github.com/asticode/go-astisub v0.25.1 h1:RZMGfZPp7CXOkI6g+zCU7DRLuciGPGup921uKZnMXPI= -github.com/asticode/go-astisub v0.25.1/go.mod h1:WTkuSzFB+Bp7wezuSf2Oxulj5A8zu2zLRVFf6bIFQK8= -github.com/asticode/go-astisub v0.26.0 h1:Ka1oUyWzo/lIx7RX97GI1QdbClqYVxI0ExKuZRN/cDk= -github.com/asticode/go-astisub v0.26.0/go.mod h1:WTkuSzFB+Bp7wezuSf2Oxulj5A8zu2zLRVFf6bIFQK8= -github.com/asticode/go-astisub v0.26.1 h1:cL53FKU52cDPJzDkvy8CQvofwMaAOkR4Wc4B1gkUPWs= -github.com/asticode/go-astisub v0.26.1/go.mod h1:WTkuSzFB+Bp7wezuSf2Oxulj5A8zu2zLRVFf6bIFQK8= -github.com/asticode/go-astisub v0.26.2 h1:cdEXcm+SUSmYCEPTQYbbfCECnmQoIFfH6pF8wDJhfVo= -github.com/asticode/go-astisub v0.26.2/go.mod h1:WTkuSzFB+Bp7wezuSf2Oxulj5A8zu2zLRVFf6bIFQK8= -github.com/asticode/go-astisub v0.27.0 h1:MLm+v38QhPxggPdnESjsGb/JTmD9916wpd2jfWakhug= -github.com/asticode/go-astisub v0.27.0/go.mod h1:WTkuSzFB+Bp7wezuSf2Oxulj5A8zu2zLRVFf6bIFQK8= -github.com/asticode/go-astisub v0.29.0 h1:1Pjz+TkaAwjPPoH88bQ6nnoXjin+WO2zZV95C/COGrs= -github.com/asticode/go-astisub v0.29.0/go.mod h1:WTkuSzFB+Bp7wezuSf2Oxulj5A8zu2zLRVFf6bIFQK8= -github.com/asticode/go-astisub v0.30.0 h1:z4k2Y+V+rlCE8qk3uw/nie56KXxJaL1/GwTP+9F2GMM= -github.com/asticode/go-astisub v0.30.0/go.mod h1:WTkuSzFB+Bp7wezuSf2Oxulj5A8zu2zLRVFf6bIFQK8= -github.com/asticode/go-astisub v0.32.0 h1:i1RHVQyTxSAuX0X3YC5zIyWruVZorS3cDXxqxYa0qss= -github.com/asticode/go-astisub v0.32.0/go.mod h1:WTkuSzFB+Bp7wezuSf2Oxulj5A8zu2zLRVFf6bIFQK8= -github.com/asticode/go-astisub v0.34.0 h1:owKNj0A9pc7YVW/rNy2MJZ1mf0L8DTdklZVfyZDhTWI= -github.com/asticode/go-astisub v0.34.0/go.mod h1:WTkuSzFB+Bp7wezuSf2Oxulj5A8zu2zLRVFf6bIFQK8= -github.com/asticode/go-astits v1.8.0 h1:rf6aiiGn/QhlFjNON1n5plqF3Fs025XLUwiQ0NB6oZg= -github.com/asticode/go-astits v1.8.0/go.mod h1:DkOWmBNQpnr9mv24KfZjq4JawCFX1FCqjLVGvO0DygQ= -github.com/aws/aws-sdk-go v1.38.20 h1:QbzNx/tdfATbdKfubBpkt84OM6oBkxQZRw6+bW2GyeA= -github.com/aws/aws-sdk-go v1.38.20/go.mod h1:hcU610XS61/+aQV88ixoOzUoG7v3b31pl2zKMmprdro= -github.com/bwesterb/go-ristretto v1.2.0/go.mod h1:fUIoIZaG73pV5biE2Blr2xEzDoMj7NFEuV9ekS419A0= -github.com/bwesterb/go-ristretto v1.2.3/go.mod h1:fUIoIZaG73pV5biE2Blr2xEzDoMj7NFEuV9ekS419A0= -github.com/bytedance/sonic v1.5.0/go.mod h1:ED5hyg4y6t3/9Ku1R6dU/4KyJ48DZ4jPhfY1O2AihPM= -github.com/bytedance/sonic v1.8.0 h1:ea0Xadu+sHlu7x5O3gKhRpQ1IKiMrSiHttPF0ybECuA= -github.com/bytedance/sonic v1.8.0/go.mod h1:i736AoUSYt75HyZLoJW9ERYxcy6eaN6h4BZXU064P/U= -github.com/bytedance/sonic v1.9.1 h1:6iJ6NqdoxCDr6mbY8h18oSO+cShGSMRGCEo7F2h0x8s= -github.com/bytedance/sonic v1.9.1/go.mod h1:i736AoUSYt75HyZLoJW9ERYxcy6eaN6h4BZXU064P/U= -github.com/bytedance/sonic v1.10.0-rc/go.mod h1:ElCzW+ufi8qKqNW0FY314xriJhyJhuoJ3gFZdAHF7NM= -github.com/bytedance/sonic v1.11.3 h1:jRN+yEjakWh8aK5FzrciUHG8OFXK+4/KrAX/ysEtHAA= -github.com/bytedance/sonic v1.11.3/go.mod h1:iZcSUejdk5aukTND/Eu/ivjQuEL0Cu9/rf50Hi0u/g4= -github.com/bytedance/sonic v1.11.6 h1:oUp34TzMlL+OY1OUWxHqsdkgC/Zfc85zGqw9siXjrc0= -github.com/bytedance/sonic v1.11.6/go.mod h1:LysEHSvpvDySVdC2f87zGWf6CIKJcAvqab1ZaiQtds4= -github.com/bytedance/sonic/loader v0.1.1 h1:c+e5Pt1k/cy5wMveRDyk2X4B9hF4g7an8N3zCYjJFNM= -github.com/bytedance/sonic/loader v0.1.1/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU= -github.com/cenkalti/dominantcolor v1.0.2 h1:nP1qLG2sD4vu+mGjvEcp3zMaiT7OvcRDtp+wE0YEtfg= -github.com/cenkalti/dominantcolor v1.0.2/go.mod h1:HvN7ziRLPAes3UkUrLDDRADCPTFsKUzZx5ZAQx8KECc= -github.com/cenkalti/dominantcolor v1.0.3 h1:Pt0vfRZ8enkZh1n22RvoboA53SMM/v2aEwNQTZKSqww= -github.com/cenkalti/dominantcolor v1.0.3/go.mod h1:mGpFMbWUnyXaGN48Zbf9bU9HJP1eCCD7dnsscb4lyR4= -github.com/chenzhuoyu/base64x v0.0.0-20211019084208-fb5309c8db06/go.mod h1:DH46F32mSOjUmXrMHnKwZdA8wcEefY7UVqBKYGjpdQY= -github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 h1:qSGYFH7+jGhDF8vLC+iwCD4WpbV1EBDSzWkJODFLams= -github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311/go.mod h1:b583jCggY9gE99b6G5LEC39OIiVsWj+R97kbl5odCEk= -github.com/chenzhuoyu/base64x v0.0.0-20230717121745-296ad89f973d h1:77cEq6EriyTZ0g/qfRdp61a3Uu/AWrgIq2s0ClJV1g0= -github.com/chenzhuoyu/base64x v0.0.0-20230717121745-296ad89f973d/go.mod h1:8EPpVsBuRksnlj1mLy4AWzRNQYxauNi62uWcE3to6eA= -github.com/chenzhuoyu/iasm v0.9.0/go.mod h1:Xjy2NpN3h7aUqeqM+woSuuvxmIe6+DDsiNLIrkAmYog= -github.com/chenzhuoyu/iasm v0.9.1 h1:tUHQJXo3NhBqw6s33wkGn9SP3bvrWLdlVIJ3hQBL7P0= -github.com/chenzhuoyu/iasm v0.9.1/go.mod h1:Xjy2NpN3h7aUqeqM+woSuuvxmIe6+DDsiNLIrkAmYog= -github.com/cloudflare/circl v1.1.0 h1:bZgT/A+cikZnKIwn7xL2OBj012Bmvho/o6RpRvv3GKY= -github.com/cloudflare/circl v1.1.0/go.mod h1:prBCrKB9DV4poKZY1l9zBXg2QJY7mvgRvtMxxK7fi4I= -github.com/cloudflare/circl v1.3.3 h1:fE/Qz0QdIGqeWfnwq0RE0R7MI51s0M2E4Ga9kq5AEMs= -github.com/cloudflare/circl v1.3.3/go.mod h1:5XYMA4rFBvNIrhs50XuiBJ15vF2pZn4nnUKZrLbUZFA= -github.com/cloudflare/circl v1.3.7 h1:qlCDlTPz2n9fu58M0Nh1J/JzcFpfgkFHHX3O35r5vcU= -github.com/cloudflare/circl v1.3.7/go.mod h1:sRTcRWXGLrKw6yIGJ+l7amYJFfAXbZG0kBSc8r4zxgA= -github.com/cloudflare/circl v1.6.0 h1:cr5JKic4HI+LkINy2lg3W2jF8sHCVTBncJr5gIIq7qk= -github.com/cloudflare/circl v1.6.0/go.mod h1:uddAzsPgqdMAYatqJ0lsjX1oECcQLIlRpzZh3pJrofs= -github.com/cloudwego/base64x v0.1.4 h1:jwCgWpFanWmN8xoIUHa2rtzmkd5J2plF/dnLS6Xd/0Y= -github.com/cloudwego/base64x v0.1.4/go.mod h1:0zlkT4Wn5C6NdauXdJRhSKRlJvmclQ1hhJgA0rcu/8w= -github.com/cloudwego/iasm v0.2.0 h1:1KNIy1I1H9hNNFEEH3DVnI4UujN+1zjpuk6gwHLTssg= -github.com/cloudwego/iasm v0.2.0/go.mod h1:8rXZaNYT2n95jn+zTI1sDr+IgcD2GVs0nlbbQPiEFhY= -github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= -github.com/cyphar/filepath-securejoin v0.2.4 h1:Ugdm7cg7i6ZK6x3xDF1oEu1nfkyfH53EtKeQYTC3kyg= -github.com/cyphar/filepath-securejoin v0.2.4/go.mod h1:aPGpWjXOXUn2NCNjFvBE6aRxGGx79pTxQpKOJNYHHl4= -github.com/cyphar/filepath-securejoin v0.3.6 h1:4d9N5ykBnSp5Xn2JkhocYDkOpURL/18CYMpo6xB9uWM= -github.com/cyphar/filepath-securejoin v0.3.6/go.mod h1:Sdj7gXlvMcPZsbhwhQ33GguGLDGQL7h7bg04C/+u9jI= -github.com/cyphar/filepath-securejoin v0.4.1 h1:JyxxyPEaktOD+GAnqIqTf9A8tHyAG22rowi7HkoSU1s= -github.com/cyphar/filepath-securejoin v0.4.1/go.mod h1:Sdj7gXlvMcPZsbhwhQ33GguGLDGQL7h7bg04C/+u9jI= -github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= -github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/disintegration/imaging v1.6.2 h1:w1LecBlG2Lnp8B3jk5zSuNqd7b4DXhcjwek1ei82L+c= -github.com/disintegration/imaging v1.6.2/go.mod h1:44/5580QXChDfwIclfc/PCwrr44amcmDAg8hxG0Ewe4= -github.com/emirpasic/gods v1.12.0 h1:QAUIPSaCu4G+POclxeqb3F+WPpdKqFGlw36+yOzGlrg= -github.com/emirpasic/gods v1.12.0/go.mod h1:YfzfFFoVP/catgzJb4IKIqXjX78Ha8FMSDh3ymbK86o= -github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc= -github.com/emirpasic/gods v1.18.1/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FMNAnJvWQ= -github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc= -github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= -github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY= -github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw= -github.com/gabriel-vasile/mimetype v1.4.2 h1:w5qFW6JKBz9Y393Y4q372O9A7cUSequkh1Q7OhCmWKU= -github.com/gabriel-vasile/mimetype v1.4.2/go.mod h1:zApsH/mKG4w07erKIaJPFiX0Tsq9BFQgN3qGY5GnNgA= -github.com/gabriel-vasile/mimetype v1.4.3 h1:in2uUcidCuFcDKtdcBxlR0rJ1+fsokWf+uqxgUFjbI0= -github.com/gabriel-vasile/mimetype v1.4.3/go.mod h1:d8uq/6HKRL6CGdk+aubisF/M5GcPfT7nKyLpA0lbSSk= -github.com/gin-contrib/sessions v0.0.5 h1:CATtfHmLMQrMNpJRgzjWXD7worTh7g7ritsQfmF+0jE= -github.com/gin-contrib/sessions v0.0.5/go.mod h1:vYAuaUPqie3WUSsft6HUlCjlwwoJQs97miaG2+7neKY= -github.com/gin-contrib/sessions v1.0.0 h1:r5GLta4Oy5xo9rAwMHx8B4wLpeRGHMdz9NafzJAdP8Y= -github.com/gin-contrib/sessions v1.0.0/go.mod h1:DN0f4bvpqMQElDdi+gNGScrP2QEI04IErRyMFyorUOI= -github.com/gin-contrib/sessions v1.0.1 h1:3hsJyNs7v7N8OtelFmYXFrulAf6zSR7nW/putcPEHxI= -github.com/gin-contrib/sessions v1.0.1/go.mod h1:ouxSFM24/OgIud5MJYQJLpy6AwxQ5EYO9yLhbtObGkM= -github.com/gin-contrib/sessions v1.0.2 h1:UaIjUvTH1cMeOdj3in6dl+Xb6It8RiKRF9Z1anbUyCA= -github.com/gin-contrib/sessions v1.0.2/go.mod h1:KxKxWqWP5LJVDCInulOl4WbLzK2KSPlLesfZ66wRvMs= -github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE= -github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI= -github.com/gin-gonic/gin v1.8.1 h1:4+fr/el88TOO3ewCmQr8cx/CtZ/umlIRIs5M4NTNjf8= -github.com/gin-gonic/gin v1.8.1/go.mod h1:ji8BvRH1azfM+SYow9zQ6SZMvR8qOMZHmsCuWR9tTTk= -github.com/gin-gonic/gin v1.8.2 h1:UzKToD9/PoFj/V4rvlKqTRKnQYyz8Sc1MJlv4JHPtvY= -github.com/gin-gonic/gin v1.8.2/go.mod h1:qw5AYuDrzRTnhvusDsrov+fDIxp9Dleuu12h8nfB398= -github.com/gin-gonic/gin v1.9.0 h1:OjyFBKICoexlu99ctXNR2gg+c5pKrKMuyjgARg9qeY8= -github.com/gin-gonic/gin v1.9.0/go.mod h1:W1Me9+hsUSyj3CePGrd1/QrKJMSJ1Tu/0hFEH89961k= -github.com/gin-gonic/gin v1.9.1 h1:4idEAncQnU5cB7BeOkPtxjfCSye0AAm1R0RVIqJ+Jmg= -github.com/gin-gonic/gin v1.9.1/go.mod h1:hPrL7YrpYKXt5YId3A/Tnip5kqbEAP+KLuI3SUcPTeU= -github.com/gin-gonic/gin v1.10.0 h1:nTuyha1TYqgedzytsKYqna+DfLos46nTv2ygFy86HFU= -github.com/gin-gonic/gin v1.10.0/go.mod h1:4PMNQiOhvDRa013RKVbsiNwoyezlm2rm0uX/T7kzp5Y= -github.com/gliderlabs/ssh v0.2.2 h1:6zsha5zo/TWhRhwqCD3+EarCAgZ2yN28ipRnGPnwkI0= -github.com/gliderlabs/ssh v0.2.2/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aevW3Awn0= -github.com/gliderlabs/ssh v0.3.5/go.mod h1:8XB4KraRrX39qHhT6yxPsHedjA08I/uBVwj4xC+/+z4= -github.com/go-git/gcfg v1.5.0 h1:Q5ViNfGF8zFgyJWPqYwA7qGFoMTEiBmdlkcfRmpIMa4= -github.com/go-git/gcfg v1.5.0/go.mod h1:5m20vg6GwYabIxaOonVkTdrILxQMpEShl1xiMF4ua+E= -github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 h1:+zs/tPmkDkHx3U66DAb0lQFJrpS6731Oaa12ikc+DiI= -github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376/go.mod h1:an3vInlBmSxCcxctByoQdvwPiA7DTK7jaaFDBTtu0ic= -github.com/go-git/go-billy/v5 v5.2.0/go.mod h1:pmpqyWchKfYfrkb/UVH4otLvyi/5gJlGI4Hb3ZqZ3W0= -github.com/go-git/go-billy/v5 v5.3.1 h1:CPiOUAzKtMRvolEKw+bG1PLRpT7D3LIs3/3ey4Aiu34= -github.com/go-git/go-billy/v5 v5.3.1/go.mod h1:pmpqyWchKfYfrkb/UVH4otLvyi/5gJlGI4Hb3ZqZ3W0= -github.com/go-git/go-billy/v5 v5.4.0 h1:Vaw7LaSTRJOUric7pe4vnzBSgyuf2KrLsu2Y4ZpQBDE= -github.com/go-git/go-billy/v5 v5.4.0/go.mod h1:vjbugF6Fz7JIflbVpl1hJsGjSHNltrSw45YK/ukIvQg= -github.com/go-git/go-billy/v5 v5.4.1 h1:Uwp5tDRkPr+l/TnbHOQzp+tmJfLceOlbVucgpTz8ix4= -github.com/go-git/go-billy/v5 v5.4.1/go.mod h1:vjbugF6Fz7JIflbVpl1hJsGjSHNltrSw45YK/ukIvQg= -github.com/go-git/go-billy/v5 v5.5.0 h1:yEY4yhzCDuMGSv83oGxiBotRzhwhNr8VZyphhiu+mTU= -github.com/go-git/go-billy/v5 v5.5.0/go.mod h1:hmexnoNsr2SJU1Ju67OaNz5ASJY3+sHgFRpCtpDCKow= -github.com/go-git/go-billy/v5 v5.6.1 h1:u+dcrgaguSSkbjzHwelEjc0Yj300NUevrrPphk/SoRA= -github.com/go-git/go-billy/v5 v5.6.1/go.mod h1:0AsLr1z2+Uksi4NlElmMblP5rPcDZNRCD8ujZCRR2BE= -github.com/go-git/go-billy/v5 v5.6.2 h1:6Q86EsPXMa7c3YZ3aLAQsMA0VlWmy43r6FHqa/UNbRM= -github.com/go-git/go-billy/v5 v5.6.2/go.mod h1:rcFC2rAsp/erv7CMz9GczHcuD0D32fWzH+MJAU+jaUU= -github.com/go-git/go-git-fixtures/v4 v4.2.1 h1:n9gGL1Ct/yIw+nfsfr8s4+sbhT+Ncu2SubfXjIWgci8= -github.com/go-git/go-git-fixtures/v4 v4.2.1/go.mod h1:K8zd3kDUAykwTdDCr+I0per6Y6vMiRR/nnVTBtavnB0= -github.com/go-git/go-git-fixtures/v4 v4.3.1/go.mod h1:8LHG1a3SRW71ettAD/jW13h8c6AqjVSeL11RAdgaqpo= -github.com/go-git/go-git/v5 v5.4.2 h1:BXyZu9t0VkbiHtqrsvdq39UDhGJTl1h55VW6CSC4aY4= -github.com/go-git/go-git/v5 v5.4.2/go.mod h1:gQ1kArt6d+n+BGd+/B/I74HwRTLhth2+zti4ihgckDc= -github.com/go-git/go-git/v5 v5.5.1 h1:5vtv2TB5PM/gPM+EvsHJ16hJh4uAkdGcKilcwY7FYwo= -github.com/go-git/go-git/v5 v5.5.1/go.mod h1:uz5PQ3d0gz7mSgzZhSJToM6ALPaKCdSnl58/Xb5hzr8= -github.com/go-git/go-git/v5 v5.5.2 h1:v8lgZa5k9ylUw+OR/roJHTxR4QItsNFI5nKtAXFuynw= -github.com/go-git/go-git/v5 v5.5.2/go.mod h1:BE5hUJ5yaV2YMxhmaP4l6RBQ08kMxKSPD4BlxtH7OjI= -github.com/go-git/go-git/v5 v5.6.0 h1:JvBdYfcttd+0kdpuWO7KTu0FYgCf5W0t5VwkWGobaa4= -github.com/go-git/go-git/v5 v5.6.0/go.mod h1:6nmJ0tJ3N4noMV1Omv7rC5FG3/o8Cm51TB4CJp7mRmE= -github.com/go-git/go-git/v5 v5.6.1 h1:q4ZRqQl4pR/ZJHc1L5CFjGA1a10u76aV1iC+nh+bHsk= -github.com/go-git/go-git/v5 v5.6.1/go.mod h1:mvyoL6Unz0PiTQrGQfSfiLFhBH1c1e84ylC2MDs4ee8= -github.com/go-git/go-git/v5 v5.7.0 h1:t9AudWVLmqzlo+4bqdf7GY+46SUuRsx59SboFxkq2aE= -github.com/go-git/go-git/v5 v5.7.0/go.mod h1:coJHKEOk5kUClpsNlXrUvPrDxY3w3gjHvhcZd8Fodw8= -github.com/go-git/go-git/v5 v5.8.0 h1:Rc543s6Tyq+YcyPwZRvU4jzZGM8rB/wWu94TnTIYALQ= -github.com/go-git/go-git/v5 v5.8.0/go.mod h1:coJHKEOk5kUClpsNlXrUvPrDxY3w3gjHvhcZd8Fodw8= -github.com/go-git/go-git/v5 v5.8.1 h1:Zo79E4p7TRk0xoRgMq0RShiTHGKcKI4+DI6BfJc/Q+A= -github.com/go-git/go-git/v5 v5.8.1/go.mod h1:FHFuoD6yGz5OSKEBK+aWN9Oah0q54Jxl0abmj6GnqAo= -github.com/go-git/go-git/v5 v5.9.0 h1:cD9SFA7sHVRdJ7AYck1ZaAa/yeuBvGPxwXDL8cxrObY= -github.com/go-git/go-git/v5 v5.9.0/go.mod h1:RKIqga24sWdMGZF+1Ekv9kylsDz6LzdTSI2s/OsZWE0= -github.com/go-git/go-git/v5 v5.10.0 h1:F0x3xXrAWmhwtzoCokU4IMPcBdncG+HAAqi9FcOOjbQ= -github.com/go-git/go-git/v5 v5.10.0/go.mod h1:1FOZ/pQnqw24ghP2n7cunVl0ON55BsjPYvhWHvZGhoo= -github.com/go-git/go-git/v5 v5.10.1 h1:tu8/D8i+TWxgKpzQ3Vc43e+kkhXqtsZCKI/egajKnxk= -github.com/go-git/go-git/v5 v5.10.1/go.mod h1:uEuHjxkHap8kAl//V5F/nNWwqIYtP/402ddd05mp0wg= -github.com/go-git/go-git/v5 v5.11.0 h1:XIZc1p+8YzypNr34itUfSvYJcv+eYdTnTvOZ2vD3cA4= -github.com/go-git/go-git/v5 v5.11.0/go.mod h1:6GFcX2P3NM7FPBfpePbpLd21XxsgdAt+lKqXmCUiUCY= -github.com/go-git/go-git/v5 v5.12.0 h1:7Md+ndsjrzZxbddRDZjF14qK+NN56sy6wkqaVrjZtys= -github.com/go-git/go-git/v5 v5.12.0/go.mod h1:FTM9VKtnI2m65hNI/TenDDDnUf2Q9FHnXYjuz9i5OEY= -github.com/go-git/go-git/v5 v5.13.1 h1:DAQ9APonnlvSWpvolXWIuV6Q6zXy2wHbN4cVlNR5Q+M= -github.com/go-git/go-git/v5 v5.13.1/go.mod h1:qryJB4cSBoq3FRoBRf5A77joojuBcmPJ0qu3XXXVixc= -github.com/go-git/go-git/v5 v5.13.2 h1:7O7xvsK7K+rZPKW6AQR1YyNhfywkv7B8/FsP3ki6Zv0= -github.com/go-git/go-git/v5 v5.13.2/go.mod h1:hWdW5P4YZRjmpGHwRH2v3zkWcNl6HeXaXQEMGb3NJ9A= -github.com/go-git/go-git/v5 v5.14.0 h1:/MD3lCrGjCen5WfEAzKg00MJJffKhC8gzS80ycmCi60= -github.com/go-git/go-git/v5 v5.14.0/go.mod h1:Z5Xhoia5PcWA3NF8vRLURn9E5FRhSl7dGj9ItW3Wk5k= -github.com/go-logr/logr v0.1.0/go.mod h1:ixOQHD9gLJUVQQ2ZOR7zLEifBX6tGkNJF4QyIY7sIas= -github.com/go-playground/assert/v2 v2.0.1 h1:MsBgLAaY856+nPRTKrp3/OZK38U/wa0CcBYNjji3q3A= -github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= -github.com/go-playground/locales v0.14.0 h1:u50s323jtVGugKlcYeyzC0etD1HifMjqmJqb8WugfUU= -github.com/go-playground/locales v0.14.0/go.mod h1:sawfccIbzZTqEDETgFXqTho0QybSa7l++s0DH+LDiLs= -github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA= -github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY= -github.com/go-playground/universal-translator v0.18.0 h1:82dyy6p4OuJq4/CByFNOn/jYrnRPArHwAcmLoJZxyho= -github.com/go-playground/universal-translator v0.18.0/go.mod h1:UvRDBj+xPUEGrFYl+lu/H90nyDXpg0fqeB/AQUGNTVA= -github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY= -github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY= -github.com/go-playground/validator/v10 v10.10.0 h1:I7mrTYv78z8k8VXa/qJlOlEXn/nBh+BF8dHX5nt/dr0= -github.com/go-playground/validator/v10 v10.10.0/go.mod h1:74x4gJWsvQexRdW8Pn3dXSGrTK4nAUsbPlLADvpJkos= -github.com/go-playground/validator/v10 v10.11.1 h1:prmOlTVv+YjZjmRmNSF3VmspqJIxJWXmqUsHwfTRRkQ= -github.com/go-playground/validator/v10 v10.11.1/go.mod h1:i+3WkQ1FvaUjjxh1kSvIA4dMGDBiPU55YFDl0WbKdWU= -github.com/go-playground/validator/v10 v10.11.2 h1:q3SHpufmypg+erIExEKUmsgmhDTyhcJ38oeKGACXohU= -github.com/go-playground/validator/v10 v10.11.2/go.mod h1:NieE624vt4SCTJtD87arVLvdmjPAeV8BQlHtMnw9D7s= -github.com/go-playground/validator/v10 v10.14.0 h1:vgvQWe3XCz3gIeFDm/HnTIbj6UGmg/+t63MyGU2n5js= -github.com/go-playground/validator/v10 v10.14.0/go.mod h1:9iXMNT7sEkjXb0I+enO7QXmzG6QCsPWY4zveKFVRSyU= -github.com/go-playground/validator/v10 v10.19.0 h1:ol+5Fu+cSq9JD7SoSqe04GMI92cbn0+wvQ3bZ8b/AU4= -github.com/go-playground/validator/v10 v10.19.0/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM= -github.com/go-playground/validator/v10 v10.20.0 h1:K9ISHbSaI0lyB2eWMPJo+kOS/FBExVwjEviJTixqxL8= -github.com/go-playground/validator/v10 v10.20.0/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM= -github.com/go-sql-driver/mysql v1.6.0 h1:BCTh4TKNUYmOmMUcQ3IipzF5prigylS7XXjEkfCHuOE= -github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= -github.com/go-sql-driver/mysql v1.7.0 h1:ueSltNNllEqE3qcWBTD0iQd3IpL/6U+mJxLkazJ7YPc= -github.com/go-sql-driver/mysql v1.7.0/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI= -github.com/go-sql-driver/mysql v1.7.1 h1:lUIinVbN1DY0xBg0eMOzmmtGoHwWBbvnWubQUrtU8EI= -github.com/go-sql-driver/mysql v1.7.1/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI= -github.com/go-sql-driver/mysql v1.8.0 h1:UtktXaU2Nb64z/pLiGIxY4431SJ4/dR5cjMmlVHgnT4= -github.com/go-sql-driver/mysql v1.8.0/go.mod h1:wEBSXgmK//2ZFJyE+qWnIsVGmvmEKlqwuVSjsCm7DZg= -github.com/go-sql-driver/mysql v1.8.1 h1:LedoTUt/eveggdHS9qUFC1EFSa8bU2+1pZjSRpvNJ1Y= -github.com/go-sql-driver/mysql v1.8.1/go.mod h1:wEBSXgmK//2ZFJyE+qWnIsVGmvmEKlqwuVSjsCm7DZg= -github.com/go-sql-driver/mysql v1.9.1 h1:FrjNGn/BsJQjVRuSa8CBrM5BWA9BWoXXat3KrtSb/iI= -github.com/go-sql-driver/mysql v1.9.1/go.mod h1:qn46aNg1333BRMNU69Lq93t8du/dwxI64Gl8i5p1WMU= -github.com/goccy/go-json v0.9.7 h1:IcB+Aqpx/iMHu5Yooh7jEzJk1JZ7Pjtmys2ukPr7EeM= -github.com/goccy/go-json v0.9.7/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= -github.com/goccy/go-json v0.9.11 h1:/pAaQDLHEoCq/5FFmSKBswWmK6H0e8g4159Kc/X/nqk= -github.com/goccy/go-json v0.9.11/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= -github.com/goccy/go-json v0.10.0 h1:mXKd9Qw4NuzShiRlOXKews24ufknHO7gx30lsDyokKA= -github.com/goccy/go-json v0.10.0/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= -github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU= -github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= -github.com/gogo/protobuf v1.3.1/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o= -github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE= -github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= -github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 h1:f+oWsMOmNPc8JmEHVZIycC7hBoQxHH9pNKQORJNozsQ= -github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8/go.mod h1:wcDNUvekVysuuOpQKo3191zZyTpiI6se1N1ULghS0sw= -github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= -github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw= -github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= -github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg= -github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= -github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= -github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= -github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.8 h1:e6P7q2lk1O+qJJb4BtCQXlK8vWEO8V1ZeuEdJNOqZyg= -github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= -github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= -github.com/google/gopacket v1.1.19 h1:ves8RnFZPGiFnTS0uPQStjwru6uO6h+nlr9j6fL7kF8= -github.com/google/gopacket v1.1.19/go.mod h1:iJ8V8n6KS+z2U1A8pUwu8bW5SyEMkXJB8Yo/Vo+TKTo= -github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/gorilla/context v1.1.1 h1:AWwleXJkX/nhcU9bZSnZoi3h/qGYqQAGhq6zZe/aQW8= -github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg= -github.com/gorilla/context v1.1.2 h1:WRkNAv2uoa03QNIc1A6u4O7DAGMUVoopZhkiXWA2V1o= -github.com/gorilla/context v1.1.2/go.mod h1:KDPwT9i/MeWHiLl90fuTgrt4/wPcv75vFAZLaOOcbxM= -github.com/gorilla/securecookie v1.1.1 h1:miw7JPhV+b/lAHSXz4qd/nN9jRiAFV5FwjeKyCS8BvQ= -github.com/gorilla/securecookie v1.1.1/go.mod h1:ra0sb63/xPlUeL+yeDciTfxMRAA+MP+HVt/4epWDjd4= -github.com/gorilla/securecookie v1.1.2 h1:YCIWL56dvtr73r6715mJs5ZvhtnY73hBvEF8kXD8ePA= -github.com/gorilla/securecookie v1.1.2/go.mod h1:NfCASbcHqRSY+3a8tlWJwsQap2VX5pwzwo4h3eOamfo= -github.com/gorilla/sessions v1.2.1 h1:DHd3rPN5lE3Ts3D8rKkQ8x/0kqfeNmBAaiSi+o7FsgI= -github.com/gorilla/sessions v1.2.1/go.mod h1:dk2InVEVJ0sfLlnXv9EAgkf6ecYs/i80K/zI+bUmuGM= -github.com/gorilla/sessions v1.2.2 h1:lqzMYz6bOfvn2WriPUjNByzeXIlVzURcPmgMczkmTjY= -github.com/gorilla/sessions v1.2.2/go.mod h1:ePLdVu+jbEgHH+KWw8I1z2wqd0BAdAQh/8LRvBeoNcQ= -github.com/hashicorp/golang-lru v0.5.4/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4= -github.com/imdario/mergo v0.3.12 h1:b6R2BslTbIEToALKP7LxUvijTsNI9TAe80pLWN2g/HU= -github.com/imdario/mergo v0.3.12/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA= -github.com/imdario/mergo v0.3.13 h1:lFzP57bqS/wsqKssCGmtLAb8A0wKjLGrve2q3PPVcBk= -github.com/imdario/mergo v0.3.13/go.mod h1:4lJ1jqUDcsbIECGy0RUJAXNIhg+6ocWgb1ALK2O4oXg= -github.com/imdario/mergo v0.3.15 h1:M8XP7IuFNsqUx6VPK2P9OSmsYsI/YFaGil0uD21V3dM= -github.com/imdario/mergo v0.3.15/go.mod h1:WBLT9ZmE3lPoWsEzCh9LPo3TiwVN+ZKEjmz+hD27ysY= -github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A= -github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo= -github.com/jessevdk/go-flags v1.5.0/go.mod h1:Fw0T6WPc1dYxT4mKEZRfG5kJhaTDP9pj1c2EWnYs/m4= -github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg= -github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo= -github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGwWFoC7ycTf1rcQZHOlsJ6N8= -github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U= -github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= -github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= -github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= -github.com/kevinburke/ssh_config v0.0.0-20201106050909-4977a11b4351 h1:DowS9hvgyYSX4TO5NpyC606/Z4SxnNYbT+WX27or6Ck= -github.com/kevinburke/ssh_config v0.0.0-20201106050909-4977a11b4351/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM= -github.com/kevinburke/ssh_config v1.2.0 h1:x584FjTGwHzMwvHx18PXxbBVzfnxogHaAReU4gf13a4= -github.com/kevinburke/ssh_config v1.2.0/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM= -github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00= -github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= -github.com/klauspost/cpuid/v2 v2.0.9 h1:lgaqFMSdTdQYdZ04uHyN2d/eKdOMyi2YLSvlQIBFYa4= -github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= -github.com/klauspost/cpuid/v2 v2.2.4 h1:acbojRNwl3o09bUq+yDCtZFc1aiwaAAxtcn8YkZXnvk= -github.com/klauspost/cpuid/v2 v2.2.4/go.mod h1:RVVoqg1df56z8g3pUjL/3lE5UfnlrJX8tyFgg4nqhuY= -github.com/klauspost/cpuid/v2 v2.2.7 h1:ZWSB3igEs+d0qvnxR/ZBzXVmxkgt8DdzP6m9pfuVLDM= -github.com/klauspost/cpuid/v2 v2.2.7/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws= -github.com/knz/go-libedit v1.10.1/go.mod h1:MZTVkCWyz0oBc7JOWP3wNAzd002ZbM/5hgShxwh4x8M= -github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= -github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= -github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= -github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0= -github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk= -github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= -github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= -github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= -github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= -github.com/leodido/go-urn v1.2.1 h1:BqpAaACuzVSgi/VLzGZIobT2z4v53pjosyNd9Yv6n/w= -github.com/leodido/go-urn v1.2.1/go.mod h1:zt4jvISO2HfUBqxjfIshjdMTYS56ZS/qv49ictyFfxY= -github.com/leodido/go-urn v1.2.4 h1:XlAE/cm/ms7TE/VMVoduSpNBoyc2dOxHs5MZSwAN63Q= -github.com/leodido/go-urn v1.2.4/go.mod h1:7ZrI8mTSeBSHl/UaRyKQW1qZeMgak41ANeCNaVckg+4= -github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ= -github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI= -github.com/matryer/is v1.2.0 h1:92UTHpy8CDwaJ08GqLDzhhuixiBUUD1p3AU6PHddz4A= -github.com/matryer/is v1.2.0/go.mod h1:2fLPjFQM9rhQ15aVEtbuwhJinnOqrmgXPNdZsdwlWXA= -github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y= -github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= -github.com/mattn/go-isatty v0.0.16 h1:bq3VjFmv/sOjHtdEhmkEV4x1AJtvUvOJ2PFAZ5+peKQ= -github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= -github.com/mattn/go-isatty v0.0.17 h1:BTarxUcIeDqL27Mc+vyvdWYSL28zpIhv3RoTdsLMPng= -github.com/mattn/go-isatty v0.0.17/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= -github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA= -github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= -github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= -github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= -github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= -github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= -github.com/mmcloughlin/avo v0.5.0/go.mod h1:ChHFdoV7ql95Wi7vuq2YT1bwCJqiWdZrQ1im3VujLYM= -github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= -github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= -github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= -github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= -github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= -github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= -github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= -github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= -github.com/panjf2000/ants/v2 v2.4.2/go.mod h1:f6F0NZVFsGCp5A7QW/Zj/m92atWwOkY0OIhFxRNFr4A= -github.com/pelletier/go-toml/v2 v2.0.1 h1:8e3L2cCQzLFi2CR4g7vGFuFxX7Jl1kKX8gW+iV0GUKU= -github.com/pelletier/go-toml/v2 v2.0.1/go.mod h1:r9LEWfGN8R5k0VXJ+0BkIe7MYkRdwZOjgMj2KwnJFUo= -github.com/pelletier/go-toml/v2 v2.0.6 h1:nrzqCb7j9cDFj2coyLNLaZuJTLjWjlaz6nvTvIwycIU= -github.com/pelletier/go-toml/v2 v2.0.6/go.mod h1:eumQOmlWiOPt5WriQQqoM5y18pDHwha2N+QD+EUNTek= -github.com/pelletier/go-toml/v2 v2.0.8 h1:0ctb6s9mE31h0/lhu+J6OPmVeDxJn+kYnJc2jZR9tGQ= -github.com/pelletier/go-toml/v2 v2.0.8/go.mod h1:vuYfssBdrU2XDZ9bYydBu6t+6a6PYNcZljzZR9VXg+4= -github.com/pelletier/go-toml/v2 v2.2.0 h1:QLgLl2yMN7N+ruc31VynXs1vhMZa7CeHHejIeBAsoHo= -github.com/pelletier/go-toml/v2 v2.2.0/go.mod h1:1t835xjRzz80PqgE6HHgN2JOsmgYu/h4qDAS4n929Rs= -github.com/pelletier/go-toml/v2 v2.2.2 h1:aYUidT7k73Pcl9nb2gScu7NSrKCSHIDE89b3+6Wq+LM= -github.com/pelletier/go-toml/v2 v2.2.2/go.mod h1:1t835xjRzz80PqgE6HHgN2JOsmgYu/h4qDAS4n929Rs= -github.com/pjbgf/sha1cd v0.2.3 h1:uKQP/7QOzNtKYH7UTohZLcjF5/55EnTw0jO/Ru4jZwI= -github.com/pjbgf/sha1cd v0.2.3/go.mod h1:HOK9QrgzdHpbc2Kzip0Q1yi3M2MFGPADtR6HjG65m5M= -github.com/pjbgf/sha1cd v0.3.0 h1:4D5XXmUUBUl/xQ6IjCkEAbqXskkq/4O7LmGn0AqMDs4= -github.com/pjbgf/sha1cd v0.3.0/go.mod h1:nZ1rrWOcGJ5uZgEEVL1VUM9iRQiZvWdbZjkKyFzPPsI= -github.com/pjbgf/sha1cd v0.3.2 h1:a9wb0bp1oC2TGwStyn0Umc/IGKQnEgF0vVaZ8QF8eo4= -github.com/pjbgf/sha1cd v0.3.2/go.mod h1:zQWigSxVmsHEZow5qaLtPYxpcKMMQpa09ixqBxuCS6A= -github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= -github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= -github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= -github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= -github.com/pkg/profile v1.4.0/go.mod h1:NWz/XGvpEW1FyYQ7fCx4dqYBLlfTcE+A9FLAkNKqjFE= -github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= -github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/quasoft/memstore v0.0.0-20191010062613-2bce066d2b0b h1:aUNXCGgukb4gtY99imuIeoh8Vr0GSwAlYxPAhqZrpFc= -github.com/quasoft/memstore v0.0.0-20191010062613-2bce066d2b0b/go.mod h1:wTPjTepVu7uJBYgZ0SdWHQlIas582j6cn2jgk4DDdlg= -github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= -github.com/rogpeppe/go-internal v1.8.0 h1:FCbCCtXNOY3UtUuHUYaghJg4y7Fd14rXifAYUAtL9R8= -github.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6poM+XZ2dLUbcbE= -github.com/sergi/go-diff v1.1.0 h1:we8PVUC3FE2uYfodKH/nBHMSetSfHDR6scGdBi+erh0= -github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM= -github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 h1:n661drycOFuPLCN3Uc8sB6B/s6Z4t2xvBgU1htSHuq8= -github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3/go.mod h1:A0bzQcvG0E7Rwjx0REVgAGH58e96+X0MeOfepqsbeW4= -github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q= -github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= -github.com/skeema/knownhosts v1.1.0 h1:Wvr9V0MxhjRbl3f9nMnKnFfiWTJmtECJ9Njkea3ysW0= -github.com/skeema/knownhosts v1.1.0/go.mod h1:sKFq3RD6/TKZkSWn8boUbDC7Qkgcv+8XXijpFO6roag= -github.com/skeema/knownhosts v1.1.1 h1:MTk78x9FPgDFVFkDLTrsnnfCJl7g1C/nnKvePgrIngE= -github.com/skeema/knownhosts v1.1.1/go.mod h1:g4fPeYpque7P0xefxtGzV81ihjC8sX2IqpAoNkjxbMo= -github.com/skeema/knownhosts v1.2.0 h1:h9r9cf0+u7wSE+M183ZtMGgOJKiL96brpaz5ekfJCpM= -github.com/skeema/knownhosts v1.2.0/go.mod h1:g4fPeYpque7P0xefxtGzV81ihjC8sX2IqpAoNkjxbMo= -github.com/skeema/knownhosts v1.2.1 h1:SHWdIUa82uGZz+F+47k8SY4QhhI291cXCpopT1lK2AQ= -github.com/skeema/knownhosts v1.2.1/go.mod h1:xYbVRSPxqBZFrdmDyMmsOs+uX1UZC3nTN3ThzgDxUwo= -github.com/skeema/knownhosts v1.2.2 h1:Iug2P4fLmDw9f41PB6thxUkNUkJzB5i+1/exaj40L3A= -github.com/skeema/knownhosts v1.2.2/go.mod h1:xYbVRSPxqBZFrdmDyMmsOs+uX1UZC3nTN3ThzgDxUwo= -github.com/skeema/knownhosts v1.3.0 h1:AM+y0rI04VksttfwjkSTNQorvGqmwATnvnAHpSgc0LY= -github.com/skeema/knownhosts v1.3.0/go.mod h1:sPINvnADmT/qYH1kfv+ePMmOBTH6Tbl7b5LvTDjFK7M= -github.com/skeema/knownhosts v1.3.1 h1:X2osQ+RAjK76shCbvhHHHVl3ZlgDm8apHEHFqRjnBY8= -github.com/skeema/knownhosts v1.3.1/go.mod h1:r7KTdC8l4uxWRyK2TpQZ/1o5HaSzh06ePQNxPwTcfiY= -github.com/spf13/afero v1.2.2/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTdifk= -github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= -github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= -github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= -github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= -github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= -github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= -github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= -github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.7.1 h1:5TQK59W5E3v0r2duFAb7P95B6hEeOyEnHRa8MjYSMTY= -github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= -github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= -github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= -github.com/stretchr/testify v1.8.3/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= -github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= -github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= -github.com/studio-b12/gowebdav v0.0.0-20221102155456-200a600c0272 h1:dXbdJSdxf0EnR4SkcsfRNuGCvoEk9lavXbSCFXN2gJc= -github.com/studio-b12/gowebdav v0.0.0-20221102155456-200a600c0272/go.mod h1:bHA7t77X/QFExdeAnDzK6vKM34kEZAcE1OX4MfiwjkE= -github.com/studio-b12/gowebdav v0.0.0-20221109171924-60ec5ad56012 h1:ZC+dlnsjxqrcB68nEFbIEfo4iXsog3Sg8FlXKytAjhY= -github.com/studio-b12/gowebdav v0.0.0-20221109171924-60ec5ad56012/go.mod h1:bHA7t77X/QFExdeAnDzK6vKM34kEZAcE1OX4MfiwjkE= -github.com/studio-b12/gowebdav v0.0.0-20230203175954-cd21842fb675 h1:zgn46xCgyM8IOOzWhM+/Wa657aeO95JhGd1/iTtl4X8= -github.com/studio-b12/gowebdav v0.0.0-20230203175954-cd21842fb675/go.mod h1:bHA7t77X/QFExdeAnDzK6vKM34kEZAcE1OX4MfiwjkE= -github.com/studio-b12/gowebdav v0.0.0-20230203202212-3282f94193f2 h1:VsBj3UD2xyAOu7kJw6O/2jjG2UXLFoBzihqDU9Ofg9M= -github.com/studio-b12/gowebdav v0.0.0-20230203202212-3282f94193f2/go.mod h1:bHA7t77X/QFExdeAnDzK6vKM34kEZAcE1OX4MfiwjkE= -github.com/studio-b12/gowebdav v0.9.0 h1:1j1sc9gQnNxbXXM4M/CebPOX4aXYtr7MojAVcN4dHjU= -github.com/studio-b12/gowebdav v0.9.0/go.mod h1:bHA7t77X/QFExdeAnDzK6vKM34kEZAcE1OX4MfiwjkE= -github.com/studio-b12/gowebdav v0.10.0 h1:Yewz8FFiadcGEu4hxS/AAJQlHelndqln1bns3hcJIYc= -github.com/studio-b12/gowebdav v0.10.0/go.mod h1:bHA7t77X/QFExdeAnDzK6vKM34kEZAcE1OX4MfiwjkE= -github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI= -github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08= -github.com/u2takey/ffmpeg-go v0.4.1 h1:l5ClIwL3N2LaH1zF3xivb3kP2HW95eyG5xhHE1JdZ9Y= -github.com/u2takey/ffmpeg-go v0.4.1/go.mod h1:ruZWkvC1FEiUNjmROowOAps3ZcWxEiOpFoHCvk97kGc= -github.com/u2takey/ffmpeg-go v0.5.0 h1:r7d86XuL7uLWJ5mzSeQ03uvjfIhiJYvsRAJFCW4uklU= -github.com/u2takey/ffmpeg-go v0.5.0/go.mod h1:ruZWkvC1FEiUNjmROowOAps3ZcWxEiOpFoHCvk97kGc= -github.com/u2takey/go-utils v0.3.1 h1:TaQTgmEZZeDHQFYfd+AdUT1cT4QJgJn/XVPELhHw4ys= -github.com/u2takey/go-utils v0.3.1/go.mod h1:6e+v5vEZ/6gu12w/DC2ixZdZtCrNokVxD0JUklcqdCs= -github.com/ugorji/go v1.2.7/go.mod h1:nF9osbDWLy6bDVv/Rtoh6QgnvNDpmCalQV5urGCCS6M= -github.com/ugorji/go/codec v1.2.7 h1:YPXUKf7fYbp/y8xloBqZOw2qaVggbfwMlI8WM3wZUJ0= -github.com/ugorji/go/codec v1.2.7/go.mod h1:WGN1fab3R1fzQlVQTkfxVtIBhWDRqOviHU95kRgeqEY= -github.com/ugorji/go/codec v1.2.9 h1:rmenucSohSTiyL09Y+l2OCk+FrMxGMzho2+tjr5ticU= -github.com/ugorji/go/codec v1.2.9/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg= -github.com/ugorji/go/codec v1.2.11 h1:BMaWp1Bb6fHwEtbplGBGJ498wD+LKlNSl25MjdZY4dU= -github.com/ugorji/go/codec v1.2.11/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg= -github.com/ugorji/go/codec v1.2.12 h1:9LC83zGrHhuUA9l16C9AHXAqEV/2wBQ4nkvumAE65EE= -github.com/ugorji/go/codec v1.2.12/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg= -github.com/xanzy/ssh-agent v0.3.0 h1:wUMzuKtKilRgBAD1sUb8gOwwRr2FGoBVumcjoOACClI= -github.com/xanzy/ssh-agent v0.3.0/go.mod h1:3s9xbODqPuuhK9JV1R321M/FlMZSBvE5aY6eAcqrDh0= -github.com/xanzy/ssh-agent v0.3.3 h1:+/15pJfg/RsTxqYcX6fHqOXZwwMP+2VyYWJeWM2qQFM= -github.com/xanzy/ssh-agent v0.3.3/go.mod h1:6dzNDKs0J9rVPHPhaGCukekBHKqfl+L3KghI1Bc68Uw= -github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= -github.com/yuin/goldmark v1.5.2 h1:ALmeCk/px5FSm1MAcFBAsVKZjDuMVj8Tm7FFIlMJnqU= -github.com/yuin/goldmark v1.5.2/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= -github.com/yuin/goldmark v1.5.3 h1:3HUJmBFbQW9fhQOzMgseU134xfi6hU+mjWywx5Ty+/M= -github.com/yuin/goldmark v1.5.3/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= -github.com/yuin/goldmark v1.5.4 h1:2uY/xC0roWy8IBEGLgB1ywIoEJFGmRrX21YQcvGZzjU= -github.com/yuin/goldmark v1.5.4/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= -github.com/yuin/goldmark v1.5.5 h1:IJznPe8wOzfIKETmMkd06F8nXkmlhaHqFRM9l1hAGsU= -github.com/yuin/goldmark v1.5.5/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= -github.com/yuin/goldmark v1.5.6 h1:COmQAWTCcGetChm3Ig7G/t8AFAN00t+o8Mt4cf7JpwA= -github.com/yuin/goldmark v1.5.6/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= -github.com/yuin/goldmark v1.6.0 h1:boZcn2GTjpsynOsC0iJHnBWa4Bi0qzfJjthwauItG68= -github.com/yuin/goldmark v1.6.0/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= -github.com/yuin/goldmark v1.7.0 h1:EfOIvIMZIzHdB/R/zVrikYLPPwJlfMcNczJFMs1m6sA= -github.com/yuin/goldmark v1.7.0/go.mod h1:uzxRWxtg69N339t3louHJ7+O03ezfj6PlliRlaOzY1E= -github.com/yuin/goldmark v1.7.1 h1:3bajkSilaCbjdKVsKdZjZCLBNPL9pYzrCakKaf4U49U= -github.com/yuin/goldmark v1.7.1/go.mod h1:uzxRWxtg69N339t3louHJ7+O03ezfj6PlliRlaOzY1E= -github.com/yuin/goldmark v1.7.4 h1:BDXOHExt+A7gwPCJgPIIq7ENvceR7we7rOS9TNoLZeg= -github.com/yuin/goldmark v1.7.4/go.mod h1:uzxRWxtg69N339t3louHJ7+O03ezfj6PlliRlaOzY1E= -github.com/yuin/goldmark v1.7.8 h1:iERMLn0/QJeHFhxSt3p6PeN9mGnvIKSpG9YYorDMnic= -github.com/yuin/goldmark v1.7.8/go.mod h1:uzxRWxtg69N339t3louHJ7+O03ezfj6PlliRlaOzY1E= -gitlab.com/nyarla/go-crypt v0.0.0-20160106005555-d9a5dc2b789b h1:7gd+rd8P3bqcn/96gOZa3F5dpJr/vEiDQYlNb/y2uNs= -gitlab.com/nyarla/go-crypt v0.0.0-20160106005555-d9a5dc2b789b/go.mod h1:T3BPAOm2cqquPa0MKWeNkmOM5RQsRhkrwMWonFMN7fE= -go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= -go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= -gocv.io/x/gocv v0.25.0/go.mod h1:Rar2PS6DV+T4FL+PM535EImD/h13hGVaHhnCu1xarBs= -golang.org/x/arch v0.0.0-20210923205945-b76863e36670 h1:18EFjUmQOcUvxNYSkA6jO9VAiXCnxFY6NyDX0bHDmkU= -golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= -golang.org/x/arch v0.1.0 h1:oMxhUYsO9VsR1dcoVUjJjIGhx1LXol3989T/yZ59Xsw= -golang.org/x/arch v0.1.0/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= -golang.org/x/arch v0.3.0 h1:02VY4/ZcO/gBOH6PUaoiptASxtXU10jazRCP865E97k= -golang.org/x/arch v0.3.0/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= -golang.org/x/arch v0.7.0 h1:pskyeJh/3AmoQ8CPE95vxHLqp1G1GfGNXTmcl9NEKTc= -golang.org/x/arch v0.7.0/go.mod h1:FEVrYAQjsQXMVJ1nsMoVVXPZg6p2JE2mx8psSWTDQys= -golang.org/x/arch v0.8.0 h1:3wRIsP3pM4yUptoR96otTUOXI367OS0+c9eeRi9doIc= -golang.org/x/arch v0.8.0/go.mod h1:FEVrYAQjsQXMVJ1nsMoVVXPZg6p2JE2mx8psSWTDQys= -golang.org/x/crypto v0.0.0-20190219172222-a4c6cb3142f2/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= -golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= -golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= -golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= -golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= -golang.org/x/crypto v0.0.0-20220525230936-793ad666bf5e/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= -golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= -golang.org/x/crypto v0.0.0-20220826181053-bd7e27e6170d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= -golang.org/x/crypto v0.1.0 h1:MDRAIl0xIo9Io2xV565hzXHw3zVseKrJKodhohM5CjU= -golang.org/x/crypto v0.1.0/go.mod h1:RecgLatLF4+eUMCP1PoPZQb+cVrJcOPbHkTkbkB9sbw= -golang.org/x/crypto v0.2.0 h1:BRXPfhNivWL5Yq0BGQ39a2sW6t44aODpfxkWjYdzewE= -golang.org/x/crypto v0.2.0/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4= -golang.org/x/crypto v0.3.0 h1:a06MkbcxBrEFc0w0QIZWXrH/9cCX6KJyWbBOIwAn+7A= -golang.org/x/crypto v0.3.0/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4= -golang.org/x/crypto v0.3.1-0.20221117191849-2c476679df9a/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4= -golang.org/x/crypto v0.4.0 h1:UVQgzMY87xqpKNgb+kDsll2Igd33HszWHFLmpaRMq/8= -golang.org/x/crypto v0.4.0/go.mod h1:3quD/ATkf6oY+rnes5c3ExXTbLc8mueNue5/DoinL80= -golang.org/x/crypto v0.5.0 h1:U/0M97KRkSFvyD/3FSmdP5W5swImpNgle/EHFhOsQPE= -golang.org/x/crypto v0.5.0/go.mod h1:NK/OQwhpMQP3MwtdjgLlYHnH9ebylxKWv3e0fK+mkQU= -golang.org/x/crypto v0.6.0 h1:qfktjS5LUO+fFKeJXZ+ikTRijMmljikvG68fpMMruSc= -golang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58= -golang.org/x/crypto v0.7.0 h1:AvwMYaRytfdeVt3u6mLaxYtErKYjxA2OXjJ1HHq6t3A= -golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU= -golang.org/x/crypto v0.8.0 h1:pd9TJtTueMTVQXzk8E2XESSMQDj/U7OUu0PqJqPXQjQ= -golang.org/x/crypto v0.8.0/go.mod h1:mRqEX+O9/h5TFCrQhkgjo2yKi0yYA+9ecGkdQoHrywE= -golang.org/x/crypto v0.9.0 h1:LF6fAI+IutBocDJ2OT0Q1g8plpYljMZ4+lty+dsqw3g= -golang.org/x/crypto v0.9.0/go.mod h1:yrmDGqONDYtNj3tH8X9dzUun2m2lzPa9ngI6/RUPGR0= -golang.org/x/crypto v0.10.0 h1:LKqV2xt9+kDzSTfOhx4FrkEBcMrAgHSYgzywV9zcGmM= -golang.org/x/crypto v0.10.0/go.mod h1:o4eNf7Ede1fv+hwOwZsTHl9EsPFO6q6ZvYR8vYfY45I= -golang.org/x/crypto v0.11.0 h1:6Ewdq3tDic1mg5xRO4milcWCfMVQhI4NkqWWvqejpuA= -golang.org/x/crypto v0.11.0/go.mod h1:xgJhtzW8F9jGdVFWZESrid1U1bjeNy4zgy5cRr/CIio= -golang.org/x/crypto v0.12.0 h1:tFM/ta59kqch6LlvYnPa0yx5a83cL2nHflFhYKvv9Yk= -golang.org/x/crypto v0.12.0/go.mod h1:NF0Gs7EO5K4qLn+Ylc+fih8BSTeIjAP05siRnAh98yw= -golang.org/x/crypto v0.13.0 h1:mvySKfSWJ+UKUii46M40LOvyWfN0s2U+46/jDd0e6Ck= -golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc= -golang.org/x/crypto v0.14.0 h1:wBqGXzWJW6m1XrIKlAH0Hs1JJ7+9KBwnIO8v66Q9cHc= -golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4= -golang.org/x/crypto v0.15.0 h1:frVn1TEaCEaZcn3Tmd7Y2b5KKPaZ+I32Q2OA3kYp5TA= -golang.org/x/crypto v0.15.0/go.mod h1:4ChreQoLWfG3xLDer1WdlH5NdlQ3+mwnQq1YTKY+72g= -golang.org/x/crypto v0.16.0 h1:mMMrFzRSCF0GvB7Ne27XVtVAaXLrPmgPC7/v0tkwHaY= -golang.org/x/crypto v0.16.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4= -golang.org/x/crypto v0.18.0 h1:PGVlW0xEltQnzFZ55hkuX5+KLyrMYhHld1YHO4AKcdc= -golang.org/x/crypto v0.18.0/go.mod h1:R0j02AL6hcrfOiy9T4ZYp/rcWeMxM3L6QYxlOuEG1mg= -golang.org/x/crypto v0.19.0 h1:ENy+Az/9Y1vSrlrvBSyna3PITt4tiZLf7sgCjZBX7Wo= -golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU= -golang.org/x/crypto v0.20.0 h1:jmAMJJZXr5KiCw05dfYK9QnqaqKLYXijU23lsEdcQqg= -golang.org/x/crypto v0.20.0/go.mod h1:Xwo95rrVNIoSMx9wa1JroENMToLWn3RNVrTBpLHgZPQ= -golang.org/x/crypto v0.21.0 h1:X31++rzVUdKhX5sWmSOFZxx8UW/ldWx55cbf08iNAMA= -golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs= -golang.org/x/crypto v0.22.0 h1:g1v0xeRhjcugydODzvb3mEM9SQ0HGp9s/nh3COQ/C30= -golang.org/x/crypto v0.22.0/go.mod h1:vr6Su+7cTlO45qkww3VDJlzDn0ctJvRgYbC2NvXHt+M= -golang.org/x/crypto v0.23.0 h1:dIJU/v2J8Mdglj/8rJ6UUOM3Zc9zLZxVZwwxMooUSAI= -golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8= -golang.org/x/crypto v0.24.0 h1:mnl8DM0o513X8fdIkmyFE/5hTYxbwYOjDS/+rK6qpRI= -golang.org/x/crypto v0.24.0/go.mod h1:Z1PMYSOR5nyMcyAVAIQSKCDwalqy85Aqn1x3Ws4L5DM= -golang.org/x/crypto v0.26.0 h1:RrRspgV4mU+YwB4FYnuBoKsUapNIL5cohGAmSH3azsw= -golang.org/x/crypto v0.26.0/go.mod h1:GY7jblb9wI+FOo5y8/S2oY4zWP07AkOJ4+jxCqdqn54= -golang.org/x/crypto v0.27.0 h1:GXm2NjJrPaiv/h1tb2UH8QfgC/hOf/+z0p6PT8o1w7A= -golang.org/x/crypto v0.27.0/go.mod h1:1Xngt8kV6Dvbssa53Ziq6Eqn0HqbZi5Z6R0ZpwQzt70= -golang.org/x/crypto v0.28.0 h1:GBDwsMXVQi34v5CCYUm2jkJvu4cbtru2U4TN2PSyQnw= -golang.org/x/crypto v0.28.0/go.mod h1:rmgy+3RHxRZMyY0jjAJShp2zgEdOqj2AO7U0pYmeQ7U= -golang.org/x/crypto v0.29.0 h1:L5SG1JTTXupVV3n6sUqMTeWbjAyfPwoda2DLX8J8FrQ= -golang.org/x/crypto v0.29.0/go.mod h1:+F4F4N5hv6v38hfeYwTdx20oUvLLc+QfrE9Ax9HtgRg= -golang.org/x/crypto v0.30.0 h1:RwoQn3GkWiMkzlX562cLB7OxWvjH1L8xutO2WoJcRoY= -golang.org/x/crypto v0.30.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk= -golang.org/x/crypto v0.31.0 h1:ihbySMvVjLAeSH1IbfcRTkD/iNscyz8rGzjF/E5hV6U= -golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk= -golang.org/x/crypto v0.32.0 h1:euUpcYgM8WcP71gNpTqQCn6rC2t6ULUPiOzfWaXVVfc= -golang.org/x/crypto v0.32.0/go.mod h1:ZnnJkOaASj8g0AjIduWNlq2NRxL0PlBrbKVyZ6V/Ugc= -golang.org/x/crypto v0.33.0 h1:IOBPskki6Lysi0lo9qQvbxiQ+FvsCC/YWOecCHAixus= -golang.org/x/crypto v0.33.0/go.mod h1:bVdXmD7IV/4GdElGPozy6U7lWdRXA4qyRVGJV57uQ5M= -golang.org/x/crypto v0.36.0 h1:AnAEvhDddvBdpY+uR+MyHmuZzzNqXSe/GvuDeob5L34= -golang.org/x/crypto v0.36.0/go.mod h1:Y4J0ReaxCR1IMaabaSMugxJES1EpwhBHhv2bDHklZvc= -golang.org/x/image v0.0.0-20191009234506-e7c1f5e7dbb8/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= -golang.org/x/image v0.1.0 h1:r8Oj8ZA2Xy12/b5KZYj3tuv7NG/fBz3TwQVvpJ9l8Rk= -golang.org/x/image v0.1.0/go.mod h1:iyPr49SD/G/TBxYVB/9RRtGUT5eNbo2u4NamWeQcD5c= -golang.org/x/image v0.2.0 h1:/DcQ0w3VHKCC5p0/P2B0JpAZ9Z++V2KOo2fyU89CXBQ= -golang.org/x/image v0.2.0/go.mod h1:la7oBXb9w3YFjBqaAwtynVioc1ZvOnNteUNrifGNmAI= -golang.org/x/image v0.3.0 h1:HTDXbdK9bjfSWkPzDJIw89W8CAtfFGduujWs33NLLsg= -golang.org/x/image v0.3.0/go.mod h1:fXd9211C/0VTlYuAcOhW8dY/RtEJqODXOWBDpmYBf+A= -golang.org/x/image v0.4.0 h1:x1RWAiZIvERqkltrFjtQP1ycmiR5pmhjtCfVOtdURuQ= -golang.org/x/image v0.4.0/go.mod h1:FVC7BI/5Ym8R25iw5OLsgshdUBbT1h5jZTpA+mvAdZ4= -golang.org/x/image v0.5.0 h1:5JMiNunQeQw++mMOz48/ISeNu3Iweh/JaZU8ZLqHRrI= -golang.org/x/image v0.5.0/go.mod h1:FVC7BI/5Ym8R25iw5OLsgshdUBbT1h5jZTpA+mvAdZ4= -golang.org/x/image v0.6.0 h1:bR8b5okrPI3g/gyZakLZHeWxAR8Dn5CyxXv1hLH5g/4= -golang.org/x/image v0.6.0/go.mod h1:MXLdDR43H7cDJq5GEGXEVeeNhPgi+YYEQ2pC1byI1x0= -golang.org/x/image v0.7.0 h1:gzS29xtG1J5ybQlv0PuyfE3nmc6R4qB73m6LUUmvFuw= -golang.org/x/image v0.7.0/go.mod h1:nd/q4ef1AKKYl/4kft7g+6UyGbdiqWqTP1ZAbRoV7Rg= -golang.org/x/image v0.8.0 h1:agUcRXV/+w6L9ryntYYsF2x9fQTMd4T8fiiYXAVW6Jg= -golang.org/x/image v0.8.0/go.mod h1:PwLxp3opCYg4WR2WO9P0L6ESnsD6bLTWcw8zanLMVFM= -golang.org/x/image v0.9.0 h1:QrzfX26snvCM20hIhBwuHI/ThTg18b/+kcKdXHvnR+g= -golang.org/x/image v0.9.0/go.mod h1:jtrku+n79PfroUbvDdeUWMAI+heR786BofxrbiSF+J0= -golang.org/x/image v0.10.0 h1:gXjUUtwtx5yOE0VKWq1CH4IJAClq4UGgUA3i+rpON9M= -golang.org/x/image v0.10.0/go.mod h1:jtrku+n79PfroUbvDdeUWMAI+heR786BofxrbiSF+J0= -golang.org/x/image v0.11.0 h1:ds2RoQvBvYTiJkwpSFDwCcDFNX7DqjL2WsUgTNk0Ooo= -golang.org/x/image v0.11.0/go.mod h1:bglhjqbqVuEb9e9+eNR45Jfu7D+T4Qan+NhQk8Ck2P8= -golang.org/x/image v0.12.0 h1:w13vZbU4o5rKOFFR8y7M+c4A5jXDC0uXTdHYRP8X2DQ= -golang.org/x/image v0.12.0/go.mod h1:Lu90jvHG7GfemOIcldsh9A2hS01ocl6oNO7ype5mEnk= -golang.org/x/image v0.13.0 h1:3cge/F/QTkNLauhf2QoE9zp+7sr+ZcL4HnoZmdwg9sg= -golang.org/x/image v0.13.0/go.mod h1:6mmbMOeV28HuMTgA6OSRkdXKYw/t5W9Uwn2Yv1r3Yxk= -golang.org/x/image v0.14.0 h1:tNgSxAFe3jC4uYqvZdTr84SZoM1KfwdC9SKIFrLjFn4= -golang.org/x/image v0.14.0/go.mod h1:HUYqC05R2ZcZ3ejNQsIHQDQiwWM4JBqmm6MKANTp4LE= -golang.org/x/image v0.15.0 h1:kOELfmgrmJlw4Cdb7g/QGuB3CvDrXbqEIww/pNtNBm8= -golang.org/x/image v0.15.0/go.mod h1:HUYqC05R2ZcZ3ejNQsIHQDQiwWM4JBqmm6MKANTp4LE= -golang.org/x/image v0.16.0 h1:9kloLAKhUufZhA12l5fwnx2NZW39/we1UhBesW433jw= -golang.org/x/image v0.16.0/go.mod h1:ugSZItdV4nOxyqp56HmXwH0Ry0nBCpjnZdpDaIHdoPs= -golang.org/x/image v0.17.0 h1:nTRVVdajgB8zCMZVsViyzhnMKPwYeroEERRC64JuLco= -golang.org/x/image v0.17.0/go.mod h1:4yyo5vMFQjVjUcVk4jEQcU9MGy/rulF5WvUILseCM2E= -golang.org/x/image v0.18.0 h1:jGzIakQa/ZXI1I0Fxvaa9W7yP25TqT6cHIHn+6CqvSQ= -golang.org/x/image v0.18.0/go.mod h1:4yyo5vMFQjVjUcVk4jEQcU9MGy/rulF5WvUILseCM2E= -golang.org/x/image v0.19.0 h1:D9FX4QWkLfkeqaC62SonffIIuYdOk/UE2XKUBgRIBIQ= -golang.org/x/image v0.19.0/go.mod h1:y0zrRqlQRWQ5PXaYCOMLTW2fpsxZ8Qh9I/ohnInJEys= -golang.org/x/image v0.20.0 h1:7cVCUjQwfL18gyBJOmYvptfSHS8Fb3YUDtfLIZ7Nbpw= -golang.org/x/image v0.20.0/go.mod h1:0a88To4CYVBAHp5FXJm8o7QbUl37Vd85ply1vyD8auM= -golang.org/x/image v0.21.0 h1:c5qV36ajHpdj4Qi0GnE0jUc/yuo33OLFaa0d+crTD5s= -golang.org/x/image v0.21.0/go.mod h1:vUbsLavqK/W303ZroQQVKQ+Af3Yl6Uz1Ppu5J/cLz78= -golang.org/x/image v0.22.0 h1:UtK5yLUzilVrkjMAZAZ34DXGpASN8i8pj8g+O+yd10g= -golang.org/x/image v0.22.0/go.mod h1:9hPFhljd4zZ1GNSIZJ49sqbp45GKK9t6w+iXvGqZUz4= -golang.org/x/image v0.23.0 h1:HseQ7c2OpPKTPVzNjG5fwJsOTCiiwS4QdsYi5XU6H68= -golang.org/x/image v0.23.0/go.mod h1:wJJBTdLfCCf3tiHa1fNxpZmUI4mmoZvwMCPP0ddoNKY= -golang.org/x/image v0.24.0 h1:AN7zRgVsbvmTfNyqIbbOraYL8mSwcKncEj8ofjgzcMQ= -golang.org/x/image v0.24.0/go.mod h1:4b/ITuLfqYq1hqZcjofwctIhi7sZh2WaCjvsBNjjya8= -golang.org/x/image v0.25.0 h1:Y6uW6rH1y5y/LK1J8BPWZtr6yZ7hrsy6hFrXjgsc2fQ= -golang.org/x/image v0.25.0/go.mod h1:tCAmOEGthTtkalusGp1g3xa2gke8J6c2N565dTyl9Rs= -golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= -golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= -golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= -golang.org/x/mod v0.6.0/go.mod h1:4mET923SAdbXp2ki8ey+zGs1SLqsuM2Y0uvdZR/fUNI= -golang.org/x/mod v0.8.0 h1:LUYupSeNrTNCGzR/hVBk2NHZO4hXcVaW1k4Qx7rjPx8= -golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= -golang.org/x/mod v0.12.0 h1:rmsUpXtvNzj340zd98LZ4KntptpfRHwpFOHG188oHXc= -golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= -golang.org/x/mod v0.17.0 h1:zY54UmvipHiNd+pm+m0x9KhZ9hl1/7QNMyxXbc6ICqA= -golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= -golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= -golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200904194848-62affa334b73/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= -golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= -golang.org/x/net v0.0.0-20210326060303-6b1517762897/go.mod h1:uSPa2vr4CLtc/ILN5odXGNXS6mhrKVzTaCXzk9m6W3k= -golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= -golang.org/x/net v0.0.0-20220826154423-83b083e8dc8b/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk= -golang.org/x/net v0.1.0 h1:hZ/3BUoy5aId7sCpA/Tc5lt8DkFgdVS2onTpJsZ/fl0= -golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco= -golang.org/x/net v0.2.0 h1:sZfSu1wtKLGlWI4ZZayP0ck9Y73K1ynO6gqzTdBVdPU= -golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY= -golang.org/x/net v0.3.0 h1:VWL6FNY2bEEmsGVKabSlHu5Irp34xmMRoqb/9lF9lxk= -golang.org/x/net v0.3.0/go.mod h1:MBQ8lrhLObU/6UmLb4fmbmk5OcyYmqtbGd/9yIeKjEE= -golang.org/x/net v0.4.0 h1:Q5QPcMlvfxFTAPV0+07Xz/MpK9NTXu2VDUuy0FeMfaU= -golang.org/x/net v0.4.0/go.mod h1:MBQ8lrhLObU/6UmLb4fmbmk5OcyYmqtbGd/9yIeKjEE= -golang.org/x/net v0.5.0 h1:GyT4nK/YDHSqa1c4753ouYCDajOYKTja9Xb/OHtgvSw= -golang.org/x/net v0.5.0/go.mod h1:DivGGAXEgPSlEBzxGzZI+ZLohi+xUj054jfeKui00ws= -golang.org/x/net v0.6.0 h1:L4ZwwTvKW9gr0ZMS1yrHD9GZhIuVjOBBnaKH+SPQK0Q= -golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= -golang.org/x/net v0.7.0 h1:rJrUqqhjsgNp7KqAIc25s9pZnjU7TUcSY7HcVZjdn1g= -golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= -golang.org/x/net v0.8.0 h1:Zrh2ngAOFYneWTAIAPethzeaQLuHwhuBkuV6ZiRnUaQ= -golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc= -golang.org/x/net v0.9.0 h1:aWJ/m6xSmxWBx+V0XRHTlrYrPG56jKsLdTFmsSsCzOM= -golang.org/x/net v0.9.0/go.mod h1:d48xBJpPfHeWQsugry2m+kC02ZBRGRgulfHnEXEuWns= -golang.org/x/net v0.10.0 h1:X2//UzNDwYmtCLn7To6G58Wr6f5ahEAQgKNzv9Y951M= -golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= -golang.org/x/net v0.11.0 h1:Gi2tvZIJyBtO9SDr1q9h5hEQCp/4L2RQ+ar0qjx2oNU= -golang.org/x/net v0.11.0/go.mod h1:2L/ixqYpgIVXmeoSA/4Lu7BzTG4KIyPIryS4IsOd1oQ= -golang.org/x/net v0.12.0 h1:cfawfvKITfUsFCeJIHJrbSxpeu/E81khclypR0GVT50= -golang.org/x/net v0.12.0/go.mod h1:zEVYFnQC7m/vmpQFELhcD1EWkZlX69l4oqgmer6hfKA= -golang.org/x/net v0.14.0 h1:BONx9s002vGdD9umnlX1Po8vOZmrgH34qlHcD1MfK14= -golang.org/x/net v0.14.0/go.mod h1:PpSgVXXLK0OxS0F31C1/tv6XNguvCrnXIDrFMspZIUI= -golang.org/x/net v0.15.0 h1:ugBLEUaxABaB5AJqW9enI0ACdci2RUd4eP51NTBvuJ8= -golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk= -golang.org/x/net v0.16.0 h1:7eBu7KsSvFDtSXUIDbh3aqlK4DPsZ1rByC8PFfBThos= -golang.org/x/net v0.16.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE= -golang.org/x/net v0.17.0 h1:pVaXccu2ozPjCXewfr1S7xza/zcXTity9cCdXQYSjIM= -golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE= -golang.org/x/net v0.18.0 h1:mIYleuAkSbHh0tCv7RvjL3F6ZVbLjq4+R7zbOn3Kokg= -golang.org/x/net v0.18.0/go.mod h1:/czyP5RqHAH4odGYxBJ1qz0+CE5WZ+2j1YgoEo8F2jQ= -golang.org/x/net v0.19.0 h1:zTwKpTd2XuCqf8huc7Fo2iSy+4RHPd10s4KzeTnVr1c= -golang.org/x/net v0.19.0/go.mod h1:CfAk/cbD4CthTvqiEl8NpboMuiuOYsAr/7NOjZJtv1U= -golang.org/x/net v0.20.0 h1:aCL9BSgETF1k+blQaYUBx9hJ9LOGP3gAVemcZlf1Kpo= -golang.org/x/net v0.20.0/go.mod h1:z8BVo6PvndSri0LbOE3hAn0apkU+1YvI6E70E9jsnvY= -golang.org/x/net v0.21.0 h1:AQyQV4dYCvJ7vGmJyKki9+PBdyvhkSd8EIx/qb0AYv4= -golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44= -golang.org/x/net v0.22.0 h1:9sGLhx7iRIHEiX0oAJ3MRZMUCElJgy7Br1nO+AMN3Tc= -golang.org/x/net v0.22.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg= -golang.org/x/net v0.24.0 h1:1PcaxkF854Fu3+lvBIx5SYn9wRlBzzcnHZSiaFFAb0w= -golang.org/x/net v0.24.0/go.mod h1:2Q7sJY5mzlzWjKtYUEXSlBWCdyaioyXzRB2RtU8KVE8= -golang.org/x/net v0.25.0 h1:d/OCCoBEUq33pjydKrGQhw7IlUPI2Oylr+8qLx49kac= -golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM= -golang.org/x/net v0.33.0 h1:74SYHlV8BIgHIFC/LrYkOGIwL19eTYXQ5wc6TBuO36I= -golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4= -golang.org/x/net v0.34.0 h1:Mb7Mrk043xzHgnRM88suvJFwzVrRfHEHJEl5/71CKw0= -golang.org/x/net v0.34.0/go.mod h1:di0qlW3YNM5oh6GqDGQr92MyTozJPmybPK4Ev/Gm31k= -golang.org/x/net v0.35.0 h1:T5GQRQb2y08kTAByq9L4/bz8cipCdA8FbRTXewonqY8= -golang.org/x/net v0.35.0/go.mod h1:EglIi67kWsHKlRzzVMUD93VMSWGFOMSZgxFjparz1Qk= -golang.org/x/oauth2 v0.1.0 h1:isLCZuhj4v+tYv7eskaN4v/TM+A1begWWgyVJDdl1+Y= -golang.org/x/oauth2 v0.1.0/go.mod h1:G9FE4dLTsbXUu90h/Pf85g4w1D+SSAgR+q46nJZ8M4A= -golang.org/x/oauth2 v0.2.0 h1:GtQkldQ9m7yvzCL1V+LrYow3Khe0eJH0w7RbX/VbaIU= -golang.org/x/oauth2 v0.2.0/go.mod h1:Cwn6afJ8jrQwYMxQDTpISoXmXW9I6qF6vDeuuoX3Ibs= -golang.org/x/oauth2 v0.3.0 h1:6l90koy8/LaBLmLu8jpHeHexzMwEita0zFfYlggy2F8= -golang.org/x/oauth2 v0.3.0/go.mod h1:rQrIauxkUhJ6CuwEXwymO2/eh4xz2ZWF1nBkcxS+tGk= -golang.org/x/oauth2 v0.4.0 h1:NF0gk8LVPg1Ml7SSbGyySuoxdsXitj7TvgvuRxIMc/M= -golang.org/x/oauth2 v0.4.0/go.mod h1:RznEsdpjGAINPTOF0UH/t+xJ75L18YO3Ho6Pyn+uRec= -golang.org/x/oauth2 v0.5.0 h1:HuArIo48skDwlrvM3sEdHXElYslAMsf3KwRkkW4MC4s= -golang.org/x/oauth2 v0.5.0/go.mod h1:9/XBHVqLaWO3/BRHs5jbpYCnOZVjj5V0ndyaAM7KB4I= -golang.org/x/oauth2 v0.6.0 h1:Lh8GPgSKBfWSwFvtuWOfeI3aAAnbXTSutYxJiOJFgIw= -golang.org/x/oauth2 v0.6.0/go.mod h1:ycmewcwgD4Rpr3eZJLSB4Kyyljb3qDh40vJ8STE5HKw= -golang.org/x/oauth2 v0.7.0 h1:qe6s0zUXlPX80/dITx3440hWZ7GwMwgDDyrSGTPJG/g= -golang.org/x/oauth2 v0.7.0/go.mod h1:hPLQkd9LyjfXTiRohC/41GhcFqxisoUQ99sCUOHO9x4= -golang.org/x/oauth2 v0.8.0 h1:6dkIjl3j3LtZ/O3sTgZTMsLKSftL/B8Zgq4huOIIUu8= -golang.org/x/oauth2 v0.8.0/go.mod h1:yr7u4HXZRm1R1kBWqr/xKNqewf0plRYoB7sla+BCIXE= -golang.org/x/oauth2 v0.9.0 h1:BPpt2kU7oMRq3kCHAA1tbSEshXRw1LpG2ztgDwrzuAs= -golang.org/x/oauth2 v0.9.0/go.mod h1:qYgFZaFiu6Wg24azG8bdV52QJXJGbZzIIsRCdVKzbLw= -golang.org/x/oauth2 v0.10.0 h1:zHCpF2Khkwy4mMB4bv0U37YtJdTGW8jI0glAApi0Kh8= -golang.org/x/oauth2 v0.10.0/go.mod h1:kTpgurOux7LqtuxjuyZa4Gj2gdezIt/jQtGnNFfypQI= -golang.org/x/oauth2 v0.11.0 h1:vPL4xzxBM4niKCW6g9whtaWVXTJf1U5e4aZxxFx/gbU= -golang.org/x/oauth2 v0.11.0/go.mod h1:LdF7O/8bLR/qWK9DrpXmbHLTouvRHK0SgJl0GmDBchk= -golang.org/x/oauth2 v0.12.0 h1:smVPGxink+n1ZI5pkQa8y6fZT0RW0MgCO5bFpepy4B4= -golang.org/x/oauth2 v0.12.0/go.mod h1:A74bZ3aGXgCY0qaIC9Ahg6Lglin4AMAco8cIv9baba4= -golang.org/x/oauth2 v0.13.0 h1:jDDenyj+WgFtmV3zYVoi8aE2BwtXFLWOA67ZfNWftiY= -golang.org/x/oauth2 v0.13.0/go.mod h1:/JMhi4ZRXAf4HG9LiNmxvk+45+96RUlVThiH8FzNBn0= -golang.org/x/oauth2 v0.14.0 h1:P0Vrf/2538nmC0H+pEQ3MNFRRnVR7RlqyVw+bvm26z0= -golang.org/x/oauth2 v0.14.0/go.mod h1:lAtNWgaWfL4cm7j2OV8TxGi9Qb7ECORx8DktCY74OwM= -golang.org/x/oauth2 v0.15.0 h1:s8pnnxNVzjWyrvYdFUQq5llS1PX2zhPXmccZv99h7uQ= -golang.org/x/oauth2 v0.15.0/go.mod h1:q48ptWNTY5XWf+JNten23lcvHpLJ0ZSxF5ttTHKVCAM= -golang.org/x/oauth2 v0.16.0 h1:aDkGMBSYxElaoP81NpoUoz2oo2R2wHdZpGToUxfyQrQ= -golang.org/x/oauth2 v0.16.0/go.mod h1:hqZ+0LWXsiVoZpeld6jVt06P3adbS2Uu911W1SsJv2o= -golang.org/x/oauth2 v0.17.0 h1:6m3ZPmLEFdVxKKWnKq4VqZ60gutO35zm+zrAHVmHyDQ= -golang.org/x/oauth2 v0.17.0/go.mod h1:OzPDGQiuQMguemayvdylqddI7qcD9lnSDb+1FiwQ5HA= -golang.org/x/oauth2 v0.18.0 h1:09qnuIAgzdx1XplqJvW6CQqMCtGZykZWcXzPMPUusvI= -golang.org/x/oauth2 v0.18.0/go.mod h1:Wf7knwG0MPoWIMMBgFlEaSUDaKskp0dCfrlJRJXbBi8= -golang.org/x/oauth2 v0.19.0 h1:9+E/EZBCbTLNrbN35fHv/a/d/mOBatymz1zbtQrXpIg= -golang.org/x/oauth2 v0.19.0/go.mod h1:vYi7skDa1x015PmRRYZ7+s1cWyPgrPiSYRe4rnsexc8= -golang.org/x/oauth2 v0.20.0 h1:4mQdhULixXKP1rwYBW0vAijoXnkTG0BLCDRzfe1idMo= -golang.org/x/oauth2 v0.20.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI= -golang.org/x/oauth2 v0.21.0 h1:tsimM75w1tF/uws5rbeHzIWxEqElMehnc+iW793zsZs= -golang.org/x/oauth2 v0.21.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI= -golang.org/x/oauth2 v0.22.0 h1:BzDx2FehcG7jJwgWLELCdmLuxk2i+x9UDpSiss2u0ZA= -golang.org/x/oauth2 v0.22.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI= -golang.org/x/oauth2 v0.23.0 h1:PbgcYx2W7i4LvjJWEbf0ngHV6qJYr86PkAV3bXdLEbs= -golang.org/x/oauth2 v0.23.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI= -golang.org/x/oauth2 v0.24.0 h1:KTBBxWqUa0ykRPLtV69rRto9TLXcqYkeswu48x/gvNE= -golang.org/x/oauth2 v0.24.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI= -golang.org/x/oauth2 v0.25.0 h1:CY4y7XT9v0cRI9oupztF8AgiIu99L/ksR/Xp/6jrZ70= -golang.org/x/oauth2 v0.25.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI= -golang.org/x/oauth2 v0.26.0 h1:afQXWNNaeC4nvZ0Ed9XvCCzXM6UHJG7iCg0W4fPqSBE= -golang.org/x/oauth2 v0.26.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI= -golang.org/x/oauth2 v0.28.0 h1:CrgCKl8PPAVtLnU3c+EDw6x11699EWlsDeWNWKdIOkc= -golang.org/x/oauth2 v0.28.0/go.mod h1:onh5ek6nERTohokkhCD/y2cV4Do3fxFHFuAejCkRWT8= -golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M= -golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= -golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ= -golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= -golang.org/x/sync v0.9.0 h1:fEo0HyrW1GIgZdpbhCRO0PkJajUS5H9IFUztCgEo2jQ= -golang.org/x/sync v0.9.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= -golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ= -golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= -golang.org/x/sync v0.11.0 h1:GGz8+XQP4FvTTrjZPzNKTMFtSXH80RAzG+5ghFPgK9w= -golang.org/x/sync v0.11.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= -golang.org/x/sync v0.12.0 h1:MHc5BpPuC30uJk597Ri8TV3CNZcTLu6B6z4lJy+g6Jw= -golang.org/x/sync v0.12.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= -golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200602225109-6fdc65e7d980/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210324051608-47abb6519492/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210502180810-71e4cd670f79/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20211007075335-d3039528d8ac/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220704084225-05e143d24a9e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220825204002-c680a09ffe64/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.1.0 h1:kunALQeHf1/185U1i0GOB/fy1IPRDDpuoOOqRReG57U= -golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.2.0 h1:ljd4t30dBnAvMZaQCevtY0xLLD0A+bRZXbgLMLU1F/A= -golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.3.0 h1:w8ZOecv6NaNa/zC8944JTU3vz4u6Lagfk4RPQxv92NQ= -golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.4.0 h1:Zr2JFtRQNX3BCZ8YtxRE9hNJYC8J6I1MVbMg6owUp18= -golang.org/x/sys v0.4.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.5.0 h1:MUK/U/4lj1t1oPg0HfuXDN/Z1wv31ZJ/YcPiGccS4DU= -golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.6.0 h1:MVltZSvRTcU2ljQOhs94SXPftV6DCNnZViHeQps87pQ= -golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.7.0 h1:3jlCCIQZPdOYu1h8BkNvLz8Kgwtae2cagcG/VamtZRU= -golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.8.0 h1:EBmGv8NaZBZTWvrbjNoL6HVt+IVy3QDQpJs7VRIw3tU= -golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.9.0 h1:KS/R3tvhPqvJvwcKfnBHJwwthS11LRhmM5D59eEXa0s= -golang.org/x/sys v0.9.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.10.0 h1:SqMFp9UcQJZa+pmYuAKjd9xq1f0j5rLcDIk0mj4qAsA= -golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.11.0 h1:eG7RXZHdqOJ1i+0lgLgCpSXAp6M3LYlAo6osgSi0xOM= -golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.12.0 h1:CM0HF96J0hcLAwsHPJZjfdNzs0gftsLfgKt57wWHJ0o= -golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.13.0 h1:Af8nKPmuFypiUBjVoU9V20FiaFXOcuZI21p0ycVYYGE= -golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.14.0 h1:Vz7Qs629MkJkGyHxUlRHizWJRG2j8fbQKjELVSNhy7Q= -golang.org/x/sys v0.14.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/sys v0.15.0 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc= -golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/sys v0.16.0 h1:xWw16ngr6ZMtmxDyKyIgsE93KNKz5HKmMa3b8ALHidU= -golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/sys v0.17.0 h1:25cE3gD+tdBA7lp7QfhuV+rJiE9YXTcS3VG1SqssI/Y= -golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4= -golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/sys v0.19.0 h1:q5f1RH2jigJ1MoAWp2KTp3gm5zAGFUTarQZ5U386+4o= -golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/sys v0.20.0 h1:Od9JTbYCk261bKm4M/mw7AklTlFYIa0bIp9BgSm1S8Y= -golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/sys v0.21.0 h1:rF+pYz3DAGSQAxAu1CbC7catZg4ebC4UIeIhKxBZvws= -golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/sys v0.23.0 h1:YfKFowiIMvtgl1UERQoTPPToxltDeZfbj4H7dVUCwmM= -golang.org/x/sys v0.23.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/sys v0.25.0 h1:r+8e+loiHxRqhXVl6ML1nO3l1+oFoWbnlu2Ehimmi34= -golang.org/x/sys v0.25.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo= -golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/sys v0.27.0 h1:wBqf8DvsY9Y/2P8gAfPDEYNuS30J4lPHJxXSb/nJZ+s= -golang.org/x/sys v0.27.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA= -golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/sys v0.29.0 h1:TPYlXGxvx1MGTn2GiZDhnjPA9wZzZeGKHHmKhHYvgaU= -golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc= -golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik= -golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= -golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= -golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= -golang.org/x/term v0.0.0-20220722155259-a9ba230a4035/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= -golang.org/x/term v0.1.0 h1:g6Z6vPFA9dYBAF7DWcH6sCcOntplXsDKcliusYijMlw= -golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= -golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc= -golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= -golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U= -golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= -golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= -golang.org/x/text v0.4.0 h1:BrVqGRd7+k1DiOgtnFvAkoQEWQvBc25ouMJM6429SFg= -golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= -golang.org/x/text v0.5.0 h1:OLmvp0KP+FVG99Ct/qFiL/Fhk4zp4QQnZ7b2U+5piUM= -golang.org/x/text v0.5.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= -golang.org/x/text v0.6.0 h1:3XmdazWV+ubf7QgHSTWeykHOci5oeekaGJBLkrkaw4k= -golang.org/x/text v0.6.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= -golang.org/x/text v0.7.0 h1:4BRB4x83lYWy72KwLD/qYDuTu7q9PjSagHvijDw7cLo= -golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= -golang.org/x/text v0.8.0 h1:57P1ETyNKtuIjB4SRd15iJxuhj8Gc416Y78H3qgMh68= -golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= -golang.org/x/text v0.9.0 h1:2sjJmO8cDvYveuX97RDLsxlyUxLl+GHoLxBiRdHllBE= -golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= -golang.org/x/text v0.10.0 h1:UpjohKhiEgNc0CSauXmwYftY1+LlaC75SJwh0SgCX58= -golang.org/x/text v0.10.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= -golang.org/x/text v0.11.0 h1:LAntKIrcmeSKERyiOh0XMV39LXS8IE9UL2yP7+f5ij4= -golang.org/x/text v0.11.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= -golang.org/x/text v0.12.0 h1:k+n5B8goJNdU7hSvEtMUz3d1Q6D/XW4COJSJR6fN0mc= -golang.org/x/text v0.12.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= -golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k= -golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= -golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= -golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= -golang.org/x/text v0.15.0 h1:h1V/4gjBv8v9cjcR6+AR5+/cIYK5N/WAgiv4xlsEtAk= -golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= -golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4= -golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI= -golang.org/x/text v0.17.0 h1:XtiM5bkSOt+ewxlOE/aE/AKEHibwj/6gvWMl9Rsh0Qc= -golang.org/x/text v0.17.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= -golang.org/x/text v0.18.0 h1:XvMDiNzPAl0jr17s6W9lcaIhGUfUORdGCNsuLmPG224= -golang.org/x/text v0.18.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= -golang.org/x/text v0.19.0 h1:kTxAhCbGbxhK0IwgSKiMO5awPoDQ0RpfiVYBfK860YM= -golang.org/x/text v0.19.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= -golang.org/x/text v0.20.0 h1:gK/Kv2otX8gz+wn7Rmb3vT96ZwuoxnQlY+HlJVj7Qug= -golang.org/x/text v0.20.0/go.mod h1:D4IsuqiFMhST5bX19pQ9ikHC2GsaKyk/oF+pn3ducp4= -golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo= -golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ= -golang.org/x/text v0.22.0 h1:bofq7m3/HAFvbF51jz3Q9wLg3jkvSPuiZu/pD1XwgtM= -golang.org/x/text v0.22.0/go.mod h1:YRoo4H8PVmsu+E3Ou7cqLVH8oXWIHVoX0jqUWALQhfY= -golang.org/x/text v0.23.0 h1:D71I7dUrlY+VX0gQShAThNGHFxZ13dGLBHQLVl1mJlY= -golang.org/x/text v0.23.0/go.mod h1:/BLNzu4aZCJ1+kcD0DNRotWKage4q2rGVAg4o22unh4= -golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20181030221726-6c7e314b6563/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= -golang.org/x/tools v0.2.0/go.mod h1:y4OqIKeOV/fWJetJ8bXPU1sEVniLMIyDAZWeHdV+NTA= -golang.org/x/tools v0.6.0 h1:BOw41kyTf3PuCW1pVQf8+Cyg8pMlkYB1oo9iJ6D/lKM= -golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= -golang.org/x/tools v0.13.0 h1:Iey4qkscZuv0VvIt8E0neZjtPVQFSc870HQ448QgEmQ= -golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58= -golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d h1:vU5i/LfpvrRCpgM/VPfJLg5KjxD3E+hfT1SH+d9zLwg= -golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk= -golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c= -google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= -google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= -google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= -google.golang.org/protobuf v1.28.0 h1:w43yiav+6bVFTBQFZX0r7ipe9JQ1QsbMgHwbBziscLw= -google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= -google.golang.org/protobuf v1.28.1 h1:d0NfwRgPtno5B1Wa6L2DAG+KivqkdutMf1UhdNx175w= -google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= -google.golang.org/protobuf v1.30.0 h1:kPPoIgf3TsEvrm0PFe15JQ+570QVxYzEvvHqChK+cng= -google.golang.org/protobuf v1.30.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= -google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8= -google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= -google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI= -google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= -google.golang.org/protobuf v1.34.1 h1:9ddQBjfCyZPOHPUiPxpYESBLc+T8P3E+Vo4IbKZgFWg= -google.golang.org/protobuf v1.34.1/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= -gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= -gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= -gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= -gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4= -gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= -gopkg.in/warnings.v0 v0.1.2 h1:wFXVbFY8DY5/xOe1ECiWdKCzZlxgshcYVNkBHstARME= -gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI= -gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.2.7/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= -gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= -gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo= -gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -gopkg.in/yaml.v3 v3.0.0/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= -gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -nullprogram.com/x/optparse v1.0.0/go.mod h1:KdyPE+Igbe0jQUrVfMqDMeJQIJZEuyV7pjYmp6pbG50= -rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4= -sigs.k8s.io/yaml v1.2.0/go.mod h1:yfXDCHCao9+ENCvLSE62v9VSji2MKu5jeNfTrofGhJc= diff --git a/htdocs-admin b/htdocs-admin deleted file mode 120000 index 4e69c177..00000000 --- a/htdocs-admin +++ /dev/null @@ -1 +0,0 @@ -admin/static \ No newline at end of file diff --git a/htdocs-dashboard b/htdocs-dashboard deleted file mode 120000 index 4f97701f..00000000 --- a/htdocs-dashboard +++ /dev/null @@ -1 +0,0 @@ -dashboard/static \ No newline at end of file diff --git a/libfic/certificate.go b/libfic/certificate.go index 84ab8974..0454ca13 100644 --- a/libfic/certificate.go +++ b/libfic/certificate.go @@ -1,112 +1,36 @@ package fic import ( - "database/sql" - "math/big" - "time" + "os/exec" ) -// Certificate represents a client certificate, which can be associated to a team. -// -// This is one method usable to handle authentication. -// To use it in nginx, you'll need to add following lines in your configuration: -// -// ssl_client_certificate PKI/shared/ca.pem; -// ssl_trusted_certificate PKI/shared/ca.pem; -// ssl_verify_client optional; -// -// Non-recognized clients will have access to a registration form. -type Certificate struct { - Id uint64 `json:"id,string"` - Creation time.Time `json:"creation"` - Password string `json:"password"` - Revoked *time.Time `json:"revoked"` -} +func GenerateCA() string { + // Call the script and return its standard and error output + cmd := exec.Command("./CA.sh", "-newca") -// GetCertificates returns the list of all generated certificates. -func GetCertificates() (certificates []*Certificate, err error) { - var rows *sql.Rows - if rows, err = DBQuery("SELECT id_cert, creation, password, revoked FROM certificates ORDER BY creation"); err == nil { - defer rows.Close() - - certificates = []*Certificate{} - for rows.Next() { - c := &Certificate{} - if err = rows.Scan(&c.Id, &c.Creation, &c.Password, &c.Revoked); err != nil { - return - } - certificates = append(certificates, c) - } - err = rows.Err() - } - return -} - -// GetCertificate retrieves a certificate from its serial number. -func GetCertificate(serial uint64) (c *Certificate, err error) { - c = &Certificate{} - err = DBQueryRow("SELECT id_cert, creation, password, revoked FROM certificates WHERE id_cert = ?", serial).Scan(&c.Id, &c.Creation, &c.Password, &c.Revoked) - return -} - -// ExistingCertSerial tells you if the given bytes correspond to a know certificate. -func ExistingCertSerial(serial [8]byte) bool { - var m big.Int - m.SetBytes(serial[:]) - - c, _ := GetCertificate(m.Uint64()) - return c.Id > 0 -} - -// RegisterCertificate registers a certificate in the database. -// -// "serial" is the certificate serial number -// "password" is the one used to crypt privatekey and .p12 -func RegisterCertificate(serial uint64, password string) (Certificate, error) { - now := time.Now() - if _, err := DBExec("INSERT INTO certificates (id_cert, creation, password) VALUES (?, ?, ?)", serial, now, password); err != nil { - return Certificate{}, err + if output, err := cmd.CombinedOutput(); err != nil { + return string(output) + err.Error() } else { - return Certificate{serial, now, password, nil}, nil + return string(output) } } -// Update applies modifications back to the database. -func (c *Certificate) Update() (int64, error) { - if res, err := DBExec("UPDATE certificates SET creation = ?, password = ?, revoked = ? WHERE id_cert = ?", c.Creation, c.Password, c.Revoked, c.Id); err != nil { - return 0, err - } else if nb, err := res.RowsAffected(); err != nil { - return 0, err +func (t Team) GenerateCert() string { + cmd := exec.Command("./CA.sh", "-newclient", t.Name) + + if output, err := cmd.CombinedOutput(); err != nil { + return string(output) + err.Error() } else { - return nb, err + return string(output) } } -// Revoke the certificate in database. -func (c *Certificate) Revoke() (int64, error) { - now := time.Now() - c.Revoked = &now - return c.Update() -} +func (t Team) RevokeCert() string { + cmd := exec.Command("./CA.sh", "-revoke", t.Name) -// Delete the certificate entry in the database. -func (c Certificate) Delete() (int64, error) { - if res, err := DBExec("DELETE FROM certificates WHERE id_cert = ?", c.Id); err != nil { - return 0, err - } else if nb, err := res.RowsAffected(); err != nil { - return 0, err + if output, err := cmd.CombinedOutput(); err != nil { + return string(output) + err.Error() } else { - return nb, err - } -} - -// ClearCertificates removes all certificates from database. -func ClearCertificates() (int64, error) { - if res, err := DBExec("DELETE FROM certificates"); err != nil { - return 0, err - } else if nb, err := res.RowsAffected(); err != nil { - return 0, err - } else { - return nb, err + return string(output) } } diff --git a/libfic/cyberrange.go b/libfic/cyberrange.go deleted file mode 100644 index 55690932..00000000 --- a/libfic/cyberrange.go +++ /dev/null @@ -1,34 +0,0 @@ -package fic - -import () - -type CyberrangeAPIResponse struct { - Data interface{} - CurrentPage int `json:"current_page"` - PerPage int `json:"per_page"` - LastPage int `json:"last_page"` - Total int `json:"total"` -} - -type CyberrangeTeamBase struct { - UUID string `json:"uuid"` - Members []CyberrangeTeamMember `json:"members"` - Name string `json:"name"` - Score int64 `json:"score"` - Rank int `json:"rank"` -} - -type CyberrangeTeam struct { - UUID string `json:"session_uuid"` - Members []CyberrangeTeamMember `json:"members"` - Name string `json:"name"` - Score int64 `json:"score"` - Rank int `json:"rank"` -} - -type CyberrangeTeamMember struct { - UUID string `json:"session_uuid"` - Name string `json:"name"` - Nickname string `json:"nickname"` - EMail string `json:"email"` -} diff --git a/libfic/db.go b/libfic/db.go index 0a96120e..2f2d6228 100644 --- a/libfic/db.go +++ b/libfic/db.go @@ -2,137 +2,46 @@ package fic import ( "database/sql" - "errors" - "fmt" - "io/ioutil" - "log" - "os" - "strings" - "time" - - "github.com/go-sql-driver/mysql" + _ "github.com/go-sql-driver/mysql" ) -// db stores the connection to the database var db *sql.DB -// DSNGenerator returns DSN filed with values from environment -func DSNGenerator() string { - db_user := "fic" - db_password := "fic" - db_host := "" - db_db := "fic" - - if v, exists := os.LookupEnv("MYSQL_HOST"); exists { - if strings.HasPrefix(v, "/") { - db_host = "unix(" + v + ")" - } else { - db_host = "tcp(" + v + ":" - if p, exists := os.LookupEnv("MYSQL_PORT"); exists { - db_host += p + ")" - } else { - db_host += "3306)" - } - } +func DBInit(dsn string) error { + var err error + if db, err = sql.Open("mysql", dsn); err != nil { + return err } - if v, exists := os.LookupEnv("MYSQL_PASSWORD"); exists { - db_password = v - } else if v, exists := os.LookupEnv("MYSQL_PASSWORD_FILE"); exists { - fd, err := os.Open(v) - if err != nil { - log.Fatal("Unable to open MYSQL_PASSWORD_FILE:", err) - } - - b, _ := ioutil.ReadAll(fd) - db_password = strings.TrimSpace(string(b)) - - fd.Close() - } else if v, exists := os.LookupEnv("MYSQL_ROOT_PASSWORD"); exists { - db_user = "root" - db_password = v - } - if v, exists := os.LookupEnv("MYSQL_USER"); exists { - db_user = v - } - if v, exists := os.LookupEnv("MYSQL_DATABASE"); exists { - db_db = v - } - - return db_user + ":" + db_password + "@" + db_host + "/" + db_db + return nil } -func DBIsDuplicateKeyError(err error) bool { - var mysqlErr *mysql.MySQLError - return errors.As(err, &mysqlErr) && mysqlErr.Number == 1062 -} - -// DBInit establishes the connection to the database -func DBInit(dsn string) (err error) { - if db, err = sql.Open("mysql", dsn+"?parseTime=true&foreign_key_checks=1"); err != nil { - return - } - - _, err = db.Exec(`SET SESSION sql_mode = 'STRICT_TRANS_TABLES,NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO';`) - for i := 0; err != nil && i < 45; i += 1 { - if _, err = db.Exec(`SET SESSION sql_mode = 'STRICT_TRANS_TABLES,NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO';`); err != nil && i <= 45 { - log.Println("An error occurs when trying to connect to DB, will retry in 2 seconds: ", err) - time.Sleep(2 * time.Second) - } - } - - return -} - -// DBCreate creates all necessary tables used by the package func DBCreate() error { if _, err := db.Exec(` CREATE TABLE IF NOT EXISTS events( id_event INTEGER NOT NULL PRIMARY KEY AUTO_INCREMENT, - txt VARCHAR(255) NOT NULL, - kind ENUM('primary', 'secondary', 'success', 'danger', 'warning', 'info', 'light', 'dark') NOT NULL, + txt VARCHAR(255) NOT NULL UNIQUE, + kind ENUM('alert-default', 'alert-primary', 'alert-info', 'alert-warning', 'alert-danger', 'alert-success') NOT NULL, time TIMESTAMP NOT NULL -) DEFAULT CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci; +); `); err != nil { return err } if _, err := db.Exec(` CREATE TABLE IF NOT EXISTS themes( id_theme INTEGER NOT NULL PRIMARY KEY AUTO_INCREMENT, - name VARCHAR(255) NOT NULL, - locked BOOLEAN NOT NULL DEFAULT 0, - url_id VARCHAR(191) NOT NULL UNIQUE, - path VARCHAR(191) NOT NULL UNIQUE, - headline TEXT NOT NULL, - intro TEXT NOT NULL, - image VARCHAR(255) NOT NULL, - background_color INTEGER UNSIGNED NOT NULL, - authors TEXT NOT NULL, - partner_img VARCHAR(255) NOT NULL, - partner_href VARCHAR(255) NOT NULL, - partner_text TEXT NOT NULL -) DEFAULT CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci; + name VARCHAR(255) NOT NULL UNIQUE, + authors VARCHAR(255) NOT NULL +); `); err != nil { return err } if _, err := db.Exec(` CREATE TABLE IF NOT EXISTS teams( id_team INTEGER NOT NULL PRIMARY KEY AUTO_INCREMENT, + initial_name VARCHAR(255) NOT NULL, name VARCHAR(255) NOT NULL, - color INTEGER UNSIGNED NOT NULL, - active BOOLEAN NOT NULL DEFAULT 1, - external_id TEXT NOT NULL, - password VARCHAR(255) NULL -) DEFAULT CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci; -`); err != nil { - return err - } - if _, err := db.Exec(` -CREATE TABLE IF NOT EXISTS certificates( - id_cert BIGINT UNSIGNED NOT NULL PRIMARY KEY AUTO_INCREMENT, - creation TIMESTAMP NOT NULL, - password VARCHAR(255) NOT NULL, - revoked TIMESTAMP NULL -) DEFAULT CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci; + color INTEGER NOT NULL +); `); err != nil { return err } @@ -145,36 +54,23 @@ CREATE TABLE IF NOT EXISTS team_members( nickname VARCHAR(255) NOT NULL, company VARCHAR(255) NOT NULL, FOREIGN KEY(id_team) REFERENCES teams(id_team) -) DEFAULT CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci; +); `); err != nil { return err } if _, err := db.Exec(` CREATE TABLE IF NOT EXISTS exercices( id_exercice INTEGER NOT NULL PRIMARY KEY AUTO_INCREMENT, - id_theme INTEGER NULL, + id_theme INTEGER NOT NULL, title VARCHAR(255) NOT NULL, - authors TEXT NOT NULL, - image VARCHAR(255) NOT NULL, - background_color INTEGER UNSIGNED NOT NULL, - disabled BOOLEAN NOT NULL DEFAULT 0, - headline TEXT NOT NULL, - url_id VARCHAR(255) NOT NULL, - path VARCHAR(191) NOT NULL UNIQUE, statement TEXT NOT NULL, - overview TEXT NOT NULL, - issue TEXT NOT NULL, - issue_kind ENUM('primary', 'secondary', 'success', 'danger', 'warning', 'info', 'light', 'dark') NOT NULL DEFAULT 'info', + hint TEXT NOT NULL, depend INTEGER, gain INTEGER NOT NULL, - coefficient_cur FLOAT NOT NULL DEFAULT 1.0, - finished TEXT NOT NULL, video_uri VARCHAR(255) NOT NULL, - resolution MEDIUMTEXT NOT NULL, - seealso TEXT NOT NULL, FOREIGN KEY(id_theme) REFERENCES themes(id_theme), FOREIGN KEY(depend) REFERENCES exercices(id_exercice) -) DEFAULT CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci; +); `); err != nil { return err } @@ -182,220 +78,24 @@ CREATE TABLE IF NOT EXISTS exercices( CREATE TABLE IF NOT EXISTS exercice_files( id_file INTEGER NOT NULL PRIMARY KEY AUTO_INCREMENT, origin VARCHAR(255) NOT NULL, - path VARCHAR(191) NOT NULL UNIQUE, + path VARCHAR(255) NOT NULL UNIQUE, id_exercice INTEGER NOT NULL, name VARCHAR(255) NOT NULL, - cksum BINARY(64) NOT NULL, - cksum_shown BINARY(64), - size BIGINT UNSIGNED NOT NULL, - disclaimer VARCHAR(255) NOT NULL, - published BOOLEAN NOT NULL DEFAULT 1, + sha1 BINARY(20) NOT NULL, + size INTEGER NOT NULL, FOREIGN KEY(id_exercice) REFERENCES exercices(id_exercice) -) DEFAULT CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci; +); `); err != nil { return err } if _, err := db.Exec(` -CREATE TABLE IF NOT EXISTS exercice_hints( - id_hint INTEGER NOT NULL PRIMARY KEY AUTO_INCREMENT, +CREATE TABLE IF NOT EXISTS exercice_keys( + id_key INTEGER NOT NULL PRIMARY KEY AUTO_INCREMENT, id_exercice INTEGER NOT NULL, - title VARCHAR(255) NOT NULL, - content TEXT NOT NULL, - cost INTEGER NOT NULL, - FOREIGN KEY(id_exercice) REFERENCES exercices(id_exercice) -) DEFAULT CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci; -`); err != nil { - return err - } - if _, err := db.Exec(` -CREATE TABLE IF NOT EXISTS exercice_flags( - id_flag INTEGER NOT NULL PRIMARY KEY AUTO_INCREMENT, - id_exercice INTEGER NOT NULL, - ordre TINYINT NOT NULL, - label VARCHAR(255) NOT NULL, type VARCHAR(255) NOT NULL, - placeholder VARCHAR(255) NOT NULL, - help VARCHAR(255) NOT NULL, - unit VARCHAR(255) NOT NULL, - ignorecase BOOLEAN NOT NULL DEFAULT 0, - notrim BOOLEAN NOT NULL DEFAULT 0, - multiline BOOLEAN NOT NULL DEFAULT 0, - validator_regexp VARCHAR(255) NULL, - sort_re_grps BOOLEAN NOT NULL DEFAULT 0, - cksum BINARY(64) NOT NULL, - choices_cost MEDIUMINT NOT NULL, - bonus_gain MEDIUMINT NOT NULL, + value BINARY(64) NOT NULL, FOREIGN KEY(id_exercice) REFERENCES exercices(id_exercice) -) DEFAULT CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci; -`); err != nil { - return err - } - if _, err := db.Exec(` -CREATE TABLE IF NOT EXISTS exercice_flag_labels( - id_label INTEGER NOT NULL PRIMARY KEY AUTO_INCREMENT, - id_exercice INTEGER NOT NULL, - ordre TINYINT NOT NULL, - label TEXT NOT NULL, - variant VARCHAR(255) NOT NULL, - FOREIGN KEY(id_exercice) REFERENCES exercices(id_exercice) -) DEFAULT CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci; -`); err != nil { - return err - } - if _, err := db.Exec(` -CREATE TABLE IF NOT EXISTS exercice_hints_okey_deps( - id_hint INTEGER NOT NULL, - id_flag_dep INTEGER NOT NULL, - FOREIGN KEY(id_hint) REFERENCES exercice_hints(id_hint), - FOREIGN KEY(id_flag_dep) REFERENCES exercice_flags(id_flag) -) DEFAULT CHARACTER SET = utf8 COLLATE = utf8_bin; -`); err != nil { - return err - } - if _, err := db.Exec(` -CREATE TABLE IF NOT EXISTS exercice_flags_deps( - id_flag INTEGER NOT NULL, - id_flag_dep INTEGER NOT NULL, - FOREIGN KEY(id_flag) REFERENCES exercice_flags(id_flag), - FOREIGN KEY(id_flag_dep) REFERENCES exercice_flags(id_flag) -) DEFAULT CHARACTER SET = utf8 COLLATE = utf8_bin; -`); err != nil { - return err - } - if _, err := db.Exec(` -CREATE TABLE IF NOT EXISTS exercice_flag_labels_deps( - id_label INTEGER NOT NULL, - id_flag_dep INTEGER NOT NULL, - FOREIGN KEY(id_label) REFERENCES exercice_flag_labels(id_label), - FOREIGN KEY(id_flag_dep) REFERENCES exercice_flags(id_flag) -) DEFAULT CHARACTER SET = utf8 COLLATE = utf8_bin; -`); err != nil { - return err - } - if _, err := db.Exec(` -CREATE TABLE IF NOT EXISTS flag_choices( - id_choice INTEGER NOT NULL PRIMARY KEY AUTO_INCREMENT, - id_flag INTEGER NOT NULL, - label VARCHAR(255) NOT NULL, - response VARCHAR(255) NOT NULL, - FOREIGN KEY(id_flag) REFERENCES exercice_flags(id_flag) -) DEFAULT CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci; -`); err != nil { - return err - } - if _, err := db.Exec(` -CREATE TABLE IF NOT EXISTS exercice_files_okey_deps( - id_file INTEGER NOT NULL, - id_flag INTEGER NOT NULL, - FOREIGN KEY(id_file) REFERENCES exercice_files(id_file), - FOREIGN KEY(id_flag) REFERENCES exercice_flags(id_flag) -) DEFAULT CHARACTER SET = utf8 COLLATE = utf8_bin; -`); err != nil { - return err - } - if _, err := db.Exec(` -CREATE TABLE IF NOT EXISTS exercice_mcq( - id_mcq INTEGER NOT NULL PRIMARY KEY AUTO_INCREMENT, - id_exercice INTEGER NOT NULL, - ordre TINYINT NOT NULL, - title VARCHAR(255) NOT NULL, - FOREIGN KEY(id_exercice) REFERENCES exercices(id_exercice) -) DEFAULT CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci; -`); err != nil { - return err - } - if _, err := db.Exec(` -CREATE TABLE IF NOT EXISTS exercice_files_omcq_deps( - id_file INTEGER NOT NULL, - id_mcq INTEGER NOT NULL, - FOREIGN KEY(id_file) REFERENCES exercice_files(id_file), - FOREIGN KEY(id_mcq) REFERENCES exercice_mcq(id_mcq) -) DEFAULT CHARACTER SET = utf8 COLLATE = utf8_bin; -`); err != nil { - return err - } - if _, err := db.Exec(` -CREATE TABLE IF NOT EXISTS exercice_hints_omcq_deps( - id_hint INTEGER NOT NULL, - id_mcq_dep INTEGER NOT NULL, - FOREIGN KEY(id_hint) REFERENCES exercice_hints(id_hint), - FOREIGN KEY(id_mcq_dep) REFERENCES exercice_mcq(id_mcq) -) DEFAULT CHARACTER SET = utf8 COLLATE = utf8_bin; -`); err != nil { - return err - } - if _, err := db.Exec(` -CREATE TABLE IF NOT EXISTS exercice_mcq_okey_deps( - id_mcq INTEGER NOT NULL, - id_flag_dep INTEGER NOT NULL, - FOREIGN KEY(id_mcq) REFERENCES exercice_mcq(id_mcq), - FOREIGN KEY(id_flag_dep) REFERENCES exercice_flags(id_flag) -) DEFAULT CHARACTER SET = utf8 COLLATE = utf8_bin; -`); err != nil { - return err - } - if _, err := db.Exec(` -CREATE TABLE IF NOT EXISTS exercice_flags_omcq_deps( - id_flag INTEGER NOT NULL, - id_mcq_dep INTEGER NOT NULL, - FOREIGN KEY(id_flag) REFERENCES exercice_flags(id_flag), - FOREIGN KEY(id_mcq_dep) REFERENCES exercice_mcq(id_mcq) -) DEFAULT CHARACTER SET = utf8 COLLATE = utf8_bin; -`); err != nil { - return err - } - if _, err := db.Exec(` -CREATE TABLE IF NOT EXISTS exercice_flag_labels_omcq_deps( - id_label INTEGER NOT NULL, - id_mcq_dep INTEGER NOT NULL, - FOREIGN KEY(id_label) REFERENCES exercice_flag_labels(id_label), - FOREIGN KEY(id_mcq_dep) REFERENCES exercice_mcq(id_mcq) -) DEFAULT CHARACTER SET = utf8 COLLATE = utf8_bin; -`); err != nil { - return err - } - if _, err := db.Exec(` -CREATE TABLE IF NOT EXISTS exercice_mcq_omcq_deps( - id_mcq INTEGER NOT NULL, - id_mcq_dep INTEGER NOT NULL, - FOREIGN KEY(id_mcq) REFERENCES exercice_mcq(id_mcq), - FOREIGN KEY(id_mcq_dep) REFERENCES exercice_mcq(id_mcq) -) DEFAULT CHARACTER SET = utf8 COLLATE = utf8_bin; -`); err != nil { - return err - } - if _, err := db.Exec(` -CREATE TABLE IF NOT EXISTS mcq_entries( - id_mcq_entry INTEGER NOT NULL PRIMARY KEY AUTO_INCREMENT, - id_mcq INTEGER NOT NULL, - label VARCHAR(255) NOT NULL, - response BOOLEAN NOT NULL, - FOREIGN KEY(id_mcq) REFERENCES exercice_mcq(id_mcq) -) DEFAULT CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci; -`); err != nil { - return err - } - if _, err := db.Exec(` -CREATE TABLE IF NOT EXISTS mcq_found( - id_mcq INTEGER NOT NULL, - id_team INTEGER NOT NULL, - time TIMESTAMP NOT NULL, - CONSTRAINT uq_found UNIQUE (id_mcq,id_team), - FOREIGN KEY(id_mcq) REFERENCES exercice_mcq(id_mcq), - FOREIGN KEY(id_team) REFERENCES teams(id_team) -) DEFAULT CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci; -`); err != nil { - return err - } - if _, err := db.Exec(` -CREATE TABLE IF NOT EXISTS flag_found( - id_flag INTEGER NOT NULL, - id_team INTEGER NOT NULL, - time TIMESTAMP NOT NULL, - CONSTRAINT uc_found UNIQUE (id_flag,id_team), - FOREIGN KEY(id_flag) REFERENCES exercice_flags(id_flag), - FOREIGN KEY(id_team) REFERENCES teams(id_team) -) DEFAULT CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci; +); `); err != nil { return err } @@ -404,209 +104,34 @@ CREATE TABLE IF NOT EXISTS exercice_solved( id_exercice INTEGER NOT NULL, id_team INTEGER NOT NULL, time TIMESTAMP NOT NULL, - coefficient FLOAT NOT NULL DEFAULT 1.0, - CONSTRAINT uc_solved UNIQUE (id_exercice,id_team), FOREIGN KEY(id_exercice) REFERENCES exercices(id_exercice), FOREIGN KEY(id_team) REFERENCES teams(id_team) -) DEFAULT CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci; +); `); err != nil { return err } if _, err := db.Exec(` CREATE TABLE IF NOT EXISTS exercice_tries( - id_try INTEGER NOT NULL PRIMARY KEY AUTO_INCREMENT, id_exercice INTEGER NOT NULL, id_team INTEGER NOT NULL, time TIMESTAMP NOT NULL, - cksum BINARY(64) NOT NULL, - nbdiff INTEGER NOT NULL DEFAULT 0, - onegood BOOLEAN NOT NULL DEFAULT 0, FOREIGN KEY(id_exercice) REFERENCES exercices(id_exercice), FOREIGN KEY(id_team) REFERENCES teams(id_team) -) DEFAULT CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci; +); `); err != nil { return err } - if _, err := db.Exec(` -CREATE TABLE IF NOT EXISTS exercice_tries_flags( - id_try INTEGER NOT NULL, - id_flag INTEGER NOT NULL, - FOREIGN KEY(id_try) REFERENCES exercice_tries(id_try) ON DELETE CASCADE, - FOREIGN KEY(id_flag) REFERENCES exercice_flags(id_flag) ON DELETE CASCADE -) DEFAULT CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci; -`); err != nil { - return err - } - if _, err := db.Exec(` -CREATE TABLE IF NOT EXISTS exercice_tries_mcq( - id_try INTEGER NOT NULL, - id_mcq INTEGER NOT NULL, - FOREIGN KEY(id_try) REFERENCES exercice_tries(id_try) ON DELETE CASCADE, - FOREIGN KEY(id_mcq) REFERENCES exercice_mcq(id_mcq) ON DELETE CASCADE -) DEFAULT CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci; -`); err != nil { - return err - } - if _, err := db.Exec(` -CREATE TABLE IF NOT EXISTS exercice_tags( - id_exercice INTEGER NOT NULL, - tag VARCHAR(255) NOT NULL, - FOREIGN KEY(id_exercice) REFERENCES exercices(id_exercice) -) DEFAULT CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci; -`); err != nil { - return err - } - if _, err := db.Exec(` -CREATE TABLE IF NOT EXISTS team_hints( - id_team INTEGER, - id_hint INTEGER, - time TIMESTAMP NOT NULL, - coefficient FLOAT NOT NULL DEFAULT 1.0, - CONSTRAINT uc_displayed UNIQUE (id_team,id_hint), - FOREIGN KEY(id_hint) REFERENCES exercice_hints(id_hint), - FOREIGN KEY(id_team) REFERENCES teams(id_team) -) DEFAULT CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci; -`); err != nil { - return err - } - if _, err := db.Exec(` -CREATE TABLE IF NOT EXISTS team_wchoices( - id_team INTEGER, - id_flag INTEGER, - time TIMESTAMP NOT NULL, - coefficient FLOAT NOT NULL DEFAULT 1.0, - CONSTRAINT uc_displayed UNIQUE (id_team,id_flag), - FOREIGN KEY(id_flag) REFERENCES exercice_flags(id_flag), - FOREIGN KEY(id_team) REFERENCES teams(id_team) -) DEFAULT CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci; -`); err != nil { - return err - } - if _, err := db.Exec(` -CREATE TABLE IF NOT EXISTS claim_assignees( - id_assignee INTEGER NOT NULL PRIMARY KEY AUTO_INCREMENT, - name VARCHAR(191) NOT NULL UNIQUE -) DEFAULT CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci; -`); err != nil { - return err - } - db.Exec(`INSERT INTO claim_assignees VALUES (0, "$team");`) - db.Exec(`UPDATE claim_assignees SET id_assignee = 0 WHERE name = "$team";`) - if _, err := db.Exec(` -CREATE TABLE IF NOT EXISTS claims( - id_claim INTEGER NOT NULL PRIMARY KEY AUTO_INCREMENT, - subject VARCHAR(255) NOT NULL, - id_team INTEGER, - id_exercice INTEGER, - id_assignee INTEGER, - creation TIMESTAMP NOT NULL, - state ENUM('new', 'need-info', 'confirmed', 'in-progress', 'need-review', 'closed', 'invalid') NOT NULL, - priority ENUM('low', 'medium', 'high', 'critical') NOT NULL, - FOREIGN KEY(id_assignee) REFERENCES claim_assignees(id_assignee), - FOREIGN KEY(id_team) REFERENCES teams(id_team), - FOREIGN KEY(id_exercice) REFERENCES exercices(id_exercice) -) DEFAULT CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci; -`); err != nil { - return err - } - if _, err := db.Exec(` -CREATE TABLE IF NOT EXISTS claim_descriptions( - id_description INTEGER NOT NULL PRIMARY KEY AUTO_INCREMENT, - id_claim INTEGER NOT NULL, - id_assignee INTEGER, - date TIMESTAMP NOT NULL, - content TEXT NOT NULL, - publish BOOLEAN NOT NULL DEFAULT 0, - FOREIGN KEY(id_claim) REFERENCES claims(id_claim) -) DEFAULT CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci; -`); err != nil { - return err - } - if _, err := db.Exec(` -CREATE TABLE IF NOT EXISTS exercices_qa( - id_qa INTEGER NOT NULL PRIMARY KEY AUTO_INCREMENT, - id_exercice INTEGER NOT NULL, - id_team INTEGER NULL, - authuser VARCHAR(255) NOT NULL, - subject VARCHAR(255) NOT NULL, - creation TIMESTAMP NOT NULL, - state VARCHAR(255) NOT NULL, - solved TIMESTAMP NULL, - closed TIMESTAMP NULL, - exported INTEGER NULL, - FOREIGN KEY(id_exercice) REFERENCES exercices(id_exercice), - FOREIGN KEY(id_team) REFERENCES teams(id_team) -) DEFAULT CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci; -`); err != nil { - return err - } - if _, err := db.Exec(` -CREATE TABLE IF NOT EXISTS qa_comments( - id_comment INTEGER NOT NULL PRIMARY KEY AUTO_INCREMENT, - id_qa INTEGER NOT NULL, - id_team INTEGER NULL, - authuser VARCHAR(255) NOT NULL, - date TIMESTAMP NOT NULL, - content TEXT NOT NULL, - FOREIGN KEY(id_qa) REFERENCES exercices_qa(id_qa), - FOREIGN KEY(id_team) REFERENCES teams(id_team) -) DEFAULT CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci; -`); err != nil { - return err - } - if _, err := db.Exec(` -CREATE TABLE IF NOT EXISTS teams_qa_todo( - id_todo INTEGER NOT NULL PRIMARY KEY AUTO_INCREMENT, - id_team INTEGER NOT NULL, - id_exercice INTEGER NOT NULL, - FOREIGN KEY(id_exercice) REFERENCES exercices(id_exercice), - FOREIGN KEY(id_team) REFERENCES teams(id_team) -) DEFAULT CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci; -`); err != nil { - return err - } - if _, err := db.Exec(` -CREATE TABLE IF NOT EXISTS teams_qa_view( - id_view INTEGER NOT NULL PRIMARY KEY AUTO_INCREMENT, - id_team INTEGER NOT NULL, - id_exercice INTEGER NOT NULL, - FOREIGN KEY(id_exercice) REFERENCES exercices(id_exercice), - FOREIGN KEY(id_team) REFERENCES teams(id_team) -) DEFAULT CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci; -`); err != nil { - return err - } - - if _, err := db.Exec("CREATE OR REPLACE VIEW exercice_distinct_tries AS SELECT id_exercice, id_team, MAX(time) AS time, cksum, nbdiff, MAX(onegood) AS onegood FROM exercice_tries GROUP BY id_team, id_exercice, cksum;"); err != nil { - return err - } - if _, err := db.Exec("CREATE OR REPLACE VIEW exercice_tries_notgood AS SELECT id_exercice, id_team, time, cksum, nbdiff, onegood FROM exercice_tries WHERE onegood = 0;"); err != nil { - return err - } - if _, err := db.Exec("CREATE OR REPLACE VIEW exercice_distinct_tries_notgood AS SELECT id_exercice, id_team, MAX(time) AS time, cksum, nbdiff, MAX(onegood) AS onegood FROM exercice_tries_notgood GROUP BY id_team, id_exercice, cksum;"); err != nil { - return err - } - if err := DBRecreateDiscountedView(); err != nil { - return err - } - return nil } -func DBRecreateDiscountedView() (err error) { - if db == nil { - return - } - - _, err = db.Exec("CREATE OR REPLACE VIEW exercices_discounted AS SELECT E.id_exercice, E.id_theme, E.title, E.authors, E.image, E.background_color, E.disabled, E.headline, E.url_id, E.path, E.statement, E.overview, E.issue, E.issue_kind, E.depend, E.gain - " + fmt.Sprintf("%f", DiscountedFactor) + " * E.gain * (COUNT(*) - 1) AS gain, E.coefficient_cur, E.finished, E.video_uri, E.resolution, E.seealso FROM exercices E LEFT OUTER JOIN exercice_solved S ON S.id_exercice = E.id_exercice GROUP BY E.id_exercice;") - return -} - -// DBClose closes the connection to the database func DBClose() error { return db.Close() } +func DBPrepare(query string) (*sql.Stmt, error) { + return db.Prepare(query) +} + func DBQuery(query string, args ...interface{}) (*sql.Rows, error) { return db.Query(query, args...) } diff --git a/libfic/doc.go b/libfic/doc.go deleted file mode 100644 index 050659c0..00000000 --- a/libfic/doc.go +++ /dev/null @@ -1,3 +0,0 @@ -// Package fic provides access to common structures shared between multiple -// services required to run the fic-server project. -package fic diff --git a/libfic/event.go b/libfic/event.go index 5e88b60e..7a91c0a1 100644 --- a/libfic/event.go +++ b/libfic/event.go @@ -4,7 +4,6 @@ import ( "time" ) -// Event represents a challenge event. type Event struct { Id int64 `json:"id"` Kind string `json:"kind"` @@ -12,9 +11,8 @@ type Event struct { Time time.Time `json:"time"` } -// GetLastEvents returns the list of the last 10 events, sorted by date, last first -func GetLastEvents() ([]Event, error) { - if rows, err := DBQuery("SELECT id_event, txt, kind, time FROM events ORDER BY time DESC LIMIT 10"); err != nil { +func GetEvents() ([]Event, error) { + if rows, err := DBQuery("SELECT id_event, txt, kind, time FROM events ORDER BY time DESC LIMIT 6"); err != nil { return nil, err } else { defer rows.Close() @@ -35,49 +33,17 @@ func GetLastEvents() ([]Event, error) { } } -// GetEvents returns the list of all events, sorted by date, last first -func GetEvents() ([]*Event, error) { - if rows, err := DBQuery("SELECT id_event, txt, kind, time FROM events ORDER BY time DESC"); err != nil { - return nil, err - } else { - defer rows.Close() - - var events []*Event - for rows.Next() { - e := &Event{} - if err := rows.Scan(&e.Id, &e.Text, &e.Kind, &e.Time); err != nil { - return nil, err - } - events = append(events, e) - } - if err := rows.Err(); err != nil { - return nil, err - } - - return events, nil - } -} - -// GetEvent retrieves the event with the given id -func GetEvent(id int64) (e *Event, err error) { - e = &Event{} - err = DBQueryRow("SELECT id_event, txt, kind, time FROM events WHERE id_event=?", id).Scan(&e.Id, &e.Text, &e.Kind, &e.Time) - return -} - -// NewEvent creates a new event in the database and returns the corresponding structure -func NewEvent(txt string, kind string) (*Event, error) { +func NewEvent(txt string, kind string) (Event, error) { if res, err := DBExec("INSERT INTO events (txt, kind, time) VALUES (?, ?, ?)", txt, kind, time.Now()); err != nil { - return nil, err + return Event{}, err } else if eid, err := res.LastInsertId(); err != nil { - return nil, err + return Event{}, err } else { - return &Event{eid, txt, kind, time.Now()}, nil + return Event{eid, txt, kind, time.Now()}, nil } } -// Update applies modifications back to the database -func (e *Event) Update() (int64, error) { +func (e Event) Update() (int64, error) { if res, err := DBExec("UPDATE events SET txt = ?, kind = ?, time = ? WHERE id_event = ?", e.Text, e.Kind, e.Time, e.Id); err != nil { return 0, err } else if nb, err := res.RowsAffected(); err != nil { @@ -87,8 +53,7 @@ func (e *Event) Update() (int64, error) { } } -// Delete the event from the database -func (e *Event) Delete() (int64, error) { +func (e Event) Delete() (int64, error) { if res, err := DBExec("DELETE FROM events WHERE id_event = ?", e.Id); err != nil { return 0, err } else if nb, err := res.RowsAffected(); err != nil { @@ -97,14 +62,3 @@ func (e *Event) Delete() (int64, error) { return nb, err } } - -// ClearEvents removes all events from database -func ClearEvents() (int64, error) { - if res, err := DBExec("DELETE FROM events"); err != nil { - return 0, err - } else if nb, err := res.RowsAffected(); err != nil { - return 0, err - } else { - return nb, err - } -} diff --git a/libfic/exercice.go b/libfic/exercice.go index 3401f0ae..a977d877 100644 --- a/libfic/exercice.go +++ b/libfic/exercice.go @@ -2,162 +2,49 @@ package fic import ( "errors" - "fmt" - "log" - "math" "time" ) -// PartialValidation allows a Team to validate an Exercice at a given point if -// it has ever found all classical flags, even if the last validation is not -// complete or contains mistakes on previously validated flags. -var PartialValidation bool - -// PartialMCQValidation allows a Team to validate each MCQ independently. -// Otherwise, all MCQ has to be correct for being validated. -var PartialMCQValidation bool - -// ExerciceCurrentCoefficient is the current coefficient applied on solved exercices -var ExerciceCurrentCoefficient = 1.0 - -// Exercice represents a challenge inside a Theme. type Exercice struct { - Id int64 `json:"id"` - IdTheme *int64 `json:"id_theme"` - Language string `json:"lang,omitempty"` - Title string `json:"title"` - Authors string `json:"authors"` - Image string `json:"image"` - BackgroundColor uint32 `json:"background_color,omitempty"` - // Disabled indicates if the exercice is available to players now or not - Disabled bool `json:"disabled"` - // WIP indicates if the exercice is in development or not - WIP bool `json:"wip"` - // URLid is used to reference the challenge from the URL path - URLId string `json:"urlid"` - // Path is the relative import location where find challenge data - Path string `json:"path"` - // Statement is the challenge description shown to players + Id int64 `json:"id"` + Title string `json:"title"` Statement string `json:"statement"` - // Overview is the challenge description shown to public - Overview string `json:"overview"` - // Headline is the challenge headline to fill in small part - Headline string `json:"headline"` - // Finished is the text shown when the exercice is solved - Finished string `json:"finished"` - // Issue is an optional text describing an issue with the exercice - Issue string `json:"issue"` - // IssueKind is the criticity level of the previous issue - IssueKind string `json:"issuekind"` + Hint string `json:"hint"` Depend *int64 `json:"depend"` - // Gain is the basis amount of points player will earn if it solve the challenge - // If this value fluctuate during the challenge, players' scores will follow changes. - // Use Coefficient value to create temporary bonus/malus. - Gain int64 `json:"gain"` - // Coefficient is a temporary bonus/malus applied on Gain when the challenge is solved. - // This value is saved along with solved informations: changing it doesn't affect Team that already solved the challenge. - Coefficient float64 `json:"coefficient"` - // VideoURI is the link to the resolution video - VideoURI string `json:"videoURI"` - // Resolution is a write-up (in HTML) - Resolution string `json:"resolution"` - // SeeAlso is a collection of links (HTML formated) - SeeAlso string `json:"seealso"` + Gain int64 `json:"gain"` + VideoURI string `json:"videoURI"` } -func (e *Exercice) AnalyzeTitle() { - if len(e.Title) > 0 && e.Title[0] == '%' { - e.WIP = true - e.Title = e.Title[1:] - } else { - e.WIP = false - } -} - -func getExercice(table, condition string, args ...interface{}) (*Exercice, error) { +func GetExercice(id int64) (Exercice, error) { var e Exercice - var tmpgain float64 - if err := DBQueryRow("SELECT id_exercice, id_theme, title, authors, image, background_color, disabled, url_id, path, statement, overview, headline, issue, issue_kind, depend, gain, coefficient_cur, video_uri, resolution, seealso, finished FROM "+table+" "+condition, args...).Scan(&e.Id, &e.IdTheme, &e.Title, &e.Authors, &e.Image, &e.BackgroundColor, &e.Disabled, &e.URLId, &e.Path, &e.Statement, &e.Overview, &e.Headline, &e.Issue, &e.IssueKind, &e.Depend, &tmpgain, &e.Coefficient, &e.VideoURI, &e.Resolution, &e.SeeAlso, &e.Finished); err != nil { - return nil, err - } - e.Gain = int64(math.Trunc(tmpgain)) - e.AnalyzeTitle() - - return &e, nil -} - -// GetExercice retrieves the challenge with the given id. -func GetExercice(id int64) (*Exercice, error) { - return getExercice("exercices", "WHERE id_exercice = ?", id) -} - -// GetExercice retrieves the challenge with the given id. -func (t *Theme) GetExercice(id int) (*Exercice, error) { - query := "WHERE id_exercice = ? AND id_theme = ?" - args := []interface{}{id} - - if t.GetId() == nil { - query = "WHERE id_exercice = ? AND id_theme IS NULL" - } else { - args = append(args, t.GetId()) + if err := DBQueryRow("SELECT id_exercice, title, statement, hint, depend, gain, video_uri FROM exercices WHERE id_exercice = ?", id).Scan(&e.Id, &e.Title, &e.Statement, &e.Hint, &e.Depend, &e.Gain, &e.VideoURI); err != nil { + return Exercice{}, err } - return getExercice("exercices", query, args...) + return e, nil } -// GetExerciceByTitle retrieves the challenge with the given title. -func (t *Theme) GetExerciceByTitle(title string) (*Exercice, error) { - query := "WHERE title = ? AND id_theme = ?" - args := []interface{}{title} - - if t.GetId() == nil { - query = "WHERE title = ? AND id_theme IS NULL" - } else { - args = append(args, t.GetId()) +func (t Theme) GetExercice(id int) (Exercice, error) { + var e Exercice + if err := DBQueryRow("SELECT id_exercice, title, statement, hint, depend, gain, video_uri FROM exercices WHERE id_theme = ? AND id_exercice = ?", t.Id, id).Scan(&e.Id, &e.Title, &e.Statement, &e.Hint, &e.Depend, &e.Gain, &e.VideoURI); err != nil { + return Exercice{}, err } - return getExercice("exercices", query, args...) + return e, nil } -// GetExerciceByPath retrieves the challenge with the given path. -func (t *Theme) GetExerciceByPath(epath string) (*Exercice, error) { - query := "WHERE path = ? AND id_theme = ?" - args := []interface{}{epath} - - if t.GetId() == nil { - query = "WHERE path = ? AND id_theme IS NULL" - } else { - args = append(args, t.GetId()) - } - - return getExercice("exercices", query, args...) -} - -// GetDiscountedExercice retrieves the challenge with the given id. -func GetDiscountedExercice(id int64) (*Exercice, error) { - table := "exercices" - if DiscountedFactor > 0 { - table = "exercices_discounted" - } - return getExercice(table, "WHERE id_exercice = ?", id) -} - -// getExercices returns the list of all challenges present in the database. -func getExercices(table string) ([]*Exercice, error) { - if rows, err := DBQuery("SELECT id_exercice, id_theme, title, authors, image, background_color, disabled, url_id, path, statement, overview, headline, issue, issue_kind, depend, gain, coefficient_cur, video_uri, resolution, seealso, finished FROM " + table + " ORDER BY path ASC"); err != nil { +func GetExercices() ([]Exercice, error) { + if rows, err := DBQuery("SELECT id_exercice, title, statement, hint, depend, gain, video_uri FROM exercices"); err != nil { return nil, err } else { defer rows.Close() - exos := []*Exercice{} + var exos = make([]Exercice, 0) for rows.Next() { - e := &Exercice{} - var tmpgain float64 - if err := rows.Scan(&e.Id, &e.IdTheme, &e.Title, &e.Authors, &e.Image, &e.BackgroundColor, &e.Disabled, &e.URLId, &e.Path, &e.Statement, &e.Overview, &e.Headline, &e.Issue, &e.IssueKind, &e.Depend, &tmpgain, &e.Coefficient, &e.VideoURI, &e.Resolution, &e.SeeAlso, &e.Finished); err != nil { + var e Exercice + if err := rows.Scan(&e.Id, &e.Title, &e.Statement, &e.Hint, &e.Depend, &e.Gain, &e.VideoURI); err != nil { return nil, err } - e.Gain = int64(math.Trunc(tmpgain)) - e.AnalyzeTitle() exos = append(exos, e) } if err := rows.Err(); err != nil { @@ -168,40 +55,18 @@ func getExercices(table string) ([]*Exercice, error) { } } -func GetExercices() ([]*Exercice, error) { - return getExercices("exercices") -} - -func GetDiscountedExercices() ([]*Exercice, error) { - table := "exercices" - if DiscountedFactor > 0 { - table = "exercices_discounted" - } - return getExercices(table) -} - -// GetExercices returns the list of all challenges in the Theme. -func (t *Theme) GetExercices() ([]*Exercice, error) { - query := "SELECT id_exercice, id_theme, title, authors, image, background_color, disabled, url_id, path, statement, overview, headline, issue, issue_kind, depend, gain, coefficient_cur, video_uri, resolution, seealso, finished FROM exercices WHERE id_theme IS NULL ORDER BY path ASC" - args := []interface{}{} - - if t.GetId() != nil { - query = "SELECT id_exercice, id_theme, title, authors, image, background_color, disabled, url_id, path, statement, overview, headline, issue, issue_kind, depend, gain, coefficient_cur, video_uri, resolution, seealso, finished FROM exercices WHERE id_theme = ? ORDER BY path ASC" - args = append(args, t.GetId()) - } - - if rows, err := DBQuery(query, args...); err != nil { +func (t Theme) GetExercices() ([]Exercice, error) { + if rows, err := DBQuery("SELECT id_exercice, title, statement, hint, depend, gain, video_uri FROM exercices WHERE id_theme = ?", t.Id); err != nil { return nil, err } else { defer rows.Close() - exos := []*Exercice{} + var exos = make([]Exercice, 0) for rows.Next() { - e := &Exercice{} - if err := rows.Scan(&e.Id, &e.IdTheme, &e.Title, &e.Authors, &e.Image, &e.BackgroundColor, &e.Disabled, &e.URLId, &e.Path, &e.Statement, &e.Overview, &e.Headline, &e.Issue, &e.IssueKind, &e.Depend, &e.Gain, &e.Coefficient, &e.VideoURI, &e.Resolution, &e.SeeAlso, &e.Finished); err != nil { + var e Exercice + if err := rows.Scan(&e.Id, &e.Title, &e.Statement, &e.Hint, &e.Depend, &e.Gain, &e.VideoURI); err != nil { return nil, err } - e.AnalyzeTitle() exos = append(exos, e) } if err := rows.Err(); err != nil { @@ -212,106 +77,28 @@ func (t *Theme) GetExercices() ([]*Exercice, error) { } } -// SaveNamedExercice looks for an exercice with the same title to update it, or create it if it doesn't exists yet. -func (t *Theme) SaveNamedExercice(e *Exercice) (err error) { - var search *Exercice - - // Search same title - search, _ = t.GetExerciceByTitle(e.Title) - - // Search on same path - if search == nil && len(e.Path) > 0 { - search, err = t.GetExerciceByPath(e.Path) - } - - if search == nil { - err = t.addExercice(e) +func (t Theme) AddExercice(title string, statement string, hint string, depend *Exercice, gain int, videoURI string) (Exercice, error) { + var dpd interface{} + if depend == nil { + dpd = nil } else { - // Force ID - e.Id = search.Id - - // Don't expect those values - if e.Coefficient == 0 { - e.Coefficient = search.Coefficient - } - if len(e.Issue) == 0 { - e.Issue = search.Issue - } - if len(e.IssueKind) == 0 { - e.IssueKind = search.IssueKind - } - - _, err = e.Update() + dpd = depend.Id } - return -} - -func (t *Theme) addExercice(e *Exercice) (err error) { - var ik = "DEFAULT" - if len(e.IssueKind) > 0 { - ik = fmt.Sprintf("%q", e.IssueKind) - } - - var cc = "DEFAULT" - if e.Coefficient != 0 { - cc = fmt.Sprintf("%f", e.Coefficient) - } - - wip := "" - if e.WIP { - wip = "%" - } - if res, err := DBExec("INSERT INTO exercices (id_theme, title, authors, image, background_color, disabled, url_id, path, statement, overview, finished, headline, issue, depend, gain, video_uri, resolution, seealso, issue_kind, coefficient_cur) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, "+ik+", "+cc+")", t.GetId(), wip+e.Title, e.Authors, e.Image, e.BackgroundColor, e.Disabled, e.URLId, e.Path, e.Statement, e.Overview, e.Finished, e.Headline, e.Issue, e.Depend, e.Gain, e.VideoURI, e.Resolution, e.SeeAlso); err != nil { - return err + if res, err := DBExec("INSERT INTO exercices (id_theme, title, statement, hint, depend, gain, video_uri) VALUES (?, ?, ?, ?, ?, ?, ?)", t.Id, title, statement, hint, dpd, gain, videoURI); err != nil { + return Exercice{}, err } else if eid, err := res.LastInsertId(); err != nil { - return err + return Exercice{}, err } else { - e.Id = eid - - return nil + if depend == nil { + return Exercice{eid, title, statement, hint, nil, int64(gain), videoURI}, nil + } else { + return Exercice{eid, title, statement, hint, &depend.Id, int64(gain), videoURI}, nil + } } } -// AddExercice creates and fills a new struct Exercice and registers it into the database. -func (t *Theme) AddExercice(title string, authors string, image string, backgroundcolor uint32, wip bool, urlId string, path string, statement string, overview string, headline string, depend *Exercice, gain int64, videoURI string, resolution string, seealso string, finished string) (e *Exercice, err error) { - var dpd *int64 = nil - if depend != nil { - dpd = &depend.Id - } - - e = &Exercice{ - Title: title, - Authors: authors, - Image: image, - BackgroundColor: backgroundcolor, - Disabled: false, - WIP: wip, - URLId: urlId, - Path: path, - Statement: statement, - Overview: overview, - Headline: headline, - Depend: dpd, - Finished: finished, - Gain: gain, - VideoURI: videoURI, - Resolution: resolution, - SeeAlso: seealso, - } - - err = t.addExercice(e) - - return -} - -// Update applies modifications back to the database. -func (e *Exercice) Update() (int64, error) { - wip := "" - if e.WIP { - wip = "%" - } - - if res, err := DBExec("UPDATE exercices SET title = ?, authors = ?, image = ?, background_color = ?, disabled = ?, url_id = ?, path = ?, statement = ?, overview = ?, headline = ?, issue = ?, issue_kind = ?, depend = ?, gain = ?, coefficient_cur = ?, video_uri = ?, resolution = ?, seealso = ?, finished = ? WHERE id_exercice = ?", wip+e.Title, e.Authors, e.Image, e.BackgroundColor, e.Disabled, e.URLId, e.Path, e.Statement, e.Overview, e.Headline, e.Issue, e.IssueKind, e.Depend, e.Gain, e.Coefficient, e.VideoURI, e.Resolution, e.SeeAlso, e.Finished, e.Id); err != nil { +func (e Exercice) Update() (int64, error) { + if res, err := DBExec("UPDATE exercices SET title = ?, statement = ?, hint = ?, depend = ?, gain = ?, video_uri = ? WHERE id_exercice = ?", e.Title, e.Statement, e.Hint, e.Depend, e.Gain, e.VideoURI, e.Id); err != nil { return 0, err } else if nb, err := res.RowsAffected(); err != nil { return 0, err @@ -320,18 +107,23 @@ func (e *Exercice) Update() (int64, error) { } } -// FixURLId generates a valid URLid from the challenge Title. -// It returns true if something has been generated. -func (e *Exercice) FixURLId() bool { - if e.URLId == "" { - e.URLId = ToURLid(e.Title) - return true +func (e Exercice) GetThemeId() (int, error) { + var tid int + if err := DBQueryRow("SELECT id_theme FROM exercices WHERE id_exercice=?", e.Id).Scan(&tid); err != nil { + return 0, err } - return false + return tid, nil } -// Delete the challenge from the database. -func (e *Exercice) Delete() (int64, error) { +func (e Exercice) GetTheme() (Theme, error) { + if tid, err := e.GetThemeId(); err != nil { + return Theme{}, err + } else { + return GetTheme(tid) + } +} + +func (e Exercice) Delete() (int64, error) { if res, err := DBExec("DELETE FROM exercices WHERE id_exercice = ?", e.Id); err != nil { return 0, err } else if nb, err := res.RowsAffected(); err != nil { @@ -341,80 +133,12 @@ func (e *Exercice) Delete() (int64, error) { } } -// DeleteCascade the challenge from the database, including inner content but not player content. -func (e *Exercice) DeleteCascade() (int64, error) { - if _, err := DBExec("UPDATE exercices SET depend = NULL WHERE depend = ?", e.Id); err != nil { - return 0, err - } else if _, err := DBExec("DELETE FROM exercice_files_okey_deps WHERE id_file IN (SELECT id_file FROM exercice_files WHERE id_exercice = ?)", e.Id); err != nil { - return 0, err - } else if _, err := DBExec("DELETE FROM exercice_files_omcq_deps WHERE id_file IN (SELECT id_file FROM exercice_files WHERE id_exercice = ?)", e.Id); err != nil { - return 0, err - } else if _, err := DBExec("DELETE FROM exercice_files WHERE id_exercice = ?", e.Id); err != nil { - return 0, err - } else if _, err := DBExec("DELETE FROM exercice_hints_okey_deps WHERE id_hint IN (SELECT id_hint FROM exercice_hints WHERE id_exercice = ?)", e.Id); err != nil { - return 0, err - } else if _, err := DBExec("DELETE FROM exercice_hints_omcq_deps WHERE id_hint IN (SELECT id_hint FROM exercice_hints WHERE id_exercice = ?)", e.Id); err != nil { - return 0, err - } else if _, err := DBExec("DELETE FROM exercice_hints WHERE id_exercice = ?", e.Id); err != nil { - return 0, err - } else if _, err := DBExec("DELETE FROM exercice_flags_deps WHERE id_flag IN (SELECT id_flag FROM exercice_flags WHERE id_exercice = ?)", e.Id); err != nil { - return 0, err - } else if _, err := DBExec("DELETE FROM flag_choices WHERE id_flag IN (SELECT id_flag FROM exercice_flags WHERE id_exercice = ?)", e.Id); err != nil { - return 0, err - } else if _, err := DBExec("DELETE FROM exercice_flags WHERE id_exercice = ?", e.Id); err != nil { - return 0, err - } else if _, err := DBExec("DELETE FROM mcq_entries WHERE id_mcq IN (SELECT id_mcq FROM exercice_flags WHERE id_exercice = ?)", e.Id); err != nil { - return 0, err - } else if _, err := DBExec("DELETE FROM exercice_mcq WHERE id_exercice = ?", e.Id); err != nil { - return 0, err - } else if _, err := DBExec("DELETE FROM exercice_tags WHERE id_exercice = ?", e.Id); err != nil { - return 0, err - } else if _, err := DBExec("DELETE FROM teams_qa_todo WHERE id_exercice = ?", e.Id); err != nil { - return 0, err - } else if _, err := DBExec("DELETE FROM teams_qa_view WHERE id_exercice = ?", e.Id); err != nil { - return 0, err - } else { - return e.Delete() - } -} - -// DeleteCascadePlayer delete player content related to this challenge. -func (e *Exercice) DeleteCascadePlayer() (int64, error) { - if _, err := DBExec("DELETE FROM mcq_found WHERE id_mcq IN (SELECT id_mcq FROM exercice_mcq WHERE id_exercice = ?)", e.Id); err != nil { - return 0, err - } else if _, err := DBExec("DELETE FROM flag_found WHERE id_flag IN (SELECT id_flag FROM exercice_flags WHERE id_exercice = ?)", e.Id); err != nil { - return 0, err - } else if _, err := DBExec("DELETE FROM team_hints WHERE id_hint IN (SELECT id_hint FROM exercice_hints WHERE id_exercice = ?)", e.Id); err != nil { - return 0, err - } else if _, err := DBExec("DELETE FROM team_wchoices WHERE id_flag IN (SELECT id_flag FROM exercice_flags WHERE id_exercice = ?)", e.Id); err != nil { - return 0, err - } else if _, err := DBExec("DELETE FROM exercice_solved WHERE id_exercice = ?", e.Id); err != nil { - return 0, err - } else if _, err := DBExec("DELETE FROM exercice_tries WHERE id_exercice = ?", e.Id); err != nil { - return 0, err - } else { - return 1, nil - } -} - -// DeleteDeep the challenge from the database, including player content. -func (e *Exercice) DeleteDeep() (int64, error) { - if _, err := e.DeleteCascadePlayer(); err != nil { - return 0, err - } else { - return e.DeleteCascade() - } -} - -// GetLevel returns the number of dependancy challenges. -func (e *Exercice) GetLevel() (int, error) { +func (e Exercice) GetLevel() (int, error) { dep := e.Depend nb := 1 for dep != nil { nb += 1 - if nb > 10 || *dep == e.Id { - return nb, errors.New("exceed number of levels") - } else if edep, err := GetExercice(*dep); err != nil { + if edep, err := GetExercice(*dep); err != nil { return nb, err } else { dep = edep.Depend @@ -423,71 +147,23 @@ func (e *Exercice) GetLevel() (int, error) { return nb, nil } -// GetOrdinal returns the position of the exercice in list (usefull for standalone exercices). -func (e *Exercice) GetOrdinal() (int, error) { - theme := &Theme{Id: 0} - if e.IdTheme != nil { - theme.Id = *e.IdTheme - } - - exercices, err := theme.GetExercices() - if err != nil { - return 0, err - } - - for n, exercice := range exercices { - if exercice.Id == e.Id { - return n, nil - } - } - - return 0, fmt.Errorf("exercice not found in theme") -} - -// NewTry registers a solving attempt for the given Team. -func (e *Exercice) NewTry(t *Team, cksum []byte, flags ...Flag) (int64, error) { - if res, err := DBExec("INSERT INTO exercice_tries (id_exercice, id_team, time, cksum) VALUES (?, ?, ?, ?)", e.Id, t.Id, time.Now(), cksum); err != nil { - return 0, err - } else { - return res.LastInsertId() - } -} - -func (e *Exercice) NewTryFlag(tryid int64, flags ...Flag) { - for _, flag := range flags { - if fk, ok := flag.(*FlagKey); ok { - if _, err := DBExec("INSERT INTO exercice_tries_flags (id_try, id_flag) VALUES (?, ?)", tryid, fk.Id); err != nil { - log.Println("Unable to add detailed try: ", err.Error()) - } - } else if fm, ok := flag.(*MCQ); ok { - if _, err := DBExec("INSERT INTO exercice_tries_mcq (id_try, id_mcq) VALUES (?, ?)", tryid, fm.Id); err != nil { - log.Println("Unable to add detailed try: ", err.Error()) - } - } - } -} - -// UpdateTry applies modifications to the latest try registered for the given Team. -// Updated values are time and the given nbdiff. -func (e *Exercice) UpdateTry(t *Team, nbdiff int, oneGood bool) error { - if _, err := DBExec("UPDATE exercice_tries SET nbdiff = ?, onegood = ?, time = ? WHERE id_exercice = ? AND id_team = ? ORDER BY time DESC LIMIT 1", nbdiff, oneGood, time.Now(), e.Id, t.Id); err != nil { +func (e Exercice) NewTry(t Team) error { + if _, err := DBExec("INSERT INTO exercice_tries (id_exercice, id_team, time) VALUES (?, ?, ?)", e.Id, t.Id, time.Now()); err != nil { return err } else { return nil } } -// Solved registers that the given Team solves the challenge. -func (e *Exercice) Solved(t *Team) error { - if _, err := DBExec("INSERT INTO exercice_solved (id_exercice, id_team, time, coefficient) VALUES (?, ?, ?, ?)", e.Id, t.Id, time.Now(), e.Coefficient*ExerciceCurrentCoefficient); err != nil { +func (e Exercice) Solved(t Team) error { + if _, err := DBExec("INSERT INTO exercice_solved (id_exercice, id_team, time) VALUES (?, ?, ?)", e.Id, t.Id, time.Now()); err != nil { return err } else { return nil } } -// SolvedCount returns the number of Team that already have solved the challenge. -func (e *Exercice) SolvedCount() int64 { +func (e Exercice) SolvedCount() int64 { var nb int64 if err := DBQueryRow("SELECT COUNT(id_exercice) FROM exercice_solved WHERE id_exercice = ?", e.Id).Scan(&nb); err != nil { return 0 @@ -496,163 +172,46 @@ func (e *Exercice) SolvedCount() int64 { } } -// TriedTeamCount returns the number of Team that attempted to solve the exercice. -func (e *Exercice) TriedTeamCount() int64 { - tries_table := "exercice_tries" - if SubmissionUniqueness { - tries_table = "exercice_distinct_tries" - } - +func (e Exercice) TriedTeamCount() int64 { var nb int64 - if err := DBQueryRow("SELECT COUNT(DISTINCT id_team) FROM "+tries_table+" WHERE id_exercice = ?", e.Id).Scan(&nb); err != nil { + if err := DBQueryRow("SELECT COUNT(DISTINCT id_team) FROM exercice_tries WHERE id_exercice = ?", e.Id).Scan(&nb); err != nil { return 0 } else { return nb } } -// TriedCount returns the number of cumulative attempts, all Team combined, for the exercice. -func (e *Exercice) TriedCount() int64 { - tries_table := "exercice_tries" - if SubmissionUniqueness { - tries_table = "exercice_distinct_tries" - } - +func (e Exercice) TriedCount() int64 { var nb int64 - if err := DBQueryRow("SELECT COUNT(id_team) FROM "+tries_table+" WHERE id_exercice = ?", e.Id).Scan(&nb); err != nil { + if err := DBQueryRow("SELECT COUNT(id_team) FROM exercice_tries WHERE id_exercice = ?", e.Id).Scan(&nb); err != nil { return 0 } else { return nb } } -// FlagSolved returns the list of flags solved. -func (e *Exercice) FlagSolved() (res []int64) { - if rows, err := DBQuery("SELECT F.id_flag FROM flag_found F INNER JOIN exercice_flags E ON E.id_flag = F.id_flag WHERE E.id_exercice = ? GROUP BY id_flag", e.Id); err != nil { - return +func (e Exercice) CheckResponse(resps map[string]string, t Team) (bool, error) { + if err := e.NewTry(t); err != nil { + return false, err + } else if keys, err := e.GetKeys(); err != nil { + return false, err } else { - defer rows.Close() - - for rows.Next() { - var n int64 - if err := rows.Scan(&n); err != nil { - return - } - res = append(res, n) + if len(keys) < 1 { + return true, errors.New("Exercice with no key registered") } - return - } -} -// MCQSolved returns the list of mcqs solved. -func (e *Exercice) MCQSolved() (res []int64) { - if rows, err := DBQuery("SELECT F.id_mcq FROM mcq_found F INNER JOIN exercice_mcq E ON E.id_mcq = F.id_mcq WHERE E.id_exercice = ? GROUP BY id_mcq", e.Id); err != nil { - return - } else { - defer rows.Close() - - for rows.Next() { - var n int64 - if err := rows.Scan(&n); err != nil { - return - } - res = append(res, n) - } - return - } -} - -// CheckResponse, given both flags and MCQ responses, figures out if thoses are correct (or if they are previously solved). -// In the meanwhile, CheckResponse registers good answers given (but it does not mark the challenge as solved at the end). -func (e *Exercice) CheckResponse(cksum []byte, respflags map[int]string, respmcq map[int]bool, t *Team) (bool, error) { - if tryId, err := e.NewTry(t, cksum); err != nil { - return false, err - } else if flags, err := e.GetFlagKeys(); err != nil { - return false, err - } else if mcqs, err := e.GetMCQ(); err != nil { - return false, err - } else if len(flags) < 1 && len(mcqs) < 1 { - return true, errors.New("Exercice with no flag registered") - } else { valid := true - diff := 0 - goodResponses := 0 - - // Check MCQs - for _, mcq := range mcqs { - if mcq.HasOneEntry(respmcq) { - e.NewTryFlag(tryId, mcq) + for _, key := range keys { + if _, ok := resps[key.Type]; !ok { + valid = false + break } - - if d := mcq.Check(respmcq); d > 0 { - if !PartialValidation || t.HasPartiallySolved(mcq) == nil { - valid = false - diff += d - } - } else if !PartialMCQValidation && d == 0 { - mcq.FoundBy(t) - goodResponses += 1 + if !key.Check(resps[key.Type]) { + valid = false + break } } - // Validate MCQs if no error - if valid { - for _, mcq := range mcqs { - if mcq.FoundBy(t) == nil { - goodResponses += 1 - } - } - } - - // Check flags - for _, flag := range flags { - if res, ok := respflags[flag.Id]; !ok && (!PartialValidation || t.HasPartiallySolved(flag) == nil) { - valid = valid && flag.IsOptionnal() - } else if ok { - if len(res) > 0 { - e.NewTryFlag(tryId, flag) - } - - if flag.Check([]byte(res)) != 0 { - if !PartialValidation || t.HasPartiallySolved(flag) == nil { - valid = valid && flag.IsOptionnal() - } - } else { - err := flag.FoundBy(t) - if err == nil { - // err is unicity issue, probably flag already found - goodResponses += 1 - } - } - } - } - - if diff > 0 || goodResponses > 0 { - e.UpdateTry(t, diff, goodResponses > 0) - } - return valid, nil } } - -// IsSolved returns the number of time this challenge has been solved and the time of the first solve occurence. -func (e *Exercice) IsSolved() (int, *time.Time) { - var nb *int - var tm *time.Time - if DBQueryRow("SELECT COUNT(id_exercice), MIN(time) FROM exercice_solved WHERE id_exercice = ?", e.Id).Scan(&nb, &tm); nb == nil || tm == nil { - return 0, nil - } else { - return *nb, tm - } -} - -func HasStandaloneExercice() (bool, error) { - var nb int - - err := DBQueryRow("SELECT COUNT(id_exercice) FROM exercices WHERE id_theme IS NULL").Scan(&nb) - if err != nil { - return false, err - } else { - return nb > 0, nil - } -} diff --git a/libfic/exercice_history.go b/libfic/exercice_history.go deleted file mode 100644 index d9da69ee..00000000 --- a/libfic/exercice_history.go +++ /dev/null @@ -1,172 +0,0 @@ -package fic - -import ( - "encoding/binary" - "fmt" - "math/rand" - "time" -) - -// GetHistory aggregates all sources of events or actions for an Exercice -func (e *Exercice) GetHistory() ([]map[string]interface{}, error) { - hist := make([]map[string]interface{}, 0) - - if rows, err := DBQuery(`SELECT id_team, U.name, U.color, "tries" AS kind, time, 0, id_exercice, id_try, NULL FROM exercice_tries NATURAL JOIN teams U WHERE id_exercice = ? UNION - SELECT id_team, U.name, U.color, "solved" AS kind, time, coefficient, id_exercice, NULL, NULL FROM exercice_solved S NATURAL JOIN teams U WHERE id_exercice = ? UNION - SELECT id_team, U.name, U.color, "hint" AS kind, time, coefficient, id_exercice, H.id_hint, H.title FROM team_hints T INNER JOIN exercice_hints H ON H.id_hint = T.id_hint NATURAL JOIN teams U WHERE id_exercice = ? UNION - SELECT id_team, U.name, U.color, "wchoices" AS kind, time, coefficient, id_exercice, F.id_flag, F.type FROM team_wchoices W INNER JOIN exercice_flags F ON F.id_flag = W.id_flag NATURAL JOIN teams U WHERE id_exercice = ? UNION - SELECT id_team, U.name, U.color, "flag_found" AS kind, time, 0, id_exercice, K.id_flag, K.type FROM flag_found F INNER JOIN exercice_flags K ON K.id_flag = F.id_flag NATURAL JOIN teams U WHERE id_exercice = ? UNION - SELECT id_team, U.name, U.color, "mcq_found" AS kind, time, 0, id_exercice, Q.id_mcq, Q.title FROM mcq_found F INNER JOIN exercice_mcq Q ON Q.id_mcq = F.id_mcq NATURAL JOIN teams U WHERE id_exercice = ? - ORDER BY time DESC`, e.Id, e.Id, e.Id, e.Id, e.Id, e.Id); err != nil { - return nil, err - } else { - defer rows.Close() - - for rows.Next() { - var id_team int64 - var team_name string - var team_color uint32 - var kind string - var time time.Time - var coeff float32 - var exercice int64 - var secondary *int64 - var secondary_title *string - - if err := rows.Scan(&id_team, &team_name, &team_color, &kind, &time, &coeff, &exercice, &secondary, &secondary_title); err != nil { - return nil, err - } - - h := map[string]interface{}{} - - h["team_id"] = id_team - h["team_name"] = team_name - h["team_color"] = fmt.Sprintf("#%x", team_color) - h["kind"] = kind - h["time"] = time - h["coefficient"] = coeff - h["primary"] = e.Id - if secondary != nil { - h["secondary"] = secondary - h["secondary_title"] = secondary_title - } - - hist = append(hist, h) - } - } - - return hist, nil -} - -// AppendHistoryItem sets values an entry from the history. -func (e *Exercice) AppendHistoryItem(tId int64, kind string, secondary *int64) error { - team, err := GetTeam(tId) - if err != nil { - return err - } - - if kind == "tries" { - bid := make([]byte, 5) - binary.LittleEndian.PutUint32(bid, rand.Uint32()) - _, err = e.NewTry(team, bid) - return err - } else if kind == "hint" && secondary != nil { - return team.OpenHint(&EHint{Id: *secondary}) - } else if kind == "wchoices" && secondary != nil { - return team.DisplayChoices(&FlagKey{Id: int(*secondary)}) - } else if kind == "flag_found" && secondary != nil { - return (&FlagKey{Id: int(*secondary)}).FoundBy(team) - } else if kind == "mcq_found" && secondary != nil { - return (&MCQ{Id: int(*secondary)}).FoundBy(team) - } else if kind == "solved" { - return e.Solved(team) - } else { - return nil - } -} - -// UpdateHistoryItem sets values an entry from the history. -func (e *Exercice) UpdateHistoryItem(coeff float32, tId int64, kind string, h time.Time, secondary *int64) (interface{}, error) { - if kind == "hint" && secondary != nil { - if res, err := DBExec("UPDATE team_hints SET coefficient = ?, time = ? WHERE id_team = ? AND time = ? AND id_hint = ?", coeff, h, tId, h, *secondary); err != nil { - return 0, err - } else if nb, err := res.RowsAffected(); err != nil { - return 0, err - } else { - return nb, err - } - } else if kind == "wchoices" && secondary != nil { - if res, err := DBExec("UPDATE team_wchoices SET coefficient = ?, time = ? WHERE id_team = ? AND time = ? AND id_flag = ?", coeff, h, tId, h, *secondary); err != nil { - return 0, err - } else if nb, err := res.RowsAffected(); err != nil { - return 0, err - } else { - return nb, err - } - } else if kind == "solved" { - if res, err := DBExec("UPDATE exercice_solved SET coefficient = ?, time = ? WHERE id_team = ? AND time = ? AND id_exercice = ?", coeff, h, tId, h, e.Id); err != nil { - return 0, err - } else if nb, err := res.RowsAffected(); err != nil { - return 0, err - } else { - return nb, err - } - } else { - return nil, nil - } -} - -// DelHistoryItem removes from the database an entry from the history. -func (e *Exercice) DelHistoryItem(tId int64, kind string, h time.Time, secondary *int64) (interface{}, error) { - if kind == "tries" { - if res, err := DBExec("DELETE FROM exercice_tries WHERE id_team = ? AND time = ? AND id_exercice = ?", tId, h, e.Id); err != nil { - return 0, err - } else if nb, err := res.RowsAffected(); err != nil { - return 0, err - } else { - return nb, err - } - } else if kind == "hint" && secondary != nil { - if res, err := DBExec("DELETE FROM team_hints WHERE id_team = ? AND time = ? AND id_hint = ?", tId, h, *secondary); err != nil { - return 0, err - } else if nb, err := res.RowsAffected(); err != nil { - return 0, err - } else { - return nb, err - } - } else if kind == "wchoices" && secondary != nil { - if res, err := DBExec("DELETE FROM team_wchoices WHERE id_team = ? AND time = ? AND id_flag = ?", tId, h, *secondary); err != nil { - return 0, err - } else if nb, err := res.RowsAffected(); err != nil { - return 0, err - } else { - return nb, err - } - } else if kind == "flag_found" && secondary != nil { - if res, err := DBExec("DELETE FROM flag_found WHERE id_team = ? AND time = ? AND id_flag = ?", tId, h, *secondary); err != nil { - return 0, err - } else if nb, err := res.RowsAffected(); err != nil { - return 0, err - } else { - return nb, err - } - } else if kind == "mcq_found" && secondary != nil { - if res, err := DBExec("DELETE FROM mcq_found WHERE id_team = ? AND time = ? AND id_mcq = ?", tId, h, *secondary); err != nil { - return 0, err - } else if nb, err := res.RowsAffected(); err != nil { - return 0, err - } else { - return nb, err - } - } else if kind == "solved" { - if res, err := DBExec("DELETE FROM exercice_solved WHERE id_team = ? AND time = ? AND id_exercice = ?", tId, h, e.Id); err != nil { - return 0, err - } else if nb, err := res.RowsAffected(); err != nil { - return 0, err - } else { - return nb, err - } - } else { - return nil, nil - } -} diff --git a/libfic/exercice_tag.go b/libfic/exercice_tag.go deleted file mode 100644 index 8b6d984b..00000000 --- a/libfic/exercice_tag.go +++ /dev/null @@ -1,57 +0,0 @@ -package fic - -import ( - "strings" -) - -// GetTags returns tags associated with this exercice. -func (e *Exercice) GetTags() (tags []string, err error) { - if rows, errr := DBQuery("SELECT tag FROM exercice_tags WHERE id_exercice = ?", e.Id); errr != nil { - return nil, errr - } else { - defer rows.Close() - - tags = make([]string, 0) - for rows.Next() { - var t string - if err = rows.Scan(&t); err != nil { - return - } - tags = append(tags, t) - } - err = rows.Err() - return - } -} - -// AddTag assign a new tag to the exercice and registers it into the database. -func (e *Exercice) AddTag(tag string) (string, error) { - tag = strings.Title(tag) - if _, err := DBExec("INSERT INTO exercice_tags (id_exercice, tag) VALUES (?, ?)", e.Id, tag); err != nil { - return "", err - } else { - return tag, nil - } -} - -// DeleteTag delete a tag assigned to the current exercice from the database. -func (e *Exercice) DeleteTag(tag string) (int64, error) { - if res, err := DBExec("DELETE FROM exercice_tags WHERE id_exercice = ? AND tag = ?", e.Id, tag); err != nil { - return 0, err - } else if nb, err := res.RowsAffected(); err != nil { - return 0, err - } else { - return nb, err - } -} - -// WipeTags delete all tag assigned to the current exercice from the database. -func (e *Exercice) WipeTags() (int64, error) { - if res, err := DBExec("DELETE FROM exercice_tags WHERE id_exercice = ?", e.Id); err != nil { - return 0, err - } else if nb, err := res.RowsAffected(); err != nil { - return 0, err - } else { - return nb, err - } -} diff --git a/libfic/exercice_tries.go b/libfic/exercice_tries.go deleted file mode 100644 index 18f8356b..00000000 --- a/libfic/exercice_tries.go +++ /dev/null @@ -1,102 +0,0 @@ -package fic - -import ( - "time" -) - -type ExerciceTryDetail struct { - Kind string `json:"kind"` - RelatedId int64 `json:"related"` -} - -type ExerciceTry struct { - Id int64 `json:"id"` - IdTeam int64 `json:"id_team"` - Time time.Time `json:"time"` - Checksum []byte `json:"checksum"` - NbDiff int64 `json:"nb_diff"` - OneGood bool `json:"one_good"` - Details []ExerciceTryDetail `json:"details,omitempty"` -} - -// TriesList returns a list of tries related to the exercice. -func (e *Exercice) TriesList() ([]*ExerciceTry, error) { - tries_table := "exercice_tries" - if SubmissionUniqueness { - tries_table = "exercice_distinct_tries" - } - - if rows, err := DBQuery("SELECT id_try, id_team, time, cksum, nbdiff, onegood FROM "+tries_table+" WHERE id_exercice = ?", e.Id); err != nil { - return nil, err - } else { - defer rows.Close() - - var tries []*ExerciceTry - for rows.Next() { - var try ExerciceTry - if err := rows.Scan(&try.Id, &try.IdTeam, &try.Time, &try.Checksum, &try.NbDiff, &try.OneGood); err != nil { - return nil, err - } - - tries = append(tries, &try) - } - - return tries, nil - } -} - -func (e *Exercice) GetTry(id int64) (try *ExerciceTry, err error) { - try = &ExerciceTry{} - err = DBQueryRow("SELECT id_try, id_team, time, cksum, nbdiff, onegood FROM exercice_tries WHERE id_exercice = ? AND id_try = ?", e.Id, id).Scan(&try.Id, &try.IdTeam, &try.Time, &try.Checksum, &try.NbDiff, &try.OneGood) - return -} - -func (try *ExerciceTry) FillDetails() error { - if rows, err := DBQuery("SELECT id_flag FROM exercice_tries_flags WHERE id_try = ?", try.Id); err != nil { - return err - } else { - defer rows.Close() - - for rows.Next() { - var relatedid int64 - if err := rows.Scan(&relatedid); err != nil { - return err - } - - try.Details = append(try.Details, ExerciceTryDetail{ - Kind: "flag", - RelatedId: relatedid, - }) - } - } - - if rows, err := DBQuery("SELECT id_mcq FROM exercice_tries_mcq WHERE id_try = ?", try.Id); err != nil { - return err - } else { - defer rows.Close() - - for rows.Next() { - var relatedid int64 - if err := rows.Scan(&relatedid); err != nil { - return err - } - - try.Details = append(try.Details, ExerciceTryDetail{ - Kind: "mcq", - RelatedId: relatedid, - }) - } - } - - return nil -} - -func (try *ExerciceTry) Delete() (int64, error) { - if res, err := DBExec("DELETE FROM exercice_tries WHERE id_try = ?", try.Id); err != nil { - return 0, err - } else if nb, err := res.RowsAffected(); err != nil { - return 0, err - } else { - return nb, err - } -} diff --git a/libfic/file.go b/libfic/file.go index d39bb6b0..d763eaf5 100644 --- a/libfic/file.go +++ b/libfic/file.go @@ -1,84 +1,36 @@ package fic import ( - "compress/gzip" - "crypto" - _ "crypto/sha1" - "encoding/hex" - "errors" - "fmt" - "hash" + "bufio" + "crypto/sha1" "io" "os" "path" "strings" - - _ "golang.org/x/crypto/blake2b" ) -// FilesDir stores the location where files to be served are stored. -var FilesDir string = "./FILES/" +var FilesDir string -// OptionalDigest permits to avoid importation failure if no digest are given. -var OptionalDigest bool = false - -// StrongDigest forces the use of BLAKE2b hash in place of SHA1 (or mixed SHA1/BLAKE2b). -var StrongDigest bool = false - -// Set PlainDigest if digest errors should contain the whole calculated hash, to be paste directly into DIGESTS file. -var PlainDigest bool = false - -// EFile represents a challenge file. type EFile struct { - Id int64 `json:"id,omitempty"` - // origin holds the import relative path of the file - origin string - // Path is the location where the file is stored, relatively to FilesDir - Path string `json:"path"` - // IdExercice is the identifier of the underlying challenge - IdExercice int64 `json:"idExercice,omitempty"` - // Name is the title displayed to players - Name string `json:"name"` - // Checksum stores the cached hash of the file - Checksum []byte `json:"checksum"` - // ChecksumShown stores the hash of the downloaded file (in case of gzipped content) - ChecksumShown []byte `json:"checksum_shown"` - // Size contains the cached size of the file - Size int64 `json:"size"` - // Disclaimer contains a string to display before downloading the content - Disclaimer string `json:"disclaimer"` - // Published indicates if the file should be shown or not - Published bool `json:"published"` + Id int64 `json:"id"` + origin string + Path string `json:"path"` + IdExercice int64 `json:"idExercice"` + Name string `json:"name"` + Checksum []byte `json:"checksum"` + Size int64 `json:"size"` } -// NewDummyFile creates an EFile, without any link to an actual Exercice File. -// It is used to check the file validity -func (e *Exercice) NewDummyFile(origin string, dest string, checksum []byte, checksumShown []byte, disclaimer string, size int64) *EFile { - return &EFile{ - Id: 0, - origin: origin, - Path: dest, - IdExercice: e.Id, - Name: path.Base(origin), - Checksum: checksum, - ChecksumShown: checksumShown, - Size: size, - Disclaimer: disclaimer, - Published: true, - } -} - -// GetFiles returns a list of all files living in the database. -func GetFiles() ([]*EFile, error) { - if rows, err := DBQuery("SELECT id_file, id_exercice, origin, path, name, cksum, cksum_shown, size, disclaimer, published FROM exercice_files"); err != nil { +func GetFiles() ([]EFile, error) { + if rows, err := DBQuery("SELECT id_file, id_exercice, origin, path, name, sha1, size FROM exercice_files"); err != nil { return nil, err } else { defer rows.Close() - files := []*EFile{} + var files = make([]EFile, 0) for rows.Next() { - f := &EFile{} - if err := rows.Scan(&f.Id, &f.IdExercice, &f.origin, &f.Path, &f.Name, &f.Checksum, &f.ChecksumShown, &f.Size, &f.Disclaimer, &f.Published); err != nil { + var f EFile + if err := rows.Scan(&f.Id, &f.IdExercice, &f.origin, &f.Path, &f.Name, &f.Checksum, &f.Size); err != nil { return nil, err } files = append(files, f) @@ -91,53 +43,17 @@ func GetFiles() ([]*EFile, error) { } } -// GetFile retrieves the file with the given id. -func GetFile(id int64) (f *EFile, err error) { - f = &EFile{} - err = DBQueryRow("SELECT id_file, origin, path, name, cksum, cksum_shown, size, disclaimer, published FROM exercice_files WHERE id_file = ?", id).Scan(&f.Id, &f.origin, &f.Path, &f.Name, &f.Checksum, &f.ChecksumShown, &f.Size, &f.Disclaimer, &f.Published) - return -} - -func (e *Exercice) GetFile(id int64) (f *EFile, err error) { - f = &EFile{} - err = DBQueryRow("SELECT id_file, origin, path, name, cksum, cksum_shown, size, disclaimer, published FROM exercice_files WHERE id_file = ? AND id_exercice = ?", id, e.Id).Scan(&f.Id, &f.origin, &f.Path, &f.Name, &f.Checksum, &f.ChecksumShown, &f.Size, &f.Disclaimer, &f.Published) - return -} - -// GetFileByPath retrieves the file that should be found at the given location. -func GetFileByPath(path string) (*EFile, error) { - path = strings.TrimPrefix(path, FilesDir) - - f := &EFile{} - if err := DBQueryRow("SELECT id_file, origin, path, id_exercice, name, cksum, cksum_shown, size, disclaimer, published FROM exercice_files WHERE path = ?", path).Scan(&f.Id, &f.origin, &f.Path, &f.IdExercice, &f.Name, &f.Checksum, &f.ChecksumShown, &f.Size, &f.Disclaimer, &f.Published); err != nil { - return f, err - } - - return f, nil -} - -// GetFileByFilename retrieves the file that should be called so. -func (e *Exercice) GetFileByFilename(filename string) (f *EFile, err error) { - filename = path.Base(filename) - - f = &EFile{} - err = DBQueryRow("SELECT id_file, origin, path, id_exercice, name, cksum, cksum_shown, size, disclaimer, published FROM exercice_files WHERE id_exercice = ? AND origin LIKE ?", e.Id, "%/"+filename).Scan(&f.Id, &f.origin, &f.Path, &f.IdExercice, &f.Name, &f.Checksum, &f.ChecksumShown, &f.Size, &f.Disclaimer, &f.Published) - - return -} - -// GetFiles returns a list of files coming with the challenge. -func (e *Exercice) GetFiles() ([]*EFile, error) { - if rows, err := DBQuery("SELECT id_file, origin, path, name, cksum, cksum_shown, size, disclaimer, published FROM exercice_files WHERE id_exercice = ?", e.Id); err != nil { +func (e Exercice) GetFiles() ([]EFile, error) { + if rows, err := DBQuery("SELECT id_file, origin, path, name, sha1, size FROM exercice_files WHERE id_exercice = ?", e.Id); err != nil { return nil, err } else { defer rows.Close() - files := []*EFile{} + var files = make([]EFile, 0) for rows.Next() { - f := &EFile{} + var f EFile f.IdExercice = e.Id - if err := rows.Scan(&f.Id, &f.origin, &f.Path, &f.Name, &f.Checksum, &f.ChecksumShown, &f.Size, &f.Disclaimer, &f.Published); err != nil { + if err := rows.Scan(&f.Id, &f.origin, &f.Path, &f.Name, &f.Checksum, &f.Size); err != nil { return nil, err } files = append(files, f) @@ -150,131 +66,37 @@ func (e *Exercice) GetFiles() ([]*EFile, error) { } } -// GetFileByPath retrieves the file that should be found at the given location, limited to the challenge files. -func (e *Exercice) GetFileByPath(path string) (*EFile, error) { - path = strings.TrimPrefix(path, FilesDir) - - f := &EFile{} - if err := DBQueryRow("SELECT id_file, origin, path, name, cksum, cksum_shown, size, disclaimer, published FROM exercice_files WHERE id_exercice = ? AND path = ?", e.Id, path).Scan(&f.Id, &f.origin, &f.Path, &f.Name, &f.Checksum, &f.ChecksumShown, &f.Size, &f.Disclaimer, &f.Published); err != nil { - return nil, err - } - - return f, nil -} - -// minifyHash returns only the begining and the end of the given hash. -// Use this function to ensure people doesn't fill their file with our calculated hash. -func minifyHash(hash string) string { - if PlainDigest { - return hash - } else { - return hash[0:len(hash)/3] + "..." + hash[len(hash)/2:] - } -} - -// CheckBufferHash checks if the bufio has the given digest. -func CreateHashBuffers(rd io.Reader) (*hash.Hash, *hash.Hash) { - hash160 := crypto.SHA1.New() - hash512 := crypto.BLAKE2b_512.New() - - w := io.MultiWriter(hash160, hash512) - - io.Copy(w, rd) - - return &hash160, &hash512 -} - -// CheckBufferHash checks if the bufio has the given digest. -func CheckBufferHash(hash160 *hash.Hash, hash512 *hash.Hash, digest []byte) ([]byte, error) { - result160 := (*hash160).Sum(nil) - result512 := (*hash512).Sum(nil) - - if len(digest) != len(result512) { - if len(digest) != len(result160) { - return []byte{}, errors.New("Digests doesn't match: calculated: sha1:" + minifyHash(hex.EncodeToString(result160)) + " & blake2b:" + minifyHash(hex.EncodeToString(result512)) + " vs. given: " + hex.EncodeToString(digest)) - } else if StrongDigest { - return []byte{}, errors.New("Invalid digests: SHA-1 checksums are no more accepted. Calculated sha1:" + minifyHash(hex.EncodeToString(result160)) + " & blake2b:" + minifyHash(hex.EncodeToString(result512)) + " vs. given: " + hex.EncodeToString(digest)) - } - - for k := range result160 { - if result160[k] != digest[k] { - return []byte{}, errors.New("Digests doesn't match: calculated: sha1:" + minifyHash(hex.EncodeToString(result160)) + " & blake2b:" + minifyHash(hex.EncodeToString(result512)) + " vs. given: " + hex.EncodeToString(digest)) - } - } - } else { - for k := range result512 { - if result512[k] != digest[k] { - return []byte{}, errors.New("Digests doesn't match: calculated: " + minifyHash(hex.EncodeToString(result512)) + " vs. given: " + hex.EncodeToString(digest)) - } - } - } - - return result512, nil -} - -// checkFileHash checks if the file at the given filePath has the given digest. -// It also returns the file's size. -func checkFileHash(filePath string, digest []byte) (dgst []byte, size int64, err error) { - if digest == nil { - return []byte{}, 0, errors.New("no digest given") - } else if fi, errr := os.Stat(filePath); errr != nil { - return []byte{}, 0, errr - } else if fd, errr := os.Open(filePath); errr != nil { - return []byte{}, fi.Size(), errr +func (e Exercice) ImportFile(filePath string, origin string) (EFile, error) { + if fi, err := os.Stat(filePath); err != nil { + return EFile{}, err + } else if fd, err := os.Open(filePath); err != nil { + return EFile{}, err } else { defer fd.Close() - size = fi.Size() - hash160, hash512 := CreateHashBuffers(fd) - - dgst, err = CheckBufferHash(hash160, hash512, digest) - - return - } -} - -// ImportFile registers (ou updates if it already exists in database) the file in database. -func (e *Exercice) ImportFile(filePath string, origin string, digest []byte, digestshown []byte, disclaimer string, published bool) (interface{}, error) { - if result512, size, err := checkFileHash(filePath, digest); !OptionalDigest && err != nil { - return nil, err - } else { - dPath := strings.TrimPrefix(filePath, FilesDir) - if f, err := e.GetFileByPath(dPath); err != nil { - return e.AddFile(dPath, origin, path.Base(filePath), result512, digestshown, size, disclaimer, published) - } else { - // Don't need to update Path and Name, because they are related to dPath - - f.IdExercice = e.Id - f.origin = origin - f.Checksum = result512 - f.ChecksumShown = digestshown - f.Size = size - f.Disclaimer = disclaimer - f.Published = published - - if _, err := f.Update(); err != nil { - return nil, err - } else { - return f, nil - } + reader := bufio.NewReader(fd) + hash := sha1.New() + if _, err := io.Copy(hash, reader); err != nil { + return EFile{}, err } + + var result []byte + return e.AddFile(strings.TrimPrefix(filePath, FilesDir), origin, path.Base(filePath), hash.Sum(result), fi.Size()) } } -// AddFile creates and fills a new struct File and registers it into the database. -func (e *Exercice) AddFile(path string, origin string, name string, checksum []byte, checksumshown []byte, size int64, disclaimer string, published bool) (*EFile, error) { - if res, err := DBExec("INSERT INTO exercice_files (id_exercice, origin, path, name, cksum, cksum_shown, size, disclaimer, published) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)", e.Id, origin, path, name, checksum, checksumshown, size, disclaimer, published); err != nil { - return nil, err +func (e Exercice) AddFile(path string, origin string, name string, checksum []byte, size int64) (EFile, error) { + if res, err := DBExec("INSERT INTO exercice_files (id_exercice, origin, path, name, sha1, size) VALUES (?, ?, ?, ?, ?, ?)", e.Id, origin, path, name, checksum, size); err != nil { + return EFile{}, err } else if fid, err := res.LastInsertId(); err != nil { - return nil, err + return EFile{}, err } else { - return &EFile{fid, origin, path, e.Id, name, checksum, checksumshown, size, disclaimer, published}, nil + return EFile{fid, origin, path, e.Id, name, checksum, size}, nil } } -// Update applies modifications back to the database. -func (f *EFile) Update() (int64, error) { - if res, err := DBExec("UPDATE exercice_files SET id_exercice = ?, origin = ?, path = ?, name = ?, cksum = ?, cksum_shown = ?, size = ?, disclaimer = ?, published = ? WHERE id_file = ?", f.IdExercice, f.origin, f.Path, f.Name, f.Checksum, f.ChecksumShown, f.Size, f.Disclaimer, f.Published, f.Id); err != nil { +func (f EFile) Update() (int64, error) { + if res, err := DBExec("UPDATE exercice_files SET id_exercice = ?, origin = ?, path = ?, name = ?, sha1 = ?, size = ? WHERE id_file = ?", f.IdExercice, f.origin, f.Path, f.Name, f.Checksum, f.Size, f.Id); err != nil { return 0, err } else if nb, err := res.RowsAffected(); err != nil { return 0, err @@ -283,7 +105,6 @@ func (f *EFile) Update() (int64, error) { } } -// Delete the file from the database. func (f EFile) Delete() (int64, error) { if res, err := DBExec("DELETE FROM exercice_files WHERE id_file = ?", f.Id); err != nil { return 0, err @@ -294,158 +115,6 @@ func (f EFile) Delete() (int64, error) { } } -// WipeFiles deletes (only in the database, not on disk) files coming with the challenge. -func (e Exercice) WipeFiles() (int64, error) { - if _, err := DBExec("DELETE FROM exercice_files_okey_deps WHERE id_flag IN (SELECT id_flag FROM exercice_flags WHERE id_exercice = ?)", e.Id); err != nil { - return 0, err - } else if _, err := DBExec("DELETE FROM exercice_files_omcq_deps WHERE id_mcq IN (SELECT id_mcq FROM exercice_mcq WHERE id_exercice = ?)", e.Id); err != nil { - return 0, err - } else if res, err := DBExec("DELETE FROM exercice_files WHERE id_exercice = ?", e.Id); err != nil { - return 0, err - } else if nb, err := res.RowsAffected(); err != nil { - return 0, err - } else { - return nb, err - } -} - -// ClearFiles removes all certificates from database (but not files on disks). -func ClearFiles() (int64, error) { - if res, err := DBExec("DELETE FROM exercice_files"); err != nil { - return 0, err - } else if nb, err := res.RowsAffected(); err != nil { - return 0, err - } else { - return nb, err - } -} - -// GetOrigin access the private field origin of the file. -func (f *EFile) GetOrigin() string { +func (f EFile) GetOrigin() (string) { return f.origin } - -// AddDepend insert a new dependency to a given flag. -func (f *EFile) AddDepend(j Flag) (err error) { - if k, ok := j.(*FlagKey); ok { - _, err = DBExec("INSERT INTO exercice_files_okey_deps (id_file, id_flag) VALUES (?, ?)", f.Id, k.Id) - } else if m, ok := j.(*MCQ); ok { - _, err = DBExec("INSERT INTO exercice_files_omcq_deps (id_file, id_mcq) VALUES (?, ?)", f.Id, m.Id) - } else { - err = fmt.Errorf("dependancy type for flag (%T) not implemented for this file", j) - } - return -} - -// DeleteDepend insert a new dependency to a given flag. -func (f *EFile) DeleteDepend(j Flag) (err error) { - if k, ok := j.(*FlagKey); ok { - _, err = DBExec("DELETE FROM exercice_files_okey_deps WHERE id_file = ? AND id_flag = ?", f.Id, k.Id) - } else if m, ok := j.(*MCQ); ok { - _, err = DBExec("DELETE FROM exercice_files_omcq_deps WHERE id_file = ? AND id_mcq = ?", f.Id, m.Id) - } else { - err = fmt.Errorf("dependancy type for flag (%T) not implemented for this file", j) - } - return -} - -// GetDepends retrieve the flag's dependency list. -func (f *EFile) GetDepends() ([]Flag, error) { - var deps = make([]Flag, 0) - - if rows, err := DBQuery("SELECT id_flag FROM exercice_files_okey_deps WHERE id_file = ?", f.Id); err != nil { - return nil, err - } else { - defer rows.Close() - - for rows.Next() { - var d int - if err := rows.Scan(&d); err != nil { - return nil, err - } - deps = append(deps, &FlagKey{d, f.IdExercice, 0, "", "", "", "", "", false, false, false, nil, false, []byte{}, 0, 0}) - } - if err := rows.Err(); err != nil { - return nil, err - } - } - - if rows, err := DBQuery("SELECT id_mcq FROM exercice_files_omcq_deps WHERE id_file = ?", f.Id); err != nil { - return nil, err - } else { - defer rows.Close() - - for rows.Next() { - var d int - if err := rows.Scan(&d); err != nil { - return nil, err - } - deps = append(deps, &MCQ{d, f.IdExercice, 0, "", []*MCQ_entry{}}) - } - if err := rows.Err(); err != nil { - return nil, err - } - } - - return deps, nil -} - -// CheckFileOnDisk recalculates the hash of the file on disk. -func (f *EFile) CheckFileOnDisk() error { - firstChecksum := f.Checksum - if len(f.ChecksumShown) > 0 { - firstChecksum = f.ChecksumShown - } - - if _, size, err := checkFileHash(path.Join(FilesDir, f.Path), firstChecksum); size > 0 && err != nil { - return err - } else if size == 0 { - if _, _, err := checkFileHash(path.Join(FilesDir, f.Path+".gz"), f.Checksum); err != nil { - return errors.New("empty file") - } else { - return nil - } - } else if err != nil { - return err - } - - if _, err := os.Stat(path.Join(FilesDir, f.Path+".gz")); !os.IsNotExist(err) { - if _, _, err = checkFileHash(path.Join(FilesDir, f.Path+".gz"), f.Checksum); err != nil { - return err - } - } - - return nil -} - -// GunzipFileOnDisk gunzip a compressed file. -func (f *EFile) GunzipFileOnDisk() error { - if !strings.HasSuffix(f.origin, ".gz") || strings.HasSuffix(f.origin, ".tar.gz") { - return nil - } - - fdIn, err := os.Open(path.Join(FilesDir, f.Path+".gz")) - if err != nil { - return err - } - defer fdIn.Close() - - fdOut, err := os.Create(path.Join(FilesDir, strings.TrimSuffix(f.Path, ".gz"))) - if err != nil { - return err - } - defer fdOut.Close() - - gunzipfd, err := gzip.NewReader(fdIn) - if err != nil { - return err - } - defer gunzipfd.Close() - - _, err = io.Copy(fdOut, gunzipfd) - if err != nil { - return err - } - - return nil -} diff --git a/libfic/flag.go b/libfic/flag.go deleted file mode 100644 index 2e419bf1..00000000 --- a/libfic/flag.go +++ /dev/null @@ -1,91 +0,0 @@ -package fic - -import () - -type Flag interface { - GetId() int - RecoverId() (Flag, error) - Create(e *Exercice) (Flag, error) - Update() (int64, error) - Delete() (int64, error) - AddDepend(d Flag) error - GetDepends() ([]Flag, error) - GetOrder() int8 - Check(val interface{}) int - IsOptionnal() bool - FoundBy(t *Team) error - NbTries() (int64, error) - TeamsOnIt() ([]int64, error) - DeleteTries() error -} - -// GetFlag returns a list of flags comming with the challenge. -func (e *Exercice) GetFlags() ([]Flag, error) { - var flags []Flag - - if ks, err := e.GetFlagKeys(); err != nil { - return nil, err - } else { - for _, k := range ks { - flags = append(flags, k) - } - } - - if ls, err := e.GetFlagLabels(); err != nil { - return nil, err - } else { - for _, l := range ls { - flags = append(flags, l) - } - } - - if ms, err := e.GetMCQ(); err != nil { - return nil, err - } else { - for _, m := range ms { - flags = append(flags, m) - } - } - - return flags, nil -} - -// AddFlag add the given flag and eventually its entries (from MCQ). -func (e *Exercice) AddFlag(flag Flag) (Flag, error) { - return flag.Create(e) -} - -// WipeFlags deletes flags coming with the challenge. -func (e *Exercice) WipeFlags() (int64, error) { - if _, err := DBExec("DELETE FROM exercice_files_okey_deps WHERE id_flag IN (SELECT id_flag FROM exercice_flags WHERE id_exercice = ?)", e.Id); err != nil { - return 0, err - } else if _, err := DBExec("DELETE FROM exercice_files_omcq_deps WHERE id_mcq IN (SELECT id_mcq FROM exercice_mcq WHERE id_exercice = ?)", e.Id); err != nil { - return 0, err - } else if _, err := DBExec("DELETE FROM exercice_flags_deps WHERE id_flag IN (SELECT id_flag FROM exercice_flags WHERE id_exercice = ?) OR id_flag_dep IN (SELECT id_flag FROM exercice_flags WHERE id_exercice = ?)", e.Id, e.Id); err != nil { - return 0, err - } else if _, err := DBExec("DELETE FROM exercice_flags_omcq_deps WHERE id_flag IN (SELECT id_flag FROM exercice_flags WHERE id_exercice = ?)", e.Id); err != nil { - return 0, err - } else if _, err := DBExec("DELETE FROM exercice_flag_labels_omcq_deps WHERE id_label IN (SELECT id_label FROM exercice_flag_labels WHERE id_exercice = ?)", e.Id); err != nil { - return 0, err - } else if _, err := DBExec("DELETE FROM exercice_flag_labels_deps WHERE id_label IN (SELECT id_label FROM exercice_flag_labels WHERE id_exercice = ?)", e.Id); err != nil { - return 0, err - } else if _, err := DBExec("DELETE FROM exercice_hints_okey_deps WHERE id_flag_dep IN (SELECT id_flag FROM exercice_flags WHERE id_exercice = ?)", e.Id); err != nil { - return 0, err - } else if _, err := DBExec("DELETE FROM exercice_hints_omcq_deps WHERE id_mcq_dep IN (SELECT id_mcq FROM exercice_mcq WHERE id_exercice = ?)", e.Id); err != nil { - return 0, err - } else if _, err := DBExec("DELETE FROM exercice_mcq_okey_deps WHERE id_flag_dep IN (SELECT id_flag FROM exercice_flags WHERE id_exercice = ?)", e.Id); err != nil { - return 0, err - } else if _, err := DBExec("DELETE FROM team_wchoices WHERE id_flag IN (SELECT id_flag FROM exercice_flags WHERE id_exercice = ?)", e.Id); err != nil { - return 0, err - } else if _, err := DBExec("DELETE FROM flag_choices WHERE id_flag IN (SELECT id_flag FROM exercice_flags WHERE id_exercice = ?)", e.Id); err != nil { - return 0, err - } else if _, err := DBExec("DELETE FROM exercice_flag_labels WHERE id_exercice = ?", e.Id); err != nil { - return 0, err - } else if res, err := DBExec("DELETE FROM exercice_flags WHERE id_exercice = ?", e.Id); err != nil { - return 0, err - } else if nb, err := res.RowsAffected(); err != nil { - return 0, err - } else { - return nb, err - } -} diff --git a/libfic/flag_choice.go b/libfic/flag_choice.go deleted file mode 100644 index a9eef7aa..00000000 --- a/libfic/flag_choice.go +++ /dev/null @@ -1,93 +0,0 @@ -package fic - -import () - -// FlagChoice represents a choice a respond to a classic flag -type FlagChoice struct { - Id int `json:"id"` - // IdFlag is the identifier of the underlying flag - IdFlag int `json:"idFlag"` - // Label is the title of the choice as displayed to players - Label string `json:"label"` - // Value is the raw content that'll be written as response if this choice is selected - Value string `json:"value"` -} - -// GetChoices returns a list of choices for the given Flag. -func (f *FlagKey) GetChoices() ([]*FlagChoice, error) { - if rows, err := DBQuery("SELECT id_choice, id_flag, label, response FROM flag_choices WHERE id_flag = ?", f.Id); err != nil { - return nil, err - } else { - defer rows.Close() - - var choices []*FlagChoice - for rows.Next() { - c := &FlagChoice{} - c.IdFlag = f.Id - - if err := rows.Scan(&c.Id, &c.IdFlag, &c.Label, &c.Value); err != nil { - return nil, err - } - - choices = append(choices, c) - } - if err := rows.Err(); err != nil { - return nil, err - } - - return choices, nil - } -} - -// GetChoice returns a choice for the given Flag. -func (f *FlagKey) GetChoice(id int) (c *FlagChoice, err error) { - c = &FlagChoice{} - if errr := DBQueryRow("SELECT id_choice, id_flag, label, response FROM flag_choices WHERE id_choice = ?", id).Scan(&c.Id, &c.IdFlag, &c.Label, &c.Value); errr != nil { - return c, errr - } - return -} - -// AddChoice creates and fills a new struct FlagChoice, from a label and a value. -func (f *FlagKey) AddChoice(c *FlagChoice) (*FlagChoice, error) { - if res, err := DBExec("INSERT INTO flag_choices (id_flag, label, response) VALUES (?, ?, ?)", f.Id, c.Label, c.Value); err != nil { - return c, err - } else { - cid, err := res.LastInsertId() - c.Id = int(cid) - return c, err - } -} - -// Update applies modifications back to the database. -func (c *FlagChoice) Update() (int64, error) { - if res, err := DBExec("UPDATE flag_choices SET id_flag = ?, label = ?, response = ? WHERE id_choice = ?", c.IdFlag, c.Label, c.Value, c.Id); err != nil { - return 0, err - } else if nb, err := res.RowsAffected(); err != nil { - return 0, err - } else { - return nb, err - } -} - -// Delete the flag from the database. -func (c *FlagChoice) Delete() (int64, error) { - if res, err := DBExec("DELETE FROM flag_choices WHERE id_choice = ?", c.Id); err != nil { - return 0, err - } else if nb, err := res.RowsAffected(); err != nil { - return 0, err - } else { - return nb, err - } -} - -// WipeFlags deletes flags coming with the challenge. -func (f *FlagKey) WipeChoices() (int64, error) { - if res, err := DBExec("DELETE FROM flag_choices WHERE id_flag = ?", f.Id); err != nil { - return 0, err - } else if nb, err := res.RowsAffected(); err != nil { - return 0, err - } else { - return nb, err - } -} diff --git a/libfic/flag_key.go b/libfic/flag_key.go deleted file mode 100644 index 40da9348..00000000 --- a/libfic/flag_key.go +++ /dev/null @@ -1,450 +0,0 @@ -package fic - -import ( - "bytes" - "errors" - "fmt" - "regexp" - "sort" - "strconv" - "strings" - "time" - - "golang.org/x/crypto/blake2b" -) - -// FlagKey represents a flag's challenge, stored as hash. -type FlagKey struct { - Id int `json:"id"` - // IdExercice is the identifier of the underlying challenge - IdExercice int64 `json:"idExercice"` - // Order is used to sort the flag between them - Order int8 `json:"order"` - // Label is the title of the flag as displayed to players - Label string `json:"label"` - // Type is the kind of flag - Type string `json:"type,omitempty"` - // Placeholder is a small piece of text that aims to add useful information like flag format, ... - Placeholder string `json:"placeholder"` - // Help is a description of the flag - Help string `json:"help"` - // Unit is another indication appended to the input - Unit string `json:"unit"` - // IgnoreCase indicates if the case is sensitive to case or not - IgnoreCase bool `json:"ignorecase"` - // NoTrim indicates if the flag should not be trimed to avoid mistakes - NoTrim bool `json:"notrim,omitempty"` - // Multiline indicates if the flag is stored on multiple lines - Multiline bool `json:"multiline"` - // CaptureRegexp extracts a subset of the player's answer, that will be checked. - CaptureRegexp *string `json:"capture_regexp"` - // SortReGroups indicates if groups resulting of the validator regexp should be sorted. - SortReGroups bool `json:"sort_re_grps"` - // Checksum is the expected hashed flag - Checksum []byte `json:"value"` - // ChoicesCost is the number of points lost to display choices. - ChoicesCost int32 `json:"choices_cost"` - // BonusGain makes the flag completion optionnal. If it is filled and correct, it gives some points. - BonusGain int32 `json:"bonus_gain"` -} - -func AnalyzeNumberFlag(t string) (min, max, step *float64, err error) { - fields := strings.Split(t, ",") - - if len(fields) != 4 || fields[0] != "number" { - err = errors.New("this is not a flag of type 'number'") - return - } - - if len(fields[1]) > 0 { - min = new(float64) - *min, err = strconv.ParseFloat(fields[1], 64) - if err != nil { - return - } - } - - if len(fields[2]) > 0 { - max = new(float64) - *max, err = strconv.ParseFloat(fields[2], 64) - if err != nil { - return - } - } - - step = new(float64) - if len(fields[3]) > 0 { - *step, err = strconv.ParseFloat(fields[3], 64) - if err != nil { - return - } - } else { - *step = 1 - } - - return -} - -func (k *FlagKey) AnalyzeFlagLabel() (label, separator string, ignoreorder bool, nblines uint64) { - if k.Label[0] == '`' && len(k.Label) > 4 { - label = k.Label[4:] - separator = string(k.Label[1]) - ignoreorder = k.Label[2] == 't' - nblines, _ = strconv.ParseUint(string(k.Label[3]), 10, 8) - } else { - label = k.Label - } - - return -} - -// GetFlagKeys returns a list of key's flags comming with the challenge. -func (e *Exercice) GetFlagKeys() ([]*FlagKey, error) { - if rows, err := DBQuery("SELECT id_flag, id_exercice, ordre, label, type, placeholder, help, unit, ignorecase, notrim, multiline, validator_regexp, sort_re_grps, cksum, choices_cost, bonus_gain FROM exercice_flags WHERE id_exercice = ?", e.Id); err != nil { - return nil, err - } else { - defer rows.Close() - - flags := []*FlagKey{} - for rows.Next() { - k := &FlagKey{} - k.IdExercice = e.Id - - if err := rows.Scan(&k.Id, &k.IdExercice, &k.Order, &k.Label, &k.Type, &k.Placeholder, &k.Help, &k.Unit, &k.IgnoreCase, &k.NoTrim, &k.Multiline, &k.CaptureRegexp, &k.SortReGroups, &k.Checksum, &k.ChoicesCost, &k.BonusGain); err != nil { - return nil, err - } - - flags = append(flags, k) - } - if err := rows.Err(); err != nil { - return nil, err - } - - return flags, nil - } -} - -// GetFlagKey returns a list of flags comming with the challenge. -func GetFlagKey(id int) (k *FlagKey, err error) { - k = &FlagKey{} - err = DBQueryRow("SELECT id_flag, id_exercice, ordre, label, type, placeholder, help, unit, ignorecase, notrim, multiline, validator_regexp, sort_re_grps, cksum, choices_cost, bonus_gain FROM exercice_flags WHERE id_flag = ?", id).Scan(&k.Id, &k.IdExercice, &k.Order, &k.Label, &k.Type, &k.Placeholder, &k.Help, &k.Unit, &k.IgnoreCase, &k.Multiline, &k.NoTrim, &k.CaptureRegexp, &k.SortReGroups, &k.Checksum, &k.ChoicesCost, &k.BonusGain) - return -} - -// GetFlagKey returns a flag. -func (e *Exercice) GetFlagKey(id int) (k *FlagKey, err error) { - k = &FlagKey{} - err = DBQueryRow("SELECT id_flag, id_exercice, ordre, label, type, placeholder, help, unit, ignorecase, notrim, multiline, validator_regexp, sort_re_grps, cksum, choices_cost, bonus_gain FROM exercice_flags WHERE id_flag = ? AND id_exercice = ?", id, e.Id).Scan(&k.Id, &k.IdExercice, &k.Order, &k.Label, &k.Type, &k.Placeholder, &k.Help, &k.Unit, &k.IgnoreCase, &k.NoTrim, &k.Multiline, &k.CaptureRegexp, &k.SortReGroups, &k.Checksum, &k.ChoicesCost, &k.BonusGain) - return -} - -// GetFlagKeyByLabel returns a flag matching the given label. -func (e *Exercice) GetFlagKeyByLabel(label string) (k *FlagKey, err error) { - k = &FlagKey{} - err = DBQueryRow("SELECT id_flag, id_exercice, ordre, label, type, placeholder, help, unit, ignorecase, notrim, multiline, validator_regexp, sort_re_grps, cksum, choices_cost, bonus_gain FROM exercice_flags WHERE label LIKE ? AND id_exercice = ?", label, e.Id).Scan(&k.Id, &k.IdExercice, &k.Order, &k.Label, &k.Type, &k.Placeholder, &k.Help, &k.Unit, &k.IgnoreCase, &k.NoTrim, &k.Multiline, &k.CaptureRegexp, &k.SortReGroups, &k.Checksum, &k.ChoicesCost, &k.BonusGain) - return -} - -// ComputeHashedFlag calculates the expected checksum for the given raw_value. -func ComputeHashedFlag(raw_value []byte, ignorecase bool, notrim bool, capture_regexp *string, sortregroups bool) (hash [blake2b.Size]byte, err error) { - if ignorecase { - raw_value = bytes.ToLower(raw_value) - } - - if !notrim { - raw_value = bytes.TrimSpace(raw_value) - } - - // Check that raw value passes through the regexp - if capture_regexp != nil { - if raw_value, err = ExecCaptureRegexp(*capture_regexp, raw_value, ignorecase, sortregroups); err != nil { - return - } - } - - // Check that the value is not empty - if len(raw_value) == 0 { - err = errors.New("empty flag after applying filters") - } - - hash = blake2b.Sum512(raw_value) - return -} - -func ExecCaptureRegexp(vre string, val []byte, ignorecase bool, sortregroups bool) ([]byte, error) { - if ignorecase { - vre = "(?i)" + vre - } - if re, err := regexp.Compile(vre); err != nil { - return val, err - } else if res := re.FindSubmatch(val); res == nil { - return val, errors.New("expected flag doesn't pass through the capture_regexp") - } else if sortregroups && len(res) > 2 { - var tab []string - for _, v := range res[1:] { - tab = append(tab, string(v)) - } - sort.Strings(tab) - return []byte(strings.Join(tab, "+")), nil - } else { - return bytes.Join(res[1:], []byte("+")), nil - } -} - -// AddRawFlagKey creates and fills a new struct FlagKey, from a non-hashed flag, and registers it into the database. -func (e *Exercice) AddRawFlagKey(name string, t string, placeholder string, ignorecase bool, multiline bool, notrim bool, capture_regexp *string, sortregroups bool, raw_value []byte, choicescost int32, bonusgain int32) (*FlagKey, error) { - hash, err := ComputeHashedFlag(raw_value, ignorecase, notrim, capture_regexp, sortregroups) - if err != nil { - return nil, err - } - - f := &FlagKey{ - Type: t, - Label: name, - Placeholder: placeholder, - IgnoreCase: ignorecase, - NoTrim: notrim, - Multiline: multiline, - CaptureRegexp: capture_regexp, - SortReGroups: sortregroups, - Checksum: hash[:], - ChoicesCost: choicescost, - BonusGain: bonusgain, - } - - _, err = f.Create(e) - return f, err -} - -// GetId returns the Flag identifier. -func (k *FlagKey) GetId() int { - return k.Id -} - -// RecoverId returns the Flag identifier as register in DB. -func (k *FlagKey) RecoverId() (Flag, error) { - if err := DBQueryRow("SELECT id_flag FROM exercice_flags WHERE label LIKE ? AND id_exercice = ?", k.Label, k.IdExercice).Scan(&k.Id); err != nil { - return nil, err - } else { - return k, err - } -} - -// NbTries returns the flag resolution statistics. -func (k *FlagKey) NbTries() (tries int64, err error) { - err = DBQueryRow("SELECT COUNT(*) AS tries FROM exercice_tries_flags WHERE id_flag = ?", k.Id).Scan(&tries) - return -} - -func (k *FlagKey) TeamsOnIt() ([]int64, error) { - if rows, err := DBQuery("SELECT DISTINCT M.id_team FROM exercice_tries_flags F INNER JOIN exercice_tries T ON T.id_try = F.id_try INNER JOIN teams M ON M.id_team = T.id_team WHERE id_flag = ?", k.Id); err != nil { - return nil, err - } else { - defer rows.Close() - - teams := []int64{} - for rows.Next() { - var idteam int64 - if err := rows.Scan(&idteam); err != nil { - return nil, err - } - teams = append(teams, idteam) - } - - return teams, nil - } -} - -func (k *FlagKey) DeleteTries() error { - if rows, err := DBQuery("SELECT id_try FROM exercice_tries_flags WHERE id_flag = ?", k.Id); err != nil { - return err - } else { - defer rows.Close() - - for rows.Next() { - var idtry int64 - err = rows.Scan(&idtry) - if err != nil { - return err - } - - _, err = DBExec("DELETE FROM exercice_tries WHERE id_try = ?", idtry) - if err != nil { - return err - } - } - - return nil - } -} - -// AddFlagKey creates and fills a new struct Flag, from a hashed flag, and registers it into the database. -func (k *FlagKey) Create(e *Exercice) (Flag, error) { - // Check the regexp compile - if k.CaptureRegexp != nil { - if _, err := regexp.Compile(*k.CaptureRegexp); err != nil { - return k, err - } - } - - if res, err := DBExec("INSERT INTO exercice_flags (id_exercice, ordre, label, type, placeholder, help, unit, ignorecase, notrim, multiline, validator_regexp, sort_re_grps, cksum, choices_cost, bonus_gain) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)", e.Id, k.Order, k.Label, k.Type, k.Placeholder, k.Help, k.Unit, k.IgnoreCase, k.NoTrim, k.Multiline, k.CaptureRegexp, k.SortReGroups, k.Checksum, k.ChoicesCost, k.BonusGain); err != nil { - return k, err - } else if kid, err := res.LastInsertId(); err != nil { - return k, err - } else { - k.Id = int(kid) - k.IdExercice = e.Id - return k, nil - } -} - -// ComputeChecksum calculates the checksum for a given value. -func (k *FlagKey) ComputeChecksum(val []byte) ([]byte, error) { - cksum, err := ComputeHashedFlag(val, k.IgnoreCase, k.NoTrim, k.CaptureRegexp, k.SortReGroups) - return cksum[:], err -} - -// Update applies modifications back to the database. -func (k *FlagKey) Update() (int64, error) { - if k.CaptureRegexp != nil { - if _, err := regexp.Compile(*k.CaptureRegexp); err != nil { - return 0, err - } - } - - if res, err := DBExec("UPDATE exercice_flags SET id_exercice = ?, ordre = ?, label = ?, type = ?, placeholder = ?, help = ?, unit = ?, ignorecase = ?, notrim = ?, multiline = ?, validator_regexp = ?, sort_re_grps = ?, cksum = ?, choices_cost = ?, bonus_gain = ? WHERE id_flag = ?", k.IdExercice, k.Order, k.Label, k.Type, k.Placeholder, k.Help, k.Unit, k.IgnoreCase, k.NoTrim, k.Multiline, k.CaptureRegexp, k.SortReGroups, k.Checksum, k.ChoicesCost, k.BonusGain, k.Id); err != nil { - return 0, err - } else if nb, err := res.RowsAffected(); err != nil { - return 0, err - } else { - return nb, err - } -} - -// Delete the flag from the database. -func (k *FlagKey) Delete() (int64, error) { - if _, err := DBExec("DELETE FROM exercice_files_okey_deps WHERE id_flag = ?", k.Id); err != nil { - return 0, err - } else if _, err := DBExec("DELETE FROM exercice_mcq_okey_deps WHERE id_flag_dep = ?", k.Id); err != nil { - return 0, err - } else if _, err := DBExec("DELETE FROM exercice_flags_omcq_deps WHERE id_flag = ?", k.Id); err != nil { - return 0, err - } else if _, err := DBExec("DELETE FROM exercice_flags_deps WHERE id_flag = ? OR id_flag_dep = ?", k.Id, k.Id); err != nil { - return 0, err - } else if _, err := DBExec("DELETE FROM exercice_hints_okey_deps WHERE id_flag_dep = ?", k.Id); err != nil { - return 0, err - } else if _, err := DBExec("DELETE FROM flag_choices WHERE id_flag = ?", k.Id); err != nil { - return 0, err - } else if res, err := DBExec("DELETE FROM exercice_flags WHERE id_flag = ?", k.Id); err != nil { - return 0, err - } else if nb, err := res.RowsAffected(); err != nil { - return 0, err - } else { - return nb, err - } -} - -func (k *FlagKey) GetOrder() int8 { - return k.Order -} - -// AddDepend insert a new dependency to a given flag. -func (k *FlagKey) AddDepend(j Flag) (err error) { - if d, ok := j.(*FlagKey); ok { - _, err = DBExec("INSERT INTO exercice_flags_deps (id_flag, id_flag_dep) VALUES (?, ?)", k.Id, d.Id) - } else if d, ok := j.(*MCQ); ok { - _, err = DBExec("INSERT INTO exercice_flags_omcq_deps (id_flag, id_mcq_dep) VALUES (?, ?)", k.Id, d.Id) - } else { - err = fmt.Errorf("dependancy type for key (%T) not implemented for this flag", j) - } - return -} - -// GetDepends retrieve the flag's dependency list. -func (k *FlagKey) GetDepends() ([]Flag, error) { - var deps []Flag - - if rows, err := DBQuery("SELECT id_flag_dep FROM exercice_flags_deps WHERE id_flag = ?", k.Id); err != nil { - return nil, err - } else { - defer rows.Close() - - for rows.Next() { - var d int - if err := rows.Scan(&d); err != nil { - return nil, err - } - deps = append(deps, &FlagKey{Id: d, IdExercice: k.IdExercice}) - } - if err := rows.Err(); err != nil { - return nil, err - } - } - - if rows, err := DBQuery("SELECT id_mcq_dep FROM exercice_flags_omcq_deps WHERE id_flag = ?", k.Id); err != nil { - return nil, err - } else { - defer rows.Close() - - for rows.Next() { - var d int - if err := rows.Scan(&d); err != nil { - return nil, err - } - deps = append(deps, &MCQ{Id: d, IdExercice: k.IdExercice}) - } - if err := rows.Err(); err != nil { - return nil, err - } - } - - return deps, nil -} - -// IsOptionnal to know if the flag can be omitted when validating the step. -func (k *FlagKey) IsOptionnal() bool { - return k.BonusGain != 0 -} - -// Check if the given val is the expected one for this flag. -func (k *FlagKey) Check(v interface{}) int { - var val []byte - - if va, ok := v.([]byte); !ok { - return -1 - } else { - val = va - } - - hash, err := k.ComputeChecksum(val) - if err != nil { - return 1 - } - if len(k.Checksum) != len(hash) { - return 1 - } - - for i := range hash { - if k.Checksum[i] != hash[i] { - return 1 - } - } - - return 0 -} - -// FoundBy registers in the database that the given Team solved the flag. -func (k *FlagKey) FoundBy(t *Team) (err error) { - _, err = DBExec("INSERT INTO flag_found (id_flag, id_team, time) VALUES (?, ?, ?)", k.Id, t.Id, time.Now()) - return -} - -// GetExercice returns the parent Exercice where this flag can be found. -func (k *FlagKey) GetExercice() (*Exercice, error) { - var eid int64 - if err := DBQueryRow("SELECT id_exercice FROM exercice_flags WHERE id_flag = ?", k.Id).Scan(&eid); err != nil { - return nil, err - } - - return GetExercice(eid) -} diff --git a/libfic/flag_label.go b/libfic/flag_label.go deleted file mode 100644 index eec13522..00000000 --- a/libfic/flag_label.go +++ /dev/null @@ -1,205 +0,0 @@ -package fic - -import ( - "fmt" -) - -// FlagLabel represents a flag's challenge, stored as hash. -type FlagLabel struct { - Id int `json:"id"` - // IdExercice is the identifier of the underlying challenge - IdExercice int64 `json:"idExercice"` - // Order is used to sort the flag between them - Order int8 `json:"order"` - // Label is the title of the flag as displayed to players - Label string `json:"label"` - // Variant stores the label color. - Variant string `json:"variant"` -} - -// GetFlagLabels returns a list of key's flags comming with the challenge. -func (e *Exercice) GetFlagLabels() ([]*FlagLabel, error) { - if rows, err := DBQuery("SELECT id_label, id_exercice, ordre, label, variant FROM exercice_flag_labels WHERE id_exercice = ?", e.Id); err != nil { - return nil, err - } else { - defer rows.Close() - - flags := []*FlagLabel{} - for rows.Next() { - k := &FlagLabel{} - k.IdExercice = e.Id - - if err := rows.Scan(&k.Id, &k.IdExercice, &k.Order, &k.Label, &k.Variant); err != nil { - return nil, err - } - - flags = append(flags, k) - } - if err := rows.Err(); err != nil { - return nil, err - } - - return flags, nil - } -} - -// GetFlagLabel returns a list of flags comming with the challenge. -func GetFlagLabel(id int) (k *FlagLabel, err error) { - k = &FlagLabel{} - err = DBQueryRow("SELECT id_label, id_exercice, ordre, label, variant FROM exercice_flag_labels WHERE id_flag = ?", id).Scan(&k.Id, &k.IdExercice, &k.Order, &k.Label, &k.Variant) - return -} - -// GetFlagLabelByLabel returns a flag matching the given label. -func (e *Exercice) GetFlagLabelByLabel(label string) (k *FlagLabel, err error) { - k = &FlagLabel{} - err = DBQueryRow("SELECT id_label, id_exercice, ordre, label, variant FROM exercice_flags WHERE type LIKE ? AND id_exercice = ?", label, e.Id).Scan(&k.Id, &k.IdExercice, &k.Order, &k.Label, &k.Variant) - return -} - -// GetId returns the Flag identifier. -func (k *FlagLabel) GetId() int { - return k.Id -} - -// RecoverId returns the Flag identifier as register in DB. -func (k *FlagLabel) RecoverId() (Flag, error) { - if err := DBQueryRow("SELECT id_label FROM exercice_flag_labels WHERE label LIKE ? AND id_exercice = ?", k.Label, k.IdExercice).Scan(&k.Id); err != nil { - return nil, err - } else { - return k, err - } -} - -func (k *FlagLabel) NbTries() (int64, error) { - return 0, nil -} - -func (k *FlagLabel) TeamsOnIt() ([]int64, error) { - return nil, nil -} - -func (k *FlagLabel) DeleteTries() error { - return nil -} - -// AddFlagLabel creates and fills a new struct Flag and registers it into the database. -func (k *FlagLabel) Create(e *Exercice) (Flag, error) { - if res, err := DBExec("INSERT INTO exercice_flag_labels (id_exercice, ordre, label, variant) VALUES (?, ?, ?, ?)", e.Id, k.Order, k.Label, k.Variant); err != nil { - return k, err - } else if kid, err := res.LastInsertId(); err != nil { - return k, err - } else { - k.Id = int(kid) - k.IdExercice = e.Id - return k, nil - } -} - -// Update applies modifications back to the database. -func (k *FlagLabel) Update() (int64, error) { - if res, err := DBExec("UPDATE exercice_flag_labels SET id_exercice = ?, ordre = ?, label = ?, variant = ? WHERE id_label = ?", k.IdExercice, k.Order, k.Label, k.Variant, k.Id); err != nil { - return 0, err - } else if nb, err := res.RowsAffected(); err != nil { - return 0, err - } else { - return nb, err - } -} - -// Delete the flag from the database. -func (k *FlagLabel) Delete() (int64, error) { - if _, err := DBExec("DELETE FROM exercice_flag_labels_deps WHERE id_label = ?", k.Id); err != nil { - return 0, err - } else if _, err := DBExec("DELETE FROM exercice_flag_labels_omcq_deps WHERE id_label = ?", k.Id); err != nil { - return 0, err - } else if res, err := DBExec("DELETE FROM exercice_flag_labels WHERE id_label = ?", k.Id); err != nil { - return 0, err - } else if nb, err := res.RowsAffected(); err != nil { - return 0, err - } else { - return nb, err - } -} - -func (k *FlagLabel) GetOrder() int8 { - return k.Order -} - -// AddDepend insert a new dependency to a given flag. -func (k *FlagLabel) AddDepend(j Flag) (err error) { - if d, ok := j.(*FlagKey); ok { - _, err = DBExec("INSERT INTO exercice_flag_labels_deps (id_label, id_flag_dep) VALUES (?, ?)", k.Id, d.Id) - } else if d, ok := j.(*MCQ); ok { - _, err = DBExec("INSERT INTO exercice_flag_labels_omcq_deps (id_label, id_mcq_dep) VALUES (?, ?)", k.Id, d.Id) - } else { - err = fmt.Errorf("dependancy type for key (%T) not implemented for this flag", j) - } - return -} - -// GetDepends retrieve the flag's dependency list. -func (k *FlagLabel) GetDepends() ([]Flag, error) { - var deps []Flag - - if rows, err := DBQuery("SELECT id_flag_dep FROM exercice_flag_labels_deps WHERE id_label = ?", k.Id); err != nil { - return nil, err - } else { - defer rows.Close() - - for rows.Next() { - var d int - if err := rows.Scan(&d); err != nil { - return nil, err - } - deps = append(deps, &FlagKey{Id: d, IdExercice: k.IdExercice}) - } - if err := rows.Err(); err != nil { - return nil, err - } - } - - if rows, err := DBQuery("SELECT id_mcq_dep FROM exercice_flag_labels_omcq_deps WHERE id_label = ?", k.Id); err != nil { - return nil, err - } else { - defer rows.Close() - - for rows.Next() { - var d int - if err := rows.Scan(&d); err != nil { - return nil, err - } - deps = append(deps, &MCQ{Id: d, IdExercice: k.IdExercice}) - } - if err := rows.Err(); err != nil { - return nil, err - } - } - - return deps, nil -} - -// IsOptionnal to know if the flag can be omitted when validating the step. -func (k *FlagLabel) IsOptionnal() bool { - return true -} - -// Check if the given val is the expected one for this flag. -func (k *FlagLabel) Check(v interface{}) int { - return 0 -} - -// FoundBy registers in the database that the given Team solved the flag. -func (k *FlagLabel) FoundBy(t *Team) error { - return nil -} - -// GetExercice returns the parent Exercice where this flag can be found. -func (k *FlagLabel) GetExercice() (*Exercice, error) { - var eid int64 - if err := DBQueryRow("SELECT id_exercice FROM exercice_flag_labels WHERE id_label = ?", k.Id).Scan(&eid); err != nil { - return nil, err - } - - return GetExercice(eid) -} diff --git a/libfic/generation.go b/libfic/generation.go deleted file mode 100644 index b78c3183..00000000 --- a/libfic/generation.go +++ /dev/null @@ -1,34 +0,0 @@ -package fic - -type GenerateType int - -const ( - GenPublic GenerateType = iota - GenEvents - GenTeam - GenTeams - GenThemes - GenTeamIssues -) - -type GenStruct struct { - Id string `json:"id"` - Type GenerateType `json:"type"` - TeamId int64 `json:"team_id,omitempty"` - ended chan error -} - -func (gs *GenStruct) GenEnded() chan error { - gs.ended = make(chan error, 1) - return gs.ended -} - -func (gs *GenStruct) GetEnded() chan error { - return gs.ended -} - -func (gs *GenStruct) End(err error) { - if gs.ended != nil { - gs.ended <- err - } -} diff --git a/libfic/hint.go b/libfic/hint.go deleted file mode 100644 index f6297817..00000000 --- a/libfic/hint.go +++ /dev/null @@ -1,209 +0,0 @@ -package fic - -import ( - "fmt" - "path" - "strings" -) - -// HintCoefficient is the current coefficient applied on its cost lost per showing -var HintCoefficient = 1.0 - -// EHint represents a challenge hint. -type EHint struct { - Id int64 `json:"id"` - // IdExercice is the identifier of the underlying challenge - IdExercice int64 `json:"idExercice"` - // Title is the hint name displayed to players - Title string `json:"title"` - // Content is the actual content of small text hints (mutually exclusive with File field) - // When File is filled, Content contains the hexadecimal file's hash. - Content string `json:"content"` - // File is path, relative to FilesDir where the file hint is stored (mutually exclusive with Content field) - File string `json:"file"` - // Cost is the amount of points the player will loose if it unlocks the hint - Cost int64 `json:"cost"` -} - -// treatHintContent reads Content to detect if this is a hint file in order to convert to such hint. -func treatHintContent(h *EHint) { - if strings.HasPrefix(h.Content, "$FILES") { - fpath := strings.TrimPrefix(h.Content, "$FILES") - h.Content = fpath[:128] - fpath = fpath[128:] - h.File = path.Join(FilesDir, fpath) - } -} - -// GetHint retrieves the hint with the given id. -func GetHint(id int64) (*EHint, error) { - h := &EHint{} - if err := DBQueryRow("SELECT id_hint, id_exercice, title, content, cost FROM exercice_hints WHERE id_hint = ?", id).Scan(&h.Id, &h.IdExercice, &h.Title, &h.Content, &h.Cost); err != nil { - return nil, err - } - treatHintContent(h) - - return h, nil -} - -func (h *EHint) TreatHintContent() { - treatHintContent(h) -} - -// GetHint retrieves the hint with the given id. -func (e *Exercice) GetHint(id int64) (*EHint, error) { - h := &EHint{} - if err := DBQueryRow("SELECT id_hint, id_exercice, title, content, cost FROM exercice_hints WHERE id_hint = ? AND id_exercice = ?", id, e.Id).Scan(&h.Id, &h.IdExercice, &h.Title, &h.Content, &h.Cost); err != nil { - return nil, err - } - treatHintContent(h) - - return h, nil -} - -// GetHintByTitle retrieves the hint with the given id. -func (e *Exercice) GetHintByTitle(id int64) (*EHint, error) { - h := &EHint{} - if err := DBQueryRow("SELECT id_hint, id_exercice, title, content, cost FROM exercice_hints WHERE title = ? AND id_exercice = ?", id, e.Id).Scan(&h.Id, &h.IdExercice, &h.Title, &h.Content, &h.Cost); err != nil { - return nil, err - } - treatHintContent(h) - - return h, nil -} - -// GetHints returns a list of hints comming with the challenge. -func (e *Exercice) GetHints() ([]*EHint, error) { - if rows, err := DBQuery("SELECT id_hint, title, content, cost FROM exercice_hints WHERE id_exercice = ?", e.Id); err != nil { - return nil, err - } else { - defer rows.Close() - - var hints []*EHint - for rows.Next() { - h := &EHint{} - h.IdExercice = e.Id - if err := rows.Scan(&h.Id, &h.Title, &h.Content, &h.Cost); err != nil { - return nil, err - } - treatHintContent(h) - hints = append(hints, h) - } - if err := rows.Err(); err != nil { - return nil, err - } - - return hints, nil - } -} - -// AddHint creates and fills a new struct EHint and registers it into the database. -func (e *Exercice) AddHint(title string, content string, cost int64) (*EHint, error) { - if res, err := DBExec("INSERT INTO exercice_hints (id_exercice, title, content, cost) VALUES (?, ?, ?, ?)", e.Id, title, content, cost); err != nil { - return nil, err - } else if hid, err := res.LastInsertId(); err != nil { - return nil, err - } else { - return &EHint{hid, e.Id, title, content, "", cost}, nil - } -} - -// Update applies modifications back to the database. -func (h *EHint) Update() (int64, error) { - if res, err := DBExec("UPDATE exercice_hints SET id_exercice = ?, title = ?, content = ?, cost = ? WHERE id_hint = ?", h.IdExercice, h.Title, h.Content, h.Cost, h.Id); err != nil { - return 0, err - } else if nb, err := res.RowsAffected(); err != nil { - return 0, err - } else { - return nb, err - } -} - -// Delete the hint from the database. -func (h *EHint) Delete() (int64, error) { - if res, err := DBExec("DELETE FROM exercice_hints WHERE id_hint = ?", h.Id); err != nil { - return 0, err - } else if nb, err := res.RowsAffected(); err != nil { - return 0, err - } else { - return nb, err - } -} - -// WipeHints deletes (only in the database, not on disk) hints coming with the challenge. -func (e *Exercice) WipeHints() (int64, error) { - if _, err := DBExec("DELETE FROM exercice_hints_okey_deps WHERE id_hint IN (SELECT id_hint FROM exercice_hints WHERE id_exercice = ?)", e.Id); err != nil { - return 0, err - } else if _, err := DBExec("DELETE FROM exercice_hints_omcq_deps WHERE id_hint IN (SELECT id_hint FROM exercice_hints WHERE id_exercice = ?)", e.Id); err != nil { - return 0, err - } else if res, err := DBExec("DELETE FROM exercice_hints WHERE id_exercice = ?", e.Id); err != nil { - return 0, err - } else if nb, err := res.RowsAffected(); err != nil { - return 0, err - } else { - return nb, err - } -} - -// AddDepend insert a new dependency to a given flag. -func (h *EHint) AddDepend(f Flag) (err error) { - if d, ok := f.(*FlagKey); ok { - _, err = DBExec("INSERT INTO exercice_hints_okey_deps (id_hint, id_flag_dep) VALUES (?, ?)", h.Id, d.Id) - } else if d, ok := f.(*MCQ); ok { - _, err = DBExec("INSERT INTO exercice_hints_omcq_deps (id_hint, id_mcq_dep) VALUES (?, ?)", h.Id, d.Id) - } else { - err = fmt.Errorf("dependancy type for key (%T) not implemented for this flag", f) - } - return -} - -// GetDepends retrieve the flag's dependency list. -func (h *EHint) GetDepends() ([]Flag, error) { - var deps []Flag - - if rows, err := DBQuery("SELECT id_flag_dep FROM exercice_hints_okey_deps WHERE id_hint = ?", h.Id); err != nil { - return nil, err - } else { - defer rows.Close() - - for rows.Next() { - var d int - if err := rows.Scan(&d); err != nil { - return nil, err - } - deps = append(deps, &FlagKey{Id: d, IdExercice: h.IdExercice}) - } - if err := rows.Err(); err != nil { - return nil, err - } - } - - if rows, err := DBQuery("SELECT id_mcq_dep FROM exercice_hints_omcq_deps WHERE id_hint = ?", h.Id); err != nil { - return nil, err - } else { - defer rows.Close() - - for rows.Next() { - var d int - if err := rows.Scan(&d); err != nil { - return nil, err - } - deps = append(deps, &MCQ{Id: d, IdExercice: h.IdExercice}) - } - if err := rows.Err(); err != nil { - return nil, err - } - } - - return deps, nil -} - -// GetExercice returns the parent Exercice where this hint can be found. -func (h *EHint) GetExercice() (*Exercice, error) { - var eid int64 - if err := DBQueryRow("SELECT id_exercice FROM exercice_hints WHERE id_hint = ?", h.Id).Scan(&eid); err != nil { - return nil, err - } - - return GetExercice(eid) -} diff --git a/libfic/key.go b/libfic/key.go new file mode 100644 index 00000000..a1a32d39 --- /dev/null +++ b/libfic/key.go @@ -0,0 +1,90 @@ +package fic + +import ( + "crypto/sha512" +) + +type Key struct { + Id int64 `json:"id"` + IdExercice int64 `json:"idExercice"` + Type string `json:"type"` + Value []byte `json:"value"` +} + +func (e Exercice) GetKeys() ([]Key, error) { + if rows, err := DBQuery("SELECT id_key, id_exercice, type, value FROM exercice_keys WHERE id_exercice = ?", e.Id); err != nil { + return nil, err + } else { + defer rows.Close() + + var keys = make([]Key, 0) + for rows.Next() { + var k Key + k.IdExercice = e.Id + + if err := rows.Scan(&k.Id, &k.IdExercice, &k.Type, &k.Value); err != nil { + return nil, err + } + keys = append(keys, k) + } + if err := rows.Err(); err != nil { + return nil, err + } + + return keys, nil + } +} + +func getHashedKey(raw_value string) []byte { + hash := sha512.Sum512([]byte(raw_value)) + return hash[:] +} + +func (e Exercice) AddRawKey(name string, raw_value string) (Key, error) { + return e.AddKey(name, getHashedKey(raw_value)) +} + +func (e Exercice) AddKey(name string, value []byte) (Key, error) { + if res, err := DBExec("INSERT INTO exercice_keys (id_exercice, type, value) VALUES (?, ?, ?)", e.Id, name, value); err != nil { + return Key{}, err + } else if kid, err := res.LastInsertId(); err != nil { + return Key{}, err + } else { + return Key{kid, e.Id, name, value}, nil + } +} + +func (k Key) Update() (int64, error) { + if res, err := DBExec("UPDATE exercice_keys SET id_exercice = ?, type = ?, value = ? WHERE id_key = ?", k.IdExercice, k.Type, k.Value, k.Id); err != nil { + return 0, err + } else if nb, err := res.RowsAffected(); err != nil { + return 0, err + } else { + return nb, err + } +} + +func (k Key) Delete() (int64, error) { + if res, err := DBExec("DELETE FROM exercice_keys WHERE id_key = ?", k.Id); err != nil { + return 0, err + } else if nb, err := res.RowsAffected(); err != nil { + return 0, err + } else { + return nb, err + } +} + +func (k Key) Check(val string) bool { + hash := getHashedKey(val) + if len(k.Value) != len(hash) { + return false + } + + for i := range hash { + if k.Value[i] != hash[i] { + return false + } + } + + return true +} diff --git a/libfic/mcq.go b/libfic/mcq.go deleted file mode 100644 index 3129ec80..00000000 --- a/libfic/mcq.go +++ /dev/null @@ -1,413 +0,0 @@ -package fic - -import ( - "errors" - "fmt" - "time" -) - -// MCQ represents a flag's challenge, in the form of checkbox. -type MCQ struct { - Id int `json:"id"` - // IdExercice is the identifier of the underlying challenge - IdExercice int64 `json:"idExercice"` - // Order is used to sort the flag between them - Order int8 `json:"order"` - // Title is the label of the question - Title string `json:"title"` - // Entries stores the set of proposed answers - Entries []*MCQ_entry `json:"entries"` -} - -// MCQ_entry represents a proposed response for a given MCQ. -type MCQ_entry struct { - Id int `json:"id"` - // Label is the text displayed to players as proposed answer - Label string `json:"label"` - // Response stores if expected checked state. - Response bool `json:"response"` -} - -// GetMCQ returns a list of flags comming with the challenge. -func GetMCQ(id int) (m *MCQ, err error) { - m = &MCQ{} - err = DBQueryRow("SELECT id_mcq, id_exercice, ordre, title FROM exercice_mcq WHERE id_mcq = ?", id).Scan(&m.Id, &m.IdExercice, &m.Order, &m.Title) - m.fillEntries() - return -} - -func (m *MCQ) fillEntries() ([]*MCQ_entry, error) { - if entries_rows, err := DBQuery("SELECT id_mcq_entry, label, response FROM mcq_entries WHERE id_mcq = ?", m.Id); err != nil { - return nil, err - } else { - defer entries_rows.Close() - - for entries_rows.Next() { - e := &MCQ_entry{} - - if err := entries_rows.Scan(&e.Id, &e.Label, &e.Response); err != nil { - return nil, err - } - - m.Entries = append(m.Entries, e) - } - } - - return m.Entries, nil -} - -// GetMCQ returns the MCQs coming with the challenge. -func (e *Exercice) GetMCQ() ([]*MCQ, error) { - if rows, err := DBQuery("SELECT id_mcq, id_exercice, ordre, title FROM exercice_mcq WHERE id_exercice = ?", e.Id); err != nil { - return nil, err - } else { - defer rows.Close() - - var mcqs = []*MCQ{} - for rows.Next() { - m := &MCQ{} - m.IdExercice = e.Id - - if err := rows.Scan(&m.Id, &m.IdExercice, &m.Order, &m.Title); err != nil { - return nil, err - } - - m.fillEntries() - - mcqs = append(mcqs, m) - } - if err := rows.Err(); err != nil { - return nil, err - } - - return mcqs, nil - } -} - -// GetMCQById returns a MCQs. -func (e *Exercice) GetMCQById(id int) (m *MCQ, err error) { - m = &MCQ{} - err = DBQueryRow("SELECT id_mcq, id_exercice, ordre, title FROM exercice_mcq WHERE id_mcq = ? AND id_exercice = ?", id, e.Id).Scan(&m.Id, &m.IdExercice, &m.Order, &m.Title) - m.fillEntries() - return -} - -// GetMCQbyChoice returns the MCQ corresponding to a choice ID. -func GetMCQbyChoice(cid int) (m *MCQ, c *MCQ_entry, err error) { - m = &MCQ{} - if errr := DBQueryRow("SELECT id_mcq, id_exercice, ordre, title FROM exercice_mcq WHERE id_mcq = (SELECT id_mcq FROM mcq_entries WHERE id_mcq_entry = ?)", cid).Scan(&m.Id, &m.IdExercice, &m.Order, &m.Title); errr != nil { - return nil, nil, errr - } - - if entries_rows, errr := DBQuery("SELECT id_mcq_entry, label, response FROM mcq_entries WHERE id_mcq = ?", m.Id); errr != nil { - return nil, nil, errr - } else { - defer entries_rows.Close() - - for entries_rows.Next() { - e := &MCQ_entry{} - - if err = entries_rows.Scan(&e.Id, &e.Label, &e.Response); err != nil { - return - } - - if e.Id == cid { - c = e - } - - m.Entries = append(m.Entries, e) - } - } - - return -} - -// GetId returns the MCQ identifier. -func (m *MCQ) GetId() int { - return m.Id -} - -// RecoverId returns the MCQ identifier as register in DB. -func (m *MCQ) RecoverId() (Flag, error) { - if err := DBQueryRow("SELECT id_mcq FROM exercice_mcq WHERE title LIKE ? AND id_exercice = ?", m.Title, m.IdExercice).Scan(&m.Id); err != nil { - return nil, err - } else { - return m, err - } -} - -// NbTries returns the MCQ resolution statistics. -func (m *MCQ) NbTries() (tries int64, err error) { - err = DBQueryRow("SELECT COUNT(*) AS tries FROM exercice_tries_mcq WHERE id_mcq = ?", m.Id).Scan(&tries) - return -} - -func (m *MCQ) TeamsOnIt() ([]int64, error) { - if rows, err := DBQuery("SELECT DISTINCT M.id_team FROM exercice_tries_mcq F INNER JOIN exercice_tries T ON T.id_try = F.id_try INNER JOIN teams M ON M.id_team = T.id_team WHERE id_mcq = ?", m.Id); err != nil { - return nil, err - } else { - defer rows.Close() - - teams := []int64{} - for rows.Next() { - var idteam int64 - if err := rows.Scan(&idteam); err != nil { - return nil, err - } - teams = append(teams, idteam) - } - - return teams, nil - } -} - -func (m *MCQ) DeleteTries() error { - if rows, err := DBQuery("SELECT id_try FROM exercice_tries_mcq WHERE id_mcq = ?", m.Id); err != nil { - return err - } else { - defer rows.Close() - - for rows.Next() { - var idtry int64 - err = rows.Scan(&idtry) - if err != nil { - return err - } - - _, err = DBExec("DELETE FROM exercice_tries WHERE id_try = ?", idtry) - if err != nil { - return err - } - } - - return nil - } -} - -// Create registers a MCQ into the database and recursively add its entries. -func (m *MCQ) Create(e *Exercice) (Flag, error) { - if res, err := DBExec("INSERT INTO exercice_mcq (id_exercice, ordre, title) VALUES (?, ?, ?)", e.Id, m.Order, m.Title); err != nil { - return m, err - } else if qid, err := res.LastInsertId(); err != nil { - return m, err - } else { - m.Id = int(qid) - m.IdExercice = e.Id - - // Add entries - for k, entry := range m.Entries { - if entry, err = m.AddEntry(entry); err != nil { - return m, err - } else { - m.Entries[k] = entry - } - } - - return m, nil - } -} - -// Update applies modifications back to the database. -func (m *MCQ) Update() (int64, error) { - if res, err := DBExec("UPDATE exercice_mcq SET id_exercice = ?, ordre = ?, title = ? WHERE id_mcq = ?", m.IdExercice, m.Order, m.Title, m.Id); err != nil { - return 0, err - } else if nb, err := res.RowsAffected(); err != nil { - return 0, err - } else { - return nb, err - } -} - -// Delete the MCQ from the database. -func (m *MCQ) Delete() (int64, error) { - if _, err := DBExec("DELETE FROM exercice_files_omcq_deps WHERE id_mcq = ?", m.Id); err != nil { - return 0, err - } else if _, err := DBExec("DELETE FROM exercice_mcq_okey_deps WHERE id_mcq = ?", m.Id); err != nil { - return 0, err - } else if _, err := DBExec("DELETE FROM exercice_mcq_omcq_deps WHERE id_mcq = ?", m.Id); err != nil { - return 0, err - } else if _, err := DBExec("DELETE FROM exercice_mcq_omcq_deps WHERE id_mcq_dep = ?", m.Id); err != nil { - return 0, err - } else if _, err := DBExec("DELETE FROM exercice_flags_omcq_deps WHERE id_mcq_dep = ?", m.Id); err != nil { - return 0, err - } else if _, err := DBExec("DELETE FROM exercice_hints_omcq_deps WHERE id_mcq_dep = ?", m.Id); err != nil { - return 0, err - } else if res, err := DBExec("DELETE FROM exercice_mcq WHERE id_mcq = ?", m.Id); err != nil { - return 0, err - } else if nb, err := res.RowsAffected(); err != nil { - return 0, err - } else { - return nb, err - } -} - -// AddEntry creates and fills a new struct MCQ_entry and registers it into the database. -func (m *MCQ) AddEntry(e *MCQ_entry) (*MCQ_entry, error) { - if res, err := DBExec("INSERT INTO mcq_entries (id_mcq, label, response) VALUES (?, ?, ?)", m.Id, e.Label, e.Response); err != nil { - return e, err - } else if nid, err := res.LastInsertId(); err != nil { - return e, err - } else { - e.Id = int(nid) - return e, nil - } -} - -// Update applies modifications back to the database. -func (n *MCQ_entry) Update() (int64, error) { - if res, err := DBExec("UPDATE mcq_entries SET label = ?, response = ? WHERE id_mcq_entry = ?", n.Label, n.Response, n.Id); err != nil { - return 0, err - } else if nb, err := res.RowsAffected(); err != nil { - return 0, err - } else { - return nb, err - } -} - -// Delete the MCQ entry from the database. -func (n *MCQ_entry) Delete() (int64, error) { - if res, err := DBExec("DELETE FROM mcq_entries WHERE id_mcq_entry = ?", n.Id); err != nil { - return 0, err - } else if nb, err := res.RowsAffected(); err != nil { - return 0, err - } else { - return nb, err - } -} - -// WipeMCQs deletes MCQs coming with the challenge. -func (e *Exercice) WipeMCQs() (int64, error) { - if _, err := DBExec("DELETE FROM exercice_files_omcq_deps WHERE id_mcq IN (SELECT id_mcq FROM exercice_mcq WHERE id_exercice = ?)", e.Id); err != nil { - return 0, err - } else if _, err := DBExec("DELETE FROM mcq_entries WHERE id_mcq IN (SELECT id_mcq FROM exercice_mcq WHERE id_exercice = ?);", e.Id); err != nil { - return 0, err - } else if _, err := DBExec("DELETE FROM exercice_flags_omcq_deps WHERE id_mcq_dep IN (SELECT id_mcq FROM exercice_mcq WHERE id_exercice = ?);", e.Id); err != nil { - return 0, err - } else if _, err := DBExec("DELETE FROM exercice_mcq_okey_deps WHERE id_mcq IN (SELECT id_mcq FROM exercice_mcq WHERE id_exercice = ?);", e.Id); err != nil { - return 0, err - } else if _, err := DBExec("DELETE FROM exercice_mcq_omcq_deps WHERE id_mcq IN (SELECT id_mcq FROM exercice_mcq WHERE id_exercice = ?);", e.Id); err != nil { - return 0, err - } else if _, err := DBExec("DELETE FROM exercice_mcq_omcq_deps WHERE id_mcq_dep IN (SELECT id_mcq FROM exercice_mcq WHERE id_exercice = ?);", e.Id); err != nil { - return 0, err - } else if res, err := DBExec("DELETE FROM exercice_mcq WHERE id_exercice = ?;", e.Id); err != nil { - return 0, err - } else if nb, err := res.RowsAffected(); err != nil { - return 0, err - } else { - return nb, err - } -} - -func (m *MCQ) GetOrder() int8 { - return m.Order -} - -// AddDepend insert a new dependency to a given flag. -func (m *MCQ) AddDepend(j Flag) (err error) { - if d, ok := j.(*FlagKey); ok { - _, err = DBExec("INSERT INTO exercice_mcq_okey_deps (id_mcq, id_flag_dep) VALUES (?, ?)", m.Id, d.Id) - } else if d, ok := j.(*MCQ); ok { - _, err = DBExec("INSERT INTO exercice_mcq_omcq_deps (id_mcq, id_mcq_dep) VALUES (?, ?)", m.Id, d.Id) - } else { - err = errors.New("dependancy type not implemented for this flag") - } - return -} - -// GetDepends retrieve the flag's dependency list. -func (m *MCQ) GetDepends() ([]Flag, error) { - deps := []Flag{} - - if rows, err := DBQuery("SELECT id_flag_dep FROM exercice_mcq_okey_deps WHERE id_mcq = ?", m.Id); err != nil { - return nil, err - } else { - defer rows.Close() - - for rows.Next() { - var d int - if err := rows.Scan(&d); err != nil { - return nil, err - } - deps = append(deps, &FlagKey{Id: d, IdExercice: m.IdExercice}) - } - if err := rows.Err(); err != nil { - return nil, err - } - - } - - if rows, err := DBQuery("SELECT id_mcq_dep FROM exercice_mcq_omcq_deps WHERE id_mcq = ?", m.Id); err != nil { - return nil, err - } else { - defer rows.Close() - - for rows.Next() { - var d int - if err := rows.Scan(&d); err != nil { - return nil, err - } - deps = append(deps, &MCQ{Id: d, IdExercice: m.IdExercice}) - } - if err := rows.Err(); err != nil { - return nil, err - } - - } - - return deps, nil -} - -// GetJustifiedFlag searchs for a flag in the scope of the given exercice. -func (c *MCQ_entry) GetJustifiedFlag(e *Exercice) (*FlagKey, error) { - return e.GetFlagKeyByLabel(fmt.Sprintf("\\%%%d\\%%%%", c.Id)) -} - -// IsOptionnal to know if the flag can be omitted when validating the step. -func (m *MCQ) IsOptionnal() bool { - return false -} - -// Check if the given vals contains at least a response for the given MCQ. -func (m *MCQ) HasOneEntry(v interface{}) bool { - var vals map[int]bool - if va, ok := v.(map[int]bool); !ok { - return false - } else { - vals = va - } - - for _, n := range m.Entries { - if _, ok := vals[n.Id]; ok { - return true - } - } - - return false -} - -// Check if the given vals are the expected ones to validate this flag. -func (m *MCQ) Check(v interface{}) int { - var vals map[int]bool - if va, ok := v.(map[int]bool); !ok { - return -1 - } else { - vals = va - } - - diff := 0 - - for _, n := range m.Entries { - if v, ok := vals[n.Id]; (ok || !n.Response) && v == n.Response { - continue - } - diff += 1 - } - - return diff -} - -// FoundBy registers in the database that the given Team solved the MCQ. -func (m *MCQ) FoundBy(t *Team) (err error) { - _, err = DBExec("INSERT INTO mcq_found (id_mcq, id_team, time) VALUES (?, ?, ?)", m.Id, t.Id, time.Now()) - return -} diff --git a/libfic/mcq_justification.go b/libfic/mcq_justification.go deleted file mode 100644 index f00291f4..00000000 --- a/libfic/mcq_justification.go +++ /dev/null @@ -1,31 +0,0 @@ -package fic - -import ( - "errors" - "strconv" - "strings" -) - -type FlagMCQLabel struct { - Label string - IdChoice int -} - -// IsMCQJustification tells you if this key represent a justification from a MCQ. -func (k FlagKey) IsMCQJustification() bool { - return len(k.Label) > 0 && k.Label[0] == '%' -} - -// GetMCQJustification returns the structure corresponding to the given flag. -func (k FlagKey) GetMCQJustification() (fl FlagMCQLabel, err error) { - spl := strings.Split(k.Label, "%") - if len(spl) >= 3 && len(spl[0]) == 0 { - var idChoice int64 - idChoice, err = strconv.ParseInt(spl[1], 10, 32) - fl.IdChoice = int(idChoice) - fl.Label = strings.Join(spl[2:], "%") - } else { - err = errors.New("this is not a MCQ justification") - } - return -} diff --git a/libfic/member.go b/libfic/member.go index 092f8254..7dad3c4c 100644 --- a/libfic/member.go +++ b/libfic/member.go @@ -2,7 +2,6 @@ package fic import () -// Member represents a team member type Member struct { Id int64 `json:"id"` Firstname string `json:"firstname"` @@ -11,16 +10,15 @@ type Member struct { Company string `json:"company"` } -// GetMembers retrieves the members of the Team -func (t *Team) GetMembers() ([]*Member, error) { +func (t Team) GetMembers() ([]Member, error) { if rows, err := DBQuery("SELECT id_member, firstname, lastname, nickname, company FROM team_members WHERE id_team = ?", t.Id); err != nil { return nil, err } else { defer rows.Close() - var members []*Member + var members = make([]Member, 0) for rows.Next() { - m := &Member{} + var m Member if err := rows.Scan(&m.Id, &m.Firstname, &m.Lastname, &m.Nickname, &m.Company); err != nil { return nil, err } @@ -34,28 +32,18 @@ func (t *Team) GetMembers() ([]*Member, error) { } } -// AddMember creates and fills a new struct Member and registers it into the database. -func (t *Team) AddMember(firstname string, lastname string, nickname string, company string) (*Member, error) { +func (t Team) AddMember(firstname string, lastname string, nickname string, company string) (Member, error) { if res, err := DBExec("INSERT INTO team_members (id_team, firstname, lastname, nickname, company) VALUES (?, ?, ?, ?, ?)", t.Id, firstname, lastname, nickname, company); err != nil { - return nil, err + return Member{}, err } else if mid, err := res.LastInsertId(); err != nil { - return nil, err + return Member{}, err } else { - return &Member{mid, firstname, lastname, nickname, company}, nil + return Member{mid, firstname, lastname, nickname, company}, nil } } -// GainMember associates (or registers, it if it doesn't exists yet) a member to the team. -func (t *Team) GainMember(m *Member) error { - if m.Id == 0 { - if res, err := DBExec("INSERT INTO team_members (id_team, firstname, lastname, nickname, company) VALUES (?, ?, ?, ?, ?)", t.Id, m.Firstname, m.Lastname, m.Nickname, m.Company); err != nil { - return err - } else if _, err := res.LastInsertId(); err != nil { - return err - } else { - return nil - } - } else if res, err := DBExec("UPDATE team_members SET id_team = ? WHERE id_member = ?", t.Id, m.Id); err != nil { +func (t Team) GainMember(m Member) error { + if res, err := DBExec("UPDATE team_members SET id_team = ? WHERE id_member = ?", t.Id, m.Id); err != nil { return err } else if _, err := res.RowsAffected(); err != nil { return err @@ -64,8 +52,7 @@ func (t *Team) GainMember(m *Member) error { } } -// Update applies modifications back to the database. -func (m *Member) Update() (int64, error) { +func (m Member) Update() (int64, error) { if res, err := DBExec("UPDATE team_members SET firstname = ?, lastname = ?, nickname = ?, company = ? WHERE id_member = ?", m.Firstname, m.Lastname, m.Nickname, m.Company, m.Id); err != nil { return 0, err } else if nb, err := res.RowsAffected(); err != nil { @@ -75,8 +62,7 @@ func (m *Member) Update() (int64, error) { } } -// Delete the member from the database. -func (m *Member) Delete() (int64, error) { +func (m Member) Delete() (int64, error) { if res, err := DBExec("DELETE FROM team_members WHERE id_member = ?", m.Id); err != nil { return 0, err } else if nb, err := res.RowsAffected(); err != nil { @@ -85,14 +71,3 @@ func (m *Member) Delete() (int64, error) { return nb, err } } - -// ClearMembers deletes members in the team. -func (t *Team) ClearMembers() (int64, error) { - if res, err := DBExec("DELETE FROM team_members WHERE id_team = ?", t.Id); err != nil { - return 0, err - } else if nb, err := res.RowsAffected(); err != nil { - return 0, err - } else { - return nb, err - } -} diff --git a/libfic/password.go b/libfic/password.go deleted file mode 100644 index 47a5633b..00000000 --- a/libfic/password.go +++ /dev/null @@ -1,31 +0,0 @@ -package fic - -import ( - "crypto/rand" - "encoding/base64" - "strings" -) - -func GeneratePassword() (password string, err error) { - // This will make a 12 chars long password - b := make([]byte, 9) - - if _, err = rand.Read(b); err != nil { - return - } - - password = base64.StdEncoding.EncodeToString(b) - - // Avoid hard to read characters - for _, i := range [][2]string{ - {"v", "*"}, {"u", "("}, - {"l", "%"}, {"1", "?"}, - {"o", "@"}, {"O", "!"}, {"0", ">"}, - // This one is to avoid problem with openssl - {"/", "^"}, - } { - password = strings.Replace(password, i[0], i[1], -1) - } - - return -} diff --git a/libfic/qa.go b/libfic/qa.go deleted file mode 100644 index bfd8b990..00000000 --- a/libfic/qa.go +++ /dev/null @@ -1,286 +0,0 @@ -package fic - -import ( - "database/sql" - "time" -) - -// QAQuery represents a QA query. -type QAQuery struct { - Id int64 `json:"id"` - IdExercice int64 `json:"id_exercice"` - IdTeam *int64 `json:"id_team"` - User string `json:"user"` - Creation time.Time `json:"creation"` - State string `json:"state"` - Subject string `json:"subject"` - Solved *time.Time `json:"solved,omitempty"` - Closed *time.Time `json:"closed,omitempty"` - Exported *int64 `json:"exported,omitempty"` -} - -// GetQAQuery retrieves the query with the given identifier. -func GetQAQuery(id int64) (q *QAQuery, err error) { - q = &QAQuery{} - err = DBQueryRow("SELECT id_qa, id_exercice, id_team, authuser, creation, state, subject, solved, closed, exported FROM exercices_qa WHERE id_qa = ?", id).Scan(&q.Id, &q.IdExercice, &q.IdTeam, &q.User, &q.Creation, &q.State, &q.Subject, &q.Solved, &q.Closed, &q.Exported) - return -} - -// GetQAQueries returns a list of all QAQuery registered in the database. -func GetQAQueries() (res []*QAQuery, err error) { - var rows *sql.Rows - if rows, err = DBQuery("SELECT id_qa, id_exercice, id_team, authuser, creation, state, subject, solved, closed, exported FROM exercices_qa"); err != nil { - return - } - defer rows.Close() - - for rows.Next() { - q := &QAQuery{} - if err = rows.Scan(&q.Id, &q.IdExercice, &q.IdTeam, &q.User, &q.Creation, &q.State, &q.Subject, &q.Solved, &q.Closed, &q.Exported); err != nil { - return - } - res = append(res, q) - } - err = rows.Err() - - return -} - -// GetQAQueries returns a list of all QAQuery registered for the Exercice. -func (e *Exercice) GetQAQueries() (res []*QAQuery, err error) { - var rows *sql.Rows - if rows, err = DBQuery("SELECT id_qa, id_exercice, id_team, authuser, creation, state, subject, solved, closed, exported FROM exercices_qa WHERE id_exercice = ?", e.Id); err != nil { - return nil, err - } - defer rows.Close() - - for rows.Next() { - q := &QAQuery{} - if err = rows.Scan(&q.Id, &q.IdExercice, &q.IdTeam, &q.User, &q.Creation, &q.State, &q.Subject, &q.Solved, &q.Closed, &q.Exported); err != nil { - return - } - res = append(res, q) - } - err = rows.Err() - - return -} - -// GetQAQueries returns a list of all QAQuery registered for the Exercice. -func (t *Team) GetQAQueries() (res []*QAQuery, err error) { - var rows *sql.Rows - if rows, err = DBQuery("SELECT id_qa, id_exercice, id_team, authuser, creation, state, subject, solved, closed, exported FROM exercices_qa WHERE id_team = ?", t.Id); err != nil { - return nil, err - } - defer rows.Close() - - for rows.Next() { - q := &QAQuery{} - if err = rows.Scan(&q.Id, &q.IdExercice, &q.IdTeam, &q.User, &q.Creation, &q.State, &q.Subject, &q.Solved, &q.Closed, &q.Exported); err != nil { - return - } - res = append(res, q) - } - err = rows.Err() - - return -} - -// GetQAQuery retrieves the query with the given identifier. -func (e *Exercice) GetQAQuery(id int64) (q *QAQuery, err error) { - q = &QAQuery{} - err = DBQueryRow("SELECT id_qa, id_exercice, id_team, authuser, creation, state, subject, solved, closed, exported FROM exercices_qa WHERE id_qa = ? AND id_exercice = ?", id, e.Id).Scan(&q.Id, &q.IdExercice, &q.IdTeam, &q.User, &q.Creation, &q.State, &q.Subject, &q.Solved, &q.Closed, &q.Exported) - return -} - -// NewQAQuery creates and fills a new struct QAQuery and registers it into the database. -func (e *Exercice) NewQAQuery(subject string, teamId *int64, user string, state string, solved *time.Time) (*QAQuery, error) { - if res, err := DBExec("INSERT INTO exercices_qa (id_exercice, id_team, authuser, creation, state, subject, solved) VALUES (?, ?, ?, ?, ?, ?, ?)", e.Id, teamId, user, time.Now(), state, subject, solved); err != nil { - return nil, err - } else if qid, err := res.LastInsertId(); err != nil { - return nil, err - } else { - return &QAQuery{qid, e.Id, teamId, user, time.Now(), state, subject, solved, nil, nil}, nil - } -} - -// Update applies modifications back to the database. -func (q *QAQuery) Update() (int64, error) { - if res, err := DBExec("UPDATE exercices_qa SET subject = ?, id_team = ?, authuser = ?, id_exercice = ?, creation = ?, state = ?, solved = ?, closed = ?, exported = ? WHERE id_qa = ?", q.Subject, q.IdTeam, q.User, q.IdExercice, q.Creation, q.State, q.Solved, q.Closed, q.Exported, q.Id); err != nil { - return 0, err - } else if nb, err := res.RowsAffected(); err != nil { - return 0, err - } else { - return nb, err - } -} - -// Delete the query from the database. -func (q *QAQuery) Delete() (int64, error) { - if _, err := DBExec("DELETE FROM qa_comments WHERE id_qa = ?", q.Id); err != nil { - return 0, err - } else if res, err := DBExec("DELETE FROM exercices_qa WHERE id_qa = ?", q.Id); err != nil { - return 0, err - } else if nb, err := res.RowsAffected(); err != nil { - return 0, err - } else { - return nb, err - } -} - -// ClearQAQueries removes all queries from database. -func ClearQAQueries() (int64, error) { - if res, err := DBExec("DELETE FROM exercices_qa"); err != nil { - return 0, err - } else if nb, err := res.RowsAffected(); err != nil { - return 0, err - } else { - return nb, err - } -} - -// QAComment represents some text describing a QAQuery. -type QAComment struct { - Id int64 `json:"id"` - IdTeam *int64 `json:"id_team"` - User string `json:"user"` - Date time.Time `json:"date"` - Content string `json:"content"` -} - -// GetComments returns a list of all descriptions stored in the database for the QAQuery. -func (q *QAQuery) GetComments() (res []*QAComment, err error) { - var rows *sql.Rows - if rows, err = DBQuery("SELECT id_comment, id_team, authuser, date, content FROM qa_comments WHERE id_qa = ?", q.Id); err != nil { - return nil, err - } - defer rows.Close() - - for rows.Next() { - c := &QAComment{} - if err = rows.Scan(&c.Id, &c.IdTeam, &c.User, &c.Date, &c.Content); err != nil { - return - } - res = append(res, c) - } - err = rows.Err() - - return -} - -// GetComment returns the comment stored in the database for the QAQuery. -func (q *QAQuery) GetComment(id int64) (c *QAComment, err error) { - c = &QAComment{} - err = DBQueryRow("SELECT id_comment, id_team, authuser, date, content FROM qa_comments WHERE id_comment = ? AND id_qa = ?", id, q.Id).Scan(&c.Id, &c.IdTeam, &c.User, &c.Date, &c.Content) - return -} - -// AddComment append in the database a new description; then returns the corresponding structure. -func (q *QAQuery) AddComment(content string, teamId *int64, user string) (*QAComment, error) { - if res, err := DBExec("INSERT INTO qa_comments (id_qa, id_team, authuser, date, content) VALUES (?, ?, ?, ?, ?)", q.Id, teamId, user, time.Now(), content); err != nil { - return nil, err - } else if cid, err := res.LastInsertId(); err != nil { - return nil, err - } else { - return &QAComment{cid, teamId, user, time.Now(), content}, nil - } -} - -// Update applies modifications back to the database -func (c *QAComment) Update() (int64, error) { - if res, err := DBExec("UPDATE qa_comments SET id_team = ?, authuser = ?, date = ?, content = ? WHERE id_comment = ?", c.IdTeam, c.User, c.Date, c.Content, c.Id); err != nil { - return 0, err - } else if nb, err := res.RowsAffected(); err != nil { - return 0, err - } else { - return nb, err - } -} - -// Delete the comment in the database. -func (c *QAComment) Delete() (int64, error) { - if res, err := DBExec("DELETE FROM qa_comments WHERE id_comment = ?", c.Id); err != nil { - return 0, err - } else if nb, err := res.RowsAffected(); err != nil { - return 0, err - } else { - return nb, err - } -} - -type QATodo struct { - Id int64 `json:"id"` - IdTeam int64 `json:"id_team,omitempty"` - IdExercice int64 `json:"id_exercice"` -} - -func (t *Team) GetQATodo() (res []*QATodo, err error) { - var rows *sql.Rows - if rows, err = DBQuery("SELECT id_todo, id_exercice FROM teams_qa_todo WHERE id_team = ?", t.Id); err != nil { - return nil, err - } - defer rows.Close() - - for rows.Next() { - t := &QATodo{} - if err = rows.Scan(&t.Id, &t.IdExercice); err != nil { - return - } - res = append(res, t) - } - err = rows.Err() - - return -} - -func (t *Team) NewQATodo(idExercice int64) (*QATodo, error) { - if res, err := DBExec("INSERT INTO teams_qa_todo (id_team, id_exercice) VALUES (?, ?)", t.Id, idExercice); err != nil { - return nil, err - } else if tid, err := res.LastInsertId(); err != nil { - return nil, err - } else { - return &QATodo{tid, t.Id, idExercice}, nil - } -} - -// Delete the comment in the database. -func (t *QATodo) Delete() (int64, error) { - if res, err := DBExec("DELETE FROM teams_qa_todo WHERE id_todo = ?", t.Id); err != nil { - return 0, err - } else if nb, err := res.RowsAffected(); err != nil { - return 0, err - } else { - return nb, err - } -} - -// QAView - -func (t *Team) GetQAView() (res []*QATodo, err error) { - var rows *sql.Rows - if rows, err = DBQuery("SELECT id_view, id_exercice FROM teams_qa_view WHERE id_team = ?", t.Id); err != nil { - return nil, err - } - defer rows.Close() - - for rows.Next() { - t := &QATodo{} - if err = rows.Scan(&t.Id, &t.IdExercice); err != nil { - return - } - res = append(res, t) - } - err = rows.Err() - - return -} - -func (t *Team) NewQAView(idExercice int64) (*QATodo, error) { - if res, err := DBExec("INSERT INTO teams_qa_view (id_team, id_exercice) VALUES (?, ?)", t.Id, idExercice); err != nil { - return nil, err - } else if tid, err := res.LastInsertId(); err != nil { - return nil, err - } else { - return &QATodo{tid, t.Id, idExercice}, nil - } -} diff --git a/libfic/reset.go b/libfic/reset.go deleted file mode 100644 index 77d59089..00000000 --- a/libfic/reset.go +++ /dev/null @@ -1,99 +0,0 @@ -package fic - -import ( - "context" -) - -// truncateTable performs an insecure wipe on the given tables. -func truncateTable(tables ...string) error { - if tx, err := db.BeginTx(context.TODO(), nil); err != nil { - return err - } else { - if _, err := tx.Exec("SET FOREIGN_KEY_CHECKS = 0;"); err != nil { - return err - } - for _, table := range tables { - if _, err := tx.Exec("TRUNCATE TABLE " + table + ";"); err != nil { - return err - } - } - if _, err := tx.Exec("SET FOREIGN_KEY_CHECKS = 1;"); err != nil { - return err - } - if err := tx.Commit(); err != nil { - return err - } - } - return nil -} - -// ResetAnnexes resets all tables containing annexe info like events, claims and qa. -func ResetAnnexes() error { - return truncateTable( - "claim_descriptions", - "claims", - "events", - "exercices_qa", - "qa_comments", - "teams_qa_todo", - "teams_qa_view", - ) -} - -// ResetGame resets all tables containing team attempts and solves. -func ResetGame() error { - return truncateTable( - "team_wchoices", - "team_hints", - "flag_found", - "mcq_found", - "exercice_solved", - "exercice_tries", - ) -} - -// ResetExercices wipes out all challenges (both attempts and statements). -func ResetExercices() error { - return truncateTable( - "team_wchoices", - "team_hints", - "exercice_files_okey_deps", - "exercice_files_omcq_deps", - "exercice_files", - "flag_found", - "exercice_flags_omcq_deps", - "exercice_mcq_okey_deps", - "exercice_mcq_omcq_deps", - "exercice_flags_deps", - "exercice_hints_okey_deps", - "exercice_hints_omcq_deps", - "flag_choices", - "exercice_flag_labels_omcq_deps", - "exercice_flag_labels_deps", - "exercice_flag_labels", - "exercice_flags", - "exercice_solved", - "exercice_tries", - "exercice_hints", - "mcq_found", - "mcq_entries", - "exercice_mcq", - "exercice_tags", - "exercices", - "themes", - ) -} - -// ResetTeams wipes out all teams, incluings members and attempts. -func ResetTeams() error { - return truncateTable( - "team_wchoices", - "team_hints", - "flag_found", - "mcq_found", - "exercice_solved", - "exercice_tries", - "team_members", - "teams", - ) -} diff --git a/libfic/stats.go b/libfic/stats.go deleted file mode 100644 index cf37f407..00000000 --- a/libfic/stats.go +++ /dev/null @@ -1,289 +0,0 @@ -package fic - -import ( - "database/sql" - "encoding/json" - "fmt" - "os" - "time" -) - -// FirstBlood is the coefficient added to the challenge coefficient when a Team is the first to solve a challenge. -var FirstBlood = 0.12 - -// SubmissionCostBase is the basis amount of point lost per submission -var SubmissionCostBase = 0.5 - -// SubmissionUniqueness don't count multiple times identical tries. -var SubmissionUniqueness = false - -// CountOnlyNotGoodTries don't count as a try when one good response is given at least. -var CountOnlyNotGoodTries = false - -// DiscountedFactor stores the percentage of the exercice's gain lost on each validation. -var DiscountedFactor = 0.0 - -// QuestionGainRatio is the ratio given to a partially solved exercice in the final scoreboard. -var QuestionGainRatio = 0.0 - -func exoptsQuery(whereExo string) string { - tries_table := "exercice_tries" - if SubmissionUniqueness { - tries_table = "exercice_distinct_tries" - } - - if CountOnlyNotGoodTries { - tries_table += "_notgood" - } - - exercices_table := "exercices" - if DiscountedFactor > 0 { - exercices_table = "exercices_discounted" - } - - questionGainQuery := "" - if QuestionGainRatio != 0.0 { - questionGainQuery = `SELECT id_team, F.id_exercice AS id_exercice, time, ` + fmt.Sprintf("%f", QuestionGainRatio) + ` / (COALESCE(T.total_flags, 0) + COALESCE(TMCQ.total_mcqs, 0)) AS coeff, CONCAT("Response flag ", F.id_flag, " ", F.ordre) AS reason FROM flag_found B INNER JOIN exercice_flags F ON F.id_flag = B.id_flag LEFT JOIN (SELECT id_exercice, COUNT(*) AS total_flags FROM exercice_flags GROUP BY id_exercice) T ON F.id_exercice = T.id_exercice LEFT JOIN (SELECT id_exercice, COUNT(*) AS total_mcqs FROM exercice_mcq GROUP BY id_exercice) TMCQ ON F.id_exercice = TMCQ.id_exercice WHERE F.bonus_gain = 0 UNION - SELECT id_team, F.id_exercice AS id_exercice, time, ` + fmt.Sprintf("%f", QuestionGainRatio) + ` / (COALESCE(T.total_flags, 0) + COALESCE(TMCQ.total_mcqs, 0)) AS coeff, CONCAT("Response MCQ ", F.id_mcq, " ", F.ordre) AS reason FROM mcq_found B INNER JOIN exercice_mcq F ON F.id_mcq = B.id_mcq LEFT JOIN (SELECT id_exercice, COUNT(*) AS total_flags FROM exercice_flags GROUP BY id_exercice) T ON F.id_exercice = T.id_exercice LEFT JOIN (SELECT id_exercice, COUNT(*) AS total_mcqs FROM exercice_mcq GROUP BY id_exercice) TMCQ ON F.id_exercice = TMCQ.id_exercice UNION` - } - - firstBloodQuery := "" - if FirstBlood != 0.0 { - firstBloodQuery = `SELECT id_team, id_exercice, time, ` + fmt.Sprintf("%f", FirstBlood) + ` AS coeff, "First blood" AS reason FROM exercice_solved JOIN (SELECT id_exercice, MIN(time) time FROM exercice_solved GROUP BY id_exercice) d1 USING (id_exercice, time) UNION` - } - - query := `SELECT S.id_team, S.time, E.gain AS points, coeff, S.reason, S.id_exercice FROM ( - ` + questionGainQuery + ` - ` + firstBloodQuery + ` - SELECT id_team, id_exercice, time, coefficient - ` + fmt.Sprintf("%f", QuestionGainRatio) + ` AS coeff, "Validation" AS reason FROM exercice_solved - ) S INNER JOIN ` + exercices_table + ` E ON S.id_exercice = E.id_exercice UNION ALL - SELECT B.id_team, B.time, F.bonus_gain AS points, 1 AS coeff, "Bonus flag" AS reason, F.id_exercice FROM flag_found B INNER JOIN exercice_flags F ON F.id_flag = B.id_flag WHERE F.bonus_gain != 0 HAVING points != 0 UNION ALL - SELECT id_team, MAX(time) AS time, (FLOOR(COUNT(*)/10 - 1) * (FLOOR(COUNT(*)/10)))/0.2 + (FLOOR(COUNT(*)/10) * (COUNT(*)%10)) AS points, ` + fmt.Sprintf("%f", SubmissionCostBase*-1) + ` AS coeff, "Tries" AS reason, id_exercice FROM ` + tries_table + ` S GROUP BY id_exercice, id_team` - - if whereExo != "" { - query = "SELECT W.* FROM (" + query + ") W " + whereExo - } - - return query -} - -func teamptsQuery() string { - return exoptsQuery("") + ` UNION ALL - SELECT D.id_team, D.time, H.cost AS points, D.coefficient * -1 AS coeff, "Hint" AS reason, H.id_exercice FROM team_hints D INNER JOIN exercice_hints H ON H.id_hint = D.id_hint HAVING points != 0 UNION ALL - SELECT W.id_team, W.time, F.choices_cost AS points, W.coefficient * -1 AS coeff, "Display choices" AS reason, F.id_exercice FROM team_wchoices W INNER JOIN exercice_flags F ON F.id_flag = W.id_flag HAVING points != 0` -} - -func rankQuery(whereTeam string) string { - return `SELECT A.id_team, SUM(A.points * A.coeff) AS score, MAX(A.time) AS time FROM ( - ` + teamptsQuery() + ` - ) A ` + whereTeam + ` GROUP BY A.id_team ORDER BY score DESC, time ASC` -} - -type ScoreGridRow struct { - Reason string `json:"reason"` - IdExercice int64 `json:"id_exercice"` - Time time.Time `json:"time"` - Points float64 `json:"points"` - Coeff float64 `json:"coeff"` -} - -func (t *Team) ScoreGrid() (grid []ScoreGridRow, err error) { - q := "SELECT G.reason, G.id_exercice, G.time, G.points, G.coeff FROM (" + teamptsQuery() + ") AS G WHERE G.id_team = ? AND G.points != 0" - if rows, err := DBQuery(q, t.Id); err != nil { - return nil, err - } else { - defer rows.Close() - - for rows.Next() { - var sgr ScoreGridRow - - if err := rows.Scan(&sgr.Reason, &sgr.IdExercice, &sgr.Time, &sgr.Points, &sgr.Coeff); err != nil { - return nil, err - } - - grid = append(grid, sgr) - } - } - return -} - -func ReadScoreGrid(fd *os.File) (grid []ScoreGridRow, err error) { - jdec := json.NewDecoder(fd) - - err = jdec.Decode(&grid) - return -} - -// Points - -// EstimateGain calculates the amount of point the Team has (or could have, if not already solved) won. -func (e *Exercice) EstimateGain(t *Team, solved bool) (pts float64, err error) { - if solved { - err = DBQueryRow("SELECT SUM(A.points * A.coeff) AS score FROM ("+exoptsQuery("WHERE W.id_team = ? AND W.id_exercice = ?")+") A GROUP BY id_team", t.Id, e.Id).Scan(&pts) - return - } - - pts += float64(e.Gain) * e.Coefficient - if e.SolvedCount() <= 0 { - pts += float64(e.Gain) * FirstBlood - } - - return -} - -// GetPoints returns the score for the Team. -func (t *Team) GetPoints() (float64, error) { - var tid *int64 - var nb *float64 - var tzzz *time.Time - err := DBQueryRow(rankQuery("WHERE A.id_team = ?"), t.Id).Scan(&tid, &nb, &tzzz) - if nb != nil { - return *nb, err - } else { - return 0, err - } -} - -// GetRank returns a map which associates team ID their rank. -func GetRank() (map[int64]int, error) { - if rows, err := DBQuery(rankQuery("")); err != nil { - return nil, err - } else { - defer rows.Close() - - rank := map[int64]int{} - nteam := 0 - for rows.Next() { - nteam += 1 - var tid int64 - var score float64 - var tzzz time.Time - if err := rows.Scan(&tid, &score, &tzzz); err != nil { - return nil, err - } - rank[tid] = nteam - } - if err := rows.Err(); err != nil { - return nil, err - } - - return rank, nil - } -} - -// Attempts - -// GetTries retrieves all attempts made by the matching Team or challenge (both can be nil to not filter). -func GetTries(t *Team, e *Exercice) (times []time.Time, err error) { - var rows *sql.Rows - - if t == nil { - if e == nil { - rows, err = DBQuery("SELECT time FROM exercice_tries ORDER BY time ASC") - } else { - rows, err = DBQuery("SELECT time FROM exercice_tries WHERE id_exercice = ? ORDER BY time ASC", e.Id) - } - } else { - if e == nil { - rows, err = DBQuery("SELECT time FROM exercice_tries WHERE id_team = ? ORDER BY time ASC", t.Id) - } else { - rows, err = DBQuery("SELECT time FROM exercice_tries WHERE id_team = ? AND id_exercice = ? ORDER BY time ASC", t.Id, e.Id) - } - } - - if err != nil { - return - } else { - defer rows.Close() - - for rows.Next() { - var tm time.Time - if err = rows.Scan(&tm); err != nil { - return - } - times = append(times, tm) - } - err = rows.Err() - return - } -} - -// GetValidations retrieves all flag validation made by the matching Team or challenge (both can be nil to not filter). -func GetValidations(t *Team, e *Exercice) (times []time.Time, err error) { - var rows *sql.Rows - - if t == nil { - if e == nil { - rows, err = DBQuery("SELECT time FROM flag_found UNION SELECT time FROM mcq_found ORDER BY time ASC") - } else { - rows, err = DBQuery("SELECT time FROM flag_found WHERE id_exercice = ? UNION SELECT time FROM mcq_found WHERE id_exercice = ? ORDER BY time ASC", e.Id, e.Id) - } - } else { - if e == nil { - rows, err = DBQuery("SELECT time FROM flag_found WHERE id_team = ? UNION SELECT time FROM mcq_found WHERE id_team = ? ORDER BY time ASC", t.Id, t.Id) - } else { - rows, err = DBQuery("SELECT time FROM flag_found WHERE id_team = ? AND id_exercice = ? UNION SELECT time FROM mcq_found WHERE id_team = ? AND id_exercice = ? ORDER BY time ASC", t.Id, e.Id, t.Id, e.Id) - } - } - - if err != nil { - return - } else { - defer rows.Close() - - for rows.Next() { - var tm time.Time - if err = rows.Scan(&tm); err != nil { - return - } - times = append(times, tm) - } - err = rows.Err() - return - } -} - -// GetTryRank generates a special rank based on number of attempts -func GetTryRank() ([]int64, error) { - if rows, err := DBQuery("SELECT id_team, COUNT(*) AS score FROM exercice_tries GROUP BY id_team HAVING score > 0 ORDER BY score DESC"); err != nil { - return nil, err - } else { - defer rows.Close() - - rank := make([]int64, 0) - for rows.Next() { - var tid int64 - var score int64 - if err := rows.Scan(&tid, &score); err != nil { - return nil, err - } - rank = append(rank, tid) - } - if err := rows.Err(); err != nil { - return nil, err - } - - return rank, nil - } -} - -func ReverseTriesPoints(points int64) int64 { - var i int64 = 1 - for (i+1)*i*5 < points { - i += 1 - } - - i = i * 10 - for (i/10-1)*i/10*5+i/10*(i%10) < points { - i += 1 - } - - return i -} - -func TermTriesSeq(i int64) float64 { - if i%10 == 0 { - return float64((i - 1) / 10) - } - return float64(i / 10) -} diff --git a/libfic/stats_test.go b/libfic/stats_test.go deleted file mode 100644 index 3052ac61..00000000 --- a/libfic/stats_test.go +++ /dev/null @@ -1,29 +0,0 @@ -package fic - -import ( - "testing" -) - -func TestReverseTriesPoints(t *testing.T) { - for _, i := range []int64{1, 2, 3, 4, 5, 6, 7, 8, 9, 10} { - if ReverseTriesPoints(i) != 10+i { - t.Fatalf("Expected %d, got %d", 10+i, ReverseTriesPoints(i)) - } - } - - for _, i := range []int64{1, 2, 3, 4, 5, 6, 7, 8, 9} { - if ReverseTriesPoints(10+i*2) != 20+i { - t.Fatalf("Expected %d, got %d", 20+i, ReverseTriesPoints(10+i*2)) - } - } -} - -func TestTermTriesSeq(t *testing.T) { - for _, j := range []float64{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10} { - for _, i := range []int64{1, 2, 3, 4, 5, 6, 7, 8, 9, 10} { - if TermTriesSeq(int64(10*j)+i) != j { - t.Fatalf("Term %d: Expected %f, got %f", int64(10*j)+i, j, TermTriesSeq(i)) - } - } - } -} diff --git a/libfic/team.go b/libfic/team.go index bb030ae2..5db84293 100644 --- a/libfic/team.go +++ b/libfic/team.go @@ -1,42 +1,28 @@ package fic import ( - "log" - "math" + "database/sql" + "fmt" "time" - - "golang.org/x/crypto/bcrypt" ) -// UnlockedChallengeDepth is the number of challenges to unlock ahead (0: only the next one, -1: all) -var UnlockedChallengeDepth int - -// UnlockedChallengeUpTo is the number of level to unlock -var UnlockedChallengeUpTo int - -// WchoiceCoefficient is the current coefficient applied on the cost of changing flag into choices -var WChoiceCoefficient = 1.0 - -// Team represents a group of players, come to solve our challenges. type Team struct { - Id int64 `json:"id"` - Name string `json:"name"` - Color uint32 `json:"color"` - Active bool `json:"active"` - ExternalId string `json:"external_id"` - Password *string `json:"password"` + Id int64 `json:"id"` + InitialName string `json:"initialName"` + Name string `json:"name"` + Color uint32 `json:"color"` } -func getTeams(filter string) ([]*Team, error) { - if rows, err := DBQuery("SELECT id_team, name, color, active, external_id, password FROM teams " + filter); err != nil { +func GetTeams() ([]Team, error) { + if rows, err := DBQuery("SELECT id_team, initial_name, name, color FROM teams"); err != nil { return nil, err } else { defer rows.Close() - var teams []*Team + var teams = make([]Team, 0) for rows.Next() { - t := &Team{} - if err := rows.Scan(&t.Id, &t.Name, &t.Color, &t.Active, &t.ExternalId, &t.Password); err != nil { + var t Team + if err := rows.Scan(&t.Id, &t.InitialName, &t.Name, &t.Color); err != nil { return nil, err } teams = append(teams, t) @@ -49,211 +35,131 @@ func getTeams(filter string) ([]*Team, error) { } } -// GetTeams returns a list of registered Team from the database. -func GetTeams() ([]*Team, error) { - return getTeams("") -} - -// GetActiveTeams returns a list of registered Team from the database, limited to team to generate. -func GetActiveTeams() ([]*Team, error) { - return getTeams("WHERE active = 1") -} - -// GetTeam retrieves a Team from its identifier. -func GetTeam(id int64) (*Team, error) { - t := &Team{} - if err := DBQueryRow("SELECT id_team, name, color, active, external_id, password FROM teams WHERE id_team = ?", id).Scan(&t.Id, &t.Name, &t.Color, &t.Active, &t.ExternalId, &t.Password); err != nil { +func GetTeam(id int) (Team, error) { + var t Team + if err := DBQueryRow("SELECT id_team, initial_name, name, color FROM teams WHERE id_team = ?", id).Scan(&t.Id, &t.InitialName, &t.Name, &t.Color); err != nil { return t, err } return t, nil } -// GetTeamBySerial retrieves a Team from one of its associated certificates. -func GetTeamBySerial(serial int64) (*Team, error) { - t := &Team{} - if err := DBQueryRow("SELECT T.id_team, T.name, T.color, T.active, T.external_id, T.password FROM certificates C INNER JOIN teams T ON T.id_team = C.id_team WHERE id_cert = ?", serial).Scan(&t.Id, &t.Name, &t.Color, &t.Active, &t.ExternalId, &t.Password); err != nil { +func GetTeamByInitialName(initialName string) (Team, error) { + var t Team + if err := DBQueryRow("SELECT id_team, initial_name, name, color FROM teams WHERE initial_name = ?", initialName).Scan(&t.Id, &t.InitialName, &t.Name, &t.Color); err != nil { return t, err } return t, nil } -// CreateTeam creates and fills a new struct Team and registers it into the database. -func CreateTeam(name string, color uint32, externalId string) (*Team, error) { - if res, err := DBExec("INSERT INTO teams (name, color, external_id) VALUES (?, ?, ?)", name, color, externalId); err != nil { - return nil, err +func CreateTeam(name string, color uint32) (Team, error) { + if res, err := DBExec("INSERT INTO teams (initial_name, name, color) VALUES (?, ?, ?)", name, name, color); err != nil { + return Team{}, err } else if tid, err := res.LastInsertId(); err != nil { + return Team{}, err + } else { + return Team{tid, name, name, color}, nil + } +} + +func (t Team) Update() (int64, error) { + if res, err := DBExec("UPDATE teams SET name = ?, color = ? WHERE id_team = ?", t.Name, t.Color, t.Id); err != nil { + return 0, err + } else if nb, err := res.RowsAffected(); err != nil { + return 0, err + } else { + return nb, err + } +} + +func (t Team) Delete() (int64, error) { + if res, err := DBExec("DELETE FROM team_members WHERE id_team = ?; DELETE FROM teams WHERE id_team = ?", t.Id, t.Id); err != nil { + return 0, err + } else if nb, err := res.RowsAffected(); err != nil { + return 0, err + } else { + return nb, err + } +} + +func (t Team) GetPoints() (int64, error) { + var nb *int64 + err := DBQueryRow("SELECT SUM(E.gain) FROM exercice_solved S INNER JOIN exercices E ON E.id_exercice = S.id_exercice WHERE id_team = ?", t.Id).Scan(&nb) + if nb != nil { + return *nb, err + } else { + return 0, err + } +} + +func GetRank() (map[int64]int, error) { + if rows, err := DBQuery("SELECT id_team, SUM(E.gain) AS score, MAX(S.time) FROM exercice_solved S INNER JOIN exercices E ON E.id_exercice = S.id_exercice GROUP BY id_team HAVING score > 0 ORDER BY score DESC, time ASC"); err != nil { return nil, err } else { - return &Team{tid, name, color, true, "", nil}, nil + defer rows.Close() + + rank := map[int64]int{} + nteam := 0 + for rows.Next() { + nteam += 1 + var tid int64 + var score int64 + var tzzz time.Time + if err := rows.Scan(&tid, &score, &tzzz); err != nil { + return nil, err + } + rank[tid] = nteam + } + if err := rows.Err(); err != nil { + return nil, err + } + + return rank, nil } } -// Update applies modifications back to the database. -func (t *Team) Update() (int64, error) { - if res, err := DBExec("UPDATE teams SET name = ?, color = ?, active = ?, external_id = ?, password = ? WHERE id_team = ?", t.Name, t.Color, t.Active, t.ExternalId, t.Password, t.Id); err != nil { - return 0, err - } else if nb, err := res.RowsAffected(); err != nil { - return 0, err +func GetTryRank() ([]int64, error) { + if rows, err := DBQuery("SELECT id_team, COUNT(*) AS score FROM exercice_tries GROUP BY id_team HAVING score > 0 ORDER BY score DESC"); err != nil { + return nil, err } else { - return nb, err + defer rows.Close() + + rank := make([]int64, 0) + for rows.Next() { + var tid int64 + var score int64 + if err := rows.Scan(&tid, &score); err != nil { + return nil, err + } + rank = append(rank, tid) + } + if err := rows.Err(); err != nil { + return nil, err + } + + return rank, nil } } -// Delete the challenge from the database -func (t *Team) Delete() (int64, error) { - if _, err := DBExec("DELETE FROM team_members WHERE id_team = ?", t.Id); err != nil { - return 0, err - } else if res, err := DBExec("DELETE FROM teams WHERE id_team = ?", t.Id); err != nil { - return 0, err - } else if nb, err := res.RowsAffected(); err != nil { - return 0, err - } else { - return nb, err - } -} - -// HasAccess checks if the Team has access to the given challenge. -func (t *Team) HasAccess(e *Exercice) bool { - // Case of standalone exercices - if e.IdTheme == nil || *e.IdTheme == 0 { - ord, err := e.GetOrdinal() - if err != nil { - return false - } - - if ord < UnlockedStandaloneExercices { - return true - } - - nbsteps, nbexos, err := t.SolvedCount() - if nbsteps == nil || nbexos == nil || err != nil { - return false - } - - if ord < UnlockedStandaloneExercices+int(math.Floor(UnlockedStandaloneExercicesByThemeStepValidation*float64(*nbsteps)))+int(math.Floor(UnlockedStandaloneExercicesByStandaloneExerciceValidation*float64(*nbexos))) { - return true - } - - return false - } - - if UnlockedChallengeDepth < 0 { +func (t Team) HasAccess(e Exercice) bool { + if e.Depend == nil { return true - } - - if UnlockedChallengeUpTo > 1 { - lvl, err := e.GetLevel() - if err == nil && lvl <= UnlockedChallengeUpTo { - return true - } - } - - for i := UnlockedChallengeDepth; i >= 0; i-- { - // An exercice without dependency is accessible - if e.Depend == nil { - return true - } - - ed := &Exercice{} + } else { + ed := Exercice{} ed.Id = *e.Depend - s := t.HasSolved(ed) - if s != nil { - return true - } - - // Prepare next iteration - var err error - e, err = GetExercice(ed.Id) - if err != nil { - return false - } - - // If our previous exercice is WIP, unlock - if i == UnlockedChallengeDepth && e.WIP { - return true - } - - } - return false -} - -// CanDownload checks if the Team has access to the given file. -func (t *Team) CanDownload(f *EFile) bool { - if !f.Published { - return false - } else if deps, err := f.GetDepends(); err != nil { - log.Printf("Unable to retrieve file dependencies: %s\n", err) - return false - } else { - res := true - - for _, dep := range deps { - if t.HasPartiallySolved(dep) == nil { - res = false - break - } - } - - return res + s, _, _ := t.HasSolved(ed) + return s } } -// CanSeeHint checks if the Team has access to the given hint. -func (t *Team) CanSeeHint(h *EHint) bool { - if deps, err := h.GetDepends(); err != nil { - log.Printf("Unable to retrieve flag dependencies: %s\n", err) - return false - } else { - res := true - - for _, dep := range deps { - if t.HasPartiallySolved(dep) == nil { - res = false - break - } - } - - return res - } -} - -// CanSeeFlag checks if the Team has access to the given flag. -func (t *Team) CanSeeFlag(k Flag) bool { - if deps, err := k.GetDepends(); err != nil { - log.Printf("Unable to retrieve flag dependencies: %s\n", err) - return false - } else { - res := true - - for _, dep := range deps { - if t.HasPartiallySolved(dep) == nil { - res = false - break - } - } - - return res - } -} - -// NbTry retrieves the number of attempts made by the Team to the given challenge. -func NbTry(t *Team, e *Exercice) int { - tries_table := "exercice_tries" - if SubmissionUniqueness { - tries_table = "exercice_distinct_tries" - } - if CountOnlyNotGoodTries { - tries_table += "_notgood" - } - +func NbTry(t *Team, e Exercice) int { var cnt *int if t != nil { - DBQueryRow("SELECT COUNT(*) FROM "+tries_table+" WHERE id_team = ? AND id_exercice = ?", t.Id, e.Id).Scan(&cnt) + DBQueryRow("SELECT COUNT(*) FROM exercice_tries WHERE id_team = ? AND id_exercice = ?", t.Id, e.Id).Scan(&cnt) } else { - DBQueryRow("SELECT COUNT(*) FROM "+tries_table+" WHERE id_exercice = ?", e.Id).Scan(&cnt) + DBQueryRow("SELECT COUNT(*) FROM exercice_tries WHERE id_exercice = ?", e.Id).Scan(&cnt) } if cnt == nil { @@ -263,129 +169,193 @@ func NbTry(t *Team, e *Exercice) int { } } -// HasHint checks if the Team has revealed the given Hint. -func (t *Team) HasHint(h *EHint) bool { - var tm *time.Time - DBQueryRow("SELECT MIN(time) FROM team_hints WHERE id_team = ? AND id_hint = ?", t.Id, h.Id).Scan(&tm) - return tm != nil -} +func GetTries(t *Team, e *Exercice) ([]time.Time, error) { + var rows *sql.Rows + var err error -// OpenHint registers to the database that the Team has now revealed. -func (t *Team) OpenHint(h *EHint) error { - _, err := DBExec("INSERT INTO team_hints (id_team, id_hint, time, coefficient) VALUES (?, ?, ?, ?)", t.Id, h.Id, time.Now(), math.Max(HintCoefficient, 0.0)) - return err -} - -// SeeChoices checks if the Team has revealed the given choices. -func (t *Team) SeeChoices(k *FlagKey) bool { - var tm *time.Time - DBQueryRow("SELECT MIN(time) FROM team_wchoices WHERE id_team = ? AND id_flag = ?", t.Id, k.Id).Scan(&tm) - return tm != nil -} - -// DisplayChoices registers to the database that the Team has now revealed. -func (t *Team) DisplayChoices(k *FlagKey) error { - _, err := DBExec("INSERT INTO team_wchoices (id_team, id_flag, time, coefficient) VALUES (?, ?, ?, ?)", t.Id, k.Id, time.Now(), math.Max(WChoiceCoefficient, 0.0)) - return err -} - -// CountTries gets the amount of attempts made by the Team and retrieves the time of the latest attempt. -func (t *Team) CountTries(e *Exercice) (nb int64, tm *time.Time) { - table := "exercice_tries" - if SubmissionUniqueness { - table = "exercice_distinct_tries" - } - if CountOnlyNotGoodTries { - table += "_notgood" - } - - DBQueryRow("SELECT COUNT(id_exercice), MAX(time) FROM "+table+" WHERE id_team = ? AND id_exercice = ?", t.Id, e.Id).Scan(&nb, &tm) - - // time is not accurate in distinct nor _notgood tables as it only considers notgood or distincts answers, so the last try is not count - if SubmissionUniqueness || CountOnlyNotGoodTries { - DBQueryRow("SELECT MAX(time) FROM exercice_tries WHERE id_team = ? AND id_exercice = ?", t.Id, e.Id).Scan(&tm) - } - - return -} - -// LastTryDist retrieves the distance to the correct answers, for the given challenge. -// The distance is the number of bad responses given in differents MCQs. -func (t *Team) LastTryDist(e *Exercice) int64 { - var nb *int64 - if DBQueryRow("SELECT nbdiff FROM exercice_tries WHERE id_team = ? AND id_exercice = ? ORDER BY time DESC LIMIT 1", t.Id, e.Id).Scan(&nb); nb == nil { - return 0 + if t == nil { + if e == nil { + rows, err = DBQuery("SELECT time FROM exercice_tries ORDER BY time ASC") + } else { + rows, err = DBQuery("SELECT time FROM exercice_tries WHERE id_exercice = ? ORDER BY time ASC", e.Id) + } } else { - return *nb + if e == nil { + rows, err = DBQuery("SELECT time FROM exercice_tries WHERE id_team = ? ORDER BY time ASC", t.Id) + } else { + rows, err = DBQuery("SELECT time FROM exercice_tries WHERE id_team = ? AND id_exercice = ? ORDER BY time ASC", t.Id, e.Id) + } } -} -// SolvedCount returns the number of solved exercices. -func (t *Team) SolvedCount() (nbsteps *int64, nbex *int64, err error) { - err = DBQueryRow("SELECT COUNT(S.id_exercice) FROM exercice_solved S INNER JOIN exercices E ON E.id_exercice = S.id_exercice WHERE S.id_team = ? AND E.id_theme IS NOT NULL", t.Id).Scan(&nbsteps) if err != nil { - return - } - - err = DBQueryRow("SELECT COUNT(S.id_exercice) FROM exercice_solved S INNER JOIN exercices E ON E.id_exercice = S.id_exercice WHERE S.id_team = ? AND E.id_theme IS NULL", t.Id).Scan(&nbex) - return -} - -// HasSolved checks if the Team already has validated the given challenge. -// Note that the function also returns the effective validation timestamp. -func (t *Team) HasSolved(e *Exercice) (tm *time.Time) { - DBQueryRow("SELECT time FROM exercice_solved WHERE id_team = ? AND id_exercice = ? ORDER BY time ASC LIMIT 1", t.Id, e.Id).Scan(&tm) - return -} - -// GetSolvedRank returns the number of teams that solved the challenge before the Team. -func (t *Team) GetSolvedRank(e *Exercice) (nb int64, err error) { - if rows, errr := DBQuery("SELECT id_team FROM exercice_solved WHERE id_exercice = ? ORDER BY time ASC", e.Id); errr != nil { - return nb, errr + return nil, err } else { defer rows.Close() + times := make([]time.Time, 0) for rows.Next() { - var tid int64 - if err = rows.Scan(&tid); err != nil { - return - } - - nb += 1 - if t.Id == tid { - break + var tm time.Time + if err := rows.Scan(&tm); err != nil { + return nil, err } + times = append(times, tm) } - return + if err := rows.Err(); err != nil { + return nil, err + } + + return times, nil } } -// HasPartiallySolved checks if the Team already has unlocked the given flag and returns the validation's timestamp. -func (t *Team) HasPartiallySolved(f Flag) (tm *time.Time) { - if _, ok := f.(*FlagLabel); ok { - now := time.Now() - return &now - } else if k, ok := f.(*FlagKey); ok { - DBQueryRow("SELECT MIN(time) FROM flag_found WHERE id_team = ? AND id_flag = ?", t.Id, k.Id).Scan(&tm) - } else if m, ok := f.(*MCQ); ok { - DBQueryRow("SELECT MIN(time) FROM mcq_found WHERE id_team = ? AND id_mcq = ?", t.Id, m.Id).Scan(&tm) - } else { - log.Fatal("Unknown flag type") - } - return -} - -// HashedPassword compute a bcrypt version of the team's password. -func (t *Team) HashedPassword() (string, error) { - if t.Password == nil { - if passwd, err := GeneratePassword(); err != nil { - return "", err +func (t Team) HasSolved(e Exercice) (bool, time.Time, int64) { + var nb *int64 + var tm *time.Time + if DBQueryRow("SELECT MIN(time) FROM exercice_solved WHERE id_team = ? AND id_exercice = ?", t.Id, e.Id).Scan(&tm); tm == nil { + if DBQueryRow("SELECT COUNT(id_exercice), MAX(time) FROM exercice_tries WHERE id_team = ? AND id_exercice = ?", t.Id, e.Id).Scan(&nb, &tm); tm == nil { + return false, time.Unix(0, 0), 0 + } else if nb == nil { + return false, *tm, 0 } else { - h, err := bcrypt.GenerateFromPassword([]byte(passwd), bcrypt.DefaultCost) - return string(h), err + return false, *tm, *nb } + } else if DBQueryRow("SELECT COUNT(id_exercice) FROM exercice_solved WHERE id_exercice = ? AND time < ?", e.Id, tm).Scan(&nb); nb == nil { + return true, *tm, 0 + } else { + return true, *tm, *nb + 1 + } +} + +func IsSolved(e Exercice) (int, time.Time) { + var nb *int + var tm *time.Time + if DBQueryRow("SELECT COUNT(id_exercice), MIN(time) FROM exercice_solved WHERE id_exercice = ?", e.Id).Scan(&nb, &tm); nb == nil || tm == nil { + return 0, time.Time{} + } else { + return *nb, *tm + } +} + +type statLine struct { + Tip string `json:"tip"` + Total int `json:"total"` + Solved int `json:"solved"` + Tried int `json:"tried"` + Tries int `json:"tries"` +} + +type teamStats struct { + Levels []statLine `json:"levels"` + Themes []statLine `json:"themes"` +} + +func (s *teamStats) GetLevel(level int) *statLine { + level -= 1 + + for len(s.Levels) <= level { + s.Levels = append(s.Levels, statLine{ + fmt.Sprintf("Level %d", (len(s.Levels) + 1)), + 0, + 0, + 0, + 0, + }) } - h, err := bcrypt.GenerateFromPassword([]byte(*t.Password), bcrypt.DefaultCost) - return string(h), err + return &s.Levels[level] +} + +func (t Team) GetStats() (interface{}, error) { + return GetTeamsStats(&t) +} + +func GetTeamsStats(t *Team) (interface{}, error) { + stat := teamStats{} + + if themes, err := GetThemes(); err != nil { + return nil, err + } else { + for _, theme := range themes { + total := 0 + solved := 0 + tried := 0 + tries := 0 + + if exercices, err := theme.GetExercices(); err != nil { + return nil, err + } else { + for _, exercice := range exercices { + var lvl int + if lvl, err = exercice.GetLevel(); err != nil { + return nil, err + } + sLvl := stat.GetLevel(lvl) + + total += 1 + sLvl.Total += 1 + + if t != nil { + if b, _, _ := t.HasSolved(exercice); b { + solved += 1 + sLvl.Solved += 1 + } + } else { + if n, _ := IsSolved(exercice); n > 0 { + solved += 1 + sLvl.Solved += 1 + } + } + + try := NbTry(t, exercice) + if try > 0 { + tried += 1 + tries += try + sLvl.Tried += 1 + sLvl.Tries += try + } + } + } + + stat.Themes = append(stat.Themes, statLine{ + theme.Name, + total, + solved, + tried, + tries, + }) + } + + return stat, nil + } +} + +type exportedTeam struct { + Name string `json:"name"` + Color string `json:"color"` + Rank int `json:"rank"` + Points int64 `json:"score"` +} + +func ExportTeams() (interface{}, error) { + if teams, err := GetTeams(); err != nil { + return nil, err + } else if rank, err := GetRank(); err != nil { + return nil, err + } else { + ret := map[string]exportedTeam{} + for _, team := range teams { + if points, err := team.GetPoints(); err != nil { + return nil, err + } else { + ret[fmt.Sprintf("%d", team.Id)] = exportedTeam{ + team.Name, + fmt.Sprintf("#%x", team.Color), + rank[team.Id], + points, + } + } + } + + return ret, nil + } } diff --git a/libfic/team_export.go b/libfic/team_export.go deleted file mode 100644 index 66b0445b..00000000 --- a/libfic/team_export.go +++ /dev/null @@ -1,48 +0,0 @@ -package fic - -import ( - "fmt" -) - -// exportedTeam is a structure representing a Team, as exposed to players. -type ExportedTeam struct { - Name string `json:"name"` - Color string `json:"color"` - Rank int `json:"rank"` - Points float64 `json:"score"` - Members []*Member `json:"members,omitempty"` - ExternalId string `json:"external_id,omitempty"` -} - -// Exportedteam creates the structure to respond as teams.json. -func ExportTeams(includeMembers bool) (ret map[string]ExportedTeam, err error) { - var teams []*Team - var rank map[int64]int - - if teams, err = GetTeams(); err != nil { - return - } else if rank, err = GetRank(); err != nil { - return nil, err - } else { - ret = map[string]ExportedTeam{} - for _, team := range teams { - points, _ := team.GetPoints() - var members []*Member - if includeMembers { - if members, err = team.GetMembers(); err != nil { - return - } - } - ret[fmt.Sprintf("%d", team.Id)] = ExportedTeam{ - team.Name, - fmt.Sprintf("#%06x", team.Color), - rank[team.Id], - points * GlobalScoreCoefficient, - members, - team.ExternalId, - } - } - - return - } -} diff --git a/libfic/team_history.go b/libfic/team_history.go deleted file mode 100644 index bb0d485a..00000000 --- a/libfic/team_history.go +++ /dev/null @@ -1,185 +0,0 @@ -package fic - -import ( - "time" -) - -// GetHistory aggregates all sources of events or actions for a Team -func (t *Team) GetHistory() ([]map[string]interface{}, error) { - hist := make([]map[string]interface{}, 0) - - if rows, err := DBQuery(`SELECT id_team, "tries" AS kind, time, 0, E.id_exercice, E.title, NULL, NULL FROM exercice_tries T INNER JOIN exercices E ON E.id_exercice = T.id_exercice WHERE id_team = ? UNION - SELECT id_team, "solved" AS kind, time, coefficient, E.id_exercice, E.title, NULL, NULL FROM exercice_solved S INNER JOIN exercices E ON E.id_exercice = S.id_exercice WHERE id_team = ? UNION - SELECT id_team, "hint" AS kind, time, coefficient, E.id_exercice, E.title, H.id_hint, H.title FROM team_hints T INNER JOIN exercice_hints H ON H.id_hint = T.id_hint INNER JOIN exercices E ON E.id_exercice = H.id_exercice WHERE id_team = ? UNION - SELECT id_team, "wchoices" AS kind, time, coefficient, E.id_exercice, E.title, F.id_flag, F.type FROM team_wchoices W INNER JOIN exercice_flags F ON F.id_flag = W.id_flag INNER JOIN exercices E ON E.id_exercice = F.id_exercice WHERE id_team = ? UNION - SELECT id_team, "flag_found" AS kind, time, 0, E.id_exercice, E.title, K.id_flag, K.type FROM flag_found F INNER JOIN exercice_flags K ON K.id_flag = F.id_flag INNER JOIN exercices E ON K.id_exercice = E.id_exercice WHERE id_team = ? UNION - SELECT id_team, "mcq_found" AS kind, time, 0, E.id_exercice, E.title, Q.id_mcq, Q.title FROM mcq_found F INNER JOIN exercice_mcq Q ON Q.id_mcq = F.id_mcq INNER JOIN exercices E ON Q.id_exercice = E.id_exercice WHERE id_team = ? - ORDER BY time DESC`, t.Id, t.Id, t.Id, t.Id, t.Id, t.Id); err != nil { - return nil, err - } else { - defer rows.Close() - - for rows.Next() { - var id_team int64 - var kind string - var time time.Time - var coefficient float32 - var primary *int64 - var primary_title *string - var secondary *int64 - var secondary_title *string - - if err := rows.Scan(&id_team, &kind, &time, &coefficient, &primary, &primary_title, &secondary, &secondary_title); err != nil { - return nil, err - } - - h := map[string]interface{}{} - - h["kind"] = kind - h["time"] = time - h["coefficient"] = coefficient - if primary != nil { - h["primary"] = primary - h["primary_title"] = primary_title - } - if secondary != nil { - h["secondary"] = secondary - h["secondary_title"] = secondary_title - } - - hist = append(hist, h) - } - } - - return hist, nil -} - -// UpdateHistoryCoeff updates the coefficient for a given entry. -func (t *Team) UpdateHistoryCoeff(kind string, h time.Time, givenId int64, newCoeff float32) (int64, error) { - if kind == "hint" { - if res, err := DBExec("UPDATE team_hints SET coefficient = ? WHERE id_team = ? AND time = ? AND id_hint = ?", newCoeff, t.Id, h, givenId); err != nil { - return 0, err - } else if nb, err := res.RowsAffected(); err != nil { - return 0, err - } else { - return nb, err - } - } else if kind == "wchoices" { - if res, err := DBExec("UPDATE team_wchoices SET coefficient = ? WHERE id_team = ? AND time = ? AND id_flag = ?", newCoeff, t.Id, h, givenId); err != nil { - return 0, err - } else if nb, err := res.RowsAffected(); err != nil { - return 0, err - } else { - return nb, err - } - } else if kind == "solved" { - if res, err := DBExec("UPDATE exercice_solved SET coefficient = ? WHERE id_team = ? AND time = ? AND id_exercice = ?", newCoeff, t.Id, h, givenId); err != nil { - return 0, err - } else if nb, err := res.RowsAffected(); err != nil { - return 0, err - } else { - return nb, err - } - } else { - return 0, nil - } -} - -// DelHistoryItem removes from the database an entry from the history. -func (t *Team) DelHistoryItem(kind string, h time.Time, primary *int64, secondary *int64) (interface{}, error) { - if kind == "tries" && primary != nil { - if res, err := DBExec("DELETE FROM exercice_tries WHERE id_team = ? AND time = ? AND id_exercice = ?", t.Id, h, *primary); err != nil { - return 0, err - } else if nb, err := res.RowsAffected(); err != nil { - return 0, err - } else { - return nb, err - } - } else if kind == "hint" && primary != nil && secondary != nil { - if res, err := DBExec("DELETE FROM team_hints WHERE id_team = ? AND time = ? AND id_hint = ?", t.Id, h, *secondary); err != nil { - return 0, err - } else if nb, err := res.RowsAffected(); err != nil { - return 0, err - } else { - return nb, err - } - } else if kind == "wchoices" && primary != nil && secondary != nil { - if res, err := DBExec("DELETE FROM team_wchoices WHERE id_team = ? AND time = ? AND id_flag = ?", t.Id, h, *secondary); err != nil { - return 0, err - } else if nb, err := res.RowsAffected(); err != nil { - return 0, err - } else { - return nb, err - } - } else if kind == "flag_found" && primary != nil && secondary != nil { - if res, err := DBExec("DELETE FROM flag_found WHERE id_team = ? AND time = ? AND id_flag = ?", t.Id, h, *secondary); err != nil { - return 0, err - } else if nb, err := res.RowsAffected(); err != nil { - return 0, err - } else { - return nb, err - } - } else if kind == "mcq_found" && primary != nil && secondary != nil { - if res, err := DBExec("DELETE FROM mcq_found WHERE id_team = ? AND time = ? AND id_mcq = ?", t.Id, h, *secondary); err != nil { - return 0, err - } else if nb, err := res.RowsAffected(); err != nil { - return 0, err - } else { - return nb, err - } - } else if kind == "solved" && primary != nil { - if res, err := DBExec("DELETE FROM exercice_solved WHERE id_team = ? AND time = ? AND id_exercice = ?", t.Id, h, *primary); err != nil { - return 0, err - } else if nb, err := res.RowsAffected(); err != nil { - return 0, err - } else { - return nb, err - } - } else { - return nil, nil - } -} - -// ResetProgressionOnExercice removes all tries and validations for a given Exercice. -func (t *Team) ResetProgressionOnExercice(exercice *Exercice) error { - hints, err := exercice.GetHints() - if err != nil { - return err - } - - flags, err := exercice.GetFlags() - if err != nil { - return err - } - - if _, err := DBExec("DELETE FROM exercice_tries WHERE id_team = ? AND id_exercice = ?", t.Id, exercice.Id); err != nil { - return err - } - if _, err := DBExec("DELETE FROM exercice_solved WHERE id_team = ? AND id_exercice = ?", t.Id, exercice.Id); err != nil { - return err - } - - for _, hint := range hints { - if _, err := DBExec("DELETE FROM team_hints WHERE id_team = ? AND id_hint = ?", t.Id, hint.Id); err != nil { - return err - } - - } - - for _, flag := range flags { - if k, ok := flag.(*FlagKey); ok { - if _, err := DBExec("DELETE FROM team_wchoices WHERE id_team = ? AND id_flag = ?", t.Id, k.Id); err != nil { - return err - } - if _, err := DBExec("DELETE FROM flag_found WHERE id_team = ? AND id_flag = ?", t.Id, k.Id); err != nil { - return err - } - } else if mcq, ok := flag.(*MCQ); ok { - if _, err := DBExec("DELETE FROM mcq_found WHERE id_team = ? AND id_mcq = ?", t.Id, mcq.Id); err != nil { - return err - } - } - } - - return nil -} diff --git a/libfic/team_my.go b/libfic/team_my.go index 4c7df921..046bfe20 100644 --- a/libfic/team_my.go +++ b/libfic/team_my.go @@ -2,418 +2,94 @@ package fic import ( "encoding/hex" - "encoding/json" "fmt" - "log" - "math" - "os" - "path" - "sort" - "strconv" - "strings" "time" ) -// DisplayAllFlags doesn't respect the predefined constraint existing between flags. -var DisplayAllFlags bool - -// DisplayMCQBadCount activates the report of MCQ bad responses counter. -var DisplayMCQBadCount bool - -// HideCaseSensitivity never tells the user if the flag is case sensitive or not. -var HideCaseSensitivity bool - -// UnlockedStandaloneExercices unlock this number of standalone exercice. -var UnlockedStandaloneExercices int - -// UnlockedStandaloneExercicesByThemeStepValidation unlock this number of standalone exercice for each theme step validated. -var UnlockedStandaloneExercicesByThemeStepValidation float64 - -// UnlockedStandaloneExercicesByStandaloneExerciceValidation unlock this number of standalone exercice for each standalone exercice validated. -var UnlockedStandaloneExercicesByStandaloneExerciceValidation float64 - type myTeamFile struct { - Path string `json:"path"` - Name string `json:"name"` - Checksum string `json:"checksum"` - Compressed bool `json:"compressed,omitempty"` - Size int64 `json:"size"` - Disclaimer string `json:"disclaimer,omitempty"` -} -type myTeamHint struct { - HintId int64 `json:"id"` - Title string `json:"title"` - Content string `json:"content,omitempty"` - File string `json:"file,omitempty"` - Cost int64 `json:"cost"` -} -type myTeamFlag struct { - Id int `json:"id,omitempty"` - Label string `json:"label"` - Type string `json:"type,omitempty"` - Placeholder string `json:"placeholder,omitempty"` - Help string `json:"help,omitempty"` - Unit string `json:"unit,omitempty"` - Separator string `json:"separator,omitempty"` - NbLines uint64 `json:"nb_lines,omitempty"` - IgnoreOrder bool `json:"ignore_order,omitempty"` - IgnoreCase bool `json:"ignore_case,omitempty"` - Multiline bool `json:"multiline,omitempty"` - CaptureRe *string `json:"capture_regexp,omitempty"` - Solved *time.Time `json:"found,omitempty"` - PSolved *time.Time `json:"part_solved,omitempty"` - Soluce string `json:"soluce,omitempty"` - Justify bool `json:"justify,omitempty"` - BonusGain int64 `json:"bonus_gain,omitempty"` - Choices map[string]interface{} `json:"choices,omitempty"` - ChoicesCost int64 `json:"choices_cost,omitempty"` - Variant string `json:"variant,omitempty"` - Min *float64 `json:"min,omitempty"` - Max *float64 `json:"max,omitempty"` - Step *float64 `json:"step,omitempty"` - order int8 -} -type myTeamMCQJustifiedChoice struct { - Label string `json:"label"` - Value bool `json:"value,omitempty"` - Justification myTeamFlag `json:"justification,omitempty"` + Path string `json:"path"` + Name string `json:"name"` + Checksum string `json:"checksum"` + Size int64 `json:"size"` } type myTeamExercice struct { - ThemeId int64 `json:"theme_id,omitempty"` - Disabled bool `json:"disabled,omitempty"` - WIP bool `json:"wip,omitempty"` - Statement string `json:"statement"` - Overview string `json:"overview,omitempty"` - Finished string `json:"finished,omitempty"` - Hints []myTeamHint `json:"hints,omitempty"` - Gain int `json:"gain"` - Files []myTeamFile `json:"files,omitempty"` - Flags []myTeamFlag `json:"flags,omitempty"` - NbFlags int `json:"nb_flags,omitempty"` - SolveDist int64 `json:"solve_dist,omitempty"` - SolvedTime *time.Time `json:"solved_time,omitempty"` - SolvedRank int64 `json:"solved_rank,omitempty"` - Tries int64 `json:"tries,omitempty"` - TotalTries int64 `json:"total_tries,omitempty"` - VideoURI string `json:"video_uri,omitempty"` - Resolution string `json:"resolution,omitempty"` - Issue string `json:"issue,omitempty"` - IssueKind string `json:"issuekind,omitempty"` + ThemeId int `json:"theme_id"` + Statement string `json:"statement"` + Hint string `json:"hint"` + Gain int64 `json:"gain"` + Files []myTeamFile `json:"files"` + Keys []string `json:"keys"` + Solved bool `json:"solved"` + SolvedTime time.Time `json:"solved_time"` + SolvedNumber int64 `json:"solved_number"` + VideoURI string `json:"video_uri"` } -type MyTeam struct { +type myTeam struct { Id int64 `json:"team_id"` Name string `json:"name"` Points int64 `json:"score"` - Points100 int64 `json:"score100"` - Members []*Member `json:"members,omitempty"` + Members []Member `json:"members"` Exercices map[string]myTeamExercice `json:"exercices"` } -type ByOrder []myTeamFlag - -func (a ByOrder) Len() int { return len(a) } -func (a ByOrder) Swap(i, j int) { a[i], a[j] = a[j], a[i] } -func (a ByOrder) Less(i, j int) bool { return a[i].order < a[j].order } - func MyJSONTeam(t *Team, started bool) (interface{}, error) { - ret := MyTeam{} - - // Fill information about the team + ret := myTeam{} if t == nil { ret.Id = 0 } else { ret.Name = t.Name ret.Id = t.Id - points, _ := t.GetPoints() - ret.Points = int64(math.Round(float64(points) * GlobalScoreCoefficient)) - ret.Points100 = int64(math.Round(float64(points) * GlobalScoreCoefficient * 100)) + ret.Points, _ = t.GetPoints() if members, err := t.GetMembers(); err == nil { ret.Members = members } - } - // Retrieve themes - themes, err := GetThemes() - if err != nil { - return ret, err } - - mapthemes := map[int64]*Theme{} - for _, theme := range themes { - mapthemes[theme.Id] = theme - } - - // Fill exercices, only if the challenge is started ret.Exercices = map[string]myTeamExercice{} - if exos, err := GetDiscountedExercices(); err != nil { + + if exos, err := GetExercices(); err != nil { return ret, err } else if started { for _, e := range exos { - if t == nil || ((!e.Disabled || (e.IdTheme != nil && !mapthemes[*e.IdTheme].Locked)) && t.HasAccess(e)) { + if t == nil || t.HasAccess(e) { exercice := myTeamExercice{} - exercice.Disabled = e.Disabled - exercice.WIP = e.WIP - if e.IdTheme != nil { - exercice.ThemeId = *e.IdTheme + if tid, err := e.GetThemeId(); err == nil { + exercice.ThemeId = tid } - - exercice.Statement = strings.Replace(e.Statement, "$FILES$", FilesDir, -1) - - if len(e.Issue) > 0 { - exercice.Issue = e.Issue - exercice.IssueKind = e.IssueKind - } - + exercice.Statement = e.Statement + exercice.Hint = e.Hint if t == nil { - exercice.Overview = strings.Replace(e.Overview, "$FILES$", FilesDir, -1) - exercice.Finished = strings.Replace(e.Finished, "$FILES$", FilesDir, -1) - exercice.VideoURI = strings.Replace(e.VideoURI, "$FILES$", FilesDir, -1) - exercice.Resolution = strings.Replace(e.Resolution, "$FILES$", FilesDir, -1) - exercice.TotalTries = e.TriedCount() - exercice.Gain = int(float64(e.Gain) * e.Coefficient * GlobalScoreCoefficient) + exercice.VideoURI = e.VideoURI + exercice.Solved = true + exercice.SolvedNumber = e.TriedCount() } else { - stime := t.HasSolved(e) - exercice.SolvedTime = stime + exercice.Solved, exercice.SolvedTime, exercice.SolvedNumber = t.HasSolved(e) + } - if stime != nil { - exercice.SolvedRank, _ = t.GetSolvedRank(e) - exercice.Finished = e.Finished - exercice.Tries, _ = t.CountTries(e) - } else { - var ttime *time.Time - exercice.Tries, ttime = t.CountTries(e) - exercice.SolvedTime = ttime - if DisplayMCQBadCount && exercice.Tries > 0 { - exercice.SolveDist = t.LastTryDist(e) + exercice.Keys = []string{} + + if keys, err := e.GetKeys(); err != nil { + return nil, err + } else { + for _, k := range keys { + if t == nil { + exercice.Keys = append(exercice.Keys, fmt.Sprintf("%x", k.Value)+k.Type) + } else { + exercice.Keys = append(exercice.Keys, k.Type) } } - - if exercice.SolvedTime != nil && exercice.SolvedTime.Equal(time.Unix(0, 0)) { - exercice.SolvedTime = nil - } - - if gain, err := e.EstimateGain(t, stime != nil); err == nil { - exercice.Gain = int(gain * GlobalScoreCoefficient) - } else { - log.Printf("ERROR during gain estimation (tid=%d ; eid=%d): %s", t.Id, e.Id, err.Error()) - exercice.Gain = int(float64(e.Gain) * GlobalScoreCoefficient) - } } - // Expose exercice files - exercice.Files = []myTeamFile{} if files, err := e.GetFiles(); err != nil { return nil, err } else { for _, f := range files { - if t == nil || t.CanDownload(f) { - cksum := f.Checksum - compressed := false - if len(f.ChecksumShown) > 0 { - cksum = f.ChecksumShown - compressed = true - } - exercice.Files = append(exercice.Files, myTeamFile{path.Join(FilesDir, f.Path), f.Name, hex.EncodeToString(cksum), compressed, f.Size, f.Disclaimer}) - } + exercice.Files = append(exercice.Files, myTeamFile{f.Path, f.Name, hex.EncodeToString(f.Checksum), f.Size}) } } - // Expose exercice hints - - exercice.Hints = []myTeamHint{} - - if hints, err := e.GetHints(); err != nil { - return nil, err - } else { - for _, h := range hints { - if t == nil || HintCoefficient < 0 || t.HasHint(h) { - exercice.Hints = append(exercice.Hints, myTeamHint{h.Id, h.Title, h.Content, h.File, int64(float64(h.Cost) * GlobalScoreCoefficient)}) - } else if t.CanSeeHint(h) { - exercice.Hints = append(exercice.Hints, myTeamHint{h.Id, h.Title, "", "", int64(float64(h.Cost) * GlobalScoreCoefficient)}) - } - } - } - - // Expose exercice flags - - if !e.Disabled { - justifiedMCQ := map[int]myTeamFlag{} - - if labels, err := e.GetFlagLabels(); err != nil { - return nil, err - } else { - for _, l := range labels { - if !DisplayAllFlags && t != nil && !t.CanSeeFlag(l) { - // Dependancy missing, skip the flag for now - continue - } - - exercice.Flags = append(exercice.Flags, myTeamFlag{ - order: l.Order, - Label: l.Label, - Variant: l.Variant, - }) - } - } - - if flags, err := e.GetFlagKeys(); err != nil { - return nil, err - } else { - for _, k := range flags { - if !strings.HasPrefix(k.Type, "label") { - exercice.NbFlags += 1 - } - - if !DisplayAllFlags && t != nil && !t.CanSeeFlag(k) { - // Dependancy missing, skip the flag for now - continue - } - - flag := myTeamFlag{ - Id: k.Id, - Type: k.Type, - order: k.Order, - Help: k.Help, - Unit: k.Unit, - } - - if strings.HasPrefix(flag.Type, "number") { - flag.Min, flag.Max, flag.Step, err = AnalyzeNumberFlag(flag.Type) - flag.Type = "number" - if err != nil { - return nil, err - } - } - - // Retrieve solved state or solution for public iface - if t == nil { - flag.IgnoreCase = k.IgnoreCase - flag.CaptureRe = k.CaptureRegexp - flag.Soluce = hex.EncodeToString(k.Checksum) - } else { - if PartialValidation { - flag.Solved = t.HasPartiallySolved(k) - } - if !HideCaseSensitivity { - flag.IgnoreCase = k.IgnoreCase - } - } - - flag.Multiline = k.Multiline - flag.BonusGain = int64(float64(k.BonusGain) * GlobalScoreCoefficient) - - var fl FlagMCQLabel - if fl, err = k.GetMCQJustification(); err == nil { - k.Label = fl.Label - } - - // Treat array flags - flag.Label, flag.Separator, flag.IgnoreOrder, flag.NbLines = k.AnalyzeFlagLabel() - - // Fill more information if the flag is displaied - if flag.Solved == nil { - flag.Placeholder = k.Placeholder - if choices, err := k.GetChoices(); err != nil { - return nil, err - } else if t == nil || WChoiceCoefficient < 0 || k.ChoicesCost == 0 || t.SeeChoices(k) { - flag.Choices = map[string]interface{}{} - for _, c := range choices { - flag.Choices[c.Value] = c.Label - } - } else { - flag.ChoicesCost = int64(float64(k.ChoicesCost) * GlobalScoreCoefficient) - } - } - - // Append to corresponding flags' map - if fl.IdChoice != 0 { - justifiedMCQ[fl.IdChoice] = flag - } else { - exercice.Flags = append(exercice.Flags, flag) - } - } - } - - if mcqs, err := e.GetMCQ(); err != nil { - return nil, err - } else { - for _, mcq := range mcqs { - exercice.NbFlags += 1 - - if !DisplayAllFlags && t != nil && !t.CanSeeFlag(mcq) { - // Dependancy missing, skip the flag for now - continue - } - - m := myTeamFlag{ - Id: mcq.Id, - Type: "mcq", - order: mcq.Order, - Label: mcq.Title, - Choices: map[string]interface{}{}, - } - - soluce := "" - fullySolved := true - if t != nil { - m.PSolved = t.HasPartiallySolved(mcq) - } - - for _, e := range mcq.Entries { - if e.Response { - soluce += "t" - } else { - soluce += "f" - } - - if v, ok := justifiedMCQ[e.Id]; ok { - m.Justify = true - - if m.PSolved != nil || v.Solved != nil { - jc := myTeamMCQJustifiedChoice{ - Label: e.Label, - Value: v.Solved != nil, - Justification: v, - } - - if v.Solved == nil { - fullySolved = false - } - - if PartialValidation && m.PSolved != nil { - jc.Value = e.Response - } - - m.Choices[strconv.Itoa(e.Id)] = jc - } else { - m.Choices[strconv.Itoa(e.Id)] = e.Label - } - } else { - m.Choices[strconv.Itoa(e.Id)] = e.Label - } - } - - if t == nil { - h, _ := ComputeHashedFlag([]byte(soluce), false, false, nil, false) - m.Soluce = hex.EncodeToString(h[:]) - } - - if fullySolved { - m.Solved = m.PSolved - m.PSolved = nil - } - - exercice.Flags = append(exercice.Flags, m) - } - } - - // Sort flags by order - sort.Sort(ByOrder(exercice.Flags)) - } - - // Hash table ordered by exercice Id ret.Exercices[fmt.Sprintf("%d", e.Id)] = exercice } } @@ -421,10 +97,3 @@ func MyJSONTeam(t *Team, started bool) (interface{}, error) { return ret, nil } - -func ReadMyJSON(fd *os.File) (my MyTeam, err error) { - jdec := json.NewDecoder(fd) - - err = jdec.Decode(&my) - return -} diff --git a/libfic/team_stats.go b/libfic/team_stats.go deleted file mode 100644 index 81d5034c..00000000 --- a/libfic/team_stats.go +++ /dev/null @@ -1,106 +0,0 @@ -package fic - -import ( - "fmt" -) - -// statLine is a line of statistics for the file stats.json exposed to players. -type statLine struct { - Tip string `json:"tip"` - Total int `json:"total"` - Solved int `json:"solved"` - Tried int `json:"tried"` - Tries int `json:"tries"` -} - -// teamStats represents the structure of stats.json. -type teamStats struct { - Levels []statLine `json:"levels"` - Themes map[int64]statLine `json:"themes"` -} - -// GetLevel -func (s *teamStats) GetLevel(level int) *statLine { - level -= 1 - - for len(s.Levels) <= level { - s.Levels = append(s.Levels, statLine{ - fmt.Sprintf("Level %d", (len(s.Levels) + 1)), - 0, - 0, - 0, - 0, - }) - } - - return &s.Levels[level] -} - -// GetStats generates statistics for the Team. -func (t Team) GetStats() (interface{}, error) { - return GetTeamsStats(&t) -} - -// GetTeamsStats returns statistics limited to the given Team. -func GetTeamsStats(t *Team) (interface{}, error) { - stat := teamStats{ - []statLine{}, - map[int64]statLine{}, - } - - if themes, err := GetThemes(); err != nil { - return nil, err - } else { - for _, theme := range themes { - total := 0 - solved := 0 - tried := 0 - tries := 0 - - if exercices, err := theme.GetExercices(); err != nil { - return nil, err - } else { - for _, exercice := range exercices { - var lvl int - if lvl, err = exercice.GetLevel(); err != nil { - return nil, err - } - sLvl := stat.GetLevel(lvl) - - total += 1 - sLvl.Total += 1 - - if t != nil { - if b := t.HasSolved(exercice); b != nil { - solved += 1 - sLvl.Solved += 1 - } - } else { - if n, _ := exercice.IsSolved(); n > 0 { - solved += n - sLvl.Solved += 1 - } - } - - try := NbTry(t, exercice) - if try > 0 { - tried += 1 - tries += try - sLvl.Tried += 1 - sLvl.Tries += try - } - } - } - - stat.Themes[theme.Id] = statLine{ - theme.Name, - total, - solved, - tried, - tries, - } - } - - return stat, nil - } -} diff --git a/libfic/theme.go b/libfic/theme.go index fa4cc909..fb6be3ac 100644 --- a/libfic/theme.go +++ b/libfic/theme.go @@ -1,57 +1,25 @@ package fic -import () +import ( + "fmt" +) -const StandaloneExercicesDirectory = "exercices" - -var StandaloneExercicesTheme = Theme{ - Name: "Défis indépendants", - URLId: "_", - Path: StandaloneExercicesDirectory, -} - -// Theme represents a group of challenges, to display to players type Theme struct { - Id int64 `json:"id"` - Language string `json:"lang,omitempty"` - Name string `json:"name"` - Locked bool `json:"locked"` - URLId string `json:"urlid"` - Path string `json:"path"` - Authors string `json:"authors,omitempty"` - Intro string `json:"intro,omitempty"` - Headline string `json:"headline,omitempty"` - Image string `json:"image,omitempty"` - BackgroundColor uint32 `json:"background_color,omitempty"` - PartnerImage string `json:"partner_img,omitempty"` - PartnerLink string `json:"partner_href,omitempty"` - PartnerText string `json:"partner_txt,omitempty"` + Id int64 `json:"id"` + Name string `json:"name"` + Authors string `json:"authors"` } -func (t *Theme) GetId() *int64 { - if t.Id == 0 { - return nil - } - - return &t.Id -} - -// CmpTheme returns true if given Themes are identicals. -func CmpTheme(t1 *Theme, t2 *Theme) bool { - return t1 != nil && t2 != nil && !(t1.Name != t2.Name || t1.URLId != t2.URLId || t1.Path != t2.Path || t1.Authors != t2.Authors || t1.Intro != t2.Intro || t1.Headline != t2.Headline || t1.Image != t2.Image || t1.BackgroundColor != t2.BackgroundColor || t1.PartnerImage != t2.PartnerImage || t1.PartnerLink != t2.PartnerLink || t1.PartnerText != t2.PartnerText) -} - -// GetThemes returns a list of registered Themes from the database. -func GetThemes() ([]*Theme, error) { - if rows, err := DBQuery("SELECT id_theme, name, locked, url_id, path, authors, intro, headline, image, background_color, partner_img, partner_href, partner_text FROM themes"); err != nil { +func GetThemes() ([]Theme, error) { + if rows, err := DBQuery("SELECT id_theme, name, authors FROM themes"); err != nil { return nil, err } else { defer rows.Close() - var themes []*Theme + var themes = make([]Theme, 0) for rows.Next() { - t := &Theme{} - if err := rows.Scan(&t.Id, &t.Name, &t.Locked, &t.URLId, &t.Path, &t.Authors, &t.Intro, &t.Headline, &t.Image, &t.BackgroundColor, &t.PartnerImage, &t.PartnerLink, &t.PartnerText); err != nil { + var t Theme + if err := rows.Scan(&t.Id, &t.Name, &t.Authors); err != nil { return nil, err } themes = append(themes, t) @@ -64,84 +32,27 @@ func GetThemes() ([]*Theme, error) { } } -// GetThemesExtended returns a list of Themes including standalone exercices. -func GetThemesExtended() ([]*Theme, error) { - if themes, err := GetThemes(); err != nil { - return nil, err +func GetTheme(id int) (Theme, error) { + var t Theme + if err := DBQueryRow("SELECT id_theme, name, authors FROM themes WHERE id_theme=?", id).Scan(&t.Id, &t.Name, &t.Authors); err != nil { + return t, err + } + + return t, nil +} + +func CreateTheme(name string, authors string) (Theme, error) { + if res, err := DBExec("INSERT INTO themes (name, authors) VALUES (?, ?)", name, authors); err != nil { + return Theme{}, err + } else if tid, err := res.LastInsertId(); err != nil { + return Theme{}, err } else { - // Append standalone exercices fake-themes - stdthm := &StandaloneExercicesTheme - - if exercices, err := stdthm.GetExercices(); err == nil && len(exercices) > 0 { - themes = append(themes, stdthm) - } - - return themes, nil + return Theme{tid, name, authors}, nil } } -// GetTheme retrieves a Theme from its identifier. -func GetTheme(id int64) (*Theme, error) { - t := &Theme{} - if err := DBQueryRow("SELECT id_theme, name, locked, url_id, path, authors, intro, headline, image, background_color, partner_img, partner_href, partner_text FROM themes WHERE id_theme=?", id).Scan(&t.Id, &t.Name, &t.Locked, &t.URLId, &t.Path, &t.Authors, &t.Intro, &t.Headline, &t.Image, &t.BackgroundColor, &t.PartnerImage, &t.PartnerLink, &t.PartnerText); err != nil { - return t, err - } - - return t, nil -} - -// GetTheme retrieves a Theme from an given Exercice. -func (e *Exercice) GetTheme() (*Theme, error) { - t := &Theme{} - if err := DBQueryRow("SELECT id_theme, name, locked, url_id, path, authors, intro, headline, image, background_color, partner_img, partner_href, partner_text FROM themes WHERE id_theme=?", e.IdTheme).Scan(&t.Id, &t.Name, &t.Locked, &t.URLId, &t.Path, &t.Authors, &t.Intro, &t.Headline, &t.Image, &t.BackgroundColor, &t.PartnerImage, &t.PartnerLink, &t.PartnerText); err != nil { - return t, err - } - - return t, nil -} - -// GetThemeByName retrieves a Theme from its title -func GetThemeByName(name string) (*Theme, error) { - t := &Theme{} - if err := DBQueryRow("SELECT id_theme, name, locked, url_id, path, authors, intro, headline, image, background_color, partner_img, partner_text FROM themes WHERE name=?", name).Scan(&t.Id, &t.Name, &t.Locked, &t.URLId, &t.Path, &t.Authors, &t.Intro, &t.Headline, &t.Image, &t.BackgroundColor, &t.PartnerImage, &t.PartnerText); err != nil { - return t, err - } - - return t, nil -} - -// GetThemeByPath retrieves a Theme from its dirname -func GetThemeByPath(dirname string) (*Theme, error) { - t := &Theme{} - err := DBQueryRow("SELECT id_theme, name, locked, url_id, path, authors, intro, headline, image, background_color, partner_img, partner_href, partner_text FROM themes WHERE path=?", dirname).Scan(&t.Id, &t.Name, &t.Locked, &t.URLId, &t.Path, &t.Authors, &t.Intro, &t.Headline, &t.Image, &t.BackgroundColor, &t.PartnerImage, &t.PartnerLink, &t.PartnerText) - - return t, err -} - -// CreateTheme creates and fills a new struct Theme and registers it into the database. -func CreateTheme(theme *Theme) (*Theme, error) { - if res, err := DBExec("INSERT INTO themes (name, locked, url_id, authors, path, intro, headline, image, background_color, partner_img, partner_href, partner_text) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)", theme.Name, theme.Locked, theme.URLId, theme.Authors, theme.Path, theme.Intro, theme.Headline, theme.Image, theme.BackgroundColor, theme.PartnerImage, theme.PartnerLink, theme.PartnerText); err != nil { - return nil, err - } else if theme.Id, err = res.LastInsertId(); err != nil { - return nil, err - } else { - return theme, nil - } -} - -// FixURLId generates a valid URLid from the Theme Title. -// It returns true if something has been generated. -func (t *Theme) FixURLId() bool { - if t.URLId == "" { - t.URLId = ToURLid(t.Name) - return true - } - return false -} - -// Update applies modifications back to the database. -func (t *Theme) Update() (int64, error) { - if res, err := DBExec("UPDATE themes SET name = ?, locked = ?, url_id = ?, authors = ?, path = ?, intro = ?, headline = ?, image = ?, background_color = ?, partner_img = ?, partner_href = ?, partner_text = ? WHERE id_theme = ?", t.Name, t.Locked, t.URLId, t.Authors, t.Path, t.Intro, t.Headline, t.Image, t.BackgroundColor, t.PartnerImage, t.PartnerLink, t.PartnerText, t.Id); err != nil { +func (t Theme) Update() (int64, error) { + if res, err := DBExec("UPDATE themes SET name = ?, authors = ? WHERE id_theme = ?", t.Name, t.Authors, t.Id); err != nil { return 0, err } else if nb, err := res.RowsAffected(); err != nil { return 0, err @@ -150,8 +61,7 @@ func (t *Theme) Update() (int64, error) { } } -// Delete the theme from the database. -func (t *Theme) Delete() (int64, error) { +func (t Theme) Delete() (int64, error) { if res, err := DBExec("DELETE FROM themes WHERE id_theme = ?", t.Id); err != nil { return 0, err } else if nb, err := res.RowsAffected(); err != nil { @@ -161,54 +71,45 @@ func (t *Theme) Delete() (int64, error) { } } -func (t *Theme) deleteFunc(f func(*Exercice) (int64, error)) (int64, error) { - exercices, err := t.GetExercices() - if err != nil { - return 0, err - } +type ExportedExercice struct { + Title string `json:"title"` + Gain int64 `json:"gain"` + Solved int64 `json:"solved"` + Tried int64 `json:"tried"` +} - for _, exercice := range exercices { - _, err := f(exercice) - if err != nil { - return 0, err +type exportedTheme struct { + Name string `json:"name"` + Authors string `json:"authors"` + Exercices map[string]ExportedExercice `json:"exercices"` +} + +func ExportThemes() (interface{}, error) { + if themes, err := GetThemes(); err != nil { + return nil, err + } else { + ret := map[string]exportedTheme{} + for _, theme := range themes { + if exercices, err := theme.GetExercices(); err != nil { + return nil, err + } else { + exos := map[string]ExportedExercice{} + for _, exercice := range exercices { + exos[fmt.Sprintf("%d", exercice.Id)] = ExportedExercice{ + exercice.Title, + exercice.Gain, + exercice.SolvedCount(), + exercice.TriedTeamCount(), + } + } + ret[fmt.Sprintf("%d", theme.Id)] = exportedTheme{ + theme.Name, + theme.Authors[:len(theme.Authors)-1], + exos, + } + } } + + return ret, nil } - - return 1, nil -} - -// DeleteCascade the theme from the database, including inner content but not player content. -func (t *Theme) DeleteCascade() (int64, error) { - _, err := t.deleteFunc(func(e *Exercice) (int64, error) { - return e.DeleteCascade() - }) - if err != nil { - return 0, err - } - - return t.Delete() -} - -// DeleteCascadePlayer delete player content related to this theme. -func (t *Theme) DeleteCascadePlayer() (int64, error) { - _, err := t.deleteFunc(func(e *Exercice) (int64, error) { - return e.DeleteCascadePlayer() - }) - if err != nil { - return 0, err - } - - return 1, nil -} - -// DeleteDeep the theme from the database, including player content. -func (t *Theme) DeleteDeep() (int64, error) { - _, err := t.deleteFunc(func(e *Exercice) (int64, error) { - return e.DeleteDeep() - }) - if err != nil { - return 0, err - } - - return t.Delete() } diff --git a/libfic/theme_export.go b/libfic/theme_export.go deleted file mode 100644 index 95478741..00000000 --- a/libfic/theme_export.go +++ /dev/null @@ -1,130 +0,0 @@ -package fic - -import ( - "fmt" - "path" - "strings" -) - -var GlobalScoreCoefficient float64 = 1 - -// exportedExercice is a structure representing a challenge, as exposed to players. -type exportedExercice struct { - Id int64 `json:"id"` - Title string `json:"title"` - Authors string `json:"authors,omitempty"` - Headline string `json:"headline,omitempty"` - Image string `json:"image,omitempty"` - BackgroundColor string `json:"background_color,omitempty"` - URLId string `json:"urlid"` - Tags []string `json:"tags"` - Gain int64 `json:"gain"` - Coeff float64 `json:"curcoeff"` - Solved int64 `json:"solved"` - Tried int64 `json:"tried"` -} - -// exportedTheme is a structure representing a Theme, as exposed to players. -type exportedTheme struct { - Name string `json:"name,omitempty"` - URLId string `json:"urlid"` - Locked bool `json:"locked,omitempty"` - Authors string `json:"authors,omitempty"` - Headline string `json:"headline,omitempty"` - Intro string `json:"intro,omitempty"` - Image string `json:"image,omitempty"` - BackgroundColor string `json:"background_color,omitempty"` - PartnerImage string `json:"partner_img,omitempty"` - PartnerLink string `json:"partner_href,omitempty"` - PartnerText string `json:"partner_txt,omitempty"` - Exercices []exportedExercice `json:"exercices"` -} - -// Exportedthemes exports themes from the database, to be displayed to players. -func ExportThemes() (interface{}, error) { - if themes, err := GetThemes(); err != nil { - return nil, err - } else { - // Append standalone exercices fake-themes - themes = append(themes, &Theme{ - Name: "Défis indépendants", - URLId: "_", - Path: "exercices", - }) - - ret := map[string]exportedTheme{} - for _, theme := range themes { - exos := []exportedExercice{} - - if exercices, err := theme.GetExercices(); err != nil { - return nil, err - } else if theme.URLId == "_" && len(exercices) == 0 { - // If no standalone exercices, don't append them - continue - } else { - for _, exercice := range exercices { - if exercice.Disabled && theme.Locked { - continue - } - - exoimgpath := "" - if len(exercice.Image) > 0 { - exoimgpath = path.Join(FilesDir, exercice.Image) - } - exobackgroundcolor := "" - if exercice.BackgroundColor > 0 || len(exercice.Image) > 0 { - exobackgroundcolor = fmt.Sprintf("#%06X", exercice.BackgroundColor) - } - - tags, _ := exercice.GetTags() - exos = append(exos, exportedExercice{ - exercice.Id, - exercice.Title, - exercice.Authors, - exercice.Headline, - exoimgpath, - exobackgroundcolor, - exercice.URLId, - tags, - int64(float64(exercice.Gain) * GlobalScoreCoefficient), - exercice.Coefficient, - exercice.SolvedCount(), - exercice.TriedTeamCount(), - }) - } - } - - imgpath := "" - if len(theme.Image) > 0 { - imgpath = path.Join(FilesDir, theme.Image) - } - - thmbackgroundcolor := "" - if theme.BackgroundColor > 0 || len(theme.Image) > 0 { - thmbackgroundcolor = fmt.Sprintf("#%06X", theme.BackgroundColor) - } - - partnerImgpath := "" - if len(theme.PartnerImage) > 0 { - partnerImgpath = path.Join(FilesDir, theme.PartnerImage) - } - - ret[fmt.Sprintf("%d", theme.Id)] = exportedTheme{ - theme.Name, - theme.URLId, - theme.Locked, - theme.Authors, - theme.Headline, - strings.Replace(theme.Intro, "$FILES$", FilesDir, -1), - imgpath, - thmbackgroundcolor, - partnerImgpath, - theme.PartnerLink, - theme.PartnerText, - exos, - } - } - - return ret, nil - } -} diff --git a/libfic/todo.go b/libfic/todo.go deleted file mode 100644 index 9ee09933..00000000 --- a/libfic/todo.go +++ /dev/null @@ -1,444 +0,0 @@ -package fic - -import ( - "database/sql" - "fmt" - "path" - "time" -) - -// Claim represents an issue, a bug or a ToDo item. -type Claim struct { - Id int64 `json:"id"` - Subject string `json:"subject"` - IdTeam *int64 `json:"id_team"` - IdExercice *int64 `json:"id_exercice"` - IdAssignee *int64 `json:"id_assignee"` - Creation time.Time `json:"creation"` - State string `json:"state"` - Priority string `json:"priority"` -} - -// GetClaim retrieves the claim with the given identifier. -func GetClaim(id int64) (c *Claim, err error) { - c = &Claim{} - err = DBQueryRow("SELECT id_claim, subject, id_team, id_exercice, id_assignee, creation, state, priority FROM claims WHERE id_claim = ?", id).Scan(&c.Id, &c.Subject, &c.IdTeam, &c.IdExercice, &c.IdAssignee, &c.Creation, &c.State, &c.Priority) - return -} - -// GetClaims returns a list of all Claim registered in the database. -func GetClaims() (res []*Claim, err error) { - var rows *sql.Rows - if rows, err = DBQuery("SELECT id_claim, subject, id_team, id_exercice, id_assignee, creation, state, priority FROM claims"); err != nil { - return - } - defer rows.Close() - - for rows.Next() { - c := &Claim{} - if err = rows.Scan(&c.Id, &c.Subject, &c.IdTeam, &c.IdExercice, &c.IdAssignee, &c.Creation, &c.State, &c.Priority); err != nil { - return - } - res = append(res, c) - } - err = rows.Err() - - return -} - -// GetClaim retrieves the claim with the given identifier and registered for the given Team. -func (t *Team) GetClaim(id int64) (c *Claim, err error) { - c = &Claim{} - err = DBQueryRow("SELECT id_claim, subject, id_team, id_exercice, id_assignee, creation, state, priority FROM claims WHERE id_claim = ? AND id_team = ?", id, t.Id).Scan(&c.Id, &c.Subject, &c.IdTeam, &c.IdExercice, &c.IdAssignee, &c.Creation, &c.State, &c.Priority) - return -} - -// GetClaims returns a list of all Claim registered for the Team. -func (t *Team) GetClaims() (res []*Claim, err error) { - var rows *sql.Rows - if rows, err = DBQuery("SELECT id_claim, subject, id_team, id_exercice, id_assignee, creation, state, priority FROM claims WHERE id_team = ?", t.Id); err != nil { - return nil, err - } - defer rows.Close() - - for rows.Next() { - c := &Claim{} - if err = rows.Scan(&c.Id, &c.Subject, &c.IdTeam, &c.IdExercice, &c.IdAssignee, &c.Creation, &c.State, &c.Priority); err != nil { - return - } - res = append(res, c) - } - err = rows.Err() - - return -} - -// GetExercices returns a list of all Claim registered for the Exercice. -func (e *Exercice) GetClaims() (res []*Claim, err error) { - var rows *sql.Rows - if rows, err = DBQuery("SELECT id_claim, subject, id_team, id_exercice, id_assignee, creation, state, priority FROM claims WHERE id_exercice = ?", e.Id); err != nil { - return nil, err - } - defer rows.Close() - - for rows.Next() { - c := &Claim{} - if err = rows.Scan(&c.Id, &c.Subject, &c.IdTeam, &c.IdExercice, &c.IdAssignee, &c.Creation, &c.State, &c.Priority); err != nil { - return - } - res = append(res, c) - } - err = rows.Err() - - return -} - -// NewClaim creates and fills a new struct Claim and registers it into the database. -func NewClaim(subject string, team *Team, exercice *Exercice, assignee *ClaimAssignee, priority string) (*Claim, error) { - var tid *int64 - if team == nil { - tid = nil - } else { - tid = &team.Id - } - - var eid *int64 - if exercice == nil { - eid = nil - } else { - eid = &exercice.Id - } - - var aid *int64 - if assignee == nil { - aid = nil - } else { - aid = &assignee.Id - } - - if res, err := DBExec("INSERT INTO claims (subject, id_team, id_exercice, id_assignee, creation, state, priority) VALUES (?, ?, ?, ?, ?, ?, ?)", subject, tid, eid, aid, time.Now(), "new", priority); err != nil { - return nil, err - } else if cid, err := res.LastInsertId(); err != nil { - return nil, err - } else { - return &Claim{cid, subject, tid, eid, aid, time.Now(), "new", priority}, nil - } -} - -// GetTeam returns the Team linked to the issue, if any. -func (c *Claim) GetTeam() (*Team, error) { - if c.IdTeam == nil { - return nil, nil - } else if t, err := GetTeam(*c.IdTeam); err != nil { - return nil, err - } else { - return t, nil - } -} - -// SetTeam defines the Team that is linked to this issue. -func (c *Claim) SetTeam(t Team) { - c.IdTeam = &t.Id -} - -// GetExercice returns the Exercice linked to the issue, if any. -func (c *Claim) GetExercice() (*Exercice, error) { - if c.IdExercice == nil { - return nil, nil - } else if e, err := GetExercice(*c.IdExercice); err != nil { - return nil, err - } else { - return e, nil - } -} - -// SetExercice defines the Exercice that is linked to this issue. -func (c *Claim) SetExercice(e Exercice) { - c.IdExercice = &e.Id -} - -// Update applies modifications back to the database. -func (c *Claim) Update() (int64, error) { - if res, err := DBExec("UPDATE claims SET subject = ?, id_team = ?, id_exercice = ?, id_assignee = ?, creation = ?, state = ?, priority = ? WHERE id_claim = ?", c.Subject, c.IdTeam, c.IdExercice, c.IdAssignee, c.Creation, c.State, c.Priority, c.Id); err != nil { - return 0, err - } else if nb, err := res.RowsAffected(); err != nil { - return 0, err - } else { - return nb, err - } -} - -// Delete the issue from the database. -func (c *Claim) Delete() (int64, error) { - if res, err := DBExec("DELETE FROM claims WHERE id_claim = ?", c.Id); err != nil { - return 0, err - } else if nb, err := res.RowsAffected(); err != nil { - return 0, err - } else { - return nb, err - } -} - -// ClearClaims removes all issues from database. -func ClearClaims() (int64, error) { - if res, err := DBExec("DELETE FROM claims"); err != nil { - return 0, err - } else if nb, err := res.RowsAffected(); err != nil { - return 0, err - } else { - return nb, err - } -} - -// ClaimDescription represents some text describing an issue. -type ClaimDescription struct { - Id int64 `json:"id"` - // IdAssignee stores the user who handle the claim (or 0 if nobody handles it). - IdAssignee int64 `json:"id_assignee"` - // Content is the raw description. - Content string `json:"content"` - // Date is the timestamp when the description was written. - Date time.Time `json:"date"` - // Publish indicates wether it is shown back to the team. - Publish bool `json:"publish"` -} - -// GetLastUpdate returns the date of the latest message written for the given Claim. -func (c *Claim) GetLastUpdate() (res *time.Time, err error) { - err = DBQueryRow("SELECT MAX(date) FROM claim_descriptions WHERE id_claim = ? GROUP BY id_claim", c.Id).Scan(&res) - return -} - -// GetDescriptions returns a list of all descriptions stored in the database for the Claim. -func (c *Claim) GetDescriptions() (res []*ClaimDescription, err error) { - var rows *sql.Rows - if rows, err = DBQuery("SELECT id_description, id_assignee, content, date, publish FROM claim_descriptions WHERE id_claim = ?", c.Id); err != nil { - return nil, err - } - defer rows.Close() - - for rows.Next() { - d := &ClaimDescription{} - if err = rows.Scan(&d.Id, &d.IdAssignee, &d.Content, &d.Date, &d.Publish); err != nil { - return - } - res = append(res, d) - } - err = rows.Err() - - return -} - -// AddDescription append in the database a new description; then returns the corresponding structure. -func (c *Claim) AddDescription(content string, assignee *ClaimAssignee, publish bool) (*ClaimDescription, error) { - var assignee_id *int64 - if assignee != nil { - assignee_id = &assignee.Id - } - - if res, err := DBExec("INSERT INTO claim_descriptions (id_claim, id_assignee, content, date, publish) VALUES (?, ?, ?, ?, ?)", c.Id, assignee_id, content, time.Now(), publish); err != nil { - return nil, err - } else if did, err := res.LastInsertId(); err != nil { - return nil, err - } else { - return &ClaimDescription{did, assignee.Id, content, time.Now(), publish}, nil - } -} - -// GetAssignee retrieves an assignee from its identifier. -func (d *ClaimDescription) GetAssignee() (a *ClaimAssignee, err error) { - a = &ClaimAssignee{} - err = DBQueryRow("SELECT id_assignee, name FROM claim_assignees WHERE id_assignee = ?", d.IdAssignee).Scan(&a.Id, &a.Name) - return -} - -// Update applies modifications back to the database -func (d *ClaimDescription) Update() (int64, error) { - if res, err := DBExec("UPDATE claim_descriptions SET id_assignee = ?, content = ?, date = ?, publish = ? WHERE id_description = ?", d.IdAssignee, d.Content, d.Date, d.Publish, d.Id); err != nil { - return 0, err - } else if nb, err := res.RowsAffected(); err != nil { - return 0, err - } else { - return nb, err - } -} - -// Delete the description in the database. -func (d *ClaimDescription) Delete() (int64, error) { - if res, err := DBExec("DELETE FROM claim_descriptions WHERE id_description = ?", d.Id); err != nil { - return 0, err - } else if nb, err := res.RowsAffected(); err != nil { - return 0, err - } else { - return nb, err - } -} - -// ClaimAssignee represents a user that can handle claims. -type ClaimAssignee struct { - Id int64 `json:"id"` - Name string `json:"name"` -} - -// GetAssignee retrieves an assignee from its identifier. -func GetAssignee(id int64) (a *ClaimAssignee, err error) { - a = &ClaimAssignee{} - err = DBQueryRow("SELECT id_assignee, name FROM claim_assignees WHERE id_assignee = ?", id).Scan(&a.Id, &a.Name) - return -} - -// GetAssignees returns a list of all assignees found in the database. -func GetAssignees() (res []*ClaimAssignee, err error) { - var rows *sql.Rows - if rows, err = DBQuery("SELECT id_assignee, name FROM claim_assignees"); err != nil { - return - } - defer rows.Close() - - for rows.Next() { - a := &ClaimAssignee{} - if err = rows.Scan(&a.Id, &a.Name); err != nil { - return - } - res = append(res, a) - } - err = rows.Err() - - return -} - -// NewClaimAssignee creates and fills a new struct ClaimAssignee and registers it into the database. -func NewClaimAssignee(name string) (*ClaimAssignee, error) { - if res, err := DBExec("INSERT INTO claim_assignees (name) VALUES (?)", name); err != nil { - return nil, err - } else if aid, err := res.LastInsertId(); err != nil { - return nil, err - } else { - return &ClaimAssignee{aid, name}, nil - } -} - -// Update applies modifications back to the database -func (a *ClaimAssignee) Update() (int64, error) { - if res, err := DBExec("UPDATE claim_assignees SET name = ? WHERE id_assignee = ?", a.Name, a.Id); err != nil { - return 0, err - } else if nb, err := res.RowsAffected(); err != nil { - return 0, err - } else { - return nb, err - } -} - -// Delete the assignee in the database. -func (a *ClaimAssignee) Delete() (int64, error) { - if res, err := DBExec("DELETE FROM claim_assignees WHERE id_assignee = ?", a.Id); err != nil { - return 0, err - } else if nb, err := res.RowsAffected(); err != nil { - return 0, err - } else { - return nb, err - } -} - -// ClearAssignees removes all assignees from database. -func ClearAssignees() (int64, error) { - if res, err := DBExec("DELETE FROM claim_assignees"); err != nil { - return 0, err - } else if nb, err := res.RowsAffected(); err != nil { - return 0, err - } else { - return nb, err - } -} - -// GetAssignee returns the assignee assigned to the claim. -func (c *Claim) GetAssignee() (*ClaimAssignee, error) { - if c.IdAssignee == nil { - return nil, nil - } else if a, err := GetAssignee(*c.IdAssignee); err != nil { - return nil, err - } else { - return a, nil - } -} - -// SetAssignee defines the assignee that'll handle the claim. -func (c *Claim) SetAssignee(a ClaimAssignee) { - c.IdAssignee = &a.Id -} - -type teamIssueText struct { - Content string `json:"cnt"` - Assignee string `json:"assignee"` - Date time.Time `json:"date"` -} - -type teamIssueFile struct { - Id int64 `json:"id"` - Subject string `json:"subject"` - Exercice *string `json:"exercice,omitempty"` - ExerciceURL string `json:"url,omitempty"` - Assignee *string `json:"assignee,omitempty"` - State string `json:"state"` - Priority string `json:"priority"` - Texts []teamIssueText `json:"texts"` -} - -func (t *Team) MyIssueFile() (ret []teamIssueFile, err error) { - var claims []*Claim - if claims, err = t.GetClaims(); err == nil { - for _, claim := range claims { - var exercice *string = nil - var url string - - if exo, err := claim.GetExercice(); err == nil && exo != nil { - exercice = &exo.Title - if exo.IdTheme == nil { - url = path.Join("_", exo.URLId) - } else if theme, err := GetTheme(*exo.IdTheme); err == nil { - url = path.Join(theme.URLId, exo.URLId) - } - } - - var assignee *string = nil - if a, err := claim.GetAssignee(); err == nil && a != nil { - assignee = &a.Name - } - - if descriptions, err := claim.GetDescriptions(); err != nil { - return nil, fmt.Errorf("error occurs during description retrieval (cid=%d): %w", claim.Id, err) - } else { - tif := teamIssueFile{ - Id: claim.Id, - Subject: claim.Subject, - Exercice: exercice, - ExerciceURL: url, - Assignee: assignee, - State: claim.State, - Priority: claim.Priority, - Texts: []teamIssueText{}, - } - - for _, description := range descriptions { - if description.Publish { - if people, err := description.GetAssignee(); err != nil { - return nil, fmt.Errorf("error ocurs during assignee retrieval (aid=%d): %w", description.IdAssignee, err) - } else { - tif.Texts = append(tif.Texts, teamIssueText{ - Content: description.Content, - Assignee: people.Name, - Date: description.Date, - }) - } - } - } - - ret = append(ret, tif) - } - } - } else { - err = fmt.Errorf("error occurs during claim retrieval: %w", err) - } - - return -} diff --git a/libfic/utils.go b/libfic/utils.go deleted file mode 100644 index 42aecc90..00000000 --- a/libfic/utils.go +++ /dev/null @@ -1,198 +0,0 @@ -package fic - -import ( - "bytes" - "crypto/md5" - "math/rand" - "regexp" - "strings" -) - -// ToURLid converts the given string to a valid URLid. -func ToURLid(str string) string { - re_a := regexp.MustCompile("[áàâäā]") - str = re_a.ReplaceAllLiteralString(str, "a") - re_A := regexp.MustCompile("[ÀÁÂÄĀ]") - str = re_A.ReplaceAllLiteralString(str, "A") - - re_c := regexp.MustCompile("[ç]") - str = re_c.ReplaceAllLiteralString(str, "c") - re_C := regexp.MustCompile("[Ç]") - str = re_C.ReplaceAllLiteralString(str, "C") - - re_e := regexp.MustCompile("[éèêëȩē]") - str = re_e.ReplaceAllLiteralString(str, "e") - re_E := regexp.MustCompile("[ÉÈÊËĒ]") - str = re_E.ReplaceAllLiteralString(str, "E") - - re_i := regexp.MustCompile("[íìîïī]") - str = re_i.ReplaceAllLiteralString(str, "i") - re_I := regexp.MustCompile("[ÌÍÎÏĪ]") - str = re_I.ReplaceAllLiteralString(str, "I") - - re_l := regexp.MustCompile("[ł]") - str = re_l.ReplaceAllLiteralString(str, "l") - - re_o := regexp.MustCompile("[òóôöō]") - str = re_o.ReplaceAllLiteralString(str, "o") - re_O := regexp.MustCompile("[ÒÓÔÖŌ]") - str = re_O.ReplaceAllLiteralString(str, "O") - - re_oe := regexp.MustCompile("[œ]") - str = re_oe.ReplaceAllLiteralString(str, "oe") - re_OE := regexp.MustCompile("[Œ]") - str = re_OE.ReplaceAllLiteralString(str, "OE") - - re_u := regexp.MustCompile("[ùúûüū]") - str = re_u.ReplaceAllLiteralString(str, "u") - re_U := regexp.MustCompile("[ÙÚÛÜŪ]") - str = re_U.ReplaceAllLiteralString(str, "U") - - re_y := regexp.MustCompile("[ỳýŷÿȳ]") - str = re_y.ReplaceAllLiteralString(str, "y") - re_Y := regexp.MustCompile("[ỲÝŶŸȲ]") - str = re_Y.ReplaceAllLiteralString(str, "Y") - - re := regexp.MustCompile("[^a-zA-Z0-9]+") - return strings.TrimSuffix(re.ReplaceAllLiteralString(str, "-"), "-") -} - -// Apr1Md5 computes a usable hash for basic auth in Apache and nginx. -// Function copied from https://github.com/jimstudt/http-authentication/blob/master/basic/md5.go -// as it was not exported in package and comes with other unwanted functions. -// Original source code under MIT. -func Apr1Md5(password string, salt string) string { - initBin := md5.Sum([]byte(password + salt + password)) - - initText := bytes.NewBufferString(password + "$apr1$" + salt) - - for i := len(password); i > 0; i -= 16 { - lim := i - if lim > 16 { - lim = 16 - } - initText.Write(initBin[0:lim]) - } - - for i := len(password); i > 0; i >>= 1 { - if (i & 1) == 1 { - initText.WriteByte(byte(0)) - } else { - initText.WriteByte(password[0]) - } - } - - bin := md5.Sum(initText.Bytes()) - - n := bytes.NewBuffer([]byte{}) - - for i := 0; i < 1000; i++ { - n.Reset() - - if (i & 1) == 1 { - n.WriteString(password) - } else { - n.Write(bin[:]) - } - - if i%3 != 0 { - n.WriteString(salt) - } - - if i%7 != 0 { - n.WriteString(password) - } - - if (i & 1) == 1 { - n.Write(bin[:]) - } else { - n.WriteString(password) - } - - bin = md5.Sum(n.Bytes()) - } - - result := bytes.NewBuffer([]byte{}) - - fill := func(a byte, b byte, c byte) { - v := (uint(a) << 16) + (uint(b) << 8) + uint(c) - - for i := 0; i < 4; i++ { - result.WriteByte("./0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"[v&0x3f]) - v >>= 6 - } - } - - fill(bin[0], bin[6], bin[12]) - fill(bin[1], bin[7], bin[13]) - fill(bin[2], bin[8], bin[14]) - fill(bin[3], bin[9], bin[15]) - fill(bin[4], bin[10], bin[5]) - fill(0, 0, bin[11]) - - resultString := string(result.Bytes()[0:22]) - - return resultString -} - -// Function copied from https://github.com/gerow/go-color/blob/master/color.go -type HSL struct { - H, S, L float64 -} - -func hueToRGB(v1, v2, h float64) uint32 { - if h < 0 { - h += 1 - } - if h > 1 { - h -= 1 - } - var res float64 - switch { - case 6*h < 1: - res = (v1 + (v2-v1)*6*h) - case 2*h < 1: - res = v2 - case 3*h < 2: - res = v1 + (v2-v1)*((2.0/3.0)-h)*6 - default: - res = v1 - } - return uint32((res + 1/512.0) * 255) - -} - -func (c HSL) ToRGB() (rgb uint32) { - h := c.H - s := c.S - l := c.L - - if s == 0 { - // it's gray - v := uint32((l + 1/512.0) * 255) - return v*65536 + v*256 + v - } - - var v1, v2 float64 - if l < 0.5 { - v2 = l * (1 + s) - } else { - v2 = (l + s) - (s * l) - } - - v1 = 2*l - v2 - - r := hueToRGB(v1, v2, h+(1.0/3.0)) - g := hueToRGB(v1, v2, h) - b := hueToRGB(v1, v2, h-(1.0/3.0)) - - return r*65536 + g*256 + b -} - -func RandomColor() HSL { - return HSL{ - H: rand.Float64(), - S: 1, - L: 0.5, - } -} diff --git a/libfic/zqsd.go b/libfic/zqsd.go deleted file mode 100644 index e585aaf1..00000000 --- a/libfic/zqsd.go +++ /dev/null @@ -1,100 +0,0 @@ -package fic - -import ( - "fmt" - - "srs.epita.fr/fic-server/settings" -) - -type WorkZoneRoleZQDS struct { - Name string `json:"name"` - Accesses []interface{} `json:"acceses"` -} - -type WorkZoneZQDS struct { - Name string `json:"name"` - Duplicable bool `json:"duplicable"` - Roles []WorkZoneRoleZQDS `json:"roles"` -} - -type ChallengeRequirementsZQDS struct { - Challenges []string `json:"challenges"` -} - -type ChallengeZQDS struct { - Name string `json:"name"` - Type string `json:"type,omitempty"` - Category string `json:"category"` - Points int64 `json:"points"` - FirstBloodBonus []float64 `json:"first_blood_bonus,omitempty"` - Description string `json:"description"` - Flags []string `json:"flags,omitempty"` - Requirements *ChallengeRequirementsZQDS `json:"requirements,omitempty"` -} - -type SessionZQDS struct { - Name string `json:"name"` - BackgroundURL string `json:"background_url,omitempty"` - Description string `json:"description"` - Scenario string `json:"scenario,omitempty"` - YourMission string `json:"your_mission"` - Rules string `json:"rules"` - Duration string `json:"duration"` - WorkZones []WorkZoneZQDS `json:"work_zones,omitempty"` - Challenges []*ChallengeZQDS `json:"challenges"` -} - -func GenZQDSSessionFile(c *settings.ChallengeInfo, s *settings.Settings) (*SessionZQDS, error) { - themes, err := GetThemes() - if err != nil { - return nil, err - } - - if s.End == nil { - return nil, fmt.Errorf("Please define an end date") - } - - var challenges []*ChallengeZQDS - - for _, th := range themes { - exos, err := th.GetExercices() - if err != nil { - return nil, err - } - - for _, ex := range exos { - var requirements *ChallengeRequirementsZQDS - - if ex.Depend != nil { - exdep, err := GetExercice(*ex.Depend) - if err != nil { - return nil, fmt.Errorf("unable to find dependancy: %w", err) - } - - requirements = &ChallengeRequirementsZQDS{} - requirements.Challenges = append(requirements.Challenges, exdep.Title) - } - - challenges = append(challenges, &ChallengeZQDS{ - Name: ex.Title, - Type: "first_blood", - Category: th.Name, - Points: ex.Gain, - FirstBloodBonus: []float64{ - float64(ex.Gain) * (1 + s.FirstBlood), - }, - Description: ex.Overview, - Requirements: requirements, - }) - } - } - - return &SessionZQDS{ - Name: c.Title, - Description: c.Description, - Rules: c.Rules, - YourMission: c.YourMission, - Duration: fmt.Sprintf("%d:%d:00", c.ExpectedDuration/60, c.ExpectedDuration%60), - Challenges: challenges, - }, nil -} diff --git a/nixos/.sops.yaml b/nixos/.sops.yaml deleted file mode 100644 index 60d021e8..00000000 --- a/nixos/.sops.yaml +++ /dev/null @@ -1,17 +0,0 @@ -keys: - # Add key signature below - - &admin_antoine C8CEBB1753433CCCD2AF0638BD721F0A3BAE578C - - # Update this signature with phobos' - # Run the following line to get the fingerprint and the public key of Phobos - # ``` - # ssh root@phobos "cat /etc/ssh/ssh_host_rsa_key" | nix-shell -p ssh-to-pgp --run "ssh-to-pgp -o phobos.asc" - # ``` - # You have to import the key afterward using `gpg --import phobos.asc` - - &srv_phobos 9cb1fda8a56fa7ab852f666fc3592125321adf42 # replace this fingerprint with the new one `gpg --list-keys` -creation_rules: - - path: secrets/phobos.yaml - key_groups: - - pgp: - - *admin_antoine - - *srv_phobos diff --git a/nixos/README.md b/nixos/README.md deleted file mode 100644 index 4e755e3a..00000000 --- a/nixos/README.md +++ /dev/null @@ -1,10 +0,0 @@ -# NixOS configuration - -## Building - -```bash -# For backend (Phobos) -nixos-rebuild switch --flake /path/to/flake.nix/directory/#phobos -# For frontend (Deimos) -nixos-rebuild switch --flake /path/to/flake.nix/directory/#deimos -``` diff --git a/nixos/backend/backend.nix b/nixos/backend/backend.nix deleted file mode 100644 index c701948f..00000000 --- a/nixos/backend/backend.nix +++ /dev/null @@ -1,60 +0,0 @@ -{ config, lib, pkgs, ... }: -{ - imports = [ - ./db.nix - ./fic-admin.nix - ./fic-checker.nix - ./fic-dashboard.nix - ./fic-evdist.nix - ./fic-generator.nix - ./fic-synchro.nix - ]; - - config.sops = { - defaultSopsFile = ../secrets/phobos.yml; # We are currently in /nix/store/...-source/backend/ - secrets.phobos_ssh = { mode = "0400"; }; - # You may need to manualy remove `/run/secrets` if modified - }; - - config.system.activationScripts = { - # Create /var/lib/fic/** directories - makeFicDirs = lib.stringAfter [ "var" ] '' - mkdir -p /var/lib/fic/dashboard; - mkdir -p /var/lib/fic/files; - mkdir -p /var/lib/fic/pki; - mkdir -p /var/lib/fic/raw_files; - mkdir -p /var/lib/fic/settings; - mkdir -p /var/lib/fic/settingsdist; - mkdir -p /var/lib/fic/ssh; - mkdir -p /var/lib/fic/submissions; - mkdir -p /var/lib/fic/sync; - mkdir -p /var/lib/fic/teams; - mkdir -p /var/log/frontend; - ''; - # Create docker network - createDockerNetworkPhobos = - let - docker = config.virtualisation.oci-containers.backend; - dockerBin = "${pkgs.${docker}}/bin/${docker}"; - in - '' - ${dockerBin} network inspect phobos-lan >/dev/null 2>&1 \ - || ${dockerBin} network create phobos-lan --subnet 172.18.0.0/24 - ''; - }; - - config = { - networking.hostName = "phobos"; - - # This is needed to install fic related pkgs - nixpkgs.config.allowUnfree = true; - - # To switch, remove `phobos-lan` from the networks before running nixos-rebuild - # ``` - # ${dockerBin} network rm phobos-lan - # ``` - virtualisation.docker.enable = true; - virtualisation.podman.enable = false; - virtualisation.oci-containers.backend = "docker"; - }; -} diff --git a/nixos/backend/db.nix b/nixos/backend/db.nix deleted file mode 100644 index 135529f9..00000000 --- a/nixos/backend/db.nix +++ /dev/null @@ -1,24 +0,0 @@ -{ config, ... }: -{ - config.virtualisation.oci-containers.containers.mariadb = { - image = "mariadb:latest"; - cmd = [ - "/bin/bash" - "/usr/local/bin/docker-entrypoint.sh" - "mysqld" - ]; - ports = [ "3306:3306" ]; - extraOptions = [ "--network=phobos-lan" "--ip=172.18.0.42" ]; - environment = { - MYSQL_DATABASE = "fic"; - MYSQL_USER = "fic"; - MYSQL_PASSWORD = "fic"; - MYSQL_RANDOM_ROOT_PASSWORD = "yes"; - }; - volumes = [ - "/etc/hosts:/etc/hosts:ro" - "/etc/mysql/conf.d:/etc/mysql/conf.d:ro" - "/var/lib/fic/mysql:/var/lib/mysql" - ]; - }; -} diff --git a/nixos/backend/fic-admin.nix b/nixos/backend/fic-admin.nix deleted file mode 100644 index ac758e70..00000000 --- a/nixos/backend/fic-admin.nix +++ /dev/null @@ -1,40 +0,0 @@ -{ config, inputs, pkgs, ... }: -{ - config.virtualisation.oci-containers.containers.fic-admin = { - image = "fic-admin:latest"; - imageFile = pkgs.dockerTools.buildImage { - name = "fic-admin"; - tag = "latest"; - created = "now"; - config = { - Cmd = [ "${inputs.ficpkgs.packages.x86_64-linux.fic-admin}/bin/admin" ]; - }; - }; - autoStart = true; - cmd = [ - "${inputs.ficpkgs.packages.x86_64-linux.fic-admin}/bin/admin" - "-4real" - "-bind=0.0.0.0:8081" - "-baseurl=/admin/" - "-localimport=/mnt/fic" - "-timestampCheck=/srv/submissions" - ]; - ports = [ "8081:8081" ]; - extraOptions = [ "--network=phobos-lan" "--ip=172.18.0.40" ]; - environment = { - MYSQL_HOST = "db"; - FICCA_PASS = "jee8AhloAith1aesCeQu5ahgIegaeM4K"; - }; - volumes = [ - "/etc/hosts:/etc/hosts:ro" - "/var/lib/fic/generator:/srv/GENERATOR:ro" - "/var/lib/fic/raw_files:/mnt/fic" - "/var/lib/fic/dashboard:/srv/DASHBOARD" - "/var/lib/fic/files:/srv/FILES" - "/var/lib/fic/pki:/srv/PKI" - "/var/lib/fic/teams:/srv/TEAMS:ro" - "/var/lib/fic/settings:/srv/SETTINGS" - "/var/lib/fic/submissions:/srv/submissions:ro" - ]; - }; -} diff --git a/nixos/backend/fic-checker.nix b/nixos/backend/fic-checker.nix deleted file mode 100644 index 70403fc7..00000000 --- a/nixos/backend/fic-checker.nix +++ /dev/null @@ -1,27 +0,0 @@ -{ config, inputs, pkgs, ... }: -{ - config.virtualisation.oci-containers.containers.fic-checker = { - image = "fic-checker:latest"; - imageFile = pkgs.dockerTools.buildImage { - name = "fic-checker"; - tag = "latest"; - created = "now"; - config = { - Cmd = [ "${inputs.ficpkgs.packages.x86_64-linux.fic-checker}/bin/checker" ]; - }; - }; - autoStart = true; - environment = { - MYSQL_HOST = "db"; - }; - workdir = "/srv"; - extraOptions = [ "--network=phobos-lan" "--ip=172.18.0.41" ]; - volumes = [ - "/etc/hosts:/etc/hosts:ro" - "/var/lib/fic/generator:/srv/GENERATOR:ro" - "/var/lib/fic/teams:/srv/TEAMS:ro" - "/var/lib/fic/settingsdist:/srv/SETTINGSDIST:ro" - "/var/lib/fic/submissions:/srv/submissions" - ]; - }; -} diff --git a/nixos/backend/fic-dashboard.nix b/nixos/backend/fic-dashboard.nix deleted file mode 100644 index 60e76fde..00000000 --- a/nixos/backend/fic-dashboard.nix +++ /dev/null @@ -1,28 +0,0 @@ -{ config, inputs, pkgs, ... }: -{ - config.virtualisation.oci-containers.containers.fic-dashboard = { - image = "fic-dashboard:latest"; - imageFile = pkgs.dockerTools.buildImage { - name = "fic-dashboard"; - tag = "latest"; - created = "now"; - config = { - Cmd = [ "${inputs.ficpkgs.packages.x86_64-linux.fic-dashboard}/bin/dashboard" ]; - }; - }; - autoStart = true; - cmd = [ - "${inputs.ficpkgs.packages.x86_64-linux.fic-dashboard}/bin/dashboard" - "-bind=:8082" - "-restrict-to-ips=/srv/DASHBOARD/restricted-ips.json" - ]; - ports = [ "8082:8082" ]; - volumes = [ - "/etc/hosts:/etc/hosts:ro" - "/var/lib/fic/dashboard:/srv/DASHBOARD:ro" - "/var/lib/fic/files:/srv/FILES:ro" - "/var/lib/fic/teams:/srv/TEAMS:ro" - "/var/lib/fic/settingsdist:/srv/SETTINGSDIST:ro" - ]; - }; -} diff --git a/nixos/backend/fic-evdist.nix b/nixos/backend/fic-evdist.nix deleted file mode 100644 index f229a895..00000000 --- a/nixos/backend/fic-evdist.nix +++ /dev/null @@ -1,21 +0,0 @@ -{ config, inputs, pkgs, ... }: -{ - config.virtualisation.oci-containers.containers.fic-evdist = { - image = "fic-evdist:latest"; - imageFile = pkgs.dockerTools.buildImage { - name = "fic-evdist"; - tag = "latest"; - created = "now"; - config = { - Cmd = [ "${inputs.ficpkgs.packages.x86_64-linux.fic-evdist}/bin/evdist" ]; - }; - }; - autoStart = true; - workdir = "/srv"; - volumes = [ - "/etc/hosts:/etc/hosts:ro" - "/var/lib/fic/settings:/srv/SETTINGS" - "/var/lib/fic/settingsdist:/srv/SETTINGSDIST" - ]; - }; -} diff --git a/nixos/backend/fic-generator.nix b/nixos/backend/fic-generator.nix deleted file mode 100644 index 99f5eb23..00000000 --- a/nixos/backend/fic-generator.nix +++ /dev/null @@ -1,26 +0,0 @@ -{ config, inputs, pkgs, ... }: -{ - config.virtualisation.oci-containers.containers.fic-generator = { - image = "fic-generator:latest"; - imageFile = pkgs.dockerTools.buildImage { - name = "fic-generator"; - tag = "latest"; - created = "now"; - config = { - Cmd = [ "${inputs.ficpkgs.packages.x86_64-linux.fic-generator}/bin/generator" ]; - }; - }; - autoStart = true; - environment = { - MYSQL_HOST = "db"; - }; - workdir = "/srv"; - extraOptions = [ "--network=phobos-lan" "--ip=172.18.0.41" ]; - volumes = [ - "/etc/hosts:/etc/hosts:ro" - "/var/lib/fic/generator:/srv/GENERATOR" - "/var/lib/fic/teams:/srv/TEAMS" - "/var/lib/fic/settingsdist:/srv/SETTINGSDIST:ro" - ]; - }; -} diff --git a/nixos/backend/fic-synchro.nix b/nixos/backend/fic-synchro.nix deleted file mode 100644 index 777a599d..00000000 --- a/nixos/backend/fic-synchro.nix +++ /dev/null @@ -1,39 +0,0 @@ -{ config, inputs, pkgs, ... }: -{ - config.virtualisation.oci-containers.containers.fic-synchro = - { - image = "fic-synchro:latest"; - imageFile = pkgs.dockerTools.buildImage { - name = "fic-synchro"; - tag = "latest"; - created = "now"; - copyToRoot = pkgs.buildEnv { - name = "packagelist"; - paths = [ pkgs.coreutils pkgs.openssh pkgs.rsync ]; - }; - config = { - Cmd = [ "${inputs.ficpkgs.packages.x86_64-linux.fic-synchro}/bin/synchro" ]; - }; - runAsRoot = '' - #!${pkgs.runtimeShell} - ${pkgs.dockerTools.shadowSetup} - mkdir -p /tmp/ - chmod a+rwx /tmp/ - ''; - }; - autoStart = true; - extraOptions = [ "--network=phobos-lan" "--ip=172.18.0.43" ]; - volumes = [ - "/etc/hosts:/etc/hosts:ro" - "/var/lib/fic/ssh:/etc/ssh:ro" - "${config.sops.secrets.phobos_ssh.path}:/root/.ssh/id_ed25519:ro" - "/var/lib/fic/files:/srv/FILES:ro" - #"/var/lib/fic/pki/ca.key:/srv/PKI/ca.key:ro" - "/var/lib/fic/pki/shared:/srv/PKI/shared:ro" - "/var/lib/fic/settingsdist:/srv/SETTINGSDIST:ro" - "/var/lib/fic/submissions:/srv/submissions" - "/var/lib/fic/teams:/srv/TEAMS:ro" - "/var/log/frontend:/var/log/frontend" - ]; - }; -} diff --git a/nixos/bios.nix b/nixos/bios.nix deleted file mode 100644 index ed1b84ce..00000000 --- a/nixos/bios.nix +++ /dev/null @@ -1,6 +0,0 @@ -{}: -{ - boot.loader.grub.enable = true; - boot.loader.grub.version = 2; - boot.loader.grub.device = "/dev/vda"; -} diff --git a/nixos/config-var.nix b/nixos/config-var.nix deleted file mode 100644 index 63a0587e..00000000 --- a/nixos/config-var.nix +++ /dev/null @@ -1,8 +0,0 @@ -{ - efi = false; - prod = false; - ip = { - deimos = "10.10.10.2"; - phobos = "10.10.10.1"; - }; -} diff --git a/nixos/configuration.nix b/nixos/configuration.nix deleted file mode 100644 index 25140b49..00000000 --- a/nixos/configuration.nix +++ /dev/null @@ -1,13 +0,0 @@ -{ config, pkgs, ... }: -{ - imports = [ - ./hardware-configuration.nix - ./locale.nix - ./network.nix - ./packages.nix - ./registry.nix - ./users.nix - ] ++ (if (import ../config-var.nix).efi then [ ./efi.nix ] else [ ./bios.nix ]); - - system.stateVersion = "22.05"; -} diff --git a/nixos/efi.nix b/nixos/efi.nix deleted file mode 100644 index c9b80f2d..00000000 --- a/nixos/efi.nix +++ /dev/null @@ -1,5 +0,0 @@ -{}: -{ - boot.loader.systemd-boot.enable = true; - boot.loader.efi.canTouchEfiVariables = true; -} diff --git a/nixos/flake.nix b/nixos/flake.nix deleted file mode 100644 index ddea475a..00000000 --- a/nixos/flake.nix +++ /dev/null @@ -1,45 +0,0 @@ -{ - description = "Fic Servers Nix Configuration"; - - inputs = { - nixpkgs = { url = "github:nixos/nixpkgs/nixos-unstable"; }; - ficpkgs = { - # Vendor hash of fic-server's flake.nix must be up to date - #url = "git+https://git.nemunai.re/fic/server"; - # For local testing only - url = "/root/fic-server"; - - inputs.nixpkgs.follows = "nixpkgs"; - }; - sops-nix = { - url = "github:thouveninantoine/sops-nix"; - inputs.nixpkgs.follows = "nixpkgs"; - }; - }; - - outputs = inputs: { - nixosConfigurations = - let - common_modules = [ - ./configuration.nix - inputs.sops-nix.nixosModules.sops - "${inputs.nixpkgs}/nixos/modules/installer/cd-dvd/installation-cd-base.nix" - ]; - in - { - phobos = inputs.nixpkgs.lib.nixosSystem { - system = "x86_64-linux"; - modules = [ - ./backend/backend.nix - ] ++ common_modules; - specialArgs = { inherit inputs; }; - }; - deimos = inputs.nixpkgs.lib.nixosSystem { - system = "x86_64-linux"; - modules = [ - ] ++ common_modules; - specialArgs = { inherit inputs; }; - }; - }; - }; -} diff --git a/nixos/locale.nix b/nixos/locale.nix deleted file mode 100644 index 5e1713df..00000000 --- a/nixos/locale.nix +++ /dev/null @@ -1,9 +0,0 @@ -{ ... }: -{ - time.timeZone = "Europe/Paris"; - i18n.defaultLocale = "fr_FR.UTF-8"; - console = { - font = "Lat2-Terminus16"; - keyMap = "fr"; - }; -} diff --git a/nixos/network.nix b/nixos/network.nix deleted file mode 100644 index 16ab36e8..00000000 --- a/nixos/network.nix +++ /dev/null @@ -1,40 +0,0 @@ -{ ... }: -{ - networking.useDHCP = false; - networking.interfaces.eno1.useDHCP = true; - networking.interfaces.enp1s0.useDHCP = true; - - networking.extraHosts = '' - ${(import ./config-var.nix).ip.phobos} phobos - - 172.18.0.40 admin - 172.18.0.41 backend - 172.18.0.42 db - 172.18.0.43 synchro - - ${(import ./config-var.nix).ip.deimos} deimos - - 172.18.1.2 nginx - 172.18.1.3 frontend - 172.18.1.4 auth - - 127.0.0.1 localhost - ::1 localhost ip6-localhost ip6-loopback - fe00::0 ip6-localnet - ff00::0 ip6-mcastprefix - ff02::1 ip6-allnodes - ff02::2 ip6-allrouters - ''; - - services.openssh = { - enable = true; - passwordAuthentication = false; - listenAddresses = [ - { addr = "0.0.0.0"; port = 2222; } - ]; - }; - - networking.firewall.allowedTCPPorts = [ 22 2222 ]; - - systemd.services.sshd.after = [ "network-interfaces.target" ]; -} diff --git a/nixos/packages.nix b/nixos/packages.nix deleted file mode 100644 index 2ac709d2..00000000 --- a/nixos/packages.nix +++ /dev/null @@ -1,14 +0,0 @@ -{ pkgs, ... }: -{ - nix = { - package = pkgs.nixFlakes; - extraOptions = '' - experimental-features = nix-command flakes - ''; - }; - environment.systemPackages = with pkgs; [ - btop - git - neovim - ]; -} diff --git a/nixos/registry.nix b/nixos/registry.nix deleted file mode 100644 index 005ec5a3..00000000 --- a/nixos/registry.nix +++ /dev/null @@ -1,7 +0,0 @@ -{ ... }: -{ - services.dockerRegistry = { - enable = true; - listenAddress = "127.0.0.1"; - }; -} diff --git a/nixos/secrets/deimos.yml b/nixos/secrets/deimos.yml deleted file mode 100644 index 47be46d0..00000000 --- a/nixos/secrets/deimos.yml +++ /dev/null @@ -1,44 +0,0 @@ -phobos_ssh_pub: ENC[AES256_GCM,data:tDmHLPJMuELIU9kU1pCLFL+F6r5YBnkoYqut2RmFmsih4VrSEyfhn8tP+0rnR6k5d/GLhqHkzBuniXhyEGbQ0G/IYmBnJBUpyQFBdnOzCVhrNzQtM2s5zwu5ges=,iv:Ymnw+2BIh7YaoM+8iepOQpUs4heISCwuMdkrS8OWiJg=,tag:IsyoQKp7i+8q9OgH8Dkf5Q==,type:str] -sops: - kms: [] - gcp_kms: [] - azure_kv: [] - hc_vault: [] - age: [] - lastmodified: "2022-09-05T13:14:03Z" - mac: ENC[AES256_GCM,data:NOoNbhyfEmB2aSQrxltZsxt1NQhl+pT9N9hdW/8a/s3VSgEQGt2teRFrqLg5hYOjlDvd4mYoeAOcG7LCkSjzOUdXj8BZYFmxbkEQGKf5n2s8ile8Qr0WofbaMP9nYCBq7R0qL4KPnhoGY6DAzGUULER13mLJKnC6wBueBr0nuio=,iv:e00tosd8DMkuSGLl4Y/SHHSWpqc+ibX2XALglN5sG2s=,tag:xK2Sk+hp77PHU++ew7IXUA==,type:str] - pgp: - - created_at: "2022-08-27T22:15:58Z" - enc: | - -----BEGIN PGP MESSAGE----- - - hF4DhKTlqbJ4OtgSAQdApKk6z/OCHK0Rkaqxd2F27AabN365lxZ2ms8MUGcOVHQw - //9xS2VQqUb2uRT4eEblZuJpNRntFWRHt63AKa31U3cnooysfm+zT4/VdbFF3oqL - 1GgBCQIQ0qDqs10qB+l7uNJJQm7cMecKWsHkDgP9Zj5P0zBR2A81FfZPApC9Jofl - 442PMWoi5GS7CVu4P3WiqGOR+XSX7I6Ih4S/EYsAD338JM4Pll5qps175njNbzqj - wvJf/ONbQR+QYQ== - =7Rq0 - -----END PGP MESSAGE----- - fp: 93A4B95A3623ED8F03CCEBD21ADC2C80A1289824 - - created_at: "2022-08-27T22:15:58Z" - enc: | - -----BEGIN PGP MESSAGE----- - - hQIMA8NZISUyGt9CAQ//d/OZBqdnhWrnF2SPCAp68KJuHgYuE/TitOhG2rNWo+UZ - +n8dzvGdzPmWRXQIIonWw1aVkuFd9I0jvJ7qSN+kYcq9sOAswdMwj6RyGSOyarfK - /XjdGnYRUvTcKKVz2M3Xq15flk5juxlYSmcGpGnDJpyeR3tXRHNRqxWCNFXKQwxR - FIJmCo3LQr87zuuOKl1QEhQ6n67edT7IKK3AQrVlLniNDKaWh6lgI3Q/OVJdJ2BU - yEDCVizRYCuBjQYM+rR9sdcaiK2P+44nw2sL5QNyyqHPnUCi38Cghb2g9EjYo+w1 - KH1335wgqATIiDjae+jGffnNQvxPMz8ZgxebMsqcOWs6NELF7yvHFwTv7sA0Dvkx - dyLraXNK3SUdz1ZLwEDPnYx/tsRMgUMTv80NA8FL4sFnNtBRlJ6mOc74YItFHzBi - errADMOllhFuPVl3yuC1j99HyxUeTNFnoukSi1kNs0dGr7N8+jvWQqIcJgMkv6Mo - P75retb3De5Bcx8XkRpxsN8In3fUUO0xyI2HQykf0ECEHRc7HdZn+2oD3yqk4fME - sp97lW448JslS9Rn4WGWA11TWrz8kUSv+1NOHan/bEkR5HWku8ETyKZTqAWRmwh5 - pCyEmMnjbNUNXA02PuxtNjTLZP2E4agdSs9oQ8MM4xOBIFdNkSLc72PjzcZ/uJjS - WAHdCpTZC393/TbeBdN8A2gmbctMRxdQF6Bilph665eUMml/07zndKF0nZYvlM4b - OkbPsz1zZ46xJRESmj5Ef3bbm7ANLrPzWJNPCKRpFdCBaUtuXMKT2T8= - =M7js - -----END PGP MESSAGE----- - fp: 9cb1fda8a56fa7ab852f666fc3592125321adf42 - unencrypted_suffix: _unencrypted - version: 3.7.3 diff --git a/nixos/secrets/phobos.yml b/nixos/secrets/phobos.yml deleted file mode 100644 index 7463477b..00000000 --- a/nixos/secrets/phobos.yml +++ /dev/null @@ -1,45 +0,0 @@ -phobos_ssh: ENC[AES256_GCM,data:M9bJJRMwEODCkwpBijCkjCd/2Km/zUj1kSs/buHsuWeGi2qnv+cNq2AmE5T1yK4auawsZgQvvytFReUqpgCFPIkU7cWH5IuUm2gGZOS1bWpfR7HwoOQVMdkU/whqRi+Gj5PPzyS3RHLlDfUenVZVPoN7GHiRWf71PyVhlhKulIe1ZiaitF5msUIblSHW2flZgRePmnqBUULrfxK2ZkmDl5bnfQY2HNsyaNo9Qj372sQwDiComuMLmJBYIXBhrbucnjJIXNkAOmox7GGcM8rZy7QKhXloTNJM1SMbZJnLPSksRqOO/enM4Tda81GV9dJetFaxSMjgSoKhLDK2hDdUwpX4iGGpsl8S37O3VRmOXi/QlWApMpRWfanC3NFApFzIqOy+GoRtWysrKBJ6S7W/NDMdlBxZmeZHxxhVmwIkMG5mfIBrdiErHUX4xlawEdbwKnRT4zz3x1e+1Xm2CpKBSrE1FkUzRRtMnsLb5pw/WNJL9ueRsKG9Wf73VZvfKGdDeu4grGr2L6cRHwF2+JAj,iv:0pwaq3zOzdXJ89N9y1G0tjAtR/sYaI+rMMixPHQcSyA=,tag:yJkJ1wg3lUTEeSkZge0xZA==,type:str] -phobos_ssh.pub: ENC[AES256_GCM,data:YRMndy7eIL2YPbf2JEfT+KRIsZrazbuJHp6vRbJ0VEU+Bg/h1CSzJpYedls/+uCmkVpoxBvdjYHeCKtneyJCzkaDzJsUz+RcfrIGQEhake76X9omur9rTK/MJyI=,iv:OtacGQQUaIgDKLkTunOsqFfdh982T9yYH1RoYdvT7vo=,tag:/nNj/xjhnvgDUalOeY+4vA==,type:str] -sops: - kms: [] - gcp_kms: [] - azure_kv: [] - hc_vault: [] - age: [] - lastmodified: "2022-08-27T22:46:21Z" - mac: ENC[AES256_GCM,data:zwp5TcQFOJEG22qrQrJR/zCnLNw31Eeb7pI60fJRT/8rDYIqKguMcYbj+44fn3rRnLOQlvL0Pek2f41UlIb7LosNnoaTzTxoYBbgFRiliyII/epFXRINHrbyBEOp4Anc5445YoY/xmO9y3MLJYF9b31PVOFaAq1CJtbtfZXHCG8=,iv:OsnA+1KgwPwVacbjIbzAhKtap/lgEPpzS/i4NJGP0Qs=,tag:/Jvk62zVKHApNhBpgcH5sg==,type:str] - pgp: - - created_at: "2022-08-27T22:15:58Z" - enc: | - -----BEGIN PGP MESSAGE----- - - hF4DhKTlqbJ4OtgSAQdApKk6z/OCHK0Rkaqxd2F27AabN365lxZ2ms8MUGcOVHQw - //9xS2VQqUb2uRT4eEblZuJpNRntFWRHt63AKa31U3cnooysfm+zT4/VdbFF3oqL - 1GgBCQIQ0qDqs10qB+l7uNJJQm7cMecKWsHkDgP9Zj5P0zBR2A81FfZPApC9Jofl - 442PMWoi5GS7CVu4P3WiqGOR+XSX7I6Ih4S/EYsAD338JM4Pll5qps175njNbzqj - wvJf/ONbQR+QYQ== - =7Rq0 - -----END PGP MESSAGE----- - fp: 93A4B95A3623ED8F03CCEBD21ADC2C80A1289824 - - created_at: "2022-08-27T22:15:58Z" - enc: | - -----BEGIN PGP MESSAGE----- - - hQIMA8NZISUyGt9CAQ//d/OZBqdnhWrnF2SPCAp68KJuHgYuE/TitOhG2rNWo+UZ - +n8dzvGdzPmWRXQIIonWw1aVkuFd9I0jvJ7qSN+kYcq9sOAswdMwj6RyGSOyarfK - /XjdGnYRUvTcKKVz2M3Xq15flk5juxlYSmcGpGnDJpyeR3tXRHNRqxWCNFXKQwxR - FIJmCo3LQr87zuuOKl1QEhQ6n67edT7IKK3AQrVlLniNDKaWh6lgI3Q/OVJdJ2BU - yEDCVizRYCuBjQYM+rR9sdcaiK2P+44nw2sL5QNyyqHPnUCi38Cghb2g9EjYo+w1 - KH1335wgqATIiDjae+jGffnNQvxPMz8ZgxebMsqcOWs6NELF7yvHFwTv7sA0Dvkx - dyLraXNK3SUdz1ZLwEDPnYx/tsRMgUMTv80NA8FL4sFnNtBRlJ6mOc74YItFHzBi - errADMOllhFuPVl3yuC1j99HyxUeTNFnoukSi1kNs0dGr7N8+jvWQqIcJgMkv6Mo - P75retb3De5Bcx8XkRpxsN8In3fUUO0xyI2HQykf0ECEHRc7HdZn+2oD3yqk4fME - sp97lW448JslS9Rn4WGWA11TWrz8kUSv+1NOHan/bEkR5HWku8ETyKZTqAWRmwh5 - pCyEmMnjbNUNXA02PuxtNjTLZP2E4agdSs9oQ8MM4xOBIFdNkSLc72PjzcZ/uJjS - WAHdCpTZC393/TbeBdN8A2gmbctMRxdQF6Bilph665eUMml/07zndKF0nZYvlM4b - OkbPsz1zZ46xJRESmj5Ef3bbm7ANLrPzWJNPCKRpFdCBaUtuXMKT2T8= - =M7js - -----END PGP MESSAGE----- - fp: 9cb1fda8a56fa7ab852f666fc3592125321adf42 - unencrypted_suffix: _unencrypted - version: 3.7.3 diff --git a/nixos/users.nix b/nixos/users.nix deleted file mode 100644 index a5aa9551..00000000 --- a/nixos/users.nix +++ /dev/null @@ -1,15 +0,0 @@ -{ ... }: -{ - users = { - mutableUsers = false; - users.fic = { - isNormalUser = true; - extraGroups = [ "wheel" ]; - openssh.authorizedKeys.keys = [ - "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAILBoJRKGvhpJGYQfq+Ocp83nJixk8zz3cmzHOvLIW2C9 antoine.thouvenin" - "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIJdjG/+FTghcl+sgsAFM7kdBTbGIR9JycgpWeLGJt2ZV elie.brami" - ]; - hashedPassword = "$6$CuDkmaet$ZWh.KlzZe2EF2c23GErwdbsa1naByrNe15j7Jy3SuJZfEwGUV16QEkz9bcfzHtMteTjGRr8ixOtKYn.wV8e10."; - }; - }; -} diff --git a/password_paper/.gitignore b/password_paper/.gitignore deleted file mode 100644 index ec2af2ab..00000000 --- a/password_paper/.gitignore +++ /dev/null @@ -1,22 +0,0 @@ -missfont.log -pass.aux -pass.fdb_latexmk -pass.fls -pass.log -pass.out -pass.pdf -pass.tex -teams.pass - -FantasqueSansMono-Regular.otf -dingbat.600pk -dingbat.720pk -dingbat.dtx -dingbat.ins -dingbat.mf -dingbat.sty -dingbat.tfm -gen_pass.sh -pass.xdv -uark.fd -udingbat.fd \ No newline at end of file diff --git a/password_paper/Makefile b/password_paper/Makefile deleted file mode 100644 index 77cabe11..00000000 --- a/password_paper/Makefile +++ /dev/null @@ -1,12 +0,0 @@ -all: pass.pdf - -.sh.tex: - cat teams.pass | sh $< > $@ - -.tex.pdf: - latexmk --xelatex ${.IMPSRC} - -clean: - latexmk -c - -.SUFFIXES: .sh .tex .pdf diff --git a/password_paper/README.md b/password_paper/README.md deleted file mode 100644 index 80f31c7c..00000000 --- a/password_paper/README.md +++ /dev/null @@ -1,42 +0,0 @@ -Password Paper -============== - -These scripts generate A4 sheets filled with 2x5 - - -## Requirements - -Those scripts requires XeLaTeX and the `texlive-fonts-extra` package. - -It uses the TexLive -[dingbat package](http://www.ctan.org/tex-archive/fonts/dingbat/). - -The fonts used come from the -[Libertine open fonts project](http://linuxlibertine.org/) and -[Fantasque Sans Mono](https://github.com/belluzj/fantasque-sans). - - -## Usage - -First, you need to generate the file containing teams and passwords. - - -### Password file format - -Passwords are given through a DSV file, using `:` as separator. - -For example, a valid `teams.pass` file with 6 teams could be: - -``` -Abricot:hie9iJ -(/usr/share/texmf-dist/tex/latex/base/docstrip.tex -\blockLevel=\count80 -\emptyLines=\count81 -\processedLines=\count82 -\commentsRemoved=\count83 -\commentsPassed=\count84 -\codeLinesPassed=\count85 -\TotalprocessedLines=\count86 -\TotalcommentsRemoved=\count87 -\TotalcommentsPassed=\count88 -\TotalcodeLinesPassed=\count89 -\NumberOfFiles=\count90 -\inFile=\read1 -\inputcheck=\read2 -\off@0=\count91 -\off@1=\count92 -\off@2=\count93 -\off@3=\count94 -\off@4=\count95 -\off@5=\count96 -\off@6=\count97 -\off@7=\count98 -\off@8=\count99 -\off@9=\count100 -\off@10=\count101 -\off@11=\count102 -\off@12=\count103 -\off@13=\count104 -\off@14=\count105 -\off@15=\count106 -\@maxfiles=\count107 -\@maxoutfiles=\count108 - -Utility: `docstrip' 2.5g <2018/05/03> -English documentation <2018/05/03> - -********************************************************** -* This program converts documented macro-files into fast * -* loadable files by stripping off (nearly) all comments! * -********************************************************** - -******************************************************** -* No Configuration file found, using default settings. * -******************************************************** - -) - -Generating file(s) ./dingbat.sty -\openout0 = `./dingbat.sty'. - - -Processing file dingbat.dtx (package) -> dingbat.sty -Lines processed: 329 -Comments removed: 277 -Comments passed: 1 -Codelines passed: 36 - - -Generating file(s) ./uark.fd -\openout0 = `./uark.fd'. - - -Processing file dingbat.dtx (uarkfd) -> uark.fd -Lines processed: 329 -Comments removed: 277 -Comments passed: 1 -Codelines passed: 36 - - -Generating file(s) ./udingbat.fd -\openout0 = `./udingbat.fd'. - - -Processing file dingbat.dtx (udingbatfd) -> udingbat.fd -Lines processed: 329 -Comments removed: 277 -Comments passed: 1 -Codelines passed: 36 - -*********************************************************** -* -* To finish the installation, you have to move -* dingbat.sty, uark.fd, ark10.mf, and dingbat.mf into -* directories searched by TeX. -* -* To produce the documentation, run dingbat.dtx -* through LaTeX. -* -* Happy TeXing! -*********************************************************** -Overall statistics: -Files processed: 3 -Lines processed: 987 -Comments removed: 831 -Comments passed: 3 -Codelines passed: 108 - ) -Here is how much of TeX's memory you used: - 332 strings out of 494525 - 3336 string characters out of 6176718 - 60622 words of memory out of 5000000 - 4103 multiletter control sequences out of 15000+600000 - 3640 words of font info for 14 fonts, out of 8000000 for 9000 - 14 hyphenation exceptions out of 8191 - 11i,0n,15p,152b,129s stack positions out of 5000i,500n,10000p,200000b,80000s - -No pages of output. diff --git a/password_paper/dnred.png b/password_paper/dnred.png deleted file mode 100644 index 25b2593e..00000000 Binary files a/password_paper/dnred.png and /dev/null differ diff --git a/password_paper/epita.png b/password_paper/epita.png deleted file mode 100644 index 6172eaf4..00000000 Binary files a/password_paper/epita.png and /dev/null differ diff --git a/password_paper/fic.png b/password_paper/fic.png deleted file mode 100644 index 4beb17c9..00000000 Binary files a/password_paper/fic.png and /dev/null differ diff --git a/password_paper/pass.sh b/password_paper/pass.sh deleted file mode 100755 index 2791abcc..00000000 --- a/password_paper/pass.sh +++ /dev/null @@ -1,94 +0,0 @@ -#!/bin/sh - -cat < /dev/null 2> /dev/null - then - GRPFIC=$(echo $TEAMSTR | sed -r "s/${TEAM_PREFIX} //") - - curl -s http://${HOST_FICADMIN}/api/themes.json | jq -r '.["'$THEMETODO'"] | .exercices['$MIN_EXO_TODO:$MAX_EXO_TODO'] | .[].id' | while read EXID - do - curl -X POST -d @- -H "X-FIC-Team: ${QA_ADMIN}" http://${HOST_FICQA}/api/qa_work.json < /dev/null 2> /dev/null - then - GRPFIC=$(echo $TEAMSTR | sed -r "s/${TEAM_PREFIX} //") - - THEMES_TO_TESTS=$(curl -s http://${HOST_FICADMIN}/api/themes | jq -r '.[] | [(.id | tostring), " ", (.path | split("-") | .[0])] | add' | grep -v " grp$GRPFIC" | shuf | head -n $NB_THEMES_TODO | cut -d " " -f 1 | xargs | sed 's/^/"/;s/ /","/g;s/$/"/') - - curl -s http://${HOST_FICADMIN}/api/themes.json | jq -r '.['$THEMES_TO_TESTS'] | .exercices['$MIN_EXO_TODO:$MAX_EXO_TODO'] | .[].id' | while read EXID - do - curl -X POST -d @- -H "X-FIC-Team: ${QA_ADMIN}" http://${HOST_FICQA}/api/qa_work.json < /dev/null 2> /dev/null; then echo export TEAM_$TEAM=$ID | sed -r "s/${TEAM_PREFIX} //"; fi; done` - -# Add all themes and exercices to assistants -curl -s http://${HOST_FICADMIN}/api/themes | jq '.[].id' | while read tid -do - TEAM="TEAM_$(curl -s http://${HOST_FICADMIN}/api/themes/$tid | jq -r .path | sed -r 's/^[^0-9]*([0-9]+)-.*$/\1/')" - curl -s http://${HOST_FICADMIN}/api/themes/$tid/exercices | jq .[].id | while read ex - do - curl -X POST -d @- -H "X-FIC-Team: ${QA_ADMIN}" http://${HOST_FICQA}/api/qa_my_exercices.json < /dev/null 2> /dev/null; then echo export TEAM_$TEAM=$ID | sed -r "s/${TEAM_PREFIX} //"; fi; done` - -# Add their themes and exercices -curl -s http://${HOST_FICADMIN}/api/themes | jq '.[].id' | while read tid -do - TEAM="TEAM_$(curl -s http://${HOST_FICADMIN}/api/themes/$tid | jq -r .path | sed -r 's/^[^0-9]*([0-9]+)-.*$/\1/')" - curl -s http://${HOST_FICADMIN}/api/themes/$tid/exercices | jq .[].id | while read ex - do - [ -z "${!TEAM}" ] || curl -X POST -d @- -H "X-FIC-Team: ${QA_ADMIN}" http://${HOST_FICQA}/api/qa_my_exercices.json <&2 echo "Please give DB to read from as argument"; exit 1; } - -source qa-common.sh - -MYSQL_USER="${MYSQL_USER:-fic}" -MYSQL_PASSWORD="${MYSQL_PASSWORD:-fic}" -MYSQL_DATABASE="$1" - -EXERCICES=$(curl -s -H "X-FIC-Team: ${QA_ADMIN}" http://${HOST_FICQA}/api/exercices/) -TEAMS=$(curl -s http://${HOST_FICADMIN}/api/teams/ | jq -r '.[].id' | while read IDTEAM; do curl -s http://${HOST_FICADMIN}/api/teams/$IDTEAM/associations | jq -r ".[] | {login: ., id_team: \"$IDTEAM\"}"; done) - -echo "select Q.*, T.name, E.title from exercices_qa Q INNER JOIN exercices E ON E.id_exercice = Q.id_exercice INNER JOIN themes T ON T.id_theme = E.id_theme;" | mysql --skip-column-names -u "${MYSQL_USER}" --password="${MYSQL_PASSWORD}" "${MYSQL_DATABASE}" | tr '\t' '|' | while IFS='|' read IDQ IDEXERCICE IDTEAM REMOTE_USER SUBJECT CREATION STATE SOLVED CLOSED THEME EXERCICE_TITLE -do - # Search exercice by title - IDEXERCICE=$(echo "${EXERCICES}" | jq -r ".[] | select(.title == \"${EXERCICE_TITLE}\") | .id") - [ -z "$IDEXERCICE" ] && { >&2 echo "QA query $IDQ: no exercice match"; continue; } - - # Search user in teams (if not found, set to null, this is allowed) - IDTEAM=$(echo "${TEAMS}" | jq -r "select(.login == \"${REMOTE_USER}\") | .id_team") - [ -z "$IDTEAM" ] && IDTEAM=null - - CREATION=$(echo ${CREATION}Z | sed 's/ /T/') - [ "$SOLVED" == "NULL" ] && SOLVED=null || SOLVED="\"$(echo ${SOLVED}Z | sed 's/ /T/')\"" - [ "$CLOSED" == "NULL" ] && CLOSED=null || CLOSED="\"$(echo ${CLOSED}Z | sed 's/ /T/')\"" - - SUBJECT=$(cat < /dev/null -{ - "id_team": $IDTEAM, - "user": "$REMOTE_USER", - "date": "$DATEC", - "content": $COMMENT -} -EOF - done -done diff --git a/qa/.gitignore b/qa/.gitignore deleted file mode 100644 index a483eac5..00000000 --- a/qa/.gitignore +++ /dev/null @@ -1 +0,0 @@ -qa \ No newline at end of file diff --git a/qa/api/admin.go b/qa/api/admin.go deleted file mode 100644 index 0d45d109..00000000 --- a/qa/api/admin.go +++ /dev/null @@ -1,51 +0,0 @@ -package api - -import ( - "encoding/json" - "flag" - "io" - "net/http" - "net/url" - "os" - "path" -) - -var adminLink string - -func init() { - flag.StringVar(&adminLink, "admin-link", os.Getenv("FIC_QA_ADMIN_LINK"), "URL to admin interface, to use its features as replacement") -} - -func fwdAdmin(method, urlpath string, body io.Reader, out interface{}) error { - u, err := url.Parse(adminLink) - if err != nil { - return err - } - - var user, pass string - if u.User != nil { - user = u.User.Username() - pass, _ = u.User.Password() - u.User = nil - } - - u.Path = path.Join(u.Path, urlpath) - - r, err := http.NewRequest(method, u.String(), body) - if err != nil { - return err - } - - if len(user) != 0 || len(pass) != 0 { - r.SetBasicAuth(user, pass) - } - - resp, err := http.DefaultClient.Do(r) - if err != nil { - return err - } - defer resp.Body.Close() - - dec := json.NewDecoder(resp.Body) - return dec.Decode(&out) -} diff --git a/qa/api/auth.go b/qa/api/auth.go deleted file mode 100644 index a6e7466a..00000000 --- a/qa/api/auth.go +++ /dev/null @@ -1,59 +0,0 @@ -package api - -import ( - "log" - "net/http" - "os" - "path" - "strconv" - - "github.com/gin-gonic/gin" -) - -var ( - Simulator string - TeamsDir string - ManagerUsers []string -) - -func authMiddleware(access ...func(string, int64, *gin.Context) bool) gin.HandlerFunc { - return func(c *gin.Context) { - ficteam := Simulator - if t := c.Request.Header.Get("X-FIC-Team"); t != "" { - ficteam = t - } - - var teamid int64 - var err error - - if ficteam == "" { - c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{"errmsg": "Need to authenticate"}) - return - } else if teamid, err = strconv.ParseInt(ficteam, 10, 64); err != nil { - if lnk, err := os.Readlink(path.Join(TeamsDir, ficteam)); err != nil { - log.Printf("[ERR] Unable to readlink %q: %s\n", path.Join(TeamsDir, ficteam), err) - c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": "Unable to validate authentication."}) - return - } else if teamid, err = strconv.ParseInt(lnk, 10, 64); err != nil { - log.Printf("[ERR] Error during ParseInt team %q: %s\n", lnk, err) - c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": "Unable to validate authentication."}) - return - } - } - - // Check access limitation - for _, a := range access { - if !a(ficteam, teamid, c) { - return - } - } - - // Retrieve corresponding user - c.Set("LoggedUser", ficteam) - c.Set("LoggedTeam", teamid) - - // We are now ready to continue - - c.Next() - } -} diff --git a/qa/api/exercice.go b/qa/api/exercice.go deleted file mode 100644 index 6109c5a4..00000000 --- a/qa/api/exercice.go +++ /dev/null @@ -1,81 +0,0 @@ -package api - -import ( - "fmt" - "log" - "net/http" - "strconv" - - adminapi "srs.epita.fr/fic-server/admin/api" - "srs.epita.fr/fic-server/libfic" - - "github.com/gin-gonic/gin" -) - -func declareExercicesRoutes(router *gin.RouterGroup) { - router.GET("/exercices", listExercices) - - exercicesRoutes := router.Group("/exercices/:eid") - exercicesRoutes.Use(exerciceHandler) - exercicesRoutes.GET("", showExercice) - - declareQARoutes(exercicesRoutes) -} - -func exerciceHandler(c *gin.Context) { - var exercice *fic.Exercice - if eid, err := strconv.ParseInt(string(c.Param("eid")), 10, 64); err != nil { - c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"errmsg": "Bad exercice identifier."}) - return - } else if exercice, err = fic.GetExercice(eid); err != nil { - c.AbortWithStatusJSON(http.StatusNotFound, gin.H{"errmsg": "Exercice not found."}) - return - } - - if th, ok := c.Get("theme"); ok { - if exercice.IdTheme == nil || *exercice.IdTheme != th.(*fic.Theme).Id { - c.AbortWithStatusJSON(http.StatusNotFound, gin.H{"errmsg": "Exercice not found."}) - return - } - } - - c.Set("exercice", exercice) - - c.Next() -} - -func listExercices(c *gin.Context) { - var exercices []*fic.Exercice - var err error - - if theme, ok := c.Get("theme"); ok { - exercices, err = theme.(*fic.Theme).GetExercices() - } else { - // List all exercices - exercices, err = fic.GetExercices() - } - if err != nil { - log.Println("Unable to GetExercices: ", err.Error()) - c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": fmt.Sprintf("Unable to list exercices: %s", err.Error())}) - return - } - - c.JSON(http.StatusOK, exercices) -} - -func showExercice(c *gin.Context) { - if adminLink == "" { - c.JSON(http.StatusOK, c.MustGet("exercice")) - return - } - - var e adminapi.Exercice - err := fwdAdmin(c.Request.Method, fmt.Sprintf("/api/exercices/%s", string(c.Param("eid"))), nil, &e) - if err != nil { - log.Println("Unable to make request to admin:", err.Error()) - c.JSON(http.StatusOK, c.MustGet("exercice")) - return - } - - c.JSON(http.StatusOK, e) -} diff --git a/qa/api/gitlab.go b/qa/api/gitlab.go deleted file mode 100644 index f31c49cc..00000000 --- a/qa/api/gitlab.go +++ /dev/null @@ -1,317 +0,0 @@ -package api - -import ( - "bytes" - "context" - "crypto/rand" - "encoding/hex" - "encoding/json" - "flag" - "fmt" - "io" - "log" - "net/http" - "net/url" - "os" - "path" - "strings" - "time" - - "github.com/gin-contrib/sessions" - "github.com/gin-gonic/gin" - "golang.org/x/oauth2" - - adminapi "srs.epita.fr/fic-server/admin/api" - "srs.epita.fr/fic-server/libfic" -) - -var ( - gitlabBaseURL = "https://gitlab.cri.epita.fr" - gitlabClientID = "" - gitlabSecret = "" - gitlaboauth2Config *oauth2.Config - oidcRedirectURL = "https://fic.srs.epita.fr" -) - -func init() { - if v, ok := os.LookupEnv("FIC_OIDC_REDIRECT"); ok { - oidcRedirectURL = v - } - - if v, ok := os.LookupEnv("FIC_GITLAB_BASEURL"); ok { - gitlabBaseURL = v - } - - flag.StringVar(&oidcRedirectURL, "oidc-redirect", oidcRedirectURL, "Base URL for the redirect after connection") - flag.StringVar(&gitlabBaseURL, "gitlab-baseurl", gitlabBaseURL, "Base URL of the Gitlab instance") - flag.StringVar(&gitlabClientID, "gitlab-clientid", os.Getenv("FIC_GITLAB_CLIENT_ID"), "ClientID for GitLab's OIDC") - flag.StringVar(&gitlabSecret, "gitlab-secret", os.Getenv("FIC_GITLAB_CLIENT_SECRET"), "Secret for GitLab's OIDC") -} - -func InitializeGitLabOIDC(baseURL string) { - if gitlabClientID != "" && gitlabSecret != "" { - gitlaboauth2Config = &oauth2.Config{ - ClientID: gitlabClientID, - ClientSecret: gitlabSecret, - RedirectURL: oidcRedirectURL + baseURL + "/callback/gitlab/complete", - - // Discovery returns the OAuth2 endpoints. - Endpoint: oauth2.Endpoint{ - AuthURL: gitlabBaseURL + "/oauth/authorize", - TokenURL: gitlabBaseURL + "/oauth/token", - }, - - // "openid" is a required scope for OpenID Connect flows. - Scopes: []string{"api", "email"}, - } - } -} - -func declareGitlabRoutes(router *gin.RouterGroup, authrouter *gin.RouterGroup) { - if gitlaboauth2Config != nil { - router.GET("/auth/gitlab", redirectOAuth_GitLab) - router.GET("/callback/gitlab/complete", GitLab_OAuth_complete) - - tokenRoutes := authrouter.Group("") - tokenRoutes.Use(gitlabHandler) - - tokenRoutes.GET("/gitlab/token", GitLab_GetMyToken) - } -} - -func declareGitlabExportRoutes(router *gin.RouterGroup) { - if gitlaboauth2Config != nil { - tokenRoutes := router.Group("") - tokenRoutes.Use(gitlabHandler) - - if adminLink == "" { - log.Println("-admin-link should be defined to be able to export to GitLab!") - } else { - tokenRoutes.POST("/gitlab_export", GitLab_ExportQA) - } - } -} - -func gitlabHandler(c *gin.Context) { - session := sessions.Default(c) - - var oauth2Token *oauth2.Token - if tok, ok := session.Get("gitlab-token").([]byte); ok { - err := json.Unmarshal(tok, &oauth2Token) - if err != nil { - c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": err.Error()}) - return - } - } else { - c.AbortWithStatusJSON(http.StatusForbidden, gin.H{"errmsg": "No token defined"}) - return - } - - err := prepareOAuth2Token(c.Request.Context(), oauth2Token) - if err != nil { - c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": err.Error()}) - return - } - - c.Set("gitlab-token", oauth2Token) - - c.Next() -} - -func prepareOAuth2Token(ctx context.Context, tk *oauth2.Token) error { - tsource := oauth2.ReuseTokenSource(tk, gitlaboauth2Config.TokenSource(ctx, tk)) - - _, err := tsource.Token() - if err != nil { - log.Println("Unable to regenerate GitLab token:", err) - } - return err -} - -func redirectOAuth_GitLab(c *gin.Context) { - session := sessions.Default(c) - - // Save next parameter - if len(c.Request.URL.Query().Get("next")) > 0 { - session.Set("gitlab-oidc-source", c.Request.URL.Query().Get("next")) - } - - b := make([]byte, 32) - _, err := rand.Read(b) - if err != nil { - c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": err.Error()}) - return - } - - session.Set("oidc-state", b) - session.Save() - - c.Redirect(http.StatusFound, gitlaboauth2Config.AuthCodeURL(hex.EncodeToString(b))) -} - -func GitLab_OAuth_complete(c *gin.Context) { - session := sessions.Default(c) - - state, err := hex.DecodeString(c.Request.URL.Query().Get("state")) - if err != nil { - c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"errmsg": err.Error()}) - return - } - - if session_state, ok := session.Get("oidc-state").([]byte); !ok { - c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"errmsg": "No OIDC session in progress."}) - return - } else if !bytes.Equal(state, session_state) { - c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"errmsg": "Bad session state."}) - return - } - - oauth2Token, err := gitlaboauth2Config.Exchange(c.Request.Context(), c.Request.URL.Query().Get("code")) - if err != nil { - c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": "Failed to exchange token: " + err.Error()}) - return - } - - jsonToken, err := json.Marshal(oauth2Token) - if err != nil { - c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": err.Error()}) - } - - session.Set("gitlab-token", jsonToken) - prepareOAuth2Token(context.Background(), oauth2Token) - session.Save() - log.Println("New GitLab OAuth2 session opened") - - baseURL := c.MustGet("baseurl").(string) - - if source, ok := session.Get("gitlab-oidc-source").(string); ok { - session.Delete("gitlab-oidc-source") - c.Redirect(http.StatusFound, baseURL+source) - } else { - c.Redirect(http.StatusFound, baseURL) - } -} - -func GitLab_GetMyToken(c *gin.Context) { - oauth2Token := c.MustGet("gitlab-token").(*oauth2.Token) - - c.JSON(http.StatusOK, oauth2Token) -} - -type GitLabIssue struct { - Id int64 `json:"id,omitempty"` - IId int64 `json:"iid,omitempty"` - ProjectId int64 `json:"project_id,omitempty"` - Title string `json:"title"` - Description string `json:"description"` - WebURL string `json:"web_url"` -} - -func gitlab_newIssue(ctx context.Context, token *oauth2.Token, exerciceid string, issue *GitLabIssue) (*GitLabIssue, error) { - client := gitlaboauth2Config.Client(ctx, token) - - enc, err := json.Marshal(issue) - if err != nil { - return nil, err - } - - req, err := http.NewRequest("POST", gitlabBaseURL+"/api/v4/projects/"+url.QueryEscape(exerciceid)+"/issues", bytes.NewBuffer(enc)) - if err != nil { - return nil, err - } - - req.Header.Add("Content-Type", "application/json") - - resp, err := client.Do(req) - if err != nil { - return nil, err - } - defer resp.Body.Close() - - if resp.StatusCode != http.StatusCreated { - str, _ := io.ReadAll(resp.Body) - return nil, fmt.Errorf("Bad status code from the API: %d %s", resp.StatusCode, string(str)) - } - - var issueCreated GitLabIssue - dec := json.NewDecoder(resp.Body) - err = dec.Decode(&issueCreated) - if err != nil { - return nil, fmt.Errorf("Unable to decode issue JSON: %s", err.Error()) - } - - return &issueCreated, nil -} - -func GitLab_getExerciceId(exercice *fic.Exercice) (string, error) { - // Get the forge link - var adminEx adminapi.Exercice - err := fwdAdmin("GET", fmt.Sprintf("/api/exercices/%d", exercice.Id), nil, &adminEx) - if err != nil { - return "", err - } - - forgelink, err := url.Parse(adminEx.ForgeLink) - if err != nil { - return "", fmt.Errorf("forge_link not found") - } - forgelink.Path = forgelink.Path[:strings.Index(forgelink.Path, "/-/tree/")] - return strings.TrimPrefix(forgelink.Path, "/"), nil -} - -func GitLab_ExportQA(c *gin.Context) { - query := c.MustGet("qa").(*fic.QAQuery) - exercice, err := fic.GetExercice(query.IdExercice) - if err != nil { - c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": err.Error()}) - return - } - - gitlabid, err := GitLab_getExerciceId(exercice) - if err != nil { - log.Println("Unable to retrieve Gitlab exercice id:", err.Error()) - c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": err.Error()}) - return - } - - comments, err := query.GetComments() - if err != nil { - log.Println("Unable to GetComments: ", err.Error()) - c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": fmt.Sprintf("Unable to list comments: %s", err.Error())}) - return - } - - description := "<" + oidcRedirectURL + path.Join(path.Clean("/"+c.MustGet("baseurl").(string)), "exercices", fmt.Sprintf("%d", exercice.Id), fmt.Sprintf("%d", query.Id)) + ">" - - if len(comments) > 0 { - for i, comment := range comments { - if i > 0 { - description += "\n\n---" - } - description += fmt.Sprintf("\n\n%s\n\nPar %s, le %s", comment.Content, comment.User, comment.Date.Format(time.UnixDate)) - } - } - - // Format the issue - issue := GitLabIssue{ - Title: "[QA] " + query.Subject, - Description: description, - } - - // Create the issue on GitLab - oauth2Token := c.MustGet("gitlab-token").(*oauth2.Token) - iid, err := gitlab_newIssue(c.Request.Context(), oauth2Token, gitlabid, &issue) - if err != nil { - log.Println("Unable to create Gitlab issue:", err.Error()) - c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": err.Error()}) - return - } - - query.Exported = &iid.IId - _, err = query.Update() - if err != nil { - log.Println("Unable to update QAquery in GitLab_ExportQA:", err.Error()) - } - - c.JSON(http.StatusOK, iid) -} diff --git a/qa/api/qa.go b/qa/api/qa.go deleted file mode 100644 index a76aa0d3..00000000 --- a/qa/api/qa.go +++ /dev/null @@ -1,413 +0,0 @@ -package api - -import ( - "fmt" - "log" - "net/http" - "strconv" - "time" - - "srs.epita.fr/fic-server/libfic" - - "github.com/gin-gonic/gin" -) - -func declareQARoutes(router *gin.RouterGroup) { - exercicesRoutes := router.Group("/qa") - exercicesRoutes.GET("", getExerciceQA) - exercicesRoutes.POST("", createExerciceQA) - - exercicesRoutes.GET("/export", exportQA) - exercicesRoutes.GET("/export.json", exportQAJSON) - - qaRoutes := exercicesRoutes.Group("/:qid") - qaRoutes.Use(qaHandler) - qaRoutes.PUT("", updateExerciceQA) - qaRoutes.DELETE("", deleteExerciceQA) - qaRoutes.GET("comments", getQAComments) - qaRoutes.POST("comments", createQAComment) - - declareGitlabExportRoutes(qaRoutes) - - commentsRoutes := qaRoutes.Group("comments/:cid") - commentsRoutes.Use(qaCommentHandler) - commentsRoutes.DELETE("", deleteQAComment) -} - -func qaHandler(c *gin.Context) { - var qa *fic.QAQuery - - qid, err := strconv.ParseInt(string(c.Param("qid")), 10, 64) - if err != nil { - c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"errmsg": "Bad QA identifier."}) - return - } - - if exercice, ok := c.Get("exercice"); ok { - qa, err = exercice.(*fic.Exercice).GetQAQuery(qid) - } else { - qa, err = fic.GetQAQuery(qid) - } - - if err != nil { - c.AbortWithStatusJSON(http.StatusNotFound, gin.H{"errmsg": "QA entry not found."}) - return - } - - c.Set("qa", qa) - - c.Next() -} - -func qaCommentHandler(c *gin.Context) { - qa := c.MustGet("qa").(*fic.QAQuery) - var comment *fic.QAComment - if cid, err := strconv.ParseInt(string(c.Param("cid")), 10, 64); err != nil { - c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"errmsg": "Bad comment identifier."}) - return - } else if comment, err = qa.GetComment(cid); err != nil { - c.AbortWithStatusJSON(http.StatusNotFound, gin.H{"errmsg": "Comment entry not found."}) - return - } - - c.Set("comment", comment) - - c.Next() -} - -type QAQuery struct { - *fic.QAQuery - ForgeLink string `json:"forge_link,omitempty"` -} - -func getExerciceQA(c *gin.Context) { - exercice := c.MustGet("exercice").(*fic.Exercice) - - qas, err := exercice.GetQAQueries() - if err != nil { - log.Println("Unable to GetQAQueries: ", err.Error()) - c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": fmt.Sprintf("Unable to list QA entries: %s", err.Error())}) - return - } - - forgelink, err := GitLab_getExerciceId(exercice) - if err != nil { - log.Println("Unable to make request to admin:", err.Error()) - c.JSON(http.StatusOK, qas) - return - } - - var res []*QAQuery - for _, qa := range qas { - if qa.Exported != nil { - res = append(res, &QAQuery{ - qa, - gitlabBaseURL + "/" + forgelink + fmt.Sprintf("/-/issues/%d", *qa.Exported), - }) - } else { - res = append(res, &QAQuery{ - qa, - "", - }) - } - } - - c.JSON(http.StatusOK, res) -} - -func exportQA(c *gin.Context) { - var report string - - themes, err := fic.GetThemesExtended() - if err != nil { - log.Println("Unable to GetThemes: ", err.Error()) - c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": fmt.Sprintf("Unable to list themes: %s", err.Error())}) - return - } - - for _, th := range themes { - report += fmt.Sprintf("# %s {#%s}\n\n", th.Name, th.URLId) - - exercices, err := th.GetExercices() - if err != nil { - log.Println("Unable to GetExercices: ", err.Error()) - c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": fmt.Sprintf("Unable to list exercices for theme #%d: %s", th.Id, err.Error())}) - return - } - - for _, exercice := range exercices { - report += fmt.Sprintf("## %s {#%s}\n\n", exercice.Title, exercice.URLId) - - qa, err := exercice.GetQAQueries() - if err != nil { - log.Println("Unable to GetQAQueries: ", err.Error()) - c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": fmt.Sprintf("Unable to list QA entries: %s", err.Error())}) - return - } - - for _, q := range qa { - emoji := "❓" - - if q.Closed != nil { - emoji = "👌" - } else if q.Solved != nil { - emoji = "✅" - } - - report += fmt.Sprintf("### %s [%s] %s\n\nOuvert par %s le %s\n\n", emoji, q.State, q.Subject, q.User, q.Creation) - - comments, err := q.GetComments() - if err != nil { - log.Println("Unable to GetQAComments: ", err.Error()) - c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": fmt.Sprintf("Unable to list QA comments: %s", err.Error())}) - return - } - - for _, c := range comments { - report += fmt.Sprintf("Le %s, %s :\n%s\n\n", c.Date, c.User, c.Content) - } - } - } - } - - c.JSON(http.StatusOK, report) -} - -type ExportTheme struct { - Theme *fic.Theme `json:"theme"` - Exercices []ExportExercice `json:"exercices"` -} - -type ExportExercice struct { - Exercice *fic.Exercice `json:"exercice"` - Reports []ExportReport `json:"reports"` -} - -type ExportReport struct { - Report *fic.QAQuery `json:"report"` - Comments []*fic.QAComment `json:"comments"` -} - -func exportQAJSON(c *gin.Context) { - report := map[string]ExportTheme{} - - themes, err := fic.GetThemesExtended() - if err != nil { - log.Println("Unable to GetThemes: ", err.Error()) - c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": fmt.Sprintf("Unable to list themes: %s", err.Error())}) - return - } - - for _, th := range themes { - export_theme := ExportTheme{ - Theme: th, - } - - exercices, err := th.GetExercices() - if err != nil { - log.Println("Unable to GetExercices: ", err.Error()) - c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": fmt.Sprintf("Unable to list exercices for theme #%d: %s", th.Id, err.Error())}) - return - } - - for _, exercice := range exercices { - export_exercice := ExportExercice{ - Exercice: exercice, - } - - qa, err := exercice.GetQAQueries() - if err != nil { - log.Println("Unable to GetQAQueries: ", err.Error()) - c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": fmt.Sprintf("Unable to list QA entries: %s", err.Error())}) - return - } - - for _, q := range qa { - export_report := ExportReport{ - Report: q, - } - - comments, err := q.GetComments() - if err != nil { - log.Println("Unable to GetQAComments: ", err.Error()) - c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": fmt.Sprintf("Unable to list QA comments: %s", err.Error())}) - return - } - - for _, c := range comments { - export_report.Comments = append(export_report.Comments, c) - } - - export_exercice.Reports = append(export_exercice.Reports, export_report) - } - - export_theme.Exercices = append(export_theme.Exercices, export_exercice) - } - - report[th.Name] = export_theme - } - - c.JSON(http.StatusOK, report) -} - -type QAQueryAndComment struct { - *fic.QAQuery - Content string `json:"content"` -} - -func createExerciceQA(c *gin.Context) { - teamid := c.MustGet("LoggedTeam").(int64) - ficteam := c.MustGet("LoggedUser").(string) - - // Create a new query - var uq QAQueryAndComment - if err := c.ShouldBindJSON(&uq); err != nil { - c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"errmsg": err.Error()}) - return - } - - if len(uq.State) == 0 { - c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"errmsg": "State not filled"}) - return - } - - if len(uq.Subject) == 0 { - if uq.State == "ok" { - tmp := time.Now() - uq.Subject = "RAS" - uq.Solved = &tmp - } else if uq.State == "timer" { - tmp := time.Now() - uq.Subject = fmt.Sprintf("Temps passé équipe #%d (%s)", teamid, ficteam) - uq.Solved = &tmp - } else { - c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"errmsg": "Subject not filled"}) - return - } - } - - exercice := c.MustGet("exercice").(*fic.Exercice) - qa, err := exercice.NewQAQuery(uq.Subject, &teamid, ficteam, uq.State, uq.Solved) - if err != nil { - log.Println("Unable to NewQAQuery: ", err.Error()) - c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"errmsg": "Unable to create the new QA query. Please retry."}) - return - } - - if len(uq.Content) > 0 { - _, err = qa.AddComment(uq.Content, &teamid, ficteam) - - if err != nil { - log.Println("Unable to AddComment: ", err.Error()) - c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"errmsg": "QA entry added successfully, but unable to create the associated comment. Please retry."}) - return - } - } - - c.JSON(http.StatusOK, qa) -} - -func updateExerciceQA(c *gin.Context) { - query := c.MustGet("qa").(*fic.QAQuery) - - var uq *fic.QAQuery - if err := c.ShouldBindJSON(&uq); err != nil { - c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"errmsg": err.Error()}) - return - } - - uq.Id = query.Id - - if uq.User != query.User && (uq.IdExercice != query.IdExercice || uq.IdTeam != query.IdTeam || uq.User != query.User || uq.Creation != query.Creation || uq.State != query.State || uq.Subject != query.Subject || uq.Closed != query.Closed) { - c.AbortWithStatusJSON(http.StatusForbidden, gin.H{"errmsg": "You can only update your own entry."}) - return - } - - _, err := uq.Update() - if err != nil { - log.Println("Unable to Update QAQuery:", err.Error()) - c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"errmsg": "Unable to update the query. Please try again."}) - return - } - - c.JSON(http.StatusOK, uq) -} - -func deleteExerciceQA(c *gin.Context) { - query := c.MustGet("qa").(*fic.QAQuery) - user := c.MustGet("LoggedUser").(string) - - if user != query.User { - c.AbortWithStatusJSON(http.StatusForbidden, gin.H{"errmsg": "You can only delete your own entry."}) - return - } - - _, err := query.Delete() - if err != nil { - log.Println("Unable to Delete QAQuery:", err.Error()) - c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": "Unable to delete the query. Please try again."}) - return - } - - c.JSON(http.StatusNoContent, nil) -} - -func getQAComments(c *gin.Context) { - query := c.MustGet("qa").(*fic.QAQuery) - - comments, err := query.GetComments() - if err != nil { - log.Println("Unable to GetComments: ", err.Error()) - c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": fmt.Sprintf("Unable to list comments: %s", err.Error())}) - return - } - - c.JSON(http.StatusOK, comments) -} - -func createQAComment(c *gin.Context) { - // Create a new query - var uc *fic.QAComment - if err := c.ShouldBindJSON(&uc); 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 - } - - teamid := c.MustGet("LoggedTeam").(int64) - ficteam := c.MustGet("LoggedUser").(string) - - query := c.MustGet("qa").(*fic.QAQuery) - comment, err := query.AddComment(uc.Content, &teamid, ficteam) - if err != nil { - log.Println("Unable to AddComment: ", err.Error()) - c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": "Unable to add your comment. Please try again later."}) - return - } - - c.JSON(http.StatusOK, comment) -} - -func deleteQAComment(c *gin.Context) { - ficteam := c.MustGet("LoggedUser").(string) - comment := c.MustGet("comment").(*fic.QAComment) - - if ficteam != comment.User { - c.AbortWithStatusJSON(http.StatusForbidden, gin.H{"errmsg": "You can only delete your own comment."}) - return - } - - _, err := comment.Delete() - if err != nil { - log.Println("Unable to Delete QAComment:", err.Error()) - c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": "Unable to delete the comment. Please try again."}) - return - } - - c.JSON(http.StatusNoContent, nil) - -} diff --git a/qa/api/router.go b/qa/api/router.go deleted file mode 100644 index b516bbf1..00000000 --- a/qa/api/router.go +++ /dev/null @@ -1,34 +0,0 @@ -package api - -import ( - "net/http" - - "github.com/gin-gonic/gin" -) - -func DeclareRoutes(router *gin.RouterGroup) { - apiRoutes := router.Group("/api") - apiRoutes.Use(authMiddleware()) - - declareExercicesRoutes(apiRoutes) - declareQARoutes(apiRoutes) - declareThemesRoutes(apiRoutes) - declareTodoRoutes(apiRoutes) - declareVersionRoutes(apiRoutes) - declareGitlabRoutes(router, apiRoutes) - - apiManagerRoutes := router.Group("/api") - apiManagerRoutes.Use(authMiddleware(func(ficteam string, teamid int64, c *gin.Context) bool { - for _, manager := range ManagerUsers { - if manager == ficteam { - return true - } - } - - c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{"errmsg": "Not authorized."}) - return false - })) - - declareTodoManagerRoutes(apiManagerRoutes) - declareTeamsRoutes(apiManagerRoutes) -} diff --git a/qa/api/team.go b/qa/api/team.go deleted file mode 100644 index 0286c0c3..00000000 --- a/qa/api/team.go +++ /dev/null @@ -1,53 +0,0 @@ -package api - -import ( - "fmt" - "log" - "net/http" - "strconv" - - "srs.epita.fr/fic-server/libfic" - - "github.com/gin-gonic/gin" -) - -func declareTeamsRoutes(router *gin.RouterGroup) { - router.GET("/teams", listTeams) - - teamsRoutes := router.Group("/teams/:tid") - teamsRoutes.Use(teamHandler) - teamsRoutes.GET("", showTeam) - - declareTodoRoutes(teamsRoutes) - declareTodoManagerRoutes(teamsRoutes) -} - -func teamHandler(c *gin.Context) { - var team *fic.Team - if tid, err := strconv.ParseInt(string(c.Param("tid")), 10, 64); err != nil { - c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"errmsg": "Bad team identifier."}) - return - } else if team, err = fic.GetTeam(tid); err != nil { - c.AbortWithStatusJSON(http.StatusNotFound, gin.H{"errmsg": "Team not found."}) - return - } - - c.Set("team", team) - - c.Next() -} - -func listTeams(c *gin.Context) { - teams, err := fic.GetTeams() - if err != nil { - log.Println("Unable to GetTeams: ", err.Error()) - c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": fmt.Sprintf("Unable to list teams: %s", err.Error())}) - return - } - - c.JSON(http.StatusOK, teams) -} - -func showTeam(c *gin.Context) { - c.JSON(http.StatusOK, c.MustGet("team")) -} diff --git a/qa/api/theme.go b/qa/api/theme.go deleted file mode 100644 index 12229be1..00000000 --- a/qa/api/theme.go +++ /dev/null @@ -1,64 +0,0 @@ -package api - -import ( - "fmt" - "log" - "net/http" - "strconv" - - "srs.epita.fr/fic-server/libfic" - - "github.com/gin-gonic/gin" -) - -func declareThemesRoutes(router *gin.RouterGroup) { - router.GET("/themes", listThemes) - router.GET("/themes.json", exportThemes) - - themesRoutes := router.Group("/themes/:thid") - themesRoutes.Use(themeHandler) - themesRoutes.GET("", showTheme) - - declareExercicesRoutes(themesRoutes) -} - -func themeHandler(c *gin.Context) { - var theme *fic.Theme - if thid, err := strconv.ParseInt(string(c.Param("thid")), 10, 64); err != nil { - c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"errmsg": "Bad theme identifier."}) - return - } else if theme, err = fic.GetTheme(thid); err != nil { - c.AbortWithStatusJSON(http.StatusNotFound, gin.H{"errmsg": "Theme not found."}) - return - } - - c.Set("theme", theme) - - c.Next() -} - -func listThemes(c *gin.Context) { - themes, err := fic.GetThemes() - if err != nil { - log.Println("Unable to GetThemes: ", err.Error()) - c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": fmt.Sprintf("Unable to list themes: %s", err.Error())}) - return - } - - 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": fmt.Sprintf("Unable to export themes: %s", err.Error())}) - return - } - - c.JSON(http.StatusOK, themes) -} - -func showTheme(c *gin.Context) { - c.JSON(http.StatusOK, c.MustGet("theme")) -} diff --git a/qa/api/todo.go b/qa/api/todo.go deleted file mode 100644 index 37987766..00000000 --- a/qa/api/todo.go +++ /dev/null @@ -1,404 +0,0 @@ -package api - -import ( - "fmt" - "log" - "net/http" - "strconv" - "strings" - - "srs.epita.fr/fic-server/libfic" - - "github.com/gin-gonic/gin" -) - -func declareTodoRoutes(router *gin.RouterGroup) { - router.GET("/qa_exercices.json", getExerciceTested) - router.GET("/qa_mywork.json", getQAWork) - router.GET("/qa_myexercices.json", getQAView) - router.GET("/qa_work.json", getQATodo) -} - -func declareTodoManagerRoutes(router *gin.RouterGroup) { - router.POST("/qa_assign_work", assignWork) - router.DELETE("/qa_assign_work", deleteAssignedWork) - router.POST("/qa_my_exercices.json", addQAView) - router.POST("/qa_work.json", createQATodo) - - todosRoutes := router.Group("/todo/:wid") - todosRoutes.Use(todoHandler) - todosRoutes.GET("", showTodo) - todosRoutes.DELETE("", deleteQATodo) -} - -type exerciceTested map[int64]string - -func getExerciceTested(c *gin.Context) { - var teamid int64 - if team, ok := c.Get("team"); ok { - teamid = team.(*fic.Team).Id - } else { - teamid = c.MustGet("LoggedTeam").(int64) - } - - team, err := fic.GetTeam(teamid) - if err != nil { - c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"errmsg": err.Error()}) - return - } - - exercices, err := fic.GetExercices() - if err != nil { - c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"errmsg": err.Error()}) - return - } - - ret := exerciceTested{} - - for _, exercice := range exercices { - if team.HasAccess(exercice) { - if t := team.HasSolved(exercice); t != nil { - ret[exercice.Id] = "solved" - } else if cnt, _ := team.CountTries(exercice); cnt > 0 { - ret[exercice.Id] = "tried" - } else { - ret[exercice.Id] = "access" - } - } - } - - c.JSON(http.StatusOK, ret) -} - -func getQAView(c *gin.Context) { - var teamid int64 - if team, ok := c.Get("team"); ok { - teamid = team.(*fic.Team).Id - } else { - teamid = c.MustGet("LoggedTeam").(int64) - } - - team, err := fic.GetTeam(teamid) - if err != nil { - c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"errmsg": err.Error()}) - return - } - - view, err := team.GetQAView() - if err != nil { - c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"errmsg": err.Error()}) - return - } - - c.JSON(http.StatusOK, view) -} - -func getQAWork(c *gin.Context) { - var teamid int64 - if team, ok := c.Get("team"); ok { - teamid = team.(*fic.Team).Id - } else { - teamid = c.MustGet("LoggedTeam").(int64) - } - - team, err := fic.GetTeam(teamid) - if err != nil { - c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"errmsg": err.Error()}) - return - } - - queries, err := team.GetQAQueries() - if err != nil { - c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"errmsg": err.Error()}) - return - } - - c.JSON(http.StatusOK, queries) -} - -func getQATodo(c *gin.Context) { - var teamid int64 - if team, ok := c.Get("team"); ok { - teamid = team.(*fic.Team).Id - } else { - teamid = c.MustGet("LoggedTeam").(int64) - } - - team, err := fic.GetTeam(teamid) - if err != nil { - c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"errmsg": err.Error()}) - return - } - - todo, err := team.GetQATodo() - if err != nil { - c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"errmsg": err.Error()}) - return - } - - exercices, err := fic.GetExercices() - if err != nil { - c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"errmsg": err.Error()}) - return - } - - for _, exercice := range exercices { - if cnt, _ := team.CountTries(exercice); cnt > 0 { - todo = append(todo, &fic.QATodo{ - Id: 0, - IdTeam: teamid, - IdExercice: exercice.Id, - }) - } - } - - c.JSON(http.StatusOK, todo) -} - -func createQATodo(c *gin.Context) { - var ut fic.QATodo - if err := c.ShouldBindJSON(&ut); err != nil { - c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"errmsg": err.Error()}) - return - } - - team, err := fic.GetTeam(ut.IdTeam) - if err != nil { - c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"errmsg": err.Error()}) - return - } - - todo, err := team.NewQATodo(ut.IdExercice) - if err != nil { - c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"errmsg": err.Error()}) - return - } - - c.JSON(http.StatusOK, todo) -} - -func addQAView(c *gin.Context) { - var ut fic.QATodo - if err := c.ShouldBindJSON(&ut); err != nil { - c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"errmsg": err.Error()}) - return - } - - team, err := fic.GetTeam(ut.IdTeam) - if err != nil { - c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"errmsg": err.Error()}) - return - } - - view, err := team.NewQAView(ut.IdExercice) - if err != nil { - c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"errmsg": err.Error()}) - return - } - - c.JSON(http.StatusOK, view) -} - -func todoHandler(c *gin.Context) { - team := c.MustGet("team").(*fic.Team) - - var wid int64 - var todos []*fic.QATodo - var err error - - if wid, err = strconv.ParseInt(string(c.Param("wid")), 10, 64); err != nil { - c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"errmsg": "Bad todo identifier."}) - return - } - - if todos, err = team.GetQATodo(); err != nil { - c.AbortWithStatusJSON(http.StatusNotFound, gin.H{"errmsg": "Todo not found."}) - return - } - - for _, t := range todos { - if t.Id == wid { - c.Set("todo", t) - c.Next() - return - } - } - - c.AbortWithStatusJSON(http.StatusNotFound, gin.H{"errmsg": "Unable to find the requested QA Todo"}) -} - -func showTodo(c *gin.Context) { - c.JSON(http.StatusOK, c.MustGet("todo")) -} - -func deleteQATodo(c *gin.Context) { - todo := c.MustGet("todo").(*fic.QATodo) - - if _, err := todo.Delete(); err != nil { - c.AbortWithError(http.StatusInternalServerError, err) - } else { - c.Status(http.StatusOK) - } -} - -type QAAssignWork struct { - Turns int `json:"turns"` - Start int `json:"start"` - TeamPrefix string `json:"team_prefix"` - TeamAssistants string `json:"team_assistants"` - OnlyExercices bool `json:"only_exercices"` - WithoutStandaloneExercices bool `json:"without_standalone_exercices"` - WithoutThemes bool `json:"without_themes"` -} - -func assignWork(c *gin.Context) { - var uaw QAAssignWork - if err := c.ShouldBindJSON(&uaw); err != nil { - c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"errmsg": err.Error()}) - return - } - if uaw.Turns == 0 { - uaw.Turns = 1 - } - if uaw.TeamPrefix == "" { - uaw.TeamPrefix = "FIC Groupe " - } - if uaw.TeamAssistants == "" { - uaw.TeamAssistants = "assistants" - } - - teams, err := fic.GetTeams() - if err != nil { - log.Println("Unable to GetTeams: ", err.Error()) - c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": fmt.Sprintf("Unable to list teams: %s", err.Error())}) - return - } - - // Remove assistant team - for tid := len(teams) - 1; tid >= 0; tid-- { - team := teams[tid] - if strings.Contains(strings.ToLower(team.Name), uaw.TeamAssistants) { - teams = append(teams[:tid], teams[tid+1:]...) - } - } - - var themes []*fic.Theme - if !uaw.OnlyExercices && !uaw.WithoutThemes { - themes, err = fic.GetThemes() - if err != nil { - log.Println("Unable to GetThemes: ", err.Error()) - c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": fmt.Sprintf("Unable to list themes: %s", err.Error())}) - return - } - } - - var exercices []*fic.Exercice - if uaw.OnlyExercices { - exercices, err = fic.GetExercices() - if err != nil { - log.Println("Unable to GetExercices: ", err.Error()) - c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": fmt.Sprintf("Unable to list exercices: %s", err.Error())}) - return - } - if uaw.WithoutStandaloneExercices || uaw.WithoutThemes { - for i := len(exercices) - 1; i >= 0; i-- { - if (exercices[i].IdTheme == nil && uaw.WithoutStandaloneExercices) || (exercices[i].IdTheme != nil && uaw.WithoutThemes) { - exercices = append(exercices[:i], exercices[i+1:]...) - } - } - } - } else if !uaw.WithoutStandaloneExercices { - exercices, err = (&fic.Theme{URLId: "_", Path: "exercices"}).GetExercices() - if err != nil { - log.Println("Unable to GetStandaloneExercices: ", err.Error()) - c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": fmt.Sprintf("Unable to list exercices: %s", err.Error())}) - return - } - } - - // Struct to store reported team (due to owned exercice) - var teamIdStack []int64 - - for i := 0; i < uaw.Turns; i++ { - lasttid := 0 - for tid, theme := range themes { - lasttid = tid - team := teams[(uaw.Start+tid+uaw.Turns*i)%len(teams)] - - if len(teamIdStack) > 0 { - teamIdStack = append(teamIdStack, team.Id) - team, _ = fic.GetTeam(teamIdStack[0]) - teamIdStack = append([]int64{}, teamIdStack[1:]...) - } - - j := 0 - // Find a team not responsible for this exercice - for (strings.Contains(theme.Path, "grp") && strings.Contains(theme.Path, "-grp"+strings.TrimPrefix(team.Name, uaw.TeamPrefix)+"-")) || (!strings.Contains(theme.Path, "grp") && strings.HasPrefix(theme.Path, strings.TrimPrefix(team.Name, uaw.TeamPrefix)+"-")) { - j += 1 - teamIdStack = append(teamIdStack, team.Id) - team = teams[(uaw.Start+tid+uaw.Turns*i+j)%len(teams)] - } - - exercices, err := theme.GetExercices() - if err != nil { - c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": err.Error()}) - return - } - - for _, ex := range exercices { - _, err := team.NewQATodo(ex.Id) - if err != nil { - c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"errmsg": err.Error()}) - return - } - } - } - - for eid, ex := range exercices { - team := teams[(uaw.Start+lasttid+eid+uaw.Turns*i)%len(teams)] - - if len(teamIdStack) > 0 { - teamIdStack = append(teamIdStack, team.Id) - team, _ = fic.GetTeam(teamIdStack[0]) - teamIdStack = append([]int64{}, teamIdStack[1:]...) - } - - j := 0 - // Find a team not responsible for this exercice - for (strings.Contains(ex.Path, "grp") && strings.Contains(ex.Path, "-grp"+strings.TrimPrefix(team.Name, uaw.TeamPrefix)+"-")) || (!strings.Contains(ex.Path, "grp") && strings.HasPrefix(ex.Path, strings.TrimPrefix(team.Name, uaw.TeamPrefix)+"-")) { - j += 1 - teamIdStack = append(teamIdStack, team.Id) - team = teams[(uaw.Start+eid+uaw.Turns*i+j)%len(teams)] - } - - _, err := team.NewQATodo(ex.Id) - if err != nil { - c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"errmsg": err.Error()}) - return - } - } - } - - c.JSON(http.StatusOK, "true") -} - -func deleteAssignedWork(c *gin.Context) { - teams, err := fic.GetTeams() - if err != nil { - log.Println("Unable to GetTeams: ", err.Error()) - c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": fmt.Sprintf("Unable to list teams: %s", err.Error())}) - return - } - - for _, team := range teams { - todos, err := team.GetQATodo() - if err != nil { - c.AbortWithStatusJSON(http.StatusNotFound, gin.H{"errmsg": "Todo not found."}) - return - } - - for _, t := range todos { - t.Delete() - } - } -} diff --git a/qa/api/version.go b/qa/api/version.go deleted file mode 100644 index 29330a54..00000000 --- a/qa/api/version.go +++ /dev/null @@ -1,34 +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) { - teamid := c.MustGet("LoggedTeam").(int64) - ficteam := c.MustGet("LoggedUser").(string) - - var ismanager bool - - for _, manager := range ManagerUsers { - if manager == ficteam { - ismanager = true - break - } - } - - c.JSON(http.StatusOK, gin.H{ - "version": 0.4, - "auth": map[string]interface{}{ - "name": ficteam, - "id_team": teamid, - "is_manager": ismanager, - }, - }) -} diff --git a/qa/app.go b/qa/app.go deleted file mode 100644 index d8426a36..00000000 --- a/qa/app.go +++ /dev/null @@ -1,85 +0,0 @@ -package main - -import ( - "context" - "log" - "net/http" - "time" - - "srs.epita.fr/fic-server/qa/api" - - "github.com/gin-contrib/sessions" - "github.com/gin-contrib/sessions/memstore" - "github.com/gin-gonic/gin" -) - -type App struct { - router *gin.Engine - srv *http.Server -} - -func NewApp(baseURL string) App { - gin.ForceConsoleColor() - router := gin.Default() - - api.InitializeGitLabOIDC(baseURL) - - store := memstore.NewStore([]byte("secret")) - router.Use(sessions.Sessions("qa-session", store)) - router.Use(func(c *gin.Context) { - c.Set("baseurl", baseURL) - }) - - api.DeclareRoutes(router.Group("")) - - var baserouter *gin.RouterGroup - if len(baseURL) > 0 { - router.GET("/", func(c *gin.Context) { - c.Redirect(http.StatusFound, baseURL) - }) - - baserouter = router.Group(baseURL) - - api.DeclareRoutes(baserouter) - declareStaticRoutes(baserouter, baseURL) - - // In case of /baseURL/baseURL, redirect to /baseURL - baserouter.GET(baseURL, func(c *gin.Context) { - c.Redirect(http.StatusFound, baseURL) - }) - baserouter.GET(baseURL+"/*path", func(c *gin.Context) { - c.Redirect(http.StatusFound, baseURL+c.Param("path")) - }) - } else { - declareStaticRoutes(router.Group(""), "") - } - - app := App{ - router: router, - } - - return app -} - -func (app *App) Start(bind string) { - app.srv = &http.Server{ - Addr: bind, - Handler: app.router, - ReadHeaderTimeout: 15 * time.Second, - ReadTimeout: 15 * time.Second, - WriteTimeout: 10 * time.Second, - IdleTimeout: 30 * time.Second, - } - - 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) - } -} diff --git a/qa/assets-dev.go b/qa/assets-dev.go deleted file mode 100644 index dee90bca..00000000 --- a/qa/assets-dev.go +++ /dev/null @@ -1,32 +0,0 @@ -//go:build dev -// +build dev - -package main - -import ( - "flag" - "net/http" - "os" - "path/filepath" -) - -var ( - Assets http.FileSystem - StaticDir string = "ui/" -) - -func init() { - flag.StringVar(&StaticDir, "static", StaticDir, "Directory containing static files") -} - -func sanitizeStaticOptions() error { - StaticDir, _ = filepath.Abs(StaticDir) - if _, err := os.Stat(StaticDir); os.IsNotExist(err) { - StaticDir, _ = filepath.Abs(filepath.Join(filepath.Dir(os.Args[0]), "ui")) - if _, err := os.Stat(StaticDir); os.IsNotExist(err) { - return err - } - } - Assets = http.Dir(StaticDir) - return nil -} diff --git a/qa/assets.go b/qa/assets.go deleted file mode 100644 index 2c8d20bc..00000000 --- a/qa/assets.go +++ /dev/null @@ -1,28 +0,0 @@ -//go:build !dev -// +build !dev - -package main - -import ( - "embed" - "io/fs" - "log" - "net/http" -) - -//go:embed all:ui/build -var _assets embed.FS - -var Assets http.FileSystem - -func init() { - sub, err := fs.Sub(_assets, "ui/build") - if err != nil { - log.Fatal("Unable to cd to ui/build directory:", err) - } - Assets = http.FS(sub) -} - -func sanitizeStaticOptions() error { - return nil -} diff --git a/qa/main.go b/qa/main.go deleted file mode 100644 index f4fd6b4a..00000000 --- a/qa/main.go +++ /dev/null @@ -1,83 +0,0 @@ -package main - -import ( - "flag" - "log" - "os" - "os/signal" - "path" - "syscall" - - "srs.epita.fr/fic-server/libfic" - "srs.epita.fr/fic-server/qa/api" - "srs.epita.fr/fic-server/settings" -) - -func reloadSettings(config *settings.Settings) { - api.ManagerUsers = config.DelegatedQA - fic.UnlockedChallengeDepth = config.UnlockedChallengeDepth - fic.UnlockedChallengeUpTo = config.UnlockedChallengeUpTo - fic.UnlockedStandaloneExercices = config.UnlockedStandaloneExercices - fic.UnlockedStandaloneExercicesByThemeStepValidation = config.UnlockedStandaloneExercicesByThemeStepValidation - fic.UnlockedStandaloneExercicesByStandaloneExerciceValidation = config.UnlockedStandaloneExercicesByStandaloneExerciceValidation -} - -func main() { - // Read paremeters from environment - if v, exists := os.LookupEnv("FIC_BASEURL"); exists { - BaseURL = v - } - - // Read parameters from command line - var bind = flag.String("bind", "127.0.0.1:8083", "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(&DevProxy, "dev", DevProxy, "Proxify traffic to this host for static assets") - flag.StringVar(&settings.SettingsDir, "settings", "./SETTINGSDIST", "Base directory where load and save settings") - flag.StringVar(&api.TeamsDir, "teams", "./TEAMS", "Base directory where save teams JSON files") - flag.StringVar(&api.Simulator, "simulator", "", "Auth string to simulate (for development only)") - flag.Parse() - - log.SetPrefix("[qa] ") - - // Sanitize options - var err error - log.Println("Checking paths...") - if BaseURL != "/" { - BaseURL = path.Clean(BaseURL) - } else { - BaseURL = "" - } - if err = sanitizeStaticOptions(); err != nil { - log.Fatal(err) - } - if api.Simulator != "" { - if _, err := os.Stat(path.Join(api.TeamsDir, api.Simulator)); os.IsNotExist(err) { - log.Fatal(err) - } - } - - // Load configuration - settings.LoadAndWatchSettings(path.Join(settings.SettingsDir, settings.SettingsFile), reloadSettings) - - // Database connection - log.Println("Opening database...") - if err = fic.DBInit(*dsn); err != nil { - log.Fatal("Cannot open the database: ", err) - } - defer fic.DBClose() - - a := NewApp(BaseURL) - go a.Start(*bind) - - // Prepare graceful shutdown - interrupt := make(chan os.Signal, 1) - signal.Notify(interrupt, os.Interrupt, syscall.SIGTERM) - - // Wait shutdown signal - <-interrupt - - log.Print("The service is shutting down...") - a.Stop() - log.Println("done") -} diff --git a/qa/static.go b/qa/static.go deleted file mode 100644 index 6993c0c5..00000000 --- a/qa/static.go +++ /dev/null @@ -1,136 +0,0 @@ -package main - -import ( - "bytes" - "io" - "io/ioutil" - "log" - "mime" - "net/http" - "net/url" - "path" - "strings" - - "github.com/gin-gonic/gin" -) - -var ( - BaseURL = "/" - DevProxy string - indexTmpl []byte -) - -func getIndexHtml(w io.Writer, file io.Reader) []byte { - if indexTmpl, err := ioutil.ReadAll(file); err != nil { - log.Println("Cannot read whole index.html: ", err) - return nil - } else { - good_base := []byte(path.Clean(path.Join(BaseURL+"/", "nuke"))[:len(path.Clean(path.Join(BaseURL+"/", "nuke")))-4]) - - indexTmpl = bytes.Replace( - bytes.Replace( - bytes.Replace( - indexTmpl, - []byte(``), - []byte(``), - -1, - ), - []byte("\"/_app/"), - append([]byte("\""), append(good_base, '_', 'a', 'p', 'p', '/')...), - -1, - ), - []byte("base: \""), - append([]byte("base: \""), good_base[:len(good_base)-1]...), - -1, - ) - - w.Write(indexTmpl) - return indexTmpl - } -} - -func serveOrReverse(forced_url string, baseURL string) func(c *gin.Context) { - return func(c *gin.Context) { - if DevProxy != "" { - if u, err := url.Parse(DevProxy); err != nil { - http.Error(c.Writer, err.Error(), http.StatusInternalServerError) - } else { - if forced_url != "" && forced_url != "/" { - u.Path = path.Join(u.Path, forced_url) - } else { - u.Path = path.Join(u.Path, strings.TrimPrefix(c.Request.URL.Path, baseURL)) - } - - if r, err := http.NewRequest(c.Request.Method, u.String(), c.Request.Body); err != nil { - http.Error(c.Writer, err.Error(), http.StatusInternalServerError) - } else if resp, err := http.DefaultClient.Do(r); err != nil { - http.Error(c.Writer, err.Error(), http.StatusBadGateway) - } else { - defer resp.Body.Close() - - for key := range resp.Header { - c.Writer.Header().Add(key, resp.Header.Get(key)) - } - c.Writer.WriteHeader(resp.StatusCode) - - if r.URL.Path == path.Join(u.Path, "/") { - getIndexHtml(c.Writer, resp.Body) - } else { - io.Copy(c.Writer, resp.Body) - } - } - } - } else { - if forced_url != "" { - c.Request.URL.Path = forced_url - } else { - c.Request.URL.Path = strings.TrimPrefix(c.Request.URL.Path, baseURL) - } - - if c.Request.URL.Path == "/" { - if len(indexTmpl) == 0 { - if file, err := Assets.Open("index.html"); err != nil { - log.Println("Unable to open index.html: ", err) - } else { - defer file.Close() - - indexTmpl = getIndexHtml(c.Writer, file) - } - } else { - c.Writer.Write(indexTmpl) - } - } else if baseURL != "/" { - if file, err := Assets.Open(c.Request.URL.Path); err != nil { - http.FileServer(Assets).ServeHTTP(c.Writer, c.Request) - } else { - defer file.Close() - - c.Writer.Header().Add("Content-Type", mime.TypeByExtension(path.Ext(c.Request.URL.Path))) - - getIndexHtml(c.Writer, file) - } - } else { - http.FileServer(Assets).ServeHTTP(c.Writer, c.Request) - } - } - } -} - -func declareStaticRoutes(router *gin.RouterGroup, baseURL string) { - router.GET("/", serveOrReverse("", baseURL)) - router.GET("/exercices", serveOrReverse("/", baseURL)) - router.GET("/exercices/*_", serveOrReverse("/", baseURL)) - router.GET("/export", serveOrReverse("/", baseURL)) - router.GET("/teams", serveOrReverse("/", baseURL)) - router.GET("/teams/*_", serveOrReverse("/", baseURL)) - router.GET("/themes", serveOrReverse("/", baseURL)) - router.GET("/themes/*_", serveOrReverse("/", baseURL)) - router.GET("/_app/*_", serveOrReverse("", baseURL)) - - router.GET("/.svelte-kit/*_", serveOrReverse("", baseURL)) - router.GET("/node_modules/*_", serveOrReverse("", baseURL)) - router.GET("/@vite/*_", serveOrReverse("", baseURL)) - router.GET("/@fs/*_", serveOrReverse("", baseURL)) - router.GET("/@id/*_", serveOrReverse("", baseURL)) - router.GET("/src/*_", serveOrReverse("", baseURL)) -} diff --git a/qa/ui/.eslintrc.cjs b/qa/ui/.eslintrc.cjs deleted file mode 100644 index e496918e..00000000 --- a/qa/ui/.eslintrc.cjs +++ /dev/null @@ -1,15 +0,0 @@ -module.exports = { - root: true, - extends: ['eslint:recommended', 'prettier'], - plugins: ['svelte3'], - overrides: [{ files: ['*.svelte'], processor: 'svelte3/svelte3' }], - parserOptions: { - sourceType: 'module', - ecmaVersion: 2019 - }, - env: { - browser: true, - es2017: true, - node: true - } -}; diff --git a/qa/ui/.gitignore b/qa/ui/.gitignore deleted file mode 100644 index a5168ff2..00000000 --- a/qa/ui/.gitignore +++ /dev/null @@ -1,8 +0,0 @@ -.DS_Store -node_modules -/build -/.svelte-kit -/.vite -/package -.env -.env.* diff --git a/qa/ui/.prettierrc b/qa/ui/.prettierrc deleted file mode 100644 index ff2677ef..00000000 --- a/qa/ui/.prettierrc +++ /dev/null @@ -1,6 +0,0 @@ -{ - "useTabs": true, - "singleQuote": true, - "trailingComma": "none", - "printWidth": 100 -} diff --git a/qa/ui/README.md b/qa/ui/README.md deleted file mode 100644 index 82510ca0..00000000 --- a/qa/ui/README.md +++ /dev/null @@ -1,38 +0,0 @@ -# create-svelte - -Everything you need to build a Svelte project, powered by [`create-svelte`](https://github.com/sveltejs/kit/tree/master/packages/create-svelte); - -## Creating a project - -If you're seeing this, you've probably already done this step. Congrats! - -```bash -# create a new project in the current directory -npm init svelte@next - -# create a new project in my-app -npm init svelte@next my-app -``` - -> Note: the `@next` is temporary - -## Developing - -Once you've created a project and installed dependencies with `npm install` (or `pnpm install` or `yarn`), start a development server: - -```bash -npm run dev - -# or start the server and open the app in a new browser tab -npm run dev -- --open -``` - -## Building - -Before creating a production version of your app, install an [adapter](https://kit.svelte.dev/docs#adapters) for your target environment. Then: - -```bash -npm run build -``` - -> You can preview the built app with `npm run preview`, regardless of whether you installed an adapter. This should _not_ be used to serve your app in production. diff --git a/qa/ui/jsconfig.json b/qa/ui/jsconfig.json deleted file mode 100644 index 81ff9770..00000000 --- a/qa/ui/jsconfig.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "extends": "./.svelte-kit/tsconfig.json" -} diff --git a/qa/ui/package-lock.json b/qa/ui/package-lock.json deleted file mode 100644 index 775181b2..00000000 --- a/qa/ui/package-lock.json +++ /dev/null @@ -1,2742 +0,0 @@ -{ - "name": "qa", - "version": "0.0.1", - "lockfileVersion": 3, - "requires": true, - "packages": { - "": { - "name": "qa", - "version": "0.0.1", - "dependencies": { - "@sveltestrap/sveltestrap": "^7.0.0", - "bootstrap": "^5.2.2", - "bootstrap-icons": "^1.9.1" - }, - "devDependencies": { - "@sveltejs/adapter-static": "^3.0.0", - "@sveltejs/kit": "^2.0.0", - "@sveltejs/vite-plugin-svelte": "^3.0.0", - "eslint": "^9.0.0", - "eslint-config-prettier": "^10.0.0", - "eslint-plugin-svelte": "^3.0.0", - "prettier": "^3.0.0", - "prettier-plugin-svelte": "^3.1.2", - "svelte": "^4.0.0", - "vite": "^5.0.0" - } - }, - "node_modules/@ampproject/remapping": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.3.0.tgz", - "integrity": "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==", - "license": "Apache-2.0", - "dependencies": { - "@jridgewell/gen-mapping": "^0.3.5", - "@jridgewell/trace-mapping": "^0.3.24" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@esbuild/aix-ppc64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz", - "integrity": "sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==", - "cpu": [ - "ppc64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "aix" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/android-arm": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.21.5.tgz", - "integrity": "sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/android-arm64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.21.5.tgz", - "integrity": "sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/android-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.21.5.tgz", - "integrity": "sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/darwin-arm64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.21.5.tgz", - "integrity": "sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/darwin-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.21.5.tgz", - "integrity": "sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/freebsd-arm64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.21.5.tgz", - "integrity": "sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/freebsd-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.21.5.tgz", - "integrity": "sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-arm": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.21.5.tgz", - "integrity": "sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-arm64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.21.5.tgz", - "integrity": "sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-ia32": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.21.5.tgz", - "integrity": "sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==", - "cpu": [ - "ia32" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-loong64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.21.5.tgz", - "integrity": "sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==", - "cpu": [ - "loong64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-mips64el": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.21.5.tgz", - "integrity": "sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==", - "cpu": [ - "mips64el" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-ppc64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.21.5.tgz", - "integrity": "sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==", - "cpu": [ - "ppc64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-riscv64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.21.5.tgz", - "integrity": "sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==", - "cpu": [ - "riscv64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-s390x": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.21.5.tgz", - "integrity": "sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==", - "cpu": [ - "s390x" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.21.5.tgz", - "integrity": "sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/netbsd-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.21.5.tgz", - "integrity": "sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "netbsd" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/openbsd-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.21.5.tgz", - "integrity": "sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openbsd" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/sunos-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.21.5.tgz", - "integrity": "sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "sunos" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/win32-arm64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.21.5.tgz", - "integrity": "sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/win32-ia32": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.21.5.tgz", - "integrity": "sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==", - "cpu": [ - "ia32" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/win32-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.21.5.tgz", - "integrity": "sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@eslint-community/eslint-utils": { - "version": "4.5.1", - "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.5.1.tgz", - "integrity": "sha512-soEIOALTfTK6EjmKMMoLugwaP0rzkad90iIWd1hMO9ARkSAyjfMfkRRhLvD5qH7vvM0Cg72pieUfR6yh6XxC4w==", - "dev": true, - "license": "MIT", - "dependencies": { - "eslint-visitor-keys": "^3.4.3" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - }, - "peerDependencies": { - "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" - } - }, - "node_modules/@eslint-community/eslint-utils/node_modules/eslint-visitor-keys": { - "version": "3.4.3", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", - "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/@eslint-community/regexpp": { - "version": "4.12.1", - "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.1.tgz", - "integrity": "sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^12.0.0 || ^14.0.0 || >=16.0.0" - } - }, - "node_modules/@eslint/config-array": { - "version": "0.19.2", - "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.19.2.tgz", - "integrity": "sha512-GNKqxfHG2ySmJOBSHg7LxeUx4xpuCoFjacmlCoYWEbaPXLwvfIjixRI12xCQZeULksQb23uiA8F40w5TojpV7w==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@eslint/object-schema": "^2.1.6", - "debug": "^4.3.1", - "minimatch": "^3.1.2" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - } - }, - "node_modules/@eslint/config-helpers": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.2.0.tgz", - "integrity": "sha512-yJLLmLexii32mGrhW29qvU3QBVTu0GUmEf/J4XsBtVhp4JkIUFN/BjWqTF63yRvGApIDpZm5fa97LtYtINmfeQ==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - } - }, - "node_modules/@eslint/core": { - "version": "0.12.0", - "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.12.0.tgz", - "integrity": "sha512-cmrR6pytBuSMTaBweKoGMwu3EiHiEC+DoyupPmlZ0HxBJBtIxwe+j/E4XPIKNx+Q74c8lXKPwYawBf5glsTkHg==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@types/json-schema": "^7.0.15" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - } - }, - "node_modules/@eslint/eslintrc": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.3.1.tgz", - "integrity": "sha512-gtF186CXhIl1p4pJNGZw8Yc6RlshoePRvE0X91oPGb3vZ8pM3qOS9W9NGPat9LziaBV7XrJWGylNQXkGcnM3IQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "ajv": "^6.12.4", - "debug": "^4.3.2", - "espree": "^10.0.1", - "globals": "^14.0.0", - "ignore": "^5.2.0", - "import-fresh": "^3.2.1", - "js-yaml": "^4.1.0", - "minimatch": "^3.1.2", - "strip-json-comments": "^3.1.1" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/@eslint/js": { - "version": "9.23.0", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.23.0.tgz", - "integrity": "sha512-35MJ8vCPU0ZMxo7zfev2pypqTwWTofFZO6m4KAtdoFhRpLJUpHTZZ+KB3C7Hb1d7bULYwO4lJXGCi5Se+8OMbw==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - } - }, - "node_modules/@eslint/object-schema": { - "version": "2.1.6", - "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.6.tgz", - "integrity": "sha512-RBMg5FRL0I0gs51M/guSAj5/e14VQ4tpZnQNWwuDT66P14I43ItmPfIZRhO9fUVIPOAQXU47atlywZ/czoqFPA==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - } - }, - "node_modules/@eslint/plugin-kit": { - "version": "0.2.7", - "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.2.7.tgz", - "integrity": "sha512-JubJ5B2pJ4k4yGxaNLdbjrnk9d/iDz6/q8wOilpIowd6PJPgaxCuHBnBszq7Ce2TyMrywm5r4PnKm6V3iiZF+g==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@eslint/core": "^0.12.0", - "levn": "^0.4.1" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - } - }, - "node_modules/@humanfs/core": { - "version": "0.19.1", - "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz", - "integrity": "sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": ">=18.18.0" - } - }, - "node_modules/@humanfs/node": { - "version": "0.16.6", - "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.6.tgz", - "integrity": "sha512-YuI2ZHQL78Q5HbhDiBA1X4LmYdXCKCMQIfw0pw7piHJwyREFebJUvrQN4cMssyES6x+vfUbx1CIpaQUKYdQZOw==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@humanfs/core": "^0.19.1", - "@humanwhocodes/retry": "^0.3.0" - }, - "engines": { - "node": ">=18.18.0" - } - }, - "node_modules/@humanfs/node/node_modules/@humanwhocodes/retry": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.3.1.tgz", - "integrity": "sha512-JBxkERygn7Bv/GbN5Rv8Ul6LVknS+5Bp6RgDC/O8gEBU/yeH5Ui5C/OlWrTb6qct7LjjfT6Re2NxB0ln0yYybA==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": ">=18.18" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/nzakas" - } - }, - "node_modules/@humanwhocodes/module-importer": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", - "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": ">=12.22" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/nzakas" - } - }, - "node_modules/@humanwhocodes/retry": { - "version": "0.4.2", - "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.2.tgz", - "integrity": "sha512-xeO57FpIu4p1Ri3Jq/EXq4ClRm86dVF2z/+kvFnyqVYRavTZmaFaUBbWCOuuTh0o/g7DSsk6kc2vrS4Vl5oPOQ==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": ">=18.18" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/nzakas" - } - }, - "node_modules/@jridgewell/gen-mapping": { - "version": "0.3.8", - "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.8.tgz", - "integrity": "sha512-imAbBGkb+ebQyxKgzv5Hu2nmROxoDOXHh80evxdoXNOrvAnVx7zimzc1Oo5h9RlfV4vPXaE2iM5pOFbvOCClWA==", - "license": "MIT", - "dependencies": { - "@jridgewell/set-array": "^1.2.1", - "@jridgewell/sourcemap-codec": "^1.4.10", - "@jridgewell/trace-mapping": "^0.3.24" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@jridgewell/resolve-uri": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", - "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", - "license": "MIT", - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@jridgewell/set-array": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.2.1.tgz", - "integrity": "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==", - "license": "MIT", - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@jridgewell/sourcemap-codec": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz", - "integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==", - "license": "MIT" - }, - "node_modules/@jridgewell/trace-mapping": { - "version": "0.3.25", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz", - "integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==", - "license": "MIT", - "dependencies": { - "@jridgewell/resolve-uri": "^3.1.0", - "@jridgewell/sourcemap-codec": "^1.4.14" - } - }, - "node_modules/@polka/url": { - "version": "1.0.0-next.28", - "resolved": "https://registry.npmjs.org/@polka/url/-/url-1.0.0-next.28.tgz", - "integrity": "sha512-8LduaNlMZGwdZ6qWrKlfa+2M4gahzFkprZiAt2TF8uS0qQgBizKXpXURqvTJ4WtmupWxaLqjRb2UCTe72mu+Aw==", - "dev": true, - "license": "MIT" - }, - "node_modules/@popperjs/core": { - "version": "2.11.8", - "resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.8.tgz", - "integrity": "sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==", - "license": "MIT", - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/popperjs" - } - }, - "node_modules/@rollup/rollup-android-arm-eabi": { - "version": "4.38.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.38.0.tgz", - "integrity": "sha512-ldomqc4/jDZu/xpYU+aRxo3V4mGCV9HeTgUBANI3oIQMOL+SsxB+S2lxMpkFp5UamSS3XuTMQVbsS24R4J4Qjg==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ] - }, - "node_modules/@rollup/rollup-android-arm64": { - "version": "4.38.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.38.0.tgz", - "integrity": "sha512-VUsgcy4GhhT7rokwzYQP+aV9XnSLkkhlEJ0St8pbasuWO/vwphhZQxYEKUP3ayeCYLhk6gEtacRpYP/cj3GjyQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ] - }, - "node_modules/@rollup/rollup-darwin-arm64": { - "version": "4.38.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.38.0.tgz", - "integrity": "sha512-buA17AYXlW9Rn091sWMq1xGUvWQFOH4N1rqUxGJtEQzhChxWjldGCCup7r/wUnaI6Au8sKXpoh0xg58a7cgcpg==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ] - }, - "node_modules/@rollup/rollup-darwin-x64": { - "version": "4.38.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.38.0.tgz", - "integrity": "sha512-Mgcmc78AjunP1SKXl624vVBOF2bzwNWFPMP4fpOu05vS0amnLcX8gHIge7q/lDAHy3T2HeR0TqrriZDQS2Woeg==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ] - }, - "node_modules/@rollup/rollup-freebsd-arm64": { - "version": "4.38.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.38.0.tgz", - "integrity": "sha512-zzJACgjLbQTsscxWqvrEQAEh28hqhebpRz5q/uUd1T7VTwUNZ4VIXQt5hE7ncs0GrF+s7d3S4on4TiXUY8KoQA==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ] - }, - "node_modules/@rollup/rollup-freebsd-x64": { - "version": "4.38.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.38.0.tgz", - "integrity": "sha512-hCY/KAeYMCyDpEE4pTETam0XZS4/5GXzlLgpi5f0IaPExw9kuB+PDTOTLuPtM10TlRG0U9OSmXJ+Wq9J39LvAg==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ] - }, - "node_modules/@rollup/rollup-linux-arm-gnueabihf": { - "version": "4.38.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.38.0.tgz", - "integrity": "sha512-mimPH43mHl4JdOTD7bUMFhBdrg6f9HzMTOEnzRmXbOZqjijCw8LA5z8uL6LCjxSa67H2xiLFvvO67PT05PRKGg==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-arm-musleabihf": { - "version": "4.38.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.38.0.tgz", - "integrity": "sha512-tPiJtiOoNuIH8XGG8sWoMMkAMm98PUwlriOFCCbZGc9WCax+GLeVRhmaxjJtz6WxrPKACgrwoZ5ia/uapq3ZVg==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-arm64-gnu": { - "version": "4.38.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.38.0.tgz", - "integrity": "sha512-wZco59rIVuB0tjQS0CSHTTUcEde+pXQWugZVxWaQFdQQ1VYub/sTrNdY76D1MKdN2NB48JDuGABP6o6fqos8mA==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-arm64-musl": { - "version": "4.38.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.38.0.tgz", - "integrity": "sha512-fQgqwKmW0REM4LomQ+87PP8w8xvU9LZfeLBKybeli+0yHT7VKILINzFEuggvnV9M3x1Ed4gUBmGUzCo/ikmFbQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-loongarch64-gnu": { - "version": "4.38.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.38.0.tgz", - "integrity": "sha512-hz5oqQLXTB3SbXpfkKHKXLdIp02/w3M+ajp8p4yWOWwQRtHWiEOCKtc9U+YXahrwdk+3qHdFMDWR5k+4dIlddg==", - "cpu": [ - "loong64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-powerpc64le-gnu": { - "version": "4.38.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.38.0.tgz", - "integrity": "sha512-NXqygK/dTSibQ+0pzxsL3r4Xl8oPqVoWbZV9niqOnIHV/J92fe65pOir0xjkUZDRSPyFRvu+4YOpJF9BZHQImw==", - "cpu": [ - "ppc64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-riscv64-gnu": { - "version": "4.38.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.38.0.tgz", - "integrity": "sha512-GEAIabR1uFyvf/jW/5jfu8gjM06/4kZ1W+j1nWTSSB3w6moZEBm7iBtzwQ3a1Pxos2F7Gz+58aVEnZHU295QTg==", - "cpu": [ - "riscv64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-riscv64-musl": { - "version": "4.38.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.38.0.tgz", - "integrity": "sha512-9EYTX+Gus2EGPbfs+fh7l95wVADtSQyYw4DfSBcYdUEAmP2lqSZY0Y17yX/3m5VKGGJ4UmIH5LHLkMJft3bYoA==", - "cpu": [ - "riscv64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-s390x-gnu": { - "version": "4.38.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.38.0.tgz", - "integrity": "sha512-Mpp6+Z5VhB9VDk7RwZXoG2qMdERm3Jw07RNlXHE0bOnEeX+l7Fy4bg+NxfyN15ruuY3/7Vrbpm75J9QHFqj5+Q==", - "cpu": [ - "s390x" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-x64-gnu": { - "version": "4.38.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.38.0.tgz", - "integrity": "sha512-vPvNgFlZRAgO7rwncMeE0+8c4Hmc+qixnp00/Uv3ht2x7KYrJ6ERVd3/R0nUtlE6/hu7/HiiNHJ/rP6knRFt1w==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-x64-musl": { - "version": "4.38.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.38.0.tgz", - "integrity": "sha512-q5Zv+goWvQUGCaL7fU8NuTw8aydIL/C9abAVGCzRReuj5h30TPx4LumBtAidrVOtXnlB+RZkBtExMsfqkMfb8g==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-win32-arm64-msvc": { - "version": "4.38.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.38.0.tgz", - "integrity": "sha512-u/Jbm1BU89Vftqyqbmxdq14nBaQjQX1HhmsdBWqSdGClNaKwhjsg5TpW+5Ibs1mb8Es9wJiMdl86BcmtUVXNZg==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ] - }, - "node_modules/@rollup/rollup-win32-ia32-msvc": { - "version": "4.38.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.38.0.tgz", - "integrity": "sha512-mqu4PzTrlpNHHbu5qleGvXJoGgHpChBlrBx/mEhTPpnAL1ZAYFlvHD7rLK839LLKQzqEQMFJfGrrOHItN4ZQqA==", - "cpu": [ - "ia32" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ] - }, - "node_modules/@rollup/rollup-win32-x64-msvc": { - "version": "4.38.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.38.0.tgz", - "integrity": "sha512-jjqy3uWlecfB98Psxb5cD6Fny9Fupv9LrDSPTQZUROqjvZmcCqNu4UMl7qqhlUUGpwiAkotj6GYu4SZdcr/nLw==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ] - }, - "node_modules/@sveltejs/adapter-static": { - "version": "3.0.8", - "resolved": "https://registry.npmjs.org/@sveltejs/adapter-static/-/adapter-static-3.0.8.tgz", - "integrity": "sha512-YaDrquRpZwfcXbnlDsSrBQNCChVOT9MGuSg+dMAyfsAa1SmiAhrA5jUYUiIMC59G92kIbY/AaQOWcBdq+lh+zg==", - "dev": true, - "license": "MIT", - "peerDependencies": { - "@sveltejs/kit": "^2.0.0" - } - }, - "node_modules/@sveltejs/kit": { - "version": "2.20.2", - "resolved": "https://registry.npmjs.org/@sveltejs/kit/-/kit-2.20.2.tgz", - "integrity": "sha512-Dv8TOAZC9vyfcAB9TMsvUEJsRbklRTeNfcYBPaeH6KnABJ99i3CvCB2eNx8fiiliIqe+9GIchBg4RodRH5p1BQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/cookie": "^0.6.0", - "cookie": "^0.6.0", - "devalue": "^5.1.0", - "esm-env": "^1.2.2", - "import-meta-resolve": "^4.1.0", - "kleur": "^4.1.5", - "magic-string": "^0.30.5", - "mrmime": "^2.0.0", - "sade": "^1.8.1", - "set-cookie-parser": "^2.6.0", - "sirv": "^3.0.0" - }, - "bin": { - "svelte-kit": "svelte-kit.js" - }, - "engines": { - "node": ">=18.13" - }, - "peerDependencies": { - "@sveltejs/vite-plugin-svelte": "^3.0.0 || ^4.0.0-next.1 || ^5.0.0", - "svelte": "^4.0.0 || ^5.0.0-next.0", - "vite": "^5.0.3 || ^6.0.0" - } - }, - "node_modules/@sveltejs/vite-plugin-svelte": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/@sveltejs/vite-plugin-svelte/-/vite-plugin-svelte-3.1.2.tgz", - "integrity": "sha512-Txsm1tJvtiYeLUVRNqxZGKR/mI+CzuIQuc2gn+YCs9rMTowpNZ2Nqt53JdL8KF9bLhAf2ruR/dr9eZCwdTriRA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@sveltejs/vite-plugin-svelte-inspector": "^2.1.0", - "debug": "^4.3.4", - "deepmerge": "^4.3.1", - "kleur": "^4.1.5", - "magic-string": "^0.30.10", - "svelte-hmr": "^0.16.0", - "vitefu": "^0.2.5" - }, - "engines": { - "node": "^18.0.0 || >=20" - }, - "peerDependencies": { - "svelte": "^4.0.0 || ^5.0.0-next.0", - "vite": "^5.0.0" - } - }, - "node_modules/@sveltejs/vite-plugin-svelte-inspector": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/@sveltejs/vite-plugin-svelte-inspector/-/vite-plugin-svelte-inspector-2.1.0.tgz", - "integrity": "sha512-9QX28IymvBlSCqsCll5t0kQVxipsfhFFL+L2t3nTWfXnddYwxBuAEtTtlaVQpRz9c37BhJjltSeY4AJSC03SSg==", - "dev": true, - "license": "MIT", - "dependencies": { - "debug": "^4.3.4" - }, - "engines": { - "node": "^18.0.0 || >=20" - }, - "peerDependencies": { - "@sveltejs/vite-plugin-svelte": "^3.0.0", - "svelte": "^4.0.0 || ^5.0.0-next.0", - "vite": "^5.0.0" - } - }, - "node_modules/@sveltestrap/sveltestrap": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/@sveltestrap/sveltestrap/-/sveltestrap-7.1.0.tgz", - "integrity": "sha512-TpIx25kqLV+z+VD3yfqYayOI1IaCeWFbT0uqM6NfA4vQgDs9PjFwmjkU4YEAlV/ngs9e7xPmaRWE7lkrg4Miow==", - "license": "MIT", - "dependencies": { - "@popperjs/core": "^2.11.8" - }, - "peerDependencies": { - "svelte": "^4.0.0 || ^5.0.0 || ^5.0.0-next.0" - } - }, - "node_modules/@types/cookie": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/@types/cookie/-/cookie-0.6.0.tgz", - "integrity": "sha512-4Kh9a6B2bQciAhf7FSuMRRkUWecJgJu9nPnx3yzpsfXX/c50REIqpHY4C82bXP90qrLtXtkDxTZosYO3UpOwlA==", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/estree": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.7.tgz", - "integrity": "sha512-w28IoSUCJpidD/TGviZwwMJckNESJZXFu7NBZ5YJ4mEUnNraUn9Pm8HSZm/jDF1pDWYKspWE7oVphigUPRakIQ==", - "license": "MIT" - }, - "node_modules/@types/json-schema": { - "version": "7.0.15", - "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", - "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", - "dev": true, - "license": "MIT" - }, - "node_modules/acorn": { - "version": "8.14.1", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.1.tgz", - "integrity": "sha512-OvQ/2pUDKmgfCg++xsTX1wGxfTaszcHVcTctW4UJB4hibJx2HXxxO5UmVgyjMa+ZDsiaf5wWLXYpRWMmBI0QHg==", - "license": "MIT", - "bin": { - "acorn": "bin/acorn" - }, - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/acorn-jsx": { - "version": "5.3.2", - "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", - "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", - "dev": true, - "license": "MIT", - "peerDependencies": { - "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" - } - }, - "node_modules/ajv": { - "version": "6.12.6", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", - "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", - "dev": true, - "license": "MIT", - "dependencies": { - "fast-deep-equal": "^3.1.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", - "uri-js": "^4.2.2" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" - } - }, - "node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "license": "MIT", - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/argparse": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", - "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", - "dev": true, - "license": "Python-2.0" - }, - "node_modules/aria-query": { - "version": "5.3.2", - "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.2.tgz", - "integrity": "sha512-COROpnaoap1E2F000S62r6A60uHZnmlvomhfyT2DlTcrY1OrBKn2UhH7qn5wTC9zMvD0AY7csdPSNwKP+7WiQw==", - "license": "Apache-2.0", - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/axobject-query": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-4.1.0.tgz", - "integrity": "sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ==", - "license": "Apache-2.0", - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/balanced-match": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", - "dev": true, - "license": "MIT" - }, - "node_modules/bootstrap": { - "version": "5.3.3", - "resolved": "https://registry.npmjs.org/bootstrap/-/bootstrap-5.3.3.tgz", - "integrity": "sha512-8HLCdWgyoMguSO9o+aH+iuZ+aht+mzW0u3HIMzVu7Srrpv7EBBxTnrFlSCskwdY1+EOFQSm7uMJhNQHkdPcmjg==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/twbs" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/bootstrap" - } - ], - "license": "MIT", - "peerDependencies": { - "@popperjs/core": "^2.11.8" - } - }, - "node_modules/bootstrap-icons": { - "version": "1.11.3", - "resolved": "https://registry.npmjs.org/bootstrap-icons/-/bootstrap-icons-1.11.3.tgz", - "integrity": "sha512-+3lpHrCw/it2/7lBL15VR0HEumaBss0+f/Lb6ZvHISn1mlK83jjFpooTLsMWbIjJMDjDjOExMsTxnXSIT4k4ww==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/twbs" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/bootstrap" - } - ], - "license": "MIT" - }, - "node_modules/brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "dev": true, - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "node_modules/callsites": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", - "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/code-red": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/code-red/-/code-red-1.0.4.tgz", - "integrity": "sha512-7qJWqItLA8/VPVlKJlFXU+NBlo/qyfs39aJcuMT/2ere32ZqvF5OSxgdM5xOfJJ7O429gg2HM47y8v9P+9wrNw==", - "license": "MIT", - "dependencies": { - "@jridgewell/sourcemap-codec": "^1.4.15", - "@types/estree": "^1.0.1", - "acorn": "^8.10.0", - "estree-walker": "^3.0.3", - "periscopic": "^3.1.0" - } - }, - "node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true, - "license": "MIT" - }, - "node_modules/concat-map": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", - "dev": true, - "license": "MIT" - }, - "node_modules/cookie": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.6.0.tgz", - "integrity": "sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/cross-spawn": { - "version": "7.0.6", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", - "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", - "dev": true, - "license": "MIT", - "dependencies": { - "path-key": "^3.1.0", - "shebang-command": "^2.0.0", - "which": "^2.0.1" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/css-tree": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-2.3.1.tgz", - "integrity": "sha512-6Fv1DV/TYw//QF5IzQdqsNDjx/wc8TrMBZsqjL9eW01tWb7R7k/mq+/VXfJCl7SoD5emsJop9cOByJZfs8hYIw==", - "license": "MIT", - "dependencies": { - "mdn-data": "2.0.30", - "source-map-js": "^1.0.1" - }, - "engines": { - "node": "^10 || ^12.20.0 || ^14.13.0 || >=15.0.0" - } - }, - "node_modules/cssesc": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", - "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==", - "dev": true, - "license": "MIT", - "bin": { - "cssesc": "bin/cssesc" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/debug": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz", - "integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==", - "dev": true, - "license": "MIT", - "dependencies": { - "ms": "^2.1.3" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/deep-is": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", - "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/deepmerge": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz", - "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/devalue": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/devalue/-/devalue-5.1.1.tgz", - "integrity": "sha512-maua5KUiapvEwiEAe+XnlZ3Rh0GD+qI1J/nb9vrJc3muPXvcF/8gXYTWF76+5DAqHyDUtOIImEuo0YKE9mshVw==", - "dev": true, - "license": "MIT" - }, - "node_modules/esbuild": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.21.5.tgz", - "integrity": "sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==", - "dev": true, - "hasInstallScript": true, - "license": "MIT", - "bin": { - "esbuild": "bin/esbuild" - }, - "engines": { - "node": ">=12" - }, - "optionalDependencies": { - "@esbuild/aix-ppc64": "0.21.5", - "@esbuild/android-arm": "0.21.5", - "@esbuild/android-arm64": "0.21.5", - "@esbuild/android-x64": "0.21.5", - "@esbuild/darwin-arm64": "0.21.5", - "@esbuild/darwin-x64": "0.21.5", - "@esbuild/freebsd-arm64": "0.21.5", - "@esbuild/freebsd-x64": "0.21.5", - "@esbuild/linux-arm": "0.21.5", - "@esbuild/linux-arm64": "0.21.5", - "@esbuild/linux-ia32": "0.21.5", - "@esbuild/linux-loong64": "0.21.5", - "@esbuild/linux-mips64el": "0.21.5", - "@esbuild/linux-ppc64": "0.21.5", - "@esbuild/linux-riscv64": "0.21.5", - "@esbuild/linux-s390x": "0.21.5", - "@esbuild/linux-x64": "0.21.5", - "@esbuild/netbsd-x64": "0.21.5", - "@esbuild/openbsd-x64": "0.21.5", - "@esbuild/sunos-x64": "0.21.5", - "@esbuild/win32-arm64": "0.21.5", - "@esbuild/win32-ia32": "0.21.5", - "@esbuild/win32-x64": "0.21.5" - } - }, - "node_modules/escape-string-regexp": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", - "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/eslint": { - "version": "9.23.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.23.0.tgz", - "integrity": "sha512-jV7AbNoFPAY1EkFYpLq5bslU9NLNO8xnEeQXwErNibVryjk67wHVmddTBilc5srIttJDBrB0eMHKZBFbSIABCw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@eslint-community/eslint-utils": "^4.2.0", - "@eslint-community/regexpp": "^4.12.1", - "@eslint/config-array": "^0.19.2", - "@eslint/config-helpers": "^0.2.0", - "@eslint/core": "^0.12.0", - "@eslint/eslintrc": "^3.3.1", - "@eslint/js": "9.23.0", - "@eslint/plugin-kit": "^0.2.7", - "@humanfs/node": "^0.16.6", - "@humanwhocodes/module-importer": "^1.0.1", - "@humanwhocodes/retry": "^0.4.2", - "@types/estree": "^1.0.6", - "@types/json-schema": "^7.0.15", - "ajv": "^6.12.4", - "chalk": "^4.0.0", - "cross-spawn": "^7.0.6", - "debug": "^4.3.2", - "escape-string-regexp": "^4.0.0", - "eslint-scope": "^8.3.0", - "eslint-visitor-keys": "^4.2.0", - "espree": "^10.3.0", - "esquery": "^1.5.0", - "esutils": "^2.0.2", - "fast-deep-equal": "^3.1.3", - "file-entry-cache": "^8.0.0", - "find-up": "^5.0.0", - "glob-parent": "^6.0.2", - "ignore": "^5.2.0", - "imurmurhash": "^0.1.4", - "is-glob": "^4.0.0", - "json-stable-stringify-without-jsonify": "^1.0.1", - "lodash.merge": "^4.6.2", - "minimatch": "^3.1.2", - "natural-compare": "^1.4.0", - "optionator": "^0.9.3" - }, - "bin": { - "eslint": "bin/eslint.js" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "url": "https://eslint.org/donate" - }, - "peerDependencies": { - "jiti": "*" - }, - "peerDependenciesMeta": { - "jiti": { - "optional": true - } - } - }, - "node_modules/eslint-compat-utils": { - "version": "0.6.4", - "resolved": "https://registry.npmjs.org/eslint-compat-utils/-/eslint-compat-utils-0.6.4.tgz", - "integrity": "sha512-/u+GQt8NMfXO8w17QendT4gvO5acfxQsAKirAt0LVxDnr2N8YLCVbregaNc/Yhp7NM128DwCaRvr8PLDfeNkQw==", - "dev": true, - "license": "MIT", - "dependencies": { - "semver": "^7.5.4" - }, - "engines": { - "node": ">=12" - }, - "peerDependencies": { - "eslint": ">=6.0.0" - } - }, - "node_modules/eslint-config-prettier": { - "version": "10.1.1", - "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-10.1.1.tgz", - "integrity": "sha512-4EQQr6wXwS+ZJSzaR5ZCrYgLxqvUjdXctaEtBqHcbkW944B1NQyO4qpdHQbXBONfwxXdkAY81HH4+LUfrg+zPw==", - "dev": true, - "license": "MIT", - "bin": { - "eslint-config-prettier": "bin/cli.js" - }, - "peerDependencies": { - "eslint": ">=7.0.0" - } - }, - "node_modules/eslint-plugin-svelte": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-svelte/-/eslint-plugin-svelte-3.4.0.tgz", - "integrity": "sha512-L0eX0W6M0YhIUhWRlOAaornY1lIz6xRSVKVJuiRovMM5wHUBQZmefwJRR0y+sqR0CHtJpFmxYiQbw3UaO8h5KA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@eslint-community/eslint-utils": "^4.4.1", - "@jridgewell/sourcemap-codec": "^1.5.0", - "eslint-compat-utils": "^0.6.4", - "esutils": "^2.0.3", - "known-css-properties": "^0.35.0", - "postcss": "^8.4.49", - "postcss-load-config": "^3.1.4", - "postcss-safe-parser": "^7.0.0", - "semver": "^7.6.3", - "svelte-eslint-parser": "^1.1.1" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "url": "https://github.com/sponsors/ota-meshi" - }, - "peerDependencies": { - "eslint": "^8.57.1 || ^9.0.0", - "svelte": "^3.37.0 || ^4.0.0 || ^5.0.0" - }, - "peerDependenciesMeta": { - "svelte": { - "optional": true - } - } - }, - "node_modules/eslint-scope": { - "version": "8.3.0", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.3.0.tgz", - "integrity": "sha512-pUNxi75F8MJ/GdeKtVLSbYg4ZI34J6C0C7sbL4YOp2exGwen7ZsuBqKzUhXd0qMQ362yET3z+uPwKeg/0C2XCQ==", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "esrecurse": "^4.3.0", - "estraverse": "^5.2.0" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/eslint-visitor-keys": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.0.tgz", - "integrity": "sha512-UyLnSehNt62FFhSwjZlHmeokpRK59rcz29j+F1/aDgbkbRTk7wIc9XzdoasMUbRNKDM0qQt/+BJ4BrpFeABemw==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/esm-env": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/esm-env/-/esm-env-1.2.2.tgz", - "integrity": "sha512-Epxrv+Nr/CaL4ZcFGPJIYLWFom+YeV1DqMLHJoEd9SYRxNbaFruBwfEX/kkHUJf55j2+TUbmDcmuilbP1TmXHA==", - "dev": true, - "license": "MIT" - }, - "node_modules/espree": { - "version": "10.3.0", - "resolved": "https://registry.npmjs.org/espree/-/espree-10.3.0.tgz", - "integrity": "sha512-0QYC8b24HWY8zjRnDTL6RiHfDbAWn63qb4LMj1Z4b076A4une81+z03Kg7l7mn/48PUTqoLptSXez8oknU8Clg==", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "acorn": "^8.14.0", - "acorn-jsx": "^5.3.2", - "eslint-visitor-keys": "^4.2.0" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/esquery": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.6.0.tgz", - "integrity": "sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "estraverse": "^5.1.0" - }, - "engines": { - "node": ">=0.10" - } - }, - "node_modules/esrecurse": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", - "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "estraverse": "^5.2.0" - }, - "engines": { - "node": ">=4.0" - } - }, - "node_modules/estraverse": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", - "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", - "dev": true, - "license": "BSD-2-Clause", - "engines": { - "node": ">=4.0" - } - }, - "node_modules/estree-walker": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", - "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==", - "license": "MIT", - "dependencies": { - "@types/estree": "^1.0.0" - } - }, - "node_modules/esutils": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", - "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", - "dev": true, - "license": "BSD-2-Clause", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/fast-deep-equal": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", - "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", - "dev": true, - "license": "MIT" - }, - "node_modules/fast-json-stable-stringify": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", - "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", - "dev": true, - "license": "MIT" - }, - "node_modules/fast-levenshtein": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", - "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", - "dev": true, - "license": "MIT" - }, - "node_modules/file-entry-cache": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz", - "integrity": "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "flat-cache": "^4.0.0" - }, - "engines": { - "node": ">=16.0.0" - } - }, - "node_modules/find-up": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", - "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", - "dev": true, - "license": "MIT", - "dependencies": { - "locate-path": "^6.0.0", - "path-exists": "^4.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/flat-cache": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz", - "integrity": "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==", - "dev": true, - "license": "MIT", - "dependencies": { - "flatted": "^3.2.9", - "keyv": "^4.5.4" - }, - "engines": { - "node": ">=16" - } - }, - "node_modules/flatted": { - "version": "3.3.3", - "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.3.tgz", - "integrity": "sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==", - "dev": true, - "license": "ISC" - }, - "node_modules/fsevents": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", - "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", - "dev": true, - "hasInstallScript": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": "^8.16.0 || ^10.6.0 || >=11.0.0" - } - }, - "node_modules/glob-parent": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", - "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", - "dev": true, - "license": "ISC", - "dependencies": { - "is-glob": "^4.0.3" - }, - "engines": { - "node": ">=10.13.0" - } - }, - "node_modules/globals": { - "version": "14.0.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz", - "integrity": "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/ignore": { - "version": "5.3.2", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", - "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 4" - } - }, - "node_modules/import-fresh": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz", - "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "parent-module": "^1.0.0", - "resolve-from": "^4.0.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/import-meta-resolve": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/import-meta-resolve/-/import-meta-resolve-4.1.0.tgz", - "integrity": "sha512-I6fiaX09Xivtk+THaMfAwnA3MVA5Big1WHF1Dfx9hFuvNIWpXnorlkzhcQf6ehrqQiiZECRt1poOAkPmer3ruw==", - "dev": true, - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/imurmurhash": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", - "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.8.19" - } - }, - "node_modules/is-extglob": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", - "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-glob": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", - "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", - "dev": true, - "license": "MIT", - "dependencies": { - "is-extglob": "^2.1.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-reference": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/is-reference/-/is-reference-3.0.3.tgz", - "integrity": "sha512-ixkJoqQvAP88E6wLydLGGqCJsrFUnqoH6HnaczB8XmDH1oaWU+xxdptvikTgaEhtZ53Ky6YXiBuUI2WXLMCwjw==", - "license": "MIT", - "dependencies": { - "@types/estree": "^1.0.6" - } - }, - "node_modules/isexe": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", - "dev": true, - "license": "ISC" - }, - "node_modules/js-yaml": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", - "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", - "dev": true, - "license": "MIT", - "dependencies": { - "argparse": "^2.0.1" - }, - "bin": { - "js-yaml": "bin/js-yaml.js" - } - }, - "node_modules/json-buffer": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", - "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/json-schema-traverse": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", - "dev": true, - "license": "MIT" - }, - "node_modules/json-stable-stringify-without-jsonify": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", - "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", - "dev": true, - "license": "MIT" - }, - "node_modules/keyv": { - "version": "4.5.4", - "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", - "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", - "dev": true, - "license": "MIT", - "dependencies": { - "json-buffer": "3.0.1" - } - }, - "node_modules/kleur": { - "version": "4.1.5", - "resolved": "https://registry.npmjs.org/kleur/-/kleur-4.1.5.tgz", - "integrity": "sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/known-css-properties": { - "version": "0.35.0", - "resolved": "https://registry.npmjs.org/known-css-properties/-/known-css-properties-0.35.0.tgz", - "integrity": "sha512-a/RAk2BfKk+WFGhhOCAYqSiFLc34k8Mt/6NWRI4joER0EYUzXIcFivjjnoD3+XU1DggLn/tZc3DOAgke7l8a4A==", - "dev": true, - "license": "MIT" - }, - "node_modules/levn": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", - "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "prelude-ls": "^1.2.1", - "type-check": "~0.4.0" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/lilconfig": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-2.1.0.tgz", - "integrity": "sha512-utWOt/GHzuUxnLKxB6dk81RoOeoNeHgbrXiuGk4yyF5qlRz+iIVWu56E2fqGHFrXz0QNUhLB/8nKqvRH66JKGQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" - } - }, - "node_modules/locate-character": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/locate-character/-/locate-character-3.0.0.tgz", - "integrity": "sha512-SW13ws7BjaeJ6p7Q6CO2nchbYEc3X3J6WrmTTDto7yMPqVSZTUyY5Tjbid+Ab8gLnATtygYtiDIJGQRRn2ZOiA==", - "license": "MIT" - }, - "node_modules/locate-path": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", - "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", - "dev": true, - "license": "MIT", - "dependencies": { - "p-locate": "^5.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/lodash.merge": { - "version": "4.6.2", - "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", - "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/magic-string": { - "version": "0.30.17", - "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.17.tgz", - "integrity": "sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA==", - "license": "MIT", - "dependencies": { - "@jridgewell/sourcemap-codec": "^1.5.0" - } - }, - "node_modules/mdn-data": { - "version": "2.0.30", - "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.30.tgz", - "integrity": "sha512-GaqWWShW4kv/G9IEucWScBx9G1/vsFZZJUO+tD26M8J8z3Kw5RDQjaoZe03YAClgeS/SWPOcb4nkFBTEi5DUEA==", - "license": "CC0-1.0" - }, - "node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "dev": true, - "license": "ISC", - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } - }, - "node_modules/mri": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/mri/-/mri-1.2.0.tgz", - "integrity": "sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=4" - } - }, - "node_modules/mrmime": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/mrmime/-/mrmime-2.0.1.tgz", - "integrity": "sha512-Y3wQdFg2Va6etvQ5I82yUhGdsKrcYox6p7FfL1LbK2J4V01F9TGlepTIhnK24t7koZibmg82KGglhA1XK5IsLQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" - } - }, - "node_modules/ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "dev": true, - "license": "MIT" - }, - "node_modules/nanoid": { - "version": "3.3.11", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", - "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "bin": { - "nanoid": "bin/nanoid.cjs" - }, - "engines": { - "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" - } - }, - "node_modules/natural-compare": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", - "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", - "dev": true, - "license": "MIT" - }, - "node_modules/optionator": { - "version": "0.9.4", - "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", - "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==", - "dev": true, - "license": "MIT", - "dependencies": { - "deep-is": "^0.1.3", - "fast-levenshtein": "^2.0.6", - "levn": "^0.4.1", - "prelude-ls": "^1.2.1", - "type-check": "^0.4.0", - "word-wrap": "^1.2.5" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/p-limit": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", - "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "yocto-queue": "^0.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/p-locate": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", - "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", - "dev": true, - "license": "MIT", - "dependencies": { - "p-limit": "^3.0.2" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/parent-module": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", - "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", - "dev": true, - "license": "MIT", - "dependencies": { - "callsites": "^3.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/path-exists": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", - "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/path-key": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", - "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/periscopic": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/periscopic/-/periscopic-3.1.0.tgz", - "integrity": "sha512-vKiQ8RRtkl9P+r/+oefh25C3fhybptkHKCZSPlcXiJux2tJF55GnEj3BVn4A5gKfq9NWWXXrxkHBwVPUfH0opw==", - "license": "MIT", - "dependencies": { - "@types/estree": "^1.0.0", - "estree-walker": "^3.0.0", - "is-reference": "^3.0.0" - } - }, - "node_modules/picocolors": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", - "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", - "dev": true, - "license": "ISC" - }, - "node_modules/postcss": { - "version": "8.5.3", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.3.tgz", - "integrity": "sha512-dle9A3yYxlBSrt8Fu+IpjGT8SY8hN0mlaA6GY8t0P5PjIOZemULz/E2Bnm/2dcUOena75OTNkHI76uZBNUUq3A==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/postcss/" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/postcss" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "dependencies": { - "nanoid": "^3.3.8", - "picocolors": "^1.1.1", - "source-map-js": "^1.2.1" - }, - "engines": { - "node": "^10 || ^12 || >=14" - } - }, - "node_modules/postcss-load-config": { - "version": "3.1.4", - "resolved": "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-3.1.4.tgz", - "integrity": "sha512-6DiM4E7v4coTE4uzA8U//WhtPwyhiim3eyjEMFCnUpzbrkK9wJHgKDT2mR+HbtSrd/NubVaYTOpSpjUl8NQeRg==", - "dev": true, - "license": "MIT", - "dependencies": { - "lilconfig": "^2.0.5", - "yaml": "^1.10.2" - }, - "engines": { - "node": ">= 10" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/postcss/" - }, - "peerDependencies": { - "postcss": ">=8.0.9", - "ts-node": ">=9.0.0" - }, - "peerDependenciesMeta": { - "postcss": { - "optional": true - }, - "ts-node": { - "optional": true - } - } - }, - "node_modules/postcss-safe-parser": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/postcss-safe-parser/-/postcss-safe-parser-7.0.1.tgz", - "integrity": "sha512-0AioNCJZ2DPYz5ABT6bddIqlhgwhpHZ/l65YAYo0BCIn0xiDpsnTHz0gnoTGk0OXZW0JRs+cDwL8u/teRdz+8A==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/postcss/" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/postcss-safe-parser" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "engines": { - "node": ">=18.0" - }, - "peerDependencies": { - "postcss": "^8.4.31" - } - }, - "node_modules/postcss-scss": { - "version": "4.0.9", - "resolved": "https://registry.npmjs.org/postcss-scss/-/postcss-scss-4.0.9.tgz", - "integrity": "sha512-AjKOeiwAitL/MXxQW2DliT28EKukvvbEWx3LBmJIRN8KfBGZbRTxNYW0kSqi1COiTZ57nZ9NW06S6ux//N1c9A==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/postcss/" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/postcss-scss" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "engines": { - "node": ">=12.0" - }, - "peerDependencies": { - "postcss": "^8.4.29" - } - }, - "node_modules/postcss-selector-parser": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.0.tgz", - "integrity": "sha512-8sLjZwK0R+JlxlYcTuVnyT2v+htpdrjDOKuMcOVdYjt52Lh8hWRYpxBPoKx/Zg+bcjc3wx6fmQevMmUztS/ccA==", - "dev": true, - "license": "MIT", - "dependencies": { - "cssesc": "^3.0.0", - "util-deprecate": "^1.0.2" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/prelude-ls": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", - "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/prettier": { - "version": "3.5.3", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.5.3.tgz", - "integrity": "sha512-QQtaxnoDJeAkDvDKWCLiwIXkTgRhwYDEQCghU9Z6q03iyek/rxRh/2lC3HB7P8sWT2xC/y5JDctPLBIGzHKbhw==", - "dev": true, - "license": "MIT", - "bin": { - "prettier": "bin/prettier.cjs" - }, - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/prettier/prettier?sponsor=1" - } - }, - "node_modules/prettier-plugin-svelte": { - "version": "3.3.3", - "resolved": "https://registry.npmjs.org/prettier-plugin-svelte/-/prettier-plugin-svelte-3.3.3.tgz", - "integrity": "sha512-yViK9zqQ+H2qZD1w/bH7W8i+bVfKrD8GIFjkFe4Thl6kCT9SlAsXVNmt3jCvQOCsnOhcvYgsoVlRV/Eu6x5nNw==", - "dev": true, - "license": "MIT", - "peerDependencies": { - "prettier": "^3.0.0", - "svelte": "^3.2.0 || ^4.0.0-next.0 || ^5.0.0-next.0" - } - }, - "node_modules/punycode": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", - "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/resolve-from": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", - "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=4" - } - }, - "node_modules/rollup": { - "version": "4.38.0", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.38.0.tgz", - "integrity": "sha512-5SsIRtJy9bf1ErAOiFMFzl64Ex9X5V7bnJ+WlFMb+zmP459OSWCEG7b0ERZ+PEU7xPt4OG3RHbrp1LJlXxYTrw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/estree": "1.0.7" - }, - "bin": { - "rollup": "dist/bin/rollup" - }, - "engines": { - "node": ">=18.0.0", - "npm": ">=8.0.0" - }, - "optionalDependencies": { - "@rollup/rollup-android-arm-eabi": "4.38.0", - "@rollup/rollup-android-arm64": "4.38.0", - "@rollup/rollup-darwin-arm64": "4.38.0", - "@rollup/rollup-darwin-x64": "4.38.0", - "@rollup/rollup-freebsd-arm64": "4.38.0", - "@rollup/rollup-freebsd-x64": "4.38.0", - "@rollup/rollup-linux-arm-gnueabihf": "4.38.0", - "@rollup/rollup-linux-arm-musleabihf": "4.38.0", - "@rollup/rollup-linux-arm64-gnu": "4.38.0", - "@rollup/rollup-linux-arm64-musl": "4.38.0", - "@rollup/rollup-linux-loongarch64-gnu": "4.38.0", - "@rollup/rollup-linux-powerpc64le-gnu": "4.38.0", - "@rollup/rollup-linux-riscv64-gnu": "4.38.0", - "@rollup/rollup-linux-riscv64-musl": "4.38.0", - "@rollup/rollup-linux-s390x-gnu": "4.38.0", - "@rollup/rollup-linux-x64-gnu": "4.38.0", - "@rollup/rollup-linux-x64-musl": "4.38.0", - "@rollup/rollup-win32-arm64-msvc": "4.38.0", - "@rollup/rollup-win32-ia32-msvc": "4.38.0", - "@rollup/rollup-win32-x64-msvc": "4.38.0", - "fsevents": "~2.3.2" - } - }, - "node_modules/sade": { - "version": "1.8.1", - "resolved": "https://registry.npmjs.org/sade/-/sade-1.8.1.tgz", - "integrity": "sha512-xal3CZX1Xlo/k4ApwCFrHVACi9fBqJ7V+mwhBsuf/1IOKbBy098Fex+Wa/5QMubw09pSZ/u8EY8PWgevJsXp1A==", - "dev": true, - "license": "MIT", - "dependencies": { - "mri": "^1.1.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/semver": { - "version": "7.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.1.tgz", - "integrity": "sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA==", - "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/set-cookie-parser": { - "version": "2.7.1", - "resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-2.7.1.tgz", - "integrity": "sha512-IOc8uWeOZgnb3ptbCURJWNjWUPcO3ZnTTdzsurqERrP6nPyv+paC55vJM0LpOlT2ne+Ix+9+CRG1MNLlyZ4GjQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/shebang-command": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", - "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", - "dev": true, - "license": "MIT", - "dependencies": { - "shebang-regex": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/shebang-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", - "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/sirv": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/sirv/-/sirv-3.0.1.tgz", - "integrity": "sha512-FoqMu0NCGBLCcAkS1qA+XJIQTR6/JHfQXl+uGteNCQ76T91DMUjPa9xfmeqMY3z80nLSg9yQmNjK0Px6RWsH/A==", - "dev": true, - "license": "MIT", - "dependencies": { - "@polka/url": "^1.0.0-next.24", - "mrmime": "^2.0.0", - "totalist": "^3.0.0" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/source-map-js": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", - "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", - "license": "BSD-3-Clause", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/strip-json-comments": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", - "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "license": "MIT", - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/svelte": { - "version": "4.2.19", - "resolved": "https://registry.npmjs.org/svelte/-/svelte-4.2.19.tgz", - "integrity": "sha512-IY1rnGr6izd10B0A8LqsBfmlT5OILVuZ7XsI0vdGPEvuonFV7NYEUK4dAkm9Zg2q0Um92kYjTpS1CAP3Nh/KWw==", - "license": "MIT", - "dependencies": { - "@ampproject/remapping": "^2.2.1", - "@jridgewell/sourcemap-codec": "^1.4.15", - "@jridgewell/trace-mapping": "^0.3.18", - "@types/estree": "^1.0.1", - "acorn": "^8.9.0", - "aria-query": "^5.3.0", - "axobject-query": "^4.0.0", - "code-red": "^1.0.3", - "css-tree": "^2.3.1", - "estree-walker": "^3.0.3", - "is-reference": "^3.0.1", - "locate-character": "^3.0.0", - "magic-string": "^0.30.4", - "periscopic": "^3.1.0" - }, - "engines": { - "node": ">=16" - } - }, - "node_modules/svelte-eslint-parser": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/svelte-eslint-parser/-/svelte-eslint-parser-1.1.1.tgz", - "integrity": "sha512-QLVGPIMDettl30qRHXU2VrPvVJKG8GsGstye7n8rFbEiu3gEARksuQg9Xu4GzubNxhGNM8stfBZkhyMbBQmjFA==", - "dev": true, - "license": "MIT", - "dependencies": { - "eslint-scope": "^8.2.0", - "eslint-visitor-keys": "^4.0.0", - "espree": "^10.0.0", - "postcss": "^8.4.49", - "postcss-scss": "^4.0.9", - "postcss-selector-parser": "^7.0.0" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "url": "https://github.com/sponsors/ota-meshi" - }, - "peerDependencies": { - "svelte": "^3.37.0 || ^4.0.0 || ^5.0.0" - }, - "peerDependenciesMeta": { - "svelte": { - "optional": true - } - } - }, - "node_modules/svelte-hmr": { - "version": "0.16.0", - "resolved": "https://registry.npmjs.org/svelte-hmr/-/svelte-hmr-0.16.0.tgz", - "integrity": "sha512-Gyc7cOS3VJzLlfj7wKS0ZnzDVdv3Pn2IuVeJPk9m2skfhcu5bq3wtIZyQGggr7/Iim5rH5cncyQft/kRLupcnA==", - "dev": true, - "license": "ISC", - "engines": { - "node": "^12.20 || ^14.13.1 || >= 16" - }, - "peerDependencies": { - "svelte": "^3.19.0 || ^4.0.0" - } - }, - "node_modules/totalist": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/totalist/-/totalist-3.0.1.tgz", - "integrity": "sha512-sf4i37nQ2LBx4m3wB74y+ubopq6W/dIzXg0FDGjsYnZHVa1Da8FH853wlL2gtUhg+xJXjfk3kUZS3BRoQeoQBQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/type-check": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", - "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", - "dev": true, - "license": "MIT", - "dependencies": { - "prelude-ls": "^1.2.1" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/uri-js": { - "version": "4.4.1", - "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", - "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "punycode": "^2.1.0" - } - }, - "node_modules/util-deprecate": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", - "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", - "dev": true, - "license": "MIT" - }, - "node_modules/vite": { - "version": "5.4.15", - "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.15.tgz", - "integrity": "sha512-6ANcZRivqL/4WtwPGTKNaosuNJr5tWiftOC7liM7G9+rMb8+oeJeyzymDu4rTN93seySBmbjSfsS3Vzr19KNtA==", - "dev": true, - "license": "MIT", - "dependencies": { - "esbuild": "^0.21.3", - "postcss": "^8.4.43", - "rollup": "^4.20.0" - }, - "bin": { - "vite": "bin/vite.js" - }, - "engines": { - "node": "^18.0.0 || >=20.0.0" - }, - "funding": { - "url": "https://github.com/vitejs/vite?sponsor=1" - }, - "optionalDependencies": { - "fsevents": "~2.3.3" - }, - "peerDependencies": { - "@types/node": "^18.0.0 || >=20.0.0", - "less": "*", - "lightningcss": "^1.21.0", - "sass": "*", - "sass-embedded": "*", - "stylus": "*", - "sugarss": "*", - "terser": "^5.4.0" - }, - "peerDependenciesMeta": { - "@types/node": { - "optional": true - }, - "less": { - "optional": true - }, - "lightningcss": { - "optional": true - }, - "sass": { - "optional": true - }, - "sass-embedded": { - "optional": true - }, - "stylus": { - "optional": true - }, - "sugarss": { - "optional": true - }, - "terser": { - "optional": true - } - } - }, - "node_modules/vitefu": { - "version": "0.2.5", - "resolved": "https://registry.npmjs.org/vitefu/-/vitefu-0.2.5.tgz", - "integrity": "sha512-SgHtMLoqaeeGnd2evZ849ZbACbnwQCIwRH57t18FxcXoZop0uQu0uzlIhJBlF/eWVzuce0sHeqPcDo+evVcg8Q==", - "dev": true, - "license": "MIT", - "peerDependencies": { - "vite": "^3.0.0 || ^4.0.0 || ^5.0.0" - }, - "peerDependenciesMeta": { - "vite": { - "optional": true - } - } - }, - "node_modules/which": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", - "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "dev": true, - "license": "ISC", - "dependencies": { - "isexe": "^2.0.0" - }, - "bin": { - "node-which": "bin/node-which" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/word-wrap": { - "version": "1.2.5", - "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", - "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/yaml": { - "version": "1.10.2", - "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz", - "integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==", - "dev": true, - "license": "ISC", - "engines": { - "node": ">= 6" - } - }, - "node_modules/yocto-queue": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", - "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - } - } -} diff --git a/qa/ui/package.json b/qa/ui/package.json deleted file mode 100644 index 8023a3d3..00000000 --- a/qa/ui/package.json +++ /dev/null @@ -1,30 +0,0 @@ -{ - "name": "qa", - "version": "0.0.1", - "scripts": { - "dev": "vite dev", - "build": "vite build", - "package": "vite package", - "preview": "vite preview", - "lint": "prettier --ignore-path .gitignore --check --plugin-search-dir=. . && eslint --ignore-path .gitignore .", - "format": "prettier --ignore-path .gitignore --write --plugin-search-dir=. ." - }, - "devDependencies": { - "@sveltejs/adapter-static": "^3.0.0", - "@sveltejs/kit": "^2.0.0", - "@sveltejs/vite-plugin-svelte": "^3.0.0", - "eslint": "^9.0.0", - "eslint-config-prettier": "^10.0.0", - "eslint-plugin-svelte": "^3.0.0", - "prettier": "^3.0.0", - "prettier-plugin-svelte": "^3.1.2", - "svelte": "^4.0.0", - "vite": "^5.0.0" - }, - "type": "module", - "dependencies": { - "bootstrap": "^5.2.2", - "bootstrap-icons": "^1.9.1", - "@sveltestrap/sveltestrap": "^7.0.0" - } -} diff --git a/qa/ui/src/app.css b/qa/ui/src/app.css deleted file mode 100644 index 0daa760f..00000000 --- a/qa/ui/src/app.css +++ /dev/null @@ -1,11 +0,0 @@ -.level .level-item { - text-align: center; -} -.level .level-item .heading { - font-variant: small-caps; - text-transform: lowercase; -} -.level .level-item .value { - font-size: 1.2rem; - font-weight: bolder; -} diff --git a/qa/ui/src/app.html b/qa/ui/src/app.html deleted file mode 100644 index d5839236..00000000 --- a/qa/ui/src/app.html +++ /dev/null @@ -1,15 +0,0 @@ - - - - - - - - - - %sveltekit.head% - - - %sveltekit.body% - - diff --git a/qa/ui/src/global.d.ts b/qa/ui/src/global.d.ts deleted file mode 100644 index 63908c66..00000000 --- a/qa/ui/src/global.d.ts +++ /dev/null @@ -1 +0,0 @@ -/// diff --git a/qa/ui/src/lib/components/BadgeState.svelte b/qa/ui/src/lib/components/BadgeState.svelte deleted file mode 100644 index 316d6c56..00000000 --- a/qa/ui/src/lib/components/BadgeState.svelte +++ /dev/null @@ -1,41 +0,0 @@ - - - - {state} - diff --git a/qa/ui/src/lib/components/DateFormat.svelte b/qa/ui/src/lib/components/DateFormat.svelte deleted file mode 100644 index f2ac4525..00000000 --- a/qa/ui/src/lib/components/DateFormat.svelte +++ /dev/null @@ -1,17 +0,0 @@ - - -{formatDate(date, dateStyle, timeStyle)} diff --git a/qa/ui/src/lib/components/ExerciceHeader.svelte b/qa/ui/src/lib/components/ExerciceHeader.svelte deleted file mode 100644 index c24b1428..00000000 --- a/qa/ui/src/lib/components/ExerciceHeader.svelte +++ /dev/null @@ -1,55 +0,0 @@ - - -

    - {#if query_selected} - - {:else if theme} - - {:else} - - {/if} - {exercice.title} - {#if exercice.wip} - - {/if} - {#if $themes.length && $themesIdx[exercice.id_theme]} - - {$themesIdx[exercice.id_theme].name} - - Site du challenge - {#if exercice.forge_link} - GitLab - {/if} - {/if} -

    diff --git a/qa/ui/src/lib/components/ExerciceLayout.svelte b/qa/ui/src/lib/components/ExerciceLayout.svelte deleted file mode 100644 index 44701353..00000000 --- a/qa/ui/src/lib/components/ExerciceLayout.svelte +++ /dev/null @@ -1,38 +0,0 @@ - - - - - -
    - {#key countCreation} - - {/key} - - - - - - diff --git a/qa/ui/src/lib/components/Header.svelte b/qa/ui/src/lib/components/Header.svelte deleted file mode 100644 index 9cf2068b..00000000 --- a/qa/ui/src/lib/components/Header.svelte +++ /dev/null @@ -1,103 +0,0 @@ - - - - - FIC - QA - - - - diff --git a/qa/ui/src/lib/components/MyExercices.svelte b/qa/ui/src/lib/components/MyExercices.svelte deleted file mode 100644 index 67be439f..00000000 --- a/qa/ui/src/lib/components/MyExercices.svelte +++ /dev/null @@ -1,107 +0,0 @@ - - -
    - -

    - {#if team} - Étapes de l'équipe - {:else} - Vos étapes - {/if} -

    - {#await my_exercicesP} -
    - -
    - {:then} -
    - - - - - - - - {#each my_exercices as todo (todo.id)} - 0} - class:table-warning={todo.queriesNSolved > 0} - on:click={() => show(todo.id_exercice)} - > - - - - {/each} - -
    DéfiRequêtes
    - {#if $exercicesIdx.length == 0 && $themesIdx.length == 0} - - {:else if $themesIdx[$exercicesIdx[todo.id_exercice]]} - - {$themesIdx[$exercicesIdx[todo.id_exercice].id_theme].name} - - – - {/if} - {#if $exercicesIdx.length == 0} - - {:else if $exercicesIdx[todo.id_exercice]} - {$exercicesIdx[todo.id_exercice].title} - {/if} - - {#if todo.queries && todo.queries.length} - {todo.queriesNSolved} / {todo.queriesNClosed} - {:else} - 0 - {/if} -
    - {/await} - diff --git a/qa/ui/src/lib/components/MyTodo.svelte b/qa/ui/src/lib/components/MyTodo.svelte deleted file mode 100644 index 581a566a..00000000 --- a/qa/ui/src/lib/components/MyTodo.svelte +++ /dev/null @@ -1,107 +0,0 @@ - - -
    - -

    Étapes à tester et valider

    - {#await teamtodos.refresh()} -
    - -
    - {:then} - {#await exo_doneP} -
    - -
    - {:then exo_done} - - - - - - - - - - {#each $teamtodos as todo (todo.id)} - show(todo.id_exercice)} - > - - - - - {/each} - -
    AvancementScénarioDéfi
    - {#if tododone[todo.id_exercice]} - Commenté - {#if !exo_done[todo.id_exercice] || exo_done[todo.id_exercice] != 'solved'} - mais pas testé/terminé - {/if} - {:else if exo_done[todo.id_exercice] && exo_done[todo.id_exercice] != 'access'} - À commenter - {:else} - À tester - {/if} - - {#if $exercicesIdx.length == 0 && $themesIdx.length == 0} - - {:else if !$themesIdx[$exercicesIdx[todo.id_exercice].id_theme]} - Défis indépendants - {:else} - - {$themesIdx[$exercicesIdx[todo.id_exercice].id_theme].name} - - {/if} - - {#if $exercicesIdx.length == 0} - - {:else} - {$exercicesIdx[todo.id_exercice].title} - {/if} -
    - {/await} - {/await} -
    diff --git a/qa/ui/src/lib/components/QAItem.svelte b/qa/ui/src/lib/components/QAItem.svelte deleted file mode 100644 index 273381f7..00000000 --- a/qa/ui/src/lib/components/QAItem.svelte +++ /dev/null @@ -1,367 +0,0 @@ - - -{#if query} - - -
    -

    {query.subject}

    -
    - {#if $auth && !query.solved} - - {#if query.forge_link} - - {/if} - {#if $gitlab} - - {/if} - - {/if} - {#if $auth && !query.solved && $viewIdx[query.id_exercice]} - - {/if} - {#if $auth && $auth.id_team == query.id_team} - {#if query.solved && !query.closed && (query.subject != "RAS" || query.state != "ok")} - - - {/if} - {#if (!query.solved && !has_comments) || (query.subject == "RAS" && query.state == "ok" && !has_comments)} - - {/if} - {/if} -
    -
    -
    - - - -
    -
    - Qui ? -
    -
    - {query.user.split("@")[0]} -
    -
    - (team #{query.id_team}) -
    -
    - - - -
    -
    - État -
    - -
    - - {thumbs.length} -
    -
    - - - -
    -
    - Date de création -
    -
    - -
    -
    - - - -
    -
    - Date de résolution -
    -
    - {#if query.solved} - - {:else} - - - {/if} -
    -
    - - - -
    -
    - Date de clôture -
    -
    - {#if query.closed} - - {:else} - - - {/if} -
    -
    - -
    -
    - {#await query_commentsP then query_comments} - {#each query_comments as comment (comment.id)} - {#if comment.content != "+1"} - -
    {comment.content}
    -
    - {#if comment.user == $auth.name} - - {/if} - - Par {comment.user}, le - -
    -
    - {/if} - {/each} - {/await} -
    - - {#if newComment.content && newComment.content.length > 0} - - {/if} -
    - -
    -
    -{/if} diff --git a/qa/ui/src/lib/components/QAItems.svelte b/qa/ui/src/lib/components/QAItems.svelte deleted file mode 100644 index 9f6e5a2e..00000000 --- a/qa/ui/src/lib/components/QAItems.svelte +++ /dev/null @@ -1,75 +0,0 @@ - - -
    - {#if queries.length} - {#each queries as q (q.id)} - - {/if} - - {/each} - {:else} -
    - Aucune requête enregistrée -
    - {/if} -
    diff --git a/qa/ui/src/lib/components/QANewItem.svelte b/qa/ui/src/lib/components/QANewItem.svelte deleted file mode 100644 index adfce09d..00000000 --- a/qa/ui/src/lib/components/QANewItem.svelte +++ /dev/null @@ -1,120 +0,0 @@ - - -
    -
    - Qu'avez-vous pensé de cette étape ? -
    -
    -
    - -
    - -
    -
    - {#if newQuery.state == "timer"} -
    - -
    - -
    -
    -
    - -
    - -
    -
    - {:else} -
    - -
    - -
    -
    - {/if} -
    - -
    - -
    -
    - -
    -
    diff --git a/qa/ui/src/lib/components/TableOverview.svelte b/qa/ui/src/lib/components/TableOverview.svelte deleted file mode 100644 index 3bc3a960..00000000 --- a/qa/ui/src/lib/components/TableOverview.svelte +++ /dev/null @@ -1,132 +0,0 @@ - - -
    - {#await themesP then themes} - {#if Object.keys(themes).length > 1} - {#each Object.keys(themes) as tname} -
    -

    goto("/themes/" + themes[tname].theme.id)} - > - {tname} -

    - {#if themes[tname].exercices} -
    - {#each themes[tname].exercices as exercice, eid} - {#if exercice.reports} -
    - goto("/themes/" + themes[tname].theme.id + "/" + exercice.exercice.id)} - > - {eid+1}. - - {#each exercice.reports as report} - goto(`exercices/${exercice.exercice.id}/${report.report.id}`)} - > - - {#if report.comments} - - {report.comments.length} - - {/if} - {report.report.subject} - - - {/each} -
    - {/if} - {/each} -
    - {/if} -
    - {/each} - {:else} - {#each Object.keys(themes) as tname} - {#if themes[tname].exercices} - {#each themes[tname].exercices as exercice} - - -

    goto(`exercices/${exercice.exercice.id}`)} - > - {exercice.exercice.title} -

    - {#if exercice.reports} - {#each exercice.reports as report} - goto(`exercices/${exercice.exercice.id}/${report.report.id}`)} - > - - {report.report.subject} - {#if report.comments} - - {report.comments.length} - - {/if} - - - {/each} -
    - {/if} - -
    - {/each} - {/if} - {/each} - {/if} - {/await} -
    diff --git a/qa/ui/src/lib/components/Toaster.svelte b/qa/ui/src/lib/components/Toaster.svelte deleted file mode 100644 index 2398ecec..00000000 --- a/qa/ui/src/lib/components/Toaster.svelte +++ /dev/null @@ -1,22 +0,0 @@ - - -
    - {#each $ToastsStore.toasts as toast} - - - {#if toast.title}{toast.title}{:else}Gustus{/if} - - - {toast.msg} - - - {/each} -
    diff --git a/qa/ui/src/lib/exercices.js b/qa/ui/src/lib/exercices.js deleted file mode 100644 index 13a13977..00000000 --- a/qa/ui/src/lib/exercices.js +++ /dev/null @@ -1,67 +0,0 @@ -export const fieldsExercices = ["title", "headline"]; - -export class Exercice { - constructor(res) { - if (res) { - this.update(res); - } - } - - update({ id, id_theme, title, wip, urlid, path, statement, overview, headline, finished, issue, issuekind, depend, gain, coefficient, videoURI, resolution, seealso, forge_link }) { - this.id = id; - this.id_theme = id_theme; - this.title = title; - this.wip = wip - this.urlid = urlid; - this.path = path; - this.statement = statement; - this.overview = overview; - this.headline = headline; - this.finished = finished; - this.issue = issue; - this.issuekind = issuekind; - this.depend = depend; - this.gain = gain; - this.coefficient = coefficient; - this.videoURI = videoURI; - this.resolution = resolution; - this.seealso = seealso; - this.forge_link = forge_link; - } -} - -export async function getExercices() { - const res = await fetch(`api/exercices`, {headers: {'Accept': 'application/json'}}) - if (res.status == 200) { - return (await res.json()).map((t) => new Exercice(t)); - } else { - throw new Error((await res.json()).errmsg); - } -} - -export async function getThemedExercices(tid) { - const res = await fetch(`api/themes/${tid}/exercices`, {headers: {'Accept': 'application/json'}}) - if (res.status == 200) { - return (await res.json()).map((t) => new Exercice(t)); - } else { - throw new Error((await res.json()).errmsg); - } -} - -export async function getExercice(eid) { - const res = await fetch(`api/exercices/${eid}`, {headers: {'Accept': 'application/json'}}) - if (res.status == 200) { - return new Exercice(await res.json()); - } else { - throw new Error((await res.json()).errmsg); - } -} - -export async function getThemedExercice(tid, eid) { - const res = await fetch(`api/themes/${tid}/exercices/${eid}`, {headers: {'Accept': 'application/json'}}) - if (res.status == 200) { - return new Exercice(await res.json()); - } else { - throw new Error((await res.json()).errmsg); - } -} diff --git a/qa/ui/src/lib/qa.js b/qa/ui/src/lib/qa.js deleted file mode 100644 index 274e62e9..00000000 --- a/qa/ui/src/lib/qa.js +++ /dev/null @@ -1,139 +0,0 @@ -export const QAStates = { - "ok": "OK", - "timer": "Temps passé", - "orthograph": "Orthographe et grammaire", - "issue-statement": "Pas compris", - "issue-flag": "Problème de flag", - "issue-mcq": "Problème de QCM/QCU", - "issue-hint": "Problème d'indice", - "issue-file": "Problème de fichier", - "issue": "Problème autre", - "suggest": "Suggestion", - "too-hard": "Trop dur", - "too-easy": "Trop facile", -}; - -export class QAQuery { - constructor(res) { - if (res) { - this.update(res); - } - } - - update({ id, id_exercice, id_team, user, creation, state, subject, solved, closed, exported, forge_link }) { - this.id = id; - this.id_team = id_team; - this.id_exercice = id_exercice; - this.user = user; - this.creation = creation; - this.state = state; - this.subject = subject; - this.solved = solved; - this.closed = closed; - this.exported = exported; - this.forge_link = forge_link; - } - - async delete() { - const res = await fetch(`api/exercices/${this.id_exercice}/qa/${this.id}`, { - method: 'DELETE', - headers: {'Accept': 'application/json'} - }); - if (res.status < 300) { - return true; - } else { - throw new Error((await res.json()).errmsg); - } - } - - async save() { - const res = await fetch(this.id?`api/exercices/${this.id_exercice}/qa/${this.id}`:`api/exercices/${this.id_exercice}/qa`, { - method: this.id?'PUT':'POST', - headers: {'Accept': 'application/json'}, - body: JSON.stringify(this), - }); - if (res.status == 200) { - const data = await res.json(); - this.update(data); - return data; - } else { - throw new Error((await res.json()).errmsg); - } - } - - async export2Gitlab() { - const res = await fetch(`api/qa/${this.id}/gitlab_export`, {method: 'POST', headers: {'Accept': 'application/json'}}) - if (res.status == 200) { - return await res.json(); - } else { - throw new Error((await res.json()).errmsg); - } - } -} - -export async function getExerciceQA(eid) { - const res = await fetch(`api/exercices/${eid}/qa`, {headers: {'Accept': 'application/json'}}) - if (res.status == 200) { - const data = await res.json(); - if (data == null) - return []; - return data.map((t) => new QAQuery(t)); - } else { - throw new Error((await res.json()).errmsg); - } -} - -export class QAComment { - constructor(res) { - if (res) { - this.update(res); - } - } - - update({ id, id_team, user, date, content }) { - this.id = id; - this.id_team = id_team; - this.user = user; - this.date = date; - this.content = content; - } - - async delete(qid) { - const res = await fetch(`api/qa/${qid}/comments/${this.id}`, { - method: 'DELETE', - headers: {'Accept': 'application/json'} - }); - if (res.status < 300) { - return true; - } else { - throw new Error((await res.json()).errmsg); - } - } - - async save(qid) { - const res = await fetch(this.id?`api/qa/${qid}/comments/${this.id}`:`api/qa/${qid}/comments`, { - method: this.id?'PUT':'POST', - headers: {'Accept': 'application/json'}, - body: JSON.stringify(this), - }); - if (res.status == 200) { - const data = await res.json(); - this.update(data); - return data; - } else { - throw new Error((await res.json()).errmsg); - } - } -} - -export async function getQAComments(qid) { - const res = await fetch(`api/qa/${qid}/comments`, {headers: {'Accept': 'application/json'}}) - if (res.status == 200) { - const data = await res.json(); - if (data == null) - return []; - return data.map((t) => new QAComment(t)); - } else { - throw new Error((await res.json()).errmsg); - } -} diff --git a/qa/ui/src/lib/stores/auth.js b/qa/ui/src/lib/stores/auth.js deleted file mode 100644 index cdf18fb0..00000000 --- a/qa/ui/src/lib/stores/auth.js +++ /dev/null @@ -1,56 +0,0 @@ -import { writable, derived } from 'svelte/store'; - -function createVersionStore() { - const { subscribe, set, update } = writable({"auth":null}); - - return { - subscribe, - - set: (v) => { - update((m) => Object.assign(m, v)); - }, - - update, - - refresh: async () => { - const version = await (await fetch('api/version', {headers: {'Accept': 'application/json'}})).json() - update((m) => version); - return version; - }, - }; - -} - -export const version = createVersionStore(); - -export const auth = derived( - version, - $version => $version.auth, -); - -function createGitlabStore() { - const { subscribe, set, update } = writable(null); - - return { - subscribe, - - set, - - update, - - refresh: async () => { - const res = await fetch('api/gitlab/token', {headers: {'Accept': 'application/json'}}); - if (res.status === 200) { - const token = await res.json(); - update((m) => token); - return token; - } else { - update((m) => null); - return null; - } - }, - }; - -} - -export const gitlab = createGitlabStore(); diff --git a/qa/ui/src/lib/stores/exercices.js b/qa/ui/src/lib/stores/exercices.js deleted file mode 100644 index 910ec591..00000000 --- a/qa/ui/src/lib/stores/exercices.js +++ /dev/null @@ -1,56 +0,0 @@ -import { writable, derived } from 'svelte/store'; - -import { getExercices } from '$lib/exercices' - -function createExercicesStore() { - const { subscribe, set, update } = writable([]); - - return { - subscribe, - - set: (v) => { - update((m) => Object.assign(m, v)); - }, - - update, - - refresh: async () => { - const list = await getExercices(); - update((m) => list); - return list; - }, - }; - -} - -export const exercices = createExercicesStore(); - -export const exercicesIdx = derived( - exercices, - $exercices => { - const exercices_idx = { }; - - for (const e of $exercices) { - exercices_idx[e.id] = e; - } - - return exercices_idx; - }, -); - -export const exercicesByTheme = derived( - exercices, - $exercices => { - const exercices_idx = { }; - - for (const e of $exercices) { - if (!e.id_theme) e.id_theme = 0; - if (!exercices_idx[e.id_theme]) { - exercices_idx[e.id_theme] = [] - } - exercices_idx[e.id_theme].push(e); - } - - return exercices_idx; - }, -); diff --git a/qa/ui/src/lib/stores/teams.js b/qa/ui/src/lib/stores/teams.js deleted file mode 100644 index 8683b686..00000000 --- a/qa/ui/src/lib/stores/teams.js +++ /dev/null @@ -1,39 +0,0 @@ -import { writable, derived } from 'svelte/store'; - -import { getTeams } from '$lib/teams' - -function createTeamsStore() { - const { subscribe, set, update } = writable([]); - - return { - subscribe, - - set: (v) => { - update((m) => Object.assign(m, v)); - }, - - update, - - refresh: async () => { - const list = await getTeams(); - update((m) => list); - return list; - }, - }; - -} - -export const teams = createTeamsStore(); - -export const teamsIdx = derived( - teams, - $teams => { - const teams_idx = { }; - - for (const e of $teams) { - teams_idx[e.id] = e; - } - - return teams_idx; - }, -); diff --git a/qa/ui/src/lib/stores/themes.js b/qa/ui/src/lib/stores/themes.js deleted file mode 100644 index dcc4e4d9..00000000 --- a/qa/ui/src/lib/stores/themes.js +++ /dev/null @@ -1,39 +0,0 @@ -import { writable, derived } from 'svelte/store'; - -import { getThemes } from '$lib/themes' - -function createThemesStore() { - const { subscribe, set, update } = writable([]); - - return { - subscribe, - - set: (v) => { - update((m) => Object.assign(m, v)); - }, - - update, - - refresh: async () => { - const list = await getThemes(); - update((m) => list); - return list; - }, - }; - -} - -export const themes = createThemesStore(); - -export const themesIdx = derived( - themes, - $themes => { - const themes_idx = { }; - - for (const t of $themes) { - themes_idx[t.id] = t; - } - - return themes_idx; - }, -); diff --git a/qa/ui/src/lib/stores/toasts.js b/qa/ui/src/lib/stores/toasts.js deleted file mode 100644 index f6e4d146..00000000 --- a/qa/ui/src/lib/stores/toasts.js +++ /dev/null @@ -1,41 +0,0 @@ -import { writable } from 'svelte/store'; - -function createToastsStore() { - const { subscribe, update } = writable({toasts: []}); - - const addToast = (o) => { - o.timestamp = new Date(); - - o.close = () => { - update((i) => { - i.toasts = i.toasts.filter((j) => { - return !(j.title === o.title && j.msg === o.msg && j.timestamp === o.timestamp) - }); - return i; - }); - } - - update((i) => { - i.toasts.unshift(o); - return i; - }); - - o.cancel = setTimeout(o.close, o.dismiss?o.dismiss:5000); - }; - - const addErrorToast = (o) => { - if (!o.title) o.title = 'Une erreur est survenue !'; - if (!o.color) o.color = 'danger'; - - return addToast(o); - }; - - return { - subscribe, - addToast, - addErrorToast, - }; - -} - -export const ToastsStore = createToastsStore(); diff --git a/qa/ui/src/lib/stores/todo.js b/qa/ui/src/lib/stores/todo.js deleted file mode 100644 index 81ab8757..00000000 --- a/qa/ui/src/lib/stores/todo.js +++ /dev/null @@ -1,64 +0,0 @@ -import { writable, derived } from 'svelte/store'; - -import { getQAView, getQATodo, getQAWork } from '$lib/todo'; - -export function createTodosStore(team) { - const { subscribe, set, update } = writable([]); - - return { - subscribe, - - set: (v) => { - update((m) => Object.assign(m, v)); - }, - - update, - - refresh: async () => { - const list = await getQATodo(team); - list.map((e) => e.id += 10000000); - list.push(...await getQAWork(team)); - update((m) => list); - return list; - }, - }; - -} - -export const todos = createTodosStore(); - -function createViewStore() { - const { subscribe, set, update } = writable([]); - - return { - subscribe, - - set: (v) => { - update((m) => Object.assign(m, v)); - }, - - update, - - refresh: async () => { - const list = await getQAView(); - update((m) => list); - return list; - }, - }; - -} - -export const view = createViewStore(); - -export const viewIdx = derived( - view, - $view => { - const idx = { }; - - for (const v of $view) { - idx[v.id_exercice] = v; - } - - return idx; - } -); diff --git a/qa/ui/src/lib/teams.js b/qa/ui/src/lib/teams.js deleted file mode 100644 index 2db94a36..00000000 --- a/qa/ui/src/lib/teams.js +++ /dev/null @@ -1,52 +0,0 @@ -import { createTodosStore } from '$lib/stores/todo.js' - -export const fieldsTeams = ["name", "color", "active", "external_id"]; - -export class Team { - constructor(res) { - if (res) { - this.update(res); - } - - this.todos = createTodosStore(this); - } - - update({ id, name, color, active, external_id }) { - this.id = id; - this.name = name; - this.color = color; - this.active = active; - this.external_id = external_id; - } - - toHexColor() { - let num = this.color; - num >>>= 0; - let b = (num & 0xFF).toString(16), - g = ((num & 0xFF00) >>> 8).toString(16), - r = ((num & 0xFF0000) >>> 16).toString(16), - a = ( (num & 0xFF000000) >>> 24 ) / 255 ; - if (r.length <= 1) r = "0" + r; - if (g.length <= 1) g = "0" + g; - if (b.length <= 1) b = "0" + b; - return "#" + r + g + b; - } -} - -export async function getTeams() { - const res = await fetch(`api/teams`, {headers: {'Accept': 'application/json'}}) - if (res.status == 200) { - return (await res.json()).map((t) => new Team(t)); - } else { - throw new Error((await res.json()).errmsg); - } -} - -export async function getTeam(tid) { - const res = await fetch(`api/teams/${tid}`, {headers: {'Accept': 'application/json'}}) - if (res.status == 200) { - return new Team(await res.json()); - } else { - throw new Error((await res.json()).errmsg); - } -} diff --git a/qa/ui/src/lib/themes.js b/qa/ui/src/lib/themes.js deleted file mode 100644 index 0ef2d23a..00000000 --- a/qa/ui/src/lib/themes.js +++ /dev/null @@ -1,44 +0,0 @@ -export class Theme { - constructor(res) { - if (res) { - this.update(res); - } - } - - update({ id, name, urlid, path, authors, intro, headline, image, partner_img, partner_href, partner_txt }) { - this.id = id; - this.name = name; - this.urlid = urlid; - this.path = path; - this.authors = authors; - this.intro = intro; - this.headline = headline; - this.image = image; - this.partner_img = partner_img; - this.partner_href = partner_href; - this.partner_txt = partner_txt; - } -} - -export async function getThemes() { - const res = await fetch(`api/themes`, {headers: {'Accept': 'application/json'}}) - if (res.status == 200) { - const data = await res.json(); - if (data) { - return data.map((t) => new Theme(t)); - } else { - return []; - } - } else { - throw new Error((await res.json()).errmsg); - } -} - -export async function getTheme(tid) { - const res = await fetch(`api/themes/${tid}`, {headers: {'Accept': 'application/json'}}) - if (res.status == 200) { - return new Theme(await res.json()); - } else { - throw new Error((await res.json()).errmsg); - } -} diff --git a/qa/ui/src/lib/todo.js b/qa/ui/src/lib/todo.js deleted file mode 100644 index 4ddd769f..00000000 --- a/qa/ui/src/lib/todo.js +++ /dev/null @@ -1,78 +0,0 @@ -import { QAQuery } from './qa'; - -export class QATodo { - constructor(res) { - if (res) { - this.update(res); - } - } - - update({ id, id_team, id_exercice }) { - this.id = id; - this.id_team = id_team; - this.id_exercice = id_exercice; - } - - async delete(team) { - const res = await fetch(team?`api/teams/${team.id}/todo/${this.id}`:`api/teams/${this.id_team}/todo/${this.id}`, { - method: 'DELETE', - headers: {'Accept': 'application/json'} - }); - if (res.status < 300) { - return true; - } else { - throw new Error((await res.json()).errmsg); - } - } -} - -export async function getQATodo(team) { - const res = await fetch(team?`api/teams/${team.id}/qa_work.json`:`api/qa_work.json`, {headers: {'Accept': 'application/json'}}) - if (res.status == 200) { - const data = await res.json(); - if (data === null) { - return [] - } else { - return data.map((t) => new QATodo(t)); - } - } else { - throw new Error((await res.json()).errmsg); - } -} - -export async function getQAWork(team) { - const res = await fetch(team?`api/teams/${team.id}/qa_mywork.json`:`api/qa_mywork.json`, {headers: {'Accept': 'application/json'}}) - if (res.status == 200) { - const data = await res.json() - if (data) { - return data.map((t) => new QAQuery(t)); - } else { - return []; - } - } else { - throw new Error((await res.json()).errmsg); - } -} - -export async function getQAView(team) { - const res = await fetch(team?`api/teams/${team.id}/qa_myexercices.json`:`api/qa_myexercices.json`, {headers: {'Accept': 'application/json'}}) - if (res.status == 200) { - const data = await res.json() - if (data) { - return data.map((t) => new QATodo(t)); - } else { - return []; - } - } else { - throw new Error((await res.json()).errmsg); - } -} - -export async function getExerciceTested(team) { - const res = await fetch(team?`api/teams/${team.id}/qa_exercices.json`:`api/qa_exercices.json`, {headers: {'Accept': 'application/json'}}) - if (res.status == 200) { - return await res.json(); - } else { - throw new Error((await res.json()).errmsg); - } -} diff --git a/qa/ui/src/routes/+layout.js b/qa/ui/src/routes/+layout.js deleted file mode 100644 index a3d15781..00000000 --- a/qa/ui/src/routes/+layout.js +++ /dev/null @@ -1 +0,0 @@ -export const ssr = false; diff --git a/qa/ui/src/routes/+layout.svelte b/qa/ui/src/routes/+layout.svelte deleted file mode 100644 index da0a4b1f..00000000 --- a/qa/ui/src/routes/+layout.svelte +++ /dev/null @@ -1,53 +0,0 @@ - - - - FIC QA - - -
    - - - - diff --git a/qa/ui/src/routes/+page.svelte b/qa/ui/src/routes/+page.svelte deleted file mode 100644 index d1d6f858..00000000 --- a/qa/ui/src/routes/+page.svelte +++ /dev/null @@ -1,51 +0,0 @@ - - -{#if $auth && $auth.is_manager} - - - -{:else} - - -

    Utilisation de la plate-forme de QA

    -

    - Vous trouverez ci-dessous une liste d'étapes à tester. - Répartissez-vous le travail. - Dès que vous rencontrez une erreur ou quelque chose qui ne vous semble pas clair, remontez-le en ajoutant un commentaire à l'étape.
    - Lorsque vous avez terminé une étape, notez-le en ajoutant un commentaire indiquant comment vous avez trouvé l'étape. -

    -

    - En parallèle, vous recevrez des commentaires des autres groupes. Suivez leurs requêtes et répondez à leurs attentes autant que possible. -

    -
    - - - - - - - - -
    -{/if} diff --git a/qa/ui/src/routes/exercices/+page.svelte b/qa/ui/src/routes/exercices/+page.svelte deleted file mode 100644 index f004e241..00000000 --- a/qa/ui/src/routes/exercices/+page.svelte +++ /dev/null @@ -1,61 +0,0 @@ - - - -

    - Étapes -

    - - {#await getExercices()} -
    - -
    - {:then exercices} -

    - -

    - - - - {#each fieldsExercices as field} - - {/each} - - - - {#each exercices as exercice (exercice.id)} - {#if exercice.title.indexOf(query) >= 0} - show(exercice.id)} style="cursor: pointer"> - {#each fieldsExercices as field} - - {/each} - - {/if} - {/each} - -
    - {field} -
    - {@html exercice[field]} - {#if field == "title" && exercice.wip} - - {/if} -
    - {/await} -
    diff --git a/qa/ui/src/routes/exercices/[eid]/+layout.js b/qa/ui/src/routes/exercices/[eid]/+layout.js deleted file mode 100644 index 193698ed..00000000 --- a/qa/ui/src/routes/exercices/[eid]/+layout.js +++ /dev/null @@ -1,14 +0,0 @@ -import { getExercice } from '$lib/exercices'; -import { getExerciceQA } from '$lib/qa.js'; - -/** @type {import('./$types').PageLoad} */ -export async function load({ depends, params }) { - const [exercice, qaitems] = await Promise.all([ - getExercice(params.eid), - getExerciceQA(params.eid), - ]); - depends(`api/exercices/${params.eid}/qa`); - depends(`api/exercices/${params.eid}`); - - return { exercice, qaitems }; -} diff --git a/qa/ui/src/routes/exercices/[eid]/+layout.svelte b/qa/ui/src/routes/exercices/[eid]/+layout.svelte deleted file mode 100644 index a623ae46..00000000 --- a/qa/ui/src/routes/exercices/[eid]/+layout.svelte +++ /dev/null @@ -1,20 +0,0 @@ - - - - - diff --git a/qa/ui/src/routes/exercices/[eid]/+page.svelte b/qa/ui/src/routes/exercices/[eid]/+page.svelte deleted file mode 100644 index 9aef825f..00000000 --- a/qa/ui/src/routes/exercices/[eid]/+page.svelte +++ /dev/null @@ -1,34 +0,0 @@ - - - -
    - {@html data.exercice.statement.replace("$FILES$", "../files")} -
    -
    - {@html data.exercice.overview.replace("$FILES$", "../files")} -
    -
    - - diff --git a/qa/ui/src/routes/exercices/[eid]/[qid]/+layout.js b/qa/ui/src/routes/exercices/[eid]/[qid]/+layout.js deleted file mode 100644 index 87356898..00000000 --- a/qa/ui/src/routes/exercices/[eid]/[qid]/+layout.js +++ /dev/null @@ -1,21 +0,0 @@ -import { error } from '@sveltejs/kit'; - -/** @type {import('./$types').PageLoad} */ -export async function load({ params, parent }) { - const { exercice, qaitems } = await parent(); - - let query_selected = null; - for (const qaitem of qaitems) { - if (qaitem.id == params.qid) { - query_selected = qaitem; - } - } - - if (!query_selected) { - error(404, { - message: 'Not found' - }); - } - - return { exercice, qaitems, query_selected }; -} diff --git a/qa/ui/src/routes/exercices/[eid]/[qid]/+page.svelte b/qa/ui/src/routes/exercices/[eid]/[qid]/+page.svelte deleted file mode 100644 index a07808bd..00000000 --- a/qa/ui/src/routes/exercices/[eid]/[qid]/+page.svelte +++ /dev/null @@ -1,10 +0,0 @@ - - - diff --git a/qa/ui/src/routes/export/+page.svelte b/qa/ui/src/routes/export/+page.svelte deleted file mode 100644 index 34291d3b..00000000 --- a/qa/ui/src/routes/export/+page.svelte +++ /dev/null @@ -1,16 +0,0 @@ - - - - {#await fetch('api/qa/export')} - - {:then res} - {#await res.json() then md} -
    {md}
    - {/await} - {/await} -
    diff --git a/qa/ui/src/routes/teams/+page.svelte b/qa/ui/src/routes/teams/+page.svelte deleted file mode 100644 index dfefe4c0..00000000 --- a/qa/ui/src/routes/teams/+page.svelte +++ /dev/null @@ -1,149 +0,0 @@ - - - -

    - Équipes -

    - -

    - -

    - - - - {#each fields as field} - - {/each} - - - - {#each $teams as team (team.id)} - {#if team.name.indexOf(query) >= 0} - show(team.id)} style="cursor: pointer"> - {#each fields as field} - - {/each} - - {/if} - {/each} - -
    - {field} -
    - {#if field == "color"} -
    - {team.toHexColor()} -
    - {:else} - {team[field]} - {/if} -
    - -
    -

    - Assigner des exercices aux équipes -

    - -
    - - - -

    - Incrémenter de 1 pour chaque nouveau challenge blanc, cela décale l'attribution des exercices. -

    -
    - - - - - - - - - - - - - - - - - -
    -
    diff --git a/qa/ui/src/routes/teams/[tid]/+page.svelte b/qa/ui/src/routes/teams/[tid]/+page.svelte deleted file mode 100644 index 19ab4c45..00000000 --- a/qa/ui/src/routes/teams/[tid]/+page.svelte +++ /dev/null @@ -1,201 +0,0 @@ - - -{#await teamP} - -
    - -
    -
    -{:then team} - -

    -
    - {team.name} -

    - - - -

    Tâches de l'équipe

    - {#await todosP} -
    - -
    - {:then} - - - - - - - - - - {#each $teamtodos as todo (todo.id)} - - - - - - {/each} - - - - - - - - - -
    ScénarioDéfiAct
    - {#if $exercicesIdx.length == 0 && $themesIdx.length == 0} - - {:else if !$themesIdx[$exercicesIdx[todo.id_exercice].id_theme]} - Défis indépendants - {:else} - - {$themesIdx[$exercicesIdx[todo.id_exercice].id_theme].name} - - {/if} - - {#if $exercicesIdx.length == 0} - - {:else} - {$exercicesIdx[todo.id_exercice].title} - {/if} - - -
    -
    submitNewTodo(team)} - > -
    - - - -
    -
    -
    -
    submitNewThemeTodo(team)} - > -
    - - - -
    -
    -
    - {/await} - - - - - -
    -
    -{/await} diff --git a/qa/ui/src/routes/themes/+page.svelte b/qa/ui/src/routes/themes/+page.svelte deleted file mode 100644 index 3ebe3c13..00000000 --- a/qa/ui/src/routes/themes/+page.svelte +++ /dev/null @@ -1,78 +0,0 @@ - - - - {#if $auth && $auth.is_manager} - - - - - {/if} -

    - Scénarios -

    - -

    - -

    - - - - {#each fields as field} - - {/each} - - - - {#each $themes as theme (theme.id)} - {#if theme.name.indexOf(query) >= 0 || theme.authors.indexOf(query) >= 0 || theme.intro.indexOf(query) >= 0} - show(theme.id)} style="cursor: pointer"> - {#each fields as field} - - {/each} - - {/if} - {/each} - -
    - {field} -
    - {#if field == "image"} - Image du scénario - {:else} - {@html theme[field]} - {/if} -
    -
    diff --git a/qa/ui/src/routes/themes/[tid]/+layout.js b/qa/ui/src/routes/themes/[tid]/+layout.js deleted file mode 100644 index 1517a070..00000000 --- a/qa/ui/src/routes/themes/[tid]/+layout.js +++ /dev/null @@ -1,9 +0,0 @@ -import { getTheme } from '$lib/themes'; - -/** @type {import('./$types').PageLoad} */ -export async function load({ depends, params }) { - const theme = await getTheme(params.tid) - depends(`api/themes/${params.tid}`); - - return { theme }; -} diff --git a/qa/ui/src/routes/themes/[tid]/+page.svelte b/qa/ui/src/routes/themes/[tid]/+page.svelte deleted file mode 100644 index e9299bf3..00000000 --- a/qa/ui/src/routes/themes/[tid]/+page.svelte +++ /dev/null @@ -1,81 +0,0 @@ - - - -
    - -

    - {data.theme.name} -

    - {@html data.theme.authors} -
    - - {#if data.theme.intro} - - {@html data.theme.intro.replace("$FILES$", "../files")} - - {/if} - - {#await getThemedExercices($page.params.tid)} - {:then exercices} -

    - Défis ({exercices.length}) -

    - -

    - -

    - - - - {#each fieldsExercices as field} - - {/each} - - - - {#each exercices as exercice (exercice.id)} - {#if exercice.title.indexOf(query) >= 0} - show(exercice.id)} style="cursor: pointer"> - {#each fieldsExercices as field} - - {/each} - - {/if} - {/each} - -
    - {field} -
    - {@html exercice[field]} - {#if field == "title" && exercice.wip} - - {/if} -
    - {/await} -
    diff --git a/qa/ui/src/routes/themes/[tid]/[eid]/+layout.js b/qa/ui/src/routes/themes/[tid]/[eid]/+layout.js deleted file mode 100644 index 62a45108..00000000 --- a/qa/ui/src/routes/themes/[tid]/[eid]/+layout.js +++ /dev/null @@ -1,16 +0,0 @@ -import { getExercice } from '$lib/exercices'; -import { getExerciceQA } from '$lib/qa.js'; - -/** @type {import('./$types').PageLoad} */ -export async function load({ depends, params, parent }) { - const { theme } = await parent(); - - const [exercice, qaitems] = await Promise.all([ - getExercice(params.eid), - getExerciceQA(params.eid), - ]); - depends(`api/exercices/${params.eid}`); - depends(`api/exercices/${params.eid}/qa`); - - return { exercice, qaitems, theme }; -} diff --git a/qa/ui/src/routes/themes/[tid]/[eid]/+layout.svelte b/qa/ui/src/routes/themes/[tid]/[eid]/+layout.svelte deleted file mode 100644 index 1221ca2e..00000000 --- a/qa/ui/src/routes/themes/[tid]/[eid]/+layout.svelte +++ /dev/null @@ -1,21 +0,0 @@ - - - - - diff --git a/qa/ui/src/routes/themes/[tid]/[eid]/+page.svelte b/qa/ui/src/routes/themes/[tid]/[eid]/+page.svelte deleted file mode 100644 index 9aef825f..00000000 --- a/qa/ui/src/routes/themes/[tid]/[eid]/+page.svelte +++ /dev/null @@ -1,34 +0,0 @@ - - - -
    - {@html data.exercice.statement.replace("$FILES$", "../files")} -
    -
    - {@html data.exercice.overview.replace("$FILES$", "../files")} -
    -
    - - diff --git a/qa/ui/src/routes/themes/[tid]/[eid]/[qid]/+layout.js b/qa/ui/src/routes/themes/[tid]/[eid]/[qid]/+layout.js deleted file mode 100644 index fcd969b2..00000000 --- a/qa/ui/src/routes/themes/[tid]/[eid]/[qid]/+layout.js +++ /dev/null @@ -1,21 +0,0 @@ -import { error } from '@sveltejs/kit'; - -/** @type {import('./$types').PageLoad} */ -export async function load({ params, parent }) { - const { exercice, qaitems, theme } = await parent(); - - let query_selected = null; - for (const qaitem of qaitems) { - if (qaitem.id == params.qid) { - query_selected = qaitem; - } - } - - if (!query_selected) { - error(404, { - message: 'Not found' - }); - } - - return { exercice, qaitems, query_selected, theme }; -} diff --git a/qa/ui/src/routes/themes/[tid]/[eid]/[qid]/+page.svelte b/qa/ui/src/routes/themes/[tid]/[eid]/[qid]/+page.svelte deleted file mode 100644 index c65c4640..00000000 --- a/qa/ui/src/routes/themes/[tid]/[eid]/[qid]/+page.svelte +++ /dev/null @@ -1,11 +0,0 @@ - - - diff --git a/qa/ui/static/favicon.png b/qa/ui/static/favicon.png deleted file mode 100644 index 825b9e65..00000000 Binary files a/qa/ui/static/favicon.png and /dev/null differ diff --git a/qa/ui/svelte.config.js b/qa/ui/svelte.config.js deleted file mode 100644 index bde06552..00000000 --- a/qa/ui/svelte.config.js +++ /dev/null @@ -1,15 +0,0 @@ -import adapt from '@sveltejs/adapter-static'; - -/** @type {import('@sveltejs/kit').Config} */ -const config = { - kit: { - adapter: adapt({ - fallback: 'index.html' - }), - paths: { - relative: true - }, - } -}; - -export default config; diff --git a/qa/ui/vite.config.js b/qa/ui/vite.config.js deleted file mode 100644 index f7d505e3..00000000 --- a/qa/ui/vite.config.js +++ /dev/null @@ -1,14 +0,0 @@ -import { sveltekit } from '@sveltejs/kit/vite'; - -/** @type {import('vite').UserConfig} */ -const config = { - server: { - hmr: { - port: 10001 - } - }, - - plugins: [sveltekit()] -}; - -export default config; diff --git a/receiver/.gitignore b/receiver/.gitignore deleted file mode 100644 index ab8eb5aa..00000000 --- a/receiver/.gitignore +++ /dev/null @@ -1 +0,0 @@ -receiver diff --git a/receiver/chname.go b/receiver/chname.go deleted file mode 100644 index 15fadd8c..00000000 --- a/receiver/chname.go +++ /dev/null @@ -1,19 +0,0 @@ -package main - -import ( - "log" - "net/http" - "path" -) - -var denyNameChange bool = true - -func ChNameHandler(w http.ResponseWriter, r *http.Request, team string, sURL []string) { - if denyNameChange { - log.Printf("UNHANDELED %s name change request from %s: %s [%s]\n", r.Method, r.RemoteAddr, r.URL.Path, r.UserAgent()) - http.Error(w, "{\"errmsg\":\"Le changement de nom est prohibé.\"}", http.StatusForbidden) - } else if saveTeamFile(path.Join(team, "name"), w, r) { - // File enqueued for backend treatment - http.Error(w, "{\"errmsg\":\"Demande de changement de nom acceptée\"}", http.StatusAccepted) - } -} diff --git a/receiver/choices.go b/receiver/choices.go deleted file mode 100644 index aefa6d54..00000000 --- a/receiver/choices.go +++ /dev/null @@ -1,19 +0,0 @@ -package main - -import ( - "net/http" - "path" - "time" -) - -func WantChoicesHandler(w http.ResponseWriter, r *http.Request, team string, sURL []string) { - if challengeEnd != nil && time.Now().After(*challengeEnd) { - http.Error(w, "{\"errmsg\":\"Le challenge est terminé, trop tard !\"}", http.StatusGone) - return - } - - // Enqueue file for backend treatment - if saveTeamFile(path.Join(team, "choices"), w, r) { - http.Error(w, "{\"errmsg\":\"Demande de choix acceptée...\"}", http.StatusAccepted) - } -} diff --git a/receiver/hint.go b/receiver/hint.go deleted file mode 100644 index bdd09142..00000000 --- a/receiver/hint.go +++ /dev/null @@ -1,19 +0,0 @@ -package main - -import ( - "net/http" - "path" - "time" -) - -func HintHandler(w http.ResponseWriter, r *http.Request, team string, sURL []string) { - if challengeEnd != nil && time.Now().After(*challengeEnd) { - http.Error(w, "{\"errmsg\":\"Le challenge est terminé, trop tard pour un indice !\"}", http.StatusGone) - return - } - - // Enqueue file for backend treatment - if saveTeamFile(path.Join(team, "hint"), w, r) { - http.Error(w, "{\"errmsg\":\"Demande d'astuce acceptée...\"}", http.StatusAccepted) - } -} diff --git a/receiver/issue.go b/receiver/issue.go deleted file mode 100644 index 7cc7e880..00000000 --- a/receiver/issue.go +++ /dev/null @@ -1,19 +0,0 @@ -package main - -import ( - "log" - "net/http" - "path" -) - -var acceptNewIssues bool = true - -func IssueHandler(w http.ResponseWriter, r *http.Request, team string, sURL []string) { - if !acceptNewIssues { - log.Printf("UNHANDELED %s issue request from %s: %s [%s]\n", r.Method, r.RemoteAddr, r.URL.Path, r.UserAgent()) - http.Error(w, "{\"errmsg\":\"Il n'est pas possible de rapporter d'anomalie.\"}", http.StatusForbidden) - } else if saveTeamFile(path.Join(team, "issue"), w, r) { - // File enqueued for backend treatment - http.Error(w, "{\"errmsg\":\"Anomalie signalée avec succès. Merci de votre patience...\"}", http.StatusAccepted) - } -} diff --git a/receiver/main.go b/receiver/main.go deleted file mode 100644 index 9c3d96b5..00000000 --- a/receiver/main.go +++ /dev/null @@ -1,148 +0,0 @@ -package main - -import ( - "context" - "flag" - "fmt" - "log" - "net/http" - "os" - "os/signal" - "path" - "strings" - "syscall" - "time" - - "srs.epita.fr/fic-server/settings" -) - -func main() { - bind := "127.0.0.1:8080" - prefix := "/" - teamsDir := "./TEAMS/" - settings.SettingsDir = "./SETTINGSDIST" - - // Read paremeters from environment - if v, exists := os.LookupEnv("FIC_BASEURL"); exists { - prefix = v - } - if v, exists := os.LookupEnv("FIC_RECEIVER_BIND"); exists { - bind = v - } - if v, exists := os.LookupEnv("FIC_SETTINGSDIST"); exists { - settings.SettingsDir = v - } - if v, exists := os.LookupEnv("FIC_STARTED_FILE"); exists { - startedFile = v - } - if v, exists := os.LookupEnv("FIC_SUBMISSIONS_DIRECTORY"); exists { - SubmissionDir = v - } - if v, exists := os.LookupEnv("FIC_TEAMS_DIRECTORY"); exists { - teamsDir = v - } - - flag.StringVar(&bind, "bind", bind, "Bind port/socket") - flag.StringVar(&prefix, "prefix", prefix, "Request path prefix to strip (from proxy)") - flag.StringVar(&teamsDir, "teams", teamsDir, "Base directory where find existing teams") - flag.StringVar(&settings.SettingsDir, "settings", settings.SettingsDir, "Base directory where read settings") - flag.StringVar(&startedFile, "startedFile", startedFile, "Path to the file to create/remove whether or not the challenge is running") - flag.StringVar(&SubmissionDir, "submission", SubmissionDir, "Base directory where save submissions") - var simulator = flag.String("simulator", "", "Team to simulate (for development only)") - flag.StringVar(&staticDir, "static", staticDir, "Set to serve pages as well (for development only, use with -simulator)") - flag.Parse() - - log.SetPrefix("[receiver] ") - - startedFile = path.Clean(startedFile) - SubmissionDir = path.Clean(SubmissionDir) - TmpSubmissionDir = path.Join(SubmissionDir, ".tmp") - - log.Println("Creating submission directory...") - if _, err := os.Stat(TmpSubmissionDir); os.IsNotExist(err) { - if err = os.MkdirAll(TmpSubmissionDir, 0700); err != nil { - log.Fatal("Unable to create submission directory:", err) - } - } - - prefix = strings.TrimRight(prefix, "/") - - // Load configuration - settings.LoadAndWatchSettings(path.Join(settings.SettingsDir, settings.SettingsFile), reloadSettings) - - // Register handlers - http.Handle(fmt.Sprintf("%s/chname", prefix), http.StripPrefix(fmt.Sprintf("%s/chname", prefix), submissionTeamChecker{"name change", ChNameHandler, teamsDir, *simulator})) - http.Handle(fmt.Sprintf("%s/issue", prefix), http.StripPrefix(fmt.Sprintf("%s/issue", prefix), submissionTeamChecker{"issue", IssueHandler, teamsDir, *simulator})) - http.Handle(fmt.Sprintf("%s/openhint/", prefix), http.StripPrefix(fmt.Sprintf("%s/openhint/", prefix), submissionTeamChecker{"opening hint", HintHandler, teamsDir, *simulator})) - http.Handle(fmt.Sprintf("%s/wantchoices/", prefix), http.StripPrefix(fmt.Sprintf("%s/wantchoices/", prefix), submissionTeamChecker{"wantint choices", WantChoicesHandler, teamsDir, *simulator})) - http.Handle(fmt.Sprintf("%s/registration", prefix), http.StripPrefix(fmt.Sprintf("%s/registration", prefix), submissionChecker{"registration", RegistrationHandler})) - http.Handle(fmt.Sprintf("%s/resolution/", prefix), http.StripPrefix(fmt.Sprintf("%s/resolution/", prefix), ResolutionHandler{})) - http.Handle(fmt.Sprintf("%s/reset_progress", prefix), http.StripPrefix(fmt.Sprintf("%s/reset_progress", prefix), submissionTeamChecker{"reset_progress", ResetProgressHandler, teamsDir, *simulator})) - http.Handle(fmt.Sprintf("%s/submission/", prefix), http.StripPrefix(fmt.Sprintf("%s/submission/", prefix), submissionTeamChecker{"submission", SubmissionHandler, teamsDir, *simulator})) - - if *simulator != "" { - if _, err := os.Stat(path.Join(teamsDir, *simulator)); os.IsNotExist(err) { - log.Printf("WARNING: Team '%s' doesn't exist yet in %s.", *simulator, teamsDir) - } - - // Serve team files - http.Handle(fmt.Sprintf("%s/wait.json", prefix), http.StripPrefix(prefix, http.FileServer(http.Dir(path.Join(teamsDir, *simulator))))) - http.Handle(fmt.Sprintf("%s/my.json", prefix), http.StripPrefix(prefix, TeamMyServer{path.Join(teamsDir, *simulator)})) - - // Serve generated content - http.Handle(fmt.Sprintf("%s/teams.json", prefix), http.StripPrefix(prefix, http.FileServer(http.Dir(teamsDir)))) - http.Handle(fmt.Sprintf("%s/themes.json", prefix), http.StripPrefix(prefix, http.FileServer(http.Dir(teamsDir)))) - http.Handle(fmt.Sprintf("%s/stats.json", prefix), http.StripPrefix(prefix, http.FileServer(http.Dir(teamsDir)))) - http.Handle(fmt.Sprintf("%s/settings.json", prefix), http.StripPrefix(prefix, http.FileServer(http.Dir(settings.SettingsDir)))) - - // Serve static assets - http.Handle(fmt.Sprintf("%s/css/", prefix), http.StripPrefix(prefix, http.FileServer(http.Dir(staticDir)))) - http.Handle(fmt.Sprintf("%s/js/", prefix), http.StripPrefix(prefix, http.FileServer(http.Dir(staticDir)))) - - http.Handle(fmt.Sprintf("%s/files/", prefix), http.StripPrefix(prefix, http.FileServer(http.Dir("FILES")))) - - // Serve index - http.HandleFunc(fmt.Sprintf("%s/edit", prefix), serveIndex) - http.HandleFunc(fmt.Sprintf("%s/rank", prefix), serveIndex) - http.HandleFunc(fmt.Sprintf("%s/register", prefix), serveIndex) - http.HandleFunc(fmt.Sprintf("%s/rules", prefix), serveIndex) - http.HandleFunc(fmt.Sprintf("%s/videos", prefix), serveIndex) - } - - // Prepare graceful shutdown - interrupt := make(chan os.Signal, 1) - signal.Notify(interrupt, os.Interrupt, syscall.SIGTERM) - - srv := &http.Server{ - Addr: bind, - ReadHeaderTimeout: 15 * time.Second, - ReadTimeout: 15 * time.Second, - WriteTimeout: 10 * time.Second, - IdleTimeout: 30 * time.Second, - } - - // Serve pages - go func() { - log.Fatal(srv.ListenAndServe()) - }() - log.Println(fmt.Sprintf("Ready, listening on %s", bind)) - - // Wait shutdown signal and touch timestamp - ticker := time.NewTicker(time.Second) - defer ticker.Stop() - -loop: - for { - select { - case <-interrupt: - break loop - case <-ticker.C: - now := time.Now() - os.Chtimes(SubmissionDir, now, now) - } - } - - log.Print("The service is shutting down...") - srv.Shutdown(context.Background()) - log.Println("done") -} diff --git a/receiver/register.go b/receiver/register.go deleted file mode 100644 index e81b2ef2..00000000 --- a/receiver/register.go +++ /dev/null @@ -1,42 +0,0 @@ -package main - -import ( - "log" - "net/http" - "path" -) - -var allowRegistration bool = false - -func RegistrationHandler(w http.ResponseWriter, r *http.Request, sURL []string) { - if !allowRegistration { - log.Printf("UNHANDLED %s registration request from %s: %s [%s]\n", r.Method, r.RemoteAddr, r.URL.Path, r.UserAgent()) - http.Error(w, "{\"errmsg\":\"L'enregistrement d'équipe n'est pas permis.\"}", http.StatusForbidden) - return - } - - teamInitialName := "-" - if t := r.Header.Get("X-FIC-Team"); t != "" { - teamInitialName = t - } else { - http.Error(w, "{\"errmsg\":\"Votre jeton d'authentification semble invalide. Contactez l'équipe serveur.\"}", http.StatusInternalServerError) - return - } - - // Check request type and size - if r.Method != "POST" { - http.Error(w, "{\"errmsg\":\"Requête invalide.\"}", http.StatusBadRequest) - return - } else if r.ContentLength < 0 || r.ContentLength > 4095 { - http.Error(w, "{\"errmsg\":\"Requête trop longue ou de taille inconnue\"}", http.StatusRequestEntityTooLarge) - return - } - - if err := saveFile(path.Join(SubmissionDir, "_registration", teamInitialName), r); err != nil { - log.Println("Unable to open registration file:", err) - http.Error(w, "{\"errmsg\":\"Internal server error. Please retry in few seconds.\"}", http.StatusInternalServerError) - } else { - // File enqueued for backend treatment - http.Error(w, "{\"errmsg\":\"Demande d'enregistrement acceptée\"}", http.StatusAccepted) - } -} diff --git a/receiver/reset.go b/receiver/reset.go deleted file mode 100644 index 90915f9a..00000000 --- a/receiver/reset.go +++ /dev/null @@ -1,26 +0,0 @@ -package main - -import ( - "net/http" - "path" - "time" -) - -var enableResetProgression = false - -func ResetProgressHandler(w http.ResponseWriter, r *http.Request, team string, sURL []string) { - if !enableResetProgression { - http.Error(w, "{\"errmsg\":\"Le challenge est terminé, trop tard !\"}", http.StatusForbidden) - return - } - - if challengeEnd != nil && time.Now().After(*challengeEnd) { - http.Error(w, "{\"errmsg\":\"Le challenge est terminé, trop tard !\"}", http.StatusGone) - return - } - - // Enqueue file for backend treatment - if saveTeamFile(path.Join(team, "reset_progress"), w, r) { - http.Error(w, "{\"errmsg\":\"Demande acceptée...\"}", http.StatusAccepted) - } -} diff --git a/receiver/resolution.go b/receiver/resolution.go deleted file mode 100644 index 70aea061..00000000 --- a/receiver/resolution.go +++ /dev/null @@ -1,48 +0,0 @@ -package main - -import ( - "log" - "net/http" - "net/url" - "path" - "strings" - "text/template" -) - -var enableResolutionRoute bool = false - -type ResolutionHandler struct{} - -const resolutiontpl = ` - - - - Résolution - - - - - -` - -func (s ResolutionHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { - if addr := r.Header.Get("X-Forwarded-For"); addr != "" { - r.RemoteAddr = addr - } - - if !enableResolutionRoute { - log.Printf("UNHANDELED %s request from %s: /resolution%s [%s]\n", r.Method, r.RemoteAddr, r.URL.Path, r.UserAgent()) - http.NotFound(w, r) - return - } - - log.Printf("%s \"%s /resolution%s\" [%s]\n", r.RemoteAddr, r.Method, r.URL.Path, r.UserAgent()) - - w.Header().Set("Content-Type", "text/html") - - if resolutionTmpl, err := template.New("resolution").Parse(resolutiontpl); err != nil { - log.Println("Cannot create template: ", err) - } else if err = resolutionTmpl.Execute(w, path.Join("/vids/", strings.Replace(url.PathEscape(r.URL.Path), "%2F", "/", -1))); err != nil { - log.Println("An error occurs during template execution: ", err) - } -} diff --git a/receiver/save.go b/receiver/save.go deleted file mode 100644 index 7db69280..00000000 --- a/receiver/save.go +++ /dev/null @@ -1,63 +0,0 @@ -package main - -import ( - "bufio" - "io/ioutil" - "log" - "net/http" - "os" - "path" -) - -var SubmissionDir = "./submissions/" -var TmpSubmissionDir string - -func saveTeamFile(p string, w http.ResponseWriter, r *http.Request) bool { - if len(SubmissionDir) < 1 || len(p) < 1 { - http.Error(w, "{\"errmsg\":\"Requête invalide.\"}", http.StatusBadRequest) - return false - } else if len(p) <= 0 { - log.Println("EMPTY $EXERCICE RECEIVED:", p) - http.Error(w, "{\"errmsg\":\"Internal server error. Please retry in few seconds.\"}", http.StatusInternalServerError) - return false - } else if _, err := os.Stat(path.Join(SubmissionDir, p)); !os.IsNotExist(err) { - // Previous submission not treated - http.Error(w, "{\"errmsg\":\"Du calme ! une requête est déjà en cours de traitement.\"}", http.StatusPaymentRequired) - return false - } else if err = saveFile(path.Join(SubmissionDir, p), r); err != nil { - log.Println("Unable to handle submission file:", err) - http.Error(w, "{\"errmsg\":\"Internal server error. Please retry in few seconds.\"}", http.StatusInternalServerError) - return false - } - return true -} - -func saveFile(p string, r *http.Request) error { - dirname := path.Dir(p) - if _, err := os.Stat(dirname); os.IsNotExist(err) { - if err = os.MkdirAll(dirname, 0751); err != nil { - return err - } - } - - // Write content to temp file - tmpfile, err := ioutil.TempFile(TmpSubmissionDir, "") - if err != nil { - return err - } - - writer := bufio.NewWriter(tmpfile) - reader := bufio.NewReader(r.Body) - if _, err = reader.WriteTo(writer); err != nil { - return err - } - writer.Flush() - tmpfile.Close() - - if err = os.Rename(tmpfile.Name(), p); err != nil { - log.Println("[ERROR] Unable to move file: ", err) - return err - } - - return nil -} diff --git a/receiver/settings.go b/receiver/settings.go deleted file mode 100644 index fb4b6053..00000000 --- a/receiver/settings.go +++ /dev/null @@ -1,68 +0,0 @@ -package main - -import ( - "log" - "os" - "time" - - "srs.epita.fr/fic-server/settings" -) - -var startedFile = "started" - -var touchTimer *time.Timer = nil -var challengeStart time.Time -var challengeEnd *time.Time - -func touchStartedFile() { - if fd, err := os.Create(startedFile); err == nil { - log.Println("Started! Go, Go, Go!!") - fd.Close() - } else { - log.Fatal("Unable to start challenge:", err) - } -} - -func reloadSettings(config *settings.Settings) { - if challengeStart != config.Start || challengeEnd != config.End { - if touchTimer != nil { - touchTimer.Stop() - } - - if config.Start.Unix() == 0 { - log.Println("WARNING: No challenge start defined!") - - if _, err := os.Stat(startedFile); !os.IsNotExist(err) { - os.Remove(startedFile) - } - - return - } - - startSub := time.Until(config.Start) - if startSub > 0 { - log.Println("Challenge will starts at", config.Start, "in", startSub) - - if _, err := os.Stat(startedFile); !os.IsNotExist(err) { - os.Remove(startedFile) - } - - touchTimer = time.AfterFunc(config.Start.Sub(time.Now().Add(time.Duration(1*time.Second))), touchStartedFile) - } else { - log.Println("Challenge started at", config.Start, "since", -startSub) - touchStartedFile() - } - log.Println("Challenge ends on", config.End) - - challengeStart = config.Start - challengeEnd = config.End - } else { - log.Println("Configuration reloaded, but start/end times doesn't change.") - } - - enableResolutionRoute = config.EnableResolutionRoute - denyNameChange = config.DenyNameChange - acceptNewIssues = config.AcceptNewIssue - allowRegistration = config.AllowRegistration - enableResetProgression = config.WorkInProgress && config.CanResetProgression -} diff --git a/receiver/static.go b/receiver/static.go deleted file mode 100644 index 4e20f5fc..00000000 --- a/receiver/static.go +++ /dev/null @@ -1,25 +0,0 @@ -package main - -import ( - "net/http" - "os" - "path" -) - -var staticDir = "static" - -func serveIndex(w http.ResponseWriter, r *http.Request) { - http.ServeFile(w, r, path.Join(staticDir, "index.html")) -} - -type TeamMyServer struct { - path2Dir string -} - -func (s TeamMyServer) ServeHTTP(w http.ResponseWriter, r *http.Request) { - if _, err := os.Stat(startedFile); os.IsNotExist(err) { - http.ServeFile(w, r, path.Join(s.path2Dir, "wait.json")) - } else { - http.ServeFile(w, r, path.Join(s.path2Dir, "my.json")) - } -} diff --git a/receiver/submissions.go b/receiver/submissions.go deleted file mode 100644 index 39564c2d..00000000 --- a/receiver/submissions.go +++ /dev/null @@ -1,74 +0,0 @@ -package main - -import ( - "log" - "net/http" - "os" - "path" - "strings" -) - -type submissionHandler func(w http.ResponseWriter, r *http.Request, sURL []string) - -type submissionChecker struct { - kind string - next submissionHandler -} - -type submissionTeamHandler func(w http.ResponseWriter, r *http.Request, team string, sURL []string) - -type submissionTeamChecker struct { - kind string - next submissionTeamHandler - teamsDir string - simulator string -} - -func (c submissionChecker) ServeHTTP(w http.ResponseWriter, r *http.Request) { - if addr := r.Header.Get("X-Forwarded-For"); addr != "" { - r.RemoteAddr = addr - } - team := "-" - if t := r.Header.Get("X-FIC-Team"); t != "" { - team = t - } - log.Printf("%s %s \"%s %s\" => %s [%s]\n", r.RemoteAddr, team, r.Method, r.URL.Path, c.kind, r.UserAgent()) - - w.Header().Set("Content-Type", "application/json") - - // Check request type and size - if r.Method != "POST" || r.ContentLength < 0 { - http.Error(w, "{\"errmsg\":\"Requête invalide.\"}", http.StatusBadRequest) - return - } else if r.ContentLength > 4097 { - http.Error(w, "{\"errmsg\":\"Requête trop longue\"}", http.StatusRequestEntityTooLarge) - return - } - - // Extract URL arguments - var sURL = strings.Split(strings.TrimPrefix(r.URL.Path, "/"), "/") - - c.next(w, r, sURL) -} - -func (c submissionTeamChecker) ServeHTTP(w http.ResponseWriter, r *http.Request) { - submissionChecker{c.kind, func(w http.ResponseWriter, r *http.Request, sURL []string) { - team := c.simulator - if t := r.Header.Get("X-FIC-Team"); t != "" { - team = t - } - - // Check team validity and existance - if len(team) < 1 || team == "-" || team == "public" { - log.Println("INVALID TEAM:", team) - http.Error(w, "{\"errmsg\":\"Équipe inexistante.\"}", http.StatusBadRequest) - return - } else if _, err := os.Stat(path.Join(c.teamsDir, team)); os.IsNotExist(err) { - log.Println("UNKNOWN TEAM:", team) - http.Error(w, "{\"errmsg\":\"Équipe inexistante.\"}", http.StatusBadRequest) - return - } - - c.next(w, r, team, sURL) - }}.ServeHTTP(w, r) -} diff --git a/receiver/submit.go b/receiver/submit.go deleted file mode 100644 index f00af98e..00000000 --- a/receiver/submit.go +++ /dev/null @@ -1,32 +0,0 @@ -package main - -import ( - "fmt" - "net/http" - "path" - "strconv" - "time" -) - -func SubmissionHandler(w http.ResponseWriter, r *http.Request, team string, sURL []string) { - if challengeEnd != nil && time.Now().After(*challengeEnd) { - http.Error(w, "{\"errmsg\":\"Vous ne pouvez plus soumettre, le challenge est terminé.\"}", http.StatusGone) - return - } - - if len(sURL) != 1 { - http.Error(w, "{\"errmsg\":\"Arguments manquants.\"}", http.StatusBadRequest) - return - } - - // Check exercice validity then save the submission - if pex, err := strconv.ParseInt(sURL[0], 10, 64); err != nil { - http.Error(w, "{\"errmsg\":\"Requête invalide.\"}", http.StatusBadRequest) - return - } else if exercice := fmt.Sprintf("%d", pex); len(exercice) < 1 { - http.Error(w, "{\"errmsg\":\"Requête invalide.\"}", http.StatusBadRequest) - return - } else if saveTeamFile(path.Join(team, exercice), w, r) { - http.Error(w, "{\"errmsg\":\"Son traitement est en cours...\"}", http.StatusAccepted) - } -} diff --git a/remote/challenge-sync-airbus/.gitignore b/remote/challenge-sync-airbus/.gitignore deleted file mode 100644 index d46d7700..00000000 --- a/remote/challenge-sync-airbus/.gitignore +++ /dev/null @@ -1 +0,0 @@ -challenge-sync-airbus \ No newline at end of file diff --git a/remote/challenge-sync-airbus/api.go b/remote/challenge-sync-airbus/api.go deleted file mode 100644 index 9bd9ac3e..00000000 --- a/remote/challenge-sync-airbus/api.go +++ /dev/null @@ -1,199 +0,0 @@ -package main - -import ( - "bytes" - "crypto/tls" - "encoding/json" - "fmt" - "io" - "log" - "net/http" - "strconv" - - "srs.epita.fr/fic-server/libfic" -) - -type AirbusAPI struct { - BaseURL string - Token string - SessionUUID string - InsecureSkipVerify bool -} - -func (a *AirbusAPI) request(method, endpoint string, data io.Reader, out interface{}) error { - var req *http.Request - var err error - - client := &http.Client{ - Transport: &http.Transport{ - TLSClientConfig: &tls.Config{InsecureSkipVerify: a.InsecureSkipVerify}, - }, - } - - if data != nil { - req, err = http.NewRequest(method, a.BaseURL+endpoint, data) - } else { - req, err = http.NewRequest(method, a.BaseURL+endpoint, nil) - } - if err != nil { - return fmt.Errorf("unable to prepare request to %q: %w", endpoint, err) - } - - req.Header.Add("Authorization", "Bearer "+a.Token) - req.Header.Add("Accept", "application/json") - req.Header.Add("Content-Type", "application/json") - - resp, err := client.Do(req) - if err != nil { - return fmt.Errorf("error during request execution to %q: %w", endpoint, err) - } - defer resp.Body.Close() - - if resp.StatusCode == http.StatusOK { - if out != nil { - jdec := json.NewDecoder(resp.Body) - - if err := jdec.Decode(&fic.CyberrangeAPIResponse{Data: out}); err != nil { - return fmt.Errorf("an error occurs when trying to decode response: %w", err) - } - } - } else if all, err := io.ReadAll(resp.Body); err != nil { - return fmt.Errorf("error returned by the API + error on decoding: %d // %w", resp.StatusCode, err) - } else { - return fmt.Errorf("error returned by the API: %d -> %s", resp.StatusCode, all) - } - - return nil -} - -type AirbusUserId int64 - -func NewAirbusUserId(externalid string) AirbusUserId { - v, _ := strconv.ParseInt(externalid, 10, 64) - return AirbusUserId(v) -} - -func (aui AirbusUserId) String() string { - return strconv.FormatInt(int64(aui), 10) -} - -type AirbusUserData struct { - Data []AirbusUser `json:"data"` -} - -type AirbusUser struct { - Id AirbusUserId `json:"id"` - Email string `json:"email"` - Name string `json:"name"` - Nickname string `json:"nickname"` -} - -func (a *AirbusAPI) GetUsers() (users AirbusUserData, err error) { - err = a.request("GET", fmt.Sprintf("/sessions/%s/users", a.SessionUUID), nil, &users) - return -} - -func (a *AirbusAPI) GetUserFromName(name string) (*AirbusUser, error) { - users, err := a.GetUsers() - if err != nil { - return nil, fmt.Errorf("unable to retrieve users list: %w", err) - } - - for _, u := range users.Data { - if u.Name == name { - return &u, nil - } - } - - return nil, fmt.Errorf("unable to find user %q", name) -} - -func (a *AirbusAPI) GetUserFromEmail(email string) (*AirbusUser, error) { - users, err := a.GetUsers() - if err != nil { - return nil, fmt.Errorf("unable to retrieve users list: %w", err) - } - - for _, u := range users.Data { - if u.Email == email { - return &u, nil - } - } - - return nil, fmt.Errorf("unable to find user with email %q", email) -} - -type AirbusChallengeId int64 - -func (aci AirbusChallengeId) String() string { - return strconv.FormatInt(int64(aci), 10) -} - -type AirbusChallengeData struct { - Data []AirbusChallenge `json:"data"` -} - -type AirbusChallenge struct { - Id AirbusChallengeId `json:"id"` - Name string `json:"name"` -} - -func (a *AirbusAPI) GetChallenges() (challenges AirbusChallengeData, err error) { - err = a.request("GET", fmt.Sprintf("/v1/sessions/%s/challenges", a.SessionUUID), nil, &challenges) - return -} - -func (a *AirbusAPI) GetChallengeFromName(name string) (*AirbusChallenge, error) { - challenges, err := a.GetChallenges() - if err != nil { - return nil, fmt.Errorf("unable to retrieve challenges list: %w", err) - } - - for _, c := range challenges.Data { - if c.Name == name { - return &c, nil - } - } - - return nil, fmt.Errorf("unable to find challenge %q", name) -} - -func (a *AirbusAPI) ValidateChallengeFromUser(team *fic.CyberrangeTeam, challengeId AirbusChallengeId) (err error) { - log.Printf("ValidateChallenge: %s, %s, %s", a.SessionUUID, challengeId.String(), team.Members[0].UUID) - if dryRun { - return - } - - err = a.request("GET", fmt.Sprintf("/v1/sessions/%s/%s/%s/validate", a.SessionUUID, challengeId.String(), team.Members[0].UUID), nil, nil) - return -} - -type AirbusUserAwards struct { - Message string `json:"name"` - Value int64 `json:"value"` -} - -func (a *AirbusAPI) AwardUser(team *fic.CyberrangeTeam, value int64, message string) (err error) { - awards := AirbusUserAwards{ - Message: message, - Value: value, - } - - var marshalled []byte - marshalled, err = json.Marshal(awards) - if err != nil { - return - } - - log.Printf("AwardUser: %s", marshalled) - if dryRun { - return - } - - err = a.request("POST", fmt.Sprintf("/v1/sessions/%s/%s/awards", a.SessionUUID, team.Members[0].UUID), bytes.NewReader(marshalled), nil) - if err != nil { - return err - } - - return -} diff --git a/remote/challenge-sync-airbus/bindings.go b/remote/challenge-sync-airbus/bindings.go deleted file mode 100644 index f7af582b..00000000 --- a/remote/challenge-sync-airbus/bindings.go +++ /dev/null @@ -1,39 +0,0 @@ -package main - -import ( - "encoding/json" - "io/ioutil" - "os" - - "srs.epita.fr/fic-server/libfic" -) - -type AirbusExercicesBindings map[int64]AirbusChallengeId - -func ReadExercicesBindings(ebpath string) (AirbusExercicesBindings, error) { - fd, err := os.Open(ebpath) - if err != nil { - return nil, err - } - defer fd.Close() - - jdec := json.NewDecoder(fd) - - var aeb AirbusExercicesBindings - err = jdec.Decode(&aeb) - - return aeb, err -} - -func getTeams(pathname string) (teams map[string]fic.ExportedTeam, err error) { - var cnt_raw []byte - if cnt_raw, err = ioutil.ReadFile(pathname); err != nil { - return - } - - if err = json.Unmarshal(cnt_raw, &teams); err != nil { - return - } - - return -} diff --git a/remote/challenge-sync-airbus/main.go b/remote/challenge-sync-airbus/main.go deleted file mode 100644 index 895f563f..00000000 --- a/remote/challenge-sync-airbus/main.go +++ /dev/null @@ -1,486 +0,0 @@ -package main - -import ( - "flag" - "fmt" - "io/ioutil" - "log" - "os" - "os/signal" - "path" - "path/filepath" - "sort" - "strconv" - "strings" - "syscall" - "time" - - "gopkg.in/fsnotify.v1" - - "srs.epita.fr/fic-server/libfic" -) - -var ( - TeamsDir string - skipInitialSync bool - dryRun bool -) - -func main() { - flag.StringVar(&TeamsDir, "teams", "./TEAMS", "Base directory where save teams JSON files") - var debugINotify = flag.Bool("debuginotify", false, "Show skipped inotofy events") - flag.BoolVar(&dryRun, "dry-run", dryRun, "Don't perform any write action, just display") - flag.BoolVar(&skipInitialSync, "skipinitialsync", skipInitialSync, "Skip the initial synchronization") - flag.BoolVar(&noValidateChallenge, "no-validate-challenge", noValidateChallenge, "Consider challenge validation as a standard award (if each exercice hasn't been imported on their side)") - skipVerify := flag.Bool("skip-tls-verify", false, "Allow not verified certificates (INSECURE!)") - tsFromFile := flag.Bool("timestamp-from-file", false, "Load timestamp matching for score from filesystem instead of the API") - daemon := flag.Bool("watch", false, "Enable daemon mode by watching the directory") - tspath := flag.String("timestamp-file", "./REMOTE/timestamp", "Path to the file storing the last timestamp") - exercicespath := flag.String("exercices-file", "./REMOTE/exercices-bindings.json", "Path to the file containing the ID bindings") - coeff := flag.Float64("global-coeff", 10.0, "Coefficient to use to multiply all scores before passing them to the other platform") - flag.Parse() - - api := AirbusAPI{ - BaseURL: "https://portal.european-cybercup.lan/api", - InsecureSkipVerify: *skipVerify, - } - - if v, exists := os.LookupEnv("AIRBUS_BASEURL"); exists { - api.BaseURL = v - } else if v, exists := os.LookupEnv("AIRBUS_BASEURL_FILE"); exists { - fd, err := os.Open(v) - if err != nil { - log.Fatal("Unable to open AIRBUS_BASEURL_FILE:", err) - } - - b, _ := ioutil.ReadAll(fd) - api.BaseURL = strings.TrimSpace(string(b)) - - fd.Close() - } - if v, exists := os.LookupEnv("AIRBUS_SKIP_TLS_VERIFY"); exists { - var err error - api.InsecureSkipVerify, err = strconv.ParseBool(v) - if err != nil { - log.Fatal("Unable to parse boolean value in AIRBUS_SKIP_TLS_VERIFY:", err) - } - } - if v, exists := os.LookupEnv("AIRBUS_TOKEN"); exists { - api.Token = v - } else if v, exists := os.LookupEnv("AIRBUS_TOKEN_FILE"); exists { - fd, err := os.Open(v) - if err != nil { - log.Fatal("Unable to open AIRBUS_TOKEN_FILE:", err) - } - - b, _ := ioutil.ReadAll(fd) - api.Token = strings.TrimSpace(string(b)) - - fd.Close() - } - - if v, exists := os.LookupEnv("AIRBUS_SESSION_UUID"); exists { - api.SessionUUID = v - } else if v, exists := os.LookupEnv("AIRBUS_SESSION_NAME_FILE"); exists { - fd, err := os.Open(v) - if err != nil { - log.Fatal("Unable to open AIRBUS_SESSION_NAME_FILE:", err) - } - - b, _ := ioutil.ReadAll(fd) - fd.Close() - - v = strings.TrimSpace(string(b)) - - sessions, err := api.GetSessions() - if err != nil { - log.Fatal("Unable to retrieve session: ", err) - } - - for _, session := range sessions { - if session.Name == v { - api.SessionUUID = session.UUID - break - } - } - - if api.SessionUUID == "" { - log.Fatal("Session ID not found") - } else { - log.Println("Session ID discovered: ", api.SessionUUID) - } - } else if v, exists := os.LookupEnv("AIRBUS_SESSION_NAME"); exists { - sessions, err := api.GetSessions() - if err != nil { - log.Fatal("Unable to retrieve session: ", err) - } - - found := false - for _, session := range sessions { - if session.Name == v { - api.SessionUUID = session.UUID - found = true - break - } - } - - if !found { - log.Fatal("Session ID not found") - } else { - log.Println("Session ID discovered: ", api.SessionUUID) - } - } else { - sessions, err := api.GetSessions() - if err != nil { - log.Fatal("Unable to retrieve session (check your credentials!): ", err) - } - - log.Println("Please define your AIRBUS_SESSIONID or AIRBUS_SESSION_NAME.") - log.Println("Existing sessions are:") - for _, session := range sessions { - log.Printf(" - %s: %q", session.UUID, session.Name) - } - os.Exit(1) - } - - log.SetPrefix("[challenge-sync-airbus] ") - - if flag.NArg() > 0 { - args := flag.Args() - switch args[0] { - case "list": - teams, err := api.GetTeams() - if err != nil { - log.Println("Unable to retrieve teams:", err) - os.Exit(1) - } - - fmt.Println("## Airbus' registered teams:") - fmt.Println("----------------------------------------------------------------------------------") - fmt.Println(" UUID | Name | Nb. | Score | Rank") - fmt.Println("----------------------------------------------------------------------------------") - for _, team := range teams { - fmt.Printf(" %s | % 20s | % 3d | % 5d | % 3d\n", team.UUID, team.Name, len(team.Members), team.Score, team.Rank) - } - case "rank": - teams, err := api.GetTeams() - if err != nil { - log.Println("Unable to retrieve teams:", err) - os.Exit(1) - } - - ranking := []*fic.CyberrangeTeam{} - for _, team := range teams { - tmp := team - ranking = append(ranking, &tmp) - } - - sort.Sort(sort.Reverse(ByScore(ranking))) - - fmt.Println("## Airbus' ranking:") - fmt.Println("----------------------------------------------------------------------------") - fmt.Println(" Rank | Name | UUID | Score") - fmt.Println("----------------------------------------------------------------------------") - for _, team := range ranking { - fmt.Printf("% 5d | % 20s | %s | % 5d\n", team.Rank, team.Name, team.UUID, team.Score) - } - case "get": - teams, err := api.GetTeams() - if err != nil { - log.Println("Unable to retrieve teams:", err) - os.Exit(1) - } - - teamid := args[1] - - for _, team := range teams { - if team.UUID == teamid { - fmt.Printf("## Airbus' registered team %s:\n\nUUID: %s\nName: %s\nScore: %d\nRank: %d\nMembers:\n", teamid, team.UUID, team.Name, team.Score, team.Rank) - for _, member := range team.Members { - fmt.Printf(" - UUID: %s\n Name: %s\n Nickname: %s\n E-mail: %s\n", member.UUID, member.Name, member.Nickname, member.EMail) - } - os.Exit(0) - } - } - fmt.Printf("Team %s not found. Use 'list' to view all existing teams\n", teamid) - case "award": - if len(args) < 3 { - fmt.Println("award ") - os.Exit(1) - } - - teams, err := api.GetTeams() - if err != nil { - log.Println("Unable to retrieve teams:", err) - os.Exit(1) - } - - teamid := args[1] - - value, err := strconv.ParseInt(args[2], 10, 64) - if err != nil { - log.Println("Invalid award value:", err) - os.Exit(1) - } - - for _, team := range teams { - if team.UUID == teamid { - err = api.AwardUser(&team, value, strings.Join(args[3:], " ")) - if err != nil { - log.Println("Unable to award team:", err) - os.Exit(1) - } - - fmt.Println("Team awarded") - os.Exit(0) - } - } - fmt.Printf("Team %s not found. Use 'list' to view all existing teams\n", teamid) - } - - os.Exit(0) - } - - var err error - TeamsDir = path.Clean(TeamsDir) - - w := Walker{ - API: api, - Coeff: *coeff, - } - - // Load teams.json - w.Teams, err = getTeams(filepath.Join(TeamsDir, "teams.json")) - if err != nil { - log.Fatal("Unable to open teams bindings file: ", err.Error()) - } - log.Println("Team bindings loaded: ", len(w.Teams)) - - // Fetch teams from Airbus API - if err = w.fetchTeams(); err != nil { - log.Fatal("Unable to fetch Airbus teams: ", err.Error()) - } - - // Load the timestamp - if *tsFromFile { - w.LastSync, err = loadTS(*tspath) - } else { - w.LastSync, err = loadTSFromAPI(w.TeamBindings) - } - if err != nil { - log.Fatal("Unable to open timestamp file: ", err.Error()) - } - - if !noValidateChallenge { - // Load exercices bindings - w.Exercices, err = ReadExercicesBindings(*exercicespath) - if err != nil { - log.Fatal("Unable to open exercices bindings file: ", err.Error()) - } - } - - if !skipInitialSync { - log.Println("Doing initial score balance") - err = w.BalanceScores() - if err != nil { - log.Println("Something goes wrong during score balance: ", err.Error()) - } - - // save current timestamp for teams - err = saveTS(*tspath, w.LastSync) - if err != nil { - log.Fatal("Unable to save timestamp file: ", err.Error()) - } - - log.Println("initial sync done") - } - - if daemon != nil && *daemon { - // Watch teams.json and scores.json - log.Println("Registering directory events...") - watcher, err := fsnotify.NewWatcher() - if err != nil { - log.Fatal(err) - } - defer watcher.Close() - - if err := watchsubdir(watcher, TeamsDir); err != nil { - log.Fatal(err) - } - - // Register SIGUSR1, SIGUSR2 - interrupt1 := make(chan os.Signal, 1) - signal.Notify(interrupt1, syscall.SIGHUP) - interrupt2 := make(chan os.Signal, 1) - signal.Notify(interrupt2, syscall.SIGUSR1) - interrupt3 := make(chan os.Signal, 1) - signal.Notify(interrupt3, syscall.SIGUSR2) - - var ticker *time.Ticker - if *tsFromFile { - ticker = time.NewTicker(5 * time.Second) - } else { - ticker = time.NewTicker(5 * time.Minute) - } - - watchedNotify := fsnotify.Create - - for { - select { - case <-interrupt1: - log.Println("SIGHUP received, reloading files...") - teamsbindings, err := getTeams(filepath.Join(TeamsDir, "teams.json")) - if err != nil { - log.Println("Unable to open teams bindings file: ", err.Error()) - } else { - w.Teams = teamsbindings - } - - if err = w.fetchTeams(); err != nil { - log.Println("Unable to fetch teams: ", err.Error()) - } - - if !*tsFromFile { - ts, err := loadTSFromAPI(w.TeamBindings) - if err != nil { - log.Println("Unable to refresh timestamp: ", err.Error()) - } else { - w.LastSync = ts - } - } - - // save current timestamp for teams - err = saveTS(*tspath, w.LastSync) - if err != nil { - log.Println("Unable to save timestamp file: ", err.Error()) - } - log.Println("SIGHUP treated.") - case <-interrupt2: - log.Println("SIGUSR1 received, resynching all teams") - // Iterate over teams scores - err = filepath.WalkDir(TeamsDir, w.WalkScoreSync) - if err != nil { - log.Printf("Something goes wrong during walking") - } - - // save current timestamp for teams - err = saveTS(*tspath, w.LastSync) - if err != nil { - log.Println("Unable to save timestamp file: ", err.Error()) - } - log.Println("SIGUSR1 treated.") - case <-interrupt3: - log.Println("SIGUSR2 received, resynching all teams from teams.json") - teamsbindings, err := getTeams(filepath.Join(TeamsDir, "teams.json")) - if err != nil { - log.Println("Unable to open teams bindings file: ", err.Error()) - return - } else { - w.Teams = teamsbindings - } - - if err = w.fetchTeams(); err != nil { - log.Println("Unable to fetch teams: ", err.Error()) - return - } - - if !*tsFromFile { - ts, err := loadTSFromAPI(w.TeamBindings) - if err != nil { - log.Println("Unable to refresh timestamp: ", err.Error()) - return - } else { - w.LastSync = ts - } - } - - err = w.BalanceScores() - if err != nil { - log.Println("Unable to balance scores: ", err.Error()) - } - - log.Println("SIGUSR2 treated.") - case ev := <-watcher.Events: - d, err := os.Lstat(ev.Name) - - if err == nil && strings.HasPrefix(d.Name(), ".") { - if *debugINotify { - log.Println("Skipped event:", ev, "for", ev.Name) - } - continue - } - - if err == nil && watchedNotify == fsnotify.Create && ev.Op&fsnotify.Write == fsnotify.Write { - log.Println("FSNOTIFY WRITE SEEN. Prefer looking at them, as it appears files are not atomically moved.") - watchedNotify = fsnotify.Write - } - - if err == nil && ev.Op&fsnotify.Create == fsnotify.Create && d.Mode().IsDir() && d.Mode()&os.ModeSymlink == 0 { - // Register new subdirectory - if err := watchsubdir(watcher, ev.Name); err != nil { - log.Println(err) - } - } else if err == nil && ev.Op&watchedNotify == watchedNotify && d.Mode().IsRegular() { - if *debugINotify { - log.Println("Treating event:", ev, "for", ev.Name) - } - - if watchedNotify == fsnotify.Write { - time.Sleep(100 * time.Millisecond) - } - - if filepath.Base(ev.Name) == "scores.json" { - go w.treat(ev.Name) - } else if filepath.Base(ev.Name) == "teams.json" { - teamsbindings, err := getTeams(filepath.Join(TeamsDir, "teams.json")) - if err != nil { - log.Println("Unable to open teams bindings file: ", err.Error()) - } else { - w.Teams = teamsbindings - } - if err = w.fetchTeams(); err != nil { - log.Println("Unable to fetch teams: ", err.Error()) - } - } - } else if err == nil && *debugINotify { - log.Println("Skipped event:", ev, "for", ev.Name) - } - case err := <-watcher.Errors: - log.Println("error:", err) - case <-ticker.C: - // save current timestamp for teams - err = saveTS(*tspath, w.LastSync) - if err != nil { - log.Println("Unable to save timestamp file: ", err.Error()) - } - } - } - } - - // save current timestamp for teams - err = saveTS(*tspath, w.LastSync) - if err != nil { - log.Fatal("Unable to save timestamp file: ", err.Error()) - } -} - -func watchsubdir(watcher *fsnotify.Watcher, pathname string) error { - log.Println("Watch new directory:", pathname) - if err := watcher.Add(pathname); err != nil { - return err - } - - if ds, err := ioutil.ReadDir(pathname); err != nil { - return err - } else { - for _, d := range ds { - p := path.Join(pathname, d.Name()) - if d.IsDir() && !strings.HasPrefix(d.Name(), ".") && d.Mode()&os.ModeSymlink == 0 { - if err := watchsubdir(watcher, p); err != nil { - return err - } - } - } - return nil - } -} diff --git a/remote/challenge-sync-airbus/session.go b/remote/challenge-sync-airbus/session.go deleted file mode 100644 index 619a0853..00000000 --- a/remote/challenge-sync-airbus/session.go +++ /dev/null @@ -1,21 +0,0 @@ -package main - -import ( - "time" -) - -type Session struct { - Name string `json:"name"` - QuestionValidation string `json:"question_validation"` - Status string `json:"status"` - UUID string `json:"uuid"` - Mode string `json:"mode"` - Difficulty int `json:"difficulty"` - StartedAt time.Time `json:"start_at"` - FinishAt time.Time `json:"finish_at"` -} - -func (a *AirbusAPI) GetSessions() (ret []Session, err error) { - err = a.request("GET", "/v1/sessions", nil, &ret) - return -} diff --git a/remote/challenge-sync-airbus/team.go b/remote/challenge-sync-airbus/team.go deleted file mode 100644 index 6c0d357c..00000000 --- a/remote/challenge-sync-airbus/team.go +++ /dev/null @@ -1,29 +0,0 @@ -package main - -import ( - "fmt" - - "srs.epita.fr/fic-server/libfic" -) - -func (a *AirbusAPI) GetTeams() ([]fic.CyberrangeTeam, error) { - var data []fic.CyberrangeTeam - err := a.request("GET", fmt.Sprintf("/v1/sessions/%s/teams", a.SessionUUID), nil, &data) - if err != nil { - return nil, err - } else { - return data, nil - } -} - -type ByRank []*fic.CyberrangeTeam - -func (a ByRank) Len() int { return len(a) } -func (a ByRank) Swap(i, j int) { a[i], a[j] = a[j], a[i] } -func (a ByRank) Less(i, j int) bool { return a[i].Rank < a[j].Rank } - -type ByScore []*fic.CyberrangeTeam - -func (a ByScore) Len() int { return len(a) } -func (a ByScore) Swap(i, j int) { a[i], a[j] = a[j], a[i] } -func (a ByScore) Less(i, j int) bool { return a[i].Score < a[j].Score } diff --git a/remote/challenge-sync-airbus/timestamp.go b/remote/challenge-sync-airbus/timestamp.go deleted file mode 100644 index 41678319..00000000 --- a/remote/challenge-sync-airbus/timestamp.go +++ /dev/null @@ -1,73 +0,0 @@ -package main - -import ( - "encoding/json" - "log" - "os" - "time" - - "srs.epita.fr/fic-server/libfic" -) - -type TSValue struct { - Time time.Time `json:"t"` - Score int64 `json:"s"` -} - -func loadTS(tspath string) (timestamp map[string]*TSValue, err error) { - var fd *os.File - if _, err = os.Stat(tspath); os.IsNotExist(err) { - timestamp = map[string]*TSValue{} - err = saveTS(tspath, timestamp) - return - } else if fd, err = os.Open(tspath); err != nil { - return nil, err - } else { - defer fd.Close() - jdec := json.NewDecoder(fd) - - if err = jdec.Decode(×tamp); err != nil { - return - } - - return - } -} - -func loadTSFromAPI(teams map[string]*fic.CyberrangeTeam) (timestamp map[string]*TSValue, err error) { - now := time.Now() - timestamp = map[string]*TSValue{} - - for _, team := range teams { - timestamp[team.Name] = &TSValue{ - Time: now, - Score: team.Score, - } - } - - return -} - -func saveTS(tspath string, ts map[string]*TSValue) error { - if dryRun { - tmp := map[string]TSValue{} - for k, v := range ts { - tmp[k] = *v - } - log.Println("saving TS: ", tmp) - return nil - } - - if fd, err := os.Create(tspath); err != nil { - return err - } else { - defer fd.Close() - jenc := json.NewEncoder(fd) - - if err := jenc.Encode(ts); err != nil { - return err - } - - return nil - } -} diff --git a/remote/challenge-sync-airbus/treat.go b/remote/challenge-sync-airbus/treat.go deleted file mode 100644 index fdb0ea8a..00000000 --- a/remote/challenge-sync-airbus/treat.go +++ /dev/null @@ -1,242 +0,0 @@ -package main - -import ( - "encoding/json" - "fmt" - "log" - "math" - "os" - "path/filepath" - "sync" - "time" - - "srs.epita.fr/fic-server/libfic" -) - -var ( - noValidateChallenge bool -) - -type Walker struct { - LastSync map[string]*TSValue - LastSyncLock sync.RWMutex - Exercices AirbusExercicesBindings - Teams map[string]fic.ExportedTeam - RevTeams map[string]string - TeamBindings map[string]*fic.CyberrangeTeam - API AirbusAPI - Coeff float64 -} - -func (w *Walker) fetchTeams() error { - teams, err := w.API.GetTeams() - if err != nil { - return err - } - - w.RevTeams = map[string]string{} - w.TeamBindings = map[string]*fic.CyberrangeTeam{} - - for tid, team := range w.Teams { - for i, t := range teams { - if team.Name == t.Name || team.ExternalId == t.Name || team.ExternalId == t.UUID { - w.TeamBindings[tid] = &teams[i] - break - } - } - - if _, ok := w.TeamBindings[tid]; !ok { - log.Printf("Team binding not found: %s - %s", tid, team.Name) - } - - w.RevTeams[team.Name] = tid - } - - return nil -} - -func (w *Walker) treat(path string) error { - teamid := filepath.Base(filepath.Dir(path)) - - if _, ok := w.TeamBindings[teamid]; ok { - return w.TreatScoreGrid(path, w.TeamBindings[teamid]) - } - - return nil -} - -func (w *Walker) LoadScoreState(path string) (int64, error) { - mypath := filepath.Join(filepath.Dir(path), "airbus.json") - if _, err := os.Stat(mypath); os.IsNotExist(err) { - fd, err := os.Create(mypath) - if err != nil { - return 0, err - } - defer fd.Close() - - fd.Write([]byte("0")) - return 0, nil - } - - fd, err := os.Open(mypath) - if err != nil { - return 0, err - } - defer fd.Close() - - var ret int64 - - jdec := json.NewDecoder(fd) - if err := jdec.Decode(&ret); err != nil { - return 0, fmt.Errorf("an error occurs when trying to decode airbus.json: %w", err) - } - - return ret, nil -} - -func (w *Walker) LoadScoreGrid(path string) ([]fic.ScoreGridRow, error) { - fd, err := os.Open(filepath.Join(filepath.Dir(path), "scores.json")) - if err != nil { - return nil, err - } - defer fd.Close() - - var ret []fic.ScoreGridRow - - jdec := json.NewDecoder(fd) - if err := jdec.Decode(&ret); err != nil { - return nil, fmt.Errorf("an error occurs when trying to decode airbus.json: %w", err) - } - - return ret, nil -} - -func (w *Walker) WalkScoreSync(path string, d os.DirEntry, err error) error { - if filepath.Base(path) == "scores.json" { - return w.treat(path) - } - - return nil -} - -func (w *Walker) loadMyFile(path string) (*fic.MyTeam, error) { - fd, err := os.Open(path) - if err != nil { - return nil, err - } - defer fd.Close() - - var ret fic.MyTeam - - jdec := json.NewDecoder(fd) - if err := jdec.Decode(&ret); err != nil { - return nil, fmt.Errorf("an error occurs when trying to decode airbus.json: %w", err) - } - - return &ret, nil -} - -func (w *Walker) WalkScore(path string, d os.DirEntry, err error) error { - if filepath.Base(path) == "scores.json" { - go w.treat(path) - } - return nil -} - -func (w *Walker) TreatScoreGrid(path string, airbusTeam *fic.CyberrangeTeam) error { - // Read score grid - fdscores, err := os.Open(path) - if err != nil { - return err - } - defer fdscores.Close() - - teamscores, err := fic.ReadScoreGrid(fdscores) - if err != nil { - return err - } - - // Found all new entries - maxts := TSValue{ - Time: time.Time{}, - } - w.LastSyncLock.RLock() - ts, ok := w.LastSync[airbusTeam.Name] - w.LastSyncLock.RUnlock() - if ok { - maxts = *ts - } else { - ts = &TSValue{Time: time.Time{}} - } - var expected_score float64 - for _, row := range teamscores { - expected_score += row.Points * row.Coeff * w.Coeff - if row.Time.After(ts.Time) { - if !noValidateChallenge && row.Reason == "Validation" { - err = w.API.ValidateChallengeFromUser(airbusTeam, w.Exercices[row.IdExercice]) - } else if row.Reason == "Tries" { - // Just add 1 try at a time as the field is updated - row.Points = fic.TermTriesSeq(fic.ReverseTriesPoints(int64(row.Points))) - err = w.API.AwardUser(airbusTeam, int64(math.Trunc(row.Points*row.Coeff*w.Coeff)), row.Reason) - } else { - err = w.API.AwardUser(airbusTeam, int64(row.Points*row.Coeff*w.Coeff), row.Reason) - } - - if err != nil { - return err - } - - maxts.Score += int64(math.Trunc(row.Points * row.Coeff * w.Coeff)) - } - if row.Time.After(maxts.Time) { - maxts.Time = row.Time - } - } - - if int64(math.Trunc(expected_score)) != maxts.Score { - log.Printf("Team %q need balancing: expected: %f, saved: %d", airbusTeam.Name, expected_score, maxts.Score) - err = w.API.AwardUser(airbusTeam, int64(math.Trunc(expected_score))-maxts.Score, "Balancing") - if err != nil { - return fmt.Errorf("unable to balance score for %q: %w", airbusTeam.Name, err) - } - maxts.Score = int64(math.Trunc(expected_score)) - } - - w.LastSyncLock.Lock() - w.LastSync[airbusTeam.Name] = &maxts - w.LastSyncLock.Unlock() - - return nil -} - -func (w *Walker) BalanceScores() error { - for team_id := range w.Teams { - myteam, err := w.loadMyFile(filepath.Join(TeamsDir, team_id, "my.json")) - if err != nil { - return fmt.Errorf("Unable to open %s/my.json: %w", team_id, err) - } - - airbusTeam := w.TeamBindings[fmt.Sprintf("%d", myteam.Id)] - - expected_score := int64(math.Floor(float64(myteam.Points100) * w.Coeff / 100)) - if airbusTeam == nil { - log.Printf("Skip team %q (tid=%d): no binding found", myteam.Name, myteam.Id) - } else if airbusTeam.Score != expected_score { - err := w.API.AwardUser(airbusTeam, expected_score-airbusTeam.Score, "Balancing") - if err != nil { - return fmt.Errorf("Unable to award team %s: %w", myteam.Name, err) - } - - w.LastSyncLock.Lock() - if _, ok := w.LastSync[airbusTeam.Name]; !ok { - w.LastSync[airbusTeam.Name] = &TSValue{} - } - - w.LastSync[airbusTeam.Name].Score = expected_score - w.LastSync[airbusTeam.Name].Time = time.Now() - w.LastSyncLock.Unlock() - } - } - - return nil -} diff --git a/remote/scores-sync-zqds/.gitignore b/remote/scores-sync-zqds/.gitignore deleted file mode 100644 index 6f95cf3b..00000000 --- a/remote/scores-sync-zqds/.gitignore +++ /dev/null @@ -1 +0,0 @@ -scores-sync-zqds \ No newline at end of file diff --git a/remote/scores-sync-zqds/main.go b/remote/scores-sync-zqds/main.go deleted file mode 100644 index de63571d..00000000 --- a/remote/scores-sync-zqds/main.go +++ /dev/null @@ -1,109 +0,0 @@ -package main - -import ( - "flag" - "log" - "os" - "os/signal" - "path" - "syscall" - - "golang.org/x/oauth2/clientcredentials" - "gopkg.in/fsnotify.v1" -) - -var ( - TeamsDir string - skipInitialSync bool -) - -func main() { - if v, exists := os.LookupEnv("ZQDS_BASEURL"); exists { - base_URL = v - } - if v, exists := os.LookupEnv("ZQDS_EVENTID"); exists { - eventId = v - } - if v, exists := os.LookupEnv("ZQDS_ROUNDID"); exists { - roundId = v - } - - var clientId string - if v, exists := os.LookupEnv("ZQDS_CLIENTID"); exists { - clientId = v - } - var clientSecret string - if v, exists := os.LookupEnv("ZQDS_CLIENTSECRET"); exists { - clientSecret = v - } - if v, exists := os.LookupEnv("ZQDS_TOKENURL"); exists { - TokenURL = v - } - - flag.StringVar(&TeamsDir, "teams", "./TEAMS", "Base directory where save teams JSON files") - var debugINotify = flag.Bool("debuginotify", false, "Show skipped inotofy events") - flag.BoolVar(&skipInitialSync, "skipinitialsync", skipInitialSync, "Skip the initial synchronization") - flag.Parse() - - log.SetPrefix("[scores-sync-zqds] ") - - TeamsDir = path.Clean(TeamsDir) - - configOauth = clientcredentials.Config{ - ClientID: clientId, - ClientSecret: clientSecret, - Scopes: []string{"score:update"}, - TokenURL: TokenURL, - } - - log.Println("Registering directory events...") - watcher, err := fsnotify.NewWatcher() - if err != nil { - log.Fatal(err) - } - defer watcher.Close() - - if err := watcher.Add(TeamsDir); err != nil { - log.Fatal(err) - } - - if !skipInitialSync { - if _, err := os.Stat(path.Join(TeamsDir, "teams.json")); err == nil { - treatAll(path.Join(TeamsDir, "teams.json")) - } - } - - // Register SIGUSR1, SIGUSR2 - interrupt := make(chan os.Signal, 1) - signal.Notify(interrupt, syscall.SIGHUP) - - watchedNotify := fsnotify.Create - - for { - select { - case <-interrupt: - log.Println("SIGHUP received, resyncing all teams' score...") - treatAll(path.Join(TeamsDir, "teams.json")) - log.Println("SIGHUP treated.") - case ev := <-watcher.Events: - if path.Base(ev.Name) == "teams.json" { - if ev.Op&watchedNotify == watchedNotify { - if *debugINotify { - log.Println("Treating event:", ev, "for", ev.Name) - } - go treatDiff(ev.Name) - } else if ev.Op&fsnotify.Write == fsnotify.Write { - log.Println("FSNOTIFY WRITE SEEN. Prefer looking at them, as it appears files are not atomically moved.") - watchedNotify = fsnotify.Write - go treatDiff(ev.Name) - } else if *debugINotify { - log.Println("Skipped teams.json event:", ev) - } - } else if *debugINotify { - log.Println("Skipped NON teams.json event:", ev, "for", ev.Name) - } - case err := <-watcher.Errors: - log.Println("error:", err) - } - } -} diff --git a/remote/scores-sync-zqds/treat.go b/remote/scores-sync-zqds/treat.go deleted file mode 100644 index f786a73d..00000000 --- a/remote/scores-sync-zqds/treat.go +++ /dev/null @@ -1,118 +0,0 @@ -package main - -import ( - "bytes" - "context" - "encoding/json" - "io/ioutil" - "log" - "net/http" - "net/url" - "path" - "sync" - - "golang.org/x/oauth2/clientcredentials" - - "srs.epita.fr/fic-server/libfic" -) - -var ( - teams_scores = map[string]float64{} - - base_URL = "https://api.well-played.gg" - eventId = "" - roundId = "" - - TokenURL = "https://idp.well-played.gg/oauth/token" - - configOauth = clientcredentials.Config{} - - lock sync.Mutex -) - -func getTeams(pathname string) (teams map[string]fic.ExportedTeam, err error) { - var cnt_raw []byte - if cnt_raw, err = ioutil.ReadFile(pathname); err != nil { - return - } - - if err = json.Unmarshal(cnt_raw, &teams); err != nil { - return - } - - return -} - -func treatAll(pathname string) { - teams, err := getTeams(pathname) - if err != nil { - log.Printf("[ERR] %s\n", err) - } - - for tid, team := range teams { - treat(team, tid) - } -} - -func treatDiff(pathname string) { - teams, err := getTeams(pathname) - if err != nil { - log.Printf("[ERR] %s\n", err) - } - - for tid, team := range teams { - if v, ok := teams_scores[tid]; !ok || v != team.Points { - treat(team, tid) - } - } -} - -type ZQDSScore struct { - Score int64 `json:"score"` -} - -func treat(team fic.ExportedTeam, tid string) { - lock.Lock() - defer lock.Unlock() - - log.Printf("Syncing score for %q: %d\n", team.Name, int64(team.Points)) - - // Save in memory what is the team's score - teams_scores[tid] = team.Points - - if u, err := url.Parse(base_URL); err != nil { - log.Println(err.Error()) - } else { - u.Path = path.Join(u.Path, "rank", "score", roundId, team.ExternalId) - - body, err := json.Marshal(ZQDSScore{int64(team.Points * 10)}) - if err != nil { - log.Println("[ERR] Unable to create JSON from Score") - return - } - - client := configOauth.Client(context.Background()) - - req, err := http.NewRequest("PUT", u.String(), bytes.NewReader(body)) - if err != nil { - log.Println("[ERR] Unable to send request: ", err.Error()) - return - } - - req.Header.Add("Content-Type", "application/json") - - resp, err := client.Do(req) - if err != nil { - log.Println("[ERR] Error during request execution: ", err.Error()) - return - } - - if resp.StatusCode != http.StatusOK { - if v, err := ioutil.ReadAll(resp.Body); err != nil { - log.Println("An error occurs when trying to send scores, then decoding response: %w", err) - } else { - log.Printf("An error occurs when trying to send scores: %s", string(v)) - } - } - } -} diff --git a/renovate.json b/renovate.json deleted file mode 100644 index 10691af3..00000000 --- a/renovate.json +++ /dev/null @@ -1,38 +0,0 @@ -{ - "$schema": "https://docs.renovatebot.com/renovate-schema.json", - "lockFileMaintenance": { - "enabled": true, - "automerge": true - }, - "regexManagers": [ - { - "fileMatch": [ - "^fickit-backend.yml$", - "^fickit-boot.yml$", - "^fickit-frontend.yml$", - "^fickit-prepare.yml$", - "^fickit-update.yml$" - ], - "matchStrings": [ - "init:(\n\s*-.*?)*\n\s*-(?.*?):(?.+)", - "\\s*-\\s+(?\\w[a-zA-Z.\\/-]*?):(?[a-zA-Z0-9.-]*?)\\n", - "image:\\s*(?\\S.*?):(?.+)" - ], - "datasourceTemplate": "docker", - "versioningTemplate": "docker" - } - ], - "packageRules": [ - { - "matchPackageNames": [ - "github.com/burntsushi/toml", - "github.com/studio-b12/gowebdav", - "golang.org/x/crypto", - "golang.org/x/image", - "golang.org/x/oauth2" - ], - "automerge": true, - "automergeType": "branch" - } - ] -} diff --git a/repochecker/.gitignore b/repochecker/.gitignore deleted file mode 100644 index ae3a90e6..00000000 --- a/repochecker/.gitignore +++ /dev/null @@ -1 +0,0 @@ -repochecker diff --git a/repochecker/epita/files.go b/repochecker/epita/files.go deleted file mode 100644 index 33cc7b47..00000000 --- a/repochecker/epita/files.go +++ /dev/null @@ -1,47 +0,0 @@ -package main - -import ( - "fmt" - "path" - "strings" - - "go.uber.org/multierr" - - "srs.epita.fr/fic-server/admin/sync" - "srs.epita.fr/fic-server/libfic" -) - -func EPITACheckFile(file *fic.EFile, _ *fic.Exercice, exceptions *sync.CheckExceptions) (errs error) { - // Enforce file format - if path.Ext(file.Name) == ".rar" || path.Ext(file.Name) == ".7z" { - errs = multierr.Append(errs, fmt.Errorf("this file use a forbidden archive type.")) - } - - // Check for stange file extension - if strings.HasSuffix(file.Name, ".tar.zip") { - errs = multierr.Append(errs, fmt.Errorf(".tar.zip is not a valid tar format")) - } - - // Check .gz files have a dedicated hash - if path.Ext(file.Name) == ".gz" && !strings.HasSuffix(file.Name, ".tar.gz") && len(file.ChecksumShown) == 0 { - errs = multierr.Append(errs, fmt.Errorf("digest of original, uncompressed, file missing in DIGESTS.txt")) - } - - // Check for huge file to compress - if file.Size > 4000000 && path.Ext(file.Name) == ".tar" { - errs = multierr.Append(errs, fmt.Errorf("archive to compress with bzip2")) - } else if file.Size > 40000000 && (path.Ext(file.Name) == "" || - path.Ext(file.Name) == ".csv" || - path.Ext(file.Name) == ".dump" || - path.Ext(file.Name) == ".eml" || - path.Ext(file.Name) == ".json" || - path.Ext(file.Name) == ".log" || - path.Ext(file.Name) == ".mbox" || - path.Ext(file.Name) == ".pcap" || - path.Ext(file.Name) == ".pcapng" || - path.Ext(file.Name) == ".txt") { - errs = multierr.Append(errs, fmt.Errorf("huge file to compress with gzip")) - } - - return -} diff --git a/repochecker/epita/flags.go b/repochecker/epita/flags.go deleted file mode 100644 index 94a2b8f5..00000000 --- a/repochecker/epita/flags.go +++ /dev/null @@ -1,63 +0,0 @@ -package main - -import ( - "fmt" - "strconv" - "strings" - "unicode" - - "go.uber.org/multierr" - - "srs.epita.fr/fic-server/admin/sync" - "srs.epita.fr/fic-server/libfic" -) - -func EPITACheckKeyFlag(flag *fic.FlagKey, raw string, _ *fic.Exercice, exceptions *sync.CheckExceptions) (errs error) { - if (flag.Label[0] == 'Q' || flag.Label[0] == 'q') && (flag.Label[1] == 'U' || flag.Label[1] == 'u') || - (flag.Label[0] == 'W' || flag.Label[0] == 'w') && (flag.Label[1] == 'H' || flag.Label[1] == 'h') { - errs = multierr.Append(errs, fmt.Errorf("Label should not begin with %s. This seem to be a question. Reword your label as a description of the expected flag, `:` are automatically appended.", flag.Label[0:2])) - flag.Label = flag.Label[1:] - } - - label := []rune(flag.Label) - if flag.Label[len(flag.Label)-1] != ')' && flag.Label[len(flag.Label)-1] != '©' && !unicode.IsLetter(label[len(label)-1]) && !unicode.IsDigit(label[len(label)-1]) { - errs = multierr.Append(errs, fmt.Errorf("Label should not end with punct (%q). Reword your label as a description of the expected flag, `:` are automatically appended.", flag.Label[len(flag.Label)-1])) - } - - if strings.HasPrefix(strings.ToLower(raw), "cve-") && flag.Type != "ucq" { - errs = multierr.Append(errs, fmt.Errorf("CVE numbers are required to be UCQ with choice_cost")) - } - - if _, err := strconv.ParseInt(raw, 10, 64); flag.Type == "key" && err == nil && !exceptions.HasException(":not-number-flag") { - errs = multierr.Append(errs, fmt.Errorf("shouldn't be this flag a number type? (:not-number-flag)")) - } - - if flag.Placeholder == "" && (strings.HasPrefix(flag.Type, "number") || flag.Type == "key" || flag.Type == "text" || (flag.Type == "ucq" && flag.ChoicesCost > 0)) { - errs = multierr.Append(errs, fmt.Errorf("no placeholder defined")) - } - - if strings.HasPrefix(flag.Type, "number") { - min, max, step, err := fic.AnalyzeNumberFlag(flag.Type) - if err != nil { - errs = multierr.Append(errs, err) - } else if min == nil || max == nil || step == nil { - errs = multierr.Append(errs, fmt.Errorf("please define min and max for your number flag")) - } else if (*max-*min) / *step <= 10 { - errs = multierr.Append(errs, fmt.Errorf("to avoid bruteforce, define more than 10 possibilities")) - } - } - - return -} - -func EPITACheckKeyFlagWithChoices(flag *fic.FlagKey, raw string, choices []*fic.FlagChoice, _ *fic.Exercice, exceptions *sync.CheckExceptions) (errs error) { - if !exceptions.HasException(":bruteforcable-choices") { - if len(choices) < 10 && flag.ChoicesCost == 0 { - errs = multierr.Append(errs, fmt.Errorf("requires at least 10 choices to avoid brute-force")) - } else if len(choices) < 6 && flag.ChoicesCost > 0 { - errs = multierr.Append(errs, fmt.Errorf("requires at least 10 choices to avoid brute-force")) - } - } - - return -} diff --git a/repochecker/epita/main.go b/repochecker/epita/main.go deleted file mode 100644 index 93402f6e..00000000 --- a/repochecker/epita/main.go +++ /dev/null @@ -1,11 +0,0 @@ -package main - -import ( - "srs.epita.fr/fic-server/admin/sync" -) - -func RegisterChecksHooks(h *sync.CheckHooks) { - h.RegisterFlagKeyHook(EPITACheckKeyFlag) - h.RegisterFlagKeyWithChoicesHook(EPITACheckKeyFlagWithChoices) - h.RegisterFileHook(EPITACheckFile) -} diff --git a/repochecker/file-inspector/files.go b/repochecker/file-inspector/files.go deleted file mode 100644 index 63ec71c5..00000000 --- a/repochecker/file-inspector/files.go +++ /dev/null @@ -1,23 +0,0 @@ -package main - -import ( - "path/filepath" - "strings" - - "go.uber.org/multierr" - - "srs.epita.fr/fic-server/admin/sync" - "srs.epita.fr/fic-server/libfic" -) - -func InspectFile(file *fic.EFile, _ *fic.Exercice, exceptions *sync.CheckExceptions) (errs error) { - if filepath.Ext(file.Name) == ".tar" || strings.HasSuffix(file.Name, ".tar.gz") || strings.HasSuffix(file.Name, ".tar.bz2") { - // Check there is more than 1 file in tarball - errs = multierr.Append(errs, checkTarball(file, exceptions)) - } else if filepath.Ext(file.Name) == ".zip" { - // Check there is more than 1 file in zip - errs = multierr.Append(errs, checkZip(file, exceptions)) - } - - return -} diff --git a/repochecker/file-inspector/main.go b/repochecker/file-inspector/main.go deleted file mode 100644 index 7cd9c1fd..00000000 --- a/repochecker/file-inspector/main.go +++ /dev/null @@ -1,13 +0,0 @@ -package main - -import ( - "srs.epita.fr/fic-server/admin/sync" -) - -var hooks *sync.CheckHooks - -func RegisterChecksHooks(h *sync.CheckHooks) { - hooks = h - - h.RegisterFileHook(InspectFile) -} diff --git a/repochecker/file-inspector/tarball.go b/repochecker/file-inspector/tarball.go deleted file mode 100644 index e7983150..00000000 --- a/repochecker/file-inspector/tarball.go +++ /dev/null @@ -1,72 +0,0 @@ -package main - -import ( - "archive/tar" - "compress/bzip2" - "compress/gzip" - "fmt" - "io" - "log" - "strings" - - "go.uber.org/multierr" - - "srs.epita.fr/fic-server/admin/sync" - "srs.epita.fr/fic-server/libfic" -) - -func checkTarball(file *fic.EFile, exceptions *sync.CheckExceptions) (errs error) { - fd, closer, err := sync.GetFile(sync.GlobalImporter, file.GetOrigin()) - if err != nil { - log.Printf("Unable to open %q: %s", file.GetOrigin(), err.Error()) - return - } - defer closer() - - var rd io.Reader - if strings.HasSuffix(file.Name, ".tar.gz") { - archive, err := gzip.NewReader(fd) - if err != nil { - log.Printf("Unable to uncompress gzip file %q: %s", file.Name, err.Error()) - return - } - defer archive.Close() - rd = archive - } else if strings.HasSuffix(file.Name, ".tar.bz2") { - rd = bzip2.NewReader(fd) - } else { - rd = fd - } - - nbFile := 0 - - tarrd := tar.NewReader(rd) - for { - header, err := tarrd.Next() - if err == io.EOF { - break - } else if err != nil { - log.Printf("An error occurs when analyzing the tarball %q: %s", file.Name, err.Error()) - return - } - - info := header.FileInfo() - if !info.IsDir() { - nbFile += 1 - } - } - - if nbFile < 2 { - if !exceptions.HasException(":one-file-tarball") { - errs = multierr.Append(errs, fmt.Errorf("don't make a tarball for one file")) - } - } else if nbFile < 5 && false { - if !exceptions.HasException(":few-files-tarball") { - errs = multierr.Append(errs, fmt.Errorf("don't make a tarball for so little files (:few-files-tarball)")) - } - } else { - log.Printf("%d files found in %q", nbFile, file.Name) - } - - return -} diff --git a/repochecker/file-inspector/zip.go b/repochecker/file-inspector/zip.go deleted file mode 100644 index be8d6bb9..00000000 --- a/repochecker/file-inspector/zip.go +++ /dev/null @@ -1,92 +0,0 @@ -package main - -import ( - "archive/zip" - "fmt" - "io" - "log" - "strings" - - "go.uber.org/multierr" - - "srs.epita.fr/fic-server/admin/sync" - "srs.epita.fr/fic-server/libfic" -) - -func checkZip(file *fic.EFile, exceptions *sync.CheckExceptions) (errs error) { - fd, closer, err := sync.GetFile(sync.GlobalImporter, file.GetOrigin()) - if err != nil { - log.Printf("Unable to open %q: %s", file.GetOrigin(), err.Error()) - return - } - defer closer() - - fdat, ok := fd.(io.ReaderAt) - if !ok { - log.Printf("The current Importer (%t) doesn't allow me to check the archive: %s. Please test-it yourself", sync.GlobalImporter, file.GetOrigin()) - return - } - - size, err := sync.GetFileSize(sync.GlobalImporter, file.GetOrigin()) - if err != nil { - log.Printf("Unable to calculate size of %q: %s", file.GetOrigin(), err.Error()) - return - } - - r, err := zip.NewReader(fdat, size) - if err != nil { - log.Printf("Unable to open %q: %s", file.GetOrigin(), err.Error()) - return - } - - if len(r.File) < 2 { - if !exceptions.HasException(":one-file-tarball") { - errs = multierr.Append(errs, fmt.Errorf("don't make a ZIP archive for one file, use gzip instead")) - } - } else if len(r.File) < 5 && false { - if !exceptions.HasException(":few-files-tarball") { - errs = multierr.Append(errs, fmt.Errorf("don't make a ZIP archive for so little files (:few-files-tarball)")) - } - } else { - log.Printf("%d files found in %q", len(r.File), file.Name) - } - - foundEtcDir := false - foundHomeDir := false - foundRootDir := false - foundVarDir := false - for _, file := range r.File { - if strings.HasPrefix(file.Name, "etc") { - foundEtcDir = true - } - if strings.HasPrefix(file.Name, "home") { - foundHomeDir = true - } - if strings.HasPrefix(file.Name, "root") { - foundRootDir = true - } - if strings.HasPrefix(file.Name, "var") { - foundVarDir = true - } - } - - nbLinuxDirFound := 0 - if foundEtcDir { - nbLinuxDirFound += 1 - } - if foundHomeDir { - nbLinuxDirFound += 1 - } - if foundRootDir { - nbLinuxDirFound += 1 - } - if foundVarDir { - nbLinuxDirFound += 1 - } - - if nbLinuxDirFound > 2 && !exceptions.HasException(":not-a-linux-rootfs") { - errs = multierr.Append(errs, fmt.Errorf("don't use a ZIP archive to store an Unix file system, prefer a tarball (:not-a-linux-rootfs)")) - } - - return -} diff --git a/repochecker/grammalecte/flags.go b/repochecker/grammalecte/flags.go deleted file mode 100644 index a9890a2d..00000000 --- a/repochecker/grammalecte/flags.go +++ /dev/null @@ -1,76 +0,0 @@ -package main - -import ( - "bytes" - "fmt" - "log" - "unicode" - - "go.uber.org/multierr" - - "srs.epita.fr/fic-server/admin/sync" - "srs.epita.fr/fic-server/libfic" - lib "srs.epita.fr/fic-server/repochecker/grammalecte/lib" -) - -func GrammalecteCheckKeyFlag(flag *fic.FlagKey, raw string, exercice *fic.Exercice, exceptions *sync.CheckExceptions) (errs error) { - if !isRecognizedLanguage(exercice.Language) { - return - } - - label, _, _, _ := flag.AnalyzeFlagLabel() - for _, err := range multierr.Errors(grammalecte("label ", label, -1, exceptions, &CommonOpts)) { - if e, ok := err.(lib.GrammarError); ok && e.RuleId == "poncfin_règle1" { - continue - } - - errs = multierr.Append(errs, err) - } - - // Flag help are checked through GrammalecteCheckMDText, no need to check them - - return -} - -func GrammalecteCheckFlagChoice(choice *fic.FlagChoice, exercice *fic.Exercice, exceptions *sync.CheckExceptions) (errs error) { - if isRecognizedLanguage(exercice.Language) { - errs = multierr.Append(errs, grammalecte("label ", choice.Label, -1, exceptions, &CommonOpts)) - } - - if len(multierr.Errors(errs)) == 0 && unicode.IsLetter(bytes.Runes([]byte(choice.Label))[0]) && !unicode.IsUpper(bytes.Runes([]byte(choice.Label))[0]) && !exceptions.HasException(":label_majuscule") { - errs = multierr.Append(errs, fmt.Errorf("%q nécessite une majuscule (:label_majuscule)", choice.Label)) - } - - return -} - -func GrammalecteCheckHint(hint *fic.EHint, exercice *fic.Exercice, exceptions *sync.CheckExceptions) (errs error) { - if len(hint.Title) > 0 { - if isRecognizedLanguage(exercice.Language) { - errs = multierr.Append(errs, grammalecte("title ", hint.Title, -1, exceptions, &CommonOpts)) - } - if len(multierr.Errors(errs)) == 0 && !unicode.IsUpper(bytes.Runes([]byte(hint.Title))[0]) && !exceptions.HasException(":title_majuscule") { - errs = multierr.Append(errs, fmt.Errorf("%q nécessite une majuscule (:title_majuscule)", hint.Title)) - } - } - - // Hint content are checked through GrammalecteCheckMDText, no need to check them - - return -} - -func GrammalecteCheckGrammar(data interface{}, exceptions *sync.CheckExceptions) error { - if s, ok := data.(struct { - Str string - Language string - }); ok { - if !isRecognizedLanguage(s.Language) { - return nil - } - - return grammalecte("", s.Str, 0, exceptions, &CommonOpts) - } else { - log.Printf("Unknown data given to GrammalecteCheckGrammar: %T", data) - return nil - } -} diff --git a/repochecker/grammalecte/grammalecte.go b/repochecker/grammalecte/grammalecte.go deleted file mode 100644 index eb591c7f..00000000 --- a/repochecker/grammalecte/grammalecte.go +++ /dev/null @@ -1,217 +0,0 @@ -package main - -import ( - "encoding/json" - "fmt" - "log" - "net/http" - "net/url" - "regexp" - "strings" - - "go.uber.org/multierr" - - "srs.epita.fr/fic-server/admin/sync" - lib "srs.epita.fr/fic-server/repochecker/grammalecte/lib" -) - -type GrammalecteOptions struct { - Typographie bool `json:"basic"` - SignesTypographiques bool `json:"typo"` - ApostropheTypographiques bool `json:"apos"` - EcritureEpicene bool `json:"eepi"` - EspaceSurnumeraires bool `json:"tab"` - EspaceInsecables bool `json:"nbsp"` - Majuscules bool `json:"maj"` - MajusculesPourMinisteres bool `json:"minis"` - Virgules bool `json:"virg"` - PonctuationFinale bool `json:"poncfin"` - TraitsDUnionEtSoudures bool `json:"tu"` - Nombres bool `json:"num"` - UnitesDeMesure bool `json:"unit"` - NormesFrancaises bool `json:"nf"` - LigaturesTypographiques bool `json:"liga"` - ApostropheManquate bool `json:"mapos"` - Chimie bool `json:"chim"` - ErreurDeNumerisation bool `json:"ocr"` - NomsEtAdjectifs bool `json:"gramm"` - FauxAmis bool `json:"conf"` - Locutions bool `json:"loc"` - Accords bool `json:"gn"` - Verbes bool `json:"verbs"` - Conjugaisons bool `json:"conj"` - Infinitif bool `json:"infi"` - Imperatif bool `json:"imp"` - Interrogatif bool `json:"inte"` - ParticipesPasses bool `json:"ppas"` - Verbose bool `json:"vmode"` - Style bool `json:"style"` - Populaire bool `json:"bs"` - Pleonasme bool `json:"pleo"` - ElisionEuphonie bool `json:"eleu"` - AdvNegation bool `json:"neg"` - RepetitionParag bool `json:"redon1"` - RepetitionPhrase bool `json:"redon2"` - Divers bool `json:"misc"` - MotsComposes bool `json:"mc"` - Dates bool `json:"date"` - Debug bool `json:"debug"` - IdRule bool `json:"idrule"` -} - -type GrammalecteGrammarError struct { - Start int `json:"nStart"` - End int `json:"nEnd"` - LineId string `json:"sLineId"` - RuleId string `json:"sRuleId"` - Type string `json:"sType"` - Colors []int `json:"aColor"` - Message string `json:"sMessage"` - Suggestions []string `json:"aSuggestions"` - URL string `json:"url"` -} - -type GrammalecteSpellingError struct { - I int `json:"i"` - Type string `json:"sType"` - Value string `json:"sValue"` - Start int `json:"nStart"` - End int `json:"nEnd"` -} - -type GrammalecteData struct { - Paragraph int `json:"iparagraph"` - Text string `json:"sText"` - GrammarErrors []GrammalecteGrammarError `json:"lGrammarErrors"` - SpellingErrors []GrammalecteSpellingError `json:"lSpellingErrors"` -} - -type GrammalecteResponse struct { - Program string `json:"program"` - Version string `json:"version"` - Lang string `json:"lang"` - Error string `json:"error,omitempty"` - Data []GrammalecteData `json:"data"` -} - -type GrammalecteSuggestions struct { - Suggestions []string `json:"suggestions"` -} - -func suggest(term string) (suggestions *GrammalecteSuggestions, err error) { - form := url.Values{} - form.Add("token", term) - - resp, err := http.Post(GRAMMALECTE_LOCAL_URL+"/suggest/fr", "application/x-www-form-urlencoded", strings.NewReader(form.Encode())) - if err != nil { - return nil, err - } - defer resp.Body.Close() - - dec := json.NewDecoder(resp.Body) - err = dec.Decode(&suggestions) - if err != nil { - return nil, err - } - - return -} - -var ( - mdimg = regexp.MustCompile(`!\[([^\]]+)\]\([^)]+\)`) -) - -func grammalecte(name string, text string, paragraph int, exceptions *sync.CheckExceptions, options *GrammalecteOptions) (errs error) { - // Remove Markdown elements - text = mdimg.ReplaceAllString(text, "Image : ${1}") - - form := url.Values{} - form.Add("text", text) - form.Add("tf", "on") - - if options != nil { - d, err := json.Marshal(options) - if err != nil { - log.Println("Unable to marshall GrammalecteOptions:", err.Error()) - } else { - form.Add("options", string(d)) - } - } - - resp, err := http.Post(GRAMMALECTE_LOCAL_URL+"/gc_text/fr", "application/x-www-form-urlencoded", strings.NewReader(form.Encode())) - if err != nil { - log.Println("Unable to contact grammalecte server:", err.Error()) - return - } - defer resp.Body.Close() - - var gresponse GrammalecteResponse - - dec := json.NewDecoder(resp.Body) - err = dec.Decode(&gresponse) - if err != nil { - log.Println("Unable to analyse grammalecte response: ", err.Error()) - return - } - - if len(gresponse.Error) > 0 { - log.Println("Grammalecte report an error: ", gresponse.Error) - } - - for _, data := range gresponse.Data { - for _, serror := range data.SpellingErrors { - allowed := false - for _, w := range ALLOWED_WORDS { - if w == serror.Value { - allowed = true - break - } - } - if strings.HasPrefix(serror.Value, "CVE-20") { - continue - } - if allowed || exceptions.HasException(":spelling:"+serror.Value) { - continue - } - - suggestions, _ := suggest(serror.Value) - errs = multierr.Append(errs, lib.SpellingError{ - Prefix: name, - Source: data.Text, - NSource: data.Paragraph, - Start: serror.Start, - End: serror.End, - Type: serror.Type, - Value: serror.Value, - Suggestions: suggestions.Suggestions, - }) - } - - for _, gerror := range data.GrammarErrors { - if exceptions.HasException(fmt.Sprintf(":*:%s", gerror.RuleId)) || (paragraph == 0 && exceptions.HasException(fmt.Sprintf(":%d:%s", data.Paragraph, gerror.RuleId))) || (paragraph < 0 && exceptions.HasException(fmt.Sprintf(":%s", gerror.RuleId))) || (paragraph > 0 && exceptions.HasException(fmt.Sprintf(":%d:%s", paragraph, gerror.RuleId))) { - continue - } - - err := lib.GrammarError{ - Prefix: name, - Source: data.Text, - NSource: data.Paragraph, - Start: gerror.Start, - End: gerror.End, - RuleId: gerror.RuleId, - Type: gerror.Type, - Message: gerror.Message, - Suggestions: gerror.Suggestions, - URL: gerror.URL, - } - - if err.RuleId == "mc_mot_composé" && exceptions.HasException(fmt.Sprintf(":spelling:%s", err.GetPassage())) { - continue - } - - errs = multierr.Append(errs, err) - } - } - - return -} diff --git a/repochecker/grammalecte/lib/errors.go b/repochecker/grammalecte/lib/errors.go deleted file mode 100644 index 6ceb7a8f..00000000 --- a/repochecker/grammalecte/lib/errors.go +++ /dev/null @@ -1,104 +0,0 @@ -package grammalecte - -import ( - "bytes" - "fmt" - "strings" - "unicode/utf8" -) - -const LOG_PREFIX_LEN = 20 - -type SpellingError struct { - Prefix string - Source string - NSource int - Start int - End int - Type string - Value string - Suggestions []string -} - -func (e SpellingError) Error() string { - suggestions := "" - if len(e.Suggestions) > 0 { - suggestions = "\nSuggestions : " + strings.Join(e.Suggestions, ", ") - } - - return fmt.Sprintf( - "%sspelling error %s %q (:spelling:%s)\n%q\n%s%s", - e.Prefix, - e.Type, - e.Value, - e.Value, - e.Source, - underline(1, e.Start, e.End), - suggestions, - ) -} - -type GrammarError struct { - Prefix string - Source string - NSource int - Start int - End int - RuleId string - Type string - Message string - Suggestions []string - URL string -} - -func (e GrammarError) Error() string { - sornot := "" - if len(e.Suggestions) > 1 { - sornot = "s" - } - - suggestions := "" - if len(e.Suggestions) > 0 { - suggestions = "\nSuggestion" + sornot + " : " + strings.Join(e.Suggestions, ", ") - } - - return fmt.Sprintf( - "%s%s (%d:%s)\n%q\n%s%s", - e.Prefix, - e.Message, - e.NSource, e.RuleId, - e.Source, - underline(1, e.Start, e.End), - suggestions, - ) -} - -func (e GrammarError) GetPassage() string { - nb := 0 - var ret []byte - for _, r := range e.Source { - if nb >= e.End { - break - } - if nb >= e.Start { - ret = utf8.AppendRune(ret, r) - } - nb += 1 - } - - return string(ret) -} - -func underline(prefix, start, end int) string { - var b bytes.Buffer - - for i := 0; i < prefix+start; i++ { - b.Write([]byte{' '}) - } - - for i := 0; i < end-start; i++ { - b.Write([]byte{'^'}) - } - - return b.String() -} diff --git a/repochecker/grammalecte/main.go b/repochecker/grammalecte/main.go deleted file mode 100644 index 219befc2..00000000 --- a/repochecker/grammalecte/main.go +++ /dev/null @@ -1,124 +0,0 @@ -package main - -import ( - "log" - "os" - "os/exec" - "strings" - "time" - - "srs.epita.fr/fic-server/admin/sync" -) - -const GRAMMALECTE_LOCAL_URL = "http://127.0.0.1:8080" - -var ALLOWED_WORDS = []string{ - "forensic", - "phishing", - "Phishing", - "keylogger", - "Keylogger", - "flag", - "ANSSI", - "DDOS", - "Peer-to-Peer", - "XSS", - "Moodle", - "APK", - "PCAP", - "sha256", - "sha512", - "spear-phishing", - "lags", - "latéraliser", - "root", - "overflow", - "SSH", - "ARP", - "TCP", - "UDP", - "ICMP", - "TLS", - "SSL", - "mimikatz", - "OpenSSL", - "RDP", - "AES", - "RSA", - "LFSR", - "Wireshark", - "reverse", -} - -var CommonOpts = GrammalecteOptions{ - Typographie: true, - SignesTypographiques: true, - ApostropheTypographiques: true, - EcritureEpicene: true, - EspaceSurnumeraires: true, - Majuscules: true, - Virgules: true, - PonctuationFinale: true, - TraitsDUnionEtSoudures: true, - Nombres: true, - NormesFrancaises: true, - LigaturesTypographiques: true, - ApostropheManquate: true, - Chimie: true, - NomsEtAdjectifs: true, - FauxAmis: true, - Locutions: true, - Accords: true, - Verbes: true, - Conjugaisons: true, - Infinitif: true, - Imperatif: true, - Interrogatif: true, - ParticipesPasses: true, - Style: true, - Populaire: true, - Pleonasme: true, - ElisionEuphonie: true, - AdvNegation: true, - RepetitionParag: true, - RepetitionPhrase: true, - Divers: true, - MotsComposes: true, - Dates: true, - IdRule: true, -} - -func runGrammalecteServer() error { - path := "grammalecte-server.py" - if _, err := os.Stat(path); os.IsNotExist(err) { - path = "/srv/grammalecte/grammalecte-server.py" - } - - cmd := exec.Command("python3", path) - if err := cmd.Start(); err != nil { - return err - } - - log.Println("Waiting for grammalecte server to be ready...") - time.Sleep(2000 * time.Millisecond) - - return nil -} - -func isRecognizedLanguage(lang string) bool { - // Grammalecte can only check french texts - return lang == "" || strings.HasPrefix(lang, "fr") -} - -func RegisterChecksHooks(h *sync.CheckHooks) { - if err := runGrammalecteServer(); err != nil { - log.Fatal("Unable to start grammalecte-server:", err) - } else { - h.RegisterFlagKeyHook(GrammalecteCheckKeyFlag) - h.RegisterFlagChoiceHook(GrammalecteCheckFlagChoice) - h.RegisterHintHook(GrammalecteCheckHint) - h.RegisterMDTextHook(GrammalecteCheckMDText) - - h.RegisterCustomHook("CheckGrammar", GrammalecteCheckGrammar) - } -} diff --git a/repochecker/grammalecte/markdown.go b/repochecker/grammalecte/markdown.go deleted file mode 100644 index 6831bef3..00000000 --- a/repochecker/grammalecte/markdown.go +++ /dev/null @@ -1,113 +0,0 @@ -package main - -import ( - "bytes" - "fmt" - "strings" - - "srs.epita.fr/fic-server/admin/sync" - lib "srs.epita.fr/fic-server/repochecker/grammalecte/lib" - "srs.epita.fr/fic-server/repochecker/grammalecte/void" - - "github.com/yuin/goldmark" - "github.com/yuin/goldmark/ast" - "github.com/yuin/goldmark/parser" - "github.com/yuin/goldmark/renderer" - "github.com/yuin/goldmark/text" - "github.com/yuin/goldmark/util" - "go.uber.org/multierr" -) - -func GrammalecteCheckMDText(str string, lang string, exceptions *sync.CheckExceptions, forbiddenStrings ...string) (errs error) { - if !isRecognizedLanguage(lang) { - return - } - - if exceptions != nil { - for k := range *exceptions { - tmp := strings.SplitN(k, ":", 3) - if len(tmp) == 3 && tmp[1] == "quote" { - str = strings.Replace(str, tmp[2], "une citation a été remplacée ici", -1) - } - } - } - - for _, s := range forbiddenStrings { - if strings.Contains(str, s) { - if !exceptions.HasException(":not-forbidden-string:" + s) { - errs = multierr.Append(errs, fmt.Errorf("Forbidden string %q included in file content, don't write your flag in text", s)) - } - } - } - - checker := &grammarChecker{ - exceptions: exceptions, - } - - voidRenderer := void.NewVoidRenderer() - - markdown := goldmark.New( - goldmark.WithParserOptions( - parser.WithASTTransformers( - util.Prioritized(checker, 200), - ), - ), - goldmark.WithRenderer( - renderer.NewRenderer( - renderer.WithNodeRenderers( - util.Prioritized(voidRenderer, 30), - ), - ), - ), - ) - - var buf bytes.Buffer - if err := markdown.Convert([]byte(str), &buf); err != nil { - errs = multierr.Append(errs, err) - } - - errs = multierr.Append(errs, checker.errs) - errs = multierr.Append(errs, voidRenderer.Errors()) - - for _, err := range multierr.Errors(grammalecte("", buf.String(), 0, exceptions, &CommonOpts)) { - if gerror, ok := err.(lib.GrammarError); ok { - if (gerror.RuleId == "redondances_paragraphe" || gerror.RuleId == "redondances_phrase") && gerror.GetPassage() == "SubstitutDeCode" { - continue - } - } else if serror, ok := err.(lib.SpellingError); ok { - if serror.Value == "SubstitutDeCode" { - continue - } - } - - errs = multierr.Append(errs, err) - } - - return -} - -type grammarChecker struct { - exceptions *sync.CheckExceptions - errs error -} - -func (t *grammarChecker) Transform(doc *ast.Document, reader text.Reader, pc parser.Context) { - ast.Walk(doc, func(node ast.Node, enter bool) (ast.WalkStatus, error) { - if !enter { - return ast.WalkContinue, nil - } - - switch child := node.(type) { - case *ast.Image: - if len(child.Title) > 0 { - t.errs = multierr.Append(t.errs, grammalecte("", string(child.Title), 0, t.exceptions, &CommonOpts)) - } - case *ast.Link: - if len(child.Title) > 0 { - t.errs = multierr.Append(t.errs, grammalecte("", string(child.Title), 0, t.exceptions, &CommonOpts)) - } - } - - return ast.WalkContinue, nil - }) -} diff --git a/repochecker/grammalecte/void/renderer.go b/repochecker/grammalecte/void/renderer.go deleted file mode 100644 index 9aaaa38d..00000000 --- a/repochecker/grammalecte/void/renderer.go +++ /dev/null @@ -1,128 +0,0 @@ -package void - -import ( - "bytes" - "fmt" - - "github.com/yuin/goldmark/ast" - goldrender "github.com/yuin/goldmark/renderer" - "github.com/yuin/goldmark/util" - "go.uber.org/multierr" -) - -type VoidRenderer struct { - errs error -} - -func NewVoidRenderer() *VoidRenderer { - return &VoidRenderer{} -} - -// RegisterFuncs implements NodeRenderer.RegisterFuncs . -func (r *VoidRenderer) RegisterFuncs(reg goldrender.NodeRendererFuncRegisterer) { - reg.Register(ast.KindParagraph, r.renderParagraph) - reg.Register(ast.KindAutoLink, r.renderAutoLink) - reg.Register(ast.KindCodeSpan, r.renderCodeSpan) - reg.Register(ast.KindRawHTML, r.renderRawHTML) - reg.Register(ast.KindImage, r.renderImage) - reg.Register(ast.KindText, r.renderText) - reg.Register(ast.KindString, r.renderString) -} - -func (r *VoidRenderer) Errors() error { - return r.errs -} - -func (r *VoidRenderer) renderParagraph(w util.BufWriter, source []byte, n ast.Node, entering bool) (ast.WalkStatus, error) { - if !entering { - w.WriteString("\n") - } - - return ast.WalkContinue, nil -} - -func (r *VoidRenderer) renderAutoLink(w util.BufWriter, source []byte, node ast.Node, entering bool) (ast.WalkStatus, error) { - if !entering { - return ast.WalkContinue, nil - } - - _, _ = w.WriteString(`lien hypertexte`) - return ast.WalkContinue, nil -} - -func (r *VoidRenderer) renderCodeSpan(w util.BufWriter, source []byte, n ast.Node, entering bool) (ast.WalkStatus, error) { - if !entering { - return ast.WalkContinue, nil - } - - _, _ = w.WriteString(`SubstitutDeCode`) - return ast.WalkSkipChildren, nil -} - -func (r *VoidRenderer) renderRawHTML(w util.BufWriter, source []byte, node ast.Node, entering bool) (ast.WalkStatus, error) { - if !entering { - return ast.WalkContinue, nil - } - - _, _ = w.WriteString(`bloc HTML remplacé`) - return ast.WalkSkipChildren, nil -} - -func (r *VoidRenderer) renderImage(w util.BufWriter, source []byte, node ast.Node, entering bool) (ast.WalkStatus, error) { - if !entering { - _, _ = w.WriteString(`.`) - return ast.WalkContinue, nil - } - n := node.(*ast.Image) - - // Check there is a correct image alt - alt := nodeToText(n, source) - if len(bytes.Fields(alt)) <= 1 { - r.errs = multierr.Append(r.errs, fmt.Errorf("No valid image alternative defined for %q", n.Destination)) - return ast.WalkContinue, nil - } - - return ast.WalkContinue, nil -} - -func nodeToText(n ast.Node, source []byte) []byte { - var buf bytes.Buffer - for c := n.FirstChild(); c != nil; c = c.NextSibling() { - if s, ok := c.(*ast.String); ok && s.IsCode() { - buf.Write(s.Text(source)) - } else if !c.HasChildren() { - buf.Write(util.EscapeHTML(c.Text(source))) - } else { - buf.Write(nodeToText(c, source)) - } - } - return buf.Bytes() -} - -func (r *VoidRenderer) renderText(w util.BufWriter, source []byte, node ast.Node, entering bool) (ast.WalkStatus, error) { - if !entering { - return ast.WalkContinue, nil - } - - n := node.(*ast.Text) - segment := n.Segment - w.Write(segment.Value(source)) - w.WriteString(" ") - - return ast.WalkContinue, nil -} - -func (r *VoidRenderer) renderString(w util.BufWriter, source []byte, node ast.Node, entering bool) (ast.WalkStatus, error) { - if !entering { - return ast.WalkContinue, nil - } - - n := node.(*ast.String) - if n.IsCode() { - w.Write(n.Value) - } else { - w.Write(n.Value) - w.WriteString("\n") - } - return ast.WalkContinue, nil -} diff --git a/repochecker/main.go b/repochecker/main.go deleted file mode 100644 index de6e1c57..00000000 --- a/repochecker/main.go +++ /dev/null @@ -1,322 +0,0 @@ -package main - -import ( - "bufio" - "bytes" - "errors" - "flag" - "fmt" - "io" - "log" - "os" - "os/exec" - "path" - "path/filepath" - "strconv" - "strings" - - "go.uber.org/multierr" - - "srs.epita.fr/fic-server/admin/sync" - "srs.epita.fr/fic-server/libfic" -) - -var ( - ignoreBinaryFileUnder = 1500000 - skipFileChecks = false - skipBinaryFileCheck = false - logMissingResolution = false -) - -func formatFileSize(size int) string { - if size > 1000000000000 { - return fmt.Sprintf("%.1f TiB", float64(size)/float64(1<<40)) - } else if size > 1000000000 { - return fmt.Sprintf("%.1f GiB", float64(size)/float64(1<<30)) - } else if size > 1000000 { - return fmt.Sprintf("%.1f MiB", float64(size)/float64(1<<20)) - } else if size > 1000 { - return fmt.Sprintf("%.1f KiB", float64(size)/float64(1<<10)) - } - return fmt.Sprintf("%d B", size) -} - -func searchBinaryInGit(edir string) (ret []string) { - // Check if git exists and if we are in a git repo - err := exec.Command("git", "-C", edir, "remote").Run() - - if err == nil { - cmd := exec.Command("git", "-C", edir, "log", "--all", "--numstat", "--no-renames", "-z") - var out bytes.Buffer - cmd.Stdout = &out - err := cmd.Run() - - if err == nil { - alreadySeen := map[string]string{} - commit := "" - - scanner := bufio.NewScanner(&out) - // Split on \n and \0 (-z option) - scanner.Split(func(data []byte, atEOF bool) (advance int, token []byte, err error) { - for i := 0; i < len(data); i++ { - if data[i] == '\n' || data[i] == '\000' { - return i + 1, data[:i], nil - } - } - if !atEOF { - return 0, nil, nil - } - return 0, data, bufio.ErrFinalToken - }) - - for scanner.Scan() { - if strings.HasPrefix(scanner.Text(), "commit ") { - commit = strings.TrimPrefix(scanner.Text(), "commit ") - } else if strings.HasPrefix(scanner.Text(), "-\t-\t") { - fname := strings.TrimPrefix(scanner.Text(), "-\t-\t") - - cmdfile := exec.Command("git", "-C", edir, "ls-tree", "-r", "-l", commit, fname) - var outfile bytes.Buffer - cmdfile.Stdout = &outfile - err = cmdfile.Run() - var fsize int = -1024 - if err == nil { - fields := strings.Fields(outfile.String()) - if len(fields) < 4 { - // This should be a file deletion - if _, ok := alreadySeen[fname]; !ok { - alreadySeen[fname] = fmt.Sprintf("%s (commit %s) deleted", fname, commit[:7]) - } - continue - } else if fsize, err = strconv.Atoi(fields[3]); err == nil && fsize < ignoreBinaryFileUnder { - if _, ok := alreadySeen[fname]; !ok || skipBinaryFileCheck { - continue - } - } else if _, ok := alreadySeen[fname]; !ok && skipBinaryFileCheck { - alreadySeen[fname] = fmt.Sprintf("%s (commit %s) (size %s)", fname, commit[:7], formatFileSize(fsize)) - continue - } - } - if as, ok := alreadySeen[fname]; ok && as != "" { - ret = append(ret, as) - alreadySeen[fname] = "" - } - ret = append(ret, fmt.Sprintf("%s (commit %s) (size %s)", fname, commit[:7], formatFileSize(fsize))) - } - } - } - } - - return -} - -func checkExercice(theme *fic.Theme, edir string, dmap *map[int64]*fic.Exercice, exceptions *sync.CheckExceptions) (errs error) { - e, _, eid, exceptions, _, berrs := sync.BuildExercice(sync.GlobalImporter, theme, path.Join(theme.Path, edir), dmap, nil) - errs = multierr.Append(errs, berrs) - - if e != nil { - // Files - var files []string - var cerrs error - if !skipFileChecks { - files, cerrs = sync.CheckExerciceFiles(sync.GlobalImporter, e, exceptions) - log.Printf("%d files checked.\n", len(files)) - } else { - files, cerrs = sync.CheckExerciceFilesPresence(sync.GlobalImporter, e) - log.Printf("%d files presents but not checked (please check digest yourself).\n", len(files)) - } - errs = multierr.Append(errs, cerrs) - - // Flags - flags, cerrs := sync.CheckExerciceFlags(sync.GlobalImporter, e, files, exceptions) - errs = multierr.Append(errs, cerrs) - log.Printf("%d flags checked.\n", len(flags)) - - // Hints - hints, cerrs := sync.CheckExerciceHints(sync.GlobalImporter, e, exceptions) - errs = multierr.Append(errs, cerrs) - log.Printf("%d hints checked.\n", len(hints)) - - if dmap != nil { - (*dmap)[int64(eid)] = e - } - } - return -} - -func main() { - cloudDAVBase := "" - cloudUsername := "fic" - cloudPassword := "" - localImporterDirectory := "" - checkplugins := sync.CheckPluginList{} - - // 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.BoolVar(&sync.AllowWIPExercice, "allow-wip-exercices", sync.AllowWIPExercice, "Are WIP exercice allowed?") - 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(&skipFileChecks, "skipfiledigests", skipFileChecks, "Don't perform DIGESTS checks on file to speed up the checks") - flag.BoolVar(&logMissingResolution, "skipresolution", logMissingResolution, "Don't fail if resolution.mp4 is absent") - flag.BoolVar(&skipBinaryFileCheck, "skip-binary-file", skipBinaryFileCheck, "In Git-LFS check, don't warn files") - flag.IntVar(&ignoreBinaryFileUnder, "skip-binary-files-under", ignoreBinaryFileUnder, "In Git-LFS check, don't warn files under this size") - 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() - - // 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 - } - - // Don't write any files - sync.SetWriteFileFunc(func(dest string) (io.WriteCloser, error) { return &nullFileWriter{}, nil }) - - if sync.GlobalImporter != nil { - log.Println("Using", sync.GlobalImporter.Kind()) - - if themes, err := sync.GetThemes(sync.GlobalImporter); err != nil { - log.Fatal(err) - } else if len(flag.Args()) == 0 { - log.Println("Existing themes:") - for _, th := range themes { - log.Println("-", th) - } - os.Exit(1) - } - } else if len(flag.Args()) == 0 { - log.Fatal("No importer nor path given!") - } - - // 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) - } - } - - // Variable that handles the exit status - hasError := false - - 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: path.Dir(p), - Symlink: true, - } - p = path.Base(p) - } - - nberr := 0 - theme, exceptions, errs := sync.BuildTheme(sync.GlobalImporter, p) - - if theme != nil && !sync.GlobalImporter.Exists(path.Join(p, "challenge.toml")) && !sync.GlobalImporter.Exists(path.Join(p, "challenge.txt")) { - thiserrors := multierr.Errors(errs) - nberr += len(thiserrors) - for _, err := range thiserrors { - log.Println(err) - } - - exercices, err := sync.GetExercices(sync.GlobalImporter, theme) - if err != nil { - nberr += 1 - log.Println(err) - continue - } - - dmap := map[int64]*fic.Exercice{} - - for _, edir := range exercices { - ex_exceptions := exceptions.GetFileExceptions(edir) - - for _, err := range multierr.Errors(checkExercice(theme, edir, &dmap, ex_exceptions)) { - log.Println(err.Error()) - - if logMissingResolution { - if e, ok := err.(*sync.ExerciceError); ok { - if errors.Is(e.GetError(), sync.ErrResolutionNotFound) { - continue - } - } - } - - nberr += 1 - } - log.Printf("================================== Exercice %q treated\n", edir) - } - - bfile := searchBinaryInGit(path.Join(sync.GlobalImporter.(sync.LocalImporter).Base, p)) - if len(bfile) > 0 { - fmt.Printf("\n") - log.Println("There are some binary files in your git repository, they HAVE TO use LFS instead:") - for _, f := range bfile { - log.Println(" -", f) - } - } - - fmt.Printf("\n") - log.Printf("Theme %q treated. %d error(s) reported.\n\n", p, nberr) - } else { - log.Printf("This is not a theme directory, run checks for exercice.\n\n") - - for _, err := range multierr.Errors(checkExercice(&fic.Theme{}, p, &map[int64]*fic.Exercice{}, nil)) { - nberr += 1 - log.Println(err) - } - - bfile := searchBinaryInGit(path.Join(sync.GlobalImporter.(sync.LocalImporter).Base, p)) - if len(bfile) > 0 { - fmt.Printf("\n") - log.Println("There are some binary files in your git repository, they HAVE TO use LFS instead:") - for _, f := range bfile { - if strings.HasPrefix(f, p) { - log.Println(" -", f) - } - } - } - - fmt.Printf("\n") - log.Printf("Exercice %q treated. %d error(s) reported.\n", p, nberr) - } - - if nberr > 0 { - hasError = true - } - } - - if hasError { - os.Exit(1) - } -} diff --git a/repochecker/null.go b/repochecker/null.go deleted file mode 100644 index e618c541..00000000 --- a/repochecker/null.go +++ /dev/null @@ -1,11 +0,0 @@ -package main - -type nullFileWriter struct{} - -func (fw *nullFileWriter) Write(p []byte) (int, error) { - return len(p), nil -} - -func (fw *nullFileWriter) Close() error { - return nil -} diff --git a/repochecker/pcap-inspector/README.md b/repochecker/pcap-inspector/README.md deleted file mode 100644 index 7a1c1217..00000000 --- a/repochecker/pcap-inspector/README.md +++ /dev/null @@ -1,18 +0,0 @@ -# PCAP-INSPECTOR - -Inspects pcap and pcapng files for packets with ip src and ip dst using private IPs - -Set `VERBOSE_PCAP_CHECK` environment variable to enable verbose mode - -## Build library - -go build -o pcap-inspector -buildmode=plugin . - -## Requirement - -github.com/google/gopacket - -## TODO - -Custom rules on packet filtering -Handle log files diff --git a/repochecker/pcap-inspector/files.go b/repochecker/pcap-inspector/files.go deleted file mode 100644 index da99949c..00000000 --- a/repochecker/pcap-inspector/files.go +++ /dev/null @@ -1,144 +0,0 @@ -package main - -import ( - "log" - "net" - "os" - "path/filepath" - "time" - - "github.com/google/gopacket" - layers "github.com/google/gopacket/layers" - "github.com/google/gopacket/pcapgo" - "srs.epita.fr/fic-server/admin/sync" - fic "srs.epita.fr/fic-server/libfic" -) - -// This interface abstract pcap and pcapng data acquisition -// pcapgo.Reader and pcapgo.NgReader both use signature : -// ReadPacketData() (data []byte, ci gopacket.CaptureInfo, err error) -type PcapPacketDataReader interface { - ReadPacketData() (data []byte, ci gopacket.CaptureInfo, err error) -} - -// Wrap pcago.Reader as we can't impl its interface outside its package -type PcapReader struct { - *pcapgo.Reader -} - -// Wrap pcago.NgReader as we can't impl its interface outside its package -type PcapNgReader struct { - *pcapgo.NgReader -} - -// Impl interface for reading pcap and pcapng data -func (pcapReader *PcapReader) ReadPacketData() (data []byte, ci gopacket.CaptureInfo, err error) { - return pcapReader.Reader.ReadPacketData() -} - -func (pcapNGReader *PcapNgReader) ReadPacketData() (data []byte, ci gopacket.CaptureInfo, err error) { - return pcapNGReader.NgReader.ReadPacketData() -} - -// Iterate thought each packet to find potentialy unwanted packets -// TODO: Allow custom rules to specify what is a unwanted packet -func CheckPcap(pcapReader PcapPacketDataReader, pcapName string) (errs error) { - warningFlows := make(map[gopacket.Flow]([]time.Time)) - - // - // Handle packets from network layer section - // - data, ci, err := pcapReader.ReadPacketData() - for ; err == nil; data, ci, err = pcapReader.ReadPacketData() { - - packet := gopacket.NewPacket(data, layers.LayerTypeEthernet, gopacket.Default) - packetNetworkLayer := packet.NetworkLayer() - - // No network layer - if packetNetworkLayer == nil { - continue - } - - flow := packetNetworkLayer.NetworkFlow() - - ENDPOINT_SELECTION: - switch flow.EndpointType() { - - case layers.EndpointIPv4, layers.EndpointIPv6: - src, dst := flow.Endpoints() - - if net.ParseIP(src.String()).IsPrivate() && - net.ParseIP(dst.String()).IsPrivate() { - - warningFlows[flow] = append(warningFlows[flow], ci.Timestamp) - } - - // Really ? - default: - break ENDPOINT_SELECTION - } - } - - if os.Getenv("VERBOSE_PCAP_CHECK") != "" { - for flow, timestamps := range warningFlows { - log.Printf( - "(%s) Found communication between two private IPs (%s => %s) at timestamps:", - pcapName, - flow.Src().String(), - flow.Dst().String()) - - for _, timestamp := range timestamps { - log.Printf("\t%s", timestamp) - } - } - } - - if len(warningFlows) > 0 { - log.Printf("/!\\ %s: Found %d endpoints communicating with private IPs \n => Set VERBOSE_PCAP_CHECK env variable for details", - pcapName, - len(warningFlows)) - } else { - log.Printf("%s: No endpoints communicating with private IPs \n", - pcapName) - } - - return -} - -func CheckTextFile(fd *os.File) (errs error) { - return -} - -func InspectFileForIPAddr(file *fic.EFile, _ *fic.Exercice, exceptions *sync.CheckExceptions) (errs error) { - fd, closer, err := sync.GetFile(sync.GlobalImporter, file.GetOrigin()) - if err != nil { - log.Printf("Unable to open %q: %s", file.GetOrigin(), err.Error()) - return - } - defer closer() - - switch filepath.Ext(file.Name) { - case ".pcap": - pcapReader, err := pcapgo.NewReader(fd) - if err != nil { - log.Printf("Unable to load the pcap file, please check if it is a real pcap file") - return - } - - errs = CheckPcap(&PcapReader{pcapReader}, file.Name) - - case ".pcapng": - pcapNgReader, err := pcapgo.NewNgReader(fd, pcapgo.DefaultNgReaderOptions) - if err != nil { - log.Printf("Unable to load the pcapng file, please check if it is a real pcapng file") - return - } - errs = CheckPcap(&PcapNgReader{pcapNgReader}, file.Name) - - default: - return - - } - - return -} diff --git a/repochecker/pcap-inspector/main.go b/repochecker/pcap-inspector/main.go deleted file mode 100644 index 93e9f6f8..00000000 --- a/repochecker/pcap-inspector/main.go +++ /dev/null @@ -1,9 +0,0 @@ -package main - -import ( - "srs.epita.fr/fic-server/admin/sync" -) - -func RegisterChecksHooks(h *sync.CheckHooks) { - h.RegisterFileHook(InspectFileForIPAddr) -} diff --git a/repochecker/update.go b/repochecker/update.go deleted file mode 100644 index d8624342..00000000 --- a/repochecker/update.go +++ /dev/null @@ -1,32 +0,0 @@ -//go:build checkupdate -// +build checkupdate - -package main - -import ( - "encoding/json" - "io" - "log" - "net/http" -) - -const version = 12 - -func init() { - go checkUpdate() -} - -func checkUpdate() { - res, err := http.Get("https://fic.srs.epita.fr/repochecker.version") - if err == nil { - defer res.Body.Close() - dec := json.NewDecoder(res.Body) - - var v int - if err := dec.Decode(&v); err == io.EOF || err == nil { - if v > version { - log.Println("Your repochecker version is outdated, please update it:\n https://fic.srs.epita.fr/fic-binaries/") - } - } - } -} diff --git a/repochecker/videos/main.go b/repochecker/videos/main.go deleted file mode 100644 index e6c711c0..00000000 --- a/repochecker/videos/main.go +++ /dev/null @@ -1,26 +0,0 @@ -package main - -import ( - "fmt" - - "go.uber.org/multierr" - - "srs.epita.fr/fic-server/admin/sync" - "srs.epita.fr/fic-server/libfic" -) - -var hooks *sync.CheckHooks - -func RegisterChecksHooks(h *sync.CheckHooks) { - hooks = h - - h.RegisterExerciceHook(CheckResolutionVideo) -} - -func CheckResolutionVideo(e *fic.Exercice, exceptions *sync.CheckExceptions) (errs error) { - for _, err := range multierr.Errors(checkResolutionVideo(e, exceptions)) { - errs = multierr.Append(errs, fmt.Errorf("resolution.mp4: %w", err)) - } - - return -} diff --git a/repochecker/videos/subtitles.go b/repochecker/videos/subtitles.go deleted file mode 100644 index fa92e0bf..00000000 --- a/repochecker/videos/subtitles.go +++ /dev/null @@ -1,51 +0,0 @@ -package main - -import ( - "fmt" - "io/ioutil" - "log" - "os" - "strings" - - "github.com/asticode/go-astisub" - ffmpeg "github.com/u2takey/ffmpeg-go" - "go.uber.org/multierr" - - "srs.epita.fr/fic-server/admin/sync" -) - -func CheckGrammarSubtitleTrack(path string, index uint, lang string, exceptions *sync.CheckExceptions) (errs error) { - tmpfile, err := ioutil.TempFile("", "resolution-*.srt") - if err != nil { - errs = multierr.Append(errs, fmt.Errorf("unable to create a temporary file: %w", err)) - return - } - defer os.Remove(tmpfile.Name()) - - // Extract subtitles - err = ffmpeg.Input(path). - Output(tmpfile.Name(), ffmpeg.KwArgs{"map": fmt.Sprintf("0:%d", index)}). - OverWriteOutput().Run() - if err != nil { - errs = multierr.Append(errs, fmt.Errorf("ffmpeg returns an error when extracting subtitles track: %w", err)) - } - - subtitles, err := astisub.OpenFile(tmpfile.Name()) - if err != nil { - log.Println("Unable to open subtitles file:", err) - return - } - - var lines []string - for _, item := range subtitles.Items { - lines = append(lines, item.String()) - } - for _, e := range multierr.Errors(hooks.CallCustomHook("CheckGrammar", struct { - Str string - Language string - }{Str: strings.Join(lines, "\n"), Language: lang[:2]}, exceptions)) { - errs = multierr.Append(errs, fmt.Errorf("subtitle-track: %w", e)) - } - - return -} diff --git a/repochecker/videos/video.go b/repochecker/videos/video.go deleted file mode 100644 index ca34398d..00000000 --- a/repochecker/videos/video.go +++ /dev/null @@ -1,169 +0,0 @@ -package main - -import ( - "encoding/json" - "fmt" - "log" - "net/url" - "strconv" - - ffmpeg "github.com/u2takey/ffmpeg-go" - "go.uber.org/multierr" - - "srs.epita.fr/fic-server/admin/sync" - "srs.epita.fr/fic-server/libfic" -) - -type VideoInfo struct { - Streams []struct { - Index uint `json:"index"` - CodecType string `json:"codec_type"` - CodecName string `json:"codec_name"` - CodecLongName string `json:"codec_long_name"` - Duration string - NbFrames string `json:"nb_frames"` - Width int - Height int - Tags map[string]string `json:"tags"` - } `json:"streams"` -} - -func gcd(a, b int) int { - var bgcd func(a, b, res int) int - - bgcd = func(a, b, res int) int { - switch { - case a == b: - return res * a - case a%2 == 0 && b%2 == 0: - return bgcd(a/2, b/2, 2*res) - case a%2 == 0: - return bgcd(a/2, b, res) - case b%2 == 0: - return bgcd(a, b/2, res) - case a > b: - return bgcd(a-b, b, res) - default: - return bgcd(a, b-a, res) - } - } - return bgcd(a, b, 1) -} - -func checkResolutionVideo(e *fic.Exercice, exceptions *sync.CheckExceptions) (errs error) { - i, ok := sync.GlobalImporter.(sync.LocalImporter) - if !ok { - log.Printf("Unable to load `videos-rules.so` as the current Importer is not a LocalImporter (%T).", sync.GlobalImporter) - return - } - - if len(e.VideoURI) == 0 { - return - } - - // Filter exceptions to only keep related to resolution.mp4 - exceptions = exceptions.GetFileExceptions("resolution.mp4") - - path, err := url.PathUnescape(e.VideoURI[9:]) - if err != nil { - path = e.VideoURI - } - path = i.GetLocalPath(path) - - data, err := ffmpeg.Probe(path) - if err != nil { - errs = multierr.Append(errs, fmt.Errorf("unable to open %q: %w", path, err)) - return - } - - vInfo := &VideoInfo{} - err = json.Unmarshal([]byte(data), vInfo) - if err != nil { - panic(err) - } - - video_seen := []int{} - subtitles_seen := []int{} - for idx, s := range vInfo.Streams { - if s.CodecType == "video" { - video_seen = append(video_seen, idx) - if (s.Width > 1920 || s.Height > 1080) && !exceptions.HasException(":size:above_maximum") { - errs = multierr.Append(errs, fmt.Errorf("video track is too wide: %dx%d (maximum allowed: 1920x1080)", s.Width, s.Height)) - } - - ratio := s.Width * 10 / s.Height - if ratio < 13 || ratio > 19 && !exceptions.HasException(":size:strange_ratio") { - m := gcd(s.Width, s.Height) - errs = multierr.Append(errs, fmt.Errorf("video track has a strange ratio: %d:%d. Is this really expected?", s.Width/m, s.Height/m)) - } - - if s.CodecName != "h264" { - errs = multierr.Append(errs, fmt.Errorf("video codec has to be H264 (currently: %s)", s.CodecLongName)) - } - - duration, err := strconv.ParseFloat(s.Duration, 64) - if err == nil { - if duration < 45 && !exceptions.HasException(":duration:too_short") { - errs = multierr.Append(errs, fmt.Errorf("video is too short")) - } - if duration > 450 && !exceptions.HasException(":duration:too_long") { - errs = multierr.Append(errs, fmt.Errorf("video is too long")) - } - } else { - errs = multierr.Append(errs, fmt.Errorf("invalid track duration: %q", s.Duration)) - } - } else if s.CodecType == "subtitle" { - subtitles_seen = append(subtitles_seen, idx) - - if s.CodecName != "mov_text" { - errs = multierr.Append(errs, fmt.Errorf("subtitle format has to be MOV text/3GPP Timed Text (currently: %s)", s.CodecLongName)) - } - - nbframes, err := strconv.ParseInt(s.NbFrames, 10, 64) - if err == nil { - if nbframes < 5 && !exceptions.HasException(":subtitle:tiny") { - errs = multierr.Append(errs, fmt.Errorf("too few subtitles")) - } - } else { - errs = multierr.Append(errs, fmt.Errorf("invalid number of frame: %q", s.NbFrames)) - } - } else if s.CodecType == "audio" { - if !exceptions.HasException(":audio:allowed") { - errs = multierr.Append(errs, fmt.Errorf("an audio track is present, use subtitle for explainations")) - } - - if s.CodecName != "aac" { - errs = multierr.Append(errs, fmt.Errorf("audio codec has to be AAC (Advanced Audio Coding) (currently: %s)", s.CodecLongName)) - } - } else { - errs = multierr.Append(errs, fmt.Errorf("unknown track found of type %q", s.CodecType)) - } - } - - if len(video_seen) == 0 { - errs = multierr.Append(errs, fmt.Errorf("no video track found")) - } else if len(video_seen) > 1 { - errs = multierr.Append(errs, fmt.Errorf("%d video tracks found, is it expected?", len(video_seen))) - } - if len(subtitles_seen) == 0 && !exceptions.HasException(":subtitle:no_track") { - errs = multierr.Append(errs, fmt.Errorf("no subtitles track found")) - } else if len(subtitles_seen) > 0 { - for _, idx := range subtitles_seen { - language := e.Language - if lang, ok := vInfo.Streams[idx].Tags["language"]; e.Language != "" && (!ok || lang == "" || lang == "und") { - errs = multierr.Append(errs, fmt.Errorf("subtitles track %d with no language defined", vInfo.Streams[idx].Index)) - } else { - language = lang - } - - errs = multierr.Append(errs, CheckGrammarSubtitleTrack(path, vInfo.Streams[idx].Index, language, exceptions)) - } - - if e.Language != "" && len(subtitles_seen) < 2 { - errs = multierr.Append(errs, fmt.Errorf("subtitle tracks must exist in original language and translated, only one subtitle track found")) - } - - } - - return -} diff --git a/settings/challenge.go b/settings/challenge.go deleted file mode 100644 index fdc32e6b..00000000 --- a/settings/challenge.go +++ /dev/null @@ -1,74 +0,0 @@ -package settings - -import ( - "encoding/json" - "os" - "strings" -) - -// ChallengeFile is the expected name of the file containing the challenge infos. -const ChallengeFile = "challenge.json" - -type ChallengePartner struct { - Src string `json:"img"` - Alt string `json:"alt,omitempty"` - Href string `json:"href,omitempty"` -} - -// ChallengeInfo stores common descriptions and informations about the challenge. -type ChallengeInfo struct { - // Title is the displayed name of the challenge. - Title string `json:"title"` - // SubTitle is appended to the title. - SubTitle string `json:"subtitle,omitempty"` - // Authors is the group name of people making the challenge. - Authors string `json:"authors"` - // ExpectedDuration is the duration (in minutes) suggested when stating the challenge. - ExpectedDuration uint `json:"duration"` - // VideoLink is the link to explaination videos when the challenge is over. - VideosLink string `json:"videoslink,omitempty"` - - // Description gives an overview of the challenge. - Description string `json:"description,omitempty"` - // Rules tell the player some help. - Rules string `json:"rules,omitempty"` - // YourMission is a small introduction to understand the goals. - YourMission string `json:"your_mission,omitempty"` - - // MainLogo stores path to logos displayed in the header. - MainLogo []string `json:"main_logo,omitempty"` - // MainLink stores link to the parent website. - MainLink string `json:"main_link,omitempty"` - // DashboardBackground stores path to the background used on the public dashboard. - DashboardBackground string `json:"dashboard_background,omitempty"` - // Partners holds the challenge partners list. - Partners []ChallengePartner `json:"partners,omitempty"` -} - -// ReadChallenge parses the file at the given location. -func ReadChallengeInfo(content string) (*ChallengeInfo, error) { - var s ChallengeInfo - jdec := json.NewDecoder(strings.NewReader(content)) - - if err := jdec.Decode(&s); err != nil { - return &s, err - } - - return &s, nil -} - -// SaveChallenge saves challenge at the given location. -func SaveChallengeInfo(path string, s *ChallengeInfo) 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 - } -} diff --git a/settings/diff.go b/settings/diff.go deleted file mode 100644 index 5e4296f2..00000000 --- a/settings/diff.go +++ /dev/null @@ -1,133 +0,0 @@ -package settings - -import ( - "encoding/json" - "fmt" - "os" - "path" - "reflect" - "strconv" - "strings" - "time" -) - -// DiffSettings returns only the fields that differs between the two objects. -func DiffSettings(old, new *Settings) (ret map[string]interface{}) { - ret = map[string]interface{}{} - for _, field := range reflect.VisibleFields(reflect.TypeOf(*old)) { - if !reflect.DeepEqual(reflect.ValueOf(*old).FieldByName(field.Name).Interface(), reflect.ValueOf(*new).FieldByName(field.Name).Interface()) { - name := field.Name - - if tag, ok := field.Tag.Lookup("json"); ok { - name = strings.Split(tag, ",")[0] - } - - ret[name] = reflect.ValueOf(*new).FieldByName(field.Name).Interface() - } - } - - return -} - -type NextSettingsFile struct { - Id int64 `json:"id"` - Date time.Time `json:"date"` - Values map[string]interface{} `json:"values"` -} - -func (nsf *NextSettingsFile) GetId() int64 { - return nsf.Id -} - -func (nsf *NextSettingsFile) GetDate() *time.Time { - return &nsf.Date -} - -func ReadNextSettingsFile(filename string, ts int64) (*NextSettingsFile, error) { - fd, err := os.Open(filename) - if err != nil { - return nil, fmt.Errorf("unable to open(%s): %w", filename, err) - } else { - defer fd.Close() - - var s map[string]interface{} - - jdec := json.NewDecoder(fd) - - if err := jdec.Decode(&s); err != nil { - return nil, fmt.Errorf("an error occurs during JSON decoding of %s: %w", filename, err) - } - - return &NextSettingsFile{ - Id: ts, - Date: time.Unix(ts, 0), - Values: s, - }, nil - } -} - -func ListNextSettingsFiles() ([]*NextSettingsFile, error) { - files, err := os.ReadDir(SettingsDir) - if err != nil { - return nil, err - } - - var ret []*NextSettingsFile - for _, file := range files { - if len(file.Name()) < 10 { - continue - } - ts, err := strconv.ParseInt(file.Name()[:10], 10, 64) - if err == nil { - nsf, err := ReadNextSettingsFile(path.Join(SettingsDir, file.Name()), ts) - if err != nil { - return nil, err - } - - ret = append(ret, nsf) - } - } - - return ret, nil -} - -func MergeNextSettingsUntil(until *time.Time) (map[string]interface{}, error) { - nsfs, err := ListNextSettingsFiles() - if err != nil { - return nil, err - } - - ret := map[string]interface{}{} - for _, nsf := range nsfs { - if until == nil || time.Unix(nsf.Id, 0).Before(*until) { - for k, v := range nsf.Values { - ret[k] = v - } - } - } - - return ret, nil -} - -func MergeSettings(current Settings, new map[string]interface{}) *Settings { - for _, field := range reflect.VisibleFields(reflect.TypeOf(current)) { - name := field.Name - - if tag, ok := field.Tag.Lookup("json"); ok { - name = strings.Split(tag, ",")[0] - } - - if v, ok := new[name]; ok { - if reflect.TypeOf(v) != reflect.ValueOf(¤t).Elem().FieldByName(field.Name).Type() { - nv := reflect.New(reflect.ValueOf(¤t).Elem().FieldByName(field.Name).Type()) - mv, _ := json.Marshal(v) - json.Unmarshal(mv, nv.Interface()) - v = nv.Elem().Interface() - } - - reflect.ValueOf(¤t).Elem().FieldByName(field.Name).Set(reflect.ValueOf(v)) - } - } - - return ¤t -} diff --git a/settings/settings.go b/settings/settings.go deleted file mode 100644 index 0fe36d92..00000000 --- a/settings/settings.go +++ /dev/null @@ -1,201 +0,0 @@ -// Package settings is shared across multiple services for easy parsing and -// retrieval of the challenge settings. -package settings - -import ( - "encoding/json" - "log" - "os" - "os/signal" - "path" - "syscall" - "time" - - "gopkg.in/fsnotify.v1" -) - -// SettingsFile is the expected name of the file containing the settings. -const SettingsFile = "settings.json" - -// SettingsDir is the relative location where the SettingsFile lies. -var SettingsDir string = "./SETTINGS" - -// Settings represents the settings panel. -type Settings struct { - // WorkInProgress indicates if the current challenge is under development or if it is in production. - WorkInProgress bool `json:"wip,omitempty"` - - // Start is the departure time (expected or effective). - Start time.Time `json:"start"` - // End is the expected end time (if empty their is no end-date). - End *time.Time `json:"end,omitempty"` - // NextChangeTime is the time of the next expected reload. - NextChangeTime *time.Time `json:"nextchangetime,omitempty"` - - // FirstBlood is the coefficient applied to each first team who solve a challenge. - FirstBlood float64 `json:"firstBlood"` - // SubmissionCostBase is a complex number representing the cost of each attempts. - SubmissionCostBase float64 `json:"submissionCostBase"` - // ExerciceCurrentCoefficient is the current coefficient applied globaly to exercices. - ExerciceCurCoefficient float64 `json:"exerciceCurrentCoefficient"` - // HintCurrentCoefficient is the current coefficient applied to hint discovery. - HintCurCoefficient float64 `json:"hintCurrentCoefficient"` - // WChoiceCurCoefficient is the current coefficient applied to wanted choices. - WChoiceCurCoefficient float64 `json:"wchoiceCurrentCoefficient"` - // GlobalScoreCoefficient is a coefficient to apply on display scores, not considered bonus. - GlobalScoreCoefficient float64 `json:"globalScoreCoefficient"` - // DiscountedFactor stores the percentage of the exercice's gain lost on each validation. - DiscountedFactor float64 `json:"discountedFactor,omitempty"` - // QuestionGainRatio is the ratio given to a partially solved exercice in the final scoreboard. - QuestionGainRatio float64 `json:"questionGainRatio,omitempty"` - - // AllowRegistration permits unregistered Team to register themselves. - AllowRegistration bool `json:"allowRegistration,omitempty"` - // CanJoinTeam permits unregistered account to join an already existing team. - CanJoinTeam bool `json:"canJoinTeam,omitempty"` - // DenyTeamCreation forces unregistered account to join a team, it's not possible to create a new team. - DenyTeamCreation bool `json:"denyTeamCreation,omitempty"` - // DenyNameChange disallow Team to change their name. - DenyNameChange bool `json:"denyNameChange,omitempty"` - // IgnoreTeamMembers don't ask team to have known members. - IgnoreTeamMembers bool `json:"ignoreTeamMembers,omitempty"` - // AcceptNewIssue enables the reporting system. - AcceptNewIssue bool `json:"acceptNewIssue,omitempty"` - // QAenabled enables links to QA interface. - QAenabled bool `json:"QAenabled,omitempty"` - // CanResetProgression allows a team to reset the progress it made on a given exercice. - CanResetProgression bool `json:"canResetProgress,omitempty"` - // EnableResolutionRoute activates the route displaying resolution movies. - EnableResolutionRoute bool `json:"enableResolutionRoute,omitempty"` - // PartialValidation validates each correct given answers, don't expect Team to give all correct answer in a try. - PartialValidation bool `json:"partialValidation,omitempty"` - // UnlockedChallengeDepth don't show (or permit to solve) to team challenges they are not unlocked through dependancies. - UnlockedChallengeDepth int `json:"unlockedChallengeDepth"` - // UnlockedChallengeUpTo unlock challenge up to a given level of deps. - UnlockedChallengeUpTo int `json:"unlockedChallengeUpTo"` - // UnlockedStandaloneExercices unlock this number of standalone exercice. - UnlockedStandaloneExercices int `json:"unlockedStandaloneExercices,omitempty"` - // UnlockedStandaloneExercicesByThemeStepValidation unlock this number of standalone exercice for each theme step validated. - UnlockedStandaloneExercicesByThemeStepValidation float64 `json:"unlockedStandaloneExercicesByThemeStepValidation,omitempty"` - // UnlockedStandaloneExercicesByStandaloneExerciceValidation unlock this number of standalone exercice for each standalone exercice validated. - UnlockedStandaloneExercicesByStandaloneExerciceValidation float64 `json:"unlockedStandaloneExercicesByStandaloneExerciceValidation,omitempty"` - // SubmissionUniqueness don't count multiple times identical tries. - SubmissionUniqueness bool `json:"submissionUniqueness,omitempty"` - // CountOnlyNotGoodTries don't count as a try when one good response is given at least. - CountOnlyNotGoodTries bool `json:"countOnlyNotGoodTries,omitempty"` - // DisplayAllFlags doesn't respect the predefined constraint existing between flags. - DisplayAllFlags bool `json:"displayAllFlags,omitempty"` - // HideCaseSensitivity never tells the user if the flag is case sensitive or not. - HideCaseSensitivity bool `json:"hideCaseSensitivity,omitempty"` - // DisplayMCQBadCount activates the report of MCQ bad responses counter. - DisplayMCQBadCount bool `json:"displayMCQBadCount,omitempty"` - // EventKindness will ask browsers to delay notification interval. - EventKindness bool `json:"eventKindness,omitempty"` - // DisableSubmitButton replace button by this text (eg. scheduled updates, ...). - DisableSubmitButton string `json:"disablesubmitbutton,omitempty"` - // GlobalTopMessage display a message on top of each pages. - GlobalTopMessage string `json:"globaltopmessage,omitempty"` - // GlobalTopMessageVariant control the variant/color of the previous message. - GlobalTopMessageVariant string `json:"globaltopmessagevariant,omitempty"` - // HideHeader will hide the countdown and partners block on front pages. - HideHeader bool `json:"hide_header,omitempty"` - - // DelegatedQA contains the users allowed to perform administrative tasks on the QA platform. - DelegatedQA []string `json:"delegated_qa,omitempty"` -} - -// ExistsSettings checks if the settings file can by found at the given path. -func ExistsSettings(settingsPath string) bool { - _, err := os.Stat(settingsPath) - return !os.IsNotExist(err) -} - -// ReadSettings parses the file at the given location. -func ReadSettings(path string) (*Settings, error) { - var s Settings - if fd, err := os.Open(path); err != nil { - return nil, err - } else { - defer fd.Close() - jdec := json.NewDecoder(fd) - - if err := jdec.Decode(&s); err != nil { - return &s, err - } - - return &s, nil - } -} - -// SaveSettings saves settings at the given location. -func SaveSettings(path string, s interface{}) 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 - } -} - -// LoadAndWatchSettings is the function you are looking for! -// Giving the location and a callback, this function will first call your reload function -// before returning (if the file can be parsed); then it starts watching modifications made to -// this file. Your callback is then run each time the file is modified. -func LoadAndWatchSettings(settingsPath string, reload func(*Settings)) { - // First load of configuration if it exists - if _, err := os.Stat(settingsPath); !os.IsNotExist(err) { - if config, err := ReadSettings(settingsPath); err != nil { - log.Println("ERROR: Unable to read challenge settings:", err) - } else { - reload(config) - } - } - - // Register SIGHUP - c := make(chan os.Signal, 1) - signal.Notify(c, syscall.SIGHUP) - go func() { - for range c { - log.Println("SIGHUP received, reloading settings...") - go tryReload(settingsPath, reload) - } - }() - - // Watch the configuration file - if watcher, err := fsnotify.NewWatcher(); err != nil { - log.Fatal(err) - } else { - if err := watcher.Add(path.Dir(settingsPath)); err != nil { - log.Fatal("Unable to watch: ", path.Dir(settingsPath), ": ", err) - } - - go func() { - defer watcher.Close() - for { - select { - case ev := <-watcher.Events: - if path.Base(ev.Name) == SettingsFile && ev.Op&(fsnotify.Write|fsnotify.Create) != 0 { - log.Println("Settings file changes, reloading it!") - go tryReload(settingsPath, reload) - } - case err := <-watcher.Errors: - log.Println("watcher error:", err) - } - } - }() - } -} - -func tryReload(settingsPath string, reload func(*Settings)) { - if config, err := ReadSettings(settingsPath); err != nil { - log.Println("ERROR: Unable to read challenge settings:", err) - } else { - reload(config) - } -} diff --git a/shell.nix b/shell.nix deleted file mode 100644 index b769069b..00000000 --- a/shell.nix +++ /dev/null @@ -1,9 +0,0 @@ -(import ( - let - lock = builtins.fromJSON (builtins.readFile ./flake.lock); - in fetchTarball { - url = "https://github.com/edolstra/flake-compat/archive/${lock.nodes.flake-compat.locked.rev}.tar.gz"; - sha256 = lock.nodes.flake-compat.locked.narHash; } -) { - src = ./.; -}).shellNix diff --git a/udev/1-usb.rules b/udev/1-usb.rules deleted file mode 100644 index 8e8c6c74..00000000 --- a/udev/1-usb.rules +++ /dev/null @@ -1 +0,0 @@ -ACTION=="add",SUBSYSTEMS=="usb",ATTR{partition}=="1",ATTRS{idVendor}=="1f75", ATTRS{idProduct}=="0917",SYMLINK+="fickey",RUN+="/root/udev-fic/run.sh" diff --git a/udev/README.txt b/udev/README.txt deleted file mode 100644 index fdcbd0e7..00000000 --- a/udev/README.txt +++ /dev/null @@ -1,9 +0,0 @@ -Bienvenue au challenge forensic 2019 de l'EPITA ! - -Commencez par vous connecter au réseau filaire afin d'obtenir une IP. Vous -n'aurez pas besoin d'être connecté au WiFi en parallèle pour accéder à -Internet. - -Rendez-vous ensuite sur https://fic.srs.epita.fr/ pour commencer le challenge. - -Bon courage ! diff --git a/udev/run.sh b/udev/run.sh deleted file mode 100755 index 4350c28d..00000000 --- a/udev/run.sh +++ /dev/null @@ -1,2 +0,0 @@ -#!/bin/sh -/bin/sh /root/udev-fic/setup_keys.sh 2>&1 $DEVNAME >> /root/udev-fic/log diff --git a/udev/setup_keys.sh b/udev/setup_keys.sh deleted file mode 100755 index b74a5121..00000000 --- a/udev/setup_keys.sh +++ /dev/null @@ -1,109 +0,0 @@ -#!/bin/sh - -export PATH="/bin:/usr/bin:/sbin:/usr/sbin" - -cd $(dirname $0) - -DEVICE_PATH=$1 -KEY_BY_TEAM=3 -MOUNT_DIR="/mnt" -COUNT1_FILE="count1" -COUNT2_FILE="count2" -BASE_URL="http://192.168.23.1:8081/api/certs/" -FILES_TO_COPY="README.txt" - -which curl > /dev/null || { echo 'curl required!'; exit 1; } -which jq > /dev/null || { echo 'jq required!'; exit 1; } -which fatlabel > /dev/null || { echo 'dosfstools required!'; exit 1; } -which mkfs.vfat > /dev/null || { echo 'dosfstools required!'; exit 1; } - -echo -n "[+] Starting at " -date - -test -e "${COUNT1_FILE}" || echo -n '1' > "${COUNT1_FILE}" -test -e "${COUNT2_FILE}" || echo -n '1' > "${COUNT2_FILE}" - -COUNT=$(cat "${COUNT1_FILE}") -COUNT2=$(cat "${COUNT2_FILE}") - -echo ">>> Doing operations for TEAM ${COUNT}" - -ORIG_LABEL=$(fatlabel "${DEVICE_PATH}") - -echo "[+] Dumping ${DEVICE_PATH}" - -if [ $(echo -n "${ORIG_LABEL}" | cut -c 1-3) = "FIC" ] -then - echo "[-] WARNING, this key has already FIC label: ${ORIG_LABEL}!!" - echo "Exiting...." - exit 42 -fi - -echo -n "Getting cert id: " -CERT_ID=$(curl -q "${BASE_URL}" | jq -r .[].id | head -n "${COUNT}" | tail -1) -echo $CERT_ID - -LABEL="FIC_"$(echo -n ${CERT_ID}) -echo "format USB key with label: ${LABEL}" -mkfs.vfat -n "${LABEL}" "${DEVICE_PATH}" > /dev/null - -if [ $? -ne 0 ] -then - echo "[-] FORMAT ERROR! Aborting..." - exit 42 -fi - -echo "${DEVICE_PATH} to ${MOUNT_DIR}" -mount "${DEVICE_PATH}" "${MOUNT_DIR}" -if [ $? -ne 0 ] -then - echo "[-] MOUNT ERROR! Aborting..." - exit 42 -fi - -echo "Copy files:" -wget -O "/tmp/team-${CERT_ID}.p12" "${BASE_URL}${CERT_ID}" -FILES_TO_COPY="${FILES_TO_COPY} /tmp/team-${CERT_ID}.p12" - -for i in $FILES_TO_COPY -do - cp -v "${i}" "${MOUNT_DIR}" -done -sync - -echo "Done!" -echo "Umounting" -umount "${MOUNT_DIR}" - -echo "[+]Verify..." -mount "${DEVICE_PATH}" "${MOUNT_DIR}" - -for i in $FILES_TO_COPY -do - SHA_1=$(sha512sum "${i}"| awk '{ print $1 }') - j=$(echo -n "${i}" |sed 's/.*\///g') - SHA_2=$(sha512sum "${MOUNT_DIR}/${j}"| awk '{ print $1 }') - if [ "${SHA_1}" != "" ] && [ "${SHA_1}" = "${SHA_2}" ] - then - echo "File \"${j}\" ok!" - else - echo "[-] -------------------- File \"${j}\" KO!" - echo "We CANNOT continue, umouting & exiting :(" - umount "${MOUNT_DIR}" - exit 42 - fi -done - -echo "[+]Exiting..." -umount "${MOUNT_DIR}" -/bin/echo -e "${COUNT2}/${KEY_BY_TEAM} keys done for \e[32;01m${CERT_ID:0:5}\e[00m" -if [ $COUNT2 -ge "${KEY_BY_TEAM}" ] -then - rm $COUNT2_FILE - echo -n $(( $COUNT + 1 )) > "${COUNT1_FILE}" -else - echo -n $(( $COUNT2 + 1 )) > "${COUNT2_FILE}" -fi -echo "Done, bye (:" -echo "" -echo "" diff --git a/update-deps.sh b/update-deps.sh deleted file mode 100755 index bb0e59b7..00000000 --- a/update-deps.sh +++ /dev/null @@ -1,18 +0,0 @@ -#! /usr/bin/env nix-shell -#! nix-shell --pure -i bash -p go gitMinimal gnused nixUnstable - -set -euxo pipefail - -go mod tidy -git add go.sum go.mod - -# e is extension of new sed that replace backreference in REPLACEMENT then execute the new script. -# it is a gnu extension but we use gnused so we are safe -# cf: https://unix.stackexchange.com/a/194229/246754 -# originally I did sed -i -e 's#vendorSha256 = ".*";#vendorSha256 = "'"$(nix build .#fic-admin.go-modules 2>&1 | sed -ne 's/ *got: *//p')"'";#' flake.nix -# this has 2 draw back: -# - not idem potent the second run will output "" -# - it hide all other error message it was ridiculous since I omitted nixUnstable and got nix: command not found in bash subshell (set -e didn't helped) -# If one need to avoid gnu extension you might be able to swap stdout and stderr (cf https://stackoverflow.com/q/3618078/7227940) and pipe to sh -nix build .#fic-admin.go-modules 2>&1 | sed -e 's/ *got: *\(.*\)/sed -i -e "s#vendorSha256 = \\\".*\\\";#vendorSha256 = \\\"\1\\\";#" flake.nix/pe' -git add flake.nix