From e27d42935cb12c941a8d47b2b623d751e91f8331 Mon Sep 17 00:00:00 2001 From: Jakob Borg Date: Tue, 29 Jul 2014 11:06:52 +0200 Subject: [PATCH] Use event interface for GUI (fixes #383) --- auto/gui.files.go | 6 +- cmd/syncthing/gui.go | 67 +++++--- cmd/transifexdl/main.go | 13 +- files/leveldb.go | 7 +- files/set.go | 2 +- gui/app.js | 357 ++++++++++++++++++++++++++++------------ gui/index.html | 7 +- gui/lang-pt.json | 2 +- model/model.go | 71 ++++---- 9 files changed, 361 insertions(+), 171 deletions(-) diff --git a/auto/gui.files.go b/auto/gui.files.go index b8a5f0d7c..dc9ba2f9e 100644 --- a/auto/gui.files.go +++ b/auto/gui.files.go @@ -28,7 +28,7 @@ func init() { bs, _ = ioutil.ReadAll(gr) Assets["angular.min.js"] = bs - bs, _ = hex.DecodeString("1f8b080000096e8800ffec7cff73dbb692f8effe2bf0f4c97ba21c9972927e3aefa238bdd44ddff9354d3271d2bb19d79da14448624d912a01dad624fedf6f1700498000282a4e73ef664ed33a12b0582c168bc57e01309990d37cb32d92e58a93e074441e1f3ffa86fc33baca67e4fbbc5892288b0122e345322b795e3012304a095f5172fae6f5fb7767df7f78ffe6dd395924291d850780ee459a12818e9182325a5cd338241f1825f9029a258cb0bc2ce694ccf39812f8b9ccaf6991d198ccb6d019f9f9ecfd11e3db9422ae3499d38c6177112773a89d51b2c84b2029c9040dafce4e5fbe3e7f29ba0f0f0e2687bfb334c9389915f90df4fd94f0a2a463e82be34956d2eaf7262d19fe2f7f93c309b45ca6f92c4ac983a76411a50c80a26c59a651a17e23d0c1b0842f0c7831e7c3e9c1c1755410b6cde630ac6c494eaa16e13a8fcb9406c3ba6e382617c34dc4e651ba29e87cc5435e44194b234e8797a3a9405416e92c02f42764087c43fc587a1da549fc0a1033a8b818c474301ed00cff30f8b328e0cf86c31f763db8048aea1e4318f22259068b120a923c23c18315e79bb7457e9dc4b4189307350555d9887c3c20f03100c3982ea232e52cbc65c5e23f680465afa3b520f3bf8e4ecfdffd78f43ebfa21990bba3ed699e5f25b46a6bb4944d2d8242e0f6398f7832ff11a697bdcab1f34012891f60e522b97d4a8629f0e76838ae2b58b99015e1ef2ccf86a2fc0ed88cff9b2ce2459ea68074785e959ef22285e9d2f8c6e6f906c4410c4de71b7c4ff379845015eb70c680aaeb1fa01a46793cad4b9794bff9098a50de9a529ce8a8e0527a849855cc109d2285191574e0f47fbc9bb62a6186edf2f5f6ec0764f1d028cd60b90919ba742039cb70fc0679aa7e53e43c9fe7e9e90a780c8bb4265383a145818ac1c20d8a227b89753635b042686a935ed04dee182916171c981af9eade02d3137ae324afdc2c0b909cb36c91abe68da886302f815a790f071314a4c12864e57c4e19d3160f56b06a96abd9c3c2695db2807106589c8899877f9e090816a6345bf215943c7ca8a3c00f0200b880bb482ea74665b24084d5fa0f932ca6b76f16829611790e9db4b199cb08978f049e5a50b382465766f1dd81f9ed6e6488e2833c0b06b5c04b7138977c1ae8cbc5c5241861dd12a4222ae6ab60149aecc3c1ca919963ea1ecf5dbdb60d59a7347ed12c9b1a7a58ac87a0157ea0a9a62ca0304e0a554e02f83ed26b515d60252e10bd9ce7e57c85151f3631aa715975d726e36cee21a2a06bd8f89c74d8551511717e93a5a0069d84448cd32261573529e29f7a6640d2c57cd198c6c62c21e7ff22f493c57b398e244b78d012a207c1f0ff6594dfe4c59558e2c311aee9280d862b50ddc316b4adfd1a66552434ba704f329a86dd44202c5b951c99d80de952cb26d10ee6fe18c1266573d63fac82f2b2c87cec704f888feb1f67d1fc2a2ef20dc801135b26ec6157743bcba3225616cc9d6752ba06582bd9058c6385c0ce856ee952f2900c276c0b12b91ebaf4292c99a83d385340a72e119018810c6caf913c927b50e0a64e21af26486fd77c7f33fb1d76da1078c6027d371a85a0d85f46a0b11ae449dc468f33c6b71b0a36aebec35d24f125393981ed0f6c563056c0cc1dba943698baaf2998c16441f97c85b630855ea9addc9d7c165d7d87d49e0ca18066685a7f7877769aaf377946338e04f79d8516c7b561b498ee62a2f84d289acb8e9dc94ffc04c6ce80a42f3d089c16ac0f157ef2dc1a5755e543f195d9be37fb7dd35097ef9e30e77a708f5933487baf6bb402b21c6d33348bc110bd0946630382a35d1920cc516d3e8fc8843c3a3e3e36219358edf4d547b3b6a1bd3930619125c257f4b15cec7d424256117b739381ebb1a105df8aa9f34c51e54c3a968255c28bad070b762ae42fc9661bb4107e8ef82a5c47b7c1f198fc9d1c4a5e0a88b3ecfb2da7ec7dcec13f3d72f8061614328fc71e89a8f1e625dfd5f59b92f7e9db00f3767e072e3ca83712501f6b2da61cf71d8203f0ce23e5f871fa57ed4d65d762907e4fef75d0f696dcdddd4d0fdaee0dfac02533365f2c6e5b1aaefd07e176ee40d2104188ab0c6d23dd16307ab051c344090f85fc05bbf062663cdf6ca06727e60a0895a8a3073467a89737a769c4be026b12701eff04bec4e845156ec422b8c4a56aebc19b8a16d504da24714afd33224576e8333f1b34e87b08dbda83695324eba8d83a3139d8d79a43c4fe961673d82ea325fdd327127614df881dec952141a1d9047ecbe1766335a67033e7302a8000bdea9413742c651713178046c3b4cd57a1b717690e962f74e3562018f74127d4602d169e2e966d5f1dd521faeab692570dc2d788ed874b53e410d06510637988d6504a45b7c84260844bf95782925f0d7b5a93550be59ab49b75299a35ecdecc2d8e629c964af9976597bd8a7bf1cc5eb23b79e65fc138d617715c7c658e29c204d3b07b60439706faae63be3596ff8f0d4123024c8bbf766a53ff507ea13ba7018931291582a6c58e3d642a08e52eb9e8fb33d99326b043fce2ef7bc73cc3a61063c71677cce1e208d668a38a50b91e39070c29a77a8401e9adbacd1a561289756ad88f384485574583c130208f3c23ae77b18e812a6c17c7975e6150a9969dd2a0f65455db73231d0c7cfb683dcfd0bda7b10ed231465362c0ca9e315ea08ff2ad7babc3ece26bd7c0db539c494968350b5a13a6f1a52f530265428b46a34e16ede24f1fe6f4e60c8d137e4e3946219997339309181457944404e38b18fc84b6dbc60752dc5a6fde6c2a8fa94a7822606064944205a43b516d04e187772fb368968ab452e0aa7e01dbdb8643fd73b0bd5c98fef1e1ac9b0c00d01b620858f1a16fec1463d61ee78c45d7b07ba82c5cc5544bdae6b8a8c83fcfdfbc0e31759c2d93458b4a8d426c906fb84819ac44b6953d251f879881070578f41e6452c4fa379b3491099589c871deddb5bdd54dceacd80df405630382c6a21397e76a79adbe3810e09a88bcc43e212e77d6510460f4b29661e208d93a66a2b780b3b2803ff99acabcfd5ca636cd39abd39d7fa9848bfe514629734bf9d816ee11f9f4c91abefc74a304891d9b12ded24b8ab6367bf124c78aceaf10a664e84cc9d4280e71153132a3342354adb7bc2071c2c477cb0eed5aa77ffb9b7319d7ebf4992f2bd9d54898b9c74e1b56c4e7ee43102a8ebdc9397ad4b68cbd9caef2e3e426495382193fc2733ca652491008a14a01b5186d4f7bf881d1f7afce45ccc0100055e11f889da537336dee41bc0025b2059a6f48a513096669d3747be0e8c394788fd6d5e47fda81c4afb62d89f75210be4a18e84465fb1b36a70d75ce8b9081cae4c1708c8a3fda68caea56b3e46e4350d16b30059aa4728b8a46e91ba9a483ee4d668079c6812fc627e4c3abb78c74a439b1260680789ff0146d9bc1bbba68e087fe3e8fb7085c9f7ec103594d75383006e548b4ee9b77f46c4d0af3d0dee39de753748d7e7e93604cf986ce36a8f3eaf5086b3313a9444d7706eee532b2f315351a30fb90e4614b163a96afbd4adbc82c7fddc40db2f33e59d3bce4416353d84bff065c9afc26accf5568bd545f5bdd8c859ef58875e7411f49a5f86b8aae3a5be34f0c7be5f38368f919e259560dbf9a74aa413a0d1ddb82f7aed5fe43d49024196c2778d211d5b4f2bdb581f7c87cf73d22e1b3acd48989cfd24e6e7e6a87307ad89fae531bfb4eec5d4fc5e2183fba4ecec841cb93aed096450186ba6af120a4b7b0fbc4c147587c55238b14ec02d8f7f216b6aa0e25afc0ce69ba90e9d25610c788e1d8e36d08abe26594c1b6484e6a3fbb2e0e7fcf930c764ae250c808fc1228c98bf00168aab78520da709e71ca2ab67d012f2bb1233706e3a1b724fe433b81c456f9cdd08d2b8a772073cde2479d5f780a6b9b456b413f46edb006f0a821f498dc965e75ceae1be6ebf33ea629e57427ffad1e2deda21bf22d96ec3e17e5e243bf009d33ba522d19cdced65787aceda13c04649b0e6d776d4e848a3308c6a922776e58d489c4760b75aba2c760ef31e0f6a0ed09b06d60873fde29355518ee146311719ed13149a6077b0a551db2748cca826ad49b4be5ddd741c021d8eb569e4069ce031b02db712c5873c305ec4572a907eb4dd5dfe16f578d9b114f2d5045b96dabe067c73961737923268f60cb116f4ab60aec6dd0b3c04396173ca8323451413f771df615da1cecaca2c2e53376f45c8857f1f4d63bb857ef34c1aa0075e7624ac0c068eb9ecf90bcacc546e7a1f4ac67e6481fbc5f881c238ed2b47b1e2af5a1cd7a337b414b4528c1b3c2fd1a981654676e3310ad6c8c25f4140d79cac7960d5fc89f86e8f335e713ebbb133ba5639ed2a878591d2aea346bf40b19069d1726d52a4575441e5d0ab2765af4a2dd4450e231bc1645027670bab5a795f14227d5758da2aff036d1f64e1166c2ea85bfb8ada6d19c06f35a998edb1934ac1b59370eb499033c9ea8ce26ef2330159c79ead8eb8bbc83eafd7c11d5c288b6797d11ad0d884b4ae79cc6d59c7db4ad5b1d5c5926d66969cb34d9d5d545a55f2eed2b032d93d2814a2589d1d66e82c36e8010d32942890d5882f9f7411f520524de48d37aeaf0f5edb63f51ba81160fbb697b1b15d19a855700ec92be5dd8bbeb3f7d22ffff73fd5014d09e5e0876fb05bc10f0da2cb1df29ef1f0d997a0ad2fbf9ced9d71f321a2b9d6396d70737f90e03da20c732a01502a7b85850b599757169d7990b58b309ec355c6b787952025d2327169751ecee2eabb4c589eccb6d8768839006c147a9659e2a423acec53766bef087ddf44e4d73b88671e80bdb0890b08646b1c730acd2bf12e7706c4348ad0130766b518fea0431e081529342d40b36c2d6216a93436d6e3846daa3c14fa686731d5e6bb573a1b79d08e933576da424aa5f3e4702e59ee1324f84a0fbb6e6fddde115b82facb59e2b09b1ec5ab03a7ccbccbbc7e27993b6298f780cd3b7b6692aa34c9d8a0ab4c324a391276e2ad1099fcc61388b4a11361c8c8927cf2567b15b93f7525b5f2698a484ca9016c7ae699c55fb4ae242f98bb7673fd1adc1a7b9292a68b3d6504594c5f9fa5c1c2f099e1c8fc993c79e606a9670df6115f7510f9574d8f79a4375e3ca75cf61478f7bde1bac98e6be1ba841a88bf1e2b489ed78ef204a9d9ed993a8fa8ebe97285fba7a47425b05ae8c48bd37f062e17addf8b92e7857e4c5055c5dd7c77f7f8e36817f75781188c3db41bb5a37edcdd1ebe7343c674d261372d6710426a309460a4854e1c9f0300c9da778b2cfde0001db0d253751c6f16047c4ae4436ae6480017eafe581b9f92a4fe63424df971ca1c11c1b72d1c6850ecf87944b44b3267159c82c1f28b128c5d46fb91913962316f881a8c5b319e406887622036238460ec4eb2a942c92025cceeb04181f92ff5cd14cbd9d22b1240c2fcc31ea260c9f7aa9f101e83a2fc4d32b19beb9529015fc61245ae663a44e71c285e78f923291923eb06a717f1324fe8214e2bac8e7e51ae8092585b5473e09be7b0afffdf6293c9cfeca0e474d23f8f5eb09fc092e7e9b5e1e8ec2c307a34fbfc1df09ec3b0f1e0d1cf7cec476d120f05e3e3349c1ac6cd3e864005aa0b9bf083f06d375747b044226aa9e1c1f3efee6f0c9b7ed734cf8f19ec7af887ba8b1e499decb1191380f45e2de8d003fb86d96f7bb0d5e13bbcb04dc5f69ee774cd09d9fed3822d8830ea906f625c1787664ef5dacebd44047a7e66b257bdf737762d103373e175b68c30fef76fad87e752c4ff1a1027815154bd48fa83a50a1a4f81bd412adfc6cc9d9ea3cc36efb48549a326e188596a129b4f9fd46a31f02fc52846166fa3595a76bbcf7dcaa9d589c5e128f1e394ee3c8972010e273820d3ea91d4c10a7bcd33e70df6917d4ee29d392d02e2ba8355877d0af7dc7a17ee5c560a77889ace5522d7e90b6fe09f9e6f8dfbe9db6ea92029cf8bc403bfad1b74ffefe4dcb911718c31fd368c9c8df00bfc4f5b069371a89c883a3c2736552bef462f9bc76670aa581df8fb41fc68638897417b1f27d991d0e7a7d210d357d3b5cd94eb3c1b22f92b8fbb2a733d13050af4554183e4f50bddabeb64577085e34cb4b7f581f57a680e87520457fd4060cbeb38cd3e21ada9844c9937bc7d5136675c79a891e446332abe8d0aeaf44e22289bc87dc5c60c1d0b802983901da9784149a670a7ce49a7d5d63aa22d5ecb96aa65870a06195314081d7ca66b7f0ca760d66d9f479dd14b8a3f146844c3cbc89c266d163d7ce45d0dd7b83e0b98ec0a6011da3b4c22b2e98693b72ea88e518e9b0f5455185ae14194a58aa7b6076872204b1d67b4cb56852eb50ca5aef2c95d1a2351e3419e9a34ea547a8b1d4a42235a980fd3759e3cd0690e431c94a83983859269c8d11a80a99aa87ceec3bd60afd71458bf847222027faf567f135cd97f24b34135d8ff0258abae6d17115d9c29e89f9ea05d008c6b6c46c0e4d9269f0588fb5a43403d9c2560792ec99a44dfcf3e91379a2761b71afa7e4d2b11f0cc6789ef3bcca1cdeac40459340d657d9d76704bec875d63044b6aa88979404a390e78a1c4149c8f0ddcce0b1a6c954d70f4f1406050223975c48b24055c8dec70447062c3168aa6383c65a50203c7f95dfd0e21454352ab43bfda54595061f6611b488ccf715abf956d8b45796b24dc9c7f2054c8750886ae8f5c7e496c6412d7246ab46e7b69e7eac089a25195e84ee4f4f5b2f8a4221b8b5eef46da4c7c4fbda82c4f21cb4fce36fc8a1fe8f95041190931307e8d4d56b37971e8fd0931d927f24fb90d687a67b10f3733f623aa9b847f73fb9bbd79f5c28f009da4a1eb0cd70979cad293e1afb2f2467e23d0aed9f0e6eb641ef2367fb50d687a4fb88592f5a3a89b847ef577f8290819959f0cf94318378e77d624fa7517a136dd9eb723da3c557916fe7032e3af9bdd8f41658fc85c97511ab5f9317f78aa24298078acfe2c0e9e4e2d7c9afbf5e4eda092e090b3684f8529b0527e4898733dae89ddc19846138416f49229426808efce8f148e5f02683dd5b276cf498f6fbdfc945efe30bbb9828d05c185c7b74b98b5778322ebbdfca743d2f28409b90f95f27cb313e36e5a326164e4a724d83612e225ef2484b175d3a19f2ddf3a76478aa1d3c509dab87d5eb62f168ef3c2d63ab46f8b5ede309eaba24e03e515fb52eee34b474bdc1a78001f099141e74624f069cdef201c99647223e703230e37a170a67086ecde0f9b38968f9bc7a26b78b4d6596fc518a8cb5c6a42e1efd514263a02e5bfe8c9468a34893ecea698343b0614c68ba1e9388f3021ca2392fd2b6506259f800a48dd1828565c656c942bb16886f5eff12a5ee132fe219af3e39f2ea23535da01ecb34c60496b0a181db4e60491970f617844af8b6c52d710e676a35d502537adebd1ec8650771184a4670194766244a0b1ac5dbcf224f8453fdf4eda6016f220a845f943b568992b19a3f669b3beb606aa7388b09c5584912ebf22c626dff27d4ee693379d62dd56e0adce9a14ca0fc2e894f860f9b113bc295a07837bec1551c4118991aea82ec3542dfc2e835d4eae3bb6324ce0a00a949ecefa017959e79a88974d6b8529e7ff28a1301e0bd37d897437bcbfb50a45023f0852bbe4ebff81eccf1663674f1efad63854cbc3eeaa848e678ebd12a9ea73973e11149c056f95df5543d72f0bf010000ffff010000ffff80e671608b660000") + bs, _ = hex.DecodeString("1f8b080000096e8800ffec7dff73dbb692f8effe2b107df22a29912927e9ebbc4f1ca7d7bac93bbfa649268e7b37e3fadd502264b1a648955fec6812ffefb7bb00497ca52827cddd9b394d224bc462b1582c16bb8b05349db2e36cbdc9e3cb65c946c763f6f8e0d1b7ec1fe15536633f66f9250bd30820d2328f675599e5051b159cb372c9d9f19bd7efdf9dfc78f6fecdbb53b688133e0ef600dd0f49c2085dc1725ef0fc9a47013b2b38cb16502d2e589155f99cb3791671065f2fb36b9ea73c62b30d34c67e3979bf5f949b8423ae249ef3b4c0e6c292cda174c6d922ab80a438251a5e9d1cbf787dfa829a0ff6f6a60f7e2f92382dd92ccf6ea0eda7accc2b3e81b6d2324e2b5e7f5f275581ffc577f6600a352f936c1626ecfe53b608930280c2f4b24ac25c7e47a0bd61051f0ae0c5bc1c1eeeed5d87392b36e91cba955eb2a3ba46b0caa22ae1a36153369cb0f3e13a2ce661b2cef97c5906651ea64512967c78313e2444559ecc42407fc486c037c48f4fafc3248e5e01e2020ace07111f4c063cc5b702de1639bcad4b782bae07174051d362005d5ec497a345050fe22c65a3fbcbb25cbfcdb3eb38e2f984dd6f28a89f8dd9c73d062f0d3088f822ac92b2083e14f9e2df7908cf5e872b22f33ff78f4fdfbddc7f9f5df114c8dd52f738cbae625ed7d56a8aaa16410170fbb40ccb78fe1286b7789561e3234124be80958bf8c353364c803ffbc3495350540b5110fc5e64e9909edf029bf1bfcea232cf9204900e5f5cf3b43c2ef304864ae15931cfd6200ad4ad863ff43048c2a2a45ad09db44a12c1001cb32c0519c45e92e0b4cfb1c6c94ff0fc407699e4a79acf7951bc4c11be69380acbb06e0f5ff1828dee09bcea63859cfb7c1597a3e1d9c91b821a8e0f35a8862614f8b6e8764f6b4252f89c1d98ad2041c122cb5f84f3a522541c1960c25a541154506e9095a2864edcadf2fdf6b025c9c169a4e39c8849787a592ed93e7b74d1d66e586c560de248415cf0f27dbce259552a7db1188b831e5cf272544fcd876c3825fa8bef8b389df3a3213c124d8eb5aa811cd55133ba4639cff32c1fd13b9429bd9fb0bf1e1cc807b78a9848d0ad42d25346168b4e2151049708f917655c2721490ccc387a346c1be86adc6ab843979cd64fbbf589aa83e17392cd4384aab989a30e1aeefa2728163aa37e0a1d7af3b33695f1292e1a615e8a95480ea0aaafe6d96a9d70a2e3887dbc3dd4cb70b1f03d3f49b1475a836d79caa96f855d997845cbd685f61c56479ed8e0ab0d4ddce1507b9a02b003c73acfca6c9e25c74b50fd603b68222b6172becef212d817da8d89b2b7c0de98df786b3b3a05c64ffa023b66935aad2f7358a14ed24526ebb985f0e1608a0bd660dcc85b2b205850a8f349ac1be9652be9a084d9081fc72415f0e71941145223c293870fcd298900004e70e7f1853eed516fb4764610a711fff06641b48cd9f3237b31a06e35b28bcbb4003eb4a066390faf0c656fa895dbb126a6f7b3743468268318df53c1a7813a955c4c42bd5fd784910a7358abc681ce3eb1cc61cf0ca5d5d99fdbc686d08493f3e88756fc1be861be1a82f5f1134f14a3041e46712e9f83f28ef3b15a8aaa040b71aaa9cfcbac9a2fb1e06c1da1b9288a6e4d324ee61e2272be0203db49875d54131165376902e696931050db3c8f8bab861473f85a23441d3052bb6055e71aeb65b538858549e1f7fdd1f0ffa5bcbcc9f22b9a6dc331aa8d30190d9760190e0dc856f575c315cbaac4aeb9a1ccd1adbb22d7caed7d2123ada5c592305f9f3ecec2f95594676b606e41f62e3476c537b32ccc23e97edc6e15462217cd652e95620f8a71e24442414249801ff599a2aa6c6176a15ebcf0d817265880bde1d26a0bcaac57175ec1044e4e50070989ffec7e80970083b27c07148d1adac68a35088ea6688a296b24925e15a46d43f068c53a447ee80db880cb30e7c29b4564ec262e978129d3b482a8dc788d381c4634e23e5e58f212f119b8ba731ebd131d386e88ab6b10c6939f264ced56c3e22eb17e0753bfe45f83cb46b9d20982c29ed8f43b69c6cefe1417d2e6e8453188238761b5ad95f3ba03e0175c6c69f378870649056c69ce33797ce09a526fda4967ebe2293b985825609afb8a4ed21f37252fde6765983801de54e516881fa2084611d0371486f04487bbed35cd8fc9b4249fdec15893a79a3d1abc59139b82b3773f8061b02ed10074982930af4f16ac2ac24bce84c187c6f1322cd80cac38967298b51ca6778d2385b91ef1396afb68626282597f13820b5a662c2cae28f80476428edf57e11567219b2fb378ce03f663453a22cad26149754c545065565d228a158baa1c89c2f52f0e13f4af2a700c8a8cb40c2f112dc54d48c55888808812dc31115ae36c11e745c9aee3222e03f61f4b9ecac099c000ca6a8d11399b208cf135b8006c9591760b530cb6e56c096f050b2fb30952257b6fe2f8a382850fedae3dad04750691f52b52854b4136af56e8900baa70d627e19c8fa6a3ef9fc2bf7f7e0a1e1cfe563c18b795e0db6f47f0363affe7e1c58371f0e0fef8d33fe17d3a6183fb8f0663db98bdd7567699ae060940d4a0ad70340027117d2e504d3760653e6483c355f8611f84888a9e1c3c78fced8327df1d1c18662de31825744c5520e8a1c282672af67d26703d787400deab5d195f68375477b7161a027b59e1cd3c54f5396966d56873fad5b4fa7f8fb0e4def31423bc67ef4e50e56729f05be071b93d6624831a516d0a5a40a521e158e2dcb49f6ec0525d8db6535e10e07057ca84c34a5a103f1fba8004ea6d94e32c696d8f97d0baea7a9afd320c810933c707450e4b402b1ea9d49a1dc941dbe4a92209fa3a6650748e281f3aed3f3f24f65cda311d1123ffd0b498bf47a42459d43528fce68bc99ad66d2b5e42ddb1faec186cb38a3286e6ebd63dd5dd7834f1574a0ff79c58509aca0c95edc184cd296c7ae0a6a28925cc576bdc52b943d7b1a187475eca01f185bb6d7c217150f9d117e051f05f255a2d18a5028aa688daa103bb02cfcd27bf348fba15ce7163c2a11f56f4d03c8ad1d75bfde078c1ea013d6d1712dd5c293120364298fd268438069ee022a3436a51717c291147a8af738ba425a6bd37df24a20944520a66d69b9bf42dac533c2f37506dec93a27a736edb92453dcb371e2cb43100e67240a63190ff4b083e19acdc2398047f630f042f0942b58381430efbdb8442e695916351d55a16a67767d39a85ed6f5b03f3367ecbe661395f82c9ec63adc5148f1eb0bbe000f41910f872c683775aac2924d267ca88b0f2ae8b75138cde8928e1a4f49bc700b8d3148609725c87ddefc5c58bd5badcbc99fd0eecd3bd9db13143cd90bdde210ba271965ec560898062cad951278474f182dfb318dcb4091b7a08a823f33a2e8a7138c921f8a0002f4c842f40c98639f720afa3eff8f797706df87f689482299de5312f8c79213818803d5c8c545c6347dcc5349bea9765fada73410bf1dc29ba6334d611dcf190e05acb6c4fa891b2ad9bb4ba476e22df7336d33519a6144cde719a1a1b4ed2de699f7967ae124756f648b5996b78058ed09498ecae027d59dfca816b9e1700be6bef6535a796dad2a2886fecdaa0b645b6739b72c36bd746f57d32b355b9d1dab1bbecdb6db324438f3ac9c1d476d077140f530a0c887a09f334bfce4e454c59a5c0e5ba61a244b6703ac0e0cf0dab34e20b8c510dddfe1c425ca5b8cd71e873ed6cd43073680b90ddc326bc988b325bafa16527e61a083d32470bb419e0e5cd7112165f813531c8cb9fc09708775c723762caf2911b213d7853d322ab409d384ab87f44c4d41bba1c191d0dea63da22f3605ae7f12acc374e4c0ef6196388d8dff27c0ebe37c65bffec817ca4c6df6eb70da2c8ed23939af05ba16237566d08d7735c5900020c7aa79ce0fa249a98ba00141a0e4dbe92c3b04832507cd08c5b81a05980bbbc1a6b1dc6851e2e6fdc0addaab05c7aad96e6592b95d837df78dc6f05a871c48f885b6e034b48537635ec1944ad6b484d6b56ebd2462bf02d0bb7cc12e596def9d7e6a9ad0f7a31d69efc5b19ebd705483dba11dbd82ac29e69aa79113eeeeada11013d93188b02e9c574e9b2ef3b8442cd95fa9fea824204585b7fe9d4cbfeaefccab70e431d2e6e29ed173836cc56177d7f267b9218d69a5ffd6d6f1967585ea2d7142437b8a377177bb0c2308beaec0a5f76112725574d55a4b76e366d59c904d643cd65c32e4abc752aeb3d98dc9e1e37eb61474725b6f3830baf30c8ecebadd220576759da73491e0c7c2b7233ced0bca7b20ad2d1475d62c0ef9815658e61b6efdc8b26a668bc7675dc1c62b15d62561b1903a6f0a52f5346d218a74ae34e166de34f1fe6f4e60c8fe2f29497b8255e7839339d8269429bdb98b5841bd55077d3ba85925babb50c2129672010d0bd6daf2665990882b3772fd27096504ae7c855dcecf86392ba0bd3dfcf4ebac90000332f4cf2a1ef1e2be6c17abccc22bce64d58cf1986209588938afde3f4cdeb004f93a497f1626345fdd40ad9baa41dc1251dc0289eb28f18ae294101eebf0799a4b4bcf53a8945eee3948e3ddc2a3eb170e0d759e10c5b4e90a00935e2f2e5fbe577df25eaa330ae7fe487b8ee4a74b247a2b78017550e6fd98a8ba33c739141a78f59936a7caf162efe471526855bca27b6708fd9a74f56f7c5ab1b2548ec449770432f49da1c0930c74b3ebf42186f1a0c97f30d935fe2823efbac58e73c6ded57f73c7de64b20eeaa4466ae3bc342dda3bd1341a838762667ff9169197b395de7a6b39b18130739d4c78c1fde4810ed2f5096a8c1687bd883b382bf7f754ad1074d006481bf237686bc7ef2c7dd891f40896c80e61b56eb448609d549b2d973b4a14bbc47eb2af2dfb54de157db96c47b29d07730b6ec739c96795080ca2c47c3092afe70ad28ab0f8a25f72100158d11413bc46e29fdd1d8193f702e32034c3e1ef8a285241f1dc14ae5b0873eb03a0680781f97092539bd6b1e0dfcd03f66d106819b432c8c925cebe260a075ca9179bd6b7e926769929887f61aef3c94a26af4d39b18b7456ff86c8d3aaf998f303771362afaad9d72c67431e695388b23d180d987240faded16eff4b567a989ccf2d775dc8e635723c7d4bf019726bb099a23104a2bf547a39909e9598f58771eb21154d2bb2eba3230ef155dbf7c9e51cd3b88675557fc6ad2d9b5f5615bf0deb9dabf8b0a923885e504d335514d4bdf5be9789f2d949e67267c96953c467127ede4e6a77232a387fde93acab1f3218a9e8ac5d17f749d9c9103c393aed156790e86baac713fe01f60f589461f61f2d5952c52b00960df8b0fb0547528790976ca9385c8f83182385a0cc7ee6f4b581d2fe3854810a831358fb57400030f02bf88703f3eb80f9aea6d4e4473f33c4fcdb62fe065c576e446633cb416477f28a7868a65763374e30aa32dc85ca3f851e5171e98daa4e18ae8c7a81d96001ed9851e836be855e7e8ba61be3eefc5998aadfcb75ab4b48b6ac81b2c710741dca695331ba52340e78caed45346b1b3d5d9214a7b280f910562d0a1acaeede1cd48c9b714192afe2d7a911466a0360a7a74f6333a6c76da1e00db0676f8e39d525387e18e31161165299fb0f8706f47a16a42968e5e5950ad7a73a9bccf7510b00bf6bc154994edd15d4d603b4ef02a6e38c19ec7176ab05e57fd1dfe765db9edf1a1052a29b76d157c6d39d2ab4f6fc4e4116cd1e375552c47f632e899e0aef43103aedf3cec2bb4199e10aa71f98c1d752fc4ab787aeb1dfd28814709d701eacec914838161ea9e3b485e6ab0d1797e3cedb973a476de2f448e1e8749d23d0eb5fa5046bd1dbd91a122a4e059e17e2da9a709aa176e3310ad6c8c25f4140d91826acb862fe4cf03f4f9d8f34658ebab07b64ac73ce161fea2ce78ed346bd4fb0c343acf75aa95db5688acad163dd59b12251ec36b91c76007271b7b588b32574975dd78d05778db687ba7081764f5c27b731e6dae64601a3b685836769e1c902307783c519d75d647606a383d83d5eb8b60a2ea6ebe88aca145dbbcbe885207c425a1a3b0f5987db4ad5b15dc9b13eb4bfff435759e36f912d615468649e940253789d1d656b22d9c00016ea790121b1431eebf0ffa904a90784995d25287af6fd7fd99f335d478d84ddbdb300f57457005c02ee9db86bdbbfcd327f6d7bbfaa128a03dbd106cf60b7821e0b55962bf55de3f6a32f514a4f7eeced9d7ef321a2b9d7d16b700adb32d06b4468e65404b044e71b1a01a33ebfcc22ed327b06213d873b8d1f02253025d2327169751ec6e2eadb5c59168cb6d87289d1006c147a1659e4a42ac1c7bc7ac93770c38c938d4cde106c6a12f6c2340c06a1ac5eec3b0defe153887f691fda1d01a0063d7a672542788015353750a512fd8088d73403a874c6e387adaa3c2cfba867325af19f55ce86d27a23daf81758424ca6f3e47423d6b22a15d4bf3eeee305e215218f3b99610cbae05abc337cdbc6b2ce69b98a63ce2d14cdfc6a6a98d32991535529249c6634fdc54a0239fcc61385321850d0713e6d9e712a3d8adc97ba9ad2f134cd2ef0b11d2e25835b55cb5af242ebcfce1edc9cf7ca3f169ae8b0adaac0d541ea651b63aa5f492d19383097bf25841ad2fa6b4cf7df66eeb6aeabf7e43ecd7e3eedbab30bfc47b31f0fa08dca548f03bd8bcbc5e51450e42bd73b19d13b510549e5ba02c91a21b3c3eaf37ea76ff97220c63d0afb9d847f3e6c6d7d392f629c5592c8709423749713aa7b5bb5941181c293c8329e21417380cbedc050e82d0ae838a4667dde6bd99cdd85cbda6b193ae213694e7e22731ab8fd8b707ffffbb43a32cce61b9ce729c318fbe7bf2b76f8d259b30062f93f0b260df007e81eb615b6f3c261bc351e0396621ae5fb35637bb318952c3ef47da0f634b9c40ba8d5871e9db96a5b8493dc7d42fd33131036a30edf338ea3e20e20c290cc40523d31ac3dd04d51b65a77305a3ed9b9fe12cabfc0e3cce4c82e8b5f5a4de7a070afe242d797e0d7574a2c41e3d6ed25378b9695809838ec2099bd5742889aa21a58c8ab34b6daa2a3ac11260e60430d381259a67127cec1a7d5563ca47b2da73594db2604fc12aac7dc26bc5ad0dbca25e8b59547dde5405ee28bc21e3c8c39b3068273d36ed9c04ddadb7089eab086c1af0ec7252e3a55472256892b8ce24ab44accef3da4895644861a933beed06c9d858a92d268add686c3fadd4c6126117ae704b69acf63a11169ec2529d8a44a702d6df7885398c20c91396561a31517c1997c504816ae748de3e6a9fcb92e80f6a5ae88f40c08ed42353f431c92ec58770464d8ff1da84a6e4d1416dc362cb4cbfa2016864fb12b3de3541a6c663d5aa4a780ab285b5f604d933411bfdf9f4893d91ab0d65f056a5d85a1f80359cf29bd33a4678b30415cd46a2bc8eb33e63f041ccb39621a2564dbca064340eca4c92439404055e9a3f7aac6832d9f4c32389418240cf0517c0429705a2f509c39e014b349a1a2f409b0b12a4cc5e65373c3f06558d0a4de5987eb54136fb5d9587b4d10b249af81505538152da52ac208d066130e832a85c39346137615c4e58bc5a816710b68b28dd9323d2aae822bf42fc5000ff005ff139d851ab359ebf2faaa454eee2c64b62f3aef46ebcd99a1de9f77c3508759f85409f1185e6d22449c3b8749bfe456d8b1e0152ac3ddeb23ab768da5bea550aee597c515fa2f3b2b301e6986f460d9390678e9b096439a36b220b67c3becd9e7aa45d9c6dd1e27e4d8b4f36027fe85637a5a4e1b93618ba0538a78d25bc4da7e103ae8ff724db0c0f533edd75a89c9b0594b82d5a77a48cedc4f52d1cb70fefd5322d797fab5e652e37a8866908a0a17e81794da8314e6c14a76b9c457456da3175a918b4c4cbf8038f46cd12a1d56a6d24e36ef59aa0599ce211c5fef498760c3da485a6b1757c86ef01f39ea816589e8355f6f85bf640fd63852709727ae4003d74b5dacda5c763bc8d70c8fe1eef425a1f9a3e83985ffa11d349c56734ffb3bb79f558758ebf1753cb03d6196e93b315c75f78f95f246774e65cf9d3c14d13f473e46c17cafa90f43962d68b964e223ea3f5ab3f41c8c02dcccb3bca9846bcf3a49fa7d130b90937c5eb6a35e3f957916fe7250d2af9bdd8f41658fc85c97511ab1e60a58cff3027735ef29952c1a6e7bf4d7ffbed626a869e052cd8fcf4a131e38fd8130f6794de3bb9330882608ad10d815098ec2af2fdc763195d9f0eb62f9d6098a371fdafc945efb1e86d4c2434e71ad71e5d6ce315e6aca49f37331d112901da5e77fc97e9e5042f94f15113515021bee6a36146116ab1d9dc45974a86f891b2a76c78ac6c09cac6e5afa0b56632fef2c53ca922ab84e250e6c6a13cc804b88fe447a5895b052d5fadd10606c067427830e87434404b75c0d2cb7d8ae71d0df438fcb9c489579f0f9e3f9b52cde7f56f4d74b1a94ae33f2ada4b5298d4c5a33f2aa80cd4a597bf20254a2f9238bd7adae220364cc0b95a81f55d96393a8a659e984289cf82fb206d05cf8ba04a8b65bc500eece04fcdfc1a26eebd68baaaa7cfee55fd125792837aac92082f1a271b1ab8ed04169401677f45a8b8dc18dca21d72db915302c9ea8e58d311efc5b0f26e72fa6d04daf7295898e43c8c3677228f1c7f3f7ddb69c0334284f08b72c7e12c938c35fc317c5ecb0bec14671a508c6dc6eafdf823eda7e8fe4fa8f561d379d62dd56e0adc67db5342f97d1c1d0d1fb63d766c2f80e25dfb3a57730461445a651764af1efa2646afaed62f5ff63f6d5f03a971e46fa017959e71688874966cbfc21e5f5f74c6d186cdce0bec8ba1bde49de5099410be6059ae922fbe06977866129af83723e147fc6a8da3209ee37924ebf13cc90a171edab4379edfd6bff7841cfc6f000000ffff010000ffff78a09d1f38760000") gr, _ = gzip.NewReader(bytes.NewBuffer(bs)) bs, _ = ioutil.ReadAll(gr) Assets["app.js"] = bs @@ -73,7 +73,7 @@ func init() { bs, _ = ioutil.ReadAll(gr) Assets["favicon.png"] = bs - bs, _ = hex.DecodeString("1f8b080000096e8800ffec7d697bdc3692f0f7fc0a9893439a57ec966cc79357917a578733510edb8f244f369b273b0f9a44772322091a0025f7289adfbe5500efa3c9d665cf4e3ed86a92205055a81b4071efc9f1eba3f39fdfbc240b1d06934ff69eb8ee27e3313912f152f2f942938da34df2747be739f98e5e88293914724e68e4438b484b3e4db4908a6c28c6885e3072f4fad5f9e9c9e1dbf3d7a76764c603b639c2ee0e828098ee14914c3179c9fc1179ab181133788d2ba244223d463ce13302977371c964c47c325dc260e4c7937357e965c0b0af807b2c52381cd5c483a7534666220190786460f8e1e4e8e5abb39766f8d127ae0b58217224a0d17cdf619143a2b94be378df51cbc883e1a3b9b9e52146220898dc77ceb227475a060ef102aad4be838d02412f1cec92517ff209217b21d394780b2a15d3fb4ea267ee574ef160a175ecb27709bfdc77fecb7d7be01e8930a69a4f0306ddc2882c82b74e5eee337fce4aef453464fbce256757b190bad4f48afb7ab1efb34b20836b2eb60071ae390d5ce5d180edef8cb61b1df94c7992c79a8ba8d457a3194df442c8468b804717307101500c1e6b2fd1847bd8d342b2d9be33a39778398a8190934ff005cd75c0263911c9efe4fa1ae7f915ccef2b186963f3e6666f6c5be503d8cea64268a5258dc79e52e3fc6a14f26804779c140ee406b5604c5b08cd35d1cb1890d0ecbdc697cd1342a6c25f926bf3939098fa3e00e44e85d622dc257fd98edf7f9d3e9b01d2ee8c863c58ee12e75b165c32cd3d4a5eb184395b24bfb1450e24507b8b281a2917b899cf6c17379f983f49f0ff165fe6238654ce79e46a11ef929dd1972cacb41d21b06e2822a1620a0270dd06cb8f2c0ac416f95144d483bf20794a00436e11e708c4863309205e01847937b52128301b72b78f72e34fb4615d2d27c83acda748afeea7339890eea779cffeca9e5b9fe63dfb3915a642fa4c5ada4522aae11588b9a89179976c7f5d9de9d21dd38dfb6531e1b1501c256217790a64f2b23e0057da8d843b4d4029e87c2873db309c8b0c6741abbd0888054918e5eff85cc50185b9e411f03a73a781f02e323880b5ad24033f66fc91338e519bc03ac58329f52ee612351e8e2224803f9f6e3c7df6628b3c7dbe8dffed6c7e5da5a0a43e4fd42e7956423ea3cf4efc9e3c2fee67847c0ab79f66b76fea78019b45239f82e2a8f179c06600ed76c1e815f476b68bdb86f369c0e7487f44f2eb7e5a6504ce147193be3817e4090f516bd248b74802d888fcb5ab05d7cc353283af5e81a2a968832b66a9ff7c7bbbb527bfa1582cfe4f41adf441e18f544883c0ad50b113a0f4e5ff0c99cf29d908e9fb94a67f79f197f8fd66de412a576065635013c0d2137ba72c7d796342c67f06de075bab0925b9ae2560c934aa6d369a8f76f3b6e4cf4014095a660a8695c40ba0b402a1228084b822385553c9e885423b1c08785db2580a782700161c2bb09060d0afb85e947bb4fca446e4cfe3fc768d0812c894cdca4dce8e7b6323826089c7d6147fb2673034f607bc18722e62101549d001c07b11bdcced38bdc427f60faa97eca7cf663409c0dc821fc04c3b3ea7c6685a5bb2e7f3bc13b491149096e933b4422015d531dca90467c999ecf1709e3d41bde510253d34632e5eb93b4fbf32d69358ebee3c7b0ab6d5f09efd3d9e90dc98ee19b6c93a5b701f14a8fb1eac5d757c6b5812cd60f4df8158f068d26a854d7793b44586491294f0c8a853fa69043647dc587074a13818f0249e83ba6127d14c8c227655a28f69081ea316516aaaed45ee5f4d75940d803fe15f3123d63bb08e1ab88017f9401b9bd501ca449807cb7881be09c97fb9de825d4af89bc44e86f5e72c54b9e0179d802c440a0c032b7eb9973448e07f261570c5be737d5d46175b287d73e34cdedabbc084e4fab3b4f5673755229b81c69604254a8e035ea56b8a8b2f45ec8baba88a2d4d09f327a7de0ef87a3e472f13154c7a51eea586e3e4a5cfb505f0dde7d114e8b137cd591d445723b5a6f08f56c62f18251f3764515201d2a031c9412dcf2168cd53160b9cc3de895bf87e6dc6ea281cf83ec1eec0ac0bb94c9b22c455a2f64184f2310822a0ca15b394e9810abbec83272724bfe43e8acd1a508355d067566bab41a07b62de0376d6ddedc8c88753f19dc4b0af0f9a05d89893e387a4a15a241a197810d06236eb85d876773bfa8101d754ea816c3883e68b1e784e6d8f0f49413a15c93090c1644bedb230d6cb3ee9c13e5700bd374e8276ed593cd91b0336264e1c8379b1ee408725b7edd17ff8864ba5c111b8da22220a960442dfab88f0198998c794a272f93549694aaea88cd0614a7d8db4fbd4203e81fe677c7e12a109cfd534745c380e15600237f4c123285bd7d273a0030b88f9df4d87ad5a847a5ba4b56f5aed2d9e956c5aa58d09c59d8c47209e643ef3c1b37a36c969d73d00fa5d75851f97a6f07c81991da441228d374516549129631047d04bccf2241afc3cf0413d08c2e00d7f54b83b244c701a52c0d0dd4c1b995c0f3818d5ae477be3b8ca1ebde063e059f35206f829e89ca8b0eca39018e2c4d4337a5449aebb100d960a40da66fcbdd33299d51b95cbd245fab3211c6c9e04e048032fd758bfc2e4597ff862619e4d1c4d36d0fbc6a069b3e8a155205eac920717e3e1d821dc477aa72370a6564b462a45d7d7f8ca113ed8c05fa39363708ccd14c225a3daf689610dfefd01c0aef99c2ba5aed20e5a8210b6895ead9971ed2a9e1bd021a0b1ca1cba187c32ccd0fda9826fe60de24d40ec531ef9ec3d3aa48dee0778ca65878b582a8d8eb9649e99bedf894906bea17a7173d3d77d211aa4885890c20a02426f61297ca6a94e8a396805bae99be79db8570b062e79125d44c6c17d6b7f349deee17d29e0ce1823a833fbe34e7d7934b2dafa2cfdd5df5ba30fd08a6d4cd50183c9c0f60c44c8c6f535f6fb86490f388ace59490a3edb6c876e6dc8b9dfcae4ed609f40e34784b9eb492dd6b1b7c0245625bfae52536590a9a2b21456053f1368924b7663b421a6b6deb09e006a977d9b842abf426a19d9f45a69c98d08b413d5a695bae655cbae47f870d16f10616a7b8d616e504ae101f4bd6260bf9224497319a9823b39364b137e274ee36ea4ee8eaf4d96b92266510fdedf9896f78070aed1ef8477e6ec8610ca05bfa494fc75c4a34b1af02eee194c96d4d175159ff7d1e5a594e28e6469c7e143b2c53c10d3be08f9afd086066df98ffb20c2dcf4fe0d0f9802cb4f832bba54af9270cae4cd4d437f730d10a6c36f918ebe0e97daf435e51145e63bfc70e45d88b08fba3f08efe1881b60e7f7435bd3d547445a2f1089ef621e2410b42f7ff71ae2c0d73313fbdd85bedded69879a8a20deb5f49f90ed6a4e485c61305c38a559daa3398b4527779d44eca9650e1bde4809b3cce95a89dcfe3e6037d9ee01679b1cae76be0ccf7c207ec2b5c01e2efa912a88e78dac3e0817b5bafa487743f15388fb5e470178693f33d547c7ceae9ed4fa7a253ede2949226fc1bc0bd627dd27f3484846c0670fb9c20511f501a6c7028130a8fb98a14a771ff32499e55797067d8b15677699f627ae17773371664065167790529bb7759fe0495788018fccd2764be0d688c9ccedf68c447b80443b927db1e42128e4b605495c8429101e90ee032fdfe3419f439bafcbd517df7a61f5693467d269d54fe4f3cfc9502b88bbf124f7598b15ecd70ef110c39f0e408e1608b25a856fbb8cb585e1b74876965395b880554a521a5eb9b72c65049d0f4b4fe62be0a57c24be7d349b634af2976c457f63f3d78f272969d1cbb29178e5229c435686cabe38b84651b65921c5d928923be6650a787a323240e03f9232cda44cef02c5c18fe4ade601ff875993b9a32159823b178ee0cfc711d168aa2e540f018edebcbd7702787192a63b6bf1055c4654279206bb3b37379ffd8bc47ac76933720a577723108c1d310f09ad7ef9420b4d832f306b338d91634206b2e2dddce0d54647db93c8445be7785966b2cd0f4dcb41c6f36dfc907414891e4e4808e0ef9f9299fb92ca01807c104502a20ef6fa7bf2649f24a01b673cea548783698efb7c1742f66518b3d1c9191e64b853aeb137ecc83663e01e51f3a24a3cdc7de0ac224a736fd76bb39336877458c8d318bbe64e3e1938f66cb6c6e01f52aff72d35fccdeee3bb9b90a59b013fe670e8b6b18ff1423337ed81c29f3b7bff033c6eeb74da0d0185d7d9e5800bbd601251571fd3ae801607fcaedb01eabb1d5bfa6875d7fbc66add1bd0b3b68cfaa76c81d2d146380f18cae229ab8099bd46fbfb64677b7bf0daf3db1837191d17c6946cc0eb7759075f03d6bdb540adaeeed78d72f7307758d66fa6c1560ce94c8eb94a9fafda3bf160ebef75b6ff63fdbdf27048ba9d477de9f603df0764ef94cbb56a033baa44f91fccf71661085ab42f530ad2b79022ba97186fa07ea82f2c7dc8600fa0c279ef4dfbe331dfa3b4f13a746aa89a8c2ea5ce9c366a9aac7e37593a7a7f32b47bccf2ff3b47d875f61c1068d75ff923deeea5ea90b0bbfece8344dfff1a511592023abab3f9f823bc7ac0c595e2a75d601957f78de32db37b4bb59fa360f8cceebe1e052c9aeb855da7fab0072adaa2b63a915f09cdbd4259dce554452906057a60fc59220b8e6c4e735e5fc3ddd1390f19a801080cd9aef3ed6e18ee2ae5dcdcec66673e215c9b49ce223f58daf8155f323360e4e7e1cf519422bff62315651130a7182c7f0c3ba1d5e7b8befefe839ca0a848407ef4283fa68cb70f4d758a61a797b3c327e92500c9fcb4bc45239cee39bedc75e8b77aceb7a4bfd2262648489516d63bd91d038a10f82552b1515e636514313d6ecac65912e329793226df0899849d27bd068dac60e839d78b643a02ff74ecd1205c8c7308c692c13c2aa69a50fc604eee9253dbe06e40ac40df8361e6422ec7bef0128c31d2c3e54dd7adf4f84149c2954ada087298ac38f9790f03b730822d0174d47d5276f5813eb364cff49590175627e2f606f0833209b257262d605b195de210654e9d609edb66b7517980727d0fd88586fe76d73131ba1d4f7c7f7194fb60d65e7d014a36c723cfae558ec0a51814a7da14039d8499a62923e8cf6f1100183389a6fe1125b114e06c84a65e02590269c849a4b1269226850b380286d57209fd7dbe6041c0d333eca9eade1b1b940beaa4e7c6cca1ba0ed2c8bc494688ec485a817fd1e61c6f615e25a3218f6622477f526e7a08d6ac6523e41b236fe48ae65e4886491712d9b1da4e14b263bc190278523707305f3e49b1c97b4b836cd63679c5ac15e716e13d336fa32e384f8e3b21e4fe3be0b380ca3940b0c4346d857e205742654f2c0ee911e9120f9acd2927a0dd359f71cff0698d0dc9e7a14fd5e2eb7252b6d82bb259f069c9285c01ed09fe8776b8569ac85ce2f2339a8cebeb70694f4a14160e4b5c643b50e1616ee5ed3bb68c0b81462ee884701a51f04d6d158c77729cf5e78c5b8969b0451fb821d559aa2f7390f3516db319f5590b9ee6a1eb731a885416dc60deea3dda87693dac0e0fd2b641afb05eec62f1bc76206c21aef69d27082cb0d3cbf7e0b51951ab74943a9145d980c5f3019d0eea13bdfd964eeb3e4c03b7862fba3713324caba4e04f272d20869cf6d2ccd38aa5107c21db0b65fc3b73f7fa0b902ed768ee2fb0ec4bd6d12832e1ede8d3f41c066e5c6b79ea73a997cd958dbd804ec1bd2f0806835b30814327568a8ec1c860abc6bb3c8af164729a206a50b84019b93d4b389738df209a9695ab8993532a5666a860f6e281b02412577010b034aec78a67ef128ebb320dfe2e0ec27db08c06bcd69c75163175f0c40a31377989060c15412f8d15673d82d28ead84b765c7db970e5a26d14ef0efbfb74d3044c788054088e7c9233b7368418d7a21c674b69503740cd34f485a41c221a9d80bfbd82cdf99ee46e40c29a04c7943549df8133a15a66c1e28930d6e76fbfa9ba3ce158b26a6ed228f4cbc714b0240f0f3d3026c90ad3805be021e82c7465be482b118f10e39a0600a249aea8a8658e61c3d10075e0356aa60af70eba516621dbc3a66d0c8ef28e7d815b25a9d473c122b0c7cd3804617f7068921e82b2331fdc0184e02a833a07cc194294c10087161a56f444e341e7c4e02df10937cf9d4147fa41e32212ed6c09478b8f263e759cc08b815f0ccb2556412f76acbfa75aac16f53665e4a39ae830ab5d0bc7b0daca96f07aa478a7b308d7244b761b57a8c034062614e23ee3bdf8a30db17936a44ecaa4d1f0e5280d4ee056d577471b39845590d9dd97a1d91d26098711e52659fe9052f48949d33f4bdea7523ee9fa8d4ae9361b07590fd5c83b2fe1248c93d432a082f3115e9b7990b5cefb6ee1792bf18f4b67390c37aa6e5ade7e2a551d1b8ae4621f289415c348899c3e35d5bdd340713a3a0025550ee3193c6d5a08916188e79c4c4d6c05f4b5b3c96652fdfeb0c96132578b8652adeb719b6d6d92bcda0256fde430789cb2b4e93e68684d6f5b3962c743b2b0d4add2229d670075b327d83eaa5e409eed2013b7ac9d24a5155ce2e69ea4ff353c4774ffa9dc1782bd27e83d0c8939366cb0d808ce79952da0c2af2128acbbef3a64718030e00b4ddd3435397b91c672c98393d0899d487ad5f83b558ca1304dc0a61f1e0625ee079247dbba38f4d979dc8f5a4502bb9d4aca44c7f7088ad3ebae070cd907060bdbb66bcb85e94d8b646d437cefd859078c6a837842c2ff6b43d6f5bf4696b3724142d001ae1cf5a28daf2b4231435635bd7c03804b67993dced152dfa4c4d413cb4fd55d721cd26ab9a8a6d304356b3a42384ed71174eede1b052bc9a44fc5d827ba14040a1794cd1118ef69df1fffc42dd7f1cb8ffbdedfe7ff7efa35faf77b65e3cbff974dce95f183c87059ba6697b90d0325579bcd5f2ac0838cfb0d01150c766db4053ce6cb296e4b5979623f2631a5fe17d05938121962dd46afd4cb323b333ae590f6c1bdb58f21ac566c21759669c3cdeb3adee75e07278d7c5ff6d200d0df26e0353ca5ceb829447c5b69a557992375e3c2f423be39906e0826db647775b596867a239e401fce703b61ba3cd2d13de910d77d33cc5030b52996f1d6cfc7db33246142c5750a5e1e1da9beddb0feea6e5b0acd72a3d679ff76bba5a1094bdea94b51cde5847c1990eaa2aee9fe3e342c9656accb6bba522cbcbe214faec51b5932570977eb24f0b0d85d7182e55955296e531454130fc02e0991c919f38e825607a4f321388f119e1bac8763034082382c2a239d0b794e0d8f8e76696644b708f2a5599e4e047216679e51db287ec5d1c5e33fd6016d1dc1edd992ebd7aa8c49f0db18f9158c375d170a91b126cddca8f7931dc8d69c56058386b9a768aa1793a30ac3dad56af68c4b425e1b7d53a565a8215aaa15df7f52724ec3177cceec6126259dc9f4e665284c8ea78009d8458bd5b44a5f430a878ac535a6f502458af52a9527844339745302f699e2275049a798a15987cf4935c2980d12ce4d755f2e351271a9348e9f864cab59df734974fb03ca1c9efa23d47bf2e9de0f4fb4411f9e6e0dc7c4ac86ab29634d30af8d699bece852ae5d8aa1ca62887c96276e50deb435626af7c74a9e7dc5215a67be11305313f4a9919d0ec5c2d4e89d48f0db59f197a18fe383370d95ac2081a0aae295d6205bb64353063bfc6e4b7dffc3fa2da15c7dd12285be9ee5db3a3a721fe46f88a161f4ec963c2cdc7a9c5bd9098f10f635c4eb590990f965032523abf61cb305add002c809ea68f01804d8699cf92151bbbeec92b37c7c4fa683cc47db72f7ecf58dce5c0975bacedc2172f3b13fc3f9be0954aa9ecc2973ab01b764ad72b9c751b5fade648db49ee16863cda77761e2fab50a66b9be75e7e5e5bceb6951cc05300ce2b181338169774b7d082d9efd9dd2d642e03b0d2856e70481dcc1cc422804e1f62847bbf817e136a98d87e807f168985ceac8a534d70af19786311cba0bfa5d37f0f4b2ca92618b239a865b1bfb04aed4bfe8c547288b8a3d0446be894f25228c48dbd9b33e3e34ecdf1de28cd5361f4b734bad3c30d7ae0d3e2b7d22e99995e43d5906a6f01b1220486c1327f1b335e59a6ab7b63f4232c2aa59f95a9663c4b0cf3c7a2d2ba8b4a775e441afca59f0fb088947deda6630529fd229afae856901a0e506525a7f886cf3dafd5f465341edeef6df54df0180c8bcc4e05dcb44bde40702f6040621f90be8d17a667ebabe0ac17ddad9345d461fcda6ce051a3123c8f1632fe48df9fb1c8ff7e1a8308bd4ef45ca0e1c0d38a4085906bb2f13d3f1cabcd612428f7b69e7756224305a44723c429c34f2e986df3a0e9cfccd76d307799dd211b438950efe9d6846880f488c4488f0d54e891debb15491afddd812a4dd81e8d306fa8049f8505a7e00833a58141815b09c80d448991f1b9b227c308d3e8efd6646942f6983ac456e2b4228b1431b1bcbd6995c9ba6aa4d4e15d144919ae8f3a8b5a3f6166d6608eb3cd6bf93a49c9da608b83287a19196fd559918b299b98da5bf7955e7928d6cae07d831bffea54217877401ea1ecd33f59418c0a5ddfd80fa9df92f5aa70ff2b715efa618855ac679baccd7b8dd73e22e67b286a5a54c9db37d19b1642e2edf568587ee341c9d791bc7824cf3cf5bc9dc95fdf9edcd21bcfba58d317870147f9e88fa6e6de2a0cd610d983442f704f893da487ab4a7218bea68b5b206b877e44074aa92b21fd566cb387437da7b4ab5558c779a306e605281faf4669dbdefdedf9f99b33b3f80858b46915c5ce7f38eb5128e9d463c37f03357c663e077a28c59562b28564e679fa78a026aebc72cf246cde5fc871dbed75a5b1b927f8cd09f99ee5467e638f9604765091023dbedc76bf72779ebb34e6ee055baaf1b3675f3ac0a974cef04cfe4a9f7fd869623c6698e9e63727002fae9438aed371d0d0f49c6551736cd6f8366b253bccb41d73c342b0099a8b454c9a7a4e6d95401e781e1f4a440e22112d4391286266ceac0998b3f86d1ae6744dafe5f483f92c1fddf195b5bebbfe6fb296b06efadd72a8341cda91824fe4874ebe131a3000cffc9f17e25e2f217f1004e28a744ae67fdc2d51dff8ea368b3cb98c71c7425226305776939a4f79b034275acb2b84927a17e6ac1e30450c5da174a92df32e51fc1fe981551ac7f922307431cbb79e4a1cce140d07458b1ddb0d553e5631c9f7c8c55284062e730ed66cb549678cce29af7dc5bbb2cf23ffe55ed220616e2283a2de0c0e5bb56676dd9acee792cdcd5e573cee89eb595ebafd2f998224074b422f811826b4a39a5c5f43b7b51a586bda9fb2e14957762d75de4876c9d955f513e1a507641f8648704f6f7a5de69116498b6565cdbc18a0280666ef1fe39cfc4e7e53b6fab87d08284af608bab35cb73ec59ac29d58bf3dbd1fbdf973e9ab41b7559b457dfb7cddd2c3daf503611ca4375f897b539af62bfd7673e28a6247d86855b9195b62a6565a94948bd0a46567f0f38cc27e9e1174060bf3b2629dc58cd3e2c5f5d2c68536b65f77c8b626ce7023430630f274c4e12e0589c09b07a6e6d1c6acbc5bb15410d2f0b38b1a60c0545d5f638f277894e317fa2bd6c2297d71281fcb3eab569234c563b3323c3373441d5fcf7e63d94d0acad5dc6ebe99d1282f5e499a60a7c23c1b9d81b235250e4defe6aaf2059cac60a65ff04c56d7b254acb2514ce7602a92ba912df885e2d32abb9459a1ca3a29214c8f19332c762a586615834c792053ee47691714bd70779e7e358a715b81d24bec05ac09e60d0297067c1eed1277e7450cfee715f7f562df79f1dc210b862433bfc793a298d6de147cf05cd9953eb090a9b8c54e065beeac574ce591889776363ef7e0e7d7e4e9f6ce73f21dbd10537228e43c3fd3331368bf7151e80893121c245848b59bdb89aeadfd7d5f0a4b2a9e3296573ba01218f690b3a94917f1c6f3c897601b8e936841c3d606522f12490edee371c3d3973f91336f1102295bdb26bee4e08d1c26fa026c8707bfdb9a1db2889c717f215a073c0472fa98f259f080c7b50e8ad26d4d45b7366dbe03e152786a08cf4ce189f82630df819e21c79c85edd4b330c60c8982c5be652bbea74b90c6b32408f8256d1de56f8c690e80d088462c1a8871f967073f1605c778e40589d9965ce13e2566facad4a691c4b88e66b32096c011b312371610a415fb2af1ff5c0460f046c0df63eb25fd55e08689b9a4618883fc004f13348918f36f913621794ad2f730ed079230aa94ebab8f891eda94eb69e25d306dc6bda012dcbe48a8316898f7a0426a37568d7c4c230ebc0d0088183c4c3660703c7c349a0b310f98a949188f55047eecd29d0b2041febb7bd41d83ef996db80edaa5428896ec639310f028c4d9cea4f83d0e64d23dfc332035024f4e226fad317f4b7e4bc698e00870078833a95e770ff89c1cc16444a895c90fda5f6b4c70c37d2d115d76c9027f0a04aedd5931bb5b207712c4cfa73221e792e3af88ae33fc25072f3a1abfa3b85c57bae818745d3e46d9001bf99b4ae5e7c05e7f77d68dd4b66b286aa770ab7f0e111fa6a7e070831b456383953339ccae5770a91de81c622bd090f59132c554f80578a13cf0d4b435d0296eb8cb76f49bdd87679e4e3a1abab9e6bac52b2eba9b6cc060bfbd4b985cba4f47dba367fdad73b28d7f53e38286fdf0c571ad01f8e9a62238b8113a0472fd2f000000ffff010000ffff99e692f5339d0000") + bs, _ = hex.DecodeString("1f8b080000096e8800ffec7d697bdb36b6f0f7fe0a44d3c59ed794ec24cdf4756dddeb259dba4be2c776a6b7b74fef3c100949a8498201403b1ad7f3dbef3900f745a42cdbf1dce987c422b11d1c9c1dc0e1deb3e3b747173f9fbe26731df8e34ff69e39ce27a3113912d142f2d95c938da34df27c7be725f98e5e8a093914724668e8418d504b3e89b5908a6c28c6889e3372f4f6cdc5d9c9e1bb8bb767e764ca7db639c4ee0e7c9f98ee14914c3179c5bc2179a718115368c6155122962e23aef01881c799b86232641e992c6030f2e3c985a3f4c267d897cf5d162a1c8e6ae242e98491a98801241e1a187e38397afde6fcb5197ef889e3c0ac7072c4a7e16c7fc0c20109670e8da2fd815a842e0c1fcecc2b1767247c9fc9fdc1795a72a4a53f20ae4f95da1f60255fd0cb0176c9a837fe8490bd80694adc39958ae9fd41aca7ce5783bc60ae75e4b0f731bfda1ffc97f3eec039124144359ff80cba85115908ad4e5eef336fc60aed421ab0fdc11567d79190ba50f59a7b7abeefb12b4083631eb660e25c73ea3bcaa53edbdf196ed73af29872258f341761a1af5a351aebb990b51a3e0f2f61e17cc018146b37d684bbd8d35cb2e9fe604aaff071180122c79f6003cdb5cfc61912c9efe4e606d7f90dacef1b186963f3f6766f646b6503d8ce264268a5258d46ae52a3ec6918f070086f06091c480d6ace98b6109a67a217114c42b30f1a1b9b124226c25b901bf39390887a1e00e44c84d622d8257fd98e3e7c9d944d61d2ce9406dc5fec92c1b7ccbf629abb94bc61311b6c91ecc516399080ed2da268a81ca0663eb55ddc7e62fec4feff9b7f998d185039e3a1a345b44b76865fb2a0547788c03a8108858a2830c04d132c3fb2d0175be447115217fe02e7290104b9450647c0369c4900f11a20ccbaa90c4181d890ba3de41b6fac0de96a3946d2a99722beda4ba7b020eda559cfded29e1b4bb39ebd0c0b13213d262dee421156e6e58b99a8a079976c7f5d5ee9c21bd38df365bee091501c396217690a78f2aa3a0057da098533894128e86c28f3da109c83046741ab348489f97110666d3cae229fc25af210689d39135fb897291c40da9693811e53fac808c7884d209dbc6042ddcb99448987a30809e0cf261bcf5fbcda22cf5f6ee37f3b9b5f973128a9c763b54b5e14269fe26727fa405ee6ef53443e87d7cfd3d7b7d579019985438f82e0a8d0b9cfa600ed764ee8a5e9ed6ce7af0de5539fcf10ff38c9afbb7195223815c475fce25a90673c40a94943ddc009a023b266d773ae996378069b5e83a02949836b66b1ff727bbbb127af2658ecfc9f8358e982c21baa80fabe53c2622b4049e3ff0c98c729d908e88704a77f79f597e8c366d641c257a06523101340d263fba6c87d596542467f06da075dab092599ac25a0c9348a6d369c0d77b3bae4cf8014095266028a954473c0b402a6223009714d70a92692d14b857ad817d05cb2480a68e303098e14684850e8d75ccf8b3d5a7a5243f2e751f6ba820409684a57e53623c7bd916141d0c423ab8a3fd93333c4228f5f5515faeb2bd06746998ff746506ef514583be44244c05292a0a180ef427a95e97b7a8525f60f8aa1f4a7c7a634f6412d43f7ccd4e3336a94abd5390682a41384820272645286da0ab8a73c8633916054016c3c98a52528df06444917d59d834fcecef3af8c9625d60a18bc780e3ad8d0a8fd3d1a934ce9ee19f24a3b9b730f04adf301b462797cab8062cd60f4df01a950346ed4d6a6bb7152239d49ec17e69162a7f0d330763671a3e971653828fa389a81586227e1540c43765dc08fa90896a51661a2d2ed4366874d74980e803fe15fbe22d68ab0061d988a97d9401b9be5018a4898f98b688e360cc97e39ee9c5d49f81b478374d69fb340650222ef04782654a04058fecbb9a27e0cff33a9802af6073737c5e9620da56f6f07e377f62d1021b9f92ca9fdd96d19c966a09145410193239f97f19accc59322f2c475589e2d4d10f3a741b51ed0f56c86d6280aa2e4a1d84b658ee3d71ed716c0f79f8713c0c7de24237560718dd89ac03f5a1a3f27946cdc8085710948338d71066a710d41ba9eb148e01a762edcdcf32a2b569dc281e711ec0ed4bf908ba42a425c466a1744c81fbd2002ac5c338b990ea8b0cb2e783244f22bee21dbac0035680f7d6ea5bbea05ba2b661d60a7dddd0d8dbc3f16df4b740fbba099832e3a397e481caa79ac91807b012da6d34e886d7777c31f287a4da5ee498653a83eef80e7ccf6f89018a41311f7031954bbd40e0b22bde8e21eec7309d07ba3d86f969e79496219e00f502fd61c68d1e4b63eda0fdf70a9341802d75b4484fe82808b7c1d123e2521739952542ebe26094ec93595211a5689ad919b2aa8109f41ff533e3b09518567621a3ace0d871230be1378601114b56ba11cf0c07c62fe779261cb1aa15a1771ed995a7bf317059d56aa635cf6414a23e077328f796081bd1867b86b1f00edb3aac08f0a4b7831c70810e22096c69a2273aac88431f037e8154683620df620d8aa2e386bd0c21be6e60e09625c860430344b934a2626040646b9ebe1de282a934727f8e8a056ac941e760a1a272a28da2824027f32b18c1e9593ab26448da47ce0b629ff306858ccf28bd263e12137adcbccc166b10f8634d07285f44b449ef6870d73f56cfc6db281d6373a579b790f8d0cf16a193f38e8374703c23dc4773202676a3967245c7473834d8eb060037f0d4f8ec130364b088f8c6adb27ba3ff8f70700bb62732ee5ba523da8094cd8c47a956ac6b42b596e80079f462a35e822b0c93092f7a7d27c536b105fc2c43ee5a1c73ea0415aebbe87a55c34b888c5d2f0984be69ae5fb9d98a0e129d5f3dbdbaeee73d620b9c7821856e038ba738be1734d759caf4123d075db3cebc4b99e3330c9e3f0323406ee3bfba36e74f7ef4b017546e8419ddb1f6bf5e5d2d04aebf3e457776fb53e402a3611550b0c2652db3110211b3737d8ef29932e50149db102177cb6d90cddca9073af91c89bc13e81ca8f08735b49c5d7b1af40259639bf2a521361908aa2221796193f65689271766db43eaab65ab11a286ae67d1bac2a362195c86df2acb4e486059a91aad3e04c73a96c2bc2c279b74284a5ed5486994229b807d0f79281bd52902489652402eee4d86c6178ad731ab54f6afdf9daa09a23221676ccfb1b53f31e269c49f4b5e69d1abb01b872fe2f09267f1df2f08afabc8d7a7aa32531741dc5675d78792da558132dcd73f8986431f3c5a4cb43fe2bd4a17e53fce33e903033bd7fc37da640f353ff9a2ed49b389830797b5b93df5c0384c9f05ba4a5afc385367d4d784891f80e3f1e7ae722e8c2ee0fc27d38e4fad8f9fde0d674f58450ebfa22f61c8c83f88276c5efde821ff8766a7cbf75f0db5e9fb688a910fc5d8bff31d92ec784c4353ac3b9519a863deaab9877b2ee22624f0d6b58b3460a334b8daea593dbdf87d98db73bc0d92687cb8d2f43331f899e70cfb0838a7ea40afc79c3ab0f42458da63ee2dd60fc0cfcbeb7a10f56dacf4c75e1b1b5ab6795bede88a7bb2471e8ce997bc9bab8fb64160ac908d8ec0157b821a23ec2f258201006751f2b54eaee292f92d9a675a8dfb559716eb7737fe27abe9e8a33032ab3b98398dabcabf904256d2e0614992df006c7ade69399d7cd1189660789b604fb22c90310c84d1b92b809934fb847b80fac7c97fb5d066db62f57dd7ceb84d5a3e18cc941a37c229f7f4efa6a413cb527b9c71ab460b77488fa28fe640072344790d5b2f936f358931b7e8760673154891b588520a5a1957b8b5286d079bff064b6035e884762eba3e90c4392bfa43bfa1b9bbf3e9da0a49d5e1a8dc42707e1ecb33354b4c5c1340ad3c30ac99c8d2059332e93c3d311910104ff1194a907653a37280e7e24ef34f7f93fcc9ecc9a8a6401e65c30843f4fc3a3d1545daa0e041c9dbebb7704b8519c843b2bfe053c8654c792fabb3bb7b79ffd8bf87ac7493572064feb2108c60e998b8856bf7ca185a6fe1718b59944483101035e716f6ff169a3a5ee4968bcad0b7c2c12d9e6c7c6652fe5f92e7a483c8a58f7472438f0f78fc9d47c49f800403e0843015e077bfb3d79b64f62908d531eb68ac3de38c7f3c07321bb228ce9e8e41c2f3cac156bec743bd2c3187896d43454b18ba70f06cb90523fdbf5d69cb8cd20ede7f2d4c6ae9893cf7a8e3d9dae30f8c794eb5d5b0d7fb3e7f8d663b2e430e0537687eeeafb182b3435d31ec8fd59dbfaef61715ba3d31e08c8adce36035ce839933875f5944e053418e0eb1e07a89e766ce8a3d15cef1aabf16c40c7de32ca9fa2064a461be23a802b8bcead2b82c867585c2d1dfedde82b0c87ee6c6ff7de957e17e1f1a3e35ccd920d68bece0ef9bdcc626fa549944f045415797588a36cf4358e02d443674b861c8c8fb94aca979db778b03dfb2aabfcb1675f2aec13a2e7615788fec0f360b26bc57fada8c18e4a91818f66af8b2000c9db155d05ee9b4b11de8b5fd82d1aaafb501fd337046071c93b7709f0f6f05152791514d5a44c8a9342678326449a4d8076b4b4f4feac6ff7b829f0efec905749b3875f5e6df2877bde89d53e5e7ab5cd8338ebff1a4e18a2023a5a5b73fce18d3de05e4cfed3eec78ccac7ccf19539eca59aaf5d302cb387b5873e0b677a6eb7b53eeefd8b2627af8ae4374273371716eb5cc228b8ac800f74570b68c191cde5cf9b1b783bbce0010331007e24db1d7cbb1b04bb4a0d6e6f77d32ba2e0dd4d2567a1e72facbb8b8dcc0a18fe79f86b170547b1f906469105cca5074b1ffd2e7475d9ac6fbfff28172e4a1c90dd54ca6e35e3eb4393f4a2df65e7f4ae4af20840322fc99a51f3be3b6e3bb7dd112e5f0b2ec8afa48af10f12a185695476473045f0f962a9d8304bdd320c991ed579e33c8ef0f23d19916f848c83d68b61bd465630f48ceb793c19827d3a72a91fcc47190423c9601d155375287e30177dc999adb01e104ba6efc230332117234fb831ba17c95df4bae956287e509470a5e226841cc64b2e8adec3c00d8460330b1db55fac5d7effcfecf0337d2de4a59589781a02eca09483ec938908d85a46960c88329754302c6e83e1283c40b87e80d90506fff6903231b21d2f887f7194d960565f7d0142369b47168c2bdd984b66905f82530c6412869f268ca03dbf4500600c3c9ab44a94445280b11198340c6401a82127a1c6544b9ae426e0100856cb05f4f7f99cf93e4faebc27a27b6f64a69c6327b96666eee0b5a04666555244a437d8f2f9e7752ef0158654521cf2702ab2e98f8b550f419b359c9b3c35fc46ae696685a433699b447a0bb7750ae9addf740278b1370330db6d496693f59604c658d3e2e5ab965f73847666dd866d709e1cb742c8bdf740673e953380608151dd12fe80af844a4bec1c921bd5051a3467594e40ba6b3ee5aea1d30a1992cf038faaf9d7c5186e7eb46433a7d38252b806dc13fc0ff57025e39179c4dd6a54193737c1c25eacc8351c66c4480fac4261a6e56d1b9b1d864025076442300929d8a63669c67b394afb1b8c1a9169668b36708dabd3285f6a2067a3da6a53eab186799a42c7e3d417092f38feacd17ab485499aad160bd2d641abb09a1b63feb2727f6c2eaef707cf105820a7d71fc06a33ac56ea283122f32c03f3973d3aedd5275afb0d9d566d98dadc6ab6e8de54c82049aa823f07495e32a4b4d7669d96ec9c6083f4e894b1efccdb9b2f80bb1c23b9bfc06c326947c3d0b8b7c34f936b1b18446f28f5b8d48bfa46c89e4f2760dee70883c12d9840a163cb45c7a064b056ad2d0f23bcc89c04886a18cea78cd49ec69a0b946f269a24b7a9b0d3a09003cd60c11cdd036689256ef82060895f8f89d4dec71c0f719af93b3808f740331af01ac3d5a9c7d442134bd8dcc4256a309418bd305694f608423bb21cde14186fde35685844bbc0bfffdeb4c0e01de32c0042bc7e1eda95430d6ac40b31aab329cbe0c010fd982409270624617b618bcd6e9fe96e48ce1103ca644d44d1893fa15361b2f18130d9e0e670b0b7396cddaca8cfb499e5918837ee8800707e7e9a830eb289acc056c03bf358698b5c3216e1bc030e533079174dd246832c73ed1e9003cd80944ab3577852530bb1cabc5a56d0f0ef30a3d825bc5a5e47bc412b0c7c139f8697f7068941e81bc331ddc0184a02a853a03cc194c963e00b7169b96f484e34de938e7dcf20937cf9dce494a42e1221eed3c092b8b8e963d7594c0998155066c92a34817bb565ed3a55a3b709338d128a6bc142c5356fdffeaacbdb9ee291e2914d231cd16c582e1e231f2631379717f707df8a203d46934844ecaa491ef61280d41e1d6d1674513df745510c9ddbf41ea1d2a098711d12619fca05d78f955d33b4bdaa6926ee1fa9d46e91a1b37590fe5c01b3de0250c95d832a702f3114e935a90bdc04b7e617a23f1ff4ae6b90c17aaee59dd7e2b511d1b8a546c1f389805d34b0d98047bb36696a06267a41f95441b8474c1a5383c65aa03be612e35b037d2d6c4e5a9636bed7152c064af02ecc447c68526c8dab5758418bdeac87161417779cc6f5530a8dfb670d51e86652ea15ba4554ac600e3644fa7aa557c902dc85fb78f48a2589a5ca945d90d49f66978ed70ffa9dc3784bc27ebda6910527cd091d0019af3f25b8e995132610575dd7538fd007ec0168b3a587aa2e3539ce993f1d744cc8843e6cba1b4cdd525c20a056708b7be7fe02cb23ee3a4c7d6cba6c9d5c4708b5144b4d33d0743b8758ebc939872bba843dd3e3d5fdc5d5bcc4a63da2ae71eecf85c42b499d2e6471b3a7a9bc69d3a7a95e1f57340768883f2bae6843698b2b6ac6b6a68131086cf53aba9b136074a99a1c79a8fbcba643124d5615115b238634c5498b0bdb612e9cd9bb64057f350ef9fb188f41018342f588a2211cee0f46fff30b75fe71e0fcf7b6f3ff9dbf0f7fbdd9d97af5f2f6d351ab7d61e6d9cfd934559b9d8486a5cafcad86b2dce13cc7bc48801d1b6d034939b5c15a92a56a5a0cc98f897f85ef152c06ba5836afabb533cd01ce56bf6635b0ad6f63d16b049b715f649170327fcfd6bad7818bee5d1bfd3781d4d7c9bb0b4c0971ad0a52e615dbe457c545de78f53277ed8c65ea8309b6d9ecdd6da5ae9df1e69006f09f07b3dd186e6e19f78e6c389ba614ef3748653ea1b0f1f7cdd218a1bf5882959a856b5f361f3f584fca6116b06572ce96774bba8a1394361d14a51cbe5845c0990eca22ee9fa3e35cc8a562ccd6bba320cbb2e8e4f2ec51a59345709b7cb2a5b984c2677497ca42298df2981c22e6e4710c6436243f71904b40f4ae64c611e353c2751eed60a8108604994573c06f21c0b1f1cfcd34c816e3f154aa52cec16f4d4cb3443d640fc93bbfeb66fac128a2793d5c1b2f9d72a8409f35b68f1059fd65517faeebe36cddc98e79d5df8c699c413f77d6546d654353dad3ad3d2b27bba8f9b405e6b7c93d966a8225a2a159f6750724ecad788cee46127c593c9a4ea6520448ea785f9d0498ec5b8485f03088784c6b5aad900758af13ae5278a333e345502f499c223104ea718a253379f28b5cca9751cffbd79621e451171a8348c9f864c2b55df724964f309ba189efa23e47bb2e59e0e4b34721f9e6e0c27ca1c84ab28630d312f85659bed68d2a35b0493c4c0e0f13c56c8b1b56872c2d5ef1a653c735a7324cf742270a7c7ee43233a039b99a5f10a9de326abe62f430f4716ee0b2a9871134645c93e9c43276416b60c47e85c56f7ef97f44b42b8ea72590b792d3bbe6444f8dfd0df3e5353e9e90c7809b874b8b672131e21f44b89d6a2133df41a164a874f6c2666db4b20148002d4d0f1d001b0c335f3bcb0f76dd93556eee8e75e1b88ff96e1b7ecf58d466c0176bac6cc2e78d0763fc3f5de0a542a968c2173ab007760acf4b8c75eb5f2da748db496616063cdc1fec3c5e54a188d726cbbd585ed9ceb6891fc05200cacb09132816b774b75083d9cfe4ade7321701586a42d728a40a660662ee402785e8e1deafa35f871a16b61be09f456ca133bbe254133c6b06d658c852e8ef68f4dfc3164b2209fa1c0e6ad8eccfb552f3963f23a518229e2834de1a1aa5bce00a71a3ef66ccd8b813731b384ce254e8fd2d8cec74f1801ed8b4f809b62b6696d76035a0da9d83af088ea1bfc85a63c42b8d74b51f8c7e844da5e42b34e588678160fed8545a755369ed4da4de1f06fa089b48e9c7715a7690920fada927b7835433804a3b39f9277fee79afa62ba2f1f0766fa36d82d76058684e2ae0a15d720acebd8001892d205d072f4ccfd656c155cfbb5b258aa883e8ad39c0a38605781ecd65fc917e3867a1f7fd2402167a1beb9940c581b715010b01d764e37b7e38529bfd5050ec6d35ebac808612488f868833865f6830c7e641d29f9b8fe160ec327d4336fa22a1dad39d115103e91191915c1b28e12379772794d4fa5b032b75d81e0d31a75482cdc2fc33308499d240a040ad04f806bcc4d0d85c69493fc4d4fabb335aea903da60cb1893b2dcb22468c2f6f5f5a61b2aa182974b88e2029c2f5a4a3a8d51b66660fe6383dbc96ed9314b40dd63808c3d7a1b156074b623145155369755fe1958722ad14de533cf857c50ac1b73de208459bfed9126494f07a6abfcf7e47d22bc3fdaf4479c9772496919eadb232edd59a3d21e27b286cdaa99277a7e1690322f1f56a382cb67850f4b5042f1ec9324f2cefc1f8afef4eee688da75dac688bc380c36cf4471373ef143a6b38d98358cff14c89bda487bb4ab2df7c4d177798ac1dfa110d28a5ae85f41a679b16f6b59d92ae96cd3aca2ad5669e83f274254ad3f1ee6f2f2e4ecfcde623cca249aa2876f1c379874049961e2bfe1b88e173f3f5d04329ae15930d2833e549714f495c6a72cf28acbf9fcb51d3eb55b9b17e26f8f4847ccf3225bfb1470b0cdb2b49811e5d6d3b5f393b2f1d1a71e7922dd4e8c58b2f0740a974c6f04efe529bbfdf6d62bc6698cae6d3138017774a06cea0e5a2a1e9398da266b359e153aea5e830d376cc0d0bc126482e163269f23935650279e0757c2816390845b80844ac885939b32760eee2374998b315ad96b38f66b33cb9eb2b2b7da6fddf642f61d5f0bba5506928b425041fcb8f1d7c27d467009ef93fcbdbbd5a40fec0f7c53569e5ccff582f505ffb48370b5db988f0c4425c443057f6909a47b9bf30375a8b3b8492ba97e6ae1e1045045d2177a92dd39628fe8fe4c22a8da26c1318ba9866474f250e67728c83a0c58eed812a0fb3986467e42229020397b9076b8eda242b466794573efa5d3ae791fd72aea81f3327967e9e6f06872d6b33bb6f4d6733c966e6ac2b5ef7c4fd2c3739fe174f8093fd05a157800ce3da514d6e6ea0db4a0eac15f54f51f1243bbb163ba7925d71765dfea278a180ecc310319ee94d9e8b34d2c069912ced99e703e4c9c0ecfb635c93dfc96fca262bb7853045c91e417616d3dc27b3a6f026d2efceee476efe5cf8c8d05dc5669e0e3fdbb77431d57d4f187bc9cd37e2de84a6fda8bf3d9cb824d911565a966ec6a698a9a41625c5243449da19fc9aa3b05f730499c1822cad586b1ee3246f7135ab712e8dedc720d2a389533cc890028c341d72784b8123f0e581c979b4312d9e562c248434f4eca004e8b1543737d8e3095ee5f885fe8ab9700a1f28cac6b265e54c9226796c9a86676aaea863f3f437a6dda4205ccdeb7acb144759f24a52073b61e6e9f01c84ad4971687a374fa50fe6a40933bd9c66d2bc96856495b5643a07131157956c4e2f144bcbe452248532e92488303da6c430df29cd32cd1864d20399743f4a3b20e885b3f3fcab6184c70a945e602fa04d306ee03bd4e7b37097383baf22b03fafb9a7e7fb83572f0764ce1065e6f7689c27d3da9b800d9e09bbc2f718521137df4961cb8cf592aa3c12d1c2aec6e72efcfc9a3cdfde7949bea39762420e859c65777aa602f5376e0a1d61508203070ba976333dd176b4bfebc36271c952c6f46a075402c11e723631e1225e2b0f3d09bae1380ee73468ac20f53c96e4e0035e373c7bfd133977e701a0b2b16eec490ed6c861ac2f4177b8f0bba9da210bc939f7e6a271c04340a787219f39f77954e9204fdd5617742be3e63b602e85b786f0ce14de88af03f31dc81972cc59d08c3d0b63c4102998e75b36cef76c01dc781efb3ebfa28da3fc8d31cd01101ad290853d675cfcd9428f79c2311eba7e6c8e2597a84f89a9be36b9692431a6a3392c882970c4b4408d390449c6be92ff3f133e28bc21d0f7c85a497f15786062266910e0203f40698c2a117dfe2dd2c424cf49d20ec37ec009c352babeea9868a14db89ec4ee25d366dc4b2ac1ec0b851a8184f90022a4f262d9c8c734e440db008088c0c2643d06c7cb47c39910339f999c84d1488560c72e9c99001464bfdb47dd31f33db7155799762111a245fbc804045c0a7ef6609cff1ef9326e1ffe05a01a812727a1bbd298bfc5bfc5230c70f8780264302e3fb70ff8921cc162842895c90fda5b694c30c33d2d71baec8af9de04105c79b36475b780ef24b09f47654c2e24c75f215d65f82b0e5674387a4f71bbaef0d032e8aa748cbc013af23795f0cf817dfeeebc7d52db8ec1a85dc2adee35c4f9303d01831bcc281a99590dc687e9f3122ab5035d806f0512b23a522a9872bb001f940b969ab60a3a991b9eb21dfe66cfe199d2714b4527935c7768e2a0b9c97a0cf6dbfb98c985f37cb83d7cd15d3b43dbe83735ca71d80d5f14552a809d6e32828319a10340d7ff020000ffff010000ffff4682f6258a9d0000") gr, _ = gzip.NewReader(bytes.NewBuffer(bs)) bs, _ = ioutil.ReadAll(gr) Assets["index.html"] = bs @@ -103,7 +103,7 @@ func init() { bs, _ = ioutil.ReadAll(gr) Assets["lang-fr.json"] = bs - bs, _ = hex.DecodeString("1f8b080000096e8800ff9459dd6e1cc7b1bef75314081820017a7d8ee1732e74618322251d1efd90114519060404bd33bdbb6dcd748fbb7b965a090e729b57c85584003114c057426e74997d933c49beaaeef95bfed83160883b5d535d5d5df5d55735ef3e23fcb777747e4a8ff566ef1eed1dafd45a131eec1de6b5b96b23af1c15da178a4addaf94253d73f8cd8ba5298cb3cad3b3edc7b1c073ddb860a2f39ba9587ebefde88d1bc97b1d020b3eb0a5f67afbf3ee9a9eae867eb9aadc151d596737b56b035d06b5d4b2898fc62ebfe5b7ceb5af4d349e1ed8b5713808d62b954c087877fbd19ada856f7b9dd6bad6169a2eb45f6bcf2af82f533acf2f2bbbfd64719ac186db764f26dfb567a7e27ebb4c07f47e78787c7e4997d154e6ad8a701eafe79fdb9fb77f85524510e9a52b17e44a1eea62a57cffd8351b6f96ab48fffc405ffdd77f7f4dffaf5ebb39dd777e89a39414579a168edd088be9d8d9e8cdbcc5c5857bacedc4788d6b0cb03fc009aac49f3768d284e7412f5b63a30ebd1af69986a26ccc89ae744c71d3a8e560e4890985b356175197b2a9e6dff889ed7a1957b4b5b6b1f745ff40bc31885dd9ca2904a04a3bbd506f3878297a65c342fbeddf7179aa137f501a0972fe77b0877ff5219e96c6f12dcbd300cf423745f703abe695a6cb737b2e278f660d49f9d94b44eda97075ade0c34679985ed2ab3dd3dce3487ab547aacb02420cbeda2b3756d5a6c04274d468bf70be26854babe19d824a76272277436e21f79b5f9ff1fea7b81957b66f15e93e9b86bd1c742613f8a2f184d6c62fdb4a61e776b233cb104736f69a7bc337e1920ddbf730022b6ed861d61f1501eebb48ef1e3e34f0cf4bed036e36a7cd231da2c4b8261c246cffa1c344bae19c0efc02cd4d0ca4bc26b3b488b692ae56da52e5dc6b0e68b886900f76a9c30cf98938b5f4f0e8052da0256c42d47572cb51e874f25e6cfcc2142b6dbca3c086887255c20f564132fcd89aa0c441755b02127e56ac5fd25393e56430ac1cf23842a72af0d6b3f14192e5356eabe4bb2c71f314a2aa1bfc5e278f04329614cd42ec1f205fe1d87450af9b4a15101774e2fc2a69bea1b0b1455cc10372bab330b241ce832d8d5c315f63e7622a146e722ec74438e27a7184c6916d11998d826113337e6c0120f04f3b0fd1c476fb0b2b4498284eee2e7e46865c3b77e35d94a4a785c776f99ea856a5dc9343ec7a3813317648c0a46b02d01b649dae4c55d11cbe4318b22339ea11f2b1cb80a26a711dfe1667881d4bf10774f5174a0b401ffed1f0441b591ab80d53f8565505750c3decb62c67b1a1661998e159af06f88b3373b2b04592186ae9db4649ce0c6e914b9582c59eee1e3fba3ca5a31667b0c82a013fc27ab872be4c95c9ae04df2093445271c06b77684022f8513de92adb9d2a9e70405bbab920f3eb40821611327e4d5bed330e3fc25f3dc03eaadc5c5574d201550ffa73ed59852cef084f01778cb43bf2a7659580bb864948eade95a7021074de63879c419e0291cf87f4ef5e78ac75d30193c83e5502d52f7720e909ce18b824545aa522bcfd73154d9d336ba84e4f5c71c7b165f516513a073a4b70304ab3bbef7ef10e6f5563e9a78af342c4af093ec5a1bc1e24df9060efb164a11458da7f6cee7f190efa420bf07fc3e79682eb84368e92ad931e693c6b2382dd968cd5cff58f2db6144f3fdb7eaa35c037299403031313647159619ad1e979e6d21b839fb976d3e9895cef0927dca87acbda33249fbce46a7d6d1d29901787343c7b2cd0f1b8ffbd5854c6eadbd8ca99ed568faf2d01c8ce1674015ceccc46b1b08547f17b3b955b3a710b3bfa8901871dbb5b1e68268131331c2d5473c7c767081e6fcaeed6c4b71728d8cc51e96987749df4b98aab013ebb084a50ab53e07061685ac652fa2e836ee1b53016b320d8583acda01c49bf0162cce805de04ccc004e6a5aae070dbffc3016a8de597db8037195029ac10da8010aed9e239300dbb7203788e42d3ba6c876097183663bebe7d0f6b80b97029233163305b6160ed1957b7edfb7eff86e30038486d0782ac12d81c559577eddd22794d572a91c5a365abfcd00f9d7bbd36fa6ac2fe59eca509ad1ad1f5710fd0bdfbfce8e9af717c88f4d23ad3643a651c5ae336f6533474bf651baf91456a0e342a322fc3c3ca2cb3cefd213a06949864cb4d3476249a30e357e06224cf5135bed05b77081c13771ead46924067ef9fc95970e8e4f9e7da58349c43bdc96bf44ceb32b518db3fa1eac33f81036b47fa02adb0942cafd6a3a730aea3a72f95f740a1def40bf675942411bac23914564c7184a28c52e9cac4d52c156ebe1e8003655e218c0b94ca085d4010e07d1246318efcd9b065e41e33648c599865dbf1915e442cf80e3b4a331312b7e854f37680bda97429e263415cd6b1ab0721043719c65fb330481ee6d753b4980156500ce75a9e07002de38702546412965c949a91aca890342e5a2e6d37a43b4044b36f52bee2a4880e3c884e086776df2db40a165fe5e07eea10a038397e8d162df835cc42cb08ae980b0753ee116d644a1edb647252c29ba129b0ebed07d9b9af328c4c1c6ce5ad3cef62d5c612dbe6d2c17939c4986b7d01acceade7f1f66369968e1ea29dee13eb4222f9be775799c49da6e8454d5b6be6dd83b6e89a2685fbb97474fd42db3048d197d0ecdb5ac2b1e5279a1fc1e5ed70e12853740e7eec0a57ddcd01c38804968954f34b92b743819b74ebac7d959e77f07771bb644ebe41629c815d9b412b1493b98695018e26f674cab75e60e10cb7adecf872742dfd3ad457ad24f1643412dc225e7142215265c0c32d107728da2dee4d77100d86abda3012514d85302fb838a245e20ce736130561188f0c0a82465fca2032d77282434a69c65d2e974af8765e2103184d6883a049a869357aa4542160dd0c281bfd06fafef5c7bf4d0dc4f6bad012a89e3369aecc1b77c88649b94455aebb3d940005aa73ab4635a4db6e4647c44d83e2ac5e4ba4ebd9ac772ad77eb55c7ac42513044e23aec6456effda397c526d08306b2a9992a848efdeb5befae9a7dc94b38971fb0bbfc42441f2893baa6e3e6042e3ecf697b566d8147dc9083e55a769644cd121a5b44143a8286ec0b9c564e2028e62d66cf08c0697d50c6a3ed7111e7564a104fa28ff13d569a6303c4b5ee3c85b72514159e72652084a5254aaf16631c35d5f9a122a26518fdb6067ef689f385d2345360dfbbc1556227c2f72f048875cc2df00ea5379220c8c491fc8d96b1943c1350d980a8f95d0eef2bb48e0b71cc4165cad69fad104542c7af8f7bc5d09fa843d44716ad74b0ed0be414754d5629784ae54c7d2a0b82f71b7ca24bf9d415d4794183cda31232acc42e077fb21b7d71c040a9c2fc51ea1b00f844ebc2639e8e5448a8f94861badc5d1a2aa15184948ac69283888af612882cb4ac99bdc0c9a494c15ed0f6da656a50c3b4a37369beb629a119448ac20cc14b1956bdd902c8878048454118e2dfa415928d9b94ba881c76cae4d3dc1e6191781199952ae12fcb9ad4a76b2a2fff96ac4b651b038a860320fa5f84f54ba0afc0127942bb56d8d4612fe904b098d2af25d23d3569a73445e6a38b241b4d31da592c7d30e83f62d9d4a8629095bb852afb7ef611c5c76a2e14d2311cb06a57152a5d65e1a173636dbca33289899c74969c0064b3d673cd44b4f083b1992d0db2ba93a32db95bf5c235f190ce272ecc0ce714ce09c309379a5eceb6be748e6776dc15abd35d3a4eaf44497ee840490731fb3702d1c9679c3ab3423fe8632fb78b5d745796ea586d116e2e99abfa5ca646fd37e9e6b1e5cb35761bb34cce5f0eeec46eaf3f43d4519b6c51d38708857dd8cfa1b1a7810ec828c0cb758e38c1e5c77aa4cc87acf667338e20fa6ce9118e2c872d56876095fbdd6ba39e4e9aa0c5df329f25d8e07bcf0404481e1fa56cb9ce550aebf1b1fdcb25bbf539d59a7ea16d9a1375ff94d9bc711c344f5eb44f4af46851ff75077c6d8b483fd8fd40e870bbb0c7cff7fbf1e725d3e1054e0660737a7fb6197ebfdb71ffebf84c5fbb38343893fdaffe240565b2676a16013f77f7f30d9c3a27adc7ab21d4f9a1b38bed82cdf397ce243c0420414e9ce6aa9df29f10ffbbc4f5f9986f750f959171b9e82554c176b7f832b5b6b60e56f3bc6f613caf0adf7d3f0d4e4e68b2f8696f737dffea57d6d87fe000e59a1511c98ee65c3497592c7aa47454c4386b100380634bf70f4eef39c1e9f273e9565339d78f72eaf0e1ce9b2b9f1e3998c967a196614bb938a6191db97bae1fea01b68ec3c1a49fedf8b17e717522b796a3ca8f47945cc1c0d94f32c565af0e964f53bfe16a24a99232ae1630cae87023e8ccab8068e7715471f0dfaa42a4bfe7e3246e6c023b3e89c5ce3efd2770ed57fc4667c706b81cc4344693df7fa0b9c86a3a5ff04300e9feecdd41426b8adf817f8c77cfba19edd7e8a21cc6e3e8ba6c90c67c2e7cc284a8d0c25965a4e37d7f18a696f6ac47942b7919a53f0940b04116f98b51610101fd52a162bb406487fb0f5ee6d6ee98756fe56278de37ed75b5de33c992381d9ed3038665ad0e8c22e49439d136fe31017324785edb919d8fe85bf1399ed2734a495d007b86df4abbb9fc2790426a084bfa8c911bb0642944f660cfd3d7d9f07a9a6ef93bf6710635fc925e16a78641889e73b39c9c44bdd1c2315381445b4c7827dc289f2d7827e1bc39f27f935fc61c3de673ffd1b0000ffff010000ffffa211e4e7b8210000") + bs, _ = hex.DecodeString("1f8b080000096e8800ff9459dd6e1cc7b1bef75314081820017a7d8ee1732e74618322251d1efd90114519060404bd33bdbb6dcd748fbb7b965a090e729b57c85584003114c057426e74997d933c49beaaeef95bfed83160883b5d535d5d5df5d55735ef3e23fcb777747e4a8ff566ef1eed1dafd45a131eec1de6b5b96b23af1c15da178a4addaf94253d73f8cd8ba5298cb3cad3b3edc7b1c073ddb860a2f39ba9587ebefde88d1bc97b1d020b3eb0a5f67afbf3ee9a9eae867eb9aadc151d596737b56b035d06b5d4b2898fc62ebfe5b7ceb5af4d349e1ed8b5713808d62b954c087877fbd19ada856f7b9dd6bad6169a2eb45f6bcf2af82f533acf2f2bbbfd64719ac186db764f26dfb567a7e27ebb4c07f47e78787c7e4997d154e6ad8a701eafe79fdb9fb77f85524510e9a52b17e44a1eea62a57cffd8351b6f96ab48fffc405ffdd77f7f4dffaf5ebb39dd777e89a39414579a168edd088be9d8d9e8cdbcc5c5857bacedc4788d6b0cb03fc009aac49f3768d284e7412f5b63a30ebd1af69986a26ccc89ae744c71d3a8e560e4890985b356175197b2a9e6dff889ed7a1957b4b5b6b1f745ff40bc31885dd9ca2904a04a3bbd506f3878297a65c342fbeddf7179aa137f501a0972fe77b0877ff5219e96c6f12dcbd300cf423745f703abe695a6cb737b2e278f660d49f9d94b44eda97075ade0c34679985ed2ab3dd3dce3487ab547aacb02420cbeda2b3756d5a6c04274d468bf70be26854babe19d824a76272277436e21f79b5f9ff1fea7b81957b66f15e93e9b86bd1c742613f8a2f184d6c62fdb4a61e776b233cb104736f69a7bc337e1920ddbf730022b6ed861d61f1501eebb48ef1e3e34f0cf4bed036e36a7cd231da2c4b8261c246cffa1c344bae19c0efc02cd4d0ca4bc26b3b488b692ae56da52e5dc6b0e68b886900f76a9c30cf98938b5f4f0e8052da0256c42d47572cb51e874f25e6cfcc2142b6dbca3c086887255c20f564132fcd89aa0c441755b02127e56ac5fd25393e56430ac1cf23842a72af0d6b3f14192e5356eabe4bb2c71f314a2aa1bfc5e278f04329614cd42ec1f205fe1d87450af9b4a15101774e2fc2a69bea1b0b1455cc10372bab330b241ce832d8d5c315f63e7622a146e722ec74438e27a7184c6916d11998d826113337e6c0120f04f3b0fd1c476fb0b2b4498284eee2e7e46865c3b77e35d94a4a785c776f99ea856a5dc9343ec7a3813317648c0a46b02d01b649dae4c55d11cbe4318b22339ea11f2b1cb80a26a711dfe1667881d4bf10774f5174a0b401ffed1f0441b591ab80d53f8565505750c3decb62c67b1a1661998e159af06f88b3373b2b04592186ae9db4649ce0c6e914b9582c59eee1e3fba3ca5a31667b0c82a013fc27ab872be4c95c9ae04df2093445271c06b77684022f8513de92adb9d2a9e70405bbab920f3eb40821611327e4d5bed330e3fc25f3dc03eaadc5c5574d201550ffa73ed59852cef084f01778cb43bf2a7659580bb864948eade95a7021074de63879c419e0291cf87f4ef5e78ac75d30193c83e5502d52f7720e909ce18b824545aa522bcfd73154d9d336ba84e4f5c71c7b165f516513a073a4b70304ab3bbef7ef10e6f5563e9a78af342c4af093ec5a1bc1e24df9060efb164a11458da7f6cee7f190efa420bf07fc3e79682eb84368e92ad931e693c6b2382dd968cd5cff58f2db6144f3fdb7eaa35c037299403031313647159619ad1e979e6d21b839fb976d3e9895cef0927dca87acbda33249fbce46a7d6d1d29901787343c7b2cd0f1b8ffbd5854c6eadbd8ca99ed568faf2d01c8ce1674015ceccc46b1b08547f17b3b955b3a710b3bfa8901871dbb5b1e68268131331c2d5473c7c767081e6fcaeed6c4b71728d8cc51e96987749df4b98aab013ebb084a50ab53e07061685ac652fa2e836ee1b53016b320d8583acda01c49bf0162cce805de04ccc004e6a5aae070dbffc3016a8de597db8037195029ac10da8010aed9e239300dbb7203788e42d3ba6c876097183663bebe7d0f6b80b97029233163305b6160ed1957b7edfb7eff86e30038486d0782ac12d81c559577eddd22794d572a91c5a365abfcd00f9d7bbd36fa6ac2fe59eca509ad1ad1f5710fd0bdfbfce8e9af717c88f4d23ad3643a651c5ae336f6533474bf651baf91456a0e342a322fc3c3ca2cb3cefd213a06949864cb4d3476249a30e357e06224cf5135bed05b77081c13771ead46924067ef9fc95970e8e4f9e7da58349c43bdc96bf44ceb32b518db3fa1eac33f81036b47fa02adb0942cafd6a3a730aea3a72f95f740a1def40bf675942411bac23914564c7184a28c52e9cac4d52c156ebe1e8003655e218c0b94ca085d4010e07d1246318efcd9b065e41e33648c599865dbf1915e442cf80e3b4a331312b7e854f37680bda97429e263415cd6b1ab0721043719c65fb330481ee6d753b4980156500ce75a9e07002de38702546412965c949a91aca890342e5a2e6d37a43b4044b36f52bee2a4880e3c884e086776df2db40a165fe5e07eea10a038397e8d162df835cc42cb08ae980b0753ee116d644a1edb647252c29ba129b0ebed07d9b9af328c4c1c6ce5ad3cef62d5c612dbe6d2c17939c4986b7d01acceade7f1f66369968e1ea29dee13eb4222f9be775799c49da6e8454d5b6be6dd83b6e89a2685fbb97474fd42db3048d197d0ecdb5ac2b1e5279a1fc1e5ed70e12853740e7eec0a57ddcd01c38804968954f34b92b743819b74ebac7d959e77f07771bb644ebe41629c815d9b412b1493b98695018e26f674cab75e60e10cb7adecf872742dfd3ad457ad24f1643412dc225e7142215265c0c32d107728da2dee4d77100d86abda3012514d85302fb838a245e20ce736130561188f0c0a82465fca2032d77282434a69c65d2e974af8765e2103184d6883a049a869357aa4542160dd0c281bfd06fafef5c7bf4d0dc4f6bad012a89e3369aecc1b77c88649b94455aebb3d940005aa73ab4635a4db6e4647c44d83e2ac5e4ba4ebd9ac772ad77eb55c7ac42513044e23aec6456effda397c526d08306b2a9992a848efdeb5befae9a7dc94b38971fb0bbfc42441f2893baa6e3e6042e3ecf697b566d8147dc9083e55a769644cd121a5b44143a8286ec0b9c564e2028e62d66cf08c0697d50c6a3ed7111e7564a104fa28ff13d569a6303c4b5ee3c85b72514159e72652084a5254aaf16631c35d5f9a122a26518fdb6067ef689f385d2345360dfbbc1556227c2f72f048875cc2df00ea5379220c8c491fc8d96b1943c1350d980a8f95d0eef2bb48e0b71cc4165cad69fad104542c7af8f7bc5d09fa843d44716ad74b0ed0be414754d5629784ae54c7d2a0b82f71b7ca24bf9d415d4794183cda31232acc42e077fb21b7d71c040a9c2fc51ea1b00f844ebc2639e8e5448a8f94861badc5d1a2aa15184948ac69283888af612882cb4ac99bdc0c9a494c15ed0f6da656a50c3b4a37369beb629a119448ac20cc14b1956bdd902c8878048454118e2dfa415928d9b94ba881c76cae4d3dc1e6191781199952ae12fcb9ad4a76b2a2fff96ac4b651b038a860320fa5f84f54ba0afc0127942bb56d8d4612fe904b098d2af25d23d3569a73445e6a38b241b4d31da592c7d30e83f62d9d4a8629095bb852afb7ef611c5c76a2e14d2311cb06a57152a5d65e1a173636dbca33289899c74969c0064b3d673cd44b4f083b1992d0db2ba93a32db95bf5c235f190ce272ecc0ce714ce09c309379a5eceb6be748e6776dc15abd35d3a4eaf44497ee840490731fb3702d1c9679c3ab3423fe8632fb78b5d745796ea586d116e2e99abfa5ca646fd37e9e6b1e5cb35761bb34cce5f0eeec46eaf3f43d4519b6c51d38708857dd8cfa1b1a7810ec828c0cb758e38c1e5c77aa4cc87acf667338e20fa6ce9118e2c872d56876095fbdd6ba39e4e9aa0c5df329f25d8e07bcf0404481e1fa56cb9ce550aebf1b1fdcb25bbf539d59a7ea16d9a1375ff94d9bc711c344f5eb44f4af46851ff75077c6d8b483fd8fd40e870bbb0c7cff7fbf1e725d3e1054e0660737a7fb6197ebfdb71ffebf84c5fbb38343893fdaffe240565b2676a16013f77f7f30d9c3a27adc7ab21d4f9a1b38bed82cdf397ce243c0420414e9ce6aa9df29f10ffbbc4f5f9986f750f959171b9e82554c176b7f832b5b6b60e56f3bc6f613caf0adf7d3f0d4e4e68b2f8696f737dffea57d6d87fe000e59a1511c98ee65c3497592c7aa47454c4386b100380634bf70f4eef39c1e9f273e9565339d78f72eaf0e1ce9b2b9f1e3998c967a196614bb938a6191db97bae1fea01b68046cd83ddb11fdbf172fce2fa458f2d878d0e9f38ad8399a28e761acf4e0d3d1ea77fc31449532485442c8185d0f057d1896710f1cf02a8ebe1af4595596fc01650ccd816766d139b9c7dfa50f1daaff8acd00e1d682998708d37aeef517380d874bff0d601c3fdd9ba92b4c785bf12f1090f9f6433dbbfd14439cdd7c164d9321ce84d09951981a994a2cb59c6eaee315f3ded489f3886e2345a7e031171822de306b2d28203eaa552c56e80d90ffa0ebdddbdcd30fbdfcad4e1a07feaeb7bace79324802b5dba1704cb5a0d1855d96864227dec6212e64900adb7337b0fd0b7f2832db4fe8482be10f70dbe857773f85f3084c60097f529323761d84289f0c19fa7bfa3e4f524ddf287fcf28c6be924bc2d5f0cc30120f7872968997ba4146aa70a88ae88f05fc8414e5cf05fd3686bf4ff26bf8c386bdcf7efa37000000ffff010000fffff4a98c3bb9210000") gr, _ = gzip.NewReader(bytes.NewBuffer(bs)) bs, _ = ioutil.ReadAll(gr) Assets["lang-pt.json"] = bs diff --git a/cmd/syncthing/gui.go b/cmd/syncthing/gui.go index 446c40192..2a5174642 100644 --- a/cmd/syncthing/gui.go +++ b/cmd/syncthing/gui.go @@ -47,7 +47,7 @@ var ( static func(http.ResponseWriter, *http.Request, *log.Logger) apiKey string modt = time.Now().UTC().Format(http.TimeFormat) - eventSub = events.NewBufferedSubscription(events.Default.Subscribe(events.AllEvents), 1000) + eventSub *events.BufferedSubscription ) const ( @@ -56,6 +56,8 @@ const ( func init() { l.AddHandler(logger.LevelWarn, showGuiError) + sub := events.Default.Subscribe(^events.EventType(events.ItemStarted | events.ItemCompleted)) + eventSub = events.NewBufferedSubscription(sub, 1000) } func startGUI(cfg config.GUIConfiguration, assetDir string, m *model.Model) error { @@ -92,32 +94,33 @@ func startGUI(cfg config.GUIConfiguration, assetDir string, m *model.Model) erro // The GET handlers getRestMux := http.NewServeMux() - getRestMux.HandleFunc("/rest/version", restGetVersion) + getRestMux.HandleFunc("/rest/completion", withModel(m, restGetCompletion)) + getRestMux.HandleFunc("/rest/config", restGetConfig) + getRestMux.HandleFunc("/rest/config/sync", restGetConfigInSync) + getRestMux.HandleFunc("/rest/connections", withModel(m, restGetConnections)) + getRestMux.HandleFunc("/rest/discovery", restGetDiscovery) + getRestMux.HandleFunc("/rest/errors", restGetErrors) + getRestMux.HandleFunc("/rest/events", restGetEvents) + getRestMux.HandleFunc("/rest/lang", restGetLang) getRestMux.HandleFunc("/rest/model", withModel(m, restGetModel)) getRestMux.HandleFunc("/rest/model/version", withModel(m, restGetModelVersion)) getRestMux.HandleFunc("/rest/need", withModel(m, restGetNeed)) - getRestMux.HandleFunc("/rest/connections", withModel(m, restGetConnections)) - getRestMux.HandleFunc("/rest/config", restGetConfig) - getRestMux.HandleFunc("/rest/config/sync", restGetConfigInSync) - getRestMux.HandleFunc("/rest/system", restGetSystem) - getRestMux.HandleFunc("/rest/errors", restGetErrors) - getRestMux.HandleFunc("/rest/discovery", restGetDiscovery) - getRestMux.HandleFunc("/rest/report", withModel(m, restGetReport)) - getRestMux.HandleFunc("/rest/events", restGetEvents) - getRestMux.HandleFunc("/rest/upgrade", restGetUpgrade) getRestMux.HandleFunc("/rest/nodeid", restGetNodeID) - getRestMux.HandleFunc("/rest/lang", restGetLang) + getRestMux.HandleFunc("/rest/report", withModel(m, restGetReport)) + getRestMux.HandleFunc("/rest/system", restGetSystem) + getRestMux.HandleFunc("/rest/upgrade", restGetUpgrade) + getRestMux.HandleFunc("/rest/version", restGetVersion) // The POST handlers postRestMux := http.NewServeMux() postRestMux.HandleFunc("/rest/config", withModel(m, restPostConfig)) - postRestMux.HandleFunc("/rest/restart", restPostRestart) - postRestMux.HandleFunc("/rest/reset", restPostReset) - postRestMux.HandleFunc("/rest/shutdown", restPostShutdown) + postRestMux.HandleFunc("/rest/discovery/hint", restPostDiscoveryHint) postRestMux.HandleFunc("/rest/error", restPostError) postRestMux.HandleFunc("/rest/error/clear", restClearErrors) - postRestMux.HandleFunc("/rest/discovery/hint", restPostDiscoveryHint) postRestMux.HandleFunc("/rest/model/override", withModel(m, restPostOverride)) + postRestMux.HandleFunc("/rest/reset", restPostReset) + postRestMux.HandleFunc("/rest/restart", restPostRestart) + postRestMux.HandleFunc("/rest/shutdown", restPostShutdown) postRestMux.HandleFunc("/rest/upgrade", restPostUpgrade) // A handler that splits requests between the two above and disables @@ -175,6 +178,25 @@ func restGetVersion(w http.ResponseWriter, r *http.Request) { w.Write([]byte(Version)) } +func restGetCompletion(m *model.Model, w http.ResponseWriter, r *http.Request) { + var qs = r.URL.Query() + var repo = qs.Get("repo") + var nodeStr = qs.Get("node") + + node, err := protocol.NodeIDFromString(nodeStr) + if err != nil { + http.Error(w, err.Error(), 500) + return + } + + res := map[string]float64{ + "completion": m.Completion(node, repo), + } + + w.Header().Set("Content-Type", "application/json; charset=utf-8") + json.NewEncoder(w).Encode(res) +} + func restGetModelVersion(m *model.Model, w http.ResponseWriter, r *http.Request) { var qs = r.URL.Query() var repo = qs.Get("repo") @@ -423,11 +445,18 @@ func restGetReport(m *model.Model, w http.ResponseWriter, r *http.Request) { func restGetEvents(w http.ResponseWriter, r *http.Request) { qs := r.URL.Query() - ts := qs.Get("since") - since, _ := strconv.Atoi(ts) + sinceStr := qs.Get("since") + limitStr := qs.Get("limit") + since, _ := strconv.Atoi(sinceStr) + limit, _ := strconv.Atoi(limitStr) + + evs := eventSub.Since(since, nil) + if 0 < limit && limit < len(evs) { + evs = evs[len(evs)-limit:] + } w.Header().Set("Content-Type", "application/json; charset=utf-8") - json.NewEncoder(w).Encode(eventSub.Since(since, nil)) + json.NewEncoder(w).Encode(evs) } func restGetUpgrade(w http.ResponseWriter, r *http.Request) { diff --git a/cmd/transifexdl/main.go b/cmd/transifexdl/main.go index 9ab696095..369a894e5 100644 --- a/cmd/transifexdl/main.go +++ b/cmd/transifexdl/main.go @@ -18,6 +18,12 @@ type translation struct { } func main() { + log.SetFlags(log.Lshortfile) + + if u, p := userPass(); u == "" || p == "" { + log.Fatal("Need environment variables TRANSIFEX_USER and TRANSIFEX_PASS") + } + resp := req("https://www.transifex.com/api/2/project/syncthing/resource/gui/stats") var stats map[string]stat @@ -63,9 +69,14 @@ func main() { json.NewEncoder(os.Stdout).Encode(langs) } -func req(url string) *http.Response { +func userPass() (string, string) { user := os.Getenv("TRANSIFEX_USER") pass := os.Getenv("TRANSIFEX_PASS") + return user, pass +} + +func req(url string) *http.Response { + user, pass := userPass() req, err := http.NewRequest("GET", url, nil) if err != nil { diff --git a/files/leveldb.go b/files/leveldb.go index db5a7a98f..4e8116afa 100644 --- a/files/leveldb.go +++ b/files/leveldb.go @@ -632,9 +632,6 @@ func ldbWithNeed(db *leveldb.DB, repo, node []byte, fn fileIterator) { if need || !have { name := globalKeyName(dbi.Key()) - if debug { - l.Debugf("need repo=%q node=%v name=%q need=%v have=%v haveV=%d globalV=%d", repo, protocol.NodeIDFromBytes(node), name, need, have, haveVersion, vl.versions[0].version) - } fk := nodeKey(repo, vl.versions[0].node, name) bs, err := snap.Get(fk, nil) if err != nil { @@ -652,6 +649,10 @@ func ldbWithNeed(db *leveldb.DB, repo, node []byte, fn fileIterator) { continue } + if debug { + l.Debugf("need repo=%q node=%v name=%q need=%v have=%v haveV=%d globalV=%d", repo, protocol.NodeIDFromBytes(node), name, need, have, haveVersion, vl.versions[0].version) + } + if cont := fn(gf); !cont { return } diff --git a/files/set.go b/files/set.go index cfb9cef5e..102a5146e 100644 --- a/files/set.go +++ b/files/set.go @@ -85,7 +85,7 @@ func (s *Set) Update(node protocol.NodeID, fs []protocol.FileInfo) { func (s *Set) WithNeed(node protocol.NodeID, fn fileIterator) { if debug { - l.Debugf("%s Need(%v)", s.repo, node) + l.Debugf("%s WithNeed(%v)", s.repo, node) } ldbWithNeed(s.db, []byte(s.repo), node[:], fn) } diff --git a/gui/app.js b/gui/app.js index 2d14047ca..0dd894909 100644 --- a/gui/app.js +++ b/gui/app.js @@ -21,23 +21,68 @@ syncthing.config(function ($httpProvider, $translateProvider) { }); }); +syncthing.controller('EventCtrl', function ($scope, $http) { + $scope.lastEvent = null; + var online = false; + var lastID = 0; + + var successFn = function (data) { + if (!online) { + $scope.$emit('UIOnline'); + online = true; + } + + if (lastID > 0) { + data.forEach(function (event) { + $scope.$emit(event.type, event); + }); + }; + + $scope.lastEvent = data[data.length - 1]; + lastID = $scope.lastEvent.id; + + setTimeout(function () { + $http.get(urlbase + '/events?since=' + lastID) + .success(successFn) + .error(errorFn); + }, 500); + }; + + var errorFn = function (data) { + if (online) { + $scope.$emit('UIOffline'); + online = false; + } + setTimeout(function () { + $http.get(urlbase + '/events?since=' + lastID) + .success(successFn) + .error(errorFn); + }, 500); + }; + + $http.get(urlbase + '/events?limit=1') + .success(successFn) + .error(errorFn); +}); + syncthing.controller('SyncthingCtrl', function ($scope, $http, $translate, $location) { var prevDate = 0; var getOK = true; var restarting = false; - $scope.connections = {}; + $scope.completion = {}; $scope.config = {}; + $scope.configInSync = true; + $scope.connections = {}; + $scope.errors = []; + $scope.model = {}; $scope.myID = ''; $scope.nodes = []; - $scope.configInSync = true; $scope.protocolChanged = false; - $scope.errors = []; - $scope.seenError = ''; - $scope.model = {}; - $scope.repos = {}; $scope.reportData = {}; $scope.reportPreview = false; + $scope.repos = {}; + $scope.seenError = ''; $scope.upgradeInfo = {}; $http.get(urlbase+"/lang").success(function (langs) { @@ -71,53 +116,118 @@ syncthing.controller('SyncthingCtrl', function ($scope, $http, $translate, $loca 'touch': 'asterisk', } - function getSucceeded() { - if (!getOK) { - $scope.init(); - $('#networkError').modal('hide'); - getOK = true; - } - if (restarting) { - $scope.init(); - $('#restarting').modal('hide'); - $('#shutdown').modal('hide'); - restarting = false; - } - } + $scope.$on('UIOnline', function (event, arg) { + $scope.init(); + $('#networkError').modal('hide'); + $('#restarting').modal('hide'); + $('#shutdown').modal('hide'); + }); - function getFailed() { - if (restarting) { - return; - } - if (getOK) { + $scope.$on('UIOffline', function (event, arg) { + if (!restarting) { $('#networkError').modal({backdrop: 'static', keyboard: false}); - getOK = false; } + }); + + $scope.$on('StateChanged', function (event, arg) { + var data = arg.data; + if ($scope.model[data.repo]) { + $scope.model[data.repo].state = data.to; + } + }); + + $scope.$on('LocalIndexUpdated', function (event, arg) { + var data = arg.data; + refreshRepo(data.repo); + + // Update completion status for all nodes that we share this repo with. + $scope.repos[data.repo].Nodes.forEach(function (nodeCfg) { + debouncedRefreshCompletion(nodeCfg.NodeID, data.repo); + }); + }); + + $scope.$on('RemoteIndexUpdated', function (event, arg) { + var data = arg.data; + refreshRepo(data.repo); + refreshCompletion(data.node, data.repo); + }); + + $scope.$on('NodeDisconnected', function (event, arg) { + delete $scope.connections[arg.data.id]; + }); + + $scope.$on('NodeConnected', function (event, arg) { + if (!$scope.connections[arg.data.id]) { + $scope.connections[arg.data.id] = { + inbps: 0, + outbps: 0, + InBytesTotal: 0, + OutBytesTotal: 0, + Address: arg.data.addr, + }; + } + }); + + $scope.$on('ConfigLoaded', function (event) { + if ($scope.config.Options.URAccepted == 0) { + // If usage reporting has been neither accepted nor declined, + // we want to ask the user to make a choice. But we don't want + // to bug them during initial setup, so we set a cookie with + // the time of the first visit. When that cookie is present + // and the time is more than four hours ago, we ask the + // question. + + var firstVisit = document.cookie.replace(/(?:(?:^|.*;\s*)firstVisit\s*\=\s*([^;]*).*$)|^.*$/, "$1"); + if (!firstVisit) { + document.cookie = "firstVisit=" + Date.now() + ";max-age=" + 30*24*3600; + } else { + if (+firstVisit < Date.now() - 4*3600*1000){ + $('#ur').modal({backdrop: 'static', keyboard: false}); + } + } + } + }) + + function refreshRepo(repo) { + $http.get(urlbase + '/model?repo=' + encodeURIComponent(repo)).success(function (data) { + $scope.model[repo] = data; + }); } - $scope.refresh = function () { + function refreshSystem() { $http.get(urlbase + '/system').success(function (data) { - getSucceeded(); + $scope.myID = data.myID; $scope.system = data; - }).error(function () { - getFailed(); }); - Object.keys($scope.repos).forEach(function (id) { - if (typeof $scope.model[id] === 'undefined') { - // Never fetched before - $http.get(urlbase + '/model?repo=' + encodeURIComponent(id)).success(function (data) { - $scope.model[id] = data; - }); - } else { - $http.get(urlbase + '/model/version?repo=' + encodeURIComponent(id)).success(function (data) { - if (data.version > $scope.model[id].version) { - $http.get(urlbase + '/model?repo=' + encodeURIComponent(id)).success(function (data) { - $scope.model[id] = data; - }); + } + + var completionFuncs = {}; + function refreshCompletion(node, repo) { + if (node === $scope.myID) { + return + } + + if (!completionFuncs[node+repo]) { + completionFuncs[node+repo] = debounce(function () { + $http.get(urlbase + '/completion?node=' + node + '&repo=' + encodeURIComponent(repo)).success(function (data) { + if (!$scope.completion[node]) { + $scope.completion[node] = {}; } + $scope.completion[node][repo] = data.completion; + + var tot = 0, cnt = 0; + for (var cmp in $scope.completion[node]) { + tot += $scope.completion[node][cmp]; + cnt += 1; + } + $scope.completion[node]._total = tot / cnt; }); - } - }); + }); + } + completionFuncs[node+repo](); + } + + function refreshConnectionStats() { $http.get(urlbase + '/connections').success(function (data) { var now = Date.now(), td = (now - prevDate) / 1000, @@ -138,9 +248,66 @@ syncthing.controller('SyncthingCtrl', function ($scope, $http, $translate, $loca } $scope.connections = data; }); + } + + function refreshErrors() { $http.get(urlbase + '/errors').success(function (data) { $scope.errors = data; }); + } + + function refreshConfig() { + $http.get(urlbase + '/config').success(function (data) { + var hasConfig = !isEmptyObject($scope.config); + + $scope.config = data; + $scope.config.Options.ListenStr = $scope.config.Options.ListenAddress.join(', '); + + $scope.nodes = $scope.config.Nodes; + $scope.nodes.sort(nodeCompare); + + $scope.repos = repoMap($scope.config.Repositories); + Object.keys($scope.repos).forEach(function (repo) { + refreshRepo(repo); + $scope.repos[repo].Nodes.forEach(function (nodeCfg) { + refreshCompletion(nodeCfg.NodeID, repo); + }); + }); + + if (!hasConfig) { + $scope.$emit('ConfigLoaded'); + } + }); + + $http.get(urlbase + '/config/sync').success(function (data) { + $scope.configInSync = data.configInSync; + }); + } + + $scope.init = function() { + refreshSystem(); + refreshConfig(); + refreshConnectionStats(); + + $http.get(urlbase + '/version').success(function (data) { + $scope.version = data; + }); + + $http.get(urlbase + '/report').success(function (data) { + $scope.reportData = data; + }); + + $http.get(urlbase + '/upgrade').success(function (data) { + $scope.upgradeInfo = data; + }).error(function () { + $scope.upgradeInfo = {}; + }); + }; + + $scope.refresh = function () { + refreshSystem(); + refreshConnectionStats(); + refreshErrors(); }; $scope.repoStatus = function (repo) { @@ -187,9 +354,8 @@ syncthing.controller('SyncthingCtrl', function ($scope, $http, $translate, $loca }; $scope.nodeIcon = function (nodeCfg) { - var conn = $scope.connections[nodeCfg.NodeID]; - if (conn) { - if (conn.Completion === 100) { + if ($scope.connections[nodeCfg.NodeID]) { + if ($scope.completion[nodeCfg.NodeID] && $scope.completion[nodeCfg.NodeID]._total === 100) { return 'ok'; } else { return 'refresh'; @@ -200,9 +366,8 @@ syncthing.controller('SyncthingCtrl', function ($scope, $http, $translate, $loca }; $scope.nodeClass = function (nodeCfg) { - var conn = $scope.connections[nodeCfg.NodeID]; - if (conn) { - if (conn.Completion === 100) { + if ($scope.connections[nodeCfg.NodeID]) { + if ($scope.completion[nodeCfg.NodeID] && $scope.completion[nodeCfg.NodeID]._total === 100) { return 'success'; } else { return 'primary'; @@ -552,60 +717,7 @@ syncthing.controller('SyncthingCtrl', function ($scope, $http, $translate, $loca cfg.APIKey = randomString(30, 32); }; - $scope.init = function() { - $http.get(urlbase + '/version').success(function (data) { - $scope.version = data; - }); - $http.get(urlbase + '/system').success(function (data) { - $scope.system = data; - $scope.myID = data.myID; - }); - - $http.get(urlbase + '/config').success(function (data) { - $scope.config = data; - $scope.config.Options.ListenStr = $scope.config.Options.ListenAddress.join(', '); - - $scope.nodes = $scope.config.Nodes; - $scope.nodes.sort(nodeCompare); - - $scope.repos = repoMap($scope.config.Repositories); - - $scope.refresh(); - - if ($scope.config.Options.URAccepted == 0) { - // If usage reporting has been neither accepted nor declined, - // we want to ask the user to make a choice. But we don't want - // to bug them during initial setup, so we set a cookie with - // the time of the first visit. When that cookie is present - // and the time is more than four hours ago, we ask the - // question. - - var firstVisit = document.cookie.replace(/(?:(?:^|.*;\s*)firstVisit\s*\=\s*([^;]*).*$)|^.*$/, "$1"); - if (!firstVisit) { - document.cookie = "firstVisit=" + Date.now() + ";max-age=" + 30*24*3600; - } else { - if (+firstVisit < Date.now() - 4*3600*1000){ - $('#ur').modal({backdrop: 'static', keyboard: false}); - } - } - } - }); - - $http.get(urlbase + '/config/sync').success(function (data) { - $scope.configInSync = data.configInSync; - }); - - $http.get(urlbase + '/report').success(function (data) { - $scope.reportData = data; - }); - - $http.get(urlbase + '/upgrade').success(function (data) { - $scope.upgradeInfo = data; - }).error(function () { - $scope.upgradeInfo = {}; - }); - }; $scope.acceptUR = function () { $scope.config.Options.URAccepted = 1000; // Larger than the largest existing report version @@ -717,6 +829,47 @@ function randomString(len, bits) return outStr.toLowerCase(); } +function isEmptyObject(obj) { + var name; + for (name in obj) { + return false; + } + return true; +} + +function debounce(func, wait, immediate) { + var timeout, args, context, timestamp, result; + + var later = function() { + var last = Date.now() - timestamp; + if (last < wait) { + timeout = setTimeout(later, wait - last); + } else { + timeout = null; + if (!immediate) { + result = func.apply(context, args); + context = args = null; + } + } + }; + + return function() { + context = this; + args = arguments; + timestamp = Date.now(); + var callNow = immediate && !timeout; + if (!timeout) { + timeout = setTimeout(later, wait); + } + if (callNow) { + result = func.apply(context, args); + context = args = null; + } + + return result; + }; +} + syncthing.filter('natural', function () { return function (input, valid) { return input.toFixed(decimals(input, valid)); diff --git a/gui/index.html b/gui/index.html index 1433a1d4f..9b89da310 100644 --- a/gui/index.html +++ b/gui/index.html @@ -89,6 +89,7 @@ +
@@ -289,10 +290,10 @@ {{nodeName(nodeCfg)}}