remove treestatus and tooltool and releng-frontend (#2273)
This commit is contained in:
Родитель
2959e48390
Коммит
4217e886d5
|
@ -121,191 +121,4 @@ PROJECTS_CONFIG = {
|
|||
},
|
||||
],
|
||||
},
|
||||
'releng-frontend': {
|
||||
'update': False,
|
||||
'run': 'ELM',
|
||||
'run_options': {
|
||||
'port': 8010,
|
||||
},
|
||||
'requires': [
|
||||
'docs',
|
||||
'tooltool/api',
|
||||
'treestatus/api',
|
||||
],
|
||||
'deploys': [
|
||||
{
|
||||
'target': 'S3',
|
||||
'options': {
|
||||
'testing': {
|
||||
'enable': True,
|
||||
's3_bucket': 'relengstatic-testing-relengfrontend-static-website',
|
||||
'url': 'https://testing.mozilla-releng.net',
|
||||
'dns': 'd1l70lpksx3ik7.cloudfront.net.',
|
||||
'csp': [
|
||||
'https://login.taskcluster.net',
|
||||
'https://auth.taskcluster.net',
|
||||
],
|
||||
},
|
||||
'staging': {
|
||||
'enable': True,
|
||||
's3_bucket': 'relengstatic-staging-relengfrontend-static-website',
|
||||
'url': 'https://staging.mozilla-releng.net',
|
||||
'dns': 'dpwmwa9tge2p3.cloudfront.net.',
|
||||
'csp': [
|
||||
'https://login.taskcluster.net',
|
||||
'https://auth.taskcluster.net',
|
||||
],
|
||||
},
|
||||
'production': {
|
||||
'enable': True,
|
||||
's3_bucket': 'relengstatic-prod-relengfrontend-static-website',
|
||||
'url': 'https://mozilla-releng.net',
|
||||
'dns': 'd1qqwps52z1e12.cloudfront.net.',
|
||||
'dns_domain': 'www.mozilla-releng.net',
|
||||
'csp': [
|
||||
'https://login.taskcluster.net',
|
||||
'https://auth.taskcluster.net',
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
'tooltool/client': {
|
||||
'update': False,
|
||||
},
|
||||
'tooltool/api': {
|
||||
'update': False,
|
||||
'run': 'FLASK',
|
||||
'run_options': {
|
||||
'port': 8002,
|
||||
},
|
||||
'requires': [
|
||||
'postgresql',
|
||||
],
|
||||
'deploys': [
|
||||
{
|
||||
'target': 'DOCKERHUB',
|
||||
'options': {
|
||||
'testing': {
|
||||
'enable': True,
|
||||
'url': 'https://dev.tooltool.mozilla-releng.net',
|
||||
'nix_path_attribute': 'dockerflow',
|
||||
'docker_registry': 'index.docker.io',
|
||||
'docker_repo': 'mozilla/release-services',
|
||||
'docker_stable_tag': 'tooltool_api_dockerflow_testing',
|
||||
},
|
||||
'staging': {
|
||||
'enable': True,
|
||||
'url': 'https://stage.tooltool.mozilla-releng.net',
|
||||
'nix_path_attribute': 'dockerflow',
|
||||
'docker_registry': 'index.docker.io',
|
||||
'docker_repo': 'mozilla/release-services',
|
||||
'docker_stable_tag': 'tooltool_api_dockerflow_staging',
|
||||
},
|
||||
'production': {
|
||||
'enable': True,
|
||||
'url': 'https://tooltool.mozilla-releng.net',
|
||||
'nix_path_attribute': 'dockerflow',
|
||||
'docker_registry': 'index.docker.io',
|
||||
'docker_repo': 'mozilla/release-services',
|
||||
'docker_stable_tag': 'tooltool_api_dockerflow_production',
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
'target': 'TASKCLUSTER_HOOK',
|
||||
'options': {
|
||||
'testing': {
|
||||
'enable': True,
|
||||
'nix_path_attribute': 'cron.replicate.testing',
|
||||
'name-suffix': '-replicate',
|
||||
'docker_registry': 'index.docker.io',
|
||||
'docker_repo': 'mozillareleng/services',
|
||||
},
|
||||
'staging': {
|
||||
'enable': True,
|
||||
'nix_path_attribute': 'cron.replicate.staging',
|
||||
'name-suffix': '-replicate',
|
||||
'docker_registry': 'index.docker.io',
|
||||
'docker_repo': 'mozillareleng/services',
|
||||
},
|
||||
'production': {
|
||||
'enable': True,
|
||||
'nix_path_attribute': 'cron.replicate.production',
|
||||
'name-suffix': '-replicate',
|
||||
'docker_registry': 'index.docker.io',
|
||||
'docker_repo': 'mozillareleng/services',
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
'target': 'TASKCLUSTER_HOOK',
|
||||
'options': {
|
||||
'testing': {
|
||||
'enable': True,
|
||||
'nix_path_attribute': 'cron.check_pending_uploads.testing',
|
||||
'name-suffix': '-check_pending_uploads',
|
||||
'docker_registry': 'index.docker.io',
|
||||
'docker_repo': 'mozillareleng/services',
|
||||
},
|
||||
'staging': {
|
||||
'enable': True,
|
||||
'nix_path_attribute': 'cron.check_pending_uploads.staging',
|
||||
'name-suffix': '-check_pending_uploads',
|
||||
'docker_registry': 'index.docker.io',
|
||||
'docker_repo': 'mozillareleng/services',
|
||||
},
|
||||
'production': {
|
||||
'enable': True,
|
||||
'nix_path_attribute': 'cron.check_pending_uploads.production',
|
||||
'name-suffix': '-check_pending_uploads',
|
||||
'docker_registry': 'index.docker.io',
|
||||
'docker_repo': 'mozillareleng/services',
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
'treestatus/api': {
|
||||
'update': False,
|
||||
'run': 'FLASK',
|
||||
'run_options': {
|
||||
'port': 8000,
|
||||
},
|
||||
'requires': [
|
||||
'postgresql',
|
||||
],
|
||||
'deploys': [
|
||||
{
|
||||
'target': 'DOCKERHUB',
|
||||
'options': {
|
||||
'testing': {
|
||||
'enable': True,
|
||||
'url': 'https://dev.treestatus.mozilla-releng.net',
|
||||
'nix_path_attribute': 'dockerflow',
|
||||
'docker_registry': 'index.docker.io',
|
||||
'docker_repo': 'mozilla/release-services',
|
||||
'docker_stable_tag': 'treestatus_api_dockerflow_testing',
|
||||
},
|
||||
'staging': {
|
||||
'enable': True,
|
||||
'url': 'https://stage.treestatus.mozilla-releng.net',
|
||||
'nix_path_attribute': 'dockerflow',
|
||||
'docker_registry': 'index.docker.io',
|
||||
'docker_repo': 'mozilla/release-services',
|
||||
'docker_stable_tag': 'treestatus_api_dockerflow_staging',
|
||||
},
|
||||
'production': {
|
||||
'enable': True,
|
||||
'url': 'https://treestatus.mozilla-releng.net',
|
||||
'nix_path_attribute': 'dockerflow',
|
||||
'docker_registry': 'index.docker.io',
|
||||
'docker_repo': 'mozilla/release-services',
|
||||
'docker_stable_tag': 'treestatus_api_dockerflow_production',
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
}
|
||||
|
|
|
@ -1,100 +0,0 @@
|
|||
===============
|
||||
RelengAPI Tools
|
||||
===============
|
||||
|
||||
TODO: what this app is for, list useful links
|
||||
|
||||
Tools used in this project:
|
||||
|
||||
- react
|
||||
- immutable
|
||||
- redux
|
||||
- redux-router
|
||||
- bootstrap (v4)
|
||||
|
||||
|
||||
Commands
|
||||
========
|
||||
|
||||
- ``npm run dev``
|
||||
|
||||
Run development instance of application, on http://localhost:8080,
|
||||
via `webpack-dev-server`_, with hot reloading of code via
|
||||
`webpack hot module reload`_.
|
||||
|
||||
- ``npm run build``
|
||||
|
||||
Build production ready static files into ``./build`` folder.
|
||||
|
||||
- ``npm run test``
|
||||
|
||||
Run all the tests for the project one time.
|
||||
|
||||
Coverage can be found in ``./coverage`` folder.
|
||||
|
||||
- ``npm run test:dev``
|
||||
|
||||
Open browser, then run all tests, keep browser open and listen for code
|
||||
changes, then rerun the tests.
|
||||
|
||||
Coverage can be found in ``./coverage`` folder.
|
||||
|
||||
|
||||
Configuration
|
||||
=============
|
||||
|
||||
This project does not use any of the *traditional* javascript building tools
|
||||
(eg. `Gulp`_, `Grunt`_). It connects all with `webpack`_ and different webpack
|
||||
loaders. Webpack is used here because it also provides nice development
|
||||
environment (hot reload of code). Configuration for development, testing and
|
||||
production can be found in ``./webpack.config.js``.
|
||||
|
||||
Webpack hooks and applies this `babel`_ transforms:
|
||||
- `babel-preset-es2015`_
|
||||
- `babel-preset-react`_
|
||||
- `babel-preset-stage-0`_
|
||||
|
||||
Configuration for `eslint`_ configuration, which is used in webpack's build
|
||||
chain is configured in ``./packages.json``, since this is the location eslint
|
||||
linter looks.
|
||||
|
||||
For testing, test runner `karma`_ is used. It used webpack's configuration to
|
||||
apply all babel transforms and runs tests in for different browsers.
|
||||
Configuration for karma can be found in ``./karma.conf.js``
|
||||
|
||||
|
||||
Deploying
|
||||
=========
|
||||
|
||||
TODO: ask dustin how setup ssl and deploy taskcluster docs to s3
|
||||
|
||||
|
||||
.. _`webpack-dev-server`: https://www.npmjs.com/package/webpack-dev-server
|
||||
.. _`webpack hot module reload`: http://webpack.github.io/docs/hot-module-replacement-with-webpack.html
|
||||
.. _`Gulp`: http://gulpjs.com/
|
||||
.. _`Grunt`: http://gruntjs.com/
|
||||
.. _`webpack`: http://webpack.github.io/
|
||||
.. _`karma`: https://karma-runner.github.io/
|
||||
.. _`babel`: http://babeljs.io/
|
||||
.. _`eslint`: http://eslint.org/
|
||||
.. _`babel-preset-es2015`: http://babeljs.io/docs/plugins/preset-es2015/
|
||||
.. _`babel-preset-react`: http://babeljs.io/docs/plugins/preset-react/
|
||||
.. _`babel-preset-stage-0`: http://babeljs.io/docs/plugins/preset-stage-0/
|
||||
|
||||
todo:
|
||||
https://pageshot.dev.mozaws.net/
|
||||
https://mozilla.github.io/mozmaker/demo/
|
||||
https://github.com/mozilla/tabzilla
|
||||
|
||||
https://github.com/xgrommx/awesome-redux
|
||||
https://github.com/reactjs/reselect
|
||||
http://indexiatech.github.io/re-notif/
|
||||
https://github.com/michaelcontento/redux-storage
|
||||
|
||||
https://github.com/raisemarketplace/redux-loop
|
||||
http://yelouafi.github.io/redux-saga/
|
||||
http://redux.js.org/docs/advanced/Middleware.html
|
||||
http://redux.js.org/docs/advanced/ExampleRedditAPI.html
|
||||
http://yelouafi.github.io/redux-saga/docs/basics/ErrorHandling.html
|
||||
|
||||
https://auth0.com/blog/2016/01/04/secure-your-react-and-redux-app-with-jwt-authentication/
|
|
@ -1 +0,0 @@
|
|||
1.0.0
|
|
@ -1,25 +0,0 @@
|
|||
{ releng_pkgs
|
||||
}:
|
||||
let
|
||||
inherit (builtins) readFile;
|
||||
inherit (releng_pkgs.lib) mkFrontend;
|
||||
inherit (releng_pkgs.pkgs.lib) fileContents;
|
||||
|
||||
nodejs = releng_pkgs.pkgs."nodejs-6_x";
|
||||
node_modules = import ./node-modules.nix {
|
||||
inherit nodejs;
|
||||
inherit (releng_pkgs) pkgs;
|
||||
};
|
||||
elm_packages = import ./elm-packages.nix;
|
||||
|
||||
in mkFrontend {
|
||||
inProduction = true;
|
||||
project_name = "releng-frontend";
|
||||
inherit nodejs node_modules elm_packages;
|
||||
version = fileContents ./VERSION;
|
||||
src = ./.;
|
||||
src_path = "src/releng_frontend";
|
||||
postInstall = ''
|
||||
cp -R src/static/* $out/
|
||||
'';
|
||||
}
|
|
@ -1,22 +0,0 @@
|
|||
{
|
||||
"version": "1.0.0",
|
||||
"summary": "Mozilla RelEng Services Frontend",
|
||||
"repository": "https://github.com/mozilla/release-services.git",
|
||||
"license": "MPL2",
|
||||
"source-directories": [
|
||||
"src"
|
||||
],
|
||||
"exposed-modules": [],
|
||||
"dependencies": {
|
||||
"elm-lang/core": "5.1.1 <= v < 6.0.0",
|
||||
"elm-lang/html": "2.0.0 <= v < 3.0.0",
|
||||
"elm-lang/http": "1.0.0 <= v < 2.0.0",
|
||||
"elm-lang/navigation": "2.1.0 <= v < 3.0.0",
|
||||
"elm-lang/virtual-dom": "2.0.4 <= v < 3.0.0",
|
||||
"etaque/elm-form": "2.0.0 <= v < 3.0.0",
|
||||
"evancz/focus": "2.0.2 <= v < 3.0.0",
|
||||
"evancz/url-parser": "2.0.1 <= v < 3.0.0",
|
||||
"krisajenkins/remotedata": "4.3.0 <= v < 5.0.0"
|
||||
},
|
||||
"elm-version": "0.18.0 <= v < 0.19.0"
|
||||
}
|
|
@ -1,62 +0,0 @@
|
|||
{
|
||||
"elm-community/lazy-list" = {
|
||||
version = "1.0.0";
|
||||
sha256 = "1hxpyd20rmicr6k6wwzyxhqwwbsh22ml7gr0j74vqql1k7yysshh";
|
||||
};
|
||||
"krisajenkins/remotedata" = {
|
||||
version = "4.3.0";
|
||||
sha256 = "0k07s964ahq0mfgvwl8yp93ziqfzz3r38h4xawbl44g7pfajlz3v";
|
||||
};
|
||||
"elm-community/elm-test" = {
|
||||
version = "4.1.1";
|
||||
sha256 = "04shqwlw3k7pqzj5qnlk1ga9y5jq0ccmgni3lj27fdfgniymxgai";
|
||||
};
|
||||
"elm-lang/navigation" = {
|
||||
version = "2.1.0";
|
||||
sha256 = "0xyamkcw3misjfyc523g0bh97lffrikaxk2hklwmla9i7j39ivf3";
|
||||
};
|
||||
"elm-community/shrink" = {
|
||||
version = "2.0.0";
|
||||
sha256 = "1f205dvgyqg4phf0hvk57nsxwhfhwhy5y4v6msc3fa9xgyzx7fik";
|
||||
};
|
||||
"elm-lang/virtual-dom" = {
|
||||
version = "2.0.4";
|
||||
sha256 = "13kc3jim6js827h91m6wx6n16qb3b6p6ms6789ks106zpwmhfwxv";
|
||||
};
|
||||
"evancz/url-parser" = {
|
||||
version = "2.0.1";
|
||||
sha256 = "0c9vxd7m11mmshlqi3s2arsr4jjkzxqcb907fvamxhaxvygkd4i7";
|
||||
};
|
||||
"etaque/elm-form" = {
|
||||
version = "2.1.0";
|
||||
sha256 = "19d904r73fqm35dxa1njyrmn613dj963hr8gbg8hdybzq46qdhzk";
|
||||
};
|
||||
"mgold/elm-random-pcg" = {
|
||||
version = "5.0.1";
|
||||
sha256 = "0gf0qhavcsg30abh99z332srqswfrpaafsphcw74wxny4fx0a49q";
|
||||
};
|
||||
"elm-lang/lazy" = {
|
||||
version = "2.0.0";
|
||||
sha256 = "086f57n0ajdyn8rax11qp1dlym1hz3s6qv5hrmq8azw0q27byipn";
|
||||
};
|
||||
"elm-lang/dom" = {
|
||||
version = "1.1.1";
|
||||
sha256 = "1y6nm3np10l34mhpc9c88vv7kc7qgaxim4cmagf4ig82rwi9mpl1";
|
||||
};
|
||||
"elm-lang/html" = {
|
||||
version = "2.0.0";
|
||||
sha256 = "05sqjd5n8jnq4lv5v0ipcg98b8im1isnnl4wns1zzn4w5nbrjjzi";
|
||||
};
|
||||
"elm-lang/http" = {
|
||||
version = "1.0.0";
|
||||
sha256 = "1fh9k278q4i2y447wkk8jd4n12px1nsmycd0fql8k0i3ypcibm8h";
|
||||
};
|
||||
"evancz/focus" = {
|
||||
version = "2.0.2";
|
||||
sha256 = "1680pbkx67cdk1fp7pdciiv00grq86kk15amf30pma9zmysp938p";
|
||||
};
|
||||
"elm-lang/core" = {
|
||||
version = "5.1.1";
|
||||
sha256 = "1ndw8ffn2la48gvs7xf3zrc5d7zqrq4j6scbrb60mb2ml1a5hhry";
|
||||
};
|
||||
}
|
|
@ -1,349 +0,0 @@
|
|||
# This file originates from node2nix
|
||||
|
||||
{stdenv, nodejs, python2, utillinux, runCommand, writeTextFile}:
|
||||
|
||||
let
|
||||
python = if nodejs ? python then nodejs.python else python2;
|
||||
|
||||
# Create a tar wrapper that filters all the 'Ignoring unknown extended header keyword' noise
|
||||
tarWrapper = runCommand "tarWrapper" {} ''
|
||||
mkdir -p $out/bin
|
||||
|
||||
cat > $out/bin/tar <<EOF
|
||||
#! ${stdenv.shell} -e
|
||||
$(type -p tar) "\$@" --warning=no-unknown-keyword
|
||||
EOF
|
||||
|
||||
chmod +x $out/bin/tar
|
||||
'';
|
||||
|
||||
# Function that generates a TGZ file from a NPM project
|
||||
buildNodeSourceDist =
|
||||
{ name, version, src, ... }:
|
||||
|
||||
stdenv.mkDerivation {
|
||||
name = "node-tarball-${name}-${version}";
|
||||
inherit src;
|
||||
buildInputs = [ nodejs ];
|
||||
buildPhase = ''
|
||||
export HOME=$TMPDIR
|
||||
tgzFile=$(npm pack)
|
||||
'';
|
||||
installPhase = ''
|
||||
mkdir -p $out/tarballs
|
||||
mv $tgzFile $out/tarballs
|
||||
mkdir -p $out/nix-support
|
||||
echo "file source-dist $out/tarballs/$tgzFile" >> $out/nix-support/hydra-build-products
|
||||
'';
|
||||
};
|
||||
|
||||
includeDependencies = {dependencies}:
|
||||
stdenv.lib.optionalString (dependencies != [])
|
||||
(stdenv.lib.concatMapStrings (dependency:
|
||||
''
|
||||
# Bundle the dependencies of the package
|
||||
mkdir -p node_modules
|
||||
cd node_modules
|
||||
|
||||
# Only include dependencies if they don't exist. They may also be bundled in the package.
|
||||
if [ ! -e "${dependency.name}" ]
|
||||
then
|
||||
${composePackage dependency}
|
||||
fi
|
||||
|
||||
cd ..
|
||||
''
|
||||
) dependencies);
|
||||
|
||||
# Recursively composes the dependencies of a package
|
||||
composePackage = { name, packageName, src, dependencies ? [], ... }@args:
|
||||
''
|
||||
DIR=$(pwd)
|
||||
cd $TMPDIR
|
||||
|
||||
unpackFile ${src}
|
||||
|
||||
# Make the base dir in which the target dependency resides first
|
||||
mkdir -p "$(dirname "$DIR/${packageName}")"
|
||||
|
||||
if [ -f "${src}" ]
|
||||
then
|
||||
# Figure out what directory has been unpacked
|
||||
packageDir="$(find . -maxdepth 1 -type d | tail -1)"
|
||||
|
||||
# Restore write permissions to make building work
|
||||
find "$packageDir" -type d -print0 | xargs -0 chmod u+x
|
||||
chmod -R u+w "$packageDir"
|
||||
|
||||
# Move the extracted tarball into the output folder
|
||||
mv "$packageDir" "$DIR/${packageName}"
|
||||
elif [ -d "${src}" ]
|
||||
then
|
||||
# Get a stripped name (without hash) of the source directory.
|
||||
# On old nixpkgs it's already set internally.
|
||||
if [ -z "$strippedName" ]
|
||||
then
|
||||
strippedName="$(stripHash ${src})"
|
||||
fi
|
||||
|
||||
# Restore write permissions to make building work
|
||||
chmod -R u+w "$strippedName"
|
||||
|
||||
# Move the extracted directory into the output folder
|
||||
mv "$strippedName" "$DIR/${packageName}"
|
||||
fi
|
||||
|
||||
# Unset the stripped name to not confuse the next unpack step
|
||||
unset strippedName
|
||||
|
||||
# Include the dependencies of the package
|
||||
cd "$DIR/${packageName}"
|
||||
${includeDependencies { inherit dependencies; }}
|
||||
cd ..
|
||||
${stdenv.lib.optionalString (builtins.substring 0 1 packageName == "@") "cd .."}
|
||||
'';
|
||||
|
||||
pinpointDependencies = {dependencies, production}:
|
||||
let
|
||||
pinpointDependenciesFromPackageJSON = writeTextFile {
|
||||
name = "pinpointDependencies.js";
|
||||
text = ''
|
||||
var fs = require('fs');
|
||||
var path = require('path');
|
||||
|
||||
function resolveDependencyVersion(location, name) {
|
||||
if(location == process.env['NIX_STORE']) {
|
||||
return null;
|
||||
} else {
|
||||
var dependencyPackageJSON = path.join(location, "node_modules", name, "package.json");
|
||||
|
||||
if(fs.existsSync(dependencyPackageJSON)) {
|
||||
var dependencyPackageObj = JSON.parse(fs.readFileSync(dependencyPackageJSON));
|
||||
|
||||
if(dependencyPackageObj.name == name) {
|
||||
return dependencyPackageObj.version;
|
||||
}
|
||||
} else {
|
||||
return resolveDependencyVersion(path.resolve(location, ".."), name);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function replaceDependencies(dependencies) {
|
||||
if(typeof dependencies == "object" && dependencies !== null) {
|
||||
for(var dependency in dependencies) {
|
||||
var resolvedVersion = resolveDependencyVersion(process.cwd(), dependency);
|
||||
|
||||
if(resolvedVersion === null) {
|
||||
process.stderr.write("WARNING: cannot pinpoint dependency: "+dependency+", context: "+process.cwd()+"\n");
|
||||
} else {
|
||||
dependencies[dependency] = resolvedVersion;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Read the package.json configuration */
|
||||
var packageObj = JSON.parse(fs.readFileSync('./package.json'));
|
||||
|
||||
/* Pinpoint all dependencies */
|
||||
replaceDependencies(packageObj.dependencies);
|
||||
if(process.argv[2] == "development") {
|
||||
replaceDependencies(packageObj.devDependencies);
|
||||
}
|
||||
replaceDependencies(packageObj.optionalDependencies);
|
||||
|
||||
/* Write the fixed package.json file */
|
||||
fs.writeFileSync("package.json", JSON.stringify(packageObj, null, 2));
|
||||
'';
|
||||
};
|
||||
in
|
||||
''
|
||||
node ${pinpointDependenciesFromPackageJSON} ${if production then "production" else "development"}
|
||||
|
||||
${stdenv.lib.optionalString (dependencies != [])
|
||||
''
|
||||
if [ -d node_modules ]
|
||||
then
|
||||
cd node_modules
|
||||
${stdenv.lib.concatMapStrings (dependency: pinpointDependenciesOfPackage dependency) dependencies}
|
||||
cd ..
|
||||
fi
|
||||
''}
|
||||
'';
|
||||
|
||||
# Recursively traverses all dependencies of a package and pinpoints all
|
||||
# dependencies in the package.json file to the versions that are actually
|
||||
# being used.
|
||||
|
||||
pinpointDependenciesOfPackage = { packageName, dependencies ? [], production ? true, ... }@args:
|
||||
''
|
||||
if [ -d "${packageName}" ]
|
||||
then
|
||||
cd "${packageName}"
|
||||
${pinpointDependencies { inherit dependencies production; }}
|
||||
cd ..
|
||||
${stdenv.lib.optionalString (builtins.substring 0 1 packageName == "@") "cd .."}
|
||||
fi
|
||||
'';
|
||||
|
||||
# Extract the Node.js source code which is used to compile packages with
|
||||
# native bindings
|
||||
nodeSources = runCommand "node-sources" {} ''
|
||||
tar --no-same-owner --no-same-permissions -xf ${nodejs.src}
|
||||
mv node-* $out
|
||||
'';
|
||||
|
||||
# Builds and composes an NPM package including all its dependencies
|
||||
buildNodePackage = { name, packageName, version, dependencies ? [], production ? true, npmFlags ? "", dontNpmInstall ? false, preRebuild ? "", ... }@args:
|
||||
|
||||
stdenv.lib.makeOverridable stdenv.mkDerivation (builtins.removeAttrs args [ "dependencies" ] // {
|
||||
name = "node-${name}-${version}";
|
||||
buildInputs = [ tarWrapper python nodejs ] ++ stdenv.lib.optional (stdenv.isLinux) utillinux ++ args.buildInputs or [];
|
||||
dontStrip = args.dontStrip or true; # Striping may fail a build for some package deployments
|
||||
|
||||
inherit dontNpmInstall preRebuild;
|
||||
|
||||
unpackPhase = args.unpackPhase or "true";
|
||||
|
||||
buildPhase = args.buildPhase or "true";
|
||||
|
||||
compositionScript = composePackage args;
|
||||
pinpointDependenciesScript = pinpointDependenciesOfPackage args;
|
||||
|
||||
passAsFile = [ "compositionScript" "pinpointDependenciesScript" ];
|
||||
|
||||
installPhase = args.installPhase or ''
|
||||
# Create and enter a root node_modules/ folder
|
||||
mkdir -p $out/lib/node_modules
|
||||
cd $out/lib/node_modules
|
||||
|
||||
# Compose the package and all its dependencies
|
||||
source $compositionScriptPath
|
||||
|
||||
# Pinpoint the versions of all dependencies to the ones that are actually being used
|
||||
echo "pinpointing versions of dependencies..."
|
||||
source $pinpointDependenciesScriptPath
|
||||
|
||||
# Patch the shebangs of the bundled modules to prevent them from
|
||||
# calling executables outside the Nix store as much as possible
|
||||
patchShebangs .
|
||||
|
||||
# Deploy the Node.js package by running npm install. Since the
|
||||
# dependencies have been provided already by ourselves, it should not
|
||||
# attempt to install them again, which is good, because we want to make
|
||||
# it Nix's responsibility. If it needs to install any dependencies
|
||||
# anyway (e.g. because the dependency parameters are
|
||||
# incomplete/incorrect), it fails.
|
||||
#
|
||||
# The other responsibilities of NPM are kept -- version checks, build
|
||||
# steps, postprocessing etc.
|
||||
|
||||
export HOME=$TMPDIR
|
||||
cd "${packageName}"
|
||||
runHook preRebuild
|
||||
npm --registry http://www.example.com --nodedir=${nodeSources} ${npmFlags} ${stdenv.lib.optionalString production "--production"} rebuild
|
||||
|
||||
if [ "$dontNpmInstall" != "1" ]
|
||||
then
|
||||
# NPM tries to download packages even when they already exist if npm-shrinkwrap is used.
|
||||
rm -f npm-shrinkwrap.json
|
||||
|
||||
npm --registry http://www.example.com --nodedir=${nodeSources} ${npmFlags} ${stdenv.lib.optionalString production "--production"} install
|
||||
fi
|
||||
|
||||
# Create symlink to the deployed executable folder, if applicable
|
||||
if [ -d "$out/lib/node_modules/.bin" ]
|
||||
then
|
||||
ln -s $out/lib/node_modules/.bin $out/bin
|
||||
fi
|
||||
|
||||
# Create symlinks to the deployed manual page folders, if applicable
|
||||
if [ -d "$out/lib/node_modules/${packageName}/man" ]
|
||||
then
|
||||
mkdir -p $out/share
|
||||
for dir in "$out/lib/node_modules/${packageName}/man/"*
|
||||
do
|
||||
mkdir -p $out/share/man/$(basename "$dir")
|
||||
for page in "$dir"/*
|
||||
do
|
||||
ln -s $page $out/share/man/$(basename "$dir")
|
||||
done
|
||||
done
|
||||
fi
|
||||
|
||||
# Run post install hook, if provided
|
||||
runHook postInstall
|
||||
'';
|
||||
});
|
||||
|
||||
# Builds a development shell
|
||||
buildNodeShell = { name, packageName, version, src, dependencies ? [], production ? true, npmFlags ? "", dontNpmInstall ? false, ... }@args:
|
||||
let
|
||||
nodeDependencies = stdenv.mkDerivation {
|
||||
name = "node-dependencies-${name}-${version}";
|
||||
|
||||
buildInputs = [ tarWrapper python nodejs ] ++ stdenv.lib.optional (stdenv.isLinux) utillinux ++ args.buildInputs or [];
|
||||
|
||||
includeScript = includeDependencies { inherit dependencies; };
|
||||
pinpointDependenciesScript = pinpointDependenciesOfPackage args;
|
||||
|
||||
passAsFile = [ "includeScript" "pinpointDependenciesScript" ];
|
||||
|
||||
buildCommand = ''
|
||||
mkdir -p $out/lib
|
||||
cd $out/lib
|
||||
source $includeScriptPath
|
||||
|
||||
# Pinpoint the versions of all dependencies to the ones that are actually being used
|
||||
echo "pinpointing versions of dependencies..."
|
||||
source $pinpointDependenciesScriptPath
|
||||
|
||||
# Create fake package.json to make the npm commands work properly
|
||||
cat > package.json <<EOF
|
||||
{
|
||||
"name": "${packageName}",
|
||||
"version": "${version}"
|
||||
}
|
||||
EOF
|
||||
|
||||
# Patch the shebangs of the bundled modules to prevent them from
|
||||
# calling executables outside the Nix store as much as possible
|
||||
patchShebangs .
|
||||
|
||||
export HOME=$PWD
|
||||
npm --registry http://www.example.com --nodedir=${nodeSources} ${npmFlags} ${stdenv.lib.optionalString production "--production"} rebuild
|
||||
|
||||
${stdenv.lib.optionalString (!dontNpmInstall) ''
|
||||
# NPM tries to download packages even when they already exist if npm-shrinkwrap is used.
|
||||
rm -f npm-shrinkwrap.json
|
||||
|
||||
npm --registry http://www.example.com --nodedir=${nodeSources} ${npmFlags} ${stdenv.lib.optionalString production "--production"} install
|
||||
''}
|
||||
|
||||
ln -s $out/lib/node_modules/.bin $out/bin
|
||||
'';
|
||||
};
|
||||
in
|
||||
stdenv.lib.makeOverridable stdenv.mkDerivation {
|
||||
name = "node-shell-${name}-${version}";
|
||||
|
||||
buildInputs = [ python nodejs ] ++ stdenv.lib.optional (stdenv.isLinux) utillinux ++ args.buildInputs or [];
|
||||
buildCommand = ''
|
||||
mkdir -p $out/bin
|
||||
cat > $out/bin/shell <<EOF
|
||||
#! ${stdenv.shell} -e
|
||||
$shellHook
|
||||
exec ${stdenv.shell}
|
||||
EOF
|
||||
chmod +x $out/bin/shell
|
||||
'';
|
||||
|
||||
# Provide the dependencies in a development shell through the NODE_PATH environment variable
|
||||
inherit nodeDependencies;
|
||||
shellHook = stdenv.lib.optionalString (dependencies != []) ''
|
||||
export NODE_PATH=$nodeDependencies/lib/node_modules
|
||||
'';
|
||||
};
|
||||
in
|
||||
{ inherit buildNodeSourceDist buildNodePackage buildNodeShell; }
|
Разница между файлами не показана из-за своего большого размера
Загрузить разницу
|
@ -1,24 +0,0 @@
|
|||
[
|
||||
{ "bootstrap" : "^4.0.0-alpha.6" },
|
||||
{ "copy-webpack-plugin" : "^4.0.1" },
|
||||
{ "css-loader" : "^0.26.2" },
|
||||
{ "elm-hot-loader" : "^0.5.4" },
|
||||
{ "elm-webpack-loader" : "^4.2.0" },
|
||||
{ "expose-loader" : "^0.7.3" },
|
||||
{ "extract-text-webpack-plugin" : "^2.0.0" },
|
||||
{ "file-loader" : "^0.10.1" },
|
||||
{ "hawk" : "^6.0.0" },
|
||||
{ "html-webpack-plugin" : "^2.28.0" },
|
||||
{ "jquery" : "^3.1.0" },
|
||||
{ "node-sass" : "^4.2.0" },
|
||||
{ "raven-js" : "^3.15.0" },
|
||||
{ "sass-loader" : "^6.0.0" },
|
||||
{ "style-loader" : "^0.13.1" },
|
||||
{ "svg-url-loader" : "^2.0.2" },
|
||||
{ "tether" : "^1.4.0" },
|
||||
{ "url" : "^0.11.0" },
|
||||
{ "url-loader" : "^0.5.7" },
|
||||
{ "webpack" : "^2.2.1" },
|
||||
{ "webpack-dev-server" : "^2.4.1" },
|
||||
{ "webpack-merge" : "^4.0.0" }
|
||||
]
|
|
@ -1,16 +0,0 @@
|
|||
# This file has been generated by node2nix 1.3.0. Do not edit!
|
||||
|
||||
{pkgs ? import <nixpkgs> {
|
||||
inherit system;
|
||||
}, system ? builtins.currentSystem, nodejs ? pkgs."nodejs-6_x"}:
|
||||
|
||||
let
|
||||
nodeEnv = import ./node-env.nix {
|
||||
inherit (pkgs) stdenv python2 utillinux runCommand writeTextFile;
|
||||
inherit nodejs;
|
||||
};
|
||||
in
|
||||
import ./node-modules-generated.nix {
|
||||
inherit (pkgs) fetchurl fetchgit;
|
||||
inherit nodeEnv;
|
||||
}
|
|
@ -1,144 +0,0 @@
|
|||
module App exposing (..)
|
||||
|
||||
import App.Tokens
|
||||
import App.ToolTool
|
||||
import App.TreeStatus
|
||||
import App.TreeStatus.Form
|
||||
import App.TreeStatus.Types
|
||||
import App.Types
|
||||
import App.UserScopes
|
||||
import Hawk
|
||||
import Navigation
|
||||
import TaskclusterLogin
|
||||
import UrlParser exposing ((</>), (<?>))
|
||||
|
||||
|
||||
--
|
||||
-- ROUTING
|
||||
--
|
||||
-- inspired by https://github.com/rofrol/elm-navigation-example
|
||||
--
|
||||
|
||||
|
||||
type Route
|
||||
= NotFoundRoute
|
||||
| HomeRoute
|
||||
| LoginRoute (Maybe String) (Maybe String)
|
||||
| LogoutRoute
|
||||
| TokensRoute
|
||||
| ToolToolRoute
|
||||
| TreeStatusRoute App.TreeStatus.Types.Route
|
||||
|
||||
|
||||
pages : List (App.Types.Page Route b)
|
||||
pages =
|
||||
[ App.Tokens.page TokensRoute
|
||||
, App.ToolTool.page ToolToolRoute
|
||||
, App.TreeStatus.page TreeStatusRoute
|
||||
]
|
||||
|
||||
|
||||
routeParser : UrlParser.Parser (Route -> a) a
|
||||
routeParser =
|
||||
pages
|
||||
|> List.map (\x -> x.matcher)
|
||||
|> List.append
|
||||
[ UrlParser.map HomeRoute UrlParser.top
|
||||
, UrlParser.map NotFoundRoute (UrlParser.s "404")
|
||||
, UrlParser.map LoginRoute
|
||||
(UrlParser.s "login"
|
||||
<?> UrlParser.stringParam "code"
|
||||
<?> UrlParser.stringParam "state"
|
||||
)
|
||||
, UrlParser.map LogoutRoute (UrlParser.s "logout")
|
||||
]
|
||||
|> UrlParser.oneOf
|
||||
|
||||
|
||||
reverseRoute : Route -> String
|
||||
reverseRoute route =
|
||||
case route of
|
||||
NotFoundRoute ->
|
||||
"/404"
|
||||
|
||||
HomeRoute ->
|
||||
"/"
|
||||
|
||||
LoginRoute _ _ ->
|
||||
"/login"
|
||||
|
||||
LogoutRoute ->
|
||||
"/logout"
|
||||
|
||||
TokensRoute ->
|
||||
"/tokens"
|
||||
|
||||
ToolToolRoute ->
|
||||
"/tooltool"
|
||||
|
||||
TreeStatusRoute route ->
|
||||
App.TreeStatus.reverseRoute route
|
||||
|
||||
|
||||
parseLocation : Navigation.Location -> Route
|
||||
parseLocation location =
|
||||
location
|
||||
|> UrlParser.parsePath routeParser
|
||||
|> Maybe.withDefault NotFoundRoute
|
||||
|
||||
|
||||
navigateTo : Route -> Cmd Msg
|
||||
navigateTo route =
|
||||
route
|
||||
|> reverseRoute
|
||||
|> Navigation.newUrl
|
||||
|
||||
|
||||
|
||||
--
|
||||
-- FLAGS
|
||||
--
|
||||
|
||||
|
||||
type alias Flags =
|
||||
{ auth0 : Maybe TaskclusterLogin.Tokens
|
||||
, treestatusUrl : String
|
||||
, docsUrl : String
|
||||
, version : String
|
||||
}
|
||||
|
||||
|
||||
|
||||
--
|
||||
-- MODEL
|
||||
--
|
||||
|
||||
|
||||
type alias Model =
|
||||
{ history : List Navigation.Location
|
||||
, route : Route
|
||||
, user : TaskclusterLogin.Model
|
||||
, userScopes : App.UserScopes.Model
|
||||
, tokens : App.Tokens.Model
|
||||
, tooltool : App.ToolTool.Model
|
||||
, treestatus : App.TreeStatus.Types.Model App.TreeStatus.Form.AddTree App.TreeStatus.Form.UpdateTree App.TreeStatus.Form.UpdateStack App.TreeStatus.Form.UpdateLog
|
||||
, docsUrl : String
|
||||
, version : String
|
||||
}
|
||||
|
||||
|
||||
|
||||
--
|
||||
-- MESSAGES
|
||||
--
|
||||
|
||||
|
||||
type Msg
|
||||
= UrlChange Navigation.Location
|
||||
| NavigateTo Route
|
||||
| TaskclusterLoginMsg TaskclusterLogin.Msg
|
||||
| HawkMsg Hawk.Msg
|
||||
| UserScopesMsg App.UserScopes.Msg
|
||||
| TokensMsg App.Tokens.Msg
|
||||
| ToolToolMsg App.ToolTool.Msg
|
||||
| TreeStatusMsg App.TreeStatus.Types.Msg
|
|
@ -1,747 +0,0 @@
|
|||
module App.Clobberer exposing (..)
|
||||
|
||||
import App.Utils exposing (..)
|
||||
import Dict exposing (Dict)
|
||||
import Focus exposing ((=>), Focus, create, set)
|
||||
import Html exposing (..)
|
||||
import Html.Attributes exposing (..)
|
||||
import Html.Events exposing (..)
|
||||
import Html.Keyed
|
||||
import Http
|
||||
import Json.Decode as JsonDecode exposing ((:=))
|
||||
import Json.Encode as JsonEncode
|
||||
import RemoteData as RemoteData exposing (RemoteData(..), WebData)
|
||||
import RouteUrl.Builder exposing (Builder, builder, replacePath)
|
||||
import Task exposing (Task)
|
||||
|
||||
|
||||
-- TYPES
|
||||
|
||||
|
||||
type alias BackendBuilder =
|
||||
{ branch : String
|
||||
, builddir : String
|
||||
, lastclobber : Int
|
||||
, name : String
|
||||
, slave : String
|
||||
, who : String
|
||||
}
|
||||
|
||||
|
||||
type alias BackendBranch =
|
||||
{ name : String
|
||||
, builders : List BackendBuilder
|
||||
}
|
||||
|
||||
|
||||
type alias BackendResponse =
|
||||
List String
|
||||
|
||||
|
||||
type alias BackendData =
|
||||
List BackendBranch
|
||||
|
||||
|
||||
type alias ModelBackend =
|
||||
{ title : String
|
||||
, data : WebData BackendData
|
||||
, clobber : WebData BackendData
|
||||
, clobber_messages : List ( String, String )
|
||||
, selected_dropdown : Maybe String
|
||||
, selected : Dict String (List String)
|
||||
, selected_details : Bool
|
||||
}
|
||||
|
||||
|
||||
type alias Model =
|
||||
{ baseUrl : String
|
||||
, buildbot : ModelBackend
|
||||
, taskcluster : ModelBackend
|
||||
}
|
||||
|
||||
|
||||
type Backend
|
||||
= BuildbotBackend
|
||||
| TaskclusterBackend
|
||||
|
||||
|
||||
type Msg
|
||||
= Clobber Backend
|
||||
| Clobbered Backend (WebData BackendData)
|
||||
| FetchData Backend
|
||||
| DataFetched Backend (WebData BackendData)
|
||||
| SelectDropdown Backend String
|
||||
| AddSelected Backend String String
|
||||
| RemoveSelected Backend String String
|
||||
| ToggleSelectedDetails Backend
|
||||
| SelectAll Backend String
|
||||
| SelectNone Backend String
|
||||
|
||||
|
||||
|
||||
-- API
|
||||
|
||||
|
||||
viewClobberButton backend model =
|
||||
let
|
||||
buttonNumber =
|
||||
List.length <| List.concat <| Dict.values model.selected
|
||||
|
||||
buttonText =
|
||||
"Submit clobberer (" ++ toString buttonNumber ++ ")"
|
||||
|
||||
buttonDisabled =
|
||||
if buttonNumber == 0 then
|
||||
True
|
||||
else
|
||||
False
|
||||
in
|
||||
button
|
||||
[ class "btn btn-primary btn-large"
|
||||
, disabled buttonDisabled
|
||||
, App.Utils.onClick <| Clobber backend
|
||||
]
|
||||
[ text buttonText ]
|
||||
|
||||
|
||||
viewClobberDetails backend model =
|
||||
let
|
||||
( buttonText, itemText, backendName ) =
|
||||
case backend of
|
||||
TaskclusterBackend ->
|
||||
( "worker type(s)", "Worker Type: ", "taskcluster" )
|
||||
|
||||
BuildbotBackend ->
|
||||
( "builder(s)", "Builder: ", "buildbot" )
|
||||
|
||||
button =
|
||||
a
|
||||
[ href <| "#clobber-selected-" ++ backendName
|
||||
, attribute "data-toggle" "collapse"
|
||||
, attribute "aria-expanded" "false"
|
||||
, attribute "aria-controls" "collapseExample"
|
||||
, App.Utils.onClick <| ToggleSelectedDetails backend
|
||||
]
|
||||
[ text <| "Show/Hide selected " ++ buttonText ++ " to be clobbered" ]
|
||||
|
||||
collapsed =
|
||||
if model.selected_details == True then
|
||||
" in"
|
||||
else
|
||||
""
|
||||
|
||||
items'' key value =
|
||||
li []
|
||||
[ div []
|
||||
[ b [] [ text "Branch: " ]
|
||||
, text key
|
||||
]
|
||||
, div []
|
||||
[ b [] [ text itemText ]
|
||||
, text value
|
||||
]
|
||||
]
|
||||
|
||||
items' ( key, value ) =
|
||||
List.map (items'' key) value
|
||||
|
||||
items =
|
||||
List.concat <| List.map items' <| Dict.toList model.selected
|
||||
in
|
||||
if items == [] then
|
||||
[]
|
||||
else
|
||||
[ button
|
||||
, ul
|
||||
[ id <| "#clobber-selected-" ++ backendName
|
||||
, class <| "collapse" ++ collapsed
|
||||
]
|
||||
items
|
||||
]
|
||||
|
||||
|
||||
viewBackend : Backend -> ModelBackend -> Html Msg
|
||||
viewBackend backend model =
|
||||
div []
|
||||
[ viewClobberButton backend model
|
||||
, div [ class "clobberer-submit-description" ] <| viewClobberDetails backend model
|
||||
, case model.data of
|
||||
Success data ->
|
||||
dropdown (SelectDropdown backend) data model.selected_dropdown
|
||||
|
||||
Failure message ->
|
||||
div [] [ error (FetchData backend) (toString message) ]
|
||||
|
||||
Loading ->
|
||||
div [] [ loading ]
|
||||
|
||||
NotAsked ->
|
||||
div [] []
|
||||
, case model.selected_dropdown of
|
||||
Nothing ->
|
||||
div [] []
|
||||
|
||||
Just selected_dropdown ->
|
||||
let
|
||||
data =
|
||||
case model.data of
|
||||
Success data ->
|
||||
data
|
||||
|
||||
_ ->
|
||||
[]
|
||||
|
||||
items =
|
||||
data
|
||||
|> List.filter (\x -> x.name == selected_dropdown)
|
||||
|> List.map (\x -> x.builders)
|
||||
|> List.concat
|
||||
|> List.foldr
|
||||
(\x y ->
|
||||
Dict.update x.name
|
||||
(\z ->
|
||||
Maybe.withDefault [] z
|
||||
|> (::) x
|
||||
|> Just
|
||||
)
|
||||
y
|
||||
)
|
||||
Dict.empty
|
||||
in
|
||||
table [ class "table table-hover" ]
|
||||
[ thead []
|
||||
[ tr []
|
||||
[ th []
|
||||
[ input
|
||||
[ type' "checkbox"
|
||||
, onCheck
|
||||
(\checked ->
|
||||
case checked of
|
||||
True ->
|
||||
SelectAll backend selected_dropdown
|
||||
|
||||
False ->
|
||||
SelectNone backend selected_dropdown
|
||||
)
|
||||
, checked
|
||||
(let
|
||||
all =
|
||||
items
|
||||
|> Dict.keys
|
||||
|
||||
current =
|
||||
Dict.get selected_dropdown model.selected
|
||||
|> Maybe.withDefault []
|
||||
in
|
||||
if current == all && current /= [] then
|
||||
True
|
||||
else
|
||||
False
|
||||
)
|
||||
]
|
||||
[]
|
||||
]
|
||||
, th []
|
||||
[ text
|
||||
(case backend of
|
||||
TaskclusterBackend ->
|
||||
"Worker Type"
|
||||
|
||||
BuildbotBackend ->
|
||||
"Builder"
|
||||
)
|
||||
]
|
||||
, th []
|
||||
[ text
|
||||
(case backend of
|
||||
TaskclusterBackend ->
|
||||
"Caches"
|
||||
|
||||
BuildbotBackend ->
|
||||
"Last clobber"
|
||||
)
|
||||
]
|
||||
]
|
||||
]
|
||||
, Html.Keyed.node "tbody"
|
||||
[]
|
||||
(Dict.values <|
|
||||
Dict.map
|
||||
(\builder_name builders ->
|
||||
( builder_name
|
||||
, tr []
|
||||
[ td []
|
||||
[ input
|
||||
[ type' "checkbox"
|
||||
, onCheck
|
||||
(\checked ->
|
||||
case checked of
|
||||
True ->
|
||||
AddSelected backend selected_dropdown builder_name
|
||||
|
||||
False ->
|
||||
RemoveSelected backend selected_dropdown builder_name
|
||||
)
|
||||
, checked
|
||||
(case Dict.get selected_dropdown model.selected of
|
||||
Just itemz ->
|
||||
List.member builder_name itemz
|
||||
|
||||
Nothing ->
|
||||
False
|
||||
)
|
||||
]
|
||||
[]
|
||||
]
|
||||
, td []
|
||||
[ text builder_name ]
|
||||
, td []
|
||||
[ ul [] <| List.map (\x -> li [] [ text (toString x.lastclobber) ]) builders
|
||||
]
|
||||
]
|
||||
)
|
||||
)
|
||||
items
|
||||
)
|
||||
]
|
||||
]
|
||||
|
||||
|
||||
view : Model -> Html Msg
|
||||
view model =
|
||||
div []
|
||||
[ h1 [] [ text "Clobberer" ]
|
||||
, p [] [ text "A repairer of buildbot builders and taskcluster worker types." ]
|
||||
, p [] [ text "TODO: link to documentation" ]
|
||||
, div [ class "row" ]
|
||||
[ div [ class "col-md-6" ]
|
||||
[ h2 [] [ text "Taskcluster" ]
|
||||
, viewBackend TaskclusterBackend model.taskcluster
|
||||
]
|
||||
, div [ class "col-md-6" ]
|
||||
[ h2 [] [ text "Buildbot" ]
|
||||
, viewBackend BuildbotBackend model.buildbot
|
||||
]
|
||||
]
|
||||
]
|
||||
|
||||
|
||||
initBackend : String -> ModelBackend
|
||||
initBackend title =
|
||||
{ title = title
|
||||
, data = NotAsked
|
||||
, clobber = NotAsked
|
||||
, clobber_messages = []
|
||||
, selected_dropdown = Just "mozilla-aurora"
|
||||
, selected = Dict.empty
|
||||
, selected_details = False
|
||||
}
|
||||
|
||||
|
||||
init : Model
|
||||
init =
|
||||
{ buildbot = initBackend "Buildbot"
|
||||
, taskcluster = initBackend "Taskcluster"
|
||||
, baseUrl = "data-clobberer-url not set"
|
||||
}
|
||||
|
||||
|
||||
initPage : String -> Model -> ( Model, Cmd Msg )
|
||||
initPage baseUrl model =
|
||||
( { buildbot = initBackend "Buildbot"
|
||||
, taskcluster = initBackend "Taskcluster"
|
||||
, baseUrl = baseUrl
|
||||
}
|
||||
, Cmd.batch
|
||||
[ fetchBackend (DataFetched BuildbotBackend) (baseUrl ++ "/buildbot")
|
||||
|
||||
--XXX, fetchBackend (DataFetched TaskclusterBackend) (baseUrl ++ "/taskcluster")
|
||||
]
|
||||
)
|
||||
|
||||
|
||||
update : Msg -> Model -> ( Model, Cmd Msg )
|
||||
update msg model =
|
||||
case msg of
|
||||
Clobber backend ->
|
||||
let
|
||||
encodeBody key items =
|
||||
( key, JsonEncode.list items )
|
||||
|
||||
( newModel, backendUrl, body ) =
|
||||
case backend of
|
||||
TaskclusterBackend ->
|
||||
( set (taskcluster => clobber) Loading model
|
||||
, model.baseUrl ++ "/taskcluster"
|
||||
, Http.empty
|
||||
-- TODO
|
||||
--, Body.BodyString
|
||||
-- <| JsonEncode.encode 0
|
||||
-- <| JsonEncode.object []
|
||||
-- <| List.map encodeBody
|
||||
-- <| Dict.toList model.taskcluster.selected
|
||||
)
|
||||
|
||||
BuildbotBackend ->
|
||||
( set (buildbot => clobber) Loading model
|
||||
, model.baseUrl ++ "/buildbot"
|
||||
, Http.empty
|
||||
-- TODO
|
||||
--, Body.BodyString
|
||||
-- <| JsonEncode.encode 0
|
||||
-- <| JsonEncode.object []
|
||||
-- <| List.map encodeBody
|
||||
-- <| Dict.toList model.buildout.selected
|
||||
)
|
||||
in
|
||||
( model, Cmd.none )
|
||||
|
||||
--(newModel, clobberBackend (Clobbered backend) backendUrl)
|
||||
Clobbered backend newData ->
|
||||
let
|
||||
-- TODO from newData set clobber_message
|
||||
newModel =
|
||||
newData
|
||||
|
||||
--case backend of
|
||||
-- TaskclusterBackend ->
|
||||
-- set (taskcluster => clobber) NotAsked model
|
||||
-- BuildbotBackend ->
|
||||
-- set (buildbot => clobber) NotAsked model
|
||||
in
|
||||
( model, Cmd.none )
|
||||
|
||||
FetchData backend ->
|
||||
let
|
||||
( newModel, backendUrl ) =
|
||||
case backend of
|
||||
TaskclusterBackend ->
|
||||
( set (taskcluster => data) Loading model
|
||||
, model.baseUrl ++ "/taskcluster"
|
||||
)
|
||||
|
||||
BuildbotBackend ->
|
||||
( set (buildbot => data) Loading model
|
||||
, model.baseUrl ++ "/buildbot"
|
||||
)
|
||||
in
|
||||
( newModel, fetchBackend (DataFetched backend) backendUrl )
|
||||
|
||||
DataFetched backend newData ->
|
||||
let
|
||||
newModel =
|
||||
case backend of
|
||||
TaskclusterBackend ->
|
||||
set (taskcluster => data) newData model
|
||||
|
||||
BuildbotBackend ->
|
||||
set (buildbot => data) newData model
|
||||
in
|
||||
( newModel, Cmd.none )
|
||||
|
||||
SelectDropdown backend name ->
|
||||
let
|
||||
newModel =
|
||||
case backend of
|
||||
TaskclusterBackend ->
|
||||
set (taskcluster => selected_dropdown) (Just name) model
|
||||
|
||||
BuildbotBackend ->
|
||||
set (buildbot => selected_dropdown) (Just name) model
|
||||
in
|
||||
( newModel, Cmd.none )
|
||||
|
||||
AddSelected backend key value ->
|
||||
let
|
||||
newModel =
|
||||
case backend of
|
||||
TaskclusterBackend ->
|
||||
let
|
||||
selected' =
|
||||
Dict.insert key values' model.taskcluster.selected
|
||||
|
||||
values' =
|
||||
if List.member value values then
|
||||
values
|
||||
else
|
||||
value :: values
|
||||
|
||||
values =
|
||||
Maybe.withDefault [] <|
|
||||
Dict.get key model.taskcluster.selected
|
||||
in
|
||||
set (taskcluster => selected) selected' model
|
||||
|
||||
BuildbotBackend ->
|
||||
let
|
||||
selected' =
|
||||
Dict.insert key values' model.buildbot.selected
|
||||
|
||||
values' =
|
||||
if List.member value values then
|
||||
values
|
||||
else
|
||||
value :: values
|
||||
|
||||
values =
|
||||
Maybe.withDefault [] <|
|
||||
Dict.get key model.buildbot.selected
|
||||
in
|
||||
set (buildbot => selected) selected' model
|
||||
in
|
||||
( newModel, Cmd.none )
|
||||
|
||||
RemoveSelected backend key value ->
|
||||
let
|
||||
newModel =
|
||||
case backend of
|
||||
TaskclusterBackend ->
|
||||
let
|
||||
selected' =
|
||||
Dict.insert key values' model.taskcluster.selected
|
||||
|
||||
values' =
|
||||
List.filter (\x -> x /= value) values
|
||||
|
||||
values =
|
||||
Maybe.withDefault [] <|
|
||||
Dict.get key model.taskcluster.selected
|
||||
in
|
||||
set (taskcluster => selected) selected' model
|
||||
|
||||
BuildbotBackend ->
|
||||
let
|
||||
selected' =
|
||||
Dict.insert key values' model.buildbot.selected
|
||||
|
||||
values' =
|
||||
List.filter (\x -> x /= value) values
|
||||
|
||||
values =
|
||||
Maybe.withDefault [] <|
|
||||
Dict.get key model.buildbot.selected
|
||||
in
|
||||
set (buildbot => selected) selected' model
|
||||
in
|
||||
( newModel, Cmd.none )
|
||||
|
||||
ToggleSelectedDetails backend ->
|
||||
let
|
||||
newModel =
|
||||
case backend of
|
||||
TaskclusterBackend ->
|
||||
let
|
||||
toggled_selected_details =
|
||||
not model.taskcluster.selected_details
|
||||
in
|
||||
set (taskcluster => selected_details) toggled_selected_details model
|
||||
|
||||
BuildbotBackend ->
|
||||
let
|
||||
toggled_selected_details =
|
||||
not model.buildbot.selected_details
|
||||
in
|
||||
set (buildbot => selected_details) toggled_selected_details model
|
||||
in
|
||||
( newModel, Cmd.none )
|
||||
|
||||
SelectAll backend selected_dropdown ->
|
||||
let
|
||||
newModel =
|
||||
case backend of
|
||||
TaskclusterBackend ->
|
||||
let
|
||||
data =
|
||||
case model.taskcluster.data of
|
||||
Success data ->
|
||||
data
|
||||
|
||||
_ ->
|
||||
[]
|
||||
|
||||
newItems =
|
||||
data
|
||||
|> List.filter (\x -> x.name == selected_dropdown)
|
||||
|> List.map (\x -> x.builders)
|
||||
|> List.concat
|
||||
|> List.foldr
|
||||
(\x y ->
|
||||
Dict.update x.name
|
||||
(\z ->
|
||||
Maybe.withDefault [] z
|
||||
|> (::) x
|
||||
|> Just
|
||||
)
|
||||
y
|
||||
)
|
||||
Dict.empty
|
||||
|> Dict.keys
|
||||
|
||||
newSelected =
|
||||
Dict.insert selected_dropdown newItems model.taskcluster.selected
|
||||
in
|
||||
set (taskcluster => selected) newSelected model
|
||||
|
||||
BuildbotBackend ->
|
||||
let
|
||||
data =
|
||||
case model.buildbot.data of
|
||||
Success data ->
|
||||
data
|
||||
|
||||
_ ->
|
||||
[]
|
||||
|
||||
newItems =
|
||||
data
|
||||
|> List.filter (\x -> x.name == selected_dropdown)
|
||||
|> List.map (\x -> x.builders)
|
||||
|> List.concat
|
||||
|> List.foldr
|
||||
(\x y ->
|
||||
Dict.update x.name
|
||||
(\z ->
|
||||
Maybe.withDefault [] z
|
||||
|> (::) x
|
||||
|> Just
|
||||
)
|
||||
y
|
||||
)
|
||||
Dict.empty
|
||||
|> Dict.keys
|
||||
|
||||
newSelected =
|
||||
Dict.insert selected_dropdown newItems model.buildbot.selected
|
||||
in
|
||||
set (buildbot => selected) newSelected model
|
||||
in
|
||||
( newModel, Cmd.none )
|
||||
|
||||
SelectNone backend selected_dropdown ->
|
||||
let
|
||||
newModel =
|
||||
case backend of
|
||||
TaskclusterBackend ->
|
||||
let
|
||||
newSelected =
|
||||
Dict.insert selected_dropdown [] model.taskcluster.selected
|
||||
in
|
||||
set (taskcluster => selected) newSelected model
|
||||
|
||||
BuildbotBackend ->
|
||||
let
|
||||
newSelected =
|
||||
Dict.insert selected_dropdown [] model.buildbot.selected
|
||||
in
|
||||
set (buildbot => selected) newSelected model
|
||||
in
|
||||
( newModel, Cmd.none )
|
||||
|
||||
|
||||
|
||||
-- IMPLEMENTATION
|
||||
|
||||
|
||||
buildbot : Focus { record | buildbot : a } a
|
||||
buildbot =
|
||||
create .buildbot (\f r -> { r | buildbot = f r.buildbot })
|
||||
|
||||
|
||||
taskcluster : Focus { record | taskcluster : a } a
|
||||
taskcluster =
|
||||
create .taskcluster (\f r -> { r | taskcluster = f r.taskcluster })
|
||||
|
||||
|
||||
data : Focus { record | data : a } a
|
||||
data =
|
||||
create .data (\f r -> { r | data = f r.data })
|
||||
|
||||
|
||||
clobber : Focus { record | clobber : a } a
|
||||
clobber =
|
||||
create .clobber (\f r -> { r | clobber = f r.clobber })
|
||||
|
||||
|
||||
selected_dropdown : Focus { record | selected_dropdown : a } a
|
||||
selected_dropdown =
|
||||
create .selected_dropdown (\f r -> { r | selected_dropdown = f r.selected_dropdown })
|
||||
|
||||
|
||||
selected : Focus { record | selected : a } a
|
||||
selected =
|
||||
create .selected (\f r -> { r | selected = f r.selected })
|
||||
|
||||
|
||||
selected_details : Focus { record | selected_details : a } a
|
||||
selected_details =
|
||||
create .selected_details (\f r -> { r | selected_details = f r.selected_details })
|
||||
|
||||
|
||||
decodeFetchData : JsonDecode.Decoder BackendData
|
||||
decodeFetchData =
|
||||
JsonDecode.list
|
||||
(JsonDecode.object2 BackendBranch
|
||||
("name" := JsonDecode.string)
|
||||
("builders"
|
||||
:= JsonDecode.list
|
||||
(JsonDecode.object6 BackendBuilder
|
||||
("branch" := JsonDecode.string)
|
||||
("builddir" := JsonDecode.string)
|
||||
("lastclobber" := JsonDecode.int)
|
||||
("name" := JsonDecode.string)
|
||||
("slave" := JsonDecode.string)
|
||||
("who" := JsonDecode.string)
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
decodeClobberData : JsonDecode.Decoder BackendResponse
|
||||
decodeClobberData =
|
||||
JsonDecode.list JsonDecode.string
|
||||
|
||||
|
||||
fetchBackend : (WebData BackendData -> Msg) -> String -> Cmd Msg
|
||||
fetchBackend afterMsg url =
|
||||
getJson decodeFetchData url
|
||||
|> RemoteData.asCmd
|
||||
|> Cmd.map afterMsg
|
||||
|
||||
|
||||
clobberBackend : (WebData BackendResponse -> Msg) -> String -> Http.Body -> Cmd Msg
|
||||
clobberBackend afterMsg url body =
|
||||
postJson decodeClobberData url body
|
||||
|> RemoteData.asCmd
|
||||
|> Cmd.map afterMsg
|
||||
|
||||
|
||||
|
||||
-- UTILS
|
||||
|
||||
|
||||
getJson : JsonDecode.Decoder value -> String -> Task Http.Error value
|
||||
getJson decoder url =
|
||||
let
|
||||
request =
|
||||
{ verb = "GET"
|
||||
, headers = [ ( "Accept", "application/json" ) ]
|
||||
, url = url
|
||||
, body = Http.empty
|
||||
}
|
||||
in
|
||||
Http.fromJson decoder
|
||||
(Http.send Http.defaultSettings request)
|
||||
|
||||
|
||||
postJson : JsonDecode.Decoder value -> String -> Http.Body -> Task Http.Error value
|
||||
postJson decoder url body =
|
||||
let
|
||||
request =
|
||||
{ verb = "POST"
|
||||
, headers = [ ( "Accept", "application/json" ) ]
|
||||
, url = url
|
||||
, body = body
|
||||
}
|
||||
in
|
||||
Http.fromJson decoder
|
||||
(Http.send Http.defaultSettings request)
|
|
@ -1,157 +0,0 @@
|
|||
module App.Form exposing (..)
|
||||
|
||||
import Form
|
||||
import Form.Input
|
||||
import Html exposing (..)
|
||||
import Html.Attributes exposing (..)
|
||||
|
||||
|
||||
maybeAppend : Maybe a -> (a -> b) -> List b -> List b
|
||||
maybeAppend maybeValue f =
|
||||
maybeValue
|
||||
|> Maybe.map (\x -> [ f x ])
|
||||
|> Maybe.withDefault []
|
||||
|> List.append
|
||||
|
||||
|
||||
maybeAppendError : Maybe a -> List (Html b) -> List (Html b)
|
||||
maybeAppendError maybeError =
|
||||
maybeAppend maybeError
|
||||
(\x ->
|
||||
div [ class "form-control-feedback" ]
|
||||
[ text (toString x) ]
|
||||
)
|
||||
|
||||
|
||||
maybeAppendHelp : Maybe String -> List (Html a) -> List (Html a)
|
||||
maybeAppendHelp maybeHelp =
|
||||
maybeAppend maybeHelp
|
||||
(\x ->
|
||||
small [ class "form-text text-muted" ]
|
||||
[ text x ]
|
||||
)
|
||||
|
||||
|
||||
maybeAppendLabel : Maybe String -> List (Html a) -> List (Html a)
|
||||
maybeAppendLabel maybeLabel =
|
||||
maybeAppend maybeLabel
|
||||
(\x ->
|
||||
label [ class "control-label" ]
|
||||
[ text x ]
|
||||
)
|
||||
|
||||
|
||||
errorClass : Maybe error -> String
|
||||
errorClass maybeError =
|
||||
maybeError
|
||||
|> Maybe.map (\_ -> "has-danger")
|
||||
|> Maybe.withDefault ""
|
||||
|
||||
|
||||
viewField :
|
||||
Maybe a
|
||||
-> Maybe String
|
||||
-> List (Html Form.Msg)
|
||||
-> Html Form.Msg
|
||||
-> Html Form.Msg
|
||||
viewField maybeError maybeLabel helpNodes inputNode =
|
||||
div
|
||||
[ class ("form-group " ++ errorClass maybeError) ]
|
||||
([]
|
||||
|> List.append helpNodes
|
||||
|> maybeAppendError maybeError
|
||||
|> List.append [ inputNode ]
|
||||
|> maybeAppendLabel maybeLabel
|
||||
)
|
||||
|
||||
|
||||
viewTextInput :
|
||||
Form.FieldState () String
|
||||
-> String
|
||||
-> List (Html Form.Msg)
|
||||
-> List (Attribute Form.Msg)
|
||||
-> Html Form.Msg
|
||||
viewTextInput state labelText helpNodes attributes =
|
||||
viewField
|
||||
state.liveError
|
||||
(Just labelText)
|
||||
helpNodes
|
||||
(Form.Input.textInput state
|
||||
(attributes
|
||||
|> List.append
|
||||
[ class "form-control"
|
||||
, value (Maybe.withDefault "" state.value)
|
||||
]
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
viewSelectInput :
|
||||
Form.FieldState a String
|
||||
-> String
|
||||
-> List (Html Form.Msg)
|
||||
-> List ( String, String )
|
||||
-> List (Attribute Form.Msg)
|
||||
-> Html Form.Msg
|
||||
viewSelectInput state labelText helpNodes options attributes =
|
||||
viewField
|
||||
state.liveError
|
||||
(Just labelText)
|
||||
helpNodes
|
||||
(Form.Input.selectInput
|
||||
options
|
||||
state
|
||||
(attributes |> List.append [ class "form-control" ])
|
||||
)
|
||||
|
||||
|
||||
viewRadioInput :
|
||||
Form.FieldState a String
|
||||
-> String
|
||||
-> List (Html Form.Msg)
|
||||
-> List ( String, String )
|
||||
-> List (Attribute Form.Msg)
|
||||
-> Html Form.Msg
|
||||
viewRadioInput state labelText helpNodes options attributes =
|
||||
let
|
||||
item ( v, l ) =
|
||||
label
|
||||
[ class "radio-inline" ]
|
||||
[ Form.Input.radioInput v state []
|
||||
, text l
|
||||
]
|
||||
in
|
||||
viewField
|
||||
state.liveError
|
||||
(Just labelText)
|
||||
helpNodes
|
||||
(div [] (List.map item options))
|
||||
|
||||
|
||||
viewCheckboxInput :
|
||||
Form.FieldState a Bool
|
||||
-> String
|
||||
-> Html Form.Msg
|
||||
viewCheckboxInput state labelText =
|
||||
label
|
||||
[ class "custom-control custom-checkbox" ]
|
||||
[ Form.Input.checkboxInput state [ class "custom-control-input" ]
|
||||
, span
|
||||
[ class "custom-control-indicator" ]
|
||||
[]
|
||||
, span
|
||||
[ class "custom-control-description" ]
|
||||
[ text labelText ]
|
||||
]
|
||||
|
||||
|
||||
viewButton : String -> List (Attribute msg) -> Html msg
|
||||
viewButton labelText attributes =
|
||||
button
|
||||
(attributes
|
||||
|> List.append
|
||||
[ type_ "submit"
|
||||
, class "btn btn-outline-primary"
|
||||
]
|
||||
)
|
||||
[ text labelText ]
|
|
@ -1,36 +0,0 @@
|
|||
module App.Home exposing (..)
|
||||
|
||||
import App
|
||||
import App.TreeStatus.Types
|
||||
import Html exposing (..)
|
||||
import Html.Attributes exposing (..)
|
||||
import Utils
|
||||
|
||||
|
||||
viewCard : String -> String -> Attribute msg -> Html msg
|
||||
viewCard title description href =
|
||||
div [ class "col-sm-6" ]
|
||||
[ a
|
||||
[ class "card card-block"
|
||||
, href
|
||||
]
|
||||
[ h4 [ class "card-title" ]
|
||||
[ text title ]
|
||||
, p [ class "card-text" ]
|
||||
[ text description ]
|
||||
]
|
||||
]
|
||||
|
||||
|
||||
view : App.Model -> Html App.Msg
|
||||
view model =
|
||||
div [ class "row" ]
|
||||
[ viewCard
|
||||
"TreeStatus"
|
||||
"Current status of Mozilla's version-control repositories."
|
||||
(Utils.onClick <| App.NavigateTo (App.TreeStatusRoute App.TreeStatus.Types.ShowTreesRoute))
|
||||
, viewCard
|
||||
"ToolTool"
|
||||
"Tooltool is tool for fetching binary artifacts for builds and tests. The web interface lets you browse the files currently available from the service."
|
||||
(href "/tooltool")
|
||||
]
|
|
@ -1,158 +0,0 @@
|
|||
module App.Layout exposing (..)
|
||||
|
||||
import App
|
||||
import App.Utils
|
||||
import Html exposing (..)
|
||||
import Html.Attributes exposing (..)
|
||||
import TaskclusterLogin
|
||||
import Utils
|
||||
|
||||
|
||||
viewDropdown : String -> List (Html msg) -> List (Html msg)
|
||||
viewDropdown title pages =
|
||||
[ div [ class "dropdown" ]
|
||||
[ a
|
||||
[ class "nav-link dropdown-toggle"
|
||||
, id ("dropdown" ++ title)
|
||||
, href "#"
|
||||
, attribute "data-toggle" "dropdown"
|
||||
, attribute "aria-haspopup" "true"
|
||||
, attribute "aria-expanded" "false"
|
||||
]
|
||||
[ text title ]
|
||||
, div
|
||||
[ class "dropdown-menu dropdown-menu-right"
|
||||
, attribute "aria-labelledby" "dropdownServices"
|
||||
]
|
||||
pages
|
||||
]
|
||||
]
|
||||
|
||||
|
||||
viewUser : App.Model -> List (Html App.Msg)
|
||||
viewUser model =
|
||||
case model.user.credentials of
|
||||
Just user ->
|
||||
viewDropdown user.clientId
|
||||
[ a
|
||||
[ class "dropdown-item"
|
||||
, href "https://tools.taskcluster.net/credentials"
|
||||
, target "_blank"
|
||||
]
|
||||
[ text "Manage credentials" ]
|
||||
, a
|
||||
[ Utils.onClick (App.NavigateTo App.LogoutRoute)
|
||||
, href "#"
|
||||
, class "dropdown-item"
|
||||
]
|
||||
[ text "Logout" ]
|
||||
]
|
||||
|
||||
Nothing ->
|
||||
let
|
||||
loginMsg =
|
||||
App.TaskclusterLoginMsg <| TaskclusterLogin.Login
|
||||
in
|
||||
[ a
|
||||
[ Utils.onClick loginMsg
|
||||
, href "#"
|
||||
, class "nav-link"
|
||||
]
|
||||
[ text "Login" ]
|
||||
]
|
||||
|
||||
|
||||
viewNavBar : App.Model -> List (Html App.Msg)
|
||||
viewNavBar model =
|
||||
[ a
|
||||
[ Utils.onClick (App.NavigateTo App.HomeRoute)
|
||||
, href "#"
|
||||
, class "navbar-brand"
|
||||
]
|
||||
[ text "Release Engineering" ]
|
||||
, ul [ class "navbar-nav" ]
|
||||
[ li [ class "nav-item" ] (viewUser model)
|
||||
]
|
||||
]
|
||||
|
||||
|
||||
viewFooter : App.Model -> List (Html App.Msg)
|
||||
viewFooter model =
|
||||
[ hr [] []
|
||||
, ul []
|
||||
[ li []
|
||||
[ a [ href model.docsUrl ]
|
||||
[ text "Documentation" ]
|
||||
]
|
||||
, li []
|
||||
[ a [ href "https://github.com/mozilla/release-services/blob/master/CONTRIBUTING.rst" ]
|
||||
[ text "Contribute" ]
|
||||
]
|
||||
, li []
|
||||
[ a [ href "https://github.com/mozilla/release-services/issues/new" ]
|
||||
[ text "Contact" ]
|
||||
]
|
||||
]
|
||||
, div []
|
||||
[ text "version: "
|
||||
, a [ href ("https://github.com/mozilla/release-services/releases/tag/v" ++ model.version) ]
|
||||
[ text model.version ]
|
||||
]
|
||||
]
|
||||
|
||||
|
||||
viewNotFound : App.Model -> Html.Html App.Msg
|
||||
viewNotFound model =
|
||||
div [ class "hero-unit" ]
|
||||
[ h1 [] [ text "Page Not Found" ] ]
|
||||
|
||||
|
||||
view : (App.Model -> Html.Html App.Msg) -> App.Model -> Html.Html App.Msg
|
||||
view viewRoute model =
|
||||
let
|
||||
routeName =
|
||||
case model.route of
|
||||
App.HomeRoute ->
|
||||
"home"
|
||||
|
||||
App.ToolToolRoute ->
|
||||
"tooltool"
|
||||
|
||||
App.TreeStatusRoute _ ->
|
||||
"treestatus"
|
||||
|
||||
_ ->
|
||||
""
|
||||
|
||||
isLoading =
|
||||
case model.user.tokens of
|
||||
Just _ ->
|
||||
case model.user.credentials of
|
||||
Just _ ->
|
||||
if List.length model.userScopes.scopes == 0 then
|
||||
True
|
||||
else
|
||||
False
|
||||
|
||||
_ ->
|
||||
True
|
||||
|
||||
_ ->
|
||||
False
|
||||
in
|
||||
div [ id ("page-" ++ routeName) ]
|
||||
[ nav
|
||||
[ id "navbar"
|
||||
, class "navbar navbar-toggleable-md bg-faded navbar-inverse"
|
||||
]
|
||||
[ div [ class "container" ] (viewNavBar model) ]
|
||||
, div [ id "content" ]
|
||||
[ div [ class "container" ]
|
||||
[ if isLoading then
|
||||
App.Utils.loading
|
||||
else
|
||||
viewRoute model
|
||||
]
|
||||
]
|
||||
, footer [ class "container" ] (viewFooter model)
|
||||
]
|
|
@ -1,39 +0,0 @@
|
|||
module App.Tokens exposing (..)
|
||||
|
||||
import App.Types
|
||||
import Html exposing (..)
|
||||
import UrlParser
|
||||
|
||||
|
||||
type alias Model =
|
||||
{}
|
||||
|
||||
|
||||
type Msg
|
||||
= Load
|
||||
|
||||
|
||||
page : a -> App.Types.Page a b
|
||||
page outRoute =
|
||||
{ title = "Tokens"
|
||||
, description =
|
||||
"Tokens are used to allow automated services to authenticate to Releng API without being tied to a user's identity."
|
||||
, matcher = UrlParser.map outRoute (UrlParser.s "tokens")
|
||||
}
|
||||
|
||||
|
||||
init : Model
|
||||
init =
|
||||
{}
|
||||
|
||||
|
||||
update : Msg -> Model -> ( Model, Cmd Msg )
|
||||
update msg model =
|
||||
case msg of
|
||||
Load ->
|
||||
( model, Cmd.none )
|
||||
|
||||
|
||||
view : Model -> Html Msg
|
||||
view model =
|
||||
div [] [ text "Not yet implemented!" ]
|
|
@ -1,39 +0,0 @@
|
|||
module App.ToolTool exposing (..)
|
||||
|
||||
import App.Types
|
||||
import Html exposing (..)
|
||||
import UrlParser
|
||||
|
||||
|
||||
type alias Model =
|
||||
{}
|
||||
|
||||
|
||||
type Msg
|
||||
= Load
|
||||
|
||||
|
||||
page : a -> App.Types.Page a b
|
||||
page outRoute =
|
||||
{ title = "ToolTool"
|
||||
, description =
|
||||
"Tooltool is tool for fetching binary artifacts for builds and tests. The web interface lets you browse the files currently available from the service."
|
||||
, matcher = UrlParser.map outRoute (UrlParser.s "tooltool")
|
||||
}
|
||||
|
||||
|
||||
init : Model
|
||||
init =
|
||||
{}
|
||||
|
||||
|
||||
update : Msg -> Model -> ( Model, Cmd Msg )
|
||||
update msg model =
|
||||
case msg of
|
||||
Load ->
|
||||
( model, Cmd.none )
|
||||
|
||||
|
||||
view : Model -> Html Msg
|
||||
view model =
|
||||
div [] [ text "Not yet implemented!" ]
|
|
@ -1,625 +0,0 @@
|
|||
module App.TreeStatus exposing (..)
|
||||
|
||||
import App.TreeStatus.Api
|
||||
import App.TreeStatus.Form
|
||||
import App.TreeStatus.Types
|
||||
import App.TreeStatus.View
|
||||
import App.Types
|
||||
import App.UserScopes
|
||||
import App.Utils
|
||||
import Form
|
||||
import Hawk
|
||||
import Html exposing (..)
|
||||
import Html.Attributes exposing (..)
|
||||
import Http
|
||||
import Navigation
|
||||
import RemoteData
|
||||
import TaskclusterLogin
|
||||
import Title
|
||||
import UrlParser exposing ((</>))
|
||||
import Utils
|
||||
|
||||
|
||||
--
|
||||
-- ROUTING
|
||||
--
|
||||
|
||||
|
||||
routeParser : UrlParser.Parser (App.TreeStatus.Types.Route -> a) a
|
||||
routeParser =
|
||||
UrlParser.oneOf
|
||||
[ UrlParser.map App.TreeStatus.Types.ShowTreesRoute UrlParser.top
|
||||
, UrlParser.map App.TreeStatus.Types.AddTreeRoute (UrlParser.s "add")
|
||||
, UrlParser.map App.TreeStatus.Types.UpdateTreesRoute (UrlParser.s "update")
|
||||
, UrlParser.map App.TreeStatus.Types.DeleteTreesRoute (UrlParser.s "delete")
|
||||
, UrlParser.map App.TreeStatus.Types.ShowTreeRoute (UrlParser.s "show" </> UrlParser.string)
|
||||
]
|
||||
|
||||
|
||||
reverseRoute : App.TreeStatus.Types.Route -> String
|
||||
reverseRoute route =
|
||||
case route of
|
||||
App.TreeStatus.Types.ShowTreesRoute ->
|
||||
"/treestatus"
|
||||
|
||||
App.TreeStatus.Types.AddTreeRoute ->
|
||||
"/treestatus/add"
|
||||
|
||||
App.TreeStatus.Types.UpdateTreesRoute ->
|
||||
"/treestatus/update"
|
||||
|
||||
App.TreeStatus.Types.DeleteTreesRoute ->
|
||||
"/treestatus/delete"
|
||||
|
||||
App.TreeStatus.Types.ShowTreeRoute name ->
|
||||
"/treestatus/show/" ++ name
|
||||
|
||||
|
||||
page : (App.TreeStatus.Types.Route -> a) -> App.Types.Page a b
|
||||
page outRoute =
|
||||
{ title = "TreeStatus"
|
||||
, description = "Current status of Mozilla's version-control repositories."
|
||||
, matcher = UrlParser.map outRoute (UrlParser.s "treestatus" </> routeParser)
|
||||
}
|
||||
|
||||
|
||||
|
||||
--
|
||||
-- UPDATE
|
||||
--
|
||||
|
||||
|
||||
init : String -> App.TreeStatus.Types.Model App.TreeStatus.Form.AddTree App.TreeStatus.Form.UpdateTree App.TreeStatus.Form.UpdateStack App.TreeStatus.Form.UpdateLog
|
||||
init url =
|
||||
{ baseUrl = url
|
||||
, treesAlerts = []
|
||||
, trees = RemoteData.NotAsked
|
||||
, treesSelected = []
|
||||
, tree = RemoteData.NotAsked
|
||||
, treeLogs = RemoteData.NotAsked
|
||||
, treeLogsAll = RemoteData.NotAsked
|
||||
, showMoreTreeLogs = False
|
||||
, formAddTree = App.TreeStatus.Form.initAddTree
|
||||
, formUpdateTree = App.TreeStatus.Form.initUpdateTree ""
|
||||
, showUpdateStackForm = Nothing
|
||||
, formUpdateStack = App.TreeStatus.Form.initUpdateStack ""
|
||||
, recentChangesAlerts = []
|
||||
, recentChanges = RemoteData.NotAsked
|
||||
, deleteTreesConfirm = False
|
||||
, deleteError = Nothing
|
||||
, showUpdateLog = Nothing
|
||||
, formUpdateLog = App.TreeStatus.Form.initUpdateLog ""
|
||||
}
|
||||
|
||||
|
||||
update :
|
||||
App.TreeStatus.Types.Route
|
||||
-> App.TreeStatus.Types.Msg
|
||||
-> App.TreeStatus.Types.Model App.TreeStatus.Form.AddTree App.TreeStatus.Form.UpdateTree App.TreeStatus.Form.UpdateStack App.TreeStatus.Form.UpdateLog
|
||||
-> ( App.TreeStatus.Types.Model App.TreeStatus.Form.AddTree App.TreeStatus.Form.UpdateTree App.TreeStatus.Form.UpdateStack App.TreeStatus.Form.UpdateLog, Cmd App.TreeStatus.Types.Msg, Maybe Hawk.Request )
|
||||
update currentRoute msg model =
|
||||
case msg of
|
||||
App.TreeStatus.Types.NavigateTo route ->
|
||||
let
|
||||
fetchIfNotAsked data fetch =
|
||||
if data == RemoteData.NotAsked then
|
||||
fetch
|
||||
else
|
||||
Cmd.none
|
||||
|
||||
fetchRecentChangesIfNotAsked =
|
||||
fetchIfNotAsked
|
||||
model.recentChanges
|
||||
(App.TreeStatus.Api.fetchRecentChanges model.baseUrl)
|
||||
|
||||
showAllTrees =
|
||||
( { model | trees = RemoteData.Loading }
|
||||
, Cmd.batch
|
||||
[ Title.set_title "TreeStatus"
|
||||
, App.TreeStatus.Api.fetchTrees model.baseUrl
|
||||
, App.TreeStatus.Api.fetchRecentChanges model.baseUrl
|
||||
]
|
||||
)
|
||||
|
||||
redirectToTrees =
|
||||
List.isEmpty model.treesSelected
|
||||
&& (currentRoute
|
||||
== App.TreeStatus.Types.UpdateTreesRoute
|
||||
|| currentRoute
|
||||
== App.TreeStatus.Types.DeleteTreesRoute
|
||||
)
|
||||
|
||||
( newModel, newCmd ) =
|
||||
-- in case there are no trees selected and we end up on update
|
||||
-- url we should redirect to ShowTreesRoute
|
||||
if redirectToTrees then
|
||||
showAllTrees
|
||||
else
|
||||
case route of
|
||||
App.TreeStatus.Types.ShowTreesRoute ->
|
||||
showAllTrees
|
||||
|
||||
App.TreeStatus.Types.AddTreeRoute ->
|
||||
( { model | treesAlerts = [] }
|
||||
, fetchIfNotAsked
|
||||
model.recentChanges
|
||||
(App.TreeStatus.Api.fetchRecentChanges model.baseUrl)
|
||||
)
|
||||
|
||||
App.TreeStatus.Types.UpdateTreesRoute ->
|
||||
( { model | treesAlerts = [] }
|
||||
, fetchRecentChangesIfNotAsked
|
||||
)
|
||||
|
||||
App.TreeStatus.Types.DeleteTreesRoute ->
|
||||
( { model
|
||||
| treesAlerts = []
|
||||
, deleteTreesConfirm = False
|
||||
, deleteError = Nothing
|
||||
}
|
||||
, fetchRecentChangesIfNotAsked
|
||||
)
|
||||
|
||||
App.TreeStatus.Types.ShowTreeRoute name ->
|
||||
( { model
|
||||
| tree = RemoteData.Loading
|
||||
, treeLogs = RemoteData.Loading
|
||||
, treeLogsAll = RemoteData.NotAsked
|
||||
, treesSelected = [ name ]
|
||||
}
|
||||
, Cmd.batch
|
||||
[ App.TreeStatus.Api.fetchTree model.baseUrl name
|
||||
, App.TreeStatus.Api.fetchTreeLogs model.baseUrl name False
|
||||
, fetchRecentChangesIfNotAsked
|
||||
]
|
||||
)
|
||||
|
||||
newRoute =
|
||||
if redirectToTrees then
|
||||
App.TreeStatus.Types.ShowTreesRoute
|
||||
else
|
||||
route
|
||||
in
|
||||
( newModel
|
||||
, Cmd.batch
|
||||
[ reverseRoute newRoute
|
||||
|> Navigation.newUrl
|
||||
, newCmd
|
||||
]
|
||||
, Nothing
|
||||
)
|
||||
|
||||
App.TreeStatus.Types.GetTreesResult trees ->
|
||||
( { model | trees = trees }, Cmd.none, Nothing )
|
||||
|
||||
App.TreeStatus.Types.GetTreeResult tree ->
|
||||
( { model | tree = tree }, Cmd.none, Nothing )
|
||||
|
||||
App.TreeStatus.Types.GetTreeLogsResult logs ->
|
||||
( { model | treeLogs = logs }, Cmd.none, Nothing )
|
||||
|
||||
App.TreeStatus.Types.GetTreeLogsAllResult logs ->
|
||||
( { model | treeLogsAll = logs }, Cmd.none, Nothing )
|
||||
|
||||
App.TreeStatus.Types.GetRecentChangesResult recentChanges ->
|
||||
( { model | recentChanges = recentChanges }, Cmd.none, Nothing )
|
||||
|
||||
App.TreeStatus.Types.GetTreeLogs name all ->
|
||||
( model
|
||||
, App.TreeStatus.Api.fetchTreeLogs model.baseUrl name True
|
||||
, Nothing
|
||||
)
|
||||
|
||||
App.TreeStatus.Types.FormAddTreeMsg formMsg ->
|
||||
let
|
||||
( newModel, hawkRequest ) =
|
||||
App.TreeStatus.Form.updateAddTree model formMsg
|
||||
in
|
||||
( newModel
|
||||
, Cmd.none
|
||||
, hawkRequest
|
||||
)
|
||||
|
||||
App.TreeStatus.Types.FormAddTreeResult result ->
|
||||
( { model | treesAlerts = App.Utils.getAlerts result }
|
||||
, Cmd.batch
|
||||
[ Utils.performMsg App.TreeStatus.Form.resetAddTree
|
||||
|> Cmd.map App.TreeStatus.Types.FormAddTreeMsg
|
||||
, reverseRoute App.TreeStatus.Types.ShowTreesRoute
|
||||
|> Navigation.newUrl
|
||||
]
|
||||
, Nothing
|
||||
)
|
||||
|
||||
App.TreeStatus.Types.FormUpdateTreesMsg formMsg ->
|
||||
let
|
||||
( newModel, hawkRequest ) =
|
||||
App.TreeStatus.Form.updateUpdateTree currentRoute model formMsg
|
||||
in
|
||||
( newModel
|
||||
, Cmd.none
|
||||
, hawkRequest
|
||||
)
|
||||
|
||||
App.TreeStatus.Types.FormUpdateTreesResult result ->
|
||||
( { model | treesAlerts = App.Utils.getAlerts result }
|
||||
, Cmd.batch
|
||||
[ App.TreeStatus.Api.fetchTrees model.baseUrl
|
||||
, App.TreeStatus.Api.fetchRecentChanges model.baseUrl
|
||||
, Utils.performMsg App.TreeStatus.Form.resetUpdateTree
|
||||
|> Cmd.map App.TreeStatus.Types.FormUpdateTreesMsg
|
||||
, reverseRoute App.TreeStatus.Types.ShowTreesRoute
|
||||
|> Navigation.newUrl
|
||||
]
|
||||
, Nothing
|
||||
)
|
||||
|
||||
App.TreeStatus.Types.SelectAllTrees ->
|
||||
let
|
||||
treesSelected =
|
||||
case model.trees of
|
||||
RemoteData.Success trees ->
|
||||
List.map .name trees
|
||||
|
||||
_ ->
|
||||
[]
|
||||
in
|
||||
( { model | treesSelected = treesSelected }
|
||||
, Cmd.none
|
||||
, Nothing
|
||||
)
|
||||
|
||||
App.TreeStatus.Types.SelectTree name ->
|
||||
let
|
||||
treesSelected =
|
||||
if List.member name model.treesSelected then
|
||||
model.treesSelected
|
||||
else
|
||||
name :: model.treesSelected
|
||||
in
|
||||
( { model | treesSelected = treesSelected }
|
||||
, Cmd.none
|
||||
, Nothing
|
||||
)
|
||||
|
||||
App.TreeStatus.Types.UnselectAllTrees ->
|
||||
( { model | treesSelected = [] }
|
||||
, Cmd.none
|
||||
, Nothing
|
||||
)
|
||||
|
||||
App.TreeStatus.Types.UnselectTree name ->
|
||||
let
|
||||
treesSelected =
|
||||
List.filter (\x -> x /= name) model.treesSelected
|
||||
in
|
||||
( { model | treesSelected = treesSelected }
|
||||
, Cmd.none
|
||||
, Nothing
|
||||
)
|
||||
|
||||
App.TreeStatus.Types.DeleteTrees ->
|
||||
let
|
||||
filterOutTrees =
|
||||
List.filter
|
||||
(\x -> Basics.not (List.member x.name model.treesSelected))
|
||||
|
||||
filterTrees =
|
||||
List.filter
|
||||
(\x -> List.member x.name model.treesSelected)
|
||||
|
||||
treesToDelete =
|
||||
model.trees
|
||||
|> RemoteData.map filterTrees
|
||||
|> RemoteData.withDefault []
|
||||
|
||||
request =
|
||||
Hawk.Request
|
||||
"DeleteTrees"
|
||||
"DELETE"
|
||||
(model.baseUrl ++ "/trees2")
|
||||
[ Http.header "Accept" "application/json" ]
|
||||
(Http.jsonBody (App.TreeStatus.Api.encoderTreeNames treesToDelete))
|
||||
in
|
||||
if model.deleteTreesConfirm then
|
||||
( { model
|
||||
| treesSelected = []
|
||||
, trees = RemoteData.map filterOutTrees model.trees
|
||||
}
|
||||
, Cmd.none
|
||||
, Just request
|
||||
)
|
||||
else
|
||||
( { model | deleteError = Just "You need to confirm to be able to delete tree(s)." }
|
||||
, Cmd.none
|
||||
, Nothing
|
||||
)
|
||||
|
||||
App.TreeStatus.Types.DeleteTreesResult result ->
|
||||
( { model | treesAlerts = App.Utils.getAlerts result }
|
||||
, reverseRoute App.TreeStatus.Types.ShowTreesRoute
|
||||
|> Navigation.newUrl
|
||||
, Nothing
|
||||
)
|
||||
|
||||
App.TreeStatus.Types.RevertChange stack ->
|
||||
( { model | recentChangesAlerts = [] }
|
||||
, Cmd.none
|
||||
, Just
|
||||
(Hawk.Request
|
||||
"RevertChange"
|
||||
"DELETE"
|
||||
(model.baseUrl ++ "/stack2/restore/" ++ toString stack)
|
||||
[ Http.header "Accept" "application/json" ]
|
||||
Http.emptyBody
|
||||
)
|
||||
)
|
||||
|
||||
App.TreeStatus.Types.DiscardChange stack ->
|
||||
( { model | recentChangesAlerts = [] }
|
||||
, Cmd.none
|
||||
, Just
|
||||
(Hawk.Request
|
||||
"DiscardChange"
|
||||
"DELETE"
|
||||
(model.baseUrl ++ "/stack2/discard/" ++ toString stack)
|
||||
[ Http.header "Accept" "application/json" ]
|
||||
Http.emptyBody
|
||||
)
|
||||
)
|
||||
|
||||
App.TreeStatus.Types.RecentChangeResult result ->
|
||||
( { model | recentChangesAlerts = App.Utils.getAlerts result }
|
||||
, Cmd.batch
|
||||
[ App.TreeStatus.Api.fetchRecentChanges model.baseUrl
|
||||
, App.TreeStatus.Api.fetchTrees model.baseUrl
|
||||
]
|
||||
, Nothing
|
||||
)
|
||||
|
||||
App.TreeStatus.Types.DeleteTreesConfirmToggle ->
|
||||
( { model | deleteTreesConfirm = Basics.not model.deleteTreesConfirm }
|
||||
, Cmd.none
|
||||
, Nothing
|
||||
)
|
||||
|
||||
App.TreeStatus.Types.UpdateStackShow stack ->
|
||||
let
|
||||
( reason, category, status ) =
|
||||
case model.recentChanges of
|
||||
RemoteData.Success recentChanges ->
|
||||
recentChanges
|
||||
|> List.filter (\x -> x.id == stack)
|
||||
|> List.head
|
||||
|> Maybe.map
|
||||
(\x ->
|
||||
x.trees
|
||||
|> List.head
|
||||
|> Maybe.map
|
||||
(\y ->
|
||||
( y.last_state.current_reason
|
||||
, y.last_state.current_tags |> List.head |> Maybe.withDefault ""
|
||||
, y.last_state.current_status
|
||||
)
|
||||
)
|
||||
|> Maybe.withDefault ( "", "", "" )
|
||||
)
|
||||
|> Maybe.withDefault ( "", "", "" )
|
||||
|
||||
_ ->
|
||||
( "", "", "" )
|
||||
in
|
||||
( { model
|
||||
| showUpdateStackForm = Just stack
|
||||
, formUpdateStack = Form.initial (App.TreeStatus.Form.initUpdateStackFields reason category) (App.TreeStatus.Form.validateUpdateLog status)
|
||||
}
|
||||
, Cmd.none
|
||||
, Nothing
|
||||
)
|
||||
|
||||
App.TreeStatus.Types.FormUpdateStackMsg formMsg ->
|
||||
let
|
||||
( newModel, hawkRequest ) =
|
||||
App.TreeStatus.Form.updateUpdateStack model formMsg
|
||||
in
|
||||
( newModel
|
||||
, Cmd.none
|
||||
, hawkRequest
|
||||
)
|
||||
|
||||
App.TreeStatus.Types.FormUpdateStackResult result ->
|
||||
case currentRoute of
|
||||
App.TreeStatus.Types.ShowTreeRoute name ->
|
||||
( { model
|
||||
| recentChangesAlerts = App.Utils.getAlerts result
|
||||
, showUpdateStackForm = Nothing
|
||||
, treeLogs = RemoteData.Loading
|
||||
, treeLogsAll = RemoteData.Loading
|
||||
}
|
||||
, Cmd.batch
|
||||
[ App.TreeStatus.Api.fetchRecentChanges model.baseUrl
|
||||
, App.TreeStatus.Api.fetchTrees model.baseUrl
|
||||
, App.TreeStatus.Api.fetchTreeLogs model.baseUrl name False
|
||||
, App.TreeStatus.Api.fetchTreeLogs model.baseUrl name True
|
||||
]
|
||||
, Nothing
|
||||
)
|
||||
|
||||
_ ->
|
||||
( { model
|
||||
| recentChangesAlerts = App.Utils.getAlerts result
|
||||
, showUpdateStackForm = Nothing
|
||||
}
|
||||
, Cmd.batch
|
||||
[ App.TreeStatus.Api.fetchRecentChanges model.baseUrl
|
||||
, App.TreeStatus.Api.fetchTrees model.baseUrl
|
||||
]
|
||||
, Nothing
|
||||
)
|
||||
|
||||
App.TreeStatus.Types.UpdateLogShow logId ->
|
||||
let
|
||||
treeLogs =
|
||||
if RemoteData.isSuccess model.treeLogsAll then
|
||||
model.treeLogsAll
|
||||
else
|
||||
model.treeLogs
|
||||
|
||||
( reason, category, status ) =
|
||||
case treeLogs of
|
||||
RemoteData.Success logs ->
|
||||
logs
|
||||
|> List.filter (\x -> x.id == logId)
|
||||
|> List.head
|
||||
|> Maybe.map
|
||||
(\x ->
|
||||
( x.reason
|
||||
, x.tags
|
||||
|> List.head
|
||||
|> Maybe.withDefault ""
|
||||
, x.status
|
||||
)
|
||||
)
|
||||
|> Maybe.withDefault ( "", "", "" )
|
||||
|
||||
_ ->
|
||||
( "", "", "" )
|
||||
in
|
||||
( { model
|
||||
| showUpdateLog = Just logId
|
||||
, formUpdateLog = Form.initial (App.TreeStatus.Form.initUpdateLogFields reason category) (App.TreeStatus.Form.validateUpdateLog status)
|
||||
}
|
||||
, Cmd.none
|
||||
, Nothing
|
||||
)
|
||||
|
||||
App.TreeStatus.Types.FormUpdateLogMsg formMsg ->
|
||||
let
|
||||
( newModel, hawkRequest ) =
|
||||
App.TreeStatus.Form.updateUpdateLog model formMsg
|
||||
in
|
||||
( newModel
|
||||
, Cmd.none
|
||||
, hawkRequest
|
||||
)
|
||||
|
||||
App.TreeStatus.Types.FormUpdateLogResult result ->
|
||||
case currentRoute of
|
||||
App.TreeStatus.Types.ShowTreeRoute name ->
|
||||
( { model
|
||||
| treeLogs = RemoteData.Loading
|
||||
, treeLogsAll = RemoteData.Loading
|
||||
}
|
||||
, Cmd.batch
|
||||
[ App.TreeStatus.Api.fetchTreeLogs model.baseUrl name False
|
||||
, App.TreeStatus.Api.fetchTreeLogs model.baseUrl name True
|
||||
]
|
||||
, Nothing
|
||||
)
|
||||
|
||||
_ ->
|
||||
( model
|
||||
, Cmd.none
|
||||
, Nothing
|
||||
)
|
||||
|
||||
|
||||
view :
|
||||
App.TreeStatus.Types.Route
|
||||
-> TaskclusterLogin.Model
|
||||
-> App.UserScopes.Model
|
||||
-> App.TreeStatus.Types.Model App.TreeStatus.Form.AddTree App.TreeStatus.Form.UpdateTree App.TreeStatus.Form.UpdateStack App.TreeStatus.Form.UpdateLog
|
||||
-> Html App.TreeStatus.Types.Msg
|
||||
view route user scopes model =
|
||||
let
|
||||
isLoadingRemoteData remotedata =
|
||||
RemoteData.isLoading remotedata || RemoteData.isNotAsked remotedata
|
||||
|
||||
isLoadingScopes =
|
||||
case user.tokens of
|
||||
Just _ ->
|
||||
case user.credentials of
|
||||
Just _ ->
|
||||
if List.length scopes.scopes == 0 then
|
||||
True
|
||||
else
|
||||
False
|
||||
|
||||
_ ->
|
||||
True
|
||||
|
||||
_ ->
|
||||
False
|
||||
|
||||
isLoading =
|
||||
case route of
|
||||
App.TreeStatus.Types.ShowTreesRoute ->
|
||||
isLoadingRemoteData model.trees
|
||||
|| isLoadingScopes
|
||||
|
||||
App.TreeStatus.Types.AddTreeRoute ->
|
||||
False
|
||||
|
||||
App.TreeStatus.Types.UpdateTreesRoute ->
|
||||
isLoadingRemoteData model.trees
|
||||
|
||||
App.TreeStatus.Types.DeleteTreesRoute ->
|
||||
False
|
||||
|
||||
App.TreeStatus.Types.ShowTreeRoute name ->
|
||||
isLoadingRemoteData model.tree
|
||||
|| isLoadingRemoteData model.treeLogs
|
||||
|| RemoteData.isLoading model.treeLogsAll
|
||||
|| isLoadingScopes
|
||||
in
|
||||
div [ class "container" ]
|
||||
[ h1 [] [ text "TreeStatus" ]
|
||||
, p [ class "lead" ]
|
||||
[ text "Current status of Mozilla's version-control repositories." ]
|
||||
, if isLoading then
|
||||
App.Utils.loading
|
||||
else
|
||||
viewLoaded route scopes.scopes model
|
||||
]
|
||||
|
||||
|
||||
viewLoaded :
|
||||
App.TreeStatus.Types.Route
|
||||
-> List String
|
||||
-> App.TreeStatus.Types.Model App.TreeStatus.Form.AddTree App.TreeStatus.Form.UpdateTree App.TreeStatus.Form.UpdateStack App.TreeStatus.Form.UpdateLog
|
||||
-> Html App.TreeStatus.Types.Msg
|
||||
viewLoaded route scopes model =
|
||||
div []
|
||||
([]
|
||||
|> App.Utils.appendItem
|
||||
(App.Utils.viewAlerts model.recentChangesAlerts)
|
||||
|> App.Utils.appendItems
|
||||
(App.TreeStatus.View.viewRecentChanges scopes model.showUpdateStackForm model.formUpdateStack model.recentChanges)
|
||||
|> App.Utils.appendItem
|
||||
(App.Utils.viewAlerts model.treesAlerts)
|
||||
|> App.Utils.appendItem
|
||||
(App.TreeStatus.View.viewTreesTitle route)
|
||||
|> App.Utils.appendItem
|
||||
(App.TreeStatus.View.viewButtons route scopes model)
|
||||
|> App.Utils.appendItems
|
||||
(case route of
|
||||
App.TreeStatus.Types.ShowTreesRoute ->
|
||||
App.TreeStatus.View.viewTrees scopes model.trees model.treesSelected
|
||||
|
||||
App.TreeStatus.Types.AddTreeRoute ->
|
||||
[ App.TreeStatus.Form.viewAddTree model.formAddTree
|
||||
|> Html.map App.TreeStatus.Types.FormAddTreeMsg
|
||||
]
|
||||
|
||||
App.TreeStatus.Types.UpdateTreesRoute ->
|
||||
[ App.TreeStatus.Form.viewUpdateTree model.treesSelected model.trees model.formUpdateTree
|
||||
|> Html.map App.TreeStatus.Types.FormUpdateTreesMsg
|
||||
]
|
||||
|
||||
App.TreeStatus.Types.DeleteTreesRoute ->
|
||||
App.TreeStatus.View.viewConfirmDelete model.deleteError model.deleteTreesConfirm model.treesSelected
|
||||
|
||||
App.TreeStatus.Types.ShowTreeRoute name ->
|
||||
App.TreeStatus.View.viewTree scopes model.tree model.treeLogs model.treeLogsAll model.showUpdateLog model.formUpdateLog name
|
||||
)
|
||||
)
|
|
@ -1,243 +0,0 @@
|
|||
module App.TreeStatus.Api exposing (..)
|
||||
|
||||
import App.TreeStatus.Types
|
||||
import Http
|
||||
import Json.Decode as JsonDecode
|
||||
import Json.Encode as JsonEncode
|
||||
import RemoteData exposing (WebData)
|
||||
|
||||
|
||||
encoderUpdateStack :
|
||||
{ a
|
||||
| reason : String
|
||||
, tags : List String
|
||||
}
|
||||
-> JsonEncode.Value
|
||||
encoderUpdateStack data =
|
||||
JsonEncode.object
|
||||
[ ( "reason", JsonEncode.string data.reason )
|
||||
, ( "tags", JsonEncode.list (List.map JsonEncode.string data.tags) )
|
||||
]
|
||||
|
||||
|
||||
encoderUpdateTree :
|
||||
{ a
|
||||
| message_of_the_day : String
|
||||
, reason : String
|
||||
, remember : Bool
|
||||
, status : String
|
||||
, trees : List String
|
||||
, tags : List String
|
||||
}
|
||||
-> JsonEncode.Value
|
||||
encoderUpdateTree data =
|
||||
JsonEncode.object
|
||||
[ ( "trees", JsonEncode.list (List.map JsonEncode.string data.trees) )
|
||||
, ( "status", JsonEncode.string data.status )
|
||||
, ( "reason", JsonEncode.string data.reason )
|
||||
, ( "tags", JsonEncode.list (List.map JsonEncode.string data.tags) )
|
||||
, ( "message_of_the_day", JsonEncode.string data.message_of_the_day )
|
||||
, ( "remember", JsonEncode.bool data.remember )
|
||||
]
|
||||
|
||||
|
||||
encoderUpdateTrees :
|
||||
{ a
|
||||
| reason : String
|
||||
, remember : Bool
|
||||
, status : String
|
||||
, trees : List String
|
||||
, tags : List String
|
||||
}
|
||||
-> JsonEncode.Value
|
||||
encoderUpdateTrees data =
|
||||
JsonEncode.object
|
||||
[ ( "trees", JsonEncode.list (List.map JsonEncode.string data.trees) )
|
||||
, ( "status", JsonEncode.string data.status )
|
||||
, ( "reason", JsonEncode.string data.reason )
|
||||
, ( "tags", JsonEncode.list (List.map JsonEncode.string data.tags) )
|
||||
, ( "remember", JsonEncode.bool data.remember )
|
||||
]
|
||||
|
||||
|
||||
encoderTree : App.TreeStatus.Types.Tree -> JsonEncode.Value
|
||||
encoderTree tree =
|
||||
JsonEncode.object
|
||||
[ ( "tree", JsonEncode.string tree.name )
|
||||
, ( "status", JsonEncode.string tree.status )
|
||||
, ( "reason", JsonEncode.string tree.reason )
|
||||
, ( "message_of_the_day", JsonEncode.string tree.message_of_the_day )
|
||||
]
|
||||
|
||||
|
||||
encoderTreeNames : App.TreeStatus.Types.Trees -> JsonEncode.Value
|
||||
encoderTreeNames trees =
|
||||
JsonEncode.object
|
||||
[ ( "trees", JsonEncode.list (List.map (\x -> JsonEncode.string x.name) trees) )
|
||||
]
|
||||
|
||||
|
||||
decoderTrees : JsonDecode.Decoder App.TreeStatus.Types.Trees
|
||||
decoderTrees =
|
||||
JsonDecode.list decoderTree2
|
||||
|> JsonDecode.at [ "result" ]
|
||||
|
||||
|
||||
decoderTree2 : JsonDecode.Decoder App.TreeStatus.Types.Tree
|
||||
decoderTree2 =
|
||||
JsonDecode.map5 App.TreeStatus.Types.Tree
|
||||
(JsonDecode.field "tree" JsonDecode.string)
|
||||
(JsonDecode.field "status" JsonDecode.string)
|
||||
(JsonDecode.field "reason" JsonDecode.string)
|
||||
(JsonDecode.field "message_of_the_day" JsonDecode.string)
|
||||
(JsonDecode.field "tags" (JsonDecode.list JsonDecode.string))
|
||||
|
||||
|
||||
decoderTree : JsonDecode.Decoder App.TreeStatus.Types.Tree
|
||||
decoderTree =
|
||||
JsonDecode.at [ "result" ] decoderTree2
|
||||
|
||||
|
||||
decoderTreeLogs : JsonDecode.Decoder App.TreeStatus.Types.TreeLogs
|
||||
decoderTreeLogs =
|
||||
JsonDecode.list decoderTreeLog
|
||||
|> JsonDecode.at [ "result" ]
|
||||
|
||||
|
||||
decoderTreeLog : JsonDecode.Decoder App.TreeStatus.Types.TreeLog
|
||||
decoderTreeLog =
|
||||
JsonDecode.map7 App.TreeStatus.Types.TreeLog
|
||||
(JsonDecode.field "id" JsonDecode.int)
|
||||
(JsonDecode.field "tree" JsonDecode.string)
|
||||
(JsonDecode.field "when" JsonDecode.string)
|
||||
(JsonDecode.field "who" JsonDecode.string)
|
||||
(JsonDecode.field "status" JsonDecode.string)
|
||||
(JsonDecode.field "reason" JsonDecode.string)
|
||||
(JsonDecode.field "tags" (JsonDecode.list JsonDecode.string))
|
||||
|
||||
|
||||
decoderRecentChanges : JsonDecode.Decoder (List App.TreeStatus.Types.RecentChange)
|
||||
decoderRecentChanges =
|
||||
JsonDecode.list decoderRecentChange
|
||||
|> JsonDecode.at [ "result" ]
|
||||
|
||||
|
||||
decoderRecentChange : JsonDecode.Decoder App.TreeStatus.Types.RecentChange
|
||||
decoderRecentChange =
|
||||
JsonDecode.map6 App.TreeStatus.Types.RecentChange
|
||||
(JsonDecode.field "id" JsonDecode.int)
|
||||
(JsonDecode.field "trees" (JsonDecode.list decoderRecentChangeTree))
|
||||
(JsonDecode.field "when" JsonDecode.string)
|
||||
(JsonDecode.field "who" JsonDecode.string)
|
||||
(JsonDecode.field "status" JsonDecode.string)
|
||||
(JsonDecode.field "reason" JsonDecode.string)
|
||||
|
||||
|
||||
decoderRecentChangeTree : JsonDecode.Decoder App.TreeStatus.Types.RecentChangeTree
|
||||
decoderRecentChangeTree =
|
||||
JsonDecode.map3 App.TreeStatus.Types.RecentChangeTree
|
||||
(JsonDecode.field "id" JsonDecode.int)
|
||||
(JsonDecode.field "tree" JsonDecode.string)
|
||||
(JsonDecode.field "last_state" decoderRecentChangeTreeLastState)
|
||||
|
||||
|
||||
decoderRecentChangeTreeLastState : JsonDecode.Decoder App.TreeStatus.Types.RecentChangeTreeLastState
|
||||
decoderRecentChangeTreeLastState =
|
||||
JsonDecode.map8 App.TreeStatus.Types.RecentChangeTreeLastState
|
||||
(JsonDecode.field "reason" JsonDecode.string)
|
||||
(JsonDecode.field "status" JsonDecode.string)
|
||||
(JsonDecode.field "tags" (JsonDecode.list JsonDecode.string))
|
||||
(JsonDecode.field "log_id" (JsonDecode.nullable JsonDecode.int))
|
||||
(JsonDecode.field "current_reason" JsonDecode.string)
|
||||
(JsonDecode.field "current_status" JsonDecode.string)
|
||||
(JsonDecode.field "current_tags" (JsonDecode.list JsonDecode.string))
|
||||
(JsonDecode.field "current_log_id" (JsonDecode.nullable JsonDecode.int))
|
||||
|
||||
|
||||
get :
|
||||
(RemoteData.RemoteData Http.Error a -> b)
|
||||
-> String
|
||||
-> JsonDecode.Decoder a
|
||||
-> Cmd b
|
||||
get msg url decoder =
|
||||
Http.get url decoder
|
||||
|> Http.toTask
|
||||
|> RemoteData.asCmd
|
||||
|> Cmd.map msg
|
||||
|
||||
|
||||
fetchTrees :
|
||||
String
|
||||
-> Cmd App.TreeStatus.Types.Msg
|
||||
fetchTrees url =
|
||||
get App.TreeStatus.Types.GetTreesResult
|
||||
(url ++ "/trees2")
|
||||
decoderTrees
|
||||
|
||||
|
||||
fetchTree :
|
||||
String
|
||||
-> String
|
||||
-> Cmd App.TreeStatus.Types.Msg
|
||||
fetchTree url name =
|
||||
get App.TreeStatus.Types.GetTreeResult
|
||||
(url ++ "/trees/" ++ name)
|
||||
decoderTree
|
||||
|
||||
|
||||
fetchTreeLogs :
|
||||
String
|
||||
-> String
|
||||
-> Bool
|
||||
-> Cmd App.TreeStatus.Types.Msg
|
||||
fetchTreeLogs url name all =
|
||||
case all of
|
||||
True ->
|
||||
get App.TreeStatus.Types.GetTreeLogsAllResult
|
||||
(url ++ "/trees/" ++ name ++ "/logs?all=1")
|
||||
decoderTreeLogs
|
||||
|
||||
False ->
|
||||
get App.TreeStatus.Types.GetTreeLogsResult
|
||||
(url ++ "/trees/" ++ name ++ "/logs?all=0")
|
||||
decoderTreeLogs
|
||||
|
||||
|
||||
fetchRecentChanges :
|
||||
String
|
||||
-> Cmd App.TreeStatus.Types.Msg
|
||||
fetchRecentChanges url =
|
||||
get App.TreeStatus.Types.GetRecentChangesResult
|
||||
(url ++ "/stack")
|
||||
decoderRecentChanges
|
||||
|
||||
|
||||
hawkResponse :
|
||||
Cmd (WebData String)
|
||||
-> String
|
||||
-> Cmd App.TreeStatus.Types.Msg
|
||||
hawkResponse response route =
|
||||
case route of
|
||||
"AddTree" ->
|
||||
Cmd.map App.TreeStatus.Types.FormAddTreeResult response
|
||||
|
||||
"DeleteTrees" ->
|
||||
Cmd.map App.TreeStatus.Types.DeleteTreesResult response
|
||||
|
||||
"UpdateTrees" ->
|
||||
Cmd.map App.TreeStatus.Types.FormUpdateTreesResult response
|
||||
|
||||
"RevertChange" ->
|
||||
Cmd.map App.TreeStatus.Types.RecentChangeResult response
|
||||
|
||||
"DiscardChange" ->
|
||||
Cmd.map App.TreeStatus.Types.RecentChangeResult response
|
||||
|
||||
"UpdateStack" ->
|
||||
Cmd.map App.TreeStatus.Types.FormUpdateStackResult response
|
||||
|
||||
"UpdateLog" ->
|
||||
Cmd.map App.TreeStatus.Types.FormUpdateLogResult response
|
||||
|
||||
_ ->
|
||||
Cmd.none
|
|
@ -1,763 +0,0 @@
|
|||
module App.TreeStatus.Form exposing (..)
|
||||
|
||||
import App.Form
|
||||
import App.TreeStatus.Api
|
||||
import App.TreeStatus.Types
|
||||
import App.Utils
|
||||
import Form
|
||||
import Form.Field
|
||||
import Form.Input
|
||||
import Form.Validate
|
||||
import Hawk
|
||||
import Html exposing (..)
|
||||
import Html.Attributes exposing (..)
|
||||
import Http
|
||||
import RemoteData
|
||||
import Utils
|
||||
|
||||
|
||||
type alias AddTree =
|
||||
{ name : String }
|
||||
|
||||
|
||||
type alias UpdateStack =
|
||||
{ reason : String
|
||||
, tags : String
|
||||
}
|
||||
|
||||
|
||||
type alias UpdateLog =
|
||||
{ reason : String
|
||||
, tags : String
|
||||
}
|
||||
|
||||
|
||||
type alias UpdateTree =
|
||||
{ status : String
|
||||
, reason : String
|
||||
, message_of_the_day : String
|
||||
, tags : String
|
||||
, remember : Bool
|
||||
}
|
||||
|
||||
|
||||
validateAddTree : Form.Validate.Validation () AddTree
|
||||
validateAddTree =
|
||||
Form.Validate.map AddTree
|
||||
(Form.Validate.field "name" Form.Validate.string)
|
||||
|
||||
|
||||
validateUpdateLog : String -> Form.Validate.Validation () UpdateLog
|
||||
validateUpdateLog status =
|
||||
if status == "closed" then
|
||||
Form.Validate.map2 UpdateLog
|
||||
(Form.Validate.field "reason" Form.Validate.string)
|
||||
(Form.Validate.field "tags" Form.Validate.string)
|
||||
else
|
||||
Form.Validate.map2 UpdateLog
|
||||
(Form.Validate.field "reason"
|
||||
(Form.Validate.oneOf
|
||||
[ Form.Validate.string
|
||||
, Form.Validate.emptyString
|
||||
]
|
||||
)
|
||||
)
|
||||
(Form.Validate.field "tags"
|
||||
(Form.Validate.oneOf
|
||||
[ Form.Validate.string
|
||||
, Form.Validate.emptyString
|
||||
]
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
validateUpdateTree : String -> Form.Validate.Validation () UpdateTree
|
||||
validateUpdateTree status =
|
||||
if status == "closed" then
|
||||
Form.Validate.map5 UpdateTree
|
||||
(Form.Validate.field "status" Form.Validate.string)
|
||||
(Form.Validate.field "reason" Form.Validate.string)
|
||||
(Form.Validate.field "message_of_the_day"
|
||||
(Form.Validate.oneOf
|
||||
[ Form.Validate.string
|
||||
, Form.Validate.emptyString
|
||||
]
|
||||
)
|
||||
)
|
||||
(Form.Validate.field "tags" Form.Validate.string)
|
||||
(Form.Validate.field "remember" Form.Validate.bool)
|
||||
else
|
||||
Form.Validate.map5 UpdateTree
|
||||
(Form.Validate.field "status" Form.Validate.string)
|
||||
(Form.Validate.field "reason"
|
||||
(Form.Validate.oneOf
|
||||
[ Form.Validate.string
|
||||
, Form.Validate.emptyString
|
||||
]
|
||||
)
|
||||
)
|
||||
(Form.Validate.field "message_of_the_day"
|
||||
(Form.Validate.oneOf
|
||||
[ Form.Validate.string
|
||||
, Form.Validate.emptyString
|
||||
]
|
||||
)
|
||||
)
|
||||
(Form.Validate.field "tags"
|
||||
(Form.Validate.oneOf
|
||||
[ Form.Validate.string
|
||||
, Form.Validate.emptyString
|
||||
]
|
||||
)
|
||||
)
|
||||
(Form.Validate.field "remember" Form.Validate.bool)
|
||||
|
||||
|
||||
initAddTreeFields : List ( String, Form.Field.Field )
|
||||
initAddTreeFields =
|
||||
[ ( "name", Form.Field.string "" ) ]
|
||||
|
||||
|
||||
initUpdateStackFields : String -> String -> List ( String, Form.Field.Field )
|
||||
initUpdateStackFields reason tags =
|
||||
[ ( "reason", Form.Field.string reason )
|
||||
, ( "tags", Form.Field.string tags )
|
||||
]
|
||||
|
||||
|
||||
initUpdateLogFields : String -> String -> List ( String, Form.Field.Field )
|
||||
initUpdateLogFields reason tags =
|
||||
[ ( "reason", Form.Field.string reason )
|
||||
, ( "tags", Form.Field.string tags )
|
||||
]
|
||||
|
||||
|
||||
initUpdateTreeFields : List ( String, Form.Field.Field )
|
||||
initUpdateTreeFields =
|
||||
[ ( "status", Form.Field.string "" )
|
||||
, ( "reason", Form.Field.string "" )
|
||||
, ( "message_of_the_day", Form.Field.string "" )
|
||||
, ( "tags", Form.Field.string "" )
|
||||
, ( "remember", Form.Field.bool True )
|
||||
]
|
||||
|
||||
|
||||
initAddTree : Form.Form () AddTree
|
||||
initAddTree =
|
||||
Form.initial initAddTreeFields validateAddTree
|
||||
|
||||
|
||||
initUpdateStack : String -> Form.Form () UpdateStack
|
||||
initUpdateStack status =
|
||||
Form.initial (initUpdateStackFields "" "") (validateUpdateLog status)
|
||||
|
||||
|
||||
initUpdateLog : String -> Form.Form () UpdateLog
|
||||
initUpdateLog status =
|
||||
Form.initial (initUpdateLogFields "" "") (validateUpdateLog status)
|
||||
|
||||
|
||||
initUpdateTree : String -> Form.Form () UpdateTree
|
||||
initUpdateTree status =
|
||||
Form.initial initUpdateTreeFields (validateUpdateTree status)
|
||||
|
||||
|
||||
resetAddTree : Form.Msg
|
||||
resetAddTree =
|
||||
Form.Reset initAddTreeFields
|
||||
|
||||
|
||||
resetUpdateTree : Form.Msg
|
||||
resetUpdateTree =
|
||||
Form.Reset initUpdateTreeFields
|
||||
|
||||
|
||||
updateAddTree :
|
||||
App.TreeStatus.Types.Model AddTree UpdateTree UpdateStack UpdateLog
|
||||
-> Form.Msg
|
||||
-> ( App.TreeStatus.Types.Model AddTree UpdateTree UpdateStack UpdateLog, Maybe Hawk.Request )
|
||||
updateAddTree model formMsg =
|
||||
let
|
||||
form =
|
||||
Form.update validateAddTree formMsg model.formAddTree
|
||||
|
||||
tree name =
|
||||
App.TreeStatus.Types.Tree name "closed" "new tree" "" []
|
||||
|
||||
newTreeRequest name =
|
||||
Hawk.Request
|
||||
"AddTree"
|
||||
"PUT"
|
||||
(model.baseUrl ++ "/trees/" ++ name)
|
||||
-- probably this should be in Hawk.elm
|
||||
[ Http.header "Accept" "application/json" ]
|
||||
(Http.jsonBody (App.TreeStatus.Api.encoderTree (tree name)))
|
||||
|
||||
( trees, alerts, hawkRequest ) =
|
||||
case formMsg of
|
||||
Form.Submit ->
|
||||
if Form.getErrors form /= [] then
|
||||
( model.trees, [], Nothing )
|
||||
else
|
||||
-- opurtonistic update
|
||||
( Form.getOutput form
|
||||
|> Maybe.map (\x -> [ tree x.name ])
|
||||
|> Maybe.withDefault []
|
||||
|> (\y -> RemoteData.map (\x -> List.append x y) model.trees)
|
||||
, []
|
||||
, Form.getOutput form
|
||||
|> Maybe.map (\x -> newTreeRequest x.name)
|
||||
)
|
||||
|
||||
_ ->
|
||||
( model.trees, model.treesAlerts, Nothing )
|
||||
in
|
||||
( { model
|
||||
| formAddTree = form
|
||||
, treesAlerts = alerts
|
||||
, trees = trees
|
||||
}
|
||||
, hawkRequest
|
||||
)
|
||||
|
||||
|
||||
updateUpdateStack :
|
||||
App.TreeStatus.Types.Model AddTree UpdateTree UpdateStack UpdateLog
|
||||
-> Form.Msg
|
||||
-> ( App.TreeStatus.Types.Model AddTree UpdateTree UpdateStack UpdateLog, Maybe Hawk.Request )
|
||||
updateUpdateStack model formMsg =
|
||||
let
|
||||
status =
|
||||
model.recentChanges
|
||||
|> RemoteData.withDefault []
|
||||
|> List.filter (\x -> Just x.id == model.showUpdateStackForm)
|
||||
|> List.map
|
||||
(\x ->
|
||||
x.trees
|
||||
|> List.head
|
||||
|> Maybe.map (\y -> y.last_state.current_status)
|
||||
|> Maybe.withDefault ""
|
||||
)
|
||||
|> List.head
|
||||
|> Maybe.withDefault ""
|
||||
|
||||
form =
|
||||
Form.update (validateUpdateLog status) formMsg model.formUpdateStack
|
||||
|
||||
formOutput =
|
||||
Form.getOutput form
|
||||
|
||||
( recentChanges, alerts, hawkRequest, showUpdateStackForm ) =
|
||||
case ( formMsg, model.recentChanges, model.showUpdateStackForm, formOutput ) of
|
||||
( Form.Submit, RemoteData.Success recentChanges, Just recentChangeId, Just formOutput ) ->
|
||||
let
|
||||
newRecentChanges =
|
||||
List.map updateRecentChange recentChanges
|
||||
|
||||
updateRecentChange recentChange =
|
||||
if recentChangeId == recentChange.id then
|
||||
{ recentChange
|
||||
| reason = formOutput.reason
|
||||
, trees = List.map updateRecentChangeTree recentChange.trees
|
||||
}
|
||||
else
|
||||
recentChange
|
||||
|
||||
updateRecentChangeTree tree =
|
||||
let
|
||||
last_state =
|
||||
tree.last_state
|
||||
in
|
||||
{ tree
|
||||
| last_state =
|
||||
{ last_state
|
||||
| reason = formOutput.reason
|
||||
, tags = [ formOutput.tags ]
|
||||
}
|
||||
}
|
||||
|
||||
hawkRequest_ =
|
||||
Hawk.Request
|
||||
"UpdateStack"
|
||||
"PATCH"
|
||||
(model.baseUrl ++ "/stack/" ++ toString recentChangeId)
|
||||
[ Http.header "Accept" "application/json" ]
|
||||
(Http.jsonBody (App.TreeStatus.Api.encoderUpdateStack { reason = formOutput.reason, tags = [ formOutput.tags ] }))
|
||||
|
||||
( alerts, hawkRequest ) =
|
||||
if Form.getErrors form == [] then
|
||||
( [], Just hawkRequest_ )
|
||||
else
|
||||
( model.recentChangesAlerts, Nothing )
|
||||
in
|
||||
( RemoteData.Success newRecentChanges, alerts, hawkRequest, model.showUpdateStackForm )
|
||||
|
||||
( Form.Reset _, _, _, _ ) ->
|
||||
( model.recentChanges, model.recentChangesAlerts, Nothing, Nothing )
|
||||
|
||||
( _, _, _, _ ) ->
|
||||
( model.recentChanges, model.recentChangesAlerts, Nothing, model.showUpdateStackForm )
|
||||
in
|
||||
( { model
|
||||
| formUpdateStack = form
|
||||
, recentChangesAlerts = alerts
|
||||
, recentChanges = recentChanges
|
||||
, showUpdateStackForm = showUpdateStackForm
|
||||
}
|
||||
, hawkRequest
|
||||
)
|
||||
|
||||
|
||||
updateUpdateLog :
|
||||
App.TreeStatus.Types.Model AddTree UpdateTree UpdateStack UpdateLog
|
||||
-> Form.Msg
|
||||
-> ( App.TreeStatus.Types.Model AddTree UpdateTree UpdateStack UpdateLog, Maybe Hawk.Request )
|
||||
updateUpdateLog model formMsg =
|
||||
let
|
||||
status =
|
||||
logs
|
||||
|> List.filter (\x -> Just x.id == model.showUpdateLog)
|
||||
|> List.map (\x -> x.status)
|
||||
|> List.head
|
||||
|> Maybe.withDefault ""
|
||||
|
||||
logs =
|
||||
RemoteData.withDefault []
|
||||
(if RemoteData.isSuccess model.treeLogsAll then
|
||||
model.treeLogsAll
|
||||
else
|
||||
model.treeLogs
|
||||
)
|
||||
|
||||
formOutput =
|
||||
Form.getOutput form |> Maybe.withDefault (UpdateLog "" "")
|
||||
|
||||
form =
|
||||
Form.update (validateUpdateLog status) formMsg model.formUpdateLog
|
||||
|
||||
updateLog logId logs =
|
||||
List.map
|
||||
(\log ->
|
||||
if logId == log.id then
|
||||
{ log
|
||||
| reason = formOutput.reason
|
||||
, tags = [ formOutput.tags ]
|
||||
}
|
||||
else
|
||||
log
|
||||
)
|
||||
logs
|
||||
|
||||
requestBody =
|
||||
App.TreeStatus.Api.encoderUpdateStack
|
||||
{ reason = formOutput.reason
|
||||
, tags = [ formOutput.tags ]
|
||||
}
|
||||
|
||||
makeHawkRequest logId =
|
||||
Hawk.Request
|
||||
"UpdateStack"
|
||||
"PATCH"
|
||||
(model.baseUrl ++ "/log/" ++ toString logId)
|
||||
[ Http.header "Accept" "application/json" ]
|
||||
(Http.jsonBody requestBody)
|
||||
|
||||
( showUpdateLog, treeLogs, treeLogsAll, hawkRequest ) =
|
||||
case ( formMsg, model.showUpdateLog ) of
|
||||
( Form.Submit, Just logId ) ->
|
||||
let
|
||||
errors =
|
||||
Form.getErrors form
|
||||
in
|
||||
( if List.isEmpty errors then
|
||||
Nothing
|
||||
else
|
||||
model.showUpdateLog
|
||||
-- TODO: update recentChanges
|
||||
, RemoteData.map (updateLog logId) model.treeLogs
|
||||
, RemoteData.map (updateLog logId) model.treeLogsAll
|
||||
-- TODO: pass in status
|
||||
, if List.isEmpty errors then
|
||||
Just (makeHawkRequest logId)
|
||||
else
|
||||
Nothing
|
||||
)
|
||||
|
||||
( Form.Reset _, _ ) ->
|
||||
( Nothing
|
||||
, model.treeLogs
|
||||
, model.treeLogsAll
|
||||
, Nothing
|
||||
)
|
||||
|
||||
( _, _ ) ->
|
||||
( model.showUpdateLog
|
||||
, model.treeLogs
|
||||
, model.treeLogsAll
|
||||
, Nothing
|
||||
)
|
||||
in
|
||||
( { model
|
||||
| formUpdateLog = form
|
||||
, showUpdateLog = showUpdateLog
|
||||
, treeLogs = treeLogs
|
||||
, treeLogsAll = treeLogsAll
|
||||
}
|
||||
, hawkRequest
|
||||
)
|
||||
|
||||
|
||||
updateUpdateTree :
|
||||
App.TreeStatus.Types.Route
|
||||
-> App.TreeStatus.Types.Model AddTree UpdateTree UpdateStack UpdateLog
|
||||
-> Form.Msg
|
||||
-> ( App.TreeStatus.Types.Model AddTree UpdateTree UpdateStack UpdateLog, Maybe Hawk.Request )
|
||||
updateUpdateTree route model formMsg =
|
||||
let
|
||||
status =
|
||||
(Form.getFieldAsString "status" model.formUpdateTree).value |> Maybe.withDefault ""
|
||||
|
||||
form =
|
||||
Form.update (validateUpdateTree status) formMsg model.formUpdateTree
|
||||
|
||||
createRequest data =
|
||||
Hawk.Request
|
||||
"UpdateTrees"
|
||||
"PATCH"
|
||||
(model.baseUrl ++ "/trees")
|
||||
[ Http.header "Accept" "application/json" ]
|
||||
((if List.length model.treesSelected /= 1 then
|
||||
{ trees = model.treesSelected
|
||||
, status = data.status
|
||||
, reason = data.reason
|
||||
, tags = [ data.tags ]
|
||||
, remember = data.remember
|
||||
}
|
||||
|> App.TreeStatus.Api.encoderUpdateTrees
|
||||
else
|
||||
{ trees = model.treesSelected
|
||||
, status = data.status
|
||||
, reason = data.reason
|
||||
, tags = [ data.tags ]
|
||||
, message_of_the_day = data.message_of_the_day
|
||||
, remember = data.remember
|
||||
}
|
||||
|> App.TreeStatus.Api.encoderUpdateTree
|
||||
)
|
||||
|> Http.jsonBody
|
||||
)
|
||||
|
||||
( alerts, hawkRequest ) =
|
||||
case formMsg of
|
||||
Form.Submit ->
|
||||
if Form.getErrors form /= [] then
|
||||
( [], Nothing )
|
||||
else
|
||||
( []
|
||||
, Form.getOutput form
|
||||
|> Maybe.map (\x -> createRequest x)
|
||||
)
|
||||
|
||||
_ ->
|
||||
( model.treesAlerts, Nothing )
|
||||
in
|
||||
( { model
|
||||
| formUpdateTree = form
|
||||
, treesAlerts = alerts
|
||||
}
|
||||
, hawkRequest
|
||||
)
|
||||
|
||||
|
||||
fieldClass : { b | error : Maybe a } -> String
|
||||
fieldClass field =
|
||||
case field.error of
|
||||
Just error ->
|
||||
"input-group has-danger"
|
||||
|
||||
Nothing ->
|
||||
"input-group "
|
||||
|
||||
|
||||
fieldError : { b | error : Maybe a } -> Html c
|
||||
fieldError field =
|
||||
case field.error of
|
||||
Just error ->
|
||||
div [ class "has-danger" ]
|
||||
[ span [ class "form-control-feedback" ]
|
||||
[ text (toString error) ]
|
||||
]
|
||||
|
||||
Nothing ->
|
||||
text ""
|
||||
|
||||
|
||||
viewAddTree : Form.Form () AddTree -> Html Form.Msg
|
||||
viewAddTree form =
|
||||
let
|
||||
state =
|
||||
Form.getFieldAsString "name" form
|
||||
in
|
||||
div
|
||||
[ id "treestatus-form" ]
|
||||
[ Html.form
|
||||
[]
|
||||
[ App.Form.viewField
|
||||
(if Form.isSubmitted form then
|
||||
state.error
|
||||
else
|
||||
Nothing
|
||||
)
|
||||
(Just "Tree name")
|
||||
[]
|
||||
(Form.Input.textInput state
|
||||
[ class "form-control"
|
||||
, value (Maybe.withDefault "" state.value)
|
||||
, placeholder "New tree name ..."
|
||||
]
|
||||
)
|
||||
, App.Form.viewButton
|
||||
"Add"
|
||||
[ Utils.onClick Form.Submit
|
||||
]
|
||||
, div [ class "clearfix" ] []
|
||||
]
|
||||
]
|
||||
|
||||
|
||||
viewUpdateStack :
|
||||
App.TreeStatus.Types.RecentChange
|
||||
-> Form.Form () UpdateStack
|
||||
-> Html Form.Msg
|
||||
viewUpdateStack recentChange form =
|
||||
div [ id "treestatus-form" ]
|
||||
[ Html.form
|
||||
[]
|
||||
[ App.Form.viewRadioInput
|
||||
(Form.getFieldAsString "tags" form)
|
||||
"Reason category"
|
||||
[]
|
||||
(App.TreeStatus.Types.possibleTreeTags
|
||||
|> List.append
|
||||
(if recentChange.status == "closed" then
|
||||
[]
|
||||
else
|
||||
[ ( "", "No category" ) ]
|
||||
)
|
||||
)
|
||||
[]
|
||||
, App.Form.viewTextInput
|
||||
(Form.getFieldAsString "reason" form)
|
||||
"Reason"
|
||||
[ small
|
||||
[ class "form-text text-muted" ]
|
||||
[ p []
|
||||
[ text
|
||||
("Please indicate the reason for "
|
||||
++ "closure, preferably with a bug link."
|
||||
)
|
||||
]
|
||||
, p []
|
||||
[ text
|
||||
("Please indicate conditions for "
|
||||
++ "reopening, especially if you might "
|
||||
++ "disappear before reopening the "
|
||||
++ "tree yourself."
|
||||
)
|
||||
]
|
||||
]
|
||||
]
|
||||
[ placeholder "(no reason)" ]
|
||||
]
|
||||
, div [ class "btn-group" ]
|
||||
[ App.Form.viewButton
|
||||
"Update"
|
||||
[ Utils.onClick Form.Submit
|
||||
]
|
||||
, App.Form.viewButton
|
||||
"Cancel"
|
||||
[ Utils.onClick (Form.Reset (initUpdateStackFields "" ""))
|
||||
, class "btn btn-outline-danger"
|
||||
]
|
||||
]
|
||||
, div [ class "clearfix" ] []
|
||||
]
|
||||
|
||||
|
||||
viewUpdateLog :
|
||||
String
|
||||
-> Form.Form () UpdateLog
|
||||
-> Html Form.Msg
|
||||
viewUpdateLog status form =
|
||||
div [ id "treestatus-form" ]
|
||||
[ Html.form
|
||||
[]
|
||||
[ App.Form.viewRadioInput
|
||||
(Form.getFieldAsString "tags" form)
|
||||
"Reason category"
|
||||
[]
|
||||
(App.TreeStatus.Types.possibleTreeTags
|
||||
|> List.append
|
||||
(if status == "closed" then
|
||||
[]
|
||||
else
|
||||
[ ( "", "No category" ) ]
|
||||
)
|
||||
)
|
||||
[]
|
||||
, App.Form.viewTextInput
|
||||
(Form.getFieldAsString "reason" form)
|
||||
"Reason"
|
||||
[ small
|
||||
[ class "form-text text-muted" ]
|
||||
[ p []
|
||||
[ text
|
||||
("Please indicate the reason for "
|
||||
++ "closure, preferably with a bug link."
|
||||
)
|
||||
]
|
||||
, p []
|
||||
[ text
|
||||
("Please indicate conditions for "
|
||||
++ "reopening, especially if you might "
|
||||
++ "disappear before reopening the "
|
||||
++ "tree yourself."
|
||||
)
|
||||
]
|
||||
]
|
||||
]
|
||||
[ placeholder "(no reason)" ]
|
||||
]
|
||||
, div [ class "btn-group" ]
|
||||
[ App.Form.viewButton
|
||||
"Update"
|
||||
[ Utils.onClick Form.Submit
|
||||
]
|
||||
, App.Form.viewButton
|
||||
"Cancel"
|
||||
-- TODO: this should reset to original fields
|
||||
[ Utils.onClick (Form.Reset (initUpdateLogFields "" ""))
|
||||
, class "btn btn-outline-danger"
|
||||
]
|
||||
]
|
||||
, div [ class "clearfix" ] []
|
||||
]
|
||||
|
||||
|
||||
viewUpdateTree :
|
||||
List String
|
||||
-> RemoteData.WebData App.TreeStatus.Types.Trees
|
||||
-> Form.Form () UpdateTree
|
||||
-> Html Form.Msg
|
||||
viewUpdateTree treesSelected trees form =
|
||||
div [ id "treestatus-form" ]
|
||||
[ div
|
||||
[]
|
||||
([]
|
||||
|> App.Utils.appendItem
|
||||
(text "You are about to update the following trees:")
|
||||
|> App.Utils.appendItem
|
||||
(treesSelected
|
||||
|> List.map
|
||||
(\treeName ->
|
||||
let
|
||||
status =
|
||||
trees
|
||||
|> RemoteData.map (List.filter (\t -> t.name == treeName))
|
||||
|> RemoteData.map (List.map (\t -> t.status))
|
||||
|> RemoteData.toMaybe
|
||||
|> Maybe.withDefault []
|
||||
|> List.head
|
||||
|> Maybe.withDefault "closed"
|
||||
in
|
||||
li []
|
||||
[ text treeName
|
||||
, text " ("
|
||||
, span
|
||||
[ class ("badge badge-" ++ App.Utils.treeStatusLevel status) ]
|
||||
[ text status ]
|
||||
, text ")"
|
||||
]
|
||||
)
|
||||
|> ul []
|
||||
)
|
||||
)
|
||||
, hr [] []
|
||||
, Html.form
|
||||
[]
|
||||
[ App.Form.viewSelectInput
|
||||
(Form.getFieldAsString "status" form)
|
||||
"Status"
|
||||
[]
|
||||
(App.TreeStatus.Types.possibleTreeStatuses
|
||||
|> List.append [ ( "", "" ) ]
|
||||
)
|
||||
[]
|
||||
, App.Form.viewRadioInput
|
||||
(Form.getFieldAsString "tags" form)
|
||||
(if (Form.getFieldAsString "status" form).value == Just "closed" then
|
||||
"Reason category (required to close)"
|
||||
else
|
||||
"Reason category"
|
||||
)
|
||||
[]
|
||||
(App.TreeStatus.Types.possibleTreeTags
|
||||
|> List.append [ ( "", "No category" ) ]
|
||||
)
|
||||
[]
|
||||
, App.Form.viewTextInput
|
||||
(Form.getFieldAsString "reason" form)
|
||||
(if (Form.getFieldAsString "status" form).value == Just "closed" then
|
||||
"Reason (required to close)"
|
||||
else
|
||||
"Reason"
|
||||
)
|
||||
[ small
|
||||
[ class "form-text text-muted" ]
|
||||
[ p []
|
||||
[ text
|
||||
("Please indicate the reason for "
|
||||
++ "closure, preferably with a bug link."
|
||||
)
|
||||
]
|
||||
, p []
|
||||
[ text
|
||||
("Please indicate conditions for "
|
||||
++ "reopening, especially if you might "
|
||||
++ "disappear before reopening the "
|
||||
++ "tree yourself."
|
||||
)
|
||||
]
|
||||
]
|
||||
]
|
||||
[ placeholder "(no reason)" ]
|
||||
, div
|
||||
[ class "form-group" ]
|
||||
[ label [ class "control-label" ] [ text "Remember change" ]
|
||||
, div
|
||||
[]
|
||||
[ App.Form.viewCheckboxInput
|
||||
(Form.getFieldAsBool "remember" form)
|
||||
"Remember this change to undo later"
|
||||
]
|
||||
]
|
||||
, if List.length treesSelected /= 1 then
|
||||
text ""
|
||||
else
|
||||
hr [] []
|
||||
, if List.length treesSelected /= 1 then
|
||||
text ""
|
||||
else
|
||||
App.Form.viewTextInput
|
||||
(Form.getFieldAsString "message_of_the_day" form)
|
||||
"Message of the day"
|
||||
[]
|
||||
[ placeholder "(no change)" ]
|
||||
, hr [] []
|
||||
, App.Form.viewButton
|
||||
"Update"
|
||||
[ Utils.onClick Form.Submit
|
||||
]
|
||||
, div [ class "clearfix" ] []
|
||||
]
|
||||
]
|
|
@ -1,147 +0,0 @@
|
|||
module App.TreeStatus.Types exposing (..)
|
||||
|
||||
import App.Types
|
||||
import Form
|
||||
import RemoteData exposing (WebData)
|
||||
|
||||
|
||||
type Route
|
||||
= AddTreeRoute
|
||||
| UpdateTreesRoute
|
||||
| DeleteTreesRoute
|
||||
| ShowTreesRoute
|
||||
| ShowTreeRoute String
|
||||
|
||||
|
||||
type alias Tree =
|
||||
{ name : String
|
||||
, status : String
|
||||
, reason : String
|
||||
, message_of_the_day : String
|
||||
, tags : List String
|
||||
}
|
||||
|
||||
|
||||
type alias Trees =
|
||||
List Tree
|
||||
|
||||
|
||||
type alias TreeLog =
|
||||
{ id : Int
|
||||
, name : String
|
||||
, when : String
|
||||
, who : String
|
||||
, status : String
|
||||
, reason : String
|
||||
, tags : List String
|
||||
}
|
||||
|
||||
|
||||
type alias TreeLogs =
|
||||
List TreeLog
|
||||
|
||||
|
||||
type alias RecentChangeTreeLastState =
|
||||
{ reason : String
|
||||
, status : String
|
||||
, tags : List String
|
||||
, log_id : Maybe Int
|
||||
, current_reason : String
|
||||
, current_status : String
|
||||
, current_tags : List String
|
||||
, current_log_id : Maybe Int
|
||||
}
|
||||
|
||||
|
||||
type alias RecentChangeTree =
|
||||
{ id : Int
|
||||
, tree : String
|
||||
, last_state : RecentChangeTreeLastState
|
||||
}
|
||||
|
||||
|
||||
type alias RecentChangeId =
|
||||
Int
|
||||
|
||||
|
||||
type alias RecentChange =
|
||||
{ id : RecentChangeId
|
||||
, trees : List RecentChangeTree
|
||||
, when : String
|
||||
, who : String
|
||||
, status : String
|
||||
, reason : String
|
||||
}
|
||||
|
||||
|
||||
type alias Model addForm updateForm updateStackForm updateLogForm =
|
||||
{ baseUrl : String
|
||||
, treesAlerts : List App.Types.Alert
|
||||
, trees : RemoteData.WebData Trees
|
||||
, treesSelected : List String
|
||||
, tree : RemoteData.WebData Tree
|
||||
, treeLogs : RemoteData.WebData TreeLogs
|
||||
, treeLogsAll : RemoteData.WebData TreeLogs
|
||||
, showMoreTreeLogs : Bool
|
||||
, formAddTree : Form.Form () addForm
|
||||
, formUpdateTree : Form.Form () updateForm
|
||||
, formUpdateStack : Form.Form () updateStackForm
|
||||
, showUpdateStackForm : Maybe RecentChangeId
|
||||
, recentChangesAlerts : List App.Types.Alert
|
||||
, recentChanges : RemoteData.WebData (List RecentChange)
|
||||
, deleteTreesConfirm : Bool
|
||||
, deleteError : Maybe String
|
||||
, showUpdateLog : Maybe Int
|
||||
, formUpdateLog : Form.Form () updateLogForm
|
||||
}
|
||||
|
||||
|
||||
type Msg
|
||||
= NavigateTo Route
|
||||
| GetTreesResult (RemoteData.WebData Trees)
|
||||
| GetTreeResult (RemoteData.WebData Tree)
|
||||
| GetTreeLogs String Bool
|
||||
| GetTreeLogsResult (RemoteData.WebData TreeLogs)
|
||||
| GetTreeLogsAllResult (RemoteData.WebData TreeLogs)
|
||||
| GetRecentChangesResult (RemoteData.WebData (List RecentChange))
|
||||
| FormAddTreeMsg Form.Msg
|
||||
| FormAddTreeResult (WebData String)
|
||||
| FormUpdateTreesMsg Form.Msg
|
||||
| FormUpdateTreesResult (WebData String)
|
||||
| SelectAllTrees
|
||||
| SelectTree String
|
||||
| UnselectAllTrees
|
||||
| UnselectTree String
|
||||
| DeleteTrees
|
||||
| DeleteTreesConfirmToggle
|
||||
| DeleteTreesResult (WebData String)
|
||||
| RevertChange Int
|
||||
| DiscardChange Int
|
||||
| RecentChangeResult (WebData String)
|
||||
| UpdateStackShow Int
|
||||
| FormUpdateStackMsg Form.Msg
|
||||
| FormUpdateStackResult (WebData String)
|
||||
| UpdateLogShow Int
|
||||
| FormUpdateLogMsg Form.Msg
|
||||
| FormUpdateLogResult (WebData String)
|
||||
|
||||
|
||||
possibleTreeStatuses : List ( String, String )
|
||||
possibleTreeStatuses =
|
||||
[ ( "open", "Open" )
|
||||
, ( "approval required", "Approval required" )
|
||||
, ( "closed", "Closed" )
|
||||
]
|
||||
|
||||
|
||||
possibleTreeTags : List ( String, String )
|
||||
possibleTreeTags =
|
||||
[ ( "checkin-compilation", "Check-in compilation failure" )
|
||||
, ( "checkin-test", "Check-in test failure" )
|
||||
, ( "infra", "Infrastructure related" )
|
||||
, ( "backlog", "Job backlog" )
|
||||
, ( "planned", "Planned closure" )
|
||||
, ( "merges", "Merges" )
|
||||
, ( "waiting-for-coverage", "Waiting for coverage" )
|
||||
, ( "other", "Other" )
|
||||
]
|
|
@ -1,869 +0,0 @@
|
|||
module App.TreeStatus.View exposing (..)
|
||||
|
||||
import App.Form
|
||||
import App.TreeStatus.Form
|
||||
import App.TreeStatus.Types
|
||||
import App.UserScopes
|
||||
import App.Utils
|
||||
import Dict
|
||||
import Form
|
||||
import Html exposing (..)
|
||||
import Html.Attributes exposing (..)
|
||||
import Html.Events exposing (..)
|
||||
import RemoteData
|
||||
import String
|
||||
import TaskclusterLogin
|
||||
import Utils
|
||||
|
||||
|
||||
hasScope : String -> List String -> Bool
|
||||
hasScope scope scopes =
|
||||
App.UserScopes.hasScope scopes ("project:releng:treestatus/" ++ scope)
|
||||
|
||||
|
||||
onClickGoTo : App.TreeStatus.Types.Route -> Attribute App.TreeStatus.Types.Msg
|
||||
onClickGoTo route =
|
||||
Utils.onClick (App.TreeStatus.Types.NavigateTo route)
|
||||
|
||||
|
||||
bugzillaBugAsLink : String -> List (Html a)
|
||||
bugzillaBugAsLink text_ =
|
||||
let
|
||||
words =
|
||||
String.words text_
|
||||
|
||||
previousWords =
|
||||
Nothing :: List.map Just words
|
||||
in
|
||||
List.map2 (\x y -> ( x, y )) previousWords words
|
||||
|> List.filter (\( _, word ) -> word /= "Bug")
|
||||
|> List.map
|
||||
(\( previousWord, number ) ->
|
||||
if previousWord == Just "Bug" then
|
||||
a [ href ("https://bugzilla.mozilla.org/show_bug.cgi?id=" ++ number) ]
|
||||
[ text ("Bug " ++ number) ]
|
||||
else
|
||||
text (" " ++ number ++ " ")
|
||||
)
|
||||
|
||||
|
||||
viewRecentChangeTree : String -> App.TreeStatus.Types.RecentChangeTree -> Html App.TreeStatus.Types.Msg
|
||||
viewRecentChangeTree status tree =
|
||||
li []
|
||||
[ em [] [ text tree.tree ]
|
||||
, span [] [ text " from " ]
|
||||
, span
|
||||
[ class ("badge badge-" ++ App.Utils.treeStatusLevel tree.last_state.status) ]
|
||||
[ text tree.last_state.status ]
|
||||
, span [] [ text " to " ]
|
||||
, span
|
||||
[ class ("badge badge-" ++ App.Utils.treeStatusLevel status) ]
|
||||
[ text status ]
|
||||
]
|
||||
|
||||
|
||||
viewRecentChange :
|
||||
List String
|
||||
-> Maybe App.TreeStatus.Types.RecentChangeId
|
||||
-> Form.Form () App.TreeStatus.Form.UpdateStack
|
||||
-> App.TreeStatus.Types.RecentChange
|
||||
-> List (Html App.TreeStatus.Types.Msg)
|
||||
viewRecentChange scopes showUpdateStackForm formUpdateStack recentChange =
|
||||
let
|
||||
( reason, category ) =
|
||||
recentChange.trees
|
||||
|> List.head
|
||||
|> Maybe.map
|
||||
(\x ->
|
||||
( x.last_state.current_reason
|
||||
, x.last_state.current_tags
|
||||
|> List.head
|
||||
|> Maybe.withDefault ""
|
||||
)
|
||||
)
|
||||
|> Maybe.withDefault ( "", "" )
|
||||
|
||||
possibleTreeTags =
|
||||
Dict.fromList App.TreeStatus.Types.possibleTreeTags
|
||||
|
||||
parseTimestamp timestamp =
|
||||
timestamp
|
||||
|> String.split "T"
|
||||
|> List.drop 1
|
||||
|> List.take 1
|
||||
|> List.append
|
||||
(timestamp
|
||||
|> String.split "T"
|
||||
|> List.take 1
|
||||
)
|
||||
|> String.join " "
|
||||
|> String.split "."
|
||||
|> List.head
|
||||
|> Maybe.withDefault timestamp
|
||||
|
||||
buttons =
|
||||
if showUpdateStackForm == Just recentChange.id then
|
||||
[]
|
||||
else
|
||||
[ div
|
||||
[ class "btn-group" ]
|
||||
[ button
|
||||
[ type_ "button"
|
||||
, class "btn btn-sm btn-outline-success"
|
||||
, Utils.onClick (App.TreeStatus.Types.RevertChange recentChange.id)
|
||||
]
|
||||
[ text "Restore" ]
|
||||
, button
|
||||
[ type_ "button"
|
||||
, class "btn btn-sm btn-outline-info"
|
||||
, Utils.onClick (App.TreeStatus.Types.UpdateStackShow recentChange.id)
|
||||
]
|
||||
[ text "Update" ]
|
||||
, button
|
||||
[ type_ "button"
|
||||
, class "btn btn-sm btn-outline-warning"
|
||||
, Utils.onClick (App.TreeStatus.Types.DiscardChange recentChange.id)
|
||||
]
|
||||
[ text "Discard" ]
|
||||
]
|
||||
]
|
||||
in
|
||||
if hasScope "recent_changes/revert" scopes then
|
||||
[ div
|
||||
[ class "list-group-item justify-content-between" ]
|
||||
([ div
|
||||
[]
|
||||
([ p
|
||||
[]
|
||||
[ text "At "
|
||||
, em [] [ text (parseTimestamp recentChange.when) ]
|
||||
, b [] [ text (" " ++ TaskclusterLogin.shortUsername recentChange.who) ]
|
||||
, text " changed trees:"
|
||||
]
|
||||
, ul [] (List.map (viewRecentChangeTree recentChange.status) recentChange.trees)
|
||||
]
|
||||
|> App.Utils.appendItems
|
||||
(if showUpdateStackForm == Just recentChange.id then
|
||||
[ App.TreeStatus.Form.viewUpdateStack recentChange formUpdateStack
|
||||
|> Html.map App.TreeStatus.Types.FormUpdateStackMsg
|
||||
]
|
||||
else
|
||||
[ p []
|
||||
[ div []
|
||||
[ text "Reason category: "
|
||||
, span [ class "badge badge-default" ]
|
||||
[ text
|
||||
(possibleTreeTags
|
||||
|> Dict.get category
|
||||
|> Maybe.withDefault category
|
||||
)
|
||||
]
|
||||
]
|
||||
, div [] [ text "Reason: ", b [] (bugzillaBugAsLink reason) ]
|
||||
]
|
||||
]
|
||||
)
|
||||
)
|
||||
]
|
||||
|> App.Utils.appendItems buttons
|
||||
)
|
||||
]
|
||||
else
|
||||
[]
|
||||
|
||||
|
||||
viewRecentChanges :
|
||||
List String
|
||||
-> Maybe App.TreeStatus.Types.RecentChangeId
|
||||
-> Form.Form () App.TreeStatus.Form.UpdateStack
|
||||
-> RemoteData.WebData (List App.TreeStatus.Types.RecentChange)
|
||||
-> List (Html App.TreeStatus.Types.Msg)
|
||||
viewRecentChanges scopes showUpdateStackForm formUpdateStack recentChanges =
|
||||
case recentChanges of
|
||||
RemoteData.Success data ->
|
||||
let
|
||||
title =
|
||||
if List.isEmpty data then
|
||||
[]
|
||||
else
|
||||
[ h2 [] [ text "Recent Changes" ] ]
|
||||
|
||||
recentChanges =
|
||||
data
|
||||
|> List.map (viewRecentChange scopes showUpdateStackForm formUpdateStack)
|
||||
|> List.concat
|
||||
in
|
||||
[ div
|
||||
[ id "treestatus-recentchanges"
|
||||
, class "list-group"
|
||||
]
|
||||
([]
|
||||
|> App.Utils.appendItems title
|
||||
|> App.Utils.appendItems recentChanges
|
||||
)
|
||||
]
|
||||
|
||||
_ ->
|
||||
[]
|
||||
|
||||
|
||||
viewTreesCategoryItem :
|
||||
List String
|
||||
-> List String
|
||||
-> { a | reason : String, status : String, name : String, tags : List String }
|
||||
-> Html App.TreeStatus.Types.Msg
|
||||
viewTreesCategoryItem scopes treesSelected tree =
|
||||
let
|
||||
isChecked =
|
||||
List.member tree.name treesSelected
|
||||
|
||||
checking checked =
|
||||
case checked of
|
||||
True ->
|
||||
App.TreeStatus.Types.SelectTree tree.name
|
||||
|
||||
False ->
|
||||
App.TreeStatus.Types.UnselectTree tree.name
|
||||
|
||||
openTree =
|
||||
App.TreeStatus.Types.ShowTreeRoute tree.name
|
||||
|> App.TreeStatus.Types.NavigateTo
|
||||
|
||||
treeTagClass =
|
||||
"float-xs-right badge badge-" ++ App.Utils.treeStatusLevel tree.status
|
||||
|
||||
checkboxItem =
|
||||
if hasScope "trees/update" scopes || hasScope "trees/delete" scopes then
|
||||
[ label
|
||||
[ class "custom-control custom-checkbox" ]
|
||||
[ input
|
||||
[ type_ "checkbox"
|
||||
, class "custom-control-input"
|
||||
, checked isChecked
|
||||
, onCheck checking
|
||||
]
|
||||
[]
|
||||
, span
|
||||
[ class "custom-control-indicator" ]
|
||||
[]
|
||||
]
|
||||
]
|
||||
else
|
||||
[]
|
||||
|
||||
itemClass =
|
||||
if hasScope "trees/update" scopes || hasScope "trees/delete" scopes then
|
||||
"list-group-item list-group-item-with-checkbox"
|
||||
else
|
||||
"list-group-item"
|
||||
|
||||
treeItem =
|
||||
a
|
||||
[ href "#"
|
||||
, class "list-group-item-action"
|
||||
, Utils.onClick openTree
|
||||
]
|
||||
([]
|
||||
|> List.append
|
||||
(if tree.reason == "" then
|
||||
[]
|
||||
else
|
||||
[ p [ class "list-group-item-text" ]
|
||||
[ text tree.reason ]
|
||||
]
|
||||
)
|
||||
|> List.append
|
||||
[ h5 [ class "list-group-item-heading" ]
|
||||
[ text tree.name
|
||||
, span [ style [ ( "margin-left", "1em" ) ] ]
|
||||
(List.map
|
||||
(\tag ->
|
||||
span
|
||||
[ class "badge badge-default" ]
|
||||
[ App.TreeStatus.Types.possibleTreeTags
|
||||
|> List.filterMap
|
||||
(\( x, y ) ->
|
||||
if x == tag then
|
||||
Just y
|
||||
else
|
||||
Nothing
|
||||
)
|
||||
|> List.head
|
||||
|> Maybe.withDefault tag
|
||||
|> text
|
||||
]
|
||||
)
|
||||
tree.tags
|
||||
)
|
||||
, span [ class treeTagClass ]
|
||||
[ text tree.status ]
|
||||
]
|
||||
]
|
||||
)
|
||||
in
|
||||
div [ class itemClass ]
|
||||
(List.append checkboxItem [ treeItem ])
|
||||
|
||||
|
||||
type Category
|
||||
= Development
|
||||
| ReleaseStabilization
|
||||
| Try
|
||||
| CommRepositories
|
||||
| Other
|
||||
|
||||
|
||||
categoryTitle : Category -> String
|
||||
categoryTitle category =
|
||||
case category of
|
||||
Development ->
|
||||
"Development"
|
||||
|
||||
ReleaseStabilization ->
|
||||
"Release Stabilization"
|
||||
|
||||
Try ->
|
||||
"Try"
|
||||
|
||||
CommRepositories ->
|
||||
"Comm Repositories"
|
||||
|
||||
Other ->
|
||||
"Other"
|
||||
|
||||
|
||||
categorizeTrees :
|
||||
App.TreeStatus.Types.Trees
|
||||
-> List ( Category, App.TreeStatus.Types.Trees )
|
||||
categorizeTrees trees =
|
||||
let
|
||||
( developmentTrees, developmentTreesOther ) =
|
||||
List.partition
|
||||
(\tree ->
|
||||
List.member tree.name
|
||||
[ "autoland"
|
||||
, "mozilla-inbound"
|
||||
, "mozilla-central"
|
||||
]
|
||||
)
|
||||
trees
|
||||
|
||||
( releaseStabilizationTrees, releaseStabilizationTreesOther ) =
|
||||
List.partition
|
||||
(\tree ->
|
||||
List.member tree.name
|
||||
[ "mozilla-beta"
|
||||
, "mozilla-release"
|
||||
, "mozilla-esr68"
|
||||
, "mozilla-esr60"
|
||||
, "mozilla-esr52"
|
||||
]
|
||||
)
|
||||
developmentTreesOther
|
||||
|
||||
( tryTrees, tryTreesOther ) =
|
||||
List.partition
|
||||
(\tree ->
|
||||
List.member tree.name
|
||||
[ "try"
|
||||
, "try-comm-central"
|
||||
]
|
||||
)
|
||||
releaseStabilizationTreesOther
|
||||
|
||||
( commRepositoriesTrees, otherTrees ) =
|
||||
List.partition
|
||||
(\tree ->
|
||||
List.member tree.name
|
||||
[ "comm-central-thunderbird"
|
||||
, "comm-central-seamonkey"
|
||||
, "comm-beta-thunderbird"
|
||||
, "comm-beta-seamonkey"
|
||||
, "comm-release-thunderbird"
|
||||
, "comm-release-seamonkey"
|
||||
, "comm-esr68-thunderbird"
|
||||
, "comm-esr60-thunderbird"
|
||||
, "comm-esr60-seamonkey"
|
||||
, "comm-esr52-thunderbird"
|
||||
, "comm-esr52-seamonkey"
|
||||
]
|
||||
)
|
||||
tryTreesOther
|
||||
in
|
||||
[ ( Development, developmentTrees )
|
||||
, ( ReleaseStabilization, releaseStabilizationTrees )
|
||||
, ( Try, tryTrees )
|
||||
, ( CommRepositories, commRepositoriesTrees )
|
||||
, ( Other, otherTrees )
|
||||
]
|
||||
|
||||
|
||||
viewTreesCategory :
|
||||
List String
|
||||
-> List String
|
||||
-> ( Category, App.TreeStatus.Types.Trees )
|
||||
-> List (Html App.TreeStatus.Types.Msg)
|
||||
viewTreesCategory scopes treesSelected ( category, trees ) =
|
||||
if List.isEmpty trees then
|
||||
[]
|
||||
else
|
||||
[ h4 [] [ text (categoryTitle category) ]
|
||||
, div
|
||||
[ id "treestatus-trees"
|
||||
, class "list-group"
|
||||
]
|
||||
(trees
|
||||
|> List.sortBy .name
|
||||
|> List.map (viewTreesCategoryItem scopes treesSelected)
|
||||
|> (\x ->
|
||||
[ div
|
||||
[ id "treestatus-trees"
|
||||
, class "list-group"
|
||||
]
|
||||
x
|
||||
]
|
||||
)
|
||||
)
|
||||
]
|
||||
|
||||
|
||||
viewTrees :
|
||||
List String
|
||||
-> RemoteData.WebData App.TreeStatus.Types.Trees
|
||||
-> List String
|
||||
-> List (Html App.TreeStatus.Types.Msg)
|
||||
viewTrees scopes trees treesSelected =
|
||||
case trees of
|
||||
RemoteData.Success trees ->
|
||||
categorizeTrees trees
|
||||
|> List.map (viewTreesCategory scopes treesSelected)
|
||||
|> List.concat
|
||||
|
||||
RemoteData.Failure message ->
|
||||
[ App.Utils.error
|
||||
(App.TreeStatus.Types.NavigateTo App.TreeStatus.Types.ShowTreesRoute)
|
||||
(toString message)
|
||||
]
|
||||
|
||||
RemoteData.Loading ->
|
||||
[ App.Utils.loading ]
|
||||
|
||||
RemoteData.NotAsked ->
|
||||
[]
|
||||
|
||||
|
||||
viewButtons :
|
||||
App.TreeStatus.Types.Route
|
||||
-> List String
|
||||
-> App.TreeStatus.Types.Model App.TreeStatus.Form.AddTree App.TreeStatus.Form.UpdateTree App.TreeStatus.Form.UpdateStack App.TreeStatus.Form.UpdateLog
|
||||
-> Html App.TreeStatus.Types.Msg
|
||||
viewButtons route scopes model =
|
||||
let
|
||||
allSelected =
|
||||
List.length (RemoteData.withDefault [] model.trees)
|
||||
== List.length model.treesSelected
|
||||
|
||||
appendIf condition button =
|
||||
if condition then
|
||||
App.Utils.appendItem button
|
||||
else
|
||||
List.append []
|
||||
|
||||
treeRoute =
|
||||
case route of
|
||||
App.TreeStatus.Types.ShowTreeRoute name ->
|
||||
Just name
|
||||
|
||||
_ ->
|
||||
Nothing
|
||||
in
|
||||
div
|
||||
[ id "treestatus-trees-buttons"
|
||||
, class "btn-group"
|
||||
]
|
||||
([]
|
||||
|> appendIf
|
||||
(route /= App.TreeStatus.Types.ShowTreesRoute)
|
||||
(button
|
||||
[ class "btn btn-outline-info btn-sm btn-pill"
|
||||
, onClickGoTo App.TreeStatus.Types.ShowTreesRoute
|
||||
]
|
||||
[ text "Show All Trees" ]
|
||||
)
|
||||
|> appendIf
|
||||
(route
|
||||
== App.TreeStatus.Types.ShowTreesRoute
|
||||
&& (hasScope "trees/delete" scopes || hasScope "trees/update" scopes)
|
||||
)
|
||||
(button
|
||||
[ class "btn btn-outline-info btn-sm btn-pill"
|
||||
, Utils.onClick
|
||||
(if allSelected then
|
||||
App.TreeStatus.Types.UnselectAllTrees
|
||||
else
|
||||
App.TreeStatus.Types.SelectAllTrees
|
||||
)
|
||||
]
|
||||
[ text
|
||||
(if allSelected then
|
||||
"Unselect all trees"
|
||||
else
|
||||
"Select all trees"
|
||||
)
|
||||
]
|
||||
)
|
||||
|> appendIf
|
||||
(route
|
||||
== App.TreeStatus.Types.ShowTreesRoute
|
||||
&& hasScope "trees/create" scopes
|
||||
)
|
||||
(button
|
||||
[ class "btn btn-outline-success btn-sm btn-pill"
|
||||
, onClickGoTo App.TreeStatus.Types.AddTreeRoute
|
||||
]
|
||||
[ text "Add Tree" ]
|
||||
)
|
||||
|> appendIf
|
||||
((route == App.TreeStatus.Types.ShowTreesRoute || treeRoute /= Nothing)
|
||||
&& hasScope "trees/update" scopes
|
||||
)
|
||||
(button
|
||||
(if List.isEmpty model.treesSelected then
|
||||
[ class "btn btn-outline-primary btn-sm btn-pill tooltip2"
|
||||
, disabled (List.isEmpty model.treesSelected)
|
||||
, title "You need to select some trees"
|
||||
]
|
||||
else
|
||||
[ class "btn btn-outline-primary btn-sm btn-pill"
|
||||
, onClickGoTo App.TreeStatus.Types.UpdateTreesRoute
|
||||
]
|
||||
)
|
||||
[ text
|
||||
("Update "
|
||||
++ (if treeRoute == Nothing then
|
||||
"Tree(s)"
|
||||
else
|
||||
"Tree"
|
||||
)
|
||||
)
|
||||
]
|
||||
)
|
||||
|> appendIf
|
||||
((route == App.TreeStatus.Types.ShowTreesRoute || treeRoute /= Nothing)
|
||||
&& hasScope "trees/delete" scopes
|
||||
)
|
||||
(button
|
||||
(if List.isEmpty model.treesSelected then
|
||||
[ class "btn btn-outline-danger btn-sm btn-pill tooltip2"
|
||||
, disabled (List.isEmpty model.treesSelected)
|
||||
, title "You need to select some trees"
|
||||
]
|
||||
else
|
||||
[ class "btn btn-outline-danger btn-sm btn-pill"
|
||||
, onClickGoTo App.TreeStatus.Types.DeleteTreesRoute
|
||||
]
|
||||
)
|
||||
[ text
|
||||
("Delete "
|
||||
++ (if treeRoute == Nothing then
|
||||
"Tree(s)"
|
||||
else
|
||||
"Tree"
|
||||
)
|
||||
)
|
||||
]
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
viewConfirmDelete :
|
||||
Maybe String
|
||||
-> Bool
|
||||
-> List String
|
||||
-> List (Html App.TreeStatus.Types.Msg)
|
||||
viewConfirmDelete deleteError deleteTreesConfirm treesSelected =
|
||||
[ div
|
||||
[ id "treestatus-form" ]
|
||||
[ div
|
||||
[]
|
||||
([]
|
||||
|> App.Utils.appendItem
|
||||
(text "You are about to delete the following trees:")
|
||||
|> App.Utils.appendItem
|
||||
(treesSelected
|
||||
|> List.map (\x -> li [] [ text x ])
|
||||
|> ul []
|
||||
)
|
||||
)
|
||||
, hr [] []
|
||||
, div
|
||||
[ class ("form-group " ++ App.Form.errorClass deleteError) ]
|
||||
([]
|
||||
|> App.Utils.appendItem
|
||||
(label
|
||||
[ class "custom-control custom-checkbox" ]
|
||||
[ input
|
||||
[ type_ "checkbox"
|
||||
, class "custom-control-input"
|
||||
, checked deleteTreesConfirm
|
||||
, onCheck (\x -> App.TreeStatus.Types.DeleteTreesConfirmToggle)
|
||||
]
|
||||
[]
|
||||
, span
|
||||
[ class "custom-control-indicator" ]
|
||||
[]
|
||||
, span
|
||||
[ class "custom-control-description" ]
|
||||
[ text "I acknowledge this action is irreversible." ]
|
||||
]
|
||||
)
|
||||
)
|
||||
, hr [] []
|
||||
, button
|
||||
[ class "btn btn-outline-danger"
|
||||
, Utils.onClick App.TreeStatus.Types.DeleteTrees
|
||||
]
|
||||
[ text "Delete" ]
|
||||
, div [ class "clearfix" ] []
|
||||
]
|
||||
]
|
||||
|
||||
|
||||
viewTreesTitle : App.TreeStatus.Types.Route -> Html msg
|
||||
viewTreesTitle route =
|
||||
case route of
|
||||
App.TreeStatus.Types.ShowTreesRoute ->
|
||||
h2 [ class "float-xs-left" ] [ text "Trees" ]
|
||||
|
||||
App.TreeStatus.Types.AddTreeRoute ->
|
||||
h2 [ class "float-xs-left" ] [ text "Add Tree" ]
|
||||
|
||||
App.TreeStatus.Types.UpdateTreesRoute ->
|
||||
h2 [ class "float-xs-left" ] [ text "Update Tree(s)" ]
|
||||
|
||||
App.TreeStatus.Types.DeleteTreesRoute ->
|
||||
h2 [ class "float-xs-left" ] [ text "Delete Tree(s)" ]
|
||||
|
||||
App.TreeStatus.Types.ShowTreeRoute name ->
|
||||
h2 [ class "float-xs-left" ] [ text ("Tree: " ++ name) ]
|
||||
|
||||
|
||||
treeRulesLink : String -> String
|
||||
treeRulesLink treeName =
|
||||
case treeName of
|
||||
"autoland" ->
|
||||
"https://wiki.mozilla.org/Tree_Rules#autoland.2FLando"
|
||||
|
||||
"mozilla-inbound" ->
|
||||
"https://wiki.mozilla.org/Tree_Rules#mozilla-inbound"
|
||||
|
||||
"mozilla-central" ->
|
||||
"https://wiki.mozilla.org/Tree_Rules#mozilla-central_.28Nightly_channel.29"
|
||||
|
||||
"mozilla-beta" ->
|
||||
"https://wiki.mozilla.org/Tree_Rules#mozilla-beta"
|
||||
|
||||
"mozilla-release" ->
|
||||
"https://wiki.mozilla.org/Tree_Rules#mozilla-release"
|
||||
|
||||
_ ->
|
||||
if String.startsWith "mozilla-esr" treeName then
|
||||
"https://wiki.mozilla.org/Release_Management/ESR_Landing_Process"
|
||||
else if String.startsWith "comm-" treeName then
|
||||
"https://wiki.mozilla.org/Tree_Rules/comm-central"
|
||||
else
|
||||
"https://wiki.mozilla.org/Tree_Rules"
|
||||
|
||||
|
||||
viewTreeDetails :
|
||||
RemoteData.RemoteData a { b | message_of_the_day : String, name : String, status : String }
|
||||
-> Html App.TreeStatus.Types.Msg
|
||||
viewTreeDetails remote =
|
||||
case remote of
|
||||
RemoteData.Success tree ->
|
||||
div
|
||||
[ id "treestatus-tree-details" ]
|
||||
[ span
|
||||
[]
|
||||
[ text tree.name ]
|
||||
, text " status is "
|
||||
, span
|
||||
[ class ("badge badge-" ++ App.Utils.treeStatusLevel tree.status) ]
|
||||
[ text tree.status ]
|
||||
, p [ class "lead" ] (bugzillaBugAsLink tree.message_of_the_day)
|
||||
, p [] [ a [ href (treeRulesLink tree.name) ] [ text "Tree rules" ] ]
|
||||
]
|
||||
|
||||
RemoteData.Failure message ->
|
||||
App.Utils.error (App.TreeStatus.Types.NavigateTo App.TreeStatus.Types.ShowTreesRoute) (toString message)
|
||||
|
||||
RemoteData.Loading ->
|
||||
App.Utils.loading
|
||||
|
||||
RemoteData.NotAsked ->
|
||||
text ""
|
||||
|
||||
|
||||
viewTreeLog :
|
||||
List String
|
||||
-> Maybe Int
|
||||
-> Form.Form () App.TreeStatus.Form.UpdateLog
|
||||
-> App.TreeStatus.Types.TreeLog
|
||||
-> Html App.TreeStatus.Types.Msg
|
||||
viewTreeLog scopes showUpdateLog formUpdateLog log =
|
||||
let
|
||||
who2 =
|
||||
if String.startsWith "human:" log.who then
|
||||
log.who
|
||||
|> String.dropLeft 6
|
||||
else
|
||||
log.who
|
||||
|
||||
who =
|
||||
who2
|
||||
|> String.split "@"
|
||||
|> List.head
|
||||
|> Maybe.withDefault who2
|
||||
|
||||
possibleTreeTags =
|
||||
Dict.fromList App.TreeStatus.Types.possibleTreeTags
|
||||
|
||||
category =
|
||||
log.tags
|
||||
|> List.head
|
||||
|> Maybe.map
|
||||
(\tag ->
|
||||
possibleTreeTags
|
||||
|> Dict.get tag
|
||||
|> Maybe.withDefault tag
|
||||
)
|
||||
|> Maybe.withDefault ""
|
||||
|
||||
view =
|
||||
[ p []
|
||||
[ div []
|
||||
[ text "Reason category: "
|
||||
, span [ class "badge badge-default" ] [ text category ]
|
||||
]
|
||||
, div [] [ text "Reason: ", b [] (bugzillaBugAsLink log.reason) ]
|
||||
]
|
||||
, if hasScope "trees/update" scopes then
|
||||
button
|
||||
[ type_ "button"
|
||||
, class "btn btn-sm btn-outline-info"
|
||||
, style [ ( "float", "left" ) ]
|
||||
, Utils.onClick (App.TreeStatus.Types.UpdateLogShow log.id)
|
||||
]
|
||||
[ text "Update" ]
|
||||
else
|
||||
span [] []
|
||||
, div [ class "clearfix" ] []
|
||||
]
|
||||
|
||||
form =
|
||||
[ App.TreeStatus.Form.viewUpdateLog log.status formUpdateLog
|
||||
|> Html.map App.TreeStatus.Types.FormUpdateLogMsg
|
||||
]
|
||||
in
|
||||
div [ class "timeline-item" ]
|
||||
--TODO: show status in hover of the badge
|
||||
[ div [ class <| "timeline-badge badge-" ++ App.Utils.treeStatusLevel log.status ]
|
||||
[ text " " ]
|
||||
, div [ class "timeline-panel" ]
|
||||
[ div [ class "timeline-time" ]
|
||||
[ text log.when ]
|
||||
, h5 [] [ text who ]
|
||||
, div []
|
||||
(if showUpdateLog == Just log.id then
|
||||
form
|
||||
else
|
||||
view
|
||||
)
|
||||
]
|
||||
]
|
||||
|
||||
|
||||
viewTreeLogs :
|
||||
List String
|
||||
-> String
|
||||
-> RemoteData.WebData App.TreeStatus.Types.TreeLogs
|
||||
-> RemoteData.WebData App.TreeStatus.Types.TreeLogs
|
||||
-> Maybe Int
|
||||
-> Form.Form () App.TreeStatus.Form.UpdateLog
|
||||
-> Html App.TreeStatus.Types.Msg
|
||||
viewTreeLogs scopes name treeLogs_ treeLogsAll_ showUpdateLog formUpdateLog =
|
||||
let
|
||||
( moreButton, treeLogsAll ) =
|
||||
case treeLogsAll_ of
|
||||
RemoteData.Success treeLogs ->
|
||||
( []
|
||||
, List.drop 5 treeLogs
|
||||
)
|
||||
|
||||
RemoteData.Failure message ->
|
||||
( [ App.Utils.error (App.TreeStatus.Types.NavigateTo App.TreeStatus.Types.ShowTreesRoute) (toString message) ]
|
||||
, []
|
||||
)
|
||||
|
||||
RemoteData.Loading ->
|
||||
( [ button
|
||||
[ class "btn btn-secondary"
|
||||
, Utils.onClick <| App.TreeStatus.Types.GetTreeLogs name True
|
||||
]
|
||||
[ text "Loading"
|
||||
, i [ class "fa fa-circle-o-notch fa-spin" ] []
|
||||
]
|
||||
]
|
||||
, []
|
||||
)
|
||||
|
||||
RemoteData.NotAsked ->
|
||||
( [ button
|
||||
[ class "btn btn-secondary"
|
||||
, Utils.onClick <| App.TreeStatus.Types.GetTreeLogs name True
|
||||
]
|
||||
[ text "Load more" ]
|
||||
]
|
||||
, []
|
||||
)
|
||||
in
|
||||
case treeLogs_ of
|
||||
RemoteData.Success treeLogs ->
|
||||
div [ class "timeline" ]
|
||||
(List.append
|
||||
(List.append
|
||||
(List.map (viewTreeLog scopes showUpdateLog formUpdateLog) treeLogs)
|
||||
(List.map (viewTreeLog scopes showUpdateLog formUpdateLog) treeLogsAll)
|
||||
)
|
||||
[ div [ class "timeline-item timeline-more" ]
|
||||
[ div [ class "timeline-panel" ] moreButton ]
|
||||
]
|
||||
)
|
||||
|
||||
RemoteData.Failure message ->
|
||||
App.Utils.error (App.TreeStatus.Types.NavigateTo App.TreeStatus.Types.ShowTreesRoute) (toString message)
|
||||
|
||||
RemoteData.Loading ->
|
||||
App.Utils.loading
|
||||
|
||||
RemoteData.NotAsked ->
|
||||
text ""
|
||||
|
||||
|
||||
viewTree :
|
||||
List String
|
||||
-> RemoteData.WebData App.TreeStatus.Types.Tree
|
||||
-> RemoteData.WebData App.TreeStatus.Types.TreeLogs
|
||||
-> RemoteData.WebData App.TreeStatus.Types.TreeLogs
|
||||
-> Maybe Int
|
||||
-> Form.Form () App.TreeStatus.Form.UpdateLog
|
||||
-> String
|
||||
-> List (Html App.TreeStatus.Types.Msg)
|
||||
viewTree scopes tree treeLogs treeLogsAll showUpdateLog formUpdateLog name =
|
||||
[ div
|
||||
[ id "treestatus-form" ]
|
||||
[ viewTreeDetails tree
|
||||
, hr [] []
|
||||
, viewTreeLogs scopes name treeLogs treeLogsAll showUpdateLog formUpdateLog
|
||||
]
|
||||
]
|
|
@ -1,32 +0,0 @@
|
|||
module App.Types exposing (..)
|
||||
|
||||
import UrlParser
|
||||
|
||||
|
||||
type alias Page a b =
|
||||
{ title : String
|
||||
, description : String
|
||||
, matcher : UrlParser.Parser (a -> b) b
|
||||
}
|
||||
|
||||
|
||||
type alias ResponseError =
|
||||
{ type_ : String
|
||||
, detail : String
|
||||
, status : Int
|
||||
, title : String
|
||||
}
|
||||
|
||||
|
||||
type AlertType
|
||||
= AlertSuccess
|
||||
| AlertInfo
|
||||
| AlertWarning
|
||||
| AlertDanger
|
||||
|
||||
|
||||
type alias Alert =
|
||||
{ type_ : AlertType
|
||||
, title : String
|
||||
, text : String
|
||||
}
|
|
@ -1,112 +0,0 @@
|
|||
module App.UserScopes exposing (..)
|
||||
|
||||
import Hawk
|
||||
import Http
|
||||
import Json.Decode as JsonDecode
|
||||
import RemoteData exposing (WebData)
|
||||
import String
|
||||
import Task
|
||||
import Time
|
||||
import Utils
|
||||
|
||||
|
||||
type alias Model =
|
||||
{ timestamp : Time.Time
|
||||
, scopes : List String
|
||||
}
|
||||
|
||||
|
||||
type Msg
|
||||
= FetchScopes
|
||||
| CacheScopes Time.Time
|
||||
| FetchedScopes (WebData String)
|
||||
|
||||
|
||||
decoderScopes : JsonDecode.Decoder (List String)
|
||||
decoderScopes =
|
||||
JsonDecode.at [ "scopes" ] (JsonDecode.list JsonDecode.string)
|
||||
|
||||
|
||||
init : Model
|
||||
init =
|
||||
{ timestamp = 0.0
|
||||
, scopes = []
|
||||
}
|
||||
|
||||
|
||||
update :
|
||||
Msg
|
||||
-> Model
|
||||
-> ( Model, Cmd Msg, Maybe Hawk.Request )
|
||||
update msg model =
|
||||
case msg of
|
||||
FetchScopes ->
|
||||
( model
|
||||
, Task.perform CacheScopes Time.now
|
||||
, Nothing
|
||||
)
|
||||
|
||||
CacheScopes currentTime ->
|
||||
let
|
||||
headers =
|
||||
[ Http.header "Accept" "application/json" ]
|
||||
|
||||
url =
|
||||
"https://auth.taskcluster.net/v1/scopes/current"
|
||||
|
||||
request =
|
||||
Hawk.Request "FetchedScopes" "GET" url headers Http.emptyBody
|
||||
|
||||
( newModel, hawkCmd ) =
|
||||
if (model.timestamp + 15000) > currentTime then
|
||||
( model, Nothing )
|
||||
else
|
||||
( { model | scopes = [] }
|
||||
, Just request
|
||||
)
|
||||
in
|
||||
( newModel, Cmd.none, hawkCmd )
|
||||
|
||||
FetchedScopes result ->
|
||||
let
|
||||
scopes =
|
||||
Utils.decodeJsonString decoderScopes result
|
||||
|> RemoteData.withDefault []
|
||||
in
|
||||
( { model | scopes = scopes }
|
||||
, Cmd.none
|
||||
, Nothing
|
||||
)
|
||||
|
||||
|
||||
hawkResponse :
|
||||
Cmd (WebData String)
|
||||
-> String
|
||||
-> Cmd Msg
|
||||
hawkResponse response route =
|
||||
case route of
|
||||
"FetchedScopes" ->
|
||||
Cmd.map FetchedScopes response
|
||||
|
||||
_ ->
|
||||
Cmd.none
|
||||
|
||||
|
||||
hasScope : List String -> String -> Bool
|
||||
hasScope existing scope =
|
||||
existing
|
||||
|> List.map
|
||||
(\x ->
|
||||
if String.endsWith "*" x then
|
||||
String.startsWith (String.dropRight 1 x) scope
|
||||
else
|
||||
scope == x
|
||||
)
|
||||
|> List.any Basics.identity
|
||||
|
||||
|
||||
hasScopes : List String -> List String -> Bool
|
||||
hasScopes existing scopes =
|
||||
scopes
|
||||
|> List.map (hasScope existing)
|
||||
|> List.all Basics.identity
|
|
@ -1,204 +0,0 @@
|
|||
module App.Utils exposing (..)
|
||||
|
||||
import App.Types
|
||||
import Html exposing (..)
|
||||
import Html.Attributes exposing (..)
|
||||
import Http
|
||||
import Json.Decode as JsonDecode
|
||||
import RemoteData exposing (RemoteData(..), WebData)
|
||||
import Utils
|
||||
import VirtualDom
|
||||
|
||||
|
||||
dropdown :
|
||||
(String -> a)
|
||||
-> List { b | name : String }
|
||||
-> Maybe String
|
||||
-> Html a
|
||||
dropdown event items selected =
|
||||
div [ class "btn-group btn-dropdown" ]
|
||||
[ span
|
||||
[ type_ "button"
|
||||
, class "btn btn-secondary dropdown-toggle"
|
||||
, attribute "data-toggle" "dropdown"
|
||||
, attribute "aria-haspopup" "true"
|
||||
, attribute "aria-expanded" "false"
|
||||
]
|
||||
[ text <| Maybe.withDefault "Select a value..." selected
|
||||
]
|
||||
, span
|
||||
[ type_ "button"
|
||||
, class "btn btn-secondary dropdown-toggle"
|
||||
, attribute "data-toggle" "dropdown"
|
||||
, attribute "aria-haspopup" "true"
|
||||
, attribute "aria-expanded" "false"
|
||||
]
|
||||
[ span [ class "sr-only" ] [ text "Toggle Dropdown" ]
|
||||
]
|
||||
, div [ class "dropdown-menu" ]
|
||||
(List.map
|
||||
(\x ->
|
||||
a
|
||||
[ Utils.onClick (event x.name)
|
||||
, class "dropdown-item"
|
||||
]
|
||||
[ text x.name ]
|
||||
)
|
||||
items
|
||||
)
|
||||
]
|
||||
|
||||
|
||||
loading : VirtualDom.Node a
|
||||
loading =
|
||||
div
|
||||
[ class "progress"
|
||||
]
|
||||
[ div
|
||||
[ class "progress-bar progress-bar-striped progress-bar-animated"
|
||||
, attribute "role" "progressbar"
|
||||
, attribute "aria-valuenow" "100"
|
||||
, attribute "aria-valuemin" "0"
|
||||
, attribute "aria-valuemax" "100"
|
||||
, style [ ( "width", "100%" ) ]
|
||||
]
|
||||
[ text "Loading ..." ]
|
||||
]
|
||||
|
||||
|
||||
error : a -> String -> Html a
|
||||
error event message =
|
||||
div
|
||||
[ class "alert alert-danger"
|
||||
, attribute "role" "alert"
|
||||
]
|
||||
[ i [ class "fa fa-exclamation-triangle" ] []
|
||||
, strong [] [ text " Error: " ]
|
||||
, span [] [ text message ]
|
||||
, a
|
||||
[ Utils.onClick event
|
||||
, class "alert-link"
|
||||
]
|
||||
[ text " Click to retry." ]
|
||||
]
|
||||
|
||||
|
||||
success : a -> String -> Html a
|
||||
success event message =
|
||||
div [ class "alert alert-success", attribute "role" "alert" ]
|
||||
[ i [ class "fa fa-check" ] []
|
||||
, strong [] [ text " Success: " ]
|
||||
, span [] [ text message ]
|
||||
, a
|
||||
[ Utils.onClick event
|
||||
, class "alert-link"
|
||||
]
|
||||
[ text " Click to remove." ]
|
||||
]
|
||||
|
||||
|
||||
getAlerts : WebData String -> List App.Types.Alert
|
||||
getAlerts response =
|
||||
let
|
||||
decoderError =
|
||||
JsonDecode.map4 App.Types.ResponseError
|
||||
(JsonDecode.field "type" JsonDecode.string)
|
||||
(JsonDecode.field "detail" JsonDecode.string)
|
||||
(JsonDecode.field "status" JsonDecode.int)
|
||||
(JsonDecode.field "title" JsonDecode.string)
|
||||
|
||||
handleError error =
|
||||
[ App.Types.Alert
|
||||
App.Types.AlertDanger
|
||||
"Error!"
|
||||
(case error of
|
||||
Http.BadUrl url ->
|
||||
"Bad Url: " ++ url
|
||||
|
||||
Http.Timeout ->
|
||||
"Request Timeout"
|
||||
|
||||
Http.NetworkError ->
|
||||
"A network error occured"
|
||||
|
||||
Http.BadPayload details response ->
|
||||
"Bad payload: " ++ details
|
||||
|
||||
Http.BadStatus response ->
|
||||
case JsonDecode.decodeString decoderError response.body of
|
||||
Ok obj ->
|
||||
obj.detail
|
||||
|
||||
Err error ->
|
||||
error
|
||||
)
|
||||
]
|
||||
in
|
||||
case response of
|
||||
Success r ->
|
||||
[]
|
||||
|
||||
Loading ->
|
||||
[]
|
||||
|
||||
NotAsked ->
|
||||
[]
|
||||
|
||||
Failure e ->
|
||||
handleError e
|
||||
|
||||
|
||||
viewAlerts :
|
||||
List App.Types.Alert
|
||||
-> Html a
|
||||
viewAlerts alerts =
|
||||
let
|
||||
getAlertTypeAsString alert =
|
||||
case alert.type_ of
|
||||
App.Types.AlertSuccess ->
|
||||
"success"
|
||||
|
||||
App.Types.AlertInfo ->
|
||||
"info"
|
||||
|
||||
App.Types.AlertWarning ->
|
||||
"warning"
|
||||
|
||||
App.Types.AlertDanger ->
|
||||
"danger"
|
||||
|
||||
createAlert alert =
|
||||
div [ class ("alert alert-" ++ getAlertTypeAsString alert) ]
|
||||
[ strong [] [ text alert.title ]
|
||||
, text alert.text
|
||||
]
|
||||
in
|
||||
alerts
|
||||
|> List.map createAlert
|
||||
|> div []
|
||||
|
||||
|
||||
appendItem : a -> List a -> List a
|
||||
appendItem item items =
|
||||
List.append items [ item ]
|
||||
|
||||
|
||||
appendItems : List a -> List a -> List a
|
||||
appendItems items1 items2 =
|
||||
List.append items2 items1
|
||||
|
||||
|
||||
treeStatusLevel : String -> String
|
||||
treeStatusLevel status =
|
||||
case status of
|
||||
"closed" ->
|
||||
"danger"
|
||||
|
||||
"open" ->
|
||||
"success"
|
||||
|
||||
"approval required" ->
|
||||
"warning"
|
||||
|
||||
_ ->
|
||||
"default"
|
|
@ -1 +0,0 @@
|
|||
../../../lib/frontend_common/Hawk.elm
|
|
@ -1,305 +0,0 @@
|
|||
module Main exposing (..)
|
||||
|
||||
import App
|
||||
import App.Home
|
||||
import App.Layout
|
||||
import App.Tokens
|
||||
import App.ToolTool
|
||||
import App.TreeStatus
|
||||
import App.TreeStatus.Api
|
||||
import App.TreeStatus.Types
|
||||
import App.UserScopes
|
||||
import Hawk
|
||||
import Html exposing (..)
|
||||
import Navigation
|
||||
import String
|
||||
import TaskclusterLogin
|
||||
import Utils
|
||||
|
||||
|
||||
main : Program App.Flags App.Model App.Msg
|
||||
main =
|
||||
Navigation.programWithFlags App.UrlChange
|
||||
{ init = init
|
||||
, view = App.Layout.view viewRoute
|
||||
, update = update
|
||||
, subscriptions = subscriptions
|
||||
}
|
||||
|
||||
|
||||
init : App.Flags -> Navigation.Location -> ( App.Model, Cmd App.Msg )
|
||||
init flags location =
|
||||
let
|
||||
route =
|
||||
App.parseLocation location
|
||||
|
||||
( user, userCmd ) =
|
||||
TaskclusterLogin.init flags.treestatusUrl flags.auth0
|
||||
|
||||
model =
|
||||
{ history = [ location ]
|
||||
, route = route
|
||||
, docsUrl = flags.docsUrl
|
||||
, version = flags.version
|
||||
, user = user
|
||||
, userScopes = App.UserScopes.init
|
||||
, tokens = App.Tokens.init
|
||||
, tooltool = App.ToolTool.init
|
||||
, treestatus = App.TreeStatus.init flags.treestatusUrl
|
||||
}
|
||||
|
||||
( model_, appCmd ) =
|
||||
initRoute model route
|
||||
in
|
||||
( model_
|
||||
, Cmd.batch
|
||||
[ appCmd
|
||||
, Cmd.map App.TaskclusterLoginMsg userCmd
|
||||
]
|
||||
)
|
||||
|
||||
|
||||
initRoute : App.Model -> App.Route -> ( App.Model, Cmd App.Msg )
|
||||
initRoute model route =
|
||||
case route of
|
||||
App.NotFoundRoute ->
|
||||
model ! []
|
||||
|
||||
App.HomeRoute ->
|
||||
{ model
|
||||
| tokens = App.Tokens.init
|
||||
, tooltool = App.ToolTool.init
|
||||
, treestatus =
|
||||
App.TreeStatus.init model.treestatus.baseUrl
|
||||
}
|
||||
! [ Utils.performMsg (App.UserScopesMsg App.UserScopes.FetchScopes) ]
|
||||
|
||||
App.LoginRoute code state ->
|
||||
let
|
||||
loginCmd =
|
||||
case TaskclusterLogin.convertUrlParametersToCode code state of
|
||||
Just code_ ->
|
||||
TaskclusterLogin.Logging code_
|
||||
|> App.TaskclusterLoginMsg
|
||||
|> Utils.performMsg
|
||||
|
||||
Nothing ->
|
||||
Cmd.none
|
||||
in
|
||||
model
|
||||
! [ loginCmd
|
||||
, App.navigateTo App.HomeRoute
|
||||
]
|
||||
|
||||
App.LogoutRoute ->
|
||||
model
|
||||
! [ Utils.performMsg (App.TaskclusterLoginMsg TaskclusterLogin.Logout)
|
||||
|
||||
-- TODO: we should be redirecting to the url that we were loging in from
|
||||
, Utils.performMsg (App.NavigateTo App.HomeRoute)
|
||||
]
|
||||
|
||||
App.TokensRoute ->
|
||||
model ! []
|
||||
|
||||
App.ToolToolRoute ->
|
||||
model ! []
|
||||
|
||||
App.TreeStatusRoute route ->
|
||||
model
|
||||
! [ Utils.performMsg (App.TreeStatusMsg (App.TreeStatus.Types.NavigateTo route))
|
||||
, Utils.performMsg (App.UserScopesMsg App.UserScopes.FetchScopes)
|
||||
]
|
||||
|
||||
|
||||
update : App.Msg -> App.Model -> ( App.Model, Cmd App.Msg )
|
||||
update msg model =
|
||||
case msg of
|
||||
--
|
||||
-- ROUTING
|
||||
--
|
||||
App.UrlChange location ->
|
||||
{ model
|
||||
| history = location :: model.history
|
||||
, route = App.parseLocation location
|
||||
}
|
||||
! []
|
||||
|
||||
App.NavigateTo route ->
|
||||
let
|
||||
( newModel, newCmd ) =
|
||||
initRoute model route
|
||||
in
|
||||
( newModel
|
||||
, Cmd.batch
|
||||
[ App.navigateTo route
|
||||
, newCmd
|
||||
]
|
||||
)
|
||||
|
||||
--
|
||||
-- LOGIN / LOGOUT
|
||||
--
|
||||
App.TaskclusterLoginMsg userMsg ->
|
||||
let
|
||||
( newUser, userCmd ) =
|
||||
TaskclusterLogin.update userMsg model.user
|
||||
|
||||
fetchScopes =
|
||||
case userMsg of
|
||||
TaskclusterLogin.LoadedTaskclusterCredentials response ->
|
||||
[ Utils.performMsg (App.UserScopesMsg App.UserScopes.FetchScopes) ]
|
||||
|
||||
_ ->
|
||||
[]
|
||||
in
|
||||
( { model | user = newUser }
|
||||
, [ Cmd.map App.TaskclusterLoginMsg userCmd ]
|
||||
|> List.append fetchScopes
|
||||
|> Cmd.batch
|
||||
)
|
||||
|
||||
--
|
||||
-- HAWK REQUESTS
|
||||
--
|
||||
App.HawkMsg hawkMsg ->
|
||||
let
|
||||
( requestId, cmd, response ) =
|
||||
Hawk.update hawkMsg
|
||||
|
||||
routeHawkMsg route =
|
||||
if String.startsWith "TreeStatus" route then
|
||||
route
|
||||
|> String.dropLeft (String.length "TreeStatus")
|
||||
|> App.TreeStatus.Api.hawkResponse response
|
||||
|> Cmd.map App.TreeStatusMsg
|
||||
else if String.startsWith "UserScopes" route then
|
||||
route
|
||||
|> String.dropLeft (String.length "UserScopes")
|
||||
|> App.UserScopes.hawkResponse response
|
||||
|> Cmd.map App.UserScopesMsg
|
||||
else
|
||||
Cmd.none
|
||||
|
||||
appCmd =
|
||||
requestId
|
||||
|> Maybe.map routeHawkMsg
|
||||
|> Maybe.withDefault Cmd.none
|
||||
in
|
||||
( model
|
||||
, Cmd.batch
|
||||
[ Cmd.map App.HawkMsg cmd
|
||||
, appCmd
|
||||
]
|
||||
)
|
||||
|
||||
App.UserScopesMsg msg_ ->
|
||||
let
|
||||
( newModel, newCmd, hawkCmd ) =
|
||||
App.UserScopes.update msg_ model.userScopes
|
||||
in
|
||||
( { model | userScopes = newModel }
|
||||
, hawkCmd
|
||||
|> Maybe.map (\req -> [ hawkSend model.user "UserScopes" req ])
|
||||
|> Maybe.withDefault []
|
||||
|> List.append [ Cmd.map App.UserScopesMsg newCmd ]
|
||||
|> Cmd.batch
|
||||
)
|
||||
|
||||
App.TokensMsg msg_ ->
|
||||
let
|
||||
( newModel, newCmd ) =
|
||||
App.Tokens.update msg_ model.tokens
|
||||
in
|
||||
( { model | tokens = newModel }
|
||||
, Cmd.map App.TokensMsg newCmd
|
||||
)
|
||||
|
||||
App.ToolToolMsg msg_ ->
|
||||
let
|
||||
( newModel, newCmd ) =
|
||||
App.ToolTool.update msg_ model.tooltool
|
||||
in
|
||||
( { model | tooltool = newModel }
|
||||
, Cmd.map App.ToolToolMsg newCmd
|
||||
)
|
||||
|
||||
App.TreeStatusMsg msg_ ->
|
||||
let
|
||||
route =
|
||||
case model.route of
|
||||
App.TreeStatusRoute x ->
|
||||
x
|
||||
|
||||
_ ->
|
||||
App.TreeStatus.Types.ShowTreesRoute
|
||||
|
||||
( newModel, newCmd, hawkCmd ) =
|
||||
App.TreeStatus.update route msg_ model.treestatus
|
||||
in
|
||||
( { model | treestatus = newModel }
|
||||
, hawkCmd
|
||||
|> Maybe.map (\req -> [ hawkSend model.user "TreeStatus" req ])
|
||||
|> Maybe.withDefault []
|
||||
|> List.append [ Cmd.map App.TreeStatusMsg newCmd ]
|
||||
|> Cmd.batch
|
||||
)
|
||||
|
||||
|
||||
hawkSend :
|
||||
TaskclusterLogin.Model
|
||||
-> String
|
||||
-> Hawk.Request
|
||||
-> Cmd App.Msg
|
||||
hawkSend user page request =
|
||||
let
|
||||
pagedRequest =
|
||||
{ request | id = page ++ request.id }
|
||||
in
|
||||
case user.credentials of
|
||||
Just credentials ->
|
||||
Hawk.send pagedRequest credentials
|
||||
|> Cmd.map App.HawkMsg
|
||||
|
||||
Nothing ->
|
||||
Cmd.none
|
||||
|
||||
|
||||
viewRoute : App.Model -> Html App.Msg
|
||||
viewRoute model =
|
||||
case model.route of
|
||||
App.NotFoundRoute ->
|
||||
App.Layout.viewNotFound model
|
||||
|
||||
App.HomeRoute ->
|
||||
App.Home.view model
|
||||
|
||||
App.LoginRoute _ _ ->
|
||||
-- TODO: this should be already a view on TaskclusterLogin
|
||||
text "Logging you in ..."
|
||||
|
||||
App.LogoutRoute ->
|
||||
-- TODO: this should be already a view on TaskclusterLogin
|
||||
text "Logging you out ..."
|
||||
|
||||
App.TokensRoute ->
|
||||
Html.map App.TokensMsg (App.Tokens.view model.tokens)
|
||||
|
||||
App.ToolToolRoute ->
|
||||
Html.map App.ToolToolMsg (App.ToolTool.view model.tooltool)
|
||||
|
||||
App.TreeStatusRoute route ->
|
||||
App.TreeStatus.view
|
||||
route
|
||||
model.user
|
||||
model.userScopes
|
||||
model.treestatus
|
||||
|> Html.map App.TreeStatusMsg
|
||||
|
||||
|
||||
subscriptions : App.Model -> Sub App.Msg
|
||||
subscriptions model =
|
||||
Sub.batch
|
||||
[ TaskclusterLogin.subscriptions App.TaskclusterLoginMsg
|
||||
, Hawk.subscriptions App.HawkMsg
|
||||
]
|
|
@ -1 +0,0 @@
|
|||
../../../lib/frontend_common/Redirect.elm
|
|
@ -1 +0,0 @@
|
|||
../../../lib/frontend_common/TaskclusterLogin.elm
|
|
@ -1 +0,0 @@
|
|||
../../../lib/frontend_common/Title.elm
|
|
@ -1 +0,0 @@
|
|||
../../../lib/frontend_common/Utils.elm
|
|
@ -1 +0,0 @@
|
|||
../../../lib/frontend_common/hawk.js
|
|
@ -1,14 +0,0 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no" />
|
||||
<meta http-equiv="x-ua-compatible" content="ie=edge" />
|
||||
<meta name="description" content="Collection of Mozilla Release Engineering Services" />
|
||||
<meta name="author" content="Mozilla RelEng Team" />
|
||||
<title>Mozilla RelEng Services</title>
|
||||
</head>
|
||||
<body>
|
||||
</body>
|
||||
</html>
|
||||
|
|
@ -1,66 +0,0 @@
|
|||
'use strict';
|
||||
|
||||
require('expose-loader?jQuery!jquery');
|
||||
require('expose-loader?Tether!tether');
|
||||
require('bootstrap');
|
||||
require('./scss/index.scss');
|
||||
|
||||
var url;
|
||||
var getData = function(name, _default) {
|
||||
url = document.body.getAttribute('data-' + name);
|
||||
if (url === null) {
|
||||
url = _default;
|
||||
}
|
||||
if (url === undefined) {
|
||||
throw Error('You need to set `data-' + name + '`');
|
||||
}
|
||||
return url;
|
||||
};
|
||||
|
||||
var title = require('./title');
|
||||
var redirect = require('./redirect');
|
||||
var localstorage = require('./localstorage');
|
||||
var hawk = require('./hawk');
|
||||
|
||||
var release_version = getData('release-version', process.env.RELEASE_VERSION)
|
||||
var release_channel = getData('release-channel', process.env.RELEASE_CHANNEL);
|
||||
|
||||
var AUTH_KEY = 'auth'; // do not change this key
|
||||
|
||||
var init = function() {
|
||||
// Start the ELM application
|
||||
var app = require('./Main.elm').Main.fullscreen({
|
||||
auth0: localstorage.load_item(AUTH_KEY),
|
||||
treestatusUrl: getData('treestatus-api-url', process.env.TREESTATUS_API_URL),
|
||||
docsUrl: getData('docs-url', process.env.DOCS_URL),
|
||||
version: release_version,
|
||||
});
|
||||
|
||||
// Setup ports
|
||||
localstorage.init(app, AUTH_KEY);
|
||||
hawk(app);
|
||||
redirect(app);
|
||||
title(app);
|
||||
};
|
||||
|
||||
// Setup logging
|
||||
var Raven = require('raven-js');
|
||||
var sentry_dsn = document.body.getAttribute('data-sentry-dsn');
|
||||
if (sentry_dsn != null) {
|
||||
Raven
|
||||
.config(
|
||||
sentry_dsn,
|
||||
{
|
||||
debug: true,
|
||||
release: release_version,
|
||||
environment: release_channel,
|
||||
tags: {
|
||||
server_name: 'mozilla/release-services',
|
||||
site: 'releng-frontend'
|
||||
}
|
||||
})
|
||||
.install()
|
||||
.context(init);
|
||||
} else {
|
||||
init();
|
||||
}
|
|
@ -1 +0,0 @@
|
|||
../../../lib/frontend_common/localstorage.js
|
|
@ -1 +0,0 @@
|
|||
../../../lib/frontend_common/redirect.js
|
|
@ -1 +0,0 @@
|
|||
../../../../lib/frontend_common/scss/fira
|
|
@ -1 +0,0 @@
|
|||
../../../../lib/frontend_common/scss/font-awesome/
|
|
@ -1 +0,0 @@
|
|||
../../../../lib/frontend_common/scss/fonts.scss
|
|
@ -1,430 +0,0 @@
|
|||
|
||||
// Mozilla colors
|
||||
// https://github.com/mozilla/mozmaker/blob/master/src/scss/custom/_colors.scss
|
||||
$charcoal: #4d4f53;
|
||||
$white: #fff;
|
||||
$black: #000;
|
||||
$dino-red: #d24735;
|
||||
$firefox-orange: #f26c23;
|
||||
$market-orange: #f89c24;
|
||||
$flame-yellow: #ffcd02;
|
||||
$gecko-green: #7dc14c;
|
||||
$summit-teal: #3bba99;
|
||||
$mobile-blue: #16afe5;
|
||||
$developer-blue: #4383bf;
|
||||
$nightly-blue: #5a6ba4;
|
||||
$aurora-purple: #ab5da4;
|
||||
$bikeshed-magenta: #e14164;
|
||||
$border-color: #adadad;
|
||||
|
||||
// Mozilla fonts
|
||||
$font-family-sans-serif: 'Fira Sans', 'Helvetica Neue', Helvetica, Arial, sans-serif;
|
||||
$font-family-serif: 'Fira Sans', Georgia, 'Times New Roman', Times, serif;
|
||||
$font-family-monospace: 'Fira Sans', Menlo, Monaco, Consolas, 'Courier New', monospace;
|
||||
$font-family-base: 'Fira Sans', $font-family-sans-serif;
|
||||
$font-size-root: 16px;
|
||||
$headings-font-weight: 300;
|
||||
|
||||
// Fonts
|
||||
@import url(./font-awesome/font-awesome.css);
|
||||
@import url(./fira/fira.css);
|
||||
|
||||
// Bootstrap
|
||||
@import "~bootstrap/scss/bootstrap";
|
||||
|
||||
|
||||
|
||||
|
||||
// navigation bar
|
||||
#navbar {
|
||||
background-color: $charcoal;
|
||||
font-size: 14px;
|
||||
padding: 1rem;
|
||||
|
||||
.navbar-toggler {
|
||||
color: $white;
|
||||
font-size: 14px;
|
||||
border: 0;
|
||||
padding: 0;
|
||||
float: right;
|
||||
text-align: right;
|
||||
}
|
||||
.navbar-toggler-icon {
|
||||
color: $white;
|
||||
}
|
||||
#navbarNavDropdown {
|
||||
justify-content: flex-end;
|
||||
}
|
||||
.nav-item a.nav-link {
|
||||
color: darken($white, 20%);
|
||||
padding: 0;
|
||||
}
|
||||
.nav-item.active a.nav-link {
|
||||
color: $white !important;
|
||||
}
|
||||
.navbar-brand {
|
||||
color: $white;
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
letter-spacing: 1px;
|
||||
padding: 0 1em 0 0;
|
||||
text-transform: uppercase;
|
||||
float: left;
|
||||
}
|
||||
.navbar-nav {
|
||||
flex-direction: row-reverse;
|
||||
width: 100%;
|
||||
}
|
||||
.dropdown-menu-right {
|
||||
right: 0.1em;
|
||||
}
|
||||
.dropdown-menu {
|
||||
border-radius: 0;
|
||||
top: 120%;
|
||||
}
|
||||
}
|
||||
|
||||
#banner-empty {
|
||||
background-color: $developer-blue;
|
||||
height: 10px;
|
||||
}
|
||||
#banner {
|
||||
background-color: $developer-blue;
|
||||
min-height: 200px;
|
||||
color: $white;
|
||||
|
||||
.container {
|
||||
font-size: 34px;
|
||||
padding: 73px 0;
|
||||
margin: 0 auto;
|
||||
text-align: center;
|
||||
line-height: 54px;
|
||||
}
|
||||
}
|
||||
|
||||
#content {
|
||||
.container {
|
||||
margin: 1.25em auto 0 auto;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
footer {
|
||||
color: $charcoal;
|
||||
font-size: 12px;
|
||||
line-height: 1.6em;
|
||||
margin: 2em 0;
|
||||
text-align: center;
|
||||
ul li {
|
||||
display: inline-block;
|
||||
margin: 0 1em;
|
||||
}
|
||||
ul {
|
||||
list-style: none;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
div a {
|
||||
color: $charcoal;
|
||||
border-bottom: 1px dotted $charcoal;
|
||||
&:hover {
|
||||
text-decoration: none;
|
||||
border-bottom: 1px dotted $charcoal;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.btn-dropdown {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
|
||||
.btn:nth-child(1) {
|
||||
width: 100%;
|
||||
text-align: left;
|
||||
border-top-right-radius: 0;
|
||||
border-bottom-right-radius: 0;
|
||||
}
|
||||
&.open .btn:nth-child(1) {
|
||||
border-bottom-left-radius: 0;
|
||||
}
|
||||
&.open .btn, &.open .btn:hover, .btn:hover {
|
||||
background-color: $white;
|
||||
border-color: $border-color;
|
||||
}
|
||||
.btn:nth-child(1)::after {
|
||||
display: none;
|
||||
}
|
||||
.btn:nth-child(2) {
|
||||
order: 1;
|
||||
}
|
||||
&.open .btn:nth-child(2) {
|
||||
border-bottom-right-radius: 0;
|
||||
}
|
||||
.dropdown-menu {
|
||||
margin-top: 0;
|
||||
width: 100%;
|
||||
border-color: $border-color;
|
||||
border-top: 0;
|
||||
border-top-left-radius: 0;
|
||||
border-top-right-radius: 0;
|
||||
& > a {
|
||||
padding-left: 16px;
|
||||
padding-right: 16px;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
.clobberer-submit-description {
|
||||
font-size: 0.8em;
|
||||
margin: 1em 0;
|
||||
ul {
|
||||
margin: 0.5em 0 1rem 0;
|
||||
border: 1px solid $border-color;
|
||||
li {
|
||||
margin: 0.5em 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#page-home .card {
|
||||
margin-bottom: 1.25em;
|
||||
|
||||
&:hover {
|
||||
text-decoration: none;
|
||||
cursor: pointer;
|
||||
}
|
||||
.card-title {
|
||||
color: #333;
|
||||
font-weight: bold;
|
||||
}
|
||||
.card-text {
|
||||
color: #333;
|
||||
}
|
||||
}
|
||||
|
||||
#treestatus table tr:hover {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
|
||||
.timeline {
|
||||
position: relative;
|
||||
padding: 2em 0;
|
||||
margin-top: 2em;
|
||||
margin-bottom: 2em;
|
||||
margin-left: 1em;
|
||||
|
||||
&::before {
|
||||
/* this is the vertical line */
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 9px;
|
||||
height: 100%;
|
||||
width: 2px;
|
||||
background: $border-color;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
.timeline-item {
|
||||
position: relative;
|
||||
margin: 2em 0;
|
||||
|
||||
&::first-child {
|
||||
margin-top: 0;
|
||||
}
|
||||
&::last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
&::before {
|
||||
content: "";
|
||||
display: table;
|
||||
clear: both;
|
||||
}
|
||||
}
|
||||
|
||||
.timeline-badge {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
border-radius: 50%;
|
||||
box-shadow: 0 0 0 4px white;
|
||||
}
|
||||
|
||||
.timeline-panel {
|
||||
position: relative;
|
||||
margin-left: 2em;
|
||||
.badge { margin-right: 0.5em; }
|
||||
}
|
||||
|
||||
.timeline-time {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.timeline-more {
|
||||
margin-bottom: -1em;
|
||||
}
|
||||
|
||||
|
||||
.float-xs-right {
|
||||
float: right;
|
||||
}
|
||||
|
||||
|
||||
.tooltip2 {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.tooltip2:hover:after{
|
||||
background: rgba(0,0,0,.8);
|
||||
border-radius: 4px;
|
||||
bottom: -2.5em;
|
||||
color: #fff;
|
||||
content: attr(title);
|
||||
right: 0;
|
||||
padding: 0.5em;
|
||||
position: absolute;
|
||||
z-index: 98;
|
||||
}
|
||||
|
||||
|
||||
#page-treestatus .radio-inline {
|
||||
margin-right: 1em;
|
||||
}
|
||||
|
||||
#page-treestatus .radio-inline > input {
|
||||
margin-right: 0.5em;
|
||||
}
|
||||
|
||||
#page-treestatus h2 {
|
||||
margin-top: 1em;
|
||||
}
|
||||
|
||||
#page-treestatus h4 {
|
||||
margin-top: 2em;
|
||||
}
|
||||
|
||||
#treestatus-trees {
|
||||
clear: both;
|
||||
}
|
||||
|
||||
#treestatus-trees .list-group-item {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
#treestatus-trees .list-group-item-action:hover {
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
#treestatus-trees .list-group-item h5 {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
#treestatus-trees .list-group-item-with-checkbox {
|
||||
padding-left: 3em;
|
||||
}
|
||||
|
||||
#treestatus-trees .custom-checkbox {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
left: 1em;
|
||||
height: 1em;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
#treestatus-trees .custom-checkbox input {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
height: 1.6em;
|
||||
width: 1.6em;
|
||||
margin-left: -0.3em;
|
||||
}
|
||||
|
||||
#treestatus-trees-buttons {
|
||||
float: right;
|
||||
margin-top: 0.3em;
|
||||
margin-left: 1em;
|
||||
margin-bottom: 0.5em;
|
||||
}
|
||||
|
||||
#treestatus-recentchanges {
|
||||
margin-bottom: 2em;
|
||||
}
|
||||
|
||||
#treestatus-recentchanges .btn-group {
|
||||
margin-left: 0.5em;
|
||||
margin-bottom: 0.5em;
|
||||
}
|
||||
|
||||
#treestatus-form {
|
||||
padding: 2em 1em 1em 1em;
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 0.25rem;
|
||||
clear: both;
|
||||
}
|
||||
|
||||
#treestatus-form button {
|
||||
float: right;
|
||||
}
|
||||
|
||||
#treestatus-form label {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
#treestatus-form label.custom-checkbox {
|
||||
font-weight: normal;
|
||||
margin: 0 1em 0 0;
|
||||
}
|
||||
|
||||
#treestatus-form small p {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
|
||||
|
||||
#treestatus-tree-details {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
#treestatus-tree-details span:first-child {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
#treestatus-tree-details p {
|
||||
margin-top: 1em;
|
||||
}
|
||||
|
||||
|
||||
@include media-breakpoint-down(xs) {
|
||||
.container {
|
||||
margin: 0em;
|
||||
}
|
||||
#navbar .navbar-brand {
|
||||
float: none;
|
||||
margin: 0 0 1em 0;
|
||||
padding: 0;
|
||||
width: 100%;
|
||||
text-align: center;
|
||||
};
|
||||
#navbar .dropdown-menu {
|
||||
background: transparent;
|
||||
border: 0;
|
||||
}
|
||||
#navbar .dropdown-menu a {
|
||||
color: #ccc;
|
||||
text-align: center;
|
||||
}
|
||||
#navbar .dropdown-menu a:hover {
|
||||
color: #292b2c;
|
||||
}
|
||||
#navbar .nav-item {
|
||||
width: 100%;
|
||||
text-align: center;
|
||||
}
|
||||
}
|
|
@ -1,137 +0,0 @@
|
|||
.alertify,
|
||||
.alertify-show,
|
||||
.alertify-log {
|
||||
-webkit-transition: all 500ms cubic-bezier(0.175, 0.885, 0.320, 1.275);
|
||||
-moz-transition: all 500ms cubic-bezier(0.175, 0.885, 0.320, 1.275);
|
||||
-ms-transition: all 500ms cubic-bezier(0.175, 0.885, 0.320, 1.275);
|
||||
-o-transition: all 500ms cubic-bezier(0.175, 0.885, 0.320, 1.275);
|
||||
transition: all 500ms cubic-bezier(0.175, 0.885, 0.320, 1.275); /* easeOutBack */
|
||||
}
|
||||
.alertify-hide {
|
||||
-webkit-transition: all 250ms cubic-bezier(0.600, -0.280, 0.735, 0.045);
|
||||
-moz-transition: all 250ms cubic-bezier(0.600, -0.280, 0.735, 0.045);
|
||||
-ms-transition: all 250ms cubic-bezier(0.600, -0.280, 0.735, 0.045);
|
||||
-o-transition: all 250ms cubic-bezier(0.600, -0.280, 0.735, 0.045);
|
||||
transition: all 250ms cubic-bezier(0.600, -0.280, 0.735, 0.045); /* easeInBack */
|
||||
}
|
||||
.alertify-log-hide {
|
||||
-webkit-transition: all 500ms cubic-bezier(0.600, -0.280, 0.735, 0.045);
|
||||
-moz-transition: all 500ms cubic-bezier(0.600, -0.280, 0.735, 0.045);
|
||||
-ms-transition: all 500ms cubic-bezier(0.600, -0.280, 0.735, 0.045);
|
||||
-o-transition: all 500ms cubic-bezier(0.600, -0.280, 0.735, 0.045);
|
||||
transition: all 500ms cubic-bezier(0.600, -0.280, 0.735, 0.045); /* easeInBack */
|
||||
}
|
||||
.alertify-cover {
|
||||
position: fixed; z-index: 99999;
|
||||
top: 0; right: 0; bottom: 0; left: 0;
|
||||
background-color:white;
|
||||
filter:alpha(opacity=0);
|
||||
opacity:0;
|
||||
}
|
||||
.alertify-cover-hidden {
|
||||
display: none;
|
||||
}
|
||||
.alertify {
|
||||
position: fixed; z-index: 99999;
|
||||
top: 50px; left: 50%;
|
||||
width: 550px;
|
||||
margin-left: -275px;
|
||||
opacity: 1;
|
||||
}
|
||||
.alertify-hidden {
|
||||
-webkit-transform: translate(0,-150px);
|
||||
-moz-transform: translate(0,-150px);
|
||||
-ms-transform: translate(0,-150px);
|
||||
-o-transform: translate(0,-150px);
|
||||
transform: translate(0,-150px);
|
||||
opacity: 0;
|
||||
display: none;
|
||||
}
|
||||
/* overwrite display: none; for everything except IE6-8 */
|
||||
:root *> .alertify-hidden {
|
||||
display: block;
|
||||
visibility: hidden;
|
||||
}
|
||||
.alertify-logs {
|
||||
position: fixed;
|
||||
z-index: 5000;
|
||||
bottom: 10px;
|
||||
right: 10px;
|
||||
width: 300px;
|
||||
}
|
||||
.alertify-logs-hidden {
|
||||
display: none;
|
||||
}
|
||||
.alertify-log {
|
||||
display: block;
|
||||
margin-top: 10px;
|
||||
position: relative;
|
||||
right: -300px;
|
||||
opacity: 0;
|
||||
}
|
||||
.alertify-log-show {
|
||||
right: 0;
|
||||
opacity: 1;
|
||||
}
|
||||
.alertify-log-hide {
|
||||
-webkit-transform: translate(300px, 0);
|
||||
-moz-transform: translate(300px, 0);
|
||||
-ms-transform: translate(300px, 0);
|
||||
-o-transform: translate(300px, 0);
|
||||
transform: translate(300px, 0);
|
||||
opacity: 0;
|
||||
}
|
||||
.alertify-dialog {
|
||||
padding: 25px;
|
||||
}
|
||||
.alertify-resetFocus {
|
||||
border: 0;
|
||||
clip: rect(0 0 0 0);
|
||||
height: 1px;
|
||||
margin: -1px;
|
||||
overflow: hidden;
|
||||
padding: 0;
|
||||
position: absolute;
|
||||
width: 1px;
|
||||
}
|
||||
.alertify-inner {
|
||||
text-align: center;
|
||||
}
|
||||
.alertify-text {
|
||||
margin-bottom: 15px;
|
||||
width: 100%;
|
||||
-webkit-box-sizing: border-box;
|
||||
-moz-box-sizing: border-box;
|
||||
box-sizing: border-box;
|
||||
font-size: 100%;
|
||||
}
|
||||
.alertify-buttons {
|
||||
}
|
||||
.alertify-button,
|
||||
.alertify-button:hover,
|
||||
.alertify-button:active,
|
||||
.alertify-button:visited {
|
||||
background: none;
|
||||
text-decoration: none;
|
||||
border: none;
|
||||
/* line-height and font-size for input button */
|
||||
line-height: 1.5;
|
||||
font-size: 100%;
|
||||
display: inline-block;
|
||||
cursor: pointer;
|
||||
margin-left: 5px;
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 680px) {
|
||||
.alertify,
|
||||
.alertify-logs {
|
||||
width: 90%;
|
||||
-webkit-box-sizing: border-box;
|
||||
-moz-box-sizing: border-box;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
.alertify {
|
||||
left: 5%;
|
||||
margin: 0;
|
||||
}
|
||||
}
|
|
@ -1,81 +0,0 @@
|
|||
/**
|
||||
* Default Look and Feel
|
||||
*/
|
||||
.alertify,
|
||||
.alertify-log {
|
||||
font-family: sans-serif;
|
||||
}
|
||||
.alertify {
|
||||
background: #FFF;
|
||||
border: 10px solid #333; /* browsers that don't support rgba */
|
||||
border: 10px solid rgba(0,0,0,.7);
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 3px 3px rgba(0,0,0,.3);
|
||||
-webkit-background-clip: padding; /* Safari 4? Chrome 6? */
|
||||
-moz-background-clip: padding; /* Firefox 3.6 */
|
||||
background-clip: padding-box; /* Firefox 4, Safari 5, Opera 10, IE 9 */
|
||||
}
|
||||
.alertify-text {
|
||||
border: 1px solid #CCC;
|
||||
padding: 10px;
|
||||
border-radius: 4px;
|
||||
}
|
||||
.alertify-button {
|
||||
border-radius: 4px;
|
||||
color: #FFF;
|
||||
font-weight: bold;
|
||||
padding: 6px 15px;
|
||||
text-decoration: none;
|
||||
text-shadow: 1px 1px 0 rgba(0,0,0,.5);
|
||||
box-shadow: inset 0 1px 0 0 rgba(255,255,255,.5);
|
||||
background-image: -webkit-linear-gradient(top, rgba(255,255,255,.3), rgba(255,255,255,0));
|
||||
background-image: -moz-linear-gradient(top, rgba(255,255,255,.3), rgba(255,255,255,0));
|
||||
background-image: -ms-linear-gradient(top, rgba(255,255,255,.3), rgba(255,255,255,0));
|
||||
background-image: -o-linear-gradient(top, rgba(255,255,255,.3), rgba(255,255,255,0));
|
||||
background-image: linear-gradient(top, rgba(255,255,255,.3), rgba(255,255,255,0));
|
||||
}
|
||||
.alertify-button:hover,
|
||||
.alertify-button:focus {
|
||||
outline: none;
|
||||
background-image: -webkit-linear-gradient(top, rgba(0,0,0,.1), rgba(0,0,0,0));
|
||||
background-image: -moz-linear-gradient(top, rgba(0,0,0,.1), rgba(0,0,0,0));
|
||||
background-image: -ms-linear-gradient(top, rgba(0,0,0,.1), rgba(0,0,0,0));
|
||||
background-image: -o-linear-gradient(top, rgba(0,0,0,.1), rgba(0,0,0,0));
|
||||
background-image: linear-gradient(top, rgba(0,0,0,.1), rgba(0,0,0,0));
|
||||
}
|
||||
.alertify-button:focus {
|
||||
box-shadow: 0 0 15px #2B72D5;
|
||||
}
|
||||
.alertify-button:active {
|
||||
position: relative;
|
||||
box-shadow: inset 0 2px 4px rgba(0,0,0,.15), 0 1px 2px rgba(0,0,0,.05);
|
||||
}
|
||||
.alertify-button-cancel,
|
||||
.alertify-button-cancel:hover,
|
||||
.alertify-button-cancel:focus {
|
||||
background-color: #FE1A00;
|
||||
border: 1px solid #D83526;
|
||||
}
|
||||
.alertify-button-ok,
|
||||
.alertify-button-ok:hover,
|
||||
.alertify-button-ok:focus {
|
||||
background-color: #5CB811;
|
||||
border: 1px solid #3B7808;
|
||||
}
|
||||
|
||||
.alertify-log {
|
||||
background: #1F1F1F;
|
||||
background: rgba(0,0,0,.9);
|
||||
padding: 15px;
|
||||
border-radius: 4px;
|
||||
color: #FFF;
|
||||
text-shadow: -1px -1px 0 rgba(0,0,0,.5);
|
||||
}
|
||||
.alertify-log-error {
|
||||
background: #FE1A00;
|
||||
background: rgba(254,26,0,.9);
|
||||
}
|
||||
.alertify-log-success {
|
||||
background: #5CB811;
|
||||
background: rgba(92,184,17,.9);
|
||||
}
|
Различия файлов скрыты, потому что одна или несколько строк слишком длинны
|
@ -1 +0,0 @@
|
|||
"format global";"deps angular";"deps moment";!function(){"use strict";function a(a,b){return a.module("angularMoment",[]).constant("angularMomentConfig",{preprocess:null,timezone:"",format:null}).constant("moment",b).constant("amTimeAgoConfig",{withoutSuffix:!1,serverTime:null,titleFormat:null}).directive("amTimeAgo",["$window","moment","amMoment","amTimeAgoConfig","angularMomentConfig",function(b,c,d,e,f){return function(g,h,i){function j(){var a;if(e.serverTime){var b=(new Date).getTime(),d=b-u+e.serverTime;a=c(d)}else a=c();return a}function k(){q&&(b.clearTimeout(q),q=null)}function l(a){if(h.text(a.from(j(),s)),t&&!h.attr("title")&&h.attr("title",a.local().format(t)),!x){var c=Math.abs(j().diff(a,"minute")),d=3600;1>c?d=1:60>c?d=30:180>c&&(d=300),q=b.setTimeout(function(){l(a)},1e3*d)}}function m(a){y&&h.attr("datetime",a)}function n(){if(k(),o){var a=d.preprocessDate(o,v,r);l(a),m(a.toISOString())}}var o,p,q=null,r=f.format,s=e.withoutSuffix,t=e.titleFormat,u=(new Date).getTime(),v=f.preprocess,w=i.amTimeAgo.replace(/^::/,""),x=0===i.amTimeAgo.indexOf("::"),y="TIME"===h[0].nodeName.toUpperCase();p=g.$watch(w,function(a){return"undefined"==typeof a||null===a||""===a?(k(),void(o&&(h.text(""),m(""),o=null))):(o=a,n(),void(void 0!==a&&x&&p()))}),a.isDefined(i.amWithoutSuffix)&&g.$watch(i.amWithoutSuffix,function(a){"boolean"==typeof a?(s=a,n()):s=e.withoutSuffix}),i.$observe("amFormat",function(a){"undefined"!=typeof a&&(r=a,n())}),i.$observe("amPreprocess",function(a){v=a,n()}),g.$on("$destroy",function(){k()}),g.$on("amMoment:localeChanged",function(){n()})}}]).service("amMoment",["moment","$rootScope","$log","angularMomentConfig",function(b,c,d,e){var f=this;this.preprocessors={utc:b.utc,unix:b.unix},this.changeLocale=function(d){var e=(b.locale||b.lang)(d);return a.isDefined(d)&&(c.$broadcast("amMoment:localeChanged"),c.$broadcast("amMoment:languageChange")),e},this.changeLanguage=function(a){return d.warn("angular-moment: Usage of amMoment.changeLanguage() is deprecated. Please use changeLocale()"),f.changeLocale(a)},this.preprocessDate=function(c,f,g){return a.isUndefined(f)&&(f=e.preprocess),this.preprocessors[f]?this.preprocessors[f](c,g):(f&&d.warn("angular-moment: Ignoring unsupported value for preprocess: "+f),!isNaN(parseFloat(c))&&isFinite(c)?b(parseInt(c,10)):b(c,g))},this.applyTimezone=function(a){var b=e.timezone;return a&&b&&(a.tz?a=a.tz(b):d.warn("angular-moment: timezone specified but moment.tz() is undefined. Did you forget to include moment-timezone.js?")),a}}]).filter("amCalendar",["moment","amMoment",function(a,b){return function(c,d){if("undefined"==typeof c||null===c)return"";c=b.preprocessDate(c,d);var e=a(c);return e.isValid()?b.applyTimezone(e).calendar():""}}]).filter("amDateFormat",["moment","amMoment",function(a,b){return function(c,d,e){if("undefined"==typeof c||null===c)return"";c=b.preprocessDate(c,e);var f=a(c);return f.isValid()?b.applyTimezone(f).format(d):""}}]).filter("amDurationFormat",["moment",function(a){return function(b,c,d){return"undefined"==typeof b||null===b?"":a.duration(b,c).humanize(d)}}]).filter("amTimeAgo",["moment","amMoment",function(a,b){return function(c,d,e){if("undefined"==typeof c||null===c)return"";c=b.preprocessDate(c,d);var f=a(c);return f.isValid()?b.applyTimezone(f).fromNow(e):""}}])}"function"==typeof define&&define.amd?define("angular-moment",["angular","moment"],a):"undefined"!=typeof module&&module&&module.exports?a(angular,require("moment")):a(angular,window.moment)}();
|
|
@ -1,292 +0,0 @@
|
|||
/*
|
||||
AngularJS v1.4.5
|
||||
(c) 2010-2015 Google, Inc. http://angularjs.org
|
||||
License: MIT
|
||||
*/
|
||||
(function(N,W,u){'use strict';function G(b){return function(){var a=arguments[0],c;c="["+(b?b+":":"")+a+"] http://errors.angularjs.org/1.4.5/"+(b?b+"/":"")+a;for(a=1;a<arguments.length;a++){c=c+(1==a?"?":"&")+"p"+(a-1)+"=";var d=encodeURIComponent,e;e=arguments[a];e="function"==typeof e?e.toString().replace(/ \{[\s\S]*$/,""):"undefined"==typeof e?"undefined":"string"!=typeof e?JSON.stringify(e):e;c+=d(e)}return Error(c)}}function Da(b){if(null==b||Ya(b))return!1;var a="length"in Object(b)&&b.length;
|
||||
return b.nodeType===pa&&a?!0:H(b)||K(b)||0===a||"number"===typeof a&&0<a&&a-1 in b}function n(b,a,c){var d,e;if(b)if(B(b))for(d in b)"prototype"==d||"length"==d||"name"==d||b.hasOwnProperty&&!b.hasOwnProperty(d)||a.call(c,b[d],d,b);else if(K(b)||Da(b)){var f="object"!==typeof b;d=0;for(e=b.length;d<e;d++)(f||d in b)&&a.call(c,b[d],d,b)}else if(b.forEach&&b.forEach!==n)b.forEach(a,c,b);else if(lc(b))for(d in b)a.call(c,b[d],d,b);else if("function"===typeof b.hasOwnProperty)for(d in b)b.hasOwnProperty(d)&&
|
||||
a.call(c,b[d],d,b);else for(d in b)Na.call(b,d)&&a.call(c,b[d],d,b);return b}function mc(b,a,c){for(var d=Object.keys(b).sort(),e=0;e<d.length;e++)a.call(c,b[d[e]],d[e]);return d}function nc(b){return function(a,c){b(c,a)}}function Ud(){return++mb}function oc(b,a){a?b.$$hashKey=a:delete b.$$hashKey}function Mb(b,a,c){for(var d=b.$$hashKey,e=0,f=a.length;e<f;++e){var g=a[e];if(D(g)||B(g))for(var h=Object.keys(g),l=0,k=h.length;l<k;l++){var m=h[l],q=g[m];c&&D(q)?ca(q)?b[m]=new Date(q.valueOf()):Oa(q)?
|
||||
b[m]=new RegExp(q):(D(b[m])||(b[m]=K(q)?[]:{}),Mb(b[m],[q],!0)):b[m]=q}}oc(b,d);return b}function Q(b){return Mb(b,xa.call(arguments,1),!1)}function Vd(b){return Mb(b,xa.call(arguments,1),!0)}function Y(b){return parseInt(b,10)}function Nb(b,a){return Q(Object.create(b),a)}function v(){}function Za(b){return b}function qa(b){return function(){return b}}function pc(b){return B(b.toString)&&b.toString!==Object.prototype.toString}function y(b){return"undefined"===typeof b}function x(b){return"undefined"!==
|
||||
typeof b}function D(b){return null!==b&&"object"===typeof b}function lc(b){return null!==b&&"object"===typeof b&&!qc(b)}function H(b){return"string"===typeof b}function X(b){return"number"===typeof b}function ca(b){return"[object Date]"===sa.call(b)}function B(b){return"function"===typeof b}function Oa(b){return"[object RegExp]"===sa.call(b)}function Ya(b){return b&&b.window===b}function $a(b){return b&&b.$evalAsync&&b.$watch}function ab(b){return"boolean"===typeof b}function rc(b){return!(!b||!(b.nodeName||
|
||||
b.prop&&b.attr&&b.find))}function Wd(b){var a={};b=b.split(",");var c;for(c=0;c<b.length;c++)a[b[c]]=!0;return a}function ta(b){return I(b.nodeName||b[0]&&b[0].nodeName)}function bb(b,a){var c=b.indexOf(a);0<=c&&b.splice(c,1);return c}function fa(b,a,c,d){if(Ya(b)||$a(b))throw Ea("cpws");if(sc.test(sa.call(a)))throw Ea("cpta");if(a){if(b===a)throw Ea("cpi");c=c||[];d=d||[];D(b)&&(c.push(b),d.push(a));var e;if(K(b))for(e=a.length=0;e<b.length;e++)a.push(fa(b[e],null,c,d));else{var f=a.$$hashKey;K(a)?
|
||||
a.length=0:n(a,function(b,c){delete a[c]});if(lc(b))for(e in b)a[e]=fa(b[e],null,c,d);else if(b&&"function"===typeof b.hasOwnProperty)for(e in b)b.hasOwnProperty(e)&&(a[e]=fa(b[e],null,c,d));else for(e in b)Na.call(b,e)&&(a[e]=fa(b[e],null,c,d));oc(a,f)}}else if(a=b,D(b)){if(c&&-1!==(f=c.indexOf(b)))return d[f];if(K(b))return fa(b,[],c,d);if(sc.test(sa.call(b)))a=new b.constructor(b);else if(ca(b))a=new Date(b.getTime());else if(Oa(b))a=new RegExp(b.source,b.toString().match(/[^\/]*$/)[0]),a.lastIndex=
|
||||
b.lastIndex;else return e=Object.create(qc(b)),fa(b,e,c,d);d&&(c.push(b),d.push(a))}return a}function ia(b,a){if(K(b)){a=a||[];for(var c=0,d=b.length;c<d;c++)a[c]=b[c]}else if(D(b))for(c in a=a||{},b)if("$"!==c.charAt(0)||"$"!==c.charAt(1))a[c]=b[c];return a||b}function ka(b,a){if(b===a)return!0;if(null===b||null===a)return!1;if(b!==b&&a!==a)return!0;var c=typeof b,d;if(c==typeof a&&"object"==c)if(K(b)){if(!K(a))return!1;if((c=b.length)==a.length){for(d=0;d<c;d++)if(!ka(b[d],a[d]))return!1;return!0}}else{if(ca(b))return ca(a)?
|
||||
ka(b.getTime(),a.getTime()):!1;if(Oa(b))return Oa(a)?b.toString()==a.toString():!1;if($a(b)||$a(a)||Ya(b)||Ya(a)||K(a)||ca(a)||Oa(a))return!1;c=ga();for(d in b)if("$"!==d.charAt(0)&&!B(b[d])){if(!ka(b[d],a[d]))return!1;c[d]=!0}for(d in a)if(!(d in c||"$"===d.charAt(0)||a[d]===u||B(a[d])))return!1;return!0}return!1}function cb(b,a,c){return b.concat(xa.call(a,c))}function tc(b,a){var c=2<arguments.length?xa.call(arguments,2):[];return!B(a)||a instanceof RegExp?a:c.length?function(){return arguments.length?
|
||||
a.apply(b,cb(c,arguments,0)):a.apply(b,c)}:function(){return arguments.length?a.apply(b,arguments):a.call(b)}}function Xd(b,a){var c=a;"string"===typeof b&&"$"===b.charAt(0)&&"$"===b.charAt(1)?c=u:Ya(a)?c="$WINDOW":a&&W===a?c="$DOCUMENT":$a(a)&&(c="$SCOPE");return c}function db(b,a){if("undefined"===typeof b)return u;X(a)||(a=a?2:null);return JSON.stringify(b,Xd,a)}function uc(b){return H(b)?JSON.parse(b):b}function vc(b,a){var c=Date.parse("Jan 01, 1970 00:00:00 "+b)/6E4;return isNaN(c)?a:c}function Ob(b,
|
||||
a,c){c=c?-1:1;var d=vc(a,b.getTimezoneOffset());a=b;b=c*(d-b.getTimezoneOffset());a=new Date(a.getTime());a.setMinutes(a.getMinutes()+b);return a}function ua(b){b=z(b).clone();try{b.empty()}catch(a){}var c=z("<div>").append(b).html();try{return b[0].nodeType===Pa?I(c):c.match(/^(<[^>]+>)/)[1].replace(/^<([\w\-]+)/,function(a,b){return"<"+I(b)})}catch(d){return I(c)}}function wc(b){try{return decodeURIComponent(b)}catch(a){}}function xc(b){var a={};n((b||"").split("&"),function(b){var d,e,f;b&&(e=
|
||||
b=b.replace(/\+/g,"%20"),d=b.indexOf("="),-1!==d&&(e=b.substring(0,d),f=b.substring(d+1)),e=wc(e),x(e)&&(f=x(f)?wc(f):!0,Na.call(a,e)?K(a[e])?a[e].push(f):a[e]=[a[e],f]:a[e]=f))});return a}function Pb(b){var a=[];n(b,function(b,d){K(b)?n(b,function(b){a.push(ma(d,!0)+(!0===b?"":"="+ma(b,!0)))}):a.push(ma(d,!0)+(!0===b?"":"="+ma(b,!0)))});return a.length?a.join("&"):""}function nb(b){return ma(b,!0).replace(/%26/gi,"&").replace(/%3D/gi,"=").replace(/%2B/gi,"+")}function ma(b,a){return encodeURIComponent(b).replace(/%40/gi,
|
||||
"@").replace(/%3A/gi,":").replace(/%24/g,"$").replace(/%2C/gi,",").replace(/%3B/gi,";").replace(/%20/g,a?"%20":"+")}function Yd(b,a){var c,d,e=Qa.length;for(d=0;d<e;++d)if(c=Qa[d]+a,H(c=b.getAttribute(c)))return c;return null}function Zd(b,a){var c,d,e={};n(Qa,function(a){a+="app";!c&&b.hasAttribute&&b.hasAttribute(a)&&(c=b,d=b.getAttribute(a))});n(Qa,function(a){a+="app";var e;!c&&(e=b.querySelector("["+a.replace(":","\\:")+"]"))&&(c=e,d=e.getAttribute(a))});c&&(e.strictDi=null!==Yd(c,"strict-di"),
|
||||
a(c,d?[d]:[],e))}function yc(b,a,c){D(c)||(c={});c=Q({strictDi:!1},c);var d=function(){b=z(b);if(b.injector()){var d=b[0]===W?"document":ua(b);throw Ea("btstrpd",d.replace(/</,"<").replace(/>/,">"));}a=a||[];a.unshift(["$provide",function(a){a.value("$rootElement",b)}]);c.debugInfoEnabled&&a.push(["$compileProvider",function(a){a.debugInfoEnabled(!0)}]);a.unshift("ng");d=eb(a,c.strictDi);d.invoke(["$rootScope","$rootElement","$compile","$injector",function(a,b,c,d){a.$apply(function(){b.data("$injector",
|
||||
d);c(b)(a)})}]);return d},e=/^NG_ENABLE_DEBUG_INFO!/,f=/^NG_DEFER_BOOTSTRAP!/;N&&e.test(N.name)&&(c.debugInfoEnabled=!0,N.name=N.name.replace(e,""));if(N&&!f.test(N.name))return d();N.name=N.name.replace(f,"");aa.resumeBootstrap=function(b){n(b,function(b){a.push(b)});return d()};B(aa.resumeDeferredBootstrap)&&aa.resumeDeferredBootstrap()}function $d(){N.name="NG_ENABLE_DEBUG_INFO!"+N.name;N.location.reload()}function ae(b){b=aa.element(b).injector();if(!b)throw Ea("test");return b.get("$$testability")}
|
||||
function zc(b,a){a=a||"_";return b.replace(be,function(b,d){return(d?a:"")+b.toLowerCase()})}function ce(){var b;if(!Ac){var a=ob();la=N.jQuery;x(a)&&(la=null===a?u:N[a]);la&&la.fn.on?(z=la,Q(la.fn,{scope:Ra.scope,isolateScope:Ra.isolateScope,controller:Ra.controller,injector:Ra.injector,inheritedData:Ra.inheritedData}),b=la.cleanData,la.cleanData=function(a){var d;if(Qb)Qb=!1;else for(var e=0,f;null!=(f=a[e]);e++)(d=la._data(f,"events"))&&d.$destroy&&la(f).triggerHandler("$destroy");b(a)}):z=R;aa.element=
|
||||
z;Ac=!0}}function pb(b,a,c){if(!b)throw Ea("areq",a||"?",c||"required");return b}function Sa(b,a,c){c&&K(b)&&(b=b[b.length-1]);pb(B(b),a,"not a function, got "+(b&&"object"===typeof b?b.constructor.name||"Object":typeof b));return b}function Ta(b,a){if("hasOwnProperty"===b)throw Ea("badname",a);}function Bc(b,a,c){if(!a)return b;a=a.split(".");for(var d,e=b,f=a.length,g=0;g<f;g++)d=a[g],b&&(b=(e=b)[d]);return!c&&B(b)?tc(e,b):b}function qb(b){var a=b[0];b=b[b.length-1];var c=[a];do{a=a.nextSibling;
|
||||
if(!a)break;c.push(a)}while(a!==b);return z(c)}function ga(){return Object.create(null)}function de(b){function a(a,b,c){return a[b]||(a[b]=c())}var c=G("$injector"),d=G("ng");b=a(b,"angular",Object);b.$$minErr=b.$$minErr||G;return a(b,"module",function(){var b={};return function(f,g,h){if("hasOwnProperty"===f)throw d("badname","module");g&&b.hasOwnProperty(f)&&(b[f]=null);return a(b,f,function(){function a(b,c,e,f){f||(f=d);return function(){f[e||"push"]([b,c,arguments]);return E}}function b(a,c){return function(b,
|
||||
e){e&&B(e)&&(e.$$moduleName=f);d.push([a,c,arguments]);return E}}if(!g)throw c("nomod",f);var d=[],e=[],s=[],t=a("$injector","invoke","push",e),E={_invokeQueue:d,_configBlocks:e,_runBlocks:s,requires:g,name:f,provider:b("$provide","provider"),factory:b("$provide","factory"),service:b("$provide","service"),value:a("$provide","value"),constant:a("$provide","constant","unshift"),decorator:b("$provide","decorator"),animation:b("$animateProvider","register"),filter:b("$filterProvider","register"),controller:b("$controllerProvider",
|
||||
"register"),directive:b("$compileProvider","directive"),config:t,run:function(a){s.push(a);return this}};h&&t(h);return E})}})}function ee(b){Q(b,{bootstrap:yc,copy:fa,extend:Q,merge:Vd,equals:ka,element:z,forEach:n,injector:eb,noop:v,bind:tc,toJson:db,fromJson:uc,identity:Za,isUndefined:y,isDefined:x,isString:H,isFunction:B,isObject:D,isNumber:X,isElement:rc,isArray:K,version:fe,isDate:ca,lowercase:I,uppercase:rb,callbacks:{counter:0},getTestability:ae,$$minErr:G,$$csp:Fa,reloadWithDebugInfo:$d});
|
||||
Rb=de(N);Rb("ng",["ngLocale"],["$provide",function(a){a.provider({$$sanitizeUri:ge});a.provider("$compile",Cc).directive({a:he,input:Dc,textarea:Dc,form:ie,script:je,select:ke,style:le,option:me,ngBind:ne,ngBindHtml:oe,ngBindTemplate:pe,ngClass:qe,ngClassEven:re,ngClassOdd:se,ngCloak:te,ngController:ue,ngForm:ve,ngHide:we,ngIf:xe,ngInclude:ye,ngInit:ze,ngNonBindable:Ae,ngPluralize:Be,ngRepeat:Ce,ngShow:De,ngStyle:Ee,ngSwitch:Fe,ngSwitchWhen:Ge,ngSwitchDefault:He,ngOptions:Ie,ngTransclude:Je,ngModel:Ke,
|
||||
ngList:Le,ngChange:Me,pattern:Ec,ngPattern:Ec,required:Fc,ngRequired:Fc,minlength:Gc,ngMinlength:Gc,maxlength:Hc,ngMaxlength:Hc,ngValue:Ne,ngModelOptions:Oe}).directive({ngInclude:Pe}).directive(sb).directive(Ic);a.provider({$anchorScroll:Qe,$animate:Re,$animateCss:Se,$$animateQueue:Te,$$AnimateRunner:Ue,$browser:Ve,$cacheFactory:We,$controller:Xe,$document:Ye,$exceptionHandler:Ze,$filter:Jc,$$forceReflow:$e,$interpolate:af,$interval:bf,$http:cf,$httpParamSerializer:df,$httpParamSerializerJQLike:ef,
|
||||
$httpBackend:ff,$location:gf,$log:hf,$parse:jf,$rootScope:kf,$q:lf,$$q:mf,$sce:nf,$sceDelegate:of,$sniffer:pf,$templateCache:qf,$templateRequest:rf,$$testability:sf,$timeout:tf,$window:uf,$$rAF:vf,$$jqLite:wf,$$HashMap:xf,$$cookieReader:yf})}])}function fb(b){return b.replace(zf,function(a,b,d,e){return e?d.toUpperCase():d}).replace(Af,"Moz$1")}function Kc(b){b=b.nodeType;return b===pa||!b||9===b}function Lc(b,a){var c,d,e=a.createDocumentFragment(),f=[];if(Sb.test(b)){c=c||e.appendChild(a.createElement("div"));
|
||||
d=(Bf.exec(b)||["",""])[1].toLowerCase();d=na[d]||na._default;c.innerHTML=d[1]+b.replace(Cf,"<$1></$2>")+d[2];for(d=d[0];d--;)c=c.lastChild;f=cb(f,c.childNodes);c=e.firstChild;c.textContent=""}else f.push(a.createTextNode(b));e.textContent="";e.innerHTML="";n(f,function(a){e.appendChild(a)});return e}function R(b){if(b instanceof R)return b;var a;H(b)&&(b=T(b),a=!0);if(!(this instanceof R)){if(a&&"<"!=b.charAt(0))throw Tb("nosel");return new R(b)}if(a){a=W;var c;b=(c=Df.exec(b))?[a.createElement(c[1])]:
|
||||
(c=Lc(b,a))?c.childNodes:[]}Mc(this,b)}function Ub(b){return b.cloneNode(!0)}function tb(b,a){a||ub(b);if(b.querySelectorAll)for(var c=b.querySelectorAll("*"),d=0,e=c.length;d<e;d++)ub(c[d])}function Nc(b,a,c,d){if(x(d))throw Tb("offargs");var e=(d=vb(b))&&d.events,f=d&&d.handle;if(f)if(a)n(a.split(" "),function(a){if(x(c)){var d=e[a];bb(d||[],c);if(d&&0<d.length)return}b.removeEventListener(a,f,!1);delete e[a]});else for(a in e)"$destroy"!==a&&b.removeEventListener(a,f,!1),delete e[a]}function ub(b,
|
||||
a){var c=b.ng339,d=c&&gb[c];d&&(a?delete d.data[a]:(d.handle&&(d.events.$destroy&&d.handle({},"$destroy"),Nc(b)),delete gb[c],b.ng339=u))}function vb(b,a){var c=b.ng339,c=c&&gb[c];a&&!c&&(b.ng339=c=++Ef,c=gb[c]={events:{},data:{},handle:u});return c}function Vb(b,a,c){if(Kc(b)){var d=x(c),e=!d&&a&&!D(a),f=!a;b=(b=vb(b,!e))&&b.data;if(d)b[a]=c;else{if(f)return b;if(e)return b&&b[a];Q(b,a)}}}function wb(b,a){return b.getAttribute?-1<(" "+(b.getAttribute("class")||"")+" ").replace(/[\n\t]/g," ").indexOf(" "+
|
||||
a+" "):!1}function xb(b,a){a&&b.setAttribute&&n(a.split(" "),function(a){b.setAttribute("class",T((" "+(b.getAttribute("class")||"")+" ").replace(/[\n\t]/g," ").replace(" "+T(a)+" "," ")))})}function yb(b,a){if(a&&b.setAttribute){var c=(" "+(b.getAttribute("class")||"")+" ").replace(/[\n\t]/g," ");n(a.split(" "),function(a){a=T(a);-1===c.indexOf(" "+a+" ")&&(c+=a+" ")});b.setAttribute("class",T(c))}}function Mc(b,a){if(a)if(a.nodeType)b[b.length++]=a;else{var c=a.length;if("number"===typeof c&&a.window!==
|
||||
a){if(c)for(var d=0;d<c;d++)b[b.length++]=a[d]}else b[b.length++]=a}}function Oc(b,a){return zb(b,"$"+(a||"ngController")+"Controller")}function zb(b,a,c){9==b.nodeType&&(b=b.documentElement);for(a=K(a)?a:[a];b;){for(var d=0,e=a.length;d<e;d++)if((c=z.data(b,a[d]))!==u)return c;b=b.parentNode||11===b.nodeType&&b.host}}function Pc(b){for(tb(b,!0);b.firstChild;)b.removeChild(b.firstChild)}function Wb(b,a){a||tb(b);var c=b.parentNode;c&&c.removeChild(b)}function Ff(b,a){a=a||N;if("complete"===a.document.readyState)a.setTimeout(b);
|
||||
else z(a).on("load",b)}function Qc(b,a){var c=Ab[a.toLowerCase()];return c&&Rc[ta(b)]&&c}function Gf(b,a){var c=b.nodeName;return("INPUT"===c||"TEXTAREA"===c)&&Sc[a]}function Hf(b,a){var c=function(c,e){c.isDefaultPrevented=function(){return c.defaultPrevented};var f=a[e||c.type],g=f?f.length:0;if(g){if(y(c.immediatePropagationStopped)){var h=c.stopImmediatePropagation;c.stopImmediatePropagation=function(){c.immediatePropagationStopped=!0;c.stopPropagation&&c.stopPropagation();h&&h.call(c)}}c.isImmediatePropagationStopped=
|
||||
function(){return!0===c.immediatePropagationStopped};1<g&&(f=ia(f));for(var l=0;l<g;l++)c.isImmediatePropagationStopped()||f[l].call(b,c)}};c.elem=b;return c}function wf(){this.$get=function(){return Q(R,{hasClass:function(b,a){b.attr&&(b=b[0]);return wb(b,a)},addClass:function(b,a){b.attr&&(b=b[0]);return yb(b,a)},removeClass:function(b,a){b.attr&&(b=b[0]);return xb(b,a)}})}}function Ga(b,a){var c=b&&b.$$hashKey;if(c)return"function"===typeof c&&(c=b.$$hashKey()),c;c=typeof b;return c="function"==
|
||||
c||"object"==c&&null!==b?b.$$hashKey=c+":"+(a||Ud)():c+":"+b}function Ua(b,a){if(a){var c=0;this.nextUid=function(){return++c}}n(b,this.put,this)}function If(b){return(b=b.toString().replace(Tc,"").match(Uc))?"function("+(b[1]||"").replace(/[\s\r\n]+/," ")+")":"fn"}function eb(b,a){function c(a){return function(b,c){if(D(b))n(b,nc(a));else return a(b,c)}}function d(a,b){Ta(a,"service");if(B(b)||K(b))b=s.instantiate(b);if(!b.$get)throw Ha("pget",a);return q[a+"Provider"]=b}function e(a,b){return function(){var c=
|
||||
E.invoke(b,this);if(y(c))throw Ha("undef",a);return c}}function f(a,b,c){return d(a,{$get:!1!==c?e(a,b):b})}function g(a){pb(y(a)||K(a),"modulesToLoad","not an array");var b=[],c;n(a,function(a){function d(a){var b,c;b=0;for(c=a.length;b<c;b++){var e=a[b],f=s.get(e[0]);f[e[1]].apply(f,e[2])}}if(!m.get(a)){m.put(a,!0);try{H(a)?(c=Rb(a),b=b.concat(g(c.requires)).concat(c._runBlocks),d(c._invokeQueue),d(c._configBlocks)):B(a)?b.push(s.invoke(a)):K(a)?b.push(s.invoke(a)):Sa(a,"module")}catch(e){throw K(a)&&
|
||||
(a=a[a.length-1]),e.message&&e.stack&&-1==e.stack.indexOf(e.message)&&(e=e.message+"\n"+e.stack),Ha("modulerr",a,e.stack||e.message||e);}}});return b}function h(b,c){function d(a,e){if(b.hasOwnProperty(a)){if(b[a]===l)throw Ha("cdep",a+" <- "+k.join(" <- "));return b[a]}try{return k.unshift(a),b[a]=l,b[a]=c(a,e)}catch(f){throw b[a]===l&&delete b[a],f;}finally{k.shift()}}function e(b,c,f,g){"string"===typeof f&&(g=f,f=null);var h=[],k=eb.$$annotate(b,a,g),l,s,m;s=0;for(l=k.length;s<l;s++){m=k[s];if("string"!==
|
||||
typeof m)throw Ha("itkn",m);h.push(f&&f.hasOwnProperty(m)?f[m]:d(m,g))}K(b)&&(b=b[l]);return b.apply(c,h)}return{invoke:e,instantiate:function(a,b,c){var d=Object.create((K(a)?a[a.length-1]:a).prototype||null);a=e(a,d,b,c);return D(a)||B(a)?a:d},get:d,annotate:eb.$$annotate,has:function(a){return q.hasOwnProperty(a+"Provider")||b.hasOwnProperty(a)}}}a=!0===a;var l={},k=[],m=new Ua([],!0),q={$provide:{provider:c(d),factory:c(f),service:c(function(a,b){return f(a,["$injector",function(a){return a.instantiate(b)}])}),
|
||||
value:c(function(a,b){return f(a,qa(b),!1)}),constant:c(function(a,b){Ta(a,"constant");q[a]=b;t[a]=b}),decorator:function(a,b){var c=s.get(a+"Provider"),d=c.$get;c.$get=function(){var a=E.invoke(d,c);return E.invoke(b,null,{$delegate:a})}}}},s=q.$injector=h(q,function(a,b){aa.isString(b)&&k.push(b);throw Ha("unpr",k.join(" <- "));}),t={},E=t.$injector=h(t,function(a,b){var c=s.get(a+"Provider",b);return E.invoke(c.$get,c,u,a)});n(g(b),function(a){a&&E.invoke(a)});return E}function Qe(){var b=!0;this.disableAutoScrolling=
|
||||
function(){b=!1};this.$get=["$window","$location","$rootScope",function(a,c,d){function e(a){var b=null;Array.prototype.some.call(a,function(a){if("a"===ta(a))return b=a,!0});return b}function f(b){if(b){b.scrollIntoView();var c;c=g.yOffset;B(c)?c=c():rc(c)?(c=c[0],c="fixed"!==a.getComputedStyle(c).position?0:c.getBoundingClientRect().bottom):X(c)||(c=0);c&&(b=b.getBoundingClientRect().top,a.scrollBy(0,b-c))}else a.scrollTo(0,0)}function g(a){a=H(a)?a:c.hash();var b;a?(b=h.getElementById(a))?f(b):
|
||||
(b=e(h.getElementsByName(a)))?f(b):"top"===a&&f(null):f(null)}var h=a.document;b&&d.$watch(function(){return c.hash()},function(a,b){a===b&&""===a||Ff(function(){d.$evalAsync(g)})});return g}]}function hb(b,a){if(!b&&!a)return"";if(!b)return a;if(!a)return b;K(b)&&(b=b.join(" "));K(a)&&(a=a.join(" "));return b+" "+a}function Jf(b){H(b)&&(b=b.split(" "));var a=ga();n(b,function(b){b.length&&(a[b]=!0)});return a}function Ia(b){return D(b)?b:{}}function Kf(b,a,c,d){function e(a){try{a.apply(null,xa.call(arguments,
|
||||
1))}finally{if(E--,0===E)for(;L.length;)try{L.pop()()}catch(b){c.error(b)}}}function f(){g();h()}function g(){a:{try{w=m.state;break a}catch(a){}w=void 0}w=y(w)?null:w;ka(w,F)&&(w=F);F=w}function h(){if(A!==l.url()||p!==w)A=l.url(),p=w,n(O,function(a){a(l.url(),w)})}var l=this,k=b.location,m=b.history,q=b.setTimeout,s=b.clearTimeout,t={};l.isMock=!1;var E=0,L=[];l.$$completeOutstandingRequest=e;l.$$incOutstandingRequestCount=function(){E++};l.notifyWhenNoOutstandingRequests=function(a){0===E?a():
|
||||
L.push(a)};var w,p,A=k.href,r=a.find("base"),M=null;g();p=w;l.url=function(a,c,e){y(e)&&(e=null);k!==b.location&&(k=b.location);m!==b.history&&(m=b.history);if(a){var f=p===e;if(A===a&&(!d.history||f))return l;var h=A&&Ja(A)===Ja(a);A=a;p=e;if(!d.history||h&&f){if(!h||M)M=a;c?k.replace(a):h?(c=k,e=a.indexOf("#"),a=-1===e?"":a.substr(e),c.hash=a):k.href=a}else m[c?"replaceState":"pushState"](e,"",a),g(),p=w;return l}return M||k.href.replace(/%27/g,"'")};l.state=function(){return w};var O=[],J=!1,F=
|
||||
null;l.onUrlChange=function(a){if(!J){if(d.history)z(b).on("popstate",f);z(b).on("hashchange",f);J=!0}O.push(a);return a};l.$$applicationDestroyed=function(){z(b).off("hashchange popstate",f)};l.$$checkUrlChange=h;l.baseHref=function(){var a=r.attr("href");return a?a.replace(/^(https?\:)?\/\/[^\/]*/,""):""};l.defer=function(a,b){var c;E++;c=q(function(){delete t[c];e(a)},b||0);t[c]=!0;return c};l.defer.cancel=function(a){return t[a]?(delete t[a],s(a),e(v),!0):!1}}function Ve(){this.$get=["$window",
|
||||
"$log","$sniffer","$document",function(b,a,c,d){return new Kf(b,d,a,c)}]}function We(){this.$get=function(){function b(b,d){function e(a){a!=q&&(s?s==a&&(s=a.n):s=a,f(a.n,a.p),f(a,q),q=a,q.n=null)}function f(a,b){a!=b&&(a&&(a.p=b),b&&(b.n=a))}if(b in a)throw G("$cacheFactory")("iid",b);var g=0,h=Q({},d,{id:b}),l={},k=d&&d.capacity||Number.MAX_VALUE,m={},q=null,s=null;return a[b]={put:function(a,b){if(!y(b)){if(k<Number.MAX_VALUE){var c=m[a]||(m[a]={key:a});e(c)}a in l||g++;l[a]=b;g>k&&this.remove(s.key);
|
||||
return b}},get:function(a){if(k<Number.MAX_VALUE){var b=m[a];if(!b)return;e(b)}return l[a]},remove:function(a){if(k<Number.MAX_VALUE){var b=m[a];if(!b)return;b==q&&(q=b.p);b==s&&(s=b.n);f(b.n,b.p);delete m[a]}delete l[a];g--},removeAll:function(){l={};g=0;m={};q=s=null},destroy:function(){m=h=l=null;delete a[b]},info:function(){return Q({},h,{size:g})}}}var a={};b.info=function(){var b={};n(a,function(a,e){b[e]=a.info()});return b};b.get=function(b){return a[b]};return b}}function qf(){this.$get=
|
||||
["$cacheFactory",function(b){return b("templates")}]}function Cc(b,a){function c(a,b,c){var d=/^\s*([@&]|=(\*?))(\??)\s*(\w*)\s*$/,e={};n(a,function(a,f){var g=a.match(d);if(!g)throw ea("iscp",b,f,a,c?"controller bindings definition":"isolate scope definition");e[f]={mode:g[1][0],collection:"*"===g[2],optional:"?"===g[3],attrName:g[4]||f}});return e}function d(a){var b=a.charAt(0);if(!b||b!==I(b))throw ea("baddir",a);if(a!==a.trim())throw ea("baddir",a);}var e={},f=/^\s*directive\:\s*([\w\-]+)\s+(.*)$/,
|
||||
g=/(([\w\-]+)(?:\:([^;]+))?;?)/,h=Wd("ngSrc,ngSrcset,src,srcset"),l=/^(?:(\^\^?)?(\?)?(\^\^?)?)?/,k=/^(on[a-z]+|formaction)$/;this.directive=function s(a,f){Ta(a,"directive");H(a)?(d(a),pb(f,"directiveFactory"),e.hasOwnProperty(a)||(e[a]=[],b.factory(a+"Directive",["$injector","$exceptionHandler",function(b,d){var f=[];n(e[a],function(e,g){try{var h=b.invoke(e);B(h)?h={compile:qa(h)}:!h.compile&&h.link&&(h.compile=qa(h.link));h.priority=h.priority||0;h.index=g;h.name=h.name||a;h.require=h.require||
|
||||
h.controller&&h.name;h.restrict=h.restrict||"EA";var l=h,k=h,s=h.name,m={isolateScope:null,bindToController:null};D(k.scope)&&(!0===k.bindToController?(m.bindToController=c(k.scope,s,!0),m.isolateScope={}):m.isolateScope=c(k.scope,s,!1));D(k.bindToController)&&(m.bindToController=c(k.bindToController,s,!0));if(D(m.bindToController)){var S=k.controller,E=k.controllerAs;if(!S)throw ea("noctrl",s);var ha;a:if(E&&H(E))ha=E;else{if(H(S)){var n=Vc.exec(S);if(n){ha=n[3];break a}}ha=void 0}if(!ha)throw ea("noident",
|
||||
s);}var r=l.$$bindings=m;D(r.isolateScope)&&(h.$$isolateBindings=r.isolateScope);h.$$moduleName=e.$$moduleName;f.push(h)}catch(u){d(u)}});return f}])),e[a].push(f)):n(a,nc(s));return this};this.aHrefSanitizationWhitelist=function(b){return x(b)?(a.aHrefSanitizationWhitelist(b),this):a.aHrefSanitizationWhitelist()};this.imgSrcSanitizationWhitelist=function(b){return x(b)?(a.imgSrcSanitizationWhitelist(b),this):a.imgSrcSanitizationWhitelist()};var m=!0;this.debugInfoEnabled=function(a){return x(a)?
|
||||
(m=a,this):m};this.$get=["$injector","$interpolate","$exceptionHandler","$templateRequest","$parse","$controller","$rootScope","$document","$sce","$animate","$$sanitizeUri",function(a,b,c,d,w,p,A,r,M,O,J){function F(a,b){try{a.addClass(b)}catch(c){}}function V(a,b,c,d,e){a instanceof z||(a=z(a));n(a,function(b,c){b.nodeType==Pa&&b.nodeValue.match(/\S+/)&&(a[c]=z(b).wrap("<span></span>").parent()[0])});var f=S(a,b,a,c,d,e);V.$$addScopeClass(a);var g=null;return function(b,c,d){pb(b,"scope");d=d||{};
|
||||
var e=d.parentBoundTranscludeFn,h=d.transcludeControllers;d=d.futureParentElement;e&&e.$$boundTransclude&&(e=e.$$boundTransclude);g||(g=(d=d&&d[0])?"foreignobject"!==ta(d)&&d.toString().match(/SVG/)?"svg":"html":"html");d="html"!==g?z(Xb(g,z("<div>").append(a).html())):c?Ra.clone.call(a):a;if(h)for(var k in h)d.data("$"+k+"Controller",h[k].instance);V.$$addScopeInfo(d,b);c&&c(d,b);f&&f(b,d,d,e);return d}}function S(a,b,c,d,e,f){function g(a,c,d,e){var f,k,l,m,s,t,O;if(p)for(O=Array(c.length),m=0;m<
|
||||
h.length;m+=3)f=h[m],O[f]=c[f];else O=c;m=0;for(s=h.length;m<s;)if(k=O[h[m++]],c=h[m++],f=h[m++],c){if(c.scope){if(l=a.$new(),V.$$addScopeInfo(z(k),l),t=c.$$destroyBindings)c.$$destroyBindings=null,l.$on("$destroyed",t)}else l=a;t=c.transcludeOnThisElement?P(a,c.transclude,e):!c.templateOnThisElement&&e?e:!e&&b?P(a,b):null;c(f,l,k,d,t,c)}else f&&f(a,k.childNodes,u,e)}for(var h=[],k,l,m,s,p,t=0;t<a.length;t++){k=new aa;l=ha(a[t],[],k,0===t?d:u,e);(f=l.length?C(l,a[t],k,b,c,null,[],[],f):null)&&f.scope&&
|
||||
V.$$addScopeClass(k.$$element);k=f&&f.terminal||!(m=a[t].childNodes)||!m.length?null:S(m,f?(f.transcludeOnThisElement||!f.templateOnThisElement)&&f.transclude:b);if(f||k)h.push(t,f,k),s=!0,p=p||f;f=null}return s?g:null}function P(a,b,c){return function(d,e,f,g,h){d||(d=a.$new(!1,h),d.$$transcluded=!0);return b(d,e,{parentBoundTranscludeFn:c,transcludeControllers:f,futureParentElement:g})}}function ha(a,b,c,d,e){var h=c.$attr,k;switch(a.nodeType){case pa:x(b,va(ta(a)),"E",d,e);for(var l,m,s,p=a.attributes,
|
||||
t=0,O=p&&p.length;t<O;t++){var L=!1,J=!1;l=p[t];k=l.name;m=T(l.value);l=va(k);if(s=ia.test(l))k=k.replace(Xc,"").substr(8).replace(/_(.)/g,function(a,b){return b.toUpperCase()});var S=l.replace(/(Start|End)$/,"");G(S)&&l===S+"Start"&&(L=k,J=k.substr(0,k.length-5)+"end",k=k.substr(0,k.length-6));l=va(k.toLowerCase());h[l]=k;if(s||!c.hasOwnProperty(l))c[l]=m,Qc(a,l)&&(c[l]=!0);X(a,b,m,l,s);x(b,l,"A",d,e,L,J)}a=a.className;D(a)&&(a=a.animVal);if(H(a)&&""!==a)for(;k=g.exec(a);)l=va(k[2]),x(b,l,"C",d,
|
||||
e)&&(c[l]=T(k[3])),a=a.substr(k.index+k[0].length);break;case Pa:if(11===Va)for(;a.parentNode&&a.nextSibling&&a.nextSibling.nodeType===Pa;)a.nodeValue+=a.nextSibling.nodeValue,a.parentNode.removeChild(a.nextSibling);wa(b,a.nodeValue);break;case 8:try{if(k=f.exec(a.nodeValue))l=va(k[1]),x(b,l,"M",d,e)&&(c[l]=T(k[2]))}catch(E){}}b.sort(za);return b}function ya(a,b,c){var d=[],e=0;if(b&&a.hasAttribute&&a.hasAttribute(b)){do{if(!a)throw ea("uterdir",b,c);a.nodeType==pa&&(a.hasAttribute(b)&&e++,a.hasAttribute(c)&&
|
||||
e--);d.push(a);a=a.nextSibling}while(0<e)}else d.push(a);return z(d)}function Wc(a,b,c){return function(d,e,f,g,h){e=ya(e[0],b,c);return a(d,e,f,g,h)}}function C(a,b,d,e,f,g,h,k,m){function s(a,b,c,d){if(a){c&&(a=Wc(a,c,d));a.require=C.require;a.directiveName=x;if(P===C||C.$$isolateScope)a=Z(a,{isolateScope:!0});h.push(a)}if(b){c&&(b=Wc(b,c,d));b.require=C.require;b.directiveName=x;if(P===C||C.$$isolateScope)b=Z(b,{isolateScope:!0});k.push(b)}}function t(a,b,c,d){var e;if(H(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;e||(d="$"+b+"Controller",e=g?c.inheritedData(d):c.data(d));if(!e&&!f)throw ea("ctreq",b,a);}else if(K(b))for(e=[],g=0,f=b.length;g<f;g++)e[g]=t(a,b[g],c,d);return e||null}function O(a,b,c,d,e,f){var g=ga(),h;for(h in d){var k=d[h],l={$scope:k===P||k.$$isolateScope?e:f,$element:a,$attrs:b,$transclude:c},m=k.controller;"@"==m&&(m=b[k.name]);l=p(m,l,!0,k.controllerAs);g[k.name]=l;r||a.data("$"+k.name+
|
||||
"Controller",l.instance)}return g}function L(a,c,e,f,g,l){function m(a,b,c){var d;$a(a)||(c=b,b=a,a=u);r&&(d=A);c||(c=r?ja.parent():ja);return g(a,b,d,c,ya)}var s,p,J,E,A,ha,ja;b===e?(f=d,ja=d.$$element):(ja=z(e),f=new aa(ja,d));P&&(E=c.$new(!0));g&&(ha=m,ha.$$boundTransclude=g);w&&(A=O(ja,f,ha,w,E,c));P&&(V.$$addScopeInfo(ja,E,!0,!(F&&(F===P||F===P.$$originalDirective))),V.$$addScopeClass(ja,!0),E.$$isolateBindings=P.$$isolateBindings,Y(c,f,E,E.$$isolateBindings,P,E));if(A){var n=P||S,M;n&&A[n.name]&&
|
||||
(p=n.$$bindings.bindToController,(J=A[n.name])&&J.identifier&&p&&(M=J,l.$$destroyBindings=Y(c,f,J.instance,p,n)));for(s in A){J=A[s];var C=J();C!==J.instance&&(J.instance=C,ja.data("$"+s+"Controller",C),J===M&&(l.$$destroyBindings(),l.$$destroyBindings=Y(c,f,C,p,n)))}}s=0;for(l=h.length;s<l;s++)p=h[s],$(p,p.isolateScope?E:c,ja,f,p.require&&t(p.directiveName,p.require,ja,A),ha);var ya=c;P&&(P.template||null===P.templateUrl)&&(ya=E);a&&a(ya,e.childNodes,u,g);for(s=k.length-1;0<=s;s--)p=k[s],$(p,p.isolateScope?
|
||||
E:c,ja,f,p.require&&t(p.directiveName,p.require,ja,A),ha)}m=m||{};for(var J=-Number.MAX_VALUE,S=m.newScopeDirective,w=m.controllerDirectives,P=m.newIsolateScopeDirective,F=m.templateDirective,A=m.nonTlbTranscludeDirective,n=!1,M=!1,r=m.hasElementTranscludeDirective,ba=d.$$element=z(b),C,x,v,y=e,za,wa=0,G=a.length;wa<G;wa++){C=a[wa];var Bb=C.$$start,I=C.$$end;Bb&&(ba=ya(b,Bb,I));v=u;if(J>C.priority)break;if(v=C.scope)C.templateUrl||(D(v)?(N("new/isolated scope",P||S,C,ba),P=C):N("new/isolated scope",
|
||||
P,C,ba)),S=S||C;x=C.name;!C.templateUrl&&C.controller&&(v=C.controller,w=w||ga(),N("'"+x+"' controller",w[x],C,ba),w[x]=C);if(v=C.transclude)n=!0,C.$$tlb||(N("transclusion",A,C,ba),A=C),"element"==v?(r=!0,J=C.priority,v=ba,ba=d.$$element=z(W.createComment(" "+x+": "+d[x]+" ")),b=ba[0],U(f,xa.call(v,0),b),y=V(v,e,J,g&&g.name,{nonTlbTranscludeDirective:A})):(v=z(Ub(b)).contents(),ba.empty(),y=V(v,e));if(C.template)if(M=!0,N("template",F,C,ba),F=C,v=B(C.template)?C.template(ba,d):C.template,v=fa(v),
|
||||
C.replace){g=C;v=Sb.test(v)?Yc(Xb(C.templateNamespace,T(v))):[];b=v[0];if(1!=v.length||b.nodeType!==pa)throw ea("tplrt",x,"");U(f,ba,b);G={$attr:{}};v=ha(b,[],G);var Q=a.splice(wa+1,a.length-(wa+1));P&&Zc(v);a=a.concat(v).concat(Q);$c(d,G);G=a.length}else ba.html(v);if(C.templateUrl)M=!0,N("template",F,C,ba),F=C,C.replace&&(g=C),L=Lf(a.splice(wa,a.length-wa),ba,d,f,n&&y,h,k,{controllerDirectives:w,newScopeDirective:S!==C&&S,newIsolateScopeDirective:P,templateDirective:F,nonTlbTranscludeDirective:A}),
|
||||
G=a.length;else if(C.compile)try{za=C.compile(ba,d,y),B(za)?s(null,za,Bb,I):za&&s(za.pre,za.post,Bb,I)}catch(R){c(R,ua(ba))}C.terminal&&(L.terminal=!0,J=Math.max(J,C.priority))}L.scope=S&&!0===S.scope;L.transcludeOnThisElement=n;L.templateOnThisElement=M;L.transclude=y;m.hasElementTranscludeDirective=r;return L}function Zc(a){for(var b=0,c=a.length;b<c;b++)a[b]=Nb(a[b],{$$isolateScope:!0})}function x(b,d,f,g,h,k,l){if(d===h)return null;h=null;if(e.hasOwnProperty(d)){var m;d=a.get(d+"Directive");for(var p=
|
||||
0,t=d.length;p<t;p++)try{m=d[p],(g===u||g>m.priority)&&-1!=m.restrict.indexOf(f)&&(k&&(m=Nb(m,{$$start:k,$$end:l})),b.push(m),h=m)}catch(J){c(J)}}return h}function G(b){if(e.hasOwnProperty(b))for(var c=a.get(b+"Directive"),d=0,f=c.length;d<f;d++)if(b=c[d],b.multiElement)return!0;return!1}function $c(a,b){var c=b.$attr,d=a.$attr,e=a.$$element;n(a,function(d,e){"$"!=e.charAt(0)&&(b[e]&&b[e]!==d&&(d+=("style"===e?";":" ")+b[e]),a.$set(e,d,!0,c[e]))});n(b,function(b,f){"class"==f?(F(e,b),a["class"]=(a["class"]?
|
||||
a["class"]+" ":"")+b):"style"==f?(e.attr("style",e.attr("style")+";"+b),a.style=(a.style?a.style+";":"")+b):"$"==f.charAt(0)||a.hasOwnProperty(f)||(a[f]=b,d[f]=c[f])})}function Lf(a,b,c,e,f,g,h,k){var l=[],m,s,p=b[0],t=a.shift(),J=Nb(t,{templateUrl:null,transclude:null,replace:null,$$originalDirective:t}),O=B(t.templateUrl)?t.templateUrl(b,c):t.templateUrl,E=t.templateNamespace;b.empty();d(O).then(function(d){var L,w;d=fa(d);if(t.replace){d=Sb.test(d)?Yc(Xb(E,T(d))):[];L=d[0];if(1!=d.length||L.nodeType!==
|
||||
pa)throw ea("tplrt",t.name,O);d={$attr:{}};U(e,b,L);var A=ha(L,[],d);D(t.scope)&&Zc(A);a=A.concat(a);$c(c,d)}else L=p,b.html(d);a.unshift(J);m=C(a,L,c,f,b,t,g,h,k);n(e,function(a,c){a==L&&(e[c]=b[0])});for(s=S(b[0].childNodes,f);l.length;){d=l.shift();w=l.shift();var M=l.shift(),V=l.shift(),A=b[0];if(!d.$$destroyed){if(w!==p){var ya=w.className;k.hasElementTranscludeDirective&&t.replace||(A=Ub(L));U(M,z(w),A);F(z(A),ya)}w=m.transcludeOnThisElement?P(d,m.transclude,V):V;m(s,d,A,e,w,m)}}l=null});return function(a,
|
||||
b,c,d,e){a=e;b.$$destroyed||(l?l.push(b,c,d,a):(m.transcludeOnThisElement&&(a=P(b,m.transclude,e)),m(s,b,c,d,a,m)))}}function za(a,b){var c=b.priority-a.priority;return 0!==c?c:a.name!==b.name?a.name<b.name?-1:1:a.index-b.index}function N(a,b,c,d){function e(a){return a?" (module: "+a+")":""}if(b)throw ea("multidir",b.name,e(b.$$moduleName),c.name,e(c.$$moduleName),a,ua(d));}function wa(a,c){var d=b(c,!0);d&&a.push({priority:0,compile:function(a){a=a.parent();var b=!!a.length;b&&V.$$addBindingClass(a);
|
||||
return function(a,c){var e=c.parent();b||V.$$addBindingClass(e);V.$$addBindingInfo(e,d.expressions);a.$watch(d,function(a){c[0].nodeValue=a})}}})}function Xb(a,b){a=I(a||"html");switch(a){case "svg":case "math":var c=W.createElement("div");c.innerHTML="<"+a+">"+b+"</"+a+">";return c.childNodes[0].childNodes;default:return b}}function R(a,b){if("srcdoc"==b)return M.HTML;var c=ta(a);if("xlinkHref"==b||"form"==c&&"action"==b||"img"!=c&&("src"==b||"ngSrc"==b))return M.RESOURCE_URL}function X(a,c,d,e,
|
||||
f){var g=R(a,e);f=h[e]||f;var l=b(d,!0,g,f);if(l){if("multiple"===e&&"select"===ta(a))throw ea("selmulti",ua(a));c.push({priority:100,compile:function(){return{pre:function(a,c,h){c=h.$$observers||(h.$$observers={});if(k.test(e))throw ea("nodomevents");var m=h[e];m!==d&&(l=m&&b(m,!0,g,f),d=m);l&&(h[e]=l(a),(c[e]||(c[e]=[])).$$inter=!0,(h.$$observers&&h.$$observers[e].$$scope||a).$watch(l,function(a,b){"class"===e&&a!=b?h.$updateClass(a,b):h.$set(e,a)}))}}}})}}function U(a,b,c){var d=b[0],e=b.length,
|
||||
f=d.parentNode,g,h;if(a)for(g=0,h=a.length;g<h;g++)if(a[g]==d){a[g++]=c;h=g+e-1;for(var k=a.length;g<k;g++,h++)h<k?a[g]=a[h]:delete a[g];a.length-=e-1;a.context===d&&(a.context=c);break}f&&f.replaceChild(c,d);a=W.createDocumentFragment();a.appendChild(d);z.hasData(d)&&(z(c).data(z(d).data()),la?(Qb=!0,la.cleanData([d])):delete z.cache[d[z.expando]]);d=1;for(e=b.length;d<e;d++)f=b[d],z(f).remove(),a.appendChild(f),delete b[d];b[0]=c;b.length=1}function Z(a,b){return Q(function(){return a.apply(null,
|
||||
arguments)},a,b)}function $(a,b,d,e,f,g){try{a(b,d,e,f,g)}catch(h){c(h,ua(d))}}function Y(a,c,d,e,f,g){var h;n(e,function(e,g){var k=e.attrName,l=e.optional,m,s,p,L;switch(e.mode){case "@":l||Na.call(c,k)||(d[g]=c[k]=void 0);c.$observe(k,function(a){H(a)&&(d[g]=a)});c.$$observers[k].$$scope=a;H(c[k])&&(d[g]=b(c[k])(a));break;case "=":if(!Na.call(c,k)){if(l)break;c[k]=void 0}if(l&&!c[k])break;s=w(c[k]);L=s.literal?ka:function(a,b){return a===b||a!==a&&b!==b};p=s.assign||function(){m=d[g]=s(a);throw ea("nonassign",
|
||||
c[k],f.name);};m=d[g]=s(a);l=function(b){L(b,d[g])||(L(b,m)?p(a,b=d[g]):d[g]=b);return m=b};l.$stateful=!0;l=e.collection?a.$watchCollection(c[k],l):a.$watch(w(c[k],l),null,s.literal);h=h||[];h.push(l);break;case "&":s=c.hasOwnProperty(k)?w(c[k]):v;if(s===v&&l)break;d[g]=function(b){return s(a,b)}}});e=h?function(){for(var a=0,b=h.length;a<b;++a)h[a]()}:v;return g&&e!==v?(g.$on("$destroy",e),v):e}var aa=function(a,b){if(b){var c=Object.keys(b),d,e,f;d=0;for(e=c.length;d<e;d++)f=c[d],this[f]=b[f]}else this.$attr=
|
||||
{};this.$$element=a};aa.prototype={$normalize:va,$addClass:function(a){a&&0<a.length&&O.addClass(this.$$element,a)},$removeClass:function(a){a&&0<a.length&&O.removeClass(this.$$element,a)},$updateClass:function(a,b){var c=ad(a,b);c&&c.length&&O.addClass(this.$$element,c);(c=ad(b,a))&&c.length&&O.removeClass(this.$$element,c)},$set:function(a,b,d,e){var f=this.$$element[0],g=Qc(f,a),h=Gf(f,a),f=a;g?(this.$$element.prop(a,b),e=g):h&&(this[h]=b,f=h);this[a]=b;e?this.$attr[a]=e:(e=this.$attr[a])||(this.$attr[a]=
|
||||
e=zc(a,"-"));g=ta(this.$$element);if("a"===g&&"href"===a||"img"===g&&"src"===a)this[a]=b=J(b,"src"===a);else if("img"===g&&"srcset"===a){for(var g="",h=T(b),k=/(\s+\d+x\s*,|\s+\d+w\s*,|\s+,|,\s+)/,k=/\s/.test(h)?k:/(,)/,h=h.split(k),k=Math.floor(h.length/2),l=0;l<k;l++)var m=2*l,g=g+J(T(h[m]),!0),g=g+(" "+T(h[m+1]));h=T(h[2*l]).split(/\s/);g+=J(T(h[0]),!0);2===h.length&&(g+=" "+T(h[1]));this[a]=b=g}!1!==d&&(null===b||b===u?this.$$element.removeAttr(e):this.$$element.attr(e,b));(a=this.$$observers)&&
|
||||
n(a[f],function(a){try{a(b)}catch(d){c(d)}})},$observe:function(a,b){var c=this,d=c.$$observers||(c.$$observers=ga()),e=d[a]||(d[a]=[]);e.push(b);A.$evalAsync(function(){e.$$inter||!c.hasOwnProperty(a)||y(c[a])||b(c[a])});return function(){bb(e,b)}}};var ca=b.startSymbol(),da=b.endSymbol(),fa="{{"==ca||"}}"==da?Za:function(a){return a.replace(/\{\{/g,ca).replace(/}}/g,da)},ia=/^ngAttr[A-Z]/;V.$$addBindingInfo=m?function(a,b){var c=a.data("$binding")||[];K(b)?c=c.concat(b):c.push(b);a.data("$binding",
|
||||
c)}:v;V.$$addBindingClass=m?function(a){F(a,"ng-binding")}:v;V.$$addScopeInfo=m?function(a,b,c,d){a.data(c?d?"$isolateScopeNoTemplate":"$isolateScope":"$scope",b)}:v;V.$$addScopeClass=m?function(a,b){F(a,b?"ng-isolate-scope":"ng-scope")}:v;return V}]}function va(b){return fb(b.replace(Xc,""))}function ad(b,a){var c="",d=b.split(/\s+/),e=a.split(/\s+/),f=0;a:for(;f<d.length;f++){for(var g=d[f],h=0;h<e.length;h++)if(g==e[h])continue a;c+=(0<c.length?" ":"")+g}return c}function Yc(b){b=z(b);var a=b.length;
|
||||
if(1>=a)return b;for(;a--;)8===b[a].nodeType&&Mf.call(b,a,1);return b}function Xe(){var b={},a=!1;this.register=function(a,d){Ta(a,"controller");D(a)?Q(b,a):b[a]=d};this.allowGlobals=function(){a=!0};this.$get=["$injector","$window",function(c,d){function e(a,b,c,d){if(!a||!D(a.$scope))throw G("$controller")("noscp",d,b);a.$scope[b]=c}return function(f,g,h,l){var k,m,q;h=!0===h;l&&H(l)&&(q=l);if(H(f)){l=f.match(Vc);if(!l)throw Nf("ctrlfmt",f);m=l[1];q=q||l[3];f=b.hasOwnProperty(m)?b[m]:Bc(g.$scope,
|
||||
m,!0)||(a?Bc(d,m,!0):u);Sa(f,m,!0)}if(h)return h=(K(f)?f[f.length-1]:f).prototype,k=Object.create(h||null),q&&e(g,q,k,m||f.name),Q(function(){var a=c.invoke(f,k,g,m);a!==k&&(D(a)||B(a))&&(k=a,q&&e(g,q,k,m||f.name));return k},{instance:k,identifier:q});k=c.instantiate(f,g,m);q&&e(g,q,k,m||f.name);return k}}]}function Ye(){this.$get=["$window",function(b){return z(b.document)}]}function Ze(){this.$get=["$log",function(b){return function(a,c){b.error.apply(b,arguments)}}]}function Yb(b){return D(b)?
|
||||
ca(b)?b.toISOString():db(b):b}function df(){this.$get=function(){return function(b){if(!b)return"";var a=[];mc(b,function(b,d){null===b||y(b)||(K(b)?n(b,function(b,c){a.push(ma(d)+"="+ma(Yb(b)))}):a.push(ma(d)+"="+ma(Yb(b))))});return a.join("&")}}}function ef(){this.$get=function(){return function(b){function a(b,e,f){null===b||y(b)||(K(b)?n(b,function(b,c){a(b,e+"["+(D(b)?c:"")+"]")}):D(b)&&!ca(b)?mc(b,function(b,c){a(b,e+(f?"":"[")+c+(f?"":"]"))}):c.push(ma(e)+"="+ma(Yb(b))))}if(!b)return"";var c=
|
||||
[];a(b,"",!0);return c.join("&")}}}function Zb(b,a){if(H(b)){var c=b.replace(Of,"").trim();if(c){var d=a("Content-Type");(d=d&&0===d.indexOf(bd))||(d=(d=c.match(Pf))&&Qf[d[0]].test(c));d&&(b=uc(c))}}return b}function cd(b){var a=ga(),c;H(b)?n(b.split("\n"),function(b){c=b.indexOf(":");var e=I(T(b.substr(0,c)));b=T(b.substr(c+1));e&&(a[e]=a[e]?a[e]+", "+b:b)}):D(b)&&n(b,function(b,c){var f=I(c),g=T(b);f&&(a[f]=a[f]?a[f]+", "+g:g)});return a}function dd(b){var a;return function(c){a||(a=cd(b));return c?
|
||||
(c=a[I(c)],void 0===c&&(c=null),c):a}}function ed(b,a,c,d){if(B(d))return d(b,a,c);n(d,function(d){b=d(b,a,c)});return b}function cf(){var b=this.defaults={transformResponse:[Zb],transformRequest:[function(a){return D(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($b),put:ia($b),patch:ia($b)},xsrfCookieName:"XSRF-TOKEN",xsrfHeaderName:"X-XSRF-TOKEN",paramSerializer:"$httpParamSerializer"},
|
||||
a=!1;this.useApplyAsync=function(b){return x(b)?(a=!!b,this):a};var c=!0;this.useLegacyPromiseExtensions=function(a){return x(a)?(c=!!a,this):c};var d=this.interceptors=[];this.$get=["$httpBackend","$$cookieReader","$cacheFactory","$rootScope","$q","$injector",function(e,f,g,h,l,k){function m(a){function d(a){var b=Q({},a);b.data=a.data?ed(a.data,a.headers,a.status,f.transformResponse):a.data;a=a.status;return 200<=a&&300>a?b:l.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(!aa.isObject(a))throw G("$http")("badreq",a);var f=Q({method:"get",transformRequest:b.transformRequest,transformResponse:b.transformResponse,paramSerializer:b.paramSerializer},a);f.headers=function(a){var c=b.headers,d=Q({},a.headers),f,g,h,c=Q({},c.common,c[I(a.method)]);a:for(f in c){g=I(f);for(h in d)if(I(h)===g)continue a;d[f]=c[f]}return e(d,ia(a))}(a);f.method=rb(f.method);f.paramSerializer=H(f.paramSerializer)?k.get(f.paramSerializer):f.paramSerializer;var g=
|
||||
[function(a){var c=a.headers,e=ed(a.data,dd(c),u,a.transformRequest);y(e)&&n(c,function(a,b){"content-type"===I(b)&&delete c[b]});y(a.withCredentials)&&!y(b.withCredentials)&&(a.withCredentials=b.withCredentials);return q(a,e).then(d,d)},u],h=l.when(f);for(n(E,function(a){(a.request||a.requestError)&&g.unshift(a.request,a.requestError);(a.response||a.responseError)&&g.push(a.response,a.responseError)});g.length;){a=g.shift();var m=g.shift(),h=h.then(a,m)}c?(h.success=function(a){Sa(a,"fn");h.then(function(b){a(b.data,
|
||||
b.status,b.headers,f)});return h},h.error=function(a){Sa(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 q(c,d){function g(b,c,d,e){function f(){k(c,b,d,e)}F&&(200<=b&&300>b?F.put(P,[b,c,cd(d),e]):F.remove(P));a?h.$applyAsync(f):(f(),h.$$phase||h.$apply())}function k(a,b,d,e){b=Math.max(b,0);(200<=b&&300>b?O.resolve:O.reject)({data:a,status:b,headers:dd(d),config:c,statusText:e})}function q(a){k(a.data,a.status,
|
||||
ia(a.headers()),a.statusText)}function E(){var a=m.pendingRequests.indexOf(c);-1!==a&&m.pendingRequests.splice(a,1)}var O=l.defer(),J=O.promise,F,n,S=c.headers,P=s(c.url,c.paramSerializer(c.params));m.pendingRequests.push(c);J.then(E,E);!c.cache&&!b.cache||!1===c.cache||"GET"!==c.method&&"JSONP"!==c.method||(F=D(c.cache)?c.cache:D(b.cache)?b.cache:t);F&&(n=F.get(P),x(n)?n&&B(n.then)?n.then(q,q):K(n)?k(n[1],n[0],ia(n[2]),n[3]):k(n,200,{},"OK"):F.put(P,J));y(n)&&((n=gd(c.url)?f()[c.xsrfCookieName||
|
||||
b.xsrfCookieName]:u)&&(S[c.xsrfHeaderName||b.xsrfHeaderName]=n),e(c.method,P,d,g,S,c.timeout,c.withCredentials,c.responseType));return J}function s(a,b){0<b.length&&(a+=(-1==a.indexOf("?")?"?":"&")+b);return a}var t=g("$http");b.paramSerializer=H(b.paramSerializer)?k.get(b.paramSerializer):b.paramSerializer;var E=[];n(d,function(a){E.unshift(H(a)?k.get(a):k.invoke(a))});m.pendingRequests=[];(function(a){n(arguments,function(a){m[a]=function(b,c){return m(Q({},c||{},{method:a,url:b}))}})})("get","delete",
|
||||
"head","jsonp");(function(a){n(arguments,function(a){m[a]=function(b,c,d){return m(Q({},d||{},{method:a,url:b,data:c}))}})})("post","put","patch");m.defaults=b;return m}]}function Rf(){return new N.XMLHttpRequest}function ff(){this.$get=["$browser","$window","$document",function(b,a,c){return Sf(b,Rf,b.defer,a.angular.callbacks,c[0])}]}function Sf(b,a,c,d,e){function f(a,b,c){var f=e.createElement("script"),m=null;f.type="text/javascript";f.src=a;f.async=!0;m=function(a){f.removeEventListener("load",
|
||||
m,!1);f.removeEventListener("error",m,!1);e.body.removeChild(f);f=null;var g=-1,t="unknown";a&&("load"!==a.type||d[b].called||(a={type:"error"}),t=a.type,g="error"===a.type?404:200);c&&c(g,t)};f.addEventListener("load",m,!1);f.addEventListener("error",m,!1);e.body.appendChild(f);return m}return function(e,h,l,k,m,q,s,t){function E(){p&&p();A&&A.abort()}function L(a,d,e,f,g){M!==u&&c.cancel(M);p=A=null;a(d,e,f,g);b.$$completeOutstandingRequest(v)}b.$$incOutstandingRequestCount();h=h||b.url();if("jsonp"==
|
||||
I(e)){var w="_"+(d.counter++).toString(36);d[w]=function(a){d[w].data=a;d[w].called=!0};var p=f(h.replace("JSON_CALLBACK","angular.callbacks."+w),w,function(a,b){L(k,a,d[w].data,"",b);d[w]=v})}else{var A=a();A.open(e,h,!0);n(m,function(a,b){x(a)&&A.setRequestHeader(b,a)});A.onload=function(){var a=A.statusText||"",b="response"in A?A.response:A.responseText,c=1223===A.status?204:A.status;0===c&&(c=b?200:"file"==Aa(h).protocol?404:0);L(k,c,b,A.getAllResponseHeaders(),a)};e=function(){L(k,-1,null,null,
|
||||
"")};A.onerror=e;A.onabort=e;s&&(A.withCredentials=!0);if(t)try{A.responseType=t}catch(r){if("json"!==t)throw r;}A.send(l)}if(0<q)var M=c(E,q);else q&&B(q.then)&&q.then(E)}}function af(){var b="{{",a="}}";this.startSymbol=function(a){return a?(b=a,this):b};this.endSymbol=function(b){return b?(a=b,this):a};this.$get=["$parse","$exceptionHandler","$sce",function(c,d,e){function f(a){return"\\\\\\"+a}function g(c){return c.replace(m,b).replace(q,a)}function h(f,h,m,q){function w(a){try{var b=a;a=m?e.getTrusted(m,
|
||||
b):e.valueOf(b);var c;if(q&&!x(a))c=a;else if(null==a)c="";else{switch(typeof a){case "string":break;case "number":a=""+a;break;default:a=db(a)}c=a}return c}catch(g){d(Ka.interr(f,g))}}q=!!q;for(var p,n,r=0,M=[],O=[],J=f.length,F=[],V=[];r<J;)if(-1!=(p=f.indexOf(b,r))&&-1!=(n=f.indexOf(a,p+l)))r!==p&&F.push(g(f.substring(r,p))),r=f.substring(p+l,n),M.push(r),O.push(c(r,w)),r=n+k,V.push(F.length),F.push("");else{r!==J&&F.push(g(f.substring(r)));break}m&&1<F.length&&Ka.throwNoconcat(f);if(!h||M.length){var S=
|
||||
function(a){for(var b=0,c=M.length;b<c;b++){if(q&&y(a[b]))return;F[V[b]]=a[b]}return F.join("")};return Q(function(a){var b=0,c=M.length,e=Array(c);try{for(;b<c;b++)e[b]=O[b](a);return S(e)}catch(g){d(Ka.interr(f,g))}},{exp:f,expressions:M,$$watchDelegate:function(a,b){var c;return a.$watchGroup(O,function(d,e){var f=S(d);B(b)&&b.call(this,f,d!==e?c:f,a);c=f})}})}}var l=b.length,k=a.length,m=new RegExp(b.replace(/./g,f),"g"),q=new RegExp(a.replace(/./g,f),"g");h.startSymbol=function(){return b};h.endSymbol=
|
||||
function(){return a};return h}]}function bf(){this.$get=["$rootScope","$window","$q","$$q",function(b,a,c,d){function e(e,h,l,k){var m=4<arguments.length,q=m?xa.call(arguments,4):[],s=a.setInterval,t=a.clearInterval,E=0,L=x(k)&&!k,w=(L?d:c).defer(),p=w.promise;l=x(l)?l:0;p.then(null,null,m?function(){e.apply(null,q)}:e);p.$$intervalId=s(function(){w.notify(E++);0<l&&E>=l&&(w.resolve(E),t(p.$$intervalId),delete f[p.$$intervalId]);L||b.$apply()},h);f[p.$$intervalId]=w;return p}var f={};e.cancel=function(b){return b&&
|
||||
b.$$intervalId in f?(f[b.$$intervalId].reject("canceled"),a.clearInterval(b.$$intervalId),delete f[b.$$intervalId],!0):!1};return e}]}function ac(b){b=b.split("/");for(var a=b.length;a--;)b[a]=nb(b[a]);return b.join("/")}function hd(b,a){var c=Aa(b);a.$$protocol=c.protocol;a.$$host=c.hostname;a.$$port=Y(c.port)||Tf[c.protocol]||null}function id(b,a){var c="/"!==b.charAt(0);c&&(b="/"+b);var d=Aa(b);a.$$path=decodeURIComponent(c&&"/"===d.pathname.charAt(0)?d.pathname.substring(1):d.pathname);a.$$search=
|
||||
xc(d.search);a.$$hash=decodeURIComponent(d.hash);a.$$path&&"/"!=a.$$path.charAt(0)&&(a.$$path="/"+a.$$path)}function ra(b,a){if(0===a.indexOf(b))return a.substr(b.length)}function Ja(b){var a=b.indexOf("#");return-1==a?b:b.substr(0,a)}function Cb(b){return b.replace(/(#.+)|#$/,"$1")}function bc(b,a,c){this.$$html5=!0;c=c||"";hd(b,this);this.$$parse=function(b){var c=ra(a,b);if(!H(c))throw Db("ipthprfx",b,a);id(c,this);this.$$path||(this.$$path="/");this.$$compose()};this.$$compose=function(){var b=
|
||||
Pb(this.$$search),c=this.$$hash?"#"+nb(this.$$hash):"";this.$$url=ac(this.$$path)+(b?"?"+b:"")+c;this.$$absUrl=a+this.$$url.substr(1)};this.$$parseLinkUrl=function(d,e){if(e&&"#"===e[0])return this.hash(e.slice(1)),!0;var f,g;(f=ra(b,d))!==u?(g=f,g=(f=ra(c,f))!==u?a+(ra("/",f)||f):b+g):(f=ra(a,d))!==u?g=a+f:a==d+"/"&&(g=a);g&&this.$$parse(g);return!!g}}function cc(b,a,c){hd(b,this);this.$$parse=function(d){var e=ra(b,d)||ra(a,d),f;y(e)||"#"!==e.charAt(0)?this.$$html5?f=e:(f="",y(e)&&(b=d,this.replace())):
|
||||
(f=ra(c,e),y(f)&&(f=e));id(f,this);d=this.$$path;var e=b,g=/^\/[A-Z]:(\/.*)/;0===f.indexOf(e)&&(f=f.replace(e,""));g.exec(f)||(d=(f=g.exec(d))?f[1]:d);this.$$path=d;this.$$compose()};this.$$compose=function(){var a=Pb(this.$$search),e=this.$$hash?"#"+nb(this.$$hash):"";this.$$url=ac(this.$$path)+(a?"?"+a:"")+e;this.$$absUrl=b+(this.$$url?c+this.$$url:"")};this.$$parseLinkUrl=function(a,c){return Ja(b)==Ja(a)?(this.$$parse(a),!0):!1}}function jd(b,a,c){this.$$html5=!0;cc.apply(this,arguments);this.$$parseLinkUrl=
|
||||
function(d,e){if(e&&"#"===e[0])return this.hash(e.slice(1)),!0;var f,g;b==Ja(d)?f=d:(g=ra(a,d))?f=b+c+g:a===d+"/"&&(f=a);f&&this.$$parse(f);return!!f};this.$$compose=function(){var a=Pb(this.$$search),e=this.$$hash?"#"+nb(this.$$hash):"";this.$$url=ac(this.$$path)+(a?"?"+a:"")+e;this.$$absUrl=b+c+this.$$url}}function Eb(b){return function(){return this[b]}}function kd(b,a){return function(c){if(y(c))return this[b];this[b]=a(c);this.$$compose();return this}}function gf(){var b="",a={enabled:!1,requireBase:!0,
|
||||
rewriteLinks:!0};this.hashPrefix=function(a){return x(a)?(b=a,this):b};this.html5Mode=function(b){return ab(b)?(a.enabled=b,this):D(b)?(ab(b.enabled)&&(a.enabled=b.enabled),ab(b.requireBase)&&(a.requireBase=b.requireBase),ab(b.rewriteLinks)&&(a.rewriteLinks=b.rewriteLinks),this):a};this.$get=["$rootScope","$browser","$sniffer","$rootElement","$window",function(c,d,e,f,g){function h(a,b,c){var e=k.url(),f=k.$$state;try{d.url(a,b,c),k.$$state=d.state()}catch(g){throw k.url(e),k.$$state=f,g;}}function l(a,
|
||||
b){c.$broadcast("$locationChangeSuccess",k.absUrl(),a,k.$$state,b)}var k,m;m=d.baseHref();var q=d.url(),s;if(a.enabled){if(!m&&a.requireBase)throw Db("nobase");s=q.substring(0,q.indexOf("/",q.indexOf("//")+2))+(m||"/");m=e.history?bc:jd}else s=Ja(q),m=cc;var t=s.substr(0,Ja(s).lastIndexOf("/")+1);k=new m(s,t,"#"+b);k.$$parseLinkUrl(q,q);k.$$state=d.state();var E=/^\s*(javascript|mailto):/i;f.on("click",function(b){if(a.rewriteLinks&&!b.ctrlKey&&!b.metaKey&&!b.shiftKey&&2!=b.which&&2!=b.button){for(var e=
|
||||
z(b.target);"a"!==ta(e[0]);)if(e[0]===f[0]||!(e=e.parent())[0])return;var h=e.prop("href"),l=e.attr("href")||e.attr("xlink:href");D(h)&&"[object SVGAnimatedString]"===h.toString()&&(h=Aa(h.animVal).href);E.test(h)||!h||e.attr("target")||b.isDefaultPrevented()||!k.$$parseLinkUrl(h,l)||(b.preventDefault(),k.absUrl()!=d.url()&&(c.$apply(),g.angular["ff-684208-preventDefault"]=!0))}});Cb(k.absUrl())!=Cb(q)&&d.url(k.absUrl(),!0);var L=!0;d.onUrlChange(function(a,b){y(ra(t,a))?g.location.href=a:(c.$evalAsync(function(){var d=
|
||||
k.absUrl(),e=k.$$state,f;k.$$parse(a);k.$$state=b;f=c.$broadcast("$locationChangeStart",a,d,b,e).defaultPrevented;k.absUrl()===a&&(f?(k.$$parse(d),k.$$state=e,h(d,!1,e)):(L=!1,l(d,e)))}),c.$$phase||c.$digest())});c.$watch(function(){var a=Cb(d.url()),b=Cb(k.absUrl()),f=d.state(),g=k.$$replace,m=a!==b||k.$$html5&&e.history&&f!==k.$$state;if(L||m)L=!1,c.$evalAsync(function(){var b=k.absUrl(),d=c.$broadcast("$locationChangeStart",b,a,k.$$state,f).defaultPrevented;k.absUrl()===b&&(d?(k.$$parse(a),k.$$state=
|
||||
f):(m&&h(b,g,f===k.$$state?null:k.$$state),l(a,f)))});k.$$replace=!1});return k}]}function hf(){var b=!0,a=this;this.debugEnabled=function(a){return x(a)?(b=a,this):b};this.$get=["$window",function(c){function d(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=c.console||{},e=b[a]||b.log||v;a=!1;try{a=!!e.apply}catch(l){}return a?function(){var a=
|
||||
[];n(arguments,function(b){a.push(d(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(){b&&c.apply(a,arguments)}}()}}]}function Wa(b,a){if("__defineGetter__"===b||"__defineSetter__"===b||"__lookupGetter__"===b||"__lookupSetter__"===b||"__proto__"===b)throw da("isecfld",a);return b}function Ba(b,a){if(b){if(b.constructor===b)throw da("isecfn",a);if(b.window===b)throw da("isecwindow",
|
||||
a);if(b.children&&(b.nodeName||b.prop&&b.attr&&b.find))throw da("isecdom",a);if(b===Object)throw da("isecobj",a);}return b}function ld(b,a){if(b){if(b.constructor===b)throw da("isecfn",a);if(b===Uf||b===Vf||b===Wf)throw da("isecff",a);}}function Xf(b,a){return"undefined"!==typeof b?b:a}function md(b,a){return"undefined"===typeof b?a:"undefined"===typeof a?b:b+a}function U(b,a){var c,d;switch(b.type){case r.Program:c=!0;n(b.body,function(b){U(b.expression,a);c=c&&b.expression.constant});b.constant=
|
||||
c;break;case r.Literal:b.constant=!0;b.toWatch=[];break;case r.UnaryExpression:U(b.argument,a);b.constant=b.argument.constant;b.toWatch=b.argument.toWatch;break;case r.BinaryExpression:U(b.left,a);U(b.right,a);b.constant=b.left.constant&&b.right.constant;b.toWatch=b.left.toWatch.concat(b.right.toWatch);break;case r.LogicalExpression:U(b.left,a);U(b.right,a);b.constant=b.left.constant&&b.right.constant;b.toWatch=b.constant?[]:[b];break;case r.ConditionalExpression:U(b.test,a);U(b.alternate,a);U(b.consequent,
|
||||
a);b.constant=b.test.constant&&b.alternate.constant&&b.consequent.constant;b.toWatch=b.constant?[]:[b];break;case r.Identifier:b.constant=!1;b.toWatch=[b];break;case r.MemberExpression:U(b.object,a);b.computed&&U(b.property,a);b.constant=b.object.constant&&(!b.computed||b.property.constant);b.toWatch=[b];break;case r.CallExpression:c=b.filter?!a(b.callee.name).$stateful:!1;d=[];n(b.arguments,function(b){U(b,a);c=c&&b.constant;b.constant||d.push.apply(d,b.toWatch)});b.constant=c;b.toWatch=b.filter&&
|
||||
!a(b.callee.name).$stateful?d:[b];break;case r.AssignmentExpression:U(b.left,a);U(b.right,a);b.constant=b.left.constant&&b.right.constant;b.toWatch=[b];break;case r.ArrayExpression:c=!0;d=[];n(b.elements,function(b){U(b,a);c=c&&b.constant;b.constant||d.push.apply(d,b.toWatch)});b.constant=c;b.toWatch=d;break;case r.ObjectExpression:c=!0;d=[];n(b.properties,function(b){U(b.value,a);c=c&&b.value.constant;b.value.constant||d.push.apply(d,b.value.toWatch)});b.constant=c;b.toWatch=d;break;case r.ThisExpression:b.constant=
|
||||
!1,b.toWatch=[]}}function nd(b){if(1==b.length){b=b[0].expression;var a=b.toWatch;return 1!==a.length?a:a[0]!==b?a:u}}function od(b){return b.type===r.Identifier||b.type===r.MemberExpression}function pd(b){if(1===b.body.length&&od(b.body[0].expression))return{type:r.AssignmentExpression,left:b.body[0].expression,right:{type:r.NGValueParameter},operator:"="}}function qd(b){return 0===b.body.length||1===b.body.length&&(b.body[0].expression.type===r.Literal||b.body[0].expression.type===r.ArrayExpression||
|
||||
b.body[0].expression.type===r.ObjectExpression)}function rd(b,a){this.astBuilder=b;this.$filter=a}function sd(b,a){this.astBuilder=b;this.$filter=a}function Fb(b){return"constructor"==b}function dc(b){return B(b.valueOf)?b.valueOf():Yf.call(b)}function jf(){var b=ga(),a=ga();this.$get=["$filter",function(c){function d(a,b){return null==a||null==b?a===b:"object"===typeof a&&(a=dc(a),"object"===typeof a)?!1:a===b||a!==a&&b!==b}function e(a,b,c,e,f){var g=e.inputs,h;if(1===g.length){var k=d,g=g[0];return a.$watch(function(a){var b=
|
||||
g(a);d(b,k)||(h=e(a,u,u,[b]),k=b&&dc(b));return h},b,c,f)}for(var l=[],m=[],q=0,n=g.length;q<n;q++)l[q]=d,m[q]=null;return a.$watch(function(a){for(var b=!1,c=0,f=g.length;c<f;c++){var k=g[c](a);if(b||(b=!d(k,l[c])))m[c]=k,l[c]=k&&dc(k)}b&&(h=e(a,u,u,m));return h},b,c,f)}function f(a,b,c,d){var e,f;return e=a.$watch(function(a){return d(a)},function(a,c,d){f=a;B(b)&&b.apply(this,arguments);x(a)&&d.$$postDigest(function(){x(f)&&e()})},c)}function g(a,b,c,d){function e(a){var b=!0;n(a,function(a){x(a)||
|
||||
(b=!1)});return b}var f,g;return f=a.$watch(function(a){return d(a)},function(a,c,d){g=a;B(b)&&b.call(this,a,c,d);e(a)&&d.$$postDigest(function(){e(g)&&f()})},c)}function h(a,b,c,d){var e;return e=a.$watch(function(a){return d(a)},function(a,c,d){B(b)&&b.apply(this,arguments);e()},c)}function l(a,b){if(!b)return a;var c=a.$$watchDelegate,c=c!==g&&c!==f?function(c,d,e,f){e=a(c,d,e,f);return b(e,c,d)}:function(c,d,e,f){e=a(c,d,e,f);c=b(e,c,d);return x(e)?c:e};a.$$watchDelegate&&a.$$watchDelegate!==
|
||||
e?c.$$watchDelegate=a.$$watchDelegate:b.$stateful||(c.$$watchDelegate=e,c.inputs=a.inputs?a.inputs:[a]);return c}var k=Fa().noUnsafeEval,m={csp:k,expensiveChecks:!1},q={csp:k,expensiveChecks:!0};return function(d,k,E){var n,w,p;switch(typeof d){case "string":p=d=d.trim();var r=E?a:b;n=r[p];n||(":"===d.charAt(0)&&":"===d.charAt(1)&&(w=!0,d=d.substring(2)),E=E?q:m,n=new ec(E),n=(new fc(n,c,E)).parse(d),n.constant?n.$$watchDelegate=h:w?n.$$watchDelegate=n.literal?g:f:n.inputs&&(n.$$watchDelegate=e),
|
||||
r[p]=n);return l(n,k);case "function":return l(d,k);default:return v}}}]}function lf(){this.$get=["$rootScope","$exceptionHandler",function(b,a){return td(function(a){b.$evalAsync(a)},a)}]}function mf(){this.$get=["$browser","$exceptionHandler",function(b,a){return td(function(a){b.defer(a)},a)}]}function td(b,a){function c(a,b,c){function d(b){return function(c){e||(e=!0,b.call(a,c))}}var e=!1;return[d(b),d(c)]}function d(){this.$$state={status:0}}function e(a,b){return function(c){b.call(a,c)}}
|
||||
function f(c){!c.processScheduled&&c.pending&&(c.processScheduled=!0,b(function(){var b,d,e;e=c.pending;c.processScheduled=!1;c.pending=u;for(var f=0,g=e.length;f<g;++f){d=e[f][0];b=e[f][c.status];try{B(b)?d.resolve(b(c.value)):1===c.status?d.resolve(c.value):d.reject(c.value)}catch(h){d.reject(h),a(h)}}}))}function g(){this.promise=new d;this.resolve=e(this,this.resolve);this.reject=e(this,this.reject);this.notify=e(this,this.notify)}var h=G("$q",TypeError);Q(d.prototype,{then:function(a,b,c){if(y(a)&&
|
||||
y(b)&&y(c))return this;var d=new g;this.$$state.pending=this.$$state.pending||[];this.$$state.pending.push([d,a,b,c]);0<this.$$state.status&&f(this.$$state);return d.promise},"catch":function(a){return this.then(null,a)},"finally":function(a,b){return this.then(function(b){return k(b,!0,a)},function(b){return k(b,!1,a)},b)}});Q(g.prototype,{resolve:function(a){this.promise.$$state.status||(a===this.promise?this.$$reject(h("qcycle",a)):this.$$resolve(a))},$$resolve:function(b){var d,e;e=c(this,this.$$resolve,
|
||||
this.$$reject);try{if(D(b)||B(b))d=b&&b.then;B(d)?(this.promise.$$state.status=-1,d.call(b,e[0],e[1],this.notify)):(this.promise.$$state.value=b,this.promise.$$state.status=1,f(this.promise.$$state))}catch(g){e[1](g),a(g)}},reject:function(a){this.promise.$$state.status||this.$$reject(a)},$$reject:function(a){this.promise.$$state.value=a;this.promise.$$state.status=2;f(this.promise.$$state)},notify:function(c){var d=this.promise.$$state.pending;0>=this.promise.$$state.status&&d&&d.length&&b(function(){for(var b,
|
||||
e,f=0,g=d.length;f<g;f++){e=d[f][0];b=d[f][3];try{e.notify(B(b)?b(c):c)}catch(h){a(h)}}})}});var l=function(a,b){var c=new g;b?c.resolve(a):c.reject(a);return c.promise},k=function(a,b,c){var d=null;try{B(c)&&(d=c())}catch(e){return l(e,!1)}return d&&B(d.then)?d.then(function(){return l(a,b)},function(a){return l(a,!1)}):l(a,b)},m=function(a,b,c,d){var e=new g;e.resolve(a);return e.promise.then(b,c,d)},q=function t(a){if(!B(a))throw h("norslvr",a);if(!(this instanceof t))return new t(a);var b=new g;
|
||||
a(function(a){b.resolve(a)},function(a){b.reject(a)});return b.promise};q.defer=function(){return new g};q.reject=function(a){var b=new g;b.reject(a);return b.promise};q.when=m;q.resolve=m;q.all=function(a){var b=new g,c=0,d=K(a)?[]:{};n(a,function(a,e){c++;m(a).then(function(a){d.hasOwnProperty(e)||(d[e]=a,--c||b.resolve(d))},function(a){d.hasOwnProperty(e)||b.reject(a)})});0===c&&b.resolve(d);return b.promise};return q}function vf(){this.$get=["$window","$timeout",function(b,a){var c=b.requestAnimationFrame||
|
||||
b.webkitRequestAnimationFrame,d=b.cancelAnimationFrame||b.webkitCancelAnimationFrame||b.webkitCancelRequestAnimationFrame,e=!!c,f=e?function(a){var b=c(a);return function(){d(b)}}:function(b){var c=a(b,16.66,!1);return function(){a.cancel(c)}};f.supported=e;return f}]}function kf(){function b(a){function b(){this.$$watchers=this.$$nextSibling=this.$$childHead=this.$$childTail=null;this.$$listeners={};this.$$listenerCount={};this.$$watchersCount=0;this.$id=++mb;this.$$ChildScope=null}b.prototype=a;
|
||||
return b}var a=10,c=G("$rootScope"),d=null,e=null;this.digestTtl=function(b){arguments.length&&(a=b);return a};this.$get=["$injector","$exceptionHandler","$parse","$browser",function(f,g,h,l){function k(a){a.currentScope.$$destroyed=!0}function m(){this.$id=++mb;this.$$phase=this.$parent=this.$$watchers=this.$$nextSibling=this.$$prevSibling=this.$$childHead=this.$$childTail=null;this.$root=this;this.$$destroyed=!1;this.$$listeners={};this.$$listenerCount={};this.$$watchersCount=0;this.$$isolateBindings=
|
||||
null}function q(a){if(p.$$phase)throw c("inprog",p.$$phase);p.$$phase=a}function s(a,b){do a.$$watchersCount+=b;while(a=a.$parent)}function t(a,b,c){do a.$$listenerCount[c]-=b,0===a.$$listenerCount[c]&&delete a.$$listenerCount[c];while(a=a.$parent)}function r(){}function L(){for(;M.length;)try{M.shift()()}catch(a){g(a)}e=null}function w(){null===e&&(e=l.defer(function(){p.$apply(L)}))}m.prototype={constructor:m,$new:function(a,c){var d;c=c||this;a?(d=new m,d.$root=this.$root):(this.$$ChildScope||
|
||||
(this.$$ChildScope=b(this)),d=new this.$$ChildScope);d.$parent=c;d.$$prevSibling=c.$$childTail;c.$$childHead?(c.$$childTail.$$nextSibling=d,c.$$childTail=d):c.$$childHead=c.$$childTail=d;(a||c!=this)&&d.$on("$destroy",k);return d},$watch:function(a,b,c,e){var f=h(a);if(f.$$watchDelegate)return f.$$watchDelegate(this,b,c,f,a);var g=this,k=g.$$watchers,l={fn:b,last:r,get:f,exp:e||a,eq:!!c};d=null;B(b)||(l.fn=v);k||(k=g.$$watchers=[]);k.unshift(l);s(this,1);return function(){0<=bb(k,l)&&s(g,-1);d=null}},
|
||||
$watchGroup:function(a,b){function c(){h=!1;k?(k=!1,b(e,e,g)):b(e,d,g)}var d=Array(a.length),e=Array(a.length),f=[],g=this,h=!1,k=!0;if(!a.length){var l=!0;g.$evalAsync(function(){l&&b(e,e,g)});return function(){l=!1}}if(1===a.length)return this.$watch(a[0],function(a,c,f){e[0]=a;d[0]=c;b(e,a===c?e:d,f)});n(a,function(a,b){var k=g.$watch(a,function(a,f){e[b]=a;d[b]=f;h||(h=!0,g.$evalAsync(c))});f.push(k)});return function(){for(;f.length;)f.shift()()}},$watchCollection:function(a,b){function c(a){e=
|
||||
a;var b,d,g,h;if(!y(e)){if(D(e))if(Da(e))for(f!==q&&(f=q,t=f.length=0,l++),a=e.length,t!==a&&(l++,f.length=t=a),b=0;b<a;b++)h=f[b],g=e[b],d=h!==h&&g!==g,d||h===g||(l++,f[b]=g);else{f!==s&&(f=s={},t=0,l++);a=0;for(b in e)e.hasOwnProperty(b)&&(a++,g=e[b],h=f[b],b in f?(d=h!==h&&g!==g,d||h===g||(l++,f[b]=g)):(t++,f[b]=g,l++));if(t>a)for(b in l++,f)e.hasOwnProperty(b)||(t--,delete f[b])}else f!==e&&(f=e,l++);return l}}c.$stateful=!0;var d=this,e,f,g,k=1<b.length,l=0,m=h(a,c),q=[],s={},p=!0,t=0;return this.$watch(m,
|
||||
function(){p?(p=!1,b(e,e,d)):b(e,g,d);if(k)if(D(e))if(Da(e)){g=Array(e.length);for(var a=0;a<e.length;a++)g[a]=e[a]}else for(a in g={},e)Na.call(e,a)&&(g[a]=e[a]);else g=e})},$digest:function(){var b,f,h,k,m,s,t=a,n,w=[],C,M;q("$digest");l.$$checkUrlChange();this===p&&null!==e&&(l.defer.cancel(e),L());d=null;do{s=!1;for(n=this;u.length;){try{M=u.shift(),M.scope.$eval(M.expression,M.locals)}catch(v){g(v)}d=null}a:do{if(k=n.$$watchers)for(m=k.length;m--;)try{if(b=k[m])if((f=b.get(n))!==(h=b.last)&&
|
||||
!(b.eq?ka(f,h):"number"===typeof f&&"number"===typeof h&&isNaN(f)&&isNaN(h)))s=!0,d=b,b.last=b.eq?fa(f,null):f,b.fn(f,h===r?f:h,n),5>t&&(C=4-t,w[C]||(w[C]=[]),w[C].push({msg:B(b.exp)?"fn: "+(b.exp.name||b.exp.toString()):b.exp,newVal:f,oldVal:h}));else if(b===d){s=!1;break a}}catch(y){g(y)}if(!(k=n.$$watchersCount&&n.$$childHead||n!==this&&n.$$nextSibling))for(;n!==this&&!(k=n.$$nextSibling);)n=n.$parent}while(n=k);if((s||u.length)&&!t--)throw p.$$phase=null,c("infdig",a,w);}while(s||u.length);for(p.$$phase=
|
||||
null;x.length;)try{x.shift()()}catch(z){g(z)}},$destroy:function(){if(!this.$$destroyed){var a=this.$parent;this.$broadcast("$destroy");this.$$destroyed=!0;this===p&&l.$$applicationDestroyed();s(this,-this.$$watchersCount);for(var b in this.$$listenerCount)t(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=v;this.$on=this.$watch=this.$watchGroup=function(){return v};this.$$listeners={};this.$parent=this.$$nextSibling=this.$$prevSibling=this.$$childHead=this.$$childTail=this.$root=this.$$watchers=null}},$eval:function(a,b){return h(a)(this,b)},$evalAsync:function(a,b){p.$$phase||u.length||l.defer(function(){u.length&&p.$digest()});u.push({scope:this,expression:a,locals:b})},$$postDigest:function(a){x.push(a)},
|
||||
$apply:function(a){try{q("$apply");try{return this.$eval(a)}finally{p.$$phase=null}}catch(b){g(b)}finally{try{p.$digest()}catch(c){throw g(c),c;}}},$applyAsync:function(a){function b(){c.$eval(a)}var c=this;a&&M.push(b);w()},$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,t(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;l<m;l++)if(d[l])try{d[l].apply(null,k)}catch(q){g(q)}else d.splice(l,1),l--,m--;if(f)return h.currentScope=null,h;e=e.$parent}while(e);h.currentScope=null;return h},$broadcast:function(a,b){var c=this,d=this,e={name:a,targetScope:this,preventDefault:function(){e.defaultPrevented=
|
||||
!0},defaultPrevented:!1};if(!this.$$listenerCount[a])return e;for(var f=cb([e],arguments,1),h,k;c=d;){e.currentScope=c;d=c.$$listeners[a]||[];h=0;for(k=d.length;h<k;h++)if(d[h])try{d[h].apply(null,f)}catch(l){g(l)}else d.splice(h,1),h--,k--;if(!(d=c.$$listenerCount[a]&&c.$$childHead||c!==this&&c.$$nextSibling))for(;c!==this&&!(d=c.$$nextSibling);)c=c.$parent}e.currentScope=null;return e}};var p=new m,u=p.$$asyncQueue=[],x=p.$$postDigestQueue=[],M=p.$$applyAsyncQueue=[];return p}]}function ge(){var b=
|
||||
/^\s*(https?|ftp|mailto|tel|file):/,a=/^\s*((https?|ftp|file|blob):|data:image\/)/;this.aHrefSanitizationWhitelist=function(a){return x(a)?(b=a,this):b};this.imgSrcSanitizationWhitelist=function(b){return x(b)?(a=b,this):a};this.$get=function(){return function(c,d){var e=d?a:b,f;f=Aa(c).href;return""===f||f.match(e)?c:"unsafe:"+f}}}function Zf(b){if("self"===b)return b;if(H(b)){if(-1<b.indexOf("***"))throw Ca("iwcard",b);b=ud(b).replace("\\*\\*",".*").replace("\\*","[^:/.?&;]*");return new RegExp("^"+
|
||||
b+"$")}if(Oa(b))return new RegExp("^"+b.source+"$");throw Ca("imatcher");}function vd(b){var a=[];x(b)&&n(b,function(b){a.push(Zf(b))});return a}function of(){this.SCE_CONTEXTS=oa;var b=["self"],a=[];this.resourceUrlWhitelist=function(a){arguments.length&&(b=vd(a));return b};this.resourceUrlBlacklist=function(b){arguments.length&&(a=vd(b));return a};this.$get=["$injector",function(c){function d(a,b){return"self"===a?gd(b):!!a.exec(b.href)}function e(a){var b=function(a){this.$$unwrapTrustedValue=
|
||||
function(){return a}};a&&(b.prototype=new a);b.prototype.valueOf=function(){return this.$$unwrapTrustedValue()};b.prototype.toString=function(){return this.$$unwrapTrustedValue().toString()};return b}var f=function(a){throw Ca("unsafe");};c.has("$sanitize")&&(f=c.get("$sanitize"));var g=e(),h={};h[oa.HTML]=e(g);h[oa.CSS]=e(g);h[oa.URL]=e(g);h[oa.JS]=e(g);h[oa.RESOURCE_URL]=e(h[oa.URL]);return{trustAs:function(a,b){var c=h.hasOwnProperty(a)?h[a]:null;if(!c)throw Ca("icontext",a,b);if(null===b||b===
|
||||
u||""===b)return b;if("string"!==typeof b)throw Ca("itype",a);return new c(b)},getTrusted:function(c,e){if(null===e||e===u||""===e)return e;var g=h.hasOwnProperty(c)?h[c]:null;if(g&&e instanceof g)return e.$$unwrapTrustedValue();if(c===oa.RESOURCE_URL){var g=Aa(e.toString()),q,s,t=!1;q=0;for(s=b.length;q<s;q++)if(d(b[q],g)){t=!0;break}if(t)for(q=0,s=a.length;q<s;q++)if(d(a[q],g)){t=!1;break}if(t)return e;throw Ca("insecurl",e.toString());}if(c===oa.HTML)return f(e);throw Ca("unsafe");},valueOf:function(a){return a instanceof
|
||||
g?a.$$unwrapTrustedValue():a}}}]}function nf(){var b=!0;this.enabled=function(a){arguments.length&&(b=!!a);return b};this.$get=["$parse","$sceDelegate",function(a,c){if(b&&8>Va)throw Ca("iequirks");var d=ia(oa);d.isEnabled=function(){return b};d.trustAs=c.trustAs;d.getTrusted=c.getTrusted;d.valueOf=c.valueOf;b||(d.trustAs=d.getTrusted=function(a,b){return b},d.valueOf=Za);d.parseAs=function(b,c){var e=a(c);return e.literal&&e.constant?e:a(c,function(a){return d.getTrusted(b,a)})};var e=d.parseAs,
|
||||
f=d.getTrusted,g=d.trustAs;n(oa,function(a,b){var c=I(b);d[fb("parse_as_"+c)]=function(b){return e(a,b)};d[fb("get_trusted_"+c)]=function(b){return f(a,b)};d[fb("trust_as_"+c)]=function(b){return g(a,b)}});return d}]}function pf(){this.$get=["$window","$document",function(b,a){var c={},d=Y((/android (\d+)/.exec(I((b.navigator||{}).userAgent))||[])[1]),e=/Boxee/i.test((b.navigator||{}).userAgent),f=a[0]||{},g,h=/^(Moz|webkit|ms)(?=[A-Z])/,l=f.body&&f.body.style,k=!1,m=!1;if(l){for(var q in l)if(k=
|
||||
h.exec(q)){g=k[0];g=g.substr(0,1).toUpperCase()+g.substr(1);break}g||(g="WebkitOpacity"in l&&"webkit");k=!!("transition"in l||g+"Transition"in l);m=!!("animation"in l||g+"Animation"in l);!d||k&&m||(k=H(l.webkitTransition),m=H(l.webkitAnimation))}return{history:!(!b.history||!b.history.pushState||4>d||e),hasEvent:function(a){if("input"===a&&11>=Va)return!1;if(y(c[a])){var b=f.createElement("div");c[a]="on"+a in b}return c[a]},csp:Fa(),vendorPrefix:g,transitions:k,animations:m,android:d}}]}function rf(){this.$get=
|
||||
["$templateCache","$http","$q","$sce",function(b,a,c,d){function e(f,g){e.totalPendingRequests++;H(f)&&b.get(f)||(f=d.getTrustedResourceUrl(f));var h=a.defaults&&a.defaults.transformResponse;K(h)?h=h.filter(function(a){return a!==Zb}):h===Zb&&(h=null);return a.get(f,{cache:b,transformResponse:h})["finally"](function(){e.totalPendingRequests--}).then(function(a){b.put(f,a.data);return a.data},function(a){if(!g)throw ea("tpload",f,a.status,a.statusText);return c.reject(a)})}e.totalPendingRequests=0;
|
||||
return e}]}function sf(){this.$get=["$rootScope","$browser","$location",function(b,a,c){return{findBindings:function(a,b,c){a=a.getElementsByClassName("ng-binding");var g=[];n(a,function(a){var d=aa.element(a).data("$binding");d&&n(d,function(d){c?(new RegExp("(^|\\s)"+ud(b)+"(\\s|\\||$)")).test(d)&&g.push(a):-1!=d.indexOf(b)&&g.push(a)})});return g},findModels:function(a,b,c){for(var g=["ng-","data-ng-","ng\\:"],h=0;h<g.length;++h){var l=a.querySelectorAll("["+g[h]+"model"+(c?"=":"*=")+'"'+b+'"]');
|
||||
if(l.length)return l}},getLocation:function(){return c.url()},setLocation:function(a){a!==c.url()&&(c.url(a),b.$digest())},whenStable:function(b){a.notifyWhenNoOutstandingRequests(b)}}}]}function tf(){this.$get=["$rootScope","$browser","$q","$$q","$exceptionHandler",function(b,a,c,d,e){function f(f,l,k){B(f)||(k=l,l=f,f=v);var m=xa.call(arguments,3),q=x(k)&&!k,s=(q?d:c).defer(),t=s.promise,n;n=a.defer(function(){try{s.resolve(f.apply(null,m))}catch(a){s.reject(a),e(a)}finally{delete g[t.$$timeoutId]}q||
|
||||
b.$apply()},l);t.$$timeoutId=n;g[n]=s;return t}var g={};f.cancel=function(b){return b&&b.$$timeoutId in g?(g[b.$$timeoutId].reject("canceled"),delete g[b.$$timeoutId],a.defer.cancel(b.$$timeoutId)):!1};return f}]}function Aa(b){Va&&(Z.setAttribute("href",b),b=Z.href);Z.setAttribute("href",b);return{href:Z.href,protocol:Z.protocol?Z.protocol.replace(/:$/,""):"",host:Z.host,search:Z.search?Z.search.replace(/^\?/,""):"",hash:Z.hash?Z.hash.replace(/^#/,""):"",hostname:Z.hostname,port:Z.port,pathname:"/"===
|
||||
Z.pathname.charAt(0)?Z.pathname:"/"+Z.pathname}}function gd(b){b=H(b)?Aa(b):b;return b.protocol===wd.protocol&&b.host===wd.host}function uf(){this.$get=qa(N)}function xd(b){function a(a){try{return decodeURIComponent(a)}catch(b){return a}}var c=b[0]||{},d={},e="";return function(){var b,g,h,l,k;b=c.cookie||"";if(b!==e)for(e=b,b=e.split("; "),d={},h=0;h<b.length;h++)g=b[h],l=g.indexOf("="),0<l&&(k=a(g.substring(0,l)),d[k]===u&&(d[k]=a(g.substring(l+1))));return d}}function yf(){this.$get=xd}function Jc(b){function a(c,
|
||||
d){if(D(c)){var e={};n(c,function(b,c){e[c]=a(c,b)});return e}return b.factory(c+"Filter",d)}this.register=a;this.$get=["$injector",function(a){return function(b){return a.get(b+"Filter")}}];a("currency",yd);a("date",zd);a("filter",$f);a("json",ag);a("limitTo",bg);a("lowercase",cg);a("number",Ad);a("orderBy",Bd);a("uppercase",dg)}function $f(){return function(b,a,c){if(!Da(b)){if(null==b)return b;throw G("filter")("notarray",b);}var d;switch(gc(a)){case "function":break;case "boolean":case "null":case "number":case "string":d=
|
||||
!0;case "object":a=eg(a,c,d);break;default:return b}return Array.prototype.filter.call(b,a)}}function eg(b,a,c){var d=D(b)&&"$"in b;!0===a?a=ka:B(a)||(a=function(a,b){if(y(a))return!1;if(null===a||null===b)return a===b;if(D(b)||D(a)&&!pc(a))return!1;a=I(""+a);b=I(""+b);return-1!==a.indexOf(b)});return function(e){return d&&!D(e)?La(e,b.$,a,!1):La(e,b,a,c)}}function La(b,a,c,d,e){var f=gc(b),g=gc(a);if("string"===g&&"!"===a.charAt(0))return!La(b,a.substring(1),c,d);if(K(b))return b.some(function(b){return La(b,
|
||||
a,c,d)});switch(f){case "object":var h;if(d){for(h in b)if("$"!==h.charAt(0)&&La(b[h],a,c,!0))return!0;return e?!1:La(b,a,c,!1)}if("object"===g){for(h in a)if(e=a[h],!B(e)&&!y(e)&&(f="$"===h,!La(f?b:b[h],e,c,f,f)))return!1;return!0}return c(b,a);case "function":return!1;default:return c(b,a)}}function gc(b){return null===b?"null":typeof b}function yd(b){var a=b.NUMBER_FORMATS;return function(b,d,e){y(d)&&(d=a.CURRENCY_SYM);y(e)&&(e=a.PATTERNS[1].maxFrac);return null==b?b:Cd(b,a.PATTERNS[1],a.GROUP_SEP,
|
||||
a.DECIMAL_SEP,e).replace(/\u00A4/g,d)}}function Ad(b){var a=b.NUMBER_FORMATS;return function(b,d){return null==b?b:Cd(b,a.PATTERNS[0],a.GROUP_SEP,a.DECIMAL_SEP,d)}}function Cd(b,a,c,d,e){if(D(b))return"";var f=0>b;b=Math.abs(b);var g=Infinity===b;if(!g&&!isFinite(b))return"";var h=b+"",l="",k=!1,m=[];g&&(l="\u221e");if(!g&&-1!==h.indexOf("e")){var q=h.match(/([\d\.]+)e(-?)(\d+)/);q&&"-"==q[2]&&q[3]>e+1?b=0:(l=h,k=!0)}if(g||k)0<e&&1>b&&(l=b.toFixed(e),b=parseFloat(l));else{g=(h.split(Dd)[1]||"").length;
|
||||
y(e)&&(e=Math.min(Math.max(a.minFrac,g),a.maxFrac));b=+(Math.round(+(b.toString()+"e"+e)).toString()+"e"+-e);var g=(""+b).split(Dd),h=g[0],g=g[1]||"",q=0,s=a.lgSize,t=a.gSize;if(h.length>=s+t)for(q=h.length-s,k=0;k<q;k++)0===(q-k)%t&&0!==k&&(l+=c),l+=h.charAt(k);for(k=q;k<h.length;k++)0===(h.length-k)%s&&0!==k&&(l+=c),l+=h.charAt(k);for(;g.length<e;)g+="0";e&&"0"!==e&&(l+=d+g.substr(0,e))}0===b&&(f=!1);m.push(f?a.negPre:a.posPre,l,f?a.negSuf:a.posSuf);return m.join("")}function Gb(b,a,c){var d="";
|
||||
0>b&&(d="-",b=-b);for(b=""+b;b.length<a;)b="0"+b;c&&(b=b.substr(b.length-a));return d+b}function $(b,a,c,d){c=c||0;return function(e){e=e["get"+b]();if(0<c||e>-c)e+=c;0===e&&-12==c&&(e=12);return Gb(e,a,d)}}function Hb(b,a){return function(c,d){var e=c["get"+b](),f=rb(a?"SHORT"+b:b);return d[f][e]}}function Ed(b){var a=(new Date(b,0,1)).getDay();return new Date(b,0,(4>=a?5:12)-a)}function Fd(b){return function(a){var c=Ed(a.getFullYear());a=+new Date(a.getFullYear(),a.getMonth(),a.getDate()+(4-a.getDay()))-
|
||||
+c;a=1+Math.round(a/6048E5);return Gb(a,b)}}function hc(b,a){return 0>=b.getFullYear()?a.ERAS[0]:a.ERAS[1]}function zd(b){function a(a){var b;if(b=a.match(c)){a=new Date(0);var f=0,g=0,h=b[8]?a.setUTCFullYear:a.setFullYear,l=b[8]?a.setUTCHours:a.setHours;b[9]&&(f=Y(b[9]+b[10]),g=Y(b[9]+b[11]));h.call(a,Y(b[1]),Y(b[2])-1,Y(b[3]));f=Y(b[4]||0)-f;g=Y(b[5]||0)-g;h=Y(b[6]||0);b=Math.round(1E3*parseFloat("0."+(b[7]||0)));l.call(a,f,g,h,b)}return a}var c=/^(\d{4})-?(\d\d)-?(\d\d)(?:T(\d\d)(?::?(\d\d)(?::?(\d\d)(?:\.(\d+))?)?)?(Z|([+-])(\d\d):?(\d\d))?)?$/;
|
||||
return function(c,e,f){var g="",h=[],l,k;e=e||"mediumDate";e=b.DATETIME_FORMATS[e]||e;H(c)&&(c=fg.test(c)?Y(c):a(c));X(c)&&(c=new Date(c));if(!ca(c)||!isFinite(c.getTime()))return c;for(;e;)(k=gg.exec(e))?(h=cb(h,k,1),e=h.pop()):(h.push(e),e=null);var m=c.getTimezoneOffset();f&&(m=vc(f,c.getTimezoneOffset()),c=Ob(c,f,!0));n(h,function(a){l=hg[a];g+=l?l(c,b.DATETIME_FORMATS,m):a.replace(/(^'|'$)/g,"").replace(/''/g,"'")});return g}}function ag(){return function(b,a){y(a)&&(a=2);return db(b,a)}}function bg(){return function(b,
|
||||
a,c){a=Infinity===Math.abs(Number(a))?Number(a):Y(a);if(isNaN(a))return b;X(b)&&(b=b.toString());if(!K(b)&&!H(b))return b;c=!c||isNaN(c)?0:Y(c);c=0>c&&c>=-b.length?b.length+c:c;return 0<=a?b.slice(c,c+a):0===c?b.slice(a,b.length):b.slice(Math.max(0,c+a),c)}}function Bd(b){function a(a,c){c=c?-1:1;return a.map(function(a){var d=1,h=Za;if(B(a))h=a;else if(H(a)){if("+"==a.charAt(0)||"-"==a.charAt(0))d="-"==a.charAt(0)?-1:1,a=a.substring(1);if(""!==a&&(h=b(a),h.constant))var l=h(),h=function(a){return a[l]}}return{get:h,
|
||||
descending:d*c}})}function c(a){switch(typeof a){case "number":case "boolean":case "string":return!0;default:return!1}}return function(b,e,f){if(!Da(b))return b;K(e)||(e=[e]);0===e.length&&(e=["+"]);var g=a(e,f);g.push({get:function(){return{}},descending:f?-1:1});b=Array.prototype.map.call(b,function(a,b){return{value:a,predicateValues:g.map(function(d){var e=d.get(a);d=typeof e;if(null===e)d="string",e="null";else if("string"===d)e=e.toLowerCase();else if("object"===d)a:{if("function"===typeof e.valueOf&&
|
||||
(e=e.valueOf(),c(e)))break a;if(pc(e)&&(e=e.toString(),c(e)))break a;e=b}return{value:e,type:d}})}});b.sort(function(a,b){for(var c=0,d=0,e=g.length;d<e;++d){var c=a.predicateValues[d],f=b.predicateValues[d],t=0;c.type===f.type?c.value!==f.value&&(t=c.value<f.value?-1:1):t=c.type<f.type?-1:1;if(c=t*g[d].descending)break}return c});return b=b.map(function(a){return a.value})}}function Ma(b){B(b)&&(b={link:b});b.restrict=b.restrict||"AC";return qa(b)}function Gd(b,a,c,d,e){var f=this,g=[],h=f.$$parentForm=
|
||||
b.parent().controller("form")||Ib;f.$error={};f.$$success={};f.$pending=u;f.$name=e(a.name||a.ngForm||"")(c);f.$dirty=!1;f.$pristine=!0;f.$valid=!0;f.$invalid=!1;f.$submitted=!1;h.$addControl(f);f.$rollbackViewValue=function(){n(g,function(a){a.$rollbackViewValue()})};f.$commitViewValue=function(){n(g,function(a){a.$commitViewValue()})};f.$addControl=function(a){Ta(a.$name,"input");g.push(a);a.$name&&(f[a.$name]=a)};f.$$renameControl=function(a,b){var c=a.$name;f[c]===a&&delete f[c];f[b]=a;a.$name=
|
||||
b};f.$removeControl=function(a){a.$name&&f[a.$name]===a&&delete f[a.$name];n(f.$pending,function(b,c){f.$setValidity(c,null,a)});n(f.$error,function(b,c){f.$setValidity(c,null,a)});n(f.$$success,function(b,c){f.$setValidity(c,null,a)});bb(g,a)};Hd({ctrl:this,$element:b,set:function(a,b,c){var d=a[b];d?-1===d.indexOf(c)&&d.push(c):a[b]=[c]},unset:function(a,b,c){var d=a[b];d&&(bb(d,c),0===d.length&&delete a[b])},parentForm:h,$animate:d});f.$setDirty=function(){d.removeClass(b,Xa);d.addClass(b,Jb);
|
||||
f.$dirty=!0;f.$pristine=!1;h.$setDirty()};f.$setPristine=function(){d.setClass(b,Xa,Jb+" ng-submitted");f.$dirty=!1;f.$pristine=!0;f.$submitted=!1;n(g,function(a){a.$setPristine()})};f.$setUntouched=function(){n(g,function(a){a.$setUntouched()})};f.$setSubmitted=function(){d.addClass(b,"ng-submitted");f.$submitted=!0;h.$setSubmitted()}}function ic(b){b.$formatters.push(function(a){return b.$isEmpty(a)?a:a.toString()})}function ib(b,a,c,d,e,f){var g=I(a[0].type);if(!e.android){var h=!1;a.on("compositionstart",
|
||||
function(a){h=!0});a.on("compositionend",function(){h=!1;l()})}var l=function(b){k&&(f.defer.cancel(k),k=null);if(!h){var e=a.val();b=b&&b.type;"password"===g||c.ngTrim&&"false"===c.ngTrim||(e=T(e));(d.$viewValue!==e||""===e&&d.$$hasNativeValidators)&&d.$setViewValue(e,b)}};if(e.hasEvent("input"))a.on("input",l);else{var k,m=function(a,b,c){k||(k=f.defer(function(){k=null;b&&b.value===c||l(a)}))};a.on("keydown",function(a){var b=a.keyCode;91===b||15<b&&19>b||37<=b&&40>=b||m(a,this,this.value)});if(e.hasEvent("paste"))a.on("paste cut",
|
||||
m)}a.on("change",l);d.$render=function(){var b=d.$isEmpty(d.$viewValue)?"":d.$viewValue;a.val()!==b&&a.val(b)}}function Kb(b,a){return function(c,d){var e,f;if(ca(c))return c;if(H(c)){'"'==c.charAt(0)&&'"'==c.charAt(c.length-1)&&(c=c.substring(1,c.length-1));if(ig.test(c))return new Date(c);b.lastIndex=0;if(e=b.exec(c))return e.shift(),f=d?{yyyy:d.getFullYear(),MM:d.getMonth()+1,dd:d.getDate(),HH:d.getHours(),mm:d.getMinutes(),ss:d.getSeconds(),sss:d.getMilliseconds()/1E3}:{yyyy:1970,MM:1,dd:1,HH:0,
|
||||
mm:0,ss:0,sss:0},n(e,function(b,c){c<a.length&&(f[a[c]]=+b)}),new Date(f.yyyy,f.MM-1,f.dd,f.HH,f.mm,f.ss||0,1E3*f.sss||0)}return NaN}}function jb(b,a,c,d){return function(e,f,g,h,l,k,m){function q(a){return a&&!(a.getTime&&a.getTime()!==a.getTime())}function s(a){return x(a)?ca(a)?a:c(a):u}Id(e,f,g,h);ib(e,f,g,h,l,k);var t=h&&h.$options&&h.$options.timezone,n;h.$$parserName=b;h.$parsers.push(function(b){return h.$isEmpty(b)?null:a.test(b)?(b=c(b,n),t&&(b=Ob(b,t)),b):u});h.$formatters.push(function(a){if(a&&
|
||||
!ca(a))throw kb("datefmt",a);if(q(a))return(n=a)&&t&&(n=Ob(n,t,!0)),m("date")(a,d,t);n=null;return""});if(x(g.min)||g.ngMin){var r;h.$validators.min=function(a){return!q(a)||y(r)||c(a)>=r};g.$observe("min",function(a){r=s(a);h.$validate()})}if(x(g.max)||g.ngMax){var w;h.$validators.max=function(a){return!q(a)||y(w)||c(a)<=w};g.$observe("max",function(a){w=s(a);h.$validate()})}}}function Id(b,a,c,d){(d.$$hasNativeValidators=D(a[0].validity))&&d.$parsers.push(function(b){var c=a.prop("validity")||{};
|
||||
return c.badInput&&!c.typeMismatch?u:b})}function Jd(b,a,c,d,e){if(x(d)){b=b(d);if(!b.constant)throw kb("constexpr",c,d);return b(a)}return e}function jc(b,a){b="ngClass"+b;return["$animate",function(c){function d(a,b){var c=[],d=0;a:for(;d<a.length;d++){for(var e=a[d],m=0;m<b.length;m++)if(e==b[m])continue a;c.push(e)}return c}function e(a){var b=[];return K(a)?(n(a,function(a){b=b.concat(e(a))}),b):H(a)?a.split(" "):D(a)?(n(a,function(a,c){a&&(b=b.concat(c.split(" ")))}),b):a}return{restrict:"AC",
|
||||
link:function(f,g,h){function l(a,b){var c=g.data("$classCounts")||ga(),d=[];n(a,function(a){if(0<b||c[a])c[a]=(c[a]||0)+b,c[a]===+(0<b)&&d.push(a)});g.data("$classCounts",c);return d.join(" ")}function k(b){if(!0===a||f.$index%2===a){var k=e(b||[]);if(!m){var n=l(k,1);h.$addClass(n)}else if(!ka(b,m)){var r=e(m),n=d(k,r),k=d(r,k),n=l(n,1),k=l(k,-1);n&&n.length&&c.addClass(g,n);k&&k.length&&c.removeClass(g,k)}}m=ia(b)}var m;f.$watch(h[b],k,!0);h.$observe("class",function(a){k(f.$eval(h[b]))});"ngClass"!==
|
||||
b&&f.$watch("$index",function(c,d){var g=c&1;if(g!==(d&1)){var k=e(f.$eval(h[b]));g===a?(g=l(k,1),h.$addClass(g)):(g=l(k,-1),h.$removeClass(g))}})}}}]}function Hd(b){function a(a,b){b&&!f[a]?(k.addClass(e,a),f[a]=!0):!b&&f[a]&&(k.removeClass(e,a),f[a]=!1)}function c(b,c){b=b?"-"+zc(b,"-"):"";a(lb+b,!0===c);a(Kd+b,!1===c)}var d=b.ctrl,e=b.$element,f={},g=b.set,h=b.unset,l=b.parentForm,k=b.$animate;f[Kd]=!(f[lb]=e.hasClass(lb));d.$setValidity=function(b,e,f){e===u?(d.$pending||(d.$pending={}),g(d.$pending,
|
||||
b,f)):(d.$pending&&h(d.$pending,b,f),Ld(d.$pending)&&(d.$pending=u));ab(e)?e?(h(d.$error,b,f),g(d.$$success,b,f)):(g(d.$error,b,f),h(d.$$success,b,f)):(h(d.$error,b,f),h(d.$$success,b,f));d.$pending?(a(Md,!0),d.$valid=d.$invalid=u,c("",null)):(a(Md,!1),d.$valid=Ld(d.$error),d.$invalid=!d.$valid,c("",d.$valid));e=d.$pending&&d.$pending[b]?u:d.$error[b]?!1:d.$$success[b]?!0:null;c(b,e);l.$setValidity(b,e,d)}}function Ld(b){if(b)for(var a in b)if(b.hasOwnProperty(a))return!1;return!0}var jg=/^\/(.+)\/([a-z]*)$/,
|
||||
I=function(b){return H(b)?b.toLowerCase():b},Na=Object.prototype.hasOwnProperty,rb=function(b){return H(b)?b.toUpperCase():b},Va,z,la,xa=[].slice,Mf=[].splice,kg=[].push,sa=Object.prototype.toString,qc=Object.getPrototypeOf,Ea=G("ng"),aa=N.angular||(N.angular={}),Rb,mb=0;Va=W.documentMode;v.$inject=[];Za.$inject=[];var K=Array.isArray,sc=/^\[object (Uint8(Clamped)?)|(Uint16)|(Uint32)|(Int8)|(Int16)|(Int32)|(Float(32)|(64))Array\]$/,T=function(b){return H(b)?b.trim():b},ud=function(b){return b.replace(/([-()\[\]{}+?*.$\^|,:#<!\\])/g,
|
||||
"\\$1").replace(/\x08/g,"\\x08")},Fa=function(){if(!x(Fa.rules)){var b=W.querySelector("[ng-csp]")||W.querySelector("[data-ng-csp]");if(b){var a=b.getAttribute("ng-csp")||b.getAttribute("data-ng-csp");Fa.rules={noUnsafeEval:!a||-1!==a.indexOf("no-unsafe-eval"),noInlineStyle:!a||-1!==a.indexOf("no-inline-style")}}else{b=Fa;try{new Function(""),a=!1}catch(c){a=!0}b.rules={noUnsafeEval:a,noInlineStyle:!1}}}return Fa.rules},ob=function(){if(x(ob.name_))return ob.name_;var b,a,c=Qa.length,d,e;for(a=0;a<
|
||||
c;++a)if(d=Qa[a],b=W.querySelector("["+d.replace(":","\\:")+"jq]")){e=b.getAttribute(d+"jq");break}return ob.name_=e},Qa=["ng-","data-ng-","ng:","x-ng-"],be=/[A-Z]/g,Ac=!1,Qb,pa=1,Pa=3,fe={full:"1.4.5",major:1,minor:4,dot:5,codeName:"permanent-internship"};R.expando="ng339";var gb=R.cache={},Ef=1;R._data=function(b){return this.cache[b[this.expando]]||{}};var zf=/([\:\-\_]+(.))/g,Af=/^moz([A-Z])/,lg={mouseleave:"mouseout",mouseenter:"mouseover"},Tb=G("jqLite"),Df=/^<(\w+)\s*\/?>(?:<\/\1>|)$/,Sb=/<|&#?\w+;/,
|
||||
Bf=/<([\w:]+)/,Cf=/<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:]+)[^>]*)\/>/gi,na={option:[1,'<select multiple="multiple">',"</select>"],thead:[1,"<table>","</table>"],col:[2,"<table><colgroup>","</colgroup></table>"],tr:[2,"<table><tbody>","</tbody></table>"],td:[3,"<table><tbody><tr>","</tr></tbody></table>"],_default:[0,"",""]};na.optgroup=na.option;na.tbody=na.tfoot=na.colgroup=na.caption=na.thead;na.th=na.td;var Ra=R.prototype={ready:function(b){function a(){c||(c=!0,b())}var c=
|
||||
!1;"complete"===W.readyState?setTimeout(a):(this.on("DOMContentLoaded",a),R(N).on("load",a))},toString:function(){var b=[];n(this,function(a){b.push(""+a)});return"["+b.join(", ")+"]"},eq:function(b){return 0<=b?z(this[b]):z(this[this.length+b])},length:0,push:kg,sort:[].sort,splice:[].splice},Ab={};n("multiple selected checked disabled readOnly required open".split(" "),function(b){Ab[I(b)]=b});var Rc={};n("input select option textarea button form details".split(" "),function(b){Rc[b]=!0});var Sc=
|
||||
{ngMinlength:"minlength",ngMaxlength:"maxlength",ngMin:"min",ngMax:"max",ngPattern:"pattern"};n({data:Vb,removeData:ub,hasData:function(b){for(var a in gb[b.ng339])return!0;return!1}},function(b,a){R[a]=b});n({data:Vb,inheritedData:zb,scope:function(b){return z.data(b,"$scope")||zb(b.parentNode||b,["$isolateScope","$scope"])},isolateScope:function(b){return z.data(b,"$isolateScope")||z.data(b,"$isolateScopeNoTemplate")},controller:Oc,injector:function(b){return zb(b,"$injector")},removeAttr:function(b,
|
||||
a){b.removeAttribute(a)},hasClass:wb,css:function(b,a,c){a=fb(a);if(x(c))b.style[a]=c;else return b.style[a]},attr:function(b,a,c){var d=b.nodeType;if(d!==Pa&&2!==d&&8!==d)if(d=I(a),Ab[d])if(x(c))c?(b[a]=!0,b.setAttribute(a,d)):(b[a]=!1,b.removeAttribute(d));else return b[a]||(b.attributes.getNamedItem(a)||v).specified?d:u;else if(x(c))b.setAttribute(a,c);else if(b.getAttribute)return b=b.getAttribute(a,2),null===b?u:b},prop:function(b,a,c){if(x(c))b[a]=c;else return b[a]},text:function(){function b(a,
|
||||
b){if(y(b)){var d=a.nodeType;return d===pa||d===Pa?a.textContent:""}a.textContent=b}b.$dv="";return b}(),val:function(b,a){if(y(a)){if(b.multiple&&"select"===ta(b)){var c=[];n(b.options,function(a){a.selected&&c.push(a.value||a.text)});return 0===c.length?null:c}return b.value}b.value=a},html:function(b,a){if(y(a))return b.innerHTML;tb(b,!0);b.innerHTML=a},empty:Pc},function(b,a){R.prototype[a]=function(a,d){var e,f,g=this.length;if(b!==Pc&&(2==b.length&&b!==wb&&b!==Oc?a:d)===u){if(D(a)){for(e=0;e<
|
||||
g;e++)if(b===Vb)b(this[e],a);else for(f in a)b(this[e],f,a[f]);return this}e=b.$dv;g=e===u?Math.min(g,1):g;for(f=0;f<g;f++){var h=b(this[f],a,d);e=e?e+h:h}return e}for(e=0;e<g;e++)b(this[e],a,d);return this}});n({removeData:ub,on:function a(c,d,e,f){if(x(f))throw Tb("onargs");if(Kc(c)){var g=vb(c,!0);f=g.events;var h=g.handle;h||(h=g.handle=Hf(c,f));for(var g=0<=d.indexOf(" ")?d.split(" "):[d],l=g.length;l--;){d=g[l];var k=f[d];k||(f[d]=[],"mouseenter"===d||"mouseleave"===d?a(c,lg[d],function(a){var c=
|
||||
a.relatedTarget;c&&(c===this||this.contains(c))||h(a,d)}):"$destroy"!==d&&c.addEventListener(d,h,!1),k=f[d]);k.push(e)}}},off:Nc,one:function(a,c,d){a=z(a);a.on(c,function f(){a.off(c,d);a.off(c,f)});a.on(c,d)},replaceWith:function(a,c){var d,e=a.parentNode;tb(a);n(new R(c),function(c){d?e.insertBefore(c,d.nextSibling):e.replaceChild(c,a);d=c})},children:function(a){var c=[];n(a.childNodes,function(a){a.nodeType===pa&&c.push(a)});return c},contents:function(a){return a.contentDocument||a.childNodes||
|
||||
[]},append:function(a,c){var d=a.nodeType;if(d===pa||11===d){c=new R(c);for(var d=0,e=c.length;d<e;d++)a.appendChild(c[d])}},prepend:function(a,c){if(a.nodeType===pa){var d=a.firstChild;n(new R(c),function(c){a.insertBefore(c,d)})}},wrap:function(a,c){c=z(c).eq(0).clone()[0];var d=a.parentNode;d&&d.replaceChild(c,a);c.appendChild(a)},remove:Wb,detach:function(a){Wb(a,!0)},after:function(a,c){var d=a,e=a.parentNode;c=new R(c);for(var f=0,g=c.length;f<g;f++){var h=c[f];e.insertBefore(h,d.nextSibling);
|
||||
d=h}},addClass:yb,removeClass:xb,toggleClass:function(a,c,d){c&&n(c.split(" "),function(c){var f=d;y(f)&&(f=!wb(a,c));(f?yb:xb)(a,c)})},parent:function(a){return(a=a.parentNode)&&11!==a.nodeType?a:null},next:function(a){return a.nextElementSibling},find:function(a,c){return a.getElementsByTagName?a.getElementsByTagName(c):[]},clone:Ub,triggerHandler:function(a,c,d){var e,f,g=c.type||c,h=vb(a);if(h=(h=h&&h.events)&&h[g])e={preventDefault:function(){this.defaultPrevented=!0},isDefaultPrevented:function(){return!0===
|
||||
this.defaultPrevented},stopImmediatePropagation:function(){this.immediatePropagationStopped=!0},isImmediatePropagationStopped:function(){return!0===this.immediatePropagationStopped},stopPropagation:v,type:g,target:a},c.type&&(e=Q(e,c)),c=ia(h),f=d?[e].concat(d):[e],n(c,function(c){e.isImmediatePropagationStopped()||c.apply(a,f)})}},function(a,c){R.prototype[c]=function(c,e,f){for(var g,h=0,l=this.length;h<l;h++)y(g)?(g=a(this[h],c,e,f),x(g)&&(g=z(g))):Mc(g,a(this[h],c,e,f));return x(g)?g:this};R.prototype.bind=
|
||||
R.prototype.on;R.prototype.unbind=R.prototype.off});Ua.prototype={put:function(a,c){this[Ga(a,this.nextUid)]=c},get:function(a){return this[Ga(a,this.nextUid)]},remove:function(a){var c=this[a=Ga(a,this.nextUid)];delete this[a];return c}};var xf=[function(){this.$get=[function(){return Ua}]}],Uc=/^[^\(]*\(\s*([^\)]*)\)/m,mg=/,/,ng=/^\s*(_?)(\S+?)\1\s*$/,Tc=/((\/\/.*$)|(\/\*[\s\S]*?\*\/))/mg,Ha=G("$injector");eb.$$annotate=function(a,c,d){var e;if("function"===typeof a){if(!(e=a.$inject)){e=[];if(a.length){if(c)throw H(d)&&
|
||||
d||(d=a.name||If(a)),Ha("strictdi",d);c=a.toString().replace(Tc,"");c=c.match(Uc);n(c[1].split(mg),function(a){a.replace(ng,function(a,c,d){e.push(d)})})}a.$inject=e}}else K(a)?(c=a.length-1,Sa(a[c],"fn"),e=a.slice(0,c)):Sa(a,"fn",!0);return e};var Nd=G("$animate"),Ue=function(){this.$get=["$q","$$rAF",function(a,c){function d(){}d.all=v;d.chain=v;d.prototype={end:v,cancel:v,resume:v,pause:v,complete:v,then:function(d,f){return a(function(a){c(function(){a()})}).then(d,f)}};return d}]},Te=function(){var a=
|
||||
new Ua,c=[];this.$get=["$$AnimateRunner","$rootScope",function(d,e){function f(d,f,l){var k=a.get(d);k||(a.put(d,k={}),c.push(d));d=function(a,c){var d=!1;a&&(a=H(a)?a.split(" "):K(a)?a:[],n(a,function(a){a&&(d=!0,k[a]=c)}));return d};f=d(f,!0);l=d(l,!1);!f&&!l||1<c.length||e.$$postDigest(function(){n(c,function(c){var d=a.get(c);if(d){var e=Jf(c.attr("class")),f="",g="";n(d,function(a,c){a!==!!e[c]&&(a?f+=(f.length?" ":"")+c:g+=(g.length?" ":"")+c)});n(c,function(a){f&&yb(a,f);g&&xb(a,g)});a.remove(c)}});
|
||||
c.length=0})}return{enabled:v,on:v,off:v,pin:v,push:function(a,c,e,k){k&&k();e=e||{};e.from&&a.css(e.from);e.to&&a.css(e.to);(e.addClass||e.removeClass)&&f(a,e.addClass,e.removeClass);return new d}}}]},Re=["$provide",function(a){var c=this;this.$$registeredAnimations=Object.create(null);this.register=function(d,e){if(d&&"."!==d.charAt(0))throw Nd("notcsel",d);var f=d+"-animation";c.$$registeredAnimations[d.substr(1)]=f;a.factory(f,e)};this.classNameFilter=function(a){if(1===arguments.length&&(this.$$classNameFilter=
|
||||
a instanceof RegExp?a:null)&&/(\s+|\/)ng-animate(\s+|\/)/.test(this.$$classNameFilter.toString()))throw Nd("nongcls","ng-animate");return this.$$classNameFilter};this.$get=["$$animateQueue",function(a){function c(a,d,e){if(e){var l;a:{for(l=0;l<e.length;l++){var k=e[l];if(1===k.nodeType){l=k;break a}}l=void 0}!l||l.parentNode||l.previousElementSibling||(e=null)}e?e.after(a):d.prepend(a)}return{on:a.on,off:a.off,pin:a.pin,enabled:a.enabled,cancel:function(a){a.end&&a.end()},enter:function(f,g,h,l){g=
|
||||
g&&z(g);h=h&&z(h);g=g||h.parent();c(f,g,h);return a.push(f,"enter",Ia(l))},move:function(f,g,h,l){g=g&&z(g);h=h&&z(h);g=g||h.parent();c(f,g,h);return a.push(f,"move",Ia(l))},leave:function(c,e){return a.push(c,"leave",Ia(e),function(){c.remove()})},addClass:function(c,e,h){h=Ia(h);h.addClass=hb(h.addclass,e);return a.push(c,"addClass",h)},removeClass:function(c,e,h){h=Ia(h);h.removeClass=hb(h.removeClass,e);return a.push(c,"removeClass",h)},setClass:function(c,e,h,l){l=Ia(l);l.addClass=hb(l.addClass,
|
||||
e);l.removeClass=hb(l.removeClass,h);return a.push(c,"setClass",l)},animate:function(c,e,h,l,k){k=Ia(k);k.from=k.from?Q(k.from,e):e;k.to=k.to?Q(k.to,h):h;k.tempClasses=hb(k.tempClasses,l||"ng-inline-animate");return a.push(c,"animate",k)}}}]}],Se=function(){this.$get=["$$rAF","$q",function(a,c){var d=function(){};d.prototype={done:function(a){this.defer&&this.defer[!0===a?"reject":"resolve"]()},end:function(){this.done()},cancel:function(){this.done(!0)},getPromise:function(){this.defer||(this.defer=
|
||||
c.defer());return this.defer.promise},then:function(a,c){return this.getPromise().then(a,c)},"catch":function(a){return this.getPromise()["catch"](a)},"finally":function(a){return this.getPromise()["finally"](a)}};return function(c,f){function g(){a(function(){f.addClass&&(c.addClass(f.addClass),f.addClass=null);f.removeClass&&(c.removeClass(f.removeClass),f.removeClass=null);f.to&&(c.css(f.to),f.to=null);h||l.done();h=!0});return l}f.from&&(c.css(f.from),f.from=null);var h,l=new d;return{start:g,
|
||||
end:g}}}]},ea=G("$compile");Cc.$inject=["$provide","$$sanitizeUriProvider"];var Xc=/^((?:x|data)[\:\-_])/i,Nf=G("$controller"),Vc=/^(\S+)(\s+as\s+(\w+))?$/,$e=function(){this.$get=["$document",function(a){return function(c){c?!c.nodeType&&c instanceof z&&(c=c[0]):c=a[0].body;return c.offsetWidth+1}}]},bd="application/json",$b={"Content-Type":bd+";charset=utf-8"},Pf=/^\[|^\{(?!\{)/,Qf={"[":/]$/,"{":/}$/},Of=/^\)\]\}',?\n/,og=G("$http"),fd=function(a){return function(){throw og("legacy",a);}},Ka=aa.$interpolateMinErr=
|
||||
G("$interpolate");Ka.throwNoconcat=function(a){throw Ka("noconcat",a);};Ka.interr=function(a,c){return Ka("interr",a,c.toString())};var pg=/^([^\?#]*)(\?([^#]*))?(#(.*))?$/,Tf={http:80,https:443,ftp:21},Db=G("$location"),qg={$$html5:!1,$$replace:!1,absUrl:Eb("$$absUrl"),url:function(a){if(y(a))return this.$$url;var c=pg.exec(a);(c[1]||""===a)&&this.path(decodeURIComponent(c[1]));(c[2]||c[1]||""===a)&&this.search(c[3]||"");this.hash(c[5]||"");return this},protocol:Eb("$$protocol"),host:Eb("$$host"),
|
||||
port:Eb("$$port"),path:kd("$$path",function(a){a=null!==a?a.toString():"";return"/"==a.charAt(0)?a:"/"+a}),search:function(a,c){switch(arguments.length){case 0:return this.$$search;case 1:if(H(a)||X(a))a=a.toString(),this.$$search=xc(a);else if(D(a))a=fa(a,{}),n(a,function(c,e){null==c&&delete a[e]}),this.$$search=a;else throw Db("isrcharg");break;default:y(c)||null===c?delete this.$$search[a]:this.$$search[a]=c}this.$$compose();return this},hash:kd("$$hash",function(a){return null!==a?a.toString():
|
||||
""}),replace:function(){this.$$replace=!0;return this}};n([jd,cc,bc],function(a){a.prototype=Object.create(qg);a.prototype.state=function(c){if(!arguments.length)return this.$$state;if(a!==bc||!this.$$html5)throw Db("nostate");this.$$state=y(c)?null:c;return this}});var da=G("$parse"),Uf=Function.prototype.call,Vf=Function.prototype.apply,Wf=Function.prototype.bind,Lb=ga();n("+ - * / % === !== == != < > <= >= && || ! = |".split(" "),function(a){Lb[a]=!0});var rg={n:"\n",f:"\f",r:"\r",t:"\t",v:"\v",
|
||||
"'":"'",'"':'"'},ec=function(a){this.options=a};ec.prototype={constructor:ec,lex:function(a){this.text=a;this.index=0;for(this.tokens=[];this.index<this.text.length;)if(a=this.text.charAt(this.index),'"'===a||"'"===a)this.readString(a);else if(this.isNumber(a)||"."===a&&this.isNumber(this.peek()))this.readNumber();else if(this.isIdent(a))this.readIdent();else if(this.is(a,"(){}[].,;:?"))this.tokens.push({index:this.index,text:a}),this.index++;else if(this.isWhitespace(a))this.index++;else{var c=a+
|
||||
this.peek(),d=c+this.peek(2),e=Lb[c],f=Lb[d];Lb[a]||e||f?(a=f?d:e?c:a,this.tokens.push({index:this.index,text:a,operator:!0}),this.index+=a.length):this.throwError("Unexpected next character ",this.index,this.index+1)}return this.tokens},is:function(a,c){return-1!==c.indexOf(a)},peek:function(a){a=a||1;return this.index+a<this.text.length?this.text.charAt(this.index+a):!1},isNumber:function(a){return"0"<=a&&"9">=a&&"string"===typeof a},isWhitespace:function(a){return" "===a||"\r"===a||"\t"===a||"\n"===
|
||||
a||"\v"===a||"\u00a0"===a},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,c,d){d=d||this.index;c=x(c)?"s "+c+"-"+this.index+" ["+this.text.substring(c,d)+"]":" "+d;throw da("lexerr",a,c,this.text);},readNumber:function(){for(var a="",c=this.index;this.index<this.text.length;){var d=I(this.text.charAt(this.index));if("."==d||this.isNumber(d))a+=d;else{var e=this.peek();if("e"==d&&
|
||||
this.isExpOperator(e))a+=d;else if(this.isExpOperator(d)&&e&&this.isNumber(e)&&"e"==a.charAt(a.length-1))a+=d;else if(!this.isExpOperator(d)||e&&this.isNumber(e)||"e"!=a.charAt(a.length-1))break;else this.throwError("Invalid exponent")}this.index++}this.tokens.push({index:c,text:a,constant:!0,value:Number(a)})},readIdent:function(){for(var a=this.index;this.index<this.text.length;){var c=this.text.charAt(this.index);if(!this.isIdent(c)&&!this.isNumber(c))break;this.index++}this.tokens.push({index:a,
|
||||
text:this.text.slice(a,this.index),identifier:!0})},readString:function(a){var c=this.index;this.index++;for(var d="",e=a,f=!1;this.index<this.text.length;){var g=this.text.charAt(this.index),e=e+g;if(f)"u"===g?(f=this.text.substring(this.index+1,this.index+5),f.match(/[\da-f]{4}/i)||this.throwError("Invalid unicode escape [\\u"+f+"]"),this.index+=4,d+=String.fromCharCode(parseInt(f,16))):d+=rg[g]||g,f=!1;else if("\\"===g)f=!0;else{if(g===a){this.index++;this.tokens.push({index:c,text:e,constant:!0,
|
||||
value:d});return}d+=g}this.index++}this.throwError("Unterminated quote",c)}};var r=function(a,c){this.lexer=a;this.options=c};r.Program="Program";r.ExpressionStatement="ExpressionStatement";r.AssignmentExpression="AssignmentExpression";r.ConditionalExpression="ConditionalExpression";r.LogicalExpression="LogicalExpression";r.BinaryExpression="BinaryExpression";r.UnaryExpression="UnaryExpression";r.CallExpression="CallExpression";r.MemberExpression="MemberExpression";r.Identifier="Identifier";r.Literal=
|
||||
"Literal";r.ArrayExpression="ArrayExpression";r.Property="Property";r.ObjectExpression="ObjectExpression";r.ThisExpression="ThisExpression";r.NGValueParameter="NGValueParameter";r.prototype={ast:function(a){this.text=a;this.tokens=this.lexer.lex(a);a=this.program();0!==this.tokens.length&&this.throwError("is an unexpected token",this.tokens[0]);return a},program:function(){for(var a=[];;)if(0<this.tokens.length&&!this.peek("}",")",";","]")&&a.push(this.expressionStatement()),!this.expect(";"))return{type:r.Program,
|
||||
body:a}},expressionStatement:function(){return{type:r.ExpressionStatement,expression:this.filterChain()}},filterChain:function(){for(var a=this.expression();this.expect("|");)a=this.filter(a);return a},expression:function(){return this.assignment()},assignment:function(){var a=this.ternary();this.expect("=")&&(a={type:r.AssignmentExpression,left:a,right:this.assignment(),operator:"="});return a},ternary:function(){var a=this.logicalOR(),c,d;return this.expect("?")&&(c=this.expression(),this.consume(":"))?
|
||||
(d=this.expression(),{type:r.ConditionalExpression,test:a,alternate:c,consequent:d}):a},logicalOR:function(){for(var a=this.logicalAND();this.expect("||");)a={type:r.LogicalExpression,operator:"||",left:a,right:this.logicalAND()};return a},logicalAND:function(){for(var a=this.equality();this.expect("&&");)a={type:r.LogicalExpression,operator:"&&",left:a,right:this.equality()};return a},equality:function(){for(var a=this.relational(),c;c=this.expect("==","!=","===","!==");)a={type:r.BinaryExpression,
|
||||
operator:c.text,left:a,right:this.relational()};return a},relational:function(){for(var a=this.additive(),c;c=this.expect("<",">","<=",">=");)a={type:r.BinaryExpression,operator:c.text,left:a,right:this.additive()};return a},additive:function(){for(var a=this.multiplicative(),c;c=this.expect("+","-");)a={type:r.BinaryExpression,operator:c.text,left:a,right:this.multiplicative()};return a},multiplicative:function(){for(var a=this.unary(),c;c=this.expect("*","/","%");)a={type:r.BinaryExpression,operator:c.text,
|
||||
left:a,right:this.unary()};return a},unary:function(){var a;return(a=this.expect("+","-","!"))?{type:r.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=fa(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 c;c=this.expect("(","[",".");)"("===c.text?(a={type:r.CallExpression,callee:a,arguments:this.parseArguments()},this.consume(")")):"["===c.text?(a={type:r.MemberExpression,object:a,property:this.expression(),computed:!0},this.consume("]")):"."===c.text?a={type:r.MemberExpression,object:a,property:this.identifier(),computed:!1}:this.throwError("IMPOSSIBLE");return a},filter:function(a){a=[a];for(var c={type:r.CallExpression,callee:this.identifier(),
|
||||
arguments:a,filter:!0};this.expect(":");)a.push(this.expression());return c},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:r.Identifier,name:a.text}},constant:function(){return{type:r.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:r.ArrayExpression,elements:a}},object:function(){var a=[],c;if("}"!==this.peekToken().text){do{if(this.peek("}"))break;c={type:r.Property,kind:"init"};this.peek().constant?c.key=this.constant():this.peek().identifier?c.key=this.identifier():this.throwError("invalid key",this.peek());this.consume(":");c.value=this.expression();a.push(c)}while(this.expect(","))}this.consume("}");return{type:r.ObjectExpression,properties:a}},
|
||||
throwError:function(a,c){throw da("syntax",c.text,a,c.index+1,this.text,this.text.substring(c.index));},consume:function(a){if(0===this.tokens.length)throw da("ueoe",this.text);var c=this.expect(a);c||this.throwError("is unexpected, expecting ["+a+"]",this.peek());return c},peekToken:function(){if(0===this.tokens.length)throw da("ueoe",this.text);return this.tokens[0]},peek:function(a,c,d,e){return this.peekAhead(0,a,c,d,e)},peekAhead:function(a,c,d,e,f){if(this.tokens.length>a){a=this.tokens[a];
|
||||
var g=a.text;if(g===c||g===d||g===e||g===f||!(c||d||e||f))return a}return!1},expect:function(a,c,d,e){return(a=this.peek(a,c,d,e))?(this.tokens.shift(),a):!1},constants:{"true":{type:r.Literal,value:!0},"false":{type:r.Literal,value:!1},"null":{type:r.Literal,value:null},undefined:{type:r.Literal,value:u},"this":{type:r.ThisExpression}}};rd.prototype={compile:function(a,c){var d=this,e=this.astBuilder.ast(a);this.state={nextId:0,filters:{},expensiveChecks:c,fn:{vars:[],body:[],own:{}},assign:{vars:[],
|
||||
body:[],own:{}},inputs:[]};U(e,d.$filter);var f="",g;this.stage="assign";if(g=pd(e))this.state.computing="assign",f=this.nextId(),this.recurse(g,f),f="fn.assign="+this.generateFunction("assign","s,v,l");g=nd(e.body);d.stage="inputs";n(g,function(a,c){var e="fn"+c;d.state[e]={vars:[],body:[],own:{}};d.state.computing=e;var f=d.nextId();d.recurse(a,f);d.return_(f);d.state.inputs.push(e);a.watchId=c});this.state.computing="fn";this.stage="main";this.recurse(e);f='"'+this.USE+" "+this.STRICT+'";\n'+this.filterPrefix()+
|
||||
"var fn="+this.generateFunction("fn","s,l,a,i")+f+this.watchFns()+"return fn;";f=(new Function("$filter","ensureSafeMemberName","ensureSafeObject","ensureSafeFunction","ifDefined","plus","text",f))(this.$filter,Wa,Ba,ld,Xf,md,a);this.state=this.stage=u;f.literal=qd(e);f.constant=e.constant;return f},USE:"use",STRICT:"strict",watchFns:function(){var a=[],c=this.state.inputs,d=this;n(c,function(c){a.push("var "+c+"="+d.generateFunction(c,"s"))});c.length&&a.push("fn.inputs=["+c.join(",")+"];");return a.join("")},
|
||||
generateFunction:function(a,c){return"function("+c+"){"+this.varsPrefix(a)+this.body(a)+"};"},filterPrefix:function(){var a=[],c=this;n(this.state.filters,function(d,e){a.push(d+"=$filter("+c.escape(e)+")")});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,c,d,e,f,g){var h,l,k=this,m,q;e=e||v;if(!g&&x(a.watchId))c=c||this.nextId(),this.if_("i",
|
||||
this.lazyAssign(c,this.computedMember("i",a.watchId)),this.lazyRecurse(a,c,d,e,f,!0));else switch(a.type){case r.Program:n(a.body,function(c,d){k.recurse(c.expression,u,u,function(a){l=a});d!==a.body.length-1?k.current().body.push(l,";"):k.return_(l)});break;case r.Literal:q=this.escape(a.value);this.assign(c,q);e(q);break;case r.UnaryExpression:this.recurse(a.argument,u,u,function(a){l=a});q=a.operator+"("+this.ifDefined(l,0)+")";this.assign(c,q);e(q);break;case r.BinaryExpression:this.recurse(a.left,
|
||||
u,u,function(a){h=a});this.recurse(a.right,u,u,function(a){l=a});q="+"===a.operator?this.plus(h,l):"-"===a.operator?this.ifDefined(h,0)+a.operator+this.ifDefined(l,0):"("+h+")"+a.operator+"("+l+")";this.assign(c,q);e(q);break;case r.LogicalExpression:c=c||this.nextId();k.recurse(a.left,c);k.if_("&&"===a.operator?c:k.not(c),k.lazyRecurse(a.right,c));e(c);break;case r.ConditionalExpression:c=c||this.nextId();k.recurse(a.test,c);k.if_(c,k.lazyRecurse(a.alternate,c),k.lazyRecurse(a.consequent,c));e(c);
|
||||
break;case r.Identifier:c=c||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);Wa(a.name);k.if_("inputs"===k.stage||k.not(k.getHasOwnProperty("l",a.name)),function(){k.if_("inputs"===k.stage||"s",function(){f&&1!==f&&k.if_(k.not(k.nonComputedMember("s",a.name)),k.lazyAssign(k.nonComputedMember("s",a.name),"{}"));k.assign(c,k.nonComputedMember("s",a.name))})},c&&k.lazyAssign(c,k.nonComputedMember("l",
|
||||
a.name)));(k.state.expensiveChecks||Fb(a.name))&&k.addEnsureSafeObject(c);e(c);break;case r.MemberExpression:h=d&&(d.context=this.nextId())||this.nextId();c=c||this.nextId();k.recurse(a.object,h,u,function(){k.if_(k.notNull(h),function(){if(a.computed)l=k.nextId(),k.recurse(a.property,l),k.addEnsureSafeMemberName(l),f&&1!==f&&k.if_(k.not(k.computedMember(h,l)),k.lazyAssign(k.computedMember(h,l),"{}")),q=k.ensureSafeObject(k.computedMember(h,l)),k.assign(c,q),d&&(d.computed=!0,d.name=l);else{Wa(a.property.name);
|
||||
f&&1!==f&&k.if_(k.not(k.nonComputedMember(h,a.property.name)),k.lazyAssign(k.nonComputedMember(h,a.property.name),"{}"));q=k.nonComputedMember(h,a.property.name);if(k.state.expensiveChecks||Fb(a.property.name))q=k.ensureSafeObject(q);k.assign(c,q);d&&(d.computed=!1,d.name=a.property.name)}},function(){k.assign(c,"undefined")});e(c)},!!f);break;case r.CallExpression:c=c||this.nextId();a.filter?(l=k.filter(a.callee.name),m=[],n(a.arguments,function(a){var c=k.nextId();k.recurse(a,c);m.push(c)}),q=l+
|
||||
"("+m.join(",")+")",k.assign(c,q),e(c)):(l=k.nextId(),h={},m=[],k.recurse(a.callee,l,h,function(){k.if_(k.notNull(l),function(){k.addEnsureSafeFunction(l);n(a.arguments,function(a){k.recurse(a,k.nextId(),u,function(a){m.push(k.ensureSafeObject(a))})});h.name?(k.state.expensiveChecks||k.addEnsureSafeObject(h.context),q=k.member(h.context,h.name,h.computed)+"("+m.join(",")+")"):q=l+"("+m.join(",")+")";q=k.ensureSafeObject(q);k.assign(c,q)},function(){k.assign(c,"undefined")});e(c)}));break;case r.AssignmentExpression:l=
|
||||
this.nextId();h={};if(!od(a.left))throw da("lval");this.recurse(a.left,u,h,function(){k.if_(k.notNull(h.context),function(){k.recurse(a.right,l);k.addEnsureSafeObject(k.member(h.context,h.name,h.computed));q=k.member(h.context,h.name,h.computed)+a.operator+l;k.assign(c,q);e(c||q)})},1);break;case r.ArrayExpression:m=[];n(a.elements,function(a){k.recurse(a,k.nextId(),u,function(a){m.push(a)})});q="["+m.join(",")+"]";this.assign(c,q);e(q);break;case r.ObjectExpression:m=[];n(a.properties,function(a){k.recurse(a.value,
|
||||
k.nextId(),u,function(c){m.push(k.escape(a.key.type===r.Identifier?a.key.name:""+a.key.value)+":"+c)})});q="{"+m.join(",")+"}";this.assign(c,q);e(q);break;case r.ThisExpression:this.assign(c,"s");e("s");break;case r.NGValueParameter:this.assign(c,"v"),e("v")}},getHasOwnProperty:function(a,c){var d=a+"."+c,e=this.current().own;e.hasOwnProperty(d)||(e[d]=this.nextId(!1,a+"&&("+this.escape(c)+" in "+a+")"));return e[d]},assign:function(a,c){if(a)return this.current().body.push(a,"=",c,";"),a},filter:function(a){this.state.filters.hasOwnProperty(a)||
|
||||
(this.state.filters[a]=this.nextId(!0));return this.state.filters[a]},ifDefined:function(a,c){return"ifDefined("+a+","+this.escape(c)+")"},plus:function(a,c){return"plus("+a+","+c+")"},return_:function(a){this.current().body.push("return ",a,";")},if_:function(a,c,d){if(!0===a)c();else{var e=this.current().body;e.push("if(",a,"){");c();e.push("}");d&&(e.push("else{"),d(),e.push("}"))}},not:function(a){return"!("+a+")"},notNull:function(a){return a+"!=null"},nonComputedMember:function(a,c){return a+
|
||||
"."+c},computedMember:function(a,c){return a+"["+c+"]"},member:function(a,c,d){return d?this.computedMember(a,c):this.nonComputedMember(a,c)},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),";")},ensureSafeObject:function(a){return"ensureSafeObject("+a+",text)"},ensureSafeMemberName:function(a){return"ensureSafeMemberName("+
|
||||
a+",text)"},ensureSafeFunction:function(a){return"ensureSafeFunction("+a+",text)"},lazyRecurse:function(a,c,d,e,f,g){var h=this;return function(){h.recurse(a,c,d,e,f,g)}},lazyAssign:function(a,c){var d=this;return function(){d.assign(a,c)}},stringEscapeRegex:/[^ a-zA-Z0-9]/g,stringEscapeFn:function(a){return"\\u"+("0000"+a.charCodeAt(0).toString(16)).slice(-4)},escape:function(a){if(H(a))return"'"+a.replace(this.stringEscapeRegex,this.stringEscapeFn)+"'";if(X(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 da("esc");},nextId:function(a,c){var d="v"+this.state.nextId++;a||this.current().vars.push(d+(c?"="+c:""));return d},current:function(){return this.state[this.state.computing]}};sd.prototype={compile:function(a,c){var d=this,e=this.astBuilder.ast(a);this.expression=a;this.expensiveChecks=c;U(e,d.$filter);var f,g;if(f=pd(e))g=this.recurse(f);f=nd(e.body);var h;f&&(h=[],n(f,function(a,c){var e=d.recurse(a);
|
||||
a.input=e;h.push(e);a.watchId=c}));var l=[];n(e.body,function(a){l.push(d.recurse(a.expression))});f=0===e.body.length?function(){}:1===e.body.length?l[0]:function(a,c){var d;n(l,function(e){d=e(a,c)});return d};g&&(f.assign=function(a,c,d){return g(a,d,c)});h&&(f.inputs=h);f.literal=qd(e);f.constant=e.constant;return f},recurse:function(a,c,d){var e,f,g=this,h;if(a.input)return this.inputs(a.input,a.watchId);switch(a.type){case r.Literal:return this.value(a.value,c);case r.UnaryExpression:return f=
|
||||
this.recurse(a.argument),this["unary"+a.operator](f,c);case r.BinaryExpression:return e=this.recurse(a.left),f=this.recurse(a.right),this["binary"+a.operator](e,f,c);case r.LogicalExpression:return e=this.recurse(a.left),f=this.recurse(a.right),this["binary"+a.operator](e,f,c);case r.ConditionalExpression:return this["ternary?:"](this.recurse(a.test),this.recurse(a.alternate),this.recurse(a.consequent),c);case r.Identifier:return Wa(a.name,g.expression),g.identifier(a.name,g.expensiveChecks||Fb(a.name),
|
||||
c,d,g.expression);case r.MemberExpression:return e=this.recurse(a.object,!1,!!d),a.computed||(Wa(a.property.name,g.expression),f=a.property.name),a.computed&&(f=this.recurse(a.property)),a.computed?this.computedMember(e,f,c,d,g.expression):this.nonComputedMember(e,f,g.expensiveChecks,c,d,g.expression);case r.CallExpression:return h=[],n(a.arguments,function(a){h.push(g.recurse(a))}),a.filter&&(f=this.$filter(a.callee.name)),a.filter||(f=this.recurse(a.callee,!0)),a.filter?function(a,d,e,g){for(var n=
|
||||
[],t=0;t<h.length;++t)n.push(h[t](a,d,e,g));a=f.apply(u,n,g);return c?{context:u,name:u,value:a}:a}:function(a,d,e,q){var n=f(a,d,e,q),t;if(null!=n.value){Ba(n.context,g.expression);ld(n.value,g.expression);t=[];for(var r=0;r<h.length;++r)t.push(Ba(h[r](a,d,e,q),g.expression));t=Ba(n.value.apply(n.context,t),g.expression)}return c?{value:t}:t};case r.AssignmentExpression:return e=this.recurse(a.left,!0,1),f=this.recurse(a.right),function(a,d,h,q){var n=e(a,d,h,q);a=f(a,d,h,q);Ba(n.value,g.expression);
|
||||
n.context[n.name]=a;return c?{value:a}:a};case r.ArrayExpression:return h=[],n(a.elements,function(a){h.push(g.recurse(a))}),function(a,d,e,f){for(var g=[],n=0;n<h.length;++n)g.push(h[n](a,d,e,f));return c?{value:g}:g};case r.ObjectExpression:return h=[],n(a.properties,function(a){h.push({key:a.key.type===r.Identifier?a.key.name:""+a.key.value,value:g.recurse(a.value)})}),function(a,d,e,f){for(var g={},n=0;n<h.length;++n)g[h[n].key]=h[n].value(a,d,e,f);return c?{value:g}:g};case r.ThisExpression:return function(a){return c?
|
||||
{value:a}:a};case r.NGValueParameter:return function(a,d,e,f){return c?{value:e}:e}}},"unary+":function(a,c){return function(d,e,f,g){d=a(d,e,f,g);d=x(d)?+d:0;return c?{value:d}:d}},"unary-":function(a,c){return function(d,e,f,g){d=a(d,e,f,g);d=x(d)?-d:0;return c?{value:d}:d}},"unary!":function(a,c){return function(d,e,f,g){d=!a(d,e,f,g);return c?{value:d}:d}},"binary+":function(a,c,d){return function(e,f,g,h){var l=a(e,f,g,h);e=c(e,f,g,h);l=md(l,e);return d?{value:l}:l}},"binary-":function(a,c,d){return function(e,
|
||||
f,g,h){var l=a(e,f,g,h);e=c(e,f,g,h);l=(x(l)?l:0)-(x(e)?e:0);return d?{value:l}:l}},"binary*":function(a,c,d){return function(e,f,g,h){e=a(e,f,g,h)*c(e,f,g,h);return d?{value:e}:e}},"binary/":function(a,c,d){return function(e,f,g,h){e=a(e,f,g,h)/c(e,f,g,h);return d?{value:e}:e}},"binary%":function(a,c,d){return function(e,f,g,h){e=a(e,f,g,h)%c(e,f,g,h);return d?{value:e}:e}},"binary===":function(a,c,d){return function(e,f,g,h){e=a(e,f,g,h)===c(e,f,g,h);return d?{value:e}:e}},"binary!==":function(a,
|
||||
c,d){return function(e,f,g,h){e=a(e,f,g,h)!==c(e,f,g,h);return d?{value:e}:e}},"binary==":function(a,c,d){return function(e,f,g,h){e=a(e,f,g,h)==c(e,f,g,h);return d?{value:e}:e}},"binary!=":function(a,c,d){return function(e,f,g,h){e=a(e,f,g,h)!=c(e,f,g,h);return d?{value:e}:e}},"binary<":function(a,c,d){return function(e,f,g,h){e=a(e,f,g,h)<c(e,f,g,h);return d?{value:e}:e}},"binary>":function(a,c,d){return function(e,f,g,h){e=a(e,f,g,h)>c(e,f,g,h);return d?{value:e}:e}},"binary<=":function(a,c,d){return function(e,
|
||||
f,g,h){e=a(e,f,g,h)<=c(e,f,g,h);return d?{value:e}:e}},"binary>=":function(a,c,d){return function(e,f,g,h){e=a(e,f,g,h)>=c(e,f,g,h);return d?{value:e}:e}},"binary&&":function(a,c,d){return function(e,f,g,h){e=a(e,f,g,h)&&c(e,f,g,h);return d?{value:e}:e}},"binary||":function(a,c,d){return function(e,f,g,h){e=a(e,f,g,h)||c(e,f,g,h);return d?{value:e}:e}},"ternary?:":function(a,c,d,e){return function(f,g,h,l){f=a(f,g,h,l)?c(f,g,h,l):d(f,g,h,l);return e?{value:f}:f}},value:function(a,c){return function(){return c?
|
||||
{context:u,name:u,value:a}:a}},identifier:function(a,c,d,e,f){return function(g,h,l,k){g=h&&a in h?h:g;e&&1!==e&&g&&!g[a]&&(g[a]={});h=g?g[a]:u;c&&Ba(h,f);return d?{context:g,name:a,value:h}:h}},computedMember:function(a,c,d,e,f){return function(g,h,l,k){var m=a(g,h,l,k),q,n;null!=m&&(q=c(g,h,l,k),Wa(q,f),e&&1!==e&&m&&!m[q]&&(m[q]={}),n=m[q],Ba(n,f));return d?{context:m,name:q,value:n}:n}},nonComputedMember:function(a,c,d,e,f,g){return function(h,l,k,m){h=a(h,l,k,m);f&&1!==f&&h&&!h[c]&&(h[c]={});
|
||||
l=null!=h?h[c]:u;(d||Fb(c))&&Ba(l,g);return e?{context:h,name:c,value:l}:l}},inputs:function(a,c){return function(d,e,f,g){return g?g[c]:a(d,e,f)}}};var fc=function(a,c,d){this.lexer=a;this.$filter=c;this.options=d;this.ast=new r(this.lexer);this.astCompiler=d.csp?new sd(this.ast,c):new rd(this.ast,c)};fc.prototype={constructor:fc,parse:function(a){return this.astCompiler.compile(a,this.options.expensiveChecks)}};ga();ga();var Yf=Object.prototype.valueOf,Ca=G("$sce"),oa={HTML:"html",CSS:"css",URL:"url",
|
||||
RESOURCE_URL:"resourceUrl",JS:"js"},ea=G("$compile"),Z=W.createElement("a"),wd=Aa(N.location.href);xd.$inject=["$document"];Jc.$inject=["$provide"];yd.$inject=["$locale"];Ad.$inject=["$locale"];var Dd=".",hg={yyyy:$("FullYear",4),yy:$("FullYear",2,0,!0),y:$("FullYear",1),MMMM:Hb("Month"),MMM:Hb("Month",!0),MM:$("Month",2,1),M:$("Month",1,1),dd:$("Date",2),d:$("Date",1),HH:$("Hours",2),H:$("Hours",1),hh:$("Hours",2,-12),h:$("Hours",1,-12),mm:$("Minutes",2),m:$("Minutes",1),ss:$("Seconds",2),s:$("Seconds",
|
||||
1),sss:$("Milliseconds",3),EEEE:Hb("Day"),EEE:Hb("Day",!0),a:function(a,c){return 12>a.getHours()?c.AMPMS[0]:c.AMPMS[1]},Z:function(a,c,d){a=-1*d;return a=(0<=a?"+":"")+(Gb(Math[0<a?"floor":"ceil"](a/60),2)+Gb(Math.abs(a%60),2))},ww:Fd(2),w:Fd(1),G:hc,GG:hc,GGG:hc,GGGG:function(a,c){return 0>=a.getFullYear()?c.ERANAMES[0]:c.ERANAMES[1]}},gg=/((?:[^yMdHhmsaZEwG']+)|(?:'(?:[^']|'')*')|(?:E+|y+|M+|d+|H+|h+|m+|s+|a|Z|G+|w+))(.*)/,fg=/^\-?\d+$/;zd.$inject=["$locale"];var cg=qa(I),dg=qa(rb);Bd.$inject=
|
||||
["$parse"];var he=qa({restrict:"E",compile:function(a,c){if(!c.href&&!c.xlinkHref)return function(a,c){if("a"===c[0].nodeName.toLowerCase()){var f="[object SVGAnimatedString]"===sa.call(c.prop("href"))?"xlink:href":"href";c.on("click",function(a){c.attr(f)||a.preventDefault()})}}}}),sb={};n(Ab,function(a,c){function d(a,d,f){a.$watch(f[e],function(a){f.$set(c,!!a)})}if("multiple"!=a){var e=va("ng-"+c),f=d;"checked"===a&&(f=function(a,c,f){f.ngModel!==f[e]&&d(a,c,f)});sb[e]=function(){return{restrict:"A",
|
||||
priority:100,link:f}}}});n(Sc,function(a,c){sb[c]=function(){return{priority:100,link:function(a,e,f){if("ngPattern"===c&&"/"==f.ngPattern.charAt(0)&&(e=f.ngPattern.match(jg))){f.$set("ngPattern",new RegExp(e[1],e[2]));return}a.$watch(f[c],function(a){f.$set(c,a)})}}}});n(["src","srcset","href"],function(a){var c=va("ng-"+a);sb[c]=function(){return{priority:99,link:function(d,e,f){var g=a,h=a;"href"===a&&"[object SVGAnimatedString]"===sa.call(e.prop("href"))&&(h="xlinkHref",f.$attr[h]="xlink:href",
|
||||
g=null);f.$observe(c,function(c){c?(f.$set(h,c),Va&&g&&e.prop(g,f[h])):"href"===a&&f.$set(h,null)})}}}});var Ib={$addControl:v,$$renameControl:function(a,c){a.$name=c},$removeControl:v,$setValidity:v,$setDirty:v,$setPristine:v,$setSubmitted:v};Gd.$inject=["$element","$attrs","$scope","$animate","$interpolate"];var Od=function(a){return["$timeout","$parse",function(c,d){function e(a){return""===a?d('this[""]').assign:d(a).assign||v}return{name:"form",restrict:a?"EAC":"E",controller:Gd,compile:function(d,
|
||||
g){d.addClass(Xa).addClass(lb);var h=g.name?"name":a&&g.ngForm?"ngForm":!1;return{pre:function(a,d,f,g){if(!("action"in f)){var n=function(c){a.$apply(function(){g.$commitViewValue();g.$setSubmitted()});c.preventDefault()};d[0].addEventListener("submit",n,!1);d.on("$destroy",function(){c(function(){d[0].removeEventListener("submit",n,!1)},0,!1)})}var t=g.$$parentForm,r=h?e(g.$name):v;h&&(r(a,g),f.$observe(h,function(c){g.$name!==c&&(r(a,u),t.$$renameControl(g,c),r=e(g.$name),r(a,g))}));d.on("$destroy",
|
||||
function(){t.$removeControl(g);r(a,u);Q(g,Ib)})}}}}}]},ie=Od(),ve=Od(!0),ig=/\d{4}-[01]\d-[0-3]\dT[0-2]\d:[0-5]\d:[0-5]\d\.\d+([+-][0-2]\d:[0-5]\d|Z)/,sg=/^(ftp|http|https):\/\/(\w+:{0,1}\w*@)?(\S+)(:[0-9]+)?(\/|\/([\w#!:.?+=&%@!\-\/]))?$/,tg=/^[a-z0-9!#$%&'*+\/=?^_`{|}~.-]+@[a-z0-9]([a-z0-9-]*[a-z0-9])?(\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$/i,ug=/^\s*(\-|\+)?(\d+|(\d*(\.\d*)))([eE][+-]?\d+)?\s*$/,Pd=/^(\d{4})-(\d{2})-(\d{2})$/,Qd=/^(\d{4})-(\d\d)-(\d\d)T(\d\d):(\d\d)(?::(\d\d)(\.\d{1,3})?)?$/,kc=/^(\d{4})-W(\d\d)$/,
|
||||
Rd=/^(\d{4})-(\d\d)$/,Sd=/^(\d\d):(\d\d)(?::(\d\d)(\.\d{1,3})?)?$/,Td={text:function(a,c,d,e,f,g){ib(a,c,d,e,f,g);ic(e)},date:jb("date",Pd,Kb(Pd,["yyyy","MM","dd"]),"yyyy-MM-dd"),"datetime-local":jb("datetimelocal",Qd,Kb(Qd,"yyyy MM dd HH mm ss sss".split(" ")),"yyyy-MM-ddTHH:mm:ss.sss"),time:jb("time",Sd,Kb(Sd,["HH","mm","ss","sss"]),"HH:mm:ss.sss"),week:jb("week",kc,function(a,c){if(ca(a))return a;if(H(a)){kc.lastIndex=0;var d=kc.exec(a);if(d){var e=+d[1],f=+d[2],g=d=0,h=0,l=0,k=Ed(e),f=7*(f-1);
|
||||
c&&(d=c.getHours(),g=c.getMinutes(),h=c.getSeconds(),l=c.getMilliseconds());return new Date(e,0,k.getDate()+f,d,g,h,l)}}return NaN},"yyyy-Www"),month:jb("month",Rd,Kb(Rd,["yyyy","MM"]),"yyyy-MM"),number:function(a,c,d,e,f,g){Id(a,c,d,e);ib(a,c,d,e,f,g);e.$$parserName="number";e.$parsers.push(function(a){return e.$isEmpty(a)?null:ug.test(a)?parseFloat(a):u});e.$formatters.push(function(a){if(!e.$isEmpty(a)){if(!X(a))throw kb("numfmt",a);a=a.toString()}return a});if(x(d.min)||d.ngMin){var h;e.$validators.min=
|
||||
function(a){return e.$isEmpty(a)||y(h)||a>=h};d.$observe("min",function(a){x(a)&&!X(a)&&(a=parseFloat(a,10));h=X(a)&&!isNaN(a)?a:u;e.$validate()})}if(x(d.max)||d.ngMax){var l;e.$validators.max=function(a){return e.$isEmpty(a)||y(l)||a<=l};d.$observe("max",function(a){x(a)&&!X(a)&&(a=parseFloat(a,10));l=X(a)&&!isNaN(a)?a:u;e.$validate()})}},url:function(a,c,d,e,f,g){ib(a,c,d,e,f,g);ic(e);e.$$parserName="url";e.$validators.url=function(a,c){var d=a||c;return e.$isEmpty(d)||sg.test(d)}},email:function(a,
|
||||
c,d,e,f,g){ib(a,c,d,e,f,g);ic(e);e.$$parserName="email";e.$validators.email=function(a,c){var d=a||c;return e.$isEmpty(d)||tg.test(d)}},radio:function(a,c,d,e){y(d.name)&&c.attr("name",++mb);c.on("click",function(a){c[0].checked&&e.$setViewValue(d.value,a&&a.type)});e.$render=function(){c[0].checked=d.value==e.$viewValue};d.$observe("value",e.$render)},checkbox:function(a,c,d,e,f,g,h,l){var k=Jd(l,a,"ngTrueValue",d.ngTrueValue,!0),m=Jd(l,a,"ngFalseValue",d.ngFalseValue,!1);c.on("click",function(a){e.$setViewValue(c[0].checked,
|
||||
a&&a.type)});e.$render=function(){c[0].checked=e.$viewValue};e.$isEmpty=function(a){return!1===a};e.$formatters.push(function(a){return ka(a,k)});e.$parsers.push(function(a){return a?k:m})},hidden:v,button:v,submit:v,reset:v,file:v},Dc=["$browser","$sniffer","$filter","$parse",function(a,c,d,e){return{restrict:"E",require:["?ngModel"],link:{pre:function(f,g,h,l){l[0]&&(Td[I(h.type)]||Td.text)(f,g,h,l[0],c,a,d,e)}}}}],vg=/^(true|false|\d+)$/,Ne=function(){return{restrict:"A",priority:100,compile:function(a,
|
||||
c){return vg.test(c.ngValue)?function(a,c,f){f.$set("value",a.$eval(f.ngValue))}:function(a,c,f){a.$watch(f.ngValue,function(a){f.$set("value",a)})}}}},ne=["$compile",function(a){return{restrict:"AC",compile:function(c){a.$$addBindingClass(c);return function(c,e,f){a.$$addBindingInfo(e,f.ngBind);e=e[0];c.$watch(f.ngBind,function(a){e.textContent=a===u?"":a})}}}}],pe=["$interpolate","$compile",function(a,c){return{compile:function(d){c.$$addBindingClass(d);return function(d,f,g){d=a(f.attr(g.$attr.ngBindTemplate));
|
||||
c.$$addBindingInfo(f,d.expressions);f=f[0];g.$observe("ngBindTemplate",function(a){f.textContent=a===u?"":a})}}}}],oe=["$sce","$parse","$compile",function(a,c,d){return{restrict:"A",compile:function(e,f){var g=c(f.ngBindHtml),h=c(f.ngBindHtml,function(a){return(a||"").toString()});d.$$addBindingClass(e);return function(c,e,f){d.$$addBindingInfo(e,f.ngBindHtml);c.$watch(h,function(){e.html(a.getTrustedHtml(g(c))||"")})}}}}],Me=qa({restrict:"A",require:"ngModel",link:function(a,c,d,e){e.$viewChangeListeners.push(function(){a.$eval(d.ngChange)})}}),
|
||||
qe=jc("",!0),se=jc("Odd",0),re=jc("Even",1),te=Ma({compile:function(a,c){c.$set("ngCloak",u);a.removeClass("ng-cloak")}}),ue=[function(){return{restrict:"A",scope:!0,controller:"@",priority:500}}],Ic={},wg={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 c=va("ng-"+a);Ic[c]=["$parse","$rootScope",function(d,e){return{restrict:"A",compile:function(f,g){var h=
|
||||
d(g[c],null,!0);return function(c,d){d.on(a,function(d){var f=function(){h(c,{$event:d})};wg[a]&&e.$$phase?c.$evalAsync(f):c.$apply(f)})}}}}]});var xe=["$animate",function(a){return{multiElement:!0,transclude:"element",priority:600,terminal:!0,restrict:"A",$$tlb:!0,link:function(c,d,e,f,g){var h,l,k;c.$watch(e.ngIf,function(c){c?l||g(function(c,f){l=f;c[c.length++]=W.createComment(" end ngIf: "+e.ngIf+" ");h={clone:c};a.enter(c,d.parent(),d)}):(k&&(k.remove(),k=null),l&&(l.$destroy(),l=null),h&&(k=
|
||||
qb(h.clone),a.leave(k).then(function(){k=null}),h=null))})}}}],ye=["$templateRequest","$anchorScroll","$animate",function(a,c,d){return{restrict:"ECA",priority:400,terminal:!0,transclude:"element",controller:aa.noop,compile:function(e,f){var g=f.ngInclude||f.src,h=f.onload||"",l=f.autoscroll;return function(e,f,n,s,r){var u=0,v,w,p,A=function(){w&&(w.remove(),w=null);v&&(v.$destroy(),v=null);p&&(d.leave(p).then(function(){w=null}),w=p,p=null)};e.$watch(g,function(g){var n=function(){!x(l)||l&&!e.$eval(l)||
|
||||
c()},q=++u;g?(a(g,!0).then(function(a){if(q===u){var c=e.$new();s.template=a;a=r(c,function(a){A();d.enter(a,null,f).then(n)});v=c;p=a;v.$emit("$includeContentLoaded",g);e.$eval(h)}},function(){q===u&&(A(),e.$emit("$includeContentError",g))}),e.$emit("$includeContentRequested",g)):(A(),s.template=null)})}}}}],Pe=["$compile",function(a){return{restrict:"ECA",priority:-400,require:"ngInclude",link:function(c,d,e,f){/SVG/.test(d[0].toString())?(d.empty(),a(Lc(f.template,W).childNodes)(c,function(a){d.append(a)},
|
||||
{futureParentElement:d})):(d.html(f.template),a(d.contents())(c))}}}],ze=Ma({priority:450,compile:function(){return{pre:function(a,c,d){a.$eval(d.ngInit)}}}}),Le=function(){return{restrict:"A",priority:100,require:"ngModel",link:function(a,c,d,e){var f=c.attr(d.$attr.ngList)||", ",g="false"!==d.ngTrim,h=g?T(f):f;e.$parsers.push(function(a){if(!y(a)){var c=[];a&&n(a.split(h),function(a){a&&c.push(g?T(a):a)});return c}});e.$formatters.push(function(a){return K(a)?a.join(f):u});e.$isEmpty=function(a){return!a||
|
||||
!a.length}}}},lb="ng-valid",Kd="ng-invalid",Xa="ng-pristine",Jb="ng-dirty",Md="ng-pending",kb=G("ngModel"),xg=["$scope","$exceptionHandler","$attrs","$element","$parse","$animate","$timeout","$rootScope","$q","$interpolate",function(a,c,d,e,f,g,h,l,k,m){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=m(d.name||"",!1)(a);var q=f(d.ngModel),s=q.assign,r=q,E=s,L=null,w,p=this;this.$$setOptions=function(a){if((p.$options=a)&&a.getterSetter){var c=f(d.ngModel+"()"),g=f(d.ngModel+"($$$p)");r=function(a){var d=q(a);B(d)&&(d=c(a));return d};E=function(a,c){B(q(a))?g(a,{$$$p:p.$modelValue}):s(a,p.$modelValue)}}else if(!q.assign)throw kb("nonassign",d.ngModel,ua(e));};this.$render=v;this.$isEmpty=function(a){return y(a)||
|
||||
""===a||null===a||a!==a};var A=e.inheritedData("$formController")||Ib,z=0;Hd({ctrl:this,$element:e,set:function(a,c){a[c]=!0},unset:function(a,c){delete a[c]},parentForm:A,$animate:g});this.$setPristine=function(){p.$dirty=!1;p.$pristine=!0;g.removeClass(e,Jb);g.addClass(e,Xa)};this.$setDirty=function(){p.$dirty=!0;p.$pristine=!1;g.removeClass(e,Xa);g.addClass(e,Jb);A.$setDirty()};this.$setUntouched=function(){p.$touched=!1;p.$untouched=!0;g.setClass(e,"ng-untouched","ng-touched")};this.$setTouched=
|
||||
function(){p.$touched=!0;p.$untouched=!1;g.setClass(e,"ng-touched","ng-untouched")};this.$rollbackViewValue=function(){h.cancel(L);p.$viewValue=p.$$lastCommittedViewValue;p.$render()};this.$validate=function(){if(!X(p.$modelValue)||!isNaN(p.$modelValue)){var a=p.$$rawModelValue,c=p.$valid,d=p.$modelValue,e=p.$options&&p.$options.allowInvalid;p.$$runValidators(a,p.$$lastCommittedViewValue,function(f){e||c===f||(p.$modelValue=f?a:u,p.$modelValue!==d&&p.$$writeModelToScope())})}};this.$$runValidators=
|
||||
function(a,c,d){function e(){var d=!0;n(p.$validators,function(e,f){var h=e(a,c);d=d&&h;g(f,h)});return d?!0:(n(p.$asyncValidators,function(a,c){g(c,null)}),!1)}function f(){var d=[],e=!0;n(p.$asyncValidators,function(f,h){var k=f(a,c);if(!k||!B(k.then))throw kb("$asyncValidators",k);g(h,u);d.push(k.then(function(){g(h,!0)},function(a){e=!1;g(h,!1)}))});d.length?k.all(d).then(function(){h(e)},v):h(!0)}function g(a,c){l===z&&p.$setValidity(a,c)}function h(a){l===z&&d(a)}z++;var l=z;(function(){var a=
|
||||
p.$$parserName||"parse";if(w===u)g(a,null);else return w||(n(p.$validators,function(a,c){g(c,null)}),n(p.$asyncValidators,function(a,c){g(c,null)})),g(a,w),w;return!0})()?e()?f():h(!1):h(!1)};this.$commitViewValue=function(){var a=p.$viewValue;h.cancel(L);if(p.$$lastCommittedViewValue!==a||""===a&&p.$$hasNativeValidators)p.$$lastCommittedViewValue=a,p.$pristine&&this.$setDirty(),this.$$parseAndValidate()};this.$$parseAndValidate=function(){var c=p.$$lastCommittedViewValue;if(w=y(c)?u:!0)for(var d=
|
||||
0;d<p.$parsers.length;d++)if(c=p.$parsers[d](c),y(c)){w=!1;break}X(p.$modelValue)&&isNaN(p.$modelValue)&&(p.$modelValue=r(a));var e=p.$modelValue,f=p.$options&&p.$options.allowInvalid;p.$$rawModelValue=c;f&&(p.$modelValue=c,p.$modelValue!==e&&p.$$writeModelToScope());p.$$runValidators(c,p.$$lastCommittedViewValue,function(a){f||(p.$modelValue=a?c:u,p.$modelValue!==e&&p.$$writeModelToScope())})};this.$$writeModelToScope=function(){E(a,p.$modelValue);n(p.$viewChangeListeners,function(a){try{a()}catch(d){c(d)}})};
|
||||
this.$setViewValue=function(a,c){p.$viewValue=a;p.$options&&!p.$options.updateOnDefault||p.$$debounceViewValueCommit(c)};this.$$debounceViewValueCommit=function(c){var d=0,e=p.$options;e&&x(e.debounce)&&(e=e.debounce,X(e)?d=e:X(e[c])?d=e[c]:X(e["default"])&&(d=e["default"]));h.cancel(L);d?L=h(function(){p.$commitViewValue()},d):l.$$phase?p.$commitViewValue():a.$apply(function(){p.$commitViewValue()})};a.$watch(function(){var c=r(a);if(c!==p.$modelValue&&(p.$modelValue===p.$modelValue||c===c)){p.$modelValue=
|
||||
p.$$rawModelValue=c;w=u;for(var d=p.$formatters,e=d.length,f=c;e--;)f=d[e](f);p.$viewValue!==f&&(p.$viewValue=p.$$lastCommittedViewValue=f,p.$render(),p.$$runValidators(c,f,v))}return c})}],Ke=["$rootScope",function(a){return{restrict:"A",require:["ngModel","^?form","^?ngModelOptions"],controller:xg,priority:1,compile:function(c){c.addClass(Xa).addClass("ng-untouched").addClass(lb);return{pre:function(a,c,f,g){var h=g[0],l=g[1]||Ib;h.$$setOptions(g[2]&&g[2].$options);l.$addControl(h);f.$observe("name",
|
||||
function(a){h.$name!==a&&l.$$renameControl(h,a)});a.$on("$destroy",function(){l.$removeControl(h)})},post:function(c,e,f,g){var h=g[0];if(h.$options&&h.$options.updateOn)e.on(h.$options.updateOn,function(a){h.$$debounceViewValueCommit(a&&a.type)});e.on("blur",function(e){h.$touched||(a.$$phase?c.$evalAsync(h.$setTouched):c.$apply(h.$setTouched))})}}}}}],yg=/(\s+|^)default(\s+|$)/,Oe=function(){return{restrict:"A",controller:["$scope","$attrs",function(a,c){var d=this;this.$options=fa(a.$eval(c.ngModelOptions));
|
||||
this.$options.updateOn!==u?(this.$options.updateOnDefault=!1,this.$options.updateOn=T(this.$options.updateOn.replace(yg,function(){d.$options.updateOnDefault=!0;return" "}))):this.$options.updateOnDefault=!0}]}},Ae=Ma({terminal:!0,priority:1E3}),zg=G("ngOptions"),Ag=/^\s*([\s\S]+?)(?:\s+as\s+([\s\S]+?))?(?:\s+group\s+by\s+([\s\S]+?))?(?:\s+disable\s+when\s+([\s\S]+?))?\s+for\s+(?:([\$\w][\$\w]*)|(?:\(\s*([\$\w][\$\w]*)\s*,\s*([\$\w][\$\w]*)\s*\)))\s+in\s+([\s\S]+?)(?:\s+track\s+by\s+([\s\S]+?))?$/,
|
||||
Ie=["$compile","$parse",function(a,c){function d(a,d,e){function f(a,c,d,e,g){this.selectValue=a;this.viewValue=c;this.label=d;this.group=e;this.disabled=g}function m(a){var c;if(!r&&Da(a))c=a;else{c=[];for(var d in a)a.hasOwnProperty(d)&&"$"!==d.charAt(0)&&c.push(d)}return c}var n=a.match(Ag);if(!n)throw zg("iexp",a,ua(d));var s=n[5]||n[7],r=n[6];a=/ as /.test(n[0])&&n[1];var u=n[9];d=c(n[2]?n[1]:s);var v=a&&c(a)||d,w=u&&c(u),p=u?function(a,c){return w(e,c)}:function(a){return Ga(a)},A=function(a,
|
||||
c){return p(a,B(a,c))},x=c(n[2]||n[1]),z=c(n[3]||""),y=c(n[4]||""),J=c(n[8]),F={},B=r?function(a,c){F[r]=c;F[s]=a;return F}:function(a){F[s]=a;return F};return{trackBy:u,getTrackByValue:A,getWatchables:c(J,function(a){var c=[];a=a||[];for(var d=m(a),f=d.length,g=0;g<f;g++){var h=a===d?g:d[g],k=B(a[h],h),h=p(a[h],k);c.push(h);if(n[2]||n[1])h=x(e,k),c.push(h);n[4]&&(k=y(e,k),c.push(k))}return c}),getOptions:function(){for(var a=[],c={},d=J(e)||[],g=m(d),h=g.length,n=0;n<h;n++){var q=d===g?n:g[n],s=
|
||||
B(d[q],q),r=v(e,s),q=p(r,s),t=x(e,s),w=z(e,s),s=y(e,s),r=new f(q,r,t,w,s);a.push(r);c[q]=r}return{items:a,selectValueMap:c,getOptionFromViewValue:function(a){return c[A(a)]},getViewValueFromOption:function(a){return u?aa.copy(a.viewValue):a.viewValue}}}}}var e=W.createElement("option"),f=W.createElement("optgroup");return{restrict:"A",terminal:!0,require:["select","?ngModel"],link:function(c,h,l,k){function m(a,c){a.element=c;c.disabled=a.disabled;a.value!==c.value&&(c.value=a.selectValue);a.label!==
|
||||
c.label&&(c.label=a.label,c.textContent=a.label)}function q(a,c,d,e){c&&I(c.nodeName)===d?d=c:(d=e.cloneNode(!1),c?a.insertBefore(d,c):a.appendChild(d));return d}function s(a){for(var c;a;)c=a.nextSibling,Wb(a),a=c}function r(a){var c=p&&p[0],d=J&&J[0];if(c||d)for(;a&&(a===c||a===d);)a=a.nextSibling;return a}function u(){var a=F&&w.readValue();F=D.getOptions();var c={},d=h[0].firstChild;O&&h.prepend(p);d=r(d);F.items.forEach(function(a){var g,k;a.group?(g=c[a.group],g||(g=q(h[0],d,"optgroup",f),d=
|
||||
g.nextSibling,g.label=a.group,g=c[a.group]={groupElement:g,currentOptionElement:g.firstChild}),k=q(g.groupElement,g.currentOptionElement,"option",e),m(a,k),g.currentOptionElement=k.nextSibling):(k=q(h[0],d,"option",e),m(a,k),d=k.nextSibling)});Object.keys(c).forEach(function(a){s(c[a].currentOptionElement)});s(d);v.$render();if(!v.$isEmpty(a)){var g=w.readValue();(D.trackBy?ka(a,g):a===g)||(v.$setViewValue(g),v.$render())}}var v=k[1];if(v){var w=k[0];k=l.multiple;for(var p,x=0,y=h.children(),B=y.length;x<
|
||||
B;x++)if(""===y[x].value){p=y.eq(x);break}var O=!!p,J=z(e.cloneNode(!1));J.val("?");var F,D=d(l.ngOptions,h,c);k?(v.$isEmpty=function(a){return!a||0===a.length},w.writeValue=function(a){F.items.forEach(function(a){a.element.selected=!1});a&&a.forEach(function(a){(a=F.getOptionFromViewValue(a))&&!a.disabled&&(a.element.selected=!0)})},w.readValue=function(){var a=h.val()||[],c=[];n(a,function(a){(a=F.selectValueMap[a])&&!a.disabled&&c.push(F.getViewValueFromOption(a))});return c},D.trackBy&&c.$watchCollection(function(){if(K(v.$viewValue))return v.$viewValue.map(function(a){return D.getTrackByValue(a)})},
|
||||
function(){v.$render()})):(w.writeValue=function(a){var c=F.getOptionFromViewValue(a);c&&!c.disabled?h[0].value!==c.selectValue&&(J.remove(),O||p.remove(),h[0].value=c.selectValue,c.element.selected=!0,c.element.setAttribute("selected","selected")):null===a||O?(J.remove(),O||h.prepend(p),h.val(""),p.prop("selected",!0),p.attr("selected",!0)):(O||p.remove(),h.prepend(J),h.val("?"),J.prop("selected",!0),J.attr("selected",!0))},w.readValue=function(){var a=F.selectValueMap[h.val()];return a&&!a.disabled?
|
||||
(O||p.remove(),J.remove(),F.getViewValueFromOption(a)):null},D.trackBy&&c.$watch(function(){return D.getTrackByValue(v.$viewValue)},function(){v.$render()}));O?(p.remove(),a(p)(c),p.removeClass("ng-scope")):p=z(e.cloneNode(!1));u();c.$watchCollection(D.getWatchables,u)}}}}],Be=["$locale","$interpolate","$log",function(a,c,d){var e=/{}/g,f=/^when(Minus)?(.+)$/;return{link:function(g,h,l){function k(a){h.text(a||"")}var m=l.count,q=l.$attr.when&&h.attr(l.$attr.when),s=l.offset||0,r=g.$eval(q)||{},u=
|
||||
{},x=c.startSymbol(),w=c.endSymbol(),p=x+m+"-"+s+w,A=aa.noop,z;n(l,function(a,c){var d=f.exec(c);d&&(d=(d[1]?"-":"")+I(d[2]),r[d]=h.attr(l.$attr[c]))});n(r,function(a,d){u[d]=c(a.replace(e,p))});g.$watch(m,function(c){var e=parseFloat(c),f=isNaN(e);f||e in r||(e=a.pluralCat(e-s));e===z||f&&X(z)&&isNaN(z)||(A(),f=u[e],y(f)?(null!=c&&d.debug("ngPluralize: no rule defined for '"+e+"' in "+q),A=v,k()):A=g.$watch(f,k),z=e)})}}}],Ce=["$parse","$animate",function(a,c){var d=G("ngRepeat"),e=function(a,c,
|
||||
d,e,k,m,n){a[d]=e;k&&(a[k]=m);a.$index=c;a.$first=0===c;a.$last=c===n-1;a.$middle=!(a.$first||a.$last);a.$odd=!(a.$even=0===(c&1))};return{restrict:"A",multiElement:!0,transclude:"element",priority:1E3,terminal:!0,$$tlb:!0,compile:function(f,g){var h=g.ngRepeat,l=W.createComment(" end ngRepeat: "+h+" "),k=h.match(/^\s*([\s\S]+?)\s+in\s+([\s\S]+?)(?:\s+as\s+([\s\S]+?))?(?:\s+track\s+by\s+([\s\S]+?))?\s*$/);if(!k)throw d("iexp",h);var m=k[1],q=k[2],r=k[3],t=k[4],k=m.match(/^(?:(\s*[\$\w]+)|\(\s*([\$\w]+)\s*,\s*([\$\w]+)\s*\))$/);
|
||||
if(!k)throw d("iidexp",m);var v=k[3]||k[1],x=k[2];if(r&&(!/^[$a-zA-Z_][$a-zA-Z0-9_]*$/.test(r)||/^(null|undefined|this|\$index|\$first|\$middle|\$last|\$even|\$odd|\$parent|\$root|\$id)$/.test(r)))throw d("badident",r);var w,p,A,y,B={$id:Ga};t?w=a(t):(A=function(a,c){return Ga(c)},y=function(a){return a});return function(a,f,g,k,m){w&&(p=function(c,d,e){x&&(B[x]=c);B[v]=d;B.$index=e;return w(a,B)});var t=ga();a.$watchCollection(q,function(g){var k,q,w=f[0],B,D=ga(),F,H,K,G,M,I,N;r&&(a[r]=g);if(Da(g))M=
|
||||
g,q=p||A;else for(N in q=p||y,M=[],g)g.hasOwnProperty(N)&&"$"!==N.charAt(0)&&M.push(N);F=M.length;N=Array(F);for(k=0;k<F;k++)if(H=g===M?k:M[k],K=g[H],G=q(H,K,k),t[G])I=t[G],delete t[G],D[G]=I,N[k]=I;else{if(D[G])throw n(N,function(a){a&&a.scope&&(t[a.id]=a)}),d("dupes",h,G,K);N[k]={id:G,scope:u,clone:u};D[G]=!0}for(B in t){I=t[B];G=qb(I.clone);c.leave(G);if(G[0].parentNode)for(k=0,q=G.length;k<q;k++)G[k].$$NG_REMOVED=!0;I.scope.$destroy()}for(k=0;k<F;k++)if(H=g===M?k:M[k],K=g[H],I=N[k],I.scope){B=
|
||||
w;do B=B.nextSibling;while(B&&B.$$NG_REMOVED);I.clone[0]!=B&&c.move(qb(I.clone),null,z(w));w=I.clone[I.clone.length-1];e(I.scope,k,v,K,x,H,F)}else m(function(a,d){I.scope=d;var f=l.cloneNode(!1);a[a.length++]=f;c.enter(a,null,z(w));w=f;I.clone=a;D[I.id]=I;e(I.scope,k,v,K,x,H,F)});t=D})}}}}],De=["$animate",function(a){return{restrict:"A",multiElement:!0,link:function(c,d,e){c.$watch(e.ngShow,function(c){a[c?"removeClass":"addClass"](d,"ng-hide",{tempClasses:"ng-hide-animate"})})}}}],we=["$animate",
|
||||
function(a){return{restrict:"A",multiElement:!0,link:function(c,d,e){c.$watch(e.ngHide,function(c){a[c?"addClass":"removeClass"](d,"ng-hide",{tempClasses:"ng-hide-animate"})})}}}],Ee=Ma(function(a,c,d){a.$watch(d.ngStyle,function(a,d){d&&a!==d&&n(d,function(a,d){c.css(d,"")});a&&c.css(a)},!0)}),Fe=["$animate",function(a){return{require:"ngSwitch",controller:["$scope",function(){this.cases={}}],link:function(c,d,e,f){var g=[],h=[],l=[],k=[],m=function(a,c){return function(){a.splice(c,1)}};c.$watch(e.ngSwitch||
|
||||
e.on,function(c){var d,e;d=0;for(e=l.length;d<e;++d)a.cancel(l[d]);d=l.length=0;for(e=k.length;d<e;++d){var r=qb(h[d].clone);k[d].$destroy();(l[d]=a.leave(r)).then(m(l,d))}h.length=0;k.length=0;(g=f.cases["!"+c]||f.cases["?"])&&n(g,function(c){c.transclude(function(d,e){k.push(e);var f=c.element;d[d.length++]=W.createComment(" end ngSwitchWhen: ");h.push({clone:d});a.enter(d,f.parent(),f)})})})}}}],Ge=Ma({transclude:"element",priority:1200,require:"^ngSwitch",multiElement:!0,link:function(a,c,d,e,
|
||||
f){e.cases["!"+d.ngSwitchWhen]=e.cases["!"+d.ngSwitchWhen]||[];e.cases["!"+d.ngSwitchWhen].push({transclude:f,element:c})}}),He=Ma({transclude:"element",priority:1200,require:"^ngSwitch",multiElement:!0,link:function(a,c,d,e,f){e.cases["?"]=e.cases["?"]||[];e.cases["?"].push({transclude:f,element:c})}}),Je=Ma({restrict:"EAC",link:function(a,c,d,e,f){if(!f)throw G("ngTransclude")("orphan",ua(c));f(function(a){c.empty();c.append(a)})}}),je=["$templateCache",function(a){return{restrict:"E",terminal:!0,
|
||||
compile:function(c,d){"text/ng-template"==d.type&&a.put(d.id,c[0].text)}}}],Bg={$setViewValue:v,$render:v},Cg=["$element","$scope","$attrs",function(a,c,d){var e=this,f=new Ua;e.ngModelCtrl=Bg;e.unknownOption=z(W.createElement("option"));e.renderUnknownOption=function(c){c="? "+Ga(c)+" ?";e.unknownOption.val(c);a.prepend(e.unknownOption);a.val(c)};c.$on("$destroy",function(){e.renderUnknownOption=v});e.removeUnknownOption=function(){e.unknownOption.parent()&&e.unknownOption.remove()};e.readValue=
|
||||
function(){e.removeUnknownOption();return a.val()};e.writeValue=function(c){e.hasOption(c)?(e.removeUnknownOption(),a.val(c),""===c&&e.emptyOption.prop("selected",!0)):null==c&&e.emptyOption?(e.removeUnknownOption(),a.val("")):e.renderUnknownOption(c)};e.addOption=function(a,c){Ta(a,'"option value"');""===a&&(e.emptyOption=c);var d=f.get(a)||0;f.put(a,d+1)};e.removeOption=function(a){var c=f.get(a);c&&(1===c?(f.remove(a),""===a&&(e.emptyOption=u)):f.put(a,c-1))};e.hasOption=function(a){return!!f.get(a)}}],
|
||||
ke=function(){return{restrict:"E",require:["select","?ngModel"],controller:Cg,link:function(a,c,d,e){var f=e[1];if(f){var g=e[0];g.ngModelCtrl=f;f.$render=function(){g.writeValue(f.$viewValue)};c.on("change",function(){a.$apply(function(){f.$setViewValue(g.readValue())})});if(d.multiple){g.readValue=function(){var a=[];n(c.find("option"),function(c){c.selected&&a.push(c.value)});return a};g.writeValue=function(a){var d=new Ua(a);n(c.find("option"),function(a){a.selected=x(d.get(a.value))})};var h,
|
||||
l=NaN;a.$watch(function(){l!==f.$viewValue||ka(h,f.$viewValue)||(h=ia(f.$viewValue),f.$render());l=f.$viewValue});f.$isEmpty=function(a){return!a||0===a.length}}}}}},me=["$interpolate",function(a){function c(a){a[0].hasAttribute("selected")&&(a[0].selected=!0)}return{restrict:"E",priority:100,compile:function(d,e){if(y(e.value)){var f=a(d.text(),!0);f||e.$set("value",d.text())}return function(a,d,e){var k=d.parent(),m=k.data("$selectController")||k.parent().data("$selectController");m&&m.ngModelCtrl&&
|
||||
(f?a.$watch(f,function(a,f){e.$set("value",a);f!==a&&m.removeOption(f);m.addOption(a,d);m.ngModelCtrl.$render();c(d)}):(m.addOption(e.value,d),m.ngModelCtrl.$render(),c(d)),d.on("$destroy",function(){m.removeOption(e.value);m.ngModelCtrl.$render()}))}}}}],le=qa({restrict:"E",terminal:!1}),Fc=function(){return{restrict:"A",require:"?ngModel",link:function(a,c,d,e){e&&(d.required=!0,e.$validators.required=function(a,c){return!d.required||!e.$isEmpty(c)},d.$observe("required",function(){e.$validate()}))}}},
|
||||
Ec=function(){return{restrict:"A",require:"?ngModel",link:function(a,c,d,e){if(e){var f,g=d.ngPattern||d.pattern;d.$observe("pattern",function(a){H(a)&&0<a.length&&(a=new RegExp("^"+a+"$"));if(a&&!a.test)throw G("ngPattern")("noregexp",g,a,ua(c));f=a||u;e.$validate()});e.$validators.pattern=function(a,c){return e.$isEmpty(c)||y(f)||f.test(c)}}}}},Hc=function(){return{restrict:"A",require:"?ngModel",link:function(a,c,d,e){if(e){var f=-1;d.$observe("maxlength",function(a){a=Y(a);f=isNaN(a)?-1:a;e.$validate()});
|
||||
e.$validators.maxlength=function(a,c){return 0>f||e.$isEmpty(c)||c.length<=f}}}}},Gc=function(){return{restrict:"A",require:"?ngModel",link:function(a,c,d,e){if(e){var f=0;d.$observe("minlength",function(a){f=Y(a)||0;e.$validate()});e.$validators.minlength=function(a,c){return e.$isEmpty(c)||c.length>=f}}}}};N.angular.bootstrap?console.log("WARNING: Tried to load angular more than once."):(ce(),ee(aa),aa.module("ngLocale",[],["$provide",function(a){function c(a){a+="";var c=a.indexOf(".");return-1==
|
||||
c?0:a.length-c-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,e){var f=a|0,g=e;u===g&&(g=Math.min(c(a),3));Math.pow(10,g);return 1==
|
||||
f&&0==g?"one":"other"}})}]),z(W).ready(function(){Zd(W,yc)}))})(window,document);!window.angular.$$csp().noInlineStyle&&window.angular.element(document.head).prepend('<style type="text/css">@charset "UTF-8";[ng\\:cloak],[ng-cloak],[data-ng-cloak],[x-ng-cloak],.ng-cloak,.x-ng-cloak,.ng-hide:not(.ng-hide-animate){display:none !important;}ng\\:form{display:block;}.ng-animate-shim{visibility:hidden;}.ng-anchor{position:absolute;}</style>');
|
|
@ -1,19 +0,0 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
$(function() {
|
||||
var authAjax = function(url, action) {
|
||||
return $.ajax({
|
||||
url: url + '?ajax=1',
|
||||
success: function(res, status, xhr) {
|
||||
return location.reload(true);
|
||||
},
|
||||
error: function(res, status, xhr) {
|
||||
alertify.error(action + " failure: " + status);
|
||||
}
|
||||
});
|
||||
}
|
||||
$('button#login').click(function() { authAjax('/userauth/login', 'login'); });
|
||||
$('button#logout').click(function() { authAjax('/userauth/logout', 'logout'); });
|
||||
});
|
Различия файлов скрыты, потому что одна или несколько строк слишком длинны
Различия файлов скрыты, потому что одна или несколько строк слишком длинны
Различия файлов скрыты, потому что одна или несколько строк слишком длинны
Разница между файлами не показана из-за своего большого размера
Загрузить разницу
|
@ -1,28 +0,0 @@
|
|||
{
|
||||
"name": "Mozilla RelEng Services Frontend",
|
||||
"description": "Code for",
|
||||
"repository": {
|
||||
"url": "https://github.com/mozilla/release-services",
|
||||
"license": "MPL2"
|
||||
},
|
||||
"bugs": {
|
||||
"open": "https://github.com/mozilla/release-services/issues?q=is%3Aissue+is%3Aopen+label%3A%224.app%3A+releng_frontend%22",
|
||||
"good-first-bug": "https://github.com/mozilla/release-services/issues?q=is%3Aissue+is%3Aopen+label%3A%224.app%3A+releng_frontend%22"
|
||||
},
|
||||
"urls": {
|
||||
"production": "https://mozilla-releng.net",
|
||||
"staging": "https://staging.mozilla-releng.net"
|
||||
},
|
||||
"participate": {
|
||||
"docs": "https://docs.mozilla-releng.net",
|
||||
"irc": "#releng",
|
||||
"irc-contacts": [ "garbas" ]
|
||||
|
||||
},
|
||||
"keywords": [
|
||||
"Bootstrap",
|
||||
"Font-Awesome",
|
||||
"Fira",
|
||||
"Elm"
|
||||
]
|
||||
}
|
Двоичные данные
src/releng_frontend/src/static/favicon.ico
Двоичные данные
src/releng_frontend/src/static/favicon.ico
Двоичный файл не отображается.
До Ширина: | Высота: | Размер: 5.3 KiB |
Двоичный файл не отображается.
|
@ -1,229 +0,0 @@
|
|||
<?xml version="1.0" standalone="no"?>
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd" >
|
||||
<svg xmlns="http://www.w3.org/2000/svg">
|
||||
<metadata></metadata>
|
||||
<defs>
|
||||
<font id="glyphicons_halflingsregular" horiz-adv-x="1200" >
|
||||
<font-face units-per-em="1200" ascent="960" descent="-240" />
|
||||
<missing-glyph horiz-adv-x="500" />
|
||||
<glyph />
|
||||
<glyph />
|
||||
<glyph unicode="
" />
|
||||
<glyph unicode=" " />
|
||||
<glyph unicode="*" d="M100 500v200h259l-183 183l141 141l183 -183v259h200v-259l183 183l141 -141l-183 -183h259v-200h-259l183 -183l-141 -141l-183 183v-259h-200v259l-183 -183l-141 141l183 183h-259z" />
|
||||
<glyph unicode="+" d="M0 400v300h400v400h300v-400h400v-300h-400v-400h-300v400h-400z" />
|
||||
<glyph unicode=" " />
|
||||
<glyph unicode=" " horiz-adv-x="652" />
|
||||
<glyph unicode=" " horiz-adv-x="1304" />
|
||||
<glyph unicode=" " horiz-adv-x="652" />
|
||||
<glyph unicode=" " horiz-adv-x="1304" />
|
||||
<glyph unicode=" " horiz-adv-x="434" />
|
||||
<glyph unicode=" " horiz-adv-x="326" />
|
||||
<glyph unicode=" " horiz-adv-x="217" />
|
||||
<glyph unicode=" " horiz-adv-x="217" />
|
||||
<glyph unicode=" " horiz-adv-x="163" />
|
||||
<glyph unicode=" " horiz-adv-x="260" />
|
||||
<glyph unicode=" " horiz-adv-x="72" />
|
||||
<glyph unicode=" " horiz-adv-x="260" />
|
||||
<glyph unicode=" " horiz-adv-x="326" />
|
||||
<glyph unicode="€" d="M100 500l100 100h113q0 47 5 100h-218l100 100h135q37 167 112 257q117 141 297 141q242 0 354 -189q60 -103 66 -209h-181q0 55 -25.5 99t-63.5 68t-75 36.5t-67 12.5q-24 0 -52.5 -10t-62.5 -32t-65.5 -67t-50.5 -107h379l-100 -100h-300q-6 -46 -6 -100h406l-100 -100 h-300q9 -74 33 -132t52.5 -91t62 -54.5t59 -29t46.5 -7.5q29 0 66 13t75 37t63.5 67.5t25.5 96.5h174q-31 -172 -128 -278q-107 -117 -274 -117q-205 0 -324 158q-36 46 -69 131.5t-45 205.5h-217z" />
|
||||
<glyph unicode="−" d="M200 400h900v300h-900v-300z" />
|
||||
<glyph unicode="◼" horiz-adv-x="500" d="M0 0z" />
|
||||
<glyph unicode="☁" d="M-14 494q0 -80 56.5 -137t135.5 -57h750q120 0 205 86.5t85 207.5t-85 207t-205 86q-46 0 -90 -14q-44 97 -134.5 156.5t-200.5 59.5q-152 0 -260 -107.5t-108 -260.5q0 -25 2 -37q-66 -14 -108.5 -67.5t-42.5 -122.5z" />
|
||||
<glyph unicode="✉" d="M0 100l400 400l200 -200l200 200l400 -400h-1200zM0 300v600l300 -300zM0 1100l600 -603l600 603h-1200zM900 600l300 300v-600z" />
|
||||
<glyph unicode="✏" d="M-13 -13l333 112l-223 223zM187 403l214 -214l614 614l-214 214zM887 1103l214 -214l99 92q13 13 13 32.5t-13 33.5l-153 153q-15 13 -33 13t-33 -13z" />
|
||||
<glyph unicode="" d="M0 1200h1200l-500 -550v-550h300v-100h-800v100h300v550z" />
|
||||
<glyph unicode="" d="M14 84q18 -55 86 -75.5t147 5.5q65 21 109 69t44 90v606l600 155v-521q-64 16 -138 -7q-79 -26 -122.5 -83t-25.5 -111q18 -55 86 -75.5t147 4.5q70 23 111.5 63.5t41.5 95.5v881q0 10 -7 15.5t-17 2.5l-752 -193q-10 -3 -17 -12.5t-7 -19.5v-689q-64 17 -138 -7 q-79 -25 -122.5 -82t-25.5 -112z" />
|
||||
<glyph unicode="" d="M23 693q0 200 142 342t342 142t342 -142t142 -342q0 -142 -78 -261l300 -300q7 -8 7 -18t-7 -18l-109 -109q-8 -7 -18 -7t-18 7l-300 300q-119 -78 -261 -78q-200 0 -342 142t-142 342zM176 693q0 -136 97 -233t234 -97t233.5 96.5t96.5 233.5t-96.5 233.5t-233.5 96.5 t-234 -97t-97 -233z" />
|
||||
<glyph unicode="" d="M100 784q0 64 28 123t73 100.5t104.5 64t119 20.5t120 -38.5t104.5 -104.5q48 69 109.5 105t121.5 38t118.5 -20.5t102.5 -64t71 -100.5t27 -123q0 -57 -33.5 -117.5t-94 -124.5t-126.5 -127.5t-150 -152.5t-146 -174q-62 85 -145.5 174t-149.5 152.5t-126.5 127.5 t-94 124.5t-33.5 117.5z" />
|
||||
<glyph unicode="" d="M-72 800h479l146 400h2l146 -400h472l-382 -278l145 -449l-384 275l-382 -275l146 447zM168 71l2 1z" />
|
||||
<glyph unicode="" d="M-72 800h479l146 400h2l146 -400h472l-382 -278l145 -449l-384 275l-382 -275l146 447zM168 71l2 1zM237 700l196 -142l-73 -226l192 140l195 -141l-74 229l193 140h-235l-77 211l-78 -211h-239z" />
|
||||
<glyph unicode="" d="M0 0v143l400 257v100q-37 0 -68.5 74.5t-31.5 125.5v200q0 124 88 212t212 88t212 -88t88 -212v-200q0 -51 -31.5 -125.5t-68.5 -74.5v-100l400 -257v-143h-1200z" />
|
||||
<glyph unicode="" d="M0 0v1100h1200v-1100h-1200zM100 100h100v100h-100v-100zM100 300h100v100h-100v-100zM100 500h100v100h-100v-100zM100 700h100v100h-100v-100zM100 900h100v100h-100v-100zM300 100h600v400h-600v-400zM300 600h600v400h-600v-400zM1000 100h100v100h-100v-100z M1000 300h100v100h-100v-100zM1000 500h100v100h-100v-100zM1000 700h100v100h-100v-100zM1000 900h100v100h-100v-100z" />
|
||||
<glyph unicode="" d="M0 50v400q0 21 14.5 35.5t35.5 14.5h400q21 0 35.5 -14.5t14.5 -35.5v-400q0 -21 -14.5 -35.5t-35.5 -14.5h-400q-21 0 -35.5 14.5t-14.5 35.5zM0 650v400q0 21 14.5 35.5t35.5 14.5h400q21 0 35.5 -14.5t14.5 -35.5v-400q0 -21 -14.5 -35.5t-35.5 -14.5h-400 q-21 0 -35.5 14.5t-14.5 35.5zM600 50v400q0 21 14.5 35.5t35.5 14.5h400q21 0 35.5 -14.5t14.5 -35.5v-400q0 -21 -14.5 -35.5t-35.5 -14.5h-400q-21 0 -35.5 14.5t-14.5 35.5zM600 650v400q0 21 14.5 35.5t35.5 14.5h400q21 0 35.5 -14.5t14.5 -35.5v-400 q0 -21 -14.5 -35.5t-35.5 -14.5h-400q-21 0 -35.5 14.5t-14.5 35.5z" />
|
||||
<glyph unicode="" d="M0 50v200q0 21 14.5 35.5t35.5 14.5h200q21 0 35.5 -14.5t14.5 -35.5v-200q0 -21 -14.5 -35.5t-35.5 -14.5h-200q-21 0 -35.5 14.5t-14.5 35.5zM0 450v200q0 21 14.5 35.5t35.5 14.5h200q21 0 35.5 -14.5t14.5 -35.5v-200q0 -21 -14.5 -35.5t-35.5 -14.5h-200 q-21 0 -35.5 14.5t-14.5 35.5zM0 850v200q0 21 14.5 35.5t35.5 14.5h200q21 0 35.5 -14.5t14.5 -35.5v-200q0 -21 -14.5 -35.5t-35.5 -14.5h-200q-21 0 -35.5 14.5t-14.5 35.5zM400 50v200q0 21 14.5 35.5t35.5 14.5h200q21 0 35.5 -14.5t14.5 -35.5v-200q0 -21 -14.5 -35.5 t-35.5 -14.5h-200q-21 0 -35.5 14.5t-14.5 35.5zM400 450v200q0 21 14.5 35.5t35.5 14.5h200q21 0 35.5 -14.5t14.5 -35.5v-200q0 -21 -14.5 -35.5t-35.5 -14.5h-200q-21 0 -35.5 14.5t-14.5 35.5zM400 850v200q0 21 14.5 35.5t35.5 14.5h200q21 0 35.5 -14.5t14.5 -35.5 v-200q0 -21 -14.5 -35.5t-35.5 -14.5h-200q-21 0 -35.5 14.5t-14.5 35.5zM800 50v200q0 21 14.5 35.5t35.5 14.5h200q21 0 35.5 -14.5t14.5 -35.5v-200q0 -21 -14.5 -35.5t-35.5 -14.5h-200q-21 0 -35.5 14.5t-14.5 35.5zM800 450v200q0 21 14.5 35.5t35.5 14.5h200 q21 0 35.5 -14.5t14.5 -35.5v-200q0 -21 -14.5 -35.5t-35.5 -14.5h-200q-21 0 -35.5 14.5t-14.5 35.5zM800 850v200q0 21 14.5 35.5t35.5 14.5h200q21 0 35.5 -14.5t14.5 -35.5v-200q0 -21 -14.5 -35.5t-35.5 -14.5h-200q-21 0 -35.5 14.5t-14.5 35.5z" />
|
||||
<glyph unicode="" d="M0 50v200q0 21 14.5 35.5t35.5 14.5h200q21 0 35.5 -14.5t14.5 -35.5v-200q0 -21 -14.5 -35.5t-35.5 -14.5h-200q-21 0 -35.5 14.5t-14.5 35.5zM0 450q0 -21 14.5 -35.5t35.5 -14.5h200q21 0 35.5 14.5t14.5 35.5v200q0 21 -14.5 35.5t-35.5 14.5h-200q-21 0 -35.5 -14.5 t-14.5 -35.5v-200zM0 850v200q0 21 14.5 35.5t35.5 14.5h200q21 0 35.5 -14.5t14.5 -35.5v-200q0 -21 -14.5 -35.5t-35.5 -14.5h-200q-21 0 -35.5 14.5t-14.5 35.5zM400 50v200q0 21 14.5 35.5t35.5 14.5h700q21 0 35.5 -14.5t14.5 -35.5v-200q0 -21 -14.5 -35.5 t-35.5 -14.5h-700q-21 0 -35.5 14.5t-14.5 35.5zM400 450v200q0 21 14.5 35.5t35.5 14.5h700q21 0 35.5 -14.5t14.5 -35.5v-200q0 -21 -14.5 -35.5t-35.5 -14.5h-700q-21 0 -35.5 14.5t-14.5 35.5zM400 850v200q0 21 14.5 35.5t35.5 14.5h700q21 0 35.5 -14.5t14.5 -35.5 v-200q0 -21 -14.5 -35.5t-35.5 -14.5h-700q-21 0 -35.5 14.5t-14.5 35.5z" />
|
||||
<glyph unicode="" d="M29 454l419 -420l818 820l-212 212l-607 -607l-206 207z" />
|
||||
<glyph unicode="" d="M106 318l282 282l-282 282l212 212l282 -282l282 282l212 -212l-282 -282l282 -282l-212 -212l-282 282l-282 -282z" />
|
||||
<glyph unicode="" d="M23 693q0 200 142 342t342 142t342 -142t142 -342q0 -142 -78 -261l300 -300q7 -8 7 -18t-7 -18l-109 -109q-8 -7 -18 -7t-18 7l-300 300q-119 -78 -261 -78q-200 0 -342 142t-142 342zM176 693q0 -136 97 -233t234 -97t233.5 96.5t96.5 233.5t-96.5 233.5t-233.5 96.5 t-234 -97t-97 -233zM300 600v200h100v100h200v-100h100v-200h-100v-100h-200v100h-100z" />
|
||||
<glyph unicode="" d="M23 694q0 200 142 342t342 142t342 -142t142 -342q0 -141 -78 -262l300 -299q7 -7 7 -18t-7 -18l-109 -109q-8 -8 -18 -8t-18 8l-300 300q-119 -78 -261 -78q-200 0 -342 142t-142 342zM176 694q0 -136 97 -233t234 -97t233.5 97t96.5 233t-96.5 233t-233.5 97t-234 -97 t-97 -233zM300 601h400v200h-400v-200z" />
|
||||
<glyph unicode="" d="M23 600q0 183 105 331t272 210v-166q-103 -55 -165 -155t-62 -220q0 -177 125 -302t302 -125t302 125t125 302q0 120 -62 220t-165 155v166q167 -62 272 -210t105 -331q0 -118 -45.5 -224.5t-123 -184t-184 -123t-224.5 -45.5t-224.5 45.5t-184 123t-123 184t-45.5 224.5 zM500 750q0 -21 14.5 -35.5t35.5 -14.5h100q21 0 35.5 14.5t14.5 35.5v400q0 21 -14.5 35.5t-35.5 14.5h-100q-21 0 -35.5 -14.5t-14.5 -35.5v-400z" />
|
||||
<glyph unicode="" d="M100 1h200v300h-200v-300zM400 1v500h200v-500h-200zM700 1v800h200v-800h-200zM1000 1v1200h200v-1200h-200z" />
|
||||
<glyph unicode="" d="M26 601q0 -33 6 -74l151 -38l2 -6q14 -49 38 -93l3 -5l-80 -134q45 -59 105 -105l133 81l5 -3q45 -26 94 -39l5 -2l38 -151q40 -5 74 -5q27 0 74 5l38 151l6 2q46 13 93 39l5 3l134 -81q56 44 104 105l-80 134l3 5q24 44 39 93l1 6l152 38q5 40 5 74q0 28 -5 73l-152 38 l-1 6q-16 51 -39 93l-3 5l80 134q-44 58 -104 105l-134 -81l-5 3q-45 25 -93 39l-6 1l-38 152q-40 5 -74 5q-27 0 -74 -5l-38 -152l-5 -1q-50 -14 -94 -39l-5 -3l-133 81q-59 -47 -105 -105l80 -134l-3 -5q-25 -47 -38 -93l-2 -6l-151 -38q-6 -48 -6 -73zM385 601 q0 88 63 151t152 63t152 -63t63 -151q0 -89 -63 -152t-152 -63t-152 63t-63 152z" />
|
||||
<glyph unicode="" d="M100 1025v50q0 10 7.5 17.5t17.5 7.5h275v100q0 41 29.5 70.5t70.5 29.5h300q41 0 70.5 -29.5t29.5 -70.5v-100h275q10 0 17.5 -7.5t7.5 -17.5v-50q0 -11 -7 -18t-18 -7h-1050q-11 0 -18 7t-7 18zM200 100v800h900v-800q0 -41 -29.5 -71t-70.5 -30h-700q-41 0 -70.5 30 t-29.5 71zM300 100h100v700h-100v-700zM500 100h100v700h-100v-700zM500 1100h300v100h-300v-100zM700 100h100v700h-100v-700zM900 100h100v700h-100v-700z" />
|
||||
<glyph unicode="" d="M1 601l656 644l644 -644h-200v-600h-300v400h-300v-400h-300v600h-200z" />
|
||||
<glyph unicode="" d="M100 25v1150q0 11 7 18t18 7h475v-500h400v-675q0 -11 -7 -18t-18 -7h-850q-11 0 -18 7t-7 18zM700 800v300l300 -300h-300z" />
|
||||
<glyph unicode="" d="M4 600q0 162 80 299t217 217t299 80t299 -80t217 -217t80 -299t-80 -299t-217 -217t-299 -80t-299 80t-217 217t-80 299zM186 600q0 -171 121.5 -292.5t292.5 -121.5t292.5 121.5t121.5 292.5t-121.5 292.5t-292.5 121.5t-292.5 -121.5t-121.5 -292.5zM500 500v400h100 v-300h200v-100h-300z" />
|
||||
<glyph unicode="" d="M-100 0l431 1200h209l-21 -300h162l-20 300h208l431 -1200h-538l-41 400h-242l-40 -400h-539zM488 500h224l-27 300h-170z" />
|
||||
<glyph unicode="" d="M0 0v400h490l-290 300h200v500h300v-500h200l-290 -300h490v-400h-1100zM813 200h175v100h-175v-100z" />
|
||||
<glyph unicode="" d="M1 600q0 122 47.5 233t127.5 191t191 127.5t233 47.5t233 -47.5t191 -127.5t127.5 -191t47.5 -233t-47.5 -233t-127.5 -191t-191 -127.5t-233 -47.5t-233 47.5t-191 127.5t-127.5 191t-47.5 233zM188 600q0 -170 121 -291t291 -121t291 121t121 291t-121 291t-291 121 t-291 -121t-121 -291zM350 600h150v300h200v-300h150l-250 -300z" />
|
||||
<glyph unicode="" d="M4 600q0 162 80 299t217 217t299 80t299 -80t217 -217t80 -299t-80 -299t-217 -217t-299 -80t-299 80t-217 217t-80 299zM186 600q0 -171 121.5 -292.5t292.5 -121.5t292.5 121.5t121.5 292.5t-121.5 292.5t-292.5 121.5t-292.5 -121.5t-121.5 -292.5zM350 600l250 300 l250 -300h-150v-300h-200v300h-150z" />
|
||||
<glyph unicode="" d="M0 25v475l200 700h800l199 -700l1 -475q0 -11 -7 -18t-18 -7h-1150q-11 0 -18 7t-7 18zM200 500h200l50 -200h300l50 200h200l-97 500h-606z" />
|
||||
<glyph unicode="" d="M4 600q0 162 80 299t217 217t299 80t299 -80t217 -217t80 -299t-80 -299t-217 -217t-299 -80t-299 80t-217 217t-80 299zM186 600q0 -172 121.5 -293t292.5 -121t292.5 121t121.5 293q0 171 -121.5 292.5t-292.5 121.5t-292.5 -121.5t-121.5 -292.5zM500 397v401 l297 -200z" />
|
||||
<glyph unicode="" d="M23 600q0 -118 45.5 -224.5t123 -184t184 -123t224.5 -45.5t224.5 45.5t184 123t123 184t45.5 224.5h-150q0 -177 -125 -302t-302 -125t-302 125t-125 302t125 302t302 125q136 0 246 -81l-146 -146h400v400l-145 -145q-157 122 -355 122q-118 0 -224.5 -45.5t-184 -123 t-123 -184t-45.5 -224.5z" />
|
||||
<glyph unicode="" d="M23 600q0 118 45.5 224.5t123 184t184 123t224.5 45.5q198 0 355 -122l145 145v-400h-400l147 147q-112 80 -247 80q-177 0 -302 -125t-125 -302h-150zM100 0v400h400l-147 -147q112 -80 247 -80q177 0 302 125t125 302h150q0 -118 -45.5 -224.5t-123 -184t-184 -123 t-224.5 -45.5q-198 0 -355 122z" />
|
||||
<glyph unicode="" d="M100 0h1100v1200h-1100v-1200zM200 100v900h900v-900h-900zM300 200v100h100v-100h-100zM300 400v100h100v-100h-100zM300 600v100h100v-100h-100zM300 800v100h100v-100h-100zM500 200h500v100h-500v-100zM500 400v100h500v-100h-500zM500 600v100h500v-100h-500z M500 800v100h500v-100h-500z" />
|
||||
<glyph unicode="" d="M0 100v600q0 41 29.5 70.5t70.5 29.5h100v200q0 82 59 141t141 59h300q82 0 141 -59t59 -141v-200h100q41 0 70.5 -29.5t29.5 -70.5v-600q0 -41 -29.5 -70.5t-70.5 -29.5h-900q-41 0 -70.5 29.5t-29.5 70.5zM400 800h300v150q0 21 -14.5 35.5t-35.5 14.5h-200 q-21 0 -35.5 -14.5t-14.5 -35.5v-150z" />
|
||||
<glyph unicode="" d="M100 0v1100h100v-1100h-100zM300 400q60 60 127.5 84t127.5 17.5t122 -23t119 -30t110 -11t103 42t91 120.5v500q-40 -81 -101.5 -115.5t-127.5 -29.5t-138 25t-139.5 40t-125.5 25t-103 -29.5t-65 -115.5v-500z" />
|
||||
<glyph unicode="" d="M0 275q0 -11 7 -18t18 -7h50q11 0 18 7t7 18v300q0 127 70.5 231.5t184.5 161.5t245 57t245 -57t184.5 -161.5t70.5 -231.5v-300q0 -11 7 -18t18 -7h50q11 0 18 7t7 18v300q0 116 -49.5 227t-131 192.5t-192.5 131t-227 49.5t-227 -49.5t-192.5 -131t-131 -192.5 t-49.5 -227v-300zM200 20v460q0 8 6 14t14 6h160q8 0 14 -6t6 -14v-460q0 -8 -6 -14t-14 -6h-160q-8 0 -14 6t-6 14zM800 20v460q0 8 6 14t14 6h160q8 0 14 -6t6 -14v-460q0 -8 -6 -14t-14 -6h-160q-8 0 -14 6t-6 14z" />
|
||||
<glyph unicode="" d="M0 400h300l300 -200v800l-300 -200h-300v-400zM688 459l141 141l-141 141l71 71l141 -141l141 141l71 -71l-141 -141l141 -141l-71 -71l-141 141l-141 -141z" />
|
||||
<glyph unicode="" d="M0 400h300l300 -200v800l-300 -200h-300v-400zM700 857l69 53q111 -135 111 -310q0 -169 -106 -302l-67 54q86 110 86 248q0 146 -93 257z" />
|
||||
<glyph unicode="" d="M0 401v400h300l300 200v-800l-300 200h-300zM702 858l69 53q111 -135 111 -310q0 -170 -106 -303l-67 55q86 110 86 248q0 145 -93 257zM889 951l7 -8q123 -151 123 -344q0 -189 -119 -339l-7 -8l81 -66l6 8q142 178 142 405q0 230 -144 408l-6 8z" />
|
||||
<glyph unicode="" d="M0 0h500v500h-200v100h-100v-100h-200v-500zM0 600h100v100h400v100h100v100h-100v300h-500v-600zM100 100v300h300v-300h-300zM100 800v300h300v-300h-300zM200 200v100h100v-100h-100zM200 900h100v100h-100v-100zM500 500v100h300v-300h200v-100h-100v-100h-200v100 h-100v100h100v200h-200zM600 0v100h100v-100h-100zM600 1000h100v-300h200v-300h300v200h-200v100h200v500h-600v-200zM800 800v300h300v-300h-300zM900 0v100h300v-100h-300zM900 900v100h100v-100h-100zM1100 200v100h100v-100h-100z" />
|
||||
<glyph unicode="" d="M0 200h100v1000h-100v-1000zM100 0v100h300v-100h-300zM200 200v1000h100v-1000h-100zM500 0v91h100v-91h-100zM500 200v1000h200v-1000h-200zM700 0v91h100v-91h-100zM800 200v1000h100v-1000h-100zM900 0v91h200v-91h-200zM1000 200v1000h200v-1000h-200z" />
|
||||
<glyph unicode="" d="M0 700l1 475q0 10 7.5 17.5t17.5 7.5h474l700 -700l-500 -500zM148 953q0 -42 29 -71q30 -30 71.5 -30t71.5 30q29 29 29 71t-29 71q-30 30 -71.5 30t-71.5 -30q-29 -29 -29 -71z" />
|
||||
<glyph unicode="" d="M1 700l1 475q0 11 7 18t18 7h474l700 -700l-500 -500zM148 953q0 -42 30 -71q29 -30 71 -30t71 30q30 29 30 71t-30 71q-29 30 -71 30t-71 -30q-30 -29 -30 -71zM701 1200h100l700 -700l-500 -500l-50 50l450 450z" />
|
||||
<glyph unicode="" d="M100 0v1025l175 175h925v-1000l-100 -100v1000h-750l-100 -100h750v-1000h-900z" />
|
||||
<glyph unicode="" d="M200 0l450 444l450 -443v1150q0 20 -14.5 35t-35.5 15h-800q-21 0 -35.5 -15t-14.5 -35v-1151z" />
|
||||
<glyph unicode="" d="M0 100v700h200l100 -200h600l100 200h200v-700h-200v200h-800v-200h-200zM253 829l40 -124h592l62 124l-94 346q-2 11 -10 18t-18 7h-450q-10 0 -18 -7t-10 -18zM281 24l38 152q2 10 11.5 17t19.5 7h500q10 0 19.5 -7t11.5 -17l38 -152q2 -10 -3.5 -17t-15.5 -7h-600 q-10 0 -15.5 7t-3.5 17z" />
|
||||
<glyph unicode="" d="M0 200q0 -41 29.5 -70.5t70.5 -29.5h1000q41 0 70.5 29.5t29.5 70.5v600q0 41 -29.5 70.5t-70.5 29.5h-150q-4 8 -11.5 21.5t-33 48t-53 61t-69 48t-83.5 21.5h-200q-41 0 -82 -20.5t-70 -50t-52 -59t-34 -50.5l-12 -20h-150q-41 0 -70.5 -29.5t-29.5 -70.5v-600z M356 500q0 100 72 172t172 72t172 -72t72 -172t-72 -172t-172 -72t-172 72t-72 172zM494 500q0 -44 31 -75t75 -31t75 31t31 75t-31 75t-75 31t-75 -31t-31 -75zM900 700v100h100v-100h-100z" />
|
||||
<glyph unicode="" d="M53 0h365v66q-41 0 -72 11t-49 38t1 71l92 234h391l82 -222q16 -45 -5.5 -88.5t-74.5 -43.5v-66h417v66q-34 1 -74 43q-18 19 -33 42t-21 37l-6 13l-385 998h-93l-399 -1006q-24 -48 -52 -75q-12 -12 -33 -25t-36 -20l-15 -7v-66zM416 521l178 457l46 -140l116 -317h-340 z" />
|
||||
<glyph unicode="" d="M100 0v89q41 7 70.5 32.5t29.5 65.5v827q0 28 -1 39.5t-5.5 26t-15.5 21t-29 14t-49 14.5v71l471 -1q120 0 213 -88t93 -228q0 -55 -11.5 -101.5t-28 -74t-33.5 -47.5t-28 -28l-12 -7q8 -3 21.5 -9t48 -31.5t60.5 -58t47.5 -91.5t21.5 -129q0 -84 -59 -156.5t-142 -111 t-162 -38.5h-500zM400 200h161q89 0 153 48.5t64 132.5q0 90 -62.5 154.5t-156.5 64.5h-159v-400zM400 700h139q76 0 130 61.5t54 138.5q0 82 -84 130.5t-239 48.5v-379z" />
|
||||
<glyph unicode="" d="M200 0v57q77 7 134.5 40.5t65.5 80.5l173 849q10 56 -10 74t-91 37q-6 1 -10.5 2.5t-9.5 2.5v57h425l2 -57q-33 -8 -62 -25.5t-46 -37t-29.5 -38t-17.5 -30.5l-5 -12l-128 -825q-10 -52 14 -82t95 -36v-57h-500z" />
|
||||
<glyph unicode="" d="M-75 200h75v800h-75l125 167l125 -167h-75v-800h75l-125 -167zM300 900v300h150h700h150v-300h-50q0 29 -8 48.5t-18.5 30t-33.5 15t-39.5 5.5t-50.5 1h-200v-850l100 -50v-100h-400v100l100 50v850h-200q-34 0 -50.5 -1t-40 -5.5t-33.5 -15t-18.5 -30t-8.5 -48.5h-49z " />
|
||||
<glyph unicode="" d="M33 51l167 125v-75h800v75l167 -125l-167 -125v75h-800v-75zM100 901v300h150h700h150v-300h-50q0 29 -8 48.5t-18 30t-33.5 15t-40 5.5t-50.5 1h-200v-650l100 -50v-100h-400v100l100 50v650h-200q-34 0 -50.5 -1t-39.5 -5.5t-33.5 -15t-18.5 -30t-8 -48.5h-50z" />
|
||||
<glyph unicode="" d="M0 50q0 -20 14.5 -35t35.5 -15h1100q21 0 35.5 15t14.5 35v100q0 21 -14.5 35.5t-35.5 14.5h-1100q-21 0 -35.5 -14.5t-14.5 -35.5v-100zM0 350q0 -20 14.5 -35t35.5 -15h800q21 0 35.5 15t14.5 35v100q0 21 -14.5 35.5t-35.5 14.5h-800q-21 0 -35.5 -14.5t-14.5 -35.5 v-100zM0 650q0 -20 14.5 -35t35.5 -15h1000q21 0 35.5 15t14.5 35v100q0 21 -14.5 35.5t-35.5 14.5h-1000q-21 0 -35.5 -14.5t-14.5 -35.5v-100zM0 950q0 -20 14.5 -35t35.5 -15h600q21 0 35.5 15t14.5 35v100q0 21 -14.5 35.5t-35.5 14.5h-600q-21 0 -35.5 -14.5 t-14.5 -35.5v-100z" />
|
||||
<glyph unicode="" d="M0 50q0 -20 14.5 -35t35.5 -15h1100q21 0 35.5 15t14.5 35v100q0 21 -14.5 35.5t-35.5 14.5h-1100q-21 0 -35.5 -14.5t-14.5 -35.5v-100zM0 650q0 -20 14.5 -35t35.5 -15h1100q21 0 35.5 15t14.5 35v100q0 21 -14.5 35.5t-35.5 14.5h-1100q-21 0 -35.5 -14.5t-14.5 -35.5 v-100zM200 350q0 -20 14.5 -35t35.5 -15h700q21 0 35.5 15t14.5 35v100q0 21 -14.5 35.5t-35.5 14.5h-700q-21 0 -35.5 -14.5t-14.5 -35.5v-100zM200 950q0 -20 14.5 -35t35.5 -15h700q21 0 35.5 15t14.5 35v100q0 21 -14.5 35.5t-35.5 14.5h-700q-21 0 -35.5 -14.5 t-14.5 -35.5v-100z" />
|
||||
<glyph unicode="" d="M0 50v100q0 21 14.5 35.5t35.5 14.5h1100q21 0 35.5 -14.5t14.5 -35.5v-100q0 -20 -14.5 -35t-35.5 -15h-1100q-21 0 -35.5 15t-14.5 35zM100 650v100q0 21 14.5 35.5t35.5 14.5h1000q21 0 35.5 -14.5t14.5 -35.5v-100q0 -20 -14.5 -35t-35.5 -15h-1000q-21 0 -35.5 15 t-14.5 35zM300 350v100q0 21 14.5 35.5t35.5 14.5h800q21 0 35.5 -14.5t14.5 -35.5v-100q0 -20 -14.5 -35t-35.5 -15h-800q-21 0 -35.5 15t-14.5 35zM500 950v100q0 21 14.5 35.5t35.5 14.5h600q21 0 35.5 -14.5t14.5 -35.5v-100q0 -20 -14.5 -35t-35.5 -15h-600 q-21 0 -35.5 15t-14.5 35z" />
|
||||
<glyph unicode="" d="M0 50v100q0 21 14.5 35.5t35.5 14.5h1100q21 0 35.5 -14.5t14.5 -35.5v-100q0 -20 -14.5 -35t-35.5 -15h-1100q-21 0 -35.5 15t-14.5 35zM0 350v100q0 21 14.5 35.5t35.5 14.5h1100q21 0 35.5 -14.5t14.5 -35.5v-100q0 -20 -14.5 -35t-35.5 -15h-1100q-21 0 -35.5 15 t-14.5 35zM0 650v100q0 21 14.5 35.5t35.5 14.5h1100q21 0 35.5 -14.5t14.5 -35.5v-100q0 -20 -14.5 -35t-35.5 -15h-1100q-21 0 -35.5 15t-14.5 35zM0 950v100q0 21 14.5 35.5t35.5 14.5h1100q21 0 35.5 -14.5t14.5 -35.5v-100q0 -20 -14.5 -35t-35.5 -15h-1100 q-21 0 -35.5 15t-14.5 35z" />
|
||||
<glyph unicode="" d="M0 50v100q0 21 14.5 35.5t35.5 14.5h100q21 0 35.5 -14.5t14.5 -35.5v-100q0 -20 -14.5 -35t-35.5 -15h-100q-21 0 -35.5 15t-14.5 35zM0 350v100q0 21 14.5 35.5t35.5 14.5h100q21 0 35.5 -14.5t14.5 -35.5v-100q0 -20 -14.5 -35t-35.5 -15h-100q-21 0 -35.5 15 t-14.5 35zM0 650v100q0 21 14.5 35.5t35.5 14.5h100q21 0 35.5 -14.5t14.5 -35.5v-100q0 -20 -14.5 -35t-35.5 -15h-100q-21 0 -35.5 15t-14.5 35zM0 950v100q0 21 14.5 35.5t35.5 14.5h100q21 0 35.5 -14.5t14.5 -35.5v-100q0 -20 -14.5 -35t-35.5 -15h-100q-21 0 -35.5 15 t-14.5 35zM300 50v100q0 21 14.5 35.5t35.5 14.5h800q21 0 35.5 -14.5t14.5 -35.5v-100q0 -20 -14.5 -35t-35.5 -15h-800q-21 0 -35.5 15t-14.5 35zM300 350v100q0 21 14.5 35.5t35.5 14.5h800q21 0 35.5 -14.5t14.5 -35.5v-100q0 -20 -14.5 -35t-35.5 -15h-800 q-21 0 -35.5 15t-14.5 35zM300 650v100q0 21 14.5 35.5t35.5 14.5h800q21 0 35.5 -14.5t14.5 -35.5v-100q0 -20 -14.5 -35t-35.5 -15h-800q-21 0 -35.5 15t-14.5 35zM300 950v100q0 21 14.5 35.5t35.5 14.5h800q21 0 35.5 -14.5t14.5 -35.5v-100q0 -20 -14.5 -35t-35.5 -15 h-800q-21 0 -35.5 15t-14.5 35z" />
|
||||
<glyph unicode="" d="M-101 500v100h201v75l166 -125l-166 -125v75h-201zM300 0h100v1100h-100v-1100zM500 50q0 -20 14.5 -35t35.5 -15h600q20 0 35 15t15 35v100q0 21 -15 35.5t-35 14.5h-600q-21 0 -35.5 -14.5t-14.5 -35.5v-100zM500 350q0 -20 14.5 -35t35.5 -15h300q20 0 35 15t15 35 v100q0 21 -15 35.5t-35 14.5h-300q-21 0 -35.5 -14.5t-14.5 -35.5v-100zM500 650q0 -20 14.5 -35t35.5 -15h500q20 0 35 15t15 35v100q0 21 -15 35.5t-35 14.5h-500q-21 0 -35.5 -14.5t-14.5 -35.5v-100zM500 950q0 -20 14.5 -35t35.5 -15h100q20 0 35 15t15 35v100 q0 21 -15 35.5t-35 14.5h-100q-21 0 -35.5 -14.5t-14.5 -35.5v-100z" />
|
||||
<glyph unicode="" d="M1 50q0 -20 14.5 -35t35.5 -15h600q20 0 35 15t15 35v100q0 21 -15 35.5t-35 14.5h-600q-21 0 -35.5 -14.5t-14.5 -35.5v-100zM1 350q0 -20 14.5 -35t35.5 -15h300q20 0 35 15t15 35v100q0 21 -15 35.5t-35 14.5h-300q-21 0 -35.5 -14.5t-14.5 -35.5v-100zM1 650 q0 -20 14.5 -35t35.5 -15h500q20 0 35 15t15 35v100q0 21 -15 35.5t-35 14.5h-500q-21 0 -35.5 -14.5t-14.5 -35.5v-100zM1 950q0 -20 14.5 -35t35.5 -15h100q20 0 35 15t15 35v100q0 21 -15 35.5t-35 14.5h-100q-21 0 -35.5 -14.5t-14.5 -35.5v-100zM801 0v1100h100v-1100 h-100zM934 550l167 -125v75h200v100h-200v75z" />
|
||||
<glyph unicode="" d="M0 275v650q0 31 22 53t53 22h750q31 0 53 -22t22 -53v-650q0 -31 -22 -53t-53 -22h-750q-31 0 -53 22t-22 53zM900 600l300 300v-600z" />
|
||||
<glyph unicode="" d="M0 44v1012q0 18 13 31t31 13h1112q19 0 31.5 -13t12.5 -31v-1012q0 -18 -12.5 -31t-31.5 -13h-1112q-18 0 -31 13t-13 31zM100 263l247 182l298 -131l-74 156l293 318l236 -288v500h-1000v-737zM208 750q0 56 39 95t95 39t95 -39t39 -95t-39 -95t-95 -39t-95 39t-39 95z " />
|
||||
<glyph unicode="" d="M148 745q0 124 60.5 231.5t165 172t226.5 64.5q123 0 227 -63t164.5 -169.5t60.5 -229.5t-73 -272q-73 -114 -166.5 -237t-150.5 -189l-57 -66q-10 9 -27 26t-66.5 70.5t-96 109t-104 135.5t-100.5 155q-63 139 -63 262zM342 772q0 -107 75.5 -182.5t181.5 -75.5 q107 0 182.5 75.5t75.5 182.5t-75.5 182t-182.5 75t-182 -75.5t-75 -181.5z" />
|
||||
<glyph unicode="" d="M1 600q0 122 47.5 233t127.5 191t191 127.5t233 47.5t233 -47.5t191 -127.5t127.5 -191t47.5 -233t-47.5 -233t-127.5 -191t-191 -127.5t-233 -47.5t-233 47.5t-191 127.5t-127.5 191t-47.5 233zM173 600q0 -177 125.5 -302t301.5 -125v854q-176 0 -301.5 -125 t-125.5 -302z" />
|
||||
<glyph unicode="" d="M117 406q0 94 34 186t88.5 172.5t112 159t115 177t87.5 194.5q21 -71 57.5 -142.5t76 -130.5t83 -118.5t82 -117t70 -116t50 -125.5t18.5 -136q0 -89 -39 -165.5t-102 -126.5t-140 -79.5t-156 -33.5q-114 6 -211.5 53t-161.5 139t-64 210zM243 414q14 -82 59.5 -136 t136.5 -80l16 98q-7 6 -18 17t-34 48t-33 77q-15 73 -14 143.5t10 122.5l9 51q-92 -110 -119.5 -185t-12.5 -156z" />
|
||||
<glyph unicode="" d="M0 400v300q0 165 117.5 282.5t282.5 117.5q366 -6 397 -14l-186 -186h-311q-41 0 -70.5 -29.5t-29.5 -70.5v-500q0 -41 29.5 -70.5t70.5 -29.5h500q41 0 70.5 29.5t29.5 70.5v125l200 200v-225q0 -165 -117.5 -282.5t-282.5 -117.5h-300q-165 0 -282.5 117.5 t-117.5 282.5zM436 341l161 50l412 412l-114 113l-405 -405zM995 1015l113 -113l113 113l-21 85l-92 28z" />
|
||||
<glyph unicode="" d="M0 400v300q0 165 117.5 282.5t282.5 117.5h261l2 -80q-133 -32 -218 -120h-145q-41 0 -70.5 -29.5t-29.5 -70.5v-500q0 -41 29.5 -70.5t70.5 -29.5h500q41 0 70.5 29.5t29.5 70.5l200 153v-53q0 -165 -117.5 -282.5t-282.5 -117.5h-300q-165 0 -282.5 117.5t-117.5 282.5 zM423 524q30 38 81.5 64t103 35.5t99 14t77.5 3.5l29 -1v-209l360 324l-359 318v-216q-7 0 -19 -1t-48 -8t-69.5 -18.5t-76.5 -37t-76.5 -59t-62 -88t-39.5 -121.5z" />
|
||||
<glyph unicode="" d="M0 400v300q0 165 117.5 282.5t282.5 117.5h300q61 0 127 -23l-178 -177h-349q-41 0 -70.5 -29.5t-29.5 -70.5v-500q0 -41 29.5 -70.5t70.5 -29.5h500q41 0 70.5 29.5t29.5 70.5v69l200 200v-169q0 -165 -117.5 -282.5t-282.5 -117.5h-300q-165 0 -282.5 117.5 t-117.5 282.5zM342 632l283 -284l567 567l-137 137l-430 -431l-146 147z" />
|
||||
<glyph unicode="" d="M0 603l300 296v-198h200v200h-200l300 300l295 -300h-195v-200h200v198l300 -296l-300 -300v198h-200v-200h195l-295 -300l-300 300h200v200h-200v-198z" />
|
||||
<glyph unicode="" d="M200 50v1000q0 21 14.5 35.5t35.5 14.5h100q21 0 35.5 -14.5t14.5 -35.5v-437l500 487v-1100l-500 488v-438q0 -21 -14.5 -35.5t-35.5 -14.5h-100q-21 0 -35.5 14.5t-14.5 35.5z" />
|
||||
<glyph unicode="" d="M0 50v1000q0 21 14.5 35.5t35.5 14.5h100q21 0 35.5 -14.5t14.5 -35.5v-437l500 487v-487l500 487v-1100l-500 488v-488l-500 488v-438q0 -21 -14.5 -35.5t-35.5 -14.5h-100q-21 0 -35.5 14.5t-14.5 35.5z" />
|
||||
<glyph unicode="" d="M136 550l564 550v-487l500 487v-1100l-500 488v-488z" />
|
||||
<glyph unicode="" d="M200 0l900 550l-900 550v-1100z" />
|
||||
<glyph unicode="" d="M200 150q0 -21 14.5 -35.5t35.5 -14.5h200q21 0 35.5 14.5t14.5 35.5v800q0 21 -14.5 35.5t-35.5 14.5h-200q-21 0 -35.5 -14.5t-14.5 -35.5v-800zM600 150q0 -21 14.5 -35.5t35.5 -14.5h200q21 0 35.5 14.5t14.5 35.5v800q0 21 -14.5 35.5t-35.5 14.5h-200 q-21 0 -35.5 -14.5t-14.5 -35.5v-800z" />
|
||||
<glyph unicode="" d="M200 150q0 -20 14.5 -35t35.5 -15h800q21 0 35.5 15t14.5 35v800q0 21 -14.5 35.5t-35.5 14.5h-800q-21 0 -35.5 -14.5t-14.5 -35.5v-800z" />
|
||||
<glyph unicode="" d="M0 0v1100l500 -487v487l564 -550l-564 -550v488z" />
|
||||
<glyph unicode="" d="M0 0v1100l500 -487v487l500 -487v437q0 21 14.5 35.5t35.5 14.5h100q21 0 35.5 -14.5t14.5 -35.5v-1000q0 -21 -14.5 -35.5t-35.5 -14.5h-100q-21 0 -35.5 14.5t-14.5 35.5v438l-500 -488v488z" />
|
||||
<glyph unicode="" d="M300 0v1100l500 -487v437q0 21 14.5 35.5t35.5 14.5h100q21 0 35.5 -14.5t14.5 -35.5v-1000q0 -21 -14.5 -35.5t-35.5 -14.5h-100q-21 0 -35.5 14.5t-14.5 35.5v438z" />
|
||||
<glyph unicode="" d="M100 250v100q0 21 14.5 35.5t35.5 14.5h1000q21 0 35.5 -14.5t14.5 -35.5v-100q0 -21 -14.5 -35.5t-35.5 -14.5h-1000q-21 0 -35.5 14.5t-14.5 35.5zM100 500h1100l-550 564z" />
|
||||
<glyph unicode="" d="M185 599l592 -592l240 240l-353 353l353 353l-240 240z" />
|
||||
<glyph unicode="" d="M272 194l353 353l-353 353l241 240l572 -571l21 -22l-1 -1v-1l-592 -591z" />
|
||||
<glyph unicode="" d="M3 600q0 162 80 299.5t217.5 217.5t299.5 80t299.5 -80t217.5 -217.5t80 -299.5t-80 -299.5t-217.5 -217.5t-299.5 -80t-299.5 80t-217.5 217.5t-80 299.5zM300 500h200v-200h200v200h200v200h-200v200h-200v-200h-200v-200z" />
|
||||
<glyph unicode="" d="M3 600q0 162 80 299.5t217.5 217.5t299.5 80t299.5 -80t217.5 -217.5t80 -299.5t-80 -299.5t-217.5 -217.5t-299.5 -80t-299.5 80t-217.5 217.5t-80 299.5zM300 500h600v200h-600v-200z" />
|
||||
<glyph unicode="" d="M3 600q0 162 80 299.5t217.5 217.5t299.5 80t299.5 -80t217.5 -217.5t80 -299.5t-80 -299.5t-217.5 -217.5t-299.5 -80t-299.5 80t-217.5 217.5t-80 299.5zM246 459l213 -213l141 142l141 -142l213 213l-142 141l142 141l-213 212l-141 -141l-141 142l-212 -213l141 -141 z" />
|
||||
<glyph unicode="" d="M3 600q0 162 80 299.5t217.5 217.5t299.5 80t299.5 -80t217.5 -217.5t80 -299.5t-80 -299.5t-217.5 -217.5t-299.5 -80t-299.5 80t-217.5 217.5t-80 299.5zM270 551l276 -277l411 411l-175 174l-236 -236l-102 102z" />
|
||||
<glyph unicode="" d="M3 600q0 162 80 299.5t217.5 217.5t299.5 80t299.5 -80t217.5 -217.5t80 -299.5t-80 -299.5t-217.5 -217.5t-299.5 -80t-299.5 80t-217.5 217.5t-80 299.5zM364 700h143q4 0 11.5 -1t11 -1t6.5 3t3 9t1 11t3.5 8.5t3.5 6t5.5 4t6.5 2.5t9 1.5t9 0.5h11.5h12.5 q19 0 30 -10t11 -26q0 -22 -4 -28t-27 -22q-5 -1 -12.5 -3t-27 -13.5t-34 -27t-26.5 -46t-11 -68.5h200q5 3 14 8t31.5 25.5t39.5 45.5t31 69t14 94q0 51 -17.5 89t-42 58t-58.5 32t-58.5 15t-51.5 3q-50 0 -90.5 -12t-75 -38.5t-53.5 -74.5t-19 -114zM500 300h200v100h-200 v-100z" />
|
||||
<glyph unicode="" d="M3 600q0 162 80 299.5t217.5 217.5t299.5 80t299.5 -80t217.5 -217.5t80 -299.5t-80 -299.5t-217.5 -217.5t-299.5 -80t-299.5 80t-217.5 217.5t-80 299.5zM400 300h400v100h-100v300h-300v-100h100v-200h-100v-100zM500 800h200v100h-200v-100z" />
|
||||
<glyph unicode="" d="M0 500v200h195q31 125 98.5 199.5t206.5 100.5v200h200v-200q54 -20 113 -60t112.5 -105.5t71.5 -134.5h203v-200h-203q-25 -102 -116.5 -186t-180.5 -117v-197h-200v197q-140 27 -208 102.5t-98 200.5h-194zM290 500q24 -73 79.5 -127.5t130.5 -78.5v206h200v-206 q149 48 201 206h-201v200h200q-25 74 -75.5 127t-124.5 77v-204h-200v203q-75 -23 -130 -77t-79 -126h209v-200h-210z" />
|
||||
<glyph unicode="" d="M4 600q0 162 80 299t217 217t299 80t299 -80t217 -217t80 -299t-80 -299t-217 -217t-299 -80t-299 80t-217 217t-80 299zM186 600q0 -171 121.5 -292.5t292.5 -121.5t292.5 121.5t121.5 292.5t-121.5 292.5t-292.5 121.5t-292.5 -121.5t-121.5 -292.5zM356 465l135 135 l-135 135l109 109l135 -135l135 135l109 -109l-135 -135l135 -135l-109 -109l-135 135l-135 -135z" />
|
||||
<glyph unicode="" d="M4 600q0 162 80 299t217 217t299 80t299 -80t217 -217t80 -299t-80 -299t-217 -217t-299 -80t-299 80t-217 217t-80 299zM186 600q0 -171 121.5 -292.5t292.5 -121.5t292.5 121.5t121.5 292.5t-121.5 292.5t-292.5 121.5t-292.5 -121.5t-121.5 -292.5zM322 537l141 141 l87 -87l204 205l142 -142l-346 -345z" />
|
||||
<glyph unicode="" d="M4 600q0 162 80 299t217 217t299 80t299 -80t217 -217t80 -299t-80 -299t-217 -217t-299 -80t-299 80t-217 217t-80 299zM186 600q0 -115 62 -215l568 567q-100 62 -216 62q-171 0 -292.5 -121.5t-121.5 -292.5zM391 245q97 -59 209 -59q171 0 292.5 121.5t121.5 292.5 q0 112 -59 209z" />
|
||||
<glyph unicode="" d="M0 547l600 453v-300h600v-300h-600v-301z" />
|
||||
<glyph unicode="" d="M0 400v300h600v300l600 -453l-600 -448v301h-600z" />
|
||||
<glyph unicode="" d="M204 600l450 600l444 -600h-298v-600h-300v600h-296z" />
|
||||
<glyph unicode="" d="M104 600h296v600h300v-600h298l-449 -600z" />
|
||||
<glyph unicode="" d="M0 200q6 132 41 238.5t103.5 193t184 138t271.5 59.5v271l600 -453l-600 -448v301q-95 -2 -183 -20t-170 -52t-147 -92.5t-100 -135.5z" />
|
||||
<glyph unicode="" d="M0 0v400l129 -129l294 294l142 -142l-294 -294l129 -129h-400zM635 777l142 -142l294 294l129 -129v400h-400l129 -129z" />
|
||||
<glyph unicode="" d="M34 176l295 295l-129 129h400v-400l-129 130l-295 -295zM600 600v400l129 -129l295 295l142 -141l-295 -295l129 -130h-400z" />
|
||||
<glyph unicode="" d="M23 600q0 118 45.5 224.5t123 184t184 123t224.5 45.5t224.5 -45.5t184 -123t123 -184t45.5 -224.5t-45.5 -224.5t-123 -184t-184 -123t-224.5 -45.5t-224.5 45.5t-184 123t-123 184t-45.5 224.5zM456 851l58 -302q4 -20 21.5 -34.5t37.5 -14.5h54q20 0 37.5 14.5 t21.5 34.5l58 302q4 20 -8 34.5t-32 14.5h-207q-21 0 -33 -14.5t-8 -34.5zM500 300h200v100h-200v-100z" />
|
||||
<glyph unicode="" d="M0 800h100v-200h400v300h200v-300h400v200h100v100h-111q1 1 1 6.5t-1.5 15t-3.5 17.5l-34 172q-11 39 -41.5 63t-69.5 24q-32 0 -61 -17l-239 -144q-22 -13 -40 -35q-19 24 -40 36l-238 144q-33 18 -62 18q-39 0 -69.5 -23t-40.5 -61l-35 -177q-2 -8 -3 -18t-1 -15v-6 h-111v-100zM100 0h400v400h-400v-400zM200 900q-3 0 14 48t36 96l18 47l213 -191h-281zM700 0v400h400v-400h-400zM731 900l202 197q5 -12 12 -32.5t23 -64t25 -72t7 -28.5h-269z" />
|
||||
<glyph unicode="" d="M0 -22v143l216 193q-9 53 -13 83t-5.5 94t9 113t38.5 114t74 124q47 60 99.5 102.5t103 68t127.5 48t145.5 37.5t184.5 43.5t220 58.5q0 -189 -22 -343t-59 -258t-89 -181.5t-108.5 -120t-122 -68t-125.5 -30t-121.5 -1.5t-107.5 12.5t-87.5 17t-56.5 7.5l-99 -55z M238.5 300.5q19.5 -6.5 86.5 76.5q55 66 367 234q70 38 118.5 69.5t102 79t99 111.5t86.5 148q22 50 24 60t-6 19q-7 5 -17 5t-26.5 -14.5t-33.5 -39.5q-35 -51 -113.5 -108.5t-139.5 -89.5l-61 -32q-369 -197 -458 -401q-48 -111 -28.5 -117.5z" />
|
||||
<glyph unicode="" d="M111 408q0 -33 5 -63q9 -56 44 -119.5t105 -108.5q31 -21 64 -16t62 23.5t57 49.5t48 61.5t35 60.5q32 66 39 184.5t-13 157.5q79 -80 122 -164t26 -184q-5 -33 -20.5 -69.5t-37.5 -80.5q-10 -19 -14.5 -29t-12 -26t-9 -23.5t-3 -19t2.5 -15.5t11 -9.5t19.5 -5t30.5 2.5 t42 8q57 20 91 34t87.5 44.5t87 64t65.5 88.5t47 122q38 172 -44.5 341.5t-246.5 278.5q22 -44 43 -129q39 -159 -32 -154q-15 2 -33 9q-79 33 -120.5 100t-44 175.5t48.5 257.5q-13 -8 -34 -23.5t-72.5 -66.5t-88.5 -105.5t-60 -138t-8 -166.5q2 -12 8 -41.5t8 -43t6 -39.5 t3.5 -39.5t-1 -33.5t-6 -31.5t-13.5 -24t-21 -20.5t-31 -12q-38 -10 -67 13t-40.5 61.5t-15 81.5t10.5 75q-52 -46 -83.5 -101t-39 -107t-7.5 -85z" />
|
||||
<glyph unicode="" d="M-61 600l26 40q6 10 20 30t49 63.5t74.5 85.5t97 90t116.5 83.5t132.5 59t145.5 23.5t145.5 -23.5t132.5 -59t116.5 -83.5t97 -90t74.5 -85.5t49 -63.5t20 -30l26 -40l-26 -40q-6 -10 -20 -30t-49 -63.5t-74.5 -85.5t-97 -90t-116.5 -83.5t-132.5 -59t-145.5 -23.5 t-145.5 23.5t-132.5 59t-116.5 83.5t-97 90t-74.5 85.5t-49 63.5t-20 30zM120 600q7 -10 40.5 -58t56 -78.5t68 -77.5t87.5 -75t103 -49.5t125 -21.5t123.5 20t100.5 45.5t85.5 71.5t66.5 75.5t58 81.5t47 66q-1 1 -28.5 37.5t-42 55t-43.5 53t-57.5 63.5t-58.5 54 q49 -74 49 -163q0 -124 -88 -212t-212 -88t-212 88t-88 212q0 85 46 158q-102 -87 -226 -258zM377 656q49 -124 154 -191l105 105q-37 24 -75 72t-57 84l-20 36z" />
|
||||
<glyph unicode="" d="M-61 600l26 40q6 10 20 30t49 63.5t74.5 85.5t97 90t116.5 83.5t132.5 59t145.5 23.5q61 0 121 -17l37 142h148l-314 -1200h-148l37 143q-82 21 -165 71.5t-140 102t-109.5 112t-72 88.5t-29.5 43zM120 600q210 -282 393 -336l37 141q-107 18 -178.5 101.5t-71.5 193.5 q0 85 46 158q-102 -87 -226 -258zM377 656q49 -124 154 -191l47 47l23 87q-30 28 -59 69t-44 68l-14 26zM780 161l38 145q22 15 44.5 34t46 44t40.5 44t41 50.5t33.5 43.5t33 44t24.5 34q-97 127 -140 175l39 146q67 -54 131.5 -125.5t87.5 -103.5t36 -52l26 -40l-26 -40 q-7 -12 -25.5 -38t-63.5 -79.5t-95.5 -102.5t-124 -100t-146.5 -79z" />
|
||||
<glyph unicode="" d="M-97.5 34q13.5 -34 50.5 -34h1294q37 0 50.5 35.5t-7.5 67.5l-642 1056q-20 34 -48 36.5t-48 -29.5l-642 -1066q-21 -32 -7.5 -66zM155 200l445 723l445 -723h-345v100h-200v-100h-345zM500 600l100 -300l100 300v100h-200v-100z" />
|
||||
<glyph unicode="" d="M100 262v41q0 20 11 44.5t26 38.5l363 325v339q0 62 44 106t106 44t106 -44t44 -106v-339l363 -325q15 -14 26 -38.5t11 -44.5v-41q0 -20 -12 -26.5t-29 5.5l-359 249v-263q100 -91 100 -113v-64q0 -20 -13 -28.5t-32 0.5l-94 78h-222l-94 -78q-19 -9 -32 -0.5t-13 28.5 v64q0 22 100 113v263l-359 -249q-17 -12 -29 -5.5t-12 26.5z" />
|
||||
<glyph unicode="" d="M0 50q0 -20 14.5 -35t35.5 -15h1000q21 0 35.5 15t14.5 35v750h-1100v-750zM0 900h1100v150q0 21 -14.5 35.5t-35.5 14.5h-150v100h-100v-100h-500v100h-100v-100h-150q-21 0 -35.5 -14.5t-14.5 -35.5v-150zM100 100v100h100v-100h-100zM100 300v100h100v-100h-100z M100 500v100h100v-100h-100zM300 100v100h100v-100h-100zM300 300v100h100v-100h-100zM300 500v100h100v-100h-100zM500 100v100h100v-100h-100zM500 300v100h100v-100h-100zM500 500v100h100v-100h-100zM700 100v100h100v-100h-100zM700 300v100h100v-100h-100zM700 500 v100h100v-100h-100zM900 100v100h100v-100h-100zM900 300v100h100v-100h-100zM900 500v100h100v-100h-100z" />
|
||||
<glyph unicode="" d="M0 200v200h259l600 600h241v198l300 -295l-300 -300v197h-159l-600 -600h-341zM0 800h259l122 -122l141 142l-181 180h-341v-200zM678 381l141 142l122 -123h159v198l300 -295l-300 -300v197h-241z" />
|
||||
<glyph unicode="" d="M0 400v600q0 41 29.5 70.5t70.5 29.5h1000q41 0 70.5 -29.5t29.5 -70.5v-600q0 -41 -29.5 -70.5t-70.5 -29.5h-596l-304 -300v300h-100q-41 0 -70.5 29.5t-29.5 70.5z" />
|
||||
<glyph unicode="" d="M100 600v200h300v-250q0 -113 6 -145q17 -92 102 -117q39 -11 92 -11q37 0 66.5 5.5t50 15.5t36 24t24 31.5t14 37.5t7 42t2.5 45t0 47v25v250h300v-200q0 -42 -3 -83t-15 -104t-31.5 -116t-58 -109.5t-89 -96.5t-129 -65.5t-174.5 -25.5t-174.5 25.5t-129 65.5t-89 96.5 t-58 109.5t-31.5 116t-15 104t-3 83zM100 900v300h300v-300h-300zM800 900v300h300v-300h-300z" />
|
||||
<glyph unicode="" d="M-30 411l227 -227l352 353l353 -353l226 227l-578 579z" />
|
||||
<glyph unicode="" d="M70 797l580 -579l578 579l-226 227l-353 -353l-352 353z" />
|
||||
<glyph unicode="" d="M-198 700l299 283l300 -283h-203v-400h385l215 -200h-800v600h-196zM402 1000l215 -200h381v-400h-198l299 -283l299 283h-200v600h-796z" />
|
||||
<glyph unicode="" d="M18 939q-5 24 10 42q14 19 39 19h896l38 162q5 17 18.5 27.5t30.5 10.5h94q20 0 35 -14.5t15 -35.5t-15 -35.5t-35 -14.5h-54l-201 -961q-2 -4 -6 -10.5t-19 -17.5t-33 -11h-31v-50q0 -20 -14.5 -35t-35.5 -15t-35.5 15t-14.5 35v50h-300v-50q0 -20 -14.5 -35t-35.5 -15 t-35.5 15t-14.5 35v50h-50q-21 0 -35.5 15t-14.5 35q0 21 14.5 35.5t35.5 14.5h535l48 200h-633q-32 0 -54.5 21t-27.5 43z" />
|
||||
<glyph unicode="" d="M0 0v800h1200v-800h-1200zM0 900v100h200q0 41 29.5 70.5t70.5 29.5h300q41 0 70.5 -29.5t29.5 -70.5h500v-100h-1200z" />
|
||||
<glyph unicode="" d="M1 0l300 700h1200l-300 -700h-1200zM1 400v600h200q0 41 29.5 70.5t70.5 29.5h300q41 0 70.5 -29.5t29.5 -70.5h500v-200h-1000z" />
|
||||
<glyph unicode="" d="M302 300h198v600h-198l298 300l298 -300h-198v-600h198l-298 -300z" />
|
||||
<glyph unicode="" d="M0 600l300 298v-198h600v198l300 -298l-300 -297v197h-600v-197z" />
|
||||
<glyph unicode="" d="M0 100v100q0 41 29.5 70.5t70.5 29.5h1000q41 0 70.5 -29.5t29.5 -70.5v-100q0 -41 -29.5 -70.5t-70.5 -29.5h-1000q-41 0 -70.5 29.5t-29.5 70.5zM31 400l172 739q5 22 23 41.5t38 19.5h672q19 0 37.5 -22.5t23.5 -45.5l172 -732h-1138zM800 100h100v100h-100v-100z M1000 100h100v100h-100v-100z" />
|
||||
<glyph unicode="" d="M-101 600v50q0 24 25 49t50 38l25 13v-250l-11 5.5t-24 14t-30 21.5t-24 27.5t-11 31.5zM100 500v250v8v8v7t0.5 7t1.5 5.5t2 5t3 4t4.5 3.5t6 1.5t7.5 0.5h200l675 250v-850l-675 200h-38l47 -276q2 -12 -3 -17.5t-11 -6t-21 -0.5h-8h-83q-20 0 -34.5 14t-18.5 35 q-55 337 -55 351zM1100 200v850q0 21 14.5 35.5t35.5 14.5q20 0 35 -14.5t15 -35.5v-850q0 -20 -15 -35t-35 -15q-21 0 -35.5 15t-14.5 35z" />
|
||||
<glyph unicode="" d="M74 350q0 21 13.5 35.5t33.5 14.5h18l117 173l63 327q15 77 76 140t144 83l-18 32q-6 19 3 32t29 13h94q20 0 29 -10.5t3 -29.5q-18 -36 -18 -37q83 -19 144 -82.5t76 -140.5l63 -327l118 -173h17q20 0 33.5 -14.5t13.5 -35.5q0 -20 -13 -40t-31 -27q-8 -3 -23 -8.5 t-65 -20t-103 -25t-132.5 -19.5t-158.5 -9q-125 0 -245.5 20.5t-178.5 40.5l-58 20q-18 7 -31 27.5t-13 40.5zM497 110q12 -49 40 -79.5t63 -30.5t63 30.5t39 79.5q-48 -6 -102 -6t-103 6z" />
|
||||
<glyph unicode="" d="M21 445l233 -45l-78 -224l224 78l45 -233l155 179l155 -179l45 233l224 -78l-78 224l234 45l-180 155l180 156l-234 44l78 225l-224 -78l-45 233l-155 -180l-155 180l-45 -233l-224 78l78 -225l-233 -44l179 -156z" />
|
||||
<glyph unicode="" d="M0 200h200v600h-200v-600zM300 275q0 -75 100 -75h61q124 -100 139 -100h250q46 0 83 57l238 344q29 31 29 74v100q0 44 -30.5 84.5t-69.5 40.5h-328q28 118 28 125v150q0 44 -30.5 84.5t-69.5 40.5h-50q-27 0 -51 -20t-38 -48l-96 -198l-145 -196q-20 -26 -20 -63v-400z M400 300v375l150 213l100 212h50v-175l-50 -225h450v-125l-250 -375h-214l-136 100h-100z" />
|
||||
<glyph unicode="" d="M0 400v600h200v-600h-200zM300 525v400q0 75 100 75h61q124 100 139 100h250q46 0 83 -57l238 -344q29 -31 29 -74v-100q0 -44 -30.5 -84.5t-69.5 -40.5h-328q28 -118 28 -125v-150q0 -44 -30.5 -84.5t-69.5 -40.5h-50q-27 0 -51 20t-38 48l-96 198l-145 196 q-20 26 -20 63zM400 525l150 -212l100 -213h50v175l-50 225h450v125l-250 375h-214l-136 -100h-100v-375z" />
|
||||
<glyph unicode="" d="M8 200v600h200v-600h-200zM308 275v525q0 17 14 35.5t28 28.5l14 9l362 230q14 6 25 6q17 0 29 -12l109 -112q14 -14 14 -34q0 -18 -11 -32l-85 -121h302q85 0 138.5 -38t53.5 -110t-54.5 -111t-138.5 -39h-107l-130 -339q-7 -22 -20.5 -41.5t-28.5 -19.5h-341 q-7 0 -90 81t-83 94zM408 289l100 -89h293l131 339q6 21 19.5 41t28.5 20h203q16 0 25 15t9 36q0 20 -9 34.5t-25 14.5h-457h-6.5h-7.5t-6.5 0.5t-6 1t-5 1.5t-5.5 2.5t-4 4t-4 5.5q-5 12 -5 20q0 14 10 27l147 183l-86 83l-339 -236v-503z" />
|
||||
<glyph unicode="" d="M-101 651q0 72 54 110t139 38l302 -1l-85 121q-11 16 -11 32q0 21 14 34l109 113q13 12 29 12q11 0 25 -6l365 -230q7 -4 17 -10.5t26.5 -26t16.5 -36.5v-526q0 -13 -86 -93.5t-94 -80.5h-341q-16 0 -29.5 20t-19.5 41l-130 339h-107q-84 0 -139 39t-55 111zM-1 601h222 q15 0 28.5 -20.5t19.5 -40.5l131 -339h293l107 89v502l-343 237l-87 -83l145 -184q10 -11 10 -26q0 -11 -5 -20q-1 -3 -3.5 -5.5l-4 -4t-5 -2.5t-5.5 -1.5t-6.5 -1t-6.5 -0.5h-7.5h-6.5h-476v-100zM1000 201v600h200v-600h-200z" />
|
||||
<glyph unicode="" d="M97 719l230 -363q4 -6 10.5 -15.5t26 -25t36.5 -15.5h525q13 0 94 83t81 90v342q0 15 -20 28.5t-41 19.5l-339 131v106q0 84 -39 139t-111 55t-110 -53.5t-38 -138.5v-302l-121 84q-15 12 -33.5 11.5t-32.5 -13.5l-112 -110q-22 -22 -6 -53zM172 739l83 86l183 -146 q22 -18 47 -5q3 1 5.5 3.5l4 4t2.5 5t1.5 5.5t1 6.5t0.5 6.5v7.5v6.5v456q0 22 25 31t50 -0.5t25 -30.5v-202q0 -16 20 -29.5t41 -19.5l339 -130v-294l-89 -100h-503zM400 0v200h600v-200h-600z" />
|
||||
<glyph unicode="" d="M2 585q-16 -31 6 -53l112 -110q13 -13 32 -13.5t34 10.5l121 85q0 -51 -0.5 -153.5t-0.5 -148.5q0 -84 38.5 -138t110.5 -54t111 55t39 139v106l339 131q20 6 40.5 19.5t20.5 28.5v342q0 7 -81 90t-94 83h-525q-17 0 -35.5 -14t-28.5 -28l-10 -15zM77 565l236 339h503 l89 -100v-294l-340 -130q-20 -6 -40 -20t-20 -29v-202q0 -22 -25 -31t-50 0t-25 31v456v14.5t-1.5 11.5t-5 12t-9.5 7q-24 13 -46 -5l-184 -146zM305 1104v200h600v-200h-600z" />
|
||||
<glyph unicode="" d="M5 597q0 122 47.5 232.5t127.5 190.5t190.5 127.5t232.5 47.5q162 0 299.5 -80t217.5 -218t80 -300t-80 -299.5t-217.5 -217.5t-299.5 -80t-300 80t-218 217.5t-80 299.5zM298 701l2 -201h300l-2 -194l402 294l-402 298v-197h-300z" />
|
||||
<glyph unicode="" d="M0 597q0 122 47.5 232.5t127.5 190.5t190.5 127.5t231.5 47.5q122 0 232.5 -47.5t190.5 -127.5t127.5 -190.5t47.5 -232.5q0 -162 -80 -299.5t-218 -217.5t-300 -80t-299.5 80t-217.5 217.5t-80 299.5zM200 600l402 -294l-2 194h300l2 201h-300v197z" />
|
||||
<glyph unicode="" d="M5 597q0 122 47.5 232.5t127.5 190.5t190.5 127.5t232.5 47.5q162 0 299.5 -80t217.5 -218t80 -300t-80 -299.5t-217.5 -217.5t-299.5 -80t-300 80t-218 217.5t-80 299.5zM300 600h200v-300h200v300h200l-300 400z" />
|
||||
<glyph unicode="" d="M5 597q0 122 47.5 232.5t127.5 190.5t190.5 127.5t232.5 47.5q162 0 299.5 -80t217.5 -218t80 -300t-80 -299.5t-217.5 -217.5t-299.5 -80t-300 80t-218 217.5t-80 299.5zM300 600l300 -400l300 400h-200v300h-200v-300h-200z" />
|
||||
<glyph unicode="" d="M5 597q0 122 47.5 232.5t127.5 190.5t190.5 127.5t232.5 47.5q121 0 231.5 -47.5t190.5 -127.5t127.5 -190.5t47.5 -232.5q0 -162 -80 -299.5t-217.5 -217.5t-299.5 -80t-300 80t-218 217.5t-80 299.5zM254 780q-8 -33 5.5 -92.5t7.5 -87.5q0 -9 17 -44t16 -60 q12 0 23 -5.5t23 -15t20 -13.5q24 -12 108 -42q22 -8 53 -31.5t59.5 -38.5t57.5 -11q8 -18 -15 -55t-20 -57q42 -71 87 -80q0 -6 -3 -15.5t-3.5 -14.5t4.5 -17q104 -3 221 112q30 29 47 47t34.5 49t20.5 62q-14 9 -37 9.5t-36 7.5q-14 7 -49 15t-52 19q-9 0 -39.5 -0.5 t-46.5 -1.5t-39 -6.5t-39 -16.5q-50 -35 -66 -12q-4 2 -3.5 25.5t0.5 25.5q-6 13 -26.5 17t-24.5 7q2 22 -2 41t-16.5 28t-38.5 -20q-23 -25 -42 4q-19 28 -8 58q6 16 22 22q6 -1 26 -1.5t33.5 -4t19.5 -13.5q12 -19 32 -37.5t34 -27.5l14 -8q0 3 9.5 39.5t5.5 57.5 q-4 23 14.5 44.5t22.5 31.5q5 14 10 35t8.5 31t15.5 22.5t34 21.5q-6 18 10 37q8 0 23.5 -1.5t24.5 -1.5t20.5 4.5t20.5 15.5q-10 23 -30.5 42.5t-38 30t-49 26.5t-43.5 23q11 39 2 44q31 -13 58 -14.5t39 3.5l11 4q7 36 -16.5 53.5t-64.5 28.5t-56 23q-19 -3 -37 0 q-15 -12 -36.5 -21t-34.5 -12t-44 -8t-39 -6q-15 -3 -45.5 0.5t-45.5 -2.5q-21 -7 -52 -26.5t-34 -34.5q-3 -11 6.5 -22.5t8.5 -18.5q-3 -34 -27.5 -90.5t-29.5 -79.5zM518 916q3 12 16 30t16 25q10 -10 18.5 -10t14 6t14.5 14.5t16 12.5q0 -24 17 -66.5t17 -43.5 q-9 2 -31 5t-36 5t-32 8t-30 14zM692 1003h1h-1z" />
|
||||
<glyph unicode="" d="M0 164.5q0 21.5 15 37.5l600 599q-33 101 6 201.5t135 154.5q164 92 306 -9l-259 -138l145 -232l251 126q13 -175 -151 -267q-123 -70 -253 -23l-596 -596q-15 -16 -36.5 -16t-36.5 16l-111 110q-15 15 -15 36.5z" />
|
||||
<glyph unicode="" horiz-adv-x="1220" d="M0 196v100q0 41 29.5 70.5t70.5 29.5h1000q41 0 70.5 -29.5t29.5 -70.5v-100q0 -41 -29.5 -70.5t-70.5 -29.5h-1000q-41 0 -70.5 29.5t-29.5 70.5zM0 596v100q0 41 29.5 70.5t70.5 29.5h1000q41 0 70.5 -29.5t29.5 -70.5v-100q0 -41 -29.5 -70.5t-70.5 -29.5h-1000 q-41 0 -70.5 29.5t-29.5 70.5zM0 996v100q0 41 29.5 70.5t70.5 29.5h1000q41 0 70.5 -29.5t29.5 -70.5v-100q0 -41 -29.5 -70.5t-70.5 -29.5h-1000q-41 0 -70.5 29.5t-29.5 70.5zM600 596h500v100h-500v-100zM800 196h300v100h-300v-100zM900 996h200v100h-200v-100z" />
|
||||
<glyph unicode="" d="M100 1100v100h1000v-100h-1000zM150 1000h900l-350 -500v-300l-200 -200v500z" />
|
||||
<glyph unicode="" d="M0 200v200h1200v-200q0 -41 -29.5 -70.5t-70.5 -29.5h-1000q-41 0 -70.5 29.5t-29.5 70.5zM0 500v400q0 41 29.5 70.5t70.5 29.5h300v100q0 41 29.5 70.5t70.5 29.5h200q41 0 70.5 -29.5t29.5 -70.5v-100h300q41 0 70.5 -29.5t29.5 -70.5v-400h-500v100h-200v-100h-500z M500 1000h200v100h-200v-100z" />
|
||||
<glyph unicode="" d="M0 0v400l129 -129l200 200l142 -142l-200 -200l129 -129h-400zM0 800l129 129l200 -200l142 142l-200 200l129 129h-400v-400zM729 329l142 142l200 -200l129 129v-400h-400l129 129zM729 871l200 200l-129 129h400v-400l-129 129l-200 -200z" />
|
||||
<glyph unicode="" d="M0 596q0 162 80 299t217 217t299 80t299 -80t217 -217t80 -299t-80 -299t-217 -217t-299 -80t-299 80t-217 217t-80 299zM182 596q0 -172 121.5 -293t292.5 -121t292.5 121t121.5 293q0 171 -121.5 292.5t-292.5 121.5t-292.5 -121.5t-121.5 -292.5zM291 655 q0 23 15.5 38.5t38.5 15.5t39 -16t16 -38q0 -23 -16 -39t-39 -16q-22 0 -38 16t-16 39zM400 850q0 22 16 38.5t39 16.5q22 0 38 -16t16 -39t-16 -39t-38 -16q-23 0 -39 16.5t-16 38.5zM514 609q0 32 20.5 56.5t51.5 29.5l122 126l1 1q-9 14 -9 28q0 22 16 38.5t39 16.5 q22 0 38 -16t16 -39t-16 -39t-38 -16q-14 0 -29 10l-55 -145q17 -22 17 -51q0 -36 -25.5 -61.5t-61.5 -25.5t-61.5 25.5t-25.5 61.5zM800 655q0 22 16 38t39 16t38.5 -15.5t15.5 -38.5t-16 -39t-38 -16q-23 0 -39 16t-16 39z" />
|
||||
<glyph unicode="" d="M-40 375q-13 -95 35 -173q35 -57 94 -89t129 -32q63 0 119 28q33 16 65 40.5t52.5 45.5t59.5 64q40 44 57 61l394 394q35 35 47 84t-3 96q-27 87 -117 104q-20 2 -29 2q-46 0 -78.5 -16.5t-67.5 -51.5l-389 -396l-7 -7l69 -67l377 373q20 22 39 38q23 23 50 23 q38 0 53 -36q16 -39 -20 -75l-547 -547q-52 -52 -125 -52q-55 0 -100 33t-54 96q-5 35 2.5 66t31.5 63t42 50t56 54q24 21 44 41l348 348q52 52 82.5 79.5t84 54t107.5 26.5q25 0 48 -4q95 -17 154 -94.5t51 -175.5q-7 -101 -98 -192l-252 -249l-253 -256l7 -7l69 -60 l517 511q67 67 95 157t11 183q-16 87 -67 154t-130 103q-69 33 -152 33q-107 0 -197 -55q-40 -24 -111 -95l-512 -512q-68 -68 -81 -163z" />
|
||||
<glyph unicode="" d="M80 784q0 131 98.5 229.5t230.5 98.5q143 0 241 -129q103 129 246 129q129 0 226 -98.5t97 -229.5q0 -46 -17.5 -91t-61 -99t-77 -89.5t-104.5 -105.5q-197 -191 -293 -322l-17 -23l-16 23q-43 58 -100 122.5t-92 99.5t-101 100q-71 70 -104.5 105.5t-77 89.5t-61 99 t-17.5 91zM250 784q0 -27 30.5 -70t61.5 -75.5t95 -94.5l22 -22q93 -90 190 -201q82 92 195 203l12 12q64 62 97.5 97t64.5 79t31 72q0 71 -48 119.5t-105 48.5q-74 0 -132 -83l-118 -171l-114 174q-51 80 -123 80q-60 0 -109.5 -49.5t-49.5 -118.5z" />
|
||||
<glyph unicode="" d="M57 353q0 -95 66 -159l141 -142q68 -66 159 -66q93 0 159 66l283 283q66 66 66 159t-66 159l-141 141q-8 9 -19 17l-105 -105l212 -212l-389 -389l-247 248l95 95l-18 18q-46 45 -75 101l-55 -55q-66 -66 -66 -159zM269 706q0 -93 66 -159l141 -141q7 -7 19 -17l105 105 l-212 212l389 389l247 -247l-95 -96l18 -17q47 -49 77 -100l29 29q35 35 62.5 88t27.5 96q0 93 -66 159l-141 141q-66 66 -159 66q-95 0 -159 -66l-283 -283q-66 -64 -66 -159z" />
|
||||
<glyph unicode="" d="M200 100v953q0 21 30 46t81 48t129 38t163 15t162 -15t127 -38t79 -48t29 -46v-953q0 -41 -29.5 -70.5t-70.5 -29.5h-600q-41 0 -70.5 29.5t-29.5 70.5zM300 300h600v700h-600v-700zM496 150q0 -43 30.5 -73.5t73.5 -30.5t73.5 30.5t30.5 73.5t-30.5 73.5t-73.5 30.5 t-73.5 -30.5t-30.5 -73.5z" />
|
||||
<glyph unicode="" d="M0 0l303 380l207 208l-210 212h300l267 279l-35 36q-15 14 -15 35t15 35q14 15 35 15t35 -15l283 -282q15 -15 15 -36t-15 -35q-14 -15 -35 -15t-35 15l-36 35l-279 -267v-300l-212 210l-208 -207z" />
|
||||
<glyph unicode="" d="M295 433h139q5 -77 48.5 -126.5t117.5 -64.5v335q-6 1 -15.5 4t-11.5 3q-46 14 -79 26.5t-72 36t-62.5 52t-40 72.5t-16.5 99q0 92 44 159.5t109 101t144 40.5v78h100v-79q38 -4 72.5 -13.5t75.5 -31.5t71 -53.5t51.5 -84t24.5 -118.5h-159q-8 72 -35 109.5t-101 50.5 v-307l64 -14q34 -7 64 -16.5t70 -31.5t67.5 -52t47.5 -80.5t20 -112.5q0 -139 -89 -224t-244 -96v-77h-100v78q-152 17 -237 104q-40 40 -52.5 93.5t-15.5 139.5zM466 889q0 -29 8 -51t16.5 -34t29.5 -22.5t31 -13.5t38 -10q7 -2 11 -3v274q-61 -8 -97.5 -37.5t-36.5 -102.5 zM700 237q170 18 170 151q0 64 -44 99.5t-126 60.5v-311z" />
|
||||
<glyph unicode="" d="M100 600v100h166q-24 49 -44 104q-10 26 -14.5 55.5t-3 72.5t25 90t68.5 87q97 88 263 88q129 0 230 -89t101 -208h-153q0 52 -34 89.5t-74 51.5t-76 14q-37 0 -79 -14.5t-62 -35.5q-41 -44 -41 -101q0 -28 16.5 -69.5t28 -62.5t41.5 -72h241v-100h-197q8 -50 -2.5 -115 t-31.5 -94q-41 -59 -99 -113q35 11 84 18t70 7q33 1 103 -16t103 -17q76 0 136 30l50 -147q-41 -25 -80.5 -36.5t-59 -13t-61.5 -1.5q-23 0 -128 33t-155 29q-39 -4 -82 -17t-66 -25l-24 -11l-55 145l16.5 11t15.5 10t13.5 9.5t14.5 12t14.5 14t17.5 18.5q48 55 54 126.5 t-30 142.5h-221z" />
|
||||
<glyph unicode="" d="M2 300l298 -300l298 300h-198v900h-200v-900h-198zM602 900l298 300l298 -300h-198v-900h-200v900h-198z" />
|
||||
<glyph unicode="" d="M2 300h198v900h200v-900h198l-298 -300zM700 0v200h100v-100h200v-100h-300zM700 400v100h300v-200h-99v-100h-100v100h99v100h-200zM700 700v500h300v-500h-100v100h-100v-100h-100zM801 900h100v200h-100v-200z" />
|
||||
<glyph unicode="" d="M2 300h198v900h200v-900h198l-298 -300zM700 0v500h300v-500h-100v100h-100v-100h-100zM700 700v200h100v-100h200v-100h-300zM700 1100v100h300v-200h-99v-100h-100v100h99v100h-200zM801 200h100v200h-100v-200z" />
|
||||
<glyph unicode="" d="M2 300l298 -300l298 300h-198v900h-200v-900h-198zM800 100v400h300v-500h-100v100h-200zM800 1100v100h200v-500h-100v400h-100zM901 200h100v200h-100v-200z" />
|
||||
<glyph unicode="" d="M2 300l298 -300l298 300h-198v900h-200v-900h-198zM800 400v100h200v-500h-100v400h-100zM800 800v400h300v-500h-100v100h-200zM901 900h100v200h-100v-200z" />
|
||||
<glyph unicode="" d="M2 300l298 -300l298 300h-198v900h-200v-900h-198zM700 100v200h500v-200h-500zM700 400v200h400v-200h-400zM700 700v200h300v-200h-300zM700 1000v200h200v-200h-200z" />
|
||||
<glyph unicode="" d="M2 300l298 -300l298 300h-198v900h-200v-900h-198zM700 100v200h200v-200h-200zM700 400v200h300v-200h-300zM700 700v200h400v-200h-400zM700 1000v200h500v-200h-500z" />
|
||||
<glyph unicode="" d="M0 400v300q0 165 117.5 282.5t282.5 117.5h300q162 0 281 -118.5t119 -281.5v-300q0 -165 -118.5 -282.5t-281.5 -117.5h-300q-165 0 -282.5 117.5t-117.5 282.5zM200 300q0 -41 29.5 -70.5t70.5 -29.5h500q41 0 70.5 29.5t29.5 70.5v500q0 41 -29.5 70.5t-70.5 29.5 h-500q-41 0 -70.5 -29.5t-29.5 -70.5v-500z" />
|
||||
<glyph unicode="" d="M0 400v300q0 163 119 281.5t281 118.5h300q165 0 282.5 -117.5t117.5 -282.5v-300q0 -165 -117.5 -282.5t-282.5 -117.5h-300q-163 0 -281.5 117.5t-118.5 282.5zM200 300q0 -41 29.5 -70.5t70.5 -29.5h500q41 0 70.5 29.5t29.5 70.5v500q0 41 -29.5 70.5t-70.5 29.5 h-500q-41 0 -70.5 -29.5t-29.5 -70.5v-500zM400 300l333 250l-333 250v-500z" />
|
||||
<glyph unicode="" d="M0 400v300q0 163 117.5 281.5t282.5 118.5h300q163 0 281.5 -119t118.5 -281v-300q0 -165 -117.5 -282.5t-282.5 -117.5h-300q-165 0 -282.5 117.5t-117.5 282.5zM200 300q0 -41 29.5 -70.5t70.5 -29.5h500q41 0 70.5 29.5t29.5 70.5v500q0 41 -29.5 70.5t-70.5 29.5 h-500q-41 0 -70.5 -29.5t-29.5 -70.5v-500zM300 700l250 -333l250 333h-500z" />
|
||||
<glyph unicode="" d="M0 400v300q0 165 117.5 282.5t282.5 117.5h300q165 0 282.5 -117.5t117.5 -282.5v-300q0 -162 -118.5 -281t-281.5 -119h-300q-165 0 -282.5 118.5t-117.5 281.5zM200 300q0 -41 29.5 -70.5t70.5 -29.5h500q41 0 70.5 29.5t29.5 70.5v500q0 41 -29.5 70.5t-70.5 29.5 h-500q-41 0 -70.5 -29.5t-29.5 -70.5v-500zM300 400h500l-250 333z" />
|
||||
<glyph unicode="" d="M0 400v300h300v200l400 -350l-400 -350v200h-300zM500 0v200h500q41 0 70.5 29.5t29.5 70.5v500q0 41 -29.5 70.5t-70.5 29.5h-500v200h400q165 0 282.5 -117.5t117.5 -282.5v-300q0 -165 -117.5 -282.5t-282.5 -117.5h-400z" />
|
||||
<glyph unicode="" d="M217 519q8 -19 31 -19h302q-155 -438 -160 -458q-5 -21 4 -32l9 -8h9q14 0 26 15q11 13 274.5 321.5t264.5 308.5q14 19 5 36q-8 17 -31 17l-301 -1q1 4 78 219.5t79 227.5q2 15 -5 27l-9 9h-9q-15 0 -25 -16q-4 -6 -98 -111.5t-228.5 -257t-209.5 -237.5q-16 -19 -6 -41 z" />
|
||||
<glyph unicode="" d="M0 400q0 -165 117.5 -282.5t282.5 -117.5h300q47 0 100 15v185h-500q-41 0 -70.5 29.5t-29.5 70.5v500q0 41 29.5 70.5t70.5 29.5h500v185q-14 4 -114 7.5t-193 5.5l-93 2q-165 0 -282.5 -117.5t-117.5 -282.5v-300zM600 400v300h300v200l400 -350l-400 -350v200h-300z " />
|
||||
<glyph unicode="" d="M0 400q0 -165 117.5 -282.5t282.5 -117.5h300q163 0 281.5 117.5t118.5 282.5v98l-78 73l-122 -123v-148q0 -41 -29.5 -70.5t-70.5 -29.5h-500q-41 0 -70.5 29.5t-29.5 70.5v500q0 41 29.5 70.5t70.5 29.5h156l118 122l-74 78h-100q-165 0 -282.5 -117.5t-117.5 -282.5 v-300zM496 709l353 342l-149 149h500v-500l-149 149l-342 -353z" />
|
||||
<glyph unicode="" d="M4 600q0 162 80 299t217 217t299 80t299 -80t217 -217t80 -299t-80 -299t-217 -217t-299 -80t-299 80t-217 217t-80 299zM186 600q0 -171 121.5 -292.5t292.5 -121.5t292.5 121.5t121.5 292.5t-121.5 292.5t-292.5 121.5t-292.5 -121.5t-121.5 -292.5zM406 600 q0 80 57 137t137 57t137 -57t57 -137t-57 -137t-137 -57t-137 57t-57 137z" />
|
||||
<glyph unicode="" d="M0 0v275q0 11 7 18t18 7h1048q11 0 19 -7.5t8 -17.5v-275h-1100zM100 800l445 -500l450 500h-295v400h-300v-400h-300zM900 150h100v50h-100v-50z" />
|
||||
<glyph unicode="" d="M0 0v275q0 11 7 18t18 7h1048q11 0 19 -7.5t8 -17.5v-275h-1100zM100 700h300v-300h300v300h295l-445 500zM900 150h100v50h-100v-50z" />
|
||||
<glyph unicode="" d="M0 0v275q0 11 7 18t18 7h1048q11 0 19 -7.5t8 -17.5v-275h-1100zM100 705l305 -305l596 596l-154 155l-442 -442l-150 151zM900 150h100v50h-100v-50z" />
|
||||
<glyph unicode="" d="M0 0v275q0 11 7 18t18 7h1048q11 0 19 -7.5t8 -17.5v-275h-1100zM100 988l97 -98l212 213l-97 97zM200 400l697 1l3 699l-250 -239l-149 149l-212 -212l149 -149zM900 150h100v50h-100v-50z" />
|
||||
<glyph unicode="" d="M0 0v275q0 11 7 18t18 7h1048q11 0 19 -7.5t8 -17.5v-275h-1100zM200 612l212 -212l98 97l-213 212zM300 1200l239 -250l-149 -149l212 -212l149 148l249 -237l-1 697zM900 150h100v50h-100v-50z" />
|
||||
<glyph unicode="" d="M23 415l1177 784v-1079l-475 272l-310 -393v416h-392zM494 210l672 938l-672 -712v-226z" />
|
||||
<glyph unicode="" d="M0 150v1000q0 20 14.5 35t35.5 15h250v-300h500v300h100l200 -200v-850q0 -21 -15 -35.5t-35 -14.5h-150v400h-700v-400h-150q-21 0 -35.5 14.5t-14.5 35.5zM600 1000h100v200h-100v-200z" />
|
||||
<glyph unicode="" d="M0 150v1000q0 20 14.5 35t35.5 15h250v-300h500v300h100l200 -200v-218l-276 -275l-120 120l-126 -127h-378v-400h-150q-21 0 -35.5 14.5t-14.5 35.5zM581 306l123 123l120 -120l353 352l123 -123l-475 -476zM600 1000h100v200h-100v-200z" />
|
||||
<glyph unicode="" d="M0 150v1000q0 20 14.5 35t35.5 15h250v-300h500v300h100l200 -200v-269l-103 -103l-170 170l-298 -298h-329v-400h-150q-21 0 -35.5 14.5t-14.5 35.5zM600 1000h100v200h-100v-200zM700 133l170 170l-170 170l127 127l170 -170l170 170l127 -128l-170 -169l170 -170 l-127 -127l-170 170l-170 -170z" />
|
||||
<glyph unicode="" d="M0 150v1000q0 20 14.5 35t35.5 15h250v-300h500v300h100l200 -200v-300h-400v-200h-500v-400h-150q-21 0 -35.5 14.5t-14.5 35.5zM600 300l300 -300l300 300h-200v300h-200v-300h-200zM600 1000v200h100v-200h-100z" />
|
||||
<glyph unicode="" d="M0 150v1000q0 20 14.5 35t35.5 15h250v-300h500v300h100l200 -200v-402l-200 200l-298 -298h-402v-400h-150q-21 0 -35.5 14.5t-14.5 35.5zM600 300h200v-300h200v300h200l-300 300zM600 1000v200h100v-200h-100z" />
|
||||
<glyph unicode="" d="M0 250q0 -21 14.5 -35.5t35.5 -14.5h1100q21 0 35.5 14.5t14.5 35.5v550h-1200v-550zM0 900h1200v150q0 21 -14.5 35.5t-35.5 14.5h-1100q-21 0 -35.5 -14.5t-14.5 -35.5v-150zM100 300v200h400v-200h-400z" />
|
||||
<glyph unicode="" d="M0 400l300 298v-198h400v-200h-400v-198zM100 800v200h100v-200h-100zM300 800v200h100v-200h-100zM500 800v200h400v198l300 -298l-300 -298v198h-400zM800 300v200h100v-200h-100zM1000 300h100v200h-100v-200z" />
|
||||
<glyph unicode="" d="M100 700v400l50 100l50 -100v-300h100v300l50 100l50 -100v-300h100v300l50 100l50 -100v-400l-100 -203v-447q0 -21 -14.5 -35.5t-35.5 -14.5h-200q-21 0 -35.5 14.5t-14.5 35.5v447zM800 597q0 -29 10.5 -55.5t25 -43t29 -28.5t25.5 -18l10 -5v-397q0 -21 14.5 -35.5 t35.5 -14.5h200q21 0 35.5 14.5t14.5 35.5v1106q0 31 -18 40.5t-44 -7.5l-276 -116q-25 -17 -43.5 -51.5t-18.5 -65.5v-359z" />
|
||||
<glyph unicode="" d="M100 0h400v56q-75 0 -87.5 6t-12.5 44v394h500v-394q0 -38 -12.5 -44t-87.5 -6v-56h400v56q-4 0 -11 0.5t-24 3t-30 7t-24 15t-11 24.5v888q0 22 25 34.5t50 13.5l25 2v56h-400v-56q75 0 87.5 -6t12.5 -44v-394h-500v394q0 38 12.5 44t87.5 6v56h-400v-56q4 0 11 -0.5 t24 -3t30 -7t24 -15t11 -24.5v-888q0 -22 -25 -34.5t-50 -13.5l-25 -2v-56z" />
|
||||
<glyph unicode="" d="M0 300q0 -41 29.5 -70.5t70.5 -29.5h300q41 0 70.5 29.5t29.5 70.5v500q0 41 -29.5 70.5t-70.5 29.5h-300q-41 0 -70.5 -29.5t-29.5 -70.5v-500zM100 100h400l200 200h105l295 98v-298h-425l-100 -100h-375zM100 300v200h300v-200h-300zM100 600v200h300v-200h-300z M100 1000h400l200 -200v-98l295 98h105v200h-425l-100 100h-375zM700 402v163l400 133v-163z" />
|
||||
<glyph unicode="" d="M16.5 974.5q0.5 -21.5 16 -90t46.5 -140t104 -177.5t175 -208q103 -103 207.5 -176t180 -103.5t137 -47t92.5 -16.5l31 1l163 162q17 18 13.5 41t-22.5 37l-192 136q-19 14 -45 12t-42 -19l-118 -118q-142 101 -268 227t-227 268l118 118q17 17 20 41.5t-11 44.5 l-139 194q-14 19 -36.5 22t-40.5 -14l-162 -162q-1 -11 -0.5 -32.5z" />
|
||||
<glyph unicode="" d="M0 50v212q0 20 10.5 45.5t24.5 39.5l365 303v50q0 4 1 10.5t12 22.5t30 28.5t60 23t97 10.5t97 -10t60 -23.5t30 -27.5t12 -24l1 -10v-50l365 -303q14 -14 24.5 -39.5t10.5 -45.5v-212q0 -21 -14.5 -35.5t-35.5 -14.5h-1100q-20 0 -35 14.5t-15 35.5zM0 712 q0 -21 14.5 -33.5t34.5 -8.5l202 33q20 4 34.5 21t14.5 38v146q141 24 300 24t300 -24v-146q0 -21 14.5 -38t34.5 -21l202 -33q20 -4 34.5 8.5t14.5 33.5v200q-6 8 -19 20.5t-63 45t-112 57t-171 45t-235 20.5q-92 0 -175 -10.5t-141.5 -27t-108.5 -36.5t-81.5 -40 t-53.5 -36.5t-31 -27.5l-9 -10v-200z" />
|
||||
<glyph unicode="" d="M100 0v100h1100v-100h-1100zM175 200h950l-125 150v250l100 100v400h-100v-200h-100v200h-200v-200h-100v200h-200v-200h-100v200h-100v-400l100 -100v-250z" />
|
||||
<glyph unicode="" d="M100 0h300v400q0 41 -29.5 70.5t-70.5 29.5h-100q-41 0 -70.5 -29.5t-29.5 -70.5v-400zM500 0v1000q0 41 29.5 70.5t70.5 29.5h100q41 0 70.5 -29.5t29.5 -70.5v-1000h-300zM900 0v700q0 41 29.5 70.5t70.5 29.5h100q41 0 70.5 -29.5t29.5 -70.5v-700h-300z" />
|
||||
<glyph unicode="" d="M-100 300v500q0 124 88 212t212 88h700q124 0 212 -88t88 -212v-500q0 -124 -88 -212t-212 -88h-700q-124 0 -212 88t-88 212zM100 200h900v700h-900v-700zM200 300h300v300h-200v100h200v100h-300v-300h200v-100h-200v-100zM600 300h200v100h100v300h-100v100h-200v-500 zM700 400v300h100v-300h-100z" />
|
||||
<glyph unicode="" d="M-100 300v500q0 124 88 212t212 88h700q124 0 212 -88t88 -212v-500q0 -124 -88 -212t-212 -88h-700q-124 0 -212 88t-88 212zM100 200h900v700h-900v-700zM200 300h100v200h100v-200h100v500h-100v-200h-100v200h-100v-500zM600 300h200v100h100v300h-100v100h-200v-500 zM700 400v300h100v-300h-100z" />
|
||||
<glyph unicode="" d="M-100 300v500q0 124 88 212t212 88h700q124 0 212 -88t88 -212v-500q0 -124 -88 -212t-212 -88h-700q-124 0 -212 88t-88 212zM100 200h900v700h-900v-700zM200 300h300v100h-200v300h200v100h-300v-500zM600 300h300v100h-200v300h200v100h-300v-500z" />
|
||||
<glyph unicode="" d="M-100 300v500q0 124 88 212t212 88h700q124 0 212 -88t88 -212v-500q0 -124 -88 -212t-212 -88h-700q-124 0 -212 88t-88 212zM100 200h900v700h-900v-700zM200 550l300 -150v300zM600 400l300 150l-300 150v-300z" />
|
||||
<glyph unicode="" d="M-100 300v500q0 124 88 212t212 88h700q124 0 212 -88t88 -212v-500q0 -124 -88 -212t-212 -88h-700q-124 0 -212 88t-88 212zM100 200h900v700h-900v-700zM200 300v500h700v-500h-700zM300 400h130q41 0 68 42t27 107t-28.5 108t-66.5 43h-130v-300zM575 549 q0 -65 27 -107t68 -42h130v300h-130q-38 0 -66.5 -43t-28.5 -108z" />
|
||||
<glyph unicode="" d="M-100 300v500q0 124 88 212t212 88h700q124 0 212 -88t88 -212v-500q0 -124 -88 -212t-212 -88h-700q-124 0 -212 88t-88 212zM100 200h900v700h-900v-700zM200 300h300v300h-200v100h200v100h-300v-300h200v-100h-200v-100zM601 300h100v100h-100v-100zM700 700h100 v-400h100v500h-200v-100z" />
|
||||
<glyph unicode="" d="M-100 300v500q0 124 88 212t212 88h700q124 0 212 -88t88 -212v-500q0 -124 -88 -212t-212 -88h-700q-124 0 -212 88t-88 212zM100 200h900v700h-900v-700zM200 300h300v400h-200v100h-100v-500zM301 400v200h100v-200h-100zM601 300h100v100h-100v-100zM700 700h100 v-400h100v500h-200v-100z" />
|
||||
<glyph unicode="" d="M-100 300v500q0 124 88 212t212 88h700q124 0 212 -88t88 -212v-500q0 -124 -88 -212t-212 -88h-700q-124 0 -212 88t-88 212zM100 200h900v700h-900v-700zM200 700v100h300v-300h-99v-100h-100v100h99v200h-200zM201 300v100h100v-100h-100zM601 300v100h100v-100h-100z M700 700v100h200v-500h-100v400h-100z" />
|
||||
<glyph unicode="" d="M4 600q0 162 80 299t217 217t299 80t299 -80t217 -217t80 -299t-80 -299t-217 -217t-299 -80t-299 80t-217 217t-80 299zM186 600q0 -171 121.5 -292.5t292.5 -121.5t292.5 121.5t121.5 292.5t-121.5 292.5t-292.5 121.5t-292.5 -121.5t-121.5 -292.5zM400 500v200 l100 100h300v-100h-300v-200h300v-100h-300z" />
|
||||
<glyph unicode="" d="M0 600q0 162 80 299t217 217t299 80t299 -80t217 -217t80 -299t-80 -299t-217 -217t-299 -80t-299 80t-217 217t-80 299zM182 600q0 -171 121.5 -292.5t292.5 -121.5t292.5 121.5t121.5 292.5t-121.5 292.5t-292.5 121.5t-292.5 -121.5t-121.5 -292.5zM400 400v400h300 l100 -100v-100h-100v100h-200v-100h200v-100h-200v-100h-100zM700 400v100h100v-100h-100z" />
|
||||
<glyph unicode="" d="M-14 494q0 -80 56.5 -137t135.5 -57h222v300h400v-300h128q120 0 205 86.5t85 207.5t-85 207t-205 86q-46 0 -90 -14q-44 97 -134.5 156.5t-200.5 59.5q-152 0 -260 -107.5t-108 -260.5q0 -25 2 -37q-66 -14 -108.5 -67.5t-42.5 -122.5zM300 200h200v300h200v-300h200 l-300 -300z" />
|
||||
<glyph unicode="" d="M-14 494q0 -80 56.5 -137t135.5 -57h8l414 414l403 -403q94 26 154.5 104.5t60.5 178.5q0 120 -85 206.5t-205 86.5q-46 0 -90 -14q-44 97 -134.5 156.5t-200.5 59.5q-152 0 -260 -107.5t-108 -260.5q0 -25 2 -37q-66 -14 -108.5 -67.5t-42.5 -122.5zM300 200l300 300 l300 -300h-200v-300h-200v300h-200z" />
|
||||
<glyph unicode="" d="M100 200h400v-155l-75 -45h350l-75 45v155h400l-270 300h170l-270 300h170l-300 333l-300 -333h170l-270 -300h170z" />
|
||||
<glyph unicode="" d="M121 700q0 -53 28.5 -97t75.5 -65q-4 -16 -4 -38q0 -74 52.5 -126.5t126.5 -52.5q56 0 100 30v-306l-75 -45h350l-75 45v306q46 -30 100 -30q74 0 126.5 52.5t52.5 126.5q0 24 -9 55q50 32 79.5 83t29.5 112q0 90 -61.5 155.5t-150.5 71.5q-26 89 -99.5 145.5 t-167.5 56.5q-116 0 -197.5 -81.5t-81.5 -197.5q0 -4 1 -11.5t1 -11.5q-14 2 -23 2q-74 0 -126.5 -52.5t-52.5 -126.5z" />
|
||||
</font>
|
||||
</defs></svg>
|
До Ширина: | Высота: | Размер: 62 KiB |
Двоичный файл не отображается.
Двоичный файл не отображается.
|
@ -1,793 +0,0 @@
|
|||
'use strict';
|
||||
|
||||
/*
|
||||
HTTP Hawk Authentication Scheme
|
||||
Copyright (c) 2012-2016, Eran Hammer <eran@hammer.io>
|
||||
BSD Licensed
|
||||
*/
|
||||
|
||||
// Declare namespace
|
||||
|
||||
var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) { return typeof obj; } : function (obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; };
|
||||
|
||||
var hawk = {
|
||||
internals: {}
|
||||
};
|
||||
|
||||
hawk.client = {
|
||||
|
||||
// Generate an Authorization header for a given request
|
||||
|
||||
/*
|
||||
uri: 'http://example.com/resource?a=b' or object generated by hawk.utils.parseUri()
|
||||
method: HTTP verb (e.g. 'GET', 'POST')
|
||||
options: {
|
||||
// Required
|
||||
credentials: {
|
||||
id: 'dh37fgj492je',
|
||||
key: 'aoijedoaijsdlaksjdl',
|
||||
algorithm: 'sha256' // 'sha1', 'sha256'
|
||||
},
|
||||
// Optional
|
||||
ext: 'application-specific', // Application specific data sent via the ext attribute
|
||||
timestamp: Date.now() / 1000, // A pre-calculated timestamp in seconds
|
||||
nonce: '2334f34f', // A pre-generated nonce
|
||||
localtimeOffsetMsec: 400, // Time offset to sync with server time (ignored if timestamp provided)
|
||||
payload: '{"some":"payload"}', // UTF-8 encoded string for body hash generation (ignored if hash provided)
|
||||
contentType: 'application/json', // Payload content-type (ignored if hash provided)
|
||||
hash: 'U4MKKSmiVxk37JCCrAVIjV=', // Pre-calculated payload hash
|
||||
app: '24s23423f34dx', // Oz application id
|
||||
dlg: '234sz34tww3sd' // Oz delegated-by application id
|
||||
}
|
||||
*/
|
||||
|
||||
header: function header(uri, method, options) {
|
||||
|
||||
var result = {
|
||||
field: '',
|
||||
artifacts: {}
|
||||
};
|
||||
|
||||
// Validate inputs
|
||||
|
||||
if (!uri || typeof uri !== 'string' && (typeof uri === 'undefined' ? 'undefined' : _typeof(uri)) !== 'object' || !method || typeof method !== 'string' || !options || (typeof options === 'undefined' ? 'undefined' : _typeof(options)) !== 'object') {
|
||||
|
||||
result.err = 'Invalid argument type';
|
||||
return result;
|
||||
}
|
||||
|
||||
// Application time
|
||||
|
||||
var timestamp = options.timestamp || hawk.utils.nowSec(options.localtimeOffsetMsec);
|
||||
|
||||
// Validate credentials
|
||||
|
||||
var credentials = options.credentials;
|
||||
if (!credentials || !credentials.id || !credentials.key || !credentials.algorithm) {
|
||||
|
||||
result.err = 'Invalid credentials object';
|
||||
return result;
|
||||
}
|
||||
|
||||
if (hawk.crypto.algorithms.indexOf(credentials.algorithm) === -1) {
|
||||
result.err = 'Unknown algorithm';
|
||||
return result;
|
||||
}
|
||||
|
||||
// Parse URI
|
||||
|
||||
if (typeof uri === 'string') {
|
||||
uri = hawk.utils.parseUri(uri);
|
||||
}
|
||||
|
||||
// Calculate signature
|
||||
|
||||
var artifacts = {
|
||||
ts: timestamp,
|
||||
nonce: options.nonce || hawk.utils.randomString(6),
|
||||
method: method,
|
||||
resource: uri.resource,
|
||||
host: uri.host,
|
||||
port: uri.port,
|
||||
hash: options.hash,
|
||||
ext: options.ext,
|
||||
app: options.app,
|
||||
dlg: options.dlg
|
||||
};
|
||||
|
||||
result.artifacts = artifacts;
|
||||
|
||||
// Calculate payload hash
|
||||
|
||||
if (!artifacts.hash && (options.payload || options.payload === '')) {
|
||||
|
||||
artifacts.hash = hawk.crypto.calculatePayloadHash(options.payload, credentials.algorithm, options.contentType);
|
||||
}
|
||||
|
||||
var mac = hawk.crypto.calculateMac('header', credentials, artifacts);
|
||||
|
||||
// Construct header
|
||||
|
||||
var hasExt = artifacts.ext !== null && artifacts.ext !== undefined && artifacts.ext !== ''; // Other falsey values allowed
|
||||
var header = 'Hawk id="' + credentials.id + '", ts="' + artifacts.ts + '", nonce="' + artifacts.nonce + (artifacts.hash ? '", hash="' + artifacts.hash : '') + (hasExt ? '", ext="' + hawk.utils.escapeHeaderAttribute(artifacts.ext) : '') + '", mac="' + mac + '"';
|
||||
|
||||
if (artifacts.app) {
|
||||
header += ', app="' + artifacts.app + (artifacts.dlg ? '", dlg="' + artifacts.dlg : '') + '"';
|
||||
}
|
||||
|
||||
result.field = header;
|
||||
|
||||
return result;
|
||||
},
|
||||
|
||||
// Generate a bewit value for a given URI
|
||||
|
||||
/*
|
||||
uri: 'http://example.com/resource?a=b'
|
||||
options: {
|
||||
// Required
|
||||
credentials: {
|
||||
id: 'dh37fgj492je',
|
||||
key: 'aoijedoaijsdlaksjdl',
|
||||
algorithm: 'sha256' // 'sha1', 'sha256'
|
||||
},
|
||||
ttlSec: 60 * 60, // TTL in seconds
|
||||
// Optional
|
||||
ext: 'application-specific', // Application specific data sent via the ext attribute
|
||||
localtimeOffsetMsec: 400 // Time offset to sync with server time
|
||||
};
|
||||
*/
|
||||
|
||||
bewit: function bewit(uri, options) {
|
||||
|
||||
// Validate inputs
|
||||
|
||||
if (!uri || typeof uri !== 'string' || !options || (typeof options === 'undefined' ? 'undefined' : _typeof(options)) !== 'object' || !options.ttlSec) {
|
||||
|
||||
return '';
|
||||
}
|
||||
|
||||
options.ext = options.ext === null || options.ext === undefined ? '' : options.ext; // Zero is valid value
|
||||
|
||||
// Application time
|
||||
|
||||
var now = hawk.utils.nowSec(options.localtimeOffsetMsec);
|
||||
|
||||
// Validate credentials
|
||||
|
||||
var credentials = options.credentials;
|
||||
if (!credentials || !credentials.id || !credentials.key || !credentials.algorithm) {
|
||||
|
||||
return '';
|
||||
}
|
||||
|
||||
if (hawk.crypto.algorithms.indexOf(credentials.algorithm) === -1) {
|
||||
return '';
|
||||
}
|
||||
|
||||
// Parse URI
|
||||
|
||||
uri = hawk.utils.parseUri(uri);
|
||||
|
||||
// Calculate signature
|
||||
|
||||
var exp = now + options.ttlSec;
|
||||
var mac = hawk.crypto.calculateMac('bewit', credentials, {
|
||||
ts: exp,
|
||||
nonce: '',
|
||||
method: 'GET',
|
||||
resource: uri.resource, // Maintain trailing '?' and query params
|
||||
host: uri.host,
|
||||
port: uri.port,
|
||||
ext: options.ext
|
||||
});
|
||||
|
||||
// Construct bewit: id\exp\mac\ext
|
||||
|
||||
var bewit = credentials.id + '\\' + exp + '\\' + mac + '\\' + options.ext;
|
||||
return hawk.utils.base64urlEncode(bewit);
|
||||
},
|
||||
|
||||
// Validate server response
|
||||
|
||||
/*
|
||||
request: object created via 'new XMLHttpRequest()' after response received or fetch API 'Response'
|
||||
artifacts: object received from header().artifacts
|
||||
options: {
|
||||
payload: optional payload received
|
||||
required: specifies if a Server-Authorization header is required. Defaults to 'false'
|
||||
}
|
||||
*/
|
||||
|
||||
authenticate: function authenticate(request, credentials, artifacts, options) {
|
||||
|
||||
options = options || {};
|
||||
|
||||
var getHeader = function getHeader(name) {
|
||||
|
||||
// Fetch API or plain headers
|
||||
|
||||
if (request.headers) {
|
||||
return typeof request.headers.get === 'function' ? request.headers.get(name) : request.headers[name];
|
||||
}
|
||||
|
||||
// XMLHttpRequest
|
||||
|
||||
return request.getResponseHeader ? request.getResponseHeader(name) : request.getHeader(name);
|
||||
};
|
||||
|
||||
var wwwAuthenticate = getHeader('www-authenticate');
|
||||
if (wwwAuthenticate) {
|
||||
|
||||
// Parse HTTP WWW-Authenticate header
|
||||
|
||||
var wwwAttributes = hawk.utils.parseAuthorizationHeader(wwwAuthenticate, ['ts', 'tsm', 'error']);
|
||||
if (!wwwAttributes) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (wwwAttributes.ts) {
|
||||
var tsm = hawk.crypto.calculateTsMac(wwwAttributes.ts, credentials);
|
||||
if (tsm !== wwwAttributes.tsm) {
|
||||
return false;
|
||||
}
|
||||
|
||||
hawk.utils.setNtpSecOffset(wwwAttributes.ts - Math.floor(Date.now() / 1000)); // Keep offset at 1 second precision
|
||||
}
|
||||
}
|
||||
|
||||
// Parse HTTP Server-Authorization header
|
||||
|
||||
var serverAuthorization = getHeader('server-authorization');
|
||||
if (!serverAuthorization && !options.required) {
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
var attributes = hawk.utils.parseAuthorizationHeader(serverAuthorization, ['mac', 'ext', 'hash']);
|
||||
if (!attributes) {
|
||||
return false;
|
||||
}
|
||||
|
||||
var modArtifacts = {
|
||||
ts: artifacts.ts,
|
||||
nonce: artifacts.nonce,
|
||||
method: artifacts.method,
|
||||
resource: artifacts.resource,
|
||||
host: artifacts.host,
|
||||
port: artifacts.port,
|
||||
hash: attributes.hash,
|
||||
ext: attributes.ext,
|
||||
app: artifacts.app,
|
||||
dlg: artifacts.dlg
|
||||
};
|
||||
|
||||
var mac = hawk.crypto.calculateMac('response', credentials, modArtifacts);
|
||||
if (mac !== attributes.mac) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!options.payload && options.payload !== '') {
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!attributes.hash) {
|
||||
return false;
|
||||
}
|
||||
|
||||
var calculatedHash = hawk.crypto.calculatePayloadHash(options.payload, credentials.algorithm, getHeader('content-type'));
|
||||
return calculatedHash === attributes.hash;
|
||||
},
|
||||
|
||||
message: function message(host, port, _message, options) {
|
||||
|
||||
// Validate inputs
|
||||
|
||||
if (!host || typeof host !== 'string' || !port || typeof port !== 'number' || _message === null || _message === undefined || typeof _message !== 'string' || !options || (typeof options === 'undefined' ? 'undefined' : _typeof(options)) !== 'object') {
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
// Application time
|
||||
|
||||
var timestamp = options.timestamp || hawk.utils.nowSec(options.localtimeOffsetMsec);
|
||||
|
||||
// Validate credentials
|
||||
|
||||
var credentials = options.credentials;
|
||||
if (!credentials || !credentials.id || !credentials.key || !credentials.algorithm) {
|
||||
|
||||
// Invalid credential object
|
||||
return null;
|
||||
}
|
||||
|
||||
if (hawk.crypto.algorithms.indexOf(credentials.algorithm) === -1) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Calculate signature
|
||||
|
||||
var artifacts = {
|
||||
ts: timestamp,
|
||||
nonce: options.nonce || hawk.utils.randomString(6),
|
||||
host: host,
|
||||
port: port,
|
||||
hash: hawk.crypto.calculatePayloadHash(_message, credentials.algorithm)
|
||||
};
|
||||
|
||||
// Construct authorization
|
||||
|
||||
var result = {
|
||||
id: credentials.id,
|
||||
ts: artifacts.ts,
|
||||
nonce: artifacts.nonce,
|
||||
hash: artifacts.hash,
|
||||
mac: hawk.crypto.calculateMac('message', credentials, artifacts)
|
||||
};
|
||||
|
||||
return result;
|
||||
},
|
||||
|
||||
authenticateTimestamp: function authenticateTimestamp(message, credentials, updateClock) {
|
||||
// updateClock defaults to true
|
||||
|
||||
var tsm = hawk.crypto.calculateTsMac(message.ts, credentials);
|
||||
if (tsm !== message.tsm) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (updateClock !== false) {
|
||||
hawk.utils.setNtpSecOffset(message.ts - Math.floor(Date.now() / 1000)); // Keep offset at 1 second precision
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
hawk.crypto = {
|
||||
|
||||
headerVersion: '1',
|
||||
|
||||
algorithms: ['sha1', 'sha256'],
|
||||
|
||||
calculateMac: function calculateMac(type, credentials, options) {
|
||||
|
||||
var normalized = hawk.crypto.generateNormalizedString(type, options);
|
||||
|
||||
var hmac = CryptoJS['Hmac' + credentials.algorithm.toUpperCase()](normalized, credentials.key);
|
||||
return hmac.toString(CryptoJS.enc.Base64);
|
||||
},
|
||||
|
||||
generateNormalizedString: function generateNormalizedString(type, options) {
|
||||
|
||||
var normalized = 'hawk.' + hawk.crypto.headerVersion + '.' + type + '\n' + options.ts + '\n' + options.nonce + '\n' + (options.method || '').toUpperCase() + '\n' + (options.resource || '') + '\n' + options.host.toLowerCase() + '\n' + options.port + '\n' + (options.hash || '') + '\n';
|
||||
|
||||
if (options.ext) {
|
||||
normalized += options.ext.replace('\\', '\\\\').replace('\n', '\\n');
|
||||
}
|
||||
|
||||
normalized += '\n';
|
||||
|
||||
if (options.app) {
|
||||
normalized += options.app + '\n' + (options.dlg || '') + '\n';
|
||||
}
|
||||
|
||||
return normalized;
|
||||
},
|
||||
|
||||
calculatePayloadHash: function calculatePayloadHash(payload, algorithm, contentType) {
|
||||
|
||||
var hash = CryptoJS.algo[algorithm.toUpperCase()].create();
|
||||
hash.update('hawk.' + hawk.crypto.headerVersion + '.payload\n');
|
||||
hash.update(hawk.utils.parseContentType(contentType) + '\n');
|
||||
hash.update(payload);
|
||||
hash.update('\n');
|
||||
return hash.finalize().toString(CryptoJS.enc.Base64);
|
||||
},
|
||||
|
||||
calculateTsMac: function calculateTsMac(ts, credentials) {
|
||||
|
||||
var hash = CryptoJS['Hmac' + credentials.algorithm.toUpperCase()]('hawk.' + hawk.crypto.headerVersion + '.ts\n' + ts + '\n', credentials.key);
|
||||
return hash.toString(CryptoJS.enc.Base64);
|
||||
}
|
||||
};
|
||||
|
||||
// localStorage compatible interface
|
||||
|
||||
hawk.internals.LocalStorage = function () {
|
||||
|
||||
this._cache = {};
|
||||
this.length = 0;
|
||||
|
||||
this.getItem = function (key) {
|
||||
|
||||
return this._cache.hasOwnProperty(key) ? String(this._cache[key]) : null;
|
||||
};
|
||||
|
||||
this.setItem = function (key, value) {
|
||||
|
||||
this._cache[key] = String(value);
|
||||
this.length = Object.keys(this._cache).length;
|
||||
};
|
||||
|
||||
this.removeItem = function (key) {
|
||||
|
||||
delete this._cache[key];
|
||||
this.length = Object.keys(this._cache).length;
|
||||
};
|
||||
|
||||
this.clear = function () {
|
||||
|
||||
this._cache = {};
|
||||
this.length = 0;
|
||||
};
|
||||
|
||||
this.key = function (i) {
|
||||
|
||||
return Object.keys(this._cache)[i || 0];
|
||||
};
|
||||
};
|
||||
|
||||
hawk.utils = {
|
||||
|
||||
storage: new hawk.internals.LocalStorage(),
|
||||
|
||||
setStorage: function setStorage(storage) {
|
||||
|
||||
var ntpOffset = hawk.utils.storage.getItem('hawk_ntp_offset');
|
||||
hawk.utils.storage = storage;
|
||||
if (ntpOffset) {
|
||||
hawk.utils.setNtpSecOffset(ntpOffset);
|
||||
}
|
||||
},
|
||||
|
||||
setNtpSecOffset: function setNtpSecOffset(offset) {
|
||||
|
||||
try {
|
||||
hawk.utils.storage.setItem('hawk_ntp_offset', offset);
|
||||
} catch (err) {
|
||||
console.error('[hawk] could not write to storage.');
|
||||
console.error(err);
|
||||
}
|
||||
},
|
||||
|
||||
getNtpSecOffset: function getNtpSecOffset() {
|
||||
|
||||
var offset = hawk.utils.storage.getItem('hawk_ntp_offset');
|
||||
if (!offset) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
return parseInt(offset, 10);
|
||||
},
|
||||
|
||||
now: function now(localtimeOffsetMsec) {
|
||||
|
||||
return Date.now() + (localtimeOffsetMsec || 0) + hawk.utils.getNtpSecOffset() * 1000;
|
||||
},
|
||||
|
||||
nowSec: function nowSec(localtimeOffsetMsec) {
|
||||
|
||||
return Math.floor(hawk.utils.now(localtimeOffsetMsec) / 1000);
|
||||
},
|
||||
|
||||
escapeHeaderAttribute: function escapeHeaderAttribute(attribute) {
|
||||
|
||||
return attribute.replace(/\\/g, '\\\\').replace(/\"/g, '\\"');
|
||||
},
|
||||
|
||||
parseContentType: function parseContentType(header) {
|
||||
|
||||
if (!header) {
|
||||
return '';
|
||||
}
|
||||
|
||||
return header.split(';')[0].replace(/^\s+|\s+$/g, '').toLowerCase();
|
||||
},
|
||||
|
||||
parseAuthorizationHeader: function parseAuthorizationHeader(header, keys) {
|
||||
|
||||
if (!header) {
|
||||
return null;
|
||||
}
|
||||
|
||||
var headerParts = header.match(/^(\w+)(?:\s+(.*))?$/); // Header: scheme[ something]
|
||||
if (!headerParts) {
|
||||
return null;
|
||||
}
|
||||
|
||||
var scheme = headerParts[1];
|
||||
if (scheme.toLowerCase() !== 'hawk') {
|
||||
return null;
|
||||
}
|
||||
|
||||
var attributesString = headerParts[2];
|
||||
if (!attributesString) {
|
||||
return null;
|
||||
}
|
||||
|
||||
var attributes = {};
|
||||
var verify = attributesString.replace(/(\w+)="([^"\\]*)"\s*(?:,\s*|$)/g, function ($0, $1, $2) {
|
||||
|
||||
// Check valid attribute names
|
||||
|
||||
if (keys.indexOf($1) === -1) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Allowed attribute value characters: !#$%&'()*+,-./:;<=>?@[]^_`{|}~ and space, a-z, A-Z, 0-9
|
||||
|
||||
if ($2.match(/^[ \w\!#\$%&'\(\)\*\+,\-\.\/\:;<\=>\?@\[\]\^`\{\|\}~]+$/) === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Check for duplicates
|
||||
|
||||
if (attributes.hasOwnProperty($1)) {
|
||||
return;
|
||||
}
|
||||
|
||||
attributes[$1] = $2;
|
||||
return '';
|
||||
});
|
||||
|
||||
if (verify !== '') {
|
||||
return null;
|
||||
}
|
||||
|
||||
return attributes;
|
||||
},
|
||||
|
||||
randomString: function randomString(size) {
|
||||
|
||||
var randomSource = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';
|
||||
var len = randomSource.length;
|
||||
|
||||
var result = [];
|
||||
for (var i = 0; i < size; ++i) {
|
||||
result[i] = randomSource[Math.floor(Math.random() * len)];
|
||||
}
|
||||
|
||||
return result.join('');
|
||||
},
|
||||
|
||||
// 1 2 3 4
|
||||
uriRegex: /^([^:]+)\:\/\/(?:[^@/]*@)?([^\/:]+)(?:\:(\d+))?([^#]*)(?:#.*)?$/, // scheme://credentials@host:port/resource#fragment
|
||||
parseUri: function parseUri(input) {
|
||||
|
||||
var parts = input.match(hawk.utils.uriRegex);
|
||||
if (!parts) {
|
||||
return { host: '', port: '', resource: '' };
|
||||
}
|
||||
|
||||
var scheme = parts[1].toLowerCase();
|
||||
var uri = {
|
||||
host: parts[2],
|
||||
port: parts[3] || (scheme === 'http' ? '80' : scheme === 'https' ? '443' : ''),
|
||||
resource: parts[4]
|
||||
};
|
||||
|
||||
return uri;
|
||||
},
|
||||
|
||||
base64urlEncode: function base64urlEncode(value) {
|
||||
|
||||
var wordArray = CryptoJS.enc.Utf8.parse(value);
|
||||
var encoded = CryptoJS.enc.Base64.stringify(wordArray);
|
||||
return encoded.replace(/\+/g, '-').replace(/\//g, '_').replace(/\=/g, '');
|
||||
}
|
||||
};
|
||||
|
||||
// $lab:coverage:off$
|
||||
/* eslint-disable */
|
||||
|
||||
// Based on: Crypto-JS v3.1.2
|
||||
// Copyright (c) 2009-2013, Jeff Mott. All rights reserved.
|
||||
// http://code.google.com/p/crypto-js/
|
||||
// http://code.google.com/p/crypto-js/wiki/License
|
||||
|
||||
var CryptoJS = CryptoJS || function (h, r) {
|
||||
var k = {},
|
||||
l = k.lib = {},
|
||||
n = function n() {},
|
||||
f = l.Base = { extend: function extend(a) {
|
||||
n.prototype = this;var b = new n();a && b.mixIn(a);b.hasOwnProperty("init") || (b.init = function () {
|
||||
b.$super.init.apply(this, arguments);
|
||||
});b.init.prototype = b;b.$super = this;return b;
|
||||
}, create: function create() {
|
||||
var a = this.extend();a.init.apply(a, arguments);return a;
|
||||
}, init: function init() {}, mixIn: function mixIn(a) {
|
||||
for (var _b in a) {
|
||||
a.hasOwnProperty(_b) && (this[_b] = a[_b]);
|
||||
}a.hasOwnProperty("toString") && (this.toString = a.toString);
|
||||
}, clone: function clone() {
|
||||
return this.init.prototype.extend(this);
|
||||
} },
|
||||
j = l.WordArray = f.extend({ init: function init(a, b) {
|
||||
a = this.words = a || [];this.sigBytes = b != r ? b : 4 * a.length;
|
||||
}, toString: function toString(a) {
|
||||
return (a || s).stringify(this);
|
||||
}, concat: function concat(a) {
|
||||
var b = this.words,
|
||||
d = a.words,
|
||||
c = this.sigBytes;a = a.sigBytes;this.clamp();if (c % 4) for (var e = 0; e < a; e++) {
|
||||
b[c + e >>> 2] |= (d[e >>> 2] >>> 24 - 8 * (e % 4) & 255) << 24 - 8 * ((c + e) % 4);
|
||||
} else if (65535 < d.length) for (var _e = 0; _e < a; _e += 4) {
|
||||
b[c + _e >>> 2] = d[_e >>> 2];
|
||||
} else b.push.apply(b, d);this.sigBytes += a;return this;
|
||||
}, clamp: function clamp() {
|
||||
var a = this.words,
|
||||
b = this.sigBytes;a[b >>> 2] &= 4294967295 << 32 - 8 * (b % 4);a.length = h.ceil(b / 4);
|
||||
}, clone: function clone() {
|
||||
var a = f.clone.call(this);a.words = this.words.slice(0);return a;
|
||||
}, random: function random(a) {
|
||||
for (var _b2 = [], d = 0; d < a; d += 4) {
|
||||
_b2.push(4294967296 * h.random() | 0);
|
||||
}return new j.init(b, a);
|
||||
} }),
|
||||
m = k.enc = {},
|
||||
s = m.Hex = { stringify: function stringify(a) {
|
||||
var b = a.words;a = a.sigBytes;for (var d = [], c = 0; c < a; c++) {
|
||||
var e = b[c >>> 2] >>> 24 - 8 * (c % 4) & 255;d.push((e >>> 4).toString(16));d.push((e & 15).toString(16));
|
||||
}return d.join("");
|
||||
}, parse: function parse(a) {
|
||||
for (var b = a.length, d = [], c = 0; c < b; c += 2) {
|
||||
d[c >>> 3] |= parseInt(a.substr(c, 2), 16) << 24 - 4 * (c % 8);
|
||||
}return new j.init(d, b / 2);
|
||||
} },
|
||||
p = m.Latin1 = { stringify: function stringify(a) {
|
||||
var b = a.words;a = a.sigBytes;for (var d = [], c = 0; c < a; c++) {
|
||||
d.push(String.fromCharCode(b[c >>> 2] >>> 24 - 8 * (c % 4) & 255));
|
||||
}return d.join("");
|
||||
}, parse: function parse(a) {
|
||||
for (var b = a.length, d = [], c = 0; c < b; c++) {
|
||||
d[c >>> 2] |= (a.charCodeAt(c) & 255) << 24 - 8 * (c % 4);
|
||||
}return new j.init(d, b);
|
||||
} },
|
||||
t = m.Utf8 = { stringify: function stringify(a) {
|
||||
try {
|
||||
return decodeURIComponent(escape(p.stringify(a)));
|
||||
} catch (b) {
|
||||
throw Error("Malformed UTF-8 data");
|
||||
}
|
||||
}, parse: function parse(a) {
|
||||
return p.parse(unescape(encodeURIComponent(a)));
|
||||
} },
|
||||
q = l.BufferedBlockAlgorithm = f.extend({ reset: function reset() {
|
||||
this._data = new j.init();this._nDataBytes = 0;
|
||||
}, _append: function _append(a) {
|
||||
"string" == typeof a && (a = t.parse(a));this._data.concat(a);this._nDataBytes += a.sigBytes;
|
||||
}, _process: function _process(a) {
|
||||
var b = this._data,
|
||||
d = b.words,
|
||||
c = b.sigBytes,
|
||||
e = this.blockSize,
|
||||
f = c / (4 * e),
|
||||
f = a ? h.ceil(f) : h.max((f | 0) - this._minBufferSize, 0);a = f * e;c = h.min(4 * a, c);if (a) {
|
||||
for (var g = 0; g < a; g += e) {
|
||||
this._doProcessBlock(d, g);
|
||||
}g = d.splice(0, a);b.sigBytes -= c;
|
||||
}return new j.init(g, c);
|
||||
}, clone: function clone() {
|
||||
var a = f.clone.call(this);a._data = this._data.clone();return a;
|
||||
}, _minBufferSize: 0 });l.Hasher = q.extend({ cfg: f.extend(), init: function init(a) {
|
||||
this.cfg = this.cfg.extend(a);this.reset();
|
||||
}, reset: function reset() {
|
||||
q.reset.call(this);this._doReset();
|
||||
}, update: function update(a) {
|
||||
this._append(a);this._process();return this;
|
||||
}, finalize: function finalize(a) {
|
||||
a && this._append(a);return this._doFinalize();
|
||||
}, blockSize: 16, _createHelper: function _createHelper(a) {
|
||||
return function (b, d) {
|
||||
return new a.init(d).finalize(b);
|
||||
};
|
||||
}, _createHmacHelper: function _createHmacHelper(a) {
|
||||
return function (b, d) {
|
||||
return new u.HMAC.init(a, d).finalize(b);
|
||||
};
|
||||
} });var u = k.algo = {};return k;
|
||||
}(Math);
|
||||
(function () {
|
||||
var k = CryptoJS,
|
||||
b = k.lib,
|
||||
m = b.WordArray,
|
||||
l = b.Hasher,
|
||||
d = [],
|
||||
b = k.algo.SHA1 = l.extend({ _doReset: function _doReset() {
|
||||
this._hash = new m.init([1732584193, 4023233417, 2562383102, 271733878, 3285377520]);
|
||||
}, _doProcessBlock: function _doProcessBlock(n, p) {
|
||||
for (var a = this._hash.words, e = a[0], f = a[1], h = a[2], j = a[3], b = a[4], c = 0; 80 > c; c++) {
|
||||
if (16 > c) d[c] = n[p + c] | 0;else {
|
||||
var g = d[c - 3] ^ d[c - 8] ^ d[c - 14] ^ d[c - 16];d[c] = g << 1 | g >>> 31;
|
||||
}g = (e << 5 | e >>> 27) + b + d[c];g = 20 > c ? g + ((f & h | ~f & j) + 1518500249) : 40 > c ? g + ((f ^ h ^ j) + 1859775393) : 60 > c ? g + ((f & h | f & j | h & j) - 1894007588) : g + ((f ^ h ^ j) - 899497514);b = j;j = h;h = f << 30 | f >>> 2;f = e;e = g;
|
||||
}a[0] = a[0] + e | 0;a[1] = a[1] + f | 0;a[2] = a[2] + h | 0;a[3] = a[3] + j | 0;a[4] = a[4] + b | 0;
|
||||
}, _doFinalize: function _doFinalize() {
|
||||
var b = this._data,
|
||||
d = b.words,
|
||||
a = 8 * this._nDataBytes,
|
||||
e = 8 * b.sigBytes;d[e >>> 5] |= 128 << 24 - e % 32;d[(e + 64 >>> 9 << 4) + 14] = Math.floor(a / 4294967296);d[(e + 64 >>> 9 << 4) + 15] = a;b.sigBytes = 4 * d.length;this._process();return this._hash;
|
||||
}, clone: function clone() {
|
||||
var b = l.clone.call(this);b._hash = this._hash.clone();return b;
|
||||
} });k.SHA1 = l._createHelper(b);k.HmacSHA1 = l._createHmacHelper(b);
|
||||
})();
|
||||
(function (k) {
|
||||
for (var g = CryptoJS, h = g.lib, v = h.WordArray, j = h.Hasher, h = g.algo, s = [], t = [], u = function u(q) {
|
||||
return 4294967296 * (q - (q | 0)) | 0;
|
||||
}, l = 2, b = 0; 64 > b;) {
|
||||
var d;a: {
|
||||
d = l;for (var w = k.sqrt(d), r = 2; r <= w; r++) {
|
||||
if (!(d % r)) {
|
||||
d = !1;break a;
|
||||
}
|
||||
}d = !0;
|
||||
}d && (8 > b && (s[b] = u(k.pow(l, 0.5))), t[b] = u(k.pow(l, 1 / 3)), b++);l++;
|
||||
}var n = [],
|
||||
h = h.SHA256 = j.extend({ _doReset: function _doReset() {
|
||||
this._hash = new v.init(s.slice(0));
|
||||
}, _doProcessBlock: function _doProcessBlock(q, h) {
|
||||
for (var a = this._hash.words, c = a[0], d = a[1], b = a[2], k = a[3], f = a[4], g = a[5], j = a[6], l = a[7], e = 0; 64 > e; e++) {
|
||||
if (16 > e) n[e] = q[h + e] | 0;else {
|
||||
var m = n[e - 15],
|
||||
p = n[e - 2];n[e] = ((m << 25 | m >>> 7) ^ (m << 14 | m >>> 18) ^ m >>> 3) + n[e - 7] + ((p << 15 | p >>> 17) ^ (p << 13 | p >>> 19) ^ p >>> 10) + n[e - 16];
|
||||
}m = l + ((f << 26 | f >>> 6) ^ (f << 21 | f >>> 11) ^ (f << 7 | f >>> 25)) + (f & g ^ ~f & j) + t[e] + n[e];p = ((c << 30 | c >>> 2) ^ (c << 19 | c >>> 13) ^ (c << 10 | c >>> 22)) + (c & d ^ c & b ^ d & b);l = j;j = g;g = f;f = k + m | 0;k = b;b = d;d = c;c = m + p | 0;
|
||||
}a[0] = a[0] + c | 0;a[1] = a[1] + d | 0;a[2] = a[2] + b | 0;a[3] = a[3] + k | 0;a[4] = a[4] + f | 0;a[5] = a[5] + g | 0;a[6] = a[6] + j | 0;a[7] = a[7] + l | 0;
|
||||
}, _doFinalize: function _doFinalize() {
|
||||
var d = this._data,
|
||||
b = d.words,
|
||||
a = 8 * this._nDataBytes,
|
||||
c = 8 * d.sigBytes;b[c >>> 5] |= 128 << 24 - c % 32;b[(c + 64 >>> 9 << 4) + 14] = k.floor(a / 4294967296);b[(c + 64 >>> 9 << 4) + 15] = a;d.sigBytes = 4 * b.length;this._process();return this._hash;
|
||||
}, clone: function clone() {
|
||||
var b = j.clone.call(this);b._hash = this._hash.clone();return b;
|
||||
} });g.SHA256 = j._createHelper(h);g.HmacSHA256 = j._createHmacHelper(h);
|
||||
})(Math);
|
||||
(function () {
|
||||
var c = CryptoJS,
|
||||
k = c.enc.Utf8;c.algo.HMAC = c.lib.Base.extend({ init: function init(a, b) {
|
||||
a = this._hasher = new a.init();"string" == typeof b && (b = k.parse(b));var c = a.blockSize,
|
||||
e = 4 * c;b.sigBytes > e && (b = a.finalize(b));b.clamp();for (var f = this._oKey = b.clone(), g = this._iKey = b.clone(), h = f.words, j = g.words, d = 0; d < c; d++) {
|
||||
h[d] ^= 1549556828, j[d] ^= 909522486;
|
||||
}f.sigBytes = g.sigBytes = e;this.reset();
|
||||
}, reset: function reset() {
|
||||
var a = this._hasher;a.reset();a.update(this._iKey);
|
||||
}, update: function update(a) {
|
||||
this._hasher.update(a);return this;
|
||||
}, finalize: function finalize(a) {
|
||||
var b = this._hasher;a = b.finalize(a);b.reset();return b.finalize(this._oKey.clone().concat(a));
|
||||
} });
|
||||
})();
|
||||
(function () {
|
||||
var h = CryptoJS,
|
||||
j = h.lib.WordArray;h.enc.Base64 = { stringify: function stringify(b) {
|
||||
var e = b.words,
|
||||
f = b.sigBytes,
|
||||
c = this._map;b.clamp();b = [];for (var a = 0; a < f; a += 3) {
|
||||
for (var d = (e[a >>> 2] >>> 24 - 8 * (a % 4) & 255) << 16 | (e[a + 1 >>> 2] >>> 24 - 8 * ((a + 1) % 4) & 255) << 8 | e[a + 2 >>> 2] >>> 24 - 8 * ((a + 2) % 4) & 255, g = 0; 4 > g && a + 0.75 * g < f; g++) {
|
||||
b.push(c.charAt(d >>> 6 * (3 - g) & 63));
|
||||
}
|
||||
}if (e = c.charAt(64)) for (; b.length % 4;) {
|
||||
b.push(e);
|
||||
}return b.join("");
|
||||
}, parse: function parse(b) {
|
||||
var e = b.length,
|
||||
f = this._map,
|
||||
c = f.charAt(64);c && (c = b.indexOf(c), -1 != c && (e = c));for (var c = [], a = 0, d = 0; d < e; d++) {
|
||||
if (d % 4) {
|
||||
var g = f.indexOf(b.charAt(d - 1)) << 2 * (d % 4),
|
||||
h = f.indexOf(b.charAt(d)) >>> 6 - 2 * (d % 4);c[a >>> 2] |= (g | h) << 24 - 8 * (a % 4);a++;
|
||||
}
|
||||
}return j.create(c, a);
|
||||
}, _map: "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=" };
|
||||
})();
|
||||
|
||||
hawk.crypto.utils = CryptoJS;
|
||||
|
||||
// Export if used as a module
|
||||
|
||||
if (typeof module !== 'undefined' && module.exports) {
|
||||
module.exports = hawk;
|
||||
}
|
||||
|
||||
/* eslint-enable */
|
||||
// $lab:coverage:on$
|
Различия файлов скрыты, потому что одна или несколько строк слишком длинны
Различия файлов скрыты, потому что одна или несколько строк слишком длинны
|
@ -1,83 +0,0 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
.navbar {
|
||||
background-color: #4d4f53;
|
||||
border-radius: 0;
|
||||
}
|
||||
|
||||
.navbar .navbar-brand {
|
||||
color: #fff;
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
letter-spacing: 1px;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.navbar .nav a {
|
||||
color: #ccc;
|
||||
}
|
||||
|
||||
.navbar .nav a:hover,
|
||||
.navbar .nav a:focus{
|
||||
color: #ccc;
|
||||
background: transparent;
|
||||
border: 0;
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.navbar .nav .dropdown-menu a {
|
||||
color: #292b2c
|
||||
}
|
||||
|
||||
|
||||
footer {
|
||||
color: #4d4f53;
|
||||
font-size: 12px;
|
||||
line-height: 1.6em;
|
||||
margin: 2em 0;
|
||||
padding: 0;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
footer ul li {
|
||||
display: inline-block;
|
||||
margin: 0 1em;
|
||||
}
|
||||
|
||||
footer ul {
|
||||
list-style: none;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
|
||||
/* Phones (<768px) */
|
||||
@media (max-width: 768px) {
|
||||
|
||||
}
|
||||
|
||||
/* Tablets (≥768px) */
|
||||
@media (max-width: 992px) and (min-width: 768px) {
|
||||
#content .container-fluid,
|
||||
.navbar .container-fluid {
|
||||
max-width: 733px;
|
||||
}
|
||||
}
|
||||
|
||||
/* Desktops (≥992px) */
|
||||
@media (max-width: 1200px) and (min-width: 992px) {
|
||||
#content .container-fluid,
|
||||
.navbar .container-fluid {
|
||||
max-width: 957px;
|
||||
}
|
||||
}
|
||||
|
||||
/* Desktops (≥1200px) */
|
||||
@media (min-width: 1200px) {
|
||||
#content .container-fluid,
|
||||
.navbar .container-fluid {
|
||||
max-width: 1165px;
|
||||
}
|
||||
}
|
|
@ -1,126 +0,0 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
angular.module('relengapi', []);
|
||||
|
||||
angular.module('relengapi').config(function($httpProvider) {
|
||||
var summarize_config = function(config) {
|
||||
var meth = config.method;
|
||||
var url = config.url;
|
||||
return meth + " " + url;
|
||||
};
|
||||
|
||||
// Unlike POST and PUT, Angular doesn't set the content-type for 'patch' by
|
||||
// default, but we'd like it to do so
|
||||
$httpProvider.defaults.headers.patch['Content-Type'] = $httpProvider.defaults.headers.post['Content-Type'];
|
||||
|
||||
$httpProvider.interceptors.push(function($q) {
|
||||
return {
|
||||
'request': function(config) {
|
||||
if (config.is_restapi_request) {
|
||||
config.headers["Authorization"] = tcauth.get_header(config.url, config.method);
|
||||
|
||||
if (config.data) {
|
||||
// Firefox will helpfully produce a clickable rendition of the data
|
||||
console.log("RelengAPI request:", summarize_config(config),
|
||||
'body', config.data);
|
||||
} else {
|
||||
console.log("RelengAPI request:", summarize_config(config));
|
||||
}
|
||||
}
|
||||
return config;
|
||||
},
|
||||
'response': function(response) {
|
||||
if (response.config.is_restapi_request) {
|
||||
if (response.data) {
|
||||
console.log("RelengAPI response:", summarize_config(response.config),
|
||||
'HTTP', response.status, 'body', response.data);
|
||||
} else {
|
||||
console.log("RelengAPI response:", summarize_config(response.config),
|
||||
'HTTP', response.status);
|
||||
}
|
||||
}
|
||||
return response;
|
||||
},
|
||||
'responseError': function(response) {
|
||||
if (response.config.is_restapi_request) {
|
||||
var message;
|
||||
if (response.data.error && response.data.error.description) {
|
||||
message = response.data.error.description;
|
||||
} else {
|
||||
message = response.statusText || ("HTTP Status " + response.status);
|
||||
}
|
||||
|
||||
var action = response.config.while
|
||||
|| (response.config.method + " " + response.config.url);
|
||||
var expectedStatuses = response.config.expectedStatuses
|
||||
|| [response.config.expectedStatus];
|
||||
if (expectedStatuses.indexOf(response.status) == -1) {
|
||||
console.log("RelengAPI call error response:", response.data);
|
||||
alertify.error("Failure while " + action + ": " + message);
|
||||
} else {
|
||||
console.log("RelengAPI response:", summarize_config(response.config),
|
||||
'HTTP', response.status);
|
||||
}
|
||||
}
|
||||
|
||||
return $q.reject(response);
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
angular.module('relengapi').provider('restapi', function() {
|
||||
var wrap = function(wrapped, config_pos) {
|
||||
return function() {
|
||||
var config;
|
||||
/* find the (possibly omitted) config argument at config_pos */
|
||||
var args = [].slice.apply(arguments);
|
||||
if (args.length == config_pos) {
|
||||
args.push({});
|
||||
}
|
||||
config = args[config_pos];
|
||||
|
||||
/* add the flag that our interceptor uses to identify RelengAPI requests */
|
||||
config.is_restapi_request = true;
|
||||
|
||||
return wrapped.apply(this, args);
|
||||
};
|
||||
};
|
||||
|
||||
this.$get = function($http) {
|
||||
// wrap the $http provider specifically for access to the backend API
|
||||
var relengapi = wrap($http, 0);
|
||||
relengapi.get = wrap($http.get, 1);
|
||||
relengapi.head = wrap($http.head, 1);
|
||||
relengapi.post = wrap($http.post, 2);
|
||||
relengapi.put = wrap($http.put, 2);
|
||||
relengapi.delete = wrap($http.delete, 1);
|
||||
relengapi.jsonp = wrap($http.jsonp, 1);
|
||||
relengapi.patch = wrap($http.patch, 2);
|
||||
return relengapi;
|
||||
};
|
||||
});
|
||||
|
||||
angular.module('relengapi').directive('perm', function(initial_data) {
|
||||
return {
|
||||
restrict: 'E',
|
||||
replace: true,
|
||||
scope: {
|
||||
'name': '@'
|
||||
},
|
||||
template:
|
||||
// note the trailing space!
|
||||
'<span class="label label-info" ' +
|
||||
'data-toggle="tooltip" data-placement="top" >{{name}}</span> ',
|
||||
link: function(scope, elt) {
|
||||
elt.tooltip({
|
||||
delay: 250,
|
||||
title: function() {
|
||||
return initial_data.perms[scope.name];
|
||||
},
|
||||
});
|
||||
}
|
||||
};
|
||||
});
|
|
@ -1,3 +0,0 @@
|
|||
# Stop all search engines from archiving this site
|
||||
User-agent: *
|
||||
Disallow: /
|
|
@ -1,114 +0,0 @@
|
|||
(function() {
|
||||
|
||||
window.tcauth = {};
|
||||
|
||||
window.tcauth.get_header = function(url, method) {
|
||||
var tc_auth = window.localStorage.getItem('tc_auth');
|
||||
|
||||
try {
|
||||
tc_auth = JSON.parse(tc_auth);
|
||||
} catch(err) {
|
||||
tc_auth = null;
|
||||
}
|
||||
|
||||
if (tc_auth == null) {
|
||||
return '';
|
||||
}
|
||||
|
||||
if (tc_auth.credentials &&
|
||||
tc_auth.credentials.clientId &&
|
||||
tc_auth.credentials.accessToken) {
|
||||
|
||||
var extData = null;
|
||||
if (tc_auth.credentials.certificate) {
|
||||
extData = new buffer.Buffer(JSON.stringify({
|
||||
certificate: JSON.parse(tc_auth.credentials.certificate)
|
||||
})).toString('base64');
|
||||
}
|
||||
|
||||
var header = hawk.client.header(
|
||||
url,
|
||||
method || 'GET',
|
||||
{
|
||||
credentials: {
|
||||
id: tc_auth.credentials.clientId,
|
||||
key: tc_auth.credentials.accessToken,
|
||||
algorithm: 'sha256'
|
||||
},
|
||||
ext: extData,
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
return header.field;
|
||||
};
|
||||
|
||||
window.tcauth.setup = function(service, default_service_url) {
|
||||
var $login = $('#login');
|
||||
var $loggedin = $('#loggedin');
|
||||
var $logout= $('#logout');
|
||||
var $email = $('#email');
|
||||
var service_url = $('body').attr('data-' + service + '-url') || default_service_url;
|
||||
|
||||
var auth = window.localStorage.getItem('auth');
|
||||
|
||||
try {
|
||||
auth = JSON.parse(auth);
|
||||
} catch(err) {
|
||||
auth = null;
|
||||
}
|
||||
if (auth != null && auth.access_token) {
|
||||
$.ajax({
|
||||
url: 'https://login.taskcluster.net/v1/oidc-credentials/mozilla-auth0',
|
||||
async: false,
|
||||
beforeSend: function (xhr) {
|
||||
xhr.setRequestHeader("Authorization", "Bearer " + auth.access_token);
|
||||
},
|
||||
error: function(xhr, status, error) {
|
||||
},
|
||||
success: function(data, status, xhr) {
|
||||
$email.html('<span>' + data.credentials.clientId + '</span>' + '<span class="caret"></span/>');
|
||||
$login.toggleClass('hidden');
|
||||
$loggedin.toggleClass('hidden');
|
||||
window.localStorage.setItem('tc_auth', JSON.stringify(data));
|
||||
$.ajax({
|
||||
url: service_url + '/init',
|
||||
async: false,
|
||||
beforeSend: function (xhr, config) {
|
||||
xhr.setRequestHeader("Authorization", tcauth.get_header(config.url, config.method));
|
||||
},
|
||||
success: function(data, status, xhr) {
|
||||
angular.module('initial_data', []).constant('initial_data', data || {});
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
} else {
|
||||
angular.module('initial_data', []).constant('initial_data', {});
|
||||
}
|
||||
|
||||
$login.on('click', function(e) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
var url = $('body').attr('data-treestatus-api-url') || 'https://localhost:8000';
|
||||
$.ajax({
|
||||
url: url + '/auth0/login',
|
||||
error: function(xhr, status, error) {
|
||||
},
|
||||
success: function(redirect_url, status, xhr) {
|
||||
window.location = redirect_url;
|
||||
}
|
||||
|
||||
});
|
||||
});
|
||||
|
||||
$logout.on('click', function(e) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
window.localStorage.removeItem('auth');
|
||||
window.localStorage.removeItem('tc_auth');
|
||||
window.location.reload();
|
||||
});
|
||||
};
|
||||
|
||||
})();
|
|
@ -1,130 +0,0 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>ToolTool - Mozilla RelEng Services</title>
|
||||
<link href="../alertify.core.css" media="screen" rel="stylesheet" type="text/css" />
|
||||
<link href="../alertify.default.css" media="screen" rel="stylesheet" type="text/css" />
|
||||
<link href="../bootstrap.min.css" media="screen" rel="stylesheet" type="text/css" />
|
||||
<link href="../bootstrap-theme.min.css" media="screen" rel="stylesheet" type="text/css" />
|
||||
<link href="../relengapi.css" media="screen" rel="stylesheet" type="text/css" />
|
||||
<link href="tokens.css" media="screen" rel="stylesheet" type="text/css" />
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<nav class="navbar">
|
||||
<div class="container-fluid">
|
||||
<div class="navbar-header">
|
||||
<a class="navbar-brand" href="../">Release Engineering</a>
|
||||
</div>
|
||||
<ul class="nav navbar-nav navbar-right">
|
||||
<li><a id="login" href="#">Login</a></li>
|
||||
<li id="loggedin" class="dropdown hidden">
|
||||
<a id="email" href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-haspopup="true" aria-expanded="false"></a>
|
||||
<ul class="dropdown-menu">
|
||||
<li><a href="https://tools.taskcluster.net/credentials">Manage credentials</a></li>
|
||||
<li><a id="logout" href="#">Logout</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<div id="content">
|
||||
<div class="container-fluid">
|
||||
|
||||
<!-- This Source Code Form is subject to the terms of the Mozilla Public
|
||||
License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
|
||||
<div ng-app="tooltool" ng-controller="TTSearchController">
|
||||
<div class="row">
|
||||
<div class="col-xs-4">
|
||||
<span class="h2">Tooltool</span>
|
||||
</div>
|
||||
<div class="col-xs-8 tt-searchbar">
|
||||
<form ng-submit="startSearch()">
|
||||
<div class="input-group">
|
||||
<input type="text" class="form-control"
|
||||
placeholder="Search for..." autofocus
|
||||
ng-model="search_query">
|
||||
<span class="input-group-btn">
|
||||
<button class="btn btn-default" type="submit">
|
||||
<span class="glyphicon glyphicon-search" aria-hidden="true"></span>
|
||||
<span class="sr-only">Search</span>
|
||||
</button>
|
||||
</span>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row" ng-show="show_help">
|
||||
<div class="col-xs-12">
|
||||
<div class="alert alert-info" role="alert">
|
||||
To begin, enter search terms above. You can search by
|
||||
<ul>
|
||||
<li>Filename</li>
|
||||
<li>SHA512 hash</li>
|
||||
<li>Upload author</li>
|
||||
<li>Upload message (including bug number)</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row" ng-show="file_results">
|
||||
<div class="col-xs-12">
|
||||
<span class="h3">Files</span>
|
||||
<div class="list-group">
|
||||
<div ng-repeat="res in file_results" class="list-group-item">
|
||||
<tt-result-file res="res"></tt-result-file>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row" ng-show="batch_results">
|
||||
<div class="col-xs-12">
|
||||
<span class="h3">Upload Batches</span>
|
||||
<div class="list-group">
|
||||
<div ng-repeat="res in batch_results" class="list-group-item">
|
||||
<tt-result-batch res="res"></tt-result-batch>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<footer>
|
||||
<hr>
|
||||
<ul>
|
||||
<li>
|
||||
<a href="https://docs.mozilla-releng.net">Documentation</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="https://github.com/mozilla/release-services/blob/master/CONTRIBUTING.rst">Contribute</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="https://github.com/mozilla/release-services/issues/new">Contact</a>
|
||||
</li>
|
||||
</ul>
|
||||
</footer>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script src="../jquery-1.11.1.min.js" type="text/javascript"></script>
|
||||
<script src="../bootstrap.min.js" type="text/javascript"></script>
|
||||
<script src="../alertify.min.js" type="text/javascript"></script>
|
||||
<script src="../auth_external.js" type="text/javascript"></script>
|
||||
<script src="../angular.min.js" type="text/javascript"></script>
|
||||
<script src="../moment.min.js" type="text/javascript"></script>
|
||||
<script src="../angular-moment.min.js" type="text/javascript"></script>
|
||||
<script src="../buffer.js" type="text/javascript"></script>
|
||||
<script src="../hawk.js" type="text/javascript"></script>
|
||||
<script src="../relengapi.js" type="text/javascript"></script>
|
||||
<script src="../tcauth.js" type="text/javascript"></script>
|
||||
<script src="tooltool.js" type="text/javascript"></script>
|
||||
<script type="text/javascript">
|
||||
window.tcauth.setup('tooltool-api', 'https://localhost:8002');
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
|
@ -1,40 +0,0 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
.list-group {
|
||||
/* list-group's bottom margin is huge and looks awful when
|
||||
* list-groups are nested */
|
||||
margin-bottom:5px
|
||||
}
|
||||
.tt-searchbar {
|
||||
/* a little breathing room beneath the searchbar */
|
||||
margin-bottom: 1em;
|
||||
}
|
||||
|
||||
.tt-result-controls {
|
||||
text-align: right;
|
||||
position: absolute;
|
||||
right: 8px;
|
||||
width: 3em;
|
||||
}
|
||||
|
||||
.tt-filesize {
|
||||
color: #888;
|
||||
}
|
||||
|
||||
.tt-sha512 {
|
||||
text-size: 85%;
|
||||
}
|
||||
|
||||
.tt-upload-message {
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
.download-link {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.details-link {
|
||||
cursor: pointer;
|
||||
}
|
|
@ -1,91 +0,0 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
angular.module('tooltool', ['relengapi', 'angularMoment']);
|
||||
|
||||
angular.module('tooltool').controller('TTSearchController',
|
||||
function($scope, restapi) {
|
||||
$scope.search_query = ''
|
||||
$scope.show_help = true;
|
||||
$scope.backend_url = $('body').attr('data-tooltool-api-url') || 'https://localhost:8002';
|
||||
$scope.file_results = []
|
||||
$scope.batch_results = []
|
||||
|
||||
// temp
|
||||
$scope.startSearch = function() {
|
||||
$scope.show_help = false;
|
||||
var q = $scope.search_query;
|
||||
|
||||
// search for files and batches at the same time
|
||||
restapi({
|
||||
url: $scope.backend_url + '/file?q=' + q,
|
||||
method: 'GET',
|
||||
while: 'searching files',
|
||||
}).then(function(response) {
|
||||
$scope.file_results = response.data.result;
|
||||
});
|
||||
|
||||
restapi({
|
||||
url: $scope.backend_url + '/upload?q=' + q,
|
||||
method: 'GET',
|
||||
while: 'searching upload batches',
|
||||
}).then(function(response) {
|
||||
var batches = response.data.result;
|
||||
|
||||
// put the filename into each file record
|
||||
angular.forEach(batches, function (batch) {
|
||||
angular.forEach(batch.files, function(file, filename) {
|
||||
file.filenames = [filename];
|
||||
});
|
||||
});
|
||||
$scope.batch_results = batches;
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
angular.module('tooltool').directive('ttResultFile', function() {
|
||||
return {
|
||||
restrict: 'E',
|
||||
replace: true,
|
||||
priority: 1001, // run after ng-repeat
|
||||
templateUrl: 'tt-result-file.html',
|
||||
scope: {
|
||||
res: '=',
|
||||
},
|
||||
link: function(scope, element, attrs) {
|
||||
scope.backend_url = $('body').attr('data-tooltool-api-url') || 'https://localhost:8002';
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
angular.module('tooltool').directive('ttResultBatch', function() {
|
||||
return {
|
||||
restrict: 'E',
|
||||
replace: true,
|
||||
priority: 1001, // run after ng-repeat
|
||||
templateUrl: 'tt-result-batch.html',
|
||||
scope: {
|
||||
res: '=',
|
||||
},
|
||||
link: function(scope, element, attrs) {
|
||||
scope.details = false;
|
||||
scope.backend_url = $('body').attr('data-tooltool-api-url') || 'https://localhost:8002';
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
angular.module('tooltool').directive('ttDigest', function() {
|
||||
return {
|
||||
restrict: 'E',
|
||||
replace: true,
|
||||
template: '<span ng-click="limit = 128" class="tt-sha512">{{digest|limitTo:limit}}</span>',
|
||||
scope: {
|
||||
algorithm: '@',
|
||||
digest: '@',
|
||||
},
|
||||
link: function(scope, element, attrs) {
|
||||
scope.limit = 8;
|
||||
},
|
||||
};
|
||||
});
|
|
@ -1,25 +0,0 @@
|
|||
<div>
|
||||
<!-- This Source Code Form is subject to the terms of the Mozilla Public
|
||||
License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
|
||||
<div class="tt-result-controls">
|
||||
<span ng-click="details = !details"
|
||||
class="glyphicon details-link"
|
||||
ng-class="{'glyphicon-chevron-up': details,
|
||||
'glyphicon-chevron-down': !details}"
|
||||
aria-hidden="true"></span>
|
||||
</div>
|
||||
<div class="tt-result-info">
|
||||
<span class="tt-upload-date">{{res.uploaded | amDateFormat:'l LTS'}}</span> -
|
||||
<span class="tt-upload-author">{{res.author}}</span> -
|
||||
<!-- TODO: filter to replace Bug XXXXXX by a link -->
|
||||
<span class="tt-upload-message">{{res.message}}</span>
|
||||
</div>
|
||||
<div ng-if="details">
|
||||
<div class="list-group">
|
||||
<div ng-repeat="file in res.files" class="list-group-item">
|
||||
<tt-result-file res="file"></tt-result-file>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
|
@ -1,19 +0,0 @@
|
|||
<div>
|
||||
<!-- This Source Code Form is subject to the terms of the Mozilla Public
|
||||
License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
|
||||
<div ng-if="res.has_instances" class="tt-result-controls">
|
||||
<a href="{{backend_url}}/{{res.algorithm}}/{{res.digest}}"><span class="glyphicon glyphicon-download download-link" aria-hidden="true"></span>
|
||||
<span class="sr-only">Download</span></a>
|
||||
</div>
|
||||
<div class="tt-result-info">
|
||||
<span ng-if="res.filenames">
|
||||
<span class="tt-filename">{{res.filenames.join(', ')}}</span> -
|
||||
</span>
|
||||
<tt-digest algorithm="{{res.algorithm}}" digest="{{res.digest}}"></tt-digest> -
|
||||
<!-- TODO: get all filenames used for this file in any batch -->
|
||||
<span class="tt-filesize">{{res.size}} bytes</span> -
|
||||
<span ng-if="res.visibility == 'public'" class="text-muted">PUBLIC</span>
|
||||
<span ng-if="res.visibility == 'internal'" class="text-danger">INTERNAL</span>
|
||||
</div>
|
||||
</div>
|
|
@ -1,13 +0,0 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no" />
|
||||
<meta http-equiv="x-ua-compatible" content="ie=edge" />
|
||||
<meta name="description" content="<%= htmlWebpackPlugin.options.description %>" />
|
||||
<meta name="author" content="<%= htmlWebpackPlugin.options.author %>" />
|
||||
<title><%= htmlWebpackPlugin.options.title %></title>
|
||||
</head>
|
||||
<body>
|
||||
</body>
|
||||
</html>
|
|
@ -1 +0,0 @@
|
|||
../../../lib/frontend_common/title.js
|
|
@ -1,34 +0,0 @@
|
|||
import React from 'react'
|
||||
import chai from 'chai'
|
||||
import chaiEnzyme from 'chai-enzyme'
|
||||
import chaiImmutable from 'chai-immutable';
|
||||
import { Link } from 'react-router'
|
||||
import { fromJS } from 'immutable';
|
||||
import { shallow } from 'enzyme'
|
||||
|
||||
import { Layout, routes } from './../src/layout';
|
||||
import app, { initialState, reducers } from './../src/index';
|
||||
|
||||
const expect = chai.expect;
|
||||
|
||||
chai.use(chaiImmutable);
|
||||
chai.use(chaiEnzyme())
|
||||
|
||||
describe('<Layout />', () => {
|
||||
|
||||
it('renders nav and main element', () => {
|
||||
const wrapper = shallow(<Layout/>);
|
||||
expect(wrapper.find('nav')).to.have.length(1);
|
||||
expect(wrapper.find('#content')).to.have.length(1);
|
||||
});
|
||||
|
||||
it('renders <Link/> elements for all routes', () => {
|
||||
const wrapper = shallow(<Layout/>);
|
||||
expect(wrapper.find(Link)).to.have.length(routes.count());
|
||||
});
|
||||
|
||||
it('test that initialState is our current state (without routing)', () => {
|
||||
expect(app.store.getState().delete('routing')).to.equal(fromJS(initialState));
|
||||
});
|
||||
|
||||
});
|
|
@ -1 +0,0 @@
|
|||
../../lib/frontend_common/webpack.config.js
|
|
@ -1 +0,0 @@
|
|||
1.0.0
|
|
@ -1,91 +0,0 @@
|
|||
{ releng_pkgs
|
||||
}:
|
||||
|
||||
let
|
||||
|
||||
inherit (releng_pkgs.lib) mkBackend mkTaskclusterHook fromRequirementsFile filterSource;
|
||||
inherit (releng_pkgs.pkgs) writeScript writeText dockerTools;
|
||||
inherit (releng_pkgs.pkgs.lib) fileContents;
|
||||
inherit (releng_pkgs.tools) pypi2nix;
|
||||
|
||||
python = import ./requirements.nix { inherit (releng_pkgs) pkgs; };
|
||||
project_name = "tooltool/api";
|
||||
version = fileContents ./VERSION;
|
||||
|
||||
mkCronJob = { schedule, command }:
|
||||
builtins.listToAttrs (
|
||||
map (channel:
|
||||
{ name = channel;
|
||||
value =
|
||||
let
|
||||
hook_name = "${self.name}_${command}_${channel}";
|
||||
hook = mkTaskclusterHook {
|
||||
name = hook_name;
|
||||
owner = "rgarbas@mozilla.com";
|
||||
inherit schedule;
|
||||
scopes =
|
||||
[ "secrets:get:repo:github.com/mozilla-releng/services:branch:${channel}"
|
||||
"queue:create-task:aws-provisioner-v1/releng-svc"
|
||||
];
|
||||
taskImage = self.docker;
|
||||
taskEnv = {
|
||||
TASKCLUSTER_SECRET = "repo:github.com/mozilla-releng/services:branch:${channel}";
|
||||
};
|
||||
taskCapabilities = {};
|
||||
taskCommand = [
|
||||
"flask"
|
||||
command
|
||||
];
|
||||
deadline = "4 hours";
|
||||
maxRunTime = 4 * 60 * 60;
|
||||
};
|
||||
in
|
||||
writeText "taskcluster-hook-${hook_name}.json" (builtins.toJSON hook);
|
||||
}) ["testing" "staging" "production"]);
|
||||
|
||||
self = mkBackend {
|
||||
inherit python version project_name;
|
||||
inStaging = true;
|
||||
inProduction = true;
|
||||
src = filterSource ./. { inherit(self) name; };
|
||||
buildInputs =
|
||||
(fromRequirementsFile ./../../../lib/cli_common/requirements-dev.txt python.packages) ++
|
||||
(fromRequirementsFile ./../../../lib/backend_common/requirements-dev.txt python.packages) ++
|
||||
(fromRequirementsFile ./requirements-dev.txt python.packages);
|
||||
propagatedBuildInputs =
|
||||
(fromRequirementsFile ./requirements.txt python.packages);
|
||||
prePatch = ''
|
||||
rm -f tooltool_api/tooltool.py.download
|
||||
ln -s ${../client/tooltool.py} tooltool_api/tooltool.py.download
|
||||
'';
|
||||
passthru = {
|
||||
cron = {
|
||||
check_pending_uploads = mkCronJob { schedule = [ "*/10 * * * *" ]; # every 10 min;
|
||||
command = "check-pending-uploads";
|
||||
};
|
||||
replicate = mkCronJob { schedule = [ "0 * * * *" ]; # every 1 hour;
|
||||
command = "replicate";
|
||||
};
|
||||
};
|
||||
update = writeScript "update-${self.name}" ''
|
||||
pushd ${self.src_path}
|
||||
cache_dir=$PWD/../../../tmp/pypi2nix
|
||||
mkdir -p $cache_dir
|
||||
eval ${pypi2nix}/bin/pypi2nix -v \
|
||||
-C $cache_dir \
|
||||
-V 3.7 \
|
||||
-O ../../../nix/requirements_override.nix \
|
||||
-E postgresql \
|
||||
-s intreehooks \
|
||||
-s flit \
|
||||
-s vcversioner \
|
||||
-s pytest-runner \
|
||||
-s setuptools-scm \
|
||||
-r requirements.txt \
|
||||
-r requirements-dev.txt
|
||||
popd
|
||||
'';
|
||||
};
|
||||
};
|
||||
|
||||
in self
|
|
@ -1 +0,0 @@
|
|||
-r ./../../../lib/backend_common/requirements-dev.txt
|
Разница между файлами не показана из-за своего большого размера
Загрузить разницу
|
@ -1,7 +0,0 @@
|
|||
-e ./../../../lib/cli_common[log,pulse] #egg=mozilla-cli-common
|
||||
-e ./../../../lib/backend_common[log,security,cors,api,auth,db,pulse] #egg=mozilla-backend-common
|
||||
|
||||
boto
|
||||
|
||||
gunicorn
|
||||
psycopg2
|
|
@ -1,109 +0,0 @@
|
|||
aioamqp==0.12.0
|
||||
aiohttp==3.5.4
|
||||
alembic==1.0.8
|
||||
amqp==2.4.2
|
||||
async-timeout==3.0.1
|
||||
atomicwrites==1.3.0
|
||||
attrs==19.1.0
|
||||
blinker==1.4
|
||||
boto==2.49.0
|
||||
boto3==1.9.123
|
||||
botocore==1.12.123
|
||||
certifi==2019.3.9
|
||||
chardet==3.0.4
|
||||
Click==7.0
|
||||
clickclick==1.2.2
|
||||
codecov==2.0.15
|
||||
connexion==2.2.0
|
||||
coverage==4.5.3
|
||||
coveralls==1.7.0
|
||||
docopt==0.6.2
|
||||
docutils==0.14
|
||||
ecdsa==0.13
|
||||
entrypoints==0.3
|
||||
fancycompleter==0.8
|
||||
flake8==3.7.7
|
||||
flake8-coding==1.3.1
|
||||
flake8-copyright==0.2.2
|
||||
flake8-debugger==3.1.0
|
||||
flake8-isort==2.7.0
|
||||
flake8-mypy==17.8.0
|
||||
flake8-quotes==1.0.0
|
||||
Flask==1.0.2
|
||||
Flask-Caching==1.6.0
|
||||
Flask-Cors==3.0.7
|
||||
Flask-Login==0.4.1
|
||||
Flask-Migrate==2.4.0
|
||||
flask-oidc==1.4.0
|
||||
Flask-SQLAlchemy==2.3.2
|
||||
flask-talisman==0.6.0
|
||||
flit==1.3
|
||||
future==0.17.1
|
||||
gunicorn==19.9.0
|
||||
httplib2==0.12.1
|
||||
idna==2.8
|
||||
inflection==0.3.1
|
||||
inotify==0.2.10
|
||||
intreehooks==1.0
|
||||
isort==4.3.16
|
||||
itsdangerous==0.24
|
||||
Jinja2==2.10
|
||||
jmespath==0.9.4
|
||||
jsonschema==2.6.0
|
||||
kombu==4.4.0
|
||||
Logbook==1.4.3
|
||||
Mako==1.0.8
|
||||
MarkupSafe==1.1.1
|
||||
mccabe==0.6.1
|
||||
mohawk==0.3.4
|
||||
more-itertools==6.0.0
|
||||
mozdef-client==1.0.11
|
||||
mozilla-backend-common==1.0.0
|
||||
mozilla-cli-common==1.0.0
|
||||
multidict==4.5.2
|
||||
mypy==0.670
|
||||
mypy-extensions==0.4.1
|
||||
nose==1.3.7
|
||||
oauth2client==4.1.3
|
||||
openapi-spec-validator==0.2.6
|
||||
pdbpp==0.9.14
|
||||
pluggy==0.9.0
|
||||
psycopg2==2.7.7
|
||||
py==1.8.0
|
||||
pyasn1==0.4.5
|
||||
pyasn1-modules==0.2.4
|
||||
pycodestyle==2.5.0
|
||||
pyflakes==2.1.1
|
||||
Pygments==2.3.1
|
||||
pytest==4.3.1
|
||||
pytest-cov==2.6.1
|
||||
pytest-runner==4.4
|
||||
python-dateutil==2.6.1
|
||||
python-editor==1.0.4
|
||||
python-hglib==2.6.1
|
||||
python-jose==3.0.1
|
||||
pytoml==0.1.20
|
||||
pytz==2018.9
|
||||
PyYAML==5.1
|
||||
raven==6.10.0
|
||||
requests==2.21.0
|
||||
requests-futures==0.9.9
|
||||
responses==0.10.6
|
||||
rsa==4.0
|
||||
s3transfer==0.2.0
|
||||
setuptools-scm==3.2.0
|
||||
six==1.12.0
|
||||
slugid==2.0.0
|
||||
SQLAlchemy==1.3.1
|
||||
structlog==19.1.0
|
||||
swagger-ui-bundle==0.0.3
|
||||
taskcluster==7.0.1
|
||||
taskcluster-urls==11.0.0
|
||||
testfixtures==6.6.2
|
||||
typed-ast==1.3.1
|
||||
urllib3==1.24.1
|
||||
vcversioner==2.16.0.0
|
||||
vine==1.3.0
|
||||
Werkzeug==0.15.1
|
||||
wmctrl==0.3
|
||||
yarl==1.3.0
|
|
@ -1,5 +0,0 @@
|
|||
{ pkgs, python }:
|
||||
|
||||
self: super: {
|
||||
|
||||
}
|
|
@ -1,71 +0,0 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# This Source Code Form is subject to the terms of the Mozilla Public
|
||||
# License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
import base64
|
||||
import os
|
||||
|
||||
import cli_common.taskcluster
|
||||
import tooltool_api.config
|
||||
|
||||
DEBUG = bool(os.environ.get('DEBUG', False))
|
||||
|
||||
|
||||
# -- LOAD SECRETS -------------------------------------------------------------
|
||||
|
||||
required = [
|
||||
'APP_CHANNEL',
|
||||
'SECRET_KEY_BASE64',
|
||||
'DATABASE_URL',
|
||||
# https://github.com/mozilla/build-cloud-tools/blob/master/configs/cloudformation/tooltool.py
|
||||
'S3_REGIONS',
|
||||
# https://github.com/mozilla/build-cloud-tools/blob/master/configs/cloudformation/iam_relengapi.py
|
||||
'S3_REGIONS_ACCESS_KEY_ID',
|
||||
'S3_REGIONS_SECRET_ACCESS_KEY',
|
||||
'PULSE_USER',
|
||||
'PULSE_PASSWORD',
|
||||
]
|
||||
|
||||
existing = {x: os.environ.get(x) for x in required if x in os.environ}
|
||||
existing['ALLOW_ANONYMOUS_PUBLIC_DOWNLOAD'] = False
|
||||
# This value should be fairly short (and its value is included in the
|
||||
# `upload_batch` docstring). Uploads cannot be validated until this
|
||||
# time has elapsed, otherwise a malicious uploader could alter a file
|
||||
# after it had been verified.
|
||||
existing['UPLOAD_EXPIRES_IN'] = 60
|
||||
existing['DOWLOAD_EXPIRES_IN'] = 60
|
||||
|
||||
secrets = cli_common.taskcluster.get_secrets(
|
||||
os.environ.get('TASKCLUSTER_SECRET'),
|
||||
tooltool_api.config.PROJECT_NAME,
|
||||
required=required,
|
||||
existing=existing,
|
||||
taskcluster_client_id=os.environ.get('TASKCLUSTER_CLIENT_ID'),
|
||||
taskcluster_access_token=os.environ.get('TASKCLUSTER_ACCESS_TOKEN'),
|
||||
)
|
||||
|
||||
locals().update(secrets)
|
||||
|
||||
RELENGAPI_AUTH = True
|
||||
SECRET_KEY = base64.b64decode(secrets['SECRET_KEY_BASE64'])
|
||||
|
||||
|
||||
# -- DATABASE -----------------------------------------------------------------
|
||||
|
||||
SQLALCHEMY_TRACK_MODIFICATIONS = False
|
||||
|
||||
if DEBUG:
|
||||
SQLALCHEMY_ECHO = True
|
||||
|
||||
# We require DATABASE_URL set by environment variables for branches deployed to Dockerflow.
|
||||
if secrets['APP_CHANNEL'] in ('testing', 'staging', 'production'):
|
||||
if 'DATABASE_URL' not in os.environ:
|
||||
SQLALCHEMY_DATABASE_URI = secrets['DATABASE_URL']
|
||||
# XXX: until we only deploy to GCP
|
||||
# raise RuntimeError(f'DATABASE_URL has to be set as an environment variable, when '
|
||||
# f'APP_CHANNEL is set to {secrets["APP_CHANNEL"]}')
|
||||
else:
|
||||
SQLALCHEMY_DATABASE_URI = os.environ['DATABASE_URL']
|
||||
else:
|
||||
SQLALCHEMY_DATABASE_URI = secrets['DATABASE_URL']
|
|
@ -1 +0,0 @@
|
|||
../../../nix/setup.cfg
|
|
@ -1,43 +0,0 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# This Source Code Form is subject to the terms of the Mozilla Public
|
||||
# License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
import setuptools
|
||||
|
||||
|
||||
def read_requirements(file_):
|
||||
lines = []
|
||||
with open(file_) as f:
|
||||
for line in f.readlines():
|
||||
line = line.strip()
|
||||
if line.startswith('-e ') or line.startswith('http://') or line.startswith('https://'):
|
||||
extras = ''
|
||||
if '[' in line:
|
||||
extras = '[' + line.split('[')[1].split(']')[0] + ']'
|
||||
line = line.split('#')[1].split('egg=')[1] + extras
|
||||
elif line == '' or line.startswith('#') or line.startswith('-'):
|
||||
continue
|
||||
line = line.split('#')[0].strip()
|
||||
lines.append(line)
|
||||
return sorted(list(set(lines)))
|
||||
|
||||
|
||||
with open('VERSION') as f:
|
||||
VERSION = f.read().strip()
|
||||
|
||||
|
||||
setuptools.setup(
|
||||
name='mozilla-tooltool-api',
|
||||
version=VERSION,
|
||||
description='The code behind https://tooltool.mozilla-releng.net/',
|
||||
author='Mozilla Release Services Team',
|
||||
author_email='release-services@mozilla.com',
|
||||
url='https://tooltool.mozilla-releng.net',
|
||||
tests_require=read_requirements('requirements-dev.txt'),
|
||||
install_requires=read_requirements('requirements.txt'),
|
||||
packages=setuptools.find_packages(),
|
||||
include_package_data=True,
|
||||
zip_safe=False,
|
||||
license='MPL2',
|
||||
)
|
|
@ -1,28 +0,0 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# This Source Code Form is subject to the terms of the Mozilla Public
|
||||
# License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
import pytest
|
||||
|
||||
import backend_common
|
||||
|
||||
|
||||
@pytest.fixture(scope='session')
|
||||
def app():
|
||||
'''Load tooltool_api in test mode
|
||||
'''
|
||||
import tooltool_api
|
||||
|
||||
config = backend_common.testing.get_app_config({
|
||||
'SQLALCHEMY_DATABASE_URI': 'sqlite://',
|
||||
'SQLALCHEMY_TRACK_MODIFICATIONS': False,
|
||||
'S3_REGIONS': dict(),
|
||||
'S3_REGIONS_ACCESS_KEY_ID': '123',
|
||||
'S3_REGIONS_SECRET_ACCESS_KEY': '123',
|
||||
})
|
||||
app = tooltool_api.create_app(config)
|
||||
|
||||
with app.app_context():
|
||||
backend_common.testing.configure_app(app)
|
||||
yield app
|
|
@ -1,30 +0,0 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# This Source Code Form is subject to the terms of the Mozilla Public
|
||||
# License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
import hashlib
|
||||
|
||||
|
||||
def test_now():
|
||||
import tooltool_api.utils
|
||||
|
||||
assert tooltool_api.utils.now().tzname() == 'UTC'
|
||||
|
||||
|
||||
def test_keyname():
|
||||
import tooltool_api.utils
|
||||
|
||||
ONE = '1\n'
|
||||
ONE_DIGEST = hashlib.sha512(ONE.encode('utf-8')).hexdigest()
|
||||
|
||||
assert tooltool_api.utils.keyname(ONE_DIGEST) == 'sha512/' + ONE_DIGEST
|
||||
|
||||
|
||||
def test_is_valid_sha512():
|
||||
import tooltool_api.utils
|
||||
|
||||
VALID_SHA512 = '1' * 128
|
||||
|
||||
assert tooltool_api.utils.is_valid_sha512('123') is None
|
||||
assert tooltool_api.utils.is_valid_sha512(VALID_SHA512).string == VALID_SHA512
|
|
@ -1,58 +0,0 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# This Source Code Form is subject to the terms of the Mozilla Public
|
||||
# License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
import os
|
||||
import typing
|
||||
|
||||
import flask
|
||||
import werkzeug.exceptions
|
||||
|
||||
import backend_common
|
||||
import backend_common.api
|
||||
import tooltool_api.aws
|
||||
import tooltool_api.cli
|
||||
import tooltool_api.config
|
||||
import tooltool_api.models # noqa
|
||||
|
||||
|
||||
def custom_handle_default_exceptions(e: Exception) -> typing.Tuple[int, str]:
|
||||
'''Conform structure of errors as before, to make it work with client (tooltool.py).
|
||||
'''
|
||||
error = backend_common.api.handle_default_exceptions_raw(e)
|
||||
error['name'] = error['title']
|
||||
error['description'] = error['detail']
|
||||
import flask # for some reason flask needs to be imported here
|
||||
return flask.jsonify(dict(error=error)), error['status']
|
||||
|
||||
|
||||
def create_app(config: dict = None) -> flask.Flask:
|
||||
app = backend_common.create_app(
|
||||
project_name=tooltool_api.config.PROJECT_NAME,
|
||||
app_name=tooltool_api.config.APP_NAME,
|
||||
config=config,
|
||||
extensions=[
|
||||
'log',
|
||||
'security',
|
||||
'cors',
|
||||
'api',
|
||||
'auth',
|
||||
'db',
|
||||
'pulse',
|
||||
],
|
||||
)
|
||||
app.api.register(os.path.join(os.path.dirname(__file__), 'api.yml'))
|
||||
app.aws = tooltool_api.aws.AWS(app.config['S3_REGIONS_ACCESS_KEY_ID'],
|
||||
app.config['S3_REGIONS_SECRET_ACCESS_KEY'])
|
||||
|
||||
for code, exception in werkzeug.exceptions.default_exceptions.items():
|
||||
app.register_error_handler(exception, custom_handle_default_exceptions)
|
||||
|
||||
app.cli.add_command(tooltool_api.cli.cmd_worker, 'worker')
|
||||
app.cli.add_command(tooltool_api.cli.cmd_replicate, 'replicate')
|
||||
app.cli.add_command(tooltool_api.cli.cmd_check_pending_uploads, 'check-pending-uploads')
|
||||
|
||||
app.add_url_rule('/tooltool.py', view_func=tooltool_api.api.download_client)
|
||||
|
||||
return app
|
|
@ -1,314 +0,0 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# This Source Code Form is subject to the terms of the Mozilla Public
|
||||
# License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
import datetime
|
||||
import pathlib
|
||||
import random
|
||||
import typing
|
||||
|
||||
import flask
|
||||
import flask_login
|
||||
import pytz
|
||||
import sqlalchemy as sa
|
||||
import werkzeug
|
||||
import werkzeug.exceptions
|
||||
|
||||
import backend_common.auth
|
||||
import cli_common.log
|
||||
import tooltool_api.aws
|
||||
import tooltool_api.config
|
||||
import tooltool_api.models
|
||||
import tooltool_api.utils
|
||||
|
||||
logger = cli_common.log.get_logger(__name__)
|
||||
|
||||
|
||||
def _get_region_and_bucket(region: typing.Optional[str],
|
||||
regions: typing.Dict[str, str],
|
||||
) -> typing.Tuple[str, str]:
|
||||
if region and region in regions:
|
||||
return region, regions[region]
|
||||
# no region specified, so return one at random
|
||||
return random.choice(list(regions.items()))
|
||||
|
||||
|
||||
def search_batches(q: str) -> dict:
|
||||
return dict(
|
||||
result=[
|
||||
row.to_dict()
|
||||
for row in tooltool_api.models.Batch.query.filter(
|
||||
sa.or_(
|
||||
tooltool_api.models.Batch.author.contains(q),
|
||||
tooltool_api.models.Batch.message.contains(q)
|
||||
)
|
||||
).all()
|
||||
]
|
||||
)
|
||||
|
||||
|
||||
def get_batch(id: int) -> dict:
|
||||
row = tooltool_api.models.Batch.query.filter(tooltool_api.models.Batch.id == id).first()
|
||||
if not row:
|
||||
raise werkzeug.exceptions.NotFound
|
||||
return row.to_dict()
|
||||
|
||||
|
||||
def upload_batch(body: dict, region: typing.Optional[str] = None) -> dict:
|
||||
if not body['message']:
|
||||
raise werkzeug.exceptions.BadRequest('message must be non-empty')
|
||||
|
||||
if not body['files']:
|
||||
raise werkzeug.exceptions.BadRequest('a batch must include at least one file')
|
||||
|
||||
if 'author' in body:
|
||||
raise werkzeug.exceptions.BadRequest('Author must NOT be specified for upload.')
|
||||
|
||||
UPLOAD_EXPIRES_IN = flask.current_app.config['UPLOAD_EXPIRES_IN']
|
||||
if type(UPLOAD_EXPIRES_IN) is not int:
|
||||
raise werkzeug.exceptions.InternalServerError('UPLOAD_EXPIRES_IN should be of type int.')
|
||||
|
||||
S3_REGIONS = flask.current_app.config['S3_REGIONS'] # type: typing.Dict[str, str]
|
||||
if type(S3_REGIONS) is not dict:
|
||||
raise werkzeug.exceptions.InternalServerError('S3_REGIONS should be of type dict.')
|
||||
region, bucket = _get_region_and_bucket(region, S3_REGIONS)
|
||||
|
||||
body['author'] = flask_login.current_user.get_id()
|
||||
|
||||
# verify permissions based on visibilities
|
||||
visibilities = set(f['visibility'] for f in body['files'].values())
|
||||
for visibility in visibilities:
|
||||
permission = f'{tooltool_api.config.SCOPE_PREFIX}/upload/{visibility}'
|
||||
if not flask_login.current_user.has_permissions(permission):
|
||||
raise werkzeug.exceptions.Forbidden(f'no permission to upload {visibility} files')
|
||||
|
||||
session = flask.g.db.session
|
||||
|
||||
batch = tooltool_api.models.Batch(
|
||||
uploaded=tooltool_api.utils.now(),
|
||||
author=body['author'],
|
||||
message=body['message'],
|
||||
)
|
||||
|
||||
s3 = flask.current_app.aws.connect_to('s3', region)
|
||||
|
||||
for filename, info in body['files'].items():
|
||||
|
||||
logger2 = logger.bind(tooltool_sha512=info['digest'],
|
||||
tooltool_operation='upload',
|
||||
tooltool_batch_id=batch.id,
|
||||
mozdef=True)
|
||||
|
||||
if info['algorithm'] != 'sha512':
|
||||
raise werkzeug.exceptions.BadRequest('`sha512` is the only allowed digest algorithm')
|
||||
|
||||
if not tooltool_api.utils.is_valid_sha512(info['digest']):
|
||||
raise werkzeug.exceptions.BadRequest('Invalid sha512 digest'
|
||||
)
|
||||
digest = info['digest']
|
||||
file = tooltool_api.models.File.query.filter(tooltool_api.models.File.sha512 == digest).first()
|
||||
if file and file.visibility != info['visibility']:
|
||||
raise werkzeug.exceptions.BadRequest('Cannot change already existing file\'s visibility level.')
|
||||
|
||||
if file and file.instances != []:
|
||||
if file.size != info['size']:
|
||||
raise werkzeug.exceptions.BadRequest(f'Size mismatch for {filename}')
|
||||
else:
|
||||
if not file:
|
||||
file = tooltool_api.models.File(sha512=digest,
|
||||
visibility=info['visibility'],
|
||||
size=info['size'])
|
||||
session.add(file)
|
||||
|
||||
logger2.info(f'Generating signed S3 PUT URL to {info["digest"][:10]} for {flask_login.current_user}; expiring in {UPLOAD_EXPIRES_IN}s')
|
||||
|
||||
info['put_url'] = s3.generate_url(
|
||||
method='PUT',
|
||||
expires_in=UPLOAD_EXPIRES_IN,
|
||||
bucket=bucket,
|
||||
key=tooltool_api.utils.keyname(info['digest']),
|
||||
headers={'Content-Type': 'application/octet-stream'},
|
||||
)
|
||||
|
||||
# The PendingUpload row needs to reflect the updated expiration
|
||||
# time, even if there's an existing pending upload that expires
|
||||
# earlier. The `merge` method does a SELECT and then either
|
||||
# UPDATEs or INSERTs the row. However, merge needs the file_id,
|
||||
# rather than just a reference to the file object; and for that, we
|
||||
# need to flush the inserted file.
|
||||
session.flush()
|
||||
expires = tooltool_api.utils.now() + datetime.timedelta(seconds=UPLOAD_EXPIRES_IN)
|
||||
pu = tooltool_api.models.PendingUpload(file_id=file.id,
|
||||
region=region,
|
||||
expires=expires)
|
||||
session.merge(pu)
|
||||
|
||||
session.add(tooltool_api.models.BatchFile(filename=filename, file=file, batch=batch))
|
||||
|
||||
session.add(batch)
|
||||
session.commit()
|
||||
|
||||
body['id'] = batch.id
|
||||
return dict(result=body)
|
||||
|
||||
|
||||
def upload_complete(digest: str) -> typing.Union[werkzeug.Response,
|
||||
typing.Tuple[str, int]]:
|
||||
|
||||
if not tooltool_api.utils.is_valid_sha512(digest):
|
||||
raise werkzeug.exceptions.BadRequest('Invalid sha512 digest')
|
||||
|
||||
# if the pending upload is still valid, then we can't check this file
|
||||
# yet, so return 409 Conflict. If there is no PU, or it's expired,
|
||||
# then we can proceed.
|
||||
file = tooltool_api.models.File.query.filter(tooltool_api.models.File.sha512 == digest).first()
|
||||
if file:
|
||||
for pending_upload in file.pending_uploads:
|
||||
until = pending_upload.expires.replace(tzinfo=pytz.UTC) - tooltool_api.utils.now()
|
||||
if until > datetime.timedelta(0):
|
||||
# add 1 second to avoid rounding / skew errors
|
||||
headers = {'X-Retry-After': str(1 + int(until.total_seconds()))}
|
||||
return werkzeug.Response(status=409, headers=headers)
|
||||
|
||||
exchange = f'exchange/{flask.current_app.config["PULSE_USER"]}/{tooltool_api.config.PROJECT_NAME}'
|
||||
logger.info(f'Sending digest `{digest}` to queue `{exchange}` for route `{tooltool_api.config.PULSE_ROUTE_CHECK_FILE_PENDING_UPLOADS}`.')
|
||||
try:
|
||||
flask.current_app.pulse.publish(
|
||||
exchange,
|
||||
tooltool_api.config.PULSE_ROUTE_CHECK_FILE_PENDING_UPLOADS,
|
||||
dict(digest=digest),
|
||||
)
|
||||
except Exception as e:
|
||||
import traceback
|
||||
msg = 'Can\'t send notification to pulse.'
|
||||
trace = traceback.format_exc()
|
||||
logger.error(f'{msg}\nException:{e}\nTraceback: {trace}')
|
||||
|
||||
return '{}', 202
|
||||
|
||||
|
||||
def search_files(q: str) -> dict:
|
||||
session = flask.g.db.session
|
||||
query = session.query(tooltool_api.models.File).join(tooltool_api.models.BatchFile)
|
||||
query = query.filter(sa.or_(tooltool_api.models.BatchFile.filename.contains(q),
|
||||
tooltool_api.models.File.sha512.startswith(q)))
|
||||
return dict(result=[row.to_dict() for row in query.all()])
|
||||
|
||||
|
||||
def get_file(digest: str) -> dict:
|
||||
|
||||
if not tooltool_api.utils.is_valid_sha512(digest):
|
||||
raise werkzeug.exceptions.BadRequest('Invalid sha512 digest')
|
||||
|
||||
row = tooltool_api.models.File.query.filter(tooltool_api.models.File.sha512 == digest).first()
|
||||
if not row:
|
||||
raise werkzeug.exceptions.NotFound
|
||||
|
||||
return row.to_dict(include_instances=True)
|
||||
|
||||
|
||||
@backend_common.auth.auth.require_permissions([tooltool_api.config.SCOPE_MANAGE])
|
||||
def patch_file(digest: str, body: dict) -> dict:
|
||||
S3_REGIONS = flask.current_app.config['S3_REGIONS'] # type: typing.Dict[str, str]
|
||||
if type(S3_REGIONS) is not dict:
|
||||
raise werkzeug.exceptions.InternalServerError('S3_REGIONS should be of type dict.')
|
||||
|
||||
session = flask.current_app.db.session
|
||||
|
||||
file = session.query(tooltool_api.models.File).filter(tooltool_api.models.File.sha512 == digest).first()
|
||||
if not file:
|
||||
raise werkzeug.exceptions.NotFound
|
||||
|
||||
for change in body:
|
||||
|
||||
if 'op' not in change:
|
||||
raise werkzeug.exceptions.BadRequest('No op.')
|
||||
|
||||
if change['op'] == 'delete_instances':
|
||||
key_name = tooltool_api.utils.keyname(digest)
|
||||
|
||||
for instance in file.instances:
|
||||
conn = flask.current_app.aws.connect_to('s3', instance.region)
|
||||
|
||||
region_bucket = S3_REGIONS.get(instance.region)
|
||||
if region_bucket is None:
|
||||
raise werkzeug.exceptions.InternalServerError(f'No bucket for region `{instance.region}` defined.')
|
||||
|
||||
bucket = conn.get_bucket(region_bucket)
|
||||
bucket.delete_key(key_name)
|
||||
|
||||
session.delete(instance)
|
||||
|
||||
elif change['op'] == 'set_visibility':
|
||||
if change['visibility'] not in ('internal', 'public'):
|
||||
raise werkzeug.exceptions.BadRequest('bad visibility level')
|
||||
file.visibility = change['visibility']
|
||||
|
||||
else:
|
||||
raise werkzeug.exceptions.BadRequest('Unknown op')
|
||||
|
||||
session.commit()
|
||||
|
||||
return file.to_dict(include_instances=True)
|
||||
|
||||
|
||||
def download_file(digest: str, region: typing.Optional[str] = None) -> werkzeug.Response:
|
||||
logger2 = logger.bind(tooltool_sha512=digest, tooltool_operation='download_file')
|
||||
|
||||
S3_REGIONS = flask.current_app.config['S3_REGIONS'] # type: typing.Dict[str, str]
|
||||
if type(S3_REGIONS) is not dict:
|
||||
raise werkzeug.exceptions.InternalServerError('S3_REGIONS should be of type dict.')
|
||||
|
||||
DOWLOAD_EXPIRES_IN = flask.current_app.config['DOWLOAD_EXPIRES_IN']
|
||||
if type(DOWLOAD_EXPIRES_IN) is not int:
|
||||
raise werkzeug.exceptions.InternalServerError('DOWLOAD_EXPIRES_IN should be of type int.')
|
||||
|
||||
ALLOW_ANONYMOUS_PUBLIC_DOWNLOAD = flask.current_app.config['ALLOW_ANONYMOUS_PUBLIC_DOWNLOAD']
|
||||
if type(ALLOW_ANONYMOUS_PUBLIC_DOWNLOAD) is not bool:
|
||||
raise werkzeug.exceptions.InternalServerError('ALLOW_ANONYMOUS_PUBLIC_DOWNLOAD should be of type bool.')
|
||||
|
||||
regions = ', '.join(S3_REGIONS.keys())
|
||||
logger2.debug(f'Looking for file in following regions: {regions}')
|
||||
|
||||
if not tooltool_api.utils.is_valid_sha512(digest):
|
||||
raise werkzeug.exceptions.BadRequest('Invalid sha512 digest')
|
||||
|
||||
# see where the file is.
|
||||
file_row = tooltool_api.models.File.query.filter(
|
||||
tooltool_api.models.File.sha512 == digest).first()
|
||||
if not file_row or not file_row.instances:
|
||||
raise werkzeug.exceptions.NotFound
|
||||
|
||||
# check visibility
|
||||
if file_row.visibility != 'public' or not ALLOW_ANONYMOUS_PUBLIC_DOWNLOAD:
|
||||
permission = f'{tooltool_api.config.SCOPE_PREFIX}/download/{file_row.visibility}'
|
||||
if not flask_login.current_user.has_permissions(permission):
|
||||
raise werkzeug.exceptions.Forbidden
|
||||
|
||||
# figure out which region to use, and from there which bucket
|
||||
selected_region = None
|
||||
for file_instance in file_row.instances:
|
||||
if file_instance.region == region:
|
||||
selected_region = file_instance.region
|
||||
break
|
||||
else:
|
||||
# preferred region not found, so pick one from the available set
|
||||
selected_region = random.choice([inst.region for inst in file_row.instances])
|
||||
|
||||
bucket = S3_REGIONS.get(selected_region)
|
||||
if bucket is None:
|
||||
raise werkzeug.exceptions.InternalServerError(f'Region `{selected_region}` can not be found in S3_REGIONS.')
|
||||
|
||||
key = tooltool_api.utils.keyname(digest)
|
||||
|
||||
s3 = flask.current_app.aws.connect_to('s3', selected_region)
|
||||
logger2.info(f'Generating signed S3 GET URL for {digest[:10]}, expiring in {DOWLOAD_EXPIRES_IN}s')
|
||||
signed_url = s3.generate_url(method='GET', expires_in=DOWLOAD_EXPIRES_IN, bucket=bucket, key=key)
|
||||
|
||||
return flask.redirect(signed_url)
|
||||
|
||||
|
||||
def download_client():
|
||||
client = pathlib.Path(__file__).parent / 'tooltool.py.download'
|
||||
return flask.send_file(str(client.absolute()), attachment_filename='tooltool.py')
|
|
@ -1,487 +0,0 @@
|
|||
---
|
||||
swagger: "2.0"
|
||||
info:
|
||||
version: "1.0.0"
|
||||
title: "ToolTool"
|
||||
consumes:
|
||||
- application/json
|
||||
produces:
|
||||
- application/json
|
||||
paths:
|
||||
|
||||
|
||||
/init:
|
||||
|
||||
get:
|
||||
operationId: "backend_common.auth.initial_data"
|
||||
description: Initial data for frontend
|
||||
responses:
|
||||
200:
|
||||
description: Initial data
|
||||
schema:
|
||||
type: object
|
||||
required:
|
||||
- user
|
||||
- perms
|
||||
properties:
|
||||
user:
|
||||
type: object
|
||||
required:
|
||||
- type
|
||||
- permissions
|
||||
properties:
|
||||
type:
|
||||
type: string
|
||||
permissions:
|
||||
type: array
|
||||
items:
|
||||
type: object
|
||||
required:
|
||||
- doc
|
||||
- name
|
||||
properties:
|
||||
doc:
|
||||
type: string
|
||||
name:
|
||||
type: string
|
||||
authenticated_email:
|
||||
type: string
|
||||
perms:
|
||||
type: object
|
||||
additionalProperties:
|
||||
type: string
|
||||
|
||||
/upload:
|
||||
|
||||
get:
|
||||
operationId: "tooltool_api.api.search_batches"
|
||||
description: Search upload batches.
|
||||
parameters:
|
||||
- name: q
|
||||
in: query
|
||||
description: |
|
||||
Query parameter can match a substring of an authors email or a
|
||||
batch message.
|
||||
required: true
|
||||
type: string
|
||||
responses:
|
||||
200:
|
||||
description: An array of upload batches.
|
||||
schema:
|
||||
type: object
|
||||
required:
|
||||
- result
|
||||
properties:
|
||||
result:
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/definitions/UploadBatch'
|
||||
|
||||
post:
|
||||
operationId: "tooltool_api.api.upload_batch"
|
||||
description: |
|
||||
Create a new upload batch. The response object will contain
|
||||
a ``put_url`` for each file which needs to be uploaded -- which may not
|
||||
be all! The caller is then responsible for uploading to those URLs.
|
||||
The resulting signed URLs are valid for one hour, so uploads should
|
||||
begin within that timeframe. Consider using Amazon MD5-verification
|
||||
capabilities to ensure that the uploaded files are transferred
|
||||
correctly, although the tooltool server will verify the integrity
|
||||
anyway. The upload must have the header ``Content-Type:
|
||||
application/octet-stream``.
|
||||
|
||||
The returned URLs are only valid for 60 seconds, so all upload requests
|
||||
must begin within that timeframe. Clients should therefore perform all
|
||||
uploads in parallel, rather than sequentially. This limitation is in
|
||||
place to prevent malicious modification of files after they have been
|
||||
verified.
|
||||
parameters:
|
||||
- name: body
|
||||
in: body
|
||||
description: Upload batch.
|
||||
required: true
|
||||
schema:
|
||||
$ref: '#/definitions/UploadBatch'
|
||||
- name: region
|
||||
in: query
|
||||
description: |
|
||||
The region query argument ``region=us-west-1`` indicates a
|
||||
preference for URLs in that region, although if the region is not
|
||||
available then URLs in other regions may be returned.
|
||||
required: false
|
||||
type: string
|
||||
responses:
|
||||
200:
|
||||
description: Upload batch.
|
||||
schema:
|
||||
type: object
|
||||
required:
|
||||
- result
|
||||
properties:
|
||||
result:
|
||||
$ref: '#/definitions/UploadBatch'
|
||||
|
||||
|
||||
/upload/{id}:
|
||||
|
||||
get:
|
||||
operationId: "tooltool_api.api.get_batch"
|
||||
description: Get a specific upload batch by id.
|
||||
parameters:
|
||||
- name: id
|
||||
in: path
|
||||
description: Upload batch id.
|
||||
required: true
|
||||
type: integer
|
||||
responses:
|
||||
200:
|
||||
description: Upload batch.
|
||||
schema:
|
||||
$ref: '#/definitions/UploadBatch'
|
||||
404:
|
||||
description: Batch can not be found.
|
||||
schema:
|
||||
$ref: '#/definitions/Problem'
|
||||
|
||||
/upload/complete/sha512/{digest}:
|
||||
get:
|
||||
operationId: "tooltool_api.api.upload_complete"
|
||||
description: |
|
||||
Signal that a file has been uploaded and the server should begin
|
||||
validating it. This is merely an optimization: the server also polls
|
||||
occasionally for uploads and validates them when they appear.
|
||||
|
||||
Uploads cannot be safely validated until the upload URL has expired,
|
||||
which occurs a short time after the URL is generated (currently 60
|
||||
seconds but subject to change).
|
||||
|
||||
parameters:
|
||||
- name: digest
|
||||
in: path
|
||||
required: true
|
||||
type: string
|
||||
responses:
|
||||
202:
|
||||
description: |
|
||||
If the upload URL has expired, then the response is an HTTP 202
|
||||
indicating that the signal has been accepted. If the URL has not
|
||||
expired, then the response is an HTTP 409, and the
|
||||
``X-Retry-After`` header gives a time, in seconds, that the client
|
||||
should wait before trying again.
|
||||
schema:
|
||||
type: string
|
||||
400:
|
||||
description: Wrong digest.
|
||||
schema:
|
||||
$ref: '#/definitions/Problem'
|
||||
409:
|
||||
description: Upload expired, send retry eader
|
||||
headers:
|
||||
X-Retry-After:
|
||||
description: Retry after timestamp in epoc.
|
||||
type: string
|
||||
|
||||
|
||||
/file:
|
||||
|
||||
get:
|
||||
operationId: "tooltool_api.api.search_files"
|
||||
description: Search for files matching the query.
|
||||
parameters:
|
||||
- name: q
|
||||
in: query
|
||||
description: |
|
||||
The query matches against prefixes of hashes (at least 8
|
||||
characters) or against filenames.
|
||||
required: true
|
||||
type: string
|
||||
responses:
|
||||
200:
|
||||
description: File
|
||||
schema:
|
||||
type: object
|
||||
required:
|
||||
- result
|
||||
properties:
|
||||
result:
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/definitions/File'
|
||||
|
||||
|
||||
|
||||
/file/sha512/{digest}:
|
||||
|
||||
get:
|
||||
operationId: "tooltool_api.api.get_file"
|
||||
description: |
|
||||
Get a single file, by its digest. Filenames are associated with upload
|
||||
batches, not directly with files, so use ``GET /uploads`` to find files
|
||||
by filename.
|
||||
|
||||
The returned File instance contains an ``instances`` attribute showing
|
||||
the regions in which the file exists.
|
||||
parameters:
|
||||
- name: digest
|
||||
in: path
|
||||
required: true
|
||||
type: string
|
||||
responses:
|
||||
200:
|
||||
description: File
|
||||
schema:
|
||||
$ref: '#/definitions/File'
|
||||
400:
|
||||
description: Wrong digest.
|
||||
schema:
|
||||
$ref: '#/definitions/Problem'
|
||||
404:
|
||||
description: File can not be found.
|
||||
schema:
|
||||
$ref: '#/definitions/Problem'
|
||||
500:
|
||||
description: Internal server error.
|
||||
schema:
|
||||
$ref: '#/definitions/Problem'
|
||||
|
||||
patch:
|
||||
operationId: "tooltool_api.api.patch_file"
|
||||
description: |
|
||||
Make administrative changes to an existing file. The body is a list of
|
||||
changes to apply, each represented by a JSON object.
|
||||
|
||||
The object ``{"op": "delete_instances"}`` will cause all instances of
|
||||
the file to be deleted. The file record itself will not be deleted,
|
||||
as it is still a part of one or more upload batches, but until and
|
||||
unless someone uploads a new copy, the content will not be available
|
||||
for download.
|
||||
|
||||
If the change has op ``"set_visibility"``, then the files visibility
|
||||
will be set to the value given by the changes ``visibility`` attribute.
|
||||
For example, ``{"op": "set_visibility", "visibility": "internal"}``
|
||||
will mark a file as "internal" after someone has accidentally uploaded
|
||||
it with public visibility.
|
||||
|
||||
The returned File instance contains an ``instances`` attribute showing
|
||||
any changes.
|
||||
parameters:
|
||||
- name: digest
|
||||
in: path
|
||||
required: true
|
||||
type: string
|
||||
- name: body
|
||||
in: body
|
||||
description: Upload batch.
|
||||
required: true
|
||||
schema:
|
||||
type: array
|
||||
items:
|
||||
type: object
|
||||
required:
|
||||
- op
|
||||
properties:
|
||||
op:
|
||||
type: string
|
||||
enum:
|
||||
- set_visibility
|
||||
- delete_instances
|
||||
visibility:
|
||||
type: string
|
||||
enum:
|
||||
- public
|
||||
- internal
|
||||
responses:
|
||||
200:
|
||||
description: File
|
||||
schema:
|
||||
$ref: '#/definitions/File'
|
||||
400:
|
||||
description: Wrong op.
|
||||
schema:
|
||||
$ref: '#/definitions/Problem'
|
||||
401:
|
||||
description: No permission.
|
||||
schema:
|
||||
$ref: '#/definitions/Problem'
|
||||
404:
|
||||
description: File can not be found.
|
||||
schema:
|
||||
$ref: '#/definitions/Problem'
|
||||
500:
|
||||
description: Internal server error.
|
||||
schema:
|
||||
$ref: '#/definitions/Problem'
|
||||
|
||||
|
||||
/sha512/{digest}:
|
||||
get:
|
||||
operationId: "tooltool_api.api.download_file"
|
||||
description: Fetch a link to the file with the given sha512 digest.
|
||||
parameters:
|
||||
- name: digest
|
||||
in: path
|
||||
required: true
|
||||
type: string
|
||||
- name: region
|
||||
in: query
|
||||
required: false
|
||||
description: |
|
||||
The query argument ``region=us-west-1`` indicates a preference for
|
||||
a URL in that region, although if the file is not available in tht
|
||||
region then a URL from another region may be returned.
|
||||
type: string
|
||||
responses:
|
||||
302:
|
||||
description: Redirect to a signed download URL.
|
||||
headers:
|
||||
Location:
|
||||
description: Download URL.
|
||||
type: string
|
||||
400:
|
||||
description: sha512 digest is not valid.
|
||||
schema:
|
||||
$ref: '#/definitions/Problem'
|
||||
401:
|
||||
description: No permission to download file.
|
||||
schema:
|
||||
$ref: '#/definitions/Problem'
|
||||
404:
|
||||
description: File can not be found.
|
||||
schema:
|
||||
$ref: '#/definitions/Problem'
|
||||
500:
|
||||
description: Internal server error.
|
||||
schema:
|
||||
$ref: '#/definitions/Problem'
|
||||
|
||||
|
||||
definitions:
|
||||
|
||||
UploadBatch:
|
||||
type: object
|
||||
description: |
|
||||
An upload batch describes a collection of related files that are uploaded
|
||||
together -- similar to a version-control commit. The message and files
|
||||
list must be non-empty.
|
||||
required:
|
||||
- message
|
||||
- files
|
||||
properties:
|
||||
id:
|
||||
type: integer
|
||||
description: Identifier for this batch
|
||||
uploaded:
|
||||
type: string # TODO: dateTime
|
||||
description: |
|
||||
The date and time when this upload occurred. This will be added by
|
||||
the server and need not be specified when making a new upload.
|
||||
author:
|
||||
type: string
|
||||
description: |
|
||||
The author (uploader) of the batch. Do not include this when
|
||||
submitting a batch for upload; it will be filled in based on the
|
||||
request authentication.
|
||||
message:
|
||||
type: string
|
||||
description: |
|
||||
The message for the batch. Format this like a version-control
|
||||
message.
|
||||
files:
|
||||
type: object
|
||||
description: |
|
||||
The collection of files in this batch, keyed by filename. Note that
|
||||
filenames containing path separators (``\`` and ``/``) will be
|
||||
rejected the tooltool client.
|
||||
additionalProperties:
|
||||
$ref: '#/definitions/File'
|
||||
|
||||
File:
|
||||
type: object
|
||||
description: |
|
||||
A representation of a single file, identified by its contents rather than
|
||||
its filename. Depending on context, this may contain URLs to download or
|
||||
upload the file.
|
||||
required:
|
||||
- visibility
|
||||
properties:
|
||||
size:
|
||||
type: integer
|
||||
description: The size of the file, in bytes
|
||||
digest:
|
||||
type: string
|
||||
description: The sha512 digest of the file contents.
|
||||
algorithm:
|
||||
type: string
|
||||
description: |
|
||||
The digest algorithm (reserved for future expansion; must always be
|
||||
sha512).
|
||||
visibility:
|
||||
type: string
|
||||
description: |
|
||||
The visibility level of this file. When making an upload, the uploader
|
||||
is (legally) responsible for selecting the correct visibility level.
|
||||
default: public
|
||||
enum:
|
||||
- public
|
||||
- internal
|
||||
has_instances:
|
||||
type: boolean
|
||||
description: |
|
||||
Boolean to determine whether the file is available to download.
|
||||
instances:
|
||||
type: array
|
||||
description: |
|
||||
The regions containing an instance of this file. This field is
|
||||
generally omitted except where specified.
|
||||
items:
|
||||
type: string
|
||||
get_url:
|
||||
type: string
|
||||
description: The URL from which this file can be downlaoded via HTTP GET.
|
||||
put_url:
|
||||
type: string
|
||||
description: |
|
||||
The URL to which this file can be uploaded via HTTP PUT. The URL
|
||||
requires the request content-type to be ``application/octet-stream``.
|
||||
|
||||
Problem:
|
||||
type: object
|
||||
properties:
|
||||
type:
|
||||
type: string
|
||||
format: uri
|
||||
description: |
|
||||
An absolute URI that identifies the problem type. When dereferenced,
|
||||
it SHOULD provide human-readable documentation for the problem type
|
||||
(e.g., using HTML).
|
||||
default: 'about:blank'
|
||||
example: 'https://zalando.github.io/problem/constraint-violation'
|
||||
title:
|
||||
type: string
|
||||
description: |
|
||||
A short, summary of the problem type. Written in english and readable
|
||||
for engineers (usually not suited for non technical stakeholders and
|
||||
not localized)
|
||||
example: Service Unavailable
|
||||
status:
|
||||
type: integer
|
||||
format: int32
|
||||
description: |
|
||||
The HTTP status code generated by the origin server for this occurrence
|
||||
of the problem.
|
||||
minimum: 100
|
||||
maximum: 600
|
||||
exclusiveMaximum: true
|
||||
example: 503
|
||||
detail:
|
||||
type: string
|
||||
description: |
|
||||
A human readable explanation specific to this occurrence of the
|
||||
problem.
|
||||
example: Connection to database timed out
|
||||
instance:
|
||||
type: string
|
||||
format: uri
|
||||
description: |
|
||||
An absolute URI that identifies the specific occurrence of the problem.
|
||||
It may or may not yield further information if dereferenced.
|
|
@ -1,144 +0,0 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# This Source Code Form is subject to the terms of the Mozilla Public
|
||||
# License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
import importlib
|
||||
import json
|
||||
import threading
|
||||
import time
|
||||
|
||||
import boto
|
||||
import boto.s3
|
||||
import boto.sqs
|
||||
|
||||
from cli_common import log
|
||||
|
||||
logger = log.get_logger(__name__)
|
||||
|
||||
|
||||
class StopListening(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class AWS(object):
|
||||
|
||||
def __init__(self, access_key_id, secret_access_key):
|
||||
self.access_key_id = access_key_id
|
||||
self.secret_access_key = secret_access_key
|
||||
self._connections = {}
|
||||
self._queues = {}
|
||||
self._listeners = []
|
||||
|
||||
def connect_to(self, service_name, region_name):
|
||||
key = service_name, region_name
|
||||
if key in self._connections:
|
||||
return self._connections[key]
|
||||
|
||||
# handle special cases
|
||||
try:
|
||||
fn = getattr(self, 'connect_to_' + service_name)
|
||||
except AttributeError:
|
||||
fn = self.connect_to_default
|
||||
conn = fn(service_name, region_name)
|
||||
self._connections[key] = conn
|
||||
return conn
|
||||
|
||||
def connect_to_default(self, service_name, region_name):
|
||||
# for the service, import 'boto.$service'
|
||||
service = importlib.import_module('boto.' + service_name)
|
||||
|
||||
for region in service.regions():
|
||||
if region.name == region_name:
|
||||
break
|
||||
else:
|
||||
raise RuntimeError('invalid region %r' % (region_name,))
|
||||
|
||||
connect_fn = getattr(boto, 'connect_' + service_name)
|
||||
return connect_fn(
|
||||
region=region,
|
||||
aws_access_key_id=self.access_key_id,
|
||||
aws_secret_access_key=self.secret_access_key,
|
||||
)
|
||||
|
||||
def connect_to_s3(self, service_name, region_name):
|
||||
# special case for S3, which boto does differently than
|
||||
# the other services
|
||||
return boto.s3.connect_to_region(
|
||||
region_name=region_name,
|
||||
aws_access_key_id=self.access_key_id,
|
||||
aws_secret_access_key=self.secret_access_key,
|
||||
)
|
||||
|
||||
def get_sqs_queue(self, region_name, queue_name):
|
||||
key = (region_name, queue_name)
|
||||
if key in self._queues:
|
||||
return self._queues[key]
|
||||
|
||||
sqs = self.connect_to('sqs', region_name)
|
||||
queue = sqs.get_queue(queue_name)
|
||||
if not queue:
|
||||
raise RuntimeError('no such queue %r in %s' %
|
||||
(queue_name, region_name))
|
||||
self._queues[key] = queue
|
||||
return queue
|
||||
|
||||
def sqs_write(self, region_name, queue_name, body):
|
||||
queue = self.get_sqs_queue(region_name, queue_name)
|
||||
m = boto.sqs.message.Message(body=json.dumps(body))
|
||||
queue.write(m)
|
||||
|
||||
def sqs_listen(self, region_name, queue_name, read_args=None):
|
||||
def decorate(func):
|
||||
self._listeners.append(
|
||||
(region_name, queue_name, read_args or {}, func))
|
||||
return func
|
||||
return decorate
|
||||
|
||||
def _listen_thd(self, region_name, queue_name, read_args, listener):
|
||||
logger.info(
|
||||
'Listening to SQS queue %r in region %s', queue_name, region_name)
|
||||
try:
|
||||
queue = self.get_sqs_queue(region_name, queue_name)
|
||||
except Exception:
|
||||
logger.exception(
|
||||
'While getting queue %r in region %s; listening cancelled',
|
||||
queue_name, region_name,
|
||||
)
|
||||
return
|
||||
|
||||
while True:
|
||||
msg = queue.read(wait_time_seconds=20, **read_args)
|
||||
if msg:
|
||||
try:
|
||||
listener(msg)
|
||||
except StopListening: # for tests
|
||||
break
|
||||
except Exception:
|
||||
logger.exception('while invoking %r', listener)
|
||||
# note that we do nothing with the message; it will
|
||||
# remain invisible for a while, then reappear and maybe
|
||||
# cause another exception
|
||||
continue
|
||||
msg.delete()
|
||||
|
||||
def _spawn_sqs_listeners(self, _testing=False):
|
||||
# launch a listening thread for each SQS queue
|
||||
threads = []
|
||||
for region_name, queue_name, read_args, listener in self._listeners:
|
||||
thd = threading.Thread(
|
||||
name='%s/%r -> %r' % (region_name, queue_name, listener),
|
||||
target=self._listen_thd,
|
||||
args=(region_name, queue_name, read_args, listener))
|
||||
# set the thread to daemon so that SIGINT will kill the process
|
||||
thd.daemon = True
|
||||
thd.start()
|
||||
threads.append(thd)
|
||||
|
||||
# sleep forever, or until we get a SIGINT, at which point the remaining
|
||||
# threads will be killed during process shutdown
|
||||
if not _testing: # pragma: no cover
|
||||
while True:
|
||||
time.sleep(2 ** 31)
|
||||
|
||||
return threads
|
|
@ -1,244 +0,0 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# This Source Code Form is subject to the terms of the Mozilla Public
|
||||
# License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
import asyncio
|
||||
import datetime
|
||||
import hashlib
|
||||
import json
|
||||
|
||||
import click
|
||||
import flask
|
||||
import pytz
|
||||
import sqlalchemy as sa
|
||||
|
||||
import cli_common.log
|
||||
import cli_common.pulse
|
||||
import tooltool_api.config
|
||||
import tooltool_api.models
|
||||
import tooltool_api.utils
|
||||
|
||||
logger = cli_common.log.get_logger(__name__)
|
||||
|
||||
|
||||
def replicate_file(session, file, regions_config, aws):
|
||||
logger2 = logger.bind(tooltool_sha512=file.sha512)
|
||||
|
||||
regions = set(regions_config)
|
||||
file_regions = set([i.region for i in file.instances])
|
||||
|
||||
# only use configured source regions; if a region is removed
|
||||
# from the configuration, we can't copy from it.
|
||||
source_regions = file_regions & regions
|
||||
if not source_regions:
|
||||
|
||||
# this should only happen when the only region containing a
|
||||
# file is removed from the configuration
|
||||
logger2.warning('no source regions for {}'.format(file.sha512))
|
||||
return
|
||||
|
||||
source_region = source_regions.pop()
|
||||
source_bucket = regions_config[source_region]
|
||||
target_regions = regions - file_regions
|
||||
logger2.info('replicating {} from {} to {}'.format(
|
||||
file.sha512, source_region, ', '.join(target_regions)))
|
||||
|
||||
key_name = tooltool_api.utils.keyname(file.sha512)
|
||||
for target_region in target_regions:
|
||||
target_bucket = regions_config[target_region]
|
||||
conn = aws.connect_to('s3', target_region)
|
||||
bucket = conn.get_bucket(target_bucket)
|
||||
|
||||
# commit the session before replicating, since the DB connection may
|
||||
# otherwise go away while we're distracted.
|
||||
session.commit()
|
||||
bucket.copy_key(
|
||||
new_key_name=key_name,
|
||||
src_key_name=key_name,
|
||||
src_bucket_name=source_bucket,
|
||||
storage_class='STANDARD',
|
||||
preserve_acl=False,
|
||||
)
|
||||
try:
|
||||
session.add(tooltool_api.models.FileInstance(file=file, region=target_region))
|
||||
session.commit()
|
||||
except sa.exc.IntegrityError:
|
||||
session.rollback()
|
||||
|
||||
|
||||
def verify_file_instance(sha512, size, key):
|
||||
'''Verify that the given S3 Key matches the given size and digest.
|
||||
'''
|
||||
|
||||
logger2 = logger.bind(tooltool_sha512=sha512)
|
||||
if key.size != size:
|
||||
logger2.warning('Uploaded file {} has unexpected size {}; expected {}'.format(sha512, key.size, size))
|
||||
return False
|
||||
|
||||
m = hashlib.sha512()
|
||||
for bytes in key:
|
||||
m.update(bytes)
|
||||
|
||||
if m.hexdigest() != sha512:
|
||||
logger2.warning('Digest of file {} does not match'.format(sha512))
|
||||
return False
|
||||
|
||||
# verify some settings on the key, in case the uploader configured
|
||||
# it differently
|
||||
if key.storage_class != 'STANDARD':
|
||||
logger2.warning('File {} was uploaded with incorrect storage class {}'.format(sha512, key.storage_class))
|
||||
return False
|
||||
|
||||
if key.get_redirect(): # pragma: no cover
|
||||
# (not covered because moto doesn't support redirects)
|
||||
logger2.warning('File {} was uploaded with a website redirect set'.format(sha512, key.storage_class))
|
||||
return False
|
||||
|
||||
# verifying the ACL is a bit tricky, so just set it correctly
|
||||
key.set_acl('private')
|
||||
|
||||
return True
|
||||
|
||||
|
||||
def check_pending_upload(session, pending_upload):
|
||||
# we can check the upload any time between the expiration of the URL
|
||||
# (after which the user can't make any more changes, but the upload
|
||||
# may yet be incomplete) and 1 day afterward (ample time for the upload
|
||||
# to complete)
|
||||
sha512 = pending_upload.file.sha512
|
||||
size = pending_upload.file.size
|
||||
|
||||
logger2 = logger.bind(tooltool_sha512=sha512)
|
||||
|
||||
if tooltool_api.utils.now() < pending_upload.expires.replace(tzinfo=pytz.UTC):
|
||||
# URL is not expired yet
|
||||
return
|
||||
elif tooltool_api.utils.now() > (pending_upload.expires + datetime.timedelta(days=1)).replace(tzinfo=pytz.UTC):
|
||||
# Upload will probably never complete
|
||||
logger2.info('Deleting abandoned pending upload for {}'.format(sha512))
|
||||
session.delete(pending_upload)
|
||||
return
|
||||
|
||||
# connect and see if the file exists..
|
||||
s3 = flask.current_app.aws.connect_to('s3', pending_upload.region)
|
||||
s3_regions = flask.current_app.config.get('S3_REGIONS')
|
||||
if not s3_regions or pending_upload.region not in s3_regions:
|
||||
logger2.warning('Pending upload for {} was to an un-configured region'.format(sha512))
|
||||
session.delete(pending_upload)
|
||||
return
|
||||
|
||||
bucket = s3.get_bucket(s3_regions[pending_upload.region], validate=False)
|
||||
key = bucket.get_key(tooltool_api.utils.keyname(sha512))
|
||||
if not key:
|
||||
# not uploaded yet
|
||||
return
|
||||
|
||||
# commit the session before verifying the file instance, since the
|
||||
# DB connection may otherwise go away while we're distracted.
|
||||
session.commit()
|
||||
|
||||
if not verify_file_instance(sha512, size, key):
|
||||
logger2.warning('Upload of {} was invalid; deleting key'.format(sha512))
|
||||
key.delete()
|
||||
session.delete(pending_upload)
|
||||
session.commit()
|
||||
return
|
||||
|
||||
logger2.info('Upload of {} considered valid'.format(sha512))
|
||||
|
||||
# add a file instance, but it's OK if it already exists
|
||||
try:
|
||||
tooltool_api.models.FileInstance(
|
||||
file=pending_upload.file,
|
||||
region=pending_upload.region,
|
||||
)
|
||||
session.commit()
|
||||
except sa.exc.IntegrityError:
|
||||
session.rollback()
|
||||
|
||||
# and delete the pending upload
|
||||
session.delete(pending_upload)
|
||||
session.commit()
|
||||
|
||||
# note that we don't try to copy the file out just yet; that can wait for
|
||||
# the next scheduled distribution, and in the interim everyone will hit
|
||||
# this one instance.
|
||||
|
||||
|
||||
async def check_file_pending_uploads(channel, body, envelope, properties):
|
||||
'''Check for pending uploads for a single file.
|
||||
'''
|
||||
body = json.loads(body.decode('utf-8'))
|
||||
digest = body['payload']['digest']
|
||||
session = flask.current_app.db.session
|
||||
file = tooltool_api.models.File.query.filter(
|
||||
tooltool_api.models.File.sha512 == digest).first()
|
||||
if file:
|
||||
for pending_upload in file.pending_uploads:
|
||||
check_pending_upload(session, pending_upload)
|
||||
session.commit()
|
||||
await channel.basic_client_ack(delivery_tag=envelope.delivery_tag)
|
||||
|
||||
|
||||
@click.command()
|
||||
@flask.cli.with_appcontext
|
||||
def cmd_check_pending_uploads():
|
||||
'''Check for any pending uploads and verify them if found.
|
||||
'''
|
||||
session = flask.current_app.db.session
|
||||
pending_uploads = tooltool_api.models.PendingUpload.query.all()
|
||||
for pending_upload in pending_uploads:
|
||||
check_pending_upload(session, pending_upload)
|
||||
session.commit()
|
||||
|
||||
|
||||
@click.command()
|
||||
@flask.cli.with_appcontext
|
||||
def cmd_replicate():
|
||||
'''Replicate objects between regions as necessary.
|
||||
'''
|
||||
# fetch all files with at least one instance, but not a full complement
|
||||
# of instances
|
||||
regions = flask.current_app.config['S3_REGIONS']
|
||||
session = flask.current_app.db.session
|
||||
subq = session.query(
|
||||
tooltool_api.models.FileInstance.file_id,
|
||||
sa.func.count('*').label('instance_count'),
|
||||
)
|
||||
subq = subq.group_by(tooltool_api.models.FileInstance.file_id)
|
||||
subq = subq.subquery()
|
||||
|
||||
q = session.query(tooltool_api.models.File)
|
||||
q = q.join(subq, tooltool_api.models.File.id == subq.c.file_id)
|
||||
q = q.filter(subq.c.instance_count < len(regions))
|
||||
q = q.all()
|
||||
|
||||
for file in q:
|
||||
replicate_file(session, file, regions, flask.current_app.aws)
|
||||
session.commit()
|
||||
|
||||
|
||||
@click.command()
|
||||
@flask.cli.with_appcontext
|
||||
def cmd_worker():
|
||||
'''Check for pending uploads for a single file.
|
||||
'''
|
||||
pulse_user = flask.current_app.config['PULSE_USER']
|
||||
pulse_pass = flask.current_app.config['PULSE_PASSWORD']
|
||||
exchange = f'exchange/{pulse_user}/{tooltool_api.config.PROJECT_NAME}'
|
||||
check_file_pending_uploads_consumer = cli_common.pulse.create_consumer(
|
||||
pulse_user,
|
||||
pulse_pass,
|
||||
exchange,
|
||||
tooltool_api.config.PULSE_ROUTE_CHECK_FILE_PENDING_UPLOADS,
|
||||
check_file_pending_uploads,
|
||||
)
|
||||
logger.info(
|
||||
'Listening for new messages on',
|
||||
exchange=exchange,
|
||||
route=tooltool_api.config.PULSE_ROUTE_CHECK_FILE_PENDING_UPLOADS,
|
||||
)
|
||||
cli_common.pulse.run_consumer(asyncio.gather(*[
|
||||
check_file_pending_uploads_consumer,
|
||||
]))
|
|
@ -1,10 +0,0 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# This Source Code Form is subject to the terms of the Mozilla Public
|
||||
# License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
PROJECT_NAME = 'tooltool/api'
|
||||
APP_NAME = 'tooltool_api'
|
||||
PULSE_ROUTE_CHECK_FILE_PENDING_UPLOADS = 'check_file_pending_uploads'
|
||||
SCOPE_PREFIX = f'project:releng:services/{PROJECT_NAME}'
|
||||
SCOPE_MANAGE = f'{SCOPE_PREFIX}/manage'
|
|
@ -1,8 +0,0 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# This Source Code Form is subject to the terms of the Mozilla Public
|
||||
# License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
import tooltool_api
|
||||
|
||||
app = tooltool_api.create_app()
|
|
@ -1,171 +0,0 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# This Source Code Form is subject to the terms of the Mozilla Public
|
||||
# License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
import sqlalchemy as sa
|
||||
|
||||
from backend_common.db import db
|
||||
|
||||
ALLOWED_REGIONS = ('us-east-1', 'us-west-1', 'us-west-2')
|
||||
|
||||
|
||||
class File(db.Model):
|
||||
'''An file, identified by size and digest. The server may have zero
|
||||
or many copies of a file.
|
||||
'''
|
||||
|
||||
__tablename__ = 'releng_tooltool_files'
|
||||
|
||||
id = sa.Column(
|
||||
sa.Integer,
|
||||
primary_key=True,
|
||||
)
|
||||
size = sa.Column(
|
||||
sa.Integer,
|
||||
nullable=False,
|
||||
)
|
||||
sha512 = sa.Column(
|
||||
sa.String(128),
|
||||
unique=True,
|
||||
nullable=False,
|
||||
)
|
||||
visibility = sa.Column(
|
||||
sa.Enum('public', 'internal', name='visibility'),
|
||||
nullable=False,
|
||||
)
|
||||
|
||||
instances = sa.orm.relationship('FileInstance', backref='file')
|
||||
|
||||
# note that changes to this dictionary will not be reflected to the DB;
|
||||
# add or delete BatchFile instances directly instead.
|
||||
@property
|
||||
def batches(self):
|
||||
return {bf.filename: bf.batch for bf in self._batches}
|
||||
|
||||
def to_dict(self, include_instances=False):
|
||||
file = dict(
|
||||
size=self.size,
|
||||
digest=self.sha512,
|
||||
algorithm='sha512',
|
||||
visibility=self.visibility,
|
||||
has_instances=any(self.instances)
|
||||
)
|
||||
if include_instances:
|
||||
file['instances'] = [i.region for i in self.instances]
|
||||
return file
|
||||
|
||||
|
||||
class Batch(db.Model):
|
||||
'''Upload batches, with batch metadata, linked to the uploaded files.
|
||||
'''
|
||||
|
||||
__tablename__ = 'releng_tooltool_batches'
|
||||
|
||||
id = sa.Column(
|
||||
sa.Integer,
|
||||
primary_key=True,
|
||||
)
|
||||
uploaded = sa.Column(
|
||||
sa.DateTime,
|
||||
index=True,
|
||||
nullable=False,
|
||||
)
|
||||
author = sa.Column(
|
||||
sa.Text,
|
||||
nullable=False,
|
||||
)
|
||||
message = sa.Column(
|
||||
sa.Text,
|
||||
nullable=False,
|
||||
)
|
||||
|
||||
# note that changes to this dictionary will not be reflected to the DB;
|
||||
# add or delete BatchFile instances directly instead.
|
||||
@property
|
||||
def files(self):
|
||||
return {
|
||||
batch_file.filename: batch_file.file
|
||||
for batch_file in self._files
|
||||
}
|
||||
|
||||
def to_dict(self):
|
||||
return dict(
|
||||
id=self.id,
|
||||
uploaded=self.uploaded,
|
||||
author=self.author,
|
||||
message=self.message,
|
||||
files={
|
||||
filename: file.to_dict()
|
||||
for filename, file in self.files.items()
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
class FileInstance(db.Model):
|
||||
'''A verified instance of a file in a single region.
|
||||
'''
|
||||
|
||||
__tablename__ = 'releng_tooltool_file_instances'
|
||||
|
||||
file_id = sa.Column(
|
||||
sa.Integer,
|
||||
sa.ForeignKey('releng_tooltool_files.id'),
|
||||
primary_key=True,
|
||||
)
|
||||
region = sa.Column(
|
||||
sa.Enum(*ALLOWED_REGIONS, name='region'),
|
||||
primary_key=True,
|
||||
)
|
||||
|
||||
|
||||
class BatchFile(db.Model):
|
||||
'''An association of upload batches to files, with filenames
|
||||
'''
|
||||
|
||||
__tablename__ = 'releng_tooltool_batch_files'
|
||||
|
||||
file_id = sa.Column(
|
||||
sa.Integer,
|
||||
sa.ForeignKey('releng_tooltool_files.id'),
|
||||
primary_key=True,
|
||||
)
|
||||
file = sa.orm.relationship('File', backref='_batches')
|
||||
batch_id = sa.Column(
|
||||
sa.Integer,
|
||||
sa.ForeignKey('releng_tooltool_batches.id'),
|
||||
primary_key=True,
|
||||
)
|
||||
batch = sa.orm.relationship('Batch', backref='_files')
|
||||
filename = sa.Column(
|
||||
sa.Text,
|
||||
nullable=False,
|
||||
)
|
||||
|
||||
|
||||
class PendingUpload(db.Model):
|
||||
'''Files for which upload URLs have been generated, but which haven't yet
|
||||
been uploaded. This table is used to poll for completed uploads, and to
|
||||
prevent trusting files for which there is an outstanding signed upload
|
||||
URL.
|
||||
'''
|
||||
|
||||
__tablename__ = 'releng_tooltool_pending_upload'
|
||||
|
||||
file_id = sa.Column(
|
||||
sa.Integer,
|
||||
sa.ForeignKey('releng_tooltool_files.id'),
|
||||
nullable=False,
|
||||
primary_key=True,
|
||||
)
|
||||
expires = sa.Column(
|
||||
sa.DateTime,
|
||||
index=True,
|
||||
nullable=False,
|
||||
)
|
||||
region = sa.Column(
|
||||
sa.Enum(*ALLOWED_REGIONS, name='region'),
|
||||
nullable=False,
|
||||
)
|
||||
|
||||
file = sa.orm.relationship('File', backref='pending_uploads')
|
|
@ -1 +0,0 @@
|
|||
../../client/tooltool.py
|
|
@ -1,22 +0,0 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# This Source Code Form is subject to the terms of the Mozilla Public
|
||||
# License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
import datetime
|
||||
import re
|
||||
import typing
|
||||
|
||||
import pytz
|
||||
|
||||
|
||||
def now() -> datetime.datetime:
|
||||
return datetime.datetime.utcnow().replace(tzinfo=pytz.UTC)
|
||||
|
||||
|
||||
def keyname(digest: str) -> str:
|
||||
return 'sha512/{}'.format(digest)
|
||||
|
||||
|
||||
def is_valid_sha512(sha512: str) -> typing.Optional[typing.Match[str]]:
|
||||
return re.compile(r'^[0-9a-f]{128}$').match(sha512)
|
|
@ -1,3 +0,0 @@
|
|||
This Source Code Form is subject to the terms of the Mozilla Public
|
||||
License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||
You can obtain one at http://mozilla.org/MPL/2.0/.
|
|
@ -1,11 +0,0 @@
|
|||
# NOTE: these files must *also* be specified in setup.py
|
||||
include LICENSE.txt
|
||||
include Makefile
|
||||
include README.md
|
||||
include VERSION
|
||||
include requirements-dev.txt
|
||||
include requirements.txt
|
||||
include test.sh
|
||||
include test_file.ogg
|
||||
include test_tooltool.py
|
||||
include tooltool.py
|
|
@ -1,17 +0,0 @@
|
|||
check: python-tests shell-tests tox
|
||||
|
||||
shell-tests:
|
||||
sh test.sh
|
||||
|
||||
python-tests:
|
||||
clear
|
||||
python test_tooltool.py
|
||||
|
||||
python-test-%:
|
||||
clear
|
||||
python test_tooltool.py $*
|
||||
|
||||
tox:
|
||||
tox
|
||||
|
||||
.PHONY: check clean shell-tests python-tests python-tests-% tox
|
|
@ -1,41 +0,0 @@
|
|||
# Tooltool
|
||||
|
||||
This is tooltool. Tooltool is a program that helps make downloading large
|
||||
binaries easier in a CI environment. The program creates a json based manifest
|
||||
that is small compared to the binaries. That manifest is transmitted to the
|
||||
machine that needs the binary somehow (checked in, included in tarball, etc)
|
||||
where the machine will run tooltool to download.
|
||||
|
||||
When using the fetch mode, the program will check to see if the file exists
|
||||
locally. If this file does not exist locally the program will try to fetch
|
||||
from one of the base URLs provided. The API that tooltool uses to fetch files
|
||||
is exceedingly simple. the API is that each file request will look for an http
|
||||
resource that is a combination of an arbitrary base url, a directory that is
|
||||
named as the hashing algorithm used and the hashing results of each file stored.
|
||||
|
||||
Example, using base url of "http://localhost:8080/tooltool", algorithm of "sha512"
|
||||
and a file that hashes to "abcedf0123456789", tooltool would look for the file
|
||||
at "http://localhost:8080/tooltool/sha512/abcdef0123456789". If there is a local
|
||||
file that has the filename specified in the manifest already, tooltool will not
|
||||
overwrite by default. In this case, tooltool will exit with a non-0 exit value.
|
||||
If overwrite mode is enabled, tooltool will overwrite the local file with the
|
||||
file specified in the manifest.
|
||||
|
||||
## Server
|
||||
|
||||
This repository contains only the tooltool client -- `tooltool.py`.
|
||||
The tooltool server component is a part of [RelengAPI](https://github.com/mozilla/build-relengapi).
|
||||
|
||||
If you want to use the client, just copy out `tooltool.py` -- it has no
|
||||
dependencies.
|
||||
|
||||
## Development
|
||||
|
||||
To hack on the tooltool client, install into a virtualenv with
|
||||
|
||||
pip install -e .[test]
|
||||
|
||||
Send pull requests through GitHub.
|
||||
|
||||
Both the client and the server components are covered by Travis, via the
|
||||
`validate.sh` script which you can run yourself.
|
|
@ -1 +0,0 @@
|
|||
1.3.0
|
|
@ -1,23 +0,0 @@
|
|||
[report]
|
||||
# Regexes for lines to exclude from consideration
|
||||
exclude_lines =
|
||||
# Have to re-enable the standard pragma
|
||||
pragma: no cover
|
||||
|
||||
# Don't complain about missing debug-only code:
|
||||
def __repr__
|
||||
if self\.debug
|
||||
|
||||
# Don't complain if tests don't hit defensive assertion code:
|
||||
raise AssertionError
|
||||
raise NotImplementedError
|
||||
|
||||
# Don't complain if non-runnable code isn't run:
|
||||
if 0:
|
||||
if __name__ == .__main__.:
|
||||
|
||||
# 'pass' generally means 'this won't be called'
|
||||
^ *pass *$
|
||||
|
||||
omit =
|
||||
*/test_*
|
Некоторые файлы не были показаны из-за слишком большого количества измененных файлов Показать больше
Загрузка…
Ссылка в новой задаче